微服务中的nacos,Ribbon,Feign,Gateway
nacos,ribbon,gateway,feign
nacos
官方文档: https://nacos.io/zh-cn/docs/what-is-nacos.html
注册中心
集群模式

prometheus+grafana监控Nacos
https://nacos.io/zh-cn/docs/monitor-guide.html
Nacos注册中心架构

核心功能
服务注册:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信
息存储在一个双层的内存Map中。
服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。
服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。
服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存
服务健康检查:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新
注册)
服务注册表结构

注册中心配置
spring:
application:
name: pearl-test
cloud:
nacos:
discovery:
# 是否开启Nacos注册
enabled: true
# Nacos服务注册地址
server-addr: localhost:8848
# Nacos 认证用户
username: nacos
# Nacos 认证密码
password: 123456
# 配置命名空间ID
namespace: ba42e722-81aa-48f1-9944-9dca57d5f396
# 分组名称
group: PEARL_GROUP
# 连接Nacos Server指定的连接点
#endpoint: localhost
# 设置注册时本服务IP地址
#ip: 127.0.0.1
# nacos客户端向服务端发送心跳的时间间隔,单位s
#heart-beat-interval: 5
# 集群名称
#cluster-name: DEFAULT
# 心跳超时时间,单位s
#heart-beat-timeout: 15
# 是否注册服务,默认为true
#register-enabled: true
# 当要上阿里云时,阿里云上面的一个云账号名
#access-key:
# 当要上阿里云时,阿里云上面的一个云账号密码
#secret-key:
# nacos客户端日志名,默认naming.log:
#log-name:
# 服务元数据标签
#metadata:
# 服务超时时,多少秒后删除
#ip-delete-timeout: 30
# 负载均衡权重,默认1,取值范围 1 到 100,数值越大,权重越大
#weight: 1
# 监视延迟,从nacos服务器拉取新服务的持续时间,单位ms
#watch-delay: 30000
# 注册服务时的服务名,默认${spring.application.name}
#service: pearl-service
# 服务是否是https
#secure: false
# 注册时本服务的端口,无需设置,自动探测
#port: 8088
# 选择固定网卡
#network-interface: eth0
# 是否从本地缓存中,默认false
#naming-load-cache-at-start: false
配置中心
官方文档: https://github.com/alibaba/springcloudalibaba/wiki/Nacosconfig
配置
spring.application.name=nacos‐config
# 配置中心地址
spring.cloud.nacos.config.server‐addr=127.0.0.1:8848
# dataid 为 yaml 的文件扩展名配置方式
# `${spring.application.name}.${file‐extension:properties}`
spring.cloud.nacos.config.file‐extension=yaml
#profile粒度的配置 `${spring.application.name}‐${profile}.${file‐extension:properties}`
spring.profiles.active=prod
#支持自定义 namespace 的配置 默认使用的是 Nacos 上 Public 这个
namespace 不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。
spring.cloud.nacos.config.namespace=12bb2385‐231f‐4eca‐b4dc‐6be446e120
#支持自定义 Group 的配置 默认是DEFAULT_GROUP 配置分组的常见场景:不同的应用或组件使用了相同的配置类型,如 database_url 配置和
MQ_topic 配置
spring.cloud.nacos.config.group=database_url
# 支持自定义扩展的 Data Id 配置 通过自定义扩展的 Data Id 配置,既可以解决多个应用间配置共享的问题,又可以支持一个应用有多个配置文件。
#不同工程的通用配置 支持共享的 DataId
spring.cloud.nacos.config.sharedConfigs[0].data‐id= common.yaml
spring.cloud.nacos.config.sharedConfigs[0].group=REFRESH_GROUP
spring.cloud.nacos.config.sharedConfigs[0].refresh=true
# config external configuration
# 支持一个应用多个 DataId 的配置
spring.cloud.nacos.config.extensionConfigs[0].data‐id=ext‐config‐common01.properties
spring.cloud.nacos.config.extensionConfigs[0].group=REFRESH_GROUP
spring.cloud.nacos.config.extensionConfigs[0].refresh=true
spring.cloud.nacos.config.extensionConfigs[1].data‐id=ext‐config‐common02.properties
spring.cloud.nacos.config.extensionConfigs[1].group=REFRESH_GROUP spring.cloud.nacos.config.extensionConfigs[1].refresh=true
优先级
- nacosconfigproduct.yaml 精准配置
- nacosconfig.yaml 同工程不同环境的通用配置
- extconfig: 不同工程 扩展配置
- shareddataids 不同工程通用配置:
配置中心架构


