- 实现容错的常见三种机制
- 在项目中如何使用 Hystrix
我们已实现服务发现和负载均衡,同时,使用 Feign 也实现了良好的远程调用——使得我们的代码是可读和可维护的。理论上,我们现在已经可以构建一个不错的分布式应用了,但微服务之间是通过网络通信的,网络可能出现问题,同时,微服务本身也并非 100% 可用。
如何提升应用的可用性呢?这是我们必须考虑的问题。
举个例子:某大型系统中,服务 A 调用服务 B,某个时刻,微服务 B 突然崩溃了,但微服务 A 中依然有大量请求在请求 B,如果没有任何措施,微服务 A 很可能很快就会被拖死——因为在 Java 中,一次请求往往对应着一个线程,如果不做任何措施,那意味着微服务 A 请求 B 的线程要等 Feign Client/RestTemplate 超时才会释放(这个时间一般非常长,长达几十秒),于是就会有大量的线程被阻塞,而线程又对应着计算资源(CPU/内存),于是乎,大量的资源被浪费,并且越积越多,最终服务器终于没有资源给微服务 A 浪费了,微服务 A 也挂了。如果在线的服务更多的话,比如几十个或者上百个,那么后果是不可想象的
容错手段
- 超时机制
即在服务中配置一下超时时间,例如超时时间的阈值为 1 秒——请求在 1 秒内必须返回,否则到点就把线程掐死,释放资源!这样,请求一旦超时,就会释放资源。由于释放资源速度较快,应用就不会那么容易被拖死。
- 舱壁模式
一般来说,现代的轮船都会分很多舱室,舱室之间用钢板焊死,彼此隔离。这样即使有某个/某些船舱进水,也不会影响其他舱室,浮力够,船不会沉。
软件世界里的仓壁模式可以这样理解:M 类使用线程池 1,N 类使用线程池 2,彼此的线程池不同,并且为每个类分配的线程池较小,例如 coreSize=10。 就好比人们常说的一句话 “不把鸡蛋放在一个篮子里”。你有你的线程池,我有我的线程池,你的线程池满了和我没关系,你挂了也和我没关系。
- 断路器
现实世界的断路器大家肯定都很了解,每个人家里都会有断路器。断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁。
软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数或者失败率达到一定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。
如何使用 Hystrix
Hystrix 是由 Netflix 开源的一个延迟和容错库,用于隔离访问远程系统、服务和第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix 主要通过以下几点实现延迟和容错:
- 包裹请求
使用 HystrixCommand(或 HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用到了设计模式中的“命令模式”。
- 跳闸机制
当某服务的错误率超过一定阈值时,Hystrix 可以自动或者手动跳闸,停止请求该服务一段时间。
- 资源隔离
Hystrix 为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求将被立即拒绝,而不是排队等候,从而加速失败判定。
- 监控
Hystrix 可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
- 回退机制
当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
- 自我修复
断路器打开一段时间后,会自动进入“半开”状态。断路器打开、关闭、半开的逻辑转换,前面我们已经详细探讨过了,不再赘述。
电影服务
调用用户服务
接口出现问题的时候,触发熔断,返回默认的值。microservice-consumer-movie-ribbon
的 pom.xml 中引入 Hystrix 依赖1 2 3 4 | <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | <?xml version= "1.0" encoding= "UTF-8" ?> <project xmlns= "http://maven.apache.org/POM/4.0.0" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion> 4.0 . 0 </modelVersion> <groupId>com.lzj1234</groupId> <artifactId>microservice-consumer-movie-ribbon</artifactId> <version> 1.0 -SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version> 2.0 . 7 .RELEASE</version> <relativePath/> </parent> <properties> <java.version> 1.8 </java.version> <!-- <maven.compiler.source> 8 </maven.compiler.source>--> <!-- <maven.compiler.target> 8 </maven.compiler.target>--> </properties> <dependencies> <!-- https: //mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!-- https: //mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- <version> 2.4 . 2 </version>--> </dependency> <!-- 引入H2数据库,一种内嵌的数据库,语法类似MySQL --> <!-- https: //mvnrepository.com/artifact/com.h2database/h2 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version> 1.4 . 200 </version> <!-- <scope>test</scope>--> </dependency> <!-- https: //mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version> 1.18 . 18 </version> <scope>provided</scope> </dependency> <!-- https: //mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <!-- <version> 2.4 . 2 </version>--> <scope>test</scope> </dependency> <!-- https: //mvnrepository.com/artifact/org.apache.maven.plugins/maven-clean-plugin --> <dependency> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-clean-plugin</artifactId> <version> 2.5 </version> </dependency> <!-- 新增依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <!-- 引入spring cloud的依赖,不能少,主要用来管理Spring Cloud生态各组件的版本 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR2</version> <type>pom</type> <scope> import </scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version> 2.5 . 1 </version> <configuration> <source> 1.8 </source> <target> 1.8 </target> </configuration> </plugin> </plugins> </build> </project> |
加注解:在启动类App.java
上添加 @EnableCircuitBreaker
注解
@LoadBalanced
app.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package com.lzj1234; 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 @EnableCircuitBreaker public class App { public static void main(String[] args) { SpringApplication.run(App. class ,args); } @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } } |
MovieController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package com.lzj1234; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.ribbon.proxy.annotation.Hystrix; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.math.BigDecimal; @RequestMapping ( "/movies" ) @RestController public class MovieController { @Autowired RestTemplate restTemplate; @HystrixCommand (fallbackMethod = "findByIdFallback" ) @GetMapping ( "/users/{id}" ) public User findById( @PathVariable Long id){ // 这里用到了RestTemplate的占位符能力 microservice-provider-user User user= this .restTemplate.getForObject( "http://microservice-provider-user/users/{id}" ,User. class ,id); return user; } public User findByIdFallback(Long id ){ return new User(id, "default username" , "default name" , 0 , new BigDecimal( 1 )); } } |
以上代码的作用:@HystrixCommand(fallbackMethod = "findByIdFallback")
使用了一个 fallbackMethod
为 /users/{id}
接口添加了熔断机制,在该接口请求过程中出错时会调用 findByIdFallback
方法返回一个默认的用户值
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现