redis简介

redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

免费和开源!是当下最热门的NoSql技术之一!也被人们称为结构化数据库!

redis能干嘛?

  1. 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
  2. 高效率、用于高速缓冲
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器(eg:浏览量)

redis特性?

  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事务

redis是单线程的,redis是基于内存操作的,cpu不是redis的性能瓶颈,redis的瓶颈是根据机器的内存和网络宽带,既然可以使用单线程来实现,就使用单线程了

linux安装

下载地址:https://redis.io/

下载:redis-6.2.10.tar.gz

[root@quan quan]# cd /opt
[root@quan opt]# ls -l
-rw-r--r-- 1 root root 2490833 2月 12 18:22 redis-6.2.10.tar.gz
[root@quan opt]# tar -zxvf redis-6.2.10.tar.gz
[root@quan opt]# cd redis-6.2.1
[root@quan redis-6.2.1]# yum install gcc-c++ # 基本的环境安装
[root@quan redis-6.2.1]# make
[root@quan redis-6.2.1]# make isntall
[root@quan redis-6.2.1]# cd /usr/local/bin # redis默认安装路径 /usr/local/bin
[root@quan bin]# mkdir myconfig
[root@quan bin]# cp /opt/redis-6.2.10/redis.conf /usr/local/bin/myconfig/ # 备份redis.conf
[root@quan bin]# cd myconfig/ # 之后就使用这个配置文件启动 redis.conf
[root@quan myconfig]# vim redis.conf
daemonize yes # 默认是no,改为yes,代表开启守护进程模式,在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。
[root@quan myconfig]# cd ..
[root@quan bin]# redis-server myconfig/redis.conf # 通过指定配置文件启动redis-server
[root@quan bin]# redis-cli -p 6379 #使用redis客户端进行连接
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
[root@quan ~]# ps -ef|grep redis # 查看redis进程是否开启
polkitd 3324 3304 0 16:26 ? 00:00:27 redis-server *:6379
root 3387 1460 0 16:26 pts/1 00:00:00 docker exec -it ae5139dfe541 redis-cli
root 3403 3304 0 16:26 pts/0 00:00:00 redis-cli
root 12600 3647 0 21:20 pts/0 00:00:00 redis-cli -p 6379
root 12621 12603 0 21:21 pts/2 00:00:00 grep --color=auto redis
[root@quan bin]#
127.0.0.1:6379> shutdown # shutdown关闭redis
(0.51s)
not connected> exit # 退出

测试性能

redis-benchmark:Redis官方提供的性能测试工具

[root@quan ~]# cd /usr/local/bin/
[root@quan bin]# ls
dump.rdb myconfig redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server
[root@quan bin]# redis-benchmark -h localhost -p 6379 -c 100 -n 100000 # 测试:100个并发连接 100000请求

Redis命令

select

语法 说明
select index 切换到指定的数据库,数据库索引号 index 用数字值指定,以 0 作为起始索引值。
[root@quan myconfig]# cd /usr/local/bin/myconfig/
[root@quan myconfig]# vim redis.conf
databases 16 # redis默认有16个数据库,默认使用第0个,可以使用select来切换数据库
[root@quan bin]# redis-cli -p 6379
127.0.0.1:6379> set db_number 0 # 默认使用 0 号数据库
OK
127.0.0.1:6379> select 1 # 使用 1 号数据库
OK
127.0.0.1:6379[1]> get db_number # 已经切换到 1 号数据库,注意 Redis 现在的命令提示符多了个 [1]
(nil)
127.0.0.1:6379[1]> select 3 # 再切换到 3 号数据库
OK
127.0.0.1:6379[3]> # 提示符从 [1] 改变成了 [3]

keys

语法 说明
keys pattern 查找所有符合给定模式 pattern 的 key
127.0.0.1:6379> set runoob1 redis
OK
127.0.0.1:6379> set runoob2 mysql
OK
127.0.0.1:6379> set runoob3 mongodb
OK
127.0.0.1:6379> keys runoob* # 查找以 runoob 为开头的 key
1) "runoob3"
2) "runoob2"
3) "runoob1"
127.0.0.1:6379> keys * # 获取 redis 中所有的 key 可用使用 *
1) "counter:__rand_int__"
2) "runoob1"
3) "myhash"
4) "runoob3"
5) "key:__rand_int__"
6) "k1"
7) "runoob2"
8) "mylist"

flushdb

语法 说明
flushdb 清空当前数据库中的所有 key。
127.0.0.1:6379> flushdb
OK

flushall

语法 说明
flushall 清空整个 Redis 服务器的数据(删除所有数据库的所有 key )。
127.0.0.1:6379[1]> flushall 
OK

Redis键(key)

exists

语法 说明
exists key 检查给定 key 是否存在
127.0.0.1:6379> exists k1    # key不存在,返回0
(integer) 0
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> exists k1 # key存在,返回1
(integer) 1

move

语法 说明
move key db 将当前数据库的 key 移动到给定的数据库 db 当中。
127.0.0.1:6379> get k1      # key 存在于当前数据库 0
"v1"
127.0.0.1:6379> move k1 1 # 将 k1 移动到数据库 1
(integer) 1
127.0.0.1:6379> exists k1 # k1 已经被移走
(integer) 0
127.0.0.1:6379> select 1 # 使用数据库 1
OK
127.0.0.1:6379[1]> exists k1 # 证实 k1 被移到了数据库 1
(integer) 1
# 注意:当key不存在时,move失败,当源数据库和目标数据库有相同的 key 时,move失败

expire

语法 说明
expire key time 设置 key 的过期时间,key 过期后将不再可用。单位以秒计
127.0.0.1:6379> set name redis
OK
127.0.0.1:6379> expire name 60 # 为 key 设置过期时间,1分钟后该键会自动删除。
(integer) 1

ttl

语法 说明
ttl 以秒为单位返回 key 的剩余过期时间
127.0.0.1:6379> set name redis
OK
127.0.0.1:6379> expire name 10
(integer) 1
127.0.0.1:6379> ttl name # 返回 key 的剩余生存时间,以秒为单位
(integer) 8
127.0.0.1:6379> ttl name # 当 key 不存在时,返回 -2
(integer) -2
127.0.0.1:6379> set name redis
OK
127.0.0.1:6379> ttl name # 当 key 存在但没有设置剩余生存时间时,返回 -1
(integer) -1

type

语法 说明
type key 返回 key 所储存的值的类型。

常见数据类型有:

  • none (key不存在)
  • string (字符串)
  • list (列表)
  • set (集合)
  • zset (有序集合)
  • hash (哈希表)
127.0.0.1:6379> set weather "sunny"
OK
127.0.0.1:6379> type weather # key 的数据类型是string (字符串)
string

Redis字符串(String)

set

语法 说明
set key 设置给定 key 的值。如果 key 已经存储其他值, set 就覆写旧值,且无视类型
127.0.0.1:6379> set key "value"
OK
127.0.0.1:6379> get key
"value"

get

语法 说明
get key 获取指定 key 的值。如果 key 不存在,返回 nil 。如果key 储存的值不是字符串类型,返回一个错误。
127.0.0.1:6379> get db       # 对不存在的 key 或字符串类型 key 进行 GET
(nil)
127.0.0.1:6379> get name
"redis"
127.0.0.1:6379> lpush db redis mongodb mysql
(integer) 3
127.0.0.1:6379> get db # 对不是字符串类型的 key 进行 GET
(error) WRONGTYPE Operation against a key holding the wrong kind of value

append

语法 说明
append key value 为指定的 key 追加值
  • 如果 key 已经存在并且是一个字符串, append 命令将 value 追加到 key 原来的值的末尾。

  • 如果 key 不存在, append 就简单地将给定 key 设为 value ,就像执行 set key value 一样。

127.0.0.1:6379> append myphone "iphone"     # 对不存在的 key 进行 append ,等同于 set myphone "iphone"
(integer) 6 # 字符长度
127.0.0.1:6379> append myphone " - 1110" # 对已存在的字符串进行 append
(integer) 13 # 长度从 6 个字符增加到 13 个字符
127.0.0.1:6379> get myphone
"iphone - 1110"

strlen

语法 说明
strlen key 获取指定 key 所储存的字符串值的长度。当 key 储存的不是字符串值时,返回一个错误。
127.0.0.1:6379> get myphone
"iphone - 1110"
127.0.0.1:6379> strlen myphone # 获取字符串的长度
(integer) 13
127.0.0.1:6379> strlen nonno # 不存在的 key 长度为 0
(integer) 0

incr

语法 说明
incr key 将 key 中储存的数字值增一
  • 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。

  • 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。

127.0.0.1:6379> set page_view 20
OK
127.0.0.1:6379> incr page_view
(integer) 21
127.0.0.1:6379> incr page_view
(integer) 22
127.0.0.1:6379> get page_view # 数字值在 Redis 中以字符串的形式保存
"22"

decr

语法 说明
decr key 将 key 中储存的数字值减一

如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECR 操作。

如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。

127.0.0.1:6379> set failure_times 10
OK
127.0.0.1:6379> decr failure_times # 对存在的数字值 key 进行 decr
(integer) 9
127.0.0.1:6379> decr cou # 对不存在的 key 值进行 decr
(integer) -1
127.0.0.1:6379> set commany YOUR_CODE_SUCKS.LLC
OK
127.0.0.1:6379> decr commany # 对存在但不是数值的 key 进行 decr
(error) ERR value is not an integer or out of range

incrby

语法 说明
incrby key amount 将 key 中储存的数字加上指定的增量值。
  • 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 incrby 命令。

  • 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误((error) ERR value is not an integer or out of range)。

127.0.0.1:6379> set rank 50
OK
127.0.0.1:6379> incrby rank 20
(integer) 70

decrby

语法 说明
decrby key amount 将 key 所储存的值减去指定的减量值
  • 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 decrby 操作。

  • 如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。

127.0.0.1:6379> set count 50
OK
127.0.0.1:6379> decrby count 20
(integer) 30

getrange

