From 7dc10779355ae32d8621e66ebd4eb33a66f4c592 Mon Sep 17 00:00:00 2001 From: Wang Hao <3388656408@qq.com> Date: Sat, 20 Apr 2024 10:29:23 +0800 Subject: [PATCH] 420 --- .gitignore | 39 ++ pom.xml | 193 +++++++++ .../java/com/muyu/LoadCenterApplication.java | 29 ++ .../java/com/muyu/aly/ALYunEcsService.java | 301 ++++++++++++++ .../java/com/muyu/aly/config/AliConfig.java | 91 +++++ .../com/muyu/aly/model/EcsRemoveModel.java | 33 ++ .../com/muyu/aly/model/EcsSelectModel.java | 37 ++ .../java/com/muyu/aly/model/InstanceInfo.java | 40 ++ .../muyu/common/constant/CacheConstants.java | 52 +++ .../muyu/common/constant/LoadConstants.java | 42 ++ .../java/com/muyu/common/domain/Result.java | 111 +++++ .../java/com/muyu/common/model/CPUInfo.java | 41 ++ .../java/com/muyu/common/model/FlowInfo.java | 38 ++ .../java/com/muyu/common/model/JVMInfo.java | 66 +++ .../java/com/muyu/common/model/MqttInfo.java | 61 +++ .../com/muyu/common/model/TotalNumber.java | 32 ++ .../FastJson2JsonRedisSerializer.java | 50 +++ .../common/redis/configure/RedisConfig.java | 43 ++ .../common/redis/service/RedisService.java | 386 ++++++++++++++++++ .../muyu/controller/GatewayController.java | 35 ++ .../com/muyu/gateway/cache/LoadNodeCache.java | 60 +++ .../muyu/gateway/cache/LoadSeriesCache.java | 59 +++ .../com/muyu/gateway/cache/NodeCache.java | 52 +++ .../com/muyu/gateway/cache/NodeReduced.java | 68 +++ .../muyu/gateway/cache/NodeScoreCache.java | 78 ++++ .../muyu/gateway/cache/NodeSetVinCache.java | 55 +++ .../gateway/cache/VehicleLineNodeCache.java | 63 +++ .../gateway/cache/abs/GatewayCacheAbs.java | 30 ++ .../java/com/muyu/gateway/model/NodeInfo.java | 39 ++ .../java/com/muyu/gateway/model/NodeJoin.java | 30 ++ .../com/muyu/gateway/model/NodeVehicle.java | 31 ++ .../muyu/gateway/model/WorkGatewayNode.java | 27 ++ .../com/muyu/service/GatewayLoadService.java | 16 + .../service/impl/GatewayLoadServiceImpl.java | 203 +++++++++ src/main/java/com/muyu/task/Collection.java | 182 +++++++++ .../task/contraction/ContractionVolume.java | 129 ++++++ src/main/resources/application.yml | 17 + src/test/java/com/muyu/LoadTest.java | 153 +++++++ 38 files changed, 3012 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/muyu/LoadCenterApplication.java create mode 100644 src/main/java/com/muyu/aly/ALYunEcsService.java create mode 100644 src/main/java/com/muyu/aly/config/AliConfig.java create mode 100644 src/main/java/com/muyu/aly/model/EcsRemoveModel.java create mode 100644 src/main/java/com/muyu/aly/model/EcsSelectModel.java create mode 100644 src/main/java/com/muyu/aly/model/InstanceInfo.java create mode 100644 src/main/java/com/muyu/common/constant/CacheConstants.java create mode 100644 src/main/java/com/muyu/common/constant/LoadConstants.java create mode 100644 src/main/java/com/muyu/common/domain/Result.java create mode 100644 src/main/java/com/muyu/common/model/CPUInfo.java create mode 100644 src/main/java/com/muyu/common/model/FlowInfo.java create mode 100644 src/main/java/com/muyu/common/model/JVMInfo.java create mode 100644 src/main/java/com/muyu/common/model/MqttInfo.java create mode 100644 src/main/java/com/muyu/common/model/TotalNumber.java create mode 100644 src/main/java/com/muyu/common/redis/configure/FastJson2JsonRedisSerializer.java create mode 100644 src/main/java/com/muyu/common/redis/configure/RedisConfig.java create mode 100644 src/main/java/com/muyu/common/redis/service/RedisService.java create mode 100644 src/main/java/com/muyu/controller/GatewayController.java create mode 100644 src/main/java/com/muyu/gateway/cache/LoadNodeCache.java create mode 100644 src/main/java/com/muyu/gateway/cache/LoadSeriesCache.java create mode 100644 src/main/java/com/muyu/gateway/cache/NodeCache.java create mode 100644 src/main/java/com/muyu/gateway/cache/NodeReduced.java create mode 100644 src/main/java/com/muyu/gateway/cache/NodeScoreCache.java create mode 100644 src/main/java/com/muyu/gateway/cache/NodeSetVinCache.java create mode 100644 src/main/java/com/muyu/gateway/cache/VehicleLineNodeCache.java create mode 100644 src/main/java/com/muyu/gateway/cache/abs/GatewayCacheAbs.java create mode 100644 src/main/java/com/muyu/gateway/model/NodeInfo.java create mode 100644 src/main/java/com/muyu/gateway/model/NodeJoin.java create mode 100644 src/main/java/com/muyu/gateway/model/NodeVehicle.java create mode 100644 src/main/java/com/muyu/gateway/model/WorkGatewayNode.java create mode 100644 src/main/java/com/muyu/service/GatewayLoadService.java create mode 100644 src/main/java/com/muyu/service/impl/GatewayLoadServiceImpl.java create mode 100644 src/main/java/com/muyu/task/Collection.java create mode 100644 src/main/java/com/muyu/task/contraction/ContractionVolume.java create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/com/muyu/LoadTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0d38f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +.idea + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..96e6623 --- /dev/null +++ b/pom.xml @@ -0,0 +1,193 @@ + + + 4.0.0 + + com.muyu + LoadCenter + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + 2.7.18 + + + + com.aliyun + darabonba-string + 0.0.3 + + + com.aliyun + tea + [1.0.3, 2.0.0) + + + + com.aliyun + ecs20140526 + 3.1.2 + + + com.aliyun + tea-openapi + 0.3.2 + + + com.aliyun + tea-util + 0.2.21 + + + com.aliyun + tea-console + 0.0.1 + + + com.aliyun + darabonba-env + 0.1.1 + + + com.aliyun + tea + 1.1.14 + + + + org.springframework.boot + spring-boot-starter-test + 2.7.18 + test + + + + + org.springframework.boot + spring-boot-starter-data-redis + 2.7.18 + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.5 + + + com.alibaba.fastjson2 + fastjson2 + 2.0.47 + + + + org.redisson + redisson + 3.15.5 + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + 16 + 16 + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.3 + true + + sonatype-nexus-staging + https://s01.oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.1.1 + + UTF-8 + none + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4.1 + + + jar-with-dependencies + + + + true + com.aliyun.sample.Sample + + + + + + make-assembly + package + + single + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/muyu/LoadCenterApplication.java b/src/main/java/com/muyu/LoadCenterApplication.java new file mode 100644 index 0000000..6090b00 --- /dev/null +++ b/src/main/java/com/muyu/LoadCenterApplication.java @@ -0,0 +1,29 @@ +package com.muyu; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/12 22:14 //创建时间 + */ + + +@SpringBootApplication +@EnableScheduling +public class LoadCenterApplication { + public static void main(String[] args) { + SpringApplication.run(LoadCenterApplication.class, args); + } + + + +} + + + + + + + diff --git a/src/main/java/com/muyu/aly/ALYunEcsService.java b/src/main/java/com/muyu/aly/ALYunEcsService.java new file mode 100644 index 0000000..7a7b28b --- /dev/null +++ b/src/main/java/com/muyu/aly/ALYunEcsService.java @@ -0,0 +1,301 @@ +package com.muyu.aly; + +import com.alibaba.fastjson2.JSON; +import com.aliyun.ecs20140526.Client; +import com.aliyun.ecs20140526.models.*; +import com.aliyun.tea.TeaException; + +import com.muyu.aly.config.AliConfig; +import com.muyu.aly.model.EcsSelectModel; +import com.muyu.aly.model.EcsRemoveModel; +import com.muyu.aly.model.InstanceInfo; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + + +import java.util.ArrayList; +import java.util.List; + + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:45 //创建时间 + */ + +@Service +@Log4j2 +public class ALYunEcsService { + + private final AliConfig aliConfig; + + private final Client client; + + public ALYunEcsService(AliConfig aliConfig, Client client) { + this.aliConfig = aliConfig; + this.client = client; + } + + /** + * 根据实例ID和实例名称查询实例信息 + * @param instanceName + * @return 返回实例集合信息 + */ + public List selectECS(String instanceName) throws Exception { + + DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest() + .setRegionId(aliConfig.getRegionId()) + .setInstanceName(instanceName) + .setPageSize(10); + + com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); + + List instanceInfos = new ArrayList<>(); // 用于存储查询到的实例信息 + + try { + DescribeInstancesResponse describeInstancesResponse = client.describeInstancesWithOptions(describeInstancesRequest, runtime); + DescribeInstancesResponseBody body = describeInstancesResponse.getBody(); + DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstances instances = body.getInstances(); + List instanceList = instances.getInstance(); + + // 修改 selectECS 方法中 IP 地址的处理部分 + for (DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstancesInstance item : instanceList) { + InstanceInfo instanceInfo = new InstanceInfo(); + instanceInfo.setInstanceId(item.getInstanceId()); + instanceInfo.setInstanceName(item.getInstanceName()); + String publicIpAddress = item.getPublicIpAddress().getIpAddress().toString(); + // 去掉方括号 + publicIpAddress = publicIpAddress.substring(1, publicIpAddress.length() - 1); + instanceInfo.setPublicIpAddress(publicIpAddress); + String privateIpAddress = item.getVpcAttributes().getPrivateIpAddress().ipAddress.toString(); + // 去掉方括号 + privateIpAddress = privateIpAddress.substring(1, privateIpAddress.length() - 1); + instanceInfo.setPrivateIpAddress(privateIpAddress); + instanceInfos.add(instanceInfo); + } + } catch (TeaException error) { + error.printStackTrace(); + // 异常处理 + } catch (Exception _error) { + _error.printStackTrace(); + // 异常处理 + } + + return instanceInfos; + } + + + + /** + * 创建实例方法 + * @throws Exception + */ + public String createAnServer(String isNull) throws Exception { + // 地域Id + //String regionId = "cn-shanghai"; + String regionId = aliConfig.getRegionId(); + // 镜像 ID,启动实例时选择的镜像资源。 + // String imageId = "m-uf6elrscl3c9wk6o762l"; + String imageId = aliConfig.getImageId(); + // 实例规格 + //String instanceType = "ecs.u1-c1m1.large"; + String instanceType = aliConfig.getInstanceType(); + // 新创建实例所属于的安全组 ID。 + //String securityGroupId = "sg-uf6bj6vxp8ruhvffdsau"; + String securityGroupId = aliConfig.getSecurityGroupId(); + // 虚拟交换机 ID。 + //String vSwitchId = "vsw-uf66jtgij0ptqxf1ix6l7 "; + String vSwitchId = aliConfig.getVSwitchId(); + // 公网出带宽最大值,单位为 Mbit/s。取值范围:0~100。 默认值:0。 + //Integer internetMaxBandwidthOut = Integer.parseInt("2"); + Integer internetMaxBandwidthOut = Integer.parseInt(aliConfig.getInternetMaxBandwidthOut()); + // 网络计费类型。取值范围: + // PayByBandwidth: 按固定带宽计费。 + // PayByTraffic: 按使用流量计费。 + // 默认值:PayByTraffic。 + //String internetChargeType = "PayByTraffic"; + String internetChargeType = aliConfig.getInternetChargeType(); + // 系统盘大小 + //String size = "20"; + String size = aliConfig.getSize(); + // 系统盘的云盘种类 + //String category = "cloud_essd"; + String category = aliConfig.getCategory(); + // ECS实例的计费方式 + // PrePaid:包年包月 + // PostPaid:按量付费 + //String instanceChargeType = "PostPaid"; + String instanceChargeType = aliConfig.getInstanceChargeType(); + // 创建 【1台】 实例 + if (isNull == null){ + String instances = RunInstance(client, regionId, imageId, instanceType, securityGroupId, vSwitchId, internetMaxBandwidthOut, internetChargeType, size, category, instanceChargeType); + return instances; //返回实例ID + } + // 批量创建实例 + String instances = RunInstances(client, regionId, imageId, instanceType, securityGroupId, vSwitchId, internetMaxBandwidthOut, internetChargeType, size, category, instanceChargeType); + //返回实例ID + return instances; + } + + + /** + * 批量创建【2】台服务器 + * RunInstances 通过备选实例规格创建ECS实例最佳实践 + * 该场景中,在调用RunInstances创建ECS实例时判断是否发生库存不足等错误,如果发生错误,将调用DescribeRecommendInstanceType查询备选实例,然后通过备选实例规格重新创建ECS实例。 + */ + public String RunInstances(Client client, String regionId, String imageId, String instanceType, String securityGroupId, String vSwitchId, Integer internetMaxBandwidthOut, String internetChargeType, String size, String category, String instanceChargeType) throws Exception { + System.setOut(new java.io.PrintStream(System.out, true, "UTF-8")); + RunInstancesRequest request1 = new RunInstancesRequest() + .setRegionId(regionId) + .setImageId(imageId) + .setInstanceType(instanceType) + .setSecurityGroupId(securityGroupId) + .setVSwitchId(vSwitchId) + .setInstanceName("Myname") + .setDescription("Myprocure") + .setInternetMaxBandwidthOut(internetMaxBandwidthOut) + .setInternetChargeType(internetChargeType) + .setInstanceChargeType(instanceChargeType) + // 批量创建五台ECS实例,如果不设置该参数,默认创建一台ECS实例。 + .setAmount(2) + // 如果缺少库存可以接受的最低创建数量。 + // minAmount = 2, + // 打开预检参数功能,不会实际创建ECS实例,只检查参数正确性、用户权限或者ECS库存等问题。 + // 实际情况下,设置了DryRun参数后,Amount必须为1,MinAmount必须为空,您可以根据实际需求修改代码。 + .setDryRun(false) + .setSystemDisk(new RunInstancesRequest.RunInstancesRequestSystemDisk() + .setSize(size) + .setCategory(category)); + RunInstancesResponse responces = client.runInstances(request1); + try { + log.info("--------------------批量创建实例开始--------------------"); + + log.info("--------------------创建实例成功,实例ID:" + JSON.toJSONString(responces.body.instanceIdSets.instanceIdSet) + "--------------------"); + } catch (TeaException error) { + log.error("--------------------创建实例失败:" + error+ "--------------------"); + } catch (Exception _error) { + TeaException error = new TeaException(_error.getMessage(), _error); + log.error("--------------------创建实例失败:" +error + "--------------------"); + } + return JSON.toJSONString(responces.body.instanceIdSets.instanceIdSet); + } + + + /** + * 批量创建【1】台服务器 + * RunInstances 通过备选实例规格创建ECS实例最佳实践 + * 该场景中,在调用RunInstances创建ECS实例时判断是否发生库存不足等错误,如果发生错误,将调用DescribeRecommendInstanceType查询备选实例,然后通过备选实例规格重新创建ECS实例。 + */ + public String RunInstance(Client client, String regionId, String imageId, String instanceType, String securityGroupId, String vSwitchId, Integer internetMaxBandwidthOut, String internetChargeType, String size, String category, String instanceChargeType) throws Exception { + System.setOut(new java.io.PrintStream(System.out, true, "UTF-8")); + RunInstancesRequest request1 = new RunInstancesRequest() + .setRegionId(regionId) + .setImageId(imageId) + .setInstanceType(instanceType) + .setSecurityGroupId(securityGroupId) + .setVSwitchId(vSwitchId) + .setInstanceName("Myname") + .setDescription("Myprocure") + .setInternetMaxBandwidthOut(internetMaxBandwidthOut) + .setInternetChargeType(internetChargeType) + .setInstanceChargeType(instanceChargeType) + // 批量创建五台ECS实例,如果不设置该参数,默认创建一台ECS实例。 + //.setAmount(2) + // 如果缺少库存可以接受的最低创建数量。 + // minAmount = 2, + // 打开预检参数功能,不会实际创建ECS实例,只检查参数正确性、用户权限或者ECS库存等问题。 + // 实际情况下,设置了DryRun参数后,Amount必须为1,MinAmount必须为空,您可以根据实际需求修改代码。 + .setDryRun(false) + .setSystemDisk(new RunInstancesRequest.RunInstancesRequestSystemDisk() + .setSize(size) + .setCategory(category)); + RunInstancesResponse responces = client.runInstances(request1); + try { + log.info("--------------------批量创建实例开始--------------------"); + + log.info("--------------------创建实例成功,实例ID:" + JSON.toJSONString(responces.body.instanceIdSets.instanceIdSet) + "--------------------"); + } catch (TeaException error) { + log.error("--------------------创建实例失败:" + error+ "--------------------"); + } catch (Exception _error) { + TeaException error = new TeaException(_error.getMessage(), _error); + log.error("--------------------创建实例失败:" +error + "--------------------"); + } + return JSON.toJSONString(responces.body.instanceIdSets.instanceIdSet); + } + + + public DescribeInstancesResponse DescribeInstances(Client client, String regionId, String instanceIds, String instanceName) throws Exception { + DescribeInstancesRequest req = new DescribeInstancesRequest() + .setRegionId(regionId) + .setInstanceName(instanceName); + if (!com.aliyun.teautil.Common.empty(instanceIds)) { + req.instanceIds = JSON.toJSONString(com.aliyun.darabonbastring.Client.split(instanceIds, ",", 50)); + } + + DescribeInstancesResponse resp = client.describeInstances(req); + log.error("--------------------查询需要删除的实例--------------------"); + return resp; + } + + public void ModifyInstanceAttribute(Client client, String instatnceId) throws Exception { + ModifyInstanceAttributeRequest req = new ModifyInstanceAttributeRequest() + .setInstanceId(instatnceId) + .setDeletionProtection(false); + client.modifyInstanceAttribute(req); + log.info("--------------------" + instatnceId + "释放保护取消成功--------------------"); + } + + public void DeleteInstances(Client client, String regionId, String instanceIds, String force) throws Exception { + DeleteInstancesRequest req = new DeleteInstancesRequest() + .setRegionId(regionId) + .setInstanceId(com.aliyun.darabonbastring.Client.split(instanceIds, ",", 50)) + .setForce(com.aliyun.teautil.Common.equalString(force, "true")); + DeleteInstancesResponse resp = client.deleteInstances(req); + log.info("--------------------实例释放成功--------------------"); + log.info(JSON.toJSONString(com.aliyun.teautil.Common.toMap(resp))); + } + + /** + * 批量删除实力 + * @param + * @throws Exception + */ + public void DeleServerCreateAn(EcsRemoveModel escRemoveModel) throws Exception { + // 区域ID + //String regionId = "cn-shanghai"; + String regionId = aliConfig.getRegionId(); + // 多个实例ID,用英文逗号分隔 + //String instanceIds = "i-uf6h4s0jtpvobykd7vzc"; + String instanceIds = escRemoveModel.getInstanceIds(); + // 实例名称,支持使用通配符*进行模糊搜索 + //String instanceName = "MyFirstEcsInstance"; + String instanceName = escRemoveModel.getInstanceName(); + // 强制删除有删除保护的机器 + //String deleteProtected = "true"; + String deleteProtected = "true"; + // 强制删除运行中的机器 + //String force = "true"; + String force = "true"; + if (com.aliyun.teautil.Common.equalString(deleteProtected, force)) { + DescribeInstancesResponse describeInstancesResp = DescribeInstances(client, regionId, instanceIds, instanceName); + instanceIds = ""; + for (DescribeInstancesResponseBody.DescribeInstancesResponseBodyInstancesInstance instance : describeInstancesResp.body.instances.instance) { + instanceIds = "" + instance.instanceId + "," + instanceIds + ""; + if (instance.deletionProtection) { + ModifyInstanceAttribute(client, instance.instanceId); + } + + } + instanceIds = com.aliyun.darabonbastring.Client.subString(instanceIds, 0, -1); + } + + if (com.aliyun.teautil.Common.empty(instanceIds)) { + log.info("--------------------无有效实例可删除--------------------"); + return ; + } + + DeleteInstances(client, regionId, instanceIds, force); + } + +} + + diff --git a/src/main/java/com/muyu/aly/config/AliConfig.java b/src/main/java/com/muyu/aly/config/AliConfig.java new file mode 100644 index 0000000..6717e04 --- /dev/null +++ b/src/main/java/com/muyu/aly/config/AliConfig.java @@ -0,0 +1,91 @@ +package com.muyu.aly.config; + +import com.aliyun.ecs20140526.Client; +import com.aliyun.teaopenapi.models.Config; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:39 //创建时间 + */ + + +@Data +@Configuration +@ConfigurationProperties(prefix = "aliyun") +public class AliConfig { + + private String accessKeyId; + + private String accessKeySecret; + + /** + * 地域ID + */ + private String regionId; + + /** + * 镜像ID + */ + private String imageId; + + /** + * 实例规格 + */ + private String instanceType; + + /** + * 安全组ID + */ + private String securityGroupId; + + /** + * 虚拟交换机ID + */ + private String vSwitchId; + + /** + * 公网出带宽最大值,单位为 Mbit/s。取值范围:0~100。 默认值:0。 + */ + private String internetMaxBandwidthOut; + + /** + * 网络计费类型,取值范围 + */ + private String internetChargeType; + + /** + * 系统盘大小 + */ + private String size; + + /** + * 系统盘的云盘种类 + */ + private String category; + + /** + * ECS实例的计费方式 + */ + private String instanceChargeType; + + @Bean + public Client createClient(AliConfig aliConfig) throws Exception { + // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。 + // 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html。 + Config config = new Config() + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 + .setAccessKeyId(aliConfig.getAccessKeyId()) + // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 + .setAccessKeySecret(aliConfig.accessKeySecret) + .setRegionId(aliConfig.getRegionId()); + return new Client(config); + } + +} + + diff --git a/src/main/java/com/muyu/aly/model/EcsRemoveModel.java b/src/main/java/com/muyu/aly/model/EcsRemoveModel.java new file mode 100644 index 0000000..c6d053c --- /dev/null +++ b/src/main/java/com/muyu/aly/model/EcsRemoveModel.java @@ -0,0 +1,33 @@ +package com.muyu.aly.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:43 //创建时间 + */ + + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class EcsRemoveModel { + + + /** + * 实例ID + */ + private String instanceIds; + + /** + * 实例名称 + */ + private String instanceName; + +} + + diff --git a/src/main/java/com/muyu/aly/model/EcsSelectModel.java b/src/main/java/com/muyu/aly/model/EcsSelectModel.java new file mode 100644 index 0000000..8b1102f --- /dev/null +++ b/src/main/java/com/muyu/aly/model/EcsSelectModel.java @@ -0,0 +1,37 @@ +package com.muyu.aly.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:42 //创建时间 + */ + + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class EcsSelectModel { + + /** + * 实例Id + * */ + private List instanceIdList; + + + /** + * 实例名称 + * */ + private List instanceNameList; + + + +} + + diff --git a/src/main/java/com/muyu/aly/model/InstanceInfo.java b/src/main/java/com/muyu/aly/model/InstanceInfo.java new file mode 100644 index 0000000..894f2c3 --- /dev/null +++ b/src/main/java/com/muyu/aly/model/InstanceInfo.java @@ -0,0 +1,40 @@ +package com.muyu.aly.model; + +import lombok.*; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:44 //创建时间 + */ + + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class InstanceInfo { + + /** + * 节点ID + */ + private String instanceId; + + /** + * 节点名称 + */ + private String instanceName; + + /** + * 公网IP地址 + */ + private String publicIpAddress; + + /** + * 内网IP地址 + */ + private String privateIpAddress; + +} + + diff --git a/src/main/java/com/muyu/common/constant/CacheConstants.java b/src/main/java/com/muyu/common/constant/CacheConstants.java new file mode 100644 index 0000000..a390ee7 --- /dev/null +++ b/src/main/java/com/muyu/common/constant/CacheConstants.java @@ -0,0 +1,52 @@ +package com.muyu.common.constant; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:47 //创建时间 + */ + + +public class CacheConstants { + /** + * 缓存有效时间 50分钟 + */ + public final static int EXPIRATIOTIME = 50; + + /** + * 通用节点缓存前缀 encode + */ + public final static String GATEWAY_COMMON = "gateway:load:"; + + /** + * 网关负载节点缓存 + */ + public final static String GATEWAY_LOAD_NODE_KEY = "node"; + + /** + * 网关负载序列 + */ + public final static String GATEWAY_LOAD_SERIES_KEY = "series"; + + /** + * 网关节点缓存前缀 + */ + public final static String GATE_WAY_NODE_INFO= "gateway:node:info:"; + + /** + * 网关节点连接数前缀 + */ + public final static String GATEWAY_NODE_SCORE_CACHE = "score"; + + /** + * 网关节点存储VIN信息 + */ + public final static String GATEWAY_VEHICLE= "gateway:vehicle:"; + + /** + * 网关车辆对应网关节点ID + */ + public final static String GATEWAY_VEHICLE_LINE= "gateway:vehicleLine:"; + +} + + diff --git a/src/main/java/com/muyu/common/constant/LoadConstants.java b/src/main/java/com/muyu/common/constant/LoadConstants.java new file mode 100644 index 0000000..35689a3 --- /dev/null +++ b/src/main/java/com/muyu/common/constant/LoadConstants.java @@ -0,0 +1,42 @@ +package com.muyu.common.constant; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:48 //创建时间 + */ + + +public class LoadConstants { + /** + * 负载的长度 + */ + public final static Long NODE_LENGTH = 100L; + + /** + * 每个节点最大连接数 + */ + public final static Long MAX_NUMBER = 100L; + + /** + * 创建节点 判断 + */ + public final static String IS_NULL = "isNotNull"; + + /** + * 节点 扩容百分比 60% 一台 + */ + public final static Long INTERMEDIATE = 60L; + + /** + * 节点 扩容百分比 80% 两台 + */ + public final static Long MAXIMUM = 80L; + + /** + * 通用数值 100 + */ + public final static int BE_COMMON = 100; + +} + + diff --git a/src/main/java/com/muyu/common/domain/Result.java b/src/main/java/com/muyu/common/domain/Result.java new file mode 100644 index 0000000..d7890b1 --- /dev/null +++ b/src/main/java/com/muyu/common/domain/Result.java @@ -0,0 +1,111 @@ +package com.muyu.common.domain; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 响应信息主体 + * + * @author muyu + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Result implements Serializable { + /** + * 成功 + */ + public static final int SUCCESS = 200; + /** + * 失败 + */ + public static final int FAIL = 500; + /** + * 警告 + */ + public static final int WARN = 800; + + private static final long serialVersionUID = 1L; + private int code; + + private String msg; + + private T data; + + public static Result success () { + return restResult(null, SUCCESS, null); + } + + public static Result success (T data) { + return restResult(data, SUCCESS, null); + } + + public static Result success (T data, String msg) { + return restResult(data, SUCCESS, msg); + } + + public static Result error () { + return restResult(null, FAIL, null); + } + + public static Result error (String msg) { + return restResult(null, FAIL, msg); + } + + public static Result error (T data) { + return restResult(data, FAIL, null); + } + + public static Result error (T data, String msg) { + return restResult(data, FAIL, msg); + } + + public static Result error (int code, String msg) { + return restResult(null, code, msg); + } + + + + public static Result warn () { + return restResult(null, WARN, null); + } + + public static Result warn (String msg) { + return restResult(null, WARN, msg); + } + + public static Result warn (T data) { + return restResult(data, WARN, null); + } + + public static Result warn (T data, String msg) { + return restResult(data, WARN, msg); + } + + public static Result warn (int code, String msg) { + return restResult(null, code, msg); + } + + private static Result restResult (T data, int code, String msg) { + return Result.builder() + .code(code) + .data(data) + .msg(msg) + .build(); + } + + public static Boolean isError (Result ret) { + return !isSuccess(ret); + } + + public static Boolean isSuccess (Result ret) { + return Result.SUCCESS == ret.getCode(); + } + +} diff --git a/src/main/java/com/muyu/common/model/CPUInfo.java b/src/main/java/com/muyu/common/model/CPUInfo.java new file mode 100644 index 0000000..eb6ec1b --- /dev/null +++ b/src/main/java/com/muyu/common/model/CPUInfo.java @@ -0,0 +1,41 @@ +package com.muyu.common.model; + +import lombok.Data; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:49 //创建时间 + */ + +@Data +public class CPUInfo { + /** + * CPU核数 + */ + private long cpuNum; + /** + * 内核态使用率 + */ + private String cSys; + /** + * 空闲率 + */ + private String idle; + /** + * I/O等待 + */ + private String iowait; + /** + * 用户态使用率 + */ + private String user; + + + + + + + +} + + diff --git a/src/main/java/com/muyu/common/model/FlowInfo.java b/src/main/java/com/muyu/common/model/FlowInfo.java new file mode 100644 index 0000000..c92a39a --- /dev/null +++ b/src/main/java/com/muyu/common/model/FlowInfo.java @@ -0,0 +1,38 @@ +package com.muyu.common.model; + +import lombok.Data; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:49 //创建时间 + */ + +@Data +public class FlowInfo { + + /** + * 上次读取吞吐量 + */ + private String lastReadThroughput; + /** + * 上次写入吞吐量 + */ + private String lastWriteThroughput; + /** + * 读取总吞吐量 + */ + private String readBytesHistory; + /** + * 实写字节 + */ + private String realWriteBytes; + /** + * 写入总吞吐量 + */ + private String writeBytesHistory; + + + +} + + diff --git a/src/main/java/com/muyu/common/model/JVMInfo.java b/src/main/java/com/muyu/common/model/JVMInfo.java new file mode 100644 index 0000000..f5cd625 --- /dev/null +++ b/src/main/java/com/muyu/common/model/JVMInfo.java @@ -0,0 +1,66 @@ +package com.muyu.common.model; + +import lombok.Data; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:49 //创建时间 + */ + +@Data +public class JVMInfo { + + + /** + * 文件描述(句柄) + */ + private String fileDescriptors; + /** + * 堆内存 + */ + private String heapCommit; + /** + * 堆初始化空间 + */ + private String heapInit; + /** + * 堆最大内存 + */ + private String heapMax; + /** + * 堆使用空间 + */ + private String heapUsed; + /** + * JAVA目录 + */ + private String jdkHome; + /** + * JDK版本 + */ + private String jdkVersion; + /** + * 非堆空间 + */ + private String noHeapCommit; + /** + * 非堆初始化空间 + */ + private String noHeapInit; + /** + * 非堆最大空间 + */ + private String noHeapMax; + /** + * 非堆使用空间 + */ + private String noHeapUsed; + /** + * 线程数量 + */ + private long threadCount; + + +} + + diff --git a/src/main/java/com/muyu/common/model/MqttInfo.java b/src/main/java/com/muyu/common/model/MqttInfo.java new file mode 100644 index 0000000..85daeb7 --- /dev/null +++ b/src/main/java/com/muyu/common/model/MqttInfo.java @@ -0,0 +1,61 @@ +package com.muyu.common.model; + +import lombok.Data; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:49 //创建时间 + */ + + +@Data +public class MqttInfo { + + /** + * 关闭事件数量 + */ + private long closeEventSize; + /** + * 连接事件数量 + */ + private long connectEventSize; + /** + * 链接总数 + */ + private long connectSize; + /** + * 断开链接数量 + */ + private long disconnectEventSize; + /** + * 推送数量 + */ + private long publishEventSize; + /** + * 发布重试事件数量 + */ + private long publishRetryEventSize; + /** + * 保留消息数量 + */ + private long retainSize; + /** + * 订阅事件数量 + */ + private long subscribeEventSize; + /** + * 订阅数量 + */ + private long subscribeSize; + /** + * 主题数量 + */ + private long topicSize; + /** + * 取消订阅数量 + */ + private long unSubscribeEventSize; + +} + + diff --git a/src/main/java/com/muyu/common/model/TotalNumber.java b/src/main/java/com/muyu/common/model/TotalNumber.java new file mode 100644 index 0000000..536931e --- /dev/null +++ b/src/main/java/com/muyu/common/model/TotalNumber.java @@ -0,0 +1,32 @@ +package com.muyu.common.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:50 //创建时间 + */ + + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TotalNumber { + + /** + * 节点数量 + */ + private Long nodeNumber; + + /** + * 连接总数 + */ + private Long ConnectionTotal; + +} + + diff --git a/src/main/java/com/muyu/common/redis/configure/FastJson2JsonRedisSerializer.java b/src/main/java/com/muyu/common/redis/configure/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..6079f51 --- /dev/null +++ b/src/main/java/com/muyu/common/redis/configure/FastJson2JsonRedisSerializer.java @@ -0,0 +1,50 @@ +package com.muyu.common.redis.configure; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.filter.Filter; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Redis使用FastJson序列化 + * + * @author muyu + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer { + + public static final String[] JSON_WHITELIST_STR = {"org.springframework", "com.muyu"}; + + public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(JSON_WHITELIST_STR); + + private Class clazz; + + public FastJson2JsonRedisSerializer (Class clazz) { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize (T t) throws SerializationException { + if (t == null) { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize (byte[] bytes) throws SerializationException { + if (bytes == null || bytes.length <= 0) { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); + } +} diff --git a/src/main/java/com/muyu/common/redis/configure/RedisConfig.java b/src/main/java/com/muyu/common/redis/configure/RedisConfig.java new file mode 100644 index 0000000..1697d37 --- /dev/null +++ b/src/main/java/com/muyu/common/redis/configure/RedisConfig.java @@ -0,0 +1,43 @@ +package com.muyu.common.redis.configure; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author muyu + */ +@Configuration +@EnableCaching +@AutoConfigureBefore(RedisAutoConfiguration.class) +public class RedisConfig extends CachingConfigurerSupport { + + + @Bean + @SuppressWarnings(value = {"unchecked", "rawtypes"}) + public RedisTemplate redisTemplate (RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } +} diff --git a/src/main/java/com/muyu/common/redis/service/RedisService.java b/src/main/java/com/muyu/common/redis/service/RedisService.java new file mode 100644 index 0000000..b63cfe0 --- /dev/null +++ b/src/main/java/com/muyu/common/redis/service/RedisService.java @@ -0,0 +1,386 @@ +package com.muyu.common.redis.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.*; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * spring redis 工具类 + * + * @author muyu + **/ +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +@Component +public class RedisService { + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject (final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + public SetOperations opsForSet() { + return redisTemplate.opsForSet(); + } + public HashOperations opsForHash() { + return (HashOperations) redisTemplate.opsForHash(); + } + + // 获取缓存集合的方法,返回 List + public List getCacheSets(String key) { + // 获取绑定的 Set 操作对象 + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + + // 获取 Redis 集合中的所有元素,并转换为 List + Set members = setOperation.members(); + return members.stream() + .map(Object::toString) + .collect(Collectors.toList()); + } + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * + * @return 缓存键值对应的数据 + */ + public List getCacheList (final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + public T getCacheListValue (final String key, long index) { + return (T) redisTemplate.opsForList().index(key, index); + } + + /** + * 减少序列值 + * @param key + * @param number + * @return + */ + public Long increment(final String key , Long number){ + return redisTemplate.opsForValue().increment(key,number); + } + /** + * 向 Redis 集合中添加元素 + * @param key Redis 集合的 key + * @param value 要添加的元素 + * @return 如果元素不存在并成功添加返回 true,如果元素已经存在则返回 false + */ + public boolean addToSet(String key, String value) { + Long added = redisTemplate.opsForSet().add(key, value); + return added == 1?true:false; + } + public ZSetOperations opsForZSet() { + return redisTemplate.opsForZSet(); + } + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject (final String key, final T value, final Long timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * + * @return true=设置成功;false=设置失败 + */ + public boolean expire (final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + public ValueOperations opsForValue() { + return redisTemplate.opsForValue(); + } + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * + * @return true=设置成功;false=设置失败 + */ + public boolean expire (final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * + * @return 有效时间 + */ + public long getExpire (final String key) { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * + * @return true 存在 false不存在 + */ + public Boolean hasKey (String key) { + return redisTemplate.hasKey(key); + } + + // 自定义的 expire 方法,支持以小时为单位设置过期时间 + public boolean expire(String key, Object value, TimeUnit timeUnit, long duration) { + // 将超时时间转换为小时 + long timeoutHours = timeUnit.toHours(duration); + + // 检查转换后的小时超时时间是否为正数 + if (timeoutHours > 0) { + // 使用 RedisTemplate 设置键的过期时间(单位为秒,需要将小时转换为秒) + return redisTemplate.expire(key, timeoutHours, TimeUnit.HOURS); + } else { + // 如果转换后的超时时间小于等于 0,则不设置过期时间,返回 false + return false; + } + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * + * @return 缓存键值对应的数据 + */ + public T getCacheObject (final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject (final String key) { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * + * @return + */ + public boolean deleteObject (final Collection collection) { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * + * @return 缓存的对象 + */ + public long setCacheList (final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * + * @return 缓存键值对应的数据 + */ + public T getCacheList (final String key,Long index) { + return (T) redisTemplate.opsForList().index(key, index); + } + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * + * @return 缓存键值对应的数据 + */ + public List getCacheLists(final String key) { + return (List) redisTemplate.opsForList().range(key, 0, -1); + } + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet (final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + return setOperation; + } + + + // 修改后的 setCacheSet 方法,支持设置集合数据和过期时间 + public BoundSetOperations setCacheSetEndTime(final String key, final Set dataSet, TimeUnit timeUnit, long duration) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + + // 设置键的过期时间 + setOperation.expire(duration, timeUnit); + + return setOperation; + } + /** + * 缓存Set + * + * @param key 缓存键值 + * @param setValue 缓存的数据 + * + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSets (final String key, final T setValue) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + setOperation.add(setValue); + return setOperation; + } + /** + * 删除set值 + * + * @param key 缓存键值 + * @param setValue 缓存的数据 + * + * @return 缓存数据的对象 + */ + public void deleteCachSet(String key, String setValue) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + setOperation.remove(setValue); + } + /** + * 获得缓存的set + * + * @param key + * + * @return + */ + public Set getCacheSet (final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap (final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * + * @return + */ + public Map getCacheMap (final String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue (final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * + * @return Hash中的对象 + */ + public T getCacheMapValue (final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * + * @return Hash对象集合 + */ + public List getMultiCacheMapValue (final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * + * @return 是否成功 + */ + public boolean deleteCacheMapValue (final String key, final String hKey) { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * + * @return 对象列表 + */ + public Collection keys (final String pattern) { + return redisTemplate.keys(pattern); + } + + + public void rightPush(String key, String value) { + redisTemplate.opsForList().rightPush(key, value); + } + + + public Object leftPopAndRemove(String key) { + return redisTemplate.opsForList().leftPop(key); + } +} diff --git a/src/main/java/com/muyu/controller/GatewayController.java b/src/main/java/com/muyu/controller/GatewayController.java new file mode 100644 index 0000000..3992674 --- /dev/null +++ b/src/main/java/com/muyu/controller/GatewayController.java @@ -0,0 +1,35 @@ +package com.muyu.controller; + +import com.muyu.common.domain.Result; +import com.muyu.service.GatewayLoadService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/18 18:35 //创建时间 + */ + + +@RequestMapping("/gateway") +@RestController +public class GatewayController { + + @Autowired + private GatewayLoadService gatewayLoadService; + + + @GetMapping("/load/node") + public Result loadNode(){ + return Result.success(gatewayLoadService.loadNode()); + } + + + + +} + + diff --git a/src/main/java/com/muyu/gateway/cache/LoadNodeCache.java b/src/main/java/com/muyu/gateway/cache/LoadNodeCache.java new file mode 100644 index 0000000..0d478fb --- /dev/null +++ b/src/main/java/com/muyu/gateway/cache/LoadNodeCache.java @@ -0,0 +1,60 @@ +package com.muyu.gateway.cache; + +import com.muyu.common.constant.CacheConstants; +import com.muyu.gateway.cache.abs.GatewayCacheAbs; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/18 16:08 //创建时间 + */ + +@Component +public class LoadNodeCache extends GatewayCacheAbs { + + + @Override + public String getPre() { + return CacheConstants.GATEWAY_COMMON; + } + + /** + * 存负载集合 + * @param nodeList 节点权重集合 + */ + public void put(List nodeList){ + //删除key + redisService.deleteObject(encode(CacheConstants.GATEWAY_LOAD_NODE_KEY)); + //存入节点权重集合 + redisService.setCacheList(encode(CacheConstants.GATEWAY_LOAD_NODE_KEY),nodeList); + } + + /** + * 获取所有负载节点 + * @return 负载节点集合 + */ + public List get(){ + return redisService.getCacheList(encode(CacheConstants.GATEWAY_LOAD_NODE_KEY)); + } + + /** + * 通过下标获取节点 + * @param index 下标 + * @return 指定节点 + */ + public String getFindByIndex(Long index){ + if (index == null || index > 100){ + throw new RuntimeException("下标违法:【0 - 100】"); + } + return redisService.getCacheListValue(encode(CacheConstants.GATEWAY_LOAD_NODE_KEY),index); + } + + + + + +} + + diff --git a/src/main/java/com/muyu/gateway/cache/LoadSeriesCache.java b/src/main/java/com/muyu/gateway/cache/LoadSeriesCache.java new file mode 100644 index 0000000..e2fba08 --- /dev/null +++ b/src/main/java/com/muyu/gateway/cache/LoadSeriesCache.java @@ -0,0 +1,59 @@ +package com.muyu.gateway.cache; + +import com.muyu.common.constant.CacheConstants; +import com.muyu.gateway.cache.abs.GatewayCacheAbs; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/18 15:23 //创建时间 + */ + +@Component +public class LoadSeriesCache extends GatewayCacheAbs { + + + @Override + public String getPre() { + return CacheConstants.GATEWAY_COMMON; + } + + /** + * bean创建完成之后执行方法 + */ + @PostConstruct + public void init(){ + redisService.setCacheObject(encode(CacheConstants.GATEWAY_LOAD_SERIES_KEY),0); + } + + /** + * 获取当前序列值 + * @return 序列值 + */ + public Long get(){ + return redisService.getCacheObject(encode(CacheConstants.GATEWAY_LOAD_SERIES_KEY)); + } + + /** + * 获取自增序列值 + * @return 自增后的值 + */ + public Long incrementAndGet(){ + return redisService.increment(encode(CacheConstants.GATEWAY_LOAD_SERIES_KEY),1L); + } + + /** + * 重置 + */ + public void reset(){ this.init(); } + + + +} + + + + + diff --git a/src/main/java/com/muyu/gateway/cache/NodeCache.java b/src/main/java/com/muyu/gateway/cache/NodeCache.java new file mode 100644 index 0000000..c47fa80 --- /dev/null +++ b/src/main/java/com/muyu/gateway/cache/NodeCache.java @@ -0,0 +1,52 @@ +package com.muyu.gateway.cache; + +import com.muyu.common.constant.CacheConstants; +import com.muyu.gateway.cache.abs.GatewayCacheAbs; +import com.muyu.gateway.model.NodeInfo; +import org.springframework.stereotype.Component; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/18 15:01 //创建时间 + */ + + +@Component +public class NodeCache extends GatewayCacheAbs { + + + + @Override + public String getPre() { + return CacheConstants.GATE_WAY_NODE_INFO; + } + + /** + * 添加缓存数据 + * @param nodeInfo 节点信息 + */ + public void put(NodeInfo nodeInfo){ + redisService.setCacheObject(encode(nodeInfo.getNodeId()),nodeInfo); + } + + /** + * 获取缓存数据 + * @param nodeId 节点ID + * @return 节点信息 + */ + public NodeInfo get(String nodeId){ + return redisService.getCacheObject(encode(nodeId)); + } + + /** + * 删除网关节点 + * @param nodeId + */ + public void remove(String nodeId){ + redisService.deleteObject(encode(nodeId)); + } + + +} + + diff --git a/src/main/java/com/muyu/gateway/cache/NodeReduced.java b/src/main/java/com/muyu/gateway/cache/NodeReduced.java new file mode 100644 index 0000000..e1fa850 --- /dev/null +++ b/src/main/java/com/muyu/gateway/cache/NodeReduced.java @@ -0,0 +1,68 @@ +package com.muyu.gateway.cache; + +import com.muyu.common.constant.CacheConstants; +import com.muyu.gateway.cache.abs.GatewayCacheAbs; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:55 //创建时间 + */ + +@Component +public class NodeReduced extends GatewayCacheAbs { + @Override + public String getPre() { + return "reduced:cache:"; + } + + /** + * 存达到缩容条件的节点ID + * @param nodeId 节点ID + */ + public void put(String nodeId){ + Set thresholeSet = new HashSet<>(); + thresholeSet.add(nodeId); + redisService.setCacheSetEndTime(encode(nodeId),thresholeSet, TimeUnit.MINUTES, CacheConstants.EXPIRATIOTIME); + } + + /** + * 判断是否被记录 + * @param nodeId 节点ID + * @return 返回true/false + */ + public boolean isWhether(String nodeId){ + return redisService.hasKey(encode(nodeId)); + } + + /** + * 删除缩容条件节点ID + */ + public void remove(String nodeId){ + redisService.deleteObject(encode(nodeId)); + } + + /** + * 获取缩容缓存内的节点ID + * @return + */ + public List get(String nodeId){ + return redisService.getCacheSets(encode(nodeId)); + } + + /** + * 查询剩余时间 + * @param nodeId 节点ID + * @return 返回剩余时间 秒 + */ + public Long remainingTime(String nodeId){ + return redisService.getExpire(encode(nodeId)); + } +} + + diff --git a/src/main/java/com/muyu/gateway/cache/NodeScoreCache.java b/src/main/java/com/muyu/gateway/cache/NodeScoreCache.java new file mode 100644 index 0000000..5792592 --- /dev/null +++ b/src/main/java/com/muyu/gateway/cache/NodeScoreCache.java @@ -0,0 +1,78 @@ +package com.muyu.gateway.cache; + +import com.muyu.common.constant.CacheConstants; +import com.muyu.gateway.cache.abs.GatewayCacheAbs; +import com.muyu.gateway.model.NodeJoin; +import com.muyu.gateway.model.WorkGatewayNode; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Set; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/18 19:41 //创建时间 + */ + + +@Component +@Service +public class NodeScoreCache extends GatewayCacheAbs { + + + @Override + public String getPre() { + return CacheConstants.GATEWAY_COMMON; + } + + public List getNodeScore(){ + Set> range = redisService.redisTemplate.opsForZSet().rangeWithScores(encode(CacheConstants.GATEWAY_NODE_SCORE_CACHE), 0, -1); + return range.stream() + .map(ZSet -> WorkGatewayNode.builder().nodeId(ZSet.getValue()).weight(Integer.valueOf(String.valueOf(ZSet.getScore()))).build()) + .toList(); + + } + + public Long getNodeNowNum(){ + List workGatewayNodes = getNodeScore(); + //目前连接数 + Long vehicleOnlineNowNum= Long.valueOf(String.valueOf( + workGatewayNodes.stream().mapToDouble(WorkGatewayNode::getWeight).sum() + )); + return vehicleOnlineNowNum; + } + + + + /** + * 存入节点ID及连接数 + * @param nodeJoin + */ + public void save(NodeJoin nodeJoin){ + redisService.setCacheSets(encode(CacheConstants.GATEWAY_NODE_SCORE_CACHE),nodeJoin); + } + + /** + * 获取连接数信息 + * @return + */ + public List get(){ + return redisService.getCacheObject(encode(CacheConstants.GATEWAY_NODE_SCORE_CACHE)); + } + + + + + + + + + + + + +} + + diff --git a/src/main/java/com/muyu/gateway/cache/NodeSetVinCache.java b/src/main/java/com/muyu/gateway/cache/NodeSetVinCache.java new file mode 100644 index 0000000..aef75d6 --- /dev/null +++ b/src/main/java/com/muyu/gateway/cache/NodeSetVinCache.java @@ -0,0 +1,55 @@ +package com.muyu.gateway.cache; + +import com.muyu.common.constant.CacheConstants; +import com.muyu.gateway.cache.abs.GatewayCacheAbs; +import com.muyu.gateway.model.NodeVehicle; +import org.springframework.stereotype.Component; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/18 20:23 //创建时间 + */ + + +@Component +public class NodeSetVinCache extends GatewayCacheAbs { + + + @Override + public String getPre() { + return CacheConstants.GATEWAY_VEHICLE; + } + + /** + * 存入车辆信息 + * @param nodeVehicle + */ + public void put(NodeVehicle nodeVehicle){ + redisService.setCacheObject(encode(nodeVehicle.getVehicleVin()),nodeVehicle); + } + + /** + * 获取车辆节点ID + * @param vehicleVin 车辆VIN + * @return 返回节点ID + */ + public String get(String vehicleVin){ + return redisService.getCacheObject(encode(vehicleVin)); + } + + /** + * 删除车辆信息 + * @param vehicleVin 车辆VIN + */ + public void delete(String vehicleVin){ + redisService.deleteObject(encode(vehicleVin)); + } + + + + + + +} + + diff --git a/src/main/java/com/muyu/gateway/cache/VehicleLineNodeCache.java b/src/main/java/com/muyu/gateway/cache/VehicleLineNodeCache.java new file mode 100644 index 0000000..8169748 --- /dev/null +++ b/src/main/java/com/muyu/gateway/cache/VehicleLineNodeCache.java @@ -0,0 +1,63 @@ +package com.muyu.gateway.cache; + +import com.muyu.common.constant.CacheConstants; +import com.muyu.gateway.cache.abs.GatewayCacheAbs; +import com.muyu.gateway.model.NodeVehicle; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Set; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/18 19:46 //创建时间 + */ + + +@Component +public class VehicleLineNodeCache extends GatewayCacheAbs { + + + @Override + public String getPre() { + return CacheConstants.GATEWAY_VEHICLE_LINE; + } + + /** + *存入车辆VIN 和 节点ID + * @param nodeVehicle 车辆VIN 网关节点ID + */ + public void save(NodeVehicle nodeVehicle){ + String key = encode(nodeVehicle.getNodeId()); + String vehicleVin = nodeVehicle.getVehicleVin(); + // Collections.singleton(vehicleVin) 来创建一个只包含一个元素 vehicleVin 的集合 + redisService.setCacheSet(key, Collections.singleton(vehicleVin)); + } + + /** + * 获取车辆VIN信息 + * @param nodeId 网关节点ID + * @return 返回车辆VIN,Set集合 + */ + + public Set get(String nodeId){ + return redisService.getCacheSet(encode(nodeId)); + } + + /** + * 删除车辆网关连接信息 + * @param nodeId 网关节点ID + */ + public void remove(String nodeId){ + redisService.deleteObject(encode(nodeId)); + } + + + + + + + +} + + diff --git a/src/main/java/com/muyu/gateway/cache/abs/GatewayCacheAbs.java b/src/main/java/com/muyu/gateway/cache/abs/GatewayCacheAbs.java new file mode 100644 index 0000000..a7afa16 --- /dev/null +++ b/src/main/java/com/muyu/gateway/cache/abs/GatewayCacheAbs.java @@ -0,0 +1,30 @@ +package com.muyu.gateway.cache.abs; + +import com.muyu.common.redis.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/18 16:09 //创建时间 + */ + + +public abstract class GatewayCacheAbs { + + + @Autowired + public RedisService redisService; + + public abstract String getPre(); + + public String encode(K key){ + return getPre()+key; + } + + + + + +} + + diff --git a/src/main/java/com/muyu/gateway/model/NodeInfo.java b/src/main/java/com/muyu/gateway/model/NodeInfo.java new file mode 100644 index 0000000..74ce40c --- /dev/null +++ b/src/main/java/com/muyu/gateway/model/NodeInfo.java @@ -0,0 +1,39 @@ +package com.muyu.gateway.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:58 //创建时间 + */ + +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class NodeInfo { + /** + * + * 节点ID + */ + private String nodeId; + + /** + * 公网IP + */ + private String publicIdAddress; + + /** + * 内网IP + */ + private String privateIdAddress; + + + + +} + + diff --git a/src/main/java/com/muyu/gateway/model/NodeJoin.java b/src/main/java/com/muyu/gateway/model/NodeJoin.java new file mode 100644 index 0000000..672e89a --- /dev/null +++ b/src/main/java/com/muyu/gateway/model/NodeJoin.java @@ -0,0 +1,30 @@ +package com.muyu.gateway.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:58 //创建时间 + */ + +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class NodeJoin { + + /** + * 节点ID + */ + private String nodeId; + + /** + * 连接数 + */ + private Long linkingNumber; +} + + diff --git a/src/main/java/com/muyu/gateway/model/NodeVehicle.java b/src/main/java/com/muyu/gateway/model/NodeVehicle.java new file mode 100644 index 0000000..577932d --- /dev/null +++ b/src/main/java/com/muyu/gateway/model/NodeVehicle.java @@ -0,0 +1,31 @@ +package com.muyu.gateway.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:58 //创建时间 + */ + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class NodeVehicle { + + /** + * 车辆VIN + */ + private String vehicleVin; + + /** + * 网关节点ID + */ + private String nodeId; + +} + + diff --git a/src/main/java/com/muyu/gateway/model/WorkGatewayNode.java b/src/main/java/com/muyu/gateway/model/WorkGatewayNode.java new file mode 100644 index 0000000..df976db --- /dev/null +++ b/src/main/java/com/muyu/gateway/model/WorkGatewayNode.java @@ -0,0 +1,27 @@ +package com.muyu.gateway.model; + +import lombok.*; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 8:58 //创建时间 + */ + +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString +@Builder +public class WorkGatewayNode { + /** + * 节点ID + */ + private String nodeId; + + /** + * 权重值 + */ + private int weight; +} + + diff --git a/src/main/java/com/muyu/service/GatewayLoadService.java b/src/main/java/com/muyu/service/GatewayLoadService.java new file mode 100644 index 0000000..fdd7e59 --- /dev/null +++ b/src/main/java/com/muyu/service/GatewayLoadService.java @@ -0,0 +1,16 @@ +package com.muyu.service; + +public interface GatewayLoadService { + + /** + * 负载节点 + * @return 返回负载节点 + * + * */ + String loadNode(); + + /** + * 刷新负载规格 + * */ + void refreshLoad(); +} diff --git a/src/main/java/com/muyu/service/impl/GatewayLoadServiceImpl.java b/src/main/java/com/muyu/service/impl/GatewayLoadServiceImpl.java new file mode 100644 index 0000000..501b0b4 --- /dev/null +++ b/src/main/java/com/muyu/service/impl/GatewayLoadServiceImpl.java @@ -0,0 +1,203 @@ +package com.muyu.service.impl; + +import com.muyu.common.constant.LoadConstants; +import com.muyu.gateway.cache.*; +import com.muyu.gateway.model.NodeInfo; +import com.muyu.gateway.model.WorkGatewayNode; +import com.muyu.service.GatewayLoadService; +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/18 18:38 //创建时间 + */ + + +@Service +@AllArgsConstructor +@Log4j2 +public class GatewayLoadServiceImpl implements GatewayLoadService { + + + + /** + * 网关负载节点缓存 + */ + private final LoadNodeCache loadNodeCache; + + /** + * 网关负载序列 + */ + private final LoadSeriesCache loadSeriesCache; + + /** + * 网关节点缓存 + */ + private final NodeCache nodeCache; + + /** + * 网关节点连接数 + */ + private final NodeScoreCache nodeScoreCache; + + /** + * 网关节点存储VIN信息 + */ + private final NodeSetVinCache nodeSetVinCache; + + /** + * 网关车辆对应网关节点 + */ + private final VehicleLineNodeCache vehicleLineNodeCache; + + /** + * 分布式锁 + */ + //private final RedissonClient redissonClient; + + + /** + * 获取负载节点 + * + * @return 返回公网IP + */ + @Override + public String loadNode() { + //去刷新负载 + refreshLoad(); + //获取自增序列值 + Long seriesLoad = loadSeriesCache.incrementAndGet(); //获取自增序列值 + Long seriesLoadIndex = seriesLoad % LoadConstants.NODE_LENGTH; + //获取负载下标 + String loadNodeId = loadNodeCache.getFindByIndex(seriesLoadIndex); + //通过获取节点ID + NodeInfo nodeInfo = nodeCache.get(loadNodeId); + //获取缓存内节点的公网/内网信息 返回公网IP + return nodeInfo.getPublicIdAddress(); + } + + /** + * 刷新负载 实现动态负载 + */ + @Override + public void refreshLoad() { + + //分布式锁 + // RLock refreshLoadLock = redissonClient.getLock("refreshLoadLock"); + // try { + // 尝试获取锁,最多等待10秒,持有锁60秒后自动释放 + // if (refreshLoadLock.tryLock(10, 60, TimeUnit.SECONDS)) { + // 在锁内执行刷新负载的逻辑 + List workGatewayNodes = nodeScoreCache.getNodeScore(); + + //车辆上线总数量 + long vehicleMaxOnlineNUm = getNodeMaxOnlineNum(); + + //目前连接数 + Long veicleOnlineNowNum = nodeScoreCache.getNodeNowNum(); + + //空余连接数 + long vehicleOnlineNum = vehicleMaxOnlineNUm - veicleOnlineNowNum; + + //转换 + List workGatewayNodeWeight = workGatewayNodes.stream() + .map(workGatewayNode -> WorkGatewayNode.builder() + .nodeId(workGatewayNode.getNodeId()) + .weight(Integer.parseInt(String.valueOf(vehicleOnlineNum / (80L - workGatewayNode.getWeight())))) + .build()) + .toList(); + + List loadNodeList = new ArrayList<>(); + + int count = workGatewayNodeWeight.stream().mapToInt(WorkGatewayNode::getWeight).sum(); + + if (count < 100) { + List list = workGatewayNodeWeight.stream().sorted((o1, o2) -> o2.getWeight() - o1.getWeight()).toList(); + + int countWeight = 0; + for (long i = count; i < 100; i++) { + WorkGatewayNode workGatewayNode = list.get(countWeight++ % list.size()); + workGatewayNode.setWeight(workGatewayNode.getWeight() + 1); + } + } + + whFor: + while (true) { + for (WorkGatewayNode workGatewayNode : workGatewayNodeWeight) { + int weight = workGatewayNode.getWeight(); + if (weight > 0) { + loadNodeList.add( + workGatewayNode.getNodeId() + ); + workGatewayNode.setWeight(weight - 1); + + } + + } + int sum = workGatewayNodeWeight.stream(). + mapToInt(WorkGatewayNode::getWeight).sum(); + if (sum <= 0) { + break whFor; + } + } + //重置 + loadSeriesCache.reset(); + //存入负载集合 + loadNodeCache.put(loadNodeList); + } + //} catch (InterruptedException e) { + // Thread.currentThread().interrupt(); + // 处理中断异常 + //} finally { + // 释放锁 + // if (refreshLoadLock.isHeldByCurrentThread()) { + // refreshLoadLock.unlock(); +//} + + /** + * 动态ECS + */ + public void dynamicECS(){ + + //车辆上线总数量 + long vehicleMaxOnlineNUm = getNodeMaxOnlineNum(); + + //目前连接数 + Long nodeNowNum = nodeScoreCache.getNodeNowNum(); + + //负载率 + BigDecimal loadRate = new BigDecimal(vehicleMaxOnlineNUm).divide(new BigDecimal(nodeNowNum), 0, BigDecimal.ROUND_HALF_UP); + + if (loadRate.longValue() > 80){ + //扩容 + }else if (loadRate.longValue() <20 ){ + //缩容 + } + } + + + /** + * 获取最大连接数 + * @return + */ + public Long getNodeMaxOnlineNum(){ + List workGatewayNodes = nodeScoreCache.getNodeScore(); + return workGatewayNodes.size() * 80L; + } + + + + + + + + +} + + diff --git a/src/main/java/com/muyu/task/Collection.java b/src/main/java/com/muyu/task/Collection.java new file mode 100644 index 0000000..974f0cf --- /dev/null +++ b/src/main/java/com/muyu/task/Collection.java @@ -0,0 +1,182 @@ +package com.muyu.task; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.muyu.aly.ALYunEcsService; +import com.muyu.aly.model.InstanceInfo; +import com.muyu.common.model.TotalNumber; +import com.muyu.gateway.cache.*; +import com.muyu.gateway.model.NodeInfo; +import com.muyu.gateway.model.NodeJoin; +import com.muyu.task.contraction.ContractionVolume; +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.List; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 9:03 //创建时间 + */ + +@Component +@Log4j2 +@AllArgsConstructor +public class Collection { + + /** + * 网关负载节点缓存 + */ + private final LoadNodeCache loadNodeCache; + + /** + * 网关负载序列 + */ + private final LoadSeriesCache loadSeriesCache; + + /** + * 网关节点缓存 存ip地址 + */ + private final NodeCache nodeCache; + + /** + * 网关节点连接数 + */ + private final NodeScoreCache nodeScoreCache; + + /** + * 网关节点存储VIN信息 + */ + private final NodeSetVinCache nodeSetVinCache; + + /** + * 网关车辆对应网关节点 + */ + private final VehicleLineNodeCache vehicleLineNodeCache; + + /** + * 缩容缓存 + */ + private final NodeReduced nodeReduced; + + /** + * 扩缩容 + */ + @Autowired + private ContractionVolume contractionVolume; + + @Autowired + private ALYunEcsService alYunEcsService; + + /** + * 定时扫描节点信息 + */ + @Scheduled(cron = "0/10 * * * * ?") + public void scheduledEcsCompanding() throws Exception { + + //查询阿里云是否存在实例 +// EcsSelectModel ecsSelectModel = new EcsSelectModel(); +// List addArryList = new ArrayList<>(); +// addArryList.add("Myname"); +// ecsSelectModel.setInstanceNameList(addArryList); + //实例集合 + List instanceLists = alYunEcsService.selectECS("Myname"); + + //节点计数 + Long nodeNumber = 0L; + + //所有节点连接数总数 + long connectionTotal = 0L; + + //判断实例集合是否为空 + if (!instanceLists.isEmpty()){ + + //将实例存入缓存 + for (InstanceInfo instance : instanceLists) { + + //清空 缓存中节点信息 + nodeCache.remove(instance.getInstanceId()); + + // 创建一个新的 NodeInfo 对象 + NodeInfo nodeInfo = new NodeInfo(); + // 设置实例的信息 + nodeInfo.setNodeId(instance.getInstanceId()); + nodeInfo.setPublicIdAddress(instance.getPublicIpAddress()); + nodeInfo.setPrivateIdAddress(instance.getPrivateIpAddress()); + // 将新的 NodeInfo 对象放入缓存 + nodeCache.put(nodeInfo); + + //获取每个FluxMQ运行信息 + String URL = "http://" + instance.getPublicIpAddress()+":8080/public/cluster"; + OkHttpClient client = new OkHttpClient(); + + Request request = new Request.Builder() + .url(URL) + .get() + .addHeader("User-Agent", "Apifox/1.0.0 (https://apifox.com)") + .addHeader("Accesstoken", "") + .build(); + + try { + Response response = client.newCall(request).execute(); + + JSONArray jsonArray = JSONArray.parseArray(response.body().string()); + JSONObject jsonObject = jsonArray.getJSONObject(0); + JSONObject mqttInfo = jsonObject.getJSONObject("mqttInfo"); + int connectSize = mqttInfo.getIntValue("connectSize"); + + log.info("当前:" + instance.getInstanceId() + ",的连接数:" + connectSize); + + //计数 + nodeNumber++; + connectionTotal+=connectSize; + + //将连接数存入缓存 + // key:网关负载业务 value:网关节点ID + 连接数 + NodeJoin nodeJoin = new NodeJoin(); + nodeJoin.setNodeId(instance.getInstanceId()); + nodeJoin.setLinkingNumber(Long.valueOf(connectSize)); + nodeScoreCache.save(nodeJoin); + + //加层判断,把不满足缩容条件的缓存删除 连接数 > 最低阈值 + if (connectSize > 20 && nodeReduced.isWhether(instance.getInstanceId())){ + nodeReduced.remove(instance.getInstanceId()); + } + + //判断是否达到缩容条件 节点数量 > 1 或 连接数 < 21 必须满足节点数量在两个及以上,且连接数低于21 + if (instanceLists.size() > 1 && connectSize < 21){ + //调用缩容方法 记录 + contractionVolume.reduction(instance.getInstanceId()); + } + + + + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + } + if (connectionTotal > 0){ + //封装节点数量和节点连接总数 + TotalNumber totalNumber = new TotalNumber(); + totalNumber.setConnectionTotal(connectionTotal); + totalNumber.setNodeNumber(nodeNumber); + //调用扩容方法去判断是否需要扩容 + contractionVolume.contractionVolume(totalNumber); + } + + } + + +} + + diff --git a/src/main/java/com/muyu/task/contraction/ContractionVolume.java b/src/main/java/com/muyu/task/contraction/ContractionVolume.java new file mode 100644 index 0000000..2a61824 --- /dev/null +++ b/src/main/java/com/muyu/task/contraction/ContractionVolume.java @@ -0,0 +1,129 @@ +package com.muyu.task.contraction; + +import com.muyu.aly.ALYunEcsService; +import com.muyu.common.constant.LoadConstants; +import com.muyu.common.model.TotalNumber; +import com.muyu.gateway.cache.NodeReduced; +import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/20 9:04 //创建时间 + */ + +@AllArgsConstructor +@Component +@Log4j2 +public class ContractionVolume { + @Autowired + private ALYunEcsService alYunEcsService; + + /** + * 缩容缓存 + */ + private final NodeReduced nodeReduced; + + /** + * 缩容 + * @param nodeId + */ + public void reduction(String nodeId){ + //非空判断 + if (null != nodeId){ + //判断是否被标记过 + if (nodeReduced.isWhether(nodeId)){ + //查询剩余过期时间 秒 + Long expire = nodeReduced.remainingTime(nodeId); + //获取 5分钟的秒值 + long fiveMinutesSeconds = TimeUnit.MINUTES.toSeconds(5); + //打印 + log.info("空闲节点 :" + nodeId + "的剩余时间:" + expire + "/秒"); + + //比较 剩余时间 小于5分钟 + if (expire < fiveMinutesSeconds){ + //数据迁移 释放节点 + + } + } + //记录 + nodeReduced.put(nodeId); + } + } + + + /** + * 扩容 + * @param totalNumber + */ + public void contractionVolume(TotalNumber totalNumber){ + //特殊情况 无节点 + if (totalNumber.getNodeNumber() == 0L){ + // 无 则创建两台实力 + log.error("当前未存在节点信息"); + try { + //创建实例方法 【2台】 + // alYunEcsService.createAnServer(LoadConstants.IS_NULL); + } catch (Exception e) { + log.error("扩容失败!!!!!"); + e.printStackTrace(); + } + //返回 + return; + } + //调用计算 + Long value = this.percentage(totalNumber); + //判断达到60% + if (value >= LoadConstants.INTERMEDIATE && value < LoadConstants.MAXIMUM){ + //当节点负载达到 60%时,调用扩容一台方法 + log.info("Node 节点负载达到 :" + value + "%,达到扩容一台的条件☑"); + try { + //创建实例方法 【1台】 + //alYunEcsService.createAnServer(null); + } catch (Exception e) { + log.error("扩容失败!!!!!"); + e.printStackTrace(); + } + } + //判断达到80% + if (value >= LoadConstants.MAXIMUM){ + //当节点负载达到 80%时,调用扩容一台方法 + log.info("Node 节点负载达到 :" + value + "%,达到扩容两台的条件☑"); + try { + //创建实例方法 【2台】 + // alYunEcsService.createAnServer(LoadConstants.IS_NULL); + } catch (Exception e) { + log.error("扩容失败!!!!!"); + e.printStackTrace(); + } + } + + } + + /** + * 计算百分比方法 + */ + private Long percentage(TotalNumber totalNumber){ + //获取节点数量 + Long nodeNumber = totalNumber.getNodeNumber(); + //根据nodeNumber去获取最大节点数 默认 100 + Long sumNodeNumber = nodeNumber * LoadConstants.MAX_NUMBER; + //获取节点连接总数 + Long connectionTotal = totalNumber.getConnectionTotal(); + //计算空余连接数 +// Long vacantNumber = sumNodeNumber - connectionTotal; + //计算当前负载情况 + double loadPercentage = (double)connectionTotal / sumNodeNumber; + //进行四舍五入取整 + long roundLoadPercentage = Math.round(loadPercentage) * LoadConstants.BE_COMMON; + //返回百分比 + return roundLoadPercentage; + } + +} + + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..e59f53e --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,17 @@ +spring: + redis: + host: 127.0.0.1 + +aliyun: + accessKeyId: LTAI5tPDLpTbAX9bUSrTSrPH + accessKeySecret: rbLG6bh8ZSttUPMxUspk9j8XLzvLU0 + regionId: cn-shanghai + image-id: m-uf6hu0jwys1efmiemfxl + instance-type: ecs.e-c1m1.large + security-group-id: sg-uf6bj6vxp8ruhvffdsau + v-switch-id: vsw-uf6sfq669js64lwke0isv + internet-max-bandwidth-out: 2 + internet-charge-type: PayByTraffic + size: 20 + category: cloud_essd + instance-charge-type: PostPaid diff --git a/src/test/java/com/muyu/LoadTest.java b/src/test/java/com/muyu/LoadTest.java new file mode 100644 index 0000000..155bf5e --- /dev/null +++ b/src/test/java/com/muyu/LoadTest.java @@ -0,0 +1,153 @@ +package com.muyu; + +import com.muyu.common.redis.service.RedisService; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.*; +import java.util.concurrent.CountDownLatch; + +/** + * @Author: wanghao //作者 + * @CreateDate: 2024/4/12 22:23 //创建时间 + */ + + +@SpringBootTest(classes = LoadCenterApplication.class) +public class LoadTest { + + @Autowired + private RedisService redisService; + + + @Test + public void load(){ + //初始化序列 + redisService.setCacheObject("cursor",0); + + List nodeIdList = new ArrayList<>(); + nodeIdList.add(new WorkGatewayNode("work-gateway-node-A",8)); + nodeIdList.add(new WorkGatewayNode("work-gateway-node-B",12)); + nodeIdList.add(new WorkGatewayNode("work-gateway-node-C",2)); + nodeIdList.add(new WorkGatewayNode("work-gateway-node-D",39)); + nodeIdList.add(new WorkGatewayNode("work-gateway-node-E",39)); + + //100 + List loadNodeList = new ArrayList<>(); + + long count = nodeIdList.stream().mapToInt(WorkGatewayNode::getWeight).sum(); + if(count<100){ + List list = nodeIdList.stream().sorted((o1, o2) -> o2.getWeight()-o1.getWeight()).toList(); + + int countWeight=0; + for (long i = count; i < 100; i++) { + WorkGatewayNode workGatewayNode=list.get(countWeight++ % list.size()); + workGatewayNode.setWeight(workGatewayNode.getWeight()+1); + } + + } + + + whFor:while (true){ + for (WorkGatewayNode workGatewayNode : nodeIdList) { + int weight = workGatewayNode.getWeight(); + if(weight>0){ + loadNodeList.add( + workGatewayNode.getNodeId() + ); + workGatewayNode.setWeight(weight-1); + + } + + } + int sum = nodeIdList.stream(). + mapToInt(WorkGatewayNode::getWeight).sum(); + if(sum<=0){ + break whFor; + } + + + } + System.out.println(loadNodeList); + redisService.deleteObject("work:node:gateway"); + redisService.setCacheList("work:node:gateway",loadNodeList); + CountDownLatch latch = new CountDownLatch(3000); + + new Thread(() ->{ + for (int i = 0; i < 1000; i++) { + Long cursor = redisService.increment("cursor", 1L); + String nodeId = redisService.getCacheListValue("work:node:gateway", cursor % 100); + System.out.println(Thread.currentThread().getName()+":"+cursor+"----"+nodeId); + stiNode.sti(nodeId); + latch.countDown(); + } + }).start(); + + + new Thread(() ->{ + for (int i = 0; i < 1000; i++) { + Long cursor = redisService.increment("cursor", 1L); + String nodeId = redisService.getCacheListValue("work:node:gateway", cursor % 100); + System.out.println(Thread.currentThread().getName()+":"+cursor+"----"+nodeId); + stiNode.sti(nodeId); + latch.countDown(); + } + }).start(); + + new Thread(() ->{ + for (int i = 0; i < 1000; i++) { + Long cursor = redisService.increment("cursor", 1L); + String nodeId = redisService.getCacheListValue("work:node:gateway", cursor % 100); + System.out.println(Thread.currentThread().getName()+":"+cursor+"----"+nodeId); + stiNode.sti(nodeId); + latch.countDown(); + } + }).start(); + try { + latch.await(); + stiNode.show(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + + } + + + + + +} + +class stiNode{ + private static Map stiNodeMap=new HashMap<>(); + + public synchronized static void sti(String nodeId){ + Integer stiCount = stiNodeMap.getOrDefault(nodeId, 0); + stiNodeMap.put(nodeId, stiCount + 1); + } + + public static void show(){ + stiNodeMap.forEach((key,val)->{ + System.out.println(key+"------"+val); + }); + } + +} + + +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString +class WorkGatewayNode{ + + private String nodeId; + private int weight; + +}