目录
1.服务保护机制SpringCloud Hystrix
1.1微服务高可用技术
大型复杂的分布式系统中,高可用相关的技术架构非常重要。
高可用架构非常重要的一个环节,就是如何将分布式系统中的各个服务打造成高可用的服务,从而足以应对分布式系统环境中的各种各样的问题,避免整个分布式系统被某个服务的故障给拖垮。
比如:服务间的调用超时,服务间的调用失败,要解决这些棘手的分布式系统可用性问题,就涉及到了高可用分布式系统中的很多重要的技术,包括:资源隔离、限流与过载保护、熔断、优雅降级、容错、超时控制、监控运维等。
1.2.概念
1)服务雪崩效应
服务雪崩效应产生与服务堆积在同一个线程池中,因为所有的请求都是同一个线程池进行处理,这时候如果在高并发情况下,所有的请求全部访问同一个接口,这时候可能会导致其他服务没有线程进行接受请求,这就是服务雪崩效应效应。
2)服务降级
在高并发情况下,防止用户一直等待,使用服务降级方式(直接返回一个友好的提示给客户端,调用fallBack方法)
3)服务熔断
熔断机制目的为了保护服务,在高并发的情况下,如果请求达到一定极限(可以自己设置阔值)如果流量超出了设置阈值,让后直接拒绝访问,保护当前服务。使用服务降级方式返回一个友好提示,服务熔断和服务降级一起使用
4)服务隔离
因为默认情况下,只有一个线程池会维护所有的服务接口,如果大量的请求访问同一个接口,达到tomcat 线程池默认极限,可能会导致其他服务无法访问。
解决服务雪崩效应:使用服务隔离机制(线程池方式和信号量),使用线程池方式实現隔离的原理: 相当于每个接口(服务)都有自己独立的线程池,因为每个线程池互不影响,这样的话就可以解决服务雪崩效应。
线程池隔离:每个服务接口,都有自己独立的线程池,每个线程池互不影响。
信号量隔离:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,当请求进来时先判断计数器的数值,若超过设置的最大线程个数则拒绝该请求,若不超过则通行,这时候计数器+1,请求返 回成功后计数器-1。
服务降级,服务熔断,服务隔离通俗理解:
线程池隔离:
5)服务限流
服务限流就是对接口访问进行限制,常用服务限流算法令牌桶、漏桶。计数器也可以进行粗暴限流实现。
1.3.Hystrix简单介绍
Hystrix是国外知名的视频网站Netflix所开源的非常流行的高可用架构框架。Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题。
Hystrix “豪猪”,具有自我保护的能力。hystrix 通过如下机制来解决雪崩效应问题。
在微服务架构中,我们把每个业务都拆成了单个服务模块,然后当有业务需求时,服务间可互相调用,但是,由于网络原因或者其他一些因素,有可能出现服务不可用的情况,当某个服务出现问题时,其他服务如果继续调用这个服务,就有可能出现线程阻塞,但如果同时有大量的请求,就会造成线程资源被用完,这样就可能会导致服务瘫痪,由于服务间会相互调用,很容易造成蝴蝶效应导致整个系统宕掉。因此,就有人提出来断路器来解决这一问题。
资源隔离:包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
融断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。
缓存:提供了请求缓存、请求合并实现。
Hystrix服务保护框架的作用:
2.演示:
注:我们基于springcloud(三)中项目重构后的代码继续演示的:
项目重构结构:
springcloud-parent 父工程,pom类型,存放共同的依赖信息
—— springcloud-api-service 接口模块,pom类型,只有接口没有实现
———— springcloud-api-member-service 会员接口模块
———— springcloud-api-order-service 订单接口模块
—— springcloud-member-service-impl 会员接口实现模块
—— springcloud-order-service-impl 订单接口实现模块
2.1.演示:服务雪崩效应
服务雪崩效应:一个Tomcat的线程数是有限的,当有限的线程数全部被占用,则别的请求再次发生请求时,无法响应。
我们通过一个例子来演示:
package com.itmayiedu.api.service.impl; import com.itmayiedu.api.entity.UserEntity; import com.itmayiedu.api.service.IOrderService; import com.itmayiedu.api.fegin.MemberApiFegin; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class IOrderServiceImpl implements IOrderService { @Autowired private MemberApiFegin memberApiFegin; @RequestMapping("/getOrederToMember") public String getOrederToMember() { UserEntity user = memberApiFegin.getMember(); return user.toString(); } @RequestMapping("/getOrederToMember2") public String getOrederToMember2() { System.out.println("访问接口:getOrederToMember2"); return memberApiFegin.getMember2(); } }
之前的 IOrderServiceImpl 有两个接口,getOrederToMember 接口调用 order项目 直接响应,getOrederToMember2 接口调用order项目休眠1.5s再响应。
我们使用jmeter发起请求 getOrederToMember2 接口,使用 100个线程 同时访问 100 次,也就是100 * 100 个请求,
之后再访问 getOrederToMember 接口,因为Tomcat中的线程全部被占用,理论上此接口是访问不到的。
1)我们先设置Tomcat最大线程数为10(order项目中的application.yml文件)
2)先在浏览器访问一下 getOrederToMember 接口,此时是可以访问到的。
3)使用jmeter发起请求 getOrederToMember2 接口
4)发起请求,我们可以在控制台看到请求进行中
5)再次访问 getOrederToMember 接口,发现请求不到了。
注意:如果Tomcat最大线程数和发送请求的线程数相近的话,可能无法达到演示的效果。
浏览器访问 getOrederToMember 接口是有超时时间限制的,如果在超时时间到达之前 getOrederToMember2 接口就访问结束了,那么浏览器访问 getOrederToMember 接口最后还是可以发访问到的。
2.2.Hystrix环境搭建
2.2.1.环境搭建
1)添加依赖:在父工程项目中添加依赖
<!-- hystrix断路器 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
2)在服务调用者 order 项目的 application.yml 中配置开启断路器
### 开启Hystrix断路器 feign: hystrix: enabled: true
3)在 IOrderServiceImpl 接口中
package com.itmayiedu.api.service.impl; import com.itmayiedu.api.entity.UserEntity; import com.itmayiedu.api.service.IOrderService; import com.itmayiedu.api.fegin.MemberApiFegin; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class IOrderServiceImpl implements IOrderService { @Autowired private MemberApiFegin memberApiFegin; // 解决服务雪崩效应 // fallback 方法的作用:服务降级执行 // @HystrixCommand默认开启线程池隔离,服务降级,服务熔断 @HystrixCommand(fallbackMethod = "getOrederToMemberHystrixFallback") @RequestMapping("/getOrederToMemberHystrix") public String getOrederToMemberHystrix() { System.out.println("getOrederToMemberHystrix:线程池名称" + Thread.currentThread().getName()); return memberApiFegin.getMember2(); } public String getOrederToMemberHystrixFallback() { return "服务忙,请稍后重试!"; } @RequestMapping("/getOrder") public String getOrder() { System.out.println("getOrder:线程池名称" + Thread.currentThread().getName()); return "order项目"; } }
注:@HystrixCommand 此注解开启了服务隔离(通过线程池隔离方式),服务降级,服务熔断。
fallbackMethod 参数指向是我们自定义的服务降级方法。
4)在启动类中添加 @EnableHystrix注解,启动Hystrix
package com.itmayiedu.api; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableEurekaClient @EnableFeignClients @EnableHystrix public class AppOrder { public static void main(String[] args) { SpringApplication.run(AppOrder.class, args); } }
2.2.2.演示:服务隔离
服务隔离是通过线程池隔离的,我们通过演示访问 getOrederToMemberHystrix 接口和 getOrder 接口,线程池名称不一致,可以证明线程池是隔离的,因为 getOrederToMemberHystrix 接口是做过服务隔离的,所以单独使用一个线程池。
启动项目,浏览器分别访问 getOrederToMemberHystrix 接口和 getOrder 接口,查看控制台
getOrederToMemberHystrix:线程池名称hystrix-IOrderServiceImpl-1
getOrder:线程池名称http-nio-8001-exec-5
我们发现访问两个接口的线程池不是一个线程池。
2.2.3.Hystrix 的超时时间
问题:当我们访问 getOrederToMemberHystrix 方法时,却发现走了服务降级的方法。
原因:hystrix 默认超时时间是1s,因为 getOrederToMemberHystrix 调用order项目时,休眠了1.5s,请求超时,所以会走服务降级的方法。而且,在远程调用接口未响应时,一秒钟内没有响应,不耽误走调用接口之前的逻辑代码,我们看控制台发现:远程调用getMember2方法之前的打印日志是执行的。
禁止掉 hystrix 超时时间。
#### hystrix禁止服务超时时间 hystrix: command: default: execution: timeout: enabled: false
但是,在实际开发中,我们是不会禁止掉超时时间的,我们会把超时时间设置长一些
#### hystrix设置超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
设置成3s之后,再次访问,发现不会因超时而走服务降级。
2.2.4.演示:服务熔断、降级
注意:我们必须把application.yml文件中的Tomcat最大线程数去掉才能实现演示效果。
###服务启动端口号 server: port: 8001 # tomcat: # max-threads: 10
1)使用jmeter 多线程多次访问 getOrder 接口,此接口没有做服务降级,所以会一直访问到请求结束为止。
2)使用jmeter 多线程多次访问 getOrederToMemberHystrix 接口,发现只执行了十次,然后自动进行服务熔断。
服务熔断:在高并发情况下,如果请求达到了一定的极限(可以自定义阈值),如果超出了设定的阈值(默认阈值为10),
自动开启服务保护功能,使用服务降级方式返回一个友好提示。服务熔断和服务降级一般是同时使用的。
2.2.5.fallback统一接口
发现:使用 @HystrixCommand 我们发现有一些缺点,如果我们在多个类中都会调用 getOrederToMemberHystrixFallback 降级方法,返回相同提示,每一个类中都要写一个 getOrederToMemberHystrixFallback 方法,这样也太麻烦了,所以我们可以把服务降级方法,提取到一个类中。而且, @HystrixCommand 注解是单独开启一个线程池的,此方法内的所有逻辑代码都是在新开的线程池中的,但实际上,除了远程调用的接口需要使用新开的线程池,之前的逻辑代码应该是还在原先的主线程中的,所以我们不建议使用 @HystrixCommand 注解来做服务降级。
我们来演示如果使用类的方式,统一服务降级的方法:
1)我们新建一个 MemberServiceFalllback 类,实现 MemberApiFegin 接口,统一服务降级返回的信息。
package com.itmayiedu.api.fallback;
import com.itmayiedu.api.entity.UserEntity;
import com.itmayiedu.api.fegin.MemberApiFegin;
import org.springframework.stereotype.Component;
@Component
public class MemberServiceFalllback implements MemberApiFegin {
public static final String MSG = "服务比较忙,下次再说吧!";
@Override
public UserEntity getMember() {
return null;
}
@Override
public String getMember2() {
return MSG;
}
}
注意点:
1>此类一定要注入到spring中:@Component
2>此类实现了 MemberApiFegin 接口,说明所有调用 MemberApiFegin 类中的方法,服务降级都会走此类中对应的方法。
3>我们可以在此类中定义所有的方法返回信息都是一致的,也可以不同的方法返回不同的信息,在每一个方法中定义各种需要返回的信息即可
2)在 MemberApiFegin 类中指定 fallback 类 ,添加属性 fallback = MemberServiceFalllback.class
package com.itmayiedu.api.fegin;
import com.itmayiedu.api.fallback.MemberServiceFalllback;
import com.itmayiedu.api.service.IMemberService;
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient(name = "app-itmayiedu-member",fallback = MemberServiceFalllback.class)
public interface MemberApiFegin extends IMemberService {
}
3)在 IOrderServiceImpl 类中添加方法,不需要再使用 @HystrixCommand 注解,因为所有调用 MemberApiFegin 类中方法的接口,走服务降级时 都会调用 MemberServiceFalllback 对应的服务降级方法
@RequestMapping("/getOrederToMemberHystrix2") public String getOrederToMemberHystrix2() { System.out.println("getOrederToMemberHystrix:线程池名称" + Thread.currentThread().getName()); return memberApiFegin.getMember2(); }
4)重启服务器,关掉hystrix服务超时设置,访问此接口
我们看到已经调用对应的服务降级方法了。
getOrederToMemberHystrix:线程池名称http-nio-8001-exec-4
而且,此接口中的打印日志操作,也走的是主线程,而不是服务降级新开的线程池。
后文:代码已上传到码云 https://gitee.com/xuruanshun/springcloud.git