当前位置: 首页 > 科技观察

Feed和秒杀,支持10Wqps,架构方案一样吗?

时间:2023-03-12 04:14:32 科技观察

《??并发扣款,如何保证一致性????》一文描述了高并发条件下并发推导的一致性、幂等性、ABA问题。很多朋友会有疑惑:如果有一个大客户,这个客户的并发度很高,版本号对比会导致大量的更新失败。所以介绍了这个方案不适合高并发场景。是这样吗?你对高并发有什么误解吗?我常说,任何脱离业务场景的架构设计都是耍流氓。今天我们就来说说三种高并发业务场景在架构设计上的差异。1.QQQQ的一些核心业务包括:个人:user(uid,user_info,…)好友:user_friends(uid,friend_id,…)加入的群组:user_groups(uid,group_id,…)群组:group(gid,group_info,…)群成员:group_members(gid,uid,…)个人消息:msgs_user(msg_id,uid,…)群消息:msgs_group(msg_id,gid,…)这些信息有一个读写特性,会带上uid/gid/msgid属性。例如拉取好友列表:selectfriend_idfromuser_friendswhereuid=$uid;当用户量大,并发量大时,不同用户/组/消息数据的读写不存在锁冲突。画外音:10万用户同时读写,互不锁冲突。只有当同一个用户在短时间内有大量并发时,才有可能出现锁冲突。画外音:比如1个用户每秒读写10000次。在这样的场景下,使用《??并发扣款,如何保证一致性????》中的CAS乐观来解决同一用户并发冲突的一致性是绝对没问题的。2.微博微博的核心业务是feed流:发送消息,写操作刷消息,读操作微博业务明显是读多写少。当用户刷消息时,消息在他们自己的feed流中,由其他人发布。查看主页提要流的最简单方法是:拉取您关注的用户的id_list;从这些用户那里拉取最新的N条消息;对N*id_list消息进行排序;返回首页消息获取自己的首页feed流;当用户量大,并发量大时,某些数据会出现读写锁冲突。画外音:与QQ基本读写自己的数据不同,微博需要写自己的数据,读取别人的数据。在这种场景下,《读扩散,写扩散,终于讲清楚了!》中提到的readdiffusion和writediffusion也是常见的解决方案。3.1230612306的核心业务是:检票,读操作买票,写操作stock(id,num)//某趟车还有多少张票?存在巨大的锁冲突。画外音:这个业务的数据量不大。这种“秒杀”的业务,如果不做特别的优化,数据库很容易死锁卡死,没人能成功买票。这类“秒杀”业务有哪些常见的优化方式?一般来说,系统和业务需要分开优化。在系统层面,秒杀业务的优化方向是什么?主要有两点:(1)尽量在系统上游拦截请求,而不是让锁冲突落到数据库上。传统秒杀系统挂掉的原因是请求被推送到后端数据层,数据读写锁冲突严重,高并发高响应慢,几乎所有请求超时,访问流量大,成功下单的有效流量较小。一张火车票有2000张,同时有200万人前来购买。没人买成功,请求有效率为0。画外音:此时系统效率还不如线下售票窗口。(2)充分利用缓存。这是一个典型的读多写少的业务场景:车次查询,读,大额票查询,读,大额下单支付,写,小额2000张火车票,200万人次购买同时,最多2000人下单成功,其他人全部清点库存。写占比仅为0.1%,读占比高达99.9%。非常适合缓存来优化。秒杀业务常见的系统分层架构如何?秒杀业务,可以采用典型的面向服务的分层架构:端(浏览器/APP),顶层,面向用户的站点层,接入后端数据,组装html/json返回服务层,屏蔽底层数据细节,并提供数据访问如何优化数据层,DB存储库存,当然还有缓存?(1)端(浏览器/APP)请求拦截。春节期间想必大家都玩过微信的摇一摇抢红包吧。用户真的每次摇一摇都会向后端发送请求吗?回顾抢票场景,用户点击“查询”按钮后,系统卡顿,用户心急如焚,会不自觉地频繁点击“查询”,不仅没有用,还增加了系统负载没原因。平均一个用户点击5次,80%的请求都是这么多。在JS层面,用户在x秒内只能提交一个请求,从而降低系统负载。画外音:对于频繁投稿,可以友情提示“频率过快”。在APP层面,也可以做类似的事情。虽然用户疯狂刷微信抢红包,但实际上向后台发起请求需要x秒。画外音:这就是所谓的“尽可能拦截系统上游的请求”,浏览器/APP层可以拦截80%+的请求。但是端上的拦截只能拦截普通用户(99%的用户都是普通用户)。程序员firebug抓包后,写一个for循环直接调用后端http接口。js拦截根本不起作用。我们现在应该做什么??(2)站点层如何拦截请求如何抵制程序员写for循环调用http接口,首先判断用户的唯一标识,对频繁使用的用户进行拦截。什么用来唯一标识用户?ip?cookie-id?不要想的太复杂,所有的购票服务都需要登录,可以通过uid来识别用户。在站点层,对相同uid的请求进行计数和限速。比如一个uid,5秒内只能有一个请求通过,可以阻断99%的for循环请求。一个uid,5s只能通过一个request,剩下的请求怎么办?缓存、页面缓存等5秒内到达site层的请求,都是返回上次返回的页面。画外音:车次查询和余票查询都可以,既能保证用户体验(至少不返回404页面),又能保证系统的健壮性(使用页面缓存拦截请求在站点层)。OK,99%的普通程序员都被计数、限速、页面缓存挡住了,但是还是有一些高端的程序员,比如黑客,控制着10万只肉鸡,手里有10万个uid,发送请求的时候同时。我现在应该怎么做??(3)服务层的请求拦截并发请求已经到了服务层,如何拦截呢?服务层非常清楚业务的库存和数据库的抗压能力,可以基于两者进行调峰和限速。比如业务服务很清楚火车票只有2000张,此时透传10万个请求到数据库是没有意义的。画外音:如果数据库每秒只能承受500个写请求,那么只透传其中的500个。削峰用什么?请求队列。对于写请求,做一个请求队列,每次只透传有限的写请求给数据层(下单,支付这样的写业务)。火车票只有2000张,即使有10万个请求过来,也只有2000个透传访问数据库:如果上一批请求成功,如果上一批请求库存不足,再下一批,全部后续请求读请求会返回“已经”“已售完”,如何优化?缓存抗性,不管是memcached还是redis,单机抗10w每秒应该没问题。画外音:缓存是水平缩放的,很容易线性扩展。通过这样的调峰限流,只有极少数的写请求和极少数的读缓存未命中请求会渗透到数据层,99%的请求都会被阻塞。(4)数据库层经过前三层的优化:浏览器拦截80%的请求,site层拦截99%的请求,做一个页面缓存服务层。根据业务存量和数据库的抗压能力,做一个写请求队列,加上数据缓存,你会发现每次请求到数据库层都是可控的。db基本没什么压力,就是在花园里闲逛。画外音:这类业务数据量不大,不需要分库。足以使数据库高可用。此时向数据库发送了2000个请求,全部成功,请求效率100%。画外音:优化前10万请求0成功,效率0%。按照上面的优化方案,站点层其实是压力最大的。假设真实有效的请求数是每秒100w,这部分压力怎么处理呢?方案有两种:站点层横向扩展,加机器扩展,一台能搞定5000台,200台也能搞定;服务降级,丢弃请求,例如丢弃50%;原则是保护系统,不让所有用户失效。site层限速,每个uid的requestcount是放在redis中的吗?在高吞吐、高并发访问redis的情况下,网络带宽会不会成为瓶颈?同样的uid计数和限速,如果担心访问redis带宽会成为瓶颈,可以这样优化:计数直接放在内存中,节省了网络请求;在nginx层做7层均衡,让一个uid请求落到同一个机器上;画外音:这个统计对数据的一致性和准确性要求不高。就算服务重启了,计数丢了,重新开始计数也是大事。除了系统优化之外,还可以在产品和业务之间做一些妥协,降低架构的难度。业务取舍1:一般来说,下单和支付同一个流程可以提高转化率。对于秒杀场景,商品listing、下单流程和支付流程是异步的,放在两个环节,可以减轻数据库写入的压力。以12306为例,下单成功后,系统会占用库存,45分钟内即可完成付款。业务权衡2:一般来说,所有用户的规则都是一样的,体验会更好。针对闪购场景,在产品方面,分时票在不同区域销售。虽然不是所有的用户都有相同的规则,但是可以大大减轻系统的压力。北京9:00开始售票,上海9:30开始售票,广州XX开始售票,可以分担系统压力。业务权衡3:在秒杀场景下,由于短时间内的高并发,导致系统返回缓慢,用户很着急,可能会频繁点击按钮,给系统带来压力。可以对产品进行优化,一旦点击,无论系统是否返回,按钮都会立即变灰,不会给用户频繁点击的机会。业务权衡4:一般来说,展示具体的库存数量可以提升用户体验。对于秒杀场景,产品上只显示有无工单,不显示工单的具体数量,可以降低缓存淘汰率。画外音:显示库存会淘汰N次,显示可用性只会淘汰1次。更多的是,用户关注的是有没有票,而不是有多少票。无论如何,当产品技术一起运营的时候,目标是一致的,把事情做好,没有谁是甲方谁是乙方的关系。总结对于高并发、锁冲突小的业务,可以使用《??并发扣款,如何保证一致性????》中的方法来保证一致性。对于秒杀业务,除了业务妥协,在架构设计上主要有两个优化方向:尽量在系统上游拦截请求;多读多写,少用缓存;