1. 程式人生 > >Zookeeper學習筆記:簡單註冊中心

Zookeeper學習筆記:簡單註冊中心

 

zookeeper可以作為微服務註冊中心,spring cloud也提供了zookeeper註冊中心的支援。

 

本文介紹如何實現一個簡單的zookeeper註冊中心,主要的實現方式:

n個服務提供者對外提供http介面獲取資料,這些服務提供者把自己的主機、埠資訊註冊到zookeeper的某個節點上面;

當服務提供者宕機或者服務不可用時,zookeeper節點會刪除該提供者的資訊;

消費者也連線zookeeper獲取可以使用的服務提供者(並且會持續監聽節點,節點變化時也會實時更新本地的服務提供者列表),然後發起http請求呼叫服務介面獲取資料

 

下面簡單介紹一下實現方式

 

1、UserService服務

後臺使用者管理服務web工程,使用spring mvc註解方式開發,不提供使用者操作介面。提供http介面根據使用者名稱獲取使用者資訊,入參使用者名稱字串,響應為json型別,註冊的服務名為UserService

 

2、UserAdminService服務

使用者管理系統web工程,使用spring mvc註解方式開發,這是一個給使用者使用的系統。提供登入頁面、和首頁,登入操作呼叫UserService提供的http介面根據使用者輸入的使用者名稱獲取使用者資訊,然後比對密碼是否正確,服務名為UserAdminService

 

3、修改UserService服務工程

1)application.properties配置
 1 ## zookeeper叢集地址
 2 # zk.url=192.168.0.28:2181,192.168.0.29:2181,192.168.0.30:2181
 3 zk.url=127.0.0.1:2181
 4 
 5 ## 註冊服務使用的二級路徑名
 6 service.name=UserService
 7 ## IP根據實際部署的伺服器修改
 8 server.hostname=127.0.0.1
 9 ## 埠根據實際監聽埠修改
10 server.port=8080
11
12 ## 註冊服務時會建立 13 ## /services/${service.name}/${server.hostname}:${server.port} 14 ## 這樣一個臨時節點,值為${service.name}

 

三個服務部署在一臺伺服器上,分別監聽7070、8080、9090,可以使用server.port引數配置

 

2)ZookeeperRegister類

編寫ZookeeperRegister類,根據zk叢集地址、服務名、IP、埠等配置註冊服務到zookeeper叢集

  a)   讀取application.properties檔案,載入配置

  b)   zkRegist方法會在依賴注入完成後由spring容器呼叫

  c)   獲取zk叢集地址、服務名、IP、埠等配置

  d)   建立ZooKeeper物件,來註冊服務,也就是在zookeeper建立一個臨時節點

  e)   節點規則是: /services/${service.name}/${server.hostname}:${server.port},值為: ${service.name}

  f)    此類物件會被spring容器管理

 

3)ZookeeperRegister類程式碼
 1 @Component
 2 @PropertySource("classpath:application.properties")
 3 public class ZookeeperRegister {
 4 
 5     private ZooKeeper zkClient;
 6 
 7     @Autowired
 8     private Environment env;
 9 
10     @PostConstruct
11     public void zkRegist() throws IOException, KeeperException,
12             InterruptedException {
13 
14         // 獲取配置資訊
15         String zkUrl = env.getProperty("zk.url");
16         String serviceName = env.getProperty("service.name");
17         String hostName = env.getProperty("server.hostname");
18         int port = Integer.parseInt(env.getProperty("server.port"));
19 
20         // 建立ZooKeeper物件
21         // 超時時間為2分
22         zkClient = new ZooKeeper(zkUrl, 120000, new Watcher() {
23             @Override
24             public void process(WatchedEvent event) {
25 
26             }
27         });
28 
29         // path = /services/${service.name}/${server.hostname}:${server.port}
30         // value = ${service.name}
31 
32         Stat exists = zkClient.exists("/services", false);
33         if (exists == null) {
34             zkClient.create("/services", "services".getBytes(),
35                     Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
36             exists = zkClient.exists("/services/UserService", false);
37             if (exists == null) {
38                 zkClient.create("/services/" + serviceName,
39                         serviceName.getBytes(), Ids.OPEN_ACL_UNSAFE,
40                         CreateMode.PERSISTENT);
41             }
42         }
43         // 建立znode節點
44         // 臨時節點
45         zkClient.create("/services/" + serviceName + "/" + hostName + ":"
46                 + port, serviceName.getBytes(), Ids.OPEN_ACL_UNSAFE,
47                 CreateMode.EPHEMERAL);
48     }
49 }
View Code

 

4、修改UserAdminService服務工程

1)application.properties配置
1 ## zookeeper叢集地址
2 # zk.url=192.168.0.28:2181,192.168.0.29:2181,192.168.0.30:2181
3 zk.url=127.0.0.1:2181

 

2)UserServiceImpl類

UserServiceImpl類實現Watcher和UserService介面,使用@Service標註,由spring容器管理

 

  a)    初始化方法中建立ZooKeeper物件,連線到zk叢集

  b)    當ZooKeeper中服務提供者節點發生變化時呼叫getProviders方法重新獲取可用的服務提供者

  c)    getUserByUsername方法先從當前可用的提供者集合中隨機獲取一個提供者,使用httpclient傳送請求獲取使用者資料返回給呼叫者

 

