玩转Redis-生产环境如何导入、导出及删除大量数据

4,865 阅读4分钟

  《玩转Redis》系列文章主要讲述Redis的基础及中高级应用。本文是《玩转Redis》系列第【13】篇,最新系列文章请前往公众号“zxiaofan”(点我点我)查看,或百度搜索“玩转Redis zxiaofan”即可。

本文关键字:玩转Redis、Redis数据导入、Redis大量插入、Redis数据导出、Redis导出指定通配符、Redis数据删除、Redis批量删除、Redis删除指定前缀key、Redis删除指定通配符key;

往期精选:《玩转Redis-删除了两百万key,为什么内存依旧未释放?》

大纲

  • Redis生产环境安全高效导入大量数据
    • 使用shell脚本构造测试数据
    • Redis非集群模式导入大量数据
    • Redis集群模式导入大量数据
  • Redis生产环境安全高效导出大量数据
    • Redis导入导出所有数据
    • Redis导出指定前缀(指定通配符)数据
  • Redis生产环境安全高效删除数据
    • Redis删除已知的指定key
    • Redis删除指定前缀(指定通配符)数据
  • 实用小技巧
    • Redis统计指定通配符key的数量
    • 免输密码连接Redis脚本
    • 思考题:Linux可以设置脚本可执行但不可读吗

概述:

  本文将模拟生产环境,构造大量测试数据,并完成高效安全的数据导入、数据导出、数据删除。

  众所周知,Redis是单线程的,一旦一个命令发生长时间阻塞,极易导致大量命令阻塞,从而导致生产环境雪崩,所以本文着重强调 【生产环境、安全高效】 操作。

  Let's Go!

1、Redis生产环境安全高效导入大量数据

1.1、使用shell脚本构造测试数据

  linux环境创建dataBuild.sh脚本,此脚本用于生成测试数据,脚本的入参是测试数据的数量,脚本内容如下:

#!/bin/sh
# init Test Data.
# Build By @zxiaofan.com

echo "构造数量:$1";
rm -f ./testdata.txt;
touch testdata.txt;

starttime=`date +'%Y-%m-%d %H:%M:%S'`;
echo "Start: $starttime";

for((i=0; i< $1 ;i++))
do
	str='set key-';
	name=${str}${i}' value'${i};
	echo  $name>> testdata.txt;
done

endtime=`date +'%Y-%m-%d %H:%M:%S'`;
echo "End: $endtime";

start_seconds=$(date --date="$starttime" +%s);
end_seconds=$(date --date="$endtime" +%s);
echo "本次运行时间: "$((end_seconds-start_seconds))"s"
#echo 'show testdata';
#cat testdata.txt;

  建议在linux环境创建脚本,否则可能执行shell脚本时报错$'\r': command not found。解决方案:vim acc.conf.base,然后执行 “:set fileformat=unix” ,并wq保存即可。

  接下来就可以执行脚本生成测试数据了,此处生成30W数据,脚本执行完毕后当前目录会生成测试数据文件testdata.txt。

# By @zxiaofan

# 执行脚本生成30W测试数据;

root@xxx:/study/redis# ./dataBuild.sh 300000
构造数量:300000
Start: 2020-10-11 17:30:45
End: 2020-10-11 17:34:54
本次运行时间: 249s
# 脚本内容示例,30W数据大约有7.5M:
set key-0 value0
set key-1 value1
set key-2 value2
set key-3 value3
set key-4 value4
set key-5 value5
...

1.2、Redis非集群模式导入大量数据

  Redis官方已经为我们提供了解决方案【mass-insert】:redis.io/topics/mass…

Using a normal Redis client to perform mass insertion is not a good idea for a few reasons: the naive approach of sending one command after the other is slow because you have to pay for the round trip time for every command. It is possible to use pipelining, but for mass insertion of many records you need to write new commands while you read replies at the same time to make sure you are inserting as fast as possible.

Only a small percentage of clients support non-blocking I/O, and not all the clients are able to parse the replies in an efficient way in order to maximize throughput. For all of these reasons the preferred way to mass import data into Redis is to generate a text file containing the Redis protocol, in raw format, in order to call the commands needed to insert the required data.

翻译过来就是:

  • 每个Redis客户端的命令从执行到返回结果都需要一定的时间;
  • 只有少部分客户端支持异步I/O处理,所以即使使用多个Redis客户端并发插入也不能提高吞吐量;
  • 建议解决方案是:将待插入数据以Redis协议生成文本文件再导入。

  Redis 2.6及以上版本,redis-cli支持管道模式 pipe mode(管道模式),该模式专门为执行大规模插入而设计。

  我们来实战一下,管道模式导入命令如下:

cat testdata.txt | redis-cli --pipe

  我们可以在命令前加time指令用于统计命名执行时间。

# By @zxiaofan

[redis@xxx]$ time cat testdata.txt | ./redis-cli --pipe
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 300000

real	0m1.778s
user	0m0.039s
sys	0m0.016s


# 连上Redis确认数据是否导入成功。

