简单搭建RPC框架项目

什么是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():设置任务阻塞策略,即可以设置当前重试完成,下次重试开始前的这段时间做什么事情.

白名单:服务端在注册节点时,将幂等性的服务注册在白名单中,客户端请求服务前,先去白名单中看该服务是否为幂等服务

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您介绍一下如何在Spring Boot中使用Dubbo和Nacos搭建RPC框架。 Dubbo是一个高性能、轻量级的开源Java RPC框架,可以提供远程方法调用和服务发现功能。Nacos是阿里巴巴开源的一个服务发现和配置管理平台,也是Dubbo官方推荐的服务注册中心和配置中心。 下面是具体的步骤: 1. 在pom.xml中添加Dubbo和Nacos的依赖: ```xml <!-- Dubbo依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.3</version> </dependency> <!-- Nacos依赖 --> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>1.2.1</version> </dependency> ``` 2. 在application.properties中配置Dubbo和Nacos: ```properties # Dubbo配置 dubbo.application.name=consumer dubbo.registry.address=nacos://localhost:8848 dubbo.registry.username=nacos dubbo.registry.password=nacos # Nacos配置 spring.cloud.nacos.discovery.server-addr=localhost:8848 spring.cloud.nacos.discovery.username=nacos spring.cloud.nacos.discovery.password=nacos ``` 3. 创建Dubbo服务提供者: ```java @Service public class UserServiceImpl implements UserService { @Override public User getById(Long id) { // 实现自己的业务逻辑 return new User(id, "张三"); } } ``` 4. 在Dubbo服务提供者中配置Dubbo: ```java @Configuration public class DubboProviderConfig { @Value("${dubbo.application.name}") private String appName; @Value("${dubbo.registry.address}") private String registryAddress; @Value("${dubbo.registry.username}") private String registryUsername; @Value("${dubbo.registry.password}") private String registryPassword; @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName(appName); return applicationConfig; } @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress(registryAddress); registryConfig.setUsername(registryUsername); registryConfig.setPassword(registryPassword); return registryConfig; } @Bean public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); return protocolConfig; } @Bean public ProviderConfig providerConfig() { ProviderConfig providerConfig = new ProviderConfig(); providerConfig.setTimeout(5000); return providerConfig; } } ``` 5. 创建Dubbo服务消费者: ```java @RestController public class UserController { @Reference(version = "1.0.0") private UserService userService; @GetMapping("/user/{id}") public User getUserById(@PathVariable Long id) { return userService.getById(id); } } ``` 6. 在Dubbo服务消费者中配置Dubbo和Nacos: ```java @Configuration public class DubboConsumerConfig { @Value("${dubbo.application.name}") private String appName; @Value("${dubbo.registry.address}") private String registryAddress; @Value("${dubbo.registry.username}") private String registryUsername; @Value("${dubbo.registry.password}") private String registryPassword; @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName(appName); return applicationConfig; } @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress(registryAddress); registryConfig.setUsername(registryUsername); registryConfig.setPassword(registryPassword); return registryConfig; } @Bean public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); return protocolConfig; } @Bean public ConsumerConfig consumerConfig() { ConsumerConfig consumerConfig = new ConsumerConfig(); consumerConfig.setTimeout(5000); return consumerConfig; } @Bean public NacosDiscoveryProperties nacosDiscoveryProperties() { NacosDiscoveryProperties nacosDiscoveryProperties = new NacosDiscoveryProperties(); nacosDiscoveryProperties.setServerAddr("localhost:8848"); nacosDiscoveryProperties.setUsername("nacos"); nacosDiscoveryProperties.setPassword("nacos"); return nacosDiscoveryProperties; } @Bean public NacosServiceDiscovery nacosServiceDiscovery() { return new NacosServiceDiscovery(); } @Bean public ReferenceConfig<UserService> userServiceReferenceConfig() { ReferenceConfig<UserService> referenceConfig = new ReferenceConfig<>(); referenceConfig.setInterface(UserService.class); referenceConfig.setVersion("1.0.0"); referenceConfig.setCluster("failfast"); return referenceConfig; } @Bean public DubboBootstrap dubboBootstrap() { return DubboBootstrap.getInstance(); } } ``` 这样,我们就成功地在Spring Boot中使用Dubbo和Nacos搭建RPC框架
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值