머리말
SOA를 사용하든 마이크로서비스 아키텍처를 사용하든 서비스 등록 및 서비스 검색 구성 요소를 사용해야 합니다. 처음 Dubbo를 접했을 때 서비스 등록/발견과 Zookeeper의 역할에 대해 항상 혼란스러웠는데 이제는 분산 시스템에 대한 이해가 깊지 않고 Dubbo와 Zookeeper의 작동 원리도 명확하지 않은 것 같습니다. .
이번 글에서는 Zookeeper를 기반으로 서비스 등록 및 서비스 검색 기능을 구현해보겠습니다.저와 같은 헷갈림이 있으신 분들은 이 글을 통해 다른 컴포넌트들이 Zookeeper를 어떻게 등록 센터로 활용하는지 이해하실 수 있기를 바랍니다.
성명
기사에 제공된 코드는 참고용일 뿐이며 기본 지식이 부족한 개발자가 서비스 등록 및 서비스 검색의 개념을 더 잘 이해할 수 있도록 돕기 위한 것입니다. 이러한 코드는 실제 응용 프로그램에 사용하기 위한 것이 아닙니다 .
사전 지식
서비스 등록 및 검색
SOA 또는 마이크로서비스 아키텍처에서는 다수의 서비스가 존재하고 상호 호출이 가능하기 때문에 이러한 서비스를 보다 효과적으로 관리하려면 일반적으로 중앙에서 관리할 수 있는 통합 장소, 즉 등록 센터를 도입해야 합니다. 등록센터는 가장 기본적인 기능으로 서비스 등록/발견입니다.
- 서비스 등록 : 다른 서비스나 클라이언트가 서비스를 검색하고 사용할 수 있도록 서비스 인스턴스의 메타데이터(예: IP 주소, 포트 번호, 상태 등)를 등록 센터에 등록합니다.
- 서비스 검색 : 서비스가 다른 서비스를 호출해야 하는 경우 정적 구성을 사용하는 것은 불가능하며, 이때 레지스트리로 이동하여 사용 가능한 서비스 인스턴스를 가져와 호출할 수 있습니다.
사육사
Zookeeper는 전통적인 분산 조정 서비스로, Hadoop 클러스터 조정 및 관리, Kafka의 리더 선택 조정 등의 조정자로 더 많이 사용됩니다.
일부 구성 요소가 이를 레지스트리로 사용하는 이유는 무엇입니까? 나는 다음과 같은 몇 가지 이유가 있다고 생각합니다.
- Zookeeper는 분산 시스템에서 더 강력한 일관성과 신뢰성을 가지므로 각 서비스의 등록 정보의 일관성을 보장할 수 있습니다.
- Zookeeper는 메모리를 사용하여 데이터를 저장하며 읽기 및 쓰기 성능이 뛰어납니다. 이는 클라이언트 요청에 신속하게 응답해야 하기 때문에 레지스트리에 매우 중요합니다.
- Zookeeper의 감시자 메커니즘을 사용하면 클라이언트가 지정된 노드의 변경 사항을 모니터링할 수 있습니다. 노드(레지스트리)가 변경되면 Zookeeper는 실시간 업데이트를 위해 다른 서비스에 알릴 수 있습니다.
작동 원리
다음 그림을 예로 들어 Dubbo가 Zookeeper를 사용하여 서비스 등록/검색을 실현하는 방법을 확인하세요.
- 서비스 제공자는
/dubbo/com.foo.BarService/providers
자신의 URL 주소를 디렉토리에 기록합니다. - 서비스 소비자는
/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);
}
}
요약하다
서비스 등록 및 서비스 검색 기능은 분산 시스템의 서비스 관리 및 통신 문제를 해결하기 위해 설계되었으며, 로드 밸런싱, 상태 모니터링, 서비스 거버넌스 및 라우팅 제어 등의 기능을 지속적으로 개발 및 개선한 후 등록 센터가 됩니다. 서비스 레지스트리 및 서비스 검색은 기존 코드를 수동으로 구성하고 수정하지 않고도 새로운 서비스 인스턴스를 시스템에 동적으로 추가할 수 있으므로 시스템 탄력성과 확장성을 달성하는 데 도움이 됩니다.