127.0.0.1:6379> get key-0
"value0"
127.0.0.1:6379> get key-299999
"value299999"
127.0.0.1:6379> get key-300000
(nil)

  30W数据,1.7秒完成导入。需要注意的是,如果 “redis-cli” 命令 没有设置为全局命令,需要先进入到 redis-cli 所在目录,然后执行以下命令:

# redis-cli 变成了 ./redis-cli
cat testdata.txt | ./redis-cli --pipe

  如果需要指定其他参数,可以如下操作:

  • 指定端口:-p 6379
  • 指定远程主机:-h 192.168.1.1
  • 需要密码认证:-a redisPasword
# By @zxiaofan

# Redis导入数据完整命令:

cat testdata.txt | ./redis-cli -h 192.168.1.1 -p 6379 -a redisPasword --pipe

1.3、Redis集群模式导入大量数据

  以上数据导入方式针对的是非集群模式,那么集群模式又该如何导入数据呢?

cat testdata.txt | redis-cli -c --pipe

  如上所示,使用“-c”即可启动集群模式,但是由于集群模式存在多个节点,此处极有可能会出现数据处理失败的场景。

  Redis集群模式有16384个hash槽(hash slots),假设集群有3个节点,node1分配 05460的槽位,node2分配 546110922的槽位,node3分配 10923~16383的槽位。集群中的每个主节点只会操作对应的hash槽,根据“CRC16(key) mod 16384”计算出每个key对应的槽位,然后到槽位对应的Redis节点执行操作即可。

HASH_SLOT = CRC16(key) mod 16384

注意:

  集群模式下节点可能动态增加或删除,所以执行以上命令时需确保集群节点的稳定性,同时做好结果检查。

2、Redis生产环境安全高效导出大量数据

2.1、Redis导入导出所有数据

  这种场景下,可使用Ruby开源工具redis-dump导出,官网:github.com/delano/redi…

# redis-dump使用示例

$ redis-dump -u 127.0.0.1:6371 > db_full.json
$ redis-dump -u 127.0.0.1:6371 -d 15 > db_db15.json

$ < db_full.json redis-load
$ < db_db15.json redis-load -d 15
# OR
$ cat db_full | redis-load
$ cat db_db15.json | redis-load -d 15

# 带密码操作
$ redis-dump -u :password@host:port -d 0 > db_full.json
$ cat db_full.json | redis-load -u :password@host:port -d 0

2.2、Redis导出指定前缀(指定通配符)数据

  安全高效导出大量数据,生产环境多是需要导出“指定通配符”数据,用于数据或者业务分析,难点是【安全高效导出大量指定通配符数据】,核心在“指定通配符”。面对数据量较大的生产环境,由于Redis是单线程的,所以不能使用阻塞性的操作指令。

完整命令示例:

# By @zxiaofan

./redis-cli -h 192.168.1.1 -p 6379 -a password --raw --scan --pattern '自定义pattern' > out.txt

  需要注意的是:这里不能使用“keys”指令,因为此命令是阻塞式的。网上有很多文章写的是 “keys”指令,切忌慎用,不然一不小心就真的是从删库到跑路了。

2.3、redis-cli命令--raw参数作用

  • 输出数据是原始数据,不包含类型;
  • 输出数据的中文正常显示,显示内容不是编码后的内容;

  redis-cli检测到标准输出是tty(终端)时,返回结果会加类型,比如(integer); 标准输出不是tty,例如,数据被重定向到管道或者文件时,会自动默认开启--raw选项,便不会增加类型信息。官方说明:redis.io/topics/redi… line usage”模块。

# 标准输出是tty(终端),注意结果有个(integer):
$ redis-cli incr mycounter
(integer) 7

# 标准输出不是tty(重定向到了文件):
$ redis-cli incr mycounter > /tmp/output.txt
$ cat /tmp/output.txt
8

# 加入--raw参数,结果没有(integer)
$ redis-cli --raw incr mycounter
9

3、Redis生产环境安全高效删除数据

3.1、Redis删除已知的指定key

  看了如何快速导入大量数据,其实通过其原理我们不难发现,本质上是执行了Redis协议格式的文本,那么当我们能提前构造好待删除数据的脚本时,也同样可以使用 “--pipe”完成大量数据的高效删除;

# By @zxiaofan

cat deletedata.txt | redis-cli -c --pipe
# deletedata.txt 数据示例:

del key-0
del key-1
del key-2
del key-3
del key-4
del key-5
del key-6
del key-7
del key-8
del key-9
...

3.2、删除指定前缀(指定通配符)数据

命令如下:

# By @zxiaofan

./redis-cli -h 127.0.0.1 -p 6379 -a password --scan --pattern 'key-*' | xargs ./redis-cli -h 127.0.0.1 -p 6379 -a password del {}

输出结果形如:

Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
(integer) 12322
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
(integer) 12326

命令核心是:

  • 【--scan --pattern 'key-*'】:利用通配符匹配指定key;
  • 【| xargs】:用于传递参数;
  • 【del {}】:接收key并删除;

