背景
其实如何设计一个高并发系统是一个非常复杂的过程,但是我们可以将他们简单的拆分成几个单独的技术模块:
- 系统拆分:一般用dubbo或者其他的
- 缓存:很好的支持高并发,一般是redis
- MQ:消息队列,能够异步处理,很好的支持高并发
- 分库分表:提高数据库的高并发、sql执行效率
- 读写分离:一般读多写少的会比较好
- ElasticSearch:分布式搜索引擎,一些全文搜索可以考虑
一、MQ的高可用性
RabbitMQ的三种模式
- 单机模式:无高可用性
- 普通集群模式:无高可用性,单个数据没有备份,一旦机器宕机,就无法使用,这个方案主要是提高吞吐量的
- 镜像集群模式:高可用性,单个数据会有多个副本,但是对资源的依赖是很大的
Kafka的高可用性
其实RabbitMQ并不是一个分布式消息队列,一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据,但是Kafka就是天然的分布式消息队列,每一个topic的数据,就是分散在多台机器上的,每个机器放一部分数据,在Kafka 0.8之后支持高可用性,支持replica副本机制,选举leader,然后只是从leader中去读写(保证数据一致性),如果这台宕机了,才会重新选举leader。
二、Redis的高并发和高可用
- redis的主从架构:一主多从,单主用来写入数据,单机几万QPS,多从用来查询数据,多个实例可以提供10W几倍的QPS
- redis基于哨兵模式实现高可用性,任何一个实例宕机,就进行主备切换
三、MQ的重复消费
Kafka是有一个offset的概念,就是每当你消费一个MQ的时候都会将这个已经消费的offset提交一下,这样你在下次消费的时候就从当前这个offset的后面的开始消费,但是有可能你消费完一条MQ,但是还没有来得及提交offset的时候系统关闭了,这样下次消费的时候就会有重复的MQ消息过来,所以我们需要从业务的层面来保证数据的幂等性,防止重复消费MQ消息。
- 根据主键查询,有就Update,没有就insert
- 写redis,天然幂等
- 每条数据全局ID,先根据ID查询redis里面有没有,没有就消费,并更新redis,有就丢弃
- 基于数据库的唯一主键来保证幂等性
四、MQ的顺序性
如果本来是增加、删除,结果你消费的顺序是删除、增加,这样数据就会出现问题
- RabbitMQ:如上执行的时候顺序不对,办法是每个queue对应一个consumer
- kafka:多线程消费的时候出现顺序不一致,如果换成单线程效率不高,每个线程消费同一个key数据的queue。
五、MQ消息丢失
MQ消息丢失是很常见的现象,可能是生产者丢失了数据,也有可能是消费者在消费的过程中丢失了数据
RabbitMQ:
- 生产者:添加事务,只有等待RabbitMQ返回成功才提交事务,但是效率低下,开启confirm模式,每次写的消息分配一个唯一的id,如果写入到RabbitMQ中就会回传一个ack消息,失败就回传nack消息,可以重试。如果是
- RabbitMQ:RabbitMQ自己弄丢消息,可以持久化,可以与confirm机制配合使用。
- 消费者:消费端主要是程序去保证吧
Kafka:
- 生产者:一般不会丢失数据的
- Kafka:Kafka的某个broker宕机,重新选举partition的leader,如果此时刚好follower有一些数据没有同步,如果这个follower成leader之后就会有一些数据的丢失。1.给 topic 设置
replication.factor
参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。2.在 Kafka 服务端设置min.insync.replicas
参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队。3.在 producer 端设置acks=all
:这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了。4.在 producer 端设置retries=MAX
(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了。 - 消费者:消费者主动提交了offset,但是当数据还没有处理完的时候机器宕机了,导致数据没有消费成功,但是Kafka认为他已经消费了,所以手动提交offset就行,