狂神说Redis笔记

1.Redis概述

Remote Dictionary Server(远程字典服务器)

  • Redis中文网

  • Redis与其他key-value缓存产品有以下三个特点Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

  • Redis不仅仅支持简单的 key-value 类型的数据,同时还提供list、set、zset、hash等数据结构的存储。

  • Redis支持数据的备份,即master-slave模式的数据备份。

Redis安装

Windows环境直接下载安装包解压即可。
在这里插入图片描述

Redis基础知识

  • 默认16个数据库,类似数组下标从零开始,初始默认使用零号库,不同的库可以存不同的数据
    查看 redis.conf ,里面有默认的配置
    在这里插入图片描述
Select命令切换数据库: select 1
Dbsize查看当前数据库的key的数量: dbsize
清空当前库: Flushdb
清空全部的库: Flushall

在这里插入图片描述

  • Redis是单线程?
    1) Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了!
    2)Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差!
  • Redis为什么这么快?
    1)以前一直有个误区,以为:高性能服务器 一定是多线程来实现的原因很简单因为误区二导致的:多线程 一定比 单线程 效率高,其实不然!在说这个事前希望大家都能对 CPU 、 内存 、 硬盘的速度都有了解了!
    2)redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处理这个事。在内存的情况下,这个方案就是最佳方案。
    3)因为一次CPU上下文的切换大概在 1500ns 左右。从内存中读取 1MB 的连续数据,耗时大约为 250us,假设1MB的数据由多个线程读取了1000次,那么就有1000次时间上下文的切换,那么就有1500ns * 1000 = 1500us ,我单线程的读完1MB数据才250us ,你光时间上下文的切换就用了1500us了,我还不算你每次读一点数据 的时间。

2.Redis五大数据类型

keys *---> 查看所有的key
exists key --->判断某个key是否存在
type key --->查看你的key是什么类型
move key db ---> 当前库就没有了,被移除了
expire key 秒钟 ---->为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
ttl key --->查看还有多少秒过期,-1 表示永不过期,-2 表示已过期

在这里插入图片描述

2.1字符串String

String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字

  • set、get、del、append、strlen
127.0.0.1:6379> set key1 value1   # 设置值OK
127.0.0.1:6379> get key1          # 获得key
"value1"
127.0.0.1:6379> del key1          # 删除key(integer)
1
127.0.0.1:6379> keys *            # 查看全部的key
(empty list or set)
127.0.0.1:6379> exists key1       # 确保 key1 不存在
(integer) 0
127.0.0.1:6379> append key1 "hello"  # 对不存在的 key 进行 APPEND ,等同于 SET key1 "hello"(integer) 
5     # 字符长度
127.0.0.1:6379> APPEND key1 "-2333"  # 对已存在的字符串进行 APPEND(integer) 
10    # 长度从 5 个字符增加到 10 个字符
127.0.0.1:6379> get key1
"hello-2333"
127.0.0.1:6379> STRLEN key1       # 获取字符串的长度(integer) 
10 
  • incr、decr、incrby、decrby
    (1)incr、decr 一定要是数字才能进行加减,+1 和 -1。
    (2)incrby、decrby 命令将 key 中储存的数字加上指定的增量值。
127.0.0.1:6379> set views 0       # 设置浏览量为0
OK
127.0.0.1:6379> incr views        # 浏览 + 1(integer)
1
127.0.0.1:6379> incr views        # 浏览 + 1(integer) 
2
127.0.0.1:6379> decr views        # 浏览 - 1(integer) 
1
127.0.0.1:6379> incrby views 10   # +10(integer) 
11
127.0.0.1:6379> decrby views 10   # -10(integer) 
1
  • range,getrange,setrang
    (1) getrange 获取指定区间范围内的值,类似between…and的关系,从零到负一表示全部
127.0.0.1:6379> set key2 abcd123456  # 设置key2的值
OK
127.0.0.1:6379> getrange key2 0 -1   # 获得全部的值
"abcd123456"
127.0.0.1:6379> getrange key2 0 2    # 截取部分字符串
"abc"

(2) setrange 设置指定区间范围内的值,格式是setrange key值 具体值

127.0.0.1:6379> get key2
"abcd123456"
127.0.0.1:6379> SETRANGE key2 1 xx   # 替换值(integer) 
10
127.0.0.1:6379> get key2
"axxd123456"
  • setex(set with expire)键秒值,setnx(set if not exist)
