我在服务端引用那篇文章里面分析到,服务端在引用的时候会去获取服务端可用的服务,并进行心跳,维护一个可用的集合。

所以我们从客户端初始化这部分说起。

服务连接的维护

客户端初始化的时候会调用cluster#init方法,这里的cluster是继承了AbstractCLuster抽象类,调用的是抽象类里面的init方法。

public synchronized void init() {
if (initialized) { // 已初始化
return;
}
// 构造Router链
routerChain = RouterChain.buildConsumerChain(consumerBootstrap);
// 负载均衡策略 考虑是否可动态替换?
loadBalancer = LoadBalancerFactory.getLoadBalancer(consumerBootstrap);
// 地址管理器
addressHolder = AddressHolderFactory.getAddressHolder(consumerBootstrap);
// 连接管理器
connectionHolder = ConnectionHolderFactory.getConnectionHolder(consumerBootstrap);
// 构造Filter链,最底层是调用过滤器
this.filterChain = FilterChain.buildConsumerChain(this.consumerConfig,
new ConsumerInvoker(consumerBootstrap)); if (consumerConfig.isLazy()) { // 延迟连接
if (LOGGER.isInfoEnabled(consumerConfig.getAppName())) {
LOGGER.infoWithApp(consumerConfig.getAppName(), "Connection will be initialized when first invoke.");
}
} // 启动重连线程
connectionHolder.init();
try {
// 得到服务端列表
List<ProviderGroup> all = consumerBootstrap.subscribe();
if (CommonUtils.isNotEmpty(all)) {
// 初始化服务端连接(建立长连接)
updateAllProviders(all);
}
} catch (SofaRpcRuntimeException e) {
throw e;
} catch (Throwable e) {
throw new SofaRpcRuntimeException("Init provider's transport error!", e);
} // 启动成功
initialized = true; // 如果check=true表示强依赖
if (consumerConfig.isCheck() && !isAvailable()) {
throw new SofaRpcRuntimeException("The consumer is depend on alive provider " +
"and there is no alive provider, you can ignore it " +
"by ConsumerConfig.setCheck(boolean) (default is false)");
}
}

这上面在服务连接的维护上面主要分为三步:

  1. 设置心跳线程,每10秒进行一次心跳
  2. 获取服务端列表
  3. 初始化服务端连接

1.SOFARPC的心跳线程

AllConnectConnectionHolder#init

这里connectionHolder是AllConnectConnectionHolder的实现类,我们进入到这个类里面看。这里面实际上实现了SOFARPC的心跳检测。


/**
* 重连线程
*/
private volatile ScheduledService reconThread; public void init() {
//如果reconThread没有初始化过,调用startReconnectThread进行初始化
if (reconThread == null) {
startReconnectThread();
}
} protected void startReconnectThread() {
final String interfaceId = consumerConfig.getInterfaceId();
// 启动线程池
// 默认每隔10秒重连
int reconnect = consumerConfig.getReconnectPeriod();
if (reconnect > 0) {
reconnect = Math.max(reconnect, 2000); // 最小2000
reconThread = new ScheduledService("CLI-RC-" + interfaceId, ScheduledService.MODE_FIXEDDELAY, new
Runnable() {
@Override
public void run() {
try {
doReconnect();
} catch (Throwable e) {
LOGGER.errorWithApp(consumerConfig.getAppName(),
"Exception when retry connect to provider", e);
}
}
}, reconnect, reconnect, TimeUnit.MILLISECONDS).start();
}
}

在startReconnectThread方法中,客户端会调用reconnectPeriod变量,如果没有设置则为10秒,如果设置小于10秒则取2秒。也就是说客户端开启的心跳是默认10秒一次,最快也是只能2秒一次。

然后创建了一个ScheduledService实例,并调用其start方法。

我们看一下ScheduledService类是怎么样的结构

ScheduledService

public class ScheduledService {

    private volatile ScheduledExecutorService scheduledExecutorService;

