RPC设计概要

Wesley13
• 阅读 440

前言

RPC全程远程方法调用,已经在各大小公司被广泛使用,种类也是很多比如:Dubbo,Spring cloud那一套,GRPC,Thrift,可能还有很多公司自研的等等;每个公司都可能根据自己的业务需求,场景选择自己合适的RPC框架;但大体的考察维度无非就这么几个:性能,可扩展性,跨平台,功能性,可监控,使用性;所以我们如果要设计一个RPC框架,可以从这几个角度去考虑。

性能

作为微服务中的核心组件,在一个系统中RPC的调用量往往是很高的,所以性能是一个很重要的考虑点;既然是远程调用,必然牵扯到网络连接,而I/O模型的选择直接影响到性能,网络的长连接短连接,序列化方式也都影响性能;

1.I/O模型

常见的Unix5种I/O模型分别是:阻塞I/O,非阻塞I/O,I/O复用(select,poll,epoll等支持I/O多路复用),信号驱动I/O,异步I/O;从早期的阻塞I/O方式只能创建大量的线程来保证每个用户互不影响,到现在广泛使用的I/O多路复用模型,再到异步I/O;从select模型到现在主流的epoll模型,性能有了质的升级;当然我们没必要自己去实现,可以直接使用网络通讯框架Netty,Mina等;

2.长连接短连接

短连接表示每次通讯完就关闭连接,而长连接通讯完继续保持连接,这样下次再通讯就不需要重新建立连接了,如果通讯频繁,很明显长连接性能更高;但是长连接需要做一些额外的工作,比如保活处理;另外就是如果客户端太多的话,服务器端是无法支撑的。

3.序列化方式

网络传输中的数据都需要经过序列化和反序列化处理,所以这一块的性能也很重要;常见的序列化包括:json和二进制方式,json常见的如fastjson,jackson等,二进制如protobuf,thrift ,kryo等;这个可以分别从序列化的性能,大小,以及使用方便性考虑;当然稳定性和安全性也需要考虑,比如fastjson频繁爆出安全漏洞;

4.协议

这里主要讲的是应用层协议,RPC一般都会自定义协议,当然也有直接使用现有协议的比如http协议;自定义协议可以自己掌控,协议可以做的很小很精简,当然解码和编码需要自己去实现;如果使用现有的http协议,相对来说整个协议包是比较大的,但是已经是一种规范了,很多东西可以直接拿来用,更加通用;

可扩展性

可扩展性可以从两个角度来看,一个是服务提供方和消费方的负载均衡策略;另一个就是用户可以对框架进行自定义扩展;

1.负载均衡

RPC的两个核心组件服务提供方和消费方,需要提供横向扩展的机制,用以达到更高的负责,比如Spring Cloud、Dubbo提供的注册中心,然后再结合容错机制达到负载均衡的效果,很容易达到横向扩展;

2.SPI机制

一个好的框架是支持用户自定义的,用户根据自己的需求实现自定义扩展;JDK提供了SPI机制,很常见的一个场景就是数据库驱动;另外Dubbo在此基础上提供了更加强大的SPI机制;这种扩展对RPC来说是多方面的,可以是底层的通讯框架扩展,序列化扩展,注册中心扩展,容错机制扩展,协议扩展等等;

跨平台

现在开发语言多种多样,如果能做成跨平台,当然是一大优势,但是可能往往为了跨平台,会在一些地方做权衡让步,当然难度也更大;所以我们在实现一个RPC时需要明确自己的定位,就是针对某一种语言的还是跨平台支持;常见的支持跨平台的RPC框架如GRPC,Thrift;
实现机制可以参考一下,都有自己的一套接口定义语言IDL然后通过不同的代码生成器,生成各种编程语言消费端和服务器端代码,来保证不同语言直接的相关通信。

功能性

作为一个RPC框架,除了最核心的通信模块,序列化模块之外,功能性模块也很重要,往往为开发者节省了大量的时间,开发者可能因为RPC的某个功能而选择用此框架;常见的功能包括:容错机制,负载均衡机制,同步异步调用,结果缓存,路由规则,服务降级,多版本,线程模型等;

