厚积薄发打卡Day61 :【狂神】Redis详细教程(中)<从数据类型到 Jedis>

视频教程:【狂神说Java】Redis最新超详细版教程通俗易懂

Redis五大数据类型

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指令,实测中文页面比较友好,反应快:http://www.redis.cn/commands.html

以下的所有指令都可以通过在此页面搜索学习

在这里插入图片描述

Redis-Key

127.0.0.1:6379> set name wayne 
OK
127.0.0.1:6379> set age 23
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> move name 1
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name2 wayne2
OK
127.0.0.1:6379> keys *
1) "name2"
2) "age"
127.0.0.1:6379> del name2
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> expire age 10
(integer) 1
127.0.0.1:6379> ttl age
(integer) 6
127.0.0.1:6379> ttl age
(integer) 1
127.0.0.1:6379> ttl age
(integer) -2
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set test test
OK
127.0.0.1:6379> type test
string

中断了一天的学习,今天重开WLS发现用Xshell连接不上了,重翻笔记才发现需要重新开启ssh命令才行: sudo service ssh start

在这里插入图片描述

String

String键值对是Redis中最基础的使用,也是最常用的使用,一定要会。

####################################################################
127.0.0.1:6379> set name wayne #追加,如果追加的key不存在,则新建
OK
127.0.0.1:6379> append name _wwww
(integer) 10
127.0.0.1:6379> get name 
"wayne_wwww"
127.0.0.1:6379> STRLEN name #获取key对应值的长度
(integer) 10

####################################################################
127.0.0.1:6379> set views 0 #初始浏览量为0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> INCR views #自+1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> DECR views
(integer) 0
127.0.0.1:6379> get views #自-1
"0"
127.0.0.1:6379> INCRBY views 10 #自+10
(integer) 10
127.0.0.1:6379> get views
"10"
127.0.0.1:6379> DECRBY views 5 #自-5
(integer) 5
127.0.0.1:6379> get views
"5"

####################################################################
127.0.0.1:6379> set key1 hello,wayne
OK
127.0.0.1:6379> get key1
"hello,wayne"
127.0.0.1:6379> GETRANGE key1 0 4
"hello"
127.0.0.1:6379> GETRANGE key1 0 -1  #截取字符串
"hello,wayne"
127.0.0.1:6379> set key2 arrow
OK
127.0.0.1:6379> SETRANGE key2 1 b #替换   SETRANGE key offset value
(integer) 5
127.0.0.1:6379> get key2
"abrow"


####################################################################
# setex (set with expire) 设置过期时间
# setnx (set if not exist) 不存在才设置(在分布式锁中会常常使用)
#   有点像cas那感觉了,不存在才存入
27.0.0.1:6379> setex key3 30 "hello"  #设置过期时间
OK
127.0.0.1:6379> ttl key3
(integer) 24
127.0.0.1:6379> setnx mykey "redis" #此时mykey不存在才能设置成功
(integer) 1
127.0.0.1:6379> keys *
1) "key1"
2) "mykey"
3) "key2"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "MongoDB" #此时mykey已存在设置不成功
(integer) 0
127.0.0.1:6379> get mykey
"redis"

####################################################################
#批量set,批量获取,批量不存在set
#MSET会用新的value替换已经存在的value,就像普通的SET命令一样。如果你不想覆盖已经存在的values,请参看命令MSETNX。
#MSET是原子的,所以所有给定的keys是一次性set的。客户端不可能看到这种一部分keys被更新而另外的没有改变的情况。
127.0.0.1:6379> mset k1 v1 k2 v2
OK
127.0.0.1:6379> mget k1 k2
1) "v1"
2) "v2"
127.0.0.1:6379> MSETNX k1 v2 k3 v3
(integer) 0
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
####################################################################
#getset 先get然后再set,先获取再覆盖
#自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。
127.0.0.1:6379> getset key1 redis #如果不存在则返回nil
(nil)
127.0.0.1:6379> get key1
"redis"
127.0.0.1:6379> getset key1 mongodb
"redis"
127.0.0.16379> get key1
"mongodb"
####################################################################

String类似的使用场景:value除了是我们的字符串还可以是我们的数字

