Apache 神禹(shenyu)源码阅读(一)——Admin向Gateway的数据同步(Admin端)
源码版本:2.6.1
单机源码启动项目
启动教程:社区新人开发者启动及开发防踩坑指南
源码阅读
前言
开了个新坑,也是第一次阅读大型项目源码,写文章记录。
在写文章前,已经跑了 Divide 插件体验了一下(体验教程:Http快速开始)。
由于 shenyu 默认使用 H2 数据,但是我因为 IDEA 连接内存模式下的数据库有 BUG,连接不到,改用 MySQL(改用MySQL教程:Apache-Shenyu入门教程(demo实战及遇到的坑))。
认识 shenyu 架构以及本文的内容
shenyu 官方的一个架构图,红色圈部分是本文和下一篇文章研究的内容:
在查看 PluginChain 的过程中,想看 shenyu-admin(以下称 Admin)是如何向 Gateway 同步数据的。
同步数据我把它划分为三个部分:
- 一个是 Gateway 是如何连接上 Admin 的(通过 Websocket——shenyu 默认的同步方式)
- 一个是 Admin 通过 Websocket发送要同步的数据。
- 一个是 Gateway 从 Websocket 接收同步的数据进行同步。
本文研究第一个部分和第二个部分,下一篇研究第三个部分。
有博主(Apache ShenYu 源码阅读系列 - 基于 WebSocket 的数据同步)已经研究了这部分的内容,不过是21年的文章了,有些源码已经更新迭代过了,所以这篇文章就以最新的源码解读。
正文
1. 第一部分:Gateway 是如何连接上 Admin 的?
在 shenyu-bootstrap/src/main/resources/application.yml
中进行配置 websocket 属性。
对应的属性解释(来自官网https://shenyu.apache.org/zh/docs/user-guide/property-config/gateway-property-config):
如此 Admin(作为Server) 和 Gateway(作为Client)建立连接
2. 第二部分:Admin 如何通过 Websocket发送要同步的数据?
以创建 Selector 为例,解释在 Admin 创建的 Selector 是如何同步到 Gateway 的。
2.1 在 Divide 插件里创建一个新的 Selector
第1步:
第2步:
2.2 在新增 Selector 点击 Sure 后
请求会发到 shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/SelectorController.java
的 #createSelector 方法中:
SelectorController.java
2.3 进入104 行的 #createOrUpdate,也就是 SelectorService 接口的一个默认实现:
SelectorService.java
2.4 继续进入该接口的另一个方法 #create 中,来到 SelectorServiceImpl:
SelectorServiceImpl.java
这里我加的第 198 行注释看不懂没关系,接下来会解释这些注释。
2.5 先是 194 行划红线部分:
SelectorServiceImpl.java
2.5.1 Mybatis mapper
一个 Mybatis 的 mapper 配置,路径在 shenyu-admin/src/main/resources/mappers/selector-sqlmap.xml
selector-sqlmap.xml
<insert id="insertSelective" parameterType="org.apache.shenyu.admin.model.entity.SelectorDO">INSERT INTO selector<trim prefix="(" suffix=")" suffixOverrides=",">id,<if test="dateCreated != null">date_created,</if><if test="dateUpdated != null">date_updated,</if><if test="pluginId != null">plugin_id,</if><if test="name != null">name,</if><if test="matchMode != null">match_mode,</if><if test="type != null">type,</if><if test="sort != null">sort,</if><if test="enabled != null">enabled,</if><if test="loged != null">loged,</if><if test="continued != null">continued,</if><if test="matchRestful != null">match_restful,</if><if test="handle != null">handle,</if></trim><trim prefix="values (" suffix=")" suffixOverrides=",">#{id, jdbcType=VARCHAR},<if test="dateCreated != null">#{dateCreated, jdbcType=TIMESTAMP},</if><if test="dateUpdated != null">#{dateUpdated, jdbcType=TIMESTAMP},</if><if test="pluginId != null">#{pluginId, jdbcType=VARCHAR},</if><if test="name != null">#{name, jdbcType=VARCHAR},</if><if test="matchMode != null">#{matchMode, jdbcType=INTEGER},</if><if test="type != null">#{type, jdbcType=INTEGER},</if><if test="sort != null">#{sort, jdbcType=INTEGER},</if><if test="enabled != null">#{enabled, jdbcType=TINYINT},</if><if test="loged != null">#{loged, jdbcType=TINYINT},</if><if test="continued != null">#{continued, jdbcType=TINYINT},</if><if test="matchRestful != null">#{matchRestful, jdbcType=TINYINT},</if><if test="handle != null">#{handle, jdbcType=VARCHAR},</if></trim></insert>
可以看到是哪个属性不为空就写入数据库。
2.6 进入 197 行的SelectorServiceImpl 的一个实例方法 #createCondition 方法
SelectorServiceImpl.java
同样还是SelectorServiceImpl.java
这里 selectorConditionMapper
和上面的 selectorMapper
类似,都是将属性选择性地插入数据库。
2.7 201 行的 #publishEvent
SelectorServiceImpl.java
2.7.1 进入该服务的 #publishEvent 后,方法如下:
/*** Implementation of the {@link org.apache.shenyu.admin.service.SelectorService}.* Maintain {@link SelectorDO} and {@link SelectorConditionDO} related data.*/
@Service
public class SelectorServiceImpl implements SelectorService {// ...// Spring 框架的一个事件发布机制,事件发布者private final ApplicationEventPublisher eventPublisher;private final SelectorEventPublisher selectorEventPublisher;// ...private void publishEvent(final SelectorDO selectorDO, final List<SelectorConditionDTO> selectorConditions, final List<SelectorConditionDO> beforeSelectorCondition) {PluginDO pluginDO = pluginMapper.selectById(selectorDO.getPluginId());List<ConditionData> conditionDataList = ListUtil.map(selectorConditions, ConditionTransfer.INSTANCE::mapToSelectorDTO);List<ConditionData> beforeConditionDataList = ListUtil.map(beforeSelectorCondition, ConditionTransfer.INSTANCE::mapToSelectorDO);// build selector data.SelectorData selectorData = SelectorDO.transFrom(selectorDO, pluginDO.getName(), conditionDataList, beforeConditionDataList);// publish change event.// 将数据变动 DataChangedEvent 对象发布出去eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,Collections.singletonList(selectorData)));}
}
小 tips:可以点击 publisher.publishEvent 旁边的带耳机的小图标,会跳转到监听这个事件的类中,如下图:
2.7.2 跳转到 DataChangedEventDispatcher,是这个分发器来监听 DatachangedEvent 的
DataChangedEventDispatcher.java
/*** Event forwarders, which forward the changed events to each ConfigEventListener.*/
@Component
public class DataChangedEventDispatcher implements ApplicationListener<DataChangedEvent>, InitializingBean {// ... @Override@SuppressWarnings("unchecked")public void onApplicationEvent(final DataChangedEvent event) {for (DataChangedListener listener : listeners) {switch (event.getGroupKey()) {// ...case SELECTOR:listener.onSelectorChanged((List<SelectorData>) event.getSource(), event.getEventType());break;// ...default:throw new IllegalStateException("Unexpected value: " + event.getGroupKey());}}}
}
2.7.3 追踪 listener.onSelectorChanged() 方法,找到一个实现类 WebsocketDataChangedListener。
WebsocketDataChangedListener.java
public class WebsocketDataChangedListener implements DataChangedListener {// ...@Overridepublic void onSelectorChanged(final List<SelectorData> selectorDataList, final DataEventTypeEnum eventType) {WebsocketData<SelectorData> websocketData =new WebsocketData<>(ConfigGroupEnum.SELECTOR.name(), eventType.name(), selectorDataList);// 由套接字收集器发送要同步的数据WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);}
2.7.4 继续追踪 WebsocketCollector#send 方法,
WebsocketCollector.java
@ServerEndpoint(value = "/websocket", configurator = WebsocketConfigurator.class)
public class WebsocketCollector {// ...public static void send(final String message, final DataEventTypeEnum type) {if (StringUtils.isBlank(message)) {return;}// 如果是 MYSELF,是全量数据,从 ThreadLocal 中拿到 session,主动发消息 pushif (DataEventTypeEnum.MYSELF == type) {Session session = (Session) ThreadLocalUtils.get(SESSION_KEY);if (Objects.nonNull(session)) {sendMessageBySession(session, message);}} else {// 否则向所有 session 发要同步的数据SESSION_SET.forEach(session -> sendMessageBySession(session, message));}}
}
通过 Websocket 发送要同步的数据,这里和官方介绍的是用 Websocket 作为默认的同步方法一致。
2.8 205 行的 SelectorEventPublisher#onCreated方法
SelectorServiceImpl.java
如果插入 selectorDO 进数据库成功,则发布出去这个创建成功的消息
SelectorEventPublisher.java
@Component
public class SelectorEventPublisher implements AdminDataModelChangedEventPublisher<SelectorDO> {// ...private final ApplicationEventPublisher publisher;@Overridepublic void onCreated(final SelectorDO selector) {// 发布“选择器创建事件”publish(new SelectorCreatedEvent(selector, SessionUtil.visitorName()));}@Overridepublic void publish(final AdminDataModelChangedEvent event) {// 由 Spring 框架发布 AdminDataModelChangedEvent 事件publisher.publishEvent(event);}
}
AdminDataModelChangedEvent 由 RecordLogDataChangedAdapterListener 监听
现在我才知道的小 tips:可以点击 publisher.publishEvent 旁边的带耳机的小图标,会跳转到监听这个事件的类中,如下图:
@Component
public class RecordLogDataChangedAdapterListener implements DataChangedListener, ApplicationListener<AdminDataModelChangedEvent> {private final OperationRecordLogMapper logMapper;// ...@Override// 产生 OperationRecordLog 日志,并插入数据库,标记 event 已消费。public void onApplicationEvent(final AdminDataModelChangedEvent event) {// 判断 event 是否已消费if (event.isConsumed()) {return;}final OperationRecordLog log = new OperationRecordLog();log.setColor(event.getType().getColor());log.setContext(event.buildContext());log.setOperationTime(event.getDate());log.setOperationType(event.getType().getTypeName());log.setOperator(event.getOperator());logMapper.insert(log);event.consumed();}
}
一张图总结
相关文章:

Apache 神禹(shenyu)源码阅读(一)——Admin向Gateway的数据同步(Admin端)
源码版本:2.6.1 单机源码启动项目 启动教程:社区新人开发者启动及开发防踩坑指南 源码阅读 前言 开了个新坑,也是第一次阅读大型项目源码,写文章记录。 在写文章前,已经跑了 Divide 插件体验了一下(体…...

Prompt Tuning:深度解读一种新的微调范式
阅读该博客,您将系统地掌握如下知识点: 什么是预训练语言模型? 什么是prompt?为什么要引入prompt?相比传统fine-tuning有什么优势? 自20年底开始,prompt的发展历程,哪些经典的代表…...

Unity3d Shader篇(五)— Phong片元高光反射着色器
文章目录 前言一、Phong片元高光反射着色器是什么?1. Phong片元高光反射着色器的工作原理2. Phong片元高光反射着色器的优缺点优点缺点 二、使用步骤1. Shader 属性定义2. SubShader 设置3. 渲染 Pass4. 定义结构体和顶点着色器函数5. 片元着色器函数 三、效果四、总…...

sql求解连续两个以上的空座位
Q:查找电影院所有连续可用的座位。 返回按 seat_id 升序排序 的结果表。 测试用例的生成使得两个以上的座位连续可用。 结果表格式如下所示。 A:我们首先找出所有的空座位:1,3,4,5 按照seat_id排序(上面已…...

【链表】-Lc146-实现LRU(双向循环链表)
写在前面 最近想复习一下数据结构与算法相关的内容,找一些题来做一做。如有更好思路,欢迎指正。 目录 写在前面一、场景描述二、具体步骤1.环境说明2.双向循环链表3.代码 写在后面 一、场景描述 运用你所掌握的数据结构,设计和实现一个 LRU (…...

MYSQL学习笔记:MYSQL存储引擎
MYSQL学习笔记:MYSQL存储引擎 MYSQL是插件式的存储引擎 存储引擎影响数据的存储方式 存储引擎是用来干什么的,innodb和myisam的主要区别–数据存储方式----索引 mysql> show engines; ----------------------------------------------------------…...

Bitcoin Bridge:治愈还是诅咒?
1. 引言 主要参考: Bitcoin Bridges: Cure or Curse? 2. 为何需关注Bitcoin bridge? 当前的Bitcoin bridge,其所谓bridge,实际是deposit: 在其它链上的BTC情况为: 尽管当前约有43.7万枚BTC在其它链上…...

Netty应用(七) 之 Handler Netty服务端编程总结
目录 15.Handler 15.1 handler的分类 15.1.1 按照方向划分 15.1.2 handler的结构 15.2 输入方向ChannelInboundHandlerAdapter 15.2.1 输出方向Handler的顺序 15.2.2 多个输入方向Handler之间的数据传递 15.2.2.1 handler消失了 15.2.2.2 手动编写netty提供的new Strin…...

LeetCode、1268. 搜索推荐系统【中等,前缀树+优先队列、排序+前缀匹配】
文章目录 前言LeetCode、1268. 搜索推荐系统【中等,前缀树优先队列、排序前缀匹配】题目类型及分类思路API调用(排序前缀匹配)前缀树优先队列 资料获取 前言 博主介绍:✌目前全网粉丝2W,csdn博客专家、Java领域优质创…...
计算机视觉基础:矩阵运算
矩阵及其表示方式 一个矩阵是由行(row)和列(column)组成的一个矩形数组,通常包含数字。我们可以用大写字母(如 A、B)来表示一个矩阵。例如,矩阵 A 可能看起来像这样: A [ a11 a12 a13 ][ a21 a22 a23 ][ a31 a32 a3…...
Gateway中Spring Security6统一处理CORS
文章目录 一、起因二、解决方法 一、起因 使用了gateway微服务作为整体的网关,并且整合了Spring Security6;还有一个system微服务,作为被请求的资源,当浏览器向gateway发送请求,请求system资源时,遇到CORS…...
突破编程_C++_基础教程(输入、输出与文件)
1 流和缓冲区 C中,流( stream )和缓冲区( buffer )是两个紧密相关的概念,它们在处理输入和输出时起着重要的作用。 流( Stream ) 流是一种抽象的概念,用于表示数据的流动…...
UE的 HUD 类中的必备方法和属性
在屏幕上绘制的方法 1. DrawText() DrawText() 方法允许开发者在屏幕上渲染文本。参数包括文本内容、位置、颜色、字体、缩放等。 void DrawText(const FString& Text, const FLinearColor& TextColor, float ScreenX, float ScreenY, UFont* Font, float Scale 1.…...

单片机的认识
单片机的定义 先简单理解为: 在一片集成电路芯片上集成了微处理器(CPU )存储器(ROM和RAM)、I/O 接口电路,构成单芯片微型计算机,即为单片机。 把组成微型计算机的控制器、运算器、存储器、输…...
转发:udig安装 用来为geoserver上shp地图配置显示样式 颜色
下载udig,解压缩 这东东是基于eclipse的,需要Java JRE 把 JDK 1.8 里面的jre目录拷贝到 udig目录下面 udig下载、安装及汉化,简单生成geoserver图层样式sld-CSDN博客...

Linux--常用命令(详解)
详细目录 一、终端命令格式二、显示文件列表命令-ls2.1作用2.2格式2.3 ls常用选项2.3.1 ls -a2.3.2 ls -l(等价于 ll)2.3.2 ls -h 三、相对路径与绝对路径3.1绝对路径3.2相对路径 四、目录操作命令 -cd4.1作用4.2格式4.3案例4.3.1 cd -: 返回上一次所在目录4.3.2 cd…...
SouthLeetCode-打卡24年02月第1周
SouthLeetCode-打卡24年02月第1周 // Date : 2024/02/01 ~ 2024/02/04 034.合并两个有序链表 (1) 题目描述 034#LeetCode.21.#北岸计划2024/02/01 将两个升序链表合并为一个新的 升序 链表并返回。 新链表是通过拼接给定的两个链表的所有节点组成的。 (2) 题解代码 cla…...
vscode的cmake工具小三角符号旁边没有目标的解决方法
vscode里面写了个项目,找了半天没办法用cmake调试,最后发现是cmake里面的set(CMAKE_BUILD_TYPE Release)导致的,都是release模式了当然不能调试了;改成Debug就行了 参考:https://stackoverflow.com/questions/7549672…...

Servlet JSP-Eclipse安装配置Maven插件
Maven 是一款比较常用的 Java 开发拓展包,它相当于一个全自动 jar 包管理器,会导入用户开发时需要使用的相应 jar 包。使用 Maven 开发 Java 程序,可以极大提升开发者的开发效率。下面我就跟大家介绍一下如何在 Eclipse 里安装和配置 Maven 插…...
os模块
os 模块是 Python 中用于与操作系统进行交互的标准库之一。它提供了许多函数来执行文件和目录操作,管理进程以及与操作系统交互的其他功能。 下面是一些 os 模块中常用的函数和功能: 文件和目录操作: os.getcwd(): 返回当前工作目录的路径。…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...