万字好文!带你入门 redis
引言|本文将会从:Redis 运用场景与介绍 -> 数据结构与简略运用 -> 小功用大用处 -> 耐久化、主从同步与缓存规划 -> 常识拓展 来书写,初学的童鞋只需能记住 Redis 是用来干嘛,各功用的运用场景有哪些,然后对 Redis 有个大约的认识就好啦,剩下的以后有需求的时分再来检查和实践吧!

一、Redis 介绍

Redis 是什么?

Redis 是一个开源(BSD答应)的,内存中的数据结构存储体系,它能够用作数据库、缓存和音讯中心件。Redis 支撑多种类型的数据结构,如 字符串(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 特性

  • 速度快
    • 单节点读 110000次/s,写81000次/s
    • 数据寄存内存中
    • 用C言语完结,离操作体系更近
    • 单线程架构,6.0开端支撑多线程(CPU、IO读写负荷)
  • 耐久化
    • 数据的更新将异步地保存到硬盘(RDB和 AOF)
  • 多种数据结构
    • 不仅仅支撑简略的 key-value 类型数据,还支撑:字符串、hash、列表、调集、有序调集,
  • 支撑多种编程言语
  • 功用丰富
    • HyperLogLog、GEO、发布订阅、Lua脚本、业务、Pipeline、Bitmaps,key 过期
  • 简略安稳
    • 源码少、单线程模型
  • 主从仿制
  • Redis 支撑数据的备份(master-slave)与集群(分片存储),以及拥有岗兵监控机制。
  • Redis 的一切操作都是原子性的,一同 Redis 还支撑对几个操作兼并后的原子性履行。

Redis 典型运用场景

缓存:

万字好文!带你入门 redis
计数器:
万字好文!带你入门 redis
音讯行列:
万字好文!带你入门 redis
排行榜:
万字好文!带你入门 redis
交际网络:
万字好文!带你入门 redis

Redis 高并发原理

  1. Redis是纯内存数据库,一般都是简略的存取操作,线程占用的时刻许多,时刻的花费首要会集在IO上,所以读取速度快
  2. Redis运用的对错堵塞 IO,IO 多路复用,运用了单线程来轮询描述符,将数据库的开、关、读、写都转化成了事情,削减了线程切换时上下文的切换和竞争。
  3. Redis选用了单线程的模型,确保了每个操作的原子性,也削减了线程的上下文切换和竞争。
  4. Redis存储结构多样化,不同的数据结构对数据存储进行了优化,如紧缩表,对短数据进行紧缩存储,再如,跳表,运用有序的数据结构加快读取的速度。
  5. Redis选用自己完结的事情分离器,功率比较高,内部选用非堵塞的履行办法,吞吐能力比较大。

Redis 装置

这儿只供给 linux 版别的装置布置

  • 下载 Redis

进入官网找到下载地址:redis.io/download![图… Download 按钮,挑选仿制链接地址,然后进入 linux 的 shell 操控台:输入 wget 将上面仿制的下载链接粘贴上,如下指令:

wget https://download.redis.io/releases/redis-6.2.4.tar.gz

回车后等待下载完毕。

  • 解压并装置 Redis

下载完结后需求将紧缩文件解压,输入以下指令解压到当时目录:

tar -zvxf redis-6.2.4.tar.gz

解压后在根目录上输入ls列出一切目录会发现与下载 redis 之前多了一个 redis-6.2.4.tar.gz 文件和 redis-6.2.4 的目录。

万字好文!带你入门 redis

  • 移动 Redis 目录(可选)

若你不想在下载的目录装置 Redis,能够将 Redis 移动到特定目录装置,我习惯放在 ‘/usr/local/’ 目录下,所以我这儿输入指令将现在在 ‘/root’ 目录下的 ‘redis-6.2.4’ 文件夹更改目录,一同修正其名字为 redis:

mv /root/rredis-6.2.4 /usr/local/redis

cd到 ‘/usr/local’ 目录下输入ls指令能够查询到当时目录现已多了一个 redis 子目录,一同 ‘/root’ 目录下现已没有 ‘redis-6.2.4’ 文件:

万字好文!带你入门 redis

  • 编译

cd到 ‘/usr/local/redis’ 目录,输入指令make履行编译指令,接下来操控台会输出各种编译进程中输出的内容:

make

终究运转成果如下:

万字好文!带你入门 redis

  • 装置

输入以下指令:

make PREFIX=/usr/local/redis install

这儿多了一个关键字 ‘PREFIX=’ 这个关键字的作用是编译的时分用于指定程序寄存的路径。比方咱们现在便是指定了 redis 有必要寄存在 ‘/usr/local/redis’ 目录。假定不增加该关键字 linux 会将可履行文件寄存在 ‘/usr/local/bin’ 目录,库文件会寄存在 ‘/usr/local/lib’ 目录。装备文件会寄存在 ‘/usr/local/etc 目录。其他的资源文件会寄存在 ‘usr/local/share’ 目录。这儿指定好目录也便利后续的卸载,后续直接rm -rf /usr/local/redis即可删去 Redis。履行成果如下图:

万字好文!带你入门 redis
到此为止,Redis 现已装置完毕,能够开端运用了~

Redis 发动

依据上面的操作现已将 redis 装置完结了。在目录 ‘/usr/local/redis’ 输入下面指令发动 redis:

./bin/redis-server& ./redis.conf

上面的发动办法是采纳后台进程办法,下面是采纳显现发动办法(如在装备文件设置了 daemonize 特点为 yes 则跟后台进程办法发动其实相同):

./bin/redis-server ./redis.conf

两种办法差异无非是有无带符号&的差异。redis-server 后边是装备文件,意图是依据该装备文件的装备发动 redis 服务。redis.conf 装备文件答应自定义多个装备文件,通过发动时指定读取哪个即可。发动能够概括为:

  • 最简默许发动
    • 装置后在 bin 目录下直接履行 redis-server
    • 验证(ps –aux | grep redis)
  • 动态参数发动(可装备一下参数,例如指定端口)
    • ./bin/redis-server –port 6380
  • 装备文件发动
    • ./bin/redis-server& ./redis.conf
  • 出产环境一般挑选装备发动
  • 单机多实例装备文件能够用端口区分开

注:若在进行 redis 指令操作,直接在 redis 中的 bin 目录下运转 redis-cli 指令即可,若开启了多个则需求加上对应的端口参数:若运转 redis-cli 提示不未装置,则装置一下即可:

万字好文!带你入门 redis

redis.conf 装备文件

在目录 ‘/usr/local/redis’ 下有一个 redis.conf 的装备文件。咱们上面发动办法便是履行了该装备文件的装备运转的。咱们能够通过cat、vim、less等 linux 内置的读取指令读取该文件。这儿罗列下比较重要的装备项:装备项称号装备项值规模说明

daemonize yes、no yes表明启用看护进程,默许是no即不以看护进程办法运转。其间Windows体系下不支撑启用看护进程办法运转
port 指定 Redis 监听端口,默许端口为 6379
bind 绑定的主机地址,假如需求设置长途拜访则直接将这个特点备注下或许改为bind * 即可,这个特点和下面的protected-mode操控了是否能够长途拜访
protected-mode yes 、no 保护办法,该办法操控外部网是否能够衔接redis服务,默许是yes,所以默许咱们外网是无法拜访的,如需外网衔接rendis服务则需求将此特点改为no
loglevel debug、verbose、notice、warning 日志等级,默许为 notice
databases 16 设置数据库的数量,默许的数据库是0。整个通过客户端工具能够看得到
rdbcompression yes、no 指定存储至本地数据库时是否紧缩数据,默许为 yes,Redis 选用 LZF 紧缩,假如为了节省 CPU 时刻,能够封闭该选项,但会导致数据库文件变得巨大
dbfilename dump.rdb 指定本地数据库文件名,默许值为 dump.rdb
dir 指定本地数据库寄存目录
requirepass 设置 Redis 衔接暗码,假如装备了衔接暗码,客户端在衔接 Redis 时需求通过 AUTH指令供给暗码,默许封闭
maxclients 0 设置同一时刻最大客户端衔接数,默许无约束,Redis 能够一同翻开的客户端衔接数为 Redis 进程能够翻开的最大文件描述符数,假如设置 maxclients 0,表明不作约束。当客户端衔接数抵达约束时,Redis 会封闭新的衔接并向客户端回来 max number of clients reached 错误信息
maxmemory XXX 指定 Redis 最大内存约束,Redis 在发动时会把数据加载到内存中,到达最大内存后,Redis 会先尝试铲除已到期或即将到期的 Key,当此办法处理 后,仍然抵达最大内存设置,将无法再进行写入操作,但仍然能够进行读取操作。Redis 新的 vm 机制,会把 Key 寄存内存,Value 会寄存在 swap 区。装备项值规模列里XXX为数值

这儿我要将 daemonize 改为 yes,不然我每次发动都得在 redis-server 指令后边加符号 &,不这样操作则只需回到 linux 操控台则 redis 服务会主动封闭,一同也将 bind 注释,将p rotected-mode 设置为 no。这样发动后我就能够在外网拜访了。修正办法通过 vim 或许你喜爱的办法即可:

vim /usr/local/redis/redis.conf

通过 /daemonize 查找到特点,默许是 no,更改为 yes 即可。(通过/关键字查找呈现多个成果则运用 n 字符切换到下一个即可,按 i 能够开端修正,ESC 退出修正办法,输入:wq指令保存并退出),如下图:其他特点也是相同办法查找和修正即可。
装置布置部分参阅:www.cnblogs.com/hunanzp/p/1…
二、Redis 数据结构与指令运用
Redis 的数据结构有:string(字符串)、hash(哈希)、list(列表)、set(调集)、zset(有序集 合)。但这些仅仅Redis 对外的数据结构,实践上每种数据结构都有自己底层的内部编码完结,而且是多种完结, 这样 Redis 会在适宜的场景挑选适宜的内部编码。

万字好文!带你入门 redis
能够看到每种数据结构都有两种以上的内部编码完结,例如 list 数据结 构包括了 linkedlist 和 ziplist 两种内部编码。一同,有些内部编码,例如 ziplist, 能够作为多种外部数据结构的内部完结,能够通过object encoding指令查询内部编码。

object encoding xxx  # xxx 为键名

Redis 一切的数据结构都是以仅有的 key 字符串作为称号,然后通过这个仅有 key 值来获取相应的 value 数据。不同类型的数据结 构的差异就在于 value 的结构不相同。

通用大局 指令

常用大局指令

  • keys:检查一切键
  • dbsize:键总数
  • exists key:检查键是否存在
  • del key [key …]:删去键
  • expire key seconds:键过期
  • ttl key: 通过 ttl 指令观察键键的剩下过期时刻
  • type key:键的数据结构类型

简略运用截图

万字好文!带你入门 redis
万字好文!带你入门 redis
依据上面的指令解说,咱们应该比较简略看懂截图里边的一切指令意义,这儿就不过多解说了。

字符串运用

字符串 string 是 Redis 最简略的数据结构。Redis 的字符串是动态字符串,是能够修正的字符串,内部结构完结上相似于 Java 的 ArrayList,选用预分配冗余空间的办法来削减内存的频频分配。字符串结构运用十分广泛,一个常见的用处便是缓存用户信息。咱们将用户信息结构体 运用 JSON 序列化成字符串,然后将序列化后的字符串塞进 Redis 来缓存。相同,取用户 信息会通过一次反序列化的进程。

常用字符串指令

  • set key value [ex seconds] [px milliseconds] [nx|xx]: 设置值,回来 ok 表明成功
    • ex seconds:为键设置秒级过期时刻。
    • px milliseconds:为键设置毫秒级过期时刻。
    • nx:键有必要不存在,才干够设置成功,用于增加。可独自用 setnx 指令代替
    • xx:与nx相反,键有必要存在,才干够设置成功,用于更新。可独自用 setxx 指令代替
  • get key:获取值
  • mset key value [key value …]:批量设置值,批量操作指令能够有用进步业务处理功率
  • mget key [key …]:批量获取值,批量操作指令能够有用进步业务处理功率
  • incr key:计数,回来成果分 3 种状况:
    • 值不是整数,回来错误。
    • 值是整数,回来自增后的成果。
    • 键不存在,依照值为0自增,回来成果为1。
  • decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)

字符串简略运用截图

万字好文!带你入门 redis

万字好文!带你入门 redis
依据上面的指令解说,咱们应该比较简略看懂截图里边的一切指令意义,这儿就不过多解说了。

字符串运用场景

  1. 缓存数据,进步查询功用。比方存储登录用户信息、电商中存储产品信息
  2. 能够做计数器(想知道什么时分封闭一个IP地址(拜访超越几次)),短信限流
  3. 同享 Session,例如:一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会形成一个问题,出于负载均衡的考虑,分布式服务会将用户的拜访均衡到不同服务器上,用户改写一次拜访可 能会发现需求从头登录,为了处理这个问题,能够运用Redis将用户的Session进行会集办理,在这种办法下只需确保Redis是高可用和扩展性的,每次用户 更新或许查询登录信息都直接从Redis中会集获取,如图:

万字好文!带你入门 redis
万字好文!带你入门 redis

哈希 hash

哈希相当于Java中的HashMap,以及 Js 中的 Map,内部是无序字典。完结原理跟HashMap共同。一个哈希表有多个节点,每个节点保存一个键值对。与Java中的HashMap不同的是,rehash 的办法不相同,由于 Java 的 HashMap 在字典很大时,rehash 是个耗时的操作,需求一次性悉数 rehash。Redis 为了高功用,不能堵塞服务,所以选用了渐进式 rehash 战略。渐进式 rehash 会在 rehash 的一同,保留新旧两个 hash 结构,查询时会一同查询两个 hash 结构,然后在后续的守时使射中以及 hash 操作指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。当搬迁完结了,就会运用新的hash结构取而代之。当 hash 移除了终究一个元素之后,该数据结构主动被删去,内存被收回。

常用哈希指令

  • hset key field value:设置值
  • hget key field:获取值
  • hdel key field [field …]:删去field
  • hlen key:核算field个数
  • hmset key field value [field value …]:批量设置field-value
  • hmget key field [field …]:批量获取field-value
  • hexists key field:判别field是否存在
  • hkeys key:获取一切field
  • hvals key:获取一切value
  • hgetall key:获取一切的field-value
  • incrbyfloat和hincrbyfloat:就像incrby和incrbyfloat指令相同,可是它们的作 用域是 filed

哈希简略运用截图

万字好文!带你入门 redis

万字好文!带你入门 redis
依据上面的指令解说,咱们应该比较简略看懂截图里边的一切指令意义,这儿相同不过多解说了

哈希运用场景

  1. Hash也能够同于方针存储,比方存储用户信息,与字符串不相同的是,字符串是需求将方针进行序列化(比方 json 序列化)之后才干保存,而 Hash 则能够讲用户方针的每个字段独自存储,这样就能节省序列化和反序列的时刻。如下:

万字好文!带你入门 redis

  1. 此外还能够保存用户的购买记载,比方 key 为用户 id,field 为产品i d,value 为产品数量。相同还能够用于购物车数据的存储,比方 key 为用户 id,field 为产品 id,value 为购买数量等等:

万字好文!带你入门 redis

列表(lists)

Redis 中的 lists 相当于 Java 中的 LinkedList,完结原理是一个双向链表(其底层是一个快速列表),即能够支撑反向查找和遍历,更便利操作。刺进和删去操作十分快,时刻杂乱度为 O(1),可是索引定位很慢,时刻杂乱度为 O(n)。

万字好文!带你入门 redis

常用列表指令

  • rpush key value [value …]:从右边刺进元素
  • lpush key value [value …]:从左面刺进元素
  • linsert key before|after pivot value:向某个元素前或许后刺进元素
  • lrange key start end:获取指定规模内的元素列表,lrange key 0 -1能够从左到右获取列表的一切元素
  • lindex key index:获取列表指定索引下标的元素
  • llen key:获取列表长度
  • lpop key:从列表左侧弹出元素
  • rpop key:从列表右侧弹出
  • lrem key count value:删去指定元素,lrem指令会从列表中找到等于value的元素进行删去,依据count的不同 分为三种状况:
    • count>0,从左到右,删去最多count个元素。
    • count<0,从右到左,删去最多count绝对值个元素。
    • count=0,删去一切。
  • ltrim key start end:依照索引规模修剪列表
  • lset key index newValue:修正指定索引下标的元素
  • blpop key [key …] timeout 和 brpop key [key …] timeout:堵塞式弹出

列表简略运用截图

万字好文!带你入门 redis

万字好文!带你入门 redis

万字好文!带你入门 redis

万字好文!带你入门 redis

万字好文!带你入门 redis
依据上面的指令解说,咱们应该比较简略看懂截图里边的一切指令意义,这儿相同不过多解说了

列表运用场景

  1. 热销榜,文章列表
  2. 完结作业行列(运用lists的push操作,将使命存在lists中,然后作业线程再用pop操作将使命取出进行履行 ),例如音讯行列
  3. 最新列表,比方最新评论

运用参阅:

  • lpush+lpop=Stack(栈)
  • lpush+rpop=Queue(行列)
  • lpsh+ltrim=Capped Collection(有限调集)
  • lpush+brpop=Message Queue(音讯行列)

set 调集和 zset 有序调集

Redis 的调集相当于 Java 言语里边的 HashSet 和 JS 里边的 Set,它内部的键值对是无序的仅有的。Set 调会集终究一个 value 被移除后,数据结构主动删去,内存被收回。zset 或许是 Redis 供给的最为特色的数据结构,它也是在面试中面试官独爱问的数据结构。它相似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,确保了内部 value 的仅有性,另一方面它能够给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部完结用的是一种叫着「跳动列表」(后边会简略介绍)的数据结构。

常用调集指令

  • sadd key element [element …]:增加元素,回来成果为增加成功的元素个数
  • srem key element [element …]:删去元素,回来成果为成功删去元素个数
  • smembers key:获取一切元素
  • sismember key element:判别元素是否在调会集,假如给定元素element在调集内回来1,反之回来0
  • scard key:核算元素个数,scard的时刻杂乱度为O(1),它不会遍历调集一切元素
  • spop key:从调集随机弹出元素,从3.2版别开端,spop也支撑[count]参数。
  • srandmember key [count]:随机从调集回来指定个数元素,[count]是可选参数,假如不写默许为1
  • sinter key [key …]:求多个调集的交集
  • suinon key [key …]:求多个调集的并集
  • sdiff key [key …]:求多个调集的差集

调集简略运用截图

万字好文!带你入门 redis

常用有序调集指令

  • zadd key score member [score member …]:增加成员,回来成果代表成功增加成员的个数。Redis3.2为zadd指令增加了nx、xx、ch、incr四个选项:
    • nx:member有必要不存在,才干够设置成功,用于增加
    • xx:member有必要存在,才干够设置成功,用于更新
    • ch:回来此次操作后,有序调集元素和分数发生改变的个数
    • incr:对score做增加,相当于后边介绍的zincrby
  • zcard key:核算成员个数
  • zscore key member:核算某个成员的分数
  • zrank key member 和 zrevrank key member:核算成员的排名,zrank是从分数从低到高回来排名,zrevrank反之
  • zrem key member [member …]:删去成员
  • zincrby key increment member:增加成员的分数
  • zrange key start end [withscores] 和 zrevrange key start end [withscores]:回来指定排名规模的成员,zrange是从低到高回来,zrevrange反之。
  • zrangebyscore key min max [withscores] [limit offset count] 和 zrevrangebyscore key max min [withscores] [limit offset count] 回来指定分数规模的成员,其间zrangebyscore依照分数从低到高回来,zrevrangebyscore反之
  • zcount key min max:回来指定分数规模成员个数
  • zremrangebyrank key start end:删去指定排名内的升序元素
  • zremrangebyscore key min max:删去指定分数规模的成员
  • zinterstore 和 zunionstore 指令求调集的交集和并集,可用参数比较多,可用到再查文档

有序调集比较调集供给了排序字段,可是也发生了价值,zadd的时刻 杂乱度为O(log(n)),sadd的时刻杂乱度为O(1)。

有序调集简略运用截图

万字好文!带你入门 redis

调集和有序调集运用场景

  1. 给用户增加标签
  2. 给标签增加用户
  3. 依据某个权重进行排序的行列的场景,比方游戏积分排行榜,设置优先级的使命列表,学生成绩表等

万字好文!带你入门 redis

关于跳动列表

跳动列表便是一种层级制,最下面一层一切的元素都会串起来。然后每隔几个元素挑选出一个代表来,再将这几个代表运用别的一级指针串起来。然后在这些代表里再挑出二级代表,再串起来。终究就形成了金字塔结构,如图:更多能够看:www.jianshu.com/p/09c3b0835…

列表、调集和有序调集异同

三、小功用大用处

慢查询剖析

许多存储体系(例如MySQL)供给慢查询日志协助开发和运维人员定位体系存在的慢操作。所谓慢查询日志便是体系在指令履行前后核算每条指令的履行时刻,当超越预设阈值,就将这条指令的相关信息(例如:发生时刻,耗时,指令的详细信息)记载下来,Redis 也供给了相似的功用。这儿能够顺带了解一下 Redis 客户端履行一条指令的进程,分为如下4个部分:

万字好文!带你入门 redis
关于慢查询功用,需求清晰 3 件事:
1、预设阈值怎样设置?在 redis 装备文件中修正装备 ‘slowlog-log-slower-than’ 的值,单位是微妙(1秒 = 1000毫秒 = 1000000微秒),默许是 10000 微秒,假如把 slowlog-log-slower-than 设置为 0,将会记载一切指令到日志中。假如把 slowlog-log-slower-than 设置小于0,将会不记载任何指令到日志中。2、慢查询记载寄存在哪?在 redis 装备文件中修正装备 ‘slowlog-max-len’ 的值。slowlog-max-len 的作用是指定慢查询日志最多存储的条数。实践上,Redis 运用了一个列表寄存慢查询日志,slowlog-max-len 便是这个列表的最大长度。当一个新的指令满意满意慢查询条件时,被刺进这个列表中。当慢查询日志列表现已到达最大长度时,最早刺进的那条指令将被从列表中移出。比方,slowlog-max-len 被设置为 10,当有第11条指令刺进时,在列表中的第1条指令先被移出,然后再把第11条指令放入列表。记载慢查询指 Redis 会对长指令进行切断,不会许多占用许多内存。在实践的出产环境中,为了减缓慢查询被移出的或许和更便利地定位慢查询,主张将慢查询日志的长度调整的大一些。比方能够设置为 1000 以上。除了去装备文件中修正,也能够通过 config set 指令动态修正装备

> config set slowlog-log-slower-than 1000
OK
> config set slowlog-max-len 1200
OK
> config rewrite
OK

3、怎样获取慢查询日志?能够运用slowlog get指令获取慢查询日志,在slowlog get后边还能够加一个数字,用于指定获取慢查询日志的条数,比方,获取2条慢查询日志:

> slowlog get 3
1) 1) (integer) 6107
   2) (integer) 1616398930
   3) (integer) 3109
   4) 1) "config"
      2) "rewrite"
