这几天工作没什么事,学习下Springboot dubbo zookeeper
1:zookeeper做服务注册和管理中心
2:dubbo服务调度
3:mybatis、springboot集成
总的来说就是通过这套框架实现了一个简单的注册demo(这里不强调注册逻辑中的细节)
环境:CentOS7、zookeeper:3.4.14(没有用zookeeper的最新版,因为集群部署的时候有个奇怪的坑)、docker。
现在只要是部署啊,安装啊什么的很多情况下都是docker安装了,这里废话不多,直接上zookeeper的部署流程
1:在usr/local下创建docker文件夹,再在下面创建zookeeper文件夹
/usr/local/docker/zookeeper
2:创建docker-compose.yml文件
vi docker-compose.yml
#以下是配置文件
services:
zoo1:
image: zookeeper:3.4.14
restart: always
hostname: zoo1
ports:
- 2182:2181
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
zoo2:
image: zookeeper:3.4.14
restart: always
hostname: zoo2
ports:
- 2183:2181
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
zoo3:
image: zookeeper:3.4.14
restart: always
hostname: zoo3
ports:
- 2184:2181
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
配置好了记住保存,千万记住wq,还有版本最好不要省去,可能会出现集群安装失败的问题!保存后启动,在当前文件夹下(与docker-compose.yml同级)
docker-compose up -d
启动后看下启动成功没有,启动成功了后随便进入一个容器去查看下集群模式启动成功没有
查看容器
docker ps
交互模式进入容器查看(这里应该有三个zookeeper的容器启动了,可以随便进入一个去查看,这里进入的是第三个)
docker exec -it zookeeper_zoo3_1 /bin/bash
#然后检查启动状态
./bin/zkServer.sh status
如果出现以下表示集群启动成功了,进入其他两个查看状态的话Mode应follower,表示我们zookeeper以及配置并且启动好了
root@zoo3:/zookeeper-3.4.14# ./bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Mode: leader
首先创建一个maven项目(官方例子是将服务接口,服务实现(provider),服务调用(consumer))三个单独分开来做的,没有一个统一的pom来管理,我多了一步是先创建了一个统一的包来管理这接口、提供、消费)
这里的demoPom就是一个最简单的一个maven项目包,在pom.xml中加入打包类型就可以了因为这个只用起到管理作用,所以src目录没有什么用了,直接删除即可
pom
创建完成后在包中新建module
依然是创建一个maven项目,这个模块只提供接口,版本号建议与项目的版本号相同,其他没有多余的设置,直接下一步下一步就完事儿了
创建完成后这个接口模块中的pom.xml需要添加
jar
注:需要去看看demoPom中的pom.xml是否自动添加了
如果没有自动添加的话,需要自己手动添加自己创建模块的artifactId
这是我本例子中的接口模块的目录结构
在api包中定义了一个interface:
package com.kj.demopom.user.api;
import com.kj.demopom.user.domain.User;
public interface RegistUser {
int registUser(User user); //用户注册
boolean flagUser(String name); //因为数据库name字段设置了主键,所以要判断数据库有没有相同的name值
}
domain中是一个user的实体类(必须要实现序列化接口):
package com.kj.demopom.user.domain;
import java.io.Serializable;
public class User implements Serializable {
private String name;
private String password;
private String old;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getOld() {
return old;
}
public void setOld(String old) {
this.old = old;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
", old='" + old + '\'' +
'}';
}
}
接口和实体类定义好了,首先我们先clean打包了,用maven自带的打包工具就可以啦啦,或者
运行下就可以了,提示build success表示成功了
或者直接控制台 mvn clean install,进行build,build成功后要刷新一下maven
还是先在再创建一个模块,这次不是创建maven项目了。创建一个Spring Initializr模块啦。
项目名这些搞完了过后选择依赖的时候可以不用选择,因为这个是提供服务的(要实现接口,方便其他服务调用),所以不是web项目,其他的依赖看你自己,也可以什么都不选择。
然后next后选一下目录然后就完成了,第一次构建可能有点慢,需要等等。
1:添加依赖
spring dubbo:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>com.alibaba.bootgroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>0.2.0version>
dependency>
mysql mybatis:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.0version>
dependency>
自己的接口(接口在本地没有上传服务器,如果报错肯定是clean时没有成功):
<dependency>
<groupId>com.kjgroupId>
<artifactId>demoPom-user-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
以上依赖就是本例子用到的依赖。
2:配置文件:
全部采用yml文件配置(有些地方打了***,基本上看的出来是什么,不是骂人哈,嘻嘻)
spring:
application:
name: demopom-user-provider
datasource:
url: jdbc:mysql://localhost:3306/demoPom?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username: root
password: ********
driver-class-name: com.mysql.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
user:
service:
version: 1.0.0
dubbo:
scan:
basePackages: com.kj.demopom.user.provider.api.impl
application:
id: demopom-user-provider
name: demopom-user-provider
registry:
id: zookeeper
address: zookeeper://1**.1**.**.1**:2182?backup=1**.1**.**.1**:2183,1**.1**.**.1**:2184
配置完成后,就可以开心的写代码了。(注册中心zookeeper的主机ip和端口,一定要看准啊)。
以下是provider这个服务的结构,简单解释一下:
1、comment里面放的是常量类,放一些常量
2、mapper,就不用多说了,mybatis的接口
3、service下有两个包,第一个包里面的RegistUserImpl是实现了自己定义的接口,RegistUserFunctionApi是为了实现RegistUserImpl中的一些方法而定义的接口,RegistUserFunctionApiImpl是对这个接口的实现
上代码:
StatusArgs.java
package com.kj.demopom.user.provider.api.impl.comment;
public class StatusArgs {
public static final int Flag_User_SUCCESS = 0; //验证成功,不存在当前name
public static final int Flag_User_FAILED = 1; //验证失败,存在当前name
}
RegistUserFunctionMapper.java
package com.kj.demopom.user.provider.api.impl.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface RegistUserFunctionMapper {
//查询数据库中存在当前name吗
int RegistFlagUser(String name);
//添加用户
int RegistUserIntsert(@Param("name") String name,
@Param("password") String password,
@Param("old") String old);
}
RegistUserImpl.java
package com.kj.demopom.user.provider.api.impl.service.apiIpml;
import com.alibaba.dubbo.config.annotation.Service;
import com.kj.demopom.user.api.RegistUser;
import com.kj.demopom.user.domain.User;
import com.kj.demopom.user.provider.api.impl.service.RegistUserFunctionApi;
import com.kj.demopom.user.provider.api.impl.comment.StatusArgs;
import org.springframework.beans.factory.annotation.Autowired;
@Service(version = "1.0.1")
public class RegistUserImpl implements RegistUser {
private boolean flagBLN; //用于接收查询name结果的参数
private int registUserIntsert = 0; //判断保存用户成功的参数
@Autowired
private RegistUserFunctionApi registUserFunctionApi;
@Override
public int registUser(User user) {
flagBLN = flagUser(user.getName());
if (flagBLN){
//如果name的判断通过,即不存在当前name执行insert
registUserIntsert = registUserFunctionApi.RegistUserIntsert(user);
}
return registUserIntsert;
}
@Override
public boolean flagUser(String name) {
int flagNum = registUserFunctionApi.RegistFlagUser(name);//查询name
flagBLN = flagNum == StatusArgs.Flag_User_SUCCESS;
return flagBLN;
}
}
RegistUserFunctionApi.java
package com.kj.demopom.user.provider.api.impl.service;
import com.kj.demopom.user.domain.User;
public interface RegistUserFunctionApi {
public int RegistFlagUser(String name);
public int RegistUserIntsert(User user);
}
RegistUserFunctionApiImpl.java
package com.kj.demopom.user.provider.api.impl.service.Impl;
import com.kj.demopom.user.domain.User;
import com.kj.demopom.user.provider.api.impl.comment.StatusArgs;
import com.kj.demopom.user.provider.api.impl.mapper.RegistUserFunctionMapper;
import com.kj.demopom.user.provider.api.impl.service.RegistUserFunctionApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RegistUserFunctionApiImpl implements RegistUserFunctionApi {
@Autowired
private RegistUserFunctionMapper registUserFunctionMapper;
@Override
public int RegistFlagUser(String name) {
int flagUser = registUserFunctionMapper.RegistFlagUser(name);
//查询name后返回值不等于0就等于1,用于判断
if (flagUser != StatusArgs.Flag_User_SUCCESS){
flagUser = StatusArgs.Flag_User_FAILED;
}
return flagUser;
}
@Override
public int RegistUserIntsert(User user) {
return registUserFunctionMapper.RegistUserIntsert(user.getName(),user.getPassword(),user.getOld());
}
}
RegistUserFunctionMapper.xml
<mapper namespace="com.kj.demopom.user.provider.api.impl.mapper.RegistUserFunctionMapper">
<select id="RegistFlagUser" parameterType="String" resultType="int">
select count(name) from user u where u.name = #{name}
select>
<insert id="RegistUserIntsert" parameterType="String"
useGeneratedKeys="true" >
insert into user values(#{name},#{password},#{old})
insert>
mapper>
到此provider这个模块完成了
和provider不一样,这个模块是一个web项目,所以新建的时候选择依赖的时候将Sping Web勾选上
其他的基本一致,
同样需要依赖自己的接口和dubbo,在pom.xml中添加:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>com.alibaba.bootgroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>0.2.0version>
dependency>
自己的接口:
<dependency>
<groupId>com.kjgroupId>
<artifactId>demoPom-user-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
如果一开始没有选择web的依赖,需要添加:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
application.yml(除了没有数据库和mybatis的配置其他基本一样):
spring:
application:
name: demopom-user-consumer
user:
service:
version: 1.0.0
dubbo:
scan:
basePackages: com.kj.demopom.user.consumer.controller
application:
id: demopom-user-consumer
name: demopom-user-consumer
registry:
id: zookeeper
address: zookeeper://1**.1**.**.1**:2182?backup=1**.1**.**.1**:2183,1**.1**.**.1**:2184
这是用于测试的三个页面,第一个是失败跳转,第二个是注册页,第三个是成功的页面,
注册页面:
<html lang="zh" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<form action="/user/registed" method="post">
用户名:<input type="text" name="name"><br>
密码:<input type="text" name="password"><br>
年龄:<input type="text" name="old"><br>
<button type="submit">提交button>
form>
body>
html>
成功页面:
<html lang="zh" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1 th:text="${user.name}">
h1>
body>
html>
页面很简单注册成功转跳并显示当前注册的name参数
模块的结构:
PageName:存放页面的名称的常量类
StatusArgs:存放固定参数的常量类
RegistController:处理业务逻辑,调用接口
RegistService:处理业务逻辑的接口
RegistServiceImpl:实现RegistService接口
代码:
PageName.java
package com.kj.demopom.user.consumer.comment;
public class PageName {
public static final String PAGE_NAME_REGIST = "regist";
public static final String PAGE_NAME_REGIST_FAILED = "faild";
public static final String PAGE_NAME_REGIST_SUCCESS = "success";
}
StatusArgs.java
package com.kj.demopom.user.consumer.comment;
public class StatusArgs {
public static final int REGIST_USER_FAILED = 0;
public static final String USER_PARAM_NAME = "user";
}
RegistController.java
package com.kj.demopom.user.consumer.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.kj.demopom.user.api.RegistUser;
import com.kj.demopom.user.consumer.comment.PageName;
import com.kj.demopom.user.consumer.comment.StatusArgs;
import com.kj.demopom.user.consumer.service.RegistService;
import com.kj.demopom.user.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpSession;
@Component
@Controller
public class RegistController {
@Autowired
private RegistService registService;
@Reference(version = "1.0.1")
private RegistUser registUser;
@PostConstruct
public void init(){
//想registService中初始化registUser,
// 直接在registService中不能直接调用registUser,
// 会报nullpoint 甚至启动时候报错服务找不到
registService.setRegistUser(registUser);
}
@GetMapping(value = "/user/regist")
public String showRegist(){
return registService.showRegist();
}
@PostMapping(value = "/user/registed")
public String Registing(User user, HttpSession session){
int registing = registService.Registing(user);
if (registing == StatusArgs.REGIST_USER_FAILED){
return PageName.PAGE_NAME_REGIST_FAILED;
}else{
session.setAttribute(StatusArgs.USER_PARAM_NAME,user);
return PageName.PAGE_NAME_REGIST_SUCCESS;
}
}
}
RegistService.java
package com.kj.demopom.user.consumer.service;
import com.kj.demopom.user.api.RegistUser;
import com.kj.demopom.user.domain.User;
public interface RegistService {
String showRegist();
int Registing(User user);
void setRegistUser(RegistUser registUser);
}
RegistServiceImpl.java
package com.kj.demopom.user.consumer.service.Impl;
import com.alibaba.dubbo.config.annotation.Reference;
import com.kj.demopom.user.api.RegistUser;
import com.kj.demopom.user.consumer.comment.PageName;
import com.kj.demopom.user.consumer.service.RegistService;
import com.kj.demopom.user.domain.User;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Service()
@Component
public class RegistServiceImpl implements RegistService {
int registUser_FLAG;
@Reference(version = "1.0.1")
private RegistUser registUser;
public void setRegistUser(RegistUser registUser) {
this.registUser = registUser;
}
@Override
public String showRegist() {
return PageName.PAGE_NAME_REGIST;
}
@Override
public int Registing(User user) {
System.out.println(user.toString());
String username = user.getName();
boolean flagUser = registUser.flagUser(user.getName());
if (flagUser){
registUser_FLAG = this.registUser.registUser(user);
}
return registUser_FLAG;
}
}
说明下:在Controller中可以直接调用
@Reference(version = “1.0.1”)
private RegistUser registUser;
并且得到接口实例,但是如果是在service中就不能直接得到,本人觉得业务逻辑隐藏起来是比较好的,所以借用Service来调用接口,达到隐蔽的效果,就为什么service中直接得到实例为null,目前我也不清楚,目前能想到的方法是在controller中使用初始化的方式来自动的注入进service。
效果:
启动provider后再启动 consumer,由于没有安装dubbo的admin,所以只能在zookeeper中查看服务同样随便交互进入一个zookeeper中,在bin中启动zkCli.sh 客户端
查看节点:
ls /
查看节点中注册的服务:
ls /节点名
进入dubbo中查看,可以发现这个服务,表名这个服务以及注册到zookeeper中了
访问 http://localhost:8080/user/regist/
返回成功结果:
数据库:
说明结果是正确的,通过zookeeper和dubbo成功实现了接口的远程调用。
实现了服务端与消费端的通信