Redis基础部分学习笔记

一、Redis的安装与配置

Redis是基于内存的,是单线程的。Redis可以用作数据库、缓存、消息中间件。

Redis一些常见的应用场景如下:

(1)缓存

利用Redis快速的读写速度,可以将热点数据放入Redis中,应用从Redis中读取数据,可以大幅提高访问速度和性能。

(2)消息队列

Redis的List结构可以作为消息队列使用,生产者向List尾部推送消息,消费者从List头部弹出消息。

(3)计数器

Redis的字符串和哈希都可以用作计数器,对数字型数据进行自增自减操作。

(4)排行榜

Redis的有序集合可以实现排行榜功能,插入元素时指定评分,就可以获得评分排序后的结果。

(5)会话缓存

可以将会话信息放入Redis中,当需要会话数据时直接从Redis读取,无需去数据库查找。

(6)分布式锁

利用Redis的SETNX命令可以实现分布式锁。

(7)其它

Redis还可以实现发布/订阅、原子增量、地理空间索引等更复杂的模式。

1、Redis的安装

在Ubuntu系统中安装Redis步骤如下

首先在https://redis.io/download/官网上下载Redis的压缩包redis-7.2.0.tar.gz,然后使用解压命令解压缩

tar zxf redis-7.2.0.tar.gz

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2、环境配置

因为Redis是使用C++写的,所以需要安装c++环境

sudo apt install build-essential

然后在redis-7.2.0 目录下使用make命令,需要换成root用户

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

等待安装,成功后信息如下

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 make install 命令查看已安装的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

回到根目录下,查看

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 redis-server即可运行redis

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

打开redis.conf文件

修改为如下,就可以让redis在后台运行,不会占用命令行

daemonize yes

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后在redis安装目录下启动(也可以在其他目录下,但是配置文件路径要写全)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

redis-server redis.conf

查看是否已经启动

ps aux | grep redis

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关闭redis

redis-cli shutdown

连接redis命令

redis-cli -p 6379

ping

-p 表示本机地址,6379是redis的端口号

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

更多的配置可以参考网上的教程

二、Redis基础知识

1、性能测试

redis-benchmark 是一个压力测试工具,是官方自带的性能测试工具

测试命令

redis-benchmark [option] [option value]

Redis 性能测试工具可选参数如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

简单测试一下

100个并发连接, 100000请求

redis-benchmark -h localhost -p 6379 -c 100 -n 100000

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2、基础知识
(1)数据库

Redis默认有16个数据库(0-15号),默认使用第一个数据库(0 号数据库),可以通过命令切换数据库

127.0.0.1:6379> select 2
OK

这样就切换到第二个数据库

使用DBSIZE查看数据库数据条数,使用set key value 设置数据

127.0.0.1:6379[2]> select 1
OK
127.0.0.1:6379[1]> DBSIZE
(integer) 0
127.0.0.1:6379[1]> set name zps
OK
127.0.0.1:6379[1]> DBSIZE
(integer) 1
(2)设置数据

使用命令设置数据

set key value

获取数据

get key

获取所有的key

keys *
127.0.0.1:6379[1]> set age 21
OK
127.0.0.1:6379[1]> get name
"zps"
127.0.0.1:6379[1]> get age
"21"
127.0.0.1:6379[1]> keys *
1) "name"
2) "age"

查看数据是否存在(存在为1,不存在为0)

exists key
127.0.0.1:6379> set name zps
OK
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0

移除数据

move key 1
127.0.0.1:6379> move name 1
(integer) 1
127.0.0.1:6379> keys *
(empty array)

查看数据类型

127.0.0.1:6379> set name zps
OK
127.0.0.1:6379> type name
string
127.0.0.1:6379> set age 21
OK
127.0.0.1:6379> type age
string
(3)清除数据库

清除本数据库所有的元素

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

清空全部数据库

flushall

(4)设置数据有效时间

时间单位为秒(s)

expire key time

使用命令查看数据还剩多少有效时间

ttl key
127.0.0.1:6379> set name zps
OK
127.0.0.1:6379> expire name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)

