Redis基础
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Redis基础,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含22447字,纯文字阅读大概需要33分钟。
内容图文
一、什么是redis?
Redis(Remote Dictionary Server,远程字典服务) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)数据库中的键值对数据库。与传统关系型数据库不同的是, Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作。它会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并在此基础上实现了master-slave(主从)同步。
Redis可以用作数据库,缓存和消息中间件MQ
二、Redis有哪些优缺点?
优点:
- 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
- 支持数据持久化,支持AOF和RDB两种持久化方式。
- 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
- 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
缺点:
- 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
- Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
- Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
三、基础知识
1.切换数据库:select
Redis有16个数据库,默认使用的是第0个,可以使用select
来切换数据库。DBSIZE来查看数据库的规模
2.清除数据库数据
清空当前数据库数据:flushdb
清空所有数据库数据:FLUSHALL
3.Redis是单线程的。
既然是单线程为什么还这么快?
redis是将所有的数据全部放在内存中的,所以使用单线程去操作效率就是最高的,而多线程操作时CPU上下文会切换,这是耗时的。对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,在内存情况下,redis的单线程就是最佳方案。
四、数据类型
Redis主要有5种数据类型,包括String,List,Set,Zset,Hash
(一)String
做简单的键值对缓存。
127.0.0.1:6379> set name yang #设置key-value
OK
127.0.0.1:6379> set age 12
OK
127.0.0.1:6379> keys * #查看所有的key
1) "age"
2) "name"
127.0.0.1:6379> exists name #查看某一个key是否存在
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
127.0.0.1:6379> move name 1 #移除某个key
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> type age #查看key的类型
string
127.0.0.1:6379> append age "foever" #给key对应的value追加字符串,如果key不存在,就相当于set key
(integer) 8
127.0.0.1:6379> get age
"12foever"
127.0.0.1:6379> strlen age #获取字符串的长度
(integer) 8
127.0.0.1:6379>
##############################################################
# i++
# 步长 i+=
127.0.0.1:6379> set views 0 #设置初始为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> incr views
(integer) 2
127.0.0.1:6379> decr views #自减1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> incrby views 10 #设置步长,指定增量
(integer) 10
127.0.0.1:6379> get views
"10"
##############################################################
#字符串范围 range
127.0.0.1:6379> set key1 "hello,yang"
OK
127.0.0.1:6379> GETRANGE key1 0 3 #截取字符串[0.3],相当于java中的substring
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1 #获取全部的字符串,相当于get key
"hello,yang"
127.0.0.1:6379> set key2 asdfwe
OK
127.0.0.1:6379> SETRANGE key2 1 ** #替换字符串,1为开始替换的下标,相当于java中的replace
(integer) 6
127.0.0.1:6379> get key2
"a**fwe"
##############################################################
# setex 设置过期时间
#setnx 如果不存在再设置 在分布式锁中经常用
127.0.0.1:6379> setex key3 30 "hello" #设置key3的值为hello,30秒后过期
OK
127.0.0.1:6379> ttl key3 #查看过期剩余时间
(integer) 24
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> get key3 #过期
(nil)
127.0.0.1:6379> setnx mykey "nihao" #如果mykey不存在,则创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "key1"
2) "mykey"
3) "key2"
127.0.0.1:6379> get mykey
"nihao"
127.0.0.1:6379> setnx mykey "study" #如果mykey已存在,则创建失败
(integer) 0
127.0.0.1:6379> get mykey
"nihao"
##############################################################
# 批量设置批量获取
# mset
# mget
#对象,user:{id}:{filed}
127.0.0.1:6379> mset user:1:name yangg user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "yangg"
2) "2"
(二)List
在redis中,list可以做栈、队列、阻塞队列,即存储一些列表型的数据结构
当从左边压入,从右边弹出时,就是消息队列(Lpush Rpop),当从左边压入,左边弹出时,就是栈(Lpush Lpop)
所有list命令都是用l或者r开头的
127.0.0.1:6379> LPUSH list one #向list中添加数据,插入列表头部(左侧)
(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 #取出list中[0 1]范围的数据,发现是后存入的两个数,这也是list队列的体现
1) "three"
2) "two"
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> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
##############################################################
#移除数据
lpop #从左边开始移除
rpop #从右边开始移除
127.0.0.1:6379> lpop list
"three"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3) "four"
127.0.0.1:6379> rpop list
"four"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
##############################################################
#通过下标获取值
#lindex
127.0.0.1:6379> LINDEX list 0 #获取下标为0的值
"two"
127.0.0.1:6379> LINDEX list 1
"one"
##############################################################
#获取列表长度
#llen
127.0.0.1:6379> llen list
(integer) 2
##############################################################
#移除指定的值
#lrem
127.0.0.1:6379> LRANGE list 0 -1
1) "2"
2) "2"
3) "1"
4) "two"
5) "one"
6) "one"
127.0.0.1:6379> lrem list 1 one #移除1个one
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "2"
2) "2"
3) "1"
4) "two"
5) "one"
127.0.0.1:6379> lrem list 2 2 #移除2个2
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "1"
2) "two"
3) "one"
##############################################################
#截断
#ltrim
127.0.0.1:6379> LRANGE list 0 -1
1) "3"
2) "2"
3) "1"
4) "two"
127.0.0.1:6379> LTRIM list 1 2 #通过下标截取指定的长度,这个时候list已经改变了,只剩下截取后的元素
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "2"
2) "1"
##############################################################
#rpoplpush list1 list 2,组合命令,移除list1中的最后一个元素,并将它移动到list2中
127.0.0.1:6379> LRANGE list 0 -1
1) "2"
2) "1"
127.0.0.1:6379> RPOPLPUSH list list2
"1"
127.0.0.1:6379> LRANGE list 0 -1
1) "2"
127.0.0.1:6379> LRANGE list2 0 -1
1) "1"
##############################################################
#lset 将列表中指定下标的值替换为另一个值,属于更新操作
127.0.0.1:6379> LRANGE list 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> lset list 0 four #将list中下标为0的值替换为four
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "four"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379>
##############################################################
#linsert 在列表中某个元素的后面或者前面插入数据
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "world"
(integer) 2
127.0.0.1:6379> LINSERT mylist before "world" "," #在world后面插入“,”
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) ","
3) "world"
127.0.0.1:6379> LINSERT mylist after "world" "wow" #在world前面插入“wow”
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) ","
3) "world"
4) "wow"
(三)Set
set是无序不重复集合。 进行交集、并集、差集的操作,比如交集,可以把两个人的粉丝列表整一个交集
#添加元素
127.0.0.1:6379> sadd myset "hello" #向set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
##############################################################
#smembers 查看元素
127.0.0.1:6379> SMEMBERS myset #查看指定set中的所有值
1) "hello"
2) "world"
127.0.0.1:6379> SISMEMBER myset hello #判断某一个值是否在set集合中
(integer) 1
127.0.0.1:6379> SISMEMBER myset sorry
(integer) 0
##############################################################
#获取set集合中元素的个数
127.0.0.1:6379> scard myset
(integer) 2
##############################################################
#srem 移除set结合中的元素
127.0.0.1:6379> srem myset world #移除set集合中的world元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
##############################################################
# srandmember 从set集合中随机抽选出一个元素
127.0.0.1:6379> SRANDMEMBER myset
"1"
127.0.0.1:6379> SRANDMEMBER myset
"5"
127.0.0.1:6379> SRANDMEMBER myset
"3"
127.0.0.1:6379> SRANDMEMBER myset
"3"
127.0.0.1:6379> SRANDMEMBER myset 2 #随机抽选出指定个数的元素
1) "1"
2) "hello"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "1"
2) "4"
127.0.0.1:6379>
##############################################################
# spop 随机删除集合中的元素
127.0.0.1:6379> spop myset
"4"
127.0.0.1:6379> spop myset
"3"
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "1"
3) "2"
4) "5"
##############################################################
# smove 将一个指定的元素移动到另一个集合中
127.0.0.1:6379> sadd myset2 sorry
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "1"
3) "2"
4) "5"
127.0.0.1:6379> smove myset myset2 "hello" #将myset中的元素移动到myset2中,移动元素为“hello”
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "2"
3) "5"
127.0.0.1:6379> SMEMBERS myset2
1) "hello"
##############################################################
# 微博或者b站中有共同关注(交集)
# 差集 SDIFF 交集 SINTER 并集 SUNION
127.0.0.1:6379> sadd myset1 a
(integer) 1
127.0.0.1:6379> sadd myset1 b
(integer) 1
127.0.0.1:6379> sadd myset1 c
(integer) 1
127.0.0.1:6379> sadd myset2 c
(integer) 1
127.0.0.1:6379> sadd myset2 d
(integer) 1
127.0.0.1:6379> sadd myset2 e
(integer) 1
127.0.0.1:6379> SDIFF myset1 myset2 #查看两个集合的差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER myset1 myset2 #查看交集
1) "c"
127.0.0.1:6379> SUNION myset1 myset2 #查看并集
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
A用户把所有关注的人都放在一个set集合中,把所有粉丝也都放在一个集合中,另一个B用户同理。他们两个取交集就实现了共同关注,共同粉丝,共同爱好等。、。
(四)Hash
结构化的数据,比如一个对象,也是键值对,形式是 key - map,也就是key - <key,value>,只不过值是map集合。
和String很像
# hset hget 添加和获取元素
127.0.0.1:6379> hset hkey vkey yang #设置一个key-value
(integer) 1
127.0.0.1:6379> hget hkey vkey
"yang"
127.0.0.1:6379> hmset hkey vkey hello vkey2 world #设置多个key-value
OK
127.0.0.1:6379> hmget hkey vkey vkey2 #获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall hkey #获取全部的数据,键和值
1) "vkey"
2) "hello"
3) "vkey2"
4) "world"
##############################################################
# hdel 删除指定的key字段,此时对应的value也没有了
127.0.0.1:6379> hdel hkey vkey2 #删除vkey2键
(integer) 1
127.0.0.1:6379> hgetall hkey
1) "vkey"
2) "hello"
##############################################################
# hlen 获取hash表中的字段数量,就是<key value>的数量
127.0.0.1:6379> hlen hkey
(integer) 1
##############################################################
# hexists 判断字段是否存在
127.0.0.1:6379> hlen hkey
(integer) 1
127.0.0.1:6379> HEXISTS hkey vkey
(integer) 1
127.0.0.1:6379> HEXISTS hkey vkey2
(integer) 0
##############################################################
# hkeys 只获取hash表中所有的key
# 只获取所有的value,就是<key value>中的value
127.0.0.1:6379> hkeys hkey
1) "vkey"
2) "vkey2"
127.0.0.1:6379> hvals hkey
1) "hello"
2) "world"
##############################################################
# incr decr 自增自检
127.0.0.1:6379> hset hkey vkey3 5
(integer) 1
127.0.0.1:6379> HINCRBY hkey vkey3 1 #自增1
(integer) 6
127.0.0.1:6379> HINCRBY hkey vkey3 -1 #自减1
(integer) 5
127.0.0.1:6379> hsetnx hkey vkey4 hello #如果键值不存在,则可以创建,也就是<vkye4 hello>
(integer) 1
127.0.0.1:6379> hsetnx hkey vkey4 world #如果键值存在,则不能,也就是已经有了<vkye4 hello>了,不能再创建<vkey4,world>了
(integer) 0
Hash可以用来存储经常变动的信息,尤其是用户信息(一个对象)之类的。
Hash更适合对象的存储,String更适合字符串的存储!
(五)Zset
有序集合,添加、获取、删除元素,根据分值范围或者成员来获取元素,或者计算一个键的排名,多用来做排序,如获取前几名的用户。或者用来存储班级成绩表和工资表。B站视频播放量排名也可以用zset来实现。
# 添加元素 zadd
127.0.0.1:6379> zadd myset 1 one #在1的位置添加one
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three #可以同时添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset myset 0 -1
(error) ERR syntax error
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
##############################################################
# zrangebyscore 从小到大排序
# zrevrange key 0 -1 从大到小排序
127.0.0.1:6379> zadd salary 1000 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 2000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 20000 yangguang
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf #从小到大排序(按工资数,也就是zset中的顺序)
1) "xiaoming"
2) "xiaohong"
3) "yangguang"
127.0.0.1:6379> ZREVRANGE salary 0 -1 #从大到小排序
1) "yangguang"
2) "xiaohong"
3) "xiaoming"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores #带着工资排序
1) "xiaoming"
2) "1000"
3) "xiaohong"
4) "2000"
5) "yangguang"
6) "20000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores #获取工资为2500以下的,并排序。
1) "xiaoming"
2) "1000"
3) "xiaohong"
4) "2000"
##############################################################
# zrem 移除元素
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaoming"
2) "xiaohong"
3) "yangguang"
127.0.0.1:6379> zrem salary xiaoming #移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaohong"
2) "yangguang"
127.0.0.1:6379> zcard salary #查看集合中元素个数
(integer) 2
##############################################################
#zount 统计某个区间内元素的个数
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "xiaohong"
2) "2000"
3) "xiaoli"
4) "2400"
5) "yangguang"
6) "20000"
127.0.0.1:6379> zcount salary 1000 3000 #查看工资在[1000,3000]内的元素有多少
(integer) 2
(六)三种特殊的数据类型
1. geospatial地理位置
可用于实现 定位,附近的人,打车距离,城市距离等。
# 添加地理位置 geoadd
#参数: key (经度 纬度 城市)
#有效经度[-180,180],有效纬度[-85.05,85.05]
#
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.52 shangzhen 108.96 34.26 xian
(integer) 2
##############################################################
# geopos 获取城市定位(经度和纬度)
127.0.0.1:6379> geopos china:city shanghai
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379> geopos china:city shanghai hangzhou
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
2) 1) "120.1600000262260437"
2) "30.2400003229490224"
##############################################################
# geodist 获取两人之间的距离。
127.0.0.1:6379> GEODIST china:city shanghai chongqing
"1447673.6920"
127.0.0.1:6379> GEODIST china:city shanghai chongqing km #以千米显示距离。如果写m的话就是按米显示
"1447.6737"
##############################################################
# georadius 以给定的经纬度为中心,找出指定半径内的元素
#附近的人,获取附近的人的地址,定位,通过半径来查询,比如我半径多少以内的人。
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km #找出经纬度为110 30地区附近半径500km以内的城市
1) "chongqing"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist #显示110 30地区与半径500km以内城市及相应距离
1) 1) "chongqing"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord #显示110 30地区与半径500km以内城市及其经纬度。
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
##############################################################
# georadiusbymember 找出位于指定范围内的元素,中心点是由给定的位置元素决定的。
#和georadius一样, 都可以找出位于指定范围内的元素,但是这个命令的中心点是由给定的位置元素决定的,而不是像georadius那样使用输入的经纬度来决定中心点
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km #找出北京附近1000km的城市
1) "beijing"
2) "xian"
##############################################################
# zrem 移除地理位置
127.0.0.1:6379> zrem china:city beijing #移除北京
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "shangzhen"
4) "hangzhou"
5) "shanghai"
2. Hyperloglog
用来做基数统计的,比如一个人访问一个网站多次,但还是算作一个人。
优点是:占用的内存固定的,2^64不同的元素,只需要占用12kb的内存。
有0.81%的错误率。如果允许容错,一定可以使用Hyperloglog,如果不允许容错,就是用set或者自己的数据类型。
3. Bitmaps
位存储, 可以用来统计比如群里活跃和不活跃的人,或者登录未登录的,打卡和未打卡的!两种状态的都可以用Bitmaps。
Bitmaps位图是一种数据结构,是用来操作二进制来进行记录,只有0和1两个状态。
五、事务
(一)什么是事务?
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子性操作:事务中的命令要么全部被执行,要么全部都不执行。
事务管理(ACID)概述
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)
事务前后数据的完整性必须保持一致。
隔离性(Isolation)
多个事务并发执行时,一个事务的执行不应影响其他事务的执行
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
(二)Redis事务
Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。redis不支持回滚*,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”。
------ 队列 set1 set2 set3 执行 ----
比如上面的,这组命令会被序列化,必须先执行set1之后才会执行后面的。
Redis单条命令是原子性的,但是Redis事务不保证原子性!而且,Redis事务没有隔离级别的概念!
虽然没有隔离级别,但是 Redis具有隔离性 :因为Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的
总结:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
Redis事务的三个阶段:
- 开启事务 MULTI
- 命令入队
- 执行事务 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)> 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
##############################################################
# discard 取消事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set key1 1
QUEUED
127.0.0.1:6379(TX)> set key2 2
QUEUED
127.0.0.1:6379(TX)> DISCARD #取消事务
OK
127.0.0.1:6379> get key2 #取消之后,事务队列中的命令不会被执行
(nil)
(三)Redis异常
和java一样,redis也有编译时异常和运行时异常。
编译时异常: 命令有错,事务中的所有命令都不会被执行!
类似于java中的代码错误。
127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379(TX)> set ke1 v1
QUEUED
127.0.0.1:6379(TX)> set ke2 v2
QUEUED
127.0.0.1:6379(TX)> setg ke3 v3 #命令错误,编译时异常,直接报错
(error) ERR unknown command `setg`, with args beginning with: `ke3`, `v3`,
127.0.0.1:6379(TX)> set ke4 v4
QUEUED
127.0.0.1:6379(TX)> EXEC #执行事务,报错。
(error) EXECABORT Transaction discarded because of previous errors.
#事务队列中的任何命令都没有执行。
127.0.0.1:6379> get ke4
(nil)
127.0.0.1:6379> get ke2
(nil)
运行时异常: 如果事务队列中存在语法性错误,那执行命令的时候,其他命令是可以正常执行的,只有错误命令会抛出异常。(类比java中的 1除以0,即 1/0)。
这在mysql中是绝对不会出现的,这也体现出了Redis事务没有原子性。
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> MULTI #开启事务
OK
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"
127.0.0.1:6379> get k2
"v2"
(四)Redis实现乐观锁
悲观锁:认为什么 都会出现问题,所以做什么都会加锁。效率很低。
乐观锁:认为什么时候都不会出问题,所以不会上锁。只是在更新数据的时候判断一下是否有人修改过这个数据(获取version,更新的时候比较version),没有修改过就提交,修改过就不提交。
WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
用watch来监控。
# 正常执行成功
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,给money加锁
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 10 #花了10块,钱的总数-10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10 #花费+10
QUEUED
127.0.0.1:6379(TX)> exec #期间数据没有发生变动,所以事务正常执行正常结束。
1) (integer) 90
2) (integer) 10
用多线程测试执行失败。
线程一:
127.0.0.1:6379> set money 50
OK
127.0.0.1:6379> set out 20
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 10 #花10,money-10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10 #花销+10
QUEUED
127.0.0.1:6379(TX)> exec #在执行事务之前,此时线程二修改了money的值,这个时候就会导致事务执行失败。
(nil)
线程二:
127.0.0.1:6379> clear
127.0.0.1:6379> get money
"50"
127.0.0.1:6379> set money 100 #修改money
OK
给money加锁时,会获取money的值,在执行事务的时候,会和原来的money比较。当另外一个线程修改了money的值之后,比较发现money已经被修改,这个时候就会发生错误。
那如何解决呢?先解锁,再加锁,从而获取最新的值。
127.0.0.1:6379> set money 50
OK
127.0.0.1:6379> set out 20
OK
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
127.0.0.1:6379(TX)> exec
(nil)
127.0.0.1:6379> UNWATCH #解锁
OK
127.0.0.1:6379> watch money #重新加锁,这个时候money就成了线程二改变后的100了。相当于获取修改后的最新的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
127.0.0.1:6379(TX)> exec #正常执行
1) (integer) 90
2) (integer) 30
内容总结
以上是互联网集市为您收集整理的Redis基础全部内容,希望文章能够帮你解决Redis基础所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。