SpringCloud基础概念(二)
四、负载均衡Robbin
假设有多个user-service集群,那么访问的时候到底该访问哪一个呢?
1.开启负载均衡
1 @Bean 2 @LoadBalanced 3 public RestTemplate restTemplate() { 4 return new RestTemplate(new OkHttp3ClientHttpRequestFactory()); 5 }
2.修改负载均衡规则
1 user-service: 2 ribbon: 3 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
3.重试机制
当请求的服务挂掉以后,不会立即抛出错误,而是再次重试另一个服务,如果还不行就继续切换,最终如果还是失败,则返回失败。
配置如下:
1 spring: 2 cloud: 3 loadbalancer: 4 retry: 5 enabled: true # 开启Spring Cloud的重试功能 6 user-service: 7 ribbon: 8 ConnectTimeout: 250 # Ribbon的连接超时时间 9 ReadTimeout: 1000 # Ribbon的数据读取超时时间 10 OkToRetryOnAllOperations: true # 是否对所有操作都进行重试 11 MaxAutoRetriesNextServer: 1 # 切换实例的重试次数 12 MaxAutoRetries: 1 # 对当前实例的重试次数
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
五、Hystix熔断器
熔断是消费者熔断,并不是服务端。
1.服务降级
当服务繁忙时,如果服务出现异常,不是粗暴的直接报错,而是返回一个友好的提示,虽然拒绝了用户的访问,但是会返回一个结果。
2.引入依赖
1 <dependency> 2 <groupId>org.springframework.cloud</groupId> 3 <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> 4 </dependency>
3.开启熔断
consumer的启动器上添加注解@EnableHystrix
4.改造消费者,提供失败时的回滚函数
1 @Autowired 2 private RestTemplate restTemplate; 3 4 private static final Logger logger = LoggerFactory.getLogger(UserDao.class); 5 6 @HystrixCommand(fallbackMethod = "queryUserByIdFallback") 7 public User queryUserById(Long id){ 8 long begin = System.currentTimeMillis(); 9 String url = "http://user-service/user/" + id; 10 User user = this.restTemplate.getForObject(url, User.class); 11 long end = System.currentTimeMillis(); 12 // 记录访问用时: 13 logger.info("访问用时:{}", end - begin); 14 return user; 15 } 16 17 public User queryUserByIdFallback(Long id){ 18 User user = new User(); 19 user.setId(id); 20 user.setName("用户信息查询出现异常!"); 21 return user; 22 }
5.改造服务提供者,随机休眠一段时间,以触发熔断
1 @Service 2 public class UserService { 3 4 @Autowired 5 private UserMapper userMapper; 6 7 public User queryById(Long id) throws InterruptedException { 8 // 为了演示超时现象,我们在这里然线程休眠,时间随机 0~2000毫秒 9 Thread.sleep(new Random().nextInt(2000)); 10 return this.userMapper.selectByPrimaryKey(id); 11 } 12 }
熔断的默认时间是1000ms,Ribbon的超时时间一定要小于Hystix的超时时间才能触发重试机制。
六、Feign
Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。
1.导入依赖
1 <dependency> 2 <groupId>org.springframework.cloud</groupId> 3 <artifactId>spring-cloud-starter-openfeign</artifactId> 4 </dependency>
2.Fegin的客户端
1 @FeignClient("user-service") 2 public interface UserFeignClient { 3 4 @GetMapping("/user/{id}") 5 User queryUserById(@PathVariable("id") Long id); 6 }
-
-
-
@FeignClient
,声明这是一个Feign客户端,类似@Mapper
注解。同时通过value
属性指定服务名称 -
-
3.开启Fegin功能
在启动类上添加注解@EnableFeignClients。
4.Feign中已经集成了负载均衡和熔断,只需简单配置即可。
<1>负载均衡
1 user-service: 2 ribbon: 3 ConnectTimeout: 250 # 连接超时时间(ms) 4 ReadTimeout: 1000 # 通信超时时间(ms) 5 OkToRetryOnAllOperations: true # 是否对所有操作重试 6 MaxAutoRetriesNextServer: 1 # 同一服务不同实例的重试次数 7 MaxAutoRetries: 1 # 同一实例的重试次数
1 feign: 2 hystrix: 3 enabled: true # 开启Feign的熔断功能
定义一个类,实现刚才编写的UserFeignClient,作为fallback的处理类
1 @Component 2 public class UserFeignClientFallback implements UserFeignClient { 3 @Override 4 public User queryUserById(Long id) { 5 User user = new User(); 6 user.setId(id); 7 user.setName("用户查询出现异常!"); 8 return user; 9 } 10 }
然后在UserFeignClient中,指定刚才编写的实现类
1 @FeignClient(value = "user-service",path = "user",fallback = UserClientFallback.class) 2 public interface UserClient { 3 4 @GetMapping("/{id}") 5 User getOne(@PathVariable("id") Integer id); 6 }
七、Zuul网关
1.Zuul简介
2.Zuul加入后的架构
不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作。Zuul就是我们服务的统一入口。
3.简单路由选择
1 @SpringBootApplication 2 @EnableZuulProxy // 开启Zuul的网关功能 3 public class ZuulDemoApplication { 4 5 public static void main(String[] args) { 6 SpringApplication.run(ZuulDemoApplication.class, args); 7 } 8 }
编写配置
1 zuul: 2 routes: 3 user-service: # 这里是路由id,随意写 4 path: /user-service/** # 这里是映射路径 5 url: http://127.0.0.1:8081 # 映射路径对应的实际url地址
4.面向服务的路由
我们应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由。
<1>开启并配置Eureka
<2>修改配置
1 zuul: 2 routes: 3 user-service: # 这里是路由id,随意写 4 path: /user-service/** # 这里是映射路径 5 serviceId: user-service # 指定服务名称
5.过滤器
Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。
<1>过滤器的生命周期
-
-
-
请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
-
-
异常流程:
-
整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
-
如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
-
如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
-
-
<2>使用场景
-
-
-
异常处理:一般会在error类型和post类型过滤器中结合来处理。
-
服务调用时长统计:pre和post结合使用。
-
<3>自定义过滤器
模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。
1 @Component 2 public class LoginFilter extends ZuulFilter{ 3 @Override 4 public String filterType() { 5 // 登录校验,肯定是在前置拦截 6 return "pre"; 7 } 8 9 @Override 10 public int filterOrder() { 11 // 顺序设置为1 12 return 1; 13 } 14 15 @Override 16 public boolean shouldFilter() { 17 // 返回true,代表过滤器生效。 18 return true; 19 } 20 21 @Override 22 public Object run() throws ZuulException { 23 // 登录校验逻辑。 24 // 1)获取Zuul提供的请求上下文对象 25 RequestContext ctx = RequestContext.getCurrentContext(); 26 // 2) 从上下文中获取request对象 27 HttpServletRequest req = ctx.getRequest(); 28 // 3) 从请求中获取token 29 String token = req.getParameter("access-token"); 30 // 4) 判断 31 if(token == null || "".equals(token.trim())){ 32 // 没有token,登录校验失败,拦截 33 ctx.setSendZuulResponse(false); 34 // 返回401状态码。也可以考虑重定向到登录页。 35 ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); 36 } 37 // 校验通过,可以考虑把用户信息放入上下文,继续向后执行 38 return null; 39 } 40 }
6.负载均衡和熔断
1 zuul: 2 retryable: true 3 ribbon: 4 ConnectTimeout: 250 # 连接超时时间(ms) 5 ReadTimeout: 2000 # 通信超时时间(ms) 6 OkToRetryOnAllOperations: true # 是否对所有操作重试 7 MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数 8 MaxAutoRetries: 1 # 同一实例的重试次数 9 hystrix: 10 command: 11 default: 12 execution: 13 isolation: 14 thread: 15 timeoutInMillisecond: 6000 # 熔断超时时长:6000ms