图解+源码讲解 Nacos 服务端处理配置获取请求
在人生中第一要紧的是发现自己。为了这个目的,各位时常需要孤独和深思 —— 南森
Nacos 源码分析系列相关文章
- 从零开始看 Nacos 源码环境搭建
- 图解+源码讲解 Nacos 客户端发起注册流程
- 图解+源码讲解 Nacos 服务端处理注册请求逻辑
- 图解+源码讲解 Nacos 客户端下线流程
- 图解+源码讲解 Nacos 服务端处理下线请求
- 图解+源码讲解 Nacos 客户端发起心跳请求
- 图解+源码讲解 Nacos 服务端处理心跳请求
- 图解+源码讲解 Nacos 服务端处理配置获取请求
从客户端的请求地址开始分析
客户端获取配置的请求地址是:/v1/cs/configs, 这样一来我们就可以去服务端找响应的请求地址,看看在哪个项目模块中,应该是在nacos-config 这个项目模块中,从下图中可以看出就是这个模块中的 getConfig方法
响应请求方法 getConfig
(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void getConfig(HttpServletRequest request, HttpServletResponse response,
("dataId") String dataId, ("group") String group,
(value = "tenant", required = false,
defaultValue = StringUtils.EMPTY) String tenant,
(value = "tag", required = false) String tag)
throws IOException, ServletException, NacosException {
// 校验参数
// check tenant
ParamUtils.checkTenant(tenant);
tenant = NamespaceUtil.processNamespaceParameter(tenant);
// check params
ParamUtils.checkParam(dataId, group, "datumId", "content");
ParamUtils.checkParam(tag);
// 获取客户端请求IP地址
final String clientIp = RequestUtil.getRemoteIp(request);
String isNotify = request.getHeader("notify");
// 获取请求方法
inner.doGetConfig(request, response, dataId, group, tenant, tag, isNotify,clientIp);
}
获取配置方法 doGetConfig
获取读锁 tryConfigReadLock
尝试添加读锁
private static int tryConfigReadLock(String groupKey) {
// 默认是 -1 ,
int lockResult = -1;
// 尝试获取锁的次数,最大次数是10,TRY_GET_LOCK_TIMES = 9
for (int i = TRY_GET_LOCK_TIMES; i >= 0; --i) {
lockResult = ConfigCacheService.tryReadLock(groupKey);
// 数据不存在
if (0 == lockResult) {
break;
}
// 读写加成功
if (lockResult > 0) {
break;
}
// 睡一毫秒进行加锁
if (i > 0) {
try {
Thread.sleep(1);
} catch (Exception e) {
LogUtil.PULL_CHECK_LOG.error("An Exception" +
"occurred while thread sleep", e);
}
}
}
return lockResult;
}
尝试加锁
public static int tryReadLock(String groupKey) {
// 从缓存中获取值
CacheItem groupItem = CACHE.get(groupKey);
// 如果不为空的话那么尝试加读锁,如果为空那么就返回 0 意味着配置不存在
int result = (null == groupItem) ? 0 : (groupItem.rwLock.tryReadLock() ? 1 : -1);
if (result < 0) {
DEFAULT_LOG.warn("[read-lock] failed, {}, {}", result, groupKey);
}
return result;
}
Nacos 实现简单锁
这里面 Nacos 自己实现了一个锁,通过 status 状态进行判断的,如果是负值那么加的是写锁,如果是正值那么就是加的读锁,如果等于 0 那么是没有被加锁
public class SimpleReadWriteLock {
// 尝试加读锁,读锁是可以进行并行加锁的,就是读写锁互斥、写写互斥,但是读读不互斥
public synchronized boolean tryReadLock() {
// 是否添加了写锁
if (isWriteLocked()) {
return false;
} else {
status++;
return true;
}
}
// 释放读锁
public synchronized void releaseReadLock() {
status--;
}
// 写锁的时候 status = -1 尝试加写锁
public synchronized boolean tryWriteLock() {
if (!isFree()) {
return false;
} else {
status = -1;
return true;
}
}
// 释放写锁
public synchronized void releaseWriteLock() {
status = 0;
}
// 是否是写锁
private boolean isWriteLocked() {
return status < 0;
}
// 是否是没有锁
private boolean isFree() {
return status == 0;
}
// 0 代表没有锁 -1 代表写锁
private int status = 0;
}
如果获取读锁成功 lockResult > 0
这里面只看获取锁成功的逻辑
if (lockResult > 0) {
FileInputStream fis = null;
try {
String md5 = Constants.NULL;
long lastModified = 0L;
// 根据 key 获取缓存中的配置信息
CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey);
if (cacheItem.isBeta() && cacheItem.getIps4Beta().contains(clientIp)) {
isBeta = true;
}
......
if (StringUtils.isBlank(tag)) {
......
md5 = cacheItem.getMd5();
lastModified = cacheItem.getLastModifiedTs();
if (PropertyUtil.isDirectRead()) {
// 根据 dataId ,group, tenant 从 Derby 嵌入式数据库中查询配置信息
configInfoBase = persistService.
findConfigInfo(dataId, group, tenant); // 查找配置信息
} else {
file = DiskUtil.targetFile(dataId, group, tenant);
}
if (configInfoBase == null && fileNotExist(file)) {
......
return get404Result(response);
}
isSli = true;
}
......
}
......
if (PropertyUtil.isDirectRead()) {
out = response.getWriter();
out.print(configInfoBase.getContent());
out.flush();
out.close();
}
......
} finally {
// 释放配置读锁,就是 status--
releaseConfigReadLock(groupKey);
IoUtils.closeQuietly(fis);
}
从 Derby 嵌入式数据库中查询配置信息
ConfigInfo{id=491540548146958336, dataId='service-consumer.properties', group='DEFAULT_GROUP', tenant='', appName='', content='useLocalCache=true', md5='25a822367cd73c79acc56da3c73fa07e'}
释放读锁 releaseConfigReadLock
无论在什么情况下,在获取一把锁并处理完业务逻辑的情况下,一定要释放这把锁,否则会造成后续请求阻塞
private static void releaseConfigReadLock(String groupKey) {
ConfigCacheService.releaseReadLock(groupKey);
}
public static void releaseReadLock(String groupKey) {
// 获取缓存值
CacheItem item = CACHE.get(groupKey);
if (null != item) {
// 释放锁
item.rwLock.releaseReadLock();
}
}
// 释放锁
public synchronized void releaseReadLock() {
status--;
}
返回成功 200
如果获取读锁返回 lockResult = 0
返回 404 找不到配置信息
// lockResult = 0 就是没有数据,获取的配置不存在
if (lockResult == 0) {
.....
return get404Result(response);
}
如果获取读锁失败 lockResult < 0
加锁失败,返回状态码 409 ,并提示请求的文件被修改中,请稍后进行获取查询
// 加读锁失败了
response.setStatus(HttpServletResponse.SC_CONFLICT);
return HttpServletResponse.SC_CONFLICT + "";
小结
- 加读锁操作
- 如果加锁成功那么从 Derby 数据库中查询配置信息
- 返回查询结果信息
其他系列源码分析
feign 源码分析系列相关文章
- 图解+源码讲解 Feign 如何将客户端注入到容器中
- 图解+源码讲解动态代理获取 FeignClient 代理对象
- 图解+源码讲解代理对象 ReflectiveFeign 分析
- 图解+源码讲解 Feign 如何选取指定服务
- 图解+源码讲解 Feign 请求的流程
ribbon 源码分析系列相关文章
- Ribbon 原理初探
- 图解+源码讲解 Ribbon 如何获取注册中心的实例
- 图解+源码讲解 Ribbon 服务列表更新
- 图解+源码讲解 Ribbon 服务选择原理
- 图解+源码讲解 Ribbon 如何发起网络请求
eureka 源码分析系列相关文章
- eureka-server 项目结构分析
- 图解+源码讲解 Eureka Server 启动流程分析
- 图解+源码讲解 Eureka Client 启动流程分析
- 图解+源码讲解 Eureka Server 注册表缓存逻辑
- 图解+源码讲解 Eureka Client 拉取注册表流程
- 图解+源码讲解 Eureka Client 服务注册流程
- 图解+源码讲解 Eureka Client 心跳机制流程
- 图解+源码讲解 Eureka Client 下线流程分析
- 图解+源码讲解 Eureka Server 服务剔除逻辑
- 图解+源码讲解 Eureka Server 集群注册表同步机制