总结来源:B站河北王校长https://space.bilibili.com/1069807951/video
文章目录
第一集 商品信息篇
淘宝秒杀架构设计中用户请求后台服务的相关流程和技术要点。具体如下:
- CDN 介绍:存放 CSS、JS、image及静态文本页,动态数据需前端访问后端服务,有缓存从缓存走,无缓存从 DB 加载,在秒杀时作用较大。
- Nginx 转发层:通过 CDN 进入 LVS 加 HA proxy,LVS 做负载均衡,转发层做 IP 限流加转发,靠 URL 判断业务并转发给各个业务层的 Nginx集群 (单品页Ngx集群、结算Ngx集群)。
- Nginx 业务层:有Nginx本地缓存 cache 扛热点数据查询,未命中热点数据通过脚本(Lua+OpenResty)访问淘宝的 KV 存储Tair(从库),若仍未找到则转发至服务层。
- 服务层与数据查询:服务层若未获取数据会查询 Tair 主 DB(SingleFlight),主从 DB 存在更新关系,服务访问主 Tair 可避免从服务器同步数据不及时和防止大量请求打入后端 DB。
- 其他说明:淘宝的 Tengine 是基于 Nginx 加上相关服务和脚本开发开源的引擎;本次未讨论服务中的 JVM catch 及商品信息更新流转等内容,留待下期分解。
- 备注: 然后秒杀地址要做url动态变换
多级缓存架构:
1级缓存-用户侧
2级缓存-CDN
3级缓存-Nginx cache
4级缓存-local cache
5级缓存-redis cache
第二集-热点数据发现(上)
- 热点数据缓存层:
- 热点数据探测:基于时间窗口和频次计算,默认每秒 QPS 大于 5000 认为是热点数据。
- 解决数据倾斜:通过 tair proxy query cache 集群(和tair集群不一样)解决热点 k 数据的倾斜查询问题,每个proxy cache预留热点数据存储区域(hotZone)。
- 各级缓存介绍:
- 前端外部 APP 和 CDN 缓存:较易理解。
- Ngx业务层 local cache:存放热点商品数据,需实时且正确的热点商品探测模块。
- Tair缓存:存储离散型商品数据,有缓存淘汰策略。
- JVM 缓存:存储微服务中零散且不易改变的数据,不涉及缓存一致性。
- 不可预知流量的热点探测:如直播推荐商品引发的搜索,这类不可控流量可能直接打到微服务层面,此为热点探测的第二部分,下期视频再深入探讨。
nginx cache-> tair proxy query cache 如果存在hotzone 直接返回, 如果不存在,查询tair从库集群(tair根据时间窗口进行累加,超过5000会提升达到proxy cache里)->服务主库tair-> server local cache或DB
第三集-热点数据发现(下)
会议讨论了关于应对商品查询突发流量及缓存击穿等问题的解决方案,具体如下:
- 京东 HOTK 组件:
- 组件构成:由数据发送者(S1、S2)、计算者(W1、W2)和存储规则的 etcd 组成。
- 工作流程:微服务发送查询数据,worker 计算,超过规则的热点 k 推送到 Server 和 etcd,还可通过管理界面查看访问次数。
- 应对突发流量:微服务将查询 k 发送给 worker,worker 判断热点 k 后推回 Server,Server 把热点 k 发送到 MQ(server在主tair设置缓存),推送服务从主 tair 读取数据推送到 nginx cache 进行抗压。
- 热点探测与限流:探测出的热点商品 k 可配置规则限流, 阿里Ahas 热点探测部分功能类似京东 HOTK,探测的 k 不含 value。
- 缓存击穿处理:DB 性能不错但面临查询量大的问题,可采用分布式锁等策略,也可使用 single flight,即某时段相同访问只执行一个,其他等待并共享结果。
- 数据同步与循环:主Tair数据同步到从tair,热点数据可从 tair 提升到 query proxy cache 形成良性循环。
其他:1.除了MQ会 还有提前可预知的预热的数据会推送到ngx cache
目前,比如,有赞自研分布式缓存系统zanKV、京东零售开源的热key探测框架(JD-hotkey)、得物热点探测框架(Burning)都是类似这种方案,在客户端进行收集,在聚合中心worker节点上进行热key统计,统计出来的热key可以推送到客户端进行本地缓存。
京东HotKey热点探测
第四集-限流与服务降级(一)
会议讨论了淘宝秒杀架构中的限流问题,包括 Nginx 限流、Lua 加 Redis 限流等内容。具体如下:
- Nginx 限流(第一层限流):
- 连接数限流:通过
http limit.conn model
模块控制连接数,可按 IP 和域名限制。 - 请求限流:
nginx-http-limit-req-model
模块限制请求访问数量,常与连接数限流结合使用。
- 连接数限流:通过
- Lua 加 Redis 限流(第二层限流):
- 数据结构与字段:Redis 中存哈希结构,包含商品原始数量、剩余数量和滑动窗口抢购次数等字段,抢购次数用于限流。
- 滑动窗口限流:配置中心为商品设置每分钟最大抢购次数,Redis 通过 Nginx 的 Lua 脚本进行滑动时间窗口计算,超次数则抢购失败,还需判断剩余数量是否为 0。
- 限流效果:Nginx 限流将 100 万请求简化到 50 万,第二层限流按商品维度再次限流,形成漏斗状。
- 商品售罄处理:商品售罄后不再判断每分钟最高抢购次数,Lua 脚本此处仅限流不扣除剩余数,抢到并支付完成后扣除剩余量,扣到 0 时秒杀结束。
注意点:
1.实际业务中,一般会搭配 SLB-> 网关GW -> Nginx - >API BFF -> Micro Sevice来做各层级的限流或消峰
2.Redis限流缺点:并发量大时,Redis单节点资源消耗较大
Redis+Lua配合进行滑动窗口限流
local redis = require "redis"
local cjson = require "cjson"
-- 连接到Redis
local function connect_redis()
local red = redis.connect("127.0.0.1", 6379)
if not red:is_connected() then
ngx.log(ngx.ERR, "failed to connect to Redis")
return nil
end
return red
end
-- 从Redis获取商品信息
local function get_product_info(red, product_id)
local product_info = red:hgetall(product_id)
if not product_info then
ngx.log(ngx.ERR, "product info not found")
return nil
end
return cjson.decode(product_info)
end
-- 更新Redis中的滑动窗口抢购次数
local function update_window_count(red, product_id, current_time)
local window_size = 60 -- 以秒为单位的时间窗口
local max_requests = 1000 -- 每分钟最大请求次数
local window_start = current_time - (current_time % window_size)
local pipeline = red:pipeline()
pipeline:zadd("product:" .. product_id .. ":window", {[current_time] = 1})
pipeline:zremrangebyscore("product:" .. product_id .. ":window", 0, window_start - 1)
local results = pipeline:exec()
-- 计算当前窗口内的请求次数
local current_window_count = #results[2]
if current_window_count >= max_requests then
return false, "limit exceeded"
end
return true
end
-- 处理请求
local function handle_request(product_id)
local red = connect_redis()
if not red then
return "failed to connect to Redis"
end
local product_info = get_product_info(red, product_id)
if not product_info then
return "product info not found"
end
local current_time = ngx.now() -- 获取当前时间(秒)
local success, err = update_window_count(red, product_id, current_time)
if not success then
return err
end
-- 检查商品剩余数量
if product_info["remaining_quantity"] <= 0 then
return "product out of stock"
end
-- 减少商品剩余数量
red:hincrby(product_id, "remaining_quantity", -1)
return "purchase successful"
end
-- 示例请求处理
local product_id = "your_product_id_here"
local result = handle_request(product_id)
ngx.say(result)
第五集-限流与服务降级(二)
- Nginx 与 Redis 限流:通过 Ngx 里的 connection 和 request model,结合 Ng 加 Lua 脚本及配置中心对 Redis 中商品单位时间内(滑动窗口)抢购次数进行二次限流,成功用户进入下单页面,失败用户请求不发至 Gateway。
- Lua 脚本用途:仅用于限流,下单时进行剩余数量预扣减,支付时实际扣减,如茅台抢购。
- Gateway 限流核心:在接口层面,如对下单和支付接口用 Sentinel 限流,防止 DB 被打挂。
- Redis 故障处理:Redis 挂了时,抢购接口请求打到 Gateway 层面,此 API 需限流,通过 Gateway 查询 MySQL 商品剩余数量。
- 微服务限流策略:对下单(预扣减)、支付(实际扣减)和抢购按钮查询接口再次限流或削峰、隔离线程(Hystrix限流与降级)。
第六集-限流与服务降级(三)
- 接口功能与操作:
- 下单接口:预扣减 Redis 里的剩余数量,处理数据库里的预锁定订单字段。
- 支付接口:实扣时需将预扣库存删除,在真正库存上减扣,并确保 Redis 剩余数量正常。
- 抢购查询接口:读 MySQL 剩余商品数量,承担服务降级与升级,根据 Redis 状态修改配置中心的并发数量。
- 服务降级与升级:
- Redis 失败时:抢购查询接口降级,修改配置中心并发数量限流。
- Redis 恢复时:更新 Redis 成功则服务升级,更新失败则保持降级。
- 微服务限流:
- Gateway 层面:限制重要接口或微服务整体流量。
- 微服务内部:为核心业务接口如下单接口进行更细微的限流、线程隔离、熔断操作,避免非核心接口占用资源影响核心接口。
- 削峰操作:下单削峰过程中需有服务降级操作。
第七集-限流与服务降级(四)
会议讨论了下单流程中的削峰、降级操作以及服务升降级的相关情况,具体如下:
- 下单削峰流程:下单从上层服务发起,先发到 MQ 进行缓冲,消费端再进行包含订单创建入库、预扣(锁定库存字段)、Redis 中剩余数预扣等的 MySQL 操作,此流程能提高并发量。
- 服务降级场景:
- MQ 不可用:当接入层与 MQ 断联,不能给用户返回挂掉,需让用户以更小并发和处理速率进行订单到支付流程处理。
- 降级操作:将下单接口的并发量降低,直连 MySQL 处理,同时更新配置中心或 Sentinel 限流模块依赖的配置中心。
- 服务升降级方式:
- 自动升降级:可配置阀值,在 MQ 恢复后自动完成服务升级。
- 手动升降级:根据服务体量、切换频繁程度和模块切换难度,人工在配置中心的 UI 界面手动完成配置切换更新。
- 选择升降级方式的依据:服务不重要、体量不大、切换简单,可自动升降级;服务重要、切换复杂,需人工介入手动升降级。