三、Redis五大基本数据类型

1、String字符串
(1)追加数据append

如果当前key不存在,就相当于set

append key value
127.0.0.1:6379> set name zps
OK
127.0.0.1:6379> type name
string
127.0.0.1:6379> append name hello
(integer) 8 # 返回的是新字符串的长度
127.0.0.1:6379> get name
"zpshello"
(2)查看字符串长度strlen
strlen key
127.0.0.1:6379> get name
"zpshello"
127.0.0.1:6379> strlen name
(integer) 8
(3)增减

对于数字字符串,可以增减

incr / decr key
127.0.0.1:6379> set age 21
OK
127.0.0.1:6379> incr age
(integer) 22
127.0.0.1:6379> decr age
(integer) 21

设置增减的步长

incrby / decrby key  步长
127.0.0.1:6379> incrby age 5
(integer) 26
127.0.0.1:6379> decrby age 10
(integer) 16
(4)截取字符串GETRANGE

使用命令查看字符串[start, end]范围的值

GETRANGE key start end
127.0.0.1:6379> set name "hello java linux python"
OK
127.0.0.1:6379> GETRANGE name 1 4
"ello"
127.0.0.1:6379> GETRANGE name 0 -1 # 查看全部
"hello java linux python"

(5)替换字符串SETRANGE

使用value替换掉从start开始的字符串

SETRANGE key start value
127.0.0.1:6379> SETRANGE name 0 c++
(integer) 23
127.0.0.1:6379> get name
"c++lo java linux python"
(6)设置过期时间setex
setex key time value

设置name为"zps",10s后过期

127.0.0.1:6379> setex name 10 zps
OK
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
(integer) 0
127.0.0.1:6379> get name
(nil)
(7)不存在再设置setnx

在分布式锁中经常用到

setnx key value
127.0.0.1:6379> setnx name zps
(integer) 1 # 成功为1
127.0.0.1:6379> setnx name zzps
(integer) 0 # 失败为0,因为已经存在name
(8)批量设置mset、mget

使用空格隔开键值对

批量设置值

mset key1 value1 key2 value2 -----

批量获取值

mget key1 key2 -----
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
(9)批量 msetnx
127.0.0.1:6379> msetnx k1 v1 k2 v2
(integer) 1 # 成功
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
127.0.0.1:6379> msetnx k1 v1 k3 v3
(integer) 0 # 失败
127.0.0.1:6379> keys *
1) "k1"
2) "k2"

可见,如果有一个key已经存在,那么整个返回0,表示失败。msetnx是原子性操作,要么一起成功,要么都失败!

(10)设置对象(JSON格式)

key的设计

user:{id}:{filed}
127.0.0.1:6379> mset user:1:name zps user:1:age 21
OK
127.0.0.1:6379> keys *
1) "user:1:name"
2) "user:1:age"
127.0.0.1:6379> mget user:1:name user:1:age
1) "zps"
2) "21"

(11)先获取再设置getset

如果key不存在,返回nil,再设置key

如果key存在,返回value,再更新value

127.0.0.1:6379> getset name zps
(nil)
127.0.0.1:6379> getset name hello
"zps"
127.0.0.1:6379> get name
"hello"

字符串中还有其他方法,不再介绍

2、List列表

所有的命令都是以 L/l 开头的

(1)添加元素

插入到头部

LPUSH list value
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> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"

插入到尾部

RPUSH list value
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> RPUSH list four
(integer) 4
127.0.0.1:6379> RPUSH list five
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
5) "five"
(2)删除元素

移除头部元素

LPOP list

移除尾部元素

RPOP list
127.0.0.1:6379> LPOP list
"three"
127.0.0.1:6379> RPOP list
"five"
(3)通过下标获取元素

下标从0开始

LINDEX list index
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3) "four"
127.0.0.1:6379> LINDEX list 0
"two"
127.0.0.1:6379> LINDEX list 1
"one"
(4)获取列表长度
LLEN list
127.0.0.1:6379> LLEN list
(integer) 3
(5)移除指定的元素
LREM list num value
num 是要移除的元素个数
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> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lrem list 1 one
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "two"
(6)截取列表