    public ScheduledService(String threadName,
int mode,
Runnable runnable,
long initialDelay,
long period,
TimeUnit unit) {
this.threadName = threadName;
this.runnable = runnable;
this.initialDelay = initialDelay;
this.period = period;
this.unit = unit;
this.mode = mode;
} //开始执行定时任务
public synchronized ScheduledService start() {
if (started) {
return this;
}
if (scheduledExecutorService == null) {
scheduledExecutorService = new ScheduledThreadPoolExecutor(1,
new NamedThreadFactory(threadName, true));
}
ScheduledFuture future = null;
//传进来的是MODE_FIXEDDELAY
switch (mode) {
case MODE_FIXEDRATE:
future = scheduledExecutorService.scheduleAtFixedRate(runnable, initialDelay,
period,
unit);
break;
case MODE_FIXEDDELAY:
//创建一个固定延迟的定时任务
future = scheduledExecutorService.scheduleWithFixedDelay(runnable, initialDelay, period,
unit);
break;
default:
break;
}
if (future != null) {
this.future = future;
// 缓存一下
SCHEDULED_SERVICE_MAP.put(this, System.currentTimeMillis());
started = true;
} else {
started = false;
}
return this;
}
}

ScheduledService的作用就是创建一个固定延迟的线程,以固定的时间定时执行一下任务。

然后会默认每10秒钟执行一次AllConnectConnectionHolder的doReconnect方法。

AllConnectConnectionHolder#doReconnect


/**
* 存活的客户端列表(保持了长连接,且一切正常的)
*/
protected ConcurrentMap<ProviderInfo, ClientTransport> aliveConnections = new ConcurrentHashMap<ProviderInfo, ClientTransport>();
/**
* 失败待重试的客户端列表(连上后断开的)
*/
protected ConcurrentMap<ProviderInfo, ClientTransport> retryConnections = new ConcurrentHashMap<ProviderInfo, ClientTransport>(); private void doReconnect() {
//获取配置的接口
String interfaceId = consumerConfig.getInterfaceId();
//获取应用名
String appName = consumerConfig.getAppName();
int thisTime = reconnectFlag.incrementAndGet();
boolean print = thisTime % 6 == 0; //是否打印error,每6次打印一次
// 可用的连接集合是否为空
boolean isAliveEmptyFirst = isAvailableEmpty();
// 检查可用连接 todo subHealth
for (Map.Entry<ProviderInfo, ClientTransport> alive : aliveConnections.entrySet()) {
ClientTransport connection = alive.getValue();
//如果该连接不可用,那么就将该连接从可用连接集合里剔除放入到重试集合里面
if (connection != null && !connection.isAvailable()) {
aliveToRetry(alive.getKey(), connection);
}
}
//遍历所有待重试集合
for (Map.Entry<ProviderInfo, ClientTransport> entry : getRetryConnections()
.entrySet()) {
ProviderInfo providerInfo = entry.getKey();
int providerPeriodCoefficient = CommonUtils.parseNum((Integer)
providerInfo.getDynamicAttr(ProviderInfoAttrs.ATTR_RC_PERIOD_COEFFICIENT), 1);
if (thisTime % providerPeriodCoefficient != 0) {
continue; // 如果命中重连周期,则进行重连
}
ClientTransport transport = entry.getValue();
if (LOGGER.isDebugEnabled(appName)) {
LOGGER.debugWithApp(appName, "Retry connect to {} provider:{} ...", interfaceId, providerInfo);
}
try {
//重连
transport.connect();
//重连完检查一下该连接是否可用
if (doubleCheck(interfaceId, providerInfo, transport)) {
providerInfo.setDynamicAttr(ProviderInfoAttrs.ATTR_RC_PERIOD_COEFFICIENT, 1);
//如果该连接可用,则把该连接从重试集合里移除,加入到可用集合里
retryToAlive(providerInfo, transport);
}
} catch (Exception e) {
if (print) {
if (LOGGER.isWarnEnabled(appName)) {
LOGGER.warnWithApp(appName, "Retry connect to {} provider:{} error ! The exception is " + e
.getMessage(), interfaceId, providerInfo);
}
} else {
if (LOGGER.isDebugEnabled(appName)) {
LOGGER.debugWithApp(appName, "Retry connect to {} provider:{} error ! The exception is " + e
.getMessage(), interfaceId, providerInfo);
}
}
}
}
if (isAliveEmptyFirst && !isAvailableEmpty()) { // 原来空,变成不空
notifyStateChangeToAvailable();
}
}

