Merge branch 'upsteam-develop' into feature_support_grpc_core

# Conflicts:
#	api/src/main/java/com/alibaba/nacos/api/common/Constants.java
#	client/src/main/java/com/alibaba/nacos/client/naming/remote/http/HttpClient.java
#	common/src/main/java/com/alibaba/nacos/common/utils/VersionUtils.java
#	core/src/main/java/com/alibaba/nacos/core/distributed/distro/DistroProtocol.java
#	naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java
#	naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java
#	naming/src/main/java/com/alibaba/nacos/naming/misc/GlobalExecutor.java
This commit is contained in:
KomachiSion 2020-11-05 19:33:46 +08:00
commit 9cbc0bfeea
141 changed files with 4584 additions and 1675 deletions

23
.github/workflows/maven.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Java CI
on: [push]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04]
java: [8, 8.0.192, 11, 11.0.9]
fail-fast: false
max-parallel: 2
name: Test JDK ${{ matrix.java }}, ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java }}
- name: Test with Maven
run: mvn test -B --file pom.xml

View File

@ -28,9 +28,13 @@ before_install:
script:
- mvn -B clean package apache-rat:check findbugs:findbugs -Dmaven.test.skip=true
- mvn clean -Premove-test-data
- mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
- mvn clean -Premove-test-data
- mvn clean install -Pcit-test
- mvn clean -Premove-test-data
- mvn clean package -Pit-test
- mvn clean -Premove-test-data
after_success:
- mvn clean package -Pit-test
- mvn sonar:sonar -Psonar-apache

View File

@ -19,7 +19,7 @@
<parent>
<artifactId>nacos-all</artifactId>
<groupId>com.alibaba.nacos</groupId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -19,7 +19,7 @@
<parent>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -188,6 +188,8 @@ public class Constants {
public static final String HTTP_PREFIX = "http";
public static final String ALL_PATTERN = "*";
public static final String COLON = ":";
}

View File

@ -24,6 +24,7 @@ package com.alibaba.nacos.api.common;
* <li> Global and common code starts with 10001.
* <li> Naming module code starts with 20001.
* <li> Config module code starts with 30001.
* <li> Core module code starts with 40001.
*
* @author nkorange
* @since 1.2.0

View File

