Redis第一篇之基础入门,可以快速上手进行一些基础的操作

在看redis之前,先来了解一下NoSQL(非关系型数据库)

1. NoSQL数据库简介

1.1 概述

NoSQL(Not Only SQL),意为"不仅仅是SQL",泛指非关系型数据库.NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储,因此大大增加了数据库的扩展能力,有以下的特点:

  • 不遵循SQL标准
  • 不支持ACID
  • 远超于SQL的性能

1.2 NoSQL的适应性场景:

  • 对数据高并发的读写
  • 海量数据的读写
  • 对数据高扩展性的

1.3 NoSQL不适应的场景:

  • 需要事务的支持
  • 基于sql的结构化查询,处理复杂的关系,需要即时查询
  • 用不着sql和用了sql也不行的情况,请考虑使用NoSQL

1.4 Memcache

  • 很早出现的NoSQL数据库

  • 数据都存储在内存中,一般不持久化

  • 支持简单的key-value模式,支持类型单一

  • 一般是作为缓存数据库辅助持久化的数据库

1.5 Redis

  • 几乎覆盖了Memcache的绝大部分功能
  • 数据都在内存中,支持持久化,主要用作备份恢复
  • 除了支持简单的key-value模式,还支持多种数据结构的存储,如list,set,hash,zset等
  • 一般是作为缓存数据库辅助持久化的数据库

1.6 MongoDB

  • 高性能,开源,模式自由的文档型数据库
  • 数据都存储在内存中,如果内存不足,把不常用的数据保存到硬盘
  • 虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能
  • 支持二进制数据及大型对象
  • 可以根据数据的特点替代RDBMS,成为独立的数据库.或者配合RDBMS,存储特定的数据

2. Redis的性能和基础知识

2.1 测试性能

redis-benchmark是一个压力测试工具!
官方自带的性能测试工具!
redis-benchmark命令参数!
我们来简单的测试一下

# 测试: 100个并发连接 每个并发100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 10000

在这里插入图片描述
在这里插入图片描述

可以发现效率非常的快,费时少

2.2 Redis的基础知识

2.2.1 Redis默认有16个数据库

用vim命令进入配置文件中进行查看
在这里插入图片描述
在这里插入图片描述

默认使用的是第0个数据库,可以使用select进行切换数据库

比如说我们使用第2个,用 select 命令进行切换
在这里插入图片描述

127.0.0.1:6379> select 2  # select切换数据库 
OK
127.0.0.1:6379[2]> DBSIZE # 查看数据库中k-v个数
(integer) 0

在这里插入图片描述

2.2.2 设置键值

127.0.0.1:6379[2]> set name xiaowang  # 设置键值
OK
127.0.0.1:6379[2]> DBSIZE
(integer) 1
127.0.0.1:6379[2]> KEYS *         # 查看所有的key
1) "name"
127.0.0.1:6379[2]> get name       # 得到当前key对应的value值
"xiaowang"
127.0.0.1:6379[2]> FLUSHDB        # 清空当前的DB,FLUSHALL是清空所有的DB 
OK
127.0.0.1:6379[2]> DBSIZE
(integer) 0

2.2.3 Redis是单线程的

官方文档: 因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈。Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。

详细原因:

  • 不需要各种锁的性能消耗
    Redis 的数据结构并不全是简单的 Key-Value,还有 list,hash 等复杂的结构。这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在 hash 当中添加或者删除一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。
    总之,在单线程的情况下,代码更清晰,处理逻辑更简单,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗,不存在多进程或者多线程导致的切换而消耗 CPU。
  • 单线程多进程的集群方案
    单线程的威力实际上非常强大,每核效率也非常高。多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。所以单线程、多进程的集群不失为一个时髦的解决方案。
  • CPU 消耗
    采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。但是如果 CPU 成为 Redis 瓶颈,或者不想让服务器其他 CPU 核闲置,那怎么办?
    可以考虑多起几个 Redis 进程,Redis 是 key-value 数据库,不是关系数据库,数据之间没有约束。只要客户端分清哪些 key 放在哪个 Redis 进程上就可以了。

2.2.4 为什么Redis是单线程的还这么快?

