计算机科学领域的所有问题都可以通过其他方式间接解决。
代码下载地址:https://github.com/f641385712/netflix-learning
关于Ribbon核心包ribbon-core
的API前3篇已经介绍完了,本篇收收尾,介绍其内置的几个“工具”,因为在实践过程中也会使用到,如好用的线程调度工具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
配置文件。
资源加载工具类。
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)
也能定位到此文件)。
它是一个异常类型(非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
:不知道啥错误类型就用它。比如: CONFIGURATION
:解析配置时抛错。 NUMBEROF_RETRIES_EXEEDED
:同一台服务超过重试数量。 NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED
:超过在nextServer的重试数量。SOCKET_TIMEOUT_EXCEPTION
:链接超时。 READ_TIMEOUT_EXCEPTION
:读取超时。 UNKNOWN_HOST_EXCEPTION
:不明主机host(异常类型UnknownHostException
)。CONNECT_EXCEPTION
:连接失败(异常类型ConnectException
)。NO_ROUTE_TO_HOST_EXCEPTION
:路由失败(异常类型NoRouteToHostException
)。CLIENT_THROTTLED
:Client抛出的异常。比如返回状态是4xxSERVER_THROTTLED
:服务端抛出的异常。比如返回状态码是5xxCACHE_MISSING
:未命中缓存。这些异常类型先混个脸熟,在讲述负载均衡执行Client时会再次遇到~
关于ribbon-core
包下的所有API就全部介绍完了,任何组件的core包一般都是最重要的,它具有概念最核心、接口最抽象等特点,基本上理解了core包就能答题掌握框架是干什么用的,如何工作的。但是,但是,但是,Ribbon稍显特殊,因为接下来讲解的ribbon-loadbalance
内容才是它的重难点,敬请持续关注。