共计 47916 个字符,预计需要花费 120 分钟才能阅读完成。
Redis 最全教程
1. Redis 装置
1.1 从官网下载安装包。
这里咱们以 Redis5.x 为例。下载好后应用 xftp 上传到你的服务器上或者你的虚拟机中。目录能够任意,但倡议把本人装置的软件放在 /opt
文件夹下。
应用命令 tar -zxvf redis-5.0.8.tar.gz
把下载的软件包解压,前面的要写你本人下载的包。
1.2 装置 Redis
进入到刚刚解压的 Redis 的文件夹。因为 Redis 是用 c 写的,所以要保障曾经装置 gcc 了。(注:最新版的 6.x 在应用 make 命令可能会报错,须要降级下 gcc 就好。)
在 Redis 的文件夹中应用命令进行装置。
make
make PREFIX=/usr/local/redis install
第二行 命令是指定把软件装置的地位,如果不指定默认是装置在 /usr/local/bin
目录中。
装置好之后咱们的 Redis 就装置在 /usr/local/redis
这个文件夹下了。装置好后,会发现没有 redis.conf 这个 Redis 的配置文件,这个配置文件在解压的时候的那个目录,把它拷贝到装置的目录中。这样每次启动的时候给他指定配置文件就好,而自带的配置文件不去动它。如果前期配置错了能够把这个删除而后从新拷贝过去。你不做这步复制,redis 也能失常启动,只是的会用一套默认配置。
Redis 装置之后的 bin 目录次要有一下的几个性能:
1.3 启动测试 Redis
首先批改一下拷贝过去的 redis.conf 文件。把后盾启动关上,默认是前台启动。
启动的时候,指定应用配置文件:# ./bin/redis-server ./redis.conf
呈现如下的就阐明启动胜利,否则会报错。
咱们能够应用 Redis 自带的 redis-cli 去连贯 Redis。应用命令 redis-cli -p 6379
留神须要指定端口。
而后如 ping,他会回一个 pong,阐明搭建是胜利的。
1.4 敞开 redis
暴力的形式是查问 redis 的过程号,而后应用 kill 命令。
正确的形式是,如果进入 redis-cli 能够如下操作:
如果没有进入,则间接应用命令敞开./bin/redis-cli shutdown
1.5 redis 的罕用设置
redis 的设置次要就是通过他的配置文件进行设置,目前次要能够设置如下几项:
-
后盾启动
# 下面曾经说了这里,把这个改成 yes 就能够了。daemonize yes
-
设置用户名明码
## 在默认的状况下,redis 是没有明码的,如果在测试的时候是没问题的,然而如果我的项目要共享进来或者是实在的我的项目,那么 ## 就会有安全隐患,所以这种状况下须要设置明码。## 设置的形式还是在配置文件中 redis.conf,找到 requirepass 标签,把它后面的 `#` 去掉,而后前面增加本人的明码即可 requirepass yourpassword ## 设置好之后重启 redis 即可,重启后如果不进行上面两种形式认证,会发现你没有权限在 redis 中进行任何操作 ## 应用 redis-cli 登录的时候能够增加参数 -a 前面增加明码(这种是明文,然而会给正告,不论他)## 第二种是依照原来的 redis-cli -p 6379 命令 登录,登录后 应用命令 `auth yourpassword` 进行验证(也是明文)
-
设置容许近程连贯
## bind 字段默认为:bind 127.0.0.1 这样只能本机拜访 redis ## 若容许近程主机拜访,可正文掉 bind 行 或者 将 bind 127.0.0.1 改为:bind 0.0.0.0
2.Redis 的五大根本数据类型
基本操作
127.0.0.1:12138> keys * # 查看所有的 key
(empty list or set)
127.0.0.1:12138> set user hello # 设置值
OK
127.0.0.1:12138> set age 18
OK
127.0.0.1:12138> EXISTS age # 判断键存不存在
(integer) 1
127.0.0.1:12138> move age 1 # 把键挪动到指定的数据库,redis 有 16 个库,默认应用第一个也就是 0 库
(integer) 1
127.0.0.1:12138> keys *
1) "user"
127.0.0.1:12138> get user # 依据 key 获取 value
"hello"
127.0.0.1:12138> type user # 判断以后 key 对应的 value 的类型
string
127.0.0.1:12138> EXPIRE user 10 # 设置 key 的过期工夫,单位是秒。- 1 示意没有过期工夫
(integer) 1
127.0.0.1:12138> TTL user # 查看 key 的剩余时间
(integer) 6
127.0.0.1:12138> TTL user
(integer) 4
127.0.0.1:12138> TTL user # 工夫为 - 2 示意已过期,key 曾经不存在
(integer) -2
127.0.0.1:12138> keys *
(empty list or set)
127.0.0.1:12138>
127.0.0.1:12138> select 1 # 切换到指定的数据库
OK
127.0.0.1:12138[1]> keys *
1) "age"
127.0.0.1:12138[1]> DBSIZE # 查看以后数据库的大小
(integer) 1
2.1 String(字符串)
#################################################################################################################
基本操作
127.0.0.1:12138> set k1 v1 # 设置值
OK
127.0.0.1:12138> get k1 # 获取值
"v1"
127.0.0.1:12138> keys * # 获取所有的 key
1) "k1"
127.0.0.1:12138> EXISTS k1 # 判断某个 key 是否存在
(integer) 1
127.0.0.1:12138> APPEND k1 "append" # 追加字符串,如果以后 key 不存在,就相当于 set key
(integer) 8
127.0.0.1:12138> get k1
"v1append"
127.0.0.1:12138> APPEND k2 "not exist"
(integer) 9
127.0.0.1:12138> keys *
1) "k1"
2) "k2"
127.0.0.1:12138> STRLEN k1 # 获取 key 对应的 value 的长度
(integer) 8
#################################################################################################################
# i++ i-- 自增,自减 以及指定步长
127.0.0.1:12138> set views 0 # 初始浏览量为 0
OK
127.0.0.1:12138> incr views # 自增一
(integer) 1
127.0.0.1:12138> incr views
(integer) 2
127.0.0.1:12138> get views
"2"
127.0.0.1:12138> decr views # 自减一
(integer) 1
127.0.0.1:12138> decr views
(integer) 0
127.0.0.1:12138> decr views
(integer) -1
127.0.0.1:12138> get views
"-1"
127.0.0.1:12138> INCRBY views 10 # 设置步长为 10
(integer) 9
127.0.0.1:12138> DECRBY views 15 # 设置步长为 15
(integer) -6
127.0.0.1:12138> get views
"-6"
#################################################################################################################
# 字符串范畴 range
127.0.0.1:12138> set key1 "you are good"
OK
127.0.0.1:12138> get key1
"you are good"
127.0.0.1:12138> GETRANGE key1 0 3 # 截取字符串 [0,3]
"you"
127.0.0.1:12138>
127.0.0.1:12138> GETRANGE key1 0 -1 # 获取全副的字符串 和 get key 是一样的
"you are good"
# 替换!127.0.0.1:12138> set key2 abcdefg
OK
127.0.0.1:12138> get key2
"abcdefg"
127.0.0.1:12138> SETRANGE key2 1 123 # 替换指定地位开始的字符串!(integer) 7
127.0.0.1:12138> get key2
"a123efg"
#################################################################################################################
# setex (set with expire) # 设置过期工夫
# setnx (set if not exist) # 不存在在设置(在分布式锁中会经常应用!)127.0.0.1:12138> setex key3 10 "hello" # 设置 key3 的值为 hello,10 秒后过期
OK
127.0.0.1:12138> ttl key3
(integer) 4
127.0.0.1:12138> ttl key3
(integer) 2
127.0.0.1:12138> ttl key3
(integer) -2
127.0.0.1:12138> get key3
(nil)
127.0.0.1:12138> setnx mykey "redis" # 如果 mykey 不存在,创立 mykey
(integer) 1
127.0.0.1:12138> setnx mykey "redisss" # 如果 mykey 存在,创立失败!不会批改之前的 value
(integer) 0
127.0.0.1:12138> get mykey
"redis"
#################################################################################################################
# mset mget 批量操作
127.0.0.1:12138> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:12138> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:12138> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:12138> mget k1 k2 k4 # 如果某个键不存在就返回 nil 空
1) "v1"
2) "v2"
3) (nil)
127.0.0.1:12138> msetnx k1 v1 k4 v4 # msetnx 是一个原子性的操作,要么一起胜利,要么一起失败!(integer) 0
127.0.0.1:12138> get k4
(nil)
127.0.0.1:12138>
#################################################################################################################
getset # 先 get 而后在 set
127.0.0.1:12138> getset db redis # 如果不存在值,则返回 nil,set 是会执行的
(nil)
127.0.0.1:12138> get db
"redis"
127.0.0.1:12138> getset db kafka # 如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:12138> get db
"kafka"
#################################################################################################################
2.2 List(列表)
在 redis 外面,咱们能够把 list 玩成,栈、队列、阻塞队列!音讯队列(Lpush Rpop),栈(Lpush Lpop)!
- 他实际上是一个链表,before Node after,left,right 都能够插入值
- 如果 key 不存在,创立新的链表
- 如果 key 存在,新增内容
- 如果移除了所有值,空链表,也代表不存在!
- 在两边插入或者改变值,效率最高!两头元素,相对来说效率会低一点~
#################################################################################################################
127.0.0.1:12138> LPUSH list one two # 将一个值或者多个值,插入到列表头部(左)(integer) 2
127.0.0.1:12138> LPUSH list three
(integer) 3
127.0.0.1:12138> LRANGE list 0 -1 # 获取 list 中值!1) "three"
2) "two"
3) "one"
127.0.0.1:12138> LRANGE list 0 1 # 通过区间获取具体的值!1) "three"
2) "two"
127.0.0.1:12138> RPUSH list right
(integer) 4
127.0.0.1:12138> LRANGE list 0 -1 # 将一个值或者多个值,插入到列表位部(右)1) "three"
2) "two"
3) "one"
4) "right"
#################################################################################################################
LPOP
RPOP
127.0.0.1:12138> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:12138> LPOP list # 移除 list 的第一个元素
"three"
127.0.0.1:12138> RPOP list # 移除 list 的最初一个元素
"right"
127.0.0.1:12138> LRANGE list 0 -1
1) "two"
2) "one"
#################################################################################################################
Lindex
127.0.0.1:12138> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:12138> LINDEX list 1 # 通过下标取得 list 中的某一个值!"one"
127.0.0.1:12138> LINDEX list 0
"two"
#################################################################################################################
LLEN
127.0.0.1:12138> LLEN list # 返回列表的长度
(integer) 2
#################################################################################################################
移除指定的值!Lrem
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 one # 移除 list 汇合中指定个数的 value,准确匹配
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 1 three
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> Lpush list three
(integer) 3
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
## list 外面 没有依据 index 去删除的,只有依据值删除的,如果想要删除指定 index,有这两种办法
### 办法一 先把想删的设置成本人的值,而后删除本人的值
lset mylist index "del"
lrem mylist 1 "del"
### 办法二 也能够用事务管道合并成一次申请
multi
lset mylist index "del"
lrem mylist 1 "del"
exec
#################################################################################################################
trim 修剪。;list 截断!
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> Rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> Rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 # 通过下标截取指定的长度,这个 list 曾经被扭转了,截断了只剩下截取的元素!OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello1"
2) "hello2"
#################################################################################################################
rpoplpush # 移除列表的最初一个元素,将他挪动到新的列表中!127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist # 移除列表的最初一个元素,将他挪动到新的列表中!"hello2"
127.0.0.1:6379> lrange mylist 0 -1 # 查看原来的列表
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1 # 查看指标列表中,的确存在改值!1) "hello2"
#################################################################################################################
lset 将列表中指定下标的值替换为另外一个值,更新操作
127.0.0.1:6379> EXISTS list # 判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item # 如果不存在列表咱们去更新就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 item # 如果存在,更新以后下标的值
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other # 如果不存在,则会报错!(error) ERR index out of range
#################################################################################################################
linsert # 将某个具体的 value 插入到列把你中某个元素的后面或者前面!127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "world"
(integer) 2
127.0.0.1:6379> LINSERT mylist before "world" "other"
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> LINSERT mylist after world new
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"
#################################################################################################################
2.3 Set(汇合)
set 中的值是不能反复的!
利用场景:
微博,A 用户将所有关注的人放在一个 set 汇合中!将它的粉丝也放在一个汇合中!
独特关注,共同爱好,二度好友,举荐好友!(六度宰割实践)
#################################################################################################################
127.0.0.1:12138> sadd myset are # set 汇合中增加 value
(integer) 1
127.0.0.1:12138> sadd myset you
(integer) 1
127.0.0.1:12138> sadd myset ok
(integer) 1
127.0.0.1:12138> SMEMBERS myset # 查看指定 set 的所有值
1) "you"
2) "are"
3) "ok"
127.0.0.1:12138> SISMEMBER myset leijun # 判断某一个值是不是在 set 汇合中!(integer) 0
127.0.0.1:12138> SISMEMBER myset are
(integer) 1
#################################################################################################################
127.0.0.1:12138> SCARD myset # 获取 set 汇合中的内容元素个数!(integer) 3
#################################################################################################################
删除元素 srem
127.0.0.1:6379> srem myset you # 移除 set 汇合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "are"
2) "ok"
#################################################################################################################
set 无序不反复汇合。抽随机!127.0.0.1:12138> sadd myset you
(integer) 1
127.0.0.1:12138> SMEMBERS myset
1) "you"
2) "are"
3) "ok"
127.0.0.1:12138> SRANDMEMBER myset # 随机抽选出一个元素
"ok"
127.0.0.1:12138> SRANDMEMBER myset
"ok"
127.0.0.1:12138> SRANDMEMBER myset
"ok"
127.0.0.1:12138> SRANDMEMBER myset
"are"
127.0.0.1:12138> SRANDMEMBER myset
"ok"
127.0.0.1:12138> SRANDMEMBER myset 2 # 随机抽选出指定个数的元素
1) "you"
2) "ok"
#################################################################################################################
随机删除 key!127.0.0.1:12138> SMEMBERS myset
1) "you"
2) "are"
3) "ok"
127.0.0.1:12138> spop myset # 随机删除一个 key
"you"
127.0.0.1:12138> spop myset
"are"
127.0.0.1:12138> SMEMBERS myset
1) "ok"
127.0.0.1:12138> spop myset 2 # 随机删除指定个数的 key,如果汇合中的个数不够就会有多少删除多少
1) "ok"
127.0.0.1:12138> SMEMBERS myset
(empty list or set)
#################################################################################################################
将一个指定的值,挪动到另外一个 set 汇合!127.0.0.1:12138> sadd myset are
(integer) 1
127.0.0.1:12138> sadd myset you
(integer) 1
127.0.0.1:12138> sadd myset ok
(integer) 1
127.0.0.1:12138> sadd myset leijun
(integer) 1
127.0.0.1:12138> smove myset newmyset leijun # 将一个指定的值,挪动到另外一个 set 汇合!(integer) 1
127.0.0.1:12138> SMEMBERS myset
1) "you"
2) "are"
3) "ok"
127.0.0.1:12138> SMEMBERS newmyset
1) "leijun"
#################################################################################################################
微博,B 站,独特关注!(并集)
数字汇合类:- 差集 SDIFF
- 交加
- 并集
127.0.0.1:12138> sadd k1 a b c d f e g
(integer) 7
127.0.0.1:12138> sadd k2 a b c d h i j k
(integer) 8
127.0.0.1:12138> SDIFF k1 k2 # 差集 绝对于 k1,k2 不存在的值
1) "f"
2) "e"
3) "g"
127.0.0.1:12138> SINTER k1 k2 # 交加
1) "c"
2) "b"
3) "d"
4) "a"
127.0.0.1:12138> SUNION k1 k2 # 并集
1) "j"
2) "c"
3) "g"
4) "h"
5) "e"
6) "i"
7) "f"
8) "d"
9) "b"
10) "k"
11) "a"
#################################################################################################################
2.4 Hash(哈希)
Map 汇合,key-map! 时候这个值是一个 map 汇合!实质和 String 类型没有太大区别,还是一个简略的
key-vlaue!
set myhash myfield myvaluehash 更适宜于对象的存储,String 更加适宜字符串存储!
#################################################################################################################
127.0.0.1:12138> hset myhash field1 hello # set 一个具体 key-vlaue
(integer) 1
127.0.0.1:12138> hget myhash field1 # 获取一个字段值
"hello"
127.0.0.1:12138> hset hash f1 v1 f2 v2 # 目前测试的 hset 也能够同时设置多个值,和 hmset 的惟一区别就是,他返回的是影响的条数,如果原来有则笼罩,没有则新增,而后者只会返回一个字符串 OK
(integer) 2
127.0.0.1:12138> hmset hash f3 v3 f4 v4 # set 多个 key-vlaue
OK
127.0.0.1:12138> HGETALL hash # 获取全副的数据
1) "f1"
2) "v1"
3) "f2"
4) "v2"
5) "f3"
6) "v3"
7) "f4"
8) "v4"
127.0.0.1:12138> hdel hash f1 # 删除 hash 指定 key 字段!对应的 value 值也就隐没了!(integer) 1
127.0.0.1:12138> hdel hash f2 f3 # 能够一次删除多个字段
(integer) 2
127.0.0.1:12138> hdel hash f2 # 删除不存在的会返回影响的条数
(integer) 0
127.0.0.1:12138> HGETALL hash
1) "f4"
2) "v4"
#################################################################################################################
hlen # 获取 hash 的字段数量
127.0.0.1:12138> HGETALL hash
1) "f4"
2) "v4"
127.0.0.1:12138> hset hash f1 v1 f2 v2
(integer) 2
127.0.0.1:12138> HGETALL hash
1) "f4"
2) "v4"
3) "f1"
4) "v1"
5) "f2"
6) "v2"
127.0.0.1:12138> hlen hash # 获取 hash 表的字段数量!(integer) 3
#################################################################################################################
127.0.0.1:12138> HEXISTS hash f5 # 判断 hash 中指定字段是否存在!(integer) 0
127.0.0.1:12138> HEXISTS hash f1
(integer) 1
#################################################################################################################
# 只取得所有 field
# 只取得所有 value
127.0.0.1:12138> hkeys hash # 只取得所有 field
1) "f4"
2) "f1"
3) "f2"
127.0.0.1:12138> HVALS hash # 只取得所有 value
1) "v4"
2) "v1"
3) "v2"
#################################################################################################################
# incr decr hash 中只有 HINCRBY 实现减少的 api,当然增量能够使负的
127.0.0.1:12138> hset mytest age 5
(integer) 1
127.0.0.1:12138> HINCRBY myset age 1 #指定增量!(integer) 1
127.0.0.1:12138> HINCRBY myset age 3
(integer) 4
127.0.0.1:12138> HINCRBY myset age -5 #指定增量!(integer) -1
127.0.0.1:12138> hget myset age
"-1"
#################################################################################################################
# HSETNX 不为空的时候设置
127.0.0.1:12138> HSETNX myset f6 v6 # 如果不存在则能够设置
(integer) 1
127.0.0.1:12138> HSETNX myset f6 v5 # 如果存在则不能设置
(integer) 0
#################################################################################################################
2.5 Zset(有序汇合)
在 set 的根底上,减少了一个值,set k1 v1 zset k1 score1 v1
利用场景:set 排序 存储班级成绩表,工资表排序!
一般音讯,1,重要音讯 2,带权重进行判断!
排行榜利用实现,取 Top N 测试!
#################################################################################################################
127.0.0.1:6379> zadd myset 1 one # 增加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three # 增加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
#################################################################################################################
排序如何实现
# zset 中也能够应用 zrange 进行排序, 和一般的 range 一样。(默认是从小到大,range 能指定索引,而 rangebyscore 能指定分数)127.0.0.1:6379> zadd salary 2500 xiaohong # 增加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 lisi
(integer) 1
# ZRANGEBYSCORE key min max
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示全副的用户 从小到大!1) "lisi"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1 # 从大到进行排序!1) "xiaohong"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 显示全副的用户并且附带成
绩
1) "lisi"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 显示工资小于 2500 员工的升
序排序!1) "lisi"
2) "500"
3) "xiaohong"
4) "2500"
#################################################################################################################
# 移除 rem 中的元素
127.0.0.1:6379> zrange salary 0 -1
1) "kaungshen"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong # 移除有序汇合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "kaungshen"
2) "zhangsan"
127.0.0.1:6379> zcard salary # 获取有序汇合中的个数
(integer) 2
#################################################################################################################
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 kuangshen
(integer) 2
127.0.0.1:6379> zcount myset 1 3 # 获取指定区间的成员数量!(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
#################################################################################################################
3.Redis 的三种非凡数据类型
3.1Geospatial 地理位置
敌人的定位,左近的人,打车间隔计算?
Redis 的 Geo 在 Redis3.2 版本就推出了!这个性能能够推算地理位置的信息,两地之间的间隔,方圆
几里的人!一共就 6 个命令
补充
# geo 没有删除命令,咱们能够应用 zrem 去删除,其实他底层应用的是 Zset!咱们能够应用 Zset 命令来操作 geo!当然 geo 的最底层还是跳跃链表
127.0.0.1:6379> ZRANGE china:city 0 -1 # 查看地图中全副的元素
1) "chongqi"
2) "xian"
3) "shengzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city beijing # 移除指定元素!这个罕用
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqi"
2) "xian"
3) "shengzhen"
4) "hangzhou"
5) "shanghai"
#################################################################################################################
# getadd 增加地理位置
# 命令:**GEOADD** key longitude latitude member [longitude latitude member ...]
# 命令形容:将指定的天文空间地位(经度、纬度、名称)增加到指定的 key 中。# 规定:北极和南极无奈间接增加,咱们个别会下载城市数据,间接通过 java 程序一次性导入!# 无效的经度从 -180 度到 180 度。# 无效的纬度从 -85.05112878 度到 85.05112878 度。# 当坐标地位超出上述指定范畴时,该命令将会返回一个谬误。# 127.0.0.1:6379> geoadd china:city 39.90 116.40 beijin
(error) ERR invalid longitude,latitude pair 39.900000,116.400000
# 参数 key 值()127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqi 114.05 22.52 shengzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
#################################################################################################################
# getpos 取得以后定位:肯定是一个坐标值!27.0.0.1:6379> GEOPOS china:city beijing # 获取指定的城市的经度和纬度!1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> GEOPOS china:city beijing chongqi
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
#################################################################################################################
# GEODIST 两个地位之间的间隔 单位:m 示意单位为米。km 示意单位为千米。mi 示意单位为英里。ft 示意单位为英尺
127.0.0.1:6379> GEODIST china:city beijing shanghai km # 查看上海到北京的直线间隔
"1067.3788"
127.0.0.1:6379> GEODIST china:city beijing chongqi km # 查看重庆到北京的直线间隔
"1464.0708"
#################################################################################################################
# georadius 以给定的经纬度为核心,找出某一半径内的元素
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km # 以 110,30 这个经纬度为核心,寻找方圆 1000km 内的城市
1) "chongqi"
2) "xian"
3) "shengzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqi"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist # 显示到两头间隔的地位
1) 1) "chongqi"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord # 显示别人的定位信息
1) 1) "chongqi"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1 #筛选出指定的后果!1) 1) "chongqi"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqi"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
#################################################################################################################
# GEORADIUSBYMEMBER 找出位于指定元素四周的其余元素!127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
#################################################################################################################
# GEOHASH 命令 - 返回一个或多个地位元素的 Geohash 示意
该命令将返回 11 个字符的 Geohash 字符串!
127.0.0.1:6379> geohash china:city beijing chongqi
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
#################################################################################################################
3.2 Hyperloglog
什么是基数?
A {1,3,5,7,8,7}
B{1,3,5,7,8}基数(不反复的元素)= 5,能够承受误差!
Redis Hyperloglog 基数统计的算法!(这个也能够去学习学习)
长处:占用的内存是固定,2^64 不同的元素的技术,只须要废占用 12KB 内存!如果要从内存角度来比拟的话 Hyperloglog 首选!
应用场景:网页的 UV(一个人拜访一个网站屡次,然而还是算作一个人!)
传统的形式,set 保留用户的 id,而后就能够统计 set 中的元素数量作为规范判断 !
这个形式如果保留大量的用户 id,就会比拟麻烦!咱们的目标是为了计数,而不是保留用户 id;
0.81% 错误率!统计 UV 工作,能够忽略不计的!如果容许容错,那么肯定能够应用 Hyperloglog!
如果不容许容错,就应用 set 或者本人的数据类型即可!
127.0.0.1:6379> PFadd mykey a b c d e f g h i j # 创立第一组元素 mykey
(integer) 1
127.0.0.1:6379> PFCOUNT mykey # 统计 mykey 元素的基数数量
(integer) 10
127.0.0.1:6379> PFadd mykey2 i j z x c v b n m # 创立第二组元素 mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 # 合并两组 mykey mykey2 => mykey3 并集
OK
127.0.0.1:6379> PFCOUNT mykey3 # 看并集的数量!(integer) 15
3.3 Bitmap 位存储
应用场景:
统计用户信息,沉闷,不沉闷!登录、未登录!打卡,365 打卡!两个状态的,都能够应用
Bitmaps!
Bitmap 位图,数据结构!都是操作二进制位来进行记录,就只有 0 和 1 两个状态!
365 天 = 365 bit 1 字节 = 8bit 46 个字节左右!
# 应用 bitmap 来记录 周一到周日的打卡!# 周一:1 周二:0 周三:0 周四:1 ......
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
127.0.0.1:6379> getbit sign 3 # 查看某一天是否有打卡!(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
127.0.0.1:6379> bitcount sign # 统计这周的打卡记录,就能够看到是否有全勤!(integer) 3
4.Redis 中的事务
事务简介
Redis 事务实质:一组命令的汇合!一个事务中的所有命令都会被序列化,在事务执行过程的中,会依照程序执行!一次性、程序性、排他性!执行一些列的命令!Redis 事务没有没有隔离级别的概念!所有的命令在事务中,并没有间接被执行!只有发动执行命令的时候才会执行!Exec
Redis 单条命令式保留原子性的,然而事务不保障原子性!redis 的事务:开启事务(multi)命令入队(......)执行事务(exec)
Redis 的事务操作
#################################################################################################################
# 失常执行事务!127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
#################################################################################################################
# 放弃事务!127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD # 勾销事务
OK
127.0.0.1:6379> get k4 # 事务队列中命令都不会被执行!(nil)
#################################################################################################################
# 编译型异样(代码有问题!命令有错!),事务中所有的命令都不会被执行!127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3 # 谬误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec # 执行事务报错!(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 所有的命令都不会被执行!(nil)
#################################################################################################################
# 运行时异样(1/0),如果事务队列中存在语法性,那么执行命令的时候,其余命令是能够失常执行的,谬误命令抛出异样!127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 # 会执行的时候失败!QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> 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
"v2"
127.0.0.1:6379> get k3
"v3"
#################################################################################################################
事务的乐观锁,不是另一个线程,是另一个客户端,对于乐观锁能够参考下这个:https://my.oschina.net/itommy…
https://www.cnblogs.com/marti…
证实:
127.0.0.1:12138> set money 100
OK
127.0.0.1:12138> set mon 0
OK
127.0.0.1:12138> watch money
OK
127.0.0.1:12138> incrby money 55
(integer) 155
127.0.0.1:12138> multi
OK
127.0.0.1:12138> decr money
QUEUED
127.0.0.1:12138> incr mon
QUEUED
127.0.0.1:12138> exec
(nil)
127.0.0.1:12138> get money
"155"
127.0.0.1:12138>
Redis 的监控机制(监控!Watch(面试常问!))
能够应用 Redis 的监控机制实现乐观锁或者分布式锁
测试:
#################################################################################################################
# 失常执行胜利!127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 监督 money 对象
OK
127.0.0.1:6379> multi # 事务失常完结,数据期间没有产生变动,这个时候就失常执行胜利!OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
#################################################################################################################
# 测试多线程批改值 , 应用 watch 能够当做 redis 的乐观锁操作!127.0.0.1:6379> watch money # 监督 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec # 执行之前,再关上一个客户端,批改了咱们的值,这个时候,就会导致事务执行失败!(nil)
#################################################################################################################
如果批改失败,获取最新的值就好
监控机制不会带来 ABA 问题
redis 是单线程的,在应用 watch 进行监控的时候,一旦批改就会被 watch,因为批改的时候就是那个线程,所以 redis 的 watch 不存在 aba 问题。应用 watch 监督一个或者多个 key,跟踪 key 的 value 批改状况,如果有 key 的 value 值在 事务 exec 执行之前被批改了,整个事务被勾销。exec 返回提示信息,示意事务曾经失败。然而如果应用 watch 监督了一个带过期工夫的键,那么即便这个键过期了,事务依然能够失常执行。
5.Java 中应用 Redis
5.1 通过 jedis 连贯 Redis
Java 操作 redis 最根底的就是用 jedis。Jedis 是 Redis 官网举荐的 java 连贯开发工具!应用 Java 操作 Redis 中间件!如果你要应用
java 操作 redis,那么肯定要对 Jedis 非常的相熟!
应用步骤:
-
导入相干的依赖
<!-- 导入 jedis 的包 --> <dependencies> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency> <!--fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> </dependencies>
- 编写测试代码
public class JedisDemo {public static void main(String[] args) { // 1、new Jedis 对象即可 Jedis jedis = new Jedis("www.njitzyd.com",12138); // 如果设置了明码须要进行验证 jedis.auth("zydredis"); // jedis 所有的命令就是咱们之前学习的所有指令!所以之前的指令学习很重要!System.out.println(jedis.ping()); } }
- 查看后果
能够看到连贯胜利!
Jedis 中罕用的 API
#################################################################################################################
# 所有的 api 命令,就是咱们对应的下面学习的指令,一个都没有变动!所有的都能够通过 Jedis 对象实现操作,和之前的命令行中的命令完全一致!String
List
Set
Hash
Zset
#################################################################################################################
# 事务 也是和之前在命令行中的统一
public class TestTX {public static void main(String[] args) {Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","leijun");
// 开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
// jedis.watch(result)
try {multi.set("user1",result);
multi.set("user2",result);
int i = 1/0 ; // 代码抛出异样事务,执行失败!multi.exec(); // 执行事务!} catch (Exception e) {multi.discard(); // 放弃事务
e.printStackTrace();} finally {System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); // 敞开连贯}
}
}
#################################################################################################################
5.2 SpringBoot 整合 Redis
在 SpringBoot 2.x 版本中,应用 lettuce 代替了 jedis 来操作 Redis。
jedis : 采纳的直连,多个线程操作的话,是不平安的,如果想要防止不平安的,应用 jedis pool 连贯
池!更像 BIO 模式
lettuce : 采纳 netty,实例能够再多个线程中进行共享,不存在线程不平安的状况!能够缩小线程数据
了,更像 NIO 模式
源码解析
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 咱们能够本人定义一个 redisTemplate 来替换这个默认的!public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory
redisConnectionFactory)
throws UnknownHostException {
// 默认的 RedisTemplate 没有过多的设置,redis 对象都是须要序列化!// 两个泛型都是 Object, Object 的类型,咱们后应用须要强制转换 <String, Object>
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean // 因为 String 是 redis 中最常应用的类型,所以说独自提出来了一
个 bean!public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory
redisConnectionFactory)
throws UnknownHostException {StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
SpringBoot 中应用案例
应用的步骤:
-
增加依赖(是 springboot 我的项目)
<!-- 操作 redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
配置连贯
在 spring.properties 中配置如下
# 配置 redis spring.redis.host=127.0.0.1 spring.redis.port=6379
-
测试
@SpringBootTest class Redis02SpringbootApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { // redisTemplate 操作不同的数据类型,api 和咱们的指令是一样的 // opsForValue 操作字符串 相似 String // opsForList 操作 List 相似 List // opsForSet // opsForHash // opsForZSet // opsForGeo // opsForHyperLogLog // 除了进本的操作,咱们罕用的办法都能够间接通过 redisTemplate 操作,比方事务,和根本的 CRUD // 获取 redis 的连贯对象 // RedisConnection connection =redisTemplate.getConnectionFactory().getConnection(); // connection.flushDb(); // connection.flushAll(); redisTemplate.opsForValue().set("mykey","myvalue"); System.out.println(redisTemplate.opsForValue().get("mykey")); } }
自定义 RedisTemplate
自带的 RedisTemplate 的问题:
- 默认的序列化形式是 jdk 自带的,当间接存入没有实现
Serializable
的类会间接报错序列化失败。 - 自带的两个泛型都是 Object,而咱们常常应用的是 key 为 string 类型。
- 咱们无奈指定序列化的形式,而理论开发中常常应用 fastjson 或者 Jackson 来实现序列化。
自定义如下,根本满足需要:
// 申明为一个配置类
@Configuration
public class RedisConfig {
// 本人定义了一个 RedisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
factory) {
// 咱们为了本人开发不便,个别间接应用 <String, Object>
RedisTemplate<String, Object> template = new RedisTemplate<String,
Object>();
template.setConnectionFactory(factory);
// Json 序列化配置
Jackson2JsonRedisSerializer 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);
// String 的序列化
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;
}
}
这样就能够实现自定义的 RedisTemplate,当咱们自定义时,零碎自带的就不会初始化。(springboot 的 starter 机制,在自带的 RedisTemplate 中有这个注解@ConditionalOnMissingBean(name = "redisTemplate")
)
自定义 redis 工具类
就是对下面自定义的 RedisTemplate 之后还能够再次封装,从而使得 redis 操作更不便。
// 在咱们实在的散发中,或者你们在公司,个别都能够看到一个公司本人封装 RedisUtil
@Component
public final class RedisUtil {
// 这里要留神,注入的是咱们刚刚自定义的 RedisTemplate,而不是官网默认的!@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存生效工夫
* @param key 键
* @param time 工夫(秒)
*/
public boolean expire(String key, long time) {
try {if (time > 0) {redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 依据 key 获取过期工夫
* @param key 键 不能为 null
* @return 工夫(秒) 返回 0 代表为永恒无效
*/
public long getExpire(String key) {return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断 key 是否存在
* @param key 键
* @return true 存在 false 不存在
*/
public boolean hasKey(String key) {
try {return redisTemplate.hasKey(key);
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 能够传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {if (key != null && key.length > 0) {if (key.length == 1) {redisTemplate.delete(key[0]);
} else {redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 一般缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 一般缓存放入
* @param key 键
* @param value 值
* @return true 胜利 false 失败
*/
public boolean set(String key, Object value) {
try {redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 一般缓存放入并设置工夫
* @param key 键
* @param value 值
* @param time 工夫(秒) time 要大于 0 如果 time 小于等于 0 将设置无限期
* @return true 胜利 false 失败
*/
public boolean set(String key, Object value, long time) {
try {if (time > 0) {redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {set(key, value);
}
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要减少几(大于 0)
*/
public long incr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递增因子必须大于 0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递加
* @param key 键
* @param delta 要缩小几(小于 0)
*/
public long decr(String key, long delta) {if (delta < 0) {throw new RuntimeException("递加因子必须大于 0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为 null
* @param item 项 不能为 null
*/
public Object hget(String key, String item) {return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取 hashKey 对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置工夫
* @param key 键
* @param map 对应多个键值
* @param time 工夫(秒)
* @return true 胜利 false 失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {expire(key, time);
}
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 向一张 hash 表中放入数据, 如果不存在将创立
* @param key 键
* @param item 项
* @param value 值
* @return true 胜利 false 失败
*/
public boolean hset(String key, String item, Object value) {
try {redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 向一张 hash 表中放入数据, 如果不存在将创立
* @param key 键
* @param item 项
* @param value 值
* @param time 工夫(秒) 留神: 如果已存在的 hash 表有工夫, 这里将会替换原有的工夫
* @return true 胜利 false 失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {expire(key, time);
}
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 删除 hash 表中的值
* @param key 键 不能为 null
* @param item 项 能够使多个 不能为 null
*/
public void hdel(String key, Object... item) {redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断 hash 表中是否有该项的值
* @param key 键 不能为 null
* @param item 项 不能为 null
* @return true 存在 false 不存在
*/
public boolean hHasKey(String key, String item) {return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash 递增 如果不存在, 就会创立一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要减少几(大于 0)
*/
public double hincr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash 递加
* @param key 键
* @param item 项
* @param by 要缩小记(小于 0)
*/
public double hdecr(String key, String item, double by) {return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 依据 key 获取 Set 中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {return redisTemplate.opsForSet().members(key);
} catch (Exception e) {e.printStackTrace();
return null;
}
}
/**
* 依据 value 从一个 set 中查问, 是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false 不存在
*/
public boolean sHasKey(String key, Object value) {
try {return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 将数据放入 set 缓存
*
* @param key 键
* @param values 值 能够是多个
* @return 胜利个数
*/
public long sSet(String key, Object... values) {
try {return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {e.printStackTrace();
return 0;
}
}
/**
* 将 set 数据放入缓存
* @param key 键
* @param time 工夫(秒)
* @param values 值 能够是多个
* @return 胜利个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {e.printStackTrace();
return 0;
}
}
/**
* 获取 set 缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {return redisTemplate.opsForSet().size(key);
} catch (Exception e) {e.printStackTrace();
return 0;
}
}
/**
* 移除值为 value 的
*
* @param key 键
* @param values 值 能够是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取 list 缓存的内容
*
* @param key 键
* @param start 开始
* @param end 完结 0 到 - 1 代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {e.printStackTrace();
return null;
}
}
/**
* 获取 list 缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {return redisTemplate.opsForList().size(key);
} catch (Exception e) {e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取 list 中的值
*
* @param key 键
* @param index 索引 index>= 0 时,0 表头,1 第二个元素,顺次类推;index<0 时,-1,表尾,- 2 倒数第二个元素,顺次类推
*/
public Object lGetIndex(String key, long index) {
try {return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {e.printStackTrace();
return null;
}
}
/**
* 将 list 放入缓存
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 将 list 放入缓存
* @param key 键
* @param value 值
* @param time 工夫(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 将 list 放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 将 list 放入缓存
*
* @param key 键
* @param value 值
* @param time 工夫(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 依据索引批改 list 中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {e.printStackTrace();
return false;
}
}
/**
* 移除 N 个值为 value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {e.printStackTrace();
return 0;
}
}
}
5.3 jedis 和 lu 的比照
6.Redis 的配置文件 redis.conf 详解
7.Redis 长久化
Redis 是内存数据库,如果不将内存中的数据库状态保留到磁盘,那么一旦服务器过程退出,服务器中
的数据库状态也会隐没。所以 Redis 提供了长久化性能!
7.1 RDB(Redis DataBase)
7.1.1 简介
在指定的工夫距离内将内存中的数据集快照写入磁盘,也就是行话讲的 Snapshot 快照,它复原时是将快照文件间接读到内存里。
Redis 会独自创立(fork)一个子过程来进行长久化,会先将数据写入到一个临时文件中,待长久化过程都完结了,再用这个临时文件替换上次长久化好的文件。整个过程中,主过程是不进行任何 IO 操作的。这就确保了极高的性能。如果须要进行大规模数据的复原,且对于数据恢复的完整性不是十分敏感,那 RDB 形式要比 AOF 形式更加的高效。RDB 的毛病是最初一次长久化后的数据可能失落。咱们默认的就是 RDB,个别状况下不须要批改这个配置!
rdb 保留的文件是默认的 dump.rdb 就是在咱们的配置文件中快照中进行配置的!具体配置就是下面所形容的 redis.conf 文件。
7.1.2 触发机制
默认的 save 的规定:
# 如果 900s 内,如果至多有一个 1 key 进行了批改,咱们及进行长久化操作
save 900 1
# 如果 300s 内,如果至多 10 key 进行了批改,咱们及进行长久化操作
save 300 10
# 如果 60s 内,如果至多 10000 key 进行了批改,咱们及进行长久化操作
save 60 10000
当满足上面的条件时就会触发生成 dump.rdb:
- save 的规定满足的状况下,会主动触发 rdb 规定(上述的规定是针对 bgsave 命令的,是对 bgsave 命令失效的)
- 当执行 save 或者 bgsave 命令的时候,会触发 rdb 生成 rdb 文件(save 和 bgsave 的区别)
- 执行 flushall 命令,也会触发咱们的 rdb 规定!
- 退出 redis(应用 shutdown 命令会触发,shutdown nosave 命令不会触发,应用 kill 命令强制退出也不会触发),也会产生 rdb 文件!
7.1.3 RDB 文件保留过程
- redis 调用 fork, 当初有了子过程和父过程。
- 父过程持续解决 client 申请,子过程负责将内存内容写入到临时文件。因为 os 的写时复制机制(copy on write)父子过程会共享雷同的物理页面,当父过程解决写申请时 os 会为父过程要批改的页面创立正本,而不是写共享的页面。所以子过程的地址空间内的数据是 fork 时刻整个数据库的一个快照。
- 当子过程将快照写入临时文件结束后,用临时文件替换原来的快照文件,而后子过程退出。
7.1.4 数据恢复
1、只须要将 rdb 文件放在咱们 redis 启动目录就能够,redis 启动的时候会主动查看 dump.rdb 复原其中的数据!(默认就是在这个地位)
2、查看须要存在的地位
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 如果在这个目录下存在 dump.rdb 文件,启动就会主动复原其中的数据
7.1.4 RDB 的优缺点
劣势
- 一旦采纳该形式,那么你的整个 Redis 数据库将只蕴含一个文件,这样十分不便进行备份。比方你可能打算没 1 天归档一些数据。
- 不便备份,咱们能够很容易的将一个一个 RDB 文件挪动到其余的存储介质上
- RDB 在复原大数据集时的速度比 AOF 的复原速度要快。
- RDB 能够最大化 Redis 的性能:父过程在保留 RDB 文件时惟一要做的就是 fork 出一个子过程,而后这个子过程就会解决接下来的所有保留工作,父过程毋庸执行任何磁盘 I/O 操作。
劣势
- 如果你须要尽量避免在服务器故障时失落数据,那么 RDB 不适宜你。尽管 Redis 容许你设置不同的保留点(save point)来管制保留 RDB 文件的频率,然而,因为 RDB 文件须要保留整个数据集的状态,所以它并不是一个轻松的操作。因而你可能会至多 5 分钟才保留一次 RDB 文件。在这种状况下,一旦产生故障停机,你就可能会失落好几分钟的数据。
- 每次保留 RDB 的时候,Redis 都要 fork() 出一个子过程,并由子过程来进行理论的长久化工作。在数据集比拟宏大时,fork() 可能会十分耗时,造成服务器在某某毫秒内进行解决客户端;如果数据集十分微小,并且 CPU 工夫十分缓和的话,那么这种进行工夫甚至可能会长达整整一秒。尽管 AOF 重写也须要进行 fork(),但无论 AOF 重写的执行距离有多长,数据的耐久性都不会有任何损失。
7.2 AOF(Append Only File)
将咱们的所有命令都记录下来,history,复原的时候就把这个文件全副在执行一遍!
以日志的模式来记录每个写操作,将 Redis 执行过的所有指令记录下来(读操作不记录),只许追加文件但不能够改写文件,redis 启动之初会读取该文件从新构建数据,换言之,redis 重启的话就依据日志文件的内容将写指令从前到后执行一次以实现数据的复原工作
Aof 保留的是 appendonly.aof 文件
默认是不开启的,咱们须要手动进行配置!咱们只须要将 appendonly 改为 yes 就开启了 aof!其余的默认就好,能够参考下面配置文件局部的解说。重启,redis 就能够失效了!
如果如果 aof 文件被毁坏,比方手动批改外面的内容,能够应用 redis-check-aof 来进行修复,具体命令是:redis-check-aof --fix appendonly.aof
(可能会把谬误的那条数据给删除,会造成失落数据。然而这种手动批改数据的场景不多见)
7.2.1 AOF 简介
redis 会将每一个收到的写命令都通过 write 函数追加到文件中(默认是 appendonly.aof)。
当 redis 重启时会通过从新执行文件中保留的写命令来在内存中重建整个数据库的内容。当然因为 os 会在内核中缓存 write 做的批改,所以可能不是立刻写到磁盘上。这样 aof 形式的长久化也还是有可能会失落局部批改。不过咱们能够通过配置文件通知 redis 咱们想要 通过 fsync 函数强制 os 写入到磁盘的机会。有三种形式如下(默认是:每秒 fsync 一次)
appendonly yes // 启用 aof 长久化形式
# appendfsync always // 每次收到写命令就立刻强制写入磁盘,最慢的,然而保障齐全的长久化,不举荐应用
appendfsync everysec // 每秒钟强制写入磁盘一次,在性能和长久化方面做了很好的折中,举荐
# appendfsync no // 齐全依赖 os,性能最好, 长久化没保障
7.2.2 AOF 的 rewrite 机制
aof 的形式也同时带来了另一个问题。长久化文件会变的越来越大。例如咱们调用 incr test 命令 100 次,文件中必须保留全副的 100 条命令,其实有 99 条都是多余的。因为要复原数据库的状态其实文件中保留一条 set test 100 就够了。
为了压缩 aof 的长久化文件。redis 提供了 bgrewriteaof 命令。收到此命令 redis 将应用与快照相似的形式将内存中的数据 以命令的形式保留到临时文件中,最初替换原来的文件。具体过程如下
- redis 调用 fork,当初有父子两个过程
- 子过程依据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
- 父过程持续解决 client 申请,除了把写命令写入到原来的 aof 文件中。同时把收到的写命令缓存起来。这样就能保障如果子过程重写失败的话并不会出问题。
- 当子过程把快照内容写入已命令形式写到临时文件中后,子过程发信号告诉父过程。而后父过程把缓存的写命令也写入到临时文件。
- 当初父过程能够应用临时文件替换老的 aof 文件,并重命名,前面收到的写命令也开始往新的 aof 文件中追加。
须要留神到是重写 aof 文件的操作,并没有读取旧的 aof 文件,而是将整个内存中的数据库内容用命令的形式重写了一个新的 aof 文件, 这点和快照有点相似。
7.2.3 AOF 的优缺点
劣势
- 应用 AOF 长久化会让 Redis 变得非常耐久(much more durable):你能够设置不同的 fsync 策略,比方无 fsync,每秒钟一次 fsync,或者每次执行写入命令时 fsync。AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 依然能够保持良好的性能,并且就算产生故障停机,也最多只会失落一秒钟的数据(fsync 会在后盾线程执行,所以主线程能够持续致力地解决命令申请)。
- AOF 文件是一个只进行追加操作的日志文件(append only log),因而对 AOF 文件的写入不须要进行 seek,即便日志因为某些起因而蕴含了未写入残缺的命令(比方写入时磁盘已满,写入中途停机,等等),redis-check-aof 工具也能够轻易地修复这种问题。
Redis 能够在 AOF 文件体积变得过大时,主动地在后盾对 AOF 进行重写:重写后的新 AOF 文件蕴含了复原以后数据集所需的最小命令汇合。整个重写操作是相对平安的,因为 Redis 在创立新 AOF 文件的过程中,会持续将命令追加到现有的 AOF 文件外面,即便重写过程中产生停机,现有的 AOF 文件也不会失落。而一旦新 AOF 文件创建结束,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。 - AOF 文件有序地保留了对数据库执行的所有写入操作,这些写入操作以 Redis 协定的格局保留,因而 AOF 文件的内容非常容易被人读懂,对文件进行剖析(parse)也很轻松。导出(export)AOF 文件也非常简单:举个例子,如果你不小心执行了 FLUSHALL 命令,但只有 AOF 文件未被重写,那么只有进行服务器,移除 AOF 文件开端的 FLUSHALL 命令,并重启 Redis,就能够将数据集复原到 FLUSHALL 执行之前的状态。
劣势
- 对于雷同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
- 因为 redis 是单线程的,每次 aof 文件同步写入都要等(能够参考上面 AOF 长久化详解文章中的
文件写入和同步
模块)。依据所应用的 fsync 策略,AOF 的速度可能会慢于 RDB。在个别状况下,每秒 fsync 的性能仍然十分高,而敞开 fsync 能够让 AOF 的速度和 RDB 一样快,即便在高负荷之下也是如此。不过在解决微小的写入载入时,RDB 能够提供更有保障的最大延迟时间(latency)。 - AOF 在过来已经产生过这样的 bug:因为个别命令的起因,导致 AOF 文件在从新载入时,无奈将数据集复原成保留时的原样。(举个例子,阻塞命令 BRPOPLPUSH 就已经引起过这样的 bug。)测试套件里为这种状况增加了测试:它们会主动生成随机的、简单的数据集,并通过从新载入这些数据来确保一切正常。尽管这种 bug 在 AOF 文件中并不常见,然而比照来说,RDB 简直是不可能呈现这种 bug 的。
7.3 两者比照
两者的开启并不是抵触的,如果都开启,零碎默认是优先加载 aof 的文件来进行复原。那个两种长久化形式如何敞开,能够参考上面的办法。RDB 和 AOF 的敞开办法
AOF 长久化详解
参考
7.4 扩大:
1、RDB 长久化形式可能在指定的工夫距离内对你的数据进行快照存储
2、AOF 长久化形式记录每次对服务器写的操作,当服务器重启的时候会从新执行这些命令来复原原始的数据,AOF 命令以 Redis 协定追加保留每次写的操作到文件开端,Redis 还能对 AOF 文件进行后盾重写,使得 AOF 文件的体积不至于过大。
3、只做缓存,如果你只心愿你的数据在服务器运行的时候存在,你也能够不应用任何长久化
4、同时开启两种长久化形式在这种状况下,当 redis 重启的时候会优先载入 AOF 文件来复原原始的数据,因为在通常状况下 AOF 文件保留的数据集要比 RDB 文件保留的数据集要残缺。RDB 的数据不实时,同时应用两者时服务器重启也只会找 AOF 文件,那要不要只应用 AOF 呢?作者倡议不要,因为 RDB 更适宜用于备份数据库(AOF 在一直变动不好备份),疾速重启,而且不会有 AOF 可能潜在的 Bug,留着作为一个万一的伎俩。
5、性能倡议
因为 RDB 文件只用作后备用处,倡议只在 Slave 上长久化 RDB 文件,而且只有 15 分钟备份一次就够了,只保留 save 900 1 这条规定。如果 Enable AOF,益处是在最顽劣状况下也只会失落不超过两秒数据,启动脚本较简略只 load 本人的 AOF 文件就能够了,代价一是带来了继续的 IO,二是 AOF rewrite 的最初将 rewrite 过程中产生的新数据写到新文件造成的阻塞简直是不可避免的。只有硬盘许可,应该尽量减少 AOF rewrite 的频率,AOF 重写的根底大小默认值 64M 太小了,能够设到 5G 以上,默认超过原大小 100% 大小重写能够改到适当的数值。如果不 Enable AOF,仅靠 Master-Slave Repllcation 实现高可用性也能够,能省掉一大笔 IO,也缩小了 rewrite 时带来的零碎稳定。代价是如果 Master/Slave 同时倒掉,会失落十几分钟的数据,启动脚本也要比拟两个 Master/Slave 中的 RDB 文件,载入较新的那个,微博就是这种架构。
8.Redis 公布订阅(临时简略理解)
公布订阅的命令
8.1 测试
订阅端:
127.0.0.1:6379> SUBSCRIBE mychannel # 订阅一个频道 mychannel
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "mychannel"
3) (integer) 1
# 期待读取推送的信息
1) "message" # 音讯
2) "mychannel" # 那个频道的音讯
3) "hello,channel" # 音讯的具体内容
1) "message"
2) "mychannel"
3) "hello,redis"
发送端:
127.0.0.1:6379> PUBLISH mychannel "hello,channel" # 发布者公布音讯到频道!(integer) 1
127.0.0.1:6379> PUBLISH mychannel "hello,redis" # 发布者公布音讯到频道!(integer) 1
8.2 原理
Redis 是应用 C 实现的,通过剖析 Redis 源码里的 pubsub.c 文件,理解公布和订阅机制的底层实现,籍此加深对 Redis 的了解。
Redis 通过 PUBLISH、SUBSCRIBE 和 PSUBSCRIBE 等命令实现公布和订阅性能。
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里保护了一个字典,字典的键就是一个个 频道!而字典的值则是一个链表,链表中保留了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的要害,就是将客户端增加到给定 channel 的订阅链表中。
通过 PUBLISH 命令向订阅者发送音讯,redis-server 会应用给定的频道作为键,在它所保护的 channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将音讯公布给所有订阅者。
Pub/Sub 从字面上了解就是公布(Publish)与订阅(Subscribe),在 Redis 中,你能够设定对某一个 key 值进行音讯公布及音讯订阅,当一个 key 值上进行了音讯公布后,所有订阅它的客户端都会收到相应的音讯。这一性能最显著的用法就是用作实时音讯零碎,比方一般的即时聊天,群聊等性能。
9.Redis 主从复制
9.1 概念
主从复制,是指将一台 Redis 服务器的数据,复制到其余的 Redis 服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。
Master 以写为主,Slave 以读为主。
能够应用命令 info replication
查看 redis 的相干信息如下:
# Replication
role:master
connected_slaves:0
master_replid:a63afb2b95c60ac0a9d20b5cb76d1505a54bac58
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
默认状况下,每台独立 Redis 服务器都是主节点;
且一个主节点能够有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用次要包含:
1、数据冗余:主从复制实现了数据的热备份,是长久化之外的一种数据冗余形式。
2、故障复原:当主节点呈现问题时,能够由从节点提供服务,实现疾速的故障复原;实际上是一种服务的冗余。
3、负载平衡:在主从复制的根底上,配合读写拆散,能够由主节点提供写服务,由从节点提供读服务(即写 Redis 数据时利用连贯主节点,读 Redis 数据时利用连贯从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,能够大大提高 Redis 服务器的并发量。
4、高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群可能施行的根底,因而说主从复制是 Redis 高可用的根底。
一般来说,要将 Redis 使用于工程项目中,只应用一台 Redis 是万万不能的(宕机),起因如下:
1、从构造上,单个 Redis 服务器会产生单点故障,并且一台服务器须要解决所有的申请负载,压力较大;
2、从容量上,单个 Redis 服务器内存容量无限,就算一台 Redis 服务器内存容量为 256G,也不能将所有内存用作 Redis 存储内存,一般来说,单台 Redis 最大应用内存不应该超过 20G。
电商网站上的商品,个别都是一次上传,无数次浏览的,说业余点也就是 ” 多读少写 ”。
<img src=”https://gitee.com/jsnucrh/blog-sharding_1/raw/master/img/20201219225402.png” alt=”image-20200929224316529″ style=”zoom:80%;” />
9.2 环境配置
如果资源比拟少,只有一台服务器的话,能够复制 3 个配置文件,而后批改对应的信息
1、端口
2、pid 名字
3、log 文件名字
4、dump.rdb 名字
批改结束之后,通过启动命令指定不同的配置文件实现主从复制。
daemonize yes
port 6379
pidfile /var/run/redis_6379.pid
logfile "redis_6379.log"
dbfilename 6379.rdb
9.3 一主二从
默认状况下,每台 Redis 服务器都是主节点;咱们个别状况下只用配置从机就好了!能够应用命令 SLAVEOF ip port
来实现主从,然而这样是长期的。如果要永恒失效就要在配置文件中配置,在 REPLICATION 下有个 slaveof,配置主机 和 端口 就好,如果主有明码就把明码也配置上就好了。
从机只能读不能写
应用 SLAVEOF 命令进行主从设置的时候,如果中途从机断了,而后从机再写入数据,那么再次进行 slaveof 的时候,从机独有的数据会被革除,也就是从机的数据在进行主从的时候会同步和主机的数据完全一致,不多不少。
9.4 哨兵模式
主从切换技术的办法是:当主服务器宕机后,须要手动把一台从服务器切换为主服务器,这就须要人工干预,麻烦费劲,还会造成一段时间内服务不可用。这不是一种举荐的形式,更多时候,咱们优先思考哨兵模式。Redis 从 2.8 开始正式提供了 Sentinel(哨兵)架构来解决这个问题。
哨兵模式可能后盾监控主机是否故障,如果故障了依据投票数主动将从库转换为主库。哨兵模式是一种非凡的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的过程,作为过程,它会独立运行。其原理是哨兵通过发送命令,期待 Redis 服务器响应,从而监控运行的多个 Redis 实例。
这里的哨兵有两个作用
- 通过发送命令,让 Redis 服务器返回监控其运行状态,包含主服务器和从服务器。
- 当哨兵监测到 master 宕机,会主动将 slave 切换成 master,而后通过公布订阅模式告诉其余的从服务器,批改配置文件,让它们切换主机。
然而一个哨兵过程对 Redis 服务器进行监控,可能会呈现问题,为此,咱们能够应用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就造成了多哨兵模式。
假如主服务器宕机,哨兵 1 先检测到这个后果,零碎并不会马上进行 failover 过程,仅仅是哨兵 1 主观的认为主服务器不可用,这个景象成为主观下线。当前面的哨兵也检测到主服务器不可用,并且数量达到肯定值时,那么哨兵之间就会进行一次投票,投票的后果由一个哨兵发动,进行 failover[故障转移]操作。切换胜利后,就会通过公布订阅模式,让各个哨兵把本人监控的从服务器实现切换主机,这个过程称为 主观下线。
9.5 哨兵模式的设置
-
配置哨兵配置文件 sentinel.conf
# sentinel monitor 被监控的名称 host port 1 # 哨兵模式的最初的一个 1 的意思是哨兵判断该节点多少次才算死亡,就是几个哨兵都失去他死了才算死(即几个哨兵认为他死了他才算死)sentinel monitor myredis 127.0.0.1 6379 1
- 指定本人的配置文件启动
./bin/redis-sentinel myconfig/sentinel.conf
- 察看日志即可
9.6 哨兵模式总结
长处:
1、哨兵集群,基于主从复制模式,所有的主从配置长处,它全有
2、主从能够切换,故障能够转移,零碎的可用性就会更好
3、哨兵模式就是主从模式的降级,手动到主动,更加强壮!
毛病:
1、Redis 不好啊在线扩容的,集群容量一旦达到下限,在线扩容就非常麻烦!
2、实现哨兵模式的配置其实是很麻烦的,外面有很多抉择!
哨兵模式的全副配置!
# Example sentinel.conf
# 哨兵 sentinel 实例运行的端口 默认 26379
port 26379
# 哨兵 sentinel 的工作目录
dir /tmp
# 哨兵 sentinel 监控的 redis 主节点的 ip port
# master-name 能够本人命名的主节点名字 只能由字母 A -z、数字 0 -9、这三个字符 ".-_" 组成。# 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 无奈失常启动胜利。#告诉脚本
# shell 编程
# 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 # 个别都是由运维来配置!
10.Redis 缓存击穿、穿透和雪崩
这部分内容能够看我之前的博客