“你遇到过的最难的问题和瓶颈是什么?”
这句话是一个拥有工作经验的人被最常问的问题。我想我会回答秒杀,但是我一直想拖时间,想着以后在想把,这眼瞅要过年了,19/12.26凌晨2点了怎么也得写博客怼下自己,年前至少把多线程总结出来。一下大篇是理论,不要急下面还有东西。
如果面试官问我,我会从 “快 准 稳” 这3个点开始回答! ”稳就是高可用“;“准就是还原秒杀本身特性不能超买”,快 怎么理解了? 快就是系统的性能要足够高,服务端做到极致的性能优化,请求链路要做到优化。俗称“高性能,一致性,高可用”,秒杀就是一个满足高并发,高性能和高可用的分布式系统。
拜读许令波老师的内容,给秒杀架构分为了 “4” 要 "1" 不要 ,并不会仔细讲41问题。
1.数据尽量要少:这个少是指 用户 —— 系统 交互的数据少,请求数据少响应数据少,因为数据网络传输需要时间,服务器在写网络时是要压缩和字符编码的,然后这些东西还是比较耗cpu,所以从而减少cpu消耗,包括不必要页面数据和效果。同时要求系统依赖也要少包括系统需要完成逻辑需要读写的数据,这些数据一般是在服务端和数据库打交道的,调用其它依赖服务会涉及数据序列化和反序列化,这对cpu也是一大杀手,本身数据库就容易成为瓶颈所以和数据库交互越少越好。
2.请求数少: 用户请求到到页面,浏览器渲染还会请求其他资源,包括css/js/img/ajax这些都是额外请求,为啥? 因为链接三次握手,最狗屎的是有的js还需要串行加载,还有不同请求域名不同的话,涉及到域名dns解析,这些都是降低服务器的资源消耗。
减少请求数最常用的一个实践就是合并 CSS 和 JS 文件,把多个 JS
文件合并成一个文件,在 URL 中用逗号隔开(https://g.xxx.com/tm/xxb/4.0.94/mods/??module-preview/index.xtpl.js,module-jhs/index.xtpl.js,modulefocus/index.xtpl.js)。这种方式在服务端仍然是单个文件各自存放,只是服务端会有一个
组件解析这个 URL,然后动态把这些文件合并起来一起返回
3.路径要短:就是请求发送到返回中间经过的节点数,这些节点可以是跨系统,可以是建立socket比如代理服务器就是建立新的Socket转发请求,保证高可用这个时候可以看到,每个节点99.9%可用 假设经过5个节点,那可用概率就是99.5%,缩短节点可以有效提升性能(减少中间节点可以减少数 据的序列化与反序列化),比如基于springcloud的微服务是建立http链接基础的,每个方法都是相互调用通过http难受死了。你可能会说那我用RPC远程过程调用,那你也得建立socket啊!! 所以缩短路径就是把多个依赖强的应用合并在一起发布部署,把rpc变成JVM内部的方法调用。(在《大型网站技术架构演进与性能优化》一书有提到这个技术)
4.依赖要少:啥意思就是主业务不要太过于和其他业务拉扯到一起,我们给业务分级 T0 .T1. T2. T3级别 ,那么T0就是最重要的业务,那么T0专注于完成T0本身,防止重要系统被次重要系统拖累,比如支付肯定是T0,支付完要扣优惠券,如果优惠券系统出错难道要把支付也跟着出错回滚? 所以优惠券扣除出错,我们先不管把它丢队列等会来处理,先把钱给我付了。
5.不要有单点:系统有单点是系统架构的大忌,因为单点意味着没有备份,分享不可控,设计分布式本身就是消除单点,关键就是将服务状态和机械接触绑定,把服务无状态化。例如:
把和机器相关的配置动态化,这些参数可以通过配置中心来动态推送,在服务启动时动态拉取下来,我们在这些配置中心设置 一些规则来方便地改变这些映射关系
同时应用也要无状态化,但是应用的存储服务很难无状态化,因为数据要存在磁盘啊,本身就和机器绑定,冒的法,通过冗余备份方式解决单点了。
当然以上是虽然说是规则,但是有时候也是相互矛盾的,保证了A却保证不了B,重要的平衡以上要求。以上哔哔赖赖的是一些理论的玩意!!!
最开始秒杀就是买了下单,库存扣了,库存没了秒杀结束。随着1W/S 增长到10W/S,我们一开始会把秒杀服务独立服务,针对性的优化,只专注于秒杀本身。然后 把单独的服务也部署集群,因为不要影响到其他正常商品购买,降低负载。再然后 将热点数据比如库存,单独放缓存里。再然后 增加答题等手段防止秒杀机器抢单。
再然后就增加限流保护,本地缓存减少服务调用,只需要请求很少的动态数据。再往后已经超冈了,不同QPS的瓶颈都不一样,一般10W的瓶颈在数据读取上,通常处理就是增加缓存,当升级到100W/S,那服务端的网络都可能是瓶颈,所以大部分的静态数据都放到CDN甚至缓存到浏览器,这些我已经不懂的啦,我要懂我就去阿里当架构了。
接下来先从数据获取这方面来谈谈以上的实现, 首先 数据存在磁盘反给用户,我们一般会增加缓存服务器去存储部分数据,但是当并发再大后,其中有很多请求都是重复的,这时候即使用缓存处理还是要经过服务端,我们为了减少服务端的请求数,我们会选择把这些数据存在用户本地,还可以用请求头缓存去处理,参考Vanish ,这个是对url缓存,可以拦截请求,如果key存在,就直接把缓存返还给用户,甚至不用走服务端,避免java本身的弊端,其实java并不擅长处理大量链接请求,消耗内存多而且servlet解析http协议也慢,所以我们选择直接在WEB服务层做(Nginx,Apache,Varnish)更擅长处理大并发的静态文件请求。
如何做到?
A. 数据动静 : RUL唯一化,在秒杀之前商品详情是作为一个缓存数据存储,商家每次更新数据都应该会随时更新缓存,但是即使是详情页大部分数据都是静态的,但是总有动态数据,如库存,所以我们可以单独拆开来处理库存,处理方式分为:ESI(Edge Side Includes)方案和 CSI(Client Side Include)方案。
ESL:即在 Web 代理服务器上做动态内容请求,并将请求插入到静态 页面中,当用户拿到页面时已经是一个完整的页面了。这种方式对服务端性能有些影响,但是用户体验较好。
CSL:即单独发起一个异步 JavaScript 请求,以向服务端获取动态内容。这种方式服务端性能更佳,但是用户端页面可能会延时,体验稍差。
网上动静数据分离也比较多,要专门说可以说很多,解决方案也有各种 cache统一层把cache作为单独一层服务集群,要么上cdn等等
B. 热点数据:就像jvm的编译算法一样28原则,百分之20的代码占据了80%使用率,我们通常说jvm是解释执行(读一行,编译一行,执行一行),其实是不严谨的,jvm在解释执行同时,在运行时会算出热点代码,接下来会把这部分代码变成机器码然后通过编译执行(上来全部编译,直接执行),一种混合模式执行。同样的数据库里的数据大部分也是同样情况,比如商品,有些热卖商品会频繁的被收藏,加入购物车,购买,点击等操作。另外一种情况,有些热点商品他的请求数可以只占据全体的亿分之一,但是她处理繁杂,可能会抢夺90%的服务资源,如果我们对这部分数据进行优化,从而降低服务器压力也是一个非常大的优化