数据库基础03 - Redis

1. Redis 是什么?

       Redis是一个非关系型数据库,不过与传统的数据库不同的是Redis数据库是存在内存中,所以读写速度非常快,因此,Redis被广泛应用于缓存方向。

       除此之外,Redis也经常用来做分布式锁,Redis提供了多种数据类型来支持不同的业务场景。除此之外,Redis支持事务持久化、LUA脚本、LRU驱动事件、多种集群方案。

2. Redis的五种数据结构

        说到Redis的数据结构,我们大概会很快想到 Redis 的5种常见数据结构:字符串(String)、列表(List)、散列(Hash)、集合(Set)、有序集合(Sorted Set),以及他们的特点和运用场景。不过它们是Redis对外暴露的数据结构,用于API的操作,而组成它们的底层基础数据结构又是什么呢?

(1)简单动态字符串(SDS)

        Redis没有直接使用 C 语言传统的字符串,而是自己构建了一种名为简单动态字符串(Simple dynamic string,SDS)的抽象类型,并将 SDS 用作 Redis 的默认字符串表示。

        其实SDS等同于C语言中的 char * ,但它可以存储任意二进制数据,不能像C语言字符串那样以字符 ’ \0 ’ 来标识字符串的结束,因此它必然有个长度字段。

        它具有很常规的 set/get 操作,value 可以是 String,也可以是数字,一般做一些复杂的计数功能的缓存。

(2)链表

        当有一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的额字符串时,Redis就会使用链表作为列表键的底层实现。

(3)字典

        字典的底层是哈希表,类似 Java 中的 map ,也就是键值对。

(4)哈希表

(5)跳跃表

3. Redis常见数据结构以及使用场景分别是什么?

        Redis 本身是一个 Map,存储的是 key-value 格式的数据,其中 key 都是字符串,value 有以下5种不同的数据结构:

(1)String

        01 String数据结构:是简单的 key-value 类型,vlaue 不仅可以是 String,也可以是数字。

        02 使用场景:常规 key-value 缓存应用;常规计数器,如粉丝数,微博数等。

        03 常用命令:set、get、decr、incr、mget等。

(2)Hash

        01 数据结构:是一个 String 类型的 field(字段)和 value(值)的映射表。

        02 使用场景:hash 特别适合用于存储对象,后续操作的时候,可以直接仅仅修改这个对象中的某个字段的值。如,存储用户信息、商品信息等。

        03 常用命令:hget、hset、hgetall。

(3)List

        01 数据结构:就是链表。实现的是一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。

        02 使用场景:微博的关注列表、粉丝列表。

        03 常⽤命令: lpush、rpush、lpop、rpop、lrange等。

(4)Set

        01 数据结构:set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。当需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。

        02 使用场景:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,具体命令如下:sinterstore key1 key2 key3将交集存在key1内。

        03 常用命令:sadd、spop、smembers、sunion等。

(5)Sorted Set

        01 数据结构:和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。

        02 使用场景:在直播系统中,实时排行信息包含直播间在线用户列表、各种礼物排行榜等信息,适合使用 Redis 中的 SortedSet 结构进行存储。

        03 常用命令:zadd、zrange、zrem、zcard等。

4. 有MySQL之后,为什么要用Redis这种新的数据库?/ 为什么要用Redis?/ 为什么要用缓存?

        主要是因为Redis具备高性能和高并发两种特性 / 主要从高性能和高并发来看待这个问题,

(1)高性能:假如用户第一次访问数据库中的某些数据,因为是从硬盘中读取的,因此这个过程会比较慢。如果将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可。

(2)高并发:直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

5. 为什么要用Redis,而不是Map做缓存?

        缓存分为本地缓存和分布式缓存

        以 Java 为例,使用自带的 map 实现的是本地缓存,最主要的特点是轻量且快速,生命周期随着 JVM 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。

        使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。

6. 使用Redis的好处?

(1)访问速度快:因为数据存在内存中,类似于 Java 中的 HashMap,HashMap 优势就是查找和操作的时间复杂度都是O(1)。

(2)数据类型丰富:支持String,list,set,sorted set,hash这五种数据结构。

(3)支持事务:Redis 中的操作都是原子性,换句话说就是对数据的更改要么全部执行,要么全部不执行。

(4)特性丰富:Redis 可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除。

7. Redis 和 Memcached 的区别

        Memcached 是分布式缓存系统。

(1)存储方式

        01 Memecached 把数据全部存在内存之中,断电后会挂掉,没有持久化功能。

        02 Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中。

(2)数据支持类型

        01 Memcached 支持简单的数据类型, String 。

        02 Redis 有复杂的数据类型。Redis不仅仅支持简单的k/v类型的数据,同时还提供 list,set,zset,hash等数据结构的存储。

(3)集群模式

        01 Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;

        02 Redis 目前是原生支持 cluster 模式的。

(4)Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。

