高并发服务如何优化,这里指的是qps大于20万的在线服务。请注意,它不是离线服务。在线服务将面临哪些挑战?不能做离线缓存,所有的数据都是实时读取的,大量的请求会发到在线服务上。服务的响应时间比较高,一般在300ms以内。如果超过这个时间,用户体验会急剧下降。数据量较大,如果单次qps超过50W,单次1kb,50万是5GB,1分钟30G,对底层数据存储和访问压力巨大~如何处理这些棘手的问题,本文将讨论.1.对关系型数据库说不一个真正面向c端的大型互联网服务,不会直接使用数据库作为自己的存储系统,不管你是分库分表还是使用各种优秀的底层的连接池等。Mysql/oracle在面对大规模在线服务时,有着天然的劣势。不管怎么优化,都难以承受qps大于50万流量的冲击。所以换个思路,我们必须使用nosql类型的缓存系统,比如redis/mermCache等作为我们自己的“数据库”,而mysql等关系型数据库只是一个异步写入数据查询的备份系统。场景示例:京东双11主会场有部分商品上架,这些商品在会场开始上架时直接写入redis。上架完成后,通过异步消息写入mysql。c端的查询直接读取redis而不是数据库。对于b端查询,可以使用数据库进行查询。这部分流量不是很高,数据库肯定能抗住。2.多级缓存大家都知道缓存是高并发提升性能的利器之一。而如何用好缓存进而用好多级缓存是我们需要思考的问题。Redis是目前缓存的首选,单机可以达到6万到8万的qps。面对高并发,我们可以通过手动扩展横向容量来应对qps可能无线增加的场景。但是这种做法也有缺点,因为redis是单线程的,会出现热点。虽然redis内部使用了crc16算法进行hash和分散,但是同一个key还是会落在单独的机器上,会增加机器的负载。Redis通常存在缓存击穿和缓存穿透两个问题,尤其是在秒杀的场景下,想要解决热点问题就变得更加困难。这时候就必须考虑多级缓存。在典型的闪购场景中,单个sku产品的qps会在开售的瞬间急剧上升。这时候我们就需要使用memeryCache来屏蔽一层。memeryCache是多线程的,并发性比redis好,自然能解决热点问题。有了memeryCache,我们还需要localCache,本地缓存,这是一种用内存换取速度的方式。本地缓存会访问用户的一级请求。如果找不到,就去memeryCache,再去redis。这个过程可以阻塞百万级的qps。3.多线程记得刚入行的时候,每次面试都会被问到多线程。当时,我惊呆了。多线程这么厉害?为什么说多线程,为什么要用多线程,你做不到吗?为了说明这个道理,我举个例子:我曾经优化过一个界面,这是一个典型的场景。原来的方式是循环一个30万到40万的列表。list执行的操作很简单,就是读取redis的数据。读取一次大约需要3ms。这是一种同步方法。预览环境中的测试直接需要30秒+超时。后来优化的方法是将原来的同步调用改为线程池调用。线程池的线程数或者阻塞队列的大小需要自己调优。最后实测接口rt只需要3秒。足以见多线程的强大。在如今的多核服务中,如果不使用多线程,就是对服务器资源的浪费。这里需要说一下,在使用多线程层的时候,一定要做好监控。你需要随时知道线程的状态。如果线程数和queueSize设置不当,会严重影响业务~当然多线程也需要分场景。线程和多线程是一种浪费,因为多线程调度会导致线程在内核态和用户态之间来回切换。如果使用不当,会产生不良影响。4.降级和熔断降级和熔断是一种自我保护措施,这和电路上熔断器的基本原理是一样的,防止在面对无法控制的巨大流量时,电流过大引起火灾等请求,很可能会破坏服务器的数据库或redis,导致服务器崩溃或瘫痪造成无法挽回的损失。因为我们的服务本身需要有一个防御机制来抵御外部服务对我们自己的入侵,导致服务受到破坏而引起连锁反应。降级不同于融合。两者的区别在于,降级是在不影响主链接的情况下,在线上关闭主链接的一些功能。在熔断的情况下,表示A请求B,B检测到服务流量过大无法启动熔断,则请求直接进入熔断池,直接返回失败。如何选择使用哪一种,需要结合实践中的业务场景来考虑。5.优化IO很多人会忽略IO的问题。频繁的连接建立和断开对系统来说是一个沉重的负担。在并发请求中,如果存在单个请求的放大效应,那么io会呈指数增长。比如主会场的产品信息,如果需要产品的某个具体细节,这个细节需要调用下游单独获取。随着主会场产品的火爆,产品越来越多,一次必须通过的产品数量X下游请求数量。在海量的qps请求下,IO数量被占用,大量请求被阻塞,接口的响应速度会下降。呈指数下降。所以需要一个批量请求接口,所有的优化都是一个IO六,谨慎使用重试重试是一种常用的临时异常处理方式。常见的处理方式是请求服务失败或者写入数据库重试,使用重试时必须注意以下几点:控制重试次数和重试间隔。您必须衡量是否应配置重试。之前我们上线有bug,kafka消费卡顿严重,word消费时间10多秒。看了代码发现是重试次数太多导致的,而且次数不支持修改配置,所以当时唯一的办法就是临时改代码再上线。重试,作为业务的二次尝试,大大提高了程序请求的成功率,但也要注意以上几点。7.边界案例的判断和自下而上作为互联网老手,很多人写的代码都不错,但是经过几轮故障review,发现很多造成重大事故的代码都缺乏对一些边界问题的处理。犯的错误很简单,但往往这些小问题就会酿成大事故。曾经回顾过一次重大事故,后来发现最终原因是空数组没有判断为空,导致下游rpc为空,下游直接全量返回业务数据,影响百万用户。这段代码改起来很简单,但是需要自省。一个小小的缺陷造成了一场灾难。8.学会优雅地打印日志。虽然有arthas这样的利器可以帮助我们排查问题,但是对于一些比较复杂的场景,还是需要日志来记录程序数据的,但是在大流量的场景下,如果打印全量日志,对线路来说就是灾难.缺点如下:(1)占用磁盘严重,估计如下,如果接口的qps在20万左右,日志每秒几G,一天几千G。(2)需要输出大量日志,占用程序IO,增加接口的RT(响应时间)如果需要解决这个问题:我们可以使用限流组件实现一个基于日志的组件电流限制。令牌桶算法可以限制打印日志的流量,比如每秒只允许打印一条日志;基于白名单日志打印,只有在线配置了白名单的用户才能打印出来,节省了大量无效日志的输出。小结本文讨论了高并发服务在大流量下的一些基本注意事项和对策。当然,实际网上的比现在的要复杂。这里只是一些建议。在路上保持敬畏,继续探索。更好的培育C端服务,做更好的互联网应用。