误区:

  • 高性能的服务器一定是多线程的?
    不一定
  • 多线程一定比多线程效率高?
    不一定,多线程会进行上下文的切换,是一个耗时的操作

核心:Redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程会进行上下文的切换,比较耗时间.对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,内存情况下,这个就是最佳的方案.

3. Redis中的五大数据类型

Redis是一个开源的,内存中的数据结构存储系统,它可以用作数据库,缓存消息中间件MQ.它支持多种类型的数据结构,如字符串(string),散列(hash),列表(list),集合(set),有序集合(sorted sets),范围查询和地理空间索引半径查询.Redis内置了复制(replication),LUA脚本,LRU驱动事件,事务和不同级别的磁盘持久化,并通过Redis哨兵和自动分区提高可用性.

Redis-Key (基础命令)

127.0.0.1:6379> FLUSHALL   			# 清空所有的数据库
OK
127.0.0.1:6379> set name wang 		# 设置key-value
OK
127.0.0.1:6379> keys * 				# 查看所有的key
1) "name"
127.0.0.1:6379> set age 1 			# 设置key-value
OK
127.0.0.1:6379> keys *  			# 查看所有的key
1) "age"
2) "name"
127.0.0.1:6379> get age				# 获取key对应的value
"1"
127.0.0.1:6379> EXISTS name			# 查看key是否存在
(integer) 1
127.0.0.1:6379> EXISTS age			# 查看key是否存在
(integer) 1
127.0.0.1:6379> EXISTS gender		# 查看key是否存在
(integer) 0
127.0.0.1:6379> move name 1			# 移动key-value 到 1号数据库
(integer) 1
127.0.0.1:6379> SELECT 1			# 切换到 1 号数据库
OK
127.0.0.1:6379[1]> keys *			# 查看所有的key
1) "name"
127.0.0.1:6379[1]> FLUSHDB			# 清空当前数据库
OK
127.0.0.1:6379[1]> SELECT 0			# 切换到 0 号数据库
OK
127.0.0.1:6379> keys *				# 查看所有的key
1) "age"
127.0.0.1:6379> set name xiaowang	# 设置key-value
OK
127.0.0.1:6379> keys *				# 查看所有的key
1) "age"
2) "name"
127.0.0.1:6379> EXPIRE name 5		# 让key在5s后过期
(integer) 1
127.0.0.1:6379> ttl name			# 查看剩余过期时间
(integer) 0
127.0.0.1:6379> keys *				# 查看所有的key,发现name已经消失了
1) "age"
127.0.0.1:6379> set name xiaowang	# 设置key-value
OK
127.0.0.1:6379> TYPE name			# 查看当前key的类型
string
127.0.0.1:6379> TYPE age			# 查看当前key的类型
string

