“12306”的架构到底有多牛逼?

每到节假日期间,一二线城市返乡、外出游玩的人们几乎都面临着一个问题:抢火车票!

12306 抢票,极限并发带来的思考

虽然现在大多数情况下都能订到票,但是放票瞬间即无票的场景,相信大家都深有体会。

尤其是春节期间,大家不仅使用 12306,还会考虑“智行”和其他的抢票软件,全国上下几亿人在这段时间都在抢票。

“12306 服务”承受着这个世界上任何秒杀系统都无法超越的 QPS,上百万的并发再正常不过了!

笔者专门研究了一下“12306”的服务端架构,学习到了其系统设计上很多亮点,在这里和大家分享一下并模拟一个例子:如何在 100 万人同时抢 1 万张火车票时,系统提供正常、稳定的服务。

Github代码地址:

https://github.com/GuoZhaoran/spikeSystem

大型高并发系统架构

高并发的系统架构都会采用分布式集群部署,服务上层有着层层负载均衡,并提供各种容灾手段(双火机房、节点容错、服务器灾备等)保证系统的高可用,流量也会根据不同的负载能力和配置策略均衡到不同的服务器上。

下边是一个简单的示意图:

image

负载均衡简介

上图中描述了用户请求到服务器经历了三层的负载均衡,下边分别简单介绍一下这三种负载均衡。

①OSPF(开放式最短链路优先)是一个内部网关协议(Interior Gateway Protocol,简称 IGP)

OSPF 通过路由器之间通告网络接口的状态来建立链路状态数据库,生成最短路径树,OSPF 会自动计算路由接口上的 Cost 值。

但也可以通过手工指定该接口的 Cost 值,手工指定的优先于自动计算的值。

OSPF 计算的 Cost,同样是和接口带宽成反比,带宽越高,Cost 值越小。

到达目标相同 Cost 值的路径,可以执行负载均衡,最多 6 条链路同时执行负载均衡。

②LVS (Linux Virtual Server)

它是一种集群(Cluster)技术,采用 IP 负载均衡技术和基于内容请求分发技术。

调度器具有很好的吞吐率,将请求均衡地转移到不同的服务器上执行,且调度器自动屏蔽掉服务器的故障,从而将一组服务器构成一个高性能的、高可用的虚拟服务器。

③Nginx

想必大家都很熟悉了,是一款非常高性能的 HTTP 代理/反向代理服务器,服务开发中也经常使用它来做负载均衡。

Nginx 实现负载均衡的方式主要有三种:

  • 轮询
  • 加权轮询
  • IP Hash 轮询

下面我们就针对 Nginx 的加权轮询做专门的配置和测试。

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 www.load_balance.com;
    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, "rn"}, "3001")
    buf := []byte(content)
    fd.Write(buf)
}

我将请求的端口日志信息写到了 ./stat.log 文件当中,然后使用 AB 压测工具做压测:

ab 
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值