天天看点

[享学Netflix] 四十一、Ribbon核心API源码解析:ribbon-core(四)ClientException客户端异常

计算机科学领域的所有问题都可以通过其他方式间接解决。

代码下载地址:https://github.com/f641385712/netflix-learning

前言

关于Ribbon核心包

ribbon-core

的API前3篇已经介绍完了,本篇收收尾,介绍其内置的几个“工具”,因为在实践过程中也会使用到,如好用的线程调度工具

ScheduledThreadPoolExectuorWithDynamicSize

等,所以本文就过一把。

正文

ScheduledThreadPoolExectuorWithDynamicSize

它是对JDK源生的任务调度线程池的

java.util.concurrent.ScheduledThreadPoolExecutor

的一个扩展:它能让你的线程池的coreSize核心数动态实时生效。

public class ScheduledThreadPoolExectuorWithDynamicSize extends ScheduledThreadPoolExecutor {
	...
	// 注意此处使用的DynamicIntProperty,因此具有动态性,且是实时的哦
	// ThreadFactory:生产Thread,用于任务执行
	public ScheduledThreadPoolExectuorWithDynamicSize(final DynamicIntProperty corePoolSize, ThreadFactory threadFactory) {
		super(corePoolSize.get(), threadFactory);

		// 通过DynamicIntProperty的回调机制实现coreSize的动态调整
		corePoolSize.addCallback(() -> {setCorePoolSize(corePoolSize.get());});
		...
	}
}           

复制

可见,它能做到改了后立即生效,这是通过

DynamicIntProperty

的callback回调机制实现的。

说明:这里的立即也是有时间差的,依托于

DynamicIntProperty

的动态轮询机制:

delayMillis=60000

默认是60s轮询一次文件的变化,具体值可参考

com.netflix.config.FixedDelayPollingScheduler

代码示例

@Test
public void fun6() throws InterruptedException {
    // =========准备一个动态属性==========
    DynamicIntProperty poolCoreSize = DynamicPropertyFactory.getInstance().getIntProperty("myThreadPoolCoreSize", 2);
    ThreadFactory threadFactory = new ThreadFactory() {
        private AtomicInteger count = new AtomicInteger();

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("myThreadPrefix-" + count.incrementAndGet());
            thread.setDaemon(true);
            return thread;
        }
    };

    ScheduledThreadPoolExectuorWithDynamicSize exectuor = new ScheduledThreadPoolExectuorWithDynamicSize(poolCoreSize, threadFactory);

    // 启动3个定时任务(默认coreSize是2哦)
    for (int i = 1; i <= 3; i++) {
        int index = i;
        exectuor.scheduleAtFixedRate(() -> {
            String currThreadName = Thread.currentThread().getName();
            int corePoolSize = exectuor.getCorePoolSize();
            System.out.printf("我是%s号任务,线程名是[%s],线程池核心数是:%s\n", index, currThreadName, corePoolSize);
        }, index, 3, TimeUnit.SECONDS);
    }

    // 阻塞主线程
    TimeUnit.MINUTES.sleep(100);
}           

复制

准备配置文件

config.properties

myThreadPoolCoreSize=2           

复制

运行程序10秒钟后,我改动配置文件的值为

myThreadPoolCoreSize=3

,控制台打印:

...
我是1号任务,线程名是[myThreadPrefix-1],线程池核心数是:2
我是2号任务,线程名是[myThreadPrefix-2],线程池核心数是:2
我是3号任务,线程名是[myThreadPrefix-1],线程池核心数是:2
我是1号任务,线程名是[myThreadPrefix-2],线程池核心数是:2
我是2号任务,线程名是[myThreadPrefix-1],线程池核心数是:2
17:23:36.690 [pollingConfigurationSource] DEBUG com.netflix.config.AbstractPollingScheduler - Polling started
17:23:36.691 [pollingConfigurationSource] DEBUG com.netflix.config.DynamicPropertyUpdater - updating property key [myThreadPoolCoreSize], value [3]
我是3号任务,线程名是[myThreadPrefix-2],线程池核心数是:3
我是1号任务,线程名是[myThreadPrefix-1],线程池核心数是:3
我是2号任务,线程名是[myThreadPrefix-3],线程池核心数是:3
我是3号任务,线程名是[myThreadPrefix-2],线程池核心数是:3
我是1号任务,线程名是[myThreadPrefix-1],线程池核心数是:3
我是2号任务,线程名是[myThreadPrefix-3],线程池核心数是:3
...           

复制

