hippo4j icon indicating copy to clipboard operation
hippo4j copied to clipboard

根据逻辑策略配置动态更新线程池参数

Open alexhaoxuan opened this issue 2 years ago • 34 comments

业务背景:一天中,某几个时间点会出现数据激增的情况。数据量 10w-100W不等,时间点不固定。 需求:期望可以通过代码中配置的策略,动态更新当前项目的线程池参数(无需重启项目)。 解决方案:

  • 通过Nacos、Apollo 调整配置文件,借助 Nacos、Apollo监听器实现线程池参数的动态更新。缺点:多个项目引用同一份配置文件的话,修改配置中心,其他项目也会修改(即一改全改)。
  • 通过SpringBoot 配置的动态更新,只调整当前项目的线程池参数。(需确认hip 是否存在快捷入口,不存在的话就需要自己封装。待验证)

alexhaoxuan avatar Aug 30 '22 02:08 alexhaoxuan

@alexhaoxuan

  1. 多个项目引用同一份配置文件:我可以理解成 A、B、C 三个项目,其中定义了相同 id 的线程池,然后引了同一个远程配置文件么?如果这样,按照项目拆分开线程池配置或不同项目配置不同线程池 id,是不是更好
  2. 这一个目前 hippo4j config 是没有提供的,server 的方式支持。或者你可以沟通下你的实现方式,看看是不是可以贡献到 hippo4j

pirme avatar Aug 30 '22 03:08 pirme

你理解没错,可以理解成多个项目。如果我同一个项目,采用分布式(或微服务)部署的方式,配置文件肯定是引用的同一份。通过SLB或Nacos 负载均衡之后,请求真正到我那个服务上是由Nacos负载算法决定的。为了一个动态线程池每一个项目单独引用一份独立的配置文件,多少有点麻烦。 我再研究下,我更倾向于可以在Hippo4j中提供入口 ,可以通过配置逻辑策略,调整配置,影响范围也仅限于当前项目。

alexhaoxuan avatar Aug 30 '22 03:08 alexhaoxuan

那这样的话,不应该叫多个项目,应该是一个项目,集群部署吧

pirme avatar Aug 30 '22 03:08 pirme

那问题再演进下,比如一个用户项目 user-project,部署三个节点:

  1. user-project-1
  2. user-project-2
  3. user-project-3

你希望在改配置中心线程池配置时,仅修改你想变更的节点,而不是全部变更,对么?

pirme avatar Aug 30 '22 03:08 pirme

都可以。一份配置文件被不同的项目同时引用,和 一个项目集群部署。我认为是一样的,我想根据当前服务需要处理的数据量,动态调整自己服务的线程池配置。不同项目配置不同的配置,我研究了Server 确实可以支持。

alexhaoxuan avatar Aug 30 '22 03:08 alexhaoxuan

是的。user-project-1 我发现5min内流量激增,我需要动态只修改user-project-1的配置就好。前提是我之前配置不同流量的处理策略。

alexhaoxuan avatar Aug 30 '22 03:08 alexhaoxuan

server 确实是支持的,这得益于它拥有可视化界面,把客户端的节点都展示出来让用户可选。

而基于配置中心的方式,就没有那么好解决,你有什么好的思路么?

pirme avatar Aug 30 '22 03:08 pirme

基于配置中心的方式,一定是可行的。我更倾向于通过 SpringBoot配置动态更新的方式去处理,影响范围仅限于当前项目。即使集群部署,也相互不影响。关于SpringBoot的解决方案,我需要研究下Server的源码,再回复你,感觉是个不错的点。

alexhaoxuan avatar Aug 30 '22 03:08 alexhaoxuan

server 内置了一个自己实现的注册中心,客户端启动的时候会把实例注册上去,并且进行续期和定期删除。

而配置中心的话,是否可以添加个配置属性,比如:

spring:
    dynamic:
        thread-pool:
            nodes:192.168.1.5:2009,192.168.1.6:2009,192.168.1.7:2009
            # nodes:all (all 代表全部修改)

当配置修改时,客户端需要判断 nodes 是否为 all 或者 nodes host 中是否包含本节点,满足再修改?这是我的想法。

