Fork me on GitHub

nacos多人发布冲突问题

前言

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

作为一个后台开发,nacos是我经常用到的一款集注册中心、配置中心于一身的平台,简单的配置启动,然后应用注册即可使用,还有简洁明了使用的UI页面,点个赞先!

贴上官网地址:
https://nacos.io/

问题及处理

问题

在使用nacos页面更改配置并发布的时候发现一个问题:

当两个人A和B都打开了某个配置的编辑页面,都进行了修改,然后点了发布,则会出现后点发布的B提交的配置生效,因为会覆盖前者A发布的版本,而此时如果没做比对或确认的话A就会没有察觉,从而导致可能的一些问题…

复现

该问题也很好复现,两个人直接按照步骤操作即可,一个人的话通过两个浏览器来模拟:

1、通过浏览器A打开nacos UI页面,找到一个配置文件,点击编辑,打开编辑页面;

2、通过浏览器B打开nacos UI页面,也找到刚刚打开的那个配置文件,点击编辑,打开编辑页面;

3、在浏览器A打开的nacos配置编辑页面对配置进行变更修改,然后点击发布;

4、在浏览器B打开的nacos配置编辑页面也对配置进行变更修改,然后点击发布;

5、再次查看nacos上这个配置文件的配置信息,会发现呈现的是浏览器B编辑发布后的结果;

处理

思路:在发布之前进行MD5比对!拿到源码,本地对逻辑进行变更,然后编译打包使用…

代码修改处如下:

1、com.alibaba.nacos.config.server.model.form.ConfigForm

增加md5参数字段

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
102
103
104
105
106
107
108
109
110
package com.alibaba.nacos.config.server.model.form;

import com.alibaba.nacos.api.exception.api.NacosApiException;
import com.alibaba.nacos.api.model.v2.ErrorCode;
import com.alibaba.nacos.common.utils.StringUtils;
import org.springframework.http.HttpStatus;

import java.io.Serializable;
import java.util.Objects;

public class ConfigForm implements Serializable {

// ... 代码省略

private String md5;

public ConfigForm() {
}

public ConfigForm(String dataId, String group, String namespaceId, String content, String tag, String appName,
String srcUser, String configTags, String desc, String use, String effect, String type, String schema,
String md5) {
this.dataId = dataId;
this.group = group;
this.namespaceId = namespaceId;
this.content = content;
this.tag = tag;
this.appName = appName;
this.srcUser = srcUser;
this.configTags = configTags;
this.desc = desc;
this.use = use;
this.effect = effect;
this.type = type;
this.schema = schema;
this.md5 = md5;
}

// ... 代码省略

public String getMd5() {
return md5;
}

public void setMd5(String md5) {
this.md5 = md5;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ConfigForm configForm = (ConfigForm) o;
return dataId.equals(configForm.dataId) && group.equals(configForm.group) && Objects.equals(namespaceId, configForm.namespaceId)
&& content.equals(configForm.content) && Objects.equals(tag, configForm.tag) && Objects
.equals(appName, configForm.appName) && Objects.equals(srcUser, configForm.srcUser) && Objects
.equals(configTags, configForm.configTags) && Objects.equals(desc, configForm.desc) && Objects
.equals(use, configForm.use) && Objects.equals(effect, configForm.effect) && Objects
.equals(type, configForm.type) && Objects.equals(schema, configForm.schema)
&& Objects.equals(md5, configForm.md5);
}

@Override
public int hashCode() {
return Objects.hash(dataId, group, namespaceId, content, tag, appName, srcUser, configTags, desc, use, effect, type,
schema, md5);
}

@Override
public String toString() {
return "ConfigVo{" +
"dataId='" + dataId + '\'' +
", group='" + group + '\'' +
", namespaceId='" + namespaceId + '\'' +
", content='" + content + '\'' +
", tag='" + tag + '\'' +
", appName='" + appName + '\'' +
", srcUser='" + srcUser + '\'' +
", configTags='" + configTags + '\'' +
", desc='" + desc + '\'' +
", use='" + use + '\'' +
", effect='" + effect + '\'' +
", type='" + type + '\'' +
", schema='" + schema + '\'' +
", md5='" + md5 + '\'' +
'}';
}

/**
* Validate.
*
* @throws NacosApiException NacosApiException.
*/
public void validate() throws NacosApiException {
if (StringUtils.isBlank(dataId)) {
throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING,
"Required parameter 'dataId' type String is not present");
} else if (StringUtils.isBlank(group)) {
throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING,
"Required parameter 'group' type String is not present");
} else if (StringUtils.isBlank(content)) {
throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING,
"Required parameter 'content' type String is not present");
}
}
}

2、com.alibaba.nacos.config.server.controller.ConfigController#publishConfig

添加md5入参,前端有传,直接接收

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
@PostMapping
@TpsControl(pointName = "ConfigPublish")
@Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG)
public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "dataId") String dataId,
@RequestParam(value = "group") String group,
@RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant,
@RequestParam(value = "content") String content, @RequestParam(value = "tag", required = false) String tag,
@RequestParam(value = "appName", required = false) String appName,
@RequestParam(value = "src_user", required = false) String srcUser,
@RequestParam(value = "config_tags", required = false) String configTags,
@RequestParam(value = "desc", required = false) String desc,
@RequestParam(value = "use", required = false) String use,
@RequestParam(value = "effect", required = false) String effect,
@RequestParam(value = "type", required = false) String type,
@RequestParam(value = "schema", required = false) String schema,
@RequestParam(value = "md5", required = false) String md5) throws NacosException {

// ... 代码省略
configForm.setMd5(md5);

// ... 代码省略

return configOperationService.publishConfig(configForm, configRequestInfo, encryptedDataKey);
}

3、com.alibaba.nacos.config.server.service.ConfigOperationService#publishConfig

添加校验逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Boolean publishConfig(ConfigForm configForm, ConfigRequestInfo configRequestInfo, String encryptedDataKey)
throws NacosException {

// ... 代码省略

ConfigInfo oldConfigInfo = configInfoPersistService.findConfigInfo(configInfo.getDataId(), configInfo.getGroup(),
configInfo.getTenant());
String oldConfigInfoMd5 = oldConfigInfo.getMd5();
String curMd5 = configForm.getMd5();
if (!StringUtils.equals(curMd5, oldConfigInfoMd5)) {
// 传进来的上一版本md5和数据库里现在的md5不相等,表示在此之前其他用户已更新,不能直接覆盖
throw new NacosApiException(HttpStatus.METHOD_NOT_ALLOWED.value(), ErrorCode.RESOURCE_CONFLICT, "已有其他用户更新了此配置,请先更新再修改发布!");
}

// ... 代码省略

return true;
}

4、编译打包

1
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U

5、拿到打包后的新包使用即可

新包路径:项目代码路径/distribution/target

复测

再次测试结果符合预期!

结语

到此这个问题就可以如愿解决了,欢迎留言交流!

-------------本文结束感谢您的阅读-------------
如果您对博主的原创满意,欢迎您继续支持下博主~