【Redis】常见知识点总结

基础

Redis为什么这么快

  • 基于内存;
  • 单线程减少上下文切换,同时保证原子性;
  • IO多路复用;
  • 高级数据结构(如 SDS、Hash以及跳表等)

四种缓存问题以及解决方案?

缓存雪崩:

redis缓存的key在同一时间大量失效,导致大量请求全部打到数据库,造成数据库挂掉

解决方法:

  • 随机设置key的失效时间
  • 热点数据key永不过期
  • 跑定时任务,在缓存失效前刷新新的缓存

缓存穿透

对redis和数据库中没有的数据进行不断查询,造成数据库挂掉

解决方法

  • 对参数进行校验
  • 将不存在的数据缓存在redis缓存中,设置k-v都为null,并设置一个很短的过期时间
  • 布隆过滤器

缓存击穿

具体的热点key一直在高并发,key突然失效,数据库压力过大宕机

解决方法

  • 设置热点key永不过期
  • 加互斥锁,第一个查询到的数据放入缓存,走缓存,每过一端时间更新缓存

缓存预热

将热点数据提前缓存到redis中,防止数据库压力过大宕机

解决方法

  • 手动写热点缓存

如何保证缓存与数据库数据一致性?


双写:缓存跟数据库均更新数据,如何保证数据一致性?

1、先更新数据库,再更新缓存

安全问题:线程A更新数据库->线程B更新数据库->线程B更新缓存->线程A更新缓存。导致脏读。

业务场景:读少写多场景,频繁更新数据库而缓存根本没用。更何况如果缓存是叠加计算后结果更浪费性能。

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

A 请求写来更新缓存。

B 发现缓存不在去数据查询旧值后写入缓存。

A 将数据写入数据库,此时缓存跟数据库不一致。

因此 FackBook 提出了 Cache Aside Pattern

因为缓存的写入速度远远大于数据库的写入速度,保证缓存为空,再去查找一遍数据库,即可保证每次查找的数据和缓存都相同

失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
命中:应用程序从cache中取数据,取到后返回。 
更新:先把数据存到数据库中,成功后,再让缓存失效。
 

如何保证 Redis 的高并发?

Redis 通过主从加集群架构,实现读写分离,主节点负责写,并将数据同步给其他从节点,从节点负责读,从而实现高并发。

Redis 如何保证原子性?


答案很简单,因为 Redis 是单线程的,所以 Redis 提供的 API 也是原子操作。

但我们业务中常常有先 get 后 set 的业务常见,在并发下会导致数据不一致的情况。

如何解决

  • 使用 incr、decr、setnx 等原子操作;
  • 客户端加锁;
  • 使用 Lua 脚本实现 CAS 操作。

redis五种基本数据类型和应用场景

  1. String(字符串):缓存、限流、分布式锁、计数器、分布式 Session 等。
  2. Hash(字典):用户信息、用户主页访问量、组合查询等。
  3. List(列表):简单队列、关注列表时间轴。
  4. Set(集合):赞、踩、标签等。
  5. ZSet(有序集合):排行榜、好友关系链表。

事务


Redis中的事务只要有如下三步:

在这里插入图片描述 

关于事务具体结论:

1、redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

2、Redis事务没有隔离级别的概念:批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。

3、Redis不保证原子性:Redis中单条命令是原子性执行的,但事务不保证原子性。

4、Redis编译型错误事务中所有代码均不执行,指令使用错误。运行时异常是错误命令导致异常,其他命令可正常执行。

5、watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端更改,则使用EXEC执行事务时,事务队列将不会被执行。


如何正确使用redis开发步骤


上线前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。

上线时:本地 ehcache 缓存 + Hystrix 限流 + 降级,避免MySQL扛不住。

上线后:Redis 持久化采用 RDB + AOF 来保证断点后自动从磁盘上加载数据,快速恢复缓存数据。


分布式锁

日常开发中我们可以用 synchronized 、Lock 实现并发编程。但是Java中的锁只能保证在同一个JVM进程内中执行。如果在分布式集群环境下用锁呢?日常一般有两种选择方案。

Redis实现分布式锁


本身原理也比较简单,Redis 自身就是一个单线程处理器,具备互斥的特性,通过setNX,exist等命令就可以完成简单的分布式锁,处理好超时释放锁的逻辑即可。

SETNX

SETNX 是SET if Not eXists的简写,日常指令是SETNX key value,如果 key 不存在则set成功返回 1,如果这个key已经存在了返回0。

SETEX

SETEX key seconds value 表达的意思是 将值 value 关联到 key ,并将 key 的生存时间设为多少秒。如果 key 已经存在,setex命令将覆写旧值。并且 setex是一个原子性(atomic)操作。

该命令相当于将下面两行操作合并为一个原子操作

SET key value
EXPIRE key seconds  # 设置生存时间

加锁:一般就是用一个标识唯一性的字符串比如UUID 配合 SETNX 实现加锁。

解锁:这里用到了LUA脚本,LUA可以保证是原子性的,思路就是判断一下Key和入参是否相等,是的话就删除,返回成功1,0就是失败。

缺点:这个锁是无法重入的,且自己实心的话各种边边角角都要考虑到,所以了解个大致思路流程即可,工程化还是用开源工具包就行。


Redis 过期策略和内存淘汰策略

Redis的过期策略

Redis中 过期策略 通常有以下三种:

1、定时过期:

每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量

2。惰性过期:

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

定期过期:

每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

expires字典会保存所有设置了过期时间的key的过期时间数据,其中 key 是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。

Redis采用的过期策略:惰性删除 + 定期删除。memcached采用的过期策略:惰性删除。
 


6种内存淘汰策略

Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰(最常问)
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-enviction(驱逐):禁止驱逐数据,不删除的意思。

面试常问常考的也就是LRU了,大家熟悉的LinkedHashMap中也实现了LRU算法的,实现如下:

class SelfLRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int CACHE_SIZE;
    /**
     * 传递进来最多能缓存多少数据
     * @param cacheSize 缓存大小
     */
    public SelfLRUCache(int cacheSize) {
  // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        CACHE_SIZE = cacheSize;
    }
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
        return size() > CACHE_SIZE;
    }
}

bitmap

BitMap 原本的含义是用一个比特位来映射某个元素的状态。由于一个比特位只能表示 0 和 1 两种状态,所以 BitMap 能映射的状态有限,但是使用比特位的优势是能大量的节省内存空间。

在 Redis 中BitMap 底层是基于字符串类型实现的,可以把 Bitmaps 想象成一个以比特位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量,BitMap 的 offset 值上限 2^32 - 1。

 

用户签到

key = 年份:用户id offset = (今天是一年中的第几天) % (今年的天数)

统计活跃用户

使用日期作为 key,然后用户 id 为 offset 设置不同offset为0 1 即可。

PS : Redis 它的通讯协议是基于TCP的应用层协议 RESP(REdis Serialization Protocol)。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值