Dubbo和ZooKeeper是如何协同工作的?
文章目录
Dubbo
-
通过RPC的方式实现服务调用
-
通过简单实现理解原理:
Client和Server共用一个接口,将接口打成一个jar包,在客户端和服务端引入这个jar包,Client端面向接口写调用,Server端面向接口写实现,中间的网络通信交给Dubbo框架去实现。
以下是黄字叙述内容:“点对点RPC”的实现细节
1.服务提供者-配置
<!--1.当前项目在整个分布式架构里面的唯一名称,用于计算依赖关系 -->
<dubbo:application name="dubbo-provider"/>
<!--2.定义协议端口-->
<dubbo:protocol port="28800"/>
<!--3.定义注册中心的地址-->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
<!--4.服务发布的配置,需要暴露的服务接口-->
<bean id="userService" class="com.duan.service.impl.UserServiceImpl"/>
<dubbo:service interface="com.duan.service.IUserService" ref="userService"/>
加载Spring配置
public class Provider {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
context.start();
System.in.read(); // 按任意键退出
}
}
2.服务消费者-配置
<!--消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="dubbo-consumer"/>
<!--定义注册中心的地址-->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
<!--生成远程服务代理-->
<dubbo:reference interface="com.duan.service.IUserService" id="userService"/>
加载Spring配置,并调用远程服务
public class Consumer {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
context.start();
// 获取远程服务代理
DemoService userService = (DemoService)context.getBean("userService");
// 执行远程方法
String hello = userService.sayHello("world");
// Hello world
System.out.println( hello );
}
}
负载均衡
以上就是典型的点对点的服务调用,假如在未指定zookeeper注册中心时,Client和Server端的配置文件部分如下
<!--dubbo这个服务所要暴露的服务地址所对应的注册中心,N/A为不使用注册中心-->
<dubbo:registry address="N/A"/>
此时,若我们为了高可用,可以:
- 配置多个服务提供者;
- consumer.xml中配置多个服务调用者,在
<dubbo:reference>
标签的url属性中加入多个地址,中间用分号隔开即可配置负载均衡策略在consumer.xml的dubbo:reference标签中增加loadbalance属性即可,属性值可以为如下四种:
1. 随机 + 权重(random)
- 计算服务Provider的总权重,并维护一个前缀和数组;
- 基于第一步的结果,在0~总权重 之间生成一个随机数;
- 随机数得到一个权重,遍历前缀和数组,找到第一个比随机权重大的位置,即可确定选择的服务Provider;
- 如果所有服务提供者节点都没有分配权重,或者分配的权重都一样,那么就采用随机的选择一个服务提供者节点;
2.轮询 + 权重(roundrobin)
- 计算服务提供者的总权重。
- 每个服务提供者除了始终不变的固定权重以外,需要记录服务提供者当前权重。
- 每次请求,更新所有的服务提供者的当前权重,当前权重 = 当前权重 + 固定权重。
- 从上一步得到的结果中,选择一个当前权重最大的服务提供者用于处理请求,如果存在多个,那么就看遍历的过程中先遇到哪个服务提供者就是哪一个。并且,选中的权重最大的服务提供者更新其当前权重 = 当前权重 - 总权重
3.最少连接(leastactive)
- Client存在一个计数器,记录当前通过某个Server提供者的请求还未完成的总数;
- 选择计数最少的那个用于处理当前请求, 将计数器+1;
- 如果存在多个相同的计数,那么使用随机+权重的方式选取;
- 否则从多个最少的服务提供者当中随机的选择一个处理当前请求;
4.一致性Hash(ConsistentHash)
一致性Hash,相同参数的请求总是发到同一提供者;
consumer.xml中生成接口的远程代理如下:
<dubbo:reference id="demoService" interface="com.st.DemoService"
url="dubbo://192.168.11.1:20880/com.st.DemoService;
dubbo://192.168.11.2:20880/com.st.DemoService;
dubbo://192.168.11.3:20880/com.st.DemoService"
loadbalance="roundrobin"/>
现在整体架构如下图(假设consumer为订单服务,provider为用户服务)
此时会有什么问题?
- 当服务器增加服务节点时,需要修改配置文件;
- 当其中一个服务Provider宕机时,consumer不能及时感知到,还会往宕机的服务发送请求;
这是后就需要引入zookeeper注册中心了!
ZooKeeper注册中心
zookeeper注册中心实现注册的大致流程如下图:
对照上图来看Dubbo官网上的Dubbo框架图,是不是觉得和上图很类似?zookeeper是被选来作为Dubbo框架中的注册中心。
节点
角色说明
Provider
暴露服务的服务提供方
Consumer
调用远程服务的服务消费方
Register
服务注册与发现的注册中心
Monitor
统计服务的调用次数和调用时间的监控中心
Container
服务运行容器
调用关系说明
- Container负责启动(上面的容器为Spring容器),加载,运行服务提供者;
- 服务提供者在启动时,向注册中心注册自己提供的服务;
- 服务消费者在启动时,向注册中心订阅自己所需的服务;
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者;
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选择一台提供者进行调用,如果调用失败,再选另一个台调用;
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
回归之前的问题,如要使用注册中心,只需要将provider.xml和consumer.xml恢复为如下:
<!--<dubbo:registry address="N/A"/>-->
<dubbo:registry protocol="zookeeper" address="192.168.11.129:2181"/>
如果zookeeper是一个集群,则多个地址之间逗号分隔即可:
<dubbo:registry protocol="zookeeper" address="192.168.11.129:2181,192.168.11.137:2181,192.168.11.138:2181"/>
将consumer.xml中配置的直连的方式去掉
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<!--<dubbo:reference id="demoService" interface="com.st.DemoService"-->
<!--url="dubbo://localhost:20880/com.st.DemoService"/>-->
启动上述服务后,我们观察在zookeeper的根节点多了一个dubbo节点,如下图(树状存储)
最后一个节点中192.168.1.104是我的内网地址,绿色代表临时节点,而其他节点是持久节点,这样,当服务宕机时,这个节点就会自动消失,不在提供服务,服务消费者也不会载请求。若部署多个Service,则Provider下面会有好几个节点,一个节点保存一个Service的服务地址。