Redis学习笔记(一) 数据类型&事务&异常&Jredis

NoSql概述

在数据越来越庞大,愈来愈复杂的情况下,比如视频,评论,图片,地理位置这类信息.传统的关系型数据库会显得特别吃力,所以需要使用NoSQL(Not Only Sql).。

Nosql四大分类

在这里插入图片描述

KV键值对

  • Redis(c编写单线程) 新浪
  • Redis+Tair 美团
  • Redis+Memecache 阿里,百度

文档型数据库

  • MongoDB(c++编写,基于分布式文件存储的数据库,主要用来处理大量文档)

== MongoDB介于关系型数据库和非关系型数据库之间,是非关系型数据库中功能最丰富,最像关系型数据库的非关系型数据库==

  • ConthDB

列存储数据库

  • HBase
  • 分布式文件系统

图关系数据库(存储关系)
如朋友圈社交网络,广告推荐

在这里插入图片描述

  • Neo4j
  • InfoGrid

Redis入门

中文网:https://www.redis.net.cn/

简介

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模式的数据备份。

作用:

  1. 内存存储,持久化
  2. 效率高,可用于高速缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器,计数器(浏览量)

特性:

  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事务

Windows安装

  1. 下载压缩包:
    在这里插入图片描述
  2. 解压
    在这里插入图片描述
  3. 开启服务
    在这里插入图片描述
  4. 连接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之前为什么是单线程

  1. Redis是基于内存操作,主要的性能瓶颈是内存或网络带宽,并不是cpu
  2. 使用单线程模式的Redis,其开发和维护更简单,因为单线程模式方便开发和调试。
  3. 即使使用单线程模型也能够并发地处理多客户端的请求,主要是因为Redis内部使用了基于epoll的多路复用。

单线程为什么这么快

多线程在单核cpu的情况下会涉及上下文切换,这是个耗时的操作,会消耗一定资源。多线程不一定就比单线程效率高。对于内存来说,如果没有上下文切换效率就是最高的。多次读写都是在一个cpu上,在内存系统中,这是个最佳的方案

  1. 基于内存操作:Redis的所有数据都存在内存中,因此所有的运算都是内存级别的,所以它的性能较高,速度较快,完全不逊于Memecache
  2. 数据结构简单:Redis的数据结构比较简单,是为Redis专门设计的,而这些简单的数据结构的查找和操作的时间复杂度都是O(1)。
  3. 多路复用和非阻塞IO:Redis使用IO多路复用功能来监听多个socket连接的客户端,这样就可以使用一个线程来处理多个情况,从而减少线程切换带来的开销,同时也避免了IO阻塞操作,从而大大提高了Redis的性能。
  4. 避免上下文切换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的开销,而且单线程不会导致死锁的问题发生。

官方使用的基准测试结果表明,单线程的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"
应用场景
  1. 计数器
  2. 统计多单位数量
  3. 粉丝数
  4. 对象缓存存储
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

  1. 导入依赖

<!--导入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>
  1. 编码测试
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();
        }


    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

月色夜雨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值