高并发系统如何做到高可用
原理
减而治之:cdn;nginx限流,异步队列(高并发流量变成均摊流量)
分而治之:lvs+nginx负载均衡
特征
写强一致性(不能超卖)
读弱一致性(读可能有库存,但是不能下单)
核心实现
读服务实现
写服务实现
排队进度查询实现
链路流量优化如何做(lvs层, sever层,减少流量涌入)
流量漏斗
请求链路中,每层服务削峰,限流,自动降级,熔断
秒杀系统的实现【滴滴讲师的一个实例】
Ab压测接口可以承受的最大qps值
-n访问1000次, -c并发100个
Requests per second 是吞吐量,是以秒为单位的,所以这个值可以了;结尾qps。
限流
Tengine版本采用http_limit_req_module进行限制
具体连接请参考 http://tengine.taobao.org/doc...
和官方nginx类似,不过支持多个变量,并且支持多个limit_req_zone的设置。比如:
limit_req_zone $binary_remote_addr zone=one:3m rate=1r/s;
limit_req_zone $binary_remote_addr $uri zone=two:3m rate=1r/s; # $uri:不带客户端请求参数
limit_req_zone $binary_remote_addr $request_uri zone=thre:3m rate=1r/s; # $request_uri:带客户端请求参数
上面的第二个指令表示当相同的ip地址并且访问相同的uri,会导致进入limit req的限制(每秒1个请求)。
Nginx官方版本限制IP的连接和并发分别有两个模块:
- limit_req_zone 用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 "leaky bucket"
- limit_req_conn 用来限制同一时间连接数,即并发限制
其中limit_req_conn模块可以根据源IP限制单用户并发访问的连接数或连接到该服务的总并发连接数。
两种算法的最大区别:令牌桶算法可以应对突发流量,漏铜算法不行。
可以参考学习:https://www.cnblogs.com/bigli...
漏桶算法
我们假设系统是一个漏桶,当请求到达时,就是往漏桶里“加水”,而当请求被处理掉,就是水从漏桶的底部漏出。水漏出的速度是固定的,当“加水”太快,桶就会溢出,也就是“拒绝请求”。从而使得桶里的水的体积不可能超出桶的容量。主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。
示例如下:
http {
limit_conn_log_level error;
limit_conn_status 503;
limit_conn_zone **$binary_remote_addr** zone=one:10m;
limit_conn_zone **$server_name** zone=perserver:10m;
limit_req_zone **$binary_remote_addr** zone=allips:100m rate=10r/s; //其中$binary_remote_addr有时需要根据自己已有的**log\_format变量**配置进行替换
server {
…………………….
limit_conn one 100;
limit_conn perserver 1000;
limit_req zone=allips burst=5 nodelay;
………………….
}
}
参数解释:
Zone=one或allips 表示设置了名为“one”或“allips”的存储区,大小为10兆字节
rate=10r/s 的意思是允许1秒钟不超过10个请求
burst=5 表示最大延迟请求数量不大于5。 如果太过多的请求被限制延迟是不需要的 ,这时需要使用nodelay参数,服务器会立刻返回503状态码。
limit_conn one 100表示限制每个客户端IP的最大并发连接数100
limit_conn perserver 1000表示该服务提供的总连接数不得超过1000,超过请求的会被拒绝
示例如下:
http {
limit_req_zone $binary_remote_addr zone=one:100m rate=10r/m;
server {
limit_req zone=one burst=1 nodelay;
}
}
配置解释
rate=10r/m
允许1秒钟不超过1个请求,最大延迟请求数量不大于5.
如果请求不需要被延迟,添加nodelay参数,服务器会立刻返回503状态码。如果没有该字段会造成大量的tcp连接请求等待。
http{
limit_zone one $binary_remote_addr 10m;
server
{
......
limit_conn one 1;
......
}
}
这里的 one 是声明一个 limit_zone 的名字,$binary_remote_addr是替代 $remore_addr 的变量,10m 是会话状态储存的空间。
limit_conn one 1
限制客户端并发连接数量为1, allow only one connection per an IP address at a time(每次).
limit_req_zone的功能是通过漏桶原理来限制用户的连接频率,(这个模块允许你去限制单个地址指定会话或特殊需要的请求数 ) 。
而 limit_zone 功能是限制一个客户端的并发连接数。(这个模块可以限制单个地址的指定会话或者特殊情况的并发连接数) 。
一个是限制并发连接一个是限制连接频率,表面上似乎看不出来有什么区别,那就看看实际的效果吧~~~
在我的测试机上面加上这两个参数下面是我的部分配置文件
http{
limit_zone one $binary_remote_addr 10m;
#limit_req_zone $binary_remote_addr zone=req_one:10m rate=1r/s;
server
{
......
limit_conn one 1;
#limit_req zone=req_one burst=120;
......
}
}
配置解释
limit_zone one $binary_remote_addr 10m;
这里的 one 是声明一个 limit_zone 的名字,$binary_remote_addr是替代 $remore_addr 的变量,10m是会话状态储存的空间
limit_conn one 1
限制客户端并发连接数量为1
limit_zone两种工作情况
limit_reqzone=one burst=10;
默认情况下是这样配置的,这样每个请求就会有一个delay时间,
limit_req_zone$binary_remote_addr zone=one:100m rate=10r/m;
就是每分钟有10个令牌供用户使用,按照a的配置情况,就会有一个delay,每个请求时间就是60/10,那每个请求时间就是6s。
limit_reqzone=one burst=10 nodelay;
添加nodelay配置,这样就是根据你的网络状况访问,一分钟访问够10次后,服务器直接返回503。
limit_req_zone$binary_remote_addr zone=one:100m rate=10r/m;
就是每分钟有10个令牌供用户使用,按照b的配置情况,就会根据网络情况访问url,如果一分钟超过10个令牌,服务器返回503,等待下一个一分钟领取访问令牌。
rate=10r/m
每个地址每分钟只能请求10次,也就是说根据漏桶原理burst=1 一共有1块令牌,并且每分钟只新增10块令牌,
1块令牌发完后多出来的那些请求就会返回503。
加上 nodelay之后超过 burst大小的请求就会直接返回503,如果没有该字段会造成大量的tcp连接请求等待。
http{
...
#定义一个名为allips的limit_req_zone用来存储session,大小是10M内存,
#以$binary_remote_addr 为key,限制平均每秒的请求为20个,
#1M能存储16000个状态,rete的值必须为整数,
#如果限制两秒钟一个请求,可以设置成30r/m
limit_req_zone $binary_remote_addr zone=allips:10m rate=20r/s;
...
server{
...
location {
...
#限制每ip每秒不超过20个请求,漏桶数burst为5
#brust的意思就是,如果第1秒、2,3,4秒请求为19个,
#第5秒的请求为25个是被允许的。
#但是如果你第1秒就25个请求,第2秒超过20的请求返回503错误。
#nodelay,如果不设置该选项,严格使用平均速率限制请求数,
#第1秒25个请求时,5个请求放到第2秒执行,
#设置nodelay,25个请求将在第1秒执行。
limit_req zone=allips burst=5 nodelay;
...
}
...
}
...
}
cdn
负载均衡
ip hash的一个问题,某个学校如果总的 ip 指定到 了某台机器,那么这个机器的访问量就会很大。
消息队列
由于在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达DB,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。通过使用消息队列,我们可以异步处理请求,从而缓解系统的压力。
流量预估
秒杀系统的特点难点
秒杀系统的策略
秒杀系统,和其他服务隔离部署,所以也是一个单独的服 务,秒杀服务。
实现需求
秒杀系统的实现-扣库存方案
扣库存的几种方式
● 下单扣库存
当买家下单后,在商品的总库存中减去买家购买数量。下单减库存是最简单的减库存方式,也是控制最精 确的一种,下单时直接通过数据库的事务机制控制商品 库存,这样一定不会出现超卖的情况。但是你要知道, 有些人下完单可能并不会付款。
● 付款扣库存
即买家下单后,并不立即减库存,而是等到 有用户付款后才真正减库存,否则库存一直保留给其他 买家。但因为付款时才减库存,如果并发比较高,有可 能出现买家下单后付不了款的情况,因为可能商品已经 被其他人买走了。
● 预扣库存
这种方式相对复杂一些,买家下单后,库存为 其保留一定的时间(如 10 分钟),超过这个时间,库存 将会自动释放,释放后其他买家就可以继续购买。在买 家付款前,系统会校验该订单的库存是否还有保留:如 果没有保留,则再次尝试预扣;如果库存不足(也就是 预扣失败)则不允许继续付款;如果预扣成功,则完成 付款并实际地减去库存。
以上减库存的几种方式存在的问题
由于购物过程中存在两步或者多步的操作,因此在不同 的操作步骤中减库存,就会存在一些可能被恶意买家利 用的漏洞,例如发生恶意下单的情况。
假如我们采用“下单减库存”的方式,即用户下单后就减去 库存,正常情况下,买家下单后付款 的概率会很高,所以不会有太大问题。但是有一种场景例 外,就是当卖家参加某个活动时,此 时活动的有效时间是商品的⻩金售卖时间,如果有竞争对手 通过恶意下单的方式将该卖家的商 品全部下单,让这款商品的库存减为零,那么这款商品就不 能正常售卖了。要知道,这些恶意 下单的人是不会真正付款的,这正是“下单减库存”方式的 不足之处。
付款减库存 既然“下单减库存”可能导致恶意下单,从而影响卖家的商 品销售,那么有没有办法解决呢? 你可能会想,采用“付款减库存”的方式是不是就可以了? 的确可以。但是,“付款减库存”又 会导致另外一个问题:库存超卖。
既然“下单减库存”和“付款减库存”都有缺点,我们能否 把两者相结合,将两次操作进行前后关联起来,下单时 先预扣,在规定时间内不付款再释放库存,即采用“预扣 库存”这种方式呢?
这种方案确实可以在一定程度上缓解上面的问题。但是否就 彻底解决了呢?其实没有!针对恶 意下单这种情况,虽然把有效的付款时间设置为 10 分钟, 但是恶意买家完全可以在 10 分钟 后再次下单,或者采用一次下单很多件的方式把库存减完。 针对这种情况,解决办法还是要结 合安全和反作弊的措施来制止。
如何解决下单扣库存问题
给经常下单不付款的买家进行识别打标(可以在被打标的买 家下单时不减库存)、给某些类目 设置最大购买件数(例如,参加活动的商品一人最多只能买 3 件),以及对重复下单不付款的
操作进行次数限制等。
服务器性能优化(极限压榨cpu)
重点是:避免不必要的上下文切换。
进程,线程之间的切换,系统调用,cpu都需要耗费资源调度,这些是无效的动作。真正执行程序,才是有效动作,
线程,协程的使用,可以避免这两者。
所以可以看出,Php 并不是做扣库存,比较好比较合适的一⻔语言。
为了达到极致性能,php 可以做的是,减少阻塞时 IO(磁盘 文件读写,远程调用)。
redis,单机单服务可以实现 10w qps。
扣库存实现
Go 是单进程,php 是多进程。 代码实现:
redis+lua 脚本,防止超卖。 商品信息和抢购进度的实现
商品标题图片详情,这种不经常改变的,相对静态信息的, 可以做静态化 +cdn
价格,促销活动,库存,这些相对动态信息,可以做热点缓 存。
数组和 hash 表的数据结构区别,
最大区别:方式不同,数组通过 key 查找 value,hash 通过函数查找。
https://www.cnblogs.com/chenj...
减库存成功的用户,存到数组 A 里面,依次存入。 数组是索引数组,key 是索引值,值是 uid。
hash 表,key 是 uid,value 是数组 A 的索引值。
每次消费数据,就是从头到尾去消费,记录起来最近消费的 索引值,为 X,
得到排队进度,
数组和 hash 是在内存中存储的,数组和 hash 表的查询复杂度都是 O1,非常快。
扣库存,放少部分流量,到统一扣库存的地方去,进行操 作。
写订单,异步消费。
请求链路实现漏斗流量
举例:12306的验证码,就是为了削峰, 增加用户操作难 度,降低请求次数。也可以防机器人破解验证。
对访问库存数,这里注意限流,但不能限流太久,注意时间 间隔。允许用户低频次的读取数据,
读写分离,一主多从,可以有效大幅度提高读的访问能力
海量并发的处理本质: 分治 ; 有效压榨 cpu。 单服务的性能提升,重点是压榨 cpu 性能。减少 IO(网络访
问,磁盘读写),减少 cpu 上下文切换, Cpu 上下文切换是无效的运算。
单进程和单线程,保证不会切换上下文,但是遇到 IO 可能 会阻塞,异步
多核 cpu,单机部署多实例(多个单进程单线程),有效利 用多核 cpu。