00-Redis 基础
官网保平安:https://redis.io/
Redis 简介
Redis 是一个开源的、使用 ANSI C 语言编写的、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言 API 的非关系型数据库。
SQL 遵循 ACID 规则。NoSQL(Not Only SQL)一般为分布式,而分布式一般遵循 CAP【一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)】定理。
Redis 是 NoSQL,虽然是单线程(6.0之后提供多线程),但是可处理 1 秒 10w 的并发。因为 Redis 的数据是存在内存中的,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。另外,Redis 也经常用来做分布式锁。Redis 提供了多种数据类型来支持不同的业务场景。除此之外,Redis 支持事务 、持久化、LUA 脚本、LRU 驱动事件、多种集群方案。
Redis 是一个 key-value 存储系统。和 Memcached 类似,它支持存储的 value 类型相对更多,包括 string(字符串)、list(链表)、set(集合)、zset(sorted set-有序集合)和 hash(哈希类型)。这些数据类型都支持 push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis 支持各种不同方式的排序。
与 memcached 一样,为了保证效率,数据都是缓存在内存中。但是 Redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且实现了 master-slave(主从)同步。
Redis 优势
- 性能极高:因为数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1),Redis 读的速度是 110000 次/s,写的速度是 81000 次/s。
- 丰富的数据类型:Redis 支持 Strings、Lists、Hashes、Sets 及 Sorted Set 数据类型操作。
- 支持事务:Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI 和 EXEC 指令包起来。
- 丰富的特性:Redis 还支持 publish/subscribe、通知、按 key 设置过期时间,过期后将会自动删除。
为什么要用 Redis?
1、访问速度更快
传统数据库数据保存在磁盘,而 Redis 基于内存,内存的访问速度比磁盘快很多。引入 Redis 之后,我们可以把一些高频访问的数据放到 Redis 中,这样下次就可以直接从内存中读取,速度可以提升几十倍甚至上百倍。
2、高并发
一般像 MySQL 这类的数据库的 QPS 大概都在 4k 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 5w+,甚至能达到 10w+(就单机 Redis 的情况,Redis 集群的话会更高)。
QPS(Query Per Second):服务器每秒可以执行的查询次数;
由此可见,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。
3、功能全面
Redis 除了可以用作缓存之外,还可以用于分布式锁、限流、消息队列、延时队列等场景,功能强大!
Redis 为什么这么快 ★★★★★
- **完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1)**;
- 采用单线程(6.0之后提供多线程),避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 使用 I/O 多路复用模型;
- 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
- 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
- Redis 通信协议实现简单且解析高效。
Redis 与其他 key-value 存储有什么不同
- Redis 有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。
- Redis 的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
- Redis 运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样 Redis 可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的, 因为他们并不需要进行随机访问。
Memcache 与 Redis 的区别都有哪些共同点和区别
共同点:
- 都是基于内存的数据库,一般都用来当做缓存使用。
- 都有过期策略。
- 两者的性能都非常高。
区别:
- 数据类型:Redis 支持更丰富的数据类型(支持更复杂的应用场景)。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
- 数据持久化:Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memcached 把数据全部存在内存之中。也就是说,Redis 有灾难恢复机制而 Memcached 没有。
- 集群模式支持:Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 自 3.0 版本起是原生支持集群模式的。
- 线程模型:Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。(Redis 6.0 针对网络数据的读写引入了多线程)
- 特性支持:Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言。
- 过期数据删除:Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。
- value 值大小不同。Redis 最大可以达到 1gb。Memecache 只有 1mb。
- 速度不同。Redis 的速度比 Memecache 快很多。
- 备份不同。Redis 支持数据的备份,也支持 master-slave 模式的数据备份。
什么是 Redis Module?有什么用?
Redis 从 4.0 版本开始,支持通过 Module 来扩展其功能以满足特殊的需求。这些 Module 以动态链接库(so 文件)的形式被加载到 Redis 中,这是一种非常灵活的动态扩展功能的实现方式,值得借鉴学习!
我们每个人都可以基于 Redis 去定制化开发自己的 Module,比如实现搜索引擎功能、自定义分布式锁和分布式限流。
目前,被 Redis 官方推荐的 Module 有:
- RediSearch:用于实现搜索引擎的模块。
- RedisJSON:用于处理 JSON 数据的模块。
- RedisGraph:用于实现图形数据库的模块。
- RedisTimeSeries:用于处理时间序列数据的模块。
- RedisBloom:用于实现布隆过滤器的模块。
- RedisAI:用于执行深度学习/机器学习模型并管理其数据的模块。
- RedisCell:用于实现分布式限流的模块。
- ……
关于 Redis 模块的详细介绍,可以查看官方文档:https://redis.io/modules。
除了 Redis,你还知道其他分布式缓存方案吗?
如果面试中被问到这个问题的话,面试官主要想看看:
- 你在选择 Redis 作为分布式缓存方案时,是否是经过严谨的调研和思考,还是只是因为 Redis 是当前的“热门”技术。
- 你在分布式缓存方向的技术广度。
如果你了解其他方案,并且能解释为什么最终选择了 Redis(更进一步!),这会对你面试表现加分不少!
下面简单聊聊常见的分布式缓存技术选型。
分布式缓存的话,比较老牌同时也是使用的比较多的还是 Memcached 和 Redis。不过,现在基本没有看过还有项目使用 Memcached 来做缓存,都是直接用 Redis。
Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来,随着 Redis 的发展,大家慢慢都转而使用更加强大的 Redis 了。
有一些大厂也开源了类似于 Redis 的分布式高性能 KV 存储数据库,例如,腾讯开源的 Tendis 。Tendis 基于知名开源项目 RocksDB 作为存储引擎,100% 兼容 Redis 协议和 Redis4.0 所有数据模型。关于 Redis 和 Tendis 的对比,腾讯官方曾经发过一篇文章:Redis vs Tendis:冷热混合存储版架构揭秘 ,可以简单参考一下。
不过,从 Tendis 这个项目的 Github 提交记录可以看出,Tendis 开源版几乎已经没有被维护更新了,加上其关注度并不高,使用的公司也比较少。因此,不建议你使用 Tendis 来实现分布式缓存。
目前,比较业界认可的 Redis 替代品还是下面这两个开源分布式缓存(都是通过碰瓷 Redis 火的):
- Dragonfly:一种针对现代应用程序负荷需求而构建的内存数据库,完全兼容 Redis 和 Memcached 的 API,迁移时无需修改任何代码,号称全世界最快的内存数据库。
- KeyDB: Redis 的一个高性能分支,专注于多线程、内存效率和高吞吐量。
不过,个人还是建议分布式缓存首选 Redis ,毕竟经过这么多年的考验,生态也这么优秀,资料也很全面!
PS:篇幅问题,我这并没有对上面提到的分布式缓存选型做详细介绍和对比,感兴趣的话,可以自行研究一下。
一个字符串类型的值能存储最大容量是多少
512M
一个 Redis 实例最多能存放多少的 keys?List、Set、Sorted Set 他们最多能存放多少元素?
理论上 Redis 可以处理多达 2**32
的 keys。
有人进行了实际测试,每个实例至少存放了 2 亿 5 千万的 keys。任何 list、set、和 sorted set 都可以放 2**32
个元素。换句话说, Redis 的存储极限是系统中的可用内存值。
Redis 中数据库默认是多少个 db
Redis 默认有 16 个 db,db0~db15
(可以通过配置文件支持更多,无上限),并且每个数据库的数据是隔离的不能共享。
注意:多个数据库之间并不是完全隔离的,比如 FLUSHALL 命令可以清空一个 Redis 实例中所有数据库中的数据。
Redis 如何选择数据库
- Redis 单机版默认有 16 个数据库,使用
select 1
进行切换。 - Redis 集群目前无法做数据库选择, 默认在 0 数据库。
Redis 如何设置密码及验证密码
开启:requirepass 1234
认证:auth 1234
查看 Redis 使用情况及状态信息用什么命令?
info
修改配置不重启 Redis 会实时生效吗
针对运行实例,有许多配置选项可以通过 CONFIG SET
命令进行修改,而无需执行任何形式的重启。 从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。
检索 CONFIG GET *
命令获取更多信息。
但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。
Redis 使用规范
实际使用 Redis 的过程中,我们尽量要准守一些常见的规范,比如:
- 使用连接池:避免频繁创建关闭客户端连接。
- 尽量不使用 O(n)指令,使用 O(n) 命令时要关注 n 的数量:像
KEYS *
、HGETALL
、LRANGE
、SMEMBERS
、SINTER
/SUNION
/SDIFF
等 O(n) 命令并非不能使用,但是需要明确 n 的值。另外,有遍历的需求可以使用HSCAN
、SSCAN
、ZSCAN
代替。 - 使用批量操作减少网络传输:原生批量操作命令(比如
MGET
、MSET
等等)、pipeline、Lua 脚本。 - 尽量不适用 Redis 事务:Redis 事务实现的功能比较鸡肋,可以使用 Lua 脚本代替。
- 禁止长时间开启 monitor:对性能影响比较大。
- 控制 key 的生命周期:避免 Redis 中存放了太多不经常被访问的数据。
- ……
相关文章推荐:阿里云 Redis 开发规范 。
假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,怎么把它们全部找出来
Redis 有一个 KEYS
命令。
语法:
KEYS pattern
该命令所支持的匹配模式如下:?
:用于匹配单个字符。例如h?llo
可以匹配hello、hallo、hxllo
等。*
:用于匹配零个或者多个字符。例如,h*llo
可以匹配hllo、heeeello
等。[]
:可以用来指定模式的选择区间。例如h[ae]llo
可以匹配hello、hallo
,但是不能匹配 hillo。同时,可以使用“/”符号来转义特殊的字符。
作用:返回与指定模式相匹配的所用的 keys。
注意: KEYS
的速度非常快,但如果数据太大,内存可能会崩掉。
对方接着追问:如果这个 Redis 正在给线上的业务提供服务,那使用 KEYS 指令会有什么问题?
这个时候你要回答 Redis 关键的一个特性:Redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。
如果 Redis 中的某个列表中的数据量非常大,如果实现循环显示每一个值
通过 scan_iter
分片取,减少内存压力
1 |
|
Redis 的内存占用情况怎么样
举个例子:100 万个键值对(键是 0 到 999999 值是字符串 hello world
)在 32 位的 Mac 笔记本上用了 100MB。同样的数据放到一个 key 里只需要 16MB,这是因为键值有一个很大的开销。在 Memcached 上执行也是类似的结果,但是相对 Redis 的开销要小一点点,因为 Redis 会记录类型信息引用计数等等。
64 位的系统比 32 位的需要更多的内存开销,尤其是键值对都较小时,这是因为 64 位的系统里指针占用了 8 个字节。当然,64 位系统支持更大的内存,所以为了运行大型的 Redis 服务器或多或少的需要使用 64 位的系统。
Redis 集群会有写操作丢失吗
Redis 不能保证数据的强一致性,所以在特定条件下可能会丢失数据,比如:
- 最大内存不足并且使用了
no-enviction
(再次写入返回报错)内存淘汰机制。 - 未开启持久化的 Master 故障重启,Slave 再次同步。
- 网络波动,哨兵主动选举,主从切换的期间。
pipeline 是什么,Pipeline 有什么好处,为什么要用 pipeline
管道(pipeline)可以一次性发送多条命令并在执行完后一次性将结果返回,pipeline 通过减少客户端与 Redis 的通信次数来实现降低往返延时时间,而且 Pipeline 实现的原理是队列,而队列的原理是先进先出,这样就保证数据的顺序性。
通俗点:pipeline 就是把一组命令进行打包,然后一次性通过网络发送到 Redis。同时将执行的结果批量的返回回来。
使用 redis-benchmark 进行压测的时候可以发现影响 Redis 的 QPS 峰值的一个重要因素是 pipeline 批次指令的数目。
如何解决 Redis 的并发竞争 Key 问题
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!
如果对这个 key 操作,不要求顺序。
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做 set 操作即可,比较简单。
推荐一种方案:分布式锁(Zookeeper 和 Redis 都可以实现分布式锁)。如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能。
基于 Zookeeper 临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在 Zookeeper 上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,当然是从以可靠性为主。所以首推 Zookeeper。
如果对这个 key 操作,要求顺序。
假设有一个 key1,系统A 需要将 key1 设置为 valueA,系统B 需要将 key1 设置为 valueB,系统C 需要将 key1 设置为 valueC。期望按照 key1 的 value 值按照
valueA–>valueB–>valueC
的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下- 系统A:
key 1 = {valueA 3:00}
- 系统B:
key 1 = {valueB 3:05}
- 系统C:
key 1 = {valueC 3:10}
那么,假设这会 系统B 先抢到锁,将 key1 设置为
{valueB 3:05}
。接下来 系统A 抢到锁,发现自己的 valueA 的时间戳早于缓存中的时间戳,那就不做 set 操作了。以此类推。- 系统A:
中间件 canal。
一致性 hash 减少并发。
利用队列,将 set 方法变成串行访问。
5TB 的硬盘上放满了数据,请写一个算法将这些数据进行去重。如果这些数据是一些 32bit 大小的数据该如何解决?如果是64bit 的呢?
位图(Bitmap)。
缺点是 Bitmap 对于每个元素只能记录 1bit 信息,如果还想完成额外的功能,只能靠牺牲更多的空间、时间来完成了,如使用 2bit 记录一个元素,其中 1bit 记录是否存在,1bit 记录其他信息。布隆过滤器(Bloom-Filter)。
Bloom-Filter 算法的核心思想就是利用多个不同的 Hash 函数来解决“冲突”。通过引入k(k>1)
个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
Hash 存在一个冲突(碰撞)的问题,用同一个 Hash 得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个 Hash,如果通过其中的一个 Hash 值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的 Hash 函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是 Bloom-Filter 的基本思想。
Bloom-Filter 一般用于在大数据量的集合中判定某元素是否存在。