7.1 Redis 的线程模型

        redis 内部使用文件事件处理器 file event handler ,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用IO 多路复用机制同时监听多个 socket(套接字),根据 socket 上的事件来选择对应的事件处理器进行处理。

Socket是进程通讯的一种方式,即调用这个网络库的一些API函数实现分布在不同主机的相关进程之间的数据交换。

 五种IO模型:IO模型

        文件事件处理器的结构包含 4 个部分: 多个 socket、IO 多路复用程序 、文件事件分派器、事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)。

        多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出⼀个事件, 把该事件交给对应的事件处理器进行处理。

8. Redis 比 Memcached 的优势在哪里?

(1)Memcached所有的值均是简单字符串,Redis作为其替代者,支持更为丰富的数据类型。

(2)Redis 的速度比 Memcached 快很多。

(3)Redis可以做到持久化数据。

9. Redis 设置过期时间

        Redis有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置⼀个过期时间。比如,对验证码设置过期时间是很有用的。

        set key 的时候,可以给⼀个 expire time,就是过期时间,通过过期时间可以指定这个 key 可以存活的时间。

        假设你设置了⼀批 key 只能存活1小时,那么1小时后,redis是怎么对这批key进⾏删除的

定期删除 + 惰性删除

        01 定期删除:redis默认是每隔 100ms 就随机抽取⼀些设置了过期时间的key,检查其是否过期, 如果过期就删除。注意这⾥是随机抽取的。为什么要随机呢?你想⼀想假如 redis 存了几十万 个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很⼤的负载!

        02 惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如过期的 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查⼀下那个 key,才会被redis给删除掉。

       定期删除和惰性删除一定能保证删除过期数据吗?如果不能Redis将采用什么样的应对方式?

仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存⾥,导致redis内存块耗尽了。怎么解决这个问题呢? redis 内存淘汰机制!

        首先需要知道什么是热点数据和冷数据?

10.缓存中常说的热点数据和冷数据是什么?

        其实就是名字上的意思,热数据就是访问次数较多的数据,冷数据就是访问很少或者从不访问的数据。

        需要注意的是只有热点数据,缓存才有价值。对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。

        数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。

11. Redis内存淘汰机制

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

        在Redis.conf 中有一行配置:maxmemory-policy volatile-lru,该配置就是配内存淘汰策略的。Redis 提供 6种数据淘汰策略:

(1)volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的键空间中挑选最近最少使用的数据淘汰。

(2)volatile-ttl:当内存不足以容纳新写入数据时,从设置了过期时间的键空间中挑选将要过期的数据淘汰 。

(3)volatile-random:当内存不足以容纳新写入数据时,从设置过了期时间的键空间中任意选择数据淘汰。

(4)allkeys-lru:当内存不足以容纳新写入数据时,在整个键空间中,移除最近最少使用的key(这个是最常用的)

(5)allkeys-random:当内存不足以容纳新写入数据时,从整个键空间中任意选择数据淘汰。

(6)no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。

12. Redis对于大量的请求,是怎么处理的?

(1)Redis是一个单线程程序,也就说同一时刻它只能处理一个客户端请求;

(2)Redis是通过IO多路复用(select,epoll,kqueue,依据不同的平台,采取不同的实现)来处理多个客户端请求。

13. Redis的持久化机制 / 怎么保证 redis 挂掉之后再重启数据可以进行恢复?

        Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件,当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。

        很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。

        实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。

Redis有两种持久化方法,RDB持久化和 AOF持久化。两种持久化方法

(1)快照持久化(RDB持久化)

        RDB的实现方式为,在指定时间将当前时刻内存中的数据生成一个快照文件(.rdb文件),并将这个快照文件保存到磁盘上(就像把某一时刻的数据以文件的形式拍下来,并保存在磁盘上)。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用(在做数据恢复时,直接将RDB文件读入内存就可以完成恢复)。

       快照持久化是Redis默认采用的持久化方式。在Redis.conf配置文件中默认有此下配置:

save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令
创建快照。

save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

 (2)AOF持久化

        与快照持久化相比,AOF持久化的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启 AOF(append only file)方式的持久化,可以通过appendonly参数开启:

appendonly yes

        开启AOF持久化后客户端每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件的末尾,在Redis重启后,会读取并加载AOP文件,达到恢复数据的目的。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。

        在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no  #让操作系统决定何时进行同步

        为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。

Redis 4.0 对于持久化机制的优化

        Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

14. AOF重写?简单说说AOF重写

           当对同一个key做多次写操作时,就会产生大量针对同一个key操作的日志指令,导致AOF文件会变得非常大,恢复数据的时候会变得非常慢。因此,redis提供了重写机制来解决这个问题。redis通过重写AOF文件,保存的只是恢复数据的最小指令集。

        AOF重写:Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个文件所保存的数据库状态是相同的,但是新的AOF文件不会包含任何浪费空间的冗余命令,通常体积会较旧AOF文件小很多。

        可以通过下面命令手动触发重写:bgrewriteaof也可以通过配置文件自动触发重写。

        在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件的期间记录服务器执行的所有写命令。当子进程完成新AOF文件的创建后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的 AOF文件,以此来完成AOF文件重写操作。

