• 技术文章 >数据库 >Redis

    Redis中必须要掌握的20个问题,快来收藏吧!!

    青灯夜游青灯夜游2021-10-19 10:32:29转载102
    本篇文章给大家分享20个必知必会、必须要掌握的Redis问题,希望对大家有所帮助,快来收藏吧!

    Redis是什么?

    Redis(Remote Dictionary Server)是一个使用 C 语言编写的,高性能非关系型的键值对数据库。与传统数据库不同的是,Redis 的数据是存在内存中的,所以读写速度非常快,被广泛应用于缓存方向。Redis可以将数据写入磁盘中,保证了数据的安全不丢失,而且Redis的操作是原子性的。【相关推荐:Redis视频教程

    Redis的优点?

    Redis为什么这么快?

    Redis为何选择单线程?

    Redis应用场景有哪些?

    Memcached和Redis的区别?

    Redis 数据类型有哪些?

    基本数据类型

    1、String:最常用的一种数据类型,String类型的值可以是字符串、数字或者二进制,但值最大不能超过512MB。

    2、Hash:Hash 是一个键值对集合。

    3、Set:无序去重的集合。Set 提供了交集、并集等方法,对于实现共同好友、共同关注等功能特别方便。

    4、List:有序可重复的集合,底层是依赖双向链表实现的。

    5、SortedSet(ZSet):有序Set。内部维护了一个score的参数来实现。适用于排行榜和带权重的消息队列等场景。

    特殊的数据类型

    1、Bitmap:位图,可以认为是一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在 Bitmap 中叫做偏移量。Bitmap的长度与集合中元素个数无关,而是与基数的上限有关。

    2、Hyperloglog。HyperLogLog 是用来做基数统计的算法,其优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。典型的使用场景是统计独立访客。

    3、Geospatial :主要用于存储地理位置信息,并对存储的信息进行操作,适用场景如定位、附近的人等。

    Redis事务

    事务的原理是将一个事务范围内的若干命令发送给 Redis,然后再让 Redis 依次执行这些命令。

    事务的生命周期:

    r01.png

    一个事务范围内某个命令出错不会影响其他命令的执行,不保证原子性:

    first:0>MULTI
    "OK"
    first:0>set a 1
    "QUEUED"
    first:0>set b 2 3 4
    "QUEUED"
    first:0>set c 6
    "QUEUED"
    first:0>EXEC
    1) "OK"
    2) "OK"
    3) "OK"
    4) "ERR syntax error"
    5) "OK"
    6) "OK"
    7) "OK"

    WATCH命令

    WATCH命令可以监控一个或多个键,一旦其中有一个键被修改,之后的事务就不会执行(类似于乐观锁)。执行EXEC命令之后,就会自动取消监控。

    first:0>watch name
    "OK"
    first:0>set name 1
    "OK"
    first:0>MULTI
    "OK"
    first:0>set name 2
    "QUEUED"
    first:0>set gender 1
    "QUEUED"
    first:0>EXEC
    (nil)
    first:0>get gender
    (nil)

    比如上面的代码中:

    1. watch name开启了对name这个key的监控
    2. 修改name的值
    3. 开启事务a
    4. 在事务a中设置了namegender的值
    5. 使用EXEC命令进提交事务
    6. 使用命令get gender发现不存在,即事务a没有执行

    使用UNWATCH可以取消WATCH命令对key的监控,所有监控锁将会被取消。

    持久化机制

    持久化就是把内存的数据写到磁盘中,防止服务宕机导致内存数据丢失。

    Redis支持两种方式的持久化,一种是RDB的方式,一种是AOF的方式。前者会根据指定的规则定时将内存中的数据存储在硬盘上,而后者在每次执行完命令后将命令记录下来。一般将两者结合使用。

    RDB方式

    RDB是 Redis 默认的持久化方案。RDB持久化时会将内存中的数据写入到磁盘中,在指定目录下生成一个dump.rdb文件。Redis 重启会加载dump.rdb文件恢复数据。

    bgsave是主流的触发 RDB 持久化的方式,执行过程如下:

    1.png

    Redis启动时会读取RDB快照文件,将数据从硬盘载入内存。通过 RDB 方式的持久化,一旦Redis异常退出,就会丢失最近一次持久化以后更改的数据。

    触发 RDB 持久化的方式:

    1. 手动触发:用户执行SAVEBGSAVE命令。SAVE命令执行快照的过程会阻塞所有客户端的请求,应避免在生产环境使用此命令。BGSAVE命令可以在后台异步进行快照操作,快照的同时服务器还可以继续响应客户端的请求,因此需要手动执行快照时推荐使用BGSAVE命令。

    2. 被动触发

      • 根据配置规则进行自动快照,如SAVE 100 10,100秒内至少有10个键被修改则进行快照。
      • 如果从节点执行全量复制操作,主节点会自动执行BGSAVE生成 RDB 文件并发送给从节点。
      • 默认情况下执行shutdown命令时,如果没有开启 AOF 持久化功能则自动执行·BGSAVE·。

    优点

    缺点

    AOF方式

    AOF(append only file)持久化:以独立日志的方式记录每次写命令,Redis重启时会重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,AOF 是Redis持久化的主流方式。

    默认情况下Redis没有开启AOF方式的持久化,可以通过appendonly参数启用:appendonly yes。开启AOF方式持久化后每执行一条写命令,Redis就会将该命令写进aof_buf缓冲区,AOF缓冲区根据对应的策略向硬盘做同步操作。

    默认情况下系统每30秒会执行一次同步操作。为了防止缓冲区数据丢失,可以在Redis写入AOF文件后主动要求系统将缓冲区数据同步到硬盘上。可以通过appendfsync参数设置同步的时机。

    appendfsync always //每次写入aof文件都会执行同步,最安全最慢,不建议配置
    appendfsync everysec  //既保证性能也保证安全,建议配置
    appendfsync no //由操作系统决定何时进行同步操作

    接下来看一下 AOF 持久化执行流程:

    2.png

    优点

    缺点

    主从复制

    Redis的复制功能是支持多个数据库之间的数据同步。主数据库可以进行读写操作,当主数据库的数据发生变化时会自动将数据同步到从数据库。从数据库一般是只读的,它会接收主数据库同步过来的数据。一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

    //启动Redis实例作为主数据库
    redis-server  
    //启动另一个实例作为从数据库
    redis-server --port 6380 --slaveof  127.0.0.1 6379   
    slaveof 127.0.0.1 6379
    //停止接收其他数据库的同步并转化为主数据库
    SLAVEOF NO ONE

    主从复制的原理?

    哨兵Sentinel

    主从复制存在不能自动故障转移、达不到高可用的问题。哨兵模式解决了这些问题。通过哨兵机制可以自动切换主从节点。

    客户端连接Redis的时候,先连接哨兵,哨兵会告诉客户端Redis主节点的地址,然后客户端连接上Redis并进行后续的操作。当主节点宕机的时候,哨兵监测到主节点宕机,会重新推选出某个表现良好的从节点成为新的主节点,然后通过发布订阅模式通知其他的从服务器,让它们切换主机。

    3.png

    工作原理

    Redis cluster

    哨兵模式解决了主从复制不能自动故障转移、达不到高可用的问题,但还是存在主节点的写能力、容量受限于单机配置的问题。而cluster模式实现了Redis的分布式存储,每个节点存储不同的内容,解决主节点的写能力、容量受限于单机配置的问题。

    Redis cluster集群节点最小配置6个节点以上(3主3从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。

    Redis cluster采用虚拟槽分区,所有的键根据哈希函数映射到0~16383个整数槽内,每个节点负责维护一部分槽以及槽所映射的键值数据。

    4.png

    哈希槽是如何映射到 Redis 实例上的?

    优点:

    缺点:

    过期键的删除策略?

    1、被动删除(惰性)。在访问key时,如果发现key已经过期,那么会将key删除。

    2、主动删除(定期)。定时清理key,每次清理会依次遍历所有DB,从db随机取出20个key,如果过期就删除,如果其中有5个key过期,那么就继续对这个db进行清理,否则开始清理下一个db。

    3、内存不够时清理。Redis有最大内存的限制,通过maxmemory参数可以设置最大内存,当使用的内存超过了设置的最大内存,就要进行内存释放, 在进行内存释放的时候,会按照配置的淘汰策略清理内存。

    内存淘汰策略有哪些?

    当Redis的内存超过最大允许的内存之后,Redis 会触发内存淘汰策略,删除一些不常用的数据,以保证Redis服务器正常运行。

    Redisv4.0前提供 6 种数据淘汰策略

    Redisv4.0后增加以下两种

    内存淘汰策略可以通过配置文件来修改,相应的配置项是maxmemory-policy,默认配置是noeviction

    如何保证缓存与数据库双写时的数据一致性?

    1、先删除缓存再更新数据库

    进行更新操作时,先删除缓存,然后更新数据库,后续的请求再次读取时,会从数据库读取后再将新数据更新到缓存。

    存在的问题:删除缓存数据之后,更新数据库完成之前,这个时间段内如果有新的读请求过来,就会从数据库读取旧数据重新写到缓存中,再次造成不一致,并且后续读的都是旧数据。

    2、先更新数据库再删除缓存

    进行更新操作时,先更新MySQL,成功之后,删除缓存,后续读取请求时再将新数据回写缓存。

    存在的问题:更新MySQL和删除缓存这段时间内,请求读取的还是缓存的旧数据,不过等数据库更新完成,就会恢复一致,影响相对比较小。

    3、异步更新缓存

    数据库的更新操作完成后不直接操作缓存,而是把这个操作命令封装成消息扔到消息队列中,然后由Redis自己去消费更新数据,消息队列可以保证数据操作顺序一致性,确保缓存系统的数据正常。

    缓存穿透、缓存雪崩、缓存击穿【详解】Redis缓存击穿、穿透、雪崩概念及解决方案

    缓存穿透

    缓存穿透是指查询一个不存在的数据,由于缓存是不命中时被动写的,如果从DB查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了。

    布隆过滤器的原理:当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。查询时,将元素通过散列函数映射之后会得到k个点,如果这些点有任何一个0,则被检元素一定不在,直接返回;如果都是1,则查询元素很可能存在,就会去查询Redis和数据库。

    缓存雪崩

    缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重挂掉。

    解决方法:在原有的失效时间基础上增加一个随机值,使得过期时间分散一些。

    缓存击穿

    缓存击穿:大量的请求同时查询一个 key 时,此时这个 key 正好失效了,就会导致大量的请求都落到数据库。缓存击穿是查询缓存中失效的 key,而缓存穿透是查询不存在的 key。

    解决方法:加分布式锁,第一个请求的线程可以拿到锁,拿到锁的线程查询到了数据之后设置缓存,其他的线程获取锁失败会等待50ms然后重新到缓存取数据,这样便可以避免大量的请求落到数据库。

    public String get(String key) {
        String value = redis.get(key);
        if (value == null) { 
            //缓存值过期
            String unique_key = systemId + ":" + key;
            //设置30s的超时
            if (redis.set(unique_key, 1, 'NX', 'PX', 30000) == 1) {  //设置成功
                value = db.get(key);
                redis.set(key, value, expire_secs);
                redis.del(unique_key);
            } else {  
                //其他线程已经到数据库取值并回写到缓存了,可以重试获取缓存值
                sleep(50);
                get(key);  //重试
            }
        } else {
            return value;
        }
    }

    pipeline的作用?

    redis客户端执行一条命令分4个过程:发送命令、命令排队、命令执行、返回结果。使用pipeline可以批量请求,批量返回结果,执行速度比逐条执行要快。

    使用pipeline组装的命令个数不能太多,不然数据量过大,增加客户端的等待时间,还可能造成网络阻塞,可以将大量命令的拆分多个小的pipeline命令完成。

    原生批命令(mset和mget)与pipeline对比:

    LUA脚本

    Redis 通过 LUA 脚本创建具有原子性的命令:当lua脚本命令正在运行的时候,不会有其他脚本或 Redis 命令被执行,实现组合命令的原子操作。

    在Redis中执行Lua脚本有两种方法:evalevalshaeval命令使用内置的 Lua 解释器,对 Lua 脚本进行求值。

    //第一个参数是lua脚本,第二个参数是键名参数个数,剩下的是键名参数和附加参数
    > eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
    1) "key1"
    2) "key2"
    3) "first"
    4) "second"

    lua脚本作用

    1、Lua脚本在Redis中是原子执行的,执行过程中间不会插入其他命令。

    2、Lua脚本可以将多条命令一次性打包,有效地减少网络开销。

    应用场景

    举例:限制接口访问频率。

    在Redis维护一个接口访问次数的键值对,key是接口名称,value是访问次数。每次访问接口时,会执行以下操作:

    private String buildLuaScript() {
        return "local c" +
            "\nc = redis.call('get',KEYS[1])" +
            "\nif c and tonumber(c) > tonumber(ARGV[1]) then" +
            "\nreturn c;" +
            "\nend" +
            "\nc = redis.call('incr',KEYS[1])" +
            "\nif tonumber(c) == 1 then" +
            "\nredis.call('expire',KEYS[1],ARGV[2])" +
            "\nend" +
            "\nreturn c;";
    }
    
    String luaScript = buildLuaScript();
    RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
    Number count = redisTemplate.execute(redisScript, keys, limit.count(), limit.period());

    PS:这种接口限流的实现方式比较简单,问题也比较多,一般不会使用,接口限流用的比较多的是令牌桶算法和漏桶算法。

    更多编程相关知识,请访问:编程入门!!

    以上就是Redis中必须要掌握的20个问题,快来收藏吧!!的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:Redis
    上一篇:2021年Redis高频面试题分享(附答案分析) 下一篇:Redis中为什么需要分布式锁?如何实现?
    大前端线上培训班

    相关文章推荐

    • 什么是Cluster?Redis中为什么需要Cluster?• 汇总30个Redis常见问题!• 深入了解Redis中主从架构数据一致性同步原理• Redis中如何实现无畏宕机快速恢复和持久化• 一起来聊聊redis中的主从复制

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网