Redis是什么

概述

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务。

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis能干什么?

  1. 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)

  2. 高效率、用于高速缓冲

  3. 发布订阅系统

  4. 地图信息分析

  5. 计时器、计数器(eg:浏览量)

  6. ……

特性

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

环境搭建

官网:https://redis.io/

推荐使用Linux服务器学习。

windows版本的Redis已经停更很久了…


五大数据类型

Redis-KEY

在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。

下面学习的命令:

  • exists key:判断键是否存在
  • del key:删除键值对
  • move key db:将键值对移动到指定数据库
  • expire key second:设置键值对的过期时间
  • type key:查看value的数据类型
127.0.0.1:6379> keys * # 查看当前数据库所有key
(empty list or set)
127.0.0.1:6379> set name qinjiang # set key
OK
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> move age 1 # 将键值对移动到指定数据库
(integer) 1
127.0.0.1:6379> EXISTS age # 判断键是否存在
(integer) 0 # 不存在
127.0.0.1:6379> EXISTS name
(integer) 1 # 存在
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> keys *
1) "age"
127.0.0.1:6379[1]> del age # 删除键值对
(integer) 1 # 删除个数


127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> EXPIRE age 15 # 设置键值对的过期时间

(integer) 1 # 设置成功 开始计数
127.0.0.1:6379> ttl age # 查看key的过期剩余时间
(integer) 13
127.0.0.1:6379> ttl age
(integer) 11
127.0.0.1:6379> ttl age
(integer) 9
127.0.0.1:6379> ttl age
(integer) -2 # -2 表示key过期,-1表示key未设置过期时间

127.0.0.1:6379> get age # 过期的key 会被自动delete
(nil)
127.0.0.1:6379> keys *
1) "name"

127.0.0.1:6379> type name # 查看value的数据类型
string

关于TTL命令

Redis的key,通过TTL命令返回key的过期时间,一般来说有3种:

  • 当前key没有设置过期时间,所以会返回-1.
  • 当前key有设置过期时间,而且key已经过期,所以会返回-2.
  • 当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.

RENAME key newkey修改 key 的名称
RENAMENX key newkey仅当 newkey 不存在时,将 key 改名为 newkey 。

String

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> APPEND k1 hello #追加字符串
(integer) 7
127.0.0.1:6379> get k1
"v1hello"
127.0.0.1:6379> APPEND k2 v2 #不存在就,就相当于set
(integer) 2
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> INCR 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 # 删除1
(integer) 1
127.0.0.1:6379> DECR views
(integer) 0
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> INCRBY views 10		#指定步长
(integer) 10
127.0.0.1:6379> get views
"10"
127.0.0.1:6379> DECRBY views 9
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> get k1
"hello"
127.0.0.1:6379> GETRANGE k1 1 3 #截取字符串
"ell"
127.0.0.1:6379> GETRANGE k1 0 -1
"hello"
127.0.0.1:6379> set k2 abcdefg
OK
127.0.0.1:6379> SETRANGE k2 1 xx
(integer) 7
127.0.0.1:6379> get k2
"axxdefg"
127.0.0.1:6379> SETEX k3 30 hello	#设置k3,值为hello,30s过期
OK
127.0.0.1:6379> TTL k3
(integer) 14

127.0.0.1:6379> SETEX mykey redis #不存在创建成功返回1
(integer) 1
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> SETNX mykey mysql #不存在没有成功返回0
(integer) 0
127.0.0.1:6379> get mykey
"redis"

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 # MSETNX,是一个原子操作,要么一起成功,要么一起失败!
(integer) 0
127.0.0.1:6379> KEYS *
1) "k3"
2) "k2"
3) "k1"

==对象==

127.0.0.1:6379> mset user:1:name bzm user:1:age 18
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "bzm"
2) "18"

127.0.0.1:6379> GETSET db redis		#如果不存在值,返回null
(nil)
127.0.0.1:6379> GET db
"redis"
127.0.0.1:6379> GETSET db mongodb #如果存在值,获取原来值,并设置新值
"redis"
127.0.0.1:6379> GET db
"mongodb"

List

基本的数据类型,列表。

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 #获取list的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1 #获取区间内的值
1) "three"
2) "two"

