图片来自宝途网。成千上万的人,不断地添加新的东西。最后总结出一套设计思路,也是一个万能模板,让面试官在问任何高并发系统的时候,只需要考虑这几个方向即可。下面我就由浅入深地讲解一下:V0:如果单体架构让你实现一个几十人的抽奖系统,那很容易死,你就狠狠地打!通知服务,查书通知,完美!相信大家在学习Java的时候可能都做过这样的案例,想一想存在哪些问题?单服,一不小心把盘全部丢了再抽,一个人就是一个恶意脚本进军,没有程序员不能中奖接下来说说怎么解决这些问题?V1:负载均衡当一台服务器单位时间内的访问量越大,服务器的压力就越大,会超过自身的容量,当超过容量时,服务器就会崩溃。为了避免服务器崩溃,让用户有更好的体验,我们使用负载均衡来分担服务器压力。负载均衡就是搭建很多服务器,组成一个服务器集群。当用户访问一个网站时,他首先访问一个中间服务器,就像管家一样,在服务器集群中选择一个压力较小的服务器,然后将访问请求引入到这个服务器上。.这样,每次用户访问时,都会保证服务器集群中每台服务器的压力均衡,分担服务器压力,避免服务器崩溃。负载均衡是利用“反向代理”的原理来实现的。下面继续介绍具体的负载均衡算法及其实现。负载均衡虽然解决了单一架构一不小心就万事俱备的问题,但是服务器成本还是不能全面保护系统。我们必须考虑一旦服务器宕机如何保证用户体验。即如何缓解开奖瞬间的大量请求。V2:服务限流限流的主要作用是保护集群后面的服务节点或者数据节点,防止服务和数据因为瞬时流量过大(比如大量的前端缓存)崩溃,导致不可用.也可用于平滑请求。上一节我们做了很好的负载均衡,保证了集群的可用性,但是公司需要考虑服务器的成本,不可能无限制的增加服务器的数量。一般都会进行计算,保证日常使用没有问题。限流的意义在于我们无法预测未知流量。比如刚才说的抽奖可能会遇到:重复抽奖恶意脚本其他场景:热点事件(微博)大量爬虫这些情况都是不可预知的,不知道是什么有时候会出现10次甚至20次流量进来了,如果出现这种情况,再扩就来不及了(弹性扩是一句空话,你秒开给我扩)。弄清楚了限流的含义之后,我们再来看看限流是如何实现的。①防止用户重复抽奖重复抽奖和恶意脚本可以组合在一起,数十万用户可能同时发送数百万个请求。如果同一用户在1分钟内多次发送抽奖请求,则视为恶意重复抽奖或脚本刷奖。这种流量不应该继续请求,直接在负载均衡层就被阻塞掉了。可以通过Nginx配置ip的访问频率,也可以在网关层结合Sentinel配置限流策略。用户的开奖状态可以通过Redis进行存储,后面会讲到。②拦截无效流量无论是抽奖还是秒杀,奖品和商品都是有限的,所以后面大量涌入的请求实际上是没有用的。比如,假设50万人抽奖,准备100部手机。然后500,000个请求立即涌入。事实上,前500个请求会抢到手机。后面几十万个请求,不需要他去执行业务。按理说,暴力拦截,回到抽奖的最后就好了。同时前端也可以做一些关于按钮变灰的文章。然后想想怎么知道奖品用完了,也就是库存和订单的数据同步问题。③业务降级和业务断路器采用上述措施是否安全?不可能的。所以在server端有降级和熔断机制。很多人容易混淆这两个概念,通过一个小例子让大家明白(请大家想象一下??)。假设现在粉丝数超过100万,冲上了微博热搜,粉丝A和粉丝B都打开微博观看,但是A看到的是一场发布会的内容,B却看到“系统忙”.是的,B也能看到内容。上述过程中,首先是热点时间导致大量请求,出现服务熔断。为了保证整个系统的可用性,牺牲了部分用户B。B看到的“systembusy”是服务降级(Fallback),过一会又恢复访问,这也是熔断器(Hystrix)的一个特性。V3:同步状态回到上一节的问题,如何同步开奖状态?这就不得不提到Redis,它被广泛用作高并发系统的缓存数据库。我们可以基于Redis实现这种共享抽奖状态,非常轻量级,适合二级系统的共享访问。当然,也可以使用ZooKeeper。在负载均衡层,可以基于ZK客户端监控Znode节点状态。一旦抽奖结束,抽奖服务更新ZK状态,负载均衡层会感知到。V4:线程优化对于线上环境,工作线程数是一个至关重要的参数,需要根据自己的情况进行调整。众所周知,每一个进入Tomcat的请求实际上都会交给一个独立的工作线程来处理,所以Tomcat有多少个线程决定了处理并发请求的能力。但是线程数需要通过压测来判断,因为每个线程都会处理一个请求,而这个请求需要访问数据库等外部系统,所以并不是所有的系统参数都可以一样,需要调整参数自己。系统进行压力测试。但是如果给个经验值,Tomcat的线程数应该不会太多。因为线程太多,普通服务器的CPU无法处理,反而会导致机器CPU负载过高,最终崩溃。同时,Tomcat的线程数也不能太少,因为如果只有100个线程,将无法充分利用Tomcat的线程资源和机器的CPU资源。所以一般情况下Tomcat的线程数在200到500之间,但是具体多少需要自己测试,不断调整参数,看具体的CPU负载和线程执行请求的效率。在CPU负载可以接受,请求执行性能正常的情况下,尽量增加线程数。但是,如果达到一个临界值,机器负载过高,线程处理请求的速度开始下降,说明这台机器无法支持这么多线程并发执行处理请求,线程数暂时不能增加。V5:业务逻辑搞定。现在是时候研究如何做彩票了。彩票逻辑怎么做?在负载均衡层面,比如50万流量中有48万已经被拦截,但可能还有2万流量进入抽奖服务。因为抽奖活动是临时服务,你可以在阿里云租一堆机器,也不是很贵。优化了tomcat,服务器问题也解决了。剩下什么?MySQL,对,你的MySQL能承受20000个并发请求吗?答案很难,我该怎么办?将MySQL换成Redis,单机抗2万并发请求非常轻松。而且redis的数据结构set非常适合抽奖,可以随机抽取一个元素。V6:自上而下流量削峰,中奖通知剩余部分未优化。想一想:假设彩票服务在20,000个请求中有10,000个中奖,这将不可避免地导致彩票服务调用礼品服务10,000次。是否必须与彩票服务一样处理?其实大可不必,因为发送通知对时效性要求不高,10000个请求慢慢发送即可。这时候就必须使用消息中间件来限流削峰。也就是说,彩票服务将中奖信息发送给MQ,然后通知服务慢慢消费来自MQ的中奖信息,最终完成礼物的分发。这就是为什么我们会延迟收到中奖信息或物流信息的原因。假设两个通知服务实例每秒可以发送100条通知,则发送10,000条消息,延迟100秒。MySQL的压力也会降低,所以数据库层面也能抗下来。看一下最终的结构图:答案模板所谓答案模板,是指针对高并发问题的几种思考方向和解决方案。①单一职责是一个基本的设计思想。回想一下高中物理中的串联和并联。同样的道理,高内聚,低耦合。微服务的兴起,是因为将复杂的功能进行了拆分。即使网站崩溃无法下单,浏览功能依然健康,不会造成所有服务的连锁反应,像雪崩一样彻底瘫痪。②URL动态加密防止恶意访问。有些爬虫或者刷机脚本会导致大量请求访问你的界面,你甚至不知道他会传给你什么参数。所以我们在定义接口的时候一定要多加验证,因为调整接口的不只是你的朋友,还有你的敌人。③静态资源——CDNCDN全称ContentDistributionNetwork,是由分布在不同区域的边缘节点服务器组建立并叠加在承载网络上的分布式网络。通俗地说,就是把经常访问和耗时的资源放在离你最近的服务器上。淘宝图片访问,98%的流量都去了CDN缓存。只有2%会回源,节省了大量的服务器资源。但如果在用户访问高峰期图片内容发生大量变化,大量用户的访问会穿透CDN,给源站造成巨大压力。所以对于图片等静态资源,尽量放在CDN中。④服务限流上面已经说明了,分为前端限流和后端限流:前端:按键禁用,ip黑名单后端:服务熔断,服务降级,权限验证⑤数据预热可以使用定时任务(elastic-job)实时查询Druid,并将热点数据放入Redis缓存中。想一个问题:比如现在库存只剩1件了。我们有高并发。4台服务器一起查询后,发现还有1个存货。变成了-3,是的只有一个真的被抢到,其他都是超卖。我应该怎么办?答:可以用CAS+LUA脚本实现。Lua脚本类似于Redis事务,具有一定的原子性,不会被其他命令排队,可以完成一些Redis事务性操作。这是关键。写个脚本把存货判断库存扣减的操作写在一个脚本里,丢给Redis去做。那么0之后的一切都会返回false,对吧?如果一个失败,你修改一个开关直接阻止所有请求。⑥削峰填谷熟练掌握一个中间件会给你加分不少,消息队列逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠传递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。现在市场上有很多主流的消息中间件,比如老牌的ActiveMQ、RabbitMQ、流行的Kafka、阿里巴巴自研的RocketMQ等。在最原始的MQ中,生产者首先将消息投递到一个叫做“queue”的容器中,然后从容器中取出消息,最后转发给消费者,仅此而已。我今天学到了很多东西。相信大家都对高并发系统有了初步的了解。面试官不会被问到无话可说,但要学好,任重而道远!作者:某IT编辑:陶佳龙来源:转载自公众号的A编码(ID:a18741865729)