扩展博客:string类型的应用场景 —— Redis实战经验

  1. 计数器

    string类型的incr和decr命令的作用是将key中储存的数字值加一/减一,这两个操作具有原子性,总能安全地进行加减操作,因此可以用string类型进行计数,如微博的评论数、点赞数、分享数,抖音作品的收藏数,京东商品的销售量、评价数等。

  2. 分布式锁

    string类型的setnx的作用是“当key不存在时,设值并返回1,当key已经存在时,不设值并返回0”,“判断key是否存在”和“设值”两个操作是原子性地执行的,因此可以用string类型作为分布式锁,返回1表示获得锁,返回0表示没有获得锁。

    例如,为了保证定时任务的高可用,往往会同时部署多个具备相同定时任务的服务,但是业务上只希望其中的某一台服务执行定时任务,当定时任务的时间点触发时,多个服务同时竞争一个分布式锁,获取到锁的执行定时任务,没获取到的放弃执行定时任务。

    定时任务执行完时通过del命令删除key即释放锁,如果担心del命令操作失败而导致锁一直未释放,可以通过expire命令给锁设置一个合理的自动过期时间,确保即使del命令失败,锁也能被释放。

在这里插入图片描述

List

在redis里面可以通过list,将队列完成,栈、队列,阻塞队列等数据结构

在这里插入图片描述

# 将所有指定的值插入到存于 key 的列表的头部。如果 key 不存在,那么在进行 push 操作前会创建一个空列表。 如果 key 对应的值不是一个 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

# 可以使用一个命令把多个元素 push 进入列表,只需在命令末尾加上多个指定的参数。元素是从最左端的到最右端的、一个接一个被插入到 list 的头部。 (可看作入栈)
127.0.0.1:6379> LRANGE list 0 -1
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 four #将一个值或多个值,插入到列表尾部(右)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"

####################################################
#LPOP key 移除并且返回 key 对应的 list 的第一个元素:从左侧移除一个值
#RPOP key 移除并返回存于 key 的 list 的最后一个元素:从右侧移除一个值

#LREM key count value 从key中移除count个value
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> LPOP list
"three"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3) "four"
127.0.0.1:6379> RPOP list
"four"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
####################################################
LINDEX 通过下角标求值
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> LINDEX list 0
"two"

127.0.0.1:6379> LRANGE list 0 -1
1) "one"
2) "one"
3) "two"
4) "one"
127.0.0.1:6379> LREM list 1 one #从list中移除1个one
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
2) "two"
3) "one"

####################################################
LLEN 求list长度
LTRIM key start end  #截取

####################################################
RPOPLPUSH source destaintion #移除列表最后一个元素,将它移至目标key中
127.0.0.1:6379> RPUSH list "hello1"
(integer) 1
127.0.0.1:6379> RPUSH list "hello2"
(integer) 2
127.0.0.1:6379> RPUSH list "hello3"
(integer) 3
127.0.0.1:6379> RPOPLPUSH list otherlist
"hello3"
127.0.0.1:6379> LRANGE list 0 -1
1) "hello1"
2) "hello2"
127.0.0.1:6379> LRANGE otherlist 0 -1
1) "hello3"
####################################################
LSET key index value 将列表中指定下标的值替换
127.0.0.1:6379> LRANGE list 0 -1
1) "hello1"
2) "hello2"
127.0.0.1:6379> LSET list 0 "aa"
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "aa"
2) "hello2"
####################################################
LINSERT key before|after pivot value  #指定字符前后插入值
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "word"
127.0.0.1:6379> LINSERT list before word find
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "find"
3) "word"
127.0.0.1:6379> LINSERT list after word yeah
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "find"
3) "word"
4) "yeah"

在这里插入图片描述

小结

  • 他实际上是一个链表,before Node after , left,right 都可以插入值
  • 如果key 不存在,创建新的链表,如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在!
  • 在两边插入或者改动值,效率最高! 中间元素,相对来说效率会低一点~
  • 消息排队!消息队列 (Lpush Rpop), 栈( Lpush Lpop)!

Set

集合:set中的值是不能重复的!

#SADD key [member....]  向set中插入值(可多个) :set-add
#SMEMBERS key 查看key中有哪些值:set-members
#SISMEMBER key value  查看key中是否存在这个值:set-is-member

