Sentinel配置持久化到Apollo实战记录

## 准备工作

最近在接入Sentinel限流中间件中遇到的一些问题和理解写于此,也作为一个学习限流中间件的里程碑阶段总结。

Sentinel的部署很简单,Github下载代码启动,或者下周sentinel-dashboard.jar包启动,Sentinel是一个标准SpringBoot工程,启动方便.

启动命令:java -jar sentinel-dashboard.jar

代码地址:https://github.com/alibaba/Sentinel

启动后进入控制台只会看到一个空的首页,必须要接入的客户端调用过一次接口,控制台再次刷新页面才会显示接入的客户端系统名称.如果还未出现,那么只能说明客户端接入配置错误了,请检查接入的客户端配置.Springboot项目请参考这个配置,

引入mvn包,要注意的是这个starter用的是2.6.x版本的Dubbo,我是用2.7.x版本的,所以要排查掉这个sentinel-dubbo-adapter包,因为这个包是支持2.6.x版本的,2.7.x不支持.

再额外把2.7.x的支持sentinel-apache-duboo-adapter引进来.如果dubbo有问题的同学自己要注意一下这个点了,可能会导致你的应用都启动不了.

其他的接入方式就暂且不表,由各位自行参考官方文档.

 

接入Sentinel遇到的问题:

大部分人刚开始接触到Sentinel,都会以为一整套都已经完成了,开箱即用,确实是开箱即用,不过也只是研发环境自己玩是足够的,如果想引入到生产还要完成一些必要的工作,下面就是我在研发环境玩个把星期后慢慢发现了一些问题要自己去理解去解决的,其中最重要的就是配置规则持久化,本篇文章也正是围绕解决这个问题而编写.

 

  1. 客户端引入jar后裸奔启动,Sentinel只对Dubbo接口进行优先适配(前提是你引入了Dubbo适配的jar),区分2.6-Alibaba Dubbo和2.7-Apache Dubbo版本。
  2. Sentinel默认的规则配置只存储于内存中,无论是Dashboard控制台重启还是客户端方重启,规则都会清空。
  3. Sentinel接入Apollo只是帮助存储而已,客户端只是在Dashboard控制台界面点击保存的那刻才推送一次,后面客户端重启后还是规则清空。
  4. Sentinel规则中的内置生成ID规则有问题。
  5. 客户端如何拉取代码?
  6. HTTP接口和普通接口接入和Sentinel注解方式需要而外的配置和激活才能使用。

 

解决:

按照Sentinel官方对配置规则的定义,规则的推送有下面三种模式:

推送模式

说明

优点

缺点

原始模式

API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource

简单,无任何依赖

不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境

Pull 模式

扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等

简单,无任何依赖;规则持久化

不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。

Push 模式

扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。

规则持久化;一致性;快速

引入第三方依赖

原始模式

如果不做任何修改,Dashboard的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中:

 

这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境。

好了,本文的目的就是解决这个配置消失的问题,我们公司选择了携程的Apollo作为配置管理工具,因为比较符合公司的使用发展模式,各位同学在公司中可以自行选择如ZK、Nacos等。

 

Apollo我就简单说一下

客户端就是配置一个app.id、app.meta地址,根据SpringBoot的启动命令指定了环境变量(dev、uat、prod),就连接过去了。

刚好有人为了一个问题:

app.id是不同的,即我在Apollo里面创建了一个名字为Sentinel的项目,但其他的项目使用的是xxx.app,他还能拿到Sentinel里面的Apollo规则配置吗?

可以的,因为我在Apollo的sentinel项目中新建了一个名字为Sentinel.rule的properties文件是公共的,默认在Apollo新建一个项目的时候有一个私有的application.properties的文件只给该项目用,你需要而外建一个public的配置文件给外部使用。

所以其他应用都能找到你的文件名,因为每个文件名的前缀都带有特殊标识,都保证这个文件名是唯一的,玩Apollo个把星期你就发现这个情况了。我刚开始也担心app.id不一样的问题会不会取不到文件配置,我请教了我们组的Apollo集成的同学才知道可以解决的。

改造Sentinel Dashboard模块代码

我在改造的时候也是用了官方的Test下的例子去改造的,这里必须要着重说明一下这个改造逻辑,这也是我改造中遇到的最大的问题,也可能是自己没理解透Sentinel开发者的思路。

1:控制台只负责把规则存储到Apollo(或其他配置中心中间件ZK、Nacos)。

