基于Zookeeper实现服务注册与发现

我们在详细了解了Zookeeper之后,我们就可以自己动手来实现一个基于Zookeeper实现的服务注册与发现,首先我们就需要建立两个微服务,一个订单服务(OrderService),一个产品服务(ProductService),其中订单服务时需要调用产品服务的。


首先我们就先来看一看产品服务(ProductService)是如何注册到Zookeeper上的,首先我们就来新来建立一个SpringBoot的项目
在这里插入图片描述

在该服务启动的时候我们就需要将该服务给注册到Zookeeper上去,这里我们就需要借助Servlet API中的ServletContextListener接口,它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期。


当Servlet容器启动或终止Web应用时,会触发ServletContextEvent事件,该事件由ServletContextListener 来处理。在ServletContextListener 接口中定义了处理ServletContextEvent 事件的两个方法。
在这里插入图片描述

@WebListener
public class InitListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        try {
            Properties properties =  new Properties();
            properties.load(InitListener.class.getClassLoader().getResourceAsStream("application.properties"));

            String hostAddress = InetAddress.getLocalHost().getHostAddress();
            int port = Integer.valueOf(properties.getProperty("server.port"));
            ServiceRegister.register(hostAddress,port);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}

这里我们使用了@WebListener 注解,这个我们曾在Web的三大组件的简单使用,从web.xml到注解中简单介绍过,但是这里我们不要忘记了在SpringBoot的启动类上,还需要加上@ServletComponentScan注解
在这里插入图片描述
在这里插入图片描述


当然我们还可以使用另外一种方式,我们不使用@WebListener @ServletComponentScan注解也是可以的,直接在SpringBoot的启动类中,使用@Bean亦可,如下:
在这里插入图片描述



接下来我们来仔细看一下InitListener类中,是如何将其注册到Zookeeper上的,这里首先是获取到了项目中的配置文件application.properties,至于如何获取,在Java获取resources下的文件路径 介绍过多种方式


当然在我们还可以将其添上注解@Component,添加到Spring环境中,然后我们使用@Value获取配置文件中的服务端口号了
在这里插入图片描述
在这里插入图片描述


在上述不仅获取了端口号,还获取到了IP地址,我们就要将IP+PORT给注册到Zookeeper上去,这里我们肯定想到的就是之前介绍的节点存储IP+PORT信息嘛,这里我们就可以使用我们之前介绍的三个客户端中的任意一种,这里我们就选择Curator,该客户端比原生的客户端的优点,我们之前已经详细介绍过了,这里我们就需要引入相关的依赖了

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.12</version>
    <exclusions>
        <exclusion>
            <artifactId>slf4j-log4j12</artifactId>
            <groupId>org.slf4j</groupId>
        </exclusion>
    </exclusions>
</dependency>

<!--对zookeeper的底层api的一些封装-->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>4.3.0</version>
</dependency>
<!--封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式Barrier-->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.3.0</version>
</dependency>

这里稍微需要注意的就是我们需要Zookeeper中的slf4j,因为这里和会SpringBoot中的slf4j发送版本冲突,然后就是非常简单的将信息注册到Zookeeper的节点上了,这里如果我们使用的是原生客户端,那我们就需要更多额外的操作了,比如一层层地判断父节点是否存在,若不存在则创建父节点,还需要在连接客户的的时候使用CountDownLatch及Watch进行等待成功连接

public class ServiceRegister {

    private static final String CONNENT_ADDR = "127.0.0.1:2181";
    private static final String BASE_SERVICES = "/services";
    private static final String SERVICE_NAME = "/products";

    public static void register(String address, int port) throws Exception {
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(CONNENT_ADDR)
                .connectionTimeoutMs(5000)  //连接超时时间
                .sessionTimeoutMs(5000)     //会话超时时间
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        curatorFramework.start();

        String path = BASE_SERVICES + SERVICE_NAME;
        String server_path = address + ":" + port;

        curatorFramework.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
                .forPath(path + "/child", server_path.getBytes());
    }
}