如果遇到不会的命令可以去官网查看(https://www.redis.net.cn/)

在这里插入图片描述

3.1 String (字符串)

3.1.1 基本操作

##############################################################################
127.0.0.1:6379> set key1 v1			# 设置值
OK	
127.0.0.1:6379> get key1			# 获取值
"v1"
127.0.0.1:6379> EXISTS key1			# 判断是否存在
(integer) 1
127.0.0.1:6379> APPEND key1 hello	# 拼接字符串
(integer) 7
127.0.0.1:6379> get key1			# 获取值
"v1hello"
127.0.0.1:6379> APPEND key1 "hello"	# 拼接字符串,如果key不存在,相当于set-key,设置值
(integer) 12
127.0.0.1:6379> get key1			# 获取值
"v1hellohello"
127.0.0.1:6379> STRLEN key1			# 获取字符串的长度
(integer) 12
127.0.0.1:6379> APPEND key1 ",xiaowang"		# 拼接字符串
(integer) 21
127.0.0.1:6379> STRLEN key1			# 获取长度
(integer) 21
127.0.0.1:6379> get key1			# 获取值
"v1hellohello,xiaowang"
##############################################################################

3.1.2 自增(incr,incrby),自减(decr,decrby)

##############################################################################
127.0.0.1:6379> set views 0    
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views          # +1操作
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incr views			# +1操作
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views			# -1操作
(integer) 1
127.0.0.1:6379> decr views			# +1操作
(integer) 0
127.0.0.1:6379> decr views			# +1操作
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> INCRBY views 10		# +10操作,指定增量
(integer) 9
127.0.0.1:6379> DECRBY views 5		# -5操作
(integer) 4
127.0.0.1:6379> get views
"4"
##############################################################################

3.1.3 字符串范围 (getrange)

##############################################################################
127.0.0.1:6379> set key1 "hello"
OK
127.0.0.1:6379> get key1
"hello"
127.0.0.1:6379> APPEND key1 ",xiaowang"   # 拼接字符串
(integer) 14
127.0.0.1:6379> get key1
"hello,xiaowang"
127.0.0.1:6379> GETRANGE key1 0 3		  # 截取字符串,是闭区间的[0,3](0.1.2.3)
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1		  # [0,-1] 表示截取整个字符串
"hello,xiaowang"
##############################################################################

3.1.4 替换(setrange)

##############################################################################
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> SETRANGE key2 1 xx		  # 替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
##############################################################################

3.1.5 setex和setnx

##############################################################################
# setex: 设置过期时间
127.0.0.1:6379> setex key3 30 "hello"     # 30s后key3过期
OK				  
127.0.0.1:6379> ttl key3				  # 查看当前剩余时间
(integer) 17
127.0.0.1:6379> get key3
"hello"
# setnx: 不存在时再设置值,存在时不生效(在分布式锁中会常常使用)
127.0.0.1:6379> setnx mykey "redis"		  # 设置mykey,当前不存在,设置成功
(integer) 1
127.0.0.1:6379> keys *					  # 查看当前所有的key,key3已经过了30s失效了
1) "mykey"
2) "key2"
3) "key1"
127.0.0.1:6379> setnx mykey "MongoDB"	  # mykey存在,所以不做修改,创建失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"
##############################################################################

3.1.6 mset和mget

# mset: 批量设置k-v
127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 k4 v4
OK
127.0.0.1:6379> keys *
1) "k4"
2) "k1"
3) "k3"
4) "k2"
# mget: 批量获取k-v
127.0.0.1:6379> MGET k1 k2 k3 k4
1) "v1"
2) "v2"
3) "v3"
4) "v4"
# msetnx 是一个原子操作,要么一起成功,要么一起失败
127.0.0.1:6379> MSETNX k1 v1 k5 v5   # k1存在,所以不能设置成功,k5为nil
(integer) 0
127.0.0.1:6379> get key5
(nil)

3.1.7 getset

# getset: 先get再set
127.0.0.1:6379> getset db redis   #先get发现db不存在,再set db为redis
(nil)
127.0.0.1:6379> get db			  # get发现db为redis
"redis"
127.0.0.1:6379> getset db mongodb # 先get发现db存在且为redis,再set db为mongodb
"redis"
127.0.0.1:6379> get db			  # get发现db已经被set为mongodb了
"mongodb"

3.2 List (列表)

在redis里面,我们可以把list玩成栈,队列,阻塞队列!

所有的 list 命令都是用 l 开头的

3.2.1 lpush,rpush,lrange

# lpush 将一个值或者多个值,插入到列表的头部(左)
127.0.0.1:6379> LPUSH list k1
(integer) 1
127.0.0.1:6379> LPUSH list k2
(integer) 2
127.0.0.1:6379> LPUSH list k3
(integer) 3
# LRANGE 获取全部数据
127.0.0.1:6379> LRANGE list 0 -1	
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> LRANGE list 0 1		# 通过区间来获取具体的值
1) "k3"
2) "k2"
# rpush 将一个值或者多个值,插入到列表的尾部(右)
127.0.0.1:6379> RPUSH list right
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1	# 获取全部数据,发现right在最后
1) "k3"
2) "k2"
3) "k1"
4) "right"

3.2.2 lpop,rpop

127.0.0.1:6379> LRANGE list 0 -1
1) "k3"
2) "k2"
3) "k1"
4) "right"

# lpop: 移除列表的第一个元素
127.0.0.1:6379> LPOP list
"k3"
127.0.0.1:6379> LRANGE list 0 -1
1) "k2"
2) "k1"
3) "right"

# rpop: 移除列表的最后一个元素
127.0.0.1:6379> RPOP list
"right"
127.0.0.1:6379> LRANGE list 0 -1
1) "k2"
2) "k1"

