---
title: 微服务架构本地尝试(六)-聚合数据
---

> 微服务架构中,每个微服务所拥有的数据对当前微服务来说是私有的,只能通过其提供的 API 进行访问.我们需要实现业务的事务在多个服务之间保持一致性,还有就是不同服务中数据的数据聚合.
>
> 最终我选择了尝试领域驱动设计和 CQRS,可以从 git 看到我对本节有不少改变=\_=,尝试一下,希望不要误人子弟,对于理论的知识我也不会在本文中写太多,我会贴我觉得不错的一些文章.

## 方案

这一节将来探究不同的微服务之间,不同服务中数据的数据聚合.我将列出几种方法.

### 字段冗余

这一种方法是在查询的表中增加一些字段存储另外一些表的数据,这种方法实现起来比较方便,但是需要改变表结构,而且如果所需要的字段比较多,这又会出现不少问题.

### 业务中合并

通过调用所需要的微服务接口得到数据,使用代码将数据合并起来,达到 join 的效果.这种方法虽然对数据库没有什么影响,而且也符合微服务的思想,但是性能损耗过大.

### 数据库 FEDERATED 引擎

MySQL 数据库能够使用 FEDERATED 引擎映射所需要的表,有点像复制但并不是的,然后可以使用 join 查询达到我们想要的效果.这个也许是一个不错的解决方案,但是不符合我们微服务的设计思想,不易于独立交付,对方微服务表结构修改之后可能会出现问题.

### 共享数据库

将需要 join 的数据表放在同一个数据库上,其余的还是作为独立的数据库.感觉优缺点和上面差不多.

### CQRS 架构

命令查询的责任分离 Command Query Responsibility Segregation (简称 CQRS)模式是一种架构体系模式，能够使改变模型的状态的命令和模型状态的查询实现分离。这是微服务跨数据库查询的一个比较流行的解决方案,具体怎么样我也不细说了,怕误人子弟

![](img/06-%E8%81%9A%E5%90%88%E6%95%B0%E6%8D%AE.assets/bqb_3.jpg)

### 事件驱动架构(EDA)

CQRS 使用了 EDA 的思想,所以感觉起来非常的相似,CQRS 的 C 可以是同步实现,也可以为异步.EDA 则是异步,也没有 Event Store 的要求.

## CQRS+DDD(领域驱动设计)

### DDD

对于领域驱动设计,我觉得不要去考虑数据库,当做数据库不存在去创建我们的模型,不然很难脱离原来的设计方式.应该是数据库来迎合我们,而不是去迎合数据库.我们的数据都是存储在内存当中,也就是内存保存到数据库.

然后对于领域模型存储到数据库中可以使用一个中间件转换,而不是将 sql 写在领域模型中.

实体和值对象:

> 引用于:[https://www.cnblogs.com/youxin/archive/2013/05/25/3099175.html](https://www.cnblogs.com/youxin/archive/2013/05/25/3099175.html)

- 实体核心是用唯一的标识符来定义，而不是通过属性来定义。即即使属性完全相同也可能是两个不同的对象。同时实体本身有状态的，实体又演进的生命周期，实体本身会体现出相关的业务行为，业务行为会实体属性或状态造成影响和改变。
- 值对象 Value Object，它用于描述领域的某个方面本身没有概念标识的对象，值对象被实例化后只是提供值或叫设计元素，我们只关心这些设计元素是什么？而不关心这些设计元素是谁。书里面谈到颜色，数字是常见的值对象。这种对象无状态，本身不产生行为，不存在生命周期演进。

### 思路

> 之前我认为是使用 CQRS 维护物化视图,实际我们所维护的物化视图就是我们的数据库(因为之前我认为物化视图只是展示给用户看的一个表,数据库中还需要在原来的表中存储一次数据),我的思想还是停留在迎合数据库上,虽然这看上去并没有什么区别.

我现在的思路是这样的(现在我们的发帖和权限两个微服务来说),使用 kafka 作为消息中间件,发布事件.

这个版本,我加入一个积分的微服务,当发送一条帖子时,增加积分.这只是一个小功能,我偷懒的将它和认证服务放在了一起.

用户改名的时候发布一条 user_update_msg 消息,然后订阅这个消息,修改用户名.(我认为如果在业务中用户名是不允许修改的话,可以将其作为值对象,如果允许修改且要随之变化的话,应该将用户作为一个实体,也需要实现这一消息,这些都是对于发帖微服务来说的)

~~然后关于发帖或者回帖时发布一个 post_msg/post_reply_msg 消息,对于这个看上去感觉是没有必要发送消息的,让系统变得更复杂,我们可以直接的写入 Q 端数据库.不过加入事件也有好处,假设当我们再加入一个积分微服务的时候,我们就不需要再改代码了,直接订阅该消息.~~

我放弃了回帖功能- -...改为积分功能,积分微服务订阅事件,进行处理

如果之前的数据迁移的话,我想的是创建数据库的时候,使用微服务 api(或者直接将数据复制过来进行操作),在代码中聚合数据,按照符合现在结构的方式插入,不知道这是不是一个可行的方法.

这一切都是一个菜鸡的尝试...如果不正确希望能够得到指教.

## 实现

### kafka

> Event Store 和 MQ 我们使用 Kafka,虽然用 Kafka 作为事件溯源不一定是好的,但是它能用啊.

依旧使用 docker 来搭建我们的环境,由于 Kafka 的基于集群的高可用特性是建基于 Zookeeper（称 zk）之上的,因此构建可用的 Kafka 镜像,是需要依赖于 zk 基础的.

```yaml
zookeeper:
  image: wurstmeister/zookeeper
  hostname: zookeeper
  container_name: micro_zookeeper
  ports:
    - 2181:2181
  networks:
    - micro

kafka_mq:
  image: wurstmeister/kafka
  hostname: kafka_mq
  container_name: micro_kafka_mq
  environment:
    - KAFKA_ADVERTISED_HOST_NAME=10.0.75.1
    - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
  ports:
    - 9092:9092
  networks:
    - micro

kafka_manager:
  image: sheepkiller/kafka-manager
  container_name: micro_kafka_manager
  depends_on:
    - zookeeper
  environment:
    - ZK_HOSTS=zookeeper
  ports:
    - 9000:9000
  networks:
    - micro
```

9000 端口是 kafka 的一个 web 管理,后来感觉不好用...可以考虑去掉,我用了一个桌面的程序,kafka tools

kafka 还有挺多概念的,这里的话我就不细说了.我在 examples 下写了一个 demo,大家可以去看看,效果如下,大概模拟的多个微服务(群组 1 和群组 2 为两个微服务)消费一条事件(不能重复消费,然后分布到不同的微服务实例上)

![](img/06-%E8%81%9A%E5%90%88%E6%95%B0%E6%8D%AE.assets/6_1.png)



### CQRS

![](img/06-%E8%81%9A%E5%90%88%E6%95%B0%E6%8D%AE.assets/cqrs.jpg)



我创建了一个 ddd 的目录,里面包括了下面这些文件夹:

- commands 命令事件以及命令处理(图中的 Command Bus 和 Command Handler)
- events 领域事件和事件处理器
- domain 领域模型(放置聚合根之类)
- repository 领域模型持久化仓库
- services 领域服务

command 由 user interface(Controller 之类的)生成然后分配到 command bus,然后由 command bus 分配给 command handler 处理,之后给 domain services 处理业务逻辑

我们将原来的代码进行大量修改,主要是之前服务注册,连接数据库什么的都一股脑写在了 main 函数里面....现在我们进行了一些分层,使用写配置的方式来启动,有些地方也可以使用 IoC 来进行解耦

现在启动方式变成了这样,docker 和 docker-compose 也进行了修改,其中有对 postgres 数据库进行了一些修改(主要是因为 windows10 下的 docker 有个 bug,不能持久化数据)

这里因为 kafka 的原因...启动之后微服务可能会出现 kafka 连接不成功的问题,因为 kafka 容器启动之后不一定端口那些服务也启动了...所以可能需要手动启动一下微服务,我使用的 vscode

![](img/06-%E8%81%9A%E5%90%88%E6%95%B0%E6%8D%AE.assets/6_2.png)

```shell
go run auth #启动auth微服务
go run post #启动post微服务
```

![](img/06-%E8%81%9A%E5%90%88%E6%95%B0%E6%8D%AE.assets/6_3.png)

效果暂时就是现在这样- -...

下一节开始学习分布式事务

## 参考

- [https://www.cnblogs.com/yangecnu/p/Introduction-CQRS.html](https://www.cnblogs.com/yangecnu/p/Introduction-CQRS.html)
- [https://juejin.im/entry/58a15a4e0ce4630056440166](https://juejin.im/entry/58a15a4e0ce4630056440166)
- [https://www.jianshu.com/p/d4ca2133875c](https://www.jianshu.com/p/d4ca2133875c)
- [https://www.cnblogs.com/daoqidelv/p/7499662.html](https://www.cnblogs.com/daoqidelv/p/7499662.html)
- [https://www.cnblogs.com/KendoCross/p/9480955.html](https://www.cnblogs.com/KendoCross/p/9480955.html)
- [https://www.cnblogs.com/netfocus/archive/2012/02/12/2347938.html](https://www.cnblogs.com/netfocus/archive/2012/02/12/2347938.html)
