惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
G
Google Developers Blog
J
Java Code Geeks
The GitHub Blog
The GitHub Blog
F
Full Disclosure
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Vercel News
Vercel News
酷 壳 – CoolShell
酷 壳 – CoolShell
Recent Announcements
Recent Announcements
Help Net Security
Help Net Security
The Hacker News
The Hacker News
IT之家
IT之家
Y
Y Combinator Blog
Martin Fowler
Martin Fowler
L
Lohrmann on Cybersecurity
C
CERT Recently Published Vulnerability Notes
V
Visual Studio Blog
博客园 - 聂微东
Hacker News: Ask HN
Hacker News: Ask HN
H
Hacker News: Front Page
Know Your Adversary
Know Your Adversary
Security Latest
Security Latest
Security Archives - TechRepublic
Security Archives - TechRepublic
Simon Willison's Weblog
Simon Willison's Weblog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Troy Hunt's Blog
Last Week in AI
Last Week in AI
Schneier on Security
Schneier on Security
N
News and Events Feed by Topic
博客园 - 【当耐特】
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
Blog — PlanetScale
Blog — PlanetScale
博客园_首页
Google DeepMind News
Google DeepMind News
Cloudbric
Cloudbric
N
News | PayPal Newsroom
A
About on SuperTechFans
S
Schneier on Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hugging Face - Blog
Hugging Face - Blog
M
MIT News - Artificial intelligence
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
雷峰网
雷峰网
T
The Exploit Database - CXSecurity.com
罗磊的独立博客
K
Kaspersky official blog
The Cloudflare Blog
I
Intezer

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角 .NET Core 原生 DI 扩展之基于名称的注入实现 - 元视角
利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角
飞鸿踏雪 · 2020-07-31 · via 元视角

终于到这个系列的最后一篇,在前两篇博客中,我们分别了介绍了Binlog的概念和事件总线(EventBus)的实现,在完成前面这将近好几千字的铺垫以后,我们终于可以进入正题,即通过 EventBus 发布 Binlog,再通过编写对应的 EventHandler 来订阅这些 Binlog,这样就实现了我们“最初的梦想”。坦白说,这个过程实在有一点漫长,庆幸的是,它终于还是来了。

Binlog 读取与解析

首先,我们通过 Python-Mysql-Replication 这个项目来读取 Binlog,直接通过pip install mysql-replication安装即可。接下来,我们编写一个简单的脚本文件,这再次印证那句名言——人生苦短,我用 Python:

def readBinLog():
    stream = BinLogStreamReader(
        # 填写IP、账号、密码即可
        connection_settings = {
            'host': '',
            'port': 3306, 
            'user': '', 
            'passwd': ''
        },
        # 每台服务器唯一
        server_id = 3, 
        # 主库Binlog读写完毕时是否阻塞连接
        blocking = True, 
        # 筛选指定的表
        only_tables = ['order_info', 'log_info'], 
        # 筛选指定的事件
        only_events = [DeleteRowsEvent, WriteRowsEvent, UpdateRowsEvent]) 

    for binlogevent in stream:
        for row in binlogevent.rows:
            event = {
                "schema": binlogevent.schema,
                "table": binlogevent.table,
                "log_pos": binlogevent.packet.log_pos
            }
            if isinstance(binlogevent, DeleteRowsEvent):
                event["action"] = "delete"
                event["origin"] = dict(row["values"].items())
                event["current"] = None
                event = dict(event.items())
            elif isinstance(binlogevent, UpdateRowsEvent):
                event["action"] = "update"
                event["origin"] = dict(row["before_values"].items())
                event["current"] = dict(row["after_values"].items())
                event = dict(event.items())
            elif isinstance(binlogevent, WriteRowsEvent):
                event["action"] = "insert"
                event["origin"] = None
                event["current"] = dict(row["values"].items())
                event = dict(event.items())
    stream.close()

发布 Binlog

在读取到 Binlog 以后,我们需要将其发布到 EventBus 里,为此,在.NET Core 这边提供一个 Web API 接口,只需要注入IEventBus然后调用Publish()即可:

// Post: /<controller>/Publish
[HttpPost]
[Route ("PublishBinLog")]
public Task PublishBinLog (BinLogEventModel<dynamic> eventModel) 
{
    if (eventModel.action == "insert" && eventModel.table.StartsWith ("log_"))
        _eventBus.Publish (eventModel.MapTo<WriteLogEvent> ());
    if (eventModel.action == "insert" && eventModel.table == "order_info")
        _eventBus.Publish (eventModel.MapTo<OrderInfoCreateEvent> ());
    return Task.CompletedTask;
}

相应地,我们需要在脚本中添加调用 Web API 的逻辑代码,使用我们最熟悉的requests库即可:

def sendBinLog(event):
    url = "https://localhost:44348/EventBus/PublishBinLog"
    headers = {
        'Content-Type': "application/json",
    }
    try:
        payload = json.dumps(event,cls=ComplexEncoder)
        response = session.request("POST", url, data=payload, headers=headers, verify=False)
    except Exception:
        pass

在这里,在处理 Binlog 的序列化的问题时,我们可能会遇到默认的 JSON 序列化器无法对 event 进行序列化的问题,此时,我们可以编写一个自定义的序列化器,下面是博主目前在使用的序列化器:

class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(obj, date):
            return obj.strftime('%Y-%m-%d')
        elif isinstance(obj, decimal.Decimal):
            return str(obj)
        elif isinstance(obj, bytes):
            return obj.decode('utf-8')  
        else:
            return json.JSONEncoder.default(self, obj)

订阅 Binlog

现在,为了订阅这些 Binlog,我们来编写对应的 EventHandler,这里我们定义两个 EventHandler,一个用于打印日志编号、日志内容、日志级别等信息,一个用于统计不同级别的日志的数目。代码实现如下:

//打印日志的EventHandler
public class WriteLogEventHandler : IEventHandler<WriteLogEvent> {
    private ILogger<WriteLogEventHandler> _logger;
    public WriteLogEventHandler (ILogger<WriteLogEventHandler> logger) {
        _logger = logger;
    }
    public Task Handle (WriteLogEvent @event) {
        _logger.LogInformation ($"日志编号:{@event.TRANSACTION_ID},日志级别:{@event.LOG_LEVEL},主机:{@event.HOST_NAME},IP:{@event.HOST_IP},内容:{@event.CONTENT}");
        return Task.CompletedTask;
    }
}

//分析日志的EventHandler
public class AnalyseLogEventHandler : IEventHandler<WriteLogEvent> {
    private readonly ILogger<AnalyseLogEventHandler> _logger;
    private readonly IDistributedCache _cache;
    public AnalyseLogEventHandler (ILogger<AnalyseLogEventHandler> logger, IDistributedCache cache) {
        _logger = logger;
        _cache = cache;
    }
    public Task Handle (WriteLogEvent @event) {
        var cacheCount = _cache.GetString (@event.LOG_LEVEL);
        if (string.IsNullOrEmpty (cacheCount))
            cacheCount = "1";
        else
            cacheCount = (int.Parse (cacheCount) + 1).ToString ();
        _cache.SetString (@event.LOG_LEVEL, cacheCount);;
        return Task.CompletedTask;
    }
}

注意,这里需要在Startup中注入EventHandlerEventBus以及各种必要的依赖项,你可以手动注册,或者参考下面的代码,实现扫描注册:

services.AddSingleton<IRabbitMQPersistentConnection, DefaultRabbitMQPersistentConnection> ();
services.AddSingleton<IEventBusSubscriptionManager, EventBusSubscriptionManager> (sp => new EventBusSubscriptionManager ());
services.AddSingleton<IConnectionFactory, ConnectionFactory> (sp => new ConnectionFactory () { HostName = "localhost", UserName = "guest", Password = "guest" });
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider> ();
services.AddControllers ().AddNewtonsoftJson ();
services.AddDistributedMemoryCache (options => {
    options.ExpirationScanFrequency = TimeSpan.FromMinutes (5);
    options.SizeLimit = 10;
});

//自动注册
services.AddEventBus();

//手动注册
services.AddSingleton<IEventBus, RabbitMQEventBus> (sp => {
    var eventBus = new RabbitMQEventBus (sp.GetRequiredService<IRabbitMQPersistentConnection> (), sp.GetRequiredService<IEventBusSubscriptionManager> (), sp.GetRequiredService<ILogger<RabbitMQEventBus>> (), sp, "eventbus-exchange", "eventbus-queue");
    eventBus.Subscribe<WriteLogEvent, WriteLogEventHandler>():
    eventBus.Subscribe<WriteLogEvent, AnalyseLogEventHandler>();
    return eventBus;
});

services.AddTransient<WriteLogEventHandler>();
services.AddTransient<AnalyseLogEventHandler>();

一起来看看效果,简直太完美了,我就是不想写中间表啊,这样多好!!! Python 读取 Binlog 演示 Python 读取 Binlog 演示 .NET Core 消费 Binlog演示 .NET Core 消费 Binlog演示 RabbitMQ Dashboard 演示 RabbitMQ Dashboard 演示

本文小结

通过三篇博客的篇幅,我们实现了“利用 MySQL 的 Binlog 实现数据同步与订阅”的想法。在这个过程中,我们了解了 Binlog 的相关概念,参考微软的 eShopOnContainers 项目实现了一个基于 RabbitMQ 的 EventBus,而这一切都在这篇博客中完成了最终的“拼合”,通过 Python-Mysql-Replication 实现了 Binlog 解析,而 EventBus 则作为整个事件系统的“上帝”对所有事件处理器(EventHandler)进行统一调度,最终我们不需要关心这些事件是如何被发布到 EventBus 中的,只需要知道它对应哪一个 Event 并为它编写对应的 EventHandler 即可,除了这篇博客中提到的 Binlog 以外,实际上它还可以作为系统内的“领域事件”来实现业务上的事件驱动,譬如OrderInfoCreateEvent这个事件可以表示一个订单被创建,而关心订单状态的人则可以通过 EventHandler 来实现订阅,实现类似发短信、发邮件、发微信等等的功能,或者可以让第三方的 Web API 来消费事件中携带的信息。同理,第三方的数据在流入系统时,可以先发布到消息队列中,再通过对应的 EventHandler 来进行异步处理,极大地改善系统接口的吞吐性能,而如果在这中间抽象出来一个数据交换层出来,那么就能收获更多不一样的东西,就在写这篇博客的时候,我在 Github 上的代码被收入了微软的"北极冰川火种计划",虽然数字世界远比现实世界宽广得多,可能为这个世界减少一点“无用”的数据或者代码,应该一样可以算作是环保行为吧!