一. Redis是什么

Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库,其具备如下特性:

基于内存运行,性能高效
支持分布式,理论上可以无限扩展
key-value存储系统

相比于其他数据库类型,Redis具备的特点是:

C/S通讯模型
单进程单线程模型
丰富的数据类型
操作具有原子性
持久化
高并发读写
支持lua脚本

二、Redis的数据类型

Redis有五大基本类型,分别试String、List、Hash、Set、Zset,和geospatial、Hyperloglog、Bitmap三大特殊类型。下面对每一种类型进行讲解。

1. 基本命令

学习数据类型前,我们先学习一些基本命令
启动服务和客户端

#进入redis安装根目录,通过redis-server redis.conf启动服务
[root@iZ2zegjlrhr7fwvemhdbmoZ bin]# redis-server redis.conf
#通过redis-cli -p 6379启动客户端
[root@iZ2zegjlrhr7fwvemhdbmoZ bin]# redis-cli -p 6379

输入密码auth password

127.0.0.1:6379> auth 123456
OK

redis有16个数据库,默认为0号数据库,可以通过select index切换数据库

127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]>

flushdb:清空当前数据库;
flushall:清空16个数据库;
keys *:查询所有key

127.0.0.1:6379[1]> keys *
1) "name"
2) "age"
127.0.0.1:6379[1]> flushdb
OK
127.0.0.1:6379[1]> keys *
(empty array)

2. String类型

String类型通常用于存储字符串,但其实可以存取对象,只要将对象序列化即可。
基本命令:
set key value:设置值;
get key:获取值;

#新增一条key为name,value为zhangsan的数据
127.0.0.1:6379> set name zhangsan
OK
#通过key为name查询数据
127.0.0.1:6379> get name
"zhangsan"

exists key:判断key是否存在

127.0.0.1:6379> exists name #判断key为name的数据是否存在
(integer) 1

append key value:追加数据

#给可以key为name的数据追加value(666),如果key不存在,相当于set name 666;
127.0.0.1:6379> append name 666
(integer) 11
127.0.0.1:6379> get name
"zhangsan666"

strlen key:查询value的长度

127.0.0.1:6379> strlen name#获取数据长度
(integer) 11

incr key:每执行一次value+1;
decr key:每执行一次value-1;
incrby key num:每执行一次value+num;
decrby key num:每执行一次value-num;

127.0.0.1:6379> set views 0 #新增一个key为views,value为0的数据
OK
127.0.0.1:6379> incr views#命令每执行一次,views对应的value+1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> decr views#命令每执行一次,views对应的value-1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> incrby views 10#命令每执行一次,views对应的value+10
(integer) 10
127.0.0.1:6379> incrby views 10
(integer) 20
127.0.0.1:6379> decrby views 10#命令每执行一次,views对应的value-10
(integer) 10
127.0.0.1:6379> decrby views 10
(integer) 0

setex key num value:设置有效时间;
ttl key:查询剩余有效时间;

#设置一个key为name,value值为zhangsan的数据,有效时间为10秒
127.0.0.1:6379> setex name 10 zhangsan
OK
127.0.0.1:6379> ttl name //查看name所剩的有效时间
(integer) 5
127.0.0.1:6379> ttl name 
(integer) 4
127.0.0.1:6379> ttl name 
(integer) 3
127.0.0.1:6379> ttl name 
(integer) 1
127.0.0.1:6379> ttl name 
(integer) -2
127.0.0.1:6379> get name 
(nil)

setnx key value:key不存在,才执行。相当于SQL里的if not exists.

127.0.0.1:6379> keys * #查询所有的key
1) "name" #已存在key为name的数据
127.0.0.1:6379> setnx name zhangsan#不存在key为name的数据才执行
(integer) 0 #0行受影响,失败
127.0.0.1:6379> setnx name1 lisi
(integer) 1 #不存在key为name1的数据,所以设置成功
127.0.0.1:6379> get name
"liwz"
127.0.0.1:6379> get name1
"lisi"

getrange key start end :截取自字符串,从下标Start开始,到end结束
setrange key start value:替换字符串,从start开始,替换为value

127.0.0.1:6379> set name zhangsan666
OK
127.0.0.1:6379> get name
"zhangsan666"
#截取key为name的数据,从下标1开始,到下标3结束
127.0.0.1:6379> getrange name 1 3
"hang"
#替换key为name的数据的值,从下标1开始,替换为xx
127.0.0.1:6379> setrange name 1 xx
(integer) 7
127.0.0.1:6379> get name
"zxxngsan666"

