你的RPC框架是如何实现的?
首先我自定义了三个重要的注解,第一个注解是@RpcService,用来实现服务注册,作用于服务实现类上,相当于Spring中的@Service注解。它有两个属性group和version,当一个接口有多个实现类时,可以根据分组名和版本号找到具体的实现类。然后在postProcessBeforeInitialization方法中会扫描被RpcService注解标注的类,并将其注册到zookeeper中,同时也将它的bean添加到一个HashMap集合中,key是接口名+分组名+版本号。
第二个注解是@RpcScan,作用于启动类上,它只有一个属性,是个字符串数组,用来指定包路径的,我们在@RpcScan注解中使用@Import注解导入了一个扫描类,它会扫描指定包路径下的带有@RpcService注解的类,并将其注册为bean,交给Spring管理。
第三个注解是@RpcReference,作用与Service接口对象上,相当于Spring中的@Autowired注解,它也有两个属性group和version,在postProcessAfterInitialization方法中会扫描被RpcReference注解标注的对象,并根据指定的分组名和版本号创建对应的代理对象。每次调用目标对象的方法时,会调用代理对象的invoke方法,先将接口名、方法名、参数、分组名、版本号等信息封装成RpcRequest对象,并给RpcRequest设置一个唯一的请求id,以便在处理响应结果时,能分辨出是哪个请求的响应结果。发送请求前先根据接口名、分组名和版本号去zookeeper中找到对应的Netty服务器的ip地址和端口号,然后就能根据ip地址和端口号找到对应的channel,接着我们会把RpcRequest封装成RpcMessage对象并设置压缩方式、序列化方式和消息类型,最后把RpcMessage写入到channel中。
别人如何使用你的框架?
我把RPC框架的核心实现类都放在了一个模块下,如果别人想要在项目中使用这个框架,需要先依赖我们的模块,然后根据三个注解的使用规则来使用RPC框架。使用@RpcService注解标注需要被注册到注册中心的服务实现类,并指定实现类的分组名和版本号,当一个接口有多个实现类时,可以根据分组名和版本号找到具体的实现类。使用@RpcScan注解标注启动类并指定一个包路径,它会将包路径下带有@RpcService注解的类注册为bean,交给Spring管理。使用@RpcReference标注服务接口对象,并指定分组名和版本号,这个注解就相当于Spring中的@Autowired注解,然后我们就能使用接口对象调用相应的方法了
请求的发送和响应流程?
被@RpcReference标注的接口对象,我们都会在postProcessAfterInitialization方法中创建出它的代理对象,当调用目标对象的方法时,会调用代理对象的invoke方法,先将接口名、方法名、参数、分组名、版本号等信息封装成RpcRequest对象,并给RpcRequest设置一个唯一的请求id,以便在处理响应结果时,能分辨出是哪个请求的响应结果。发送请求前先创建一个CompletableFuture对象并存入到ConcurrentHashMap中,key是请求id。然后根据接口名、分组名和版本号去zookeeper中找到对应的Netty服务器的ip地址和端口号,然后就能根据ip地址和端口号找到对应的channel,接着我们会把RpcRequest封装成RpcMessage对象并设置压缩方式、序列化方式和消息类型,然后把RpcMessage写入到channel中,接着调用CompletableFuture的get方法阻塞等待异步任务完成,在channel中把RpcMessage编码成字节数据。
服务端收到消息后先把消息解码为RpcMessage对象,如果是心跳请求,就直接返回心跳响应,如果是其他请求,就使用反射的方式执行目标方法,然后将结果和请求id封装成RpcResponse对象返回给客户端,客户端收到响应结果后会获取请求id对应的CompletableFuture对象,并调用它的complete方法将RpcResponse传递到代理对象的invoke方法中,最后将结果返回给用户,至此完成了一次RPC请求
服务在zookeeper中的路径?
服务在zookeeper中的路径格式为:/my-rpc/接口的包路径+分组名+版本号/ip地址 : 端口号
负载均衡算法控制打到服务节点的流量?
使用Random类根据服务子节点列表的长度,随机选择一个服务子节点。
首先在服务端使用ServerBootstrap配置并启动服务器,给它配置bossGroup和workerGroup,在ChannelPipeline中添加IdleStateHandler、自定义编解码器和自定义服务端消息处理器,然后在客户端使用Bootstrap配置并启动客户器,给它配置NioEventLoopGroup,在ChannelPipeline中添加IdleStateHandler、自定义编解码器和自定义客户端消息处理器。