动态加载smart-reload

Smart Reload

SmartReload说明

可能一看到这个名字会很困惑,不知道什么意思。但是接下来我们想象一个场景:

比如一个电商:
如果你把一些商品信息放到java进程里的缓存里,那么当你需要更新某两个商品信息的时候怎么办?

有人说为啥不使用redis缓存? 额…并不是所有缓存都有必要使用redis
有人说不使用缓存?额…那还不如不用程序算了
有人说 重启?额…做好没年终奖的准备吧

所以系统需要有这个东西,在不重启java程序的前提下,能执行一个预留的代码。

这种场景叫做Reload

解决方案

其实有很多种解决方案,最经典的应该是 轮询和订阅这两种。
轮询:每个一段时间重新查询数据库
订阅:系统订阅消息,然后使用第三者发送消息给系统,进行一些操作

SmartAdmin的选择:轮询

SmartAdmin中的reload选择轮询+监听者模式策略。
原因:

  1. 轮询比订阅简单,订阅需要依赖其他第三方:比如redis订阅,kafka等MQ订阅,Zookeeper等
  2. 轮询可以专注于本应用

设计思想

启动线程去扫描某个表,表中存放着一些 reload项(reload item),但凡有reload项标识发生变化,就发送事件给那些监听reload项的java监听者。

具体请看:smartadmin.common.reload

1 表结构:
CREATE TABLE `t_reload_item` ( `tag` VARCHAR(255) NOT NULL COMMENT '项名称' COLLATE 'utf8_general_ci', `args` VARCHAR(255) NULL DEFAULT NULL COMMENT '参数 可选', `identification` VARCHAR(255) NOT NULL COMMENT '运行标识' COLLATE 'utf8_general_ci', `update_time` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`tag`) )

tag: reload项
args:listener执行之后的参数
identification:标识,identification与上次比较发生变化才进行reload

2 接口代码:

public interface SmartReloadCommandInterface { /** * 任务: * 读取数据库中 ReloadItem 数据 * 校验是否发生变化 * 执行重加载动作 */ void doTask(); /** * 该方法返回一个List<ReloadItem></>:<br> * ReloadItem对象的tagIdentify为:该tag的 状态(状态其实就是个字符串,如果该字符串跟上次有变化则进行reload操作)<br> * ReloadItem对象的args为: reload操作需要的参数<br><br> * * @return List<ReloadItem> */ List<ReloadItem> readReloadItem(); /** * 处理Reload结果 * * @param reloadResult */ void handleReloadResult(SmartReloadResult reloadResult); }
为Spring设计的reload项比较实现类
/** * 检测是否 Reload 的类 * * @author zhuoda */ public abstract class AbstractSmartReloadCommand4Spring implements SmartReloadCommandInterface { /** * 当前ReloadItem的存储器 */ protected ConcurrentHashMap<String, String> currentTags = new ConcurrentHashMap<>(); /** * Reload的执行类 */ @Autowired protected SmartReloadManager reloadManager; // @PostConstruct public void init() { List<ReloadItem> readTagStatesFromDb = readReloadItem(); if (readTagStatesFromDb != null) { for (ReloadItem reloadItem : readTagStatesFromDb) { String tag = reloadItem.getTag(); String tagChangeIdentifier = reloadItem.getIdentification(); this.currentTags.put(tag, tagChangeIdentifier); } } } /** * 任务: * 读取数据库中 ReloadItem 数据 * 校验是否发生变化 * 执行重加载动作 */ @Override public void doTask() { // 获取数据库数据 List<ReloadItem> readTagStatesFromDb = readReloadItem(); String tag; String tagIdentifier; String preTagChangeIdentifier; for (ReloadItem reloadItem : readTagStatesFromDb) { tag = reloadItem.getTag(); tagIdentifier = reloadItem.getIdentification(); preTagChangeIdentifier = currentTags.get(tag); // 数据不一致 if (preTagChangeIdentifier == null || ! preTagChangeIdentifier.equals(tagIdentifier)) { // 更新map数据 currentTags.put(tag, tagIdentifier); // 执行重新加载此项的动作 handleReloadResult(this.reloadManager.doReload(reloadItem)); } } } }
3 样例 Demo
@Slf4j @Service public class SystemConfigService { /** * 系统配置缓存 */ private ConcurrentHashMap<String, SystemConfigEntity> systemConfigMap = new ConcurrentHashMap<>(); @Autowired private SystemConfigDao systemConfigDao; @Autowired private SmartReloadService smartReloadService; /** * 初始化系统设置缓存 */ @PostConstruct private void initSystemConfigCache() { //系统启动的时候,注册到smart-reload smartReloadService.register(this); } @SmartReload(SmartReloadTagConst.SYSTEM_CONFIG) public boolean reload(String args) { this.initSystemConfigCache(); log.warn("<<SmartReload>> <<{}>> , args {} reload success ", SmartReloadTagConst.SYSTEM_CONFIG, args); return true; }

作者简介:
卓大, 1024创新实验室主任,混迹于各个技术,熟悉点java,略懂点前端。