1.容错机制

在错综复杂的网络环境中,远程调用失败再正常不过了,容错机制就显得非常有必要了,常见的容错机制比如:失败重试,快速失败,失败直接忽略,并行调用多个服务器只要一个成功即返回等;用户可以根据需求选择自己合适的容错方案;

2.负责均衡

上面提到服务提供方和消费方的可扩展性,消费方面对多个提供方的时候需要有一定的负载均衡策略,来保证系统的稳定性;常见的策略如:随机,轮询,最少活跃调用数,一致性hash等;

3.同步异步调用

常见的同步调用有些场景无法满足需求,比如同时需要调用多个远程方法,而这其中可能有些执行比较慢的;这时候异步调用就显得重要了,可以同时异步发送多个请求,等待时间就是响应最慢的请求;具体可以通过Future,CompletableFuture来实现;

4.结果缓存

缓存一直是性能的不二法宝,某些场景下可能对服务提供方响应的数据实时要求性并不高,这时候如果可以在服务提供端提供结果的缓存机制,那么在性能上是一个很大的提升;可以自己设计一个本地缓存,当然也可以直接整合第三方缓存框架比如ehcache,jcache等;

5.路由规则

服务提供方往往是很多的,用户可能有一些特殊的需求,可以按照自己定义个规则来做路由,比如我们经常做的灰度发布,结合RPC提供的路由规则来实现会很简单;此规则可能是条件表达式,脚本,标签等,我们设计的RPC框架需要有一个给用户定义规则的地方,比如通过注册中心来实时推送,另外还需要相关的引擎来处理规则,比如脚本引擎;

6.服务降级

系统的高可用性原则中,很重要的一条就是降级处理,在一些非核心的功能中,可以在出现超时/故障时或者直接设置为降级服务,给一个统一的响应,这样可以把宝贵的资源留给那些核心的功能;可以参考Dubbo的实现,向注册中心写入动态配置覆盖规则,从而实现实时降级处理;

7.多版本

接口升级时常有的事情,我们可能会为了兼容之前的版本绞尽脑汁,但有时候还是无能为力,这时候多版本就显得很重要了,可以让两个版本同时存在,等到合适的机会在慢慢升级;像dubbo这种服务的维度就是服务名+版本号,所以很好实现多版本;而Spring Cloud维度没有到版本号,可以通过路由规则去实现;

8.线程模型

在系统的高可用原则中,线程隔离是一条重要的原则,为什么要做隔离,可以拿Dubbo及其底层通信框架netty为例,netty作为通信框架本身是有自己的线程模型,如果业务处理线程直接使用底层的通信线程模型,这样就会出现因为业务阻塞而导致通信线程模型阻塞;这时Dubbo提供自己的线程模型就尤为重要,可以做到线程隔离,业务线程不影响通信线程;

当然作为一个RPC框架实现的功能可以很多,这里主要讲一些我们平常用的比较多的功能;

可监控

一个运行稳定的系统,没有一个专门的监控平台是不行的,当然RPC也不例外,常见的比如dubbo的monitor模块,Spring Cloud Admin等;对于一个RPC我们主要监控:服务提供者有哪些,服务消费者有哪些,以及它们的状态,最好还有一些统计功能比如一段时间内的调用量,成功率,失败率,平均响应时间,最大响应时间,最大并发量等等;

使用性

最后说一下,我们设计出来的框架最终还是要给用户使用的,所以用户是否可以方便的使用也是一个很重要的点,某些框架可能就是因为使用繁琐导致最终被弃用;比如注解的方式相比较xml的方式就简单不少;还有比如服务维度来说:dubbo维度是接口,而Spring cloud维度是应用,整体来看Spring cloud使用起来更加方便;当然简单的API,文档以及Demo对开发者来说也是必不可少的;

总结

RPC本质上其实就是一次网络调用,很多设计其实都是在围绕,如何把它变成一个高可用,高并发的框架;其实这些设计理论适用于大部分的系统,都在为达到此目标而努力。

感谢关注

可以关注微信公众号「 回滚吧代码」,第一时间阅读,文章持续更新;专注Java源码、架构、算法和面试

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这