127.0.0.1:6379> SADD myset hello fine word
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "fine"
2) "word"
3) "hello"
127.0.0.1:6379> SISMEMBER myset hello
(integer) 1
127.0.0.1:6379> SISMEMBER myset WWW
(integer) 0
#################################################
#SCARD key 查看set中值的数量:返回集合存储的key的基数 (集合元素的数量).SET-CARD
#SREM key value 移除key中某一个值 SET-REMOVE
127.0.0.1:6379> SCARD myset
(integer) 3
127.0.0.1:6379> SREM myset word
(integer) 1
#################################################
# SMOVE key otherkey member  移除指定元素至另一个key中:SET-MOVE
127.0.0.1:6379> SMOVE myset myotnerset hello
(integer) 1
#################################################
#SDIFF key1 key2 差集 :SET-DIFFERENCE
#SINTER key1 key2 交集 :set-internal
#SUNION key1 key2 并集  :set-union
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 d
(integer) 1
127.0.0.1:6379> SADD key2 e
(integer) 1
127.0.0.1:6379> SDIFF key1 key2
1) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2
1) "c"
127.0.0.1:6379> SUNION key1 key2
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"

Hash

(哈希) Map集合,HashMap集合,key-map! 时候这个值是一个map集合! 本质和String类型没有太大区别,还是一个简单的key-vlaue,命令特点:Hash-命令

#HSET key filed value #hash存值
#HGET key filed #hash取值
#HMSET key [filed value ...] #批量存
#HMGET key [filed...] #批量取
#HGETALL key #取全部
#HDEL key [filed...] #删除某一个或某几个
#HLEN key #获取长度
#HEXISTS key value #获取某个值是否存在
#HKEYS key #获取所有的filed
#HVALS key #获取所有的value
127.0.0.1:6379> HSET myhash name wayne 
(integer) 1
127.0.0.1:6379> HGET myhash name
"wayne"
127.0.0.1:6379> HMSET myhash name wayne1 age 18
OK
127.0.0.1:6379> HMGET myhash name age
1) "wayne1"
2) "18"
127.0.0.1:6379> HGETALL myhash
1) "name"
2) "wayne1"
3) "age"
4) "18"
127.0.0.1:6379> HDEL myhash name
(integer) 1
127.0.0.1:6379> HLEN myhash
(integer) 1
127.0.0.1:6379> HEXISTS myhash age
(integer) 1

Zset

在set的基础上,增加了一个值

Hello.Zis as in XYZ, so the idea is, sets with another dimension: the order. It’s a far association… I know 😃

# ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
#将所有指定成员添加到键为key有序集合(sorted set)里面。 添加时可以指定多个分数/成员(score/member)对。 如果指定添加的成员已经是有序集合里面的成员,则会更新改成员的分数(scrore)并更新到正确的排序位置。

#如果key不存在,将会创建一个新的有序集合(sorted set)并将分数/成员(score/member)对添加到有序集合,就像原来存在一个空的有序集合一样。
#如果key存在,但是类型不是有序集合,将会返回一个错误应答。
#分数值是一个双精度的浮点型数字字符串。+inf和-inf都是有效值。

127.0.0.1:6379> ZADD myset 100 xiaoming
(integer) 1
127.0.0.1:6379> ZADD myset 200 xiaohong
(integer) 1
127.0.0.1:6379> ZADD myset 300 xiaowang
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE myset -inf +inf  #显示全部的用户,从小到大
1) "xiaoming"
2) "xiaohong"
3) "xiaowang"
127.0.0.1:6379> ZRANGEBYSCORE myset -inf +inf withscores #显示全部的用户,从小到大并附带值
1) "xiaoming"
2) "100"
3) "xiaohong"
4) "200"
5) "xiaowang"
6) "300"
127.0.0.1:6379> ZREVRANGE myset 0 -1  #显示全部的用户,从大到小
1) "xiaowang"
2) "xiaohong"
3) "xiaoming"
127.0.0.1:6379> ZREVRANGE myset 0 -1 withscores
1) "xiaowang"
2) "300"
3) "xiaohong"
4) "200"
5) "xiaoming"
6) "100"

127.0.0.1:6379> ZRANGEBYSCORE myset -inf 200 withscores #显示小于等于200全部的用户,从小到大并附带值
1) "xiaoming"
2) "100"
3) "xiaohong"
4) "200"

