问题 1: 为什么要把系统拆分成分布式的? 为啥要用 dubbo?
1. 为什么要将系统进行拆分?
要是不拆分系统, 一个大系统几十万行代码, 很多人共同维护一份代码, 简直是悲剧;
拆分了以后, 一个大系统拆分成很多小服务, 平均每个系统也就几万行代码, 每个服务部署到单独的机器
2. 如何进行服务拆分?
? 大部分系统, 是要进行多轮拆分的, 第一次拆分就可能将原来的多个模块拆分开来.
? 但是后来可能每个系统都变得很复杂了, 每个模块拆分出来的服务又要继续拆分.
3. 拆分后可以不用 dubbo 吗?
? 当然可以, 大不了各个系统之间, 直接基于 springmvc, 就通过纯 httpj 接口互相通信. 但是这里肯定是由问题的, 因为 HTTP 接口通信维护起来成本很高, 要考虑超时重试, 负载均衡等各种问题.
所以 dubbo 说白了, 就是一个 rpc 框架, 就是本地就是进行接口调用, 但是 dubbo 会代理这个调用请求, 跟远程机器网络通信, 处理负载均衡, 服务上下线自动感知, 超时重试等问题, 就不用我们自己做, 交给 dubbo.
?
问题 2: 说一下 dubbo 的工作原理, 注册中心挂了可以继续通信吗? 说一下一次 rpc 请求的流程?
1.dubbo 工作原理
2.dubbo 分层
第一层: service 层, 需要服务提供方和消费方来实现
第二层: config 层, 配置层, 主要是对 dubbo 的各种配置
第三层: proxy 层, 服务代理层, 透明生成客户端的 stub 和服务端的 skeleton
第四层: registry 层, 服务端的注册与发现
第五层: cluster 层, 集群层, 封装多个服务提供者的路由以及负载均衡, 将多个实例组合成一个服务
第六层: monitor 层, 监控层, 对 rpc 接口的调用次数和调用时间进行监控
第七层: protocol 层, 远程调用层, 封装 rpc 调用
第八层: exchange 层, 信息交换层, 封装请求响应模式, 同步转异步
第九层: transport 层, 网络传输层, 抽象 mina 和 netty 为统一接口
第十层: serilize 层, 数据序列化层
2. 注册中心挂了可以继续通信吗?
是可以的, 因为客户端第一次从注册中心获取服务端的服务后, 会缓存在自己本地, 下一次调用服务端不用去请求注册中心, 因此注册中心挂了是可以继续通信的
问题 3:dubbo 都支持哪些通信协议和序列化协议
1.dubbo 支持不同的协议
dubbo 协议
默认就是走 dubbo 协议, 单一长链接, NIO 异步通信
(NIO 通信原理: NIO 采用了 Reactor 模式 (类似于观察者模式, 不同之处在于 Reactor 模式可以监听多个主题), 通过一个多路复用器来监听多个客户端的网络句柄, 一旦监听到客户端的请求消息, 将对应的请求消息转发给对应的 Handler(业务处理类))
适合场景: 传输数据量较小, 但是并发很高
rmi 协议
走 java 二进制序列化, 多个短连接, 适合消费者和提供者数量差不多, 适用于文件的传输, 一般较少
hessian 协议
走 hessian 序列化协议, 多个短连接, 适用于提供者数量和消费者数量还多, 适用于文件传输, 一般较少使用
http 协议
走 JSON 序列化
webservice
走 SOAP 文本序列化
问题 4:dobbo 负载均衡策略和集群容错策略? 动态代理策略?
dobbo 负载均衡策略
1.random loadbalance
默认情况下, dubbo 是 random load balcance 随机调用实现负载均衡, 可以对 provider 不同的实例设置不同的权限, 会按照权重来负载均衡, 权重越大分配流量越高, 一般就用这个默认的就可以了.
2.roundrobin loadbalance
这个默认就是均匀地将流量打到各个机器上去, 但是如果各个机器的性能都不一样, 容易导致性能差的机器负载过高而宕机. 此时需要调整权重, 让性能差一点的机器承载权重少一些
3.leastactive loadbalance
这个就是自动感知一下, 如果某个机器性能差, 那么接收的请求越少, 约不活跃, 此时就会给不活跃的性能差的机器更少的请求
4.consistanthash loadbalance
一致性 hash 算法, 相同的参数的请求一定分配到一个 provider 上去, provider 挂掉的时候, 会基于虚拟节点均匀分配剩余的流量, 抖动不会太大. 如果需要的不是随机负载均衡, 是要一类请求都到这个节点上, 那就走这个一致性 hash 策略
dobbo 集群容错策略
1.failover cluster 模式
? 失败自动切换, 自动重试其他机器, dubbo 默认使用这种策略
2.failfast cluster 模式
一次失败就立即失败, 常见于写操作
3.failsafe cluster 模式
出现异常就忽略, 常用于不重要的接口调用, 比如日志记录
4.failbackc cluster 模式
失败了后台自动记录请求, 然后定时发送, 比较适合消息队列这种情况
5.focking cluster 模式
并行的调用多个 provider, 只要一个成功就立即返回
6.broadcast cluster 模式
逐个调用所有的 provider
dubbo 的动态代理策略
默认使用 javassist 动态字节码生成, 创建代理类
问题 5: 你了不了解 spi 机制呢? 如何基于 spi 机制对 dubbo 进行扩展?
SPI 机制
简单来说, 就是 service provider interface; 比如有一个接口 A, 现在这个接口 A 有三个实现类, 那么在运行的时候对接口到底用哪个实现类呢? 这就需要 SPI, 需要根据指定的配置和默认的配置, 去找到对应的实现类加载进来, 然后用这个实现类的对象
例如: 接口 A --> 实现类 A1 实现类 A2 实现类 A3
配置一下, 接口 A= 实现类 A2
在系统运行的时候, 会加载这个配置, 用实现类 A2 实例化出对象来提供服务
比如说你有一个工程 Demo , 里面有一个接口 A, 接口 A 在工程里是没有实现的 --> 系统在运行的时候, 怎么给接口 A 选择一个实现类呢?
你就可以自己搞一个 jar 包, META-INF/services/, 上放一个文件, 文件名就是接口名, 接口 A, 接口 A 的实现类 = com.ultrapower.service. 实现类 A2. 让工程 A 来依赖这个 jar 包, 然后在系统运行的时候, 工程跑起来, 对接口 A, 就会扫描自己依赖的所有 jar 包, 在每个 jar 包里找找, 有没有 META-INF/service 文件夹? 如果有, 在里面找, 有没有接口 A 这个名字的文件? 如果有, 在里面找找有没有接口 A 的实现类是你的 jar 包里的那个类
Dubbo 的 SPI 机制
dubbo 也用了 spi 的思想, 不过没有用到 jdk 的 spi 机制, 是自己实现的一套 SPI 机制
1 Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()
Protocol 接口, dubbo 需要判断一下, 在系统运行的时候, 应该选用这个 Protocol 接口的哪个实现类来实例化对象来使用呢?
他会去找一个你配置的 Protocol, 它就会将你配置的 Protocol 实现类, 加载到 jvm 中来, 然后会实例化对象, 就会用到你提供的哪个 Protocol 接口实现类
微内核, 可插拔, 大量的组件, Protocol 负责 RPC 调用, 你可以实现自己的 rpc 调用组件, 实现 Protocol 接口, 给自己一个实现类即可
这行代码就是 Dubbo 中大量使用的, 就是对很多组件, 都保留一个接口和多个实现, 然后在运行的时候动态的根据配置去找对应的实现类, 如果没有配置, 那就走默认的实现类.
- ?
- @SPI("dubbo")
- public interface Protocol{
- /**
- * 获取缺省端口, 当用户没有配置端口时使用.
- *
- * @return 缺省端口
- */
- int getDefaultPort();
- ?
- /**
- * 暴露远程服务:<br>
- * 1. 协议在接收请求时, 应记录请求来源方地址信息: RpcContext.getContext().setRemoteAddress();<br>
- * 2. export() 必须是幂等的, 也就是暴露同一个 URL 的 Invoker 两次, 和暴露一次没有区别.<br>
- * 3. export() 传入的 Invoker 由框架实现并传入, 协议不需要关心.<br>
- *
- * @param <T> 服务的类型
- * @param invoker 服务的执行体
- * @return exporter 暴露服务的引用, 用于取消暴露
- * @throws RpcException 当暴露服务出错时抛出, 比如端口已占用
- */
- @Adaptive
- <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
- ?
- /**
- * 引用远程服务:<br>
- * 1. 当用户调用 refer() 所返回的 Invoker 对象的 invoke() 方法时, 协议需相应执行同 URL 远端 export() 传入的 Invoker 对象的 invoke() 方法.<br>
- * 2. refer() 返回的 Invoker 由协议实现, 协议通常需要在此 Invoker 中发送远程请求.<br>
- * 3. 当 url 中有设置 check=false 时, 连接失败不能抛出异常, 并内部自动恢复.<br>
- *
- * @param <T> 服务的类型
- * @param type 服务的类型
- * @param url 远程服务的 URL 地址
- * @return invoker 服务的本地代理
- * @throws RpcException 当连接服务提供方失败时抛出
- */
- @Adaptive
- <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
- ?
- /**
- * 释放协议:<br>
- * 1. 取消该协议所有已经暴露和引用的服务.<br>
- * 2. 释放协议所占用的所有资源, 比如连接和端口.<br>
- * 3. 协议在释放后, 依然能暴露和引用新的服务.<br>
- */
- void destroy();
- ?
- }
在 dubbo 自己的 jar 里, 在 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol 文件中
- dubbo=com.alibaba.dubbo.rpc.DubboProtocol
- http=com.alibaba.dubbo.rpc.HttpProtocol
- hessian=com.alibaba.dubbo.rpc.hessianProtocol
所有说这里就是 dubbo 默认的配置, 其实就是一个 Protocol 接口,@SPI("dubbo") 说的就是, 通过 SPI 机制来提供实现类, 实现类是通过 dubbo 作为默认 key 去配置文件里找的, 配置文件的名称和接口名的全路径名时一样的, 通过 dubbo 作为 key 可以找到默认的实现类就是 com.alibaba.dubbo.rpc.DubboProtocol
dubbo 默认的网络通信协议, 就是 dubbo 协议, 用的 DubboProtocol
如果想要动态替换默认的实现类, 需要使用 @Adapter 接口, Protocol 接口中, 有两个方法加了 @Adapter 注解, 就是说那俩接口会被代理实现.
比如: 这个 Protocol 接口搞了俩 @Adapter 注解标注了方法, 在运行的时候, 就会针对 Protocol 生成代理类, 这个代理类的俩方法里面会代理代码, 代理代码会在运行时候动态根据 url 中的 protocol 来获取那个 key, 默认是 dubbo, 你也可以自己指定别的 key, 那么就会获取别的实现类的实例了.
通过这个 URL 的参数不同, 就可以控制动态的使用不同的组件实现类
问题 6: 如何基于 dubbo 做服务治理, 服务降级, 失败重试以及超时重试?
1. 服务治理
1. 调用链路生成
一个大型的分布式系统, 由大量的服务组成. 这些服务之间是如何调用的呢? 调用链路是啥?
那就需要基于 dubbo 做的分布式系统中, 对各个服务之间的调用自动记录下来, 然后自动将各个服务的依赖关系和调用链路生成出来, 做成一张图, 大家可以看到的
2. 服务访问压力以及时长统计
需要自动统计各个接口之间的访问次数和调用延迟, 而且要分成两个级别. 一个级别是接口粒度, 就是每个服务额每个接口每天被调用的次数, TP50 ,TP90,TP99, 三个档次的请求延迟分别是多少; 第二个级别是从源头入口, 一个完整的请求链路经过几十个服务之后, 完成一次请求, 每天全链路走多少次, 全链路请求延迟的 TP50,TP90,TP99
分别是多少?
这些东西都搞定以后, 后面才看当前系统的压力到底在哪里? 如何来扩容和优化?
3. 其他的
服务分层 (避免循环依赖), 调用链路失败监控和报警, 服务鉴权, 每个服务 的可用性的监控
2. 服务降级
比如服务 A 调用服务 B, 结果服务 B 挂掉了, 服务 A 重试几次调用服务 B, 还是不行, 直接降级, 走一个备用逻辑, 给用户返回响应
?
问题 7: 分布式服务接口的幂等性如何设计?
所谓幂等性, 就是说一个接口, 多次发起同一个请求, 你这个接口得保证结果是准确的, 比如不能多扣款, 不能多插入一条数据, 不能将统计值多加 1. 这就是幂等性.
保证幂等性的手段如下:
对于每次请求必须有一个全局唯一的标识
每次处理完请求后, 必须有一个记录标识这个请求处理完了, 比如常见的方案是在 MySQL 中记录个啥状态, 比如支付之前记录一条这个订单的支付流水, 而且支付流水采用 orderid 作为 唯一键. 只有成功插入这个支付流水, 才可以执行实际的支付扣款.
每次接收到了请求需要进行判断之前是否处理过的逻辑处理, 比如说吗, 如果有一个订单已经支付了, 就已经有一个支付流水了, 那么如果重复发送请求, 则此时先插入支付流水, 然而 orderid 已经存在了, 唯一键约束生效, 报错插入失败, 然后你就不用扣款了
一般生产上可以用 Redis 中的 set 来保证幂等
问题 8: 如何设计一个类似于 Dubbo 的 rpc 框架
简单梳理一下思路:
上来就要把服务到注册中心注册, 就应该有一个注册中心, 保留各个服务的地址信息, 就可以用 zookeeper 来做.
然后消费者要去注册中心去拿服务信息, 每个服务可能会存在于有多台机器
接着你就该发起一次请求, 如何发呢? 基于动态代理, 面向接口获取到一个动态代理, 这个动态代理就是本地接口的一个代理, 然后这个代理会找到服务对应的机器地址
然后找哪台机器发送请求? 这里会有一个负载均衡算法, 比如可以使用随机轮训的算法
找到一台机器, 如何发送给他? 可以用 netty,nio 的方式, 第二个问题发送什么格式? 可以是序列化格式或者 JSON 格式等
服务起那边一样, 需要针对你的服务生成一个动态代理, 监听某个网络端口, 然后代理你本地的服务代码, 接收到请求, 就代理本地的服务方法.
来源: http://www.bubuko.com/infodetail-3433910.html