diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/ability/RemoteAbilityInitializer.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/ability/RemoteAbilityInitializer.java new file mode 100644 index 00000000..b26db0a4 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/ability/RemoteAbilityInitializer.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2021 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.ability; + +import com.alibaba.nacos.api.ability.ServerAbilities; + +/** + * Server ability initializer for remote. + * + * @author xiweng.yy + */ +public class RemoteAbilityInitializer implements ServerAbilityInitializer { + + @Override + public void initialize(ServerAbilities abilities) { + abilities.getRemoteAbility().setSupportRemoteConnection(true); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/ability/ServerAbilityInitializer.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/ability/ServerAbilityInitializer.java new file mode 100644 index 00000000..368d9e44 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/ability/ServerAbilityInitializer.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2021 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.ability; + +import com.alibaba.nacos.api.ability.ServerAbilities; +import com.alibaba.nacos.api.ability.initializer.AbilityInitializer; + +/** + * Nacos server ability initializer. + * + * @author xiweng.yy + */ +public interface ServerAbilityInitializer extends AbilityInitializer { + + /** + * Initialize server abilities content. + * + * @param abilities server abilities + */ + @Override + void initialize(ServerAbilities abilities); +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/ability/ServerAbilityInitializerHolder.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/ability/ServerAbilityInitializerHolder.java new file mode 100644 index 00000000..1893ab3d --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/ability/ServerAbilityInitializerHolder.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2021 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.ability; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.core.utils.Loggers; + +import java.util.Collection; +import java.util.HashSet; + +/** + * Nacos server ability initializer holder. + * + * @author xiweng.yy + */ +public class ServerAbilityInitializerHolder { + + private static final ServerAbilityInitializerHolder INSTANCE = new ServerAbilityInitializerHolder(); + + private final Collection initializers; + + private ServerAbilityInitializerHolder() { + initializers = new HashSet<>(); + for (ServerAbilityInitializer each : NacosServiceLoader.load(ServerAbilityInitializer.class)) { + Loggers.CORE.info("Load {} for ServerAbilityInitializer", each.getClass().getCanonicalName()); + initializers.add(each); + } + } + + public static ServerAbilityInitializerHolder getInstance() { + return INSTANCE; + } + + public Collection getInitializers() { + return initializers; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/auth/AuthConfig.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/auth/AuthConfig.java new file mode 100644 index 00000000..db43e8a8 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/auth/AuthConfig.java @@ -0,0 +1,48 @@ +/* + * 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.auth; + +import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * auth filter config. + * + * @author mai.jh + */ +@Configuration +public class AuthConfig { + + @Bean + public FilterRegistrationBean authFilterRegistration(AuthFilter authFilter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(authFilter); + registration.addUrlPatterns("/*"); + registration.setName("authFilter"); + registration.setOrder(6); + + return registration; + } + + @Bean + public AuthFilter authFilter(AuthConfigs authConfigs, ControllerMethodsCache methodsCache) { + return new AuthFilter(authConfigs, methodsCache); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java new file mode 100644 index 00000000..92853e00 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java @@ -0,0 +1,164 @@ +/* + * 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.auth; + +import com.alibaba.nacos.auth.HttpProtocolAuthService; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.common.utils.ExceptionUtil; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.core.utils.WebUtils; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.api.Permission; +import com.alibaba.nacos.plugin.auth.api.Resource; +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import com.alibaba.nacos.sys.env.Constants; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Unified filter to handle authentication and authorization. + * + * @author nkorange + * @since 1.2.0 + */ +public class AuthFilter implements Filter { + + private final AuthConfigs authConfigs; + + private final ControllerMethodsCache methodsCache; + + private final HttpProtocolAuthService protocolAuthService; + + public AuthFilter(AuthConfigs authConfigs, ControllerMethodsCache methodsCache) { + this.authConfigs = authConfigs; + this.methodsCache = methodsCache; + this.protocolAuthService = new HttpProtocolAuthService(authConfigs); + this.protocolAuthService.initialize(); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + if (!authConfigs.isAuthEnabled()) { + chain.doFilter(request, response); + return; + } + + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + if (authConfigs.isEnableUserAgentAuthWhite()) { + String userAgent = WebUtils.getUserAgent(req); + if (StringUtils.startsWith(userAgent, Constants.NACOS_SERVER_HEADER)) { + chain.doFilter(request, response); + return; + } + } else if (StringUtils.isNotBlank(authConfigs.getServerIdentityKey()) && StringUtils + .isNotBlank(authConfigs.getServerIdentityValue())) { + String serverIdentity = req.getHeader(authConfigs.getServerIdentityKey()); + if (StringUtils.isNotBlank(serverIdentity)) { + if (authConfigs.getServerIdentityValue().equals(serverIdentity)) { + chain.doFilter(request, response); + return; + } + Loggers.AUTH.warn("Invalid server identity value for {} from {}", authConfigs.getServerIdentityKey(), + req.getRemoteHost()); + } + } else { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, + "Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`"); + return; + } + + try { + + Method method = methodsCache.getMethod(req); + + if (method == null) { + chain.doFilter(request, response); + return; + } + + if (method.isAnnotationPresent(Secured.class) && authConfigs.isAuthEnabled()) { + + if (Loggers.AUTH.isDebugEnabled()) { + Loggers.AUTH.debug("auth start, request: {} {}", req.getMethod(), req.getRequestURI()); + } + + Secured secured = method.getAnnotation(Secured.class); + if (!protocolAuthService.enableAuth(secured)) { + chain.doFilter(request, response); + return; + } + Resource resource = protocolAuthService.parseResource(req, secured); + IdentityContext identityContext = protocolAuthService.parseIdentity(req); + boolean result = protocolAuthService.validateIdentity(identityContext, resource); + if (!result) { + // TODO Get reason of failure + throw new AccessException("Validate Identity failed."); + } + injectIdentityId(req, identityContext); + String action = secured.action().toString(); + result = protocolAuthService.validateAuthority(identityContext, new Permission(resource, action)); + if (!result) { + // TODO Get reason of failure + throw new AccessException("Validate Authority failed."); + } + } + chain.doFilter(request, response); + } catch (AccessException e) { + if (Loggers.AUTH.isDebugEnabled()) { + Loggers.AUTH.debug("access denied, request: {} {}, reason: {}", req.getMethod(), req.getRequestURI(), + e.getErrMsg()); + } + resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getErrMsg()); + } catch (IllegalArgumentException e) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, ExceptionUtil.getAllExceptionMsg(e)); + } catch (Exception e) { + Loggers.AUTH.warn("[AUTH-FILTER] Server failed: ", e); + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Server failed, " + e.getMessage()); + } + } + + /** + * Set identity id to request session, make sure some actual logic can get identity information. + * + *

May be replaced with whole identityContext. + * + * @param request http request + * @param identityContext identity context + */ + private void injectIdentityId(HttpServletRequest request, IdentityContext identityContext) { + String identityId = identityContext + .getParameter(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID, StringUtils.EMPTY); + request.getSession() + .setAttribute(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID, identityId); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java new file mode 100644 index 00000000..ad9a250b --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java @@ -0,0 +1,106 @@ +/* + * Copyright 1999-2020 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.auth; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.auth.GrpcProtocolAuthService; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.common.utils.ExceptionUtil; +import com.alibaba.nacos.core.remote.AbstractRequestFilter; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.api.Permission; +import com.alibaba.nacos.plugin.auth.api.Resource; +import com.alibaba.nacos.plugin.auth.constant.Constants; +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +/** + * request auth filter for remote. + * + * @author liuzunfei + * @version $Id: RemoteRequestAuthFilter.java, v 0.1 2020年09月14日 12:38 PM liuzunfei Exp $ + */ +@Component +public class RemoteRequestAuthFilter extends AbstractRequestFilter { + + private final AuthConfigs authConfigs; + + private final GrpcProtocolAuthService protocolAuthService; + + public RemoteRequestAuthFilter(AuthConfigs authConfigs) { + this.authConfigs = authConfigs; + this.protocolAuthService = new GrpcProtocolAuthService(authConfigs); + this.protocolAuthService.initialize(); + } + + @Override + public Response filter(Request request, RequestMeta meta, Class handlerClazz) throws NacosException { + + try { + + Method method = getHandleMethod(handlerClazz); + if (method.isAnnotationPresent(Secured.class) && authConfigs.isAuthEnabled()) { + + if (Loggers.AUTH.isDebugEnabled()) { + Loggers.AUTH.debug("auth start, request: {}", request.getClass().getSimpleName()); + } + + Secured secured = method.getAnnotation(Secured.class); + if (!protocolAuthService.enableAuth(secured)) { + return null; + } + String clientIp = meta.getClientIp(); + request.putHeader(Constants.Identity.X_REAL_IP, clientIp); + Resource resource = protocolAuthService.parseResource(request, secured); + IdentityContext identityContext = protocolAuthService.parseIdentity(request); + boolean result = protocolAuthService.validateIdentity(identityContext, resource); + if (!result) { + // TODO Get reason of failure + throw new AccessException("Validate Identity failed."); + } + String action = secured.action().toString(); + result = protocolAuthService.validateAuthority(identityContext, new Permission(resource, action)); + if (!result) { + // TODO Get reason of failure + throw new AccessException("Validate Authority failed."); + } + } + } catch (AccessException e) { + if (Loggers.AUTH.isDebugEnabled()) { + Loggers.AUTH.debug("access denied, request: {}, reason: {}", request.getClass().getSimpleName(), + e.getErrMsg()); + } + Response defaultResponseInstance = getDefaultResponseInstance(handlerClazz); + defaultResponseInstance.setErrorInfo(NacosException.NO_RIGHT, e.getErrMsg()); + return defaultResponseInstance; + } catch (Exception e) { + Response defaultResponseInstance = getDefaultResponseInstance(handlerClazz); + + defaultResponseInstance.setErrorInfo(NacosException.SERVER_ERROR, ExceptionUtil.getAllExceptionMsg(e)); + return defaultResponseInstance; + } + + return null; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/AbstractMemberLookup.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/AbstractMemberLookup.java new file mode 100644 index 00000000..46cc913b --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/AbstractMemberLookup.java @@ -0,0 +1,70 @@ +/* + * 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.cluster; + +import com.alibaba.nacos.api.exception.NacosException; + +import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Addressable pattern base class. + * + * @author liaochuntao + */ +public abstract class AbstractMemberLookup implements MemberLookup { + + protected ServerMemberManager memberManager; + + protected AtomicBoolean start = new AtomicBoolean(false); + + @Override + public void injectMemberManager(ServerMemberManager memberManager) { + this.memberManager = memberManager; + } + + @Override + public void afterLookup(Collection members) { + this.memberManager.memberChange(members); + } + + @Override + public void destroy() throws NacosException { + if (start.compareAndSet(true, false)) { + doDestroy(); + } + } + + @Override + public void start() throws NacosException { + if (start.compareAndSet(false, true)) { + doStart(); + } + } + + /** + * subclass can override this method if need. + * @throws NacosException NacosException + */ + protected abstract void doStart() throws NacosException; + + /** + * subclass can override this method if need. + * @throws NacosException nacosException + */ + protected abstract void doDestroy() throws NacosException; +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/Member.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/Member.java new file mode 100644 index 00000000..090cf9f4 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/Member.java @@ -0,0 +1,252 @@ +/* + * 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.cluster; + +import com.alibaba.nacos.api.ability.ServerAbilities; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.common.utils.StringUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; + +/** + * Cluster member node. + * + * @author liaochuntao + */ +public class Member implements Comparable, Cloneable, Serializable { + + private static final long serialVersionUID = -6061130045021268736L; + + private String ip; + + private int port = -1; + + private volatile NodeState state = NodeState.UP; + + private Map extendInfo = Collections.synchronizedMap(new TreeMap<>()); + + private String address = ""; + + private transient int failAccessCnt = 0; + + private ServerAbilities abilities = new ServerAbilities(); + + public Member() { + String prefix = "nacos.core.member.meta."; + extendInfo.put(MemberMetaDataConstants.SITE_KEY, + EnvUtil.getProperty(prefix + MemberMetaDataConstants.SITE_KEY, "unknow")); + extendInfo.put(MemberMetaDataConstants.AD_WEIGHT, + EnvUtil.getProperty(prefix + MemberMetaDataConstants.AD_WEIGHT, "0")); + extendInfo + .put(MemberMetaDataConstants.WEIGHT, EnvUtil.getProperty(prefix + MemberMetaDataConstants.WEIGHT, "1")); + } + + public ServerAbilities getAbilities() { + return abilities; + } + + public void setAbilities(ServerAbilities abilities) { + this.abilities = abilities; + } + + public static MemberBuilder builder() { + return new MemberBuilder(); + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public NodeState getState() { + return state; + } + + public void setState(NodeState state) { + this.state = state; + } + + public Map getExtendInfo() { + return extendInfo; + } + + public void setExtendInfo(Map extendInfo) { + Map newExtendInfo = Collections.synchronizedMap(new TreeMap<>()); + newExtendInfo.putAll(extendInfo); + this.extendInfo = newExtendInfo; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public String getAddress() { + if (StringUtils.isBlank(address)) { + address = ip + ":" + port; + } + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public Object getExtendVal(String key) { + return extendInfo.get(key); + } + + public void setExtendVal(String key, Object value) { + extendInfo.put(key, value); + } + + public void delExtendVal(String key) { + extendInfo.remove(key); + } + + public boolean check() { + return StringUtils.isNoneBlank(ip, address) && port != -1; + } + + public int getFailAccessCnt() { + return failAccessCnt; + } + + public void setFailAccessCnt(int failAccessCnt) { + this.failAccessCnt = failAccessCnt; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Member that = (Member) o; + if (StringUtils.isAnyBlank(address, that.address)) { + return port == that.port && StringUtils.equals(ip, that.ip); + } + return StringUtils.equals(address, that.address); + } + + @Override + public String toString() { + return "Member{" + "ip='" + ip + '\'' + ", port=" + port + ", state=" + state + ", extendInfo=" + extendInfo + + '}'; + } + + @Override + public int hashCode() { + return Objects.hash(ip, port); + } + + @Override + public int compareTo(Member o) { + return getAddress().compareTo(o.getAddress()); + } + + /** + * get a copy. + * + * @return member. + */ + public Member copy() { + Member copy = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(this); + // convert the input stream to member object + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + copy = (Member) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + Loggers.CORE.warn("[Member copy] copy failed", e); + } + return copy; + } + + public static final class MemberBuilder { + + private String ip; + + private int port; + + private NodeState state; + + private Map extendInfo = Collections.synchronizedMap(new TreeMap<>()); + + private MemberBuilder() { + } + + public MemberBuilder ip(String ip) { + this.ip = ip; + return this; + } + + public MemberBuilder port(int port) { + this.port = port; + return this; + } + + public MemberBuilder state(NodeState state) { + this.state = state; + return this; + } + + public MemberBuilder extendInfo(Map extendInfo) { + this.extendInfo.putAll(extendInfo); + return this; + } + + /** + * build Member. + * + * @return {@link Member} + */ + public Member build() { + Member serverNode = new Member(); + if (Objects.nonNull(this.extendInfo)) { + serverNode.extendInfo.putAll(this.extendInfo); + } + serverNode.state = this.state; + serverNode.ip = this.ip; + serverNode.port = this.port; + serverNode.address = this.ip + ":" + this.port; + return serverNode; + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberChangeListener.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberChangeListener.java new file mode 100644 index 00000000..25b7192c --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberChangeListener.java @@ -0,0 +1,49 @@ +/* + * 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.cluster; + +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.listener.Subscriber; + +/** + * Node change listeners. + * + * @author liaochuntao + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class MemberChangeListener extends Subscriber { + + /** + * return NodeChangeEvent.class info. + * + * @return {@link MembersChangeEvent#getClass()} + */ + @Override + public Class subscribeType() { + return MembersChangeEvent.class; + } + + /** + * Whether to ignore expired events. + * + * @return default value is {@link Boolean#TRUE} + */ + @Override + public boolean ignoreExpireEvent() { + return true; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberLookup.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberLookup.java new file mode 100644 index 00000000..9dee565b --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberLookup.java @@ -0,0 +1,76 @@ +/* + * 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.cluster; + +import com.alibaba.nacos.api.exception.NacosException; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +/** + * Member node addressing mode. + * + * @author liaochuntao + */ +public interface MemberLookup { + + /** + * start. + * + * @throws NacosException NacosException + */ + void start() throws NacosException; + + /** + * is using address server. + * + * @return using address server or not. + */ + boolean useAddressServer(); + + /** + * Inject the ServerMemberManager property. + * + * @param memberManager {@link ServerMemberManager} + */ + void injectMemberManager(ServerMemberManager memberManager); + + /** + * The addressing pattern finds cluster nodes. + * + * @param members {@link Collection} + */ + void afterLookup(Collection members); + + /** + * Addressing mode closed. + * + * @throws NacosException NacosException + */ + void destroy() throws NacosException; + + /** + * Some data information about the addressing pattern. + * + * @return {@link Map} + */ + default Map info() { + return Collections.emptyMap(); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberMetaDataConstants.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberMetaDataConstants.java new file mode 100644 index 00000000..a72bc666 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberMetaDataConstants.java @@ -0,0 +1,47 @@ +/* + * 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.cluster; + +/** + * The necessary metadata information for the node. + * + * @author liaochuntao + */ +public class MemberMetaDataConstants { + + /** + * Raft port,This parameter is dropped when RPC is used as a whole. + */ + public static final String RAFT_PORT = "raftPort"; + + public static final String SITE_KEY = "site"; + + public static final String AD_WEIGHT = "adWeight"; + + public static final String WEIGHT = "weight"; + + public static final String LAST_REFRESH_TIME = "lastRefreshTime"; + + public static final String VERSION = "version"; + + public static final String SUPPORT_REMOTE_C_TYPE = "remoteConnectType"; + + public static final String READY_TO_UPGRADE = "readyToUpgrade"; + + public static final String[] BASIC_META_KEYS = new String[] {SITE_KEY, AD_WEIGHT, RAFT_PORT, WEIGHT, VERSION, + READY_TO_UPGRADE}; +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberUtil.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberUtil.java new file mode 100644 index 00000000..3a81aeb0 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MemberUtil.java @@ -0,0 +1,301 @@ +/* + * 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.cluster; + +import com.alibaba.nacos.common.utils.ExceptionUtil; +import com.alibaba.nacos.common.utils.InternetAddressUtil; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.common.utils.StringUtils; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Member node tool class. + * + * @author liaochuntao + */ +public class MemberUtil { + + protected static final String TARGET_MEMBER_CONNECT_REFUSE_ERRMSG = "Connection refused"; + + private static final String SERVER_PORT_PROPERTY = "server.port"; + + private static final int DEFAULT_SERVER_PORT = 8848; + + private static final int DEFAULT_RAFT_OFFSET_PORT = 1000; + + private static final String MEMBER_FAIL_ACCESS_CNT_PROPERTY = "nacos.core.member.fail-access-cnt"; + + private static final int DEFAULT_MEMBER_FAIL_ACCESS_CNT = 3; + + /** + * Information copy. + * + * @param newMember {@link Member} + * @param oldMember {@link Member} + */ + public static void copy(Member newMember, Member oldMember) { + oldMember.setIp(newMember.getIp()); + oldMember.setPort(newMember.getPort()); + oldMember.setState(newMember.getState()); + oldMember.setExtendInfo(newMember.getExtendInfo()); + oldMember.setAddress(newMember.getAddress()); + oldMember.setAbilities(newMember.getAbilities()); + } + + /** + * parse ip:port to member. + * + * @param member ip:port + * @return {@link Member} + */ + @SuppressWarnings("PMD.UndefineMagicConstantRule") + public static Member singleParse(String member) { + // Nacos default port is 8848 + int defaultPort = EnvUtil.getProperty(SERVER_PORT_PROPERTY, Integer.class, DEFAULT_SERVER_PORT); + // Set the default Raft port information for securit + + String address = member; + int port = defaultPort; + String[] info = InternetAddressUtil.splitIPPortStr(address); + if (info.length > 1) { + address = info[0]; + port = Integer.parseInt(info[1]); + } + + Member target = Member.builder().ip(address).port(port).state(NodeState.UP).build(); + Map extendInfo = new HashMap<>(4); + // The Raft Port information needs to be set by default + extendInfo.put(MemberMetaDataConstants.RAFT_PORT, String.valueOf(calculateRaftPort(target))); + extendInfo.put(MemberMetaDataConstants.READY_TO_UPGRADE, true); + target.setExtendInfo(extendInfo); + return target; + } + + /** + * check whether the member support long connection or not. + * + * @param member member instance of server. + * @return support long connection or not. + */ + public static boolean isSupportedLongCon(Member member) { + if (member.getAbilities() == null || member.getAbilities().getRemoteAbility() == null) { + return false; + } + return member.getAbilities().getRemoteAbility().isSupportRemoteConnection(); + } + + public static int calculateRaftPort(Member member) { + return member.getPort() - DEFAULT_RAFT_OFFSET_PORT; + } + + /** + * Resolves to Member list. + * + * @param addresses ip list, example [127.0.0.1:8847,127.0.0.1:8848,127.0.0.1:8849] + * @return member list + */ + public static Collection multiParse(Collection addresses) { + List members = new ArrayList<>(addresses.size()); + for (String address : addresses) { + Member member = singleParse(address); + members.add(member); + } + return members; + } + + /** + * Successful processing of the operation on the node. + * + * @param member {@link Member} + */ + public static void onSuccess(final ServerMemberManager manager, final Member member) { + final NodeState old = member.getState(); + manager.getMemberAddressInfos().add(member.getAddress()); + member.setState(NodeState.UP); + member.setFailAccessCnt(0); + if (!Objects.equals(old, member.getState())) { + manager.notifyMemberChange(member); + } + } + + public static void onFail(final ServerMemberManager manager, final Member member) { + // To avoid null pointer judgments, pass in one NONE_EXCEPTION + onFail(manager, member, ExceptionUtil.NONE_EXCEPTION); + } + + /** + * Failure processing of the operation on the node. + * + * @param member {@link Member} + * @param ex {@link Throwable} + */ + public static void onFail(final ServerMemberManager manager, final Member member, Throwable ex) { + manager.getMemberAddressInfos().remove(member.getAddress()); + final NodeState old = member.getState(); + member.setState(NodeState.SUSPICIOUS); + member.setFailAccessCnt(member.getFailAccessCnt() + 1); + int maxFailAccessCnt = EnvUtil.getProperty(MEMBER_FAIL_ACCESS_CNT_PROPERTY, Integer.class, DEFAULT_MEMBER_FAIL_ACCESS_CNT); + + // If the number of consecutive failures to access the target node reaches + // a maximum, or the link request is rejected, the state is directly down + if (member.getFailAccessCnt() > maxFailAccessCnt || StringUtils + .containsIgnoreCase(ex.getMessage(), TARGET_MEMBER_CONNECT_REFUSE_ERRMSG)) { + member.setState(NodeState.DOWN); + } + if (!Objects.equals(old, member.getState())) { + manager.notifyMemberChange(member); + } + } + + /** + * Node list information persistence. + * + * @param members member list + */ + public static void syncToFile(Collection members) { + try { + StringBuilder builder = new StringBuilder(); + builder.append('#').append(LocalDateTime.now()).append(StringUtils.LF); + for (String member : simpleMembers(members)) { + builder.append(member).append(StringUtils.LF); + } + EnvUtil.writeClusterConf(builder.toString()); + } catch (Throwable ex) { + Loggers.CLUSTER.error("cluster member node persistence failed : {}", ExceptionUtil.getAllExceptionMsg(ex)); + } + } + + /** + * We randomly pick k nodes. + * + * @param members member list + * @param filter filter {@link Predicate} + * @param k node number + * @return target members + */ + @SuppressWarnings("PMD.UndefineMagicConstantRule") + public static Collection kRandom(Collection members, Predicate filter, int k) { + + Set kMembers = new HashSet<>(); + + // Here thinking similar consul gossip protocols random k node + int totalSize = members.size(); + Member[] membersArray = members.toArray(new Member[totalSize]); + ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current(); + for (int i = 0; i < 3 * totalSize && kMembers.size() < k; i++) { + int idx = threadLocalRandom.nextInt(totalSize); + Member member = membersArray[idx]; + if (filter.test(member)) { + kMembers.add(member); + } + } + + return kMembers; + } + + /** + * Default configuration format resolution, only NACos-Server IP or IP :port or hostname: Port information. + */ + public static Collection readServerConf(Collection members) { + Set nodes = new HashSet<>(); + + for (String member : members) { + Member target = singleParse(member); + nodes.add(target); + } + + return nodes; + } + + /** + * Select target members with filter. + * + * @param members original members + * @param filter filter + * @return target members + */ + public static Set selectTargetMembers(Collection members, Predicate filter) { + return members.stream().filter(filter).collect(Collectors.toSet()); + } + + /** + * Get address list of members. + * + * @param members members + * @return address list + */ + public static List simpleMembers(Collection members) { + return members.stream().map(Member::getAddress).sorted() + .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + } + + /** + * Judge whether basic info has changed. + * + * @param actual actual member + * @param expected expected member + * @return true if all content is same, otherwise false + */ + public static boolean isBasicInfoChanged(Member actual, Member expected) { + if (null == expected) { + return null == actual; + } + if (!expected.getIp().equals(actual.getIp())) { + return true; + } + if (expected.getPort() != actual.getPort()) { + return true; + } + if (!expected.getAddress().equals(actual.getAddress())) { + return true; + } + if (!expected.getState().equals(actual.getState())) { + return true; + } + + if (!expected.getAbilities().equals(actual.getAbilities())) { + return true; + } + + return isBasicInfoChangedInExtendInfo(expected, actual); + } + + private static boolean isBasicInfoChangedInExtendInfo(Member expected, Member actual) { + for (String each : MemberMetaDataConstants.BASIC_META_KEYS) { + if (expected.getExtendInfo().containsKey(each) != actual.getExtendInfo().containsKey(each)) { + return true; + } + if (!Objects.equals(expected.getExtendVal(each), actual.getExtendVal(each))) { + return true; + } + } + return false; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MembersChangeEvent.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MembersChangeEvent.java new file mode 100644 index 00000000..8feb572a --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/MembersChangeEvent.java @@ -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.cluster; + +import com.alibaba.nacos.common.notify.Event; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + +/** + * Publish this event when the node list changes,All interested in the node list change event can listen to this event. + * + *

    + *
  • {@link com.alibaba.nacos.core.distributed.ProtocolManager}
  • + *
  • {@link com.alibaba.nacos.naming.core.DistroMapper}
  • + *
  • {@link com.alibaba.nacos.naming.consistency.persistent.raft.RaftPeerSet}
  • + *
+ * + * @author liaochuntao + */ +public class MembersChangeEvent extends Event { + + private static final long serialVersionUID = 7308126651076668976L; + + private final Collection members; + + private final Collection triggers; + + private MembersChangeEvent(Collection members, Collection triggers) { + this.members = members; + this.triggers = new HashSet<>(); + if (triggers != null) { + this.triggers.addAll(triggers); + } + } + + public static MemberChangeEventBuilder builder() { + return new MemberChangeEventBuilder(); + } + + public Collection getMembers() { + return members; + } + + public boolean hasTriggers() { + return !triggers.isEmpty(); + } + + public Collection getTriggers() { + return triggers; + } + + @Override + public String toString() { + return "MembersChangeEvent{" + "members=" + members + ", triggers=" + triggers + ", no=" + sequence() + '}'; + } + + public static final class MemberChangeEventBuilder { + + private Collection allMembers; + + private Collection triggers; + + private MemberChangeEventBuilder() { + } + + public MemberChangeEventBuilder members(Collection allMembers) { + this.allMembers = allMembers; + return this; + } + + public MemberChangeEventBuilder triggers(Collection triggers) { + this.triggers = triggers; + return this; + } + + public MemberChangeEventBuilder trigger(Member trigger) { + this.triggers = Collections.singleton(trigger); + return this; + } + + /** + * build MemberChangeEvent. + * + * @return {@link MembersChangeEvent} + */ + public MembersChangeEvent build() { + return new MembersChangeEvent(allMembers, triggers); + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/NodeState.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/NodeState.java new file mode 100644 index 00000000..93f6adca --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/NodeState.java @@ -0,0 +1,54 @@ +/* + * 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.cluster; + +/** + * The life cycle state of a node plays an important role. + * + *

1.3.0 The unified sinking operation should be done first, and the node state + * should be radiated out later, mainly for whether the request can be processed and so on

+ * + * @author liaochuntao + */ +public enum NodeState { + + /** + * Node is starting. + */ + STARTING, + + /** + * Node is up and ready for request. + */ + UP, + + /** + * Node may Crash. + */ + SUSPICIOUS, + + /** + * Node is out of service, something abnormal happened. + */ + DOWN, + + /** + * The Node is isolated. + */ + ISOLATION, + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/ServerMemberManager.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/ServerMemberManager.java new file mode 100644 index 00000000..16c6824a --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/ServerMemberManager.java @@ -0,0 +1,602 @@ +/* + * 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.cluster; + +import com.alibaba.nacos.api.ability.ServerAbilities; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.auth.util.AuthHeaderUtil; +import com.alibaba.nacos.common.JustForTest; +import com.alibaba.nacos.common.http.Callback; +import com.alibaba.nacos.common.http.HttpClientBeanHolder; +import com.alibaba.nacos.common.http.HttpUtils; +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; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.Subscriber; +import com.alibaba.nacos.common.utils.ConcurrentHashSet; +import com.alibaba.nacos.common.utils.ExceptionUtil; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.common.utils.VersionUtils; +import com.alibaba.nacos.core.ability.ServerAbilityInitializer; +import com.alibaba.nacos.core.ability.ServerAbilityInitializerHolder; +import com.alibaba.nacos.core.cluster.lookup.LookupFactory; +import com.alibaba.nacos.core.utils.Commons; +import com.alibaba.nacos.core.utils.GenericType; +import com.alibaba.nacos.core.utils.GlobalExecutor; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.Constants; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.utils.InetUtils; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import javax.annotation.PreDestroy; +import javax.servlet.ServletContext; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * Cluster node management in Nacos. + * + *

{@link ServerMemberManager#init()} Cluster node manager initialization {@link ServerMemberManager#shutdown()} The + * cluster node manager is down {@link ServerMemberManager#getSelf()} Gets local node information {@link + * ServerMemberManager#getServerList()} Gets the cluster node dictionary {@link ServerMemberManager#getMemberAddressInfos()} + * Gets the address information of the healthy member node {@link ServerMemberManager#allMembers()} Gets a list of + * member information objects {@link ServerMemberManager#allMembersWithoutSelf()} Gets a list of cluster member nodes + * with the exception of this node {@link ServerMemberManager#hasMember(String)} Is there a node {@link + * ServerMemberManager#memberChange(Collection)} The final node list changes the method, making the full size more + * {@link ServerMemberManager#memberJoin(Collection)} Node join, can automatically trigger {@link + * ServerMemberManager#memberLeave(Collection)} When the node leaves, only the interface call can be manually triggered + * {@link ServerMemberManager#update(Member)} Update the target node information {@link + * ServerMemberManager#isUnHealth(String)} Whether the target node is healthy {@link + * ServerMemberManager#initAndStartLookup()} Initializes the addressing mode + * + * @author liaochuntao + */ +@Component(value = "serverMemberManager") +public class ServerMemberManager implements ApplicationListener { + + private final NacosAsyncRestTemplate asyncRestTemplate = HttpClientBeanHolder + .getNacosAsyncRestTemplate(Loggers.CORE); + + private static final int DEFAULT_SERVER_PORT = 8848; + + private static final String SERVER_PORT_PROPERTY = "server.port"; + + private static final String SPRING_MANAGEMENT_CONTEXT_NAMESPACE = "management"; + + private static final String MEMBER_CHANGE_EVENT_QUEUE_SIZE_PROPERTY = "nacos.member-change-event.queue.size"; + + private static final int DEFAULT_MEMBER_CHANGE_EVENT_QUEUE_SIZE = 128; + + private static boolean isUseAddressServer = false; + + private static final long DEFAULT_TASK_DELAY_TIME = 5_000L; + + /** + * Cluster node list. + */ + private volatile ConcurrentSkipListMap serverList; + + /** + * Is this node in the cluster list. + */ + private static volatile boolean isInIpList = true; + + /** + * port. + */ + private int port; + + /** + * Address information for the local node. + */ + private String localAddress; + + /** + * Addressing pattern instances. + */ + private MemberLookup lookup; + + /** + * self member obj. + */ + private volatile Member self; + + /** + * here is always the node information of the "UP" state. + */ + private volatile Set memberAddressInfos = new ConcurrentHashSet<>(); + + /** + * Broadcast this node element information task. + */ + private final MemberInfoReportTask infoReportTask = new MemberInfoReportTask(); + + public ServerMemberManager(ServletContext servletContext) throws Exception { + this.serverList = new ConcurrentSkipListMap<>(); + EnvUtil.setContextPath(servletContext.getContextPath()); + + init(); + } + + protected void init() throws NacosException { + Loggers.CORE.info("Nacos-related cluster resource initialization"); + this.port = EnvUtil.getProperty(SERVER_PORT_PROPERTY, Integer.class, DEFAULT_SERVER_PORT); + this.localAddress = InetUtils.getSelfIP() + ":" + port; + this.self = MemberUtil.singleParse(this.localAddress); + this.self.setExtendVal(MemberMetaDataConstants.VERSION, VersionUtils.version); + + // init abilities. + this.self.setAbilities(initMemberAbilities()); + + serverList.put(self.getAddress(), self); + + // register NodeChangeEvent publisher to NotifyManager + registerClusterEvent(); + + // Initializes the lookup mode + initAndStartLookup(); + + if (serverList.isEmpty()) { + throw new NacosException(NacosException.SERVER_ERROR, "cannot get serverlist, so exit."); + } + + Loggers.CORE.info("The cluster resource is initialized"); + } + + private ServerAbilities initMemberAbilities() { + ServerAbilities serverAbilities = new ServerAbilities(); + for (ServerAbilityInitializer each : ServerAbilityInitializerHolder.getInstance().getInitializers()) { + each.initialize(serverAbilities); + } + return serverAbilities; + } + + private void initAndStartLookup() throws NacosException { + this.lookup = LookupFactory.createLookUp(this); + isUseAddressServer = this.lookup.useAddressServer(); + this.lookup.start(); + } + + /** + * switch look up. + * + * @param name look up name. + * @throws NacosException exception. + */ + public void switchLookup(String name) throws NacosException { + this.lookup = LookupFactory.switchLookup(name, this); + isUseAddressServer = this.lookup.useAddressServer(); + this.lookup.start(); + } + + private void registerClusterEvent() { + // Register node change events + NotifyCenter.registerToPublisher(MembersChangeEvent.class, + EnvUtil.getProperty(MEMBER_CHANGE_EVENT_QUEUE_SIZE_PROPERTY, Integer.class, + DEFAULT_MEMBER_CHANGE_EVENT_QUEUE_SIZE)); + + // The address information of this node needs to be dynamically modified + // when registering the IP change of this node + NotifyCenter.registerSubscriber(new Subscriber() { + @Override + public void onEvent(InetUtils.IPChangeEvent event) { + String newAddress = event.getNewIP() + ":" + port; + ServerMemberManager.this.localAddress = newAddress; + EnvUtil.setLocalAddress(localAddress); + + Member self = ServerMemberManager.this.self; + self.setIp(event.getNewIP()); + + String oldAddress = event.getOldIP() + ":" + port; + ServerMemberManager.this.serverList.remove(oldAddress); + ServerMemberManager.this.serverList.put(newAddress, self); + + ServerMemberManager.this.memberAddressInfos.remove(oldAddress); + ServerMemberManager.this.memberAddressInfos.add(newAddress); + } + + @Override + public Class subscribeType() { + return InetUtils.IPChangeEvent.class; + } + }); + } + + public static boolean isUseAddressServer() { + return isUseAddressServer; + } + + /** + * member information update. + * + * @param newMember {@link Member} + * @return update is success + */ + public boolean update(Member newMember) { + Loggers.CLUSTER.debug("member information update : {}", newMember); + + String address = newMember.getAddress(); + if (!serverList.containsKey(address)) { + return false; + } + + serverList.computeIfPresent(address, (s, member) -> { + if (NodeState.DOWN.equals(newMember.getState())) { + memberAddressInfos.remove(newMember.getAddress()); + } + boolean isPublishChangeEvent = MemberUtil.isBasicInfoChanged(newMember, member); + newMember.setExtendVal(MemberMetaDataConstants.LAST_REFRESH_TIME, System.currentTimeMillis()); + MemberUtil.copy(newMember, member); + if (isPublishChangeEvent) { + // member basic data changes and all listeners need to be notified + notifyMemberChange(member); + } + return member; + }); + + return true; + } + + void notifyMemberChange(Member member) { + NotifyCenter.publishEvent(MembersChangeEvent.builder().trigger(member).members(allMembers()).build()); + } + + /** + * Whether the node exists within the cluster. + * + * @param address ip:port + * @return is exists + */ + public boolean hasMember(String address) { + boolean result = serverList.containsKey(address); + if (result) { + return true; + } + + // If only IP information is passed in, a fuzzy match is required + for (Map.Entry entry : serverList.entrySet()) { + if (StringUtils.contains(entry.getKey(), address)) { + result = true; + break; + } + } + return result; + } + + public List getServerListUnhealth() { + List unhealthyMembers = new ArrayList<>(); + for (Member member : this.allMembers()) { + NodeState state = member.getState(); + if (state.equals(NodeState.DOWN)) { + unhealthyMembers.add(member.getAddress()); + } + + } + return unhealthyMembers; + } + + public MemberLookup getLookup() { + return lookup; + } + + public Member getSelf() { + return this.self; + } + + public Member find(String address) { + return serverList.get(address); + } + + /** + * return this cluster all members. + * + * @return {@link Collection} all member + */ + public Collection allMembers() { + // We need to do a copy to avoid affecting the real data + HashSet set = new HashSet<>(serverList.values()); + set.add(self); + return set; + } + + /** + * return this cluster all members without self. + * + * @return {@link Collection} all member without self + */ + public List allMembersWithoutSelf() { + List members = new ArrayList<>(serverList.values()); + members.remove(self); + return members; + } + + synchronized boolean memberChange(Collection members) { + + if (members == null || members.isEmpty()) { + return false; + } + + boolean isContainSelfIp = members.stream() + .anyMatch(ipPortTmp -> Objects.equals(localAddress, ipPortTmp.getAddress())); + + if (isContainSelfIp) { + isInIpList = true; + } else { + isInIpList = false; + members.add(this.self); + Loggers.CLUSTER.warn("[serverlist] self ip {} not in serverlist {}", self, members); + } + + // If the number of old and new clusters is different, the cluster information + // must have changed; if the number of clusters is the same, then compare whether + // there is a difference; if there is a difference, then the cluster node changes + // are involved and all recipients need to be notified of the node change event + + boolean hasChange = members.size() != serverList.size(); + ConcurrentSkipListMap tmpMap = new ConcurrentSkipListMap<>(); + Set tmpAddressInfo = new ConcurrentHashSet<>(); + for (Member member : members) { + final String address = member.getAddress(); + + Member existMember = serverList.get(address); + if (existMember == null) { + hasChange = true; + tmpMap.put(address, member); + } else { + //to keep extendInfo and abilities that report dynamically. + tmpMap.put(address, existMember); + } + + if (NodeState.UP.equals(member.getState())) { + tmpAddressInfo.add(address); + } + } + + serverList = tmpMap; + memberAddressInfos = tmpAddressInfo; + + Collection finalMembers = allMembers(); + + // Persist the current cluster node information to cluster.conf + // need to put the event publication into a synchronized block to ensure + // that the event publication is sequential + if (hasChange) { + Loggers.CLUSTER.warn("[serverlist] updated to : {}", finalMembers); + MemberUtil.syncToFile(finalMembers); + Event event = MembersChangeEvent.builder().members(finalMembers).build(); + NotifyCenter.publishEvent(event); + } else { + if (Loggers.CLUSTER.isDebugEnabled()) { + Loggers.CLUSTER.debug("[serverlist] not updated, is still : {}", finalMembers); + } + } + + return hasChange; + } + + /** + * members join this cluster. + * + * @param members {@link Collection} new members + * @return is success + */ + public synchronized boolean memberJoin(Collection members) { + Set set = new HashSet<>(members); + set.addAll(allMembers()); + return memberChange(set); + } + + /** + * members leave this cluster. + * + * @param members {@link Collection} wait leave members + * @return is success + */ + public synchronized boolean memberLeave(Collection members) { + Set set = new HashSet<>(allMembers()); + set.removeAll(members); + return memberChange(set); + } + + /** + * this member {@link Member#getState()} is health. + * + * @param address ip:port + * @return is health + */ + public boolean isUnHealth(String address) { + Member member = serverList.get(address); + if (member == null) { + return false; + } + return !NodeState.UP.equals(member.getState()); + } + + public boolean isFirstIp() { + return Objects.equals(serverList.firstKey(), this.localAddress); + } + + @Override + public void onApplicationEvent(WebServerInitializedEvent event) { + String serverNamespace = event.getApplicationContext().getServerNamespace(); + if (SPRING_MANAGEMENT_CONTEXT_NAMESPACE.equals(serverNamespace)) { + // ignore + // fix#issue https://github.com/alibaba/nacos/issues/7230 + return; + } + getSelf().setState(NodeState.UP); + if (!EnvUtil.getStandaloneMode()) { + GlobalExecutor.scheduleByCommon(this.infoReportTask, DEFAULT_TASK_DELAY_TIME); + } + EnvUtil.setPort(event.getWebServer().getPort()); + EnvUtil.setLocalAddress(this.localAddress); + Loggers.CLUSTER.info("This node is ready to provide external services"); + } + + /** + * ServerMemberManager shutdown. + * + * @throws NacosException NacosException + */ + @PreDestroy + public void shutdown() throws NacosException { + serverList.clear(); + memberAddressInfos.clear(); + infoReportTask.shutdown(); + LookupFactory.destroy(); + } + + public Set getMemberAddressInfos() { + return memberAddressInfos; + } + + @JustForTest + public void updateMember(Member member) { + serverList.put(member.getAddress(), member); + } + + @JustForTest + public void setMemberAddressInfos(Set memberAddressInfos) { + this.memberAddressInfos = memberAddressInfos; + } + + @JustForTest + public MemberInfoReportTask getInfoReportTask() { + return infoReportTask; + } + + public Map getServerList() { + return Collections.unmodifiableMap(serverList); + } + + public static boolean isInIpList() { + return isInIpList; + } + + // Synchronize the metadata information of a node + // A health check of the target node is also attached + + class MemberInfoReportTask extends Task { + + private final GenericType> reference = new GenericType>() { + }; + + private int cursor = 0; + + @Override + protected void executeBody() { + List members = ServerMemberManager.this.allMembersWithoutSelf(); + + if (members.isEmpty()) { + return; + } + + this.cursor = (this.cursor + 1) % members.size(); + Member target = members.get(cursor); + + Loggers.CLUSTER.debug("report the metadata to the node : {}", target.getAddress()); + + final String url = HttpUtils + .buildUrl(false, target.getAddress(), EnvUtil.getContextPath(), Commons.NACOS_CORE_CONTEXT, + "/cluster/report"); + + try { + Header header = Header.newInstance().addParam(Constants.NACOS_SERVER_HEADER, VersionUtils.version); + AuthHeaderUtil.addIdentityToHeader(header); + asyncRestTemplate + .post(url, header, Query.EMPTY, getSelf(), reference.getType(), new Callback() { + @Override + public void onReceive(RestResult result) { + if (result.getCode() == HttpStatus.NOT_IMPLEMENTED.value() + || result.getCode() == HttpStatus.NOT_FOUND.value()) { + Loggers.CLUSTER + .warn("{} version is too low, it is recommended to upgrade the version : {}", + target, VersionUtils.version); + Member memberNew = null; + if (target.getExtendVal(MemberMetaDataConstants.VERSION) != null) { + memberNew = target.copy(); + // Clean up remote version info. + // This value may still stay in extend info when remote server has been downgraded to old version. + memberNew.delExtendVal(MemberMetaDataConstants.VERSION); + memberNew.delExtendVal(MemberMetaDataConstants.READY_TO_UPGRADE); + Loggers.CLUSTER.warn("{} : Clean up version info," + + " target has been downgrade to old version.", memberNew); + } + if (target.getAbilities() != null + && target.getAbilities().getRemoteAbility() != null && target.getAbilities() + .getRemoteAbility().isSupportRemoteConnection()) { + if (memberNew == null) { + memberNew = target.copy(); + } + memberNew.getAbilities().getRemoteAbility().setSupportRemoteConnection(false); + Loggers.CLUSTER + .warn("{} : Clear support remote connection flag,target may rollback version ", + memberNew); + } + if (memberNew != null) { + update(memberNew); + } + return; + } + if (result.ok()) { + MemberUtil.onSuccess(ServerMemberManager.this, target); + } else { + Loggers.CLUSTER.warn("failed to report new info to target node : {}, result : {}", + target.getAddress(), result); + MemberUtil.onFail(ServerMemberManager.this, target); + } + } + + @Override + public void onError(Throwable throwable) { + Loggers.CLUSTER.error("failed to report new info to target node : {}, error : {}", + target.getAddress(), ExceptionUtil.getAllExceptionMsg(throwable)); + MemberUtil.onFail(ServerMemberManager.this, target, throwable); + } + + @Override + public void onCancel() { + + } + }); + } catch (Throwable ex) { + Loggers.CLUSTER.error("failed to report new info to target node : {}, error : {}", target.getAddress(), + ExceptionUtil.getAllExceptionMsg(ex)); + } + } + + @Override + protected void after() { + GlobalExecutor.scheduleByCommon(this, 2_000L); + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/Task.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/Task.java new file mode 100644 index 00000000..9b7ee8f3 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/Task.java @@ -0,0 +1,64 @@ +/* + * 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.cluster; + +import com.alibaba.nacos.common.utils.ExceptionUtil; +import com.alibaba.nacos.core.utils.Loggers; + +/** + * task. + * + * @author liaochuntao + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class Task implements Runnable { + + protected volatile boolean shutdown = false; + + @Override + public void run() { + if (shutdown) { + return; + } + try { + executeBody(); + } catch (Throwable t) { + Loggers.CORE.error("this task execute has error : {}", ExceptionUtil.getStackTrace(t)); + } finally { + if (!shutdown) { + after(); + } + } + } + + /** + * Task executive. + */ + protected abstract void executeBody(); + + /** + * after executeBody should do. + */ + protected void after() { + + } + + public void shutdown() { + shutdown = true; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/AddressServerMemberLookup.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/AddressServerMemberLookup.java new file mode 100644 index 00000000..76e747a8 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/AddressServerMemberLookup.java @@ -0,0 +1,215 @@ +/* + * 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.cluster.lookup; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.common.http.HttpClientBeanHolder; +import com.alibaba.nacos.common.http.client.NacosRestTemplate; +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.ExceptionUtil; +import com.alibaba.nacos.core.cluster.AbstractMemberLookup; +import com.alibaba.nacos.core.cluster.MemberUtil; +import com.alibaba.nacos.core.utils.GenericType; +import com.alibaba.nacos.core.utils.GlobalExecutor; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.common.utils.StringUtils; + +import java.io.Reader; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.nacos.common.constant.RequestUrlConstants.HTTP_PREFIX; + +/** + * Cluster member addressing mode for the address server. + * + * @author liaochuntao + */ +public class AddressServerMemberLookup extends AbstractMemberLookup { + + private final GenericType genericType = new GenericType() { }; + + public String domainName; + + public String addressPort; + + public String addressUrl; + + public String envIdUrl; + + public String addressServerUrl; + + private volatile boolean isAddressServerHealth = true; + + private int addressServerFailCount = 0; + + private int maxFailCount = 12; + + private final NacosRestTemplate restTemplate = HttpClientBeanHolder.getNacosRestTemplate(Loggers.CORE); + + private volatile boolean shutdown = false; + + private static final String HEALTH_CHECK_FAIL_COUNT_PROPERTY = "maxHealthCheckFailCount"; + + private static final String DEFAULT_HEALTH_CHECK_FAIL_COUNT = "12"; + + private static final String DEFAULT_SERVER_DOMAIN = "jmenv.tbsite.net"; + + private static final String DEFAULT_SERVER_POINT = "8080"; + + private static final int DEFAULT_SERVER_RETRY_TIME = 5; + + private static final long DEFAULT_SYNC_TASK_DELAY_MS = 5_000L; + + private static final String ADDRESS_SERVER_DOMAIN_ENV = "address_server_domain"; + + private static final String ADDRESS_SERVER_DOMAIN_PROPERTY = "address.server.domain"; + + private static final String ADDRESS_SERVER_PORT_ENV = "address_server_port"; + + private static final String ADDRESS_SERVER_PORT_PROPERTY = "address.server.port"; + + private static final String ADDRESS_SERVER_URL_ENV = "address_server_url"; + + private static final String ADDRESS_SERVER_URL_PROPERTY = "address.server.url"; + + private static final String ADDRESS_SERVER_RETRY_PROPERTY = "nacos.core.address-server.retry"; + + @Override + public void doStart() throws NacosException { + this.maxFailCount = Integer.parseInt(EnvUtil.getProperty(HEALTH_CHECK_FAIL_COUNT_PROPERTY, DEFAULT_HEALTH_CHECK_FAIL_COUNT)); + initAddressSys(); + run(); + } + + @Override + public boolean useAddressServer() { + return true; + } + + private void initAddressSys() { + String envDomainName = System.getenv(ADDRESS_SERVER_DOMAIN_ENV); + if (StringUtils.isBlank(envDomainName)) { + domainName = EnvUtil.getProperty(ADDRESS_SERVER_DOMAIN_PROPERTY, DEFAULT_SERVER_DOMAIN); + } else { + domainName = envDomainName; + } + String envAddressPort = System.getenv(ADDRESS_SERVER_PORT_ENV); + if (StringUtils.isBlank(envAddressPort)) { + addressPort = EnvUtil.getProperty(ADDRESS_SERVER_PORT_PROPERTY, DEFAULT_SERVER_POINT); + } else { + addressPort = envAddressPort; + } + String envAddressUrl = System.getenv(ADDRESS_SERVER_URL_ENV); + if (StringUtils.isBlank(envAddressUrl)) { + addressUrl = EnvUtil.getProperty(ADDRESS_SERVER_URL_PROPERTY, EnvUtil.getContextPath() + "/" + "serverlist"); + } else { + addressUrl = envAddressUrl; + } + addressServerUrl = HTTP_PREFIX + domainName + ":" + addressPort + addressUrl; + envIdUrl = HTTP_PREFIX + domainName + ":" + addressPort + "/env"; + + Loggers.CORE.info("ServerListService address-server port:" + addressPort); + Loggers.CORE.info("ADDRESS_SERVER_URL:" + addressServerUrl); + } + + @SuppressWarnings("PMD.UndefineMagicConstantRule") + private void run() throws NacosException { + // With the address server, you need to perform a synchronous member node pull at startup + // Repeat three times, successfully jump out + boolean success = false; + Throwable ex = null; + int maxRetry = EnvUtil.getProperty(ADDRESS_SERVER_RETRY_PROPERTY, Integer.class, DEFAULT_SERVER_RETRY_TIME); + for (int i = 0; i < maxRetry; i++) { + try { + syncFromAddressUrl(); + success = true; + break; + } catch (Throwable e) { + ex = e; + Loggers.CLUSTER.error("[serverlist] exception, error : {}", ExceptionUtil.getAllExceptionMsg(ex)); + } + } + if (!success) { + throw new NacosException(NacosException.SERVER_ERROR, ex); + } + + GlobalExecutor.scheduleByCommon(new AddressServerSyncTask(), DEFAULT_SYNC_TASK_DELAY_MS); + } + + @Override + protected void doDestroy() throws NacosException { + shutdown = true; + } + + @Override + public Map info() { + Map info = new HashMap<>(4); + info.put("addressServerHealth", isAddressServerHealth); + info.put("addressServerUrl", addressServerUrl); + info.put("envIdUrl", envIdUrl); + info.put("addressServerFailCount", addressServerFailCount); + return info; + } + + private void syncFromAddressUrl() throws Exception { + RestResult result = restTemplate + .get(addressServerUrl, Header.EMPTY, Query.EMPTY, genericType.getType()); + if (result.ok()) { + isAddressServerHealth = true; + Reader reader = new StringReader(result.getData()); + try { + afterLookup(MemberUtil.readServerConf(EnvUtil.analyzeClusterConf(reader))); + } catch (Throwable e) { + Loggers.CLUSTER.error("[serverlist] exception for analyzeClusterConf, error : {}", + ExceptionUtil.getAllExceptionMsg(e)); + } + addressServerFailCount = 0; + } else { + addressServerFailCount++; + if (addressServerFailCount >= maxFailCount) { + isAddressServerHealth = false; + } + Loggers.CLUSTER.error("[serverlist] failed to get serverlist, error code {}", result.getCode()); + } + } + + class AddressServerSyncTask implements Runnable { + + @Override + public void run() { + if (shutdown) { + return; + } + try { + syncFromAddressUrl(); + } catch (Throwable ex) { + addressServerFailCount++; + if (addressServerFailCount >= maxFailCount) { + isAddressServerHealth = false; + } + Loggers.CLUSTER.error("[serverlist] exception, error : {}", ExceptionUtil.getAllExceptionMsg(ex)); + } finally { + GlobalExecutor.scheduleByCommon(this, DEFAULT_SYNC_TASK_DELAY_MS); + } + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/FileConfigMemberLookup.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/FileConfigMemberLookup.java new file mode 100644 index 00000000..d50ffbb4 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/FileConfigMemberLookup.java @@ -0,0 +1,90 @@ +/* + * 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.cluster.lookup; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.core.cluster.AbstractMemberLookup; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.MemberUtil; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.file.FileChangeEvent; +import com.alibaba.nacos.sys.file.FileWatcher; +import com.alibaba.nacos.sys.file.WatchFileCenter; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.common.utils.StringUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Cluster.conf file managed cluster member node addressing pattern. + * + * @author liaochuntao + */ +public class FileConfigMemberLookup extends AbstractMemberLookup { + + private static final String DEFAULT_SEARCH_SEQ = "cluster.conf"; + + private FileWatcher watcher = new FileWatcher() { + @Override + public void onChange(FileChangeEvent event) { + readClusterConfFromDisk(); + } + + @Override + public boolean interest(String context) { + return StringUtils.contains(context, DEFAULT_SEARCH_SEQ); + } + }; + + @Override + public void doStart() throws NacosException { + readClusterConfFromDisk(); + + // Use the inotify mechanism to monitor file changes and automatically + // trigger the reading of cluster.conf + try { + WatchFileCenter.registerWatcher(EnvUtil.getConfPath(), watcher); + } catch (Throwable e) { + Loggers.CLUSTER.error("An exception occurred in the launch file monitor : {}", e.getMessage()); + } + } + + @Override + public boolean useAddressServer() { + return false; + } + + @Override + protected void doDestroy() throws NacosException { + WatchFileCenter.deregisterWatcher(EnvUtil.getConfPath(), watcher); + } + + private void readClusterConfFromDisk() { + Collection tmpMembers = new ArrayList<>(); + try { + List tmp = EnvUtil.readClusterConf(); + tmpMembers = MemberUtil.readServerConf(tmp); + } catch (Throwable e) { + Loggers.CLUSTER + .error("nacos-XXXX [serverlist] failed to get serverlist from disk!, error : {}", e.getMessage()); + } + + afterLookup(tmpMembers); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/LookupFactory.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/LookupFactory.java new file mode 100644 index 00000000..437247cb --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/LookupFactory.java @@ -0,0 +1,181 @@ +/* + * 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.cluster.lookup; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.cluster.MemberLookup; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.core.utils.Loggers; + +import java.io.File; +import java.util.Arrays; +import java.util.Objects; + +/** + * An addressing pattern factory, responsible for the creation of all addressing patterns. + * + * @author liaochuntao + */ +public final class LookupFactory { + + private static final String LOOKUP_MODE_TYPE = "nacos.core.member.lookup.type"; + + @SuppressWarnings("checkstyle:StaticVariableName") + private static MemberLookup LOOK_UP = null; + + private static LookupType currentLookupType = null; + + /** + * Create the target addressing pattern. + * + * @param memberManager {@link ServerMemberManager} + * @return {@link MemberLookup} + * @throws NacosException NacosException + */ + public static MemberLookup createLookUp(ServerMemberManager memberManager) throws NacosException { + if (!EnvUtil.getStandaloneMode()) { + String lookupType = EnvUtil.getProperty(LOOKUP_MODE_TYPE); + LookupType type = chooseLookup(lookupType); + LOOK_UP = find(type); + currentLookupType = type; + } else { + LOOK_UP = new StandaloneMemberLookup(); + } + LOOK_UP.injectMemberManager(memberManager); + Loggers.CLUSTER.info("Current addressing mode selection : {}", LOOK_UP.getClass().getSimpleName()); + return LOOK_UP; + } + + /** + * Switch to target addressing mode. + * + * @param name target member-lookup name + * @param memberManager {@link ServerMemberManager} + * @return {@link MemberLookup} + * @throws NacosException {@link NacosException} + */ + public static MemberLookup switchLookup(String name, ServerMemberManager memberManager) throws NacosException { + LookupType lookupType = LookupType.sourceOf(name); + + if (Objects.isNull(lookupType)) { + throw new IllegalArgumentException( + "The addressing mode exists : " + name + ", just support : [" + Arrays.toString(LookupType.values()) + + "]"); + } + + if (Objects.equals(currentLookupType, lookupType)) { + return LOOK_UP; + } + MemberLookup newLookup = find(lookupType); + currentLookupType = lookupType; + if (Objects.nonNull(LOOK_UP)) { + LOOK_UP.destroy(); + } + LOOK_UP = newLookup; + LOOK_UP.injectMemberManager(memberManager); + Loggers.CLUSTER.info("Current addressing mode selection : {}", LOOK_UP.getClass().getSimpleName()); + return LOOK_UP; + } + + private static MemberLookup find(LookupType type) { + if (LookupType.FILE_CONFIG.equals(type)) { + LOOK_UP = new FileConfigMemberLookup(); + return LOOK_UP; + } + if (LookupType.ADDRESS_SERVER.equals(type)) { + LOOK_UP = new AddressServerMemberLookup(); + return LOOK_UP; + } + // unpossible to run here + throw new IllegalArgumentException(); + } + + private static LookupType chooseLookup(String lookupType) { + if (StringUtils.isNotBlank(lookupType)) { + LookupType type = LookupType.sourceOf(lookupType); + if (Objects.nonNull(type)) { + return type; + } + } + File file = new File(EnvUtil.getClusterConfFilePath()); + if (file.exists() || StringUtils.isNotBlank(EnvUtil.getMemberList())) { + return LookupType.FILE_CONFIG; + } + return LookupType.ADDRESS_SERVER; + } + + public static MemberLookup getLookUp() { + return LOOK_UP; + } + + public static void destroy() throws NacosException { + Objects.requireNonNull(LOOK_UP).destroy(); + } + + public enum LookupType { + + /** + * File addressing mode. + */ + FILE_CONFIG(1, "file"), + + /** + * Address server addressing mode. + */ + ADDRESS_SERVER(2, "address-server"); + + private final int code; + + private final String name; + + LookupType(int code, String name) { + this.code = code; + this.name = name; + } + + /** + * find one {@link LookupType} by name, if not found, return null. + * + * @param name name + * @return {@link LookupType} + */ + public static LookupType sourceOf(String name) { + for (LookupType type : values()) { + if (Objects.equals(type.name, name)) { + return type; + } + } + return null; + } + + public int getCode() { + return code; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name; + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/StandaloneMemberLookup.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/StandaloneMemberLookup.java new file mode 100644 index 00000000..70deb5b2 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/lookup/StandaloneMemberLookup.java @@ -0,0 +1,48 @@ +/* + * 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.cluster.lookup; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.core.cluster.AbstractMemberLookup; +import com.alibaba.nacos.core.cluster.MemberUtil; +import com.alibaba.nacos.sys.env.EnvUtil; + +import java.util.Collections; + +/** + * Member node addressing mode in stand-alone mode. + * + * @author liaochuntao + */ +public class StandaloneMemberLookup extends AbstractMemberLookup { + + @Override + public void doStart() { + String url = EnvUtil.getLocalAddress(); + afterLookup(MemberUtil.readServerConf(Collections.singletonList(url))); + } + + @Override + protected void doDestroy() throws NacosException { + + } + + @Override + public boolean useAddressServer() { + return false; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxy.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxy.java new file mode 100644 index 00000000..b0febd7a --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxy.java @@ -0,0 +1,243 @@ +/* + * Copyright 1999-2020 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.cluster.remote; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.RemoteConstants; +import com.alibaba.nacos.api.remote.RequestCallBack; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.remote.ConnectionType; +import com.alibaba.nacos.common.remote.client.RpcClient; +import com.alibaba.nacos.common.remote.client.RpcClientFactory; +import com.alibaba.nacos.common.remote.client.ServerListFactory; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.MemberChangeListener; +import com.alibaba.nacos.core.cluster.MemberUtil; +import com.alibaba.nacos.core.cluster.MembersChangeEvent; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.alibaba.nacos.api.exception.NacosException.CLIENT_INVALID_PARAM; + +/** + * cluster rpc client proxy. + * + * @author liuzunfei + * @version $Id: ClusterRpcClientProxy.java, v 0.1 2020年08月11日 2:11 PM liuzunfei Exp $ + */ +@Service +public class ClusterRpcClientProxy extends MemberChangeListener { + + private static final long DEFAULT_REQUEST_TIME_OUT = 3000L; + + @Autowired + ServerMemberManager serverMemberManager; + + /** + * init after constructor. + */ + @PostConstruct + public void init() { + try { + NotifyCenter.registerSubscriber(this); + List members = serverMemberManager.allMembersWithoutSelf(); + refresh(members); + Loggers.CLUSTER + .warn("[ClusterRpcClientProxy] success to refresh cluster rpc client on start up,members ={} ", + members); + } catch (NacosException e) { + Loggers.CLUSTER.warn("[ClusterRpcClientProxy] fail to refresh cluster rpc client,{} ", e.getMessage()); + } + + } + + /** + * init cluster rpc clients. + * + * @param members cluster server list member list. + */ + private void refresh(List members) throws NacosException { + + //ensure to create client of new members + for (Member member : members) { + + if (MemberUtil.isSupportedLongCon(member)) { + createRpcClientAndStart(member, ConnectionType.GRPC); + } + } + + //shutdown and remove old members. + Set> allClientEntrys = RpcClientFactory.getAllClientEntries(); + Iterator> iterator = allClientEntrys.iterator(); + List newMemberKeys = members.stream().filter(MemberUtil::isSupportedLongCon) + .map(this::memberClientKey).collect(Collectors.toList()); + while (iterator.hasNext()) { + Map.Entry next1 = iterator.next(); + if (next1.getKey().startsWith("Cluster-") && !newMemberKeys.contains(next1.getKey())) { + Loggers.CLUSTER.info("member leave,destroy client of member - > : {}", next1.getKey()); + RpcClientFactory.getClient(next1.getKey()).shutdown(); + iterator.remove(); + } + } + + } + + private String memberClientKey(Member member) { + return "Cluster-" + member.getAddress(); + } + + private void createRpcClientAndStart(Member member, ConnectionType type) throws NacosException { + Map labels = new HashMap<>(2); + labels.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_CLUSTER); + String memberClientKey = memberClientKey(member); + RpcClient client = buildRpcClient(type, labels, memberClientKey); + if (!client.getConnectionType().equals(type)) { + Loggers.CLUSTER.info(",connection type changed,destroy client of member - > : {}", member); + RpcClientFactory.destroyClient(memberClientKey); + client = buildRpcClient(type, labels, memberClientKey); + } + + if (client.isWaitInitiated()) { + Loggers.CLUSTER.info("start a new rpc client to member - > : {}", member); + + //one fixed server + client.serverListFactory(new ServerListFactory() { + @Override + public String genNextServer() { + return member.getAddress(); + } + + @Override + public String getCurrentServer() { + return member.getAddress(); + } + + @Override + public List getServerList() { + return CollectionUtils.list(member.getAddress()); + } + }); + + client.start(); + } + } + + /** + * Using {@link EnvUtil#getAvailableProcessors(int)} to build cluster clients' grpc thread pool. + */ + private RpcClient buildRpcClient(ConnectionType type, Map labels, String memberClientKey) { + return RpcClientFactory.createClusterClient(memberClientKey, type, + EnvUtil.getAvailableProcessors(2), EnvUtil.getAvailableProcessors(8), labels); + } + + /** + * send request to member. + * + * @param member member of server. + * @param request request. + * @return Response response. + * @throws NacosException exception may throws. + */ + public Response sendRequest(Member member, Request request) throws NacosException { + return sendRequest(member, request, DEFAULT_REQUEST_TIME_OUT); + } + + /** + * send request to member. + * + * @param member member of server. + * @param request request. + * @return Response response. + * @throws NacosException exception may throws. + */ + public Response sendRequest(Member member, Request request, long timeoutMills) throws NacosException { + RpcClient client = RpcClientFactory.getClient(memberClientKey(member)); + if (client != null) { + return client.request(request, timeoutMills); + } else { + throw new NacosException(CLIENT_INVALID_PARAM, "No rpc client related to member: " + member); + } + } + + /** + * aync send request to member with callback. + * + * @param member member of server. + * @param request request. + * @param callBack RequestCallBack. + * @throws NacosException exception may throws. + */ + public void asyncRequest(Member member, Request request, RequestCallBack callBack) throws NacosException { + RpcClient client = RpcClientFactory.getClient(memberClientKey(member)); + if (client != null) { + client.asyncRequest(request, callBack); + } else { + throw new NacosException(CLIENT_INVALID_PARAM, "No rpc client related to member: " + member); + } + } + + /** + * send request to member. + * + * @param request request. + * @throws NacosException exception may throw. + */ + public void sendRequestToAllMembers(Request request) throws NacosException { + List members = serverMemberManager.allMembersWithoutSelf(); + for (Member member1 : members) { + sendRequest(member1, request); + } + } + + @Override + public void onEvent(MembersChangeEvent event) { + try { + List members = serverMemberManager.allMembersWithoutSelf(); + refresh(members); + } catch (NacosException e) { + Loggers.CLUSTER.warn("[serverlist] fail to refresh cluster rpc client, event:{}, msg: {} ", event, e.getMessage()); + } + } + + /** + * Check whether client for member is running. + * + * @param member member + * @return {@code true} if target client is connected, otherwise {@code false} + */ + public boolean isRunning(Member member) { + RpcClient client = RpcClientFactory.getClient(memberClientKey(member)); + if (null == client) { + return false; + } + return client.isRunning(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/ControllerMethodsCache.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/ControllerMethodsCache.java new file mode 100644 index 00000000..af105446 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/ControllerMethodsCache.java @@ -0,0 +1,225 @@ +/* + * 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.code; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; +import com.alibaba.nacos.common.packagescan.DefaultPackageScan; +import com.alibaba.nacos.common.utils.ArrayUtils; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.core.code.RequestMappingInfo.RequestMappingInfoComparator; +import com.alibaba.nacos.core.code.condition.ParamRequestCondition; +import com.alibaba.nacos.core.code.condition.PathRequestCondition; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static com.alibaba.nacos.sys.env.Constants.REQUEST_PATH_SEPARATOR; + + +/** + * Method cache. + * + * @author nkorange + * @since 1.2.0 + */ +@Component +public class ControllerMethodsCache { + + private static final Logger LOGGER = LoggerFactory.getLogger(ControllerMethodsCache.class); + + private ConcurrentMap methods = new ConcurrentHashMap<>(); + + private final ConcurrentMap> urlLookup = new ConcurrentHashMap<>(); + + public Method getMethod(HttpServletRequest request) { + String path = getPath(request); + String httpMethod = request.getMethod(); + String urlKey = httpMethod + REQUEST_PATH_SEPARATOR + path.replaceFirst(EnvUtil.getContextPath(), ""); + List requestMappingInfos = urlLookup.get(urlKey); + if (CollectionUtils.isEmpty(requestMappingInfos)) { + return null; + } + List matchedInfo = findMatchedInfo(requestMappingInfos, request); + if (CollectionUtils.isEmpty(matchedInfo)) { + return null; + } + RequestMappingInfo bestMatch = matchedInfo.get(0); + if (matchedInfo.size() > 1) { + RequestMappingInfoComparator comparator = new RequestMappingInfoComparator(); + matchedInfo.sort(comparator); + bestMatch = matchedInfo.get(0); + RequestMappingInfo secondBestMatch = matchedInfo.get(1); + if (comparator.compare(bestMatch, secondBestMatch) == 0) { + throw new IllegalStateException( + "Ambiguous methods mapped for '" + request.getRequestURI() + "': {" + bestMatch + ", " + + secondBestMatch + "}"); + } + } + return methods.get(bestMatch); + } + + private String getPath(HttpServletRequest request) { + try { + return new URI(request.getRequestURI()).getPath(); + } catch (URISyntaxException e) { + LOGGER.error("parse request to path error", e); + throw new NacosRuntimeException(NacosException.NOT_FOUND, "Invalid URI"); + } + } + + private List findMatchedInfo(List requestMappingInfos, + HttpServletRequest request) { + List matchedInfo = new ArrayList<>(); + for (RequestMappingInfo requestMappingInfo : requestMappingInfos) { + ParamRequestCondition matchingCondition = requestMappingInfo.getParamRequestCondition() + .getMatchingCondition(request); + if (matchingCondition != null) { + matchedInfo.add(requestMappingInfo); + } + } + return matchedInfo; + } + + /** + * find target method from this package. + * + * @param packageName package name + */ + public void initClassMethod(String packageName) { + DefaultPackageScan packageScan = new DefaultPackageScan(); + Set> classesList = packageScan.getTypesAnnotatedWith(packageName, RequestMapping.class); + for (Class clazz : classesList) { + initClassMethod(clazz); + } + } + + /** + * find target method from class list. + * + * @param classesList class list + */ + public void initClassMethod(Set> classesList) { + for (Class clazz : classesList) { + initClassMethod(clazz); + } + } + + /** + * find target method from target class. + * + * @param clazz {@link Class} + */ + private void initClassMethod(Class clazz) { + RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); + for (String classPath : requestMapping.value()) { + for (Method method : clazz.getMethods()) { + if (!method.isAnnotationPresent(RequestMapping.class)) { + parseSubAnnotations(method, classPath); + continue; + } + requestMapping = method.getAnnotation(RequestMapping.class); + RequestMethod[] requestMethods = requestMapping.method(); + if (requestMethods.length == 0) { + requestMethods = new RequestMethod[1]; + requestMethods[0] = RequestMethod.GET; + } + for (String methodPath : requestMapping.value()) { + String urlKey = requestMethods[0].name() + REQUEST_PATH_SEPARATOR + classPath + methodPath; + addUrlAndMethodRelation(urlKey, requestMapping.params(), method); + } + } + } + } + + private void parseSubAnnotations(Method method, String classPath) { + + final GetMapping getMapping = method.getAnnotation(GetMapping.class); + final PostMapping postMapping = method.getAnnotation(PostMapping.class); + final PutMapping putMapping = method.getAnnotation(PutMapping.class); + final DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class); + final PatchMapping patchMapping = method.getAnnotation(PatchMapping.class); + + if (getMapping != null) { + put(RequestMethod.GET, classPath, getMapping.value(), getMapping.params(), method); + } + + if (postMapping != null) { + put(RequestMethod.POST, classPath, postMapping.value(), postMapping.params(), method); + } + + if (putMapping != null) { + put(RequestMethod.PUT, classPath, putMapping.value(), putMapping.params(), method); + } + + if (deleteMapping != null) { + put(RequestMethod.DELETE, classPath, deleteMapping.value(), deleteMapping.params(), method); + } + + if (patchMapping != null) { + put(RequestMethod.PATCH, classPath, patchMapping.value(), patchMapping.params(), method); + } + + } + + private void put(RequestMethod requestMethod, String classPath, String[] requestPaths, String[] requestParams, + Method method) { + if (ArrayUtils.isEmpty(requestPaths)) { + String urlKey = requestMethod.name() + REQUEST_PATH_SEPARATOR + classPath; + addUrlAndMethodRelation(urlKey, requestParams, method); + return; + } + for (String requestPath : requestPaths) { + String urlKey = requestMethod.name() + REQUEST_PATH_SEPARATOR + classPath + requestPath; + addUrlAndMethodRelation(urlKey, requestParams, method); + } + } + + private void addUrlAndMethodRelation(String urlKey, String[] requestParam, Method method) { + RequestMappingInfo requestMappingInfo = new RequestMappingInfo(); + requestMappingInfo.setPathRequestCondition(new PathRequestCondition(urlKey)); + requestMappingInfo.setParamRequestCondition(new ParamRequestCondition(requestParam)); + List requestMappingInfos = urlLookup.get(urlKey); + if (requestMappingInfos == null) { + urlLookup.putIfAbsent(urlKey, new ArrayList<>()); + requestMappingInfos = urlLookup.get(urlKey); + // For issue #4701. + String urlKeyBackup = urlKey + "/"; + urlLookup.putIfAbsent(urlKeyBackup, requestMappingInfos); + } + requestMappingInfos.add(requestMappingInfo); + methods.put(requestMappingInfo, method); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/RequestMappingInfo.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/RequestMappingInfo.java new file mode 100644 index 00000000..78dcc954 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/RequestMappingInfo.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2021 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.code; + +import com.alibaba.nacos.core.code.condition.ParamRequestCondition; +import com.alibaba.nacos.core.code.condition.PathRequestCondition; + +import java.util.Comparator; + +/** + * Request mapping information. to find the matched method by request + * + * @author horizonzy + * @since 1.3.2 + */ +public class RequestMappingInfo { + + private PathRequestCondition pathRequestCondition; + + private ParamRequestCondition paramRequestCondition; + + public ParamRequestCondition getParamRequestCondition() { + return paramRequestCondition; + } + + public void setParamRequestCondition(ParamRequestCondition paramRequestCondition) { + this.paramRequestCondition = paramRequestCondition; + } + + public void setPathRequestCondition(PathRequestCondition pathRequestCondition) { + this.pathRequestCondition = pathRequestCondition; + } + + @Override + public String toString() { + return "RequestMappingInfo{" + "pathRequestCondition=" + pathRequestCondition + ", paramRequestCondition=" + + paramRequestCondition + '}'; + } + + public static class RequestMappingInfoComparator implements Comparator { + + @Override + public int compare(RequestMappingInfo o1, RequestMappingInfo o2) { + return Integer.compare(o2.getParamRequestCondition().getExpressions().size(), + o1.getParamRequestCondition().getExpressions().size()); + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/SpringApplicationRunListener.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/SpringApplicationRunListener.java new file mode 100644 index 00000000..8e207187 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/SpringApplicationRunListener.java @@ -0,0 +1,108 @@ +/* + * 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.code; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.core.listener.NacosApplicationListener; +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.event.EventPublishingRunListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; + +import java.util.Collection; + +/** + * {@link org.springframework.boot.SpringApplicationRunListener} before {@link EventPublishingRunListener} execution. + * + * @author Mercy + * @since 0.2.2 + */ +public class SpringApplicationRunListener implements org.springframework.boot.SpringApplicationRunListener, Ordered { + + private final SpringApplication application; + + private final String[] args; + + Collection nacosApplicationListeners = NacosServiceLoader.load(NacosApplicationListener.class); + + public SpringApplicationRunListener(SpringApplication application, String[] args) { + this.application = application; + this.args = args; + } + + @Override + public void starting(ConfigurableBootstrapContext bootstrapContext) { + for (NacosApplicationListener nacosApplicationListener : nacosApplicationListeners) { + nacosApplicationListener.starting(); + } + } + + @Override + public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, + ConfigurableEnvironment environment) { + for (NacosApplicationListener nacosApplicationListener : nacosApplicationListeners) { + nacosApplicationListener.environmentPrepared(environment); + } + } + + @Override + public void contextPrepared(ConfigurableApplicationContext context) { + for (NacosApplicationListener nacosApplicationListener : nacosApplicationListeners) { + nacosApplicationListener.contextPrepared(context); + } + } + + @Override + public void contextLoaded(ConfigurableApplicationContext context) { + for (NacosApplicationListener nacosApplicationListener : nacosApplicationListeners) { + nacosApplicationListener.contextLoaded(context); + } + } + + @Override + public void started(ConfigurableApplicationContext context) { + for (NacosApplicationListener nacosApplicationListener : nacosApplicationListeners) { + nacosApplicationListener.started(context); + } + } + + @Override + public void running(ConfigurableApplicationContext context) { + for (NacosApplicationListener nacosApplicationListener : nacosApplicationListeners) { + nacosApplicationListener.running(context); + } + } + + @Override + public void failed(ConfigurableApplicationContext context, Throwable exception) { + for (NacosApplicationListener nacosApplicationListener : nacosApplicationListeners) { + nacosApplicationListener.failed(context, exception); + } + } + + /** + * Before {@link EventPublishingRunListener}. + * + * @return HIGHEST_PRECEDENCE + */ + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/StandaloneProfileApplicationListener.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/StandaloneProfileApplicationListener.java new file mode 100644 index 00000000..43ab8f36 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/StandaloneProfileApplicationListener.java @@ -0,0 +1,65 @@ +/* + * 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.code; + +import com.alibaba.nacos.sys.env.EnvUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Profile; +import org.springframework.core.PriorityOrdered; +import org.springframework.core.env.ConfigurableEnvironment; + +import java.util.Arrays; + +import static com.alibaba.nacos.sys.env.Constants.STANDALONE_MODE_PROPERTY_NAME; +import static com.alibaba.nacos.sys.env.Constants.STANDALONE_SPRING_PROFILE; + +/** + * Standalone {@link Profile} {@link ApplicationListener} for {@link ApplicationEnvironmentPreparedEvent}. + * + * @author Mercy + * @see ConfigurableEnvironment#addActiveProfile(String) + * @since 0.2.2 + */ +public class StandaloneProfileApplicationListener + implements ApplicationListener, PriorityOrdered { + + private static final Logger LOGGER = LoggerFactory.getLogger(StandaloneProfileApplicationListener.class); + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + + ConfigurableEnvironment environment = event.getEnvironment(); + + if (environment.getProperty(STANDALONE_MODE_PROPERTY_NAME, boolean.class, false)) { + environment.addActiveProfile(STANDALONE_SPRING_PROFILE); + } + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Spring Environment's active profiles : {} in standalone mode : {}", + Arrays.asList(environment.getActiveProfiles()), EnvUtil.getStandaloneMode()); + } + + } + + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/condition/ParamRequestCondition.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/condition/ParamRequestCondition.java new file mode 100644 index 00000000..0431242f --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/condition/ParamRequestCondition.java @@ -0,0 +1,114 @@ +/* + * 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.code.condition; + +import org.springframework.util.ObjectUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * request param info. {@link org.springframework.web.bind.annotation.RequestMapping#params()} + * + * @author horizonzy + * @since 1.3.2 + */ +public class ParamRequestCondition { + + private final Set expressions; + + public ParamRequestCondition(String... expressions) { + this.expressions = parseExpressions(expressions); + } + + private Set parseExpressions(String... params) { + if (ObjectUtils.isEmpty(params)) { + return Collections.emptySet(); + } + Set expressions = new LinkedHashSet<>(params.length); + for (String param : params) { + expressions.add(new ParamExpression(param)); + } + return expressions; + } + + public Set getExpressions() { + return expressions; + } + + public ParamRequestCondition getMatchingCondition(HttpServletRequest request) { + for (ParamExpression expression : this.expressions) { + if (!expression.match(request)) { + return null; + } + } + return this; + } + + @Override + public String toString() { + return "ParamRequestCondition{" + "expressions=" + expressions + '}'; + } + + static class ParamExpression { + + private final String name; + + private final String value; + + private final boolean isNegated; + + ParamExpression(String expression) { + int separator = expression.indexOf('='); + if (separator == -1) { + this.isNegated = expression.startsWith("!"); + this.name = isNegated ? expression.substring(1) : expression; + this.value = null; + } else { + this.isNegated = (separator > 0) && (expression.charAt(separator - 1) == '!'); + this.name = isNegated ? expression.substring(0, separator - 1) : expression.substring(0, separator); + this.value = expression.substring(separator + 1); + } + } + + public final boolean match(HttpServletRequest request) { + boolean isMatch; + if (this.value != null) { + isMatch = matchValue(request); + } else { + isMatch = matchName(request); + } + return this.isNegated != isMatch; + } + + private boolean matchName(HttpServletRequest request) { + return request.getParameterMap().containsKey(this.name); + } + + private boolean matchValue(HttpServletRequest request) { + return ObjectUtils.nullSafeEquals(this.value, request.getParameter(this.name)); + } + + @Override + public String toString() { + return "ParamExpression{" + "name='" + name + '\'' + ", value='" + value + '\'' + ", isNegated=" + isNegated + + '}'; + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/condition/PathRequestCondition.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/condition/PathRequestCondition.java new file mode 100644 index 00000000..33bd9deb --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/code/condition/PathRequestCondition.java @@ -0,0 +1,64 @@ +/* + * 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.code.condition; + +import static com.alibaba.nacos.sys.env.Constants.REQUEST_PATH_SEPARATOR; + +/** + * request path info. method:{@link org.springframework.web.bind.annotation.RequestMapping#method()} path: {@link + * org.springframework.web.bind.annotation.RequestMapping#value()} or {@link org.springframework.web.bind.annotation.RequestMapping#value()} + * + * @author horizonzy + * @since 1.3.2 + */ +public class PathRequestCondition { + + private final PathExpression pathExpression; + + public PathRequestCondition(String pathExpression) { + this.pathExpression = parseExpressions(pathExpression); + } + + private PathExpression parseExpressions(String pathExpression) { + String[] split = pathExpression.split(REQUEST_PATH_SEPARATOR); + String method = split[0]; + String path = split[1]; + return new PathExpression(method, path); + } + + @Override + public String toString() { + return "PathRequestCondition{" + "pathExpression=" + pathExpression + '}'; + } + + static class PathExpression { + + private final String method; + + private final String path; + + PathExpression(String method, String path) { + this.method = method; + this.path = path; + } + + @Override + public String toString() { + return "PathExpression{" + "method='" + method + '\'' + ", path='" + path + '\'' + '}'; + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/config/AbstractDynamicConfig.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/config/AbstractDynamicConfig.java new file mode 100644 index 00000000..26499556 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/config/AbstractDynamicConfig.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2020 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.config; + +import com.alibaba.nacos.common.event.ServerConfigChangeEvent; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.Subscriber; +import com.alibaba.nacos.core.utils.Loggers; + +/** + * Nacos abstract dynamic config. + * + * @author xiweng.yy + */ +public abstract class AbstractDynamicConfig extends Subscriber { + + private final String configName; + + protected AbstractDynamicConfig(String configName) { + this.configName = configName; + NotifyCenter.registerSubscriber(this); + } + + @Override + public void onEvent(ServerConfigChangeEvent event) { + resetConfig(); + } + + @Override + public Class subscribeType() { + return ServerConfigChangeEvent.class; + } + + protected void resetConfig() { + try { + getConfigFromEnv(); + Loggers.CORE.info("Get {} config from env, {}", configName, printConfig()); + } catch (Exception e) { + Loggers.CORE.warn("Upgrade {} config from env failed, will use old value", configName, e); + } + } + + /** + * Execute get config from env actually. + */ + protected abstract void getConfigFromEnv(); + + /** + * Print config content. + * + * @return config content + */ + protected abstract String printConfig(); +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/CoreOpsController.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/CoreOpsController.java new file mode 100644 index 00000000..1c9efb1a --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/CoreOpsController.java @@ -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.core.controller; + +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.model.RestResultUtils; +import com.alibaba.nacos.core.distributed.ProtocolManager; +import com.alibaba.nacos.core.distributed.id.IdGeneratorManager; +import com.alibaba.nacos.core.utils.Commons; +import com.alibaba.nacos.core.utils.Loggers; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +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.HttpServletResponse; +import java.util.HashMap; +import java.util.Map; + +/** + * Kernel modules operate and maintain HTTP interfaces. + * + * @author liaochuntao + */ +@RestController +@RequestMapping(Commons.NACOS_CORE_CONTEXT + "/ops") +public class CoreOpsController { + + private final ProtocolManager protocolManager; + + private final IdGeneratorManager idGeneratorManager; + + public CoreOpsController(ProtocolManager protocolManager, IdGeneratorManager idGeneratorManager) { + this.protocolManager = protocolManager; + this.idGeneratorManager = idGeneratorManager; + } + + // Temporarily overpassed the raft operations interface + // { + // "groupId": "xxx", + // "command": "transferLeader or doSnapshot or resetRaftCluster or removePeer" + // "value": "ip:{raft_port}" + // } + + @PostMapping(value = "/raft") + @Secured(action = ActionTypes.WRITE, resource = "nacos/admin") + public RestResult raftOps(@RequestBody Map commands) { + return protocolManager.getCpProtocol().execute(commands); + } + + /** + * Gets the current health of the ID generator. + * + * @return {@link RestResult} + */ + @GetMapping(value = "/idInfo") + public RestResult>> idInfo() { + Map> info = new HashMap<>(10); + idGeneratorManager.getGeneratorMap().forEach((resource, idGenerator) -> info.put(resource, idGenerator.info())); + return RestResultUtils.success(info); + } + + @PutMapping(value = "/log") + public String setLogLevel(@RequestParam String logName, @RequestParam String logLevel) { + Loggers.setLogLevel(logName, logLevel); + return HttpServletResponse.SC_OK + ""; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/NacosClusterController.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/NacosClusterController.java new file mode 100644 index 00000000..a17c9621 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/NacosClusterController.java @@ -0,0 +1,146 @@ +/* + * 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.controller; + +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.model.RestResultUtils; +import com.alibaba.nacos.common.utils.LoggerUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.NodeState; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.utils.Commons; +import com.alibaba.nacos.core.utils.Loggers; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Cluster communication interface. + * + * @author liaochuntao + */ +@RestController +@RequestMapping(Commons.NACOS_CORE_CONTEXT + "/cluster") +public class NacosClusterController { + + private final ServerMemberManager memberManager; + + public NacosClusterController(ServerMemberManager memberManager) { + this.memberManager = memberManager; + } + + @GetMapping(value = "/self") + public RestResult self() { + return RestResultUtils.success(memberManager.getSelf()); + } + + /** + * The console displays the list of cluster members. + * + * @param ipKeyWord search keyWord + * @return all members + */ + @GetMapping(value = "/nodes") + public RestResult> listNodes( + @RequestParam(value = "keyword", required = false) String ipKeyWord) { + Collection members = memberManager.allMembers(); + Collection result = new ArrayList<>(); + + members.stream().sorted().forEach(member -> { + if (StringUtils.isBlank(ipKeyWord)) { + result.add(member); + return; + } + final String address = member.getAddress(); + if (StringUtils.equals(address, ipKeyWord) || StringUtils.startsWith(address, ipKeyWord)) { + result.add(member); + } + }); + + return RestResultUtils.success(result); + } + + // The client can get all the nacos node information in the current + // cluster according to this interface + + @GetMapping(value = "/simple/nodes") + public RestResult> listSimpleNodes() { + return RestResultUtils.success(memberManager.getMemberAddressInfos()); + } + + @GetMapping("/health") + public RestResult getHealth() { + return RestResultUtils.success(memberManager.getSelf().getState().name()); + } + + /** + * Other nodes return their own metadata information. + * + * @param node {@link Member} + * @return {@link RestResult} + */ + @PostMapping(value = {"/report"}) + public RestResult report(@RequestBody Member node) { + if (!node.check()) { + return RestResultUtils.failedWithMsg(400, "Node information is illegal"); + } + LoggerUtils.printIfDebugEnabled(Loggers.CLUSTER, "node state report, receive info : {}", node); + node.setState(NodeState.UP); + node.setFailAccessCnt(0); + + boolean result = memberManager.update(node); + + return RestResultUtils.success(Boolean.toString(result)); + } + + /** + * Addressing mode switch. + * + * @param type member-lookup name + * @return {@link RestResult} + */ + @PostMapping(value = "/switch/lookup") + public RestResult switchLookup(@RequestParam(name = "type") String type) { + try { + memberManager.switchLookup(type); + return RestResultUtils.success(); + } catch (Throwable ex) { + return RestResultUtils.failed(ex.getMessage()); + } + } + + /** + * member leave. + * + * @param params member ip list, example [ip1:port1,ip2:port2,...] + * @return {@link RestResult} + * @throws Exception {@link Exception} + */ + @PostMapping("/server/leave") + public RestResult leave(@RequestBody Collection params, + @RequestParam(defaultValue = "true") Boolean notifyOtherMembers) throws Exception { + return RestResultUtils.failed(405, "/v1/core/cluster/server/leave API not allow to use temporarily."); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/ServerLoaderController.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/ServerLoaderController.java new file mode 100644 index 00000000..17cd2eea --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/ServerLoaderController.java @@ -0,0 +1,414 @@ +/* + * Copyright 1999-2020 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.controller; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.RequestCallBack; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.request.ServerLoaderInfoRequest; +import com.alibaba.nacos.api.remote.request.ServerReloadRequest; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.api.remote.response.ServerLoaderInfoResponse; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.MemberUtil; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.cluster.remote.ClusterRpcClientProxy; +import com.alibaba.nacos.core.remote.Connection; +import com.alibaba.nacos.core.remote.ConnectionManager; +import com.alibaba.nacos.core.remote.core.ServerLoaderInfoRequestHandler; +import com.alibaba.nacos.core.remote.core.ServerReloaderRequestHandler; +import com.alibaba.nacos.core.utils.Commons; +import com.alibaba.nacos.core.utils.RemoteUtils; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +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 java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * controller to control server loader. + * + * @author liuzunfei + * @version $Id: ServerLoaderController.java, v 0.1 2020年07月22日 4:28 PM liuzunfei Exp $ + */ +@RestController +@RequestMapping(Commons.NACOS_CORE_CONTEXT_V2 + "/loader") +public class ServerLoaderController { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServerLoaderController.class); + + private static final String X_REAL_IP = "X-Real-IP"; + + private static final String X_FORWARDED_FOR = "X-Forwarded-For"; + + private static final String X_FORWARDED_FOR_SPLIT_SYMBOL = ","; + + private static final String SUCCESS_RESULT = "Ok"; + + private static final String FAIL_RESULT = "Fail"; + + private static final String SDK_CONNECTION_COUNT_METRIC = "sdkConCount"; + + private final ConnectionManager connectionManager; + + private final ServerMemberManager serverMemberManager; + + private final ClusterRpcClientProxy clusterRpcClientProxy; + + private final ServerReloaderRequestHandler serverReloaderRequestHandler; + + private final ServerLoaderInfoRequestHandler serverLoaderInfoRequestHandler; + + public ServerLoaderController(ConnectionManager connectionManager, ServerMemberManager serverMemberManager, + ClusterRpcClientProxy clusterRpcClientProxy, ServerReloaderRequestHandler serverReloaderRequestHandler, + ServerLoaderInfoRequestHandler serverLoaderInfoRequestHandler) { + this.connectionManager = connectionManager; + this.serverMemberManager = serverMemberManager; + this.clusterRpcClientProxy = clusterRpcClientProxy; + this.serverReloaderRequestHandler = serverReloaderRequestHandler; + this.serverLoaderInfoRequestHandler = serverLoaderInfoRequestHandler; + } + + /** + * Get current clients. + * + * @return state json. + */ + @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.READ) + @GetMapping("/current") + public ResponseEntity> currentClients() { + Map stringConnectionMap = connectionManager.currentClients(); + return ResponseEntity.ok().body(stringConnectionMap); + } + + /** + * Get server state of current server. + * + * @return state json. + */ + @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.WRITE) + @GetMapping("/reloadCurrent") + public ResponseEntity reloadCount(@RequestParam Integer count, + @RequestParam(value = "redirectAddress", required = false) String redirectAddress) { + connectionManager.loadCount(count, redirectAddress); + return ResponseEntity.ok().body("success"); + } + + /** + * Get server state of current server. + * + * @return state json. + */ + @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.WRITE) + @GetMapping("/smartReloadCluster") + public ResponseEntity smartReload(HttpServletRequest request, + @RequestParam(value = "loaderFactor", required = false) String loaderFactorStr, + @RequestParam(value = "force", required = false) String force) { + + LOGGER.info("Smart reload request receive,requestIp={}", getRemoteIp(request)); + + Map serverLoadMetrics = getServerLoadMetrics(); + Object avgString = serverLoadMetrics.get("avg"); + List details = (List) serverLoadMetrics.get("detail"); + int avg = Integer.parseInt(avgString.toString()); + float loaderFactor = + StringUtils.isBlank(loaderFactorStr) ? RemoteUtils.LOADER_FACTOR : Float.parseFloat(loaderFactorStr); + int overLimitCount = (int) (avg * (1 + loaderFactor)); + int lowLimitCount = (int) (avg * (1 - loaderFactor)); + + List overLimitServer = new ArrayList<>(); + List lowLimitServer = new ArrayList<>(); + + for (ServerLoaderMetrics metrics : details) { + int sdkCount = Integer.parseInt(metrics.getMetric().get(SDK_CONNECTION_COUNT_METRIC)); + if (sdkCount > overLimitCount) { + overLimitServer.add(metrics); + } + if (sdkCount < lowLimitCount) { + lowLimitServer.add(metrics); + } + } + + // desc by sdkConCount + overLimitServer.sort((o1, o2) -> { + Integer sdkCount1 = Integer.valueOf(o1.getMetric().get(SDK_CONNECTION_COUNT_METRIC)); + Integer sdkCount2 = Integer.valueOf(o2.getMetric().get(SDK_CONNECTION_COUNT_METRIC)); + return sdkCount1.compareTo(sdkCount2) * -1; + }); + + LOGGER.info("Over load limit server list ={}", overLimitServer); + + //asc by sdkConCount + lowLimitServer.sort((o1, o2) -> { + Integer sdkCount1 = Integer.valueOf(o1.getMetric().get(SDK_CONNECTION_COUNT_METRIC)); + Integer sdkCount2 = Integer.valueOf(o2.getMetric().get(SDK_CONNECTION_COUNT_METRIC)); + return sdkCount1.compareTo(sdkCount2); + }); + + LOGGER.info("Low load limit server list ={}", lowLimitServer); + AtomicBoolean result = new AtomicBoolean(true); + + for (int i = 0; i < overLimitServer.size() & i < lowLimitServer.size(); i++) { + ServerReloadRequest serverLoaderInfoRequest = new ServerReloadRequest(); + serverLoaderInfoRequest.setReloadCount(overLimitCount); + serverLoaderInfoRequest.setReloadServer(lowLimitServer.get(i).address); + Member member = serverMemberManager.find(overLimitServer.get(i).address); + + LOGGER.info("Reload task submit ,fromServer ={},toServer={}, ", overLimitServer.get(i).address, + lowLimitServer.get(i).address); + + if (serverMemberManager.getSelf().equals(member)) { + try { + serverReloaderRequestHandler.handle(serverLoaderInfoRequest, new RequestMeta()); + } catch (NacosException e) { + LOGGER.error("Fail to loader self server", e); + result.set(false); + } + } else { + + try { + clusterRpcClientProxy.asyncRequest(member, serverLoaderInfoRequest, new RequestCallBack() { + @Override + public Executor getExecutor() { + return null; + } + + @Override + public long getTimeout() { + return 100L; + } + + @Override + public void onResponse(Response response) { + if (response == null || !response.isSuccess()) { + LOGGER.error("Fail to loader member={},response={}", member.getAddress(), response); + result.set(false); + + } + } + + @Override + public void onException(Throwable e) { + LOGGER.error("Fail to loader member={}", member.getAddress(), e); + result.set(false); + } + }); + } catch (NacosException e) { + LOGGER.error("Fail to loader member={}", member.getAddress(), e); + result.set(false); + } + } + } + + return ResponseEntity.ok().body(result.get() ? SUCCESS_RESULT : FAIL_RESULT); + } + + + /** + * Get server state of current server. + * + * @return state json. + */ + @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.WRITE) + @GetMapping("/reloadClient") + public ResponseEntity reloadSingle(@RequestParam String connectionId, + @RequestParam(value = "redirectAddress", required = false) String redirectAddress) { + connectionManager.loadSingle(connectionId, redirectAddress); + return ResponseEntity.ok().body("success"); + } + + /** + * Get current clients. + * + * @return state json. + */ + @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.READ) + @GetMapping("/cluster") + public ResponseEntity> loaderMetrics() { + + Map serverLoadMetrics = getServerLoadMetrics(); + + return ResponseEntity.ok().body(serverLoadMetrics); + } + + private Map getServerLoadMetrics() { + + List responseList = new LinkedList<>(); + + // default include self. + int memberSize = serverMemberManager.allMembersWithoutSelf().size(); + CountDownLatch countDownLatch = new CountDownLatch(memberSize); + for (Member member : serverMemberManager.allMembersWithoutSelf()) { + if (MemberUtil.isSupportedLongCon(member)) { + ServerLoaderInfoRequest serverLoaderInfoRequest = new ServerLoaderInfoRequest(); + + try { + clusterRpcClientProxy.asyncRequest(member, serverLoaderInfoRequest, new RequestCallBack() { + @Override + public Executor getExecutor() { + return null; + } + + @Override + public long getTimeout() { + return 200L; + } + + @Override + public void onResponse(Response response) { + if (response instanceof ServerLoaderInfoResponse) { + ServerLoaderMetrics metrics = new ServerLoaderMetrics(); + metrics.setAddress(member.getAddress()); + metrics.setMetric(((ServerLoaderInfoResponse) response).getLoaderMetrics()); + responseList.add(metrics); + } + countDownLatch.countDown(); + } + + @Override + public void onException(Throwable e) { + LOGGER.error("Get metrics fail,member={}", member.getAddress(), e); + countDownLatch.countDown(); + } + }); + } catch (NacosException e) { + LOGGER.error("Get metrics fail,member={}", member.getAddress(), e); + countDownLatch.countDown(); + } + } else { + countDownLatch.countDown(); + } + } + + try { + ServerLoaderInfoResponse handle = serverLoaderInfoRequestHandler + .handle(new ServerLoaderInfoRequest(), new RequestMeta()); + ServerLoaderMetrics metrics = new ServerLoaderMetrics(); + metrics.setAddress(serverMemberManager.getSelf().getAddress()); + metrics.setMetric(handle.getLoaderMetrics()); + responseList.add(metrics); + } catch (NacosException e) { + LOGGER.error("Get self metrics fail", e); + } + + try { + countDownLatch.await(1000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + LOGGER.warn("Get metrics timeout,metrics info may not complete."); + } + int max = 0; + int min = -1; + int total = 0; + + for (ServerLoaderMetrics serverLoaderMetrics : responseList) { + String sdkConCountStr = serverLoaderMetrics.getMetric().get("sdkConCount"); + + if (StringUtils.isNotBlank(sdkConCountStr)) { + int sdkConCount = Integer.parseInt(sdkConCountStr); + if (max == 0 || max < sdkConCount) { + max = sdkConCount; + } + if (min == -1 || sdkConCount < min) { + min = sdkConCount; + } + total += sdkConCount; + } + } + Map responseMap = new HashMap<>(9); + responseList.sort(Comparator.comparing(ServerLoaderMetrics::getAddress)); + responseMap.put("detail", responseList); + responseMap.put("memberCount", serverMemberManager.allMembers().size()); + responseMap.put("metricsCount", responseList.size()); + responseMap.put("completed", responseList.size() == serverMemberManager.allMembers().size()); + responseMap.put("max", max); + responseMap.put("min", min); + responseMap.put("avg", total / responseList.size()); + responseMap.put("threshold", total / responseList.size() * 1.1); + responseMap.put("total", total); + return responseMap; + + } + + class ServerLoaderMetrics { + + String address; + + Map metric = new HashMap<>(); + + /** + * Getter method for property address. + * + * @return property value of address + */ + public String getAddress() { + return address; + } + + /** + * Setter method for property address. + * + * @param address value to be assigned to property address + */ + public void setAddress(String address) { + this.address = address; + } + + /** + * Getter method for property metric. + * + * @return property value of metric + */ + public Map getMetric() { + return metric; + } + + /** + * Setter method for property metric. + * + * @param metric value to be assigned to property metric + */ + public void setMetric(Map metric) { + this.metric = metric; + } + } + + private static String getRemoteIp(HttpServletRequest request) { + String xForwardedFor = request.getHeader(X_FORWARDED_FOR); + if (!StringUtils.isBlank(xForwardedFor)) { + return xForwardedFor.split(X_FORWARDED_FOR_SPLIT_SYMBOL)[0].trim(); + } + String nginxHeader = request.getHeader(X_REAL_IP); + return StringUtils.isBlank(nginxHeader) ? request.getRemoteAddr() : nginxHeader; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/v2/CoreOpsV2Controller.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/v2/CoreOpsV2Controller.java new file mode 100644 index 00000000..b6f5f13d --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/v2/CoreOpsV2Controller.java @@ -0,0 +1,102 @@ +/* + * 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.controller.v2; + +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.common.Beta; +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.model.RestResultUtils; +import com.alibaba.nacos.core.distributed.ProtocolManager; +import com.alibaba.nacos.core.distributed.id.IdGeneratorManager; +import com.alibaba.nacos.core.model.request.LogUpdateRequest; +import com.alibaba.nacos.core.model.vo.IdGeneratorVO; +import com.alibaba.nacos.core.utils.Commons; +import com.alibaba.nacos.core.utils.Loggers; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Kernel modules operate and maintain HTTP interfaces v2. + * + * @author wuzhiguo + */ +@Beta +@RestController +@RequestMapping(Commons.NACOS_CORE_CONTEXT_V2 + "/ops") +public class CoreOpsV2Controller { + + private final ProtocolManager protocolManager; + + private final IdGeneratorManager idGeneratorManager; + + public CoreOpsV2Controller(ProtocolManager protocolManager, IdGeneratorManager idGeneratorManager) { + this.protocolManager = protocolManager; + this.idGeneratorManager = idGeneratorManager; + } + + // Temporarily overpassed the raft operations interface + // { + // "groupId": "xxx", + // "command": "transferLeader or doSnapshot or resetRaftCluster or removePeer" + // "value": "ip:{raft_port}" + // } + + @PostMapping(value = "/raft") + @Secured(action = ActionTypes.WRITE, resource = "nacos/admin") + public RestResult raftOps(@RequestBody Map commands) { + return protocolManager.getCpProtocol().execute(commands); + } + + /** + * Gets the current health of the ID generator. + * + * @return {@link RestResult} + */ + @GetMapping(value = "/ids") + public RestResult> ids() { + List result = new ArrayList<>(); + idGeneratorManager.getGeneratorMap().forEach((resource, idGenerator) -> { + IdGeneratorVO vo = new IdGeneratorVO(); + vo.setResource(resource); + + IdGeneratorVO.IdInfo info = new IdGeneratorVO.IdInfo(); + info.setCurrentId(idGenerator.currentId()); + info.setWorkerId(idGenerator.workerId()); + vo.setInfo(info); + + result.add(vo); + }); + + return RestResultUtils.success(result); + } + + @PutMapping(value = "/log") + public RestResult updateLog(@RequestBody LogUpdateRequest logUpdateRequest) { + Loggers.setLogLevel(logUpdateRequest.getLogName(), logUpdateRequest.getLogLevel()); + return RestResultUtils.success(); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/v2/NacosClusterV2Controller.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/v2/NacosClusterV2Controller.java new file mode 100644 index 00000000..4d1d4699 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/controller/v2/NacosClusterV2Controller.java @@ -0,0 +1,161 @@ +/* + * 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.controller.v2; + +import com.alibaba.nacos.common.Beta; +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.model.RestResultUtils; +import com.alibaba.nacos.common.utils.LoggerUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.NodeState; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.model.request.LookupUpdateRequest; +import com.alibaba.nacos.core.utils.Commons; +import com.alibaba.nacos.core.utils.Loggers; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; + +/** + * Cluster communication interface v2. + * + * @author wuzhiguo + */ +@Beta +@RestController +@RequestMapping(Commons.NACOS_CORE_CONTEXT_V2 + "/cluster") +public class NacosClusterV2Controller { + + private final ServerMemberManager memberManager; + + public NacosClusterV2Controller(ServerMemberManager memberManager) { + this.memberManager = memberManager; + } + + @GetMapping(value = "/nodes/self") + public RestResult self() { + return RestResultUtils.success(memberManager.getSelf()); + } + + /** + * The console displays the list of cluster members. + * + * @param address match address + * @param state match state + * @return members that matches condition + */ + @GetMapping(value = "/nodes") + public RestResult> listNodes(@RequestParam(value = "address", required = false) String address, + @RequestParam(value = "state", required = false) String state) { + + NodeState nodeState = null; + if (StringUtils.isNoneBlank(state)) { + try { + nodeState = NodeState.valueOf(state.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + return RestResultUtils.failedWithMsg(400, "Illegal state: " + state); + } + } + + Collection members = memberManager.allMembers(); + Collection result = new ArrayList<>(); + + for (Member member : members) { + if (StringUtils.isNoneBlank(address) && !StringUtils.startsWith(member.getAddress(), address)) { + continue; + } + + if (nodeState != null && member.getState() != nodeState) { + continue; + } + + result.add(member); + } + + return RestResultUtils.success(result); + } + + // The client can get all the nacos node information in the current + // cluster according to this interface + + /** + * Other nodes return their own metadata information. + * + * @param nodes List of {@link Member} + * @return {@link RestResult} + */ + @PutMapping(value = "/nodes") + public RestResult updateNodes(@RequestBody List nodes) { + for (Member node : nodes) { + if (!node.check()) { + LoggerUtils.printIfWarnEnabled(Loggers.CLUSTER, "node information is illegal, ignore node: {}", node); + continue; + } + + LoggerUtils.printIfDebugEnabled(Loggers.CLUSTER, "node state updating, node: {}", node); + node.setState(NodeState.UP); + node.setFailAccessCnt(0); + + boolean update = memberManager.update(node); + if (!update) { + LoggerUtils.printIfErrorEnabled(Loggers.CLUSTER, "node state update failed, node: {}", node); + } + } + + return RestResultUtils.success(); + } + + /** + * Addressing mode switch. + * + * @param request {@link LookupUpdateRequest} + * @return {@link RestResult} + */ + @PutMapping(value = "/lookup") + public RestResult updateLookup(@RequestBody LookupUpdateRequest request) { + try { + memberManager.switchLookup(request.getType()); + return RestResultUtils.success(); + } catch (Throwable ex) { + return RestResultUtils.failed(ex.getMessage()); + } + } + + /** + * member leave. + * + * @param addresses member ip list, example [ip1:port1,ip2:port2,...] + * @return {@link RestResult} + * @throws Exception throw {@link Exception} + */ + @DeleteMapping("/nodes") + public RestResult deleteNodes(@RequestParam("addresses") List addresses) throws Exception { + return RestResultUtils.failed(405, null, "DELETE /v2/core/cluster/nodes API not allow to use temporarily."); + + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/AbstractConsistencyProtocol.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/AbstractConsistencyProtocol.java new file mode 100644 index 00000000..250c12c7 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/AbstractConsistencyProtocol.java @@ -0,0 +1,55 @@ +/* + * 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.distributed; + +import com.alibaba.nacos.consistency.Config; +import com.alibaba.nacos.consistency.ConsistencyProtocol; +import com.alibaba.nacos.consistency.RequestProcessor; +import com.alibaba.nacos.consistency.ProtocolMetaData; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Consistent protocol base class. + * + * @author liaochuntao + */ +@SuppressWarnings("all") +public abstract class AbstractConsistencyProtocol + implements ConsistencyProtocol { + + protected final ProtocolMetaData metaData = new ProtocolMetaData(); + + protected Map processorMap = Collections.synchronizedMap(new HashMap<>()); + + public void loadLogProcessor(List logProcessors) { + logProcessors.forEach(logDispatcher -> processorMap.put(logDispatcher.group(), logDispatcher)); + } + + protected Map allProcessor() { + return processorMap; + } + + @Override + public ProtocolMetaData protocolMetaData() { + return this.metaData; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/ConsistencyConfiguration.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/ConsistencyConfiguration.java new file mode 100644 index 00000000..1748b3bd --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/ConsistencyConfiguration.java @@ -0,0 +1,57 @@ +/* + * 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.distributed; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.consistency.cp.CPProtocol; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.distributed.raft.JRaftProtocol; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.Callable; + +/** + * consistency configuration. + * + * @author liaochuntao + */ +@Configuration +public class ConsistencyConfiguration { + + @Bean(value = "strongAgreementProtocol") + public CPProtocol strongAgreementProtocol(ServerMemberManager memberManager) throws Exception { + final CPProtocol protocol = getProtocol(CPProtocol.class, () -> new JRaftProtocol(memberManager)); + return protocol; + } + + private T getProtocol(Class cls, Callable builder) throws Exception { + Collection protocols = NacosServiceLoader.load(cls); + + // Select only the first implementation + + Iterator iterator = protocols.iterator(); + if (iterator.hasNext()) { + return iterator.next(); + } else { + return builder.call(); + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/ProtocolExecutor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/ProtocolExecutor.java new file mode 100644 index 00000000..db0bb4e0 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/ProtocolExecutor.java @@ -0,0 +1,45 @@ +/* + * 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.distributed; + +import com.alibaba.nacos.common.executor.ExecutorFactory; +import com.alibaba.nacos.core.utils.ClassUtils; + +import java.util.concurrent.ExecutorService; + +/** + * ProtocolExecutor. + * + * @author liaochuntao + */ +public final class ProtocolExecutor { + + private static final ExecutorService CP_MEMBER_CHANGE_EXECUTOR = ExecutorFactory.Managed + .newSingleExecutorService(ClassUtils.getCanonicalName(ProtocolManager.class)); + + private static final ExecutorService AP_MEMBER_CHANGE_EXECUTOR = ExecutorFactory.Managed + .newSingleExecutorService(ClassUtils.getCanonicalName(ProtocolManager.class)); + + public static void cpMemberChange(Runnable runnable) { + CP_MEMBER_CHANGE_EXECUTOR.execute(runnable); + } + + public static void apMemberChange(Runnable runnable) { + AP_MEMBER_CHANGE_EXECUTOR.execute(runnable); + } + +} \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/ProtocolManager.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/ProtocolManager.java new file mode 100644 index 00000000..af518582 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/ProtocolManager.java @@ -0,0 +1,164 @@ +/* + * 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.distributed; + +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.consistency.Config; +import com.alibaba.nacos.consistency.ap.APProtocol; +import com.alibaba.nacos.consistency.cp.CPProtocol; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.MemberChangeListener; +import com.alibaba.nacos.core.cluster.MemberMetaDataConstants; +import com.alibaba.nacos.core.cluster.MemberUtil; +import com.alibaba.nacos.core.cluster.MembersChangeEvent; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.utils.ClassUtils; +import com.alibaba.nacos.sys.utils.ApplicationUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.stereotype.Component; + +import javax.annotation.PreDestroy; +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Conformance protocol management, responsible for managing the lifecycle of conformance protocols in Nacos. + * + * @author liaochuntao + */ +@SuppressWarnings("all") +@Component(value = "ProtocolManager") +public class ProtocolManager extends MemberChangeListener implements DisposableBean { + + private CPProtocol cpProtocol; + + private APProtocol apProtocol; + + private final ServerMemberManager memberManager; + + private boolean apInit = false; + + private boolean cpInit = false; + + private Set oldMembers; + + public ProtocolManager(ServerMemberManager memberManager) { + this.memberManager = memberManager; + NotifyCenter.registerSubscriber(this); + } + + public static Set toAPMembersInfo(Collection members) { + Set nodes = new HashSet<>(); + members.forEach(member -> nodes.add(member.getAddress())); + return nodes; + } + + public static Set toCPMembersInfo(Collection members) { + Set nodes = new HashSet<>(); + members.forEach(member -> { + final String ip = member.getIp(); + final int raftPort = MemberUtil.calculateRaftPort(member); + nodes.add(ip + ":" + raftPort); + }); + return nodes; + } + + public CPProtocol getCpProtocol() { + synchronized (this) { + if (!cpInit) { + initCPProtocol(); + cpInit = true; + } + } + return cpProtocol; + } + + public APProtocol getApProtocol() { + synchronized (this) { + if (!apInit) { + initAPProtocol(); + apInit = true; + } + } + return apProtocol; + } + + @PreDestroy + @Override + public void destroy() { + if (Objects.nonNull(apProtocol)) { + apProtocol.shutdown(); + } + if (Objects.nonNull(cpProtocol)) { + cpProtocol.shutdown(); + } + } + + private void initAPProtocol() { + ApplicationUtils.getBeanIfExist(APProtocol.class, protocol -> { + Class configType = ClassUtils.resolveGenericType(protocol.getClass()); + Config config = (Config) ApplicationUtils.getBean(configType); + injectMembers4AP(config); + protocol.init(config); + ProtocolManager.this.apProtocol = protocol; + }); + } + + private void initCPProtocol() { + ApplicationUtils.getBeanIfExist(CPProtocol.class, protocol -> { + Class configType = ClassUtils.resolveGenericType(protocol.getClass()); + Config config = (Config) ApplicationUtils.getBean(configType); + injectMembers4CP(config); + protocol.init(config); + ProtocolManager.this.cpProtocol = protocol; + }); + } + + private void injectMembers4CP(Config config) { + final Member selfMember = memberManager.getSelf(); + final String self = selfMember.getIp() + ":" + Integer + .parseInt(String.valueOf(selfMember.getExtendVal(MemberMetaDataConstants.RAFT_PORT))); + Set others = toCPMembersInfo(memberManager.allMembers()); + config.setMembers(self, others); + } + + private void injectMembers4AP(Config config) { + final String self = memberManager.getSelf().getAddress(); + Set others = toAPMembersInfo(memberManager.allMembers()); + config.setMembers(self, others); + } + + @Override + public void onEvent(MembersChangeEvent event) { + // Here, the sequence of node change events is very important. For example, + // node change event A occurs at time T1, and node change event B occurs at + // time T2 after a period of time. + // (T1 < T2) + // Node change events between different protocols should not block each other. + // and we use a single thread pool to inform the consistency layer of node changes, + // to avoid multiple tasks simultaneously carrying out the consistency layer of + // node changes operation + if (Objects.nonNull(apProtocol)) { + ProtocolExecutor.apMemberChange(() -> apProtocol.memberChange(toAPMembersInfo(event.getMembers()))); + } + if (Objects.nonNull(cpProtocol)) { + ProtocolExecutor.cpMemberChange(() -> cpProtocol.memberChange(toCPMembersInfo(event.getMembers()))); + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/DistroConfig.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/DistroConfig.java new file mode 100644 index 00000000..09b4145d --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/DistroConfig.java @@ -0,0 +1,137 @@ +/* + * 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.distributed.distro; + +import com.alibaba.nacos.core.config.AbstractDynamicConfig; +import com.alibaba.nacos.sys.env.EnvUtil; + +/** + * Distro configuration. + * + * @author xiweng.yy + */ +public class DistroConfig extends AbstractDynamicConfig { + + private static final String DISTRO = "Distro"; + + private static final DistroConfig INSTANCE = new DistroConfig(); + + private long syncDelayMillis = DistroConstants.DEFAULT_DATA_SYNC_DELAY_MILLISECONDS; + + private long syncTimeoutMillis = DistroConstants.DEFAULT_DATA_SYNC_TIMEOUT_MILLISECONDS; + + private long syncRetryDelayMillis = DistroConstants.DEFAULT_DATA_SYNC_RETRY_DELAY_MILLISECONDS; + + private long verifyIntervalMillis = DistroConstants.DEFAULT_DATA_VERIFY_INTERVAL_MILLISECONDS; + + private long verifyTimeoutMillis = DistroConstants.DEFAULT_DATA_VERIFY_TIMEOUT_MILLISECONDS; + + private long loadDataRetryDelayMillis = DistroConstants.DEFAULT_DATA_LOAD_RETRY_DELAY_MILLISECONDS; + + private long loadDataTimeoutMillis = DistroConstants.DEFAULT_DATA_LOAD_TIMEOUT_MILLISECONDS; + + private DistroConfig() { + super(DISTRO); + resetConfig(); + } + + @Override + protected void getConfigFromEnv() { + syncDelayMillis = EnvUtil.getProperty(DistroConstants.DATA_SYNC_DELAY_MILLISECONDS, Long.class, + DistroConstants.DEFAULT_DATA_SYNC_DELAY_MILLISECONDS); + syncTimeoutMillis = EnvUtil.getProperty(DistroConstants.DATA_SYNC_TIMEOUT_MILLISECONDS, Long.class, + DistroConstants.DEFAULT_DATA_SYNC_TIMEOUT_MILLISECONDS); + syncRetryDelayMillis = EnvUtil.getProperty(DistroConstants.DATA_SYNC_RETRY_DELAY_MILLISECONDS, Long.class, + DistroConstants.DEFAULT_DATA_SYNC_RETRY_DELAY_MILLISECONDS); + verifyIntervalMillis = EnvUtil.getProperty(DistroConstants.DATA_VERIFY_INTERVAL_MILLISECONDS, Long.class, + DistroConstants.DEFAULT_DATA_VERIFY_INTERVAL_MILLISECONDS); + verifyTimeoutMillis = EnvUtil.getProperty(DistroConstants.DATA_VERIFY_TIMEOUT_MILLISECONDS, Long.class, + DistroConstants.DEFAULT_DATA_VERIFY_TIMEOUT_MILLISECONDS); + loadDataRetryDelayMillis = EnvUtil.getProperty(DistroConstants.DATA_LOAD_RETRY_DELAY_MILLISECONDS, Long.class, + DistroConstants.DEFAULT_DATA_LOAD_RETRY_DELAY_MILLISECONDS); + loadDataTimeoutMillis = EnvUtil.getProperty(DistroConstants.DATA_LOAD_TIMEOUT_MILLISECONDS, Long.class, + DistroConstants.DEFAULT_DATA_LOAD_TIMEOUT_MILLISECONDS); + } + + public static DistroConfig getInstance() { + return INSTANCE; + } + + public long getSyncDelayMillis() { + return syncDelayMillis; + } + + public void setSyncDelayMillis(long syncDelayMillis) { + this.syncDelayMillis = syncDelayMillis; + } + + public long getSyncTimeoutMillis() { + return syncTimeoutMillis; + } + + public void setSyncTimeoutMillis(long syncTimeoutMillis) { + this.syncTimeoutMillis = syncTimeoutMillis; + } + + public long getSyncRetryDelayMillis() { + return syncRetryDelayMillis; + } + + public void setSyncRetryDelayMillis(long syncRetryDelayMillis) { + this.syncRetryDelayMillis = syncRetryDelayMillis; + } + + public long getVerifyIntervalMillis() { + return verifyIntervalMillis; + } + + public void setVerifyIntervalMillis(long verifyIntervalMillis) { + this.verifyIntervalMillis = verifyIntervalMillis; + } + + public long getVerifyTimeoutMillis() { + return verifyTimeoutMillis; + } + + public void setVerifyTimeoutMillis(long verifyTimeoutMillis) { + this.verifyTimeoutMillis = verifyTimeoutMillis; + } + + public long getLoadDataRetryDelayMillis() { + return loadDataRetryDelayMillis; + } + + public void setLoadDataRetryDelayMillis(long loadDataRetryDelayMillis) { + this.loadDataRetryDelayMillis = loadDataRetryDelayMillis; + } + + public long getLoadDataTimeoutMillis() { + return loadDataTimeoutMillis; + } + + public void setLoadDataTimeoutMillis(long loadDataTimeoutMillis) { + this.loadDataTimeoutMillis = loadDataTimeoutMillis; + } + + @Override + protected String printConfig() { + return "DistroConfig{" + "syncDelayMillis=" + syncDelayMillis + ", syncTimeoutMillis=" + syncTimeoutMillis + + ", syncRetryDelayMillis=" + syncRetryDelayMillis + ", verifyIntervalMillis=" + verifyIntervalMillis + + ", verifyTimeoutMillis=" + verifyTimeoutMillis + ", loadDataRetryDelayMillis=" + + loadDataRetryDelayMillis + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/DistroConstants.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/DistroConstants.java new file mode 100644 index 00000000..1a0b3e1b --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/DistroConstants.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2020 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.distributed.distro; + +/** + * Distro constants. + * + * @author xiweng.yy + */ +public class DistroConstants { + + public static final String DATA_SYNC_DELAY_MILLISECONDS = "nacos.core.protocol.distro.data.sync.delayMs"; + + public static final long DEFAULT_DATA_SYNC_DELAY_MILLISECONDS = 1000L; + + public static final String DATA_SYNC_TIMEOUT_MILLISECONDS = "nacos.core.protocol.distro.data.sync.timeoutMs"; + + public static final long DEFAULT_DATA_SYNC_TIMEOUT_MILLISECONDS = 3000L; + + public static final String DATA_SYNC_RETRY_DELAY_MILLISECONDS = "nacos.core.protocol.distro.data.sync.retryDelayMs"; + + public static final long DEFAULT_DATA_SYNC_RETRY_DELAY_MILLISECONDS = 3000L; + + public static final String DATA_VERIFY_INTERVAL_MILLISECONDS = "nacos.core.protocol.distro.data.verify.intervalMs"; + + public static final long DEFAULT_DATA_VERIFY_INTERVAL_MILLISECONDS = 5000L; + + public static final String DATA_VERIFY_TIMEOUT_MILLISECONDS = "nacos.core.protocol.distro.data.verify.timeoutMs"; + + public static final long DEFAULT_DATA_VERIFY_TIMEOUT_MILLISECONDS = 3000L; + + public static final String DATA_LOAD_RETRY_DELAY_MILLISECONDS = "nacos.core.protocol.distro.data.load.retryDelayMs"; + + public static final long DEFAULT_DATA_LOAD_RETRY_DELAY_MILLISECONDS = 30000L; + + public static final String DATA_LOAD_TIMEOUT_MILLISECONDS = "nacos.core.protocol.distro.data.load.timeoutMs"; + + public static final long DEFAULT_DATA_LOAD_TIMEOUT_MILLISECONDS = 30000L; + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/DistroProtocol.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/DistroProtocol.java new file mode 100644 index 00000000..5eb24c5b --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/DistroProtocol.java @@ -0,0 +1,227 @@ +/* + * 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.distributed.distro; + +import com.alibaba.nacos.consistency.DataOperation; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.distributed.distro.component.DistroCallback; +import com.alibaba.nacos.core.distributed.distro.component.DistroComponentHolder; +import com.alibaba.nacos.core.distributed.distro.component.DistroDataProcessor; +import com.alibaba.nacos.core.distributed.distro.component.DistroDataStorage; +import com.alibaba.nacos.core.distributed.distro.component.DistroTransportAgent; +import com.alibaba.nacos.core.distributed.distro.entity.DistroData; +import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; +import com.alibaba.nacos.core.distributed.distro.task.DistroTaskEngineHolder; +import com.alibaba.nacos.core.distributed.distro.task.delay.DistroDelayTask; +import com.alibaba.nacos.core.distributed.distro.task.load.DistroLoadDataTask; +import com.alibaba.nacos.core.distributed.distro.task.verify.DistroVerifyTimedTask; +import com.alibaba.nacos.core.utils.GlobalExecutor; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.springframework.stereotype.Component; + +/** + * Distro protocol. + * + * @author xiweng.yy + */ +@Component +public class DistroProtocol { + + private final ServerMemberManager memberManager; + + private final DistroComponentHolder distroComponentHolder; + + private final DistroTaskEngineHolder distroTaskEngineHolder; + + private volatile boolean isInitialized = false; + + public DistroProtocol(ServerMemberManager memberManager, DistroComponentHolder distroComponentHolder, + DistroTaskEngineHolder distroTaskEngineHolder) { + this.memberManager = memberManager; + this.distroComponentHolder = distroComponentHolder; + this.distroTaskEngineHolder = distroTaskEngineHolder; + startDistroTask(); + } + + private void startDistroTask() { + if (EnvUtil.getStandaloneMode()) { + isInitialized = true; + return; + } + startVerifyTask(); + startLoadTask(); + } + + private void startLoadTask() { + DistroCallback loadCallback = new DistroCallback() { + @Override + public void onSuccess() { + isInitialized = true; + } + + @Override + public void onFailed(Throwable throwable) { + isInitialized = false; + } + }; + GlobalExecutor.submitLoadDataTask( + new DistroLoadDataTask(memberManager, distroComponentHolder, DistroConfig.getInstance(), loadCallback)); + } + + private void startVerifyTask() { + GlobalExecutor.schedulePartitionDataTimedSync(new DistroVerifyTimedTask(memberManager, distroComponentHolder, + distroTaskEngineHolder.getExecuteWorkersManager()), + DistroConfig.getInstance().getVerifyIntervalMillis()); + } + + public boolean isInitialized() { + return isInitialized; + } + + /** + * Start to sync by configured delay. + * + * @param distroKey distro key of sync data + * @param action the action of data operation + */ + public void sync(DistroKey distroKey, DataOperation action) { + sync(distroKey, action, DistroConfig.getInstance().getSyncDelayMillis()); + } + + /** + * Start to sync data to all remote server. + * + * @param distroKey distro key of sync data + * @param action the action of data operation + * @param delay delay time for sync + */ + public void sync(DistroKey distroKey, DataOperation action, long delay) { + for (Member each : memberManager.allMembersWithoutSelf()) { + syncToTarget(distroKey, action, each.getAddress(), delay); + } + } + + /** + * Start to sync to target server. + * + * @param distroKey distro key of sync data + * @param action the action of data operation + * @param targetServer target server + * @param delay delay time for sync + */ + public void syncToTarget(DistroKey distroKey, DataOperation action, String targetServer, long delay) { + DistroKey distroKeyWithTarget = new DistroKey(distroKey.getResourceKey(), distroKey.getResourceType(), + targetServer); + DistroDelayTask distroDelayTask = new DistroDelayTask(distroKeyWithTarget, action, delay); + distroTaskEngineHolder.getDelayTaskExecuteEngine().addTask(distroKeyWithTarget, distroDelayTask); + if (Loggers.DISTRO.isDebugEnabled()) { + Loggers.DISTRO.debug("[DISTRO-SCHEDULE] {} to {}", distroKey, targetServer); + } + } + + /** + * Query data from specified server. + * + * @param distroKey data key + * @return data + */ + public DistroData queryFromRemote(DistroKey distroKey) { + if (null == distroKey.getTargetServer()) { + Loggers.DISTRO.warn("[DISTRO] Can't query data from empty server"); + return null; + } + String resourceType = distroKey.getResourceType(); + DistroTransportAgent transportAgent = distroComponentHolder.findTransportAgent(resourceType); + if (null == transportAgent) { + Loggers.DISTRO.warn("[DISTRO] Can't find transport agent for key {}", resourceType); + return null; + } + return transportAgent.getData(distroKey, distroKey.getTargetServer()); + } + + /** + * Receive synced distro data, find processor to process. + * + * @param distroData Received data + * @return true if handle receive data successfully, otherwise false + */ + public boolean onReceive(DistroData distroData) { + Loggers.DISTRO.info("[DISTRO] Receive distro data type: {}, key: {}", distroData.getType(), + distroData.getDistroKey()); + String resourceType = distroData.getDistroKey().getResourceType(); + DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType); + if (null == dataProcessor) { + Loggers.DISTRO.warn("[DISTRO] Can't find data process for received data {}", resourceType); + return false; + } + return dataProcessor.processData(distroData); + } + + /** + * Receive verify data, find processor to process. + * + * @param distroData verify data + * @param sourceAddress source server address, might be get data from source server + * @return true if verify data successfully, otherwise false + */ + public boolean onVerify(DistroData distroData, String sourceAddress) { + if (Loggers.DISTRO.isDebugEnabled()) { + Loggers.DISTRO.debug("[DISTRO] Receive verify data type: {}, key: {}", distroData.getType(), + distroData.getDistroKey()); + } + String resourceType = distroData.getDistroKey().getResourceType(); + DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType); + if (null == dataProcessor) { + Loggers.DISTRO.warn("[DISTRO] Can't find verify data process for received data {}", resourceType); + return false; + } + return dataProcessor.processVerifyData(distroData, sourceAddress); + } + + /** + * Query data of input distro key. + * + * @param distroKey key of data + * @return data + */ + public DistroData onQuery(DistroKey distroKey) { + String resourceType = distroKey.getResourceType(); + DistroDataStorage distroDataStorage = distroComponentHolder.findDataStorage(resourceType); + if (null == distroDataStorage) { + Loggers.DISTRO.warn("[DISTRO] Can't find data storage for received key {}", resourceType); + return new DistroData(distroKey, new byte[0]); + } + return distroDataStorage.getDistroData(distroKey); + } + + /** + * Query all datum snapshot. + * + * @param type datum type + * @return all datum snapshot + */ + public DistroData onSnapshot(String type) { + DistroDataStorage distroDataStorage = distroComponentHolder.findDataStorage(type); + if (null == distroDataStorage) { + Loggers.DISTRO.warn("[DISTRO] Can't find data storage for received key {}", type); + return new DistroData(new DistroKey("snapshot", type), new byte[0]); + } + return distroDataStorage.getDatumSnapshot(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroCallback.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroCallback.java new file mode 100644 index 00000000..7f2456f7 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroCallback.java @@ -0,0 +1,37 @@ +/* + * 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.distributed.distro.component; + +/** + * Distro callback. + * + * @author xiweng.yy + */ +public interface DistroCallback { + + /** + * Callback when distro task execute successfully. + */ + void onSuccess(); + + /** + * Callback when distro task execute failed. + * + * @param throwable throwable if execute failed caused by exception + */ + void onFailed(Throwable throwable); +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroComponentHolder.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroComponentHolder.java new file mode 100644 index 00000000..499965d1 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroComponentHolder.java @@ -0,0 +1,76 @@ +/* + * 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.distributed.distro.component; + +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Distro component holder. + * + * @author xiweng.yy + */ +@Component +public class DistroComponentHolder { + + private final Map transportAgentMap = new HashMap<>(); + + private final Map dataStorageMap = new HashMap<>(); + + private final Map failedTaskHandlerMap = new HashMap<>(); + + private final Map dataProcessorMap = new HashMap<>(); + + public DistroTransportAgent findTransportAgent(String type) { + return transportAgentMap.get(type); + } + + public void registerTransportAgent(String type, DistroTransportAgent transportAgent) { + transportAgentMap.put(type, transportAgent); + } + + public DistroDataStorage findDataStorage(String type) { + return dataStorageMap.get(type); + } + + public void registerDataStorage(String type, DistroDataStorage dataStorage) { + dataStorageMap.put(type, dataStorage); + } + + public Set getDataStorageTypes() { + return dataStorageMap.keySet(); + } + + public DistroFailedTaskHandler findFailedTaskHandler(String type) { + return failedTaskHandlerMap.get(type); + } + + public void registerFailedTaskHandler(String type, DistroFailedTaskHandler failedTaskHandler) { + failedTaskHandlerMap.put(type, failedTaskHandler); + } + + public void registerDataProcessor(DistroDataProcessor dataProcessor) { + dataProcessorMap.putIfAbsent(dataProcessor.processType(), dataProcessor); + } + + public DistroDataProcessor findDataProcessor(String processType) { + return dataProcessorMap.get(processType); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroDataProcessor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroDataProcessor.java new file mode 100644 index 00000000..635d19aa --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroDataProcessor.java @@ -0,0 +1,59 @@ +/* + * 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.distributed.distro.component; + +import com.alibaba.nacos.core.distributed.distro.entity.DistroData; + +/** + * Distro data processor. + * + * @author xiweng.yy + */ +public interface DistroDataProcessor { + + /** + * Process type of this processor. + * + * @return type of this processor + */ + String processType(); + + /** + * Process received data. + * + * @param distroData received data + * @return true if process data successfully, otherwise false + */ + boolean processData(DistroData distroData); + + /** + * Process received verify data. + * + * @param distroData verify data + * @param sourceAddress source server address, might be get data from source server + * @return true if the data is available, otherwise false + */ + boolean processVerifyData(DistroData distroData, String sourceAddress); + + /** + * Process snapshot data. + * + * @param distroData snapshot data + * @return true if process data successfully, otherwise false + */ + boolean processSnapshot(DistroData distroData); +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroDataStorage.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroDataStorage.java new file mode 100644 index 00000000..4789b2de --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroDataStorage.java @@ -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.core.distributed.distro.component; + +import com.alibaba.nacos.core.distributed.distro.entity.DistroData; +import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; + +import java.util.List; + +/** + * Distro data storage. + * + * @author xiweng.yy + */ +public interface DistroDataStorage { + + /** + * Set this distro data storage has finished initial step. + */ + void finishInitial(); + + /** + * Whether this distro data is finished initial. + * + *

If not finished, this data storage should not send verify data to other node. + * + * @return {@code true} if finished, otherwise false + */ + boolean isFinishInitial(); + + /** + * Get distro datum. + * + * @param distroKey key of distro datum + * @return need to sync datum + */ + DistroData getDistroData(DistroKey distroKey); + + /** + * Get all distro datum snapshot. + * + * @return all datum + */ + DistroData getDatumSnapshot(); + + /** + * Get verify datum. + * + * @return verify datum + */ + List getVerifyData(); +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroFailedTaskHandler.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroFailedTaskHandler.java new file mode 100644 index 00000000..2bc4f886 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroFailedTaskHandler.java @@ -0,0 +1,36 @@ +/* + * 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.distributed.distro.component; + +import com.alibaba.nacos.consistency.DataOperation; +import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; + +/** + * Distro failed task handler. + * + * @author xiweng.yy + */ +public interface DistroFailedTaskHandler { + + /** + * Build retry task when distro task execute failed. + * + * @param distroKey distro key of failed task + * @param action action of task + */ + void retry(DistroKey distroKey, DataOperation action); +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroTransportAgent.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroTransportAgent.java new file mode 100644 index 00000000..0db97e4d --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/component/DistroTransportAgent.java @@ -0,0 +1,92 @@ +/* + * 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.distributed.distro.component; + +import com.alibaba.nacos.core.distributed.distro.entity.DistroData; +import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; + +/** + * Distro transport agent. + * + * @author xiweng.yy + */ +public interface DistroTransportAgent { + + /** + * Whether support transport data with callback. + * + * @return true if support, otherwise false + */ + boolean supportCallbackTransport(); + + /** + * Sync data. + * + * @param data data + * @param targetServer target server + * @return true is sync successfully, otherwise false + */ + boolean syncData(DistroData data, String targetServer); + + /** + * Sync data with callback. + * + * @param data data + * @param targetServer target server + * @param callback callback + * @throws UnsupportedOperationException if method supportCallbackTransport is false, should throw {@code + * UnsupportedOperationException} + */ + void syncData(DistroData data, String targetServer, DistroCallback callback); + + /** + * Sync verify data. + * + * @param verifyData verify data + * @param targetServer target server + * @return true is verify successfully, otherwise false + */ + boolean syncVerifyData(DistroData verifyData, String targetServer); + + /** + * Sync verify data. + * + * @param verifyData verify data + * @param targetServer target server + * @param callback callback + * @throws UnsupportedOperationException if method supportCallbackTransport is false, should throw {@code + * UnsupportedOperationException} + */ + void syncVerifyData(DistroData verifyData, String targetServer, DistroCallback callback); + + /** + * get Data from target server. + * + * @param key key of data + * @param targetServer target server + * @return distro data + */ + DistroData getData(DistroKey key, String targetServer); + + /** + * Get all datum snapshot from target server. + * + * @param targetServer target server. + * @return distro data + */ + DistroData getDatumSnapshot(String targetServer); +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/entity/DistroData.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/entity/DistroData.java new file mode 100644 index 00000000..dc362ede --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/entity/DistroData.java @@ -0,0 +1,65 @@ +/* + * 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.distributed.distro.entity; + +import com.alibaba.nacos.consistency.DataOperation; + +/** + * Distro data. + * + * @author xiweng.yy + */ +public class DistroData { + + private DistroKey distroKey; + + private DataOperation type; + + private byte[] content; + + public DistroData() { + } + + public DistroData(DistroKey distroKey, byte[] content) { + this.distroKey = distroKey; + this.content = content; + } + + public DistroKey getDistroKey() { + return distroKey; + } + + public void setDistroKey(DistroKey distroKey) { + this.distroKey = distroKey; + } + + public DataOperation getType() { + return type; + } + + public void setType(DataOperation type) { + this.type = type; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/entity/DistroKey.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/entity/DistroKey.java new file mode 100644 index 00000000..2ae92efd --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/entity/DistroKey.java @@ -0,0 +1,95 @@ +/* + * 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.distributed.distro.entity; + +import java.util.Objects; + +/** + * Distro key. + * + * @author xiweng.yy + */ +public class DistroKey { + + private String resourceKey; + + private String resourceType; + + private String targetServer; + + public DistroKey() { + } + + public DistroKey(String resourceKey, String resourceType) { + this.resourceKey = resourceKey; + this.resourceType = resourceType; + } + + public DistroKey(String resourceKey, String resourceType, String targetServer) { + this.resourceKey = resourceKey; + this.resourceType = resourceType; + this.targetServer = targetServer; + } + + public String getResourceKey() { + return resourceKey; + } + + public void setResourceKey(String resourceKey) { + this.resourceKey = resourceKey; + } + + public String getResourceType() { + return resourceType; + } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public String getTargetServer() { + return targetServer; + } + + public void setTargetServer(String targetServer) { + this.targetServer = targetServer; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DistroKey distroKey = (DistroKey) o; + return Objects.equals(resourceKey, distroKey.resourceKey) && Objects + .equals(resourceType, distroKey.resourceType) && Objects.equals(targetServer, distroKey.targetServer); + } + + @Override + public int hashCode() { + return Objects.hash(resourceKey, resourceType, targetServer); + } + + @Override + public String toString() { + return "DistroKey{" + "resourceKey='" + resourceKey + '\'' + ", resourceType='" + resourceType + '\'' + + ", targetServer='" + targetServer + '\'' + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/exception/DistroException.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/exception/DistroException.java new file mode 100644 index 00000000..24c2c8f3 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/exception/DistroException.java @@ -0,0 +1,40 @@ +/* + * 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.distributed.distro.exception; + +/** + * Distro exception. + * + * @author xiweng.yy + */ +public class DistroException extends RuntimeException { + + private static final long serialVersionUID = 1711141952413139786L; + + public DistroException(String message) { + super(message); + } + + public DistroException(String message, Throwable cause) { + super(message, cause); + } + + @Override + public String getMessage() { + return "[DISTRO-EXCEPTION]" + super.getMessage(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/monitor/DistroRecord.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/monitor/DistroRecord.java new file mode 100644 index 00000000..9ceb0663 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/monitor/DistroRecord.java @@ -0,0 +1,80 @@ +/* + * Copyright 1999-2020 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.distributed.distro.monitor; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Distro record for monitor. + * + * @author xiweng.yy + */ +public class DistroRecord { + + private final String type; + + private final AtomicLong totalSyncCount; + + private final AtomicLong successfulSyncCount; + + private final AtomicLong failedSyncCount; + + private final AtomicInteger failedVerifyCount; + + public DistroRecord(String type) { + this.type = type; + this.totalSyncCount = new AtomicLong(); + this.successfulSyncCount = new AtomicLong(); + this.failedSyncCount = new AtomicLong(); + this.failedVerifyCount = new AtomicInteger(); + } + + public String getType() { + return type; + } + + public void syncSuccess() { + successfulSyncCount.incrementAndGet(); + totalSyncCount.incrementAndGet(); + } + + public void syncFail() { + failedSyncCount.incrementAndGet(); + totalSyncCount.incrementAndGet(); + } + + public void verifyFail() { + failedVerifyCount.incrementAndGet(); + } + + public long getTotalSyncCount() { + return totalSyncCount.get(); + } + + public long getSuccessfulSyncCount() { + return successfulSyncCount.get(); + } + + public long getFailedSyncCount() { + return failedSyncCount.get(); + } + + public int getFailedVerifyCount() { + return failedVerifyCount.get(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/monitor/DistroRecordsHolder.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/monitor/DistroRecordsHolder.java new file mode 100644 index 00000000..42748f87 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/monitor/DistroRecordsHolder.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2020 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.distributed.distro.monitor; + +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Distro records holder. + * + * @author xiweng.yy + */ +public class DistroRecordsHolder { + + private static final DistroRecordsHolder INSTANCE = new DistroRecordsHolder(); + + private final ConcurrentMap distroRecords; + + private DistroRecordsHolder() { + distroRecords = new ConcurrentHashMap<>(); + } + + public static DistroRecordsHolder getInstance() { + return INSTANCE; + } + + public Optional getRecordIfExist(String type) { + return Optional.ofNullable(distroRecords.get(type)); + } + + public DistroRecord getRecord(String type) { + distroRecords.computeIfAbsent(type, s -> new DistroRecord(type)); + return distroRecords.get(type); + } + + public long getTotalSyncCount() { + final AtomicLong result = new AtomicLong(); + distroRecords.forEach((s, distroRecord) -> result.addAndGet(distroRecord.getTotalSyncCount())); + return result.get(); + } + + public long getSuccessfulSyncCount() { + final AtomicLong result = new AtomicLong(); + distroRecords.forEach((s, distroRecord) -> result.addAndGet(distroRecord.getSuccessfulSyncCount())); + return result.get(); + } + + public long getFailedSyncCount() { + final AtomicLong result = new AtomicLong(); + distroRecords.forEach((s, distroRecord) -> result.addAndGet(distroRecord.getFailedSyncCount())); + return result.get(); + } + + public int getFailedVerifyCount() { + final AtomicInteger result = new AtomicInteger(); + distroRecords.forEach((s, distroRecord) -> result.addAndGet(distroRecord.getFailedVerifyCount())); + return result.get(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/DistroTaskEngineHolder.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/DistroTaskEngineHolder.java new file mode 100644 index 00000000..8cfe9ea0 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/DistroTaskEngineHolder.java @@ -0,0 +1,54 @@ +/* + * 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.distributed.distro.task; + +import com.alibaba.nacos.common.task.NacosTaskProcessor; +import com.alibaba.nacos.core.distributed.distro.component.DistroComponentHolder; +import com.alibaba.nacos.core.distributed.distro.task.delay.DistroDelayTaskExecuteEngine; +import com.alibaba.nacos.core.distributed.distro.task.delay.DistroDelayTaskProcessor; +import com.alibaba.nacos.core.distributed.distro.task.execute.DistroExecuteTaskExecuteEngine; +import org.springframework.stereotype.Component; + +/** + * Distro task engine holder. + * + * @author xiweng.yy + */ +@Component +public class DistroTaskEngineHolder { + + private final DistroDelayTaskExecuteEngine delayTaskExecuteEngine = new DistroDelayTaskExecuteEngine(); + + private final DistroExecuteTaskExecuteEngine executeWorkersManager = new DistroExecuteTaskExecuteEngine(); + + public DistroTaskEngineHolder(DistroComponentHolder distroComponentHolder) { + DistroDelayTaskProcessor defaultDelayTaskProcessor = new DistroDelayTaskProcessor(this, distroComponentHolder); + delayTaskExecuteEngine.setDefaultTaskProcessor(defaultDelayTaskProcessor); + } + + public DistroDelayTaskExecuteEngine getDelayTaskExecuteEngine() { + return delayTaskExecuteEngine; + } + + public DistroExecuteTaskExecuteEngine getExecuteWorkersManager() { + return executeWorkersManager; + } + + public void registerNacosTaskProcessor(Object key, NacosTaskProcessor nacosTaskProcessor) { + this.delayTaskExecuteEngine.addProcessor(key, nacosTaskProcessor); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/delay/DistroDelayTask.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/delay/DistroDelayTask.java new file mode 100644 index 00000000..17785374 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/delay/DistroDelayTask.java @@ -0,0 +1,72 @@ +/* + * 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.distributed.distro.task.delay; + +import com.alibaba.nacos.common.task.AbstractDelayTask; +import com.alibaba.nacos.consistency.DataOperation; +import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; + +/** + * Distro delay task. + * + * @author xiweng.yy + */ +public class DistroDelayTask extends AbstractDelayTask { + + private final DistroKey distroKey; + + private DataOperation action; + + private long createTime; + + public DistroDelayTask(DistroKey distroKey, long delayTime) { + this(distroKey, DataOperation.CHANGE, delayTime); + } + + public DistroDelayTask(DistroKey distroKey, DataOperation action, long delayTime) { + this.distroKey = distroKey; + this.action = action; + this.createTime = System.currentTimeMillis(); + setLastProcessTime(createTime); + setTaskInterval(delayTime); + } + + public DistroKey getDistroKey() { + return distroKey; + } + + public DataOperation getAction() { + return action; + } + + public long getCreateTime() { + return createTime; + } + + @Override + public void merge(AbstractDelayTask task) { + if (!(task instanceof DistroDelayTask)) { + return; + } + DistroDelayTask oldTask = (DistroDelayTask) task; + if (!action.equals(oldTask.getAction()) && createTime < oldTask.getCreateTime()) { + action = oldTask.getAction(); + createTime = oldTask.getCreateTime(); + } + setLastProcessTime(oldTask.getLastProcessTime()); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/delay/DistroDelayTaskExecuteEngine.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/delay/DistroDelayTaskExecuteEngine.java new file mode 100644 index 00000000..a672b92e --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/delay/DistroDelayTaskExecuteEngine.java @@ -0,0 +1,50 @@ +/* + * 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.distributed.distro.task.delay; + +import com.alibaba.nacos.common.task.NacosTaskProcessor; +import com.alibaba.nacos.common.task.engine.NacosDelayTaskExecuteEngine; +import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; +import com.alibaba.nacos.core.utils.Loggers; + +/** + * Distro delay task execute engine. + * + * @author xiweng.yy + */ +public class DistroDelayTaskExecuteEngine extends NacosDelayTaskExecuteEngine { + + public DistroDelayTaskExecuteEngine() { + super(DistroDelayTaskExecuteEngine.class.getName(), Loggers.DISTRO); + } + + @Override + public void addProcessor(Object key, NacosTaskProcessor taskProcessor) { + Object actualKey = getActualKey(key); + super.addProcessor(actualKey, taskProcessor); + } + + @Override + public NacosTaskProcessor getProcessor(Object key) { + Object actualKey = getActualKey(key); + return super.getProcessor(actualKey); + } + + private Object getActualKey(Object key) { + return key instanceof DistroKey ? ((DistroKey) key).getResourceType() : key; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/delay/DistroDelayTaskProcessor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/delay/DistroDelayTaskProcessor.java new file mode 100644 index 00000000..fabcdea3 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/delay/DistroDelayTaskProcessor.java @@ -0,0 +1,65 @@ +/* + * 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.distributed.distro.task.delay; + +import com.alibaba.nacos.common.task.NacosTask; +import com.alibaba.nacos.common.task.NacosTaskProcessor; +import com.alibaba.nacos.core.distributed.distro.component.DistroComponentHolder; +import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; +import com.alibaba.nacos.core.distributed.distro.task.DistroTaskEngineHolder; +import com.alibaba.nacos.core.distributed.distro.task.execute.DistroSyncChangeTask; +import com.alibaba.nacos.core.distributed.distro.task.execute.DistroSyncDeleteTask; + +/** + * Distro delay task processor. + * + * @author xiweng.yy + */ +public class DistroDelayTaskProcessor implements NacosTaskProcessor { + + private final DistroTaskEngineHolder distroTaskEngineHolder; + + private final DistroComponentHolder distroComponentHolder; + + public DistroDelayTaskProcessor(DistroTaskEngineHolder distroTaskEngineHolder, + DistroComponentHolder distroComponentHolder) { + this.distroTaskEngineHolder = distroTaskEngineHolder; + this.distroComponentHolder = distroComponentHolder; + } + + @Override + public boolean process(NacosTask task) { + if (!(task instanceof DistroDelayTask)) { + return true; + } + DistroDelayTask distroDelayTask = (DistroDelayTask) task; + DistroKey distroKey = distroDelayTask.getDistroKey(); + switch (distroDelayTask.getAction()) { + case DELETE: + DistroSyncDeleteTask syncDeleteTask = new DistroSyncDeleteTask(distroKey, distroComponentHolder); + distroTaskEngineHolder.getExecuteWorkersManager().addTask(distroKey, syncDeleteTask); + return true; + case CHANGE: + case ADD: + DistroSyncChangeTask syncChangeTask = new DistroSyncChangeTask(distroKey, distroComponentHolder); + distroTaskEngineHolder.getExecuteWorkersManager().addTask(distroKey, syncChangeTask); + return true; + default: + return false; + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/AbstractDistroExecuteTask.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/AbstractDistroExecuteTask.java new file mode 100644 index 00000000..8804aed2 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/AbstractDistroExecuteTask.java @@ -0,0 +1,138 @@ +/* + * 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.distributed.distro.task.execute; + +import com.alibaba.nacos.common.task.AbstractExecuteTask; +import com.alibaba.nacos.consistency.DataOperation; +import com.alibaba.nacos.core.distributed.distro.component.DistroCallback; +import com.alibaba.nacos.core.distributed.distro.component.DistroComponentHolder; +import com.alibaba.nacos.core.distributed.distro.component.DistroFailedTaskHandler; +import com.alibaba.nacos.core.distributed.distro.component.DistroTransportAgent; +import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; +import com.alibaba.nacos.core.distributed.distro.monitor.DistroRecord; +import com.alibaba.nacos.core.distributed.distro.monitor.DistroRecordsHolder; +import com.alibaba.nacos.core.utils.Loggers; + +/** + * Abstract distro execute task. + * + * @author xiweng.yy + */ +public abstract class AbstractDistroExecuteTask extends AbstractExecuteTask { + + private final DistroKey distroKey; + + private final DistroComponentHolder distroComponentHolder; + + protected AbstractDistroExecuteTask(DistroKey distroKey, DistroComponentHolder distroComponentHolder) { + this.distroKey = distroKey; + this.distroComponentHolder = distroComponentHolder; + } + + protected DistroKey getDistroKey() { + return distroKey; + } + + protected DistroComponentHolder getDistroComponentHolder() { + return distroComponentHolder; + } + + @Override + public void run() { + String type = getDistroKey().getResourceType(); + DistroTransportAgent transportAgent = distroComponentHolder.findTransportAgent(type); + if (null == transportAgent) { + Loggers.DISTRO.warn("No found transport agent for type [{}]", type); + return; + } + Loggers.DISTRO.info("[DISTRO-START] {}", toString()); + if (transportAgent.supportCallbackTransport()) { + doExecuteWithCallback(new DistroExecuteCallback()); + } else { + executeDistroTask(); + } + } + + private void executeDistroTask() { + try { + boolean result = doExecute(); + if (!result) { + handleFailedTask(); + } + Loggers.DISTRO.info("[DISTRO-END] {} result: {}", toString(), result); + } catch (Exception e) { + Loggers.DISTRO.warn("[DISTRO] Sync data change failed.", e); + handleFailedTask(); + } + } + + /** + * Get {@link DataOperation} for current task. + * + * @return data operation + */ + protected abstract DataOperation getDataOperation(); + + /** + * Do execute for different sub class. + * + * @return result of execute + */ + protected abstract boolean doExecute(); + + /** + * Do execute with callback for different sub class. + * + * @param callback callback + */ + protected abstract void doExecuteWithCallback(DistroCallback callback); + + /** + * Handle failed task. + */ + protected void handleFailedTask() { + String type = getDistroKey().getResourceType(); + DistroFailedTaskHandler failedTaskHandler = distroComponentHolder.findFailedTaskHandler(type); + if (null == failedTaskHandler) { + Loggers.DISTRO.warn("[DISTRO] Can't find failed task for type {}, so discarded", type); + return; + } + failedTaskHandler.retry(getDistroKey(), getDataOperation()); + } + + private class DistroExecuteCallback implements DistroCallback { + + @Override + public void onSuccess() { + DistroRecord distroRecord = DistroRecordsHolder.getInstance().getRecord(getDistroKey().getResourceType()); + distroRecord.syncSuccess(); + Loggers.DISTRO.info("[DISTRO-END] {} result: true", getDistroKey().toString()); + } + + @Override + public void onFailed(Throwable throwable) { + DistroRecord distroRecord = DistroRecordsHolder.getInstance().getRecord(getDistroKey().getResourceType()); + distroRecord.syncFail(); + if (null == throwable) { + Loggers.DISTRO.info("[DISTRO-END] {} result: false", getDistroKey().toString()); + } else { + Loggers.DISTRO.warn("[DISTRO] Sync data change failed. key: {}", getDistroKey().toString(), throwable); + } + handleFailedTask(); + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/DistroExecuteTaskExecuteEngine.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/DistroExecuteTaskExecuteEngine.java new file mode 100644 index 00000000..1883dac2 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/DistroExecuteTaskExecuteEngine.java @@ -0,0 +1,32 @@ +/* + * 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.distributed.distro.task.execute; + +import com.alibaba.nacos.common.task.engine.NacosExecuteTaskExecuteEngine; +import com.alibaba.nacos.core.utils.Loggers; + +/** + * Distro execute task execute engine. + * + * @author xiweng.yy + */ +public class DistroExecuteTaskExecuteEngine extends NacosExecuteTaskExecuteEngine { + + public DistroExecuteTaskExecuteEngine() { + super(DistroExecuteTaskExecuteEngine.class.getSimpleName(), Loggers.DISTRO); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/DistroSyncChangeTask.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/DistroSyncChangeTask.java new file mode 100644 index 00000000..b1c8c026 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/DistroSyncChangeTask.java @@ -0,0 +1,80 @@ +/* + * 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.distributed.distro.task.execute; + +import com.alibaba.nacos.consistency.DataOperation; +import com.alibaba.nacos.core.distributed.distro.component.DistroCallback; +import com.alibaba.nacos.core.distributed.distro.component.DistroComponentHolder; +import com.alibaba.nacos.core.distributed.distro.entity.DistroData; +import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; +import com.alibaba.nacos.core.utils.Loggers; + +/** + * Distro sync change task. + * + * @author xiweng.yy + */ +public class DistroSyncChangeTask extends AbstractDistroExecuteTask { + + private static final DataOperation OPERATION = DataOperation.CHANGE; + + public DistroSyncChangeTask(DistroKey distroKey, DistroComponentHolder distroComponentHolder) { + super(distroKey, distroComponentHolder); + } + + @Override + protected DataOperation getDataOperation() { + return OPERATION; + } + + @Override + protected boolean doExecute() { + String type = getDistroKey().getResourceType(); + DistroData distroData = getDistroData(type); + if (null == distroData) { + Loggers.DISTRO.warn("[DISTRO] {} with null data to sync, skip", toString()); + return true; + } + return getDistroComponentHolder().findTransportAgent(type) + .syncData(distroData, getDistroKey().getTargetServer()); + } + + @Override + protected void doExecuteWithCallback(DistroCallback callback) { + String type = getDistroKey().getResourceType(); + DistroData distroData = getDistroData(type); + if (null == distroData) { + Loggers.DISTRO.warn("[DISTRO] {} with null data to sync, skip", toString()); + return; + } + getDistroComponentHolder().findTransportAgent(type) + .syncData(distroData, getDistroKey().getTargetServer(), callback); + } + + @Override + public String toString() { + return "DistroSyncChangeTask for " + getDistroKey().toString(); + } + + private DistroData getDistroData(String type) { + DistroData result = getDistroComponentHolder().findDataStorage(type).getDistroData(getDistroKey()); + if (null != result) { + result.setType(OPERATION); + } + return result; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/DistroSyncDeleteTask.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/DistroSyncDeleteTask.java new file mode 100644 index 00000000..0bab753c --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/execute/DistroSyncDeleteTask.java @@ -0,0 +1,67 @@ +/* + * 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.distributed.distro.task.execute; + +import com.alibaba.nacos.consistency.DataOperation; +import com.alibaba.nacos.core.distributed.distro.component.DistroCallback; +import com.alibaba.nacos.core.distributed.distro.component.DistroComponentHolder; +import com.alibaba.nacos.core.distributed.distro.entity.DistroData; +import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; + +/** + * Distro sync delete task. + * + * @author xiweng.yy + */ +public class DistroSyncDeleteTask extends AbstractDistroExecuteTask { + + private static final DataOperation OPERATION = DataOperation.DELETE; + + public DistroSyncDeleteTask(DistroKey distroKey, DistroComponentHolder distroComponentHolder) { + super(distroKey, distroComponentHolder); + } + + @Override + protected DataOperation getDataOperation() { + return OPERATION; + } + + @Override + protected boolean doExecute() { + String type = getDistroKey().getResourceType(); + DistroData distroData = new DistroData(); + distroData.setDistroKey(getDistroKey()); + distroData.setType(OPERATION); + return getDistroComponentHolder().findTransportAgent(type) + .syncData(distroData, getDistroKey().getTargetServer()); + } + + @Override + protected void doExecuteWithCallback(DistroCallback callback) { + String type = getDistroKey().getResourceType(); + DistroData distroData = new DistroData(); + distroData.setDistroKey(getDistroKey()); + distroData.setType(OPERATION); + getDistroComponentHolder().findTransportAgent(type) + .syncData(distroData, getDistroKey().getTargetServer(), callback); + } + + @Override + public String toString() { + return "DistroSyncDeleteTask for " + getDistroKey().toString(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/load/DistroLoadDataTask.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/load/DistroLoadDataTask.java new file mode 100644 index 00000000..29885daf --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/load/DistroLoadDataTask.java @@ -0,0 +1,138 @@ +/* + * 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.distributed.distro.task.load; + +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.distributed.distro.DistroConfig; +import com.alibaba.nacos.core.distributed.distro.component.DistroCallback; +import com.alibaba.nacos.core.distributed.distro.component.DistroComponentHolder; +import com.alibaba.nacos.core.distributed.distro.component.DistroDataProcessor; +import com.alibaba.nacos.core.distributed.distro.component.DistroTransportAgent; +import com.alibaba.nacos.core.distributed.distro.entity.DistroData; +import com.alibaba.nacos.core.utils.GlobalExecutor; +import com.alibaba.nacos.core.utils.Loggers; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * Distro load data task. + * + * @author xiweng.yy + */ +public class DistroLoadDataTask implements Runnable { + + private final ServerMemberManager memberManager; + + private final DistroComponentHolder distroComponentHolder; + + private final DistroConfig distroConfig; + + private final DistroCallback loadCallback; + + private final Map loadCompletedMap; + + public DistroLoadDataTask(ServerMemberManager memberManager, DistroComponentHolder distroComponentHolder, + DistroConfig distroConfig, DistroCallback loadCallback) { + this.memberManager = memberManager; + this.distroComponentHolder = distroComponentHolder; + this.distroConfig = distroConfig; + this.loadCallback = loadCallback; + loadCompletedMap = new HashMap<>(1); + } + + @Override + public void run() { + try { + load(); + if (!checkCompleted()) { + GlobalExecutor.submitLoadDataTask(this, distroConfig.getLoadDataRetryDelayMillis()); + } else { + loadCallback.onSuccess(); + Loggers.DISTRO.info("[DISTRO-INIT] load snapshot data success"); + } + } catch (Exception e) { + loadCallback.onFailed(e); + Loggers.DISTRO.error("[DISTRO-INIT] load snapshot data failed. ", e); + } + } + + private void load() throws Exception { + while (memberManager.allMembersWithoutSelf().isEmpty()) { + Loggers.DISTRO.info("[DISTRO-INIT] waiting server list init..."); + TimeUnit.SECONDS.sleep(1); + } + while (distroComponentHolder.getDataStorageTypes().isEmpty()) { + Loggers.DISTRO.info("[DISTRO-INIT] waiting distro data storage register..."); + TimeUnit.SECONDS.sleep(1); + } + for (String each : distroComponentHolder.getDataStorageTypes()) { + if (!loadCompletedMap.containsKey(each) || !loadCompletedMap.get(each)) { + loadCompletedMap.put(each, loadAllDataSnapshotFromRemote(each)); + } + } + } + + private boolean loadAllDataSnapshotFromRemote(String resourceType) { + DistroTransportAgent transportAgent = distroComponentHolder.findTransportAgent(resourceType); + DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType); + if (null == transportAgent || null == dataProcessor) { + Loggers.DISTRO.warn("[DISTRO-INIT] Can't find component for type {}, transportAgent: {}, dataProcessor: {}", + resourceType, transportAgent, dataProcessor); + return false; + } + for (Member each : memberManager.allMembersWithoutSelf()) { + long startTime = System.currentTimeMillis(); + try { + Loggers.DISTRO.info("[DISTRO-INIT] load snapshot {} from {}", resourceType, each.getAddress()); + DistroData distroData = transportAgent.getDatumSnapshot(each.getAddress()); + Loggers.DISTRO.info("[DISTRO-INIT] it took {} ms to load snapshot {} from {} and snapshot size is {}.", + System.currentTimeMillis() - startTime, resourceType, each.getAddress(), + getDistroDataLength(distroData)); + boolean result = dataProcessor.processSnapshot(distroData); + Loggers.DISTRO + .info("[DISTRO-INIT] load snapshot {} from {} result: {}", resourceType, each.getAddress(), + result); + if (result) { + distroComponentHolder.findDataStorage(resourceType).finishInitial(); + return true; + } + } catch (Exception e) { + Loggers.DISTRO.error("[DISTRO-INIT] load snapshot {} from {} failed.", resourceType, each.getAddress(), e); + } + } + return false; + } + + private static int getDistroDataLength(DistroData distroData) { + return distroData != null && distroData.getContent() != null ? distroData.getContent().length : 0; + } + + private boolean checkCompleted() { + if (distroComponentHolder.getDataStorageTypes().size() != loadCompletedMap.size()) { + return false; + } + for (Boolean each : loadCompletedMap.values()) { + if (!each) { + return false; + } + } + return true; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/verify/DistroVerifyExecuteTask.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/verify/DistroVerifyExecuteTask.java new file mode 100644 index 00000000..6ca0cc79 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/verify/DistroVerifyExecuteTask.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2020 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.distributed.distro.task.verify; + +import com.alibaba.nacos.common.task.AbstractExecuteTask; +import com.alibaba.nacos.core.distributed.distro.component.DistroCallback; +import com.alibaba.nacos.core.distributed.distro.component.DistroTransportAgent; +import com.alibaba.nacos.core.distributed.distro.entity.DistroData; +import com.alibaba.nacos.core.distributed.distro.monitor.DistroRecord; +import com.alibaba.nacos.core.distributed.distro.monitor.DistroRecordsHolder; +import com.alibaba.nacos.core.utils.Loggers; + +import java.util.List; + +/** + * Execute distro verify task. + * + * @author xiweng.yy + */ +public class DistroVerifyExecuteTask extends AbstractExecuteTask { + + private final DistroTransportAgent transportAgent; + + private final List verifyData; + + private final String targetServer; + + private final String resourceType; + + public DistroVerifyExecuteTask(DistroTransportAgent transportAgent, List verifyData, + String targetServer, String resourceType) { + this.transportAgent = transportAgent; + this.verifyData = verifyData; + this.targetServer = targetServer; + this.resourceType = resourceType; + } + + @Override + public void run() { + for (DistroData each : verifyData) { + try { + if (transportAgent.supportCallbackTransport()) { + doSyncVerifyDataWithCallback(each); + } else { + doSyncVerifyData(each); + } + } catch (Exception e) { + Loggers.DISTRO + .error("[DISTRO-FAILED] verify data for type {} to {} failed.", resourceType, targetServer, e); + } + } + } + + private void doSyncVerifyDataWithCallback(DistroData data) { + transportAgent.syncVerifyData(data, targetServer, new DistroVerifyCallback()); + } + + private void doSyncVerifyData(DistroData data) { + transportAgent.syncVerifyData(data, targetServer); + } + + private class DistroVerifyCallback implements DistroCallback { + + @Override + public void onSuccess() { + if (Loggers.DISTRO.isDebugEnabled()) { + Loggers.DISTRO.debug("[DISTRO] verify data for type {} to {} success", resourceType, targetServer); + } + } + + @Override + public void onFailed(Throwable throwable) { + DistroRecord distroRecord = DistroRecordsHolder.getInstance().getRecord(resourceType); + distroRecord.verifyFail(); + if (Loggers.DISTRO.isDebugEnabled()) { + Loggers.DISTRO + .debug("[DISTRO-FAILED] verify data for type {} to {} failed.", resourceType, targetServer, + throwable); + } + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/verify/DistroVerifyTimedTask.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/verify/DistroVerifyTimedTask.java new file mode 100644 index 00000000..3cd609bc --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/distro/task/verify/DistroVerifyTimedTask.java @@ -0,0 +1,85 @@ +/* + * 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.distributed.distro.task.verify; + +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.distributed.distro.component.DistroComponentHolder; +import com.alibaba.nacos.core.distributed.distro.component.DistroDataStorage; +import com.alibaba.nacos.core.distributed.distro.component.DistroTransportAgent; +import com.alibaba.nacos.core.distributed.distro.entity.DistroData; +import com.alibaba.nacos.core.distributed.distro.task.execute.DistroExecuteTaskExecuteEngine; +import com.alibaba.nacos.core.utils.Loggers; + +import java.util.List; + +/** + * Timed to start distro verify task. + * + * @author xiweng.yy + */ +public class DistroVerifyTimedTask implements Runnable { + + private final ServerMemberManager serverMemberManager; + + private final DistroComponentHolder distroComponentHolder; + + private final DistroExecuteTaskExecuteEngine executeTaskExecuteEngine; + + public DistroVerifyTimedTask(ServerMemberManager serverMemberManager, DistroComponentHolder distroComponentHolder, + DistroExecuteTaskExecuteEngine executeTaskExecuteEngine) { + this.serverMemberManager = serverMemberManager; + this.distroComponentHolder = distroComponentHolder; + this.executeTaskExecuteEngine = executeTaskExecuteEngine; + } + + @Override + public void run() { + try { + List targetServer = serverMemberManager.allMembersWithoutSelf(); + if (Loggers.DISTRO.isDebugEnabled()) { + Loggers.DISTRO.debug("server list is: {}", targetServer); + } + for (String each : distroComponentHolder.getDataStorageTypes()) { + verifyForDataStorage(each, targetServer); + } + } catch (Exception e) { + Loggers.DISTRO.error("[DISTRO-FAILED] verify task failed.", e); + } + } + + private void verifyForDataStorage(String type, List targetServer) { + DistroDataStorage dataStorage = distroComponentHolder.findDataStorage(type); + if (!dataStorage.isFinishInitial()) { + Loggers.DISTRO.warn("data storage {} has not finished initial step, do not send verify data", + dataStorage.getClass().getSimpleName()); + return; + } + List verifyData = dataStorage.getVerifyData(); + if (null == verifyData || verifyData.isEmpty()) { + return; + } + for (Member member : targetServer) { + DistroTransportAgent agent = distroComponentHolder.findTransportAgent(type); + if (null == agent) { + continue; + } + executeTaskExecuteEngine.addTask(member.getAddress() + type, + new DistroVerifyExecuteTask(agent, verifyData, member.getAddress(), type)); + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/id/IdGeneratorManager.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/id/IdGeneratorManager.java new file mode 100644 index 00000000..a4861a75 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/id/IdGeneratorManager.java @@ -0,0 +1,89 @@ +/* + * 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.distributed.id; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.consistency.IdGenerator; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * Id generator manager. + * + * @author liaochuntao + */ +@Component +public class IdGeneratorManager { + + private final Map generatorMap = new ConcurrentHashMap<>(); + + private final Function supplier; + + public IdGeneratorManager() { + this.supplier = s -> { + IdGenerator generator; + Collection idGenerators = NacosServiceLoader.load(IdGenerator.class); + Iterator iterator = idGenerators.iterator(); + if (iterator.hasNext()) { + generator = iterator.next(); + } else { + generator = new SnowFlowerIdGenerator(); + } + generator.init(); + return generator; + }; + } + + public void register(String resource) { + generatorMap.computeIfAbsent(resource, s -> supplier.apply(resource)); + } + + /** + * Register resources that need to use the ID generator. + * + * @param resources resource name list + */ + public void register(String... resources) { + for (String resource : resources) { + generatorMap.computeIfAbsent(resource, s -> supplier.apply(resource)); + } + } + + /** + * request next id by resource name. + * + * @param resource resource name + * @return id + */ + public long nextId(String resource) { + if (generatorMap.containsKey(resource)) { + return generatorMap.get(resource).nextId(); + } + throw new NoSuchElementException( + "The resource is not registered with the distributed " + "ID resource for the time being."); + } + + public Map getGeneratorMap() { + return generatorMap; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/id/SnowFlowerIdGenerator.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/id/SnowFlowerIdGenerator.java new file mode 100644 index 00000000..ae084127 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/id/SnowFlowerIdGenerator.java @@ -0,0 +1,189 @@ +/* + * 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.distributed.id; + +import com.alibaba.nacos.consistency.IdGenerator; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.utils.InetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * copy from http://www.cluozy.com/home/hexo/2018/08/11/shariding-JDBC-snowflake/. + * + * WorkerId generation policy: Calculate the InetAddress hashcode + * + *

The repeat rate of the dataCenterId, the value of the maximum dataCenterId times the time of each Raft election. + * The + * time for raft to select the master is generally measured in seconds. If the interval of an election is 5 seconds, it + * will take 150 seconds for the DataCenterId to be repeated. This is still based on the situation that the new master + * needs to be selected after each election of the Leader + * + * @author liaochuntao + */ +@SuppressWarnings("all") +public class SnowFlowerIdGenerator implements IdGenerator { + + private static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; + + /** + * Start time intercept (2018-08-05 08:34) + */ + public static final long EPOCH = 1533429240000L; + + private static final Logger logger = LoggerFactory.getLogger(SnowFlowerIdGenerator.class); + + // the bits of sequence + private static final long SEQUENCE_BITS = 12L; + + // the bits of workerId + private static final long WORKER_ID_BITS = 10L; + + // the mask of sequence (111111111111B = 4095) + private static final long SEQUENCE_MASK = 4095L; + + // the left shift bits of workerId equals 12 bits + private static final long WORKER_ID_LEFT_SHIFT_BITS = 12L; + + // the left shift bits of timestamp equals 22 bits (WORKER_ID_LEFT_SHIFT_BITS + workerId) + private static final long TIMESTAMP_LEFT_SHIFT_BITS = 22L; + + // the max of worker ID is 1024 + private static final long WORKER_ID_MAX_VALUE = 1024L; + + //CLOCK_REALTIME + private final long startWallTime = System.currentTimeMillis(); + + //CLOCK_MONOTONIC + private final long monotonicStartTime = System.nanoTime(); + + private long workerId; + + private long sequence; + + private long lastTime; + + private long currentId; + + { + long workerId = EnvUtil.getProperty("nacos.core.snowflake.worker-id", Integer.class, -1); + + if (workerId != -1) { + this.workerId = workerId; + } else { + InetAddress address; + try { + address = InetAddress.getByName(InetUtils.getSelfIP()); + } catch (final UnknownHostException e) { + throw new IllegalStateException("Cannot get LocalHost InetAddress, please check your network!", e); + } + byte[] ipAddressByteArray = address.getAddress(); + this.workerId = (((ipAddressByteArray[ipAddressByteArray.length - 2] & 0B11) << Byte.SIZE) + ( + ipAddressByteArray[ipAddressByteArray.length - 1] & 0xFF)); + } + } + + @Override + public void init() { + initialize(workerId); + } + + @Override + public long currentId() { + return currentId; + } + + @Override + public long workerId() { + return workerId; + } + + @Override + public synchronized long nextId() { + long currentMillis = currentTimeMillis(); + if (this.lastTime == currentMillis) { + if (0L == (this.sequence = ++this.sequence & 4095L)) { + currentMillis = this.waitUntilNextTime(currentMillis); + } + } else { + this.sequence = 0L; + } + + this.lastTime = currentMillis; + + if (logger.isDebugEnabled()) { + logger.debug("{}-{}-{}", (new SimpleDateFormat(DATETIME_PATTERN)).format(new Date(this.lastTime)), + workerId, this.sequence); + } + + currentId = currentMillis - EPOCH << 22 | workerId << 12 | this.sequence; + return currentId; + } + + @Override + public Map info() { + Map info = new HashMap<>(4); + info.put("currentId", currentId); + info.put("workerId", workerId); + return info; + } + + // ==============================Constructors===================================== + + /** + * init + * + * @param workerId worker id (0~1024) + */ + public void initialize(long workerId) { + if (workerId > WORKER_ID_MAX_VALUE || workerId < 0) { + throw new IllegalArgumentException( + String.format("worker Id can't be greater than %d or less than 0, current workId %d", + WORKER_ID_MAX_VALUE, workerId)); + } + this.workerId = workerId; + } + + /** + * Block to the next millisecond until a new timestamp is obtained + * + * @param lastTimestamp The time intercept of the last ID generated + * @return Current timestamp + */ + private long waitUntilNextTime(long lastTimestamp) { + long time; + time = currentTimeMillis(); + while (time <= lastTimestamp) { + time = currentTimeMillis(); + } + + return time; + } + + private long currentTimeMillis() { + return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - monotonicStartTime) + startWallTime; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JRaftMaintainService.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JRaftMaintainService.java new file mode 100644 index 00000000..3adfa0d5 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JRaftMaintainService.java @@ -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.core.distributed.raft; + +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.model.RestResultUtils; +import com.alibaba.nacos.core.distributed.raft.utils.JRaftConstants; +import com.alibaba.nacos.core.distributed.raft.utils.JRaftOps; +import com.alipay.sofa.jraft.CliService; +import com.alipay.sofa.jraft.Node; + +import java.util.Map; +import java.util.Objects; + +/** + * JRaft operations interface. + * + * @author liaochuntao + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JRaftMaintainService { + + private final JRaftServer raftServer; + + public JRaftMaintainService(JRaftServer raftServer) { + this.raftServer = raftServer; + } + + public RestResult execute(String[] args) { + return RestResultUtils.failed("not support yet"); + } + + /** + * Execute relevant commands. + * + * @param args {@link Map} + * @return {@link RestResult} + */ + public RestResult execute(Map args) { + final CliService cliService = raftServer.getCliService(); + if (args.containsKey(JRaftConstants.GROUP_ID)) { + final String groupId = args.get(JRaftConstants.GROUP_ID); + final Node node = raftServer.findNodeByGroup(groupId); + return single(cliService, groupId, node, args); + } + Map tupleMap = raftServer.getMultiRaftGroup(); + for (Map.Entry entry : tupleMap.entrySet()) { + final String group = entry.getKey(); + final Node node = entry.getValue().getNode(); + RestResult result = single(cliService, group, node, args); + if (!result.ok()) { + return result; + } + } + return RestResultUtils.success(); + } + + private RestResult single(CliService cliService, String groupId, Node node, Map args) { + try { + if (node == null) { + return RestResultUtils.failed("not this raft group : " + groupId); + } + final String command = args.get(JRaftConstants.COMMAND_NAME); + JRaftOps ops = JRaftOps.sourceOf(command); + if (Objects.isNull(ops)) { + return RestResultUtils.failed("Not support command : " + command); + } + return ops.execute(cliService, groupId, node, args); + } catch (Throwable ex) { + return RestResultUtils.failed(ex.getMessage()); + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JRaftProtocol.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JRaftProtocol.java new file mode 100644 index 00000000..c6f73d16 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JRaftProtocol.java @@ -0,0 +1,227 @@ +/* + * 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.distributed.raft; + +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.Subscriber; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.utils.MapUtil; +import com.alibaba.nacos.common.utils.ThreadUtils; +import com.alibaba.nacos.consistency.ProtocolMetaData; +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.RequestProcessor4CP; +import com.alibaba.nacos.consistency.cp.MetadataKey; +import com.alibaba.nacos.consistency.entity.ReadRequest; +import com.alibaba.nacos.consistency.entity.Response; +import com.alibaba.nacos.consistency.entity.WriteRequest; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.distributed.AbstractConsistencyProtocol; +import com.alibaba.nacos.core.distributed.raft.exception.NoSuchRaftGroupException; +import com.alibaba.nacos.core.utils.Loggers; +import com.alipay.sofa.jraft.Node; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A concrete implementation of CP protocol: JRaft. + * + *

+ *                                           ┌──────────────────────┐
+ *            ┌──────────────────────┐       │                      ▼
+ *            │   ProtocolManager    │       │        ┌───────────────────────────┐
+ *            └──────────────────────┘       │        │for p in [LogProcessor4CP] │
+ *                        │                  │        └───────────────────────────┘
+ *                        ▼                  │                      │
+ *      ┌──────────────────────────────────┐ │                      ▼
+ *      │    discovery LogProcessor4CP     │ │             ┌─────────────────┐
+ *      └──────────────────────────────────┘ │             │  get p.group()  │
+ *                        │                  │             └─────────────────┘
+ *                        ▼                  │                      │
+ *                 ┌─────────────┐           │                      │
+ *                 │ RaftConfig  │           │                      ▼
+ *                 └─────────────┘           │      ┌──────────────────────────────┐
+ *                        │                  │      │  create raft group service   │
+ *                        ▼                  │      └──────────────────────────────┘
+ *              ┌──────────────────┐         │
+ *              │  JRaftProtocol   │         │
+ *              └──────────────────┘         │
+ *                        │                  │
+ *                     init()                │
+ *                        │                  │
+ *                        ▼                  │
+ *               ┌─────────────────┐         │
+ *               │   JRaftServer   │         │
+ *               └─────────────────┘         │
+ *                        │                  │
+ *                        │                  │
+ *                        ▼                  │
+ *             ┌────────────────────┐        │
+ *             │JRaftServer.start() │        │
+ *             └────────────────────┘        │
+ *                        │                  │
+ *                        └──────────────────┘
+ * 
+ * + * @author liaochuntao + */ +@SuppressWarnings("all") +public class JRaftProtocol extends AbstractConsistencyProtocol + implements CPProtocol { + + private final AtomicBoolean initialized = new AtomicBoolean(false); + + private final AtomicBoolean shutdowned = new AtomicBoolean(false); + + private final Serializer serializer = SerializeFactory.getDefault(); + + private RaftConfig raftConfig; + + private JRaftServer raftServer; + + private JRaftMaintainService jRaftMaintainService; + + private ServerMemberManager memberManager; + + public JRaftProtocol(ServerMemberManager memberManager) throws Exception { + this.memberManager = memberManager; + this.raftServer = new JRaftServer(); + this.jRaftMaintainService = new JRaftMaintainService(raftServer); + } + + @Override + public void init(RaftConfig config) { + if (initialized.compareAndSet(false, true)) { + this.raftConfig = config; + NotifyCenter.registerToSharePublisher(RaftEvent.class); + this.raftServer.init(this.raftConfig); + this.raftServer.start(); + + // There is only one consumer to ensure that the internal consumption + // is sequential and there is no concurrent competition + NotifyCenter.registerSubscriber(new Subscriber() { + @Override + public void onEvent(RaftEvent event) { + Loggers.RAFT.info("This Raft event changes : {}", event); + final String groupId = event.getGroupId(); + Map> value = new HashMap<>(); + Map properties = new HashMap<>(); + final String leader = event.getLeader(); + final Long term = event.getTerm(); + final List 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. + MapUtil.putIfValNoEmpty(properties, MetadataKey.LEADER_META_DATA, leader); + MapUtil.putIfValNoNull(properties, MetadataKey.TERM_META_DATA, term); + MapUtil.putIfValNoEmpty(properties, MetadataKey.RAFT_GROUP_MEMBER, raftClusterInfo); + MapUtil.putIfValNoEmpty(properties, MetadataKey.ERR_MSG, errMsg); + + value.put(groupId, properties); + metaData.load(value); + + // The metadata information is injected into the metadata information of the node + injectProtocolMetaData(metaData); + } + + @Override + public Class subscribeType() { + return RaftEvent.class; + } + + }); + } + } + + @Override + public void addRequestProcessors(Collection processors) { + raftServer.createMultiRaftGroup(processors); + } + + @Override + public Response getData(ReadRequest request) throws Exception { + CompletableFuture future = aGetData(request); + return future.get(5_000L, TimeUnit.MILLISECONDS); + } + + @Override + public CompletableFuture aGetData(ReadRequest request) { + return raftServer.get(request); + } + + @Override + public Response write(WriteRequest request) throws Exception { + CompletableFuture future = writeAsync(request); + // Here you wait for 10 seconds, as long as possible, for the request to complete + return future.get(10_000L, TimeUnit.MILLISECONDS); + } + + @Override + public CompletableFuture writeAsync(WriteRequest request) { + return raftServer.commit(request.getGroup(), request, new CompletableFuture<>()); + } + + @Override + public void memberChange(Set addresses) { + for (int i = 0; i < 5; i++) { + if (this.raftServer.peerChange(jRaftMaintainService, addresses)) { + return; + } + ThreadUtils.sleep(100L); + } + Loggers.RAFT.warn("peer removal failed"); + } + + @Override + public void shutdown() { + if (initialized.get() && shutdowned.compareAndSet(false, true)) { + Loggers.RAFT.info("shutdown jraft server"); + raftServer.shutdown(); + } + } + + @Override + public RestResult execute(Map args) { + return jRaftMaintainService.execute(args); + } + + private void injectProtocolMetaData(ProtocolMetaData metaData) { + Member member = memberManager.getSelf(); + member.setExtendVal("raftMetaData", metaData); + memberManager.update(member); + } + + @Override + public boolean isLeader(String group) { + Node node = raftServer.findNodeByGroup(group); + if (node == null) { + throw new NoSuchRaftGroupException(group); + } + return node.isLeader(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JRaftServer.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JRaftServer.java new file mode 100644 index 00000000..e6fd3100 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JRaftServer.java @@ -0,0 +1,571 @@ +/* + * 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.distributed.raft; + +import com.alibaba.nacos.common.JustForTest; +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.utils.ConvertUtils; +import com.alibaba.nacos.common.utils.InternetAddressUtil; +import com.alibaba.nacos.common.utils.LoggerUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.common.utils.ThreadUtils; +import com.alibaba.nacos.consistency.ProtoMessageUtil; +import com.alibaba.nacos.consistency.RequestProcessor; +import com.alibaba.nacos.consistency.SerializeFactory; +import com.alibaba.nacos.consistency.Serializer; +import com.alibaba.nacos.consistency.cp.RequestProcessor4CP; +import com.alibaba.nacos.consistency.entity.ReadRequest; +import com.alibaba.nacos.consistency.entity.Response; +import com.alibaba.nacos.consistency.exception.ConsistencyException; +import com.alibaba.nacos.core.distributed.raft.exception.DuplicateRaftGroupException; +import com.alibaba.nacos.core.distributed.raft.exception.JRaftException; +import com.alibaba.nacos.core.distributed.raft.exception.NoLeaderException; +import com.alibaba.nacos.core.distributed.raft.exception.NoSuchRaftGroupException; +import com.alibaba.nacos.core.distributed.raft.utils.FailoverClosure; +import com.alibaba.nacos.core.distributed.raft.utils.FailoverClosureImpl; +import com.alibaba.nacos.core.distributed.raft.utils.JRaftConstants; +import com.alibaba.nacos.core.distributed.raft.utils.JRaftUtils; +import com.alibaba.nacos.core.distributed.raft.utils.RaftExecutor; +import com.alibaba.nacos.core.distributed.raft.utils.RaftOptionsBuilder; +import com.alibaba.nacos.core.monitor.MetricsMonitor; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alipay.sofa.jraft.CliService; +import com.alipay.sofa.jraft.Node; +import com.alipay.sofa.jraft.RaftGroupService; +import com.alipay.sofa.jraft.RaftServiceFactory; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.closure.ReadIndexClosure; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.core.CliServiceImpl; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.entity.Task; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.option.CliOptions; +import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.option.RaftOptions; +import com.alipay.sofa.jraft.rpc.InvokeCallback; +import com.alipay.sofa.jraft.rpc.RpcProcessor; +import com.alipay.sofa.jraft.rpc.RpcServer; +import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; +import com.alipay.sofa.jraft.util.BytesUtil; +import com.alipay.sofa.jraft.util.Endpoint; +import com.google.protobuf.Message; +import org.springframework.util.CollectionUtils; + +import java.nio.ByteBuffer; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; + +/** + * JRaft server instance, away from Spring IOC management. + * + *

+ * Why do we need to create a raft group based on the value of LogProcessor group (), that is, each function module has + * its own state machine. Because each LogProcessor corresponds to a different functional module, such as Nacos's naming + * module and config module, these two modules are independent of each other and do not affect each other. If we have + * only one state machine, it is equal to the log of all functional modules The processing is loaded together. Any + * module that has an exception during the log processing and a long block operation will affect the normal operation of + * other functional modules. + *

+ * + * @author liaochuntao + */ +@SuppressWarnings("all") +public class JRaftServer { + + // Existential life cycle + + private RpcServer rpcServer; + + private CliClientServiceImpl cliClientService; + + private CliService cliService; + + // Ordinary member variable + + private Map multiRaftGroup = new ConcurrentHashMap<>(); + + private volatile boolean isStarted = false; + + private volatile boolean isShutdown = false; + + private Configuration conf; + + private RpcProcessor userProcessor; + + private NodeOptions nodeOptions; + + private Serializer serializer; + + private Collection processors = Collections.synchronizedSet(new HashSet<>()); + + private String selfIp; + + private int selfPort; + + private RaftConfig raftConfig; + + private PeerId localPeerId; + + private int failoverRetries; + + private int rpcRequestTimeoutMs; + + public JRaftServer() { + this.conf = new Configuration(); + } + + public void setFailoverRetries(int failoverRetries) { + this.failoverRetries = failoverRetries; + } + + void init(RaftConfig config) { + this.raftConfig = config; + this.serializer = SerializeFactory.getDefault(); + Loggers.RAFT.info("Initializes the Raft protocol, raft-config info : {}", config); + RaftExecutor.init(config); + + final String self = config.getSelfMember(); + String[] info = InternetAddressUtil.splitIPPortStr(self); + selfIp = info[0]; + selfPort = Integer.parseInt(info[1]); + localPeerId = PeerId.parsePeer(self); + nodeOptions = new NodeOptions(); + + // Set the election timeout time. The default is 5 seconds. + int electionTimeout = Math.max(ConvertUtils.toInt(config.getVal(RaftSysConstants.RAFT_ELECTION_TIMEOUT_MS), + RaftSysConstants.DEFAULT_ELECTION_TIMEOUT), RaftSysConstants.DEFAULT_ELECTION_TIMEOUT); + + rpcRequestTimeoutMs = ConvertUtils.toInt(raftConfig.getVal(RaftSysConstants.RAFT_RPC_REQUEST_TIMEOUT_MS), + RaftSysConstants.DEFAULT_RAFT_RPC_REQUEST_TIMEOUT_MS); + + nodeOptions.setSharedElectionTimer(true); + nodeOptions.setSharedVoteTimer(true); + nodeOptions.setSharedStepDownTimer(true); + nodeOptions.setSharedSnapshotTimer(true); + + nodeOptions.setElectionTimeoutMs(electionTimeout); + RaftOptions raftOptions = RaftOptionsBuilder.initRaftOptions(raftConfig); + nodeOptions.setRaftOptions(raftOptions); + // open jraft node metrics record function + nodeOptions.setEnableMetrics(true); + + CliOptions cliOptions = new CliOptions(); + + this.cliService = RaftServiceFactory.createAndInitCliService(cliOptions); + this.cliClientService = (CliClientServiceImpl) ((CliServiceImpl) this.cliService).getCliClientService(); + } + + synchronized void start() { + if (!isStarted) { + Loggers.RAFT.info("========= The raft protocol is starting... ========="); + try { + // init raft group node + com.alipay.sofa.jraft.NodeManager raftNodeManager = com.alipay.sofa.jraft.NodeManager.getInstance(); + for (String address : raftConfig.getMembers()) { + PeerId peerId = PeerId.parsePeer(address); + conf.addPeer(peerId); + raftNodeManager.addAddress(peerId.getEndpoint()); + } + nodeOptions.setInitialConf(conf); + + rpcServer = JRaftUtils.initRpcServer(this, localPeerId); + + if (!this.rpcServer.init(null)) { + Loggers.RAFT.error("Fail to init [BaseRpcServer]."); + throw new RuntimeException("Fail to init [BaseRpcServer]."); + } + + // Initialize multi raft group service framework + isStarted = true; + createMultiRaftGroup(processors); + Loggers.RAFT.info("========= The raft protocol start finished... ========="); + } catch (Exception e) { + Loggers.RAFT.error("raft protocol start failure, cause: ", e); + throw new JRaftException(e); + } + } + } + + synchronized void createMultiRaftGroup(Collection processors) { + // There is no reason why the LogProcessor cannot be processed because of the synchronization + if (!this.isStarted) { + this.processors.addAll(processors); + return; + } + + final String parentPath = Paths.get(EnvUtil.getNacosHome(), "data/protocol/raft").toString(); + + for (RequestProcessor4CP processor : processors) { + final String groupName = processor.group(); + if (multiRaftGroup.containsKey(groupName)) { + throw new DuplicateRaftGroupException(groupName); + } + + // Ensure that each Raft Group has its own configuration and NodeOptions + Configuration configuration = conf.copy(); + NodeOptions copy = nodeOptions.copy(); + JRaftUtils.initDirectory(parentPath, groupName, copy); + + // Here, the LogProcessor is passed into StateMachine, and when the StateMachine + // triggers onApply, the onApply of the LogProcessor is actually called + NacosStateMachine machine = new NacosStateMachine(this, processor); + + copy.setFsm(machine); + copy.setInitialConf(configuration); + + // Set snapshot interval, default 1800 seconds + int doSnapshotInterval = ConvertUtils.toInt(raftConfig.getVal(RaftSysConstants.RAFT_SNAPSHOT_INTERVAL_SECS), + RaftSysConstants.DEFAULT_RAFT_SNAPSHOT_INTERVAL_SECS); + + // If the business module does not implement a snapshot processor, cancel the snapshot + doSnapshotInterval = CollectionUtils.isEmpty(processor.loadSnapshotOperate()) ? 0 : doSnapshotInterval; + + copy.setSnapshotIntervalSecs(doSnapshotInterval); + Loggers.RAFT.info("create raft group : {}", groupName); + RaftGroupService raftGroupService = new RaftGroupService(groupName, localPeerId, copy, rpcServer, true); + + // Because BaseRpcServer has been started before, it is not allowed to start again here + Node node = raftGroupService.start(false); + machine.setNode(node); + RouteTable.getInstance().updateConfiguration(groupName, configuration); + + RaftExecutor.executeByCommon(() -> registerSelfToCluster(groupName, localPeerId, configuration)); + + // Turn on the leader auto refresh for this group + Random random = new Random(); + long period = nodeOptions.getElectionTimeoutMs() + random.nextInt(5 * 1000); + RaftExecutor.scheduleRaftMemberRefreshJob(() -> refreshRouteTable(groupName), + nodeOptions.getElectionTimeoutMs(), period, TimeUnit.MILLISECONDS); + multiRaftGroup.put(groupName, new RaftGroupTuple(node, processor, raftGroupService, machine)); + } + } + + CompletableFuture get(final ReadRequest request) { + final String group = request.getGroup(); + CompletableFuture future = new CompletableFuture<>(); + final RaftGroupTuple tuple = findTupleByGroup(group); + if (Objects.isNull(tuple)) { + future.completeExceptionally(new NoSuchRaftGroupException(group)); + return future; + } + final Node node = tuple.node; + final RequestProcessor processor = tuple.processor; + try { + node.readIndex(BytesUtil.EMPTY_BYTES, new ReadIndexClosure() { + @Override + public void run(Status status, long index, byte[] reqCtx) { + if (status.isOk()) { + try { + Response response = processor.onRequest(request); + future.complete(response); + } catch (Throwable t) { + MetricsMonitor.raftReadIndexFailed(); + future.completeExceptionally(new ConsistencyException( + "The conformance protocol is temporarily unavailable for reading", t)); + } + return; + } + MetricsMonitor.raftReadIndexFailed(); + Loggers.RAFT.error("ReadIndex has error : {}, go to Leader read.", status.getErrorMsg()); + MetricsMonitor.raftReadFromLeader(); + readFromLeader(request, future); + } + }); + return future; + } catch (Throwable e) { + MetricsMonitor.raftReadFromLeader(); + Loggers.RAFT.warn("Raft linear read failed, go to Leader read logic : {}", e.toString()); + // run raft read + readFromLeader(request, future); + return future; + } + } + + public void readFromLeader(final ReadRequest request, final CompletableFuture future) { + commit(request.getGroup(), request, future); + } + + public CompletableFuture commit(final String group, final Message data, + final CompletableFuture future) { + LoggerUtils.printIfDebugEnabled(Loggers.RAFT, "data requested this time : {}", data); + final RaftGroupTuple tuple = findTupleByGroup(group); + if (tuple == null) { + future.completeExceptionally(new IllegalArgumentException("No corresponding Raft Group found : " + group)); + return future; + } + + FailoverClosureImpl closure = new FailoverClosureImpl(future); + + final Node node = tuple.node; + if (node.isLeader()) { + // The leader node directly applies this request + applyOperation(node, data, closure); + } else { + // Forward to Leader for request processing + invokeToLeader(group, data, rpcRequestTimeoutMs, closure); + } + return future; + } + + /** + * Add yourself to the Raft cluster + * + * @param groupId raft group + * @param selfIp local raft node address + * @param conf {@link Configuration} without self info + * @return join success + */ + void registerSelfToCluster(String groupId, PeerId selfIp, Configuration conf) { + for (; ; ) { + try { + List peerIds = cliService.getPeers(groupId, conf); + if (peerIds.contains(selfIp)) { + return; + } + Status status = cliService.addPeer(groupId, conf, selfIp); + if (status.isOk()) { + return; + } + Loggers.RAFT.warn("Failed to join the cluster, retry..."); + } catch (Exception e) { + Loggers.RAFT.error("Failed to join the cluster, retry...", e); + } + ThreadUtils.sleep(1_000L); + } + } + + protected PeerId getLeader(final String raftGroupId) { + return RouteTable.getInstance().selectLeader(raftGroupId); + } + + synchronized void shutdown() { + if (isShutdown) { + return; + } + isShutdown = true; + try { + Loggers.RAFT.info("========= The raft protocol is starting to close ========="); + + for (Map.Entry entry : multiRaftGroup.entrySet()) { + final RaftGroupTuple tuple = entry.getValue(); + final Node node = tuple.getNode(); + tuple.node.shutdown(); + tuple.raftGroupService.shutdown(); + } + + cliService.shutdown(); + cliClientService.shutdown(); + + Loggers.RAFT.info("========= The raft protocol has been closed ========="); + } catch (Throwable t) { + Loggers.RAFT.error("There was an error in the raft protocol shutdown, cause: ", t); + } + } + + public void applyOperation(Node node, Message data, FailoverClosure closure) { + final Task task = new Task(); + task.setDone(new NacosClosure(data, status -> { + NacosClosure.NacosStatus nacosStatus = (NacosClosure.NacosStatus) status; + closure.setThrowable(nacosStatus.getThrowable()); + closure.setResponse(nacosStatus.getResponse()); + closure.run(nacosStatus); + })); + + // add request type field at the head of task data. + byte[] requestTypeFieldBytes = new byte[2]; + requestTypeFieldBytes[0] = ProtoMessageUtil.REQUEST_TYPE_FIELD_TAG; + if (data instanceof ReadRequest) { + requestTypeFieldBytes[1] = ProtoMessageUtil.REQUEST_TYPE_READ; + } else { + requestTypeFieldBytes[1] = ProtoMessageUtil.REQUEST_TYPE_WRITE; + } + + byte[] dataBytes = data.toByteArray(); + task.setData((ByteBuffer) ByteBuffer.allocate(requestTypeFieldBytes.length + dataBytes.length) + .put(requestTypeFieldBytes).put(dataBytes).position(0)); + node.apply(task); + } + + private void invokeToLeader(final String group, final Message request, final int timeoutMillis, + FailoverClosure closure) { + try { + final Endpoint leaderIp = Optional.ofNullable(getLeader(group)) + .orElseThrow(() -> new NoLeaderException(group)).getEndpoint(); + cliClientService.getRpcClient().invokeAsync(leaderIp, request, new InvokeCallback() { + @Override + public void complete(Object o, Throwable ex) { + if (Objects.nonNull(ex)) { + closure.setThrowable(ex); + closure.run(new Status(RaftError.UNKNOWN, ex.getMessage())); + return; + } + if (!((Response)o).getSuccess()) { + closure.setThrowable(new IllegalStateException(((Response) o).getErrMsg())); + closure.run(new Status(RaftError.UNKNOWN, ((Response) o).getErrMsg())); + return; + } + closure.setResponse((Response) o); + closure.run(Status.OK()); + } + + @Override + public Executor executor() { + return RaftExecutor.getRaftCliServiceExecutor(); + } + }, timeoutMillis); + } catch (Exception e) { + closure.setThrowable(e); + closure.run(new Status(RaftError.UNKNOWN, e.toString())); + } + } + + boolean peerChange(JRaftMaintainService maintainService, Set newPeers) { + // This is only dealing with node deletion, the Raft protocol, where the node adds itself to the cluster when it starts up + Set oldPeers = new HashSet<>(this.raftConfig.getMembers()); + this.raftConfig.setMembers(localPeerId.toString(), newPeers); + oldPeers.removeAll(newPeers); + if (oldPeers.isEmpty()) { + return true; + } + + Set waitRemove = oldPeers; + AtomicInteger successCnt = new AtomicInteger(0); + multiRaftGroup.forEach(new BiConsumer() { + @Override + public void accept(String group, RaftGroupTuple tuple) { + Map params = new HashMap<>(); + params.put(JRaftConstants.GROUP_ID, group); + params.put(JRaftConstants.COMMAND_NAME, JRaftConstants.REMOVE_PEERS); + params.put(JRaftConstants.COMMAND_VALUE, StringUtils.join(waitRemove, StringUtils.COMMA)); + RestResult result = maintainService.execute(params); + if (result.ok()) { + successCnt.incrementAndGet(); + } else { + Loggers.RAFT.error("Node removal failed : {}", result); + } + } + }); + return successCnt.get() == multiRaftGroup.size(); + } + + void refreshRouteTable(String group) { + if (isShutdown) { + return; + } + + final String groupName = group; + Status status = null; + try { + RouteTable instance = RouteTable.getInstance(); + Configuration oldConf = instance.getConfiguration(groupName); + String oldLeader = Optional.ofNullable(instance.selectLeader(groupName)).orElse(PeerId.emptyPeer()) + .getEndpoint().toString(); + // 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); + } + } + + public RaftGroupTuple findTupleByGroup(final String group) { + RaftGroupTuple tuple = multiRaftGroup.get(group); + return tuple; + } + + public Node findNodeByGroup(final String group) { + final RaftGroupTuple tuple = multiRaftGroup.get(group); + if (Objects.nonNull(tuple)) { + return tuple.node; + } + return null; + } + + Map getMultiRaftGroup() { + return multiRaftGroup; + } + + @JustForTest + void mockMultiRaftGroup(Map map) { + this.multiRaftGroup = map; + } + + CliService getCliService() { + return cliService; + } + + public static class RaftGroupTuple { + + private RequestProcessor processor; + + private Node node; + + private RaftGroupService raftGroupService; + + private NacosStateMachine machine; + + @JustForTest + public RaftGroupTuple() { + } + + public RaftGroupTuple(Node node, RequestProcessor processor, RaftGroupService raftGroupService, + NacosStateMachine machine) { + this.node = node; + this.processor = processor; + this.raftGroupService = raftGroupService; + this.machine = machine; + } + + public Node getNode() { + return node; + } + + public RequestProcessor getProcessor() { + return processor; + } + + public RaftGroupService getRaftGroupService() { + return raftGroupService; + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JSnapshotOperation.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JSnapshotOperation.java new file mode 100644 index 00000000..3fb58926 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/JSnapshotOperation.java @@ -0,0 +1,70 @@ +/* + * 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.distributed.raft; + +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.consistency.snapshot.LocalFileMeta; +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.entity.LocalFileMetaOutter; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import com.google.protobuf.ZeroByteStringHelper; + +/** + * JRaft snapshot operation. + * + * @author liaochuntao + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +interface JSnapshotOperation { + + /** + * do snapshot save operation. + * + * @param writer {@link SnapshotWriter} + * @param done {@link Closure} + */ + void onSnapshotSave(SnapshotWriter writer, Closure done); + + /** + * do snapshot load operation. + * + * @param reader {@link SnapshotReader} + * @return operation label + */ + boolean onSnapshotLoad(SnapshotReader reader); + + /** + * return actually snapshot executor. + * + * @return name + */ + String info(); + + /** + * Metadata information for snapshot files. + * + * @param metadata meta data + * @return {@link LocalFileMetaOutter.LocalFileMeta} + * @throws Exception Exception + */ + default LocalFileMetaOutter.LocalFileMeta buildMetadata(final LocalFileMeta metadata) throws Exception { + return metadata == null ? null : LocalFileMetaOutter.LocalFileMeta.newBuilder() + .setUserMeta(ZeroByteStringHelper.wrap(JacksonUtils.toJsonBytes(metadata))).build(); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/NacosClosure.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/NacosClosure.java new file mode 100644 index 00000000..8334d718 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/NacosClosure.java @@ -0,0 +1,159 @@ +/* + * 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.distributed.raft; + +import com.alibaba.nacos.consistency.entity.Response; +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.error.RaftError; +import com.google.protobuf.Message; + +/** + * implement jraft closure. + * + * @author liaochuntao + */ +public class NacosClosure implements Closure { + + private Message message; + + private Closure closure; + + private NacosStatus nacosStatus = new NacosStatus(); + + public NacosClosure(Message message, Closure closure) { + this.message = message; + this.closure = closure; + } + + @Override + public void run(Status status) { + nacosStatus.setStatus(status); + closure.run(nacosStatus); + clear(); + } + + private void clear() { + message = null; + closure = null; + nacosStatus = null; + } + + public void setResponse(Response response) { + this.nacosStatus.setResponse(response); + } + + public void setThrowable(Throwable throwable) { + this.nacosStatus.setThrowable(throwable); + } + + public Message getMessage() { + return message; + } + + // Pass the Throwable inside the state machine to the outer layer + + @SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") + public static class NacosStatus extends Status { + + private Status status; + + private Response response = null; + + private Throwable throwable = null; + + public void setStatus(Status status) { + this.status = status; + } + + @Override + public void reset() { + status.reset(); + } + + @Override + public boolean isOk() { + return status.isOk(); + } + + @Override + public int getCode() { + return status.getCode(); + } + + @Override + public void setCode(int code) { + status.setCode(code); + } + + @Override + public RaftError getRaftError() { + return status.getRaftError(); + } + + @Override + public void setError(int code, String fmt, Object... args) { + status.setError(code, fmt, args); + } + + @Override + public void setError(RaftError error, String fmt, Object... args) { + status.setError(error, fmt, args); + } + + @Override + public String toString() { + return status.toString(); + } + + @Override + public Status copy() { + NacosStatus copy = new NacosStatus(); + copy.status = this.status; + copy.response = this.response; + copy.throwable = this.throwable; + return copy; + } + + @Override + public String getErrorMsg() { + return status.getErrorMsg(); + } + + @Override + public void setErrorMsg(String errMsg) { + status.setErrorMsg(errMsg); + } + + public Response getResponse() { + return response; + } + + public void setResponse(Response response) { + this.response = response; + } + + public Throwable getThrowable() { + return throwable; + } + + public void setThrowable(Throwable throwable) { + this.throwable = throwable; + } + + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/NacosStateMachine.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/NacosStateMachine.java new file mode 100644 index 00000000..5c986d0f --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/NacosStateMachine.java @@ -0,0 +1,321 @@ +/* + * 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.distributed.raft; + +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.utils.ExceptionUtil; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.LoggerUtils; +import com.alibaba.nacos.consistency.RequestProcessor; +import com.alibaba.nacos.consistency.ProtoMessageUtil; +import com.alibaba.nacos.consistency.cp.RequestProcessor4CP; +import com.alibaba.nacos.consistency.entity.ReadRequest; +import com.alibaba.nacos.consistency.entity.Response; +import com.alibaba.nacos.consistency.entity.WriteRequest; +import com.alibaba.nacos.consistency.exception.ConsistencyException; +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.JRaftUtils; +import com.alibaba.nacos.core.utils.Loggers; +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Iterator; +import com.alipay.sofa.jraft.Node; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.core.StateMachineAdapter; +import com.alipay.sofa.jraft.entity.LeaderChangeContext; +import com.alipay.sofa.jraft.entity.LocalFileMetaOutter; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.error.RaftException; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import com.google.protobuf.Message; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; + +/** + * JRaft StateMachine implemented. + * + * @author liaochuntao + */ +class NacosStateMachine extends StateMachineAdapter { + + protected final JRaftServer server; + + protected final RequestProcessor processor; + + private final AtomicBoolean isLeader = new AtomicBoolean(false); + + private final String groupId; + + private Collection operations; + + private Node node; + + private volatile long term = -1; + + private volatile String leaderIp = "unknown"; + + NacosStateMachine(JRaftServer server, RequestProcessor4CP processor) { + this.server = server; + this.processor = processor; + this.groupId = processor.group(); + adapterToJRaftSnapshot(processor.loadSnapshotOperate()); + } + + @Override + public void onApply(Iterator iter) { + int index = 0; + int applied = 0; + Message message; + NacosClosure closure = null; + try { + while (iter.hasNext()) { + Status status = Status.OK(); + try { + if (iter.done() != null) { + closure = (NacosClosure) iter.done(); + message = closure.getMessage(); + } else { + final ByteBuffer data = iter.getData(); + message = ProtoMessageUtil.parse(data.array()); + if (message instanceof ReadRequest) { + //'iter.done() == null' means current node is follower, ignore read operation + applied++; + index++; + iter.next(); + continue; + } + } + + LoggerUtils.printIfDebugEnabled(Loggers.RAFT, "receive log : {}", message); + + if (message instanceof WriteRequest) { + Response response = processor.onApply((WriteRequest) message); + postProcessor(response, closure); + } + + if (message instanceof ReadRequest) { + Response response = processor.onRequest((ReadRequest) message); + postProcessor(response, closure); + } + } catch (Throwable e) { + index++; + status.setError(RaftError.UNKNOWN, e.toString()); + Optional.ofNullable(closure).ifPresent(closure1 -> closure1.setThrowable(e)); + throw e; + } finally { + Optional.ofNullable(closure).ifPresent(closure1 -> closure1.run(status)); + } + + applied++; + index++; + iter.next(); + } + } catch (Throwable t) { + Loggers.RAFT.error("processor : {}, stateMachine meet critical error: {}.", processor, t); + iter.setErrorAndRollback(index - applied, + new Status(RaftError.ESTATEMACHINE, "StateMachine meet critical error: %s.", + ExceptionUtil.getStackTrace(t))); + } + } + + public void setNode(Node node) { + this.node = node; + } + + @Override + public void onSnapshotSave(SnapshotWriter writer, Closure done) { + for (JSnapshotOperation operation : operations) { + try { + operation.onSnapshotSave(writer, done); + } catch (Throwable t) { + Loggers.RAFT.error("There was an error saving the snapshot , error : {}, operation : {}", t, + operation.info()); + throw t; + } + } + } + + @Override + public boolean onSnapshotLoad(SnapshotReader reader) { + for (JSnapshotOperation operation : operations) { + try { + if (!operation.onSnapshotLoad(reader)) { + Loggers.RAFT.error("Snapshot load failed on : {}", operation.info()); + return false; + } + } catch (Throwable t) { + Loggers.RAFT.error("Snapshot load failed on : {}, has error : {}", operation.info(), t); + return false; + } + } + return true; + } + + @Override + public void onLeaderStart(final long term) { + super.onLeaderStart(term); + this.term = term; + this.isLeader.set(true); + this.leaderIp = node.getNodeId().getPeerId().getEndpoint().toString(); + NotifyCenter.publishEvent( + RaftEvent.builder().groupId(groupId).leader(leaderIp).term(term).raftClusterInfo(allPeers()).build()); + } + + @Override + public void onLeaderStop(final Status status) { + super.onLeaderStop(status); + this.isLeader.set(false); + } + + @Override + public void onStartFollowing(LeaderChangeContext ctx) { + this.term = ctx.getTerm(); + this.leaderIp = ctx.getLeaderId().getEndpoint().toString(); + NotifyCenter.publishEvent( + RaftEvent.builder().groupId(groupId).leader(leaderIp).term(ctx.getTerm()).raftClusterInfo(allPeers()) + .build()); + } + + @Override + public void onConfigurationCommitted(Configuration conf) { + NotifyCenter.publishEvent( + RaftEvent.builder().groupId(groupId).raftClusterInfo(JRaftUtils.toStrings(conf.getPeers())).build()); + } + + @Override + 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() { + return isLeader.get(); + } + + private List allPeers() { + if (node == null) { + return Collections.emptyList(); + } + + if (node.isLeader()) { + return JRaftUtils.toStrings(node.listPeers()); + } + + return JRaftUtils.toStrings(RouteTable.getInstance().getConfiguration(node.getGroupId()).getPeers()); + } + + private void postProcessor(Response data, NacosClosure closure) { + if (Objects.nonNull(closure)) { + closure.setResponse(data); + } + } + + public long getTerm() { + return term; + } + + private void adapterToJRaftSnapshot(Collection userOperates) { + List tmp = new ArrayList<>(); + + for (SnapshotOperation item : userOperates) { + + if (item == null) { + Loggers.RAFT.error("Existing SnapshotOperation for null"); + continue; + } + + tmp.add(new JSnapshotOperation() { + + @Override + public void onSnapshotSave(SnapshotWriter writer, Closure done) { + final Writer wCtx = new Writer(writer.getPath()); + + // Do a layer of proxy operation to shield different Raft + // components from implementing snapshots + + final BiConsumer callFinally = (result, t) -> { + boolean[] results = new boolean[wCtx.listFiles().size()]; + int[] index = new int[] {0}; + wCtx.listFiles().forEach((file, meta) -> { + try { + results[index[0]++] = writer.addFile(file, buildMetadata(meta)); + } catch (Exception e) { + throw new ConsistencyException(e); + } + }); + final Status status = result + && !Arrays.asList(results).stream().anyMatch(Boolean.FALSE::equals) ? Status.OK() + : new Status(RaftError.EIO, "Fail to compress snapshot at %s, error is %s", + writer.getPath(), t == null ? "" : t.getMessage()); + done.run(status); + }; + item.onSnapshotSave(wCtx, callFinally); + } + + @Override + public boolean onSnapshotLoad(SnapshotReader reader) { + final Map metaMap = new HashMap<>(reader.listFiles().size()); + for (String fileName : reader.listFiles()) { + final LocalFileMetaOutter.LocalFileMeta meta = (LocalFileMetaOutter.LocalFileMeta) reader + .getFileMeta(fileName); + + byte[] bytes = meta.getUserMeta().toByteArray(); + + final LocalFileMeta fileMeta; + if (bytes == null || bytes.length == 0) { + fileMeta = new LocalFileMeta(); + } else { + fileMeta = JacksonUtils.toObj(bytes, LocalFileMeta.class); + } + + metaMap.put(fileName, fileMeta); + } + final Reader rCtx = new Reader(reader.getPath(), metaMap); + return item.onSnapshotLoad(rCtx); + } + + @Override + public String info() { + return item.toString(); + } + }); + } + + this.operations = Collections.unmodifiableList(tmp); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftConfig.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftConfig.java new file mode 100644 index 00000000..123f4233 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftConfig.java @@ -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.distributed.raft; + +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.consistency.Config; +import com.alibaba.nacos.consistency.cp.RequestProcessor4CP; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * raft config. + * + * @author liaochuntao + */ +@Component +@ConfigurationProperties(prefix = "nacos.core.protocol.raft") +public class RaftConfig implements Config { + + private static final long serialVersionUID = 9174789390266064002L; + + private Map data = Collections.synchronizedMap(new HashMap<>()); + + private String selfAddress; + + private Set members = Collections.synchronizedSet(new HashSet<>()); + + @Override + public void setMembers(String self, Set members) { + this.selfAddress = self; + this.members.clear(); + this.members.addAll(members); + } + + @Override + public String getSelfMember() { + return selfAddress; + } + + @Override + public Set getMembers() { + return members; + } + + @Override + public void addMembers(Set members) { + this.members.addAll(members); + } + + @Override + public void removeMembers(Set members) { + this.members.removeAll(members); + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = Collections.synchronizedMap(data); + } + + @Override + public void setVal(String key, String value) { + data.put(key, value); + } + + @Override + public String getVal(String key) { + return data.get(key); + } + + @Override + public String getValOfDefault(String key, String defaultVal) { + return data.getOrDefault(key, defaultVal); + } + + @Override + public String toString() { + try { + return JacksonUtils.toJson(data); + } catch (Exception e) { + return String.valueOf(data); + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftErrorEvent.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftErrorEvent.java new file mode 100644 index 00000000..ba6bbc69 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftErrorEvent.java @@ -0,0 +1,40 @@ +/* + * 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.distributed.raft; + +import com.alibaba.nacos.common.notify.Event; + +/** + * The RAFT protocol runs an exception event. If this event is published, it means that the current raft Group cannot + * continue to run normally + * + * @author liaochuntao + */ +public class RaftErrorEvent extends Event { + + private static final long serialVersionUID = 3016514657754158167L; + + private String groupName; + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftEvent.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftEvent.java new file mode 100644 index 00000000..0cb9b4c4 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftEvent.java @@ -0,0 +1,144 @@ +/* + * 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.distributed.raft; + +import com.alibaba.nacos.common.notify.SlowEvent; + +import java.util.Collections; +import java.util.List; + +/** + * Changes to metadata information during the raft protocol run. + * + * @author liaochuntao + */ +@SuppressWarnings("all") +public class RaftEvent extends SlowEvent { + + private static final long serialVersionUID = -4304258594602886451L; + + private String groupId; + + private String leader = null; + + private Long term = null; + + private String errMsg = ""; + + private List raftClusterInfo = Collections.emptyList(); + + public static RaftEventBuilder builder() { + return new RaftEventBuilder(); + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getLeader() { + return leader; + } + + public void setLeader(String leader) { + this.leader = leader; + } + + public Long getTerm() { + return term; + } + + public void setTerm(Long term) { + this.term = term; + } + + public List getRaftClusterInfo() { + return raftClusterInfo; + } + + public void setRaftClusterInfo(List raftClusterInfo) { + 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 + + ", raftClusterInfo=" + raftClusterInfo + '}'; + } + + public static final class RaftEventBuilder { + + private String groupId; + + private String leader; + + private Long term = null; + + private List raftClusterInfo = Collections.emptyList(); + + private String errMsg = ""; + + private RaftEventBuilder() { + } + + public RaftEventBuilder groupId(String groupId) { + this.groupId = groupId; + return this; + } + + public RaftEventBuilder leader(String leader) { + this.leader = leader; + return this; + } + + public RaftEventBuilder term(long term) { + this.term = term; + return this; + } + + public RaftEventBuilder raftClusterInfo(List raftClusterInfo) { + this.raftClusterInfo = raftClusterInfo; + return this; + } + + public RaftEventBuilder errMsg(String errMsg) { + this.errMsg = errMsg; + return this; + } + + public RaftEvent build() { + RaftEvent raftEvent = new RaftEvent(); + raftEvent.setGroupId(groupId); + raftEvent.setLeader(leader); + raftEvent.setTerm(term); + raftEvent.setRaftClusterInfo(raftClusterInfo); + raftEvent.setErrMsg(errMsg); + return raftEvent; + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftSysConstants.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftSysConstants.java new file mode 100644 index 00000000..2f03ecee --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/RaftSysConstants.java @@ -0,0 +1,234 @@ +/* + * 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.distributed.raft; + +/** + * jraft system constants. + * + * @author liaochuntao + */ +@SuppressWarnings("all") +public final class RaftSysConstants { + + // ========= default setting value ========= // + + /** + * {@link RaftSysConstants#RAFT_ELECTION_TIMEOUT_MS} + */ + public static final int DEFAULT_ELECTION_TIMEOUT = 5000; + + /** + * {@link RaftSysConstants#RAFT_SNAPSHOT_INTERVAL_SECS} + */ + public static final int DEFAULT_RAFT_SNAPSHOT_INTERVAL_SECS = 30 * 60; + + /** + * {@link RaftSysConstants#RAFT_CLI_SERVICE_THREAD_NUM} + */ + public static final int DEFAULT_RAFT_CLI_SERVICE_THREAD_NUM = 8; + + /** + * {@link RaftSysConstants#RAFT_READ_INDEX_TYPE} + */ + public static final String DEFAULT_READ_INDEX_TYPE = "ReadOnlySafe"; + + /** + * {@link RaftSysConstants#RAFT_RPC_REQUEST_TIMEOUT_MS} + */ + public static final int DEFAULT_RAFT_RPC_REQUEST_TIMEOUT_MS = 5000; + + /** + * The maximum size of each file RPC (snapshot copy) request between nodes is 128 K by default 节点之间每次文件 RPC + * (snapshot拷贝)请求的最大大小,默认为 128 K + */ + public static final int DEFAULT_MAX_BYTE_COUNT_PER_RPC = 128 * 1024; + + /** + * The maximum number of logs sent from the leader to the followers is 1024 by default 从 leader 往 follower + * 发送的最大日志个数,默认 1024 + */ + public static final int DEFAULT_MAX_ENTRIES_SIZE = 1024; + + /** + * The maximum body size of the log sent from the leader to the followers is 512K by default 从 leader 往 follower + * 发送日志的最大 body 大小,默认 512K + */ + public static final int DEFAULT_MAX_BODY_SIZE = 512 * 1024; + + /** + * The maximum size of the log storage buffer is 256K by default 日志存储缓冲区最大大小,默认256K + */ + public static final int DEFAULT_MAX_APPEND_BUFFER_SIZE = 256 * 1024; + + /** + * The election timer interval will be a random maximum outside the specified time, 1 second by default + * 选举定时器间隔会在指定时间之外随机的最大范围,默认1秒 + */ + public static final int DEFAULT_MAX_ELECTION_DELAY_MS = 1000; + + /** + * Specifies the ratio of the election timeout to the heartbeat interval. Heartbeat interval is equal to the + * electionTimeoutMs/electionHeartbeatFactor, default one of 10 points. 指定选举超时时间和心跳间隔时间之间的比值。心跳间隔等于electionTimeoutMs/electionHeartbeatFactor,默认10分之一。 + */ + public static final int DEFAULT_ELECTION_HEARTBEAT_FACTOR = 10; + + /** + * The tasks submitted to the leader will accumulate one batch into the maximum batch size stored in the log, and 32 + * tasks will be assigned by default 向 leader 提交的任务累积一个批次刷入日志存储的最大批次大小,默认 32 个任务 + */ + public static final int DEFAULT_APPLY_BATCH = 32; + + /** + * Call fsync when necessary when writing log, meta information, and it should always be true 写入日志、元信息的时候必要的时候调用 + * fsync,通常都应该为 true + */ + public static final boolean DEFAULT_SYNC = true; + + /** + * If fsync is called by writing snapshot/raft information, the default is false. If sync is true, it is better to + * respect sync 写入 snapshot/raft 元信息是否调用 fsync,默认为 false,在 sync 为 true 的情况下,优选尊重 sync + */ + public static final boolean DEFAULT_SYNC_META = false; + + /** + * Internal disruptor buffer size, need to be appropriately adjusted for high write throughput applications, default + * 16384 内部 disruptor buffer 大小,如果是写入吞吐量较高的应用,需要适当调高该值,默认 16384 + */ + public static final int DEFAULT_DISRUPTOR_BUFFER_SIZE = 16384; + + /** + * Whether to enable replicated pipeline request optimization by default 是否启用复制的 pipeline 请求优化,默认打开 + */ + public static final boolean DEFAULT_REPLICATOR_PIPELINE = true; + + /** + * Maximum in-flight requests with pipeline requests enabled, 256 by default 在启用 pipeline 请求情况下,最大 in-flight + * 请求数,默认256 + */ + public static final int DEFAULT_MAX_REPLICATOR_INFLIGHT_MSGS = 256; + + /** + * Whether LogEntry checksum is enabled 是否启用 LogEntry checksum + */ + public static final boolean DEFAULT_ENABLE_LOG_ENTRY_CHECKSUM = false; + + // ========= setting key ========= // + + /** + * Election timeout in milliseconds + */ + public static final String RAFT_ELECTION_TIMEOUT_MS = "election_timeout_ms"; + + /** + * Snapshot interval in seconds + */ + public static final String RAFT_SNAPSHOT_INTERVAL_SECS = "snapshot_interval_secs"; + + /** + * Requested retries + */ + public static final String REQUEST_FAILOVER_RETRIES = "request_failoverRetries"; + + /** + * raft internal worker threads + */ + public static final String RAFT_CORE_THREAD_NUM = "core_thread_num"; + + /** + * Number of threads required for raft business request processing + */ + public static final String RAFT_CLI_SERVICE_THREAD_NUM = "cli_service_thread_num"; + + /** + * raft linear read strategy, defaults to read_index read + */ + public static final String RAFT_READ_INDEX_TYPE = "read_index_type"; + + /** + * rpc request timeout, default 5 seconds + */ + public static final String RAFT_RPC_REQUEST_TIMEOUT_MS = "rpc_request_timeout_ms"; + + /** + * Maximum size of each file RPC (snapshot copy) request between nodes, default is 128 K + */ + public static final String MAX_BYTE_COUNT_PER_RPC = "max_byte_count_per_rpc"; + + /** + * Maximum number of logs sent from leader to follower, default is 1024 + */ + public static final String MAX_ENTRIES_SIZE = "max_entries_size"; + + /** + * Maximum body size for sending logs from leader to follower, default is 512K + */ + public static final String MAX_BODY_SIZE = "max_body_size"; + + /** + * Maximum log storage buffer size, default 256K + */ + public static final String MAX_APPEND_BUFFER_SIZE = "max_append_buffer_size"; + + /** + * Election timer interval will be a random maximum outside the specified time, default is 1 second + */ + public static final String MAX_ELECTION_DELAY_MS = "max_election_delay_ms"; + + /** + * Specify the ratio between election timeout and heartbeat interval. Heartbeat interval is equal to + * electionTimeoutMs/electionHeartbeatFactor,One tenth by default. + */ + public static final String ELECTION_HEARTBEAT_FACTOR = "election_heartbeat_factor"; + + /** + * The tasks submitted to the leader accumulate the maximum batch size of a batch flush log storage. The default is + * 32 tasks. + */ + public static final String APPLY_BATCH = "apply_batch"; + + /** + * Call fsync when necessary when writing logs and meta information, usually should be true + */ + public static final String SYNC = "sync"; + + /** + * Whether to write snapshot / raft meta-information to call fsync. The default is false. When sync is true, it is + * preferred to respect sync. + */ + public static final String SYNC_META = "sync_meta"; + + /** + * Internal disruptor buffer size. For applications with high write throughput, you need to increase this value. The + * default value is 16384. + */ + public static final String DISRUPTOR_BUFFER_SIZE = "disruptor_buffer_size"; + + /** + * Whether to enable replication of pipeline request optimization, which is enabled by default + */ + public static final String REPLICATOR_PIPELINE = "replicator_pipeline"; + + /** + * Maximum number of in-flight requests with pipeline requests enabled, default is 256 + */ + public static final String MAX_REPLICATOR_INFLIGHT_MSGS = "max_replicator_inflight_msgs"; + + /** + * Whether to enable LogEntry checksum + */ + public static final String ENABLE_LOG_ENTRY_CHECKSUM = "enable_log_entry_checksum"; +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/DuplicateRaftGroupException.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/DuplicateRaftGroupException.java new file mode 100644 index 00000000..a6e98b83 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/DuplicateRaftGroupException.java @@ -0,0 +1,31 @@ +/* + * 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.distributed.raft.exception; + +/** + * Duplicate groupId when creating Raft Group throws this exception. + * + * @author liaochuntao + */ +public class DuplicateRaftGroupException extends RuntimeException { + + private static final long serialVersionUID = -6276695537457486790L; + + public DuplicateRaftGroupException(String group) { + super("The Raft Group [" + group + "] is already used"); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/JRaftException.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/JRaftException.java new file mode 100644 index 00000000..d201fd9a --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/JRaftException.java @@ -0,0 +1,47 @@ +/* + * 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.distributed.raft.exception; + +/** + * Abnormal JRaft. + * + * @author liaochuntao + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JRaftException extends RuntimeException { + + private static final long serialVersionUID = 8802314713344513544L; + + public JRaftException() { + } + + public JRaftException(String message) { + super(message); + } + + public JRaftException(String message, Throwable cause) { + super(message, cause); + } + + public JRaftException(Throwable cause) { + super(cause); + } + + public JRaftException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/NoLeaderException.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/NoLeaderException.java new file mode 100644 index 00000000..86ec8e08 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/NoLeaderException.java @@ -0,0 +1,46 @@ +/* + * 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.distributed.raft.exception; + +/** + * This exception is thrown if the current Raft Group Cluster does not elect a leader. + * + * @author liaochuntao + */ +public class NoLeaderException extends Exception { + + private static final long serialVersionUID = 1755681688785678765L; + + public NoLeaderException() { + } + + public NoLeaderException(String group) { + super("The Raft Group [" + group + "] did not find the Leader node"); + } + + public NoLeaderException(String message, Throwable cause) { + super(message, cause); + } + + public NoLeaderException(Throwable cause) { + super(cause); + } + + public NoLeaderException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/NoSuchRaftGroupException.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/NoSuchRaftGroupException.java new file mode 100644 index 00000000..625ea733 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/exception/NoSuchRaftGroupException.java @@ -0,0 +1,47 @@ +/* + * 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.distributed.raft.exception; + +/** + * no this raft group exception. + * + * @author liaochuntao + */ +public class NoSuchRaftGroupException extends RuntimeException { + + private static final long serialVersionUID = 1755681688785678765L; + + public NoSuchRaftGroupException() { + } + + public NoSuchRaftGroupException(String message) { + super(message); + } + + public NoSuchRaftGroupException(String message, Throwable cause) { + super(message, cause); + } + + public NoSuchRaftGroupException(Throwable cause) { + super(cause); + } + + public NoSuchRaftGroupException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/AbstractProcessor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/AbstractProcessor.java new file mode 100644 index 00000000..ed087686 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/AbstractProcessor.java @@ -0,0 +1,95 @@ +/* + * 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.distributed.raft.processor; + +import com.alibaba.nacos.consistency.Serializer; +import com.alibaba.nacos.consistency.entity.Response; +import com.alibaba.nacos.core.distributed.raft.JRaftServer; +import com.alibaba.nacos.core.distributed.raft.utils.FailoverClosure; +import com.alibaba.nacos.core.utils.Loggers; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.rpc.RpcContext; +import com.google.protobuf.Message; + +import java.util.Objects; + +/** + * abstract rpc processor. + * + * @author liaochuntao + */ +public abstract class AbstractProcessor { + + private final Serializer serializer; + + public AbstractProcessor(Serializer serializer) { + this.serializer = serializer; + } + + protected void handleRequest(final JRaftServer server, final String group, final RpcContext rpcCtx, Message message) { + try { + final JRaftServer.RaftGroupTuple tuple = server.findTupleByGroup(group); + if (Objects.isNull(tuple)) { + rpcCtx.sendResponse(Response.newBuilder().setSuccess(false) + .setErrMsg("Could not find the corresponding Raft Group : " + group).build()); + return; + } + if (tuple.getNode().isLeader()) { + execute(server, rpcCtx, message, tuple); + } else { + rpcCtx.sendResponse( + Response.newBuilder().setSuccess(false).setErrMsg("Could not find leader : " + group).build()); + } + } catch (Throwable e) { + Loggers.RAFT.error("handleRequest has error : ", e); + rpcCtx.sendResponse(Response.newBuilder().setSuccess(false).setErrMsg(e.toString()).build()); + } + } + + protected void execute(JRaftServer server, final RpcContext asyncCtx, final Message message, + final JRaftServer.RaftGroupTuple tuple) { + FailoverClosure closure = new FailoverClosure() { + + Response data; + + Throwable ex; + + @Override + public void setResponse(Response data) { + this.data = data; + } + + @Override + public void setThrowable(Throwable throwable) { + this.ex = throwable; + } + + @Override + public void run(Status status) { + if (Objects.nonNull(ex)) { + Loggers.RAFT.error("execute has error : ", ex); + asyncCtx.sendResponse(Response.newBuilder().setErrMsg(ex.toString()).setSuccess(false).build()); + } else { + asyncCtx.sendResponse(data); + } + } + }; + + server.applyOperation(tuple.getNode(), message, closure); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosGetRequestProcessor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosGetRequestProcessor.java new file mode 100644 index 00000000..59d82682 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosGetRequestProcessor.java @@ -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.distributed.raft.processor; + +import com.alibaba.nacos.consistency.ProtoMessageUtil; +import com.alibaba.nacos.consistency.Serializer; +import com.alibaba.nacos.consistency.entity.GetRequest; +import com.alibaba.nacos.core.distributed.raft.JRaftServer; +import com.alipay.sofa.jraft.rpc.RpcContext; +import com.alipay.sofa.jraft.rpc.RpcProcessor; + +/** + * deal with {@link GetRequest}. + * + * @author liaochuntao + */ +@Deprecated +public class NacosGetRequestProcessor extends AbstractProcessor implements RpcProcessor { + + private static final String INTEREST_NAME = GetRequest.class.getName(); + + private final JRaftServer server; + + public NacosGetRequestProcessor(JRaftServer server, Serializer serializer) { + super(serializer); + this.server = server; + } + + @Override + public void handleRequest(final RpcContext rpcCtx, GetRequest request) { + handleRequest(server, request.getGroup(), rpcCtx, ProtoMessageUtil.convertToReadRequest(request)); + } + + @Override + public String interest() { + return INTEREST_NAME; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosLogProcessor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosLogProcessor.java new file mode 100644 index 00000000..a57c6ab0 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosLogProcessor.java @@ -0,0 +1,53 @@ +/* + * 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.distributed.raft.processor; + +import com.alibaba.nacos.consistency.ProtoMessageUtil; +import com.alibaba.nacos.consistency.Serializer; +import com.alibaba.nacos.consistency.entity.Log; +import com.alibaba.nacos.core.distributed.raft.JRaftServer; +import com.alipay.sofa.jraft.rpc.RpcContext; +import com.alipay.sofa.jraft.rpc.RpcProcessor; + +/** + * deal with {@link Log}. + * + * @author liaochuntao + */ +@Deprecated +public class NacosLogProcessor extends AbstractProcessor implements RpcProcessor { + + private static final String INTEREST_NAME = Log.class.getName(); + + private final JRaftServer server; + + public NacosLogProcessor(JRaftServer server, Serializer serializer) { + super(serializer); + this.server = server; + } + + @Override + public void handleRequest(final RpcContext rpcCtx, Log log) { + handleRequest(server, log.getGroup(), rpcCtx, ProtoMessageUtil.convertToWriteRequest(log)); + } + + @Override + public String interest() { + return INTEREST_NAME; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosReadRequestProcessor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosReadRequestProcessor.java new file mode 100644 index 00000000..fd0a61c7 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosReadRequestProcessor.java @@ -0,0 +1,50 @@ +/* + * 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.distributed.raft.processor; + +import com.alibaba.nacos.consistency.Serializer; +import com.alibaba.nacos.consistency.entity.ReadRequest; +import com.alibaba.nacos.core.distributed.raft.JRaftServer; +import com.alipay.sofa.jraft.rpc.RpcContext; +import com.alipay.sofa.jraft.rpc.RpcProcessor; + +/** + * nacos request processor for {@link ReadRequest}. + * + * @author liaochuntao + */ +public class NacosReadRequestProcessor extends AbstractProcessor implements RpcProcessor { + + private static final String INTEREST_NAME = ReadRequest.class.getName(); + + private final JRaftServer server; + + public NacosReadRequestProcessor(JRaftServer server, Serializer serializer) { + super(serializer); + this.server = server; + } + + @Override + public void handleRequest(RpcContext rpcCtx, ReadRequest request) { + handleRequest(server, request.getGroup(), rpcCtx, request); + } + + @Override + public String interest() { + return INTEREST_NAME; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosWriteRequestProcessor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosWriteRequestProcessor.java new file mode 100644 index 00000000..6562db2d --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/processor/NacosWriteRequestProcessor.java @@ -0,0 +1,50 @@ +/* + * 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.distributed.raft.processor; + +import com.alibaba.nacos.consistency.Serializer; +import com.alibaba.nacos.consistency.entity.WriteRequest; +import com.alibaba.nacos.core.distributed.raft.JRaftServer; +import com.alipay.sofa.jraft.rpc.RpcContext; +import com.alipay.sofa.jraft.rpc.RpcProcessor; + +/** + * nacos request processor for {@link WriteRequest}. + * + * @author liaochuntao + */ +public class NacosWriteRequestProcessor extends AbstractProcessor implements RpcProcessor { + + private static final String INTEREST_NAME = WriteRequest.class.getName(); + + private final JRaftServer server; + + public NacosWriteRequestProcessor(JRaftServer server, Serializer serializer) { + super(serializer); + this.server = server; + } + + @Override + public void handleRequest(RpcContext rpcCtx, WriteRequest request) { + handleRequest(server, request.getGroup(), rpcCtx, request); + } + + @Override + public String interest() { + return INTEREST_NAME; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/FailoverClosure.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/FailoverClosure.java new file mode 100644 index 00000000..f07c4186 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/FailoverClosure.java @@ -0,0 +1,43 @@ +/* + * 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.distributed.raft.utils; + +import com.alibaba.nacos.consistency.entity.Response; +import com.alipay.sofa.jraft.Closure; + +/** + * Failure callback based on Closure. + * + * @author liaochuntao + */ +public interface FailoverClosure extends Closure { + + /** + * Set the return interface if needed. + * + * @param response {@link Response} data + */ + void setResponse(Response response); + + /** + * Catch exception. + * + * @param throwable {@link Throwable} + */ + void setThrowable(Throwable throwable); + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/FailoverClosureImpl.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/FailoverClosureImpl.java new file mode 100644 index 00000000..f3545eba --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/FailoverClosureImpl.java @@ -0,0 +1,64 @@ +/* + * 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.distributed.raft.utils; + +import com.alibaba.nacos.consistency.entity.Response; +import com.alibaba.nacos.consistency.exception.ConsistencyException; +import com.alipay.sofa.jraft.Status; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * Closure with internal retry mechanism. + * + * @author liaochuntao + */ +public class FailoverClosureImpl implements FailoverClosure { + + private final CompletableFuture future; + + private volatile Response data; + + private volatile Throwable throwable; + + public FailoverClosureImpl(final CompletableFuture future) { + this.future = future; + } + + @Override + public void setResponse(Response data) { + this.data = data; + } + + @Override + public void setThrowable(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public void run(Status status) { + if (status.isOk()) { + future.complete(data); + return; + } + final Throwable throwable = this.throwable; + future.completeExceptionally(Objects.nonNull(throwable) ? new ConsistencyException(throwable.getMessage()) + : new ConsistencyException("operation failure")); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftConstants.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftConstants.java new file mode 100644 index 00000000..7c525eb8 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftConstants.java @@ -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.distributed.raft.utils; + +/** + * constant. + * + * @author liaochuntao + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JRaftConstants { + + public static final String JRAFT_EXTEND_INFO_KEY = JRaftLogOperation.class.getCanonicalName(); + + public static final String GROUP_ID = "groupId"; + + public static final String COMMAND_NAME = "command"; + + public static final String COMMAND_VALUE = "value"; + + public static final String TRANSFER_LEADER = "transferLeader"; + + public static final String RESET_RAFT_CLUSTER = "restRaftCluster"; + + public static final String DO_SNAPSHOT = "doSnapshot"; + + public static final String REMOVE_PEER = "removePeer"; + + public static final String REMOVE_PEERS = "removePeers"; + + public static final String CHANGE_PEERS = "changePeers"; + + /** + * resetPeers. + */ + public static final String RESET_PEERS = "resetPeers"; + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftLogOperation.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftLogOperation.java new file mode 100644 index 00000000..0e7eb499 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftLogOperation.java @@ -0,0 +1,31 @@ +/* + * 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.distributed.raft.utils; + +/** + * JRaft for additional information on logging operations. + * + * @author liaochuntao + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JRaftLogOperation { + + public static final String MODIFY_OPERATION = "modify"; + + public static final String READ_OPERATION = "read"; + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftOps.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftOps.java new file mode 100644 index 00000000..b0959ffb --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftOps.java @@ -0,0 +1,189 @@ +/* + * 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.distributed.raft.utils; + +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.model.RestResultUtils; +import com.alipay.sofa.jraft.CliService; +import com.alipay.sofa.jraft.JRaftUtils; +import com.alipay.sofa.jraft.Node; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.entity.PeerId; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * jraft maintain service. + * + * @author liaochuntao + */ +@SuppressWarnings("all") +public enum JRaftOps { + + TRANSFER_LEADER(JRaftConstants.TRANSFER_LEADER) { + @Override + public RestResult execute(CliService cliService, String groupId, Node node, Map args) { + final Configuration conf = node.getOptions().getInitialConf(); + final PeerId leader = PeerId.parsePeer(args.get(JRaftConstants.COMMAND_VALUE)); + Status status = cliService.transferLeader(groupId, conf, leader); + if (status.isOk()) { + return RestResultUtils.success(); + } + return RestResultUtils.failed(status.getErrorMsg()); + } + }, + + RESET_RAFT_CLUSTER(JRaftConstants.RESET_RAFT_CLUSTER) { + @Override + public RestResult execute(CliService cliService, String groupId, Node node, Map args) { + final Configuration conf = node.getOptions().getInitialConf(); + final String peerIds = args.get(JRaftConstants.COMMAND_VALUE); + Configuration newConf = JRaftUtils.getConfiguration(peerIds); + Status status = cliService.changePeers(groupId, conf, newConf); + if (status.isOk()) { + return RestResultUtils.success(); + } + return RestResultUtils.failed(status.getErrorMsg()); + } + }, + + DO_SNAPSHOT(JRaftConstants.DO_SNAPSHOT) { + @Override + public RestResult execute(CliService cliService, String groupId, Node node, Map args) { + final Configuration conf = node.getOptions().getInitialConf(); + final PeerId peerId = PeerId.parsePeer(args.get(JRaftConstants.COMMAND_VALUE)); + Status status = cliService.snapshot(groupId, peerId); + if (status.isOk()) { + return RestResultUtils.success(); + } + return RestResultUtils.failed(status.getErrorMsg()); + } + }, + + REMOVE_PEER(JRaftConstants.REMOVE_PEER) { + @Override + public RestResult execute(CliService cliService, String groupId, Node node, Map args) { + final Configuration conf = node.getOptions().getInitialConf(); + + List peerIds = cliService.getPeers(groupId, conf); + + final PeerId waitRemove = PeerId.parsePeer(args.get(JRaftConstants.COMMAND_VALUE)); + + if (!peerIds.contains(waitRemove)) { + return RestResultUtils.success(); + } + + Status status = cliService.removePeer(groupId, conf, waitRemove); + if (status.isOk()) { + return RestResultUtils.success(); + } + return RestResultUtils.failed(status.getErrorMsg()); + } + }, + + REMOVE_PEERS(JRaftConstants.REMOVE_PEERS) { + @Override + public RestResult execute(CliService cliService, String groupId, Node node, Map args) { + final Configuration conf = node.getOptions().getInitialConf(); + final String peers = args.get(JRaftConstants.COMMAND_VALUE); + for (String s : peers.split(",")) { + + List peerIds = cliService.getPeers(groupId, conf); + final PeerId waitRemove = PeerId.parsePeer(s); + + if (!peerIds.contains(waitRemove)) { + continue; + } + + Status status = cliService.removePeer(groupId, conf, waitRemove); + if (!status.isOk()) { + return RestResultUtils.failed(status.getErrorMsg()); + } + } + return RestResultUtils.success(); + } + }, + + CHANGE_PEERS(JRaftConstants.CHANGE_PEERS) { + @Override + public RestResult execute(CliService cliService, String groupId, Node node, Map args) { + final Configuration conf = node.getOptions().getInitialConf(); + final Configuration newConf = new Configuration(); + String peers = args.get(JRaftConstants.COMMAND_VALUE); + for (String peer : peers.split(",")) { + newConf.addPeer(PeerId.parsePeer(peer.trim())); + } + + if (Objects.equals(conf, newConf)) { + return RestResultUtils.success(); + } + + Status status = cliService.changePeers(groupId, conf, newConf); + if (status.isOk()) { + return RestResultUtils.success(); + } + return RestResultUtils.failed(status.getErrorMsg()); + } + }, + + /** + * resetPeers. + *

+ * Use only in very urgent situations where availability is more important! + * https://www.sofastack.tech/projects/sofa-jraft/jraft-user-guide/#7.3 + *

+ */ + RESET_PEERS(JRaftConstants.RESET_PEERS) { + @Override + public RestResult execute(CliService cliService, String groupId, Node node, Map args) { + final Configuration newConf = new Configuration(); + String peers = args.get(JRaftConstants.COMMAND_VALUE); + for (String peer : peers.split(",")) { + newConf.addPeer(PeerId.parsePeer(peer.trim())); + } + + final PeerId nodePeerId = node.getNodeId().getPeerId(); + Status status = cliService.resetPeer(groupId, nodePeerId, newConf); + if (status.isOk()) { + return RestResultUtils.success(); + } + return RestResultUtils.failed(status.getErrorMsg()); + } + }; + + private String name; + + JRaftOps(String name) { + this.name = name; + } + + public static JRaftOps sourceOf(String command) { + for (JRaftOps enums : JRaftOps.values()) { + if (Objects.equals(command, enums.name)) { + return enums; + } + } + return null; + } + + public RestResult execute(CliService cliService, String groupId, Node node, Map args) { + return RestResultUtils.success(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftUtils.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftUtils.java new file mode 100644 index 00000000..f0782ef4 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/JRaftUtils.java @@ -0,0 +1,152 @@ +/* + * 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.distributed.raft.utils; + +import com.alibaba.nacos.common.utils.ThreadUtils; +import com.alibaba.nacos.consistency.SerializeFactory; +import com.alibaba.nacos.consistency.entity.GetRequest; +import com.alibaba.nacos.consistency.entity.Log; +import com.alibaba.nacos.consistency.entity.ReadRequest; +import com.alibaba.nacos.consistency.entity.Response; +import com.alibaba.nacos.consistency.entity.WriteRequest; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.distributed.raft.JRaftServer; +import com.alibaba.nacos.core.distributed.raft.processor.NacosGetRequestProcessor; +import com.alibaba.nacos.core.distributed.raft.processor.NacosLogProcessor; +import com.alibaba.nacos.core.distributed.raft.processor.NacosReadRequestProcessor; +import com.alibaba.nacos.core.distributed.raft.processor.NacosWriteRequestProcessor; +import com.alibaba.nacos.sys.utils.ApplicationUtils; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.utils.DiskUtils; +import com.alipay.sofa.jraft.CliService; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.rpc.RaftRpcServerFactory; +import com.alipay.sofa.jraft.rpc.RpcServer; +import com.alipay.sofa.jraft.rpc.impl.GrpcRaftRpcFactory; +import com.alipay.sofa.jraft.rpc.impl.MarshallerRegistry; +import com.alipay.sofa.jraft.util.RpcFactoryHelper; + +import java.io.File; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * JRaft utils. + * + * @author liaochuntao + */ +@SuppressWarnings("all") +public class JRaftUtils { + + public static RpcServer initRpcServer(JRaftServer server, PeerId peerId) { + GrpcRaftRpcFactory raftRpcFactory = (GrpcRaftRpcFactory) RpcFactoryHelper.rpcFactory(); + raftRpcFactory.registerProtobufSerializer(Log.class.getName(), Log.getDefaultInstance()); + raftRpcFactory.registerProtobufSerializer(GetRequest.class.getName(), GetRequest.getDefaultInstance()); + raftRpcFactory.registerProtobufSerializer(WriteRequest.class.getName(), WriteRequest.getDefaultInstance()); + raftRpcFactory.registerProtobufSerializer(ReadRequest.class.getName(), ReadRequest.getDefaultInstance()); + raftRpcFactory.registerProtobufSerializer(Response.class.getName(), Response.getDefaultInstance()); + + MarshallerRegistry registry = raftRpcFactory.getMarshallerRegistry(); + registry.registerResponseInstance(Log.class.getName(), Response.getDefaultInstance()); + registry.registerResponseInstance(GetRequest.class.getName(), Response.getDefaultInstance()); + + registry.registerResponseInstance(WriteRequest.class.getName(), Response.getDefaultInstance()); + registry.registerResponseInstance(ReadRequest.class.getName(), Response.getDefaultInstance()); + + final RpcServer rpcServer = raftRpcFactory.createRpcServer(peerId.getEndpoint()); + RaftRpcServerFactory.addRaftRequestProcessors(rpcServer, RaftExecutor.getRaftCoreExecutor(), + RaftExecutor.getRaftCliServiceExecutor()); + + // Deprecated + rpcServer.registerProcessor(new NacosLogProcessor(server, SerializeFactory.getDefault())); + // Deprecated + rpcServer.registerProcessor(new NacosGetRequestProcessor(server, SerializeFactory.getDefault())); + + rpcServer.registerProcessor(new NacosWriteRequestProcessor(server, SerializeFactory.getDefault())); + rpcServer.registerProcessor(new NacosReadRequestProcessor(server, SerializeFactory.getDefault())); + + return rpcServer; + } + + public static final void initDirectory(String parentPath, String groupName, NodeOptions copy) { + final String logUri = Paths.get(parentPath, groupName, "log").toString(); + final String snapshotUri = Paths.get(parentPath, groupName, "snapshot").toString(); + final String metaDataUri = Paths.get(parentPath, groupName, "meta-data").toString(); + + // Initialize the raft file storage path for different services + try { + DiskUtils.forceMkdir(new File(logUri)); + DiskUtils.forceMkdir(new File(snapshotUri)); + DiskUtils.forceMkdir(new File(metaDataUri)); + } catch (Exception e) { + Loggers.RAFT.error("Init Raft-File dir have some error, cause: ", e); + throw new RuntimeException(e); + } + + copy.setLogUri(logUri); + copy.setRaftMetaUri(metaDataUri); + copy.setSnapshotUri(snapshotUri); + } + + public static List toStrings(List peerIds) { + return peerIds.stream().map(peerId -> peerId.getEndpoint().toString()).collect(Collectors.toList()); + } + + public static void joinCluster(CliService cliService, Collection members, Configuration conf, String group, + PeerId self) { + ServerMemberManager memberManager = ApplicationUtils.getBean(ServerMemberManager.class); + if (!memberManager.isFirstIp()) { + return; + } + Set peerIds = new HashSet<>(); + for (String s : members) { + peerIds.add(PeerId.parsePeer(s)); + } + peerIds.remove(self); + for (; ; ) { + if (peerIds.isEmpty()) { + return; + } + conf = RouteTable.getInstance().getConfiguration(group); + Iterator iterator = peerIds.iterator(); + while (iterator.hasNext()) { + final PeerId peerId = iterator.next(); + + if (conf.contains(peerId)) { + iterator.remove(); + continue; + } + + Status status = cliService.addPeer(group, conf, peerId); + if (status.isOk()) { + iterator.remove(); + } + } + ThreadUtils.sleep(1000L); + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/RaftExecutor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/RaftExecutor.java new file mode 100644 index 00000000..4a269794 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/RaftExecutor.java @@ -0,0 +1,110 @@ +/* + * 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.distributed.raft.utils; + +import com.alibaba.nacos.common.executor.ExecutorFactory; +import com.alibaba.nacos.common.executor.NameThreadFactory; +import com.alibaba.nacos.core.distributed.raft.JRaftServer; +import com.alibaba.nacos.core.distributed.raft.RaftConfig; +import com.alibaba.nacos.core.distributed.raft.RaftSysConstants; +import com.alibaba.nacos.core.utils.ClassUtils; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * raft executor. + * + * @author liaochuntao + */ +public final class RaftExecutor { + + private static ExecutorService raftCoreExecutor; + + private static ExecutorService raftCliServiceExecutor; + + private static ScheduledExecutorService raftCommonExecutor; + + private static ExecutorService raftSnapshotExecutor; + + private static final String OWNER = ClassUtils.getCanonicalName(JRaftServer.class); + + private RaftExecutor() { + } + + /** + * init raft executor by {@link RaftConfig}. + * + * @param config {@link RaftConfig} + */ + public static void init(RaftConfig config) { + + int raftCoreThreadNum = Integer.parseInt(config.getValOfDefault(RaftSysConstants.RAFT_CORE_THREAD_NUM, "8")); + int raftCliServiceThreadNum = Integer + .parseInt(config.getValOfDefault(RaftSysConstants.RAFT_CLI_SERVICE_THREAD_NUM, "4")); + + raftCoreExecutor = ExecutorFactory.Managed.newFixedExecutorService(OWNER, raftCoreThreadNum, + new NameThreadFactory("com.alibaba.nacos.core.raft-core")); + + raftCliServiceExecutor = ExecutorFactory.Managed.newFixedExecutorService(OWNER, raftCliServiceThreadNum, + new NameThreadFactory("com.alibaba.nacos.core.raft-cli-service")); + + raftCommonExecutor = ExecutorFactory.Managed.newScheduledExecutorService(OWNER, 8, + new NameThreadFactory("com.alibaba.nacos.core.protocol.raft-common")); + + int snapshotNum = raftCoreThreadNum / 2; + snapshotNum = snapshotNum == 0 ? raftCoreThreadNum : snapshotNum; + + raftSnapshotExecutor = ExecutorFactory.Managed.newFixedExecutorService(OWNER, snapshotNum, + new NameThreadFactory("com.alibaba.nacos.core.raft-snapshot")); + + } + + public static void scheduleRaftMemberRefreshJob(Runnable runnable, long initialDelay, long period, TimeUnit unit) { + raftCommonExecutor.scheduleAtFixedRate(runnable, initialDelay, period, unit); + } + + public static ExecutorService getRaftCoreExecutor() { + return raftCoreExecutor; + } + + public static ExecutorService getRaftCliServiceExecutor() { + return raftCliServiceExecutor; + } + + public static void executeByCommon(Runnable r) { + raftCommonExecutor.execute(r); + } + + public static void scheduleByCommon(Runnable r, long delayMs) { + raftCommonExecutor.schedule(r, delayMs, TimeUnit.MILLISECONDS); + } + + public static void scheduleAtFixedRateByCommon(Runnable command, long initialDelayMs, long periodMs) { + raftCommonExecutor.scheduleAtFixedRate(command, initialDelayMs, periodMs, TimeUnit.MILLISECONDS); + } + + public static ScheduledExecutorService getRaftCommonExecutor() { + return raftCommonExecutor; + } + + public static void doSnapshot(Runnable runnable) { + raftSnapshotExecutor.execute(runnable); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/RaftOptionsBuilder.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/RaftOptionsBuilder.java new file mode 100644 index 00000000..57a65f04 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/RaftOptionsBuilder.java @@ -0,0 +1,125 @@ +/* + * 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.distributed.raft.utils; + +import com.alibaba.nacos.common.utils.ConvertUtils; +import com.alibaba.nacos.core.distributed.raft.RaftConfig; +import com.alibaba.nacos.core.distributed.raft.RaftSysConstants; +import com.alipay.sofa.jraft.option.RaftOptions; +import com.alipay.sofa.jraft.option.ReadOnlyOption; +import com.alibaba.nacos.common.utils.StringUtils; + +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.APPLY_BATCH; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_APPLY_BATCH; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_DISRUPTOR_BUFFER_SIZE; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_ELECTION_HEARTBEAT_FACTOR; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_ENABLE_LOG_ENTRY_CHECKSUM; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_MAX_APPEND_BUFFER_SIZE; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_MAX_BODY_SIZE; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_MAX_BYTE_COUNT_PER_RPC; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_MAX_ELECTION_DELAY_MS; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_MAX_ENTRIES_SIZE; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_MAX_REPLICATOR_INFLIGHT_MSGS; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_REPLICATOR_PIPELINE; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_SYNC; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DEFAULT_SYNC_META; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.DISRUPTOR_BUFFER_SIZE; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.ELECTION_HEARTBEAT_FACTOR; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.ENABLE_LOG_ENTRY_CHECKSUM; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.MAX_APPEND_BUFFER_SIZE; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.MAX_BODY_SIZE; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.MAX_BYTE_COUNT_PER_RPC; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.MAX_ELECTION_DELAY_MS; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.MAX_ENTRIES_SIZE; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.MAX_REPLICATOR_INFLIGHT_MSGS; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.REPLICATOR_PIPELINE; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.SYNC; +import static com.alibaba.nacos.core.distributed.raft.RaftSysConstants.SYNC_META; + +/** + * build {@link RaftOptions}. + * + * @author liaochuntao + */ +public class RaftOptionsBuilder { + + /** + * By {@link RaftConfig} creating a {@link RaftOptions}. + * + * @param config {@link RaftConfig} + * @return {@link RaftOptions} + */ + public static RaftOptions initRaftOptions(RaftConfig config) { + RaftOptions raftOptions = new RaftOptions(); + raftOptions.setReadOnlyOptions(raftReadIndexType(config)); + + raftOptions.setMaxByteCountPerRpc( + ConvertUtils.toInt(config.getVal(MAX_BYTE_COUNT_PER_RPC), DEFAULT_MAX_BYTE_COUNT_PER_RPC)); + + raftOptions.setMaxEntriesSize(ConvertUtils.toInt(config.getVal(MAX_ENTRIES_SIZE), DEFAULT_MAX_ENTRIES_SIZE)); + + raftOptions.setMaxBodySize(ConvertUtils.toInt(config.getVal(MAX_BODY_SIZE), DEFAULT_MAX_BODY_SIZE)); + + raftOptions.setMaxAppendBufferSize( + ConvertUtils.toInt(config.getVal(MAX_APPEND_BUFFER_SIZE), DEFAULT_MAX_APPEND_BUFFER_SIZE)); + + raftOptions.setMaxElectionDelayMs( + ConvertUtils.toInt(config.getVal(MAX_ELECTION_DELAY_MS), DEFAULT_MAX_ELECTION_DELAY_MS)); + + raftOptions.setElectionHeartbeatFactor( + ConvertUtils.toInt(config.getVal(ELECTION_HEARTBEAT_FACTOR), DEFAULT_ELECTION_HEARTBEAT_FACTOR)); + + raftOptions.setApplyBatch(ConvertUtils.toInt(config.getVal(APPLY_BATCH), DEFAULT_APPLY_BATCH)); + + raftOptions.setSync(ConvertUtils.toBoolean(config.getVal(SYNC), DEFAULT_SYNC)); + + raftOptions.setSyncMeta(ConvertUtils.toBoolean(config.getVal(SYNC_META), DEFAULT_SYNC_META)); + + raftOptions.setDisruptorBufferSize( + ConvertUtils.toInt(config.getVal(DISRUPTOR_BUFFER_SIZE), DEFAULT_DISRUPTOR_BUFFER_SIZE)); + + raftOptions.setReplicatorPipeline( + ConvertUtils.toBoolean(config.getVal(REPLICATOR_PIPELINE), DEFAULT_REPLICATOR_PIPELINE)); + + raftOptions.setMaxReplicatorInflightMsgs( + ConvertUtils.toInt(config.getVal(MAX_REPLICATOR_INFLIGHT_MSGS), DEFAULT_MAX_REPLICATOR_INFLIGHT_MSGS)); + + raftOptions.setEnableLogEntryChecksum( + ConvertUtils.toBoolean(config.getVal(ENABLE_LOG_ENTRY_CHECKSUM), DEFAULT_ENABLE_LOG_ENTRY_CHECKSUM)); + + return raftOptions; + } + + private static ReadOnlyOption raftReadIndexType(RaftConfig config) { + String readOnySafe = "ReadOnlySafe"; + String readOnlyLeaseBased = "ReadOnlyLeaseBased"; + + String val = config.getVal(RaftSysConstants.RAFT_READ_INDEX_TYPE); + + if (StringUtils.isBlank(val) || StringUtils.equals(readOnySafe, val)) { + return ReadOnlyOption.ReadOnlySafe; + } + + if (StringUtils.equals(readOnlyLeaseBased, val)) { + return ReadOnlyOption.ReadOnlyLeaseBased; + } + throw new IllegalArgumentException("Illegal Raft system parameters => ReadOnlyOption" + " : [" + val + + "], should be 'ReadOnlySafe' or 'ReadOnlyLeaseBased'"); + + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/RetryRunner.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/RetryRunner.java new file mode 100644 index 00000000..d55c9020 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/distributed/raft/utils/RetryRunner.java @@ -0,0 +1,32 @@ +/* + * 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.distributed.raft.utils; + +/** + * Retry function. + * + * @author liaochuntao + */ +@FunctionalInterface +public interface RetryRunner { + + /** + * Tasks that require retry. + */ + void run(); + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/exception/ErrorCode.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/exception/ErrorCode.java new file mode 100644 index 00000000..a81b7bac --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/exception/ErrorCode.java @@ -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 liaochuntao + */ +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; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/exception/KvStorageException.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/exception/KvStorageException.java new file mode 100644 index 00000000..9402f336 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/exception/KvStorageException.java @@ -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 liaochuntao + */ +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(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/exception/SnakflowerException.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/exception/SnakflowerException.java new file mode 100644 index 00000000..fdd5dcce --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/exception/SnakflowerException.java @@ -0,0 +1,46 @@ +/* + * 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; + +/** + * SnakflowerException. + * + * @author liaochuntao + */ +public class SnakflowerException extends RuntimeException { + + public SnakflowerException() { + super(); + } + + public SnakflowerException(String message) { + super(message); + } + + public SnakflowerException(String message, Throwable cause) { + super(message, cause); + } + + public SnakflowerException(Throwable cause) { + super(cause); + } + + protected SnakflowerException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/listener/LoggingApplicationListener.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/listener/LoggingApplicationListener.java new file mode 100644 index 00000000..d0968b13 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/listener/LoggingApplicationListener.java @@ -0,0 +1,80 @@ +/* + * 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.listener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; + +import static org.springframework.boot.context.logging.LoggingApplicationListener.CONFIG_PROPERTY; +import static org.springframework.core.io.ResourceLoader.CLASSPATH_URL_PREFIX; + +/** + * For init logging configuration. + * + * @author horizonzy + * @since 1.4.1 + */ +public class LoggingApplicationListener implements NacosApplicationListener { + + private static final String DEFAULT_NACOS_LOGBACK_LOCATION = CLASSPATH_URL_PREFIX + "META-INF/logback/nacos.xml"; + + private static final Logger LOGGER = LoggerFactory.getLogger(LoggingApplicationListener.class); + + @Override + public void starting() { + + } + + @Override + public void environmentPrepared(ConfigurableEnvironment environment) { + if (!environment.containsProperty(CONFIG_PROPERTY)) { + System.setProperty(CONFIG_PROPERTY, DEFAULT_NACOS_LOGBACK_LOCATION); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("There is no property named \"{}\" in Spring Boot Environment, " + + "and whose value is {} will be set into System's Properties", CONFIG_PROPERTY, + DEFAULT_NACOS_LOGBACK_LOCATION); + } + } + } + + @Override + public void contextPrepared(ConfigurableApplicationContext context) { + + } + + @Override + public void contextLoaded(ConfigurableApplicationContext context) { + + } + + @Override + public void started(ConfigurableApplicationContext context) { + + } + + @Override + public void running(ConfigurableApplicationContext context) { + + } + + @Override + public void failed(ConfigurableApplicationContext context, Throwable exception) { + + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/listener/NacosApplicationListener.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/listener/NacosApplicationListener.java new file mode 100644 index 00000000..96d29a24 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/listener/NacosApplicationListener.java @@ -0,0 +1,78 @@ +/* + * 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.listener; + +import com.alibaba.nacos.core.code.SpringApplicationRunListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * Nacos Application Listener, execute init process. + * + * @author horizonzy + * @since 1.4.1 + */ +public interface NacosApplicationListener { + + /** + * {@link SpringApplicationRunListener#starting}. + */ + void starting(); + + /** + * {@link SpringApplicationRunListener#environmentPrepared}. + * + * @param environment environment + */ + void environmentPrepared(ConfigurableEnvironment environment); + + /** + * {@link SpringApplicationRunListener#contextLoaded}. + * + * @param context context + */ + void contextPrepared(ConfigurableApplicationContext context); + + /** + * {@link SpringApplicationRunListener#contextLoaded}. + * + * @param context context + */ + void contextLoaded(ConfigurableApplicationContext context); + + /** + * {@link SpringApplicationRunListener#started}. + * + * @param context context + */ + void started(ConfigurableApplicationContext context); + + /** + * {@link SpringApplicationRunListener#running}. + * + * @param context context + */ + void running(ConfigurableApplicationContext context); + + /** + * {@link SpringApplicationRunListener#failed}. + * + * @param context context + * @param exception exception + */ + void failed(ConfigurableApplicationContext context, Throwable exception); +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/listener/StartingApplicationListener.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/listener/StartingApplicationListener.java new file mode 100644 index 00000000..fa1e21b6 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/listener/StartingApplicationListener.java @@ -0,0 +1,269 @@ +/* + * 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.listener; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; +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.notify.NotifyCenter; +import com.alibaba.nacos.common.event.ServerConfigChangeEvent; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.file.FileChangeEvent; +import com.alibaba.nacos.sys.file.FileWatcher; +import com.alibaba.nacos.sys.file.WatchFileCenter; +import com.alibaba.nacos.sys.utils.ApplicationUtils; +import com.alibaba.nacos.sys.utils.DiskUtils; +import com.alibaba.nacos.sys.utils.InetUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.env.OriginTrackedMapPropertySource; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * init environment config. + * + * @author hxy1991 + * @since 0.5.0 + */ +public class StartingApplicationListener implements NacosApplicationListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(StartingApplicationListener.class); + + private static final String MODE_PROPERTY_KEY_STAND_MODE = "nacos.mode"; + + private static final String MODE_PROPERTY_KEY_FUNCTION_MODE = "nacos.function.mode"; + + private static final String LOCAL_IP_PROPERTY_KEY = "nacos.local.ip"; + + private static final String NACOS_APPLICATION_CONF = "nacos_application_conf"; + + private static final String NACOS_MODE_STAND_ALONE = "stand alone"; + + private static final String NACOS_MODE_CLUSTER = "cluster"; + + private static final String DEFAULT_FUNCTION_MODE = "All"; + + private static final String DEFAULT_DATABASE = "mysql"; + + private static final String DATASOURCE_PLATFORM_PROPERTY = "spring.datasource.platform"; + + private static final String DEFAULT_DATASOURCE_PLATFORM = ""; + + private static final String DATASOURCE_MODE_EXTERNAL = "external"; + + private static final String DATASOURCE_MODE_EMBEDDED = "embedded"; + + private static final Map SOURCES = new ConcurrentHashMap<>(); + + private ScheduledExecutorService scheduledExecutorService; + + private volatile boolean starting; + + @Override + public void starting() { + starting = true; + } + + @Override + public void environmentPrepared(ConfigurableEnvironment environment) { + makeWorkDir(); + + injectEnvironment(environment); + + loadPreProperties(environment); + + initSystemProperty(); + } + + @Override + public void contextPrepared(ConfigurableApplicationContext context) { + logClusterConf(); + + logStarting(); + } + + @Override + public void contextLoaded(ConfigurableApplicationContext context) { + + } + + @Override + public void started(ConfigurableApplicationContext context) { + starting = false; + + closeExecutor(); + + ApplicationUtils.setStarted(true); + judgeStorageMode(context.getEnvironment()); + } + + @Override + public void running(ConfigurableApplicationContext context) { + } + + @Override + public void failed(ConfigurableApplicationContext context, Throwable exception) { + starting = false; + + makeWorkDir(); + + LOGGER.error("Startup errors : ", exception); + ThreadPoolManager.shutdown(); + WatchFileCenter.shutdown(); + NotifyCenter.shutdown(); + + closeExecutor(); + + context.close(); + + LOGGER.error("Nacos failed to start, please see {} for more details.", + Paths.get(EnvUtil.getNacosHome(), "logs/nacos.log")); + } + + private void injectEnvironment(ConfigurableEnvironment environment) { + EnvUtil.setEnvironment(environment); + } + + private void loadPreProperties(ConfigurableEnvironment environment) { + try { + SOURCES.putAll(EnvUtil.loadProperties(EnvUtil.getApplicationConfFileResource())); + environment.getPropertySources() + .addLast(new OriginTrackedMapPropertySource(NACOS_APPLICATION_CONF, SOURCES)); + registerWatcher(); + } catch (Exception e) { + throw new NacosRuntimeException(NacosException.SERVER_ERROR, e); + } + } + + private void registerWatcher() throws NacosException { + + WatchFileCenter.registerWatcher(EnvUtil.getConfPath(), new FileWatcher() { + @Override + public void onChange(FileChangeEvent event) { + try { + Map tmp = EnvUtil.loadProperties(EnvUtil.getApplicationConfFileResource()); + SOURCES.putAll(tmp); + NotifyCenter.publishEvent(ServerConfigChangeEvent.newEvent()); + } catch (IOException ignore) { + LOGGER.warn("Failed to monitor file ", ignore); + } + } + + @Override + public boolean interest(String context) { + return StringUtils.contains(context, "application.properties"); + } + }); + + } + + private void initSystemProperty() { + if (EnvUtil.getStandaloneMode()) { + System.setProperty(MODE_PROPERTY_KEY_STAND_MODE, NACOS_MODE_STAND_ALONE); + } else { + System.setProperty(MODE_PROPERTY_KEY_STAND_MODE, NACOS_MODE_CLUSTER); + } + if (EnvUtil.getFunctionMode() == null) { + System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, DEFAULT_FUNCTION_MODE); + } else if (EnvUtil.FUNCTION_MODE_CONFIG.equals(EnvUtil.getFunctionMode())) { + System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, EnvUtil.FUNCTION_MODE_CONFIG); + } else if (EnvUtil.FUNCTION_MODE_NAMING.equals(EnvUtil.getFunctionMode())) { + System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, EnvUtil.FUNCTION_MODE_NAMING); + } + + System.setProperty(LOCAL_IP_PROPERTY_KEY, InetUtils.getSelfIP()); + } + + private void logClusterConf() { + if (!EnvUtil.getStandaloneMode()) { + try { + List clusterConf = EnvUtil.readClusterConf(); + LOGGER.info("The server IP list of Nacos is {}", clusterConf); + } catch (IOException e) { + LOGGER.error("read cluster conf fail", e); + } + } + } + + private void closeExecutor() { + if (scheduledExecutorService != null) { + scheduledExecutorService.shutdownNow(); + } + } + + private void makeWorkDir() { + String[] dirNames = new String[] {"logs", "conf", "data"}; + for (String dirName : dirNames) { + LOGGER.info("Nacos Log files: {}", Paths.get(EnvUtil.getNacosHome(), dirName)); + try { + DiskUtils.forceMkdir(new File(Paths.get(EnvUtil.getNacosHome(), dirName).toUri())); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + private void logStarting() { + if (!EnvUtil.getStandaloneMode()) { + + scheduledExecutorService = ExecutorFactory + .newSingleScheduledExecutorService(new NameThreadFactory("com.alibaba.nacos.core.nacos-starting")); + + scheduledExecutorService.scheduleWithFixedDelay(() -> { + if (starting) { + LOGGER.info("Nacos is starting..."); + } + }, 1, 1, TimeUnit.SECONDS); + } + } + + private void judgeStorageMode(ConfigurableEnvironment env) { + + // External data sources are used by default in cluster mode + boolean useExternalStorage = (DEFAULT_DATABASE.equalsIgnoreCase(env.getProperty(DATASOURCE_PLATFORM_PROPERTY, DEFAULT_DATASOURCE_PLATFORM))); + + // must initialize after setUseExternalDB + // This value is true in stand-alone mode and false in cluster mode + // If this value is set to true in cluster mode, nacos's distributed storage engine is turned on + // default value is depend on ${nacos.standalone} + + if (!useExternalStorage) { + boolean embeddedStorage = EnvUtil.getStandaloneMode() || Boolean.getBoolean("embeddedStorage"); + // If the embedded data source storage is not turned on, it is automatically + // upgraded to the external data source storage, as before + if (!embeddedStorage) { + useExternalStorage = true; + } + } + + LOGGER.info("Nacos started successfully in {} mode. use {} storage", + System.getProperty(MODE_PROPERTY_KEY_STAND_MODE), useExternalStorage ? DATASOURCE_MODE_EXTERNAL : DATASOURCE_MODE_EMBEDDED); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/model/request/LogUpdateRequest.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/model/request/LogUpdateRequest.java new file mode 100644 index 00000000..6f8cc674 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/model/request/LogUpdateRequest.java @@ -0,0 +1,45 @@ +/* + * 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.model.request; + +/** + * Request entity for log operator interface. + * + * @author wuzhiguo + */ +public class LogUpdateRequest { + + private String logName; + + private String logLevel; + + public String getLogName() { + return logName; + } + + public void setLogName(String logName) { + this.logName = logName; + } + + public String getLogLevel() { + return logLevel; + } + + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/model/request/LookupUpdateRequest.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/model/request/LookupUpdateRequest.java new file mode 100644 index 00000000..56d6e5bc --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/model/request/LookupUpdateRequest.java @@ -0,0 +1,35 @@ +/* + * 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.model.request; + +/** + * Update member lookup type. + * + * @author wuzhiguo + */ +public class LookupUpdateRequest { + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/model/vo/IdGeneratorVO.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/model/vo/IdGeneratorVO.java new file mode 100644 index 00000000..60a0ddab --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/model/vo/IdGeneratorVO.java @@ -0,0 +1,78 @@ +/* + * 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.model.vo; + +/** + * Id generator vo. + * + * @author wuzhiguo + */ +public class IdGeneratorVO { + + private String resource; + + private IdInfo info; + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public IdInfo getInfo() { + return info; + } + + public void setInfo(IdInfo info) { + this.info = info; + } + + public static class IdInfo { + + private Long currentId; + + private Long workerId; + + public Long getCurrentId() { + return currentId; + } + + public void setCurrentId(Long currentId) { + this.currentId = currentId; + } + + public Long getWorkerId() { + return workerId; + } + + public void setWorkerId(Long workerId) { + this.workerId = workerId; + } + + @Override + public String toString() { + return "IdInfo{" + "currentId=" + currentId + ", workerId=" + workerId + '}'; + } + } + + @Override + public String toString() { + return "IdGeneratorVO{" + "resource='" + resource + '\'' + ", info=" + info + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/monitor/MetricsMonitor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/monitor/MetricsMonitor.java new file mode 100644 index 00000000..dce43301 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/monitor/MetricsMonitor.java @@ -0,0 +1,87 @@ +/* + * 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.monitor; + +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.ImmutableTag; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The Metrics center. + * + * @author liaochuntao + */ +public final class MetricsMonitor { + + private static final DistributionSummary RAFT_READ_INDEX_FAILED; + + private static final DistributionSummary RAFT_FROM_LEADER; + + private static final Timer RAFT_APPLY_LOG_TIMER; + + private static final Timer RAFT_APPLY_READ_TIMER; + + private static AtomicInteger longConnection = new AtomicInteger(); + + static { + RAFT_READ_INDEX_FAILED = NacosMeterRegistry.summary("protocol", "raft_read_index_failed"); + RAFT_FROM_LEADER = NacosMeterRegistry.summary("protocol", "raft_read_from_leader"); + + RAFT_APPLY_LOG_TIMER = NacosMeterRegistry.timer("protocol", "raft_apply_log_timer"); + RAFT_APPLY_READ_TIMER = NacosMeterRegistry.timer("protocol", "raft_apply_read_timer"); + + List tags = new ArrayList<>(); + tags.add(new ImmutableTag("module", "config")); + tags.add(new ImmutableTag("name", "longConnection")); + Metrics.gauge("nacos_monitor", tags, longConnection); + + } + + public static AtomicInteger getLongConnectionMonitor() { + return longConnection; + } + + public static void raftReadIndexFailed() { + RAFT_READ_INDEX_FAILED.record(1); + } + + public static void raftReadFromLeader() { + RAFT_FROM_LEADER.record(1); + } + + public static Timer getRaftApplyLogTimer() { + return RAFT_APPLY_LOG_TIMER; + } + + public static Timer getRaftApplyReadTimer() { + return RAFT_APPLY_READ_TIMER; + } + + public static DistributionSummary getRaftReadIndexFailed() { + return RAFT_READ_INDEX_FAILED; + } + + public static DistributionSummary getRaftFromLeader() { + return RAFT_FROM_LEADER; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/monitor/NacosMeterRegistry.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/monitor/NacosMeterRegistry.java new file mode 100644 index 00000000..c2686c67 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/monitor/NacosMeterRegistry.java @@ -0,0 +1,57 @@ +/* + * 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.monitor; + +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.ImmutableTag; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.composite.CompositeMeterRegistry; + +import java.util.ArrayList; +import java.util.List; + +/** + * Metrics unified usage center. + * + * @author liaochuntao + */ +@SuppressWarnings("all") +public final class NacosMeterRegistry { + + private static final CompositeMeterRegistry METER_REGISTRY = new CompositeMeterRegistry(); + + public static DistributionSummary summary(String module, String name) { + ImmutableTag moduleTag = new ImmutableTag("module", module); + List tags = new ArrayList<>(); + tags.add(moduleTag); + tags.add(new ImmutableTag("name", name)); + return METER_REGISTRY.summary("nacos_monitor", tags); + } + + public static Timer timer(String module, String name) { + ImmutableTag moduleTag = new ImmutableTag("module", module); + List tags = new ArrayList<>(); + tags.add(moduleTag); + tags.add(new ImmutableTag("name", name)); + return METER_REGISTRY.timer("nacos_monitor", tags); + } + + public static CompositeMeterRegistry getMeterRegistry() { + return METER_REGISTRY; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/AbstractRequestFilter.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/AbstractRequestFilter.java new file mode 100644 index 00000000..b8156e06 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/AbstractRequestFilter.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.response.Response; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.PostConstruct; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * interceptor fo request. + * + * @author liuzunfei + * @version $Id: AbstractRequestFilter.java, v 0.1 2020年09月14日 11:46 AM liuzunfei Exp $ + */ +public abstract class AbstractRequestFilter { + + @Autowired + private RequestFilters requestFilters; + + public AbstractRequestFilter() { + } + + @PostConstruct + public void init() { + requestFilters.registerFilter(this); + } + + protected Class getResponseClazz(Class handlerClazz) throws NacosException { + ParameterizedType parameterizedType = (ParameterizedType) handlerClazz.getGenericSuperclass(); + try { + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + return Class.forName(actualTypeArguments[1].getTypeName()); + + } catch (Exception e) { + throw new NacosException(NacosException.SERVER_ERROR, e); + } + } + + protected Method getHandleMethod(Class handlerClazz) throws NacosException { + try { + Method method = handlerClazz.getMethod("handle", Request.class, RequestMeta.class); + return method; + } catch (NoSuchMethodException e) { + throw new NacosException(NacosException.SERVER_ERROR, e); + } + } + + protected Response getDefaultResponseInstance(Class handlerClazz) throws NacosException { + ParameterizedType parameterizedType = (ParameterizedType) handlerClazz.getGenericSuperclass(); + try { + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + return (Response) Class.forName(actualTypeArguments[1].getTypeName()).newInstance(); + + } catch (Exception e) { + throw new NacosException(NacosException.SERVER_ERROR, e); + } + } + + /** + * filter request. + * + * @param request request. + * @param meta request meta. + * @param handlerClazz request handler clazz. + * @return response + * @throws NacosException NacosException. + */ + protected abstract Response filter(Request request, RequestMeta meta, Class handlerClazz) throws NacosException; +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/BaseRpcServer.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/BaseRpcServer.java new file mode 100644 index 00000000..3b6b5066 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/BaseRpcServer.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.common.remote.ConnectionType; +import com.alibaba.nacos.common.remote.PayloadRegistry; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.EnvUtil; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +/** + * abstract rpc server . + * + * @author liuzunfei + * @version $Id: BaseRpcServer.java, v 0.1 2020年07月13日 3:41 PM liuzunfei Exp $ + */ +public abstract class BaseRpcServer { + + static { + PayloadRegistry.init(); + } + + /** + * Start sever. + */ + @PostConstruct + public void start() throws Exception { + String serverName = getClass().getSimpleName(); + Loggers.REMOTE.info("Nacos {} Rpc server starting at port {}", serverName, getServicePort()); + + startServer(); + + Loggers.REMOTE.info("Nacos {} Rpc server started at port {}", serverName, getServicePort()); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + Loggers.REMOTE.info("Nacos {} Rpc server stopping", serverName); + try { + BaseRpcServer.this.stopServer(); + Loggers.REMOTE.info("Nacos {} Rpc server stopped successfully...", serverName); + } catch (Exception e) { + Loggers.REMOTE.error("Nacos {} Rpc server stopped fail...", serverName, e); + } + })); + + } + + /** + * get connection type. + * + * @return connection type. + */ + public abstract ConnectionType getConnectionType(); + + /** + * Start sever. + * + * @throws Exception exception throw if start server fail. + */ + public abstract void startServer() throws Exception; + + /** + * the increase offset of nacos server port for rpc server port. + * + * @return delta port offset of main port. + */ + public abstract int rpcPortOffset(); + + /** + * get service port. + * + * @return service port. + */ + public int getServicePort() { + return EnvUtil.getPort() + rpcPortOffset(); + } + + /** + * Stop Server. + * + * @throws Exception throw if stop server fail. + */ + public final void stopServer() throws Exception { + shutdownServer(); + } + + /** + * the increase offset of nacos server port for rpc server port. + */ + @PreDestroy + public abstract void shutdownServer(); + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ClientConnectionEventListener.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ClientConnectionEventListener.java new file mode 100644 index 00000000..2a92a628 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ClientConnectionEventListener.java @@ -0,0 +1,77 @@ +/* + * Copyright 1999-2020 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.remote; + +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.PostConstruct; + +/** + * ClientConnectionEventListener. + * + * @author liuzunfei + * @version $Id: ClientConnectionEventListener.java, v 0.1 2020年07月16日 3:06 PM liuzunfei Exp $ + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class ClientConnectionEventListener { + + /** + * listener name. + */ + private String name; + + @Autowired + protected ClientConnectionEventListenerRegistry clientConnectionEventListenerRegistry; + + @PostConstruct + public void init() { + clientConnectionEventListenerRegistry.registerClientConnectionEventListener(this); + } + + /** + * Getter method for property name. + * + * @return property value of name + */ + public String getName() { + return name; + } + + /** + * Setter method for property name. + * + * @param name value to be assigned to property name + */ + public void setName(String name) { + this.name = name; + } + + /** + * notified when a client connected. + * + * @param connect connect. + */ + public abstract void clientConnected(Connection connect); + + /** + * notified when a client disconnected. + * + * @param connect connect. + */ + public abstract void clientDisConnected(Connection connect); + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ClientConnectionEventListenerRegistry.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ClientConnectionEventListenerRegistry.java new file mode 100644 index 00000000..3abe92e7 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ClientConnectionEventListenerRegistry.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.core.utils.Loggers; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +/** + * registry for client connection event listeners. + * + * @author liuzunfei + * @version $Id: ClientConnectionEventListenerRegistry.java, v 0.1 2020年07月20日 1:47 PM liuzunfei Exp $ + */ +@Service +public class ClientConnectionEventListenerRegistry { + + final List clientConnectionEventListeners = new ArrayList<>(); + + /** + * notify where a new client connected. + * + * @param connection connection that new created. + */ + public void notifyClientConnected(final Connection connection) { + + for (ClientConnectionEventListener clientConnectionEventListener : clientConnectionEventListeners) { + try { + clientConnectionEventListener.clientConnected(connection); + } catch (Throwable throwable) { + Loggers.REMOTE + .info("[NotifyClientConnected] failed for listener {}", clientConnectionEventListener.getName(), + throwable); + + } + } + + } + + /** + * notify where a new client disconnected. + * + * @param connection connection that disconnected. + */ + public void notifyClientDisConnected(final Connection connection) { + + for (ClientConnectionEventListener clientConnectionEventListener : clientConnectionEventListeners) { + try { + clientConnectionEventListener.clientDisConnected(connection); + } catch (Throwable throwable) { + Loggers.REMOTE.info("[NotifyClientDisConnected] failed for listener {}", + clientConnectionEventListener.getName(), throwable); + } + } + + } + + /** + * register ClientConnectionEventListener. + * + * @param listener listener. + */ + public void registerClientConnectionEventListener(ClientConnectionEventListener listener) { + Loggers.REMOTE.info("[ClientConnectionEventListenerRegistry] registry listener - " + listener.getClass() + .getSimpleName()); + this.clientConnectionEventListeners.add(listener); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/Connection.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/Connection.java new file mode 100644 index 00000000..96eace5e --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/Connection.java @@ -0,0 +1,101 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.api.ability.ClientAbilities; +import com.alibaba.nacos.api.remote.Requester; + +import java.util.Map; + +/** + * Connection. + * + * @author liuzunfei + * @version $Id: Connection.java, v 0.1 2020年07月13日 7:08 PM liuzunfei Exp $ + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class Connection implements Requester { + + private boolean traced = false; + + private ClientAbilities abilities; + + private final ConnectionMeta metaInfo; + + public Connection(ConnectionMeta metaInfo) { + this.metaInfo = metaInfo; + } + + public Map getLabels() { + return metaInfo.getLabels(); + } + + public boolean isTraced() { + return traced; + } + + public void setTraced(boolean traced) { + this.traced = traced; + } + + /** + * get abilities. + * + * @return + */ + public ClientAbilities getAbilities() { + return abilities; + } + + /** + * set abilities. + * + * @param abilities abilities. + */ + public void setAbilities(ClientAbilities abilities) { + this.abilities = abilities; + } + + /** + * check is connected. + * + * @return if connection or not,check the inner connection is active. + */ + public abstract boolean isConnected(); + + /** + * Update last Active Time to now. + */ + public void freshActiveTime() { + metaInfo.setLastActiveTime(System.currentTimeMillis()); + } + + /** + * Getter method for property metaInfo. + * + * @return property value of metaInfo + */ + public ConnectionMeta getMetaInfo() { + return metaInfo; + } + + @Override + public String toString() { + return "Connection{" + "traced=" + traced + ", abilities=" + abilities + ", metaInfo=" + metaInfo + '}'; + } +} + diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ConnectionManager.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ConnectionManager.java new file mode 100644 index 00000000..c3ae2393 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ConnectionManager.java @@ -0,0 +1,759 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.RemoteConstants; +import com.alibaba.nacos.api.remote.RequestCallBack; +import com.alibaba.nacos.api.remote.RpcScheduledExecutor; +import com.alibaba.nacos.api.remote.request.ClientDetectionRequest; +import com.alibaba.nacos.api.remote.request.ConnectResetRequest; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.api.utils.NetUtils; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.Subscriber; +import com.alibaba.nacos.common.remote.exception.ConnectionAlreadyClosedException; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.common.utils.VersionUtils; +import com.alibaba.nacos.core.monitor.MetricsMonitor; +import com.alibaba.nacos.core.remote.event.ConnectionLimitRuleChangeEvent; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.file.FileChangeEvent; +import com.alibaba.nacos.sys.file.FileWatcher; +import com.alibaba.nacos.sys.file.WatchFileCenter; +import com.alibaba.nacos.sys.utils.DiskUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * connect manager. + * + * @author liuzunfei + * @version $Id: ConnectionManager.java, v 0.1 2020年07月13日 7:07 PM liuzunfei Exp $ + */ +@Service +public class ConnectionManager extends Subscriber { + + public static final String RULE_FILE_NAME = "limitRule"; + + /** + * 4 times of client keep alive. + */ + private static final long KEEP_ALIVE_TIME = 20000L; + + /** + * connection limit rule. + */ + private ConnectionLimitRule connectionLimitRule = new ConnectionLimitRule(); + + /** + * current loader adjust count,only effective once,use to re balance. + */ + private int loadClient = -1; + + String redirectAddress = null; + + private Map connectionForClientIp = new ConcurrentHashMap<>(16); + + Map connections = new ConcurrentHashMap<>(); + + @Autowired + private ClientConnectionEventListenerRegistry clientConnectionEventListenerRegistry; + + public ConnectionManager() { + NotifyCenter.registerToPublisher(ConnectionLimitRuleChangeEvent.class, NotifyCenter.ringBufferSize); + NotifyCenter.registerSubscriber(this); + } + + /** + * if monitor detail. + * + * @param clientIp clientIp. + * @return + */ + public boolean traced(String clientIp) { + return connectionLimitRule != null && connectionLimitRule.getMonitorIpList() != null && connectionLimitRule + .getMonitorIpList().contains(clientIp); + } + + @PostConstruct + protected void initLimitRue() { + try { + loadRuleFromLocal(); + registerFileWatch(); + } catch (Exception e) { + Loggers.REMOTE.warn("Fail to init limit rue from local ,error= ", e); + } + } + + /** + * check connection id is valid. + * + * @param connectionId connectionId to be check. + * @return is valid or not. + */ + public boolean checkValid(String connectionId) { + return connections.containsKey(connectionId); + } + + /** + * register a new connect. + * + * @param connectionId connectionId + * @param connection connection + */ + public synchronized boolean register(String connectionId, Connection connection) { + + if (connection.isConnected()) { + if (connections.containsKey(connectionId)) { + return true; + } + if (!checkLimit(connection)) { + return false; + } + if (traced(connection.getMetaInfo().clientIp)) { + connection.setTraced(true); + } + connections.put(connectionId, connection); + connectionForClientIp.get(connection.getMetaInfo().clientIp).getAndIncrement(); + + clientConnectionEventListenerRegistry.notifyClientConnected(connection); + Loggers.REMOTE_DIGEST + .info("new connection registered successfully, connectionId = {},connection={} ", connectionId, + connection); + return true; + + } + return false; + + } + + private boolean checkLimit(Connection connection) { + String clientIp = connection.getMetaInfo().clientIp; + + if (connection.getMetaInfo().isClusterSource()) { + if (!connectionForClientIp.containsKey(clientIp)) { + connectionForClientIp.putIfAbsent(clientIp, new AtomicInteger(0)); + } + return true; + } + if (isOverLimit()) { + return false; + } + + if (!connectionForClientIp.containsKey(clientIp)) { + connectionForClientIp.putIfAbsent(clientIp, new AtomicInteger(0)); + } + + AtomicInteger currentCount = connectionForClientIp.get(clientIp); + + if (connectionLimitRule != null) { + // 1.check rule of specific client ip limit. + if (connectionLimitRule.getCountLimitPerClientIp().containsKey(clientIp)) { + Integer integer = connectionLimitRule.getCountLimitPerClientIp().get(clientIp); + if (integer != null && integer >= 0) { + return currentCount.get() < integer; + } + } + // 2.check rule of specific client app limit. + String appName = connection.getMetaInfo().getAppName(); + if (StringUtils.isNotBlank(appName) && connectionLimitRule.getCountLimitPerClientApp() + .containsKey(appName)) { + Integer integerApp = connectionLimitRule.getCountLimitPerClientApp().get(appName); + if (integerApp != null && integerApp >= 0) { + return currentCount.get() < integerApp; + } + } + + // 3.check rule of default client ip. + int countLimitPerClientIpDefault = connectionLimitRule.getCountLimitPerClientIpDefault(); + return countLimitPerClientIpDefault <= 0 || currentCount.get() < countLimitPerClientIpDefault; + } + + return true; + + } + + /** + * unregister a connection . + * + * @param connectionId connectionId. + */ + public synchronized void unregister(String connectionId) { + Connection remove = this.connections.remove(connectionId); + if (remove != null) { + String clientIp = remove.getMetaInfo().clientIp; + AtomicInteger atomicInteger = connectionForClientIp.get(clientIp); + if (atomicInteger != null) { + int count = atomicInteger.decrementAndGet(); + if (count <= 0) { + connectionForClientIp.remove(clientIp); + } + } + remove.close(); + Loggers.REMOTE_DIGEST.info("[{}]Connection unregistered successfully. ", connectionId); + clientConnectionEventListenerRegistry.notifyClientDisConnected(remove); + } + } + + /** + * get by connection id. + * + * @param connectionId connection id. + * @return connection of the id. + */ + public Connection getConnection(String connectionId) { + return connections.get(connectionId); + } + + /** + * get by client ip. + * + * @param clientIp client ip. + * @return connections of the client ip. + */ + public List getConnectionByIp(String clientIp) { + Set> entries = connections.entrySet(); + List connections = new ArrayList<>(); + for (Map.Entry entry : entries) { + Connection value = entry.getValue(); + if (clientIp.equals(value.getMetaInfo().clientIp)) { + connections.add(value); + } + } + return connections; + } + + /** + * get current connections count. + * + * @return get all connection count + */ + public int getCurrentConnectionCount() { + return this.connections.size(); + } + + /** + * regresh connection active time. + * + * @param connectionId connectionId. + */ + public void refreshActiveTime(String connectionId) { + Connection connection = connections.get(connectionId); + if (connection != null) { + connection.freshActiveTime(); + } + } + + /** + * Start Task:Expel the connection which active Time expire. + */ + @PostConstruct + public void start() { + + // Start UnHealthy Connection Expel Task. + RpcScheduledExecutor.COMMON_SERVER_EXECUTOR.scheduleWithFixedDelay(() -> { + try { + + int totalCount = connections.size(); + Loggers.REMOTE_DIGEST.info("Connection check task start"); + MetricsMonitor.getLongConnectionMonitor().set(totalCount); + Set> entries = connections.entrySet(); + int currentSdkClientCount = currentSdkClientCount(); + boolean isLoaderClient = loadClient >= 0; + int currentMaxClient = isLoaderClient ? loadClient : connectionLimitRule.countLimit; + int expelCount = currentMaxClient < 0 ? 0 : Math.max(currentSdkClientCount - currentMaxClient, 0); + + Loggers.REMOTE_DIGEST + .info("Total count ={}, sdkCount={},clusterCount={}, currentLimit={}, toExpelCount={}", + totalCount, currentSdkClientCount, (totalCount - currentSdkClientCount), + currentMaxClient + (isLoaderClient ? "(loaderCount)" : ""), expelCount); + + List expelClient = new LinkedList<>(); + + Map expelForIp = new HashMap<>(16); + + //1. calculate expel count of ip. + for (Map.Entry entry : entries) { + + Connection client = entry.getValue(); + String appName = client.getMetaInfo().getAppName(); + String clientIp = client.getMetaInfo().getClientIp(); + if (client.getMetaInfo().isSdkSource() && !expelForIp.containsKey(clientIp)) { + //get limit for current ip. + int countLimitOfIp = connectionLimitRule.getCountLimitOfIp(clientIp); + if (countLimitOfIp < 0) { + int countLimitOfApp = connectionLimitRule.getCountLimitOfApp(appName); + countLimitOfIp = countLimitOfApp < 0 ? countLimitOfIp : countLimitOfApp; + } + if (countLimitOfIp < 0) { + countLimitOfIp = connectionLimitRule.getCountLimitPerClientIpDefault(); + } + + if (countLimitOfIp >= 0 && connectionForClientIp.containsKey(clientIp)) { + AtomicInteger currentCountIp = connectionForClientIp.get(clientIp); + if (currentCountIp != null && currentCountIp.get() > countLimitOfIp) { + expelForIp.put(clientIp, new AtomicInteger(currentCountIp.get() - countLimitOfIp)); + } + } + } + } + + Loggers.REMOTE_DIGEST + .info("Check over limit for ip limit rule, over limit ip count={}", expelForIp.size()); + + if (expelForIp.size() > 0) { + Loggers.REMOTE_DIGEST.info("Over limit ip expel info, {}", expelForIp); + } + + Set outDatedConnections = new HashSet<>(); + long now = System.currentTimeMillis(); + //2.get expel connection for ip limit. + for (Map.Entry entry : entries) { + Connection client = entry.getValue(); + String clientIp = client.getMetaInfo().getClientIp(); + AtomicInteger integer = expelForIp.get(clientIp); + if (integer != null && integer.intValue() > 0) { + integer.decrementAndGet(); + expelClient.add(client.getMetaInfo().getConnectionId()); + expelCount--; + } else if (now - client.getMetaInfo().getLastActiveTime() >= KEEP_ALIVE_TIME) { + outDatedConnections.add(client.getMetaInfo().getConnectionId()); + } + + } + + //3. if total count is still over limit. + if (expelCount > 0) { + for (Map.Entry entry : entries) { + Connection client = entry.getValue(); + if (!expelForIp.containsKey(client.getMetaInfo().clientIp) && client.getMetaInfo() + .isSdkSource() && expelCount > 0) { + expelClient.add(client.getMetaInfo().getConnectionId()); + expelCount--; + outDatedConnections.remove(client.getMetaInfo().getConnectionId()); + } + } + } + + String serverIp = null; + String serverPort = null; + if (StringUtils.isNotBlank(redirectAddress) && redirectAddress.contains(Constants.COLON)) { + String[] split = redirectAddress.split(Constants.COLON); + serverIp = split[0]; + serverPort = split[1]; + } + + for (String expelledClientId : expelClient) { + try { + Connection connection = getConnection(expelledClientId); + if (connection != null) { + ConnectResetRequest connectResetRequest = new ConnectResetRequest(); + connectResetRequest.setServerIp(serverIp); + connectResetRequest.setServerPort(serverPort); + connection.asyncRequest(connectResetRequest, null); + Loggers.REMOTE_DIGEST + .info("Send connection reset request , connection id = {},recommendServerIp={}, recommendServerPort={}", + expelledClientId, connectResetRequest.getServerIp(), + connectResetRequest.getServerPort()); + } + + } catch (ConnectionAlreadyClosedException e) { + unregister(expelledClientId); + } catch (Exception e) { + Loggers.REMOTE_DIGEST.error("Error occurs when expel connection, expelledClientId:{}", expelledClientId, e); + } + } + + //4.client active detection. + Loggers.REMOTE_DIGEST.info("Out dated connection ,size={}", outDatedConnections.size()); + if (CollectionUtils.isNotEmpty(outDatedConnections)) { + Set successConnections = new HashSet<>(); + final CountDownLatch latch = new CountDownLatch(outDatedConnections.size()); + for (String outDateConnectionId : outDatedConnections) { + try { + Connection connection = getConnection(outDateConnectionId); + if (connection != null) { + ClientDetectionRequest clientDetectionRequest = new ClientDetectionRequest(); + connection.asyncRequest(clientDetectionRequest, new RequestCallBack() { + @Override + public Executor getExecutor() { + return null; + } + + @Override + public long getTimeout() { + return 1000L; + } + + @Override + public void onResponse(Response response) { + latch.countDown(); + if (response != null && response.isSuccess()) { + connection.freshActiveTime(); + successConnections.add(outDateConnectionId); + } + } + + @Override + public void onException(Throwable e) { + latch.countDown(); + } + }); + + Loggers.REMOTE_DIGEST + .info("[{}]send connection active request ", outDateConnectionId); + } else { + latch.countDown(); + } + + } catch (ConnectionAlreadyClosedException e) { + latch.countDown(); + } catch (Exception e) { + Loggers.REMOTE_DIGEST + .error("[{}]Error occurs when check client active detection ,error={}", + outDateConnectionId, e); + latch.countDown(); + } + } + + latch.await(3000L, TimeUnit.MILLISECONDS); + Loggers.REMOTE_DIGEST + .info("Out dated connection check successCount={}", successConnections.size()); + + for (String outDateConnectionId : outDatedConnections) { + if (!successConnections.contains(outDateConnectionId)) { + Loggers.REMOTE_DIGEST + .info("[{}]Unregister Out dated connection....", outDateConnectionId); + unregister(outDateConnectionId); + } + } + } + + //reset loader client + + if (isLoaderClient) { + loadClient = -1; + redirectAddress = null; + } + + Loggers.REMOTE_DIGEST.info("Connection check task end"); + + } catch (Throwable e) { + Loggers.REMOTE.error("Error occurs during connection check... ", e); + } + }, 1000L, 3000L, TimeUnit.MILLISECONDS); + + } + + private RequestMeta buildMeta() { + RequestMeta meta = new RequestMeta(); + meta.setClientVersion(VersionUtils.getFullClientVersion()); + meta.setClientIp(NetUtils.localIP()); + return meta; + } + + public void loadCount(int loadClient, String redirectAddress) { + this.loadClient = loadClient; + this.redirectAddress = redirectAddress; + } + + /** + * send load request to spefic connetionId. + * + * @param connectionId connection id of client. + * @param redirectAddress server address to redirect. + */ + public void loadSingle(String connectionId, String redirectAddress) { + Connection connection = getConnection(connectionId); + + if (connection != null) { + if (connection.getMetaInfo().isSdkSource()) { + ConnectResetRequest connectResetRequest = new ConnectResetRequest(); + if (StringUtils.isNotBlank(redirectAddress) && redirectAddress.contains(Constants.COLON)) { + String[] split = redirectAddress.split(Constants.COLON); + connectResetRequest.setServerIp(split[0]); + connectResetRequest.setServerPort(split[1]); + } + try { + connection.request(connectResetRequest, 3000L); + } catch (ConnectionAlreadyClosedException e) { + unregister(connectionId); + } catch (Exception e) { + Loggers.REMOTE.error("error occurs when expel connection, connectionId: {} ", connectionId, e); + } + } + } + + } + + /** + * get all client count. + * + * @return client count. + */ + public int currentClientsCount() { + return connections.size(); + } + + /** + * get client count with labels filter. + * + * @param filterLabels label to filter client count. + * @return count with the specific filter labels. + */ + public int currentClientsCount(Map filterLabels) { + int count = 0; + for (Connection connection : connections.values()) { + Map labels = connection.getMetaInfo().labels; + boolean disMatchFound = false; + for (Map.Entry entry : filterLabels.entrySet()) { + if (!entry.getValue().equals(labels.get(entry.getKey()))) { + disMatchFound = true; + break; + } + } + if (!disMatchFound) { + count++; + } + } + return count; + } + + /** + * get client count from sdk. + * + * @return sdk client count. + */ + public int currentSdkClientCount() { + Map filter = new HashMap<>(2); + filter.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_SDK); + return currentClientsCount(filter); + } + + public Map currentClients() { + return connections; + } + + /** + * check if over limit. + * + * @return over limit or not. + */ + private boolean isOverLimit() { + return connectionLimitRule.countLimit > 0 && currentSdkClientCount() >= connectionLimitRule.getCountLimit(); + } + + @Override + public void onEvent(ConnectionLimitRuleChangeEvent event) { + String limitRule = event.getLimitRule(); + Loggers.REMOTE.info("connection limit rule change event receive :{}", limitRule); + + try { + ConnectionLimitRule connectionLimitRule = JacksonUtils.toObj(limitRule, ConnectionLimitRule.class); + if (connectionLimitRule != null) { + this.connectionLimitRule = connectionLimitRule; + + try { + saveRuleToLocal(this.connectionLimitRule); + } catch (Exception e) { + Loggers.REMOTE.warn("Fail to save rule to local error is ", e); + } + } else { + Loggers.REMOTE.info("Parse rule is null,Ignore illegal rule :{}", limitRule); + } + + } catch (Exception e) { + Loggers.REMOTE.error("Fail to parse connection limit rule :{}", limitRule, e); + } + } + + @Override + public Class subscribeType() { + return ConnectionLimitRuleChangeEvent.class; + } + + static class ConnectionLimitRule { + + private Set monitorIpList = new HashSet<>(); + + private int countLimit = -1; + + private int countLimitPerClientIpDefault = -1; + + private Map countLimitPerClientIp = new HashMap<>(); + + private Map countLimitPerClientApp = new HashMap<>(); + + public int getCountLimit() { + return countLimit; + } + + public void setCountLimit(int countLimit) { + this.countLimit = countLimit; + } + + public int getCountLimitPerClientIpDefault() { + return countLimitPerClientIpDefault; + } + + public void setCountLimitPerClientIpDefault(int countLimitPerClientIpDefault) { + this.countLimitPerClientIpDefault = countLimitPerClientIpDefault; + } + + public int getCountLimitOfIp(String clientIp) { + if (countLimitPerClientIp.containsKey(clientIp)) { + Integer integer = countLimitPerClientIp.get(clientIp); + if (integer != null && integer >= 0) { + return integer; + } + } + return -1; + } + + public int getCountLimitOfApp(String appName) { + if (countLimitPerClientApp.containsKey(appName)) { + Integer integer = countLimitPerClientApp.get(appName); + if (integer != null && integer >= 0) { + return integer; + } + } + return -1; + } + + public Map getCountLimitPerClientIp() { + return countLimitPerClientIp; + } + + public void setCountLimitPerClientIp(Map countLimitPerClientIp) { + this.countLimitPerClientIp = countLimitPerClientIp; + } + + public Map getCountLimitPerClientApp() { + return countLimitPerClientApp; + } + + public void setCountLimitPerClientApp(Map countLimitPerClientApp) { + this.countLimitPerClientApp = countLimitPerClientApp; + } + + public Set getMonitorIpList() { + return monitorIpList; + } + + public void setMonitorIpList(Set monitorIpList) { + this.monitorIpList = monitorIpList; + } + } + + public ConnectionLimitRule getConnectionLimitRule() { + return connectionLimitRule; + } + + private synchronized void loadRuleFromLocal() throws Exception { + File limitFile = getRuleFile(); + if (!limitFile.exists()) { + limitFile.createNewFile(); + } + + String ruleContent = DiskUtils.readFile(limitFile); + ConnectionLimitRule connectionLimitRule = StringUtils.isBlank(ruleContent) ? new ConnectionLimitRule() + : JacksonUtils.toObj(ruleContent, ConnectionLimitRule.class); + // apply rule. + if (connectionLimitRule != null) { + this.connectionLimitRule = connectionLimitRule; + Set monitorIpList = connectionLimitRule.monitorIpList; + for (Connection connection : this.connections.values()) { + String clientIp = connection.getMetaInfo().getClientIp(); + if (!CollectionUtils.isEmpty(monitorIpList) && monitorIpList.contains(clientIp)) { + connection.setTraced(true); + } else { + connection.setTraced(false); + } + } + + } + Loggers.REMOTE.info("Init loader limit rule from local,rule={}", ruleContent); + + } + + private synchronized void saveRuleToLocal(ConnectionLimitRule limitRule) throws IOException { + + File limitFile = getRuleFile(); + if (!limitFile.exists()) { + limitFile.createNewFile(); + } + DiskUtils.writeFile(limitFile, JacksonUtils.toJson(limitRule).getBytes(Constants.ENCODE), false); + } + + private File getRuleFile() { + File baseDir = new File(EnvUtil.getNacosHome(), "data" + File.separator + "loader" + File.separator); + if (!baseDir.exists()) { + baseDir.mkdir(); + } + return new File(baseDir, RULE_FILE_NAME); + } + + private void registerFileWatch() { + try { + String tpsPath = Paths.get(EnvUtil.getNacosHome(), "data", "loader").toString(); + WatchFileCenter.registerWatcher(tpsPath, new FileWatcher() { + @Override + public void onChange(FileChangeEvent event) { + try { + String fileName = event.getContext().toString(); + if (RULE_FILE_NAME.equals(fileName)) { + loadRuleFromLocal(); + } + } catch (Throwable throwable) { + Loggers.REMOTE.warn("Fail to load rule from local", throwable); + } + } + + @Override + public boolean interest(String context) { + return RULE_FILE_NAME.equals(context); + } + }); + } catch (NacosException e) { + Loggers.REMOTE.warn("Register connection rule fail ", e); + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ConnectionMeta.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ConnectionMeta.java new file mode 100644 index 00000000..2fc085b8 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/ConnectionMeta.java @@ -0,0 +1,303 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.api.remote.RemoteConstants; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; + +/** + * ConnectionMetaInfo. + * + * @author liuzunfei + * @version $Id: ConnectionMetaInfo.java, v 0.1 2020年07月13日 7:28 PM liuzunfei Exp $ + */ +public class ConnectionMeta { + + /** + * ConnectionType. + */ + String connectType; + + /** + * Client IP Address. + */ + String clientIp; + + /** + * Remote IP Address. + */ + String remoteIp; + + /** + * Remote IP Port. + */ + int remotePort; + + /** + * Local Ip Port. + */ + int localPort; + + /** + * Client version. + */ + String version; + + /** + * Identify Unique connectionId. + */ + String connectionId; + + /** + * create time. + */ + Date createTime; + + /** + * lastActiveTime. + */ + long lastActiveTime; + + /** + * String appName. + */ + String appName; + + /** + * tenant. + */ + String tenant; + + protected Map labels = new HashMap<>(); + + public String getLabel(String labelKey) { + return labels.get(labelKey); + } + + public String getTag() { + return labels.get(VIPSERVER_TAG); + } + + public ConnectionMeta(String connectionId, String clientIp, String remoteIp, int remotePort, int localPort, + String connectType, String version, String appName, Map labels) { + this.connectionId = connectionId; + this.clientIp = clientIp; + this.connectType = connectType; + this.version = version; + this.appName = appName; + this.remoteIp = remoteIp; + this.remotePort = remotePort; + this.localPort = localPort; + this.createTime = new Date(); + this.lastActiveTime = System.currentTimeMillis(); + this.labels.putAll(labels); + } + + /** + * check if this connection is sdk source. + * + * @return if this connection is sdk source. + */ + public boolean isSdkSource() { + String source = labels.get(RemoteConstants.LABEL_SOURCE); + return RemoteConstants.LABEL_SOURCE_SDK.equalsIgnoreCase(source); + } + + /** + * check if this connection is sdk source. + * + * @return if this connection is sdk source. + */ + public boolean isClusterSource() { + String source = labels.get(RemoteConstants.LABEL_SOURCE); + return RemoteConstants.LABEL_SOURCE_CLUSTER.equalsIgnoreCase(source); + } + + /** + * Getter method for property labels. + * + * @return property value of labels + */ + public Map getLabels() { + return labels; + } + + /** + * Setter method for property labels. + * + * @param labels value to be assigned to property labels + */ + public void setLabels(Map labels) { + this.labels = labels; + } + + /** + * Getter method for property clientIp. + * + * @return property value of clientIp + */ + public String getClientIp() { + return clientIp; + } + + /** + * Setter method for property clientIp. + * + * @param clientIp value to be assigned to property clientIp + */ + public void setClientIp(String clientIp) { + this.clientIp = clientIp; + } + + /** + * Getter method for property connectionId. + * + * @return property value of connectionId + */ + public String getConnectionId() { + return connectionId; + } + + /** + * Setter method for property connectionId. + * + * @param connectionId value to be assigned to property connectionId + */ + public void setConnectionId(String connectionId) { + this.connectionId = connectionId; + } + + /** + * Getter method for property createTime. + * + * @return property value of createTime + */ + public Date getCreateTime() { + return createTime; + } + + /** + * Setter method for property createTime. + * + * @param createTime value to be assigned to property createTime + */ + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + /** + * Getter method for property lastActiveTime. + * + * @return property value of lastActiveTime + */ + public long getLastActiveTime() { + return lastActiveTime; + } + + /** + * Setter method for property lastActiveTime. + * + * @param lastActiveTime value to be assigned to property lastActiveTime + */ + public void setLastActiveTime(long lastActiveTime) { + this.lastActiveTime = lastActiveTime; + } + + /** + * Getter method for property connectType. + * + * @return property value of connectType + */ + public String getConnectType() { + return connectType; + } + + /** + * Setter method for property connectType. + * + * @param connectType value to be assigned to property connectType + */ + public void setConnectType(String connectType) { + this.connectType = connectType; + } + + /** + * Getter method for property version. + * + * @return property value of version + */ + public String getVersion() { + return version; + } + + /** + * Setter method for property version. + * + * @param version value to be assigned to property version + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * Getter method for property localPort. + * + * @return property value of localPort + */ + public int getLocalPort() { + return localPort; + } + + /** + * Setter method for property localPort. + * + * @param localPort value to be assigned to property localPort + */ + public void setLocalPort(int localPort) { + this.localPort = localPort; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + @Override + public String toString() { + return "ConnectionMeta{" + "connectType='" + connectType + '\'' + ", clientIp='" + clientIp + '\'' + + ", remoteIp='" + remoteIp + '\'' + ", remotePort=" + remotePort + ", localPort=" + localPort + + ", version='" + version + '\'' + ", connectionId='" + connectionId + '\'' + ", createTime=" + + createTime + ", lastActiveTime=" + lastActiveTime + ", appName='" + appName + '\'' + ", tenant='" + + tenant + '\'' + ", labels=" + labels + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/HealthCheckRequestHandler.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/HealthCheckRequestHandler.java new file mode 100644 index 00000000..7baec498 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/HealthCheckRequestHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.request.HealthCheckRequest; +import com.alibaba.nacos.api.remote.response.HealthCheckResponse; +import com.alibaba.nacos.core.remote.control.TpsControl; +import org.springframework.stereotype.Component; + +/** + * push response to clients. + * + * @author liuzunfei + * @version $Id: PushService.java, v 0.1 2021年07月17日 1:12 PM liuzunfei Exp $ + */ +@Component +public class HealthCheckRequestHandler extends RequestHandler { + + @Override + @TpsControl(pointName = "HealthCheck") + public HealthCheckResponse handle(HealthCheckRequest request, RequestMeta meta) { + return new HealthCheckResponse(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RequestFilters.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RequestFilters.java new file mode 100644 index 00000000..4511da8b --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RequestFilters.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2020 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.remote; + +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +/** + * filters of request. + * + * @author liuzunfei + * @version $Id: RequestFilters.java, v 0.1 2020年09月14日 12:00 PM liuzunfei Exp $ + */ +@Service +public class RequestFilters { + + List filters = new ArrayList<>(); + + public void registerFilter(AbstractRequestFilter requestFilter) { + filters.add(requestFilter); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RequestHandler.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RequestHandler.java new file mode 100644 index 00000000..96bd69f8 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RequestHandler.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.core.utils.Loggers; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Nacos based request handler. + * + * @author liuzunfei + * @author xiweng.yy + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class RequestHandler { + + @Autowired + private RequestFilters requestFilters; + + /** + * Handler request. + * + * @param request request + * @param meta request meta data + * @return response + * @throws NacosException nacos exception when handle request has problem. + */ + public Response handleRequest(T request, RequestMeta meta) throws NacosException { + for (AbstractRequestFilter filter : requestFilters.filters) { + try { + Response filterResult = filter.filter(request, meta, this.getClass()); + if (filterResult != null && !filterResult.isSuccess()) { + return filterResult; + } + } catch (Throwable throwable) { + Loggers.REMOTE.error("filter error", throwable); + } + + } + return handle(request, meta); + } + + /** + * Handler request. + * + * @param request request + * @param meta request meta data + * @return response + * @throws NacosException nacos exception when handle request has problem. + */ + public abstract S handle(T request, RequestMeta meta) throws NacosException; + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RequestHandlerRegistry.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RequestHandlerRegistry.java new file mode 100644 index 00000000..84f8639a --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RequestHandlerRegistry.java @@ -0,0 +1,95 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.core.remote.control.TpsControl; +import com.alibaba.nacos.core.remote.control.TpsControlConfig; +import com.alibaba.nacos.core.remote.control.TpsMonitorManager; +import com.alibaba.nacos.core.remote.control.TpsMonitorPoint; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * RequestHandlerRegistry. + * + * @author liuzunfei + * @version $Id: RequestHandlerRegistry.java, v 0.1 2020年07月13日 8:24 PM liuzunfei Exp $ + */ + +@Service +public class RequestHandlerRegistry implements ApplicationListener { + + Map registryHandlers = new HashMap<>(); + + @Autowired + private TpsMonitorManager tpsMonitorManager; + + /** + * Get Request Handler By request Type. + * + * @param requestType see definitions of sub constants classes of RequestTypeConstants + * @return request handler. + */ + public RequestHandler getByRequestType(String requestType) { + return registryHandlers.get(requestType); + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + Map beansOfType = event.getApplicationContext().getBeansOfType(RequestHandler.class); + Collection values = beansOfType.values(); + for (RequestHandler requestHandler : values) { + + Class clazz = requestHandler.getClass(); + boolean skip = false; + while (!clazz.getSuperclass().equals(RequestHandler.class)) { + if (clazz.getSuperclass().equals(Object.class)) { + skip = true; + break; + } + clazz = clazz.getSuperclass(); + } + if (skip) { + continue; + } + + try { + Method method = clazz.getMethod("handle", Request.class, RequestMeta.class); + if (method.isAnnotationPresent(TpsControl.class) && TpsControlConfig.isTpsControlEnabled()) { + TpsControl tpsControl = method.getAnnotation(TpsControl.class); + String pointName = tpsControl.pointName(); + TpsMonitorPoint tpsMonitorPoint = new TpsMonitorPoint(pointName); + tpsMonitorManager.registerTpsControlPoint(tpsMonitorPoint); + } + } catch (Exception e) { + //ignore. + } + Class tClass = (Class) ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0]; + registryHandlers.putIfAbsent(tClass.getSimpleName(), requestHandler); + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RpcAckCallbackSynchronizer.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RpcAckCallbackSynchronizer.java new file mode 100644 index 00000000..d7a6e093 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RpcAckCallbackSynchronizer.java @@ -0,0 +1,132 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.DefaultRequestFuture; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.core.utils.Loggers; +import com.alipay.hessian.clhm.ConcurrentLinkedHashMap; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +/** + * server push ack synchronier. + * + * @author liuzunfei + * @version $Id: RpcAckCallbackSynchronizer.java, v 0.1 2020年07月29日 7:56 PM liuzunfei Exp $ + */ +public class RpcAckCallbackSynchronizer { + + @SuppressWarnings("checkstyle:linelength") + public static final Map> CALLBACK_CONTEXT = new ConcurrentLinkedHashMap.Builder>() + .maximumWeightedCapacity(1000000) + .listener((s, pushCallBack) -> pushCallBack.entrySet().forEach( + stringDefaultPushFutureEntry -> stringDefaultPushFutureEntry.getValue().setFailResult(new TimeoutException()))).build(); + + /** + * notify ack. + */ + public static void ackNotify(String connectionId, Response response) { + + Map stringDefaultPushFutureMap = CALLBACK_CONTEXT.get(connectionId); + if (stringDefaultPushFutureMap == null) { + + Loggers.REMOTE_DIGEST + .warn("Ack receive on a outdated connection ,connection id={},requestId={} ", connectionId, + response.getRequestId()); + return; + } + + DefaultRequestFuture currentCallback = stringDefaultPushFutureMap.remove(response.getRequestId()); + if (currentCallback == null) { + + Loggers.REMOTE_DIGEST + .warn("Ack receive on a outdated request ,connection id={},requestId={} ", connectionId, + response.getRequestId()); + return; + } + + if (response.isSuccess()) { + currentCallback.setResponse(response); + } else { + currentCallback.setFailResult(new NacosException(response.getErrorCode(), response.getMessage())); + } + } + + /** + * notify ackid. + */ + public static void syncCallback(String connectionId, String requestId, DefaultRequestFuture defaultPushFuture) + throws NacosException { + + Map stringDefaultPushFutureMap = initContextIfNecessary(connectionId); + + if (!stringDefaultPushFutureMap.containsKey(requestId)) { + DefaultRequestFuture pushCallBackPrev = stringDefaultPushFutureMap + .putIfAbsent(requestId, defaultPushFuture); + if (pushCallBackPrev == null) { + return; + } + } + throw new NacosException(NacosException.INVALID_PARAM, "request id conflict"); + + } + + /** + * clear context of connectionId. + * + * @param connectionId connectionId + */ + public static void clearContext(String connectionId) { + CALLBACK_CONTEXT.remove(connectionId); + } + + /** + * clear context of connectionId. + * + * @param connectionId connectionId + */ + public static Map initContextIfNecessary(String connectionId) { + if (!CALLBACK_CONTEXT.containsKey(connectionId)) { + Map context = new HashMap<>(128); + Map stringDefaultRequestFutureMap = CALLBACK_CONTEXT + .putIfAbsent(connectionId, context); + return stringDefaultRequestFutureMap == null ? context : stringDefaultRequestFutureMap; + } else { + return CALLBACK_CONTEXT.get(connectionId); + } + } + + /** + * clear context of connectionId. + * + * @param connectionId connectionId + */ + public static void clearFuture(String connectionId, String requestId) { + Map stringDefaultPushFutureMap = CALLBACK_CONTEXT.get(connectionId); + + if (stringDefaultPushFutureMap == null || !stringDefaultPushFutureMap.containsKey(requestId)) { + return; + } + stringDefaultPushFutureMap.remove(requestId); + } + +} + diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RpcPushService.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RpcPushService.java new file mode 100644 index 00000000..d751ac8d --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/RpcPushService.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2020 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.remote; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.AbstractRequestCallBack; +import com.alibaba.nacos.api.remote.request.ServerRequest; +import com.alibaba.nacos.api.remote.PushCallBack; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.common.remote.exception.ConnectionAlreadyClosedException; +import com.alibaba.nacos.core.utils.Loggers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.concurrent.Executor; + +/** + * push response to clients. + * + * @author liuzunfei + * @version $Id: PushService.java, v 0.1 2020年07月20日 1:12 PM liuzunfei Exp $ + */ +@Service +public class RpcPushService { + + @Autowired + private ConnectionManager connectionManager; + + /** + * push response with no ack. + * + * @param connectionId connectionId. + * @param request request. + * @param requestCallBack requestCallBack. + */ + public void pushWithCallback(String connectionId, ServerRequest request, PushCallBack requestCallBack, + Executor executor) { + Connection connection = connectionManager.getConnection(connectionId); + if (connection != null) { + try { + connection.asyncRequest(request, new AbstractRequestCallBack(requestCallBack.getTimeout()) { + + @Override + public Executor getExecutor() { + return executor; + } + + @Override + public void onResponse(Response response) { + if (response.isSuccess()) { + requestCallBack.onSuccess(); + } else { + requestCallBack.onFail(new NacosException(response.getErrorCode(), response.getMessage())); + } + } + + @Override + public void onException(Throwable e) { + requestCallBack.onFail(e); + } + }); + } catch (ConnectionAlreadyClosedException e) { + connectionManager.unregister(connectionId); + requestCallBack.onSuccess(); + } catch (Exception e) { + Loggers.REMOTE_DIGEST + .error("error to send push response to connectionId ={},push response={}", connectionId, + request, e); + requestCallBack.onFail(e); + } + } else { + requestCallBack.onSuccess(); + } + } + + /** + * push response with no ack. + * + * @param connectionId connectionId. + * @param request request. + */ + public void pushWithoutAck(String connectionId, ServerRequest request) { + Connection connection = connectionManager.getConnection(connectionId); + if (connection != null) { + try { + connection.request(request, 3000L); + } catch (ConnectionAlreadyClosedException e) { + connectionManager.unregister(connectionId); + } catch (Exception e) { + Loggers.REMOTE_DIGEST + .error("error to send push response to connectionId ={},push response={}", connectionId, + request, e); + } + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/ClientIpMonitorKey.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/ClientIpMonitorKey.java new file mode 100644 index 00000000..1df477fb --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/ClientIpMonitorKey.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2020 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.remote.control; + +/** + * MonitorType. + * + * @author liuzunfei + * @version $Id: ClientIpMonitorKey.java, v 0.1 2021年01月20日 20:38 PM liuzunfei Exp $ + */ +public class ClientIpMonitorKey extends MonitorKey { + + private static final String TYPE = "clientIp"; + + public ClientIpMonitorKey() { + + } + + public ClientIpMonitorKey(String clientIp) { + this.key = clientIp; + } + + @Override + public String getType() { + return TYPE; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/ConnectionIdMonitorKey.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/ConnectionIdMonitorKey.java new file mode 100644 index 00000000..ec610289 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/ConnectionIdMonitorKey.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2020 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.remote.control; + +/** + * ConnectionIdMonitorKey. + * + * @author liuzunfei + * @version $Id: ConnectionIdMonitorKey.java, v 0.1 2021年01月20日 20:38 PM liuzunfei Exp $ + */ +public class ConnectionIdMonitorKey extends MonitorKey { + + private static final String TYPE = "connectionId"; + + String key; + + public ConnectionIdMonitorKey() { + + } + + public ConnectionIdMonitorKey(String clientIp) { + this.key = clientIp; + } + + @Override + public String getType() { + return TYPE; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MatchMode.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MatchMode.java new file mode 100644 index 00000000..cc721a1b --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MatchMode.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2020 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.remote.control; + +/** + * MatchMode. + * + * @author liuzunfei + * @version $Id: MatchMode.java, v 0.1 2021年01月22日 12:38 PM liuzunfei Exp $ + */ +public enum MatchMode { + + /** + * equal match . + */ + EQUAL("equal", "complete equal."), + + /** + * prefix match. nacosConfig matches "prefix#nacos" + */ + PREFIX("prefix", "prefix match."), + + /** + * postfix match.nacosConfig matches "postfix#Config" + */ + POSTFIX("postfix", "postfix match."), + + /** + * middle fuzzy. nacosTestConfig matches "middlefuzzy#nacos*Config" + */ + MIDDLE_FUZZY("middlefuzzy", "middle fuzzy, both match prefix and postfix."); + + String model; + + String desc; + + MatchMode(String model, String desc) { + this.model = model; + this.desc = desc; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorKey.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorKey.java new file mode 100644 index 00000000..34d8453f --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorKey.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2020 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.remote.control; + +import com.alibaba.nacos.api.common.Constants; + +/** + * MonitorType. + * + * @author liuzunfei + * @version $Id: MonitorKey.java, v 0.1 2021年01月20日 20:38 PM liuzunfei Exp $ + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class MonitorKey { + + String key; + + public MonitorKey() { + + } + + public MonitorKey(String key) { + this.key = key; + } + + /** + * get monitor key type. + * + * @return type. + */ + public abstract String getType(); + + public String getKey() { + return this.key; + } + + public void setKey(String key) { + this.key = key; + } + + public String build() { + return this.getType() + Constants.COLON + this.getKey(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorKeyMatcher.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorKeyMatcher.java new file mode 100644 index 00000000..4094f5d1 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorKeyMatcher.java @@ -0,0 +1,82 @@ +/* + * Copyright 1999-2020 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.remote.control; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.common.utils.StringUtils; + +import java.util.Objects; + +/** + * MatchMode. + * + * @author liuzunfei + * @version $Id: MatchMode.java, v 0.1 2021年01月22日 12:38 PM liuzunfei Exp $ + */ +@SuppressWarnings({"PMD.AbstractClassShouldStartWithAbstractNamingRule", "PMD.UndefineMagicConstantRule"}) +public class MonitorKeyMatcher { + + /** + * if provided monitor key match this monitor ,with monitor type. + * + * @param monitorKey monitorKey. + * @return type matched result. + */ + public static boolean matchWithType(String pattern, String monitorKey) { + String[] typeInPattern = pattern.split(Constants.COLON); + String[] typeInMonitorKey = monitorKey.split(Constants.COLON); + if (!Objects.equals(typeInPattern[0], typeInMonitorKey[0])) { + return false; + } + return match(pattern.substring(pattern.indexOf(Constants.COLON)), + monitorKey.substring(monitorKey.indexOf(Constants.COLON))); + } + + /** + * if provided monitor key match this monitor. + * + * @param monitorKey monitorKey. + * @return matched result. + */ + public static boolean match(String pattern, String monitorKey) { + pattern = pattern.trim(); + monitorKey = monitorKey.trim(); + //"AB",equals. + if (!pattern.contains(Constants.ALL_PATTERN)) { + return pattern.equals(monitorKey.trim()); + } + //"*",match all. + if (pattern.equals(Constants.ALL_PATTERN)) { + return true; + } + String[] split = pattern.split("\\" + Constants.ALL_PATTERN); + + if (split.length == 1) { + //"A*",prefix match. + return monitorKey.startsWith(split[0]); + } else if (split.length == 2) { + //"*A",postfix match. + if (StringUtils.isBlank(split[0])) { + return monitorKey.endsWith(split[1]); + } + return monitorKey.startsWith(split[0]) && monitorKey.endsWith(split[1]); + } + + return false; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorKeyParser.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorKeyParser.java new file mode 100644 index 00000000..9cf773a9 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorKeyParser.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2020 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.remote.control; + +/** + * MonitorType. + * + * @author liuzunfei + * @version $Id: MonitorType.java, v 0.1 2021年01月20日 20:38 PM liuzunfei Exp $ + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class MonitorKeyParser { + + /** + * parse monitor key. + * + * @param arg0 parameters. + * @return monitor key. + */ + public abstract MonitorKey parse(Object... arg0); + +} + + + diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorType.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorType.java new file mode 100644 index 00000000..4aa59834 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/MonitorType.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2020 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.remote.control; + +/** + * MonitorType. + * + * @author liuzunfei + * @version $Id: MonitorType.java, v 0.1 2021年01月12日 20:38 PM liuzunfei Exp $ + */ +public enum MonitorType { + // monitor mode. + MONITOR("monitor", "only monitor ,not reject request."), + //intercept mode. + INTERCEPT("intercept", "reject request if tps over limit"); + + String type; + + String desc; + + MonitorType(String type, String desc) { + this.type = type; + this.desc = desc; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControl.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControl.java new file mode 100644 index 00000000..efd16a62 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControl.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2020 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.remote.control; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * tps control manager. + * + * @author liuzunfei + * @version $Id: TpsControlManager.java, v 0.1 2021年01月09日 12:38 PM liuzunfei Exp $ + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface TpsControl { + + /** + * The action type of the request. + * + * @return action type, default READ + */ + String pointName(); + + /** + * Resource name parser. Should have lower priority than resource(). + * + * @return class type of resource parser + */ + Class[] parsers() default {}; + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlConfig.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlConfig.java new file mode 100644 index 00000000..ceaf9dac --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2020 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.remote.control; + +/** + * tps control manager. + * + * @author liuzunfei + * @version $Id: TpsControlManager.java, v 0.1 2021年01月12日 12:38 PM liuzunfei Exp $ + */ +public class TpsControlConfig { + + /** + * tps control is enabled. + * @return true/false. + */ + public static final boolean isTpsControlEnabled() { + return true; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlRequestFilter.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlRequestFilter.java new file mode 100644 index 00000000..01d7e1aa --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlRequestFilter.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2020 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.remote.control; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.core.remote.AbstractRequestFilter; +import com.alibaba.nacos.core.utils.Loggers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * tps control point. + * + * @author liuzunfei + * @version $Id: TpsControlRequestFilter.java, v 0.1 2021年01月09日 12:38 PM liuzunfei Exp $ + */ +@Service +public class TpsControlRequestFilter extends AbstractRequestFilter { + + @Autowired + private TpsMonitorManager tpsMonitorManager; + + @Override + protected Response filter(Request request, RequestMeta meta, Class handlerClazz) { + + Method method = null; + try { + method = getHandleMethod(handlerClazz); + } catch (NacosException e) { + return null; + } + + if (method.isAnnotationPresent(TpsControl.class) && TpsControlConfig.isTpsControlEnabled()) { + + TpsControl tpsControl = method.getAnnotation(TpsControl.class); + + String pointName = tpsControl.pointName(); + Class[] parsers = tpsControl.parsers(); + List monitorKeys = new ArrayList<>(); + monitorKeys.add(new ClientIpMonitorKey(meta.getClientIp())); + if (parsers != null) { + for (Class clazz : parsers) { + try { + if (MonitorKeyParser.class.isAssignableFrom(clazz)) { + MonitorKey parseKey = ((MonitorKeyParser) (clazz.newInstance())).parse(request, meta); + if (parseKey != null) { + monitorKeys.add(parseKey); + } + } + } catch (Throwable throwable) { + //ignore + } + } + } + + boolean pass = tpsMonitorManager.applyTps(pointName, meta.getConnectionId(), monitorKeys); + + if (!pass) { + Response response; + try { + response = super.getDefaultResponseInstance(handlerClazz); + response.setErrorInfo(NacosException.OVER_THRESHOLD, "Tps Flow restricted"); + return response; + } catch (Exception e) { + Loggers.TPS_CONTROL_DETAIL + .warn("Tps monitor fail , request: {},exception:{}", request.getClass().getSimpleName(), e); + return null; + } + + } + } + + return null; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlRule.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlRule.java new file mode 100644 index 00000000..25f51fde --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlRule.java @@ -0,0 +1,143 @@ +/* + * Copyright 1999-2020 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.remote.control; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * tps control point. + * + * @author liuzunfei + * @version $Id: TpsControlPoint.java, v 0.1 2021年01月09日 12:38 PM liuzunfei Exp $ + */ +public class TpsControlRule { + + private String pointName; + + private Rule pointRule; + + /** + * Pattern,Rule map. + */ + private Map monitorKeyRule = new HashMap<>(); + + public String getPointName() { + return pointName; + } + + public void setPointName(String pointName) { + this.pointName = pointName; + } + + public Rule getPointRule() { + return pointRule; + } + + public void setPointRule(Rule pointRule) { + this.pointRule = pointRule; + } + + public Map getMonitorKeyRule() { + return monitorKeyRule; + } + + public void setMonitorKeyRule(Map monitorKeyRule) { + this.monitorKeyRule = monitorKeyRule; + } + + public static class Rule { + + long maxCount = -1; + + TimeUnit period = TimeUnit.SECONDS; + + public static final String MODEL_FUZZY = "FUZZY"; + + public static final String MODEL_PROTO = "PROTO"; + + String model = MODEL_FUZZY; + + /** + * monitor/intercept. + */ + String monitorType = ""; + + public Rule() { + + } + + public boolean isFuzzyModel() { + return MODEL_FUZZY.equalsIgnoreCase(model); + } + + public boolean isProtoModel() { + return MODEL_PROTO.equalsIgnoreCase(model); + } + + public Rule(long maxCount, TimeUnit period, String model, String monitorType) { + this.maxCount = maxCount; + this.period = period; + this.model = model; + this.monitorType = monitorType; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public TimeUnit getPeriod() { + return period; + } + + public void setPeriod(TimeUnit period) { + this.period = period; + } + + public long getMaxCount() { + return maxCount; + } + + public void setMaxCount(long maxCount) { + this.maxCount = maxCount; + } + + public String getMonitorType() { + return monitorType; + } + + public void setMonitorType(String monitorType) { + this.monitorType = monitorType; + } + + @Override + public String toString() { + return "Rule{" + "maxTps=" + maxCount + ", monitorType='" + monitorType + '\'' + '}'; + } + } + + @Override + public String toString() { + return "TpsControlRule{" + "pointName='" + pointName + '\'' + ", pointRule=" + pointRule + ", monitorKeyRule=" + + monitorKeyRule + '}'; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlRuleChangeEvent.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlRuleChangeEvent.java new file mode 100644 index 00000000..61ea3ffb --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsControlRuleChangeEvent.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2020 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.remote.control; + +import com.alibaba.nacos.common.notify.Event; + +/** + * tps control point. + * + * @author liuzunfei + * @version $Id: TpsControlPoint.java, v 0.1 2021年01月09日 12:38 PM liuzunfei Exp $ + */ +public class TpsControlRuleChangeEvent extends Event { + + String pointName; + + String ruleContent; + + public TpsControlRuleChangeEvent(String pointName, String ruleContent) { + this.pointName = pointName; + this.ruleContent = ruleContent; + } + + public String getPointName() { + return pointName; + } + + public void setPointName(String pointName) { + this.pointName = pointName; + } + + public String getRuleContent() { + return ruleContent; + } + + public void setRuleContent(String ruleContent) { + this.ruleContent = ruleContent; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsMonitorManager.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsMonitorManager.java new file mode 100644 index 00000000..b0455556 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsMonitorManager.java @@ -0,0 +1,325 @@ +/* + * Copyright 1999-2020 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.remote.control; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.common.executor.ExecutorFactory; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.Subscriber; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.common.utils.ThreadUtils; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.file.FileChangeEvent; +import com.alibaba.nacos.sys.file.FileWatcher; +import com.alibaba.nacos.sys.file.WatchFileCenter; +import com.alibaba.nacos.sys.utils.DiskUtils; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * tps control manager. + * + * @author liuzunfei + * @version $Id: TpsControlManager.java, v 0.1 2021年01月09日 12:38 PM liuzunfei Exp $ + */ +@Service +public class TpsMonitorManager extends Subscriber implements DisposableBean { + + public final Map points = new ConcurrentHashMap<>(16); + + private static ScheduledExecutorService executorService = ExecutorFactory.newSingleScheduledExecutorService(r -> { + Thread thread = new Thread(r, "nacos.core.remote.tps.control.reporter"); + thread.setDaemon(true); + return thread; + }); + + public TpsMonitorManager() { + NotifyCenter.registerToPublisher(TpsControlRuleChangeEvent.class, NotifyCenter.ringBufferSize); + NotifyCenter.registerSubscriber(this); + executorService.scheduleWithFixedDelay(new TpsMonitorReporter(), 0, 900, TimeUnit.MILLISECONDS); + registerFileWatch(); + } + + /** + * register point. + * + * @param tpsMonitorPoint tps point. + */ + public void registerTpsControlPoint(TpsMonitorPoint tpsMonitorPoint) { + Loggers.TPS_CONTROL + .info("Register tps control,pointName={}, point={} ", tpsMonitorPoint.getPointName(), tpsMonitorPoint); + try { + loadRuleFromLocal(tpsMonitorPoint); + } catch (IOException e) { + Loggers.TPS_CONTROL + .error("Fail to init rule from local,pointName={},error={}", tpsMonitorPoint.getPointName(), e); + } + points.putIfAbsent(tpsMonitorPoint.getPointName(), tpsMonitorPoint); + + } + + private void registerFileWatch() { + try { + String tpsPath = Paths.get(EnvUtil.getNacosHome(), "data" + File.separator + "tps" + File.separator) + .toString(); + checkBaseDir(); + WatchFileCenter.registerWatcher(tpsPath, new FileWatcher() { + @Override + public void onChange(FileChangeEvent event) { + String fileName = event.getContext().toString(); + try { + + if (points.get(fileName) != null) { + loadRuleFromLocal(points.get(fileName)); + } + } catch (Throwable throwable) { + Loggers.TPS_CONTROL + .warn("Fail to load rule from local,pointName={},error={}", fileName, throwable); + } + } + + @Override + public boolean interest(String context) { + for (String pointName : points.keySet()) { + if (context.equals(pointName)) { + return true; + } + } + return false; + } + }); + } catch (NacosException e) { + Loggers.TPS_CONTROL.warn("Register fire watch fail.", e); + } + } + + /** + * apply tps. + * + * @param clientIp clientIp. + * @param pointName pointName. + * @return pass or not. + */ + public boolean applyTpsForClientIp(String pointName, String connectionId, String clientIp) { + if (points.containsKey(pointName)) { + + return points.get(pointName).applyTps(connectionId, Arrays.asList(new ClientIpMonitorKey(clientIp))); + } + return true; + } + + /** + * apply tps. + * + * @param pointName pointName. + * @param monitorKeyList monitorKeyList. + * @return pass or not. + */ + public boolean applyTps(String pointName, String connectionId, List monitorKeyList) { + if (points.containsKey(pointName)) { + return points.get(pointName).applyTps(connectionId, monitorKeyList); + } + return true; + } + + @Override + public void onEvent(TpsControlRuleChangeEvent event) { + + Loggers.TPS_CONTROL + .info("Tps control rule change event receive,pointName={}, ruleContent={} ", event.getPointName(), + event.ruleContent); + if (event == null || event.getPointName() == null) { + return; + } + try { + TpsControlRule tpsControlRule = StringUtils.isBlank(event.ruleContent) ? new TpsControlRule() + : JacksonUtils.toObj(event.ruleContent, TpsControlRule.class); + if (!points.containsKey(event.getPointName())) { + Loggers.TPS_CONTROL.info("Tps control rule change event ignore,pointName={} ", event.getPointName()); + return; + } + try { + saveRuleToLocal(event.getPointName(), tpsControlRule); + } catch (Throwable throwable) { + Loggers.TPS_CONTROL + .warn("Tps control rule persist fail,pointName={},error={} ", event.getPointName(), throwable); + + } + } catch (Exception e) { + Loggers.TPS_CONTROL.warn("Tps control rule apply error ,error= ", e); + } + + } + + @Override + public Class subscribeType() { + return TpsControlRuleChangeEvent.class; + } + + @Override + public void destroy() throws Exception { + if (executorService == null) { + return; + } + ThreadUtils.shutdownThreadPool(executorService); + } + + class TpsMonitorReporter implements Runnable { + + long lastReportSecond = 0L; + + long lastReportMinutes = 0L; + + @Override + public void run() { + try { + long now = System.currentTimeMillis(); + StringBuilder stringBuilder = new StringBuilder(); + Set> entries = points.entrySet(); + + long tempSecond = 0L; + long tempMinutes = 0L; + + String formatString = TpsMonitorPoint.getTimeFormatOfSecond(now - 1000L); + for (Map.Entry entry : entries) { + TpsMonitorPoint value = entry.getValue(); + //get last second + TpsRecorder.TpsSlot pointSlot = value.getTpsRecorder().getPoint(now - 1000L); + if (pointSlot == null) { + continue; + } + + //already reported. + if (lastReportSecond != 0L && lastReportSecond == pointSlot.time) { + continue; + } + String point = entry.getKey(); + tempSecond = pointSlot.time; + stringBuilder.append(point).append('|').append("point|").append(value.getTpsRecorder().period) + .append('|').append(formatString).append('|') + .append(pointSlot.getCountHolder(point).count.get()).append('|') + .append(pointSlot.getCountHolder(point).interceptedCount.get()).append('\n'); + for (Map.Entry monitorKeyEntry : value.monitorKeysRecorder.entrySet()) { + String monitorPattern = monitorKeyEntry.getKey(); + TpsRecorder ipRecord = monitorKeyEntry.getValue(); + TpsRecorder.TpsSlot keySlot = ipRecord.getPoint(now - ipRecord.period.toMillis(1)); + if (keySlot == null) { + continue; + } + //already reported. + if (ipRecord.period == TimeUnit.SECONDS) { + if (lastReportSecond != 0L && lastReportSecond == keySlot.time) { + continue; + } + } + if (ipRecord.period == TimeUnit.MINUTES) { + if (lastReportMinutes != 0L && lastReportMinutes == keySlot.time) { + continue; + } + } + String timeFormatOfSecond = TpsMonitorPoint.getTimeFormatOfSecond(keySlot.time); + tempMinutes = keySlot.time; + if (ipRecord.isProtoModel()) { + Map keySlots = ((TpsRecorder.MultiKeyTpsSlot) keySlot).keySlots; + for (Map.Entry slotCountHolder : keySlots.entrySet()) { + stringBuilder.append(point).append('|').append(monitorPattern).append('|') + .append(ipRecord.period).append('|').append(timeFormatOfSecond).append('|') + .append(slotCountHolder.getKey()).append('|') + .append(slotCountHolder.getValue().count).append('|') + .append(slotCountHolder.getValue().interceptedCount).append('\n'); + } + + } else { + stringBuilder.append(point).append('|').append(monitorPattern).append('|') + .append(ipRecord.period).append('|').append(timeFormatOfSecond).append('|') + .append(keySlot.getCountHolder(point).count.get()).append('|') + .append(keySlot.getCountHolder(point).interceptedCount.get()).append('\n'); + } + } + } + + if (tempSecond > 0) { + lastReportSecond = tempSecond; + } + if (tempMinutes > 0) { + lastReportMinutes = tempMinutes; + } + if (stringBuilder.length() > 0) { + Loggers.TPS_CONTROL_DIGEST.info("Tps reporting...\n" + stringBuilder.toString()); + } + } catch (Throwable throwable) { + Loggers.TPS_CONTROL_DIGEST.error("Tps reporting error", throwable); + + } + + } + } + + private synchronized void loadRuleFromLocal(TpsMonitorPoint tpsMonitorPoint) throws IOException { + + File pointFile = getRuleFile(tpsMonitorPoint.getPointName()); + if (!pointFile.exists()) { + pointFile.createNewFile(); + } + String ruleContent = DiskUtils.readFile(pointFile); + TpsControlRule tpsControlRule = StringUtils.isBlank(ruleContent) ? new TpsControlRule() + : JacksonUtils.toObj(ruleContent, TpsControlRule.class); + Loggers.TPS_CONTROL.info("Load rule from local,pointName={}, ruleContent={} ", tpsMonitorPoint.getPointName(), + ruleContent); + tpsMonitorPoint.applyRule(tpsControlRule); + + } + + private synchronized void saveRuleToLocal(String pointName, TpsControlRule tpsControlRule) throws IOException { + + File pointFile = getRuleFile(pointName); + if (!pointFile.exists()) { + pointFile.createNewFile(); + } + String content = JacksonUtils.toJson(tpsControlRule); + DiskUtils.writeFile(pointFile, content.getBytes(Constants.ENCODE), false); + Loggers.TPS_CONTROL.info("Save rule to local,pointName={}, ruleContent ={} ", pointName, content); + } + + private File getRuleFile(String pointName) { + File baseDir = checkBaseDir(); + return new File(baseDir, pointName); + } + + private File checkBaseDir() { + File baseDir = new File(EnvUtil.getNacosHome(), "data" + File.separator + "tps" + File.separator); + if (!baseDir.exists()) { + baseDir.mkdirs(); + } + return baseDir; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsMonitorPoint.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsMonitorPoint.java new file mode 100644 index 00000000..acc3d1b3 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsMonitorPoint.java @@ -0,0 +1,298 @@ +/* + * Copyright 1999-2020 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.remote.control; + +import com.alibaba.nacos.core.utils.Loggers; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * tps control point. + * + * @author liuzunfei + * @version $Id: TpsControlPoint.java, v 0.1 2021年01月09日 12:38 PM liuzunfei Exp $ + */ +public class TpsMonitorPoint { + + public static final int DEFAULT_RECORD_SIZE = 10; + + private static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + private long startTime; + + private String pointName; + + private TpsRecorder tpsRecorder; + + public Map monitorKeysRecorder = new HashMap<>(); + + public TpsMonitorPoint(String pointName) { + this(pointName, -1, "monitor"); + } + + public TpsMonitorPoint(String pointName, int maxTps, String monitorType) { + // trim to second,uniform all tps control. + this.startTime = getTrimMillsOfSecond(System.currentTimeMillis()); + this.pointName = pointName; + this.tpsRecorder = new TpsRecorder(startTime, TimeUnit.SECONDS, TpsControlRule.Rule.MODEL_FUZZY, + DEFAULT_RECORD_SIZE); + this.tpsRecorder.setMaxCount(maxTps); + this.tpsRecorder.setMonitorType(monitorType); + } + + /** + * get trim mills of second. + * + * @param timeStamp timestamp milliseconds. + * @return mills of second. + */ + public static long getTrimMillsOfSecond(long timeStamp) { + String millString = String.valueOf(timeStamp); + String substring = millString.substring(0, millString.length() - 3); + return Long.parseLong(substring + "000"); + + } + + /** + * get trim mills of second. + * + * @param timeStamp timestamp milliseconds. + * @return minis of minute. + */ + public static long getTrimMillsOfMinute(long timeStamp) { + String millString = String.valueOf(timeStamp); + String substring = millString.substring(0, millString.length() - 3); + return Long.parseLong(Long.parseLong(substring) / 60 * 60 + "000"); + } + + /** + * get trim mills of second. + * + * @param timeStamp timestamp milliseconds. + * @return mills of hour. + */ + public static long getTrimMillsOfHour(long timeStamp) { + String millString = String.valueOf(timeStamp); + String substring = millString.substring(0, millString.length() - 3); + return Long.parseLong(Long.parseLong(substring) / (60 * 60) * (60 * 60) + "000"); + } + + /** + * get format string "2021-01-16 17:20:21" of timestamp. + * + * @param timeStamp timestamp milliseconds. + * @return datetime string. + */ + public static String getTimeFormatOfSecond(long timeStamp) { + return new SimpleDateFormat(DATETIME_PATTERN).format(new Date(timeStamp)); + } + + private void stopAllMonitorClient() { + monitorKeysRecorder.clear(); + } + + /** + * increase tps. + * + * @param monitorKeys monitorKeys. + * @return check current tps is allowed. + */ + public boolean applyTps(String connectionId, List monitorKeys) { + + long now = System.currentTimeMillis(); + TpsRecorder.TpsSlot currentTps = tpsRecorder.createSlotIfAbsent(now); + + //1.check monitor keys. + List passedSlots = new ArrayList<>(); + for (MonitorKey monitorKey : monitorKeys) { + for (Map.Entry entry : monitorKeysRecorder.entrySet()) { + if (MonitorKeyMatcher.matchWithType(entry.getKey(), monitorKey.build())) { + TpsRecorder tpsRecorderKey = entry.getValue(); + TpsRecorder.TpsSlot currentKeySlot = tpsRecorderKey.createSlotIfAbsent(now); + long maxTpsCount = tpsRecorderKey.getMaxCount(); + TpsRecorder.SlotCountHolder countHolder = currentKeySlot.getCountHolder(monitorKey.build()); + boolean overLimit = maxTpsCount >= 0 && countHolder.count.longValue() >= maxTpsCount; + if (overLimit) { + Loggers.TPS_CONTROL_DETAIL + .info("[{}]Tps over limit ,pointName=[{}],barrier=[{}],monitorModel={},maxTps={}", + connectionId, this.getPointName(), entry.getKey(), + tpsRecorderKey.getMonitorType(), maxTpsCount + "/" + tpsRecorderKey.period); + if (tpsRecorderKey.isInterceptMode()) { + currentKeySlot.getCountHolder(monitorKey.build()).interceptedCount.incrementAndGet(); + currentTps.getCountHolder(monitorKey.build()).interceptedCount.incrementAndGet(); + return false; + } + } else { + passedSlots.add(countHolder); + } + } + } + } + + //2.check total tps. + long maxTps = tpsRecorder.getMaxCount(); + boolean overLimit = maxTps >= 0 && currentTps.getCountHolder(pointName).count.longValue() >= maxTps; + if (overLimit) { + Loggers.TPS_CONTROL_DETAIL + .info("[{}]Tps over limit ,pointName=[{}],barrier=[{}],monitorType={}", connectionId, + this.getPointName(), "pointRule", tpsRecorder.getMonitorType()); + if (tpsRecorder.isInterceptMode()) { + currentTps.getCountHolder(pointName).interceptedCount.incrementAndGet(); + return false; + } + } + + currentTps.getCountHolder(pointName).count.incrementAndGet(); + for (TpsRecorder.SlotCountHolder passedTpsSlot : passedSlots) { + passedTpsSlot.count.incrementAndGet(); + } + //3.check pass. + return true; + } + + public TpsRecorder getTpsRecorder() { + return tpsRecorder; + } + + public String getPointName() { + return pointName; + } + + public void setPointName(String pointName) { + this.pointName = pointName; + } + + /** + * apply tps control rule to this point. + * + * @param newControlRule controlRule. + */ + public synchronized void applyRule(TpsControlRule newControlRule) { + + Loggers.TPS_CONTROL.info("Apply tps control rule parse start,pointName=[{}] ", this.getPointName()); + + //1.reset all monitor point for null. + if (newControlRule == null) { + Loggers.TPS_CONTROL.info("Clear all tps control rule ,pointName=[{}] ", this.getPointName()); + this.tpsRecorder.clearLimitRule(); + this.stopAllMonitorClient(); + return; + } + + //2.check point rule. + TpsControlRule.Rule newPointRule = newControlRule.getPointRule(); + if (newPointRule == null) { + Loggers.TPS_CONTROL.info("Clear point control rule ,pointName=[{}] ", this.getPointName()); + this.tpsRecorder.clearLimitRule(); + } else { + Loggers.TPS_CONTROL.info("Update point control rule ,pointName=[{}],original maxTps={}, new maxTps={}" + + ",original monitorType={}, original monitorType={}, ", this.getPointName(), + this.tpsRecorder.getMaxCount(), newPointRule.maxCount, this.tpsRecorder.getMonitorType(), + newPointRule.monitorType); + + this.tpsRecorder.setMaxCount(newPointRule.maxCount); + this.tpsRecorder.setMonitorType(newPointRule.monitorType); + } + + //3.check monitor key rules. + Map newMonitorKeyRules = newControlRule.getMonitorKeyRule(); + //3.1 clear all monitor keys. + if (newMonitorKeyRules == null || newMonitorKeyRules.isEmpty()) { + Loggers.TPS_CONTROL + .info("Clear point control rule for monitorKeys, pointName=[{}] ", this.getPointName()); + this.stopAllMonitorClient(); + } else { + Map monitorKeysRecorderCurrent = this.monitorKeysRecorder; + + for (Map.Entry newMonitorRule : newMonitorKeyRules.entrySet()) { + if (newMonitorRule.getValue() == null) { + continue; + } + boolean checkPattern = newMonitorRule.getKey() != null; + if (!checkPattern) { + Loggers.TPS_CONTROL.info("Invalid monitor rule, pointName=[{}] ,monitorRule={} ,Ignore this.", + this.getPointName(), newMonitorRule.getKey()); + continue; + } + TpsControlRule.Rule newRule = newMonitorRule.getValue(); + if (newRule.period == null) { + newRule.period = TimeUnit.SECONDS; + } + + if (newRule.model == null) { + newRule.model = TpsControlRule.Rule.MODEL_FUZZY; + } + + //update rule. + if (monitorKeysRecorderCurrent.containsKey(newMonitorRule.getKey())) { + TpsRecorder tpsRecorder = monitorKeysRecorderCurrent.get(newMonitorRule.getKey()); + Loggers.TPS_CONTROL + .info("Update point control rule for client ip ,pointName=[{}],monitorKey=[{}],original maxTps={}" + + ", new maxTps={},original monitorType={}, new monitorType={}, ", + this.getPointName(), newMonitorRule.getKey(), tpsRecorder.getMaxCount(), + newRule.maxCount, tpsRecorder.getMonitorType(), newRule.monitorType); + + if (!Objects.equals(tpsRecorder.period, newRule.period) || !Objects + .equals(tpsRecorder.getModel(), newRule.model)) { + TpsRecorder tpsRecorderNew = new TpsRecorder(startTime, newRule.period, newRule.model, + DEFAULT_RECORD_SIZE); + tpsRecorderNew.setMaxCount(newRule.maxCount); + tpsRecorderNew.setMonitorType(newRule.monitorType); + monitorKeysRecorderCurrent.put(newMonitorRule.getKey(), tpsRecorderNew); + } else { + tpsRecorder.setMaxCount(newRule.maxCount); + tpsRecorder.setMonitorType(newRule.monitorType); + } + + } else { + Loggers.TPS_CONTROL + .info("Add point control rule for client ip ,pointName=[{}],monitorKey=[{}], new maxTps={}, new monitorType={}, ", + this.getPointName(), newMonitorRule.getKey(), newMonitorRule.getValue().maxCount, + newMonitorRule.getValue().monitorType); + // add rule + TpsRecorder tpsRecorderAdd = new TpsRecorder(startTime, newRule.period, newRule.model, + DEFAULT_RECORD_SIZE); + tpsRecorderAdd.setMaxCount(newRule.maxCount); + tpsRecorderAdd.setMonitorType(newRule.monitorType); + monitorKeysRecorderCurrent.put(newMonitorRule.getKey(), tpsRecorderAdd); + } + } + + //delete rule. + Iterator> iteratorCurrent = monitorKeysRecorderCurrent.entrySet().iterator(); + while (iteratorCurrent.hasNext()) { + Map.Entry next1 = iteratorCurrent.next(); + if (!newMonitorKeyRules.containsKey(next1.getKey())) { + Loggers.TPS_CONTROL.info("Delete point control rule for pointName=[{}] ,monitorKey=[{}]", + this.getPointName(), next1.getKey()); + iteratorCurrent.remove(); + } + } + + } + + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsRecorder.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsRecorder.java new file mode 100644 index 00000000..cf7a71c8 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/control/TpsRecorder.java @@ -0,0 +1,220 @@ +/* + * Copyright 1999-2020 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.remote.control; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * tps record. + * + * @author liuzunfei + * @version $Id: TpsRecorder.java, v 0.1 2021年01月09日 12:38 PM liuzunfei Exp $ + */ +public class TpsRecorder { + + private long startTime; + + TimeUnit period; + + private int slotSize; + + private List slotList; + + private long maxCount = -1; + + private String model; + + /** + * monitor/intercept. + */ + private String monitorType = MonitorType.MONITOR.type; + + public TpsRecorder(long startTime, TimeUnit period, String model, int recordSize) { + + this.startTime = startTime; + if (period.equals(TimeUnit.MINUTES)) { + this.startTime = TpsMonitorPoint.getTrimMillsOfMinute(startTime); + } + if (period.equals(TimeUnit.HOURS)) { + this.startTime = TpsMonitorPoint.getTrimMillsOfHour(startTime); + } + this.period = period; + this.model = model; + this.slotSize = recordSize + 1; + slotList = new ArrayList<>(slotSize); + for (int i = 0; i < slotSize; i++) { + slotList.add(isProtoModel() ? new MultiKeyTpsSlot() : new TpsSlot()); + } + } + + public boolean isProtoModel() { + return TpsControlRule.Rule.MODEL_PROTO.equalsIgnoreCase(this.model); + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + /** + * get slot of the timestamp second,create if not exist. + * + * @param timeStamp the timestamp second. + * @return tps slot. + */ + public TpsSlot createSlotIfAbsent(long timeStamp) { + long distance = timeStamp - startTime; + + long diff = (distance < 0 ? distance + period.toMillis(1) * slotSize : distance) / period.toMillis(1); + long currentWindowTime = startTime + diff * period.toMillis(1); + int index = (int) diff % slotSize; + if (slotList.get(index).time != currentWindowTime) { + slotList.get(index).reset(currentWindowTime); + } + return slotList.get(index); + } + + /** + * get slot of the timestamp second,read only ,return nul if not exist. + * + * @param timeStamp the timestamp second. + * @return tps slot. + */ + public TpsSlot getPoint(long timeStamp) { + long distance = timeStamp - startTime; + long diff = (distance < 0 ? distance + period.toMillis(1) * slotSize : distance) / period.toMillis(1); + long currentWindowTime = startTime + diff * period.toMillis(1); + int index = (int) diff % slotSize; + TpsSlot tpsSlot = slotList.get(index); + if (tpsSlot.time != currentWindowTime) { + return null; + } + return tpsSlot; + } + + public long getMaxCount() { + return maxCount; + } + + public void setMaxCount(long maxCount) { + this.maxCount = maxCount; + } + + public boolean isInterceptMode() { + return MonitorType.INTERCEPT.type.equals(this.monitorType); + } + + /** + * clearLimitRule. + */ + public void clearLimitRule() { + this.setMonitorType(MonitorType.MONITOR.type); + this.setMaxCount(-1); + } + + public String getMonitorType() { + return monitorType; + } + + public void setMonitorType(String monitorType) { + this.monitorType = monitorType; + } + + static class TpsSlot { + + long time = 0L; + + private SlotCountHolder countHolder = new SlotCountHolder(); + + public SlotCountHolder getCountHolder(String key) { + return countHolder; + } + + public void reset(long second) { + synchronized (this) { + if (this.time != second) { + this.time = second; + countHolder.count.set(0L); + countHolder.interceptedCount.set(0); + } + } + } + + @Override + public String toString() { + return "TpsSlot{" + "time=" + time + ", countHolder=" + countHolder + '}'; + } + + } + + static class MultiKeyTpsSlot extends TpsSlot { + + Map keySlots = new HashMap<>(16); + + @Override + public SlotCountHolder getCountHolder(String key) { + if (!keySlots.containsKey(key)) { + keySlots.putIfAbsent(key, new SlotCountHolder()); + } + return keySlots.get(key); + } + + public Map getKeySlots() { + return keySlots; + } + + @Override + public void reset(long second) { + synchronized (this) { + if (this.time != second) { + this.time = second; + keySlots.clear(); + } + } + } + + @Override + public String toString() { + return "MultiKeyTpsSlot{" + "time=" + time + "}'"; + } + + } + + static class SlotCountHolder { + + AtomicLong count = new AtomicLong(); + + AtomicLong interceptedCount = new AtomicLong(); + + @Override + public String toString() { + return "{" + count + "|" + interceptedCount + '}'; + } + } + + public List getSlotList() { + return slotList; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/core/RpcAckCallbackInitorOrCleaner.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/core/RpcAckCallbackInitorOrCleaner.java new file mode 100644 index 00000000..49217ec0 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/core/RpcAckCallbackInitorOrCleaner.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2020 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.remote.core; + +import com.alibaba.nacos.core.remote.ClientConnectionEventListener; +import com.alibaba.nacos.core.remote.Connection; +import com.alibaba.nacos.core.remote.RpcAckCallbackSynchronizer; +import org.springframework.stereotype.Component; + +/** + * RemoteConnectionEventListener. + * + * @author liuzunfei + * @version $Id: RemoteConnectionEventListener.java, v 0.1 2020年08月10日 1:04 AM liuzunfei Exp $ + */ +@Component +public class RpcAckCallbackInitorOrCleaner extends ClientConnectionEventListener { + + @Override + public void clientConnected(Connection connect) { + RpcAckCallbackSynchronizer.initContextIfNecessary(connect.getMetaInfo().getConnectionId()); + } + + @Override + public void clientDisConnected(Connection connect) { + RpcAckCallbackSynchronizer.clearContext(connect.getMetaInfo().getConnectionId()); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/core/ServerLoaderInfoRequestHandler.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/core/ServerLoaderInfoRequestHandler.java new file mode 100644 index 00000000..3232c5bb --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/core/ServerLoaderInfoRequestHandler.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2020 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.remote.core; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.RemoteConstants; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.request.ServerLoaderInfoRequest; +import com.alibaba.nacos.api.remote.response.ServerLoaderInfoResponse; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.core.remote.ConnectionManager; +import com.alibaba.nacos.core.remote.RequestHandler; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * request handler to handle server loader info. + * + * @author liuzunfei + * @version $Id: ServerLoaderInfoRequestHandler.java, v 0.1 2020年09月03日 2:51 PM liuzunfei Exp $ + */ +@Component +public class ServerLoaderInfoRequestHandler extends RequestHandler { + + @Autowired + private ConnectionManager connectionManager; + + @Override + public ServerLoaderInfoResponse handle(ServerLoaderInfoRequest request, RequestMeta meta) throws NacosException { + ServerLoaderInfoResponse serverLoaderInfoResponse = new ServerLoaderInfoResponse(); + serverLoaderInfoResponse.putMetricsValue("conCount", String.valueOf(connectionManager.currentClientsCount())); + Map filter = new HashMap<>(2); + filter.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_SDK); + serverLoaderInfoResponse + .putMetricsValue("sdkConCount", String.valueOf(connectionManager.currentClientsCount(filter))); + serverLoaderInfoResponse.putMetricsValue("limitRule", JacksonUtils.toJson(connectionManager.getConnectionLimitRule())); + serverLoaderInfoResponse.putMetricsValue("load", String.valueOf(EnvUtil.getLoad())); + serverLoaderInfoResponse.putMetricsValue("cpu", String.valueOf(EnvUtil.getCpu())); + + return serverLoaderInfoResponse; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/core/ServerReloaderRequestHandler.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/core/ServerReloaderRequestHandler.java new file mode 100644 index 00000000..a1c62567 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/core/ServerReloaderRequestHandler.java @@ -0,0 +1,64 @@ +/* + * Copyright 1999-2020 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.remote.core; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.remote.RemoteConstants; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.request.ServerReloadRequest; +import com.alibaba.nacos.api.remote.response.ServerReloadResponse; +import com.alibaba.nacos.core.remote.ConnectionManager; +import com.alibaba.nacos.core.remote.RequestHandler; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.core.utils.RemoteUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * server reload request handler. + * + * @author liuzunfei + * @version $Id: ServerReloaderRequestHandler.java, v 0.1 2020年11月09日 4:38 PM liuzunfei Exp $ + */ +@Component +public class ServerReloaderRequestHandler extends RequestHandler { + + @Autowired + private ConnectionManager connectionManager; + + @Override + public ServerReloadResponse handle(ServerReloadRequest request, RequestMeta meta) throws NacosException { + ServerReloadResponse response = new ServerReloadResponse(); + Loggers.REMOTE.info("server reload request receive,reload count={},redirectServer={},requestIp={}", + request.getReloadCount(), request.getReloadServer(), meta.getClientIp()); + int reloadCount = request.getReloadCount(); + Map filter = new HashMap<>(2); + filter.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_SDK); + int sdkCount = connectionManager.currentClientsCount(filter); + if (sdkCount <= reloadCount) { + response.setMessage("ignore"); + } else { + reloadCount = (int) Math.max(reloadCount, sdkCount * (1 - RemoteUtils.LOADER_FACTOR)); + connectionManager.loadCount(reloadCount, request.getReloadServer()); + response.setMessage("ok"); + } + return response; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/event/ConnectionLimitRuleChangeEvent.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/event/ConnectionLimitRuleChangeEvent.java new file mode 100644 index 00000000..0680acf6 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/event/ConnectionLimitRuleChangeEvent.java @@ -0,0 +1,40 @@ +/* + * 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.remote.event; + +import com.alibaba.nacos.common.notify.Event; + +/** + * connection limit rule change event. + * @author zunfei.lzf + */ +public class ConnectionLimitRuleChangeEvent extends Event { + + String limitRule; + + public ConnectionLimitRuleChangeEvent(String limitRule) { + this.limitRule = limitRule; + } + + public String getLimitRule() { + return limitRule; + } + + public void setLimitRule(String limitRule) { + this.limitRule = limitRule; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/event/RemotingHeartBeatEvent.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/event/RemotingHeartBeatEvent.java new file mode 100644 index 00000000..14047836 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/event/RemotingHeartBeatEvent.java @@ -0,0 +1,51 @@ +/* + * 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.remote.event; + +import com.alibaba.nacos.common.notify.Event; + +/** + * Remoting connection heart beat event. + * + * @author xiweng.yy + */ +public class RemotingHeartBeatEvent extends Event { + + private final String connectionId; + + private final String clientIp; + + private final String clientVersion; + + public RemotingHeartBeatEvent(String connectionId, String clientIp, String clientVersion) { + this.connectionId = connectionId; + this.clientIp = clientIp; + this.clientVersion = clientVersion; + } + + public String getConnectionId() { + return connectionId; + } + + public String getClientIp() { + return clientIp; + } + + public String getClientVersion() { + return clientVersion; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/BaseGrpcServer.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/BaseGrpcServer.java new file mode 100644 index 00000000..52a72e2b --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/BaseGrpcServer.java @@ -0,0 +1,230 @@ +/* + * Copyright 1999-2020 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.remote.grpc; + +import com.alibaba.nacos.api.grpc.auto.Payload; +import com.alibaba.nacos.common.remote.ConnectionType; +import com.alibaba.nacos.common.utils.ReflectUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.remote.BaseRpcServer; +import com.alibaba.nacos.core.remote.ConnectionManager; +import com.alibaba.nacos.core.utils.Loggers; +import io.grpc.Attributes; +import io.grpc.CompressorRegistry; +import io.grpc.Context; +import io.grpc.Contexts; +import io.grpc.DecompressorRegistry; +import io.grpc.Grpc; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.ServerInterceptors; +import io.grpc.ServerServiceDefinition; +import io.grpc.ServerTransportFilter; +import io.grpc.internal.ServerStream; +import io.grpc.netty.shaded.io.netty.channel.Channel; +import io.grpc.protobuf.ProtoUtils; +import io.grpc.stub.ServerCalls; +import io.grpc.util.MutableHandlerRegistry; +import org.springframework.beans.factory.annotation.Autowired; + +import java.net.InetSocketAddress; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * Grpc implementation as a rpc server. + * + * @author liuzunfei + * @version $Id: BaseGrpcServer.java, v 0.1 2020年07月13日 3:42 PM liuzunfei Exp $ + */ +public abstract class BaseGrpcServer extends BaseRpcServer { + + private Server server; + + private static final String REQUEST_BI_STREAM_SERVICE_NAME = "BiRequestStream"; + + private static final String REQUEST_BI_STREAM_METHOD_NAME = "requestBiStream"; + + private static final String REQUEST_SERVICE_NAME = "Request"; + + private static final String REQUEST_METHOD_NAME = "request"; + + private static final String GRPC_MAX_INBOUND_MSG_SIZE_PROPERTY = "nacos.remote.server.grpc.maxinbound.message.size"; + + private static final long DEFAULT_GRPC_MAX_INBOUND_MSG_SIZE = 10 * 1024 * 1024; + + @Autowired + private GrpcRequestAcceptor grpcCommonRequestAcceptor; + + @Autowired + private GrpcBiStreamRequestAcceptor grpcBiStreamRequestAcceptor; + + @Autowired + private ConnectionManager connectionManager; + + @Override + public ConnectionType getConnectionType() { + return ConnectionType.GRPC; + } + + @Override + public void startServer() throws Exception { + final MutableHandlerRegistry handlerRegistry = new MutableHandlerRegistry(); + + // server interceptor to set connection id. + ServerInterceptor serverInterceptor = new ServerInterceptor() { + @Override + public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, + ServerCallHandler next) { + Context ctx = Context.current() + .withValue(CONTEXT_KEY_CONN_ID, call.getAttributes().get(TRANS_KEY_CONN_ID)) + .withValue(CONTEXT_KEY_CONN_REMOTE_IP, call.getAttributes().get(TRANS_KEY_REMOTE_IP)) + .withValue(CONTEXT_KEY_CONN_REMOTE_PORT, call.getAttributes().get(TRANS_KEY_REMOTE_PORT)) + .withValue(CONTEXT_KEY_CONN_LOCAL_PORT, call.getAttributes().get(TRANS_KEY_LOCAL_PORT)); + if (REQUEST_BI_STREAM_SERVICE_NAME.equals(call.getMethodDescriptor().getServiceName())) { + Channel internalChannel = getInternalChannel(call); + ctx = ctx.withValue(CONTEXT_KEY_CHANNEL, internalChannel); + } + return Contexts.interceptCall(ctx, call, headers, next); + } + }; + + addServices(handlerRegistry, serverInterceptor); + + server = ServerBuilder.forPort(getServicePort()).executor(getRpcExecutor()) + .maxInboundMessageSize(getInboundMessageSize()).fallbackHandlerRegistry(handlerRegistry) + .compressorRegistry(CompressorRegistry.getDefaultInstance()) + .decompressorRegistry(DecompressorRegistry.getDefaultInstance()) + .addTransportFilter(new ServerTransportFilter() { + @Override + public Attributes transportReady(Attributes transportAttrs) { + InetSocketAddress remoteAddress = (InetSocketAddress) transportAttrs + .get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR); + InetSocketAddress localAddress = (InetSocketAddress) transportAttrs + .get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR); + int remotePort = remoteAddress.getPort(); + int localPort = localAddress.getPort(); + String remoteIp = remoteAddress.getAddress().getHostAddress(); + Attributes attrWrapper = transportAttrs.toBuilder() + .set(TRANS_KEY_CONN_ID, System.currentTimeMillis() + "_" + remoteIp + "_" + remotePort) + .set(TRANS_KEY_REMOTE_IP, remoteIp).set(TRANS_KEY_REMOTE_PORT, remotePort) + .set(TRANS_KEY_LOCAL_PORT, localPort).build(); + String connectionId = attrWrapper.get(TRANS_KEY_CONN_ID); + Loggers.REMOTE_DIGEST.info("Connection transportReady,connectionId = {} ", connectionId); + return attrWrapper; + + } + + @Override + public void transportTerminated(Attributes transportAttrs) { + String connectionId = null; + try { + connectionId = transportAttrs.get(TRANS_KEY_CONN_ID); + } catch (Exception e) { + // Ignore + } + if (StringUtils.isNotBlank(connectionId)) { + Loggers.REMOTE_DIGEST + .info("Connection transportTerminated,connectionId = {} ", connectionId); + connectionManager.unregister(connectionId); + } + } + }).build(); + + server.start(); + } + + private int getInboundMessageSize() { + String messageSize = System + .getProperty(GRPC_MAX_INBOUND_MSG_SIZE_PROPERTY, String.valueOf(DEFAULT_GRPC_MAX_INBOUND_MSG_SIZE)); + return Integer.parseInt(messageSize); + } + + private Channel getInternalChannel(ServerCall serverCall) { + ServerStream serverStream = (ServerStream) ReflectUtils.getFieldValue(serverCall, "stream"); + return (Channel) ReflectUtils.getFieldValue(serverStream, "channel"); + } + + private void addServices(MutableHandlerRegistry handlerRegistry, ServerInterceptor... serverInterceptor) { + + // unary common call register. + final MethodDescriptor unaryPayloadMethod = MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName(MethodDescriptor.generateFullMethodName(REQUEST_SERVICE_NAME, REQUEST_METHOD_NAME)) + .setRequestMarshaller(ProtoUtils.marshaller(Payload.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Payload.getDefaultInstance())).build(); + + final ServerCallHandler payloadHandler = ServerCalls + .asyncUnaryCall((request, responseObserver) -> grpcCommonRequestAcceptor.request(request, responseObserver)); + + final ServerServiceDefinition serviceDefOfUnaryPayload = ServerServiceDefinition.builder(REQUEST_SERVICE_NAME) + .addMethod(unaryPayloadMethod, payloadHandler).build(); + handlerRegistry.addService(ServerInterceptors.intercept(serviceDefOfUnaryPayload, serverInterceptor)); + + // bi stream register. + final ServerCallHandler biStreamHandler = ServerCalls.asyncBidiStreamingCall( + (responseObserver) -> grpcBiStreamRequestAcceptor.requestBiStream(responseObserver)); + + final MethodDescriptor biStreamMethod = MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.BIDI_STREAMING).setFullMethodName(MethodDescriptor + .generateFullMethodName(REQUEST_BI_STREAM_SERVICE_NAME, REQUEST_BI_STREAM_METHOD_NAME)) + .setRequestMarshaller(ProtoUtils.marshaller(Payload.newBuilder().build())) + .setResponseMarshaller(ProtoUtils.marshaller(Payload.getDefaultInstance())).build(); + + final ServerServiceDefinition serviceDefOfBiStream = ServerServiceDefinition + .builder(REQUEST_BI_STREAM_SERVICE_NAME).addMethod(biStreamMethod, biStreamHandler).build(); + handlerRegistry.addService(ServerInterceptors.intercept(serviceDefOfBiStream, serverInterceptor)); + + } + + @Override + public void shutdownServer() { + if (server != null) { + server.shutdownNow(); + } + } + + /** + * get rpc executor. + * + * @return executor. + */ + public abstract ThreadPoolExecutor getRpcExecutor(); + + static final Attributes.Key TRANS_KEY_CONN_ID = Attributes.Key.create("conn_id"); + + static final Attributes.Key TRANS_KEY_REMOTE_IP = Attributes.Key.create("remote_ip"); + + static final Attributes.Key TRANS_KEY_REMOTE_PORT = Attributes.Key.create("remote_port"); + + static final Attributes.Key TRANS_KEY_LOCAL_PORT = Attributes.Key.create("local_port"); + + static final Context.Key CONTEXT_KEY_CONN_ID = Context.key("conn_id"); + + static final Context.Key CONTEXT_KEY_CONN_REMOTE_IP = Context.key("remote_ip"); + + static final Context.Key CONTEXT_KEY_CONN_REMOTE_PORT = Context.key("remote_port"); + + static final Context.Key CONTEXT_KEY_CONN_LOCAL_PORT = Context.key("local_port"); + + static final Context.Key CONTEXT_KEY_CHANNEL = Context.key("ctx_channel"); + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcBiStreamRequestAcceptor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcBiStreamRequestAcceptor.java new file mode 100644 index 00000000..15432c91 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcBiStreamRequestAcceptor.java @@ -0,0 +1,203 @@ +/* + * Copyright 1999-2020 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.remote.grpc; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.grpc.auto.BiRequestStreamGrpc; +import com.alibaba.nacos.api.grpc.auto.Payload; +import com.alibaba.nacos.api.remote.request.ConnectResetRequest; +import com.alibaba.nacos.api.remote.request.ConnectionSetupRequest; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.common.remote.ConnectionType; +import com.alibaba.nacos.common.remote.client.grpc.GrpcUtils; +import com.alibaba.nacos.core.remote.Connection; +import com.alibaba.nacos.core.remote.ConnectionManager; +import com.alibaba.nacos.core.remote.ConnectionMeta; +import com.alibaba.nacos.core.remote.RpcAckCallbackSynchronizer; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.utils.ApplicationUtils; +import io.grpc.stub.ServerCallStreamObserver; +import io.grpc.stub.StreamObserver; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Map; + +import static com.alibaba.nacos.core.remote.grpc.BaseGrpcServer.CONTEXT_KEY_CHANNEL; +import static com.alibaba.nacos.core.remote.grpc.BaseGrpcServer.CONTEXT_KEY_CONN_ID; +import static com.alibaba.nacos.core.remote.grpc.BaseGrpcServer.CONTEXT_KEY_CONN_LOCAL_PORT; +import static com.alibaba.nacos.core.remote.grpc.BaseGrpcServer.CONTEXT_KEY_CONN_REMOTE_IP; +import static com.alibaba.nacos.core.remote.grpc.BaseGrpcServer.CONTEXT_KEY_CONN_REMOTE_PORT; + +/** + * grpc bi stream request . + * + * @author liuzunfei + * @version $Id: GrpcBiStreamRequest.java, v 0.1 2020年09月01日 10:41 PM liuzunfei Exp $ + */ +@Service +public class GrpcBiStreamRequestAcceptor extends BiRequestStreamGrpc.BiRequestStreamImplBase { + + @Autowired + ConnectionManager connectionManager; + + private void traceDetailIfNecessary(Payload grpcRequest) { + String clientIp = grpcRequest.getMetadata().getClientIp(); + String connectionId = CONTEXT_KEY_CONN_ID.get(); + try { + if (connectionManager.traced(clientIp)) { + Loggers.REMOTE_DIGEST.info("[{}]Bi stream request receive, meta={},body={}", connectionId, + grpcRequest.getMetadata().toByteString().toStringUtf8(), + grpcRequest.getBody().toByteString().toStringUtf8()); + } + } catch (Throwable throwable) { + Loggers.REMOTE_DIGEST.error("[{}]Bi stream request error,payload={},error={}", connectionId, + grpcRequest.toByteString().toStringUtf8(), throwable); + } + + } + + @Override + public StreamObserver requestBiStream(StreamObserver responseObserver) { + + StreamObserver streamObserver = new StreamObserver() { + + final String connectionId = CONTEXT_KEY_CONN_ID.get(); + + final Integer localPort = CONTEXT_KEY_CONN_LOCAL_PORT.get(); + + final int remotePort = CONTEXT_KEY_CONN_REMOTE_PORT.get(); + + String remoteIp = CONTEXT_KEY_CONN_REMOTE_IP.get(); + + String clientIp = ""; + + @Override + public void onNext(Payload payload) { + + clientIp = payload.getMetadata().getClientIp(); + traceDetailIfNecessary(payload); + + Object parseObj; + try { + parseObj = GrpcUtils.parse(payload); + } catch (Throwable throwable) { + Loggers.REMOTE_DIGEST + .warn("[{}]Grpc request bi stream,payload parse error={}", connectionId, throwable); + return; + } + + if (parseObj == null) { + Loggers.REMOTE_DIGEST + .warn("[{}]Grpc request bi stream,payload parse null ,body={},meta={}", connectionId, + payload.getBody().getValue().toStringUtf8(), payload.getMetadata()); + return; + } + if (parseObj instanceof ConnectionSetupRequest) { + ConnectionSetupRequest setUpRequest = (ConnectionSetupRequest) parseObj; + Map labels = setUpRequest.getLabels(); + String appName = "-"; + if (labels != null && labels.containsKey(Constants.APPNAME)) { + appName = labels.get(Constants.APPNAME); + } + + ConnectionMeta metaInfo = new ConnectionMeta(connectionId, payload.getMetadata().getClientIp(), + remoteIp, remotePort, localPort, ConnectionType.GRPC.getType(), + setUpRequest.getClientVersion(), appName, setUpRequest.getLabels()); + metaInfo.setTenant(setUpRequest.getTenant()); + Connection connection = new GrpcConnection(metaInfo, responseObserver, CONTEXT_KEY_CHANNEL.get()); + connection.setAbilities(setUpRequest.getAbilities()); + boolean rejectSdkOnStarting = metaInfo.isSdkSource() && !ApplicationUtils.isStarted(); + + if (rejectSdkOnStarting || !connectionManager.register(connectionId, connection)) { + //Not register to the connection manager if current server is over limit or server is starting. + try { + Loggers.REMOTE_DIGEST.warn("[{}]Connection register fail,reason:{}", connectionId, + rejectSdkOnStarting ? " server is not started" : " server is over limited."); + connection.request(new ConnectResetRequest(), 3000L); + connection.close(); + } catch (Exception e) { + //Do nothing. + if (connectionManager.traced(clientIp)) { + Loggers.REMOTE_DIGEST + .warn("[{}]Send connect reset request error,error={}", connectionId, e); + } + } + } + + } else if (parseObj instanceof Response) { + Response response = (Response) parseObj; + if (connectionManager.traced(clientIp)) { + Loggers.REMOTE_DIGEST + .warn("[{}]Receive response of server request ,response={}", connectionId, response); + } + RpcAckCallbackSynchronizer.ackNotify(connectionId, response); + connectionManager.refreshActiveTime(connectionId); + } else { + Loggers.REMOTE_DIGEST + .warn("[{}]Grpc request bi stream,unknown payload receive ,parseObj={}", connectionId, + parseObj); + } + + } + + @Override + public void onError(Throwable t) { + if (connectionManager.traced(clientIp)) { + Loggers.REMOTE_DIGEST.warn("[{}]Bi stream on error,error={}", connectionId, t); + } + + if (responseObserver instanceof ServerCallStreamObserver) { + ServerCallStreamObserver serverCallStreamObserver = ((ServerCallStreamObserver) responseObserver); + if (serverCallStreamObserver.isCancelled()) { + //client close the stream. + } else { + try { + serverCallStreamObserver.onCompleted(); + } catch (Throwable throwable) { + //ignore + } + } + } + + } + + @Override + public void onCompleted() { + if (connectionManager.traced(clientIp)) { + Loggers.REMOTE_DIGEST.warn("[{}]Bi stream on completed", connectionId); + } + if (responseObserver instanceof ServerCallStreamObserver) { + ServerCallStreamObserver serverCallStreamObserver = ((ServerCallStreamObserver) responseObserver); + if (serverCallStreamObserver.isCancelled()) { + //client close the stream. + } else { + try { + serverCallStreamObserver.onCompleted(); + } catch (Throwable throwable) { + //ignore + } + + } + } + } + }; + + return streamObserver; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcClusterServer.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcClusterServer.java new file mode 100644 index 00000000..2a1a710e --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcClusterServer.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2020 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.remote.grpc; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.core.utils.GlobalExecutor; +import org.springframework.stereotype.Service; + +import java.util.concurrent.ThreadPoolExecutor; + +/** + * Grpc implementation as a rpc server. + * + * @author liuzunfei + * @version $Id: BaseGrpcServer.java, v 0.1 2020年07月13日 3:42 PM liuzunfei Exp $ + */ +@Service +public class GrpcClusterServer extends BaseGrpcServer { + + @Override + public int rpcPortOffset() { + return Constants.CLUSTER_GRPC_PORT_DEFAULT_OFFSET; + } + + @Override + public ThreadPoolExecutor getRpcExecutor() { + if (!GlobalExecutor.clusterRpcExecutor.allowsCoreThreadTimeOut()) { + GlobalExecutor.clusterRpcExecutor.allowCoreThreadTimeOut(true); + } + return GlobalExecutor.clusterRpcExecutor; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcConnection.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcConnection.java new file mode 100644 index 00000000..f5bf9f52 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcConnection.java @@ -0,0 +1,152 @@ +/* + * Copyright 1999-2020 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.remote.grpc; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.grpc.auto.Payload; +import com.alibaba.nacos.api.remote.DefaultRequestFuture; +import com.alibaba.nacos.api.remote.RequestCallBack; +import com.alibaba.nacos.api.remote.RequestFuture; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.common.remote.client.grpc.GrpcUtils; +import com.alibaba.nacos.common.remote.exception.ConnectionAlreadyClosedException; +import com.alibaba.nacos.core.remote.Connection; +import com.alibaba.nacos.core.remote.ConnectionMeta; +import com.alibaba.nacos.core.remote.RpcAckCallbackSynchronizer; +import com.alibaba.nacos.core.utils.Loggers; +import io.grpc.StatusRuntimeException; +import io.grpc.netty.shaded.io.netty.channel.Channel; +import io.grpc.stub.ServerCallStreamObserver; +import io.grpc.stub.StreamObserver; + +/** + * grpc connection. + * + * @author liuzunfei + * @version $Id: GrpcConnection.java, v 0.1 2020年07月13日 7:26 PM liuzunfei Exp $ + */ +public class GrpcConnection extends Connection { + + private StreamObserver streamObserver; + + private Channel channel; + + public GrpcConnection(ConnectionMeta metaInfo, StreamObserver streamObserver, Channel channel) { + super(metaInfo); + this.streamObserver = streamObserver; + this.channel = channel; + } + + private void sendRequestNoAck(Request request) throws NacosException { + try { + //StreamObserver#onNext() is not thread-safe,synchronized is required to avoid direct memory leak. + synchronized (streamObserver) { + + Payload payload = GrpcUtils.convert(request); + traceIfNecessary(payload); + streamObserver.onNext(payload); + } + } catch (Exception e) { + if (e instanceof StatusRuntimeException) { + throw new ConnectionAlreadyClosedException(e); + } + throw e; + } + } + + private void traceIfNecessary(Payload payload) { + String connectionId = null; + if (this.isTraced()) { + try { + connectionId = getMetaInfo().getConnectionId(); + Loggers.REMOTE_DIGEST.info("[{}]Send request to client ,payload={}", connectionId, + payload.toByteString().toStringUtf8()); + } catch (Throwable throwable) { + Loggers.REMOTE_DIGEST + .warn("[{}]Send request to client trace error, ,error={}", connectionId, throwable); + } + } + } + + private DefaultRequestFuture sendRequestInner(Request request, RequestCallBack callBack) throws NacosException { + final String requestId = String.valueOf(PushAckIdGenerator.getNextId()); + request.setRequestId(requestId); + + DefaultRequestFuture defaultPushFuture = new DefaultRequestFuture(getMetaInfo().getConnectionId(), requestId, + callBack, () -> RpcAckCallbackSynchronizer.clearFuture(getMetaInfo().getConnectionId(), requestId)); + + RpcAckCallbackSynchronizer.syncCallback(getMetaInfo().getConnectionId(), requestId, defaultPushFuture); + sendRequestNoAck(request); + return defaultPushFuture; + } + + @Override + public Response request(Request request, long timeoutMills) throws NacosException { + DefaultRequestFuture pushFuture = sendRequestInner(request, null); + try { + return pushFuture.get(timeoutMills); + } catch (Exception e) { + throw new NacosException(NacosException.SERVER_ERROR, e); + } finally { + RpcAckCallbackSynchronizer.clearFuture(getMetaInfo().getConnectionId(), pushFuture.getRequestId()); + } + } + + @Override + public RequestFuture requestFuture(Request request) throws NacosException { + return sendRequestInner(request, null); + } + + @Override + public void asyncRequest(Request request, RequestCallBack requestCallBack) throws NacosException { + sendRequestInner(request, requestCallBack); + } + + @Override + public void close() { + String connectionId = null; + + try { + connectionId = getMetaInfo().getConnectionId(); + + if (isTraced()) { + Loggers.REMOTE_DIGEST.warn("[{}] try to close connection ", connectionId); + } + + closeBiStream(); + channel.close(); + + } catch (Exception e) { + Loggers.REMOTE_DIGEST.warn("[{}] connection close exception : {}", connectionId, e); + } + } + + private void closeBiStream() { + if (streamObserver instanceof ServerCallStreamObserver) { + ServerCallStreamObserver serverCallStreamObserver = ((ServerCallStreamObserver) streamObserver); + if (!serverCallStreamObserver.isCancelled()) { + serverCallStreamObserver.onCompleted(); + } + } + } + + @Override + public boolean isConnected() { + return channel != null && channel.isOpen() && channel.isActive(); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcRequestAcceptor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcRequestAcceptor.java new file mode 100644 index 00000000..801d3d84 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcRequestAcceptor.java @@ -0,0 +1,185 @@ +/* + * Copyright 1999-2020 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.remote.grpc; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.grpc.auto.Payload; +import com.alibaba.nacos.api.grpc.auto.RequestGrpc; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.request.ServerCheckRequest; +import com.alibaba.nacos.api.remote.response.ErrorResponse; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.api.remote.response.ServerCheckResponse; +import com.alibaba.nacos.common.remote.client.grpc.GrpcUtils; +import com.alibaba.nacos.core.remote.Connection; +import com.alibaba.nacos.core.remote.ConnectionManager; +import com.alibaba.nacos.core.remote.RequestHandler; +import com.alibaba.nacos.core.remote.RequestHandlerRegistry; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.sys.utils.ApplicationUtils; +import io.grpc.stub.StreamObserver; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import static com.alibaba.nacos.core.remote.grpc.BaseGrpcServer.CONTEXT_KEY_CONN_ID; + +/** + * rpc request acceptor of grpc. + * + * @author liuzunfei + * @version $Id: GrpcCommonRequestAcceptor.java, v 0.1 2020年09月01日 10:52 AM liuzunfei Exp $ + */ +@Service +public class GrpcRequestAcceptor extends RequestGrpc.RequestImplBase { + + @Autowired + RequestHandlerRegistry requestHandlerRegistry; + + @Autowired + private ConnectionManager connectionManager; + + private void traceIfNecessary(Payload grpcRequest, boolean receive) { + String clientIp = grpcRequest.getMetadata().getClientIp(); + String connectionId = CONTEXT_KEY_CONN_ID.get(); + try { + if (connectionManager.traced(clientIp)) { + Loggers.REMOTE_DIGEST.info("[{}]Payload {},meta={},body={}", connectionId, receive ? "receive" : "send", + grpcRequest.getMetadata().toByteString().toStringUtf8(), + grpcRequest.getBody().toByteString().toStringUtf8()); + } + } catch (Throwable throwable) { + Loggers.REMOTE_DIGEST.error("[{}]Monitor request error,payload={},error={}", connectionId, clientIp, + grpcRequest.toByteString().toStringUtf8()); + } + + } + + @Override + public void request(Payload grpcRequest, StreamObserver responseObserver) { + + traceIfNecessary(grpcRequest, true); + String type = grpcRequest.getMetadata().getType(); + + //server is on starting. + if (!ApplicationUtils.isStarted()) { + Payload payloadResponse = GrpcUtils.convert( + ErrorResponse.build(NacosException.INVALID_SERVER_STATUS, "Server is starting,please try later.")); + traceIfNecessary(payloadResponse, false); + responseObserver.onNext(payloadResponse); + + responseObserver.onCompleted(); + return; + } + + // server check. + if (ServerCheckRequest.class.getSimpleName().equals(type)) { + Payload serverCheckResponseP = GrpcUtils.convert(new ServerCheckResponse(CONTEXT_KEY_CONN_ID.get())); + traceIfNecessary(serverCheckResponseP, false); + responseObserver.onNext(serverCheckResponseP); + responseObserver.onCompleted(); + return; + } + + RequestHandler requestHandler = requestHandlerRegistry.getByRequestType(type); + //no handler found. + if (requestHandler == null) { + Loggers.REMOTE_DIGEST.warn(String.format("[%s] No handler for request type : %s :", "grpc", type)); + Payload payloadResponse = GrpcUtils + .convert(ErrorResponse.build(NacosException.NO_HANDLER, "RequestHandler Not Found")); + traceIfNecessary(payloadResponse, false); + responseObserver.onNext(payloadResponse); + responseObserver.onCompleted(); + return; + } + + //check connection status. + String connectionId = CONTEXT_KEY_CONN_ID.get(); + boolean requestValid = connectionManager.checkValid(connectionId); + if (!requestValid) { + Loggers.REMOTE_DIGEST + .warn("[{}] Invalid connection Id ,connection [{}] is un registered ,", "grpc", connectionId); + Payload payloadResponse = GrpcUtils + .convert(ErrorResponse.build(NacosException.UN_REGISTER, "Connection is unregistered.")); + traceIfNecessary(payloadResponse, false); + responseObserver.onNext(payloadResponse); + responseObserver.onCompleted(); + return; + } + + Object parseObj = null; + try { + parseObj = GrpcUtils.parse(grpcRequest); + } catch (Exception e) { + Loggers.REMOTE_DIGEST + .warn("[{}] Invalid request receive from connection [{}] ,error={}", "grpc", connectionId, e); + Payload payloadResponse = GrpcUtils.convert(ErrorResponse.build(NacosException.BAD_GATEWAY, e.getMessage())); + traceIfNecessary(payloadResponse, false); + responseObserver.onNext(payloadResponse); + responseObserver.onCompleted(); + return; + } + + if (parseObj == null) { + Loggers.REMOTE_DIGEST.warn("[{}] Invalid request receive ,parse request is null", connectionId); + Payload payloadResponse = GrpcUtils + .convert(ErrorResponse.build(NacosException.BAD_GATEWAY, "Invalid request")); + traceIfNecessary(payloadResponse, false); + responseObserver.onNext(payloadResponse); + responseObserver.onCompleted(); + return; + } + + if (!(parseObj instanceof Request)) { + Loggers.REMOTE_DIGEST + .warn("[{}] Invalid request receive ,parsed payload is not a request,parseObj={}", connectionId, + parseObj); + Payload payloadResponse = GrpcUtils + .convert(ErrorResponse.build(NacosException.BAD_GATEWAY, "Invalid request")); + traceIfNecessary(payloadResponse, false); + responseObserver.onNext(payloadResponse); + responseObserver.onCompleted(); + return; + } + + Request request = (Request) parseObj; + try { + Connection connection = connectionManager.getConnection(CONTEXT_KEY_CONN_ID.get()); + RequestMeta requestMeta = new RequestMeta(); + requestMeta.setClientIp(connection.getMetaInfo().getClientIp()); + requestMeta.setConnectionId(CONTEXT_KEY_CONN_ID.get()); + requestMeta.setClientVersion(connection.getMetaInfo().getVersion()); + requestMeta.setLabels(connection.getMetaInfo().getLabels()); + connectionManager.refreshActiveTime(requestMeta.getConnectionId()); + Response response = requestHandler.handleRequest(request, requestMeta); + Payload payloadResponse = GrpcUtils.convert(response); + traceIfNecessary(payloadResponse, false); + responseObserver.onNext(payloadResponse); + responseObserver.onCompleted(); + } catch (Throwable e) { + Loggers.REMOTE_DIGEST + .error("[{}] Fail to handle request from connection [{}] ,error message :{}", "grpc", connectionId, + e); + Payload payloadResponse = GrpcUtils.convert(ErrorResponse.build(e)); + traceIfNecessary(payloadResponse, false); + responseObserver.onNext(payloadResponse); + responseObserver.onCompleted(); + } + + } + +} \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcSdkServer.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcSdkServer.java new file mode 100644 index 00000000..983395e5 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcSdkServer.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2020 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.remote.grpc; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.core.utils.GlobalExecutor; +import org.springframework.stereotype.Service; + +import java.util.concurrent.ThreadPoolExecutor; + +/** + * Grpc implementation as a rpc server. + * + * @author liuzunfei + * @version $Id: BaseGrpcServer.java, v 0.1 2020年07月13日 3:42 PM liuzunfei Exp $ + */ +@Service +public class GrpcSdkServer extends BaseGrpcServer { + + @Override + public int rpcPortOffset() { + return Constants.SDK_GRPC_PORT_DEFAULT_OFFSET; + } + + @Override + public ThreadPoolExecutor getRpcExecutor() { + return GlobalExecutor.sdkRpcExecutor; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/PushAckIdGenerator.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/PushAckIdGenerator.java new file mode 100644 index 00000000..c8e15af4 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/remote/grpc/PushAckIdGenerator.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2020 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.remote.grpc; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * id generator to server push ack. + * + * @author liuzunfei + * @version $Id: PushAckIdGenerator.java, v 0.1 2020年07月20日 5:49 PM liuzunfei Exp $ + */ +public class PushAckIdGenerator { + + private static AtomicLong id = new AtomicLong(0L); + + private static final int ID_PREV_REGEN_OFFSET = 1000; + + /** + * get server push id. + */ + public static long getNextId() { + if (id.longValue() > Long.MAX_VALUE - ID_PREV_REGEN_OFFSET) { + id.getAndSet(0L); + } + return id.incrementAndGet(); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/StorageFactory.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/StorageFactory.java new file mode 100644 index 00000000..13fa26a1 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/StorageFactory.java @@ -0,0 +1,51 @@ +/* + * 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 liaochuntao + */ +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(); + default: + throw new IllegalArgumentException("this kv type : [" + type.name() + "] not support"); + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/kv/FileKvStorage.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/kv/FileKvStorage.java new file mode 100644 index 00000000..b730a219 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/kv/FileKvStorage.java @@ -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 liaochuntao + */ +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 batchGet(List keys) throws KvStorageException { + readLock.lock(); + try { + Map 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 keys, List 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 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 allKeys() throws KvStorageException { + List 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() { + } +} \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/kv/KvStorage.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/kv/KvStorage.java new file mode 100644 index 00000000..a976daed --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/kv/KvStorage.java @@ -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 liaochuntao + */ +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 KvStorageException + */ + Map batchGet(List keys) throws KvStorageException; + + /** + * write data. + * + * @param key byte[] + * @param value byte[] + * @throws KvStorageException KvStorageException + */ + void put(byte[] key, byte[] value) throws KvStorageException; + + /** + * batch write. + * + * @param keys List byte[] + * @param values List byte[] + * @throws KvStorageException KvStorageException + */ + void batchPut(List keys, List values) throws KvStorageException; + + /** + * delete with key. + * + * @param key byte[] + * @throws KvStorageException KvStorageException + */ + void delete(byte[] key) throws KvStorageException; + + /** + * batch delete with keys. + * + * @param keys List byte[] + * @throws KvStorageException KvStorageException + */ + void batchDelete(List keys) 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 allKeys() throws KvStorageException; + + /** + * shutdown. + */ + void shutdown(); + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/kv/MemoryKvStorage.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/kv/MemoryKvStorage.java new file mode 100644 index 00000000..34577f70 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/storage/kv/MemoryKvStorage.java @@ -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 liaochuntao + */ +public class MemoryKvStorage implements KvStorage { + + private final Map storage = new ConcurrentSkipListMap<>(); + + @Override + public byte[] get(byte[] key) throws KvStorageException { + return storage.get(new Key(key)); + } + + @Override + public Map batchGet(List keys) throws KvStorageException { + Map 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 keys, List 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 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 allKeys() throws KvStorageException { + List 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 { + + 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); + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ClassUtils.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ClassUtils.java new file mode 100644 index 00000000..7831425d --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ClassUtils.java @@ -0,0 +1,77 @@ +/* + * 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.utils; + +import org.springframework.core.ResolvableType; + +import java.util.Objects; + +/** + * class operation utils. + * + * @author liaochuntao + */ +@SuppressWarnings("all") +public final class ClassUtils { + + public static Class resolveGenericType(Class declaredClass) { + return (Class) ResolvableType.forClass(declaredClass).getSuperType().resolveGeneric(0); + } + + public static Class resolveGenericTypeByInterface(Class declaredClass) { + return (Class) ResolvableType.forClass(declaredClass).getInterfaces()[0].resolveGeneric(0); + } + + public static Class findClassByName(String className) { + try { + return Class.forName(className); + } catch (Exception e) { + throw new RuntimeException("this class name not found"); + } + } + + public static String getName(Object obj) { + Objects.requireNonNull(obj, "obj"); + return obj.getClass().getName(); + } + + public static String getCanonicalName(Object obj) { + Objects.requireNonNull(obj, "obj"); + return obj.getClass().getCanonicalName(); + } + + public static String getSimplaName(Object obj) { + Objects.requireNonNull(obj, "obj"); + return obj.getClass().getSimpleName(); + } + + public static String getName(Class cls) { + Objects.requireNonNull(cls, "cls"); + return cls.getName(); + } + + public static String getCanonicalName(Class cls) { + Objects.requireNonNull(cls, "cls"); + return cls.getCanonicalName(); + } + + public static String getSimplaName(Class cls) { + Objects.requireNonNull(cls, "cls"); + return cls.getSimpleName(); + } + +} \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/Commons.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/Commons.java new file mode 100644 index 00000000..1a6d95b1 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/Commons.java @@ -0,0 +1,39 @@ +/* + * 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.utils; + +/** + * Constants. + * + * @author liaochuntao + */ +public final class Commons { + + public static final String NACOS_SERVER_CONTEXT = "/nacos"; + + public static final String NACOS_SERVER_VERSION = "/v1"; + + public static final String NACOS_SERVER_VERSION_V2 = "/v2"; + + public static final String DEFAULT_NACOS_CORE_CONTEXT = NACOS_SERVER_VERSION + "/core"; + + public static final String NACOS_CORE_CONTEXT = DEFAULT_NACOS_CORE_CONTEXT; + + public static final String NACOS_CORE_CONTEXT_V2 = NACOS_SERVER_VERSION_V2 + "/core"; + + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/GenericType.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/GenericType.java new file mode 100644 index 00000000..c4ef5784 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/GenericType.java @@ -0,0 +1,55 @@ +/* + * 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.utils; + +import com.alibaba.nacos.common.utils.Preconditions; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +/** + * Encapsulates third party tools for generics acquisition. + * + * @author liaochuntao + */ +public class GenericType { + + private static final long serialVersionUID = -2103808581228167629L; + + private final Type runtimeType; + + final Type capture() { + Type superclass = getClass().getGenericSuperclass(); + Preconditions.checkArgument(superclass instanceof ParameterizedType, "%s isn't parameterized", superclass); + return ((ParameterizedType) superclass).getActualTypeArguments()[0]; + } + + protected GenericType() { + this.runtimeType = capture(); + if (runtimeType instanceof TypeVariable) { + throw new IllegalArgumentException("runtimeType must be ParameterizedType Class"); + } + } + + /** + * Returns the represented type. + */ + public final Type getType() { + return runtimeType; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/GlobalExecutor.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/GlobalExecutor.java new file mode 100644 index 00000000..275b50f1 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/GlobalExecutor.java @@ -0,0 +1,87 @@ +/* + * 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.utils; + +import com.alibaba.nacos.common.executor.ExecutorFactory; +import com.alibaba.nacos.common.executor.NameThreadFactory; +import com.alibaba.nacos.common.utils.ThreadFactoryBuilder; +import com.alibaba.nacos.sys.env.EnvUtil; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * core module global executor. + * + * @author liaochuntao + */ +@SuppressWarnings("all") +public class GlobalExecutor { + + private static final ScheduledExecutorService COMMON_EXECUTOR = ExecutorFactory.Managed + .newScheduledExecutorService(ClassUtils.getCanonicalName(GlobalExecutor.class), 4, + new NameThreadFactory("com.alibaba.nacos.core.common")); + + private static final ScheduledExecutorService DISTRO_EXECUTOR = ExecutorFactory.Managed + .newScheduledExecutorService(ClassUtils.getCanonicalName(GlobalExecutor.class), + EnvUtil.getAvailableProcessors(2), new NameThreadFactory("com.alibaba.nacos.core.protocal.distro")); + + public static final ThreadPoolExecutor sdkRpcExecutor = new ThreadPoolExecutor( + EnvUtil.getAvailableProcessors(RemoteUtils.getRemoteExecutorTimesOfProcessors()), + EnvUtil.getAvailableProcessors(RemoteUtils.getRemoteExecutorTimesOfProcessors()), 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(RemoteUtils.getRemoteExecutorQueueSize()), + new ThreadFactoryBuilder().daemon(true).nameFormat("nacos-grpc-executor-%d").build()); + + public static final ThreadPoolExecutor clusterRpcExecutor = new ThreadPoolExecutor( + EnvUtil.getAvailableProcessors(RemoteUtils.getRemoteExecutorTimesOfProcessors()), + EnvUtil.getAvailableProcessors(RemoteUtils.getRemoteExecutorTimesOfProcessors()), 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(RemoteUtils.getRemoteExecutorQueueSize()), + new ThreadFactoryBuilder().daemon(true).nameFormat("nacos-cluster-grpc-executor-%d").build()); + + public static void runWithoutThread(Runnable runnable) { + runnable.run(); + } + + public static void executeByCommon(Runnable runnable) { + if (COMMON_EXECUTOR.isShutdown()) { + return; + } + COMMON_EXECUTOR.execute(runnable); + } + + public static void scheduleByCommon(Runnable runnable, long delayMs) { + if (COMMON_EXECUTOR.isShutdown()) { + return; + } + COMMON_EXECUTOR.schedule(runnable, delayMs, TimeUnit.MILLISECONDS); + } + + public static void submitLoadDataTask(Runnable runnable) { + DISTRO_EXECUTOR.submit(runnable); + } + + public static void submitLoadDataTask(Runnable runnable, long delay) { + DISTRO_EXECUTOR.schedule(runnable, delay, TimeUnit.MILLISECONDS); + } + + public static void schedulePartitionDataTimedSync(Runnable runnable, long interval) { + DISTRO_EXECUTOR.scheduleWithFixedDelay(runnable, interval, interval, TimeUnit.MILLISECONDS); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/Loggers.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/Loggers.java new file mode 100644 index 00000000..b6fb75cf --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/Loggers.java @@ -0,0 +1,77 @@ +/* + * 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.utils; + +import ch.qos.logback.classic.Level; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Loggers for core. + * + * @author nkorange + * @since 1.2.0 + */ +public class Loggers { + + public static final Logger AUTH = LoggerFactory.getLogger("com.alibaba.nacos.core.auth"); + + public static final Logger CORE = LoggerFactory.getLogger("com.alibaba.nacos.core"); + + public static final Logger RAFT = LoggerFactory.getLogger("com.alibaba.nacos.core.protocol.raft"); + + public static final Logger DISTRO = LoggerFactory.getLogger("com.alibaba.nacos.core.protocol.distro"); + + public static final Logger CLUSTER = LoggerFactory.getLogger("com.alibaba.nacos.core.cluster"); + + public static final Logger REMOTE = LoggerFactory.getLogger("com.alibaba.nacos.core.remote"); + + public static final Logger REMOTE_PUSH = LoggerFactory.getLogger("com.alibaba.nacos.core.remote.push"); + + public static final Logger REMOTE_DIGEST = LoggerFactory.getLogger("com.alibaba.nacos.core.remote.digest"); + + public static final Logger TPS_CONTROL_DIGEST = LoggerFactory + .getLogger("com.alibaba.nacos.core.remote.control.digest"); + + public static final Logger TPS_CONTROL = LoggerFactory.getLogger("com.alibaba.nacos.core.remote.control"); + + public static final Logger TPS_CONTROL_DETAIL = LoggerFactory.getLogger("com.alibaba.nacos.core.remote.control.detail"); + + public static void setLogLevel(String logName, String level) { + + switch (logName) { + case "core-auth": + ((ch.qos.logback.classic.Logger) AUTH).setLevel(Level.valueOf(level)); + break; + case "core": + ((ch.qos.logback.classic.Logger) CORE).setLevel(Level.valueOf(level)); + break; + case "core-raft": + ((ch.qos.logback.classic.Logger) RAFT).setLevel(Level.valueOf(level)); + break; + case "core-distro": + ((ch.qos.logback.classic.Logger) DISTRO).setLevel(Level.valueOf(level)); + break; + case "core-cluster": + ((ch.qos.logback.classic.Logger) CLUSTER).setLevel(Level.valueOf(level)); + break; + default: + break; + } + + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/OverrideParameterRequestWrapper.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/OverrideParameterRequestWrapper.java new file mode 100644 index 00000000..4d238908 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/OverrideParameterRequestWrapper.java @@ -0,0 +1,110 @@ +/* + * 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.utils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.util.HashMap; +import java.util.Map; + +/** + * A request wrapper to override the parameters. + * + *

Referenced article is https://blog.csdn.net/xieyuooo/article/details/8447301 + * + * @author nkorange + * @since 0.8.0 + */ +public class OverrideParameterRequestWrapper extends HttpServletRequestWrapper { + + private Map params = new HashMap<>(); + + /** + * Constructs a request object wrapping the given request. + * + * @param request The request to wrap + * @throws IllegalArgumentException if the request is null + */ + public OverrideParameterRequestWrapper(HttpServletRequest request) { + super(request); + this.params.putAll(request.getParameterMap()); + } + + public static OverrideParameterRequestWrapper buildRequest(HttpServletRequest request) { + return new OverrideParameterRequestWrapper(request); + } + + /** + * build OverrideParameterRequestWrapper and addParameter. + * + * @param request origin HttpServletRequest + * @param name name + * @param value value + * @return {@link OverrideParameterRequestWrapper} + */ + public static OverrideParameterRequestWrapper buildRequest(HttpServletRequest request, String name, String value) { + OverrideParameterRequestWrapper requestWrapper = new OverrideParameterRequestWrapper(request); + requestWrapper.addParameter(name, value); + return requestWrapper; + } + + /** + * build OverrideParameterRequestWrapper and addParameter. + * + * @param request origin HttpServletRequest + * @param appendParameters need to append to request + * @return {@link OverrideParameterRequestWrapper} + */ + public static OverrideParameterRequestWrapper buildRequest(HttpServletRequest request, + Map appendParameters) { + OverrideParameterRequestWrapper requestWrapper = new OverrideParameterRequestWrapper(request); + requestWrapper.params.putAll(appendParameters); + return requestWrapper; + } + + @Override + public String getParameter(String name) { + String[] values = params.get(name); + if (values == null || values.length == 0) { + return null; + } + return values[0]; + } + + @Override + public Map getParameterMap() { + return params; + } + + @Override + public String[] getParameterValues(String name) { + return params.get(name); + } + + /** + * addParameter. + * + * @param name name + * @param value value + */ + public void addParameter(String name, String value) { + if (value != null) { + params.put(name, new String[] {value}); + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/RemoteUtils.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/RemoteUtils.java new file mode 100644 index 00000000..863ee287 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/RemoteUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2020 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.utils; + +import com.alibaba.nacos.common.utils.NumberUtils; + +/** + * util of remote. + * + * @author liuzunfei + * @version $Id: RemoteUtils.java, v 0.1 2020年11月12日 8:54 PM liuzunfei Exp $ + */ +public class RemoteUtils { + + public static final float LOADER_FACTOR = 0.1f; + + /** + * Default remote execute times for CPU count of task processors. + */ + private static final int REMOTE_EXECUTOR_TIMES_OF_PROCESSORS = 1 << 4; + + /** + * Default remote execute queue size: 16384. + */ + private static final int REMOTE_EXECUTOR_QUEUE_SIZE = 1 << 14; + + /** + * get remote executors thread times of processors,default is 64. see the usage of this method for detail. + * + * @return times of processors. + */ + public static int getRemoteExecutorTimesOfProcessors() { + String timesString = System.getProperty("remote.executor.times.of.processors"); + if (NumberUtils.isDigits(timesString)) { + int times = Integer.parseInt(timesString); + return times > 0 ? times : REMOTE_EXECUTOR_TIMES_OF_PROCESSORS; + } else { + return REMOTE_EXECUTOR_TIMES_OF_PROCESSORS; + } + } + + public static int getRemoteExecutorQueueSize() { + String queueSizeString = System.getProperty("remote.executor.queue.size"); + if (NumberUtils.isDigits(queueSizeString)) { + int size = Integer.parseInt(queueSizeString); + return size > 0 ? size : REMOTE_EXECUTOR_QUEUE_SIZE; + } else { + return REMOTE_EXECUTOR_QUEUE_SIZE; + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ReuseHttpRequest.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ReuseHttpRequest.java new file mode 100644 index 00000000..0d2be058 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ReuseHttpRequest.java @@ -0,0 +1,58 @@ +/* + * 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.utils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * ReuseHttpRequest. + * + * @author liaochuntao + */ +public interface ReuseHttpRequest extends HttpServletRequest { + + /** + * get request body. + * + * @return object + * @throws Exception exception + */ + Object getBody() throws Exception; + + /** + * Remove duplicate values from the array. + * + * @param request {@link HttpServletRequest} + * @return {@link Map} + */ + default Map toDuplication(HttpServletRequest request) { + Map tmp = request.getParameterMap(); + Map result = new HashMap<>(tmp.size()); + Set set = new HashSet<>(); + for (Map.Entry entry : tmp.entrySet()) { + set.addAll(Arrays.asList(entry.getValue())); + result.put(entry.getKey(), set.toArray(new String[0])); + set.clear(); + } + return result; + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ReuseHttpServletRequest.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ReuseHttpServletRequest.java new file mode 100644 index 00000000..d93d27e3 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ReuseHttpServletRequest.java @@ -0,0 +1,138 @@ +/* + * 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.utils; + +import com.alibaba.nacos.common.http.HttpUtils; +import com.alibaba.nacos.common.http.param.MediaType; +import com.alibaba.nacos.common.utils.ByteUtils; +import com.alibaba.nacos.common.utils.StringUtils; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * httprequest wrapper. + * + * @author liaochuntao + */ +public class ReuseHttpServletRequest extends HttpServletRequestWrapper implements ReuseHttpRequest { + + private final HttpServletRequest target; + + private byte[] body; + + private Map stringMap; + + /** + * Constructs a request object wrapping the given request. + * + * @param request The request to wrap + * @throws IllegalArgumentException if the request is null + */ + public ReuseHttpServletRequest(HttpServletRequest request) throws IOException { + super(request); + this.target = request; + this.body = toBytes(request.getInputStream()); + this.stringMap = toDuplication(request); + } + + @Override + public Object getBody() throws Exception { + if (StringUtils.containsIgnoreCase(target.getContentType(), MediaType.MULTIPART_FORM_DATA)) { + return target.getParts(); + } else { + String s = ByteUtils.toString(body); + if (StringUtils.isBlank(s)) { + return HttpUtils + .encodingParams(HttpUtils.translateParameterMap(stringMap), StandardCharsets.UTF_8.name()); + } + return s; + } + } + + private byte[] toBytes(InputStream inputStream) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int n = 0; + while ((n = inputStream.read(buffer)) != -1) { + bos.write(buffer, 0, n); + } + return bos.toByteArray(); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public Map getParameterMap() { + return stringMap; + } + + @Override + public String getParameter(String name) { + String[] values = stringMap.get(name); + if (values == null || values.length == 0) { + return null; + } + return values[0]; + } + + @Override + public String[] getParameterValues(String name) { + return stringMap.get(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + + final ByteArrayInputStream inputStream = new ByteArrayInputStream(body); + + return new ServletInputStream() { + @Override + public int read() throws IOException { + return inputStream.read(); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + }; + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ReuseUploadFileHttpServletRequest.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ReuseUploadFileHttpServletRequest.java new file mode 100644 index 00000000..d0221b94 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/ReuseUploadFileHttpServletRequest.java @@ -0,0 +1,81 @@ +/* + * 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.utils; + +import com.alibaba.nacos.common.http.HttpUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.multipart.MultipartException; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Objects; + +/** + * httprequest wrapper. + * + * @author liaochuntao + */ +public class ReuseUploadFileHttpServletRequest extends StandardMultipartHttpServletRequest implements ReuseHttpRequest { + + private static final String DEFAULT_FILE_NAME = "file"; + + private final HttpServletRequest request; + + private Map stringMap; + + public ReuseUploadFileHttpServletRequest(HttpServletRequest request) throws MultipartException { + super(request); + this.request = request; + this.stringMap = toDuplication(request); + } + + @Override + public Map getParameterMap() { + return stringMap; + } + + @Override + public String getParameter(String name) { + String[] values = stringMap.get(name); + if (values == null || values.length == 0) { + return null; + } + return values[0]; + } + + @Override + public String[] getParameterValues(String name) { + return stringMap.get(name); + } + + @Override + public Object getBody() throws Exception { + MultipartFile target = super.getFile(DEFAULT_FILE_NAME); + if (Objects.nonNull(target)) { + MultiValueMap parts = new LinkedMultiValueMap<>(); + parts.add(DEFAULT_FILE_NAME, target.getResource()); + return parts; + } else { + // The content-type for the configuration publication might be "multipart/form-data" + return HttpUtils.encodingParams(HttpUtils.translateParameterMap(stringMap), StandardCharsets.UTF_8.name()); + } + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/StringPool.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/StringPool.java new file mode 100644 index 00000000..4d111c36 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/StringPool.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2020 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.utils; + +import com.alibaba.nacos.common.cache.Cache; +import com.alibaba.nacos.common.cache.builder.CacheBuilder; + +import java.util.concurrent.TimeUnit; + +/** + * StringPool,aim to reduce memory allocation. + * + * @author liuzunfei + * @author ZZQ + * @version $Id: StringPool.java, v 0.1 2020年11月12日 3:05 PM liuzunfei Exp $ + */ +public class StringPool { + + private static final Cache GROUP_KEY_CACHE; + + static { + GROUP_KEY_CACHE = CacheBuilder.builder().maximumSize(5000000) + .expireNanos(180, TimeUnit.SECONDS) + .lru(true) + .build(); + } + + /** + * get singleton string value from the pool. + * + * @param key key string to be pooled. + * @return value after pooled. + */ + public static String get(String key) { + if (key == null) { + return key; + } + String value = GROUP_KEY_CACHE.get(key); + if (value == null) { + GROUP_KEY_CACHE.put(key, key); + value = GROUP_KEY_CACHE.get(key); + } + + return value; + } + + public static long size() { + return GROUP_KEY_CACHE.getSize(); + } + + public static void remove(String key) { + GROUP_KEY_CACHE.remove(key); + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/TimerContext.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/TimerContext.java new file mode 100644 index 00000000..4d9b53b6 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/TimerContext.java @@ -0,0 +1,152 @@ +/* + * 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.utils; + +import com.alibaba.nacos.common.utils.LoggerUtils; +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; + +/** + * Simple task time calculation,Currently only the task time statistics task that supports synchronizing code blocks is + * supported. + * + * @author liaochuntao + */ +public class TimerContext { + + private static final ThreadLocal> TIME_RECORD = ThreadLocal.withInitial(() -> new HashMap<>(2)); + + /** + * Record context start time. + * + * @param name context name + */ + public static void start(final String name) { + TIME_RECORD.get().put(name, System.currentTimeMillis()); + } + + 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 String name, final Logger logger, final String level) { + Map record = TIME_RECORD.get(); + long contextTime = System.currentTimeMillis() - record.remove(name); + if (record.isEmpty()) { + TIME_RECORD.remove(); + } + switch (level) { + case LoggerUtils.DEBUG: + LoggerUtils.printIfDebugEnabled(logger, "{} cost time : {} ms", name, contextTime); + break; + case LoggerUtils.INFO: + LoggerUtils.printIfInfoEnabled(logger, "{} cost time : {} ms", name, contextTime); + break; + case LoggerUtils.TRACE: + LoggerUtils.printIfTraceEnabled(logger, "{} cost time : {} ms", name, contextTime); + break; + case LoggerUtils.ERROR: + LoggerUtils.printIfErrorEnabled(logger, "{} cost time : {} ms", name, contextTime); + break; + case LoggerUtils.WARN: + LoggerUtils.printIfWarnEnabled(logger, "{} cost time : {} ms", name, contextTime); + break; + default: + LoggerUtils.printIfErrorEnabled(logger, "level not found , {} cost time : {} ms", name, contextTime); + break; + } + } + + /** + * Execution with time-consuming calculations for {@link Runnable}. + * + * @param job runnable + * @param name job name + * @param logger logger + */ + public static void run(final Runnable job, final String name, final Logger logger) { + start(name); + try { + job.run(); + } finally { + end(name, logger); + } + } + + /** + * Execution with time-consuming calculations for {@link Supplier}. + * + * @param job Supplier + * @param name job name + * @param logger logger + */ + public static V run(final Supplier job, final String name, final Logger logger) { + start(name); + try { + return job.get(); + } finally { + end(name, logger); + } + } + + /** + * Execution with time-consuming calculations for {@link Function}. + * + * @param job Function + * @param args args + * @param name job name + * @param logger logger + */ + public static R run(final Function job, T args, final String name, final Logger logger) { + start(name); + try { + return job.apply(args); + } finally { + end(name, logger); + } + } + + /** + * Execution with time-consuming calculations for {@link Consumer}. + * + * @param job Consumer + * @param args args + * @param name job name + * @param logger logger + */ + public static void run(final Consumer job, T args, final String name, final Logger logger) { + start(name); + try { + job.accept(args); + } finally { + end(name, logger); + } + } + +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/WebUtils.java b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/WebUtils.java new file mode 100644 index 00000000..ed35a484 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/java/com/alibaba/nacos/core/utils/WebUtils.java @@ -0,0 +1,254 @@ +/* + * 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.utils; + +import com.alibaba.nacos.common.constant.HttpHeaderConsts; +import com.alibaba.nacos.common.http.HttpUtils; +import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.model.RestResultUtils; +import com.alibaba.nacos.sys.utils.DiskUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; + +import static com.alibaba.nacos.common.constant.HttpHeaderConsts.ACCEPT_ENCODING; +import static com.alibaba.nacos.common.http.param.MediaType.APPLICATION_JSON; + +/** + * web utils. + * + * @author nkorange + */ +public class WebUtils { + + private static final String ENCODING_KEY = "encoding"; + + private static final String COMMA = ","; + + private static final String SEMI = ";"; + + private static final String TMP_SUFFIX = ".tmp"; + + /** + * get target value from parameterMap, if not found will throw {@link IllegalArgumentException}. + * + * @param req {@link HttpServletRequest} + * @param key key + * @return value + */ + public static String required(final HttpServletRequest req, final String key) { + String value = req.getParameter(key); + if (StringUtils.isEmpty(value)) { + throw new IllegalArgumentException("Param '" + key + "' is required."); + } + String encoding = req.getParameter(ENCODING_KEY); + return resolveValue(value, encoding); + } + + /** + * get target value from parameterMap, if not found will return default value. + * + * @param req {@link HttpServletRequest} + * @param key key + * @param defaultValue default value + * @return value + */ + public static String optional(final HttpServletRequest req, final String key, final String defaultValue) { + if (!req.getParameterMap().containsKey(key) || req.getParameterMap().get(key)[0] == null) { + return defaultValue; + } + String value = req.getParameter(key); + if (StringUtils.isBlank(value)) { + return defaultValue; + } + String encoding = req.getParameter(ENCODING_KEY); + return resolveValue(value, encoding); + } + + /** + * decode target value. + * + * @param value value + * @param encoding encode + * @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. + * + *

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(); + } + try { + value = HttpUtils.decode(new String(value.getBytes(StandardCharsets.UTF_8), encoding), encoding); + } catch (UnsupportedEncodingException ignore) { + } catch (Exception ex) { + // If the value contains a special character without encoding (such as "[IPv6]"), + // a URLDecoder exception is thrown, which is ignored and the original value is returned + final String seq = "URLDecoder"; + if (!StringUtils.contains(ex.toString(), seq)) { + throw ex; + } + } + return value.trim(); + } + + /** + * get accept encode from request. + * + * @param req {@link HttpServletRequest} + * @return accept encode + */ + public static String getAcceptEncoding(HttpServletRequest req) { + String encode = StringUtils.defaultIfEmpty(req.getHeader(ACCEPT_ENCODING), StandardCharsets.UTF_8.name()); + encode = encode.contains(COMMA) ? encode.substring(0, encode.indexOf(COMMA)) : encode; + return encode.contains(SEMI) ? encode.substring(0, encode.indexOf(SEMI)) : encode; + } + + /** + * Returns the value of the request header "user-agent" as a String. + * + * @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". + */ + public static String getUserAgent(HttpServletRequest request) { + String userAgent = request.getHeader(HttpHeaderConsts.USER_AGENT_HEADER); + if (StringUtils.isEmpty(userAgent)) { + userAgent = StringUtils + .defaultIfEmpty(request.getHeader(HttpHeaderConsts.CLIENT_VERSION_HEADER), StringUtils.EMPTY); + } + return userAgent; + } + + /** + * response data to client. + * + * @param response {@link HttpServletResponse} + * @param body body + * @param code http code + * @throws IOException IOException + */ + public static void response(HttpServletResponse response, String body, int code) throws IOException { + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentType(APPLICATION_JSON); + response.getWriter().write(body); + response.setStatus(code); + } + + /** + * Handle file upload operations. + * + * @param multipartFile file + * @param consumer post processor + * @param response {@link DeferredResult} + */ + public static void onFileUpload(MultipartFile multipartFile, Consumer consumer, + DeferredResult> response) { + + if (Objects.isNull(multipartFile) || multipartFile.isEmpty()) { + response.setResult(RestResultUtils.failed("File is empty")); + return; + } + File tmpFile = null; + try { + tmpFile = DiskUtils.createTmpFile(multipartFile.getName(), TMP_SUFFIX); + multipartFile.transferTo(tmpFile); + consumer.accept(tmpFile); + } catch (Throwable ex) { + if (!response.isSetOrExpired()) { + response.setResult(RestResultUtils.failed(ex.getMessage())); + } + } finally { + DiskUtils.deleteQuietly(tmpFile); + } + } + + /** + * Register DeferredResult in the callback of CompletableFuture. + * + * @param deferredResult {@link DeferredResult} + * @param future {@link CompletableFuture} + * @param errorHandler {@link Function} + * @param target type + */ + public static void process(DeferredResult deferredResult, CompletableFuture future, + Function errorHandler) { + + deferredResult.onTimeout(future::join); + + future.whenComplete((t, throwable) -> { + if (Objects.nonNull(throwable)) { + deferredResult.setResult(errorHandler.apply(throwable)); + return; + } + deferredResult.setResult(t); + }); + } + + /** + * Register DeferredResult in the callback of CompletableFuture. + * + * @param deferredResult {@link DeferredResult} + * @param future {@link CompletableFuture} + * @param success if future success, callback runnable + * @param errorHandler {@link Function} + * @param target type + */ + public static void process(DeferredResult deferredResult, CompletableFuture future, Runnable success, + Function errorHandler) { + + deferredResult.onTimeout(future::join); + + future.whenComplete((t, throwable) -> { + if (Objects.nonNull(throwable)) { + deferredResult.setResult(errorHandler.apply(throwable)); + return; + } + success.run(); + deferredResult.setResult(t); + }); + } +} diff --git a/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/logback/nacos.xml b/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/logback/nacos.xml new file mode 100644 index 00000000..2ebac1f8 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/logback/nacos.xml @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + UTF-8 + + + + + ${LOG_HOME}/nacos.log + true + + ${LOG_HOME}/nacos.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${LOG_HOME}/core-auth.log + true + + ${LOG_HOME}/core-auth.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${LOG_HOME}/protocol-raft.log + true + + ${LOG_HOME}/protocol-raft.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${LOG_HOME}/remote-core.log + true + + ${LOG_HOME}/remote-core.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${LOG_HOME}/remote-digest.log + true + + ${LOG_HOME}/remote-digest.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${LOG_HOME}/remote-push.log + true + + ${LOG_HOME}/remote-push.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + + ${LOG_HOME}/tps-control.log + true + + ${LOG_HOME}/tps-control.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + ${LOG_HOME}/tps-control-detail.log + true + + ${LOG_HOME}/tps-control-detail.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${LOG_HOME}/tps-control-digest.log + true + + ${LOG_HOME}/tps-control-digest.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + + ${LOG_HOME}/protocol-distro.log + true + + ${LOG_HOME}/protocol-distro.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${LOG_HOME}/nacos-cluster.log + true + + ${LOG_HOME}/nacos-cluster.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${LOG_HOME}/alipay-jraft.log + true + + ${LOG_HOME}/alipay-jraft.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/services/com.alibaba.nacos.core.ability.ServerAbilityInitializer b/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/services/com.alibaba.nacos.core.ability.ServerAbilityInitializer new file mode 100644 index 00000000..239d8712 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/services/com.alibaba.nacos.core.ability.ServerAbilityInitializer @@ -0,0 +1,17 @@ +# +# Copyright 1999-2021 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.core.ability.RemoteAbilityInitializer diff --git a/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/services/com.alibaba.nacos.core.listener.NacosApplicationListener b/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/services/com.alibaba.nacos.core.listener.NacosApplicationListener new file mode 100644 index 00000000..4adb1d49 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/services/com.alibaba.nacos.core.listener.NacosApplicationListener @@ -0,0 +1,18 @@ +# +# Copyright 1999-2021 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.core.listener.LoggingApplicationListener +com.alibaba.nacos.core.listener.StartingApplicationListener \ No newline at end of file diff --git a/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/spring.factories b/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..af692650 --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/resources/META-INF/spring.factories @@ -0,0 +1,6 @@ +# ApplicationListener +org.springframework.context.ApplicationListener=\ +com.alibaba.nacos.core.code.StandaloneProfileApplicationListener +# SpringApplicationRunListener +org.springframework.boot.SpringApplicationRunListener=\ +com.alibaba.nacos.core.code.SpringApplicationRunListener diff --git a/ruoyi-visual/ruoyi-nacos/src/main/resources/application.properties b/ruoyi-visual/ruoyi-nacos/src/main/resources/application.properties index ad8a7b86..1d6a7ce9 100644 --- a/ruoyi-visual/ruoyi-nacos/src/main/resources/application.properties +++ b/ruoyi-visual/ruoyi-nacos/src/main/resources/application.properties @@ -38,9 +38,9 @@ spring.datasource.platform=mysql db.num=1 ### Connect URL of DB: -db.url.0=jdbc:mysql://101.42.248.151:3306/ry-config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC -db.user.0=lihongbo -db.password.0=lihongbo.123 +db.url.0=jdbc:mysql://127.0.0.1:3306/ry-config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC +db.user.0=root +db.password.0=root db.pool.config.connectionTimeout=30000 db.pool.config.validationTimeout=10000 db.pool.config.maximumPoolSize=20 diff --git a/ruoyi-visual/ruoyi-nacos/src/main/resources/banner.txt b/ruoyi-visual/ruoyi-nacos/src/main/resources/banner.txt new file mode 100644 index 00000000..e197a61d --- /dev/null +++ b/ruoyi-visual/ruoyi-nacos/src/main/resources/banner.txt @@ -0,0 +1,15 @@ + + ,--. + ,--.'| + ,--,: : | Nacos ${application.version} +,`--.'`| ' : ,---. Running in ${nacos.mode} mode, ${nacos.function.mode} function modules +| : : | | ' ,'\ .--.--. Port: ${server.port} +: | \ | : ,--.--. ,---. / / | / / ' Pid: ${pid} +| : ' '; | / \ / \. ; ,. :| : /`./ Console: http://${nacos.local.ip}:${server.port}${server.servlet.contextPath}/index.html +' ' ;. ;.--. .-. | / / '' | |: :| : ;_ +| | | \ | \__\/: . .. ' / ' | .; : \ \ `. https://nacos.io +' : | ; .' ," .--.; |' ; :__| : | `----. \ +| | '`--' / / ,. |' | '.'|\ \ / / /`--' / +' : | ; : .' \ : : `----' '--'. / +; |.' | , .-./\ \ / `--'---' +'---' `--`---' `----'