语法 说明
getrange key start end 获取存储在指定 key 中字符串的子字符串,字符串的截取范围由 start 和 end 两个偏移量决定(包括 start 和 end 在内)。
127.0.0.1:6379> set mykey "This is my test key"
OK
127.0.0.1:6379> getrange mykey 0 3 # 截取字符串 [0,3]
"This"
127.0.0.1:6379> getrange mykey 0 -1 # 获取全部的字符串 和 get key 是一样的
"This is my test key"

setrange

语法 说明
getrange key offset value 用指定的字符串覆盖给定 key 所储存的字符串值,覆盖的位置从偏移量 offset 开始。
127.0.0.1:6379> set key1 "Hello World"
OK
127.0.0.1:6379> setrange key1 6 "Redis"
(integer) 11
127.0.0.1:6379> get key1
"Hello Redis"

setnx

语法 说明
setnx key value 在指定的 key 不存在时,为 key 设置指定的值。set if not exists
127.0.0.1:6379> exists job               # job 不存在
(integer) 0
127.0.0.1:6379> setnx job "programer" # job 设置成功
(integer) 1
127.0.0.1:6379> setnx job "code-farmer" # 尝试覆盖 job ,失败
(integer) 0
127.0.0.1:6379> get job # 没有被覆盖
"programer"

setex

语法 说明
setex key timout value 为指定的 key 设置值及其过期时间。如果 key 已经存在,setex 命令将会替换旧的值
127.0.0.1:6379> setex mykey 60 redis
OK
127.0.0.1:6379> ttl mykey
(integer) 57
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> ttl mykey
(integer) -2
127.0.0.1:6379> get mykey
(nil)

mset

语法 说明
mset key1 val1 key2 val3… 同时设置一个或多个 key-value 对
127.0.0.1:6379> mset key1 "Hello" key2 "World"
OK
127.0.0.1:6379> get key1
"Hello"
127.0.0.1:6379> get key2
"World"

mget

语法 说明
mget key1 key2 … 返回所有(一个或多个)给定 key 的值。 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil
127.0.0.1:6379> mget key1 key2 key3
1) "Hello"
2) "World"
3) (nil)

msetnx

语法 说明
msetnx key1 val1 key2 val3… 所有给定 key 都不存在时,同时设置一个或多个 key-value 对。
127.0.0.1:6379> msetnx rmdbs "Mysql" nosql "Mongodb" key-value-store "redis"   # 原子性操作,要么一起成功,要么一起失败
(integer) 1
127.0.0.1:6379> mget rmdbs nosql key-value-store
1) "Mysql"
2) "Mongodb"
3) "redis"
127.0.0.1:6379> msetnx key1 val1 key2 val2 # key1已经存在,失败
(integer) 0

getset

语法 说明
getset key value 设置指定 key 的值,并返回 key 的旧值。
  • 返回给定 key 的旧值。 当 key 没有旧值时,即 key 不存在时,返回 nil 。

  • 当 key 存在但不是字符串类型时,返回一个错误。

127.0.0.1:6379> getset key1 redis     # 没有旧值,返回 (nil)[如果不存在值,则返回(nil)]
(nil)
127.0.0.1:6379> get key1
"redis"
127.0.0.1:6379> getset key1 haha # 返回旧值 redis[如果存在值,获取原来的值,并设置新的值]
"redis"
127.0.0.1:6379> get key1
"haha"

Redis列表(List)

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 4294967295个元素, 每个列表超过40亿个元素。

lpush

语法 说明
lpush key val1 val2… 将一个或多个值插入到列表头部(左)
  • 如果 key 不存在,一个空列表会被创建并执行 lpush 操作。 当 key 存在但不是列表类型时,返回一个错误。
127.0.0.1:6379> lpush list "one"
(integer) 1 # 列表的长度
127.0.0.1:6379> lpush list "two"
(integer) 2
127.0.0.1:6379> lpush list "three"
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"

lrange

语法 说明
lrange key start end 返回列表中指定区间内的元素,区间以偏移量 start 和 end 指定
  • 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
127.0.0.1:6379> lrange list 0 -1    # 先进后出
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"

rpush

语法 说明
rpush key val1 val2… 将一个或多个值插入到列表的尾部(最右边)
  • 如果列表不存在,一个空列表会被创建并执行 rpush 操作。 当列表存在但不是列表类型时,返回一个错误。
127.0.0.1:6379> rpush mylist "hello"
(integer) 1 # 列表的长度
127.0.0.1:6379> rpush mylist "foo"
(integer) 2
127.0.0.1:6379> rpush mylist "bar"
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
3) "bar"

lpop

语法 说明
lpop key 移除并返回列表的第一个元素
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
3) "bar"
127.0.0.1:6379> lpop mylist # 返回列表的第一个元素。 当列表 key 不存在时,返回 nil
"hello"

rpop

语法 说明
rpop key 移除列表的最后一个元素,返回值为移除的元素
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
3) "bar"
127.0.0.1:6379> rpop mylist # 返回被移除的元素。当列表不存在时,返回 nil
"bar"

lindex

语法 说明
lindex key index 通过索引获取列表中的元素
  • 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lindex list 0
"three" # 返回列表中下标为指定索引值的元素
127.0.0.1:6379> lindex list 2
"one"
127.0.0.1:6379> lindex list 3
(nil) # 如果指定索引值不在列表的区间范围内,返回 (nil)
127.0.0.1:6379> lindex list -1
"one"

llen

语法 说明
llen key 返回列表的长度
  • 如果列表 key 不存在,则 key 被解释为一个空列表,返回 0 。 如果 key 不是列表类型,返回一个错误
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> llen list
(integer) 3

lrem

语法 说明
llrem key count value 根据参数 count 的值,移除列表中与参数 value 相等的元素

count 的值可以是以下几种:

  • count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
  • count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
  • count = 0 : 移除表中所有与 value 相等的值。
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> lrem list 1 one
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
127.0.0.1:6379> lpush list five
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "five"
3) "four"
4) "three"
5) "two"
127.0.0.1:6379> lrem list 2 five
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"

ltrim

语法 说明
ltrim key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除
  • 下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"
127.0.0.1:6379> ltrim list 1 -1
OK
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"

rpoplpush

语法 说明
rpoplpush source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> rpoplpush list otherlist
"one"
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> lrange otherlist 0 -1
1) "one"

lset

语法 说明
lset key index element 通过索引来设置元素的值。当索引参数超出范围,或对一个空列表进行 LSET 时,返回一个错误。
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
3) "this"
4) "world"
127.0.0.1:6379> lset mylist 0 bar
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "bar"
2) "foo"
3) "this"
4) "world"
127.0.0.1:6379> lset oother 1 other # list不存在,报错
(error) ERR no such key

linsert

语法 说明
linsert key before|after pivot element 在列表的元素前或者后插入元素。当指定元素不存在于列表中时,不执行任何操作

列表不存在时,被视为空列表,不执行任何操作。

如果 key 不是列表类型,返回一个错误。

将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。

127.0.0.1:6379> lrange mylist 0 -1
1) "bar"
2) "foo"
3) "this"
4) "world"
127.0.0.1:6379> linsert mylist before this three # 将three插入到列表mylist中,位于值this之前
(integer) 5
127.0.0.1:6379> lrange mylist 0 -1
1) "bar"
2) "foo"
3) "three"
4) "this"
5) "world"

Redis集合(Set)

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

集合中最大的成员数为 4294967295, 每个集合可存储40多亿个成员。

sadd

语法 说明
sadd key member [member …] 将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略

假如集合 key 不存在,则创建一个只包含添加的元素作成员的集合。

当集合 key 不是集合类型时,返回一个错误。

127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "foo"
(integer) 1
127.0.0.1:6379> sadd myset "hello"
(integer) 0
127.0.0.1:6379> smembers myset
1) "hello"
2) "foo"

smembers

语法 说明
smembers key 返回集合中的所有的成员。 不存在的集合 key 被视为空集合
127.0.0.1:6379> smembers myset
1) "hello"
2) "foo"

sismember

语法 说明
sismember key member 判断成员元素是否是集合的成员
127.0.0.1:6379> smembers myset
1) "hello"
2) "foo"
127.0.0.1:6379> sismember myset "hello"
(integer) 1
127.0.0.1:6379> sismember myset "hellossss"
(integer) 0

scard

语法 说明
scard key 返回集合中元素的数量
127.0.0.1:6379> smembers myset
1) "hello"
2) "foo"
127.0.0.1:6379> scard myset
(integer) 2

srem

语法 说明
srem key member [member …] 移除集合中的一个或多个成员元素,不存在的成员元素会被忽略
127.0.0.1:6379> smembers myset
1) "hello"
2) "world"
3) "foo"
127.0.0.1:6379> srem myset world
(integer) 1
127.0.0.1:6379> smembers myset
1) "hello"
2) "foo"
127.0.0.1:6379> srem myset hello foo
(integer) 2 # 返回被成功移除的元素的数量,不包括被忽略的元素
127.0.0.1:6379> smembers myset
(empty array)

srandmember

语法 说明
srandmember key [count] 返回集合中的一个随机元素

可选的 count 参数:

  • 如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合。
  • 如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。

该操作和 SPOP 相似,但 SPOP 将随机元素从集合中移除并返回,而 Srandmember 则仅仅返回随机元素,而不对集合进行任何改动。

127.0.0.1:6379> smembers myset
1) "nihao"
2) "hello"
3) "world"
127.0.0.1:6379> srandmember myset
"nihao"
127.0.0.1:6379> srandmember myset
"world"
127.0.0.1:6379> srandmember myset
"nihao"
127.0.0.1:6379> srandmember myset 2
1) "hello"
2) "world"
127.0.0.1:6379> srandmember myset -2
1) "world"
2) "world"
127.0.0.1:6379> srandmember myset -3
1) "hello"
2) "nihao"
3) "hello"

spop

语法 说明
spop key [count] 移除集合中的指定 key 的一个或多个随机元素,移除后会返回移除的元素

该命令类似 srandmember 命令,但 SPOP 将随机元素从集合中移除并返回,而 srandmember 则仅仅返回随机元素,而不对集合进行任何改动。

