redis必知必会

概述

什么是Redis

Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的高性能非关系型(NoSQL)的键值对数据库

Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、Hash、有序集合。

Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过10万次读写操作。

Redis有哪些优缺点

优点

读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
数据结构丰富,支持string、hash、set、zset、list等数据结构。
支持数据持久化,支持AOF和RDB两种持久化方式。
支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
支持主从复制,主机会自动将数据同步到从机,支持读写分离。

缺点

数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,适合的场景主要局限在较小数据量的高性能操作和运算上。
主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后会引入数据不一致的问题。

Redis为什么这么快

一方面,Redis 的大部分操作在内存上完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。

另一方面,就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率。

IO多路复用模型

Linux 中的 IO 多路复用机制是指一个线程处理多个 IO 流– select/epoll 机制。

  1. 允许同时存在多个监听套接字和已连接套接字。
  2. 内核会一直监听这些套接字上的连接请求或数据请求。select/epoll 一旦监测到文件描述符上有请求到达时,就会触发相应的事件。
  3. 这些事件会被放进一个事件队列,Redis 单线程对该事件队列不断进行处理。

redis不会阻塞在某一个特定的客户端请求处理上。Redis 可以同时和多个客户端连接并处理请求,从而提升并发性。

数据类型

Redis有哪些数据类型

Redis主要有5种数据类型,包括String,List,Set,Zset,Hash.

数据类型 可存储的值 操作 应用场景
STRING 字符串、整数或者浮点数 对整个字符串或者字符串的其中一部分执行操作 整数和浮点数自增减操作;键值对缓存
LIST 列表 从两端压入或者弹出元素;对单个或者多个元素进行修剪,只保留一个范围内的元素 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据
SET 无序集合 添加、获取、移除单个元素;检查一个元素是否存在于集合中;计算交集、并集、差集 可以把两个人的粉丝列表整一个交集
HASH 包含键值对的无序散列表 添加、获取、移除单个键值对;获取所有键值对;检查某个键是否存在 结构化的数据,比如一个对象
ZSET 有序集合 添加、获取、删除元素;根据分值范围或者成员来获取元素;计算一个键的排名 去重但可以排序,如获取排名前几名的用户

字符串

数据结构

Redis 的字符串叫着SDS,Simple Dynamic String。它的结构是一个带长度信息的字节数组。

  • embstr :将 RedisObject 对象头和 SDS 对象连续存在一起,使用 malloc 方法一次分配。
  • raw :需要两次 malloc,两个对象头在内存地址上一般是不连续的。

Hash

数据结构

  • 内部包含两个 dictht,通常情况下只有一个 hashtable 是有值的。
  • 每个dictht包括,哈希表数组,同时包含数组长度和可用元素的个数。
  • 数组中的每一个元素dictEntry组成包括, key、value、下一个元素的指针
struct dict {
    ...
    dictht ht[2];        //哈希表
}
struct dictht {
    dictEntry** table;     // 二维
    long size;             // 第一维数组的长度
    long used;             // hash 表中的元素个数
    ...
}
struct dictEntry {
    void* key;
    void* val;
    dictEntry* next;     // 链接下一个 entry
}

hash 冲突

使用链地址法解决哈希冲突。哈希表节点的next指针指向下一个哈希表节点,通过单向链表解决哈希冲突。为了速度考虑,总是将新节点添加到链表的表头位置。

rehash

  1. 为字典的 ht[1] 哈希表分配空间,初始默认hash长度为4,当元素个数与hash表长度一致时,就发生扩容,hash长度变为原来的二倍
  2. 将保存在 ht[0] 中的所有键值对 rehash 到 ht[1] 上面
  3. ht[0] 包含的所有键值对都迁移到了 ht[1] 之后 (ht[0] 变为空表), 释放 ht[0] , 将 ht[1] 设置为 ht[0] , 并在 ht[1] 新创建一个空白哈希表, 为下一次 rehash 做准备

渐进式rehash

  1. ht[1] 分配空间, 让字典同时持有 ht[0]ht[1] 两个哈希表。
  2. 在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash 工作正式开始。
  3. 在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。
  4. 随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。

负载因子

Redis中,loader_factor哈希表中键值对数量 / 哈希表长度

  • 服务器目前没有在执行 BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于 1 ;
  • 服务器目前正在执行BGSAVE 命令或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于 5 ;