2) 1) (integer) 6106
   2) (integer) 1613701788
   3) (integer) 36004
   4) 1) "flushall"

能够看出每一条慢查询日志都有4个特点组成:

  1. 仅有标识ID
  2. 指令履行的时刻戳
  3. 指令履行时长
  4. 履行的命名和参数

此外,能够通过slowlog len指令获取慢查询日志的长度;通过slowlog reset指令整理慢查询日志。

Pipeline(流水线)机制

Redis 供给了批量操作指令(例如mget、mset等),有用地节省RTT。但大部分指令是不支撑批量操作的,例如要履行 n 次 hgetall 指令,并没有 mhgetall 指令存在,需求耗费 n 次 RTT。Redis 的客户端和服务端或许布置在不同的机器上。例如客户端在北京,Redis 服务端在上海,两地直线间隔约为 1300 公里,那么 1 次 RTT 时刻 = 13002/(3000002/3) = 13 毫秒(光在真空中 传输速度为每秒 30 万公里,这儿假定光纤为光速的 2/3),那么客户端在 1 秒 内大约只能履行 80 次左右的指令,这个和 Redis 的高并发高吞吐特性背道而驰。Pipeline(流水线)机制能改进上面这类问题,它能将一组 Redis 指令进 行拼装,通过一次 RTT 传输给 Redis,再将这组 Redis 指令的履行成果按次序回来给客户端。不运用 Pipeline 的指令履行流程:

万字好文!带你入门 redis
运用 Pipeline 的指令履行流程:
万字好文!带你入门 redis
Redis 的流水线是一种通讯协议,没有办法通过客户端演示给咱们,这儿以 Jedis 为例,通过 Java API 或许运用 Spring 操作它(代码来源于互联网):

/**
 * 测验Redis流水线 
 * @author liu
 */
publicclass TestPipelined {
    /**
     * 运用Java API测验流水线的功用
     */
    @SuppressWarnings({ "unused", "resource" })
    @Test
    public void testPipelinedByJavaAPI() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(20);
        jedisPoolConfig.setMaxTotal(10);
        jedisPoolConfig.setMaxWaitMillis(20000);
        JedisPool jedisPool = new JedisPool(jedisPoolConfig,"localhost",6379);
        Jedis jedis = jedisPool.getResource();
        long start = System.currentTimeMillis();
        // 开启流水线
        Pipeline pipeline = jedis.pipelined();
        // 测验10w条数据读写
        for(int i = 0; i < 100000; i++) {
            int j = i + 1;
            pipeline.set("key" + j, "value" + j);
            pipeline.get("key" + j);
        }
        // 只履行同步但不回来成果
        //pipeline.sync();
        // 以list的办法回来履行过的指令的成果
        List<Object> result = pipeline.syncAndReturnAll();
        long end = System.currentTimeMillis();
        // 核算耗时
        System.out.println("耗时" + (end - start) + "毫秒");
    }
    /**
     * 运用RedisTemplate测验流水线
     */
    @SuppressWarnings({ "resource", "rawtypes", "unchecked", "unused" })
    @Test
    public void testPipelineBySpring() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        RedisTemplate rt = (RedisTemplate)applicationContext.getBean("redisTemplate");
        SessionCallback callback = (SessionCallback)(RedisOperations ops)->{
            for(int i = 0; i < 100000; i++) {
                int j = i + 1;
                ops.boundValueOps("key" + j).set("value" + j);
                ops.boundValueOps("key" + j).get();
            }
            returnnull;
        };
        long start = System.currentTimeMillis();
        // 履行Redis的流水线指令
        List result = rt.executePipelined(callback);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

网上写的测验成果为:运用Java API耗时在550ms到700ms之间,也便是不到1s就完结了10万次读写,运用Spring 耗时在1100ms到1300ms之间。这个与之前一条一条指令运用,1s 内就发送几十几百条(客户端和服务端间隔导致)指令的间隔不是一般的大了。留意,这儿仅仅为了测验功用罢了,当你要履行许多的指令并回来成果的时分,需求考虑 List 方针的巨细,由于它会“吃掉”服务器上许多的内存空间,严峻时会导致内存不足,引发 JVM 溢出反常,所以在作业环境中,是需求读者自己去评价的,能够考虑运用迭代的办法去处理。

业务与 Lua

multi 和 exec 指令

许多状况下咱们需求一次履行不止一个指令,而且需求其一同成功或许失利。为了确保多条指令组合的原子性,Redis 供给了简略的业务功用以及集成 Lua 脚原本处理这个问题。Redis 供给了简略的业务功用,将一组需求一同履行的指令放到 multi 和 exec 两个指令之间。Multi 指令代表业务开端,exec 指令代表业务完毕,它们之间的指令是原子次序履行的。运用事例:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> SET msg "hello chrootliu"
QUEUED
127.0.0.1:6379> GET msg
QUEUED
127.0.0.1:6379> EXEC
1) OK
1) hello chrootliu

