1. 首页
  2. IT资讯

12306 的架构到底有多牛逼?

“u003Cpu003EJava后端,选择 u003Cu002Fpu003Eu003Cpu003E优质文章,及时送达u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRcsUnd92eoboDR” img_width=”640″ img_height=”29″ alt=”12306 的架构到底有多牛逼?” inline=”0″u003Eu003Cpu003E链接 | 绘你一世倾城u003Cu002Fpu003Eu003Cpu003E来源 | httpjuejin.imu002Fpostu002F5d84e21f6fb9a06ac8248149u003Cu002Fpu003Eu003Cpu003E每到节假日期间,一二线城市返乡、外出游玩的人们几乎都面临着一个问题:抢火车票!u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E12u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E6 抢票,极限并发带来的思考u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E虽然现在大多数情况下都能订到票,但是放票瞬间即无票的场景,相信大家都深有体会。u003Cu002Fpu003Eu003Cpu003E尤其是春节期间,大家不仅使用 12u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E6,还会考虑“智行”和其他的抢票软件,全国上下几亿人在这段时间都在抢票。u003Cu002Fpu003Eu003Cpu003E“12u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E6 服务”承受着这个世界上任何秒杀系统都无法超越的 QPS,上百万的并发再正常不过了!u003Cu002Fpu003Eu003Cpu003E笔者专门研究了一下“12u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E6”的服务端架构,学习到了其系统设计上很多亮点,在这里和大家分享一下并模拟一个例子:如何在 100 万人同时抢 1 万张火车票时,系统提供正常、稳定的服务。u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003EGithub代码地址:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Ehttps:u002Fu002Fgithubu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E.comu003Cu002Fiu003Eu002FGuoZhaoranu002FspikeSystemu003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Cstrongu003E大型高并发系统架构u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E高并发的系统架构都会采用分布式集群部署,服务u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003E上层u003Cu002Fiu003E有着层层负载均衡,并提供各种容灾手段(双火机房、节点容错、服务器灾备等)保证系统的高可用,流量也会根据不同的负载能力和配置策略均衡到不同的服务器上。u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E下边是一个简单的示意图:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRejZGikBYjO8s7″ img_width=”593″ img_height=”368″ alt=”12306 的架构到底有多牛逼?” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E负载均衡简介u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E上图中描述了用户请求到服务器经历了三层的负载均衡,下边分别简单介绍一下这三种负载均衡。u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E1. OSPF(开放式最短链路优先)是一个内部网关协议(Interior Gateway Protocol,简称 IGP)u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003EOSPF 通过路由器之间通告网络接口的状态来建立链路状态数据库,生成最短路径树,OSPF 会自动计算路由接口上的 Cost 值,但也可以通过手工指定该接口的 Cost 值,手工指定的优先于自动计算的值。u003Cu002Fpu003Eu003Cpu003EOSPF 计算的 Cost,同样是和接口带宽成反比,带宽越高,Cost 值越小。到达目标相同 Cost 值的路径,可以执行负载均衡,最多 6 条链路同时执行负载均衡。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E2. LVS (Linux Virtual Server)u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E它是一种集群(Cluster)技术,采用 IP 负载均衡技术和基于内容请求分发技术。u003Cu002Fpu003Eu003Cpu003E调度器具有很好的吞吐率,将请求均衡地转移到不同的服务器上执行,且调度器自动屏蔽掉服务器的故障,从而将一组服务器构成一个高性能的、高可用的虚拟服务器。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E3. Nginxu003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E想必大家都很熟悉了,是一款非常高性能的 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003EHTTPu003Cu002Fiu003E 代理u002F反向代理服务器,服务开发中也经常使用它来做负载均衡。这里有一份Nginx不错的文章:Nginx 从入门到实战u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003ENginx 实现负载均衡的方式主要有三种:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Culu003Eu003Cliu003Eu003Cpu003Eu003Cstrongu003E轮询u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003Eu003Cstrongu003E加权轮询u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003Eu003Cstrongu003EIP Hash 轮询u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cu002Fliu003Eu003Cu002Fulu003Eu003Cpu003E下面我们就针对 Nginx 的加权轮询做专门的配置和测试。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003ENginx 加权轮询的演示u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003ENginx 实现负载均衡通过 Upstream 模块实现,其中加权轮询的配置是可以给相关的服务u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003E加上u003Cu002Fiu003E一个权重值,配置的时候可能根据服务器的性能、负载能力设置相应的负载。u003Cu002Fpu003Eu003Cpu003E下面是一个加权轮询负载的配置,我将在本地的监听 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E01-u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E04 端口,分别配置 1,2,3,4 的权重:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003E#配置负载均衡u003Cbru003Eupstream load_rule {u003Cbru003Eserver 127.0.0.1:u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E01 weight=1;u003Cbru003Eserver 127.0.0.1:u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E02 weight=2;u003Cbru003Eserver 127.0.0.1:u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E03 weight=3;u003Cbru003Eserver 127.0.0.1:u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E04 weight=4;u003Cbru003E}u003Cbru003E…u003Cbru003Eserver {u003Cbru003Elu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003Eten 80;u003Cbru003Eserver_name load_balanceu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E.comu003Cu002Fiu003E u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003Ewww.u003Cu002Fiu003Eload_balanceu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E.comu003Cu002Fiu003E;u003Cbru003Elocation u002F {u003Cbru003Eproxy_pass u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E:u002Fu002Fload_rule;u003Cbru003E}u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E我在本地 u002Fetcu002Fhosts 目录下配置了 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003Ewww.u003Cu002Fiu003Eload_balanceu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E.comu003Cu002Fiu003E 的虚拟域名地址。u003Cu002Fpu003Eu003Cpu003E接下来使用 Go 语言开启四个 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003EHTTPu003Cu002Fiu003E 端口监听服务,下面是监听在 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E01 端口的 Go 程序,其他几个只需要修改端口即可:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Epackage mainu003Cbru003Eu003Cbru003Eimport (u003Cbru003E”netu002Fu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E”u003Cbru003E”os”u003Cbru003E”strings”u003Cbru003E)u003Cbru003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003Efuncu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003Emainu003Cu002Fstrongu003E {u003Cbru003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E.HandleFunc(“u002Fbuyu002Fticket”, handleReq)u003Cbru003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E.Lu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003EtenAndServe(“:u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E01”, nil)u003Cbru003E}u003Cbru003Eu003Cbru003Eu002Fu002F处理请求函数,根据请求将响应结果信息写入日志u003Cbru003Eu003Cstrong toutiao-origin=”span”u003Efuncu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003EhandleRequ003Cu002Fstrongu003E(w u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E.ResponseWriter, r *u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E.Request) {u003Cbru003EfailedMsg := “handle in port:”u003Cbru003EwriteLog(failedMsg, “.u002Fstat.log”)u003Cbru003E}u003Cbru003Eu003Cbru003Eu002Fu002F写入日志u003Cbru003Eu003Cstrong toutiao-origin=”span”u003Efuncu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003EwriteLogu003Cu002Fstrongu003E(msg string, logPath string) {u003Cbru003Efd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003EAPPu003Cu002Fiu003EEND, 0644)u003Cbru003Edefer fd.Closeu003Cbru003Econtent := strings.Join([]string{msg, “\r\n”}, “u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E01″)u003Cbru003Ebuf := byte(content)u003Cbru003Efd.Write(buf)u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E我将请求的端口日志信息写到了 .u002Fstat.log 文件当中,然后使用 AB 压测工具做压测:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eab -n 1000 -c 100 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E:u002Fu002Fu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003Ewww.u003Cu002Fiu003Eload_balanceu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E.comu003Cu002Fiu003Eu002Fbuyu002Fticketu003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E统计日志中的结果,u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E01-u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E04 端口分别得到了 100、200、u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E0、400 的请求量。u003Cu002Fpu003Eu003Cpu003E这和我在 Nginx 中配置的权重占比很好的吻合在了一起,并且负载后的流量非常的均匀、随机。u003Cu002Fpu003Eu003Cpu003E具体的实现大家可以参考 Nginx 的 Upsteam 模块实现源码,这里推荐一篇文章《Nginx 中 Upstream 机制的负载均衡》:u003Cu002Fpu003Eu003Cpu003Ehttps:u002Fu002Fu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003Ewww.u003Cu002Fiu003Ekancloudu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003E.cnu003Cu002Fiu003Eu002Fdigestu002Funderstandingnginxu002F202607u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E秒杀抢购系统选型u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E回到我们最初提到的问题中来:火车票秒杀系统如何在高并发情况下提供正常、稳定的服务呢?u003Cu002Fpu003Eu003Cpu003E从u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003E上面u003Cu002Fiu003E的介绍我们知道用户秒杀流量通过层层的负载均衡,均匀到了不同的服务器上,即使如此,集群中的单机所承受的 QPS 也是非常高的。如何将单机性能优化到极致呢?u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E要解决这个问题,我们就要想明白一件事:u003Cu002Fstrongu003E通常订票系统要处理生成订单、减扣库存、用户支付这三个基本的阶段。u003Cu002Fpu003Eu003Cpu003E我们系统要做的事情是要保证火车票订单不超卖、不少卖,每张售卖的车票都必须支付才有效,还要保证系统承受极高的并发。u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E这三个阶段的先后顺序该怎么分配才更加合理呢?我们来分析一下:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E下单减库存u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp9.pstatp.comu002Flargeu002Fpgc-imageu002FRejZGj41DDCqKX” img_width=”571″ img_height=”82″ alt=”12306 的架构到底有多牛逼?” inline=”0″u003Eu003Cpu003E当用户并发请求到达服务端时,首先创建订单,然后扣除库存,等待用户支付。u003Cu002Fpu003Eu003Cpu003E这种顺序是我们一般人首先会想到的解决方案,这种情况下也能保证订单不会超卖,因为创建订单之后就会减库存,这是一个原子操作。u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E但是这样也会产生一些问题:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Culu003Eu003Cliu003Eu003Cpu003E在极限并发情况下,任何一个内存操作的细节都至关影响性能,尤其像创建订单这种逻辑,一般都需要存储到磁盘数据库的,对数据库的压力是可想而知的。u003Cu002Fpu003Eu003Cbru003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003E如果用户存在恶意下单的情况,只下单不支付这样库存就会变少,会少卖很多订单,虽然服务端可以限制 IP 和用户的购买订单数量,这也不算是一个好方法。u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cu002Fulu003Eu003Cpu003Eu003Cstrongu003E支付减库存u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRejZGjK11JLven” img_width=”571″ img_height=”82″ alt=”12306 的架构到底有多牛逼?” inline=”0″u003Eu003Cpu003E如果等待用户支付了订单在减库存,第一感觉就是不会少卖。但是这是并发架构的大忌,因为在极限并发情况下,用户可能会创建很多订单。u003Cu002Fpu003Eu003Cpu003E当库存减为零的时候很多用户发现抢到的订单支付不了了,这也就是所谓的“超卖”。也不能避免并发操作数据库磁盘 IO。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E预扣库存u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002FRejZGjb6sYAL49″ img_width=”426″ img_height=”291″ alt=”12306 的架构到底有多牛逼?” inline=”0″u003Eu003Cpu003E从上边两种方案的考虑,我们可以得出结论:只要创建订单,就要频繁操作数据库 IO。u003Cu002Fpu003Eu003Cpu003E那么有没有一种不需要直接操作数据库 IO 的方案呢,这就是预扣库存。先扣除了库存,保证不超卖,然后异步生成用户订单,这样响应给用户的速度就会快很多;那么怎么保证不少卖呢?用户拿到了订单,不支付怎么办?u003Cu002Fpu003Eu003Cpu003E我们都知道现在订单都有有效期,比如说用户五分钟内不支付,订单就失效了,订单一旦失效,就会加入新的库存,这也是现在很多网上零售企业保证商品不少卖采用的方案。u003Cu002Fpu003Eu003Cpu003E订单的生成是异步的,一般都会放到 MQ、Kafka 这样的即时消费队列中处理,订单量比较少的情况下,生成订单非常快,用户几乎不用排队。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E扣库存的艺术u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E从u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003E上面u003Cu002Fiu003E的分析可知,显然预扣库存的方案最合理。我们进一步分析扣库存的细节,这里还有很大的优化空间,库存存在哪里?怎样保证高并发下,正确的扣库存,还能快速的响应用户请求?u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E在单机低并发情况下,我们实现扣库存通常是这样的:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRejZGjzFeoFZTc” img_width=”368″ img_height=”150″ alt=”12306 的架构到底有多牛逼?” inline=”0″u003Eu003Cpu003E为了保证扣库存和生成订单的原子性,需要采用事务处理,然后取库存判断、减库存,最后提交事务,整个流程有很多 IO,对数据库的操作又是阻塞的。u003Cu002Fpu003Eu003Cpu003E这种方式根本不适合高并发的秒杀系统。接下来我们对单机扣库存的方案做优化:本地扣库存。u003Cu002Fpu003Eu003Cpu003E我们把一定的库存量分配到本地机器,直接在内存中减库存,然后按照之前的逻辑异步创建订单。u003Cu002Fpu003Eu003Cpu003ETips:欢迎大家u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E关注u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E微信u003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003E公众号u003Cu002Fiu003E:Java后端,来获取更多推送。u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E改进过之后的单机系统是这样的:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002FRejZGolbmr4u4″ img_width=”511″ img_height=”167″ alt=”12306 的架构到底有多牛逼?” inline=”0″u003Eu003Cpu003E这样就避免了对数据库频繁的 IO 操作,只在内存中做运算,极大的提高了单机抗并发的能力。u003Cu002Fpu003Eu003Cpu003E但是百万的用户请求量单机是无论如何也抗不住的,虽然 Nginx 处理网络请求使用 Epoll 模型,c10k 的问题在业界早已得到了解决。u003Cu002Fpu003Eu003Cpu003E但是 Linux 系统下,一切资源皆文件,网络请求也是这样,大量的文件描述符会使操作系统瞬间失去响应。u003Cu002Fpu003Eu003Cpu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003E上面u003Cu002Fiu003E我们提到了 Nginx 的加权均衡策略,我们不妨假设将 100W 的用户请求量平均均衡到 100 台服务器上,这样单机所承受的并发量就小了很多。u003Cu002Fpu003Eu003Cpu003E然后我们每台机器本地库存 100 张火车票,100 台服务器上的总库存还是 1 万,这样保证了库存订单不超卖,u003Cstrong toutiao-origin=”span” class=”highlight-text”u003E下面是我们描述的集群架构:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRejZGpBDxlBXFw” img_width=”661″ img_height=”309″ alt=”12306 的架构到底有多牛逼?” inline=”0″u003Eu003Cpu003E问题接踵而至,在高并发情况下,现在我们还无法保证系统的高可用,假如这 100 台服务器上有两三台机器因为扛不住并发的流量或者其他的原因宕机了。那么这些服务器上的订单就卖不出去了,这就造成了订单的少卖。u003Cu002Fpu003Eu003Cpu003E要解决这个问题,我们需要对总订单量做统一的管理,这就是接下来的容错方案。服务器不仅要在本地减库存,另外要远程统一减库存。u003Cu002Fpu003Eu003Cpu003E有了远程统一减库存的操作,我们就可以根据机器负载情况,为每台机器分配一些多余的“Buffer 库存”用来防止机器中有机器宕机的情况。u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E我们u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003E结合u003Cu002Fiu003E下面架构图具体分析一下:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRejZGpTJEzN4cR” img_width=”708″ img_height=”367″ alt=”12306 的架构到底有多牛逼?” inline=”0″u003Eu003Cpu003E我们采用 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 存储统一库存,因为 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 的性能非常高,号称单机 QPS 能抗 10W 的并发。u003Cu002Fpu003Eu003Cpu003E在本地减库存以后,如果本地有订单,我们再去请求 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 远程减库存,本地减库存和远程减库存都成功了,才返回给用户抢票成功的提示,这样也能有效的保证订单不会超卖。u003Cu002Fpu003Eu003Cpu003E当机器中有机器宕机时,因为每个机器上有预留的 Buffer 余票,所以宕机机器上的余票依然能够在其他机器上得到弥补,保证了不少卖。u003Cu002Fpu003Eu003Cpu003EBuffer 余票设置多少合适呢,理论上 Buffer 设置的越多,系统容忍宕机的机器数量就越多,但是 Buffer 设置的太大也会对 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 造成一定的影响。u003Cu002Fpu003Eu003Cpu003E虽然 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 内存数据库抗并发能力非常高,请求依然会走一次网络 IO,其实抢票过程中对 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 的请求次数是本地库存和 Buffer 库存的总量。u003Cu002Fpu003Eu003Cpu003E因为当本地库存不足时,系统直接返回用户“已售罄”的信息提示,就不会再走统一扣库存的逻辑。u003Cu002Fpu003Eu003Cpu003E这在一定程度上也避免了巨大的网络请求量把 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 压跨,所以 Buffer 值设置多少,需要架构师对系统的负载能力做认真的考量。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E代码演示u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003EGo 语言原生为并发设计,我采用 Go 语言给大家演示一下单机抢票的具体流程。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E初始化工作u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003EGo 包中的 Init 函数先于 Main 函数执行,在这个阶段主要做一些准备性工作。u003Cu002Fpu003Eu003Cpu003E我们系统需要做的准备工作有:初始化本地库存、初始化远程 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 存储统一库存的 Hash 键值、初始化 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 连接池。u003Cu002Fpu003Eu003Cpu003E另外还需要初始化一个大小为 1 的 Int 类型 Chan,目的是实现分布式锁的功能。u003Cu002Fpu003Eu003Cpu003E也可以直接使用读写锁或者使用 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 等其他的方式避免资源竞争,但使用 Channel 更加高效,这就是 Go 语言的哲学:不要通过共享内存来通信,而要通过通信来共享内存。u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 库使用的是 Redigo,下面是代码实现:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003E…u003Cbru003Eu002Fu002FlocalSpike包结构体定义u003Cbru003Epackage localSpikeu003Cbru003Eu003Cbru003Etype LocalSpike struct {u003Cbru003ELocalInStock int64u003Cbru003ELocalSalesVolume int64u003Cbru003E}u003Cbru003E…u003Cbru003Eu002Fu002FremoteSpike对hash结构的定义和u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E连接池u003Cbru003Epackage remoteSpikeu003Cbru003Eu002Fu002F远程订单存储健值u003Cbru003Etype RemoteSpikeKeys struct {u003Cbru003ESpikeOrderHashKey string u002Fu002Fu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E中秒杀订单hash结构keyu003Cbru003ETotalInventoryKey string u002Fu002Fhash结构中总订单库存keyu003Cbru003EQuantityOfOrderKey string u002Fu002Fhash结构中已有订单数量keyu003Cbru003E}u003Cbru003Eu003Cbru003Eu002Fu002F初始化u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E连接池u003Cbru003Efunc NewPool *u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E.Pool {u003Cbru003Ereturn &u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E.Pool{u003Cbru003EMau003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003ExIu003Cu002Fiu003Edle: 10000,u003Cbru003EMaxActive: 12000, u002Fu002F max number u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eofu003Cu002Fiu003E connectionsu003Cbru003EDial:u003Cstrong toutiao-origin=”span”u003Efuncu003Cu002Fstrongu003E (u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E.Conn, error) {u003Cbru003Ec, err := u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E.Dial(“tcp”, “:6379”)u003Cbru003Eif err != nil {u003Cbru003Epanic(err.Error)u003Cbru003E}u003Cbru003Ereturn c, erru003Cbru003E},u003Cbru003E}u003Cbru003E}u003Cbru003E…u003Cbru003Eu003Cstrong toutiao-origin=”span”u003Efuncu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003Einitu003Cu002Fstrongu003E {u003Cbru003ElocalSpike = localSpike2.LocalSpike{u003Cbru003ELocalInStock: 150,u003Cbru003ELocalSalesVolume: 0,u003Cbru003E}u003Cbru003EremoteSpike = remoteSpike2.RemoteSpikeKeys{u003Cbru003ESpikeOrderHashKey: “ticket_hash_key”,u003Cbru003ETotalInventoryKey: “ticket_total_nums”,u003Cbru003EQuantityOfOrderKey: “ticket_sold_nums”,u003Cbru003E}u003Cbru003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003EPool = remoteSpike2.NewPoolu003Cbru003Edone = make(chan int, 1)u003Cbru003Edone <- 1u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Cstrongu003E本地扣库存和统一扣库u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E本地扣库存逻辑非常简单,用户请求过来,添加销量,然后对比销量是否大于本地库存,返回 Bool 值:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Epackage localSpikeu003Cbru003Eu002Fu002F本地扣库存,返回bool值u003Cbru003Eu003Cstrong toutiao-origin=”span”u003Efuncu003Cu002Fstrongu003E(spike *LocalSpike)u003Cstrong toutiao-origin=”span”u003ELocalDeductionStocku003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003Eboolu003Cu002Fstrongu003E{u003Cbru003Espike.LocalSalesVolume = spike.LocalSalesVolume + 1u003Cbru003Ereturn spike.LocalSalesVolume < spike.LocalInStocku003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E注意这里对共享数据 LocalSalesVolume 的操作是要使用锁来实现的,但是因为本地扣库存和统一扣库存是一个原子性操作,所以在最u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003E上层u003Cu002Fiu003E使用 Channel 来实现,这块后边会讲。u003Cu002Fpu003Eu003Cpu003E统一扣库存操作 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E,因为 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 是u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003E单线u003Cu002Fiu003E程的,而我们要实现从中取数据,写数据并计算一些列步骤,我们要配合 Lua 脚本打包命令,保证操作的原子性:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Epackage remoteSpikeu003Cbru003E……u003Cbru003Econst LuaScript = `u003Cbru003Elocal ticket_key = KEYS[1]u003Cbru003Elocal ticket_total_key = ARGV[1]u003Cbru003Elocal ticket_sold_key = ARGV[2]u003Cbru003Elocal ticket_total_nums = tonumber(u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E.call(‘HGET’, ticket_key, ticket_total_key))u003Cbru003Elocal ticket_sold_nums = tonumber(u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E.call(‘HGET’, ticket_key, ticket_sold_key))u003Cbru003E– 查看是否还有余票,增加订单数量,返回结果值u003Cbru003Eif(ticket_total_nums >= ticket_sold_nums) thenu003Cbru003Ereturn u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E.call(‘HINCRBY’, ticket_key, ticket_sold_key, 1)u003Cbru003Eendu003Cbru003Ereturn 0u003Cbru003E`u003Cbru003Eu002Fu002F远端统一扣库存u003Cbru003Efunc (RemoteSpikeKeys *RemoteSpikeKeys) RemoteDeductionStock(conn u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E.Conn) bool {u003Cbru003Elua := u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E.NewScript(1, LuaScript)u003Cbru003Eresult, err := u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E.Int(lua.Do(conn, RemoteSpikeKeys.SpikeOrderHashKey, RemoteSpikeKeys.TotalInventoryKey, RemoteSpikeKeys.QuantityOfOrderKey))u003Cbru003Eif err != nil {u003Cbru003Ereturn falseu003Cbru003E}u003Cbru003Ereturn result != 0u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E我们使用 Hash 结构存储总库存和总销量的信息,用户请求过来时,判断总销量是否大于库存,然后返回相关的 Bool 值。u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E在启动服务之前,我们需要初始化 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 的初始库存信息:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Ehmset ticket_hash_key “ticket_total_nums” 10000 “ticket_sold_nums” 0u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Cstrongu003E响应用户信息u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E我们开启一个 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003EHTTPu003Cu002Fiu003E 服务,监听在一个端口上:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Epackage mainu003Cbru003E…u003Cbru003Eu003Cstrong toutiao-origin=”span”u003Efuncu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003Emainu003Cu002Fstrongu003E {u003Cbru003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E.HandleFunc(“u002Fbuyu002Fticket”, handleReq)u003Cbru003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E.Lu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003EtenAndServe(“:u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E05”, nil)u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003E上面u003Cu002Fiu003E我们做完了所有的初始化工作,接下来 handleReq 的逻辑非常清晰,判断是否抢票成功,返回给用户信息就可以了。u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Epackage mainu003Cbru003Eu002Fu002F处理请求函数,根据请求将响应结果信息写入日志u003Cbru003Eu003Cstrong toutiao-origin=”span”u003Efuncu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003EhandleRequ003Cu002Fstrongu003E(w u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E.ResponseWriter, r *u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E.Request) {u003Cbru003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003EConn := u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003EPool.Getu003Cbru003ELogMsg := “”u003Cbru003E<-doneu003Cbru003Eu002Fu002F全局读写锁u003Cbru003Eif localSpike.LocalDeductionStock && remoteSpike.RemoteDeductionStock(u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003Eredu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003EConn) {u003Cbru003Eutil.RespJson(w, 1, “抢票成功”, nil)u003Cbru003ELogMsg = LogMsg + “result:1,localSales:” + strconv.FormatInt(localSpike.LocalSalesVolume, 10)u003Cbru003E} else {u003Cbru003Eutil.RespJson(w, -1, “已售罄”, nil)u003Cbru003ELogMsg = LogMsg + “result:0,localSales:” + strconv.FormatInt(localSpike.LocalSalesVolume, 10)u003Cbru003E}u003Cbru003Edone <- 1u003Cbru003Eu003Cbru003Eu002Fu002F将抢票状态写入到log中u003Cbru003EwriteLog(LogMsg, “.u002Fstat.log”)u003Cbru003E}u003Cbru003Eu003Cbru003Eu003Cstrong toutiao-origin=”span”u003Efuncu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span”u003EwriteLogu003Cu002Fstrongu003E(msg string, logPath string) {u003Cbru003Efd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-3″u003EAPPu003Cu002Fiu003EEND, 0644)u003Cbru003Edefer fd.Closeu003Cbru003Econtent := strings.Join([]string{msg, “\r\n”}, “”)u003Cbru003Ebuf := byte(content)u003Cbru003Efd.Write(buf)u003Cbru003E}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E前边提到我们扣库存时要考虑竞态条件,我们这里是使用 Channel 避免并发的读写,保证了请求的高效顺序执行。我们将接口的返回信息写入到了 .u002Fstat.log 文件方便做压测统计。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E单机服务压测u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E开启服务,我们使用 AB 压测工具进行测试:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eab -n 10000 -c 100 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E:u002Fu002F127.0.0.1:u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E05u002Fbuyu002Fticketu003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E下面是我本地低配 Mac 的压测信息:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003EThu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E ApacheBench, Version 2.3 <$revu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003Eion: 1826891=””>u003Cbru003ECopyright 1996 Adam Twu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003Es, Zeus Technology Ltd, u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E:u002Fu002Fu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003Ewww.u003Cu002Fiu003Ezeustech.netu002Fu003Cbru003ELicensed to u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003ETheu003Cu002Fiu003E Apache Software Foundation, u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003Ehttpu003Cu002Fiu003E:u002Fu002Fu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003Ewww.u003Cu002Fiu003Eapache.orgu002Fu003Cbru003Eu003Cbru003EBenchmarking 127.0.0.1 (be patient)u003Cbru003ECompleted 1000 requestsu003Cbru003ECompleted 2000 requestsu003Cbru003ECompleted u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E00 requestsu003Cbru003ECompleted 4000 requestsu003Cbru003ECompleted 5000 requestsu003Cbru003ECompleted 6000 requestsu003Cbru003ECompleted 7000 requestsu003Cbru003ECompleted 8000 requestsu003Cbru003ECompleted 9000 requestsu003Cbru003ECompleted 10000 requestsu003Cbru003EFinu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003Ehed 10000 requestsu003Cbru003Eu003Cbru003Eu003Cbru003EServer Software:u003Cbru003EServer Hostname: 127.0.0.1u003Cbru003EServer Port: u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E30u003Cu002Fiu003E05u003Cbru003Eu003Cbru003EDocument Path: u002Fbuyu002Fticketu003Cbru003EDocument Length: 29 bytesu003Cbru003Eu003Cbru003EConcurrency Level: 100u003Cbru003ETime taken for tests: 2.339 secondsu003Cbru003EComplete requests: 10000u003Cbru003EFailed requests: 0u003Cbru003ETotal transferred: 1370000 bytesu003Cbru003EHTML transferred: 290000 bytesu003Cbru003ERequests per second: 4275.96 [#u002Fsec] (mean)u003Cbru003ETime per request: 23.387 [ms] (mean)u003Cbru003ETime per request: 0.234 [ms] (mean, across all concurrent requests)u003Cbru003ETransfer rate: 572.08 [Kbytesu002Fsec] receivedu003Cbru003Eu003Cbru003EConnection Times (ms)u003Cbru003Emin mean[+u002F-sd] median maxu003Cbru003EConnect: 0 8 14.7 6 223u003Cbru003EProcessing: 2 15 17.6 11 232u003Cbru003EWaiting: 1 11 13.5 8 225u003Cbru003ETotal: 7 23 22.8 18 239u003Cbru003Eu003Cbru003EPercentage u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eofu003Cu002Fiu003E the requests served within a certain time (ms)u003Cbru003E50% 18u003Cbru003E66% 24u003Cbru003E75% 26u003Cbru003E80% 28u003Cbru003E90% 33u003Cbru003E95% 39u003Cbru003E98% 45u003Cbru003E99% 54u003Cbru003E100% 239 (longest request)u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E根据指标显示,我单机每秒就能处理 4000+ 的请求,正常服务器都是多核配置,处理 1W+ 的请求根本没有问题。u003Cu002Fpu003Eu003Cpu003E而且查看日志发现整个服务过程中,请求都很正常,流量均匀,u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 也很正常:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Eu002Fu002Fstat.logu003Cbru003E…u003Cbru003Eresult:1,localSales:145u003Cbru003Eresult:1,localSales:146u003Cbru003Eresult:1,localSales:147u003Cbru003Eresult:1,localSales:148u003Cbru003Eresult:1,localSales:149u003Cbru003Eresult:1,localSales:150u003Cbru003Eresult:0,localSales:151u003Cbru003Eresult:0,localSales:152u003Cbru003Eresult:0,localSales:153u003Cbru003Eresult:0,localSales:154u003Cbru003Eresult:0,localSales:156u003Cbru003E…u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Cstrongu003E总结回顾u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E总体来说,秒杀系统是非常复杂的。我们这里只是简单介绍模拟了一下单机如何优化到高性能,集群如何避免单点故障,保证订单不超卖、不少卖的一些策略u003Cu002Fpu003Eu003Cpu003E完整的订单系统还有订单进度的查看,每台服务器上都有一个任务,定时的从总库存同步余票和库存信息展示给用户,还有用户在订单有效期内不支付,释放订单,补充到库存等等。u003Cu002Fpu003Eu003Cpu003E我们实现了高并发抢票的核心逻辑,可以说系统设计的非常的巧妙,巧妙的避开了对 DB 数据库 IO 的操作。u003Cu002Fpu003Eu003Cpu003E对 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 网络 IO 的高并发请求,几乎所有的计算都是在内存中完成的,而且有效的保证了不超卖、不少卖,还能够容忍部分机器的宕机。u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E我觉得其中有两点特别值得学u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E习总u003Cu002Fiu003E结:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E1. 负载均衡,分而治之u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E通过负载均衡,将不同的流量划分到不同的机器上,每台机器处理好自己的请求,将自己的性能发挥到极致。u003Cu002Fpu003Eu003Cpu003E这样系统的整体也就能承受极高的并发了,就像工作的一个团队,每个人都将自己的价值发挥到了极致,团队成长自然是很大的。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E2. 合理的使用并发和异步u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E自 Epoll 网络架构模型解决了 c10k 问题以来,异步越来越被服务端开发人员所接受,能够用异步来做的工作,就用异步来做,在功能拆解上能达到意想不到的效果。u003Cu002Fpu003Eu003Cpu003E这点在 Nginx、Node.JS、u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003ERedu003Cu002Fiu003Eu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003Eisu003Cu002Fiu003E 上都能体现,他们处理网络请求使用的 Epoll 模型,用实践告诉了我们u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-1″u003E单线u003Cu002Fiu003E程依然可以发挥强大的威力。u003Cu002Fpu003Eu003Cpu003E服务器已经进入了多核时代,Go 语言这种天生为并发而生的语言,完美的发挥了服务器多核优势,很多可以并发处理的任务都可以使用并发来解决,比如 Go 处理 u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-5″u003EHTTPu003Cu002Fiu003E 请求时每个请求都会在一个 Goroutine 中执行。u003Cu002Fpu003Eu003Cpu003E总之,怎样合理的压榨 CPU,让其发挥出应有的价值,是我们一直需要探索学习的方向。u003Cu002Fpu003Eu003Cp class=”pgc-end-source”u003E-END-u003Cu002Fpu003E”

原文始发于:12306 的架构到底有多牛逼?

主题测试文章,只做测试使用。发布者:逗乐男神i,转转请注明出处:http://www.cxybcw.com/17806.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code