127.0.0.1:6379> smembers myset
1) "haha"
2) "shi"
3) "world"
4) "nihao"
5) "ni"
6) "de"
127.0.0.1:6379> spop myset
"haha"
127.0.0.1:6379> spop myset 2
1) "de"
2) "nihao"
127.0.0.1:6379> smembers myset
1) "shi"
2) "ni"
3) "world"

smove

语法 说明
smove source destination member 将指定成员 member 元素从 source 集合移动到 destination 集合

SMOVE 是原子性操作。

如果 source 集合不存在或不包含指定的 member 元素,则 SMOVE 命令不执行任何操作,仅返回 0 。否则, member 元素从 source 集合中被移除,并添加到 destination 集合中去。

当 destination 集合已经包含 member 元素时, SMOVE 命令只是简单地将 source 集合中的 member 元素删除。

当 source 或 destination 不是集合类型时,返回一个错误。

127.0.0.1:6379> smembers myset
1) "ni"
2) "hello"
3) "hao"
4) "world"
5) "ya"
127.0.0.1:6379> smembers myset2
1) "zhe"
127.0.0.1:6379> smove myset myset2 "hello"
(integer) 1
127.0.0.1:6379> smembers myset
1) "hao"
2) "world"
3) "ni"
4) "ya"
127.0.0.1:6379> smembers myset2
1) "hello"
2) "zhe"

sdiff

语法 说明
sdiff key [key …] 返回第一个集合与其他集合之间的差集,也可以认为说第一个集合中独有的元素

不存在的集合 key 将视为空集

差集的结果来自前面的 first_key,而不是后面的 other_key1,也不是整个 first_key other_key1..other_kenn 的差集

key1 = {a,b,c,d}
key2 = {c}
key3 = {a,c,e}
sdiff key1 key2 key3 = {b,d}
127.0.0.1:6379> smembers myset   # a,b,c,d
1) "b"
2) "d"
3) "a"
4) "c"
127.0.0.1:6379> smembers myset2 # c,d,e,f
1) "e"
2) "d"
3) "f"
4) "c"
127.0.0.1:6379> sdiff myset myset2
1) "b"
2) "a"

sinter

语法 说明
sinter key [key …] 返回给定所有给定集合的交集

不存在的集合 key 将视为空集。当给定集合当中有一个空集时,结果也为空集(根据集合运算定律)。

127.0.0.1:6379> smembers myset      # a,b,c,d
1) "b"
2) "d"
3) "a"
4) "c"
127.0.0.1:6379> smembers myset2 # c,d,e,f
1) "e"
2) "d"
3) "f"
4) "c"
127.0.0.1:6379> sinter myset myset2
1) "d"
2) "c"

sunion

语法 说明
sunion key [key …] 返回给定集合的并集。不存在的集合 key 被视为空集。
127.0.0.1:6379> smembers myset      # a,b,c,d
1) "b"
2) "d"
3) "a"
4) "c"
127.0.0.1:6379> smembers myset2 # c,d,e,f
1) "e"
2) "d"
3) "f"
4) "c"
127.0.0.1:6379> sunion myset myset2
1) "c"
2) "b"
3) "f"
4) "e"
5) "d"
6) "a"

Redis哈希(Hash)

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。

Redis 中每个 hash 可以存储 40多亿键值对。

hset

语法 说明
hset key field value [field value …] 为哈希表中的字段赋值

如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。

如果字段已经存在于哈希表中,旧值将被覆盖。

127.0.0.1:6379> hset myhash fidle1 "foo"          
(integer) 1 # 如果字段是哈希表中的一个新建字段,并且值设置成功,返回 1
127.0.0.1:6379> hset website google "www.g.cn" # 设置一个新域
(integer) 1
127.0.0.1:6379> hset website google "www.google.com" # 覆盖一个旧域
(integer) 0 # 如果哈希表中域字段已经存在且旧值已被新值覆盖,返回 0

hget

语法 说明
hget key field 返回哈希表中指定字段的值
127.0.0.1:6379> hget myhash fidle1
"foo"
127.0.0.1:6379> hget website google
"www.google.com"

hmset

语法 说明
hmset key field value [field value …] 同时将多个 field-value (字段-值)对设置到哈希表中

此命令会覆盖哈希表中已存在的字段。

如果哈希表不存在,会创建一个空哈希表,并执行 HMSET 操作。

127.0.0.1:6379> hmset myhash field1 "hello" field2 "world"
OK
127.0.0.1:6379> hget myhash field1
"hello"
127.0.0.1:6379> hget myhash field2
"world"

hmget

语法 说明
hmget key field [field …] 返回哈希表中,一个或多个给定字段的值

如果指定的字段不存在于哈希表,那么返回一个 nil 值。

127.0.0.1:6379> hmget myhash field1 field2
1) "hello"
2) "world"

hgetall

语法 说明
hgetall key 返回哈希表中,所有的字段和值

在返回值里,紧跟每个字段名(field name)之后是字段的值(value),所以返回值的长度是哈希表大小的两倍。

127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"

hdel

语法 说明
hdel key field [field …] 删除哈希表 key 中的一个或多个指定字段,不存在的字段将被忽略
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myhash field1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"

hlen

语法 说明
hlen key 获取哈希表中字段的数量
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field"
4) "hello"
127.0.0.1:6379> hlen myhash
(integer) 2

hexists

语法 说明
hexists key filed 查看哈希表的指定字段是否存在
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field"
4) "hello"
127.0.0.1:6379> hexists myhash field
(integer) 1

hkeys

语法 说明
hkeys key 获取哈希表中的所有域(field)
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field"
4) "hello"
127.0.0.1:6379> hkeys myhash
1) "field2"
2) "field"

hvals

语法 说明
hvals key 返回哈希表所有域(field)的值
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field"
4) "hello"
127.0.0.1:6379> hvals myhash
1) "world"
2) "hello"

hincrby

语法 说明
hincrby key field increment 为哈希表中的字段值加上指定增量值
127.0.0.1:6379> hset myhash field 5
(integer) 0
127.0.0.1:6379> hincrby myhash field 1
(integer) 6
127.0.0.1:6379> hincrby myhash field -1
(integer) 5
127.0.0.1:6379> hincrby myhash field -10
(integer) -5

hsetnx

语法 说明
hsetnx key field value 为哈希表中不存在的的字段赋值
  • 如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。

  • 如果字段已经存在于哈希表中,操作无效。

  • 如果 key 不存在,一个新哈希表被创建并执行 HSETNX 命令。

127.0.0.1:6379> hsetnx myhash field1 "foo"
(integer) 1
127.0.0.1:6379> hsetnx myhash field1 "bar" # 操作无效, field1 已存在
(integer) 0
127.0.0.1:6379> hget myhash field1
"foo"

Redis有序集合(ZSet)

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

集合中最大的成员数为 4294967295, 每个集合可存储40多亿个成员。

zadd

语法 说明
zadd key sorce member [score member …] 将一个或多个成员元素及其分数值加入到有序集当中
  • 如果某个成员已经是有序集的成员,那么更新这个成员的分数值,并通过重新插入这个成员元素,来保证该成员在正确的位置上。

  • 分数值可以是整数值或双精度浮点数。

  • 如果有序集合 key 不存在,则创建一个空的有序集并执行 ZADD 操作。

  • 当 key 存在但不是有序集类型时,返回一个错误。

127.0.0.1:6379> zadd myzset 1 "one"
(integer) 1
127.0.0.1:6379> zadd myzset 1 "uno"
(integer) 1
127.0.0.1:6379> zadd myzset 2 "two" 3 "three"
(integer) 2
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "one"
2) "1"
3) "uno"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"

zrange

语法 说明
zrange key min max [withscores] 返回有序集中,指定区间内的成员
  • 其中成员的位置按分数值递增(从小到大)来排序。具有相同分数值的成员按字典序(lexicographical order )来排列。

  • 如果你需要成员按值递减(从大到小)来排列,请使用 zrevrange 命令。

  • 下标参数 min 和 max都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。

  • 你也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。

127.0.0.1:6379> zrange salary 0 -1 withscores   # 显示整个有序集成员
1) "jack"
2) "3500"
3) "tom"
4) "5000"
5) "boss"
6) "10086"
127.0.0.1:6379> zrange salary 1 2 withscores # 显示有序集下标区间 1 至 2 的成员
1) "tom"
2) "5000"
3) "boss"
4) "10086"
127.0.0.1:6379> zrange salary 0 200000 withscores # 测试 end 下标超出最大下标时的情况
1) "jack"
2) "3500"
3) "tom"
4) "5000"
5) "boss"
6) "10086"
127.0.0.1:6379> zrange salary 200000 3000000 withscores # 测试当给定区间不存在于有序集时的情况
(empty array)

zrangebyscore

语法 说明
zrangebyscore key min max [withscores] [limit offset count] 返回有序集合中指定分数区间的成员列表
  • 有序集成员按分数值递增(从小到大)次序排列。

  • 具有相同分数值的成员按字典序来排列(该属性是有序集提供的,不需要额外的计算)。

  • 默认情况下,区间的取值使用闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。

zrangebyscore zset (1 5     返回所有符合条件 1 < score <= 5 的成员
zrangebyscore zset (5 (10 返回所有符合条件 5 < score < 10 的成员
127.0.0.1:6379> zrangebyscore salary -inf +inf    # 显示整个有序集
1) "jack"
2) "tom"
3) "boss"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 显示整个有序集及成员的 score 值 -inf表示负无穷 +inf表示正无穷
1) "jack"
2) "3500"
3) "tom"
4) "5000"
5) "boss"
6) "10086"
127.0.0.1:6379> zrangebyscore salary -inf 5000 withscores # 显示工资 <=5000 的所有成员
1) "jack"
2) "3500"
3) "tom"
4) "5000"
127.0.0.1:6379> zrangebyscore salary (5000 (400000 # 显示工资大于 5000 小于等于 400000 的成员
1) "boss"

zrem

语法 说明
zrem key member [member …] 移除有序集中的一个或多个成员,不存在的成员将被忽略
  • 当 key 存在但不是有序集类型时,返回一个错误。
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "jack"
2) "3500"
3) "tom"
4) "5000"
5) "boss"
6) "10086"
127.0.0.1:6379> zrem salary jack # 移除单个元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "tom"
2) "5000"
3) "boss"
4) "10086"
127.0.0.1:6379> zrem salary tom boss # 移除多个元素
(integer) 2
127.0.0.1:6379> zrange salary 0 -1 withscores
(empty array)

