Redis
Nosql概述
大数据一般的数据库无法处理
1.单机Mysql的年代
数据量如果太大,一个机器放不下!
数据的索引,300万(B+Tree),一个机器内存放不下
访问量(读写混合),一个服务器承受不了
2.Memcached(缓存)+MySQL+垂直拆分
网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!
为了减轻数据库的压力,使用缓存
发展过程:优化数据结构和索引–>使用文件缓存(IO)–>Memcache
3.分库分表+水平拆分+MySQL集群
技术和业务在发展的同时,对人的要求也越来越高!
本质:数据库,(读,写)
为什么用NOSQL?
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等
就需要用到NoSQL
**NoSQL:**Not Only SQL
泛指非关系型数据库,随着web2.0互联网的诞生!传统的关系型数据库很难!尤其是超大规模的高并发社区!暴露出很多难以克制的问题,NoSQL在当今大数据情况下发展迅速。Redis式发展最快的。
NoSQL特点:
1.方便扩展(数据之间没有关系)
2.大数据量高性能(Redis一秒写8万次,读取11万,NoSQL的缓存记录集,十一黄宗羲力度的缓存,性能会比较高!)
3.数据类型是多样型的!(不需要事先设计数据库!随取随用!)
4.传统的DDBMS和NOSQL
传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存在在单独的表中
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
NoSQL
- 不仅仅十数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- 最终在一致性
- CAP定理和BASE(异地多活)
- 高性能,高可用,高可扩
真正在公司中的实践:NoSQL+RDBMS
NoSQL的四大分类:
kv键值对:
新浪:Redis
美团:Redis+Tair
阿里,百度:Redis+memecache
文档型数据库:(bson和json一样)
MongDB:是一个基于分布式文件存储的数据库,c++编写,主要用来处理大量的文档。是一个介于关系型数据库和非关系型数据库中间的产品。
列存储数据库:
HNase,分布式文件系统,CounchDB
以列簇式存储,将同一列数据存在一起
图形(Graph)数据库:
Neo4j,InfoGrid,Infinite Graph
利用图结构相关算法,比如最短路径寻址,N度关系查找等
Redis入门
**Redis概述:**Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis能干什么?
1.内存存储,持久化,内存中是断电即失,所以持久化很重要(rdb,aof)
2.效率高,可以用与高速缓存
3.发布订阅系统
4.地图信息分析
5.计时器,计数器(浏览量!)
特性:
1.多样的数据类型
2.持久化
3.集群
4.事务
Redis基础知识
Redis有16个数据库,默认使用的是第0个,可以使用select进行切换数据库!
127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379[3]>DBSIZE #查看大小
(integer)0
127.0.0.1:6379[3]>Keys * #查看数据库所有的key
"name"
清除当前数据库flushdb
127.0.0.1:6379[3]>flushdb
OK
127.0.0.1:6379[3]>Keys *
(empty list or set)
请除全部数据库FLUSHALL
为什么redis的端口号是6379,mysql的端口号是3306!
https://blog.csdn.net/qq_25504829/article/details/115271672
https://www.php.cn/mysql-tutorials-418118.html
Redis是单线程的!
Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器内存和网络带宽,既然可以使用单线程来实现,就使用单线程!
Redis为什么单线程还这么快?
Redis是c语言写的,官方提供的数据100000+的QPS,完全不比同样是使用Key-value的Memecache差!
核心:redis是将所有的数据全部放在内存中,所以说使用单线程去操作的效率就是最高的,多线程(CPU上下文切换:耗时的操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案!
五大数据类型
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
Redis-Key
127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379>DBSIZE #查看大小
(integer)0
127.0.0.1:6379>Keys * #查看数据库所有的key
"name"
127.0.0.1:6379>flushdb
OK
127.0.0.1:6379>Keys *
(empty list or set)
127.0.0.1:6379>set name wangjie #set KEY
OK
127.0.0.1:6379>Keys *
"age"
"name"
127.0.0.1:6379>clear #清理
127.0.0.1:6379>get name
"wangjie"
127.0.0.1:6379>EXISTS name 10 #判断当前KEY是否存在
(integer) 1
127.0.0.1.6379>EXPTRE name #设置KEY的过期时间 ,单位是秒
127.0.0.1:6379>ttl name #查看KEY的剩余时间,单位是秒
(integer) 4
String(字符串)
127.0.0.1:6379>set key1 v1 #设置key1值为v1
ok
127.0.0.1:6379>get key1 #获得值
"v1"
127.0.0.1:6379>key *
"key1"
127.0.0.1:6379>EXISTS key1
(integer)1
127.0.0.1:6379>APPEND key1 "hello" #追加key1内容"hello"
(integer)7
127.0.0.1:6379>get key1
"v1hello"
127.0.0.1:6379>STRLEN key1 #获取字符串长度
(integer)7
127.0.0.1:6379>APPEND key1 ",wangjie"
(integer)15
127.0.0.1:6379>STRLEN key1
(integer)15
127.0.0.1:6379>get key1
"v1hello,wangjie"
127.0.0.1:6379>APPEND wangjie "wangjie" #如果当前key不存在,APPEND就是新建set key
##########################################################
i++
127.0.0.1:6379>set views 0 #新建views为0,初始为0
ok
127.0.0.1:6379>get views
"0"
127.0.0.1:6379>incr views #views的值自增1
(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 #views的值自减1
(integer)1
127.0.0.1:6379>INCRBY views 10 #views的值+10,可以设置,指定增量
(integer)11
127.0.0.1:6379>>DECRBY views 10 #views的值-10
(integer)1
##########################################################
字符串范围
127.0.0.1:6379>keys *
(empty list or set)
127.0.0.1:6379>>set key1 "hello,wangjie"
ok
127.0.0.1:6379>get key1
"hello,wangjie"
127.0.0.1:6379>GETRANGE key1 0 3 #截取从0到3的字符串
"hell"
127.0.0.1:6379>GETRANGE key1 0 -1 #可以通过这样查看全部的字符串
"hello,wangjie"
##########################################################
替换
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"
##########################################################
#setex (set with expire) #设置过期时间
#setnx (set if not exist)#不存在设置(在分布式锁中会常常使用)
127.0.0.1:6379>setex key3 30 "hello"
ok
127.0.0.1:6379>ttl key3
(integer) 26
127.0.0.1:6379>get key3
"hello"
127.0.0.1:6379>setnx mykey "redis"
(integer) 1
127.0.0.1:6379>keys *
"key2"
"mykey"
"key1"
127.0.0.1:6379>ttl key3
(integer) -2
127.0.0.1:6379>setnx mykey "MongDB" #如果mykey存在,创建失败
(integer) 0
127.0.0.1:6379>get mykey
"redis"
##########################################################
mset
mget
127.0.0.1:6379>mset k1 v1 k2 v2 k3 v3 #同时设置多个值
ok
127.0.0.1:6379>keys *
"k1"
"k2"
"k3"
127.0.0.1:6379>mget k1 k2 k3 #同时获取多个值
"v1"
"v2"
"v3"
127.0.0.1:6379>msetnx k1 v1 k4 v4 #msetnx是一个原子性的操作,要么一起成功,要么一起失败
##########################################################
#对象
set user:1 {name:zhangsan,age:3} #设置一个user:1的对象 值为json字符来保存一个对象!
#这里的key是一个巧妙的设计: user:{id}:{filed}
127.0.0.1:6379>mset user:1:name zhangsan user:1:age 2
ok
127.0.0.1:6379>mget user:1:name user:1:age
"zhangsan"
"2"
##########################################################
#getset 先get然后在set
127.0.0.1:6379>getset db redis #如果不存在值,则返回nil
(nil)
127.0.0.1:6379>get db
"redis"
127.0.0.1:6379>getset db mongdb #如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379>get db
"mongdb"
数据结构是相同的
String类似的场景:value除了是我么的字符串还可以是我们的数字!
- 计数器
- 统计多单位的数量
- 对象缓存存储
List
##########################################################
#lpush list插入
127.0.0.1:6379>keys *
(empty list or set)
127.0.0.1:6379>LPUSH list one #将一个值或多个值,插入到列表头部(左)
(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
"three"
"two"
"one"
127.0.0.1:6379>LRANGE list 0 1
"three"
"two"
127.0.0.1:6379>Rpush list righr #将一个值或多个值,插入到列表尾部(右)
(integer) 4
127.0.0.1:6379>LRANGE list 0 1 #通过区间具体取值
"three"
"two"
127.0.0.1:6379>LRANGE list 0 -1 #获取list全部的值
"three"
"two"
"one"
"righr"
##########################################################
#lpop rpop 取值
127.0.0.1:6379>LRANGE list 0 -1 #获取list全部的值
"three"
"two"
"one"
"righr"
127.0.0.1:6379>lpop list #移除list左边第一个元素
"three"
127.0.0.1:6379>rpop list #移除list右边第一个元素
"righr"
##########################################################
#lindex
127.0.0.1:6379>LRANGE list 0 -1
"two"
"one"
127.0.0.1:6379>lindex list 1 #通过下标获得list中的某一个值
"one"
127.0.0.1:6379>lindex list 0
"two"
##########################################################
#Llen
127.0.0.1:6379>LPUSH list one
(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>Llen list #返回列表的长度
(integer) 3
##########################################################
#移除指定的值 Lrem
127.0.0.1:6379>Lrem list 1 one #移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379>LRANGE list 0 -1
"three"
"two"
127.0.0.1:6379>Lrem list 1 three
(integer) 1
127.0.0.1:6379>LRANGE list 0 -1
"two"
127.0.0.1:6379>Lpush list three
(integer) 2
127.0.0.1:6379>Lpush list three
(integer) 3
127.0.0.1:6379>lrem list 2 three
(integer) 2
127.0.0.1:6379>LRANGE list 0 -1
"two"
##########################################################
#trim 修剪: list 截断
127.0.0.1:6379>FlushDB
ok
127.0.0.1:6379>keys *
(empty list or set)
127.0.0.1:6379>Rpush mylist "hello"
(integer) 1
127.0.0.1:6379>Rpush mylist "hello1"
(integer) 2
127.0.0.1:6379>Rpush mylist "hello2"
(integer) 3
127.0.0.1:6379>Rpush mylist "hello3"
(integer) 4
127.0.0.1:6379>ltrim mylist 1 2 #通过下标截取指定的长度,这个list已经被改变了,阶段了只剩下截取的元素!
ok
127.0.0.1:6379>LRANGE mylist 0 -1
"hello1"
"hello2"
##########################################################
#rpop rpush 移出列表做后一个元素,将它移动到新的列表中
127.0.0.1:6379>Lpush mylist "hello"
(integer) 3
127.0.0.1:6379>rpoplpush mylist myotherlist
"hello2"
127.0.0.1:6379>lrange mylist 0 -1
"hello"
"hello1"
127.0.0.1:6379>lrange myotherlist 0 -1
"hello2"
##########################################################
#lset 将列表中指定下标的值替换为另外一个值,更新操作
127.0.0.1:6379>lset list 0 item #不存在会报错
(error) ERR no such key
127.0.0.1:6379>lpush list value1
(integer) 1
127.0.0.1:6379>lrange list 0 0
"value1"
127.0.0.1:6379>lset list 0 item
ok
127.0.0.1:6379>lrange list 0 0
"item"
##########################################################
#linsert #将某一个具体的值value插入到指定的值前或后
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 befor "world" "other"
(integer) 3
127.0.0.1:6379>lrange mylist 0 -1
"hello"
"other"
"world"
127.0.0.1:6379>linsert mylist after "world" "new"
(integer) 4
127.0.0.1:6379>lrange mylist 0 -1
"hello"
"other"
"world"
"new"
- 它实际上是一个链表,before Node after ,left right都可以插入值
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有的值,空链表,也代表不存在
- 在两边插入或改动值,效率最高,中间元素,相对来说效率会低一些
消息排队!消息队列(lpush rpop) ,栈(lpush lpop)
在Redis里面,可以把List玩成栈,队列,阻塞队列!
Set
set的值是不能重复的!
##########################################################
#setadd set集合中添加值
127.0.0.1:6379>sadd myset "hello"
(integer) 1
127.0.0.1:6379>sadd myset "wangjie"
(integer) 1
127.0.0.1:6379>sadd myset "learn wangjie"
(integer) 1
127.0.0.1:6379>SMEMBERS myset #获取myset的所有值
"hello"
"learn wangjie"
"wangjie"
127.0.0.1:6379>SISMEMBER myset hello #判断myset中是否有hello
(integer) 1
##########################################################
127.0.0.1:6379>scard myset #获取set集合中的内容元素个数!
(integer) 4
127.0.0.1:6379>srem myset hello #移除set集合中的指定元素
(integer) 1
127.0.0.1:6379>scard myset
(integer) 3
##########################################################
#set 无需不重复集合,抽随机!
127.0.0.1:6379>SRANDMEMBER myset #随机抽出一个元素
"wangjie"
127.0.0.1:6379>SRANDMEMBER myset 2 #随机抽出指定个数的元素
"wangjie"
"learn wangjie"
##########################################################
删除指定的key,随即删除key
127.0.0.1:6379>setadd myset"learnwangjie2"
ok
127.0.0.1:6379>SMEEMBERS myset
"learn wangjie"
"learnwangjie2"
"wangjie"
127.0.0.1:6379>spop myset #随即删除一些set集合中的元素
"learnwangjie2"
127.0.0.1:6379>SMEEMBERS myset
"learn wangjie"
"wangjie"
##########################################################
将一个指定的值,移动到另外一个set集合中!
127.0.0.1:6379>flushdb
127.0.0.1:6379>sadd myset "hello"
(integer) 1
127.0.0.1:6379>sadd myset "world"
(integer) 1
127.0.0.1:6379>sadd myset "wangjie"
(integer) 1
127.0.0.1:6379>sadd mysets "set2"
(integer) 1
127.0.0.1:6379>smove myset myset2 "wangjie" #将一个指定的值,移动到另外一个set集合中!
(integer) 1
127.0.0.1:6379>SMEEMBERS myset
"hello"
"world"
127.0.0.1:6379>SMEEMBERS myset2
"wangjie"
"set2"
##########################################################
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 key2 c
(integer) 1
127.0.0.1:6379>sadd key2 e
(integer) 1
127.0.0.1:6379>sadd key2 d
(integer) 1
127.0.0.1:6379>SDIFF key1 key2 #差集
"b"
"a"
127.0.0.1:6379>INTER key1 key2 #交集
"c"
127.0.0.1:6379>SUNION key1 key2 #并集
"b"
"a"
"c"
"d"
"e"
Hash
map集合,key-map集合!这个时候值是一个map集合.本质和String没有太大区别,还是一个简单的key-value!
set myhash
127.0.0.1:6379>hset myhash fieldl wangjie #set一个具体的key-value
(integer)1
127.0.0.1:6379>hget myhash fieldl
wangjie
127.0.0.1:6379>hmset myhash fieldl hello field2 world #set多个
ok
127.0.0.1:6379>hmget myhash fieldl field2 #获取多个
"hello"
"world"
127.0.0.1:6379>hgetall myhash #获取全部数据
"fieldl"
"hello"
"field2"
"world"
127.0.0.1:6379>hdel myhash fieldl #删除指定的key,对应的value也没了
(integer) 1
127.0.0.1:6379>hgetall myhash
"field2"
"world"
##########################################################
127.0.0.1:6379>hlen myhash #获取当前key的数量
(integer) 1
127.0.0.1:6379>HEXISTS myhash field2 #判断当前hash的可以是否存在,存在为1,不存在为
(integer) 1
127.0.0.1:6379>HEXISTS myhash field3
(integer)
##########################################################
#只获得所有field
#只获得所有的value
127.0.0.1:6379>hkeys myhash #只获得hash所有的key
"field2"
127.0.0.1:6379>hvals myhash #只获得所有的value
"world"
##########################################################
#incr decr HINCRBY hset hsetnx hstnx 与String一样
hash变更的数据对象 user name age尤其是用户信息之类的,经常变动的信息!
hash公示和与对象的存储,String更适合于字符串的存储
Zset(有序集合)
在set的基础上,增加了一个值
127.0.0.1:6379>zadd myset 1 one #添加一个值
(integer) 1
127.0.0.1:6379>zadd myset 2 two 3 three #添加多个值
(integer) 2
127.0.0.1:6379>ZRANGE myset 0 -1 #遍历
"one"
"two"
"three"
##########################################################
#排序 -inf负无穷 +inf正无穷
127.0.0.1:6379>ZRANGEBYSCORE myset -inf +inf #显示全部的用户,从小到大
"one"
"two"
"three"
127.0.0.1:6379>ZRANGEBYSCORE myset -inf +inf withscores #显示所有用户并附带成绩
"one"
"1"
"two"
"2"
"three"
"3"
127.0.0.1:6379>ZRANGEBYSCORE myset -inf 2 withscores #显示小于2的降序排序
"one"
"1"
"two"
"2"
127.0.0.1:6379>ZREVRANGE myset 0 -1 #从大到小排序
"three"
"two"
"one"
##########################################################
#移除rem中的元素
127.0.0.1:6379>zrem myset three #移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379>zrange myset 0 -1
"one"
"two"
127.0.0.1:6379>zcard mysetc #获取有序集合中的个数
(integer) 2
##########################################################
127.0.0.1:6379>zcount myset 1 2 #获取有序集合指定区间的成员数量
(integer) 2
127.0.0.1:6379>zadd myset 3 "three"
(integer) 1
127.0.0.1:6379>zcount myset 1 3
(integer) 3
其余的API,通过我们的学习,工作中有需要的话,可以去官方文档查看!
http://www.redis.cn/commands.html
三种特殊数据类型
geospatial(地理位置)
距离的计算,Redis的Geo,可以查询一些测试数据
这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的用户!
只有六个命令
- GEOADD
- GEODIST
- GEOHASH
- GEOPOS
- GEORADIUS
- GEORADIUSBYMEMBER
GEOADD
###########################################################geoadd 添加地理位置
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
有效经度为-180-180,纬度为-85-85.
GEOPOS
###########################################################Geopos 获取指定的城市地址
127.0.0.1:6379>GEOPOS china:city beijing
"116.40"
"39.90"
127.0.0.1:6379>GEOPOS china:city shanghai
"121.47"
"31.23"
GEODIST
两人之间的距离!
单位:
- m表示单位为米
- km表示单位为千米
- mi表示单位为英里
- ft表示单位为英尺
###########################################################GEODIST
127.0.0.1:6379>GEODIST china:city beijing shanghai #查看北京到上海的直线距离
"1037378.7564"
127.0.0.1:6379>GEODIST china:city beijing shanghai km
"1067.3788"
GEORADIUS
一给定的经度纬度为中心,给定的距离为半径,查找范围内的元素
附近的人 (获得附近所有人的地址:定位!,通过半径查询)
###########################################################附近的人 (获得附近所有人的地址:定位!,通过半径查询)
127.0.0.1:6379>GEORADIUS china:city 116 30 1000km
"beijing"
GEORADIUSBYMEMBER
找出位于指定范围内的元素,中心点是由给定的位置元素决定
###########################################################附近的人 (获得附近所有人的地址:城市!,通过半径查询)
127.0.0.1:6379>GEORADIUSBYMEMBER china:city beijing 1000km
"beijing"
GEOHASH
返回一个或多个位置元素的Geohash表示
该命令将返回11个字符的Geohash字符串!
127.0.0.1:6379>GEOHASH china:city beijing chongqin
"wx4fbxxfke0"
"wm5xzrybty0"
Hyperloglog(基数统计的算法)
什么是基数?
基数:不重复的元素
场景:网页UV
优点:占用的内存是固定的,2^64不同元素的技术,只需要费12kb内存!
############################################################
127.0.0.1:6379>PFadd mykey a b c d e f g h i j #创建第一组元素mykey
(integer) 1
127.0.0.1:6379>PFCOUNT mykey #统计第一组元素的基数数量
(integer) 10
127.0.0.1:6379>PFadd mykey2 i j z x c v b n m
(integer) 1
127.0.0.1:6379>PFCOUNT mykey2
(integer) 9
127.0.0.1:6379>PFMERGE mykey3 mykey mykey2 #合并两组 mykey mykey2 => mykey3 并集
ok
127.0.0.1:6379>PFCOUNT mykey3 #看并集数量
(integer) 15
如果允许容错
Bitmaps(位存储)
统计用户信息,活跃,不活跃!登录,未登录!打卡
############################################################记录周一到周日的打卡
127.0.0.1:6379>setbit sign 0 1
(integer) 0
127.0.0.1:6379>setbit sign 1 0
(integer) 0
127.0.0.1:6379>setbit sign 2 0
(integer) 0
127.0.0.1:6379>setbit sign 3 1
(integer) 0
127.0.0.1:6379>setbit sign 4 1
(integer) 0
127.0.0.1:6379>setbit sign 5 0
(integer) 0
127.0.0.1:6379>setbit sign 6 0
(integer) 0
127.0.0.1:6379>getbit sign 3 #查看是否打卡,1为是,0为否
(integer) 1
127.0.0.1:6379>getbit sign6
(integer) 0
############################################################ 统计操作 统计打卡的天数
127.0.0.1:6379>bitcount sign #统计这周打卡的天数
(integer) 3
事务
Redis单条命令是保存原子性的,但是事务不保证原子性!
Redis事物的本质:一组命令的集合!一个事务中的命令都会被序列化,在事务被执行的过程中,会按照顺序执行!
一次性,顺序性,排他性!
Redis事务没有隔离级别的概念!
在所有的命令食物中,并没有直接被执行!只有发起执行命令的时候才会执行!
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 k2
QUEUED
127.0.0.1:6379>set k3 v3
QUEUED
127.0.0.1:6379>exec #执行命令
ok
ok
"v2"
ok
放弃事务
127.0.0.1:6379>multi
127.0.0.1:6379>set k1 v1 #命令入队
QUEUED
127.0.0.1:6379>set k2 v2
QUEUED
127.0.0.1:6379>set k4 v4
QUEUED
127.0.0.1:6379>DISCARD #放弃事务
ok
127.0.0.1:6379>get k4
(nil)
编译型异常(代码有问题!命令有错!)。事务中所有的命令都不会被执行!
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>set k3 v3
QUEUED
127.0.0.1:6379>getset k2 #语法错误
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379>set k4 v4
QUEUED
127.0.0.1:6379>exec #执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常,如果事务队列中存在语法性,那么执行命令的时候,其他命令式可以正常执行的
127.0.0.1:6379>set k1 "v1"
ok
127.0.0.1:6379>multi #开启事务
ok
127.0.0.1:6379>incr k1
QUEUED
127.0.0.1:6379>set k2 v2
QUEUED
127.0.0.1:6379>set k3 v3
QUEUED
127.0.0.1:6379>get k3
QUEUED
127.0.0.1:6379>exec
(error) ERR value is not an integer or out of range
ok
ok
"v3"
悲观锁:
- 无论做什么都会加锁!(影响性能)
乐观锁:
- 不会上锁!
更新数据的时候去判断一下,在此期间是否有人修改过这个数据
Redis监视测试
正常执行成功!
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
(integer) 80
(integer) 20
测试多线程修改值,使用watch可以当做redis的乐观锁操作!
#正在执行的事务1
127.0.0.1:6379>watch money #监视money 对象
ok
127.0.0.1:6379>multi
ok
127.0.0.1:6379>DECRBY money 10
QUEUED
127.0.0.1:6379>INCRBY out 10
QUEUED
127.0.0.1:6379>exec #在执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失败!
(nil)
#另一个事务2
127.0.0.1:6379>watch money #监视money 对象
ok
127.0.0.1:6379>multi
ok
127.0.0.1:6379>set money 1000
ok
127.0.0.1:6379>exec
多线程,当事务2在事务1之前修改了money的值,这个时候事务1修改money的值就会失败。
无论redis的事务执行是否成功,都会取消监视
127.0.0.1:6379>unwatch #放弃监视 #如果发现事务执行失败,就先解锁
ok
127.0.0.1:6379>watch money #再次监视 ,获取最新的值
ok
127.0.0.1:6379>DECRBY money 10
(integer) 990
127.0.0.1:6379>INCRBY out 10
(integer) 30
127.0.0.1:6379>exec #比对监视是否发生了变化,如果没有变化,那么可以执行成功,如果变化了就会失败
(integer) 990
如果发现事务执行失败,就先解锁,再次监视 ,获取最新的值,再次执行就好!
Jedis
我们要使用Java来操作Redis
Jedis:是Redis官方推荐的java连接开发工具!使用java操作Redis的中间件!
用IDEA进行测试
1.导入对应的依赖
<!--导入jedis的包-->
<dependencies>
<!--Redis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
2.编码测试
- 连接数据库
- 操作命令
- 断开连接
package com.wj.redis;
import com.alibaba.fastjson.JSONObject;
import netscape.javascript.JSObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestRedis {
public static void main(String[] args) {
//1.new Jedis 对象即可
Jedis jedis=new Jedis("127.0.0.1",6379);
//jedis所有的命令就是我们之前学习所有的指令
System.out.println( jedis.ping());
JSONObject jsObject=new JSONObject();
jsObject.put("hello","world");
jsObject.put("name","kuangshen");
//jedis开启事务
Transaction mutil=jedis.multi();
String result=jsObject.toJSONString();
try {
mutil.set("user1",result);
mutil.set("user2",result);
//执行事务
mutil.exec();
}catch (Exception e){
//放弃事务
mutil.discard();
e.printStackTrace();
}finally {
jedis.close();
}
}
}
下图是redis中一些数据类型的方法调用:
在SpringBoot2.x之后,原来使用的Jedis被替换为了lettuce