127.0.0.1:6379> setex key3 60 expire  # 设置过期时间
OK
127.0.0.1:6379> ttl key3  # 查看剩余的时间(integer) 
55
127.0.0.1:6379> setnx mykey "redis"   # 如果不存在就设置,成功返回1(integer) 
1
127.0.0.1:6379> setnx mykey "mongodb"  # 如果存在就设置,失败返回0(integer) 
0
127.0.0.1:6379> get mykey
"redis"
  • mset,mget,msetnx
    (1) mset Mset 命令用于同时设置一个或多个 key-value 对。
    (2) mget Mget 命令返回所有(一个或多个)给定 key 的值。如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。
    (3) msetnx 当所有 key 都成功设置,返回 1 。 如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。原子操作
127.0.0.1:6379> mset k10 v10 k11 v11 k12 v12
OK
127.0.0.1:6379> keys *
1) "k12"
2) "k11"
3) "k10"
127.0.0.1:6379> mget k10 k11 k12 k13
1) "v10"
2) "v11"
3) "v12"
4) (nil)
127.0.0.1:6379> msetnx k10 v10 k15 v15 # 原子性操作!(integer) 
0
127.0.0.1:6379> get key15
(nil)
  • getset(先get再set)
127.0.0.1:6379> getset db mongodb   # 没有旧值,返回 nil
(nil)
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379> getset db redis     # 返回旧值 mongodb
"mongodb"
127.0.0.1:6379> get db
"redis"
  • 开发中
# 传统对象缓存
set user:1 value(json数据)
# 可以用来缓存对象
mset user:1:name zhangsan user:1:age 2
mget user:1:name user:1:age

2.2列表List

  • Lpush,rpush,lrange
    (1).Lpush:将一个或多个值插入到列表头部。(左)
    (2).rpush:将一个或多个值插入到列表尾部。(右)
    (3).lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。# 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。# 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
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> RPUSH list "right"
(integer) 3
127.0.0.1:6379> Lrange list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> Lrange list 0 1
1) "two"
2) "one"
  • lpop,rpop
    (1).lpop 命令用于移除并返回列表的第一个元素。当列表 key 不存在时,返回 nil 。
    (2).rpop 移除列表的最后一个元素,返回值为移除的元素。
127.0.0.1:6379> Lpop list
"two"
127.0.0.1:6379> Rpop list
"right"
127.0.0.1:6379> Lrange list 0 -1
1) "one"
  • Lindex
    (1).Lindex,按照索引下标获得元素(-1代表最后一个,0代表是第一个)
127.0.0.1:6379> Lindex list 1
(nil)
127.0.0.1:6379> Lindex list 0
"one"
127.0.0.1:6379> Lindex list -1
"one"
  • llen
    (1).llen 用于返回列表的长度。
127.0.0.1:6379> flushdb
OK
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> Llen list   # 返回列表的长度(integer) 
3
  • lrem,Ltrim
    (1).lrem key 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
127.0.0.1:6379> lrem list 1 "two"
(integer) 1
127.0.0.1:6379> Lrange list 0 -1
1) "three"
2) "one"

(2).Ltrim key 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "hello"
(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
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "hello2"
  • rpoplpush,lset,linsert
    (1).rpoplpush 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "foo"
(integer) 2
127.0.0.1:6379> rpush mylist "bar"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist "bar"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "bar"

(2).lset key index value 将列表 key 下标为 index 的元素的值设置为 value 。

127.0.0.1:6379> exists list  # 对空列表(key 不存在)进行 LSET
(integer) 0
127.0.0.1:6379> lset list 0 item # 报错
(error) ERR no such key
127.0.0.1:6379> lpush list "value1" # 对非空列表进行 LSET
(integer) 1
127.0.0.1:6379> lrange list 0 01) "value1"
127.0.0.1:6379> lset list 0 "new"  # 更新值
OK
127.0.0.1:6379> lrange list 0 0
1) "new"
127.0.0.1:6379> lset list 1 "new"  # index 超出范围报错
(error) ERR index out of range

(3).linsert key before/after pivot value 用于在列表的元素前或者后插入元素。将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。

redis> RPUSH mylist "Hello"
(integer) 1
redis> RPUSH mylist "World"
(integer) 2
redis> LINSERT mylist BEFORE "World" "There"
(integer) 3
redis> LRANGE mylist 0 -1
1) "Hello"
2) "There"
3) "World"

