# 13. 软件架构设计中的常见问题


哪些常见的软件设计问题

<!-- more -->

## 1. 消息队列
### 1.1 消息系统的实现方式
消息系统采用发布/订阅模式，为了区分不同的消息系统，提出以下两个问题对区分很有帮助:
1. 如果生产者发送消息的速度比消费者能处理的快，会发生什么？
    - 一般有三种选择: 系统丢弃消息；将消息缓存在队列中；激活背压(流量控制，即阻止生产者发送消息)
    - 如果消息被缓存在队列中，那么队列增长时会发生什么非常重要
    - 如果内存无法容纳所有队列，系统是否会崩溃，还是消息会被写入磁盘
    - 如果消息会落盘，又会如何影响消息传递系统的性能
2. 如果节点崩溃或者暂时离线，是否会有消息丢失?
    - 持久化需要写入磁盘或者结合复制方案，这些都是有成本的
    - 如果能够接受消息丢失，那么同样的硬件上可以获得更高的吞吐量和更低的延迟

消息传递有如下几种方式:
1. 生产者与消费者之间直接消息传递
    - 这种方式通常要求应用程序意识消息丢失的可能性，它们只支持有限的容错
    - 通常还假定生产者和消费者同时在线。诸如 RPC 和 HTTP 请求，webhooks 都是这种消息传递方式
2. 消息代理
    - 消息代理本质上是一种针对**处理消息而优化的数据库**
    - 将数据集中在代理上，可以更容易的适应不断变化的客户端
    - 持久性问题被转移到代理。消息写入代理的结果也通常导致**消费以异步方式工作**
    - 为了确保消息不会丢失，消息代理使用**确认**: 客户端必须在处理完消息后显示的告诉代理，以便代理可以将其从队列中移除
    - 但是消息的确认/重传会导致消息的重复和乱序

### 1.2 pull 和 push 的对比
消息系统的消息拉取有 pull 和 push 两种模式:
1. pull: 消费者周期性拉取消息
2. push: 服务端向客户端推送消息

它们之前的取舍可以从以下几个方面去考虑
1. 消息消费的及时性
2. 负载控制

对于 push，消息到达服务器后，就会被推送值消费者，实时性好。消费的消费速率由服务端把控，有可能会出现服务端发送过快压垮消费者客户端的情况。

对于 pull 由消费者定期轮询拉取消息，消费的速率由客户端把控，轮询总是存在周期间隔，因而对实时性有一定影响。

CMQ 提供了长轮询的优化方法，用以平衡 Pull/Push 模型各自的缺点。基本方式是：消费者如果尝试拉取失败，不是直接 return，而是把连接挂在那里 wait，服务端如果有新的消息到来，把连接拉起，返回最新消息。

因此 pull/push 的选择，本质上是对实时性和负载控制的一种均衡。

## 2. 数据库对比
### 2.1 如何对比两个数据库

## 3. RabbitMQ 与 Kafka

之所以没有选择 Kafka 是因为如下几个原因:
1. `max.poll.interval.ms` 需要预估每批数据的消费时间，否则 Kafka 会将消费者视为失败，重新再均衡

AMQP 类型的消息代理更适合如下场景:
- 消息处理的代价很高，希望在逐个消息的基础上并行处理
- 而且消息排序又不那么重要的情况下，并且需要在他们处理后返回并在此读取旧消息时 更可取

相反，基于日志的消息系统更适用于:
- 在消息吞吐量高，每个消息处理速度快
- 消息顺序有很重要
- 需要读取已经确认的旧消息
