Spring Cloud Alibaba 本地调试介绍及方案设计
作者:isea533 发布时间:2023-05-26 21:24:26
1 本地调试介绍
本地调试: 这里是指在开发环境中,部署了一整套的某个项目或者产品的服务,开发人员开发时,本地会起一个或多个服务,这些服务和开发环境中部署的服务是相同的,这种情况下,一个服务就会有多个实例,大多数微服务中的默认负载均衡策略都是轮询,这些实例会轮流被调用。
为了方便 本地调试,需要提供一种策略,可以指定在负载均衡时,选择哪个实例进行调用。在使用 Nacos 作为注册中心时,可以通过 上线和下线 的方式来选择使用哪个实例,但是这种方式只能强制调用某个实例,如果开发环境还有其他人在调试,自己程序 设置断点 时会阻塞所有调用,非常不利于多人调试的协调。
为了解决 本地调试 的问题,本文实现了一种简单实用的策略,可以通过 Nacos 动态配置服务路由,还可以基于用户,部门,组织等级别配置服务路由,实现 本地调试 的同时,实际上也实现 灰度发布。
2 框架环境
本文基于 Spring Cloud Alibaba 框架,和 Spring Cloud 相比增加了一部分针对 Dubbo 的方案,因此本文适合以下框架参考:
Spring Cloud Alibaba
Spring Cloud
Spring Cloud Gateway
Spring Cloud Ribbon
Dubbo
下图是 Spring Cloud Alibaba 框架中,一次方法调用的可能情况,Ailbaba 这部分多的是图中 ServiceA -> ServiceB
部分使用 Dubbo 协议。Spring Cloud 框架中,用的是 ServiceA -> ServiceC
这种 Feign(HTTP) 方式。
图中的所有过滤器和 * ,虽然名称不同,但是作用相同。这部分的主要作用就是 获取或传递路由规则,例如,可以实现基于 HTTP Header 设置路由规则的配置,可以基于 HTTP 和 token 实现基于用户的路由规则配置,这部分的实现和需求有关,没有统一的实现。
3 方案设计
这里以这两种场景简单举个例子。
3.1 基于 HTTP Header 的本地调试方案
在这个方案中,按照上面的流程图叙述一遍。
用户调用服务前,在 Header 中设置调用规则,比如增加
service-route
请求头,请求头的内容为servicea:10.10.10.130;serviceb:10.10.10.100;servicec:10.10.10.0/24
,在请求头中指明需要控制路由的服务信息(不需要控制的直接省略走默认)。通过 Spring Cloud Gateway 的 GlobalFilter 实现提取请求头信息,将配置信息记录下来(如
ThreadLocal
)负载均衡时,根据这里的配置选择优先路由的服务,调用 ServiceA 时,仍然是 HTTP 请求,请求头会传递过去。
* 获取请求头中的路由规则,这一步和 1 类似,但是属于 Spring MVC 的 * ,获取路由规则后记录下来(如
ThreadLocal
)ServiceA 调用 ServiceB 是 Dubbo 协议的路径,和 7,8 Feign 方式没有先后顺序,是两个分支。 在 4 这一步通过 Dubbo 的 Consumer Filter 过滤器和
RpcContext
将路由信息记录到attachment
中,这样可以把路由配置传递到 ServiceB,如果 ServiceB 还需要调用其他服务,路由仍然会起到作用。在 Dubbo 的 Router 实现中,根据路由信息选择优先调用的服务,然后进行调用。
Dubbo 的 Provider Filter 从
RpcContext
获取路由配置,记录下来(如ThreadLocal
),如果后续调用其他服务,逻辑和 4,5,6一样。在 6 这一步的 Provider Filter 结束调用的时候,注意清空路由信息(如ThreadLocal.clear()
),避免对其他调用产生污染。这一步和4,5,6没有顺序关系,是纯 Spring Cloud 方式的调用,在 ServiceA 调用时,通过自定义 Ribbon 中的
IRule
实现基于自己路由规则的调用。在最终调用 ServiceC 之前,通过 Feign 的
RequestInterceptor
* 添加service-route
头,将服务路由传递下去。和第3步相同,通过 Spring MVC * 获取服务路由记录下来。后续在调用其他服务时,Dubbo服务走4,5,6,Feign方式走7,8,9。
3.2 基于操作用户的本地调试方案
基于操作用户的方案中,和上面类似,但是不需要在每次请求的时候设置 HTTP Header,但是需要一种方式存取服务路由的配置。
这里以使用 Nacos 配置管理实现服务路由配置的存取。
根据自己使用的用户在 Nacos 配置服务路由,配置名规则如 服务名.user-routes
,使用 Spring Cloud Alibaba 的默认组 dubbo
,用户服务路由的配置规则可以自己定义,这里举个简单例子:
enabled: true # 启用,停用
ip: 10.10.0.0/24 # 默认优先IP或网段,所有IP都支持具体IP和网段
userIps: # Map<Long, String>,优先级最高,针对用户配置 IP 优先
# userId: IP
1: 10.10.0.100
2: 10.10.0.101
# 这部分定义根据自己需要设计
deptIps: # 针对部门配置
# deptId: IP
1: 10.10.0.0/24
orgIps: # 针对组织配置
# orgId: IP
1: 10.10.10.0/24
Spring Cloud Gateway 的 GlobalFilter 根据请求 token 获取 用户信息,记录用户信息(如 ThreadLocal
)。
负载均衡时,使用 Nacos
ConfigService
,根据服务名.user-routes
查询配置信息,同时监听该配置信息,根据这里的配置选择优先路由的服务。* 根据请求 token 获取 用户信息,记录用户信息(如
ThreadLocal
)。ServiceA 调用 ServiceB 是 Dubbo 协议的路径,和 7,8 Feign 方式没有先后顺序,是两个分支。 在 4 这一步通过 Dubbo 的 Consumer Filter 过滤器和
RpcContext
将用户信息记录到attachment
中,这样可以把用户信息传递到 ServiceB,如果 ServiceB 还需要调用其他服务,用户信息仍然会起到作用。在 Dubbo 的 Router 实现中,根据路由信息选择优先调用的服务,然后进行调用。
Dubbo 的 Provider Filter 从
RpcContext
获取用户信息,记录下来(如ThreadLocal
),如果后续调用其他服务,逻辑和 4,5,6一样。在 6 这一步的 Provider Filter 结束调用的时候,注意清空用户信息(如ThreadLocal.clear()
),避免对其他调用产生污染。这一步和4,5,6没有顺序关系,是纯 Spring Cloud 方式的调用,在 ServiceA 调用时,通过自定义 Ribbon 中的
IRule
实现基于自己路由规则的调用。在最终调用 ServiceC 之前,通过 Feign 的
RequestInterceptor
* 设置token或用户信息,将操作用户传递下去。和第3步相同,通过 Spring MVC * 获取用户信息记录下来。后续在调用其他服务时,Dubbo服务走4,5,6,Feign方式走7,8,9。
本文选择第 2 种方案,针对 1~9 步,分别讲解需要实现的接口和接口应用(生效)的配置。
4 实现要点
上面提到的 ThreadLocal
,实现时使用一个 static
变量存储,提供相应的存取清空的静态方法,方便跨接口的 用户信息 传递。
4.1 Spring Cloud Gateway 全局过滤器
假设有一个 UserGlobalFilter
,该过滤器根据 token
获取并缓存用户信息,在请求完成后需要清空缓存的用户信息。
Spring Cloud Gateway 中的过滤器,直接在 @Configuration
的配置类中用 @Bean
提供即可。
4.2 Ribbon 负载均衡
实现 ribbon-loadbalancer 中的 com.netflix.loadbalancer.IRule
接口,将来调用具体服务时通过 choose
接口返回符合条件的实例。
实现这个接口之后,需要特殊的方式注册该接口,在启动类增加注解 @RibbonClients(defaultConfiguration = UserRuleConfiguration.class)
,
注解中指定了一个配置类,这个类一定不要添加 @Configuration
注解!!!。
在这个类中,通过 @Bean
注解返回一个 IRule
接口的实现。
在 Ribbon 中,会创建一个新的 ApplicationContext 来初始化这些配置,在这个新的 ApplicationContext 中,配置的 IRule
实现会被使用。
4.3 Spring MVC *
实现 HandlerInterceptor
* ,从请求获取用户信息并记录下来。
* 想要生效,需要提供一个配置类,继承 WebMvcConfigurer
接口,实现 addInterceptors
方法,在这个方法实现中添加 * 的实现类。
4.4 Dubbo Consumer Filter 过滤器
实现Dubbo 的Filter接口,通过 RpcContext
传递前面记录的用户信息。
可以在实现类添加 @Activate
注解,指定 group
为 CommonConstants.CONSUMER
。
按照 dubbo SPI 要求,添加 META-INF/dubbo/org.apache.dubbo.rpc.Filter
文件,写上实现类。
4.5 Dubbo Router 路由
这一步实际上可以放在 Dubbo 负载均衡实现,也可以用 Router 实现。
使用 Router 时,需要同时使用 RouterFactory
和 Router
接口,然后配置 RouterFactory
的 SPI 配置文件。
在 Router
的 route
方法中根据规则返回合适的 Invoker
。
4.6 Dubbo Provider Filter 过滤器
实现Dubbo 的Filter接口,通过 RpcContext
获取传递过来的用户信息。
可以在实现类添加 @Activate
注解,指定 group
为 CommonConstants.PROVIDER
。
按照 dubbo SPI 要求,添加 META-INF/dubbo/org.apache.dubbo.rpc.Filter
文件,写上实现类。
这个实现类可以和 4.4 的放一个 Filter 实现中,需要自己区分当前是 consumer 还是 provider 实现不同的逻辑。
4.7 Ribbon 负载均衡,同 4.2
这一步的实现和 4.2 一样,4.2 是用在 Spring Cloud Gateway 中,这里是配置到具体的服务中。配置方式一样。
4.8 Feign RequestInterceptor *
首先实现 RequestInterceptor
接口,在实现中往 requst 的 Header 中放置要传递的数据。
接口想要生效,需要和 Ribbon 类似的配置。
在 @EnableFeignClients
的注解中,通过 defaultConfiguration
设置一个 Feign 的配置类。在这个配置中通过 @Bean
提供 RequestInterceptor
接口的实现。
4.9 Spring MVC * ,同 4.3
4.3 中是网关调用服务,4.9是服务通过 Feign (或resttemplate)调用服务,对被调用的服务来说都是 HTTP 请求,因此都会执行 Spring MVC 的 * ,所以这里的实现是一样的。
5. 总结
本文提供了本地调试的方案和主要的实现要点,可以根据文中的关键指引和自己的实际需求实现自己的方案。关于本地调试如果有更好的方案,欢迎留言讨论。
附:工具方法
判断IP是否相等或输入子网IP的方法:
public static boolean ipInRange(String ip, String cidr) {
if(cidr.indexOf('/') < 0) {
return ip.equals(cidr);
}
int ipAddr = ipToInt(ip);
int type = Integer.parseInt(cidr.replaceAll(".*/", ""));
String cidrIp = cidr.replaceAll("/.*", "");
if(type == 32){
return ip.equals(cidrIp);
}
int cidrIpAddr = ipToInt(cidrIp);
int mask = 0xFFFFFFFF << (32 - type);
return (ipAddr & mask) == (cidrIpAddr & mask);
}
public static int ipToInt(String ip) {
String[] ips = ip.split("\\.");
return (Integer.parseInt(ips[0] << 24) |
Integer.parseInt(ips[1] << 16) |
Integer.parseInt(ips[2] << 8) |
Integer.parseInt(ips[3]));
}
来源:https://blog.csdn.net/isea533/article/details/118513868


