redis热点key独立集群实现方案
1. 设计背景
在高并发场景下,热点key会导致redis实例负载过高,影响整个系统的稳定性。通过将热点key分离到独立的redis集群,可以实现资源隔离,提高系统的抗风险能力。
2. 实现方案
2.1 核心设计思路
- 多实例配置:支持配置多个redis实例,包括普通实例和热点实例
- key路由策略:根据key的特征或规则,将请求路由到不同的redis实例
- 统一访问接口:对外提供统一的redis访问接口,屏蔽底层实例差异
- 灵活的路由规则:支持多种路由规则,如前缀匹配、正则匹配、哈希路由等
2.2 配置扩展
2.2.1 修改配置属性类
@data
@configurationproperties(prefix = "redis.sdk", ignoreinvalidfields = true)
public class redisclientconfigproperties {
// 默认实例配置
private defaultconfig defaultconfig;
// 多个实例配置
private map<string, instanceconfig> instances = new hashmap<>();
// 路由规则配置
private map<string, routerule> routerules = new hashmap<>();
@data
public static class defaultconfig {
private string host;
private int port;
private string password;
private int poolsize = 64;
private int minidlesize = 10;
private int idletimeout = 10000;
private int connecttimeout = 10000;
private int retryattempts = 3;
private int retryinterval = 1000;
private int pinginterval = 0;
private boolean keepalive = true;
}
@data
public static class instanceconfig {
private string host;
private int port;
private string password;
private int poolsize = 64;
private int minidlesize = 10;
private int idletimeout = 10000;
private int connecttimeout = 10000;
private int retryattempts = 3;
private int retryinterval = 1000;
private int pinginterval = 0;
private boolean keepalive = true;
}
@data
public static class routerule {
// 路由类型:prefix(前缀匹配)、regex(正则匹配)、hash(哈希路由)
private string type;
// 匹配规则
private string pattern;
// 目标实例名称
private string targetinstance;
}
}2.2.2 配置文件示例
redis:
sdk:
# 默认实例配置
default-config:
host: 127.0.0.1
port: 6379
# 多个redis实例配置
instances:
# 普通实例
normal:
host: 127.0.0.1
port: 6379
# 热点key实例
hot:
host: 127.0.0.1
port: 6380
# 活动相关实例
activity:
host: 127.0.0.1
port: 6381
# 路由规则配置
route-rules:
# 热点key路由规则:以"hot_"开头的key路由到hot实例
hot-rule:
type: prefix
pattern: hot_
target-instance: hot
# 活动key路由规则:以"activity_"开头的key路由到activity实例
activity-rule:
type: prefix
pattern: activity_
target-instance: activity2.3 redis客户端配置扩展
@configuration
@enableconfigurationproperties(redisclientconfigproperties.class)
public class redisclientconfig {
// redis实例映射,key为实例名称,value为redissonclient实例
private final map<string, redissonclient> redisinstances = new concurrenthashmap<>();
// 路由规则列表
private final list<routerulewrapper> routerules = new arraylist<>();
@postconstruct
public void init(configurableapplicationcontext applicationcontext, redisclientconfigproperties properties) {
// 1. 初始化默认实例
initdefaultinstance(applicationcontext, properties);
// 2. 初始化其他实例
initotherinstances(applicationcontext, properties);
// 3. 初始化路由规则
initrouterules(properties);
}
private void initdefaultinstance(configurableapplicationcontext applicationcontext, redisclientconfigproperties properties) {
redisclientconfigproperties.defaultconfig defaultconfig = properties.getdefaultconfig();
redissonclient redissonclient = createredissonclient(defaultconfig);
redisinstances.put("default", redissonclient);
}
private void initotherinstances(configurableapplicationcontext applicationcontext, redisclientconfigproperties properties) {
map<string, redisclientconfigproperties.instanceconfig> instances = properties.getinstances();
for (map.entry<string, redisclientconfigproperties.instanceconfig> entry : instances.entryset()) {
string instancename = entry.getkey();
redisclientconfigproperties.instanceconfig instanceconfig = entry.getvalue();
redissonclient redissonclient = createredissonclient(instanceconfig);
redisinstances.put(instancename, redissonclient);
}
}
private void initrouterules(redisclientconfigproperties properties) {
map<string, redisclientconfigproperties.routerule> routerulesconfig = properties.getrouterules();
for (map.entry<string, redisclientconfigproperties.routerule> entry : routerulesconfig.entryset()) {
redisclientconfigproperties.routerule config = entry.getvalue();
routerulewrapper wrapper = new routerulewrapper();
wrapper.settype(config.gettype());
wrapper.setpattern(config.getpattern());
wrapper.settargetinstance(config.gettargetinstance());
routerules.add(wrapper);
}
}
private redissonclient createredissonclient(object configobj) {
config config = new config();
config.setcodec(jsonjacksoncodec.instance);
string host;
int port;
string password;
int poolsize;
int minidlesize;
int idletimeout;
int connecttimeout;
int retryattempts;
int retryinterval;
int pinginterval;
boolean keepalive;
// 根据配置对象类型获取配置属性
if (configobj instanceof redisclientconfigproperties.defaultconfig) {
redisclientconfigproperties.defaultconfig defaultconfig = (redisclientconfigproperties.defaultconfig) configobj;
host = defaultconfig.gethost();
port = defaultconfig.getport();
password = defaultconfig.getpassword();
poolsize = defaultconfig.getpoolsize();
minidlesize = defaultconfig.getminidlesize();
idletimeout = defaultconfig.getidletimeout();
connecttimeout = defaultconfig.getconnecttimeout();
retryattempts = defaultconfig.getretryattempts();
retryinterval = defaultconfig.getretryinterval();
pinginterval = defaultconfig.getpinginterval();
keepalive = defaultconfig.iskeepalive();
} else {
redisclientconfigproperties.instanceconfig instanceconfig = (redisclientconfigproperties.instanceconfig) configobj;
host = instanceconfig.gethost();
port = instanceconfig.getport();
password = instanceconfig.getpassword();
poolsize = instanceconfig.getpoolsize();
minidlesize = instanceconfig.getminidlesize();
idletimeout = instanceconfig.getidletimeout();
connecttimeout = instanceconfig.getconnecttimeout();
retryattempts = instanceconfig.getretryattempts();
retryinterval = instanceconfig.getretryinterval();
pinginterval = instanceconfig.getpinginterval();
keepalive = instanceconfig.iskeepalive();
}
// 配置单节点redis
singleserverconfig singleserverconfig = config.usesingleserver()
.setaddress("redis://" + host + ":" + port)
.setconnectionpoolsize(poolsize)
.setconnectionminimumidlesize(minidlesize)
.setidleconnectiontimeout(idletimeout)
.setconnecttimeout(connecttimeout)
.setretryattempts(retryattempts)
.setretryinterval(retryinterval)
.setpingconnectioninterval(pinginterval)
.setkeepalive(keepalive);
// 设置密码(如果有)
if (stringutils.isnotblank(password)) {
singleserverconfig.setpassword(password);
}
return redisson.create(config);
}
/**
* 根据key获取对应的redissonclient实例
*/
public redissonclient getredissonclient(string key) {
// 遍历路由规则,找到匹配的规则
for (routerulewrapper rule : routerules) {
if (matchrule(key, rule)) {
string targetinstance = rule.gettargetinstance();
return redisinstances.get(targetinstance);
}
}
// 没有匹配到规则,使用默认实例
return redisinstances.get("default");
}
/**
* 检查key是否匹配路由规则
*/
private boolean matchrule(string key, routerulewrapper rule) {
string type = rule.gettype();
string pattern = rule.getpattern();
switch (type) {
case "prefix":
// 前缀匹配
return key.startswith(pattern);
case "regex":
// 正则匹配
return key.matches(pattern);
case "hash":
// 哈希路由(根据key的哈希值路由到不同实例)
// 这里简化实现,实际可以根据哈希值和实例数量计算路由
int hash = key.hashcode();
return math.abs(hash) % 2 == 0; // 示例:偶数哈希值匹配
default:
return false;
}
}
/**
* 路由规则包装类
*/
@data
private static class routerulewrapper {
private string type;
private string pattern;
private string targetinstance;
}
/**
* 注入redis服务
*/
@bean("redisservice")
public redisservice redisservice() {
return new redisserviceimpl(this);
}
}2.4 统一redis服务封装
/**
* redis服务接口
*/
public interface redisservice {
/**
* 设置key-value
*/
<t> void set(string key, t value);
/**
* 设置key-value,带过期时间
*/
<t> void set(string key, t value, long expiretime, timeunit timeunit);
/**
* 获取value
*/
<t> t get(string key, class<t> clazz);
/**
* 删除key
*/
boolean delete(string key);
/**
* 设置hash字段
*/
<t> void hset(string key, string field, t value);
/**
* 获取hash字段
*/
<t> t hget(string key, string field, class<t> clazz);
// 其他redis操作方法...
}
/**
* redis服务实现类
*/
@service
public class redisserviceimpl implements redisservice {
private final redisclientconfig redisclientconfig;
public redisserviceimpl(redisclientconfig redisclientconfig) {
this.redisclientconfig = redisclientconfig;
}
@override
public <t> void set(string key, t value) {
redissonclient redissonclient = redisclientconfig.getredissonclient(key);
rmap<string, t> map = redissonclient.getmap("cache");
map.put(key, value);
}
@override
public <t> void set(string key, t value, long expiretime, timeunit timeunit) {
redissonclient redissonclient = redisclientconfig.getredissonclient(key);
rbucket<t> bucket = redissonclient.getbucket(key);
bucket.set(value, expiretime, timeunit);
}
@override
public <t> t get(string key, class<t> clazz) {
redissonclient redissonclient = redisclientconfig.getredissonclient(key);
rbucket<t> bucket = redissonclient.getbucket(key);
return bucket.get();
}
@override
public boolean delete(string key) {
redissonclient redissonclient = redisclientconfig.getredissonclient(key);
rbucket<object> bucket = redissonclient.getbucket(key);
return bucket.delete();
}
@override
public <t> void hset(string key, string field, t value) {
redissonclient redissonclient = redisclientconfig.getredissonclient(key);
rmap<string, t> map = redissonclient.getmap(key);
map.put(field, value);
}
@override
public <t> t hget(string key, string field, class<t> clazz) {
redissonclient redissonclient = redisclientconfig.getredissonclient(key);
rmap<string, t> map = redissonclient.getmap(key);
return map.get(field);
}
// 其他redis操作方法实现...
}2.5 使用示例
@service
public class activityservice {
@autowired
private redisservice redisservice;
public void cacheactivityinfo(string activityid, activity activity) {
// 活动相关key,会路由到activity实例
string key = "activity_" + activityid;
redisservice.set(key, activity, 1, timeunit.hours);
}
public activity getactivityinfo(string activityid) {
string key = "activity_" + activityid;
return redisservice.get(key, activity.class);
}
public void cachehotproduct(string productid, product product) {
// 热点key,会路由到hot实例
string key = "hot_product_" + productid;
redisservice.set(key, product, 30, timeunit.minutes);
}
public product gethotproduct(string productid) {
string key = "hot_product_" + productid;
return redisservice.get(key, product.class);
}
public void cacheuserinfo(string userid, user user) {
// 普通key,会路由到default实例
string key = "user_" + userid;
redisservice.set(key, user, 24, timeunit.hours);
}
}3. 方案优势
3.1 资源隔离
- 热点key单独存储在独立的redis实例中,避免影响其他业务
- 不同业务线的key可以分离到不同实例,实现业务隔离
3.2 灵活扩展
- 支持动态添加redis实例,应对业务增长
- 支持多种路由规则,适应不同业务场景
3.3 高可用性
- 单个redis实例故障不会影响整个系统
- 可以为热点实例配置更高的资源规格
3.4 统一访问接口
- 对外提供统一的redis访问接口,简化开发
- 底层实例变更对业务代码透明
3.5 易于维护
- 集中管理redis实例配置
- 统一监控和管理所有redis实例
4. 部署架构
+-------------------+ +-------------------+ +-------------------+
| | | | | |
| 应用服务 | | redis普通实例 | | redis热点实例 |
| (redisservice) |--->| (port: 6379) | | (port: 6380) |
| | | | | |
+-------------------+ +-------------------+ +-------------------+
|
v
+-------------------+
| |
| redis活动实例 |
| (port: 6381) |
| |
+-------------------+5. 注意事项
- 路由规则设计:路由规则应根据业务特点精心设计,避免规则冲突
- 数据迁移:已有数据需要考虑迁移策略,确保平滑过渡
- 监控告警:需要为每个redis实例配置独立的监控和告警
- 一致性问题:不同实例间的数据一致性需要业务层面保证
- 连接管理:需要合理配置连接池大小,避免连接泄漏
- 性能测试:上线前需要进行充分的性能测试,验证方案效果
6. 扩展建议
- 自动热点识别:结合监控数据,实现热点key的自动识别和迁移
- 动态路由调整:支持根据实例负载动态调整路由规则
- redis集群支持:扩展支持redis集群配置,提高可用性
- 多种客户端支持:除了redisson,支持其他redis客户端如lettuce
- 缓存预热:实现热点数据的自动预热,提高系统响应速度
通过以上方案,可以实现热点key的独立集群部署,提高系统的抗风险能力和性能表现,同时保持良好的扩展性和维护性。
到此这篇关于redis热点key独立集群实现方案的文章就介绍到这了,更多相关redis热点key内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论