全部产品
云市场

SOFARPC 进阶指南

更新时间:2019-12-30 11:51:22

本指南主要介绍以下内容:

发布 SOFARPC 服务

RPC 是日常开发中最常用的中间件,通过本教程,您将学习到如何利用一个 SOFABoot Core 工程发布一个 RPC 服务。

重要点击此链接 下载示例工程。

SOFARPC 项目示例代码位于 middleware-v2/SOFA_Lite2_Share_RPC 文件夹下。

下载完成后,需导入 IDE 工具,具体方法请参见 SOFABoot 快速入门 > 导入 IDE 工具

配置额外参数

您需要在 application.properties 中配置以下参数:

  1. com.alipay.env
  2. com.alipay.instanceid
  3. com.antcloud.antvip.endpoint
  4. com.antcloud.mw.access
  5. com.antcloud.mw.secret

参数的具体含义见 引入 SOFA 中间件 > 添加中间件全局配置项

定义服务接口并提供实现

要发布一个 RPC 服务,我们首先要定义一个接口,示例如下:

  1. // com.alipay.APPNAME.facade.SampleService
  2. public interface SampleService {
  3. String message();
  4. }

我们对这个接口提供一个默认实现,示例如下:

  1. // com.alipay.APPNAME.service.SampleServiceImpl
  2. public class SampleServiceImpl implements SampleService {
  3. @Override
  4. public String message() {
  5. return "Hello, Service SOFABoot";
  6. }
  7. }

同时,将提供的这个实现配置为一个 Java bean。

  1. <bean id="sampleServiceBean" class="com.alipay.APPNAME.service.SampleServiceImpl"/>

发布 RPC 服务

RPC 服务提供方通过服务 <sofa:service> 来定义,主要属性有 interfaceunique-idref

服务提供方定义服务 <sofa:service>,进行服务发布;服务消费方定义服务引用 <sofa:reference>,进行服务引用。任何一个 SOFA 框架应用节点都可以同时发布服务和引用其它节点的服务。

interface

SOFA 服务以 Java 接口形式定义,最主要的属性就是 interface,该属性用于确定一个服务,属性值为:命名空间包名 + Java 接口名。

ref

ref 属性用于指定服务实现所对应的 Spring Bean,通过 Bean ID 和服务实现类进行关联。

下面的代码样例中 ref 字段配置的值引用的就是我们之前配置的 sampleServiceBean。

  1. <!-- 服务 -->
  2. <sofa:service ref="sampleServiceBean" interface="com.alipay.APPNAME.facade.SampleService">
  3. <sofa:binding.bolt/>
  4. </sofa:service>

unique-id

如果同一个接口有两个不同的实现,而这两个不同的实现都需要发布成 SOFA 的 RPC 服务,那么您可以在发布服务的时候加上一个 unique-id 属性来进行区分。

  1. <!-- 服务一 -->
  2. <sofa:service ref="sampleServiceBean1" interface="com.alipay.APPNAME.facade.SampleService" unique-id="service1">
  3. <sofa:binding.bolt/>
  4. </sofa:service>
  5. <!-- 服务二 -->
  6. <sofa:service ref="sampleServiceBean2" interface="com.alipay.APPNAME.facade.SampleService" unique-id="service2">
  7. <sofa:binding.bolt/>
  8. </sofa:service>

本地运行

  1. 在工程的根目录下执行 mvn clean install 命令,会在 target 目录下生成一个 APPNAME-service-1.0-SNAPSHOT-executable.jar 文件,这是一个可执行的 fat jar 文件。
  2. 通过以下任一方法执行 jar 文件,如果没有错误日志输出,则表示 sofaboot-rpc-server 工程启动成功。
    • 在服务器上执行 java -jar APPNAME-service-1.0-SNAPSHOT-executable.jar 命令;
    • 在本地 IDE 中直接运行 main 函数。

云端运行

详情参见 在云端运行 SOFABoot 应用

