开发需求背景,在开发小程序视频时(springboot后端),需要一个后台管理系统(ssm后端),并且这两个系统是部署在不同的服务器上,当管理人员通过短视频后台管理背景音乐的上传和删除,需要小程序端的服务器能通过监听能自动下载背景音乐。这里我们就通过zookeeper中间件,springboot监听并且下载。
###内容目录
什么是Zookeeper?
1、中间件,提供协调服务。
2、作用于分布式系统,发挥其优势,可以为大数据服务。
3、支持java,提供java和c语言的客户端api。
什么是分布式系统?
1、很多台计算机组成一个整体,一个整体一致对外并且处理同一请求。
2、内部的每台计算机都可以相互通信。
3、客户端到服务端的一次请求到响应结束会经历多台计算机。
windows10 单机安装zookeeper
1、在安装zookeeper之前,需要先安装JDK,并进行环境配置。
2、下载zookeeper解压。在bin目录下, zkServer.cmd是zookeeper的启动脚本。
在你执行启动脚本之前,还有几个基本的配置项需要配置一下,Zookeeper 的配置文件在 conf 目录下,这个目录下有 zoo_sample.cfg 和 log4j.properties,你需要做的就是将 zoo_sample.cfg 改名为 zoo.cfg,因为 Zookeeper 在启动时会找这个文件作为默认配置文件。
常用的zk java客户端
1、zk原生API
缺点:超时重连,不支持自动,需要手动操作。Watch注册一次后会失效,不支持递归的创建节点。
2、zkClient (很久没有更新)
3、apache curator
apche的开源项目,解决watcher的注册一次就失效的问题,api更加简单方便。
支持代码块,并解决常见的代码块换行不正确,特别是iPone、iPad上不能滚动的问题;
解决把内容粘贴到公众号时,图片、或样式丢失的问题;
Curator 创建zk客户端的步骤
1、创建重试策略 - retryPolicy
2、创建客户端 - client
3、 初始化客户端,加入到spring容器,项目启动加载的时候执行。
zookeeepr代码实现
1、zookeeper需要导入的pom依赖
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.11</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>
2、后台管理ssm进行zookeeper的spring容器的配置
<description>zookeeper 放入spring容器,项目启动加载的时候就建立和zk的连接</description>
<!-- 创建重连策略 -->
<bean id="retryPolicy" class="org.apache.curator.retry.ExponentialBackoffRetry">
<!-- 每次重试连接的等待时间 -->
<constructor-arg index="0" value="1000"></constructor-arg>
<!-- 设置的重连的次数 -->
<constructor-arg index="1" value="5"></constructor-arg>
</bean>
<!-- 创建zookeeper客户端 -->
<bean id="client" class="org.apache.curator.framework.CuratorFrameworkFactory"
factory-method="newClient" init-method="start">
<constructor-arg index="0" value="192.168.1.210:2181"></constructor-arg>
<constructor-arg index="1" value="10000"></constructor-arg>
<constructor-arg index="2" value="10000"></constructor-arg>
<constructor-arg index="3" ref="retryPolicy"></constructor-arg>
</bean>
<!-- 客户端配置 -->
<!-- init-method="init" 不使用zk的话,仅仅只是测试,为了方便可以把这个方法暂时拿掉 -->
<bean id="ZKCurator" class="com.imooc.web.util.ZKCurator" init-method="init">
<constructor-arg index="0" ref="client"></constructor-arg>
</bean>
3、后台管理zookeeper的客户端工具类
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs.Ids;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ZKCurator {
// zk客户端
private CuratorFramework client = null;
final static Logger log = LoggerFactory.getLogger(ZKCurator.class);
public ZKCurator(CuratorFramework client) {
this.client = client;
}
/**
初始化调用
*/
public void init() {
//使用admin命名空间,方便zookeeper节点目录区分。
client = client.usingNamespace("admin");
try {
// 判断在admin命名空间下是否有bgm节点 /admin/bmg
if (client.checkExists().forPath("/bgm") == null) {
/**
* 对于zk来讲,有两种类型的节点:
* 持久节点: 当你创建一个节点的时候,这个节点就永远存在,除非你手动删除
* 临时节点: 你创建一个节点之后,会话断开,会自动删除,当然也可以手动删除
*/
client.create().creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT) // 节点类型:持久节点
.withACL(Ids.OPEN_ACL_UNSAFE) // acl:匿名权限
.forPath("/bgm");
log.info("zookeeper初始化成功...");
log.info("zookeeper服务器状态:{}", client.isStarted());
}
} catch (Exception e) {
log.error("zookeeper客户端连接、初始化错误...");
e.printStackTrace();
}
}
/**
* @Description: 增加或者删除bgm,向zk-server创建子节点,供小程序后端监听
* forPath的两个参数:第一个:表示创建的目录节点,第二个表示该节点下要保存的数据。
*/
public void sendBgmOperator(String bgmId, String operObj) {
try {
client.create().creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT) // 节点类型:持久节点
.withACL(Ids.OPEN_ACL_UNSAFE) // acl:匿名权限
.forPath("/bgm/" + bgmId, operObj.getBytes()); // bgm/bgmId 子节点
} catch (Exception e) {
e.printStackTrace();
}
}
}
3、后台管理–枚举类
package com.imooc.enums;
public enum BGMOperatorTypeEnum {
ADD("1", "添加bgm"),
DELETE("2", "删除bgm");
public final String type;
public final String value;
BGMOperatorTypeEnum(String type, String value){
this.type = type;
this.value = value;
}
public String getUserType() {
return type;
}
public String getValue() {
return value;
}
public static String getValueByKey(String key) {
for (BGMOperatorTypeEnum type : BGMOperatorTypeEnum.values()) {
if (type.getUserType().equals(key)) {
return type.value;
}
}
return null;
}
}
4、后台管理对bgm进行添加和删除时候,将数据保存在zookeeper节点上。
@Override
public void addBgm(Bgm bgm) {
String bgmId = sid.nextShort();
bgm.setId(bgmId);
bgmMapper.insert(bgm);
Map<String, String> map = new HashMap<>();
map.put("operType", BGMOperatorTypeEnum.ADD.type);
map.put("path", bgm.getPath());
zkCurator.sendBgmOperator(bgmId, JsonUtils.objectToJson(map));
}
@Override
public void deleteBgm(String id) {
Bgm bgm = bgmMapper.selectByPrimaryKey(id);
bgmMapper.deleteByPrimaryKey(id);
Map<String, String> map = new HashMap<>();
map.put("operType", BGMOperatorTypeEnum.DELETE.type);
map.put("path", bgm.getPath());
zkCurator.sendBgmOperator(id, JsonUtils.objectToJson(map));
}
5、小程序端后台监听,并进行删除或新增操作。
package com.imooc;
import java.io.File;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.imooc.cofig.ResourceConfig;
import com.imooc.enums.BGMOperatorTypeEnum;
import com.imooc.utils.JsonUtils;
@Component
public class ZKCuratorClient {
// zk客户端
private CuratorFramework client = null;
final static Logger log = LoggerFactory.getLogger(ZKCuratorClient.class);
// @Autowired
// private BgmService bgmService;
// public static final String ZOOKEEPER_SERVER = "192.168.1.210:2181";
@Autowired
private ResourceConfig resourceConfig;
public void init() {
if (client != null) {
return;
}
// 重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
// 创建zk客户端
client = CuratorFrameworkFactory.builder().connectString(resourceConfig.getZookeeperServer())
.sessionTimeoutMs(10000).retryPolicy(retryPolicy).namespace("admin").build();
// 启动客户端
client.start();
try {
//测试 从这个(admin/bgm/18052674D26HH3X4)节点下获取数据
// String testNodeData = new String(client.getData().forPath("/bgm/18052674D26HH3X4"));
// log.info("测试的节点数据为: {}", testNodeData);
addChildWatch("/bgm"); //监听bgm下的子节点
} catch (Exception e) {
e.printStackTrace();
}
}
//事件监听
public void addChildWatch(String nodePath) throws Exception {
final PathChildrenCache cache = new PathChildrenCache(client, nodePath, true);
cache.start();
cache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
throws Exception {
if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)) {
log.info("监听到事件 CHILD_ADDED");
// 1. 从数据库查询bgm对象,获取路径path
String path = event.getData().getPath();
String operatorObjStr = new String(event.getData().getData()); //节点类型
Map<String, String> map = JsonUtils.jsonToPojo(operatorObjStr, Map.class);
String operatorType = map.get("operType");
String songPath = map.get("path");
// String arr[] = path.split("/");
// String bgmId = arr[arr.length - 1];
//在删除的时候,有可能bgm这条记录已经被后台管理删除,这里再查询就查不到记录。
// Bgm bgm = bgmService.queryBgmById(bgmId);
// if (bgm == null) {
// return;
// }
// 1.1 bgm所在的相对路径
// String songPath = bgm.getPath();
// 2. 定义保存到本地的bgm路径
// String filePath = "C:\\imooc_videos_dev" + songPath;
String filePath = resourceConfig.getFileSpace() + songPath;
// 3. 定义下载的路径(播放url)
String arrPath[] = songPath.split("\\\\");
String finalPath = "";
// 3.1 处理url的斜杠以及编码
for(int i = 0; i < arrPath.length ; i ++) {
if (StringUtils.isNotBlank(arrPath[i])) {
finalPath += "/";
finalPath += URLEncoder.encode(arrPath[i], "UTF-8") ;
}
}
// String bgmUrl = "http://192.168.1.2:8080/mvc" + finalPath;
String bgmUrl = resourceConfig.getBgmServer() + finalPath;
if (operatorType.equals(BGMOperatorTypeEnum.ADD.type)) {
// 下载bgm到spingboot服务器
URL url = new URL(bgmUrl);
File file = new File(filePath);
FileUtils.copyURLToFile(url, file);
client.delete().forPath(path);
} else if (operatorType.equals(BGMOperatorTypeEnum.DELETE.type)) {
File file = new File(filePath);
FileUtils.forceDelete(file);
client.delete().forPath(path);
}
}
}
});
}
}
6、小程序后端,springboot对ZKCuratorClient进行bean的配置。
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Bean(initMethod="init")
public ZKCuratorClient zkCuratorClient() {
return new ZKCuratorClient();
}
}
7、小程序后端,springboot进行自定义资源文件配置。
@ConfigurationProperties(prefix=“com.imooc”)中的com.imooc是对resource.properties文件中com.imooc.zookeeperServer的com.imooc的统一定义。
@Configuration //定义配置文件
@ConfigurationProperties(prefix="com.imooc") // 定义前缀。
@PropertySource("classpath:resource.properties") //指定资源文件目录
public class ResourceConfig {
private String zookeeperServer;
private String bgmServer;
private String fileSpace;
public String getZookeeperServer() {
return zookeeperServer;
}
public void setZookeeperServer(String zookeeperServer) {
this.zookeeperServer = zookeeperServer;
}
public String getBgmServer() {
return bgmServer;
}
public void setBgmServer(String bgmServer) {
this.bgmServer = bgmServer;
}
public String getFileSpace() {
return fileSpace;
}
public void setFileSpace(String fileSpace) {
this.fileSpace = fileSpace;
}
}
资源文件:resource.properties
com.imooc.zookeeperServer=192.168.1.210:2181
com.imooc.bgmServer=http://192.168.1.2:8080/mvc
com.imooc.fileSpace=C:\\imooc_videos_dev