Redis 供给了简略的业务,之所以说它简略,首要是由于它不支撑业务中的回滚特性,一同无法完结指令之间的逻辑关系核算,首要有以下几点:

  1. 不行满意原子性。一个业务履行进程中,其他业务或 client 是能够对相应的 key 进行修正的(并发状况下,例如电商常见的超卖问题),想要防止这样的并发性问题就需求运用 WATCH 指令,可是一般来说,有必要通过细心考虑才干决议究竟需求对哪些 key 进行 WATCH 加锁。可是,额定的 WATCH 会增加业务失利的或许,而缺少必要的 WATCH 又会让咱们的程序发生竞争条件。
  2. 后履行的指令无法依靠先履行指令的成果。由于业务中的一切指令都是互相独立的,在遇到 exec 指令之前并没有实在的履行,所以咱们无法在业务中的指令中运用前面指令的查询成果。咱们仅有能够做的便是通过 watch 确保在咱们进行修正时,假如其它业务刚好进行了修正,则咱们的修正中止,然后应用层做相应的处理。
  3. 业务中的每条指令都会与 Redis 服务器进行网络交互。Redis 业务开启之后,每履行一个操作回来的都是 queued,这儿就触及到客户端与服务器端的屡次交互,分明是需求一次批量履行的 n 条指令,还需求通过屡次网络交互,明显十分浪费(这个便是为什么会有 pipeline 的原因,削减 RTT 的时刻)。

Redis 业务缺陷的处理 – Lua

Lua 是一个细巧的脚本言语,用规范 C 编写,几乎在一切操作体系和平台上都能够编译运转。一个完好的 Lua 解说器不过 200k,在现在一切脚本引擎中,Lua 的速度是最快的,这一切都决议了 Lua 是作为嵌入式脚本的最佳挑选。Redis 2.6 版别之后内嵌了一个 Lua 解说器,能够用于一些简略的业务与逻辑运算,也可协助开发者定制自己的 Redis 指令(例如:一次性的履行杂乱的操作,和带有逻辑判别的操作),在这之前,有必要修正源码。在 Redis 中履行Lua脚本有两种办法:eval 和 evalsha,这儿以 eval 做为事例介绍:eval 语法:

eval script numkeys key [key ...] arg [arg ...]

其间:

  • script 一段 Lua 脚本或 Lua 脚本文件地点路径及文件名
  • numkeys Lua 脚本对应参数数量
  • key [key …] Lua 中通过大局变量 KEYS 数组存储的传入参数
  • arg [arg …] Lua 中通过大局变量 ARGV 数组存储的传入附加参数
EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

Lua 履行流程图:

万字好文!带你入门 redis
SCRIPT LOAD 与 EVALSHA 指令
关于不当即履行的 Lua 脚本,或需求重用的 Lua 脚本,能够通过 SCRIPT LOAD 提早载入 Lua 脚本,这个指令会当即回来对应的 SHA1 校验码当需求履行函数时,通过 EVALSHA 调用 SCRIPT LOAD 回来的 SHA1 即可

SCRIPT LOAD "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"
"232fd51614574cf0867b83d384a5e898cfd24e5a"
EVALSHA "232fd51614574cf0867b83d384a5e898cfd24e5a" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"

通过 Lua 脚本履行 Redis 指令在 Lua 脚本中,只需运用redis.call()redis.pcall()传入 Redis 指令就能够直接履行:

eval "return redis.call('set',KEYS[1],'bar')" 1 foo     --等同于在服务端履行 set foo bar

事例,运用 Lua 脚本完结拜访频率约束:

--
-- KEYS[1] 要约束的ip
-- ARGV[1] 约束的拜访次数
-- ARGV[2] 约束的时刻
--
local key = "rate.limit:" .. KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = ARGV[2]
local is_exists = redis.call("EXISTS", key)
if is_exists == 1then
    if redis.call("INCR", key) > limit then
        return0
    else
        return1
    end
else
    redis.call("SET", key, 1)
    redis.call("EXPIRE", key, expire_time)
    return1
end

运用办法,通过:

eval(file_get_contents(storage_path("limit.lua")), 3, "127.0.0.1", "3", "100");

redis 的业务与Lua,就先介绍到这儿了,更多的用法咱们请检查Lua 官方文档

Bitmaps

许多开发言语都供给了操作位的功用,合理地运用位能够有用地进步内存运用率和开发功率。Redis供给了Bitmaps这个“数据结构”能够完结对位的操作。把数据结构加上引号首要由于:

  • Bitmaps本身不是一种数据结构,实践上它便是字符串,可是它能够对字符串的位进行操作。
  • Bitmaps独自供给了一套指令,所以在 Redis 中运用 Bitmaps 和运用字符串的办法不太相同。能够把Bitmaps 幻想成一个以位为单位的数组,数组的每个单元只能存储 0 和 1,数组的下标在 Bitmaps 中叫做偏移量。

在咱们平时开发进程中,会有一些 bool 型数据需求存取,比方用户一年的签到记载, 签了是 1,没签是 0,要记载 365 天。假如运用一般的 key/value,每个用户要记载 365 个,当用户上亿的时分,需求的存储空间是惊人的。为了处理这个问题,Redis 供给了位图数据结构,这样每天的签到记载只占据一个位, 365 天便是 365 个位,46 个字节 (一个稍长一点的字符串) 就能够彻底包容下,这就大大节省了存储空间。语法:

setbit key offset value  # 设置或许清空 key 的 value(字符串)在 offset 处的 bit 值
getbit key offset  # 回来 key 对应的 string 在 offset 处的 bit 值 
bitcount key [start end] # start end 规模内被设置为1的数量,不传递 start end 默许全规模

运用事例,核算用户登录(活泼)状况

127.0.0.1:6379> setbit userLogin:2021-04-10 66666 1 #userId=66666的用户登录,这是今日登录的第一个用户。
(integer) 0
127.0.0.1:6379> setbit userLogin:2021-04-10 999999 1 #userId=999999的用户登录,这是今日第二个登录、的用户。
(integer) 0
127.0.0.1:6379> setbit userLogin:2021-04-10 3333 1
(integer) 0
127.0.0.1:6379> setbit userLogin:2021-04-10 8888 1 
(integer) 0
127.0.0.1:6379> setbit userLogin:2021-04-10 100000 1
(integer) 0
127.0.0.1:6379> getbit active:2021-04-10 66666
(integer) 1
127.0.0.1:6379> getbit active:2021-04-10 55555
(integer) 
127.0.0.1:6379> bitcount active:2021-04-10
(integer) 5

由于 bit 数组的每个方位只能存储 0 或许 1 这两个状况;所以关于实践生活中,处理两个状况的业务场景就能够考虑运用 bitmaps。如用户登录/未登录,签到/未签到,关注/未关注,打卡/未打卡等。一同 bitmap 还通过了相关的核算办法进行快速核算。

HyperLogLog

HyperLogLog 并不是一种新的数据结构(实践类型为字符串类型),而 是一种基数算法,通过 HyperLogLog 能够运用极小的内存空间完结独立总数的核算,数据集能够是IP、Email、ID等。HyperLogLog 供给了3个指令:pfadd、pfcount、pfmerge。

# 用于向 HyperLogLog 增加元素
# 假如 HyperLogLog 估计的近似基数在 PFADD 指令履行之后呈现了改变, 那么指令回来 1 , 不然回来 0 
# 假如指令履行时给定的键不存在, 那么程序将先创立一个空的 HyperLogLog 结构, 然后再履行指令
pfadd key value1 [value2 value3]
# PFCOUNT 指令会给出 HyperLogLog 包括的近似基数
# 在核算出基数后, PFCOUNT 会将值存储在 HyperLogLog 中进行缓存,知道下次 PFADD 履行成功前,就都不需求再次进行基数的核算。
pfcount key
# PFMERGE 将多个 HyperLogLog 兼并为一个 HyperLogLog , 兼并后的 HyperLogLog 的基数接近于一切输入 HyperLogLog 的并集基数。
pfmerge destkey key1 key2 [...keyn]
127.0.0.1:6379> pfadd totaluv user1
(integer) 1
127.0.0.1:6379> pfcount totaluv
(integer) 1
127.0.0.1:6379> pfadd totaluv user2
(integer) 1
127.0.0.1:6379> pfcount totaluv
(integer) 2
127.0.0.1:6379> pfadd totaluv user3
(integer) 1
127.0.0.1:6379> pfcount totaluv
(integer) 3
127.0.0.1:6379> pfadd totaluv user4
(integer) 1
127.0.0.1:6379> pfcount totaluv
(integer) 4
127.0.0.1:6379> pfadd totaluv user5
(integer) 1
127.0.0.1:6379> pfcount totaluv
(integer) 5
127.0.0.1:6379> pfadd totaluv user6 user7 user8 user9 user10
(integer) 1
127.0.0.1:6379> pfcount totaluv
(integer) 10

HyperLogLog内存占用量十分小,可是存在错误率,开发者在进行数据 229结构选型时只需求确认如下两条即可:

  1. 只为了核算独立总数,不需求获取单条数据。
  2. 能够忍受必定误差率,究竟HyperLogLog在内存的占用量上有很大的优势。

例如:假如你担任开发保护一个大型的网站,有一天老板找产品经理要网站每个网页每天的 UV 数据,然后让你来开发这个核算模块,你会怎样完结?假如核算 PV 那十分好办,给每个网页一个独立的 Redis 计数器就能够了,这个计数器 的 key 后缀加上当天的日期。这样来一个恳求,incrby 一次,终究就能够核算出一切的 PV 数据。可是 UV 不相同,它要去重,同一个用户一天之内的屡次拜访恳求只能计数一次。这就 要求每一个网页恳求都需求带上用户的 ID,无论是登录用户仍是未登录用户都需求一个仅有 ID 来标识。你或许现已想到了一个简略的计划,那便是为每一个页面一个独立的 set 调集来存储所 有当天拜访过此页面的用户 ID。当一个恳求过来时,咱们运用 sadd 将用户 ID 塞进去就可 以了。通过 scard 能够取出这个调集的巨细,这个数字便是这个页面的 UV 数据。没错,这是一个十分简略的计划。可是,假如你的页面拜访量十分大,比方一个爆款页面几千万的 UV,你需求一个很大 的 set 调集来核算,这就十分浪费空间。假如这样的页面许多,那所需求的存储空间是惊人 的。为这样一个去重功用就耗费这样多的存储空间,值得么?其实老板需求的数据又不需求 太准确,105w 和 106w 这两个数字关于老板们来说并没有多大差异,So,有没有更好的解 决计划呢?Redis 供给了 HyperLogLog 数据结构便是用来处理 这种核算问题的。HyperLogLog 供给不准确的去重计数计划,尽管不准确可是也不对错常不准确,规范误差是 0.81%,这样的准确度现已能够满意上面的 UV 核算需求了。关于上面的场景,同学们或许有疑问,我或许相同能够运用 HashMap、BitMap 和 HyperLogLog 来处理。关于这三种处理计划,这边做下比照:

  • HashMap:算法简略,核算精度高,关于少量数据主张运用,可是关于许多的数据会占用很大内存空间;
  • BitMap:位图算法,详细内容能够参阅我的这篇文章,核算精度高,尽管内存占用要比 HashMap 少,可是关于许多数据仍是会占用较大内存;
  • HyperLogLog:存在必定误差,占用内存少,安稳占用 12k 左右内存,能够核算 2^64 个元素,关于上面举例的应用场景,主张运用。

