本文意在把工作中经常涉及到的技术要点整理出来,形成一个知识体系,结构化、系统化地概括Reactive响应式开发、Spring/Spring Boot/Spring Cloud、分布式知识及涉及到的其他常用的附加知识。
Spring Cloud 官网:https://spring.io/projects/spring-cloud/
Spring Cloud Alibaba 官网:https://spring.io/projects/spring-cloud-alibaba
Spring Cloud Github: https://github.com/spring-cloud
Spring Cloud Alibaba Github: https://github.com/alibaba/spring-cloud-alibaba
Spring Cloud 生态

响应式 Reactive
Spring 5 引入了Reactor,特点是event-based、非阻塞、异步。基于JDK 8以上,比如:CompletableFuture,Stream和Duration等,为 Java 、Groovy 和其他 JVM 语言提供了构建基于事件和数据驱动应用的抽象库。Reactor 性能相当高,在最新的硬件平台上,使用无堵塞分发器每秒钟可处理 1500 万事件。
Reactor 官网:https://projectreactor.io/ (reference doc: https://projectreactor.io/docs/core/release/reference/)
Reactor 核心接口
-
Publisher<T>
-
Subscriber<T>
-
Subscription
-
Processor<T, R>
1. Publisher<T>
Publisher 代表的就是一种可以生产无限数据的发布者,接口如下:

可以看到,Publisher 里的 subscribe 方法传入的是 Subscriber 接口,这里用的是回调,Publisher 根据收到的请求向当前订阅者 Subscriber 发送元素。
2. Subscriber<T>
Subscriber 代表的是一种可以从发布者那里订阅并接收元素的订阅者,接口如下:

Subscriber 接口定义的这组方法构成了数据流请求和处理的基本流程,其中,onSubscribe() 从命名上看就是一个回调方法,当发布者的 subscribe() 方法被调用时就会触发这个回调。而在该方法中有一个参数 Subscription,可以把这个 Subscription 看作是一种用于订阅的上下文对象。Subscription 对象中包含了这次回调中订阅者想要向发布者请求的数据个数。
当订阅关系已经建立,那么发布者就可以调用订阅者的 onNext() 方法向订阅者发送一个数据。这个过程是持续不断的,直到所发送的数据已经达到 Subscription 对象中所请求的数据个数。这时候 onComplete() 方法就会被触发,代表这个数据流已经全部发送结束。而一旦在这个过程中出现了异常,那么就会触发 onError() 方法,我们可以通过这个方法捕获到具体的异常信息进行处理,而数据流也就自动终止了。
3. Subscription
Subscription 代表的就是一种订阅上下文对象,它在订阅者和发布者之间进行传输,从而在两者之间形成一种契约关系,接口如下:

这里的 request() 方法用于请求 n 个元素,订阅者可以通过不断调用该方法来向发布者请求数据;而 cancel() 方法显然是用来取消这次订阅。请注意,Subscription 对象是确保生产者和消费者针对数据处理速度达成一种动态平衡的基础,也是流量控制中实现背压机制的关键所在。
4. Processor<T,R>

Processor 代表的就是订阅者和发布者的处理阶段,Processor 接口继承了 Publisher 和 Subscriber 接口。它用于转换发布者——订阅者管道中的元素。Processor 订阅类型 T 的数据元素,接收并转换为类型 R 的数据,并发布变换后的数据。可以拥有多个处理者。
背压
发布订阅模式都有推和拉两种,需要在“推”模式和“拉”模式之间考虑一定的平衡性,从而优雅地实现流量控制。这就需要引出响应式系统中非常重要的一个概念——背压机制(Backpressure)。
什么是背压?简单来说就是下游能够向上游反馈流量请求的机制。通过前面的分析,我们知道如果消费者消费数据的速度赶不上生产者生产数据的速度时,它就会持续消耗系统的资源,直到这些资源被消耗殆尽。
这个时候,就需要有一种机制使得消费者可以根据自身当前的处理能力通知生产者来调整生产数据的速度,这种机制就是背压。采用背压机制,消费者会根据自身的处理能力来请求数据,而生产者也会根据消费者的能力来生产数据,从而在两者之间达成一种动态的平衡,确保系统的即时响应性。
Reactor、Flux、Mono 概述
Reactor的核心是Flux /Mono类型,它代表了数据或事件的流。
Reactor 异步序列:

Flux:

Flux代表0~n个元素的异步序列:

Flux类源码截图:

代码片段示例:

Mono:

Mono代表0个或1个元素的异步序列:

Mono<Void> 代表一个空的异步序列。
Mono类源码截图:

代码片段示例:

相较 Mono, Flux 是更通用的一种响应式组件,所以针对 Flux 的操作要比 Mono 更丰富。
另一方面,Flux 和Mono 之间可以相互转换。例如,把两个Mono 序列合并起来就得到一个Flux 序列,而对一个Flux 序列进行计数操作,得到的就是 Mono 对象。
创建 Flux 和 Mono
创建 Flux
1. 静态创建:
(1)just()
just() 方法可以指定序列中包含的全部元素,创建出来的 Flux 序列在发布这些元素之后会自动结束。一般情况下,在已知元素数量和内容时,使用just() 方法是创建 Flux 的最简单的做法。
(2)fromArray()、fromIterable()、fromStream()
(3)range()
使用 range(int start, int count) 方法可以创建包含从start起始的count个对象的序列,序列中的所有对象类型都是 Integer,这在有些场景下非常有用。
(4)interval()
interval(Duration period) 方法用来创建一个包含从0开始递增的 Long 对象的序列,序列中的元素按照指定的时间间隔来发布。而 interval(Duration delay, Duration period)方法除了可以指定时间间隔,还可以指定起始元素发布之前的延迟时间。另外,intervalMillis(long period)和intervalMillis(long delay, long period)与前面两个方法的作用相同,只不过这两个方法通过亳秒数来指定时间间隔和延迟时间。使用 interval() 方法创建 Flux 对象的示意图:

(5)empty()、error()、never()
empty() 方法创建一个不包含任何元素而只发布结束消息的序列;
error() 方法创建一个只包含错误消息的序列;
never() 方法创建一个不包含任何消息通知的序列。
使用 empty()方法创建 Flux 对象的示意图:

2. 动态创建:
(1)generate()
generate() 产生Flux 序列依赖于 Reactor 所提供的 SynchronousSink 组件。 SynchronousSink 组件包括 next()、complete() 和 error(Throwable) 这三个核心方法。在具体的元素生成逻辑中,next() 方法最多只能被调用一次。
Flux.generate (sink -> {
sink.next ("Hello");
sink.complete();
]).subscribe (System.out::println);
(2)create()
create() 使用的是 FluxSink 组件。FluxSink 支持同步和异步的消息产生方式,并且可以在一次调用中产生多个元素。
Flux.create (sink -> {
for (int i = 0; i < 10; i++) {
sink.next(i);
}
sink.complete();
}).subscribe (System.out::printin);
创建 Mono
1. 静态创建:
包含了一些与Flux 中相同的静态方法,如 just()、empty()、error()和 never()等。除了这些方法,Mono 还有一些特有的静态方法,比较常见的包括 delay()、justOrEmpty()等。
(1)delay()
delay(Duration duration)和 delayMillis(long duration)方法可以用于创建 Mono。它们的特点是,在指定的延退时间之后会产生数宇 0 作为唯一值。
(2)justOrEmpty()
justOrEmpty(Optional<? extends T> data) 方法从一个Optional 对象创建 Mono,只有当 Optional 对象中包含值时,Mono 序列才产生对应的元素。而 justOrEmpty(T data)方法从一个可能为 null 的对象中创建 Mono,只有对象不为 null 时,Mono 序列才产生对应的元素。
2. 动态创建:
同样可以通过 create() 方法并使用 Monosink 组件。
Mono.create (sink ->
sink.success("Hello")).subscribe(System.out::println);
Flux 和 Mono 操作符
示例:

转换操作符
常见的有map、flatMap、buffer、window等。
- map
映射操作,对流中每一个元素进行变换(transform)。

- flatMap
《Hands-On Reactive Programming in Spring 5》(Oleh Dokuka、Igor Lozynskyi著,即英文版《Spring响应式编程》)中,有一段我看过的解释flatMap最好的文字:

- buffer和window类似。
(1)可用指定元素个数,都是把流中的元素收集到Flux序列中。所不同的是,window 操作符是把当前流中的元素收集到另外的 Flux 序列中。因此,返回值类型是Flux<Flux<T>>,而不是buffer简单的 Flux<T>。
(2)buffer还可以指定收集的时间间隔,为一个Duration对象或毫秒。比如bufferUntil,bufferWhile,bufferTimeout,bufferMillis,bufferTimeoutMillis。
过滤操作符
常见的有filter、first、last、skip/skipLast、take/takeLast等。
- filter就是留下满足符合过滤条件的元素。
组合操作符
常见的有then/when、merge、startWith、zip等。
条件操作符
常见的有defaultEmpty、skipUntil、skipWhile、takeUntil、takeWhile等。
数学操作符
常见的有concat、count、reduce等。
- reduce
reduce 操作符对流中包含的所有元素进行累积操作,得到一个包含计算结果的 Mono 序列。
Observable 工具操作符
常见的有delay、subcribe、timeout、block等。
- subscribe
通过 subscribe() 方法来添加相应的订阅逻辑。在调用 subscribe() 方法时可以指定需要处理的消息类型。

- block
block 操作符常用来把响应式数据流转换为传统的数据流。例如,使用如下方法时,我们分别将 Flux 数据流和 Mono 数据流转变成了普通的 List<Order>对象和单个的Order对象,同样可以设置block操作的等待时间。
public List<Order> getAllOrders () {
return orderservice.getAllOrders ()
.block (Duration.ofSecond (5));
}
public Order getOrderById (Long orderId) {
return orderservice.getOrderById(orderId)
.block (Duration.ofSecond (2));
日志和调试操作符
常见的有log、debug(Hooks.onOperator(providedHook ->
providedHook.operatorStacktrace()) 和 checkpoint)等。
<待整理>
Spring Boot 和 Spring Cloud 通用的模块
接口文档(Swagger Open API)
Swagger 3:
访问地址:http://<ip>:port/swagger-ui/index.html
通常情况下swagger只能在开发环境或测试环境下开启,生产环境下需要进行关闭的。而swagger的开启与关闭可在application.properties中进行配置:
# 生产环境需设置为false
springfox.documentation.swagger-ui.enabled=true
@EnableOpenApi
<待整理>
安全 (Security OAuth2,JWT,Shiro)
Spring Security 与 Spring Cloud Gateway 的结合
/**
* 鉴权认证
*/
@Slf4j
@Component
@AllArgsConstructor
public class AuthFilter implements GlobalFilter, Ordered {
private final ObjectMapper objectMapper;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getPath();
log.info("Request path=[{}]", path);
// skip
if (StringUtils.contains(path, "/v3/api-docs") || StringUtils.contains(path, "/swagger-") || StringUtils.contains(path, "/xxx/generateJWT")) {
return chain.filter(exchange);
}
ServerHttpResponse resp = exchange.getResponse();
String headerToken = exchange.getRequest().getHeaders().getFirst(“X-xxx-xxx”);
if (StringUtils.isBlank(headerToken)) {
return unAuth(resp, "缺失令牌,鉴权失败");
}
String token = JwtUtil.getToken(headerToken);
// also can get expiry
Claims claims = JwtUtil.parseJWT(token);
if (claims == null) {
return unAuth(resp, "请求未授权");
}
/* // 如果向请求里加header,记得build
ServerHttpRequest host = exchange.getRequest().mutate().header("X-YYY-ZZZ", "test-header").build();
//将现在的request变成exchange对象
ServerWebExchange build = exchange.mutate().request(host).build();
return chain.filter(build);*/
// ServerHttpRequest serverHttpRequest = exchange.getRequest();
// String method = serverHttpRequest.getMethodValue();
// if("GET".equals(method)){
// MultiValueMap<String, String> queryParams = serverHttpRequest.getQueryParams();
// log.info("Details: Path=[{}], Method=GET,params=[{}]", path, JSONObject.toJSONString(queryParams));
// }
return chain.filter(exchange);
}
private Mono<Void> unAuth(ServerHttpResponse resp, String msg) {
resp.setStatusCode(HttpStatus.UNAUTHORIZED);
resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String result = "";
try {
// set GatewayResponse
GatewayResponse gatewayResponse = new GatewayResponse();
gatewayResponse.setMsgCode(403);
gatewayResponse.setMsg(msg;
result = objectMapper.writeValueAsString(gatewayResponse);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
}
DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Flux.just(buffer));
}
@Override
public int getOrder() {
return -100;
}
}
Spring Security 详情可参考:
https://blog.youkuaiyun.com/Beth_Chan/article/details/125080402
Spring Cloud
架构:

项目 parent
依赖管理:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
注册中心(Nacos,Eureka ,Consul,Zookeeper)
Eureka
服务注册中心,服务发现,特性有失效剔除、服务保护。
1. pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
2. 给 Spring Boot main application类加上 @EnableEurekaServer。
3. 配置文件
application.properties
spring.application.name=servicediscovery
server.port=8260
eureka.instance.hostname=localhost
eureka.server.wait-time-in-ms-when-sync-empty=5
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
logging.level.org.springframework=INFO
logging.level.com.cxf=DEBUG
application-eureka1.properties:
server.port=8260 eureka.instance.hostname=sdpeer1 eureka.client.register-with-eureka=true eureka.client.fetch-registry=true eureka.client.service-url.defaultZone=http://<ip2>:8262/eureka,http://<ip3>:8263/eureka
application-eureka2.properties:
spring.application.name=servicediscovery server.port=8262 eureka.instance.hostname=sdpeer2 eureka.client.register-with-eureka=true eureka.client.fetch-registry=true eureka.client.service-url.defaultZone=http://<ip1>:8260/eureka,http://<ip3>:8263/eureka
application-eureka3.properties:
spring.application.name=servicediscovery server.port=8263 eureka.instance.hostname=sdpeer3 eureka.client.register-with-eureka=true eureka.client.fetch-registry=true eureka.client.service-url.defaultZone=http://<ip1>:8260/eureka,http://<ip2>:8262/eureka
示例
启动程序:(或者使用IDE:Intellij IDEA/STS都可以)
java -jar "/Users/cxf/IT/code/springcloud-demo/service-discovery/target/service-discovery-0.0.1-SNAPSHOT.jar"
java -jar "/Users/cxf/IT/code/springcloud-demo/hello-provider/target/hello-provider-0.0.1-SNAPSHOT.jar"
java -jar "/Users/cxf/IT/code/springcloud-demo/hello-consumer/target/hello-consumer-0.0.1-SNAPSHOT.jar"
访问 http://localhost:8260/,可看到:

consumer依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>
配置文件:
spring.application.name=hc-service eureka.instance.prefer-ip-address=true eureka.client.register-with-eureka=true eureka.client.fetch-registry=true eureka.client.service-url.defaultZone=http://localhost:8260/eureka #feign.hystrix.enabled=true logging.level.org.springframework=INFO logging.level.com.cxf=DEBUG
main application:
@EnableDiscoveryClient @EnableFeignClients
controller endpoint:
@RestController
@RequestMapping("/hello")
public class HelloConsumerEndpoint {
@Autowired
private HelloService helloService;
/**
* Hello
* @return
*/
@RequestMapping(value = "/{name}", method = RequestMethod.GET)
public String hello(@PathVariable String name){
return this.helloService.hello(name);
}
}
service:
FeignCilent:
@FeignClient(value = "HP-SERVICE", fallback = HelloServiceFallback.class)
public interface HelloService {
@RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
String hello(@PathVariable("name") String name);
}
Fallback:
@Component
public class HelloServiceFallback implements HelloService {
public String hello(String name) {
return "Hello, " + name + ", I'm fallback!";
}
}
http://localhost:8260/eureka/apps/hc-service,可查看服务详细信息
hp-service 服务:
Provider:
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
Main application:
@EnableDiscoveryClient
Controller API:
@RestController
@RequestMapping("/hello")
public class HelloProviderEndpoint {
@RequestMapping(value = "/{name}", method = RequestMethod.GET)
public String hello(@PathVariable String name){
return "Hello, " + name + "!";
}
}
provider 配置:
server.port=2100
spring.application.name=hp-service
eureka.instance.prefer-ip-address=true
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://localhost:8260/eureka
logging.level.org.springframework=INFO
logging.level.com.cxf=DEBUG
Postman 里可看到:
即:
在程序里也做了容错机制:
Nacos
可作为依赖中心、配置中心。
依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<artifactId>fastjson</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
在GIthub Releases中下载压缩包,https://github.com/alibaba/nacos
sh bin/startup.sh -m standalone