获取配置
获取配置的主要方法是 NacosConfigService 类的 getConfig 方法,通常情况下该方法直接从本地文件中取得配置
的值,如果本地文件不存在或者内容为空,则再通过 HTTP GET 方法从远端拉取配置,并保存到本地快照中。当通
过 HTTP 获取远端配置时,Nacos 提供了两种熔断策略,一是超时时间,二是最大重试次数,默认重试三次。
** 注册监听器**
配置中心客户端会通过对配置项注册监听器达到在配置项变更的时候执行回调的功能。
1 NacosConfigService#getConfigAndSignListener
2 ConfigService#addListener
Nacos 可以通过以上方式注册监听器,它们内部的实现均是调用 ClientWorker 类的 addCacheDataIfAbsent。其
中 CacheData 是一个维护配置项和其下注册的所有监听器的实例,所有的 CacheData 都保存在 ClientWorker 类中的
原子 cacheMap 中,其内部的核心成员有:
配置长轮询
ClientWorker 通过其下的两个线程池完成配置长轮询的工作,一个是单线程的 executor,每隔 10ms 按照每 3000 个
配置项为一批次捞取待轮询的 cacheData 实例,将其包装成为一个 LongPollingTask 提交进入第二个线程
池 executorService 处理。
配置发布
发布配置的代码位于 ConfigController#publishConfig中。集群部署,请求一开始也只会打到一台机器,这台机器
将配置插入Mysql中进行持久化。服务端并不是针对每次配置查询都去访问 MySQL ,而是会依赖 dump 功能在本地文
件中将配置缓存起来。因此当单台机器保存完毕配置之后,需要通知其他机器刷新内存和本地磁盘中的文件内容,因此
它会发布一个名为 ConfigDataChangeEvent 的事件,这个事件会通过 HTTP 调用通知所有集群节点(包括自身),触
发本地文件和内存的刷新。
处理长轮询
客户端会有一个长轮询任务,拉取服务端的配置变更,服务端处理逻辑在LongPollingService类中,其中有一
个 Runnable 任务名为ClientLongPolling,服务端会将受到的轮询请求包装成一个 ClientLongPolling 任务,该任务持
有一个 AsyncContext 响应对象,通过定时线程池延后 29.5s 执行。比客户端 30s 的超时时间提前 500ms 返回是为了
最大程度上保证客户端不会因为网络延时造成超时。
为什么将轮询请求包装成一个ClientLongPolling任务并且使用定时线程池延后29.5秒执行,这实际上是一种优化手段,背后有以下几个考虑:
减少资源占用:如果服务器直接为每个长轮询请求都分配一个线程并保持连接打开,会消耗大量的系统资源。通过将请求包装成任务并在定时线程池中延迟执行,可以减少长时间占用线程的数量。
批量处理:延迟执行允许服务器将多个客户端的长轮询请求聚集在一起处理。如果在29.5秒内发生了配置变更,服务器可以一次性通知所有等待的客户端,而不是逐个处理。
以下是具体的几个原因:
避免频繁的请求:如果客户端发送的轮询请求立即得到响应,那么在配置没有变更的情况下,客户端可能会频繁发起请求,造成不必要的网络开销。
平衡实时性和资源消耗:29.5秒是一个经验值,它可以在实时性和资源消耗之间找到一个平衡点。这个时间窗口足够小,能够保证配置变更可以较快地推送到客户端,而又不会太短导致频繁的请求和响应。
利用HTTP Keep-Alive:HTTP长连接通常有一定的超时时间,而29.5秒的延时执行可以确保在一个HTTP连接的生命周期内完成,从而充分利用HTTP Keep-Alive机制,减少频繁建立和关闭连接的成本。
超时机制:如果29.5秒内没有配置变更,这个任务会触发执行,此时服务器可以响应客户端,告诉它当前没有变更,客户端可以继续等待或根据自身逻辑进行下一轮轮询。
ribbon
Spring Cloud Ribbon是基于Netflix Ribbon 实现的一套客户端的负载均衡工具,Ribbon客户端组件提供一系列的完善的配置,如超时,重试等。通过Load Balancer获取到服务提供的所有机器实例,Ribbon会自动基于某种规则(轮询,随机)去调用这些服务。Ribbon也可以实现我们自己的负载均衡算法。
目前主流的负载方案分为以下两种:
集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如F5),也有软件的(比如 Nginx)。
客户端根据自己的请求情况做负载均衡,Ribbon 就属于客户端自己做负载均衡。
添加@LoadBalanced注解
@LoadBalanced 注解原理
@LoadBalanced利用@Qualifier作为restTemplates注入的筛选条件,筛选出具有负载均衡标识的RestTemplate。
被@LoadBalanced注解的restTemplate会被定制,添加LoadBalancerInterceptor拦截器。
Ribbon相关接口
参考: org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
IClientConfig:Ribbon的客户端配置,默认采用DefaultClientConfigImpl实现。
IRule:Ribbon的负载均衡策略,默认采用ZoneAvoidanceRule实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。
IPing:Ribbon的实例检查策略,默认采用DummyPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服务实例都是可用的。
ServerList:服务实例清单的维护机制,默认采用ConfigurationBasedServerList实现。
ServerListFilter:服务实例清单过滤机制,默认采ZonePreferenceServerListFilter,该策略能够优先过滤出与请求方处于同区域的服务实例。
ILoadBalancer:负载均衡器,默认采用ZoneAwareLoadBalancer实现,它具备了区域感知的能力。
Ribbon负载均衡策略
- RandomRule: 随机选择一个Server。
- RetryRule: 对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server。
- RoundRobinRule: 轮询选择, 轮询index,选择index对应位置的Server。
- AvailabilityFilteringRule: 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其
实就是检查status里记录的各个Server的运行状态。 - BestAvailableRule: 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。
- WeightedResponseTimeRule: 根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低。
- ZoneAvoidanceRule: 默认的负载均衡策略,即复合判断Server所在区域的性能和Server的可用性选择Server,在没有区域的环境下,类似于轮询(RandomRule)
- NacosRule: 同集群优先调用
修改默认负载均衡策略
全局配置:调用其他微服务,一律使用指定的负载均衡算法
局部配置:调用指定微服务提供的服务时,使用对应的负载均衡算法
自定义负载均衡策略
通过实现 IRule 接口可以自定义负载策略,主要的选择服务逻辑在 choose 方法中。
1)实现基于Nacos权重的负载均衡策略
@Slf4j
2 public class NacosRandomWithWeightRule extends AbstractLoadBalancerRule {
3
4 @Autowired
5 private NacosDiscoveryProperties nacosDiscoveryProperties;
6
7 @Override
8 public Server choose(Object key) {
9 DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadB
alancer();
10 String serviceName = loadBalancer.getName();
11 NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
12 try {
13 //nacos基于权重的算法
14 Instance instance = namingService.selectOneHealthyInstance(serviceName);
15 return new NacosServer(instance);
16 } catch (NacosException e) {
17 log.error("获取服务实例异常:{}", e.getMessage());
18 e.printStackTrace();
19 }
20 return null;
21 }
22 @Override
23 public void initWithNiwsConfig(IClientConfig clientConfig) {
24
25 }
26 }
局部配置:
修改application.yml
1 # 被调用的微服务名
2 mall‐order:
3 ribbon:
4 # 自定义的负载均衡策略(基于随机&权重)
5 NFLoadBalancerRuleClassName: com.tuling.mall.ribbondemo.rule.NacosRandomWithWeightRule
全局配置
1 @Bean
2 public IRule ribbonRule() {
3 return new NacosRandomWithWeightRule();
4 }
饥饿加载
在进行服务调用的时候,如果网络情况不好,第一次调用会超时。
Ribbon默认懒加载,意味着只有在发起调用的时候才会创建客户端。
开启饥饿加载,解决第一次调用慢的问题
1 ribbon:
2 eager‐load:
3 # 开启ribbon饥饿加载
4 enabled: true
5 # 配置mall‐user使用ribbon饥饿加载,多个使用逗号分隔
6 clients: mall‐order
CAP
feign
feign的设计
Spring Cloud Alibaba快速整合Feign
扩展机制
1.日志
有时候我们遇到 Bug,比如接口调用失败、参数没收到等问题,或者想看看调用性能,就需要配置 Feign 的日志了,以此让 Feign 把请求信息输出来。
通过源码可以看到日志等级有 4 种,分别是:
NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。
全局配置:
局部配置

