用 Docker,Spring Boot/Cloud 和 Axon CQRS/ES(事件溯源)来构建微服务

mush1834 7年前
   <p>软件架构变化的步伐在过去几年快速演进。新的实践,如 DevOps,微服务和容器化已经成为热点话题也被逐渐广泛采用。在这篇文章中,作者会介绍一个自己实践的微服务项目,包含了两个在架构层面上比较突出的点:命令和查询职责分离(CQRS)与容器化。</p>    <p>在第一部分,作者会演示如何轻松用容器分发和运行一个多服务的微服务应用。</p>    <p>为了做到这一点,我使用 Docker 创建了一套包含所有运行演示所需的微服务容器集群。在写本文的时候,Demo 包含了七个微服务:-</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/2bbb93f3c09437abdd71908c48e22ce9.png"></p>    <p>这个演示的源代码在 <a href="/misc/goto?guid=4959746388308109209" rel="nofollow,noindex">Github</a> .该项目同时演示了如何实施和集成多个’云原生(Java)应用’需要的特性,包括:</p>    <ul>     <li>Microservices with Java and Spring Boot;</li>     <li>Build, Ship and Run anywhere using Docker containers;</li>     <li>Command and Query Responsibility Separation (CQRS) and Event Sourcing (ES) using the Axon Framework v2, MongoDB and RabbitMQ;</li>     <li>Centralised configuration(统一配置管理), service registration(服务注册) and API Gateway(API网关) using Spring Cloud;</li>    </ul>    <h2>工作原理</h2>    <p>这里介绍的微服务示例项目围绕一个虚构的‘产品’主数据应用,它会和大多数零售或制造企业得的产品应用相关。使用简单的RESTful服务API,产品可以被添加,存储,搜索和获取。当变化发生时,通知会通过消息的方式发送给相关服务。</p>    <p>产品数据应用使用 CQRS 构建。在 CQRS 命令中如 ADD  在物理上与查询命令例如  VIEW(其中id = 1) 分离。事实上,在这个特殊的例子中,产品领域的代码库被分成两个独立的部分 – 命令侧微服务和查询侧微服务。</p>    <p>就像通常的 12-Factor 应用一样,每个微服务都有自己独立的职责,独立的数据存储,并且可以独立的进行部署和扩展,这是 CQRS 和微服务最直观的字面解释。CQRS 或者微服务并非一定要如此实现,在这里我选择将读写逻辑关注点分离只是为了更好的示范。</p>    <p>整体的逻辑结构如下图:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/df83c7b037ed896dfe98d90097c17761.jpg"></p>    <p>命令侧和查询侧的微服务都是采用 Spring Boot 框架搭建的。命令和查询部分之间的所有通信都是单纯通过 event-driven(事件驱动) 。事件在微服务组件之间的传递采用 RabbitMQ 消息。消息传递提供了以松散耦合的方式在进程,微服务,传统系统和其他部分之间传递事件的可扩展手段。</p>    <p>请注意,任何服务都不会与其他服务共享数据库。这是非常重要的,因为它使得每个服务具有高度的自主性,这反过来保证了个体服务能够独立于系统中的其他服务进行扩展。有关 CQRS 架构的更多信息,请参阅上面幻灯片中 Slideshare 上的 CQRS 微服务。</p>    <p>CQRS 架构模式中存在的高度自主和隔离给我们带来了一个有趣的问题————我们应该如何分配和运行如此松散耦合的组件?在我看来,对于这个问题容器化提供了最好的机制,并且随着 Docker 被越来越广泛的应用,它的格式已经成为容器镜像的标准,并且那些最流行的云平台也提供了直接的支持,再加上它确实非常方便使用,因此必定有所帮助。</p>    <h2>命令侧微服务</h2>    <p>所谓命令就是“改变状态的动作”。命令侧微服务包含所有逻辑部分和业务规则。命令用于添加新内容或更改其状态。在特定内容上执行这些命令将导致生成“事件”,这些事件由 Axon 框架持久保存到 MongoDB 中,并通过 RabbitMQ 消息传播到其他进程(由业务决定可能更多的进程)。</p>    <p>在事件溯源中,事件是系统唯一的状态记录,系统使用它们来描述和重建任何实体的当前状态(依次重放它过去的每个事件,直到所有事件被重新应用一遍)。这听起来很慢,但实际上因为事件很简单因此执行的很快,并且可以使用 snapshots(快照) 进一步实现回滚。</p>    <p>在域驱动设计(DDD)中,实体通常被称为 Aggregate(聚合) 或  AggregateRoot(聚合根) 。</p>    <h2>查询侧微服务</h2>    <p>查询侧的微服务充当着一个事件监听者的角色。监听着被命令方提交的 事件 并将其通过最直观的方式表达出来(比如一个表格视图)。</p>    <p>在本例子中,查询侧只是简单的构建并且维护一个产品状态的 可视化 或 投影 (依据它们的 ID 和描述与是否在售的状态)。查询侧可以横向拓展多实例,RabbitMQ 的消息队列也可被设置为持久化的,因此如果查询侧服务不可用的时候,RabbitMQ 可以保存临时存储消息。</p>    <p>命令侧和查询侧都提供了通过 REST API 查询的能力。</p>    <p>更多信息,可以查看 Axon 的文档,其中描述了 Axon 如何使用 CQRS,事件采集等一系列的细节与如何配置和用法。</p>    <h2>运行演示</h2>    <p>运行示例代码非常简单,但是首先需要在你的机器上安装以下软件。作为参考,我使用 Ubuntu 16.04 作为操作系统,但同时我也在新的 Docker for Windows Beta 版本上成功测试了该应用程序。</p>    <ul>     <li>Docker (版本:v1.8.2)</li>     <li>Docker-compose (版本: v1.7.1)</li>    </ul>    <p>如果以上两个软件都已存在,那么你可以通过下面列出的步骤运行示例。</p>    <p>如果您已经安装有 MongoDB 或 RabbitMQ,请先关闭这些服务,然后再按照以下步骤继续,以避免端口冲突。</p>    <h3>步骤1:获取 Docker-compose 配置文件</h3>    <p>新建一个空文件夹,在终端执行下列命令以下载此示例的最新 docker-compose 配置文件。</p>    <p>$ wget https://raw.githubusercontent.com/benwilcock/microservice-sampler/master/docker-compose.yml</p>    <p>请不要尝试更改文件的名称———Docker 在默认情况下,会去寻找名为“docker-compose.yml”的文件。 如果你确实需要更改文件名称,请在以下步骤中使用 -f 转换。</p>    <h3>步骤2:启动微服务</h3>    <p>因为我们使用 docker-compose 文件,所以启动微服务只需要简单的执行下面的命令。</p>    <p>$ docker-compose up</p>    <p>随着 Docker 镜像的下载和运行,你将在终端窗口中看到大量的下载和日志输出。</p>    <p>总共需要下载七个 docker 镜像,它们是 mongodb,rabbitmq,config-service,discovery-service,gateway-service,product-cmd-side和 product-qry-side。</p>    <p>如果需要查看哪些 docker 容器正在运行(并获取其本地 IP 地址),请打开一个单独的终端窗口并执行以下命令:</p>    <p>$ docker ps</p>    <p>一旦容器启动并运行(一开始可能需要一点时间),你立即可以通过浏览器进行查看。你应该能够访问:</p>    <ol>     <li>The Rabbit Management Console on port 15672</li>     <li>The Eureka Discovery Server Console on port 8761</li>     <li>The Configuration Server mappings on port 8888</li>     <li>The API Gateway Routes on port ‘8080’</li>    </ol>    <h3>步骤3:使用产品</h3>    <p>到目前为止都很顺利,现在我们要测试新增的产品。</p>    <p>在本次手动的系统测试中,我们将向命令侧 REST API 发出一个 add  命令。</p>    <p>当命令侧处理该指令时,产生了一个 ‘ProductAddedEvent’,存储在 MongoDB 中,并通过 RabbitMQ 转发给查询端。然后查询侧处理此事件,并为产品的物化视图(本简单示例中实际上是一个 H2 内存数据库)添加一条记录。一旦事件被处理,我们就可以使用查询侧微服务来查找关于已经添加的新产品的信息。执行这些任务时,您可以在 docker-compose 终端窗口中观察到一些日志的输出。</p>    <p>步骤3.1:添加新产品</p>    <p>要执行这项测试,我们首先需要打开第二个终端窗口,我们可以继续输入一些 CURL 命令,同时不停止在第一个窗口中运行着的 docker 组合实例。</p>    <p>为了达到测试目的,我们将在产品目录中添加一个名为 “Everything is Awesome” 的 MP3 产品。为此,我们可以使用命令侧的 REST API,并发出 POST 请求,如下所示:</p>    <p>$ curl -X POST -v --header &quot;Content-Type: application/json&quot; --header &quot;Accept: */*&quot; &quot;http://localhost:8080/commands/products/add/01?name=Everything%20Is%20Awesome&quot;</p>    <p>如果没有可用的“CURL”,可以使用任何你喜欢的 REST API 测试工具(例如 Postman,SoapUI,RESTeasy 等)。</p>    <p>如果你使用的是 Mac 或 Windows 的 Docker 公开测试版(强烈推荐),那么当你在终端窗口中使用 docker ps 时你需要转换 ‘localhost’ 为 IP 地址。</p>    <p>你应该会看到类似于以下的响应内容:</p>    <pre>  * Trying 127.0.0.1...  * Connectedto localhost (127.0.0.1) port 8080(#0)  > POST /commands/products/add/01?name=Everything%20Is%20Awesome    HTTP/1.1  > Host: localhost:9000  > User-Agent: curl/7.47.0  > Content-Type: application/json  > Accept: */*$ http://localhost:8080/commands/products/01  < HTTP/1.1 201 Created  < Date: Thu, 02 Jun 2016 13:37:07 GMTThis  < X-Application-Context: product-command-side:9000  < Content-Length: 0  < Server: Jetty(9.2.16.v20160414)  </pre>    <p>响应码为 HTTP / 1.1 201 Created 。这意味着 MP3 产品 “Everything is Awesome” 已成功添加到命令端事件源存储库。</p>    <p>步骤3.2:查询新产品</p>    <p>现在我们可以查看刚添加的产品。为此,首先发出一个简单的 “GET” 请求。</p>    <p>$ curl http://localhost:8080/queries/products/1</p>    <p>你应该可以看到以下输出,这表明查询侧微服务具有我们新添加的 MP3 产品的记录:</p>    <pre>  {      name: "Everything Is Awesome",      saleable: false,      _links: {          self: {          href: "http://localhost:8080/queries/products/1"          },      product: {          href: "http://localhost:8080/queries/products/1"          }      }    }  </pre>    <p>搞定!如果你愿意,可以重复测试,添加更多的产品,不过小心不要尝试使用相同的 ID,否则当你发出 POST 请求时将会反馈一个错误。</p>    <p>如果你熟悉 MongoDB,你可以检查数据库以查看创建的所有事件。同样,如果你知道 RabbitMQ 管理控制台的方式,也可以看到消息在命令端和查询端微服务之间传输的过程。</p>    <h2> </h2>    <p> </p>    <p>来自:http://blog.daocloud.io/microservices-with-spring-boot-axon-cqrses-anddock/</p>    <p> </p>