这个doReconnect方法里面主要做了以下几件事:

  1. 检查可用连接集合,如果该连接不可用,那么就将该连接从可用连接集合里剔除放入到重试集合里面。
  2. 遍历所有待重试集合,如果该thisTime和providerPeriodCoefficient取模为零,那么就进行重连。
  3. 设置监听器。

这里有个细节,在aliveToRetry方法里面是加锁的,尽管aliveConnections和retryConnections都是安全的集合,但是这里有一个if判断,这两步操作并不是线程安全的。

protected void aliveToRetry(ProviderInfo providerInfo, ClientTransport transport) {
providerLock.lock();
try {
//这里两步操作并不是原子性的,所以需要加锁
if (aliveConnections.remove(providerInfo) != null) {
retryConnections.put(providerInfo, transport);
}
} finally {
providerLock.unlock();
}
}

由于我们这里并不分析网络是怎么传输和连接的,所以暂时不分析transport#connect,大家只要知道这里是保持一个长连接的就可以了。

接下来我们再看一下doubleCheck方法:

protected boolean doubleCheck(String interfaceId, ProviderInfo providerInfo, ClientTransport transport) {
if (transport.isAvailable()) {
try { // 睡一下下 防止被连上又被服务端踢下线
Thread.sleep(100);
} catch (InterruptedException e) {
// ignore
}
if (transport.isAvailable()) { // double check
return true;
} else { // 可能在黑名单里,刚连上就断开了
if (LOGGER.isWarnEnabled(consumerConfig.getAppName())) {
LOGGER.warnWithApp(consumerConfig.getAppName(),
"Connection has been closed after connected (in last 100ms)!" +
" Maybe connectionNum of provider has been reached limit," +
" or your host is in the blacklist of provider {}/{}",
interfaceId, transport.getConfig().getProviderInfo());
}
providerInfo.setDynamicAttr(ProviderInfoAttrs.ATTR_RC_PERIOD_COEFFICIENT, 5);
return false;
}
} else {
return false;
}
}

这里面主要是检查一下连接的稳定性,如果一开始连接成功,在100ms内又断开连接,那么就打出警告日志,当看到这个日志在后台的时候需要我们查看一下网络连接的情况。

然后再把reconnectCoefficient属性设置为5,当thisTime与providerPeriodCoefficient取模为0的时候再次尝试连接,其中如果按默认设置的话,需要50秒才会进行重连。

2. 获取服务列表

调用consumerBootstrap#subscribe方法进行获取服务列表,会进入到抽象类DefaultConsumerBootstrap的subscribe方法中。

DefaultConsumerBootstrap#subscribe

public List<ProviderGroup> subscribe() {
List<ProviderGroup> result = null;
String directUrl = consumerConfig.getDirectUrl();
if (StringUtils.isNotEmpty(directUrl)) {
// 如果走直连
result = subscribeFromDirectUrl(directUrl);
} else {
// 没有配置url直连
List<RegistryConfig> registryConfigs = consumerConfig.getRegistry();
if (CommonUtils.isNotEmpty(registryConfigs)) {
// 从多个注册中心订阅服务列表
result = subscribeFromRegistries();
}
}
return result;
}

这里分成两步:

  1. 如果在客户端设置了直连地址的话则调用subscribeFromDirectUrl方法。
  2. 如果没有配置直接地址则获取注册中心后调用subscribeFromRegistries方法。

DefaultConsumerBootstrap#subscribeFromDirectUrl

这个方法里面主要是将直连地址用“;”拆分,然后封装成provider放入直连分组集合中。

protected List<ProviderGroup> subscribeFromDirectUrl(String directUrl) {
List<ProviderGroup> result = new ArrayList<ProviderGroup>();
List<ProviderInfo> tmpProviderInfoList = new ArrayList<ProviderInfo>();
//拆分url,多个url可以用“;”分割
String[] providerStrs = StringUtils.splitWithCommaOrSemicolon(directUrl);
for (String providerStr : providerStrs) {
ProviderInfo providerInfo = convertToProviderInfo(providerStr);
if (providerInfo.getStaticAttr(ProviderInfoAttrs.ATTR_SOURCE) == null) {
providerInfo.setStaticAttr(ProviderInfoAttrs.ATTR_SOURCE, "direct");
}
tmpProviderInfoList.add(providerInfo);
}
//加入直连分组
result.add(new ProviderGroup(RpcConstants.ADDRESS_DIRECT_GROUP, tmpProviderInfoList));
return result;
}