zcard

语法 说明
zcard key 计算集合中元素的数量
127.0.0.1:6379> zrange salary 0 -1 
1) "tom"
2) "jack"
3) "boss"
127.0.0.1:6379> zcard salary
(integer) 3

zrevrange

语法 说明
zrevrange ksy start stop [withscores] 返回有序集中,指定区间内的成员
  • 中成员的位置按分数值递减(从大到小)来排列。

  • 具有相同分数值的成员按字典序的逆序(reverse lexicographical order)排列。

  • 除了成员按分数值递减的次序排列这一点外, zrevrange 命令的其他方面和 zrange 命令一样。

127.0.0.1:6379> zrange salary 0 -1 withscores        # 递增排列
1) "tom"
2) "2500"
3) "jack"
4) "3000"
5) "boss"
6) "6000"
127.0.0.1:6379> zrevrange salary 0 -1 withscores # 递减排列
1) "boss"
2) "6000"
3) "jack"
4) "3000"
5) "tom"
6) "2500"

zcount

语法 说明
zcount key min max 计算有序集合中指定分数区间的成员数量
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "tom"
2) "2500"
3) "jack"
4) "3000"
5) "boss"
6) "6000"
127.0.0.1:6379> zcount salary 2000 3500
(integer) 2

geospatial地理空间

geoadd

语法 说明
geoadd key longitude latitude member [longitude latitude member …] 添加一个或多个地理位置元素到一个key中
  • 有效的经度是 -180度到180
  • 有效的纬度是-85.05112878度到85.05112878
  • 经度(longitude) 纬度(latitude)

上海市经纬度 经度:121.48941 维度:31.40527

北京市经纬度 经度:116.23128 维度:40.22077

广州市经纬度 经度:113.27324 维度:23.15792

深圳市经纬度 经度:113.88308 维度:22.55329

127.0.0.1:6379> geoadd china:city 121.48941 31.40527 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 116.23128 40.22077 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 113.27324 23.15792 guangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 113.88308 22.55329 shenzhen
(integer) 1

geopos

语法 说明
geopos key member [member …] 返回一个或多个位置的经纬度信息
127.0.0.1:6379> geopos china:city shanghai
1) 1) "121.48941010236740112"
2) "31.40526993848380499"
127.0.0.1:6379> geopos china:city beijing shenzhen
1) 1) "116.23128265142440796"
2) "40.22076905438526495"
2) 1) "113.88307839632034302"
2) "22.55329111565713873"

geodist

语法 说明
geodist key member1 member2 [m|km|ft|mi] 返回一个key中指定两个位置之间的距离

m 表示单位为米。

km表示单位为千米。

mi 表示单位为英里。

ft 表示单位为英尺。

127.0.0.1:6379> geodist china:city beijing shanghai
"1088644.3544"
127.0.0.1:6379> geodist china:city beijing shanghai km # 北京到上海的直线距离
"1088.6444"
127.0.0.1:6379> geodist china:city shenzhen guangzhou km # 深圳到广州的直线距离
"91.8118"

georadius

语法 说明
georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count] 以给定位置为中心,半径不超过给定半径的附近所有位置
127.0.0.1:6379> georadius china:city 110 30 1000 km  #以经度110维度30位中心,查找半径在1000km以内的位置
1) "shenzhen"
2) "guangzhou"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord # 显示他人的的定位信息
1) 1) "shenzhen"
2) 1) "113.88307839632034302"
2) "22.55329111565713873"
2) 1) "guangzhou"
2) 1) "113.27324062585830688"
2) "23.1579209662846921"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist # 显示到中间距离的位置
1) 1) "shenzhen"
2) "914.1294"
2) 1) "guangzhou"
2) "827.6084"
127.0.0.1:6379> georadius china:city 110 30 1000 km withhash
1) 1) "shenzhen"
2) (integer) 4046340107163728
2) 1) "guangzhou"
2) (integer) 4046534010880445
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord count 1
1) 1) "guangzhou"
2) 1) "113.27324062585830688"
2) "23.1579209662846921"

georadiusbymember

语法 说明
georadiusbymember key member radius m|km|ft|mi [withdist] [withhash] [count count] 使用输入的经度和纬度来决定中心点,找出位于指定范围内的元素
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km  # 北京周围1000km的城市
1) "beijing"

geohash

语法 说明
geohash key member [member…] 返回一个或多个位置元素的 Geohash表示
127.0.0.1:6379> geohash china:city beijing shanghai
1) "wx4sucvncn0"
2) "wtw6st1uuq0"

geo底层的实现原理其实就是Zset,我们可以使用Zset命令来操作geo

127.0.0.1:6379> zrange china:city 0 -1
1) "shenzhen"
2) "guangzhou"
3) "shanghai"
4) "beijing"
127.0.0.1:6379> zrem china:city beijing
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "shenzhen"
2) "guangzhou"
3) "shanghai"

Hyperloglog

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

A={1,3,5,7,8,7,8}

B={1,3,5,8}

A与B的基数是5,基数就是不重复元素,基数估计就是在误差可接受的范围内,快速计算基数

pfadd

语法 说明
pfadd key element [element …] 将所有元素参数添加到 HyperLogLog 数据结构中
127.0.0.1:6379> pfadd mykey a b b c d e f g h i j
(integer) 1

pfcount

语法 说明
pfcount key [key …] 返回给定 HyperLogLog 的基数估算值
127.0.0.1:6379> pfadd mykey a b b c d e f g h i j
(integer) 1
127.0.0.1:6379> pfcount mykey # 不重复的元素个数
(integer) 10

pfmerge

语法 说明
pfmerge destkey sourcekey [sourcekey …] 将多个 HyperLogLog 合并为一个 HyperLogLog
  • 合并后的 HyperLogLog 的基数估算值是通过对所有 给定 HyperLogLog 进行并集计算得出的。
127.0.0.1:6379> pfadd mykey a b c d e
(integer) 1
127.0.0.1:6379> pfadd mykey2 e f g h i j k
(integer) 1
127.0.0.1:6379> pfmerge mykey3 mykey mykey2
OK
127.0.0.1:6379> pfcount mykey3
(integer) 11

Bitmap

Redis提供的Bitmaps这个“数据结构”可以实现对位的操作。Bitmaps本身不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作。

可以把Bitmaps想象成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在bitmaps中叫做偏移量。单个bitmaps的最大长度是512MB,即2^32个比特位。

统计用户信息,例如统计活跃、不活跃!登录、未登录!打卡、未打卡!两个状态的,都可以使用Bitmps

Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态!

setbit

语法 说明
setbit key offset value 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit),返回指定偏移量原来储存的位

例如使用Bitmaps来记录周一到周日的打卡记录

127.0.0.1:6379> setbit sign 0 1     # 星期一打卡,记为1
(integer) 0
127.0.0.1:6379> setbit sign 1 0 # 星期二未打卡,记为0
(integer) 0
127.0.0.1:6379> setbit sign 2 0 # 星期三未打卡,记为0
(integer) 0
127.0.0.1:6379> setbit sign 3 1 # 星期四打卡,记为1
(integer) 0
127.0.0.1:6379> setbit sign 4 1 # 星期五打卡,记为1
(integer) 0
127.0.0.1:6379> setbit sign 5 1 # 星期六打卡,记为1
(integer) 0
127.0.0.1:6379> setbit sign 6 0 # 星期日未打卡,记为0
(integer) 0

getbit

语法 说明
getbit key offset 对 key 所储存的字符串值,获取指定偏移量上的位(bit)

查看某一天是否打卡

127.0.0.1:6379> getbit sign 0    # 获取星期一打的打卡记录,星期一打卡
(integer) 1
127.0.0.1:6379> getbit sign 1 # 获取星期二打的打卡记录,星期二未打卡
(integer) 0
127.0.0.1:6379> getbit sign 3 # 获取星期四打的打卡记录,星期四打卡
(integer) 1

bitcount

语法 说明
bitcount key [start end] 计算给定字符串中,被设置为 1的比特位的数量
127.0.0.1:6379> bitcount sign   # 统计值为1的数量,共计4天打卡
(integer) 4

Redis事务

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务(multi)
  • 命令入队(一系列命令)
  • 执行事务(exec)

Redis单条命令是保证原子性的,但是redis事务是不保证原子性的。

redis事务的本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。

一次性、顺序性、排他性!执行一系列的命令。

Redis的事务没有隔离级别的概念!所有命令在事务中,并没有被直接执行!只有发起执行命令exec时候才会执行!

Redis可以实现乐观锁!

multi

语法 说明
multi 标记一个事务块的开始
127.0.0.1:6379> multi    # 标记一个事务块的开始
OK

exec

语法 说明
exec 执行所有事务块内的命令
127.0.0.1:6379> multi               # 开始事务
OK
127.0.0.1:6379(TX)> set k1 v1 # set一个值k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2 # set一个值k2
QUEUED
127.0.0.1:6379(TX)> get k2 # get一个值k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3 # set一个值k3
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务,返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil
1) OK # 可以发现,入队的时候并没有被执行,而是执行exec命令时事务才被执行
2) OK # 这个事务已经结束
3) "v2"
4) OK

编译型异常(代码有问题!命令有错!),事务中的所有命令都不会被执行!

127.0.0.1:6379> multi            # 开始事务
OK
127.0.0.1:6379(TX)> set k1 v1 # set一个值k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2 # set一个值k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3 # set一个值k3
QUEUED
127.0.0.1:6379(TX)> getset k2 # getset这条命令执行错误(命令错误),正确应该是 getset key value
(error) ERR wrong number of arguments for 'getset' command # 返回一个错误
127.0.0.1:6379(TX)> set k4 v4 # set一个值k4
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务报错,所有命令都没有被执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4 # 事务没有被执行,k4没有被赋值
(nil)

