redis大纲:
我们先来复习一些命令:
SCAN index [reg] [count] 迭代当前数据库的key
SSCAN key index [reg] [count] 迭代集合的元素
HSCAN key index [reg] [count] 迭代hash的键值对
ZSCAN key index [reg] [count] 迭代zset的值和分数
index 指针偏移量
count默认10 也就是默认只查询10条
在redis2.8以后增加了scan命令, 可以分批扫描redis记录, 会导致查询的时间很长,
但是不会导致redis阻塞, 不会影响服务器的使用, 通常在删除情况下会用到
我们再复习一下keys * 所带来的问题:
因为 redis 是单线程的, 如果在并发量很高的情况下, 如果使用 keys 会将所有请求阻塞,
等待 keys 命令执行完毕再处理, 并发量很高会导致 redis 服务器的宕机, 因为redis服务器宕机,
所以请求全部并发到数据库, 造成数据库的宕机, 影响正常服务正常使用
例子: 当QPS为200w的时候使用keys *操作:
1. redis是单线程的, 用keys *命令查询所有的key, 导致redis被锁住
2. 此时QPS非常高, 在keys * 检索的时候又来了很多个请求, 因为redis被锁住,
所有的请求都被hang住了
3. 因为很多请求被hang住了, CPU又飙升, 导致redis宕机
4. 因为redis宕机, 请求全部并发到数据库, 导致数据库宕机
使用高耗时的命令是很危险的, 会占用唯一一个线程的大量处理时间, 导致所有请求都被延迟
keys 命令的时间复杂度为O(n), 严禁在生产环境使用!
还有很多时间复杂度为O(n)的命令如:hgetall lrange smemebers zrange sinter等命令, 并非不
能使用, 需要指定N的值, 否则也可能出现缓存服务器宕机!
如何在生产环境禁用某些命令呢?
在 redis 的配置文件中, 找到 SECURITY 这一模块
通常在生产环境禁用:
rename-command KEYS ""
rename-command CONFIG ""
rename-command FLUSHDB ""
rename-command FLUSHALL ""
注意:
这些命令重命名后需要关闭 AOF 持久化, 不然会出现问题, 在配置文件中有明确的说明
下面使用 scan 进行产出操作:
因为如果key很大, 删除需要很长时间, 会发生阻塞!
渐进删除list
/**
* 删除biglist 使用ltrim进行渐进删除
*
* @param jedis 连接
*/
private static void deleteBigList(Jedis jedis) {
String key = "biglist";
// 获得list长度
long len = jedis.llen(key);
// 计数器
int index = 0;
// 每次删除多少个
int left = 100;
while (index < len) {
// 每次从左侧截掉100个 保留left-len区间的元素
jedis.ltrim(key, left, len);
// 计数器增加
index += left;
}
// 最终删除key
jedis.del(key);
System.out.println("删除完毕");
}
渐进删除hash
/**
* 创建bighash
*
* @param jedis 连接
*/
private static void createBigHash(Jedis jedis) {
// 创建map
Map<String, String> map = new HashMap<>();
// 循环插值
for (int i = 0; i < 2000; i++) {
map.put(String.valueOf(i + 1), String.valueOf(i + 1));
}
// 批量添加
jedis.hmset("bighash", map);
System.out.println("生成成功");
}
渐进删除set
/**
* 删除bigset 使用sscan+srem渐进式删除
*
* @param jedis 连接
*/
private static void deleteBigSet(Jedis jedis) {
String key = "bigset";
// 遍历100条
ScanParams scan = new ScanParams().count(100);
// 指针
String cursor = "0";
do {
// 进行scan遍历
ScanResult<String> sscan = jedis.sscan(key, cursor, scan);
// 结果集对象
List<String> result = sscan.getResult();
// 遍历删除
result.forEach(e -> {
jedis.srem(key, e);
});
// 获取到遍历完毕的指针
cursor = sscan.getStringCursor();
System.out.println("cursor: " + cursor);
} while (!"0".equals(cursor));
// 最终删除key
jedis.del(key);
}
渐进删除zset
/**
* 删除bigzset zscan+ztem
*
* @param jedis 连接
*/
private static void deleteBigZset(Jedis jedis) {
String key = "bigzset";
// 遍历100条
ScanParams scan = new ScanParams().count(100);
// 指针
String cursor = "0";
do {
// 进行scan遍历
ScanResult<Tuple> zscan = jedis.zscan(key, cursor, scan);
// 结果集对象
List<Tuple> result = zscan.getResult();
// 遍历删除
result.forEach(e -> {
jedis.zrem(key, e.getElement());
});
// 获取到遍历完毕的指针
cursor = zscan.getStringCursor();
System.out.println("cursor: " + cursor);
} while (!"0".equals(cursor));
// 最终删除key
jedis.del(key);
}
结束
这就是我对redis规范解决篇的总结 感觉有用就点个赞吧 如果有错误或更好的方法评论区请多多指出 相互学习共同进步