一.为什么要用redis缓存

1.它把数据存到内存而不是硬盘中,操作缓存就是操作内存,具有高性能。

2.直接操作缓存能够承受的请求是远远大于数据库的,具有高并发

二.为什么要用 redis 而不用 map/guava 做缓存

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

map是本地缓存,具有轻量快捷的特点,生命周期随着JVM的销毁而结束,并且在多实例的情况下,每个实例都有一份自己的缓存,缓存的数据不一致。

redis是分布式缓存,在多实例情况下,各个实例用同一份缓存,保证了缓存的一致性。

三.redis 的线程模型

redis内部使用的文件事件处理器是单线程的,所以redis是单线程的。

文件事件处理器包含四个部分:sorket,IO多路复用程序,文件事件分派器,事件处理器

多个sorket可能会并发产生不同的操作,每个操作对应不同的文件事件,ID多路复用同时监听着多个sorket,将sorket产生的文件事件放入队列中,文件事件分派器从队列中取得一个事件交给对应的事件处理器处理

四.redis 和 memcached 的区别

1.redis支持数据持久化,支持将数据保存到磁盘中,重启的时候可以进行再次加载,但memcached将数据全部缓存在内存中

2.redis有多种数据类型,memcached只支持string

3.redis支持事物

4.redis是IO多路复用模型,memcached是非堵塞IO复用模型

五.redis 设置过期时间

定期删除+惰性删除

定期删除:redis默认100ms随机抽取设置了过期时间的key,检查其是否过期,过期就删除

惰性删除:当查询到的key已经过期,就删除

六.redis内存淘汰机制

最近最少使用,随机,最不经常使用*2 即将过期,写入报错

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

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

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

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

4.0版本后增加以下两种:

volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰

allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key

七.redis 持久化机制

RDB快照 ,AOF只追加文件

混合持久化:AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头,优点:快速加载同时避免丢失过多数据

八.redis 事务

watch key1 key2 ... : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )

multi : 标记一个事务块的开始( queued )

exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 ) 

discard : 取消事务,放弃事务块中的所有命令

unwatch : 取消watch对所有key的监控

事务总是具有原子性、一致性和隔离性,不能回滚

九.缓存雪崩和缓存穿透问题解决方案

缓存雪崩:缓存同一时间大量失效,造成大量请求落到数据库上,造成数据库崩溃

解决方法:

事前:尽量保证redis的高可用性,选择合适的内存淘汰机制

事中:加上本地缓存+限流,降低mysql压力

事后:用redis持久化机制,尽快恢复数据

缓存穿透:大量请求不存在的数据,导致请求落到数据库上

解决方法:

1.使用布隆过滤器,将所有有可能存在的数据哈希到一个足够大的bitmap中,一定不存在的数据会被这个map拦截掉

2.如果查询返回的数据是空,仍把空结果进行缓存,但过期时间设置的很短

十.单线程的redis为什么这么快

1.纯内存操作

2.单线程,避免了不必要的上下文切换和竞争条件

3.非堵塞IO多路复用

十一.Redis 架构模式

1.单机版

缺点:内存能力有限

处理能力有限

无法高可用

2.主从复制

一类是主数据库,一类是从数据库,主数据库进行读写操作,当进行写数据操作,数据同步到从数据库,从数据库只能读数据和接收主数据库同步过来的数据。

一个主数据库可以有多个从数据库,一个从数据库只能有一个主数据库

优点:降低了主数据库的读压力

缺点:无法保证高可用,没有降低主数据库的写压力

3.哨兵

哨兵是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移

优点:保证高可用

监控各个结点

自动进行故障转移

缺点:没有降低主服务器的写压力

主从复用,切换需要时间

丢数据

4.集群(proxy型)

 

 

 

 

 

优点:支持失败结点自动删除

后端分片逻辑对业务透明,业务方的读写方式跟操作单个redis一致

缺点:需要维护proxy

不支持自动故障转移

可扩展性差,进行扩缩容需要手动干预

5.集群(直连型)Redis-Cluster

 

Redis-Cluster采用无中心结构,每个节点保存数据且与其他节点相连。

优点:无中心架构,不存在哪个节点影响性能瓶颈,没有proxy层

数据存储分布在多个节点,节点间数据共享,可动态调整数据分布

具有可扩展性,节点可动态增加或删除

具有高可用性,部分节点不可用时,集群仍可用

也有主从结构,若主数据库failover,则用投票机制选择从数据库变为主数据库

缺点:资源隔离性较差,容易出现相互影响的情况

数据通过异步复制,不保证数据的强一致性

十二.什么是一致性哈希算法?什么是哈希槽?

十三.redis分布式锁

分布式锁可靠性的四个条件:

1.加索和解锁的是同一人

2.不能造成死锁

3.具有容错性,只要大部分redis结点正常运行,客户就可以进行加锁和解锁

4.互斥性。在任意时刻,只能有一人持有锁

Jedis.set()函数

/**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
 
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
 
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
 
    }

用lua脚本

/**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
 
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
 
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
 
    }