Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

这周开始打算写个比较简单的php工具包,然后顺带学习使用下php的单元测试,通过phpunit还是比较方便的,首先就composer require phpunit/phpunit
安装下 phpunit, 前面包就是通过 composer init 创建,装完依赖后就可以把自动加载代码生成下 composer dump-autoload
目录结构差不多这样

1
2
3
4
5
6
7
8
9
10
11
.
├── composer.json
├── composer.lock
├── oldfile.txt
├── phpunit.xml
├── src
│   └── Rename.php
└── tests
└── RenameTest.php

2 directories, 6 files

src/是源码,tests/是放的单测,比较重要的是phpunit.xml

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="php-rename">
<directory>./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

其中bootstrap就是需要把依赖包的自动加载入口配上,因为这个作为一个package,也会指出命名空间
然后就是testsuite的路径,源码中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
namespace Nicksxs\PhpRename;

class Rename
{
public static function renameSingleFile($file, $newFileName): bool
{
if(!is_file($file)) {
echo "it's not a file";
return false;
}
$fileInfo = pathinfo($file);
return rename($file, $fileInfo["dirname"] . DIRECTORY_SEPARATOR . $newFileName . "." . $fileInfo["extension"]);
}
}

就是一个简单的重命名
然后test代码是这样,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

// require_once 'vendor/autoload.php';

use PHPUnit\Framework\TestCase;
use Nicksxs\PhpRename\Rename;
use function PHPUnit\Framework\assertEquals;

class RenameTest extends TestCase
{
public function setUp() :void
{
$myfile = fopen(__DIR__ . DIRECTORY_SEPARATOR . "oldfile.txt", "w") or die("Unable to open file!");
$txt = "file test1\n";
fwrite($myfile, $txt);
fclose($myfile);
}
public function testRename()
{
Rename::renameSingleFile(__DIR__ . DIRECTORY_SEPARATOR . "oldfile.txt", "newfile");
assertEquals(is_file(__DIR__ . DIRECTORY_SEPARATOR . "newfile.txt"), true);
}

protected function tearDown(): void
{
unlink(__DIR__ . DIRECTORY_SEPARATOR . "newfile.txt");
}
}

setUptearDown 就是初始化跟结束清理的,但是注意如果不指明 __DIR__ ,待会的目录就会在执行 vendor/bin/phpunit 下面,
或者也可以指定在一个 tmp/ 目录下
最后就可以通过vendor/bin/phpunit 来执行测试
执行结果

1
2
3
4
5
6
7
8
❯ vendor/bin/phpunit
PHPUnit 9.5.25 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 00:00.005, Memory: 6.00 MB

OK (1 test, 1 assertion)

在目前环境下使用容器部署Java应用还是挺普遍的,但是有一些问题也是随之而来需要解决的,比如容器中应用的dubbo注册,在比较早的版本的dubbo中,就是简单地获取网卡的ip地址。
具体代码在这个方法里 com.alibaba.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}

