生产环境每天通过定时任务自动清理redis数据,由于使用的是其它项目的redis集群,其它项目把生产环境上的keys *命令禁用了,导致我们清理redis数据失败。
*3
$3
set
$2
k1
$2
v1
*3
$3
set
$5
count
$6
123456
解释:
不同的字符需要用换行隔开
简单直观,推荐使用。
set k1 v1
set k2 v2
set k3 v3
## 文件格式转码, 会去掉行尾的^M符号
unix2dos data.txt
## unix2dos -k filename把一个文件从UNIX的断行符LF转为DOS的CRLF时
## 终端报错:-bash: unix2dos: command not found
## 解决:安装unix2dos、安装dos2unix(顺便安装)
yum install -y unix2dos dos2unix
#!/bin/sh
echo "构造数量:$1";
rm -f ./testdata.txt;
touch testdata.txt;
starttime=`date +'%Y-%m-%d %H:%M:%S'`;
echo "开始时间: $starttime";
for((i=0; i< $1 ;i++))
do
total='set IMS:total:'${i}' '${i};
last='set IMS:last:'${i}' '${i};
echo $total>> testdata.txt;
echo $last>> testdata.txt;
done
endtime=`date +'%Y-%m-%d %H:%M:%S'`;
echo "结束时间: $endtime";
start_seconds=$(date --date="$starttime" +%s);
end_seconds=$(date --date="$endtime" +%s);
echo "本次运行时间: "$((end_seconds-start_seconds))"s"
保存之后给脚本执行权限
chmod 777 dataBuild.sh
## 生成1w条数据
./dataBuild.sh 10000
public class MobileUtil {
//中国移动
public static final String[] CHINA_MOBILE = {
"134", "135", "136", "137", "138", "139", "150", "151", "152", "157", "158", "159",
"182", "183", "184", "187", "188", "178", "147", "172", "198"
};
//中国联通
public static final String[] CHINA_UNICOM = {
"130", "131", "132", "145", "155", "156", "166", "171", "175", "176", "185", "186", "166"
};
//中国电信
public static final String[] CHINA_TELECOME = {
"133", "149", "153", "173", "177", "180", "181", "189", "199"
};
/**
* 生成手机号
*
* @param op 0 移动 1 联通 2 电信
*/
public static String createMobile(int op) {
StringBuilder sb = new StringBuilder();
Random random = new Random();
String mobile01;//手机号前三位
int temp;
switch (op) {
case 0:
mobile01 = CHINA_MOBILE[random.nextInt(CHINA_MOBILE.length)];
break;
case 1:
mobile01 = CHINA_UNICOM[random.nextInt(CHINA_UNICOM.length)];
break;
case 2:
mobile01 = CHINA_TELECOME[random.nextInt(CHINA_TELECOME.length)];
break;
default:
mobile01 = "op标志位有误!";
break;
}
if (mobile01.length() > 3) {
return mobile01;
}
sb.append(mobile01);
//生成手机号后8位
for (int i = 0; i < 8; i++) {
temp = random.nextInt(10);
sb.append(temp);
}
return sb.toString();
}
public static String generateMobile() {
Random random = new Random();
int op = random.nextInt(3);//随机运营商标志位
String mobile = createMobile(op);
return mobile;
}
public static String generateSceneCode() {
DecimalFormat decimalFormat = new DecimalFormat("0000");
Random random = new Random();
//生成1-600的整数
int num = random.nextInt(600) + 1;
String numFormat = decimalFormat.format(num);
return numFormat;
}
public static String generateCount() {
Random random = new Random();
//生成1-50的整数
int num = random.nextInt(50) + 1;
return String.valueOf(num);
}
}
public class Test01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
//生成手机号
String mobile = MobileUtil.generateMobile();
//生成场景码
String sceneCode = MobileUtil.generateSceneCode();
//生成随机total总数
String count = MobileUtil.generateCount();
list.add("set IMS:total:" + sceneCode + ":" + mobile + " " + count);
list.add("set IMS:last:" + sceneCode + ":" + mobile + " " + System.currentTimeMillis());
}
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter("C:\\Users\\Administrator\\Desktop\\data.txt");//创建文本文件
for (int i = 0; i < list.size(); i++) {
fileWriter.write(list.get(i) + "\r\n");//写入 \r\n换行
}
System.out.println("共" + list.size() + "条");
fileWriter.flush();
fileWriter.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
## 使用pipe批量导入(分片集群必须要每个节点都要执行一次才可以导入成功)
cat data.txt | ./redis-cli -a redis\!\@\# -p 8001 -h 192.168.0.101 --pipe
## 报错
## MOVED 13861 192.168.0.101:8002
## MOVED 6169 192.168.0.101:8003
## Last reply received from server.
## 解决:使用 redis-cli -c 来启动redis集群模式
cat data.txt | ./redis-cli -c -h 192.168.0.101 -p 8001 -a redis\!\@\#
通过 --pipe 来启动集群模式的,解决报错
## 只需要在主节点上分别执行
cat data.txt | ./redis-cli -c -h 192.168.0.101 -p 8001 -a redis\!\@\# --pipe
cat data.txt | ./redis-cli -c -h 192.168.0.102 -p 8001 -a redis\!\@\# --pipe
cat data.txt | ./redis-cli -c -h 192.168.0.103 -p 8001 -a redis\!\@\# --pipe
xargs参数说明
## 会有警告直接忽略 Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
## 没有key会报(error) ERR wrong number of arguments for 'del' command
./redis-cli -h 127.0.0.1 -a redis\!\@\# keys 'IMS:last:*'|xargs -r./redis-cli -h 127.0.0.1 -a redis\!\@\# del
./redis-cli -h 127.0.0.1 -a redis\!\@\# keys 'IMS:total:*'|xargs -r ./redis-cli -h 127.0.0.1 -a redis\!\@\# del
## 通过keys * 命令删除redis的key(对分片集群测试)
## 报错(error) CROSSSLOT Keys in request don't hash to the same slot
## 原因是因为哈希槽,在xargs参数后加上 -n1解决
## 每台节点上执行才可以删除
./redis-cli -c -h 192.168.0.101 -p 8001 -a redis\!\@\# keys 'IMS:last:*' |xargs -r -n1 ./redis-cli -c -h 192.168.0.101 -p 8001 -a redis\!\@\# del
./redis-cli -c -h 192.168.0.101 -p 8001 -a redis\!\@\# keys 'IMS:total:*'|xargs -r -n1 ./redis-cli -c -h 192.168.0.101 -p 8001 -a redis\!\@\# del
# 核心命令
# 1.把KEYS结果写入文件
# 2.读取文件,执行DEL命令
# 用法示例:删除IMS:last:*这样的key数据
# ./deleteKeys.sh IMS:last:*
# ./deleteKeys.sh IMS:total:*
#!/bin/bash
redis_list=("192.168.0.101:8001" "192.168.0.102:8001" "192.168.0.103:8001")
redis_password='redis!@#'
redis_key_file='key.txt'
echo "Configuration info:"
echo "Redis cluster is $redis_list"
echo "Redis password is $redis_password"
echo "The key file is $redis_key_file"
rm -rf $redis_key_file
echo "-----------------------------------------------------------------------------------"
echo "Collecting the key list: "
for info in ${redis_list[@]}
do
echo "Running on :$info"
ip=`echo $info | cut -d \: -f 1`
port=`echo $info | cut -d \: -f 2`
./redis-cli -h $ip -p $port -a $redis_password -c KEYS $1 2>/dev/null >> $redis_key_file
done
echo "Key list collected."
echo "-----------------------------------------------------------------------------------"
echo "Deleting the key: "
for info in ${redis_list[@]}
do
echo "Running on :$info"
ip=`echo $info | cut -d \: -f 1`
port=`echo $info | cut -d \: -f 2`
cat $redis_key_file | xargs -t -n1 --verbose ./redis-cli -h $ip -p $port -a $redis_password -c del
done
echo "Deleted."
执行脚本
./deleteKeys.sh IMS:last:*
./deleteKeys.sh IMS:total:*
因为redis生产环境禁用了keys *命令,这里无法使用上面的方式,使用scan方式删除key
## 先导入数据
cat data.txt | ./redis-cli -a redis\!\@\# --pipe
## xargs参数:-L 1000 返回条数(只有单机节点才生效)
## 指令表示xargs一次读取的行数,也就是每次删除key的数量,不要一次行读取太多数量key。
./redis-cli -p 6379 -a redis\!\@\# --scan --pattern "IMS:total:*" 2>/dev/null| xargs -r -L 100 ./redis-cli -p 6379 -a redis\!\@\# del
./redis-cli -p 6379 -a redis\!\@\# --scan --pattern "IMS:last:*" 2>/dev/null | xargs -r -L 1000 ./redis-cli -p 6379 -a redis\!\@\# del
通过命令可以查看集群状态:
./redis-cli -a redis\!\@\# -p 8001 cluster nodes | grep master
删除
## 每个master节点都要执行一次分才可以删除
./redis-cli -h 192.168.0.101 -c -p 8001 -a redis\!\@\# --scan --pattern "IMS:total:0001:*" | xargs -r -n1 ./redis-cli -h 192.168.0.101 -c -p 8001 -a redis\!\@\# del
./redis-cli -h 192.168.0.102 -c -p 8001 -a redis\!\@\# --scan --pattern "IMS:total:*" | xargs -r -n1 -L 1000 ./redis-cli -h 192.168.0.102 -c -p 8002 -a redis\!\@\# del
./redis-cli -h 192.168.0.103 -c -p 8001 -a redis\!\@\# --scan --pattern "IMS:total:*" | xargs -r -n1 -L 1000 ./redis-cli -h 192.168.0.103 -c -p 8003 -a redis\!\@\# del
#!/bin/bash
starttime=`date +'%Y-%m-%d %H:%M:%S'`;
echo "开始时间: $starttime";
redis_list=("192.168.0.101:8002" "192.168.0.101:8003" "192.168.0.103:8001")
redis_password='redis!@#'
redis_key_file='key.txt'
echo "Configuration info:"
echo "Redis cluster is $redis_list"
echo "Redis password is $redis_password"
echo "The key file is $redis_key_file"
rm -rf $redis_key_file
echo "-----------------------------------------------------------------------------------"
echo "Collecting the key list: "
for info in ${redis_list[@]}
do
echo "Running on :$info"
ip=`echo $info | cut -d \: -f 1`
port=`echo $info | cut -d \: -f 2`
./redis-cli -h $ip -p $port -a $redis_password -c --scan --pattern $1 2>/dev/null | xargs -r -n1 -L 1000 >> $redis_key_file
done
echo "Key list collected."
echo "-----------------------------------------------------------------------------------"
echo "Deleting the key: "
for info in ${redis_list[@]}
do
echo "Running on :$info"
ip=`echo $info | cut -d \: -f 1`
port=`echo $info | cut -d \: -f 2`
cat $redis_key_file | xargs -n1 -r --verbose ./redis-cli -h $ip -p $port -a $redis_password -c del 2>/dev/null
done
endtime=`date +'%Y-%m-%d %H:%M:%S'`;
echo "结束时间: $endtime";
start_seconds=$(date --date="$starttime" +%s);
end_seconds=$(date --date="$endtime" +%s);
echo "本次运行时间: "$((end_seconds-start_seconds))"s"
执行脚本
## 6253 486s
./deleteScan.sh IMS:last:*
## 6457 518
./deleteScan.sh IMS:total:*
依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
配置文件
server:
port: 9527
spring:
redis:
database: 0
# redis密码 必须一致
password: redis!@#
timeout: 10000
jedis:
pool:
max-active: 8
max-idle: 8
max-wait: -1
min-idle: 0
cluster:
nodes:
- 192.168.0.101:8001
- 192.168.0.102:8001
- 192.168.0.103:8001
配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(redisSerializer);
//value hashmap序列化
template.setHashValueSerializer(redisSerializer);
//key haspmap序列化
template.setHashKeySerializer(redisSerializer);
return template;
}
}
代码
@SpringBootTest
class RedisClusterCleanApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
private Integer totalKeyCount = 0;
private Integer lastKeyCount = 0;
@Test
public void redisKeyCount() {
try {
redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match("IMS:total:*")
.count(Integer.MAX_VALUE).build());
while (cursor.hasNext()) {
totalKeyCount++;
cursor.next();
}
return null;
});
} catch (Throwable e) {
e.printStackTrace();
}
try {
redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match("IMS:last:*")
.count(Integer.MAX_VALUE).build());
while (cursor.hasNext()) {
lastKeyCount++;
cursor.next();
}
return null;
});
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("total key的数量" + totalKeyCount);
System.out.println("last key的数量" + lastKeyCount);
}
@Test
public void redisKeyClean() {
long start = System.currentTimeMillis();
DecimalFormat decimalFormat = new DecimalFormat("0000");
int count1 = 0;
int count2 = 0;
删除last
for (int i = 0; i <= 600; i++) {
long nowStart = System.currentTimeMillis();
String sceneCode = decimalFormat.format(i);
List<String> keyResultList = new ArrayList<>();
try {
redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match("IMS:last:" + sceneCode + ":*")
.count(Integer.MAX_VALUE).build());
while (cursor.hasNext()) {
keyResultList.add(new String(cursor.next()));
}
return null;
});
} catch (Throwable e) {
e.printStackTrace();
}
//单个删除
for (String key : keyResultList) {
redisTemplate.delete(key);
}
//批量删除
//redisTemplate.delete(keyResultList);
count1 = count1 + keyResultList.size();
long nowEnd = System.currentTimeMillis();
System.out.println("删除[IMS:last:" + sceneCode + ":*], 数量:" + keyResultList.size() + " 耗时:" + (nowEnd - nowStart) + "ms");
}
//删除total
for (int i = 0; i <= 600; i++) {
long nowStart = System.currentTimeMillis();
String sceneCode = decimalFormat.format(i);
List<String> keyResultList = new ArrayList<>();
try {
redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match("IMS:total:" + sceneCode + ":*")
.count(Integer.MAX_VALUE).build());
while (cursor.hasNext()) {
keyResultList.add(new String(cursor.next()));
}
return null;
});
} catch (Throwable e) {
e.printStackTrace();
}
//单个删除
for (String key : keyResultList) {
redisTemplate.delete(key);
}
//批量删除
//redisTemplate.delete(keyResultList);
count2 = count2 + keyResultList.size();
long nowEnd = System.currentTimeMillis();
System.out.println("删除[IMS:total:" + sceneCode + ":*], 数量:" + keyResultList.size() + " 耗时:" + (nowEnd - nowStart) + "ms");
}
long end = System.currentTimeMillis();
System.out.println("删除" + (count1 + count2) + "个key, 总耗时:" + (end - start) / 1000 + "s");
}
}
这里只是测试使用的单线程,在实际删除的时候使用多线程提升效率。
通过测试,发现数据量过大之后通过脚本删除很慢,通过代码删除较快,最后通过修改代码,调用定时任务删除。
key的规则:
项目名称:类型:场景码:手机号
以前的删除方式:通过项目名称:类型
匹配,匹配到之后再进行删除
目前采用方式
项目名称:类型:场景码
匹配,然后通过线程池进行匹配和删除。