DefaultConsumerBootstrap#subscribeFromRegistries

protected List<ProviderGroup> subscribeFromRegistries() {
List<ProviderGroup> result = new ArrayList<ProviderGroup>();
List<RegistryConfig> registryConfigs = consumerConfig.getRegistry();
//没有配置注册中心,直接返回
if (CommonUtils.isEmpty(registryConfigs)) {
return result;
}
// 是否等待结果
int addressWaitTime = consumerConfig.getAddressWait();
int maxAddressWaitTime = SofaConfigs.getIntegerValue(consumerConfig.getAppName(),
SofaOptions.CONFIG_MAX_ADDRESS_WAIT_TIME, SofaOptions.MAX_ADDRESS_WAIT_TIME);
addressWaitTime = addressWaitTime < 0 ? maxAddressWaitTime : Math.min(addressWaitTime, maxAddressWaitTime); ProviderInfoListener listener = consumerConfig.getProviderInfoListener();
//设置CountDownLatch用来等待
respondRegistries = addressWaitTime == 0 ? null : new CountDownLatch(registryConfigs.size()); // 从注册中心订阅 {groupName: ProviderGroup}
Map<String, ProviderGroup> tmpProviderInfoList = new HashMap<String, ProviderGroup>();
for (RegistryConfig registryConfig : registryConfigs) {
Registry registry = RegistryFactory.getRegistry(registryConfig);
registry.init();
registry.start(); try {
List<ProviderGroup> current;
try {
if (respondRegistries != null) {
consumerConfig.setProviderInfoListener(new WrapperClusterProviderInfoListener(listener,
respondRegistries));
}
current = registry.subscribe(consumerConfig);
} finally {
if (respondRegistries != null) {
consumerConfig.setProviderInfoListener(listener);
}
}
if (current == null) {
continue; // 未同步返回结果
} else {
if (respondRegistries != null) {
respondRegistries.countDown();
}
}
for (ProviderGroup group : current) { // 当前注册中心的
String groupName = group.getName();
if (!group.isEmpty()) {
ProviderGroup oldGroup = tmpProviderInfoList.get(groupName);
if (oldGroup != null) {
oldGroup.addAll(group.getProviderInfos());
} else {
tmpProviderInfoList.put(groupName, group);
}
}
}
} catch (SofaRpcRuntimeException e) {
throw e;
} catch (Throwable e) {
String appName = consumerConfig.getAppName();
if (LOGGER.isWarnEnabled(appName)) {
LOGGER.warnWithApp(appName,
"Catch exception when subscribe from registry: " + registryConfig.getId()
+ ", but you can ignore if it's called by JVM shutdown hook", e);
}
}
}
if (respondRegistries != null) {
try {
respondRegistries.await(addressWaitTime, TimeUnit.MILLISECONDS);
} catch (Exception ignore) { // NOPMD
}
}
return new ArrayList<ProviderGroup>(tmpProviderInfoList.values());
}

这个这么长的方法实际上做了那么几件事:

  1. 遍历注册中心
  2. 初始化注册中心,然后订阅注册中心,以异步的方式拉去provider
  3. 如果设置了等待,那么就等待一段时间后返回

3. 初始化服务端连接

如果在调用consumerBootstrap#subscribe()后不是异步获取,返回的就不是null,那么就会进入到updateAllProviders,所以我们来看一下这个方法里面做了什么。

DefaultConsumerBootstrap#updateAllProviders

