一、什么是Doubbo
1.1、doubbo的引入
如何实现远程调用(系统之间的相互调用)
- WebService:基于soa协议,效率不高;
- doubbo:使用rpc协议远程调用,直接使用socket通信,传输效率高,并且可以统计出系统之间的调用关系,调用次数;
doubbo缺点:
由于doubbo是java开发的,所以只能是两个java系统之间互相调用时才能使用doubbo作为服务治理的中间件,即doubbo不能跨语言;
1.2、什么是doubbo
doubo是阿里的开源项目;分布式架构下,系统之间的调用非常混乱,doubbo就是用来治理服务的一个工具;即doubo就是资源调度和治理中心的管理工具;
1.3、doubbo架构
我们解释一下这个架构图:
- Provider:暴露服务的服务提供者;
- Consumer:调用远程服务的服务消费者;
- Container:服务运行容器,一般使用Spring作为容器;
- Registry:服务注册与发现的注册中心,官方推荐使用zookeeper,当然也可以是redis;
- Monitor:统计服务的调用次数和调用时间的监控中心,Monitor是可以没有的;
调用关系说明:
- 服务容器负责启动加载,运行服务提供者;
- 服务提供者在启动时,想注册中心注册自己的服务;
- 注册中心返回服务提供者提供的地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者;
- 服务消费者,从提供者地址列表里,基于软负载均衡算法,选一台提供者进行调用。如果调用失败,再选另一台进行调用;
- 服务消费者与提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心;
二、doubbo的使用
Doubbo采用全Spring配置的方式,透明化接入应用,对应用没有任何的API侵入,只需要用spring加载doubbo的配置即可,Doubbo基于Spring的Schema扩展进行加载;
单一工程的spring配置:
远程服务:
在本地服务的基础上,只需要做简单的配置,即可完成远程化;
将上面的xml文件配置财拆分,将服务定义部分放在提供方,将服务引用部分放在服务消费放,并在服务提供方配置增加暴露服务的配置dubbo:service,在服务消费放增加引用服务的配置doubbo:reference;
三、注册中心
3.1、Zookeeper介绍
注册中心负责服务地址的注册于查找,相当于目录服务,服务提供者与消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小;
zookeeper是Apache Hadoop下的一个子项目,是一个树形的目录服务,支持变更推送,适合做Doubbo的注册中心;
Zookeeper主要是两个功能:管理(存储、读取)数据和提供监听(监听哪个服务器挂了);zookeeper内部本身就是一个集群(配置文件里没有主(leader)从(follower),但是自己内部机制会选出主与从),且内部的机器是奇数台,只要有半数以上的机器存活,zookeeper就能提供服务;zookeeper配置文件只需要配置一个id和服务器即可;
3.2、linux下的Zookeeper集群安装
3.2.1、在linux下安装jdk
安装好jdk后,记得关闭防火墙;如果对linux不熟悉的同学,则具体操作参考:linux安装jdk以及关闭防火墙
这里配置了三台linux来搭建zookeeper集群,每台机器里的/etc/hosts文件都要配置其他机器地址与主机名对应,这样做是为了让集群里的每个zookeeper找到其他zookeeper
# 集群里的每台机器,/etc/hosts文件都要添加这个配置
192.168.75.129 maltose01
192.168.75.130 maltose02
192.168.75.131 maltose03
3.2.2、安装zookeeper
在Apache官网下载zookeeper
zookeeper官网
打开官网后点击下边的链接
选择要下载的版本
点击下载即可
linux下安装Zookeeper
上传zookeeper压缩包到linux下(我这里上传到了/usr/local目录下),并解压;解压后进入conf目录里
,将zoo_sample.cfg名字改为zoo.cfg
cp zoo_sample.cfg zoo.cfg
然后对这个文件进行编辑
vim zoo.cfg
zoo.cfg配置内容如下:
# 心跳周期默认2000毫秒;
tickTime=2000
# 数据目录,需要修改为自己的目录;这里改为/root/zkdada;
dataDir=/tmp/zookeeper
# 客户端访问zookeeper的端口;
clientPort=2181
# 添加如下内容,配置集群
# server.1=192.168.75.131:2888:3888(ip地址也可以写成主机名,2888是leader与followe之间通信的端口,3888是投票时通信程序的端口);配置三个如下:
server.1=maltose01:2888:3888
server.2=maltose02:2888:3888
server.3=maltose03:2888:3888
注意,上边的安装过程,在三台机器里的操作都是一样的;为了方便,使用如下命令可以将第一台机器上的配置直接传到其他两台机器上去
# 将当前机器的zookeeper文件夹传到其他机器的/usr/local目录下
scp -r zookeeper/ [email protected]:/usr/local;
scp -r zookeeper/ [email protected]:/usr/local;
启动zookeeper:
bin/zkServer.sh start
查看哪台机器时leader,哪台是folwer:
bin/zkServer.sh status
使用zookeeper自己的客户端连接zookeeper服务端:
# 进入zookeeper安装目录的bin目录执行命令
./zkCli.sh
可以看到该客户端连接的是哪个zookeeper服务器(此时执行help命令可以看到创建节点,修改节点,获取节点值等等的语法);
./zkCli.sh命令连接的都是本机器上的zookeeper,如果想要连上其他服务器上的zookeeper,可以执行如下命令:
connect maltose02:2181;
3.3、Zookeeper的详细介绍
3.3.1、zookeeper节点
- 层次化的目录结构,命名符合常规文件系统规范(见下图)
- 每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识(ls / 查看根节点)
- 节点Znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点,后边会详细讲解)
- 客户端应用可以在节点上设置监视器(后续详细讲解)
3.3.2、znode节点类型
Znode有两种类型
- 短暂型(ephemeral)(客户端断开连接后,该节点自己删除)
建立短暂节点:
# 不加-e的话创建的就是持久节点
create -e /app-empheral 888
# 查看该节点信息
get /app-empheral
使用quit命令退出该客户端后,再连上,发现找不到该节点了,这就是短暂型的意思;
在新建的节点下再新建节点且赋值123:
create /app-empheral/test01 123;
通过get来获取数据:
get / app-empheral /test01
- 持久型(persistent)(客户端断开连接后,该节点不自己删除,需要人为删除)
3.3.3、Znode四种形式的目录节点
znode默认是persistent,上边-e前加-s的话就是带序号的节点
- PERSISTENT 持久的
- PERSISTENT_SEQUENTIAL(持久序列/test0000000019 )持久带序号
- EPHEMERAL 暂时的
- EPHEMERAL_SEQUENTIAL 暂时带序号的
eg:
create /test 888;
# 该文件会带上序号;(-s前或后可以加-e)
create -s /test/aa 111
同一个目录里创建文件的话,文件的序号会依次增加,此时在其他目录里创建文件的话,序号又从0开始;
# 修改节点值:
set /test/aa 222;
# 当其他服务器的节点值发生改变时,使用如下命令会有提示
get /test/aa watch
删除节点:
# 递归全部删除
rmr /zk
# 只删除一个节点,如果有子节点是删除不了的
delete /zk
注意事项:
- 创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护
- 在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序;
- 临时节点下创建永久节点会报错;临时节点不会有子节点;
3.3.4、zookeeper的java客户端API
使用java实现增删改查:
public class SimpleDemo {
// 会话超时时间,设置为与系统默认时间一致
private static final int SESSION_TIMEOUT = 2000;
//连不上服务器端其中一个的话,会继续连接及群里的下一个
private static final String connectString="maltose01:2181,maltose02:2181,maltose03:2181";
// 创建 ZooKeeper 实例
ZooKeeper zk;
// 创建 Watcher 实例(监听器)
Watcher wh = new Watcher() {
public void process(org.apache.zookeeper.WatchedEvent event)
{
//收到事件通知后的回调函数(自己的事件处理逻辑)
System.out.println(event.toString());
}
};
// 初始化 ZooKeeper 实例
private void createZKInstance() throws IOException
{
//参数:1获得连接 2会话超时 3监听器
zk = new ZooKeeper(connectString , SimpleDemo.SESSION_TIMEOUT, this.wh);
}
private void ZKOperations() throws IOException, InterruptedException, KeeperException
{
System.out.println("/n1. 创建 ZooKeeper 节点 (znode : zoo2, 数据: myData2 ,权限: OPEN_ACL_UNSAFE ,节点类型(前边学的四种类型): Persistent");
zk.create("/zoo2", "myData2".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("/n2. 查看是否创建成功: ");
System.out.println(new String(zk.getData("/zoo2", false, null)));
System.out.println("/n3. 修改节点数据 ");
zk.setData("/zoo2", "shenlan211314".getBytes(), -1);
System.out.println("/n4. 查看是否修改成功: ");
System.out.println(new String(zk.getData("/zoo2", false, null)));
System.out.println("/n5. 删除节点 ");
zk.delete("/zoo2", -1);
System.out.println("/n6. 查看节点是否被删除: ");
System.out.println(" 节点状态: [" + zk.exists("/zoo2", false) + "]");
}
//下边的方法,当子节点改变时,会执行
Public void getChildren() throws Exception{
List<String> children=zk.getChildren(“/”,true);
For(String child:children){
System.out.print(child);
}
Thread.sleep(Long.Max_VALUE);
}
private void ZKClose() throws InterruptedException
{
zk.close();
}
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
SimpleDemo dm = new SimpleDemo();
dm.createZKInstance();
dm.ZKOperations();
dm.ZKClose();
}
}
3.3.5、实现分布式应用及客户端动态更新主节点状态
- 某分布式系统中,主节点可以有多台,可以动态上下线
- 任意一台客户端都能实时感知到主节点服务器的上下线
上边案例创建的是临时节点,因为服务器挂了的话,会发生一个事件去通知客户端;
此时,客户端的实现:
public class DistributedClient {
private static final String connectString = "mini1:2181,mini2:2181,mini3:2181";
private static final int sessionTimeout = 2000;
private static final String parentNode = "/servers";
// 注意:加volatile的意义何在?暂时理解为多线程里static修饰的共享数据
private volatile List<String> serverList;
private ZooKeeper zk = null;
/**
* 创建到zk的客户端连接
*
* @throws Exception
*/
public void getConnect() throws Exception {
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 收到事件通知后的回调函数(应该是我们自己的事件处理逻辑)
try {
//服务器数据更改后,重新更新服务器列表,并且注册了监听
getServerList();
} catch (Exception e) {
}
}
});
}
/**
* 获取服务器信息列表
*
* @throws Exception
*/
public void getServerList() throws Exception {
// 获取服务器子节点信息,并且对父节点进行监听(true)
List<String> children = zk.getChildren(parentNode, true);
// 先创建一个局部的list来存服务器信息
List<String> servers = new ArrayList<String>();
for (String child : children) {
// child只是子节点的节点名,参数二表示不监听子节点(子节点改变时不通知客户端),
byte[] data = zk.getData(parentNode + "/" + child, false, null);
servers.add(new String(data));
}
// 把servers赋值给成员变量serverList,已提供给各业务线程使用
serverList = servers;
//打印服务器列表
System.out.println(serverList);
}
/**
* 业务功能
*
* @throws InterruptedException
*/
public void handleBussiness() throws InterruptedException {
System.out.println("client start working.....");
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
// 获取zk连接
DistributedClient client = new DistributedClient();
client.getConnect();
// 获取servers的子节点信息(并监听),从中获取服务器信息列表
client.getServerList();
// 业务线程启动
client.handleBussiness();
}
}
服务端的实现:
package cn.maltose.zkdist;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
public class DistributedServer {
private static final String connectString = "mini1:2181,mini2:2181,mini3:2181";
private static final int sessionTimeout = 2000;
private static final String parentNode = "/servers";
private ZooKeeper zk = null;
/**
* 创建到zk的客户端连接
*
* @throws Exception
*/
public void getConnect() throws Exception {
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 收到事件通知后的回调函数(应该是我们自己的事件处理逻辑)
System.out.println(event.getType() + "---" + event.getPath());
try {
zk.getChildren("/", true);
} catch (Exception e) {
}
}
});
}
/**
* 向zk集群注册服务器信息
*
* @param hostname
* @throws Exception
*/
public void registerServer(String hostname) throws Exception {
String create = zk.create(parentNode + "/server", hostname.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname + "is online.." + create);
}
/**
* 业务功能
*
* @throws InterruptedException
*/
public void handleBussiness(String hostname) throws InterruptedException {
System.out.println(hostname + "start working.....");
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
// 获取zk连接
DistributedServer server = new DistributedServer();
server.getConnect();
// 利用zk连接注册服务器信息
server.registerServer(args[0]);
// 启动业务功能
server.handleBussiness(args[0]);
}
}