这里的截取也是截断,即将截取的元素覆盖到原来的列表

ltrim list start end

start 起始位置
end 结束位置(包括)
127.0.0.1:6379> rpush list one
(integer) 1
127.0.0.1:6379> rpush list two
(integer) 2
127.0.0.1:6379> rpush list three
(integer) 3
127.0.0.1:6379> rpush list four
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> ltrim list 1 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "three"
(7)更新指定下标位置的值

如果列表不存在或下标不存在就会报错

lset list index value
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "three"
127.0.0.1:6379> lset list 0 hello
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "three"
127.0.0.1:6379> lset list 2 world
(error) ERR index out of range
(8)插入值

可以将某个具体的value插入到列表中某个元素的前面或者后面。

linsert list before/after oldVal newVal
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "three"
127.0.0.1:6379> linsert list before three two
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "two"
3) "three"
127.0.0.1:6379> linsert list after three four
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "two"
3) "three"
4) "four"
3、Set集合

Set中的值是不能重复的,而且是无序的。

(1)添加元素
sadd set value
127.0.0.1:6379> sadd set one
(integer) 1
127.0.0.1:6379> sadd set two
(integer) 1
127.0.0.1:6379> sadd set three
(integer) 1
127.0.0.1:6379> smembers set
1) "one"
2) "two"
3) "three"
(2)查看元素
smembers set
(3)判断是否存在
sismember set value
127.0.0.1:6379> smembers set
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> sismember set one
(integer) 1
127.0.0.1:6379> sismember set four
(integer) 0
(4)求元素个数
scard set
127.0.0.1:6379> scard set
(integer) 3
127.0.0.1:6379> sadd set four 
(integer) 1
127.0.0.1:6379> scard set
(integer) 4
(5)移除元素

删除指定的元素

srem set value
127.0.0.1:6379> smembers set
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> srem set one
(integer) 1
127.0.0.1:6379> smembers set
1) "two"
2) "three"
3) "four"

随机删除元素

spop set
127.0.0.1:6379> smembers set
1) "two"
2) "three"
3) "four"
127.0.0.1:6379> spop set
"three"
(6)得到任意元素
srandmember set num(个数,默认1)
127.0.0.1:6379> smembers set
1) "two"
2) "three"
3) "four"
127.0.0.1:6379> srandmember set 
"two"
127.0.0.1:6379> srandmember set 2
1) "two"
2) "four"
(7)将元素移动到另一个集合中
smove set1 set2 value
127.0.0.1:6379> smembers set
1) "two"
2) "four"
127.0.0.1:6379> sadd set1 five
(integer) 1
127.0.0.1:6379> smove set set1 two
(integer) 1
127.0.0.1:6379> smembers set1
1) "five"
2) "two"
(8)求差集、并集、交集
差集
sdiff set1 set2
交集
sinter set1 set2
并集
sunion set1 set2
127.0.0.1:6379> sadd s1 a
(integer) 1
127.0.0.1:6379> sadd s1 b
(integer) 1
127.0.0.1:6379> sadd s1 c
(integer) 1
127.0.0.1:6379> sadd s2 c
(integer) 1
127.0.0.1:6379> sadd s2 d
(integer) 1
127.0.0.1:6379> sadd s2 e
(integer) 1
127.0.0.1:6379> sdiff s1 s2
1) "a"
2) "b"
127.0.0.1:6379> sinter s1 s2
1) "c"
127.0.0.1:6379> sunion s1 s2
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
4、Hash哈希

哈希就是一个键值对集合

(1)添加元素

添加单个元素

hset hash(集合名字) key value
127.0.0.1:6379> hset hash name zhangsan
(integer) 1

添加多个元素

hmset hash key1 val1 key2 val2
127.0.0.1:6379> hmset hash name wangwu name lisi
OK
(2)获取元素

获取一个

hget hash key
127.0.0.1:6379> hget hash name
"lisi"

获取多个

