字节二面:假设10W人突访,系统怎么保证不Crash ?

网站建设3年前发布
41 00

今年行情不大好,很多小伙伴来找我改简历聊面试经历,这不,最近知识星球的同学经过朋友内推才获得面试机会,但是在二面的时候被问倒了,这是一道场景题:假设10W人突防,系统怎么做到不crash(崩溃) ?,心理防线比较差的同学一听到10W这个数字估计就有点崩溃了,其实仔细理解,这个题,是面试当中一道比较高端的场景题。,这个题目的回答,大有玄机。回答得有水平,能够直接拿到offer。可惜,很多小伙伴可能一句话都说不上来。,如果面试遇到这个问题,很多小伙伴的第一反应:,怎么可能,我们的系统,总体的用户量,不到1万, 怎么可能会有10W人同时访问。,疑问归疑问。,这个问题,如果遇到了,只能会硬着头皮作答。,如果直接用这个疑问去反问面试官,那么 ,面试官一定会说:这小子 没有遇到过事情,没有见过市面。,为啥呢?,因为哪怕用户不到1万,其实,咱们的系统,还是可能会有10W人同时访问滴。这10W人从哪里来的呢? 这些人包括:,大家应该听过一句话吧,整个互联网上大概有 50%以上的流量其实是爬虫。,一个做反爬虫哥们,发现了一个极端案例,,某个页面12000次点击里边,98%的点击率,是爬虫贡献的。,爬虫和用户的比例是 19比1.,那么 1W用户, 可能会对应到19W 爬虫,,那么 1W用户, 有没有  10W的 同时访问的可能呢?,因为 大量爬虫的存着, 当然有的。,“羊毛党”战术之一:开启机器人批量注册新账号,招募“新兵”。,“这是个‘昏招’,批量注册的账号很容易识别。,“羊毛党”战术之二:提前囤积账号,囤积的老账号”。,但通过注册时间、注册地与下单地比对等方式,很快识别出来。,某年的“618”电商节活动期间,某电商公司,平均每天拦截“羊毛党”账号2000万个。,那么 1W用户, 可能会对应到多少羊毛党用户呢?,其中,可能会包含部分 提前囤积账号,另外,在 活动执行的过程中, 还是有 可能 批量注册大量的新账号,那么 1W用户, 有没有  10W的 同时访问的可能呢?,只要有利可图,就会有 刷子用户(羊毛党),他们会通过群体人手,或者 自动化工具,制造大量的瞬间流量。,这些自动化工具,在 1s之内, 尝试10W次。,所以,只要是有利可图,如 秒杀等, 那么 1W用户, 有没有  10W的 同时访问的可能呢?,当然有的。,按照之前和大家分析的架构理论,一个系统10W人同时访问 , 也就是:并发量为 10w Qps,那么 10w Qps ,对应到多少用户量呢  ?,是 一个1亿的用户量。,而 ,我们很多同学手上的 系统, 总体的用户量 不到1万,,不到1万的用户,对应到多少 的 吞吐量呢?,是 10。,没错,如果 总体的用户量 不到1万,按照 正常估算, 吞吐量就只有 10。,也就是说, 如果我们按照 1万 做系统架构,字节二面:假设10W人突访,系统怎么保证不Crash ?,这种架构,对于 qps 为10 的小流量来说,可以说是 小菜一碟。,可以说,用牛刀 在 杀鸡。,那么,如果发生突发情况,,假设10W人突然访问,我们的架构,如何抵抗?,字节二面:假设10W人突访,系统怎么保证不Crash ?,大家看看,上面的架构, 能抵抗吗?,方式之一:扩容,方式之二:限流,大家首先会想到的策略,就是扩容。,字节二面:假设10W人突访,系统怎么保证不Crash ?,但是,如果流量是突发的, 又不知道什么时候扩, 怎么办呢?,那么就是自动扩容。,接入层限流可以进行 nginx 限流,字节二面:假设10W人突访,系统怎么保证不Crash ?,微服务 SpringCloud gateway 里边,,还 可以使用 redis lua进行分布式限流, 或者使用 sentinel 进行 限流,,经过扩容和限流,关注公众号:码猿技术专栏,回复关键词:1111 获取阿里内部Java性能调优手册! 咱们的系统,应该可以扛住 10Wqps, 因为可以把流量限制到 1Wqps,甚至是 1 K qps。,谁怪 有那么多刷子流量,或者 爬虫流量呢。,但是,限流是无奈之举。,或者说,如果10Wqps,都是有效流量, 不能使用限流这 简单粗暴的方式 , 而是这个 10Wqps 必须进入到服务层。,这个 10Wqps 必须进入到服务层。,怎么办?,服务层 倒是好说,和网关一样, 可以通过扩容解决。,所以,后面的 流量就进入  redis集群。,字节二面:假设10W人突访,系统怎么保证不Crash ?,redis 集群一般搭建的是 3主3从:,字节二面:假设10W人突访,系统怎么保证不Crash ?,一般来说,主节点提供服务, 从节点是做冗余的, 并不提供数据的写入服务。,redis cluster模式官方默认主节点提供读写,  从节点提供slot数据备份以及故障转移。默认情况下,从节点并不提供数据读写服务。,单个redis 的吞吐量,一般就是 2W左右。,那么 10Wqps,访问 redis cluster,分布到 3个节点, 还是不够。,如果 10Wqps 访问的是同一个key, 那就问题更大了。,因为 单个redis 的吞吐量,一般就是 2W左右。怎么可能扛住  5倍的吞吐量。,于是就很容易出现 redis cpu 100%, 请求排队, 没有响应,严重的情况 出现 redis 雪崩。,于是乎,改怎么办?,大概也有两种方案:,方法一:redis 扩容,方法二:本地缓存,方法一 redis 扩容 可以解决 key 的访问量比较 均匀的问题。比如扩容到 10主10从,就可以承担 20Wqps的 流量。,但是方法一 要求每个key的访问量 必须分布得比较均匀。如果20w qps 的流量,全部来自一个key, 则方案一 无效。,方法二 本地缓存 可以解决 单个key 访问量 巨大的问题。这种 占据大量流量的 单个key,叫做 hotkey(热key)。,所以,接下来,还是得调整系统的架构, 加入本地缓存的 环节。,解决 10WQps 突发流量的本地缓存架构,大致有两种:,java 本地缓存+ redis 分布式缓存,具体如下图:,字节二面:假设10W人突访,系统怎么保证不Crash ?,先发问一级缓存caffeine ,如果 没有找到,再访问 二级缓存 redis 集群,nginx本地缓存+ java 本地缓存+ redis 分布式缓存,具体如下图:,字节二面:假设10W人突访,系统怎么保证不Crash ?,1. 快但是量少:访问速度快,但无法进行大数据存储,本地缓存相对于分布式缓存的好处是,由于数据不需要跨网络传输,故性能更好,,但是由于占用了应用进程的内存空间,如 Java 进程的 JVM 内存空间,故不能进行大数据量的数据存储。,2. 需要解决数据一致性问题:本地缓存、分布式缓存、DB数据一致性问题,与此同时,本地缓存只支持被该应用进程访问,一般无法被其他应用进程访问,故在应用进程的集群部署当中,,如果对应的数据库数据,存在数据更新,则需要同步更新不同部署节点的缓存数据来包保证数据一致性,,复杂度较高并且容易出错,如基于 rocketmq 的发布订阅机制来同步更新各个部署节点。,3.未持久化,容易丢失:数据随应用进程的重启而丢失,由于本地缓存的数据是存储在应用进程的内存空间的,所以当应用进程重启时,本地缓存的数据会丢失。,所以对于需要更改然后持久化的数据,需要注意及时保存,否则可能会造成数据丢失。,4.需要尽量缓存热点key,而提升缓存的命中率,由于本地缓存太小,从而很容易被淘汰,,如果还没有来得及访问,本地缓存中的数据,就被淘汰了,那就失去了本地缓存的价值, 当然,本地缓存的命中率也会很低。,方式1:采用更好的  缓存淘汰策略,比如caffeine中,使用了 w-tinylfu 策略。,这种策略的  缓存的命中率,比较简单的 lfu、lru 都 高出很多。,有测试表明:caffeine 比 guava 的命中率,不同场景,都会高出10%以上。,方式2:尽量识别和缓存 热点数据,简单的说,把热点数据, 加载到本地缓存。,在某个Key接收到的访问次数、显著高于其它Key时,我们可以将其称之为HotKey,,从访问量上来说,常见的HotKey如:,从业务上来说, 常见的HotKey如:,1 、MySQL等数据库会被频繁访问的热数据,如爆款商品的skuId。,2 、redis的被密集访问的key,如爆款商品的各维度信息,skuId、shopId等。,3 、机器人、爬虫、刷子用户,如用户的userId、uuid、ip等。,4 、某个接口地址,如/sku/query或者更精细维度的。,注意,我们的HotKey探测框架只关心key,其实就是一个字符串,,在拥有大量并发用户的系统中,HotKey一直以来都是一个不可避免的问题。,以京东为例的这些头部互联网公司,动辄某个爆品,会瞬间引入每秒上百万甚至数百万的请求,当然流量多数会在几秒内就消失。,但就是这短短的几秒的HotKey,就会瞬间造成其所在redis分片集群瘫痪。,原因也很简单,redis作为一个单线程的结构,所有的请求到来后都会去排队,当请求量远大于自身处理能力时,后面的请求会陷入等待、超时。,由于该redis分片完全被这个key的请求给打满,导致该分片上所有其他数据操作都无法继续提供服务,也就是HotKey不仅仅影响自己,还会影响和它合租的数据。,这样,redis 缓存没有响应之后,相当于 redis 击穿, 请求直接转向DB,DB的吞吐量,比如会低很多,DB 就会雪崩。,HotKey问题归根到底就是如何找到HotKey,并将HotKey放到本地内存的问题。,只要该key在内存里,我们就能极快地来对它做逻辑,内存访问和redis访问的速度不在一个量级。,如果该key是在本地内存中,读取一个内存中的值,每秒多少个万次都是很正常的,不存在任何数据层的瓶颈。,但问题是事先不知道HotKey在哪里?,那么,问题就来了,如何进行 HotKey的探测?,1、实时性,这个很容易理解,key往往是突发性瞬间就热了,根本不给你再慢悠悠手工去配置中心添加HotKey再推送到jvm的机会。,它大部分时间不可预知,来得也非常迅速,可能某个商家上个活动,瞬间HotKey就出现了。如果短时间内没能进到内存,就有redis集群被打爆的风险。,所以HotKey探测框架最重要的就是实时性,最好是某个key刚有热的苗头,在1秒内它就已经进到整个服务集群的内存里了,1秒后就不会再去密集访问redis了。,同理,对于刷子用户也一样,刚开始刷,1秒内我就把它给禁掉了。,2、准确性,这个很重要,也容易实现,累加数量,做到不误探,精准探测,保证探测出的HotKey是完全符合用户自己设定的阈值。,3、集群一致性,这个比较重要,尤其是某些带删除key的场景,要能做到删key时整个集群内的该key都会删掉,以避免数据的错误。,4、高性能,这个是核心之一,高性能带来的就是低成本,做HotKey探测目的就是为了降低数据层的负载,提升应用层的性能,节省服务器资源。不然,大家直接去整体扩充redis集群规模就好了。,理论上,在不影响实时性的情况下,要完成实时HotKey探测,所消耗的机器资源越少,那么经济价值就越大。,通过  流式计算集群 storm/ flink 集群,进行 topkey,字节二面:假设10W人突访,系统怎么保证不Crash ?,java 应用将访问 记录发送到 消息队列,如 kafka,storm、flink 集群,进行top N 的计算,把top N 结果存在 redis,其中的 top N 的key,就是热点 key,有赞透明多级缓存解决方案,比如:结合京东开源hotkey,做热点探测,TMC ,即“透明多级缓存( Transparent Multilevel Cache )”,是有赞 PaaS 团队给公司内应用提供的整体缓存解决方案。,TMC 在通用“分布式缓存解决方案(如 CodisProxy + Redis ,如有赞自研分布式缓存系统 zanKV )”基础上,增加了以下功能:,以帮助应用层解决缓存使用过程中出现的热点访问问题。,使用有赞服务的电商商家数量和类型很多,商家会不定期做一些“商品秒杀”、“商品推广”活动,导致“营销活动”、“商品详情”、“交易下单”等链路应用出现 缓存热点访问 的情况:,为了应对以上问题,需要一个能够 自动发现热点 并 将热点缓存访问请求前置在应用层本地缓存 的解决方案,这就是 TMC 产生的原因。,基于上述描述,我们总结了下列 多级缓存解决方案 需要解决的需求痛点:,TMC 聚焦上述痛点,设计并实现了整体解决方案。,以支持“热点探测”和“本地缓存”,减少热点访问时对下游分布式缓存服务的冲击,避免影响应用服务的性能及稳定性。,字节二面:假设10W人突访,系统怎么保证不Crash ?,TMC 整体架构如上图,共分为三层:,TMC 是如何减少对业务应用系统的入侵,做到透明接入的?,对于公司 Java 应用服务,在缓存客户端使用方式上分为两类:,不论使用以上那种方式,最终通过JedisPool​创建的Jedis对象与缓存服务端代理层做请求交互。,字节二面:假设10W人突访,系统怎么保证不Crash ?,TMC 对原生jedis包的JedisPool和Jedis类做了改造,,在JedisPool初始化过程中, 集成TMC“热点发现”+“本地缓存”功能Hermes-SDK包的初始化逻辑,,使Jedis​客户端与缓存服务端代理层交互时,  先与Hermes-SDK交互,从而完成 “热点探测”+“本地缓存”功能的透明接入。,对于 Java 应用服务,只需使用特定版本的 jedis-jar 包,无需修改代码,即可接入 TMC 使用“热点发现”+“本地缓存”功能,做到了对应用系统的最小入侵。,字节二面:假设10W人突访,系统怎么保证不Crash ?,TMC 本地缓存整体结构分为如下模块:,1) key 值获取,2)key值过期,3)热点发现,4)配置读取,TMC本地缓存稳定性表现在以下方面:,TMC 本地缓存一致性表现在以下方面:,字节二面:假设10W人突访,系统怎么保证不Crash ?,TMC 热点发现流程分为四步:,Hermes-SDK 通过本地rsyslog将 key访问事件 以协议格式放入 kafka ,Hermes服务端集群 的每个节点消费 kafka 消息,实时获取 key访问事件。,访问事件协议格式如下:,Hermes服务端集群 节点将收集到的 key访问事件 存储在本地内存中,,内存数据结构为Map<String, Map<String, LongAdder>>,,对应业务含义映射为Map< appName , Map< uniqueKey , 热度 >>。,字节二面:假设10W人突访,系统怎么保证不Crash ?,Hermes服务端集群 节点,对每个App的每个 key ,维护了一个 时间轮:,Hermes服务端集群 节点,对每个 App 每3秒 生成一个 映射任务 ,交由节点内 “缓存映射线程池” 执行。,映射任务 内容如下:,字节二面:假设10W人突访,系统怎么保证不Crash ?,完成第二步“热度滑窗”后,映射任务 继续对当前 App 进行“热度汇聚”工作:,TMC 热点发现整体流程如下图:,字节二面:假设10W人突访,系统怎么保证不Crash ?,Hermes-SDK基于rsyslog + kafka 实时上报 key访问事件。映射任务 3秒一个周期完成“热度滑窗” + “热度汇聚”工作,当有 热点访问场景 出现时最长3秒即可探测出对应 热点key。,key 的热度汇聚结果由“基于时间轮实现的滑动窗口”汇聚得到,相对准确地反应当前及最近正在发生访问分布。,Hermes服务端集群 节点无状态,节点数可基于 kafka 的 partition 数量横向扩展。,“热度滑窗” + “热度汇聚” 过程基于 App 数量,在单节点内多线程扩展。,有赞商家通过快手直播平台为某商品搞活动,造成该商品短时间内被集中访问产生访问热点,活动期间 TMC 记录的实际热点访问效果数据如下:,5-1-1. 某核心应用的缓存请求&命中率曲线图,字节二面:假设10W人突访,系统怎么保证不Crash ?,字节二面:假设10W人突访,系统怎么保证不Crash ?,可以看出活动期间缓存请求量及本地缓存命中量均有明显增长,本地缓存命中率达到近 80% (即应用集群中 80% 的缓存查询请求被 TMC 本地缓存拦截)。,字节二面:假设10W人突访,系统怎么保证不Crash ?,字节二面:假设10W人突访,系统怎么保证不Crash ?,可以看出活动期间应用接口的请求量有明显增长,由于 TMC 本地缓存的效果应用接口的 RT 反而出现下降。,字节二面:假设10W人突访,系统怎么保证不Crash ?,字节二面:假设10W人突访,系统怎么保证不Crash ?,字节二面:假设10W人突访,系统怎么保证不Crash ?,在有赞, TMC 目前已为商品中心、物流中心、库存中心、营销活动、用户中心、网关&消息等多个核心应用模块提供服务,后续应用也在陆续接入中。,TMC 在提供“热点探测” + “本地缓存”的核心能力同时,也为应用服务提供了灵活的配置选择,应用服务可以结合实际业务情况在“热点阈值”、“热点key探测数量”、“热点黑白名单”维度进行自由配置以达到更好的使用效果。,配合三级缓存的使用,需要进行 热key的 探测,有赞平台通过 热key的探测和 支持,,其中:活动期间,本地缓存命中率达到近 80%的命中率, 并且, 响应时间,和平峰时段,没有变化。,基于开源hotkey进行 热点探测,有很多小伙伴,在生产系统进行了 缓存系统的重构,下面将重构前与重构后做下对照,来说明这套机制的优缺点。,三级缓存  强烈推荐进行 热点探测 相结合, 主要的优势是:

© 版权声明

相关文章