mset key1 value1 key2 value2…:批量设置值;
mget key1 key2…:批量获取值;

127.0.0.1:6379> mset name1 zhangsan name2 lisi name3 wangwu
OK
127.0.0.1:6379> keys *
1) "name2"
2) "name3"
3) "name1"
127.0.0.1:6379> mget name1 name2 name3 #批量获取值
1) "zhangsan"
2) "lisi"
3) "wangwu"

msetnx key1 value1 key2 value2…:批量设置值,不存在才设置,此操作满足原子性,必须key全部不存在才能执行

127.0.0.1:6379> msetnx name1 zhangsan name4 zhaoliu//msetnx 不存在才批量设置,此命令符合原子性,必须都不存在才能添加成功
(integer) 0//已存在name1,所以不成功
127.0.0.1:6379> keys *
1) "name2"
2) "name3"
3) "name1"
127.0.0.1:6379> msetnx name4 zhaoliu name5 laohu
(integer) 1 //name4和name5都不存在,所以设置成功
127.0.0.1:6379> keys *
1) "name3"
2) "name2"
3) "name4"
4) "name1"
5) "name5"

getset key value:先get再set

127.0.0.1:6379> get name
"liwz"
127.0.0.1:6379> getset name zhangsan//getset 先get再set
"liwz"
127.0.0.1:6379> get name
"zhangsan"

2. List类型

此类型应用场景可用于关注粉丝
Lpush key value:向左边第一位放入一个数据;
Rpush key value: 向由边第一位放入一个数据;
Lrange key start end:从左边开始查看数据,从start开始,到end结束

#向左边第一个位置存放数据
127.0.0.1:6379> Lpush userlist zhangsan #(zhangsan)
(integer) 1
#向左边第一个位置存放数据,刚才的zhangsan就被推到了左边第二个的位置
127.0.0.1:6379> Lpush userlist lisi  #(lisi,zhangsan)
(integer) 2
127.0.0.1:6379> Lpush userlist wangwu #(wangwu,lisi,zhangsan)
(integer) 3
#从左边开始查看数据,从0开始到-1结束,意为查询所有
127.0.0.1:6379> Lrange userlist 0 -1 
1) "wangwu"
2) "lisi"
3) "zhangsan"
#向右边第一个存放数据
127.0.0.1:6379> Rpush userlist zhaoliu 
#(wangwu,lisi,zhangsan,zhaoliu)
(integer) 4
127.0.0.1:6379> Lrange userlist 0 -1
1) "wangwu"
2) "lisi"
3) "zhangsan"
4) "zhaoliu"

Lpop key:移除左边第一个元素
Rpop key:移除右边第一个元素

127.0.0.1:6379> Lpop userlist
"wangwu"
127.0.0.1:6379> Rpop userlist
"zhaoliu"
127.0.0.1:6379> lrange userlist 0 -1
1) "lisi"
2) "zhangsan"

lindex key index:从左边开始按下标取元素;

127.0.0.1:6379> lindex userlist 0
"lisi"

llen key:list的长度;

127.0.0.1:6379> Lpush list1 one
(integer) 1
127.0.0.1:6379> Lpush list1 two
(integer) 2
127.0.0.1:6379> Lpush list1 three
(integer) 3
127.0.0.1:6379> llen list1
(integer) 3

lrem key num value:从左边开始按内容移除元素,num为移除的个数

127.0.0.1:6379>lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrem list 1 one  #移除list1中的1个one
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"

ltrim key start end:移除key中的元素,除了下标start到end的

127.0.0.1:6379> lrange list1 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> ltrim list1 1 2 #截取list1的数据,只留下从下标1到下标2的数据
OK
127.0.0.1:6379> lrange list1 0 -1
1) "three"
2) "two"

rpoplpush key1 key2:移除key1右边第一个元素,并把这个元素插入到key2中

127.0.0.1:6379> lrange list1 0 -1//list1有四个元素
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> rpoplpush list1 list2//移除右边第一个,并且放入list2
"one"
127.0.0.1:6379> lrange list1 0 -1
1) "four"
2) "three"
3) "two"
127.0.0.1:6379> lrange list2 0 -1
1) "one"

lset key index value:修改指定下标的值
需要注意,key或者index不存在时,执行会报错。