public void updateAllProviders(List<ProviderGroup> providerGroups) {
List<ProviderGroup> oldProviderGroups = new ArrayList<ProviderGroup>(addressHolder.getProviderGroups());
int count = 0;
if (providerGroups != null) {
for (ProviderGroup providerGroup : providerGroups) {
//校验检查providerGroup里面的元素是不是为空
//消费者的配置的protocol是不是和provider 的protocol相同
checkProviderInfo(providerGroup);
count += providerGroup.size();
}
}
//走到这里说明没有provider
if (count == 0) {
Collection<ProviderInfo> currentProviderList = currentProviderList();
addressHolder.updateAllProviders(providerGroups);
if (CommonUtils.isNotEmpty(currentProviderList)) {
if (LOGGER.isWarnEnabled(consumerConfig.getAppName())) {
LOGGER.warnWithApp(consumerConfig.getAppName(), "Provider list is emptied, may be all " +
"providers has been closed, or this consumer has been add to blacklist");
closeTransports();
}
}
} else {
addressHolder.updateAllProviders(providerGroups);
connectionHolder.updateAllProviders(providerGroups);
}
if (EventBus.isEnable(ProviderInfoUpdateAllEvent.class)) {
ProviderInfoUpdateAllEvent event = new ProviderInfoUpdateAllEvent(consumerConfig, oldProviderGroups,
providerGroups);
EventBus.post(event);
}
}

其实这个方法里面就做了一件事情,那就是把provider放入到addressHolder和connectionHolder中。

故障剔除

客户端在引用的时候会调用FailoverCluster#doInvoke方法,然后调用父类的select进行路由和负载均衡选用合适的provider。

AbstractCluster#doInvoke

public SofaResponse doInvoke(SofaRequest request) throws SofaRpcException {
String methodName = request.getMethodName();
int retries = consumerConfig.getMethodRetries(methodName);
int time = 0;
SofaRpcException throwable = null;// 异常日志
List<ProviderInfo> invokedProviderInfos = new ArrayList<ProviderInfo>(retries + 1);
do {
//负载均衡
ProviderInfo providerInfo = select(request, invokedProviderInfos);
try {
//调用过滤器链
SofaResponse response = filterChain(providerInfo, request);
if (response != null) {
if (throwable != null) {
if (LOGGER.isWarnEnabled(consumerConfig.getAppName())) {
LOGGER.warnWithApp(consumerConfig.getAppName(),
LogCodes.getLog(LogCodes.WARN_SUCCESS_BY_RETRY,
throwable.getClass() + ":" + throwable.getMessage(),
invokedProviderInfos));
}
}
return response;
} else {
throwable = new SofaRpcException(RpcErrorType.CLIENT_UNDECLARED_ERROR,
"Failed to call " + request.getInterfaceName() + "." + methodName
+ " on remote server " + providerInfo + ", return null");
time++;
}
} catch (SofaRpcException e) { // 服务端异常+ 超时异常 才发起rpc异常重试
if (e.getErrorType() == RpcErrorType.SERVER_BUSY
|| e.getErrorType() == RpcErrorType.CLIENT_TIMEOUT) {
throwable = e;
time++;
} else {
throw e;
}
} catch (Exception e) { // 其它异常不重试
throw new SofaRpcException(RpcErrorType.CLIENT_UNDECLARED_ERROR,
"Failed to call " + request.getInterfaceName() + "." + request.getMethodName()
+ " on remote server: " + providerInfo + ", cause by unknown exception: "
+ e.getClass().getName() + ", message is: " + e.getMessage(), e);
} finally {
if (RpcInternalContext.isAttachmentEnable()) {
RpcInternalContext.getContext().setAttachment(RpcConstants.INTERNAL_KEY_INVOKE_TIMES,
time + 1); // 重试次数
}
}
invokedProviderInfos.add(providerInfo);
} while (time <= retries); throw throwable;
}

这个方法需要注意的是,一开始invokedProviderInfos集合是空的,如果调用完后没有返回response,而是抛出异常了,那么就会把这个抛出异常的provider实例加入到invokedProviderInfos集合。这个集合会在select方法里面用到。

AbstractCluster#select

客户端在引用服务端的时候会通过路由找到所有的provider,然后进行剔除。路由是在调用AbstractCluster#select的时候做的。所以我们先看看这个方法。

