当前位置: 首页 > 科技观察

高并发服务优化篇:从RPC预热和转发看服务端性能调优

时间:2023-03-20 15:04:54 科技观察

在上一篇文章中,我们详细阐述了RPC的调用流程,分析了其耗时构成,为我们的日常提供了理论依据性能调整支持。为了更好的体验和更好的性能,RPC其实也悄悄做了很多工作。本文将带你了解RPC的一些高级特性及其背后的原因。(以开源的dubbo和sofa为例)Part1RPC为性能做了哪些努力1.1Provider分组和直接路由寻址,负载均衡很好,可以保证流量均匀,保护服务节点稳定。但是,有时候我们不想让我们的请求跑来跑去,最好是打到指定的机器上。比如在联调联测中,直连功能就非常重要。只有经历过多方合作联调时请求跑来跑去的痛苦,才知道分组直连功能对开发的友好程度。//以沙发为例@Extension(value="directUrl",order=-20000)@AutoActive(consumerSide=true)publicclassDirectUrlRouterextendsRouter{//...}我们可以看到给出了直接路由策略的order属性一个很小的值就成为优先级最高的路由策略,所以只要配置了直连列表,就会优先考虑配置中的列表地址。摘自:www.sofastack.tech1.2异步调用未来的异步调用异步调用对服务性能和并发支持有很大的作用。一般异步调用有Future、callback等方法。这里讲一下Future的原理:调用下游后,先返回一个Future,上游通过Future.get()方法获取结果。如果没有返回结果,则释放CPU资源进入等待,直到结果到达后触发回调方法或超时才被唤醒。由于篇幅问题,关于Future核心逻辑的相关评论就不放出来了。在之前的消息消费顺序保证的文章中也有介绍。有兴趣的同学可以看看~1.3本地优先,远程优先很多时候,我们会遇到消费端和服务端都可能是自己的情况。这时候除了常规的路由寻址之外,还为我们提供了调用的可能,就是直接调用当前服务器上的程序。高的。ListlocalProviderInfo=newArrayList();//解析IP是否与本地一致for(ProviderInfoproviderInfo:providerInfos){if(localhost.equals(providerInfo.getHost())){localProviderInfo.add(providerInfo);}}//命中本地服务器if(CommonUtils.isNotEmpty(localProviderInfo)){returnsuper.doSelect(invocation,localProviderInfo);}else{//不命中本地服务器returnsuper.doSelect(invocation,providerInfos);}当然也要看业务和内部服务路由的实际情况。比如在阿里的单元化部署下,需要根据用户ID路由到相应的zone进行处理。如果还是首选本地机,可能在操作数据库的时候涉及到跨区调用,比远程rpc更耗时。因此,在这种情况下,需要关闭本地优先级策略。1.4延迟暴露很多时候,我们的服务需要依赖一些其他的内容才能正常提供服务,比如缓存预热,线程池预热等,所以需要在服务上线后向配置中心注册真正准备好了。//服务注册前,延迟publicvoidexport(){//根据配置延迟加载if(providerConfig.getDelay()>0){Threadthread=factory.newThread(newRunnable(){@Overridepublicvoidrun(){try{Thread.sleep(providerConfig.getDelay());}catch(Throwableignore){}//真正的服务注册逻辑doExport();}});thread.start();}else{doExport();}}1.5粘连Q:我们是否需要每次都进行路由寻址和负载均衡来确定服务地址?答:在大多数情况下是有益的,但在一些特殊场景下,多个请求连接到同一台服务器更可取。比如statefulservices(很多有数据功能的服务都是有状态的,比如很久以前的tomcat服务有loginsession,storageclusterservices等),其实是希望每次请求都连接到同一个服务器。这使用粘性连接功能。protectedProviderInfoselect(...)throwsSofaRpcException{//判断isSticky粘连配置if(consumerConfig.isSticky()){//如果最后使用的provider不为空,则使用if(lastProviderInfo!=null){ProviderInfoproviderInfo=lastProviderInfo;//获取对应的连接ClientTransportlastTransport=connectionHolder.getAvailableClientTransport(providerInfo);if(lastTransport!=null&&lastTransport.isAvailable()){checkAlias(providerInfo,message);returnproviderInfo;}}}...}1.6预热转发这么多,在其实这就是我们今天要讲的重点。预热转发是为了服务节点的负载均衡。因为刚启动服务的时候,如果请求过多,可能会影响机器的性能和正常的业务。如果将处于预热期的机器的请求转发给集群中的其他机器,预热期结束后再恢复正常,就可以保证服务节点的性能和服务的整体可用性。那么这个功能是如何实现的呢?--带权重的随机负载平衡。摘自sofaack:权重随机性原理//累加总权重totalWeight,代码忽略。..//获取总权重内的一个随机值inoffset=random.nextInt(totalWeight);//判断随机值落在哪个分片上for(inti=0;i