127.0.0.1:6379> lPush list lisi
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "lisi"
127.0.0.1:6379> lset list 0 zhangsan #把下标为0的数据改为zhangsan
OK
127.0.0.1:6379> lrange list 0 -1
1) "zhangsan"

linsert key before|after value1 value2:在value1前或后添加value2。

127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> linsert list before two oneone #在list中的two前面加上Oneone
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "oneone"
3) "two"
4) "one"
127.0.0.1:6379> linsert list after two twotwo #在list中的two后面加上oneone
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "oneone"
3) "two"
4) "twotwo"
5) "one"

3. Set

sadd key value:新增Set元素
smembers key:查看Set元素
scard key :查看set元素大小

127.0.0.1:6379> sadd myset one
(integer) 1
127.0.0.1:6379> smembers myset  #查看Set元素
1) "one"
127.0.0.1:6379> scard myset #查看Set元素大小
(integer) 1

sismember key value :判断Set中是否存在某元素
srem key value:移除元素

127.0.0.1:6379> sadd mySet one two three
(integer) 3
127.0.0.1:6379> smembers mySet
1) "two"
2) "three"
3) "one"
127.0.0.1:6379> sismember mySet one
(integer) 1
127.0.0.1:6379> sismember mySet four
(integer) 0
127.0.0.1:6379> srem mySet three #移除three元素
(integer) 1
127.0.0.1:6379> smembers mySet
1) "two"
2) "one"

srandmember key num:随机抽取num个元素,num不写,代表1个。

127.0.0.1:6379> smembers mySet
1) "two"
2) "three"
3) "one"
4) "four"
127.0.0.1:6379> srandmember mySet 
"four"
127.0.0.1:6379> srandmember mySet 
"three"
127.0.0.1:6379> srandmember mySet 
"two"
127.0.0.1:6379> srandmember mySet 
"four"
127.0.0.1:6379> srandmember mySet  2
1) "three"
2) "one"

spop key :随机删除一个元素

127.0.0.1:6379> smembers mySet
1) "two"
2) "three"
3) "one"
4) "four"
127.0.0.1:6379> spop mySet
"one"
127.0.0.1:6379> smembers mySet
1) "two"
2) "three"
3) "four"

smove key1 key2 value:将value从key1移动到key2。

127.0.0.1:6379> smembers mySet
1) "two"
2) "three"
3) "four"
127.0.0.1:6379> smembers mySet2
1) "nihao"
127.0.0.1:6379> smove mySet mySet2 two
(integer) 1
127.0.0.1:6379> smembers mySet
1) "three"
2) "four"
127.0.0.1:6379> smembers mySet2
1) "two"
2) "nihao"

求差集、交集、并集
sdiff key1 key2
sinter key1 key2
sunion key1 key2

127.0.0.1:6379> smembers set1
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> smembers set2
1) "d"
2) "e"
3) "c"
127.0.0.1:6379> sdiff set1 set2
1) "a"
2) "b"
127.0.0.1:6379> sinter set1 set2
1) "c"
127.0.0.1:6379> sunion set1 set2
1) "d"
2) "c"
3) "b"
4) "a"
5) "e"

4. Hash

Hash和String存放方式相似,都是Key-value,只不过它的Value又是一个key-value。类似Map<key,Map<key,value>>,所以和String类型命令也基本相同

hset Key column value
hget Key column
hmset Key column1 value1 column2 value2…:批量设置值
hmget Key column1 column2…:批量获取值
代码解释

127.0.0.1:6379> hset user name 
(integer) 1
127.0.0.1:6379> hget user name
"zhangsan"
127.0.0.1:6379> hmset user email 999@qq.com sex 1
OK
127.0.0.1:6379> hmget user name age email sex
1) "zhangsan"
2) "999@qq.com"
3) "1"

hgetall key:查询所有属性
hkeys key:查询所有key
hvals key:查询所有value
hlen key:查看大小

127.0.0.1:6379> hgetall user
1) "name"
2) "zhangsan"
3) "email"
4) "999@qq.com"
5) "sex"
6) "1"
127.0.0.1:6379> hlen user
(integer) 3
127.0.0.1:6379> hkeys user #查询所有的key
1) "name"
2) "email"
3) "sex"
127.0.0.1:6379> hvals user #查询所有的value
1) "zhangsan"
2) "999@qq.com"
3) "1"

hexists key column:判断column是否存在

127.0.0.1:6379> hexists user name
(integer) 1
127.0.0.1:6379> hexists user name1
(integer) 0

