原视频参考地址:[全网最全的手写RPC教程] 手摸手教你写一个RPC-架构-设计-落地实现_哔哩哔哩_bilibili
项目地址:kkoneone11/kkoneoneRPC-master (github.com)
觉得对你有帮助的帮忙文章给个like和项目给个stars呀!!!
因为我的设计是从顶到底去写代码,因此这一部分的代码量会比往后的多,大家可以分几天去编写
拦截器层
在服务发送前后以及服务接收前后可进行一系列的拦截操作,或者是增强行为等。这是一个拓展机制。
写了ClientAfterFilter、ClientBeforeFilter、ServiceAfterFilter、ServiceBeforeFilter四个拦截器方便SPI的扩展。
FilterChain采用的是责任链的形式,addFilter用来加入过滤链,然后doFilter用来执行
而FilterConfig则是初始化服务提供方和服务调用方的SPI拓展。其中在extensionLoader的loadExtension方法先传入对应过滤器的接口去配置文件中找到对应的配置类,然后再通过get方法进行初始化并加入到addFilter中进行执行对应的过滤器链。
ServiceTokenFilter
首先如果客户端想要设置token的话得先在RpcProperties中客户端Map配置
从filterData拿到客户端的token之后,再到服务提供方拿对应的token然后进行token的检验
RpcInvokerProxy的invoke方法就是在此处开始进行第一次拦截
代码部分
ServiceTokenFilter
typescript
package org.kkoneone.rpc.Filter.service;
import org.kkoneone.rpc.Filter.FilterData;
import org.kkoneone.rpc.Filter.ServiceBeforeFilter;
import org.kkoneone.rpc.config.RpcProperties;
import java.util.Map;
/**
* token拦截器
* @Author:kkoneone11
* @name:ServiceTokenFilter
* @Date:2023/12/11 23:25
*/
public class ServiceTokenFilter implements ServiceBeforeFilter {
@Override
public void doFilter(FilterData filterData) {
final Map<String, Object> attachments = filterData.getClientAttachments();
final Map<String, Object> serviceAttachments = RpcProperties.getInstance().getServiceAttachments();
if (!attachments.getOrDefault("token","").equals(serviceAttachments.getOrDefault("token",""))){
throw new IllegalArgumentException("token不正确");
}
}
}
SPI
使用SPI后对于整个程序的拓展性有了极大的提升。 概念: 制定好接口规范协议后,其他服务按照你制定好的服务协议。随后使用SPI进行可插拔方式来加载不同的类。 例如:mysql的jdbc驱动,我先制定好一个协议,我不管你是什么sql厂商,你都遵循我的协议来然后并把依赖给我,然后我使用spi机制来进行加载类就行。
其中 load方法是懒加载的形式,
因为ServiceLoader底层实现的是一个懒迭代器的形式
而只有当load被调用的时候,如以下情况,才会被加载
而加载对应的服务就是依靠迭代器中的hasNextService()方法
通过这个来拼接去寻找对应的服务名,通常是在META-INF下会有services,里面放的就是接口名和路径。
当用户或者客户想要实现自己的注册中心或者编码序列化或者拦截器的时候
只需要实现一个ExtensionLoader,里面配置好客户要动态实现的前缀为寻找该动态配置的路径,然后在下面撰写自己的方案即可
而加载方法就是loadExtention()方法,传入接口之后,然后同时去系统SPI和用户SPI中寻找该接口下面的类
前缀和类名拼接起来之后就是对应的路径名,然后根据对应的路径名去读取对应配置的文件然后放到对应的map中,并没有进行初始化
例如:ConsumerPostProcessor中的afterPropertiesSet()方法
其中序列化工厂初始化的时候就是里面实现了init()方法是通过SPI机制的ExtensionLoader的loadExtension方法传入RpcSerialization这个接口去加载,而加载之后不会立刻去初始化。
只有当要调用get方法,传入的是对应的是key,key是前面的这个,=后面的是value
容错层
在服务提供方发生错误时(无法提供服务),此时有可能还会有其他服务能提供,因此还需要尝试找其他服务。而这里采用的是switch的方式去选择对应的容错策略,这样的缺点是没有办法实现拓展性,因此此处也可以通过SPI机制来进行策略的拓展。
如果想改造的可以根据自己的需求去拓展,这里只是先创建了容错策略接口和容错策略工厂。这里的作用和实现SPI的流程是一致的。如果要实现具体的容错策略算法,要先继承接口FaultTolerantStrategy,具体的逻辑写在对应的类中(如FailoverFaultTolerantStrategy),并在META-INF配置写上对应的配置文件。然后把工厂类就get和init方法即可。
线程池
如果服务提供方B和C都挂了,那么请求就都打到A上了,这样也会增大A的压力导致A挂掉,因此加一个线程池还可以进一步减缓压力