根据 BGSAVE 命令或 BGREWRITEAOF 命令是否正在执行, 服务器执行扩展操作所需的负载因子并不相同, 这是因为在执行BGSAVE 命令或BGREWRITEAOF 命令的过程中, Redis 需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)技术来优化子进程的使用效率, 所以在子进程存在期间,服务器会提高执行扩展操作所需的负载因子, 从而尽可能地避免在子进程存在期间进行哈希表扩展操作, 这可以避免不必要的内存写入操作,最大限度地节约内存。

有序集合

跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位。跳表的查找复杂度就是 O(logN)

跳表

跳表是一种可以进行二分查找的有序链表

跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。

跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。

跳表怎么增加节点

  • 根据投硬币的方式,决定新元素要占据的层数
  • 然后,找到这个元素在下面两层的前置节点。
  • 接着,就是链表的插入元素操作了

跳表怎么删除节点

  • 找到各层中包含元素x的节点。
  • 使用标准的链表删除元素的方法删除即可。

Redis中的skiplist和经典有何不同

  • score允许重复,即skiplist的key允许重复。经典skiplist中是不允许的。
  • 在比较时,不仅比较分数(相当于skiplist的key),还比较数据本身。在Redis的skiplist实现中,数据本身的内容唯一标识这份数据,而不是由key来唯一标识。另外,当多个元素分数相同的时候,还需要根据数据内容来进字典排序。
  • 第1层链表是一个双向链表。这是为了方便以倒序方式获取一个范围内的元素。
  • redis的跳表维护了span字段,可以快速计算出节点的rank或者获取指定rank的节点。

为什么Redis选择使用跳表而不是红黑树来实现有序集合?

首先,我们来分析下Redis的有序集合支持的操作:

1)插入元素

2)删除元素

3)查找元素

4)有序输出所有元素

5)查找区间内所有元素

其中,前4项红黑树都可以完成,且时间复杂度与跳表一致。

但是,查找区间内所有元素,红黑树的效率就没有跳表高了。

在跳表中,要查找区间的元素,我们只要定位到两个区间端点在最低层级的位置,然后按顺序遍历元素就可以了,非常高效。

而红黑树只能定位到端点后,再从首位置开始每次都要查找后继节点,相对来说是比较耗时的。

redis的跳表维护了span字段,可以快速计算出节点的rank或者获取指定rank的节点

此外,跳表实现起来很容易且易读,红黑树实现起来相对困难,所以Redis选择使用跳表来实现有序集合。

span 表示当前节点在当前层到达下一个节点的距离。后续查找节点的时候,每层会获取一个节点,这些节点的span加起来,就是节点的rank。

BitMaps

BitMaps是在字符串类型上定义的位操作。可用于用户签到、应用访问统计等场景。

Hyperloglog

Hyperloglog 提供了一种不太精确的基数统计方法,用来统计一个集合中不重复的元素个数,比如统计网站的UV,或者应用的日活、月活,存在一定的误差。

GEO

适用于位置信息服务(Location-Based Service,LBS)的应用。

实现原理

GEO 类型的底层数据结构就是用 Sorted Set 来实现的。Sorted Set 元素的权重分数是一个浮点数(float 类型),而一组经纬度包含的是经度和纬度两个值。因此需要对一组经纬度进行 GeoHash 编码,基本原理就是二分区间,区间编码,经纬度编码需要交叉组合成一个数。

Streams(5.0)

Streams 是 Redis 专门为消息队列设计的数据类型。

  • 对于插入的每一条消息,Streams 可以自动为其生成一个全局唯一的 ID。

  • Streams支持消费组。消息队列中的消息一旦被消费组里的一个消费者读取了,就不能再被该消费组内的其他消费者读取了。

  • Streams 会自动使用内部队列( PENDING List)留存消费组里每个消费者读取的消息,直到消费者使用 XACK 命令通知 Streams消息已经处理完成

Redis的应用场景

总结一

计数器

可以对 String 进行自增自减运算,从而实现计数器功能。

缓存

将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。

会话缓存

统一存储多台应用服务器的会话信息。

全页缓存

Redis还提供很简便的全页缓存平台。Magento、WordPress提供一个插件来使用Redis作为全页缓存后端

