作者:京东物流 王北永 姚再毅 李振

1 背景

目前,ducc实现了实时近乎所有配置动态生效的场景,但是配置是否实时生效,不能直观展示每个机器上jvm内对象对应的参数是否已变更为准确的值,大部分时候需要查看日志确认是否生效。

2 技术依赖

1)Jsf:京东RPC框架,用作机器之间的通讯工具

2)redis/redisson:redis,用作配置信息的存储

3)ZK/Curator: Zookeeper,用作配置信息的存储 和redis二选一

3)clover:定时任务集群,用作任务延迟或周期性执行

3 实现原理

1)接入方:

各个接入系统通过接入管理模块获取token,并指定所在系统发布的的服务器ip,用作后续的ip鉴权。当系统启动时,自动在各个系统生成接口提供方,并注册到JSF注册中心。别名需各个系统唯一不重复。鉴权为统一服务端做IP鉴权。

2)统一配置服务端:

提供按不同接入方、不同系统、不同环境的配置界面。业务人员可设定自动生效时间或者立即生效时间。如果是立刻生效,则通过JSF广播或者指定机器生效配置。如果是定时生效,则新增定时器并指定生效规则,达到时间后触发广播通知。

整个接入方和统一配置服务端的架构如下图

4 实现步骤

1)重写JSF类ConsumerConfig类方法refer,将其中的轮训调用客户端改为广播调用客户端BroadCastClient。

this.client= new BroadCastClient(this);
this.proxyInvoker = new ClientProxyInvoker(this);
ProtocolFactory.check(Constants.ProtocolType.valueOf(this.getProtocol()), Constants.CodecType.valueOf(this.getSerialization()));
this.proxyIns = (T) ProxyFactory.buildProxy(this.getProxy(), this.getProxyClass(), this.proxyInvoker);

2)广播调用客户端方法分别获取当前注册中心有心跳的服务提供者和已失去连接的机器列表。对统一配置来讲,要么同时失败,要么同时成功,判断如果存在不正常的服务提供方,则不同步。只有全部提供方存在才可以开始广播配置信息

 ConcurrentHashMap<Provider, ClientTransport>  concurrentHashMap = this.connectionHolder.getAliveConnections();
ConcurrentHashMap<Provider, ClientTransportConfig> deadConcurrentHashMap = this.connectionHolder.getDeadConnections();
if(deadConcurrentHashMap!=null && deadConcurrentHashMap.size()>0){
log.warn("当前别名{}存在不正常服务提供方数量{},请关注!",msg.getAlias(),deadConcurrentHashMap.size());
throw new RpcException(String.format("当前别名%s存在不正常服务提供方数量%s,请关注!",msg.getAlias(),deadConcurrentHashMap.size()));
}
if(concurrentHashMap.isEmpty()){
log.info("当前别名{}不存在正常服务提供方",msg.getAlias());
throw new RpcException(String.format("当前别名%s不存在正常服务提供方",msg.getAlias()));
}
Iterator aliveConnections = concurrentHashMap.entrySet().iterator();
log.info("当前别名{}存在正常服务提供方数量{}",msg.getAlias(),concurrentHashMap.size());
while (aliveConnections.hasNext()) {
Entry<Provider, ClientTransport> entry = (Entry) aliveConnections.next();
Provider provider = (Provider) entry.getKey();
log.info("当前连接ip={}、port={}、datacenterCode={}",provider.getIp(),provider.getPort(),provider.getDatacenterCode());
ClientTransport connection = (ClientTransport) entry.getValue();
if (connection != null && connection.isOpen()) {
try {
result = super.sendMsg0(new Connection(provider, connection), msg);
} catch (RpcException rpc) {
exception = rpc;
log.warn(rpc.getMessage(), rpc);
} catch (Throwable e) {
exception = new RpcException(e.getMessage(), e);
log.warn(e.getMessage(), e);
}
}
}

3)服务配置端,当业务人员配置及时生效或者任务达到时,则根据配置,生成服务调用方,通过统一刷新接口将配置同步刷新到对应的接入系统中,如下图为操作界面,当增删改查时,会将属性增量同步。

服务端在上面操作增删改时,通过以下方式获取服务调用方

 public static ExcuteAction createJsfConsumer(String alias, String token) {
RegistryConfig jsfRegistry = new RegistryConfig();
jsfRegistry.setIndex("i.jsf.jd.com");
BroadCastConsumerConfig consumerConfig = new BroadCastConsumerConfig<>();
Map<String, String> parameters = new HashMap<>();
parameters.put(".token",token);
consumerConfig.setParameters(parameters);
consumerConfig.setInterfaceId(RefreshRemoteService.class.getName());
consumerConfig.setRegistry(jsfRegistry);
consumerConfig.setProtocol("jsf");
consumerConfig.setAlias(alias);
consumerConfig.setRetries(2);
return new ExcuteAction(consumerConfig);
}

