Nacos中的线程池使用总结记录

1. ThreadFactory优雅创建线程

/**
 * Name thread factory.
 *
 * @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
 */
public class NameThreadFactory implements ThreadFactory {
    // 原子类构建线程名称,防止重复
    private final AtomicInteger id = new AtomicInteger(0);
    
    // 线程组名称
    private String name;
    
    public NameThreadFactory(String name) {
        if (!name.endsWith(StringUtils.DOT)) {
            name += StringUtils.DOT;
        }
        this.name = name;
    }
    
    @Override
    public Thread newThread(Runnable r) {
        String threadName = name + id.getAndIncrement();
        Thread thread = new Thread(r, threadName);
        thread.setDaemon(true);
        return thread;
    }
}


-----------------------------------------------------------------------------
// 使用
new NameThreadFactory("com.alibaba.nacos.naming.timer")

我们平时都会使用ThreadPoolExecutor来创建线程池。使用较多的是如下构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

这种构建线程池的缺点是我们无法定制线程,无法设置线程名称、是否为守护线程等信息。但是通常在项目中会使用多个线程池,所以为了区分线程,我们往往需要根据业务或者服务来命名线程。所以定制线程就称为了一个必要的需求。

所以,我们可以使用如下的方式,通过ThreadFactory的方式定制线程:

public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}

我们只需要定义一个类实现接口ThreadFactory接口,然后在newThread方法中定义自己的线程创建逻辑就可以了。

2. 线程池构建

private static final ScheduledExecutorService NAMING_TIMER_EXECUTOR = ExecutorFactory.Managed
            .newScheduledExecutorService(ClassUtils.getCanonicalName(NamingApp.class),
                    EnvUtil.getAvailableProcessors(2), new NameThreadFactory("com.alibaba.nacos.naming.timer"));

3. 设置核心线程算法

public static int getAvailableProcessors(int multiple) {
    if (multiple < 1) {
        throw new IllegalArgumentException("processors multiple must upper than 1");
    }
    // 获取配置中的处理器个数
    Integer processor = getProperty(Constants.AVAILABLE_PROCESSORS_BASIC, Integer.class);
    // 如果获取到处理器个数,则核心线程数为处理器个数*2
    // 否则执行getSuitableThreadCount构建核心线程数
    return null != processor && processor > 0 ? processor * multiple : ThreadUtils.getSuitableThreadCount(multiple);
}

----------------------------------------------------------------------
public static int getSuitableThreadCount(int threadMultiple) {
   final int coreCount = Runtime.getRuntime().availableProcessors();
    int workerCount = 1;
    while (workerCount < coreCount * threadMultiple) {
        workerCount <<= 1;
    }
    return workerCount;
}

4. 优雅定义任务

我们都知道,线程池提交的任务一般是Runnable接口的子类。我们在实际项目中往往会根据业务,定义出专属的也谢task类。这些的做法是比较好的,因为我们根据task的名称可以很容易的区分出是哪个业务相关的任务。

比如Nacos中的这个Notifier 就实现了Runnable接口。

public class Notifier implements Runnable {
    // 定义ConcurrentHashMap来存储服务实例    
    private ConcurrentHashMap<String, String> services = new ConcurrentHashMap<>(10 * 1024);
    // 存储需要注册的服务实例到一个阻塞队列中,然后异步注册服务
    private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);
    
    /**
     * Add new notify task to queue.
     *
     * @param datumKey data key
     * @param action   action for data
     */
    public void addTask(String datumKey, DataOperation action) {
        // 如果任务已经存在直接返回
        if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
            return;
        }
        // 如果任务不存在,将其记录到services map中去
        if (action == DataOperation.CHANGE) {
            services.put(datumKey, StringUtils.EMPTY);
        }
        // 向阻塞队列添加任务
        tasks.offer(Pair.with(datumKey, action));
    }
    
    public int getTaskSize() {
        return tasks.size();
    }
    
    @Override
    public void run() {
        Loggers.DISTRO.info("distro notifier started");
        // 死循环不会一直占用CPU资源,因为当阻塞队列中没有元素的时候,take方法中判断个数为0,会调用condition.await方法阻塞等待。直到有元素被添加进来。
        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();
            // 服务map中移除记录
            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);
        }
    }
}

思考:Nacos为何要异步注册服务?

因为注册服务本身是一个较为复杂的过程,如果是同步操作,那么系统启动的时候就需要完成注册,这个事件是非常慢的。

5. 启动线程池

@PostConstruct
public void init() {
    GlobalExecutor.submitDistroNotifyTask(notifier);
}


public static void submitDistroNotifyTask(Runnable runnable) {
  DISTRO_NOTIFY_EXECUTOR.submit(runnable);
}

6. 写时复制-解决读写并发冲突