日志查看

  1. 查看 sofaboot-rpc-server 工程 RPC 发布服务启动日志 logs/rpc/common-default.log,如出现类似以下内容,说明 RPC 服务端成功启动:

    1. 2016-12-17 15:16:44,466 INFO main - sofa rpc run.mode = DEV
    2. 2016-12-17 15:16:49,479 INFO main - PID:42843 sofa rpc starting!
  2. 查看日志 logs/rpc/rpc-registry.log,内容参考如下:

    1. 2016-12-17 15:17:07,764 INFO main RPC-REGISTRY - 发布 RPC 服务:服务名[com.alipay.APPNAME.facade.SampleService:1.0@DEFAULT]
  3. 查看错误日志 logs/rpc/common-error.log,如果没有任何错误日志输出且应用启动正常,说明我们成功发布了一个 RPC 服务。

关于日志的详细信息,请参见 日志说明

引用 SOFARPC 服务

通过本教程,您将快速学习到如何引用一个 RPC 服务。

重要:点击此处 下载示例工程

项目示例代码位于 middleware-v2/SOFA_Lite2_Share_RPC 文件夹下。

下载完成后,需导入 IDE 工具,具体方法请参见 SOFABoot 快速开始 > 导入 IDE 工具

配置额外参数

您需要在 application.properties 中配置以下参数:

  1. com.alipay.env
  2. com.alipay.instanceid
  3. com.antcloud.antvip.endpoint

参数的具体含义参见 引入 SOFA 中间件 > 中间件全局配置项

引入接口定义依赖

要引用一个 RPC 服务,我们需要知道 RPC 服务的提供方所发布的接口是什么(如果发布的服务有 unique-id,我们还需要知道 unique-id),这就要求服务提供方将自己所发布的接口所在的 JAR 及依赖信息传到 Maven 仓库,以便服务引用方能够引用服务提供方所发布的 RPC 服务。

  • 如果是本地运行,需要在 sofaboot-rpc-server 工程目录运行 mvn clean install,将接口依赖 JAR 安装到本地仓库;
  • 若非本地运行,需要将接口依赖 JAR 上传到对应的 Maven 仓库。

获得 RPC 服务的发布接口后,在 sofaboot-rpc-client 工程下的主 pom 中添加所引用的 RPC 服务的接口依赖信息,示例如下:

  1. <dependency>
  2. <groupId>com.alipay.APPNAME</groupId>
  3. <artifactId>APPNAME-facade</artifactId>
  4. <version>1.0-SNAPSHOT</version>
  5. </dependency>
说明:此处客户端和服务端的应用名称均命名为 APPNAME,仅供学习使用。在实际环境中,两个应用名称不可完全一样。

引用 RPC 服务

在配置文件 META-INF/APPNAME/APPNAME-web.xml 中,根据接口配置引用一个 RPC 服务:

  1. <sofa:reference id="sampleRpcService"
  2. interface="com.alipay.APPNAME.facade.SampleService">
  3. <sofa:binding.bolt/>
  4. </sofa:reference>

此处的 RPC 引用也是一个 bean,其 bean id 为 sampleRpcService

将引用的 RPC 服务注入 Controller

注意:这一步是为了演示方便,实现用户通过浏览器或者其他方式访问一个 rest 接口,然后触发调用引用的服务,再调用到服务端,实际开发中,并不需要注入 Controller 这一步,请使用方注意。

本教程中,我们将这个 RPC 服务注入到了 com.alipay.APPNAME.web.springrest.RpcTestController 中,示例如下:

  1. @RestController
  2. @RequestMapping("/rpc")
  3. public class RpcTestController {
  4. @Autowired
  5. private SampleService sampleRpcService;
  6. @RequestMapping("/hello")
  7. String rpcUniqueAndTimeout() {
  8. String rpcResult = this.sampleRpcService.message();
  9. return rpcResult;
  10. }
  11. }

本地编译