hmget hash key1 key2
127.0.0.1:6379> hset hash age 21
(integer) 1
127.0.0.1:6379> hset hash sex man
(integer) 1
127.0.0.1:6379> hgetall hash
1) "name"
2) "lisi"
3) "age"
4) "21"
5) "sex"
6) "man"
127.0.0.1:6379> hmget hash name age 
1) "lisi"
2) "21"

获取全部,包括key和value

hgetall hash
(3)删除元素
hdel hash key
127.0.0.1:6379> hgetall hash
1) "name"
2) "lisi"
3) "age"
4) "21"
5) "sex"
6) "man"
127.0.0.1:6379> hdel hash sex
(integer) 1
127.0.0.1:6379> hgetall hash
1) "name"
2) "lisi"
3) "age"
4) "21"
(4)获取集合大小

获取Hash表的字段数量

hlen hash
127.0.0.1:6379> hgetall hash
1) "name"
2) "lisi"
3) "age"
4) "21"
127.0.0.1:6379> hlen hash
(integer) 2
(5)判断指定字段是否存在
hexists hash key
127.0.0.1:6379> hexists hash name
(integer) 1
127.0.0.1:6379> hexists hash sex
(integer) 0

存在返回1,不存在返回0

(6)获取键、值

只获取键

hkeys hash

只获取值

hvals hash
127.0.0.1:6379> hkeys hash
1) "name"
2) "age"
127.0.0.1:6379> hvals hash
1) "lisi"
2) "21"
(7)指定增量

如果增量为负数,就是减少

hincrby hash key num(增量)
127.0.0.1:6379> hget hash age
"21"
127.0.0.1:6379> hincrby hash age 2
(integer) 23
127.0.0.1:6379> hincrby hash age -5
(integer) 18
(8)不存在则设置

如果key不存在,就可以设置新键值对,否则不能

hsetnx hash key value
127.0.0.1:6379> hsetnx hash sex man
(integer) 1
127.0.0.1:6379> hsetnx hash name lisi
(integer) 0
5、Zset有序集合

是一个可排序的Set集合,每一个元素都带有一个score属性,可以基于score属性对元素进行排序。其底层实现是一个跳表(SkipList)加Hash表。

Zset具备:可排序、元素不重复、查询速度快的特点,经常被用于实现排行榜这样的功能。

(1)添加元素

key是名称,score是一个关键字(用于排序的值),是float类型

添加单个元素

zadd key score member
127.0.0.1:6379> zadd salary 1000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 2000 lisi
(integer) 1
127.0.0.1:6379> zadd salary 3000 wangwu
(integer) 1

添加多个元素

zadd set key1 val1 key2 val2
127.0.0.1:6379> zadd salary 4000 zhaoliu 5000 wulexin
(integer) 2
(2)查看元素

查看所有的元素,从小到大排序

zrange set 0 -1
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "lisi"
3) "wangwu"
4) "zhaoliu"
5) "wulexin"

查看指定区间[ start, end ]

127.0.0.1:6379> zrange salary 1 3
1) "lisi"
2) "wangwu"
3) "zhaoliu"

查看所有的元素,从大到小排序

zrevrange set 0 -1
127.0.0.1:6379> zrevrange salary 0 -1
1) "wulexin"
2) "zhaoliu"
3) "wangwu"
4) "lisi"
5) "zhangsan"
(3)排序实现

按key排序

zrangebyscore set min max
127.0.0.1:6379> zrangebyscore salary 2000 4000
1) "lisi"
2) "wangwu"
3) "zhaoliu"

显示全部,从小到大

zrangebyscore set -inf +inf
127.0.0.1:6379> zrangebyscore salary -inf +inf
1) "zhangsan"
2) "lisi"
3) "wangwu"
4) "zhaoliu"
5) "wulexin"

显示key和value

zrangebyscore set -inf +inf withscores
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores
 1) "zhangsan"
 2) "1000"
 3) "lisi"
 4) "2000"
 5) "wangwu"
 6) "3000"
 7) "zhaoliu"
 8) "4000"
 9) "wulexin"
10) "5000"

显示区间范围内的排序结果

zrangebyscope set low high 
(4)移除元素

移除指定的元素

