一 操作命令
flushdb,清除所有数据
1、准备工作
- 认知1:redis使用的是命令来访问和操作redis和redis中的数据的。
- 认知2:在redis中所有的key都是文本类型的。
- 在linux中,执行make命令对redis的源文件进行编译以后,需要执行make install命令。make install相当于给redis配置了环境变量,这样在linux中就可以在任意的位置去执行redis的相关命令了。例如,我们在windows中安装了jdk,并配置了环境变量,这时我们在任意的目录中就可以执行javac来编译.java文件了。如果安装jdk以后不配置环境变量,那么想要执行javac命令就要到jdk的安装目录下的bin目录下才能执行javac命令。
2、redis的基本操作命令
1)、测试redis服务的性能:
redis-benchmark
2)、查看redis服务是否正常运行:
ping 如果正常---pong
3)、查看redis服务器的统计信息:
info 查看redis服务的所有统计信息
info [信息段] 查看redis服务器的指定的统计信息,如:info Replication
4)、redis的数据库实例:作用类似于mysql的数据库实例,redis中的数据库实例只能由redis服务来创建和维护,开发人员不能修改和自行创建数据库实例;默认情况下,redis会自动创建16个数据库实例,并且给这些数据库实例进行编号,从0开始,一直到15,使用时通过编号来使用数据库;可以通过配置文件,指定redis自动创建的数据库个数;redis的每一个数据库实例本身占用的存储空间是很少的,所以也不造成存储空间的太多浪费。
默认情况下,redis客户端连接的是编号是0的数据库实例;
可以使用select index切换数据库实例。
5)、查当前数据库实例中所有key的数量(也可以理解为有几行/几条数据),默认查0库:dbsize
6)、查看当前数据库实例中所有的key:keys *
7)、清空当前数据库实例:flushdb
8)、清空所有的数据库实例:flushall
9)、查看redis中所有的配置信息:config get *
查看redis中的指定的配置信息:config get parameter
10)redis 自带的客户端退出当前 redis 连接: exit 或 quit
3、redis中有关key的操作命令:
1)、查看数据库中的key:keys pattern
|->*:匹配0个或者多个字符
|->?: 匹配1个字符
|->[]:匹配[]里边的1个字符
keys *:查看数据库中所有的key
keys k*:查看数据库中所有以k开头的key
keys h*o:查看数据库中所有以h开头、以o结尾的key
keys h?o: 查看数据库中所有以h开头、以o结尾的、并且中间只有一个字符的key
keys h[abc]llo:查看数据库中所有以h开头以llo结尾,并且h后边只能取abc中的一个字符的key
2)、判断key在数据库中是否存在:exists key 如果存在,则返回1;如果不存在,则返回0
exists key [key key ....] 返回值是存在的key的数量
exists k1
exists k1 k2 k3 hello
3)、移动指定key到指定的数据库实例:move key index
move k 1
4)、查看指定key的剩余生存时间:ttl key
|->如果key没有设置生存时间,即永久存活,返回-1
|->如果key不存在,返回-2
|->数字:key 的剩余时间,秒为单位
应用:验证码过期,减少访问数据库
ttl k1
ttl k2
5)、设置key的最大生存时间:expire key seconds
返回值:设置成功返回数字 1,其他情况是 0 (如没有指定的key)。
超过时间,key 与value自动被删除,释放空间
应用:验证码过期,减少访问数据库
expire k2 20
6)、查看指定key的数据类型:type key
语法:type key
作用:查看 key 所存储值的数据类型
7)、重命名key: rename key newkey
rename hello k2
8)、删除指定的key:del key [key key .....]
语法:del key [key…]
作用:从当前库中删除存在的 key和对应的value,不存在的 key 忽略。
del k1 k2 k3 k4
4、String类型:最大512M。字符串、数字、二进制数据、序列化后的数据、json化的对象、甚至是一张图片/文件。
日常使用:欢迎语、用户常用的提示。
1)set 键 值:将string类型的数据设置到redis中。覆盖行为。
set zsname zhangsan
set zsage 20
set totalRows 100
set zsage 30 如果key已经存在,则后来的value会把以前的value覆盖掉.
2)get 键:从redis中获取string类型的数据。nil等同于java中的null
get zsname
get zsage
get totalRows
3)append key value:追加字符串
|->返回追加之后的字符串长度
|->如果key不存在,则新创建一个key。并且把value值设置为value。
set phone 1389999
append phone 8888
4)strlen key:获取字符串数据的长度如果key不存在返回0
strlen phone
5)incr key:将字符串数值进行加1运算。等同于java中的i=i++。
日常使用:因为incr对v的操作是原子性的,即在多线程的操作下使用incr命令进行加1处理是安全的、准确的、一致的,因此可以使用incr命令来做全局的计数器,如记录当前登录数等。
|->返回加1运算之后的数据
|->如果key不存在,首先设置一个key,值初始化为0,然后进行incr运算。如果键不存在,创建此键并设置值为0,最后执行++操作。
|->要求key所表示value必须是数值,否则,报错
incr zsage
incr age
incr zsname 报错
6)decr key:将字符串数值进行减1运算
|->返回减1运算之后的数据
|->如果key不存在,首先设置一个key,值初始化为0,然后进行decr运算,最终v的值是-1。
|->要求key所表示value必须是数值,否则,报错
7)incrby key offset:将字符串数值进行加offset运算
|->返回加offset运算之后的数据
|->如果key不存在,首先设置一个key,值初始化为0,然后进行incrby运算。
|->要求key所表示value必须是数值,否则,报错
incrby zsage 10
8)decrby key offset:将字符串数值进行减offset运算
|->返回减offset运算之后的数据
|->如果key不存在,首先设置一个key,值初始化为0,然后进行decrby运算。
|->要求key所表示value必须是数值,否则,报错
decrby zsage 10
10)getrange key startIndex endIndex:闭区间获取字符串key中从startIndex到endIndex的字符组成的子字符串。等同于java中的subString()方法。
|->下标自左至右,从0开始,依次往后,最后一个字符的下标是字符串长多-1;
|->字符串中每一个下标也可以是负数,负下标表示自右至左,从-1开始,依次往前,最右边一个字符的下标是-1
zhangsan
getrange zsname 2 5 angs
getrange zsname 2 -3 angs
getrange zsname 0 -1 zhangsan
11)setrange key startIndex value:用value覆盖从下标为startIndex开始的字符串,能覆盖几个字符就覆盖几个字符,返回覆盖后的字符串长度
setrange zsname 5 xiaosan //zhangxiaosan
setrange zsname 5 lao //zhanglaoosan
12)setex key seconds value:设置字符串数据的同时,设置它最大生命周期
setex k1 20 v1
13)setnx key value:设置string类型的数据value到redis数据库中,当key不存在时设置成功,否则,则放弃设置
setnx zsage 20
14)mset 键1 值1 键2 值2 .....:同时设置一个或多个 key-value 对;返回值:OK;
mset k1 v1 k2 v2 k3 v3 k4 v4 k5 v5
15)mget 键1 键2 键3.....:获取所有(一个或多个)给定 key 的值;返回值:包含所有 key 的列表;
mget k1 k2 k3 k4 k5 k6 zsname zs age totalRows
16)msetnx 键1 值1 键2 值2 .....:批量设置string类型的数据value到redis数据库中,当所有key都不存在时设置成功,否则(只要有一个已经存在),则全部放弃设置
msetnx kk1 vv1 kk2 vv2 kk3 vv3 k1 v1
5、hash类型:类型于java的map,一个hash中可以存储多个属性和属性的值。
日常使用:一般用于存储java对象的全部数据。比如在分布式的环境中,多台服务器中可以使用redis的hash存储登录用户的信息,实现登录用户信息数据的共享。
单key:field-value
field-value
.....
例如:studentzs:id-1001 name-zhangsan age-20
1)、 hset
语法:hset hash 表的 key field value
作用:将哈希表 key 中的域 field 的值设为 value,如果 key 不存在,则新建 hash 表,执行
赋值,如果有 field ,则覆盖值。
返回值:
①如果 field 是 hash 表中新 field,且设置值成功,返回 1
②如果 field 已经存在,旧值覆盖新值,返回 0
hset stu1001 id 1001
hset stu1001 name zhangsan age 20
2)、hget
语法:hget key field
作用:获取哈希表 key 中给定域 field 的值
返回值:field 域的值,如果 key 不存在或者 field 不存在返回 nil
hget stu1001 id
hget stu1001 name
3)、hmset
语法:hmset key field value [field value…]
说明:同时将多个 field-value (域-值)设置到哈希表 key 中,此命令会覆盖已经存在的 field,
hash 表 key 不存在,创建空的 hash 表,执行 hmset.
返回值:设置成功返回 ok,如果失败返回一个错误
hmset stu1002 id 1002 name lisi age 20
4)、hmget
语法:hmget key field [field…]
作用: 获取哈希表 key 中一个或多个给定域的值
返回值:返回和 field 顺序对应的值,如果 field 不存在,返回 nil做为点位
hmget stu1001 id name age
5)、hgetall
语法:hgetall key
作用:获取哈希表 key 中所有的域和值
返回值:以列表形式返回 hash 中域和域的值,key 不存在,返回空 hash
hgetall stu1002
6)、hdel
语法:hdel key field [field…]
作用:删除哈希表 key 中的一个或多个指定域 field,不存在 field 直接忽略
返回值:成功删除的 field 的数量
hdel stu1002 name age
7)、hlen key:获取指定哈希表中所有的filed个数
hlen stu1001
hlen stu1002
8)、hexists
语法:hexists key field
作用:查看哈希表 key 中,给定域 field 是否存在
返回值:如果 field 存在,返回 1,其他返回 0
hexists stu1001 name
hexists stu1002 name
9)、hkeys
语法:hkeys key
作用:查看哈希表 key 中的所有 field 域
返回值:包含所有 field 的列表,key 不存在返回空列表
hkeys stu1001
hkeys stu1002
10)、hvals
语法:hvals key
作用:返回哈希表 中所有域的值
返回值:包含哈希表所有域值的列表,key 不存在返回空列表
hvals stu1001
hvals stu1002
11)、hincrby key field int:对指定哈希表中指定field值进行整数加法运算
hincrby stu1001 age 5
12)、hincrbyfloat key field float:对指定哈希表中指定field值进行浮点数加法运算
hset stu1001 score 80.5
hincrbyfloat stu1001 score 5.5
13)、hsetnx key field value:将一个field-vlaue对设置到哈希表中,当key-field已经存在时,则放弃设置;否则,设置file-value
hsetnx stu1001 age 30
6、list类型:等同于java中的ArrayList。特点:排序、可重复、通过下标访问、可以通过下标指定插入位置(开头/中间/结尾)。
一个key对应多个value;
多个value之间有顺序,最左侧是表头,最右侧是表尾;
每一个元素都有下标,表头元素的下标是0,依次往后排序,最后一个元素下标是列表长度-1;
每一个元素的下标又可以用负数表示,负下标表示从表尾计算,最后一个元素下标用-1表示;
元素在列表中的顺序或者下标由放入的顺序来决定。
通过key和下标来操作数据。
1)、lpush
语法:lpush key value [value…]
作用:将一个或多个值 value 插入到列表 key 的表头(最左边),从左边开始加入值,从左
到右的顺序依次插入到表头
返回值:数字,新列表的长度
lpush list01 1 2 3 结果:3 2 1
lpush list01 4 5 结果:5 4 3 2 1
2)、rpush
语法:rpush key value [value…]
作用:将一个或多个值 value 插入到列表 key 的表尾(最右边),各个 value 值按从左到右
的顺序依次插入到表尾
返回值:数字,新列表的长度
rpush list02 a b c 结果:a b c
rpush list02 d e 结果:a b c d e
lpush list02 m n 结果: n m a b c d e
3)、lrange:
语法:lrange key start stop
作用:获取列表 key 中指定区间内的元素,0 表示列表的第一个元素,以 1 表示列表的第
二个元素;start , stop 是列表的下标值,也可以负数的下标, -1 表示列表的最后一
个元素, -2 表示列表的倒数第二个元素,以此类推。start ,stop 超出列表的范围不
会出现错误。
返回值:指定区间的列表
lrange list01 1 3 结果:4 3 2
lrange list01 1 -2 结果: 4 3 2
lrange list01 0 -1 结果:5 4 3 2 1
4)、lpop key:从指定列表中移除并且返回表头元素
lpop list02
5)、rpop key:从指定列表中移除并且返回表尾元素
rpop list02
6)、lindex
语法:lindex key index
作用:获取列表 key 中下标为指定 index 的元素,列表元素不删除,只是查询。0 表示列
表的第一个元素,以 1 表示列表的第二个元素;start , stop 是列表的下标值,也可
以负数的下标, -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
返回值:指定下标的元素;index 不在列表范围,返回 nil
lindex list01 2 结果:3
7)、llen key
语法:llen key
作用:获取列表 key 的长度
返回值:数值,列表的长度;key 不存在返回 0
llen list01
8)、lrem
语法:lrem key count value
作用:根据参数 count 的值,移除列表中与参数 value 相等的元素,count >0 ,从列表的
左侧向右开始移除;count < 0 从列表的尾部开始移除;count = 0 移除表中所有与 value 相等的值。
返回值:数值,移除的元素个数
lpush list03 a a b c a d e a b b 结果:b b a e d a c b a a
lrem list03 2 a 结果:b b e d c b a a
lrem list03 -1 a 结果:b b e d c b a
lrem list03 0 a 结果:b b e d c b
9)、ltrim key startIndex endIndex截取指定列表中指定下标区间的元素组成新的列表,并且赋值给key
lpush list04 1 2 3 4 5 结果:5 4 3 2 1
ltrim list04 1 3
lrange list04 0 -1 结果:4 3 2
10)、lset
语法:lset key index value
作用:将列表 key 下标为 index 的元素的值设置为 value。
返回值:设置成功返回 ok ; key 不存在或者 index 超出范围返回错误信息
lset list04 1 10
11)、linsert
语法:linsert key BEFORE|AFTER pivot value
作用:将值 value 插入到列表 key 当中位于值 pivot 之前或之后的位置。key 不存在,pivot
不在列表中,不执行任何操作。
返回值:命令执行成功,返回新列表的长度。没有找到 pivot 返回 -1, key 不存在返回 0。
linsert list04 before 10 50
linsert list04 after 10 60
7、set类型:等同于java中的Set,特点:无序、不可重复(唯一)
一个key对应多个vlaue;
value之间没有顺序,并且不能重复;
通过业务数据直接操作集合。
1)、sadd
语法:sadd key member [member…]
作用:将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略,不会再加入。
返回值:加入到集合的新元素的个数。不包括被忽略的元素。
sadd set01 a b c a 结果:a b c
sadd set01 b d e
2)、smembers
语法:smembers key
作用:获取集合 key 中的所有成员元素,不存在的 key 视为空集合
smembers set01
3)、sismember
语法:sismember key member
作用:判断 member 元素是否是集合 key 的成员
返回值:member 是集合成员返回 1,其他返回 0 。
sismember set01 f
sismember set01 a
4)、scard
语法:scard key
作用:获取集合里面的元素个数
返回值:数字,key 的元素个数。其他情况返回 0 。
scard set01
5)、srem
语法:srem key member [member…]
作用:删除集合 key 中的一个或多个 member 元素,不存在的元素被忽略。
返回值:数字,成功删除的元素个数,不包括被忽略的元素。
srem set01 b d m
6)、srandmember
语法:srandmember key [count]
作用:只提供 key,随机返回集合中一个元素,元素不删除,依然在集合中;提供了 count
时,count 正数, 返回包含 count 个数元素的集合,集合元素各不相同。count 是负数,
返回一个 count 绝对值的长度的集合,集合中元素可能会重复多次。
返回值:一个元素;多个元素的集合
sadd set02 1 2 3 4 5 6 7 8
srandmember set02
srandmember set02 3
srandmember set02 -3
7)、spop
语法:spop key [count]
作用:随机从集合中删除一个元素, count 是删除的元素个数。
返回值:被删除的元素,key 不存在或空集合返回 nil
spop set02
8)、smove source dest member:将指定集合中的指定元素移动到另一个元素
smove set01 set02 a
9)、sdiff key key [key key ....]:获取第一个集合中有、但是其它集合中都没有的元素组成的新集合
sdiff set01 set02 set03
10)、sinter key key [key key ....]:获取所有指定集合中都有的元素组成的新集合
sinter set01 set02 set03
11)、sunion key key [key key .....]:获取所有指定集合中所有元素组成的大集合
sunion set01 set02 set03
8、zset类型:有序集合,即Set类型,特点:有序、不可重复(唯一)
redis 有序集合zset和集合set一样也是string类型元素的集合,且不允许重复的成员。
不同的是 zset 的每个元素都会关联一个分数(分数可以重复),redis 通过分数来为集合中的成员进行从小到大的排序。
1)、zadd
语法:zadd key score member [score member…]
作用:将一个或多个 member 元素及其 score 值加入到有序集合 key 中,如果 member 存
在集合中,则更新值;score 可以是整数或浮点数
返回值:数字,新添加的元素个数
zadd zset01 20 z1 30 z2 50 z3 40 z4
zadd zset01 60 z2
2)、zrange
语法:zrange key start stop [WITHSCORES]
作用:查询有序集合,指定区间的内的元素。集合成员按 score 值从小到大来排序。start,
stop 都是从 0 开始。0 是第一个元素,1 是第二个元素,依次类推。以 -1 表示最后一
个成员,-2 表示倒数第二个成员。WITHSCORES 选项让 score 和 value 一同返回。
返回值:自定区间的成员集合
zrange zset01 0 -1
zrange zset01 0 -1 withscores
3)、zrevrange
语法:zrevrange key start stop [WITHSCORES]
作用:返回有序集 key 中,指定区间内的成员。其中成员的位置按 score 值递减(从大到小)来排列。其它同 zrange 命令。
返回值:自定区间的成员集合
zrevrange zset01 0 -1
zrevrange zset01 0 -1 withscores
4)、zrangebyscore
语法:zrangebyscore key min max [WITHSCORES ] [LIMIT offset count]
作用:获取有序集 key 中,所有 score 值介于 min 和 max 之间(包括 min 和 max)的成
员,有序成员是按递增(从小到大)排序。
min ,max 是包括在内,使用符号( 表示不包括。 min, max 可以使用 -inf ,+inf 表示最小和最大
limit 用来限制返回结果的数量和区间。
withscores 显示 score 和 value
返回值:指定区间的集合数据
zrangebyscore zset01 30 50 withscores
zrangebyscore zset01 (30 (50 withscores
zrangebyscore zset01 (30 50 withscores
zrangebyscore zset01 (30 +inf withscores
zrangebyscore zset01 -inf +inf withscores limit 1 2
5)、zrevrangebyscore
语法:zrevrangebyscore key max min [WITHSCORES ] [LIMIT offset count]
作用:返回有序集 key 中, score 值介于 max 和 min 之间(默认包括等于 max 或 min )的所有
的成员。有序集成员按 score 值递减(从大到小)的次序排列。其他同 zrangebyscore
6)、zrem
语法:zrem key member [member…]
作用:删除有序集合 key 中的一个或多个成员,不存在的成员被忽略
返回值:被成功删除的成员数量,不包括被忽略的成员。
zrem zset01 z3 z4
7)、zcard
语法:zcard key
作用:获取有序集 key 的元素成员的个数
返回值:key 存在返回集合元素的个数, key 不存在,返回 0
zcard zset01
8)、zcount
语法:zcount key min max
作用:返回有序集 key 中,score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量
zcount zset01 20 50
9)、zrank key member:获取指定有序集合中指定元素的排名(排名从0开始)
zrank zset01 z4 ===>2
10)、zscore key member:获取指定有序集合中指定元素的分数
zscore zset01 z4
11)、zrevrank key member:获取指定有序集合中指定元素的排名(按照分数从大到小的排名)
zrevrank zset01 z4 ===>1
二 需求调研、需求分析(即应用场景)
Redis 是当今非常流行的基于 KV 结构的作为 Cache 使用的 NoSQL 数据库
随着互联网的高速发展,而互联网应用程序的特征有:
- 用户量大,访问量大
- 高并发,资源竞争大。如电商秒杀
说明:并发指的是多个用户同时做一件事情。
-
高可用
说明:高可用是指系统能够持续稳定地运行。比如,系统的某一个部分损坏了,比如说数据库崩溃了,运维人员要在极短的时间内修复,让系统能够稳定安全地运行。
-
海量数据,如微信用户7亿元,每天要存储的聊天记录。
传统的数据在处理这些互联网应用的问题时,显得力不从心。而nosql数据库是为了解决以上所列的互联网应用程序的痛点的。
nosql数据库的优势:
- 大数据量,高性能。即能够处理海量数据,性能还很好。
- 灵活的数据模型。第一:明确一点,nosql数据中的数据是独立的,无联系的。第二:nosql数据库数据的内容和结构可以是任意的,不像传统数据库有字段、字段还限制长度和类型。
- 高可用。nosql数据库有它自己的高可用解决方案(如redis的哨兵模式),能够在数据库发生问题时,能够快速地自动的解决问题。
- 成本低。第一:开源。第二:对硬件不挑。
- 可以做为缓存减少对数据库的访问量。
nosql数据库的劣势:
- 无关系,数据之间是无联系的。在传统的数据库中,两个销售部的工作人员,在要在员工表和部门表分别存储员工和部门的信息就可以了,后面用外键进行关联员工和部门表。但在nosql数据库中,为了灵活的数据模型,数据是独立存在的,无任何联系。所以两个销售部的员工,在nosql数据库中必须冗余地存储销售部部门的信息。而且在修改数据的时候,比如说这两个人都转部门了,那么就要同时修改这两条数据。
- 不支持标准的 SQL,没有公认的 NoSQL 标准。因为没有Nosql标准,所以学习成本高,因为不同的nosql数据库可能在设计和实现上完全不同,如redis和MongoDb就完全不同,你得一个一个学习。而我们的传统数据库,至少遵循了标准的sql语句吧,sql语句是通用的,所以学了一个数据库,其它进了一样。
- 没有关系型数据库的约束,大多数也没有索引的概念。传统数据库有线束,比如约束年龄列是0~100吧。而nosql数据库就没有约束的概念,年龄来个2000都行。
- 没有事务,不能依靠事务实现 ACID。现在的nosql数据库没有真正意义上的事务或者说nosql数据库没有等同于传统关系型数据库的事务。比如做事务回滚等功能,nosql数据库就做不到。
- 没有丰富的数据类型(数值,日期,字符,二进制,大文本等)。关系型数据库有的字段类型有数值,日期,字符,二进制,大文本等,而nosql数据库就只是文本。
案例:
三 框架的设计思想
- nosql是一个概念,not only sql,不仅仅是sql。 “不仅仅是sql”:传统的数据库(mysql)只是用来存储数据的,并使用sql语句来对数据库的数据进行CRUD操作的。notsql数据库不仅仅只是能够存储数据,不仅仅只能对数据库的数据进行CRUD操作的,它还有其他的功能,即nosql数据库比传统的数据库还多了一些功能在里边,它还能干别的。比如在dubbo课程中,可以使用redis作为注册中心使用。
- nosql,也解释为 non-relational(非关系型数据库)。说明市面上有两大类的数据库,RDBMS关系型数据库(如mysql、oracle、sqlserver等)和NoSql非关系型数据库(如redis、 MongoDb等)。
四 体系的组织结构设计(重要组件、模块划分、模块间交互)
五 工作原理、运行流程
- keys *少用。因为redis接受请求的方式是单线程的,然后才会把请求分发给redis内部的其它线程或进程来处理。那就意味着发送给redis的所有命令都得排队接受和处理,如果数据量一大keys *就比较耗时,其它命令的处理就会很多滞后。
- redis是一种数据库,redis的数据是存储在内存中,基于KV结构的。redis常作为 Cache(缓存) 使用。Cache(缓存)指的是内存中存放的数据。放入内存中的数据,我们一般称之为Cache(缓存)数据。用户经常访问的数据,我们一般会放到内存中,这样访问数据就会很多快速、性能与相对好一些。
- redis是一个中间件: 是一个独立的服务器。也就是说,redis也可以在不需要我们对它做任何的处理的情况下单独使用的。
中间件的定义:中间件是一个独立的服务器,是能够独立提供某一个功能的,如redis、tomcat(dljd-springboot)。
框架的定义: 不能单独使用,是在我们的程序代码中使用的,如mybaits,springboot。(dljd-springboot)。 - redis提供了很多客户端(工具库》一堆.jar包》类和方法》已经完成与redis的交互工作),用于访问网络中的redis数据库。在开发过程中,我们要在程序代码中必须使用访问redis的客户端(工具库》一堆.jar包》类和方法》已经完成与redis的交互工作)(如JRedis、lettuce、Redisson),才能访问redis,并对redis进行CRUD操作。(dljd-springboot)。
说明:为什么需要客户端呢?redis是一个网络应用,有服务端,也有客户端。redis服务端启动之后,怎么去访问服务端的数据呢?要去启动客户端,因为你服务端你看不见,也摸不着,怎么操作数据呢,需要使用客户端。要想使用redis,首先要在windows或linux中启动服务端。谁是redis的服务端呢?windwos中redis的安装目录下的redis-server.exe就是redis的服务端,双击即可。谁是redis的客户端呢?windows中redis安装目录下的redis-cli.exe,就是redis自带的客户端程序,也是双击即可。
六 详细设计:实现方法(技术)
七 使用说明1:redis.conf
1)、redis安装完成之后,在redis的根目录下提供一个配置文件(redis.conf);
redis.conf中可以配置一些redis服务端运行时的一些参数;
redis服务可以参考配置文件中的参数进行运行;
如果不使用配置文件,那么redis会按照默认的参数运行;
如果使用配置文件,在启动redis服务时必须指定所使用的配置文件。
*强调*:只有启动redis服务器指定使用的配置文件,参数才会生效;否则,redis会采用默认的参数运行。
2)、redis配置信息:
port:配置redis服务运行的端口号;如果不配置port,则redis服务默认使用6379端口。
bind: redis服务被客户端连接时,客户端所能使用的ip地址。默认情况下,不配置bind,客户端连接redis服务时,通过服务器上任何一个ip都能连接到redis服务;一旦配置了bind,客户端就只能通过bind指定的ip地址连接redis服务。一般情况下,bind都是配置服务器上某一个真实ip。
*强调*:一旦redis服务配置了port和bind(如果port不是6379、bind也不是127.0.0.1),客户端连接redis服务时,就要指定端口和ip:
redis-cli :默认连接127.0.0.1本机上的6379端口服务
redis-cli -h 127.0.0.1 -p 6379:同上
redis-cli -h 192.168.11.128 -p 6379:
redis-cli:默认连接127.0.0.1上的6379服务
redis-cli -h bind绑定的ip地址 -p port设置的端口:连接bind绑定的ip地址主机上的port设置的端口redis服务;
关闭redis服务时:redis-cli -h bind绑定的ip地址 -p port设置的端口 shutdown
3)、redis配置文件中关于网络的配置:
port:指定redis服务所使用的端口,默认使用6379。
bind: 配置客户端连接redis服务时,所能使用的ip地址,默认可以使用redis服务所在主机上任何一个ip都可以;一般情况下,都会配置一个ip,而且通常是一个真实。
如果配置了port和bind,则客户端连接redis服务时,必须指定端口和ip:
redis-cli -h 192.168.11.128 -p 6380
redis-cli -h 192.168.11.128 -p 6380 shutdown
tcp-keepalive:连接保活策略。
4)、常规配置:
loglevel:配置日志级别,开发阶段配置debug,上线阶段配置notice或者warning.
logfile:指定日志文件。redis在运行过程中,会输出一些日志信息;默认情况下,这些日志信息会输出到控制台;我们可以使用logfile配置日志文件,使redis把日志信息输出到指定文件中。
databases:配置redis服务默认创建的数据库实例个数,默认值是16。
5)、安全配置:
requirepass:设置访问redis服务时所使用的密码;默认不使用。
此参数必须在protected-mode=yes时才起作用。
一旦设置了密码验证,客户端连接redis服务时,必须使用密码连接:redis-cli -h ip -p port -a pwd
八 使用说明2:服务器端的配置和管理
1、序言
高级话题是指:对服务器端的配置和管理。高级话题里边,大多都是对服务器端的配置和管理,一般都是公司中的技术经理、技术总监等级的人员去完成的工作。整个高级话题部分跟开发的关系很少,主要是服务器端的控制和管理。这里边,只有事务跟写代码有一点关系。
2、事务
(1)redis事务的概念
Redis 中的事务(transaction)是一组命令的集合,至少是两个或两个以上的命令,redis 事务保证这些命令被执行时中间不会被任何其他操作打断。
说明:Redis 中事务的概念不等同于传统数据库(mysql、oracle、sqlserver)中事务的概念。Redis 的中事务指:把redis中的一些命令放在一起做为一个整体、一个集合来执行,redis能保证在事务之中的这些命令是一定都能被执行,也就是说redis能保证事务之中的语句一定都能被执行。如果你的操作是要求事务之中的命令一定都能被执行成功,那就可以使用redis的事务。
注意,在这里边老师没有提到回滚吧。在redis的事务中,没有回滚这个概念。为什么没有回滚的概念呢?可能因为redis的作者想要把redis的执行效率和性能放在第一位,所以没有设计回滚。你想,如果有回滚的话,像mysql那样先得把数据保存一个旧值(原来的数据),即事务执行之前的数据结果,然后执行出现错误,才能回滚到原来的数据和状态。但这个过程,大家想一想,是不是很复杂、很影响性能和执行效率。所以redis的作者为了保证执行效率和性能,没有搞这个回滚的功能。
(2)redis事务中的命令
1)、multi
语法: multi
作用:标记一个事务的开始。事务内的多条命令会按照先后顺序被放进一个队列当中,并没有立即执行。
返回值:总是返回 ok
multi
set k1 v1
set k2 v2
2)、exec
语法:exec
作用:标志着事务的提交,即真正地去执行事务块内的所有redis命令
返回值:输出事务内的所有执行语句返回的结果(内容)的列表,如果事务被打断则返回 nil
exec
3)、discard
语法:watch key [key ...]
作用:表示放弃事务,取消redis事务中所有redis命令的执行。监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
返回值:总是返回 ok
multi
set k5 v5
set k6 v6
discard
5)、watch
语法:watch key [key ...]
作用:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key被其他命令所改动,那么事务将被打断。
返回值:总是返回 ok
id balance version
update table set balance=balance-dept,version=version+1
where id=xxxx and version=100
set balance 100
set balance2 1000
set version 1
watch version
multi
decrby balance 50
incrby balance2 50
exec
6)unwatch
语法:unwatch
作用:取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。
返回值:总是返回 ok
watch version
unwach
multi
decrby balance 50
incrby balance2 50
exec
(3)redis对事务实现的5种情况
- multi:用 multi 命令告诉 Redis,接下来要执行的命令你先不要执行,而是把它们暂时存起来(开启事务)
- saddworks john 第一条命令进入等待队列(命令入队)
- sadd works rose 第二条命令进入等待队列(命令入队)
- exce 告知 redis 执行前面发送的两条命令(提交事务)
查看 works 集合
- MULTI 正常命令
- SET key value 正常命令
- INCR 命令语法错误
-
EXEC 无法执行事务,那么第一条正确的命令也不会执行,所以 key 的值不会设置成功
![](https://img-blog.csdnimg.cn/772423b14d954693bb62021d1d6e27dd.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWl3b2thY2hl,size_20,color_FFFFFF,t_70,g_se,x_16)
结论:事务执行 exec 之前,入队命令错误,事务终止,取消,不执行。
3)、运行时错误:事务执行 exec 命令后,命令执行错误,事务提交
- MULTI 正常命令
- SET username zhangsan 正常命令
- lpop username 正常命令,语法没有错误,执行命令时才会有错误。
- EXEC 正常执行,发现错误可以在事务提交前放弃事务,执行 discard.
Redis 在事务失败时不进行回滚,而是继续执行余下的命令。
- MULTI 开启事务
- SET age 25 命令入队
- SET age 30 命令入队
- DISCARD 放弃事务,则命令队列不会被执行
![](https://img-blog.csdnimg.cn/5126db60450f48b2b30e693f5f276535.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWl3b2thY2hl,size_18,color_FFFFFF,t_70,g_se,x_16)
4)、Redis 的 watch 机制
说明:使用传统数据库时,经常提到的概念是锁吧。为了保证数据在多事务的情况下,能够保持安全性、准确性、一致性,可以给数据进行上锁操作。一般情况下,都会使用独占的上锁方式,这时其它的事务得等着当前的事务完成后才能继续进行,即所有的事务都得排队依次执行,这种情况下执行效率是很低的。
而在redis这里,执行效率是第一位的,怎么能够提高redis对事务执行的效率呢?我们可以通过redis提供的watch机制来实现。
![](https://img-blog.csdnimg.cn/9588267652a046739ac1f2840b565ce5.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWl3b2thY2hl,size_13,color_FFFFFF,t_70,g_se,x_16)
首先,A事务要修改a,那么它先执行watch a来监视它的值,有没有被别人改。
然后,在watch a监控命令之后,A启动一个事务去改a所对应的值。在A事务提交之:
如果通过watch a监控命令监视到A事务中a所对应的值被修改了,那么事务A就会被放弃、取消。由于事务A取消了,那么其他的事务如事务B/事务C/事务D会正常地执行下去。这种机制是:A事务主动放弃了本事务的所有操作(命令),主动放弃修改,这样就避免了多个事务同时竞争同一个资源的情况。
如果通过watch a监控命令监视到A事务中a所对应的值没有被修改,那就说明没有资源的竞争,那么事务A就可以顺利地执行,顺利地修改数据了。
总结,redis中watch机制没有对数据进行上锁,那么redis什么时候改数据,什么时候不改数据呢?redis主要是通过watch来监视一个key所对应的值。如果key对应的值没被人改过,就表明没有资源竞争,那么当前事务顺序执行。如果key对应的值被人修改了,为了保证数据的准确性、一致性,g 我们说一次只有一个人改最安全吧,这时当前事务就取消,给其它事务修改的权利,这样就保持了数据的准确性、一致性。如果事务A想修改,下次再开启新的事务,再去修改。
通过watch机制,首先不用对数据上锁,其次提高了redis的性能,同时能够保证数据的安全性、准确性和一致性。
最后说明一下,redis的watch机制如果对标mysql的话,就相当于mysql的乐观锁机制。
![](https://img-blog.csdnimg.cn/dd7c09881ab94ed68b61b06ae384b338.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWl3b2thY2hl,size_20,color_FFFFFF,t_70,g_se,x_16)
A 客户端(红色):WATCH 某个 key,同时执行事务 :
![](https://img-blog.csdnimg.cn/661f25c64eaa41568e04b5d796120da9.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWl3b2thY2hl,size_20,color_FFFFFF,t_70,g_se,x_16)
B 客户端(黄色):对 A 客户端 WATCH 的 key 修改其 value 值。
3、持久化策略
(1)持久化概念
(2)RDB:有丢失数据的风险,适合对数据不怎么严格的情况
说明:
Redis Database(RDB),就是在指定的时间间隔内将内存中的数据写到磁盘的二进制文件中,即把此时内存中的当前数据的情况写到磁盘文件中,所以RDB机制也称为 内存快照。
快照,即日常生活中的拍照片呗。拍照片,就是把你当前此刻下的动作、表情给拍下来了。内存快照也是这个意思,即把当前内存中的所有数据的情况写到磁盘文件中。
rdb持久化机制默认是开启的。
RDB配置:redis.conf
RDB:指定时间间隔内,redis服务执行指定次数的写操作,会自动触发一次持久化操作。
RDB策略是redis默认的持久化策略,redis服务开启时这种持久化策略就已经默认开启了。
save <seconds> <changes>:配置持久化策略。多个save的情况下,只要其中一个save满足,就持久化。如果只有save "",表明关闭rdb机制。
dbfilename:配置redis RDB持久化数据存储的文件名,默认文件名为 dump.rdb
dir: 配置redis RDB持久化文件所在目录,默认是 ./ 当前目录
rdb机制优点:适合做备份,因为二进制文件dump.rdb读写速度快。
rdb机制的缺点:
(3)AOF:无数据丢失风险,适合对数据非常严格的情况
说明:
aof持久化方式是指:redis把对每一个对数据的修改命令和数据,同时记录到磁盘文件中。这个文件是一个文本文件,因此可以查看、也可以修改。当redis重新启动时,redis会把aof所生成的文本文件中的redis命令重新执行一遍,用来恢复内存中的数据。
aof默认情况不开启,得通过配置开启。
AOF策略:采用操作日志来记录进行每一次写操作,每次redis服务启动时,都会重新执行一遍操作日志中的指令。
特点:效率低下,redis默认不开启AOF功能。
配置:
①:appendonly:默认是 no,改成 yes 即开启了 aof 持久化
②:appendfilename:指定 AOF 文件名,默认文件名为 appendonly.aof
小结:根据数据的特点决定开启哪种持久化策略;
一般情况,开启RDB足够了。
4、集群 - 主从复制
(1)需求调研、需求分析(即应用场景
d说明:主从复制是redis自己提供的一种高可用的解决方案。通过redis自己提供的主从复制这种解决方案,可以实现在某个redis发生故障生以后,在主从复制这种结构下,可以实现使用其它的redis服务来代替发生故障的redis,从而继续为系统提供redis的相关服务和功能。
(2)高可用解决方案的设计思想、体系的组织结构设计、工作原理、运行流程
d说明:集群:指的是多台服务器端的一种(网络)结构,多台服务器在里面充当的是同一种功能。
d说明:主从复制高可用的解决方案,它是redis服务器端的一种配置方式,它也是一种多台redis服务器端的一种集群,集群的结构如下图:
(3)实现方法
d说明:只要是通过配置多台redis服务器的配置文件,并且标明谁是主服务器,哪些是从服务器,redis就会自动帮我们实现主从复制的功能了。
(4)使用说明:常用配置
redis的主从复制:主少从多、主写从读、读写分离、主写同步复制到从。
1)、搭建三台redis服务:使用一个redis模拟三台redis服务
提供三分redis配置文件:redis6379.conf、redis6380.conf、redis6381.conf
修改三份配置文件:以redis6379.conf为例
bind 127.0.0.1
port 6379
pidfile /var/run/redis_6379.pid
logfile "6379.log"
dbfilename dump6379.rdb
分别使用三个redis配置文件,启动三个redis服务:
redis-server redis6379.conf &
redis-server redis6380.conf &
redis-server redis6381.conf &
2)、通过redis客户端分别连接三台redis服务:
redis-cli -h 127.0.0.1 -p 6379
redis-cli -h 127.0.0.1 -p 6380
redis-cli -h 127.0.0.1 -p 6381
3)、查看三台redis服务在集群中的主从角色:
info replication
默认情况下,所有的redis服务都是主机,即都能写和读,但是都还没有从机。
4)、先在6379进行写操作:
set k1 v1
三台rdis服务互相独立,互不影响。
5)、设置主从关系:设从不设主
在6380上执行:slaveof 127.0.0.1 6379
在6381上执行:slaveof 127.0.0.1 6379
6、)全量复制:一旦主从关系确定,会自动把主库上已有的数据同步复制到从库。
在6380和6381上执行:keys *
7)、增量复制:主库写数据会自动同步到从库。
在6379上执行:set k2 v2
在6380和6381上执行:keys *
8)、主写从读,读写分离:
在6380和6381上执行:set k3 v3 ===>报错
9)、主机宕机、从机原地待命:
关闭6379服务:redis-cli -h 127.0.0.1 -p 6379 shutdown
查看6380和6381服务的主从角色:info replication
10)、主机恢复、一切恢复正常:
重启6379服务:redis-server redis6379.conf &
客户端连接6379:redis-cli -h 127.0.0.1 -p 6379
11)、从机宕机、主机少一个从机、其它从机不变:
关闭6380服务: redis-cli -h 127.0.0.1 -p 6380 shutdown
查看6379服务的主从角色:info replication
查看6381服务的主从角色:info replication
12)、从机恢复、需要重新设置主从关系:
重启6380服务:redis-server redis6380.conf &
客户端连接6380:redis-cli -h 127.0.0.1 -p 6380
在6380上执行: slaveof 127.0.0.1 6379
13)、从机上位:
a)主机宕机、从机原地待命:
关闭6379服务:redis-cli -h 127.0.0.1 -p 6379 shutdown
查看6380和6381服务的主从角色:info replication
b)从机断开原来主从关系:
在6380上执行:slaveof no one
查看6380服务的主从角色:info replication
c)重新设置主从关系:
在6381上执行:slaveof 127.0.0.1 6380
d)之前主机恢复、变成孤家寡人:
重启6379服务:redis-server redis6379.conf &
客户端连接6379:redis-cli -h 127.0.0.1 -p 6379
e)天堂变地狱:
在6379上执行:slaveof 127.0.0.1 6381
在6381上执行:info replication 既是主机又是从机
小结:一台主机配置多台从机,一台从机又可以配置多台从机,从而形成一个庞大的集群架构。
减轻一台主机的压力,但是增加了服务间的延迟时间。
5、集群 - 主从复制 + 哨兵模式
(1)需求调研、需求分析(即应用场景)
d说明:刚才主从复制的容灾处理是通过人为地手动编写命令的方式来实现的,这种方式对故障的处理是存在一定延迟的。
(2)框架的设计思想
d说明:为了避免这种延迟,redis又开发了一套高可用的解决方案。这套解决方案是自动化的容灾处理方案,即不用从为地手动编写命令来实现主从复制的容灾处理,那么这套高可用的解决方案就是我们所说的:高可用Sentinel 哨兵。
(3)体系结构、工作原理、运行流程、实现方法
d说明:哨兵实际上就是一个应用程序,它是redis官网提供的,一种在特殊模式下运行的reids服务器。这个reids服务器不接收用户的数据请求,即不处理数据,只是做为其它redis服务器运行的监控程序。
Sentinel 系统有三个主要任务:
![](https://img-blog.csdnimg.cn/5d67e101482544a5ba965997a936f087.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWl3b2thY2hl,size_20,color_FFFFFF,t_70,g_se,x_16)
d说明: 上图为哨兵、主服务器、从服务器工作的关系图。从图中可以看出,有3个哨兵,需要注意的是:哨兵的个数是奇数个的,同时哨兵的个数至少是3个,比如3个、5个、7个、9个......。哨兵默认使用的端口号是26379,当然也可以自定义。
哨兵应该是在独立的硬件服务器上来运行的。也就是说,我们应该准备不同的硬件服务器,在这些硬件服务器上单独来运行哨兵。这样一来,就不会出现一台哨兵崩溃其它哨兵也同时崩溃的情况了。每个哨兵都需要在独立的模式下运行的,不能互相影响。
多个哨兵彼此之间是能够进行通讯的,这个通讯主要是用于交换监控的结果的。
多个哨兵都是独自去监控主redis的运行状况的,而从主redis就能够间接地监控了从redis的运行状况了,也就是每个哨兵都可以监控到主、从redis的运行状况。
那哨兵是如何去监控redis的运行状况呢?采用的是“心跳”机制来进行监控的。每隔多少时间,就主动向redis发出一个请求,比如说发送ping命令,如果返回的是pone表明redis还在正常工作,反之说明redis已经挂掉了。
那什么时候哨兵会自动进行主从复制的容灾处理呢?哨兵one没有收到心跳,哨兵one会投1票认为主redis服务器挂掉了;哨兵two没有收到心跳,哨兵two也会投1票认为主redis服务器挂掉了;现在3个哨兵中,已经有2哨兵个表明主redis挂掉了,这时不管第3个哨兵监控的结果如何,都要进行主从复制的容灾处理了。哨兵主要使用的原则是,少数服从多数,所以哨兵是奇数个的。
(4)使用说明:常用配置
1)搭建一主二从集群架构:(17节前五步)
2)提供哨兵配置文件:
在redis安装目下创建配置文件:redis_sentinel.conf
sentinel monitor dc-redis 127.0.0.1 6379 1
3)启动哨兵服务:
redis-sentinel redis_sentinel.conf
4)主机宕机:
关闭6379服务:redis-cli -h 127.0.0.1 -p 6379 shutdown
哨兵程序自动选择从机上位。
5)之前主机恢复:自动从属于新的主机。
重启6379服务:redis-server redis6379.conf &
客户端连接6379:redis-cli -h 127.0.0.1 -p 6379
6、安全设置
(1) 设置密码
![](https://img-blog.csdnimg.cn/97dc765207014ff0ac3cc8995880f5fb.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWl3b2thY2hl,size_13,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/350c3dbdf84f49f2bb09fc90ae453d2c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWl3b2thY2hl,size_20,color_FFFFFF,t_70,g_se,x_16)
使用①访问
![](https://img-blog.csdnimg.cn/9ad6e9a3669f40a3af9c6ab75bad77a3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWl3b2thY2hl,size_20,color_FFFFFF,t_70,g_se,x_16)
输入命令 auth 密码
![](https://img-blog.csdnimg.cn/7e0eee6504aa481384078d0b8c26adbf.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWl3b2thY2hl,size_20,color_FFFFFF,t_70,g_se,x_16)
使用②方式
(2)绑定ip
(3) 修改默认端口
7、redis消息的发布与订阅
(1)需求调研、需求分析(即应用场景)
d说明:redis客户端(包括redis-cli、程序代码使用Jedis)之间需要通信。
(2)框架的设计思想、体系的组织结构设计(重要组件、模块划分、模块间交互)、工作原理、运行流程、实现方法(技术)
redis客户端订阅频道,消息的发布者往频道上发布消息,所有订阅此频道的客户端都能够接受到消息。
d说明:redis消息的发布与订阅解决什么问题呢?有一个redis服务器端,然后有很多的redis客户端(包括redis-cli、程序代码使用Jedis)去连接这个服务端,此时这些redis客户端都可以对redis服务中的数据进行crud。假如redis服务器有一条数据,此时有某一个客户端修改了这条数据,这种情况下其它的客户端不能实时地知道这条数据被修改过,只有在调用get命令以后才能知道这条数据被修改了。那怎么办呢?就是说,其中的某个redis客户端修改了reids服务器的某一条记录以后,要发送一个通知给其它的redis客户端。其它的redis客户端在收到修改通知以后,才能对数据被修改采取相应的计算(措施)。其它的redis客户端在接收到数据被修改通知,并采取了相应的计算(措施)以后,还可以向之前修改这条数据的redis客户端返回一个通知,表示已经收到数据被修改通知,并做出相应的处理。
为了满足以上的需求,实现redis客户端(包括redis-cli、程序代码使用Jedis)之间需要通信,redis官网提供了高可用的redis的消息发布与订阅机制的解决方案。redis中有一个频道(广播频道、电视频道)的概念,redis中可以定义频道,频道号是随便定义的。首先,得定义一个频道。其次,有多个其它的redis客户端对redis服务器端的某条数据感兴趣,那么这多个客户端都要订阅这个频道(号)。接着,只要这个频道上有消息,订阅这个频道的所有redis客户端都能收到这个消息。然后,某一个redis客户端修改了服务器中的这条数据,就会向这个频道上发送修改数据的消息。最后,redis服务器会自动地把这个修改数据的消息发送给订阅了这个频道的所有的redis客户端。这就是redis消息的发布与订阅。
各个客户端都可以订阅自己感兴趣的频道;每一个频道上面,都会绑定某一种特殊的消息,当redis服务器的某条数据修改了或者某个reids客户端想发布这样的消息时,对这种消息感兴趣的客户端都能收到这个消息。
如下图所示:
(4)使用说明:常用配置
d说明:注:这个技术使用得不多,即使多个redis客户端真的需要通信,会使用我们后面学习的中间件ActiveMQRabbi、RabbitMQ来实现,这两个都是专门做消息的发布与订阅的中间件。redis消息的发布与订阅功能太过于单一,且只能做简单消息交互,还不稳定。redis还是用来做缓存比较好。
1)subscribe:订阅一个或者多个频道的消息。
subscribe ch1 ch2 ch3
2)publish:将消息发布到指定频道
publish ch1 hello
3)psubcribe:订阅一个或者多个频道的消息,频道名支持通配符。
subscribe news.*
8、使用redis做注册中心(dubbo学习中提到)
9、使用reids实现限流功能(spring cloud netflix)
九 Jedis客户端的使用
1、需求调研、需求分析(即应用场景)
d说明:在我们的程序代码中,使用Jedis客户端来访问redis数据库、操作redis中的数据。
2、工作原理、运行流程、实现方法(技术)、体系的组织结构设计
- redis关键对象:Jedis jedis = new Jedis("192.168.32.130",6379); jedis.auth();//密码设置
- commons-pool关键对象:
JedisPoolConfig config = new JedisPoolConfig();
pool = new JedisPool(config,host,port,6000);
jedis = jedisPool.getResource();
- Jedis是redis官网提供的,使用java语言编写的工具库。
- Jedis通过连网,连接到指定ip和port的redis服务器。
- 通过Jedis中的类的各种方法来操作redis服务器中的数据。Jedis类提供的操作redis数据的方法的名称、作用,和我们学习的redis命令的名称、作用一模一样。
- Jedis在使用过程中会有线程不安全的问题,所以一般使用Jedis时要一起和线程池Commons-pool一起使用。通过线程池Commons-pool的方式,来使用Jedis对象。Commons-pool是一个线程池,既能提高Jedis的使用效率,又能避免Jedis线程不安全的问题。
- 线程池Commons-pool中存放的是多个Jedis对象。
3、使用说明常用配置
但在命令行中,是用一种编码方式来显示中文:
所以一般情况下,我们会使用工具来查看中文。
十 典型问题:雪崩
十一 典型问题:穿透(dljd_springboot_练习-dubbo-redis-消费者)
1、redis的穿透,如下代码:
/**
* 到redis查询学生,如果redis没有此学生,从数据库查询,把查询到的学生放入到redis。
* 后面再次查询这个学生应该从redis就能获取到。
* @param id
* @return
*/
@Override
public Student queryStudent(Integer id) {
// 从redis查询student , 使用json来存储对象
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 值存为json
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Student>(Student.class));
// 从redis查
Student student = (Student) redisTemplate.opsForValue().get("STUDENT:"+id);
System.out.println("从redis查询数据,student = "+student);
if(student == null ){
// 从mybatis查
student = studentDao.selectStudentById(id);
System.out.println("从数据库查询数据,student = "+student);
// 存到redis
if(student != null){
redisTemplate.opsForValue().set("STUDENT:"+id,student);
}
}
return student;
}
说明:
- 首先,如果我们传进来的id=1000,而数据库中没有id=1000的学生。那么,流程是:1、查询redis没有;2、查询数据库也没有;3、id=1000的学生也就没有存到redis。
- 其次,再次查询传进来的还是id=1000,流程一样
- 接着,再次查询传进来的还是id=1000,流程还一样
- ......,流程都是一样的。
- 最后,我们会发现这种情况下,redis并没有起到帮我们的数据库查询减轻压力的作用,还是不断地查询redis再查询数据库吧,我们称这种情况叫做缓存穿透,这里指的是redis穿透。缓存穿透是指:在缓存中一直没有找到查询的数据,导致做查询时会不停地去访问数据库进行查询。假如,某个黑客做了一个程序,功能是1秒钟查询id=1000十万次,那么最终的结果是我们的数据库(mysql/oracle)会被压死。
2、解决方案
/**
* 注意:redis缓存穿透 解决
* 到redis查询学生,如果redis没有此学生,从数据库查询,把查询到的学生放入到redis。
* 后面再次查询这个学生应该从redis就能获取到。
* @param id
* @return
*/
@Override
public Student queryStudent(Integer id) {
// 从redis查询student , 使用json来存储对象
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 值存为json
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Student>(Student.class));
// 从redis查
Student student = (Student) redisTemplate.opsForValue().get("STUDENT:"+id);
System.out.println("从redis查询数据,student = "+student);
if(student == null ){
// 从mybatis查
student = studentDao.selectStudentById(id);
System.out.println("从数据库查询数据,student = "+student);
// 存到redis
if(student != null){
redisTemplate.opsForValue().set("STUDENT:"+id,student);
}else{
//数据库查不到,在redis添加一个此id默认值
redisTemplate.opsForValue().set("STUDENT:"+id,Student.defaultStudent());
}
}
return student;
}
package com.bjpowernode.model;
import java.io.Serializable;
// 实体类
// 1、实现序列化接口
// 2、添加序列化版本号:private static final long serialVersionUID = 1901229007746699151L;
public class Student implements Serializable {
private static final long serialVersionUID = 1901229007746699151L;
private Integer id;
private String name;
private String phone;
private Integer age;
public Student() {
}
public Student(Integer id, String name, String phone, Integer age) {
this.id = id;
this.name = name;
this.phone = phone;
this.age = age;
}
/**
* 为了解决redis的穿透问题,设置一个student对象的默认值
* @return
*/
public static Student defaultStudent(){
Student student = new Student();
// 按照数据库自动增长,肯定没有0这个id的
student.setId(0);
student.setName("-");
student.setPhone("-");
student.setAge(0);
return student;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
", age=" + age +
'}';
}
}
思路:
- 第一次,查询id=1000的学生。1、查询redis没有;2、查询数据库也没有;3、id=1000的学生也就没有存到redis。
- 第二次,查询id=1000的学生。我们的想法是,不应该让它再去访问数据库进行查询了。直接让它去redis里面查询,有就有,没有就没有,完事了,别再走我们数据库查询了。
- 也就是说,当用户第一次查询id=1000的学生,在redis和mybatis中都没有找到时,我们可以在redis里面创建id=1000这个key对应一个默认值,比如说key = STUDENT:1000,value = null 或者 key = STUDENT:1000,value = 空数据的Student对象 或者 key = STUDENT:1000,value = 有特殊标识的Student对象(如id=0)。这样就用户第二次查询id=1000的学生的时候,通过得到默认值,我们就可以告诉用户没有这个id=1000的学生了,这样缓存不就继续起作用了嘛。