手動(dòng)實(shí)現(xiàn)一款輕量 高效的RPC框架

一输瓜、概述

手動(dòng)實(shí)現(xiàn)一款輕量灌具,高效的RPC框架割捅,基于TCP的二進(jìn)制協(xié)議實(shí)現(xiàn)

github源碼:https://github.com/wosn00/srpc 恩掷, 歡迎fork項(xiàng)目持續(xù)開(kāi)發(fā),性能改進(jìn)

二树灶、特征

  • 基于netty的主從Reactor模型纤怒,NIO通信

  • 支持同步,異步天通,攜帶回調(diào)等調(diào)用方式

  • 支持spring項(xiàng)目下引入starter包開(kāi)箱即用泊窘,整合spring,實(shí)現(xiàn)服務(wù)接口透明使用

  • 支持非spring項(xiàng)目下單獨(dú)使用土砂,可不依賴spring環(huán)境

  • 支持多種序列化類型,Protostuff谜洽,Kryo萝映,Json,Jdk等

  • 支持多種壓縮算法阐虚,Snappy序臂,Lz4,gzip,bzip2奥秆,Deflate逊彭,Lzo等

  • 支持注冊(cè)中心,自動(dòng)服務(wù)注冊(cè)和發(fā)現(xiàn)构订,默認(rèn)實(shí)現(xiàn)zookpeer侮叮,也可不使用注冊(cè)中心,手動(dòng)指定服務(wù)端節(jié)點(diǎn)地址列表

  • 支持多種負(fù)載均衡策略悼瘾,隨機(jī)囊榜,輪詢,一致性hash等

  • 支持服務(wù)容錯(cuò)亥宿,連接/調(diào)用異常情況下自動(dòng)排除服務(wù)端故障節(jié)點(diǎn)

  • 支持SPI擴(kuò)展點(diǎn)卸勺,可擴(kuò)展負(fù)載均衡策略,壓縮算法烫扼,序列化類型曙求,線程池,注冊(cè)中心等

  • 支持TLS雙向認(rèn)證加密

  • 支持流量整形映企,請(qǐng)求異常重試悟狱,服務(wù)端請(qǐng)求去重等功能

三、設(shè)計(jì)

image

可能對(duì)RPC框架性能產(chǎn)生影響的幾個(gè)因素:

  • 網(wǎng)絡(luò)IO線程模型

  • 通信協(xié)議設(shè)計(jì)

  • 序列化性能

  • 服務(wù)調(diào)用管理方式

  • 連接池的維護(hù)

3.1 RPC協(xié)議

image

協(xié)議上設(shè)計(jì)盡量緊湊卑吭,4位bit用于標(biāo)識(shí)序列化類型芽淡,壓縮類型和指令類型,方法映射上不需要像dubbo那樣傳輸完整的類 方法 參數(shù)信息導(dǎo)致的無(wú)用流量增大豆赏,而是自行生成對(duì)應(yīng)rpc調(diào)用方法的唯一標(biāo)識(shí)字符串

3.2 同步線程模型

image

3.3 異步線程模型

image

四挣菲、使用示例

4.1、spring環(huán)境下

maven依賴

項(xiàng)目目前暫時(shí)還未上傳到maven中央倉(cāng)庫(kù)掷邦,需clone項(xiàng)目到本地maven選擇srpc(root)下install后使用


<dependency>

    <groupId>com.hex</groupId>

    <artifactId>srpc-spring-boot-starter</artifactId>

    <version>1.1.0</version>

</dependency>

若需使用zookeeper作為注冊(cè)中心則引入


<dependency>

    <groupId>com.hex</groupId>

    <artifactId>srpc-registry-zookeeper</artifactId>

    <version>1.1.0</version>

</dependency>

server端使用

1.定義服務(wù)接口


@SRpcClient(serviceName = "testService")

public interface HelloService {

    String hello(String name);

}

接口添加@SRpcClient注解白胀,serviceName屬性為rpc服務(wù)都在注冊(cè)中心的服務(wù)名稱,若不使用注冊(cè)中心抚岗,則注解nodes屬性需手動(dòng)指定服務(wù)端節(jié)點(diǎn)集群地址或杠,將根據(jù)負(fù)載均衡策略自動(dòng)選取節(jié)點(diǎn)調(diào)用


@SRpcClient(nodes = {"127.0.0.1:9955;127.0.0.1:9956;127.0.0.1:9957"})

public interface HelloService {

    String hello(String name);

}