三大特殊数据类型

geospatial

地理位置:

Redis 的 Geo 在Redis3.2 版本就推出了! 这个功能可以推算地理位置的信息,两地之间的距离,方圆
几里的人!
可以查询一些测试数据:http://www.jsons.cn/lngcode/

官方文档:https://www.redis.net.cn/order/3685.html

详见文档,本文基于视频操作

在这里插入图片描述

  • GEOADD

    # geoadd 添加地理位置
    # 规则:两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
    # 有效的经度从-180度到180度。
    # 有效的纬度从-85.05112878度到85.05112878度。
    # 当坐标位置超出上述指定范围时,该命令将会返回一个错误。
    # 127.0.0.1:6379> geoadd china:city 39.90 116.40 beijin
    # 报错 (error) ERR invalid longitude,latitude pair 39.900000,116.400000
    # 参数 key 值()
    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
    127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqi 114.05 22.52 shengzhen
    (integer) 2
    127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
    (integer) 2
    
  • GEOPOS

    # 获得当前定位:一定是一个坐标值!
    127.0.0.1:6379> GEOPOS china:city beijing  # 获取指定的城市的经度和纬度!
    1) 1) "116.39999896287918091"
       2) "39.90000009167092543"
    127.0.0.1:6379> GEOPOS china:city beijing chongqi
    1) 1) "116.39999896287918091"
       2) "39.90000009167092543"
    2) 1) "106.49999767541885376"
       2) "29.52999957900659211"
    
  • GEODIST

    两个位置之间的距离!
    单位:

    • m 表示单位为米。
    • km 表示单位为千米。
    • mi 表示单位为英里。
    • ft 表示单位为英尺。
    127.0.0.1:6379> GEODIST china:city beijing shanghai km  # 查看上海到北京的直线距离
    "1067.3788"
    127.0.0.1:6379> GEODIST china:city beijing chongqi km  # 查看重庆到北京的直线距离
    "1464.0708"
    
  • GEORADIUS

    以给定的经纬度为中心, 找出某一半径内的元素。

    如:【我附近的人】功能(获得所有附近的人的地址,定位!)通过半径来查询!

    获得指定数量的人,200

    所有数据应该都录入:china:city ,才会让结果更加请求!

    127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km  # 以110,30 这个经纬度为中心,寻找方圆1000km内的城市
    1) "chongqi"
    2) "xian"
    3) "shengzhen"
    4) "hangzhou"
    127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
    1) "chongqi"
    2) "xian"
    127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist  # 显示到中间距离的位置
    1) 1) "chongqi"
     2) "341.9374"
    2) 1) "xian"
     2) "483.8340"
    127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord  # 显示他人的定位信息
    1) 1) "chongqi"
     2) 1) "106.49999767541885376"
       2) "29.52999957900659211"
    2) 1) "xian"
     2) 1) "108.96000176668167114"
       2) "34.25999964418929977"
    127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1  #筛选出指定的结果!
    1) 1) "chongqi"
     2) "341.9374"
     3) 1) "106.49999767541885376"
       2) "29.52999957900659211"
    127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
    1) 1) "chongqi"
     2) "341.9374"
     3) 1) "106.49999767541885376"
       2) "29.52999957900659211"
    2) 1) "xian"
     2) "483.8340"
     3) 1) "108.96000176668167114"
       2) "34.25999964418929977"
    
  • GEORADIUSBYMEMBER

    # 找出位于指定元素周围的其他元素!
    127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
    1) "beijing"
    2) "xian"
    127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
    1) "hangzhou"
    2) "shanghai
    
  • GEOHASH

    # 该命令将返回11个字符的Geohash字符串!
    # 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近!
    127.0.0.1:6379> geohash china:city beijing chongqi
    1) "wx4fbxxfke0"
    2) "wm5xzrybty0"
    
  • GEO底层实现:

    GEO 底层的实现原理其实就是 Zset!我们可以使用Zset命令来操作geo!

    127.0.0.1:6379> ZRANGE china:city 0 -1  # 查看地图中全部的元素
    1) "chongqi"
    2) "xian"
    3) "shengzhen"
    4) "hangzhou"
    5) "shanghai"
    6) "beijing"
    127.0.0.1:6379> zrem china:city beijing  # 移除指定元素!
    (integer) 1
    127.0.0.1:6379> ZRANGE china:city 0 -1
    1) "chongqi"
    2) "xian"
    3) "shengzhen"
    4) "hangzhou"
    5) "shanghai"
    