String host = protocolConfig.getHost();
if (provider != null && (host == null || host.length() == 0)) {
host = provider.getHost();
}
boolean anyhost = false;
if (NetUtils.isInvalidLocalHost(host)) {
anyhost = true;
try {
host = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
logger.warn(e.getMessage(), e);
}
if (NetUtils.isInvalidLocalHost(host)) {
if (registryURLs != null && registryURLs.size() > 0) {
for (URL registryURL : registryURLs) {
try {
Socket socket = new Socket();
try {
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
host = socket.getLocalAddress().getHostAddress();
break;
} finally {
try {
socket.close();
} catch (Throwable e) {}
}
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
if (NetUtils.isInvalidLocalHost(host)) {
host = NetUtils.getLocalHost();
}
}
}

通过jdk自带的方法 java.net.InetAddress#getLocalHost来获取本机地址,这样子对于容器来讲,获取到容器内部ip注册上去其实是没办法被调用到的,
而在之后的版本中例如dubbo 2.6.5,则可以通过在docker中设置环境变量的形式来注入docker所在的宿主机地址,
代码同样在com.alibaba.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol这个方法中,但是获取host的方法变成了 com.alibaba.dubbo.config.ServiceConfig#findConfigedHosts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
private String findConfigedHosts(ProtocolConfig protocolConfig, List<URL> registryURLs, Map<String, String> map) {
boolean anyhost = false;

String hostToBind = getValueFromConfig(protocolConfig, Constants.DUBBO_IP_TO_BIND);
if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) {
throw new IllegalArgumentException("Specified invalid bind ip from property:" + Constants.DUBBO_IP_TO_BIND + ", value:" + hostToBind);
}

// if bind ip is not found in environment, keep looking up
if (hostToBind == null || hostToBind.length() == 0) {
hostToBind = protocolConfig.getHost();
if (provider != null && (hostToBind == null || hostToBind.length() == 0)) {
hostToBind = provider.getHost();
}
if (isInvalidLocalHost(hostToBind)) {
anyhost = true;
try {
hostToBind = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
logger.warn(e.getMessage(), e);
}
if (isInvalidLocalHost(hostToBind)) {
if (registryURLs != null && !registryURLs.isEmpty()) {
for (URL registryURL : registryURLs) {
if (Constants.MULTICAST.equalsIgnoreCase(registryURL.getParameter("registry"))) {
// skip multicast registry since we cannot connect to it via Socket
continue;
}
try {
Socket socket = new Socket();
try {
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
hostToBind = socket.getLocalAddress().getHostAddress();
break;
} finally {
try {
socket.close();
} catch (Throwable e) {
}
}
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
if (isInvalidLocalHost(hostToBind)) {
hostToBind = getLocalHost();
}
}
}
}

map.put(Constants.BIND_IP_KEY, hostToBind);

// registry ip is not used for bind ip by default
String hostToRegistry = getValueFromConfig(protocolConfig, Constants.DUBBO_IP_TO_REGISTRY);
if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
} else if (hostToRegistry == null || hostToRegistry.length() == 0) {
// bind ip is used as registry ip by default
hostToRegistry = hostToBind;
}

map.put(Constants.ANYHOST_KEY, String.valueOf(anyhost));

return hostToRegistry;
}

String hostToRegistry = getValueFromConfig(protocolConfig, Constants.DUBBO_IP_TO_REGISTRY);
就是这一行,

1
2
3
4
5
6
7
8
private String getValueFromConfig(ProtocolConfig protocolConfig, String key) {
String protocolPrefix = protocolConfig.getName().toUpperCase() + "_";
String port = ConfigUtils.getSystemProperty(protocolPrefix + key);
if (port == null || port.length() == 0) {
port = ConfigUtils.getSystemProperty(key);
}
return port;
}

也就是配置了DUBBO_IP_TO_REGISTRY这个环境变量

题目介绍

You are given an integer array nums where the largest integer is unique.

Determine whether the largest element in the array is at least twice as much as every other number in the array. If it is, return the index of the largest element, or return -1 otherwise.
确认在数组中的最大数是否是其余任意数的两倍大及以上,如果是返回索引,如果不是返回-1

示例

Example 1:

Input: nums = [3,6,1,0]
Output: 1
Explanation: 6 is the largest integer.
For every other number in the array x, 6 is at least twice as big as x.
The index of value 6 is 1, so we return 1.

Example 2:

Input: nums = [1,2,3,4]
Output: -1
Explanation: 4 is less than twice the value of 3, so we return -1.

提示:

  • 2 <= nums.length <= 50
  • 0 <= nums[i] <= 100
  • The largest element in nums is unique.

简要解析

这个题easy是题意也比较简单,找最大值,并且最大值是其他任意值的两倍及以上,其实就是找最大值跟次大值,比较下就好了

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int dominantIndex(int[] nums) {
int largest = Integer.MIN_VALUE;
int second = Integer.MIN_VALUE;
int largestIndex = -1;
for (int i = 0; i < nums.length; i++) {
// 如果有最大的就更新,同时更新最大值和第二大的
if (nums[i] > largest) {
second = largest;
largest = nums[i];
largestIndex = i;
} else if (nums[i] > second) {
// 没有超过最大的,但是比第二大的更大就更新第二大的
second = nums[i];
}
}

// 判断下是否符合题目要求,要是所有值的两倍及以上
if (largest >= 2 * second) {
return largestIndex;
} else {
return -1;
}
}

通过图

第一次错了是把第二大的情况只考虑第一种,也有可能最大值完全没经过替换就变成最大值了

原来一直有点被误导,
gatingSequences用来标识每个 processer 的操作位点,但是怎么记录更新有点搞不清楚
其实问题在于 gatingSequences 是个 Sequence 数组,首先要看下怎么加进去的,
可以看到是在 com.lmax.disruptor.RingBuffer#addGatingSequences 这个方法里添加
首先是 com.lmax.disruptor.dsl.Disruptor#handleEventsWith(com.lmax.disruptor.EventHandler<? super T>...)
然后执行 com.lmax.disruptor.dsl.Disruptor#createEventProcessors(com.lmax.disruptor.Sequence[], com.lmax.disruptor.EventHandler<? super T>[])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
EventHandlerGroup<T> createEventProcessors(
final Sequence[] barrierSequences,
final EventHandler<? super T>[] eventHandlers)
{
checkNotStarted();

final Sequence[] processorSequences = new Sequence[eventHandlers.length];
final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);

for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++)
{
final EventHandler<? super T> eventHandler = eventHandlers[i];

// 这里将 handler 包装成一个 BatchEventProcessor
final BatchEventProcessor<T> batchEventProcessor =
new BatchEventProcessor<>(ringBuffer, barrier, eventHandler);

if (exceptionHandler != null)
{
batchEventProcessor.setExceptionHandler(exceptionHandler);
}

consumerRepository.add(batchEventProcessor, eventHandler, barrier);
processorSequences[i] = batchEventProcessor.getSequence();
}

updateGatingSequencesForNextInChain(barrierSequences, processorSequences);

return new EventHandlerGroup<>(this, consumerRepository, processorSequences);
}

BatchEventProcessor 在类内有个定义 sequence

1
private final Sequence sequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE);

然后在上面循环中的这一句取出来

1
processorSequences[i] = batchEventProcessor.getSequence();

调用com.lmax.disruptor.dsl.Disruptor#updateGatingSequencesForNextInChain 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
private void updateGatingSequencesForNextInChain(final Sequence[] barrierSequences, final Sequence[] processorSequences)
{
if (processorSequences.length > 0)
{
// 然后在这里添加
ringBuffer.addGatingSequences(processorSequences);
for (final Sequence barrierSequence : barrierSequences)
{
ringBuffer.removeGatingSequence(barrierSequence);
}
consumerRepository.unMarkEventProcessorsAsEndOfChain(barrierSequences);
}
}

而如何更新则是在处理器 com.lmax.disruptor.BatchEventProcessor#run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void run()
{
if (running.compareAndSet(IDLE, RUNNING))
{
sequenceBarrier.clearAlert();

notifyStart();
try
{
if (running.get() == RUNNING)
{
processEvents();
}
}
finally
{
notifyShutdown();
running.set(IDLE);
}
}
else
{
// This is a little bit of guess work. The running state could of changed to HALTED by
// this point. However, Java does not have compareAndExchange which is the only way
// to get it exactly correct.
if (running.get() == RUNNING)
{
throw new IllegalStateException("Thread is already running");
}
else
{
earlyExit();
}
}
}

然后是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private void processEvents()
{
T event = null;
long nextSequence = sequence.get() + 1L;

while (true)
{
try
{
final long availableSequence = sequenceBarrier.waitFor(nextSequence);
if (batchStartAware != null)
{
batchStartAware.onBatchStart(availableSequence - nextSequence + 1);
}

while (nextSequence <= availableSequence)
{
event = dataProvider.get(nextSequence);
eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence);
nextSequence++;
}
// 如果正常处理完,那就是会更新为 availableSequence,因为都处理好了
sequence.set(availableSequence);
}
catch (final TimeoutException e)
{
notifyTimeout(sequence.get());
}
catch (final AlertException ex)
{
if (running.get() != RUNNING)
{
break;
}
}
catch (final Throwable ex)
{
handleEventException(ex, nextSequence, event);
// 如果是异常就只是 nextSequence
sequence.set(nextSequence);
nextSequence++;
}
}
}