发布订阅

Redis供给了根据“发布/订阅”办法的音讯机制,此种办法下,音讯发布者和订阅者不进行直接通讯,发布者客户端向指定的频道(channel)发布消 息,订阅该频道的每个客户端都能够收到该音讯:

万字好文!带你入门 redis
首要对应的 Redis 指令为:

subscribe channel [channel ...] # 订阅一个或多个频道
unsubscribe channel # 退订指定频道
publish channel message # 发送音讯
psubscribe pattern # 订阅指定办法
punsubscribe pattern # 退订指定办法

运用事例:翻开一个 Redis 客户端,如向 TestChanne 说一声 hello:

127.0.0.1:6379> publish TestChanne hello
(integer) 1 # 回来的是接纳这条音讯的订阅者数量

这样音讯就发出去了。发出去的音讯不会被耐久化,也便是有客户端订阅 TestChanne 后只能接纳到后续发布到该频道的音讯,之前的就接纳不到了。翻开另一 Redis 个客户端,这儿假定发送音讯之前就翻开而且订阅了 TestChanne 频道:

127.0.0.1:6379> subscribe TestChanne # 履行上面指令客户端会进入订阅状况
Reading messages... (press Ctrl-C to quit)
1) "subscribe" // 音讯类型
2) "TestChanne" // 频道
3) "hello" // 音讯内容

咱们能够运用 Redis 发布订阅功用,完结的简略 MQ 功用,完结上下游的解耦。不过需求留意了,由于 Redis 发布的音讯不会被耐久化,这就会导致新订阅的客户端将不会收到前史音讯。所以,假如当时的业务场景不能忍受这些缺陷,那仍是用专业 MQ 吧。

GEO

Redis3.2 版别供给了 GEO(地舆信息定位)功用,支撑存储地舆方位信 息用来完结比方邻近方位、摇一摇这类依靠于地舆方位信息的功用,关于需 要完结这些功用的开发者来说是一大福音。GEO 功用是 Redis 的另一位作者 Matt Stancliff 学习 NoSQL 数据库 Ardb 完结的,Ardb 的作者来自我国,它供给了优秀的 GEO 功用。Redis GEO 相关的指令如下:

# 增加一个空间元素,longitude、latitude、member分别是该地舆方位的经度、纬度、成员
# 这儿的成员便是指代详细的业务数据,比方说用户的ID等
# 需求留意的是Redis的纬度有用规模不是[-90,90]而是[-85,85]
# 假如在增加一个空间元素时,这个元素中的menber现已存在key中,那么GEOADD指令会回来0,相当于更新了这个menber的方位信息
GEOADD key longitude latitude member [longitude latitude member]
# 用于增加城市的坐标信息
geoadd cities:locations 117.12 39.08 tianjin 114.29 38.02 shijiazhuang 118.01 39.38 tangshan 115.29 38.51 baoding
# 获取地舆方位信息
geopos key member [member ...]
# 获取天津的坐标
geopos cities:locations tianjin
# 获取两个坐标之间的间隔
# unit代表单位,有4个单位值
  - m (meter) 代表米
  - km (kilometer)代表千米
  - mi (miles)代表英里
  - ft (ft)代表尺
geodist key member1 member2 [unit]
# 获取天津和保定之间的间隔
GEODIST cities:locations tianjin baoding km
# 获取指定方位规模内的地舆信息方位调集,此指令能够用于完结邻近的人的功用
# georadius和georadiusbymember两个指令的作用是相同的,都是以一个地舆方位为中心算出指定半径内的其他地舆信息方位,不同的是georadius指令的中心方位给出了详细的经纬度,georadiusbymember只需给出成员即可。其间radiusm|km|ft|mi是必需参数,指定了半径(带单位),这两个指令有许多可选参数,参数意义如下:
# - withcoord:回来成果中包括经纬度。
# - withdist:回来成果中包括离中心节点方位的间隔。
# - withhash:回来成果中包括geohash,有关geohash后边介绍。
# - COUNT count:指定回来成果的数量。
# - asc|desc:回来成果依照离中心节点的间隔做升序或许降序。
# - store key:将回来成果的地舆方位信息保存到指定键。
# - storedist key:将回来成果离中心节点的间隔保存到指定键。
georadius key longitude latitude radiusm|km|ft|mi [withcoord] [withdist] [withhash] [COUNT count] [asc|desc] [store key] [storedist key]
georadiusbymember key member radiusm|km|ft|mi [withcoord] [withdist] [withhash] [COUNT count] [asc|desc] [store key] [storedist key]
# 获取geo hash
# Redis运用geohash将二维经纬度转化为一维字符串,geohash有如下特点:
# - GEO的数据类型为zset,Redis将一切地舆方位信息的geohash寄存在zset中。
# - 字符串越长,表明的方位更准确,表3-8给出了字符串长度对应的精度,例如geohash长度为9时,精度在2米左右。长度和精度的对应关系,请参阅:https://easyreadfs.nosdn.127.net/9F42_CKRFsfc8SUALbHKog==/8796093023252281390
# - 两个字符串越相似,它们之间的间隔越近,Redis运用字符串前缀匹配算法完结相关的指令。
# - geohash编码和经纬度是能够相互转化的。
# - Redis正是运用有序调集并结合geohash的特性完结了GEO的若干指令。
geohash key member [member ...]
# 删去操作,GEO没有供给删去成员的指令,可是由于GEO的底层完结是zset,所以能够借用zrem指令完结对地舆方位信息的删去。
zrem key member

运用事例,例如咋部门是做直播的,那直播业务一般会有一个“邻近的直播”功用,这儿就能够考虑用 Redis 的 GEO 技能来完结这个功用。数据操作首要有两个:一是主播开播的时分写入主播Id的经纬度,二是主播关播的时分删去主播Id元素。这样就保护了一个具有方位信息的在线主播调集供给给线上检索。咱们详细运用的时分,能够去了解一下 Redis GEO 原理,首要用到了空间索引的算法 GEOHASH 的相关常识,针对索引咱们日常所见都是一维的字符,那么怎样对三维空间里边的坐标点树立索引呢,直接点便是三维变二维,二维变一维。这儿就不再详细论述了。
四、Redis 客户端主流编程言语都有对应的常用 Redis 客户端,例如:

  • java -> Jedis
  • python -> redis-py
  • node -> ioredis

详细运用语法,咱们能够依据自己的需求查找对应的官方文档:Jedis 文档:github.com/redis/jedis… 文档:github.com/redis/redis… 文档:github.com/luin/ioredi…

五、耐久化、主从同步与缓存规划耐久化

Redis 支撑 RDB 和 AOF 两种耐久化机制,耐久化功用有用地防止因进程 退出形成的数据丢掉问题,当下次重启时运用之前耐久化的文件即可完结数据康复。

  • RDB 是一次全量备份,AOF 日志是接连的增量备份, RDB 是内存数据的二进制序列化办法,在存储上十分紧凑,而 AOF 日志记载的是内存数据修正的指令记载文本。
  • AOF 以独立日志的办法记载每次写指令, 重启时再从头履行 AOF 文件中的指令到达康复数据的意图。AOF 的首要作用 是处理了数据耐久化的实时性,现在现已是 Redis 耐久化的主流办法。

AOF 日志在长时刻的运转进程中会变的无比庞大,数据库重启时需求加载 AOF 日志进行指令重放,这个时刻就会无比绵长。所以需求定期进行 AOF 重写,给 AOF 日志进行减肥。

RDB

咱们知道 Redis 是单线程程序,这个线程要一同担任多个客户端套接字的并发读写操作和内存数据结构的逻辑读写。在服务线上恳求的一同,Redis 还需求进行内存 RDB,内存 RDB 要求 Redis 有必要进行文件 IO 操作,可文件 IO 操作是不能运用多路复用 API。这意味着单线程一同在服务线上的恳求还要进行文件 IO 操作,文件 IO 操作会严峻拖垮服务器恳求的功用。还有个重要的问题是为了不堵塞线上的业务,就需求边耐久化边呼应客户端恳求。耐久化的一同,内存数据结构还在改变,比方一个大型的 hash 字典正在耐久化,成果一个恳求过来把它给删掉了,还没耐久化完呢,这可怎样办?那该怎样办呢? Redis 运用操作体系的多进程 COW(Copy On Write) 机制来完结 RDB 耐久化,以下为 RDB 备份流程:

万字好文!带你入门 redis

  1. 履行 bgsave 指令,Redis 父进程判别当时是否存在正在履行的子进 程,如 RDB/AOF 子进程,假如存在bgsave指令直接回来。
  2. 父进程履行 fork 操作创立子进程,fork 操作进程中父进程会堵塞,通 过 info stats 指令检查 latest_fork_usec 选项,能够获取最近一个 fork 操作的耗 时,单位为微秒。
  3. 父进程fork完结后,bgsave 指令回来 “Background saving started” 信息 并不再堵塞父进程,能够持续呼应其他指令。
  4. 子进程创立 RDB 文件,依据父进程内存生成暂时快照文件,完结后 对原有文件进行原子替换。履行lastsave指令能够获取终究一次生成 RDB 的 时刻,对应 info 核算的 rdb_last_save_time 选项。
  5. 进程发送信号给父进程表明完结,父进程更新核算信息,详细见 info Persistence下的 rdb_* 相关选项。

AOF

AOF 日志存储的是 Redis 服务器的次序指令序列,AOF 日志只记载对内存进行修正的 指令记载。假定 AOF 日志记载了自 Redis 实例创立以来一切的修正性指令序列,那么就能够通过 对一个空的 Redis 实例次序履行一切的指令,也便是「重放」,来康复 Redis 当时实例的内 存数据结构的状况。Redis 会在收到客户端修正指令后,先进行参数校验,假如没问题,就当即将该指令文本存储到 AOF 日志中,也便是先存到磁盘,然后再履行指令。这样即便遇到突发宕机,现已存储到 AOF 日志的指令进行重放一下就能够康复到宕机前的状况。通过 appendfsync 参数能够 操控实时/秒级耐久化 。AOF流程:

万字好文!带你入门 redis

  1. 一切的写入指令会追加到aof_buf(缓冲区)中。
  2. AOF缓冲区依据对应的战略向硬盘做同步操作。
  3. 跟着AOF文件越来越大,需求定期对AOF文件进行重写,到达紧缩的意图。
  4. 当Redis服务器重启时,能够加载AOF文件进行数据康复。

Redis 在长时刻运转的进程中,AOF 的日志会越变越长。假如实例宕机重启,重放整个 AOF 日志会十分耗时,导致长时刻 Redis 无法对外供给服务。所以需求对 AOF 日志减肥。Redis 供给了 bgrewriteaof 指令用于对 AOF 日志进行减肥。其原理便是拓荒一个子进程对内存进行遍历转化成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就当即代替旧的 AOF 日志文件了,减肥作业就完结了。AOF减肥重写流程:

万字好文!带你入门 redis
AOF 重写能够通过 auto-aof-rewrite-min-siz e和 auto-aof-rewrite- percentage 参数操控主动触发,也能够运用 bgrewriteaof 指令手动触发。
子进程履行期间运用 copy-on-write 机制与父进程同享内存,防止内 存耗费翻倍。AOF 重写期间还需求保护重写缓冲区,保存新的写入指令防止 数据丢掉。单机下布置多个实例时,为了防止呈现多个子进程履行重写操作, 主张做阻隔操控,防止 CPU 和 IO 资源竞争。

Redis 4.0 混合耐久化

重启 Redis 时,咱们很少运用 RDB 来康复内存状况,由于会丢掉许多数据。咱们一般 运用 AOF 日志重放,可是重放 AOF 日志功用相对 rdb 来说要慢许多,这样在 Redis 实例很大的状况下,发动需求花费很长的时刻。Redis 4.0 为了处理这个问题,带来了一个新的耐久化选项——混合耐久化。将 RDB 文 件的内容和增量的 AOF 日志文件存在一同。这儿的 AOF 日志不再是全量的日志,而是自 耐久化开端到耐久化完毕的这段时刻发生的增量 AOF 日志,一般这部分 AOF 日志很小。所以在 Redis 重启的时分,能够先加载 RDB 的内容,然后再重放增量 AOF 日志就可 以彻底代替之前的 AOF 全量文件重放,重启功率因而大幅得到提高。

万字好文!带你入门 redis

主从同步—简略了解

许多企业都没有运用到 Redis 的集群,可是至少都做了主从。有了主从,当 master 挂 掉的时分,运维让从库过来接管,服务就能够持续,不然 master 需求通过数据康复和重启的进程,这就或许会拖很长的时刻,影响线上业务的持续服务。Redis 通过主从同步功用完结主节点的多个副本。从节点可灵敏地通过 slaveof 指令树立或断开同步流程。同步仿制分为:全量仿制和部分增量仿制主从节点之间保护心跳和偏移量检查机制,确保主从节点通讯正常和数据共同。Redis 为了确保高功用仿制进程是异步的,写指令处理完后直接回来给客户端,不等待从节点仿制完结。因而从节点数据聚会有推迟状况。即当运用从节点用于读写分离时会存在数据推迟、过期数据、从节点可用性等问题,需求依据本身业务提早作出规避。留意:在运维进程中,主节点存在多个从节点或许一台机器上布置许多主节点的状况下,会有仿制风暴的风险。**Redis Sentinel(岗兵) **主从仿制是 Redis 分布式的基础,Redis 的高可用离开了主从仿制将无从进行。后边的咱们会讲到 Redis 的集群办法,集群办法都依靠于本节所讲的主从仿制。不过仿制功用也不是有必要的,假如你将 Redis 只用来做缓存,也就无需求从库做备份,挂掉了从头发动一下就行。可是只需你运用了 Redis 的耐久化 功用,就有必要认真对待主从仿制,它是体系数据安全的基础保障。举例:假如主节点清晨 3 点突发宕机怎样办?就坐等运维从床上爬起来,然后手艺进行从主切换,再通知一切的程 序把地址通通改一遍从头上线么?毫无疑问,这样的人工运维功率太低,事端发生时估计得 至少 1 个小时才干缓过来。Sentinel 担任持续监控主从节点的健康,当主节点挂掉时,主动挑选一个最优的从节点切换为主节点。客户端来衔接集群时,会首要衔接 sentinel,通过 sentinel 来查询主节点的地址, 然后再去衔接主节点进行数据交互。当主节点发生毛病时,客户端会从头向 sentinel 要地址,sentinel 会将最新的主节点地址告诉客户端。如此应用程序将无需重启即可主动完结节点切换。如图:

万字好文!带你入门 redis

万字好文!带你入门 redis

音讯丢掉

Redis 主从选用异步仿制,意味着当主节点挂掉时,从节点或许没有收到悉数的同步音讯,这部分未同步的音讯就丢掉了。假如主从推迟特别大,那么丢掉的数据就或许会特别 多。Sentinel 无法确保音讯彻底不丢掉,可是也尽或许确保音讯少丢掉。它有两个选项能够 约束主从推迟过大:

  • min-slaves-to-write 1
  • min-slaves-max-lag 10

第一个参数表明主节点有必要至少有一个从节点在进行正常仿制,不然就中止对外写服务,损失可用性。何为正常仿制,何为反常仿制?这个便是由第二个参数操控的,它的单位是秒,表明假如 10s 没有收到从节点的反应,就意味着从节点同步不正常,要么网络断开了,要么一向没有给反应。

Redis 终究共同

Redis 的主从数据是异步同步的,所以分布式的 Redis 体系并不满意「共同性」要求。当客户端在 Redis 的主节点修正了数据后,当即回来,即便在主从网络断开的状况下,主节 点仍旧能够正常对外供给修正服务,所以 Redis 满意「可用性」。Redis 确保「终究共同性」,从节点会努力追赶主节点,终究从节点的状况会和主节点 的状况将坚持共同。假如网络断开了,主从节点的数据将会呈现许多不共同,一旦网络恢 复,从节点会选用多种战略努力追赶上落后的数据,持续尽力坚持和主节点共同。

缓存

缓存的收益与本钱

收益:

  • 加快读写:CPU L1/L2/L3 Cache、浏览器缓存等。由于缓存一般都是全内存的(例如 Redis、Memcache),而 存储层一般读写功用不行强悍(例如 MySQL),通过缓存的运用能够有用 地加快读写,优化用户体验。
  • 下降后端负载:协助后端削减拜访量和杂乱核算,在很大程度下降了后端的负载。本钱:
  • 数据不共同:缓存层和数据层有时刻窗口不共同,和更新战略有关。
  • 代码保护本钱:参加缓存后,需求一同处理缓存层和存储层的逻辑, 增大了开发者保护代码的本钱。
  • 运维本钱:以 Redis Cluster 为例,参加后无形中增加了运维本钱。运用场景:
  • 下降后端负载:对高耗费的 SQL:join 成果集/分组核算成果缓存。
  • 加快恳求呼应:运用 Redis/Memcache 优化 IO 呼应时刻。
  • 许多写兼并为批量写:比方计数器先 Redis 累加再批量写入 DB。

缓存更新战略—算法除掉

  • LRU:Least Recently Used,最近最少运用。
  • LFU:Least Frequently Used,最不常常运用。
  • FIFO:First In First Out,先进先出。

运用场景:除掉算法一般用于缓存运用量超越了预设的最大值时分,怎样对现有的数据进行除掉。例如 Redis 运用 maxmemory-policy 这个装备作为内存最大值后关于数据的除掉战略。共同性:要整理哪些数据是由详细算法决议,开发人员只能决议运用哪种算法,所以数据的共同性是最差的。保护本钱:算法不需求开发人员自己来完结,一般只需求装备最大 maxmemory 和对应的战略即可。

缓存更新战略—超时除掉

