工作中常用到的Reids命令
- 模糊匹配key并批量删除
1 | redis-cli -u redis://password@ip:port/database keys "business:data:10103*" | xargs redis-cli -u redis://password@ip:port/database del |
在一些分布式系统中,应用与应用之间是相互独立部署的,Java应用运行在不同的JVM中,所以,在操作一些共享资源的时候,使用JDK提供的Lock工具类时就有些力不从心,这时候就需要借助外力来实现分布式一致性问题。
通常会使用以下三种方式进行实现:
下面简单对比几种方式的优缺点:
方式 | 优点 | 缺点 |
---|---|---|
数据库 | 实现简单、易于理解 | 对数据库压力大 |
Redis | 易于理解 | 自己实现、不支持阻塞 |
Zookeeper | 支持阻塞 | 需要理解Zookeeper、程序复杂 |
Curator | 提供锁的方法 | 依赖Zookeeper、强一致 |
Redisson | 提供锁的方法、可阻塞 |
Redis官方文档提出以下三点作为分布式锁的最低保证:
使用Redis实现分布式锁最简单的方法就是加锁的时候创建一个带有过期时间Key(这样是为了防止出现死锁),当客户端需要释放锁的时候删除这个Key。
从表面上看没有什么问题,但是当Redis出现宕机的时候怎么办?为了解决单点故障问题,我们可以添加一个从节点,当主节点不可用的时候,切换到从节点,但是这样实际上是不可行的,因为Redis使用的是异步复制。
该模型明显存在的竞争条件:
假设可以克服以上单节点不足的问题,我们可以使用以下命令实现分布式锁:
1 | SET resource_name my_random_value NX PX 30000 |
该命令仅在Key不存在(NX)、且到期时间(PX)为30000毫秒的情况下才设置Key。Key的值为一个随机数,该值要求必须全局唯一,使用全局唯一值是为了在删除Key的时候,Key的值是我们之前设置的值时,才删除Key。(Tips:我们总不能删除其他客户端设置的Key吧?)
可以使用以下Lua脚本完成,因为Lua脚本可以保证两个操作的原子性。
1 | if redis.call("get",KEYS[1]) == ARGV[1] then |
在算法的分布式版本中,我们假设有5个Master节点,这些节点是完全独立的,我们将各个节点部署在不同的服务器中,以保证他们同时出现故障的概率。
为了获取锁,客户端执行以下操作:
释放锁很简单,只需在所有实例中释放锁(即使之前在某个实例中没有获取到锁)。
参考Redis官方文档 https://redis.io/topics/distlock
RedLock分析 [http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html][http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html]
在一些分布式系统中,应用与应用之间是相互独立部署的,Java应用运行在不同的JVM中,所以,在操作一些共享资源的时候,使用JDK提供的Lock工具类时就有些力不从心,这时候就需要借助外力来实现分布式一致性问题。
通常会使用以下三种方式进行实现:
下面简单对比几种方式的优缺点:
方式 | 优点 | 缺点 |
---|---|---|
数据库 | 实现简单、易于理解 | 对数据库压力大 |
Redis | 易于理解 | 自己实现、不支持阻塞 |
Zookeeper | 支持阻塞 | 需要理解Zookeeper、程序复杂 |
Curator | 提供锁的方法 | 依赖Zookeeper、强一致 |
Redisson | 提供锁的方法、可阻塞 |
Redis官方文档提出以下三点作为分布式锁的最低保证:
使用Redis实现分布式锁最简单的方法就是加锁的时候创建一个带有过期时间Key(这样是为了防止出现死锁),当客户端需要释放锁的时候删除这个Key。
从表面上看没有什么问题,但是当Redis出现宕机的时候怎么办?为了解决单点故障问题,我们可以添加一个从节点,当主节点不可用的时候,切换到从节点,但是这样实际上是不可行的,因为Redis使用的是异步复制。
该模型明显存在的竞争条件:
假设可以克服以上单节点不足的问题,我们可以使用以下命令实现分布式锁:
1 | SET resource_name my_random_value NX PX 30000 |
该命令仅在Key不存在(NX)、且到期时间(PX)为30000毫秒的情况下才设置Key。Key的值为一个随机数,该值要求必须全局唯一,使用全局唯一值是为了在删除Key的时候,Key的值是我们之前设置的值时,才删除Key。(Tips:我们总不能删除其他客户端设置的Key吧?)
可以使用以下Lua脚本完成,因为Lua脚本可以保证两个操作的原子性。
1 | if redis.call("get",KEYS[1]) == ARGV[1] then |
在算法的分布式版本中,我们假设有5个Master节点,这些节点是完全独立的,我们将各个节点部署在不同的服务器中,以保证他们同时出现故障的概率。
为了获取锁,客户端执行以下操作:
释放锁很简单,只需在所有实例中释放锁(即使之前在某个实例中没有获取到锁)。
参考Redis官方文档 https://redis.io/topics/distlock
RedLock分析 [http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html][http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html]
Redis Sentinel是一个分布式系统:
Sentinel本身的设计是在为多个Sentinel进程协同合作的配置中运行。
1 | redis-sentinel /path/to/sentinel.conf |
redis-sentinel
是redis-server
的一个软链接,所以也可以使用以下方法:
1 | redis-server /path/to/sentinel.conf --sentinel |
两种方法的工作原理相同。
但是,再运行Sentinel
时必须指定配置文件,因为系统将使用此文件来保存当前状态,以便在重新启动时重新加载。如果未指定文件,则会启动失败。
Sentinels默认情况下会监听TCP端口26379连接,因此必须打开26379端口。
Redis的源码包包含一个sentinel.conf文件可用于配置Sentinel,典型的最小配置如下所示:
1 | sentinel monitor mymaster 127.0.0.1 6379 2 |
sentinel monitor含义如下:
1 | sentinel monitor <master-group-name> <ip> <port> <quorum> |
<master-group-name>
指主节点名称,<ip>
IP地址 <port>
端口号,重点说一下<quorum>
:
假如有5个Sentinel进程,并且给定主服务器的quorum置为2,则将发生以下情况:
1 | sentinel down-after-milliseconds |
down-after-milliseconds
是指Sentinel在指定的时间内没有获得实例的响应,则认为实例已关闭。
1 | sentinel parallel-syncs |
parallel-syncs
设置每次可以对几个副本进行同步数据
1 | +----+ |
如果主M1发生故障,则S2和S3将达成协议,并能够开启故障转移,从而使客户端能够继续使用。
1 | +----+ |
在这种情况下,网络分区隔离了旧的主数据库M1,因此副本R2被提升为主数据库。但是,与旧主服务器位于同一分区中的客户端(例如C1)可能会继续向旧主服务器写入数据。该数据将永远丢失,因为当分区恢复正常时,主服务器将被重新配置为新主服务器的副本,从而造成数据丢失。
使用以下Redis复制功能可以缓解此问题,如果主服务器检测到副本数量没有达到指定的副本数据,则停止接受数据写入。
1 | # 最小副本数量 |
Redis Sentinel是一个分布式系统:
Sentinel本身的设计是在为多个Sentinel进程协同合作的配置中运行。
1 | redis-sentinel /path/to/sentinel.conf |
redis-sentinel
是redis-server
的一个软链接,所以也可以使用以下方法:
1 | redis-server /path/to/sentinel.conf --sentinel |
两种方法的工作原理相同。
但是,再运行Sentinel
时必须指定配置文件,因为系统将使用此文件来保存当前状态,以便在重新启动时重新加载。如果未指定文件,则会启动失败。
Sentinels默认情况下会监听TCP端口26379连接,因此必须打开26379端口。
Redis的源码包包含一个sentinel.conf文件可用于配置Sentinel,典型的最小配置如下所示:
1 | sentinel monitor mymaster 127.0.0.1 6379 2 |
sentinel monitor含义如下:
1 | sentinel monitor <master-group-name> <ip> <port> <quorum> |
<master-group-name>
指主节点名称,<ip>
IP地址 <port>
端口号,重点说一下<quorum>
:
假如有5个Sentinel进程,并且给定主服务器的quorum置为2,则将发生以下情况:
1 | sentinel down-after-milliseconds |
down-after-milliseconds
是指Sentinel在指定的时间内没有获得实例的响应,则认为实例已关闭。
1 | sentinel parallel-syncs |
parallel-syncs
设置每次可以对几个副本进行同步数据
1 | +----+ |
如果主M1发生故障,则S2和S3将达成协议,并能够开启故障转移,从而使客户端能够继续使用。
1 | +----+ |
在这种情况下,网络分区隔离了旧的主数据库M1,因此副本R2被提升为主数据库。但是,与旧主服务器位于同一分区中的客户端(例如C1)可能会继续向旧主服务器写入数据。该数据将永远丢失,因为当分区恢复正常时,主服务器将被重新配置为新主服务器的副本,从而造成数据丢失。
使用以下Redis复制功能可以缓解此问题,如果主服务器检测到副本数量没有达到指定的副本数据,则停止接受数据写入。
1 | # 最小副本数量 |
Redis使用主从复制非常简单,它允许从实例成为主实例的副本,每当链接断开时,从节点将自动重新连接到主服务器上。并且无论主服务器发生什么情况,从节点都尝试完全复制所有数据。
默认情况下,Redis会使用异步复制,Redis从节点会异步地确认接收的数据。因此,主节点不会等待从节点处理完成。但是,如果需要知道从节点都处理了那些命令,也可以选择同步复制。
每个Redis Master 节点都有一个replication ID:这是一个较大的伪随机字符串,用于标记当前数据集。每个Master 还有一个偏移量,该偏移量会针对复制流中要发送到副本的每个字节的增量而增加。
以便更新副本的状态。即使未连接任何副本,复制偏移也会增加。
1 | # Replication |
当从节点连接主节点时,会使用PSYNC
命令来发送当前旧的主节点的Replication ID和offset,这样主节点只会同步相差的数据量,如果主节点缓冲区的副本引用(Replication ID)不存在,则会进行全量同步。
主节点开始后台生成RDB文件。同时,它开始缓冲从客户端收到的所有新写入命令。后台保存完成后,主数据库将数据库文件传输到从节点,从节点将其保存在磁盘上,然后将其加载到内存中。然后,主服务器将所有缓冲的命令发送到副本。
如果两个实例具有相同的Replication ID和offset,则它们具有完全相同的数据。但是为什么会有两个Replication ID(主和副)?
因为两个实例A和B具有相同的Replication ID,但一个实例的offset为1000,另一个实例的offset为1023,则意味着第一个实例缺少应用于数据集的某些命令。这也意味着,仅通过应用一些命令,A即可达到与B完全相同的状态。
Redis实例具有两个Replication ID的原因是由于副本被提升为主副本。故障转移后,升级后的副本仍需要记住其过去的Replication ID,因为该Replication ID是以前的主副本之一。这样,当其他副本将与新的主副本同步时,它们将尝试使用旧的主Replication ID执行部分重新同步。因为将副本提升为主副本时,它会将其辅助ID设置为其主ID,并记住发生此ID切换时的offset。稍后它将选择一个新的随机Replication ID,因为新的历史记录开始了。处理新的副本连接时,主机将其ID和offset与当前ID和辅助ID相匹配(为安全起见,直到给定的偏移量)。简而言之,这意味着在故障转移后,连接到新提升的主服务器的副本不必执行完全同步。
通常,完全重新同步需要在磁盘上创建RDB文件,然后从磁盘重新加载相同的RDB,以便为副本提供数据。
但是对于硬盘速度慢,但是在内网环境下,可以采用无盘复制,这样可以直接通过Socket将RDB发送给从节点,而无需使用硬盘作为中间存储。
1 | replicaof 192.168.1.1 6379 |
也可以使用REPLICAOF
命令进行同步。
可以使用repl-diskless-sync
配置参数启用无盘复制。在repl-diskless-sync-delay
参数控制第一个副本之后,为了等待更多副本到达而开始传输的延迟。
1 | masterauth <password> |
设置主节点的密码,也可以使用redis-cli输入config set masterauth <password>
进行设置。
1 | # 在当前至少有N个副本连接到主服务器时才接受写查询 |
Redis使用主从复制非常简单,它允许从实例成为主实例的副本,每当链接断开时,从节点将自动重新连接到主服务器上。并且无论主服务器发生什么情况,从节点都尝试完全复制所有数据。
默认情况下,Redis会使用异步复制,Redis从节点会异步地确认接收的数据。因此,主节点不会等待从节点处理完成。但是,如果需要知道从节点都处理了那些命令,也可以选择同步复制。
每个Redis Master 节点都有一个replication ID:这是一个较大的伪随机字符串,用于标记当前数据集。每个Master 还有一个偏移量,该偏移量会针对复制流中要发送到副本的每个字节的增量而增加。
以便更新副本的状态。即使未连接任何副本,复制偏移也会增加。
1 | # Replication |
当从节点连接主节点时,会使用PSYNC
命令来发送当前旧的主节点的Replication ID和offset,这样主节点只会同步相差的数据量,如果主节点缓冲区的副本引用(Replication ID)不存在,则会进行全量同步。
主节点开始后台生成RDB文件。同时,它开始缓冲从客户端收到的所有新写入命令。后台保存完成后,主数据库将数据库文件传输到从节点,从节点将其保存在磁盘上,然后将其加载到内存中。然后,主服务器将所有缓冲的命令发送到副本。
如果两个实例具有相同的Replication ID和offset,则它们具有完全相同的数据。但是为什么会有两个Replication ID(主和副)?
因为两个实例A和B具有相同的Replication ID,但一个实例的offset为1000,另一个实例的offset为1023,则意味着第一个实例缺少应用于数据集的某些命令。这也意味着,仅通过应用一些命令,A即可达到与B完全相同的状态。
Redis实例具有两个Replication ID的原因是由于副本被提升为主副本。故障转移后,升级后的副本仍需要记住其过去的Replication ID,因为该Replication ID是以前的主副本之一。这样,当其他副本将与新的主副本同步时,它们将尝试使用旧的主Replication ID执行部分重新同步。因为将副本提升为主副本时,它会将其辅助ID设置为其主ID,并记住发生此ID切换时的offset。稍后它将选择一个新的随机Replication ID,因为新的历史记录开始了。处理新的副本连接时,主机将其ID和offset与当前ID和辅助ID相匹配(为安全起见,直到给定的偏移量)。简而言之,这意味着在故障转移后,连接到新提升的主服务器的副本不必执行完全同步。
通常,完全重新同步需要在磁盘上创建RDB文件,然后从磁盘重新加载相同的RDB,以便为副本提供数据。
但是对于硬盘速度慢,但是在内网环境下,可以采用无盘复制,这样可以直接通过Socket将RDB发送给从节点,而无需使用硬盘作为中间存储。
1 | replicaof 192.168.1.1 6379 |
也可以使用REPLICAOF
命令进行同步。
可以使用repl-diskless-sync
配置参数启用无盘复制。在repl-diskless-sync-delay
参数控制第一个副本之后,为了等待更多副本到达而开始传输的延迟。
1 | masterauth <password> |
设置主节点的密码,也可以使用redis-cli输入config set masterauth <password>
进行设置。
1 | # 在当前至少有N个副本连接到主服务器时才接受写查询 |
The RDB persistence performs point-in-time snapshots of your dataset at specified intervals.(RDB持久化是以指定的时间间隔执行数据集的时间点快照。)
简单来说,RDB是每隔一段时间,会把内存中的数据写入磁盘的临时文件作为快照,恢复时把快照文件读取到内存中。如果机器宕机,那么内存中的数据就会丢失,使用RDB机制后,重启后数据会恢复。
内存备份 --> 磁盘临时文件
临时文件 --> 恢复到内存
优势
劣势
1 | dir /var/redis/6379/dump.rdb |
1 | # save <seconds> <changes> |
The AOF persistence logs every write operation received by the server, that will be played again at server startup, reconstructing the original dataset. Commands are logged using the same format as the Redis protocol itself, in an append-only fashion. Redis is able to rewrite the log in the background when it gets too big.
AOF持久化会记录服务器接收到的每个写入操作,这些操作将在服务器启动时再次执行,以重新构建原始的数据。命令记录的格式与Redis协议本身的格式相同,采用追加方式。当日志文件过大时,Redis可以在后台进行rewrite
。
日志备份 --> 命令追加方式
日志文件 --> 根据命令日志文件重新构建
redis-check-aof
工具进行修复FLUSHALL
命令清空了所有数据,也可以通过修改AOF文件进行数据恢复1 | appendonly yes |
1 | # If unsure, use "everysec". |
1 | # 设置一个百分比 |
1 | # 优先加载AOF 文件 |
The RDB persistence performs point-in-time snapshots of your dataset at specified intervals.(RDB持久化是以指定的时间间隔执行数据集的时间点快照。)
简单来说,RDB是每隔一段时间,会把内存中的数据写入磁盘的临时文件作为快照,恢复时把快照文件读取到内存中。如果机器宕机,那么内存中的数据就会丢失,使用RDB机制后,重启后数据会恢复。
内存备份 --> 磁盘临时文件
临时文件 --> 恢复到内存
优势
劣势
1 | dir /var/redis/6379/dump.rdb |
1 | # save <seconds> <changes> |
The AOF persistence logs every write operation received by the server, that will be played again at server startup, reconstructing the original dataset. Commands are logged using the same format as the Redis protocol itself, in an append-only fashion. Redis is able to rewrite the log in the background when it gets too big.
AOF持久化会记录服务器接收到的每个写入操作,这些操作将在服务器启动时再次执行,以重新构建原始的数据。命令记录的格式与Redis协议本身的格式相同,采用追加方式。当日志文件过大时,Redis可以在后台进行rewrite
。
日志备份 --> 命令追加方式
日志文件 --> 根据命令日志文件重新构建
redis-check-aof
工具进行修复FLUSHALL
命令清空了所有数据,也可以通过修改AOF文件进行数据恢复1 | appendonly yes |
1 | # If unsure, use "everysec". |
1 | # 设置一个百分比 |
1 | # 优先加载AOF 文件 |
1 | make && make test && make install |
make时报如下错误:
1 | zmalloc.h:50:31: error: jemalloc/jemalloc.h: No such file or directory |
原因是jemalloc重载了Linux下的ANSI C的malloc和free函数。
解决办法:
1 | make MALLOC=libc |
参数 | 值 | 说明 |
---|---|---|
daemonize | yes | 让redis以daemon进程运行 |
pidfile | /var/run/redis_6379.pid | 设置redis的pid文件位置 |
port | 6379 | 设置redis的监听端口号 |
dir | /var/redis/6379 | 设置持久化文件的存储位置 |
1 | cd /etc/init.d |
ps -ef | grep redis
1 | # chkconfig: 2345 90 10 |
1 | slaveof 192.168.1.1 6379 |
基于主从复制架构,实现读写分离
redis slave node 只读,默认开启,slave-read-only
开启了只读的redis slave node,会拒绝所有的写操作,这样可以强制搭建成读写分离架构
集成安全认证
master 上启用安全认证策略,requirepass
master 连接口令 masterauth
读写分离架构的测试
先启动主节点,再启动从节点,在搭建生产环境的时候,不要忘记修改一个配置:
1 | bind 127.0.0.1 |
每个redis.conf 中的bind 127.0.0.1
-> bind自己的ip,并且防火墙打开6379端口
命令 | 说明 |
---|---|
redis-cli shutdown | 连接本机的6379端口停止redis进程 |
redis-cli -h 127.0.0.1 -p 6379 | 指定要连接的ip和端口号 |
redis-cli ping | ping redis的端口,看是否正常 |
redis-cli | 进入交互式命令行 |
set k1 v1 | 设置一个key:value |
get k1 | 获取key的值 |
1 | make && make test && make install |
make时报如下错误:
1 | zmalloc.h:50:31: error: jemalloc/jemalloc.h: No such file or directory |
原因是jemalloc重载了Linux下的ANSI C的malloc和free函数。
解决办法:
1 | make MALLOC=libc |
参数 | 值 | 说明 |
---|---|---|
daemonize | yes | 让redis以daemon进程运行 |
pidfile | /var/run/redis_6379.pid | 设置redis的pid文件位置 |
port | 6379 | 设置redis的监听端口号 |
dir | /var/redis/6379 | 设置持久化文件的存储位置 |
1 | cd /etc/init.d |
ps -ef | grep redis
1 | # chkconfig: 2345 90 10 |
1 | slaveof 192.168.1.1 6379 |
基于主从复制架构,实现读写分离
redis slave node 只读,默认开启,slave-read-only
开启了只读的redis slave node,会拒绝所有的写操作,这样可以强制搭建成读写分离架构
集成安全认证
master 上启用安全认证策略,requirepass
master 连接口令 masterauth
读写分离架构的测试
先启动主节点,再启动从节点,在搭建生产环境的时候,不要忘记修改一个配置:
1 | bind 127.0.0.1 |
每个redis.conf 中的bind 127.0.0.1
-> bind自己的ip,并且防火墙打开6379端口
命令 | 说明 |
---|---|
redis-cli shutdown | 连接本机的6379端口停止redis进程 |
redis-cli -h 127.0.0.1 -p 6379 | 指定要连接的ip和端口号 |
redis-cli ping | ping redis的端口,看是否正常 |
redis-cli | 进入交互式命令行 |
set k1 v1 | 设置一个key:value |
get k1 | 获取key的值 |