Redis学习笔记(一) 数据类型事务异常Jredis
Redis学习笔记(一)
- NoSql概述
- Nosql四大分类
- Redis入门
- 简介
- Windows安装
- 基础使用
- 问题
- Redis4.0之前为什么是单线程
- 单线程为什么这么快
- Redis4.0后的多线程
- 线程数的设置
- 性能
- 简单命令:Redis-KEY键(KEY)
- 数据类型
- 五大数据类型
- String
- 应用场景
- List
- 小结
- Set
- 应用场景
- Hash(哈希)
- 应用场景
- Zset(有序集合)
- 三种特殊数据类型
- GEO(地理位置)
- HyperLogLog (估计集合基数的数据结构)
- Bitmaps (位图)
- 事务
- 前言
- 命令
- 异常
- 监控(使用watch实现乐观锁)
- Jedis
NoSql概述
在数据越来越庞大,愈来愈复杂的情况下,比如视频,评论,图片,地理位置这类信息.传统的关系型数据库会显得特别吃力,所以需要使用NoSQL(Not Only Sql).。
Nosql四大分类
KV键值对
- Redis(c编写单线程) 新浪
- Redis+Tair 美团
- Redis+Memecache 阿里,百度
文档型数据库
- MongoDB(c++编写,基于分布式文件存储的数据库,主要用来处理大量文档)
== MongoDB介于关系型数据库和非关系型数据库之间,是非关系型数据库中功能最丰富,最像关系型数据库的非关系型数据库==
- ConthDB
列存储数据库
- HBase
- 分布式文件系统
图关系数据库(存储关系)
如朋友圈社交网络,广告推荐
- Neo4j
- InfoGrid
Redis入门
简介
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。–来源Redis中文网
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C
语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value
数据库,并提供多种语言的API。
是当下最热门的NoSql语言,被称为结构化数据库
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
作用:
- 内存存储,持久化
- 效率高,可用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器,计数器(浏览量)
特性:
- 多样的数据类型
- 持久化
- 集群
- 事务
Windows安装
- 下载压缩包:
- 解压
- 开启服务
- 连接redis
基础使用
redis默认支持16个数据库,默认为0号数据库,可在conf文件中查看:
127.0.0.1:6379> select 1 #切换1号数据库
OK
127.0.0.1:6379[1]> dbsize #查看数据库大小
(integer) 0
127.0.0.1:6379[1]> set name yeyu
OK
127.0.0.1:6379[1]> get name
"yeyu"
127.0.0.1:6379[1]> select 2 #切换2号数据库
OK
127.0.0.1:6379[2]> dbsize
(integer) 0
127.0.0.1:6379[2]> get name
(nil)
127.0.0.1:6379[2]> select 1
OK
127.0.0.1:6379[1]> get name
"yeyu"
问题
Redis4.0之前为什么是单线程
- Redis是基于内存操作,主要的性能瓶颈是内存或网络带宽,并不是cpu
- 使用单线程模式的Redis,其开发和维护更简单,因为单线程模式方便开发和调试。
- 即使使用单线程模型也能够并发地处理多客户端的请求,主要是因为Redis内部使用了基于epoll的多路复用。
单线程为什么这么快
多线程在单核cpu的情况下会涉及上下文切换,这是个耗时的操作,会消耗一定资源。多线程不一定就比单线程效率高。对于内存来说,如果没有上下文切换效率就是最高的。多次读写都是在一个cpu上,在内存系统中,这是个最佳的方案
- 基于内存操作:Redis的所有数据都存在内存中,因此所有的运算都是内存级别的,所以它的性能较高,速度较快,完全不逊于
Memecache
- 数据结构简单:Redis的数据结构比较简单,是为Redis专门设计的,而这些简单的数据结构的查找和操作的时间复杂度都是O(1)。
- 多路复用和非阻塞IO:Redis使用IO多路复用功能来监听多个socket连接的客户端,这样就可以使用一个线程来处理多个情况,从而减少线程切换带来的开销,同时也避免了IO阻塞操作,从而大大提高了Redis的性能。
- 避免上下文切换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的开销,而且单线程不会导致死锁的问题发生。
官方使用的基准测试结果表明,单线程的Redis可以达到10W/S的吞吐量。
Redis4.0后的多线程
Redis单线程的优点在于不但降低了Redis内部实现的负责性,也让所有操作都可以在无锁的情况下进行,并且不存在死锁和线程切换带来的性能以及时间上的消耗;
但是其缺点也很明显**,单线程机制导致Redis的QPS(Query Per Second,每秒查询数)很难得到有效的提高**
- Redis4.0中的多线程
此版本的多线程只能用于大数据量的异步删除,对于非删除操作的意义并不是很大。
- Redis6.0中的多线程
注意:默认为单线程,开启多线程需要在配置文件中进行配置
如果使用Redis多线程就可以分摊Redis同步读写IO的压力,以及充分利用多核CPU资源,并且可以有效的提升Redis的QPS。
在Redis中虽然使用了IO多路复用,并且是基于非阻塞的IO进行操作的,但是IO的读写本身是阻塞的。比如当socket中有数据时,Redis会先将数据从内核态空间拷贝到用户态空间,然后再进行相关操作,而这个拷贝过程是阻塞的,并且当数据量越大时拷贝所需要的的时间就越多,而这些操作都是基于单线程完成的。因此在Redis6.0中新增了多线程的功能来提高IO的读写性能,
它的主要实现思路是将主线程的IO读写任务拆分给一组独立的线程去执行,这样就可以使用多个socket的读写并行化了,但Redis的命令依旧是主线程串行执行的。
但是注意:Redis6.0是默认禁用多线程的,但可以通过配置文件redis.conf中的io-threads-do-reads 等于 true 来开启。但是还不够,除此之外我们还需要设置线程的数量才能正确地开启多线程的功能,同样是修改Redis的配置,例如设置 io-threads 4,表示开启4个线程。
线程数的设置
官方的建议是如果为4核CPU,那么设置线程数为2或3;如果为8核CPU,那么设置线程数为6.总之线程数一定要小于机器的CPU核数,线程数并不是越大越好。
性能
Redis的作者在2019年的RedisConf大会上提到,Redis6.0引入的多线程IO特性对性能的提升至少是一倍以上。
简单命令:Redis-KEY键(KEY)
查看redis常用命令
127.0.0.1:6379[1]> keys * #查看所有key
1) "name"
2) "id"
127.0.0.1:6379[1]> flushdb #清空当前数据库
OK
127.0.0.1:6379[1]> keys *
(empty list or set)
127.0.0.1:6379[1]>flushall #清空全部
127.0.0.1:6379> exists name #判断当前key是否存在,返回1即存在
(integer) 1
127.0.0.1:6379> move name 1 #将当前key移动到数据库1
(integer) 1
127.0.0.1:6379> expire name 10 #设置当前key过期时间
(integer) 1
127.0.0.1:6379> ttl name #查看当前key剩余时间
(integer) 7
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type name #查看当前key类型
string
数据类型
五大数据类型
String
127.0.0.1:6379> append name yuese #在字符串后追加,如果key不存在,则相当于set key
(integer) 9
127.0.0.1:6379> get name
"yeyuyuese"
127.0.0.1:6379> strlen name #获取字符串长度
(integer) 9
###########################################################
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views #浏览量,播放量使用redis自动增加
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views #自动减一
(integer) 1
127.0.0.1:6379> incrby views 10 #自动加10
(integer) 11
127.0.0.1:6379> decrby views 5 #自动减5
(integer) 6
###########################################################
127.0.0.1:6379> set key1 yueseyeyu
OK
127.0.0.1:6379> get key1
"yueseyeyu"
127.0.0.1:6379> getrange key1 0 3 #获取字符串索引0-3的值
"yues"
127.0.0.1:6379> getrange key1 0 -1 #获取全部字符串
"yueseyeyu"
127.0.0.1:6379> setrange key1 1 bb #替换指定位置字符
(integer) 9
127.0.0.1:6379> get key1
"ybbseyeyu"
#########################################################
# setex设置过期时间 setnx 不存在设置
127.0.0.1:6379> setex key2 30 "yeyu" #设置过期时间
OK
127.0.0.1:6379> ttl key2
(integer) 20
127.0.0.1:6379> get key2
"yeyu"
127.0.0.1:6379> setnx key3 "yuese" #如果key不存在则创建key
(integer) 1
127.0.0.1:6379> keys *
1) "key1"
2) "key3"
127.0.0.1:6379> setnx key3 "u" #如果key存在,失败
(integer) 0
#########################################################
#mset mget
#msetnx 原子性
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3 #同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #原子性操作
(integer) 0
127.0.0.1:6379> get k4
(nil)
########################################################
# 创建对象: user:{id}:{filed}
127.0.0.1:6379> mset user:1:name yeyu user:1:age 18
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "yeyu"
2) "18"
#######################################################
# getset :先get再set,返回旧值设置新值,如果没有旧值返回nil
127.0.0.1:6379> getset user:2:name yuese #
(nil)
127.0.0.1:6379> get user:2:name
"yuese"
127.0.0.1:6379> getset user:2:name yeyu
"yuese"
127.0.0.1:6379> get user:2:name
"yeyu"
应用场景
- 计数器
- 统计多单位数量
- 粉丝数
- 对象缓存存储
List
注意:所有list的命令都是l
开头的
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)
在redis中,我们可以把list玩成栈,队列,阻塞队列
127.0.0.1:6379> LPUSH LIST A #向list头部存值
(integer) 1
127.0.0.1:6379> LPUSH LIST B
(integer) 2
127.0.0.1:6379> LPUSH LIST C
(integer) 3
127.0.0.1:6379> LRANGE LIST 0 -1 #通过区间获取具体值
1) "C"
2) "B"
3) "A"
127.0.0.1:6379> LRANGE LIST 0 1
1) "C"
2) "B"
127.0.0.1:6379> RPUSH LIST LOVE #往list尾部添加值
(integer) 4
127.0.0.1:6379> LRANGE LIST 0 -1
1) "C"
2) "B"
3) "A"
4) "LOVE"
#######################################################
127.0.0.1:6379> LPOP LIST #移除头部第一个元素
"C"
127.0.0.1:6379> RPOP LIST #移除尾部第一个元素
"LOVE"
127.0.0.1:6379> LRANGE LIST 0 -1
1) "B"
2) "A"
127.0.0.1:6379>
#######################################################
127.0.0.1:6379> Lindex LIST 1 #获取该key索引为1的值
"A"
127.0.0.1:6379> LINDEX LIST 0
"B"
127.0.0.1:6379> LLEN LIST #获取列表长度
(integer) 2
#######################################################
127.0.0.1:6379> LPUSH LIST A #可以有重复值
(integer) 3
127.0.0.1:6379> LRANGE LIST 0 -1
1) "A"
2) "B"
3) "A"
127.0.0.1:6379> LREM LIST 2 A #移除两个值为A的数据
(integer) 2
127.0.0.1:6379> LRANGE LIST 0 -1
1) "B"
#######################################################
# LTRIM截取指定位置元素
127.0.0.1:6379> LPUSH LIST 1
(integer) 1
127.0.0.1:6379> LPUSH LIST 2
(integer) 2
127.0.0.1:6379> LPUSH LIST 3
(integer) 3
127.0.0.1:6379> LPUSH LIST 4
(integer) 4
127.0.0.1:6379> LTRIM LIST 1 3 #通过下标截断指定位置元素
OK
127.0.0.1:6379> LRANGE LIST 0 -1
1) "3"
2) "2"
3) "1"
#######################################################
#组合命令RPopLpush
127.0.0.1:6379> lpush list1 1
(integer) 1
127.0.0.1:6379> lpush list1 2
(integer) 2
127.0.0.1:6379> lpush list1 13
(integer) 3
127.0.0.1:6379> lrange list1 0 -1
1) "13"
2) "2"
3) "1"
127.0.0.1:6379> rpoplpush list1 list2 #移除列表最后一个元素移至新列表
"1"
127.0.0.1:6379> lrange list1 0 -1
1) "13"
2) "2"
127.0.0.1:6379> lrange list2 0 -1
1) "1"
#######################################################
# lset更新
127.0.0.1:6379> lset list5 1 1 #key值不存在则报错
(error) ERR no such key
127.0.0.1:6379> lset list2 0 love #更新列表索引为0的值
OK
127.0.0.1:6379> lrange list2 0 0
1) "love"
127.0.0.1:6379> exists list2 #判断key值是否存在
(integer) 1
#######################################################
# LInsert 插入
127.0.0.1:6379> LInsert list2 before love I #在指定元素前插入
(integer) 2
127.0.0.1:6379> LInsert list2 after love you #在指定元素后插入
(integer) 3
127.0.0.1:6379> lrange list2 0 -1
1) "I"
2) "love"
3) "you"
小结
- 实际就是一个链表,有
Node
节点,可以通过before或者after来在node前后插入 - 若是移除了所有值,空链表代表不存在
- 在头部和尾部进行插入或更新操作效率最高,中间位置的元素效率就比较低
可以做消息排队
可以做消息队列,如:
LPush RPop
: 消息队列LPush LPop
: 栈
Set
set值无序且唯一
127.0.0.1:6379> sadd s I #添加set元素
(integer) 1
127.0.0.1:6379> sadd s love
(integer) 1
127.0.0.1:6379> sadd s love #不可重复
(integer) 0
127.0.0.1:6379> sadd s you
(integer) 1
127.0.0.1:6379> SMEMBERS s #查看set
1) "love"
2) "I"
3) "you"
127.0.0.1:6379> SISMember s me #判断值是否存在
(integer) 0
127.0.0.1:6379> SISMember s you
(integer) 1
127.0.0.1:6379> scard s #查看set元素个数
(integer) 3
#######################################################
# srem 移除
127.0.0.1:6379> srem s I #移除指定元素
(integer) 1
127.0.0.1:6379> SMEMBERS s
1) "love"
2) "you"
#######################################################
# SRANDMEMBER 随机取值
127.0.0.1:6379> SRANDMEMBER s #随机取值
"you"
127.0.0.1:6379> SRANDMEMBER s
"I"
127.0.0.1:6379> srandmember s 2 #随机取出指定个数的值
1) "I"
2) "you"
127.0.0.1:6379> srandmember s 2
1) "love"
2) "I"
#######################################################
# spop 随机移除元素
127.0.0.1:6379> smembers s
1) "love"
2) "I"
3) "you"
127.0.0.1:6379> spop s #随机移除元素
"love"
127.0.0.1:6379> spop s
"I"
127.0.0.1:6379> smembers s
1) "you"
#######################################################
# smove 移动元素到其它set
127.0.0.1:6379> smove s s1 love #没有元素显示0
(integer) 0
127.0.0.1:6379> smove s s1 you #移动指定元素到另一set
(integer) 1
127.0.0.1:6379> smembers s1
1) "1"
2) "you"
#######################################################
#交集 并集 差集
127.0.0.1:6379> sdiff s1 s2 #差集
1) "b"
2) "a"
127.0.0.1:6379> sinter s1 s2 #交集
1) "c"
127.0.0.1:6379> sunion s1 s2 #并集
1) "a"
2) "c"
3) "d"
4) "e"
5) "b"
应用场景
- 共同喜好
- 微博共同关注
Hash(哈希)
本质还是一个key-value,其中value又是一个map集合
命令和string基本一样
127.0.0.1:6379> hset h f1 a
(integer) 1
127.0.0.1:6379> HGET h f1
"a"
127.0.0.1:6379> HMSET h f1 I f2 love f3 you #创建多个值
OK
127.0.0.1:6379> hmget h f1 f2 #获取指定key的value
1) "I"
2) "love"
127.0.0.1:6379> hgetall h #
1) "f1"
2) "I"
3) "f2"
4) "love"
5) "f3"
6) "you"
#######################################################
127.0.0.1:6379> hlen h #获取指定key的字段个数
(integer) 3
127.0.0.1:6379> hkeys h #获取所有key
1) "f1"
2) "f2"
3) "f3"
127.0.0.1:6379> HVALS h #获取所有value
1) "I"
2) "love"
3) "you"
127.0.0.1:6379> hdel h1 s #删除hash指定key,对应value也消失
(integer) 1
#######################################################
127.0.0.1:6379> hset h f1 1
(integer) 1
127.0.0.1:6379> hincrby h f1 5 #设置指定增量
(integer) 6
127.0.0.1:6379> hgetall h
1) "f1"
2) "6"
127.0.0.1:6379> hsetnx h f2 love #如果不存在则设置
(integer) 1
127.0.0.1:6379> hsetnx h f2 you
(integer) 0
#可以存对象
127.0.0.1:6379> hset user:1 name you
(integer) 1
127.0.0.1:6379> hget user:1 name
"you"
应用场景
- 经常变动的数据(不想设置大量key),hash适合元对象的存储,string适合字符串的存储
- 用户信息的储存
Zset(有序集合)
Zset和set区别在于前者在set的基础上给每个value
赋予一个score
,代表这个value
的排序权重。它的内部实现用的是"跳跃列表"的数据结构。
127.0.0.1:6379> zadd myset 1 A
(integer) 1
127.0.0.1:6379> zadd myset 2 B 3 C #添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "A"
2) "B"
3) "C"
127.0.0.1:6379> zadd myset 1 D #重复添加已有序列自动排在下一位
(integer) 1
127.0.0.1:6379> ZRANGE myset 0 -1
1) "A"
2) "D"
3) "B"
4) "C"
#######################################################
#可用作工资排序
127.0.0.1:6379> zadd s 2000 a
(integer) 1
127.0.0.1:6379> zadd s 5999 b
(integer) 1
127.0.0.1:6379> zadd s 899 c
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE s -inf +inf #工资排序
1) "c"
2) "a"
3) "b"
127.0.0.1:6379> ZREVRANGEBYSCORE -inf +inf withscores #顺序排序
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> ZRANGEBYSCORE s 0 -1 #排序并显示工资
1) "c"
2) "899"
3) "a"
4) "2000"
5) "b"
6) "5999"
127.0.0.1:6379> ZRANGEBYSCORE s -inf 2500 withscores #指定范围排序并显示工资
1) "c"
2) "899"
3) "a"
4) "2000"
#######################################################
# ZRANGE 移除 ZCARD 获取有序集合中的个数
127.0.0.1:6379> ZREM s a #移除集合中指定元素
(integer) 1
127.0.0.1:6379> ZRANGE s 0 -1
1) "c"
2) "b"
127.0.0.1:6379> ZCARD s #获取有序集合中的个数
(integer) 3
#######################################################
#ZCOUNT 获取指定区间元素个数
127.0.0.1:6379> zadd s 1 I 2 love 3 you
(integer) 3
127.0.0.1:6379> ZRANGE s 0 -1
1) "I"
2) "love"
3) "you"
127.0.0.1:6379> ZCOUNT s 1 3
(integer) 3
三种特殊数据类型
GEO(地理位置)
有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。
#geoadd 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中
127.0.0.1:6379> geoadd china:city 116.41 39.91 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.43 34.50 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 120.20 30.26 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.95 34.26 xian
(integer) 1
#######################################################
#geopos 获取指定城市的精度与纬度
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.4099982380867"2) "39.909999566644508"
127.0.0.1:6379> geopos china:city shanghai xian
1) 1) "121.42999917268753"2) "34.499999717161309"
2) 1) "108.95000249147415"2) "34.2599996441893"
geodist
命令中指定单位的参数 unit 必须是以下单位的其中一个:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
#######################################################
#geodist 获取指定两个城市的直线距离
127.0.0.1:6379> geodist china:city xian hangzhou
"1146984.2264"
127.0.0.1:6379> geodist china:city xian hangzhou km
"1146.9842"
以给定经纬度为中心,返回某一半径内的所有元素
可以实现附近的人功能
#######################################################
#GEORADIUS 获取附近的人(城市)
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km #获取附近1000km的城市
1) "xian"
2) "hangzhou"127.0.0.1:6379> GEORADIUS china:city 110 30 1500 km
1) "xian"
2) "hangzhou"
3) "shanghai"
4) "beijing"127.0.0.1:6379> GEORADIUS china:city 110 30 1500 km withdist #显示直线距离
1) 1) "xian"2) "484.0254"
2) 1) "hangzhou"2) "981.3209"
3) 1) "shanghai"2) "1184.9674"
4) 1) "beijing"2) "1246.6750"127.0.0.1:6379> GEORADIUS china:city 120 30 1500 km withcoord #显示经纬度
1) 1) "xian"2) 1) "108.95000249147415"2) "34.2599996441893"
2) 1) "hangzhou"2) 1) "120.20000249147415"2) "30.259999272896209"
3) 1) "shanghai"2) 1) "121.42999917268753"2) "34.499999717161309"
4) 1) "beijing"2) 1) "116.4099982380867"2) "39.909999566644508"127.0.0.1:6379> GEORADIUS china:city 120 30 1500 km withcoord count 2 #限制返回两个值
1) 1) "hangzhou"2) 1) "120.20000249147415"2) "30.259999272896209"
2) 1) "shanghai"2) 1) "121.42999917268753"2) "34.499999717161309"
GEORADIUSBYMEMBER
: 以给定昵称或者城市名称为中心,返回某一半径内的所有元素
#杭州一千公里内的城市
127.0.0.1:6379> GEORADIUSBYMEMBER china:city hangzhou 1000 km
1) "hangzhou"
2) "shanghai"
HyperLogLog (估计集合基数的数据结构)
Bitmaps (位图)
其它命令可查看Redis命令
事务
前言
在Mysql这种关系型数据库中,事务遵循
ACID
- 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
- 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务的实质就是一组命令的集合,一个事务中的所有命令都会被序列化,在执行事务的过程中,命令按照先后顺序执行!
所有的命令在事务中不会直接执行,而是等到Exec
执行命令才开始执行
而在Redis中,事务没有没有隔离级别的概念,单条命令可保证原子性,但事务不保证原子性!
命令
正常执行事务:
- 开启事务
MULTI
- 命令入队
...
- 执行事务
EXEC
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "v1"
4) OK
- 放弃事务
discard
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a
QUEUED
127.0.0.1:6379> set b b
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get a
(nil)
异常
编译型异常(代码问题或者命令有问题,所有的命令都不会被执行)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a
QUEUED
127.0.0.1:6379> set b b
QUEUED
127.0.0.1:6379> getset b
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set c c
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get a
(nil)
运行时异常(运行时异常,除了语法错误不会被执行且抛出异常后,其他的正确命令可以正常执行)
127.0.0.1:6379> set k "v"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k #会执行失败
QUEUED
127.0.0.1:6379> set a a
QUEUED
127.0.0.1:6379> set b b
QUEUED
127.0.0.1:6379> get b
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "b"
监控(使用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对象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec #事务正常结束,数据期间未发生变动,执行成功
1) (integer) 80
2) (integer) 20
- 多线程执行
在执行事务前,watch会再次获取监控对象的值,通过和之前的监控值对比查看其是否经其它线程改变,若值不同,则事务执行失败
127.0.0.1:6379> watch money #监控money对象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec #执行之前,另一线程更改money数据,事务执行失败
(nil)
127.0.0.1:6379> unwatch #事务失败,解锁
127.0.0.1:6379> watch money #再次money对象
127.0.0.1:6379> set money 1000
OK
Jedis
通过Jedis
操作redis
- 导入依赖
<!--导入Jredis包--><!-- https://mvnrepository.com/artifact/redis.clients/jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.2.3</version></dependency><!--导入fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.75</version></dependency>
- 编码测试
public class Test {public static void main(String[] args) {//1.新建jedis对象Jedis jedis = new Jedis("127.0.0.1",6379);//2.操作命令System.out.println(jedis.ping());//关闭连接jedis.close();}
}
所有的命令都通过jedis对象,具体命令在Redis学习笔记(一)
使用Jedis
操作事务:
public class Test {public static void main(String[] args) {//1.新建jedis对象Jedis jedis = new Jedis("127.0.0.1",6379);JSONObject jsonObject = new JSONObject();jsonObject.put("Name","yeyu");jsonObject.put("age",18);//开启事务Transaction multi = jedis.multi();String result = jsonObject.toString();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();}}
public class Test {public static void main(String[] args) {//1.新建jedis对象Jedis jedis = new Jedis("127.0.0.1",6379);System.out.println(jedis.ping());jedis.flushAll();JSONObject jsonObject = new JSONObject();jsonObject.put("Name","yeyu");jsonObject.put("age",18);//开启事务Transaction multi = jedis.multi();String result = jsonObject.toString();try {multi.set("user1",result);multi.set("user2",result);int i = 1/0;//代码抛出异常,执行失败multi.exec();//执行事务}catch (Exception e){multi.discard();//放弃事务e.printStackTrace();}finally {System.out.println(jedis.get("user1"));System.out.println(jedis.get("user2"));//关闭连接jedis.close();}}
}
Redis学习笔记(一) 数据类型事务异常Jredis相关推荐
- Redis学习笔记1-Redis数据类型
Redis数据类型 Redis支持5种数据类型,它们描述如下: Strings - 字符串 字符串是 Redis 最基本的数据类型.Redis 字符串是二进制安全的,也就是说,一个 Redis 字符串 ...
- redis完整笔记总结-数据类型-事务与锁-集群-分布式锁-常见问题(缓存穿透、击穿、雪崩)
1. 数据类型 五大基本类型 String hash -> 类似map list set -> zset -> 基于set的有序集合 新增 bitmaps:其实就是string,主要 ...
- Redis学习笔记 - 数据类型与API(1)Key
Redis学习笔记 - 数据类型与API(1)Key Key相关命令 1. 常用命令 命令 含义 时间复杂度 keys 查找所有符合给定模式 pattern 的 key O(N), N 为数据库中 k ...
- StackExchange.Redis学习笔记(二) Redis查询 五种数据类型的应用
StackExchange.Redis学习笔记(二) Redis查询 五种数据类型的应用 原文: StackExchange.Redis学习笔记(二) Redis查询 五种数据类型的应用 Connec ...
- Redis学习笔记---Redis的事务
Redis学习笔记-Redis的事务 1. Redis事务(弱事务)和Mysql事务对比 Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部执行,要么全部不执行. Redi ...
- Redis学习笔记(B站狂神说)(自己总结方便复习)
Redis学习笔记B站狂神说 redis: 非关系型数据库 一.NoSQL概述 1.为什么要用Nosql 1.单机Mysql的年代 思考一下,这种情况下:整个网站的瓶颈是什么? 1.数据量如果太大,一 ...
- Redis(学习笔记)
Redis学习笔记 1.NoSQL数据库 1.1解决的问题 1.1.1解决CPU及内存压力 1.1.2解决IO压力 1.2NoSQL数据库概述 1.2.1什么是NoSQL数据库 1.2.2适用与不适用 ...
- Redis学习笔记②实战篇_黑马点评项目
若文章内容或图片失效,请留言反馈.部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 资料链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA( ...
- Redis学习笔记——SpringDataRedis的使用
与Spring集成 我需要哪些jar包? <dependency><groupId>org.springframework.data</groupId><ar ...
最新文章
- 易语言 基础知识一及认识句柄 局部变量
- opencv读取手机摄像头
- Swift 本地推送通知UILocalNotification
- NTU 课程笔记 CV6422 假设检验
- 软工网络15团队作业4——Alpha阶段敏捷冲刺之Scrum 冲刺博客(Day5)
- 你真的了解Ioc与AOP 吗?(2)
- Qt的安装和使用中的常见问题(详细版)
- 查看地区的ip段_「教程」CloudFlare 自选 IP优化网站速度
- linux安装curl扩展
- homestead修改php版本
- 在Java中通过线程池实现异步执行
- idea git push 码云: Remote: [31mYou do not have permission to push to the repository via HTTPS
- 学习英文必记的九种前缀与三种后缀
- java服务器限速下载_Java文件下载限速
- windows如何获取端口号
- 如何优雅的完成一场说来就来的APP自建
- 开源软件及国内发展趋势
- matlab中的下标都是从1开始
- 武大计算机学院2017年博士分数线,武汉大学高等研究院2017年博士研究生综合考核录取工作通知...
- 计算机硬件故障的维修方法,计算机硬件故障和维修方法分析.doc
热门文章
- 热血江湖服务器维护时间,热血江湖2016年10月8日停机更新维护公告 喜庆大烟花套餐国庆上架...
- Hive基础知识(三)--分桶表
- 低水平黑客也可远程攻击工业电机并造成物理破坏
- 控制语句-条件和分支
- 基于Python的指数基金量化投资-为什么量化指数基金投资
- 图解数据分析(5) | 核心步骤2 - 数据清洗与预处理(数据科学家入门·完结)
- JSHint 与 JSLint 的区别
- 帝国CMS7.5仿完美游戏台游戏视频网站模板
- 打开.py文件的方法
- 不登录QQ,恢复QQ聊天中的语音到电脑上,并导出为MP3