入口是可以在 springboot 的启动类上打上EnableApolloConfig注解

1
2
@Import(ApolloConfigRegistrar.class)
public @interface EnableApolloConfig {

这个 import 实现了

1
2
3
4
5
6
7
8
9
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {

private ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class);

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
helper.registerBeanDefinitions(importingClassMetadata, registry);
}
}

然后就调用了

1
com.ctrip.framework.apollo.spring.spi.DefaultApolloConfigRegistrarHelper#registerBeanDefinitions

接着是注册了这个 bean,com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(EnableApolloConfig.class.getName()));
String[] namespaces = attributes.getStringArray("value");
int order = attributes.getNumber("order");
PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order);

Map<String, Object> propertySourcesPlaceholderPropertyValues = new HashMap<>();
// to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer
propertySourcesPlaceholderPropertyValues.put("order", 0);

BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
// 注册了这个 bean
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
PropertySourcesProcessor.class);

而com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor 实现了 org.springframework.beans.factory.config.BeanFactoryPostProcessor
它里面的 com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor#postProcessBeanFactory 方法就会被 spring 调用,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void initializePropertySources() {
if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
//already initialized
return;
}
CompositePropertySource composite = new CompositePropertySource(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME);

//sort by order asc
ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet());
Iterator<Integer> iterator = orders.iterator();

