v1.3
(启动器依旧使用1.2 1.3版本在启动服务版本上尚未做出大变动 主要是增加了方便学习的功能)
更新事项 以下更新均在非阻塞模块进行更新,阻塞模块可供读者自己尝试
-
使用注解方式 改造一下启动类 不分成这么多启动类直接
- 首先新创建了几个包 下面进行阐述下具体作用
1.功能调用模块
2.注解类 自定义两个注解
-
自定义注解代码
-
客户端启动注解
package annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //注解 通过此注解可以判断当前是哪一个版本 选择调用哪个版本的客户端启动器 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface RpcClientBootStrap { String version(); }
-
服务端启动注解
package annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //注解 通过此注解可以判断当前是哪一个版本 选择调用哪个版本的服务端启动器 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface RpcServerBootStrap { String version(); }
-
-
服务调用和实际进行判断的业务逻辑,下面代码就放客户端的,两者同理
-
服务调用
package service.call; import service.bootstrap.ClientBootStrap; import java.io.IOException; //通用启动类 将启动的逻辑藏在ClientBootStrap中 public class ClientCall { public static void main(String[] args) throws IOException { ClientBootStrap.start(); } }
-
服务启动真正逻辑
package service.bootstrap; import annotation.RpcClientBootStrap; import consumer.bootstrap.NIOConsumerBootStrap10; import consumer.bootstrap.NIOConsumerBootStrap11; import consumer.bootstrap.NIOConsumerBootStrap12; import java.io.IOException; //之后启动直接在这边启动根据 在注解中配置对应的版本号 将相应的操作封装到之后的操作中即可 这样很方便 就是每次咱加一个启动器还得改下switch //比如说这里的version 1.2 就是v1.2版本的启动器 @RpcClientBootStrap(version = "1.1") public class ClientBootStrap { public static void start() throws IOException{ //获取当前的注解上的版本然后去调用相应的远端方法 反射的方法 //当前客户端启动器class对象 Class<ClientBootStrap> currentClientBootStrapClass = ClientBootStrap.class; RpcClientBootStrap annotation = currentClientBootStrapClass.getAnnotation(RpcClientBootStrap.class); String currentVersion = annotation.version(); //根据注解获得的版本进行判断是哪个版本 然后进行启动 switch (currentVersion) { case "1.0": NIOConsumerBootStrap10.main(null); break; case "1.1": NIOConsumerBootStrap11.main(null); break; case "1.2": NIOConsumerBootStrap12.main(null); break; default: System.out.println("太着急了兄弟,这个版本还没出呢!要不你给我提个PR"); } } }
-
- 首先新创建了几个包 下面进行阐述下具体作用
-
利用ZK实现调用方法软负载均衡
-
对zookeeper节点注册进行修改,节点名就是对应的地址,对应的数据就是被调用的次数
-
zookeeper服务注册端修改
//因为这个地区属于一个临界区 可能会发生线程不安全问题 所以进行上🔒 synchronized (ZkServiceRegistry.class) { Stat exists = zooKeeper.exists("/service", false); if (exists ==null) { zooKeeper.create("/service", "".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT ); } //v1.3进行软负载均衡修改 exists = zooKeeper.exists("/service/"+RpcServiceName, false); if (exists ==null) { zooKeeper.create("/service/"+RpcServiceName, "".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT ); } } String date = hostname+":"+port; //权限目前都设置为全放开 创建方式均为持久化 //修改 v1.3 数据为访问次数 应该是可以进行加减的 然后发现服务端取的是最低的然后再进行+1 zooKeeper.create("/service/"+RpcServiceName+"/"+date, "0".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT ); }
-
zookeeper服务发现端进行修改
//v1.3更新 使用软负载 //到对应节点中获取下面的子节点 List<String> children = zooKeeper.getChildren(prePath, false, null); if (children.isEmpty()) { System.out.println("当前没有服务器提供该服务 请联系工作人员"); } //进行排序 根据每个节点的访问次数 从小到大进行排序 然后选用最小的 Collections.sort(children, new Comparator<String>() { @Override public int compare(String o1, String o2) { try { return Integer.valueOf(new String(zooKeeper.getData(prePath+"/"+o1,false,null))) - Integer.valueOf(new String(zooKeeper.getData(prePath+"/"+o2,false,null))); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return 0; } }); //对选用的对象的访问量加1 todo 暂时不知道怎么让数据直接+1 // 获取节点数据+1,然后修改对应节点, String chooseNode = children.get(0); byte[] data = zooKeeper.getData(prePath+"/"+chooseNode, false, null); int visitCount = Integer.valueOf(new String(data)); ++visitCount; //version参数用于指定节点的数据版本,表名本次更新操作是针对指定的数据版本进行的。 cas zooKeeper.setData(prePath+"/"+chooseNode,String.valueOf(visitCount).getBytes(StandardCharsets.UTF_8),-1); String address = new String(children.get(0)); return address; }
-
效果:成功实现软负载均衡
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t9ACIwCo-1653195899582)(/upload/2022/05/image-2c12437604b847aba60d45a933a2100d.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2vnFJIL7-1653195899584)(/upload/2022/05/image-e2d47e835e7e402db732e8d4684300a0.png)]
-
-
-
问题 服务端下线的时候,希望能把zk对应的节点也进行删除,不能让用户每次都自己去到zk中删吧
-
当前想到的办法是提取出一个方法来 在启用之前进行调用,删除节点!!有力扣刷题那味了☺
package init; import constants.RpcConstants; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import java.io.IOException; import java.util.List; //zookeeper 进行一键初始化的方法 public class ZK { private static ZooKeeper zooKeeper; public static void init() throws IOException, InterruptedException, KeeperException { zooKeeper = new ZooKeeper(RpcConstants.ZOOKEEPER_ADDRESS, RpcConstants.ZOOKEEPER_SESSION_TIMEOUT, new Watcher() { @Override public void process(WatchedEvent watchedEvent) { } }); //如果存在就删 不存在就不删 if (zooKeeper.exists("/service",false)!=null) { //内部得实现递归删除 deleteAll("/service"); } zooKeeper.close(); } //实现循环递归删除的方法 private static void deleteAll(String prePath) throws InterruptedException, KeeperException { List<String> children = zooKeeper.getChildren(prePath, false); if (!children.isEmpty()) { for (String child : children) { deleteAll(prePath+"/"+child); } } zooKeeper.delete(prePath,-1); } }
-