一种面向业务配置基于JSF广播定时生效的工具
作者:京东物流 王北永 姚再毅 李振
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广播定时生效的工具的更多相关文章
- 两种交换机配置模式,以配置基于端口划分的VLAN为例
关于交换机的配置模式,大体上可以分为两类:其一以CISCO交换机为代表的配置模式,其二以Huawei.H3C交换机为代表的配置模式.其实这两种配置模式并没有本质的不同,只是配置的命令名称和配置方式存在 ...
- mysql主丛之基于binlog的不停业务配置主从
一 环境准备 主:192.168.132.121 从:192.168.132.122 主的数据库上面已经有数据,而且还在不断的写入 mysql> select * from darren.tes ...
- 一种M2M业务的架构及实现M2M业务的方法
http://www.cnblogs.com/coryxie/p/3849764.html 技术领域 [0001] 本发明涉及通信技术领域,尤其涉及一种M2M业务的架构及实现M2M业务的方法. 背景技 ...
- 业务配置开发平台qMISPlat 2.0 产品介绍
qMISPlat是什么 qMISPlat(业务配置开发平台)是一套基于.net core 2.0.跨平台的,面向开发人员和具有一定技术水平的业务人员使用的业务配置开发平台.基于此平台您只需通过配置和少 ...
- 一种面向云服务的UCON多义务访问控制方法及系统
)设置每一云服务的义务项:建立每一云服务所包含的义务图:2)根据用户所请求的云服务查找该云服务的所有强制义务图和可选义务图,并提取该用户对该云服务的历史完成情况:3)对每一强制义务图,监控其每一义务项 ...
- Django的几种缓存的配置
1.缓存的简介 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面. 当一个网站的用户访问量很大的时候,每一次的的后台操作,都会 ...
- Nginx配置基于多域名、端口、IP的虚拟主机
原文:https://www.cnblogs.com/ssgeek/p/9220922.html ------------------------------- Nginx配置基于多域名.端口.IP的 ...
- 关于ActiveMQ的几种集群配置
ActiveMQ的几种集群配置. Queue consumer clusters 此集群让多个消费者同时消费一个队列,若某个消费者出问题无法消费信息,则未消费掉的消息将被发给其他正常的消费者,结构图如 ...
- ActiveMQ的几种集群配置
ActiveMQ是一款功能强大的消息服务器,它支持许多种开发语言,例如Java, C, C++, C#等等.企业级消息服务器无论对服务器稳定性还是速度,要求都很高,而ActiveMQ的分布式集群则能很 ...
- linux apache虚拟主机配置(基于ip,端口,域名)
配置环境: linux版本:Centos6.4 httpd版本: [root@centos64Study init.d]# pwd/etc/init.d[root@centos64Study init ...
随机推荐
- python3使用mutagen进行音频元数据处理
python版本:python 3.9 mutagen版本:1.46.0 mutagen是一个处理音频元数据的python模块,支持多种音频格式,是一个纯粹的python库,仅依赖python标准 ...
- CSS 渐变锯齿消失术
在 CSS 中,渐变(Gradient)可谓是最为强大的一个属性之一. 但是,经常有同学在使用渐变的过程中会遇到渐变图形产生的锯齿问题. 何为渐变锯齿? 那么,什么是渐变图形产生的锯齿呢? 简单的一个 ...
- 深入学习SpringBoot
1. 快速上手SpringBoot 1.1 SpringBoot入门程序开发 SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程 1. ...
- springboot中使用mybatis_plus逆向工程
创建springboot项目,选择图片中所示依赖 mybatis-plus生成的依赖 <!-- mybatis_plus --> <dependency> <groupI ...
- cookie中 防止重复存值 (可用于历史记录等)
function makeCookie($key,$val){ // 查看cookie中是否已经存过键为history_ids if(Cookie::has($key)){ // 已经存过了 $jso ...
- minio API demo
package mainimport ( "context" "fmt" "github.com/minio/minio-go/v7" &q ...
- DHorse系列文章之maven打包
插件打包 这种方式是平时最常用的,首先要下载并安装maven环境,然后在被打包的项目中引入插件,有各种各样的打包插件,比如springboot自带插件: <plugin> <grou ...
- Git基操记录
小结 说多了都是泪,最近在整理Java的笔记,记笔记我比较喜欢使用markdown(当时使用Hexo的原因之一),毕竟作为刚入门槛一点点的程序小白,还没用上Idea(很多大佬都推荐这个),目前还在使用 ...
- c++题目:吃西瓜
吃西瓜 [问题描述] 老胡买了是长方体形的西瓜来犒劳大家.... 这块西瓜长m厘米,宽n厘米,高h厘米.他发现如果把这块西瓜平均地分成m*n*h块1立方厘米的小正方体,那么每一小块都会有一个营养值(可 ...
- VH6501模板工程介绍(一)
VH6501硬件结构 1.式样 1.正向有5个灯,用来指示干扰的触发状态,干扰类型(数字或模拟),通道通信以及设备状态. 2.两个DB9接口(公头male和母头female),这是CAN或CANFD通 ...