sofaboot-rpc-client 是一个 SOFABoot Web 工程。依次执行以下命令以进行本地编译并启动 RPC client:

  1. 将客户端和服务端 config/application.properties 中的 run.mode 均配置为 DEV,即 run.mode=DEV
  2. 在工程根目录下执行:mvn clean install,生成可执行文件 target/APPNAME-web-1.0-SNAPSHOT-executable.jar
  3. 在工程根目录下执行:java -jar ./target/APPNAME-web-1.0-SNAPSHOT-executable.jar
    • 如果控制台输出如下信息,则表示 WEB 容器启动成功:
      1. 16:11:13.625 INFO org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8080 (http)
    • 如果输出错误信息,请在解决问题后重试以上步骤。

测试 RPC 服务

  1. 启动 RPC 服务端 sofaboot-rpc-server 及客户端 sofaboot-rpc-client。
  2. 在浏览器中访问 http://localhost:8080/rpc/hello 来测试引用的 RPC 服务。当浏览器输出如下信息时,表示 RPC 服务发布和引用均成功。
    1. Hello, Service SOFABoot

云端运行

详情参见 在云端运行 SOFABoot 应用

日志查看

查看 sofaboot-rpc-client 工程引用 RPC 服务启动日志:查看日志目录 logs/rpc/rpc-registry.log,内容参考如下:

  1. 2016-12-17 15:45:50,340 INFO main RPC-REGISTRY - 订阅 RPC 服务:服务名[com.alipay.APPNAME.facade.SampleService:1.0@DEFAULT]

以上日志说明 RPC 服务引用成功,如果发现引用 RPC 服务失败,请重点关注日志目录 logs 下的所有 common-error.log

有关日志的详细信息,请参考 日志说明

调用上下文

RPC 上下文中存放了当前调用过程中的一些其他信息,如服务提供方应用名、IP。应用开发人员可以获取这些信息做一些业务上的操作。RPC 提供获取单次调用上下文的工具类 com.alipay.sofa.rpc.api.context.RpcContextManager,通过该类,可以获得最后一次 Reference 以及当次 Service 的相关信息。

需要注意的是,RPC 上下文是存在 ThreadLocal 中的临时数据,切换线程或者清空 ThreadLocal 后数据都将丢失。

使用方式

  1. // Reference Context
  2. SampleService sampleService;
  3. public void do() {
  4. sampleService.hello();
  5. // 参数为 true 代表清空上下文信息
  6. RpcReferenceContext referenceContext = RpcContextManager.lastReferenceContext(true);
  7. // do something on referenceContext
  8. }
  1. // Service Context
  2. public void doService() {
  3. // do sth
  4. ...
  5. // 参数为 true 代表清空上下文信息
  6. RpcServiceContext serviceContext = RpcContextManager.currentServiceContext(true);
  7. // do something on serviceContext
  8. ...
  9. }

上下文内容

RPC 上下文的信息均是从 Tracer 中获得,参见 RPC 日志格式 了解更多信息。

Reference

  • traceId
  • rpcId
  • interfaceName:服务接口
  • methodName:服务方法
  • uniqueId:服务的唯一标识
  • serviceName:唯一的服务名
  • isGeneric:是否为泛化调用
  • targetAppName:服务提供方的应用名
  • targetUrl:服务提供方的地址
  • protocol:调用协议,如 TR
  • invokeType:调用类型,如 sync、oneway 等
  • routeRecord:路由寻址链路,如 TURL>CFS>RDM,表示路由寻址路径是从 test-url 到软负载到随机寻址。如果上次请求的路由策略是 test-url 的话,那么 routeRecord 等于 TURL>RDM。如要判断 test-url 或者软负载是否生效,请使用 RpcReferenceContext.isTestUrlValid 或者 RpcReferenceContext.isConfigServerValid 方法。详细的路由规则参见 RPC 路由
  • costTime:调用耗时,单位为 ms。
  • resultCode:结果码,00 - 成功;01 - 业务异常;02 - RPC 框架错误;03 - 超时失败;04 - 路由失败。

