本文源碼:GitHub·點這里 || GitEE·點這里
一、Zookeeper基礎簡介
1、概念簡介
Zookeeper是一個Apache開源的分布式的應用,為系統架構提供協調服務。從設計模式角度來審視:該組件是一個基於觀察者模式設計的框架,負責存儲和管理數據,接受觀察者的注冊,一旦數據的狀態發生變化,Zookeeper就將負責通知已經在Zookeeper上注冊的觀察者做出相應的反應,從而實現集群中類似Master/Slave管理模式。ZooKeeper的目標就是封裝好復雜易出錯的關鍵服務,將簡單易用的接口和性能高效、功能穩定的系統提供給用戶。
2、基本理論
- 數據結構
ZooKeeper記錄數據的結構與Linux文件系統相似,整體可以看作一棵樹,每個節點稱ZNode。每個Znode默認能夠存儲1MB的數據,每個ZNode都可以通過其路徑唯一標識。
- 節點類型
短暫(ephemeral):客戶端和服務器端斷開連接后,創建的節點自動刪除。
持久(persistent):客戶端和服務器端斷開連接后,創建的節點持久化保存。
- 集群服務
在Zookeeper集群服務是由一個領導者(leader),多個跟隨者(follower)組成的集群。領導者負責進行投票的發起和決議,更新集群服務狀態。跟隨者用於接收客戶請求並向客戶端返回結果,在選舉Leader過程中參與投票。集群中只要有半數以上節點存活,Zookeeper集群就能正常服務。
- 數據一致性
每個server保存一份相同的數據拷貝,客戶端無論請求到被集群中哪個server處理,得到的數據都是一致的。
3、應用場景
- 經典應用:Dubbo框架的服務注冊和發現;
- 分布式消息同步和協調機制;
- 服務器節點動態上下線;
- 統一配置管理、負載均衡、集群管理;
二、安全管理操作
1、操作權限
ZooKeeper的節點有5種操作權限:CREATE(增)、READ(查)、WRITE(改)、DELETE(刪)、ADMIN(管理)等相關權限,這5種權限集合可以簡寫為crwda,每個單詞的首字符拼接而成。
2、認證方式:
- world
默認方式,開放的權限,意解為全世界都能隨意訪問。
- auth
已經授權且認證通過的用戶才可以訪問。
- digest
用戶名:密碼方式認證,實際業務開發中最常用的方式。
- IP白名單
授權指定的Ip地址,和指定的權限點,控制訪問。
3、Digest授權流程
- 添加認證用戶
addauth digest 用戶名:密碼
- 設置權限
setAcl /path auth:用戶名:密碼:權限
- 查看Acl設置
getAcl /path
- 完整操作流程
-- 添加授權用戶
[zk: localhost:2181] addauth digest smile:123456
-- 創建節點
[zk: localhost:2181] create /cicada cicada
-- 節點授權
[zk: localhost:2181] setAcl /cicada auth:smile:123456:cdrwa
-- 查看授權
[zk: localhost:2181] getAcl /cicada
三、整合 SpringBoot2 框架
1、核心依賴
Curator是Apache開源的一個Zookeeper客戶端連接和操作的組件,Curator框架在Zookeeper原生API接口上進行二次包裝。提供ZooKeeper各種應用場景:比如:分布式鎖服務、集群領導選舉、共享計數器、緩存機制、分布式隊列等API封裝。
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>2.12.0</version>
</dependency>
2、Zookeeper參數
zoo:
keeper:
#開啟標志
enabled: true
#服務器地址
server: 127.0.0.1:2181
#命名空間,被稱為ZNode
namespace: cicada
#權限控制,加密
digest: smile:123456
#會話超時時間
sessionTimeoutMs: 3000
#連接超時時間
connectionTimeoutMs: 60000
#最大重試次數
maxRetries: 2
#初始休眠時間
baseSleepTimeMs: 1000
3、服務初始化配置
@Configuration
public class ZookeeperConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(ZookeeperConfig.class) ;
@Resource
private ZookeeperParam zookeeperParam ;
private static CuratorFramework client = null ;
/**
* 初始化
*/
@PostConstruct
public void init (){
//重試策略,初試時間1秒,重試10次
RetryPolicy policy = new ExponentialBackoffRetry(
zookeeperParam.getBaseSleepTimeMs(),
zookeeperParam.getMaxRetries());
//通過工廠創建Curator
client = CuratorFrameworkFactory.builder()
.connectString(zookeeperParam.getServer())
.authorization("digest",zookeeperParam.getDigest().getBytes())
.connectionTimeoutMs(zookeeperParam.getConnectionTimeoutMs())
.sessionTimeoutMs(zookeeperParam.getSessionTimeoutMs())
.retryPolicy(policy).build();
//開啟連接
client.start();
LOGGER.info("zookeeper 初始化完成...");
}
public static CuratorFramework getClient (){
return client ;
}
public static void closeClient (){
if (client != null){
client.close();
}
}
}
4、封裝系列接口
public interface ZookeeperService {
/**
* 判斷節點是否存在
*/
boolean isExistNode (final String path) ;
/**
* 創建節點
*/
void createNode (CreateMode mode,String path ) ;
/**
* 設置節點數據
*/
void setNodeData (String path, String nodeData) ;
/**
* 創建節點
*/
void createNodeAndData (CreateMode mode, String path , String nodeData) ;
/**
* 獲取節點數據
*/
String getNodeData (String path) ;
/**
* 獲取節點下數據
*/
List<String> getNodeChild (String path) ;
/**
* 是否遞歸刪除節點
*/
void deleteNode (String path,Boolean recursive) ;
/**
* 獲取讀寫鎖
*/
InterProcessReadWriteLock getReadWriteLock (String path) ;
}
5、接口實現
@Service
public class ZookeeperServiceImpl implements ZookeeperService {
private static final Logger LOGGER = LoggerFactory.getLogger(ZookeeperServiceImpl.class);
@Override
public boolean isExistNode(String path) {
CuratorFramework client = ZookeeperConfig.getClient();
client.sync() ;
try {
Stat stat = client.checkExists().forPath(path);
return client.checkExists().forPath(path) != null;
} catch (Exception e) {
LOGGER.error("isExistNode error...", e);
e.printStackTrace();
}
return false;
}
@Override
public void createNode(CreateMode mode, String path) {
CuratorFramework client = ZookeeperConfig.getClient() ;
try {
// 遞歸創建所需父節點
client.create().creatingParentsIfNeeded().withMode(mode).forPath(path);
} catch (Exception e) {
LOGGER.error("createNode error...", e);
e.printStackTrace();
}
}
@Override
public void setNodeData(String path, String nodeData) {
CuratorFramework client = ZookeeperConfig.getClient() ;
try {
// 設置節點數據
client.setData().forPath(path, nodeData.getBytes("UTF-8"));
} catch (Exception e) {
LOGGER.error("setNodeData error...", e);
e.printStackTrace();
}
}
@Override
public void createNodeAndData(CreateMode mode, String path, String nodeData) {
CuratorFramework client = ZookeeperConfig.getClient() ;
try {
// 創建節點,關聯數據
client.create().creatingParentsIfNeeded().withMode(mode)
.forPath(path,nodeData.getBytes("UTF-8"));
} catch (Exception e) {
LOGGER.error("createNode error...", e);
e.printStackTrace();
}
}
@Override
public String getNodeData(String path) {
CuratorFramework client = ZookeeperConfig.getClient() ;
try {
// 數據讀取和轉換
byte[] dataByte = client.getData().forPath(path) ;
String data = new String(dataByte,"UTF-8") ;
if (StringUtils.isNotEmpty(data)){
return data ;
}
}catch (Exception e) {
LOGGER.error("getNodeData error...", e);
e.printStackTrace();
}
return null;
}
@Override
public List<String> getNodeChild(String path) {
CuratorFramework client = ZookeeperConfig.getClient() ;
List<String> nodeChildDataList = new ArrayList<>();
try {
// 節點下數據集
nodeChildDataList = client.getChildren().forPath(path);
} catch (Exception e) {
LOGGER.error("getNodeChild error...", e);
e.printStackTrace();
}
return nodeChildDataList;
}
@Override
public void deleteNode(String path, Boolean recursive) {
CuratorFramework client = ZookeeperConfig.getClient() ;
try {
if(recursive) {
// 遞歸刪除節點
client.delete().guaranteed().deletingChildrenIfNeeded().forPath(path);
} else {
// 刪除單個節點
client.delete().guaranteed().forPath(path);
}
} catch (Exception e) {
LOGGER.error("deleteNode error...", e);
e.printStackTrace();
}
}
@Override
public InterProcessReadWriteLock getReadWriteLock(String path) {
CuratorFramework client = ZookeeperConfig.getClient() ;
// 寫鎖互斥、讀寫互斥
InterProcessReadWriteLock readWriteLock = new InterProcessReadWriteLock(client, path);
return readWriteLock ;
}
}
6、基於Swagger2接口
@Api("Zookeeper接口管理")
@RestController
public class ZookeeperApi {
@Resource
private ZookeeperService zookeeperService ;
@ApiOperation(value="查詢節點數據")
@GetMapping("/getNodeData")
public String getNodeData (String path) {
return zookeeperService.getNodeData(path) ;
}
@ApiOperation(value="判斷節點是否存在")
@GetMapping("/isExistNode")
public boolean isExistNode (final String path){
return zookeeperService.isExistNode(path) ;
}
@ApiOperation(value="創建節點")
@GetMapping("/createNode")
public String createNode (CreateMode mode, String path ){
zookeeperService.createNode(mode,path) ;
return "success" ;
}
@ApiOperation(value="設置節點數據")
@GetMapping("/setNodeData")
public String setNodeData (String path, String nodeData) {
zookeeperService.setNodeData(path,nodeData) ;
return "success" ;
}
@ApiOperation(value="創建並設置節點數據")
@GetMapping("/createNodeAndData")
public String createNodeAndData (CreateMode mode, String path , String nodeData){
zookeeperService.createNodeAndData(mode,path,nodeData) ;
return "success" ;
}
@ApiOperation(value="遞歸獲取節點數據")
@GetMapping("/getNodeChild")
public List<String> getNodeChild (String path) {
return zookeeperService.getNodeChild(path) ;
}
@ApiOperation(value="是否遞歸刪除節點")
@GetMapping("/deleteNode")
public String deleteNode (String path,Boolean recursive) {
zookeeperService.deleteNode(path,recursive) ;
return "success" ;
}
}
四、源代碼地址
GitHub·地址
https://github.com/cicadasmile/middle-ware-parent
GitEE·地址
https://gitee.com/cicadasmile/middle-ware-parent