zookeeper使用篇(二):服务注册与发现+本地负载均衡

1、架构

SpringBoot+zookeeper

2、实现思路

  • 服务注册:服务启动后,向zk中注册一个临时有序节点(如:/server/order/child000000001),存储服务相关信息(如:服务的ip地址、端口号等)
  • 服务发现:服务启动后,根据所需监听服务信息,对应设置Watch进行节点持续监听,获取服务信息列表缓存本地,后续可采用一系列算法(如:本地轮询)进行服务调用,监听节点发生变化,则更新本地服务信息列表缓存
  • 服务注册需采用临时有序节点,服务宕机或者zk会话断开可自动删除坏节点,实现自动感知服务下线功能,有序则是为了方便后续实现负载均衡

3、实现步骤

/**
 * @author: Lanrriet
 * @date: 2020/7/23 6:11 下午
 * @description: 服务注册与发现
 */
public class ZkDiscovery {

    private static final String BASE_SERVICE = "/server";

    private ZooKeeper zooKeeper;

    private String monitorServer;

    @Value("${server.port}")
    private String port;

    @Autowired
    private ZookeeperConfig zkConfig;

    @PostConstruct
    public void init() {

        try {

            // 1.创建zk连接
            zooKeeper = new ZooKeeper(zkConfig.getAddress(), zkConfig.getTimeout(), getWatcher());

            // 2.服务注册
            if (!StringUtils.isEmpty(zkConfig.getAppName())) {

                //服务节点路径
                String serverPath = BASE_SERVICE + "/" + zkConfig.getAppName();

                if (zooKeeper.exists(serverPath, false) == null) {
                    zooKeeper.create(serverPath, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                }

                //服务节点信息
                String serverData = ServerUtil.getServerIp() + ":" + port;

                zooKeeper.create(serverPath + "/child", serverData.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

            } else {
                throw new RuntimeException("服务注册失败,请检查配置信息");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        // 3.监听服务不为空时,更新服务信息列表
        monitorServer = zkConfig.getMonitorServer();
        if (!StringUtils.isEmpty(monitorServer)) {
            for (String serverName : monitorServer.split(",")) {
                updateServerList(serverName);
            }
        }

    }

    // 根据配置生成节点监听器
    private Watcher getWatcher() {
        Watcher watcher = null;
        if (!StringUtils.isEmpty(zkConfig.getMonitorServer())) {
            String monitorServer = zkConfig.getMonitorServer();
            watcher = new Watcher() {
                @Override
                public void process(WatchedEvent watchedEvent) {

                    for (String serverName : monitorServer.split(",")) {

                        String serverPath = BASE_SERVICE + "/" + serverName;

                        if (watchedEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged && watchedEvent.getPath().equals(serverPath)) {
                            updateServerList(serverName);
                        }

                        //持续节点监听
                        try {
                            zooKeeper.exists(serverPath, getWatcher());
                            zooKeeper.getChildren(serverPath, getWatcher());
                        } catch (KeeperException e) {
                            e.printStackTrace();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }

                }
            };
        }
        return watcher;
    }

    // 更新服务信息列表
    private void updateServerList(String serverName) {

        String serverPath = BASE_SERVICE + "/" + serverName;
        try {
            if (zooKeeper.exists(serverPath, false) != null) {
                List<String> urlList = new ArrayList<>();

                List<String> children = zooKeeper.getChildren(serverPath, true);

                for (String subNode : children) {
                    byte[] data = zooKeeper.getData(serverPath + "/" + subNode, false, null);
                    String host = new String(data, "utf-8");
                    System.out.println("host:" + host);
                    urlList.add(host);
                }

                ServerData sd = new ServerData();
                sd.setAppName(serverName);
                sd.setBalance(0);
                sd.setUrlList(urlList);

                if (LoadBalanse.SERVER_LIST == null) {
                    LoadBalanse.SERVER_LIST = new ArrayList<ServerData>();
                }

                List<ServerData> sList = LoadBalanse.SERVER_LIST.stream().filter(s -> serverName.equals(s.getAppName())).collect(Collectors.toList());
                if (sList.size() > 0) {
                    ServerData preSd = sList.get(0);
                    preSd.setBalance(sd.getBalance());
                    preSd.setUrlList(sd.getUrlList());
                } else {
                    LoadBalanse.SERVER_LIST.add(sd);
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


}

public abstract class LoadBalanse {

    /**
     * 监听服务列表
     */
    public volatile static List<ServerData> SERVER_LIST;

    /**
     * 获取指定服务信息
     *
     * @param appName
     * @return
     */
    public abstract String getServerUrl(String appName);

}

 /**
  * 本地轮询
  */
public class RandomLoadBalance extends LoadBalanse {

    @Override
    public String getServerUrl(String appName) {
        String result = "";
        if (!CollectionUtils.isEmpty(SERVER_LIST)) {
            ServerData serverData = SERVER_LIST.stream().filter(sd -> appName.equals(sd.getAppName())).collect(Collectors.toList()).get(0);
            List<String> urlList = serverData.getUrlList();
            int balance = serverData.getBalance();
            int instanceSize = urlList.size();
            int serviceIndex = balance % instanceSize;
            serverData.setBalance(++balance);
            String server = urlList.get(serviceIndex);
            result = server;
            System.out.println("发现可用的" + appName + "服务:" + server);
        }
        return result;
    }

}
/**
 * @author: Lanrriet
 * @date: 2020/7/23 6:11 下午
 * @description: 自动装配,方便后续使用
 */
@Data
@ConfigurationProperties(prefix = "zookeeper")
public class ZookeeperConfig {

    /**
     * zk服务连接地址
     */
    private String address = "127.0.0.1:2181";

    /**
     * 连接超时
     */
    private int timeout = 5000;

    /**
     * 注册服务名称
     */
    private String appName;

    /**
     * 监控服务名称
     */
    private String monitorServer;

}

@Configuration
@EnableConfigurationProperties({ZookeeperConfig.class})
public class ZookeeperConfiguration {

    @Bean
    public ZkDiscovery zkDiscovery() {
        return new ZkDiscovery();
    }

}

4、具体使用

添加maven依赖
 <dependency>
      <groupId>com.demo</groupId>
      <artifactId>zookeeper-discovery</artifactId>
      <version>1.0</version>
 </dependency>
application.properties配置信息
#zk
zookeeper.address=127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
zookeeper.appname=user  # 注册节点名称
zookeeper.monitor-server=order,test # 监听服务名称
服务调用
@RequestMapping("/getOrder")
    public Object getProduct(@RequestBody Map entity) {
        String host = loadBalanse.getServerUrl("order");
        Map res = restTemplate.postForObject("http://" + host + "/order/getProduct", entity, Map.class);
        res.put("host", host);
        return res;
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值