Zookeeperについて詳細に学んだ後、Zookeeperに基づいてサービス登録とディスカバリーを実装するために自分でそれを行うことができます。最初に、注文サービス(OrderService)と製品サービス(ProductService)の2つのマイクロサービスを確立する必要があります。サービスを注文するときは、製品サービスを呼び出す必要があります。
まず、製品サービス(ProductService)がZookeeperに登録される方法を見てみましょう。最初に、SpringBootプロジェクトをビルドします。
サービスが開始したら、Zookeeperにサービスを登録する必要があります。ここでは、サーブレットAPIを使用する必要があります。ServletContextListenerServletContextオブジェクトのライフサイクルを監視できるインターフェースは、実際にはWebアプリケーションのライフサイクルを監視しています。
サーブレットコンテナがWebアプリケーションを起動または終了すると、ServletContextListenerによって処理されるServletContextEventイベントがトリガーされます。ServletContextEventイベントを処理する2つのメソッドは、ServletContextListenerインターフェースで定義されています。
@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) {
}
}
ここでは、web.xmlからannotationsまで、Webの3つの主要コンポーネントで簡単に紹介した@WebListener
注釈を使用しますが、ここでは、SpringBootスタートアップクラスに注釈を追加することを忘れないでください@ServletComponentScan
もちろん、別の方法を使用することもできます。次のよう@WebListener
@ServletComponentScan
に、SpringBootスタートアップクラスで直接アノテーションを使用する@Bean
こともできます。
次に、ZookeeperのInitListenerクラスに登録する方法を詳しく見てみましょう。ここでは、最初にプロジェクトの構成ファイルapplication.propertiesを取得しました。取得方法については、Javaでのファイルパスの紹介が多すぎます方法
もちろん、アノテーション@ComponentをSpring環境に追加し、@ Valueを使用して構成ファイル内のサービスポート番号を取得することもできます。
上記では、ポート番号だけでなくIPアドレスも含めて、IP + PORTをZookeeperに登録する必要があります。ここでは、IP + PORT情報を格納するために以前に導入したノードについて考える必要があります。ここで、前に紹介した3つのクライアントのいずれか、ここでは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プロジェクトも作成する必要があります。同様に、サーブレットAPIのServletContextListenerインターフェイスを使用する必要があります
製品サービスとは異なり、製品サービスのInitListenerクラスは、サーブレットコンテナーの開始時に製品サービスのIP + PORT情報をZookeeperに登録し、注文サービスのInitListenerは、サーブレットコンテナーの開始時に開始します。 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上のノードの変更を監視する必要があります。これは、製品サービスがダウンした場合や新しい製品サービスの場合に、時間内に情報を取得できる必要があるためです。キュレーターモニタリングの概要については、Zookeeperクライアント(5)-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に基づくサービスの登録と検出が基本的に完了します。次に、注文サービスでコントローラーも提供します。コントローラーは、製品サービスのコントローラーを呼び出します。ここでは、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 2つのサービスサービス、ZooInspectorを使用して、Zookeeperのノード情報を次のように表示できます。
次に、注文サービスを開始し、常にブラウザで更新しています。さまざまな製品サービスがランダムに呼び出されることがわかります