性能总结

  • 它是一个字符串链表,left,right 都可以插入添加如果键不存在,
  • 创建新的链表如果键已存在,新增内容如果值全移除,对应的键也就消失了链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。
  • list就是链表,略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消息排行等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工作线程再用POP操作将任务取出进行执行。
  • Redis还提供了操作List中某一段的api,你可以直接查询,删除List中某一段的元素。 Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。

2.3 集合Set

  • sadd,smembers,sismember
    (1).sadd 将一个或多个成员元素加入到集合中,不能重复
    (2).smembers 返回集合中的所有的成员。
    (3).sismember 命令判断成员元素是否是集合的成员。
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 0
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "hello"
127.0.0.1:6379> SISMEMBER myset "hello"
(integer) 1
127.0.0.1:6379> SISMEMBER myset "world"
(integer) 0
  • scard,srem,srandmember,spop,smove
    (1).scard,获取集合里面的元素个数
127.0.0.1:6379> scard myset
(integer) 2

(2).srem key value 用于移除集合中的一个或多个成员元素

127.0.0.1:6379> srem myset "kuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"

(3).srandmember key 命令用于返回集合中的一个随机元素。

127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "world"
2) "kuangshen"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "kuangshen"
2) "hello"

(4).spop key 用于移除集合中的指定 key 的一个或多个随机元素

127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> spop myset
"world"
127.0.0.1:6379> spop myset
"kuangshen"
127.0.0.1:6379> spop myset
"hello"

(5).smove SOURCE DESTINATION MEMBER 将指定成员 member 元素从 source 集合移动到 destination 集合。

127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "kuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "kuangshen"
2) "set2"
  • 数字集合类
  • 差集: sdiff 交集: sinter 并集: sunion
    在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
127.0.0.1:6379> sadd key1 "a"
(integer) 1
127.0.0.1:6379> sadd key1 "b"
(integer) 1
127.0.0.1:6379> sadd key1 "c"
(integer) 1
127.0.0.1:6379> sadd key2 "c"
(integer) 1
127.0.0.1:6379> sadd key2 "d"
(integer) 1
127.0.0.1:6379> sadd key2 "e"
(integer) 1
127.0.0.1:6379> SDIFF key1 key2 # 差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2 # 交集
1) "c"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "a"
2) "b"
3) "c"
4) "e"
5) "d"

2.4哈希Hash

kv模式不变,但V是一个键值对:Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 存储部分变更的数据,如用户信息等。

  • hset,hget,hmset、hmget,hgetall,hdel
    (1).hset、hget 命令用于为哈希表中的字段赋值 。
    (2).hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段。
    (3).hgetall 用于返回哈希表中,所有的字段和值。
    (4).hdel 用于删除哈希表 key 中的一个或多个指定字段
127.0.0.1:6379> hset myhash field1 "kuangshen"
(integer) 1
127.0.0.1:6379> hget myhash field1
"kuangshen"
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> HGET myhash field1
"Hello"
127.0.0.1:6379> HGET myhash field2
"World"
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
127.0.0.1:6379> HDEL myhash field1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "World"
  • hlen,hexists,hkeys,hvals,hincrby,hsetnx
    (1).hlen 获取哈希表中字段的数量。
127.0.0.1:6379> hlen myhash
(integer) 1
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> hlen myhash
(integer) 2

(2).hexists 查看哈希表的指定字段是否存在。

127.0.0.1:6379> hexists myhash field1
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0

(3).hkeys 获取哈希表中的所有域(field)。

127.0.0.1:6379> HKEYS myhash
1) "field2"
2) "field1"

(4). hvals 返回哈希表所有域(field)的值。

127.0.0.1:6379> HVALS myhash
1) "World"
2) "Hello"

(5).hincrby 为哈希表中的字段值加上指定增量值。

127.0.0.1:6379> hset myhash field 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash field -1
(integer) 5
127.0.0.1:6379> HINCRBY myhash field -10
(integer) -5

(6).hsetnx 为哈希表中不存在的的字段赋值 。

127.0.0.1:6379> HSETNX myhash field1 "hello"
(integer) 1   # 设置成功,返回 1 。
127.0.0.1:6379> HSETNX myhash field1 "world"
(integer) 0   # 如果给定字段已经存在,返回 0 。
127.0.0.1:6379> HGET myhash field1
"hello"

2.5有序集合Zset