Hyperloglog

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。
基数估计就是在误差可接受的范围内,快速计算基数。

概念:

Redis HyperLogLog

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

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

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

应用举例:

网页的 UV UserVist(一个人访问一个网站多次,但是还是算作一个人!)

传统的方式, set 保存用户的id,然后就可以统计 set 中的元素数量作为标准判断 !

这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id;
0.81% 错误率! 统计UV任务,可以忽略不计的!

测试使用:

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  # 统计 mykey 元素的基数数量
(integer) 10
127.0.0.1:6379> PFadd mykey2 i j z x c v b n m  # 创建第二组元素 mykey2
(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

如果允许容错,那么一定可以使用 Hyperloglog !
如果不允许容错,就使用 set 或者自己的数据类型即可!

Bitmap

BitMap是什么

就是通过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身。我们知道8个bit可以组成一个Byte,
Bitmap 位图,是一种特殊的数据结构! 都是操作二进制位来进行记录,就只有0 和 1 两个状态!
所以bitmap本身会极大的节省储存空间。

Redis中的BitMap

Redis从2.2.0版本开始新增了setbit,getbit,bitcount等几个bitmap相关命令。虽然是新命令,但是并没有新增新的数据类型,因为setbit等命令只不过是在set上的扩展。

打卡测试:

在这里插入图片描述

使用bitmap 来记录 周一到周日的打卡!
周一:1 周二:0 周三:0 周四:1 …
在这里插入图片描述

查看某一天是否有打卡!

127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0

统计操作,统计 打卡的天数!

127.0.0.1:6379> bitcount sign  # 统计这周的打卡记录,就可以看到是否有全勤!
(integer) 3

扩展:

  • Redis中bitmap的妙用

    1. 使用场景一:用户签到

      思路见上述

    2. 使用场景二:统计活跃用户

      • 使用时间作为cacheKey,然后用户ID为offset,如果当日活跃过就设置为1
      • 那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个redis的命令
      • 命令 BITOP operation destkey key [key ...]
      • 说明:
        • 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
        • BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数
    3. 使用场景三:用户在线状态

      • 需求:提供了一个查询当前用户是否在线的接口。
      • 使用bitmap是一个节约空间效率又高的一种方法,只需要一个key,然后用户ID为offset,如果在线就设置为1,不在线就设置为0,和上面的场景一样,5000W用户只需要6MB的空间。
  • 一看就懂系列之 详解redis的bitmap在亿级项目中的应用

事务

Redis之Redis事务

Redis 事务

概念:

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

------ 队列 set set set 执行------

Redis事务没有没有隔离级别的概念!

所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Exec

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

Redis事务的三个阶段:

  • 开启事务(multi)
  • 命令入队(…)
  • 执行事务(exec)

Redis事务相关命令:

watch key1 key2 … : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )

multi : 标记一个事务块的开始( queued )

exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )

discard : 取消事务,放弃事务块中的所有命令

unwatch : 取消watch对所有key的监控

正常执行事务:

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  # 执行事务
1) OK
2) OK
3) "v2"
4) O

放弃事务

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 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 k3   # 错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec  # 执行事务报错!
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5  # 所有的命令都不会被执行!
(nil)
运行时异常

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

运行时异常,执行正常的:因此说明没有原子性

已经买来了 跑的时候发现坏了,拿去修,其他照样用

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
1) (error) ERR value is not an integer or out of range  # 虽然第一条命令报错了,但是
依旧正常执行成功了!
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

事务监控

监控! Watch (面试常问!)watch 属于一种乐观锁

  • 悲观锁:
    • 很悲观,认为什么时候都会出问题,无论做什么都会加锁!
  • 乐观锁:
    • 很乐观,认为什么时候都不会出问题,所以不会上锁! 更新数据的时候去判断一下,在此期间是否有人修改过这个数据,
    • 获取version
    • 更新的时候比较 version

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
1) (integer) 80
2) (integer) 20

测试多线程修改值 , 使用watch 可以当做redis的乐观锁操作!

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)

