Dubbo分布式系统框架知识点流程化总结
1、为什么要进行系统拆分?如何进行系统拆分?拆分后不用dubbo可以吗?
- 拆分系统是为了提高复杂系统团队的开发效率
- 核心意思就是根据情况,先拆分一轮,后面如果系统更复杂了,可以继续分拆
- 可以不用,那就基于spring mvc,利用http接口进行通讯,但是在负载均衡上、超时重试上等等做不来。
- dubbo 说白了,是一种 rpc 框架,就是说本地就是进行接口调用,但是 dubbo 会代理这个调用请求,跟远程机器网络通信,给你处理掉负载均衡、服务实例上下线自动感知、超时重试等等乱七八糟的问题
2、 说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程?
-
1、dubbo的工作原理:
- 第一层:service 层,接口层,给服务提供者和消费者来实现的
- 第二层:config 层,配置层,主要是对 dubbo 进行各种配置的
- 第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信
- 第四层:registry 层,服务注册层,负责服务的注册与发现
- 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
- 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控
- 第七层:protocal 层,远程调用层,封装 rpc 调用
- 第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步
- 第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口
- 第十层:serialize 层,数据序列化层
-
2、工作流程:
-
第一步:provider 向注册中心去注册
-
第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务
-
第三步:consumer 调用 provider
-
第四步:consumer 和 provider 都异步通知监控中心
-
3、dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?
-
序列化: 就是把数据结构或者是一些对象,转换为二进制串的过程,而反序列化是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
-
1、dubbo支持不同的协议:
- dubbo 协议
-
默认就是走 dubbo 协议,单一长连接,进行的是 NIO 异步通信,基于 hessian 作为序列化协议。使用的场景是:传输数据量小(每次请求在 100kb 以内),但是并发量很高。
- 为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就 100 个连接。然后后面直接基于长连接 NIO 异步通信,可以支撑高并发请求。
-
长连接,通俗点说,就是建立连接过后可以持续发送请求,无须再建立连接。
- 短连接,每次要发送请求之前,需要先重新建立一次连接
4、 dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?
- 1、dubbo的负载均衡
- random loadbalance
- 默认情况下,dubbo 是 random load balance ,即随机调用实现负载均衡,可以对 provider 不同实例设置不同的权重,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了 。
- round robin loadbalance
- 这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些 。
- leastactive loadbalance
- 这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给不活跃的性能差的机器更少的请求
- consistant hash loadbalance
- 一致性 Hash 算法,相同参数的请求一定分发到一个 provider 上去,provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。如果你需要的不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性 Hash 策略 。
- random loadbalance
- 2、dubbo集群容错策略
- failover cluster 模式
- 失败自动切换,自动重试其他机器,默认就是这个,常见于读操作。
- failfast cluster
- 一次调用失败就立即失败,常见于非幂等性的写操作,比如新增一条记录
- failsafe cluster 模式
- 出现异常时忽略掉,常用于不重要的接口调用,比如记录日志。
- failback cluster
- 失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种
- forking cluster 模式
- 并行调用多个 provider,只要一个成功就立即返回。常用于实时性要求比较高的读操作,但是会浪费更多的服务资源,可通过
forks="2"
来设置最大并行数
- 并行调用多个 provider,只要一个成功就立即返回。常用于实时性要求比较高的读操作,但是会浪费更多的服务资源,可通过
- broadcast cluster
- 逐个调用所有的 provider。任何一个 provider 出错则报错(从
2.1.0
版本开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息
- 逐个调用所有的 provider。任何一个 provider 出错则报错(从
- failover cluster 模式
5、 dubbo 的 spi 思想是什么?
-
1、spi是什么:用于插件拓展
- 需要根据指定的配置或者是默认的配置,去找到对应的实现类加载进来,然后用这个实现类的实例对象。
-
2、dubbo的spi思想
-
1、 Protocol 接口,在系统运行的时候,,dubbo 会判断一下应该选用这个 Protocol 接口的哪个实现类来实例化对象来使用
- 它会去找你配置的 Protocol,将你配置的 Protocol 实现类,加载到 jvm 中来,然后实例化对象,就用你的那个 Protocol 实现类就可以了。
- 下面那行代码就是 dubbo 里大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系统运行的时候动态根据配置去找到对应的实现类。如果你没配置,那就走默认的实现。
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
-
2、在 dubbo 自己的 jar 里,在
/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
文件中:dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
Protocol 接口,`@SPI("dubbo")` 说的是,通过 SPI 机制来提供实现类,实现类是通过 **dubbo 作为默认 key** 去配置文件里找到的,配置文件名称与接口全限定名一样的,通过 dubbo 作为 key 可以找到默认的实现类就是 `com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol`。
-
5、如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试?
-
1、调用链路自动生成
- 各个服务之间的依赖关系和调用链路生成出来
-
2、服务访问压力和时长统计
- 各个接口和服务之间的调用次数以及访问延时
-
3、服务降级
- 比如说服务 A 调用服务 B,结果服务 B 挂掉了,服务 A 重试几次调用服务 B,还是不行,那么直接降级,走一个备用的逻辑,给用户返回响应 。
public interface HelloService { void sayHello(); } public class HelloServiceImpl implements HelloService { public void sayHello() { System.out.println("hello world......"); } }
<dubbo:service interface="com.zhss.service.HelloService" ref="helloServiceImpl" timeout="10000" /> <bean id="helloServiceImpl" class="com.zhss.service.HelloServiceImpl" />
<dubbo:reference id="fooService" interface="com.test.service.FooService" timeout="10000" check="false" mock="return null"> </dubbo:reference>
-
**降级一:**调用接口失败的时候,可以通过
mock
统一返回 null。 -
降级二:mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+
Mock
” 后缀。然后在 Mock 类里实现自己的降级逻辑。public class HelloServiceMock implements HelloService { public void sayHello() { // 降级逻辑 } }
-
失败重试和超时重试
-
timeout
:一般设置为200ms
,我们认为不能超过200ms
还没返回。 -
retries
:设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。<dubbo:reference id="xxxx" interface="xx" check="true" async="false" retries="3" timeout="200"/>
-
6、分布式服务接口的幂等性如何设计(比如不能重复扣款)?
-
1、对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。
-
2、每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。
-
3、每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。
-
实际应用:
- 实际运作过程中,你要结合自己的业务来,比如说利用 redis,用 orderId 作为唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣款。
- 要求是支付一个订单,必须插入一条支付流水,order_id 建一个唯一键
unique key
。你在支付一个订单之前,先插入一条支付流水,order_id 就已经进去了。你就可以写一个标识到 redis 里面去,set order_id payed
,下一次重复请求过来了,先查 redis 的 order_id 对应的 value,如果是payed
就说明已经支付过了,你就别重复支付了。
7、 分布式服务接口请求的顺序性如何保证?
- dubbo 的一致性 hash 负载均衡策略,将比如某一个订单 id 对应的请求都给分发到某个机器上去,接着就是在那个机器上,因为可能还是多线程并发执行的,你可能得立即将某个订单 id 对应的请求扔一个内存队列里去,强制排队,这样来确保他们的顺序性
8、zookeeper都有哪些应用场景
-
1、分布式协调
- A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上对某个节点的值注册个监听器,一旦 B 系统处理完了就修改 zookeeper 那个节点的值,A 系统立马就可以收到通知 。
- A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上对某个节点的值注册个监听器,一旦 B 系统处理完了就修改 zookeeper 那个节点的值,A 系统立马就可以收到通知 。
-
2、分布式锁
- 对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也尝试去创建那个 znode,只能等第一个机器执行完了自己再执行。
- 对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也尝试去创建那个 znode,只能等第一个机器执行完了自己再执行。
-
3、元数据/配置信息管理
- zookeeper 可以用作很多系统的配置信息的管理
- zookeeper 可以用作很多系统的配置信息的管理
-
4、HA高可用性
- 重要进程一般会做主备两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程
- 重要进程一般会做主备两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程
9、 集群部署时的分布式 session 如何实现?
-
1、tomcat+redis
-
使用 session 的代码,跟以前一样,还是基于 tomcat 原生的 session 支持即可,然后就是用一个叫做
Tomcat RedisSessionManager
的东西,让所有我们部署的 tomcat 都将 session 数据存储到 redis 即可 。
-
缺点:严重依赖web容器,与tomcat严重耦合
-
-
2、spring session +redis
-
sping session 配置基于 redis 来存储 session 数据,然后配置了一个 spring session 的过滤器,这样的话,session 相关操作都会交给 spring session 来管了。接着在代码中,就用原生的 session 操作,就是直接基于 spring sesion 从 redis 中获取数据了 。
-
pom文件
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>1.2.1.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.1</version> </dependency>
-
spring配置文件
<bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"> <property name="maxInactiveIntervalInSeconds" value="600"/> </bean> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="100" /> <property name="maxIdle" value="10" /> </bean> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy"> <property name="hostName" value="${redis_hostname}"/> <property name="port" value="${redis_port}"/> <property name="password" value="${redis_pwd}" /> <property name="timeout" value="3000"/> <property name="usePool" value="true"/> <property name="poolConfig" ref="jedisPoolConfig"/> </bean>
-
web.xml
<filter> <filter-name>springSessionRepositoryFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
例子
@RestController @RequestMapping("/test") public class TestController { @RequestMapping("/putIntoSession") public String putIntoSession(HttpServletRequest request, String username) { request.getSession().setAttribute("name", "leo"); return "ok"; } @RequestMapping("/getFromSession") public String getFromSession(HttpServletRequest request, Model model){ String name = request.getSession().getAttribute("name"); return name; } }
-
10、 分布式事务了解吗?你们是如何解决分布式事务问题的?
-
1、XA方案:两阶段提交
- 事务管理器,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务
- 缺点:系统内部出现跨库操作,但是 每个服务只能操作自己对应的一个数据库
-
2、TCC方案 :
Try
、Confirm
、Cancel
- Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留。
- Confirm 阶段:这个阶段说的是在各个服务中执行实际的操作。
- Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
- 应用场景及缺点:
- 事务回滚实际上是严重依赖于你自己写代码来回滚和补偿 ,维护异常困难
- 在涉及交易支付的场景才会使用TCC保证资金的完成性。
-
3、本地消息表
-
A 系统在自己本地一个事务里操作同时,插入一条数据到消息表;
-
接着 A 系统将这个消息发送到 MQ 中去;
-
B 系统接收到消息之后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样保证不会重复处理消息;
-
B 系统执行成功之后,就会更新自己本地消息表的状态以及 A 系统消息表的状态;
-
如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理;
-
这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止。
-
缺点: 严重依赖于数据库的消息表来管理事务 ,不方便应用在高并发和扩展场景。
-
-
4、可靠消息最终一致性方案:基于RocketMQ
-
A 系统先发送一个 prepared 消息到 mq,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;
-
如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 mq 发送确认消息,如果失败就告诉 mq 回滚消息;
-
如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;
-
mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。
-
系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。
-
-
5、最大努力通知方案
- 系统 A 本地事务执行完之后,发送个消息到 MQ;
- 这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口;
- 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。
-
6、实际应用
- 一个严格资金要求绝对不能错的场景,你可以说你是用的 TCC 方案;
- 如果是一般的分布式事务场景,订单插入之后要调用库存服务更新库存,库存数据没有资金那么的敏感,可以用可靠消息最终一致性方案 。
-
5、最大努力通知方案
- 系统 A 本地事务执行完之后,发送个消息到 MQ;
- 这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口;
- 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。
-
6、实际应用
- 一个严格资金要求绝对不能错的场景,你可以说你是用的 TCC 方案;
- 如果是一般的分布式事务场景,订单插入之后要调用库存服务更新库存,库存数据没有资金那么的敏感,可以用可靠消息最终一致性方案 。