Zookeeper入門編程之對zookeeper節點的增刪改查
最近做了一個項目,關於一個訪問API的開放平臺,其中有很重要的一個模塊是從mysql數據庫到zookeeper節點的數據同步,這一塊是我獨立負責的,這兩天剛剛開發和測試完,其中遇到了一些問題,也有了不少的收穫,現在做一個總結。
對於zk,百度百科上有詳細的介紹,不知道的童鞋可以自行了解,這裏我們只需要明白兩點即可:
1:zk是可以單實例或者集羣化部署的
2:如果zk以集羣化部署,相應會產生多個zk節點,那麼只要有超過一半(必須超過)的zk節點宕機,則整個zk集羣都將無法正常工作。
對zk操作的pom.xml文件中maven添加的依賴地址如下:
<!-- zk 連接客戶端 start -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<!-- zk 連接客戶端 end -->
要想對zk的節點即zktree進行操作,第一步是與zk建立連接,代碼如下:
/**
* @描述:創建一個zookeeper連接
*/
private static void CreateZkclientConnection() {
// 定義zk服務器的ip和port,多個節點的話用","分隔
final String connectString = "197.3.153.159:2181,197.3.153.160:2181,197.3.153.161:2181";
// retryPolicy是連接zk過程中重連策略,兩個參數分別代表:兩次重連的等待時間和最大重試次數
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 100);
// 創建CuratorFramework實例,創建完成即代表連接zk成功
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
// 調用start方法打開連接
client.start();
}
與zk建立連接之後就可以對zk節點進行操作了,首先是創建節點,代碼如下:
/**
* @描述:創建一個zookeeper節點
* @param path 路徑
* @param json 節點名
* @throws Exception
*/
public void createNode(String path, String json) throws Exception {
final String connectString = "197.3.153.159:2181,197.3.153.160:2181,197.3.153.161:2181";RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 100);CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);client.start();try {
// 傳入路徑和節點名調用zkclient自身的create方法client.create().creatingParentsIfNeeded().forPath(path, json.getBytes(GatewayConstant.UTF8));} catch (Exception e) {e.printStackTrace();} finally {// 對zk操作完成之後要注意關閉連接if (client != null) {
client.close();
}
}
}
這裏對create()方法做個說明:create()---執行創建操作,可以調用額外的方法(比如後臺執行background)並在最後調用forPath()指定要操作的znode。
接下來是查詢某個路徑下的節點,代碼如下:
/**
* @描述:查詢zookeeper路徑的節點
* @param path 要查詢節點的路徑
* @return attrJson 查詢到的節點名
* @throws Exception
*/
public String queryNode(String path) throws Exception {
final String connectString = "197.3.153.159:2181,197.3.153.160:2181,197.3.153.161:2181";
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 100);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
client.start();
// 定義查詢到的節點
String attrJson = null;
try {
// 調用zkclient的getData()方法
byte[] byteNode = client.getData().forPath(path);
// 轉成UTF-8格式的字符串
attrJson = new String(byteNode, GatewayConstant.UTF8);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 對zk操作完成之後要注意關閉連接
if (client != null) {
client.close();
}
}
return attrJson;
這裏對getData()方法做個說明:getData()---執行獲取znode節點數據的操作,可以調用額外的方法(比如監控、後臺處理或者獲取狀態watch)並在最後調用forPath()指定要操作的znode。}
然後是修改某個路徑下的節點值,代碼如下:
/**
* @描述:修改zookeeper路徑的節點值
* @param path 要修改節點值的路徑
* @param json 要修改成的內容
* @throws Exception
*/
public void editNode(String path, String json) throws Exception {
final String connectString = "197.3.153.159:2181,197.3.153.160:2181,197.3.153.161:2181";
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 100);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
client.start();
try {
// 調用zkclient的setData()方法
client.setData().forPath(path, json.getBytes(GatewayConstant.UTF8));
} catch (Exception e) {
e.printStackTrace();
} finally {
// 對zk操作完成之後要注意關閉連接
if (client != null) {
client.close();
}
}
}
這裏對setData()方法做個說明:setData()---執行設置znode節點數據的操作,可以調用額外的方法(比如版本、後臺處理)並在最後調用forPath()指定要操作的znode。
然後是刪除某個路徑的節點,代碼如下:
/**
* @描述:刪除zookeeper路徑的節點
* @param path 要刪除節點的路徑
* @throws Exception
*/
public void deteleNode(String path) throws Exception {
final String connectString = "197.3.153.159:2181,197.3.153.160:2181,197.3.153.161:2181";
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 100);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retryPolicy);
client.start();
try {
// 調用zkclient的delete()方法
client.delete().forPath(path);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 對zk操作完成之後要注意關閉連接
if (client != null) {
client.close();
}
}
}
這裏對delete()方法做個說明:delete()---執行刪除znode節點的操作,可以調用額外的方法(比如版本、後臺處理)並在最後調用forPath()指定要操作的znode。
上面的增刪改查都是在路徑和json都確定的情況下進行的,下面給出一個完整的類代碼,這個類功能是一鍵將數據庫中的某些數據同步到zk服務器的zktree上,現在暫定zktree上有5個需要同步的節點,分別爲:
inbound_channel、accessVerification、authentication、router、outbound_channel,代碼如下:
/**
* mysql--->zk一鍵同步類 適用於插件第一次初始化數據和運行過程中出現問題需要強制同步的場景
* Created by xuzheng on 2017/09.
* @since 3.0.0
*/
@Functions
@Service
public class OneClickSyncFunction {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private GatewayCuratorUtil gatewayCuratorUtil;
@Autowired
private SysParamsMapper sysParamsMapper;
@Autowired
ApiInfoMapper apiInfoMapper;
@Autowired
AuthenticationInfoMapper authenticationInfoMapper;
@Autowired
AccessAuthorityMapper accessAuthorityMapper;
@Autowired
AuthenticationConfigFunctions authenticationConfigFunction;
@Autowired
AccessVerificationConfigFunctions accessVerificationConfigFunction;
@Autowired
RouterConfigFunctions routerConfigFunctions;
@Autowired
OutboundChannelConfigFunctions outboundChannelConfigFunction;
/**
* 數據同步一鍵執行所有插件
* @throws Exception
*/
@Function
public void dataSync() throws Exception {
// 加載本地配置文件,獲取大插件名
Map<String, String> config = LoadConfigurationDocument.LoadConfiguration();
String plugName = config.get("plugName");
// 用插件名和插件節點名獲取節點路徑
String nodePath = GatewayZkPathUtil.getNodepath(plugName);
String path = nodePath.substring(0, nodePath.length() - 1);
// 獲取該路徑下節點名的集合,如:[authentication]
List<String> nodeList = GatewayCuratorUtil.queryList(path);
String idAuthen = "authentication";
Boolean blAuthen = nodeList.contains(idAuthen);
// 如果集合中有身份驗證節點名:authentication,調用身份驗證同步方法
if (blAuthen) {
authenticationDataSync(plugName, idAuthen);
}
String idAccess = "accessVerification";
Boolean blAccess = nodeList.contains(idAccess);
// 如果集合中有權限校驗節點名:accessVerification,調用權限校驗同步方法
if (blAccess) {
accessVerificationDataSync(plugName, idAccess);
}
String idRouter = "router";
Boolean blRouter = nodeList.contains(idRouter);
// 如果集合中有router節點名:router,調用router同步方法
if (blRouter) {
routerDataSync(plugName, idRouter);
}
String idOutboundChannel = "outbound_channel";
Boolean blOutboundChannel = nodeList.contains(idOutboundChannel);
// 如果集合中有outbound_channel節點名:outbound_channel,調用outbound_channel同步方法
if (blOutboundChannel) {
outboundChannelDataSync(plugName);
}
}
/**
* 身份驗證一鍵同步方法
* @param plugName 大插件名
* @param id 小插件名
*/
public void authenticationDataSync(String plugName, String id) throws Exception {
logger.info("+++++ 身份驗證節點數據同步開始 +++++");
// 創建要同步節點數據實體對象
AuthenticationConfig authenticationConfig = new AuthenticationConfig();
// 定義api級別的map
Map<String, String> uriGradeMap = new HashMap<>();
// 定義app認證信息map
Map<String, String> authenticationMap = new HashMap<>();
// 從數據庫中查詢出需要同步的參數,包括
// JWT祕鑰:keyStr
String keyStr = sysParamsMapper.queryJwtKeystr();
authenticationConfig.setKeyStr(keyStr);
// Token的有效時間:ttlMillis
String ttlMillis = sysParamsMapper.queryTokenTtlMillis();
authenticationConfig.setTtlMillis(Long.valueOf(ttlMillis));
// 查詢API信息表,用查詢出的元素組裝uriGradeMap
List<ApiInfo> apiInfoList = apiInfoMapper.queryApiInfoList();
// apiInfoList不爲空則從apiInfoList循環取出每個元素--ApiInfo的實體
if (apiInfoList != null) {
for (int i = 0; i < apiInfoList.size(); i++) {
ApiInfo apiInfo = apiInfoList.get(i);
// 再從apiInfo實體中取出需要組裝的字段,包括:version,openURI,api_level
String version = apiInfo.getVersion();
String openUri = apiInfo.getOpenuri();
Integer apiLevel = apiInfo.getApiLevel();
// 組裝放到uriGradeMap中
String versionAddUri = GatewayConstant.PATH_SEPARATOR+ GatewayConstant.VSEPARATOR
+ version+ GatewayConstant.PATH_SEPARATOR + openUri;
if (apiLevel == null) {
logger.error("同步身份驗證節點時發現API信息列表參數有誤:api級別不能爲空"+ apiInfo.getApiId());
throw new TeslaApplicationException("api級別不能爲空");
} else {
String apiLevelString = String.valueOf(apiLevel);
uriGradeMap.put(versionAddUri, apiLevelString);
}
}
} else {
logger.error("同步身份驗證節點時發現API信息列表信息有誤:API信息列表爲空");
throw new TeslaApplicationException("API信息列表不能爲空");
}
authenticationConfig.setUriGradeMap(uriGradeMap);
// 查詢鑑權信息表,用查詢出的元素組裝authenticationMap
List<AuthenticationInfo> authenticationInfoList = authenticationInfoMapper.queryAuthenticationInfo();
// 從authenticationInfoList循環取出每個元素--ApiInfo的實體
for (int j = 0; j < authenticationInfoList.size(); j++) {
AuthenticationInfo authenticationInfo = authenticationInfoList.get(j);
// 再從authenticationInfo實體中取出需要組裝的字段,包括:appkey,secret
String appkey = authenticationInfo.getAppkey();
String secret = authenticationInfo.getSecret();
// 放到authenticationMap中
authenticationMap.put(appkey, secret);
}
authenticationConfig.setAuthenticationMap(authenticationMap);
// 同步之前需要把子節點強行設置爲true,插件名不變
authenticationConfig.setAvailable(true);
authenticationConfig.setId(id);
// 調用單節點同步方法執行同步操作
authenticationConfigFunction.editAuthentication(authenticationConfig,plugName, id);
logger.info("+++++ 身份驗證節點數據同步結束 +++++");
}
/**
* 權限校驗一鍵同步方法
* @param plugName 大插件名
* @param id 小插件名
*/
public void accessVerificationDataSync(String plugName, String id) throws Exception {
logger.info("+++++ 權限校驗節點數據同步開始 +++++");
// 創建要同步節點數據實體對象
AccessVerificationConfig accessVerificationConfig = new AccessVerificationConfig();
// 定義app對api的訪問權限關係map,以${appkey+path}爲key,以""爲value
Map<String, String> accessVerificationMap = new HashMap<>();
// 從數據庫中查詢出需要同步的參數,包括
// 從訪問權限表查出所有的appId
List<String> appidList = accessAuthorityMapper.queryAppid();
// appidList不爲空則循環取出每一個appId去查詢該appid對應的appkey和關聯的apiid
if (appidList != null) {
for (int k = 0; k < appidList.size(); k++) {
String appId = appidList.get(k);
// 從鑑權信息表查該appId對應的appkey(唯一)
String appkey = authenticationInfoMapper.queryAppkeyByAppid(appId);
// 從訪問權限表查該appId關聯的apiId(不唯一)
List<String> apiidList = accessAuthorityMapper.queryApiidByAppid(appId);
// 循環取出每一個apiid去查詢api信息表
for (int l = 0; l < apiidList.size(); l++) {
String apiid = apiidList.get(l);
ApiInfo apiInfo = apiInfoMapper.queryApiInfoByApiid(apiid);
if (apiInfo != null) {
// 從apiInfo校驗API是否廢棄
int availability = apiInfo.getAvailability();
// 如果
// availability爲1代表API有效,從apiInfo中取出version和OPenUri
if (availability == 1) {
String version = apiInfo.getVersion();
String openUri = apiInfo.getOpenuri();
// 拼接appkey+version+uri放到map的key中
String accessMapkey = appkey+ GatewayConstant.PATH_SEPARATOR
+ GatewayConstant.VSEPARATOR + version
+ GatewayConstant.PATH_SEPARATOR + openUri;
accessVerificationMap.put(accessMapkey, "");
}
}
}
}
} else {
logger.error("同步權限校驗節點時發現APP訪問權限表信息列表信息有誤:appid都爲空");
throw new TeslaApplicationException("appid不能都爲空");
}
accessVerificationConfig.setAccessVerificationMap(accessVerificationMap);
// 同步之前需要把子節點強行設置爲true,插件名不變
accessVerificationConfig.setAvailable(true);
accessVerificationConfig.setId(id);
// 調用單節點同步方法執行同步操作
accessVerificationConfigFunction.editAccessVerification(
accessVerificationConfig, plugName, id);
logger.info("+++++ 權限校驗節點數據同步結束 +++++");
}
/**
* router一鍵同步方法
* @param plugName 大插件名
* @param id 小插件名
*/
public void routerDataSync(String plugName, String id) throws Exception {
logger.info("+++++ router節點數據同步開始 +++++");
// 創建要同步節點數據實體對象
RouterConfig routerConfig = new RouterConfig();
// 定義APP對uri轉發配置map,以${pathToken}->/v+version+openuri爲key,
// 以clientChannel+apiid爲值
Map<String, String> routerMap = new HashMap<>();
// 從數據庫中查詢出需要同步的參數,包括
// 查詢API信息表
List<ApiInfo> apiInfoList = apiInfoMapper.queryApiInfoList();
// apiInfoList不爲空則從apiInfoList循環取出每個元素--ApiInfo的實體
if (apiInfoList != null) {
for (int i = 0; i < apiInfoList.size(); i++) {
ApiInfo apiInfo = apiInfoList.get(i);
// 再從apiInfo實體中取出availability校驗API是否廢棄
Integer availability = apiInfo.getAvailability();
// 如果 availability爲1代表API有效,從apiInfo中取出version和OPenUri
if (availability != null && availability == 1) {
String version = apiInfo.getVersion();
String openUri = apiInfo.getOpenuri();
// 拼接routerMap的key和值
String routerMapKey = GatewayConstant.PATHTOKEN+ GatewayConstant.PATH_SEPARATOR
+ GatewayConstant.VSEPARATOR + version
+ GatewayConstant.PATH_SEPARATOR + openUri;
// 從apiInfo中取出apiid拼接value
String apiId = apiInfo.getApiId();
String routerMapValue = GatewayConstant.CLIENTCHANNEL+ apiId;
// 給routerMap賦值
routerMap.put(routerMapKey, routerMapValue);
}
}
} else {
logger.error("同步router節點時發現API信息列表信息有誤:API信息列表爲空");
throw new TeslaApplicationException("API信息列表不能爲空");
}
routerConfig.setRouterMap(routerMap);
// 同步之前需要把子節點強行設置爲true,插件名不變,type依舊爲default
routerConfig.setAvailable(true);
routerConfig.setId(id);
routerConfig.setType("default");
// 調用單節點同步方法執行同步操作
routerConfigFunctions.editRouterConfig(routerConfig, plugName, id);
logger.info("+++++ router節點數據同步結束 +++++");
}
/**
* outbound_channel一鍵同步方法
* @param plugName 大插件名
*/
public void outboundChannelDataSync(String plugName) throws Exception {
logger.info("+++++ outbound_channel節點數據同步開始 +++++");
// 創建要同步節點數據實體對象
// channel
OutboundChannelParent outboundChannelParent = new OutboundChannelParent();
// connector
OutboundChannelConfig outboundChannelConfig = new OutboundChannelConfig();
// connectionConfig
ConnectionConfig connectionConfig = new ConnectionConfig();
// 查詢API信息表
List<ApiInfo> apiInfoList = apiInfoMapper.queryApiInfoList();
// 若apiInfoList不爲空則從apiInfoList循環取出每個元素--ApiInfo的實體
if (apiInfoList != null) {
for (int i = 0; i < apiInfoList.size(); i++) {
ApiInfo apiInfo = apiInfoList.get(i);
// 再從apiInfo實體中取出availability校驗API是否廢棄
Integer availability = apiInfo.getAvailability();
// 如果 availability爲1代表API有效
if (availability != null && availability == 1) {
// 先給channel賦默認值
OutboundChannelParent defaultOutboundChannelParent = outboundChannelParent.defaultOutboundChannelParent();
// 從apiInfo中取出apiid
String apiId = apiInfo.getApiId();
// 拼接clientChannelApiid
// set給defaultOutboundChannelParent的id
String clientChannelApiid = defaultOutboundChannelParent.getId() + apiId;
defaultOutboundChannelParent.setId(clientChannelApiid);
// 給connector賦默認值
OutboundChannelConfig defaultOutboundChannelConfig = outboundChannelConfig.defaultOutboundChannelConfig();
// 給connector賦默認值中的connectionConfig賦默認值
ConnectionConfig defaultConnectionConfig = connectionConfig.defaultConnectionConfig();
// defaultConnectionConfig
// set給defaultOutboundChannelConfig
defaultOutboundChannelConfig.setConnectionConfig(defaultConnectionConfig);
// 從apiInfo中取出Inneruri,set給defaultOutboundChannelConfig
String innerUri = apiInfo.getInneruri();
defaultOutboundChannelConfig.setUri(innerUri);
// 拼接clientConnectorApiid
// set給defaultOutboundChannelConfig的id
String clientConnectorApiid = defaultOutboundChannelConfig.getId() + apiId;
defaultOutboundChannelConfig.setId(clientConnectorApiid);
// 將初始化好的defaultOutboundChannelParent、defaultOutboundChannelConfig調用創建渠道和連接器的方法
outboundChannelConfigFunction.createOutboundChannelParent(plugName, defaultOutboundChannelParent,defaultOutboundChannelConfig);
}
}
} else {
logger.error("同步outbound_channel節點時發現API信息列表信息有誤:API信息列表爲空");
throw new TeslaApplicationException("API信息列表不能爲空");
}
logger.info("+++++ outbound_channel節點數據同步結束 +++++");
}
}
這個類的代碼稍微有點長,關鍵是看裏面對從mysql同步到zk的邏輯和方法,暫時就先寫這麼多,之後會把zktree的截圖放上去的。