基于ZooKeeper,Spring设计实现的参数系统

发布日期:2019-07-16

 一、简介

  基于ZooKeeper服务端、ZooKeeper Java客户端以及Spring框架设计的用于系统内部进行参数维护的系统。

二、设计背景

  在我们日常开发的系统内部,开发过程中最常见的一项工作便是常用参数的维护,从我学习Java以来,参数的配置多样化,最常见的方式是properties配置文件或者是xml配置文件,高深点的用法是JMX MBean进行参数管理以及数据库参数配置。我们对现有的参数配置方式进行分析,详见下表:

配置方式常见问题备注
 代码内字符窜字面量配置 每次参数的修改都需要重新编译 
 properties配置文件/xml配置文件 普通用法虽然将参数从代码内抽离出来,但是无法随时更新生效 Spring提供的ReloadableResourceBundleMessageSource工具类可以实现热加载Properties文件,将参数配置文件从代码分离可以做到不停机不重启做参数维护,并被程序加载,但是仍需系统重新从文件资源内获取新的参数值
 JMX参数配置 标准MBEAN是有侵入性的,他要管理的对象是符合JAVA BEAN规范的对象。但是要作为标准MBEAN而被管理,就需要实现一个接口。这个接口的名称必须是类名加上MBean。 Spring支持将普通Bean通过配置MBeanExporter生成MBean
 数据库参数配置 需要从数据库内加载参数,每次参数的使用需要连接数据库进行数据库查询。后来出现了代码缓存数据库参数,在一次使用后将参数信息放置缓存内,但是这种做法无法感知数据库参数的变化。 代码缓存数据库参数方式,可以新增代码设计用于刷新缓存参数,从库内重新读取放置缓存内,也不失为一种方便的参数管理。

  基于上述各类参数配置分析,一番思考设想,设计出如下结构的[参数中心系统](详细设计链接),设计说明查看下一节:

  

三、系统设计说明

  参数中心系统,顾名思义,主要是将参数集中化,在实际开发中,一个业务的实现需要几个甚至数十个模块联合完成,每个模块都需要进行参数的更新维护,一个模块的参数更新设计缺陷,在进行参数维护时,就可能导致某个业务的中断,故需要将多参数管理统一化管理;统一化的参数管理方式,便可能涉及到了参数数据的统一存储,统一之后便出现了性能瓶颈需求,不然所有鸡蛋装一个篮子里,一出问题全部碎掉;在集中化后,各个模块有自己的参数,有些参数可能仅限单个系统访问,便需要安全的参数访问方式;在参数使用过程中,常见的功能之一便是参数的实时维护;在项目投产过程中,经常因为参数配置问题比如配置错误等情况导致业务中断,故需要一个参数检查表来确认参数的正确性;参数管理整合后,需要方便的操作来实现管理功能。概括一下,参数中心系统需要满足以下技术需求:

多系统、多模式、安全、动态维护的参数配置个性化话参数配置(普通字符窜,JSON字符窜,数组窜)低侵入快捷的参数导入导出功能便捷的管理方式上线参数检查表,用于上线时各类参数的检查,防止出错高可用安全控制

  根据上述分析,设计之初,思考如何实现多系统多模式的参数存储,虽然一直知道ZooKeeper这个东西,但从未详细了解过,偶然机会大致学习了一下ZooKeeper,发现ZooKeeper的各类机制与参数中心系统设计相吻合,比如:多系统多模式的参数存储与ZooKeeper的目录型存储方式相似,查看下面图3-1展示;参数的安全方式认证与ZooKeeper的ACL机制吻合;参数实时维护可以借鉴ZooKeeper的Watcher机制;参数中心系统的高性能高可用设计与ZooKeeper的集群方式相吻合。这样下来,参数中心系统最大的问题参数存储模块服务端得到了完美的解决。接下来的便是基于ZooKeeper设计出对应的客户端,管理端。

图3-1 基于ZooKeeper的参数存储

  Java应用端常用的技术之一便是Spring框架,也符合低侵入的设计原则,在使用Spring开发过程中,常用的功能之一便是使用${}引用properties配置文件内的参数,如此方便的参数配置方式,我决定使用类似的方式,配置方式为zk{}(zk表示ZooKeeper参数),故客户端的设计是基于Spring的设计。

四、系统技术组合

  ZooKeeper集群 + ZooKeeper Java客户端 + Spring BeanFactoryPostProcessor扩展点 + JSON字符窜解析 + Spring SpEL表达式

五、设计实现(重点)

  根据上述设计说明等信息,最后得出这样一个系统,基于ZooKeeper参数存储,Spring客户端使用zk{}进行参数配置的参数中心系统。

服务端

  服务端设计如3-1图所示(在实际开发过程中可能稍有变动)

