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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/462408.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

零知识玩转AVH(7)—— 门槛任务(2)所遇错误及解决(1)

接前一篇文章&#xff1a;零知识玩转AVH&#xff08;6&#xff09;—— 门槛任务&#xff08;1&#xff09;源码下载、编译及运行 上一回说到完成门槛任务 https://github.com/ArmDeveloperEcosystem/Paddle-examples-for-AVH &#xff08;推荐&#xff0c;内含 ML 视觉用例&am…

前后端分离项目环境搭建

1. 使用到的技术和工具 springboot vue项目的搭建 工具 idea&#xff0c;mavennodejs 2. 后端框架搭建 利用maven创建springboot项目 3. 前端项目搭建 1. 安装相关工具 nodejs&#xff1a; 一个开源、跨平台的 JavaScript 运行时环境&#xff0c;可以理解成java当中需要…

企业微信 API 接口调用教程:深入解析企业微信 API 的用法

本文通过 access_token 凭证的方式来讲解怎么调用 企业微信 API&#xff0c;并一步步介绍如何获取企业微信 API 的 corpsecret、corpid、access_token 凭证以及怎么向企业微信的应用发送消息。 企业微信 API 在线地址为&#xff1a;qiyeweixin.apifox.cn/ &#xff0c;这个在线…

LInux 进程替换(理解系统调用)

目录 一、替换原理 二、替换函数 1、exec函数 2、命名理解 3、返回值 4、使用execl/lp、execv/vp 5、执行自定义命令 Makefile编译多个文件 命令行程序mycmd.c 传入自己的可执行文件 7、子进程都继承父进程环境变量 8、execle/ve修改子进程环境变量 9、exece函数为…

Hack The Box-Jab

目录 信息收集 nmap enum4linux 服务信息收集 Pidgin kerbrute hashcat 反弹shell & get user 提权 系统信息收集 端口转发 漏洞利用 get root 信息收集 nmap 端口探测┌──(root㉿ru)-[~/kali/hackthebox] └─# nmap -p- 10.10.11.4 --min-rate 10000 -oA…

Docker----Dockerfile构建微服务镜像

目录 一、关键步骤 二、具体步骤 1、准备后端jar包(这里以java后端演示) 2、编写Dockerfile 3、构建镜像 4、运行镜像容器 5、测试是否成功 一、关键步骤 1、准备后端jar包(这里以java后端演示) 2、编写Dockerfile 3、构建镜像 4、运行镜像容器 5、测试是否成功 二…

Openfeign使用教程(带你快速体验Openfeign的便捷)

文章摘要 本文中将教会您如何快速使用Openfeign&#xff0c;包括Opengfeign的基础配置、接口调用、接口重试、拦截器实现、记录接口日志信息到数据库 文章目录 文章摘要一、Openfeign初步定义二、Openfeign快速入门1.引入maven坐标2.启动类增加EnableFeignClients注解3.定义fei…

从金蝶云星空到钉钉通过接口配置打通数据

从金蝶云星空到钉钉通过接口配置打通数据 对接系统金蝶云星空 金蝶K/3Cloud&#xff08;金蝶云星空&#xff09;是移动互联网时代的新型ERP&#xff0c;是基于WEB2.0与云技术的新时代企业管理服务平台。金蝶K/3Cloud围绕着“生态、人人、体验”&#xff0c;旨在帮助企业打造面…

IDEA连接Mysql失败:下载驱动失败,Failed todownload Cannot download Read timed out

解决&#xff1a; 1. 手动加入jar包 2.选择自己maven仓库中存在mysql-connector 3. 选择完毕后&#xff0c;确定使用&#xff1a; 4. 进行测试连接

【代码随想录】【回溯算法】补day24:组合问题以及组合的优化

回溯算法&#xff1a;递归函数里面嵌套着for循环 给定两个整数 n 和 k&#xff0c;返回 1 … n 中所有可能的 k 个数的组合。 示例: 输入: n 4, k 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 包含组合问题和组合问题的剪枝优化 class solution:def combine(se…

免费接口调用 招标信息自动抽取|招标信息|招标数据解析接口

一、开源项目介绍 一款多模态AI能力引擎&#xff0c;专注于提供自然语言处理&#xff08;NLP&#xff09;、情感分析、实体识别、图像识别与分类、OCR识别和语音识别等接口服务。该平台功能强大&#xff0c;支持本地化部署&#xff0c;并鼓励用户体验和开发者共同完善&#xf…

git:码云仓库提交以及Spring项目创建

git&#xff1a;码云仓库提交 1 前言 码云访问稳定性优于github&#xff0c;首先准备好码云的账户&#xff1a; 官网下载GIT&#xff0c;打开git bash&#xff1a; 查看当前用户的所有GIT仓库&#xff0c;需要查看全局的配置信息&#xff0c;使用如下命令&#xff1a; git …

旅游管理系统 |基于springboot框架+ Mysql+Java+Tomcat的旅游管理系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 管理员功能登录前台功能效果图 系统功能设计 数据库E-R图设计 lunwen参考 摘要 研究…

羊大师分析羊奶入菜,美味新体验

羊大师分析羊奶入菜&#xff0c;美味新体验 羊奶&#xff0c;这一古老而珍贵的食材&#xff0c;近年来在料理界掀起了一股新风潮。其醇厚的口感和丰富的营养价值&#xff0c;让越来越多的人开始尝试将羊奶融入日常烹饪中&#xff0c;为味蕾带来前所未有的新体验。 在传统的烹饪…

由浅到深认识C语言(5):函数

该文章Github地址&#xff1a;https://github.com/AntonyCheng/c-notes 在此介绍一下作者开源的SpringBoot项目初始化模板&#xff08;Github仓库地址&#xff1a;https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址&#xff1a;https://blog.csdn…

湖南麒麟SSH服务漏洞

针对湖南麒麟操作系统进行漏洞检测时&#xff0c;会报SSH漏洞风险提醒&#xff0c;具体如下&#xff1a; 针对这些漏洞&#xff0c;可以关闭SSH服务&#xff08;前提是应用已经部署完毕不再需要通过SSH远程访问传输文件的情况下&#xff0c;此时可以通过VNC远程登录方法&#x…

Arduino IDE配置ESP8266开发环境

一、配置步骤 在Arduino IDE中配置ESP8266开发环境的详细步骤如下&#xff1a; 1.打开Arduino IDE&#xff0c;依次点击“文件”->“首选项”&#xff0c;在“附加开发板管理器网址”一栏添加ESP8266开发板的网址。常用的网址是&#xff1a; http://arduino.esp8266.com/s…

软件测试——接口常见问题汇总

前言 今天我们来聊聊接口设计用例设计&#xff0c;说到这个接口&#xff0c;相信绝大多数的测试员都有遇到过某些棘手的问题&#xff0c;那么今天我们就来总结一下在接口方面会遇到的难题。 一、接口用例设计 接口测试用例可以从功能、性能、安全三方面进行入手&#xff0c;…

【好书推荐-第十二期】《并行计算与高性能计算》

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公众号&#xff1a;洲与AI。 &#x1f388; 本文专栏&#xff1a;本文收录…

[java基础揉碎]多态参数

多态参数 方法定义的形参类型为父类类型&#xff0c;实参类型允许为子类类型 例子: 定义一个员工类, 有名字和工资两个属性, 有年工资的方法 定义一个普通员工继承了员工类 , 重写了年工资的方法 定义一个经理类, 也继承了员工类, 同时经理多以了一个奖金的属性, 重写的年…