在set基础上,加一个score值。之前set是k1 v1 v2 v3,现在zset是 k1 score1 v1 score2 v2

和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如一个存储全班同学成绩的sorted set,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。可以用sorted set来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。排行榜应用,取TOP N操作 !

  • zadd,zrange
    (1).zadd 将一个或多个成员元素及其分数值加入到有序集当中。
    (2).zrange 返回有序集中,指定区间内的成员。
127.0.0.1:6379> zadd myset 1 "one"
(integer) 1
127.0.0.1:6379> zadd myset 2 "two" 3 "three"
(integer) 2
27.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
  • zrangebyscore
    (1).zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列。
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
# Inf无穷大量+∞,同样地,-∞可以表示为-Inf。
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示整个有序集
1) "kuangshen"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 递增排列
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> ZREVRANGE salary 0 -1 WITHSCORES  # 递减排列
1) "xiaohong"
2) "5000"
3) "xiaoming"
4) "2500"
5) "kuangshen"
6) "500"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 WITHSCORES # 显示工资 <=2500 的所有成员
1) "kuangshen"
2) "500"
3) "xiaomi"
4) "2500"
  • zrem,zcard,zcount
    (1).zrem 移除有序集中的一个或多个成员
127.0.0.1:6379> ZRANGE salary 0 -1
1) "kuangshen"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> zrem salary kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaoming"
2) "xiaohong"

(2).zcard 命令用于计算集合中元素的数量

127.0.0.1:6379> zcard salary
(integer) 2
OK

(3).zcount 计算有序集合中指定分数区间的成员数量

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
  • zrank,zrevrank
    (1).zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES  # 显示所有成员及其 score 值
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> zrank salary kuangshen  # 显示 kuangshen 的薪水排名,最少
(integer) 0
127.0.0.1:6379> zrank salary xiaohong   # 显示 xiaohong 的薪水排名,第三
(integer) 2

(2).zrevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。

127.0.0.1:6379> ZREVRANK salary kuangshen # 狂神第三
(integer) 2
127.0.0.1:6379> ZREVRANK salary xiaohong  # 小红第一
(integer) 0

3.Redis的持久化

Redis 是内存数据库(断电即失),如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能!
RDB—>frok子进程
AOF—>读写文件,文件无限制追加,
redis默认是RDB,大数据量使用RDB,Aof的读写文件,速度较慢。

3.1RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
在这里插入图片描述
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
Rdb 保存的是 dump.rdb 文件

/usr/local/bin/ dump.rdb文件,
在这里插入图片描述
这里的触发条件机制,我们可以修改测试一下:

save 120 10  # 120秒内修改10次则触发RDB

如何触发RDB快照?
1、配置文件中默认的快照配置,建议多用一台机子作为备份,复制一份 dump.rdb
2、命令save或者是bgsave

  • save 时只管保存,其他不管,全部阻塞
  • bgsave,Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。可以通过lastsave命令获取最后一次成功执行快照的时间。

3、执行flushall命令,也会产生 dump.rdb 文件,但里面是空的,无意义 !
4、退出的时候也会产生 dump.rdb 文件!

如何恢复?

127.0.0.1:6379> config get dir
dir
/usr/local/bin

1、将备份文件(dump.rdb)移动到redis安装目录并启动服务即可,redis启动会自动检查dump.rdb,回复其中的数据。

优点:1、适合大规模的数据恢复2、对数据完整性和一致性要求不高
缺点:1、在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改2、Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。

3.2 AOF(Append Only File)

在这里插入图片描述
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

  • Aof保存的是 appendonly.aof 文件
    在这里插入图片描述
appendonly no   # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种方式在许多应用中已经足够用了
appendfilename "appendonly.aof"   # appendfilename AOF 文件名称
appendfsync everysec  # appendfsync aof持久化策略的配置    
# no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。    
# always表示每次写入都执行fsync,以保证数据同步到磁盘。    
# everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
No-appendfsync-on-rewrite #重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性
Auto-aof-rewrite-min-size # 设置重写的基准值
Auto-aof-rewrite-percentage #设置重写的基准值
AOF 启动/修复/恢复
正常恢复:启动:设置Yes,修改默认的appendonly no,改为yes
将有数据的aof文件复制一份保存到对应目录(config get dir)
恢复:重启redis然后重新加载
异常恢复:启动:设置Yes
故意破坏 appendonly.aof 文件!
修复:redis-check-aof --fix appendonly.aof进行修复
恢复:重启 redis 然后重新加载