nodes屬性有兩種配置方式:

  • 直接指定節(jié)點(diǎn)地址列表,以分號(hào)隔開(kāi)的字符串宣蔚,例如
    @SRpcClient(nodes = "127.0.0.1:9955;127.0.0.1:9956;127.0.0.1:9957")
  • 以$符號(hào)開(kāi)頭支持從yml或properties配置文件獲取節(jié)點(diǎn)配置向抢,需保證服務(wù)消費(fèi)方的yml或properties配置文件有對(duì)應(yīng)的集群地址配置,同樣以分號(hào)隔開(kāi)胚委,例如
    @SRpcClient(nodes = "${srpc.helloService}")

2.服務(wù)接口實(shí)現(xiàn)


@SRpcRoute

public class HelloServiceImpl implements HelloService {

    @Override

    public String hello(String name) {

        return name + " Hey bro, it's a good day";

    }

}

實(shí)現(xiàn)類添加@SRpcRoute注解挟鸠,便會(huì)自動(dòng)注冊(cè)為spring的單例bean,可視為等同@Comphonent使用亩冬,內(nèi)部可用@Autowired等spring相關(guān)注解艘希,也可被其他bean注入。

3.配置yml

因同時(shí)包含了rpc客戶端和服務(wù)端,所以客戶端和服務(wù)端都需要配置覆享,如需個(gè)性化配置的地方在yml或properties文件按需配置即可佳遂,以srpc.server或srpc.client為前綴。所有可自由配置的選項(xiàng)如下

服務(wù)端默認(rèn)配置:


@ConfigurationProperties(prefix = "srpc.server")

public class RpcServerProperties {

    private Integer port = 9957; //綁定端口

    private Integer businessThreads = 200; //業(yè)務(wù)處理線程池大小撒顿,0為不設(shè)置

    private Integer businessQueueSize = 500; //業(yè)務(wù)線程池隊(duì)列大小

    private Integer connectionIdleTime = 180;//超過(guò)連接空閑時(shí)間(秒)未收發(fā)數(shù)據(jù)則關(guān)閉連接

    private Integer printConnectionNumInterval = 0; //打印服務(wù)端當(dāng)前連接詳情, 時(shí)間間隔(秒), 0為不打印

    private Boolean isPrintHearBeatPacketInfo = false; //是否打印心跳包信息

    private CompressType compressType = CompressType.SNAPPY; //壓縮算法類型丑罪,無(wú)需壓縮為NONE

    private SerializeType serializeType = SerializeType.PROTOSTUFF; //序列化類型,默認(rèn)protostuff

    private Integer sendBuf = 65535; //tcp發(fā)送緩沖區(qū)

    private Integer receiveBuf = 65535; //tcp接收緩沖區(qū)

    private Integer lowWaterLevel = 1024 * 1024; //netty低水位

    private Integer highWaterLevel = 10 * 1024 * 1024; //netty高水位

    private boolean deDuplicateEnable = false; //是否開(kāi)啟去重處理

    private Integer duplicateCheckTime = 10; //請(qǐng)求去重緩存時(shí)長(zhǎng)(秒)

    private Long duplicateMaxSize = 1024 * 64L; //最大緩存請(qǐng)求個(gè)數(shù)

    private Boolean trafficMonitorEnable = false; //是否開(kāi)啟流控

    private Long maxReadSpeed = 10 * 1000 * 1000L; //帶寬限制核蘸,最大讀取速度

    private Long maxWriteSpeed = 10 * 1000 * 1000L; //帶寬限制巍糯,最大寫(xiě)出速度

    // ----tls加密部分配置

    private Boolean useTLS = false; //是否開(kāi)啟tls加密

    private String keyPath; //私鑰文件路徑

    private String keyPwd; //密碼

    private String certPath; //證書(shū)文件路徑

    private String trustCertPath; //受信任ca證書(shū)路徑

    private String clientAuth; //是否要求客戶端認(rèn)證

    // ----注冊(cè)中心配置部分

    private Boolean enableRegistry = false; //是否使用注冊(cè)中心

    private String registrySchema; //注冊(cè)中心模式名稱

    private List<String> registryAddress; //注冊(cè)中心地址

客戶端默認(rèn)配置:


@ConfigurationProperties(prefix = "srpc.client")

public class RpcClientProperties {

    private Integer callBackTaskThreads = 200; //回調(diào)任務(wù)處理線程池大小,0為不設(shè)置

    private Integer callBackTaskQueueSize = 500; //回調(diào)任務(wù)線程池隊(duì)列大小

    private Integer connectionTimeout = 5; //連接超時(shí)時(shí)間(秒)

    private Integer requestTimeout = 10; //請(qǐng)求超時(shí)時(shí)間(秒)

    private Integer connectionSizePerNode = 3; //每個(gè)節(jié)點(diǎn)連接數(shù)