hincrby key column num column自增num

5. ZSet

Zset与String不同的是会有一项Score,可以利用它来排序。
zadd key score vlue :增加
zrange key scoreStart scoreEnd:查看,利用score设置范围。

127.0.0.1:6379> zadd salary 4000 zhangsan//插入值
(integer) 1
127.0.0.1:6379> zadd salary 5000 lisi
(integer) 1
127.0.0.1:6379> zadd salary 500 liwz
(integer) 1
127.0.0.1:6379> zrange salary 0 -1//查看所有的值
1) "liwz"
2) "zhangsan"
3) "lisi"

zrangebyscore key scoreStart scoreEnd 利用score,从start至从小到大排序
zrangebyscore key scoreStart scoreEnd withscores:利用score,从start至从小到大排序并带出score。

127.0.0.1:6379> zrangebyscore salary -inf +inf//从小到大排序
1) "liwz"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores//从小到大排序,并带出值
1) "liwz"
2) "500"
3) "zhangsan"
4) "4000"
5) "lisi"
6) "5000"

Zrevrange key start end:从大到小排序

127.0.0.1:6379> Zrevrange salary 0 -1//从大到小排序
1) "lisi"
2) "zhangsan"
3) "liwz"

zcard key:查看总数
zcount salary start end:查看指定区间的个数

127.0.0.1:6379> zcard salary//查看总数
(integer) 3
127.0.0.1:6379> zcount salary 400 5000//获取指定区间的个数
(integer) 3

三、三大特殊类型

1. geospatial(地理位置)

geoadd key 经度 纬度 name :增加

127.0.0.1:6379> geoadd china:city 116.408 39.904 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.445 31.213 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 108.969 34.285 xian
(integer) 1
127.0.0.1:6379> keys *
1) "china:city"
127.0.0.1:6379> zrange china:city 0 -1 #List一样读取
1) "xian"
2) "shanghai"
3) "beijing"

geodist key city1 city2 m|km|fm :查看key中的两地距离

#单位默认是米
127.0.0.1:6379> geodist china:city beijing shanghai
#指定为千米
"1068232.0171"
127.0.0.1:6379> geodist china:city beijing shanghai km
"1068.2320"
127.0.0.1:6379> geodist china:city beijing shanghai m
"1068232.0171"

geohash key name:获取位置的hash值

127.0.0.1:6379> geohash china:city xian
1) "wqj7p9ku9e0"

geopos key name:查看name的经纬度

127.0.0.1:6379> geopos china:city xian//查询city的经纬度
1) 1) "108.96899789571762085"
   2) "34.28499959898385896"

georadius key 经度 纬度 count km:以经纬度为中心,查找key中count Km内的元素。

127.0.0.1:6379> georadius china:city 100 30 1500 km//查经度100,纬度30为中心,方圆1500千米的city
1) "xian"
127.0.0.1:6379> georadius china:city 100 30 4500 km//查经度100,纬度30为中心,方圆4500千米的city
1) "xian"
2) "shanghai"
3) "beijing"

georadiusbymember key name num m|km :与前一个有所不同,是用key中的元素为中心,搜索在nummi或千米内的元素。

127.0.0.1:6379> georadiusbymember china:city xian 1000 km//以元素xian为中心,查询方圆1000千米的元素
1) "xian"
2) "beijing"
127.0.0.1:6379> georadiusbymember china:city xian 2000 km//以元素xian为中心,查询方圆2000千米的元素
1) "xian"
2) "shanghai"
3) "beijing"

2. Hyperloglog基数存储

应用场景:求基数,不重复的数
pfadd key value1 value2 value3…:增加
pfmerge key1 key2:合并基数

#mylog里面存放abcdef
127.0.0.1:6379> pfadd mylog a b c d e f
(integer) 1
#mylog2里面存放cdefgh
127.0.0.1:6379> pfadd mylog2  c d e f g h
(integer) 1
#将两者合并放入mylog,不重复的数
127.0.0.1:6379> pfmerge mylog mylog2
OK
#计数,两者合并不重复的数有abcdefgh八位
127.0.0.1:6379> pfcount mylog
(integer) 8

3. Bitmap位存储

位存储:只有0或1。
setbit key flag 0|1:设置key的flag的值是0或1。
getbit key flag:查看flag的值
bitcount key:查看key中为1的总数
举例,员工打卡。