优点:1、每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好
2、每秒同步: appendfsync everysec 异步操作,每秒记录 ,如果一秒内宕机,有数据丢失
3、不同步: appendfsync no 从不同步
缺点:1、相同数据集的数据而言,aof 文件要远大于 rdb文件,恢复速度慢于 rdb。
2、Aof 运行效率要慢于 rdb,每秒同步策略效率较好,不同步效率和rdb相同。

总结
1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
4、同时开启两种持久化方式

  • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
  • RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。

5、性能建议

  • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。
  • 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite 的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
  • 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

4.Redis事务

Redis事务的概念:Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

Redis事务没有隔离级别的概念:批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行!

Redis不保证原子性Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

Redis事务的三个阶段:
开始事务—>命令入队—>执行事务

Redis事务相关命令:

watch key1 key2 ...  #监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
multi # 标记一个事务块的开始( queued )
exec # 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 ) 
discard # 取消事务,放弃事务块中的所有命令
unwatch # 取消watch对所有key的监控

(1)正常执行
在这里插入图片描述
(2)放弃事务:
在这里插入图片描述
(3)若在事务队列中存在命令性错误(类似于java编译性错误),则执行EXEC命令时,所有命令都不会执行:
在这里插入图片描述
(4)若在事务队列中存在语法性错误(类似于java的1/0的运行时异常),则执行EXEC命令时,其他正确命令会被执行,错误命令抛出异常:
在这里插入图片描述

5.Watch 监控(乐观锁)

watch指令类似于乐观锁,在事务提交(EXEC)时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。

悲观锁:
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿到这个数据就会block直到它拿到锁。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。

乐观锁:
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁。但是在更新的时候会判断一下再此期间别人有没有去更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能执行更新。

测试:
1、初始化信用卡可用余额和欠额

127.0.0.1:6379> set balance 100
OK
127.0.0.1:6379> set debt 0
OK

2、使用watch检测balance,事务期间balance数据未变动,事务执行成功

127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

3、使用watch检测balance,事务期间balance数据变动,事务执行失败

# 窗口一
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI  # 执行完毕后,执行窗口二代码测试
OK127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec  # 修改失败!
(nil)
# 窗口二
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> set balance 200
OK
# 窗口一:出现问题后放弃监视,然后重来!
127.0.0.1:6379> UNWATCH  # 放弃监视
OK
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec  # 成功!
1) (integer) 180
2) (integer) 40

6.Redis主从复制

7.缓存穿透+击穿+雪崩+双写不一致

Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。

7.1缓存穿透(请求查不到)

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。
当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透

常见的解决方案:

(1).布隆过滤器:

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
比如:接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截,不走缓存,代码上就直接return出去;
在这里插入图片描述

(2)缓存空对象:

从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value的键值对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。

但是这种方法会存在两个问题:
1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

7.2缓存击穿(请求量太大)

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,并发查同一条数据缓存不在,流量打到数据库上,引起数据库压力瞬间增大,造成过大压力。

常见的解决方案:

(1).设置热点数据永不过期(不推荐)
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。

(2).加互斥锁/分布式锁

  • 使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验大。
    -使用互斥锁,表示并发要一个一个加锁执行,等于在阻塞队列等待,释放锁后,再去执行下一个请求

7.3缓存雪崩

缓存雪崩,是指在某一个时间段,缓存集中过期失效。–> 断电

(产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。)

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据缓存不在,流量打到数据库上,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

常见的解决方案:

(1).redis高可用
如果缓存数据库是分布式来部署的,将热点数据均匀分布在不同得缓存数据库中。查询的时候,不会导致某一台压力过大,压力可以分担下去

(2).限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

(3).数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

7.4缓存双写不一致

7.4.1 先更新数据库,再更新缓存

数据库stock=6,缓存stock=10,出现了数据库和缓存双写不一致的情况,对于线程1来说,数据库应该是10的,但是却是6,对于线程2来说,缓存应该是6的,但是却是10。针对每个线程,都做了个双写操作,结果却导致了写的不对,数据库和缓存不一致!
在这里插入图片描述

7.4.2 先更新数据库,再删除缓存
  • 先往数据库里更新
  • 再删除缓存的数据