把UserService注入到Controller即可使用

 

3)UserServiceImpl類程式碼
  1 @Service
  2 @PropertySource("classpath:application.properties")
  3 public class UserServiceImpl implements UserService, Watcher {
  4 
  5     private static Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
  6 
  7     private static final String SERVICE_URL = 
  8                         "/UserService/user/getUserByUsername?username=";
  9     private static final String SERVICE_REGIST_PATH = "/services/UserService";
 10 
 11     @Autowired
 12     private Environment env;
 13 
 14     private ZooKeeper zkClient;
 15     private List<String> providers = new ArrayList<String>();
 16 
 17     @PostConstruct
 18     public void init() {
 19         String zkUrl = env.getProperty("zk.url");
 20         try {
 21             zkClient = new ZooKeeper(zkUrl, 120000, this);
 22         } catch (IOException e) {
 23             throw new RuntimeException(e.getMessage(), e);
 24         }
 25     }
 26 
 27     @Override
 28     public User getUserByUsername(String username) {
 29 
 30         User user = null;
 31 
 32         // 獲取一個可以使用的服務提供者
 33         String provider = getProvider();
 34 
 35         String url = "http://" + provider + SERVICE_URL + username;
 36 
 37         // 傳送請求獲取資料
 38         CloseableHttpClient http = HttpClients.createDefault();
 39 
 40         HttpGet httpGet = null;
 41         CloseableHttpResponse response = null;
 42         InputStream in = null;
 43 
 44         try {
 45             // 構建httpget
 46             httpGet = new HttpGet(new URI(url));
 47 
 48             // 傳送請求獲取響應
 49             response = http.execute(httpGet);
 50             HttpEntity entity = response.getEntity();
 51 
 52             // 獲取輸入流和資料字串
 53             in = entity.getContent();
 54             String json = IOUtils.toString(in, "utf-8");
 55 
 56             log.info(String.format("請求: %s, 響應: %s", url, json));
 57 
 58             // 解析json字串為user物件
 59             user = json2User(json);
 60 
 61         } catch (URISyntaxException e) {
 62             e.printStackTrace();
 63         } catch (ClientProtocolException e) {
 64             e.printStackTrace();
 65         } catch (IOException e) {
 66             e.printStackTrace();
 67         } finally {
 68             // 關閉輸入流和響應物件
 69             IOUtils.closeQuietly(in);
 70             IOUtils.closeQuietly(response);
 71         }
 72         return user;
 73     }
 74 
 75     @Override
 76     public void process(WatchedEvent event) {
 77         getProviders();
 78     }
 79 
 80     private void getProviders() {
 81         try {
 82             this.providers = zkClient.getChildren(SERVICE_REGIST_PATH, true);
 83             log.info(String.format("可用服務提供者: %s", this.providers));
 84         } catch (KeeperException e) {
 85             e.printStackTrace();
 86         } catch (InterruptedException e) {
 87             e.printStackTrace();
 88         }
 89     }
 90 
 91     private String getProvider() {
 92         int size = this.providers.size();
 93         Random r = new Random();
 94         int i = r.nextInt(size);
 95         String provider = this.providers.get(i);
 96         log.info(String.format("可用服務提供者: %s, 隨機獲取提供者 [%s], 序號 [%s]",
 97                 this.providers, provider, i));
 98         return provider;
 99     }
100 
101     private User json2User(String json) {
102         ObjectMapper mapper = new ObjectMapper();
103         User user = null;
104         try {
105             user = mapper.readValue(json, User.class);
106         } catch (JsonParseException e) {
107             e.printStackTrace();
108         } catch (JsonMappingException e) {
109             e.printStackTrace();
110         } catch (IOException e) {
111             e.printStackTrace();
112         }
113         return user;
114     }
115 }
View Code

 

5、部署測試

1)部署啟動zookeeper

 

2)服務部署

服務提供者和消費者是部署在同一臺機器上面的,監聽埠不同

 

部署三個UserService服務,修改server.port引數,分別監聽7070、8080、9090

部署UserAdminService服務,我測試的時候和其中一個服務提供者部署在了同一個tomcat裡面,監聽9090

 

其中7070和8080的tomcat使用cmd命令列啟動,9090的tomcat在eclipse裡面啟動便於觀察服務消費者的日誌

 

啟動了三個tomcat之後可以看看zookeeper的變化

 

另外,在UserAdminService服務的日誌中可以看到當前可以使用的服務提供者列表

 

訪問一下http://localhost:9090/UserAdminWeb/login登入,可以看到後臺程式獲取到了一個可以使用的服務提供者,然後發起了http請求查詢使用者資訊

 

 

 

當某個服務提供者掛掉之後,可以看到消費者的日誌,已經重新獲取了提供者列表

 

6、存在的一些問題

a)    服務提供者突然掛掉之後,zookeeper並不能馬上刪除該提供者的znode資訊,所以在服務消費者這邊還需要做一些優化,即發起請求獲取使用者資訊時如果出現問題,應該從可用服務提供者列表刪除該提供者資訊

b)    服務消費者使用的httpclient發起請求,如果每次獲取使用者資訊都去重新建立tcp連線,效率很低,所以需要使用連線池技術管理用於發起http請求的tcp連線

 

7、原始碼下載

https://files.cnblogs.com/files/xugf/Zookeeper%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E6%BA%90%E7%A0%81.zip