通过拦截器实现认证
feign.RequestInterceptor
每次 feign 发起http调用之前,会去执行拦截器中的逻辑。
自定义拦截器
yml中个别服务配置
feign:
client:
config:
mall‐order: #对应微服务
requestInterceptors[0]: #配置拦截器
com.leecurry.mall.feigndemo.interceptor.FeignAuthRequestInterceptor
被调用端如何获取
1.@RequestHeader获取请求参数
2.建议通过过滤器(filter)和拦截器(HandlerInterceptor)处理



超时时间配置
客户端组件配置
Feign 中默认使用 JDK 原生的 URLConnection 发送 HTTP 请求,我们可以集成别的组件来替换掉URLConnection,比如 Apache HttpClient,OkHttp。
Feign发起调用真正执行逻辑:feign.Client#execute (扩展点)
然后修改yml配置,将 Feign 的 Apache HttpClient启用 :

编码器解码器配置
Feign 中提供了自定义的编码解码器设置,同时也提供了多种编码器的实现,比如 Gson、Jaxb、Jackson。
我们可以用不同的编码解码器来处理数据的传输。如果你想传输 XML 格式的数据,可以自定义 XML 编码解码器来实现获取使用官方提供的 Jaxb。
扩展点:Encoder & Decoder
yml配置方式
Spring Cloud Gateway
网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
工作原理

