本文根据蔡磊于唯品会“唯享·技”分享会2018年6月9日上海场分享内容整理而成。
“唯享·技”是唯品会对外分享的平台。希望以此平台分享唯品会的技术研究案例与实践的结果,也能听到更多技术方向上的干货分享。
唯有分享可以让技术的脚步更快,享受技术钻研的乐趣,在此让我们听见技术流~
本文讲师
蔡磊
任职于唯品会基础架构服务化团队,负责API Gateway/Reverse Proxy的设计与开发,性能调优与疑难杂症。在营销系统与微服务架构方面技术经验积累深厚。
目录
janus背景介绍与技术选型
线程模型与优化
netty深度调优与实践
性能瓶颈快速定位工具与方法论
通用计数器实现
一个有意思的gc问题
踩坑后的代码规范总结
janus背景介绍与技术选型
先说标题中的亿级网关,我的角度是用户量亿级,tps按小时算是百亿级别以上,主要是Api网关。
我们定位为:高性能、高可用、可扩展、可管理、可治理、安全的网关产品,作为唯品会所有流量的入口。
网关价值:所有中心化控制点都可以统一控制。
我们底层技术选型从Servlet到Akka到netty。Servlet就跳过,底层性能模型差太多;用Akka 这种纯异步的方式是比较好的,但是作为网关的话,可能不算适合,它在线程模型上是有一些问题的,这块我们后面会讲到;所以现在基本上主流的是用netty来做这一块。
我们现在这块主要做一个ApiGateway,但是我们考虑到一个问题,我们唯品会这边有部分Php程序,即一些老的程序,它们现在也要容器化,并且不止是Api接口有页面存在,容器化了之后就涉及到一个问题,动态的一些发现,包括要做一个对外的统一控制,所以我们也把我们的Api网关进行了一部分裁剪,然后做了整个的一个反向代理的网关 。
Api网关需要一个一个配对对外暴露的,亚马逊的、阿里的这块都是这样一个方式,但是我们这里就说反向代理,通常反向代理是会支持一个正侧匹配,还有反向代理其实不是Api,因为有页面,可能是动态页面有一些静态化的东西,所以需要做一个反向代理的网关。我们去年就说这块定义上,只要有页面就走反向代理。
线程模型与优化
前面是一个介绍。然后是线程模型优化,这块我们遇到一些问题,也涉及到一些调优,包括在后面讲netty的时候也会说到这一块。
我们现在的一个性能数据,因为跟机器很强相关,单纯的谈数据,抛开机器与压测场景是没有什么意义的。我们在常规的一个普通12核机器上跑,在payload大小是1K的情况下,这种请求转换为后端的http,大概性能是会在7万多到8万之间的一个数据。如果后端是一个RPC的话会到9万左右的一个数据。如果payload大的话,这块性能是会下降的,比如说100K的,测下来大概后端http会有大概4万多5万,因为后端超过100K我们会进行压缩,压缩这块对CPU的消耗很大。但我们最后测下来的一个相关的瓶颈,没有在IO方面反而存在CPU方面,只要我们CPU增强,我们的tps可以继续涨。
线程模型这一块,用到netty经典的boss worker模型。这个线程池其实是有一些可做的点。请求来的时候,怎么样去让这个worker线程更加均衡一点?比如说一个请求发过来之后,从接收它会转到worker线程上去,boss线程接收到这个时候去读的话,会在worker线程转给它去读,下面问题就来了,那后端可能是一个RPC 的调用,还要发一个RPC到后端,那怎么让这个性能更好?
先共用线程池,共用worker线程池比较简单,在操作上比较好做。但是基于这个做了之后,后面的优化点出来可能是比较难做的一点。接受请求之后最好是不切换上下文,虽然共用worker线程池,但是不知道大家看到了netty的源码没有,有个切换过程,就是我们怎么减少这个切换。比如说现在worker线程通常是和CPU数量是一样的,可能是24核的,那就24个线程,当一个请求来的时候,再去调后端的一个服务的时候,那么调后端服务怎么不切换这个?
因为去调后面的一个服务也是个异步的,整个网关是全异步化的一个过程,这个时候的请求会去看netty做一个判断,是否在当前的线程里面,如果没有就提交一个队列,放到队列里面,然后去触发,这时候线程会不停的从这个里面拉东西过去执行的,所以最好是不要切换。我们测完切换和切换成本大概是10%的一个性能数据,如果切换了性能数据下降10%,不切换10%的一个性能数据会有提升。
我们这块做的就是有一些复杂点,在这个切换上需要通常后端连,比如说是http到http, 后端http就是一个连接池,拿连接的时候,怎么判断连接就是当前线程所在的连接?因为有很多连接,可能分在不同的worker上,需要一个标志位,这个标志位可以选择线程ID或者是线程特点的一个东西去做,优先拿这个,如果没有再去拿其他的。也看你的模型,http这种,可能连接用一百条连接,均匀的分在这个24个模块线程上,这种不是完全的一个异步通信。
我们做RPC,最后的连接不会太多,因为RPC中我们自己做的是全异步化的一个方式,全双工的方式,RPC可能一条链接就会跑很高的pps,可能不是等于CPU数量,所以可能这个worker线程上并没有这样一条连接,就需要去其他地方去,但是可以优先判断一下,如果这里面有就从这里面去,这样性能大概有10%的提升,但是这块复杂度比较高一点。这是我们在线程池上的一些优化变化。
再下来一点就是插件化的问题。网关如果做插件化的话,我们可以减少一些发布的问题,插件做成一个类似于配置中心数据下发的概念,现在网关是每个域来做一个接入,一个业务来接入之后,可能有些相关的配置,但是这个域可能有些特殊的权限认证,或者有些特殊的线流,或者有很特殊的一个安全处理,所以这部分做一个插件形式的下发之后,怎么让它去运行呢?
这块线程池的一个模型,我们现在是想做一个共享线程池。首先大家先共享,共享之后有个卡的问题。大家可能做这种都会遇到线程池隔离,线程池隔离之后,就会遇到线程池隔离导致到底怎么分配的问题,线程池隔离是快慢的一个隔离,还是怎么样的隔离?我们现在是先用共享一个线程池,如果不够给这个域再单独分,就不会导致卡的一个问题。
netty深度调优与实践
netty来调优实践。前面讲的是worker线程池的共享。但是我们通常在写代码、在做微服务的时候,会经常提到尽量无状态去处理这些东西,我们的代码无状态化,但是有些东西确实是很难无状态化的,你可能要有一些有状态的东西,或者是代码里面可能涉及线程安全的一个问题。一些是非线程安全怎么去做?怎么保证它的性能?这块我们也遇到这些问题。但是我们结合参考点netty本身的一个经验,多读netty的源码,多去了解它的一个实现机制,就会找到一些方式去做。
我们这块参考的比较多的是用netty去做。举个例子,我们有一些加密的类在使用JDK,或者是用一些其他的,这些都是非线程安全的,那其实我们的模型是跑在worker线程上了。刚才讲的这个模型,其实网关通常做netty都会遇到几种选型的问题,netty本身运行在worker上,但是业务线程怎么处理?业务到底怎么做,到底是跑在在netty 的worker上,还是单独开新线程池,这里单线程池的切换成本性能损耗较大,但带来的好处就是你可以对这个线程池有很多独特的管理,可以做定制化的内容。
现在由于常规的轻量级 CPU运算,处理速度特别快,然后都是耗CPU的,所有全部是运行在worker上。包括说独立的线程池,都会遇到的问题,怎么去做这种非线程安全的代码?线程内共享,包括netty会做很多种优化,在单独的线程内享,对线程内是无锁的,这个很像AKKA的一个方式。用AKKA的时候会发现AKKA其实就是不太去考虑线程安全性的方式,因为它本身是一个线程去运行一个actor,它并不会有并发的问题,那我们参考这个netty也是类似的,其实是一个代码运行在一个worker线程上,也不会有这相关的问题,只需要在里面做一个判断是否在当前线程,如果不在就提交到这个队列里面去做处理。
一个非线程安全,只要线程内共享,每次从当前线程获取,比如说我们用的线程内Map这种安全方式,netty提供了一些比JDK更好更快的FastThreadlocal,我们也是在里面来做模块线程的共享,整个的这种非安全性的代码就可以进行处理掉。整个网关就不会出现任何一处有锁或者有卡的问题,如果一定有锁,比如说涉及到一些多个线程做共享状态改变,那就有准确性的问题,要求必须准确,尽量通过cas的方式来搞定,而不是说强行加锁,通常用自旋方式来把这一块解决掉,整个性能是比较好的。说到日志这个点,后面也会说到日志的优化,我们日志这个点,也是全异步。
第二点就是netty这块的一个模型,这点可能在各个网关的优化点上说的比较多,就会聊到epoll底层的一个网络的这个模型,包括之前有同事问这个不是用epoll ,只看到选择器,好像没有发现什么代码。其实这个epoll模型全是JDK底层帮我们自动选择了。比如说跑到MAC上,它可能是用另外一种网络模型,不同的版本的内核上,会选择不同的模型,如果觉得这块你不太确定的话,比如说用2.6以上它就会自动epoll,不太确定可以加参数,在启动参数的时候,强行指定它必须用那个模型。
这块对比netty觉得JDK实现这个C代码是有些可能没有自己做性能好吧,参考tomcat基于epoll来写了一些C的代码,对比JDK自带的,底层epoll细节使用上还稍微有区别。netty号称是比JDK自带的性能好,这块其实不需要安装什么东西,只需要引入一个native就可以了,然后在里面写法上就是有改变,比如说在写netty的时候,在里面构建的时候几个东西稍微需要改几个参数,名字改一下就行了。这块性能话我们测下来是有一个提升的,CPU会稍微低一点点,性能没有太明显,就CPU稍微比平时负载会低一点。
第三点就是个bytebuf的问题。netty在官方文档有几种对比。一个是池化的内存和非池化的内存的一个对比,还有一个是直接堆外内存和堆内的一个使用对比,组合下来就是四种。池化的堆外这个对外的性能是比较高的,但这一块也是根据业务来。通常抛开业务场景来谈这个性能都会有一些问题。比如说后端如果是一个RPC调用,因为前端是http这种APP端,请求都是http,发过来之后,需要把它转成RPC,所以这就涉及到必须要去拆包,拆了之后去转,那其实就意味着必须把它堆内,可能堆外性能并没有明显优势。跑一下确实性能上数据是没有太大差距。
但有另一个场景——有后面的http的,http很多时候我们不去解析,直接把它写出去,不解包的话性能也会很好,那就用堆外可能性能是非常好的。所以这块,大家结合自己的场景做压测,通过压测来得出这个东西,而并不是直接看官方数据,按官方数据说哪方面性能好。
再下来一点,就是说这个boss的线程,通常我们demo上都会有一个问题,包括大家写一些代码copy的一些例子都会有这个问题。就是IT在启动的时候都会把worker默认设置成CPU核数,这个是根据最佳经验得出来是没什么问题,但是就是这个boss线程再去接收的话,线程通常都会是CPU的一半,写这样的一个代码是有一些问题的,boss线程其实没有用到CPU的一半,它其实根据端口决定了。如果去看它的源码,就会发现这样一个问题:它完全是根据端口来决定,就是有多少端口,就用多少线程。当然如果端口特别多,比如现在有20个端口,可能启动的时候又在http上监听,又在https做了什么其他的一个内网RPC,可能有多个端口,端口可能有24个,那boss线程设置12个还是比较合理,但是其实只有一个网关,对外只有http请求,所以我们只有一个端口,写多了也没有用。配多少其实也没有影响,只不过这个会给别人疑惑,因为在真正启动过程中,其实不会启动多个,所以倒也没有这个损耗。
然后再下来一点是我们最近正好在讨论的方案,netty这块解析,handle我们准备做一个优化。就netty目前大部分都是拿到完整的http做业务处理,但是我们其实有很多可以前置,不需要解析完整的http。比如说,我们通常是一次请求,或者是我们的一些非法请求,包括限流防刷这种非法的请求,我们其实只需要根据ITP的一个请求函和请求头,我们就可以做一个提前判断,并不需要把它完整地拆包。这块就需要我们把netty的http的解析重写,然后再结合我们future做一些优化。netty本身对http是一个状态机的方式解析。
代码参考netty的一些东西,比如说我们重度使用netty的InternalThreadLocalMap,包括前面说的fastthreadlocal。线程安全的,我们就可以在这个线程内共享,而不需要频繁的去创建,包括我们用了一些数组或者是其他相关东西,大家可以看看这块的源码,这块也是用的比较多的。
目前是整个唯品会这边都是在跑的JDK7,我们用JDK7的话,JDK8的一些东西——比如说这个concurrenthashmap这种优化——我们可以用PlatformDependent享受到这些方面的东西,但是用netty的时候去看一下,4.1的代码就把这块移除掉,因为不支持jdk7了,没必要了。4.0的代码是有了,netty会把JDK8里面的一些点明显的优化点挪过来。
然后最后一点就是worker线程的一个问题。前面也讲到了,就是说这个线程尽量不去切换上下文,怎么去做到这个切换的问题,尽量保持在一个线程上整合请求来做,比如说现在可能要两次外部请求,就要常规的一个请求来做,首先要进行一次外部调用认证,过了之后再去调后面的服务,然后整个这个流程都不切换的话,性能会数据会跑得非常好。
性能瓶颈工具与快速定位
再下面一点就是性能瓶颈工具快速定位。这块我司正好最近开源一个工具。大家可以关注下,相关这几点都有。这块我觉得是排查问题的利器。通常我们在做优化的时候,到底优化哪个代码?是去一段段扣代码还是怎么弄?我觉得还是不要扣代码,扣代码这个价值点很难找,你可能觉得这块可能是不是怎么样更好,是利用一个string拼,如果换一个方式是不是性能更好?可能效果并不是那么明显。
还是尽量用工具,用工具从系统底层到整个jvm部分最好是能贯通起来,贯通起来也方便于排查问题。如果把这块从系统底层到jvm层整个通了之后,排查问题的速度也比较快,然后定位问题比较快。
比如说火焰图,我们跑其实是会跑到火焰图数据的。我们性能测试是会看一下跑出来的是不是符合我们的预期。第一个系统函数底层的调动是否符合我们的预期,系统上的一个开销,然后到jvm方法栈上的一个消耗,这块比如说比较简单的作用,谷歌的火焰图这个工具生成netfilx火焰图,jvm上采样你可以设置采样频率,采样下来之后会拿会拿到一个详细的一个图。
这儿有一个图,这是我们整个jvm裁剪的一个图,这一个火焰图,我们跑出来一个数据,它的底层是比较大的,比如说这块是一个read,会看到上面的一个调用到底是什么样一个情况。就会看到我们整个网关跑出来一个结果,当然这里面又有细节,这就是怎么看火焰图的问题了,快速识别一些性能问题。
下面还有java 自带,就是jmc这块自带的一个分析工具,它其实也会看到一个消耗,是整个的一个方法调用,可以看到哪一块消耗CPU比较多,但jmc的话是觉得没有火焰图这么直观,具体看分析哪些问题,各有特点。火焰图可能就更多的在系统函数上,jvm可以做到就是整个都可以看到。
剩下就是在写代码的时候,比如说一个选择,举个上周的例子,我们在做这种base64的一个转码的过程当中,可能一些特殊需求需要转,那现在有这么多工具类,到底用哪个工具的性能更好呢?通常做法是写一个单元测试来跑一下、测一下,但是这种单元测试很不可靠,因为这种单元测试跟最终你去jvm运行跑代码其实区别还是很大,它会没有去做编译优化,有很多问题。
java官方也是推荐直接基准测试,用基准测试来测它到底性能怎么样,但是这种基准测试写的话,最好去把官方的demo读一遍,它里面其实有很多坑,比如说很容易写一个for循环在里面,for循环还很容易被优化掉。还有就是解决测试的时候,其实最后值并不返回,它去跑的时候就会把优化掉,系统去跑一块代码,就认为最后这个值根本没有用到,没用到优化就全部盖掉,全部盖掉之后,跑出来你会发现系统数据很好,但是实际并不是这个样子。所以这块有很多坑,写它之前最好去把官方的demo通读一遍,会避免很多东西,它的demo也比较详尽,避免很多错误的一个写法。我附了一个日常的demo,这就是上周我们跑64的一个demo写的。比如刚才的坑,说明这样去运行的时候,没有返回就不会处理,它其实提供一种方式上处理,就是你把它消费掉,这块的话会跑一个性能数据出来,最终结果会是这样的一个情况。这样话就会看到那到底选择哪一个比较好,比如说我们每秒的调动次数,那可能下来发现确实JDK8自带的这个性能比较好一点。
日常jmh的demo
高并发下的gc优化与排查
下面聊到这个gc优化与排查。其实对网关来说很难容忍这个gc问题,特别是高并发的情况下,比如说在上次做促销的时候,流量很大的情况下,由于网关发生gc导致整个超时了是很大一个问题,所以我们在gc上尽量控制频率,大概现在的情况下是我们能控制到一个月一次cms gc,但是要想网关流量很大,每天都有流量过来,这种大促、更别说这种抢购之类导致的一些流量。所以我们在这块做了很多优化,整个控制它的一个频率,后面会讲到。
然后的话是我们一些常见的踩坑的问题,比如说我们会遇到熔断、指标统计上的一个踩坑。比如说熔断分方法的和服务器的,大家都知道垄断要写这个计数器,我们可能要统计容纳多少秒内失败次数等于多少,比如说我定义是十秒内失败次数——请求数(就有几个维度)请求十次,十秒内连续失败,或者按几个维度来,失败率是60%,我就认为这个服务不好,应该熔断。
这就会导致我们会去统计很多这种指标,就会带来一个问题,我们的统计量太大。比如说我们当时是Api网关,是一个一个Api没有什么问题,但我们现在反向代理就会带来这个问题,因为反向代理涉及一个通配,后面的这个方法很多,所以这个带来问题就是熔断上统计会导致我们内存爆掉。指标统计容量上,也是反向代理的时候也会遇到一个问题,因为我们请求的url是很多的,无数的url过来,我们去统计的话,最后遇到这个问题,我们通常会统计1秒钟、10秒钟这种各种的一个指标,或者说1分钟5分钟,这种都会带来一个问题。
包括这块顺带再说一下kafka,我们用到kafka这个坑是跳不过去的,原生代码里面自带的采样会统计1分钟5分钟15分钟,但是我们网关流量很大的情况下,根据jvm的一个原理,肯定首先对象进入这个年轻带,在年轻带里面倒腾几次,就老年代。流量很大,我们这块这gc就会特别频繁,它就很快进入老年代,就会带来这个问题。相当于它的统计指标全部在老年代堆积。堆积之后,关键是它采样采样指标是1分钟5分钟15分钟,old区直线增长,就会触发我们的cms gc,这块我们最后的一个就直接把这块代码干掉,因为我们没有办法统计资料,就不用统计指标,我们不想它这个给我们带来的问题。
这块也是old区的一个问题,就是说我们当时之前是15天左右发生一次,上线之后发现每天稳定增长400兆,就去排查这个问题,反查发现kafka的一个问题,之后发现数据结构里面有个跳表,我们反查了一下代码发现只有kafka在用,然后这块就跟踪到kafka,因为已经找到这个到底是谁关联它,这个反查到了之后发现是kafka的问题。我们做了一个空实验之后,发现这个问题就解决掉了,就回到我们原先那个频率上。
说到这个日志,我们刚才说的日志其实是权益部的一个输出日志,这块我们是按log4j2的一个方式处理,就是二点几的版本,但我们在用的时候2.6还没出来,但是后面2.6出来是很推荐大家试一下,因为这块还做了什么gc优化,它号称是减少了很多gc问题,我们测下来也是基本上gc耗时也会有明显的一个降低。
这一块的话是说我们当时上线的时候,其实没有设cms gc到底什么时候触发,比如说都是区,可能通常大家会设置到75%就触发,然后不设置,我们觉得这块让jvm去做可能更好一点。我们不知道什么时候触发是合理值,既然我们不知道就让jvm自己来做动态调整。但发现一个问题,就是现唉网上的资料都说,包括我们去读这个几本jvm的书,他们也都会说,这个JDK你不设的话,它默认是68%触发这个cms,我们线上上线之后发现并不是,我们92%才触发,我们就看了一下我们jdk的一个版本,我们版本是7,就拿了一个open JDK的原代码算了一下,算出来确实是92%出发。
这块举这个例子意思是看到这种问题,其实通过代码是很容易找的,我们直接可以将JDK版本拉下来之后去找,并不需要去相信查各种资料,发现没有,到底是多少触发?这块代码里面很明显,其实也不复杂,虽然说C++这种代码,其实我们只是去找一些这种配置的话,很容易快速识别的。代码里面是没有秘密的。
还有一个问题就是高IO导致jvm停顿的问题,这种测试会发现一个问题,在连续压测大概四五个小时,就会发现我们的有些四个9的数据可能会出现有一秒增加,我们大部分时间都是在一毫秒的一个范围,但是发现有四个9会有一种一秒的特别长的一个时间,觉得这可能是一个问题,我们线上会这样是不太能接受的。我们线上基本上时间都是经过网络的调动后端返回这个时间,请求发起进来出去的时间大概都在一两毫秒,所以觉得这块我们是不能接受这一点。然后就去排查问题,就发现高IO导致jvm的停顿这样一个问题。
一个是把gc日志并不直接放磁盘上,放内存文件系统上必须加上这个参数,因为jvm在运行,它会输出、关联操作好几个文件,有一个文件是用一些jvm的命令做一些分析用的。
通用计数器实现方案
后面我们就举例子来讲做网关的时候,因为做这种对质量要求比较高一点。我们这个承载的是一线流量,其实影响是非常大的,对性能也是要求很高,而且又涉及到改动很频繁。各种业务方可能有些需求点要去为他们做一些定制化的一些开发。当然可以用插件化来做,但是这块其实有很多改动,所以就举一些场景,到底应该怎么做?
比如让你来写网关的代码你应该考虑哪些点?我们通常做一些抽象,会用到一些通用技计数器,我们以这个场景来讲,要去做一些考虑。这也是拿我们平时遇到一个点来发散的讲,就说网关到底哪些问题?
我们通常会用一个通用计数器这种东西,因为我们会做很多计数,比如限流、后端的服务:我们要保护后端服务我们来做限流;再比如说我们要做防刷,我们可能要根据ip或者根据session各个维度来做防刷,做成保护安全上的一个东西;或者有一些技术上的一些需求,比如说是熔断,它其实也是一个技术上的需求,这种技术怎么来做的?
想比较简单,就Java自带的这个原子类的这种cas,我就直接上面加就可以。其实也会有问题,原子类它的维度比较单一。来看这样一个实现,它这里面就有一个计数的概念,还有一个时间的概念,这两个概念我们就需要用时轮的方式来实现。那时间轮的方式就会带来一个问题,用时间轮要有一个清晰的概念在里面,包括时间轮怎么快速去查找(比如说你看时间轮列表的话,性能上一个问题),包括考虑用时间轮怎么去避免产生GC或者一系列的问题。
我们这块要实现一个滑动窗口的一个时间轮。那首先第一个就是选择,是提前清理的话,就使用时清理。我们的最后选择是使用时清理。
这样一个统计,实现上首先用数组去实现,做对应上的一个映射数组里面的对象,但是这就涉及到我们要怎么去清理它?我们并不是直接加一把锁去清理,每次去锁住,它每个线程都会遇到这个锁,性能会降很低。所以我们是用一个自旋锁的方式,因为我们其实是不停的请求进来,我们去自旋一下就可以了,这块时间的话性能是比较好的。
然后这块还有一个问题,拿JDK8在后面供应对象是明显的比前面这个cas一个原生对象性能高很多,但为什么不用后面这个?这就是一个内存上的问题。比如说前面用度量工具测,测完之后发现前面完全满足我们的需求,后面的话确实性能会好,但是后面的内存又会高很多。大概在60万的对象,后面是45兆,前面是一点几兆,就这种内存上的一个对比,在内存上我们接受不了这个。
包括这种对象,用完之后要清理掉,对象清理掉之后,它就会带来gc问题,这个对象已经在新生代里面倒腾了几次,因为流量很大,容量很大的时候,就不停的ygc。ygc几次就进老年带,那又会带来老年代这个对象增加,清理掉,解除这个关联关系之后,老一代又会直线增加,我们想控制1月一次的cms这个目标就很难达成了。所以这块我们的一个需求就有很多点需要考虑,有相关的一些点考虑到之后,我们还要把它做成一个抽象工具,要用的话直接调就可以做,这些考虑点全部在里面,而并不是说我们每个人去实现一套。
一个有意思的gc场景
再说一个非常有意思的一个gc场景,其实我们有些gc场景会用到这种lru的方式去统计,因为不够用。比如说我们要统计一些东西,我们只能用最近最常使用的一个算法去做,但是这就会带来几个问题。我们如果把它设置很大,准确性倒是挺高的,准确性提高了之后,gc问题就来了,如果设小了,但是又准确性不够,那我多小合适了,这个需要根据流量来。
这个问题呢可以进一步抽象,其实会带来很多问题,这个问题进一步抽象到说所有能熬过ygc到了老年带,并且把它反复创建问题解除,其实都会有这个老年带增长的一个问题。
像广泛的一些日常使用,在开发日常代码的时候肯定会涉及到连接池,这是一个比较常见的例子。你的连接池会设有一个共享连接,几次ygc后也会进入老年带。会有配置一些参数去把这些共享连接给关掉动态变化数量,检查闲置连接之类的会清理掉,这个对象就在老年带里待着了。但是在老年带里的连接数发现不够又会去创建它,创建之后就会随着流量不停的在老年带里面创建对象,就会带来这个问题。
唯品会AR系统介绍
最后一点,我们写了这么多代码之后,发现一些规律性的东西,尽可能复用对象、尽快释放对象。如我们在创建一个对象的话,能复用尽量复用,比如说我们用了一些计数器,移除的时候,其实放在一个队列上,要创建什么首先从队列上去取。队列没有,我们再用一个减少这种垃圾对象地方创建。
netty还是很有参考价值的,如果把源码过一遍的话,可能会有很多想象不到的点,会对你有些价值。
尽量状态无锁,如果一定要有状态,先走线程内共享,就是模块内的共享,之后如果发现还是解决不了问题,那没办法,只能cas,尽量选择小的一把锁,怎么小怎么来,而并不是加一把大的读写锁或者怎么样,在这可能做一个循环、一个自选锁来做。
最后一点,这里比较重要,在写作代码,因为网关的代码这块入口流量如果有问题会影响很严重。当然我们通过一些发布的方式可以避免,但这块尽量去了解你写的每一行代码背后的逻辑。内部到底是怎么运行,再用这种基准测试去度量,度量你写的代码到底有多少的性能损耗,到底是怎样的。
https://mp.weixin.qq.com/s/dbgLn55Ki9pfdEerbu7awg