通过以上的配置的客户端,调用服务提供方方法refreshRemoteService#refresh,将配置信息进行同步到各个接入系统

public void call(Map<String,Object> propertiesValue){
try{
RefreshRemoteService refreshRemoteService = (RefreshRemoteService)consumerConfig.refer();
if(refreshRemoteService!=null){
refreshRemoteService.refresh(propertiesValue);
}
}catch (Exception e){
log.error(e.getMessage());
throw new EasyConfigException(e);
}finally {
consumerConfig.unrefer(); ;
}
}

4)接入方启动时,需要根据自己配置,将存在redis或者zk的配置一次加载到实例变量中。并注册刷新接口到JSF注册中心。

其中注册刷新接口到JSF注册中心代码如下

 @Bean(name = "refreshPorpertiesService")
public ProviderConfig createJsfProvider() throws Exception {
ProviderConfig providerConfig = new ProviderConfig();
providerConfig.setId("refreshPorpertiesService");
providerConfig.setInterfaceId(RefreshRemoteService.class.getName());
providerConfig.setRef(new RefreshRemoteServiceDelage(applicationContext));
providerConfig.setTimeout(30000);
providerConfig.setAlias(EasyConfigure.getAppCode()+EasyConfigure.getEnv());
providerConfig.setServer(serverConfig);
providerConfig.setRegistry(jsfRegistry);
providerConfig.setParameter("token", MD5Util.md5(EasyConfigure.getAppCode()));
providerConfig.export();
return providerConfig;
}

其中

RefreshRemoteServiceDelage类提供刷新接口的实际逻辑如下,需判断当前实例是jdk动态代理还是cglib代理

判断逻辑如下

 if(AopUtils.isJdkDynamicProxy(object)) {
object= AopUtil.getJdkDynamicProxyTargetObject(object);
} else if(AopUtils.isCglibProxy(object)){ //cglib
object= AopUtil.getCglibProxyTargetObject(object);
}

实例对象变量值根据自定义的参数转换方式转换后赋值实例变量

if(autoValue.convert()!=null && !autoValue.getClass().isInterface()){
if(!autoValue.convert().newInstance().getInClassType().isAssignableFrom(newVal.getClass()) ){
continue;
}
newVal = autoValue.convert().newInstance().convert(newVal);
if(newVal!=null){
if(!autoValue.convert().newInstance().getOutClassType().isAssignableFrom(newVal.getClass()) ){
continue;
}
field.setAccessible(true);
Object value = ReflectionUtils.getField(field,object);
log.info("change properties{} for object {} before value {}",field.getName(),object.getClass().getName(),value);
ReflectionUtils.setField(field,object,newVal);
log.info("change properties{} for object {} after value {}",field.getName(),object.getClass().getName(),newVal);
}
}

5 实践

1)pom引入

<dependency>
<groupId>com.jdl</groupId>
<artifactId>easyconfig</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

2)配置存储配置(比如redis方式)

refresh:
config:
appCode: zdzq-worker-appcode
redisUrl: redis://:@127.0.0.1

3)类全局变量需要实时刷新配置,需在类统一指定注解PropertiryChangeListener,实例变量需要增加注解AutoValue并指定数据格式转换器

@PropertiryChangeListener
public class ChanceServiceImpl implement ChanceService{
@AutoValue(convert = DateConvert.class,alias = "config-id")
private Date signDate;
@AutoValue(convert = SpmKaApply Convert.class,alias = "config-id")
private SpmKaApply spmKaApply;
}

以上convert方法自定义,支持各种复杂配置对象,举例数据转换为List如下

public  class Convert2 implements Convert<Map<String, String>, Set<String>> {
public Convert2(){}
@Override
public Set<String> convert(Map<String, String> siteInfoMap) {
....你的对象值转换
}
@Override
public Class<?> getInClassType() {
return Map.class;
}
@Override
public Class<?> getOutClassType() {
return Set.class;
}

接入应用服务启动后,可访问/refreshUI 可查看应用在集群中为自动配置的实例,并显示当前实例中变量值参数。key为实例变量名。

6 总结

1、支持jdk动态代理的实例对象和cglib代理对象的参数动态配置

2、支持定时刷新配置

3、直接查看和验证应用集群中实例变量是否一致

一种面向业务配置基于JSF广播定时生效的工具的更多相关文章

  1. 两种交换机配置模式,以配置基于端口划分的VLAN为例

    关于交换机的配置模式,大体上可以分为两类:其一以CISCO交换机为代表的配置模式,其二以Huawei.H3C交换机为代表的配置模式.其实这两种配置模式并没有本质的不同,只是配置的命令名称和配置方式存在 ...

