什么是RPC框架:目前许多项目被分成多个微服务模块,如一个抖音APP中有短视频模块、电商模块、抖音课堂模块,而这些模块都被部署在不同的服务器上,为了保证这些模块能在不同服务器上,系统在高并发环境下的正常运行。就需要使用到RPC协议思想。
RPC(Remote Procedure Call Protocol):远程过程调用协议
目的:就是不同的服务间方法调用就像本地调用一样便捷
应用:应用框架:阿里的Dubbo,谷歌的gRPC、SpringBoot/SpringCloud
远程通信协议:RMI、Socket、soap(HTTP XML)、rest(HTTP JSON)
通信框架:MINA/Netty
技术栈:
fastjson/protobuf主流数据序列化方式
高性能网路框架netty
分布式协调应用zookeeper
负载均衡算法实现
限流算法实现
重试任务和定时任务在项目场景下的运用
1.封装信息格式,请求与响应
请求(接口:serializable主要用于实现对象的序列化和反序列化):服务类名、调用的方法名、参数列表、参数类型
响应(反序列化):状态码、状态信息、具体数据、构造成功信息
2.动态代理
客户端通过获取clientproxy的代理对象,通过代理对象调用方法,获取结果
动态代理的具体实现:
1.传入参数service接口的class对象,反射封装成一个request,反射获取request对象,socket发送到服务端
2.构建request,服务类名,通过method的getDeclaringClass().getName()获得,调用的方法名通过method.getName()直接获得,参数列表args,参数类型method.getParameterTypes()
3.IOClient.sendRequest 和服务器进行数据传输
3.服务端实现
1.定义Server接口,start,stop
2.实现接口,首先通过serviceprovider实现依赖注入,其次创建一个serversocke对象监听客服端发送请求,当没有连接时候,通过serversocket.accept()堵塞,有连接时,创建一个新的线程执行处理
3.workThread类负责启动线程和客户端进行数据传输
其中getResponse方法负责解析收到的request请求,寻找服务进行调用并返回结果
4.接收到request后,我们在本地服务存放器找到需要的服务,通过反射调用方法,得到结果并返回
本地服务存放器
serviceProvider类
1.集合中存放服务的实例,通过哈希表来存储Map<String,Object>
2.本地注册服务,先将服务器中有哪些方法存储进哈希表中
3.获取服务实例,直接拿服务类名
4.引入netty框架
1.socket效率低,netty高性能网络框架io传输由bio->nio模式,底层是池化技术复用资源
2.可以自主编写 encode/decode,序列化器等,可扩展性和灵活性高
3.支持TCP/UDP多种传输协议,支持堵塞返回和异步返回
主要是在信息传输部分进行代码的重构
1.对IOClient进行重写
1.对传输类定义接口RpcClient,可以灵活选择不同方式的传输类,耦合性低,里面只有senrequest方法
2.传输类NettyRpcClient类实现接口,构造方法里面要接收到IP地址和端口,
2.1netty客户端初始化,第一个对象接收NioEventLoopGroup,第二个对象接收Bootstrap。nettyClientinitializer配置netty对信息的处理机制
2.2实现sendRequest方法:1.创建一个channelFuture对象,代表这一个操作事件,sync方法表示堵塞直到connect完成。2.channel表示一个连接的单位,类似与socket。3.发送数据,channel.writeAndFlush(request)
4.sync堵塞获取结果。(当前场景是堵塞获取结果,也可以选择添加监听器的方式来异步获取结果,channelFuture.addlistener),而堵塞获得结果通过给channel设计别名,获取特定名字下的channel中的内容这个在hanlder中设置,AttributeKey是线程隔离的,不会有线程安全问题。
获取结果:attributekey《response》 key = attributekey.valueof("response")
Response = channel.attr(key).get()
2.NettyClientInitializer类,配置netty对信息的处理机制
1.指定编码器(将信息转为字节数组),解码器(将字节数组转为信息)
2.指定信息格式,信息长度,解决粘包问题(TCP传输是面向流的协议,接收方不知道一条信息的具体字节数,由于TCP的流量控制机制,发生少读或者多读导致信息不能读完全的情况就叫做粘包)只要在发送消息时,先告诉接收方信息的长度,读取指定长度的字节就能避免
3.指定对接收信息的处理handler
消息格式【长度】【消息体】,其次计算当前待发信息的长度,写入到前四个字节中,使用java自带的编码形式。解码也可以用java自带的,但要在构造函数中传入一个ClassResolver对象,用于解析类名并加载相应的类,Handler指定对接收消息的处理方式:接收到response,给channel设计别名,让sendRequest里读取response
3.服务端重构
NettyServer类首先实现server接口:
netty服务线程组boss负责建立连接,work负责具体请求
与客户端的netty相似,1.启动netty服务器,2.初始化3.同步堵塞4.死循环监听。
nettyserverinitializer与客户端一样。handler负责接收来自客户端的信息,并解析调用
getresponse()1.得到服务名,从请求中获取具体调用的方法名。2.得到服务端相应服务实现类3.反射调用方法
Method method = null;
try {
method = service.getClass().getMethod(request.getMethodName(), request.getParamsTypes());
Object invoke = method.invoke(service, request.getParams());
return RPCResponse.
success
(invoke);
至此netty网络框架就已经搭好了。
客户端-》调用rpcclient.sendRequest->nettyclientInitializer->编码->发送服务端rpcserver接收->nettyserverinitializer->decoder解码->nettyserverhandler->getResponse调用->返回结果->客户端接受->解码->clienthandler处理结果并返回给上层
5.引入zookeeper作为注册中心
上述处理中,我们在调用服务时,对目标的IP地址和端口号都是写死的,默认本地地址和9999端口号,但在实际场景下,服务的地址和端口会被记录到注册中心,服务端上线时,在注册中心注册自己的服务与对于的地址,而客户端调用服务时,去注册中心根据服务名找到对应的服务端地址。
zookeeper是一个经典的分布式数据一致性解决方案:1.高性能2.高可用
3.严格顺序访问。其数据结构为树状结构,节点为znode:一个znode可以由多个子节点。使用路径path定位某个znode。三个部分:节点数据,子节点,节点状态。
1.客户端重构
sendrequest方法中,从注册中心拿到host,port。
实现向注册中心查询服务地址的类是实现ServiceCenter接口的ZKServiceCenter类:
1.curator提供的zookeeper客户端2.zookeeper根路径节点3.构造方法,指数时间重试,zookeeper地址固定,客户端和服务端都要与之建立连接,sessionTimeoutMs与zoo.cfg中的tickTime有关系,根据minSession,maxSession两个参数默认是ticktime的两倍和二十倍,使用心跳监听状态
4.根据服务名返回地址InetSocketAddress
6.netty自定义编码器,解码器,序列化器实现json,protobuf序列化方式
7.负载均衡算法
1.轮询法:将所有请求按顺序轮流分配给后端服务器,依次循环。优点:简单易实现,缺点:服务器配置不一样时不适合使用轮询法。2.随机法:将请求随机分配到各个服务器。优点:分配较为均匀,避免了轮询法可能出现的连续请求分配给同一台服务器的问题3.一致性哈希法:将输入(客户端IP地址)通过哈希函数映射到一个固定大小的环形空间上,每个服务器也映射到这个哈希环上。客服端的请求会根据哈希值在哈希环上顺时针查找,遇到的第一个服务器就是该请求的目标服务器。优点:提高系统可用性,具有良好的可扩展性。缺点:实现较为复杂,哈希环可能会偏斜
8.超时重试&白名单
当调用端发起的请求失败时,RPC框架自身可以进行重试,再重新发送请求,通过这种方式保证系统的容错率
本文中使用了灵活性和功能性更强的Guava Retry,
通过很多方法来设置重试机制:
retryIfException():对所有异常进行重试 retryIfRuntimeException():设置对指定异常进行重试 retryIfExceptionOfType():对所有 RuntimeException 进行重试 retryIfResult():对不符合预期的返回结果进行重试还有五个以 withXxx 开头的方法,用来对重试策略/等待策略/阻塞策略/单次任务执行时间限制/自定义监听器进行设置,以实现更加强大的异常处理:withRetryListener():设置重试监听器,用来执行额外的处理工作 withWaitStrategy():重试等待策略 withStopStrategy():停止重试策略 withAttemptTimeLimiter:设置任务单次执行的时间限制,如果超时则抛出异常 withBlockStrategy():设置任务阻塞策略,即可以设置当前重试完成,下次重试开始前的这段时间做什么事情.
白名单:服务端在注册节点时,将幂等性的服务注册在白名单中,客户端请求服务前,先去白名单中看该服务是否为幂等服务