Service

  • traceId
  • rpcId
  • methodName:服务方法
  • serviceName:唯一的服务名
  • callerAppName:服务消费方的应用名
  • callerUrl:服务消费方的地址

泛化调用

在进行 RPC 调用时,应用无需依赖服务提供方的 Jar 包,只需要知道服务的接口名、方法名即可调用 RPC 服务。

泛化接口

  1. public interface GenericService {
  2. /**
  3. * 泛化调用仅支持方法参数为基本数据类型,
  4. * 或者方法参数类型在当前应用的 ClassLoader 中存在的情况
  5. *
  6. * @param methodName 调用方法名
  7. * @param args 调用参数列表
  8. * @return 调用结果
  9. * @throws com.alipay.sofa.rpc.core.exception.GenericException 调用异常
  10. */
  11. Object $invoke(String methodName, String[] argTypes, Object[] args) throws GenericException;
  12. /**
  13. * 支持参数类型无法在类加载器加载情况的泛化调用,对于非 JDK 类会序列化为 GenericObject
  14. *
  15. * @param methodName 调用方法名
  16. * @param argTypes 参数类型
  17. * @param args 方法参数,参数类型支持 GenericObject
  18. * @return result GenericObject 类型
  19. * @throws com.alipay.sofa.rpc.core.exception.GenericException
  20. */
  21. Object $genericInvoke(String methodName, String[] argTypes, Object[] args)
  22. throws GenericException;
  23. /**
  24. * 支持参数类型无法在类加载器加载情况的泛化调用
  25. *
  26. * @param methodName 调用方法名
  27. * @param argTypes 参数类型
  28. * @param args 方法参数,参数类型支持 GenericObject
  29. * @param context GenericContext
  30. * @return result GenericObject 类型
  31. * @throws com.alipay.sofa.rpc.core.exception.GenericException
  32. */
  33. Object $genericInvoke(String methodName, String[] argTypes, Object[] args,
  34. GenericContext context) throws GenericException;
  35. /**
  36. * 支持参数类型无法在类加载器加载情况的泛化调用,返回结果类型为 T
  37. *
  38. * @param methodName 调用方法名
  39. * @param argTypes 参数类型
  40. * @param args 方法参数,参数类型支持 GenericObject
  41. * @return result T 类型
  42. * @throws com.alipay.sofa.rpc.core.exception.GenericException
  43. */
  44. <T> T $genericInvoke(String methodName, String[] argTypes, Object[] args, Class<T> clazz) throws GenericException;
  45. /**
  46. * 支持参数类型无法在类加载器加载情况的泛化调用
  47. *
  48. * @param methodName 调用方法名
  49. * @param argTypes 参数类型
  50. * @param args 方法参数,参数类型支持 GenericObject
  51. * @param clazz 返回类型
  52. * @param context GenericContext
  53. * @return result T 类型
  54. * @throws com.alipay.sofa.rpc.core.exception.GenericException
  55. */
  56. <T> T $genericInvoke(String methodName, String[] argTypes, Object[] args, Class<T> clazz, GenericContext context) throws GenericException;
  57. }

$invoke 仅支持方法参数类型在当前应用的 ClassLoader 中存在的情况;$genericInvoke 支持方法参数类型在当前应用的 ClassLoader 中不存在的情况。

使用示例

Spring XML 配置:

  1. <!-- 引用 TR 服务 -->
  2. <sofa:reference interface="com.alipay.sofa.rpc.api.GenericService" id="genericService">
  3. <sofa:binding.tr>
  4. <sofa:global-attrs generic-interface="com.alipay.test.SampleService"/>
  5. </sofa:binding.tr>
  6. </sofa:reference>