3.2.3 lindex和llen

127.0.0.1:6379> LINDEX list 0   # 获取0号下标元素
"k2"
127.0.0.1:6379> LINDEX list 1	# 获取1号下标元素
"k1"
#################################################
127.0.0.1:6379> LLEN list   	# 获取list长度
(integer) 2

3.2.4 lrem 移除

127.0.0.1:6379> LREM list 1 k1       # 将k1移除一个
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "k2"
2) "k2"
3) "k1"
127.0.0.1:6379> LREM list 2 k2		# 将k2移除两个
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "k1"

3.2.5 ltrim 截短

# 清空当前数据库
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> RPUSH mylist "hello1"
(integer) 1
127.0.0.1:6379> RPUSH mylist "hello2"
(integer) 2
127.0.0.1:6379> RPUSH mylist "hello3"
(integer) 3
127.0.0.1:6379> RPUSH mylist "hello4"
(integer) 4

# 通过下标截取指定的长度,mylist已经被改变了
127.0.0.1:6379> LTRIM mylist 1 2		
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello2"
2) "hello3"

3.2.6 rpoplpush

# 清空当前数据库
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> rpush list "hello0"
(integer) 1
127.0.0.1:6379> rpush list "hello1"
(integer) 2
127.0.0.1:6379> rpush list "hello2"
(integer) 3

# rpop移除list中的最后一个元素lpush到mylist中,如果mylist不存在则会创建mylist
127.0.0.1:6379> rpoplpush list mylist	
"hello2"
# 查看list中的所有数据
127.0.0.1:6379> lrange list 0 -1
1) "hello0"
2) "hello1"
# 查看mylist中的所有数据
127.0.0.1:6379> lrange mylist 0 -1
1) "hello2"

3.2.7 lset 设置,更新

# 清空当前数据库
127.0.0.1:6379> FLUSHDB
OK
# 判断list是否存在
127.0.0.1:6379> EXISTS list
(integer) 0
# 往0号位置设置一个item的值,list不存在会报错
127.0.0.1:6379> LSET list 0 item
(error) ERR no such key
# 创建list,将value1添加进去
127.0.0.1:6379> LPUSH list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
# lset: 将指定下标的值替换成另外一个值,相当于一个更新操作
# 如果下标存在,则更新
127.0.0.1:6379> LSET list 0 item
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"
# 1号位置的值不存在,所以不能更新
127.0.0.1:6379> LSET list 1 other
(error) ERR index out of range

3.2.8 lindex

127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> rpush list hello
(integer) 1
127.0.0.1:6379> rpush list world
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "world"

# linsert: 将某个具体的value插入到列表中某个元素的前面或者后面
# 插入到world的前面
127.0.0.1:6379> LINSERT list before world other
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "other"
3) "world"
# 插入到hello的后面
127.0.0.1:6379> LINSERT list after hello new
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "new"
3) "other"
4) "world"

3.2.9 一个小结

  • list实际上是一个链表,可以在某个元素的前面,后面插入,也可以在头尾插入
  • 当我们lpush或者rpush的时候,如果key不存在,则会创建链表
  • 如果key存在的话,新增内容
  • 如果移除了所有的内容,也就是一个空链表,也代表不存在
  • 在两边插入或者删除值效率最高,中间元素相对来说效率会低一点

3.3 Set(集合)

set是一个无序不重复集合,命令都是s开头的

3.3.1 sadd,smembers,sismember

# sadd: 添加元素,set不存在时创建set,存在时直接添加
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset xiaowang
(integer) 1
127.0.0.1:6379> sadd myset xiaozhang
(integer) 1
# smembers: 输出set中的所有元素
127.0.0.1:6379> SMEMBERS myset
1) "xiaozhang"
2) "hello"
3) "xiaowang"
# ismember: 判断某个元素是否存在
127.0.0.1:6379> SISMEMBER myset hello
(integer) 1
127.0.0.1:6379> SISMEMBER myset wang
(integer) 0

3.3.2 scard,srem,srandmember

# scard: 获取集合中的元素个数
127.0.0.1:6379> SCARD myset
(integer) 3