线程2在写缓存的时候卡住了,卡成这个鬼样~此时线程3结束了,把数据库更新为6,线程2写缓存在3结束后,才结束,导致缓存现在是10,数据库和缓存数据不一致了。
在这里插入图片描述
所以这对于这个方案来说,也会出现缓存和数据库数据不一致的情况,于是市面上出现了个所谓的延时双删的操作

7.4.3 延时双删

延时双删的方案:

  • 先往数据库里更新
  • 再删除缓存的数据
  • 休眠一段时间后,再删一次缓存的数据

现在的方案改成了延迟双删,就是在上面的基础上多删一次,叫做双删。为什么要加这个,这个就是要避免其他线程在查询数据库后,发现不存在,于是写一把缓存,类似线程2的操作,这个时候的缓存很有可能是脏数据,并不是数据库最近的数据;
在这里插入图片描述
假设这个线程2卡的时间,并不是你正常预料的那个写缓存的时间,他是真的卡了,服务器卡顿了,这个操作卡了很久,而你休眠的时间,是无法跟上的,就会出现下面的图:
在这里插入图片描述
这样既然解决不了,那有人又提出了一种方案:

7.4.4 基于内存模型的序列化队列

把每个操作都放在一个内存队列里面,按照顺序执行,比如多个线程的请求进来,严格按照线程的进来的顺序,串行化依次执行,这个是可以彻底解决问题。
但是带来了性能上的大大降低,吞吐量大大降低,此外我还需要搞个内存结构,再搞个监听去一个一个取,再顺序执行,或者本身一个查询很快的操作,被排列在这个内存队列里。

7.4.5 分布式锁之读写锁
  • 对于redisson来说,存在一个api,叫做读写锁api——ReadWriteLock,里面你可以获取读锁,也可以获取写锁。读锁和读锁之间是不会互斥的,就是不冲突的。即使加了读锁,但是你不会影响程序并发执行的效果,也就意味着没有加锁。
  • 这个读写锁,是同一个,什么意思呢,对于redis的同一个key,进行加的读锁,或者写锁。实际上在分布式锁Redisson中,把锁细分化下,一个是读锁,一个是写锁。对于Redisson来说,底层lua脚本对读锁是这么实现的,如果多个线程来进行读操作,没关系,底层会用lua脚本操作redis的同时,绑定了mode模式,读锁,即绑定的是read模式,写锁绑定的是write模式;如果多个请求同时尝试去加读锁,每个线程进来的时候会先判断下当前key加锁的模式,如果是读锁read,可以继续加锁,不会互斥,不会影响继续执行自己的读逻辑;
  • 假设之前这把锁已经是写锁了,这个时候读请求进来,写请求进来,不好意思,全部给我在外面等着,直到这个锁被释放后,下一个请求再进来;再重新加锁;读请求进来,判断mode=read还是write,是read,执行逻辑;判断mode=write,就表明当前有请求在写,则在外面等待,直到这个变成了mode=read;

所以这样,其实把性能,就大大提升了在读这块的,不至于读写都放在阻塞队列里,影响性能。

8.Jedis

Jedis是Redis官方推荐的Java连接开发工具。要在Java开发中使用好Redis中间件。

8.1.测试联通

(1).pom.xml中导入:

<dependency>    
 <groupId>redis.clients</groupId>    
 <artifactId>jedis</artifactId>    
 <version>3.2.0</version>
</dependency>
public class Ping {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println("连接成功"); //查看服务是否运行        
        System.out.println("服务正在运行: "+jedis.ping()); // 服务正在运行: PONG
    }
}
public class Ping {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);

        //验证密码,如果没有设置密码这段代码省略     
        //jedis.auth("password");
        jedis.connect(); //连接        
        jedis.disconnect(); //断开连接
        jedis.flushAll(); //清空所有的key
    }
}

8.2常用API

