为什么要使用线程池?dubbo是如何扩展的?

为什么要使用线程池

多线程能够提高系统的并发性,充分利用服务的资源。但是,如果无限制的创建线程,反而会拖垮服务器的性能。一是创建线程是一个耗资源的操作,二是过多的线程会加剧线程上下文切换,竞争CPU。所以,会对线程使用池化的方案,重复的利用已经创建的线程。在Java中使用ThreadPoolExecutor定义线程池,其部分的源码如下:

    /**
     * ThreadPoolExecutor 初始化方法
     */
    // ThreadPoolExecutor.class
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

 属性介绍分别如下:

  • corePoolSize,核心线程数。
  • maximumPoolSize,最大线程数。
  • workQueue,阻塞任务队列。
  • threadFactory,线程工厂。
  • handler,拒绝策略。

线程池执行任务的步骤如图:

  1. 当添加任务时,检测是否存在线程或者空闲线程,如果都没有线程则创建线程。
  2. 当继续添加任务时,如果都没有空闲线程,则继续创建,直到创建corePoolSize个核心线程数。
  3. 当继续添加任务时,创建的任务数大于corePoolSize个数时,就不会把未执行的任务加入workQueue阻塞队列排队,直到填满队列。此时,如果有线程执行完毕处于空闲状态,就从workQueue阻塞队列中获取任务执行。
  4. 当继续添加任务时,创建的任务个数大于满足2,并且workQueue阻塞队列已满,则继续创建新的线程,直到maximumPoolSize指定的最大线程数。
  5. 当继续添加任务时,创建任务的满足步骤4,并且创建的个数大于maximumPoolSize指定的最大线程数,这时,就会执行handler拒绝策略,拒绝执行新增的任务。

线程池执行任务的部分其源码如下:

    /**
     * 线程池执行任务
     */
    ThreadPoolExecutor.class
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // 创建的线程数小于corePoolSize核心线程数
        if (workerCountOf(c) < corePoolSize) {
            // 添加新的任务到新的线程
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 添加任务到阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 创建线程到maximumPoolSize个数,如果继续创建失败则拒绝执行任务
        else if (!addWorker(command, false))
            reject(command);
    }

    /**
     * 创建worker任务并绑定线程
     */
    ThreadPoolExecutor.class
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                // 已经创建的线程个数
                int wc = workerCountOf(c);
                // 大于指定个数的线程设置个数(corePoolSize 或者maximumPoolSize)
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 修改执行线程的个数成功,则退出循环
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 创建Worker 任务,并创建新的线程
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                 
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //执行任务
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

    /**
     * 创建Worker,设置任务,通过threadFactory创建线程
     */
    ThreadPoolExecutor.class
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        // 创建线程
        this.thread = getThreadFactory().newThread(this);
    }

dubbo是如何扩展线程池 

 在dubbo的服务发布过程中,使用Netty的NioEventLoopGroup基于NIO的线程模式来监听连接客户端的各类事件。当监听到客户端的事件时,会使用线程池执行各类时间的任务。按照特性,dubbo扩展了4类线程池,分别如下:

  • FixedThreadPool,固定线程数的线程池,核心线程(corePoolSize)与最大线程数(maximumPoolSize)相同,启动时建立线程,并且线程不会被回收。
  • CachedThreadPool,可缓存corePoolSize个数的线程,当创建了更多的线程,在达到失效时间时会被回收。
  • LimitedThreadPool,可缓存corePoolSize个数的线程,当创建的线程数小于maximumPoolSize个数的线程时,线程不会被回收。
  • EagerThreadPool,当创建的任务超过corePoolSize个数的线程,会继续创建线程执行任务,直到maximumPoolSize个的线程。

其线程池的源码分别如下:

/**
 * Creates a thread pool that reuses a fixed number of threads
 *
 * @see java.util.concurrent.Executors#newFixedThreadPool(int)
 */
public class FixedThreadPool implements ThreadPool {

    @Override
    public Executor getExecutor(URL url) {
        String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
        int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
        int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
        // 核心线程(corePoolSize)与最大线程数(maximumPoolSize)相同,并且不会过期
        return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
                queues == 0 ? new SynchronousQueue<Runnable>() :
                        (queues < 0 ? new LinkedBlockingQueue<Runnable>()
                                : new LinkedBlockingQueue<Runnable>(queues)),
                new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }

}