# srem: 移除某个特定的值
127.0.0.1:6379> srem myset hello
(integer) 1
# 查看集合中的元素
127.0.0.1:6379> SMEMBERS myset
1) "xiaozhang"
2) "xiaowang"
# 查看集合个数
127.0.0.1:6379> SCARD myset
(integer) 2

# srandmember: 随机抽取一个元素
127.0.0.1:6379> SRANDMEMBER myset
"xiaowang"
127.0.0.1:6379> SRANDMEMBER myset
"hello1"
127.0.0.1:6379> SRANDMEMBER myset
"hello1"
127.0.0.1:6379> SRANDMEMBER myset
"xiaowang"

3.3.3 spop,smove

# 查看所有元素
127.0.0.1:6379> SMEMBERS myset
1) "hello1"
2) "xiaozhang"
3) "hello"
4) "xiaowang"

# spop: 随机移除一个元素
127.0.0.1:6379> SPOP myset
"hello1"
# spop: 随机移除一个元素
127.0.0.1:6379> SPOP myset
"hello"

# 查看所有元素
127.0.0.1:6379> SMEMBERS myset
1) "xiaozhang"
2) "xiaowang"
# 再创建一个myset2,添加一个set2
127.0.0.1:6379> SADD myset2 set2
(integer) 1

# smove: 将指定的值进行移动
# 将myset中的xiaowang移动到myset2
127.0.0.1:6379> SMOVE myset myset2 xiaowang
(integer) 1
127.0.0.1:6379> SMEMBERS myset2
1) "set2"
2) "xiaowang"

3.3.4 sdiff,sinter,sunion

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 key1 d
(integer) 1
127.0.0.1:6379> sadd key2 b
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
# sdiff: 求差集
# key1和key2的差集为 a,c
127.0.0.1:6379> SDIFF key1 key2
1) "a"
2) "c"
127.0.0.1:6379> SDIFF key2 key1
1) "e"

# sinter: 求交集
# key1和key2的交集为 b,d
127.0.0.1:6379> SINTER key1 key2
1) "d"
2) "b"

# sunion: 求并集
127.0.0.1:6379> SUNION key1 key2
1) "a"
2) "e"
3) "c"
4) "b"
5) "d"

3.4 Hash(哈希)

Map集合,key-map集合,这时候的值是一个map集合.本质和String类型没有太大的区别,还是一个简单的集合

hash的命令都是以h开头的

基本操作

# hset
127.0.0.1:6379> hset myhash field1 v1
(integer) 1

# hget
127.0.0.1:6379> hget myhash field1
"v1"

# hmset
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK

# hmget
127.0.0.1:6379> hmget myhash field1 field2
1) "hello"
2) "world"

# hgetall
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"

# hlen: 求内容大小
127.0.0.1:6379> hlen myhash
(integer) 2

# hexists: 判断是否存在
127.0.0.1:6379> HEXISTS myhash field1
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0

# hkeys: 获取hash中所有的键
127.0.0.1:6379> hkeys myhash
1) "field1"
2) "field2"

#hvals: 获取hash中所有的值
127.0.0.1:6379> hvals myhash
1) "hello"
2) "world"

剩下的都和string里面的差不多,就是命令前加了个h,就不过多写了

3.5 Zset(有序集合)

zset就是在set的基础上加了一个值

set k1 v1
zset k1 score v1

基本操作

# zadd  添加一个值
127.0.0.1:6379> ZADD myzset 1 one	# 
(integer) 1
127.0.0.1:6379> ZADD myzset 2 two	
(integer) 1

#zadd 添加多个值
127.0.0.1:6379> ZADD myzset 3 three 4 four	
(integer) 2
# zrange 查看所有元素
127.0.0.1:6379> ZRANGE myzset 0 -1  
1) "one"
2) "two"
3) "three"
4) "four"

##############################################################################

127.0.0.1:6379> zadd salary 2500 xiaowang
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaozhang
(integer) 1
127.0.0.1:6379> zadd salary 500 xiaoli
(integer) 1
# zrangebyscore: 排序,-inf是负无穷,+inf是正无穷
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "xiaoli"
2) "xiaowang"
3) "xiaozhang"
# zrangebyscore..........withscores: 在显示的时候带上数据
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "xiaoli"
2) "500"
3) "xiaowang"
4) "2500"
5) "xiaozhang"
6) "5000"
# zrangebyscore: 排序,负无穷-3000之间的进行排序
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 3000 withscores
1) "xiaoli"
2) "500"
3) "xiaowang"
4) "2500"

