0
点赞
收藏
分享

微信扫一扫

图解+源码讲解 Nacos 服务端处理配置获取请求

图解+源码讲解 Nacos 服务端处理配置获取请求


在人生中第一要紧的是发现自己。为了这个目的,各位时常需要孤独和深思 —— 南森


Nacos 源码分析系列相关文章

  1. 从零开始看 Nacos 源码环境搭建
  2. 图解+源码讲解 Nacos 客户端发起注册流程
  3. 图解+源码讲解 Nacos 服务端处理注册请求逻辑
  4. 图解+源码讲解 Nacos 客户端下线流程
  5. 图解+源码讲解 Nacos 服务端处理下线请求
  6. 图解+源码讲解 Nacos 客户端发起心跳请求
  7. 图解+源码讲解 Nacos 服务端处理心跳请求
  8. 图解+源码讲解 Nacos 服务端处理配置获取请求

从客户端的请求地址开始分析

    客户端获取配置的请求地址是:/v1/cs/configs, 这样一来我们就可以去服务端找响应的请求地址,看看在哪个项目模块中,应该是在nacos-config 这个项目模块中,从下图中可以看出就是这个模块中的 getConfig方法
图解+源码讲解 Nacos 服务端处理配置获取请求_服务端

响应请求方法 getConfig

@GetMapping
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void getConfig(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId, @RequestParam("group") String group,
@RequestParam(value = "tenant", required = false,
defaultValue = StringUtils.EMPTY) String tenant,
@RequestParam(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'}
图解+源码讲解 Nacos 服务端处理配置获取请求_Spring Cloud_02

释放读锁 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 + "";

小结

  1. 加读锁操作
  2. 如果加锁成功那么从 Derby 数据库中查询配置信息
  3. 返回查询结果信息

其他系列源码分析

feign 源码分析系列相关文章

  1. 图解+源码讲解 Feign 如何将客户端注入到容器中
  2. 图解+源码讲解动态代理获取 FeignClient 代理对象
  3. 图解+源码讲解代理对象 ReflectiveFeign 分析
  4. 图解+源码讲解 Feign 如何选取指定服务
  5. 图解+源码讲解 Feign 请求的流程

ribbon 源码分析系列相关文章

  1. Ribbon 原理初探
  2. 图解+源码讲解 Ribbon 如何获取注册中心的实例
  3. 图解+源码讲解 Ribbon 服务列表更新
  4. 图解+源码讲解 Ribbon 服务选择原理
  5. 图解+源码讲解 Ribbon 如何发起网络请求

eureka 源码分析系列相关文章

  1. eureka-server 项目结构分析
  2. 图解+源码讲解 Eureka Server 启动流程分析
  3. 图解+源码讲解 Eureka Client 启动流程分析
  4. 图解+源码讲解 Eureka Server 注册表缓存逻辑
  5. 图解+源码讲解 Eureka Client 拉取注册表流程
  6. 图解+源码讲解 Eureka Client 服务注册流程
  7. 图解+源码讲解 Eureka Client 心跳机制流程
  8. 图解+源码讲解 Eureka Client 下线流程分析
  9. 图解+源码讲解 Eureka Server 服务剔除逻辑
  10. 图解+源码讲解 Eureka Server 集群注册表同步机制
举报

相关推荐

0 条评论