Java 代码:

  1. /**
  2. * Java Bean
  3. */
  4. public class People {
  5. private String name;
  6. private int age;
  7. // getters and setters
  8. }
  9. /**
  10. * 服务方提供的接口
  11. */
  12. interface SampleService {
  13. String hello(String arg);
  14. People hello(People people);
  15. }
  1. /**
  2. * 消费方测试类
  3. */
  4. public class ConsumerClass {
  5. GenericService genericService;
  6. public void do() {
  7. // 1. $invoke 仅支持方法参数类型在当前应用的 ClassLoader 中存在的情况
  8. genericService.$invoke("hello", new String[]{ String.class.getName() }, new Object[]{"I'm an arg"});
  9. // 2. $genericInvoke 支持方法参数类型在当前应用的 ClassLoader 中不存在的情况。
  10. // 2.1 构造参数
  11. GenericObject genericObject = new GenericObject("com.alipay.sofa.rpc.test.generic.bean.People"); // 构造函数中指定全路径类名
  12. genericObject.putField("name", "Lilei"); // 调用 putField,指定field值
  13. genericObject.putField("age", 15);
  14. // 2.2 进行调用,不指定返回类型,返回结果类型为 GenericObject
  15. Object obj = genericService.$genericInvoke("hello", new String[]{"com.alipay.sofa.rpc.test.generic.bean.People"}, new Object[] { genericObject });
  16. Assert.assertTrue(obj.getClass() == GenericObject.class);
  17. // 2.3 进行调用,指定返回类型
  18. People people = genericService.$genericInvoke("hello", new String[]{"com.alipay.sofa.rpc.test.generic.bean.People"}, new Object[] { genericObject }, People.class);
  19. // 3. LDC 架构下的泛化调用使用
  20. // 3.1 构造 GenericContext 对象
  21. AlipayGenericContext genericContext = new AlipayGenericContext();
  22. genericContext.setUid("33");
  23. // 3.2 进行调用
  24. People people = genericService.$genericInvoke("hello", new String[] {"com.alipay.sofa.rpc.test.generic.bean.People"}, new Object[] { genericObject }, People.class, genericContext);
  25. }

特殊说明

调用 $genericInvoke(String methodName, String[] argTypes, Object[] args) 接口,会将除以下包以外的其他类序列化为 GenericObject

  1. "com.sun",
  2. "java",
  3. "javax",
  4. "org.ietf",
  5. "org.ogm",
  6. "org.w3c",
  7. "org.xml",
  8. "sunw.io",
  9. "sunw.util"

预热转发

集群中一台机器刚启动的一段时间(称之为“预热期”)内,如果请求过多可能会影响机器性能和正常业务。框架提供一种功能,将处于预热期的机器的请求转发到集群内其它机器,过了预热期之后再恢复正常。

也就是说,预热转发功能是指机器在启动完成后的“一段时间”内将其接收的请求转发至集群内的其它机器,等过了这段时间后再正常接收请求。这段时间内接收的请求既可以全部转发至其它机器,也可以按照一定比例转发,比如 80% 的请求转发出去,20% 自身系统处理。

与 RPC 压测转发不同的是,RPC 预热转发仅适用于应用启动后的一段时间,而压测转发则是长期生效。

RPC 转发配置

配置的方式有两种:

  • 直接转发到 IP

    core_proxy_url = x.x.x.x

    不论何时都直接转发到 x.x.x.x,和 core_proxy_url=address:x.x.x.x 有同样效果。

  • 配置预热与权重

    core_proxy_url=weightStarting:0.3,during:60,weightStarted:0.2,address:x.x.x.x,uniqueId:core_unique

    • weightStarting:预热期内的转发权重或概率,RPC 框架内部会在集群中随机找一台机器以此权重转出或接收。
    • during:预热期的时间长度,单位为秒。
    • weightStarted:预热期过后的转发权重,将会一直生效。
    • address:预热期过后的转发地址,将会一直生效。
    • uniqueId:同 appName 多集群部署的情况下,要区别不同集群可以通过配置此项区分。指定一个自定义的系统变量,保证集群唯一即可。core_unique 是一个 application.properties 的配置,可以动态替换。

说明:
application.properties 中可以配置转发请求超时时间,如下所示:
rpc_transmit_url_timeout_tr=8000
单位为 ms,默认为 10000 ms。

自定义 Filter 类

继承 com.alipay.sofa.rpc.filter.Filter 类。

  1. package com.alipay.sofa.rpc.customfilter;
  2. import com.alipay.sofa.rpc.core.exception.SofaRpcException;
  3. import com.alipay.sofa.rpc.core.request.SofaRequest;
  4. import com.alipay.sofa.rpc.core.response.SofaResponse;
  5. import com.alipay.sofa.rpc.filter.Filter;
  6. import com.alipay.sofa.rpc.filter.FilterInvoker;
  7. import com.alipay.sofa.rpc.log.Logger;
  8. import com.alipay.sofa.rpc.log.LoggerFactory;
  9. public class CustomEchoFilter extends Filter {
  10. /**
  11. * Logger for CustomEchoFilter
  12. **/
  13. private static final Logger LOGGER = LoggerFactory.getLogger(CustomEchoFilter.class);
  14. @Override
  15. public boolean needToLoad(FilterInvoker invoker) {
  16. // 判断一些条件,自己决定是否加载这个 Filter
  17. return true;
  18. }
  19. @Override
  20. public SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException {
  21. // 调用前打印请求
  22. LOGGER.info("echo request : {}, {}", request.getInterfaceName() + "." + request.getMethod(),
  23. request.getMethodArgs());
  24. // 继续调用
  25. SofaResponse response = invoker.invoke(request);
  26. // 调用后打印返回值
  27. if (response == null) {
  28. return response;
  29. } else if (response.isError()) {
  30. LOGGER.info("server rpc error: {}", response.getErrorMsg());
  31. } else {
  32. Object ret = response.getAppResponse();
  33. if (ret instanceof Throwable) {
  34. LOGGER.error("server biz error: {}", (Throwable) ret);
  35. } else {
  36. LOGGER.info("echo response : {}", response.getAppResponse());
  37. }
  38. }
  39. return response;
  40. }
  41. }

配置对单个服务发布者(消费者)生效的 Filter

  1. <!-- 配置一个自定义 Filter -->
  2. <bean id="customEchoFilter" class="com.alipay.sofa.rpc.customfilter.CustomEchoFilter"/>
  3. <property name="filed1" value="xxxx" /> <!-- 假如有一些自己的字段赋值 -->
  4. </bean>
  5. <bean id="xxServiceImpl" class="com.alipay.xxx.XXServiceImpl" />
  6. <!-- 配置这个服务发布者的 Filter,支持配置多个,彼此以逗号分开 -->
  7. <sofa:service id="xxServiceExport" ref="xxServiceImpl" interface="com.alipay.xxx.XXService">
  8. <sofa:binding.bolt>
  9. <sofa:global-attrs filter="customEchoFilter"/> <!-- 设置到这里 -->
  10. </sofa:binding.bolt>
  11. </sofa:service>
  12. <!-- 配置这个服务引用者的 Filter,支持配置多个,彼此以逗号分开 -->
  13. <sofa:reference id="xxServiceRef" interface="com.alipay.xxx.XXService">
  14. <sofa:binding.bolt>
  15. <sofa:global-attrs filter="customEchoFilter"/> <!-- 设置到这里 -->
  16. </sofa:binding.bolt>
  17. </sofa:reference>

全局 Filter

全局 Filter 的 XML 配置方式和非全局 Filter 配置相似,示例如下:

  1. <!-- 方式一:配置一个自定义 Filter -->
  2. <bean id="customEchoFilter" class="com.alipay.sofa.rpc.customfilter.CustomEchoFilter">
  3. <property name="filed1" value="xxxx" /> <!-- 假如有一些自己的字段赋值 -->
  4. </bean>
  5. <!-- 方式二:标记这个 Filter 为全局 Filter -->
  6. <sofa:rpc-global-filter ref="customEchoFilter" />
  7. <!-- 方式三:标记这个 Filter 为全局 Filter -->
  8. <sofa:rpc-global-filter class="com.alipay.sofa.rpc.customfilter.CustomEchoFilter" />
  9. <!-- 无需任何配置 -->
  10. <sofa:service ... />
  11. <!-- 无需任何配置 -->
  12. <sofa:reference ... />

配置说明

配置项 类型 说明 默认值
sofa_runtime_local_mode BOOLEAN 本地优先调用开关 false
run_mode STRING RPC 运行模式
rpc_tr_port INTEGER TR 端口号 12200
rpc_bind_network_interface STRING 服务器绑定固定网卡
rpc_enabled_ip_range STRING 服务器绑定本地 IP 范围
rpc_min_pool_size_tr INTEGER TR 服务器线程池最小线程数 20
rpc_max_pool_size_tr INTEGER TR 服务器线程池最大线程数 200
rpc_pool_queue_size_tr INTEGER TR 服务器线程池队列大小 0
com.alipay.sofa.rpc.bolt.port INTEGER * BOLT端口号 12200
com.alipay.sofa.rpc.bolt.thread.pool.core.size INTEGER * BOLT 服务器线程池最小线程数 20
com.alipay.sofa.rpc.bolt.thread.pool.max.size INTEGER * BOLT 服务器线程池最大线程数 200
com.alipay.sofa.rpc.bolt.thread.pool.queue.size INTEGER * BOLT 服务器线程池队列大小 0
com.alipay.sofa.rpc.rest.port INTEGER SOFAREST 端口号 8341
rpc_transmit_url STRING 预热与权重配置
rpc_transmit_url_timeout_tr INTEGER 预热调用超时时间,单位 ms 0
rpc_profile_threshold_tr INTEGER RPC 服务处理性能日志打印阈值,单位 ms 300

本地优先调用模式

当本地启动多个 SOFA 应用时,要使这几个应用能优先相互调用,而不需要经过软负载过程,只需要在 application.properties 中加入 sofa_runtime_local_mode=true 即可。

但是 sofa_runtime_local_mode 这个配置依然需要依赖于配置中心推送下来的地址。拿到服务提供方地址列表后,服务消费方会优先选择本地的 IP 地址进行服务调用。如果开发者所处的工作空间没有配置中心,则需要指定服务提供方地址进行调用,具体参见 路由与配置中心

  1. application.properties: run_mode=TEST
  2. <!-- 服务应用方配置 -->
  3. <sofa:reference ...>
  4. <sofa:binding.bolt>
  5. <global-attrs test-url="localhost:12200" />
  6. </sofa:binding.bolt>
  7. </sofa:reference>

IP/网卡绑定

SOFARPC 发布服务地址的时候,只会选取本地的第一张网卡的 IP 发布到配置中心,如果有多张网卡(如在 SOFAStack 平台上,有内网 IP 和外网 IP),则需要设置 IP 选择策略。

SOFARPC 提供了两种方式选择 IP:

  1. rpc_bind_network_interface

    指定具体的网卡名进行选择,如:rpc_bind_network_interface=eth0

  2. rpc_enabled_ip_range

    指定 IP 范围进行绑定,格式:IP_RANGE1:IP_RANGE2,IP_RANGE。例如,rpc_enabled_ip_range=10.1:10.2,11 表示 IP 范围在 10.1.0.0~10.2.255.25511.0.0.0~11.255.255.255 内的才会选择。

说明:SOFAStack 平台的内网地址均绑定在 eth0 网卡上,推荐直接使用 rpc_bind_network_interface=eth0 配置。如果应用运行在其它非 SOFAStack 平台上,请查看运行机器的内网地址自行斟酌。查看机器地址的命令:Windows 系统为 ipconfig;Mac/Linux 系统为 ifconfig

TR 线程池配置

application.properties 文件中使用以下选项配置 TR 线程池信息:

  • com.alipay.sofa.rpc.bolt.thread.pool.core.size:最小线程数,默认 20
  • com.alipay.sofa.rpc.bolt.thread.pool.max.size:最大线程数,默认 200
  • com.alipay.sofa.rpc.bolt.thread.pool.queue.size:队列大小,默认 0

TR 采用了 JDK 中的线程池 ThreadPoolExecutor。当核心线程池扩张时,先涨到最小线程数大小。当并发请求达到最小线程数后,请求被放入线程池队列中。队列满了之后,线程池会扩张到最大线程数指定的大小。如果超过最大线程数则会抛出 RejectionException 异常。

性能日志打印阈值配置

参见 性能埋点日志

日志说明

当您使用 SOFARPC 启动应用程序以后,默认情况下,RPC 会创建 logs 目录,并生成以下几个日志文件,包含:

  • rpc/rpc-registry.log:服务地址订阅与接收日志
  • rpc/tr-threadpool:服务连接池日志(SOFALite 1.0 以及 SOFABoot 均支持)
  • rpc/rpc-default.log:SOFARPC INFO/WARN 日志,无标准格式
  • rpc/common-error.log:SOFARPC 错误日志,无标准格式
  • rpc/rpc-remoting.log:网络层日志,无标准格式
  • rpc/sofa-router.log:SOFARouter 相关日志,无标准格式
  • rpc/rpc-remoting-serialization.log:网络层序列化日志,无标准格式
  • tracelog/rpc-client-digest.log:SOFARPC 调用客户端摘要日志
  • tracelog/rpc-server-digest.log:SOFARPC 调用服务端摘要日志
  • tracelog/rpc-profile.log:SOFARPC 处理性能日志
  • confreg/config.client.log:服务注册中心客户端日志

有关 Tracer 日志,具体参见 RPC Tracer 日志格式

性能测试

测试包括 BOLT 和 REST 协议的两种性能数据。

压测环境

服务端机器配置

  • 内存:4G
  • CPU:4-Core(Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz)
  • 磁盘:普通机械硬盘100G(非 SSD)

压测方案

为了测试 RPC 框架的性能数据,服务端提供一个用于增删改查的接口,客户端进行真实调用。压测工具使用标准 JMH(Java Microbenchmark Harness)进行多轮测试。

  1. // 服务端接口
  2. public interface RpcUserService {
  3. public boolean existUser(String email);
  4. public boolean createUser(RpcUser user);
  5. public RpcUser getUser(long id);
  6. public Page<RpcUser> listUser(int pageNo);
  7. }

测试结果

在服务端 CPU 达到 60-70%、内存占用 50% 的情况下,测试结果如下:

BOLT
  • 在没有打开 Tracer 的情况下,不会统计每笔调用信息。此时,服务端平均达到 50000 tps,成功率 100%,其中 99% 的请求在 1.5 ms 内响应完成。

  • 在已经打开 Tracer 的情况下,统计每笔调用信息并打印在磁盘中。此时,服务端平均达到 30000 tps,成功率 100%,其中 99% 的请求在 2.4 ms 内响应完成。

REST
  • 在没有打开 Tracer 的情况下,不会统计每笔调用信息。此时,服务端平均达到 15000 tps,成功率 100%,其中 99% 的请求在 4 ms 内响应完成。

  • 在已经打开 Tracer 的情况下,统计每笔调用信息并打印在磁盘中。此时,服务端平均达到 9000 tps,成功率 100%,其中 99% 的请求在 7 ms 内响应完成。

使用建议

基于以上测试结果,在选择通信协议时您可以考虑以下方案:

  • 应用内部的调用使用 BOLT 通信协议。
  • 当需要对外提供 REST/HTTP 服务时,可以将 REST 协议作为入口,并在内部转发给 BOLT 进行处理,以便进行更高效的通信。

建议您根据自己的实际场景来选择,或者进行相关的性能测试。