SpringCloud
第一个SpringCloud项目
服务提供
@RestController
public class TestController {
@RequestMapping("/test")
public String test(){
return "第一个SpringCloud服务提供者";
}
}
服务消费
@RestController public class TestController { @Resource private RestTemplate restTemplate; @RequestMapping("/test") public String test(){ /** * RestTemplate 基于Http协议的工具对象 * SpringCloud中利用这个对象访问服务提供者 这个对象可以new 也可以交给spring(建议) * * getForEntity 以get方式提交请求 @getMapping或@RequestMapping * 参数1 :需要访问的请求路径 * 参数2 返回的数据类型 * 参数3 可变长度为Object类型的数据,表示本次请求的url参数 * * 注意 SpringCloud 返回的是rest风格的数据,因此数据类型全部都是string类型的json格式 * * 返回值 ResponseEntity 封装本次请求的访问实体 * * */ RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> forEntity = restTemplate.getForEntity("http://localhost:8081/test", String.class); System.out.println(forEntity.getStatusCode());//状态码 System.out.println(forEntity.getHeaders());//响应码 String body = forEntity.getBody(); return "第一个SpringCloud服务消费者"+body; } }
服务的注册中心Eureka(保证AP)

CAP理论
搭建eruka注册中心

配置文件
spring.application.name=eurkea-server # 应用服务 WEB 访问端口 server.port=9001 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
启动文件
@EnableEurekaServer @SpringBootApplication public class EurkeaServerApplication { public static void main(String[] args) { SpringApplication.run(EurkeaServerApplication.class, args); } }
页面打开

eruka服务提供
idea 创建项目

启动文件
@EnableEurekaClient @SpringBootApplication public class EurkeaClientProviderApplication { public static void main(String[] args) { SpringApplication.run(EurkeaClientProviderApplication.class, args); } }
配置文件
spring.application.name=eurkea-client-provider # 应用服务 WEB 访问端口 server.port=8081 eureka.client.service-url.defaultZone=http://localhost:9001/eureka
eruka服务消费
RestTemplateConfig
@Configuration public class RestTemplateConfig { /** * 在spring的应用上下文定义一个Rest模板对象 * * LoadBalanced 注解用于标记当前的RestTemplate使用Ribbon的负载均衡访问服务的提供者 * 必须使用 否则无法访问服务 * * 默认Ribbon 的负载均衡是轮训的 */ @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
启动文件
@EnableEurekaClient @SpringBootApplication public class EurkeaClientProviderApplication { public static void main(String[] args) { SpringApplication.run(EurkeaClientProviderApplication.class, args); } }
配置文件
# 应用名称 spring.application.name=eurkea-client-consumer # 应用服务 WEB 访问端口 server.port=8080 eureka.client.service-url.defaultZone=http://localhost:9001/eureka/
controller
package com.springcloud.controller; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController public class TestController { @Resource private RestTemplate restTemplate; @RequestMapping("/test") public String test(){ /** * 通过注册中心的注册中心发现服务并访问服务 EURKEA-CLIENT-PROVIDER 不区分大小写 */ ResponseEntity<String> forEntity = restTemplate.getForEntity("http://EURKEA-CLIENT-PROVIDER/test", String.class); String body = forEntity.getBody(); return "eruka服务消费者"+body; } }
负载均衡
Ribbon 提供负载均衡 主要是客户端的负载均衡
在springcloud中使用注解@LoadBalanced,在RestTemplate开启负载均衡


服务端负载均衡

高可用注册中心
eruka集群(相互复制注册数据)

搭建eruka集群
主要是创建多个eruka注册中心
注册中心
# 应用名称 spring.application.name=eurkea-server # 应用服务 WEB 访问端口 server.port=9001 eureka.instance.hostname=eureka9001 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false eureka.client.serviceUrl.defaultZone=http://localhost:9001/eureka/
9002
# 应用名称 spring.application.name=eurkea-cluster-server-9002 # 应用服务 WEB 访问端口 server.port=9002 eureka.instance.hostname=eureka9002 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false eureka.client.serviceUrl.defaultZone=http://localhost:9003/eureka/
9003
# 应用名称 spring.application.name=eurkea-cluster-server-9003 # 应用服务 WEB 访问端口 server.port=9003 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false eureka.client.serviceUrl.defaultZone=http://localhost:9001/eureka/
客户端
eureka.client.service-url.defaultZone=http://localhost:9001/eureka/,http://localhost:9002/eureka/,http://localhost:9003/eureka/
自我保护模式
服务端

客服端配置

REST请求
传递对象和集合
@RestController public class TestController { @Resource private RestTemplate restTemplate; @RequestMapping("/getForEntity01") public String getForEntity01(){ /** * 接收对象 */ ResponseEntity<User> forEntity = restTemplate.getForEntity("http://localhost:8081/getForEntity01", User.class); User body = forEntity.getBody(); return "第一个SpringCloud服务消费者"+body; } @RequestMapping("/getForEntity02") public String getForEntity02(){ /** * 接收对象 */ ResponseEntity<List> forEntity = restTemplate.getForEntity("http://localhost:8081/getForEntity02", List.class); List body = forEntity.getBody(); return "第一个SpringCloud服务消费者"+body; } }
参数传递
@RequestMapping("/getForEntityParams01")
public String getForEntityParams01(){
/**
* 参数传递
* id={0}&name={1}&age={2} 请求路径中的占位符
*
*/
Object[] params={2L,"李四",34};
String url = "http://localhost:8081/getForEntityParams01?id={0}&name={1}&age={2}";
ResponseEntity<User> forEntity = restTemplate.getForEntity(url, User.class,params);
User body = forEntity.getBody();
return "第一个SpringCloud服务消费者"+body;
}
@RequestMapping("/getForEntityParams02")
public String getForEntityParams02(){
/**
* id={id}&name={name}&age={age} 请求路径中的占位符
*/
String url = "http://localhost:8081/getForEntityParams01?id={id}&name={name}&age={age}";
Map<String,Object> params =new HashMap<String,Object>();
params.put("id",3L);
params.put("name","王五");
params.put("age",23);
ResponseEntity<User> forEntity = restTemplate.getForEntity(url, User.class,params);
User body = forEntity.getBody();
return "第一个SpringCloud服务消费者"+body;
}
getForObject
@RequestMapping("/getForObject")
public String getForObject(){
/**
* getForObject 和 getForEntity 方法使用完全相同 都是对应GetMapping或RequestMapping
* 区别在于getForObject 直接将服务提供者的返回的数据以参数2 所指定的类型进行返回 不会以ResponseEntity对象返回
* 如果不需要服务提供者的响应头文件信息时 完全可以使用getForObject来替代getForEntity
*/
String url = "http://localhost:8081/getForObject";
User user = restTemplate.getForObject(url,User.class);
return "第一个SpringCloud服务消费者"+user;
}
postForObject

@RequestMapping("/postForObject")
public String postForObject(){
/**
*post相关的对应服务提供者POSTMapping或者RequestMapping
*
* postForObject:
* 参数1:为请求的地址路径参数
* 参数2:具体参数对象没有写null
* 参数3:为本次响应的具体数据类型
* 参数4:地址路径动态数据 Object类型的数据 可以是Map具体和get相似
*/
LinkedMultiValueMap params = new LinkedMultiValueMap();
params.add("name","钱七");
params.add("id",6L);
params.add("age",48);
String url = "http://localhost:8081/postForObject";
User user = restTemplate.postForObject(url,params,User.class);
return "第一个SpringCloud服务消费者"+user;
}
put

@RequestMapping("/put")
public String put(){
String url = "http://localhost:8081/put";
LinkedMultiValueMap params = new LinkedMultiValueMap();
params.add("name","钱七");
params.add("id",6L);
params.add("age",48);
restTemplate.put(url,params);
return "服务消费者 执行put请求";
}
delete

@RequestMapping("/delete")
public String delete(){
String url = "http://localhost:8081/delete?id={0}";
restTemplate.delete(url,8L);
return "服务消费者 执行delete请求";
}
服务熔断
熔断器服务保护机制

在启动文件中激活服务熔断机制
@SpringBootApplication @EnableEurekaClient //激活熔断机制 @EnableCircuitBreaker public class EurekaClientHystrixConsumerApplication { public static void main(String[] args) { SpringApplication.run(EurekaClientHystrixConsumerApplication.class, args); } }

@HystrixCommand(fallbackMethod = "error") @RequestMapping("/test") public String test(){ ResponseEntity<String> forEntity = restTemplate.getForEntity("http://eureka-client-hystrix-provider/test", String.class); String body = forEntity.getBody(); return "熔断机制服务消费者"+body; } public String error(){ return "服务已经被熔断"; }
超时熔断

@HystrixCommand(fallbackMethod = "error",commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")})
@RequestMapping("/test02")
public String test02(){
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://eureka-client-hystrix-provider/test02", String.class);
String body = forEntity.getBody();
return "熔断机制服务消费者"+body;
}
服务降级

public String error(){ return "服务已经被熔断"; }
异常信息
public String error(Throwable throwable){ System.out.println(throwable.getClass()); System.out.println(throwable.getMessage()); return "服务已经被熔断"+throwable.getMessage(); } @HystrixCommand(fallbackMethod = "error",commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")}) @RequestMapping("/test02") public String test02(){ // 服务提供者延时异常 ResponseEntity<String> forEntity = restTemplate.getForEntity("http://eureka-client-hystrix-provider/test02", String.class); String body = forEntity.getBody(); return "熔断机制服务消费者"+body; } @HystrixCommand(fallbackMethod = "error",commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")}) @RequestMapping("/test03") public String test03(){ System.out.println(10/0); //抛出异常 ResponseEntity<String> forEntity = restTemplate.getForEntity("http://eureka-client-hystrix-provider/test03", String.class); String body = forEntity.getBody(); return "熔断机制服务消费者"+body; }
忽略异常

@HystrixCommand(fallbackMethod = "error", commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")}, ignoreExceptions = {Exception.class} )
注意服务提供者和服务消费者所抛出的异常是不一样的
服务提供者统一抛出 HttpServerErrorException.InternalServerError异常,此异常有spring抛出
自定义异常熔断
自定义类
mport com.netflix.hystrix.HystrixCommand; import org.springframework.web.client.RestTemplate; /** * 自定义 异常熔断器 */ public class MyHystrixCommand extends HystrixCommand { /** * 必须使用有参构造 * @param setter */ private RestTemplate restTemplate; private String url; public MyHystrixCommand(Setter setter, RestTemplate restTemplate,String url) { super(setter); this.restTemplate = restTemplate; this.url = url; } /** * 此方法不能手动调用 spring自动调用方法来访问我们的服务提供者 * @return * @throws Exception */ @Override protected Object run() throws Exception { return restTemplate.getForObject(url,String.class); } /** * 服务降级方法 当spring自动调用run方法之后,如果服务出现了异常则自动调用服务,用服务降级的方法来进行异常熔断 * @return */ @Override protected Object getFallback() { System.out.println(super.getFailedExecutionException().getClass()); System.out.println(super.getFailedExecutionException().getMessage()); return "自定义异常熔断器"; } }
使用熔断器
@RequestMapping("/test04")
public String test04(){
String url = "http://eureka-client-hystrix-provider/test03";
MyHystrixCommand command = new MyHystrixCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")),restTemplate,url);
//调用处理结果
// execute方法会自动调用run方法访问服务提供者,如果服务提供者抛出异常则会自动调用getFallback方法来返回响应数据
String body = (String) command.execute();
return "熔断机制服务消费者"+body;
}
Hystrix仪表盘监控
。。。
声明式消费服务feign
service
import com.springcloud.hystrix.MyFallback; import com.springcloud.hystrix.MyFallbackFactory; import com.springcloud.model.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; /** * @FeignClient 用于标记当前接口是一个Feign声明式服务接口 * spring会为这个接口生成动态代理 * 属性 * name 用于指定注册中心某个服务的 名字 * fallback 自定义熔断器 */ //@FeignClient(name = "eureka-client-feign-provider",fallback = MyFallback.class) @FeignClient(name = "eureka-client-feign-provider",fallbackFactory = MyFallbackFactory.class) public interface TestService { /** * 定义抽象方法 并使用RequestMapping标记这个方法用于服务提供者 * 其中RequestMapping的参数 /test 应该与当前接口所标记的服务名称中的服务中的某个请求路径相同 * @return */ @RequestMapping("/test") String test(); @RequestMapping("/testParams01") String testParams01(@RequestParam("name") String name, @RequestParam("age") Integer age); /** * 传递对象参数时 要加注解@RequestBody * @param user * @return */ @RequestMapping("/testParams02") String testParams02(@RequestBody User user); @RequestMapping("/testReturnUser") User testReturnUser(); @RequestMapping("/testReturnList") List<User> testReturnList(); }
controller
import com.springcloud.model.User; import com.springcloud.service.TestService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.List; @RestController public class TestController { @Resource private TestService testService; @RequestMapping("/test") public String test(){ String result = testService.test(); return "feign服务消费者"+result; } /** * 带参数 * @return */ @RequestMapping("/testParams01") public String testParams01(){ String result = testService.testParams01("张三",23); return "feign服务消费者"+result; } @RequestMapping("/testParams02") public String testParams02(){ User user = new User("李四", 23); String result = testService.testParams02(user); return "feign服务消费者"+result; } @RequestMapping("/testReturnUser") public String testReturnUser(){ User result = testService.testReturnUser(); return "feign服务消费者"+result.getName()+result.getAge(); } @RequestMapping("/testReturnList") public String testReturnList(){ List<User> result = testService.testReturnList(); return "feign服务消费者"+result; } }
自定义熔断器
import com.springcloud.model.User; import com.springcloud.service.TestService; import org.springframework.stereotype.Component; import java.util.List; /** * 自定义熔断器类 */ @Component public class MyFallback implements TestService { @Override public String test() { return "test 请求熔断"; } @Override public String testParams01(String name, Integer age) { return "testParams01 请求熔断"; } @Override public String testParams02(User user) { return "testParams02 请求熔断"; } @Override public User testReturnUser() { return null; } @Override public List<User> testReturnList() { return null; } }
import com.springcloud.model.User; import com.springcloud.service.TestService; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; import java.util.List; /** * 自定义熔断器类 实现Hystrix的降级回调的父接口 * 注意 * 这个父接口的泛型非常重要,这个泛型决定当前类要为那个声明式接口提供异常熔断 */ @Component public class MyFallbackFactory implements FallbackFactory<TestService> { @Override public TestService create(Throwable throwable) { return new TestService() { @Override public String test() { System.out.println(throwable.getMessage()); System.out.println(throwable.getClass()); return "test 熔断"; } @Override public String testParams01(String name, Integer age) { return "testParams01 熔断"; } @Override public String testParams02(User user) { return "testParams02 熔断"; } @Override public User testReturnUser() { return null; } @Override public List<User> testReturnList() { return null; } }; } }
启动入口
@SpringBootApplication @EnableEurekaClient //激活声明式消费feign支持 @EnableFeignClients public class EurekaClientFeignConsumerApplication { public static void main(String[] args) { SpringApplication.run(EurekaClientFeignConsumerApplication.class, args); } }
Zuul网关
建立springboot网关项目

application.properties
# 应用名称
spring.application.name=eureka-client-zuul-gateway
# 应用服务 WEB 访问端口
server.port=9000
eureka.client.serviceUrl.defaultZone=http://localhost:9100/eureka/
# 配置
#zuul.routes.api-zuul.path=/api-zuul/**
## 用于对服务下的某个特定请求进行拦截
#zuul.routes.api-zuul.service-id=eureka-client-zuul-provider
# 简化写法 作用与上面两行完全相同(配置服务提供者者经过网关)
zuul.routes.eureka-client-zuul-provider=/api-zuul/**
# 忽略某些请求
# 当请求被忽略以后访问就会出现404,表示资源不存在 如需多个同时忽略 则需要用 ,分割如
# /api-zuul/test02,/api-zuul/test03
#在忽略请求时也可以使用* 和 **
zuul.ignored-patterns=/api-zuul/test02
# 统一网关访问前缀 类似配置tomcat应用上下文路径
zuul.prefix=/myapi
#配置自我转发 转发到当前的网关工程中
zuul.routes.gateway.path=/gateway/**
zuul.routes.gateway.url=forward:/api/local
# 禁用异常拦截器 如果要使用自定义拦截器那么就必须先要禁用默认异常拦截器 (使用全局异常页面时关闭)
#zuul.SendErrorFilter.error.disable=true
Application
@SpringBootApplication @EnableEurekaClient // 激活zuul支持 @EnableZuulProxy public class EurekaClientZuulGatewayApplication { public static void main(String[] args) { SpringApplication.run(EurekaClientZuulGatewayApplication.class, args); } }
filter
@Component public class AuthFilter extends ZuulFilter { //当前方法的返回值用于决定当前过滤器的类型的执行时期 @Override public String filterType() { // 返回pre表示当前过滤器作为前置过滤器 需要在执行转发服务器(访问服务提供者)前执行 通常用于做身份认证 return "pre"; } //过滤器序号 如果有多个同类型的过滤器 那么根据返回值的大小决定执行的先后顺序 @Override public int filterOrder() { //返回值越大 执行的序号越低 return 0; } // 过滤器是否启动 @Override public boolean shouldFilter() { return true; } // 返回过滤器的具体执行方法 //注意 当前版本没有特殊作用所以返回null @Override public Object run() throws ZuulException { System.out.println(10/0); //抛出异常 System.out.println("0----------------------------"); //获取当前请求的上下文对象 RequestContext currentContext = RequestContext.getCurrentContext(); //获取用户请求对象 HttpServletRequest request = currentContext.getRequest(); //获取请求参数中的token (身份令牌用于验证身份是否请求合法) String token = request.getParameter("token"); if(token==null || ! "123".equals(token)){ //设定false表示请求不再继续执行 (不转发给服务提供者) currentContext.setSendZuulResponse(false); //设置响应编码为401 currentContext.setResponseStatusCode(401); //设置响应类型和编码格式 currentContext.addZuulResponseHeader("content-type","text/html;charset=utf-8"); currentContext.setResponseBody("非法请求"); }else { System.out.println("请求合法请继续执行请求准备进入下一个服务"); } return null; } }
package com.springcloud.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @Component public class MyErrorFilter extends ZuulFilter { @Override public String filterType() { //异常过滤器 return "error"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return false; } @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); //获取异常对象 ZuulException throwable = (ZuulException) currentContext.getThrowable(); HttpServletResponse response = currentContext.getResponse(); response.setStatus(throwable.nStatusCode); response.setContentType("text/html;charset=utf-8"); PrintWriter writer = null; try { writer = response.getWriter(); writer.println("出现异常code "+throwable.nStatusCode+" message "+throwable.getMessage()); } catch (IOException e) { e.printStackTrace(); }finally { if (writer != null){ writer.close(); } } return null; } }
全局异常
@RestController public class MyErrorController implements ErrorController { /** * 全局异常页面和自定义异常可能会有冲突 二者选一即可 在在使用全局异常页面的时候要把默认异常打开 * @return */ @Override public String getErrorPath() { return "error"; } @RequestMapping("/error") public String error(){ ZuulException exception = (ZuulException) RequestContext.getCurrentContext().getThrowable(); return "全局异常页面 code="+exception.nStatusCode+" message="+exception.getMessage(); } }
消费者调用网关服务
@RestController public class TestController { @Resource private RestTemplate restTemplate; @RequestMapping("/test") public String test(){ String url = "http://eureka-client-zuul-provider/test"; String result = restTemplate.getForObject(url,String.class); return "zuul服务消费者"+result; } @RequestMapping("/test02") public String test02(){ String url = "http://eureka-client-zuul-gateway/api-zuul/test"; String result = restTemplate.getForObject(url,String.class); return "经过zuul服务消费者"+result; } }

浙公网安备 33010602011771号