127.0.0.1:6379> RPUSH list hello
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1 #右插
1) "three"
2) "two"
3) "one"
4) "hello"
127.0.0.1:6379> LPOP list #左移除一个元素
"three"
127.0.0.1:6379> RPOP list #右移除一个元素
"hello"
127.0.0.1:6379>
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> LINDEX list 1 #通过下标获取值
"one"
127.0.0.1:6379> LINDEX list 0
"two"

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
127.0.0.1:6379> LRANGE list 0 -1
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> LLEN list |#返回列表的长度
(integer) 3
127.0.0.1:6379> LREM list 1 v1 #移除list列表中1个v1
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "v3"
2) "v2"

127.0.0.1:6379> RPUSH list v1
(integer) 1
127.0.0.1:6379> RPUSH list v2
(integer) 2
127.0.0.1:6379> RPUSH list v3
(integer) 3
127.0.0.1:6379> LTRIM list 1 2
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "v2"
2) "v3"

127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> RPOPLPUSH list mylist #移除列表的最后一个元素,到新的列表中
"v4"
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> KEYS *
1) "mylist"
2) "list"
127.0.0.1:6379> LRANGE mylist 0 -1 #查看新列表
1) "v4"

127.0.0.1:6379> EXISTS list		#判断list是否存在
(integer) 0
127.0.0.1:6379> RPUSH list v1
(integer) 1
127.0.0.1:6379> LSET list 0 vv #设置0索引位置值为vv
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "vv"
127.0.0.1:6379> LSET list 1 v2 #设置不存在的报错
(error) ERR index out of range

127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> LINSERT list before v2 oo #往指定list表位置之前插入
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "oo"
3) "v2"
4) "v3"
5) "v4"
127.0.0.1:6379> LINSERT list after v4 aa
(integer) 6
127.0.0.1:6379> LRANGE list 0 -1
1) "v1"
2) "oo"
3) "v2"
4) "v3"
5) "v4"
6) "aa"

小结

  • 实际上是一个链表,before Node after
  • 列表中的元素是有序的,可以通过索引下标来获取某个元素或者某个范围内的元素列表
  • 列表中的元素是可以重复的

Set

127.0.0.1:6379> SADD list v1		#set集合中添加 
(integer) 1
127.0.0.1:6379> SADD list v2
(integer) 1
127.0.0.1:6379> SADD list v3
(integer) 1
127.0.0.1:6379> SADD list v4
(integer) 1
127.0.0.1:6379> SMEMBERS list #查看指定set的所有值
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> SISMEMBER list v2 #判断某一个值是否存在,1存在,0不存在
(integer) 1
127.0.0.1:6379> SISMEMBER list vv
(integer) 0

127.0.0.1:6379> SCARD list #查看集合中元素的个数
(integer) 4
127.0.0.1:6379> SREM list v2 #移除集合中的元素
(integer) 1
127.0.0.1:6379> SMEMBERS list
1) "v4"
2) "v3"
3) "v1"

127.0.0.1:6379> SRANDMEMBER list 	#随机抽选出一个元素
"v4"
127.0.0.1:6379>
127.0.0.1:6379> SRANDMEMBER list
"v1"

127.0.0.1:6379> SPOP list	#随机移除元素
"v3"
127.0.0.1:6379> SMEMBERS list
1) "v4"
2) "v1"

127.0.0.1:6379> SADD list hello1
(integer) 1
127.0.0.1:6379> SADD list hello2
(integer) 1
127.0.0.1:6379> SADD list hello3
(integer) 1
127.0.0.1:6379> SADD list2 2
(integer) 1
127.0.0.1:6379> SMOVE list list2 hello2 #指定一个值,移到另一个集合
(integer) 1
127.0.0.1:6379> SMEMBERS list
1) "hello3"
2) "hello1"
127.0.0.1:6379> SMEMBERS list2
1) "hello2"
2) "2"

Hash

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。

Set就是一种简化的Hash,只变动key,而value使用默认值填充。可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。