/**
 * This thread pool is self-tuned. Thread will be recycled after idle for one minute, and new thread will be created for
 * the upcoming request.
 *
 * @see java.util.concurrent.Executors#newCachedThreadPool()
 */
public class CachedThreadPool implements ThreadPool {

    @Override
    public Executor getExecutor(URL url) {
        String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
        int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
        int threads = url.getParameter(THREADS_KEY, Integer.MAX_VALUE);
        int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
        int alive = url.getParameter(ALIVE_KEY, DEFAULT_ALIVE);
        // 大于corePoolSize个数小于等于maximumPoolSize个数的线程,在空闲alive毫秒后会被回收
        return new ThreadPoolExecutor(cores, threads, alive, TimeUnit.MILLISECONDS,
                queues == 0 ? new SynchronousQueue<Runnable>() :
                        (queues < 0 ? new LinkedBlockingQueue<Runnable>()
                                : new LinkedBlockingQueue<Runnable>(queues)),
                new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }
}

/**
 * Creates a thread pool that creates new threads as needed until limits reaches. This thread pool will not shrink
 * automatically.
 */
public class LimitedThreadPool implements ThreadPool {

    @Override
    public Executor getExecutor(URL url) {
        String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
        int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
        int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
        int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
        // 创建指定的核心线程数和最大线程数,并且空闲线程不会被回收
        return new ThreadPoolExecutor(cores, threads, Long.MAX_VALUE, TimeUnit.MILLISECONDS,
                queues == 0 ? new SynchronousQueue<Runnable>() :
                        (queues < 0 ? new LinkedBlockingQueue<Runnable>()
                                : new LinkedBlockingQueue<Runnable>(queues)),
                new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }

}


public class EagerThreadPool implements ThreadPool {

    @Override
    public Executor getExecutor(URL url) {
        String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
        int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
        int threads = url.getParameter(THREADS_KEY, Integer.MAX_VALUE);
        int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
        int alive = url.getParameter(ALIVE_KEY, DEFAULT_ALIVE);

        // init queue and executor
        TaskQueue<Runnable> taskQueue = new TaskQueue<Runnable>(queues <= 0 ? 1 : queues);
        // 自定义的EagerThreadPoolExecutor,会在刚大于cores个数的线程时,继续创建线程
        EagerThreadPoolExecutor executor = new EagerThreadPoolExecutor(cores,
                threads,
                alive,
                TimeUnit.MILLISECONDS,
                taskQueue,
                new NamedInternalThreadFactory(name, true),
                new AbortPolicyWithReport(name, url));
        taskQueue.setExecutor(executor);
        return executor;
    }
}

    /**
     * EagerThreadPoolExecutor执行任务的逻辑
     */
    // EagerThreadPoolExecutor.class
    @Override
    public void execute(Runnable command) {
        if (command == null) {
            throw new NullPointerException();
        }
        // do not increment in method beforeExecute!
        submittedTaskCount.incrementAndGet();
        try {
            super.execute(command);
        } catch (RejectedExecutionException rx) {
            // 队列已满,任务被拒绝后,继续从队列中获取任务
            final TaskQueue queue = (TaskQueue) super.getQueue();
            try {
                 // 重试从队列中获取任务失败,抛出RejectedExecutionException
                if (!queue.retryOffer(command, 0, TimeUnit.MILLISECONDS)) {
                    submittedTaskCount.decrementAndGet();
                    throw new RejectedExecutionException("Queue capacity is full.", rx);
                }
            } catch (InterruptedException x) {
                submittedTaskCount.decrementAndGet();
                throw new RejectedExecutionException(x);
            }
        } catch (Throwable t) {
            // decrease any way
            submittedTaskCount.decrementAndGet();
            throw t;
        }
    }

    /**
     * EagerThreadPool 中使用的任务队列的添加任务(offer)方法
     */
    //TaskQueue.class
    @Override
    public boolean offer(Runnable runnable) {
        if (executor == null) {
            throw new RejectedExecutionException("The task queue does not have executor!");
        }

        int currentPoolThreadSize = executor.getPoolSize();
        // have free worker. put task into queue to let the worker deal with task.
        // 当有空闲线程时,添加任务到队列中,让线程继续执行
        if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {
            return super.offer(runnable);
        }

        // return false to let executor create new worker.
        // 当创建的线程大于corePoolSize核心线程数,并且小于maximumPoolSize最大线程数时,返回添加失败,让线程池继续创建新的线程执行
        if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
            return false;
        }

