,《并发扣款,如何保证一致性?》一文,描述了高并发情况下,并发扣款的一致性,幂等性,以及ABA问题。,很有朋友有疑问:如果存在一个大客户,这一个客户并发量就非常高,版本号比对会导致大量的更新失败。于是推出,这个方案不适用于高并发场景。,究竟是不是这样呢?大家对高并发是不是有什么误解呢?,我经常说,任何脱离业务场景的架构设计都是耍流氓,今天来聊一聊三个高并发业务场景的架构设计差异。,QQ的一些核心业务有:,这些信息的读写有一个特点,都会带上uid/gid/msgid属性。,例如,拉取好友列表:,在用户量很大,并发量很大时,不同用户/群/消息数据的读写并没有锁冲突。,画外音:10W个用户同时读写,彼此没有锁冲突。,只有当,同一个用户,很短的时间内,有大量并发时,才可能存在锁冲突。,画外音:例如,1个用户,1秒钟读写1W次。,这类场景下,使用《并发扣款,如何保证一致性?》中的CAS乐观来解决同一个用户的并发冲突一致性,是绝对没有问题的。,微博的核心业务是feed流:,微博业务显然是读多写少的,在用户刷消息时,自己feed流里的消息,是由别人发出的。,查看自己主页feed流,最朴素的实现方法是:,在用户量很大,并发量很大时,会有一定数据的读写锁冲突。,画外音:不像QQ,基本是读写自己的数据,微博要写自己的数据,读别人的数据。,这类场景下,《读扩散,写扩散,终于讲清楚了!》中提到的读扩散,写扩散,也是常见的解决方案。,12306的核心业务是:,在用户量很大,并发量很大时,有极大的锁冲突。,画外音:这个业务,数据量并不大。,这类“秒杀”业务,如果不做特殊的优化,数据库很容易死锁卡死,没有任何人能买票成功。,一般来说,系统上和业务上分别需要配合优化。,主要有两项:,传统秒杀系统之所以挂,是因为请求都压到了后端数据层,数据读写锁冲突严重,并发高响应慢,几乎所有请求都超时,访问流量大,下单成功的有效流量小。,一趟火车2000张票,200w个人同时来买,没有人能买成功,请求有效率为0。,画外音:此时系统的效率,还不如线下售票窗口。,秒杀买票,这是一个典型的读多写少的业务场景:,一趟火车2000张票,200w个人同时来买,最多2000个人下单成功,其他人都是查询库存,写比例只有0.1%,读比例占99.9%,非常适合使用缓存来优化。,
,秒杀业务,可以使用典型的服务化分层架构:,想必春节大家都玩过微信的摇一摇抢红包,用户每摇一次,真的就会往后端发送一次请求么?,回顾抢票的场景,用户点击“查询”按钮之后,系统卡顿,用户着急,会不自觉的再去频繁点击“查询”,不但没用,反而平白无故增加系统负载,平均一个用户点5次,80%的请求是这么多出来的。,JS层面,可以限制用户在x秒之内只能提交一次请求,从而降低系统负载。,画外音:频繁提交,可以友好提示“频率过快”。,APP层面,可以做类似的事情,虽然用户疯狂的在摇微信抢红包,但其实x秒才向后端发起一次请求。,画外音:这就是所谓的“将请求尽量拦截在系统上游”,浏览器/APP层就能拦截80%+的请求。,不过,端上的拦截只能挡住普通用户(99%的用户是普通用户),程序员firebug一抓包,写个for循环直接调用后端http接口,js拦截根本不起作用,这下怎么办?,如何抗住程序员写for循环调用http接口,首先要确定用户的唯一标识,对于频繁访问的用户予以拦截。,ip?cookie-id?别想得太复杂,购票类业务都需要登录,用uid就能标识用户。,在站点层,对同一个uid的请求进行计数和限速,例如:一个uid,5秒只准透过1个请求,这样又能拦住99%的for循环请求。,缓存,页面缓存,5秒内到达站点层的其他请求,均返回上次返回的页面。,画外音:车次查询和余票查询都能够这么做,既能保证用户体验(至少没有返回404页面),又能保证系统的健壮性(利用页面缓存,把请求拦截在站点层了)。,OK,通过计数、限速、页面缓存拦住了99%的普通程序员,但仍有些高端程序员,例如黑客,控制了10w个肉鸡,手里有10w个uid,同时发请求,这下怎么办?,服务层非常清楚业务的库存,非常清楚数据库的抗压能力,可以根据这两者进行削峰限速。,例如,业务服务很清楚的知道,一列火车只有2000张车票,此时透传10w个请求去数据库,是没有意义的。,画外音:假如数据库每秒只能抗500个写请求,就只透传500个。,请求队列。,对于写请求,做请求队列,每次只透传有限的写请求去数据层(下订单,支付这样的写业务)。,只有2000张火车票,即使10w个请求过来,也只透传2000个去访问数据库:,cache抗,不管是memcached还是redis,单机抗个每秒10w应该都是没什么问题的。,画外音:缓存做水平扩展,很容易线性扩容。,如此削峰限流,只有非常少的写请求,和非常少的读缓存mis的请求会透到数据层去,又有99%的请求被拦住了。,经过前三层的优化:,你会发现,每次透到数据库层的请求都是可控的。,db基本就没什么压力了,闲庭信步。,画外音:这类业务数据量不大,无需分库,数据库做一个高可用就行。,此时,透2000个到数据库,全部成功,请求有效率100%。,画外音:优化前,10w个请求0个成功,有效性0%。,解决方向有两个:,原则是要保护系统,不能让所有用户都失败。,站点层限速,是每个uid的请求计数放到redis里么?吞吐量很大情况下,高并发访问redis,网络带宽会不会成为瓶颈?,同一个uid计数与限速,如果担心访问redis带宽成为瓶颈,可以这么优化:,画外音:这个计数对数据一致性、准确性要求不高,即使服务重启计数丢了,大不了重新开始计。,除了系统上的优化,产品与业务还能够做一些折衷,降低架构难度。,画外音:显示库存会淘汰N次,显示有无只会淘汰1次。更多的,用户关注是否有票,而不是票有几张。,无论如何,产品技术运营一起,目标是一致的,把事情做好,不存在谁是甲方,谁是乙方的关系。,对于并发高,锁冲突小的业务,可以采用《并发扣款,如何保证一致性?》中的方法保障一致性。,对于秒杀类业务,除了业务折衷,架构设计上主要有两大优化方向:
© 版权声明
文章版权归作者所有,未经允许请勿转载。