------------------------HSET--HMSET--HSETNX----------------
127.0.0.1:6379> HSET studentx name sakura # 将studentx哈希表作为一个对象,设置name为sakura
(integer) 1
127.0.0.1:6379> HSET studentx name gyc # 重复设置field进行覆盖,并返回0
(integer) 0
127.0.0.1:6379> HSET studentx age 20 # 设置studentx的age为20
(integer) 1
127.0.0.1:6379> HMSET studentx sex 1 tel 15623667886 # 设置sex为1,tel为15623667886
OK
127.0.0.1:6379> HSETNX studentx name gyc # HSETNX 设置已存在的field
(integer) 0 # 失败
127.0.0.1:6379> HSETNX studentx email 12345@qq.com
(integer) 1 # 成功

----------------------HEXISTS--------------------------------
127.0.0.1:6379> HEXISTS studentx name # name字段在studentx中是否存在
(integer) 1 # 存在
127.0.0.1:6379> HEXISTS studentx addr
(integer) 0 # 不存在

-------------------HGET--HMGET--HGETALL-----------
127.0.0.1:6379> HGET studentx name # 获取studentx中name字段的value
"gyc"
127.0.0.1:6379> HMGET studentx name age tel # 获取studentx中name、age、tel字段的value
1) "gyc"
2) "20"
3) "15623667886"
127.0.0.1:6379> HGETALL studentx # 获取studentx中所有的field及其value
1) "name"
2) "gyc"
3) "age"
4) "20"
5) "sex"
6) "1"
7) "tel"
8) "15623667886"
9) "email"
10) "12345@qq.com"


--------------------HKEYS--HLEN--HVALS--------------
127.0.0.1:6379> HKEYS studentx # 查看studentx中所有的field
1) "name"
2) "age"
3) "sex"
4) "tel"
5) "email"
127.0.0.1:6379> HLEN studentx # 查看studentx中的字段数量
(integer) 5
127.0.0.1:6379> HVALS studentx # 查看studentx中所有的value
1) "gyc"
2) "20"
3) "1"
4) "15623667886"
5) "12345@qq.com"

-------------------------HDEL--------------------------
127.0.0.1:6379> HDEL studentx sex tel # 删除studentx 中的sex、tel字段
(integer) 2
127.0.0.1:6379> HKEYS studentx
1) "name"
2) "age"
3) "email"

-------------HINCRBY--HINCRBYFLOAT------------------------
127.0.0.1:6379> HINCRBY studentx age 1 # studentx的age字段数值+1
(integer) 21
127.0.0.1:6379> HINCRBY studentx name 1 # 非整数字型字段不可用
(error) ERR hash value is not an integer
127.0.0.1:6379> HINCRBYFLOAT studentx weight 0.6 # weight字段增加0.6
"90.8"

Hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!Hash更适合于对象的存储,Sring更加适合字符串存储!

Zset

不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。score相同:按字典顺序排序

有序集合的成员是唯一的,但分数(score)却可以重复。

-------------------ZADD--ZCARD--ZCOUNT--------------
127.0.0.1:6379> ZADD myzset 1 m1 2 m2 3 m3 # 向有序集合myzset中添加成员m1 score=1 以及成员m2 score=2..
(integer) 2
127.0.0.1:6379> ZCARD myzset # 获取有序集合的成员数
(integer) 2
127.0.0.1:6379> ZCOUNT myzset 0 1 # 获取score在 [0,1]区间的成员数量
(integer) 1
127.0.0.1:6379> ZCOUNT myzset 0 2
(integer) 2

----------------ZINCRBY--ZSCORE--------------------------
127.0.0.1:6379> ZINCRBY myzset 5 m2 # 将成员m2的score +5
"7"
127.0.0.1:6379> ZSCORE myzset m1 # 获取成员m1的score
"1"
127.0.0.1:6379> ZSCORE myzset m2
"7"

--------------ZRANK--ZRANGE-----------------------------------
127.0.0.1:6379> ZRANK myzset m1 # 获取成员m1的索引,索引按照score排序,score相同索引值按字典顺序顺序增加
(integer) 0
127.0.0.1:6379> ZRANK myzset m2
(integer) 2
127.0.0.1:6379> ZRANGE myzset 0 1 # 获取索引在 0~1的成员
1) "m1"
2) "m3"
127.0.0.1:6379> ZRANGE myzset 0 -1 # 获取全部成员
1) "m1"
2) "m3"
3) "m2"

