缓存思想
性能优化,缓存为王,所以开始先介绍一下缓存。缓存在我们的架构设计中无处不在,常规请求是浏览器发起请求,请求服务端服务,服务端服务再查询数据库中的数据,每次读取数据都至少需要两次网络 I/O,性能会差一些,我们可以在整个流程中增加缓存来提升性能。
异步化处理
- 例如 Redis 的 bgsave,bgrewriteof 就是分别用来异步保存 RDB 跟 AOF 文件的命令,bgsave 执行后会立刻返回成功,主线程 fork 出一个线程用来将内存中数据生成快照保存到磁盘,而主线程继续执行客户端命令;
- Redis 删除 key 的方式有 del 跟 unlink 两种,对于 del 命令是同步删除,直接释放内存,当遇到大 key 时,删除操作会让 Redis 出现卡顿的问题,而 unlink 是异步删除的方式,执行后对于 key 只做不可达的标识,对于内存的回收由异步线程回收,不阻塞主线程。
- MySQL 的主从同步支持异步复制、同步复制跟半同步复制。异步复制是指主库执行完提交的事务后立刻将结果返回给客户端,并不关心从库是否已经同步了数据;同步复制是指主库执行完提交的事务,所有的从库都执行了该事务才将结果返回给客户端;半同步复制指主库执行完后,至少一个从库接收并执行了事务才返回给客户端。有多种主要是因为异步复制客户端写入性能高,但是存在丢数据的风险,在数据一致性要求不高的场景下可以采用,同步方式写入性能差,适合在数据一致性要求高的场景使用。
- 此外对于 Kafka 的生产者跟消费者都可以采用异步的方式进行发送跟消费消息,但是采用异步的方式有可能会导致出现丢消息的问题。对于异步发送消息可以采用带有回调函数的方式,当发送失败后通过回调函数进行感知,后续进行消息补偿。
- 非核心功能,异步处理是将发送消息这个动作发送消息到消息队列中,不同的场景消费消息队列中的消息进行各自逻辑的处理,这种设计保证了写入性能,也解耦不同场景业务逻辑,提高系统可维护性。
并行化处理
- 线程的并发处理(线程池),将多个串行的 I/O 操作改为并行处理,缩短接口的响应时长
- 对于 I/O 存在相互依赖的情况,可以进行多阶段分批并行化处理(CompletableFuture)
- Redis6.0版本引入了多线程模型,多个IO线程处理网络请求
- Redis 的切片集群
- Kafka多分区,多个消费者并行处理
批量化处理
- Kafka批量发送消息处理
- Redis持久化,执行命令写入AOF 缓冲区,再写入到磁盘中,AOF通常配置为 everysec 批量写入到磁盘
- 批量查:
- 数据库一次批量查询数据,节省网络 I/O;(注意批量的条数也会做限制,防止单批数据量过大)
- 批量写:
- 调下游服务接口,下游就不支持过多的批量数据,此时可以将多条数据分批并发请求。
分片化
- Redis 集群是将数据自动分片到多个节点上,每个节点负责数据的一部分,每个节点都可以对外提供服务,突破单机 Redis 存储限制跟读写上限,提高整个服务的高并发能力。除了官方推出的集群模式,代理模式 codis 等也是将数据分片到不同节点,codis 将多个完全独立的 Redis 节点组成集群,通过 codis 转发请求到某一节点,来提高服务存储能力和读写性能。
- 同样的 Kafka 中每个 topic 也支持多个 partition,partition 分布到多个 broker 上,减轻单台机器的读写压力,通过增加 partition 数量可以增加消费者并行消费消息,提高 Kafka 的水平扩展能力和吞吐量。
顺序写
- MySQL 的 InnoDB 存储引擎在创建主键时通常会建议使用自增主键,而不是使用 uuid。最主要的原因是 InnoDB 底层采用 B+树用来存储数据,每个叶子结点是一个数据页,存储多条数据记录,页面内的数据通过链表有序存储,数据页间通过双向链表存储。由于 uuid 是无序的,有可能会插入到已经空间不足的数据页中间,导致数据页分裂成两个新的数据页以便插入新数据,影响整体写入性能。
- 此外 MySQL 中的写入过程并不是每次将修改的数据直接写入到磁盘中,而是修改内存中 buffer pool 内存储的数据页,将数据页的变更记录到 undolog 和 binlog 日志中,保证数据变更不丢失,每次记录 log 都是追加写到日志文件尾部,顺序写入到磁盘。对数据进行变更时通过顺序写 log,避免随机写磁盘数据页,提升写入性能,这种将随机写转变为顺序写的思想在很多中间件中都有所体现。
- kakfa 中的每个分区是一个有序不可变的消息队列,新的消息会不断的添加的 partition 的尾部,每个 partition 由多个 segment 组成,一个 segment 对应一个物理日志文件,kafka 对 segment 日志文件的写入也是顺序写。顺序写入的好处是避免了磁盘的不断寻道和旋转次数,极大的提高了写入性能。
池化
- MySQL等数据库也都提供连接池,可以预先创建一定数量的连接用于处理数据库请求。当请求到来时,可以从连接池中选择空闲连接来处理请求,请求结束后将连接归还到连接池中,避免连接创建和销毁带来的开销,提升数据库性能。
- 创建线程池用来处理请求,在请求到来时同样的从链接池中选择空闲的线程来处理请求,处理结束后归还到线程池中,避免线程创建带来的消耗,在 Web 框架等需要高并发的场景下非常常见。
- 线程池
- 连接池