zrem set key
127.0.0.1:6379> zrem salary lisi
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "wangwu"
3) "zhaoliu"
4) "wulexin"
(5)获取整个集合元素个数
zcard set
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "wangwu"
3) "zhaoliu"
4) "wulexin"
127.0.0.1:6379> zcard salary
(integer) 4
(6)获取指定区间元素个数
zcount set low high
127.0.0.1:6379> zcount salary 2000 4000
(integer) 2
(7)查看某个元素的排名
zrank set member 因为默认按score升序,如果要从大到小排,使用
zrevrank set member

三、Redis三大特殊数据类型

1、geospatial地理位置

可以用于地图定位、距离计算等场景。

(1)添加地理位置geoadd

不能直接添加二级地理位置,有效的经度[-180, 180],有效的纬度[-85.0511, 85.0511],当坐标超出这个范围时,该命令会返回一个错误。

geoadd china:city(key的名字) 经度 纬度 地名
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijin
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
(2)获得当前定位geopos

得到一个经纬度坐标值

geopos china:city beijing
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.38999968767166138"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city chongqin
1) 1) "106.49999767541885376"
   2) "29.50000115408581536"
(3)求两地之间的距离

必须是已经设置过经纬度的地方

geodist china:city 地名1 地名2 单位(m,km,mi,ft)默认m
127.0.0.1:6379> geodist china:city beijing chongqin
"1466232.5278"
127.0.0.1:6379> geodist china:city beijing chongqin km
"1466.2325"
(4)范围查询georadius

以[经度,纬度]为中心,寻找方圆 距离 单位内的城市

georadius china:city 经度 纬度 距离 单位 [withdist, withcoord, count num]

可选参数:
withdist:将城市与中心位置的距离算出
withcoord: 将城市的经纬度写出
count num:只展示出num个符合条件的城市
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqin"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist
1) 1) "chongqin"
   2) "342.5131"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord
1) 1) "chongqin"
   2) 1) "106.49999767541885376"
      2) "29.50000115408581536"
(5)找出位于指定位置周围的元素

指定的位置是北京,周围1000km内

georadiusbymember china:city beijing 1000 km 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(6)使用Zset命令

因为geo底层实现原理是Zset,所以可以使用Zset命令来操作geo。

例如,查看所有的城市

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2、Hyperloglog基数统计

用于计数,例如计算某个网站的浏览人数,因为一个人可能多次浏览,就需要过滤出重复的数据,如果使用set会占用很多空间,而Hyperloglog只计数并不存储数据。

(1)添加数据

pfadd key val1 val2 ----
127.0.0.1:6379> pfadd site a b c d e f a c
(integer) 1

(2)统计数据

统计基数数量

pfcount key
127.0.0.1:6379> pfcount site
(integer) 6

(3)合并数据

会合并到第一个key即key1中

pfmerge key1 key2
127.0.0.1:6379> pfadd site1 a b c d e
(integer) 1
127.0.0.1:6379> pfadd site2 c d f g h
(integer) 1
127.0.0.1:6379> pfmerge site1 site2
OK
127.0.0.1:6379> pfcount site1
(integer) 8
127.0.0.1:6379> pfcount site2
(integer) 5
3、Bitmaps位图场景详解

Bitmaps是通过操作二进制来进行记录的,只有0、1两个状态。

用于统计用户信息,比如某个网站的活跃或不活跃用户,是否打卡、全勤等,适用于具有两个对立的状态的数据的统计。

例如,一周七天的打卡次数

周一记录为 (0,1)表示打卡成功。周二记录为(1,0)表示打卡失败。其中周一至周天为0-6

(1)添加数据

setbit key offset value
127.0.0.1:6379> setbit sign 0 1 周一打卡
(integer) 0
127.0.0.1:6379> setbit sign 1 0 周二未打卡
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0

(2)获取数据

getbit key offset
127.0.0.1:6379> getbit sign 0
(integer) 1

(3)统计数据

就是统计offset对应的value等于1的个数

bitcount key
127.0.0.1:6379> bitcount sign
(integer) 4

四、Redis事务

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