#testset=>{abc,add,amaze,apple,back,java,redis} score均为0
------------------ZRANGEBYLEX---------------------------------
127.0.0.1:6379> ZRANGEBYLEX testset - + # 返回所有成员
1) "abc"
2) "add"
3) "amaze"
4) "apple"
5) "back"
6) "java"
7) "redis"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 0 3 # 分页 按索引显示查询结果的 0,1,2条记录
1) "abc"
2) "add"
3) "amaze"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 显示 3,4,5条记录
1) "apple"
2) "back"
3) "java"
127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 显示 (-,apple] 区间内的成员
1) "abc"
2) "add"
3) "amaze"
4) "apple"
127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 显示 [apple,java]字典区间的成员
1) "apple"
2) "back"
3) "java"

-----------------------ZRANGEBYSCORE---------------------
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 10 # 返回score在 [1,10]之间的的成员
1) "m1"
2) "m3"
3) "m2"
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 5
1) "m1"
2) "m3"

--------------------ZLEXCOUNT-----------------------------
127.0.0.1:6379> ZLEXCOUNT testset - +
(integer) 7
127.0.0.1:6379> ZLEXCOUNT testset [apple [java
(integer) 3

------------------ZREM--ZREMRANGEBYLEX--ZREMRANGBYRANK--ZREMRANGEBYSCORE--------------------------------
127.0.0.1:6379> ZREM testset abc # 移除成员abc
(integer) 1
127.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java # 移除字典区间[apple,java]中的所有成员
(integer) 3
127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1 # 移除排名0~1的所有成员
(integer) 2
127.0.0.1:6379> ZREMRANGEBYSCORE myzset 0 3 # 移除score在 [0,3]的成员
(integer) 2


# testset=> {abc,add,apple,amaze,back,java,redis} score均为0
# myzset=> {(m1,1),(m2,2),(m3,3),(m4,4),(m7,7),(m9,9)}
----------------ZREVRANGE--ZREVRANGEBYSCORE--ZREVRANGEBYLEX-----------
127.0.0.1:6379> ZREVRANGE myzset 0 3 # 按score递减排序,然后按索引,返回结果的 0~3
1) "m9"
2) "m7"
3) "m4"
4) "m3"
127.0.0.1:6379> ZREVRANGE myzset 2 4 # 返回排序结果的 索引的2~4
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYSCORE myzset 6 2 # 按score递减顺序 返回集合中分数在[2,6]之间的成员
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典区间的成员
1) "java"
2) "back"
3) "apple"
4) "amaze"

-------------------------ZREVRANK------------------------------
127.0.0.1:6379> ZREVRANK myzset m7 # 按score递减顺序,返回成员m7索引
(integer) 1
127.0.0.1:6379> ZREVRANK myzset m2
(integer) 4


# mathscore=>{(xm,90),(xh,95),(xg,87)} 小明、小红、小刚的数学成绩
# enscore=>{(xm,70),(xh,93),(xg,90)} 小明、小红、小刚的英语成绩
-------------------ZINTERSTORE--ZUNIONSTORE-----------------------------------
127.0.0.1:6379> ZINTERSTORE sumscore 2 mathscore enscore # 将mathscore enscore进行合并 结果存放到sumscore
(integer) 3
127.0.0.1:6379> ZRANGE sumscore 0 -1 withscores # 合并后的score是之前集合中所有score的和
1) "xm"
2) "160"
3) "xg"
4) "177"
5) "xh"
6) "188"

127.0.0.1:6379> ZUNIONSTORE lowestscore 2 mathscore enscore AGGREGATE MIN # 取两个集合的成员score最小值作为结果的
(integer) 3
127.0.0.1:6379> ZRANGE lowestscore 0 -1 withscores
1) "xm"
2) "70"
3) "xg"
4) "87"
5) "xh"
6) "93"

应用案例:

  • set排序 存储班级成绩表 工资表排序!
  • 普通消息,1.重要消息 2.带权重进行判断
  • 排行榜应用实现,取Top N测试

三种特殊数据类型

geospatial地理位置

使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用

有效经纬度

  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。

指定单位的参数 unit 必须是以下单位的其中一个:

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。

关于GEORADIUS的参数

通过georadius就可以完成 附近的人功能

withcoord:带上坐标

withdist:带上距离,单位与半径单位相同

COUNT n : 只显示前n个(按距离递增排序)

