Redis最全教程

Redis最全教程html

1. Redis安装

1.1 从官网下载安装包。

这里咱们以Redis5.x为例。下载好后使用xftp上传到你的服务器上或者你的虚拟机中。目录能够任意,但建议把本身安装的软件放在/opt文件夹下。java

使用命令tar -zxvf redis-5.0.8.tar.gz把下载的软件包解压,后面的要写你本身下载的包。git

1.2安装Redis

进入到刚刚解压的Redis的文件夹。由于Redis是用c写的,因此要保证已经安装gcc了。(注:最新版的6.x在使用make命令可能会报错,须要升级下gcc就好。)面试

在Redis的文件夹中使用命令进行安装。redis

make 
make PREFIX=/usr/local/redis install

第二行 命令是指定把软件安装的位置,若是不指定默认是安装在/usr/local/bin目录中。算法

安装好以后咱们的Redis就安装在/usr/local/redis这个文件夹下了。安装好后,会发现没有redis.conf这个Redis的配置文件,这个配置文件在解压的时候的那个目录,把它拷贝到安装的目录中。这样每次启动的时候给他指定配置文件就好,而自带的配置文件不去动它。若是后期配置错了能够把这个删除而后从新拷贝过来。你不作这步复制,redis也能正常启动,只是的会用一套默认配置。spring

Redis安装以后的bin目录主要有一下的几个功能:shell

img

1.3启动测试Redis

首先修改一下拷贝过来的redis.conf文件。把后台启动打开,默认是前台启动。数据库

image-20200919143554152

启动的时候,指定使用配置文件:# ./bin/redis-server ./redis.conf 编程

出现以下的就说明启动成功,不然会报错。image-20200919144940574

咱们可使用Redis自带的redis-cli去链接Redis。使用命令redis-cli -p 6379注意须要指定端口。

而后如ping,他会回一个pong,说明搭建是成功的。

image-20200919145512785

1.4 关闭redis

暴力的方式是查询redis的进程号,而后使用kill命令。

正确的方式是,若是进入redis-cli能够以下操做:

image-20200919145806875

若是没有进入,则直接使用命令关闭./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 myvalue

hash 更适合于对象的存储,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)
#################################################################################################################

若是修改失败,获取最新的值就好

image-20200926204958179

监控机制不会带来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) {
        // 一、 new Jedis 对象便可
            Jedis jedis = new Jedis("www.njitzyd.com",12138);
            // 若是设置了密码须要进行验证
            jedis.auth("zydredis");
         // jedis 全部的命令就是咱们以前学习的全部指令!因此以前的指令学习很重要!
            System.out.println(jedis.ping());
        }
    }
    • 查看结果

      image-20200926215422152

能够看到链接成功!

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的问题:

  1. 默认的序列化方式是jdk自带的,当直接存入没有实现Serializable的类会直接报错序列化失败。

    image-20200926225941986

  2. 自带的两个泛型都是Object,而咱们常用的是key为string类型。
  3. 咱们没法指定序列化的方式,而实际开发中常用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简介

image-20200928205610092

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

rdb保存的文件是默认的dump.rdb 就是在咱们的配置文件中快照中进行配置的!具体配置就是上面所描述的redis.conf文件。

7.1.2 触发机制

默认的save的规则:

image-20200928210407847

# 若是900s内,若是至少有一个1 key进行了修改,咱们及进行持久化操做
save 900 1
# 若是300s内,若是至少10 key进行了修改,咱们及进行持久化操做
save 300 10
# 若是60s内,若是至少10000 key进行了修改,咱们及进行持久化操做
save 60 10000

当知足下面的条件时就会触发生成dump.rdb:

  1. save的规则知足的状况下,会自动触发rdb规则(上述的规则是针对bgsave命令的,是对bgsave命令生效的)
  2. 当执行save或者bgsave命令的时候,会触发rdb生成rdb文件(save和bgsave的区别)
  3. 执行 flushall 命令,也会触发咱们的rdb规则!
  4. 退出redis(使用shutdown命令会触发,shutdown nosave 命令不会触发,使用kill命令强制退出也不会触发),也会产生 rdb 文件!

7.1.3 RDB文件保存过程

  • redis调用fork,如今有了子进程和父进程。
  • 父进程继续处理client请求,子进程负责将内存内容写入到临时文件。因为os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面建立副本,而不是写共享的页面。因此子进程的地址空间内的数据是fork时刻整个数据库的一个快照。
  • 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,而后子进程退出。

7.1.4数据恢复

一、只须要将rdb文件放在咱们redis启动目录就能够,redis启动的时候会自动检查dump.rdb 恢复其中的数据!(默认就是在这个位置)
二、查看须要存在的位置

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,恢复的时候就把这个文件所有在执行一遍!

image-20200928224944407

以日志的形式来记录每一个写操做,将Redis执行过的全部指令记录下来(读操做不记录),只许追加文件但不能够改写文件,redis启动之初会读取该文件从新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工做
Aof保存的是 appendonly.aof 文件

image-20200928225027558

默认是不开启的,咱们须要手动进行配置!咱们只须要将 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扩展:

一、RDB 持久化方式可以在指定的时间间隔内对你的数据进行快照存储
二、AOF 持久化方式记录每次对服务器写的操做,当服务器重启的时候会从新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操做到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
三、只作缓存,若是你只但愿你的数据在服务器运行的时候存在,你也能够不使用任何持久化
四、同时开启两种持久化方式在这种状况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,由于在一般状况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。RDB 的数据不实时,同时使用二者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?做者建议不要,由于RDB更适合用于备份数据库(AOF在不断变化很差备份),快速重启,并且不会有AOF可能潜在的Bug,留着做为一个万一的手段。
五、性能建议
由于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发布订阅(暂时简单了解)

发布订阅的命令

image-20200929214049296

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服务器都是主节点;
且一个主节点能够有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

主从复制的做用主要包括:
一、数据冗余:主从复制实现了数据的热备份,是持久化以外的一种数据冗余方式。
二、故障恢复:当主节点出现问题时,能够由从节点提供服务,实现快速的故障恢复;其实是一种服务的冗余。
三、负载均衡:在主从复制的基础上,配合读写分离,能够由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用链接主节点,读Redis数据时应用链接从节点),分担服务器负载;尤为是在写少读多的场景下,经过多个从节点分担读负载,能够大大提升Redis服务器的并发量。
四、高可用(集群)基石:除了上述做用之外,主从复制仍是哨兵和集群可以实施的基础,所以说主从复制是Redis高可用的基础。

通常来讲,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机),缘由以下:
一、从结构上,单个Redis服务器会发生单点故障,而且一台服务器须要处理全部的请求负载,压力较大;
二、从容量上,单个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个配置文件,而后修改对应的信息
一、端口
二、pid 名字
三、log文件名字
四、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实例。

img

这里的哨兵有两个做用

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

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

img

假设主服务器宕机,哨兵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 哨兵模式总结

优势:
一、哨兵集群,基于主从复制模式,全部的主从配置优势,它全有
二、 主从能够切换,故障能够转移,系统的可用性就会更好
三、哨兵模式就是主从模式的升级,手动到自动,更加健壮!
缺点:
一、Redis 很差啊在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦!
二、实现哨兵模式的配置实际上是很麻烦的,里面有不少选择!

哨兵模式的所有配置!

# 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缓存击穿、穿透和雪崩

这部份内容能够看我以前的博客

相关文章
相关标签/搜索