我是靠谱客的博主 土豪小甜瓜,这篇文章主要介绍【Spring Cloud】Spring Cloud 组件个人最佳实践分享前言Eureka ServerConfig ServerGatewayClient 组件Slueth总结,现在分享给大家,希望可以做个参考。
【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.yamlapplication-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等手段的辅助 - 优秀的版本管理,
applicationprofilelabel的级别完美契合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-resilience4jRequestRateLimiter组件默认实现基于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内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复