运行时异常(比如1除以0 1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的!错误命令抛出异常

127.0.0.1:6379> set k1 "v1"        # set 一个 字符串
OK
127.0.0.1:6379> multi # 开始事务
OK
127.0.0.1:6379(TX)> incr k1 # incr加一,由于k1是一个字符串,所以这里不会加1,执行的时候失败
QUEUED
127.0.0.1:6379(TX)> set k2 v2 # set一个值k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3 # set一个值k3
QUEUED
127.0.0.1:6379(TX)> get k3 # get一个值k3
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务,按照顺序执行!虽然第一条命令报错了,但是依旧执行成功了
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2 # k2 设置成功
"v2"
127.0.0.1:6379> get k3 # k3 设置成功
"v3"

discard

语法 说明
discard 用于取消事务,放弃执行事务块内的所有命令。
127.0.0.1:6379> multi             # 开始事务
OK
127.0.0.1:6379(TX)> set k1 v1 # set一个值k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2 # set一个值k2
QUEUED
127.0.0.1:6379(TX)> set k4 v4 # set一个值k3
QUEUED
127.0.0.1:6379(TX)> discard # 取消事务,队列中的命令都不会被执行
OK
127.0.0.1:6379> get k4 # 由于事务被取消了,k4没有被set值,所以返回nil
(nil)

watch

语法 说明
watch 用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断

悲观锁:顾名思义,很悲观,什么时候都会出问题,无论做什么都加锁!

乐观锁:顾名思义,很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据。一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会+1。当线程A要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

127.0.0.1:6379> set money 100        # 有100块钱
OK
127.0.0.1:6379> set out 0 # 花掉了0元,此时还剩100元
OK
127.0.0.1:6379> watch money # 监视money对象
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> decrby money 20 # 花了100块钱,还剩80元
QUEUED
127.0.0.1:6379(TX)> incrby out 20 # 花掉的钱是20元
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务,事务正常结束,数据期间没有发生变动,正常执行成功!
1) (integer) 80
2) (integer) 20
# 第一个redis客户端窗口(第一个线程)
127.0.0.1:6379> watch money # 监视money对象
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> decrby money 10 # 花了10块钱,money减10元
QUEUED
127.0.0.1:6379(TX)> incrby out 10 # 花掉的钱,out加10元
QUEUED
127.0.0.1:6379(TX)> # 此时没有执行exec命令。而是新开了一个新的redis客户端窗口(另外一个线程)

127.0.0.1:6379(TX)> exec # 在第二个redis客户端窗口执行set money命令后再执行exec命令
(nil) # 监视失败,使用watch可以当做redis乐观锁操作



# 第二个redis客户端窗口(第二个线程)
127.0.0.1:6379> get money # 在第二个redis客户端窗口获取money值,为80
"80"
127.0.0.1:6379> set money 1000 # 在第二个redis客户端窗口修改了money值,改为了 1000
OK

unwatch

语法 说明
unwatch 取消 WATCH 命令对所有 key 的监视
127.0.0.1:6379> unwatch             # 如果发现事务执行失败,就先解锁
OK
127.0.0.1:6379> watch money # 获取最新的值,再次监视
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec # 对比监视的值是否发生了变化,如果没有变化那么可以执行成功,如果变化了,就执行失败
1) (integer) 80 # 如果修改失败,获取最新的值就好
2) (integer) 20

Jedis

jedis是redis的java版本的客户端实现

导入对应的依赖

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>

编码测试

public static void main(String[] args) {
Jedis jedis=new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping()); //PONG
jedis.set("name","zhangsan");
System.out.println(jedis.get("name"));//zhangsan
System.out.println(jedis.exists("name"));//true
}

通过jedis理解事务

public static void main(String[] args) {
Jedis jedis=new Jedis("127.0.0.1",6379);
Transaction multi = jedis.multi();
try {
multi.set("name","lisi");
multi.set("age","20");
//int i=1/0; //代码抛出异常,执行失败
multi.exec(); //执行事务
} catch (Exception e) {
multi.discard(); //放弃事务
e.printStackTrace();
} finally {
System.out.println(jedis.get("name"));
System.out.println(jedis.get("age"));
jedis.close();//关闭连接
}
}

Redis.cong详解

Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf

[root@quan ~]# cd /usr/local/bin/myconfig/
[root@quan myconfig]# ls
redis.conf
[root@quan myconfig]# vim redis.conf

config get

语法 说明
config get name 查看或设置配置项
127.0.0.1:6379> config get loglevel
1) "loglevel"
2) "notice"

使用 ***** 号获取所有配置项

127.0.0.1:6379> config get *
1) "rdbchecksum"
2) "yes"
3) "daemonize"
4) "yes"
5) "io-threads-do-reads"
6) "no"
7) "lua-replicate-commands"
8) "yes"
9) "always-show-logo"
10) "no"
11) "protected-mode"
12) "yes"
13) "rdbcompression"
14) "yes"
15) "rdb-del-sync-files"
16) "no"
17) "activerehashing"
18) "yes"
19) "stop-writes-on-bgsave-error"
20) "yes"
21) "set-proc-title"
22) "yes"
23) "dynamic-hz"
24) "yes"
25) "lazyfree-lazy-eviction"
26) "no"
27) "lazyfree-lazy-expire"
28) "no"
29) "lazyfree-lazy-server-del"
30) "no"
31) "lazyfree-lazy-user-del"
32) "no"
33) "lazyfree-lazy-user-flush"
34) "no"
35) "repl-disable-tcp-nodelay"
36) "no"
37) "repl-diskless-sync"
38) "no"
39) "gopher-enabled"
40) "no"
41) "aof-rewrite-incremental-fsync"
42) "yes"
43) "no-appendfsync-on-rewrite"
44) "no"
45) "cluster-require-full-coverage"
46) "yes"
47) "rdb-save-incremental-fsync"
48) "yes"
49) "aof-load-truncated"
50) "yes"
51) "aof-use-rdb-preamble"
52) "yes"
53) "cluster-replica-no-failover"
54) "no"
55) "cluster-slave-no-failover"
56) "no"
57) "replica-lazy-flush"
58) "no"
59) "slave-lazy-flush"
60) "no"
61) "replica-serve-stale-data"
62) "yes"
63) "slave-serve-stale-data"
64) "yes"
65) "replica-read-only"
66) "yes"
67) "slave-read-only"
68) "yes"
69) "replica-ignore-maxmemory"
70) "yes"
71) "slave-ignore-maxmemory"
72) "yes"
73) "jemalloc-bg-thread"
74) "yes"
75) "activedefrag"
76) "no"
77) "syslog-enabled"
78) "no"
79) "cluster-enabled"
80) "no"
81) "appendonly"
82) "no"
83) "cluster-allow-reads-when-down"
84) "no"
85) "crash-log-enabled"
86) "yes"
87) "crash-memcheck-enabled"
88) "yes"
89) "use-exit-on-panic"
90) "no"
91) "disable-thp"
92) "yes"
93) "aclfile"
94) ""
95) "unixsocket"
96) ""
97) "pidfile"
98) "/var/run/redis_6379.pid"
99) "replica-announce-ip"
100) ""
101) "slave-announce-ip"
102) ""
103) "masteruser"
104) ""
105) "cluster-announce-ip"
106) ""
107) "syslog-ident"
108) "redis"
109) "dbfilename"
110) "dump.rdb"
111) "appendfilename"
112) "appendonly.aof"
113) "server_cpulist"
114) ""
115) "bio_cpulist"
116) ""
117) "aof_rewrite_cpulist"
118) ""
119) "bgsave_cpulist"
120) ""
121) "ignore-warnings"
122) ""
123) "proc-title-template"
124) "{title} {listen-addr} {server-mode}"
125) "masterauth"
126) ""
127) "requirepass"
128) ""
129) "supervised"
130) "no"
131) "syslog-facility"
132) "local0"
133) "repl-diskless-load"
134) "disabled"
135) "loglevel"
136) "notice"
137) "maxmemory-policy"
138) "noeviction"
139) "appendfsync"
140) "everysec"
141) "oom-score-adj"
142) "no"
143) "acl-pubsub-default"
144) "allchannels"
145) "sanitize-dump-payload"
146) "no"
147) "databases"
148) "16"
149) "port"
150) "6379"
151) "io-threads"
152) "1"
153) "auto-aof-rewrite-percentage"
154) "100"
155) "cluster-replica-validity-factor"
156) "10"
157) "cluster-slave-validity-factor"
158) "10"
159) "list-max-ziplist-size"
160) "-2"
161) "tcp-keepalive"
162) "300"
163) "cluster-migration-barrier"
164) "1"
165) "active-defrag-cycle-min"
166) "1"
167) "active-defrag-cycle-max"
168) "25"
169) "active-defrag-threshold-lower"
170) "10"
171) "active-defrag-threshold-upper"
172) "100"
173) "lfu-log-factor"
174) "10"
175) "lfu-decay-time"
176) "1"
177) "replica-priority"
178) "100"
179) "slave-priority"
180) "100"
181) "repl-diskless-sync-delay"
182) "5"
183) "maxmemory-samples"
184) "5"
185) "maxmemory-eviction-tenacity"
186) "10"
187) "timeout"
188) "0"
189) "replica-announce-port"
190) "0"
191) "slave-announce-port"
192) "0"
193) "tcp-backlog"
194) "511"
195) "cluster-announce-bus-port"
196) "0"
197) "cluster-announce-port"
198) "0"
199) "repl-timeout"
200) "60"
201) "repl-ping-replica-period"
202) "10"
203) "repl-ping-slave-period"
204) "10"
205) "list-compress-depth"
206) "0"
207) "rdb-key-save-delay"
208) "0"
209) "key-load-delay"
210) "0"
211) "active-expire-effort"
212) "1"
213) "hz"
214) "10"
215) "min-replicas-to-write"
216) "0"
217) "min-slaves-to-write"
218) "0"
219) "min-replicas-max-lag"
220) "10"
221) "min-slaves-max-lag"
222) "10"
223) "maxclients"
224) "10000"
225) "active-defrag-max-scan-fields"
226) "1000"
227) "slowlog-max-len"
228) "128"
229) "acllog-max-len"
230) "128"
231) "lua-time-limit"
232) "5000"
233) "cluster-node-timeout"
234) "15000"
235) "slowlog-log-slower-than"
236) "10000"
237) "latency-monitor-threshold"
238) "0"
239) "proto-max-bulk-len"
240) "536870912"
241) "stream-node-max-entries"
242) "100"
243) "repl-backlog-size"
244) "1048576"
245) "maxmemory"
246) "0"
247) "hash-max-ziplist-entries"
248) "512"
249) "set-max-intset-entries"
250) "512"
251) "zset-max-ziplist-entries"
252) "128"
253) "active-defrag-ignore-bytes"
254) "104857600"
255) "hash-max-ziplist-value"
256) "64"
257) "stream-node-max-bytes"
258) "4096"
259) "zset-max-ziplist-value"
260) "64"
261) "hll-sparse-max-bytes"
262) "3000"
263) "tracking-table-max-keys"
264) "1000000"
265) "client-query-buffer-limit"
266) "1073741824"
267) "repl-backlog-ttl"
268) "3600"
269) "auto-aof-rewrite-min-size"
270) "67108864"
271) "logfile"
272) ""
273) "watchdog-period"
274) "0"
275) "dir"
276) "/root"
277) "save"
278) "3600 1 300 100 60 10000"
279) "client-output-buffer-limit"
280) "normal 0 0 0 slave 268435456 67108864 60 pubsub 33554432 8388608 60"
281) "unixsocketperm"
282) "0"
283) "slaveof"
284) ""
285) "notify-keyspace-events"
286) ""
287) "bind"
288) "127.0.0.1 -::1"
289) "oom-score-adj-values"
290) "0 200 800"

