一、服务暴露过程
-》 创建ServiceBean,这个类实现了InitializingBean
-》 准备各种配置,保证服务暴露时不会缺失属性
-》 ServiceBean实现了ApplicationListener接口,在spring触发ContextRefreshEvent时,开始暴露服务。
-》 检查存根服务或者本地服务(现在已经属于废弃状态),所谓存根,就是一个接口的本地实现,用于提供缓存服务或者其他的操作,它的其中一个参数就是能够传入一个远程
代理实现,用于在缓存未命中时调用
-》 检查mock服务,但服务调用失败时,进行降级处理
-》 将配置参数与协议服务构建URL --> 比如:dubbo://192.168.56.1:20880/groupName/com.dubbo.service.UserService:1.0?anyhost=true&application=hello-world-app&bean.name=com.dubbo.service.UserService
&bind.ip=192.168.56.1&bind.port=20880&dubbo=2.0.2&generic=false&interface=com.dubbo.service.UserService&methods=sayHello
&pid=26392&side=provider×tamp=1567943599518
-》 通过SPI获取代理工厂,默认的是(JavassistFactory),创建Invoker
-》 创建Exporter,用于记录已经暴露的Invoker,消费者可以通过对应的协议找到已经注册的服务提供接口实现,然后调用
-》 包装Exporter为ListenerExporterWrapper并设置监听器,在服务暴露与注销时触发监听器的调用
-》 使用ProtocolFilterWrapper创建Filter拦截链
-》 创建HeaderExchangeServer并在构造器中启动心跳,在netty的通道属性上记录通信时间,每隔固定时间发送心跳事件,心跳得到回应就会刷新通道时间,其他通信方式,比
如收到请求时,也会主动刷新通道时间,如果超过允许的心跳超时,那么关闭通道(如果是客户端,会尝试重新连接,调用Boostrap进行连接)
-》 创建ServerBoostrap,创建netty服务,添加解码器
-》 根据我们设置的协议创建对应的注册中心实例,比如redis,那么就会创建redis连接,通过hset设置提供者地址,hash键为gourpName+接口名+/+category名
(提供者的为provider)hash表的key值为提供者的url,value为注册时间
-》 设置成功后,发布以通道为hash键的注册事件,那么订阅了这个通道的redis客户端就可以收到消息,用于更新自己的提供者目录
-》 设置调度器,调度未注册成功,未注销成功,未订阅成功,未退订成功的
二、服务引入过程
-》 创建ReferenceBean,这个类也实现了InitializingBean
-》 准备好各种配置,检查存根服务或者本地服务(现在已经属于废弃状态),所谓存根,就是一个接口的本地实现,用于提供缓存服务或者其他的操作,
它的其中一个参数就是能够传入一个远程代理实现,用于在缓存未命中时调用
-》 检查mock服务,但服务调用失败时,进行降级处理
-》 暂时修改注册中心协议为registry,通过SPI获取registryProtocol,在registryProtocol协议的refer方法中恢复协议,假设是redis,那么协议url变回redis://xxxx
-》 根据设置的协议redis创建RedisRegistry,订阅groupname/接口名/providers,configurators,routers,当这些redis通道发生变化时会收到提醒
-》 根据不同的订阅目录,比如providers,configurators,routers对url进行分组,形成不同的List
-》 接下来就是和服务暴露差不多的逻辑了,创建ExchangeClient,利用netty创建与提供链接的nettyClient
下面是服务引入所创建的一些重要类
MockClusterInvoker
invoker -> FailoverClusterInvoker(如果消费者设置了group,那么这个会变成MergeableClusterInvoker)
directory -> RegistryDirectory(具有动态更新提供者目录的功能)
urlInvokerMap -> 这是一个Map,key为提供者url,值为InvokeDelegate
InvokeDelegate
invoker -> ProtocolFilterWrapper
invoker -> ListenerInvokerWrapper
invoker -> DubboInvoker
clients -> 这是一个数组,它的元素个数取决于配置服务引入时设置了允许多少连接数,其实例为ReferenceCountExchangeClient
ReferenceCountExchangeClient
client -> HeaderExchangeClient
client -> NettyClient
channel -> NioSocketChannel
handler -> MultiMessageHandler
handler -> HeartbeatHandler
handler -> AllChannelHandler
handler -> DecodeHandler
handler -> HeaderExchangeChannel
handler -> DubboProtocol#requestHandler(内部类)
三、dubbo请求协议头
协议头有16个字节(0 ~ 127)大小
0~15位:魔数 oxdabb
16位:是否为Request,1为request,0为response
17位:是否为twoWay,从源代码来看这个twoWay的意思好像表示是否需要响应的意思
18位:是否是事件,表示心跳事件,可能当前的请求或者响应来自心跳,不是接口消费请求
19~23位:序列化id,用于标识当前使用的数据序列化方式是什么,比如hessian2
24~31位:状态,比如ok(20)
32~95位:请求的id,从后面的源码中,我发现它会以key为请求id -> DefaultFuture存储在com.alibaba.dubbo.remoting.exchange.support.DefaultFuture#FUTURES中
而这个DefaultFuture类似于JDK的Future,调用其get方法会发生阻塞知道有结果处理完毕
96~127位:标识请求体或者响应体的数据长度
128及以后:数据,如果是响应,第一个字节保存调用结果,用于表示返回值是否null,是否有异常,是否有附带参数,是否正常调用返回了值
四、请求处理过程
-》 消费者端
-》 检查是否强制要求mock服务调用,如果不是强制要求并且需要mock服务调用的话,在调用远程接口失败时调用mock服务
-》 获取提供者,通过路由进行一波选择,然后再通过负载均衡获取提供者
-》 如果调用时发生错误(会对异常进行判断是否是业务异常,换句话说就是不是提供者那边抛出的业务错误,比如通道被关闭这种),那么进行换一个提供者进行重试(故障转移),重试次数默认2次,重试的时候会重新进行路由筛选(因为抛出了错误,那么就有可能是因为提供者下掉了,所以要重新筛选),然后再次根据负载均衡去获取一个invoker。
-》 获取提供者时会判断是否使用了粘性策略,换句话说就是使用上次筛选出来的提供者
-》 通过负载均衡获取提供者,默认使用随机策略,如果给提供者设置了权重,那么会通过权重来进行概率性计算
-》 获取到invoker之后可能内部定义了多个连接,即有多个客户端,那么通过轮询的方式获取其中一个
-》 执行filter链
-》 如果请求是异步的并且不需要接收响应的,那么返回一个空RpcResult对象即可,如果是请求是异步的并且需要提供者响应,那么将
首先构建一个Future(dubbo的future),然后以 请求id -》future的方式
注册到DefaultFuture#FUTURES中,最后将这个future设置到RpcContext中。用户可以通过这个RpcContext获取到这个future
-》 如果请求不是异步的,那么首先构建一个Future(dubbo的future),然后以 请求id -》future的方式注册到DefaultFuture#FUTURES中,然后调用future的get方法进行
等待,如果设置了等待时间,那么超过这个时间远程服务没有返回,那么会抛错
-》 创建Request,通过InternalEncoder进行编码
protected void com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec#encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
RpcInvocation inv = (RpcInvocation) data;
//写入协议版本
out.writeUTF(version);
//写入接口
out.writeUTF(inv.getAttachment(Constants.PATH_KEY));
//写入接口版本
out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));
//写入方法名
out.writeUTF(inv.getMethodName());
//写入参数类型
out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes()));
Object[] args = inv.getArguments();
if (args != null)
for (int i = 0; i < args.length; i++) {
//写入参数,如果存在回调参数,那么会在注册exporter,以便服务端进行回调时能够调用
out.writeObject(encodeInvocationArgument(channel, inv, i));
}
//写入附加参数
out.writeObject(inv.getAttachments());
}
|
|
V
-》 服务提供者端
-》 通过InternalDecoder进行解码,读取16字节的协议头,解析魔数,不是魔数的数据将以telnet命令的方式进行解析
-》 直到找到魔数头为止,然后开始检查数据体是否超过负载,超过会抛错
-》 解析flag,1表示请求,0表示响应,解析协议类型
-》 此处我们讨论的是请求过程,那么这个flag就是1,默认序列化协议为hessian2,解析请求id
-》 创建Request对象,创建DecodeableRpcInvocation,然后调用DecodeableRpcInvocation#decode方法解析数据体
-》 解析请求接口版本,接口名,方法名,参数,参数类型
-》 处理参数回调,生成代理,在提供者的方法体调用了这个回调参数后会向消费端发送请求(和消费者请求提供者是一样的格式)
-》 触发netty的通道读事件
-》 经过一系列dubbo的通道处理,然后从对应的协议中的com.alibaba.dubbo.rpc.protocol.AbstractProtocol#exporterMap<String, Exporter<?>>中获取到导出的invoker
-》 调用dubbo的Filter链,其中有个GenericFilter,用于处理泛化调用的参数的,最终调用目标服务实现
-》 将返回结果包装成Response对象,然后再包装成RpcResult对象
-》 使用InternalEncoder编码器对返回值进行编码
-》 根据调用情况返回不同的请求体:
1、无异常,但返回值为null的,如果有附加参数,设置附加参数
2、无异常,有返回值的,如果有附加参数,设置附加参数
3、有异常的,如果有附加参数,设置附加参数
protected void com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec#encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
Result result = (Result) data;
// currently, the version value in Response records the version of Request
//检查dubbo版本是否支持附加参数
boolean attach = Version.isSupportResponseAttatchment(version);
Throwable th = result.getException();
if (th == null) {
Object ret = result.getValue();
//无异常,返回为null的
if (ret == null) {
out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE);
} else {
//写入返回值
out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE);
out.writeObject(ret);
}
} else {
//写入异常
out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION);
out.writeObject(th);
}
if (attach) {
// returns current version of Response to consumer side.
//写入附加参数
result.getAttachments().put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
out.writeObject(result.getAttachments());
}
}
|
|
V
-》 消费端
-》 创建DecodeableRpcResult,根据服务端返回的参数类型进行解码
public Object DecodeableRpcResult#decode(Channel channel, InputStream input) throws IOException {
ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
.deserialize(channel.getUrl(), input);
//读取一个字节,这个字节是用来标识响应结果类型的
byte flag = in.readByte();
switch (flag) {
//如果响应结果为null
case DubboCodec.RESPONSE_NULL_VALUE:
break;
case DubboCodec.RESPONSE_VALUE:
try {
//解析方法的返回类型
Type[] returnType = RpcUtils.getReturnTypes(invocation);
//根据接口方法返回类型反序列化值
setValue(returnType == null || returnType.length == 0 ? in.readObject() :
(returnType.length == 1 ? in.readObject((Class<?>) returnType[0])
: in.readObject((Class<?>) returnType[0], returnType[1])));
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read response data failed.", e));
}
break;
//如果调用远程接口出错了
case DubboCodec.RESPONSE_WITH_EXCEPTION:
try {
Object obj = in.readObject();
//如果这个obj不是异常类型,嘻嘻,报错
if (obj instanceof Throwable == false)
throw new IOException("Response data error, expect Throwable, but get " + obj);
//记录异常
setException((Throwable) obj);
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read response data failed.", e));
}
break;
//响应null值,但是附带了其他的附加参数的
case DubboCodec.RESPONSE_NULL_VALUE_WITH_ATTACHMENTS:
try {
//直接反序列化附加参数即可
setAttachments((Map<String, String>) in.readObject(Map.class));
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read response data failed.", e));
}
break;
//正常调用还带有附加参数的
case DubboCodec.RESPONSE_VALUE_WITH_ATTACHMENTS:
try {
//解析调用方法的返回值类型
Type[] returnType = RpcUtils.getReturnTypes(invocation);
//发序列化返回值
setValue(returnType == null || returnType.length == 0 ? in.readObject() :
(returnType.length == 1 ? in.readObject((Class<?>) returnType[0])
: in.readObject((Class<?>) returnType[0], returnType[1])));
//反序列化附加参数
setAttachments((Map<String, String>) in.readObject(Map.class));
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read response data failed.", e));
}
break;
//调用远程接口发生异常,但附加了参数的
case DubboCodec.RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS:
try {
Object obj = in.readObject();
if (obj instanceof Throwable == false)
throw new IOException("Response data error, expect Throwable, but get " + obj);
//先反序列化异常
setException((Throwable) obj);
//然后反序列化附加参数
setAttachments((Map<String, String>) in.readObject(Map.class));
} catch (ClassNotFoundException e) {
throw new IOException(StringUtils.toString("Read response data failed.", e));
}
break;
default:
throw new IOException("Unknown result flag, expect '0' '1' '2', get " + flag);
}
//清理
if (in instanceof Cleanable) {
((Cleanable) in).cleanup();
}
return this;
}
解析返回值
public Object recreate() throws Throwable {
if (exception != null) {
//如果有异常,抛出
throw exception;
}
return result;
}
类介绍
- ProviderConfig:提供者配置,用于作为父级配置,也可以指定为默认配置,为那些不
被标签包裹的service标签提供默认配置 - ApplicationConfig:用于提供应用信息,比如版本,应用名,使用的JDK版本,所属组
织,使用的注册中心,监控中心等 - ModuleConfig:模块信息
- RegistryConfig:注册信息,用于定义注册中心地址,密码,用户名,协议等
- MonitorConfig:监控中心,定义协议,地址等,监控中心用于获取服务调用信息,记录
调用次数,健康状态 - ConsumerConfig:消费者配置,通常用于作为其他引入的父配置,或者作为默认配置
- ProtocolConfig:协议配置,用于定义提供者将暴露在什么端口,什么地址,使用什么
协议进行通信 - ServiceBean:服务提供者bean,除了定义提供者配置外,它将合并所有配置,进行服务
的暴露和注册 - ReferenceBean:服务引入bean,用于为消费者接口生成代理,提供远程调用的能力
- ProxyFactory:代理工厂,实现了有JavassistProxyFactory和JdkProxyFactory等,用于生成代理类
- ExtensionLoader:用于加载SPI
- Protocol:协议,比如有DubboProtocol实现类,用于通过什么样的协议去暴露服务,注册什么样的协议编码解码器
- Exchanger:定义了绑定,连接的能力
- NettyServer:维护中创建服务socket时的ServerBootstrap,netty的Channel,还有两个EventLoopGroup(Boss和Worker)
- InternalDecoder:解码器
- InternalEncoder:编码器
- NettyServerHandler:netty通道处理器,内部适配了dubbo自己的通道处理器
- MockClusterInvoker:提供mock服务的调用
- FailoverClusterInvoker:故障转移,比如提供者调不通的时候,会自动调用其他的提供者
- RegistryDirectory:注册目录,用于维护提供者目录
设计模式
- 代理模式:JavassistProxyFactory
- 工厂模式:JavassistProxyFactory
- 策略模式:SPI,根据url参数的指定获取不同的实现
- 适配器模式:NettyServerHandler适配dubbo自己的通道处理
- 责任链模式:filter
- 装饰器模式:各种Wrapper,ProtocolFilterWrapper,通道handler
- 观察者模式:ListenerExporterWrapper,在服务暴露与注销时调用监听器
SPI
Service Provider Interface
通过ExtensionLoader加载META-INF/services/,META-INF/dubbo/,META-INF/dubbo/internal/下的服务。SPI服务实现类必须有@SPI注解,@SPI注解可以指定默认的自适应接口实现,
如果某个实现类上面标注了@Adaptive,那么这个类优先被选中为自适应的默认实现,如果没有找到标注为@Adaptive的实现类,那么会通过javasist进行动态生成一个接口实现,
这个动态生成的实现类会实现被@Adaptive标注的方法,没有被@Adaptive标注的方法默认实现为抛出不支持的异常,被@Adaptive标注的方法,必须要有URL参数或者对应传入的bean有
getUrl方法,否则抛错,@Adaptive注解上需要指定从URl中获取什么样的参数值的参数名,如果url上面没有指定对应参数名的值,那么使用@SPI注解上指定的默认扩展名去获取接口实现。
@Activate:这个注解主要用于标识每个SPI实现类被激活,比如我们使用的Filter,只有被标注了@Activate注解的才能被用于构建过滤器链。
@Extension:这个注解已被废弃,用来指定扩展实现的名字,通常我们在配置SPI实现的时候就会指定SPI扩展名
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
2022-03-14 4、MapperScannerConfigurer