4. 事务

Redis 事务本质:一组命令的集合!一个事务中的所有命令都会被序列化

在事务的执行过程中,会按照顺序执行!

特性: 一次性,顺序性,排他性 ,执行一系列的命令!

-------- 队列 set set set  执行 ------------

Redis单条命令是保证原子性的

但是Redis的事务是不保证原子性的,没有隔离级别的概念

所有的命令在事务,并没有被直接执行,只有发起执行命令的时候才会执行

redis的事务:

  • 开启事务 (multi)
  • 命令入队 (…)
  • 执行事务 (exec)

4.1 执行事务

# multi 开启一个事务
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)> set k3 v3
QUEUED

# 执行事务
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v2"
4) OK

注意: exec执行后,这个事务就执行结束了,想要另外操作,需要再次去开启事务

4.2 放弃事务

# 开启一个事务
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)> set k4 v4
QUEUED

# discard 放弃一个事务
127.0.0.1:6379(TX)> discard
OK

# 放弃一个事务后,事务队列中的命令都不会被执行
127.0.0.1:6379> get k4
(nil)

4.3 出现的异常

4.3.1 编译型异常

代码有问题,命令有错,事务中所有的命令都不会被执行

# 开启一个事务
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)> set k3 v3
QUEUED

# 错误的命令
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k0 v5
QUEUED

# 执行时事务报错
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
# 所有的命令都不会被执行
127.0.0.1:6379> get k1
(nil)

4.3.2 运行时异常

如果事务队列中存在语法性错误,那么执行命令的时候,其他命令是可以正常执行的,错误命令会抛出异常

# 清空当前库
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set k1 v1
OK
# 开启事务
127.0.0.1:6379> multi
OK
# k1 自增1,执行的时候失败
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)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED

# 出现异常,但是其他的命令不会收到影响,正常执行
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"

# 可以正确的获得到k2和k3的值
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

4.4 监控 Watch(面试常问)

  • 悲观锁
    很悲观,认为什么时候都会出问题,所以无论做什么都会加锁!比较影响性能.

  • 乐观锁

    • 很乐观,认为什么时候都不会出问题,所以不会上锁,更新数据的时候去判断一下在此期间是否有人修改过这个数据
    • 获取version
    • 更新的时候比较version
  • redis的监视测试

    正常执行成功:

    127.0.0.1:6379> set money 100
    OK
    127.0.0.1:6379> set out 0
    OK
    # 监视 money
    127.0.0.1:6379> watch 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
    

    测试多线程修改值,使用watch可以当做redis的乐观锁操作

    # 线程1 :
    # 对money进行监控
    127.0.0.1:6379> watch money
    OK
    # 开启事务
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379(TX)> decrby money 10
    QUEUED
    127.0.0.1:6379(TX)> incrby out 10
    QUEUED
    
    # 此时线程1的事务不执行,并且线程2对money进行了修改
    
    # 线程2 :
    127.0.0.1:6379> get money
    "80"
    127.0.0.1:6379> set money 1000
    OK
    
    # 此时线程1的事务进行执行,发现没有返回结果
    # 原因是watch监控到了线程2对money的操作,导致了执行失败
    127.0.0.1:6379(TX)> exec
    (nil)
    

    事务执行失败后进行解锁操作(unwatch),再进行监控(watch)

    # 如果发现事务执行失败,先进行解锁
    127.0.0.1:6379> unwatch
    OK
    # 获取最新的值,重新进行监控
    127.0.0.1:6379> watch money
    OK
    127.0.0.1:6379> get money
    "1000"
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379(TX)> decrby money 10
    QUEUED
    127.0.0.1:6379(TX)> incrby out 10
    QUEUED
    # 对比监视的值是否发生了变化,如果没有发生变化,那么可以执行成功,变化了就执行失败
    127.0.0.1:6379(TX)> exec
    1) (integer) 990
    2) (integer) 30
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

感冒不能喝咖啡!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值