public class TestKey {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);        
        System.out.println("清空数据:" + jedis.flushDB());        
        System.out.println("判断某个键是否存在:" + jedis.exists("username"));        
        System.out.println("新增<'username','kuangshen'>的键值对:" + jedis.set("username", "kuangshen"));        
        System.out.println("新增<'password','password'>的键值对:" + jedis.set("password", "password"));        
        System.out.print("系统中所有的键如下:");        Set<String> keys = jedis.keys("*");        
        System.out.println(keys);        System.out.println("删除键password:" + jedis.del("password"));        
        System.out.println("判断键password是否存在:" + jedis.exists("password"));        
        System.out.println("查看键username所存储的值的类型:" + jedis.type("username"));        
        System.out.println("随机返回key空间的一个:" + jedis.randomKey());        
        System.out.println("重命名key:" + jedis.rename("username", "name"));        
        System.out.println("取出改后的name:" + jedis.get("name"));        System.out.println("按索引查询:" + jedis.select(0));        
        System.out.println("删除当前选择数据库中的所有key:" + jedis.flushDB());        
        System.out.println("返回当前数据库中key的数目:" + jedis.dbSize());        
        System.out.println("删除所有数据库中的所有key:" + jedis.flushAll());
    }
}
public class TestString {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);        
        jedis.flushDB();        
        System.out.println("===========增加数据===========");        
        System.out.println(jedis.set("key1","value1"));        
        System.out.println(jedis.set("key2","value2"));        
        System.out.println(jedis.set("key3", "value3"));

        System.out.println("删除键key2:"+jedis.del("key2"));        
        System.out.println("获取键key2:"+jedis.get("key2"));        
        System.out.println("修改key1:"+jedis.set("key1", "value1Changed"));        
        System.out.println("获取key1的值:"+jedis.get("key1"));        
        System.out.println("在key3后面加入值:"+jedis.append("key3", "End"));        
        System.out.println("key3的值:"+jedis.get("key3"));        
        System.out.println("增加多个键值对:"+jedis.mset("key01","value01","key02","value02","key03","value03"));        
        System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));        
        System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03","key04"));        
        System.out.println("删除多个键值对:"+jedis.del("key01","key02"));        
        System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));        
        jedis.flushDB();        
        System.out.println("===========新增键值对防止覆盖原先值==============");        
        System.out.println(jedis.setnx("key1", "value1"));        
        System.out.println(jedis.setnx("key2", "value2"));        
        System.out.println(jedis.setnx("key2", "value2-new"));        
        System.out.println(jedis.get("key1"));        
        System.out.println(jedis.get("key2"));        
        System.out.println("===========新增键值对并设置有效时间=============");        
        System.out.println(jedis.setex("key3", 2, "value3"));        
        System.out.println(jedis.get("key3"));        
        try {            
            TimeUnit.SECONDS.sleep(3);       
        } catch (InterruptedException e) {            
            e.printStackTrace();       
        }        
        System.out.println(jedis.get("key3"));        
        System.out.println("===========获取原值,更新为新值==========");        
        System.out.println(jedis.getSet("key2", "key2GetSet"));        
        System.out.println(jedis.get("key2"));        
        System.out.println("获得key2的值的字串:"+jedis.getrange("key2", 2, 4));
    
    }
}

public class TestList {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);        
        jedis.flushDB();        
        System.out.println("===========添加一个list===========");        
        jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");        
        jedis.lpush("collections", "HashSet");        
        jedis.lpush("collections", "TreeSet");        
        jedis.lpush("collections", "TreeMap");        
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));//-1代表倒数第一个元素,-2代表倒数第二个元素,end为-1表示查询全部
        System.out.println("collections区间0-3的元素:"+jedis.lrange("collections",0,3));        
        System.out.println("===============================");        // 删除列表指定的值 ,第二个参数为删除的个数(有重复时),后add进去的值先被删,类似于出栈        
        System.out.println("删除指定元素个数:"+jedis.lrem("collections", 2, "HashMap"));       
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));        
        System.out.println("删除下表0-3区间之外的元素:"+jedis.ltrim("collections", 0, 3));        
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));        
        System.out.println("collections列表出栈(左端):"+jedis.lpop("collections"));        
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));        
        System.out.println("collections添加元素,从列表右端,与lpush相对应:"+jedis.rpush("collections", "EnumMap"));        
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));        
        System.out.println("collections列表出栈(右端):"+jedis.rpop("collections"));        
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));        
        System.out.println("修改collections指定下标1的内容:"+jedis.lset("collections", 1, "LinkedArrayList"));        
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));        
        System.out.println("===============================");        
        System.out.println("collections的长度:"+jedis.llen("collections"));       
        System.out.println("获取collections下标为2的元素:"+jedis.lindex("collections", 2));        
        System.out.println("===============================");        
        jedis.lpush("sortedList", "3","6","2","0","7","4");        
        System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0, -1));        
        System.out.println(jedis.sort("sortedList"));        
        System.out.println("sortedList排序后:"+jedis.lrange("sortedList", 0, -1));
    }
}

