当你想在你的代码中找到一个错误时,这很难;当你认为你的代码是不会有错误时,这就更难了。
–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning
目录
前言
又是一个上下文概念。通过这么多篇的源码研究,发现Context上下文是常常遇到的一种“设计模式”,比如我们最为熟悉的ApplicationContext
就是典型的Spring上下文。
百度百科解释上下文含义:即语境、语意,是语言学科(语言学、社会语言学、篇章分析、语用学、符号学等)的概念。
程序中的上下文含义:通俗一点,叫它环境更好。每一段代码的执行都设计到很多“局部变量”,而这些值的集合就叫上下文。
正文
IClient在执行的时候可能过程冗长,会伴随着很多的环境因素(如各种组件、变量等),特备是当具有负载均衡功能的客户端执行时,这将变得更为复杂,因此Ribbon使用了Context上下文的概念来保持每次执行的状态,这边是LoadBalancerContext
。
LoadBalancerContext
负载均衡上下文。它的作用是提供了IClient
在执行时一系列的方法,从而获取到执行过程中的一些列信息~
说明:每次的执行均会有一个所属的上下文实例~
成员属性
public class LoadBalancerContext implements IClientConfigAware {
protected String clientName = "default";
protected String vipAddresses;
protected int maxAutoRetriesNextServer = DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER;
protected int maxAutoRetries = DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES;
protected RetryHandler defaultRetryHandler = new DefaultLoadBalancerRetryHandler();
protected boolean okToRetryOnAllOperations = DefaultClientConfigImpl.DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS.booleanValue();
private ILoadBalancer lb;
private volatile Timer tracer;
}
clientName
:取值为clientConfig.getClientName()
,若你木有指定就是defaultvipAddresses
:此值通过配置,然后经过VipAddressResolver#resolveDeploymentContextbasedVipAddresses
解析而来- 值使用的key是:
DeploymentContextBasedVipAddresses
,如<ClientName>.ribbon.DeploymentContextBasedVipAddresses={aaa}movieservice
无默认值,它一般用于在集合eureka使用时会配置上- 该值可以配置多个,使用,分隔
- 解析器的key使用的是:
VipAddressResolverClassName
默认值是com.netflix.client.SimpleVipAddressResolver
- 值使用的key是:
maxAutoRetriesNextServer/maxAutoRetries
:这两个参数解释过多次,此处不再重复解释defaultRetryHandler
:重试处理器,默认使用的DefaultLoadBalancerRetryHandler
,你可通过set方法指定okToRetryOnAllOperations
:是有允许所有操作都执行重试,默认是false- 通过key:
OkToRetryOnAllOperations
配置
- 通过key:
lb
:负载均衡器,通过构造器传入
以上属性在构造阶段/initWithNiwsConfig阶段完成初始化赋值。
成员方法
LoadBalancerContext
的成员方法中断,普通的get/set方法不用叙述,有必要介绍些执行过程中的必要方法:
LoadBalancerContext:
// 记录一个状态,需要传入rt,所以肯定代表请求已经结束了喽。让是个私有方法,旨在本类被调用
// 1、activeRequestsCount -1
// 2、totalRequests +1(注意:总请求数是在完成时候+1的,而非请求的时候哦)
// 3、记录rt(包括时间窗口收集dataDist以及历史统计的responseTimeDist)
private void recordStats(ServerStats stats, long responseTime) {
if (stats == null) {
return;
}
stats.decrementActiveRequestsCount();
stats.incrementNumRequests();
stats.noteResponseTime(responseTime);
}
// ========重要========此方法表示请求完成后调用
// 请求完成:在接收到响应或从客户端抛出异常(出错)
// response:返回值
public void noteRequestCompletion(ServerStats stats, Object response, Throwable e, long responseTime, RetryHandler errorHandler) {
if (stats == null) {
return;
}
recordStats(stats, responseTime);
// 很明显:callErrorHandler永远不可能为null(排除故意把重拾起set为null的情况)
RetryHandler callErrorHandler = errorHandler == null ? getRetryHandler() : errorHandler;
// 判断看看是否出错了,是否需要重试
// 有response那就是正常返回:该请求正常,那就把重复连续失败的count清零
if (callErrorHandler != null && response != null) {
stats.clearSuccessiveConnectionFailureCount();
} else if (callErrorHandler != null && e != null) {
// 如果是熔断类型的异常,那就连续次数 + 1
// 失败总数也+1(窗口统计)
if (callErrorHandler.isCircuitTrippingException(e)) {
stats.incrementSuccessiveConnectionFailureCount();
stats.addToFailureCount();
// 表示虽然失败了,但是是其它异常,比如你的业务异常,比如NPE
// 那就清零。再次证明:ribbon的熔断只管它自己是被的异常(链接异常)
// 并不管业务异常,业务异常交给hystrix才是合理的
} else {
stats.clearSuccessiveConnectionFailureCount();
}
}
}
// 客户端执行请求之前调用。增加一个活跃连接数
public void noteOpenConnection(ServerStats serverStats) {
serverStats.incrementActiveRequestsCount();
}
这几个方法提供的是Client在执行过程中,对指标的收集。小Tips:noteError/noteResponse
分别代表异常完成/正常完成,但其实都没有被调用过,而是统一调用更高级的noteRequestCompletion()
方法。
LoadBalancerContext:
// 仅仅根据URI就拿到端口
// http 80 / https 443
protected Pair<String, Integer> deriveSchemeAndPortFromPartialUri(URI uri) { ... }
// 从虚拟地址中尝试获取到主机的host和port
// 如果虚拟地址确实包含实际的主机,那就直接拿。如虚拟地址就是:localhost:8080
protected Pair<String, Integer> deriveHostAndPortFromVipAddress(String vipAddress) throws URISyntaxException, ClientException {
...
if (host == null) {
throw new ClientException("Unable to derive host/port from vip address " + vipAddress);
}
int port = uri.getPort();
if (port < 0) {
port = getDefaultPortFromScheme(scheme);
}
if (port < 0) {
throw new ClientException("Unable to derive host/port from vip address " + vipAddress);
}
hostAndPort.setFirst(host);
hostAndPort.setSecond(port);
return hostAndPort;
}
// 检测你配置的vipAddress是否是被认证过的
//若配置了多个,只要有一个符合要求,就返回true
private boolean isVipRecognized(String vipEmbeddedInUri) {
// vipAddresses是上下文中指定的,也就是你配置的,可以使用逗号分隔配置多个
if (vipAddresses == null) {
return false;
}
// 配置了多个的话,就一个一个的检查
// 只要有一个地址值是被认证过的,那就返回true
String[] addresses = vipAddresses.split(",");
for (String address: addresses) {
if (vipEmbeddedInUri.equalsIgnoreCase(address.trim())) {
return true;
}
}
return false;
}
该方法只是尝试去从虚拟地址里拿host和port,可见抛出异常的概率是很大的,因为虚拟地址我们一般写服务名(不过这里似乎告诉我们:虚拟地址写地址值也是欧克的),如果没有可用的负载均衡器,并且请求URI不完整。子类可以覆盖此方法(而实际情况是哪怕Spring Cloud下都没有复写过此方法)
URL和URI(小科普)
开源框架经常看到会用到URL或者URI之类的,其实你使用字符串也可以,但很明显那不够逼格。
URI:统一资源标志符(Uniform Resource Identifier)
URL:统一资源定位符(uniform resource location)
URI与URL都是定位资源位置的,就是表示这个资源的位置信息。URI是一种宽泛的含义更广的定义,而URL则是URI的一个子集。另外URL可直接操作文件(如openConnection()方法可获取文件流,但URI不行)
URL提供了一种访问定位因特网上任意资源的手段,但是这些资源可以通过不同的方法(例如HTTP、FTP、SMTP)来访问,他都基本上由9个部分构成:
<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<fragment>
这里只介绍你可能觉得陌生的部分:
user:password
:用户名与密码,一般访问ftp时会用到。但是这个可以不写,不写的话访问时可能会让你输入用户名密码params
:这个很少见,它使用;来连接。向服务器提供额外的参数,略fragment
:我们常说的锚点
每个部分对应的有对应的方法,同样的,此处仅介绍几个偏陌生的方法:
getAuthority()
:简单粗暴的理解为它是getHost() + ":" + getPort()
getRawUserInfo/getUserInfo
:用户名+密码(前者原样,后者decode了一下)
另请注意如下输出:
public static void main(String[] args) {
URI original = URI.create("www.baidu.com:8080");
System.out.println(original.getScheme()); // www.baidu.com(诧异不?)
System.out.println(original.getHost()); // null
System.out.println(original.getPort()); // -1
System.out.println(original.getAuthority()); // null
original = URI.create("tcp://www.baidu.com:8080");
System.out.println(original.getScheme()); // tcp
System.out.println(original.getHost()); // www.baidu.com
System.out.println(original.getPort()); // 8080
System.out.println(original.getAuthority()); // www.baidu.com:8080
}
构造URI时,请显示提供合法的scheme~
重要方法
下面介绍几个非常重要的方法,它们在Client客户端执行过程中占有重要地位。
getServerFromLoadBalancer()
根据负载均衡策略最终最终最终使用决定用哪个Server。该方法是最为重要的一个方法了,没有之一。它会被LoadBalancerCommand#selectServer
调用,用于选择一台合适的Server服务器。
LoadBalancerContext:
//original这个URI可能是无host无ip的,如/api/v1/ping。所以此处需要处理
// 注意:调用此方法两个入参均可能为null哦
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
if (host == null) {
if (lb != null) {
Server svc = lb.chooseServer(loadBalancerKey);
} else {
Pair<String,Integer> hostAndPort = deriveHostAndPortFromVipAddress(vipAddresses);
host = hostAndPort.first();
port = hostAndPort.second();
}
} else {
shouldInterpretAsVip = isVipRecognized(original.getAuthority());
if (shouldInterpretAsVip) {
Server svc = lb.chooseServer(loadBalancerKey);
} else {
logger.debug("Using full URL passed in by caller (not using load balancer): {}", original);
}
}
if (host == null){
throw new ClientException(ClientException.ErrorType.GENERAL,"Request contains no HOST to talk to");
}
return new Server(host, port);
}
以上是简版源码,主要看如下的文字版步骤总结(非常重要)。
该方法从URI original
中,最终目标是得到一个Server(比如包含ip地址、port),它会分为如下情况:
- host不存在:
- lb存在:使用
lb.chooseServer(loadBalancerKey)
选出一台Server - lb不存在:
- 配置了虚拟地址
vipAddresses
:- 有且仅配置了一个值:使用上面介绍的
deriveHostAndPortFromVipAddress(vipAddresses)
把host和port解析出来 ,分隔
配置了多个值:throw new ClientException()
- 有且仅配置了一个值:使用上面介绍的
- 没配置虚拟地址
vipAddresses
:throw new ClientException()
- 配置了虚拟地址
- lb存在:使用
- host存在:
original.getAuthority()
部分就是你配置的虚拟地址值vipAddresses
(这就是虚拟地址值的意义)- 有lb:使用
lb.chooseServer(loadBalancerKey)
选出一台Server - 无lb:
throw new ClientException()
- 有lb:使用
- host也不是
original.getAuthority()
部分,那就当ip地址用。自己new一个Server返回
代码示例
基准代码:
@Test
public void fun1() throws ClientException {
URI original = URI.create("");
List<Server> serverList = new ArrayList<>();
serverList.add(createServer("华南", 1));
serverList.add(createServer("华东", 1));
serverList.add(createServer("华东", 2));
serverList.add(createServer("华北", 1));
serverList.add(createServer("华北", 2));
serverList.add(createServer("华北", 3));
serverList.add(createServer("华北", 4));
BaseLoadBalancer lb = new BaseLoadBalancer();
lb.addServers(serverList);
IClientConfig config = DefaultClientConfigImpl.getClientConfigWithDefaultValues("YourBatman");
LoadBalancerContext loadBalancerContext = new LoadBalancerContext(null, config);
for (int i = 0; i < 5; i++) {
System.out.println(loadBalancerContext.getServerFromLoadBalancer(original, null));
}
}
private Server createServer(String zone, int index) {
Server server = new Server("www.baidu" + zone + ".com", index);
server.setZone(zone);
return server;
}
host不存在:这种情况下url随意,没有任何要求,空都行…
- case1:正如基准代码,因为host为null,所有使用lb负载均衡算法(轮询)选出Server
www.baidu华东.com:1
www.baidu华东.com:2
www.baidu华北.com:1
www.baidu华北.com:2
www.baidu华北.com:3
- case2:不指定lb
new LoadBalancerContext(null, config);
,并且提供配置ribbon.DeploymentContextBasedVipAddresses=http://www.baiud.com:9999
,再次运行程序- 注意:配置里必须以协议打头,如http://绝对不能省略,否则不能被是被为正常的URI。结果抛出异常:
com.netflix.client.ClientException: Unable to derive host/port from vip address www.baiud.com:9999
- 若你此处配置,分隔配了多个,那也抛出异常
- 注意:配置里必须以协议打头,如http://绝对不能省略,否则不能被是被为正常的URI。结果抛出异常:
www.baiud.com:9999
www.baiud.com:9999
www.baiud.com:9999
www.baiud.com:9999
www.baiud.com:9999
host存在:在基准代码上改为URI original = URI.create("http://account:3333");
,这样host就是account
- case1:基于标准代码(有host,有lb,无vipAddress配置)
- 解释:因为虽有lb,但是
getAuthority
部分并不在vipAddresses
里面(因为没配嘛),所以解析为直接的屋里地址来使用了(host+port)
- 解释:因为虽有lb,但是
account:3333
account:3333
account:3333
account:3333
account:3333
- case2:加上配置
ribbon.DeploymentContextBasedVipAddresses=account:3333
- 请注意:此处是拿
getAuthority
来做equals比较,所以千万别家http://前缀,否则不相等就匹配不上,最终还是向上面一样原样输出的
- 请注意:此处是拿
www.baidu华东.com:1
www.baidu华东.com:2
www.baidu华北.com:1
www.baidu华北.com:2
www.baidu华北.com:3
reconstructURIWithServer()
根据一个给定的Server重构URI,让其变得完整。
LoadBalancerContext:
public URI reconstructURIWithServer(Server server, URI original) {
// 若original里已经有了host、port、scheme等,那还解析个啥 你已经是完整的了
String host = server.getHost();
int port = server.getPort();
String scheme = server.getScheme();
if (host.equals(original.getHost())
&& port == original.getPort()
&& scheme == original.getScheme()) {
return original;
}
... // 使用server里的host、port等完成拼接,形成一个完整的URI返回
}
该方法相对简单,主要注意优先级关系就好:
- original有的就使用自己的
- original没有的才用Server的(scheme、host、port)
使用方式
LoadBalancerContext
作为负载均衡器的执行上下文,那必然在执行过程中使用喽。所以它的唯一使用处是在LoadBalancerCommand
里,而它就代表着一个负载均衡执行命令。
另外,还有个有意思的地方是,AbstractLoadBalancerAwareClient
继承自LoadBalancerContext
,也就是说每个Client它自己就是个上下文,可以访问到执行时候的任何环境值。
说明:此处说每个Client是建立在我们认为所有的Client均是
AbstractLoadBalancerAwareClient
的子类的基础上的
总结
关于Ribbon负载均衡器执行上下文:LoadBalancerContext就先介绍到这,此文内容很重要,很重要,重要。了解了上下文,就为继续讲解ILoadBalancer
以及整个执行流程奠定了扎实基础。
声明
原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭
。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
- [享学Netflix] 一、Apache Commons Configuration:你身边的配置管理专家
- [享学Netflix] 二、Apache Commons Configuration事件监听机制及使用ReloadingStrategy实现热更新
- [享学Netflix] 三、Apache Commons Configuration2.x全新的事件-监听机制
- [享学Netflix] 四、Apache Commons Configuration2.x文件定位系统FileLocator和FileHandler
- [享学Netflix] 五、Apache Commons Configuration2.x别样的Builder模式:ConfigurationBuilder
- [享学Netflix] 六、Apache Commons Configuration2.x快速构建工具Parameters和Configurations
- [享学Netflix] 七、Apache Commons Configuration2.x如何实现文件热加载/热更新?
- [享学Netflix] 八、Apache Commons Configuration2.x相较于1.x使用上带来哪些差异?
- [享学Netflix] 九、Archaius配置管理库:初体验及基础API详解
- [享学Netflix] 十、Archaius对Commons Configuration核心API Configuration的扩展实现
- [享学Netflix] 十一、Archaius配置管理器ConfigurationManager和动态属性支持DynamicPropertySupport
- [享学Netflix] 十二、Archaius动态属性DynamicProperty原理详解(重要)
- [享学Netflix] 十三、Archaius属性抽象Property和PropertyWrapper详解
- [享学Netflix] 十四、Archaius如何对多环境、多区域、多云部署提供配置支持?
- [享学Netflix] 十五、Archaius和Spring Cloud的集成:spring-cloud-starter-netflix-archaius
- [享学Netflix] 十六、Hystrix断路器:初体验及RxJava简介
- [享学Netflix] 十七、Hystrix属性抽象以及和Archaius整合实现配置外部化、动态化
- [享学Netflix] 十八、Hystrix配置之:全局配置和实例配置
- [享学Netflix] 十九、Hystrix插件机制:SPI接口介绍和HystrixPlugins详解
- [享学Netflix] 二十、Hystrix跨线程传递数据解决方案:HystrixRequestContext
- [享学Netflix] 二十一、Hystrix指标数据收集(预热):滑动窗口算法(附代码示例)
- [享学Netflix] 二十二、Hystrix事件源与事件流:HystrixEvent和HystrixEventStream
- [享学Netflix] 二十三、Hystrix桶计数器:BucketedCounterStream
- [享学Netflix] 二十四、Hystrix在滑动窗口内统计:BucketedRollingCounterStream、HealthCountsStream
- [享学Netflix] 二十五、Hystrix累计统计流、分发流、最大并发流、配置流、功能流(附代码示例)
- [享学Netflix] 二十六、Hystrix指标数据收集器:HystrixMetrics(HystrixDashboard的数据来源)
- [享学Netflix] 二十七、Hystrix何为断路器的半开状态?HystrixCircuitBreaker详解
- [享学Netflix] 二十八、Hystrix事件计数器EventCounts和执行结果ExecutionResult
- [享学Netflix] 二十九、Hystrix执行过程核心接口:HystrixExecutable、HystrixObservable和HystrixInvokableInfo
- [享学Netflix] 三十、Hystrix的fallback回退/降级逻辑源码解读:getFallbackOrThrowException
- [享学Netflix] 三十一、Hystrix触发fallback降级逻辑的5种情况及代码示例
- [享学Netflix] 三十二、Hystrix抛出HystrixBadRequestException异常为何不会触发熔断?
- [享学Netflix] 三十三、Hystrix执行目标方法时,如何调用线程池资源?
- [享学Netflix] 三十四、Hystrix目标方法执行逻辑源码解读:executeCommandAndObserve
- [享学Netflix] 三十五、Hystrix执行过程集大成者:AbstractCommand详解
- [享学Netflix] 三十六、Hystrix请求命令:HystrixCommand和HystrixObservableCommand
- [享学Netflix] 三十七、源生Ribbon介绍 — 客户端负载均衡器
- [享学Netflix] 三十八、Ribbon核心API源码解析:ribbon-core(一)IClient请求客户端
- [享学Netflix] 三十九、Ribbon核心API源码解析:ribbon-core(二)IClientConfig配置详解
- [享学Netflix] 四十、Ribbon核心API源码解析:ribbon-core(三)RetryHandler重试处理器
- [享学Netflix] 四十一、Ribbon核心API源码解析:ribbon-core(四)ClientException客户端异常
- [享学Netflix] 四十二、Ribbon的LoadBalancer五大组件之:IPing心跳检测
- [享学Netflix] 四十三、Ribbon的LoadBalancer五大组件之:ServerList服务列表
- [享学Netflix] 四十四、netflix-statistics详解,手把手教你写个超简版监控系统
- [享学Netflix] 四十五、Ribbon服务器状态:ServerStats及其断路器原理
- [享学Netflix] 四十六、Ribbon负载均衡策略服务器状态总控:LoadBalancerStats
- [享学Netflix] 四十七、Ribbon多区域选择:ZoneAvoidanceRule.getAvailableZones()获取可用区
- [享学Netflix] 四十八、Ribbon服务器过滤逻辑的基础组件:AbstractServerPredicate
- [享学Netflix] 四十九、Ribbon的LoadBalancer五大组件之:ServerListFilter服务列表过滤器
- [享学Netflix] 五十、Ribbon的LoadBalancer五大组件之:ServerListUpdater服务列表更新器
- [享学Netflix] 五十一、Ribbon的LoadBalancer五大组件之:IRule(一)轮询和加权轮询
- [享学Netflix] 五十二、Ribbon的LoadBalancer五大组件之:IRule(二)应用于大规模集群的可配置规则
- [享学Netflix] 五十三、Ribbon的LoadBalancer五大组件之:IRule(三)随机和重试,所有IRule实现总结
- [享学Netflix] 五十四、Ribbon启动连接操作:IPrimeConnection检测Server是否能够提供服务