pirme avatar Aug 30 '22 03:08 pirme

站在Server的角度,确实可以这样考虑确实可行。 站在Client的角度,只是自己当前业务配置的策略造成配置要修改。(Client可以将配置变更推给Server进行日志记录,也方便回滚) 实际服务 A、B、C、D 、E 5个服务。 nodes:A、B、C、D 这样配置 ,通过调整配置中心 A、B、C、D四个服务可以动态更新,一旦峰值流量过去 A、B、C、D四个服务,我还是需要回滚到 配置修改前的状态的。

alexhaoxuan avatar Aug 30 '22 03:08 alexhaoxuan

你先尝试实现吧,我暂时想不到有比较通用的方式

pirme avatar Aug 30 '22 04:08 pirme

是否可以进行如下方式实现: 针对 threadPoolId添加指定的匹配器,在使用Refresh动态调整线程池之前,可以通过该匹配器对threadPoolId进行筛选,这和SpringMVC中的拦截器匹配机制类似,下面是MappedInterceptor的部分代码:

private final String[] includePatterns;
private final String[] excludePatterns;
private final HandlerInterceptor interceptor;
private PathMatcher pathMatcher;
  1. includePatterns:匹配后放行的
  2. excludePatterns:匹配后不放行的
  3. interceptor:MVC中的拦截器,此处要换成线程池:DynamicThreadPoolExecutor或者threadPoolId
  4. pathMatcher:具体的匹配器,可以实现多种:字符串全值、前缀、后缀匹配等 也可以额外添加其他的条件限制,比如includePatterns和excludePatterns数据重复时使用是否会进行覆盖,不同匹配器的执行顺序order,加入ognl表达式控制时间等。

当配置发生改变时,通过以上的方式对线程池进行匹配,只有通过的线程池或threadPoolId才可以进行参数调整,这样的话就算是多个实例使用了一个配置文件,只要匹配器不通过那么就不会产生影响

pizihao avatar Aug 30 '22 10:08 pizihao

是否可以进行如下方式: 针对threadPool添加指定的匹配,在使用Refresh动态解决对线程池部分实现之前,可以threadPoolId进行匹配匹配Spring进行筛选,这和MVC中的拦截器匹配机制类似,下面是MappedInterceptors的代码:

private final String[] includePatterns;
private final String[] excludePatterns;
private final HandlerInterceptor interceptor;
private PathMatcher pathMatcher;
  1. includePatterns:匹配后放行的
  2. excludePatterns:匹配后不放行的
  3. 拦截器:MVC中的拦截器,这里要换成线程池:DynamicThreadPoolExecutor或者threadPoolId
  4. pathMatcher,具体的匹配器,可以在字符串全值加入限制,可以实现后缀匹配等其他的,比如includePatterns 和excludePatterns数据重复时是否会进行覆盖,匹配器的执行顺序,ognl表达式控制时间等。

当发生变化的时候,通过以上的线程对线程进行匹配,只有通过的线程池或线程可以进行配置,这样的就是多个实例的一个文件,只要匹配不通过就可以通过多个线程进行配置产生影响

这是个很好的建议。includePatterns 和 excludePatterns 应该存储的是 ip+port 地址吧,比如五个节点,想修改其中一个节点时,对 includePatterns 进行赋值对应节点的 ip+port。

magestacks avatar Aug 30 '22 11:08 magestacks

是否可以进行如下方式:针对线程池添加指定的匹配,在使用刷新动态解决对线程池部分实现之前,threadId进行匹配Spring进行过滤,这和MVC中的拦截器匹配机制类似,下面是MappedInterceptors的代码:

private final String[] includePatterns;
private final String[] excludePatterns;
private final HandlerInterceptor interceptor;
private PathMatcher pathMatcher;
  1. includePatterns:匹配后放行的
  2. excludePatterns:匹配后不放行的
  3. 拦截器:MVC中的拦截器,这里要换成线程池:DynamicThreadPoolExecutor或者threadPoolId
  4. pathMatcher,具体的匹配器,可以在字符串全加入限制,可以实现后缀匹配等其他的,比如包括 Patterns和excludePatterns数据重复时是否会进行覆盖,匹配器的执行顺序,ognl表达式控制等。