    private Integer connectionIdleTime = 180; //超過(guò)連接空閑時(shí)間(秒)未收發(fā)數(shù)據(jù)則關(guān)閉連接

    private Integer heartBeatTimeInterval = 30; //發(fā)送心跳包間隔時(shí)間(秒)

    private CompressType compressType = CompressType.SNAPPY; //壓縮算法類型客扎,無(wú)需壓縮為NONE

    private SerializeType serializeType = SerializeType.PROTOSTUFF; //序列化類型祟峦,默認(rèn)protostuff

    private LoadBalanceRule loadBalanceRule = LoadBalanceRule.RANDOM; //集群負(fù)載均衡策略

    private boolean excludeUnAvailableNodesEnable = true; //集群模式下是否排除不可用的節(jié)點(diǎn)

    private Integer nodeErrorTimes = 3; //節(jié)點(diǎn)連接或請(qǐng)求超時(shí)/異常超過(guò)設(shè)置次數(shù)則置為節(jié)點(diǎn)不可用

    private Integer nodeHealthCheckTimeInterval = 10; //節(jié)點(diǎn)健康檢查周期(秒),心跳包響應(yīng)成功則恢復(fù)不可用的節(jié)點(diǎn)

    private Integer sendBuf = 65535; //tcp發(fā)送緩沖區(qū)

    private Integer receiveBuf = 65535; //tcp接收緩沖區(qū)

    private Integer lowWaterLevel = 1024 * 1024; //netty低水位

    private Integer highWaterLevel = 10 * 1024 * 1024; //netty高水位

    private Boolean trafficMonitorEnable = false; //是否開(kāi)啟流量控制

    private Long maxReadSpeed = 10 * 1000 * 1000L; //帶寬限制,最大讀取速度

    private Long maxWriteSpeed = 10 * 1000 * 1000L; //帶寬限制徙鱼,最大寫(xiě)出速度

    // ----TLS加密部分配置

    private Boolean useTLS = false; //是否開(kāi)啟TLS加密

    private String keyPath; //私鑰文件路徑

    private String keyPwd; //密碼

    private String certPath; //證書(shū)文件路徑

    private String trustCertPath; //受信任ca證書(shū)路徑

    private String clientAuth; //是否要求客戶端認(rèn)證

    // ----注冊(cè)中心配置部分

    private Boolean enableRegistry = false; //是否使用注冊(cè)中心

    private String registrySchema; //注冊(cè)中心模式名稱, 缺省為zookeeper

    private List<String> registryAddress; //注冊(cè)中心地址

配置類信息:

https://github.com/wosn00/srpc/blob/master/srpc-spring-boot-starter/src/main/java/com/hex/rpc/spring/starter/properties/RpcClientProperties.java

4.服務(wù)端啟動(dòng)


@SpringBootApplication

@EnableSRpc(basePackages = "com.hex.example.provider")

public class RpcTestApplication {

    public static void main(String[] args) {

        SpringApplication.run(RpcTestApplication.class, args);

    }

}

啟動(dòng)類上添加@EnableSRpc注解宅楞,basePackages為需要掃描的包路徑,包含@SRpcClient和@SRpcRoute注解的包路徑袱吆,相應(yīng)的類都會(huì)被自動(dòng)注冊(cè)為spring的單例bean厌衙,缺省為啟動(dòng)類上級(jí)包路徑

client端使用

1.服務(wù)接口調(diào)用


@Component

public class HelloRpcTest {

    @Autowired

    private HelloService helloService; // 上面定義的rpc服務(wù)接口

    public void rpcServerTest(String name) {

        String msg = helloService.hello(name);

        System.out.println(msg);

    }

}

上述服務(wù)端定義的帶有@SRpcClient注解的rpc服務(wù)接口,使用spring的@Autowired注入即可遠(yuǎn)程調(diào)用

2.配置yml(同上)

3.客戶端啟動(dòng)(同上)

4.2绞绒、非spring環(huán)境下

maven依賴


<dependency>

    <groupId>com.hex</groupId>

    <artifactId>srpc-core</artifactId>

    <version>1.1.0</version>

</dependency>

若需使用zookeeper作為注冊(cè)中心則引入


<dependency>

    <groupId>com.hex</groupId>

    <artifactId>srpc-registry-zookeeper</artifactId>

    <version>1.1.0</version>

</dependency>

server端使用

1.定義服務(wù)接口實(shí)現(xiàn)


@SRpcRoute

public class HelloServiceImpl {

    @Mapping("hello")

    public String hello(String name) {

        return name + " Hey bro, it's a good day";

    }

}

2.服務(wù)端啟動(dòng)


@SRpcScan("com.hex.example")

public class ServerTest {

