Dubbo+Nacos系列专题二:Dubbo服务注册到Nacos(下篇)
一、Nacos服务端进行服务注册
1.1 InstanceController#register
从这个Controller方法来看,先是解析出来instance,就是根据client发送的那堆参数解析出来的。
接着就是调用serviceManager组件进行实例注册,这个serviceManager 组件在注册中心是个核心组件,服务注册,下线,获取服务列表啥的,都是找这个组件的。
@CanDistro @PostMapping @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) public String register(HttpServletRequest request) throws Exception { // 得到namespaceId, 默认是public final String namespaceId = WebUtils .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); // 获取serviceName final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); NamingUtils.checkServiceNameFormat(serviceName); // 解析instance实例 final Instance instance = parseInstance(request); // 进行注册 serviceManager.registerInstance(namespaceId, serviceName, instance); return "ok"; }
我们可以大概看下这个解析数来的instance:
1.2 serviceManager.registerInstance
/** * Register an instance to a service in AP mode. * * <p>This method creates service or cluster silently if they don't exist. * * @param namespaceId id of namespace * @param serviceName service name * @param instance instance to register * @throws Exception any error occurred in the process */ public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException { createEmptyService(namespaceId, serviceName, instance.isEphemeral()); Service service = getService(namespaceId, serviceName); if (service == null) { throw new NacosException(NacosException.INVALID_PARAM, "service not found, namespace: " + namespaceId + ", service: " + serviceName); } addInstance(namespaceId, serviceName, instance.isEphemeral(), instance); }
1.2.1 ServiceManager#createEmptyService
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster) throws NacosException { // 根据namespace和serviceName得到Service Service service = getService(namespaceId, serviceName); if (service == null) { Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName); service = new Service(); service.setName(serviceName); service.setNamespaceId(namespaceId); // 设置group service.setGroupName(NamingUtils.getGroupName(serviceName)); // now validate the service. if failed, exception will be thrown service.setLastModifiedMillis(System.currentTimeMillis()); service.recalculateChecksum(); if (cluster != null) { cluster.setService(service); service.getClusterMap().put(cluster.getName(), cluster); } service.validate(); // put service并初始化 putServiceAndInit(service); if (!local) { addOrReplaceService(service); } } }
getService方法:本质就是从 private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>(); 中来获取,最开始肯定是没有,没有就需要创建。
/** * Map(namespace, Map(group::serviceName, Service)). */ private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
public Service getService(String namespaceId, String serviceName) { if (serviceMap.get(namespaceId) == null) { return null; } return chooseServiceMap(namespaceId).get(serviceName); }
再来看下putServiceAndInit.
private void putServiceAndInit(Service service) throws NacosException { putService(service); service = getService(service.getNamespaceId(), service.getName()); service.init(); consistencyService .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service); consistencyService .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service); Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson()); }
putService就是把service放到serviceMap中去,key为namespaceId.
接下来看service.init(). 这里这个初始化有个非常重要的地方就是往健康检查器中添加一个任务,健康检查的任务,这个任务其实就是扫描这个service里面长时间没有心跳的instance(服务实例),然后进行健康状态改变,服务下线.
/** * Init service. */ public void init() { // 往健康Check中添加 HealthCheckReactor.scheduleCheck(clientBeatCheckTask); for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) { entry.getValue().setService(this); entry.getValue().init(); } }
1.2.2 ServiceManager#registerInstance
再回到这个添加实例的方法
/** * Add instance to service. * * @param namespaceId namespace * @param serviceName service name * @param ephemeral whether instance is ephemeral * @param ips instances * @throws NacosException nacos exception */ public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException { // 创建一个key String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral); // 获取Service Service service = getService(namespaceId, serviceName); synchronized (service) { // 更新,然后获得这个服务的所有实例 List<Instance> instanceList = addIpAddresses(service, ephemeral, ips); // 放到实例中 Instances instances = new Instances(); instances.setInstanceList(instanceList); // 调用保存--一致性服务的put方法 consistencyService.put(key, instances); } }
可以看到生成的key格式:
com.alibaba.nacos.naming.iplist.ephemeral.{namespace}##{serviceName}
接着就是获取service, 上锁,调用addIpAddresses 得到一个instance集合.
private List<Instance> addIpAddresses(Service service, boolean ephemeral, Instance... ips) throws NacosException { return updateIpAddresses(service, UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD, ephemeral, ips); }
主要就是新的instance 与之前的instance进行合并啥的,生成一个新的instance集合。接着就是创建一个instances 对象,将instance集合塞进去
接下来,看这个保存方法:DelegateConsistencyServiceImpl#put
@Override public void put(String key, Record value) throws NacosException { mapConsistencyService(key).put(key, value); }
这个方法会根据你这个key是临时的还是永久的选择一个consisitencyService
private ConsistencyService mapConsistencyService(String key) { return KeyBuilder.matchEphemeralKey(key) ? ephemeralConsistencyService : persistentConsistencyService; }
这里我们是临时的,所以就走EphemeralConsistencyService 的实现类DistroConsistencyServiceImpl 的put方法。
DistroConsistencyServiceImpl#put
@Override public void put(String key, Record value) throws NacosException { onPut(key, value); distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE, globalConfig.getTaskDispatchPeriod() / 2); }
重点关注onPut方法:
/** * Put a new record. * * @param key key of record * @param value record */ public void onPut(String key, Record value) { // 判断节点是否临时,主要是看key的前缀是否以这个字符串开头”com.alibaba.nacos.naming.iplist.ephemeral“ if (KeyBuilder.matchEphemeralInstanceListKey(key)) { // 封装对象Datum Datum<Instances> datum = new Datum<>(); datum.value = (Instances) value; datum.key = key; datum.timestamp.incrementAndGet(); // 放到dataStore进行存储 dataStore.put(key, datum); } // 如果listener没有这个key 直接返回 if (!listeners.containsKey(key)) { return; } // 添加通知任务 notifier.addTask(key, DataOperation.CHANGE); }
这个dataStore:
private Map<String, Datum> dataMap = new ConcurrentHashMap<>(1024); public void put(String key, Datum value) { dataMap.put(key, value); }
接下来,我们来看addTask:
/** * Add new notify task to queue. * * @param datumKey data key * @param action action for data */ public void addTask(String datumKey, DataOperation action) { // 如果已经存在且是change事件 if (services.containsKey(datumKey) && action == DataOperation.CHANGE) { return; } if (action == DataOperation.CHANGE) { // 缓存中也放一份 services.put(datumKey, StringUtils.EMPTY); } tasks.offer(Pair.with(datumKey, action)); }
这个tasks:
private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);
这里其实就是往任务队列中添加了一个任务。到这按理说我们服务注册就该结束了,但是,我们发现生成了新的instance集合并没有更新到service对象里面去,所以还得继续往下看,看看这个通知任务是怎么回事。
其实DistroConsistencyServiceImpl 这个类在初始化的时候,然后提交了一个任务:
@PostConstruct public void init() { GlobalExecutor.submitDistroNotifyTask(notifier); }
这个提交任务的run方法做了什么事情呢?
@Override public void run() { Loggers.DISTRO.info("distro notifier started"); for (; ; ) { try { Pair<String, DataOperation> pair = tasks.take(); handle(pair); } catch (Throwable e) { Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e); } } }
private void handle(Pair<String, DataOperation> pair) { try { String datumKey = pair.getValue0(); DataOperation action = pair.getValue1(); services.remove(datumKey); int count = 0; if (!listeners.containsKey(datumKey)) { return; } for (RecordListener listener : listeners.get(datumKey)) { count++; try { if (action == DataOperation.CHANGE) { // 数据更新 listener.onChange(datumKey, dataStore.get(datumKey).value); continue; } if (action == DataOperation.DELETE) { // 删除 listener.onDelete(datumKey); continue; } } catch (Throwable e) { Loggers.DISTRO.error("[NACOS-DISTRO] error while notifying listener of key: {}", datumKey, e); } } if (Loggers.DISTRO.isDebugEnabled()) { Loggers.DISTRO .debug("[NACOS-DISTRO] datum change notified, key: {}, listener count: {}, action: {}", datumKey, count, action.name()); } } catch (Throwable e) { Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e); } }
这里直接通知,调用listener 的onChange活着onDelete执行相关的工作。之前已经将service作为listener注册进来了,看下service的onChange方法:
@Override public void onChange(String key, Instances value) throws Exception { Loggers.SRV_LOG.info("[NACOS-RAFT] datum is changed, key: {}, value: {}", key, value); for (Instance instance : value.getInstanceList()) { if (instance == null) { // Reject this abnormal instance list: throw new RuntimeException("got null instance " + key); } if (instance.getWeight() > 10000.0D) { instance.setWeight(10000.0D); } if (instance.getWeight() < 0.01D && instance.getWeight() > 0.0D) { instance.setWeight(0.01D); } } updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key)); recalculateChecksum(); }
核心在updateIps这个方法:参数1是新的instance集合,参数2是是否是临时节点这个方法其实就是遍历instance集合,然后更新clusterMap 这个里面的内容,这个clusterMap 其实就是clusterName 与cluster的对应关系,从代码上可以看到实现弄出所有的cluster,然后遍历instance集合,如果没有某个instance没有cluster,就设置成默认DEFAULT_CLUSTER_NAME,如果某个cluster没有的话就创建。然后塞到一个cluster与instance集合对应关系的map中。
接着就是遍历clusterMap更新下instance列表,主要还是比对新老的,然后找出新的instance,与挂了的instance,注意这一步是更新 cluster对象里面的集合,其实就是2个set,一个存临时节点的,一个是存永久节点的。
/** * Update instances. * * @param instances instances * @param ephemeral whether is ephemeral instance */ public void updateIPs(Collection<Instance> instances, boolean ephemeral) { Map<String, List<Instance>> ipMap = new HashMap<>(clusterMap.size()); for (String clusterName : clusterMap.keySet()) { ipMap.put(clusterName, new ArrayList<>()); } for (Instance instance : instances) { try { if (instance == null) { Loggers.SRV_LOG.error("[NACOS-DOM] received malformed ip: null"); continue; } if (StringUtils.isEmpty(instance.getClusterName())) { instance.setClusterName(UtilsAndCommons.DEFAULT_CLUSTER_NAME); } if (!clusterMap.containsKey(instance.getClusterName())) { Loggers.SRV_LOG .warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.", instance.getClusterName(), instance.toJson()); Cluster cluster = new Cluster(instance.getClusterName(), this); cluster.init(); getClusterMap().put(instance.getClusterName(), cluster); } List<Instance> clusterIPs = ipMap.get(instance.getClusterName()); if (clusterIPs == null) { clusterIPs = new LinkedList<>(); ipMap.put(instance.getClusterName(), clusterIPs); } clusterIPs.add(instance); } catch (Exception e) { Loggers.SRV_LOG.error("[NACOS-DOM] failed to process ip: " + instance, e); } } for (Map.Entry<String, List<Instance>> entry : ipMap.entrySet()) { //make every ip mine List<Instance> entryIPs = entry.getValue(); clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral); } setLastModifiedMillis(System.currentTimeMillis()); getPushService().serviceChanged(this); StringBuilder stringBuilder = new StringBuilder(); for (Instance instance : allIPs()) { stringBuilder.append(instance.toIpAddr()).append("_").append(instance.isHealthy()).append(","); } Loggers.EVT_LOG.info("[IP-UPDATED] namespace: {}, service: {}, ips: {}", getNamespaceId(), getName(), stringBuilder.toString()); }
到以上,服务注册就完成了。
二、回顾一下整个过程
本篇就到这里,欢迎围观评论及批评指正。下一篇继续。