Zookeeper 기반의 서비스 등록 및 서비스 검색

머리말

SOA를 사용하든 마이크로서비스 아키텍처를 사용하든 서비스 등록 및 서비스 검색 구성 요소를 사용해야 합니다. 처음 Dubbo를 접했을 때 서비스 등록/발견과 Zookeeper의 역할에 대해 항상 혼란스러웠는데 이제는 분산 시스템에 대한 이해가 깊지 않고 Dubbo와 Zookeeper의 작동 원리도 명확하지 않은 것 같습니다. .

이번 글에서는 Zookeeper를 기반으로 서비스 등록 및 서비스 검색 기능을 구현해보겠습니다.저와 같은 헷갈림이 있으신 분들은 이 글을 통해 다른 컴포넌트들이 Zookeeper를 어떻게 등록 센터로 활용하는지 이해하실 수 있기를 바랍니다.

성명

기사에 제공된 코드는 참고용일 뿐이며 기본 지식이 부족한 개발자가 서비스 등록 및 서비스 검색의 개념을 더 잘 이해할 수 있도록 돕기 위한 것입니다. 이러한 코드는 실제 응용 프로그램에 사용하기 위한 것이 아닙니다 .

사전 지식

서비스 등록 및 검색

SOA 또는 마이크로서비스 아키텍처에서는 다수의 서비스가 존재하고 상호 호출이 가능하기 때문에 이러한 서비스를 보다 효과적으로 관리하려면 일반적으로 중앙에서 관리할 수 있는 통합 장소, 즉 등록 센터를 도입해야 합니다. 등록센터는 가장 기본적인 기능으로 서비스 등록/발견입니다.

  • 서비스 등록 : 다른 서비스나 클라이언트가 서비스를 검색하고 사용할 수 있도록 서비스 인스턴스의 메타데이터(예: IP 주소, 포트 번호, 상태 등)를 등록 센터에 등록합니다.
  • 서비스 검색 : 서비스가 다른 서비스를 호출해야 하는 경우 정적 구성을 사용하는 것은 불가능하며, 이때 레지스트리로 이동하여 사용 가능한 서비스 인스턴스를 가져와 호출할 수 있습니다.

사육사

Zookeeper는 전통적인 분산 조정 서비스로, Hadoop 클러스터 조정 및 관리, Kafka의 리더 선택 조정 등의 조정자로 더 많이 사용됩니다.

일부 구성 요소가 이를 레지스트리로 사용하는 이유는 무엇입니까? 나는 다음과 같은 몇 가지 이유가 있다고 생각합니다.

  1. Zookeeper는 분산 시스템에서 더 강력한 일관성과 신뢰성을 가지므로 각 서비스의 등록 정보의 일관성을 보장할 수 있습니다.
  2. Zookeeper는 메모리를 사용하여 데이터를 저장하며 읽기 및 쓰기 성능이 뛰어납니다. 이는 클라이언트 요청에 신속하게 응답해야 하기 때문에 레지스트리에 매우 중요합니다.
  3. Zookeeper의 감시자 메커니즘을 사용하면 클라이언트가 지정된 노드의 변경 사항을 모니터링할 수 있습니다. 노드(레지스트리)가 변경되면 Zookeeper는 실시간 업데이트를 위해 다른 서비스에 알릴 수 있습니다.

작동 원리

다음 그림을 예로 들어 Dubbo가 Zookeeper를 사용하여 서비스 등록/검색을 실현하는 방법을 확인하세요.

여기에 이미지 설명을 삽입하세요

  1. 서비스 제공자는 /dubbo/com.foo.BarService/providers자신의 URL 주소를 디렉토리에 기록합니다.
  2. 서비스 소비자는 /dubbo/com.foo.BarService/providers디렉터리 아래의 공급자 URL 주소를 구독합니다. 그리고 /dubbo/com.foo.BarService/consumers디렉토리에 자신의 URL 주소를 쓰십시오.

여기의 디렉토리는 Zookeeper의 데이터 구조입니다. 원리는 매우 간단합니다. 본질적으로 서비스 제공자와 소비자는 계약에 따라 Zookeeper에서 데이터를 읽고 쓰며 동시에 Watcher 메커니즘, 임시 노드 및 신뢰성을 사용하여 효율적으로 작업을 수행합니다. 다음 기능을 구현하십시오.

  • 공급자 서비스에 정전 등의 비정상적인 다운타임이 발생하는 경우 등록 센터는 공급자 정보를 자동으로 삭제할 수 있습니다.
  • 등록 센터가 다시 시작되면 등록 데이터 및 구독 요청을 자동으로 복원할 수 있습니다.

구현 프로세스

등록 센터

다음으로 Zookeeper의 Java API를 통한 서비스 등록/검색만 포함하는 등록 센터를 구현합니다. 코드는 다음과 같습니다.

public class RegistrationCenter {
    
    

    // 连接信息
    private String connectString = "192.168.10.11:2181,192.168.10.11:2182,192.168.10.11:2183";

    // 超时时间
    private int sessionTimeOut = 30000;

    private final String ROOT_PATH = "/servers";

    private ZooKeeper client;

    public RegistrationCenter() {
    
    
        this(null);
    }

