• 基于ZooKeeper实现简单的服务注册于发现


    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/Shaun_luotao/article/details/87098482
    分布式系统离不开服务的注册与发现。这里我采用ZooKeeper实现一个简单的服务注册与发现的例子。

    首先简单介绍一下Zookeeper的基本特性。
    Zookeeper 实现了一个类似于文件系统的树状结构:

    Zookeeper的数据结构

    层次化的目录结构,命名符合常规文件系统规范。
    每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识。
    节点Znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点)。
    客户端应用可以在节点上设置监视器。
    Zookeeper的节点类型

    永久节点(除非手动删除,节点永远存在)
    永久有序节点(按照创建顺序会为每个节点末尾带上一个序号如:root-1)
    瞬时节点(创建客户端与 Zookeeper 保持连接时节点存在,断开时则删除并会有相应的通知)
    瞬时有序节点(在瞬时节点的基础上加上了顺序)
    思路:
    既然Zookeeper能够在节点上保存一定的数据信息,那么我们在服务注册的时候,创建服务端的节点,并将服务的Ip地址和端口保存在Zookeeper的节点中,服务调用的时候,根据负载算法,获取到Zookeeper节点的节点数据,拿到IP地址和端口,根据IP地址和端口就能调用相应的服务。

    创建三个新的SpringBoot工程,引入Zookeeper开发包。其中两个工程演示服务端,一个工程演示客户端。

    服务端工程
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.shaun.zookeeper</groupId>
    <artifactId>serviceproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>serviceproject</name>
    <description>Demo project for Spring Boot</description>

    <properties>
    <java.version>1.8</java.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>

    <dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.5</version>
    </dependency>
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    </project>

    在启动类中开启使用 @ServletComponentScan注解后,直接通过@WebListener 开启监听,启动项目的时候调用ServiceRegister,将服务端的物理地址信息注册到Zookeeper。

    启动类

    @SpringBootApplication
    @ComponentScan(basePackages = "com.shaun.*")
    @ServletComponentScan
    public class ServiceprojectApplication {

    public static void main(String[] args) {
    SpringApplication.run(ServiceprojectApplication.class, args);
    }

    }

    InitListener 监听器

    @WebListener
    public class InitListener implements ServletContextListener{

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

    String hostAddress = InetAddress.getLocalHost().getHostAddress();

    String po = properties.getProperty("port");
    int port = Integer.parseInt(po);

    ServiceRegister.reister(hostAddress,port);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

    ServiceRegister服务注册的具体实现。

    public class ServiceRegister {

    private static final String BASE_SERVICE = "/zookeeper";

    private static final String SERVICE_NAME = "/server";

    public static void reister(String address,int port){
    String path = BASE_SERVICE+SERVICE_NAME;
    try {

    ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181",5000, null);

    Stat exists = zooKeeper.exists(BASE_SERVICE+SERVICE_NAME,false);

    if(exists == null){
    zooKeeper.create(path,"".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }
    String server_path = address+":"+port;

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

    System.out.println("产品服务注册成功!");
    } catch (Exception e) {
    e.printStackTrace();
    }

    }
    }

    为什么创建znode的时候是创建临时有序节点,因为服务关闭的时候,节点会自动删除。服务端开启节点监听会刷新服务列表。

    新建一个测试controller,返回服务端的地址信息。方便测试客户端调用服务端。

    @RestController
    @RequestMapping("/product")
    public class ProductController {

    @RequestMapping("/getProduct")
    public Map getProduct(@RequestBody Map entity){
    Map map = new HashMap();
    map.put("id",entity.get("id"));
    map.put("name","你好");
    return map;
    }
    }

    客户端工程
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.shaun.zookeeper</groupId>
    <artifactId>clientproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>clientproject</name>
    <description>Demo project for Spring Boot</description>

    <properties>
    <java.version>1.8</java.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>

    <dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.5</version>
    </dependency>
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    </project>


    和服务端类似,不过客户端在启动监听器中需获取Zookeeper中注册的服务列表。将服务列表保存到负载均衡类中。调用服务的时候,从负载均衡类中获取注册的服务端的物理地址信息。

    启动监听程序
    开启了节点监听,当节点事件类型watchedEvent为NodeChildrenChanged 并且节点路径和服务端注册的节点路径一致时,说明有服务关闭,更新获取到的服务节点。将服务节点信息保存到负载均衡类中。

    @WebListener
    public class InitListener implements ServletContextListener{

    private static final String BASE_SERVICE = "/zookeeper";

    private static final String SERVICE_NAME = "/server";

    private ZooKeeper zooKeeper;

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent){
    init();
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent){

    }

    public void init(){
    try {
    zooKeeper = new ZooKeeper("127.0.0.1:2181",5000, (watchedEvent -> {
    if(watchedEvent.getType()== Watcher.Event.EventType.NodeChildrenChanged && watchedEvent.getPath().equals(BASE_SERVICE+SERVICE_NAME)){
    updateServerList();
    }
    }));
    updateServerList();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public void updateServerList(){
    List <String> newServiceList = new ArrayList<>();
    try {
    List <String> children = zooKeeper.getChildren(BASE_SERVICE+SERVICE_NAME,true);
    for(String subNode:children){
    byte [] data = zooKeeper.getData(BASE_SERVICE + SERVICE_NAME +"/" + subNode,false,null);
    String host = new String(data,"utf-8");
    System.out.println("host:"+host);
    newServiceList.add(host);
    }
    LoadBalanse.SERVICE_LIST = newServiceList;
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    public abstract class LoadBalanse {
    public volatile static List<String> SERVICE_LIST;
    public abstract String choseServiceHost();
    }

    public class RandomLoadBalance extends LoadBalanse {

    @Override
    public String choseServiceHost() {
    String result = "";

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

    调用示例:

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

    private RestTemplate restTemplate = new RestTemplate();

    private LoadBalanse loadBalanse = new RandomLoadBalance();

    @RequestMapping("/getOrder")
    public Object getProduct(@RequestBody Map entity){
    String host = loadBalanse.choseServiceHost();
    Map res = restTemplate.postForObject("http://"+host+"/product/getProduct",entity,Map.class);
    res.put("host",host);
    return res;
    }

    @RequestMapping("/test")
    public Map test(){
    return null;
    }
    }


    启动服务端之前,我们看一下node节点,只有永久节点/zookeeper/server

    启动成功后,输出服务注册成功!

    再查下一下znode节点。发现child节点创建成功,说明服务注册成功。


    启动客户端工程,测试/order/getOrder


    根据测试结果,客户端调用8092或者8091完全是随机的。

    本文只简单演示基于Zookeeper做服务注册与发现,Zookeeper还有很多特性未深入研究,示例工程后续会传到GitHub。
    ————————————————
    版权声明:本文为CSDN博主「Shaun_luotao」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/Shaun_luotao/article/details/87098482

  • 相关阅读:
    字符串hash+回文树——hdu6599
    数位dp——牛客多校H
    线段树区间离散化——牛客多校E
    最小表示法——牛客多校第七场A
    后缀自动机求多串LCS——spojlcs2
    后缀自动机求LCS——spoj-LCS
    后缀自动机求字典序第k小的串——p3975
    后缀自动机模板——不同子串个数p2408
    同构图+思维构造——牛客多校第六场E
    封装,调用函数,以及参数化
  • 原文地址:https://www.cnblogs.com/zhoading/p/11968685.html
Copyright © 2020-2023  润新知