while (iterator.hasNext()) {
int order = iterator.next();
for (String namespace : NAMESPACE_NAMES.get(order)) {
// 这里获取每个 namespace 的配置
Config config = ConfigService.getConfig(namespace);

composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
}
}

然后是 com.ctrip.framework.apollo.ConfigService#getConfig
接着就是它
com.ctrip.framework.apollo.internals.DefaultConfigManager#getConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public Config getConfig(String namespace) {
Config config = m_configs.get(namespace);

if (config == null) {
synchronized (this) {
config = m_configs.get(namespace);

if (config == null) {
ConfigFactory factory = m_factoryManager.getFactory(namespace);

// 通过 factory 来创建配置获取
config = factory.create(namespace);
m_configs.put(namespace, config);
}
}
}

创建配置

1
2
3
4
5
6
7
8
9
10
com.ctrip.framework.apollo.spi.DefaultConfigFactory#create
@Override
public Config create(String namespace) {
ConfigFileFormat format = determineFileFormat(namespace);
if (ConfigFileFormat.isPropertiesCompatible(format)) {
return new DefaultConfig(namespace, createPropertiesCompatibleFileConfigRepository(namespace, format));
}
// 调用到这
return new DefaultConfig(namespace, createLocalConfigRepository(namespace));
}

然后

1
2
3
4
5
6
7
8
9
10
LocalFileConfigRepository createLocalConfigRepository(String namespace) {
if (m_configUtil.isInLocalMode()) {
logger.warn(
"==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====",
namespace);
return new LocalFileConfigRepository(namespace);
}
// 正常会走这个,因为要从配置中心获取
return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
}

然后是创建远程配置仓库

1
2
3
4
com.ctrip.framework.apollo.spi.DefaultConfigFactory#createRemoteConfigRepository
RemoteConfigRepository createRemoteConfigRepository(String namespace) {
return new RemoteConfigRepository(namespace);
}

继续对当前的 namespace 创建远程配置仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
com.ctrip.framework.apollo.internals.RemoteConfigRepository#RemoteConfigRepository
public RemoteConfigRepository(String namespace) {
m_namespace = namespace;
m_configCache = new AtomicReference<>();
m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);
m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);
m_longPollServiceDto = new AtomicReference<>();
m_remoteMessages = new AtomicReference<>();
m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());
m_configNeedForceRefresh = new AtomicBoolean(true);
m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),
m_configUtil.getOnErrorRetryInterval() * 8);
gson = new Gson();
// 尝试同步
this.trySync();
this.schedulePeriodicRefresh();
this.scheduleLongPollingRefresh();
}

然后是同步配置,下面的日志异常经常可以看到,比如配置拉取地址不通

1
2
3
4
5
6
7
8
9
10
11
12
13
com.ctrip.framework.apollo.internals.AbstractConfigRepository#trySync
protected boolean trySync() {
try {
sync();
return true;
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
logger
.warn("Sync config failed, will retry. Repository {}, reason: {}", this.getClass(), ExceptionUtil
.getDetailMessage(ex));
}
return false;
}

实际的同步方法,加了synchronized锁,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
com.ctrip.framework.apollo.internals.RemoteConfigRepository#sync
@Override
protected synchronized void sync() {
Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");

try {
// 获取本地配置
ApolloConfig previous = m_configCache.get();
// 获取配置
ApolloConfig current = loadApolloConfig();

//reference equals means HTTP 304
if (previous != current) {
logger.debug("Remote Config refreshed!");
m_configCache.set(current);
this.fireRepositoryChange(m_namespace, this.getConfig());
}

if (current != null) {
Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),
current.getReleaseKey());
}

transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
throw ex;
} finally {
transaction.complete();
}
}