127.0.0.1:6379> setbit zhangsan 0 0#zhangsan 周一未打卡
(integer) 0
127.0.0.1:6379> setbit zhangsan 1 1#zhangsan 周二打卡
(integer) 0
127.0.0.1:6379> setbit zhangsan 2 1#zhangsan 周三打卡
(integer) 0
127.0.0.1:6379> setbit zhangsan 3 1#zhangsan 周四打卡
(integer) 0
127.0.0.1:6379> setbit zhangsan 4 1#zhangsan 周五打卡
(integer) 0
127.0.0.1:6379> setbit zhangsan  5 0#zhangsan 周六打卡
(integer) 0
127.0.0.1:6379> getbit zhangsan 5#查看周六是否打卡
(integer) 0
127.0.0.1:6379> getbit zhangsan 3 //查看周三是否打卡
(integer) 1
127.0.0.1:6379> bitcount zhangsan //查看打卡的有多少天
(integer) 4

四、Redis事务

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

Redis事务不存在隔离级别的概念,因为在事务中,所有的命令并没有直接运行,只有发起执行命令(EXEC)时才会按顺序执行。

Redis单条命令保证原子性,事务不保证原子性。
Redis的事务:
开启事务(multi)->命令入队->执行事务(exec)

正常执行

127.0.0.1:6379> multi//开启事务
OK
127.0.0.1:6379(TX)> set name liwz//命令入队
QUEUED//并没有立即执行
127.0.0.1:6379(TX)> set age 25
QUEUED
127.0.0.1:6379(TX)> set email 8968796@qq.com
QUEUED
127.0.0.1:6379(TX)> exec//执行事务,才一条一条的执行命令
1) OK
2) OK
3) OK

取消事务discard

127.0.0.1:6379> multi//开启事务
OK
127.0.0.1:6379(TX)> set key1 zhangsan
QUEUED
127.0.0.1:6379(TX)> set key2 lisi
QUEUED
127.0.0.1:6379(TX)> discard//取消事务
OK
127.0.0.1:6379> get jey1//命令不是立即执行,所有set key1 zhangsan并没有执行
(nil)

命令编译错误

127.0.0.1:6379> multi//开启事务
OK
127.0.0.1:6379(TX)> set k1 v1 
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> getset k3//写错命令执行直接报错
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> exec//提交事务也会报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1//命令不会执行
(nil)

运行时错误

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 "lisi"
QUEUED
127.0.0.1:6379(TX)> set k2 "zhangsan"
QUEUED
127.0.0.1:6379(TX)> incr k2//对字符串实现自加1,语法没错,运行时才会出错
QUEUED
127.0.0.1:6379(TX)> exec//执行
1) OK
2) OK
3) OK
4) (error) ERR value is not an integer or out of range
//可以看到其他的命令还是成功了,这就是Redis事务没有原子性
127.0.0.1:6379> get k1
"lisi"
127.0.0.1:6379> get k2
"zhangsan"

监控实现乐观锁
正常情况

127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 200
QUEUED
127.0.0.1:6379(TX)> incrby out 200
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 800
2) (integer) 200

加一个监视,监视money,如果在此事务执行期间,有其他事务更改了money的值,此事务就不能执行成功。

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(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
//先不执行事务,再开一个线程更改money的值
********************************************************
127.0.0.1:6379> set money 1200
OK
*******************************************************
//此时再回来执行事务
127.0.0.1:6379(TX)> exec
(nil)//没有执行成功

五、持久化

Redis数据是基于内存的数据库,所以断电就没有了,所有持久化非常重要。

1. RDB持久化(Redis DataBase)

RDB持久化的文件叫做dump.rdb,也是redis的默认持久化方式。
运行机制:主进程用于运行redis,会fork一个子进程,将内存的内容先写入一个临时文件,持久化完成后才生成一个正式文件。
触发机制
a). 在配置文件中有RDB的save规则,例如save 60 5,意思就是60秒内如果操作了5次key,就会触发RDB持久化。
b). 执行flushall,会默认触发RDB持久化。
c). 退出redis,也会持久化。
优点:
a). 适合大规模的数据恢复;
b). 对数据完整性要求不高;
缺点:
a). 需要一定的隔离进程操作!如果redis意外宕机了,最后一次修改的数据就不能保存下来;
b). 子进程会占用一定的内存;

2. AOF持久化(AppendOnly)

