本文为开源实验性工程:“github.com/GuoZhaoran/spikeSystem”的配套文章,原做者:“绘你一世倾城”,现为:猎豹移动php开发工程师,感谢原做者的技术分享。php
Go语言的出现,让开发高性能、高稳定性服务端系统变的容易,与高贵冷艳的Erlang语言不一样的是,Go语言简单易学,在高性能服务端架构中的应用愈来愈普遍。html
对于即时通信(IM系统、消息推送系统等高性能实时通讯等)等场景下,Go语言已经被愈来愈多的用于核心通讯模块中。本文内容虽是从秒杀系统谈起,并未直接涉及即时通信相关知识,但有关Go的高并发实践,仍然值得广大即时通信技术爱好者们研究和学习,必竟业务能够不一样,但技术都是相通的,或许能为你即时通信系统的高并发架构带来新的思路和灵感。技多不压身,杀望对你有所帮助!nginx
本文所述并不是纯理论水文,均以已付实践,配套源码请见:git
1)主要地址: https://github.com/GuoZhaoran/spikeSystem
2)备用地址: https://github.com/52im/spikeSystem
友情提示:本文适合有必定高并发服务端设计经验的开发者阅读,如您对这方面知识了解太少,请务必先按序阅读如下文章:github
《 新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》
《 一篇读懂分布式架构下的负载均衡技术:分类、原理、算法、常见方案等》
《 重新手到架构师,一篇就够:从100到1000万高并发的架构演进之路》
《 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》
《 快速理解高性能HTTP服务端的负载均衡技术原理》
(本文同步发布于:http://www.52im.net/thread-2771-1-1.html)redis
虽然如今大多数状况下都能订到票,可是放票瞬间即无票的场景,相信你们都深有体会。算法
尤为是春节期间,你们不只使用 12306,还会考虑“智行”和其余的抢票软件,全国上下几亿人在这段时间都在抢票。数据库
“12306 服务”承受着这个世界上任何秒杀系统都没法超越的 QPS,上百万的并发再正常不过了!apache
笔者专门研究了一下“12306”的服务端架构,学习到了其系统设计上不少亮点,在这里和你们分享一下并模拟一个例子:如何在 100 万人同时抢 1 万张火车票时,系统提供正常、稳定的服务。后端
高并发的系统架构都会采用分布式集群部署,服务上层有着层层负载均衡,并提供各类容灾手段(双火机房、节点容错、服务器灾备等)保证系统的高可用,流量也会根据不一样的负载能力和配置策略均衡到不一样的服务器上。
下边是一个简单的示意图:
上图中描述了用户请求到服务器经历了三层的负载均衡,下边分别简单介绍一下这三种负载均衡。
① OSPF(开放式最短链路优先)是一个内部网关协议(Interior Gateway Protocol,简称 IGP)
OSPF 经过路由器之间通告网络接口的状态来创建链路状态数据库,生成最短路径树,OSPF 会自动计算路由接口上的 Cost 值,但也能够经过手工指定该接口的 Cost 值,手工指定的优先于自动计算的值。
OSPF 计算的 Cost,一样是和接口带宽成反比,带宽越高,Cost 值越小。到达目标相同 Cost 值的路径,能够执行负载均衡,最多 6 条链路同时执行负载均衡。
② LVS (Linux Virtual Server)
它是一种集群(Cluster)技术,采用 IP 负载均衡技术和基于内容请求分发技术。
调度器具备很好的吞吐率,将请求均衡地转移到不一样的服务器上执行,且调度器自动屏蔽掉服务器的故障,从而将一组服务器构成一个高性能的、高可用的虚拟服务器。
③ Nginx
想必你们都很熟悉了,是一款很是高性能的 HTTP 代理/反向代理服务器,服务开发中也常用它来作负载均衡。
Nginx 实现负载均衡的方式主要有三种:
1)轮询;
2)加权轮询;
3)IP Hash 轮询。
下面咱们就针对 Nginx 的加权轮询作专门的配置和测试。
Nginx 实现负载均衡经过 Upstream 模块实现,其中加权轮询的配置是能够给相关的服务加上一个权重值,配置的时候可能根据服务器的性能、负载能力设置相应的负载。
下面是一个加权轮询负载的配置,我将在本地的监听 3001-3004 端口,分别配置 1,2,3,4 的权重:
配置负载均衡
upstream load_rule {
server 127.0.0.1:3001 weight=1;
server 127.0.0.1:3002 weight=2;
server 127.0.0.1:3003 weight=3;
server 127.0.0.1:3004 weight=4;
}
...
server {
listen 80;
server_name load_balance.com [url=http://www.load_balance.com]www.load_balance.com[/url];
location / {
proxy_pass http://load_rule;
}
}
我在本地 /etc/hosts 目录下配置了 www.load_balance.com 的虚拟域名地址。
接下来使用 Go 语言开启四个 HTTP 端口监听服务,下面是监听在 3001 端口的 Go 程序,其余几个只须要修改端口便可:
package main
import(
"net/http"
"os"
"strings"
)func main() {
http.HandleFunc("/buy/ticket", handleReq)
http.ListenAndServe(":3001", nil)
}//处理请求函数,根据请求将响应结果信息写入日志
func handleReq(w http.ResponseWriter, r *http.Request) {
failedMsg := "handle in port:"
writeLog(failedMsg, "./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, "\r\n"}, "3001")
buf := []byte(content)
fd.Write(buf)
}
我将请求的端口日志信息写到了 ./stat.log 文件当中,而后使用 AB 压测工具作压测:
ab -n 1000 -c 100 http://www.load_balance.com/buy/ticket
统计日志中的结果,3001-3004 端口分别获得了 100、200、300、400 的请求量。
这和我在 Nginx 中配置的权重占比很好的吻合在了一块儿,而且负载后的流量很是的均匀、随机。
具体的实现你们能够参考 Nginx 的 Upsteam 模块实现源码,这里推荐一篇文章《Nginx 中 Upstream 机制的负载均衡》。
回到咱们最初提到的问题中来:火车票秒杀系统如何在高并发状况下提供正常、稳定的服务呢?
从上面的介绍咱们知道用户秒杀流量经过层层的负载均衡,均匀到了不一样的服务器上,即便如此,集群中的单机所承受的 QPS 也是很是高的。如何将单机性能优化到极致呢?
要解决这个问题,咱们就要想明白一件事:通常订票系统要处理生成订单、减扣库存、用户支付这三个基本的阶段。
咱们系统要作的事情是要保证火车票订单不超卖、很多卖,每张售卖的车票都必须支付才有效,还要保证系统承受极高的并发。
这三个阶段的前后顺序该怎么分配才更加合理呢?咱们来分析一下。。。
当用户并发请求到达服务端时,首先建立订单,而后扣除库存,等待用户支付。
这种顺序是咱们通常人首先会想到的解决方案,这种状况下也能保证订单不会超卖,由于建立订单以后就会减库存,这是一个原子操做。
可是这样也会产生一些问题:
1)在极限并发状况下,任何一个内存操做的细节都相当影响性能,尤为像建立订单这种逻辑,通常都须要存储到磁盘数据库的,对数据库的压力是可想而知的;
2)若是用户存在恶意下单的状况,只下单不支付这样库存就会变少,会少卖不少订单,虽然服务端能够限制 IP 和用户的购买订单数量,这也不算是一个好方法。
若是等待用户支付了订单在减库存,第一感受就是不会少卖。可是这是并发架构的大忌,由于在极限并发状况下,用户可能会建立不少订单。
当库存减为零的时候不少用户发现抢到的订单支付不了了,这也就是所谓的“超卖”。也不能避免并发操做数据库磁盘 IO。
从上边两种方案的考虑,咱们能够得出结论:只要建立订单,就要频繁操做数据库 IO。
那么有没有一种不须要直接操做数据库 IO 的方案呢,这就是预扣库存。先扣除了库存,保证不超卖,而后异步生成用户订单,这样响应给用户的速度就会快不少;那么怎么保证很多卖呢?用户拿到了订单,不支付怎么办?
咱们都知道如今订单都有有效期,好比说用户五分钟内不支付,订单就失效了,订单一旦失效,就会加入新的库存,这也是如今不少网上零售企业保证商品很多卖采用的方案。
订单的生成是异步的,通常都会放到 MQ、Kafka 这样的即时消费队列中处理,订单量比较少的状况下,生成订单很是快,用户几乎不用排队。
从上面的分析可知,显然预扣库存的方案最合理。咱们进一步分析扣库存的细节,这里还有很大的优化空间,库存存在哪里?怎样保证高并发下,正确的扣库存,还能快速的响应用户请求?
在单机低并发状况下,咱们实现扣库存一般是这样的:
为了保证扣库存和生成订单的原子性,须要采用事务处理,而后取库存判断、减库存,最后提交事务,整个流程有不少 IO,对数据库的操做又是阻塞的。
这种方式根本不适合高并发的秒杀系统。接下来咱们对单机扣库存的方案作优化:本地扣库存。
咱们把必定的库存量分配到本地机器,直接在内存中减库存,而后按照以前的逻辑异步建立订单。
改进过以后的单机系统是这样的:
这样就避免了对数据库频繁的 IO 操做,只在内存中作运算,极大的提升了单机抗并发的能力。
可是百万的用户请求量单机是不管如何也抗不住的,虽然 Nginx 处理网络请求使用 Epoll 模型,c10k 的问题在业界早已获得了解决。
可是 Linux 系统下,一切资源皆文件,网络请求也是这样,大量的文件描述符会使操做系统瞬间失去响应。
上面咱们提到了 Nginx 的加权均衡策略,咱们不妨假设将 100W 的用户请求量平均均衡到 100 台服务器上,这样单机所承受的并发量就小了不少。
而后咱们每台机器本地库存 100 张火车票,100 台服务器上的总库存仍是 1 万,这样保证了库存订单不超卖。
下面是咱们描述的集群架构:
问题接踵而至,在高并发状况下,如今咱们还没法保证系统的高可用,假如这 100 台服务器上有两三台机器由于扛不住并发的流量或者其余的缘由宕机了。那么这些服务器上的订单就卖不出去了,这就形成了订单的少卖。
要解决这个问题,咱们须要对总订单量作统一的管理,这就是接下来的容错方案。服务器不只要在本地减库存,另外要远程统一减库存。
有了远程统一减库存的操做,咱们就能够根据机器负载状况,为每台机器分配一些多余的“Buffer 库存”用来防止机器中有机器宕机的状况。
咱们结合下面架构图具体分析一下:
咱们采用 Redis 存储统一库存,由于 Redis 的性能很是高,号称单机 QPS 能抗 10W 的并发。
在本地减库存之后,若是本地有订单,咱们再去请求 Redis 远程减库存,本地减库存和远程减库存都成功了,才返回给用户抢票成功的提示,这样也能有效的保证订单不会超卖。
当机器中有机器宕机时,由于每一个机器上有预留的 Buffer 余票,因此宕机机器上的余票依然可以在其余机器上获得弥补,保证了很多卖。
Buffer 余票设置多少合适呢,理论上 Buffer 设置的越多,系统容忍宕机的机器数量就越多,可是 Buffer 设置的太大也会对 Redis 形成必定的影响。
虽然 Redis 内存数据库抗并发能力很是高,请求依然会走一次网络 IO,其实抢票过程当中对 Redis 的请求次数是本地库存和 Buffer 库存的总量。
由于当本地库存不足时,系统直接返回用户“已售罄”的信息提示,就不会再走统一扣库存的逻辑。
这在必定程度上也避免了巨大的网络请求量把 Redis 压跨,因此 Buffer 值设置多少,须要架构师对系统的负载能力作认真的考量。
Go 语言原生为并发设计,我采用 Go 语言给你们演示一下单机抢票的具体流程。
Go 包中的 Init 函数先于 Main 函数执行,在这个阶段主要作一些准备性工做。
咱们系统须要作的准备工做有:初始化本地库存、初始化远程 Redis 存储统一库存的 Hash 键值、初始化 Redis 链接池。
另外还须要初始化一个大小为 1 的 Int 类型 Chan,目的是实现分布式锁的功能。
也能够直接使用读写锁或者使用 Redis 等其余的方式避免资源竞争,但使用 Channel 更加高效,这就是 Go 语言的哲学:不要经过共享内存来通讯,而要经过通讯来共享内存。
Redis 库使用的是 Redigo,下面是代码实现:
...
//localSpike包结构体定义
packagelocalSpiketypeLocalSpike struct{
LocalInStock int64
LocalSalesVolume int64
}
...
//remoteSpike对hash结构的定义和redis链接池
packageremoteSpike
//远程订单存储健值
typeRemoteSpikeKeys struct{
SpikeOrderHashKey string//redis中秒杀订单hash结构key
TotalInventoryKey string//hash结构中总订单库存key
QuantityOfOrderKey string//hash结构中已有订单数量key
}//初始化redis链接池
funcNewPool() *redis.Pool {
return&redis.Pool{
MaxIdle: 10000,
MaxActive: 12000, // max number of connections
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", ":6379")
iferr != nil{
panic(err.Error())
}
returnc, err
},
}
}
...
funcinit() {
localSpike = localSpike2.LocalSpike{
LocalInStock: 150,
LocalSalesVolume: 0,
}
remoteSpike = remoteSpike2.RemoteSpikeKeys{
SpikeOrderHashKey: "ticket_hash_key",
TotalInventoryKey: "ticket_total_nums",
QuantityOfOrderKey: "ticket_sold_nums",
}
redisPool = remoteSpike2.NewPool()
done = make(chanint, 1)
done <- 1
}
本地扣库存逻辑很是简单,用户请求过来,添加销量,而后对比销量是否大于本地库存,返回 Bool 值:
package localSpike
//本地扣库存,返回bool值
func(spike *LocalSpike) LocalDeductionStock() bool{
spike.LocalSalesVolume = spike.LocalSalesVolume + 1
returnspike.LocalSalesVolume < spike.LocalInStock
}
注意这里对共享数据 LocalSalesVolume 的操做是要使用锁来实现的,可是由于本地扣库存和统一扣库存是一个原子性操做,因此在最上层使用 Channel 来实现,这块后边会讲。
统一扣库存操做 Redis,由于 Redis 是单线程的,而咱们要实现从中取数据,写数据并计算一些列步骤,咱们要配合 Lua 脚本打包命令,保证操做的原子性:
package remoteSpike
......
constLuaScript = `
local ticket_key = KEYS[1]
local ticket_total_key = ARGV[1]
local ticket_sold_key = ARGV[2]
local ticket_total_nums = tonumber(redis.call('HGET', ticket_key, ticket_total_key))
local ticket_sold_nums = tonumber(redis.call('HGET', ticket_key, ticket_sold_key))
-- 查看是否还有余票,增长订单数量,返回结果值
if(ticket_total_nums >= ticket_sold_nums) then
return redis.call('HINCRBY', ticket_key, ticket_sold_key, 1)
end
return 0
`
//远端统一扣库存
func(RemoteSpikeKeys *RemoteSpikeKeys) RemoteDeductionStock(conn redis.Conn) bool{
lua := redis.NewScript(1, LuaScript)
result, err := redis.Int(lua.Do(conn, RemoteSpikeKeys.SpikeOrderHashKey, RemoteSpikeKeys.TotalInventoryKey, RemoteSpikeKeys.QuantityOfOrderKey))
iferr != nil{
returnfalse
}
returnresult != 0
}
咱们使用 Hash 结构存储总库存和总销量的信息,用户请求过来时,判断总销量是否大于库存,而后返回相关的 Bool 值。
在启动服务以前,咱们须要初始化 Redis 的初始库存信息:
1hmset ticket_hash_key "ticket_total_nums"10000 "ticket_sold_nums"0
咱们开启一个 HTTP 服务,监听在一个端口上:
package main
...
funcmain() {
http.HandleFunc("/buy/ticket", handleReq)
http.ListenAndServe(":3005", nil)
}
上面咱们作完了全部的初始化工做,接下来 handleReq 的逻辑很是清晰,判断是否抢票成功,返回给用户信息就能够了。
package main
//处理请求函数,根据请求将响应结果信息写入日志
funchandleReq(w http.ResponseWriter, r *http.Request) {
redisConn := redisPool.Get()
LogMsg := ""
<-done
//全局读写锁
iflocalSpike.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")
}funcwriteLog(msg string, logPath string) {
fd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
deferfd.Close()
content := strings.Join([]string{msg, "\r\n"}, "")
buf := []byte(content)
fd.Write(buf)
}
前边提到咱们扣库存时要考虑竞态条件,咱们这里是使用 Channel 避免并发的读写,保证了请求的高效顺序执行。咱们将接口的返回信息写入到了 ./stat.log 文件方便作压测统计。
开启服务,咱们使用 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, [url= http://www.zeustech.net/] http://www.zeustech.net/[/url]
Licensed to The Apache Software Foundation, [url= http://www.apache.org/] http://www.apache.org/[/url]Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Completed 6000 requests
Completed 7000 requests
Completed 8000 requests
Completed 9000 requests
Completed 10000 requests
Finished 10000 requestsServer Software:
Server Hostname: 127.0.0.1
Server Port: 3005Document Path: /buy/ticket
Document Length: 29 bytesConcurrency Level: 100
Time taken fortests: 2.339 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 1370000 bytes
HTML transferred: 290000 bytes
Requests 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)
min mean[+/-sd] median max
Connect: 0 8 14.7 6 223
Processing: 2 15 17.6 11 232
Waiting: 1 11 13.5 8 225
Total: 7 23 22.8 18 239Percentage of the requests served within a certain time(ms)
50% 18
66% 24
75% 26
80% 28
90% 33
95% 39
98% 45
99% 54
100% 239 (longest request)
根据指标显示,我单机每秒就能处理 4000+ 的请求,正常服务器都是多核配置,处理 1W+ 的请求根本没有问题。
并且查看日志发现整个服务过程当中,请求都很正常,流量均匀,Redis 也很正常:
//stat.log
...
result:1,localSales:145
result:1,localSales:146
result:1,localSales:147
result:1,localSales:148
result:1,localSales:149
result:1,localSales:150
result:0,localSales:151
result:0,localSales:152
result:0,localSales:153
result:0,localSales:154
result:0,localSales:156
...
整体来讲,秒杀系统是很是复杂的。咱们这里只是简单介绍模拟了一下单机如何优化到高性能,集群如何避免单点故障,保证订单不超卖、很多卖的一些策略,完整的订单系统还有订单进度的查看,每台服务器上都有一个任务,定时的从总库存同步余票和库存信息展现给用户,还有用户在订单有效期内不支付,释放订单,补充到库存等等。
咱们实现了高并发抢票的核心逻辑,能够说系统设计的很是的巧妙,巧妙的避开了对 DB 数据库 IO 的操做。
对 Redis 网络 IO 的高并发请求,几乎全部的计算都是在内存中完成的,并且有效的保证了不超卖、很多卖,还可以容忍部分机器的宕机。
我以为其中有两点特别值得学习总结的。
① 负载均衡,分而治之:
经过负载均衡,将不一样的流量划分到不一样的机器上,每台机器处理好本身的请求,将本身的性能发挥到极致。
这样系统的总体也就能承受极高的并发了,就像工做的一个团队,每一个人都将本身的价值发挥到了极致,团队成长天然是很大的。
② 合理的使用并发和异步:
自 Epoll 网络架构模型解决了 c10k 问题以来,异步愈来愈被服务端开发人员所接受,可以用异步来作的工做,就用异步来作,在功能拆解上能达到意想不到的效果。
这点在 Nginx、Node.JS、Redis 上都能体现,他们处理网络请求使用的 Epoll 模型,用实践告诉了咱们单线程依然能够发挥强大的威力。
服务器已经进入了多核时代,Go 语言这种天生为并发而生的语言,完美的发挥了服务器多核优点,不少能够并发处理的任务均可以使用并发来解决,好比 Go 处理 HTTP 请求时每一个请求都会在一个 Goroutine 中执行。
总之,怎样合理的压榨 CPU,让其发挥出应有的价值,是咱们一直须要探索学习的方向。
[1] 通用架构设计相关文章:
《 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》
《 快速理解高性能HTTP服务端的负载均衡技术原理》
《 子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》
《 知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路》
《 新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》
《 阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史》
《 阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路》
《 达达O2O后台架构演进实践:从0到4000高并发请求背后的努力》
《 优秀后端架构师必会知识:史上最全MySQL大表优化方案总结》
《 小米技术分享:解密小米抢购系统千万高并发架构的演进和实践》
《 一篇读懂分布式架构下的负载均衡技术:分类、原理、算法、常见方案等》
《 通俗易懂:如何设计能支撑百万并发的数据库架构?》
《 多维度对比5款主流分布式MQ消息队列,妈妈不再担忧个人技术选型了》
《 重新手到架构师,一篇就够:从100到1000万高并发的架构演进之路》
《 美团技术分享:深度解密美团的分布式ID生成算法》
《 12306抢票带来的启示:看我如何用Go实现百万QPS的秒杀系统(含源码)》
>> 更多同类文章 ……
[2] 有关IM架构设计的文章:
《 浅谈IM系统的架构设计》
《 简述移动端IM开发的那些坑:架构设计、通讯协议和客户端》
《 一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》
《 一套原创分布式即时通信(IM)系统理论架构方案》
《 从零到卓越:京东客服即时通信系统的技术架构演进历程》
《 蘑菇街即时通信/IM服务器开发之架构选择》
《 腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT》
《 微信后台基于时间序的海量数据冷热分级架构设计实践》
《 微信技术总监谈架构:微信之道——大道至简(演讲全文)》
《 如何解读《微信技术总监谈架构:微信之道——大道至简》》
《 快速裂变:见证微信强大后台架构从0到1的演进历程(一)》
《 17年的实践:腾讯海量产品的技术方法论》
《 移动端IM中大规模群消息的推送如何保证效率、实时性?》
《 现代IM系统中聊天消息的同步和存储方案探讨》
《 IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》
《 IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》
《 IM开发基础知识补课(四):正确理解HTTP短链接中的Cookie、Session和Token》
《 WhatsApp技术实践分享:32人工程团队创造的技术神话》
《 微信朋友圈千亿访问量背后的技术挑战和实践总结》
《 王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等》
《 IM系统的MQ消息中间件选型:Kafka仍是RabbitMQ?》
《 腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》
《 以微博类应用场景为例,总结海量社交系统的架构设计步骤》
《 快速理解高性能HTTP服务端的负载均衡技术原理》
《 子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》
《 知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路》
《 IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》
《 微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》
《 微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)》
《 新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》
《 一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践》
《 阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史》
《 阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路》
《 社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等》
《 社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进》
《 社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节》
《 社交软件红包技术解密(四):微信红包系统是如何应对高并发的》
《 社交软件红包技术解密(五):微信红包系统是如何实现高可用性的》
《 社交软件红包技术解密(六):微信红包系统的存储层架构演进实践》
《 社交软件红包技术解密(七):支付宝红包的海量高并发技术实践》
《 社交软件红包技术解密(八):全面解密微博红包技术方案》
《 社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等》
《 即时通信新手入门:一文读懂什么是Nginx?它可否实现IM的负载均衡?》
《 即时通信新手入门:快速理解RPC技术——基本概念、原理和用途》
《 多维度对比5款主流分布式MQ消息队列,妈妈不再担忧个人技术选型了》
《 从游击队到正规军:马蜂窝旅游网的IM系统架构演进之路》
《 IM开发基础知识补课(六):数据库用NoSQL仍是SQL?读这篇就够了!》
>> 更多同类文章 ……
(本文同步发布于:http://www.52im.net/thread-2771-1-1.html)