热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

Netty+ZooKeeper实现简单的服务注册与发现

服务注册和发现一直是分布式的核心组件。本文介绍了借助ZooKeeper做注册中心,如何实现一个简单的服务注册和发现。,需要的朋友可以参考下

一. 背景

最近的一个项目:我们的系统接收到上游系统的派单任务后,会推送到指定的门店的相关设备,并进行相应的业务处理。

二. Netty 的使用

在接收到派单任务之后,通过 Netty 推送到指定门店相关的设备。在我们的系统中 Netty 实现了消息推送、长连接以及心跳机制。

2.1 Netty Server 端:

每个 Netty 服务端通过 ConcurrentHashMap 保存了客户端的 clientId 以及它连接的 SocketChannel。

服务器端向客户端发送消息时,只要获取 clientId 对应的 SocketChannel,往 SocketChannel 里写入相应的 message 即可。

EventLoopGroup boss = new NioEventLoopGroup(1);
  EventLoopGroup worker = new NioEventLoopGroup();
  ServerBootstrap bootstrap = new ServerBootstrap();
  bootstrap.group(boss, worker)
    .channel(NioServerSocketChannel.class)
    .option(ChannelOption.SO_BACKLOG, 128)
    .option(ChannelOption.TCP_NODELAY, true)
    .childOption(ChannelOption.SO_KEEPALIVE, true)
    .childHandler(new ChannelInitializer() {
     @Override
     protected void initChannel(Channel channel) throws Exception {
      ChannelPipeline p = channel.pipeline();
      p.addLast(new MessageEncoder());
      p.addLast(new MessageDecoder());
      p.addLast(new PushServerHandler());
     }
    });
  ChannelFuture future = bootstrap.bind(host,port).sync();
  if (future.isSuccess()) {
   logger.info("server start...");
  }

2.2 Netty Client 端:

客户端用于接收服务端的消息,随即进行业务处理。客户端还有心跳机制,它通过 IdleEvent 事件定时向服务端放送 Ping 消息以此来检测 SocketChannel 是否中断。

public PushClientBootstrap(String host, int port) throws InterruptedException {
  this.host = host;
  this.port = port;
  start(host,port);
 }
 private void start(String host, int port) throws InterruptedException {
  bootstrap = new Bootstrap();
  bootstrap.channel(NioSocketChannel.class)
    .option(ChannelOption.SO_KEEPALIVE, true)
    .group(workGroup)
    .remoteAddress(host, port)
    .handler(new ChannelInitializer(){
     @Override
     protected void initChannel(Channel channel) throws Exception {
      ChannelPipeline p = channel.pipeline();
      p.addLast(new IdleStateHandler(20, 10, 0)); // IdleStateHandler 用于检测心跳
      p.addLast(new MessageDecoder());
      p.addLast(new MessageEncoder());
      p.addLast(new PushClientHandler());
     }
    });
  doConnect(port, host);
 }
 /**
  * 建立连接,并且可以实现自动重连.
  * @param port port.
  * @param host host.
  * @throws InterruptedException InterruptedException.
  */
 private void doConnect(int port, String host) throws InterruptedException {
  if (socketChannel != null && socketChannel.isActive()) {
   return;
  }
  final int portCOnnect= port;
  final String hostCOnnect= host;
  ChannelFuture future = bootstrap.connect(host, port);
  future.addListener(new ChannelFutureListener() {
   @Override
   public void operationComplete(ChannelFuture futureListener) throws Exception {
    if (futureListener.isSuccess()) {
     socketChannel = (SocketChannel) futureListener.channel();
     logger.info("Connect to server successfully!");
    } else {
     logger.info("Failed to connect to server, try connect after 10s");
     futureListener.channel().eventLoop().schedule(new Runnable() {
      @Override
      public void run() {
       try {
        doConnect(portConnect, hostConnect);
       } catch (InterruptedException e) {
        e.printStackTrace();
       }
      }
     }, 10, TimeUnit.SECONDS);
    }
   }
  }).sync();
 }

三. 借助 ZooKeeper 实现简单的服务注册与发现

3.1 服务注册

服务注册本质上是为了解耦服务提供者和服务消费者。服务注册是一个高可用强一致性的服务发现存储仓库,主要用来存储服务的api和地址对应关系。为了高可用,服务注册中心一般为一个集群,并且能够保证分布式一致性。目前常用的有 ZooKeeper、Etcd 等等。

在我们项目中采用了 ZooKeeper 实现服务注册。

