• 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

  • 相关阅读:
    c语言中sscanf()与sprintf()的使用
    oracle 12c 创建能用navicat 远程登录的帐号
    ubuntu14.0安装 oracle instant client(oracle 12c)
    【转】Xilinx FPGA ChipScope的ICON/ILA/VIO核使用
    ChipScope——ISE软件的抓波形操作
    【转】彻底掌握Quartus——基础篇
    千兆以太网(4):发送——ODDR原语和Wireshark抓包工具
    千兆以太网(3):发送——组建以太网心跳包
    千兆以太网(2):接收——包校验和数据筛选
    千兆以太网(1):接收——RGMII协议和IDDR原语
  • 原文地址:https://www.cnblogs.com/xugf/p/10223555.html
Copyright © 2020-2023  润新知