第十章 配置文件与配置中心实现人工开关降级
通过监控发现生产环境中一些服务的问题,需要将这些服务暂时摘除掉。有的时候,比如:服务调用量大,需要同步转异步;数据库存在,但是查询速度慢,这时需要暂停服务,立即修复;新开发的功能上线进行灰度测试,不确定新的功能是否有影响,一旦有影响就直接切回老的版本。配置可以存放在配置文件,redis/zookeeper或者数据库等。
1.配置文件实现开关配置
配置文件:switcher.properties
#不调用分片缓存
user.not.call.sharding.redis.cache=true
1.1.通过JDK7提供的WatchService,来监听本地文件系统中配置文件的变化,代码片段如下:
public void startWatch() throws Exception {
//监控文件
WatchService watchService = FileSystems.getDefault().newWatchService();
String fileName = "switcher.properties";
Resource resource = new ClassPathResource(fileName);
String watchPath = resource.getFile().getParent();
Paths.get(watchPath).register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
properties = PropertiesLoaderUtils.loadProperties(resource);
Thread watchThread = new Thread(() -> {
while (true) {
try {
WatchKey key = watchService.take();
key.pollEvents().forEach(watchEvent -> {
if (Objects.equals(watchEvent.context().toString(), fileName)) {
try {
properties = PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException e) {
logger.warn(e.getMessage());
}
}
logger.info(watchEvent.context() + " comes to " + watchEvent.kind());
});
boolean valid = key.reset();
if (!valid) {
break;
}
} catch (InterruptedException e) {
logger.warn(e.getMessage());
}
}
});
watchThread.setDaemon(true);
watchThread.start();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
watchService.close();
} catch (IOException e) {
logger.warn(e.getMessage());
}
}));
}
1.2.新建一个Switchs类封装
public class Switchs {
public static class USER {
public static boolean notCallInFile() {
return Boolean.valueOf(WatchServiceManager.properties.getProperty("user.not.call.sharding.redis.cache"));
}
}
}
1.3.使用开关
if (Switchs.USER.notCallInFile()) {
//业务逻辑
}
总结:通过配置文件实现开关配置,不具有一定的灵活性,一旦系统太多,配置文件的管理维护就变得过于繁杂,需要投入更多的成本,最好的方式还是通过配置中心统一管理。
2.配置中心实现开关配置
可以通过zookeeper,disconf、Apollo、Spring Cloud Config、Consul等作为配置中心,笔者通过Consul作为讲解,主要是Consul提供多数据中心、KV存储、服务发现等特性,拥有简单的Web UI。
2.1.下载和安装Consul
下载地址:https://www.consul.io/downloads.html
笔者下载的是consul_1.0.2_linux_386.zip,解压zip文件到指定目录,你会看到目录中有一个consul文件
# uzip consul_1.0.2_linux_386
2.2.启动consul
./consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -bind 0.0.0.0 -client 0.0.0.0 -ui &
2.3.在Consul新增/创建开关值
curl -XPUT -d 'true' http://localhost:8500/v1/kv/switchs/user.not.call.sharding.redis.cache
备注:也可以直接通过访问web ui直接新增/修改
2.3.在应用中代码中引入配置中心
pulic void startWatch() {
final String system = "switchs";
Consul consul = Consul.builder()
.withHostAndPort(HostAndPort.fromString("本机ipd地址:8500"))
.withConnectTimeoutMillis(1000)
.withReadTimeoutMillis(30 * 1000)
.withWriteTimeoutMillis(5000)
.build();
final KeyValueClient keyValueClient = consul.keyValueClient();
final AtomicBoolean needBreak = new AtomicBoolean(true);
Thread watchThread = new Thread(() -> {
BigInteger index = BigInteger.ZERO;
while (true) {
Properties _properties = new Properties();
try {
List<Value> values = keyValueClient.getValues(system, QueryOptions.blockSeconds(30, index).build());
for (Value value : values) {
_properties.put(value.getKey().substring(system.length() + 1), value.getValueAsString());
logger.info("key:{}, value:{}",
value.getKey().substring(system.length() + 1),
value.getValueAsString().get());
index = index.max(BigInteger.valueOf(value.getModifyIndex()));
}
properties = _properties;
} catch (ConsulException e) {
logger.warn(e.getMessage());
//如果没有key,就休眠5秒钟
if (e.getCode() == 404) {
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
logger.error(e.getMessage());
}
}
}
if (needBreak.get()) {
break;
}
}
});
watchThread.run();
needBreak.set(false);
watchThread.setDaemon(true);
watchThread.start();
}
备注:使用方式跟配置文件实现开关配置一样使用