起因
阿里Sentinel团队不稳定,超过了一半年的时间没有更新了,虽然认为一个成熟产品没必要老更新,在特定的阶段能完成他的历史使命就好,并且sentinel开源这块依旧还在维护开发中。但是它是为了kpi开放出来的,生怕哪天不维护了,总感觉阿里是在培养用户习惯之后,让你绑定阿里的生态,再到阿里云割韭菜,开源嘛,本来也是无偿用,人家没有义务像商业软件那样永久更新,之前开源社区好几个作者就是因为不更新或者更新慢被别人喷不搞了,所以个人觉得有必要多掌握几个限流熔断组件。
resilience4j落地实现
pom.xml依赖
如果你的springboot版本是2的使用以下依赖:
<!-- 熔断限流resilience4j--> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot2</artifactId> <version>2.2.0</version> </dependency>
如果你的springboot版本是3的使用以下依赖:
<!-- 熔断限流resilience4j--> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot3</artifactId> <version>2.2.0</version> </dependency>
application.yml配置
resilience4j: retry: # 重试策略机制配置 instances: # 定义多个重试策略实例 retryApi: # 第一个重试策略重试名称 max-attempts: 3 # 操作失败最大重试次数为3 wait-duration: 1s # 每次重试等待时间1秒 circuitbreaker: instances: # 定义多个断路器实例 circuitBreakerApi: # 第一个断路器实例名称 registerHealthIndicator: true # 配置健康指示器 slidingWindowSize: 10 # 滑动窗口大小 minimumNumberOfCalls: 5 # 最小调用次数 permittedNumberOfCallsInHalfOpenState: 3 # 半开状态下允许的调用次数 slidingWindowType: TIME_BASED # 滑动窗口类型 automaticTransitionFromOpenToHalfOpenEnabled: true # 是否自动从开启状态转换为半开状态 waitDurationInOpenState: 1s # 开启状态下等待时间 failureRateThreshold: 20 # 失败率阈值,失败率20%时,断路器打开 eventConsumerBufferSize: 10 # 事件消费者缓冲区大小 ignoreExceptions: # 忽略的异常列表 - java.io.IOException ratelimiter: instances: # 定义多个限流策略实例 rateLimiterApi: # 第一个限流策略实例名称 limitForPeriod: 10000 # 限制周期内的请求数量10000 limitRefreshPeriod: 10s # 限制刷新周期,10秒一个周期 timeoutDuration: 500ms # 超时时间为0.5秒,请求超过限制客户端立即收到超时响应,不等待 subscribeForEvents: AFTER_SUCCESS # 订阅事件类型 eventConsumerBufferSize: 10 # 事件消费者缓冲区大小
接口使用
@Retry(name = "retryApi",fallbackMethod = "fallbackRedPackage") @CircuitBreaker(name = "circuitBreakerApi",fallbackMethod = "fallbackRedPackage") @RateLimiter(name = "rateLimiterApi",fallbackMethod = "fallbackRedPackage") @PostMapping(value = "/redPackage") public Result redPackage(@RequestBody RedPackegeRainVo redPackage) { //红包唯一标识\红包的key String redPackageKey = redPackage.getRedPackageKey(); //用户id\用户token String userId = redPackage.getUserId(); //使用StringBuilder拼接字符串,作为红包的键(key) String redAppend = StringUtil.StringAppend(RedPackageRainConstant.RED_PACKAGE_KEY, redPackageKey); //从redis缓存中获取红包池中的红包 String partRedPackage = redisUtil.leftPop(redAppend); //判断是否为空,不为空进入后续流程;为空直接返回 if (StringUtils.isNotEmpty(partRedPackage)) { //将红包的key和用户的id作为存储redis缓存已抢红包池的键(key) String redConsumeAppend = StringUtil.StringAppend(RedPackageRainConstant.RED_PACKAGE_CONSUME_KEY, redPackageKey,":", RedPackageRainConstant.RED_PACKAGE_USER_ID, userId); //存入redis缓存并且设置过期时间(使用了redis事务,保证原子性,因为操作简单、依赖关系简单,使用使用redis事务比使用 Lua 脚本更适合当前场景) redisUtil.multiStr(redConsumeAppend,partRedPackage,1,TimeUnit.DAYS); //自适应(根据当前机器的线程数适配核心线程数和最大线程数)全局线程池 ThreadPoolExecutor.getThreadPoolExecutor().execute(new Runnable() { @Override public void run() { ConcurrentHashMap concurrentHashMap = pool.acquire(); // 从对象池中获取一个ConcurrentHashMap实例 concurrentHashMap.put(RedPackageRainConstant.RED_PACKAGE_KEY,redPackageKey); concurrentHashMap.put(RedPackageRainConstant.RED_PACKAGE_USER_ID,userId); concurrentHashMap.put(RedPackageRainConstant.RED_PACKAGE_VALUE,partRedPackage); //将ConcurrentHashMap对象转换成字符串。 String convertToString = StringUtil.convertToString(concurrentHashMap); //释放对象 pool.release(concurrentHashMap); //发送MQ消息 发送字符串比发送对象的网络传输更小。这是因为字符串可以被序列化为字节数组,而对象需要被序列化为字节数组,并包含对象的类信息和其他序列化数据。因此,发送字符串可以节省网络传输的带宽。 messageProducer.sendMessage(RedPackageRainConstant.TOPIC,convertToString); } }); return Result.build(ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS); } return Result.error(ResultCodeEnum.RED_PACKAGE_FINISHED.getCode(),ResultCodeEnum.RED_PACKAGE_FINISHED.getMessage()); } public Result fallbackRedPackage(Throwable throwable) { log.error("fallback RedPackage info:",throwable.getMessage()); return Result.error(ResultCodeEnum.RED_PACKAGE_ERROR.getCode(),ResultCodeEnum.RED_PACKAGE_ERROR.getMessage()); }
hystrix 落地实现
pom.xml依赖
<!-- hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
启动类上添加注解
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableDiscoveryClient // 开启断路器,开启Hystrix容错能力 @EnableCircuitBreaker public class SpringCloudHystrixDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudHystrixDemoApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
接口上使用
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @RestController @RequestMapping("/hystrix") public class HystrixController { @Autowired private RestTemplate restTemplate; /** * HystrixCommand 开启Hystrix命令 当方法执行失败时,使用Hystrix逻辑处理 * fallbackMethod 当方法执行失败时,调用此方法。 */ @GetMapping("/getNacosConfigure") @HystrixCommand(fallbackMethod = "defaultFallbackMethod") public String getNacosConfigure() { return restTemplate.getForObject("http://nacos-config/getNacosConfigure", String.class); } private String defaultFallbackMethod() { return "方法执行失败啦,Hystrix起作用了"; } // 信号量隔离 @HystrixCommand( commandProperties = { // 超时时间,默认1000ms @HystrixProperty(name = HystrixPropertiesManager. EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "5000"), // 信号量隔离 @HystrixProperty(name = HystrixPropertiesManager. EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"), // 信号量最大并发 @HystrixProperty(name = HystrixPropertiesManager. EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "5") },//回退方法 fallbackMethod = "defaultFallbackMethod" ) @GetMapping("/semaphoreIsolation") public String semaphoreIsolation() { return "信号量隔离" + Thread.currentThread().getName(); } // 线程池隔离 @HystrixCommand(groupKey = "productService", // 服务名称,相同名称使用同一个线程池 commandKey = "selectById", // 接口名称,默认为方法名 threadPoolKey = "productServiceListPool", // 线程池名称,相同名称使用同一个线程池 commandProperties = { // 超时时间,默认1000ms @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000") }, threadPoolProperties = { // 线程池大小 @HystrixProperty(name = "coreSize", value = "5"), // 等待队列长度(最大队列长度,默认值-1) @HystrixProperty(name = "maxQueueSize", value = "100"), // 线程存活时间,默认1min @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"), // 超出等待队列阈值执行拒绝策略 @HystrixProperty(name = "queueSizeRejectionThreshold", value = "100") },//回退方法 fallbackMethod = "defaultFallbackMethod" ) @GetMapping("/threadPoolIsolation") public String threadPoolIsolation() { return "线程池隔离" + Thread.currentThread().getName(); } // 服务熔断 @HystrixCommand( commandProperties = { // 请求数阈值:在快照时间窗口内,必须满足请求阈值数才有资格熔断。打开断路器的最少请求数,默认20个请求。 //意味着在时间窗口内,如果调用次数少于20次,即使所有的请求都超时或者失败,断路器都不会打开 @HystrixProperty(name = HystrixPropertiesManager. CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "10"), // 错误百分比阈值:当请求总数在快照内超过了阈值,且有一半的请求失败,这时断路器将会打开。默认50% @HystrixProperty(name = HystrixPropertiesManager. CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "50"), // 快照时间窗口:断路器开启时需要统计一些请求和错误数据,统计的时间范围就是快照时间窗口,默认5秒 @HystrixProperty(name = HystrixPropertiesManager. CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "5000") }, fallbackMethod = "defaultFallbackMethod" ) @GetMapping("/serviceFuse") public String serviceFuse() { return "服务熔断" + Thread.currentThread().getName() + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME); } // 服务降级 @HystrixCommand(fallbackMethod = "defaultFallbackMethod") @GetMapping("/serviceDegradation ") public String serviceDegradation() { return "服务降级" + Thread.currentThread().getName() + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME); } }