Redis原理和Jedis
Redis是什么?
Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。(B/S架构)
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
Redis的优势
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
安装Redis
Windows环境下安装
-
下载压缩包
github:https://github.com/tporadowski/redis/releases
-
解压缩
-
配置环境变量
添加REDIS_HOME系统变量:
再配置环境变量path(即在path中添加):
-
试运行
尝试在任何地方使用cmd运行如下命令:
redis-server
如果出现如下内容则证明redis安装成功并且环境变量配置成功:
linux环境下安装
Redis默认端口号:6379
Redis服务端
服务端启动Redis服务
-
前台启动
redis-server
-
后台启动
redis-server &
-
指定配置文件启动
redis-server <config_name> [&] (是否后台启动)
服务端停止Redis服务
redis-cli shutdown
Redis客户端
Redis分为客户端和服务端。客户端用于连接Redis服务,并且向Redis服务端发送命令。
启动cli客户端
使用以下命令即可启动redis的客户端程序
redis-cli #默认连接127.0.0.1:6379的redis服务
[-p port] #指定端口号
[-h hostname] #指定主机地址
退出cli客户端
exit [quit]
Redis基本知识
服务端测试Redis性能
redis-benchmark
客户端查看Redis服务是否正常运行
127.0.0.1:6379>ping
PONG #返回PONG说明Redis服务正常运行
客户端查看Redis服务的统计信息
127.0.0.1:6379>info #返回redis服务的所有相关信息
[section] #查看具体某一指定端的信息
Redis数据库实例
Redis的数据库实例只能由Redis服务来创建和维护,开发人员不能修改和自行创建数据库实例。默认情况下,Redis会自动创建16个数据库实例,并且这些数据库实例由编号来区别,由0—15来使用。Redis的数据库实例本身占用的存储空间很少。Redis客户端默认连接的是编号为0的数据库实例。
客户端切换数据库实例
127.0.0.1:6379>select [index] #根据编号切换数据库实例
客户端查看数据库实例记录数(key的数量)
127.0.0.1:6379>dbsize
(integer) 2 #返回key的数量
客户端查看当前数据库实例所有key
127.0.0.1:6379>keys [pattern] [*] # * 所有
1) "key:__rand_int__"
2) "k1" #返回有哪些key
客户端清空当前数据库实例
127.0.0.1:6379>flushdb
客户端清空所有数据库实例
127.0.0.1:6379>flushall
客户端查看当前Redis服务的配置信息
127.0.0.1:6379>config get [param] [*]
Redis的五种数据结构
Redis支持五种数据类型:string(字符串),list(列表),hash(哈希),set(集合)及zset(sorted set:有序集合)。
string(字符串)
string是redis最基本的类型,一个key对应一个value,是二进制安全的。可以包含任何数据,比如jpg图片或者序列化的对象。
最大能存储512MB
cli> set key value
127.0.0.1:6379> SET runoob "菜鸟教程"
127.0.0.1:6379> GET runoob
"菜鸟教程"
list(列表)
简单的字符串列表,按照插入顺序排序,可以添加元素到头部或者尾部
列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。
cli> lpush key member
cli> lrange key start end #从start到end遍历key列表
127.0.0.1:6379> lpush runoob redis
127.0.0.1:6379> lpush runoob mongodb
redis 127.0.0.1:6379> lrange runoob 0 10
1) "mongodb"
2) "redis"
set(集合)
字符串的无序集合(基于哈希表实现,故不能插入重复的value)。
cli> sadd key member #成功返回1,失败返回0(如重复)
cli> smembers key #遍历key集合
127.0.0.1:6379> sadd runoob redis
127.0.0.1:6379> sadd runoob mongodb
127.0.0.1:6379> smembers runoob
1) "redis"
2) "mongodb"
hash(哈希)
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
每个 hash 可以存储 232 -1 键值对(40多亿)。
#存入key对象,其中属性field1的值为value1,属性filed2的值为value2
cli> HMSET key field1 value1 field2 value2
#获取key对象中属性为filed的值value
cli> HGET key filed
127.0.0.1:6379> HMSET runoob field1 "Hello" field2 "World"
"OK"
127.0.0.1:6379> HGET runoob field1
"Hello"
127.0.0.1:6379> HGET runoob field2
"World"
zset(有序集合)
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
cli> zadd key score member
cli> zrangebyscore key start end #根据具体分数(score必须在start和end范围内)排序
cli> zrange key start end #排序(根据分数)
redis 127.0.0.1:6379> zadd runoob 0 redis
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 mongodb
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 rabbitmq
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 rabbitmq
(integer) 0
redis 127.0.0.1:6379> ZRANGEBYSCORE runoob 0 1000
1) "mongodb"
2) "rabbitmq"
3) "redis"
Redis命令的基本语法
cli> COMMAND KEY_NAME
COMMAND是命令,KEY_NAME是键
如
127.0.0.1:6379> DEL runoobkey
DEL是删除命令,runoobkey是一个键,该命令的目的是删除runoobkey
Redis关于key的命令
查询key的命令(keys)
基本语法格式
cli> keys [pattern] #pattern是匹配规则
[*] #匹配0个或者多个字符
[?] #只匹配1个字符
[[]] #只匹配[]中的1个字符
[a*] #以'a'字符开始的key
[a*b] #以'a'开始,'b'结尾的key
如
127.0.0.1:6379> keys * #查看所有key
1) "ageZset"
2) "name"
3) "osList"
4) "citySet"
5) "key:__rand_int__"
6) "k1"
127.0.0.1:6379> keys k* #查看以'k'字符开头的key
1) "key:__rand_int__"
2) "k1"
127.0.0.1:6379> keys a*t #查看以'a'开头,t结尾的key
1) "ageZset"
127.0.0.1:6379> keys n?me #查看第1个字符为n,第34字符为me的key
1) "name"
127.0.0.1:6379> keys n[abc]me #查看第1个字符为n,第2个字符为[abc]其中一个,第34字符为me的key
1) "name"
判断key是否存在(exists)
cli> exists key [key key key...] #判断key是否存在
#存在返回1(或存在的数量),否则返回0
如
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name osList ageZset citySet
(integer) 4
127.0.0.1:6379> exists name osList ageZset citySet test
(integer) 4
移动key到指定的数据库实例(move)
cli> move key index #移动key到index数据库实例
查看key剩余生存时间(ttl)
单位是秒。(TTL:time to live)
cli> ttl key #查看key的剩余生存时间
#返回-1,永不过期(默认)
#返回-2,key不存在
设置定key的生存时间(expire)
cli> expire key seconds #给key设置生存时间seconds秒(超出即被删除)
查看key的数据类型(type)
cli> type key #查看key的数据类型
重命名key(rename)
cli> rename key newkey
删除key(del)
cli> del key [key key key...]
#返回成功删除key的条数
总结
- 增
- 删
- del:删除key
- 改
- move:移动key
- expire:设置key的生存时间
- rename:重命名
- 查
- keys:查询key
- exists:判断key是否存在
- ttl:查询key的剩余生存时间
- type:查看key的数据类型
Redis操作string类型
单key——单value
存string类型数据(set)
cli> set key value
#如果key已经存在,会发生覆盖
取string类型数据(get)
cli> get key
追加字符串(append)
cli> append key value #往key中追加字符串value
#返回值是追加后字符串长度
#如果key不存在,相当于set
获取字符串长度(strlen)
cli> strlen key #返回key的字符串长度
将字符串数值进行加1运算(incr)
cli> incr key #key的value+1并且返回
Tips:
- 如果key不存在,则先创建该key-value,value初始化为0然后+1
- key的value必须是数值,否则报错
将字符串数值进行减1运算(decr)
cli>decr key
与incr同理
将字符串数值进行加offset(指定数值)运算(incrby)
cli>incrby key offset #key的vaule+offset并且返回
将字符串数值进行减offset(指定数值)运算(decrby)
cli>decrby key offset #key的vaule-offset并且返回
获取字符串中字符串(getrange)
cli>getrange key startIndex endIndex
#截取key中value从startIndex到endIndex
设置字符串中字符串(setrange)
会改变数据库中的值
cli>setrange key startIndex value
#用value覆盖从下标为startIndex开始的字符串
设置字符串的同时设置生命周期(setex)
cli>setex key seconds value
#给key-value设置seconds秒后过期
cli> setex k1 30 v1
OK
cli> ttl k1
(integer) 27
设置字符串数据当key不存在才设置,key存在则不设置(setnx)
cli>setnx key value
批量设置字符串数据(mset)
cli>mset key1 value1 key2 value2 key3 value3 ...
批量获取字符串数据(mget)
cli>mget key1 key2 key3 ...
总结
Redis操作string类型
- 增
- set:最基本的存储string类型数据的方式(如果存在则会覆盖)
- setex:存储数据的同时设置生命周期
- setnx:如果存在则不存储
- mset:批量存储
- 删
- 改
- set:修改string类型的数据(如果存在则会覆盖,也就是修改)
- append:在原有的基础上追加数据
- incr:数值字符串增加
- decr:数值字符串减少
- setrange:修改字符串中指定下标的数据
- 查
- get:获得数据
- mget:批量获得数据
- getrange:获得字符串中子串
- strlen:字符串长度
Redis操作list类型
单key——多有序value。顺序跟插入元素的顺序有关
从左侧起存list类型数据(lpush)
依次往左侧(头部)添加元素
cli>lpush key value [value value...]
cli> lpush list1 1 2 3 4
(integer) 4
cli> lrange list1 0 4
1) "4"
2) "3"
3) "2"
4) "1"
#可以看到lpush的顺序后插入的元素在列表中越靠前
从右侧起存list类型数据(rpush)
依次从右侧(尾部)添加元素
cli>rpush key value [value value...]
获取指定列表中指定下标区间的元素(lrange)
cli>lrange key startIndex endIndex
#获取key列表中startIndex到endIndex的元素
cli> lrange list1 0 4
1) "4"
2) "3"
3) "2"
4) "1"
cli> lrange list1 0 -1 #获取从头到尾的全部元素,-1表示倒数第一个,即尾部
1) "4"
2) "3"
3) "2"
4) "1"
从指定列表中移除并返回表头(左侧)元素(lpop)
cli> lpop key
从指定列表中移除并返回表尾(右侧)元素(rpop)
cli> rpop key
获取列表中指定下标的元素(lindex)
cli> lindex key index
获取指定列表的长度(元素个数)(llen)
cli> llen key
移除指定列表中数据(lrem)
cli>lrem key count value
#移除key列表count个中值为value的元素
# count > 0 从左侧起移除
# count < 0 从右侧起移除
# count = 0 移除所有
总结
Redis操作list类型
- 增
- lpush:从左边添加元素
- rpush:从右边添加元素
- 删
- lpop:从左边弹出元素
- rpop:从右边弹出元素
- lrem:移除指定元素
- 改
- 查
- lrange:遍历
- lindex:指定下标的元素
- llen:长度
Redis操作set类型
单key——多无序且不可重复value
将一个或多个元素添加到指定集合中(sadd)
cli> sadd key value [value value ...] #若元素重复,则忽略
cli> sadd set1 a b c a
(integer) 3 #成功添加元素的个数
#只添加成功三个
获取指定集合中的元素(smembers)
cli> smembers key
cli> smembers set1
1) "c"
2) "b"
3) "a"
判断元素在集合中是否存在(sismember)
cli> sismember key member
(integer) 1 #返回1则存在
(integer) 0 #返回0则不存在
获取集合的长度(scard)
cli> scard key
移除集合中1个或者多个元素(srem)
cli> srem key member [member ...]
#返回成功移除的元素的个数
随机获取集合中的一个或多个元素(srandmember)
cli> srandmember key [count]
#count>0 随机获取的元素不会重复
#count<0 随机获取的元素可能重复
随机移除集合中的一个或多个元素(spop)
cli> spop key [count]
将集合中的元素移动到另一集合(smove)
cli> smove source dest member #将source集合中的member移动到dest集合
获取集合的差集(sdiff)
即一个集合中有,但是其他集合中没有的元素
cli> sdiff key1 key2 [key3 ...]
#即key1集合中有,其他集合中没有的元素
cli> smembers set1
1) "f"
2) "c"
3) "e"
4) "g"
5) "a"
6) "b"
cli> smembers set2
1) "d"
2) "c"
3) "g"
4) "f"
5) "e"
6) "i"
7) "b"
8) "a"
9) "h"
cli> sdiff set2 set1 #set2中有hid,但是set1中没有
1) "i"
2) "d"
3) "h"
获取集合的交集(sinter)
即指定集合中都有的元素
cli> sinter key key [key key...]
127.0.0.1:6379> smembers set1
1) "f"
2) "c"
3) "e"
4) "g"
5) "a"
6) "b"
127.0.0.1:6379> smembers set2
1) "d"
2) "c"
3) "g"
4) "f"
5) "e"
6) "i"
7) "b"
8) "a"
9) "h"
127.0.0.1:6379> sinter set1 set2
1) "f"
2) "c"
3) "e"
4) "g"
5) "a"
6) "b"
#set1和set2中都有abcefg
获取集合的并集(sunion)
即指定集合中的所有元素
cli> sunion key key [key key...]
总结
Redis操作set类型
- 增
- sadd:新增元素到集合中
- 删
- srem:移除元素
- spop:弹出元素
- smove:移动过元素到另一集合
- 改
- sadd:新增元素到集合中,如果value已经存在则会覆盖
- 查
- smembers:遍历集合元素
- sismember:判断元素是否存在
- scard:集合长度
- srandmember:随机获取集合元素
- sdiff:差集
- sinter:交集
- sunion:并集
Redis操作hash类型
单key——field-value
field-value
field-value
适合存储对象
将一个或多个field-value(属性—值)对存入hash表,会覆盖(hset)
如果key中的filed之前是存在的,则会强制覆盖(使用hsetnx不会强制覆盖)
cli> hset key field1 value1 [field2 value2 ...]
cli> hset student1 name wqk age 20
(integer) 2
获取指定hash表中指定field值(hget)
cli> hget key field #获取key哈希表中field的值
cli> hget student1 name
"wqk"
批量获取指定hash表中field的值(hmget)
cli> hmget key field1 [field2 field3 ...]
cli> hmget student1 name age
1) "wqk"
2) "20"
获取hash表中所有的field和value(hgetall)
cli> hgetall key
cli> hgetall student1
1) "name"
2) "wqk"
3) "age"
4) "20"
移除hash表中指定一个或者多个field(hdel)
cli> hdel key field1 [field2 field3 ...]
统计hash表中field数量(hlen)
cli> hlen key
判断hash表中是否存在某field(hexists)
cli> hexists key field
#返回0表示不存在,1表示存在
获取hash表中所有的field列表(hkeys)
cli> hkeys key
cli> hkeys student2
1) "id"
2) "name"
3) "age"
获取hash表中所有的value(hvals)
cli> hvals key
cli> hvals student2
1) "10001"
2) "test"
3) "18"
对hash表中指定field的value进行整数加法运算(hincrby)
cli> hincrby key field incrment
#对key哈希表中的field属性的值进行加incrment值运算
cli> hget student2 age
"18"
cli> hincrby student2 age 2
(integer) 20
对hash表中指定field的value进行浮点数加法运算(hincrbyfloat)
cli> hincrbyfloat key field incrment
cli> hset student2 score 85
(integer) 1
cli> hincrbyfloat student2 score 1.5
"86.5"
将一个或多个field-value(属性—值)对存入hash表,不会覆盖(hsetnx)
如果key的field已经存在则放弃设置
cli> hsetnx key filed1 value1 [field2 value2]
总结
Redis操作hash类型
- 增
- hset:存入hash类型数据
- hsetnx:存入数据,如果存在则不存入
- 删
- hdel:删除hash表中field及其value
- 改
- hset:存入hash类型数据,如果field存在则会覆盖
- hincrby:value是数值,则会增加
- 查
- hget:获取hash表中指定field的value
- hmget:批量获取hash表中指定field的value
- hgetall:获取hash表中所有field及其value
- hlen:获取hash表中field的数量
- hexists:判断hash表中该field是否存在
- hkeys:获取hash表中所有field
- hvals:获取hash表中所有value
Redis操作zset类型
有序集合。顺序跟score有关
将一个或多个member及其score值加入有序集合(zadd)
score必须是数值。如果member已经存在,则会覆盖
cli> zadd key score member [score member]
cli> zadd zset1 95 wqk 90 lk 98 zp
(integer) 3
获取有序集合中指定下标区间的元素(zrange)
cli> zrange key startIndex endIndex [withscores] #获取key集合startIndex到endIndex区间的元素(是否显示分数)
获取有序集合中指定分数区间(闭区间)的元素(zrangebyscore)
cli> zrangebyscore key min max [withscores] #获取key集合min到max分数区间的元素(是否显示分数)
删除有序集合中一个或者多个元素(zrem)
cli> zrem key member [member]
获取有序集合中元素个数(zcard)
cli> zcard key
获取有序集合中分数在指定区间的元素个数(zcount)
cli> zcount key min max #获取key有序集合中min到max区间的元素个数
获取有序集合中指定元素的排名(升序)(zrank)
cli> zrank key member
获取有序集合中指定元素的排名(降序)(zrevrank)
cli> zrevrank key member
获取有序集合指定元素的分数(zscore)
cli> zscore key member
总结
Redis操作zset类型
- 增
- zadd:往集合中存入元素
- 删
- zrem:移除集合中的元素
- 改
- 查
- zrange:遍历集合元素
- zrangebyscore:根据score遍历集合元素
- zcard:集合元素个数
- zcount:指定分数区间元素个数
- zrank:元素的排名(正序)
- zrevrank:元素的排名(倒序)
- zscore:元素的分数
Redis配置文件
Redis根目录下有个默认的配置文件:.\redis.conf
,里面有一些redis默认的配置参数
cli> redis-server [config_name] #默认使用默认的配置的文件
网络配置
-
port
指定redis服务所使用的端口号,默认6379
-
bind
指定客户端连接redis服务时,所能使用的主机地址,默认是redis服务所在主机名,一般情况下都需要指定主机名
-
tcp-keeplive
TCP连接保活策略,单位是秒。指的是Redis服务端会在指定秒内向连接他的客户端发送一次ACK请求,以确保连接有效。默认是60秒
如果以上配置在配置文件中生效,则在客户端中连接需使用:
redis-cli -h bind -p 6380
常规配置
-
loglevel
配置日志级别,分为四个级别:debug、verbose、notice、warning。默认是notice
-
logfile
日志文件持久化存储的地方。默认不会持久化,只会输出到终端
-
databases
配置Redis服务默认创建的数据库实例个数。默认是16个。
安全配置
-
requirepass
配置Redis客户端访问Redis服务时的密码,需要
protected-mode = yes
时生效
如配置了requirepass,则在客户端连接中需使用:
redis-cli [-h hostname] [-p port] [-a pwd]
Redis数据持久化
Redis提供持久化策略,可以在适当的时机采用适当的手段把内存中的数据持久化的磁盘。
RDB策略(Redis默认启用的持久化策略)
RDB(Redis DataBase)策略是指,在指定时间间隔内,Redis服务执行指定次数的写操作,会自动触发一次持久化操作。如需修改可以在redis的配置文件中修改RDB相关的配置(dbfilename
持久化数据的文件名,默认是dump.rdb,dir
持久化数据的目录,默认是./
,即根目录)。
AOF策略
记录每一次Redis服务的完整写操作进入日志,每次Redis服务启动时,都会重新执行一遍完整的操作日志以便恢复数据。效率不高,默认不开启AOF(appendonly
配置是否开启AOF策略,appendfilename
配置操作日志文件)。
Redis事务
数据库事务:一组对数据库的操作一起执行,保证操作的原子性,要么同时成功,要么同时失败。
Redis事务:允许一组Redis命令放在一起,将命令序列化,然后按队列执行,保证部分原子性。
multi
标记一个Redis事务的开始(与exec成对存在)。
exec
标记一个Redis事务的结束(与multi成对存在),即立即开始执行上述事务。
一个完整的Redis事务的输入格式应该是如下
cli> multi #标识事务开始了
cli> set k1 v1 #操作1
cli> set k2 v2 #操作2
cli> exec #标识事务结束了(即立即开始执行)
cli> multi
OK #事务开启
cli> set str1 value1
QUEUED #操作1加入队列
cli> set str2 value2
QUEUED #操作2加入队列
cli> exec
1) OK #操作1执行结果
2) OK #操作2执行结果
Tips:
Redis事务不是脚本命令。而是在cli执行
multi
后接着一行一行输入操作的命令(操作会插入队列),最后执行exec
才会按队列执行上述操作。为保证事务的部分原子性,Redis事务遵循以下两则规则:
输入事务操作过程中如果发生错误,则事务输入失败,即事务无法被创建。即
multi
和exec
中间操作语句有错误则事务无法被创建。cli> multi cli> set k1 v1 cli> seta k2 v2 #操作2:命令错误 cli> exec #该事务无法被创建:操作的命令错误
如果上述规则没有发生错误,则在最后执行操作队列时,即使其中一项操作执行异常仍会继续执行其余操作。
cli> multi cli> set k1 v1 cli> incr k1 cli> exec #事务会被创建,即使incr的执行结果异常
discard
清除已经存在于操作队列中的命令
cli> multi
OK
cli> set k5 v5
QUEUED
cli> set k6 v6
QUEUED
cli> discard #清空上述操作队列
OK
cli> exec
(error) ERR EXEC without MULTI #exec执行失败的原因是操作命令的队列被清空
watch
在事务开启前(multi
)监控某一个键,在事务执行的过程中监控的键的值发生变化,则此次事务放弃执行;否则正常执行。
用以下事务来模拟一个场景:钱包A余额50,钱包B余额50,钱包B向钱包A转账50,那么最后钱包B余额0,钱包A余额100。并发情况下假如多个钱包需要向钱包B转账,那么就会发生错误。所以需要监控该次事务执行前的version是否还是该version。
cli> set balanceA 50
OK
cli> set balanceB 50
OK
cli> set verison 1
OK
cli> watch version #监控version的值
OK
cli> multi
OK
cli> incrby balanceA 50
QUEUED
cli> decrby balanceB 50
QUEUED
cli> incr version
QUEUED
cli> exec
1) (integer) 100
2) (integer) 0
3) (integer) 2
Tips
watch就是类似于乐观锁,A事务执行前查询一个键(该键会在其他事务进行该事务后发生改变,即标识此次事务执行成功),如果该键在A事务执行过程中发生了改变(说明其他事务对数据进行了操作),即A事务应该不执行,如果该键没有发生改变,则A事务正常执行。
unwatch
放弃监控的所有键。
Redis消息的发布与订阅(了解)
Redis客户端订阅频道,订阅了同一频道的客户端可以发送或者收到来自同一频道的消息。(消息中间件)
subscribe
订阅频道(以便接收消息)
cli> subscribe channel [channel ...] #可以订阅多个频道
client1:
订阅了ch1频道
cli> subscribe ch1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ch1"
3) (integer) 1
client2:
订阅了ch1频道
cli> subscribe ch1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ch1"
3) (integer) 1
此时client1和client2都订阅了ch1频道,如果有客户端往ch1频道发送消息,那么client1和client2都会收到
psubscribe
订阅频道(支持通配符)
cli> psubscribe pattern [pattern ...]
cli> psubscribe news* #订阅以news开头的频道
publish
往频道上发布消息
cli> publish channel message #往channel频道发送message消息
client3向ch1频道发送消息:
cli> publish ch1 Hello,Redis!
(integer) 2
此时client1和client2就会收到来自client3的消息:
1) "message"
2) "ch1"
3) "Hello,Redis!"
Redis主从复制
搭建一主二从的Redis集群并启动
实际开发中应分别在三台不同的服务器上部署三个Redis服务,这里为了方便演示选择在同一台服务器启动三个配置文件不同的Redis服务来模拟,三个Redis服务端口号分别是6379、6380、6381,三份配置文件名命名为:redis_6379.conf
、redis_6380.conf
、redis_6381.conf
。
redis_6379.conf:
port 6379 #修改端口号
logfile "redis_6379.log" #修改生成的日志文件名
dbfilename redis_6379.rdb #修改持久化的文件名
同理,redis_6380.conf和redis_6381.conf也需要修改以上配置
Tips
需要修改日志和持久化文件的文件名,不然可能会造成数据冲突。
接下来使用redis-server <config_name>
分别启动三个Redis服务
redis-server redis_6379.conf
redis-server redis_6380.conf
redis-server redis_6381.conf
使用cli分别连接三台Redis服务
redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381
查看Redis服务的主从信息
cli> info replication
# Replication
role:master #角色:主机
connected_slaves:0 #已连接的从机数量:0
master_replid:91792be1189896bfa47e7a99aae23b8702c8c93b
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服务的角色都是主机,即role:master
。
设置从机从属于主机(slaveof)
命令格式如下:
cli> slaveof host port #设置当前连接的服务从属于host:port的redis服务
在已连接从机服务的客户端上执行:slaveof 127.0.0.1 6379
,即
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave #角色:从机
master_host:127.0.0.1 #主机host地址
master_port:6379 #主机port端口号
master_link_status:up #与主机连接状态:up(已连接)
...
127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
...
全量复制
一旦主从关系确定,主库会将所有数据立马同步到从库。
增量复制
在主库上写的数据,会同步到从库。
读写分离,主写从读
主机既能读也能写(尽量写),从机只能读不能写。
主机宕机,从机原地待命
主机宕机后,从机仍然可以读数据,可以从info replication
中看到与主机的连接状态变为down
127.0.0.1:6380> info replication
# Replication
role:slave #角色仍是从机
master_host:127.0.0.1
master_port:6379
master_link_status:down #与主机的连接状态变为down(未连接)
主机恢复,恢复原样
主机恢复后,恢复到原样,从机与主机继续建立连接
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up #与主机的连接状态变为up(已连接)
从机宕机
从机宕机后,主机少一台从机,其余从机不受影响
从机恢复
从机恢复后,会初始化到最初的状态(即变为role:master
),需要重新设置从属主机,然后主机的数据会全量复制到从机上。
主机宕机,从机上位
主机宕机后,如果主机灾害程度无法短时间恢复则可以使用从机进行上位(即取代主机)。
-
选择一个从机取消其原本的从属关系,成为新的主机
127.0.0.1:6380> slaveof no one #取消从属关系(成为主机) OK 127.0.0.1:6380> info replication #查看复制关系 # Replication role:master #角色:主机(新的主机) connected_slaves:0 ...
此时6380从机成为了新的主机,并且仍保留原有的数据
-
其余从机重新从属于新的主机
127.0.0.1:6381> slaveof 127.0.0.1 6380 #6381从机从属于6380 OK 127.0.0.1:6381> info replication # Replication role:slave master_host:127.0.0.1 master_port:6380 master_link_status:up
Tips
主机可读可写,从机只能读,不能写。
Redis哨兵模式
Redis的哨兵模式会在某一Redis服务发生故障(如宕机)时,自动执行从机上位(按照一定规则),并且主机恢复后会从属于新的主机。流程如下:
-
配置哨兵的配置文件
redis_sentinel.conf:(配置文件名称自定义)
#该哨兵的名称是sentinel-redis。作用是monitor(监控)主机127.0.0.1的6379(端口)上的Redis服务 sentinel monitor sentinel-redis 127.0.0.1 6379 1
-
启动哨兵
cmd> redis-server redis_sentinel.conf --sentinel
出现以下内容则启动成功:
cmd>redis-server.exe redis_sentinel.conf --sentinel [16228] 07 Oct 21:58:19.446 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo [16228] 07 Oct 21:58:19.446 # Redis version=5.0.10, bits=64, commit=1c047b68, modified=0, pid=16228, just started [16228] 07 Oct 21:58:19.446 # Configuration loaded _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 5.0.10 (1c047b68/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in sentinel mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 26379 | `-._ `._ / _.-' | PID: 16228 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' [16228] 07 Oct 21:58:19.452 # Sentinel ID is 192b87ae0be76565fefed74ead2f28454a49f746 [16228] 07 Oct 21:58:19.452 # +monitor master sentinel-redis 127.0.0.1 6379 quorum 1 [16228] 07 Oct 21:58:19.456 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ sentinel-redis 127.0.0.1 6379 [16228] 07 Oct 21:58:19.457 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ sentinel-redis 127.0.0.1 6379
-
监控的主机宕机,从机会在短时间后上位(变为新的主机,其余从机从属于该新主机)
(此时6379宕机)
6381成为新的主机:
127.0.0.1:6381> info replication # Replication role:master connected_slaves:1 slave0:ip=127.0.0.1,port=6380,state=online,offset=22068,lag=0 ...
6380从属于6381:
127.0.0.1:6380> info replication # Replication role:slave master_host:127.0.0.1 master_port:6381 master_link_status:up ...
-
旧主机恢复,变为新主机的从机
(此时6379恢复)
6379从属于6381:
127.0.0.1:6379> info replication # Replication role:slave master_host:127.0.0.1 master_port:6381 master_link_status:up ...
Tips
哨兵并不会在主机宕机的一瞬间就将从机上位,而是在一定间隔时间内扫描,扫描到主机宕机了再让从机上位,旧主机恢复时,哨兵将其变为新主机的从机时同理(并不会立马执行,而是在一定时间间隔内)。
总结
-
查看Redis服务的主从关系
cli> info replication
-
设置Redis服务之间的从属关系
cli> slaveof host port #执行命令的当前主机从属于host:port上的Redis服务
-
开启哨兵模式
cmd> redis-sentinel <sentinel_config> #以sentinel_config配置文件执行哨兵
Tips
- 主从复制原则:先全量复制,再增量复制
- 哨兵三大任务:监控、提醒、自动故障迁移
Jedis的使用
Jedis是在Java上使用Redis的客户端,封装了一些命令和操作。类似jdbc,同时Spring官方还提供Spring Data Redis
引入Jedis依赖
采用Maven项目进行演示,需要引入Jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
配置主机和端口号
创建Jedis的连接
JedisConnection.java
public class JedisConnection {
//host
public static final String REDIS_HOST = "127.0.0.1";
//port
public static final int REDIS_PORT = 6379;
public static Jedis getJedis(){
return new Jedis(REDIS_HOST,REDIS_PORT);
}
}
操作key
具体命令参考Redis操作key
public class JedisKeys {
public static void main(String[] args) {
//获得Jedis对象
Jedis jedis = JedisConnection.getJedis();
//是否能连接上redis服务
String ping = jedis.ping();
//查询keys
Set<String> keys = jedis.keys("*");
//k1是否存在
Boolean existsK1 = jedis.exists("k1");
//k1的生存时间
Long ttlK1 = jedis.ttl("k1");
//k1的类型
String typeK1 = jedis.type("k1");
// ...
}
}
操作string
具体命令参考Redis操作string类型
public class JedisString {
public static void main(String[] args) {
//获得Jedis对象
Jedis jedis = JedisConnection.getJedis();
//存储string数据
String setResult = jedis.set("k3", "v3");
//批量存储string数据
String msetResult = jedis.mset("k4", "v4", "k5", "v5");
//获得k3的value
String getK3 = jedis.get("k3");
//存储k10并且自加
jedis.set("k10","5");
jedis.incr("k10");
//存储k11并且自加50
jedis.set("k11","100");
jedis.incrBy("k11", 50);
//获得k3的字符串长度
jedis.strlen("k3");
//...
}
}
操作list
具体命令参考Redis操作list类型
public class JedisList {
public static void main(String[] args) {
Jedis jedis = JedisConnection.getJedis();
//从左边添加元素
jedis.lpush("list1","v1","v2","v3");
//移除左边的元素
jedis.lpop("list1");
//遍历list1
List<String> list1 = jedis.lrange("list1", 0, -1);
list1.forEach(System.out::println);
//返回list1长度
jedis.llen("list1");
//从右边添加元素
jedis.rpush("list2","v1","v2","v3");
//从右边移除元素
jedis.rpop("list2");
//遍历list2
List<String> list2 = jedis.lrange("list2", 0, -1);
list2.forEach(System.out::println);
//返回list2长度
jedis.llen("list2");
//...
}
}
操作set
具体命令参考Redis操作set类型
public class JedisSet {
public static void main(String[] args) {
Jedis jedis = JedisConnection.getJedis();
//添加元素
jedis.sadd("set1","v1","v2","v3");
//判断元素是否存在
jedis.sismember("set1","v1");
//(随机)弹出元素
jedis.spop("set1",1);
//移除v1,v2元素
jedis.srem("set1","v1","v2");
//遍历set1
Set<String> smembers = jedis.smembers("set1");
smembers.forEach(System.out::println);
//set1长度
jedis.scard("set1");
//...
}
}
操作hash
具体命令参考Redis操作hash类型
public class JedisHash {
public static void main(String[] args) {
Jedis jedis = JedisConnection.getJedis();
//存入对象student1
jedis.hset("student1","name","wqk");
HashMap<String, String> map = new HashMap<>();
map.put("age","18");
map.put("score","98");
//批量存入student1
jedis.hmset("student1",map);
//获取student1对象的name值
jedis.hget("student1","name");
//批量获取student1对象的name、age、score
jedis.hmget("student1","name","age","score");
//获取student1对象的所有field-value
jedis.hgetAll("student1");
//...
}
}
操作zset
具体命令参考Redis操作zset类型
public class JedisZset {
public static void main(String[] args) {
Jedis jedis = JedisConnection.getJedis();
//向zset1添加v1,v2,v3
jedis.zadd("zset1",500,"v1");
jedis.zadd("zset1",1000,"v2");
jedis.zadd("zset1",100,"v3");
//按照下标遍历zset1
Set<String> zset1 = jedis.zrange("zset1", 0, -1);
zset1.forEach(System.out::println);
//按照分数遍历zset1
Set<String> zset11 = jedis.zrangeByScore("zset1", 0, 1000);
zset11.forEach(System.out::println);
//zset1长度
jedis.zcard("zset1");
//...
}
}
Redis事务
public class JedisTransaction {
public static void main(String[] args) {
Jedis jedis = JedisConnection.getJedis();
//获取Redis的事务对象
Transaction transaction = jedis.multi();
// start
transaction.set("k6","v6");
transaction.set("k7","v7");
// end
transaction.exec(); //执行事务
}
}
Redis客户端可视化
一个开源的Redis客户端可视化工具Redis-Desktop-Manager