什么是Redis
Redis是一个高性能非关系型(Nosql)的键值(K-V)数据库,与传统数据库不同,Redis的数据存在内存中,读写非常快,被广泛用于缓存.而且redis的操作是原子性的
Redis优缺点
优点:
1.基于内存操作,存取很快
2.Redis是单线程的,避免线程切换开销及多线程的竞争问题.单线程是指网络请求使用一个线程去处理,也就是一个线程处理所有的网络请求,
3.支持多种数据类型 String Hash List Set Zset等
4.支持持久化 Redis使用RDB和AOF两种持久化机制,持久化功能可以有效的避免数据丢失问题
5.支持事务,Redis的操作都是原子性的,同时Redis还支持对几个操作合并后的原子操作执行
6.支持主从复制 主节点会自动将数据同步到从节点,从而读写分离
缺点
1.对结构化查询的支持比较差
2.数据容量受到物理内存的限制,不适合用作海量数据的高性能读写,Redis更适合在较小数据量的操作
3.Redis较难支持在线扩容,在集群容量达到上限时,在线扩容会变得很复杂
Redis为什么会这么快
基于内存: Redis是使用内存存储,没用磁盘io上的开销
单线程(Redis 6.0之前):使用单线程处理请求,避免了多个线程之间线程切换和锁资源的竞争之间的开销
io多路复用 Redis 采用 IO 多路复用技术。Redis 使用单线程来轮询描述符,将数据库的操作都转换成了事件,不在网络I/O上浪费过多的时间
高效的数据结构: Redis 每种数据类型底层都做了优化,目的就是为了追求更快的速度
关于redis6.0的多线程
https://www.bilibili.com/video/BV1HA41147PD?from=search&seid=5765525821085986393&spm_id_from=333.337.0.0
概括为读取和响应操作多线程,计算单线程
Redis为什么选择单线程
1.避免过多的上下文切换的开销,程序始终在进程的单个线程中,没有多线程切换
2.避免同步机制: 如果Redis使用多线程,需要考虑数据一致性的问题
Redis 6.0的多线程
可以充分利用服务器的cpu资源,单线程模型的主线程只能使用一个cpu
多线程任务可以分摊Redis同步io的读写分离
Redis的场景
缓存热点数据
排行表 Zset
利用原子性的自增,可以实现计数器
简单的消息队列,可以使用Redis自身的发布/订阅模式或者List来实现简单的消息队列,实现异步操作。
分布式锁
Redis的数据类型
1.string 最常用的一种数据类型,String类型的值可以是字符串、数字或者二进制,但值最大不能超过512MB。
2.hash Hash 是一个键值对集合
3.Set 无序去重的集合Set 提供了交集、并集等方法,对于实现共同好友、共同关注等功能特别方便。
4.List 有序可重复的集合,底层是依赖双向链表实现的。
5.SortedSet有序Set。内部维护了一个score
的参数来实现。适用于排行榜和带权重的消息队列等场景。
特殊的数据类型:
1、Bitmap:位图,可以认为是一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在 Bitmap 中叫做偏移量。Bitmap的长度与集合中元素个数无关,而是与基数的上限有关。
2、Hyperloglog。HyperLogLog 是用来做基数统计的算法,其优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。典型的使用场景是统计独立访客。
3、Geospatial :主要用于存储地理位置信息,并对存储的信息进行操作,适用场景如定位、附近的人等。
连接docker 这里6382是我的端口
docker exec -it 8b24bf236869 redis-cli -p 6382
默认是不开启的
如需开启,需要修改配置文件redis.conf:io-threads-do-reads no,no改为yes
Keys命令存在的问题
redis是单线程的。keys指令会导致线程阻塞一段时间,直到执行完毕,服务才能恢复。scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度是O(1)
,但是要真正实现keys的功能,需要执行多次scan。
scan的缺点:在scan的过程中如果有键的变化(增加、删除、修改),遍历过程可能会有以下问题:新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键。
Redis的数据结构有哪些
Redis的数据结构有简单动态字符串、链表、字典、跳跃表、整数集合、压缩列表等。
**简单动态字符串:**大家都知道,Redis的底层是用C语言编写,但Redis并没有直接使用C语言传统的字符串表示,而是构建了一种名为简单动态字符串的抽象类型。
链表:链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。链表是列表的底层实现之一。
**字典:**字典,又称为符号表(symbol table)、关联数组(associativearray)或映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构。字典在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增、删、查、改操作也是构建在对字典的操作之上的。
整数集合: 整数集合(intset)是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。
**压缩列表(ziplist):**压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构。
跳跃表(skiplist):跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。跳跃表作是序集合键的底层实现之一。
和链表、字典等数据结构被广泛地应用在Redis内部不同,Redis只在两个地方用到了跳跃表,比如实现有序集合键
跳表相比于红黑树的优点(redis为什么用跳表不同红黑树):
- 内存占用更少,自定义参数化决定使用多少内存
- 查询性能至少不比红黑树差
- 简单更容易实现和维护
最后,说下Redis中的跳跃表和普通的跳跃表有什么区别?
- Redis中的跳跃表分数(score)允许重复,即跳跃表的key允许重复,如果分数重复,还需要根据数据内容来进字典排序。普通的跳跃表是不支持的。
- 第1层链表不是一个单向链表,而是一个双向链表。这是为了方便以倒序方式获取一个范围内的元素。
- 在Redis的跳跃表中可以很方便地计算出每个元素的排名。
过期键的删除策略
惰性删除 : 只有访问这个键时才会检查他是否过期,如果过期则清除,
可以节约cpu资源
大量过期键没有被访问,会一直占用内存
定时删除: 为每个设置过期时间的key都设置一个定时器,到了过期时间就清除
节约内存
占用cpu
定期删除: 每隔一段时间就对键进行检查,删除过期的键
惰性和定时的折中方案
Redis的内存淘汰机制
Redis是基于内存的,所以容量肯定是有限的,有效的内存淘汰机制对Redis是非常重要的。
当存入的数据超过Redis最大允许内存后,会触发Redis的内存淘汰策略。在Redis4.0前一共有6种淘汰策略。
- volatile-lru:当Redis内存不足时,会在设置了过期时间的键中使用LRU算法移除那些最少使用的键。(注:在面试中,手写LRU算法也是个高频题,使用双向链表和哈希表作为数据结构)
- volatile-ttl:从设置了过期时间的键中移除将要过期的
- volatile-random:从设置了过期时间的键中随机淘汰一些
- allkeys-lru:当内存空间不足时,根据LRU算法移除一些键
- allkeys-random:当内存空间不足时,随机移除某些键
- noeviction:当内存空间不足时,新的写入操作会报错
前三个是在设置了过期时间的键的空间进行移除,后三个是在全局的空间进行移除
在Redis4.0后可以增加两个
- volatile-lfu:从设置过期时间的键中移除一些最不经常使用的键(LFU算法:Least Frequently Used))
- allkeys-lfu:当内存不足时,从所有的键中移除一些最不经常使用的键
这两个也是一个是在设置了过期时间的键的空间,一个是在全局空间。
Redis持久化
redis是基于内存的,为了防止一些意外导致的数据丢失,需要将数据持久化到磁盘上
Redis常见的持久化机制有RDB 和 AOF
RDB (默认)
RDB是redis默认的持久化方式,按照一定的间隔时间将内存的数据以快照的形式保存到硬盘,恢复时是将快照读取到内存中,RDB持久化实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储
优点
适合对大规模的数据恢复,比AOF的启动效率高
只有一个文件 dump.rdb,方便持久化
性能最大化,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
缺点
数据安全性低,在一定间隔时间内做一次备份,如果Redis突然宕机,会丢失最后一次快照的修改
由于RDB是通过fork子进程来协助完成数据持久化工作的,因此当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
AOF
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录
优点:
- 具备更高的安全性,Redis提供了3种同步策略,分别是每秒同步、每修改同步和不同步。相比RDB突然宕机丢失的数据会更少,每秒同步会丢失一秒种的数据,每修改同步会不会丢失数据。
- 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。
- AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作,可以通过该文件完成数据的重建。
缺点:
- 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
- 根据AOF选择同步策略的不同,效率也不同,但AOF在运行效率上往往会慢于RDB。
Redis的分布式
方案一 在 lua 脚本里面 set ex px nx + value(唯一性)
锁过期释放了,业务还没执行完
方案二 Redisson框架
watch dog 机制
方案三 : redlock
如果线程一在Redis的master节点上拿到了锁,但是加锁的key还没同步到slave节点。恰好这时,master节点发生故障,一个slave节点就会升级为master节点。线程二就可以获取同个key的锁啦,但线程一也已经拿到锁了,锁的安全性就没了。
RedLock算法
假设有5个完全独立的Redis服务器,多节点Redis实现的RedLock算法如下
- 获取当前时间戳
- 客户端尝试在5个实例中按顺序获取锁,在所有实例中使用相同的键名和随机值。当在每个实例中设置锁时,需要将锁的获取时间设置为比锁过期短很多。例如,如果锁自动释放时间为10秒,则锁的获取时间在5-50毫秒。这是为了不要过长时间等待已经关闭的Redis实例,如果一个Redis实例不可用,我们应该尽快尝试获取下一个Redis实例的锁。
- 客户端通过从当前时间中减去步骤1中获得的时间戳,计算出获取锁所需的时间。当且仅当客户端能够在大多数实例(至少3个)中获得锁,并且花费在获取锁的总时间小于锁的有效性时间时,该锁被认为已经获得。
- 如果获得了锁,锁真正的有效时间为锁初始设置的有效时间(过期时间)减去第三步的时间,例如,锁初始有限时间为5s,获取锁花了0.5s,则锁真正的有效时间为4.5s(忽略了时钟漂移,时间漂移指指两个电脑间时间流速基本相同的情况下,两个电脑(或两个进程间)时间的差值)
- 如果客户端由于某些原因无法获得锁(要么无法锁定N/2+1个Redis实例,要么有锁的有效时间为负数),客户端将尝试解锁所有Redis实例(即使是它认为无法锁定的Redis实例)。