动态改变核心数成功:开始仅有两个线程执行3个任务,改变后有3个线程分别去执行3个任务了,这种动态性支持在线上是非常有意义的。

本例子强依赖于对

Netflix Archaius

动态配置库的掌握,关于它的全文讲解可参考本系列前面文章有非常详细的讲解。另外,要想文件修改生效,请务必重新编译

config.properties

配置文件。

Resources

资源加载工具类。

public abstract class Resources {

	// 有且仅有一个方法
	public static URL getResource(String resourceName) {
		URL url = null;
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		...
			//1、使用ClassLoader从context classpath里加载资源
			url = loader.getResource(resourceName);
			//2、从system classpath下加载资源
			url = ClassLoader.getSystemResource(resourceName);
			
			//3、通过文件系统加载资源
			resourceName = URLDecoder.decode(resourceName, "UTF-8");
			url = (new File(resourceName)).toURI().toURL();
		...
		return url;
	}
}           

复制

使用示例:

@Test
public void fun7(){
    URL resource = Resources.getResource("config.properties");
    System.out.println(resource);
}           

复制

因为

config.properties

就在工程的classpath下面,所以它会被

loader.getResource(resourceName)

这句代码找到(其实

ClassLoader.getSystemResource(resourceName)

也能定位到此文件)。

ClientException

它是一个异常类型(非Runtime异常),客户端Client的执行过程中抛出的均是此种异常。比如你工作中常见的异常信息:

com.netflix.client.ClientException: Load balancer does not have available server for client           

复制

它表示Client在执行时,使用Loadbalancer计算时木有可用的Server了。

public class ClientException extends Exception{

    protected int errorCode;
    protected String message; // 错误消息
    protected Object errorObject; // 错误实体
    protected ErrorType errorType = ErrorType.GENERAL;
}           

复制

该异常保存了出错误时的错误状态码、错误消息、错误实体等。当然最最最为重要的当属这个错误类型枚举:

ClientException:

    public enum ErrorType{
        GENERAL, 
        CONFIGURATION, 
        NUMBEROF_RETRIES_EXEEDED, 
        NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED, 
        SOCKET_TIMEOUT_EXCEPTION, 
        READ_TIMEOUT_EXCEPTION,
        UNKNOWN_HOST_EXCEPTION,
        CONNECT_EXCEPTION,
        CLIENT_THROTTLED,
        SERVER_THROTTLED,
        NO_ROUTE_TO_HOST_EXCEPTION,
        CACHE_MISSING;
	}           

复制

  • GENERAL

    :不知道啥错误类型就用它。比如:
    • Unable to execute RestClient request for URI:
    • Load balancer does not have available server for client:
    • Invalid Server for :
    • Request contains no HOST to talk to
  • CONFIGURATION

    :解析配置时抛错。
    • Unable to InitializeAndAssociateNFLoadBalancer set for RestClient:
    • Unable to get an instance of CommonClientConfigKey.NIWSServerListFilterClassName. Configured class:
  • NUMBEROF_RETRIES_EXEEDED

    :同一台服务超过重试数量。
    • Number of retries exceeded max 1 retries, while making a call for:
  • NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED

    :超过在nextServer的重试数量。
  • SOCKET_TIMEOUT_EXCEPTION

    :链接超时。
    • Unable to execute RestClient request for URI:
  • READ_TIMEOUT_EXCEPTION

    :读取超时。
    • Unable to execute RestClient request for URI:
  • UNKNOWN_HOST_EXCEPTION

    :不明主机host(异常类型

    UnknownHostException

    )。
  • CONNECT_EXCEPTION

    :连接失败(异常类型

    ConnectException

    )。
  • NO_ROUTE_TO_HOST_EXCEPTION

    :路由失败(异常类型

    NoRouteToHostException

    )。
  • CLIENT_THROTTLED

    :Client抛出的异常。比如返回状态是4xx
  • SERVER_THROTTLED

    :服务端抛出的异常。比如返回状态码是5xx
  • CACHE_MISSING

    :未命中缓存。

这些异常类型先混个脸熟,在讲述负载均衡执行Client时会再次遇到~

总结

关于

ribbon-core

包下的所有API就全部介绍完了,任何组件的core包一般都是最重要的,它具有概念最核心、接口最抽象等特点,基本上理解了core包就能答题掌握框架是干什么用的,如何工作的。但是,但是,但是,Ribbon稍显特殊,因为接下来讲解的

ribbon-loadbalance

内容才是它的重难点,敬请持续关注。

[享学Netflix] 四十一、Ribbon核心API源码解析:ribbon-core(四)ClientException客户端异常