如果修改失败,获取最新的值就好

在这里插入图片描述

Jedis

Jedis 对 Redis 的操作详解

Jedis 是 Redis 官方推荐的 java连接开发工具! 使用Java 操作Redis 中间件!如果你要使用
java操作redis,那么一定要对Jedis 十分的熟悉!不过实际工作中将会有封装成一个具体的utils类。

测试步骤:

  1. 导入依赖:

    <!--导入jedis的包-->
    <dependencies>
      <!-- https://mvnrepository.com/artifact/redis.clients/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>
    </dependencies>
    
  2. 代码测试:

    • 连接数据库
    • 操作命令
    • 断开连接!
    import redis.clients.jedis.Jedis;
    public class TestPing {
      public static void main(String[] args) {
        // 1、 new Jedis 对象即可
        Jedis jedis = new Jedis("127.0.0.1",6379);
        // jedis 所有的命令就是我们之前学习的所有指令!所以之前的指令学习很重要!
        System.out.println(jedis.ping());
     }
    }
    

    输出:

    在这里插入图片描述

常用的API

所有的api命令,就是我们对应的上面学习的指令,一个都没有变化!

举例最常见的String操作:

@Test
public void testString() throws InterruptedException
    {
        jedis.flushDB();
        System.out.println("===========增加数据===========");
        System.out.println(jedis.set("key1","value1"));
        System.out.println(jedis.set("key2","value2"));
        System.out.println(jedis.set("key3", "value3"));
        System.out.println("删除键key2:"+jedis.del("key2"));
        System.out.println("获取键key2:"+jedis.get("key2"));
        System.out.println("修改key1:"+jedis.set("key1", "value1Changed"));
        System.out.println("获取key1的值:"+jedis.get("key1"));
        System.out.println("在key3后面加入值:"+jedis.append("key3", "End"));
        System.out.println("key3的值:"+jedis.get("key3"));
        System.out.println("增加多个键值对:"+jedis.mset("key01","value01","key02","value02","key03","value03"));
        System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));
        System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03","key04"));
        System.out.println("删除多个键值对:"+jedis.del(new String[]{"key01","key02"}));
        System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));

        jedis.flushDB();
        System.out.println("===========新增键值对防止覆盖原先值==============");
        System.out.println(jedis.setnx("key1", "value1"));
        System.out.println(jedis.setnx("key2", "value2"));
        System.out.println(jedis.setnx("key2", "value2-new"));
        System.out.println(jedis.get("key1"));
        System.out.println(jedis.get("key2"));

        System.out.println("===========新增键值对并设置有效时间=============");
        System.out.println(jedis.setex("key3", 2, "value3"));
        System.out.println(jedis.get("key3"));
        TimeUnit.SECONDS.sleep(3);
        System.out.println(jedis.get("key3"));

        System.out.println("===========获取原值,更新为新值==========");//GETSET is an atomic set this value and return the old value command.
        System.out.println(jedis.getSet("key2", "key2GetSet"));
        System.out.println(jedis.get("key2"));

        System.out.println("获得key2的值的字串:"+jedis.getrange("key2", 2, 4));
    }

输出:

===========增加数据===========
OK
OK
OK
删除键key2:1
获取键key2:null
修改key1:OK
获取key1的值:value1Changed
在key3后面加入值:9
key3的值:value3End
增加多个键值对:OK
获取多个键值对:[value01, value02, value03]
获取多个键值对:[value01, value02, value03, null]
删除多个键值对:2
获取多个键值对:[null, null, value03]
===========新增键值对防止覆盖原先值==============
1
1
0
value1
value2
===========新增键值对并设置有效时间=============
OK
value3
null
===========获取原值,更新为新值==========
value2
key2GetSet
获得key2的值的字串:y2G

事务:

public class TestTX {
  public static void main(String[] args) {
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    jedis.flushDB();
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("hello","world");
    jsonObject.put("name","kuangshen");
    // 开启事务
    Transaction multi = jedis.multi();
    String result = jsonObject.toJSONString();
          // jedis.watch(result)
    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(); // 关闭连接
   }
 }
}

踩坑

连接云服务器时:

  • Linux开放端口号命令:

    firewall-cmd --zone=public --add-port=6379/tcp --permanent  && firewall-cmd --reload
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值