dubbo + zookeeper 搭建分布式服务入门
dubbo是阿里开源的高性能RPC框架,框架图如下:
可以分为4个部分,注册中心,消费者,提供者和监控中心,这也是一般分布式服务的常见架构。
本文作为dubbo入门例子,采用zookeeper作为注册中心,可分为两个部分,如下:
- 搭建zookeeper注册中心
- 利用dubbo搭建分布式服务
搭建zookeeper注册中心
我们学习dubbo,可以在本机上搭建一个zookeeper注册中心,zookeeper在主流操作系统上都可以运行(windows,linux等)。需要注意的是zookeeper是依赖jdk的,在安装zookeeper前先安装java。无论在windows还是linux。
在windows上安装zookeeper
1、直接在官网下载zookeeper压缩包zookeeper-x.x.xx.tar.gz(x.x.xx是版本号),解压到本地文件夹。
2、修改conf文件夹下zoo_sample.cfg文件名为zoo.cfg,并将文件内容替换为下面:
tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
这个也官网介绍的方法,以standalone方式安装zookeeper,官网地址:http://zookeeper.apache.org/doc/current/zookeeperStarted.html。
3、启动bin文件下启动zkServer.cmd,可以用命令行启动,也可以直接启动。
在ubuntu中安装zookeeper
在生产环境,很少会把zookeeper安装的windows下,所以为了学习更多linux相关知识,我在本机上装了一个虚拟机。
1、安装virtualBox虚拟机软件。
2、新建虚拟机,并安装ubuntu系统。
3、用xsheel远程连接ubuntu,并把zookeeper压缩包上传至服务器,建议用root用户登录,一般用户远程连接,传输文件时经常没有权限被拒绝,ubuntu系统默认不支持root用户远程连接,需要修改配置,配置教程:https://www.cnblogs.com/TechSnail/p/7695090.html
4、解压zookeeper文件。
5、在/etc/profile文件中设置PATH。
修改profile文件:指令 sudo vim /etc/profile,
export ZOOKEEPER_HOME=/xxx/xx/zookeeper-3.4.3
PATH=$ZOOKEEPER_HOME/bin:$PATH
export PATH
注意,ZOOKEEPER_HOME的值是解压zookeeper的路径。
6、启动zookeeper。
指令:zkServer.sh start
查看状态:zkServer.sh status.
注意:若在虚拟机中安装zookeeper,需要修改虚拟机网络的配置(配置NAT转换和host-only网卡),添加端口映射才能连接到虚拟机的zookeeper。
到此,zookeeper服务注册中心已经搭建完毕,下面讲讲利用dubbo搭建分布式服务。
利用dubbo搭建分布式服务
本利为加深对分布式服务的理解,以一个学生信息(Student)的增删查为例进行讲解。
服务端(提供者)实现对学生信息的插入,查询和删除三项操作。
客户端(消费者)可以远程调用服务端接口,实现信息的操作。
项目地址:https://github.com/xubaodian/dubbo-study ,可下载参考
项目结构如下:
简单介绍下工程结构,这是一个maven工程,包括三个模块(module),分别为consumer(消费者、客户端),demoapi(都依赖的一些接口和实体类)和provider(服务提供者、服务端)。
最外层pom依赖进行依赖管理,把本项目所有的依赖都添加进来.
注意:各项依赖之间存在隐性依赖,可能会产生冲突,利用 标签消除依赖冲突,具体可百度。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xbd</groupId>
<artifactId>dubbo-study</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>provider</module>
<module>demoapi</module>
<module>consumer</module>
</modules>
<properties>
<junit.version>4.11</junit.version>
<spring.version>4.3.16.RELEASE</spring.version>
<slf4j.version>1.6.4</slf4j.version>
<dubbo.version>2.6.2</dubbo.version>
<zk.version>3.4.10</zk.version>
<log4j.version>1.2.17</log4j.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>${zk.version}</version>
</dependency>
<!--日志依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- spring相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.4.2</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
</project>
按顺序介绍三个模块
demoapi模块
该模块包括两个实体类和一个接口,实体类分别是Student.java学生信息类,RetMessage操作信息类,提示消费者操作是否成功及操作失败原因。
一个接口DemoApi.java,提供学生信息增删改接口。
//学生信息实体类
package com.xbd.demoapi.entity;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = 0L;
private String stuId;
private String name;
private int age;
private int grade;
public String getStuId() {
return stuId;
}
public void setStuId(String stuId) {
this.stuId = stuId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
}
//RetMessage操作信息类
package com.xbd.demoapi.entity;
import java.io.Serializable;
public class RetMessage implements Serializable {
private static final long serialVersionUID = 1L;
private boolean success;
private String message;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
//DemoApi接口
package com.xbd.demoapi.service;
import com.xbd.demoapi.entity.RetMessage;
import com.xbd.demoapi.entity.Student;
public interface DemoApi {
public Student getInfoById(String Id);
public RetMessage insertInfo(Student stu);
public RetMessage deleteById(String Id);
}
provider模块
该模块依赖我就不多说了,主要介绍下spring配置文件,dubbo-provider.xml,该文件内容如下,各个语句都有注释:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- provider's application name, used for tracing dependency relationship -->
<!--服务提供者名称-->
<dubbo:application name="provider"/>
<!-- use multicast registry center to export service -->
<!-- <dubbo:registry protocol="zookeeper" address="localhost:2181"/>-->
<!--zookeeper注册中心地址-->
<dubbo:registry protocol="zookeeper" address="zookeeper://192.168.56.10:10000"/>
<!-- use dubbo protocol to export service on port 20880 -->
<!--服务名称和端口号-->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- service implementation, as same as regular local bean -->
<!--接口的执行类-->
<bean id="demoService" class="com.xbd.provider.DemoApiImpl"/>
<!-- declare the service interface to be exported -->
<!--声明接口-->
<dubbo:service interface="com.xbd.demoapi.service.DemoApi" ref="demoService"/>
</beans>
服务实现类为DemoApiImpl.java,如下,各个接口的意义意义在注释中有解释。
package com.xbd.provider;
import com.xbd.demoapi.entity.RetMessage;
import com.xbd.demoapi.entity.Student;
import com.xbd.demoapi.service.DemoApi;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class DemoApiImpl implements DemoApi {
/**
* 学生信息List,存放学生信息,本来打算在数据库中增删,为了简化例子,直接用一个List代替;
* 若是连接数据库进行增删改查,在此处调用dao层接口即可
*/
List<Student> stuList = new ArrayList<>();
/**
* 根据Id获取学生信息,若是连接数据库,调用dao层接口查询学生信息
* @param Id String
* @return Student
**/
@Override
public Student getInfoById(String Id) {
Student stu = this.getInfo(Id);
return stu;
}
/**
* 插入学生信息,并判断是否插入成功,返回操作信息
* @param stu
*/
@Override
public RetMessage insertInfo(Student stu) {
RetMessage ret = new RetMessage();
ret.setSuccess(false);
if(stu!=null && !isBlank(stu.getStuId()) && !isBlank(stu.getName())){
Student stuVo = this.getInfo(stu.getStuId());
if(stuVo != null) {
ret.setMessage("该用户已存在!");
} else{
stuList.add(stu);
ret.setSuccess(true);
ret.setMessage("成功");
}
} else {
ret.setMessage("数据不全,请检查!");
}
return ret;
}
/**
* 删除学生信息
* @param Id
* @return RetMessage
*/
@Override
public RetMessage deleteById(String Id) {
RetMessage ret = new RetMessage();
ret.setSuccess(false);
if(!isBlank(Id)) {
int len = stuList.size();
for(int i = 0; i < len; i++) {
if(Id.equals(stuList.get(i).getStuId())){
stuList.remove(i);
ret.setSuccess(true);
ret.setMessage("成功!");
break;
}
}
} else {
ret.setMessage("Id为空,请检查输入数据");
}
return ret;
}
private Student getInfo(String Id){
Iterator<Student> it = stuList.iterator();
Student stu = null;
while (it.hasNext()) {
stu = it.next();
if(stu.getStuId().equals(Id)){
break;
}
}
return stu;
}
private boolean isBlank(String value){
return value == null || "".equals(value) || "null".equals(value);
}
}
启动类provider.java如下,在代码中启动了spring支持。
package com.xbd.provider;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
public class Provider {
public static void main(String [] args) throws IOException {
System.setProperty("java.net.preferIPv4Stack", "true");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-provider.xml"});
context.start();
System.out.println("the server start");
System.in.read(); // press any key to exit
}
}
消费者模块(consumer)
消费者模块代码如下:
package com.xbd;
import com.xbd.demoapi.entity.RetMessage;
import com.xbd.demoapi.entity.Student;
import com.xbd.demoapi.service.DemoApi;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Consumer {
public static void main(String [] args){
System.setProperty("java.net.preferIPv4Stack", "true");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-consumer.xml"});
context.start();
DemoApi demoService = (DemoApi) context.getBean("demoService"); // get remote service proxy
Student student = new Student();
student.setStuId("111");
student.setName("xxx");
RetMessage retMessage1 = demoService.insertInfo(student);
System.out.println(retMessage1.getMessage());
RetMessage retMessage2 = demoService.insertInfo(student);
System.out.println(retMessage2.getMessage());
}
}
同样的信息插入了两次,打印信息如下:
成功
该用户已存在!
对照DemoServiceImpl中的实现,我们想要实现的功能已经跑通了,赶紧动手试一试吧。