接下来我们几乎就完成了产品服务的工作,我们可以改变application.properties中的端口号,启动多个产品服务,其产品服务向外提供的接口如下,就是简单返回该服务的端口号
在这里插入图片描述




在完成了产品服务的注册,接下来肯定就是订单服务的发现啦,首先同时我们也是需要新建一个SpringBoot项目的,同样的我们还是需要借助Servlet API中的ServletContextListener接口
在这里插入图片描述
在这里插入图片描述

与产品服务不同的是,产品服务中的InitListener类是为了在当Servlet容器启动时,将产品服务的IP+PORT信息注册到Zookeeper上去,而订单服务中的InitListener是为了在Servlet容器启动时,将Zookeeper中注册的产品服务信息拉取下来,以供订单服务来调用

public class InitListener implements ServletContextListener {

    private static final String CONNENT_ADDR = "127.0.0.1:2181";
    private static final String BASE_SERVICES = "/services";
    private static final String SERVICE_NAME = "/products";

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(CONNENT_ADDR)
                .connectionTimeoutMs(5000)  //连接超时时间
                .sessionTimeoutMs(5000)     //会话超时时间
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        curatorFramework.start();

        List<String> newServerList = new ArrayList<>();
        String path = BASE_SERVICES + SERVICE_NAME;
        final PathChildrenCache cache = new PathChildrenCache(curatorFramework, path, true);
        try {
            cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
            cache.getListenable().addListener(new PathChildrenCacheListener() {
                //监听子节点的变化
                @Override
                public void childEvent(CuratorFramework cf, PathChildrenCacheEvent event) throws Exception {
                    List<String> list = curatorFramework.getChildren().forPath(path);
                    for (String subNode : list) {
                        byte[] bytes = curatorFramework.getData().forPath(path + "/" + subNode);
                        String host = new String(bytes, "utf-8");
                        newServerList.add(host);
                    }
                    LoadBalance.SERVICE_LIST = newServerList;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }
}

这里我们需要监听Zookeeper上节点的变化,因为万一有产品服务宕机或者新增了产品服务,我们需要能够及时获取到信息,有关Curator监听的相关介绍,可见Zookeeper客户端(五)—— Curator
在这里插入图片描述


我们在监听到节点的变化时,我们就会将其更新值LoadBalance类中,其中主要就是简单模拟下负载均衡,这里我们就是在订单服务调用产品服务时,在所有的产品服务中随机选取一个服务

public class LoadBalance {

    public volatile static List<String> SERVICE_LIST;

    public String choseService() {
        String result = "";
        if (!CollectionUtils.isEmpty(SERVICE_LIST)) {
            int index = new Random().nextInt(SERVICE_LIST.size());
            result = SERVICE_LIST.get(index);
        }
        return result;
    }
}

这里我们基于Zookeeper的服务注册与发现就基本完成了,然后我们在订单服务中也提供一个Controller,该Controller会去调用产品服务的Controller,这里借助了RestTemplate,如下:

@RestController
@RequestMapping("/order")
public class OrderController {

    @Resource
    private RestTemplate restTemplate;

    private LoadBalance loadBalance = new LoadBalance();

    @RequestMapping("/getOrder")
    public Object getProduct(HttpServletRequest request) {
        String host = loadBalance.choseService();
        return restTemplate.getForObject("http://" + host + "/product/getProduct", Object.class);
    }
}

在这里插入图片描述




以上就完成了一个简单的服务注册与发现,这里我们来进行测试一下,我们可以改变产品服务(ProductService)中的applicat.properties文件中的端口号,来启动多个产品服务,这里我们启动了8080,8081两个服务服务,我们可以通过ZooInspector来查看下Zookeeper的节点信息,如下:
在这里插入图片描述在这里插入图片描述


然后我们在启动一个订单服务,在浏览器中不停的刷新,就可以看到会随机调用不同的产品服务
在这里插入图片描述
在这里插入图片描述

发布了286 篇原创文章 · 获赞 12 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/newbie0107/article/details/105442764