一、redis简介
redis官网地址:https://redis.io/
1、redis的优势
- Reids是REmote DIctionary Server远程数据服务的缩写。是一款内存高速缓存数据库。
- Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的String类型的数据,同时还提供list,set,sorted set,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
- 性能极高:Redis能读的速度是11万次/s,写的速度是8.1万次/s 。
- 原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key过期等等特性。
2、redis的应用场景
- 缓存
- 计数器:incr
- 消息队列
- 排行榜:sorted set有序集合
- 社交网络
- 实时系统
二、redis安装
Redis最初只支持linux,后来微软自己也开发了适合win系统的redis。
1、linux OS下安装与启动
从redis官网https://redis.io上下载redis安装包:redis-4.0.1.tar.gz,并上传到linux服务器/home/wm/redis目录下(安装目录请用户自定义),进入目录 /home/wm/redis,执行解压命令:tar zxvf redis-4.0.1.tar.gz,最后进入/home/wm/redis/redis-4.0.1目录直接执行make命令编译安装;安装完成后,在/home/wm/redis/redis-4.0.1/src目录下有redis-server、redis-cli、redis-conf这三个重要目录,执行命令./redis-server启动redis。
- 修改redis为后台启动:打开redis.conf文件,将daemonize设置为yes
- 查看redis进程:ps -ef|grep redis
- 使用redis客户端访问redis:在src目录下执行命令:./redis-cli
2、windows OS下安装与启动
Redis 支持 32 位和 64 位。这个需要根据你操作系统的实际情况选择,这里我们下载 redis-x64-3.2.100.zip压缩包到 C 盘,解压后即可使用;
下载地址:https://github.com/MSOpenTech/redis/releases。
使用cmd命令打开一个DOS 窗口, 使用cd命令切换目录到redis安装目录 C:\redis-x64-3.2.100 运行./redis-server.exe redis.windows.conf 启动redis。如果看到如下界面,则说明redis启动成功!
这时候另启一个DOS命令窗口,原来的不要关闭,不然就无法访问服务端了。
切换到redis安装目录( C:\redis-x64-3.2.100)下运行 redis-cli.exe -h 127.0.0.1 -p 6379 启动redis客户端即可访问redis。
三、redis配置介绍
Redis 的配置文件名为 redis.conf。
1、常用配置
bind 127.0.0.1 #绑定主机IP,默认值为127.0.0.1
protected-mode yes
port 6379 #监听端口,默认为6379
tcp-backlog 511
# 超时时间,默认为300(秒)。当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
tcp-keepalive 300
daemonize yes #是否以后台进程运行,默认为no
supervised no
#如以后台进程运行,则需指定一个pid,默认为/var/run/redis.pid
pidfile /var/run/redis.pid
loglevel notice #日志级别
logfile /var/log/redis.log #日志记录方式,默认值为stdout
databases 16 #可用数据库数,默认值为16,默认数据库为0
always-show-logo yes
save 900 1 #900秒(15分钟)内至少有1个key被改变
save 300 10 #300秒(5分钟)内至少有300个key被改变
save 60 10000 #60秒内至少有10000个key被改变
stop-writes-on-bgsave-error yes
rdbcompression yes #存储至本地数据库时是否压缩数据,默认为yes
rdbchecksum yes
dbfilename dump.rdb #本地数据库文件名,默认值为dump.rdb
dir /home/wm/redis/redis-cluster/7001 #本地数据库存放路径,默认值为 ./
#slaveof 10.0.0.12 6379 #当本机为从服务时,设置主服务的IP及端口
slaveof <masterip> <masterport>
masterauth master-password #当本机为从服务时,设置主服务的连接密码
# 在master服务器挂掉或者同步失败时,从服务器是否继续提供服务。
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
# 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭
requirepass foobared
# 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,
# 如果设置 maxclients 0,表示不作限制。
# 当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 10000
# 设置最大内存,达到最大内存设置后,Redis会先尝试清除已到期或即将到期的Key,
# 当此方法处理后,任到达最大内存设置,将无法再进行写入操作
# 但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory <bytes>
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
slave-lazy-flush no
# 是否在每次更新操作后进行日志记录,如果不开启,可能会在断电时导致一段时间内的数据丢失。
# 因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认值为no
appendonly yes
# 更新日志文件名,默认值为appendonly.aof
appendfilename "appendonly.aof"
#更新日志条件,共有3个可选值。no表示等操作系统进行数据缓存同步到磁盘,always表示每次更新操作后手动调用fsync()将数据写到磁盘,everysec表示每秒同步一次(默认值)。
# appendfsync always
appendfsync everysec
# appendfsync no
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble no
lua-time-limit 5000
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 15000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
2、其他配置
- .指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制):vm-enabled no
- 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享vm-swap-file /tmp/redis.swap
- 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0: vm-max-memory 0
- Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值: vm-page-size 32
- 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存: vm-pages 134217728
- 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4: vm-max-threads 4
- 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启: glueoutputbuf yes
- 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
- 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍): activerehashing yes
- 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件:include /path/to/local.con
四、redis的数据类型及常用命令
本部分内容来自于https://www.runoob.com/redis,若有侵权请与博主联系,谢谢!
1、String类型
SET key value 设置指定 key 的值 |
GET key 获取指定 key 的值。 |
GETRANGE key start end 返回 key 中字符串值的子字符,既包括start,也包括end,若end>string.length()也不报错 |
GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
GETBIT key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
MGET key1 [key2..] 获取所有(一个或多个)给定 key 的值。 |
SETBIT key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 |
SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
SETNX key value 只有在 key 不存在时设置 key 的值。成功返回1,失败返回0 |
SETRANGE key offset value 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 |
STRLEN key 返回 key 所储存的字符串值的长度。 |
MSET key value [key value ...] 同时设置一个或多个 key-value 对。 |
MSETNX key value [key value ...] 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 |
PSETEX key milliseconds value 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 |
INCR key 将 key 中储存的数字值增一。若该key对应的value不是int类型时会报错 |
INCRBY key increment 若该key对应的值不是int类型,则抛出如下异常 redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range |
INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment) 。并将结果返回 |
DECR key 将 key 中储存的数字值减一。 |
DECRBY key decrement key 所储存的值减去给定的减量值(decrement) 。 |
APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。 |
2、List列表类型
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
LINDEX key index 通过索引获取列表中的元素 |
LINSERT key BEFORE|AFTER pivot value 在列表的元素前或者后插入元素 |
LLEN key 获取列表长度 |
LPOP key 移出并获取列表的第一个元素 |
LPUSH key value1 [value2] 将一个或多个值插入到列表头部 |
LPUSHX key value 将一个值插入到已存在的列表头部 |
LRANGE key start stop 获取列表指定范围内的元素 |
LREM key count value 移除列表元素 |
LSET key index value 通过索引设置列表元素的值(用新值替换指定位置的值) |
LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
RPOP key 移除并获取列表最后一个元素 |
RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
RPUSH key value1 [value2] 在列表中添加一个或多个值 |
RPUSHX key value 为已存在的列表添加值 |
3、Set类型
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
集合中最大的成员数为 2的(32 - 1)次方, (4294967295, 每个集合可存储40多亿个成员)。
SADD key member1 [member2] 向集合添加一个或多个成员 |
SCARD key 获取集合的成员数量,返回值是int类型 |
SDIFF key1 [key2] 返回给定所有集合的差集,返回key1包含、但key2不包含的成员(key1与key2的顺序不同,返回结果不同) |
SDIFFSTORE destination key1 [key2] 返回给定所有集合的差集并存储在 destination 中 |
SINTER key1 [key2] 返回给定所有集合的交集(既key1和key2都包含的数据) |
SINTERSTORE destination key1 [key2] 返回给定所有集合的交集并存储在 destination 中 |
SISMEMBER key member 判断 member 元素是否是集合 key 的成员,是返回1,不是则返回0 |
SMEMBERS key 返回集合中的所有成员 |
SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合 |
SPOP key 移除并返回集合中的一个随机元素 |
SRANDMEMBER key [count] 返回集合中一个或多个随机数 |
SREM key member1 [member2] 移除集合中一个或多个成员 |
SUNION key1 [key2] 返回所有给定集合的并集 |
SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中 |
4、Hash类型
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Redis 中每个 hash 可以存储 2的(32 - 1)次方, 键值对(40多亿)。
HDEL key field1 [field2] 删除一个或多个哈希表字段 |
HEXISTS key field 查看哈希表 key 中,指定的字段是否存在。 |
HGET key field 获取存储在哈希表中指定字段的值。 |
HGETALL key 获取在哈希表中指定 key 的所有字段和值 |
HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
HKEYS key 获取所有哈希表中的字段 |
HLEN key 获取哈希表中字段的数量 |
HMGET key field1 [field2] 获取所有给定字段的值 |
HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
HSET key field value 将哈希表 key 中的字段 field 的值设为 value 。 |
HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值。 |
HVALS key 获取哈希表中所有值 |
HSCAN key cursor [MATCH pattern] [COUNT count] 迭代哈希表中的键值对。 |
5、Sorted set类型
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 2的(32 - 1)次方 (4294967295, 每个集合可存储40多亿个成员)。
五、redis持久化及数据备份与恢复
1、RDB快照持久化
该持久化默认开。是一次性把redis中的全部数据保存在硬盘上。数据量大时(大于10G)不适合该方式。且不适合频繁执行该操作。dump.rdb文件就是redis默认快照持久化的数据库文件。具体参数配置在redis-conf文件中:快照持久化的保存频率如下
save 900 1 #900秒内如果超过1个KEY被修改过(增、删除、改),则发起一次快照持久化;
save 300 10 #300秒内如果超过10个KEY被修改过,则发起一次快照持久化;
save 60 10000 #参考如上解释
以上三个save的意思是:数据修改的频率高,保存的频率也高;数据修改的频率低,保存的频率也低。
手动发起快照命令:redis-cli bgsave
2、AOF持久化
AOF持久化也叫做精细持久化;生产环境常采用这种模式!把用户执行的每个“写”指令(删、改、增)备份到文件中,还原的时候就是执行备份的写指令。默认是关闭的。开启AOF持久化时会清空redis数据库,所以在redis 安装完成之后首先开启AOF持久化。开启AOF持久化,需要修改配置文件redis.conf,默认文件名为:appendonly.aof。配置文件被修改后,需重启redis。
AOF持久化的备份频率:
# appendfsync always # 每次遇到写指令都会保存到磁盘,数据最安全,性能最低;
# appendfsync everysec # 每秒写一次,数据安全,性能中等;
appendfsync no # 完全依赖操作系统,性能最好,数据没有安全保障
Redis持久化相关指令
- bgsave:异步保存数据到磁盘(快照保存);
- ./redis-cli -h 127.0.0.1 -p 6379 bgsave #手动发起快照,可以指定远程服务器的IP和端口号对远程服务器进行操作
- Shutdown:同步保存数据并关闭服务器;
- bgrewriteaof:当前日志文件过长时优化AOF日志文件存储(对appendonly.aof文件保存的重复指令进行优化压缩处理)
- 不同服务器的数据库文件:appendonly.aof和dump.rdb可以通用。
RDB和AOF的对比:
命令 | RDB | AOF |
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 丢数据 | 根据策略决定 |
轻重 | 重 | 轻 |
在断电重启时,优先加载AOF文件;
3、数据备份与恢复
如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。获取 redis 目录可以使用 CONFIG 命令,如下所示:redis 127.0.0.1:6379> CONFIG GET dir1) "dir"2) "/usr/local/redis/bin"
六、redis事务(了解)
以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令。
下表列出了 redis 事务的相关命令:
序号 | 命令及描述 |
1 | DISCARD 取消事务,放弃执行事务块内的所有命令。 |
2 | EXEC 执行所有事务块内的命令。 |
3 | MULTI 标记一个事务块的开始。 |
4 | UNWATCH 取消 WATCH 命令对所有 key 的监视。 |
5 | WATCH key [key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
七、redis安全
1、设置密码
在redis.conf中设置密码requirepass 123456
修改配置文件后重启redis
客户端登录使用-a加密码: ./redis-cli -c -h 127.0.0.1 -p 6379-a 123456
2、绑定IP
bind 127.0.0.1 # 绑定到允许访问redis服务器的IP地址;
表示允许这个IP可以连接redis
3、命令禁止或重命名
在redis.conf中配置命令禁止或重命名
例如:重命名极度危险的命令”flushall为”a123”
rename-command FLUSHALL "a123"
注意事项:对于flushall命令需要保证appendonly.aof文件中没有该命令,否则redis服务器将无法启动!也就是说重命名操作要在redis安装完成之后启动redis之前就操作!
4、修改默认端口,防止骇客攻击
八、常用连接命令和服务端操作命令
下表列出了 redis 连接的基本命令:
序号 | 命令及描述 |
1 | AUTH password 验证密码是否正确 |
2 | ECHO message 打印字符串 |
3 | PING 查看服务是否运行 |
4 | QUIT 关闭当前连接 |
5 | SELECT index 切换到指定的数据库 |
下表列出了 redis 服务器的相关命令:
序号 | 命令及描述 |
1 | BGREWRITEAOF 异步执行一个 AOF(AppendOnly File) 文件重写操作 |
2 | BGSAVE 在后台异步保存当前数据库的数据到磁盘 |
3 | |
4 | CLIENT LIST 获取连接到服务器的客户端连接列表 |
5 | CLIENT GETNAME 获取连接的名称 |
6 | CLIENT PAUSE timeout 在指定时间内终止运行来自客户端的命令 |
7 | CLIENT SETNAME connection-name 设置当前连接的名称 |
8 | CLUSTER SLOTS 获取集群节点的映射数组 |
9 | COMMAND 获取 Redis 命令详情数组 |
10 | COMMAND COUNT 获取 Redis 命令总数 |
11 | COMMAND GETKEYS 获取给定命令的所有键 |
12 | TIME 返回当前服务器时间 |
13 | COMMAND INFO command-name [command-name ...] 获取指定 Redis 命令描述的数组 |
14 | CONFIG GET parameter 获取指定配置参数的值 |
15 | CONFIG REWRITE 对启动 Redis 服务器时所指定的 redis.conf 配置文件进行改写 |
16 | CONFIG SET parameter value 修改 redis 配置参数,无需重启 |
17 | CONFIG RESETSTAT 重置 INFO 命令中的某些统计数据 |
18 | DBSIZE 返回当前数据库的 key 的数量 |
19 | DEBUG OBJECT key 获取 key 的调试信息 |
20 | DEBUG SEGFAULT 让 Redis 服务崩溃 |
21 | FLUSHALL 删除所有数据库的所有key |
22 | FLUSHDB 删除当前数据库的所有key |
23 | INFO [section] 获取 Redis 服务器的各种信息和统计数值 |
24 | LASTSAVE 返回最近一次 Redis 成功将数据保存到磁盘上的时间,以 UNIX 时间戳格式表示 |
25 | MONITOR 实时打印出 Redis 服务器接收到的命令,调试用 |
26 | ROLE 返回主从实例所属的角色 |
27 | SAVE 同步保存数据到硬盘 |
28 | SHUTDOWN [NOSAVE] [SAVE] 异步保存数据到硬盘,并关闭服务器 |
29 | SLAVEOF host port 将当前服务器转变为指定服务器的从属服务器(slave server) |
30 | SLOWLOG subcommand [argument] 管理 redis 的慢日志 |
31 | SYNC 用于复制功能(replication)的内部命令 |
九、redis性能测试
redis-benchmark.exe在redis的安装目录下,Redis-benchmark是官方自带的Redis性能测试工具,可以有效的测试Redis服务的性能。redis 性能测试工具可选参数如下所示:
序号 | 选项 | 描述 | 默认值 |
1 | -h | 指定服务器主机名 | 127.0.0.1 |
2 | -p | 指定服务器端口 | 6379 |
3 | -s | 指定服务器 socket |
|
4 | -c | 指定并发连接数 | 50 |
5 | -n | 指定请求数 | 10000 |
6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 |
|
9 | -P | 通过管道传输 <numreq> 请求 | 1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值,通过 -q 参数让结果只显示每秒执行的请求数 |
|
11 | --csv | 以 CSV 格式输出 |
|
12 | -l | 生成循环,永久执行测试 |
|
13 | -t | 仅运行以逗号分隔的测试命令列表。 |
|
14 | -I | Idle 模式。仅打开 N 个 idle 连接并等待。 |
|
测试命令实例:
1、redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000
100个并发连接,100000个请求,检测host为localhost 端口为6379的redis服务器性能
2、redis-benchmark -h 127.0.0.1 -p 6379 -q -d 100
测试存取大小为100字节的数据包的性能
3、redis-benchmark -t set,lpush -n 100000 -q
只测试某些操作的性能
4、redis-benchmark -n 100000 -q script load "redis.call('set','foo','bar')"
只测试某些数值存取的性能
九、redis集群部署
Redis3.0版本之后支持Cluster。主要作用就是读写分离,容灾恢复!
带着问题去学习:
- slave1...slaveN是从头开始复制还是从切入点开始复制?
- master shutdown后情况如何?master恢复之后呢?
- slave shutdown之后呢?slave恢复之后呢?
注意:主从数据库版本要一致,否则可能会出现数据不同步的问题。
在redis.conf中配置主从服务器:
slaveof <主服务器IP> <主服务器端口>
slave-serve-stale-data yes
slave-read-only yes # 默认从库只支持读,不支持写;
protected-mode no # 关闭保护模式允许远程访问,或使用用户名和密码访问
1、一主两仆
- 第一次slaver1 和slaver2切入点,是全量复制,之后是增量复制;
- 主机可写可读,但是从机不可以写,配置文件里配置了从机只可读,写会报错;
- 主机挂机后,从机还是slaver;
- 主机shutdowm后从机待机状态,等主机回来后,主机新增记录从机可以顺利复制
- 从机shutdowm后,每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件;
- 一台从机只能是一个master的slaver;
2、薪火相传
A(master)-->(slaver)B(master)->c(slaver)
上一个Slaver可以是下一个slaver的Master,Slaver同样可以接收其他slavers的连接和同步请求,那么该slaver作为了链条中下一个的master,可以有效减轻master的写压力。
中途变更转向:会清除之前的数据,重新建立拷贝最新的 slaveof 新主库IP 新主库端口
第一个开头的是master,其他都是slave,只是中间的slave是下一个的master
3、反客为主
SLAVEOF no one # 使当前数据库停止与其他数据库的同步,转成主数据库
4、哨兵模式(生产环境)
十、redis哨兵模式(重点)
redis集群至少需要3个主节点!
1、redis哨兵模式架构图
原理(执行步骤):
①从数据库向主数据库发送sync命令。
②主数据库接收sync命令后,执行BGSAVE命令(保存快照),创建一个RDB文件,在创建RDB文件期间的命令将保存在缓冲区中。
③当主数据库执行完BGSAVE时,会向从数据库发送RDB文件,而从数据库会接收并载入该文件。
④主数据库将缓冲区的所有写命令发给从服务器执行。
⑤以上处理完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库。
2、redis哨兵模式搭建步骤
redis.conf配置集群参数说明:
#复制选项,slave对应的master。
# slaveof <masterip> <masterport>
#如果master设置了requirepass,那么slave要连上master,需要有master的密码才行。masterauth就是用来配置master的密码,这样可以在连上master后进行认证。
# masterauth <master-password>
#当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。2) 如果slave-serve-stale-data设置为no,除去INFO和SLAVOF命令之外的任何请求都会返回一个错误”SYNC with master in progress”。
slave-serve-stale-data yes
#作为从服务器,默认情况下是只读的(yes),可以修改成NO,用于写(不建议)。
slave-read-only yes
#是否使用socket方式复制数据。目前redis复制提供两种方式,disk和socket。如果新的slave连上来或者重连的slave无法部分同步,就会执行全量同步,master会生成rdb文件。有2种方式:disk方式是master创建一个新的进程把rdb文件保存到磁盘,再把磁盘上的rdb文件传递给slave。socket是master创建一个新的进程,直接把rdb文件以socket的方式发给slave。disk方式的时候,当一个rdb保存的过程中,多个slave都能共享这个rdb文件。socket的方式就的一个个slave顺序复制。在磁盘速度缓慢,网速快的情况下推荐用socket方式。
repl-diskless-sync no
#diskless复制的延迟时间,防止设置为0。一旦复制开始,节点不会再接收新slave的复制请求直到下一个rdb传输。所以最好等待一段时间,等更多的slave连上来。
repl-diskless-sync-delay 5
#slave根据指定的时间间隔向服务器发送ping请求。时间间隔可以通过 repl_ping_slave_period 来设置,默认10秒。
# repl-ping-slave-period 10
#复制连接超时时间。master和slave都有超时时间的设置。master检测到slave上次发送的时间超过repl-timeout,即认为slave离线,清除该slave信息。slave检测到上次和master交互的时间超过repl-timeout,则认为master离线。需要注意的是repl-timeout需要设置一个比repl-ping-slave-period更大的值,不然会经常检测到超时。
# repl-timeout 60
#是否禁止复制tcp链接的tcp nodelay参数,可传递yes或者no。默认是no,即使用tcp nodelay。如果master设置了yes来禁止tcp nodelay设置,在把数据复制给slave的时候,会减少包的数量和更小的网络带宽。但是这也可能带来数据的延迟。默认我们推荐更小的延迟,但是在数据量传输很大的场景下,建议选择yes。
repl-disable-tcp-nodelay no
#复制缓冲区大小,这是一个环形复制缓冲区,用来保存最新复制的命令。这样在slave离线的时候,不需要完全复制master的数据,如果可以执行部分同步,只需要把缓冲区的部分数据复制给slave,就能恢复正常复制状态。缓冲区的大小越大,slave离线的时间可以更长,复制缓冲区只有在有slave连接的时候才分配内存。没有slave的一段时间,内存会被释放出来,默认1m。
# repl-backlog-size 5mb
#master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。单位为秒。
# repl-backlog-ttl 3600
#当master不可用,Sentinel会根据slave的优先级选举一个master。最低的优先级的slave,当选master。而配置成0,永远不会被选举。
slave-priority 100
#redis提供了可以让master停止写入的方式,如果配置了min-slaves-to-write,健康的slave的个数小于N,mater就禁止写入。master最少得有多少个健康的slave存活才能执行写命令。这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。设置为0是关闭该功能。
# min-slaves-to-write 3
#延迟小于min-slaves-max-lag秒的slave才认为是健康的slave。
# min-slaves-max-lag 10
搭建步骤如下:
此处使用9个配置文件来模拟9台redis服务器,端口从7001-7009,期中7001的配置文件如下:
bind 192.168.188.230
port 7001
daemonize yes
pidfile /home/wm/redis-sentinel/redis-cluster/7001/redis_7001.pid
logfile /home/wm/redis-sentinel/redis-cluster/7001/7001.log
dbfilename dump.rdb
dir /home/wangmin/redis-sentinel/redis-cluster/7001
slave-serve-stale-data yes
# slaveof <masterip> <masterport> # 集群模式下禁用该配置
# masterauth <master-password>
slave-serve-stale-data yes
slave-read-only yes
appendfilename "appendonly.aof"
cluster-enabled yes
cluster-config-file nodes-7001.conf
配置完成后依次启动9台服务器:
./redis-server 7001/redis_7001.conf
...
./redis-server 7009/redis_7009.conf
查看redis服务运行情况 :
创建集群:--replicas后面接的数字,表示1个主节点对应几个从节点
./redis-trib.rb create --replicas 2 192.168.188.230:7001 192.168.188.230:7002 192.168.188.230:7003 192.168.188.230:7004 192.168.188.230:7005 192.168.188.230:7006 192.168.188.230:7007 192.168.188.230:7008 192.168.188.230:7009
此处使用三个配置文件来模拟三台sentinel服务器,端口依次为:28001,28002,28003;
其中28001的配置文件如下:
port 28001
dir /home/wangmin/redis-sentinel/redis-sentinel/28001
#该行的意思是:监控的master的名字叫做master1 (自定义),地址为192.168.188.230:7001,行尾最后的一个2代表在sentinel集群中,多少个sentinel认为masters死了,才能真正认为该master不可用了。本例中有三个master
sentinel monitor master1 192.168.188.230 7001 2
sentinel monitor master2 192.168.188.230 7002 2
sentinel monitor master3 192.168.188.230 7003 2
#sentinel会向master发送心跳PING来确认master是否存活,如果master在“一定时间范围”内不回应PONG 或者是回复了一个错误消息,那么这个sentinel会主观地(单方面地)认为这个master已经不可用了(subjectively down, 也简称为SDOWN)。而这个down-after-milliseconds就是用来指定这个“一定时间范围”的,单位是毫秒,默认30秒。
sentinel down-after-milliseconds master1 30000
sentinel down-after-milliseconds master2 30000
sentinel down-after-milliseconds master3 30000
#在发生failover(故障转移)主备切换时,这个选项指定了最多可以有多少个slave同时对新的master进行同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave处于不能处理命令请求的状态。
sentinel parallel-syncs master1 1
sentinel parallel-syncs master2 1
sentinel parallel-syncs master3 1
#failover过期时间,当failover开始后,在此时间内仍然没有触发任何failover操作,当前sentinel将会认为此次failoer失败。默认180秒,即3分钟。
sentinel failover-timeout master1 180000
sentinel failover-timeout master2 180000
sentinel failover-timeout master3 180000
daemonize yes
logfile /home/wangmin/redis-sentinel/redis-sentinel/28001/sentinel-28001.log
注意:要是参数配置的是默认值,在sentinel运行时该参数会在配置文件文件里被删除掉,直接不显示。也可以在运行时用命令SENTINEL SET command动态修改,后面说明。
启动三个sentinel:
./redis-sentinel 28001/sentinel_28001.conf
./redis-sentinel 28002/sentinel_28002.conf
./redis-sentinel 28003/sentinel_28003.conf
查看sentinel日志:
集群模式启动redis客户端:
登录7001:
登录7003:
3、假想测试
测试前:
7001是master,7004和7005是slaver;
7002是master,7006和7007是slaver;
7003是master,7008和7009是slaver;
1)关闭7002
发现原来master的slaver7006主动切换成master了:
2)继续关闭7007
访问7006:
发现7009变成了7006的slaver,7009原来是7003的slaver:
查看7009:
查看7003,原本有两个slaver(7008和7009),现在只剩下7008:
继续关闭7003:
主从关系更新如下:
master7008-slaver7001; master7006-slaver7009,master7004-slaver7005;
继续关闭7001,集群还剩下5个节点,仍然可用:
7004master-slaver7005; 7006master-slaver7009,7008master-no slaver;
继续关闭7009,集群还剩下4个节点,仍然可用:
7004master-slaver7005; 7006master-no slaver,7008master-no slaver;
继续关闭7008,集群还剩下3个节点,集群不可用:
十一、springboot整合redis(重点)
本部分内部待确认!欢迎有实战经验的大牛留言指正!
1、pom.xml
springboot相关的依赖请自己根据项目需求添加,以下只列入了springboot-redis相关的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Jedis 客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--springboot2.X默认使用lettuce连接池,需要引入commons-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、application.properties
# redis sentinel配置
spring.redis.password=
spring.redis.cluster.nodes=192.168.188.232:7001,192.168.188.232:7002,192.168.188.232:7003,192.168.188.232:7004,192.168.188.232:7005,192.168.188.232:7006,192.168.188.232:7007,192.168.188.232:7008,192.168.188.232:7009
# Maximum number of redirects to follow when executing commands across the cluster.
spring.redis.cluster.max-redirects=3
# Maximum number of connections that can be allocated by the pool at a given time. Use a negative value for no limit.
#spring.redis.jedis.pool.max-active=2000
# Maximum number of "idle" connections in the pool. Use a negative value to indicate an unlimited number of idle connections.
#spring.redis.jedis.pool.max-idle=20000
# Maximum amount of time a connection allocation should block before throwing an exception when the pool is exhausted. Use a negative value to block indefinitely
# 最大阻塞时间,使用负值表示无限等待,默认值:-1ms
#spring.redis.jedis.pool.max-wait=60000
# Target for the minimum number of idle connections to maintain in the pool. This setting only has an effect if it is positive.
#spring.redis.jedis.pool.min-idle=50
# Name of the Redis server.
#spring.redis.sentinel.master=master7001
#spring.redis.sentinel.nodes=192.168.188.232:28001,192.168.188.232:28002,192.168.188.232:28003
# Database index used by the connection factory
#spring.redis.database=0
3、RedisConfig.java
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPoolConfig;
@Configuration
public class RedisConfig {
private static Logger LOG = LoggerFactory.getLogger(RedisConfig.class);
// @Value("${spring.redis.jedis.pool.max-active}")
// private int maxActive;
//
// @Value("${spring.redis.jedis.pool.max-idle}")
// private int maxIdle;
//
// @Value("${spring.redis.jedis.pool.max-wait}")
// private long maxWaitMillis;
//
// @Value("${spring.redis.jedis.pool.min-idle}")
// private int minIdle;
//
// @Value("${spring.redis.sentinel.master}")
// private String sentinelMaster;
//
// @Value("#{'${spring.redis.sentinel.nodes}'.split(',')}")
// private List<String> sentinelNodes;
// @Value("${spring.redis.database}")
// private int database;
@Value("#{'${spring.redis.cluster.nodes}'.split(',')}")
private List<String> clusterNodes;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.cluster.max-redirects}")
private int maxRedirects;
@Bean
public RedisClusterConfiguration redisClusterConfiguration() {
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(clusterNodes);
clusterConfiguration.setMaxRedirects(maxRedirects);
clusterConfiguration.setPassword(password);
return clusterConfiguration;
}
@Bean
public JedisConnectionFactory redisConnectionFactory(RedisClusterConfiguration cluster) {
return new JedisConnectionFactory(cluster);
}
// class JedisConnectionFactory implements RedisConnectionFactory
@Bean
public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
// @Bean
// public JedisPoolConfig jedisPoolConfig() {
// JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// // poolConfig.setMaxTotal(8);
// jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
// jedisPoolConfig.setMaxIdle(maxIdle);
// jedisPoolConfig.setMinIdle(minIdle);
// jedisPoolConfig.setTestOnBorrow(true);
// jedisPoolConfig.setTestOnReturn(false);
// jedisPoolConfig.setTestWhileIdle(true);
// return jedisPoolConfig;
// }
//
// @Bean
// public RedisSentinelConfiguration redisSentinelConfiguration() {
// RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();
// // 配置matser的名称
// redisSentinelConfiguration.master(sentinelMaster);
// // redisSentinelConfiguration.setMaster(sentinelMaster);
// // 配置redis的哨兵sentinel
// Set<RedisNode> redisNodeSet = new HashSet<>();
// sentinelNodes.forEach(x -> redisNodeSet.add(new RedisNode(x.split(":")[0], Integer.parseInt(x.split(":")[1]))));
// redisSentinelConfiguration.setSentinels(redisNodeSet);
// redisSentinelConfiguration.setPassword(password);
// redisSentinelConfiguration.setDatabase(database);
// return redisSentinelConfiguration;
// }
// @Bean
// public JedisConnectionFactory jedisConnectionFactory(RedisSentinelConfiguration sentinelConfig,JedisPoolConfig jedisPoolConfig) {
// return new JedisConnectionFactory(sentinelConfig, jedisPoolConfig);
// }
// @Bean
// public JedisCluster jedisCluster() {
// Set<HostAndPort> nodes = new HashSet<>();
// clusterNodes.stream().forEach(node -> {
// nodes.add(new HostAndPort(node.split(":")[0], Integer.parseInt(node.split(":")[1])));
// });
// return new JedisCluster(nodes, jedisPoolConfig());
// }
// @Bean
// public JedisSentinelPool redisPoolFactory(){
// Set<String> sentinelsets = new HashSet<String>(sentinelNodes);
// JedisSentinelPool jedisSentinelPool =
// new JedisSentinelPool(sentinelMaster, sentinelsets, jedisPoolConfig(), password);
// return jedisSentinelPool;
// }
}
4、测试类
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SignConsumerApp.class)
@Slf4j
public class RedisClusterTemplateTest {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
String key = "10820";
long timeout = 3 * 60 ;
TimeUnit unit = TimeUnit.SECONDS;
BizOrder bizOrder = BizOrder.builder().createTime(new Date()).id(Long.parseLong(key)).build();
@Test
public void expire() {
Boolean expireResult = redisTemplate.expire(key, timeout, unit);
log.info(">>>>>>>>>>> expireResult = {}", expireResult);
}
@Test
public void getExpire() {
Long expireTime = redisTemplate.getExpire(key, unit);
log.info(">>>>>>>>>>> key = {},expireTime = {}", key ,expireTime);
}
@Test
public void hasKey() {
Boolean hasKey = redisTemplate.hasKey(key);
log.info(">>>>>>>>>>> hasKey = {}", hasKey);
}
@Test
public void del() {
Boolean delete = redisTemplate.delete(key);
log.info(">>>>>>>>>>> delete = {}", delete);
}
@Test
public void get() {
Object obj = redisTemplate.opsForValue().get(key);
if(obj instanceof ResultModel) {
log.info(">>>>>>>>>>> ResutlModel = {}",obj);
}else if(obj instanceof BizOrder) {
log.info(">>>>>>>>>>> BizOrder = {}",obj);
}else {
log.info(">>>>>>>>>>> obj = {}",obj);
}
}
@Test
public void set() {
redisTemplate.opsForValue().set(key, bizOrder);
log.info(">>>>>>>>>>> set {} success",key);
}
}
十二、redis分布式锁(重点)
注意:分布式环境下,不同节点所在容器之间若存在时间差,则可会导致redis分布式锁无效;这种情况 下可以使用数据库实现分布式锁;
十三、缓存雪崩、缓存穿透、缓存击穿(重点)
十四、redis其他常见面试题
单线程的redis为什么这么快
(一)纯内存操作
(二)单线程操作,避免了频繁的上下文切换
(三)采用了非阻塞I/O多路复用机制