基於netty輕量的高性能分布式RPC服務框架forest<上篇>
文章已經簡單介紹了forest的快速入門,本文旨在介紹forest用戶指南。
Forest是一套基於java開發的RPC框架,除了常規的點對點調用外,Motan還提供服務治理功能,包括服務節點的自動發現、摘除、高可用和負載均衡等。
架構概述
Forest中分為服務提供方(RPC Server),服務調用方(RPC Client)和服務注冊中心(Registry)三個角色。
Server提供服務,向Registry注冊自身服務,並向注冊中心定期發送心跳匯報狀態; Client使用服務,需要向注冊中心訂閱RPC服務,Client根據Registry返回的服務列表,與具體的Sever建立連接,並進行RPC調用。 當Server發生變更時,Registry會同步變更,Client感知後會對本地的服務列表作相應調整。 三者的交互關系如下圖:

forest代碼層面包含三個module
forest-common:一些基礎的功用的功能
forest-rpc:和rpc相關的核心功能實現,
forest-demo:一些forest示例的推薦示例
forest從設計的分層包含以下基層,分層設計圖如下:

Forest體功能了靈活的功能,可以基於注解來配置,也可以通過spring xml來覆蓋這些配置,當然你可以可以代碼裡面自己構造。
配置優先級如下:注解默認配置<spring xml配置(或者代碼構造)
a. inteface api類上可以通過@ServiceProvider注解來提供服務,@ServiceProvider用於接口類上,可以配置參數如下:
public @interface ServiceProvider {
String serviceName() default "";//服務名稱,隔離級別是以一個服務為粒度
HaStrategyType haStrategyType() default HaStrategyType.FAIL_FAST;
LoadBalanceType loadBalanceType() default LoadBalanceType.RANDOM;
String hashKey() default "";// 僅當使用hash策略時候使用
int connectionTimeout() default Constants.CONNECTION_TIMEOUT;
}
接口層提供給調用方,調用方的client可以繼承interface上的@ServiceProvider配置作為默認配置。
b. interface method可以通過@MethodProvider注解來提供具體的業務服務,參數配置如下:
public @interface MethodProvider {
String methodName() default "";
SerializeType serializeType() default SerializeType.Kyro;
CompressType compressType() default CompressType.None;
int timeout() default Constants.DEFAULT_TIMEOUT; // 客戶端超時時間
}
你可以針對不同的業務方法指定不同的序列化方式或者 壓縮方式。
a. servcieImpl可以通過@ServiceExport注解來發布服務,interface impl只有加上了@ServiceExport才會發布服務。 如果不指定port,則使用默認的port,如需和其他業務隔離,建議使用不同的port來發布。
public @interface ServiceExport {
int port() default Constants.DEF_PORT;
}
當然你也可以加上
@Path來暴露restful的path,Forest支持基於jersey的restful服務。
b. servcieImpl可以通過@MethodExport注解來發布方法
serviceImpl的方法上面同時可以支持其他的注解,類似如下:
/**
* 支持jersey,可以通過配置打開,同時啟動http服務
*
* @param str
* @return
*/
@Path("/hello/{str}")
@GET
@Produces("text/plain")
@MethodExport
@Rate(2)
@Override
public String say(@PathParam("str") String str) {
return "say " + str;
}
對於客戶端,是沒有辦法控制提供方提供接口層的注解配置,如果客戶端想自定義配置,可以選擇基於spring xml的配置來覆蓋服務提供方推薦的默認配置。
示例如下:
<bean id="methodConfig" class="com.zhizus.forest.common.config.MethodConfig">
<property name="compressType">
<util:constant static-field="com.zhizus.forest.common.CompressType.None"/>
</property>
<property name="serializeType">
<util:constant static-field="com.zhizus.forest.common.SerializeType.Fastjson"/>
</property>
<property name="timeout" value="5000"/>
</bean>
<bean id="sampleServiceProxy" class="com.zhizus.forest.support.spring.ForestProxyFactoryBean">
<property name="serviceInterface" value="com.zhizus.forest.demo.api.SampleService"/>
<!--methodConfMap如果不配置,則使用接口方法注解上面的配置-->
<property name="methodConfigMap">
<map>
<entry key="echo" value-ref="methodConfig"/>
<entry key="say" value-ref="methodConfig"/>
</map>
</property>
</bean>
有的時候覺得xml配置很麻煩,但是又不想使用服務提供方注解上面的默認配置,那麼我們可以可以使用代碼自己實例化, 示例如下:
SampleService sampleService = Forest.from(SampleService.class, ServiceProviderConfig.Builder.newBuilder()
.withMethodConfig("say", MethodConfig.Builder.newBuilder()
.withCompressType(CompressType.None)
.withSerializeType(SerializeType.Fastjson)
.build())
.withMethodConfig("echo", MethodConfig.Builder.newBuilder()
.withCompressType(CompressType.None)
.withSerializeType(SerializeType.Hession2)
.build())
.build());
Forest在協議層面支持多種壓縮方式&多種序列化方式,也就是說,你可以指定某一個請求的壓縮方式和序列化的方式。壓縮方式和序列化方式的粒度都是每個請求的。
RANDOM, ROBBIN, HASH
FAIL_FAST, FAIL_OVER,
Forest支持定制策略,(在一個時間段出現一定次數的錯誤,則自動將服務隔離開來,一段時間後恢復)
Forest采用common-pool2連接池來管理連接,每個客戶端的proxy對應一個或者多個pool,不需要額外的創建連接池來提供吞吐。
hystrix是一個非常優秀的服務容錯組件,這裡也非常推薦能使用hystrix對forest提供的服務做一層包裝來加強服務的容錯降級能力。
Forest可以自定義攔截器,同時也可以自定義限流。只需要在發布的方法上面加上對應的注解。
@Component
public class SampleServiceCommand {
@Resource(name = "sampleServiceProxy")
SampleService remoteServiceRef;
@HystrixCommand(groupKey = "ExampleGroup", commandKey = "HelloWorld", threadPoolKey = "HelloWorldPool", fallbackMethod = "sayFallback")
public String say(String str) {
String say = remoteServiceRef.say(str);
System.out.println("say:" + say);
str.toString();
return say;
}
public String sayFallback(String str) {
return "sayFallBack:" + str;
}
}
xml 配置如下:
<context:component-scan base-package="com.zhizus.forest.demo.client"/>
<!--添加hystrix aop-->
<aop:aspectj-autoproxy/>
<bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"></bean>
<bean id="methodConfig" class="com.zhizus.forest.common.config.MethodConfig">
<property name="compressType">
<util:constant static-field="com.zhizus.forest.common.CompressType.None"/>
</property>
<property name="serializeType">
<util:constant static-field="com.zhizus.forest.common.SerializeType.Fastjson"/>
</property>
<property name="timeout" value="5000"/>
</bean>
<bean id="zkRegistry" class="com.zhizus.forest.common.registry.impl.ZkServiceDiscovery">
<property name="connStr" value="localhost:2181"/>
</bean>
<bean id="sampleServiceProxy" class="com.zhizus.forest.support.spring.ForestProxyFactoryBean">
<property name="serviceInterface" value="com.zhizus.forest.demo.api.SampleService"/>
<!--注冊本地-->
<property name="discovery" ref="zkRegistry"/>
<!--methodConfMap如果不配置,則使用接口方法注解上面的配置-->
<property name="methodConfigMap">
<map>
<entry key="echo" value-ref="methodConfig"/>
<entry key="say" value-ref="methodConfig"/>
</map>
</property>
</bean>
有時候,我們需要同時暴露http服務供第三方服務調用或者測試。針對這些情況,Forest支持基於jersey的restful服務。
http服務打開的開關:server.properties
http.server.start=true
其他的參數配置,詳見:com.zhizus.forest.common.config.ServerConfig 之後,我們可以無阻礙的使用基於jersey的restful服務了。
對Forest進行了簡單的壓測,發現在hession2的序列化方式下,win64 8g內存可以達到8w+的tps。 其他的情況也均在3w+的tps。
當然Forest應該還有一些性能優化空間,到目前為止,還沒有對其進行一些性能優化。
client代碼:
public static void benchmarkTest() throws Exception {
final SampleService sampleService = Forest.from(SampleService.class);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000000; i++) {
String say = sampleService.echo("hello");
if (i % 10000 == 0) {
System.out.println(say);
}
}
}
});
}
}
服務端加上系統自帶的metric攔截器:
@Interceptor("metricInterceptor")
@MethodExport
@Override
public String echo(String msg) {
return "echo>>> " + msg;
}
console日志:
23:10:10.295 [pool-1-thread-1] INFO MetricInterceptor 34 - methodName:/sampleService/say, current tps:83342, avgTime:0, maxTime:63, minTime:0 23:10:11.298 [pool-1-thread-1] INFO MetricInterceptor 34 - methodName:/sampleService/say, current tps:86271, avgTime:0, maxTime:63, minTime:0 23:10:12.295 [pool-1-thread-1] INFO MetricInterceptor 34 - methodName:/sampleService/say, current tps:86063, avgTime:0, maxTime:63, minTime:0
源代碼地址:forest
歡迎各路大俠參與。