查找表

例如 DNS 记录就很适合使用 Redis 进行存储。

消息队列

List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息。

其它

Set 可以实现交集、并集等操作,从而实现共同好友等功能。

ZSet 可以实现有序性操作,从而实现排行榜等功能。

持久化

what?

持久化就是把内存的数据写到磁盘中去,防止服务宕机了,内存数据丢失。

持久化机制?各自的优缺点?

Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制。

RDB(Redis DataBase)

  • 主进程fork出子进程,共享主线程的所有内存数据
  • 子进程读取主线程的内存数据,并把它们写入 RDB 文件
  • 如果主线程要修改一块数据,这块数据就会被复制一份,生成数据副本。bgsave 子进程会把这个副本数据写入 RDB 文件

借助了操作系统提供的写时复制技术(Copy-On-Write),在执行快照的同时,正常处理写操作,避免了主线程的阻塞。

优点

  • 性能最大化,fork 子进程来完成写操作,主进程不会进行任何 IO 操作,保证了 redis 的高性能
  • 比 AOF 的启动效率更高。

缺点

如果两次持久化之间 发生故障,会导致数据丢失。

AOF持久化(Append Only File)

执行命令后,将Redis执行的每次写命令记录到单独的日志文件中。写后日志,避免了命令的检查,并且不阻塞当前的写操作。但是可能会给下一个操作带来阻塞风险,因为AOF日志在主线程中运行。

当重启Redis,利用持久化日志,恢复数据。

优点:

  • 支持每进行一次命令操作就记录到 aof 文件中一次。

缺点:

  • AOF 文件比 RDB 文件大,且恢复速度慢。

AOF重写流程

  • 主线程 fork 出 bgrewriteaof 子进程,把主线程的内存拷贝一份给 bgrewriteaof 子进程
  • 新操作写到正在使用的 AOF 日志的缓冲区(因为可能记录了最新操作)
  • 新操作写到正在使用的 重写日志的缓冲区(重写日志也不会丢失最新的操作)。
  • 拷贝数据的所有操作记录重写完成后,重写日志记录缓冲区的最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时可以用新的 AOF 文件替代旧文件了。

AOF 重写时,Redis 会先执行一个内存拷贝,用于重写;

然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。

而且,因为 Redis 采用额外的线程进行数据重写,所以,这个过程并不会阻塞主线程。

如何选择合适的持久化方式

  • 数据不能丢失时,内存快照和 AOF 的混合使用;
  • 如果允许分钟级别的数据丢失,只使用 RDB;
  • 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。

aof 和 rdb 哪个效率好

  • RDB文件内容是经过压缩的二进制数据(不同数据类型数据做了针对性优化),文件很小。而AOF文件记录的是每一次写操作的命令,写操作越多文件会变得很大,其中还包括很多对同一个key的多次冗余操作。
  • 因为RDB文件存储的都是二进制数据,从库直接按照RDB协议解析还原数据即可,速度会非常快,而AOF需要依次重放每个写命令,这个过程会经历冗长的处理逻辑,恢复速度相比RDB会慢得多,所以使用RDB进行主从全量同步的成本最低。

过期键的删除策略

Redis的过期键的删除策略

惰性过期:只有当访问一个key时,才会判断它是否已过期,过期则清除。该策略可以节省CPU资源,极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。
Redis中同时使用了惰性过期和定期过期两种过期策略。

Redis key的过期时间和永久有效分别怎么设置?

EXPIRE和PERSIST命令。

内存相关

MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据

redis内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。

Redis的内存淘汰策略有哪些

全局选择性移除

  • allkeys-lru:移除最近最少使用的key。

  • allkeys-lfu:所有键根据数据的被访问次数和访问时效性,进行移除

  • allkeys-random:随机移除某个key。

设置过期时间的键空间选择性移除

  • volatile-lru:在设置了过期时间的键中,移除最近最少使用的key。

  • volatile-lfu:在设置了过期时间的键中,根据数据的被访问次数和访问时效性,进行移除

  • volatile-random:在设置了过期时间的键中,随机移除某个key。

  • volatile-ttl:在设置了过期时间的键中,有更早过期时间的key优先移除。

Redis的内存用完了会发生什么?

