初见微服务之服务注册与发现
1.什么是服务注册与发现
微服务将传统的"巨石"应用拆分成一个一个的组件应用,每个组件应用提供特定的服务,可以是一个,也可以是多个,并且组件所含服务应该是可以动态扩展的,随着时间推移、系统进化,可任意拆分、合并。
组件化应用和颗粒化的服务,遍布在系统的各个角落,由不同的项目成员进行维护,微服务的核心是化整为零、各司其职,这就要求开发人员不得操作其业务或服务范围以外的数据模型等资源,只能通过接口的访问,使用某一服务。
由于服务的跨度很大(公司很大的情况下)、数量很多(数以百计甚至更多),为保障系统的正常运行,必然需要有一个中心化的组件完成对各个服务的整合,即将分散于各处的服务进行汇总,汇总的信息可以是提供服务的组件名称、地址、数量等,每个组件拥有一个监听设备,当本组件内的某个服务的状态变化时报告至中心化的组件进行状态的更新。服务的调用方在请求某项服务时首先到中心化组件获取可提供该项服务的组件信息(IP、端口等),通过默认或自定义的策略选择该服务的某一提供者进行访问,实现服务的调用。
随着分布式系统的发展,出现了越来越多的分布式调度系统,典型的有Zookeeper、Consul、etcd,在分布式系统中需要解决的一个问题即拜占庭将军问题,参考网站8btc(比特币咨询网站)《拜占庭将军问题深入探讨》http://www.8btc.com/baizhantingjiangjun
其中Zookeeper最为成熟,是Yahoo贡献给Apache基金会的一个顶级开源项目,基于Paxos算法,参考维基百科条目Paxos (computer science) https://en.wikipedia.org/wiki/Paxos_(computer_science),是Hadoop和HBase的重要组件。
下面一段是官网对于Zk的介绍。
ZooKeeper is a high-performance coordination service for distributed applications. It exposes common services - such as naming, configuration management, synchronization, and group services - in a simple interface so you don't have to write them from scratch. You can use it off-the-shelf to implement consensus, group management, leader election, and presence protocols. And you can build on it for your own, specific needs.
Zk是为分布式应用设计的一个高性能协调服务,提供了如下的通用服务,如命名、配置管理、通过锁和分组服务,封装成简单易用的接口而无需开发人员从头编写代码。可以拿来即用,应用的领域有取得共识、分组管理、领导者选举和协议呈现。还可以按需自定义功能。
Zk和etcd的比较如下表
语言 | 算法 | 存储方式 | 量级 | |
Zookeeper | Java | Paxos | 名称空间(文件系统) | 重 |
etcd | Go | Raft | K-V存储 | 轻 |
2.Zookeeper集群配置
2.1 Standalone单机部署
Zookeeper 的配置文件在 conf 目录下,这个目录下有 zoo_sample.cfg 和 log4j.properties,你需要做的就是将 zoo_sample.cfg 改名为 zoo.cfg,因为 Zookeeper 在启动时会找这个文件作为默认配置文件。下面详细介绍一下,这个配置文件中各个配置项的意义。
tickTime=2000 dataDir=D:/devtools/zookeeper-3.2.2/build clientPort=2181
tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。
dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
当这些配置项配置好后,你现在就可以启动 Zookeeper 了,启动后要检查 Zookeeper 是否已经在服务,可以通过 netstat – ano 命令查看是否有你配置的 clientPort 端口号在监听服务。
2.2 集群部署
Zk进行集群部署时,需保证集群的数量为奇数个,即3、5、7…。
Zookeeper 的集群模式除了上面的三个配置项还要增加下面几个配置项:
initLimit=5 syncLimit=2 server.1=192.168.211.1:2888:3888 server.2=192.168.211.2:2888:3888
initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 5*2000=10 秒
syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 2*2000=4 秒
server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。
除了修改 zoo.cfg 配置文件,集群模式下还要配置一个文件 myid,这个文件在 dataDir 目录下,这个文件里面就有一个数据就是 A 的值,Zookeeper 启动时会读取这个文件,拿到里面的数据与 zoo.cfg 里面的配置信息比较从而判断到底是那个 server。
3.Zookeeper服务注册
对Zookeeper新增、删除节点的操作可以通过zk提供的基础api进行操作,也可以选择一些框架,方便我们的使用,这里采用的是Curator。
服务url路径,举例如下:
myapp/service/user/info
myapp/service/configuration
存储为zk集群如下名称格式
myapp/service/
| user/info
| configuration
提供服务的节点信息:
public class ServiceProvider { // 提供服务的实例id private Integer instanceId; // 提供服务的实例ip private String ip; // 提供服务的实例端口号 private Integer port; // 实例注册时间 private Date registTime; 省略getter、setter方法 public byte[] toBytes(){ try { return JSONObject.fromObject(this).toString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new SystemException(); } } }
注册服务的方法如下:
public void register(String url, ServiceProvider node) { CuratorFramework client = CuratorFrameworkFactory.newClient(url, sessionTimeout, connectionTimeout, new ExponentialBackoffRetry(1000, 3)); client.getConnectionStateListenable().addListener(new ConnectionStateListener() { @Override public void stateChanged(CuratorFramework client, ConnectionState newState) { watcher.process(); } }); client.start();
logger.info("正在注册 zookeeper 服务节点:path=" + path + ", data=" + node);
client.create().creatingParentsIfNeeded().forPath(path, node.toBytes());
}
logger.info("正在注册 zookeeper 服务节点:path=" + path + ", data=" + node);
4.Zookeeper服务发现
服务发现的策略可以自定义,如随机分发、定比例分发、根据服务器状态分发等等,其中某种分发策略需要注册时提供额外的服务器负载信息等。
监听器如下:
public class ZookeeperGlobalCacheWatcher { public void watch(Closeable client, String path){ cache = new TreeCache((CuratorFramework)client, path); cache.getListenable().addListener(new TreeCacheListener() { @Override public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception { publishEvent(new CacheWatcherEvent(event)); } }); try { cache.start(); logger.info("启动treeCache watcher"); } catch (Exception e) { throw new SystemException(); } } }
服务发现:
public List<ServiceProvider> getServiceProvider() { if(treeCache == null){ // 监听器zookeeperGlobalCacheWatcher,监听zookeeper的状态 treeCache = (TreeCache)zookeeperGlobalCacheWatcher.getCache(); } List<ServiceProvider> providers = new ArrayList<ServiceProvider>(); try { // 数据转换 if(treeCache.getCurrentChildren(path) != null){ for(Entry<String, ChildData> item : treeCache.getCurrentChildren(path).entrySet()){ ChildData data = item.getValue(); if(data != null && data.getData() != null && data.getData().length > 0){ providers.add(JsonUtil.json2Object(new String(item.getValue().getData(), "UTF-8"), ZookeeperServiceProvider.class)); } } } } catch (Exception e) { throw new SystemException(); } return providers; }
服务注册和发现是整个微服务软件架构的核心,zookeeper成熟稳定的性能广受青睐,在系统技术选型和开发过程中,zookeeper都是占有这绝对的优势。