diff --git a/client/src/main/java/cn/icanci/loopstack/ras/client/exception/ServerApplicationWrongfulException.java b/client/src/main/java/cn/icanci/loopstack/ras/client/exception/ServerApplicationWrongfulException.java new file mode 100644 index 0000000000000000000000000000000000000000..103a867be57f5da0bd9e89dd4094ffa58093434b --- /dev/null +++ b/client/src/main/java/cn/icanci/loopstack/ras/client/exception/ServerApplicationWrongfulException.java @@ -0,0 +1,27 @@ +package cn.icanci.loopstack.ras.client.exception; + +/** + * @author icanci + * @since 1.0 Created in 2023/01/20 21:15 + */ +public class ServerApplicationWrongfulException extends RuntimeException{ + public ServerApplicationWrongfulException() { + super(); + } + + public ServerApplicationWrongfulException(String message) { + super(message); + } + + public ServerApplicationWrongfulException(String message, Throwable cause) { + super(message, cause); + } + + public ServerApplicationWrongfulException(Throwable cause) { + super(cause); + } + + protected ServerApplicationWrongfulException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/client/src/main/java/cn/icanci/loopstack/ras/client/holder/RasRepositoryHolder.java b/client/src/main/java/cn/icanci/loopstack/ras/client/holder/RasRepositoryHolder.java index 2d51e87b771db9d33fa249de9ba224070f70e531..a419d692ab9d5095ed753eeab0da633fd9903eb5 100644 --- a/client/src/main/java/cn/icanci/loopstack/ras/client/holder/RasRepositoryHolder.java +++ b/client/src/main/java/cn/icanci/loopstack/ras/client/holder/RasRepositoryHolder.java @@ -5,27 +5,38 @@ import cn.hutool.json.JSONUtil; import cn.icanci.loopstack.api.client.Client; import cn.icanci.loopstack.api.client.http.HttpClientImpl; import cn.icanci.loopstack.lsi.common.result.R; +import cn.icanci.loopstack.ras.client.exception.ServerApplicationWrongfulException; +import cn.icanci.loopstack.ras.client.holder.model.ApplicationKey; +import cn.icanci.loopstack.ras.client.holder.model.ApplicationValue; +import cn.icanci.loopstack.ras.client.holder.model.ClientApplicationValue; import cn.icanci.loopstack.ras.client.properties.RasProperties; import cn.icanci.loopstack.ras.client.server.NamedNettyServerHandler; import cn.icanci.loopstack.ras.client.utils.RandomAddressUtils; +import cn.icanci.loopstack.ras.common.enums.LoadBalanceTypeEnum; import cn.icanci.loopstack.ras.common.exception.ServerOfflineException; import cn.icanci.loopstack.ras.common.model.Application; +import cn.icanci.loopstack.ras.common.model.Instance; import cn.icanci.loopstack.ras.common.socket.RasLoadRequestDTO; import cn.icanci.loopstack.ras.common.socket.RasRefreshDTO; import cn.icanci.loopstack.ras.common.socket.UriConstant; import io.netty.util.internal.ThrowableUtil; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import javax.annotation.Resource; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Service; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; /** * 客户端知晓的服务端信息 @@ -36,24 +47,32 @@ import com.google.common.collect.Maps; @Service public class RasRepositoryHolder implements InitializingBean { - private static final Logger logger = LoggerFactory.getLogger(RasRepositoryHolder.class); + private static final Logger logger = LoggerFactory.getLogger(RasRepositoryHolder.class); @Resource - private RasProperties rasProperties; + private RasProperties rasProperties; + /** + * 服务缓存 + */ + private static final Map SERVER_APPLICATION_MAP = Maps.newConcurrentMap(); + /** + * 服务缓存 + */ + private static final Map CLIENT_APPLICATION_MAP = Maps.newConcurrentMap(); /** * http://{address}:port+UriConstant.ToClient.LOAD */ - private static final String LOAD_REQUEST_FORMAT = "http://%s:%s" + UriConstant.ToServer.LOAD; + private static final String LOAD_REQUEST_FORMAT = "http://%s:%s" + UriConstant.ToServer.LOAD; /** * 请求地址 */ - private static final String REQUEST_FORMAT = "http://%s:%s"; + private static final String REQUEST_FORMAT = "http://%s:%s"; /** * 客户端 */ - private static final Client CLIENT = HttpClientImpl.getInstance(); + private static final Client CLIENT = HttpClientImpl.getInstance(); @Override public void afterPropertiesSet() throws Exception { @@ -106,14 +125,20 @@ public class RasRepositoryHolder implements InitializingBean { /** * 服务信息缓存刷新 + * - TODO 客户端和服务端的主动请求只有注册的时候才会进行处理, + * 其他的时间并不会执行逻辑,因此这里存储Server的信息,但是没有使用 + * 可能的使用场景就是客户端向服务端发起调用,也就是客户端没有目标appId的寻址 + * 这种情况下,会消耗更多的性能和网络带宽,因此在第一个迭代版本并不这样处理 * * @param refresh refresh */ public void refresh(RasRefreshDTO refresh) { - // 先刷新服务端信息 + // 刷新服务端信息 refreshServerInfo(refresh.getServerApplication()); - // 再刷新客户端信息 + // 刷新客户端信息 refreshClientInfo(refresh.getClientApplications()); + // 构建客户端请求路由算法 & 初始化请求算法逻辑 + initClientLoadBalance(); } /** @@ -123,15 +148,96 @@ public class RasRepositoryHolder implements InitializingBean { */ private void refreshServerInfo(Application serverApplication) { // 这里服务端的信息不可能为空,因为数据是从服务端请求而来的 + // 1.如果为空,那么抛出异常,数据有问题,此处选择抛出异常,因为理论上这种情况不应该出现 + refreshServerInfoValidate(serverApplication); + // 2.数据不为空,则更新本地缓存的服务持有者数据 + doRefreshServerInfo(serverApplication); + } - // 1.TODO 如果为空,那么抛出异常,数据有问题 - // 此处选择抛出异常,因为理论上这种情况不应该出现 - if (serverApplication == null) { + /** + * 执行刷新仓储逻辑 + * + * @param serverApplication serverApplication + */ + private void doRefreshServerInfo(Application serverApplication) { + String appId = serverApplication.getAppId(); + LoadBalanceTypeEnum loadBalanceType = LoadBalanceTypeEnum.getByCode(serverApplication.getLoadBalanceType()); + Set instances = serverApplication.getInstances(); + for (Instance instance : instances) { + SERVER_APPLICATION_MAP.put(buildApplicationKey(appId, instance), builderApplicationValue(instance)); } - // 2.数据不为空,则更新本地缓存的服务持有者数据 + } - // 3. + /** + * 获取Key + * + * @param appId appId + * @param instance instance + * @return 返回 ApplicationKey + */ + private ApplicationKey buildApplicationKey(String appId, Instance instance) { + ApplicationKey key = new ApplicationKey(); + key.setAppId(appId); + key.setAddress(instance.getAddress()); + key.setPort(instance.getPort()); + return key; + } + + /** + * 获取Value + * + * @param instance instance + * @return 返回 ApplicationValue + */ + private ApplicationValue builderApplicationValue(Instance instance) { + ApplicationValue value = new ApplicationValue(); + value.setAppId(instance.getAppId()); + value.setAddress(instance.getAddress()); + value.setPort(instance.getPort()); + value.setIsDelete(instance.getIsDelete()); + value.setOnline(instance.getOnline()); + return value; + } + + /** + * 获取Values + * + * @param instances instances + * @return 返回 ApplicationValues + */ + private Set builderApplicationValues(Set instances) { + if (CollectionUtils.isEmpty(instances)) { + return Sets.newHashSet(); + } + Set applicationValues = Sets.newHashSet(); + for (Instance instance : instances) { + applicationValues.add(builderApplicationValue(instance)); + } + return applicationValues; + } + + /** + * Server信息验证 + * + * @param serverApplication serverApplication + */ + private void refreshServerInfoValidate(Application serverApplication) { + if (serverApplication == null) { + throw new ServerApplicationWrongfulException("ServerApplication is Null"); + } + Set instances = serverApplication.getInstances(); + if (CollectionUtils.isEmpty(instances)) { + throw new ServerApplicationWrongfulException("ServerApplication's instances is Empty"); + } + String appId = serverApplication.getAppId(); + if (StringUtils.isBlank(appId)) { + throw new ServerApplicationWrongfulException("ServerApplication's appId is Empty"); + } + LoadBalanceTypeEnum loadBalanceType = LoadBalanceTypeEnum.getByCode(serverApplication.getLoadBalanceType()); + if (loadBalanceType == null) { + throw new ServerApplicationWrongfulException("ServerApplication's loadBalanceType is Empty"); + } } /** @@ -140,6 +246,39 @@ public class RasRepositoryHolder implements InitializingBean { * @param clientApplications clientApplications */ private void refreshClientInfo(List clientApplications) { + // 1.数据验证,可以为空 + if (CollectionUtils.isEmpty(clientApplications)) { + // 1.1 如果为空,则忽略 + return; + } + // 1.2 如果不为空,则加载到缓存,如果缓存中有,则进行替换 + for (Application clientApplication : clientApplications) { + // 验证服务信息 + refreshServerInfoValidate(clientApplication); + String appId = clientApplication.getAppId(); + LoadBalanceTypeEnum loadBalanceType = LoadBalanceTypeEnum.getByCode(clientApplication.getLoadBalanceType()); + Set instances = clientApplication.getInstances(); + CLIENT_APPLICATION_MAP.put(appId, builderClientApplication(loadBalanceType, clientApplication)); + } + } + + /** + * 构建客户端服务信息 + * + * + * @param loadBalanceType loadBalanceType + * @param clientApplication clientApplication + * @return 返回ClientApplicationValue + */ + private ClientApplicationValue builderClientApplication(LoadBalanceTypeEnum loadBalanceType, Application clientApplication) { + ClientApplicationValue applicationValue = new ClientApplicationValue(); + applicationValue.setAppId(clientApplication.getAppId()); + applicationValue.setLoadBalanceType(loadBalanceType); + applicationValue.setApplicationValues(builderApplicationValues(clientApplication.getInstances())); + return applicationValue; + } + + private void initClientLoadBalance() { } @@ -151,6 +290,7 @@ public class RasRepositoryHolder implements InitializingBean { */ public String routingRequestAddress(String appId) { // REQUEST_FORMAT + return null; } } diff --git a/client/src/main/java/cn/icanci/loopstack/ras/client/holder/model/ApplicationKey.java b/client/src/main/java/cn/icanci/loopstack/ras/client/holder/model/ApplicationKey.java new file mode 100644 index 0000000000000000000000000000000000000000..60a0fc70c27655a261950c99cb3b08d963729f4f --- /dev/null +++ b/client/src/main/java/cn/icanci/loopstack/ras/client/holder/model/ApplicationKey.java @@ -0,0 +1,61 @@ +package cn.icanci.loopstack.ras.client.holder.model; + +import java.util.Objects; + +/** + * @author icanci + * @since 1.0 Created in 2023/01/21 08:32 + */ +public class ApplicationKey { + /** + * appId + */ + private String appId; + /** + * address + */ + private String address; + /** + * 服务端口号 + */ + private int port; + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ApplicationKey that = (ApplicationKey) o; + return port == that.port && Objects.equals(appId, that.appId) && Objects.equals(address, that.address); + } + + @Override + public int hashCode() { + return Objects.hash(appId, address, port); + } +} diff --git a/client/src/main/java/cn/icanci/loopstack/ras/client/holder/model/ApplicationValue.java b/client/src/main/java/cn/icanci/loopstack/ras/client/holder/model/ApplicationValue.java new file mode 100644 index 0000000000000000000000000000000000000000..281167d051ea10c554ad5c5c23682309de9b0c2e --- /dev/null +++ b/client/src/main/java/cn/icanci/loopstack/ras/client/holder/model/ApplicationValue.java @@ -0,0 +1,91 @@ +package cn.icanci.loopstack.ras.client.holder.model; + +import java.util.Objects; +import java.util.StringJoiner; + +/** + * @author icanci + * @since 1.0 Created in 2023/01/21 08:32 + */ +public class ApplicationValue { + /** + * AppId 唯一 + */ + private String appId; + /** + * 客户端/服务端注册地址 + */ + private String address; + /** + * 客户端/服务端注册端口 + */ + private int port; + /** + * 是否删除:状态 1无效,0有效(主动设置) + */ + private int isDelete; + /** + * 是否在线 1不在线,0在线(客户端注册处理) + */ + private int online; + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public int getIsDelete() { + return isDelete; + } + + public void setIsDelete(int isDelete) { + this.isDelete = isDelete; + } + + public int getOnline() { + return online; + } + + public void setOnline(int online) { + this.online = online; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ApplicationValue that = (ApplicationValue) o; + return port == that.port && isDelete == that.isDelete && online == that.online && Objects.equals(appId, that.appId) && Objects.equals(address, that.address); + } + + @Override + public int hashCode() { + return Objects.hash(appId, address, port, isDelete, online); + } + + @Override + public String toString() { + return new StringJoiner(",").add("appId=" + appId).add("address=" + address).add("port=" + port).add("isDelete=" + isDelete).add("online=" + online).toString(); + } +} diff --git a/client/src/main/java/cn/icanci/loopstack/ras/client/holder/model/ClientApplicationValue.java b/client/src/main/java/cn/icanci/loopstack/ras/client/holder/model/ClientApplicationValue.java new file mode 100644 index 0000000000000000000000000000000000000000..6d9c21caebbc23b1de3990b8ada35b847fb4fa6c --- /dev/null +++ b/client/src/main/java/cn/icanci/loopstack/ras/client/holder/model/ClientApplicationValue.java @@ -0,0 +1,64 @@ +package cn.icanci.loopstack.ras.client.holder.model; + +import cn.icanci.loopstack.ras.common.enums.LoadBalanceTypeEnum; + +import java.util.Objects; +import java.util.Set; + +/** + * @author icanci + * @since 1.0 Created in 2023/01/21 08:32 + */ +public class ClientApplicationValue { + /** + * AppId + */ + private String appId; + /** + * 负载均衡算法 + */ + private LoadBalanceTypeEnum loadBalanceType; + /** + * 注册的App + */ + private Set applicationValues; + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public LoadBalanceTypeEnum getLoadBalanceType() { + return loadBalanceType; + } + + public void setLoadBalanceType(LoadBalanceTypeEnum loadBalanceType) { + this.loadBalanceType = loadBalanceType; + } + + public Set getApplicationValues() { + return applicationValues; + } + + public void setApplicationValues(Set applicationValues) { + this.applicationValues = applicationValues; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ClientApplicationValue that = (ClientApplicationValue) o; + return Objects.equals(appId, that.appId) && loadBalanceType == that.loadBalanceType && Objects.equals(applicationValues, that.applicationValues); + } + + @Override + public int hashCode() { + return Objects.hash(appId, loadBalanceType, applicationValues); + } +} diff --git a/common/src/main/java/cn/icanci/loopstack/ras/common/enums/LoadBalanceTypeEnum.java b/common/src/main/java/cn/icanci/loopstack/ras/common/enums/LoadBalanceTypeEnum.java index 36e5fd9abc657906158ae41d1d9b44be236e7f6f..878358bedb87bd4075b869a8c519a30394e1d78e 100644 --- a/common/src/main/java/cn/icanci/loopstack/ras/common/enums/LoadBalanceTypeEnum.java +++ b/common/src/main/java/cn/icanci/loopstack/ras/common/enums/LoadBalanceTypeEnum.java @@ -1,5 +1,8 @@ package cn.icanci.loopstack.ras.common.enums; +import java.util.HashMap; +import java.util.Map; + /** * @author icanci * @since 1.0 Created in 2023/01/19 17:28 @@ -41,6 +44,16 @@ public enum LoadBalanceTypeEnum { // ; + private static final Map ENUMS = new HashMap<>(); + + static { + LoadBalanceTypeEnum[] loadBalanceTypeEnums = LoadBalanceTypeEnum.values(); + + for (LoadBalanceTypeEnum loadBalanceTypeEnum : loadBalanceTypeEnums) { + ENUMS.put(loadBalanceTypeEnum.getCode(), loadBalanceTypeEnum); + } + } + LoadBalanceTypeEnum(String code, String desc) { this.code = code; this.desc = desc; @@ -56,4 +69,8 @@ public enum LoadBalanceTypeEnum { public String getDesc() { return desc; } + + public static LoadBalanceTypeEnum getByCode(String code) { + return ENUMS.get(code); + } }