2:客户端系统要自己去拉取Apollo的配置,而不是去拉Sentinel 控制台里面的数据,因为Sentinel已经把配置数据存储到Apollo了。

3:控制台中维护的规则有一个动作是调用Http ApiClient推送到你的系统,这也是原本就有的,但是也只是点击保存那一刻给你推一次,当你自己的系统重启,那还是清空了。我原本以为我只要把规则保存好了,Sentinel会自动帮我推送给我的系统中。

 

小结

控制台Dashboard只负责把规则数据保存到Apollo,并且给你推送点击保存的那一次就不管了,拉取动作要自己的系统去监听Apollo,这个Sentinel已经帮我们做了监听的例子,各位放心,非常简单。

 

##改造Sentinel Dashboard的界面保存到Controller的代码

我们会看到Sentinel Dashboard代码中限流模块有V1和V2两个Controller代码,其他的又没有,但出于最低限度的改造代码考虑,我保留了这两个类,就看哪个页面触发到哪个Controller,反正两个都一起改了做一样的事就好了。

这里要注意:

1:簇点链路中直接生成规则是用V2的Controller代码,单独去限流模块页面维护规则是调用了V1的Controller。

2:其他的规则都是没有V2的区分,所以只要改他对应的Controller类即可。

3:注意保存的时候有一个生成这条规则记录的ID规则,你看到到代码会发现,Sentinel是用一个原子类Int值每次1,但是Sentinel Dashboard重启后,又回到1开始,那么就会导致原本已存在的ID为1的规则被覆盖掉(很严重)。我的做法是每次把规则拿出来,遍历一下最大的那个出来1就解决这个问题。

4:把Apollo的包去掉test作用域.

 

开始改造代码

1ApolloConfig中需要修改的有portalUrl和token:

Converter<List<FlowRuleEntity>, String> 这些方法是把配置json话保存到Apollo中

这是获取Apollo配置的方法.

2FlowControllerV2改造

把FlowRuleApiProvider改一下,用自己的所有的规则都在这个自己的接口SentinelPersistenceApiService中实现.(该接口做了哪些东西可以直接看我改造的源码,太多了就不帖了.)

FlowControllerV2中最后的写数据到Apollo的方法换掉.用自己实现的接口SentinelPersistenceApiService.

再看我的实现

第一步要说明的是unionFlow这个方法,这个方法主要是实现id的不重复性,上面有提到过,原实现是从0开始加1,但是重启后又是0开始加1,所以会导致覆盖,我的做法是把数据拉出来,遍历后用最大的那条去加1就好了,isDelete是标识删除还是新增或修改.删除的话要load之前的出来删掉,在把数据重新保存一份到Apollo,否则数据会乱.

SetRules就调用Apollo方法去保存完事.

到了这里就算完成了保存到Apollo配置了.我们现在解决了上面2&3&4的问题,其实官方还说要我们自己改造sidebar页面为(flow,把V1去掉),使用FlowControllerV2,我觉得尽量不改动html页面,因为很多人都不是做前端的,改起来麻烦.所以还是按照原来的,就改java代码比较舒服.

也只有流量控制需要改,因为官方做的例子中有FlowControllerV2和FlowControllerV1两个,而我上面也说了,两个都做一样的事情就好,不管页面调用哪个类,都是一样的.

还需要说明一下,Apollo里面存储的是Key – Value

是根据客户端系统名称加xxx-rules的规则.效果如:

3改造客户端代码

客户端(这是指哪个系统要接入Sentinel,那该系统就是客户端,有些同学别理解错了)

引入mvn的包,记得把Apollo的包引进来,这个是sentinel里面带有的,直接用sentinel里面的即可.

利用SPI技术植入实现层代码,这点Sentinel已经帮我们做好了.只要实现com.alibaba.csp.sentinel.init.InitFunc接口即可自动调用里面的代码.

先在resources\META-INF\services下建一个名字为com.alibaba.csp.sentinel.init.InitFunc的文件,内容是:

自己的实现类

实现内容为:

就这几句代码,而且也是官方给的例子代码,直接就能用,在客户端实现这段代码就是去Apollo里面拉取配置用的.

APP这个变量要额外注意,这个APP就是指接入到Sentinel后,左边的菜单显示了你的APP名称的那个,因为接入Apollo的用系统名+规则名作为key保存的,这里APP变量肯定是该系统的名称+规则名去拉取对应的那条json记录.

其实现就是定时任务去拉取配置.每3秒一次,请看拉取源码:

到这里就是第5个问题解决了.

4:服务器定时推送规则到客户端.

在我的dashboard控制台里面自己还实现了定时任务自动推送到客户端,当做双重保险吧.也是用官方的SentinelApiClient去推送,具体代码如下:

注意要判断一下机器的健康检查,如果不判断,有些机器下线了地址失效了或者挂了,这里还是会一直推送,所以加这个判断后避免很多推送异常.

 

5:Sentinel注解的接口激活.

在客户端把这个代码加上,就是激活注解功能了,否则你用的注解都是没有生效的.我在做的时候也已经注解是随便用,没想到这一层.如果不加这个Bean,注解的方法也不会出现在控制台的鏃点链路上.

其实也就是用了Spring的切面技术去扫描代码中的@SentinelResource注解.

再给大家介绍一下@SentinelResource注解的用法和解释.

其一:

value对应的就是资源名,控制台链路中会显示这个名字.也可以在控制台上直接配置限流规则,但必须是激活了注解Bean.

其二:

blockHandler 对应处理 BlockException 的函数名称,可选项。若未配置,则将 BlockException 直接抛出。

blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。

blockHandler 函数默认需要和原方法在同一个类中

若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

意思就是blockHandler方法是处理限流异常的钩子方法,可以理解为异常回调,里面的值是方法名称,必须和注解的方法再同一个类中.

,如果不想在同一个类,那么就要定义blockHandlerClass属性,其值就是类属性,限流被触发后就会去这个类中找handleException方法.两个属性是相呼应的.

再要说明一下方法的返回值, @SentinelResource注解的方法,如:sayHello是String,那这个方法也得是String,如果大家自定义了其他返回如:Response<String>那handleException方法也得是Response<String>,这样的方式非常的友好,有人开发者的业务肯定定义了自己的返回code一类的给调用者的.

其三:

fallback = "helloFallback" 可选项,仅针对降级功能生效(DegradeException)。

fallback 函数的访问范围需要是 public,参数类型和返回类型都需要与原方法相匹配,并且需要和原方法在同一个类中。业务异常不会进入 fallback 逻辑。

这个属性是高优先级(如果你定义了的话),如果降级熔断了那么首先会调用helloFallback方法去返回,该方法只能和注解一起放,没有其他指定类.

其四:

该属性上的方法不可以自己新增参数,自己新增的参数导致整个方法都无法识别.切记!

 

6:HTTP接口限流拦截

其实就是加一个Filter拦截器.如果不加这个Filter,注解的方法也不会出现在控制台的鏃点链路上.

需要这个包

到这里第6个问题也解决了.

7:Alibaba Dubbo和Apache Dubbo支持.

由于Sentinel对两个版本的Dubbo框架都做了支持,但是要注意一下mvn的引入,用Alibaba Dubbo就只能用:

用Apache Dubbo就只能用:

否则可能会启动不成功,或者无法限流Dubbo接口,由于Sentinel优先接入Dubbo接口的,引入Sentinel的包,自动也支持了Dubbo的控制台显示,所以控制台会显示出Dubbo的调用链路,不用什么注解支持都可以直接配置限流.

到这里第1个问题也解决了.

 

小结:

服务端把规则写到Apollo,客户端主动去拉取,或者服务端定时任务主动推送到客户端.

客户端使用Sentinel注解需要在客户端自己注册注解AOP工具代码.

客户端HTTP接口需要在web.xml中加入CommonFilter过滤器.

使用Dubbo接口要区分Sentinel Dubbo adapter支持的jar版本.

 

## 最后

我们的Fork代码仓库如下地址,里面也只动了Dashboard这个包的一点代码,基于1.6.2来开发.https://github.com/360jinrong/Sentinel.

还有一个问题没有解决,就是集群限流的问题,集群限流遇到容器化感觉就难受了,因为要指定一台机器去当Master,或者一部分当Master分发令牌,IP一变,那就不知道咋整了.索性不使用集群限流,要用就自己去实时创建,等发布完后自己再去配置,虽然麻烦了点,但聊胜于无.

最后一个问题就是权限问题,目前无法实现给谁分配角色的问题,如果自己动手改造工程量也大,万一哪天官方实现了,自己的代码又要花时间适配或废弃,还是算了算了.

最后的最后还有一个最最最重要的还没有实现,打开我们的GitHub分支->点个星☺撒

发布了52 篇原创文章 · 获赞 12 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/caodegao/article/details/100009618