如果配置了内存淘汰机制,会冲刷掉旧的内容。否则Redis的写命令会返回错误信息。

LRU

Redis 中,LRU 算法被做了简化。

  • Redis 在决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为候选集合。
  • Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去。
  • 再次淘汰数据时,Redis 需要挑选数据进入第一次淘汰时创建的候选集合。能进入候选集合的数据的 lru 字段值必须小于候选集合中最小的 lru 值

Redis如何做内存优化?

利用Hash、list、sorted set、set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起

事务

what?

事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序地执行。

事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的三个阶段

  1. 事务开始 MULTI
  2. 命令入队
  3. 事务执行 EXEC

事务执行过程中,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求,将会把请求放入队列中排队。

Redis事务相关命令

WATCH 命令是一个乐观锁,可以为 Redis 事务提供CAS。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
MULTI命令用于开启一个事务。 MULTI执行之后,客户端可向服务器发送任意多条命令,这些命令会被放到一个队列中,当EXEC被调用时,队列中的命令才会被执行。
EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务
UNWATCH命令可以取消watch对所有key的监控

ACID

  • 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  • 一致性(Consistency)
    事务前后数据的完整性必须保持一致。

  • 隔离性(Isolation)
    多个事务并发执行时,一个事务的执行不应影响其他事务的执行

  • 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

Redis的事务具备一致性和隔离性

Redis事务支持隔离性吗

支持。Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。

Redis事务保证原子性吗,支持回滚吗

单条命令是原子性执行的,但事务不保证原子性,且没有回滚。

如果事务中的命令出现错误,那么所有的命令都不会执行

如果事务中出现运行错误,那么正确的命令会被执行。

Redis事务其他实现

  • 基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序地执行

集群

哨兵

监控

  • 哨兵进程周期性地给所有的主从库发送 PING 命令,检测它们是否仍然在线运行。
  • 主库或从库对 PING 命令的响应超时了,哨兵会标记为主观下线。
  • 需有quorum 个实例判断主库为主观下线,才能判定主库为客观下线

选主

  • 筛选当前在线从库,且网络连接状况较好;
  • 选择从库优先级最高的从库
  • 选择从库复制进度最快的
  • 选择从库 ID 号小

通知

  • 通知从库执行replicaof,与新主库同步
  • 通知客户端,向新主库请求

通知客户端的实现方法

1、哨兵会把新主库的地址写入自己实例的pubsub中。客户端需要订阅这个pubsub,当这个pubsub有数据时,客户端就能感知到主库发生变更,同时可以拿到最新的主库地址。

2、客户端需要支持主动去获取最新主从的地址进行访问。

集群模式的工作原理? key 如何寻址的?

简介

  • Redis Cluster是一种服务端Sharding技术。

  • 每个key通过CRC16校验后对16384取模来决定放置哪个槽。每个节点存储一定哈希槽的数据,默认分配了16384 个槽位

  • 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点

节点间的内部通信机制

redis 节点间采用 gossip 协议进行通信。

分布式寻址算法

  • hash 算法(大量缓存重建)
  • 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
  • redis cluster 的 哈希槽算法(其实也是一致性哈希算法)

优点

  • 无中心架构,支持动态扩容,对业务透明
  • 具备Sentinel的监控和自动Failover能力
  • 高性能,客户端直连redis服务,免去了proxy代理的损耗

缺点

  • 只能使用0号数据库
  • 不支持批量操作(pipeline管道操作)

Redis 主从架构

主从复制原理

