秒杀系统设计(一)解决高性能问题

秒杀涉及高读和高写的支持,核心优化理念是高读就尽量“少读"或“读少",高写就数据拆分。

下面从动静分离、热点优化以及服务端性能优化 3 个方面解决性能问题。

动静分离

秒杀过程中你是不需要刷新整个页面的,只有时间在不停跳动。这是因为一般都会对大流量的秒杀系统做系统的静态化改造,即数据意义上的动静分离。动静分离分为三部分:数据拆分、静态缓存、数据整合

数据拆分

将动态页面改造成适合缓存的静态页面。

用户身份信息包括登录状态以及登录画像等,相关要素可以单独拆分出来,通过动态请求进行获取。与之相关的广平推荐,如用户偏好、地域偏好等,同样可以通过异步方式进行加载。

静态缓存

直接缓存整个 HTTP 连接,而不是仅仅缓存静态数据。Web 代理服务器根据请求 URL,可以直接取出对应的响应体然后直接返回,响应过程无需重组 HTTP 协议,也无需解析 HTTP 请求头。

而作为缓存键,URL唯一化是必不可少的,只是对于商品系统,URL 天然是可以基于商品 ID 来进行唯一标识。

缓存三种方式:浏览器、服务端、CDN

浏览器

是第一选择,但用户的浏览器是不可控的,如果用户不主动刷新,系统很难主动地把消息推送给用户,如此可能会导致用户端在很长一段时间内看到的信息都是错误的。对于秒杀系统,保证缓存可以在秒级时间内失效是不可或缺的。

服务端

主要进行动态逻辑计算及加载,本身并不擅长处理大量连接,每个连接消耗内存较多,同时 Servlet 容器解析 HTTP 较慢,容易侵占逻辑计算资源;另外,静态数据下沉至此也会拉长请求路径。

CDN

因此通常将静态数据缓存在 CDN,其本身更擅长处理大并发的静态文件请求,既可以做到主动失效,又离用户尽可能近,同时规避 Java 语言层面的弱点。

CDN缓存有以下几个问题需要解决:

失效问题。任何一个缓存都应该是有时效的,尤其对于一个秒杀场景。所以,系统需要保证全国各地的 CDN 在秒级时间内失效掉缓存信息,这实际对 CDN 的失效系统要求是很高的

命中率问题。高命中是缓存系统最为核心的性能要求,不然缓存就失去了意义。如果将数据放到全国各地的 CDN ,势必会导致请求命中同一个缓存的可能性降低,那么命中率就成为一个问题。

因此将数据放到全国所有的 CDN 节点是不太现实的,失效问题、命中率问题都会面临比较大的挑战。

节点的选取通常需要满足以下几个条件:

  • 临近访问量集中的地区
  • 距离主站较远的地区
  • 节点与主站间网络质量良好的地区

基于以上因素,选择 CDN 的二级缓存比较合适,因为二级缓存数量偏少,容量也更大,访问量相对集中,这样就可以较好解决缓存的失效问题以及命中率问题,是当前比较理想的一种 CDN 化方案。

数据整合

分离出动静态数据之后,需要对数据页整合。

通常有两种方案:ESI(Edge Side Includes)方案和 CSI(Client Side Include)方案。

  • ESI 方案:Web 代理服务器上请求动态数据,并将动态数据插入到静态页面中,用户看到页面时已经是一个完整的页面。这种方式对服务端性能要求高,但用户体验较好。
  • CSI 方案:Web 代理服务器上只返回静态页面,前端单独发起一个异步 JS 请求动态数据。这种方式对服务端性能友好,但用户体验稍差。

热点优化

热点操作

零点刷新、零点下单、零点添加购物车等都属于热点操作。热点操作是用户的行为,不好改变,但可以做一些限制保护,比如用户频繁刷新页面时进行提示阻断。

热点数据

热点识别

热点数据分为静态热点和动态热点。

  • 静态热点:能够提前预测的热点数据。大促前夕,可以根据大促的行业特点、活动商家等纬度信息分析出热点商品,或者通过卖家报名的方式提前筛选;另外,还可以通过技术手段提前预测,例如对买家每天访问的商品进行大数据计算,然后统计出 TOP N 的商品,即可视为热点商品
  • 动态热点:无法提前预测的热点数据。冷热数据往往是随实际业务场景发生交替变化的,可能一件商品在短时间内被大量购买。由于此类商品日常访问较少,即使在缓存系统中一段时间后也会被逐出或过期掉,甚至在 DB 中也是冷数据。瞬时流量的涌入,往往导致缓存被击穿,请求直接到达 DB,引发 DB 压力过大。

因此秒杀系统需要实现热点数据的动态发现能力,热点数据采集最好采用异步方式,一方面不会影响业务的核心交易链路,一方面可以保证采集方式的通用性。热点发现最好做到秒级实时,这样动态发现才有意义,实际上也是对核心节点的数据采集和分析能力提出了较高的要求。

一个常见的实现思路是:

  1. 异步采集交易链路各个环节的热点 Key 信息,如 Nginx 采集访问 URL 或 Agent 采集热点日志(一些中间件本身已具备热点发现能力),提前识别潜在的热点数据。
  2. 聚合分析热点数据,达到一定规则的热点数据,通过订阅分发推送到链路系统,各系统根据自身需求决定如何处理热点数据,或限流或缓存,从而实现热点保护。

热点隔离

热点数据识别出来之后,需要将热点数据隔离出来,不要让 1% 影响到另外的 99%。比如按照用户来区分,为不同的用户分配不同的 Cookie,入口层路由到不同的服务接口中。域名保持一致,但后端调用不同的服务接口。在数据层给数据打标进行区分。

可以基于以下几个层次实现热点隔离:

  • 业务隔离。秒杀作为一种营销活动,卖家需要单独报名,从技术上来说,系统可以提前对已知热点做缓存预热。
  • 系统隔离。系统隔离是运行时隔离,通过分组部署和另外 99% 进行分离,另外秒杀也可以申请单独的域名,入口层就让请求落到不同的集群中。
  • 数据隔离。秒杀数据作为热点数据,可以启用单独的缓存集群或者 DB 服务组,从而更好的实现横向或纵向能力扩展。

热点优化

  • 缓存:热点缓存是最为有效的办法。如果热点数据做了动静分离,那么可以长期缓存静态数据
  • 限流:流量限制更多是一种保护机制。需要注意的是,各服务要时刻关注请求是否触发限流并及时进行review

系统优化

减少序列化

减少 Java 中的序列化操作可以很好的提升系统性能。序列化大部分是在 RPC 阶段发生,因此应该尽量减少 RPC 调用。

直接输出流数据

只要涉及字符串的 I/O 操作,无论是磁盘 I/O 还是网络 I/O,都比较耗费 CPU 资源,因为字符需要转换成字节,而这个转换又必须查表编码。所以对于常用数据,比如静态字符串,推荐提前编码成字节并缓存,具体到代码层面就是通过 OutputStream() 类函数从而减少数据的编码转换。另外,热点方法 toString() 不要直接调用 ReflectionToString 实现,推荐直接硬编码,并且只打印 DO 的基础要素和核心要素。

裁剪日志异常堆栈

无论是外部系统异常还是应用本身异常,都会有堆栈打出,超大流量下,频繁的输出完整堆栈,只会加剧系统当前负载。可以通过日志配置文件控制异常堆栈输出的深度。

去组件框架

极致优化要求下,可以去掉一些组件框架,比如去掉传统的 MVC 框架,直接使用 Servlet 处理请求。这样可以绕过一大堆复杂且用处不大的处理逻辑,节省毫秒级的时间。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值