运用场景:超时除掉通过给缓存数据设置过期时刻,让其在过期时刻后主动删去,例如 Redis 供给的 expire 指令。假如业务能够忍受一段时刻内,缓存层数据和存储层数据不共同,那么能够为其设置过期时刻。在数据过期后,再从实在数据源获取数据,从头放到缓存并设置过期时刻。共同性:一段时刻窗口内(取决于过期时刻长短)存在共同性问题,即缓存数据和实在数据源的数据不共同。保护本钱:保护本钱不是很高,只需设置 expire 过期时刻即可,当然条件是应用方答应这段时刻或许发生的数据不共同。

缓存更新战略—主动更新

运用场景:应用方关于数据的共同性要求高,需求在实在数据更新后, 当即更新缓存数据。例如能够运用音讯体系或许其他办法通知缓存更新。共同性:共同性最高,但假如主动更新发生了问题,那么这条数据很或许很长时刻不会更新,所以主张结合超时除掉一同运用作用会更好。保护本钱:保护本钱会比较高,开发者需求自己来完结更新,并确保更新操作的正确性。

缓存更新战略—总结

万字好文!带你入门 redis
低共同性业务:主张装备最大内存和筛选战略的办法运用。
高共同性业务:能够结合运用超时除掉和主动更新,这样即便主动更新出了问题,也能确保数据过期时刻后删去脏数据。

缓存或许会遇到的问题

缓存穿透:指查询一个必定不存在的数据,由于缓存是不射中时被动写的,而且出于容错考虑,假如从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次恳求都要到存储层去查询,失去了缓存的意义。在流量大时,或许 DB 就挂掉了,要是有人运用不存在的 key 频频进犯咱们的应用,这便是漏洞。处理办法:

  • 布隆过滤器,将一切或许存在的数据哈希到一个足够大的 bitmap 中,一个必定不存在的数据会被 这个 bitmap 拦截掉,然后防止了对底层存储体系的查询压力。
  • 别的也有一个更为简略粗犷的办法(咱们选用的便是这种),假如一个查询回来的数据为空(不管是数 据不存在,仍是体系毛病),咱们仍然把这个空成果进行缓存,但它的过期时刻会很短,最长不超越五分钟。