config set

语法 说明
config set name value 修改配置
127.0.0.1:6379> config set loglevel "notice"
OK
127.0.0.1:6379> config get loglevel
1) "loglevel"
2) "notice"

启动的时候,就是通过配置文件来启动!

单位,配置文件 unit 单位大小写不敏感

# ./redis-server /path/to/redis.conf

# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

INCLUDES 包含

# include /path/to/local.conf
# include /path/to/other.conf

NETWORK 网络

# Examples:
#
# bind 192.168.1.100 10.0.0.1 # listens on two specific IPv4 addresses
# bind 127.0.0.1 ::1 # listens on loopback IPv4 and IPv6
# bind * -::* # like the default, all available interfaces
bind 127.0.0.1 -::1 # 绑定的ip

protected-mode yes # 保护的模式

port 6379 # 端口

GENERAL 通用

daemonize yes   # 以守护进程的方式运行,默认是no,我们需要自己开启为yes

pidfile /var/run/redis_6379.pid # 如果以后台方式运行,我们就需要指定一个 pid 进程文件

# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境
# warning (only very important / critical messages are logged)
loglevel notice # 日志级别

logfile "" # 日志的文件位置名

databases 16 # 数据库的数量,默认是16个数据库

always-show-logo no # 是否总是显示logo

SNAPSHOTTING 快照

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb .aof,redis是内存数据库,如果没有持久化,那么数据断电即失!

# save 3600 1   # After 3600 seconds (an hour) if at least 1 key changed
# save 300 100 # After 300 seconds (5 minutes) if at least 100 keys changed
# save 60 10000 # After 60 seconds if at least 10000 keys changed

save 900 1 # 如果900秒内,如果至少有 1 个key进行了修改,我们将进行持久化操作
save 300 10 # 如果300秒内,如果至少有 10 个key进行了修改,我们将进行持久化操作
save 60 10000 # 如果60秒内,如果至少有 10000 个key进行了修改,我们将进行持久化操作

stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作

rdbcompression yes # 是否压缩rdb文件,需要消耗一些cpu资源

rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验

dir ./ # rdb文件保存的目录

SECURITY 安全

可以在这里设置redis的密码,默认是没有密码的!

127.0.0.1:6379> config get requirepass    # 获取redis密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" # 设置redis密码
OK
127.0.0.1:6379> auth 123456 # 使用密码进行登录
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"

CLIENTS 限制

maxclients 10000   # 设置能连接上redis的最大客户端数量

MEMORY MANAGEMENT

maxmemory <bytes>  # redis配置最大的内存容量

# volatile-lru -> 只对设置了过期时间的key进行LRU(默认值)
# allkeys-lru -> 删除lru算法的 key
# volatile-lfu ->
# allkeys-lfu ->
# volatile-random -> 随机删除即将过期的 key
# allkeys-random -> 随机删除
# volatile-ttl -> 删除即将过期的
# noeviction -> 永不过期,返回错误
maxmemory-policy noeviction # 内存到达上限之后的处理策略

APPEND ONLY MODE 模式 aof 配置

appendonly no # 默认是不开启aof模式的,默认是rdb方式持久化的,在大部分所有的情况下,rdb完全够用!

appendfilename "appendonly.aof" # 持久化的文件的名字

# appendfsync always # 每次修改都会写入同步 sync 。消耗性能
appendfsync everysec # 每秒执行一次同步 sync,可能会丢失这 1s 的数据
# appendfsync no # 不执行同步,这个时候操作系统自己同步数据,速度最快

RDB持久化

RDB持久化

RDB全称Redis DataBase

redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以redis提供了持久化功能。

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的 Snapshot 快照,它恢复时是将快照文件直接读到内存里。 Redis 会单独创建( fork )一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。 RDB 的缺点是最后一次持久化后的数据可能丢失。

我们默认就是RDB,一般情况下不需要修改这个配置。rdb保存的文件是 dump.rdb。

在配置文件redis.conf配置

# save 3600 1
# save 300 100
# save 60 10000

save 60 5 # 60秒内有一个值进行了修改,则进行持久化操作

dbfilename dump.rdb # rdb保存的文件名 dump.rdb
[root@quan ~]# cd /usr/local/bin/myconfig/
[root@quan myconfig]# vim redis.conf
save 60 5 # 60秒内有一个值进行了修改,则进行持久化操作【自己测试用的】
[root@quan myconfig]# cd /usr/local/bin
[root@quan bin]# ls
dump.rdb redis-benchmark redis-check-rdb redis-sentinel
myconfig redis-check-aof redis-cli redis-server
[root@quan bin]# rm -rf dump.rdb # 删除 dump.rdb
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
127.0.0.1:6379> set k4 v4
OK
127.0.0.1:6379> set k5 v5 # 60秒内修改了5个值,就会产生rdb.dump
OK
[root@quan bin]# cd myconfig/
[root@quan myconfig]# ls # 产生的dump.rdb文件在这里
dump.rdb redis.conf
[root@quan myconfig]# ls /root # root目录下也有一个dump.rdb
appendonly.aof dump.rdb yum.log

rdb文件触发机制,备份就会自动生成一个dump.rdb

  1. save的规则满足的情况下,会触发rdb规则
  2. 执行flushall命令,也会触发rdb规则
  3. 退出redis,也会产生rdb文件

怎么恢复rdb文件?

只需要将rdb文件放在redis启动目录即可,redis启动的时候会自动检查dump.rdb,恢复其中的数据。

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin/myconfig" # 查看需要存放的位置,如果在这个目录下存在dump.rdb,启动就会自动恢复其中的数据。

优点

  1. 适合大规模数据恢复
  2. 对数据的完整性要求不高

缺点

  1. 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了
  2. fork进程的时候,会占用一定的内存空间

AOF持久化

AOF全称 Append Only File

将我们所有的命令都记录下来,恢复的时候就会把这个文件全部执行一遍!

以日志的形式来记录每个写操作,将redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

aof保存的文件是 appendonly.aof

aof文件默认就是文件的无限追加,文件会越来越大!

redis.conf

appendonly no    # aof默认是不开启的,需要手动改为yes,重启redis就可以生效

appendfilename "appendonly.aof" # aof保存的文件名是 appendonly.aof

# appendfsync always
appendfsync everysec
# appendfsync no

no-appendfsync-on-rewrite no

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb #如果aof文件大于64mb,太大了,fork一个新的进程将文件重写

aof-load-truncated yes

aof-use-rdb-preamble yes
[root@quan ~]# cd /usr/local/bin/myconfig/
[root@quan myconfig]# vim redis.conf
appendonly yes # 开启aof
127.0.0.1:6379> shutdown # 修改配置文件许重启
not connected> exit
[root@quan ~]# ps -ef|grep redis
root 25039 24860 0 14:05 pts/0 00:00:00 grep --color=auto redis
[root@quan ~]# redis-server /usr/local/bin/myconfig/redis.conf
[root@quan ~]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "yes"
[root@quan myconfig]# find / -name appendonly.aof # 查找aof文件位置
/root/appendonly.aof
[root@quan myconfig]# cat /root/appendonly.aof # 查看aof文件内容
*2
$6
SELECT
$1
0
*3
$3
set
$2
k1
$2
v1
*3
$3
set
$2
k2
$2
v2
*2
$6
SELECT
$1
0
*3
$3
[root@quan ~]# redis-server /usr/local/bin/myconfig/redis.conf
[root@quan ~]# redis-cli -p 6379 # 如果不小心删除了aof文件内容,redis启动会报错,需要修复
Could not connect to Redis at 127.0.0.1:6379: Connection refused

如果aof文件有错,这时候redis是启动不起来的,我们需要修复这个aof文件

redis给我们提供看一个工具redis-check-aof --fix来修复

[root@quan bin]# ls
myconfig redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server
[root@quan bin]# redis-check-aof --fix /root/appendonly.aof # 使用redis-check-aof进行修复
'x 7d: Expected prefix '$', got: '
AOF analyzed: size=159, ok_up_to=104, diff=55
This will shrink the AOF from 159 bytes, with 55 bytes, to 104 bytes
Continue? [y/N]: y
Successfully truncated AOF
[root@quan ~]# redis-server /usr/local/bin/myconfig/redis.conf
[root@quan ~]# redis-cli -p 6379
127.0.0.1:6379> ping #启动成功
PONG

优点

  1. 每一次修改都同步,文件的完整会更好
  2. 每秒同步一次数据,可能会丢失一秒的数据
  3. 从不同步,效率最高的

