太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己( 七 )


太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
服务+数据库是一套(省去了缓存) , 数据库采用“双主”的模式 。扩容步骤:
  • 第一步 , 将一个主库提升;
  • 第二步 , 修改配置 , 2库变4库(原来MOD2 , 现在配置修改后MOD4) , 扩容完成 。
原MOD2为偶的部分 , 现在会MOD4余0或者2;原MOD2为奇的部分 , 现在会MOD4余1或者3;数据不需要迁移 , 同时 , 双主互相同步 , 一遍是余0 , 一边余2 , 两边数据同步也不会冲突 , 秒级完成扩容!最后 , 要做一些收尾工作:
  1. 将旧的双主同步解除;
  2. 增加新的双主(双主是保证可用性的 , shadow-master平时不提供服务);
  3. 删除多余的数据(余0的主 , 可以将余2的数据删除掉) 。

太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
这样 , 秒级别内 , 我们就完成了2库变4库的扩展 。
大并发带来的挑战请求接口的合理设计
一个秒杀或者抢购页面 , 通常分为2个部分 , 一个是静态的HTML等内容 , 另一个就是参与秒杀的Web后台请求接口 。通常静态HTML等内容 , 是通过CDN的部署 , 一般压力不大 , 核心瓶颈实际上在后台请求接口上 。这个后端接口 , 必须能够支持高并发请求 , 同时 , 非常重要的一点 , 必须尽可能“快” , 在最短的时间里返回用户的请求结果 。为了实现尽可能快这一点 , 接口的后端存储使用内存级别的操作会更好一点 。仍然直接面向MySQL之类的存储是不合适的 , 如果有这种复杂业务的需求 , 都建议采用异步写入 。
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
当然 , 也有一些秒杀和抢购采用“滞后反馈” , 就是说秒杀当下不知道结果 , 一段时间后才可以从页面中看到用户是否秒杀成功 。但是 , 这种属于“偷懒”行为 , 同时给用户的体验也不好 , 容易被用户认为是“暗箱操作” 。
高并发的挑战:一定要“快”
我们通常衡量一个Web系统的吞吐率的指标是QPS(Query Per Second , 每秒处理请求数) , 解决每秒数万次的高并发场景 , 这个指标非常关键 。举个例子 , 我们假设处理一个业务请求平均响应时间为100ms , 同时 , 系统内有20台Apache的Web服务器 , 配置MaxClients为500个(表示Apache的最大连接数目) 。
那么 , 我们的Web系统的理论峰值QPS为(理想化的计算方式):20*500/0.1 = 100000 (10万QPS)咦?我们的系统似乎很强大 , 1秒钟可以处理完10万的请求 , 5w/s的秒杀似乎是“纸老虎”哈 。实际情况 , 当然没有这么理想 。在高并发的实际场景下 , 机器都处于高负载的状态 , 在这个时候平均响应时间会被大大增加 。
就Web服务器而言 , Apache打开了越多的连接进程 , CPU需要处理的上下文切换也越多 , 额外增加了CPU的消耗 , 然后就直接导致平均响应时间增加 。因此上述的MaxClient数目 , 要根据CPU、内存等硬件因素综合考虑 , 绝对不是越多越好 。可以通过Apache自带的abench来测试一下 , 取一个合适的值 。然后 , 我们选择内存操作级别的存储的Redis , 在高并发的状态下 , 存储的响应时间至关重要 。网络带宽虽然也是一个因素 , 不过 , 这种请求数据包一般比较小 , 一般很少成为请求的瓶颈 。负载均衡成为系统瓶颈的情况比较少 , 在这里不做讨论哈 。
那么问题来了 , 假设我们的系统 , 在5w/s的高并发状态下 , 平均响应时间从100ms变为250ms(实际情况 , 甚至更多):20*500/0.25 = 40000 (4万QPS)于是 , 我们的系统剩下了4w的QPS , 面对5w每秒的请求 , 中间相差了1w 。然后 , 这才是真正的恶梦开始 。
举个例子 , 高速路口 , 1秒钟来5部车 , 每秒通过5部车 , 高速路口运作正常 。突然 , 这个路口1秒钟只能通过4部车 , 车流量仍然依旧 , 结果必定出现大塞车 。(5条车道忽然变成4条车道的感觉) 。同理 , 某一个秒内 , 20*500个可用连接进程都在满负荷工作中 , 却仍然有1万个新来请求 , 没有连接进程可用 , 系统陷入到异常状态也是预期之内 。


推荐阅读