Redis学习笔记(1)——感谢尚硅谷官方文档
- 1. NoSQL
- 1.1 NoSQL数据库概述
- 1.2 各种NoSQL数据库
- 2. Redis数据库安装
- 2.1 安装条件
- 2.2 Widows下如何安装Redis?
- 2.3 Linux下如何安装Redis?
- 3. Redis介绍
- 3.1 Redis 简介
- 3.2 Redis 优势
- 3.3 Redis与其他key-value存储有什么不同?
- 4. 数据类型
- 4.1 概述
- 4.2 Redis的基本了解
- 4.3 Redis的五大数据类型&&三大特殊数据类型
- 4.3.1 String(字符串)
- 4.3.2 List(列表)
- 4.3.3 Set(集合)元素唯一不重复
- 4.3.4 Hash(哈希)
- 4.3.5 zSet(有序集合)
- 4.3.6 Geospatial: 地理位置
- 4.3.7 Hyperloglog: 基数
- 4.3.8 Bitmap: 位存储
- 5. Redis中的事务和乐观锁
- 5.1 Redis如何实现事务?
- 5.2 Redis如何实现乐观锁?
1. NoSQL
1.1 NoSQL数据库概述
解决各种问题用到的技术
-
解决功能性的问题:ava、Jsp、RDBMS、Tomcat、HTML、Linux、JDBC、SVNe
-
解决扩展性的问题:Struts、Spring、SpringMVC、Hibernate、Mybatise
-
解决性能的问题:NoSQL、Java线程、Hadoop、Nginx、MQ、ElasticSearch
特点
-
不遵循SQL标准
-
不支持ACID
-
远超于SQL性能
使用场景
-
对数据的高并发
-
海量数据的读写
-
读数据访问的高扩展性
1.2 各种NoSQL数据库
关系型数据库产品很多,如 MySQL、Oracle、Microsoft SQL Sever 等,但它们的基本模型都是关系型数据模型。
非关系型数据库又称为:NoSQL ,没有统一的模型,而且是非关系型的。
常见的 NoSQL 数据库包括键值数据库、列族数据库、文档数据库和图形数据库,其具体分类和特点如表所示:
NoSQL 数据库并没有一个统一的架构,两种不同的 NoSQL 数据库之间的差异程度,远远超过两种关系型数据库之间的不同。
可以说,NoSQL 数据库各有所长,一个优秀的 NoSQL 数据库必然特别适用于某些场合或者某些应用,在这些场合中会远远胜过关系型数据库和其他的 NoSQL 数据库。
分类 | 相关产品 | 应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值数据库 | Redis、Memcached、Riak | 内容缓存,如会话、配置文件、参数等; | 频繁读写、拥有简单数据模型的应用 | <key,value> 键值对,通过散列表来实现 | 扩展性好,灵活性好,大量操作时性能高 |
列族数据库 | Bigtable、HBase、Cassandra | 分布式数据存储与管理 | 以列族式存储,将同一列数据存在一起 | 可扩展性强,查找速度快,复杂性低 | 功能局限,不支持事务的强一致性 |
文档数据库 | MongoDB、ES、CouchDB | Web 应用,存储面向文档或类似半结构化的数据 | <key,value> | value 是 JSON 结构的文档 | 数据结构灵活,可以根据 value 构建索引 |
图形数据库 | Neo4j、InfoGrid | 社交网络、推荐系统,专注构建关系图谱 | 图结构 | 支持复杂的图形算法 | 复杂性高,只能支持一定的数据规模 |
特点
根据上面图标中列出的常见的 NoSQL 数据库分类,我们简单介绍下各类数据库的特点。
键值数据库
这一类数据库主要会使用到一个散列表,这个表中有一个特定的键和一个指针指向特定的数据。
键值模型对于 IT 系统来说,其优势在于简单、易部署。键值数据库可以按照键对数据进行定位,还可以通过对键进行排序和分区,以实现更快速的数据定位。
列族数据库
列族数据库通常用来应对分布式存储的海量数据。键仍然存在,但是它们的特点是指向了多个列,如图所。
此列族数据库表中由两行组成,每一行都有关键字 Row Key,每一行由多个列族组成,即 Column-Family-1 和 Column-Family-2,而每个列族由多个列组成。
文档数据库
文档数据库的灵感来自 Lotus Notes 办公软件,它与键值数据库类似。该类型的数据模型是版本化的文档,文档以特定的格式存储,如 JSON。
文档数据库可以看作键值数据库的升级版,允许之间嵌套键值,如图所示。
图形数据库
图形数据库来源于图论中的拓扑学,以节点、边及节点之间的关系来存储复杂网络中的数据,如图所示。
这种拓扑结构类似 E-R 图,但在图形模式中,关系和节点本身就是数据,而在 E-R 图中,关系描述的是一种结构。
2. Redis数据库安装
2.1 安装条件
Redis官网: www.Redis.io
安装环境: CentOS 7 (建议安装在Liunx)、 GCC编译环境
2.2 Widows下如何安装Redis?
如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210114212314327.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzgyOTQ0Mw==,size_16,color_FFFFFF,t_70)
下载成功后将其移动到我们想要安装的目录下并且解压:
如下图:
进入后有以下内容文件:
(4) .先点击redis-server.exe启动Redis服务,显示如下:
(5) .再点击redis-cli.exel连接Redis,如下图所示:
(6) .至此,在Windows下的安装就结束了。不过Redis官方文档不建议我们在Widows环境下搭建Redis服务。
最好是Linux环境下搭建并使用Redis服务!
2.3 Linux下如何安装Redis?
前提条件:我这边安装的Linux系统是CentOS7.7版本,并且是云服务器,使用的远程工具是Xshell 6。
(1) .首先下载安装包,下载地址:点击跳转.
如下图:
(2) .一般我们都是将这些服务安装在指定文件夹下,在/opt目录下新建soft文件夹,再在/soft文件下建我们需要安装的服务名称文件夹,redis:如图:
(3) .将包通过rz命令上传到远程服务器上的redis文件夹下:如下图:
(4) .等待上传成功,速度一般根据你的服务器带宽而定:然后解压redis安装包:
命令:tar -zxvf 需要解压的包名
(5) .解压后,进入文件夹,正常情况下解压后有19个文件,如若不对,请删除,重新解压:
(6) .接下来我们安装基本环境gcc,安装命令:yum install gcc-c++
等待其安装完成,输入gcc -v
查看当前gcc的版本:
(7) .这里是重点:我们安装的Redis版本是6.0.10.对gcc环境的版本有一定的要求,所以我们要升级gcc的版本,输入以下命令:
#第一步
sudo yum install centos-release-scl
#第二步
sudo yum install devtoolset-7-gcc*
#第三步
scl enable devtoolset-7 bash
中途遇到输入时不用管太多,直接yes即可。如下图所示:
全部执行完毕后,再次查看版本变为:7.3.1即代表环境升级成功。
(8) .然后在我们解压的redis目录下执行命令:make
,,出现以下显示即成功make下载!
再执行命令:make install
,安装文件!
(9) .大家应该会发现我们并没有指定安装的路径,但是Redis有一个默认的路径,得记住:
/usr/local/bin
进入该文件夹内,可以发现,有两个文件和我们再Windows下的文件一样,分别时启动和连接。
(10) .我们将配置文件拷贝过来,以后就用这个配置文件来启动Redis服务。
命令: cp /opt/soft/redis/redis-6.0.10/redis.conf mtconfig/
(11) .redis默认不是后台启动的,修改配置文件!
#打开配置文件
vim redis.conf
#保存
wq!
(12) 接下来就是.启动和连接Redis服务!
#启动redis服务
redis-server mtconfig/redis.conf
#连接redis服务
redis-cli -p 6379
(13).测试redis服务:
先测试set和get基本操作:
再查看redis服务的进程:
3. Redis介绍
3.1 Redis 简介
Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
3.2 Redis 优势
- 性能极高 —— Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 丰富的数据类型 —— Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子—— Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 —— Redis还支持 publish/subscribe, 通知, key 过期等等特性。
3.3 Redis与其他key-value存储有什么不同?
-
Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
-
Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
4. 数据类型
4.1 概述
- Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务 !
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
免费和开源!是当下最热门的 NoSQL 技术之一!也被人们称之为结构化数据库! - Redis能干嘛?
1、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof) 2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量!)
6、…
4.2 Redis的基本了解
需要安装教程请参考本人的另一篇博客文章:Widows和Linux下如何安装Redis.
-
首先我们可以看下官方文档是如何介绍Redis的:
①、英文文档 点击跳转.
②、中文文档 点击跳转.
-
Redis-Key
简单介绍一下Redis中队Key的操作命令。希望大家可以跟着注释敲一遍,简单记一下,都是最常用的命令!
127.0.0.1:6379> ping #查看当前连接是否正常,正常返回PONG
PONG
127.0.0.1:6379> clear #清楚当前控制台(为了更好的看到下面输入的命令)
127.0.0.1:6379> keys * #查看当前库里所有的key
1) "db"
127.0.0.1:6379> FLUSHALL #清空所有库的内容
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set name dingdada #添加一个key为‘name’ value为‘dingdada’的数据
OK
127.0.0.1:6379> get name #查询key为‘name’的value值
"dingdada"
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set name1 dingdada2
OK
127.0.0.1:6379> get name1
"dingdada2"
127.0.0.1:6379> keys * #查看当前库里所有的key
1) "name1"
2) "name"
127.0.0.1:6379> EXISTS name #判断当前key是否存在
(integer) 1
127.0.0.1:6379> move name 1 #移除当前库1的key为‘name‘的数据
(integer) 1
127.0.0.1:6379> keys *
1) "name1"
127.0.0.1:6379> FLUSHALL #再次清空所有库的内容
OK
## 多加几条数据 下面测试设置key的过期时间
127.0.0.1:6379> set name dingdada
OK
127.0.0.1:6379> set name1 dingdada1
OK
127.0.0.1:6379> set name2 dingdada2
OK
127.0.0.1:6379> EXPIRE name 15 #设置key为’name‘的数据过期时间为15秒 单位seconds
(integer) 1
127.0.0.1:6379> ttl name #查看当前key为’name‘的剩余生命周期时间
(integer) 13
127.0.0.1:6379> ttl name
(integer) 12
127.0.0.1:6379> ttl name
(integer) 11
127.0.0.1:6379> ttl name
(integer) 8
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> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) 0
127.0.0.1:6379> ttl name #如若返回-2,证明key已过期
(integer) -2
127.0.0.1:6379> get name #再次查询即为空
(nil)
127.0.0.1:6379> type name1
string
127.0.0.1:6379>
如若遇到不会的命令!记得查看Redis中文官网,上面有官方文档!链接上面有,可以点击跳转~
4.3 Redis的五大数据类型&&三大特殊数据类型
4.3.1 String(字符串)
①添加、查询、追加、获取长度,判断是否存在的操作
127.0.0.1:6379> set name dingdada #插入一个key为‘name’值为‘dingdada’的数据
OK
127.0.0.1:6379> get name #获取key为‘name’的数据
"dingdada"
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> keys * #查看当前库的所有数据
1) "name"
127.0.0.1:6379> EXISTS name #判断key为‘name’的数据存在不存在,存在返回1
(integer) 1
127.0.0.1:6379> EXISTS name1 #不存在返回0
(integer) 0
127.0.0.1:6379> APPEND name1 dingdada1 #追加到key为‘name’的数据后拼接值为‘dingdada1’,如果key存在类似于java中字符串‘+’,不存在则新增一个,类似于Redis中的set name1 dingdada1 ,并且返回该数据的总长度
(integer) 9
127.0.0.1:6379> get name1
"dingdada1"
127.0.0.1:6379> STRLEN name1 #查看key为‘name1’的字符串长度
(integer) 9
127.0.0.1:6379> APPEND name1 ,dingdada2 #追加,key存在的话,拼接‘+’,返回总长度
(integer) 19
127.0.0.1:6379> STRLEN name1
(integer) 19
127.0.0.1:6379> get name1
"dingdada1,dingdada2"
127.0.0.1:6379> set key1 "hello world!" #注意点:插入的数据中如果有空格的数据,请用“”双引号,否则会报错!
OK
127.0.0.1:6379> set key1 hello world! #报错,因为在Redis中空格就是分隔符,相当于该参数已结束
(error) ERR syntax error
127.0.0.1:6379> set key1 hello,world! #逗号是可以的
OK
②自增、自减操作
127.0.0.1:6379> set num 0 #插入一个初始值为0的数据
OK
127.0.0.1:6379> get num
"0"
127.0.0.1:6379> incr num #指定key为‘num’的数据自增1,返回结果 相当于java中 i++
(integer) 1
127.0.0.1:6379> get num #一般用来做文章浏览量、点赞数、收藏数等功能
"1"
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incr num
(integer) 3
127.0.0.1:6379> get num
"3"
127.0.0.1:6379> decr num #指定key为‘num’的数据自减1,返回结果 相当于java中 i--
(integer) 2
127.0.0.1:6379> decr num
(integer) 1
127.0.0.1:6379> decr num
(integer) 0
127.0.0.1:6379> decr num #可以一直减为负数~
(integer) -1
127.0.0.1:6379> decr num #一般用来做文章取消点赞、取消收藏等功能
(integer) -2
127.0.0.1:6379> decr num
(integer) -3
127.0.0.1:6379> INCRBY num 10 #后面跟上by 指定key为‘num’的数据自增‘参数(10)’,返回结果
(integer) 7
127.0.0.1:6379> INCRBY num 10
(integer) 17
127.0.0.1:6379> DECRBY num 3 #后面跟上by 指定key为‘num’的数据自减‘参数(3)’,返回结果
(integer) 14
127.0.0.1:6379> DECRBY num 3
(integer) 11
③截取、替换字符串操作
#截取
127.0.0.1:6379> set key1 "hello world!"
OK
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> GETRANGE key1 0 4 #截取字符串,相当于java中的subString,下标从0开始,不会改变原有数据
"hello"
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> GETRANGE key1 0 -1 #0至-1相当于 get key1,效果一致,获取整条数据
"hello world!"
#替换
127.0.0.1:6379> set key2 "hello,,,world!"
OK
127.0.0.1:6379> get key2
"hello,,,world!"
127.0.0.1:6379> SETRANGE key2 5 888 #此语句跟java中replace有点类似,下标也是从0开始,但是有区别:java中是指定替换字符,Redis中是从指定位置开始替换,替换的数据根据你所需替换的长度一致,返回值是替换后的长度
(integer) 14
127.0.0.1:6379> get key2
"hello888world!"
127.0.0.1:6379> SETRANGE key2 5 67 #该处只替换了两位
(integer) 14
127.0.0.1:6379> get key2
"hello678world!"
④设置过期时间、不存在设置操作
#设置过期时间,跟Expire的区别是前者设置已存在的key的过期时间,而setex是在创建的时候设置过期时间
127.0.0.1:6379> setex name1 15 dingdada #新建一个key为‘name1’,值为‘dingdada’,过期时间为15秒的字符串数据
OK
127.0.0.1:6379> ttl name1 #查看key为‘name1’的key的过期时间
(integer) 6
127.0.0.1:6379> ttl name1
(integer) 5
127.0.0.1:6379> ttl name1
(integer) 3
127.0.0.1:6379> ttl name1
(integer) 1
127.0.0.1:6379> ttl name1
(integer) 0
127.0.0.1:6379> ttl name1 #返回为-2时证明该key已过期,即不存在
(integer) -2
#不存在设置
127.0.0.1:6379> setnx name2 dingdada2 #如果key为‘name2’不存在,新增数据,返回值1证明成功
(integer) 1
127.0.0.1:6379> get name2
"dingdada2"
127.0.0.1:6379> keys *
1) "name2"
127.0.0.1:6379> setnx name2 "dingdada3" #如果key为‘name2’的已存在,设置失败,返回值0,也就是说这个跟set的区别是:set会替换原有的值,而setnx不会,存在即不设置,确保了数据误操作~
(integer) 0
127.0.0.1:6379> get name2
"dingdada2"
⑤mset、mget操作
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #插入多条数据
OK
127.0.0.1:6379> keys * #查询所有数据
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3 #查询key为‘k1’,‘k2’,‘k3’的数据
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> MSETNX k1 v1 k4 v4 #msetnx是一个原子性的操作,在一定程度上保证了事务!要么都成功,要么都失败!相当于if中的条件&&(与)
(integer) 0
127.0.0.1:6379> keys *
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379> MSETNX k5 v5 k4 v4 #全部成功
(integer) 1
127.0.0.1:6379> keys *
1) "k2"
2) "k4"
3) "k3"
4) "k5"
5) "k1"
⑥添加获取对象、getset操作
#这里其实本质上还是字符串,但是我们讲其key巧妙的设计了一下。
##mset student:1:name student 相当于类名,1 相当于id,name 相当于属性
#如果所需数据全部这样设计,那么我们在java的业务代码中,就不需要关注太多的key
#只需要找到student类,下面哪个id,需要哪个属性即可,减少了代码的繁琐,在一定程度上可以理解为这个一个类的对象!
127.0.0.1:6379> mset student:1:name dingdada student:1:age 22 #新增一个key为‘student:1:name’,value为‘dingdada ’。。等数据
OK
127.0.0.1:6379> keys * #查看所有的key
1) "student:1:age"
2) "student:1:name"
127.0.0.1:6379> mget student:1:age student:1:name #获取数据
1) "22"
2) "dingdada"
##getset操作
127.0.0.1:6379> getset name1 dingdada1 #先get再set,先获取key,如果没有,set值进去,返回的是get的值
(nil)
127.0.0.1:6379> get name1
"dingdada1"
127.0.0.1:6379> getset name1 dingdada2 ##先获取key,如果有,set(替换)最新的值进去,返回的是get的值
"dingdada1"
127.0.0.1:6379> get name1 #替换成功
"dingdada2"
⑦总结
String是Redis中最常用的一种数据类型,也是Redis中最简单的一种数据类型。首先,表面上它是字符串,但其实他可以灵活的表示字符串、整数、浮点数3种值。Redis会自动的识别这3种值。
4.3.2 List(列表)
①lpush(左插入)、lrange(查询集合)、rpush(右插入)操作
#lpush
127.0.0.1:6379> lpush list v1 #新增一个集合
(integer) 1
127.0.0.1:6379> lpush list v2
(integer) 2
127.0.0.1:6379> lpush list v3
(integer) 3
#lrange
127.0.0.1:6379> LRANGE list 0 -1 #查询list的所有元素值
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> lpush list1 v1 v2 v3 v4 v5 #批量添加集合元素
(integer) 5
127.0.0.1:6379> LRANGE list1 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
5) "v1"
###这里大家有没有注意到,先进去的会到后面,也就是我们的lpush的意思是左插入,l--left
#rpush
127.0.0.1:6379> LRANGE list 0 1 #指定查询列表中的元素,从下标零开始,1结束,两个元素
1) "v3"
2) "v2"
127.0.0.1:6379> LRANGE list 0 0 #指定查询列表中的唯一元素
1) "v3"
127.0.0.1:6379> rpush list rv0 #右插入,跟lpush相反,这里添加进去元素是在尾部!
(integer) 4
127.0.0.1:6379> lrange list 0 -1 #查看集合所有元素
1) "v3"
2) "v2"
3) "v1"
4) "rv0"
##联想:这里我们是不是可以做一个,保存的记录值(如:账号密码的记录),
每次都使用lpush,老的数据永远在后面,我们每次获取 0 0 位置的元素,是不是相当于更新了
数据操作,但是数据记录还在?想要查询记录即可获取集合所有元素!
②lpop(左移除)、rpop(右移除)操作
#lpop
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
5) "v1"
127.0.0.1:6379> lpop list #从头部开始移除第一个元素
"v5"
##################
#rpop
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> rpop list
"v1"
127.0.0.1:6379> LRANGE list 0 -1 #从尾部开始移除第一个元素
1) "v4"
2) "v3"
3) "v2"
③lindex(查询指定下标元素)、llen(获取集合长度) 操作
#lindex
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
127.0.0.1:6379> lindex list 1 #获取指定下标位置集合的元素,下标从0开始计数
"v3"
127.0.0.1:6379> lindex list 0 #相当于java中的indexof
"v4"
#llen
127.0.0.1:6379> llen list #获取指定集合的元素长度,相当于java中的length或者size
(integer) 3
④lrem(根据value移除指定的值)
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
127.0.0.1:6379> lrem list 1 v2 #移除集合list中的元素是v2的元素1个
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
127.0.0.1:6379> lrem list 0 v3 #移除集合list中的元素是v2的元素1个,这里的0和1效果是一致的
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
127.0.0.1:6379> lpush list v3 v2 v2 v2
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "v2"
2) "v2"
3) "v2"
4) "v3"
5) "v4"
127.0.0.1:6379> lrem list 3 v2 #移除集合list中元素为v2 的‘3’个,这里的参数数量,如果实际中集合元素数量不达标,不会报错,全部移除后返回成功移除后的数量值
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "v3"
2) "v4"
⑥ltrim(截取元素)、rpoplpush(移除指定集合中最后一个元素到一个新的集合中)操作
#ltrim
127.0.0.1:6379> lpush list v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> ltrim list 1 2 #通过下标截取指定的长度,这个list已经被改变了,只剩下我们所指定截取后的元素
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "v3"
2) "v2"
################
#rpoplpush
127.0.0.1:6379> lpush list v1 v2 v3 v4 v5
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
5) "v1"
127.0.0.1:6379> rpoplpush list newlist #移除list集合中的最后一个元素到新的集合newlist中,返回值是移除的最后一个元素值
"v1"
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
127.0.0.1:6379> LRANGE newlist 0 -1 #确实存在该newlist集合并且有刚刚移除的元素,证明成功
1) "v1"
⑦lset(更新)、linsert操作
#lset
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
127.0.0.1:6379>
127.0.0.1:6379> lset list 1 newV5 #更新list集合中下标为‘1’的元素为‘newV5’
OK
127.0.0.1:6379> LRANGE list 0 -1 #查看证明更新成功
1) "v5"
2) "newV5"
3) "v3"
4) "v2"
##注意点:
127.0.0.1:6379> lset list1 0 vvvv #如果指定的‘集合’不存在,报错
(error) ERR no such key
127.0.0.1:6379> lset list 8 vvv #如果集合存在,但是指定的‘下标’不存在,报错
(error) ERR index out of range
########################
#linsert
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "newV5"
3) "v3"
4) "v2"
127.0.0.1:6379> LINSERT list after v3 insertv3 #在集合中的‘v3’元素 ‘(after)之后’ 加上一个元素
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "newV5"
3) "v3"
4) "insertv3"
5) "v2"
127.0.0.1:6379> LINSERT list before v3 insertv3 #在集合中的‘v3’元素 ‘(before)之前’ 加上一个元素
(integer) 6
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "newV5"
3) "insertv3"
4) "v3"
5) "insertv3"
6) "v2"
⑧小结:
实际上是一个链表,before Node after , left,right 都可以插入值
如果key 不存在,创建新的链表
如果key存在,新增内容
如果移除了所有值,空链表,也代表不存在!
在两边插入或者改动值,效率最高! 中间元素,相对来说效率会低一点~
消息排队!消息队列 (Lpush Rpop), 栈( Lpush Lpop)!
4.3.3 Set(集合)元素唯一不重复
①sadd(添加)、smembers(查看所有元素)、sismember(判断是否存在)、scard(查看长度)、srem(移除指定元素)操作
#set中所有的元素都是唯一的不重复的!
127.0.0.1:6379> sadd set1 ding da mian tiao #添加set集合(可批量可单个,写法一致,不再赘述)
(integer) 4
127.0.0.1:6379> SMEMBERS set1 #查看set中所有元素
1) "mian"
2) "da"
3) "tiao"
4) "ding"
127.0.0.1:6379> SISMEMBER set1 da #判断某个值在不在set中,在返回1
(integer) 1
127.0.0.1:6379> SISMEMBER set1 da1 #不在返回0
(integer) 0
127.0.0.1:6379> SCARD set1 #查看集合的长度,相当于size、length
(integer) 4
127.0.0.1:6379> srem set1 da #移除set中指定的元素
(integer) 1
127.0.0.1:6379> SMEMBERS set1 #移除成功
1) "mian"
2) "tiao"
3) "ding"
②srandmember(抽随机)操作
127.0.0.1:6379> sadd myset 1 2 3 4 5 6 7 #在set中添加7个元素
(integer) 7
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
127.0.0.1:6379> SRANDMEMBER myset 1 #随机抽取myset中1个元素返回
1) "4"
127.0.0.1:6379> SRANDMEMBER myset 1 #随机抽取myset中1个元素返回
1) "1"
127.0.0.1:6379> SRANDMEMBER myset 1 #随机抽取myset中1个元素返回
1) "5"
127.0.0.1:6379> SRANDMEMBER myset #不填后参数,默认抽1个值,但是下面返回不会带序号值
"3"
127.0.0.1:6379> SRANDMEMBER myset 3 #随机抽取myset中3个元素返回
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> SRANDMEMBER myset 3 #随机抽取myset中3个元素返回
1) "6"
2) "3"
3) "5"
③spop(随机删除元素)、smove(移动指定元素到新的集合中)操作
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
127.0.0.1:6379> spop myset #随机删除1个元素,不指定参数值即删除1个
"2"
127.0.0.1:6379> spop myset 1 #随机删除1个元素
1) "7"
127.0.0.1:6379> spop myset 2 #随机删除2个元素
1) "3"
2) "5"
127.0.0.1:6379> SMEMBERS myset #查询删除后的结果
1) "1"
2) "4"
3) "6"
127.0.0.1:6379> smove myset myset2 1 #移动指定set中的指定元素到新的set中
(integer) 1
127.0.0.1:6379> SMEMBERS myset #查询原来的set集合
1) "4"
2) "6"
127.0.0.1:6379> SMEMBERS myset2 #查询新的set集合,如果新的set存在,即往后加,如果不存在,则自动创建set并且加入进去
1) "1"
④sdiff(差集)、sinter(交集)、sunion(并集)操作
127.0.0.1:6379> sadd myset1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> sadd myset2 3 4 5 6 7
(integer) 5
127.0.0.1:6379> SMEMBERS myset1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> SMEMBERS myset2
1) "3"
2) "4"
3) "5"
4) "6"
5) "7"
127.0.0.1:6379> SDIFF myset1 myset2 #查询指定的set之间的差集,可以是多个set
1) "1"
2) "2"
127.0.0.1:6379> SINTER myset1 myset2 #查询指定的set之间的交集,可以是多个set
1) "3"
2) "4"
3) "5"
127.0.0.1:6379> sunion myset1 myset2 #查询指定的set之间的并集,可以是多个set
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
⑤总结:可实现共同好友、共同关注等需求。
4.3.4 Hash(哈希)
①hset(添加hash)、hget(查询)、hgetall(查询所有)、hdel(删除hash中指定的值)、hlen(获取hash的长度)、hexists(判断key是否存在)操作
127.0.0.1:6379> hset myhash name dingdada age 23 #添加hash,可多个
(integer) 2
127.0.0.1:6379> hget myhash name #获取hash中key是name的值
"dingdada"
127.0.0.1:6379> hget myhash age #获取hash中key是age的值
"23"
127.0.0.1:6379> hgetall myhash #获取hash中所有的值,包含key
1) "name"
2) "dingdada"
3) "age"
4) "23"
127.0.0.1:6379> hset myhash del test #添加
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "dingdada"
3) "age"
4) "23"
5) "del"
6) "test"
127.0.0.1:6379> hdel myhash del age #删除指定hash中的key(可多个),key删除后对应的value也会被删除
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "name"
2) "dingdada"
127.0.0.1:6379> hlen myhash #获取指定hash的长度,相当于length、size
(integer) 1
127.0.0.1:6379> HEXISTS myhash name #判断key是否存在于指定的hash,存在返回1
(integer) 1
127.0.0.1:6379> HEXISTS myhash age #判断key是否存在于指定的hash,不存在返回0
(integer) 0
②hkeys(获取所有key)、hvals(获取所有value)、hincrby(给值加增量)、hsetnx(存在不添加)操作
127.0.0.1:6379> hset myhash age 23 high 173
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "name"
2) "dingdada"
3) "age"
4) "23"
5) "high"
6) "173"
127.0.0.1:6379> hkeys myhash #获取指定hash中的所有key
1) "name"
2) "age"
3) "high"
127.0.0.1:6379> hvals myhash #获取指定hash中的所有value
1) "dingdada"
2) "23"
3) "173"
127.0.0.1:6379> hincrby myhash age 2 #让hash中age的value指定+2(自增)
(integer) 25
127.0.0.1:6379> hincrby myhash age -1 #让hash中age的value指定-1(自减)
(integer) 24
127.0.0.1:6379> hsetnx myhash nokey novalue #添加不存在就新增返回新增成功的数量(只能单个增加哦)
(integer) 1
127.0.0.1:6379> hsetnx myhash name miaotiao #添加存在则失败返回0
(integer) 0
127.0.0.1:6379> hgetall myhash
1) "name"
2) "dingdada"
3) "age"
4) "24"
5) "high"
6) "173"
7) "nokey"
8) "novalue"
③总结:比String更加适合存对象~
4.3.5 zSet(有序集合)
①zadd(添加)、zrange(查询)、zrangebyscore(排序小-大)、zrevrange(排序大-小)、zrangebyscore withscores(查询所有值包含key)操作
127.0.0.1:6379> zadd myzset 1 one 2 two 3 three #添加zset值,可多个
(integer) 3
127.0.0.1:6379> ZRANGE myzset 0 -1 #查询所有的值
1) "one"
2) "two"
3) "three"
#-inf 负无穷 +inf 正无穷
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf #将zset的值根据key来从小到大排序并输出
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> ZRANGEBYSCORE myzset 0 1 #只查询key<=1的值并且排序从小到大
1) "one"
127.0.0.1:6379> ZREVRANGE myzset 1 -1 #从大到小排序输出
1) "two"
2) "one"
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf withscores #查询指定zset的所有值,包含序号的值
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
②zrem(移除元素)、zcard(查看元素个数)、zcount(查询指定区间内的元素个数)操作
127.0.0.1:6379> zadd myset 1 v1 2 v2 3 v3 4 v4
(integer) 4
127.0.0.1:6379> ZRANGE myset 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> zrem myset v3 #移除指定的元素,可多个
(integer) 1
127.0.0.1:6379> ZRANGE myset 0 -1
1) "v1"
2) "v2"
3) "v4"
127.0.0.1:6379> zcard myset #查看zset的元素个数,相当于长度,size。
(integer) 3
127.0.0.1:6379> zcount myset 0 100 #查询指定区间内的元素个数
(integer) 3
127.0.0.1:6379> zcount myset 0 2 #查询指定区间内的元素个数
(integer) 2
③总结:成绩表排序,工资表排序,年龄排序等需求可以用zset来实现!
4.3.6 Geospatial: 地理位置
城市经纬度查询: 经纬度查询
注意点1:两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
注意点2:有效的经度从-180度到180度。
注意点3:有效的纬度从-85.05112878度到85.05112878度。
注意点4:m 为米。km 为千米。mi 为英里。ft 为英尺。
①geoadd(添加)、geopos(查看)、geodist(计算距离)操作
127.0.0.1:6379> geoadd city 118.8921 31.32751 nanjing 197.30794 31.79322
#当经纬度其中一个或者两个超过界限值,报错,信息如下:
(error) ERR syntax error. Try GEOADD key [x1] [y1] [name1] [x2] [y2] [name2] ...
#添加城市经纬度 语法格式: geoadd key 经度 纬度 name +++可多个添加
#添加成功后返回添加成功的数量值
127.0.0.1:6379> geoadd city 118.8921 31.32751 nanjing 117.30794 31.79322 hefei 102.82147 24.88554 kunming 91.13775 29.65262 lasa 116.23128 40.22077 beijing 106.54041 29.40268 chongqing
(integer) 6
127.0.0.1:6379> ZRANGE city 0 -1 #注意:geo的查看方式和zset的命令是一致的,
#由此可知,geo本质上还是个集合,不过Redis官方对其进行了二次封装
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
127.0.0.1:6379> geopos city nanjing #查看看指定城市的经纬度信息
1) 1) "118.89209836721420288"
2) "31.32750976275760735"
127.0.0.1:6379> geopos city nanjing beijing #查看看多个城市的经纬度信息
1) 1) "118.89209836721420288"
2) "31.32750976275760735"
2) 1) "116.23128265142440796"
2) "40.22076905438526495"
127.0.0.1:6379> geodist city nanjing beijing #计算南京到北京之间的距离,默认返回单位是m
"1017743.1413"
127.0.0.1:6379> geodist city nanjing beijing km #km 千米
"1017.7431"
127.0.0.1:6379> geodist city nanjing beijing mi #mi 英里
"632.3978"
127.0.0.1:6379> geodist city nanjing beijing ft #ft 英尺
"3339052.3010"
②georadius(查询附近位置)操作
127.0.0.1:6379> ZRANGE city 0 -1 #查看城市
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
#查看指定位置的1000公里范围内有哪些城市
127.0.0.1:6379> georadius city 120 38 1000 km
1) "beijing"
2) "hefei"
3) "nanjing"
127.0.0.1:6379> georadius city 120 38 400 km #查看指定位置的400公里范围内有哪些城市
(empty array)
127.0.0.1:6379> georadius city 120 38 550 km #查看指定位置的550公里范围内有哪些城市
1) "beijing"
#查看指定位置的550公里范围内有哪些城市,withcoord指定返回城市的name
127.0.0.1:6379> georadius city 120 38 1000 km withcoord
1) 1) "beijing"
2) 1) "116.23128265142440796"
2) "40.22076905438526495"
2) 1) "hefei"
2) 1) "117.30793744325637817"
2) "31.79321915080526395"
3) 1) "nanjing"
2) 1) "118.89209836721420288"
2) "31.32750976275760735"
#查看指定位置的550公里范围内有哪些城市,withdist指定返回城市的’经纬度‘值
127.0.0.1:6379> georadius city 120 38 1000 km withcoord withdist
1) 1) "beijing"
2) "408.3496"
3) 1) "116.23128265142440796"
2) "40.22076905438526495"
2) 1) "hefei"
2) "732.6371"
3) 1) "117.30793744325637817"
2) "31.79321915080526395"
3) 1) "nanjing"
2) "749.0265"
3) 1) "118.89209836721420288"
2) "31.32750976275760735"
#查看指定位置的550公里范围内有哪些城市,withhash指定返回城市的’经纬度‘的hash值
#如果两个城市的hash值越’像‘,证明城市距离越近!
127.0.0.1:6379> georadius city 120 38 1000 km withcoord withdist withhash
1) 1) "beijing"
2) "408.3496"
3) (integer) 4069896088584598
4) 1) "116.23128265142440796"
2) "40.22076905438526495"
2) 1) "hefei"
2) "732.6371"
3) (integer) 4052763834193093
4) 1) "117.30793744325637817"
2) "31.79321915080526395"
3) 1) "nanjing"
2) "749.0265"
3) (integer) 4054278565840695
4) 1) "118.89209836721420288"
2) "31.32750976275760735"
#查看指定位置的550公里范围内有哪些城市,count num 指定返回’num‘个城市数据量
127.0.0.1:6379> georadius city 120 38 1000 km withcoord withdist withhash count 2
1) 1) "beijing"
2) "408.3496"
3) (integer) 4069896088584598
4) 1) "116.23128265142440796"
2) "40.22076905438526495"
2) 1) "hefei"
2) "732.6371"
3) (integer) 4052763834193093
4) 1) "117.30793744325637817"
2) "31.79321915080526395"
③ georadiusbymember (查找指定元素指定范围内的元素)、geohash (返回经纬度的hash值)、zrange、zrem(使用zset命令操作geo)
#查询南京 500公里范围有哪些城市
127.0.0.1:6379> georadiusbymember city nanjing 500 km
1) "hefei"
2) "nanjing"
#查询重庆 1500公里范围有哪些城市
127.0.0.1:6379> georadiusbymember city chongqing 1500 km
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
#返回北京和南京的经纬度的 hash值
127.0.0.1:6379> geohash city beijing nanjing
1) "wx4sucvncn0"
2) "wtsd1qyxfx0"
#查看所有城市name
127.0.0.1:6379> ZRANGE city 0 -1
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
#根据geo中的name删除g元素
127.0.0.1:6379> ZREM city lasa
(integer) 1
#删除成功
127.0.0.1:6379> ZRANGE city 0 -1
1) "kunming"
2) "chongqing"
3) "hefei"
4) "nanjing"
5) "beijing"
④总结:实际需求中,我们可以用来查询附近的人、计算两人之间的距离等。当然,那些所需的经纬度我们肯定要结合java代码来一次导入,手动查询和录入太过于浪费时间!
4.3.7 Hyperloglog: 基数
首先得明白什么是基数?
再数学层面上可以说是:两个数据集中不重复的元素~
但是再Redis中,可能会有一定的误差性。 官方给出的误差率是0.81%。
Hyperloglog的优点: 占用的内存是固定的,2^64个元素,相当于只需要12kb的内存即可。效率极高!
①pfadd(添加数据集)、pfcount(统计数据集)、pfmegre(合并数据集-自动去重)
127.0.0.1:6379> pfadd dataList 1 2 3 4 5 6 7 #添加数据集
(integer) 1
127.0.0.1:6379> pfcount dataList #统计数据集中的元素
(integer) 7
127.0.0.1:6379> pfadd dataList1 4 5 6 7 8 9 10 #添加数据集
(integer) 1
127.0.0.1:6379> pfcount dataList1 #统计数据集中的元素
(integer) 7
#将dataList 和dataList1 两个数据集合并成一个新的 newdata数据集,并且自动去重
127.0.0.1:6379> pfmerge newdata dataList dataList1
OK
127.0.0.1:6379> pfcount newdata
(integer) 10
②总结:如果在实际业务中,允许一定的误差值,我们可以使用基数统计来计算~效率非常高!比如:网站的访问量,就可以利用Hyperloglog来进行计算统计!
4.3.8 Bitmap: 位存储
Bitmap 位图,数据结构! 都是操作二进制位来进行记录,就只有0 和 1 两个状态!
①setbit(添加)、getset(获取)、bitcount(统计)操作
127.0.0.1:6379> setbit login 1 1 #添加周一已登陆 为1
(integer) 0
127.0.0.1:6379> setbit login 2 1
(integer) 0
127.0.0.1:6379> setbit login 3 1
(integer) 0
127.0.0.1:6379> setbit login 4 0 #添加周四已登陆 为0
(integer) 0
127.0.0.1:6379> setbit login 5 0
(integer) 0
127.0.0.1:6379> setbit login 6 1
(integer) 0
127.0.0.1:6379> setbit login 7 0
(integer) 0
127.0.0.1:6379> getbit login 1 #获取周一是否登录
(integer) 1
127.0.0.1:6379> getbit login 4 #获取周四是否登陆
(integer) 0
127.0.0.1:6379> bitcount login #统计这周登陆的天数
(integer) 4
②总结:实际需求中,可能需要我们统计用户的登陆信息,员工的打卡信息等等。只要是事务的只有两个状态的,我们都可以用Bitmap来进行操作!!!
5. Redis中的事务和乐观锁
-
事务
- 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
- 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
在Redis事务没有没有隔离级别的概念!
在Redis单条命令式保证原子性的,但是事务不保证原子性!
-
乐观锁
- 当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。
- 没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
在Redis是可以实现乐观锁的!
5.1 Redis如何实现事务?
①正常执行事务
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name dingyongjun #添加数据
QUEUED
127.0.0.1:6379> set age 26 #添加数据
QUEUED
127.0.0.1:6379> set high 172 #添加数据
QUEUED
127.0.0.1:6379> exec 执行事务
1) OK
2) OK
3) OK
127.0.0.1:6379> get name #获取数据成功,证明事务执行成功
"dingyongjun"
127.0.0.1:6379> get age
"26"
②放弃事务
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name dingyongjun #添加数据
QUEUED
127.0.0.1:6379> set age 26 #添加数据
QUEUED
127.0.0.1:6379> discard #放弃事务
OK
127.0.0.1:6379> get name #不会执行事务里面的添加操作
(nil)
③编译时异常,代码有问题,或者命令有问题,所有的命令都不会被执行
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name dingyongjun #添加数据
QUEUED
127.0.0.1:6379> set age 23 #添加数据
QUEUED
127.0.0.1:6379> getset name #输入一个错误的命令,这时候已经报错了,但是这个还是进入了事务的队列当中
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set high 173 #添加数据
QUEUED
127.0.0.1:6379> exec #执行事务,报错,并且所有的命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name #获取数据为空,证明没有执行
(nil)
④运行时异常,除了语法错误不会被执行且抛出异常后,其他的正确命令可以正常执行
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name dingyongjun #添加字符串数据
QUEUED
127.0.0.1:6379> incr name #对字符串数据进行自增操作
QUEUED
127.0.0.1:6379> set age 23 #添加数据
QUEUED
127.0.0.1:6379> get age #获取数据
QUEUED
127.0.0.1:6379> exec #执行事务。虽然对字符串数据进行自增操作报错了,但是其他的命令还是可以正常执行的
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "23"
127.0.0.1:6379> get age #获取数据成功
"23"
⑤总结:由以上可以得出结论,Redis是支持单条命令事务的,但是事务并不能保证原子性!
5.2 Redis如何实现乐观锁?
①watch(监视)
127.0.0.1:6379> set money 100 #添加金钱100
OK
127.0.0.1:6379> set cost 0 #添加花费0
OK
127.0.0.1:6379> watch money #监控金钱
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> DECRBY money 30 #金钱-30
QUEUED
127.0.0.1:6379> incrby cost 30 #花费+30
QUEUED
127.0.0.1:6379> exec #执行事务,成功!这时候数据没有发生变动才可以成功
1) (integer) 70
2) (integer) 30
②多线程测试watch
#线程1
#线程1
127.0.0.1:6379> set money 100 #添加金钱100
OK
127.0.0.1:6379> set cost 0 #添加花费0
OK
127.0.0.1:6379> watch money #开启监视(乐观锁)
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> DECRBY money 20 #金钱-20
QUEUED
127.0.0.1:6379> INCRBY cost 20 #花费+20
QUEUED
#这里先不要执行,先执行线程2来修改被监视的值
127.0.0.1:6379> exec #执行报错,因为我们监视了money这个值,如果事务要对这个值进行操作前
#监视器会判断这个值是否正常,如果发生改变,事务执行失败!
(nil)
#线程2
#线程2,这个在事务执行前操作执行
127.0.0.1:6379> INCRBY money 20 #金钱+20
(integer) 120
③总结:乐观锁和悲观锁的区别。
悲观锁: 什么时候都会出问题,所以一直监视着,没有执行当前步骤完成前,不让任何线程执行,十分浪费性能!一般不使用!
乐观锁: 只有更新数据的时候去判断一下,在此期间是否有人修改过被监视的这个数据,没有的话正常执行事务,反之执行失败!