缺点

  1. 相对于数据文件来说,aof文件远远大于rdb,修复的速度也比rdb慢
  2. aof的运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化

Redis发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

redis客户端可以定义任意数量的频道。

订阅/发布消息图:

第一个:消息发送者 第二个:频道 第三个:消息订阅者

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

subscribe

语法 说明
subscribe channel [channel …] 订阅给定的一个或多个频道的信息
127.0.0.1:6379> subscribe myChannel   # 订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "myChannel"
3) (integer) 1
1) "message" # 等待读取推送的信息
2) "myChannel"
3) "hello,world,nihao" # 这里接收下面发送的消息
1) "message"
2) "myChannel" # 哪个频道的消息
3) "hello,redis" # 消息内容

publish

语法 说明
publish channel message 用于将信息发送到指定的频道
127.0.0.1:6379> publish myChannel "hello,world,nihao"  # 发布者发布消息到频道
(integer) 1
127.0.0.1:6379> publish myChannel "hello,redis" # 发布者发布消息到频道
(integer) 1

Redis主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能有一个主节点。

主从复制的作用主要包括:

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。

  2. 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式

  3. 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作(即写redis数据时应用连接主节点,读redis数据时应用连接从节点),分担服务器的负载;尤其是写少读多的场景下,通过多个从节点分担负载,可以大大提高redis服务器的并发量。

  4. 高可用(集群)基石:除上述作用意外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是redis高可用的基础。

一般来说,要将 Redis 运用于工程项目中,只使用一台 Redis 是万万不能的(宕机),原因如下:

  1. 从结构上,单个 Redis 服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;
  2. 从容量上,单个 Redis 服务器内存容量有限,就算一台 Redis 服务器内存容量为 256G ,也不能将所有内存用作 Redis 存储内存。一般来说,单台 Redis 最大使用内存不应该超过 20G 。

电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是”多读少写”。

对于这种场景,我们可以使如下这种架构:

主从复制,读写分离,80%的情况下都是在进行读操作!减缓服务器压力,架构中经常使用。一主二从。

只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用 Redis

为什么使用集群

  1. 单台服务器难以负载大量的请求
  2. 单台服务器故障率高,系统崩坏概率大
  3. 单台服务器内存容量有限
[root@quan ~]# redis-server /usr/local/bin/myconfig/redis.conf 
[root@quan ~]# redis-cli -p 6379
127.0.0.1:6379> info replication # 查看当前库的信息
# Replication
role:master # 角色 master
connected_slaves:0 # 没有从机
master_failover_state:no-failover
master_replid:169fe236d591291c56ec23d87b251305a04c6868
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

集群搭建

因为只有一台服务器,所以需要编写3个配置文件,通过3个不同的配置文件来启动服务,搭建一个伪集群。

默认情况下,每台redis服务器都是主节点;我们一般情况下只用配置从机就好了;找老大!一主(79)二从(80,81)

从机配置:slave of

只配置从库,不用配置主库。

[root@quan ~]# cd /usr/local/bin/myconfig/
[root@quan myconfig]# ls
redis.conf
[root@quan myconfig]# cp redis.conf redis79.conf # 拷贝redis.conf redis79.conf模拟主机
[root@quan myconfig]# cp redis.conf redis80.conf # redis80.conf模拟从机
[root@quan myconfig]# cp redis.conf redis81.conf # redis81.conf模拟从机
[root@quan myconfig]# ls
redis79.conf redis80.conf redis81.conf redis.conf

第一个redis窗口:主机(redis79.conf)

[root@quan ~]# cd /usr/local/bin/myconfig/
[root@quan myconfig]# vim redis79.conf
port 6379 #端口
daemonize yes
pidfile /var/run/redis_6379.pid # pid名字
logfile "6379.log" # log文件名字
dbfilename dump6379.rdb # dump.rdb名字
[root@quan myconfig]# redis-server redis79.conf
[root@quan myconfig]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> info replication
# Replication
role:master # 默认角色主机 master
connected_slaves:0 # 没有从机
master_failover_state:no-failover
master_replid:46c717d739570dbd04aa91d28abe424500068e19
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2 # 该主机有2个从机
slave0:ip=127.0.0.1,port=6380,state=online,offset=378,lag=0 # 从机6380
slave1:ip=127.0.0.1,port=6381,state=online,offset=378,lag=0 # 从机6381
master_failover_state:no-failover
master_replid:e60babaf15a4dada473a82d261008f1e2c5309e1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:378
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:378


################################################################################
# 主机可以写,从机不能写只能读!主机中的所有信息和数据,都会被从机保存!
127.0.0.1:6379> set k1 v1 # 在主机中设置值,在从机中可以读到值。
OK
127.0.0.1:6379> get k1
"v1

第二个redis窗口:从机(redis80.conf)

[root@quan ~]# cd /usr/local/bin/myconfig/
[root@quan myconfig]# vim redis80.conf
port 6380
daemonize yes
pidfile /var/run/redis_6380.pid
logfile "6380.log"
dbfilename dump6380.rdb
[root@quan myconfig]# redis-server redis80.conf
[root@quan myconfig]# redis-cli -p 6380
127.0.0.1:6380> ping
PONG
127.0.0.1:6380> info replication
# Replication
role:master # 默认角色主机 master
connected_slaves:0 # 没有从机
master_failover_state:no-failover
master_replid:0e411f26920fdc8e1c626e02acc36477ae85f9e4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6380> slaveof 127.0.0.1 6379 # 配置从机,指定主机为 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave # 角色从主机变为从机 slave
master_host:127.0.0.1 # 主机ip
master_port:6379 # 主机端口
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_failover_state:no-failover
master_replid:e60babaf15a4dada473a82d261008f1e2c5309e1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14


################################################################################
# 主机可以写,从机不能写只能读!主机中的所有信息和数据,都会被从机保存!
127.0.0.1:6380> get k1 # 读取主机设置的值
"v1"
127.0.0.1:6380> set k2 v2 # 从机只能读,不能写
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380> set k2 v2 # 从机只能读取内容,不能写
(error) READONLY You can't write against a read only replica.

第三个redis窗口:从机(redis81.conf)

[root@quan ~]# cd /usr/local/bin/myconfig/
[root@quan myconfig]# vim redis80.conf
port 6381
daemonize yes
pidfile /var/run/redis_6381.pid
logfile "6381.log"
dbfilename dump6381.rdb
[root@quan myconfig]# redis-server redis81.conf
[root@quan myconfig]# redis-cli -p 6381
127.0.0.1:6381> ping
PONG
127.0.0.1:6381> info replication
# Replication
role:master # 默认角色主机 master
connected_slaves:0
master_failover_state:no-failover
master_replid:7486ac9fe45463bae0299d42294294628bea2572
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6381> slaveof 127.0.0.1 6379 # 配置从机,指定主机为 127.0.0.1 6379
OK
127.0.0.1:6381> info replication
# Replication
role:slave # 角色从主机变为从机 slave
master_host:127.0.0.1 # 主机ip
master_port:6379 # 主机端口
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_repl_offset:308
slave_priority:100
slave_read_only:1
connected_slaves:0
master_failover_state:no-failover
master_replid:e60babaf15a4dada473a82d261008f1e2c5309e1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:308
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:309
repl_backlog_histlen:0


################################################################################
# 主机可以写,从机不能写只能读!主机中的所有信息和数据,都会被从机保存!
127.0.0.1:6380> get k1 # 读取主机设置的值
"v1"
127.0.0.1:6380> set k2 v2 # 从机只能读取内容,不能写
(error) READONLY You can't write against a read only replica.

第四个redis窗口:测试

[root@quan ~]# ps -ef|grep redis
root 16831 1 0 20:46 ? 00:00:00 redis-server 127.0.0.1:6379
root 16844 15792 0 20:46 pts/0 00:00:00 redis-cli -p 6379
root 16871 1 0 20:47 ? 00:00:00 redis-server 127.0.0.1:6380
root 16885 16501 0 20:47 pts/1 00:00:00 redis-cli -p 6380
root 16903 1 0 20:48 ? 00:00:00 redis-server 127.0.0.1:6381
root 16913 16674 0 20:48 pts/2 00:00:00 redis-cli -p 6381
root 16992 16960 0 20:49 pts/3 00:00:00 grep --color=auto redis

注意:

主机断开连接(shutdown),从机依旧连接到主机的,但是没有写操作,这个时候,如果主机回来了,从机依旧可以读取到主机写的信息。

如果是使用命令行来配置的主从,这个时候如果从机断开连接后,就会变成主机!此时只要变为从机,立马就会从主机中获取值。

复制原理

slave 启动成功连接到 master 后会发送一个 sync 同步命令

Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, master 将传送整个数据文件到 slave ,并完成一次完全同步。

全量复制:而 slave 服务在接收到数据库文件数据后,将其存盘并加载到内存中。

增量复制: Master 继续将新的所有收集到的修改命令依次传给slave ,完成同步

但是只要是重新连接 master ,一次完全同步(全量复制)将被自动执行!我们的数据一定可以在从机中看到

真实的主从配置应该是在配置文件中配置,这样的话是永久的,我们这里使用的是命令,暂时的

配置redis.cofig配置位置如下:

################################# REPLICATION #################################

# Master-Replica replication. Use replicaof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
#
# +------------------+ +---------------+
# | Master | ---> | Replica |
# | (receive writes) | | (exact copy) |
# +------------------+ +---------------+
#
# 1) Redis replication is asynchronous, but you can configure a master to
# stop accepting writes if it appears to be not connected with at least
# a given number of replicas.
# 2) Redis replicas are able to perform a partial resynchronization with the
# master if the replication link is lost for a relatively small amount of
# time. You may want to configure the replication backlog size (see the next
# sections of this file) with a sensible value depending on your needs.
# 3) Replication is automatic and does not need user intervention. After a
# network partition replicas automatically try to reconnect to masters
# and resynchronize with them.
#
replicaof <masterip> <masterport> # 在这里配置主机ip和端口

宕机后手动配置主机

上一个主节点连接下一个从节点

如果主机断开连接,我们可以使用slave of one 让自己变成主机!其他的节点就可以手动连接到最新的这个主节点(手动)

