当前位置: 首页 > 后端技术 > Java

近期业务大量突增微服务性能优化总结-4.增加对于同步微服务的 HTTP 请求等待队列的监控

时间:2023-04-01 13:40:55 Java

近期大量业务激增的微服务性能优化总结-4.增加对同步微服务的HTTP请求等待队列的监控核心业务接口遇到的性能瓶颈不是一个单一的问题,而是几个问题的组合:我们解决了一个之后发到网上,然后发现还有另一个性能瓶颈。这也是由于我经验不足,导致我没能一下子定位解决;而我对整个后台团队有着顽固的自尊心,不想通过大量的横向扩张来熬过压力的巅峰,导致连续几个晚上在线出现分歧度的问题肯定会对我们的业务增长产生影响。这也是我不成熟,不得不反思的地方。本系列文章主要记录我们的后台微服务系统针对本次业务增长的一般技术优化,业务流程和缓存的优化只适用于我们的业务,这里不再赘述。本系列将分为以下几个部分:改进客户端负载均衡算法开发日志输出异常栈过滤插件改进x86云环境异步日志等待策略增加同步微服务HTTP请求等待队列的监控并部署在云端。注意实例网络流量达到上限导致请求响应变慢对系统关键业务增加必要的侵入式监控增加对同步微服务HTTP请求等待队列的监控微服务,基于spring-webmvc的同步微服务做没有很好地处理客户端的请求超时配置。当客户端请求超时时,客户端会直接返回超时异常,但是调用的服务端任务在基于spring-webmvc的同步微服务中不会被取消,但是基于spring-webflux的异步微服务会被取消。目前,在同步环境下,没有很好的方法来取消已经超时的任务。我们基于spring-webmvc的同步微服务,HTTP容器使用Undertow。在spring-boot环境下,我们可以配置处理HTTP请求的线程池大小:server:undertow:#下面的配置会影响buffers,buffer会用于连接到server的IO操作#如果每次都需要ByteBuffer应用中,堆内存的ByteBuffer需要经过JVM内存分配过程(TLAB->heap),直接内存需要经过系统调用,效率很低。#所以一般都会引入内存池。在这种情况下,它是“BufferPool”。#目前UnderTow中只有一个`DefaultByteBufferPool`,其他实现暂时无用。#这个DefaultByteBufferPool相对于netty的ByteBufArena来说非常简单,类似于JVMTLAB的机制#对于bufferSize,最好和你系统的TCPSocketBuffer配置一样#`/proc/sys/net/ipv4/tcp_rmem`(forread)#`/proc/sys/net/ipv4/tcp_wmem`(forwriting)#当内存大于128MB时,bufferSize为16KB减去20字节,用于协议头buffer-size:16364#是否分配directmemory(NIO直接分配的堆外内存),这里开启,所以java启动参数需要配置directmemory大小,减少不必要的GC#当内存大于128MB时,默认为使用directmemorydirectBuffers:truethreads:#设置IO线程数,主要执行非阻塞任务,它们会负责多个连接,默认设置是每个CPU核心1个读线程和1个写线程io:4#阻塞任务线程池,当执行类似servlet请求阻塞IO操作时,undertow会从这个线程池中获取线程#其值设置取决于系统线程执行任务的阻塞系数,默认值为IO线程数*8worker:128后面的线程池是jboss的线程池:org.jboss.threads.EnhancedQueueExecutor,spring-boot目前无法通过配置修改这个线程池的队列大小,默认的队列大小是Integer.MAX我们需要监控这个线程池的队列大小,并针对这个指标做一些操作:当任务数不断增加时,说明此时请求处理跟不上传入的请求,需要报警。当累积到一定数量时,需要暂时将实例从注册中心移除并扩容。队列消费完后,重新上线。如果一定时间后仍未消费完毕,则会重启实例。添加同步微服务HTTP请求等待队列监控好在org.jboss.threads.EnhancedQueueExecutor本身通过JMX暴露了HTTPservlet请求的线程池的各种指标:在我们的项目中,我们使用两种监控:prometheus+grafana微服务指标监控,主要用于告警,快速定位问题根源。JFR监控,主要用于详细定位单例问题。对于HTTP请求等待队列监控,我们应该通过prometheus接口暴露给grafana,收集指标,完善响应操作。暴露prometheus接口指标的代码是:@Log4j2@Configuration(proxyBeanMethods=false)//需要在引入prometheus时加载,actuator暴露prometheus端口@ConditionalOnEnabledMetricsExport("prometheus")publicclassUndertowXNIOConfiguration{@AutowiredprivateObjectProvidermeterRegistry;//只初始化一次privatevolatilebooleanisInitialized=false;//ApplicationContext刷新后需要注册//在加载ApplicationContext之前,已经初始化了日志配置//但是prometheus的相关Bean加载比较复杂,随着版本的变化也有很多变化,偷懒就好了整个ApplicationContext刷新后注册//ApplicationContext可能会刷新多次,比如调用/actuator/refresh,ApplicationContext有多个场景//这里为了简单起见,用一个简单的isInitialized来判断是否是第一次初始化,以及确保只初始化一次@EventListener(ContextRefreshedEvent.class)publicsynchronizedvoidinit(){if(!isInitialized){Gauge.builder("http_servlet_queue_size",()->{try{return(Integer)ManagementFactory.getPlatformMBeanServer().getAttribute(newObjectName("org.xnio:type=Xnio,provider=\"nio\",worker=\"XNIO-2\""),"WorkerQueueSize");}catch(Exceptione){log.error("gethttp_servlet_queue_sizeerror",e);}返回-1;}).register(meterRegistry.getIfAvailable());isInitialized=true;}}}调用/actuator/prometheus后我们可以看到相应的Indicators:#HELPhttp_servlet_queue_size#TYPEhttp_servlet_queue_sizegaugehttp_servlet_queue_size0.0当出现队列堆积时,我们可以快速报警,通过grafana监控直观的发现:对于公有云部署,注意网络限制的监控。现在的公有云会针对物理机资源进行虚拟化,网卡资源也进行虚拟化。以AWS为例,其网络资源的虚拟化实现是ENA(ElasticNetworkAdapter)。它将监控和限制以下指标:带宽:每个虚拟机实例(AWS中的每个EC2实例)都有出站流量的最大带宽和入站流量的最大带宽。该统计使用网络I/O集成机制,根据平均带宽使用情况分配网络带宽。最终的效果是允许在短时间内超过额定带宽,但不是连续的。每秒数据包数(PPS,PacketPerSecond):每个虚拟机实例(AWS中的每个EC2实例)都有一个PPS大小的限制连接:建立连接的数量是有限的链接本地服务访问流量:一般在公有云,每个虚拟机实例(AWS中的每个EC2实例)访问DNS,元数据服务器等,这将限制流量。同时,在成熟的公有云中,这些指标一般都会为用户提供一个展示和分析的界面,比如AWS的CloudWatch中,提供了以下指标的监控:当业务流量激增时,我们通过JFR发现有访问Redis存在性能瓶颈,但是对Redis本身的监控显示并没有遇到性能瓶颈。这个时候我们需要排查一下是不是网络流量限制导致的问题。在我们遇到问题的那段时间,发现NetworkBandwidthOutAllowanceExceeded事件明显增多:对于这类问题,需要考虑纵向扩展(提升实例配置)和横向扩容(多实例负载均衡),或者减少网络流量(加大压缩等)微信搜索“我的编程喵”关注公众号,天天刷,轻松提升技术,赢各种优惠:

猜你喜欢