diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/NodeDTO.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/NodeDTO.java new file mode 100644 index 0000000000..808f48d1a9 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/NodeDTO.java @@ -0,0 +1,31 @@ +package io.metersphere.api.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@AllArgsConstructor +public class NodeDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 接口测试 性能测试 node节点ip + */ + private String ip; + + /** + * 接口测试 性能测试 node节点端口 + */ + private String port; + + /** + * 资源池最大并发数 + */ + private int podThreads; + +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/RoundRobinService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/RoundRobinService.java new file mode 100644 index 0000000000..b4d412af47 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/RoundRobinService.java @@ -0,0 +1,79 @@ +package io.metersphere.api.service; + +import io.metersphere.api.dto.NodeDTO; +import io.metersphere.sdk.util.JSON; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class RoundRobinService { + @Resource + private RedisTemplate redisTemplate; + + /** + * 获取下一个节点 + */ + public String getNextNode(String poolId) throws Exception { + // 从列表头部获取下一个节点 + String node = redisTemplate.opsForList().leftPop(poolId); + + if (StringUtils.isBlank(node)) { + // 重试3次获取 + for (int i = 0; i < 3; i++) { + node = redisTemplate.opsForList().leftPop(poolId); + if (StringUtils.isNotBlank(node)) { + break; + } + Thread.sleep(1000); + } + } + + if (StringUtils.isNotBlank(node)) { + // 将节点重新放回列表尾部,实现轮询 + redisTemplate.opsForList().rightPush(poolId, node); + } + + return node; + } + + /** + * 初始化节点列表 + */ + public void initializeNodes(String poolId, List nodes) { + // 检查节点是否有变更 + Long poolSize = redisTemplate.opsForList().size(poolId); + int size = poolSize != null ? poolSize.intValue() : 0; + if (size == nodes.size()) { + // 对比redis中的节点列表和传入的节点列表是否一致 + boolean isSame = true; + for (NodeDTO node : nodes) { + boolean isExist = false; + for (int i = 0; i < size; i++) { + if (JSON.toJSONString(node).equals(redisTemplate.opsForList().index(poolId, i))) { + isExist = true; + break; + } + } + if (!isExist) { + isSame = false; + break; + } + } + if (!isSame) { + // 清空旧的节点列表 + redisTemplate.delete(poolId); + // 添加节点到列表 + nodes.forEach(n -> redisTemplate.opsForList().rightPush(poolId, JSON.toJSONString(n))); + } + } else { + // 清空旧的节点列表 + redisTemplate.delete(poolId); + // 添加节点到列表 + nodes.forEach(n -> redisTemplate.opsForList().rightPush(poolId, JSON.toJSONString(n))); + } + } +} diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/config/RoundRobinServiceTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/config/RoundRobinServiceTests.java new file mode 100644 index 0000000000..8a95330ede --- /dev/null +++ b/backend/services/api-test/src/test/java/io/metersphere/api/config/RoundRobinServiceTests.java @@ -0,0 +1,76 @@ +package io.metersphere.api.config; + +import io.metersphere.api.dto.NodeDTO; +import io.metersphere.api.service.RoundRobinService; +import io.metersphere.sdk.util.LogUtils; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.LinkedList; +import java.util.List; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@AutoConfigureMockMvc +public class RoundRobinServiceTests { + + @Resource + private RoundRobinService roundRobinService; + + @Test + @Order(1) + public void testInit() throws Exception { + List nodes = new LinkedList<>(); + nodes.add(new NodeDTO("172.0.0.1", "8080", 10)); + nodes.add(new NodeDTO("172.0.0.2", "8080", 10)); + nodes.add(new NodeDTO("172.0.0.3", "8080", 10)); + nodes.add(new NodeDTO("172.0.0.4", "8080", 10)); + + roundRobinService.initializeNodes("test", nodes); + } + + @Test + @Order(2) + public void testGetNode() throws Exception { + LogUtils.info(roundRobinService.getNextNode("test")); + LogUtils.info(roundRobinService.getNextNode("test")); + LogUtils.info(roundRobinService.getNextNode("test")); + LogUtils.info(roundRobinService.getNextNode("test")); + LogUtils.info(roundRobinService.getNextNode("test")); + LogUtils.info(roundRobinService.getNextNode("test")); + LogUtils.info(roundRobinService.getNextNode("test")); + } + + @Test + @Order(3) + public void testInitAfter() throws Exception { + List nodes = new LinkedList<>(); + nodes.add(new NodeDTO("172.0.0.1", "8080", 10)); + nodes.add(new NodeDTO("172.0.0.2", "8080", 10)); + nodes.add(new NodeDTO("172.0.0.3", "8080", 10)); + roundRobinService.initializeNodes("test", nodes); + + nodes.add(new NodeDTO("172.0.0.3", "8080", 10)); + nodes.add(new NodeDTO("172.0.0.7", "8080", 10)); + nodes.add(new NodeDTO("172.0.0.6", "8080", 10)); + roundRobinService.initializeNodes("test", nodes); + + } + + @Test + @Order(4) + public void testGetNodeAfter() throws Exception { + LogUtils.info(roundRobinService.getNextNode("test1")); + LogUtils.info(roundRobinService.getNextNode("test")); + LogUtils.info(roundRobinService.getNextNode("test2")); + LogUtils.info(roundRobinService.getNextNode("test")); + LogUtils.info(roundRobinService.getNextNode("test3")); + LogUtils.info(roundRobinService.getNextNode("test")); + LogUtils.info(roundRobinService.getNextNode("test1")); + } +}