protected ProviderInfo select(SofaRequest message, List<ProviderInfo> invokedProviderInfos)
throws SofaRpcException {
// 粘滞连接,当前连接可用
if (consumerConfig.isSticky()) {
//这个变量会在下面的selectByProvider方法为其赋值
if (lastProviderInfo != null) {
ProviderInfo providerInfo = lastProviderInfo;
ClientTransport lastTransport = connectionHolder.getAvailableClientTransport(providerInfo);
if (lastTransport != null && lastTransport.isAvailable()) {
checkAlias(providerInfo, message);
return providerInfo;
}
}
}
// 原始服务列表数据 --> 路由结果
List<ProviderInfo> providerInfos = routerChain.route(message, null); //保存一下原始地址,为了打印
List<ProviderInfo> orginalProviderInfos = new ArrayList<ProviderInfo>(providerInfos); if (CommonUtils.isEmpty(providerInfos)) {
throw noAvailableProviderException(message.getTargetServiceUniqueName());
}
//invokedProviderInfos保存的是重试的provider,说明该provider已经调用过,并且失败了
//所以在这里排除
if (CommonUtils.isNotEmpty(invokedProviderInfos) && providerInfos.size() > invokedProviderInfos.size()) { // 总数大于已调用数
providerInfos.removeAll(invokedProviderInfos);// 已经调用异常的本次不再重试
} String targetIP = null;
ProviderInfo providerInfo;
RpcInternalContext context = RpcInternalContext.peekContext();
if (context != null) {
targetIP = (String) RpcInternalContext.getContext().getAttachment(RpcConstants.HIDDEN_KEY_PINPOINT);
}
if (StringUtils.isNotBlank(targetIP)) {
// 如果指定了调用地址
providerInfo = selectPinpointProvider(targetIP, providerInfos);
if (providerInfo == null) {
// 指定的不存在
throw unavailableProviderException(message.getTargetServiceUniqueName(), targetIP);
}
ClientTransport clientTransport = selectByProvider(message, providerInfo);
if (clientTransport == null) {
// 指定的不存在或已死,抛出异常
throw unavailableProviderException(message.getTargetServiceUniqueName(), targetIP);
}
return providerInfo;
} else {
do {
// 再进行负载均衡筛选,默认使用RandomLoadBalancer
providerInfo = loadBalancer.select(message, providerInfos);
ClientTransport transport = selectByProvider(message, providerInfo);
if (transport != null) {
return providerInfo;
}
providerInfos.remove(providerInfo);
} while (!providerInfos.isEmpty());
}
throw unavailableProviderException(message.getTargetServiceUniqueName(),
convertProviders2Urls(orginalProviderInfos));
}

这个方法主要做了如下几件事:

  1. 如果设置了粘滞连接,那么会继续调用上一次使用过的provider
  2. 调用router获取原始服务列表数据
  3. 如果invokedProviderInfos不为空的话,原始服务列表里面需要剔除掉这些provider
  4. 如果设置了直连,那么调用selectPinpointProvider获取选定的provider,不存在故障剔除
  5. 没有设置直连,则循环调用筛选

路由筛选porvider

RouterChain#route

public List<ProviderInfo> route(SofaRequest request, List<ProviderInfo> providerInfos) {
for (Router router : routers) {
providerInfos = router.route(request, providerInfos);
}
return providerInfos;
} //RegistryRouter#route
public List<ProviderInfo> route(SofaRequest request, List<ProviderInfo> providerInfos) { //has address. FIXME
if (CommonUtils.isNotEmpty(providerInfos)) {
return providerInfos;
} AddressHolder addressHolder = consumerBootstrap.getCluster().getAddressHolder();
if (addressHolder != null) {
List<ProviderInfo> current = addressHolder.getProviderInfos(RpcConstants.ADDRESS_DEFAULT_GROUP);
if (providerInfos != null) {
providerInfos.addAll(current);
} else {
providerInfos = current;
}
}
recordRouterWay(RPC_REGISTRY_ROUTER);
return providerInfos;
}

我们这里考虑RegistryRouter进行路由选择的情况。

RegistryRouter#route里面首先获取addressHolder,调用其实现类SingleGroupAddressHolder

SingleGroupAddressHolder#getProviderInfos


