《分佈式邏輯管理平臺XXL-GLUE》
一、簡介
1.1 概述
XXL-GLUE 是一個分佈式環境下的 "可執行邏輯單元" 管理平臺, 學習簡單,擴展JVM的動態語言支持。現已開放源代碼並接入多家公司線上產品線,開箱即用。
GLUE:即"可執行邏輯",本質上是一段可執行的代碼。GLUE可以方便的嵌入業務代碼中, GLUE中邏輯代碼支持在線開發、動態推送更新、實時編譯生效。 可以節省部分因爲項目編譯、打包、部署和重啓線上機器所帶來的時間和人工消耗, 提高開發效率。
可以參考 “配置管理系統,如disconf xxl-conf等” 概念來幫助我們來理解XXL-GLUE。 前者維護 "配置信息",而且支持數據類型有限。XXL-GLUE功能更強大, 支持維護"可執行邏輯代碼"。 XXL-GLUE在功能上完全可以替代前者,只需要在可執行代碼塊中返回配置即可,支持返回任意類型配置數據。XXL-GLUE主要作用是託管"可執行邏輯代碼",將會爲開發者代碼不一樣的開發體驗。
1.2 特性
- 1、動態:"可執行邏輯單元(GLUE)"支持在線開發、動態推送更新、實時編譯生效, 擴展JVM的動態語言支持;
- 2、Wed IDE:提供WedIDE,支持在線開發GLUE代碼;
- 3、推送更新:GLUE代碼修改後,開發人員可手動觸發GLUE更新廣播,廣播組件將會實時推送GLUE到接入方項目,從而實時更新GLUE;
- 4、兼容Spring:GLUE代碼中支持@Resource和@Autowired兩種方式注入Spring容器中服務;
- 5、版本:支持30個歷史版本的版本回溯;
- 6、調試: 在開發階段可開啓本地模式, 該模式下將會加載本地GlueHandler文件, 支持Debug, 可以方便的進行本地調試;
- 7、項目分組:支持設置項目分組,以項目爲維度進行GLUE分組管理;
1.3 下載
文檔地址
源碼倉庫地址
源碼倉庫地址 | Release Download |
---|---|
https://github.com/xuxueli/xxl-glue | Download |
https://gitee.com/xuxueli0323/xxl-glue | Download |
技術交流羣 (僅作技術交流)
1.4 環境
- JDK:1.7+
- Servlet/JSP Spec:3.1/2.3
- Tomcat:8.5.x
- Mysql:5.6+
- Maven:3+
二、快速入門
源碼目錄介紹
/db : 數據庫交表腳本位置
/xxl-glue-admin : GLUE管理中心
/xxl-glue-core : 公共依賴
/xxl-glue-core-example : GLUE接入Example項目, 可以參考它來學習如何在項目中接入並使用GLUE
1、初始化數據庫
執行數據庫建表腳本: /xxl-glue/doc/db/mysql_xxl_glue.sql
2、部署部署"GLUE管理中心"(xxl-glue-admin)
配置文件位置:xxl-glue-admin/resources/xxl-glue-admin.properties
### 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包部署即可。
3、部署部署 "GLUE接入Example項目"(xxl-glue-core-example)
客戶端maven依賴
<!-- 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.properties
### 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包部署即可。
4、開發第一個Glue (Hello World)
登陸 "GLUE管理中心" 並點擊右上角 "新增GLUE" 按鈕,填寫 “GLUE名稱”(該名稱是該GLUE項的唯一標示)和簡介,確定後即新增一條GLUE。
點擊GLUE右側 “Web IDE”按鈕,即可進入GLUE的代碼開發界面,可在該界面開發GLUE代碼,也可以在IDE中開發完成後粘貼進來,默認已經初始化Demo代碼。 (每個Glue必須是實現統一父接口GlueHandler的子類;詳情可參考章節 "5.1" )
初始化數據局之後,系統默認生成了三個典型場景的GLUE示例,可以參考GLUE示例開發第一個GLUE(GLUE的三種經典使用場景,可參考 "章節四")。
5、調用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/
三、操作詳解
1、登陸GLUE
2、項目列表
系統以項目爲維度進行GLUE分組管理;可以在 "項目管理" 模塊查看系統中的項目列表,默認已經提供了一個 "示例項目";
3、新增項目
在 "項目管理" 界面,點擊右上角 "新增項目" 可以新增項目,項目屬性說明如下:
項目AppName:項目AppName爲項目分組標識,在廣播刷新GLUE時可指定AppName實現灰度刷新指定AppNamd項目中的GLUE示例。正確格式爲:長度4-20位的小寫字母、數字和下劃線
項目名稱:項目中文名稱
4、:GLUE列表
5、新建GLUE
點擊右上角 "新建GLUE" 按鈕,彈框填寫GLUE信息即可新建GLUE,屬性介紹如下:
項目:GLUE所屬的項目,
GLUE:Glue名稱,每個GLUE的唯一標示,新建GLUE時將會自動將所項目的AppName作爲名稱前綴。正確格式爲:長度4-20位的大小寫字母、數字和下劃線
描述:GLUE的描述介紹信息
6、開發GLUE代碼
找到新建的GLUE,點擊右側的 “Web IDE” 按鈕進入GLUE開發的Wed IDE界面。默認已經初始化示例代碼,如需開發業務代碼,只需要在handle方法中開發即可。
7、一句話執行GlueHandler
首先確定項目中已經接入GLUE(參考上文 “GLUE接入Example項目(xxl-glue-core-example)”,接入非常方便);
業務中調用Glue只需要執行以下一行代碼即可:
Object result = GlueFactory.glue("glue名稱", "glue入參,Map類型");
8、推送更新
Glue在第一次加載之後將會緩存在內存中,點擊右側 “清除緩存” 按鈕可以推送刷新GLUE緩存。 清除緩存彈框中有一個輸入框 "Witch APP", 輸入接入方項目的AppName, 即可精確的灰度刷新該項目中的相應Glue。如果不輸入, 則廣播刷新所有項目中響應的Glue。
9、如何Debug測試GlueHandler
首先,需要了解源碼加載器配置,見項目"xxl-glue-core-example"的"applicationcontext-glue.xml"配置中"GlueFactory"實例的"glueLoader"屬性;
- dbGlueLoader(數據庫加載器,不支持Debug,源碼線上維護): 默認XXL-GLUE在接入方通過加載數據庫中GLUE源碼進而實例化並執行, 需要配置一個"dbGlueLoader"。
- FileGlueLoader(本地加載器,支持Debug): XXL-GLUE支持配置本地加載器, 只是掉上述配置中的"glueLoader"屬性即可,則使用默認的文件加載器;擁有以下特點:
- 默認加載項目"resources/config/glue"目錄下的groovy文件,文件名稱必須和配置的GlueHandler名稱一致;
- 會循環遍歷該目錄下子目錄文件進行匹配,因此文件可以在該目錄下自由存放;
- FileGlueLoader方式使用XXL-GLUE,支持GlueHandler的debug斷點;
當源碼加載器選擇 "FileGlueLoader" 時,將會加載本地GLUE腳本文件,此時可以進行代碼Debug操作。
四、GlueHandler的三種經典使用場景,
示例場景01:託管 “配置信息”
尤其適用於數據結構比較複雜的配置項
package com.xxl.glue.example.handler;
import com.xxl.glue.core.handler.GlueHandler;
import java.util.HashSet;
import java.util.Map;
/**
* 示例場景01:託管 “配置信息”
*
* 優點:
* 1、在線編輯;推送更新;
* 2、該場景下,相較於同類型配置管理系統,支持數據類型更加豐富,不僅支持基礎類型,甚至支持複雜對象;
* 3、該場景下,配置信息的操作和展示,更加直觀;
*
* @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;
}
}
場景02:託管 “靜態方法”
可以將配置解析邏輯一併託管,只關注返回結果即可
package com.xxl.glue.example.handler;
import com.xxl.glue.core.handler.GlueHandler;
import java.util.HashSet;
import java.util.Map;
/**
* 示例場景02:託管 “靜態方法”
*
* 優點:
* 1、在線編輯;推送更新;
* 2、該場景下,託管公共組件,方便組件統一維護和升級;
*
* @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);
}
}
場景C:示例場景03:託管 “動態服務”
可以靈活組裝接口和服務,擴展服務的動態特性,作爲公共服務。
package com.xxl.glue.example.handler;
import com.xxl.glue.core.handler.GlueHandler;
import java.util.Map;
/**
* 示例場景03:託管 “動態服務”
*
* 優點:
* 1、在線編輯;推送更新;
* 2、該場景下,服務內部可以靈活組裝和調用其他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的代碼實現, 本質上是實現統一父接口的子類, 約定了公共方法以及公共的輸入輸出以便於與業務代碼交互。
- Broadcase: 廣播組件, 當GlueHandler更新時, 將會觸發廣播消息;
- GLUE管理中心: 管理中心, 提供對GlueHandler的管理功能, 同時提供 WebIDE 支持在線開發GlueHandler, 並藉助 "Broadcase" 組件提供邏輯單元"GlueHandler"實時推送功能;
- GLUE服務: Glue源碼加載的RPC服務, 提供GlueHandler對應源碼的加載服務;
- Client: GLUE的接入方, 可以動態的使用託管的GlueHandler, 並享受實時推送功能。避免了邏輯變更帶來的一系列編譯、打包、部署和重啓線上機器等流程;
5.1、GlueHandler 剖析
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 虛擬機的一種敏捷的動態語言;
- 1、以強大的Java爲基礎;
- 2、包含Python、Ruby、Smalltalk等語言強帶附加功能,例如動態類型轉換、閉包和元編程支持;
- 3、一種成熟的面嚮對象語言,同時可用作純粹的校驗語言;
- 4、適合與Spring動態語言支持一起使用,因爲它專門爲JVM設計,充分考慮Java繼承;
- 5、與Java代碼互操作很容易;
5.2、GlueHandler 執行步驟
接入方,執行託管在GLUE平臺上的一個GlueHandler中的代碼邏輯時, 執行步驟如下:
- 1、緩存命中: 首先, 在本地緩存中匹配 "GlueHandler實例" , 匹配成功則調用統一公共方法即可; 否則執行步驟2;
- 2、實例化: RPC方式加載相應GlueHandler對應源碼, 並通過 "GroovyClassLoader" 最終實例化爲 "GlueHandler實例" ;
- 3、依賴注入: 執行 "injectService", 反射方式爲 "GlueHandler實例" 注入依賴的Spring服務;
- 4、加入緩存: 將 "GlueHandler實例" 加入本地緩存;
- 5、註冊監聽: 註冊對該 "GlueHandler" 的廣播消息監聽;
- 6、invoke: 執行 "GlueHandler實例" 的公共方法, 返回執行結果;
- 7、Finish;
5.3、Spring服務注入
支持 “Resource.class” 和 “Autowired.class” 兩種方式爲GlueHandler輸入Spring服務,實現邏輯如下:
- 1、 反射獲取GlueHandler的Field數組;
- 2、 遍歷Field數組,根據其註解 “@Resource” 和 “@Autowired” 在Spring容器匹配服務(注入規則同Spring默認規則:@Autowired按類型注入;@Resource按照首先名稱注入,失敗則按照類型注入;);
- 3、將匹配到的Spring服務注入到該Field中。
5.4、廣播組件
Glue中通過ZK實現了一套廣播機制, 採用廣播的方式進行觸發主動更新。 系統在ZK中持久化一個node節點, 當GLUE需要廣播更新時將會將廣播消息(包含:GLUE名稱、灰度項目、版本號等)序列化後賦值給該節點。該GLUE的接入方項目將會監聽到事件通知並及時刷新緩存;
5.5、緩存更新策略(異步 + 覆蓋)
緩存對象
Glue中緩存的對象是“groovyClassLoader”解析生成的GlueHandler實例。
Timeout
GlueHandler緩存支持設置Timeout時間,單位毫秒,緩存失效時將會實例化加載新的GLueHander實例,Timeout設置爲-1時將永不失效。
避免緩存雪崩
常規緩存更新,通常是通過remove(key)的方式進行緩存清理,然後在下次請求時將會以懶加載的方式進行緩存初始化,但是這樣在併發環境中有雪崩的隱患。 爲避免緩存雪崩情況,GlueHandler採用 “異步(queue + thread)”+“覆蓋”的方式進行GlueHandler更新,步驟如下:
1、在接收到緩存更新的廣播消息時,首先會將待更新的GlueHandler的名稱push到待更新隊列中;
2、異步線程監控待更新隊列,獲取待更新GlueHandler名稱,加載並實例化新GlueHandler實例;
3、將新的GlueHandler實例,覆蓋緩存中舊的GlueHandler實例,後續調用將會執行新的業務邏輯。
避免冗餘的緩存刷新
常規緩存刷新,通常流程是:每點擊一次刷新,生成一個新value值,覆蓋舊的value值。但是,當新value值和舊value值相等時,這種邏輯是冗餘甚至會降低性能的,特別是生成新value比較複雜時。 爲避免冗餘緩存刷新情況,底層對每個GLUE記錄一個version,當監聽到GLUE廣播刷新消息時會對比version是否一致,相同版本的GLUE不會觸發GlueLoader的刷新流程。
5.6、灰度更新
GlueHandler通過廣播的方式進行推送更新,在推送廣播消息時支持輸入待刷新該GlueHandler的項目AppName列表,只有匹配到的項目纔會對本項目中GlueHandler進行覆蓋更新,否則忽視該條廣播消息。爲空則全站廣播。 因此,可通過上述機制只刷新集羣中一臺機器上的某個GlueHandler,從而實現灰度功能。
5.7、版本回溯
GlueHandler的每次更新都會進行歷史版本源碼備份,默認支持記錄最近的30個版本。 同時,在Web IDE界面上,可以查看到所有的備份記錄,並且可以方便的進行版本回退。
5.8、預熱
GlueHandler創建時和項目關聯起來,這樣在項目啓動時會主動加載關聯到的GlueHandler,避免懶加載引起的併發問題。
5.9、避免因Permanet Generation空間被佔滿引起的Full GC
PermanetGeneration中存放的爲一些class的信息等,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被佔滿。
GLUE底層基於Groovy實現,Groovy之前使用時曾經出現過頻繁Full GC的問題,原因如下:
系統在執行 “groovy.lang.GroovyClassLoader.parseClass(groovyScript)” 進行groovy代碼解析時,Groovy爲了保證解析後執行的都是最新的腳本內容,每進行一次解析都會生成一次新命名的Class文件,如下圖:
因此,如果Groovy類加載器設置爲靜態,當對同一段腳本均多次執行該方法時,會導致 “GroovyClassLoader” 裝載的Class越來越多,從而導致PermGen被用滿。
爲了避免出現類似問題,GLUE做了以下幾點優化。
- 1、針對每個GlueHandler解析後生成的Java對象實例做緩存,而不是代碼本身做緩存;
- 2、僅僅在接收到清除緩存的廣播時解析生成新的GlueHandler實例對象,避免groovy的頻繁解析,減少Class裝載頻率;
- 3、週期性的異步刷新類加載器,避免因全局類加載器裝載Class過多且不及時卸載導致的PermGen被用滿。
5.10、調試
由於GlueHandler源碼存儲在雲端, 調試不便。因此係統提供了 "com.xxl.glue.core.loader.impl.FileGlueLoader" ,用於開啓 "本地模式", 該模式系統將會從項目資源目錄的子目錄 "config/glue" 中加載源碼, 同時支持debug, 可以方便的進行業務代碼調試。
六、歷史版本
版本1.0.0
- 1、動態(groovy):託管在平臺中的GlueHandler以 "groovy" 的方式進行加載實例化, 擴展JVM的動態語言支持;
- 2、在線(Wed IDE):提供WedIDE,支持在線管理和開發GlueHandler;
- 3、推送更新:當GlueHandler變動時, 將會通過廣播組件, 實時推送接入方對應的GlueHandler進行reload更新, 保證GlueHandler中業務邏輯的實時性;
- 4、兼容Spring:無縫兼容Spring, 支持@Resource和@Autowired兩種方式注入Spring容器中服務;
- 5、版本:支持50個歷史版本的版本回溯;
版本1.1.0
- 1、系統重構;
- 2、調試: 在開發階段可開啓本地模式, 該模式下將會加載本地GlueHandler文件, 支持Debug, 可以方便的進行本地調試;
版本1.2.0
- 1、廣播組件由Activemq改爲自主實現的基於ZK的廣播組件, 減少系統第三方依賴;
- 2、新增Local模式,提供GLUE本地加載器, 支持加載本地GlueHandler, 方便進行Debug調試;
- 3、異步刷新緩存邏輯更新,新實例正常則覆蓋,否則remove掉舊實例;
- 4、修復一處因ReentrantLock導致可能死鎖的問題;
- 5、導航菜單更新;
- 6、底層代碼重構, 結構優化;
版本1.3.0
- 1、項目分組:支持設置項目分組,以項目爲維度進行GLUE分組管理;
- 2、UI和交互升級;
- 3、GLUE依賴注入邏輯優化,支持別名注入;
- 4、廣播機制優化:接收到GLUE刷新廣播時校驗GLUE源碼version,避免冗餘的刷新邏輯;
版本1.3.1
- 1、項目推送maven中央倉庫,groupId遷移至 "com.xuxueli";
- 2、規範maven依賴版本;
TODO LIST
- 4、用戶管理;
- 5、權限管理;
七、其他
7.1 項目貢獻
歡迎參與項目貢獻!比如提交PR修復一個bug,或者新建 Issue 討論新特性或者變更。
7.2 用戶接入登記
更多接入的公司,歡迎在 登記地址 登記,登記僅僅爲了產品推廣。
7.3 開源協議和版權
產品開源免費,並且將持續提供免費的社區技術支持。個人或企業內部可自由的接入和使用。
- Licensed under the GNU General Public License (GPL) v3.
- Copyright (c) 2015-present, xuxueli.
捐贈
無論金額多少都足夠表達您這份心意,非常感謝 :) 前往捐贈