    public RegistrationCenter(Consumer<List<String>> consumer) {
    
    
        try {
    
    
            getConnection(null == consumer ? null : watchedEvent -> {
    
    
                //监听服务器地址的上下线
                if (watchedEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged) {
    
    
                    try {
    
    
                        consumer.accept(subServers());
                    } catch (Exception e) {
    
    
                        e.printStackTrace();
                    }
                }
            });
            Stat stat = client.exists(ROOT_PATH, false);
            if (stat == null) {
    
    
                //创建根节点
                client.create(ROOT_PATH, ROOT_PATH.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * @param serverName 将服务器注册到zk集群时,所需的服务名称
     * @param metadata   服务元数据
     * @throws Exception
     */
    public void doRegister(String serverName, Metadata metadata) throws Exception {
    
    


        /**
         * ZooDefs.Ids.OPEN_ACL_UNSAFE: 此权限表示允许所有人访问该节点(服务器)
         * CreateMode.EPHEMERAL_SEQUENTIAL: 由于服务器是动态上下线的,上线后存在,下线后不存在,所以是临时节点
         * 而服务器一般都是有序号的,所以是临时、有序的节点.
         */
        String node = client.create(ROOT_PATH + "/" + serverName, metadata.toString().getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

        System.out.println(serverName + " 已经上线");
    }


    /**
     * 发现/订阅服务
     */
    public List<String> subServers() throws InterruptedException, KeeperException {
    
    
        List<String> zkChildren = client.getChildren(ROOT_PATH, true);
        List<String> servers = new ArrayList<>();
        zkChildren.forEach(node -> {
    
    
            //拼接服务完整信息
            try {
    
    
                byte[] data = client.getData(ROOT_PATH + "/" + node, false, null);
                servers.add(new String(data));
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        });
        return servers;
    }

    private void getConnection(Watcher watcher) throws IOException {
    
    
        this.client = new ZooKeeper(connectString, sessionTimeOut, watcher);
    }

    /**
     * 服务元数据
     */
    public static class Metadata {
    
    
        public Metadata() {
    
    
        }

        public Metadata(String ip, int port) {
    
    
            this.ip = ip;
            this.port = port;
        }

        private String ip;

        private int port;

        public String getIp() {
    
    
            return ip;
        }

        public void setIp(String ip) {
    
    
            this.ip = ip;
        }

        public int getPort() {
    
    
            return port;
        }

        public void setPort(int port) {
    
    
            this.port = port;
        }

        @Override
        public String toString() {
    
    
            return "{" + "ip='" + ip + '\'' + ", port=" + port + '}';
        }
    }
}

이 클래스에는 서비스 등록과 구독이라는 doRegister()가지 핵심 메소드가 있습니다 .subServers()

  • doRegister()주로 Zookeeper에서 임시 노드 데이터를 생성하기 위한 것으로 임시 노드의 장점은 정전 등 서비스가 비정상적으로 종료되는 경우 자동으로 노드가 삭제된다는 점입니다.
  • subServers()Zookeeper로 가서 모든 서비스 제공자의 정보를 읽고, 노드의 상태를 모니터링하며, 노드 생성, 삭제, 업데이트 등의 이벤트가 발생하면 서비스 제공자의 정보를 다시 획득하여 데이터를 업데이트할 수 있습니다. 실시간.

지금까지 간단한 등록센터는 완성되었지만, 물론 성숙한 등록센터가 구현되려면 로드 밸런싱, 고가용성 및 내결함성, 서비스 거버넌스, 라우팅 제어 등의 기능도 고려해야 하는데, 이는 불가능하다. 여기에서 확장하세요.

서비스 등록

등록 센터가 있는 경우 서비스 제공업체에서 전화하여 doRegister() 등록할 수 있으며 코드는 다음과 같습니다.

public class ProviderServer {
    
    
    public static void main(String[] args) throws Exception {
    
    
        RegistrationCenter registrationCenter = new RegistrationCenter();
        registrationCenter.doRegister("provider", new RegistrationCenter.Metadata("127.0.0.1", 8080));
        Thread.sleep(Long.MAX_VALUE);
    }
}

서비스 발견

마찬가지로 서비스 소비자는 subServers()서비스 공급자를 찾기 위해 전화를 걸 수 있으며, 서비스 공급자가 변경되면 소비자에게 알림이 전송됩니다. 코드는 아래와 같이 표시됩니다.

public class ConsumerServer {
    
    
    public static void main(String[] args) throws Exception {
    
    
        RegistrationCenter registrationCenter = new RegistrationCenter(newServers -> {
    
    
            System.out.println("服务更新了..."+newServers);
        });
        List<String> servers = registrationCenter.subServers();
        System.out.println(servers);
        Thread.sleep(Long.MAX_VALUE);
    }
}

요약하다

서비스 등록 및 서비스 검색 기능은 분산 시스템의 서비스 관리 및 통신 문제를 해결하기 위해 설계되었으며, 로드 밸런싱, 상태 모니터링, 서비스 거버넌스 및 라우팅 제어 등의 기능을 지속적으로 개발 및 개선한 후 등록 센터가 됩니다. 서비스 레지스트리 및 서비스 검색은 기존 코드를 수동으로 구성하고 수정하지 않고도 새로운 서비스 인스턴스를 시스템에 동적으로 추가할 수 있으므로 시스템 탄력성과 확장성을 달성하는 데 도움이 됩니다.

추천

출처blog.csdn.net/qq_28314431/article/details/132581610