Redis
Redis是为了解决什么问题?
Redis是什么?
- Redis:RemoteDictionaryServer(远程数据服务),是一个开源的、基于内存的数据结构存储器,可以用作数据库、缓存和消息中间件,最受欢迎的NoSQL数据库之一。
- 遵守BSD协议:基本上是为所欲为,不作恶的意思。可以二次开发后商业销售,但是不可以借用原软件的名义进行推广。开源但尊重原作者。
- 单操作原子性,多操作支持事务。
Redis能干什么?(广)
-
解决高并发。
-
丰富数据类型,数据结构灵活,方便扩展。(key-value)(String、lists、hash、set、Zset等)
-
支持大数据存储。
Redis是如何实现解决问题?
Redis是单线程的吗?
- 线程是进程的一个逻辑流:逻辑流即串行执行逻辑。
- I/O多路复用(事件驱动):多个I/O操作都共用一个逻辑流。当某个socket可以读写的时候,操作系统会进行通知。所以非阻塞socket只有当系统通知描述符可读/写,才会去执行读/写操作。操作系统的通知功能通过select/poll/epoll/kqueue之类的系统调用函数来使用,且调用函数都可以同时监视多个描述符的读写就绪状况,多个描述符的I/O操作都能在一个线程内并发交替地顺序完成,即:I/O多路复用。“复用”指的是复用同一个线程。
- Redis的Server是单线程服务器,基于Event-Loop模式来处理Client请求。不过对于需要依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。
Redis支持事务吗?
redis事务的特性
-
传统关系型数据库为了保证数据的完整性和一致性,事务需要满足ACID四个特性,即:atomicity(原子性)、consistency(一致性)、isolation(隔离性)、durability(持久性)。那么,redis的事务是否也保障上面四个事务特性?(编译阶段实行连坐,执行阶段冤有头债有主。)
- redis会将所有命令按照顺序序列化,存储到磁盘。然后,顺序的执行。执行过程中,不会被其他客户端发来的命令请求中断。
- 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在事务内的查询要看到事务里的更新,在事务外查询不能看到这个让人万分头痛的问题。
- 不保证原子性:运行阶段,redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。编译阶段,语法错误会实行连坐,有错则都不通过。(整个事务内的命令,在入“命令”队列的时候,如果检查出错误,则整体不会被执行。如果队列中的命令有错但是没有被检查出来,则仅错误命令会被跳过(如果将错误的命令理解为执行效果为空,也可以理解为满足原子性)。暂且理解,Redis是实现了编译原子性,运行失败部分丢弃的原则。)
备注:如果本地执行事务执行了一部分,宕机,则下一次启动服务器,会检查上述状态并报错,需要redis-check-aof工具删除执行不完全的事务,才能再次正常运行服务器。
小结:传统关系型数据库之所以要保障ACID,原因在于事务提交的二阶段中,一阶段prepare是真实执行的,事务的并发执行可能会导致事务间的动作看到数据不一致的情况出现。而redis实际上可以理解为串行化分批执行事务,也就是关系型数据库事务的最高隔离级别:串行化。
又是如何保障的呢?具体实现是通过事务命令。
1. discard,取消事务,放弃执行事务块内的所有命令。
2. exec,执行所有事务块内的命令。
3. multi,标记一个事务块的开始。
4. unwatch,取消watch命令对所有key的监视。
5. watch key [key...],监视一个或多个key,如果在事务执行之前,key有被其他命令所改动,则事务将被打断。具体是依靠redis的锁cas(check and set),其类似于乐观锁(类比乐观锁的标记,如版本号解ABA问题),如果事务提交之前相关数据发生了改变,那么事务将提交失败。
Redis执行的3个阶段?
1. 开启:以MULTI开始一个事务。
2. 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面。
3. 执行:由EXEC命令触发事务。
Redis事务队列实现?
1. 生产者(PUSH)/消费者(POP)模式。
2. 基于阻塞队列实现:可先生产再消费,也可消费者先阻塞获取等待生产者入队。但获取方式只有阻塞获取。
3. 发布(PUB)-订阅(SUB)模式,可以订阅给定一个或者多个频道信息。
Redis哨兵模式(sentinel)?
1. 手动主从切换技术:当主服务器宕机后,需要手动把一台服务器切换为主服务器。
2. 为了避免手动切换成本,优先考虑哨兵模式。
3. 哨兵,作为进程独立运行。通过发送命令监控Redis服务器(包括主从)是否正常运行。(类比心跳检测)
4. 发现主服务器宕机,会自动将slave切换成master,然后通过发布-订阅模式通知其他从服务器,修改配置文件,让它们切换主机。即:故障切换(failover)的过程。
Redis多哨兵模式(sentinel)?
1. 多个哨兵协作检测。
2. 哨兵之间互相检测。
3. 主观下线:仅单个哨兵认为主服务器不可用。
4. 客观下线:当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
Redis实现分布式锁?
基本特性:
1. 保证互斥性。
2. 保证无死锁。
3. 保证上锁和解锁都是一个客户端。
实现思路:
setnx命令。(SET if not exists)
单节点Redis实现分布式锁:
- ClientA获取Lock后,长时间阻塞(比如:Full GC STW),然后Lock Expire后仍然阻塞,导致ClientB可以获取Lock,但是ClientA阻塞之后释放Lock(此时ClientB持有Lock),导致错误释放了ClientB的锁。(解法:为每个client分配一个unique的string值。比如:UUID生成unique的string值。)备注:正常仅锁的持有者具备操控锁的能力。锁过期导致持有者本身在逻辑层面无法感知锁的操控权限的转移,进而导致误释放,导致错误的原因在于“锁不识主”,解法是让锁有所标。
多节点实现分布式锁:RedLock(Redis Distributed Lock)
- 多节点要考虑:
- 加锁成功的判别标准?是同步的算法吗? 答:大多数成功则认为成功,即过半。一个client锁定大多数事例耗费的时间大于或接近锁的过期时间,就认为锁无效,并且解锁这个redis实例(不执行业务)。只要在TTL时间内成功获取一半以上的锁便是有效锁,否则无效。 时钟漂移(clock drift)相对于TTL时间很短,可以忽略,如此理解,可以看作是同步的。
- 有效锁的时长是多少?申请锁的等待时长如何设定?获取锁失败重试吗? 答:client有效获取锁的最小时间为TTL(Time To Live)-(T2-T1)-时钟漂移(clock drift)。申请锁的等待时长时间要小于TTL。client不能获取锁时,应该在随机时间后重试,并且最好在同一时间把所有的set命令发送给所有的Redis实例。同时,对于获得锁的client在完成工作后要主动及时释放锁,避免TTL锁失效的机制触发,以提高锁交替的整体性能。
- 宕机了怎么办? 答:选举新的slave节点为master节点,同时可以释放死锁。
- 新的master节点如何保障之前的锁定逻辑的一致性? 答:TTL后生效或者TTL后再选举,防止新的master与之前的master相比,因为不知道已有的锁定逻辑,而无法分配后续的加锁申请,导致重复加锁,丢掉互斥性。
- 崩溃恢复的策略:
5.1 如果没有持久化,则ClientA获得锁成功之后,所有Redis重启,ClientB又可以获得锁,违反了锁的排他互斥性。
5.2 如果启用AOF持久化存储,由于Redis的过期机制是根据Unix时间戳来判别的,所以在重启后,会按照既定的时间过期,不会影响业务。
5.3 AOF默认更新同步到磁盘的时间是1秒,所以如果1秒内断电,则数据会丢失(断电保护)。如果实时更新(每个命令都立刻同步到磁盘),会造成性能急剧下降,需要在性能和数据一致性方面作取舍。
5.4 一种方法是,AOF默认使用每秒刷盘(保证性能),然后设置约定,Redis无论什么原因停掉后要等待TTL时间后再重启(延迟重启),缺点是TTL时间内相当于服务暂停(不可用)。 - 如何保障异常情况可以自动释放死锁? 答:“让之前的锁定随着时间而过期失效”,设定TTL。由于释放锁时会判断这个锁的value是不是自己设置的,如果是才删除;所以在释放锁时非常简单,只要向所有实例都发出释放锁的命令,不用考虑能否成功释放锁。
- 为什么以故障转移为基础的分布式锁是不够的? 答:因为Redis进行主从复制的过程是异步的,ClientA获取锁后,主Redis复制数据到从Redis时宕机,导致主Redis数据没有成功复制到从Redis中,信息不对称会导致从Redis转为新的主Redis后,能够继续获取锁,进而导致锁的排他性错误。
什么是Lua脚本?Redis用Lua脚本?
- Lua由标准C编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。
- 一个完整的Lua解释器不过200k,
在所有脚本引擎中,Lua的速度是最快的
。这一切都决定了Lua是作为嵌入式脚本的最佳选择。 - 同时支持面向过程(procedure-oriented)编程和函数式编程(functional programming);自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
- Redis使用同一个Lua解释器来执行所有命令,同时,Redis保证以一种原子性的方式来执行脚本:当lua脚本在执行的时候,不会有其他脚本和命令同时执行,这种语义类似于MULTI/EXEC(串行独有)。从别的客户端的视角来看,一个lua脚本要么不可见,要么已经执行完。然而这也意味着,执行一个较慢的lua脚本是不建议的,由于脚本的开销非常低,构造一个快速执行的脚本并非难事。但是你要注意到,当你正在执行一个比较慢的脚本时,所有其他的客户端都无法执行命令。
什么是闭包?
- 闭:封闭。封闭的逻辑块、一个函数。
- 包:包含。子函数被包含于母函数。子函数包含母函数变量的使用权。所以,闭包可以理解为被包裹状态下作用域的延伸。
- 正常程序是如何区分局部变量与全局变量的?在函数内访问某个变量是根据函数作用域链来判断变量是否存在的,而
函数作用域链是程序根据函数所在的执行环境栈来初始化的。栈是先进后出,所以同名局部变量优先级在全局变量之上
。 - 闭包,子函数包含母函数变量的使用权,是如何达成的?由于子函数引用母函数,所以内层次执行环境并没有被销毁,程序执行完成,内外层次才会被销毁。《JavaScript高级编程》书中建议:
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多内容,过度使用闭包,会导致内存占用过多
。
什么是函数式编程?
- 函数式编程关心数据的映射,命令式编程关心解决问题的步骤。
- 典型的语言:LISP
- 特性:
3.1 闭包和高阶函数:函数是第一公民
,可以和其他数据类型一样进行赋值。函数即表达式即确定的值,可以看作是一个“惰性加载”的变量
。
3.2 惰性计算:表达式不是在绑定到变量时立即计算,而是在求值程序需要产生表达式的值时进行计算
。也就是避免了使用变量,即避免了内存泄漏。
3.3 递归:递归做为控制流程的机制。例如,Lisp 处理的列表定义为在头元素后面有子列表
,这种表示法使得它自己自然地对更小的子列表不断递归
。
3.4 不修改状态:上一点已经提到,函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。在其他类型的语言中,变量往往用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。
3.5 因为 FP 语言不包含任何赋值语句,变量值一旦被指派就永远不会改变。而且,调用函数只会计算出结果 ── 不会出现其他效果。因此,FP 语言没有副作用。 - 优点:
4.1 函数式编程不需要考虑"死锁"(deadlock)
,因为它不修改变量(函数间是完全隔离独立的),所以根本不存在"锁"线程的问题。
4.2函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同
。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
4.3代码的热升级
:函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机。
4.4接近自然语言
,易于理解。
4.5代码简洁
,开发快速。
什么是编程范式(programming paradigm)?
- 如果编程是在创造虚拟世界,那么编程范式就是对应的
世界观和方法论
。 - 编程是在解决问题,解决问题有多种视角和思路,其中普适且行之有效的模式被归结为
范式
。 - 由于着眼点和思维方式的不同,相应的范式自然各有侧重和倾向,因此一些范式常用‘oriented’来描述。换言之,
每种范式都引导人们带着某种的倾向去分析问题、解决问题,这不就是“导向”吗?
如果把一门编程语言比作兵器,它的语法、工具和技巧等是招法,它采用的编程范式则是心法。编程范式是抽象的,必须通过具体的编程语言来体现。它代表的世界观往往体现在语言的核心概念中,代表的方法论往往体现在语言的表达机制中。一种范式可以在不同的语言中实现,一种语言也可以同时支持多种范式
。
常见的编程范式有哪些?
- 命令式编程: 如常见的Java编程。一步一步告诉计算机先做什么再做什么。
- 声明式编程: 如SQL语句。以数据结构的形式来表达程序执行的逻辑。告诉计算机应该做什么,但不指定具体要怎么做。特点是它不需要创建变量用来存储数据,它不包含循环控制的代码如for,while。
- 函数式编程: 如Java 8 Stream API。与声明式编程思想相同,只关注做什么而不是怎么做。
Redis可以持久化存储吗?
第一种:AOF日志追加
- AOF持久化存储便是以日志的形式将redis存储在aof_buf缓冲区中的数据写入到磁盘中。简而言之,就是记录redis的操作日志,将redis执行过的命令记录下来,当我们需要数据恢复时,redis去重新执行一次日志文件中的命令。
第二种:RDB数据快照
- 存在内存中的数据以
快照的形式
保存在本地磁盘中。分为自动备份和手动备份。
什么是数据库快照?
- 数据库快照,正如其名称所示那样,是数据库
在某一时间点的视图
。快照设计最开始的目的是为了报表服务。比如我需要出2011的资产负债表,这需要数据保持在2011年12月31日零点时的状态,则利用快照可以实现这一点。oracle数据库的快照是一个表,它包含有对一个本地或远程数据库上一个或多个表或视图的查询的结果。正因为快照是一个主表的查询子集,使用快照可以加快数据的查询速度
;在保持不同数据库中的两个表的同步中,利用快照刷新
,数据的更新性能也会有很大的改善。
Redis支持哪些数据类型?
- string(字符串):该类型是二进制安全的,可以包含任何数据,比如jpg图片或者序列化对象。
- hash(哈希):是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
- list(列表):是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)。
- set(集合):是string类型的无序集合,集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
- zset(sorted set:有序集合):zset 和 set 一样也是string类型元素的集合,且不允许重复的成员,不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
Redis涉及通讯协议有哪些?
Redis客户端通讯协议:RESP(Redis Serialization Protocol),其特点是:
- 简单。
- 解析速度快。
- 可读性好。
Redis集群内部通讯协议:RECP(Redis Cluster Protocol ) ,其特点是:
- 每一个node两个tcp连接。
- 一个负责client-server通讯。
- 一个负责node之间通讯。
Redis特点有哪些?
Redis稳定吗?(稳)
- 支持主从复制(sentinel哨兵集群监控类比心跳检测)。
- 可以配置集群,支撑大型项目。
Redis存取效率如何?(快)
- 可以读写分离。
- 内存存储,支持周期性数据同步磁盘文件。绝大部分请求是纯粹的内存操作。(支持持久化)
- 采用单线程,避免了不必要的上下文切换和竞争条件。
- 非阻塞IO - IO多路复用。(内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。)
- 若存取一个256字节字符串,读取速度可
高达110000次/s,写速度高达81000次/s
。
Redis操作便捷吗?(易)
- 提供并集、交集、排序等便捷操作。
Redis什么语言编写实现的?
- 最“接近”操作系统的语言编写,执行速度相对较快。
Redis有什么技术亮点?(巧)
- Redis巧妙的使用了
SDS
避免了C字符串的缺陷。在SDS中,buf数组的长度不一定就是字符串的字符数量加一,buf数组里面可以包含未使用的字节,而这些未使用的字节由free属性记录。 - 空间预分配的策略,避免C字符串每一次修改时都需要进行内存重分配的耗时操作,将内存重分配从原来的每修改N次就分配N次—>
降低到了修改N次最多分配N次
。
Redis作为缓存,如何解决缓存面临的问题?
缓存有穿透->Redis穿透
- 针对第一次访问:服务器启动时,提前写入。
- 针对恶意访问:规范key命名,通过中间件拦截。
缓存有击穿->Redis击穿
- 针对key过期:高频Key,设置合理TTL或永不过期。
缓存雪崩->Redis雪崩
redis宕机:使用Redis集群,限流。
Redis的作用?Redis哪些应用?
1、获取最新的n个数据;
2、获取TOP N的数据;
3、设置精准的抢购时间;
4、实现计数器;
5、去除重复值;
6、利用set命令制作反垃圾系统;
7、构建队列系统。
- 大型网站UV达百万,数据库查询消耗大,放Redis中。(缓存)
- 实时变化与展示的功能,可以借助Redis。
- 消息队列,经常用来构建类似实时聊天系统的功能,大大提高应用的可用性。
类似的解决问题的方案(产品)有哪些,Redis与它们相比有什么异同(优势)?
Redis与memcache的对比?
- Redis跟memcache不同的是,储存在Redis中的数据是
支持持久化到磁盘的,断电或重启后,数据也不会丢失
。因为Redis的存储分为内存存储
、磁盘存储
和log文件
三部分,重启后,Redis可以从磁盘重新将数据加载到内存中,这些可以通过配置文件对其进行配置
,正因为这样,Redis才能实现持久化。 - Memcached所有的值均是简单的字符串,
redis
作为其替代者,支持更为丰富的数据类型
。 Redis
的速度比Memcached快
。memcache
为临时数据存储、偶尔数据丢失。读多写少
,读QPS达到万级别以上。
其他类似产品?
- Apache Cassandra:(社区内一般简称为C*)这是一套开源分布式NoSQL数据库系统。它最初由Facebook开发,用于储存收件箱等简单格式数据,集Google BigTable的数据模型与Amazon Dynamo的完全分布式架构于一身。Facebook于2008将 Cassandra 开源,由于其良好的可扩展性和性能,被 Apple、Comcast、Instagram、Spotify、eBay、Rackspace、Netflix等知名网站所采用,成为了一种流行的分布式结构化数据存储方案。
- Mongo是一个基于分布式文件存储、面向文档的NoSQL数据库,由C++编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系型数据库的,它支持的数据结构非常松散,是一种类似json的BSON格式。
Redis(Redis5.0新增特性stream)与metaQ的对比?
Related Knowledge
OLAP和OLTP是什么?
- on-line analysis processing(OLAP,联机事务处理):日常操作,比如,基于事务的
数据库
的实时
存取、统计。 - on-line transaction processing(OLTP,联机分析处理):分析决策,比如,基于
复杂查询
的数据仓库
海量多维
数据分析。
Reference
- https://blog.csdn.net/wenbingoon/article/details/9004512(IO模型及select、poll、epoll和kqueue的区别)
- https://blog.csdn.net/Design407/article/details/103242874?utm_medium=distribute.pc_relevant_t0.none-task-blog-2defaultBlogCommendFromMachineLearnPai2default-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2defaultBlogCommendFromMachineLearnPai2default-1.control(面试还搞不懂redis,快看看这40道面试题(含答案和思维导图))
- https://blog.csdn.net/qq_29846663/article/details/68066953(Redis是什么)
- https://redis.io/(Redis官网)
- https://www.runoob.com/redis/redis-tutorial.html(Redis菜鸟教程)
- https://blog.csdn.net/hukaijun/article/details/81010475
- https://zhuanlan.zhihu.com/p/329964428
- https://www.cnblogs.com/powertoolsteam/p/redis.html(Redis是什么?看这一篇就够了)
- https://blog.csdn.net/u013967628/article/details/84501515(Redis的事务功能详解)
- https://blog.csdn.net/xuheng8600/article/details/80334971(分布式关系数据库(OLAP、OLTP)的介绍和比较)
- https://blog.csdn.net/weixin_39040059/article/details/79120459(Redis事务(Transaction))
- https://blog.csdn.net/weixin_34191845/article/details/87988667?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control(Redis 为什么用跳表而不用平衡树?)⌛️
- https://mp.weixin.qq.com/s?__biz=Mzg5MDEzMjEwNw==&mid=2247493165&idx=2&sn=e9b78a4a11bcba055fde8a8c914bba57&chksm=cfe3e5c9f8946cdfdec00e7dc34d7829eab4e44aa0336e81347e390887a940a3b3186c24d7c9&scene=132#wechat_redirect(万字长文:选 Redis 还是 MQ,终于说明白了!)
- https://www.jianshu.com/p/06ab9daf921d(Redis哨兵(Sentinel)模式)
- https://www.cnblogs.com/sandaizi/p/11582488.html(什么是闭包?闭包的优缺点?)
- https://baike.baidu.com/item/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/4035031(函数式编程)
- https://www.cnblogs.com/jiangyinh/p/10432497.html(什么是编程范式?)
- https://zhuanlan.zhihu.com/p/356277655(【Redis】如何保证原子操作)
- https://blog.csdn.net/zhang197093/article/details/108167598(Redis中Lua脚本是如何保证操作的原子性的)
- https://mp.weixin.qq.com/s?__biz=MzU0OTk3ODQ3Ng==&mid=2247483893&idx=1&sn=32e7051116ab60e41f72e6c6e29876d9&chksm=fba6e9f6ccd160e0c9fa2ce4ea1051891482a95b1483a63d89d71b15b33afcdc1f2bec17c03c&mpshare=1&scene=23&srcid=1121Vlt0Mey0OD5eYWt8HPyB#rd(拜托,面试请不要再问我Redis分布式锁的实现原理【石杉的架构笔记】)
- https://www.cnblogs.com/williamjie/p/11250679.html(Redis分布式锁的实现原理)
- https://www.cnblogs.com/rgcLOVEyaya/p/RGC_LOVE_YAYA_1003days.html(Redlock(redis分布式锁)原理分析)
- https://blog.csdn.net/shuningzhang/article/details/89445417(最详细的Redis通信协议规范)
- https://www.cnblogs.com/powertoolsteam/p/redis.html(Redis是什么?看这一篇就够了)
- https://www.zhangshengrong.com/p/noXQbpV61G/(Redis分布式锁实现方式及超时问题解决)
ShareAWord
There's an answer for everything.凡事皆有解。