public class ServiceRegistry {
 private static final Logger logger = LoggerFactory.getLogger(ServiceRegistry.class);
 private CountDownLatch latch = new CountDownLatch(1);
 private String registryAddress;
 public ServiceRegistry(String registryAddress) {
  this.registryAddress = registryAddress;
 }
 public void register(String data) {
  if (data != null) {
   ZooKeeper zk = connectServer();
   if (zk != null) {
    createNode(zk, data);
   }
  }
 }
 /**
  * 连接 zookeeper 服务器
  * @return
  */
 private ZooKeeper connectServer() {
  ZooKeeper zk = null;
  try {
   zk = new ZooKeeper(registryAddress, Constants.ZK_SESSION_TIMEOUT, new Watcher() {
    @Override
    public void process(WatchedEvent event) {
     if (event.getState() == Event.KeeperState.SyncConnected) {
      latch.countDown();
     }
    }
   });
   latch.await();
  } catch (IOException | InterruptedException e) {
   logger.error("", e);
  }
  return zk;
 }
 /**
  * 创建节点
  * @param zk
  * @param data
  */
 private void createNode(ZooKeeper zk, String data) {
  try {
   byte[] bytes = data.getBytes();
   String path = zk.create(Constants.ZK_DATA_PATH, bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
   logger.debug("create zookeeper node ({} => {})", path, data);
  } catch (KeeperException | InterruptedException e) {
   logger.error("", e);
  }
 }
}

有了服务注册,在 Netty 服务端启动之后,将 Netty 服务端的 ip 和 port 注册到 ZooKeeper。

EventLoopGroup boss = new NioEventLoopGroup(1);
  EventLoopGroup worker = new NioEventLoopGroup();
  ServerBootstrap bootstrap = new ServerBootstrap();
  bootstrap.group(boss, worker)
    .channel(NioServerSocketChannel.class)
    .option(ChannelOption.SO_BACKLOG, 128)
    .option(ChannelOption.TCP_NODELAY, true)
    .childOption(ChannelOption.SO_KEEPALIVE, true)
    .childHandler(new ChannelInitializer() {
     @Override
     protected void initChannel(Channel channel) throws Exception {
      ChannelPipeline p = channel.pipeline();
      p.addLast(new MessageEncoder());
      p.addLast(new MessageDecoder());
      p.addLast(new PushServerHandler());
     }
    });
  ChannelFuture future = bootstrap.bind(host,port).sync();
  if (future.isSuccess()) {
   logger.info("server start...");
  }
  if (serviceRegistry != null) {
   serviceRegistry.register(host + ":" + port);
  }

3.2 服务发现

这里我们采用的是客户端的服务发现,即服务发现机制由客户端实现。

客户端在和服务端建立连接之前,通过查询注册中心的方式来获取服务端的地址。如果存在有多个 Netty 服务端的话,可以做服务的负载均衡。在我们的项目中只采用了简单的随机法进行负载。

public class ServiceDiscovery {
 private static final Logger logger = LoggerFactory.getLogger(ServiceDiscovery.class);
 private CountDownLatch latch = new CountDownLatch(1);
 private volatile List serviceAddressList = new ArrayList<>();
 private String registryAddress; // 注册中心的地址
 public ServiceDiscovery(String registryAddress) {
  this.registryAddress = registryAddress;
  ZooKeeper zk = connectServer();
  if (zk != null) {
   watchNode(zk);
  }
 }
 /**
  * 通过服务发现,获取服务提供方的地址
  * @return
  */
 public String discover() {
  String data = null;
  int size = serviceAddressList.size();
  if (size > 0) {
   if (size == 1) { //只有一个服务提供方
    data = serviceAddressList.get(0);
    logger.info("unique service address : {}", data);
   } else {   //使用随机分配法。简单的负载均衡法
    data = serviceAddressList.get(ThreadLocalRandom.current().nextInt(size));
    logger.info("choose an address : {}", data);
   }
  }
  return data;
 }
 /**
  * 连接 zookeeper
  * @return
  */
 private ZooKeeper connectServer() {
  ZooKeeper zk = null;
  try {
   zk = new ZooKeeper(registryAddress, Constants.ZK_SESSION_TIMEOUT, new Watcher() {
    @Override
    public void process(WatchedEvent event) {
     if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
      latch.countDown();
     }
    }
   });
   latch.await();
  } catch (IOException | InterruptedException e) {
   logger.error("", e);
  }
  return zk;
 }
 /**
  * 获取服务地址列表
  * @param zk
  */
 private void watchNode(final ZooKeeper zk) {
  try {
   //获取子节点列表
   List nodeList = zk.getChildren(Constants.ZK_REGISTRY_PATH, new Watcher() {
    @Override
    public void process(WatchedEvent event) {
     if (event.getType() == Event.EventType.NodeChildrenChanged) {
      //发生子节点变化时再次调用此方法更新服务地址
      watchNode(zk);
     }
    }
   });
   List dataList = new ArrayList<>();
   for (String node : nodeList) {
    byte[] bytes = zk.getData(Constants.ZK_REGISTRY_PATH + "/" + node, false, null);
    dataList.add(new String(bytes));
   }
   logger.debug("node data: {}", dataList);
   this.serviceAddressList = dataList;
  } catch (KeeperException | InterruptedException e) {
   logger.error("", e);
  }
 }
}