  2. mysql主丛之基于binlog的不停业务配置主从

    一 环境准备 主:192.168.132.121 从:192.168.132.122 主的数据库上面已经有数据,而且还在不断的写入 mysql> select * from darren.tes ...

  3. 一种M2M业务的架构及实现M2M业务的方法

    http://www.cnblogs.com/coryxie/p/3849764.html 技术领域 [0001] 本发明涉及通信技术领域,尤其涉及一种M2M业务的架构及实现M2M业务的方法. 背景技 ...

  4. 业务配置开发平台qMISPlat 2.0 产品介绍

    qMISPlat是什么 qMISPlat(业务配置开发平台)是一套基于.net core 2.0.跨平台的,面向开发人员和具有一定技术水平的业务人员使用的业务配置开发平台.基于此平台您只需通过配置和少 ...

  5. 一种面向云服务的UCON多义务访问控制方法及系统

    )设置每一云服务的义务项:建立每一云服务所包含的义务图:2)根据用户所请求的云服务查找该云服务的所有强制义务图和可选义务图,并提取该用户对该云服务的历史完成情况:3)对每一强制义务图,监控其每一义务项 ...

  6. Django的几种缓存的配置

    1.缓存的简介 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面. 当一个网站的用户访问量很大的时候,每一次的的后台操作,都会 ...

  7. Nginx配置基于多域名、端口、IP的虚拟主机

    原文:https://www.cnblogs.com/ssgeek/p/9220922.html ------------------------------- Nginx配置基于多域名.端口.IP的 ...

  8. 关于ActiveMQ的几种集群配置

    ActiveMQ的几种集群配置. Queue consumer clusters 此集群让多个消费者同时消费一个队列,若某个消费者出问题无法消费信息,则未消费掉的消息将被发给其他正常的消费者,结构图如 ...

  9. ActiveMQ的几种集群配置

    ActiveMQ是一款功能强大的消息服务器,它支持许多种开发语言,例如Java, C, C++, C#等等.企业级消息服务器无论对服务器稳定性还是速度,要求都很高,而ActiveMQ的分布式集群则能很 ...

  10. linux apache虚拟主机配置(基于ip,端口,域名)

    配置环境: linux版本:Centos6.4 httpd版本: [root@centos64Study init.d]# pwd/etc/init.d[root@centos64Study init ...

随机推荐

  1. 齐博X1数据表之系统参数

    https://v.youku.com/v_show/id_XMzg0MTEzMzEyOA== 不会插入视频 直接发 优酷地址吧= =!

  2. 深入学习SpringBoot

    1. 快速上手SpringBoot 1.1 SpringBoot入门程序开发 SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程 1. ...

  3. Day04:Java数据类型

    Java的数据类型 强类型语言 要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用 弱类型语言 什么是变量 变量:可以变化的量. 在Java中每个变量都必须先申明这个变量是什么类型 Stri ...

  4. Oracle收集统计信息的一些思考

    一.问题 Oracle在收集统计信息时默认的采样比例是DBMS_STATS.AUTO_SAMPLE_SIZE,那么AUTO_SAMPLE_SIZE的值具体是多少? 假设采样比例为10%,那么在计算单个 ...

  5. K3S 安装及配置

    K3S安装 curl -sfL https://rancher-mirror.oss-cn-beijing.aliyuncs.com/k3s/k3s-install.sh | INSTALL_K3S_ ...

  6. vulnhub靶场之VIKINGS: 1

    准备: 攻击机:虚拟机kali.本机win10. 靶机:DRIPPING BLUES: 1,网段地址我这里设置的桥接,所以与本机电脑在同一网段,下载地址:https://download.vulnhu ...

  7. Go语言核心36讲29

    在上篇文章中,我们主要说的是互斥锁,今天我和你来聊一聊条件变量(conditional variable). 前导内容:条件变量与互斥锁 我们常常会把条件变量这个同步工具拿来与互斥锁一起讨论.实际上, ...

  8. 详解Native Memory Tracking之追踪区域分析

    摘要:本篇图文将介绍追踪区域的内存类型以及 NMT 无法追踪的内存. 本文分享自华为云社区<[技术剖析]17. Native Memory Tracking 详解(3)追踪区域分析(二)> ...

  9. dfs 序

    dfs序可以\(O(1)\)判断书上两个点的从属关系 Tree Queries 题面翻译 给你一个以\(1\)为根的有根树. 每回询问\(k\)个节点\({v_1, v_2 \cdots v_k}\) ...

  10. MYSQL快速安装整理

    参考教程:https://www.cnblogs.com/brad93/p/16650780.html [检查是否已安装过] find / -name mysql [快速安装开始] groupadd  ...