15. 是否使用过Redis集群?集群的原理是什么?

        Redis Sentinel(哨兵)着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

        Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从服务器中选举出新的主服务器。

        Redis Cluster(集群)着眼于扩展性,在单个Redis内存不足时,使用Cluster进行分片存储。

16. 缓存雪崩

(1)什么是缓存雪崩?

        大量请求无法在 Redis 缓存中进行处理,后面的请求都落在数据库上,造成数据库短时间内承受大量请求而崩掉。

(2)出现原因

        01 缓存中的大量 key 同时过期,导致大量请求无法得到处理,大量请求落到从数据库上。

        02 Redis实例发生故障宕机,无法处理请求,就会导致大量请求积压在数据库上。

(3)解决方法

        01 差异化设置过期时间,不要让大量key在同一时期过期。

        02 尽量保证整个 Redis 集群的高可用性,发现机器宕机赶快补上。

        03 利用Redis持久化机制保存的数据,来尽快的恢复缓存。

        04 选择合适的内存淘汰机制。

 17. 缓存穿透

(1)什么是缓存穿透? 缓存中不存在,数据库中也不存在

       大量请求的 key 不存在于缓存中,会尝试从数据库中获取,从而导致了大量请求到达数据库,但当数据库中也不存在时,这就导致数据库执行了很多不必要的查询操作,从而导致巨大冲击和压力。

(2)产生原因

        01 误操作导致:缓存中的数据和数据库中的数据被误删了。

        02 恶意攻击:专门访问数据库中不存在的数据。

(3)解决办法

        01 布隆过滤器:最常见的一种解决方法。把所有可能存在的请求的值都存放在布隆过滤器中,当请求过来,先判断用户请求的值是否存在于过滤器中。不存在的话直接返回请求参数错误的信息给客户端;存在的话会先去访问缓存,若缓存不命中再去访问数据库。

        02 缓存无效 key / 缓存空对象:对查询结果为空的对象也进行缓存,如果是集合,可以缓存一个空的集合;如果是缓存单个对象,可以通过字段标识来区分。这样后续客户端再请求该数据时,可以直接命中缓存,不再访问数据库。

布隆过滤器

        01 作用:由一个初值都为 0 的 bit 数组和 N 个哈希函数组成,可以用来快速判断某个数据是否存在。

        02 工作原理:使用 N 个哈希函数,分别计算这个数据的哈希值,得到 N 个哈希值;把这 N 个哈希值对 bit 数组的长度取模,得到每个哈希值在数组中的对应位置;标记数据,把对应位置的 bit 位设置为 1;如果数据不存在,那布隆过滤器中bit数组对应的bit位的值为0。

        03 过滤规则:当一个元素需要被检索时,使用N个哈希函数对该元素求得哈希值,并定位到bit数组中。如果发现对应位置处bit位有任何一个0,就说明这个元素不存在于布隆过滤器;当bit位都是1,说明这个元素可能存在于布隆过滤器(这里说可能,是因为不同元素经过哈希运算可能得到相同的哈希值)。我们只需要初始化布隆过滤器,即将数据添加到布隆过滤器中。然后借助布隆过滤器去判断元素是否存在。

18. 缓存击穿

        缓存击穿,是指热点数据在某一时刻过期了,可能会导致后续大量的请求直接到达数据库,导致数据库压力激增。缓存中没有,但数据库中有。

        比如常见的电商项目中,某些货物成为“爆款”了,可以对一些主打商品的缓存直接设置为永不过期。即便某些商品自己发酵成了爆款,也是直接设为永不过期就好了。mutex key互斥锁基本上是用不上的,有个词叫做大道至简。

解决方法:热点数据不设置过期时间;分布式锁,采用锁机制控制打到数据库的请求,读取数据后将其写回到缓存,保证了其他线程可以命中缓存(基本不用该方法)。

19. 如何解决 Redis 的并发竞争 key 的问题

        所谓 Redis 的并发竞争 Key 的问题就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!下面是解决方法,

(1)分布式锁

        zookeeper 和 Redis 都可以实现分布式锁。如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能。准备一个分布式锁,抢到锁就做set操作即可。

(2)分布式锁 + 时间戳

        假设有一个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操作了。以此类推。

(3)基于消息队列

        把所有操作写入同一个队列,利用消息队列把所有操作串行化。

        

在实践中,当然是从以可靠性为主。所以首推Zookeeper。

20. 如何保证缓存与数据库双写时的数据一致性?
        首先说一句,你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?

        一般来说,就是如果你的系统不是严格要求缓存 + 数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,最好将读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。

        串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值