通过 replicaof(Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系。

  1. 主从库间建立连接、协商同步,为全量复制做准备。

    从库和主库建立起连接,发送 psync 命令,表示要进行数据同步。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。

    主库确认回复,FULLRESYNC响应表示第一次复制采用的全量复制。

  2. 主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。

    主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库。

    从库接收到 RDB 文件后,会先清空当前数据库(避免之前数据的影响),然后加载 RDB 文件。

  3. 主库会把第二阶段执行过程中新收到的写命令(replication buffer中的修改操作),再发送给从库。
    主库会在内存中使用 replication buffer,记录 RDB 文件生成后收到的所有写操作。

缺点

可能会造成master节点压力太大,使用主从从结构来解决

增量复制机制

  • 主库把断连期间收到的写操作命令写入 repl_backlog_buffer 缓冲区
  • repl_backlog_buffer 是一个环形缓冲区,主库会记录写到的位置,从库会记录已经读到的位置。
  • 连接恢复后,从库给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库,主库会判断 master_repl_offset 和 slave_repl_offset 之间的差距。把 master_repl_offset 和 slave_repl_offset 之间的命令操作同步给从库。
  • 库还未读取的操作被主库新写的操作覆盖,需要全量复制

主从集群切换数据丢失

通过配置控制同步时间

min-slaves-max-lag、min-slaves-to-write:要求至少有1个slave,数据复制和同步延迟不能超过10秒;如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么master就会拒绝接收任何请求。

集群模式下key怎么保证在一个节点上?

分布在一个节点上,可能是需要对这些key做聚合处理。

基于redis cluster分片机制,key 进行规划和使用 hash tag 特性。在开源 Redis 中,花括号{}表示 hash tag,这个两个花括号中间的字符才会进行 CRC16 散列计算。Crc16 散列函数返回的是一个 14bit 的整数,当中间字符只有数字的时候,CRC16计算的位置就可控了。

分区

Redis是单线程的,如何提高多核CPU的利用率?

  • 可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用。
  • 把 Redis 实例和 CPU 物理核绑定了,让一个 Redis 实例固定运行在一个 CPU 物理核上
  • 把操作系统的网络中断处理程序和 CPU 物理核绑定。Redis 实例绑定在同一个物理核上。

为什么要做Redis分区?

分区可以让Redis管理更大的内存。

RDB 持久化时,fork 子进程用时和 Redis 的数据量是正相关的。数据量越大,fork 操作造成的主线程阻塞的时间越长。

Redis分区有什么缺点?

  • 涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例。
  • 同时操作多个key,则不能使用Redis事务.

分布式问题

Redis实现分布式锁

加锁

  • SET key value [EX seconds | PX milliseconds] [NX]

  • key 不存在, key 会被创建。

  • Value 要具有唯一性。这个是为了在解锁的时候,需要验证 Value 是和加锁的一致才删除 Key。

  • 过期时间是为了避免操作共享数据时发生了异常,结果一直没有执行最后的 DEL 命令释放锁。

解锁

执行完业务逻辑后,使用 DEL 命令删除锁变量,从而释放锁(释放锁涉及到两条指令,这两条指令不是原子性的,通过执行一段lua脚本)。

缺点

  • 它获取锁的方式简单粗暴,获取不到锁直接不断尝试获取锁,比较消耗性能。
  • 即便使用 Redlock 算法来实现,在某些复杂场景下,也无法保证其实现 100% 没有问题。
  • Redis 的设计定位决定了它的数据并不是强一致性的,在某些极端情况下,可能会出现问题。锁的模型不够健壮。比如,锁过期问题

优点

Redis 的性能很高,可以支撑高并发的获取、释放锁操作

Zookeeper 实现

  • 使用 ZK 的临时节点和有序节点,每个线程获取锁就是在 ZK 创建一个临时有序的节点,比如在 /lock/ 目录下。
  • 创建节点成功后,获取 /lock 目录下的所有临时节点,再判断当前线程创建的节点是否是所有的节点的序号最小的节点。
  • 如果当前线程创建的节点是所有节点序号最小的节点,则认为获取锁成功。
  • 如果当前线程创建的节点不是所有节点序号最小的节点,则对节点序号的前一个节点添加一个事件监听

优点

  • ZK 天生设计定位就是分布式协调,强一致性。锁的模型健壮、简单易用、适合做分布式锁。
  • 如果获取不到锁,只需要添加一个监听器就可以了,不用一直轮询,性能消耗较小。

缺点

如果有较多的客户端频繁的申请加锁、释放锁,对于 ZK 集群的压力会比较大。

分布式Redis是前期做还是后期规模上来了再做好?为什么?

一开始就多设置几个Redis实例,当你的数据不断增长,需要更多的Redis服务器时,你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)

什么是 RedLock

分布式锁算法 Redlock

  • 客户端获取当前时间。

  • 客户端按顺序依次向 N 个 Redis 实例执行加锁操作。

  • 一旦客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时

  • 客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁并且客户端获取锁的总耗时没有超过锁的有效时间,加锁成功

  • 别人建立了一把分布式锁,你就得不断轮询去尝试获取锁。