对于注册表的读写并发冲突,Nacos中采用的是写时复制策略。即读取数据的时候读取的是原始集合中的数据,但是写数据的时候就会先加锁,然后复制一份数据集合,然后对这个复制后的数据集合进行写操作。完成写操作之后将初始数据集合指向修改后的地址,然后解锁。


Nacos中保存数据的初始集合(分为临时和持久):

源码类:com.alibaba.nacos.naming.core.Cluster

@JsonIgnore
private Set<Instance> persistentInstances = new HashSet<>();

@JsonIgnore
private Set<Instance> ephemeralInstances = new HashSet<>();
public void updateIps(List<Instance> ips, boolean ephemeral) {
    // 获取到初始数据(注意,此时并不是复制整个注册表,而是某个cluster下的服务列表)
    Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;

    // 复制一份数据到map中
    HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());
    // 将旧数据存储到这个oldIpMap中去
    for (Instance ip : toUpdateInstances) {
        oldIpMap.put(ip.getDatumKey(), ip);
    }

    // ====================================== 数据修改开始 ========================================================
    List<Instance> updatedIps = updatedIps(ips, oldIpMap.values());
    if (updatedIps.size() > 0) {
        for (Instance ip : updatedIps) {
            Instance oldIP = oldIpMap.get(ip.getDatumKey());
            
            // do not update the ip validation status of updated ips
            // because the checker has the most precise result
            // Only when ip is not marked, don't we update the health status of IP:
            if (!ip.isMarked()) {
                ip.setHealthy(oldIP.isHealthy());
            }
            
            if (ip.isHealthy() != oldIP.isHealthy()) {
                // ip validation status updated
                Loggers.EVT_LOG.info("{} {SYNC} IP-{} {}:{}@{}", getService().getName(),
                        (ip.isHealthy() ? "ENABLED" : "DISABLED"), ip.getIp(), ip.getPort(), getName());
            }
            
            if (ip.getWeight() != oldIP.getWeight()) {
                // ip validation status updated
                Loggers.EVT_LOG.info("{} {SYNC} {IP-UPDATED} {}->{}", getService().getName(), oldIP, ip);
            }
        }
    }
    
    List<Instance> newIPs = subtract(ips, oldIpMap.values());
    if (newIPs.size() > 0) {
        Loggers.EVT_LOG
                .info("{} {SYNC} {IP-NEW} cluster: {}, new ips size: {}, content: {}", getService().getName(),
                        getName(), newIPs.size(), newIPs);
        
        for (Instance ip : newIPs) {
            HealthCheckStatus.reset(ip);
        }
    }
    
    List<Instance> deadIPs = subtract(oldIpMap.values(), ips);
    
    if (deadIPs.size() > 0) {
        Loggers.EVT_LOG
                .info("{} {SYNC} {IP-DEAD} cluster: {}, dead ips size: {}, content: {}", getService().getName(),
                        getName(), deadIPs.size(), deadIPs);
        
        for (Instance ip : deadIPs) {
            HealthCheckStatus.remv(ip);
        }
    }
    // ====================================== 数据修改结束 ========================================================

    // 将修改后的数据复制给toUpdateInstances
    toUpdateInstances = new HashSet<>(ips);

    // 将上面做的执行修改操作后的数据集合赋值给初始数据集合
    if (ephemeral) {
        ephemeralInstances = toUpdateInstances;
    } else {
        persistentInstances = toUpdateInstances;
    }
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Nacos是一个用于动态服务发现、配置管理和服务管理的开源平台,它可以帮助应用程序在云原生环境实现弹性、可伸缩和高可用性。在使用Nacos时,你可以考虑以下几个方面的应用: 1. 服务注册与发现:Nacos可以作为应用程序的服务注册心,应用程序可以将自己注册到Nacos服务器,并从Nacos获取其他服务的信息。通过Nacos的服务发现功能,应用程序可以动态地获取可用的服务实例,并进行负载均衡和故障转移。 2. 动态配置管理:Nacos提供了一种便捷的方式来管理应用程序的配置信息。你可以将应用程序的配置存储在Nacos,并通过订阅机制实现配置的动态更新。这样,你可以在不重启应用程序的情况下修改配置,从而实现配置的灵活管理。 3. 服务路由与网关:Nacos可以与微服务框架(如Spring Cloud、Dubbo等)集成,提供服务路由和网关功能。通过Nacos的动态路由功能,你可以根据特定规则将请求路由到不同的服务实例上,从而实现请求的动态转发和负载均衡。 4. 分布式任务调度:Nacos还提供了分布式任务调度功能,可以帮助你实现分布式任务的调度和管理。你可以将任务定义和调度规则存储在Nacos,然后使用Nacos提供的调度器来执行任务。这样,你可以实现分布式任务的自动化调度和管理。 总之,使用Nacos可以帮助你简化微服务架构的服务发现、配置管理、路由转发和任务调度等方面的工作,提高应用程序的弹性和可伸缩性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值