/**
* 配置的直连地址列表
*/
protected ProviderGroup directUrlGroup;
/**
* 注册中心来的地址列表
*/
protected ProviderGroup registryGroup; private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private Lock rLock = lock.readLock();
public List<ProviderInfo> getProviderInfos(String groupName) {
rLock.lock();
try {
// 复制一份
return new ArrayList<ProviderInfo>(getProviderGroup(groupName).getProviderInfos());
} finally {
rLock.unlock();
}
} public ProviderGroup getProviderGroup(String groupName) {
rLock.lock();
try {
return RpcConstants.ADDRESS_DIRECT_GROUP.equals(groupName) ? directUrlGroup
: registryGroup;
} finally {
rLock.unlock();
}
}

这里用的是读写锁,也就是说,在读的时候可以并发读,但是不允许读的时候有写的操作。然后根据groupName获取到相应的直连集合。

实现故障剔除,筛选合适的provider

do {
// 再进行负载均衡筛选,默认使用RandomLoadBalancer
providerInfo = loadBalancer.select(message, providerInfos);
ClientTransport transport = selectByProvider(message, providerInfo);
if (transport != null) {
return providerInfo;
}
providerInfos.remove(providerInfo);
} while (!providerInfos.isEmpty());

这里是真正实现了故障剔除的方法,负载均衡我已经在上一篇已经分析过了,这里不再赘述,所以我们直接看到selectByProvider方法中

protected ClientTransport selectByProvider(SofaRequest message, ProviderInfo providerInfo) {
ClientTransport transport = connectionHolder.getAvailableClientTransport(providerInfo);
if (transport != null) {
if (transport.isAvailable()) {
lastProviderInfo = providerInfo;
checkAlias(providerInfo, message); //检查分组
return transport;
} else {
connectionHolder.setUnavailable(providerInfo, transport);
}
}
return null;
} //AllConnectConnectionHolder#getAvailableClientTransport public ClientTransport getAvailableClientTransport(ProviderInfo providerInfo) {
// 先去存活列表
ClientTransport transport = aliveConnections.get(providerInfo);
if (transport != null) {
return transport;
}
// 再去亚健康列表 这个列表暂时没有实现的地方
transport = subHealthConnections.get(providerInfo);
if (transport != null) {
return transport;
}
// 最后看看是否第一次调用未初始化
transport = uninitializedConnections.get(providerInfo);
if (transport != null) {
// 未初始化则初始化,这里是lazy为ture的情况,延迟初始化
synchronized (this) {
transport = uninitializedConnections.get(providerInfo);
if (transport != null) {
initClientTransport(consumerConfig.getInterfaceId(), providerInfo, transport);
uninitializedConnections.remove(providerInfo);
}
return getAvailableClientTransport(providerInfo);
}
}
return null;
}

当我们进入到getAvailableClientTransport这个方法的看到存货列表和未初始化列表的时候有没有似曾相识的感觉?没错,这几个参数就是我们上面讲到的客户端会初始化一个心跳线程,在心跳线程里面维护这几个参数。

所以这个方法主要做了以下几件事:

  1. 去存活列表里面找transport
  2. 去亚健康列表里面找transport,当然目前的版本并没有维护亚健康列表,所以永远找不到
  3. 如果设置了延迟加载,那么会去uninitializedConnections里面找到transport,然后再调用initClientTransport方法进行初始化
  4. 如果找不到那么就返回null
  5. 如果返回null,那么会回到上面的do-while循环进行再次的筛选

好了,那么SOFARPCSOFARPC是如何实现连接管理与心跳的就已经分析完了,如果这篇文章对你有所帮助,不妨点个赞,谢谢。

SOFARPC源码解析系列:

1. 源码分析---SOFARPC可扩展的机制SPI

2. 源码分析---SOFARPC客户端服务引用

3. 源码分析---SOFARPC客户端服务调用

4. 源码分析---SOFARPC服务端暴露

5.源码分析---SOFARPC调用服务

6.源码分析---和dubbo相比SOFARPC是如何实现负载均衡的?