    public static void main(String[] args) {

        // 啟動(dòng)服務(wù)端, 需填入rpc服務(wù)端配置, 可使用默認(rèn)配置, source填寫(xiě)有@RouteScan注解的類

        SRpcServer.builder()

                .serverConfig(new SRpcServerConfig()) //包含rpc服務(wù)端的各項(xiàng)默認(rèn)配置婶希,可自行修改

                .sourceClass(ServerTest.class) //有@RouteScan注解的類

                .port(8005) //rpc服務(wù)端綁定的端口,默認(rèn)9957

                .start();

    }

}

啟動(dòng)類上添加@SRpcScan注解蓬衡,值需填寫(xiě)包含@SRpcRoute注解的類的包路徑喻杈,缺省為啟動(dòng)類的上級(jí)包路徑,即可自動(dòng)掃描

client端使用

1.客戶端啟動(dòng)和服務(wù)接口調(diào)用


public class ClientTest {

    public static void main(String[] args1) {

        // 初始化客戶端狰晚,需填入rpc客戶端配置筒饰,可使用默認(rèn)配置

        Client rpcClient = SRpcClient.builder()

                .config(new SRpcClientConfig())

                .start();

        Object[] args = {"Jack"};

        HostAndPort node = HostAndPort.from("127.0.0.1:8005");

        // 同步發(fā)送請(qǐng)求,獲取響應(yīng)

        String response = rpcClient.invoke("hello", String.class, args, node);

        System.out.println(response);

        // 異步發(fā)送請(qǐng)求壁晒,發(fā)送完成即返回瓷们,不阻塞等待響應(yīng)結(jié)果

        rpcClient.invokeAsync("hello",

                rpcResponse -> System.out.println("收到響應(yīng),開(kāi)始執(zhí)行回調(diào)方法" + rpcResponse), args, node);

    }

}

Client更多調(diào)用接口及參數(shù)可查看接口說(shuō)明:

https://github.com/wosn00/srpc/blob/master/srpc-core/src/main/java/com/hex/srpc/core/rpc/Client.java

五秒咐、性能測(cè)試

5.1 與dubbo的性能對(duì)比測(cè)試

目前只是與dubbo進(jìn)行了簡(jiǎn)單了的性能測(cè)試對(duì)比 0_0谬晕,后續(xù)有時(shí)間會(huì)進(jìn)行更多的測(cè)試

測(cè)試代碼:https://github.com/wosn00/THOC/blob/master/srpc-demo-provider/src/main/java/com/hex/srpc/SRpcProviderApplication.java

條件:

1.測(cè)試相同接口模擬業(yè)務(wù)處理延遲30ms后返回

2.服務(wù)端業(yè)務(wù)處理線程池均為500

3.dubbo采用默認(rèn)的dubbo協(xié)議,srpc使用protostuff序列化

| 并發(fā)調(diào)用線程數(shù) | srpc(TPS) | dubbo(TPS) |

| -------------- | ----------- | ------------ |

| 100 | 2810 | 2800 |

| 200 | 5522 | 5480 |

| 300 | 7834 | 7100 |

| 400 | 9480 | 8520 |

| 500 | 11380 | 9700 |

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末携取,一起剝皮案震驚了整個(gè)濱河市攒钳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歹茶,老刑警劉巖夕玩,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惊豺,居然都是意外死亡燎孟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)尸昧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)揩页,“玉大人,你說(shuō)我怎么就攤上這事烹俗”拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵幢妄,是天一觀的道長(zhǎng)兔仰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蕉鸳,這世上最難降的妖魔是什么乎赴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮潮尝,結(jié)果婚禮上榕吼,老公的妹妹穿的比我還像新娘。我一直安慰自己勉失,他們只是感情好羹蚣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著乱凿,像睡著了一般顽素。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上告匠,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天戈抄,我揣著相機(jī)與錄音,去河邊找鬼后专。 笑死划鸽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的戚哎。 我是一名探鬼主播裸诽,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼型凳!你這毒婦竟也來(lái)了丈冬?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤甘畅,失蹤者是張志新(化名)和其女友劉穎埂蕊,沒(méi)想到半個(gè)月后往弓,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蓄氧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年函似,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喉童。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撇寞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出堂氯,到底是詐尸還是另有隱情蔑担,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布咽白,位于F島的核電站啤握,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏晶框。R本人自食惡果不足惜恨统,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望三妈。 院中可真熱鬧畜埋,春花似錦、人聲如沸畴蒲。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)模燥。三九已至咖祭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蔫骂,已是汗流浹背么翰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辽旋,地道東北人浩嫌。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像补胚,于是被迫代替她去往敵國(guó)和親码耐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容