微服务面试篇-Nacos如何支撑数十万服务注册压力

注册思想

Nacos内部接收到注册的请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列,就立即响应给客户端。然后利用线程池读取阻塞队列中的任务,异步来完成实例更新,从而提高并发写能力。

注册示例过程

InstanceController

@CanDistro
    @PostMapping
    @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
    public String register(HttpServletRequest request) throws Exception {
        // 从request 获取namespaceId
        final String namespaceId = WebUtils
                .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        // 获取服务名称
        final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        NamingUtils.checkServiceNameFormat(serviceName);
        // 把request中的参数封装为Instance对象
        final Instance instance = parseInstance(request);
        // 注册实例
        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";
    }

ServiceManager

/**
     * Map(namespace, Map(group::serviceName, Service)).
     */
    private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();
 public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        // 如果是第一次, 则创建空服务
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        // 从注册表中,拿到Service
        Service service = getService(namespaceId, serviceName);
        
        if (service == null) {
            throw new NacosException(NacosException.INVALID_PARAM,
                    "service not found, namespace: " + namespaceId + ", service: " + serviceName);
        }
        // 添加实例到Service中
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    }
public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
        createServiceIfAbsent(namespaceId, serviceName, local, null);
    }
    
    /**
     * Create service if not exist.
     *
     * @param namespaceId namespace
     * @param serviceName service name
     * @param local       whether create service by local
     * @param cluster     cluster
     * @throws NacosException nacos exception
     */
    public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException {
        // 尝试从注册表中获取服务
        Service service = getService(namespaceId, serviceName);
        if (service == null) {
            // 如果为空,说明该服务是第一次注册,创建新的Service
            Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
            service = new Service();
            service.setName(serviceName);
            service.setNamespaceId(namespaceId);
            // 因为serviceName是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();
            
            putServiceAndInit(service);
            if (!local) {
                addOrReplaceService(service);
            }
        }
    }
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());
    }
    
public void putService(Service service) {
        if (!serviceMap.containsKey(service.getNamespaceId())) {
            synchronized (putServiceLock) {
                if (!serviceMap.containsKey(service.getNamespaceId())) {
                    serviceMap.put(service.getNamespaceId(), new ConcurrentSkipListMap<>());
                }
            }
        }
        serviceMap.get(service.getNamespaceId()).putIfAbsent(service.getName(), service);
    }
    
    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());
    }
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
            throws NacosException {
        // 给当前服务生成一个唯一标识,可以理解为ServiceId
        String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
        // 从注册表中拿到Service
        Service service = getService(namespaceId, serviceName);
        // 以service为锁对象,同一个服务的多个实例,只能串行来完成注册
        synchronized (service) {
        	// 拷贝注册表中旧的实例列表,然后结合新注册的示例,得到最终的示例列表
            List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
            // 封装新实例列表到Instances对象中
            Instances instances = new Instances();
            instances.setInstanceList(instanceList);
            // 更新注册表(更新本地注册表、同步给Nacos集群中的其他节点)
            consistencyService.put(key, instances);
        }
    }

DistroConsistencyServiceImpl (临时非持久化)

com.alibaba.nacos.naming.consistency.ephemeral.distro.DistroConsistencyServiceImpl

@Override
    public void put(String key, Record value) throws NacosException {
    	// 异步,只是放入队列,更新本地注册表
        onPut(key, value);
        // 同步给Nacos集群中的其他节点
        distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
                globalConfig.getTaskDispatchPeriod() / 2);
    }


public void onPut(String key, Record value) {
        // 判断是否临时实例
        if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
        	// 封装实例列表,封装到Datum
            Datum<Instances> datum = new Datum<>();
            // value是服务中的实例列表 Instances
            datum.value = (Instances) value;
            // key是服务的唯一标识
            datum.key = key;
            datum.timestamp.incrementAndGet();
            // 以serviceId为key,以Datum位置,缓存起来
            dataStore.put(key, datum);
        }
        
        if (!listeners.containsKey(key)) {
            return;
        }
        // 把ServiceId和当前的操作类型存入notifier
        notifier.addTask(key, DataOperation.CHANGE);
    }
    

初始化线程池

@PostConstruct
    public void init() {
    	// 利用线程池,执行notifier
        GlobalExecutor.submitDistroNotifyTask(notifier);
    }

 @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);
                }
            }
        }

Nacos客户端注册过程

在NacosServiceRegistry.java中调用NacosNamingService的registerInstance方法注册
在这里插入图片描述

serverProxy发送restApi注册
beatReactor注册心跳

心跳发送:
在这里插入图片描述

Nacos如何变并发读写冲突问题

Nacos在更新实例列表时,会采用CopyOnWrite技术,首先将旧的实例列表拷贝一份,然后更新拷贝的实例列表,在用更新后的实例列表来覆盖旧的实例列表。
这样在更新的过程中,就不会对读实例列表的请求产生影响,也不会出现脏读问题了。

Nacos和Eureka的区别

NacosEureka
接口方式对外暴露Rest风格的API用来实现注册、发现等功能对外暴露Rest风格的API用来实现注册、发现等功能
实例类型有永久和临时实例之分只支持临时实例
健康监测对临时实例采用心跳模式监测,对永久实例采用主动请求来监测只支持心跳模式
服务发现支持定时拉去和订阅推送两种模式只支持定时拉取模式
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值