XXL-GLUE 是一个分布式环境下的 "可执行逻辑单元" 管理平台, 学习简单,扩展JVM的动态语言支持。现已开放源代码并接入多家公司线上产品线,开箱即用。html
GLUE:即"可执行逻辑",本质上是一段可执行的代码。GLUE能够方便的嵌入业务代码中, GLUE中逻辑代码支持在线开发、动态推送更新、实时编译生效。 能够节省部分由于项目编译、打包、部署和重启线上机器所带来的时间和人工消耗, 提升开发效率。java
能够参考 “配置管理系统,如disconf xxl-conf等” 概念来帮助咱们来理解XXL-GLUE。 前者维护 "配置信息",并且支持数据类型有限。XXL-GLUE功能更强大, 支持维护"可执行逻辑代码"。 XXL-GLUE在功能上彻底能够替代前者,只须要在可执行代码块中返回配置便可,支持返回任意类型配置数据。XXL-GLUE主要做用是托管"可执行逻辑代码",将会为开发者代码不同的开发体验。node
源码仓库地址 | Release Download |
---|---|
https://github.com/xuxueli/xxl-glue | Download |
https://gitee.com/xuxueli0323/xxl-glue | Download |
/db : 数据库交表脚本位置 /xxl-glue-admin : GLUE管理中心 /xxl-glue-core : 公共依赖 /xxl-glue-core-example : GLUE接入Example项目, 能够参考它来学习如何在项目中接入并使用GLUE
执行数据库建表脚本: /xxl-glue/doc/db/mysql_xxl_glue.sqlmysql
配置文件位置:xxl-glue-admin/resources/xxl-glue-admin.propertiesgit
### JDBC 配置 xxl.glue.db.driverClass=com.mysql.jdbc.Driver xxl.glue.db.url=jdbc:mysql://localhost:3306/xxl-glue?useUnicode=true&characterEncoding=UTF-8 xxl.glue.db.user=root xxl.glue.db.password=root_pwd ### zookeeper 地址配置:例如 "127.0.0.1:2181" 或 "127.0.0.1:2181,127.0.0.1:2182" xxl.glue.zkserver=127.0.0.1:2181 ### 登陆帐号密码 xxl.glue.login.username=admin xxl.glue.login.password=123456
编译War包部署便可。github
<!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-glue-core --> <dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-glue-core</artifactId> <version>1.3.1</version> </dependency>
配置文件位置:xxl-glue-core-example/resources/xxl-glue.propertiesredis
### JDBC 配置 xxl.glue.db.driverClass=com.mysql.jdbc.Driver xxl.glue.db.url=jdbc:mysql://localhost:3306/xxl-glue?useUnicode=true&characterEncoding=UTF-8 xxl.glue.db.user=root xxl.glue.db.password=root_pwd ### zookeeper 地址配置:例如 "127.0.0.1:2181" 或 "127.0.0.1:2181,127.0.0.1:2182" xxl.glue.zkserver=127.0.0.1:2181
编译War包部署便可。sql
登录 "GLUE管理中心" 并点击右上角 "新增GLUE" 按钮,填写 “GLUE名称”(该名称是该GLUE项的惟一标示)和简介,肯定后即新增一条GLUE。数据库
点击GLUE右侧 “Web IDE”按钮,便可进入GLUE的代码开发界面,可在该界面开发GLUE代码,也能够在IDE中开发完成后粘贴进来,默认已经初始化Demo代码。 (每一个Glue必须是实现统一父接口GlueHandler的子类;详情可参考章节 "5.1" )编程
初始化数据局以后,系统默认生成了三个典型场景的GLUE示例,能够参考GLUE示例开发第一个GLUE(GLUE的三种经典使用场景,可参考 "章节四")。
业务中调用Glue只须要执行如下一行代码便可:
Object result = GlueFactory.glue("glue名称", "glue入参,Map类型");
"GLUE接入Example项目(xxl-glue-core-example)" 中,针对 "初始化数据局以后,系统默认生成了三个典型场景的GLUE示例" 提供了相应的示例调用代码,代码位置是:
xxl-glue-core-example/com.xxl.glue.example.controller.IndexController.index
部署启动 "GLUE接入Example项目(xxl-glue-core-example)",假设项目部署在 "/xxl-glue-core-example" 路径,访问如下连接可执行测试逻辑。
http://localhost:8080/xxl-glue-core-example/
系统以项目为维度进行GLUE分组管理;能够在 "项目管理" 模块查看系统中的项目列表,默认已经提供了一个 "示例项目";
在 "项目管理" 界面,点击右上角 "新增项目" 能够新增项目,项目属性说明以下:
项目AppName:项目AppName为项目分组标识,在广播刷新GLUE时可指定AppName实现灰度刷新指定AppNamd项目中的GLUE示例。正确格式为:长度4-20位的小写字母、数字和下划线 项目名称:项目中文名称
点击右上角 "新建GLUE" 按钮,弹框填写GLUE信息便可新建GLUE,属性介绍以下:
项目:GLUE所属的项目, GLUE:Glue名称,每一个GLUE的惟一标示,新建GLUE时将会自动将所项目的AppName做为名称前缀。正确格式为:长度4-20位的大小写字母、数字和下划线 描述:GLUE的描述介绍信息
找到新建的GLUE,点击右侧的 “Web IDE” 按钮进入GLUE开发的Wed IDE界面。默认已经初始化示例代码,如需开发业务代码,只须要在handle方法中开发便可。
首先肯定项目中已经接入GLUE(参考上文 “GLUE接入Example项目(xxl-glue-core-example)”,接入很是方便);
业务中调用Glue只须要执行如下一行代码便可:
Object result = GlueFactory.glue("glue名称", "glue入参,Map类型");
Glue在第一次加载以后将会缓存在内存中,点击右侧 “清除缓存” 按钮能够推送刷新GLUE缓存。 清除缓存弹框中有一个输入框 "Witch APP", 输入接入方项目的AppName, 便可精确的灰度刷新该项目中的相应Glue。若是不输入, 则广播刷新全部项目中响应的Glue。
首先,须要了解源码加载器配置,见项目"xxl-glue-core-example"的"applicationcontext-glue.xml"配置中"GlueFactory"实例的"glueLoader"属性;
当源码加载器选择 "FileGlueLoader" 时,将会加载本地GLUE脚本文件,此时能够进行代码Debug操做。
尤为适用于数据结构比较复杂的配置项
package com.xxl.glue.example.handler; import com.xxl.glue.core.handler.GlueHandler; import java.util.HashSet; import java.util.Map; /** * 示例场景01:托管 “配置信息” * * 优势: * 一、在线编辑;推送更新; * 二、该场景下,相较于同类型配置管理系统,支持数据类型更加丰富,不只支持基础类型,甚至支持复杂对象; * 三、该场景下,配置信息的操做和展现,更加直观; * * @author xuxueli 2016-4-14 15:36:37 */ public class DemoGlueHandler01 implements GlueHandler { @Override public Object handle(Map<String, Object> params) { /* // 【基础类型配置】,例如:活动开关、短信发送次数阀值、redis地址等; boolean activitySwitch = true; // 活动开关:true=开、false=关 int smsLimitCount = 3; // 短信发送次数阀值 String brokerURL = "failover:(tcp://127.0.0.1:61616,tcp://127.0.0.2:61616)"; // redis地址等 // 【对象类型配置……】 */ // 【列表配置】 HashSet<String> blackTelephones = new HashSet<String>(); // 手机号码黑名单列表 blackTelephones.add("15000000000"); blackTelephones.add("15000000001"); blackTelephones.add("15000000002"); return blackTelephones; } }
能够将配置解析逻辑一并托管,只关注返回结果便可
package com.xxl.glue.example.handler; import com.xxl.glue.core.handler.GlueHandler; import java.util.HashSet; import java.util.Map; /** * 示例场景02:托管 “静态方法” * * 优势: * 一、在线编辑;推送更新; * 二、该场景下,托管公共组件,方便组件统一维护和升级; * * @author xuxueli 2016-4-14 16:07:03 */ public class DemoGlueHandler02 implements GlueHandler { // 手机号码黑名单列表 private static HashSet<String> blackTelephones = new HashSet<String>(); static { blackTelephones.add("15000000000"); blackTelephones.add("15000000001"); blackTelephones.add("15000000002"); } /** * 手机号码黑名单校验Util * * @param telephone * @return */ private boolean isBlackTelephone(String telephone) { if (telephone!=null && blackTelephones.contains(telephone)) { return true; } return false; } @Override public Object handle(Map<String, Object> params) { String telephone = (params!=null)? (String) params.get("telephone") :null; return isBlackTelephone(telephone); } }
能够灵活组装接口和服务,扩展服务的动态特性,做为公共服务。
package com.xxl.glue.example.handler; import com.xxl.glue.core.handler.GlueHandler; import java.util.Map; /** * 示例场景03:托管 “动态服务” * * 优势: * 一、在线编辑;推送更新; * 二、该场景下,服务内部能够灵活组装和调用其余Service服务, 扩展服务的动态特性,做为公共服务。 * * @author xuxueli 2016-4-14 16:07:03 */ public class DemoGlueHandler03 implements GlueHandler { private static final String SHOPID = "shopid"; /* @Resource private UserPhoneService userPhoneService; // 手机号码黑名单Service,此处仅做为示例 */ /** * 商户黑名单判断 */ @Override public Object handle(Map<String, Object> params) { /* String telephone = (params!=null)? (String) params.get("telephone") :null; boolean isBlackTelephone = userPhoneService.isBlackTelephone(telephone); return isBlackTelephone; */ return true; } }
GlueHandler是 "可执行逻辑"GLUE 的代码实现,本质上是实现统一父接口的子类, 约定了公共方法以及公共的输入输出以便于与业务代码交互。
其源码维护在数据库表中, 接入方经过GroovyClassLoader加载相应源码并实例化为 "GlueHandler对象", 调用时将会执行父类公共方法。
统一父接口:
package com.xxl.glue.core.handler; import java.util.Map; /** * default glue iface, it could be use in your biz service * @author xuxueli 2016-1-2 21:31:56 */ public interface GlueHandler { /** * defaule method * @param params * @return */ public Object handle(Map<String, Object> params); }
Groovy简介 : 用于 Java 虚拟机的一种敏捷的动态语言;
接入方,执行托管在GLUE平台上的一个GlueHandler中的代码逻辑时, 执行步骤以下:
支持 “Resource.class” 和 “Autowired.class” 两种方式为GlueHandler输入Spring服务,实现逻辑以下:
Glue中经过ZK实现了一套广播机制, 采用广播的方式进行触发主动更新。 系统在ZK中持久化一个node节点, 当GLUE须要广播更新时将会将广播消息(包含:GLUE名称、灰度项目、版本号等)序列化后赋值给该节点。该GLUE的接入方项目将会监听到事件通知并及时刷新缓存;
Glue中缓存的对象是“groovyClassLoader”解析生成的GlueHandler实例。
GlueHandler缓存支持设置Timeout时间,单位毫秒,缓存失效时将会实例化加载新的GLueHander实例,Timeout设置为-1时将永不失效。
常规缓存更新,一般是经过remove(key)的方式进行缓存清理,而后在下次请求时将会以懒加载的方式进行缓存初始化,可是这样在并发环境中有雪崩的隐患。 为避免缓存雪崩状况,GlueHandler采用 “异步(queue + thread)”+“覆盖”的方式进行GlueHandler更新,步骤以下:
一、在接收到缓存更新的广播消息时,首先会将待更新的GlueHandler的名称push到待更新队列中; 二、异步线程监控待更新队列,获取待更新GlueHandler名称,加载并实例化新GlueHandler实例; 三、将新的GlueHandler实例,覆盖缓存中旧的GlueHandler实例,后续调用将会执行新的业务逻辑。
常规缓存刷新,一般流程是:每点击一次刷新,生成一个新value值,覆盖旧的value值。可是,当新value值和旧value值相等时,这种逻辑是冗余甚至会下降性能的,特别是生成新value比较复杂时。 为避免冗余缓存刷新状况,底层对每一个GLUE记录一个version,当监听到GLUE广播刷新消息时会对比version是否一致,相同版本的GLUE不会触发GlueLoader的刷新流程。
GlueHandler经过广播的方式进行推送更新,在推送广播消息时支持输入待刷新该GlueHandler的项目AppName列表,只有匹配到的项目才会对本项目中GlueHandler进行覆盖更新,不然忽视该条广播消息。为空则全站广播。 所以,可经过上述机制只刷新集群中一台机器上的某个GlueHandler,从而实现灰度功能。
GlueHandler的每次更新都会进行历史版本源码备份,默认支持记录最近的30个版本。 同时,在Web IDE界面上,能够查看到全部的备份记录,而且能够方便的进行版本回退。
GlueHandler建立时和项目关联起来,这样在项目启动时会主动加载关联到的GlueHandler,避免懒加载引发的并发问题。
PermanetGeneration中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满。
GLUE底层基于Groovy实现,Groovy以前使用时曾经出现过频繁Full GC的问题,缘由以下:
系统在执行 “groovy.lang.GroovyClassLoader.parseClass(groovyScript)” 进行groovy代码解析时,Groovy为了保证解析后执行的都是最新的脚本内容,每进行一次解析都会生成一次新命名的Class文件,以下图:
所以,若是Groovy类加载器设置为静态,当对同一段脚本均屡次执行该方法时,会致使 “GroovyClassLoader” 装载的Class愈来愈多,从而致使PermGen被用满。
为了不出现相似问题,GLUE作了如下几点优化。
因为GlueHandler源码存储在云端, 调试不便。所以系统提供了 "com.xxl.glue.core.loader.impl.FileGlueLoader" ,用于开启 "本地模式", 该模式系统将会从项目资源目录的子目录 "config/glue" 中加载源码, 同时支持debug, 能够方便的进行业务代码调试。
欢迎参与项目贡献!好比提交PR修复一个bug,或者新建 Issue 讨论新特性或者变动。
更多接入的公司,欢迎在 登记地址 登记,登记仅仅为了产品推广。
产品开源免费,而且将持续提供免费的社区技术支持。我的或企业内部可自由的接入和使用。
不管金额多少都足够表达您这份心意,很是感谢 :) 前往捐赠