前言
Redis是当前炙手可热的NoSQL数据库,几乎已经成为高并发、高可用系统的标配了。若是对Redis响应快的认知仅仅停留在基于内存和单线程的层面,恐怕是很难斩获大厂的Offer。本篇重点全面介绍Redis的单线程模型及如何支撑高并发的。
1 Redis完全基于内存
Redis能够支撑高并发的第一个重要的原因就是完全基于内存,这在很大程度上避免了诸如MySQL等关系型数据库需要频繁的磁盘I/O(磁盘I/O的寻道时间、旋转延迟、传输时间等很耗时,皆是ms级的开销,而基于内存基本都是ns级的开销,完全不是一个数量级)。
2 Redis的线程模型
2.1 阻塞式I/O模型Blocking I/O
说到I/O模型,大家一定能想到传统意义上的Blocking I/O,该模型的工作方式是:当对某一个文件描述符(File Descriptor,以下简称FD)读写时,如果当前FD不可读或不可写,则不会对其它进程的操作做出响应,导致整体服务不可用。
文件描述符:在形式上是一个非负整数。实际上它指向内核为每一个进程所维护的该进程打开文件的记录表。
2.2 I/O多路复用
在 I/O 多路复用模型中,最重要的函数调用就是 select,该方法的能够同时监控多个文件描述符的可读可写情况,当其中的某些文件描述符可读或者可写时,select 方法就会返回可读以及可写的文件描述符个数。
2.3 Redis的单线程模型
Redis内部使用文件事件处理器File event handler,该处理器是单线程的,所以Redis才叫做单线程模型。它采用 I/O 多路复用机制同时监听多个 Socket,根据 Socket上的事件来选择对应的事件处理器进行处理。Redis服务采用Reactor的方式来实现文件事件处理器,如下图所示:
综上分析,Redis支撑高并发的第二个原因就是基于非阻塞式的I/O多路复用机制,是单线程模型,这在很大程度上避免了多线程频繁上下文切换的消耗。
3 Redis丰富灵活的数据类型
Redis提供了string、hash、list、set、sorted set五种基本数据结构,并且还包含了HyperLogLog、Pub/Sub等多种扩展的优秀数据结构。
Redis中数据结构不仅仅是丰富,阅读源码你还会发现,设计也很巧妙很灵活,如string底层不同于C语言的实现,而是通过一种SDS(Simple Dynamic String)
结构实现的,本质上是一个带长度信息的字节数组。这里少侠以字典hash为例,介绍Redis是如何做到快速响应的。
3.1 hash内部结构
Redis中的hash和Java中的HashMap几乎一样,都是通过分桶的方式存储元素并解决hash冲突,第一维是数组,第二维是链表,如下图所示,数组中保存的是链表中第一个元素的指针。
3.2 hash函数的选择
在介绍Redis的hash函数前,少侠想先引入一个概念:hash攻击,简而言之,就是因为hash函数具有偏向性,黑客会根据这种偏向性进行攻击,导致hash的链表长度极不均匀,甚至所有的元素都集中到一个链表,最终会导致查询性能急剧下降(从O(1)退化到O(n))。
那么Redis如何设计hash函数的呢?不同于Java中的hash算法,Redis实际上采用了一种称为siphash
算法,这个算法是兼顾了随机性和性能的结果。
3.3 渐进式rehash
说到hash,不可避免的就是扩容,但是Redis是单线程的,并且一些热点数据频繁被请求,如果此时扩容Redis很难承受这样的耗时过程。所以Redis采用了渐进式rehash策略。
渐进式rehash会在rehash的同时,保留新旧两个hash结构,如下图所示,查询时会同时查询两个hash结构,然后会在操作指令(hget,hdel)中实现数据迁移,但是如果客户端一直没有命令来触发此类操作,Redis会在定时任务中主动对hash进行数据迁移。
4 Redis如何解决并发竞争问题
- 客户端加锁(ReentrantLock或synchronized),但此方式只限于单机加锁,无法解决分布式系统的并发竞争问题。
- 乐观锁(redis 的命令 watch):
当执行多键值事务操作时,Redis 不仅要求这些键值需要落在同一个 Redis 实例上,还要求落在同一个 slot 上,所以 redis 的事务比较鸡肋,不过可以想办法遵循 redis 内部的分片算法把设计到的所有 key 分到同一个 slot。 - redis 的 setnx 实现分布式锁:
要设置超时时间,防止抢占到锁的客户端因失败、崩溃或其他原因没有办法释放锁而造成死锁。
小结
Redis为何这么快其实是个涉及面很广泛的问题,面试者如果仅仅停留在基于内存和多路IO复用这些概念层的理解,在面试中是要吃亏的。熟练运用,深入原理才能在面试中斩获佳绩。
我是少侠露飞,热爱技术,热爱分享。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。