1. Redis存储设计
我们需要在 Redis 中存储以下信息:
- 配置列表(
List<Config>
):存储所有配置项。 - 总权重:存储所有配置的总权重。
- 当前轮询状态:存储当前的轮询状态(如当前随机值或索引)。
2. 实现加权轮询
以下是改进后的代码,使用 Redis 来存储和管理状态。
WeightedConfigSelector
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Random;
@Service
public class WeightedConfigSelector {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private NebulaProperties nebulaProperties;
private static final String CONFIG_LIST_KEY = "nebula:config_list";
private static final String TOTAL_WEIGHT_KEY = "nebula:total_weight";
/**
* 初始化配置到 Redis 中
*/
public void initialize() {
List<NebulaProperties.Config> configs = nebulaProperties.getConfig();
// 将配置列表存储到 Redis 中
redisTemplate.delete(CONFIG_LIST_KEY); // 清空旧数据
configs.forEach(config -> redisTemplate.opsForList().rightPush(CONFIG_LIST_KEY, config));
// 计算总权重并存储
int totalWeight = configs.stream().mapToInt(NebulaProperties.Config::getWeight).sum();
redisTemplate.opsForValue().set(TOTAL_WEIGHT_KEY, totalWeight);
}
/**
* 根据权重从 Redis 中选择配置
*/
public NebulaProperties.Config selectConfig() {
if (Boolean.FALSE.equals(redisTemplate.hasKey(CONFIG_LIST_KEY))) {
initialize();
}
// 获取总权重
Integer totalWeight = (Integer) redisTemplate.opsForValue().get(TOTAL_WEIGHT_KEY);
if (totalWeight == null || totalWeight == 0) {
throw new RuntimeException("Total weight is zero or not initialized.");
}
// 随机生成一个值
int rand = new Random().nextInt(totalWeight);
// 遍历配置列表,根据权重选择配置
List<Object> configList = redisTemplate.opsForList().range(CONFIG_LIST_KEY, 0, -1);
if (configList == null || configList.isEmpty()) {
throw new RuntimeException("No configs available in Redis.");
}
for (Object obj : configList) {
NebulaProperties.Config config = (NebulaProperties.Config) obj;
rand -= config.getWeight();
if (rand < 0) {
return config;
}
}
return null; // 不应该到达这里
}
}
3. 控制器使用示例
在控制器中调用 WeightedConfigSelector
的 selectConfig
方法来获取加权选择的配置。
ConfigController
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConfigController {
@Autowired
private WeightedConfigSelector weightedConfigSelector;
@GetMapping("/getConfig")
public NebulaProperties.Config getConfig() {
return weightedConfigSelector.selectConfig();
}
}
4. 注意事项
- 性能优化
- 如果配置列表较大,可以将配置列表缓存到本地内存中,并定期从 Redis 同步更新。
- 配置变更
- 如果配置发生变更(如新增或删除配置),需要重新调用
initialize
方法将最新配置同步到 Redis。
- 如果配置发生变更(如新增或删除配置),需要重新调用
- 线程安全
- Redis 的操作是线程安全的,因此可以放心在多线程环境中使用。
5. 总结
通过将配置列表和状态存储在 Redis 中,我们实现了一个支持分布式系统的加权轮询算法。Redis 的高性能和分布式特性确保了多个实例之间的状态一致性,同时 Spring Data Redis 提供了便捷的操作接口,简化了开发流程。