当发生变化的时候,通过线程对线程进行匹配,只有通过的线程池或线程可以进行,只要是多个实例的一个文件,只要匹配不通过就可以通过多个线程进行配置

这是一个很好的建议。 includePatterns 和 excludePatterns 应该是存储 ip+port 地址吧,比如五个节点,想修改其中一个节点的时候,对包括 Patterns 进行间接的 ip+port。

Server去控制 具体哪个Client去更新配置,这样的方案确实不错。 但是,主体需求是 Client 发现数据激增后,Client 自己要更新到不同数据量的配置方案参数。峰值流量过去后,还是要回滚到配置更新前的配置的。 所以,我认为 有两个方案:

  • Server可以指定配置不同pool的策略方案配置
  • Client 开放线程参数动态修改入口,支持可比编程式配置。 两个方案其实本质上还是一样的。都需要Client 自己调整自己参数配置。

alexhaoxuan avatar Aug 30 '22 11:08 alexhaoxuan

@pizihao @alexhaoxuan 你是在说 hippo4j server 还是在说 hippo4j config,怎么一会配置中心,一会 server 呢

magestacks avatar Aug 30 '22 11:08 magestacks

抱歉有点没表达清楚。hippo4j config 可以给executor 指定配置策略,就很完美了(类似于现在的通知机制一样,可以支持多种策略,类似于通知机制每个executor配置都可以独立绑定)。

alexhaoxuan avatar Aug 30 '22 11:08 alexhaoxuan

抱歉有点没表达清楚。hippo4j config 可以给executor 指定配置策略,就很完美了(类似于现在的通知机制一样,可以支持多种策略,类似于通知机制每个executor配置都可以独立绑定)。

@alexhaoxuan 配置策略能的概念再详细描述下么?最好能搭配例子

magestacks avatar Aug 30 '22 11:08 magestacks

是否可以进行如下:针对线程这部分指定的匹配,在使用代码实现拦截器之前解决对线程池的处理,threadId进行匹配Spring进行过滤,和MVC中的拦截器匹配类似,下面是MappedInterceptors的:

private final String[] includePatterns;
private final String[] excludePatterns;
private final HandlerInterceptor interceptor;
private PathMatcher pathMatcher;
  1. includePatterns:匹配后放行的
  2. excludePatterns:匹配后不放行的
  3. 拦截器:MVC中的拦截器,这里要换成线程池:DynamicThreadPoolExecutor或者threadPoolId
  4. pathMatcher,具体的匹配器,可以在字符串全加入限制,可以实现后缀匹配等其他的,比如包括 Patterns和excludePatterns数据重复时是否会进行覆盖,匹配器的执行顺序,ognl表达式控制等。

当发生变化的时候,通过线程就可以进行匹配,只有通过的线程池或线程可以进行,是多个实例的一个文件,只要匹配不通过多个线程配置即可

includePatterns 和 excludePatterns 应该是存储 ip+port 地址吧,比如五个节点,想修改其中一个节点的时候,对包括 Patterns 进行直接的 ip+port。

是的,需要ip+port才能准确的找到需要修改的线程池

pizihao avatar Aug 30 '22 12:08 pizihao

@pizihao 因为涉及到 ip+port 这种方式进行修改,我认为仅需要一个字段即可,如下所示

spring:
    dynamic:
        thread-pool:
            executors:
              - thread-pool-id: 'message-consume'
                 nodes:192.168.1.5:2009,192.168.1.6:2009,192.168.1.7:2009 (多个以英文逗号隔开)
                # nodes:all (all 或空代表全部修改)

因为太复杂反而不会有人使用,你觉得呢。

magestacks avatar Aug 30 '22 13:08 magestacks

@agentart 哈哈,其实我最开始的想法是通过线程池内部的某个标识来匹配的,不过那样需要在真正到调整参数的时候才能确定,而且会添加许多额外的配置,甚至需要人去自己定义匹配器,会麻烦很多,如果可以在注册中心直接把不需要修改的节点去掉一定是最方便的

pizihao avatar Aug 30 '22 13:08 pizihao

@pizihao hippo4j config 的方式是依赖配置中心做动态调参的,和注册中心没关系