缺点

无法保证加锁的过程一定正确

缓存异常

https://wangyixin-tom.github.io/2020/10/27/redis-huan-cun

缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存

解决方案

  • 统计出频率较高的热数据,直接写个缓存刷新页面,上线时手工操作一下;

缓存热点key过期

一般都会从后端DB加载数据并回设到缓存,大并发的请求可能会瞬间把后端DB压垮。

解决方案

对缓存查询加锁:如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;

其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询

热key怎么解决?

怎么发现热key

方法一:预估哪些是热key
比如某商品在做秒杀,那这个商品的key就可以判断出是热key。缺点很明显,并非所有业务都能预估出哪些key是热key。
方法二:在客户端进行收集
这个方式就是在操作redis之前,加入一行代码进行数据统计。缺点就是对客户端代码造成入侵。
方法三:在Proxy层做收集
可以在Proxy层做收集上报,但是缺点很明显,并非所有的redis集群架构都有proxy。

方法四:用redis自带命令
monitor命令,该命令可以实时抓取出redis服务器接收到的命令,然后写代码统计出热key是啥。

如何解决

  • 利用二级缓存

在你发现热key以后,把热key加载到系统的JVM中。针对这种热key请求,会直接从jvm中取,而不会走到redis层。

  • 备份热key

我们把这个key,在多个redis上都存一份,有热key请求进来的时候,我们就在有备份的redis上随机选取一台,进行访问取值,返回数据。

其他问题

Redis与Memcached的区别

(1) memcached所有的值均是简单的字符串,redis支持更为丰富的数据类型

(2) redis的速度比memcached快很多

(3) redis可以持久化数据

如何保证缓存与数据库双写时的数据一致性?

先更新数据库,然后再删除缓存。

Redis常见性能问题和解决方案?

  • Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。

  • 如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

  • 为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。

  • 尽量避免在压力较大的主库上增加从库

一个字符串类型的值能存储最大容量是多少?

512M

Redis如何做大量数据插入?

pipe mode的新模式用于执行大量数据插入工作。

Lua脚本是如何保证操作的原子性的

Redis使用同一个Lua解释器来执行所有命令,同时,Redis保证以一种原子性的方式来执行脚本:当lua脚本在执行的时候,不会有其他脚本和命令同时执行,这种语义类似于 MULTI/EXEC。

从别的客户端的视角来看,一个lua脚本要么不可见,要么已经执行完。

找出Redis里面有10w个key是以某个固定的已知的前缀开头的

使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

redis数据统计,在高并发下的问题?

Set 集合的交差并的计算复杂度很高,如果数据量很大的情况下,可能会造成Redis的阻塞。

那么如何规避阻塞呢?建议如下:

  1. 在 Redis 集群中选一个从库专门负责聚合统计,这样就不会阻塞主库和其他的从库了
  2. 将数据交给客户端,由客户端进行聚合统计。

big key如何优化

  1. 优化big key的原则就是string减少字符串长度,list、hash、set、zset等拆分减少成员数
  2. 以hash类型举例来说,对于field过多的场景,可以根据field进行hash取模,生成一个新的key,例如原来的hash_key:{filed1:value, filed2:value, filed3:value …},可以hash取模后形成如下key:value形式
    hash_key:mod1:{filed1:value}
    hash_key:mod2:{filed2:value}
    hash_key:mod3:{filed3:value}
    取模后,将原先单个key分成多个key,每个key filed个数为原先的1/N

big key 删除

1、异步的键值对删除操作是 Redis 4.0 后提供的功能。

2、之前的版本Big key删除可以先使用集合类型提供的 SCAN 命令(sscan、hscan、zscan)读取数据,然后再进行删除。因为用 SCAN 命令可以每次只读取一部分数据并进行删除,这样可以避免一次性删除大量 key 给主线程带来的阻塞。

redis怎么防止超买

  • 先检查 库存是否充足
  • DECR或者INCR,设置一个键值对存放被抢购数量,每次一个用户进来就将该值加一进行判断,如果小于抢购的商品数量则抢购成功,否则失败。
  • 如果超过数字,需要恢复库存,其他人可以买剩下的较少数量物品

最好使用lua脚本实现

或者使用事务机制