它与RDB最大的区别的就是,RDB保存的是数据,AOF保存所有修改数据的命令,查看命令的不保存。
运行机制:
AOF的运行机制类似于日志,记录每一秒的操作命令。
优点:
每一次的修改都会同步,数据完整性好。
缺点:
文件会远大于RDB文件,且运行效率也会比RDB慢。

六、发布订阅

Redis发布订阅是一种通信模式:发送者发布消息,订阅者接收消息。类似关注类的系统,微信公众号,微博等。
会涉及三个角色:
a). 消息发送者;
b). 频道;
c). 消息接收者。

命令:
SUBSCRIBE 频道:订阅频道,会一直处于监听状态

127.0.0.1:6379> SUBSCRIBE yeting
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yeting"
3) (integer) 1

PUBLISH 频道 message:发布消息到指定频道

127.0.0.1:6379> PUBLISH yeting goodnight!
(integer) 1

会发现刚才的订阅者,收到了消息

127.0.0.1:6379> SUBSCRIBE yeting
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yeting"
3) (integer) 1
#接受的消息
1) "message"
2) "yeting"
3) "goodnight!"

七、主从复制

1. 什么是主从复制?

将一台Redis服务器的数据复制到另一台服务器,前者称为主,后者称为从,数据复制是单向的,只能由主向从复制。主节点主要负责写,从节点负责读,分担主节点的压力。

2. 主从复制的作用

  1. 数据冗余:主从复制实现了数据的热备份,是持久化的一种冗余方式。
  2. 数据恢复:当主服务器宕机后,保证数据不会丢失;
  3. 负载均衡:实现读写分离,减轻主节点压力;
  4. 高可用:主从复制是哨兵模式的基础。

3. 环境搭建

在一台机器上搭建多台redis服务器,只需要配置多个配置文件,用不同的配置文件启动服务即可。配置文件需要修改的地方有:

a). 端口号

java redis uri 密码有特殊字符 redis 输入密码的命令_数据库

b). pid文件命名

java redis uri 密码有特殊字符 redis 输入密码的命令_数据库_02

c). log文件命名

java redis uri 密码有特殊字符 redis 输入密码的命令_数据库_03

d). rdb文件命名

java redis uri 密码有特殊字符 redis 输入密码的命令_数据库_04

4. 搭建redis集群

只需要配置从机,不需要配置主机,因为每一台redis都默认自己是主机。现在配置三个redis服务器
info replication命令可以查看主从信息
6379:

127.0.0.1:6379> info replication
# Replication
role:master  #是主机
connected_slaves:0  #它从机目前有0个
master_failover_state:no-failover
master_replid:b32984c6f6f71cc254b303df0a0188e83e4a64c6
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

6380

127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:a877a4f60f845577cfd527c315f040f0557364e1
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

6381:

127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:fdce39446353a2819138803440a39f5ba06a8869
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

SLAVEOF IP PORT 配置从机的主机信息
6380

127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #配置本地的6379为它的主机
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up #有时候这里显示down,是因为主机配置了密码导致的
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_read_repl_offset:0
slave_repl_offset:0
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:e21e1f7eae54cecc4657b1f26685e7dd01922c06
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0

6381配置同上。
我们再回来查看6379的主从信息

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=0,lag=69
slave1:ip=127.0.0.1,port=6380,state=online,offset=84,lag=1
master_failover_state:no-failover
master_replid:e21e1f7eae54cecc4657b1f26685e7dd01922c06
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84

主机可以读写

127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> get name
"zhangsan"

从机可以读到主机写的内容,但不可以写

127.0.0.1:6380> get name
"zhangsan"
127.0.0.1:6380> set age 25
(error) READONLY You can't write against a read only replica.

这种配置情况下,如果主机断开了,从机还可以进行读,当主机回来时,从机依旧可以获取主机修改的数据,但这是不理想的,如果主机断开过久,那么整个服务器都不能进行写操作,所有就有了哨兵模式。

5. 哨兵模式

Redsi集群时,当主节点宕机时,其他从节点只能读,不能写,如果主节点宕机时间过长,肯定会影影响用户体验。那么就可以使用哨兵模式。

java redis uri 密码有特殊字符 redis 输入密码的命令_数据库_05


建立一个Redis哨兵(Sentinel),用于监视主节点的状态,如果发现主节点宕机后,会在剩下的结点中进行投票选择一个新的主节点。

java redis uri 密码有特殊字符 redis 输入密码的命令_数据_06