总结说:Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

  • Redis事务没有隔离级别的概念:

批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。

  • Redis不保证原子性:

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

Redis事务的三个阶段:

  • 开始事务
  • 命令入队
  • 执行事务

Redis事务相关命令:

watch key1 key2 … : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )

multi : 标记一个事务块的开始( queued )

exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )

discard : 取消事务,放弃事务块中的所有命令

unwatch : 取消watch对所有key的监控

127.0.0.1:6379> multi 开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec 执行事务
1) OK
2) OK
3) "v2"
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> discard 取消事务
OK
127.0.0.1:6379> exec 再次执行就会出错
(error) ERR EXEC without MULTI

若在事务队列中存在命令性错误(类似于java编译性错误),则执行EXEC命令时,所有命令都不会执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> setw k3 v3
(error) ERR unknown command 'setw', with args beginning with: 'k3' 'v3' 
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.

若在事务队列中存在语法性错误(类似于java的1/0的运行时异常),则执行EXEC命令时,其他正确命令会被执行,错误命令抛出异常。

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK

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

Redis监视测试

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money 监视money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20

如果发现事务执行失败,就先解锁事务,再次监视

五、SpringBoot集成Redis

1、通过Jedis操作Redis

首先创建一个SpringBoot项目,导入如下依赖

<!-- Jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>5.0.0</version>
        </dependency>
        <!-- fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.31_noneautotype</version>
        </dependency>

测试连接Redis

这里就使用Windows下安装的Redis,也可以远程连接Linux中安装的Redis。

需要先启动Redis,即双击Redis安装目录下的redis-server.exe文件,Java连接代码如下

    @Test
    void testPing() {
        Jedis jedis = new Jedis("127.0.0.1", 6379); // 本机地址,Redis端口号(默认)
        // Jedis中的命令就是Redis中的命令
        System.out.println(jedis.ping());
    }

输出PONG就表示连接成功!

测试其他基本命令

    @Test
    void testPing() {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println(jedis.ping());

        System.out.println("清空当前数据库" + jedis.flushDB());
        System.out.println("新增键值对" + jedis.set("name", "jpc客栈"));
        System.out.println("判断键是否存在" + jedis.exists("name"));
        System.out.println("获取键对应的值" + jedis.get("name"));
        System.out.println("新增键值对" + jedis.set("age", "21"));
        System.out.println("获取所有的键值对" + jedis.keys("*"));
    }
PONG
清空当前数据库OK
新增键值对OK
判断键是否存在true
获取键对应的值jpc客栈
新增键值对OK
获取所有的键值对[name, age]

测试Redis事务

    @Test
    void testTx() {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "jpc");
        // 开启事务
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();
        try{
            multi.set("user1", result);
            multi.set("user2", result);
            multi.exec(); // 执行事务
        }catch(Exception e){
            multi.discard(); // 放弃事务
            e.printStackTrace();
        }finally{
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close(); // 关闭连接
        }
    }
{"name":"jpc","hello":"world"}
{"name":"jpc","hello":"world"}

修改此处