路由(route) 断言(predicates)
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
断言:Java8中的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允
许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
路由断言工厂(Route Predicate Factories)配置
时间匹配
Cookie匹配
Header匹配
路径匹配
自定义路由断言工厂
自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以
通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。
注意: 命名需要以 RoutePredicateFactory 结尾
1 @Component
2 @Slf4j
3 public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredic
ateFactory.Config> {
4
5 public CheckAuthRoutePredicateFactory() {
6 super(Config.class);
7 }
8
9 @Override
10 public Predicate<ServerWebExchange> apply(Config config) {
11 return new GatewayPredicate() {
12
13 @Override
14 public boolean test(ServerWebExchange serverWebExchange) {
15 log.info("调用CheckAuthRoutePredicateFactory" + config.getName());
16 if(config.getName().equals("fox")){
17 return true;
18 }
19 return false;
20 }
21 };
22 }
23
24 /**
25 * 快捷配置
26 * @return
27 */
28 @Override
29 public List<String> shortcutFieldOrder() {
30 return Collections.singletonList("name");
31 }
32
33 public static class Config {
34
35 private String name;
36
37 public String getName() {
38 return name;
39 }
40
41 public void setName(String name) {
42 this.name = name;
43 }
44 }
45 }
1 spring:
2 cloud:
3 gateway:
4 #设置路由:路由id、路由到微服务的uri、断言
5 routes:
6 ‐ id: order_route #路由ID,全局唯一
7 uri: http://localhost:8020 #目标微服务的请求地址和端口
8 predicates:
9 # 测试:http://localhost:8888/order/findOrderByUserId/1
10 ‐ Path=/order/** #Path路径匹配
11 #自定义CheckAuth断言工厂
12 # ‐ name: CheckAuth
13 # args:
14 # name: fox
15 ‐ CheckAuth=fox
过滤器工厂( GatewayFilter Factories)配置
SpringCloudGateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加
剔除响应头,添加去除参数等
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
添加请求头
添加请求参数
为匹配的路由统一添加前缀
重定向操作
自定义过滤器工厂
继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。
配置自定义的过滤器工厂
1 spring:
2 cloud:
3 gateway:
4 #设置路由:路由id、路由到微服务的uri、断言
5 routes:
6 ‐ id: order_route #路由ID,全局唯一
7 uri: http://localhost:8020 #目标微服务的请求地址和端口
8 #配置过滤器工厂
9 filters:
10 ‐ CheckAuth=fox,男
11
全局过滤器(Global Filters)配置
LoadBalancerClientFilter
LoadBalancerClientFilter 会查看exchange的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的
值(一个URI),如果该值的scheme是 lb,比如:lb://myservice ,它将会使用Spring Cloud的LoadBalancerClient 来将 myservice 解析成实际的host和port,并替换掉ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的内
容。
自定义全局过滤器
TOKEN
1 @Component
2 @Order(‐1)
3 @Slf4j
4 public class CheckAuthFilter implements GlobalFilter {
5 @Override
6 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
7 //校验请求头中的token
8 List<String> token = exchange.getRequest().getHeaders().get("token");
9 log.info("token:"+ token);
10 if (token.isEmpty()){
11 return null;
12 }
13 return chain.filter(exchange);
14 }
15 }
16
黑白名单
17 @Component
18 public class CheckIPFilter implements GlobalFilter, Ordered {
19
20 @Override
21 public int getOrder() {
22 return 0;
23 }
24
25 @Override
26 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
27 HttpHeaders headers = exchange.getRequest().getHeaders();
28 //模拟对 IP 的访问限制,即不在 IP 白名单中就不能调用的需求
29 if (getIp(headers).equals("127.0.0.1")) {
30 return null;
31 }
32 return chain.filter(exchange);
33 }
34
35 private String getIp(HttpHeaders headers) {
36 return headers.getHost().getHostName();
37 }
38 }
Gateway跨域配置(CORS Configuration)

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)