<待整理>
网关 (Spring Cloud Gateway,Zuul)
功能有路由分发和过滤。
Spring Cloud Gateway
旧网关Netflix Zuul 1.x只是性能一般的网关,加上Netflix Zuul 2.x版本经常不能如期发布,所以新版的Spring Cloud不打算捆绑Zuul了。新版的Spring Cloud提供了新的网关给开发者使用,这便是Spring Cloud Gateway。为了简便,下文在没有特别指明的情况下,将简称它为Gateway。Gateway并非是使用传统的Jakarta EE的Servlet容器,它是采用响应式编程的方式进行开发的。在Gateway中,需要Spring Boot和Spring WebFlux提供的基于Netty的运行环境。那么为什么Zuul 1.x是一个性能一般的网关,而Gateway又与它有什么不同呢?Zuul 1.x是基于传统的Jakarta EE的Servlet容器方式的,而Gateway是基于响应式方式的,其内部执行方式的不同,决定了二者的性能完全不一样。
从执行的原理来说,Zuul会为一个请求分配一条线程,然后通过执行不同类型的过滤器来完成路由的功能。但是请注意,这条线程会等route类型的过滤器去调用源服务器,显然这是线程执行过程中最为缓慢的一步,因为源服务器可能会因执行比较复杂的业务而响应特别慢,这样Zuul中的线程就需要执行比较长的时间,容易造成线程积压,导致性能变慢。从图中可以看到,请求达到Gateway后,便由Gateway的组件进行处理。Gateway的组件是这样处理的:
Spring Cloud Gateway的执行方式
●创建一条线程,通过类似Zuul的过滤器拦截请求;
●对源服务器转发请求,但注意,Gateway并不会等待请求调用源服务器的过程,而是将处理线程挂起,这样便不再占用资源了;
●等源服务器返回消息后,再通过寻址的方式来响应之前客户端发送的请求。
从上述过程描述中大家可以看到,Gateway线程在处理请求的时候,仅仅是负责转发请求到源服务器,并不会等待源服务器执行完成,要知道源服务器执行是最缓慢的一步。因此Gateway的线程活动的时间会更短,线程积压的概率更低,性能相对Zuul来说也更好。事实上,Gateway除了性能更好之外,它还顺应了响应式、函数式和Lambda表达式的潮流,允许开发者通过它们灵活地构建微服务网关。
Route
Predicate
Filter
Zuul
在Zuul中,服务路由信息的设置包括基于服务发现映射服务路由和基于动态配置映射服务路由。
pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
配置文件:
# omit server port, application name & eureka config
zuul.prefix=/api # 请求地址的前缀 zuul.routes.userservice = /user/** # 自定义服务路由的映射 zuul.ignored-services=userservice # 将系统自动映射的路由从服务路由中删除
Main application:
@EnableZuulProxy
可通过 http:<zuul service hostname:port>/actuator/routes 查看服务路由映射信息。
比如:
{
"/api/user/**": "userservice"
}
配置(Config)
Spring Cloud Config
分布式配置中心,支持本地仓库、SVN、Git、Jar包内配置等模式,避免分布式架构下多微服务的多配置文件出错。
服务调用 (OpenFeign,Feign,WebClient,RestTemplate,HttpClient,okhttp,HTTP Interface)
OpenFeign
- 引入依赖:基于Spring Cloud项目的话使用 spring-cloud-starter-feign,否则可引入feign-core等。
- main application: @EnableFeignClients
- Controller:同Spring MVC一样,@RestController,@PostMapping / @GetMapping / @RequestMapping,调用 FeignClient 所在 service 的 方法/函数。
- FeignClient:interface service,可以指定url、name、fallback、fallback factory,均可使用占位符。
- value:String:服务名。比如 service-provider、http://service-provider。如果 EchoService
- 中配置了 value=service-provider,那么调用 echo 方法的 URL 为 http://service-provider/
- echo;如果配置了 value=https://service-provider,那么调用 echo 方法的 URL 为 https://
- service-provider/divide。
- serviceId:String:该属性已过期,但还能用,其作用跟 value 一致。
- name:String:跟 value 属性的作用一致。
- qualifier:String:给@FeignClient 注解修饰的 Bean 设置的 Spring Bean 名称。
- url:String:绝对路径,用于替换服务名,优先级比服务名高。比如,EchoService 中如果配置了 url=aaa,则调用 echo 方法的 URL 为 http://aaa/echo;如果配置了 url=https://aaa,则调用 echo 方法的 URL 为 https://aaa/divide。
- decode404:boolean:默认是 false,表示对于一个 HTTP 状态码为 404 的请求是否需要进行解码。默认为 false ,表示不进行解码,当成一个异常处理。设置为 true 之后,遇到 HTTP 状态码为 404 的 Response 还是会解析这个请求的 body。
- fallback:Class<?>:默认值是 void.class,表示 fallback 类,需要实现 FeignClient 对应的接口,当调用方法发生异常时,会调用这个 Fallback 类对应的 FeignClient 接口方法。如果配置了 fallback 属性,就会把 Fallback 类包装在一个默认的 FallbackFactory实现类 FallbackFactory.Default 上,而不使用 fallbackFactory 属性对应的FallbackFactory 实现类。
- fallbackFactory:Class<?>:默认值是 void.class,表示生产 fallback 类的 Factory,可以实现 feign.hystrix.FallbackFactory 接口,FallbackFactory 内部会针对一个 Throwable 异常返回一个 Fallback 类进行 fallback 操作。
- path:String:请求路径。在服务名或 URL 与 requestPath 之间。
- primary:boolean:默认是 true,表示当前这个 FeignClient 生成的 Bean 是否是 primary。如果在 ApplicationContext 中存在一个实现 EchoService 接口的 Bean,但注入时并不会使用该 Bean,因为 FeignClient 生成的 Bean 是 primary。
- contextId:String:FeignContext 里维护着 map 的 key,如果没设置,会使用 name 属性。
限流 (Circuit Breaker (Implementation: Resilience4j / Spring Retry),Sentinel,Hystrix)
熔断器有3种状态:Closed(关闭)、Half-Open(半开启)和Open(开启)。
- Closed 状态:为默认状态。Circuit Breaker 内部维护着最近失败的次数(failure count)。操作每失败一次,就会增加一次失败次数。当错误数达到一定阈值时,就会从 Closed状态进入 Open 状态。
- Open 状态:操作不会执行,而是立即失败,Circuit Breaker 内部维护着一个计时器,当时间达到一定阈值时,就会从 Open 状态进入 Half-Open 状态。
- Half-Open 状态:只要操作失败一次,立即进入 Open 状态;否则增加操作成功的次数,当成功次数达到一定阈值时,进入 Closed 状态并重置失败次数
断路器,最重要的就是状态之间的转换。3 种状态之间的转换逻辑如下:
- Closed→Open:指定的时间窗口 failureTimeInterval 内(比如 2s 内),Closed 状态进入 Open 状态需要满足的错误个数阈值 failureCount。
- Open→Half-Open:Open 状态进入 Half-Open 状态的超时时间 halfOpenTimeout。
- Half-Open→Closed:Half-Open 状态进入 Open 状态的成功个数阈值 halfOpenSuccessCount。
Resilience4j
Resilience4j 容错包括:断路器(Circuit Breaker)、隔板(BulkHead)、重试(Retry)、限时器(Time Limiter)、限流器(Rate Limiter),Cache,Fallback。
依赖引入:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
CircuitBreaker
示例:
resilience4j.circuitbreaker:
instances:
xxx:
register-health-indicator: true
sliding-window-type: TIME_ BASED
sliding-window-size: 100
minimum-number-of-calls: 50
failure-rate-threshold: 50
automatic-transition-from-open-to-half-open-enabled: true
permitted-number-of-cal1s-in-half-open-state: 5
wait-duration-in-open-state: 5s
record-exceptions:
- java.lang.Throwable
- org.springframework.web.client.HttpServerErrorException
CircuitBreakerConfig
CircuitBreakerRegistry
Bulkhead
什么是bulkhead?
bulkhead是舱壁,隔板的意思,术语舱壁来自它在船舶中的使用,其中船舶的底部被分成彼此分开的部分。如果有裂缝,并且有水流入,则只有该部分会充满水。这可以防止整艘船沉没。
为什么要使用bulkhead?
故障隔离背后的思想是控制并发调用的数量,将不同调用视为不同的、隔离的池,可以避免某类调用异常或占用过多资源,危及系统整体。
如何使用bulkhead?
Resilience4j提供了两种类型的隔板:信号量Semaphore或线程池ThreadPool(使用了一个有界队列ArrayBlockingQueue和一个固定数量的线程池)。
在Resilience4j中使用舱壁,如果采用代码的形式,则需要创建舱壁配置(BulkheadConfig)、注册机(BulkheadRegistry)和单个舱壁 (Bulkhead)。
采用 resilience4j-spring-boot2 包则可采用下列简单的例子。配置示例:
#resilience4j.bulkhead:
# instance:
# xxx:
# max-wait-duration: 1000ms
# max-concurrent-calls: 20
resilience4j.thread-pool-bulkhead:
instance:
xxx:
max-thread-pool-size: 5
core-thread-pool-size: 5
queue-capacity: 1
配置参数说明:
信号量
| 属性 | 默认值 | 描述 |
| max-wait-duration | 0 | 获取信号量的最长排队等待时间 |
| max-concurrent-calls | 25 | 最大并发 |
线程池
| 属性 | 默认值 | 描述 |
| max-thread-pool-size | Runtime.getRuntime().avaliableProcess() | 最大线程数 |
| core-thread-pool-size | Runtime.getRuntime().avaliableProcess() - 1 | 最少线程数 |
| queue-capacity | 100 | 等待队列大小 |
| keep-alive-duration | 20[ms] | 线程空闲时,销毁前的等待时间 |
代码示例:
@Bulkhead(name = "xxx", fallbackMethod = "fallback")
public String method(String param1) {
return "test";
}
private String fallback(String param1, IllegalArgumentException e) {
return "test illegal argument fallback";
}
private String fallback(String param1, Exception e) {
return "test fallback";
}
重要的是要记住,回退方法应该放在同一个类中,并且必须具有相同的方法签名,只有一个额外的目标异常参数。
如果有多个 fallbackMethod 方法,将调用最接近匹配的方法。上面配置了20个并发限制,如果超过20个并发,会进入第二个fallback方法。如果方法抛出IllegalArgumentException,则会进入第一个fallback方法。
Retry
示例:
resilience4j.retry:
instances:
xxx:
max-attempts: 3
wait-duration: 10s
enable-exponential-backoff: true
exponential-backoff-multiplier: 2
retry-exceptions:
- org.spring{ramework.web.client.HttpServerErrorException
- java.io.I0Exception
RetryConfig
RetryRegistry
RateLimiter
示例:
resilience4j.ratelimiter:
instances:
xxx:
limit-for-period: 1
limit-refresh-period: 1s
timeout-duration: 0
register-health-indicator: true
event-consumer-buffer-size: 100
RateLimiterConfig
RateLimiterRegistry
TimeLimiter
示例:
resilience4j.timelimiter:
instances:
xxx:
timeout-duration: 2s
TimeLimiterConfig
Cache
<待整理>
Sentinel
可参考阅读:
https://www.cnblogs.com/crazymakercircle/p/14285001.html
Hystrix
客户端容错保护,特性有服务降级、服务熔断、请求缓存、请求合并、依赖隔离。(怕访问过于频繁服务挂了,进行限流,太频繁的请求就直接拒绝)。当某个服务发生故障后,触发熔断机制向服务调用方返回结果标识错误,而不是一直等待服务提供方返回结果,这样就不会使得线程因调用故障服务而被长时间占用不释放,避免故障在分布式系统中的蔓延。
负载均衡 (Spring Cloud LoadBalancer,Ribbon)
Spring Cloud Loadbalancer
常用的负载均衡算法:
- 随机(Random)算法:在实例列表中随机选择某个实例。
- 轮询(RoundRobin)算法:循环取下一个。比如,一共有 3 个实例,第一次取第 1 个,第二次取第 2 个,第三次取第 3 个,第四次取第 1 个,以此类推。
- 最少连接数(Least Connections)算法:每次取连接数最少的实例。
- 一致性哈希(Consistent Hashing)算法:基于一致性哈希算法总是将相同参数的请求落在同一个实例上。
- 权重随机(Weightd Random)算法:比如有 4 个实例,实例 A 权重为 10,实例 B 权重 为 30,实例 C 权重为 40,实例 D 权重为 20。每次随机取 100 内的整数,若结果在 1~10 之间,则选择实例 A,在 11~40 之间选择实例 B,在 41~80 之间选择实例 C,在 81~100 之间选择实例 D。
相关类和接口的关系:
- ServiceInstanceChooser:服务实例选择器,根据服务名获取一个服务实例(ServiceInstance)。
- LoadBalancerClient:客户端负载均衡器,继承 ServiceInstanceChooser,会根据 ServiceInstance 和 Request 请求信息执行最终的结果。
- BlockingLoadBalancerClient:基于 Spring Cloud LoadBalancer 的 LoadBalancerClient 默认实现。
- RibbonLoadBalancerClient:基于 Netflix Ribbon 的 LoadBalancerClient 实现。
<待整理>
Ribbon
客户端负载均衡,特性有区域亲和、重试机制。(类似nginx)
全链路追踪(Sleuth,Zipkin)
分布式服务追踪,需要搞清楚TraceID和SpanID以及抽样,如何与ELK整合。(服务多了,调用的线路就会很复杂,需要跟踪来知道你到底是怎么走的)
<待整理>
微服务的监控(Actuator,Admin,Prometheos,Grafana)
推荐参考阅读:
热部署
基于Maven提供Spring-boot-devtools来监控应用中各文件,当发生变动后自动触发重启应用。
Spring Cloud Bus,Spring Cloud Stream
Stream,消息驱动,有Sink、Source、Processor三种通道,特性有订阅发布、消费组、消息分区。
Bus,消息总线,配合Config仓库修改的一种Stream实现,用于广播消息。
Spring Integration
Java实时处理 - Spring Integration - MQ Message_channeladapter_Beth_Chan的博客-优快云博客
远程过程调用 RPC (Thrift,gRPC)
<待整理>
网络编程(Netty,Web Socket)
<待整理>
分布式知识
发号机制 (生成唯一的ID)
<待整理>
分布式数据库
分表、分库、分区
Sharding 分片
分布式事务
Alibaba Seata;
Spring Cloud: 支持XA协议, Java实现JTA, Java Transaction API规范的有Atomikos, Biytonix和Narayana, 选用Atomikos
分布式缓存
Redis
分布式会话
<待整理>
分布式系统权限验证
<待整理>
附加知识
数据库(JPA, Mybatis Plus; PostgreSQL, Oracle, MySQL, MariaDB, Redis, MongoDB, InfluxDB; H2, R2DBC; HikariCP)
<待整理>
MQ (Kafka, RocketMQ, Websphere MQ,Solace)
<待整理>
Spring Cloud Data Flow, Spring Cloud Bus, Spring Cloud Stream, Spring Integration, Spring Messaging
Spring Cloud Data Flow 有提供 Dashboard 和 Shell 两种方式。

Spring Cloud Bus:
Enterprise Integration Pattern (企业集成模式)对消息总线的定义是:消息总线是一种消息传递基础结构,它允许不同的系统通过一组共享的接口(消息总线)进行通信。

Spring CLoud Bus 强依赖 Spring Cloud Stream。
加密
Jasypt
<待整理>
日志(Log4j2,Slf4j,Logback)
log4j2:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
JSON(Jackson,Fastjson,gson)
Spring默认是Jackson。
<待整理>
XML(Dom4j)
<待整理>
Excel(POI)
<待整理>
定时器(Scheduler,Quartz,XXL-Job)
<待整理>
LDAP
<待整理>
2033

被折叠的 条评论
为什么被折叠?