----------------georadius---------------------
127.0.0.1:6379> GEORADIUS china:city 120 30 500 km withcoord withdist # 查询经纬度(120,30)坐标500km半径内的成员
1) 1) "hangzhou"
2) "29.4151"
3) 1) "120.20000249147415"
2) "30.199999888333501"
2) 1) "shanghai"
2) "205.3611"
3) 1) "121.40000134706497"
2) "31.400000253193539"

------------geohash---------------------------
127.0.0.1:6379> geohash china:city yichang shanghai # 获取成员经纬坐标的geohash表示
1) "wmrjwbr5250"
2) "wtw6ds0y300"

Hyperloglog

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。

因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

其底层使用string数据类型

什么是基数?

数据集中不重复的元素的个数。

应用场景:

网页的访问量(UV):一个用户多次访问,也只能算作一个人。

传统实现,存储用户的id,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,Hyperloglog就能帮助我们利用最小的空间完成。

----------PFADD--PFCOUNT---------------------
127.0.0.1:6379> PFADD myelemx a b c d e f g h i j k # 添加元素
(integer) 1
127.0.0.1:6379> type myelemx # hyperloglog底层使用String
string
127.0.0.1:6379> PFCOUNT myelemx # 估算myelemx的基数
(integer) 11
127.0.0.1:6379> PFADD myelemy i j k z m c b v p q s
(integer) 1
127.0.0.1:6379> PFCOUNT myelemy
(integer) 11

----------------PFMERGE-----------------------
127.0.0.1:6379> PFMERGE myelemz myelemx myelemy # 合并myelemx和myelemy 成为myelemz
OK
127.0.0.1:6379> PFCOUNT myelemz # 估算基数
(integer) 17

Bitmaps(位图)

使用位存储,信息状态只有 0 和 1

Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。

应用场景

签到统计、状态统计

------------setbit--getbit--------------
127.0.0.1:6379> setbit sign 0 1 # 设置sign的第0位为 1
(integer) 0
127.0.0.1:6379> setbit sign 2 1 # 设置sign的第2位为 1 不设置默认 是0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> type sign
string

127.0.0.1:6379> getbit sign 2 # 获取第2位的数值
(integer) 1
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 4 # 未设置默认是0
(integer) 0

-----------bitcount----------------------------
127.0.0.1:6379> BITCOUNT sign # 统计sign中为1的位数
(integer) 4

bitmaps是一串从左到右的二进制串

事务

Redis单条命令式保存原子性的,但是事务不保证原子性!

Redis事务本质: 一组命令的集合!

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

基本操作

一个事务从开始到执行会经历以下三个阶段

  • 开始事务。(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

取消事务

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> 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> setget k1 #错误的命令
(error) ERR unknown command `setget`, with args beginning with: `k1`,
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> EXEC #执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k3
(nil)

==如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!==

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
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
1) OK
2) (error) ERR value is not an integer or out of range #错误命令,但是其他依然执行成功
3) OK
4) OK
5) "v3"

监控

悲观锁

  • 什么情况下都去加锁

乐观锁

  • 一般不是上锁,只有更新数据的时候去判断一下,此期间数据是否改变
  • 获取version
  • 更新的时候比较version

测试

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可以当做redis的乐观锁操作!

127.0.0.1:6379> set m1 100
OK
127.0.0.1:6379> KEYS *
1) "m1"
127.0.0.1:6379> get m1
"100"
127.0.0.1:6379> WATCH m1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY m1 10
QUEUED
127.0.0.1:6379> INCRBY m1 20
QUEUED
127.0.0.1:6379> EXEC #执行前开启另外一个线程修改m1的值为200
(nil)
127.0.0.1:6379> GET m1
"200"

127.0.0.1:6379> UNWATCH		#事务执行失败,先解锁
OK
127.0.0.1:6379> WATCH m1 #然后监视,最新的值
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY m1 10
QUEUED
127.0.0.1:6379> INCRBY m1 20
QUEUED
127.0.0.1:6379> EXEC #对比监视的值,如果没变,一致即可执行成功,反之失败null
1) (integer) 190
2) (integer) 210

Jedis

Java操作redis,是官方推荐的java连接开发工具

Jedis连接测试

1、导入依赖

<!--导入jedis的包-->
<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>

2、编码测试

  • 连接数据库
  • 操作命令
  • 断开连接