讲座预约高并发方案
# 项目高并发介绍
草稿
# MQ + Redis 并发方案
两个微服务:
Order 微服务:从 Redis 中读取信息判断用户是否可以预约,当用户可以预约讲座时,将用户 id 和讲座 id 封装成一个对象发送到 MQ。
Lecture 微服务:监听 MQ,从 MQ 中获取用户 id 和讲座 id,将用户预约记录插入数据库并减少用户可预约讲座数量。同时更新 Redis 内的信息。
Redis:
- 存储每一场讲座的开始预约时间,开始时间之后用户才可以预约。
- 存储每一场讲座的剩余可预约数量,当可预约数量为 0 时就不可预约了。
- 存储已预约记录,防止用户重复预约。
MQ:
- 存储用户 id + 讲座 id 封装成的对象,根据这信息,消费者服务就可以更新 MySQL 和 Redis。
MySQL:
- Lecture表:存放讲座的相关信息,包括剩余可预约数量。
- Order表:存放用于的预约记录(user Id 和 lecture Id 构成唯一索引)
预约流程:
- 前端发送预约请求 nginx 反向代理到 Order 微服务
- 开始业务处理流程:
- 判断用户是否登录
- 从 Redis 内查询讲座预约是否开始或结束
- 从 Redis 内查询该用户是否重复预订讲座
- 递减 Redis 内该讲座的剩余可预约数量
- 将用户 id 和讲座 id 封装,发送到 MQ
- Lecture 微服务监听 MQ
- 发现 MQ 的队列中有消息就开始消费
- 开启事务
- 添加用户预约讲座记录
- 减少该讲座的剩余可预约数量
- commit
# 改进
# 问题及改进方案
🔶 问题1:MQ 积压情况下,重复预约
问题:当用户第一次预约,封装有用户 id 和讲座 id 的消息在 MQ 中没有被消费时,此时该用户又多次预约,这些预约信息会同样堆积到 MQ 中,导致 MQ 中出现无用的消息。并且,随着用户每一次的预约,Redis 内保存的可预约数量也会减少。
解决:当用户将预约记录发送给 MQ 的时候可以在 Zookeeper 内创建一个节点,表示用户正在预约,下一次用户再次预约的时候发现这个节点的存在,就不会在往 MQ 中发送消息了。
🔶 问题2:操作数据库失败,Redis 和 MySQL 数据不一致
问题:当 Lecture 微服务在操作数据库失败时,回滚事务后,需要将 Redis 内该讲座的剩余可预约数量 +1。因为在 Order 微服务内对该讲座可预约数量 -1,没有预约成功的时候要加回来。
🔶 问题3:预约之后无法立刻知道预约情况
问题:消息发送到消息队列中了,正常情况可以预约成功,但是如果发生意外,Order 微服务也不知道是否预约成功。
解决:使用 Zookeeper 做分布式协调,Zookeeper 中的节点表示正在预约,当该节点存在的时候,前端界面就一直 Loading,当 Zookeeper 中该节点消失就说明可以再次刷新用户预约状态。
# 其他改进
🔶 高并发读改进
之前解决的高并发问题都是针对于预约,其他一些常用查询也可以缓存到 Redis 里,所有的查询请求都是从 Redis 内查询,当 Redis 内不存在时再去请求 MySQL。当写请求修改讲座信息时,需要先删除 Redis 缓存内的数据,再更新 MySQL,保证 Redis 和数据库的一致性。(JVM 内部队列,保证更新数据或当获取 Redis 中不存在数据时会按队列内部串行执行)
🔶 静态资源缓存
预约开始之前会存大量的刷新请求页面资源。
页面资源访问多:需要考虑静态化,CDN,静态资源缓存及压缩
🔶 接口限流
- 前端限流:隐藏、disable 按钮等方法
- 同一个用户 10s 内只能请求预约操作一次,通过 redis 键过期策略,
order:userId:lectuerId
- 令牌桶算法,令牌桶算法的基本思路是每个请求尝试获取一个令牌,后端只处理持有令牌的请求,生产令牌的速度和效率我们都可以自己限定。
🔶 服务降级
当服务某个服务器出现宕机或服务不可用时,可以使用 Hystrix 进行熔断和降级,给用户一个友好的提示。
# 高并发进一步解决
系统拆分:将服务进一步拆分,每个服务到单独操作一类数据库。原本一个服务对应一个数据库,就变成了 n 个服务对应 n 个数据库,提高了并发量。
分库分表:一个数据库拆分成多个数据库,多个库分担压力;一个表拆分为多个表,提高 sql 语句性能。
读写分离:数据库搞主从架构,主库写入,从库读取,实现读取分离。