如果这个时候老大修复了,那就重新连接!

第一个redis窗口:主机(redis79.conf)

[root@quan myconfig]# redis-server redis79.conf 
[root@quan myconfig]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master # 主机,下面有2个从机
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=111226,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=111226,lag=1
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1 # 6381指定主机为6380,6379从机只剩一个
slave0:ip=127.0.0.1,port=6380,state=online,offset=111660,lag=0
127.0.0.1:6379> set k6 v6
OK
127.0.0.1:6379> shutdown # 6379如果挂了,那么6380和6381就没有老大了
not connected> exit
[root@quan ~]# redis-server /usr/local/bin/myconfig/redis79.conf
[root@quan ~]# redis-cli -p 6379
127.0.0.1:6379> info replication # 6379重启后老大位置已经没了
# Replication
role:master
connected_slaves:0

第二个redis窗口:从机(redis80.conf)

[root@quan myconfig]# redis-server redis80.conf 
[root@quan myconfig]# redis-cli -p 6380
127.0.0.1:6380> info replication
# Replication
role:slave # 从机
master_host:127.0.0.1
master_port:6379
127.0.0.1:6380> info replication
# Replication
role:slave # 6380还是从机 6380同时是6381的主机,6379的从机
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:7
master_sync_in_progress:0
slave_repl_offset:111590
slave_priority:100
slave_read_only:1
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=111590,lag=1 # 从机6380同时也是6381的主机
127.0.0.1:6380> get k6
"v6"
127.0.0.1:6380> set k7 v7
(error) READONLY You can't write against a read only replica.

127.0.0.1:6380> slaveof no one # 6380自己当老大了
OK
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=112305,lag=1

第三个redis窗口:从机(redis81.conf)

[root@quan myconfig]# redis-server redis81.conf 
[root@quan myconfig]# redis-cli -p 6381
127.0.0.1:6381> info replication
# Replication
role:slave # 从机
master_host:127.0.0.1
master_port:6379
127.0.0.1:6381> slaveof 127.0.0.1 6380 # 配置从机为6380
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380 # 此时主机变为6380,6380也是从机,6380的主机是6379
127.0.0.1:6381> get k6
"v6"
127.0.0.1:6381> set k7 v7
(error) READONLY You can't write against a read only replica.


###################################
# 如果此时6379挂了,没有老大了,这个时候能不能选一个老大出来呢?手动!滑稽
127.0.0.1:6381> slaveof no one # 对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。
OK
127.0.0.1:6381> info replication # 6379挂了,老大没了,要自己当老大了
# Replication
role:master
connected_slaves:0



127.0.0.1:6381> slaveof 127.0.0.1 6380 # 继续指定主机为6380,6380此时是6379的从机,6380即是主机也是从机
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380

哨兵模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。

自动选举老大,能够监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

哨兵模式是一种特殊的模式,首先redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,他会独立运行,其原理是哨兵通过发送命令,等待redis服务器响应,从而监控运行的多个redis实例。

然而一个哨兵对redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行 failover 过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线.当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行 failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线

哨兵的作用:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

我们目前的状态是一主二从

  1. 配置哨兵配置文件 sentinel.conf

    # sentinel monitor 被监控的名字 host port 1    
    sentinel monitor myredis 127.0.0.1 6379 1

    后面的这个数字1代表主机挂了,slave投票看让谁接替成为主机,票数最多的,就会成为主机

  2. 启动哨兵

如果master节点断开了,这个时候就会从从机中随机选择一个服务器!(投票算法)

如果主机此时回来了,只能归并袄新的主机下,当做从机,这就是哨兵模式的规则。

哨兵模式的优点

  1. 哨兵集群,基于主从复制模式,所有主从配置的优点,它都有
  2. 主从可以切换,故障可以转移,系统的可用性更好
  3. 哨兵模式是主从模式的升级,手动到自动,更加健壮

哨兵模式的缺点

  1. Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
  2. 实现哨兵模式的配置其实是很麻烦的,里面有很多配置项

哨兵模式的全部配置

完整的哨兵模式配置文件 sentinel.conf

# Example sentinel.conf

# 哨兵sentinel实例运行的端口 默认26379
port 26379

# 哨兵sentinel的工作目录
dir /tmp

# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd


# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000

# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
#这个数字越小,完成failover所需的时间就越长,
#但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
#可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1



# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000

# SCRIPTS EXECUTION

#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。

#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh

# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

第一个redis窗口:主机(redis79.conf)

[root@quan myconfig]# redis-server redis79.conf 
[root@quan myconfig]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master # 主机,下面有2个从机
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=111226,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=111226,lag=1

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> shutdown # 主机挂了
not connected> exit
[root@quan ~]# redis-server /usr/local/bin/myconfig/redis79.conf
[root@quan ~]# redis-cli -p 6379
127.0.0.1:6379> info replication # 6379主机重新连接后就只能当6380的小弟了,老大没了
# Replication
role:slave
master_host:127.0.0.1
master_port:6380

第二个redis窗口:从机(redis80.conf)

[root@quan myconfig]# redis-server redis80.conf 
[root@quan myconfig]# redis-cli -p 6380
127.0.0.1:6380> info replication
# Replication
role:slave # 从机
master_host:127.0.0.1
master_port:6379

127.0.0.1:6380> info replication
# Replication
role:master # 6380被选为了主机
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=19485,lag=1

第三个redis窗口:从机(redis81.conf)

[root@quan myconfig]# redis-server redis81.conf 
[root@quan myconfig]# redis-cli -p 6381
127.0.0.1:6381> info replication
# Replication
role:slave # 从机
master_host:127.0.0.1
master_port:6379
127.0.0.1:6381> info replication
# Replication
role:slave # 主机变为了6380
master_host:127.0.0.1
master_port:6380

第三个redis窗口:测试

[root@quan ~]# cd /usr/local/bin/myconfig/
[root@quan myconfig]# vim sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1
[root@quan myconfig]# ll
total 636
-rw-r--r-- 1 root root 4064 Apr 13 19:14 6379.log
-rw-r--r-- 1 root root 141078 Apr 13 19:28 6380.log
-rw-r--r-- 1 root root 92207 Apr 13 19:28 6381.log
-rw-r--r-- 1 root root 111 Apr 13 19:28 appendonly.aof
-rw-r--r-- 1 root root 197 Apr 13 19:14 dump6379.rdb
-rw-r--r-- 1 root root 194 Apr 13 19:28 dump6380.rdb
-rw-r--r-- 1 root root 194 Apr 13 19:28 dump6381.rdb
-rw-r--r-- 1 root root 92 Apr 11 14:05 dump.rdb
-rw-r--r-- 1 root root 92232 Apr 12 20:40 redis79.conf
-rw-r--r-- 1 root root 92232 Apr 12 20:43 redis80.conf
-rw-r--r-- 1 root root 92232 Apr 12 20:45 redis81.conf
-rw-r--r-- 1 root root 92220 Apr 11 14:03 redis.conf
-rw-r--r-- 1 root root 42 Apr 13 19:50 sentinel.conf
[root@quan myconfig]# cd ..
[root@quan bin]# ls
myconfig redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server
[root@quan bin]# redis-sentinel myconfig/sentinel.conf # 启动sentinel
20268:X 13 Apr 2021 19:55:57.598 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
20268:X 13 Apr 2021 19:55:57.598 # Redis version=6.2.1, bits=64, commit=00000000, modified=0, pid=20268, just started
20268:X 13 Apr 2021 19:55:57.598 # Configuration loaded
20268:X 13 Apr 2021 19:55:57.599 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.1 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 20268
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'

20268:X 13 Apr 2021 19:55:57.599 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
20268:X 13 Apr 2021 19:55:57.604 # Sentinel ID is 6faff83add2c1e8f6ce7cbedac3f51cb7a0ad9fa
20268:X 13 Apr 2021 19:55:57.604 # +monitor master myredis 127.0.0.1 6379 quorum 1
20268:X 13 Apr 2021 19:55:57.605 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:55:57.610 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379

###############################################
# 此时如果主机6379挂了,哨兵投票选取主机
20268:X 13 Apr 2021 19:58:01.811 # +sdown master myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:01.811 # +odown master myredis 127.0.0.1 6379 #quorum 1/1
20268:X 13 Apr 2021 19:58:01.811 # +new-epoch 1
20268:X 13 Apr 2021 19:58:01.811 # +try-failover master myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:01.817 # +vote-for-leader 6faff83add2c1e8f6ce7cbedac3f51cb7a0ad9fa 1
20268:X 13 Apr 2021 19:58:01.817 # +elected-leader master myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:01.817 # +failover-state-select-slave master myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:01.883 # +selected-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:01.883 * +failover-state-send-slaveof-noone slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:01.983 * +failover-state-wait-promotion slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:02.041 # +promoted-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:02.041 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:02.112 * +slave-reconf-sent slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:03.056 * +slave-reconf-inprog slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:03.056 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:03.111 # +failover-end master myredis 127.0.0.1 6379
20268:X 13 Apr 2021 19:58:03.111 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6380
20268:X 13 Apr 2021 19:58:03.111 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6380
20268:X 13 Apr 2021 19:58:03.111 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
20268:X 13 Apr 2021 19:58:33.123 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
# 主机变为了6380

缓存穿透和雪崩

Redis 缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面.但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。

缓存穿透

概念

缓存穿透的概念很简单,用户想要查询一个数据,发现 redis 内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。查不到数据

解决方案

布隆过滤器

对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。

缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源!

但是这种方法会存在两个问题:

  1. 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
  2. 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

缓存击穿

概述

这里需要注意和缓存击穿的区别,缓存击穿,是指一个 key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

当某个 key 在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。量太大,缓存过期!

解决方案

设置热点数据永不过期

从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。

加互斥锁

分布式锁:使用分布式锁,保证对于每个 key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

缓存雪崩

概念

缓存雪崩,是指在某一个时间段,缓存集中过期失效。

产生雪崩的原因之一,比如在写本文的时候,马上就要到双十一零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

解决方案

redis高可用

这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。

限流降级

这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

数据预热

数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key ,设置不同的过期时间,让缓存失效的时间点尽量均匀。