learn.lianglianglee.com/文章/当我们在讨论CQRS时,我们在讨论些神马?.md.html
2022-05-11 19:04:14 +08:00

697 lines
38 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>当我们在讨论CQRS时我们在讨论些神马.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析.md.html</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析.md.html</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通.md.html</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析.md.html</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器.md.html</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点.md.html</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结.md.html</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析.md.html</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇).md.html</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI.md.html</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal.md.html</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践.md.html</a>
</li>
<li>
<a href="/文章/Java魔法类Unsafe应用解析.md.html">Java魔法类Unsafe应用解析.md.html</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记.md.html</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md.html">MySQL 主从复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log).md.html</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享.md.html</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集.md.html</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL.md.html</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?.md.html</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC.md.html</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL优化优化 select count().md.html">MySQL优化优化 select count().md.html</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁.md.html</a>
</li>
<li>
<a href="/文章/MySQL的MVCC多版本并发控制.md.html">MySQL的MVCC多版本并发控制.md.html</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践.md.html</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦.md.html</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter.md.html</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md.html">Spring MVC 原理.md.html</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘.md.html</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖.md.html</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式.md.html</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition.md.html</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门.md.html</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践.md.html</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事.md.html</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战.md.html</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用.md.html</a>
</li>
<li>
<a href="/文章/从SpringCloud开始聊微服务架构.md.html">从SpringCloud开始聊微服务架构.md.html</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft.md.html</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析.md.html</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计.md.html</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?.md.html</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践.md.html</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器.md.html</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?.md.html</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统.md.html</a>
</li>
<li>
<a href="/文章/异步网络模型.md.html">异步网络模型.md.html</a>
</li>
<li>
<a class="current-tab" href="/文章/当我们在讨论CQRS时我们在讨论些神马.md.html">当我们在讨论CQRS时我们在讨论些神马.md.html</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制.md.html</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答.md.html</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM).md.html</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践.md.html</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html">服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache.md.html</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现.md.html</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge.md.html</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索.md.html</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?.md.html</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?.md.html</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践.md.html</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践.md.html</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现.md.html</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考.md.html</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题.md.html</a>
</li>
<li>
<a href="/文章/进阶Dockerfile 高阶使用指南及镜像优化.md.html">进阶Dockerfile 高阶使用指南及镜像优化.md.html</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构.md.html</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>当我们在讨论CQRS时我们在讨论些神马</h1>
<p>当我写下这个标题的时候我就有些后悔了题目有点大不太好控制。但我还是打算尝试一下通过这篇内容来说清楚CQRS模式以及和这个模式关联的其它东西。希望我能说得清楚你能看得明白如果觉得不错右下角点个推荐</p>
<p>先从CQRS说起CQRS的全称是Command Query Responsibility Segregation翻译成中文叫作命令查询职责分离。从字面上就能看出这个模式要求开发者按照方法的职责是命令还是查询进行分离什么是命令什么是查询我们来继续往下看。</p>
<h2>Query &amp; Command</h2>
<p><strong>什么是命令?什么是查询?</strong></p>
<ul>
<li>命令(Command):不返回任何结果(void),但会改变对象的状态。</li>
<li>查询(Query):返回结果,但是不会改变对象的状态,对系统没有副作用。</li>
</ul>
<p><strong>对象的状态是什么意思呢?</strong></p>
<p>对象的状态我们可以理解成它的属性例如我们定义一个Person类定义如下</p>
<pre><code>public class Person {
public string Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public void Say(string word) {
Console.WriteLine($&quot;{Name} Say: {word}&quot;);
}
}
</code></pre>
<p>在Person类中</p>
<ul>
<li>Name、Age属性状态</li>
<li>Say(string): 方法(行为)</li>
</ul>
<p>再回到本小节讨论的内容是不是就很好理解了呢当我定义一个方法要改变Person实例的Name或Age的时候这个方法就属于Command如果定一个方法只查询Person实例信息的时候这个方法就属于Query。当我们按照职责将Command和Query进行分离的时候你就在使用CQRS模式了。</p>
<p><strong>其实这就是CQRS的全部。</strong></p>
<p>有朋友可能要说了如果这就是CQRS的全部也太过于简单了吧是的大道至简</p>
<h2>读写分离</h2>
<p>当我们按照CQRS进行分离以后你是不是已经看出来这玩意儿太适合做读写分离了当我们的数据库是主从模式的时候主库负责写入、从库负责读取完全匹配Command和Query简直完美。那么我们接下来就说一下读写分离。</p>
<p>现在主流的数据库都支持主从模式,主从模式的好处是方便我做故障迁移,当主库宕机的时候,可以快速的启用从库,从而减小系统不可用时间。</p>
<p>当我们在使用数据库主从模式的时候如果应用程序不做读写分离你会发现从库基本上没用主库每天忙的要死既要负责写入又要负责查询遇见访问量大的时候CPU飙升是常有的事。然而从库就太闲了除了接收主库的变更记录做数据同步再没有别的事情可做不管主库压力多大从库的CPU一直跟心电图似的0-1-0-1...当我们读写分离以后主库负责写入从库负责读取代码要怎么改呢我们只需要定义两个Repository就可以了</p>
<pre><code>public interface IWritablePersonRepository {
//写入数据的方法
}
public interface IReadonlyPersonRepository {
//读取数据的方法
}
</code></pre>
<p>在IWritablePersonRepository中使用主库的连接IReadonlyPersonRepository中使用从库的连接。然后在Command里面使用IWritablePersonRepository 在Query里面使用IReadonlyPersonRepository这样就在应用层实现了读写分离。</p>
<h2>CRUD和EventSourcing</h2>
<p>说到CQRS不可避免的要说到这两个数据操作模型。为什么要说数据操作模型呢因为数据操作严重影响性能而我们分离的一个重要目的就是要提高性能。</p>
<h3>CRUD</h3>
<p>CRUDCreate、Read、Update、Delete<strong>面向数据</strong>它将对数据的操作分为创建、更新、删除和读取四类这四个操作可以对应我们SQL语句中的insert、select、update、delete非常直观明了它的存在就是操作数据的。</p>
<p>因为存在即合理我们不能片面的说CRUD是好或者坏这里只简单说一下它存在的问题</p>
<ul>
<li>并发冲突这是个大问题当A和B同时更新一行记录的时候你的事务必然报错。</li>
<li>丢失数据操作的上下文这个问题也不小对于开发者来说我们通常要知道数据是谁在什么时候做了什么更新但是CURD只存储了最终的状态对数据操作的上下文一无所知。</li>
</ul>
<p>好了更多的问题不再列举单是“并发冲突”这一个问题在高并发的环境下就不适用。既然CRUD不适用我们在构建高性能应用的时候就只能寄希望于ES了。</p>
<h3>Event Souring</h3>
<p>Event Souring翻译过来叫事件溯源。什么意思呢它把对象的创建、修改、删除等一系列的操作都当作事件<em>注意:事件和命令还有区别,后面会讲到</em>),持久化的时候只存储事件,存储事件的介质叫做<strong>EventStore</strong>当要获取一个对象的最新状态时通过EventStore检索该对象的所有Event并重新加载来获取对象的最新状态。EventStore可以是数据库、磁盘文件、MongoDB等由于Event的存储都是新增的所以不存在并发冲突的问题。</p>
<h2>Command和Event</h2>
<p>在CQRS+ES的方案中我们要面对这两个概念命令和事件。</p>
<ul>
<li>Command描述了用户的意图。</li>
<li>Event描述了对象状态的改变。</li>
</ul>
<p>我们举一个例子比如说你要更新自己的个人资料例如将Age由35修改为18那么对应的命令为</p>
<pre><code>public class PersonUpdateCommand {
public string Id { get; set; }
public int Age{ get; set; }
public PersonUpdateCommand(string id, int age){
this.Id = id;
this.Age = age;
}
}
</code></pre>
<p>PersonUpdateCommand是一个命令它描述了用户更新个人资料的意图。当程序接收到这个命令以后就需要对数据更改从而引发数据状态变化产生Event</p>
<pre><code>public class PersonAgeChangeEvent {
public string Id { get; private set; }
public int Age{ get; private set; }
public PersonAgeChangeEvent(string id, int age){
this.Id = id;
this.Age = age;
}
}
public class PersonUpdateCommandHandler {
private PersonUpdateCommand Command;
public PersonUpdateCommandHandler(PersonUpdateCommand command) {
this.Command = command;
}
public void Handle() {
var person = GetPersonById(Command.Id);
if(person.Age != Command.Age) {
//生成并发送事件
var @event = new PersonAgeChangeEvent(Command.Id, Command.Age);
EventBus.Send(@event);
}
}
}
</code></pre>
<h2>数据一致性</h2>
<p>常见的数据一致性模型有两种:强一致性和最终一致性。</p>
<ul>
<li>强一致性:在任何时刻所有的用户或者进程查询到的都是最近一次成功更新的数据。</li>
<li>最终一致性:和强一致性相对,在某一时刻用户或者进程查询到的数据可能有不同,但是最终成功更新的数据都会被所有用户或者进程查询到。</li>
</ul>
<p>说到一致性的问题我们就不得不说一下CAP定理。</p>
<h3>CAP定理</h3>
<p>1998年加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标。</p>
<ul>
<li>Consistency一致性</li>
<li>Availability可用性</li>
<li>Partition tolerance分区容错</li>
</ul>
<p>它们的第一个字母分别是 C、A、P这三个指标不可能同时做到。这个结论就叫做 CAP 定理。</p>
<p>对于分布式系统来说受CAP定理的约束最终一致性就成了唯一的选择。实现最终一致性要考虑以下问题</p>
<ul>
<li>重试策略:在分布式系统中,我们无法保证每一次操作都能被成功的执行,例如网络中断、服务器宕机等临时性的错误,都会导致操作执行失败,那么我们就要等待故障恢复后进行重试。重试的操作对于系统来说可能会造成一些副作用,例如你正在支付的时候网络中断了,这个时候你不知道是否支付成功,联网以后再次重试,可能就会造成重复扣款。如果要避免重试造成的系统危害,就要将操作设计为幂等操作。</li>
<li>
<ul>
<li><strong>幂等性</strong>:简单的说,就是一个操作执行一次和执行多次产生的结果是一样的,不会产生副作用。</li>
</ul>
</li>
<li>撤销策略:与重试策略相对应的,如果一个操作最终确定执行失败,那么我们需要撤销这个操作,将系统还原到执行该操作之前的状态。撤销操作有两种,一种是直接将对象修改为执行前的状态,这种情况将造成数据审计不一致的问题;另一种是类似于财务上的红冲操作,新增一个命令,冲掉上一个操作,从而保证数据的完整性,并能够满足数据审计的要求。</li>
</ul>
<h2>Messaging</h2>
<p>通过上面的介绍我们已经知道在一个系统中所有的改变都是基于操作和由操作产生的事件所引发的。消息可以是一个Command也可以是一个Event。当我们基于消息来实现CQRS中的命令和事件发布的时候我们的系统将会更加的灵活可扩展。</p>
<p>如果你的系统基于消息,那么我猜你离不开消息总线,我在<a href="https://www.cnblogs.com/youring2/p/10991338.html">《手撸一套纯粹的CQRS实现》</a>中写了一个基于内存的CommandBus的实现感兴趣的朋友可以去看一下CommandBus的代码定义如下</p>
<pre><code>public class CommandBus : ICommandBus
{
private readonly ICommandHandlerFactory handlerFactory;
public CommandBus(ICommandHandlerFactory handlerFactory)
{
this.handlerFactory = handlerFactory;
}
public void Send&lt;T&gt;(T command) where T : ICommand
{
var handler = handlerFactory.GetHandler&lt;T&gt;();
if (handler == null)
{
throw new Exception(&quot;未找到对应的处理程序&quot;);
}
handler.Execute(command);
}
}
</code></pre>
<p>基于内存的消息总线只能用于开发环境在生产环境下不能够满足我们分布式部署的需要这个时候就需要采用基于消息队列的方式来实现了。消息队列有很多例如Redis的订阅发布、RabbitMQ等消息总线的实现也有很多优秀的开源框架例如Rebus、Masstransit等选一个你熟悉的框架即可。</p>
<h2>数据审计</h2>
<p>数据审计是CQRS带给我们的另一个便利。由于我们存储了所有事件当我们要获取对象变更记录的时候只需要将EventStore中的记录查询出来便可以看到整个的生命周期。这种操作简直比打开了你青春期的日记本还要清晰明了。</p>
<p>当然如果你要想知道对象的操作审计日志怎么办同样的道理我们记录下所有的Command就可以了。那所有查询日志呢哈哈不要调皮了。记录的东西越多你的存储就越大如果你的存储空间允许的话当然是越详细越好的主要还是看业务需求。</p>
<p>如果我们记录了所有Command我们还可以有针对性的进行分析哪些命令使用量大、哪些命令执行时间长。。这些数据将对我们的扩容提供数据支撑。</p>
<h2>分组部署</h2>
<p>在分布式系统中Command和Query的使用比例是不一样的Command和Command之间、Query和Query之间的权重也存在差异如果单纯的将这些服务平均的部署在每一个节点上那纯粹就是瞎搞。一个比较靠谱的实践是将不同权重的Command和Query进行分组然后进行有针对性的部署。</p>
<h2>总结</h2>
<p>CQRS很简单如何用好CQRS才是关键。CQRS更像是一种思想它为我们提供了系统分离的基本思路结合ES、Messaging等模式为构建分布式高可用可扩展的系统提供了良好的理论依据。</p>
<p>园子里有很多钻研CQRS+ES的前辈本文借鉴了他们的文章和思想感谢他们的分享</p>
<p>文章中有任何不准确或错误的地方,请不吝赐教!欢迎讨论!</p>
<h2>参考文档</h2>
<ul>
<li><a href="https://www.cnblogs.com/yangecnu/p/Introduction-CQRS.html">https://www.cnblogs.com/yangecnu/p/Introduction-CQRS.html</a></li>
<li><a href="https://www.cnblogs.com/netfocus/p/4150084.html">https://www.cnblogs.com/netfocus/p/4150084.html</a></li>
<li><a href="https://www.ruanyifeng.com/blog/2018/07/cap.html">http://www.ruanyifeng.com/blog/2018/07/cap.html</a></li>
<li><a href="https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn589800(v=pandp.10)">https://docs.microsoft.com/en-us/previous-versions/msp-n-p/dn589800(v=pandp.10)</a></li>
<li><a href="https://msdn.microsoft.com/magazine/mt238399">https://msdn.microsoft.com/magazine/mt238399</a></li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/异步网络模型.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70998058d9b88b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>