1.声明
当前内容主要用于本人学习和复习,当前内容主要为使用zookeeper模拟服务发布的测试和思考
由于zookeeper本身就是一个分布式服务协调的分布式应用,所以一般用来做为服务注册中心,于是本人思考开始模拟实现服务发布
- 提供一个支付服务节点
- 在支付节点下面直接注册可以使用的ip地址和对应的url
2.开始实现
1.提供的支付服务节点为payService
2.提供服务的ip地址为:192.168.1.101、192.168.1.102、192.168.1.103
3.提供的url为:/pay
4.可以通过:ip+url都可访问任何一个支付服务
具体使用java实现方式如下
/**
* @description 模拟实现支付服务的地址发布
* @author hy
* @date 2020-06-06
*/
public class PayServiceZooKeeperTest {
private static String payService = "/payService";
private static String payApi = "/pay";
// 模拟发布支付服务的地址:192.168.1.101,192.168.1.102,192.168.1.103
private static String[] payServiceAddresses = { "192.168.1.101", "192.168.1.102", "192.168.1.103" };
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
ZooKeeper zk = new ZooKeeper("192.168.1.102:2181", 10000, null);
// 判断当前的根节点是否存在
Stat exists = zk.exists(payService, false);
if (exists == null) {
// 不存在直接创建支付
zk.create(payService, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
// 开始创建当前节点下面的各种地址
for (int i = 0; i < payServiceAddresses.length; i++) {
zk.create(payService + "/" + payServiceAddresses[i], payApi.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
}
// 开始拉取当前的支付服务
System.out.println("=============开始拉取服务==================");
List<String> childrens = zk.getChildren(payService, false);
for (String ip : childrens) {
System.out.println("ip==>" + ip);
String payNode = payService + "/" + ip;
Stat stat = zk.exists(payNode, false);
String uri = new String(zk.getData(payService + "/" + ip, false, stat), "utf-8");
System.out.println("访问的服务地址为====>" + payNode + uri);
}
zk.close();
}
}
结果:
3.分析上面实现具有的问题
1.由于上面使用的是持久化节点,那么如果某个服务注册后掉线或者服务不可用怎么办(个人觉得这里应该是临时节点)
2.服务注册,应该是程序启动完毕后就应该注册到支付服务节点上面,并创建当前地址ip的支付节点,那么服务可能出现多次注册的情况
3.支付服务的拉取任何人都可以操作,那么如何保证不是恶意的注册(本人觉得应该对当前的支付服务节点开启访问控制列表acl)
4.服务可能存在更新的情况,掉线后的上线更新,或者直接在运行时更新
5.当前的注册节点就是当前的ip地址,是否主动暴露了ip(本人觉得应该直接创建序列节点)
分析发现:
- 服务发布时应该发布临时的序列的节点
- 如果有必要为访问服务的节点添加访问控制acl
- 直接将当前的ip地址+uri放入节点的数据中
4.修改并实现
/**
* @description 模拟实现支付服务的地址发布
* @author hy
* @date 2020-06-06
*/
public class PayServiceProvider {
private static String payService = "/payService";
private static String payApi = "/pay";
// 模拟发布支付服务的地址:192.168.1.101,192.168.1.102,192.168.1.103
private static String[] payServiceAddresses = { "192.168.1.101", "192.168.1.102", "192.168.1.103" };
private static String nodeName = "pay";
private static String schame = "digest";
private static String idPwd = "user:123456";
public static void main(String[] args) throws IOException, KeeperException, InterruptedException, NoSuchAlgorithmException {
List<ACL> acls = createACL();
ZooKeeper zk = new ZooKeeper("192.168.1.102:2181", 10000, null);
// 添加认证信息
synchronized (PayServiceProvider.class) {
zk.addAuthInfo(schame, idPwd.getBytes());
}
System.out.println("添加用户认证信息");
// 判断当前的根节点是否存在
Stat exists = zk.exists(payService, false);
if (exists == null) {
// 不存在直接创建支付
// 创建访问控制
zk.create(payService, new byte[0], acls, CreateMode.EPHEMERAL);
exists = zk.exists(payService, false);
System.out.println("创建节点并设置ACL成功!");
/* zk.setACL(payService, acl, aclVersion) */
}
List<ACL> needACLs = zk.getACL(payService, exists);
for (int i = 0; i < needACLs.size(); i++) {
System.out.println(needACLs.get(i).getId());
}
Thread.sleep(2000);
// 开始创建当前节点下面的各种地址
for (int i = 0; i < payServiceAddresses.length; i++) {
// 问题出现在这里,无法认证当前的用户信息
// KeeperErrorCode = NoChildrenForEphemerals for /payService/pay (解决完NoAuth for 错误又出现)
// 说明当前的节点中,临时节点是不能拥有子节点的所以当前的payService只能时持久化节点
zk.create(payService + "/" + nodeName, (payServiceAddresses[i] + payApi).getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL,
CreateMode.EPHEMERAL_SEQUENTIAL);
}
System.in.read();
zk.close();
}
public static List<ACL> createACL() throws NoSuchAlgorithmException {
ArrayList<ACL> acls = new ArrayList<>();
ACL acl = new ACL();
// 创建acl的时候注意当前的用户名密码必须使用加密形式:否则报错:KeeperErrorCode = NoAuth for (就是用户名密码认证失败)
acl.setId(new Id(schame, DigestAuthenticationProvider.generateDigest(idPwd)));
acl.setPerms(Perms.ALL);
acls.add(acl);
return acls;
}
}
期间出现的问题:
KeeperErrorCode = NoAuth for XXX
,表示当前的用户名密码不匹配(本人是当前的添加的acl的用户名密码没有使用加密(DigestAuthenticationProvider.generateDigest(idPwd))
导致的)
测试结果:
KeeperErrorCode = NoChildrenForEphemerals for /payService/pay
,通过命令行操作发现:
当前的错误就是,临时节点中是不能又子节点(节点一旦声明为临时节点那么就不能又有任何子节点)
直接修改,payService节点为持久化节点
即可
zk.create(payService, new byte[0], acls, CreateMode.PERSISTENT);
再次测试
测试成功,根节点为持久化节点,并且添加访问控制,其内部节点为临时的序列的节点
5.创建服务拉取者
由于当前的payService已经设置了访问控制列表所以这里的服务拉取者必须要添加认证信息
/**
* @description 支付服务的消费者(专门拉取服务)
* @author hy
* @date 2020-06-06
*/
public class PayServiceConsumer {
private static String payService = "/payService";
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
ZooKeeper zk = new ZooKeeper("192.168.1.102:2181", 10000, null);
// 添加认证信息
zk.addAuthInfo("digest", "user:123456".getBytes());
// 开始拉取当前的支付服务
System.out.println("=============开始拉取服务==================");
List<String> childrens = zk.getChildren(payService, false);
for (String node : childrens) {
System.out.println("node==>" + node);
String payNode = payService +"/" + node;
Stat stat = zk.exists(payNode, false);
String uri = new String(zk.getData(payNode, false, stat), "utf-8");
System.out.println("访问的服务地址为====>" + uri);
}
System.in.read();
zk.close();
}
}
启动测试后的结果:
此时拉取成功
当当前的服务宕机时(即session关闭时),此时的拉取结果
此时由于当前的节点中的数据是临时的节点所以,拉取不到服务
6.总结
1.通过分析发现,如果在分布式服务中一般会使用根节点为持久化节点,采用发布的服务为临时的序列的节点,保证服务宕机后消失
2.一般分布式服务中都会添加认证,只有具有认证信息的用户才允许拉取该节点下面的服务
3.注意给节点添加acl控制访问列表的时候,需要采用加密的方式,并且是用户名:密码,还需要设定当前用户的权限
4.临时节点是不能有任何子节点的,会报错的
以上纯属个人见解,如有问题请联本人!