随着项目规模的不断扩大,分解项目的复杂度就变成一个必须要面对的问题,在诸多的解决方案中,服务化架构被很多项目采纳。本文不对服务化做过多的展开,主要聊一下网易有钱在服务化过程中RPC框架的选型与设计。
RPC(Remote Procedure Call)即远程方法调用,让应用可以像调用本地方法一样调用远端的方法
主流RPC框架存在的问题:
配置、依赖复杂,他们提供了一整套服务化的框架,但各个框架之间有比较大的耦合,也就意味着需要使用这些框架,必须要对整个全家桶有所了解,也增大了开发人员的学习成本和项目的维护成本
引入IDL,需要增加大量的定义和编译工作,学习成本高,可维护性差,影响编码效率
为了解决上述问题,我们打算基于Spring和通用的序列化协议设计一个轻量级松耦合的RPC框架
结合项目中的实际场景,我们定义如下的设计原则:
Protocol:RPC服务的描述和配置信息
Serialize/Deserialize:序列化与反序列化层
主要用于对请求的方法、参数和返回的结果、异常进行序列化与反序列化
Transport:网络传输层,基于HTTP/S
Consul Nginx:服务发现组件
注解@RemoteMethod修饰interface,如
@RemoteMethod(mapping = {
@RpcUrlMapping(beanName = "hello", path = "/helloService.s") },
baseUrl = "http://${testService.domain}/rpc-framework")
public interface Hello {
HelloResponse sayHello(String name);
}
说明:
- baseUrl:服务所在的域;支持占位符
- betaBaseUrl:用于灰度流量控制,表示beta版本所在的域
- beanName:Client端的实例名称,不声明则使用接口名称的小驼峰式表达,如:接口mypackage.MyService对应的默认beanName为myService
- path:访问路径,baseUrl + path 为接口的实际访问地址
注解@RpcService修饰接口的实现类,如:
@RpcService("/helloService.s")
说明: value值必须和接口的@RpcUrlMapping#path保持一致
RpcScannerConfigurer
调用方通过BeanDefinitionRegistryPostProcessor在实例定义阶段扫描RPC接口,并将它们的代理类注册到ApplicationContext中
说明:
- basePackage:必选参数。声明rpc接口所在的包。
- markerInterface、annotation:选填参数。可通过这两个配置项对basePackage下面的接口进行过滤。
- mockDir:通过该参数实现调用方mock接口的返回结果
需要进行跟踪的请求需配置TraceFilter
主要目的是将HTTP请求中的跟踪信息设置到本地session,并通过MDC实现日志打印时输出跟踪信息对开发人员是透明的
所有暴露的接口默认支持JSON模式,因此可以通过命令行、rest client等工具进行测试
如:
curl -H "Content-Type: application/json" -X POST -d '{"sign":"addUser","args":["wgy@163.com","wgy"]}' http://127.0.0.1:8080/rpc-framework/v3/userService.s
调用方可以mock接口的返回结果是实现分离开发和服务降级的基础,可通过增加配置RpcScannerConfigurer#mockDir实现该功能。
说明:
- 打开配置后调用一次指定的接口,会在mockDir下面产生一个文件(mockFile demo)
- mockFile的文件名为:$mockDir/$RpcUrlMapping#path/$methodSign
- $methodSign为方法签名,由方法名和参数类型等构造
- mockFile的内容为一个JSON对象,如{"tip":null,"priority":null}
- 开发过程中可以根据测试的需要调整mockFile的内容,无需重启应用
服务提供方可通过声明@RemoteMethod#betaBaseUrl,并根据自己的需求实现接口BetaSwitcher,将符合条件的流量指向到Beta版本
如:
网易有钱的自动分类版本升级后,通过这种方式测试新版本的实际分类效果
环境限制:
- 使用Spring Framework(>= 3.1.1.RELEASE)或者Spring Boot
- HttpClient 4.x
- RPC框架
<groupId>com.netease.qian</groupId> <artifactId>qian-rpc-framework</artifactId> <version>${qian-rpc-framework.version}</version>
定义接口、模型以及异常
public interface Hello {
HelloResponse sayHello(String name);
}
为上述定义的接口声明暴露方式:
@RemoteMethod(mapping = {
@RpcUrlMapping(beanName = "hello", path = "/helloService.s")},
baseUrl = "http://${testService.domain}/rpc-framework")
public interface Hello {
HelloResponse sayHello(String name);
}
实现接口:
@RpcService("/helloService.s")
public class HelloImpl implements Hello {
@Override
public HelloResponse sayHello(String name) {
HelloResponse response = new HelloResponse();
response.setTip("hello," + name + "!");
return response;
}
}
配置servlet,暴露RPC接口(以SpringMVC为例,Spring Boot类似)
web.xml添加servlet配置
<servlet>
<servlet-name>rpc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:rpc-servlet.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
rpc-servlet.xml
<context:component-scan
base-package="com.netease.mail.test.server1,com.netease.qian.rpc.provider">
</context:component-scan>
启动应用,测试接口可用 如执行
curl -H "Content-Type: application/json" -X POST -d '{"sign":"sayHello","args":["wgy"]}' http://127.0.0.1:8080/rpc-framework/helloService.s
返回
{"value":{"tip":"hello,wgy!"}}
增加配置
占位符定义:server.properties
testService.domain=127.0.0.1:8080
扫描RPC Client实例(applicationContext-hello-client.xml)
<bean class="com.netease.qian.rpc.consumer.RpcScannerConfigurer">
<property name="basePackage" value="com.netease.mail.test.api1" />
</bean>
测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "/applicationContext-hello-client.xml" })
public class HttpClientTester {
@Autowired
private Hello hello;
@Test
public void test() {
System.out.println(hello.sayHello("wgy"));
}
}
RPC框架的设计初衷是简洁,易用:
目前框架还有不少未完善的地方,比如接口文档的维护管理,欢迎大家一起交流。
本文来自网易实践者社区,经作者王国云授权发布。