客户端(重点)

  在进行参数中心系统客户端实现之前,我们先了解一点Spring框架的基础知识。

  在Spring框架开发过程中,最常用的配置是<bean/>标签的使用,在工作中,最常听的一种说法是,在xml里配置上就可以使用bean了,就有对象了,这种理解潜在一层含义xml直接配置成了java bean,而实际上,Spring中bean的定义最终表现为BeanDefinition对象,个人理解为xml实际配置的是bean的说明信息,Spring将这些说明信息转换为了BeanDefinition对象,再由BeanDefinition生成了我们最终看到的bean,实际流程为[xml配置->BeanDefinition->Bean]而一般开发者不知道BeanDefinition,故理解含义成了[xml配置->Bean],中间缺少了重要的环节。在BeanDefinition到Bean这个过程中,Spring由BeanFactory生成实际的bean,在实际bean产生前,Spring提供了BeanFactoryPostProcessor扩展点(类似还有BeanPostProcessor),通过该扩展点可以获取到配置的BeanDefinition信息,用于自定义扩展对bean定义变更修改,实现自由控制。

  Spring允许开发者实现自定义的扩展点,实现特定的接口,使用通用的配置即可注册一个扩展点到Spring容器内。详细学习参考https://docs.spring.io/spring/docs/4.3.19.RELEASE/spring-framework-reference/htmlsingle/#beans-factory-nature。

  参数中心系统参数的配置实现参考了Spring的${}参数配置,我们对${}的实现做简单学习。${}的实现便使用了Spring扩展点BeanFactoryPostProcessor,开发中常见配置如下:

  

  

  上图中采用了context:property-placeholder标签配置,根据Spring context的xsd说明文件,我们知道了property-placeholder对应的实际类为org.springframework.context.support.PropertySourcesPlaceholderConfigurer,context:property-placeholder配置实际为在spring容器内注册一个扩展点,实现${}表达式的解析实现类图以及调用流程大致如下,再详细过程查看源码,(有句话叫做师傅领进门,修行在个人):

  参数中心系统客户端的实现代码与上述实现类似,不同的是properties配置文件变成了ZooKeeper参数存储,${}变成了zk{}。

  客户端项目名称:itwatertop-pczk-client

管理端

  基于H5的管理页面设计,详细情况还未设想(先实现了主要的服务端客户端,管理端暂时可以使用ZooKeeper的客户端)。 

六、客户端设计源码

  客户端程序结构如下:

  文件说明:

BaseLoader.java 数据加载基类ZookeeperDataLoader.java Java ZooKeeper客户端实现数据加载,参数更新回调。PlaceholderMsg.java zk{zkexp} zk配置表达式解析结果,以及使用该表达式的bean属性获取SpEL表达式。ParamCenterStore.java 对ZooKeeper参数服务端获取的参数信息做缓存,并且将对应的使用该参数的Bean属性表达式统计,方便ZooKeeper参数变更时回调使用。PczkConstants.java 系统内常量字符窜整合。PczkStringValueResolver.java 表达式解析统一接口PczkPropertyPlaceholderConfiguer.java 实现Spring扩展点BeanFactoryPostProcessor,通过该扩展点对BeanDefinition配置元信息做解析以及变更。PropertyPlaceholderHelper.java 具体实现zk{}表达式解析规则。PczkBeanDefinitionVisitor.java 对Spring IoC容器内的BeanDefinition属性配置信息做解析,主要结合PczkPropertyPlaceholderConfiguer.java使用。test目录下包含一部分测试代码,可以自行查看

  客户端代码实现简介:

  根据上述需求,客户端代码需要具备的能力包括:

    与ZooKeeper服务器的连通,并获取参数信息Spring xml中zk{}表达式的解析多样化的参数配置,支持字符窜,对象,数组ZooKeeper参数变更回调,维护参数信息

  针对上面4种要求,在开发时使用以下解决办法:

    使用ZooKeeper Java客户端;结合Spring的扩展点BeanFactoryPostProcessor;在ZooKeeper服务端节点内设置数据时设置字符窜/JSON对象字符窜或者是JSON数组字符窜,客户端使用数据时分别配置为zk{param},zk{param.key},zk{param[i]},通过对zk{}表达式解析判断ZooKeeper参数格式,若为JSON字符窜使用FastJSON对字符窜做解析,访问对应属性值;针对参数变更回调,在解析zk{}表达式时拼接出了可以访问到对应bean属性的SpEL表达式,通过SpEL表达式访问bean属性调用setter方法,因此属性操作也受到SpEL表达式的限制。在个别情况下,由于参数的变更可能需要别的一下操作处理,比如重新建立连接,这个可以自行扩展代码,比如比较上一次的值和当前值是否一致,不一致做出新的操作(现在也在设想怎么可以自动识别进行额外操作)。

  客户端详细实现源码下载:访问GitHub项目(注释还是比较清晰的)

七、说明

  本人技术有限,上述有错误的理解欢迎指出,共同交流学习,若对上述说明不了解,建议先学习一点Spring IoC设计,学习地址:https://docs.spring.io/spring/docs/4.3.19.RELEASE/spring-framework-reference/htmlsingle,若对于客户端的设计有好的建议可以提出来,共同讨论。