"12306”的架构到底有多牛逼?( 四 )

hmset ticket_hash_key "ticket_total_nums" 10000 "ticket_sold_nums" 04.3 响应用户信息我们开启一个http服务,监听在一个端口上:
package main...func main() {http.HandleFunc("/buy/ticket", handleReq)http.ListenAndServe(":3005", nil)}上面我们做完了所有的初始化工作,接下来handleReq的逻辑非常清晰,判断是否抢票成功,返回给用户信息就可以了 。
package main//处理请求函数,根据请求将响应结果信息写入日志func handleReq(w http.ResponseWriter, r *http.Request) {redisConn := redisPool.Get()LogMsg := ""<-done//全局读写锁if localSpike.LocalDeductionStock() && remoteSpike.RemoteDeductionStock(redisConn) {util.RespJson(w, 1,"抢票成功", nil)LogMsg = LogMsg + "result:1,localSales:" + strconv.FormatInt(localSpike.LocalSalesVolume, 10)} else {util.RespJson(w, -1, "已售罄", nil)LogMsg = LogMsg + "result:0,localSales:" + strconv.FormatInt(localSpike.LocalSalesVolume, 10)}done <- 1//将抢票状态写入到log中writeLog(LogMsg, "./stat.log")}func writeLog(msg string, logPath string) {fd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)defer fd.Close()content := strings.Join([]string{msg, "rn"}, "")buf := []byte(content)fd.Write(buf)}前边提到我们扣库存时要考虑竞态条件,我们这里是使用channel避免并发的读写,保证了请求的高效顺序执行 。我们将接口的返回信息写入到了./stat.log文件方便做压测统计 。
4.4 单机服务压测开启服务,我们使用ab压测工具进行测试:
ab -n 10000 -c 100 http://127.0.0.1:3005/buy/ticket下面是我本地低配mac的压测信息
This is ApacheBench, Version 2.3 <$Revision: 1826891 $>Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking 127.0.0.1 (be patient)Completed 1000 requestsCompleted 2000 requestsCompleted 3000 requestsCompleted 4000 requestsCompleted 5000 requestsCompleted 6000 requestsCompleted 7000 requestsCompleted 8000 requestsCompleted 9000 requestsCompleted 10000 requestsFinished 10000 requestsServer Software:Server Hostname:127.0.0.1Server Port:3005Document Path:/buy/ticketDocument Length:29 bytesConcurrency Level:100Time taken for tests:2.339 secondsComplete requests:10000Failed requests:0Total transferred:1370000 byteshtml transferred:290000 bytesRequests per second:4275.96 [#/sec] (mean)Time per request:23.387 [ms] (mean)Time per request:0.234 [ms] (mean, across all concurrent requests)Transfer rate:572.08 [Kbytes/sec] receivedConnection Times (ms)minmean[+/-sd] medianmaxConnect:0814.76223Processing:21517.611232Waiting:11113.58225Total:72322.818239Percentage of the requests served within a certain time (ms)50%1866%2475%2680%2890%3395%3998%4599%54 100%239 (longest request)根据指标显示,我单机每秒就能处理4000+的请求,正常服务器都是多核配置,处理1W+的请求根本没有问题 。而且查看日志发现整个服务过程中,请求都很正常,流量均匀,redis也很正常:
//stat.log...result:1,localSales:145result:1,localSales:146result:1,localSales:147result:1,localSales:148result:1,localSales:149result:1,localSales:150result:0,localSales:151result:0,localSales:152result:0,localSales:153result:0,localSales:154result:0,localSales:156...5.总结回顾总体来说,秒杀系统是非常复杂的 。我们这里只是简单介绍模拟了一下单机如何优化到高性能,集群如何避免单点故障,保证订单不超卖、不少卖的一些策略,完整的订单系统还有订单进度的查看,每台服务器上都有一个任务,定时的从总库存同步余票和库存信息展示给用户,还有用户在订单有效期内不支付,释放订单,补充到库存等等 。
我们实现了高并发抢票的核心逻辑,可以说系统设计的非常的巧妙,巧妙的避开了对DB数据库IO的操作,对Redis网络IO的高并发请求,几乎所有的计算都是在内存中完成的,而且有效的保证了不超卖、不少卖,还能够容忍部分机器的宕机 。我觉得其中有两点特别值得学习总结:
负载均衡,分而治之 。通过负载均衡,将不同的流量划分到不同的机器上,每台机器处理好自己的请求,将自己的性能发挥到极致,这样系统的整体也就能承受极高的并发了,就像工作的的一个团队,每个人都将自己的价值发挥到了极致,团队成长自然是很大的 。
合理的使用并发和异步 。自epoll网络架构模型解决了c10k问题以来,异步越来被服务端开发人员所接受,能够用异步来做的工作,就用异步来做,在功能拆解上能达到意想不到的效果,这点在nginx、node.js、redis上都能体现,他们处理网络请求使用的epoll模型,用实践告诉了我们单线程依然可以发挥强大的威力 。


推荐阅读