Redis 的数据结构有哪些?
string
- 字符串类型是 Redis 最基础的数据结构,键都是字符串类型,而且其他几种数据结构都是在字符串类型的基础上构建的。字符串类型的值可以实际可以是字符串(简单的字符串、复杂的字符串如 JSON、XML)、数字(整形、浮点数)、甚至二进制(图片、音频、视频),但是值最大不能超过 512 MB。
命令
设置值
set key value [ex seconds] [px millseconds] [nx|xx]
- ex seconds:为键设置秒级过期时间,跟 setex 效果一样
- px millseconds:为键设置毫秒级过期时间
- nx:键必须不存在才可以设置成功,用于添加,跟 setnx 效果一样。由于 Redis 的单线程命令处理机制,如果多个客户端同时执行,则只有一个客户端能设置成功,可以用作分布式锁的一种实现。
- xx:键必须存在才可以设置成功,用于更新
获取值
get key,如果不存在返回 nil
批量设置值
mset key value [key value...]
批量获取值
mget key [key...]
批量操作命令可以有效提高开发效率,假如没有 mget,执行 n 次 get 命令需要 n 次网络时间 + n 次命令时间,使用 mget 只需要 1 次网络时间 + n 次命令时间。Redis 可以支持每秒数万的读写操作,但这指的是 Redis 服务端的处理能力,对于客户端来说一次命令处理命令时间还有网络时间。因为 Redis 的处理能力已足够高,对于开发者来说,网络可能会成为性能瓶颈。
计数
incr key
incr 命令用于对值做自增操作,返回结果分为三种:① 值不是整数返回错误。② 值是整数,返回自增后的结果。③ 值不存在,按照值为 0 自增,返回结果 1。除了 incr 命令,还有自减 decr、自增指定数字 incrby、自减指定数组 decrby、自增浮点数 incrbyfloat。
应用场景
缓存功能
Redis 作为缓存层,MySQL 作为存储层,首先从 Redis 获取数据,如果失败就从 MySQL 获取并将结果写回 Redis 并添加过期时间。
计数
Redis 可以实现快速计数功能,例如视频每播放一次就用 incy 把播放数加 1。
共享 Session
一个分布式 Web 服务将用户的 Session 信息保存在各自服务器,但会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问负载到不同服务器上,用户刷新一次可能会发现需要重新登陆。为解决该问题,可以使用 Redis 将用户的 Session 进行集中管理,在这种模式下只要保证 Redis 是高可用和扩展性的,每次用户更新或查询登录信息都直接从 Redis 集中获取。
限速
例如为了短信接口不被频繁访问会限制用户每分钟获取验证码的次数或者网站限制一个 IP 地址不能在一秒内访问超过 n 次。可以使用键过期策略和自增计数实现。
hash
- 哈希类型指键值本身又是一个键值对结构
命令
设置值
hset key field value,如果设置成功会返回 1,反之会返回 0,此外还提供了 hsetnx 命令,作用和 setnx 类似,只是作用于由键变为 field。
获取值
hget key field,如果不存在会返回 nil。
删除 field
hdel key field [field...],会删除一个或多个 field,返回结果为删除成功 field 的个数。
计算 field 个数
hlen key
批量设置或获取 field-value
hmget key field [field...]
hmset key field value [field value...]
判断 field 是否存在
hexists key field,存在返回 1,否则返回 0。
获取所有的 field
hkeys key,返回指定哈希键的所有 field。
获取所有 value
hvals key,获取指定键的所有 value。
获取所有的 field-value
hgetall key,获取指定键的所有 field-value。
应用场景
缓存用户信息,每个用户属性使用一对 键值对,但只用一个键保存。
优点:简单直观,如果合理使用可以减少内存空间使用。
缺点:要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,hashtable 会消耗更多内存。
list
-
list 是用来存储多个有序的字符串,列表中的每个字符串称为元素,一个列表最多可以存储 2^32^-1 个元素。可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发中有很多应用场景。
-
list 有两个特点:
- ① 列表中的元素是有序的,可以通过索引下标获取某个元素或者某个范围内的元素列表。
- ② 列表中的元素可以重复。
命令
添加
从右边插入元素:rpush key value [value...]
从左到右获取列表的所有元素:lrange 0 -1
从左边插入元素:lpush key value [value...]
向某个元素前或者后插入元素:linsert key before|after pivot value,会在列表中找到等于 pivot 的元素,在其前或后插入一个新的元素 value。
查找
获取指定范围内的元素列表:lrange key start end,索引从左到右的范围是 0N-1,从右到左是 -1-N,lrange 中的 end 包含了自身。
获取列表指定索引下标的元素:lindex key index,获取最后一个元素可以使用 lindex key -1。
获取列表长度:llen key
删除
从列表左侧弹出元素:lpop key
从列表右侧弹出元素:rpop key
删除指定元素:lrem key count value,如果 count 大于 0,从左到右删除最多 count 个元素,如果 count 小于 0,从右到左删除最多个 count 绝对值个元素,如果 count 等于 0,删除所有。
按照索引范围修剪列表:ltrim key start end,只会保留 start ~ end 范围的元素。
修改
修改指定索引下标的元素:lset key index newValue。
阻塞操作
阻塞式弹出:blpop/brpop key [key...] timeout,timeout 表示阻塞时间。
当列表为空时,如果 timeout = 0,客户端会一直阻塞,如果在此期间添加了元素,客户端会立即返回。
如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回。
如果多个客户端对同一个键执行 brpop,那么最先执行该命令的客户端可以获取弹出的值。
应用场景
消息队列
Redis 的 lpush + brpop 即可实现阻塞队列,生产者客户端使用 lpush 从列表左侧插入元素,多个消费者客户端使用 brpop 命令阻塞式地抢列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
文章列表
每个用户有属于自己的文章列表,现在需要分页展示文章列表,就可以考虑使用列表。因为列表不但有序,同时支持按照索引范围获取元素。每篇文章使用哈希结构存储。
lpush + lpop = 栈、lpush + rpop = 队列、lpush + ltrim = 优先集合、lpush + brpop = 消息队列。
set
- 集合类型也是用来保存多个字符串元素,和列表不同的是集合不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。一个集合最多可以存储 2^32^-1 个元素。Redis 除了支持集合内的增删改查,还支持多个集合取交集、并集、差集。
命令
添加元素
sadd key element [element...],返回结果为添加成功的元素个数。
删除元素
srem key element [element...],返回结果为成功删除的元素个数。
计算元素个数
scard key,时间复杂度为 O(1),会直接使用 Redis 内部的遍历。
判断元素是否在集合中
sismember key element,如果存在返回 1,否则返回 0。
随机从集合返回指定个数个元素
srandmember key [count],如果不指定 count 默认为 1。
从集合随机弹出元素
spop key,可以从集合中随机弹出一个元素。
获取所有元素
smembers key
求多个集合的交集/并集/差集
sinter key [key...]
sunion key [key...]
sdiff key [key...]
保存交集、并集、差集的结果
sinterstore/sunionstore/sdiffstore destination key [key...]
集合间运算在元素较多情况下比较耗时,Redis 提供这三个指令将集合间交集、并集、差集的结果保存在 destination key 中。
应用场景
set 比较典型的使用场景是标签,例如一个用户可能与娱乐、体育比较感兴趣,另一个用户可能对例时、新闻比较感兴趣,这些兴趣点就是标签。这些数据对于用户体验以及增强用户黏度比较重要。
sadd = 标签、spop/srandmember = 生成随机数,比如抽奖、sadd + sinter = 社交需求。
zset
- 有序集合保留了集合不能有重复成员的特性,不同的是可以排序。但是它和列表使用索引下标作为排序依据不同的是,他给每个元素设置一个分数(score)作为排序的依据。有序集合提供了获取指定分数和元素查询范围、计算成员排名等功能。
命令
zadd key score member [score member...],返回结果是成功添加成员的个数
Redis 3.2 为 zadd 命令添加了 nx、xx、ch、incr 四个选项:
- nx:member 必须不存在才可以设置成功,用于添加。
- xx:member 必须存在才能设置成功,用于更新。
- ch:返回此次操作后,有序集合元素和分数变化的个数。
- incr:对 score 做增加,相当于 zincrby。
zadd 的时间复杂度为 O(logn),sadd 的时间复杂度为 O(1)。
计算成员个数
zcard key,时间复杂度为 O(1)。
计算某个成员的分数
zscore key member ,如果不存在则返回 nil。
计算成员排名
zrank key member,从低到高返回排名。
zrevrank key member,从高到低返回排名。
删除成员
zrem key member [member...],返回结果是成功删除的个数。
增加成员的分数
zincrby key increment member
返回指定排名范围的成员
zrange key start end [withscores],从低到高返回
zrevrange key start end [withscores], 从高到底返回
返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count],从低到高返回
zrevrangebyscore key min max [withscores] [limit offset count], 从高到底返回
返回指定分数范围成员个数
zcount key min max
删除指定分数范围内的成员
zremrangebyscore key min max
交集和并集
zinterstore/zunionstore destination numkeys key [key...] [weights weight [weight...]] [aggregate sum|min|max]
destination:交集结果保存到这个键numkeys:要做交集计算键的个数key:需要做交集计算的键weight:每个键的权重,默认 1aggregate sum|min|max:计算交集后,分值可以按和、最小值、最大值汇总,默认 sum。
应用场景
有序集合的典型使用场景就是排行榜系统,如果要展示获取赞数最多的十个用户,可以使用 zrange。如果需要将用户从榜单删除,可以使用 zrem。例如用户上传了一个视频并获得了点赞,可以使用 zadd 和 zincrby。
持久化
什么是Redis持久化?
将数据(如内存中的对象)保存到可永久保存的存储设备中。有RDB和AOF两种持久化方式。
RDB持久化的原理?
RDB持久化——增量持久化
RDB 持久化是把当前进程数据生成快照保存到硬盘的过程,触发 RDB 持久化过程分为手动触发和自动触发。
手动触发分别对应 save 和 bgsave 命令:
- save:阻塞当前 Redis 服务器,直到 RDB 过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。
- bgasve:Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。bgsave 是针对 save 阻塞问题做的优化,因此 Redis 内部所有涉及 RDB 的操作都采用 bgsave 的方式,而 save 方式已经废弃。
除了手动触发外,Redis 内部还存在自动触发 RDB 的持久化机制,例如:
- 使用 save 相关配置,如 save m n,表示 m 秒内数据集存在 n 次修改时,自动触发 bgsave。
- 如果从节点执行全量复制操作,主节点自动执行 bgsave 生成 RDB 文件并发送给从节点。
- 执行 debug reload 命令重新加载 Redis 时也会自动触发 save 操作。
- 默认情况下执行 shutdown 命令时,如果没有开启 AOF 持久化功能则自动执行 bgsave。
RDB 持久化的优缺点?
- 优点
- 非常适合于备份,全量复制等场景。
- Redis 加载 RDB 恢复数据远远快于 AOF 的方式。
- 缺点
- RDB 方式数据无法做到实时持久化/秒级持久化(bgsave每次运行都要执行 fork 操作创建子进程,属于重量级操作)
AOF概述
在 redis配置文件的 APPEND ONLY MODE 中,可以设置AOF持久化。通过记录redis服务器所执行的写命令来记录数据库状态。恢复时可以将AOF文件载入内存,并且可以通过redis-check-aof --fix 进行修复AOF文件。
AOF日志重写:
- AOF文件会随着服务器运行的时间越来越大,可以通过AOF重写来控制AOF文件的大小。
- AOF重写会首先读取数据库中现有的键值对状态,然后根据类型使用一条命令来替代前面对键值对操作的多条命令。
- 使用命令 bgrewriteaof 来实现AOF重写
AOF重写缓存区:
redis 是单线程工作,当AOF文件较大时重写时间会比较长,在重写 AOF 期间,redis将长时间无法处理客户端请求。为了解决这个问题,可以将 AOF 重写程序放到子进程中执行,好处如下:
- 子进程进行 AOF 重写期间,服务器进程(父进程)可以继续处理其它客户端请求。
- 子进程带有父进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。
子进程中AOF重写导致的问题:
- 子进程在进行 AOF 重写期间,服务器进程依然可以处理其它客户端请求,这就会导致数据库状态已经发生了改变,使得当前数据库数据状态和重写后的 AOF 文件中的数据不一致。
- 也就是出现了AOF文件和数据库中数据不一致的问题。
数据状态不一致解决办法:
- redis 服务器设置了一个 AOF 重写缓冲区。这个缓冲区在创建子进程后开始使用,当redis服务器执行一个客户端的写请求命令,之后将这个写命令也发送到 AOF 重写缓冲区。
- 当子进程完成 AOF 日志重写之后,给父进程发送信号,父进程接收此信号后,将 AOF 重写缓冲区的内容写到新的 AOF 文件中,保持数据的一致性。
AOF 持久化的原理?
AOF 持久化以独立日志的方式记录每次写命令,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF 的主要作用是解决了数据持久化的实时性,目前是 Redis 持久化的主流方式。
开启 AOF 功能需要设置:appendonly yes,默认不开启。保存路径同 RDB 方式一致,通过 dir 配置指定。
AOF 的工作流程操作:命令写入 append、文件同步 sync、文件重写 rewrite、重启加载 load:
- 所有的写入命令会追加到 aof_buf 缓冲区中。
- AOF 缓冲区根据对应的策略向硬盘做同步操作。
- 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
- 当服务器重启时,可以加载 AOF 文件进行数据恢复。
AOF 命令写入的原理?
AOF 命令写入的内容直接是文本协议格式,采用文本协议格式的原因:
- 文本协议具有很好的兼容性。
- 开启 AOF 后所有写入命令都包含追加操作,直接采用协议格式避免了二次处理开销。
- 文本协议具有可读性,方便直接修改和处理。
AOF 把命令追加到缓冲区的原因:
Redis 使用单线程响应命令,如果每次写 AOF 文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负载。先写入缓冲区中还有另一个好处,Redis 可以提供多种缓冲区同步硬盘策略,在性能和安全性方面做出平衡。
AOF 文件同步的原理?
Redis 提供了多种 AOF 缓冲区文件同步策略,由参数 appendfsync 控制,不同值的含义如下:
- always:命令写入缓冲区后调用系统 fsync 操作同步到 AOF 文件,fsync 完成后线程返回。每次写入都要同步 AOF,性能较低,不建议配置。
- everysec:命令写入缓冲区后调用系统 write 操作,write 完成后线程返回。fsync 同步文件操作由专门线程每秒调用一次。是建议的策略,也是默认配置,兼顾性能和数据安全。
- no:命令写入缓冲区后调用系统 write 操作,不对 AOF 文件做 fsync 同步,同步硬盘操作由操作系统负责,周期通常最长 30 秒。由于操作系统每次同步 AOF 文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但安全性无法保证。
AOF 文件重写的原理?
文件重写是把 Redis 进程内的数据转化为写命令同步到新 AOF 文件的过程,可以降低文件占用空间,更小的文件可以更快地被加载。
重写后 AOF 文件变小的原因:
- 进程内已经超时的数据不再写入文件。
- 旧的 AOF 文件含有无效命令,重写使用进程内数据直接生成,这样新的 AOF 文件只保留最终数据写入命令。
- 多条写命令可以合并为一个,为了防止单条命令过大造成客户端缓冲区溢出,对于 list、set、hash、zset 等类型操作,以 64 个元素为界拆分为多条。
AOF 重写分为手动触发和自动触发,手动触发直接调用 bgrewriteaof 命令,自动触发根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数确定自动触发时机。
重写流程:
① 执行 AOF 重写请求,如果当前进程正在执行 AOF 重写,请求不执行并返回,如果当前进程正在执行 bgsave 操作,重写命令延迟到 bgsave 完成之后再执行。
② 父进程执行 fork 创建子进程,开销等同于 bgsave 过程。
③ 父进程 fork 操作完成后继续响应其他命令,所有修改命令依然写入 AOF 缓冲区并同步到硬盘,保证原有 AOF 机制正确性。
④ 子进程根据内存快照,按命令合并规则写入到新的 AOF 文件。每次批量写入数据量默认为 32 MB,防止单次刷盘数据过多造成阻塞。
⑤ 新 AOF 文件写入完成后,子进程发送信号给父进程,父进程更新统计信息。
⑥ 父进程把 AOF 重写缓冲区的数据写入到新的 AOF 文件并替换旧文件,完成重写。
AOF 重启加载的原理?
AOF 和 RDB 文件都可以用于服务器重启时的数据恢复。Redis 持久化文件的加载流程:
① AOF 持久化开启且存在 AOF 文件时,优先加载 AOF 文件。
② AOF 关闭时且存在 RDB 文件时,记载 RDB 文件。
③ 加载 AOF/RDB 文件成功后,Redis 启动成功。
④ AOF/RDB 文件存在错误导致加载失败时,Redis 启动失败并打印错误信息。
RDB和AOF优缺点分析
- AOF文件可以做到秒级持久化,使用追加写的方式来写入,可读性强并且可以使用命令进行文件修复。
- 相比于RDB文件,同样数据下AOF文件体积要大。在redis负载较高时,秒级更新AOF文件会影响性能
持久化策略选择:
- AOF更安全,可将数据及时同步到文件中,但需要较多的磁盘IO,AOF文件尺寸较大,文件内容恢复相对较慢也更加完整。
- RDB持久化,安全性较差,它是正常时期数据备份及 master-slave数据同步的最佳手段,文件尺寸较小并且恢复速度较快。
redis数据的过期回收策略与内存淘汰机制
redis中的数据过期回收策略使用了定期删除和惰性删除相结合的方式。
定期删除:
redis会每隔一定的时间去抽查一定量的数据判断其是否过期,过期则进行删除。
惰性删除:
在获取一个key的时候,redis会检查这个key是否已经过期,若过期,则会进行删除操作。
内存淘汰机制:
在配置文件中,我们可以对内存淘汰机制进行配置。当内存使用达到最大值时,redis可以使用的清除策略如下:
- volatile-lru:利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )
- allkeys-lru:利用LRU算法移除任何key
- volatile-random:移除设置过过期时间的随机key
- allkeys-random:移除随机key
- volatile-ttl:移除即将过期的key(minor TTL)
- noeviction :不移除任何key,只是返回一个写错误 ,默认选项
如何设置键过期?
expire key seconds:键在 seconds 秒后过期。
如果过期时间为负值,键会被立即删除,和 del 命令一样。persist 命令可以将键的过期时间清除。
对于字符串类型键,执行 set 命令会去掉过期时间,set 命令对应的函数 setKey 最后执行了 removeExpire 函数去掉了过期时间。setex 命令作为 set + expire 的组合,不单是原子执行并且减少了一次网络通信的时间。
缓存异常
缓存雪崩
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
这就是缓存雪崩。
缓存雪崩的事前事中事后的解决方案如下。
事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。
限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?走降级!可以返回一些默认的值,或者友情提示,或者空白的值。
好处:
- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
- 只要数据库不死,就是说,对用户来说,部分的请求都是可以被处理的。
- 只要有部分的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。
缓存穿透
对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是找不到的
比如发出的那 4000 个请求,缓存中查不到,每次你去数据库里查,也查不到。
举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。
解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
缓存击穿
缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。
缓存预热
新的缓存系统没有任何缓存数据,在缓存重建数据的过程中,系统性能和数据库负载都不太好,所以最好是在系统上线之前就把要缓存的热点数据加载到缓存中,这种缓存预加载手段就是缓存预热。
缓存预热的思路
提前给redis中嵌入部分数据,再提供服务,需要更具当天的具体访问情况,试试统计出频率较高的热数据。一般用nginx+lua将访问量上报到kafka中,要统计出来当前最新的实时的热数据是哪些,我们就得将商品详情页访问的请求对应的流量,日志,实时上报到kafka中,从kafka中消费数据,实时统计出每个商品的访问次数,访问次数基于LRU内存数据结构的存储方案
缓存降级
缓存降级,是为保证核心服务可用,使部分关键数据自动降级,同时也可避免缓存雪崩。
页面降级
在大促或者某些特殊情况下,某些页面占用了一些稀缺服务资源,在紧急情况下可以对其整个降级,以达到丢卒保帅;
服务功能降级
比如渲染商品详情页时需要调用一些不太重要的服务:相关分类、热销榜等,而这些服务在异常情况下直接不获取,即降级即可;
读降级
比如多级缓存模式,如果后端服务有问题,可以降级为只读缓存,这种方式适用于对读一致性要求不高的场景;
写降级
比如秒杀抢购,我们可以只进行Cache的更新,然后异步同步扣减库存到DB,保证最终一致性即可,此时可以将DB降级为Cache。
缓存热key
事务
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
1.redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
2.如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
3.如果在一个事务中出现运行错误,那么正确的命令会被执行。
1)MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
3)通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
分布式锁
分布式锁是控制分布式系统之间共同访问共享资源的一种锁的实现。如果一个系统,或者不同系统的不同主机之间共享某个资源时,往往需要互斥,来排除干扰,满足数据一致性。
分布式锁需要解决的问题如下:
- 互斥性:任意时刻只有一个客户端获取到锁,不能有两个客户端同时获取到锁。
- 安全性:锁只能被持有该锁的客户端删除,不能由其他客户端删除。
- 死锁:获取锁的客户端因为某些原因而宕机继而无法释放锁,其他客户端再也无法获取锁而导致死锁,此时需要有特殊机制来避免死锁。
- 容错:当各个节点,如某个 Redis 节点宕机的时候,客户端仍然能够获取锁或释放锁。
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
使用 SETNX 实现,SETNX key value:如果 Key 不存在,则创建并赋值。
该命令时间复杂度为 O(1),如果设置成功,则返回 1,否则返回 0。
由于 SETNX 指令操作简单,且是原子性的,所以初期的时候经常被人们作为分布式锁,我们在应用的时候,可以在某个共享资源区之前先使用 SETNX 指令,查看是否设置成功。
如果设置成功则说明前方没有客户端正在访问该资源,如果设置失败则说明有客户端正在访问该资源,那么当前客户端就需要等待。
但是如果真的这么做,就会存在一个问题,因为 SETNX 是长久存在的,所以假设一个客户端正在访问资源,并且上锁,那么当这个客户端结束访问时,该锁依旧存在,后来者也无法成功获取锁,这个该如何解决呢?
由于 SETNX 并不支持传入 EXPIRE 参数,所以我们可以直接使用 EXPIRE 指令来对特定的 Key 来设置过期时间。
将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作
解锁:使用 del key 命令就能释放锁
解决死锁:
1)通过Redis中expire()给锁设定最大持有时间,如果超过,则Redis来帮我们释放锁。
2) 使用 setnx key “当前系统时间+锁持有的时间”和getset key “当前系统时间+锁持有的时间”组合的命令就可以实现。
解决:从 Redis 2.6.12 版本开始,我们就可以使用 Set 操作,将 SETNX 和 EXPIRE 融合在一起执行,具体做法如下:
SET KEY value [EX seconds] [PX milliseconds] [NX|XX]
- EX second:设置键的过期时间为 Second 秒。
- PX millisecond:设置键的过期时间为 MilliSecond 毫秒。
- NX:只在键不存在时,才对键进行设置操作。
- XX:只在键已经存在时,才对键进行设置操作。
注:SET 操作成功完成时才会返回 OK,否则返回 nil。
集群
Redis 集群方案应该怎么做?都有哪些方案?
1.twemproxy,大概概念是,它类似于一个代理方式, 使用时在本需要连接 redis 的地方改为连接 twemproxy, 它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。 缺点: twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。
2.codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新 hash 节点
3.redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。
Redis 集群会有写操作丢失吗? 为什么? Redis 并不能保证数据的强一致性, 这意味这在实际中集群在特定的条件下可能会丢失写操 作。
Redis 常见性能问题和解决方案?
(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即: Master <- Slave1 <- Slave2 <-
Slave3…
其他
Redis 是单线程的, 如何提高多核 CPU 的利用率?
分区