Linux xargs 命令:

  xargs(英文全拼: eXtended ARGuments)是给命令传递参数的一个过滤器,也是组合多个命令的一个工具。 xargs 可以将管道或标准输入(stdin)数据转换成命令行参数,也能够从文件的输出中读取数据。

  xargs 也可以将单行或多行文本输入转换为其他格式,例如多行变单行,单行变多行。

  xargs 默认的命令是 echo,这意味着通过管道传递给 xargs 的输入将会包含换行和空白,不过通过 xargs 的处理,换行和空白将被空格取代。

  xargs 是一个强有力的命令,它能够捕获一个命令的输出,然后传递给另外一个命令。

  由于很多命令不支持|管道来传递参数,所以就有了 xargs 命令。

以上命令介绍源自:www.runoob.com。

xargs 一般是和管道一起使用,命令格式:

somecommand |xargs -item  command

4、实用小技巧

4.1、Redis统计指定通配符key的数量

  前面我们教会大家在生产环境导出或者删除数据,那我们如何验证数据量是否正确呢?

借助“ | wc -l”即可:

# By @zxiaofan
# 命令的核心是“ | wc -l”

>./redis-cli -h 127.0.0.1 -p 6379 -a password --scan --pattern 'id:*' | wc -l
66666

4.2、免输密码连接Redis脚本

  在生产环境每次操作都输入密码肯定是非常不安全、非常low的操作,那么我们有什么方式可以不用输入密码就连接redis吗。

  将以下内容保存于脚本“openredis.sh”中,使用 “chmod u+x openredis.sh” 授予脚本可执行权限。此处的免输密码实际上是将密码保存到脚本中,所以请注意此脚本的安全性。

# By @zxiaofan
# 连接Redis脚本,如有密码,请将脚本中的“password”换成实际密码
# 第一个参数是端口号,第二个参数是待执行命令;
# 如果需要指定主机,则加入 -h 即可;

Port=$1
CMD=$2
/usr/local/bin/redis-cli -p $Port -a password $CMD

使用方式:

# 使用 openredis.sh 查看Clients连接信息


[redis@xxx redis]# ./openredis.sh 6379 'info  Clients'
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
# Clients
connected_clients:1
client_recent_max_input_buffer:51
client_recent_max_output_buffer:0
blocked_clients:0
# 使用 openredis.sh 存入及查询数据

[redis@redis]# ./openredis.sh 6379 'set key1 v1 '
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
OK
[redis@redis]# ./openredis.sh 6379 'get key1 '
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
"v1"
# 使用 openredis.sh 统计数据
# 注意包裹指令的是单引号,'--scan --pattern 'k*''

[redis@redis]# ./openredis.sh 6379 '--scan --pattern 'id:*'' | wc -l
66666
# 使用 openredis.sh 导入数据
# testdata.txt 存放的是 大量“set key-0 value0”数据

cat testdata.txt | ./openredis.sh 6379 --pipe
# 使用 openredis.sh 导出数据

[redis@redis]# ./openredis.sh 6379 '--scan --pattern 'id:*'' > out.txt
66666

4.3、思考题:Linux可以设置脚本可执行但不可读吗

  “免输密码连接Redis脚本”我们提到,密码保存于脚本“openredis.sh”中,那么我们可以将本“openredis.sh”权限设置为“可执行但不可读”吗,以此来保证脚本中私密信息的安全。

# 设置权限为:所有者可读可写可执行;其他用户仅可执行;

chmod 711 openredis.sh
# redis用户仅有可执行权限

[redis@redis]$ ll openredis.sh 
-rwx--x--x 1 root root 186 Nov  8 22:20 openredis.sh

[redis@redis]$ cat openredis.sh 
cat: openredis.sh: Permission denied

[redis@redis]$ ./openredis.sh 6379 '--scan --pattern 'id:*'' | wc -l
bash: ./openredis.sh: Permission denied
0

  从上面的执行情况看,当脚本设置为“可执行但不可读”时,该脚本是无法执行的,因为“解释器还需要读取脚本”。

知识延伸:

  如果权限设置为“可执行但不可读”的对象不是脚本,而是一个二进制文件,此时该二进制文件是可执行的。

# 将JDK的bin目录下的java文件设置权限为仅可执行,实际发现是可执行的;

[admin@xxx bin]$ ll java
---x--x--x 1 10 143 7734 Dec 13  2016 java
[admin@xxx bin]$ java -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

  那脚本有什么方式可以实现“可执行但不可读”吗?欢迎留言探讨。

【玩转Redis系列文章 近期精选 @zxiaofan】
《玩转Redis-删除了两百万key,为什么内存依旧未释放?》

《玩转Redis-Redis中布隆过滤器的使用及原理》

《玩转Redis-HyperLogLog原理探索》

《玩转Redis-HyperLogLog统计微博日活月活》

《玩转Redis-京东签到领京豆如何实现》


祝君好运!
Life is all about choices!
将来的你一定会感激现在拼命的自己!
CSDN】【GitHub】【OSCHINA】【掘金】【语雀】【微信公众号