0. 环境
- nacos版本:1.4.1
- Spring Cloud : 2020.0.2
- Spring Boot :2.4.4
- Spring Cloud alibaba: 2.2.5.RELEASE
本文将解析下nacos的服务发现源码实现,nacos支持两种服务发现方式,一种是直接去nacos服务端拉取某个服务的实例列表,就像eureka那样定时去拉取注册表信息,另一种是服务订阅的方式,就是订阅某个服务,然后这个服务下面的实例列表一旦发生变化,nacos服务端就会使用udp的方式通知客户端,并将实例列表带过去
。在本文中我们主要先看下 直接拉取服务实例列表的实现与订阅,对于订阅服务实例列表发生变化,nacos服务端通知订阅的客户端这部分代码我们在下篇详细介绍。
1. 直接拉取方式
1.1 直接拉取客户端源码
Properties properties = new Properties();
properties.setProperty("serverAddr", "10.0.8.247:8825");
properties.setProperty("namespace","public");
NamingService naming = NamingFactory.createNamingService(properties);
naming.registerInstance("userService", "11.11.11.11", 8888, "DEFAULT");
List<Instance> userService = naming.getAllInstances("userService");
复制代码
上面这段代码,先是创建一个NamingService
,接着注册了一个服务实例,最后是调用了getAllInstances
方法获取某个服务的实例列表。
我们来看下这个getAllInstances
方法实现:
@Override
public List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters,
boolean subscribe) throws NacosException {
ServiceInfo serviceInfo;
// 是否订阅
if (subscribe) {
// 订阅
serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName),
StringUtils.join(clusters, ","));
} else {
// 不进行订阅
serviceInfo = hostReactor
.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName),
StringUtils.join(clusters, ","));
}
List<Instance> list;
if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {
return new ArrayList<Instance>();
}
return list;
}
复制代码
前面一堆重载方法,就是设置默认的group
与 cluster
我们跳过去,直接到这个参数最全的方法来,判断是否订阅
,到这里默认是订阅的,也就是subscribe =true
, 不过可以指定,我们就先看下这个不订阅的是怎么实现的吧,调用了getServiceInfoDirectlyFromServer
这个方法,从方法名上也能看出来,就是直接从server上获取serviceInfo:
public ServiceInfo getServiceInfoDirectlyFromServer(final String serviceName, final String clusters)
throws NacosException {
String result = serverProxy.queryList(serviceName, clusters, 0, false);
if (StringUtils.isNotEmpty(result)) {
return JacksonUtils.toObj(result, ServiceInfo.class);
}
return null;
}
复制代码
这里直接是调用了serverProxy
组件的queryList
方法,需要主要的是这个udp端口是0
,因为咱们这里是不订阅的,这个upd是给订阅的接收通知用的:
public String queryList(String serviceName, String clusters, int udpPort, boolean healthyOnly)
throws NacosException {
final Map<String, String> params = new HashMap<String, String>(8);
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, serviceName);
params.put("clusters", clusters);
// udp端口
params.put("udpPort", String.valueOf(udpPort));
params.put("clientIP", NetUtils.localIP());
params.put("healthyOnly", String.valueOf(healthyOnly));
// 发起请求/nacos/v1/ns/instance/list GET请求
return reqApi(UtilAndComs.nacosUrlBase + "/instance/list", params, HttpMethod.GET);
}
复制代码
这个方法就是封装请求参数,udpPort,clientIP,healthyOnly
这几个参数需要注意下,接着调用reqApi 选择server 发送请求了,请求uri是 /nacos/v1/ns/instance/list
,请求方法是get
。后面的选择server发送请求我们就不看了,在服务注册,服务发现里面我们看了无数次了。
1.2 直接拉取服务端处理源码
InstanceController 的list 方法
是处理这个请求的,我们来看下:
@GetMapping("/list")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public ObjectNode list(HttpServletRequest request) throws Exception {
// 从请求中获取各种属性
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
NamingUtils.checkServiceNameFormat(serviceName);
// agent属性用于指定提交请求的客户端是哪种类型
String agent = WebUtils.getUserAgent(request);
String clusters = WebUtils.optional(request, "clusters", StringUtils.EMPTY);
String clientIP = WebUtils.optional(request, "clientIP", StringUtils.EMPTY);
// 获取到client的端口号,后续UDP通信会使用
int udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort", "0"));
String env = WebUtils.optional(request, "env", StringUtils.EMPTY);
boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request, "isCheck", "false"));
String app = WebUtils.optional(request, "app", StringUtils.EMPTY);
String tenant = WebUtils.optional(request, "tid", StringUtils.EMPTY);
boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request, "healthyOnly", "false"));
// todo 对请求进行详细处理
return doSrvIpxt(namespaceId, serviceName, agent, clusters, clientIP, udpPort, env, isCheck, app, tenant,
healthyOnly);
}
复制代码
上面这一堆就是解析参数,我们不过多关注,主要看下doSrvIpxt
这个方法。方法太长了我们分段看:
public ObjectNode doSrvIpxt(String namespaceId, String serviceName, String agent, String clusters, String clientIP,
int ud