然后走到这

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
com.ctrip.framework.apollo.internals.RemoteConfigRepository#loadApolloConfig
private ApolloConfig loadApolloConfig() {
if (!m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
//wait at most 5 seconds
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
}
String appId = m_configUtil.getAppId();
String cluster = m_configUtil.getCluster();
String dataCenter = m_configUtil.getDataCenter();
Tracer.logEvent("Apollo.Client.ConfigMeta", STRING_JOINER.join(appId, cluster, m_namespace));
int maxRetries = m_configNeedForceRefresh.get() ? 2 : 1;
long onErrorSleepTime = 0; // 0 means no sleep
Throwable exception = null;

// 获取配置
List<ServiceDTO> configServices = getConfigServices();
String url = null;
for (int i = 0; i < maxRetries; i++) {
List<ServiceDTO> randomConfigServices = Lists.newLinkedList(configServices);
Collections.shuffle(randomConfigServices);
//Access the server which notifies the client first
if (m_longPollServiceDto.get() != null) {
randomConfigServices.add(0, m_longPollServiceDto.getAndSet(null));
}

for (ServiceDTO configService : randomConfigServices) {
if (onErrorSleepTime > 0) {
logger.warn(
"Load config failed, will retry in {} {}. appId: {}, cluster: {}, namespaces: {}",
onErrorSleepTime, m_configUtil.getOnErrorRetryIntervalTimeUnit(), appId, cluster, m_namespace);

try {
m_configUtil.getOnErrorRetryIntervalTimeUnit().sleep(onErrorSleepTime);
} catch (InterruptedException e) {
//ignore
}
}

url = assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace,
dataCenter, m_remoteMessages.get(), m_configCache.get());

logger.debug("Loading config from {}", url);
HttpRequest request = new HttpRequest(url);

Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "queryConfig");
transaction.addData("Url", url);
try {

HttpResponse<ApolloConfig> response = m_httpUtil.doGet(request, ApolloConfig.class);
m_configNeedForceRefresh.set(false);
m_loadConfigFailSchedulePolicy.success();

transaction.addData("StatusCode", response.getStatusCode());
transaction.setStatus(Transaction.SUCCESS);

if (response.getStatusCode() == 304) {
logger.debug("Config server responds with 304 HTTP status code.");
return m_configCache.get();
}

ApolloConfig result = response.getBody();

logger.debug("Loaded config for {}: {}", m_namespace, result);

return result;
} catch (ApolloConfigStatusCodeException ex) {
ApolloConfigStatusCodeException statusCodeException = ex;
//config not found
if (ex.getStatusCode() == 404) {
String message = String.format(
"Could not find config for namespace - appId: %s, cluster: %s, namespace: %s, " +
"please check whether the configs are released in Apollo!",
appId, cluster, m_namespace);
statusCodeException = new ApolloConfigStatusCodeException(ex.getStatusCode(),
message);
}
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(statusCodeException));
transaction.setStatus(statusCodeException);
exception = statusCodeException;
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
transaction.setStatus(ex);
exception = ex;
} finally {
transaction.complete();
}

// if force refresh, do normal sleep, if normal config load, do exponential sleep
onErrorSleepTime = m_configNeedForceRefresh.get() ? m_configUtil.getOnErrorRetryInterval() :
m_loadConfigFailSchedulePolicy.fail();
}

}
String message = String.format(
"Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s, url: %s",
appId, cluster, m_namespace, url);
throw new ApolloConfigException(message, exception);
}
1
2
3
4
5
6
7
8
9
com.ctrip.framework.apollo.internals.RemoteConfigRepository#getConfigServices
private List<ServiceDTO> getConfigServices() {
List<ServiceDTO> services = m_serviceLocator.getConfigServices();
if (services.size() == 0) {
throw new ApolloConfigException("No available config service");
}

return services;
}
1
2
3
4
5
6
7
8
com.ctrip.framework.apollo.internals.ConfigServiceLocator#getConfigServices
public List<ServiceDTO> getConfigServices() {
if (m_configServices.get().isEmpty()) {
updateConfigServices();
}

return m_configServices.get();
}

更新配置服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
com.ctrip.framework.apollo.internals.ConfigServiceLocator#updateConfigServices
private synchronized void updateConfigServices() {
String url = assembleMetaServiceUrl();

HttpRequest request = new HttpRequest(url);
int maxRetries = 2;
Throwable exception = null;

for (int i = 0; i < maxRetries; i++) {
Transaction transaction = Tracer.newTransaction("Apollo.MetaService", "getConfigService");
transaction.addData("Url", url);
try {
// 发起 http 请求获取配置
HttpResponse<List<ServiceDTO>> response = m_httpUtil.doGet(request, m_responseType);
transaction.setStatus(Transaction.SUCCESS);
List<ServiceDTO> services = response.getBody();
if (services == null || services.isEmpty()) {
logConfigService("Empty response!");
continue;
}
setConfigServices(services);
return;
} catch (Throwable ex) {
Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
transaction.setStatus(ex);
exception = ex;
} finally {
transaction.complete();
}

try {
m_configUtil.getOnErrorRetryIntervalTimeUnit().sleep(m_configUtil.getOnErrorRetryInterval());
} catch (InterruptedException ex) {
//ignore
}
}

throw new ApolloConfigException(
String.format("Get config services failed from %s", url), exception);
}
0%