commit de22a25f440cd640b8b1ddfff2d2f3eda710acf5 Author: DongZeLiang <2746733890@qq.com> Date: Thu Apr 18 10:58:11 2024 +0800 初始化 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e403e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### 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..05e7f2f --- /dev/null +++ b/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + com.muyu + load-center + 1.0.0 + + + 车辆网关负载中心 + + + + + 17 + 17 + UTF-8 + 2.7.13 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.projectlombok + lombok + + + com.alibaba.fastjson2 + fastjson2 + 2.0.47 + + + + 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..ba8760e --- /dev/null +++ b/src/main/java/com/muyu/common/redis/configure/RedisConfig.java @@ -0,0 +1,41 @@ +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..d43409b --- /dev/null +++ b/src/main/java/com/muyu/common/redis/service/RedisService.java @@ -0,0 +1,289 @@ +package com.muyu.common.redis.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * 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); + } + + /** + * 缓存基本的对象,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); + } + + /** + * 设置有效时间 + * + * @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); + } + + /** + * 获得缓存的基本对象。 + * + * @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 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); + } + + /** + * 缓存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; + } + + /** + * 获得缓存的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); + } + + /** + * 判断redis中hashKey是否存在 + * @param key redis键 + * @param hashKey hash键 + */ + public boolean hashKey(final String key, final String hashKey){ + return this.redisTemplate.opsForHash().hasKey(key, hashKey); + } + + /** + * 删除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); + } + + /** + * 减少序列值 + * @param key key + * @param number 值 + * @return 操作后的值 + */ + public Long decrement (final String key, Long number) { + return redisTemplate.opsForValue().decrement(key,number); + } + /** + * 增加序列值 + * @param key key + * @param number 值 + * @return 操作后的值 + */ + public Long increment (final String key, Long number) { + return redisTemplate.opsForValue().increment(key,number); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..a580c43 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + redis: + host: 127.0.0.1 diff --git a/src/test/java/com/muyu/LoadTest.java b/src/test/java/com/muyu/LoadTest.java new file mode 100644 index 0000000..1151202 --- /dev/null +++ b/src/test/java/com/muyu/LoadTest.java @@ -0,0 +1,131 @@ +package com.muyu; + +import com.muyu.common.redis.service.RedisService; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +/** + * @author DongZl + * @description: 负载测试 + * @Date 2024/4/12 下午5:13 + */ +@SpringBootTest(classes = LoadCenterApplication.class) +public class LoadTest { + + @Autowired + private RedisService redisService; + + @Test + public void load(){ + // 初始化序列 + redisService.setCacheObject("cursor", 0); + List nodeIdList = new ArrayList<>(){{ + add(new WorkGatewayNode("work-gateway-node-A", 8)); + add(new WorkGatewayNode("work-gateway-node-B", 12)); + add(new WorkGatewayNode("work-gateway-node-C", 2)); + add(new WorkGatewayNode("work-gateway-node-D", 39)); + 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; + } + } + redisService.deleteObject("work:node:gateway"); + redisService.setCacheList("work:node:gateway", loadNodeList); + CountDownLatch countDownLatch = 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); + countDownLatch.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); + countDownLatch.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); + countDownLatch.countDown(); + } + }).start(); + try { + countDownLatch.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 +class WorkGatewayNode { + + private String nodeId; + + private int weight; +}