缓存雪崩:指在咱们设置缓存时选用了相同的过期时刻,导致缓存在某一时刻一同失效,恳求悉数转发到 DB,DB 瞬时压力过重雪崩。处理办法:咱们能够在原有的失效时刻基础上增加一个随机值,比方1-5分钟随机,这样每一个缓存的过期时刻的重复率就会下降,就很难引发集体失效的事情。缓存击穿:关于一些设置了过期时刻的 key,假如这些 key 或许会在某些时刻点被超高并发地拜访,是一种十分“热点”的数据。这个时分,需求考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的差异在于这儿针对某一 key 缓存,前者则是许多 key。缓存在某个时刻点过期的时分,刚好在这个时刻点对这个 Key 有许多的并发恳求过来,这些恳求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时分大并发的恳求或许会瞬间把后端 DB 压垮。处理办法:互斥锁、永久不过期设置、资源保护等等。缓存无底洞问题:Facebook 的作业人员反应2010年已到达3000个 memcached 节点,贮存数千G的缓存。他们发现一个问题– memcached 的衔接功率下降了,所以增加 memcached 节点,增加完之后,并没有好转。称为“无底洞”现象。原因:客户端一次批量操作会触及屡次网络操作,也就意味着批量操作会跟着实例的增多,耗时会不断增大。服务端网络衔接次数变多,对实例的功用也有必定影响。即:更多的机器不代表更多的功用,所谓“无底洞”便是说投入越多不必定产出越多。处理计划有:串行 mget、串行 IO、并行 IO、Hash tag 完结等,更多请看:缓存无底洞问题(ifeve.com/redis-multi…

六、常识拓展

缓存与数据库同步战略 (怎样确保缓存(Redis)与数据库(MySQL)的共同性?)

关于热点数据(常常被查询,但不常常被修正的数据),咱们一般会将其放入 Redis 缓存中,以增加查询功率,但需求确保从 Redis 中读取的数据与数据库中存储的数据终究是共同的,这便是经典的缓存与数据库同步问题。那么,怎样确保缓存(Redis)与数据库(MySQL)的共同性呢?依据缓存是删去仍是更新,以及操作次序大约是能够分为下面四种状况:

  1. 先更新数据库,再更新缓存
  2. 先更新缓存,再更新数据库
  3. 先删去缓存,再更新数据库
  4. 先更新数据库,再删去缓存

删去缓存比照更新缓存

  • 删去缓存: 数据只会写入数据库,不会写入缓存,只会删去缓存
  • 更新缓存: 数据不光写入数据库,还会写入缓存

删去缓存

  • 优点:操作简略,无论更新操作是否杂乱,直接删去,而且能防止更新呈现的线程安全问题
  • 缺陷:删去后,下一次查询无法在 cache 中查到,会有一次 Cache Miss,这时需求从头读取数据库,高并发下或许会呈现上面说的缓存问题

更新缓存

  • 优点:射中率高,直接更新缓存,不会有 Cache Miss 的状况
  • 缺陷:更新缓存耗费较大,尤其在杂乱的操作流程中

那到底是挑选更新缓存仍是删去缓存呢,首要取决于更新缓存的杂乱度

  • 更新缓存的价值很小,此刻咱们应该更倾向于更新缓存,以确保更高的缓存射中率
  • 更新缓存的价值很大,此刻咱们应该更倾向于删去缓存

例如:仅仅简略的更新一下用户积分,只操作一个字段,那就能够选用更新缓存,还有相似秒杀下产品库存数量这种并发下查询频频的数据,也能够运用更新缓存,不过也要留意线程安全的问题,防止发生脏数据。可是当更新操作的逻辑较杂乱时,需求触及到其它数据,如用户购买产品付款时,需求考虑打折、优惠券、红包等多种要素,这样需求缓存与数据库进行屡次交互,将打折等信息传入缓存,再与缓存中的其它值进行核算才干得到终究成果,此刻更新缓存的耗费要大于直接筛选缓存。所以仍是要依据业务场景来进行挑选,不过大部分场景下删去缓存操作简略,而且带来的副作用仅仅增加了一次 Cache Miss,主张作为通用的处理办法。

先更新数据库,再更新缓存

这种办法就适合更新缓存的价值很小的数据,例如上面说的用户积分,库存数量这类数据,相同仍是要留意线程安全的问题。
线程安全角度一同有恳求 A 和恳求 B 进行更新操作,那么会呈现

  1. 线程 A 更新了数据库
  2. 线程 B 更新了数据库
  3. 线程 B 更新了缓存
  4. 线程 A 更新了缓存

这就呈现恳求 A 更新缓存应该比恳求 B 更新缓存早才对,可是由于网络等原因,B 却比 A 更早更新了缓存,这就导致了脏数据。
业务场景角度有如下两种不适合场景:

  1. 假如你是一个写数据库场景比较多,而读数据场景比较少的业务需求,选用这种计划就会导致,数据压根还没读到,缓存就被频频的更新,浪费功用
  2. 假如你写入数据库的值,并不是直接写入缓存的,而是要通过一系列杂乱的核算再写入缓存。那么,每次写入数据库后,都再次核算写入缓存的值,无疑是也浪费功用的

先更新缓存,再更新数据库

这种状况应该是和第一种状况相同会存在线程安全问题的,可是这种状况是有人运用过的,依据书籍《淘宝技能这十年》里,多隆把产品详情页放入缓存,采纳的正是先更新缓存,再将缓存中的数据异步更新到数据库这种办法,有兴趣了解的能够检查这篇博客:www.cnblogs.com/rjzheng/p/9…

先删去缓存,再更新数据库

简略的想一下,好像这种办法不错,就算是第一步删去缓存成功,第二步写数据库失利,则只会引发一次 Cache Miss,对数据没有影响,其实细心一想并发下也很简略导致了脏数据,例如

  1. 恳求 A 进行写操作,删去缓存
  2. 恳求 B 查询发现缓存不存在
  3. 恳求 B 去数据库查询得到旧值
  4. 恳求 B 将旧值写入缓存
  5. 恳求 A 将新值写入数据库

那怎样处理呢,先看第四种状况(先更新数据库,再删去缓存),后边再统一说第三种和第四种的处理计划。

先更新数据库,再删去缓存

先说一下,国外有人提出了一个缓存更新套路,名为 Cache-Aside Pattern:docs.microsoft.com/en-us/azure…

  • 失效:应用程序先从 cache 取数据,没有得到,则从数据库中取数据,成功后,放到缓存中
  • 射中:应用程序从 cache 中取数据,渠道后回来
  • 更新:先把数据存到数据库中,成功后再让缓存失效

更新操作便是先更新数据库,再删去缓存;读取操作先从缓存取数据,没有,则从数据库中取数据,成功后,放到缓存中;这是规范的规划计划,包括 Facebook 的论文 Scaling Memcache at Facebook:chrome-extension://ikhdkkncnoglghljlkmcimlnlhkeamad/pdf-viewer/web/viewer.html?file=https%3A%2F%2Fwww.usenix.org%2Fsystem%2Ffiles%2Fconference%2Fnsdi13%2Fnsdi13-final170_update.pdf 也运用了这个战略。为什么他们都用这种办法呢,这种状况不存在并发问题么?答案是也存在,可是呈现概率比第三种低,例如:

  1. 恳求缓存刚好失效
  2. 恳求A查询数据库,得一个旧值
  3. 恳求B将新值写入数据库
  4. 恳求B删去缓存
  5. 恳求A将查到的旧值写入缓存

这样就呈现脏数据了,可是,实践上呈现的概率或许十分低,由于这个条件需求发生在读缓存时缓存失效,而且并发着有一个写操作。而实践上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作删去缓存,一切的这些条件都具有的概率根本并不大,可是仍是会有呈现的概率。而且假如第一步写数据库成功,第二步删去缓存失利,这样也导致脏数据,请看处理计划。

计划三四脏数据处理计划

那怎样处理呢,能够选用延时双删战略(缓存双筛选法) ,能够将前面所形成的缓存脏数据,再次删去

  1. 先删去(筛选)缓存
  2. 再写数据库(这两步和本来相同)
  3. 休眠1秒,再次删去(筛选)缓存

或许是

  1. 先写数据库
  2. 再删去(筛选)缓存(这两步和本来相同)
  3. 休眠1秒,再次删去(筛选)缓存

这个1秒应该看你的业务场景,应该自行评价自己的项意图读数据业务逻辑的耗时,然后写数据的休眠时刻则在读数据业务逻辑的耗时基础上,加几百毫秒即可,这么做确保读恳求完毕,写恳求能够删去读恳求形成的缓存脏数据。假如你用了 MySql 的读写分离架构怎样办?,例如:

  1. 恳求 A 进行写操作,删去缓存
  2. 恳求 A 将数据写入数据库了,(或许是先更新数据库,后删去缓存)
  3. 恳求 B 查询缓存发现,缓存没有值
  4. 恳求 B 去从库查询,这时,还没有完结主从同步,因而查询到的是旧值
  5. 恳求 B 将旧值写入缓存
  6. 数据库完结主从同步,从库变为新值

这种情景,便是数据不共同的原因,仍是选用延时双删战略(缓存双筛选法),仅仅,休眠时刻修正为在主从同步的延时时刻基础上,加几百毫秒而且为了功用更快,能够把第二次删去缓存能够做成异步的,这样不会堵塞恳求了,假如再严谨点,防止第二次删去缓存失利,这个异步删去缓存能够加上重试机制,失利一向重试,直到成功。 这儿给出两种重试机制参阅计划一

  1. 更新数据库数据
  2. 缓存由于种种问题删去失利
  3. 将需求删去的 key 发送至音讯行列
  4. 自己消费音讯,取得需求删去的 key
  5. 持续重试删去操作,直到成功

万字好文!带你入门 redis
可是,该计划有一个缺陷,对业务线代码形成许多的侵入,所以有了计划二,发动一个订阅程序去订阅数据库的Binlog,取得需求操作的数据。在应用程序中,另起一段程序,取得这个订阅程序传来的信息,进行删去缓存操作
计划二

  1. 更新数据库数据
  2. 数据库会将操作信息写入 binlog 日志傍边
  3. 订阅程序提取出所需求的数据以及key
  4. 另起一段非业务代码,取得该信息
  5. 尝试删去缓存操作,发现删去失利
  6. 将这些信息发送至音讯行列
  7. 从头从音讯行列中取得该数据,重试操作

万字好文!带你入门 redis
上述的订阅 Binlog 程序在 MySql 中有现成的中心件叫 Canal,能够完结订阅 Binlog 日志的功用,别的,重试机制,这儿选用的是音讯行列的办法。假如对共同性要求不是很高,直接在程序中另起一个线程,每隔一段时刻去重试即可,这些咱们能够灵敏自由发挥,仅仅供给一个思路。
**总结:**大部分应该运用的都是第三种或第四种办法,假如都是选用延时双删战略(缓存双筛选法),或许差异不会很大,不过第四种办法呈现脏数据概率是更小点,更多的话仍是要结合本身业务场景运用,灵敏变通。

分布式锁

例如一个操作要修正用户的状况,修正状况需求先读出用户的状况,在内存里进行修 改,改完了再存回去。假如这样的操作一同进行了,就会呈现并发问题,由于读取和保存状 态这两个操作不是原子的。(Wiki 解说:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开端,就一向运转到完毕,中心不会有任何 context switch 线程切换。)如图:

万字好文!带你入门 redis
这个时分就要运用到分布式锁来约束程序的并发履行。
分布式锁本质上要完结的方针便是在 Redis 里边占一个“茅坑”,当别的进程也要来占 时,发现现已有人蹲在那里了,就只好抛弃或许稍后再试。占坑一般是运用 setnx(set if not exists) 指令,只答应被一个客户端占坑。先来先占, 用 完了,再调用 del 指令开释茅坑。

setnx lock:codehole true
OK
 ... do something critical ... 
del lock:codehole
(integer) 1

可是有个问题,假如逻辑履行到中心呈现反常了,或许会导致 del 指令没有被调用,这样 就会陷入死锁,锁永久得不到开释。所以咱们在拿到锁之后,再给锁加上一个过期时刻,比方 5s,这样即便中心呈现反常也 能够确保 5 秒之后锁会主动开释。

setnx lock:codehole true
OK 
> expire lock:codehole 5 ... 
do something critical ... 
> del lock:codehole
 (integer) 1

假如在 setnx 和 expire 之间服务器进程忽然挂掉了,或许是由于机器掉电或许是被人为杀掉的,就会导致 expire 得不到履行,也会形成死锁。这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。假如这两条指令可 以一同履行就不会呈现问题。或许你会想到用 Redis 业务来处理。可是这儿不行,由于 expire 是依靠于 setnx 的履行成果的,假如 setnx 没抢到锁,expire 是不应该履行的。业务里没有 if else 分支逻辑,业务的特点是一口气履行,要么悉数履行要么一个都不履行。Redis 2.8 版别中作者参加了 set 指令的扩展参数,使得 setnx 和 expire 指令能够一同履行:

set lock:codehole trueex 5 nx
OK 
... do something critical ... 
del lock:codehole

上面这个指令便是 setnx 和 expire 组合在一同的原子指令,它便是分布式锁的奥义地点。

分布式锁存在的问题

超时问题:假如在加锁和开释锁之间的逻辑履行的太长,以至于超出了锁的超时约束,就会呈现问题。由于这时分锁过期了,第二个线程从头持有了这把锁,可是紧接着第一个线程履行完了业务逻辑,就把锁给开释了,第三个线程就会在第二个线程逻辑履行完之间拿到了锁。单节点的分布式锁问题:在单 Matste 的主从 Matster-Slave Redis 体系中,正常状况下 Client 向 Master 获取锁之后同步给 Slave,假如 Client 获取锁成功之后 Master 节点挂掉,而且未将该锁同步到Slave,之后在Sentinel的协助下Slave晋级为Master可是并没有之前未同步的锁的信息,此刻假如有新的 Client 要在新 Master 获取锁,那么将或许呈现两个Client持有同一把锁的问题,来看个图来想下这个进程:

万字好文!带你入门 redis
所以,为了确保自己的锁只能自己开释需求增加仅有性的校验,综上根据单 Redis 节点的获取锁和开释锁的简略进程如下:

// 获取锁 unique_value作为仅有性的校验
SET resource_name unique_value NX PX 30000
// 开释锁 比较unique_value是否相等 防止误开释
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

关于分布式锁的 Redlock 算法

Redis 功用好而且完结便利,可是单节点的分布式锁在毛病迁移时发生安全问题,Redlock 算法是 Redis 的作者 Antirez 提出的集群办法分布式锁,根据 N 个彻底独立的 Redis 节点完结分布式锁的高可用。在 Redis 的分布式环境中,咱们假定有 N 个彻底互相独立的 Redis 节点,在 N 个 Redis 实例上运用与在 Redis 单实例下相同办法获取锁和开释锁。现在假定有5个 Redis 主节点(大于3的奇数个),这样根本确保他们不会一同都宕掉,获取锁和开释锁的进程中,客户端会履行以下操作:

  1. 获取当时 Unix 时刻,以毫秒为单位
  2. 依次尝试从5个实例,运用相同的key和具有仅有性的value获取锁 当向 Redis 恳求获取锁时,客户端应该设置一个网络衔接和呼应超时时刻,这个超时时刻应该小于锁的失效时刻,这样能够防止客户端死等
  3. 客户端运用当时时刻减去开端获取锁时刻就得到获取锁运用的时刻。当且仅当从半数以上的 Redis 节点取到锁,而且运用的时刻小于锁失效时刻时,锁才算获取成功
  4. 假如取到了锁,key 的实在有用时刻等于有用时刻减去获取锁所运用的时刻,这个很重要
  5. 假如由于某些原因,获取锁失利(没有在半数以上实例取到锁或许取锁时刻现已超越了有用时刻),客户端应该在一切的 Redis 实例上进行解锁,无论 Redis 实例是否加锁成功,由于或许服务端呼应音讯丢掉了可是实践成功了,究竟多开释一次也不会有问题

关于集群

在大数据高并发场景下,单个 Redis 实例往往会显得绰绰有余。首要体现在内存上,单个 Redis 的内存不宜过大,内存太大会导致 rdb 文件过大,进一步导致主从同步时全量同步时刻过长,在实例重启康复时也会耗费很长的数据加载时刻,特别是在云环境下,单个实例内存往往都是受限的。其次体现在 CPU 的运用率上,单个 Redis 实例只能运用单个核心,这单个核心要完结海量数据的存取和办理作业压力会十分大。所以孕育而生了 Redis 集群,集群计划首要有以下几种:Sentinel:Sentinel(岗兵)办法,根据主从仿制办法,仅仅引入了岗兵来监控与主动处理毛病Codis:Codis 是 Redis 集群计划之一,令咱们感到自豪的是,它是我国人开发并开源的,来自前豌豆荚中心件团队。Cluster:Redis Cluster 是 Redis 的亲儿子,它是 Redis 作者自己供给的 Redis 集群化计划。

腾讯工程师技能干货直达:

  1. 快保藏!最全GO言语完结规划办法【下】

  2. 怎样成为优秀工程师之软技能篇

  3. 怎样更好地运用Kafka?

  4. 从鹅厂实例出发!剖析Go Channel底层原理

阅览原文