针对业务量庞大、查询维度复杂的系统(如全房通的合租管理),传统的"增删改查全靠 MySQL"模式会面临巨大的性能瓶颈。为此,现代架构通常引入 CQRS(命令查询职责分离) 思想,结合 Elasticsearch (ES) 和 RabbitMQ 来实现高性能的读写分离。
如果你之前熟悉 Solr 但较少接触 ES 和 RabbitMQ,本文档将帮你快速建立概念映射,并温习它们的基础语法。
一、核心架构思想:CQRS(读写分离)
CQRS 的全称是 Command Query Responsibility Segregation(命令查询职责分离)。 简单来说,就是把系统中的**读操作(查询数据)和写操作(修改数据)**彻底拆分开,甚至使用不同的数据库来支撑。
为什么需要 CQRS?
-
写操作(命令 Command) :比如"分配房间"、"退租"。这些操作需要强一致性、事务支持(ACID)。所以必须由 MySQL 等关系型数据库来兜底。
-
读操作(查询 Query) :比如"查询某店面下、已租完、面积大于20平的房源列表"。这种多维度、高并发的组合查询,MySQL 的 B+ 树索引扛不住。所以交给 Elasticsearch (ES) 这种基于倒排索引的搜索引擎来处理。
数据怎么同步?(RabbitMQ 的角色)
MySQL 负责写,ES 负责读,那两边数据怎么保持一致? 这就是 RabbitMQ(消息队列) 的用武之地:
-
写操作完成:业务代码在 MySQL 中更新完数据后,立刻向 RabbitMQ 发送一条消息("房源 ZB0288 状态已变更")。
-
异步消费:系统中的同步服务(Consumer)监听到这条消息,从 MySQL 查出最新数据。
-
更新 ES:同步服务将拼装好的宽表大 JSON 写入 ES 索引。
优势:解耦了主业务流程,即便 ES 暂时挂了或同步很慢,用户的"退租"操作依然能在毫秒级完成,不会被卡住。
二、Elasticsearch (ES) 基础快速回顾
ES 和你熟悉的 Solr 底层都是 Lucene。你可以把 ES 当作一个"只支持 JSON 格式的、分布式的、超级快的非关系型数据库"。
1. 概念映射(MySQL vs ES vs Solr)
| MySQL | Elasticsearch | Solr | 说明 |
|---|---|---|---|
| Database / Table | Index (索引) | Collection / Core | 存储数据的逻辑集合 |
| Row | Document (文档) | Document | 一条具体的数据记录(JSON 格式) |
| Column | Field (字段) | Field | 文档中的一个属性(如 house_code) |
| Schema | Mapping (映射) | Schema.xml | 定义字段的类型(text, keyword, integer 等) |
注意:ES 7.x 之后已经废弃了 Type(表)的概念,一个 Index 就相当于一张宽表。
2. 基础语法(DSL - 领域特定语言)
ES 的交互全部基于 HTTP 和 JSON。
① 查询所有文档 (Match All) 相当于 SELECT * FROM table:
GET /qft_housing_index/_search
{
"query": {
"match_all": {}
}
}
② 精确匹配 (Term) 相当于 WHERE store_id = 1001。用于数字、布尔值或不分词的字符串(keyword):
GET /qft_housing_index/_search
{
"query": {
"term": {
"store_id": 1001
}
}
}
③ 组合查询 (Bool) 这是最常用的,相当于 WHERE (a = 1 AND b = 2) OR (c = 3):
-
must:必须匹配(AND),计算相关性得分。 -
filter:必须匹配(AND),不计算得分,有缓存,性能极高。 -
should:可选匹配(OR)。 -
must_not:必须不匹配(NOT)。
示例:查询店面为 1001 且状态为已租完的房源:
GET /qft_housing_index/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "store_id": 1001 } },
{ "term": { "rest_room_count": 0 } }
]
}
}
}
三、RabbitMQ 基础快速回顾
RabbitMQ 是一个消息代理(Message Broker),就像一个高级邮局。
1. 核心概念
-
Producer(生产者):发消息的程序(如"退租"接口)。
-
Consumer(消费者):收消息并处理的程序(如"同步 ES"的服务)。
-
Exchange(交换机):邮局的分类中心。生产者不直接把消息发给队列,而是发给交换机,交换机根据规则决定把消息投递到哪个队列。
-
Queue(队列):实际存放消息的信箱,消费者从这里取信。
-
Routing Key(路由键) :发消息时带的标签(如
house.update),交换机靠它来决定分发路径。
2. 常见的工作模式
最常用于数据同步的是 Topic(主题模式/路由模式)。
-
交换机类型为
topic。 -
队列绑定交换机时,指定自己关心的路由键规则(比如
house.*表示关心所有房源变动,bill.#表示关心所有账单及子账单变动)。
3. Java (Spring Boot) 基础语法示例
① 生产者发消息: 通常在增删改事务提交后调用:
@Autowired
private RabbitTemplate rabbitTemplate;
public void updateHouseStatus(Long housingId) {
// 1. 执行 MySQL 更新逻辑
// ...
// 2. 发送 MQ 消息通知 ES 同步
// 参数:交换机名称, 路由键, 消息内容
rabbitTemplate.convertAndSend("sync_exchange", "house.update", housingId);
}
② 消费者收消息: 监听指定的队列,拿到数据后处理(如查 MySQL 更新 ES):
@Component
public class SyncEsConsumer {
@RabbitListener(queues = "sync_house_queue")
public void processHouseUpdate(Long housingId) {
System.out.println("收到房源更新消息,准备同步 ES,房源ID:" + housingId);
// 1. 根据 housingId 从 MySQL 查出宽表数据
// 2. 将组装好的 JSON 调用 ES API 写入索引
}
}
总结
-
遇到写操作(增删改)代码时 :重点看 MyBatis/MySQL 的处理,最后找找有没有
rabbitTemplate.convertAndSend。 -
遇到消费者(
@RabbitListener)代码时:这通常是在做"擦屁股"的脏活累活,比如查数据库组装宽表推给 ES。 -
遇到读操作(列表查询)代码时 :如果看到拼装
BoolQueryBuilder的代码,那就是在查 ES。用 Solr 的思维去理解那些过滤条件即可。