mget和pipeline区别

  • mget和pipeline都是多命令一起执行,只有一次往返的网络IO
  • mget在集群下可以并行去获取,pipeline还是串行

使用Redis做过异步队列吗,是如何实现的

使用list类型保存数据信息,rpush生产消息,

使用blpop消费消息, 在没有信息的时候,会一直阻塞,直到信息的到来。

redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。

Redis如何实现延时队列

使用sortedset,使用时间戳做score, 消息内容作为key,调用zadd来生产消息,消费者使用zrangbyscore获取n秒之前的数据做轮询处理。

如何实现分页

SortedSet的添加元素指令ZADD key score member [[score,member]…]会给每个添加的元素member绑定一个用于排序的值score,SortedSet就会根据score值的大小对元素进行排序。

SortedSet中的指令ZREVRANGE key start stop又可以返回指定区间内的成员,可以用来做分页

Redis回收进程如何工作的?

Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。

RDB持久化内存分析

使用 4GB 内存云主机运行 Redis,Redis 数据库的数据量 2GB,我们使用了 RDB 做持久化保证,写读比例差不多在 8:2 左右。

redis fork子进程做RDB持久化,由于写的比例为80%,那么在持久化过程中,“写实复制”会重新分配整个实例80%的内存副本,大约需要重新分配1.6GB内存空间,这样整个系统的内存使用接近饱和

如果此时父进程又有大量新key写入,很快机器内存就会被吃光,如果机器开启了Swap机制,那么Redis会有一部分数据被换到磁盘上,当Redis访问这部分在磁盘上的数据时,性能会急剧下降,已经达不到高性能的标准(可以理解为武功被废)。

如果机器没有开启Swap,会直接触发OOM,父子进程会面临被系统kill掉的风险。

商品秒杀实现过程

分析

秒杀活动可以分为3个阶段:

秒杀前:用户不断刷新商品详情页,页面请求达到瞬时峰值。

秒杀开始:用户点击秒杀按钮,下单请求达到瞬时峰值。

秒杀后:一部分成功下单的用户不断刷新订单或者产生退单操作,大部分用户继续刷新商品详情页等待退单机会。消费者提交订单,一般做法是利用数据库的行级锁,只有抢到锁的请求可以进行库存查询和下单操作。但是在高并发的情况下,数据库无法承担如此大的请求,往往会使整个服务blocked,在消费者看来就是服务器宕机。

实施

1、利用浏览器缓存和CDN抗压静态页面流量

秒杀前,用户不断刷新商品详情页,造成大量的页面请求。

2、利用读写分离Redis缓存拦截流量

CDN是第一级流量拦截,第二级流量拦截我们使用支持读写分离的Redis。

3、利用主从版Redis缓存加速库存扣量

成功参与下单后,进入下层服务,开始进行订单信息校验,库存扣量。为了避免直接访问数据库,我们使用主从版Redis来进行库存扣量,主从版Redis提供10万级别的QPS。使用Redis来优化库存查询,提前拦截秒杀失败的请求,将大大提高系统的整体吞吐量。通过数据控制模块提前将库存存入Redis,将每个秒杀商品在Redis中用一个hash结构表示。

4、使用主从版Redis实现简单的消息队列异步下单入库

扣量完成后,需要进行订单入库。如果商品数量较少的时候,直接操作数据库即可。如果秒杀的商品是1万,甚至10万级别,那数据库锁冲突将带来很大的性能瓶颈。因此,利用消息队列组件,当秒杀服务将订单信息写入消息队列后,即可认为下单完成,避免直接操作数据库。消息队列组件依然可以使用Redis实现。


   转载规则


《redis必知必会》 wangyixin-tom 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
python必知必会 python必知必会
语言特性解释型语言。Python不需要在运行之前进行编译。 动态语言,不需要声明变量的类型,动态增加类方法。 适合面向对象的编程,允许类的定义和继承。 python2和python3区别 Python2 的默认编码是 ascii,Pytho
2021-04-13
下一篇 
mysql数据类型 mysql数据类型
数据类型1、整数类型,包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分别表示1字节、2字节、3字节、4字节、8字节整数。任何整数类型都可以加上UNSIGNED属性,表示数据是无符号的,即非负整数。 长度:整
2021-04-11
  目录