RuleLinKClient - 再也不担心表达引擎宕机了
原来有这么多时间
六月的那么一天,天气比以往时候都更凉爽,媳妇边收拾桌子,边漫不经心的对我说:你最近好像都没怎么阅读了。 正刷着新闻我,如同被一记响亮的晴空霹雳击中一般,不知所措。是了,最近几月诸事凑一起,加之两大项目接踵而至,确实有些许糟心,于是总是在空闲的时间泡在新闻里聊以解忧,再回首,隐隐有些恍如隔世之感。于是收拾好心情,翻开了躺在书架良久的整洁三步曲。也许是太久没有阅读了, 一口气,Bob大叔 Clean 系列三本都读完了,重点推荐Clear Architecture,部分章节建议重复读,比如第5部分-软件架构,可以让你有真正的提升,对代码,对编程,对软件都会有不一样的认识。
Clean Code 次之,基本写了一些常见的规约,大部分也是大家熟知,数据结构与面向对象的看法,是少有的让我 哇喔的点,如果真是在码路上摸跋滚打过的,快速翻阅即可。
The Clean Coder 对个人而言可能作用最小。 确实写人最难,无法聚焦。讲了很多,但是感觉都不深入,或者作者是在写自己,很难映射到自己身上。 当然,第二章说不,与第14章辅导,学徒与技艺,还是值得一看的。
阅读技术书之余,又战战兢兢的翻开了敬畏已久的朱生豪先生翻译的《莎士比亚》, 不看则已,因为看了根本停不来。其华丽的辞职,幽默的比喻,真的会让人情不自禁的开怀朗读起来。
。。。
再看从6月到现在,电子书阅读时间超过120小时,平均每天原来有1个多小时的空余时间,简直超乎想像。
看了整洁架构一书,就想写代码,于是有了这篇文章。
灵魂拷问 - 宕机怎么办
为了解决系统中大量规则配置的问题,与同事一起构建了一个可视化表达式引擎 RuleLink《非全自研可视化表达引擎-RuleLinK》,解决了公司内部几乎所有配置问题。尤为重要的一点,所有配置业务同学即可自助完成。随着业务深入又增加了一些自定义函数,增加了公式及计算功能,增加组件无缝嵌入其他业务...我一度以为现在的功能已经可以满足绝大部分场景了。真到Wsin强同学说了一句:业财项目是深度依赖RuleLink的,流水打标,关联科目。。。我知道他看了数据,10分RuleLink执行了5万+次。这也就意味着,如果RuleLink宕机了,业财服务也就宕机了,也就意味着巨大的事故。这却是是一个问题,公司业务确实属于非常低频,架不住财务数据这么多。如果才能让RuleLink更稳定成了当前的首要问题。
高可用VS少依赖
要提升服务的可用性,增加服务的实例是最快的方式。 但是考虑到我们自己的业务属性,以及业财只是在每天固定的几个时间点短时高频调用。 增加节点似乎不是最经济的方式。看 Bob大叔的《Clear Architecture》书中,对架构的稳定性有这样一个公式:不稳定性,I=Fan-out/(Fan-in+Fan-out)
Fan-in:入向依赖,这个指标指代了组件外部类依赖于组件内部类的数量。
Fan-out:出向依赖,这个指标指代了组件内部类依赖于组件外部类的数量。
这个想法,对于各个微服务的稳定性同时适用,少一个外部依赖,稳定性就增加一些。站在业财系统来说,如果我能减少调用次数,其稳定性就在提升,批量接口可以一定程度上减少依赖,但并未解决根本问题。那么调用次数减少到极限会是什么样的呢?答案是:一次。如果规则不变的话,我只需要启动时加载远程规则,并在本地容器执行规则的解析。如果有变动,我们只需要监听变化即可。这样极大减少了业财对RuleLink的依赖,也不用增RuleLink的节点。实际上大部分配置中心都是这样的设计的,比如apollo,nacos。 当然,本文的实现方式也有非常多借鉴(copy)了apollo的思想与实现。
服务端设计
模型比较比较简单,应用订阅场景,场景及其规则变化时,或者订阅关系变化时,生成应用与场景变更记录。类似于生成者-消费都模型,使用DB做存储。
”推送”原理
整体逻辑参考apollo实现方式。 服务端启动后 创建Bean ReleaseMessageScanner 注入变更监听器 NotificationController。
ReleaseMessageScanner 一个线程定时扫码变更,如果有变化 通知到所有监听器。
NotificationController在得知有配置发布后是如何通知到客户端的呢?
实现方式如下:
1,客户端会发起一个Http请求到RuleLink的接口,NotificationController
2,NotificationController不会立即返回结果,而是通过Spring DeferredResult把请求挂起
3,如果在60秒内没有该客户端关心的配置发布,那么会返回Http状态码304给客户端
4,如果有该客户端关心的配置发布,NotificationController会调用DeferredResult的setResult方法,传入有变化的场景列表,同时该请求会立即返回。客户端从返回的结果中获取到有变化的场景后,会直接更新缓存中场景,并更新刷新时间
ReleaseMessageScanner 比较简单,如下。NotificationController 代码也简单,就是收到更新消息,setResult返回(如果有请求正在等待的话)
public class ReleaseMessageScanner implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(ReleaseMessageScanner.class); private final AppSceneChangeLogRepository changeLogRepository;
private int databaseScanInterval;
private final List<ReleaseMessageListener> listeners;
private final ScheduledExecutorService executorService; public ReleaseMessageScanner(final AppSceneChangeLogRepository changeLogRepository) {
this.changeLogRepository = changeLogRepository;
databaseScanInterval = 5000;
listeners = Lists.newCopyOnWriteArrayList();
executorService = Executors.newScheduledThreadPool(1, RuleThreadFactory
.create("ReleaseMessageScanner", true));
} @Override
public void afterPropertiesSet() throws Exception {
executorService.scheduleWithFixedDelay(() -> {
try {
scanMessages();
} catch (Throwable ex) {
logger.error("Scan and send message failed", ex);
} finally { }
}, databaseScanInterval, databaseScanInterval, TimeUnit.MILLISECONDS); } /**
* add message listeners for release message
* @param listener
*/
public void addMessageListener(ReleaseMessageListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
} /**
* Scan messages, continue scanning until there is no more messages
*/
private void scanMessages() {
boolean hasMoreMessages = true;
while (hasMoreMessages && !Thread.currentThread().isInterrupted()) {
hasMoreMessages = scanAndSendMessages();
}
} /**
* scan messages and send
*
* @return whether there are more messages
*/
private boolean scanAndSendMessages() {
//current batch is 500
List<AppSceneChangeLogEntity> releaseMessages =
changeLogRepository.findUnSyncAppList();
if (CollectionUtils.isEmpty(releaseMessages)) {
return false;
}
fireMessageScanned(releaseMessages); return false;
} /**
* Notify listeners with messages loaded
* @param messages
*/
private void fireMessageScanned(Iterable<AppSceneChangeLogEntity> messages) {
for (AppSceneChangeLogEntity message : messages) {
for (ReleaseMessageListener listener : listeners) {
try {
listener.handleMessage(message.getAppId(), "");
} catch (Throwable ex) {
logger.error("Failed to invoke message listener {}", listener.getClass(), ex);
}
}
}
}
}
客户端设计
上图简要描述了客户端的实现原理:
- 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。(通过Http Long Polling实现)
- 客户端还会定时从RuleLink配置中心服务端拉取应用的最新配置。
- 这是一个fallback机制,为了防止推送机制失效导致配置不更新
- 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
- 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定配置项: rule.refreshInterval来覆盖,单位为分钟。
- 客户端从RuleLink配置中心服务端获取到应用的最新配置后,会写入内存保存到SceneHolder中,
- 可以通过RuleLinkMonitor 查看client 配置刷新时间,以及内存中的规则是否远端相同
客户端工程
客户端以starter的形式,通过注解EnableRuleLinkClient 开始初始化。
1 /**
2 * @author JJ
3 */
4 @Retention(RetentionPolicy.RUNTIME)
5 @Target(ElementType.TYPE)
6 @Documented
7 @Import({EnableRuleLinkClientImportSelector.class})
8 public @interface EnableRuleLinkClient {
9
10 /**
11 * The order of the client config, default is {@link Ordered#LOWEST_PRECEDENCE}, which is Integer.MAX_VALUE.
12 * @return
13 */
14 int order() default Ordered.LOWEST_PRECEDENCE;
15 }
在最需求的地方应用起来
花了大概3个周的业余时间,搭建了client工程,经过一番斗争后,决定直接用到了最迫切的项目 - 业财。当然,也做了完全准备,可以随时切换到RPC版本。 得益于DeferredResult的应用,变更总会在60s内同步,也有兜底方案:每300s主动查询变更,即便是启动后RuleLink宕机了,也不影响其运行。这样的准备之下,上线后几乎没有任何波澜。当然,也就没有人会担心宕机了。这真可以算得上一次愉快的编程之旅。
成为一名优秀的程序员!
RuleLinKClient - 再也不担心表达引擎宕机了的更多相关文章
- Linux服务器宕机案例一则
案例环境 操作系统 :Oracle Linux Server release 5.7 64bit 虚拟机 硬件配置 : 物理机型号为DELL R720 资源配置 :RAM 8G Intel(R) Xe ...
- MySQL - 高可用性:少宕机即高可用?
我们之前了解了复制.扩展性,接下来就让我们来了解可用性.归根到底,高可用性就意味着 "更少的宕机时间". 老规矩,讨论一个名词,首先要给它下个定义,那么什么是可用性? 1 什么是可 ...
- MySQL 系列(四) 主从复制、读写分离、模拟宕机、备份恢复方案生产环境实战
本章内容: 主从复制 简介原理 备份主库及恢复从库,配置从库生效 读写分离 如果主宕机了,怎么办? 双主的情况 MySQL 备份及恢复方案 备份单个及多个数据库 mysqldump 的常用参数 如何增 ...
- 利用CH341A编程器刷新BIOS,恢复BIOS,妈妈再也不用担心BIOS刷坏了
前几天,修电脑主析就捣鼓刷BIOS,结果刷完黑屏开不了机,立刻意识到完了,BIOS刷错了.就从网上查资料,各种方法试了个遍,什么用处都没有.终于功夫不负有心人,找到了编码器,知道了怎么用.下面看看具体 ...
- Cloud Native Weekly |面对云平台宕机,企业如何止损
KubeEdge v0.2发布 KubeEdge在18年11月24日的上海KubeCon上宣布开源的一个开源项目,旨在依托K8S的容器编排和调度能力,实现云边协同.计算下沉.海量设备的平滑接入. Ku ...
- HBase–RegionServer宕机恢复原理
Region Server宕机总述 HBase一个很大的特色是扩展性极其友好,可以通过简单地加机器实现集群规模的线性扩展,而且机器的配置并不需要太好,通过大量廉价机器代替价格昂贵的高性能机器.但也正因 ...
- clickhouse高可用-节点宕机数据一致性方案-热扩容
1. 集群节点及服务分配 说明: 1.1. 在每个节点上启动两个clickhouse服务(后面会详细介绍如何操作这一步),一个数据分片,一个数据备份,为了确保宕机数据一致性,数据分片和数据备份不能同一 ...
- 妈妈再也不担心我面试被Redis问得脸都绿了
长文前排提醒,收藏向前排提醒,素质三连 (转发 + 在看 + 留言) 前排提醒! 前言 Redis 作为一个开源的,高级的键值存储和一个适用的解决方案,已经越来越在构建 「高性能」.「可扩展」 的 W ...
- Redis 日志篇:无畏宕机快速恢复的杀手锏
特立独行是对的,融入圈子也是对的,重点是要想清楚自己向往怎样的生活,为此愿意付出怎样的代价. 我们通常将 Redis 作为缓存使用,提高读取响应性能,一旦 Redis 宕机,内存中的数据全部丢失,假如 ...
- 一寸宕机一寸血,十万容器十万兵|Win10/Mac系统下基于Kubernetes(k8s)搭建Gunicorn+Flask高可用Web集群
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_185 2021年,君不言容器技术则已,欲言容器则必称Docker,毫无疑问,它是当今最流行的容器技术之一,但是当我们面对海量的镜像 ...
随机推荐
- CF2B
非常恶心的dp题,测试数据也恶心,坑多,特别是0的坑 #include <iostream> #include <utility> #include <string> ...
- 解决方案 | 在 Tkinter 中导入 pywinauto/pyautogui 时窗口大小发生变化
上面问题也可以换一个说法,pywinauto/pyautogui 时改变了tkinter的原有的窗口大小.这个问题困扰了我好几天而且网上有这样的问题但是并没有答案,今天摸索出答案给大家分享下.解决方法 ...
- [oeasy]python0072_整数类型_int_integer_整型变量
帮助手册 回忆上次内容 上次了解的是 字符串 字符串 就是 字符的串 字符串长度 可以用 len函数 字符可以用下标索引 [] 可以用str 将整型数字 转化为 字符串 字符的长度本身 有 ...
- oeasy 教您玩转linux 之010208 满屏乱码 bb
我们来回顾一下 上一部分我们都讲了什么? 黑客帝国 各种速度颜色参数 最后我们想找一个让人完全崩溃的软件包,这不就来了,话不多说,先试试. apt show bbapt search bbsudo a ...
- oeasy教您玩转vim - 46 - # 范围控制
范围控制 回忆上节课内容 这次我们主要就是看命令行 首先是选择一个 [range] 这个范围 然后进行相应的操作 :11,30d :2,7y 还可以指定寄存器 :"a3,40y :&qu ...
- Oracle 存储过程学习总结
创建/更新存储过程 基础基础用法 创建/修改无参存储过程 CREATE OR REPLACE PROCEDURE procedure_name [IS|AS] --声明全局变量(可选) BEGIN - ...
- docker centos8 java8 mysql8 部署springboot项目
docker centos8 java8 mysql8 部署springboot项目 一,用idea将springboot项目打成jar包 二,将打的jar包用xshell的rz上传到docker的c ...
- ambari+ bigtop 编译、打包、部署步骤总览
1 ambari + bigtop 构建大数据基础平台 1.1 参考: 1.2 参考 amabri bigtop 打包部署 2 ambari+bigtop编译.打包.部署 2.0 基础环境准备 2.1 ...
- 【HTML】hr标签 分割线 自定义样式
参考: https://blog.csdn.net/weixin_40716682/article/details/90064473 默认是有边框线的 删除边框线,设置高度和背景色即可 backgro ...
- 【SpringMVC】10 对Ajax的应用
编写一个AjaxController package cn.dai.controller; import org.springframework.web.bind.annotation.GetMapp ...