magestacks avatar Aug 30 '22 13:08 magestacks

@pizihao hippo4j config 的方式是依赖配置中心做动态调参的,和注册中心没关系

那ip+port在config中的具体含义是什么呢? 我在ServerThreadPoolDynamicRefresh以及涉及到的类中并没有找到ip+poet的相关信息,因为我看到GlobalThreadPoolManage中是通过threadPoolId为key的map来存储线程池的,所以就自然而然的认为threadPoolId可以代表一个实例中的唯一的一个线程池。

pizihao avatar Aug 30 '22 13:08 pizihao

比如一个用户项目 user-project,使用了 hippo4j config,部署三个节点:

  • user-project-1
  • user-project-2
  • user-project-3

如果修改线程池相关配置,三个节点对应的线程池都会变更,没办法仅变更其中一个,所以需要 ip:port 的方式来仅修改其中一个节点

magestacks avatar Aug 30 '22 13:08 magestacks

啊,我知道了,在这三个节点中是会有相同threadPoolId的线程池,使用ip+port之后可以直接区分具体是那个节点的线程池从而进行调整,会省去很多配置信息,毕竟线程池的参数实在是太多了,会显得配置起来很麻烦

pizihao avatar Aug 30 '22 14:08 pizihao

感觉咱们还是没对齐,还是按照上面说的

user-project 有一个线程池,叫 message-send-threadpool,连接 nacos 某一个配置文件,配置如下:

spring:
  profiles:
    active: dev

  dynamic:
    thread-pool:
      executors:
        - thread-pool-id: 'message-send-threadpool'
          # 核心线程数
          core-pool-size: 1
          # 最大线程数
          maximum-pool-size: 1
          # 阻塞队列名称,参考 BlockingQueueTypeEnum,支持 SPI
          blocking-queue: 'LinkedBlockingQueue'
          # 阻塞队列大小
          queue-capacity: 1
          # 执行超时时间,超过此时间发起报警,单位毫秒
          execute-time-out: 1000
          # 拒绝策略名称,参考 RejectedPolicyTypeEnum,支持 SPI
          rejected-handler: 'AbortPolicy'
          # 线程存活时间,单位秒
          keep-alive-time: 1024
          # 是否允许核心线程超时
          allow-core-thread-time-out: true
          # 线程工厂名称前缀
          thread-name-prefix: 'message-consume'
          # 是否报警
          alarm: true
          # 活跃度报警阈值;假设线程池最大线程数 10,当线程数达到 8 发起报警
          active-alarm: 80
          # 容量报警阈值;假设阻塞队列容量 100,当容量达到 80 发起报警
          capacity-alarm: 80
          # 通知配置,线程池中通知配置如果存在,则会覆盖全局通知配置
          notify:
            # 报警间隔,同一线程池下同一报警纬度,在 interval 时间内只会报警一次,单位分钟
            interval: 8
            # 企业微信填写用户 ID(填写其它将无法达到 @ 效果)、钉钉填手机号、飞书填 ou_ 开头唯一 ID
            receives: xxx

部署了三个节点,修改 nacos 配置中心的话,三个节点就都改了,没办法仅变更其中一个,所以需要 ip:port 的方式来仅修改其中一个节点

magestacks avatar Aug 30 '22 14:08 magestacks

就是说在每一个节点中都会有一个threadPoolId为message-send-threadpool的线程池,在配置中心的文件被修改之后,这些节点中的message-send-threadpool都会被修改掉,对吧! 确实是我之前的理解出现了错误

pizihao avatar Aug 30 '22 14:08 pizihao

@pizihao 是这个意思

magestacks avatar Aug 30 '22 14:08 magestacks

@pizihao #614 这个 issue 有兴趣贡献么,就是上面说的逻辑

magestacks avatar Aug 30 '22 14:08 magestacks

可以,很荣幸能参与到这个项目当中,不过可以加个微信吗,在写的过程中如果出现问题还有我的具体思路的问题可以随时联系你

pizihao avatar Aug 30 '22 14:08 pizihao

@pizihao 可以的,另外在 #614 issue 留个言,任务指派到你哈

image

magestacks avatar Aug 30 '22 14:08 magestacks