        // currentPoolThreadSize >= max
        // 当执行线程数大于maximumPoolSize最大线程数,则添加任务到队列
        return super.offer(runnable);
    }

 

 

 

<think>我们首先需要解决用户提出的三个问题: 1. Dubbo 服务熔断和降级实现原理及代码示例 2. Dubbo 与 Spring Cloud 集成实践指南 3. Dubbo 服务性能监控与调优方法 根据用户提供的引用,我们可以得到一些线索: 引用[1]和[3]提到了Dubbo的熔断降级,其中引用[3]给出了一个使用`@DubboReference`注解并设置`mock`属性来实现降级的代码示例。 引用[4]提到了使用Sentinel进行熔断降级,并给出了一个处理限流异常的方法示例。 对于第一个问题,Dubbo本身并没有内置熔断降级功能,但可以通过扩展来实现。常见的做法是整合Hystrix或Sentinel。在Dubbo 2.7之后,官方推荐使用Sentinel。 第二个问题,引用[2]提到了Spring Cloud Alibaba,它是将Dubbo与Spring Cloud集成的一种方式。通过Spring Cloud Alibaba Dubbo,我们可以将Dubbo作为Spring Cloud的RPC框架。 第三个问题,Dubbo提供了监控中心(Monitor)来收集统计数据,同时也可以整合Prometheus和Grafana等工具进行监控。 下面我们逐一回答:</think>### Dubbo 服务熔断与降级实现原理及代码示例 #### **1. 实现原理** - **熔断(Circuit Breaker)**:当服务调用失败率超过阈值时,自动切断请求链路,避免资源耗尽。 - **降级(Fallback)**:调用失败时返回预设的默认值(如缓存数据、空结果),保障核心链路可用。 Dubbo 通过 **Mock 机制** 和 **Sentinel 整合** 实现熔断降级[^1][^3][^4]。 #### **2. 代码示例** ##### (1) Mock 降级(Dubbo 原生支持) ```java // 1. 服务接口 public interface UserService { String getUserInfo(String userId); } // 2. 实现降级逻辑的 Mock 类 public class UserServiceMock implements UserService { @Override public String getUserInfo(String userId) { // 降级逻辑:返回缓存数据或默认值 return "[MOCK] 用户服务暂不可用,默认用户ID: " + userId; } } // 3. 消费者通过注解启用降级 import org.apache.dubbo.config.annotation.DubboReference; public class ConsumerService { // 设置 mock 类路径 @DubboReference(mock = "com.example.UserServiceMock") private UserService userService; public void execute() { // 正常调用失败时自动触发降级 String result = userService.getUserInfo("1001"); System.out.println(result); // 输出降级结果 } } ``` ##### (2) Sentinel 熔断(整合 Spring Cloud Alibaba) ```java // 1. 定义熔断规则 @Configuration public class SentinelConfig { @PostConstruct public void initRules() { // 设置熔断规则:失败率>50%时熔断5秒 List<DegradeRule> rules = new ArrayList<>(); DegradeRule rule = new DegradeRule("userService") .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) // 按异常比例熔断 .setCount(0.5) // 阈值50% .setTimeWindow(5); // 熔断时长5秒 rules.add(rule); DegradeRuleManager.loadRules(rules); } } // 2. 定义熔断降级处理方法 @Component public class UserServiceFallback { // 参数需包含 BlockException public String handleFlowException(String userId, BlockException ex) { return "[SENTINEL] 服务熔断,请稍后重试"; } } // 3. 通过注解绑定熔断方法 @DubboReference public class ConsumerService { @SentinelResource( value = "userService", blockHandler = "handleFlowException", blockHandlerClass = UserServiceFallback.class // 指定降级类 ) public String callUserService(String userId) { return userService.getUserInfo(userId); } } ``` > **原理说明**: > - Mock 降级:Dubbo 在 RPC 调用失败时自动调用 Mock 类[^3]。 > - Sentinel 熔断:通过滑动窗口统计失败率,触发熔断后直接跳转到降级方法[^4]。 --- ### Dubbo 与 Spring Cloud 集成实践指南 #### **1. 最佳实践方案** 通过 **Spring Cloud Alibaba Dubbo** 实现无缝整合: - **角色定位**:Dubbo 作为高性能 RPC 框架,Spring Cloud 提供配置中心、网关等组件[^2]。 - **架构优势**: ``` ┌─────────────┐ ┌─────────────┐ │ Spring Cloud │──HTTP──▶│ API网关 │──Dubbo RPC──▶│ Dubbo服务集群 │ │ (Feign) │ │(Spring Cloud Gateway) │ └─────────────┘ └─────────────┘ ``` #### **2. 集成步骤** ##### (1) 添加依赖 ```xml <!-- Spring Cloud Alibaba Dubbo --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> <version>2022.0.0.0</version> </dependency> <!-- Nacos 注册中心 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> ``` ##### (2) 配置 Dubbo 和注册中心 ```yaml # application.yml dubbo: scan: base-packages: com.example.service # 服务扫描路径 protocol: name: dubbo port: 20880 registry: address: nacos://localhost:8848 # 使用Nacos spring: cloud: nacos: discovery: server-addr: localhost:8848 ``` ##### (3) 暴露 Dubbo 服务 ```java import org.apache.dubbo.config.annotation.DubboService; // 通过注解暴露服务(原@Service替换为@DubboService) @DubboService public class UserServiceImpl implements UserService { @Override public String getUserInfo(String userId) { return "User: " + userId; } } ``` ##### (4) 通过 Feign 调用 Dubbo 服务 ```java // 声明 Feign 客户端(网关层) @FeignClient(name = "dubbo-provider") public interface UserServiceFeign { @GetMapping("/user/get") String getUserInfo(@RequestParam("userId") String userId); } // 网关路由配置 spring: cloud: gateway: routes: - id: dubbo-route uri: lb://dubbo-provider predicates: - Path=/user/** ``` > **关键点**: > - Dubbo 服务通过 Nacos 注册,Spring Cloud 网关通过 Feign 将 HTTP 请求转换为 Dubbo RPC 调用[^2]。 > - 消费者无需感知 Dubbo,直接调用网关 HTTP 接口。 --- ### Dubbo 服务性能监控与调优方法 #### **1. 监控方案** | **工具** | **功能** | **集成方式** | |------------------|---------------------------------------|----------------------------------| | **Dubbo Admin** | 可视化查看服务状态、调用关系 | 部署独立控制台 | | **Prometheus** | 收集QPS、耗时、错误率等指标 | 通过 `dubbo-metrics-api` 暴露数据 | | **Grafana** | 仪表盘展示监控数据 | 配置 Prometheus 数据源 | #### **2. 关键指标与调优** | **指标** | **优化方法** | |-------------------|-----------------------------------------------------------------------------| | **高调用耗时** | - 启用异步调用:`@DubboReference(async = true)`<br>- 调整线程池:`dubbo.protocol.threads=200` | | **频繁Full GC** | - 限制参数大小:`dubbo.protocol.payload=8388608` (8MB)<br>- 优化序列化:切换为 Kryo 或 Protobuf | | **节点负载不均** | - 调整负载均衡策略:`<dubbo:reference loadbalance="leastactive" />` | #### **3. 配置示例** ##### (1) 暴露 Prometheus 指标 ```yaml # application.yml dubbo: metrics: enabled: true protocol: prometheus # 暴露Prometheus格式指标 port: 9090 # 指标端口 ``` ##### (2) Grafana 仪表盘配置 ```sql # 查询Dubbo服务QPS sum(rate(dubbo_request_total{application="user-service"}[1m])) by (service) ``` ##### (3) 异步调用优化 ```java // 消费者异步调用 @DubboReference(async = true) private UserService userService; public void asyncCall() { // 返回CompletableFuture CompletableFuture<String> future = userService.getUserInfo("1001"); future.whenComplete((result, ex) -> { if (ex != null) System.err.println("调用失败"); else System.out.println("结果: " + result); }); } ``` > **调优建议**: > - **网络瓶颈**:启用 `dubbo.protocol.keepalive=true` 复用连接。 > - **内存泄漏**:分析堆转储文件(Heap Dump),排查未释放的 Invoker 对象[^4]。 --- ### 总结 1. **熔断降级**: - 优先使用 Sentinel 实现精细化的熔断规则[^4]。 - Mock 机制适合简单降级场景[^3]。 2. **Spring Cloud 集成**: - 通过 Spring Cloud Alibaba Dubbo 实现协议转换[^2]。 - 网关层屏蔽 RPC 细节,保持技术栈统一。 3. **性能监控**: - Prometheus + Grafana 监控核心指标(QPS/耗时/错误率)。 - 异步调用和线程池优化解决高并发瓶颈。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值