String result = jsonObject.toJSONString();
        try{
            multi.set("user1", result);
            multi.set("user2", result);
            int i = 1 / 0; // 手动抛出异常
            multi.exec(); // 执行事务

运行后user1和user2都为null

2、自定义RedisTemplate

创建一个SpringBoot项目,导入相关的依赖

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到已经使用如下依赖

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

这里不再使用Jedis,而是lettuce

Jedis:采用的是直连方式,多个线程操作的话,是不安全的,如果想避免不安全,使用Jedis pool连接池

Lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况。

在application.yml文件中配置

基础配置

# Redis配置
spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379

详细配置

# Redis配置
spring:
  application:
    name: springboot3-redis  # 应用名称
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      database: 0 # 默认数据库
      timeout: 3000ms # 连接超时时间(毫秒)
      lettuce:
        pool:
          max-active: 20 # 连接池最大连接数(使用负值表示没有限制)默认8
          max-idle: 10 # 连接池中的最大空闲连接 默认8
          min-idle: 5 # 连接池中的最小空闲连接 默认0
          max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1ms

测试连接

    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void test1() {
        redisTemplate.opsForValue().set("name","jpc");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

RedisTemplate中操作不同的数据类型,使用opsFor形式,再使用Redis中的命令。

opsForValue 操作字符串
opsForList 操作List
opsForSet 操作Set
------其他类似

除了数据类型的操作,其他Redis命令都可以直接通过RedisTemplate完成,比如事务,基本的CRUD。

        RedisConnection connection = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection();
        // 清除缓存
        connection.flushDb();
自定义RedisTemplate如下(基于SpringBoot3和jdk17)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        // 序列化配置
        // 设置key序列化配置方式String
        redisTemplate.setKeySerializer(RedisSerializer.string());
        // 设置value序列化配置方式json
        redisTemplate.setValueSerializer(RedisSerializer.json());
        // 设置hashKey序列化配置方式String
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        // 设置hashValue序列化配置方式json
        redisTemplate.setHashValueSerializer(RedisSerializer.json());
        // 使配置生效
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

测试

实体类编写,需要实现Serializable接口

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable { // 需要实现序列化
    private String name;
    private Integer age;
}
@SpringBootTest
public class RedisTemplateTest {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    void test1(){
        User user = new User("JPC", 21);
        redisTemplate.boundValueOps("userKey").set(user);
        User rs = (User) redisTemplate.boundValueOps("userKey").get();
        System.out.println("rs= " + rs.toString());
        User rs1 = (User) redisTemplate.opsForValue().get("userKey");
        System.out.println("rs1= " + rs1.toString());
    }
}
rs= User(name=JPC, age=21)
rs1= User(name=JPC, age=21)
ramework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        // 序列化配置
        // 设置key序列化配置方式String
        redisTemplate.setKeySerializer(RedisSerializer.string());
        // 设置value序列化配置方式json
        redisTemplate.setValueSerializer(RedisSerializer.json());
        // 设置hashKey序列化配置方式String
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        // 设置hashValue序列化配置方式json
        redisTemplate.setHashValueSerializer(RedisSerializer.json());
        // 使配置生效
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

测试

实体类编写,需要实现Serializable接口

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable { // 需要实现序列化
    private String name;
    private Integer age;
}
@SpringBootTest
public class RedisTemplateTest {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    void test1(){
        User user = new User("JPC", 21);
        redisTemplate.boundValueOps("userKey").set(user);
        User rs = (User) redisTemplate.boundValueOps("userKey").get();
        System.out.println("rs= " + rs.toString());
        User rs1 = (User) redisTemplate.opsForValue().get("userKey");
        System.out.println("rs1= " + rs1.toString());
    }
}
rs= User(name=JPC, age=21)
rs1= User(name=JPC, age=21)

相关推荐

  1. Redis学习笔记

    2024-03-17 22:26:03       52 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-17 22:26:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-17 22:26:03       101 阅读
  3. 在Django里面运行非项目文件

    2024-03-17 22:26:03       82 阅读
  4. Python语言-面向对象

    2024-03-17 22:26:03       91 阅读

热门阅读

  1. linux下的进程间通信

    2024-03-17 22:26:03       38 阅读
  2. Python中的变量是什么类型?

    2024-03-17 22:26:03       40 阅读
  3. Mysql 表设计范式

    2024-03-17 22:26:03       43 阅读
  4. PyTorch学习笔记之激活函数篇(五)

    2024-03-17 22:26:03       46 阅读
  5. C/C++蓝桥杯之杨辉三角

    2024-03-17 22:26:03       42 阅读
  6. MySQL 中的自增ID及其应用场景

    2024-03-17 22:26:03       40 阅读
  7. C语言学习笔记day7

    2024-03-17 22:26:03       41 阅读
  8. 人工智能的发展与未来

    2024-03-17 22:26:03       50 阅读
  9. git |常用命令

    2024-03-17 22:26:03       44 阅读
  10. C++ 11:基于范围的 for 循环

    2024-03-17 22:26:03       43 阅读
  11. 服务器硬件基础知识

    2024-03-17 22:26:03       46 阅读