7.源码分析---SOFARPC是如何实现故障剔除的?的更多相关文章

  1. 9.源码分析---SOFARPC是如何实现故障剔除的?

    SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...

  2. 11.源码分析---SOFARPC数据透传是实现的?

    先把栗子放上,让大家方便测试用: Service端 public static void main(String[] args) { ServerConfig serverConfig = new S ...

  3. 10.源码分析---SOFARPC内置链路追踪SOFATRACER是怎么做的?

    SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...

  4. 5.源码分析---SOFARPC调用服务

    我们这一次来接着上一篇文章<4. 源码分析---SOFARPC服务端暴露>讲一下服务暴露之后被客户端调用之后服务端是怎么返回数据的. 示例我们还是和上篇文章一样使用一样的bolt协议来讲: ...

  5. 4. 源码分析---SOFARPC服务端暴露

    服务端的示例 我们首先贴上我们的服务端的示例: public static void main(String[] args) { ServerConfig serverConfig = new Ser ...

  6. 1. 源码分析---SOFARPC可扩展的机制SPI

    这几天离职在家,正好没事可以疯狂的输出一下,本来想写DUBBO的源码解析的,但是发现写DUBBO源码的太多了,所以找一个写的不那么多的框架,所以就选中SOFARPC这个框架了. SOFARPC是蚂蚁金 ...

  7. 3. 源码分析---SOFARPC客户端服务调用

    我们首先看看BoltClientProxyInvoker的关系图 所以当我们用BoltClientProxyInvoker#invoke的时候实际上是调用了父类的invoke方法 ClientProx ...

  8. 2. 源码分析---SOFARPC客户端服务引用

    我们先上一张客户端服务引用的时序图. 我们首先来看看ComsumerConfig的refer方法吧 public T refer() { if (consumerBootstrap == null) ...

  9. 12.源码分析—如何为SOFARPC写一个序列化?

    SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...

随机推荐

  1. 戴尔R720安装ESXI系统

    1.U盘安装系统,使用UltraISO制作启动盘 参考地址:https://jingyan.baidu.com/article/5225f26b0bb45fe6fa0908bc.html 2.插上U盘 ...

  2. 小白开学Asp.Net Core 《四》

    小白开学Asp.Net Core<三>                               —— 使用AspectCore-Framework 一.AspectCore-Frame ...

  3. Ubuntu18.04安装postgresql-10

    Ubuntu18安装postgresql-10 最近切换Ubuntu作为办公系统,所有软件安装都要重来一遍. 官方文档: https://www.postgresql.org/download/lin ...

  4. BFS(三):双向广度优先搜索

    所谓双向广度搜索指的是搜索沿两个方向同时进行:(1)正向搜索:从初始结点向目标结点方向搜索:(2)逆向搜索:从目标结点向初始结点方向搜索:当两个方向的搜索生成同一子结点时终止此搜索过程. 广度双向搜索 ...

  5. PyCharm2018 汉化&激活

    一.汉化 将下载好的resources_cn_PyCharm_2018.1_r2.jar 放入pycharm 的lib 目录中,启动app即可 下载链接: https://pan.baidu.com/ ...

  6. JcApiHelper 简单好用的.Net ApiHelper

    一 背景 随着前端技术的不断发展,各种框架逐渐成熟,前端 Angular,React,Vue 三分天下.再加上移动端的崛起,前后端分离开发成为主流,前端后端代码混合开发的方式沦为被淘汰的局面.如今 M ...

  7. [golang]golang time.After内存泄露问题分析

    无意中看到一篇文章说,当在for循环里使用select + time.After的组合时会产生内存泄露,于是进行了复现和验证,以此记录 内存泄露复现 问题复现测试代码如下所示: package mai ...

  8. TPL DataFlow .Net 数据流组件,了解一下

    回顾上文 作为单体程序,依赖的第三方服务虽不多,但是2C的程序还是有不少内容可讲: 作为一个常规互联网系统,无外乎就是接受请求.处理请求,输出响应. 由于业务渐渐增长,数据处理的过程会越来越复杂和冗长 ...

  9. 基于lua-nginx-module(openresty)的WEB应用防火墙

    独乐乐,不如众乐乐,分享给大家一篇WEB应用防火墙的文章,基于Lua+ Nginx实现.以下是ngx_lua_waf的作者全文输出. Github地址:https://github.com/loves ...

  10. shell的用处到底大不大

    我曾在智联招聘等网站上搜寻有关shell脚本员的职位,与C++.JAVA等热门语言相比,冷清很多.看上去似乎招shell程序员的公司比较少.是不是公司不重视或者是很少用到shell这个东东呢?     ...