秒杀设计服务稳定性思考
- 导语:秒杀在现在的运营过程中是一种非常常见一种活动,它业务价值曝光量大、转化率高,对应的技术重点在于流量集中时间短,并发量大。本文主要通过一个常见的场景和大家探讨一下秒杀场景中设计的缓存、限流、降级的运用。
1、概要
秒杀活动主要涉及的前端页面有活动推广页、商品详情页,涉及到的后端服务主要有商品服务、库存服务、订单服务,简要流程图如下:
2、缓存设计
Q:为什么要缓存呢?
A:缓存的主要目的是为了解决秒杀活动高并发的天然特性,减轻服务的压力。
Q:什么样的数据应该缓存,什么样的数据不应该缓存呢?
A:在整个活动过程中不会变的数据缓存,比如商品信息;动态变的数据视情况缓存,比如库存信息。
在这种场景下,缓存可以分为前端页面缓存和接口数据缓存,怎么来实现呢?下面我们来探讨一下缓存的实现方式:
2.1、前端页面缓存:
前端缓存主要采用的页面静态化,CDN缓存加速。
资料参考:阿里CDN对OSS加速
2.2、库存数据缓存:
库存数据使用redis来缓存,例如:
set stock_{skuId} stockNum
3、限流
限流的主要是为了防止非法流量对系统的冲击以及正常流量超过预期导致系统的不可用,那么限流主要采用nginx限流和sentinel限流。
3.1、nginx限流
NGINX速率限制使用漏斗算法,该算法广泛应用于电信和分组交换计算机网络中,以在带宽受限时处理突发性问题。比方说一个水桶,在水桶的顶部浇水,然后从底部漏水。如果倒水的速度超过漏水的速度,则水桶会溢出。在请求处理方面,水代表来自客户端的请求,存储桶代表队列,根据先进先出(FIFO)调度算法,请求等待处理。漏水表示退出缓冲区以供服务器处理的请求,溢出表示已丢弃且从未得到服务的请求。
配置基本速率限制
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location /login/ {
limit_req zone=mylimit;
proxy_pass http://my_upstream;
}
}
该limit_req_zone指令定义了速率限制的参数,同时limit_req在它出现的上下文中启用了速率限制(在示例中,针对/ login /的所有请求)。
该limit_req_zone指令通常在http块中定义,使其可在多个上下文中使用。它采用以下三个参数:
- Key–定义要应用限制的请求特征。在示例中,它是NGINX变量binary_remote_addr,其中包含客户端IP地址的二进制表示形式。这意味着我们将每个唯一的IP地址限制为第三个参数所定义的请求速率。(我们正在使用此变量,因为它占用的空间少于客户端IP地址的字符串表示形式remote_addr)。
- Zone –定义用于存储每个IP地址状态及其访问请求限制URL的频率的共享内存区域。将信息保存在共享内存中意味着可以在NGINX工作进程之间共享信息。该定义分为两部分:由zone=关键字标识的区域名称,以及冒号后面的大小。大约16,000个IP地址的状态信息需要1兆字节,因此我们的区域可以存储大约160,000个地址。
如果NGINX需要添加新条目时存储空间耗尽,它将删除最旧的条目。如果释放的空间仍然不足以容纳新记录,则NGINX返回状态码。另外,为防止内存耗尽,NGINX每次创建一个新条目时,都会删除最多60秒钟内未使用的两个条目。503 (Service Temporarily Unavailable) - Rate –设置最大请求速率。在该示例中,速率不能超过每秒10个请求。NGINX实际上以毫秒粒度跟踪请求,因此此限制对应于每100毫秒(ms)1个请求。因为我们不允许突发(请参阅下一节),所以这意味着如果请求在上一个允许的请求之后不到100毫秒到达,则该请求将被拒绝。
该limit_req_zone指令设置速率限制和共享内存区域的参数,但实际上并没有限制请求速率。为此,您需要通过在其中包含指令来将限制应用于特定location或server块limit_req。在示例中,我们将对/ login /的请求进行速率限制。
因此,现在每个唯一IP地址每秒只能对/ login /请求10个请求, 或者更确切地说,不能在前一个IP地址的100ms内对该URL发出请求。
资料来源:rate-limiting-nginx
3.2、sentinel限流
一个活动的开始我们首先会对这个活动有一个预期值,进行压测最终给出一个比较合理的QPS值,用sentinel对这个QPS进行限流。
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
FlowSlot 会根据预设的规则,结合前面 NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot 统计出来的实时信息进行流量控制。
限流的直接表现是在执行 Entry nodeA = SphU.entry(resourceName) 的时候抛出 FlowException 异常。FlowException 是 BlockException 的子类,您可以捕捉 BlockException 来自定义被限流之后的处理逻辑。
同一个资源可以创建多条限流规则。FlowSlot 会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕。
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
- resource:资源名,即限流规则的作用对象
- count: 限流阈值
- grade: 限流阈值类型(QPS 或并发线程数)
- limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
- strategy: 调用关系限流策略
- controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
资源示例:
public class OrderService {
// 原函数
@SentinelResource(value = "createOrder", blockHandler = "exceptionHandler")
public String createOrder(long s) {
//Do some create order here
return "order 对象";
}
// Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
public String exceptionHandler(long s, BlockException ex) {
// Do some log here.
ex.printStackTrace();
return "Oops, error occurred at " + s;
}
}
定义规则示例:
private static void initFlowRules(){
List rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("createOrder");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// Set limit QPS to 20.
rule.setCount(20);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
资料来源:Sentinel 流量控制
4、降级
当订单服务调用库存服务锁库存的时候,库存服务出现超时或者其它未知的一些异常,那么系统应该做异常降级处理。
示例:
DefaultHttpClient httpClient = new DefaultHttpClient();
int timeout = 5;
HttpParams httpParams = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(httpParams, timeout * 1000);
HttpConnectionParams.setSoTimeout(httpParams, timeout * 1000);
HttpGet getMethod = new HttpGet("锁库存接口");
try {
HttpResponse response = httpClient.execute(getMethod);
} catch (org.apache.http.conn.ConnectTimeoutException connectTimeoutException) {
//执行降级逻辑
} catch (java.net.SocketTimeoutException socketTimeoutException) {
//执行降级逻辑
} catch (Exception e) {
//执行降级逻辑
}
*注:库存服务库存锁定接口必须保证具有幂等性,防止重复锁库存。
我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1q6kiz7c7v1lg