概述
【Spring Cloud】Spring Cloud 组件个人最佳实践分享
- 前言
- Eureka Server
- pom.xml
- ConfigData(配置文件)
- application-k8s
- application-k8s4replic
- helm
- deployment.yaml
- service.yaml
- values.yaml
- 部署
- 小结
- Config Server
- pom.xml
- application.yaml
- helm
- deployment.yaml
- 小结
- Gateway
- pom.xml
- Gateway CircuitBreaker
- RequestRateLimiter
- KeyResolver
- 小结
- Client 组件
- 最佳实践
- 关于整合 CircuitBreaker
- 示例
- 小结
- Slueth
- common
- TraceFilter
- TraceFilterConfiguration
- 自动装配
- 总结
前言
Spring Cloud
的学习分享有一段时间了,之前多是从代码、配置角度去了解,最终还是要落地的,打算使用个人倾向的组件搭建一套微服务并给出容器化部署方案,顺便总结一下学习内容
个人倾向组件选择:
eureka
:服务注册、发现spring-cloud-loadbalancer
:负载均衡spring-cloud-circuitbreaker-resilience4j
:服务熔断openFeign
:服务调用,同时整合spring-cloud-loadbalance
,当前版本暂不能整合spring-cloud-circuitbreaker-resilience4j
,但后者完全可以单独使用spring-cloud-gateway
:网关spring-cloud-config
:配置中心spring-cloud-slueth
:链路排查、监控- 基于
docker + k8s + helm
技术栈部署
说明:
Spring Cloud BOM
定义版本号为2020.0.2
- 弃用
netflix
全家桶是因为Spring Cloud 2020
版本后已经移除对netflix
除eureka
外所有组件的支持 spring-cloud-loadbalancer
代替netflix-ribbon
,前者由Spring Cloud
提供,因而更加契合Spring
,且实现更加轻量级spring-cloud-circuitbreaker-resilience4j
代替spring-cloud-circuitbreaker-hystrix
,前者基于reslience4j
实现,提供更加丰富的组件功能比如:限流器、熔断器、重试服务 等- 服务调用依旧使用
openFeign
,最佳选择,同时支持整合所有包括ribbon
、spring-cloud-loadbalancer
等组件,当前版本暂不能整合spring-cloud-circuitbreaker-resilience4j
,后续会支持 spring-cloud-gateway
代替zuul
,前者由Spring Cloud
提供,更加契合Spring
,同时更加契合WebFlux
- 个人觉得容器时代下配置中心的作用没以前大,同时
spring-cloud-config
的配置更新还需要Spring Cloud Bus
或者Github Hook
的协助,更加倾向于使用类似diamond
的监听式配置中心,但此处还是保持Spring Cloud
生态完整性吧 spring-cloud-slueth
负责链路的记录,方便排查错误、分析性能- 分享个人基于
docker + k8s + helm
技术栈的部署方案,helm
版本v3.6.2
Eureka Server
Eureka Server
的使用相对简单,本文将体现:
- 启用
副本
模式 正本
与副本
复用一个jar
的helm
部署方案
pom.xml
<dependencies>
<!-- 框架依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 二方依赖 -->
<!-- 三方依赖 -->
</dependencies>
<build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>application.yaml</include>
<include>application-dev.yaml</include>
<include>application-${profiles.active}.yaml</include>
<include>*.xml</include>
<include>application-k8s4replic.yaml</include>
<include>application-replic.yaml</include>
</includes>
</resource>
</resources>
</build>
- 依赖
spring-cloud-starter-netflix-eureka-server
- 基于
maven profile
可选择性打包配置文件,默认添加application-replic.yaml
application-k8s4replic.yaml
以支持副本配置打包
ConfigData(配置文件)
application-k8s
eureka:
instance:
prefer-ip-address: true
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka-server-replic.default:8762/eureka/
application-k8s4replic
eureka:
instance:
prefer-ip-address: true
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka-server.default:8761/eureka/
- 开启副本模式互相注册
k8s
环境下我们以DNS
的方式获取注册地址
使用 DNS 方式而不是 环境变量 方式,是因为本文会采用 helm 部署,通常 helm
chart 是以 应用 为单位的,因此无法保证 service 组件的启动顺序而导致 环境
变量 无法正确获取到
当然也可以通过多创建几个 chart 解决上述问题以使用 环境变量 方式获取地址
helm
事实上,基于 helm
默认 chart
就能满足部署了,下面简单描述下调整到 默认模板
的地方
deployment.yaml
env:
{{- with .Values.env }}
{{- range . }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
containers
模块下增加env
模块支持镜像的 环境变量 配置- 基于此,我们可以使用
spring.profiles.active
顺序来控制启动的jar
是否副本 - 正副本的模板相同
service.yaml
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
{{- if eq .Values.service.type "NodePort" }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
- 简单调整
service
模板,以支持NodePort
类型 - 本文将采用
NodePort
来暴露服务 - 正副本的模板相同
values.yaml
image:
repository: shuiniudocker/sc-max-eureka-server
pullPolicy: Never
tag: "1.0"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: "eureka-server"
service:
type: NodePort
port: 8761
nodePort: 30001
env:
- name: spring.profiles.active
value: k8s
- 使用本地镜像,故拉取策略为
Never
fullnameOverride
覆盖service name
,呼应配置文件中的注册中心DNS
地址- 正本
NodePort
配置转发30001
端口至容器8761
端口 - 正本使用配置文件
application-k8s.yaml
- 副本针对
service
和env
的配置自行调整即可
部署
# 部署正本
helm install eureka-server ./eureka-server
# 部署副本
helm install eureka-server-replic ./eureka-server-replic
也可以复用同一个 chart,通过不同的 values.yaml 启动
小结
Eureka Server
的使用整体相对简单- 因为
Eureka Client
最终肯定是体现到各个服务中,故此处没有提到
Config Server
关于 Config Server
的个人理解:
- 有些环境(比如
容器
)下Config Server
与Repository
的连接可能涉及到网络问题,这种情况下可能会使用本地仓库等形式 - 配置项的刷新需要通过
/actuator/refresh
端口进行,可能需要Git Hook
或者Spring Cloud Bus
等手段的辅助 - 优秀的版本管理,
application
profile
label
的级别完美契合Git
的版本管理和Spring
对ConfigData
的管理
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
Config Server
端需要引入的是spring-cloud-config-server
,不同于常规的starter
模式- 引入
spring-cloud-starter-netflix-eureka-client
是期望客户端基于Eureka
发现Config Server
服务
application.yaml
spring:
application:
name: configserver
profiles:
active: native
cloud:
config:
server:
native:
search-locations: classpath:/, classpath:/config, file:./, file:./config
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8761/eureka
server:
port: 8888
- 这里是基于本地仓库管理配置,
spring.profiles.active=native
,默认配置文件路径为classpath:/, classpath:/config, file:./, file:./config
,对应的客户端配置放在上述路径即可 - 服务注册
helm
同样的,也是基于 默认模板
进行些许调整
deployment.yaml
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /client1/dev
port: http
readinessProbe:
httpGet:
path: /client1/dev
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
env:
{{- with .Values.env }}
{{- range . }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
Config Server
的存活探针
需要额外配置下,可以指定一个自定义的 endpoint
、存在的配置文件路径
或不存在的配置文件
等,如上述示例中是一个测试配置文件的访问路径/client1/dev
- 支持
env
属性的指定,主要用来覆盖application.yaml
,对应values.yaml
中的env
配置如下:
env:
- name: eureka.client.service-url.defaultZone
value: http://eureka-server.default:8761/eureka/,http://eureka-server-replic.default:8762/eureka/
有必要的话也可以覆盖对应 配置仓库 等属性
小结
Config Server
的使用也相对简单,重点是关注仓库的相关配置- 其他未提到的
helm
配置,即跟之前Eureka Server
的配置基本无异 Config Client
的配置体现在对应的服务中,此处没有体现
Gateway
关于 Gateway
的使用:
Spring Cloud
栈下提供了对各服务的默认Route
定义,前提是包含 服务发现 组件依赖,并开启配置项:spring.cloud.gateway.discovery.locator.enable=true
- 基于 服务发现 的默认路由规则为:
http://serviceId/*
route tohttp://lb:serviceId
,lb
前缀由专门的微服务负载均衡
组件拦截器处理 - 提供了大量现成的拦截器,本文主要示例
CircuitBreakerFilter
和RequestRateLimiter
的使用
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>application.yaml</include>
<include>application-dev.yaml</include>
<include>application-${profiles.active}.yaml</include>
</includes>
</resource>
</resources>
</build>
CircuitBreakerFilter
组件默认实现基于spring-cloud-starter-circuitbreaker-reactor-resilience4j
RequestRateLimiter
组件默认实现基于spring-boot-starter-data-redis-reactive
- 基于
Maven Profile
机制隔离配置文件打包
Gateway CircuitBreaker
Gateway
可以针对路由提供各种拦截器,其中就包括 CircuitBreakerFilter
,基于 resilience4j
提供熔断、超时等组件
@Bean
public Customizer<ReactiveResilience4JCircuitBreakerFactory> reactiveResilience4JCircuitBreakerCustomizer() {
return factory -> {
factory.configureDefault(
id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(
CircuitBreakerConfig.custom()
.recordException(e -> e instanceof RuntimeException)
.minimumNumberOfCalls(2)
.failureRateThreshold(50L)
.build()
)
.build()
);
};
}
- 创建对应的
ReactiveResilience4JCircuitBreakerFactory
(而不是Resilience4JCircuitBreakerFactory
) - 示例中提供的是一个默认配置,因此对所有
CircuitBreaker
生效
@Bean
public RouteLocator routeLocator(ConfigurableApplicationContext applicationContext) {
return new RouteLocatorBuilder(applicationContext)
.routes()
.route("circuit-breaker-route"
, p -> p.path("/test/circuitBreaker")
.filters(f -> f
.circuitBreaker(
c -> c.setName("circuitBreaker").setFallbackUri("forward:/fallback")
)
.stripPrefix(1)
)
.uri("http://localhost:9000")
)
.build();
}
- 提供一个路由配置,匹配路径
/test/circuitBreaker
,创建对应的CircuitBreaker
,基于之前的配置进行熔断、降级处理 - 指定降级处理路径为
fallback
RequestRateLimiter
spring:
cloud:
gateway:
filter:
request-rate-limiter:
deny-empty-key: false
routes:
- id: rateLimiter
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 30
redis-rate-limiter.requestedTokens: 30
uri: lb://CLIENT1
predicates:
- Path=/rateLimiter
- 限流的默认是基于
Redis
的令牌桶算法
实现 - 这里提供一个基于
ConfigurationProperties
的路由配置,其中:redis-rate-limiter.replenishRate
即每秒令牌生成数、redis-rate-limiter.burstCapacity
即令牌桶最大容量、redis-rate-limiter.requestedTokens
即每个请求消耗的令牌数,综上配置含义为:每三十秒允许一次请求 - 这里是针对单个路由的限流配置,当然也可以配置为
default-filters
以支持所有路由限流,也可以使用代码形式配置
KeyResolver
@Bean
public KeyResolver keyResolver() {
return exchange -> Mono.justOrEmpty(
Optional.ofNullable(exchange)
.map(ServerWebExchange::getRequest)
.map(ServerHttpRequest::getQueryParams)
.map(map -> map.getFirst("limitKey"))
.orElse(null)
);
}
- 上述为
KeyResolver
的配置示例,以参数limitKey
作为限流Key
- 如果解析结果为
null
,则基于spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key
配置决定是否拒绝该次请求
小结
Gateway
针对基于服务发现
的路由,提供了一套默认配置Gateway
提供了大量的拦截器
路由断言 实现,可基于代码
或者配置文件
直接配置使用,详情可参考 官方文档- 常规 helm 部署,略
Client 组件
服务间的调用通常依赖于客户端组件进行,最好用最常用的应该就是 OpenFeign
了
- 基于
Spring
的整合使用方便,注解式声明即可 - 基于单个客户端,可以提供自定义配置类进行个性化配置
- 无缝整合
spring-cloud-loadbalancer(ribbon)
- 暂时不能整合
spring-cloud-circuitbreaker-resilience4j
(但是可以整合hystrix
)
最佳实践
@FeignClient(contextId = "eureka-client-1-1", value = "eureka-client-1", configuration = EurekaClient1Client.EurekaClient1ClientFeignConfig.class)
@LoadBalancerClient(name = "eureka-client-1", configuration = EurekaClient1Client.EurekaClient1LoadBalancerConfig.class)
public interface EurekaClient1Client {
// @Configuration
static class EurekaClient1ClientFeignConfig {
@Bean
Logger.Level level() {
return Logger.Level.FULL;
}
}
static class EurekaClient1LoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
}
- 正如官方所说,不建议
客户端
与服务端
实现同一个接口,此处也正是如此,针对每一个 客户端 提供单独的接口,也方便我们以静态类
的形式高内聚
地提供对应的自定义配置类
- 关于配置类隔离客户端属性的原理,
spring-cloud-openfeign
和spring-cloud-loadbalancer
的实现差不多,之前的文章有分享过 @FeignClient
使用同value
属性不同contextId
属性,可以为同一个 客户端 提供不同配置的实现,有必要可以使用- 自定义配置类 不需要额外使用
@Configuration
注解修饰,否则会被视为 默认配置 - 上述示例为
eureka-client-1
提供了一个OpenFeignClient
,同时配置其日志记录级别为FULL
,其下的LoadBalancer
策略为RandomLoadBalancer
关于整合 CircuitBreaker
spring-cloud-openfeign
暂时不支持spring-cloud-circuitbreaker-resilience4j
,但后期会支持- 但是其实如果要使用
spring-cloud-circuitbreaker-resilience4j
,也完全可以单独整合的,但是不支持接口层的注解修饰,因此可能得从client
层提前到service
层进行CircuitBreakerFactory
的包装
示例
@Configuration
public class FeignCircuitBreakerConfig {
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> circuitBreakerFactoryCustomizer1() {
return factory -> factory.configure(
resilience4JConfigBuilder -> resilience4JConfigBuilder
.circuitBreakerConfig(
CircuitBreakerConfig
.custom()
.slowCallDurationThreshold(Duration.ofSeconds(1))
.slowCallRateThreshold(50)
.minimumNumberOfCalls(4)
.build()
)
.timeLimiterConfig(
TimeLimiterConfig
.custom()
.timeoutDuration(Duration.ofSeconds(3))
.build()
)
, "eureka-client"
);
}
}
=================================
@Service
public class EurekaClientServiceImpl implements EurekaClientService {
@Autowired
EurekaClient1Client eurekaClient1Client;
@Autowired
CircuitBreakerFactory circuitBreakerFactory;
@Override
public String delay(int time) {
return circuitBreakerFactory
.create("eureka-client")
.run(() -> eurekaClient1Client.delay(time));
}
}
- 上述示例提供
spring-cloud-circuitbreaker
配置类(基于resilience4j
):3s
的超时熔断 - 在
client
上层的service
层进行熔断包装(也可以基于注解
包装,但注解
并不是SpringCloud
原生提供,之前有文章分享过)
当然,如果使用旧版本的 SpringCloud,直接使用 hystrix 即可,跟
openfeign 也是无缝整合的
小结
spring-cloud-openfeign
实现服务间的调用- 整合
spring-cloud-loadbalancer
实现负载均衡
相关 - 有必要的话,单独依赖
spring-cloud-circuitbreaker-resilience4j
提供 熔断 等功能(或整合hystrix
) - 常规
helm
部署,略
Slueth
服务间的调用排查错误需要用到 链路监控 组件,此处整合 spring-cloud-slueth
- 通常是基于 拦截器 给
响应头
中添加traceId
信息,以方便在服务间追踪链路,这是一个通用的组件,可以抽象单独的common
层提供该组件 - 每个服务模块也可以添加对应的切面组件,来给整个业务逻辑链路也添加对应的
traceId
信息,以方便追踪调用链路
common
- 正如之前提到,可以抽象单独的
common
层来提供一些通用组件,或者一些顶层抽象 - 此处示例提供
TraceFilter
及其装配类,基于拦截器实现traceId
记录
TraceFilter
public class TraceFilter implements Filter {
private Tracer tracer;
public TraceFilter(Tracer tracer) {
this.tracer = tracer;
}
private static final String TRACE_ID = "traceId";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Optional.ofNullable(response)
.map(rep -> (HttpServletResponse) rep)
.ifPresent(rep -> rep.addHeader(TRACE_ID, Optional.ofNullable(tracer)
.map(t -> t.currentSpan())
.map(span -> span.context())
.map(context -> context.traceId())
.orElse("")));
chain.doFilter(request, response);
}
}
将 traceId 加入 响应头 中
TraceFilterConfiguration
@Configuration
public class TraceFilterConfiguration {
@Bean
@ConditionalOnMissingBean
public TraceFilter traceFilter(Tracer tracer) {
return new TraceFilter(tracer);
}
}
TraceFilter
组件注册- 也支持组件的覆盖
- 其实如果
common
层包含了大量组件并且想代入模块化
思想,其实也可以提供ConfigurationProperties
来控制各个组件功能的开关,当然基本上SpringCloud
组件都已经有自己的开关了,此处也就不示例了
自动装配
业务依赖于 common
层的组件包路径不可控,可以使用 自动装配 的方式加载组件,在 src/main/resources/META-INF/spring.factories
文件中添加对应的组件类路径,比如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.xsn.demo.sc.max.common.config.sleuth.TraceFilterConfiguration
总结
- 算是个
just run
分享吧,但是其中还是有一些个人对Spring Cloud
组件选择的理解的 - 对一些
核心组件
的配置、微服务组件
的组合、部署方案
等给出了个人的最佳实践分享
最后
以上就是土豪小甜瓜为你收集整理的【Spring Cloud】Spring Cloud 组件个人最佳实践分享前言Eureka ServerConfig ServerGatewayClient 组件Slueth总结的全部内容,希望文章能够帮你解决【Spring Cloud】Spring Cloud 组件个人最佳实践分享前言Eureka ServerConfig ServerGatewayClient 组件Slueth总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复