@ -19,6 +19,7 @@ package com.alibaba.nacos.api.naming.pojo;
import com.alibaba.nacos.api.naming.pojo.healthcheck.AbstractHealthChecker;
import com.alibaba.nacos.api.naming.pojo.healthcheck.impl.Tcp;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@ -31,7 +32,9 @@ import java.util.Map;
* @author nkorange
*/
@SuppressWarnings("checkstyle:abbreviationaswordinname")
public class Cluster {
public class Cluster implements Serializable {
private static final long serialVersionUID = -7196138840047197271L;
/**
* Name of belonging service.

View File

@ -22,6 +22,7 @@ import com.alibaba.nacos.api.utils.StringUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@ -33,7 +34,9 @@ import static com.alibaba.nacos.api.common.Constants.NUMBER_PATTERN;
* @author nkorange
*/
@JsonInclude(Include.NON_NULL)
public class Instance {
public class Instance implements Serializable {
private static final long serialVersionUID = -742906310567291979L;
/**
* unique id of this instance.

View File

@ -16,6 +16,7 @@
package com.alibaba.nacos.api.naming.pojo;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@ -29,7 +30,9 @@ import java.util.Map;
*
* @author nkorange
*/
public class Service {
public class Service implements Serializable {
private static final long serialVersionUID = -3470985546826874460L;
/**
* service name.

View File

@ -25,6 +25,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import java.io.Serializable;
/**
* Abstract health checker.
*
@ -34,7 +36,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
@JsonSubTypes({@JsonSubTypes.Type(name = Http.TYPE, value = Http.class),
@JsonSubTypes.Type(name = Mysql.TYPE, value = Mysql.class),
@JsonSubTypes.Type(name = Tcp.TYPE, value = Tcp.class)})
public abstract class AbstractHealthChecker implements Cloneable {
public abstract class AbstractHealthChecker implements Cloneable, Serializable {
private static final long serialVersionUID = 3848305577423336421L;
@JsonIgnore
protected final String type;

View File

@ -54,4 +54,25 @@ public class NamingUtils {
}
return serviceNameWithGroup.split(Constants.SERVICE_INFO_SPLITER)[0];
}
/**
* check combineServiceName format. the serviceName can't be blank. some relational logic in {@link
* com.alibaba.nacos.naming.web.DistroFilter#doFilter}, it will handle combineServiceName in some case, you should
* know it.
* <pre>
* serviceName = "@@"; the length = 0; illegal
* serviceName = "group@@"; the length = 1; illegal
* serviceName = "@@serviceName"; the length = 2; legal
* serviceName = "group@@serviceName"; the length = 2; legal
* </pre>
*
* @param combineServiceName such as: groupName@@serviceName
*/
public static void checkServiceNameFormat(String combineServiceName) {
String[] split = combineServiceName.split(Constants.SERVICE_INFO_SPLITER);
if (split.length <= 1) {
throw new IllegalArgumentException(
"Param 'serviceName' is illegal, it should be format as 'groupName@@serviceName'");
}
}
}

View File

@ -20,6 +20,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import java.io.Serializable;
/**
* Abstract selector that only contains a type.
*
@ -27,7 +29,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
* @since 0.7.0
*/
@JsonTypeInfo(use = Id.NAME, property = "type", defaultImpl = NoneSelector.class)
public abstract class AbstractSelector {
public abstract class AbstractSelector implements Serializable {
private static final long serialVersionUID = 4530233098102379229L;
/**
* The type of this selector, each child class should announce its own unique type.

View File

@ -21,7 +21,7 @@
<parent>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -84,4 +84,4 @@
</plugin>
</plugins>
</build>
</project>
</project>

View File

@ -19,7 +19,7 @@
<parent>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -1,305 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.client.config.impl;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.utils.ParamUtil;
import com.alibaba.nacos.common.constant.HttpHeaderConsts;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.utils.IoUtils;
import com.alibaba.nacos.common.utils.MD5Utils;
import com.alibaba.nacos.common.utils.UuidUtils;
import com.alibaba.nacos.common.utils.VersionUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Http tool.
*
* @author Nacos
* @deprecated Use NacosRestTemplate{@link NacosRestTemplate} unified http client
*/
@Deprecated
public class HttpSimpleClient {
/**
* Get method.
*
* @param url url
* @param headers headers
* @param paramValues paramValues
* @param encoding encoding
* @param readTimeoutMs readTimeoutMs
* @param isSsl isSsl
* @return result
* @throws IOException io exception
*/
public static HttpResult httpGet(String url, List<String> headers, List<String> paramValues, String encoding,
long readTimeoutMs, boolean isSsl) throws IOException {
String encodedContent = encodingParams(paramValues, encoding);
url += (null == encodedContent) ? "" : ("?" + encodedContent);
if (Limiter
.isLimit(MD5Utils.md5Hex(new StringBuilder(url).append(encodedContent).toString(), Constants.ENCODE))) {
return new HttpResult(NacosException.CLIENT_OVER_THRESHOLD,
"More than client-side current limit threshold");
}
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(ParamUtil.getConnectTimeout() > 100 ? ParamUtil.getConnectTimeout() : 100);
conn.setReadTimeout((int) readTimeoutMs);
List<String> newHeaders = getHeaders(url, headers, paramValues);
setHeaders(conn, newHeaders, encoding);
conn.connect();
int respCode = conn.getResponseCode();
String resp = null;
if (HttpURLConnection.HTTP_OK == respCode) {
resp = IoUtils.toString(conn.getInputStream(), encoding);
} else {
resp = IoUtils.toString(conn.getErrorStream(), encoding);
}
return new HttpResult(respCode, conn.getHeaderFields(), resp);
} finally {
IoUtils.closeQuietly(conn);
}
}
/**
* 发送GET请求.
*/
public static HttpResult httpGet(String url, List<String> headers, List<String> paramValues, String encoding,
long readTimeoutMs) throws IOException {
return httpGet(url, headers, paramValues, encoding, readTimeoutMs, false);
}
/**
* 发送POST请求.
*
* @param url url
* @param headers 请求Header可以为null
* @param paramValues 参数可以为null
* @param encoding URL编码使用的字符集
* @param readTimeoutMs 响应超时
* @param isSsl 是否https
* @return result
* @throws IOException io exception
*/
public static HttpResult httpPost(String url, List<String> headers, List<String> paramValues, String encoding,
long readTimeoutMs, boolean isSsl) throws IOException {
String encodedContent = encodingParams(paramValues, encoding);
encodedContent = (null == encodedContent) ? "" : encodedContent;
if (Limiter
.isLimit(MD5Utils.md5Hex(new StringBuilder(url).append(encodedContent).toString(), Constants.ENCODE))) {
return new HttpResult(NacosException.CLIENT_OVER_THRESHOLD,
"More than client-side current limit threshold");
}
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(ParamUtil.getConnectTimeout() > 3000 ? ParamUtil.getConnectTimeout() : 3000);
conn.setReadTimeout((int) readTimeoutMs);
conn.setDoOutput(true);
conn.setDoInput(true);
List<String> newHeaders = getHeaders(url, headers, paramValues);
setHeaders(conn, newHeaders, encoding);
conn.getOutputStream().write(encodedContent.getBytes(encoding));
int respCode = conn.getResponseCode();
String resp = null;
if (HttpURLConnection.HTTP_OK == respCode) {
resp = IoUtils.toString(conn.getInputStream(), encoding);
} else {
resp = IoUtils.toString(conn.getErrorStream(), encoding);
}
return new HttpResult(respCode, conn.getHeaderFields(), resp);
} finally {
IoUtils.closeQuietly(conn);
}
}
/**
* 发送POST请求.
*
* @param url url
* @param headers 请求Header可以为null
* @param paramValues 参数可以为null
* @param encoding URL编码使用的字符集
* @param readTimeoutMs 响应超时
* @return result
* @throws IOException io exception
*/
public static HttpResult httpPost(String url, List<String> headers, List<String> paramValues, String encoding,
long readTimeoutMs) throws IOException {
return httpPost(url, headers, paramValues, encoding, readTimeoutMs, false);
}
/**
* Delete method.
*
* @param url url
* @param headers 请求Header可以为null
* @param paramValues 参数可以为null
* @param encoding URL编码使用的字符集
* @param readTimeoutMs 响应超时
* @param isSsl 是否https
* @return result
* @throws IOException io exception
*/
public static HttpResult httpDelete(String url, List<String> headers, List<String> paramValues, String encoding,
long readTimeoutMs, boolean isSsl) throws IOException {
String encodedContent = encodingParams(paramValues, encoding);
url += (null == encodedContent) ? "" : ("?" + encodedContent);
if (Limiter
.isLimit(MD5Utils.md5Hex(new StringBuilder(url).append(encodedContent).toString(), Constants.ENCODE))) {
return new HttpResult(NacosException.CLIENT_OVER_THRESHOLD,
"More than client-side current limit threshold");
}
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("DELETE");
conn.setConnectTimeout(ParamUtil.getConnectTimeout() > 100 ? ParamUtil.getConnectTimeout() : 100);
conn.setReadTimeout((int) readTimeoutMs);
List<String> newHeaders = getHeaders(url, headers, paramValues);
setHeaders(conn, newHeaders, encoding);
conn.connect();
int respCode = conn.getResponseCode();
String resp = null;
if (HttpURLConnection.HTTP_OK == respCode) {
resp = IoUtils.toString(conn.getInputStream(), encoding);
} else {
resp = IoUtils.toString(conn.getErrorStream(), encoding);
}
return new HttpResult(respCode, conn.getHeaderFields(), resp);
} finally {
IoUtils.closeQuietly(conn);
}
}
/**
* Delete method.
*
* @param url url
* @param headers 请求Header可以为null
* @param paramValues 参数可以为null
* @param encoding URL编码使用的字符集
* @param readTimeoutMs 响应超时
* @return result
* @throws IOException io exception
*/
public static HttpResult httpDelete(String url, List<String> headers, List<String> paramValues, String encoding,
long readTimeoutMs) throws IOException {
return httpGet(url, headers, paramValues, encoding, readTimeoutMs, false);
}
private static void setHeaders(HttpURLConnection conn, List<String> headers, String encoding) {
if (null != headers) {
for (Iterator<String> iter = headers.iterator(); iter.hasNext(); ) {
conn.addRequestProperty(iter.next(), iter.next());
}
}
conn.addRequestProperty(HttpHeaderConsts.CLIENT_VERSION_HEADER, VersionUtils.version);
conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + encoding);
String ts = String.valueOf(System.currentTimeMillis());
String token = MD5Utils.md5Hex(ts + ParamUtil.getAppKey(), Constants.ENCODE);
conn.addRequestProperty(Constants.CLIENT_APPNAME_HEADER, ParamUtil.getAppName());
conn.addRequestProperty(Constants.CLIENT_REQUEST_TS_HEADER, ts);
conn.addRequestProperty(Constants.CLIENT_REQUEST_TOKEN_HEADER, token);
}
private static List<String> getHeaders(String url, List<String> headers, List<String> paramValues)
throws IOException {
List<String> newHeaders = new ArrayList<String>();
newHeaders.add("exConfigInfo");
newHeaders.add("true");
newHeaders.add("RequestId");
newHeaders.add(UuidUtils.generateUuid());
if (headers != null) {
newHeaders.addAll(headers);
}
return newHeaders;
}
private static String encodingParams(List<String> paramValues, String encoding)
throws UnsupportedEncodingException {
StringBuilder sb = new StringBuilder();
if (null == paramValues) {
return null;
}
for (Iterator<String> iter = paramValues.iterator(); iter.hasNext(); ) {
sb.append(iter.next()).append("=");
sb.append(URLEncoder.encode(iter.next(), encoding));
if (iter.hasNext()) {
sb.append("&");
}
}
return sb.toString();
}
public static class HttpResult {
public final int code;
public final Map<String, List<String>> headers;
public final String content;
public HttpResult(int code, String content) {
this.code = code;
this.headers = null;
this.content = content;
}
public HttpResult(int code, Map<String, List<String>> headers, String content) {
this.code = code;
this.headers = headers;
this.content = content;
}
@Override
public String toString() {
return "HttpResult{" + "code=" + code + ", headers=" + headers + ", content='" + content + '\'' + '}';
}
}
}

View File

@ -1,248 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.client.naming.remote.http;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.tls.TlsSystemConfig;
import com.alibaba.nacos.common.utils.HttpMethod;
import com.alibaba.nacos.common.utils.IoUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.google.common.net.HttpHeaders;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import static com.alibaba.nacos.client.utils.LogUtils.NAMING_LOGGER;
/**
* Http client.
*
* @author nkorange
* @deprecated Use NacosRestTemplate{@link NacosRestTemplate} unified http client
*/
@Deprecated
public class HttpClient {
public static final int READ_TIME_OUT_MILLIS = Integer
.getInteger("com.alibaba.nacos.client.naming.rtimeout", 50000);
public static final int CON_TIME_OUT_MILLIS = Integer.getInteger("com.alibaba.nacos.client.naming.ctimeout", 3000);
private static final boolean ENABLE_HTTPS = Boolean.getBoolean(TlsSystemConfig.TLS_ENABLE);
static {
// limit max redirection
System.setProperty("http.maxRedirects", "5");
}
public static String getPrefix() {
if (ENABLE_HTTPS) {
return "https://";
}
return "http://";
}
public static HttpResult httpGet(String url, List<String> headers, Map<String, String> paramValues,
String encoding) {
return request(url, headers, paramValues, StringUtils.EMPTY, encoding, HttpMethod.GET);
}
/**
* request.
*
* @param url url
* @param headers headers
* @param paramValues paramValues
* @param body body
* @param encoding encoding
* @param method method
* @return result
*/
public static HttpResult request(String url, List<String> headers, Map<String, String> paramValues, String body,
String encoding, String method) {
HttpURLConnection conn = null;
try {
String encodedContent = encodingParams(paramValues, encoding);
url += (StringUtils.isEmpty(encodedContent)) ? "" : ("?" + encodedContent);
conn = (HttpURLConnection) new URL(url).openConnection();
setHeaders(conn, headers, encoding);
conn.setConnectTimeout(CON_TIME_OUT_MILLIS);
conn.setReadTimeout(READ_TIME_OUT_MILLIS);
conn.setRequestMethod(method);
conn.setDoOutput(true);
if (StringUtils.isNotBlank(body)) {
byte[] b = body.getBytes();
conn.setRequestProperty("Content-Length", String.valueOf(b.length));
conn.getOutputStream().write(b, 0, b.length);
conn.getOutputStream().flush();
conn.getOutputStream().close();
}
conn.connect();
if (NAMING_LOGGER.isDebugEnabled()) {
NAMING_LOGGER.debug("Request from server: " + url);
}
return getResult(conn);
} catch (Exception e) {
try {
if (conn != null) {
NAMING_LOGGER.warn("failed to request " + conn.getURL() + " from " + InetAddress
.getByName(conn.getURL().getHost()).getHostAddress());
}
} catch (Exception ex) {
NAMING_LOGGER.error("[NA] failed to request ", ex);
//ignore
}
NAMING_LOGGER.error("[NA] failed to request ", e);
return new HttpResult(500, e.toString(), Collections.<String, String>emptyMap());
} finally {
IoUtils.closeQuietly(conn);
}
}
private static HttpResult getResult(HttpURLConnection conn) throws IOException {
int respCode = conn.getResponseCode();
InputStream inputStream;
if (HttpURLConnection.HTTP_OK == respCode || HttpURLConnection.HTTP_NOT_MODIFIED == respCode
|| Constants.WRITE_REDIRECT_CODE == respCode) {
inputStream = conn.getInputStream();
} else {
inputStream = conn.getErrorStream();
}
Map<String, String> respHeaders = new HashMap<String, String>(conn.getHeaderFields().size());
for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
respHeaders.put(entry.getKey(), entry.getValue().get(0));
}
String encodingGzip = "gzip";
if (encodingGzip.equals(respHeaders.get(HttpHeaders.CONTENT_ENCODING))) {
inputStream = new GZIPInputStream(inputStream);
}
HttpResult httpResult = new HttpResult(respCode, IoUtils.toString(inputStream, getCharset(conn)), respHeaders);
//InputStream from HttpURLConnection can be closed automatically,but new GZIPInputStream can't be closed automatically
//so needs to close it manually
if (inputStream instanceof GZIPInputStream) {
inputStream.close();
}
return httpResult;
}
private static String getCharset(HttpURLConnection conn) {
String contentType = conn.getContentType();
if (StringUtils.isEmpty(contentType)) {
return "UTF-8";
}
String[] values = contentType.split(";");
if (values.length == 0) {
return "UTF-8";
}
String charset = "UTF-8";
for (String value : values) {
value = value.trim();
if (value.toLowerCase().startsWith("charset=")) {
charset = value.substring("charset=".length());
}
}
return charset;
}
private static void setHeaders(HttpURLConnection conn, List<String> headers, String encoding) {
if (null != headers) {
for (Iterator<String> iter = headers.iterator(); iter.hasNext(); ) {
conn.addRequestProperty(iter.next(), iter.next());
}
}
conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + encoding);
conn.addRequestProperty("Accept-Charset", encoding);
}
private static String encodingParams(Map<String, String> params, String encoding)
throws UnsupportedEncodingException {
if (null == params || params.isEmpty()) {
return "";
}
params.put("encoding", encoding);
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (StringUtils.isEmpty(entry.getValue())) {
continue;
}
sb.append(entry.getKey()).append("=");
sb.append(URLEncoder.encode(entry.getValue(), encoding));
sb.append("&");
}
if (sb.length() > 0) {
sb = sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
public static class HttpResult {
public final int code;
public final String content;
private final Map<String, String> respHeaders;
public HttpResult(int code, String content, Map<String, String> respHeaders) {
this.code = code;
this.content = content;
this.respHeaders = respHeaders;
}
public String getHeader(String name) {
return respHeaders.get(name);
}
@Override
public String toString() {
return "HttpResult{" + "code=" + code + ", content='" + content + '\'' + ", respHeaders=" + respHeaders
+ '}';
}
}
}

View File

@ -21,7 +21,7 @@
<parent>
<artifactId>nacos-all</artifactId>
<groupId>com.alibaba.nacos</groupId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -21,7 +21,7 @@
<parent>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -1,124 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.common.http;
import com.alibaba.nacos.common.http.handler.ResponseHandler;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.model.RestResult;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.util.EntityUtils;
import java.lang.reflect.Type;
import java.net.URI;
/**
* Base http client.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
* @deprecated Refer to the new {@link com.alibaba.nacos.common.http.client.request.HttpClientRequest}
*/
@Deprecated
public abstract class BaseHttpClient {
protected <T> RestResult<T> execute(CloseableHttpClient httpClient, final Type type, HttpUriRequest request)
throws Exception {
CloseableHttpResponse response = httpClient.execute(request);
try {
final String body = EntityUtils.toString(response.getEntity());
RestResult<T> data = ResponseHandler.convert(body, type);
return data;
} finally {
HttpClientUtils.closeQuietly(response);
}
}
protected <T> void execute(CloseableHttpAsyncClient httpAsyncClient, final Type type, final Callback<T> callback,
final HttpUriRequest request) {
if (!httpAsyncClient.isRunning()) {
throw new IllegalArgumentException("httpAsyncClient already shutdown");
}
httpAsyncClient.execute(request, new FutureCallback<HttpResponse>() {
@Override
public void completed(HttpResponse response) {
try {
final String body = EntityUtils.toString(response.getEntity());
RestResult<T> data = ResponseHandler.convert(body, type);
data.setCode(response.getStatusLine().getStatusCode());
callback.onReceive(data);
} catch (Throwable e) {
callback.onError(e);
}
}
@Override
public void failed(Exception ex) {
callback.onError(ex);
}
@Override
public void cancelled() {
callback.onCancel();
}
});
}
protected String buildUrl(String baseUrl, Query query) {
if (query.isEmpty()) {
return baseUrl;
}
return baseUrl + "?" + query.toQueryUrl();
}
protected HttpRequestBase build(String url, Header header, String method) throws Exception {
return build(url, header, null, method);
}
protected HttpRequestBase build(String url, Header header, Object body, String method) throws Exception {
final BaseHttpMethod httpMethod = BaseHttpMethod.sourceOf(method);
final HttpRequestBase httpRequestBase = httpMethod.init(url);
HttpUtils.initRequestHeader(httpRequestBase, header);
HttpUtils.initRequestEntity(httpRequestBase, body, header);
return httpRequestBase;
}
public static class HttpGetWithEntity extends HttpEntityEnclosingRequestBase {
public static final String METHOD_NAME = "GET";
public HttpGetWithEntity(String url) {
super();
setURI(URI.create(url));
}
@Override
public String getMethod() {
return METHOD_NAME;
}
}
}

View File

@ -1,88 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.common.http;
import com.alibaba.nacos.common.utils.ExceptionUtil;
import com.alibaba.nacos.common.utils.ThreadUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Use the same HttpClient object in the same space.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
* @deprecated Refer to the new {@link HttpClientBeanHolder}
*/
@Deprecated
public class HttpClientManager {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientManager.class);
private static final int TIMEOUT = Integer.getInteger("nacos.http.timeout", 5000);
private static final RequestConfig DEFAULT_CONFIG = RequestConfig.custom().setConnectTimeout(TIMEOUT)
.setSocketTimeout(TIMEOUT << 1).build();
private static final NSyncHttpClient SYNC_HTTP_CLIENT = new NacosSyncHttpClient(
HttpClients.custom().setDefaultRequestConfig(DEFAULT_CONFIG).build());
private static final NAsyncHttpClient ASYNC_HTTP_CLIENT = new NacosAsyncHttpClient(
HttpAsyncClients.custom().setDefaultRequestConfig(DEFAULT_CONFIG).build());
private static final AtomicBoolean ALREADY_SHUTDOWN = new AtomicBoolean(false);
static {
ThreadUtils.addShutdownHook(new Runnable() {
@Override
public void run() {
shutdown();
}
});
}
public static NSyncHttpClient getSyncHttpClient() {
return SYNC_HTTP_CLIENT;
}
public static NAsyncHttpClient getAsyncHttpClient() {
return ASYNC_HTTP_CLIENT;
}
/**
* Shutdown http client manager and close http client.
*/
public static void shutdown() {
if (!ALREADY_SHUTDOWN.compareAndSet(false, true)) {
return;
}
LOGGER.warn("[HttpClientManager] Start destroying HttpClient");
try {
SYNC_HTTP_CLIENT.close();
ASYNC_HTTP_CLIENT.close();
} catch (Exception ex) {
LOGGER.error("An exception occurred when the HTTP client was closed : {}", ExceptionUtil.getStackTrace(ex));
}
LOGGER.warn("[HttpClientManager] Destruction of the end");
}
}

View File

@ -1,95 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.common.http;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import java.lang.reflect.Type;
/**
* Nacos async http client interface.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
* @deprecated Refer to the new {@link com.alibaba.nacos.common.http.client.NacosAsyncRestTemplate}
*/
@Deprecated
@SuppressWarnings("all")
public interface NAsyncHttpClient extends NHttpClient {
/**
* http get
*
* @param url url
* @param header http header param
* @param query http query param
* @param token return type
* @param callback {@link Callback#onReceive(com.alibaba.nacos.common.model.RestResult)}
*/
<T> void get(String url, Header header, Query query, Type token, Callback<T> callback) throws Exception;
/**
* get request, may be pulling a lot of data
*
* @param url url
* @param header http header param
* @param query http query param
* @param body get with body
* @param token return type
* @param callback {@link Callback#onReceive(com.alibaba.nacos.common.model.RestResult)}
*/
<T> void getLarge(String url, Header header, Query query, Object body, Type token, Callback<T> callback)
throws Exception;
/**
* http delete
*
* @param url url
* @param header http header param
* @param query http query param
* @param token return type
* @param callback {@link Callback#onReceive(com.alibaba.nacos.common.model.RestResult)}
*/
<T> void delete(String url, Header header, Query query, Type token, Callback<T> callback) throws Exception;
/**
* http put
*
* @param url url
* @param header http header param
* @param query http query param
* @param body http body param
* @param token return type
* @param callback {@link Callback#onReceive(com.alibaba.nacos.common.model.RestResult)}
*/
<T> void put(String url, Header header, Query query, Object body, Type token, Callback<T> callback)
throws Exception;
/**
* http post
*
* @param url url
* @param header http header param
* @param query http query param
* @param body http body param
* @param token return type
* @param callback {@link Callback#onReceive(com.alibaba.nacos.common.model.RestResult)}
*/
<T> void post(String url, Header header, Query query, Object body, Type token, Callback<T> callback)
throws Exception;
}

View File

@ -1,98 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.common.http;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.model.RestResult;
import java.lang.reflect.Type;
/**
* Nacos sync http client.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
* @deprecated Refer to the new {@link com.alibaba.nacos.common.http.client.NacosRestTemplate}
*/
@Deprecated
@SuppressWarnings("all")
public interface NSyncHttpClient extends NHttpClient {
/**
* http get
*
* @param url url
* @param header http header param
* @param query http query param
* @param token return type
* @return {@link RestResult <T>}
* @throws Exception
*/
<T> RestResult<T> get(String url, Header header, Query query, Type token) throws Exception;
/**
* get request, may be pulling a lot of data
*
* @param url url
* @param header http header param
* @param query http query param
* @param body get with body
* @param token return type
* @return {@link RestResult <T>}
* @throws Exception
*/
<T> RestResult<T> getLarge(String url, Header header, Query query, Object body, Type token) throws Exception;
/**
* http delete
*
* @param url url
* @param header http header param
* @param query http query param
* @param token return type
* @return {@link RestResult <T>}
* @throws Exception
*/
<T> RestResult<T> delete(String url, Header header, Query query, Type token) throws Exception;
/**
* http put
*
* @param url url
* @param header http header param
* @param query http query param
* @param body http body param
* @param token return type
* @return {@link RestResult}
* @throws Exception
*/
<T> RestResult<T> put(String url, Header header, Query query, Object body, Type token) throws Exception;
/**
* http post
*
* @param url url
* @param header http header param
* @param query http query param
* @param body http body param
* @param token return type
* @return {@link RestResult}
* @throws Exception
*/
<T> RestResult<T> post(String url, Header header, Query query, Object body, Type token) throws Exception;
}

View File

@ -1,83 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.common.http;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.utils.HttpMethod;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* Nacos async http client.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
* @deprecated Refer to the new {@link com.alibaba.nacos.common.http.client.request.DefaultAsyncHttpClientRequest}
*/
@Deprecated
class NacosAsyncHttpClient extends BaseHttpClient implements NAsyncHttpClient {
private CloseableHttpAsyncClient asyncClient;
NacosAsyncHttpClient(CloseableHttpAsyncClient asyncClient) {
this.asyncClient = asyncClient;
this.asyncClient.start();
}
@Override
public <T> void get(final String url, final Header header, final Query query, final Type token,
final Callback<T> callback) throws Exception {
HttpRequestBase requestBase = build(buildUrl(url, query), header, HttpMethod.GET);
execute(asyncClient, token, callback, requestBase);
}
@Override
public <T> void getLarge(final String url, final Header header, final Query query, final Object body,
final Type token, final Callback<T> callback) throws Exception {
HttpRequestBase requestBase = build(buildUrl(url, query), header, body, HttpMethod.GET_LARGE);
execute(asyncClient, token, callback, requestBase);
}
@Override
public <T> void delete(final String url, final Header header, final Query query, final Type token,
final Callback<T> callback) throws Exception {
HttpRequestBase requestBase = build(buildUrl(url, query), header, HttpMethod.DELETE);
execute(asyncClient, token, callback, requestBase);
}
@Override
public <T> void put(final String url, final Header header, final Query query, final Object body, final Type token,
final Callback<T> callback) throws Exception {
HttpRequestBase requestBase = build(buildUrl(url, query), header, body, HttpMethod.PUT);
execute(asyncClient, token, callback, requestBase);
}
@Override
public <T> void post(final String url, final Header header, final Query query, final Object body, final Type token,
final Callback<T> callback) throws Exception {
HttpRequestBase requestBase = build(buildUrl(url, query), header, body, HttpMethod.POST);
execute(asyncClient, token, callback, requestBase);
}
@Override
public void close() throws IOException {
asyncClient.close();
}
}

View File

@ -1,83 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.common.http;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.model.RestResult;
import com.alibaba.nacos.common.utils.HttpMethod;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* Nacos sync http client.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
* @deprecated Refer to the new {@link com.alibaba.nacos.common.http.client.request.JdkHttpClientRequest}
*/
@Deprecated
class NacosSyncHttpClient extends BaseHttpClient implements NSyncHttpClient {
private CloseableHttpClient client;
NacosSyncHttpClient(CloseableHttpClient client) {
this.client = client;
}
@Override
public <T> RestResult<T> get(final String url, final Header header, final Query query, final Type token)
throws Exception {
HttpRequestBase requestBase = build(buildUrl(url, query), header, HttpMethod.GET);
return execute(client, token, requestBase);
}
@Override
public <T> RestResult<T> getLarge(String url, Header header, Query query, Object body, Type token)
throws Exception {
HttpRequestBase requestBase = build(buildUrl(url, query), header, body, HttpMethod.GET_LARGE);
return execute(client, token, requestBase);
}
@Override
public <T> RestResult<T> delete(final String url, final Header header, final Query query, final Type token)
throws Exception {
HttpRequestBase requestBase = build(buildUrl(url, query), header, HttpMethod.DELETE);
return execute(client, token, requestBase);
}
@Override
public <T> RestResult<T> put(final String url, final Header header, final Query query, final Object body,
final Type token) throws Exception {
HttpRequestBase requestBase = build(buildUrl(url, query), header, body, HttpMethod.PUT);
return execute(client, token, requestBase);
}
@Override
public <T> RestResult<T> post(final String url, final Header header, final Query query, final Object body,
final Type token) throws Exception {
HttpRequestBase requestBase = build(buildUrl(url, query), header, body, HttpMethod.POST);
return execute(client, token, requestBase);
}
@Override
public void close() throws IOException {
client.close();
}
}

View File

@ -56,7 +56,7 @@ public class DefaultPublisher extends Thread implements EventPublisher {
protected volatile Long lastEventSequence = -1L;
private final AtomicReferenceFieldUpdater<DefaultPublisher, Long> updater = AtomicReferenceFieldUpdater
private static final AtomicReferenceFieldUpdater<DefaultPublisher, Long> UPDATER = AtomicReferenceFieldUpdater
.newUpdater(DefaultPublisher.class, Long.class, "lastEventSequence");
@Override
@ -116,7 +116,7 @@ public class DefaultPublisher extends Thread implements EventPublisher {
}
final Event event = queue.take();
receiveEvent(event);
updater.compareAndSet(this, lastEventSequence, Math.max(lastEventSequence, event.sequence()));
UPDATER.compareAndSet(this, lastEventSequence, Math.max(lastEventSequence, event.sequence()));
}
} catch (Throwable ex) {
LOGGER.error("Event listener exception : {}", ex);

View File

@ -79,7 +79,7 @@ public class DefaultSharePublisher extends DefaultPublisher {
try {
Set<Subscriber> sets = subMappings.get(subSlowEventType);
if (sets != null && sets.contains(subscriber)) {
if (sets != null) {
sets.remove(subscriber);
}
} finally {

View File

@ -129,21 +129,22 @@ public class MapUtils {
/**
* ComputeIfAbsent lazy load.
*
* @param target target Map data.
* @param key map key.
* @param mappingFunction funtion which is need to be executed.
* @param param1 function's parameter value1.
* @param param2 function's parameter value1.
* @param target target Map data.
* @param key map key.
* @param mappingFunction function which is need to be executed.
* @param param1 function's parameter value1.
* @param param2 function's parameter value1.
* @return
*/
@NotThreadSafe
public static Object computeIfAbsent(Map target, Object key, BiFunction mappingFunction, Object param1,
Object param2) {
Objects.requireNonNull(target, "target");
Objects.requireNonNull(key, "key");
Objects.requireNonNull(key, "mappingFunction");
Objects.requireNonNull(key, "param1");
Objects.requireNonNull(key, "param2");
Objects.requireNonNull(mappingFunction, "mappingFunction");
Objects.requireNonNull(param1, "param1");
Objects.requireNonNull(param2, "param2");
Object val = target.get(key);
if (val == null) {
@ -153,5 +154,4 @@ public class MapUtils {
}
return val;
}
}

View File

@ -17,6 +17,7 @@
package com.alibaba.nacos.common.utils;
import java.io.InputStream;
import java.util.Comparator;
import java.util.Properties;
/**
@ -59,6 +60,38 @@ public class VersionUtils {
}
}
private static final Comparator<String> STRING_COMPARATOR = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
};
/**
* compare two version who is latest.
*
* @param versionA version A, like x.y.z(-beta)
* @param versionB version B, like x.y.z(-beta)
* @return compare result
*/
public static int compareVersion(final String versionA, final String versionB) {
final String[] sA = versionA.split("\\.");
final String[] sB = versionB.split("\\.");
int expectSize = 3;
if (sA.length != expectSize || sB.length != expectSize) {
throw new IllegalArgumentException("version must be like x.y.z(-beta)");
}
int first = Objects.compare(sA[0], sB[0], STRING_COMPARATOR);
if (first != 0) {
return first;
}
int second = Objects.compare(sA[1], sB[1], STRING_COMPARATOR);
if (second != 0) {
return second;
}
return Objects.compare(sA[2].split("-")[0], sB[2].split("-")[0], STRING_COMPARATOR);
}
public static String getFullClientVersion() {
return clientVersion;
}

View File

@ -25,6 +25,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
@ -48,8 +50,9 @@ public class NacosExecuteTaskExecuteEngineTest {
private AbstractExecuteTask task;
@Test
public void testAddTask() {
public void testAddTask() throws InterruptedException {
executeTaskExecuteEngine.addTask("test", task);
TimeUnit.SECONDS.sleep(1);
verify(task).run();
assertTrue(executeTaskExecuteEngine.isEmpty());
assertEquals(0, executeTaskExecuteEngine.size());

View File

@ -0,0 +1,66 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.common.utils;
import org.junit.Assert;
import org.junit.Test;
public class VersionUtilsTest {
@Test
public void testVersionCompareLt() {
final String versionA = "1.2.0";
final String versionB = "1.2.1";
Assert.assertTrue(VersionUtils.compareVersion(versionA, versionB) < 0);
}
@Test
public void testVersionCompareGt() {
final String versionA = "1.2.2";
final String versionB = "1.2.1";
Assert.assertTrue(VersionUtils.compareVersion(versionA, versionB) > 0);
}
@Test
public void testVersionCompareEt() {
final String versionA = "1.2.1";
final String versionB = "1.2.1";
Assert.assertEquals(0, VersionUtils.compareVersion(versionA, versionB));
}
@Test
public void testVersionCompareLtWithChar() {
final String versionA = "1.2.0-beta";
final String versionB = "1.2.1";
Assert.assertTrue(VersionUtils.compareVersion(versionA, versionB) < 0);
}
@Test
public void testVersionCompareGtWithChar() {
final String versionA = "1.2.2-beta";
final String versionB = "1.2.1-beta";
Assert.assertTrue(VersionUtils.compareVersion(versionA, versionB) > 0);
}
@Test
public void testVersionCompareEtWithChar() {
final String versionA = "1.2.1";
final String versionB = "1.2.1-beta";
Assert.assertEquals(0, VersionUtils.compareVersion(versionA, versionB));
}
}

View File

@ -20,7 +20,7 @@
<parent>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -28,9 +28,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* History management controller.
*
@ -46,11 +43,11 @@ public class HistoryController {
/**
* Query the list history config.
*
* @param dataId dataId string value.
* @param group group string value.
* @param tenant tenant string value.
* @param appName appName string value.
* @param pageNo pageNo string value.
* @param dataId dataId string value.
* @param group group string value.
* @param tenant tenant string value.
* @param appName appName string value.
* @param pageNo pageNo string value.
* @param pageSize pageSize string value.
* @param modelMap modeMap.
* @return
@ -72,12 +69,26 @@ public class HistoryController {
}
/**
* Query the detailed configuration history informations.
* Query the detailed configuration history information.
*
* @param nid history_config_info nid
* @return history config info
*/
@GetMapping
public ConfigHistoryInfo getConfigHistoryInfo(HttpServletRequest request, HttpServletResponse response,
@RequestParam("nid") Long nid, ModelMap modelMap) {
public ConfigHistoryInfo getConfigHistoryInfo(@RequestParam("nid") Long nid) {
return persistService.detailConfigHistory(nid);
}
/**
* Query previous config history information.
*
* @param id config_info id
* @return history config info
* @since 1.4.0
*/
@GetMapping(value = "/previous")
public ConfigHistoryInfo getPreviousConfigHistoryInfo(@RequestParam("id") Long id) {
return persistService.detailPreviousConfigHistory(id);
}
}

View File

@ -133,7 +133,8 @@ public abstract class DumpService {
protected void dumpOperate(DumpProcessor processor, DumpAllProcessor dumpAllProcessor,
DumpAllBetaProcessor dumpAllBetaProcessor, DumpAllTagProcessor dumpAllTagProcessor) throws NacosException {
TimerContext.start("CONFIG_DUMP_TO_FILE");
String dumpFileContext = "CONFIG_DUMP_TO_FILE";
TimerContext.start(dumpFileContext);
try {
LogUtil.DEFAULT_LOG.warn("DumpService start");
@ -229,7 +230,7 @@ public abstract class DumpService {
ConfigExecutor.scheduleConfigTask(clearConfigHistory, 10, 10, TimeUnit.MINUTES);
} finally {
TimerContext.end(LogUtil.DUMP_LOG);
TimerContext.end(dumpFileContext, LogUtil.DUMP_LOG);
}
}

View File

@ -1077,6 +1077,15 @@ public interface PersistService {
*/
ConfigHistoryInfo detailConfigHistory(Long nid);
/**
* Get previous config detail.
*
* @param id id
* @return {@link ConfigHistoryInfo}
*/
ConfigHistoryInfo detailPreviousConfigHistory(Long id);
/**
* insert tenant info.
*
@ -1229,4 +1238,5 @@ public interface PersistService {
* @return count by tenantId
*/
int tenantInfoCountByTenantId(String tenantId);
}

View File

@ -51,6 +51,10 @@ import javax.sql.DataSource;
*/
public class DerbySnapshotOperation implements SnapshotOperation {
private static final String DERBY_SNAPSHOT_SAVE = DerbySnapshotOperation.class.getSimpleName() + ".SAVE";
private static final String DERBY_SNAPSHOT_LOAD = DerbySnapshotOperation.class.getSimpleName() + ".LOAD";
private final String backupSql = "CALL SYSCS_UTIL.SYSCS_BACKUP_DATABASE(?)";
private final String snapshotDir = "derby_data";
@ -72,8 +76,7 @@ public class DerbySnapshotOperation implements SnapshotOperation {
@Override
public void onSnapshotSave(Writer writer, BiConsumer<Boolean, Throwable> callFinally) {
RaftExecutor.doSnapshot(() -> {
TimerContext.start("CONFIG_DERBY_SNAPSHOT_SAVE");
TimerContext.start(DERBY_SNAPSHOT_SAVE);
final Lock lock = writeLock;
lock.lock();
@ -100,7 +103,7 @@ public class DerbySnapshotOperation implements SnapshotOperation {
callFinally.accept(false, t);
} finally {
lock.unlock();
TimerContext.end(LogUtil.FATAL_LOG);
TimerContext.end(DERBY_SNAPSHOT_SAVE, LogUtil.FATAL_LOG);
}
});
}
@ -109,8 +112,7 @@ public class DerbySnapshotOperation implements SnapshotOperation {
public boolean onSnapshotLoad(Reader reader) {
final String readerPath = reader.getPath();
final String sourceFile = Paths.get(readerPath, snapshotArchive).toString();
TimerContext.start("CONFIG_DERBY_SNAPSHOT_LOAD");
TimerContext.start(DERBY_SNAPSHOT_LOAD);
final Lock lock = writeLock;
lock.lock();
try {
@ -145,7 +147,7 @@ public class DerbySnapshotOperation implements SnapshotOperation {
return false;
} finally {
lock.unlock();
TimerContext.end(LogUtil.FATAL_LOG);
TimerContext.end(DERBY_SNAPSHOT_LOAD, LogUtil.FATAL_LOG);
}
}

View File

@ -2059,6 +2059,12 @@ public class EmbeddedStoragePersistServiceImpl implements PersistService {
return databaseOperate.queryOne(sqlFetchRows, new Object[] {nid}, HISTORY_DETAIL_ROW_MAPPER);
}
@Override
public ConfigHistoryInfo detailPreviousConfigHistory(Long id) {
String sqlFetchRows = "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create,gmt_modified FROM his_config_info WHERE nid = (select max(nid) from his_config_info where id = ?)";
return databaseOperate.queryOne(sqlFetchRows, new Object[] {id}, HISTORY_DETAIL_ROW_MAPPER);
}
@Override
public void insertTenantInfoAtomic(String kp, String tenantId, String tenantName, String tenantDesc,
String createResoure, final long time) {
@ -2299,7 +2305,7 @@ public class EmbeddedStoragePersistServiceImpl implements PersistService {
int skipCount = 0;
List<Map<String, String>> failData = null;
List<Map<String, String>> skipData = null;
final BiConsumer<Boolean, Throwable> callFinally = (result, t) -> {
if (t != null) {
throw new NacosRuntimeException(0, t);

View File

@ -2297,6 +2297,19 @@ public class ExternalStoragePersistServiceImpl implements PersistService {
}
}
@Override
public ConfigHistoryInfo detailPreviousConfigHistory(Long id) {
String sqlFetchRows = "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create,gmt_modified FROM his_config_info WHERE nid = (select max(nid) from his_config_info where id = ?) ";
try {
ConfigHistoryInfo historyInfo = jt
.queryForObject(sqlFetchRows, new Object[] {id}, HISTORY_DETAIL_ROW_MAPPER);
return historyInfo;
} catch (DataAccessException e) {
LogUtil.FATAL_LOG.error("[detail-previous-config-history] error, id:{}", new Object[] {id}, e);
throw e;
}
}
@Override
public void insertTenantInfoAtomic(String kp, String tenantId, String tenantName, String tenantDesc,
String createResoure, final long time) {

View File

@ -85,7 +85,7 @@
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
</parent>
<properties>

View File

@ -48,4 +48,7 @@ public class SerializeFactory {
return SERIALIZER_MAP.get(defaultSerializer);
}
public static Serializer getSerializer(String type) {
return SERIALIZER_MAP.get(type.toLowerCase());
}
}

View File

@ -16,6 +16,7 @@
package com.alibaba.nacos.consistency;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -28,6 +29,15 @@ public interface Serializer {
Map<String, Class<?>> CLASS_CACHE = new ConcurrentHashMap<>(8);
/**
* Deserialize the data.
*
* @param data byte[]
* @param <T> class type
* @return target object instance
*/
<T> T deserialize(byte[] data);
/**
* Deserialize the data.
*
@ -36,7 +46,17 @@ public interface Serializer {
* @param <T> class type
* @return target object instance
*/
<T> T deserialize(byte[] data, Class cls);
<T> T deserialize(byte[] data, Class<T> cls);
/**
* Deserialize the data.
*
* @param data byte[]
* @param type data type
* @param <T> class type
* @return target object instance
*/
<T> T deserialize(byte[] data, Type type);
/**
* Deserialize the data.

View File

@ -29,4 +29,6 @@ public class MetadataKey {
public static final String RAFT_GROUP_MEMBER = "raftGroupMember";
public static final String ERR_MSG = "errMsg";
}

View File

@ -25,6 +25,7 @@ import com.caucho.hessian.io.SerializerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Type;
/**
* hessian serializer.
@ -40,12 +41,25 @@ public class HessianSerializer implements Serializer {
}
@Override
public <T> T deserialize(byte[] data, Class cls) {
public <T> T deserialize(byte[] data) {
return deseiralize0(data);
}
@Override
public <T> T deserialize(byte[] data, Class<T> cls) {
return deserialize(data);
}
@Override
public <T> T deserialize(byte[] data, Type type) {
return deserialize(data);
}
private <T> T deseiralize0(byte[] data) {
if (ByteUtils.isEmpty(data)) {
return null;
}
Hessian2Input input = new Hessian2Input(new ByteArrayInputStream(data));
input.setSerializerFactory(serializerFactory);
Object resultObject;
@ -78,4 +92,4 @@ public class HessianSerializer implements Serializer {
return "Hessian";
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.consistency.serialize;
import com.alibaba.nacos.common.utils.ByteUtils;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.consistency.Serializer;
import java.lang.reflect.Type;
/**
* Serializer implement by jackson.
*
* @author xiweng.yy
*/
public class JacksonSerializer implements Serializer {
@Override
public <T> T deserialize(byte[] data) {
throw new UnsupportedOperationException("Jackson serializer can't support deserialize json without type");
}
@Override
public <T> T deserialize(byte[] data, Class<T> cls) {
if (ByteUtils.isEmpty(data)) {
return null;
}
return JacksonUtils.toObj(data, cls);
}
@Override
public <T> T deserialize(byte[] data, Type type) {
if (ByteUtils.isEmpty(data)) {
return null;
}
return JacksonUtils.toObj(data, type);
}
@Override
public <T> byte[] serialize(T obj) {
return JacksonUtils.toJsonBytes(obj);
}
@Override
public String name() {
return "JSON";
}
}

View File

@ -0,0 +1,17 @@
#
# Copyright 1999-2018 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
com.alibaba.nacos.consistency.serialize.JacksonSerializer

View File

@ -21,7 +21,7 @@
<parent>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
</parent>
<artifactId>nacos-console</artifactId>
<packaging>jar</packaging>

View File

@ -32,6 +32,9 @@ class DiffEditorDialog extends React.Component {
static propTypes = {
publishConfig: PropTypes.func,
title: PropTypes.string,
currentArea: PropTypes.string,
originalArea: PropTypes.string,
};
constructor(props) {
@ -71,6 +74,7 @@ class DiffEditorDialog extends React.Component {
highlightDifferences: true,
connect: 'align',
collapseIdentical: false,
revertButtons: typeof this.props.publishConfig === 'function',
});
}
@ -80,19 +84,29 @@ class DiffEditorDialog extends React.Component {
}
render() {
const { locale = {} } = this.props;
const { locale = {}, title, currentArea, originalArea } = this.props;
const publishButton = (
<Button type="primary" onClick={this.confirmPub.bind(this)}>
{locale.publish}
</Button>
);
const footer = (
<div>
{' '}
<Button type="primary" onClick={this.confirmPub.bind(this)}>
{locale.publish}
</Button>
{typeof this.props.publishConfig === 'function' ? (
publishButton
) : (
<Button type="primary" onClick={this.closeDialog.bind(this)}>
{locale.back}
</Button>
)}
</div>
);
console.log(footer);
return (
<div>
<Dialog
title={locale.contents}
title={title}
style={{ width: '80%' }}
visible={this.state.dialogvisible}
footer={footer}
@ -101,8 +115,8 @@ class DiffEditorDialog extends React.Component {
<div style={{ height: 400 }}>
<div>
<Row>
<Col style={{ textAlign: 'center' }}>{locale.currentArea}</Col>
<Col style={{ textAlign: 'center' }}>{locale.originalValue}</Col>
<Col style={{ textAlign: 'center' }}>{currentArea}</Col>
<Col style={{ textAlign: 'center' }}>{originalArea}</Col>
</Row>
</div>
<div style={{ clear: 'both', height: 480 }} ref={this.diffeditor} />

View File

@ -378,9 +378,7 @@ const I18N_CONF = {
},
DiffEditorDialog: {
publish: 'Publish',
contents: 'Content Comparison',
currentArea: 'Current Value',
originalValue: 'Original Value',
back: 'Back',
},
ConfigEditor: {
official: 'Official',
@ -411,6 +409,9 @@ const I18N_CONF = {
publish: 'Publish',
back: 'Back',
codeValErrorPrompt: 'Configuration information may have syntax errors. Are you sure to submit?',
dialogTitle: 'Content Comparison',
dialogCurrentArea: 'Current Value',
dialogOriginalArea: 'Original Value',
},
EditorNameSpace: {
notice: 'Notice',
@ -498,6 +499,9 @@ const I18N_CONF = {
betaRelease: 'Beta Publish:',
configuration: 'Configuration Content:',
back: 'Back',
versionComparison: 'Version Comparison',
dialogCurrentArea: 'Current Version',
dialogOriginalArea: 'Previous Version',
},
ConfigRollback: {
rollBack: 'Roll Back',

View File

@ -376,9 +376,7 @@ const I18N_CONF = {
},
DiffEditorDialog: {
publish: '确认发布',
contents: '内容比较',
currentArea: '当前值',
originalValue: '原始值',
back: '返回',
},
ConfigEditor: {
official: '正式',
@ -409,6 +407,9 @@ const I18N_CONF = {
publish: '发布',
back: '返回',
codeValErrorPrompt: '配置信息可能有语法错误, 确定提交吗?',
dialogTitle: '内容比较',
dialogCurrentArea: '当前值',
dialogOriginalArea: '原始值',
},
EditorNameSpace: {
notice: '提示',
@ -495,6 +496,9 @@ const I18N_CONF = {
betaRelease: 'Beta发布',
configuration: '配置内容',
back: '返回',
versionComparison: '版本对比',
dialogCurrentArea: '当前版本',
dialogOriginalArea: '上一版本',
},
ConfigRollback: {
rollBack: '回滚配置',

View File

@ -15,15 +15,27 @@
*/
import React from 'react';
import { Button, ConfigProvider, Dialog, Field, Form, Input, Loading, Tab } from '@alifd/next';
import {
Button,
ConfigProvider,
Dialog,
Field,
Form,
Input,
Loading,
Tab,
Grid,
} from '@alifd/next';
import { getParams, request } from '../../../globalLib';
import { generateUrl } from '../../../utils/nacosutil';
import DiffEditorDialog from '../../../components/DiffEditorDialog';
import './index.scss';
import PropTypes from 'prop-types';
const TabPane = Tab.Item;
const FormItem = Form.Item;
const { Row, Col } = Grid;
@ConfigProvider.config
class ConfigDetail extends React.Component {
@ -56,6 +68,7 @@ class ConfigDetail extends React.Component {
this.searchGroup = getParams('searchGroup') || '';
this.pageSize = getParams('pageSize');
this.pageNo = getParams('pageNo');
this.diffEditorDialog = React.createRef();
// this.params = window.location.hash.split('?')[1]||'';
}
@ -188,6 +201,32 @@ class ConfigDetail extends React.Component {
}
}
openDiff() {
let self = this;
const { locale = {} } = this.props;
let leftvalue = this.monacoEditor.getValue();
let url = `v1/cs/history/previous?id=${this.valueMap.normal.id}`;
request({
url,
beforeSend() {
self.openLoading();
},
success(result) {
if (result != null) {
let rightvalue = result.content;
leftvalue = leftvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n');
rightvalue = rightvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n');
self.diffEditorDialog.current.getInstance().openDialog(leftvalue, rightvalue);
} else {
Dialog.alert({ title: locale.error, content: result.message });
}
},
complete() {
self.closeLoading();
},
});
}
render() {
const { locale = {} } = this.props;
const { init } = this.field;
@ -276,12 +315,23 @@ class ConfigDetail extends React.Component {
<FormItem label={locale.configuration} required {...formItemLayout}>
<div style={{ clear: 'both', height: 300 }} id="container" />
</FormItem>
<FormItem label={' '} {...formItemLayout}>
<Button type={'primary'} onClick={this.goList.bind(this)}>
</Form>
<Row>
<Col span="24" className="button-list">
<Button size="large" type="primary" onClick={this.openDiff.bind(this)}>
{locale.versionComparison}
</Button>{' '}
<Button size="large" type="normal" onClick={this.goList.bind(this)}>
{locale.back}
</Button>
</FormItem>
</Form>
</Col>
</Row>
<DiffEditorDialog
ref={this.diffEditorDialog}
title={locale.versionComparison}
currentArea={locale.dialogCurrentArea}
originalArea={locale.dialogOriginalArea}
/>
</Loading>
</div>
);

View File

@ -13,3 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.button-list {
text-align: right;
button {
margin-left: 1em;
font-size: 14px;
}
}

View File

@ -605,6 +605,9 @@ class ConfigEditor extends React.Component {
});
});
}}
title={locale.dialogTitle}
currentArea={locale.dialogCurrentArea}
originalArea={locale.dialogOriginalArea}
/>
<SuccessDialog ref={this.successDialog} />
</Loading>

View File

@ -0,0 +1,428 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs');
const path = require('path');
const chai = require('chai');
const should = chai.should();
const JWebDriver = require('jwebdriver');
chai.use(JWebDriver.chaiSupportChainPromise);
const resemble = require('resemblejs-node');
resemble.outputSettings({
errorType: 'flatDifferenceIntensity',
});
const rootPath = getRootPath();
module.exports = function() {
let driver, testVars;
before(function() {
let self = this;
driver = self.driver;
testVars = self.testVars;
});
it('url: http://localhost:8000', async function() {
await driver.url(_(`http://localhost:8000`));
});
it('waitBody: ', async function() {
await driver
.sleep(500)
.wait('body', 30000)
.html()
.then(function(code) {
isPageError(code).should.be.false;
});
});
it('click: #username, 126, 37, 0', async function() {
await driver
.sleep(300)
.wait('#username', 30000)
.sleep(300)
.mouseMove(126, 37)
.click(0);
});
it('sendKeys: nacos{TAB}nacos{ENTER}', async function() {
await driver.sendKeys('nacos{TAB}nacos{ENTER}');
});
it('click: 详情 ( #root tr.first > td[role="gridcell"].last > div.next-table-cell-wrapper > div > a:nth-child(1), 11, 3, 0 )', async function() {
await driver
.sleep(300)
.wait(
'#root tr.first > td[role="gridcell"].last > div.next-table-cell-wrapper > div > a:nth-child(1)',
30000
)
.sleep(300)
.mouseMove(11, 3)
.click(0);
});
it('click: 版本对比 ( //span[text()="版本对比"], 41, 0, 0 )', async function() {
await driver
.sleep(300)
.wait('//span[text()="版本对比"]', 30000)
.sleep(300)
.mouseMove(41, 0)
.click(0);
});
it('click: i.next-icon-close, 1, 2, 0', async function() {
await driver
.sleep(300)
.wait('i.next-icon-close', 30000)
.sleep(300)
.mouseMove(1, 2)
.click(0);
});
it('click: 返回 ( #root button.next-btn-normal, 36, 9, 0 )', async function() {
await driver
.sleep(300)
.wait('#root button.next-btn-normal', 30000)
.sleep(300)
.mouseMove(36, 9)
.click(0);
});
it('click: 详情 ( #root tr.first > td[role="gridcell"].last > div.next-table-cell-wrapper > div > a:nth-child(1), 10, 2, 0 )', async function() {
await driver
.sleep(300)
.wait(
'#root tr.first > td[role="gridcell"].last > div.next-table-cell-wrapper > div > a:nth-child(1)',
30000
)
.sleep(300)
.mouseMove(10, 2)
.click(0);
});
it('click: 版本对比 ( //span[text()="版本对比"], 20, -1, 0 )', async function() {
await driver
.sleep(300)
.wait('//span[text()="版本对比"]', 30000)
.sleep(300)
.mouseMove(20, -1)
.click(0);
});
it('click: i.next-icon-close, 5, 5, 0', async function() {
await driver
.sleep(300)
.wait('i.next-icon-close', 30000)
.sleep(300)
.mouseMove(5, 5)
.click(0);
});
it('click: En ( //span[text()="En"], 13, 13, 0 )', async function() {
await driver
.sleep(300)
.wait('//span[text()="En"]', 30000)
.sleep(300)
.mouseMove(13, 13)
.click(0);
});
it('click: Version Comparison ( #root button.next-btn-primary > span.next-btn-helper, 67, 4, 0 )', async function() {
await driver
.sleep(300)
.wait('#root button.next-btn-primary > span.next-btn-helper', 30000)
.sleep(300)
.mouseMove(67, 4)
.click(0);
});
it('click: Back ( button.next-medium, 11, 21, 0 )', async function() {
await driver
.sleep(300)
.wait('button.next-medium', 30000)
.sleep(300)
.mouseMove(11, 21)
.click(0);
});
it('click: Version Comparison ( #root button.next-btn-primary > span.next-btn-helper, 128, 0, 0 )', async function() {
await driver
.sleep(300)
.wait('#root button.next-btn-primary > span.next-btn-helper', 30000)
.sleep(300)
.mouseMove(128, 0)
.click(0);
});
it('click: i.next-icon-close, 7, 9, 0', async function() {
await driver
.sleep(300)
.wait('i.next-icon-close', 30000)
.sleep(300)
.mouseMove(7, 9)
.click(0);
});
it('click: Back ( //span[text()="Back"], 11, -1, 0 )', async function() {
await driver
.sleep(300)
.wait('//span[text()="Back"]', 30000)
.sleep(300)
.mouseMove(11, -1)
.click(0);
});
it('click: Details ( #root tr.first > td[role="gridcell"].last > div.next-table-cell-wrapper > div > a:nth-child(1), 25, 8, 0 )', async function() {
await driver
.sleep(300)
.wait(
'#root tr.first > td[role="gridcell"].last > div.next-table-cell-wrapper > div > a:nth-child(1)',
30000
)
.sleep(300)
.mouseMove(25, 8)
.click(0);
});
it('click: Version Comparison ( #root button.next-btn-primary > span.next-btn-helper, 104, 3, 0 )', async function() {
await driver
.sleep(300)
.wait('#root button.next-btn-primary > span.next-btn-helper', 30000)
.sleep(300)
.mouseMove(104, 3)
.click(0);
});
function _(str) {
if (typeof str === 'string') {
return str.replace(/\{\{(.+?)\}\}/g, function(all, key) {
return testVars[key] || '';
});
} else {
return str;
}
}
};
if (module.parent && /mocha\.js/.test(module.parent.id)) {
runThisSpec();
}
function runThisSpec() {
// read config
let webdriver = process.env['webdriver'] || '';
let proxy = process.env['wdproxy'] || '';
let config = require(rootPath + '/config.json');
let webdriverConfig = Object.assign({}, config.webdriver);
let host = webdriverConfig.host;
let port = webdriverConfig.port || 4444;
let group = webdriverConfig.group || 'default';
let match = webdriver.match(/([^\:]+)(?:\:(\d+))?/);
if (match) {
host = match[1] || host;
port = match[2] || port;
}
let testVars = config.vars;
let browsers = webdriverConfig.browsers;
browsers = browsers.replace(/^\s+|\s+$/g, '');
delete webdriverConfig.host;
delete webdriverConfig.port;
delete webdriverConfig.group;
delete webdriverConfig.browsers;
// read hosts
let hostsPath = rootPath + '/hosts';
let hosts = '';
if (fs.existsSync(hostsPath)) {
hosts = fs.readFileSync(hostsPath).toString();
}
let specName = path
.relative(rootPath, __filename)
.replace(/\\/g, '/')
.replace(/\.js$/, '');
browsers.split(/\s*,\s*/).forEach(function(browserName) {
let caseName = specName + ' : ' + browserName;
let browserInfo = browserName.split(' ');
browserName = browserInfo[0];
let browserVersion = browserInfo[1];
describe(caseName, function() {
this.timeout(600000);
this.slow(1000);
let driver;
before(function() {
let self = this;
let driver = new JWebDriver({
host: host,
port: port,
});
let sessionConfig = Object.assign({}, webdriverConfig, {
group: group,
browserName: browserName,
version: browserVersion,
'ie.ensureCleanSession': true,
});
if (proxy) {
sessionConfig.proxy = {
proxyType: 'manual',
httpProxy: proxy,
sslProxy: proxy,
};
} else if (hosts) {
sessionConfig.hosts = hosts;
}
try {
self.driver = driver
.session(sessionConfig)
.windowSize(1024, 768)
.config({
pageloadTimeout: 30000, // page onload timeout
scriptTimeout: 5000, // sync script timeout
asyncScriptTimeout: 10000, // async script timeout
});
} catch (e) {
console.log(e);
}
self.testVars = testVars;
let casePath = path.dirname(caseName);
if (config.reporter && config.reporter.distDir) {
self.screenshotPath = config.reporter.distDir + '/reports/screenshots/' + casePath;
self.diffbasePath = config.reporter.distDir + '/reports/diffbase/' + casePath;
} else {
self.screenshotPath = rootPath + '/reports/screenshots/' + casePath;
self.diffbasePath = rootPath + '/reports/diffbase/' + casePath;
}
self.caseName = caseName.replace(/.*\//g, '').replace(/\s*[:\.\:\-\s]\s*/g, '_');
mkdirs(self.screenshotPath);
mkdirs(self.diffbasePath);
self.stepId = 0;
return self.driver;
});
module.exports();
beforeEach(function() {
let self = this;
self.stepId++;
if (self.skipAll) {
self.skip();
}
});
afterEach(async function() {
let self = this;
let currentTest = self.currentTest;
let title = currentTest.title;
if (
currentTest.state === 'failed' &&
/^(url|waitBody|switchWindow|switchFrame):/.test(title)
) {
self.skipAll = true;
}
if (
(config.screenshots && config.screenshots.captureAll && !/^(closeWindow):/.test(title)) ||
currentTest.state === 'failed'
) {
const casePath = path.dirname(caseName);
const filepath = `${self.screenshotPath}/${self.caseName}_${self.stepId}`;
const relativeFilePath = `./screenshots/${casePath}/${self.caseName}_${self.stepId}`;
let driver = self.driver;
try {
// catch error when get alert msg
await driver.getScreenshot(filepath + '.png');
let url = await driver.url();
let html = await driver.source();
html = '<!--url: ' + url + ' -->\n' + html;
fs.writeFileSync(filepath + '.html', html);
let cookies = await driver.cookies();
fs.writeFileSync(filepath + '.cookie', JSON.stringify(cookies));
appendToContext(self, relativeFilePath + '.png');
} catch (e) {}
}
});
after(function() {
return this.driver.close();
});
});
});
}
function getRootPath() {
let rootPath = path.resolve(__dirname);
while (rootPath) {
if (fs.existsSync(rootPath + '/config.json')) {
break;
}
rootPath = rootPath.substring(0, rootPath.lastIndexOf(path.sep));
}
return rootPath;
}
function mkdirs(dirname) {
if (fs.existsSync(dirname)) {
return true;
} else {
if (mkdirs(path.dirname(dirname))) {
fs.mkdirSync(dirname);
return true;
}
}
}
function callSpec(name) {
try {
require(rootPath + '/' + name)();
} catch (e) {
console.log(e);
process.exit(1);
}
}
function isPageError(code) {
return (
code == '' ||
/ jscontent="errorCode" jstcache="\d+"|diagnoseConnectionAndRefresh|dnserror_unavailable_header|id="reportCertificateErrorRetry"|400 Bad Request|403 Forbidden|404 Not Found|500 Internal Server Error|502 Bad Gateway|503 Service Temporarily Unavailable|504 Gateway Time-out/i.test(
code
)
);
}
function appendToContext(mocha, content) {
try {
const test = mocha.currentTest || mocha.test;
if (!test.context) {
test.context = content;
} else if (Array.isArray(test.context)) {
test.context.push(content);
} else {
test.context = [test.context];
test.context.push(content);
}
} catch (e) {
console.log('error', e);
}
}
function catchError(error) {}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -21,7 +21,7 @@
<parent>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -108,6 +108,10 @@ public class Member implements Comparable<Member>, Cloneable {
extendInfo.put(key, value);
}
public void delExtendVal(String key) {
extendInfo.remove(key);
}
public boolean check() {
return StringUtils.isNoneBlank(ip, address) && port != -1;
}

View File

@ -19,7 +19,6 @@ package com.alibaba.nacos.core.code;
import com.alibaba.nacos.common.executor.ExecutorFactory;
import com.alibaba.nacos.common.executor.NameThreadFactory;
import com.alibaba.nacos.common.executor.ThreadPoolManager;
import com.alibaba.nacos.common.http.HttpClientManager;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.sys.file.WatchFileCenter;
import com.alibaba.nacos.sys.utils.ApplicationUtils;
@ -142,8 +141,6 @@ public class StartingSpringApplicationRunListener implements SpringApplicationRu
logFilePath();
LOGGER.error("Startup errors : {}", exception);
HttpClientManager.shutdown();
ThreadPoolManager.shutdown();
WatchFileCenter.shutdown();
NotifyCenter.shutdown();

View File

@ -17,9 +17,9 @@
package com.alibaba.nacos.core.controller;
import com.alibaba.nacos.common.http.Callback;
import com.alibaba.nacos.common.http.HttpClientManager;
import com.alibaba.nacos.common.http.HttpClientBeanHolder;
import com.alibaba.nacos.common.http.HttpUtils;
import com.alibaba.nacos.common.http.NAsyncHttpClient;
import com.alibaba.nacos.common.http.client.NacosAsyncRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.model.RestResult;
@ -152,7 +152,7 @@ public class NacosClusterController {
public RestResult<String> leave(@RequestBody Collection<String> params) throws Exception {
Collection<Member> memberList = MemberUtils.multiParse(params);
memberManager.memberLeave(memberList);
final NAsyncHttpClient asyncHttpClient = HttpClientManager.getAsyncHttpClient();
final NacosAsyncRestTemplate nacosAsyncRestTemplate = HttpClientBeanHolder.getNacosAsyncRestTemplate(Loggers.CLUSTER);
final GenericType<RestResult<String>> genericType = new GenericType<RestResult<String>>() {
};
final Collection<Member> notifyList = memberManager.allMembersWithoutSelf();
@ -162,7 +162,7 @@ public class NacosClusterController {
final String url = HttpUtils
.buildUrl(false, member.getAddress(), ApplicationUtils.getContextPath(), Commons.NACOS_CORE_CONTEXT,
"/cluster/server/leave");
asyncHttpClient.post(url, Header.EMPTY, Query.EMPTY, params, genericType.getType(), new Callback<String>() {
nacosAsyncRestTemplate.post(url, Header.EMPTY, Query.EMPTY, params, genericType.getType(), new Callback<String>() {
@Override
public void onReceive(RestResult<String> result) {
try {

View File

@ -36,6 +36,7 @@ import org.springframework.context.event.ContextStartedEvent;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
@ -108,6 +109,7 @@ public class ProtocolManager extends MemberChangeListener implements Application
return apProtocol;
}
@PreDestroy
public void destroy() {
if (Objects.nonNull(apProtocol)) {
apProtocol.shutdown();

View File

@ -51,7 +51,7 @@ public class DistroProtocol {
private final DistroConfig distroConfig;
private volatile boolean loadCompleted = false;
private volatile boolean isInitialized = false;
public DistroProtocol(ServerMemberManager memberManager, DistroComponentHolder distroComponentHolder,
DistroTaskEngineHolder distroTaskEngineHolder, DistroConfig distroConfig) {
@ -59,32 +59,41 @@ public class DistroProtocol {
this.distroComponentHolder = distroComponentHolder;
this.distroTaskEngineHolder = distroTaskEngineHolder;
this.distroConfig = distroConfig;
startVerifyTask();
startDistroTask();
}
private void startVerifyTask() {
private void startDistroTask() {
if (ApplicationUtils.getStandaloneMode()) {
isInitialized = true;
return;
}
startVerifyTask();
startLoadTask();
}
private void startLoadTask() {
DistroCallback loadCallback = new DistroCallback() {
@Override
public void onSuccess() {
loadCompleted = true;
isInitialized = true;
}
@Override
public void onFailed(Throwable throwable) {
loadCompleted = false;
isInitialized = false;
}
};
GlobalExecutor.schedulePartitionDataTimedSync(new DistroVerifyTask(memberManager, distroComponentHolder),
distroConfig.getVerifyIntervalMillis());
GlobalExecutor.submitLoadDataTask(
new DistroLoadDataTask(memberManager, distroComponentHolder, distroConfig, loadCallback));
}
public boolean isLoadCompleted() {
return loadCompleted;
private void startVerifyTask() {
GlobalExecutor.schedulePartitionDataTimedSync(new DistroVerifyTask(memberManager, distroComponentHolder),
distroConfig.getVerifyIntervalMillis());
}
public boolean isInitialized() {
return isInitialized;
}
/**

View File

@ -18,6 +18,7 @@ package com.alibaba.nacos.core.distributed.id;
import com.alibaba.nacos.consistency.IdGenerator;
import com.alibaba.nacos.sys.utils.ApplicationUtils;
import com.alibaba.nacos.sys.utils.InetUtils;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -86,7 +87,7 @@ public class SnowFlowerIdGenerator implements IdGenerator {
} else {
InetAddress address;
try {
address = InetAddress.getLocalHost();
address = InetAddress.getByName(InetUtils.getSelfIP());
} catch (final UnknownHostException e) {
throw new IllegalStateException("Cannot get LocalHost InetAddress, please check your network!", e);
}

View File

@ -133,12 +133,14 @@ public class JRaftProtocol extends AbstractConsistencyProtocol<RaftConfig, LogPr
final String leader = event.getLeader();
final Long term = event.getTerm();
final List<String> raftClusterInfo = event.getRaftClusterInfo();
final String errMsg = event.getErrMsg();
// Leader information needs to be selectively updated. If it is valid data,
// the information in the protocol metadata is updated.
MapUtils.putIfValNoEmpty(properties, MetadataKey.LEADER_META_DATA, leader);
MapUtils.putIfValNoNull(properties, MetadataKey.TERM_META_DATA, term);
MapUtils.putIfValNoEmpty(properties, MetadataKey.RAFT_GROUP_MEMBER, raftClusterInfo);
MapUtils.putIfValNoEmpty(properties, MetadataKey.ERR_MSG, errMsg);
value.put(groupId, properties);
metaData.load(value);
@ -198,6 +200,7 @@ public class JRaftProtocol extends AbstractConsistencyProtocol<RaftConfig, LogPr
@Override
public void shutdown() {
if (initialized.get() && shutdowned.compareAndSet(false, true)) {
Loggers.RAFT.info("shutdown jraft server");
raftServer.shutdown();
}
}

View File

@ -497,17 +497,17 @@ public class JRaftServer {
Configuration oldConf = instance.getConfiguration(groupName);
String oldLeader = Optional.ofNullable(instance.selectLeader(groupName)).orElse(PeerId.emptyPeer())
.getEndpoint().toString();
status = instance.refreshConfiguration(this.cliClientService, groupName, rpcRequestTimeoutMs);
if (!status.isOk()) {
Loggers.RAFT
.error("Fail to refresh route configuration for group : {}, status is : {}", groupName, status);
}
// fix issue #3661 https://github.com/alibaba/nacos/issues/3661
status = instance.refreshLeader(this.cliClientService, groupName, rpcRequestTimeoutMs);
if (!status.isOk()) {
Loggers.RAFT
.error("Fail to refresh leader for group : {}, status is : {}", groupName, status);
}
status = instance.refreshConfiguration(this.cliClientService, groupName, rpcRequestTimeoutMs);
if (!status.isOk()) {
Loggers.RAFT
.error("Fail to refresh route configuration for group : {}, status is : {}", groupName, status);
}
} catch (Exception e) {
Loggers.RAFT.error("Fail to refresh raft metadata info for group : {}, error is : {}", groupName, e);
}

View File

@ -225,6 +225,10 @@ class NacosStateMachine extends StateMachineAdapter {
public void onError(RaftException e) {
super.onError(e);
processor.onError(e);
NotifyCenter.publishEvent(
RaftEvent.builder().groupId(groupId).leader(leaderIp).term(term).raftClusterInfo(allPeers())
.errMsg(e.toString())
.build());
}
public boolean isLeader() {

View File

@ -37,6 +37,8 @@ public class RaftEvent extends SlowEvent {
private Long term = null;
private String errMsg = "";
private List<String> raftClusterInfo = Collections.emptyList();
public static RaftEventBuilder builder() {
@ -75,6 +77,14 @@ public class RaftEvent extends SlowEvent {
this.raftClusterInfo = raftClusterInfo;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
@Override
public String toString() {
return "RaftEvent{" + "groupId='" + groupId + '\'' + ", leader='" + leader + '\'' + ", term=" + term
@ -91,6 +101,8 @@ public class RaftEvent extends SlowEvent {
private List<String> raftClusterInfo = Collections.emptyList();
private String errMsg = "";
private RaftEventBuilder() {
}
@ -113,6 +125,11 @@ public class RaftEvent extends SlowEvent {
this.raftClusterInfo = raftClusterInfo;
return this;
}
public RaftEventBuilder errMsg(String errMsg) {
this.errMsg = errMsg;
return this;
}
public RaftEvent build() {
RaftEvent raftEvent = new RaftEvent();
@ -120,6 +137,7 @@ public class RaftEvent extends SlowEvent {
raftEvent.setLeader(leader);
raftEvent.setTerm(term);
raftEvent.setRaftClusterInfo(raftClusterInfo);
raftEvent.setErrMsg(errMsg);
return raftEvent;
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.core.exception;
/**
* Core module code starts with 40001.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public enum ErrorCode {
/**
* unknow error.
*/
UnKnowError(40001),
// kv error
/**
* KVStorage write error.
*/
KVStorageWriteError(40100),
/**
* KVStorage read error.
*/
KVStorageReadError(40101),
/**
* KVStorage delete error.
*/
KVStorageDeleteError(40102),
/**
* KVStorage snapshot save error.
*/
KVStorageSnapshotSaveError(40103),
/**
* KVStorage snapshot load error.
*/
KVStorageSnapshotLoadError(40104),
/**
* KVStorage reset error.
*/
KVStorageResetError(40105),
/**
* KVStorage create error.
*/
KVStorageCreateError(40106),
/**
* KVStorage write error.
*/
KVStorageBatchWriteError(40107),
// disk error
/**
* mkdir error.
*/
IOMakeDirError(40201),
/**
* copy directory has error.
*/
IOCopyDirError(40202),
// consistency protocol error
/**
* protocol write error.
*/
ProtoSubmitError(40301),
/**
* protocol read error.
*/
ProtoReadError(40302);
private final int code;
ErrorCode(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.alibaba.nacos.core.exception;
import com.alibaba.nacos.api.exception.NacosException;
/**
* RocksDB Exception.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public class KvStorageException extends NacosException {
public KvStorageException() {
super();
}
public KvStorageException(ErrorCode code, String errMsg) {
super(code.getCode(), errMsg);
}
public KvStorageException(ErrorCode errCode, Throwable throwable) {
super(errCode.getCode(), throwable);
}
public KvStorageException(ErrorCode errCode, String errMsg, Throwable throwable) {
super(errCode.getCode(), errMsg, throwable);
}
public KvStorageException(int errCode, String errMsg) {
super(errCode, errMsg);
}
public KvStorageException(int errCode, Throwable throwable) {
super(errCode, throwable);
}
public KvStorageException(int errCode, String errMsg, Throwable throwable) {
super(errCode, errMsg, throwable);
}
@Override
public int getErrCode() {
return super.getErrCode();
}
@Override
public String getErrMsg() {
return super.getErrMsg();
}
@Override
public void setErrCode(int errCode) {
super.setErrCode(errCode);
}
@Override
public void setErrMsg(String errMsg) {
super.setErrMsg(errMsg);
}
@Override
public void setCauseThrowable(Throwable throwable) {
super.setCauseThrowable(throwable);
}
@Override
public String toString() {
return super.toString();
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.core.storage;
import com.alibaba.nacos.core.storage.kv.FileKvStorage;
import com.alibaba.nacos.core.storage.kv.KvStorage;
import com.alibaba.nacos.core.storage.kv.MemoryKvStorage;
/**
* Ket-value Storage factory.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public final class StorageFactory {
/**
* Create {@link KvStorage} implementation.
*
* @param type type of {@link KvStorage}
* @param label label for {@code RocksStorage}
* @param baseDir base dir of storage file.
* @return implementation of {@link KvStorage}
* @throws Exception exception during creating {@link KvStorage}
*/
public static KvStorage createKvStorage(KvStorage.KvType type, final String label, final String baseDir)
throws Exception {
switch (type) {
case File:
return new FileKvStorage(baseDir);
case Memory:
return new MemoryKvStorage();
case RocksDB:
default:
throw new IllegalArgumentException("this kv type : [" + type.name() + "] not support");
}
}
}

View File

@ -0,0 +1,195 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.core.storage.kv;
import com.alibaba.nacos.common.utils.ByteUtils;
import com.alibaba.nacos.core.exception.ErrorCode;
import com.alibaba.nacos.core.exception.KvStorageException;
import com.alibaba.nacos.sys.utils.DiskUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Kv storage based on file system. // TODO 写文件的方式需要优化
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public class FileKvStorage implements KvStorage {
private final String baseDir;
/**
* Ensure that a consistent view exists when implementing file copies.
*/
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
public FileKvStorage(String baseDir) throws IOException {
this.baseDir = baseDir;
DiskUtils.forceMkdir(baseDir);
}
@Override
public byte[] get(byte[] key) throws KvStorageException {
readLock.lock();
try {
final String fileName = new String(key);
File file = Paths.get(baseDir, fileName).toFile();
if (file.exists()) {
return DiskUtils.readFileBytes(file);
}
return null;
} finally {
readLock.unlock();
}
}
@Override
public Map<byte[], byte[]> batchGet(List<byte[]> keys) throws KvStorageException {
readLock.lock();
try {
Map<byte[], byte[]> result = new HashMap<>(keys.size());
for (byte[] key : keys) {
byte[] val = get(key);
if (val != null) {
result.put(key, val);
}
}
return result;
} finally {
readLock.unlock();
}
}
@Override
public void put(byte[] key, byte[] value) throws KvStorageException {
readLock.lock();
try {
final String fileName = new String(key);
File file = Paths.get(baseDir, fileName).toFile();
try {
DiskUtils.touch(file);
DiskUtils.writeFile(file, value, false);
} catch (IOException e) {
throw new KvStorageException(ErrorCode.KVStorageWriteError, e);
}
} finally {
readLock.unlock();
}
}
@Override
public void batchPut(List<byte[]> keys, List<byte[]> values) throws KvStorageException {
readLock.lock();
try {
if (keys.size() != values.size()) {
throw new KvStorageException(ErrorCode.KVStorageBatchWriteError,
"key's size must be equal to value's size");
}
int size = keys.size();
for (int i = 0; i < size; i++) {
put(keys.get(i), values.get(i));
}
} finally {
readLock.unlock();
}
}
@Override
public void delete(byte[] key) throws KvStorageException {
readLock.lock();
try {
final String fileName = new String(key);
DiskUtils.deleteFile(baseDir, fileName);
} finally {
readLock.unlock();
}
}
@Override
public void batchDelete(List<byte[]> keys) throws KvStorageException {
readLock.lock();
try {
for (byte[] key : keys) {
delete(key);
}
} finally {
readLock.unlock();
}
}
@Override
public void doSnapshot(String backupPath) throws KvStorageException {
writeLock.lock();
try {
File srcDir = Paths.get(baseDir).toFile();
File descDir = Paths.get(backupPath).toFile();
DiskUtils.copyDirectory(srcDir, descDir);
} catch (IOException e) {
throw new KvStorageException(ErrorCode.IOCopyDirError, e);
} finally {
writeLock.unlock();
}
}
@Override
public void snapshotLoad(String path) throws KvStorageException {
writeLock.lock();
try {
File srcDir = Paths.get(path).toFile();
// If snapshot path is non-exist, means snapshot is empty
if (srcDir.exists()) {
// First clean up the local file information, before the file copy
DiskUtils.deleteDirThenMkdir(baseDir);
File descDir = Paths.get(baseDir).toFile();
DiskUtils.copyDirectory(srcDir, descDir);
}
} catch (IOException e) {
throw new KvStorageException(ErrorCode.IOCopyDirError, e);
} finally {
writeLock.unlock();
}
}
@Override
public List<byte[]> allKeys() throws KvStorageException {
List<byte[]> result = new LinkedList<>();
File[] files = new File(baseDir).listFiles();
if (null != files) {
for (File each : files) {
if (each.isFile()) {
result.add(ByteUtils.toBytes(each.getName()));
}
}
}
return result;
}
@Override
public void shutdown() {
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.core.storage.kv;
import com.alibaba.nacos.core.exception.KvStorageException;
import java.util.List;
import java.util.Map;
/**
* Universal KV storage interface.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public interface KvStorage {
enum KvType {
/**
* Local file storage.
*/
File,
/**
* Local memory storage.
*/
Memory,
/**
* RocksDB storage.
*/
RocksDB,
}
/**
* get data by key.
*
* @param key byte[]
* @return byte[]
* @throws KvStorageException KVStorageException
*/
byte[] get(byte[] key) throws KvStorageException;
/**
* batch get by List byte[].
*
* @param keys List byte[]
* @return Map byte[], byte[]
* @throws KvStorageException RocksStorageException
*/
Map<byte[], byte[]> batchGet(List<byte[]> keys) throws KvStorageException;
/**
* write data.
*
* @param key byte[]
* @param value byte[]
* @throws KvStorageException RocksStorageException
*/
void put(byte[] key, byte[] value) throws KvStorageException;
/**
* batch write.
*
* @param key List byte[]
* @param values List byte[]
* @throws KvStorageException RocksStorageException
*/
void batchPut(List<byte[]> key, List<byte[]> values) throws KvStorageException;
/**
* delete with key.
*
* @param key byte[]
* @throws KvStorageException RocksStorageException
*/
void delete(byte[] key) throws KvStorageException;
/**
* batch delete with keys.
*
* @param key List byte[]
* @throws KvStorageException RocksStorageException
*/
void batchDelete(List<byte[]> key) throws KvStorageException;
/**
* do snapshot.
*
* @param backupPath snapshot file save path
* @throws KvStorageException KVStorageException
*/
void doSnapshot(final String backupPath) throws KvStorageException;
/**
* load snapshot.
*
* @param path The path to the snapshot file
* @throws KvStorageException KVStorageException
*/
void snapshotLoad(String path) throws KvStorageException;
/**
* Get all keys.
*
* @return all keys
* @throws KvStorageException KVStorageException
*/
List<byte[]> allKeys() throws KvStorageException;
/**
* shutdown.
*/
void shutdown();
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.core.storage.kv;
import com.alibaba.nacos.core.exception.ErrorCode;
import com.alibaba.nacos.core.exception.KvStorageException;
import com.alipay.sofa.jraft.util.BytesUtil;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
/**
* Realization of KV storage based on memory.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public class MemoryKvStorage implements KvStorage {
private final Map<Key, byte[]> storage = new ConcurrentSkipListMap<>();
@Override
public byte[] get(byte[] key) throws KvStorageException {
return storage.get(new Key(key));
}
@Override
public Map<byte[], byte[]> batchGet(List<byte[]> keys) throws KvStorageException {
Map<byte[], byte[]> result = new HashMap<>(keys.size());
for (byte[] key : keys) {
byte[] val = storage.get(new Key(key));
if (val != null) {
result.put(key, val);
}
}
return result;
}
@Override
public void put(byte[] key, byte[] value) throws KvStorageException {
storage.put(new Key(key), value);
}
@Override
public void batchPut(List<byte[]> keys, List<byte[]> values) throws KvStorageException {
if (keys.size() != values.size()) {
throw new KvStorageException(ErrorCode.KVStorageBatchWriteError.getCode(),
"key's size must be equal to value's size");
}
int size = keys.size();
for (int i = 0; i < size; i++) {
storage.put(new Key(keys.get(i)), values.get(i));
}
}
@Override
public void delete(byte[] key) throws KvStorageException {
storage.remove(new Key(key));
}
@Override
public void batchDelete(List<byte[]> keys) throws KvStorageException {
for (byte[] key : keys) {
storage.remove(new Key(key));
}
}
@Override
public void doSnapshot(String backupPath) throws KvStorageException {
throw new UnsupportedOperationException();
}
@Override
public void snapshotLoad(String path) throws KvStorageException {
throw new UnsupportedOperationException();
}
@Override
public List<byte[]> allKeys() throws KvStorageException {
List<byte[]> result = new LinkedList<>();
for (Key each : storage.keySet()) {
result.add(each.origin);
}
return result;
}
@Override
public void shutdown() {
storage.clear();
}
private static class Key implements Comparable<Key> {
private final byte[] origin;
private Key(byte[] origin) {
this.origin = origin;
}
@Override
public int compareTo(Key o) {
return BytesUtil.compare(origin, o.origin);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Key key = (Key) o;
return Arrays.equals(origin, key.origin);
}
@Override
public int hashCode() {
return Arrays.hashCode(origin);
}
}
}

View File

@ -17,10 +17,11 @@
package com.alibaba.nacos.core.utils;
import com.alibaba.nacos.common.utils.LoggerUtils;
import com.alibaba.nacos.common.utils.Pair;
import com.alibaba.nacos.common.utils.StringUtils;
import org.slf4j.Logger;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@ -33,47 +34,49 @@ import java.util.function.Supplier;
*/
public class TimerContext {
private static final ThreadLocal<Pair<String, Long>> TIME_RECORD = new ThreadLocal<>();
private static final ThreadLocal<Map<String, Long>> TIME_RECORD = ThreadLocal.withInitial(() -> new HashMap<>(2));
/**
* Record context start time.
*
* @param name context name
*/
public static void start(final String name) {
long startTime = System.currentTimeMillis();
TIME_RECORD.set(Pair.with(name, startTime));
TIME_RECORD.get().put(name, System.currentTimeMillis());
}
public static void end(final Logger logger) {
end(logger, LoggerUtils.DEBUG);
public static void end(final String name, final Logger logger) {
end(name, logger, LoggerUtils.DEBUG);
}
/**
* End the task and print based on the log level.
*
* @param name context name
* @param logger logger
* @param level logger level
*/
public static void end(final Logger logger, final String level) {
long endTime = System.currentTimeMillis();
Pair<String, Long> record = TIME_RECORD.get();
public static void end(final String name, final Logger logger, final String level) {
Map<String, Long> record = TIME_RECORD.get();
long contextTime = System.currentTimeMillis() - record.remove(name);
if (StringUtils.equals(level, LoggerUtils.DEBUG)) {
LoggerUtils.printIfDebugEnabled(logger, "{} cost time : {} ms", record.getFirst(),
(endTime - record.getSecond()));
LoggerUtils.printIfDebugEnabled(logger, "{} cost time : {} ms", name, contextTime);
}
if (StringUtils.equals(level, LoggerUtils.INFO)) {
LoggerUtils.printIfInfoEnabled(logger, "{} cost time : {} ms", record.getFirst(),
(endTime - record.getSecond()));
LoggerUtils.printIfInfoEnabled(logger, "{} cost time : {} ms", name, contextTime);
}
if (StringUtils.equals(level, LoggerUtils.TRACE)) {
LoggerUtils.printIfTraceEnabled(logger, "{} cost time : {} ms", record.getFirst(),
(endTime - record.getSecond()));
LoggerUtils.printIfTraceEnabled(logger, "{} cost time : {} ms", name, contextTime);
}
if (StringUtils.equals(level, LoggerUtils.ERROR)) {
LoggerUtils.printIfErrorEnabled(logger, "{} cost time : {} ms", record.getFirst(),
(endTime - record.getSecond()));
LoggerUtils.printIfErrorEnabled(logger, "{} cost time : {} ms", name, contextTime);
}
if (StringUtils.equals(level, LoggerUtils.WARN)) {
LoggerUtils.printIfWarnEnabled(logger, "{} cost time : {} ms", record.getFirst(),
(endTime - record.getSecond()));
LoggerUtils.printIfWarnEnabled(logger, "{} cost time : {} ms", name, contextTime);
}
if (record.isEmpty()) {
TIME_RECORD.remove();
}
TIME_RECORD.remove();
}
/**
@ -88,7 +91,7 @@ public class TimerContext {
try {
job.run();
} finally {
end(logger);
end(name, logger);
}
}
@ -104,7 +107,7 @@ public class TimerContext {
try {
return job.get();
} finally {
end(logger);
end(name, logger);
}
}
@ -121,7 +124,7 @@ public class TimerContext {
try {
return job.apply(args);
} finally {
end(logger);
end(name, logger);
}
}
@ -138,7 +141,7 @@ public class TimerContext {
try {
job.accept(args);
} finally {
end(logger);
end(name, logger);
}
}

View File

@ -87,6 +87,26 @@ public class WebUtils {
* @return Decoded data
*/
private static String resolveValue(String value, String encoding) {
if (StringUtils.isEmpty(encoding)) {
encoding = StandardCharsets.UTF_8.name();
}
try {
value = new String(value.getBytes(StandardCharsets.UTF_8), encoding);
} catch (UnsupportedEncodingException ignore) {
}
return value.trim();
}
/**
* decode target value with UrlDecode.
*
* <p>Under Content-Type:application/x-www-form-urlencoded situation.
*
* @param value value
* @param encoding encode
* @return Decoded data
*/
private static String resolveValueWithUrlDecode(String value, String encoding) {
if (StringUtils.isEmpty(encoding)) {
encoding = StandardCharsets.UTF_8.name();
}
@ -114,7 +134,7 @@ public class WebUtils {
*
* @param request HttpServletRequest
* @return the value of the request header "user-agent", or the value of the request header "client-version" if the
* request does not have a header of "user-agent".
* request does not have a header of "user-agent".
*/
public static String getUserAgent(HttpServletRequest request) {
String userAgent = request.getHeader(HttpHeaderConsts.USER_AGENT_HEADER);
@ -144,8 +164,8 @@ public class WebUtils {
* Handle file upload operations.
*
* @param multipartFile file
* @param consumer post processor
* @param response {@link DeferredResult}
* @param consumer post processor
* @param response {@link DeferredResult}
*/
public static void onFileUpload(MultipartFile multipartFile, Consumer<File> consumer,
DeferredResult<RestResult<String>> response) {

View File

@ -22,7 +22,7 @@ rem removed the last 5 chars(which means \bin\) to get the base DIR.
set BASE_DIR="%BASE_DIR:~0,-5%"
set DEFAULT_SEARCH_LOCATIONS="classpath:/,classpath:/config/,file:./,file:./config/"
set CUSTOM_SEARCH_LOCATIONS=%DEFAULT_SEARCH_LOCATIONS%,file:%BASE_DIR%/conf/
set CUSTOM_SEARCH_LOCATIONS=file:%BASE_DIR%/conf/,%DEFAULT_SEARCH_LOCATIONS%
set MODE="cluster"
set FUNCTION_MODE="all"
@ -93,4 +93,4 @@ set "NACOS_LOG4J_OPTS=--logging.config=%BASE_DIR%/conf/nacos-logback.xml"
set COMMAND="%JAVA%" %NACOS_JVM_OPTS% %NACOS_OPTS% %NACOS_CONFIG_OPTS% %NACOS_LOG4J_OPTS% nacos.nacos %*
rem start nacos command
%COMMAND%
%COMMAND%

View File

@ -79,7 +79,7 @@ export JAVA_HOME
export JAVA="$JAVA_HOME/bin/java"
export BASE_DIR=`cd $(dirname $0)/..; pwd`
export DEFAULT_SEARCH_LOCATIONS="classpath:/,classpath:/config/,file:./,file:./config/"
export CUSTOM_SEARCH_LOCATIONS=${DEFAULT_SEARCH_LOCATIONS},file:${BASE_DIR}/conf/
export CUSTOM_SEARCH_LOCATIONS=file:${BASE_DIR}/conf/,${DEFAULT_SEARCH_LOCATIONS}
#===========================================================================================
# JVM Configuration

View File

@ -21,7 +21,7 @@
<parent>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -21,7 +21,7 @@
<parent>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -19,7 +19,7 @@
<parent>
<artifactId>nacos-all</artifactId>
<groupId>com.alibaba.nacos</groupId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -21,7 +21,7 @@
<parent>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-all</artifactId>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -20,7 +20,6 @@ import com.alibaba.nacos.naming.consistency.ConsistencyService;
import com.alibaba.nacos.naming.misc.GlobalExecutor;
import com.alibaba.nacos.naming.misc.SwitchDomain;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@ -38,11 +37,14 @@ public class ServerStatusManager {
@Resource(name = "consistencyDelegate")
private ConsistencyService consistencyService;
@Autowired
private SwitchDomain switchDomain;
private final SwitchDomain switchDomain;
private ServerStatus serverStatus = ServerStatus.STARTING;
public ServerStatusManager(SwitchDomain switchDomain) {
this.switchDomain = switchDomain;
}
@PostConstruct
public void init() {
GlobalExecutor.registerServerStatusUpdater(new ServerStatusUpdater());

View File

@ -18,6 +18,7 @@ package com.alibaba.nacos.naming.consistency;
import com.alibaba.nacos.naming.pojo.Record;
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicLong;
/**
@ -25,11 +26,28 @@ import java.util.concurrent.atomic.AtomicLong;
*
* @author nacos
*/
public class Datum<T extends Record> {
public class Datum<T extends Record> implements Serializable {
private static final long serialVersionUID = -2525482315889753720L;
public String key;
public T value;
public AtomicLong timestamp = new AtomicLong(0L);
/**
* Create datum.
*
* @param key key of datum
* @param value value of datum
* @param <T> Types of value
* @return new datum
*/
public static <T extends Record> Datum createDatum(final String key, final T value) {
Datum datum = new Datum();
datum.key = key;
datum.value = value;
return datum;
}
}

View File

@ -18,7 +18,7 @@ package com.alibaba.nacos.naming.consistency;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.naming.consistency.ephemeral.EphemeralConsistencyService;
import com.alibaba.nacos.naming.consistency.persistent.PersistentConsistencyService;
import com.alibaba.nacos.naming.consistency.persistent.PersistentConsistencyServiceDelegateImpl;
import com.alibaba.nacos.naming.pojo.Record;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
@ -33,11 +33,11 @@ import org.springframework.stereotype.Service;
@Service("consistencyDelegate")
public class DelegateConsistencyServiceImpl implements ConsistencyService {
private final PersistentConsistencyService persistentConsistencyService;
private final PersistentConsistencyServiceDelegateImpl persistentConsistencyService;
private final EphemeralConsistencyService ephemeralConsistencyService;
public DelegateConsistencyServiceImpl(PersistentConsistencyService persistentConsistencyService,
public DelegateConsistencyServiceImpl(PersistentConsistencyServiceDelegateImpl persistentConsistencyService,
EphemeralConsistencyService ephemeralConsistencyService) {
this.persistentConsistencyService = persistentConsistencyService;
this.ephemeralConsistencyService = ephemeralConsistencyService;

View File

@ -19,6 +19,8 @@ package com.alibaba.nacos.naming.consistency;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import org.apache.commons.lang3.StringUtils;
import static com.alibaba.nacos.naming.misc.UtilsAndCommons.RAFT_CACHE_FILE_PREFIX;
/**
* Key operations for data.
*
@ -81,8 +83,7 @@ public class KeyBuilder {
}
public static boolean matchSwitchKey(String key) {
return key.endsWith(UtilsAndCommons.SWITCH_DOMAIN_NAME) || key
.endsWith(UtilsAndCommons.SWITCH_DOMAIN_NAME + UtilsAndCommons.RAFT_CACHE_FILE_SUFFIX);
return key.endsWith(UtilsAndCommons.SWITCH_DOMAIN_NAME);
}
public static boolean matchServiceName(String key, String namespaceId, String serviceName) {
@ -141,4 +142,8 @@ public class KeyBuilder {
public static String getServiceName(String key) {
return key.split(NAMESPACE_KEY_CONNECTOR)[1];
}
public static boolean isDatumCacheFile(String key) {
return key.startsWith(RAFT_CACHE_FILE_PREFIX);
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.consistency;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.consistency.DataOperation;
import com.alibaba.nacos.naming.pojo.Record;
/**
* The value changes events. //TODO Recipients need to implement the ability to receive batch events
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public class ValueChangeEvent extends Event {
private final String key;
private final Record value;
private final DataOperation action;
public ValueChangeEvent(String key, Record value, DataOperation action) {
this.key = key;
this.value = value;
this.action = action;
}
public String getKey() {
return key;
}
public Record getValue() {
return value;
}
public DataOperation getAction() {
return action;
}
public static ValueChangeEventBuilder builder() {
return new ValueChangeEventBuilder();
}
public static final class ValueChangeEventBuilder {
private String key;
private Record value;
private DataOperation action;
private ValueChangeEventBuilder() {
}
public ValueChangeEventBuilder key(String key) {
this.key = key;
return this;
}
public ValueChangeEventBuilder value(Record value) {
this.value = value;
return this;
}
public ValueChangeEventBuilder action(DataOperation action) {
this.action = action;
return this;
}
public ValueChangeEvent build() {
return new ValueChangeEvent(key, value, action);
}
}
}

View File

@ -351,7 +351,7 @@ public class DistroConsistencyServiceImpl implements EphemeralConsistencyService
}
public boolean isInitialized() {
return distroProtocol.isLoadCompleted() || !globalConfig.isDataWarmup();
return distroProtocol.isInitialized() || !globalConfig.isDataWarmup();
}
public class Notifier implements Runnable {

View File

@ -0,0 +1,115 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.consistency.persistent;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.common.utils.VersionUtils;
import com.alibaba.nacos.core.cluster.Member;
import com.alibaba.nacos.core.cluster.MemberMetaDataConstants;
import com.alibaba.nacos.core.cluster.ServerMemberManager;
import com.alibaba.nacos.naming.misc.GlobalExecutor;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* An automated task that determines whether all nodes in the current cluster meet the requirements of a particular
* version.
*
* <p>This will be removed in a future release, just to smooth the transition.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
@Component
public class ClusterVersionJudgement {
private volatile boolean allMemberIsNewVersion = false;
private final ServerMemberManager memberManager;
private final List<ConsumerWithPriority> observers = new CopyOnWriteArrayList<>();
public ClusterVersionJudgement(ServerMemberManager memberManager) {
this.memberManager = memberManager;
GlobalExecutor.submitClusterVersionJudge(this::runVersionListener, TimeUnit.SECONDS.toMillis(5));
}
/**
* register member version watcher.
*
* @param observer Listens for the latest version of all current nodes
* @param priority The higher the priority, the first to be notified
*/
public void registerObserver(Consumer<Boolean> observer, int priority) {
observers.add(new ConsumerWithPriority(observer, priority));
}
protected void runVersionListener() {
try {
judge();
} finally {
GlobalExecutor.submitClusterVersionJudge(this::runVersionListener, TimeUnit.SECONDS.toMillis(5));
}
}
protected void judge() {
Collection<Member> members = memberManager.allMembers();
final String oldVersion = "1.4.0";
boolean allMemberIsNewVersion = true;
for (Member member : members) {
final String curV = (String) member.getExtendVal(MemberMetaDataConstants.VERSION);
if (StringUtils.isBlank(curV) || VersionUtils.compareVersion(oldVersion, curV) > 0) {
allMemberIsNewVersion = false;
}
}
// can only trigger once
if (allMemberIsNewVersion && !this.allMemberIsNewVersion) {
this.allMemberIsNewVersion = true;
Collections.sort(observers);
for (ConsumerWithPriority consumer : observers) {
consumer.consumer.accept(true);
}
observers.clear();
}
}
public boolean allMemberIsNewVersion() {
return allMemberIsNewVersion;
}
private static class ConsumerWithPriority implements Comparable<ConsumerWithPriority> {
private final Consumer<Boolean> consumer;
private final int priority;
public ConsumerWithPriority(Consumer<Boolean> consumer, int priority) {
this.consumer = consumer;
this.priority = priority;
}
@Override
public int compareTo(ConsumerWithPriority o) {
return o.priority - this.priority;
}
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.consistency.persistent;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.naming.consistency.Datum;
import com.alibaba.nacos.naming.consistency.RecordListener;
import com.alibaba.nacos.naming.consistency.persistent.impl.PersistentServiceProcessor;
import com.alibaba.nacos.naming.consistency.persistent.raft.RaftConsistencyServiceImpl;
import com.alibaba.nacos.naming.pojo.Record;
import org.springframework.stereotype.Component;
/**
* Persistent consistency service delegate.
*
* @author xiweng.yy
*/
@Component("persistentConsistencyServiceDelegate")
public class PersistentConsistencyServiceDelegateImpl implements PersistentConsistencyService {
private final ClusterVersionJudgement versionJudgement;
private final RaftConsistencyServiceImpl oldPersistentConsistencyService;
private final PersistentServiceProcessor newPersistentConsistencyService;
private volatile boolean switchNewPersistentService = false;
public PersistentConsistencyServiceDelegateImpl(ClusterVersionJudgement versionJudgement,
RaftConsistencyServiceImpl oldPersistentConsistencyService,
PersistentServiceProcessor newPersistentConsistencyService) {
this.versionJudgement = versionJudgement;
this.oldPersistentConsistencyService = oldPersistentConsistencyService;
this.newPersistentConsistencyService = newPersistentConsistencyService;
init();
}
private void init() {
this.versionJudgement.registerObserver(isAllNewVersion -> switchNewPersistentService = isAllNewVersion, -1);
}
@Override
public void put(String key, Record value) throws NacosException {
switchOne().put(key, value);
}
@Override
public void remove(String key) throws NacosException {
switchOne().remove(key);
}
@Override
public Datum get(String key) throws NacosException {
return switchOne().get(key);
}
@Override
public void listen(String key, RecordListener listener) throws NacosException {
oldPersistentConsistencyService.listen(key, listener);
newPersistentConsistencyService.listen(key, listener);
}
@Override
public void unListen(String key, RecordListener listener) throws NacosException {
newPersistentConsistencyService.unListen(key, listener);
oldPersistentConsistencyService.unListen(key, listener);
}
@Override
public boolean isAvailable() {
return switchOne().isAvailable();
}
private PersistentConsistencyService switchOne() {
return switchNewPersistentService ? newPersistentConsistencyService : oldPersistentConsistencyService;
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.consistency.persistent;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.alibaba.nacos.common.utils.ConcurrentHashSet;
import com.alibaba.nacos.consistency.DataOperation;
import com.alibaba.nacos.naming.consistency.KeyBuilder;
import com.alibaba.nacos.naming.consistency.RecordListener;
import com.alibaba.nacos.naming.consistency.ValueChangeEvent;
import com.alibaba.nacos.naming.misc.Loggers;
import com.alibaba.nacos.naming.pojo.Record;
import org.jboss.netty.util.internal.ConcurrentHashMap;
import java.util.Map;
import java.util.function.Function;
/**
* persistent notifier, It is responsible for notifying interested listeners of all write changes to the data.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public final class PersistentNotifier extends Subscriber<ValueChangeEvent> {
private final Map<String, ConcurrentHashSet<RecordListener>> listenerMap = new ConcurrentHashMap<>(32);
private final Function<String, Record> find;
public PersistentNotifier(Function<String, Record> find) {
this.find = find;
}
/**
* register listener with key.
*
* @param key key
* @param listener {@link RecordListener}
*/
public void registerListener(final String key, final RecordListener listener) {
listenerMap.computeIfAbsent(key, s -> new ConcurrentHashSet<>());
listenerMap.get(key).add(listener);
}
/**
* deregister listener by key.
*
* @param key key
* @param listener {@link RecordListener}
*/
public void deregisterListener(final String key, final RecordListener listener) {
if (!listenerMap.containsKey(key)) {
return;
}
listenerMap.get(key).remove(listener);
}
/**
* deregister all listener by key.
*
* @param key key
*/
public void deregisterAllListener(final String key) {
listenerMap.remove(key);
}
public Map<String, ConcurrentHashSet<RecordListener>> getListeners() {
return listenerMap;
}
/**
* notify value to listener with {@link DataOperation} and key.
*
* @param key key
* @param action {@link DataOperation}
* @param value value
* @param <T> type
*/
public <T extends Record> void notify(final String key, final DataOperation action, final T value) {
if (listenerMap.containsKey(KeyBuilder.SERVICE_META_KEY_PREFIX)) {
if (KeyBuilder.matchServiceMetaKey(key) && !KeyBuilder.matchSwitchKey(key)) {
for (RecordListener listener : listenerMap.get(KeyBuilder.SERVICE_META_KEY_PREFIX)) {
try {
if (action == DataOperation.CHANGE) {
listener.onChange(key, value);
}
if (action == DataOperation.DELETE) {
listener.onDelete(key);
}
} catch (Throwable e) {
Loggers.RAFT.error("[NACOS-RAFT] error while notifying listener of key: {}", key, e);
}
}
}
}
if (!listenerMap.containsKey(key)) {
return;
}
for (RecordListener listener : listenerMap.get(key)) {
try {
if (action == DataOperation.CHANGE) {
listener.onChange(key, value);
continue;
}
if (action == DataOperation.DELETE) {
listener.onDelete(key);
}
} catch (Throwable e) {
Loggers.RAFT.error("[NACOS-RAFT] error while notifying listener of key: {}", key, e);
}
}
}
@Override
public void onEvent(ValueChangeEvent event) {
notify(event.getKey(), event.getAction(), find.apply(event.getKey()));
}
@Override
public Class<? extends Event> subscribeType() {
return ValueChangeEvent.class;
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.consistency.persistent.impl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Batch read response.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public class BatchReadResponse implements Serializable {
private static final long serialVersionUID = 5639813122389207205L;
private List<byte[]> keys = new ArrayList<>(16);
private List<byte[]> values = new ArrayList<>(16);
public List<byte[]> getKeys() {
return keys;
}
public void setKeys(List<byte[]> keys) {
this.keys = keys;
}
public List<byte[]> getValues() {
return values;
}
public void setValues(List<byte[]> values) {
this.values = values;
}
public void append(byte[] key, byte[] value) {
keys.add(key);
values.add(value);
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.consistency.persistent.impl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* batch write request.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public class BatchWriteRequest implements Serializable {
private static final long serialVersionUID = 5620748357962129879L;
private List<byte[]> keys = new ArrayList<>(16);
private List<byte[]> values = new ArrayList<>(16);
public List<byte[]> getKeys() {
return keys;
}
public void setKeys(List<byte[]> keys) {
this.keys = keys;
}
public List<byte[]> getValues() {
return values;
}
public void setValues(List<byte[]> values) {
this.values = values;
}
public void append(byte[] key, byte[] value) {
keys.add(key);
values.add(value);
}
}

View File

@ -0,0 +1,226 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.consistency.persistent.impl;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.core.exception.ErrorCode;
import com.alibaba.nacos.core.exception.KvStorageException;
import com.alibaba.nacos.core.storage.StorageFactory;
import com.alibaba.nacos.core.storage.kv.KvStorage;
import com.alibaba.nacos.core.storage.kv.MemoryKvStorage;
import com.alibaba.nacos.core.utils.TimerContext;
import com.alibaba.nacos.naming.consistency.KeyBuilder;
import com.alibaba.nacos.naming.misc.Loggers;
import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* Kv storage implementation for naming.
*
* @author xiweng.yy
*/
public class NamingKvStorage extends MemoryKvStorage {
private static final String LOAD_SNAPSHOT = NamingKvStorage.class.getSimpleName() + ".snapshotLoad";
private final String baseDir;
private final KvStorage baseDirStorage;
private final Map<String, KvStorage> namespaceKvStorage;
public NamingKvStorage(final String baseDir) throws Exception {
this.baseDir = baseDir;
this.baseDirStorage = StorageFactory.createKvStorage(KvStorage.KvType.File, "naming-persistent", baseDir);
this.namespaceKvStorage = new ConcurrentHashMap<>(16);
}
@Override
public byte[] get(byte[] key) throws KvStorageException {
// First get the data from the memory Cache
byte[] result = super.get(key);
if (null == result) {
try {
KvStorage storage = createActualStorageIfAbsent(key);
result = null == storage ? null : storage.get(key);
if (null != result) {
super.put(key, result);
}
} catch (Exception e) {
throw new KvStorageException(ErrorCode.KVStorageWriteError.getCode(),
"Get data failed, key: " + new String(key), e);
}
}
return result;
}
@Override
public Map<byte[], byte[]> batchGet(List<byte[]> keys) throws KvStorageException {
Map<byte[], byte[]> result = new HashMap<>(keys.size());
for (byte[] key : keys) {
byte[] val = get(key);
if (val != null) {
result.put(key, val);
}
}
return result;
}
@Override
public void put(byte[] key, byte[] value) throws KvStorageException {
try {
KvStorage storage = createActualStorageIfAbsent(key);
storage.put(key, value);
} catch (Exception e) {
throw new KvStorageException(ErrorCode.KVStorageWriteError.getCode(),
"Put data failed, key: " + new String(key), e);
}
// after actual storage put success, put it in memory, memory put should success all the time
super.put(key, value);
}
@Override
public void batchPut(List<byte[]> keys, List<byte[]> values) throws KvStorageException {
if (keys.size() != values.size()) {
throw new KvStorageException(ErrorCode.KVStorageBatchWriteError,
"key's size must be equal to value's size");
}
int size = keys.size();
for (int i = 0; i < size; i++) {
put(keys.get(i), values.get(i));
}
}
@Override
public void delete(byte[] key) throws KvStorageException {
try {
KvStorage storage = createActualStorageIfAbsent(key);
if (null != storage) {
storage.delete(key);
}
} catch (Exception e) {
throw new KvStorageException(ErrorCode.KVStorageDeleteError.getCode(),
"Delete data failed, key: " + new String(key), e);
}
// after actual storage delete success, put it in memory, memory delete should success all the time
super.delete(key);
}
@Override
public void batchDelete(List<byte[]> key) throws KvStorageException {
for (byte[] each : key) {
delete(each);
}
}
@Override
public void doSnapshot(String backupPath) throws KvStorageException {
baseDirStorage.doSnapshot(backupPath);
}
@Override
public void snapshotLoad(String path) throws KvStorageException {
TimerContext.start(LOAD_SNAPSHOT);
try {
baseDirStorage.snapshotLoad(path);
loadSnapshotFromActualStorage(baseDirStorage);
loadNamespaceSnapshot();
} finally {
TimerContext.end(LOAD_SNAPSHOT, Loggers.RAFT);
}
}
private void loadSnapshotFromActualStorage(KvStorage actualStorage) throws KvStorageException {
for (byte[] each : actualStorage.allKeys()) {
byte[] datum = actualStorage.get(each);
super.put(each, datum);
}
}
private void loadNamespaceSnapshot() {
for (String each : getAllNamespaceDirs()) {
try {
KvStorage kvStorage = createActualStorageIfAbsent(each);
loadSnapshotFromActualStorage(kvStorage);
} catch (Exception e) {
Loggers.RAFT.error("load snapshot for namespace {} failed", each, e);
}
}
}
private List<String> getAllNamespaceDirs() {
File[] files = new File(baseDir).listFiles();
List<String> result = Collections.emptyList();
if (null != files) {
result = new ArrayList<>(files.length);
for (File each : files) {
if (each.isDirectory()) {
result.add(each.getName());
}
}
}
return Collections.unmodifiableList(result);
}
@Override
public List<byte[]> allKeys() throws KvStorageException {
return super.allKeys();
}
@Override
public void shutdown() {
baseDirStorage.shutdown();
for (KvStorage each : namespaceKvStorage.values()) {
each.shutdown();
}
namespaceKvStorage.clear();
super.shutdown();
}
private KvStorage createActualStorageIfAbsent(byte[] key) throws Exception {
String keyString = new String(key);
String namespace = KeyBuilder.getNamespace(keyString);
return createActualStorageIfAbsent(namespace);
}
private KvStorage createActualStorageIfAbsent(String namespace) throws Exception {
if (StringUtils.isBlank(namespace)) {
return baseDirStorage;
}
Function<String, KvStorage> kvStorageBuilder = key -> {
try {
String namespacePath = Paths.get(baseDir, key).toString();
return StorageFactory.createKvStorage(KvType.File, "naming-persistent", namespacePath);
} catch (Exception e) {
throw new NacosRuntimeException(NacosException.SERVER_ERROR, e);
}
};
namespaceKvStorage.computeIfAbsent(namespace, kvStorageBuilder);
return namespaceKvStorage.get(namespace);
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.consistency.persistent.impl;
import com.alibaba.nacos.common.utils.Objects;
import com.alibaba.nacos.consistency.snapshot.LocalFileMeta;
import com.alibaba.nacos.consistency.snapshot.Reader;
import com.alibaba.nacos.consistency.snapshot.SnapshotOperation;
import com.alibaba.nacos.consistency.snapshot.Writer;
import com.alibaba.nacos.core.distributed.raft.utils.RaftExecutor;
import com.alibaba.nacos.core.storage.kv.KvStorage;
import com.alibaba.nacos.core.utils.TimerContext;
import com.alibaba.nacos.naming.misc.Loggers;
import com.alibaba.nacos.sys.utils.DiskUtils;
import com.alipay.sofa.jraft.util.CRC64;
import java.nio.file.Paths;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiConsumer;
import java.util.zip.Checksum;
/**
* Snapshot processing of persistent service data for accelerated Raft protocol recovery and data synchronization.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
public class NamingSnapshotOperation implements SnapshotOperation {
private static final String NAMING_SNAPSHOT_SAVE = NamingSnapshotOperation.class.getSimpleName() + ".SAVE";
private static final String NAMING_SNAPSHOT_LOAD = NamingSnapshotOperation.class.getSimpleName() + ".LOAD";
private final String snapshotDir = "naming_persistent";
private final String snapshotArchive = "naming_persistent.zip";
private final String checkSumKey = "checkSum";
private final KvStorage storage;
private final ReentrantReadWriteLock.WriteLock writeLock;
public NamingSnapshotOperation(KvStorage storage, ReentrantReadWriteLock lock) {
this.storage = storage;
this.writeLock = lock.writeLock();
}
@Override
public void onSnapshotSave(Writer writer, BiConsumer<Boolean, Throwable> callFinally) {
RaftExecutor.doSnapshot(() -> {
TimerContext.start(NAMING_SNAPSHOT_SAVE);
final Lock lock = writeLock;
lock.lock();
try {
final String writePath = writer.getPath();
final String parentPath = Paths.get(writePath, snapshotDir).toString();
DiskUtils.deleteDirectory(parentPath);
DiskUtils.forceMkdir(parentPath);
storage.doSnapshot(parentPath);
final String outputFile = Paths.get(writePath, snapshotArchive).toString();
final Checksum checksum = new CRC64();
DiskUtils.compress(writePath, snapshotDir, outputFile, checksum);
DiskUtils.deleteDirectory(parentPath);
final LocalFileMeta meta = new LocalFileMeta();
meta.append(checkSumKey, Long.toHexString(checksum.getValue()));
callFinally.accept(writer.addFile(snapshotArchive, meta), null);
} catch (Throwable t) {
Loggers.RAFT.error("Fail to compress snapshot, path={}, file list={}, {}.", writer.getPath(),
writer.listFiles(), t);
callFinally.accept(false, t);
} finally {
lock.unlock();
TimerContext.end(NAMING_SNAPSHOT_SAVE, Loggers.RAFT);
}
});
}
@Override
public boolean onSnapshotLoad(Reader reader) {
final String readerPath = reader.getPath();
final String sourceFile = Paths.get(readerPath, snapshotArchive).toString();
TimerContext.start(NAMING_SNAPSHOT_LOAD);
final Lock lock = writeLock;
lock.lock();
try {
final Checksum checksum = new CRC64();
DiskUtils.decompress(sourceFile, readerPath, checksum);
LocalFileMeta fileMeta = reader.getFileMeta(snapshotArchive);
if (fileMeta.getFileMeta().containsKey(checkSumKey)) {
if (!Objects.equals(Long.toHexString(checksum.getValue()), fileMeta.get(checkSumKey))) {
throw new IllegalArgumentException("Snapshot checksum failed");
}
}
final String loadPath = Paths.get(readerPath, snapshotDir).toString();
storage.snapshotLoad(loadPath);
Loggers.RAFT.info("snapshot load from : {}", loadPath);
DiskUtils.deleteDirectory(loadPath);
return true;
} catch (final Throwable t) {
Loggers.RAFT.error("Fail to load snapshot, path={}, file list={}, {}.",
Paths.get(readerPath, snapshotDir).toString(), reader.listFiles(), t);
return false;
} finally {
lock.unlock();
TimerContext.end(NAMING_SNAPSHOT_LOAD, Loggers.RAFT);
}
}
}

View File

@ -0,0 +1,366 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.naming.consistency.persistent.impl;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.utils.ByteUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.consistency.DataOperation;
import com.alibaba.nacos.consistency.SerializeFactory;
import com.alibaba.nacos.consistency.Serializer;
import com.alibaba.nacos.consistency.cp.CPProtocol;
import com.alibaba.nacos.consistency.cp.LogProcessor4CP;
import com.alibaba.nacos.consistency.cp.MetadataKey;
import com.alibaba.nacos.consistency.entity.GetRequest;
import com.alibaba.nacos.consistency.entity.Log;
import com.alibaba.nacos.consistency.entity.Response;
import com.alibaba.nacos.consistency.snapshot.SnapshotOperation;
import com.alibaba.nacos.core.distributed.ProtocolManager;
import com.alibaba.nacos.core.exception.ErrorCode;
import com.alibaba.nacos.core.exception.KvStorageException;
import com.alibaba.nacos.core.storage.kv.KvStorage;
import com.alibaba.nacos.naming.consistency.Datum;
import com.alibaba.nacos.naming.consistency.KeyBuilder;
import com.alibaba.nacos.naming.consistency.RecordListener;
import com.alibaba.nacos.naming.consistency.ValueChangeEvent;
import com.alibaba.nacos.naming.consistency.persistent.ClusterVersionJudgement;
import com.alibaba.nacos.naming.consistency.persistent.PersistentConsistencyService;
import com.alibaba.nacos.naming.consistency.persistent.PersistentNotifier;
import com.alibaba.nacos.naming.misc.Loggers;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import com.alibaba.nacos.naming.pojo.Record;
import com.alibaba.nacos.naming.utils.Constants;
import com.alibaba.nacos.sys.utils.ApplicationUtils;
import com.google.protobuf.ByteString;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.springframework.stereotype.Service;
import java.lang.reflect.Type;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* New service data persistence handler.
*
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule")
@Service
public class PersistentServiceProcessor extends LogProcessor4CP implements PersistentConsistencyService {
enum Op {
/**
* write ops.
*/
Write("Write"),
/**
* read ops.
*/
Read("Read"),
/**
* delete ops.
*/
Delete("Delete");
private final String desc;
Op(String desc) {
this.desc = desc;
}
}
private final CPProtocol protocol;
private final KvStorage kvStorage;
private final ClusterVersionJudgement versionJudgement;
private final Serializer serializer;
/**
* During snapshot processing, the processing of other requests needs to be paused.
*/
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
private final PersistentNotifier notifier;
/**
* Is there a leader node currently.
*/
private volatile boolean hasLeader = false;
/**
* Whether an unrecoverable error occurred.
*/
private volatile boolean hasError = false;
/**
* If use old raft, should not notify listener even new listener add.
*/
private volatile boolean startNotify = false;
public PersistentServiceProcessor(final ProtocolManager protocolManager,
final ClusterVersionJudgement versionJudgement) throws Exception {
this.protocol = protocolManager.getCpProtocol();
this.versionJudgement = versionJudgement;
this.kvStorage = new NamingKvStorage(Paths.get(UtilsAndCommons.DATA_BASE_DIR, "data").toString());
this.serializer = SerializeFactory.getSerializer("JSON");
this.notifier = new PersistentNotifier(key -> {
try {
byte[] data = kvStorage.get(ByteUtils.toBytes(key));
Datum datum = serializer.deserialize(data, getDatumTypeFromKey(key));
return null != datum ? datum.value : null;
} catch (KvStorageException ex) {
throw new NacosRuntimeException(ex.getErrCode(), ex.getErrMsg());
}
});
init();
}
@SuppressWarnings("unchecked")
private void init() {
NotifyCenter.registerToPublisher(ValueChangeEvent.class, 16384);
this.protocol.addLogProcessors(Collections.singletonList(this));
this.protocol.protocolMetaData()
.subscribe(Constants.NAMING_PERSISTENT_SERVICE_GROUP, MetadataKey.LEADER_META_DATA,
(o, arg) -> hasLeader = StringUtils.isNotBlank(String.valueOf(arg)));
// If you choose to use the new RAFT protocol directly, there will be no compatible logical execution
if (ApplicationUtils.getProperty(Constants.NACOS_NAMING_USE_NEW_RAFT_FIRST, Boolean.class, false)) {
NotifyCenter.registerSubscriber(notifier);
waitLeader();
startNotify = true;
} else {
this.versionJudgement.registerObserver(isNewVersion -> {
if (isNewVersion) {
NotifyCenter.registerSubscriber(notifier);
startNotify = true;
}
}, 10);
}
}
private void waitLeader() {
while (!hasLeader && !hasError) {
Loggers.RAFT.info("Waiting Jraft leader vote ...");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException ignored) {
}
}
}
@Override
public Response onRequest(GetRequest request) {
final List<byte[]> keys = serializer
.deserialize(request.getData().toByteArray(), TypeUtils.parameterize(List.class, byte[].class));
final Lock lock = readLock;
lock.lock();
try {
final Map<byte[], byte[]> result = kvStorage.batchGet(keys);
final BatchReadResponse response = new BatchReadResponse();
result.forEach(response::append);
return Response.newBuilder().setSuccess(true).setData(ByteString.copyFrom(serializer.serialize(response)))
.build();
} catch (KvStorageException e) {
return Response.newBuilder().setSuccess(false).setErrMsg(e.getErrMsg()).build();
} finally {
lock.unlock();
}
}
@Override
public Response onApply(Log log) {
final byte[] data = log.getData().toByteArray();
final BatchWriteRequest request = serializer.deserialize(data, BatchWriteRequest.class);
final Op op = Op.valueOf(log.getOperation());
final Lock lock = readLock;
lock.lock();
try {
switch (op) {
case Write:
kvStorage.batchPut(request.getKeys(), request.getValues());
break;
case Delete:
kvStorage.batchDelete(request.getKeys());
break;
default:
return Response.newBuilder().setSuccess(false).setErrMsg("unsupport operation : " + op).build();
}
publishValueChangeEvent(op, request);
return Response.newBuilder().setSuccess(true).build();
} catch (KvStorageException e) {
return Response.newBuilder().setSuccess(false).setErrMsg(e.getErrMsg()).build();
} finally {
lock.unlock();
}
}
private void publishValueChangeEvent(final Op op, final BatchWriteRequest request) {
final List<byte[]> keys = request.getKeys();
final List<byte[]> values = request.getValues();
for (int i = 0; i < keys.size(); i++) {
final String key = new String(keys.get(i));
final Datum datum = serializer.deserialize(values.get(i), getDatumTypeFromKey(key));
final Record value = null != datum ? datum.value : null;
final ValueChangeEvent event = ValueChangeEvent.builder().key(key).value(value)
.action(Op.Delete.equals(op) ? DataOperation.DELETE : DataOperation.CHANGE).build();
NotifyCenter.publishEvent(event);
}
}
@Override
public String group() {
return Constants.NAMING_PERSISTENT_SERVICE_GROUP;
}
@Override
public List<SnapshotOperation> loadSnapshotOperate() {
return Collections.singletonList(new NamingSnapshotOperation(this.kvStorage, lock));
}
@Override
public void put(String key, Record value) throws NacosException {
final BatchWriteRequest req = new BatchWriteRequest();
Datum datum = Datum.createDatum(key, value);
req.append(ByteUtils.toBytes(key), serializer.serialize(datum));
final Log log = Log.newBuilder().setData(ByteString.copyFrom(serializer.serialize(req)))
.setGroup(Constants.NAMING_PERSISTENT_SERVICE_GROUP).setOperation(Op.Write.desc).build();
try {
protocol.submit(log);
} catch (Exception e) {
throw new NacosException(ErrorCode.ProtoSubmitError.getCode(), e.getMessage());
}
}
@Override
public void remove(String key) throws NacosException {
final BatchWriteRequest req = new BatchWriteRequest();
req.append(ByteUtils.toBytes(key), ByteUtils.EMPTY);
final Log log = Log.newBuilder().setData(ByteString.copyFrom(serializer.serialize(req)))
.setGroup(Constants.NAMING_PERSISTENT_SERVICE_GROUP).setOperation(Op.Delete.desc).build();
try {
protocol.submit(log);
} catch (Exception e) {
throw new NacosException(ErrorCode.ProtoSubmitError.getCode(), e.getMessage());
}
}
@Override
public Datum get(String key) throws NacosException {
final List<byte[]> keys = new ArrayList<>(1);
keys.add(ByteUtils.toBytes(key));
final GetRequest req = GetRequest.newBuilder().setGroup(Constants.NAMING_PERSISTENT_SERVICE_GROUP)
.setData(ByteString.copyFrom(serializer.serialize(keys))).build();
try {
Response resp = protocol.getData(req);
if (resp.getSuccess()) {
BatchReadResponse response = serializer
.deserialize(resp.getData().toByteArray(), BatchReadResponse.class);
final List<byte[]> rValues = response.getValues();
return rValues.isEmpty() ? null : serializer.deserialize(rValues.get(0), getDatumTypeFromKey(key));
}
throw new NacosException(ErrorCode.ProtoReadError.getCode(), resp.getErrMsg());
} catch (Throwable e) {
throw new NacosException(ErrorCode.ProtoReadError.getCode(), e.getMessage());
}
}
@Override
public void listen(String key, RecordListener listener) throws NacosException {
notifier.registerListener(key, listener);
if (startNotify) {
notifierDatumIfAbsent(key, listener);
}
}
@Override
public void unListen(String key, RecordListener listener) throws NacosException {
notifier.deregisterListener(key, listener);
}
@Override
public void onError(Throwable error) {
super.onError(error);
hasError = true;
}
@Override
public boolean isAvailable() {
return hasLeader && !hasError;
}
private Type getDatumTypeFromKey(String key) {
return TypeUtils.parameterize(Datum.class, getClassOfRecordFromKey(key));
}
private Class<? extends Record> getClassOfRecordFromKey(String key) {
if (KeyBuilder.matchSwitchKey(key)) {
return com.alibaba.nacos.naming.misc.SwitchDomain.class;
} else if (KeyBuilder.matchServiceMetaKey(key)) {
return com.alibaba.nacos.naming.core.Service.class;
} else if (KeyBuilder.matchInstanceListKey(key)) {
return com.alibaba.nacos.naming.core.Instances.class;
}
return Record.class;
}
private void notifierDatumIfAbsent(String key, RecordListener listener) throws NacosException {
if (KeyBuilder.SERVICE_META_KEY_PREFIX.equals(key)) {
notifierAllServiceMeta(listener);
} else {
Datum datum = get(key);
if (null != datum) {
notifierDatum(key, datum, listener);
}
}
}
/**
* This notify should only notify once during startup. See {@link com.alibaba.nacos.naming.core.ServiceManager#init()}
*/
private void notifierAllServiceMeta(RecordListener listener) throws NacosException {
for (byte[] each : kvStorage.allKeys()) {
String key = new String(each);
if (listener.interests(key)) {
Datum datum = get(key);
if (null != datum) {
notifierDatum(key, datum, listener);
}
}
}
}
private void notifierDatum(String key, Datum datum, RecordListener listener) {
try {
listener.onChange(key, datum.value);
} catch (Exception e) {
Loggers.RAFT.error("NACOS-RAFT failed to notify listener", e);
}
}
}

View File

@ -21,9 +21,11 @@ import org.springframework.context.ApplicationEvent;
/**
* Base raft event.
*
* @deprecated will remove in 1.4.x
* @author pbting
* @date 2019-07-01 8:46 PM
*/
@Deprecated
public abstract class BaseRaftEvent extends ApplicationEvent {
private final RaftPeer raftPeer;

View File

@ -19,9 +19,11 @@ package com.alibaba.nacos.naming.consistency.persistent.raft;
/**
* Leader election finished event.
*
* @deprecated will remove in 1.4.x
* @author pbting
* @date 2019-07-01 8:25 PM
*/
@Deprecated
public class LeaderElectFinishedEvent extends BaseRaftEvent {
public LeaderElectFinishedEvent(Object source, RaftPeer raftPeer, RaftPeer local) {

View File

@ -19,9 +19,11 @@ package com.alibaba.nacos.naming.consistency.persistent.raft;
/**
* Make leader event.
*
* @deprecated will remove in 1.4.x
* @author pbting
* @date 2019-07-01 8:45 PM
*/
@Deprecated
public class MakeLeaderEvent extends BaseRaftEvent {
public MakeLeaderEvent(Object source, RaftPeer raftPeer, RaftPeer local) {

View File

@ -17,39 +17,70 @@
package com.alibaba.nacos.naming.consistency.persistent.raft;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
import com.alibaba.nacos.naming.cluster.ServerStatus;
import com.alibaba.nacos.naming.consistency.Datum;
import com.alibaba.nacos.naming.consistency.KeyBuilder;
import com.alibaba.nacos.naming.consistency.RecordListener;
import com.alibaba.nacos.naming.consistency.persistent.ClusterVersionJudgement;
import com.alibaba.nacos.naming.consistency.persistent.PersistentConsistencyService;
import com.alibaba.nacos.naming.misc.Loggers;
import com.alibaba.nacos.naming.misc.SwitchDomain;
import com.alibaba.nacos.naming.pojo.Record;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.nacos.naming.utils.Constants;
import com.alibaba.nacos.sys.utils.ApplicationUtils;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
/**
* Use simplified Raft protocol to maintain the consistency status of Nacos cluster.
*
* @author nkorange
* @since 1.0.0
* @deprecated will remove in 1.4.x
*/
@Deprecated
@DependsOn("ProtocolManager")
@Service
public class RaftConsistencyServiceImpl implements PersistentConsistencyService {
@Autowired
private RaftCore raftCore;
private final RaftCore raftCore;
@Autowired
private RaftPeerSet peers;
private final RaftPeerSet peers;
@Autowired
private SwitchDomain switchDomain;
private final SwitchDomain switchDomain;
private volatile boolean stopWork = false;
public RaftConsistencyServiceImpl(ClusterVersionJudgement versionJudgement, RaftCore raftCore,
SwitchDomain switchDomain) {
this.raftCore = raftCore;
this.peers = raftCore.getPeerSet();
this.switchDomain = switchDomain;
versionJudgement.registerObserver(isAllNewVersion -> {
stopWork = isAllNewVersion;
if (stopWork) {
try {
this.raftCore.shutdown();
} catch (NacosException e) {
throw new NacosRuntimeException(NacosException.SERVER_ERROR, e);
}
}
}, 1000);
}
@PostConstruct
protected void init() throws Exception {
if (ApplicationUtils.getProperty(Constants.NACOS_NAMING_USE_NEW_RAFT_FIRST, Boolean.class, false)) {
this.raftCore.shutdown();
}
}
@Override
public void put(String key, Record value) throws NacosException {
checkIsStopWork();
try {
raftCore.signalPublish(key, value);
} catch (Exception e) {
@ -61,16 +92,14 @@ public class RaftConsistencyServiceImpl implements PersistentConsistencyService
@Override
public void remove(String key) throws NacosException {
checkIsStopWork();
try {
if (KeyBuilder.matchInstanceListKey(key) && !raftCore.isLeader()) {
Datum datum = new Datum();
datum.key = key;
raftCore.onDelete(datum.key, peers.getLeader());
raftCore.unlistenAll(key);
return;
raftCore.onDelete(key, peers.getLeader());
} else {
raftCore.signalDelete(key);
}
raftCore.signalDelete(key);
raftCore.unlistenAll(key);
raftCore.unListenAll(key);
} catch (Exception e) {
Loggers.RAFT.error("Raft remove failed.", e);
throw new NacosException(NacosException.SERVER_ERROR, "Raft remove failed, key:" + key, e);
@ -79,6 +108,7 @@ public class RaftConsistencyServiceImpl implements PersistentConsistencyService
@Override
public Datum get(String key) throws NacosException {
checkIsStopWork();
return raftCore.getDatum(key);
}
@ -130,4 +160,10 @@ public class RaftConsistencyServiceImpl implements PersistentConsistencyService
"Raft onRemove failed, datum:" + datum + ", source: " + source, e);
}
}
private void checkIsStopWork() {
if (stopWork) {
throw new IllegalStateException("old raft protocol already stop work");
}
}
}

View File

@ -16,18 +16,22 @@
package com.alibaba.nacos.naming.consistency.persistent.raft;
import com.alibaba.nacos.common.executor.ExecutorFactory;
import com.alibaba.nacos.common.executor.NameThreadFactory;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
import com.alibaba.nacos.common.http.Callback;
import com.alibaba.nacos.common.lifecycle.Closeable;
import com.alibaba.nacos.common.model.RestResult;
import com.alibaba.nacos.common.notify.EventPublisher;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.utils.ConcurrentHashSet;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.sys.utils.ApplicationUtils;
import com.alibaba.nacos.consistency.DataOperation;
import com.alibaba.nacos.core.utils.ClassUtils;
import com.alibaba.nacos.naming.NamingApp;
import com.alibaba.nacos.naming.consistency.Datum;
import com.alibaba.nacos.naming.consistency.KeyBuilder;
import com.alibaba.nacos.naming.consistency.RecordListener;
import com.alibaba.nacos.naming.consistency.ValueChangeEvent;
import com.alibaba.nacos.naming.consistency.persistent.ClusterVersionJudgement;
import com.alibaba.nacos.naming.consistency.persistent.PersistentNotifier;
import com.alibaba.nacos.naming.core.Instances;
import com.alibaba.nacos.naming.core.Service;
import com.alibaba.nacos.naming.misc.GlobalConfig;
@ -39,14 +43,13 @@ import com.alibaba.nacos.naming.misc.SwitchDomain;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import com.alibaba.nacos.naming.monitor.MetricsMonitor;
import com.alibaba.nacos.naming.pojo.Record;
import com.alibaba.nacos.sys.utils.ApplicationUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.javatuples.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
@ -63,13 +66,10 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -79,10 +79,12 @@ import java.util.zip.GZIPOutputStream;
* Raft core code.
*
* @author nacos
* @deprecated will remove in 1.4.x
*/
@Deprecated
@DependsOn("ProtocolManager")
@Component
public class RaftCore {
public class RaftCore implements Closeable {
public static final String API_VOTE = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/vote";
@ -100,10 +102,6 @@ public class RaftCore {
public static final String API_GET_PEER = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/peer";
private final ScheduledExecutorService executor = ExecutorFactory.Managed
.newSingleScheduledExecutorService(ClassUtils.getCanonicalName(NamingApp.class),
new NameThreadFactory("com.alibaba.nacos.naming.raft.notifier"));
public static final Lock OPERATE_LOCK = new ReentrantLock();
public static final int PUBLISH_TERM_INCREASE_COUNT = 100;
@ -112,25 +110,42 @@ public class RaftCore {
private volatile ConcurrentMap<String, Datum> datums = new ConcurrentHashMap<>();
@Autowired
private RaftPeerSet peers;
@Autowired
private SwitchDomain switchDomain;
private final SwitchDomain switchDomain;
@Autowired
private GlobalConfig globalConfig;
private final GlobalConfig globalConfig;
@Autowired
private RaftProxy raftProxy;
private final RaftProxy raftProxy;
@Autowired
private RaftStore raftStore;
private final RaftStore raftStore;
public volatile Notifier notifier = new Notifier();
private final ClusterVersionJudgement versionJudgement;
public final PersistentNotifier notifier;
private final EventPublisher publisher;
private boolean initialized = false;
private volatile boolean stopWork = false;
private ScheduledFuture masterTask = null;
private ScheduledFuture heartbeatTask = null;
public RaftCore(RaftPeerSet peers, SwitchDomain switchDomain, GlobalConfig globalConfig, RaftProxy raftProxy,
RaftStore raftStore, ClusterVersionJudgement versionJudgement) {
this.peers = peers;
this.switchDomain = switchDomain;
this.globalConfig = globalConfig;
this.raftProxy = raftProxy;
this.raftStore = raftStore;
this.versionJudgement = versionJudgement;
this.notifier = new PersistentNotifier(key -> null == getDatum(key) ? null : getDatum(key).value);
this.publisher = NotifyCenter.registerToPublisher(ValueChangeEvent.class, 16384);
}
/**
* Init raft core.
*
@ -138,11 +153,7 @@ public class RaftCore {
*/
@PostConstruct
public void init() throws Exception {
Loggers.RAFT.info("initializing Raft sub-system");
executor.submit(notifier);
final long start = System.currentTimeMillis();
raftStore.loadDatums(notifier, datums);
@ -151,26 +162,32 @@ public class RaftCore {
Loggers.RAFT.info("cache loaded, datum count: {}, current term: {}", datums.size(), peers.getTerm());
while (true) {
if (notifier.tasks.size() <= 0) {
break;
}
Thread.sleep(1000L);
}
initialized = true;
Loggers.RAFT.info("finish to load data from disk, cost: {} ms.", (System.currentTimeMillis() - start));
GlobalExecutor.registerMasterElection(new MasterElection());
GlobalExecutor.registerHeartbeat(new HeartBeat());
masterTask = GlobalExecutor.registerMasterElection(new MasterElection());
heartbeatTask = GlobalExecutor.registerHeartbeat(new HeartBeat());
versionJudgement.registerObserver(isAllNewVersion -> {
stopWork = isAllNewVersion;
if (stopWork) {
try {
shutdown();
} catch (NacosException e) {
throw new NacosRuntimeException(NacosException.SERVER_ERROR, e);
}
}
}, 100);
NotifyCenter.registerSubscriber(notifier);
Loggers.RAFT.info("timer started: leader timeout ms: {}, heart-beat timeout ms: {}",
GlobalExecutor.LEADER_TIMEOUT_MS, GlobalExecutor.HEARTBEAT_INTERVAL_MS);
}
public Map<String, List<RecordListener>> getListeners() {
return listeners;
public Map<String, ConcurrentHashSet<RecordListener>> getListeners() {
return notifier.getListeners();
}
/**
@ -181,7 +198,9 @@ public class RaftCore {
* @throws Exception any exception during publish
*/
public void signalPublish(String key, Record value) throws Exception {
if (stopWork) {
throw new IllegalStateException("old raft protocol already stop work");
}
if (!isLeader()) {
ObjectNode params = JacksonUtils.createEmptyJsonNode();
params.put("key", key);
@ -195,8 +214,8 @@ public class RaftCore {
return;
}
OPERATE_LOCK.lock();
try {
OPERATE_LOCK.lock();
final long start = System.currentTimeMillis();
final Datum datum = new Datum();
datum.key = key;
@ -267,7 +286,9 @@ public class RaftCore {
* @throws Exception any exception during delete
*/
public void signalDelete(final String key) throws Exception {
if (stopWork) {
throw new IllegalStateException("old raft protocol already stop work");
}
OPERATE_LOCK.lock();
try {
@ -328,6 +349,9 @@ public class RaftCore {
* @throws Exception any exception during publish
*/
public void onPublish(Datum datum, RaftPeer source) throws Exception {
if (stopWork) {
throw new IllegalStateException("old raft protocol already stop work");
}
RaftPeer local = peers.local();
if (datum.value == null) {
Loggers.RAFT.warn("received empty datum");
@ -369,9 +393,7 @@ public class RaftCore {
}
}
raftStore.updateTerm(local.term.get());
notifier.addTask(datum.key, DataOperation.CHANGE);
NotifyCenter.publishEvent(ValueChangeEvent.builder().key(datum.key).action(DataOperation.CHANGE).build());
Loggers.RAFT.info("data added/updated, key={}, term={}", datum.key, local.term);
}
@ -383,7 +405,9 @@ public class RaftCore {
* @throws Exception any exception during delete
*/
public void onDelete(String datumKey, RaftPeer source) throws Exception {
if (stopWork) {
throw new IllegalStateException("old raft protocol already stop work");
}
RaftPeer local = peers.local();
if (!peers.isLeader(source.ip)) {
@ -423,12 +447,30 @@ public class RaftCore {
}
@Override
public void shutdown() throws NacosException {
this.stopWork = true;
this.raftStore.shutdown();
this.peers.shutdown();
Loggers.RAFT.warn("start to close old raft protocol!!!");
Loggers.RAFT.warn("stop old raft protocol task for notifier");
NotifyCenter.deregisterSubscriber(notifier);
Loggers.RAFT.warn("stop old raft protocol task for master task");
masterTask.cancel(true);
Loggers.RAFT.warn("stop old raft protocol task for heartbeat task");
heartbeatTask.cancel(true);
Loggers.RAFT.warn("clean old cache datum for old raft");
datums.clear();
}
public class MasterElection implements Runnable {
@Override
public void run() {
try {
if (stopWork) {
return;
}
if (!peers.isReady()) {
return;
}
@ -508,6 +550,9 @@ public class RaftCore {
* @return self-peer information
*/
public synchronized RaftPeer receivedVote(RaftPeer remote) {
if (stopWork) {
throw new IllegalStateException("old raft protocol already stop work");
}
if (!peers.contains(remote)) {
throw new IllegalStateException("can not find peer: " + remote.ip);
}
@ -540,7 +585,9 @@ public class RaftCore {
@Override
public void run() {
try {
if (stopWork) {
return;
}
if (!peers.isReady()) {
return;
}
@ -578,7 +625,7 @@ public class RaftCore {
ArrayNode array = JacksonUtils.createEmptyArrayNode();
if (switchDomain.isSendBeatOnly()) {
Loggers.RAFT.info("[SEND-BEAT-ONLY] {}", String.valueOf(switchDomain.isSendBeatOnly()));
Loggers.RAFT.info("[SEND-BEAT-ONLY] {}", switchDomain.isSendBeatOnly());
}
if (!switchDomain.isSendBeatOnly()) {
@ -667,6 +714,9 @@ public class RaftCore {
* @throws Exception any exception during handle
*/
public RaftPeer receivedBeat(JsonNode beat) throws Exception {
if (stopWork) {
throw new IllegalStateException("old raft protocol already stop work");
}
final RaftPeer local = peers.local();
final RaftPeer remote = new RaftPeer();
JsonNode peer = beat.get("peer");
@ -767,8 +817,10 @@ public class RaftCore {
processedCount, beatDatums.size(), datums.size());
// update datum entry
String url = buildUrl(remote.ip, API_GET) + "?keys=" + URLEncoder.encode(keys, "UTF-8");
HttpClient.asyncHttpGet(url, null, null, new Callback<String>() {
String url = buildUrl(remote.ip, API_GET);
Map<String, String> queryParam = new HashMap<>(1);
queryParam.put("keys", URLEncoder.encode(keys, "UTF-8"));
HttpClient.asyncHttpGet(url, null, queryParam, new Callback<String>() {
@Override
public void onReceive(RestResult<String> result) {
if (!result.ok()) {
@ -821,7 +873,7 @@ public class RaftCore {
raftStore.write(newDatum);
datums.put(newDatum.key, newDatum);
notifier.addTask(newDatum.key, DataOperation.CHANGE);
notifier.notify(newDatum.key, DataOperation.CHANGE, newDatum.value);
local.resetLeaderDue();
@ -900,21 +952,9 @@ public class RaftCore {
* @param listener new listener
*/
public void listen(String key, RecordListener listener) {
List<RecordListener> listenerList = listeners.get(key);
if (listenerList != null && listenerList.contains(listener)) {
return;
}
if (listenerList == null) {
listenerList = new CopyOnWriteArrayList<>();
listeners.put(key, listenerList);
}
notifier.registerListener(key, listener);
Loggers.RAFT.info("add listener: {}", key);
listenerList.add(listener);
// if data present, notify immediately
for (Datum datum : datums.values()) {
if (!listener.interests(datum.key)) {
@ -936,22 +976,11 @@ public class RaftCore {
* @param listener listener
*/
public void unListen(String key, RecordListener listener) {
if (!listeners.containsKey(key)) {
return;
}
for (RecordListener dl : listeners.get(key)) {
// TODO maybe use equal:
if (dl == listener) {
listeners.get(key).remove(listener);
break;
}
}
notifier.deregisterListener(key, listener);
}
public void unlistenAll(String key) {
listeners.remove(key);
public void unListenAll(String key) {
notifier.deregisterAllListener(key);
}
public void setTerm(long term) {
@ -1006,7 +1035,7 @@ public class RaftCore {
public void addDatum(Datum datum) {
datums.put(datum.key, datum);
notifier.addTask(datum.key, DataOperation.CHANGE);
NotifyCenter.publishEvent(ValueChangeEvent.builder().key(datum.key).action(DataOperation.CHANGE).build());
}
/**
@ -1035,7 +1064,9 @@ public class RaftCore {
raftStore.delete(deleted);
Loggers.RAFT.info("datum deleted, key: {}", key);
}
notifier.addTask(URLDecoder.decode(key, "UTF-8"), DataOperation.DELETE);
NotifyCenter.publishEvent(
ValueChangeEvent.builder().key(URLDecoder.decode(key, "UTF-8")).action(DataOperation.DELETE)
.build());
} catch (UnsupportedEncodingException e) {
Loggers.RAFT.warn("datum key decode failed: {}", key);
}
@ -1045,115 +1076,9 @@ public class RaftCore {
return initialized || !globalConfig.isDataWarmup();
}
@Deprecated
public int getNotifyTaskCount() {
return notifier.getTaskSize();
return (int) publisher.currentEventSize();
}
public class Notifier implements Runnable {
private ConcurrentHashMap<String, String> services = new ConcurrentHashMap<>(10 * 1024);
private BlockingQueue<Pair> tasks = new LinkedBlockingQueue<>(1024 * 1024);
/**
* Add notify task.
*
* @param datumKey datum key
* @param action action of datum
*/
public void addTask(String datumKey, DataOperation action) {
if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
return;
}
if (action == DataOperation.CHANGE) {
services.put(datumKey, StringUtils.EMPTY);
}
Loggers.RAFT.info("add task {}", datumKey);
tasks.add(Pair.with(datumKey, action));
}
public int getTaskSize() {
return tasks.size();
}
@Override
public void run() {
Loggers.RAFT.info("raft notifier started");
while (true) {
try {
Pair pair = tasks.take();
if (pair == null) {
continue;
}
String datumKey = (String) pair.getValue0();
DataOperation action = (DataOperation) pair.getValue1();
services.remove(datumKey);
Loggers.RAFT.info("remove task {}", datumKey);
int count = 0;
if (listeners.containsKey(KeyBuilder.SERVICE_META_KEY_PREFIX)) {
if (KeyBuilder.matchServiceMetaKey(datumKey) && !KeyBuilder.matchSwitchKey(datumKey)) {
for (RecordListener listener : listeners.get(KeyBuilder.SERVICE_META_KEY_PREFIX)) {
try {
if (action == DataOperation.CHANGE) {
listener.onChange(datumKey, getDatum(datumKey).value);
}
if (action == DataOperation.DELETE) {
listener.onDelete(datumKey);
}
} catch (Throwable e) {
Loggers.RAFT
.error("[NACOS-RAFT] error while notifying listener of key: {}", datumKey,
e);
}
}
}
}
if (!listeners.containsKey(datumKey)) {
continue;
}
for (RecordListener listener : listeners.get(datumKey)) {
count++;
try {
if (action == DataOperation.CHANGE) {
listener.onChange(datumKey, getDatum(datumKey).value);
continue;
}
if (action == DataOperation.DELETE) {
listener.onDelete(datumKey);
continue;
}
} catch (Throwable e) {
Loggers.RAFT.error("[NACOS-RAFT] error while notifying listener of key: {}", datumKey, e);
}
}
if (Loggers.RAFT.isDebugEnabled()) {
Loggers.RAFT.debug("[NACOS-RAFT] datum change notified, key: {}, listener count: {}", datumKey,
count);
}
} catch (Throwable e) {
Loggers.RAFT.error("[NACOS-RAFT] Error while handling notifying task", e);
}
}
}
}
}

View File

@ -19,7 +19,8 @@ package com.alibaba.nacos.naming.consistency.persistent.raft;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.core.cluster.Member;
import com.alibaba.nacos.core.cluster.ServerMemberManager;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.nacos.naming.consistency.persistent.ClusterVersionJudgement;
import com.alibaba.nacos.naming.misc.Loggers;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.stereotype.Component;
@ -30,25 +31,47 @@ import java.util.Map;
/**
* Inject the raft information from the naming module into the outlier information of the node.
*
* @deprecated will remove in 1.4.x
* @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
*/
@Deprecated
@Component
public class RaftListener implements SmartApplicationListener {
private static final String GROUP = "naming";
@Autowired
private ServerMemberManager memberManager;
private final ServerMemberManager memberManager;
private final ClusterVersionJudgement versionJudgement;
private volatile boolean stopUpdate = false;
public RaftListener(ServerMemberManager memberManager, ClusterVersionJudgement versionJudgement) {
this.memberManager = memberManager;
this.versionJudgement = versionJudgement;
this.init();
}
private void init() {
this.versionJudgement.registerObserver(isAllNewVersion -> {
stopUpdate = isAllNewVersion;
if (stopUpdate) {
Loggers.RAFT.warn("start to move old raft protocol metadata");
Member self = memberManager.getSelf();
self.delExtendVal(GROUP);
memberManager.update(self);
}
}, -2);
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
boolean a = BaseRaftEvent.class.isAssignableFrom(eventType);
return a;
return BaseRaftEvent.class.isAssignableFrom(eventType);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof BaseRaftEvent) {
if (event instanceof BaseRaftEvent && !stopUpdate) {
BaseRaftEvent raftEvent = (BaseRaftEvent) event;
RaftPeer local = raftEvent.getLocal();
String json = JacksonUtils.toJson(local);

View File

@ -26,8 +26,10 @@ import java.util.concurrent.atomic.AtomicLong;
/**
* Raft peer.
*
* @deprecated will remove in 1.4.x
* @author nacos
*/
@Deprecated
public class RaftPeer {
public String ip;

Some files were not shown because too many files have changed in this diff Show More