在前面我们将Curator集成到了Spring Boot项目中,这一节我们看看Curator支持的应用场景之一:注册中心之服务的注册和发现
一、基本概念和思路
1.1 注册中心/服务注册/服务发现概念
1.1.1 注册中心
注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用。
在我们这里注册中心是不要我们进行编码的,ZooKeeper就是一个注册中心,我把服务的信息存放在注册中心上。
1.1.2 服务注册
就是将提供某个服务的模块信息(通常是这个服务的ip和端口)注册到1个公共的组件上去(比如: zookeeper/consul)。
1.1.3 服务发现
服务发现,简单的说,就是消费者去注册中心上查询注册了哪些服务,服务有多少个实例,哪些是健康的,哪些是不可用的。
1.2 Curator Service Discovery
CuratorService Discovery就是为了解决这个问题而生的,它对此抽象出了ServiceInstance、ServiceProvider、ServiceDiscovery三个接口,通过它我们可以很轻易的实现Service Discovery。
1.3 思路
对于Curator Service Discovery的使用大体的思路就是:
(1)引入依赖包:curator-x-discovery-server
(2)使用ServiceDiscovery进行服务的发现和注册。
(3)服务实体的类是ServiceInstance,构造ServiceInstance的类是ServiceInstanceBuilder。
二、Curator的服务注册和服务发现实战
说的再多,不如实际来操作下。
2.1 环境说明
(1)基于上一节《Spring Boot 使用 Curator 操作 ZooKeeper》进行往下编码。
(2)ZooKeeper版本:3.6.2
(3)Curator版本:5.1.0
(4)对于在普通的java项目中,服务发现和注册的代码是一样的,只要能获取到CuratorFramework就能搞定。
2.2 添加依赖
在pom.xml文件中,新增依赖curator-x-discovery,加上原先的依赖curator-recipes,那么现在核心的依赖就是:
<dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>5.1.0</version></dependency>
<dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-x-discovery</artifactId> <version>5.1.0</version></dependency>
2.3 服务详情信息
在构造服务实例ServiceInstance的时候,允许我们自己自定义一些其他的服务的信息,这里我们取名为ServiceDetail:
package com.kfit.springbootcuratordemo.register;
/** * 服务详情信息 * * @author 悟纤「公众号SpringBoot」 * @date 2021-03-25 * @slogan 大道至简 悟在天成 */public class ServiceDetail { //服务注册的根路径 public static final String REGISTER_ROOT_PATH = "/apps";
private String desc; private int weight;
@Override public String toString() { return "ServiceDetail{" + "desc='" + desc + '\'' + ", weight=" + weight + '}'; }
public ServiceDetail() { }
public ServiceDetail(String desc, int weight) { this.desc = desc; this.weight = weight; }
public String getDesc() { return desc; }
public void setDesc(String desc) { this.desc = desc; }
public int getWeight() { return weight; }
public void setWeight(int weight) { this.weight = weight; }}
2.4 服务注册和服务发现
这里对于服务注册和服务发现,我们放在一个类中进行管理,具体的代码如下:
package com.kfit.springbootcuratordemo.register;
import org.apache.curator.framework.CuratorFramework;import org.apache.curator.x.discovery.ServiceDiscovery;import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;import org.apache.curator.x.discovery.ServiceInstance;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.env.Environment;import org.springframework.stereotype.Service;
import java.net.InetAddress;import java.util.Collection;
/** * TODO * * @author 悟纤「公众号SpringBoot」 * @date 2021-03-25 * @slogan 大道至简 悟在天成 */@Servicepublic class RegisterService {
//环境信息类 @Autowired private Environment environment;
//Curator client @Autowired private CuratorFramework curatorFramework;
/** * 服务注册:将当前启动的服务信息(ip+port)进行注册到zk中。 */ public void register(){ try { //address.getHostAddress(): 192.168.0.106 InetAddress address = InetAddress.getLocalHost(); ServiceInstance<ServiceDetail> instance = ServiceInstance.<ServiceDetail>builder() .address(address.getHostAddress())//ip地址:192.168.0.106 .port(Integer.parseInt(environment.getProperty("local.server.port")))//port:8080 .name("userService") //服务的名称 .payload(new ServiceDetail("用户服务", 1)) .build();
ServiceDiscovery<ServiceDetail> serviceDiscovery = ServiceDiscoveryBuilder.builder(ServiceDetail.class) .client(curatorFramework) //.serializer() //序列化方式 .basePath(ServiceDetail.REGISTER_ROOT_PATH) .build();
//服务注册 serviceDiscovery.registerService(instance); serviceDiscovery.start();
} catch (Exception e) { e.printStackTrace(); }
}
/** * 服务发现:通过serviceDiscovery查询到服务 */ public void discovery(){ try { ServiceDiscovery<ServiceDetail> serviceDiscovery = ServiceDiscoveryBuilder.builder(ServiceDetail.class) .client(curatorFramework) .basePath(ServiceDetail.REGISTER_ROOT_PATH) .build(); serviceDiscovery.start();
//根据名称获取服务 Collection<ServiceInstance<ServiceDetail>> services = serviceDiscovery.queryForInstances("userService"); for(ServiceInstance<ServiceDetail> service : services) { System.out.print(service.getPayload()+" -- "); System.out.println(service.getAddress() + ":" + service.getPort()); } System.out.println(); } catch (Exception e) { e.printStackTrace(); }
}
}
2.5 启动注册到zk上
在应用启动成功之后,注册到zk上:
package com.kfit.springbootcuratordemo;
import com.kfit.springbootcuratordemo.register.RegisterService;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplicationpublic class SpringbootCuratorDemoApplication {
public static void main(String[] args) { ConfigurableApplicationContext ctx = SpringApplication.run(SpringbootCuratorDemoApplication.class, args);
//将服务进行注册 RegisterService registerService = ctx.getBean(RegisterService.class); registerService.register();
//测试:服务发现 while (true){ registerService.discovery(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }
}
2.6 启动测试
首先启动zk server服务,然后启动springboot应用。
修改application.properties的属性server.port=8081,再次启动应用。
观察控制台的打印信息:
先启动的那个应用,一开始只能找到自己本身发布的那个服务,当另外一个应用启动的时候就能够监听到另外的服务信息了。
这时候我们看下zk server的节点信息:
可以看到userService/下有两个节点,我们来看下数据:
get /curator/apps/userService/260508a9-83c8-4d95-bdf6-8879888a6600
返回的数据是:
{ "name": "userService", "id": "260508a9-83c8-4d95-bdf6-8879888a6600", "address": "192.168.0.106", "port": 8080, "sslPort": null, "payload": { "@class": "com.kfit.springbootcuratordemo.register.ServiceDetail", "desc": "用户服务", "weight": 1 }, "registrationTimeUTC": 1616657422516, "serviceType": "DYNAMIC", "uriSpec": null}
当我们关闭一个应用的时候,瞬间就只能发现一个服务了,并且zk server上的节点就少了。
三、Curator的服务注册和服务发现一探究竟
3.1 服务注册使用的是什么类型的节点
从这里我们对于每个服务的节点是临时节点,那么对于临时节点有什么特性呢?也就是当我们的session和服务端断开的时候,该节点会被删除。
3.2 服务注册源码分析
我们跟进方法serviceDiscovery.registerService(instance),可以找到
org.apache.curator.x.discovery.details.ServiceDiscoveryImpl#internalRegisterService里面有这么一段代码:
这里创建节点核心的代码就是下面这句:
client.create().creatingParentContainersIfNeeded().withMode(mode).forPath(path,bytes);
父节点是容器节点,大家还记得容器节点的特性吗?这里在帮大家温习一下:
container 节点用来存放子节点,如果container节点中的子节点为0 ,则container节点在未来会被服务器删除,定时任务默认60秒执行一次。
我们可以把所有的应用删除看看,父类节点是不是自动被删除了。
等待60秒看一下,确实父类节点被删除了。
在过一会apps也被删除掉了,在过一段时间/curator也会被删除掉。
withMode是传入了参数mode,对于mode是在switch case进行判断的,我们并没有指定这个ServiceType,那么默认值是什么呢,可以跟进去看下,在ServiceInstance的构造方法设置了默认值:ServiceType.DYNAMIC。
所以对于每个服务的节点是临时节点。
四、小结
最后我们小结本文的内容:
(1)使用curator实现服务的注册和发现需要添加依赖curator-x-discovery,核心的类是ServiceDiscovery。
(2)创建的父类节点是容器节点,在没有子节点的时候60秒后会被删除;创建的服务实例的节点是临时节点,在session断开的时候,会立即被删除。/容器节点/临时节点
我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。