Redis面试必备知识点

作者: maskwang520 | 来源:发表于2018-07-28 22:27 被阅读15次

    1. Redis五种基本的数据结构

    • 字符串(strings)

        这是最简单Redis类型。如果你只用这种类型,Redis就像一个可以持久化的memcached服务器.key,value就是字符串.


    string.jpg
    set mykey maskwang
    get mykey
    //设置多个值
    mset a 10 b 20 c 30
    //设置5秒的过期
    expire key 5
    
    • 列表(Redis Lists)

        一个列表结构可以有序的存储多个字符串,多个字符串是可以重复的。


    未命名文件 (1).jpg
    > rpush mylist A
    (integer) 1
    > rpush mylist B
    (integer) 2
    > lpush mylist first
    (integer) 3
    //顺序取列表中每个值
    > lrange mylist 0 -1
    1) "first"
    2) "A"
    3) "B"
    
    • 集合(set)

        Redis的集合和列表都可以存储多个字符串,不同之处在于,列表可以存储多个相同的字符串,而集合则通过散列表保证自己存储的每个字符串都是各不相同的。


    未命名文件 (3).jpg
    > sadd myset 1 2 3
    (integer) 3
    > smembers myset
    1. 3
    2. 1
    3. 2
    
    • 散列(hash)

        Redis的散列可以存储多个键值对之间的映射。


    未命名文件.png
    > hmset user:1000 username maskwang birthyear 1994 verified 1
    OK
    > hget user:1000 username
    "maskwang"
    > hget user:1000 birthyear
    "1993"
    > hgetall user:1000
    1) "username"
    2) "maskwang"
    3) "birthyear"
    4) "1994"
    5) "verified"
    6) "1"
    
    • 有序集合(zset)

        有序集合和散列一样,都用于存储键值对:有序集合的键成为成员(member),每个成员都是各不相同的;而有序集合的值称为分值(score),分值必须是浮点数。有序集合是Redis里面唯一一个既可以根据成员访问元素,又可以根据分值的排列顺序来访问元素的结构。


    未命名文件 (1).png
    > zadd student 1994 "maskwang"
    (integer) 1
    > zadd student  1987 "tom"
    (integer 1)
    > zadd student  1999 "bob"
    (integer) 1
    > zadd student  1949 "mary"
    (integer) 1
    //遍历zset,元素则按分值大小从小到大显示。
    > zrange hackers 0 -1
    1) "mary"
    2) "tom"
    3) "maskwang"
    4) "bob"
    

    2. Redis持久化

        Redis是内存数据库,它把数据存储在内存中,这样在加快读取速度的同时也对数据安全性产生了新的问题,即当redis所在服务器发生宕机后,redis数据库里的所有数据将会全部丢失。Redis提供了两种持久化方式,分别是RDB和AOF。

    • 持久化之全量写入:RDB

        RDB持久化是把当前进程数据生成快照保存到硬盘的过程.类似于savepoint.
    如果要启用RDB,需要在在redis.conf配置如下。

    //900秒内有1次写入,则会触发BGSAVE命令,执行RDB持久化
    save 900 1
    save 300 10
    save 60 10000
    dbfilename "dump.rdb"          #持久化文件名称
    dir "./"    #持久化数据文件存放的路径
    

    RDB的优点
        (1)RDB是一个紧凑的单一文件,它保存了某个时间点得数据集,非常适用于数据集的备份。
        (2)RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
        (3)与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.
    RDB的缺点
        (1)如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.会丢失两次备份之间这段时间内数据的新增,删除,以及变化。
        (2)RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒。(这个过程的原理是一个CopyOnWrite机制,将原来的数据复制到一个新的空间进行修改,读写分离的思想)

    fork()用于创建一个进程,所创建的进程复制父进程的代码段/数据段/BSS段/堆/栈等所有用户空间信息;在内核中操作系统重新为其申请了一个PCB,并使用父进程的PCB进行初始化;

    • 持久化之增量写入:AOF

        与RDB的保存整个redis数据库状态不同,AOF是通过保存对redis服务端的写命令(如set、sadd、rpush)来记录数据库状态的,即保存你对redis数据库的写操作.
    如果要启用AOF,需要在在redis.conf配置如下。

    dir "./"           #AOF文件存放目录
    3 appendonly yes                       #开启AOF持久化,默认关闭
    4 appendfilename "appendonly.aof"      #AOF文件名称(默认)
    5 appendfsync no                       #AOF持久化策略
    6 auto-aof-rewrite-percentage 100      #触发AOF文件重写的条件(默认)
    7 auto-aof-rewrite-min-size 64mb       #触发AOF文件重写的条件(默认)
    

    AOF 优点
    (1)默认的fsync策略是1秒,一旦出现故障,你最多丢失1秒的数据.(这个策略可以配置)
    (2)Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。
    (3)AOF 文件有序地保存了Redis执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析也很轻松。
    AOF 缺点
    (1)对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
    (2)根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间。

    3. Redis事务

        事务提供了一种“将多个命令打包, 然后一次性、按顺序地执行”的机制, 并且事务在执行的期间不会主动中断 —— 服务器在执行完事务中的所有命令之后, 才会继续处理其他客户端的其他命令,通常如下。

    > MULTI
    OK
    > SET book-name "Mastering C++ in 21 days"
    QUEUED
    > GET book-name
    QUEUED
    > SADD tag "C++" "Programming" "Mastering Series"
    QUEUED
    > SMEMBERS tag
    QUEUED
    > EXEC
    1) OK
    2) "Mastering C++ in 21 days"
    3) (integer) 3
    4) 1) "Mastering Series"
       2) "C++"
       3) "Programming"
    

    一个事务从开始到执行会经历以下三个阶段:

    • 开始事务

        这个命令唯一做的就是, 将客户端的 REDIS_MULTI 选项打开, 让客户端从非事务状态切换到事务状态。


    image.png
    • 命令入队

    命令执行过程如下:


    image.png

        当客户端处于非事务状态下时, 所有发送给服务器端的命令都会立即被服务器执行。

    > SET msg "maskwang"
    OK
    > GET msg
    "maskwang"
    

        但是, 当客户端进入事务状态之后, 服务器在收到来自客户端的命令时, 不会立即执行命令, 而是将这些命令全部放进一个事务队列里, 然后返回 QUEUED , 表示命令已入队:

    > MULTI
    OK
    > SET msg "maskwang"
    QUEUED
    > GET msg
    QUEUED
    

    事务队列是一个数组, 每个数组项是都包含三个属性:
    要执行的命令(cmd),命令的参数(argv),参数的个数(argc)。

    • 执行事务

    (1)如果客户端正处于事务状态, 那么当 EXEC 命令执行时, 服务器根据客户端所保存的事务队列, 以先进先出(FIFO)的方式执行事务队列中的命令。
    (2)执行事务中的命令所得的结果会以 FIFO 的顺序保存到一个回复队列中。
    (3)当事务队列里的所有命令被执行完之后,EXEC 命令会将回复队列作为自己的执行结果返回给客户端, 客户端从事务状态返回到非事务状态, 至此, 事务执行完毕。
    需要注意的地方:
    除了 EXEC 之外, 服务器在客户端处于事务状态时, 不加入到事务队列而直接执行的另外三个命令是 DISCARD 、 MULTI 和 WATCH 。

    • 带 WATCH 的事务

        WATCH 命令用于在事务开始之前监视任意数量的键: 当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败。

    redis> WATCH name
    OK
    > MULTI
    OK
    > SET name mask
    QUEUED
    > EXEC
    (nil)
    

        带 WATCH 的事务是以乐观锁的形式执行的,也就是说先执行,再判断所监控的键有没有变化,是一种CAS的思想。

    • DISCARD

         DISCARD命令用于取消一个事务, 它清空客户端的整个事务队列, 然后将客户端从事务状态调整回非事务状态, 最后返回字符串 OK 给客户端, 说明事务已被取消。

    Redis发布订阅

        Redis 通过 PUBLISH、 SUBSCRIBE等命令实现了订阅与发布模式, 这个功能提供两种信息机制, 分别是订阅/发布到频道和订阅/发布到模式。这个功能和消息队列类似于rabbitmq,rocketmq等消息队列的用处一样。由于Redis在这方面的性能表现不及专门的消息队列好,但是在小规模下,依然有应用的场景。

    • 频道的订阅和发布

        Redis的SUBSCRIBE命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。

    image.png
        当有新消息通过PUBLISH命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
    image.png
        原理:通过一个pubsub_channels的字典来实现的,字典的键为频道,与每个频道对应的值为一个链表,对应着订阅了某频道的客户端。
    image.png
    • 模式的订阅与信息发送

        当使用 PUBLISH命令发送信息到某个频道时, 不仅所有订阅该频道的客户端会收到信息, 如果有某个/某些模式和这个频道匹配的话, 那么所有订阅这个/这些频道的客户端也同样会收到信息。

        下图展示了一个带有频道和模式的例子, 其中 tweet.shop.* 模式匹配了 tweet.shop.kindle 频道和 tweet.shop.ipad 频道, 并且有不同的客户端分别订阅它们三个:

    image.png
        当有信息发送到 tweet.shop.kindle 频道时, 信息除了发送给 clientX 和 clientY 之外, 还会发送给订阅 tweet.shop.* 模式的 client123 和 client256 。模式的订阅与发布的实现原理和频道的相似,只不过是现在字典key是一个模式,利用正则表达式匹配客户端而已。

    Redis实现分布式锁

        分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁,实现redis锁需要确保锁的实现同时满足以下四个条件:

    • 互斥性:在任意时刻,只有一个客户端能持有锁。
    • 无死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
    • 容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
    • 解锁和加锁必须是同一个对象:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

    网上的实现:https://yq.aliyun.com/articles/307547?utm_content=m_37928
    官方推荐的实现Redisson实现:https://redisson.org/

    Redis实现Session共享

        在tomcat等web容器中,Session是保存在本机内存中。如果我们对tomcat做集群,不可避免要涉及到Session同步的问题,必须保证同一个集群中的tomcat的Session是共享的,因此如何让Session在不同的节点之间共享就成为关键之一。通常来说有Session sticky,redis保存Session等方式。
        这里采用的是Spring Session+Redis简单实现的,把Session存储在Redis里面。

    • pom文件
    <!-- spring 引入 session 信息存储到redis里的依赖包  -->
            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-data-redis</artifactId>
            </dependency>
    
    • application.properties里面配置如下
    # session的存储方式的类型配置
    spring.session.store-type=redis
    # session 存活时间
    server.session.timeout=300
    

    配置完之后,就可以跟平常一样获取Session。

    HttpSession httpSession = request.getSession();
    

        SpringSession实现会话共享的关键代码是通过SessionRepositoryFilter这个过滤器拦截每个每个请求,通过 Filter 将使用我们上文的SessionRepositoryRequestWrapper封装HttpServletRequest 请求,之后每次获取Session,都是通过 SessionRepositoryRequestWrapper来获取。如果Redis存在,则Redis里面获取,否则生成新的,放入到Redis里面。

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
            SessionRepositoryFilter<S>.SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryFilter.SessionRepositoryRequestWrapper(request, response, this.servletContext);
            SessionRepositoryFilter<S>.SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryFilter.SessionRepositoryResponseWrapper(wrappedRequest, response);
            HttpServletRequest strategyRequest = this.httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse);
            HttpServletResponse strategyResponse = this.httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse);
    
            try {
                filterChain.doFilter(strategyRequest, strategyResponse);
            } finally {
                wrappedRequest.commitSession();
            }
    
        }
    

        上述问题是最常见的,我也是抛砖引玉。另外觉得写的有帮助的话,麻烦点下二维码关注下。你的关注是我不断创作的动力。

    参考文章:

    Redis 分布式锁的正确实现方式( Java 版 )
    redis设计与实现
    Redis持久化
    Spring Boot系列十二 通过redis实现Tomcat集群的Session同步及从源码分析其原理

    qrcode_for_gh_2d4794fe3d16_258.jpg

    相关文章

      网友评论

        本文标题:Redis面试必备知识点

        本文链接:https://www.haomeiwen.com/subject/nqmtsftx.html