猜你喜欢
- 附GitHub源码:WebViewExplore一、WebView的基础配置WebSettings ws = getSettings();w
- 本文实例分析了JAVA反射机制。分享给大家供大家参考,具体如下:反射,当时经常听他们说,自己也看过一些资料,也可能在设计模式中使用过,但是感
- Android EditText限制输入字符的方法总结最近项目要求限制密码输入的字符类型, 例如不能输入中文。 &nb
- springboot 启动项目打印接口列表环境springboot 2.3.2.RELEASE修改配置文件logging: le
- 本文实例为大家分享了java库存管理系统的具体代码,供大家参考,具体内容如下模拟真实的库存管理逻辑,完成超市管理系统的日常功能实现。经过分析
- 以下代码为一个工具类package com.imooc.reflect;import java.lang.reflect.Method;pu
- 依赖注入介绍先回顾下依赖注入的概念:我们常提起的依赖注入(Dependency Injection)和控制反转(Inversion of C
- 本文实例讲述了Java链表中元素删除的实现方法。分享给大家供大家参考,具体如下:该部分与上一节是息息相关的,关于如何在链表中删除元素,我们一
- 最近碰到这么个恶心的问题问题:有个arr文件被放到Module A中引用,现在Module B又依赖了Module A,则在编译过程中会发生
- 背景很多时候我们使用WPF开发界面的时候经常会用到各种空间,很多时候我们需要去自定义控件的样式来替换默认的样式,今天通过两个方法来替换WPF
- 1.预警需求为了更好的管理商品日期,需要对仓库的商品进行预警管理,对商品的保质期控制在一个范围内提示出来,也可以通过该功能间接的展示出一个商
- 这里给一个样例树:代码:#include <stdio.h> #include <string.h>#include
- Spring P标签的使用Spring的p标签是基于XML Schema的配置方式,目的是为了简化配置方式。由于Spring的p标签是spr
- 前言用户注册功能是每一个系统的入口门面功能,很多人可能会以为很简单,不就是一个简单的CRUD吗?其实不然,要把前后端功能都做出来,页面跳转也
- 当我们在项目中登录使用验证码的时候,不妨试试Kaptcha生成验证码,非常简单1、首先,我们在pom.xml文件中引入kaptcha的mav
- 最近项目上产品经理提了个需求,要求关闭语言国际化,不管手机系统设置那个国家的语言,都要显示汉语,好吧,既然有需求,那就做吧。但是项目中已经有
- 最近项目中使用了 MyBatis-Plus,点击看官方文档。使用一个新的框架,首先是验证框架的使用。 使用 MyBatis-Plu
- 前言嗯。最近工程上遇到一个byte数组转换为int的问题,解决过程中遇到了几个坑,经过各种查资料终于还是解决了。撒花。Java的位运算以及b
- 一、二叉排序树定义1.二叉排序树的定义二叉排序树(Binary Sort Tree)又称二叉查找(搜索)树(Binary Search Tr
- 本文实例讲述了Android Dialog对话框用法。分享给大家供大家参考,具体如下:Activities提供了一种方便管理的创建、保存、回