public class TestHash {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);        
        jedis.flushDB();        
        Map<String,String> map = new HashMap<>();        
        map.put("key1","value1");        
        map.put("key2","value2");        
        map.put("key3","value3");        
        map.put("key4","value4");        
        //添加名称为hash(key)的hash元素        
        jedis.hmset("hash",map);        
        //向名称为hash的hash中添加key为key5,value为value5元素        
        jedis.hset("hash", "key5", "value5");        
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));//return Map<String,String>
        System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//return Set<String>        
        System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//return List<String>        
        System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 6));        
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));        
        System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 3));        
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));        
        System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash", "key2"));       
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));        
        System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));        
        System.out.println("判断hash中是否存在key2:"+jedis.hexists("hash","key2"));        
        System.out.println("判断hash中是否存在key3:"+jedis.hexists("hash","key3"));        
        System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));        
        System.out.println("获取hash中的值:"+jedis.hmget("hash","key3","key4"));
    }
}

8.3.事务

public class TestMulti {
    public static void main(String[] args) {
        //创建客户端连接服务端,redis服务端需要被开启        
        Jedis jedis = new Jedis("127.0.0.1", 6379);        
        jedis.flushDB();        
        JSONObject jsonObject = new JSONObject();        
        jsonObject.put("hello", "world");        
        jsonObject.put("name", "java");        
        //开启事务        
        Transaction multi = jedis.multi();        
        String result = jsonObject.toJSONString();        
        try {            
            //向redis存入一条数据            
            multi.set("json", result);            
            //再存入一条数据            
            multi.set("json2", result);            
            //这里引发了异常,用0作为被除数            
            int i = 100 / 0;          
            //如果没有引发异常,执行进入队列的命令
            multi.exec();
        }catch (Exception e) {
            e.printStackTrace();            
            //如果出现异常,回滚            
            multi.discard();
        }finally {
            System.out.println(jedis.get("json"));            
            System.out.println(jedis.get("json2"));
            //最终关闭客户端            
            jedis.close();
        }
    }
}

9.SpringBoot整合Redis

在SpringBoot中一般使用RedisTemplate提供的方法来操作Redis。
(1)pom.xml中导入依赖:

<dependency>    
 <groupId>org.springframework.boot</groupId>    
 <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)yaml配置中配置:

spring:
	redis:
	   database: 0 #Redis一共16个数据库,默认使用第0个
	   host: 127.0.0.1  #Redis服务器地址
	   port: 6379 #端口号
	   password: #密码为空
	   timeout: 3000
	   jedis:
	     pool:
	       max-active: 200
	       max-idle: 10
	       max-wait: -1
	       min-idle: 0

(3)SpringBoot自动帮我们在容器中生成了一个RedisTemplate,这个RedisTemplate的泛型是<Object,Object>,写代码不方便,需要写好多类型转换的代码;我们需要一个泛型为<String,Object>形式的RedisTemplate。并且,这个RedisTemplate没有设置数据存在Redis时,key及value的序列化方式。因此我们可以直接自己写个配置类,配置RedisTemplate。

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;

/**
 * Redis存储的value值(尤其是User对象,可以考虑将user对象转成接送字符串)经过JDK默认序列格式化后的HEX格式储存
 * 自定义Redis缓存序列化机制
 */
@Configuration
public class RedisConfig {

    /**
     * 基于API的Redis缓存数据序列化机制 redisTemplate
     * @param factory
     * @return
     */
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        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);
        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;
    }

    /**
     * 基于注解的Redis缓存数据序列化机制 @Cacheable,@CachePut,@CacheEvict
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //使用RedisSerializer来序列化和反序列化redis的key值
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //指定要序列化的域,field,get和set,以及修饰范围,ANY是都有包括private和public
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        // 配置序列化(解决乱码的问题)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 缓存有效期为1天
                .entryTtl(Duration.ofDays(1))
                // 使用StringRedisSerializer来序列化和反序列化redis的key值
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringSerializer))
                // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                // 禁用空值
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
        return cacheManager;
    }

}

(4).直接用RedisTemplate操作Redis。

@Autowired    
private RedisTemplate<String,String> redisTemplate;    
  
redisTemplate.opsForValue().set("myKey","myValue");
System.out.println(redisTemplate.opsForValue().get("myKey"));   

(5)封装工具类:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {

    @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);
    }

    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 键    
     */
    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;
        }
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值