Netty 客户端启动之后,通过服务发现获取 Netty 服务端的 ip 和 port。

/**
  * 支持通过服务发现来获取 Socket 服务端的 host、port
  * @param discoveryAddress
  * @throws InterruptedException
  */
 public PushClientBootstrap(String discoveryAddress) throws InterruptedException {

  serviceDiscovery = new ServiceDiscovery(discoveryAddress);
  serverAddress = serviceDiscovery.discover();

  if (serverAddress!=null) {
   String[] array = serverAddress.split(":");
   if (array!=null && array.length==2) {

    String host = array[0];
    int port = Integer.parseInt(array[1]);

    start(host,port);
   }
  }
 }

四. 总结

服务注册和发现一直是分布式的核心组件。本文介绍了借助 ZooKeeper 做注册中心,如何实现一个简单的服务注册和发现。其实,注册中心的选择有很多,例如 Etcd、Eureka 等等。选择符合我们业务需求的才是最重要的。

以上所述是小编给大家介绍的Netty + ZooKeeper 实现简单的服务注册与发现,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!


推荐阅读
  • ZooKeeper 学习
    前言相信大家对ZooKeeper应该不算陌生。但是你真的了解ZooKeeper是个什么东西吗?如果别人面试官让你给他讲讲ZooKeeper是个什么东西, ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • Netty源代码分析服务器端启动ServerBootstrap初始化
    本文主要分析了Netty源代码中服务器端启动的过程,包括ServerBootstrap的初始化和相关参数的设置。通过分析NioEventLoopGroup、NioServerSocketChannel、ChannelOption.SO_BACKLOG等关键组件和选项的作用,深入理解Netty服务器端的启动过程。同时,还介绍了LoggingHandler的作用和使用方法,帮助读者更好地理解Netty源代码。 ... [详细]
  • 本文总结了初学者在使用dubbo设计架构过程中遇到的问题,并提供了相应的解决方法。问题包括传输字节流限制、分布式事务、序列化、多点部署、zk端口冲突、服务失败请求3次机制以及启动时检查。通过解决这些问题,初学者能够更好地理解和应用dubbo设计架构。 ... [详细]
  • [翻译]微服务设计模式5. 服务发现服务端服务发现
    服务之间需要互相调用,在单体架构中,服务之间的互相调用直接通过编程语言层面的方法调用就搞定了。在传统的分布式应用的部署中,服务地 ... [详细]
  • LVS-DR直接路由实现负载均衡示例
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 域名解析系统DNS
    文章目录前言一、域名系统概述二、因特网的域名结构三、域名服务器1.根域名服务器2.顶级域名服务器(TLD,top-leveldomain)3.权威(Authoritative)域名 ... [详细]
  • 什么是网关服务器初学linux服务器开发时,我们的服务器是很简单的,只需要一个程序完成与客户端的连接,接收客户端数据,数据处理,向客户端发送数据。但是在处理量很大的情况下,一 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • Centos下安装memcached+memcached教程
    本文介绍了在Centos下安装memcached和使用memcached的教程,详细解释了memcached的工作原理,包括缓存数据和对象、减少数据库读取次数、提高网站速度等。同时,还对memcached的快速和高效率进行了解释,与传统的文件型数据库相比,memcached作为一个内存型数据库,具有更高的读取速度。 ... [详细]
  • 解决Sharepoint 2013运行状况分析出现的“一个或多个服务器未响应”问题的方法
    本文介绍了解决Sharepoint 2013运行状况分析中出现的“一个或多个服务器未响应”问题的方法。对于有高要求的客户来说,系统检测问题的存在是不可接受的。文章详细描述了解决该问题的步骤,包括删除服务器、处理分布式缓存留下的记录以及使用代码等方法。同时还提供了相关关键词和错误提示信息,以帮助读者更好地理解和解决该问题。 ... [详细]
  • ElasticSearch成功安装完毕。 测试数据添加出现{  error:{    root_cause ... [详细]
  • 本文内容皆为作者原创,如需转载,请注明出处:https:www.cnblogs.comxuexianqip13045462.html1.自定义分页器的拷贝及使用当我们需要使用 ... [详细]
  • Kylin 单节点安装
    软件环境Hadoop:2.7,3.1(sincev2.5)Hive:0.13-1.2.1HBase:1.1,2.0(sincev2.5)Spark(optional)2.3.0K ... [详细]
author-avatar
gaoyong0713
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有