redis持久化原理、缓存问题处理方案

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: redis redis持久化 redis开机的时候--->加载持久化文件(第一次开启的时候没有)--->启动了--->会写入一些数据 --->redis会在某一时刻把内存的数据写入磁盘(生成持久化文件

redis

redis持久化

redis开机的时候--->加载持久化文件(第一次开启的时候没有)--->启动了--->会写入一些数据
--->redis会在某一时刻把内存的数据写入磁盘(生成持久化文件)

1.RDB持久化原理
原理是redis会单独创建(fork)一个和当前线程一模一样的子进程来进行持久化,这个子进程的所有数据
(变量、环境变量、程序计数器等)和原进程一模一样,会先将数据写入到一个临时文件中,待持久化结束,
再用这个临时文件替换上次持久化好的文件,整个过程中,主进程不进行任何io操作,这就确保了较高的性能。
  • 这个持久化文件在哪里?
  • dir ./(默认情况下会根据启动的位置为准,最好配置一下)
  • 产生dump.rdb文件
  • 他什么时候fork子进程,或者什么时候出发rdb持久化机制?
  • 1.shutdown时,如果没有开启aof,会触发。
  • 2.配置文件中默认的快照配置
  • save 900 1 (定时器900秒,有一个更改,会触发)
  • save 300 100
  • save 60 10000 (优化方案,将300、60的删掉;集群环境下,rdb是删不了的)
  • 3.执行save(主进程执行,会阻塞),bgsave(fork的进程,后台异步执行快照)
  • 4.执行flushall,把内存数据清空了,但里面是空的,无意义。(把磁盘的数据也清空,否则会丢失数据)

rdb缺点:
1.出现意外宕机,会出现数据丢失。因为无法触发rdb持久化机制。

2.AOF持久化原理
原理是将redis的操作日志以追加的方式写入文件,读操作是不记录的。
  • 这持久化文件在哪里?
  • appendonly yes 表示开启aof
  • dir ./(默认情况下会根据启动的位置为准,最好配置一下)
  • 产生appendonly.aof文件
  • 触发机制(根据配置文件配置项)
  • appendfsync no 表示等操作系统进行数据同步到磁盘(批量操作)(效率快,持久化没保证)
  • appendfsync always 同步持久化,每次发生数据变更时,立即记录到磁盘(效率慢,安全)
  • appendfsync everysec 表示每秒同步一次(默认值,很快,但可能会丢失一秒以内的数据)
  • 数据走向流程:主进程 ---> 缓冲区 ---> aof文件(磁盘)
  • 数据会先从主进程进入到缓冲区,最后才会写入到aof文件。
  • no 表示数据进入到缓冲区,会批量等待,才会写入到aof文件
  • always 表示数据进入到缓冲区,立马就写入到aof文件
  • everysec 表示数据进入到缓冲区,每秒执行一次,写入到aof文件
  • aof重写机制(解决aof不断变大问题)
  • 重写,会fork子进程。根据当前内存数据,生成aof快照。
  • redis4.0开启混合持久化,是对重写的进一步优化
  • aof-use-rdb-preamble yes(是否开启重写aof文件机制)
  • 手动执行命令:bgrewriteaof,(以rdb的方式的数据格式保存到aof文件中)
  • 自动触发重写:
# 增长比例(100%)
auto-aof-rewrite-percentage 100
# 当aof文件增长到一定大小时,redis会调用bgrewriteaof对日志文件进行重写。
auto-aof-rewrite-min-size 64mb(优化点,生产环境必改,一般5G以上)

例如:
第一次触发:64mb ,第二次触发:64mb + 64mb * 100% = 128mb

总结

类型 是否fork子进程 缺点 优点
rdb 会fork子进程 可能会丢失最后一次写入的数据 启动redis时,从磁盘加载持久化数据非常快
aof 不会fork子进程 最多丢失不会超过2秒的数据 启动redis时,从磁盘加载持久化数据不如rdb
aof重写 会fork子进程

redis缓存问题

1.缓存穿透
指查询数据库和缓存中都没有存在的数据

解决方法:

  • 1.缓存空对象 (代码简单、效果不好)
  • 缺点:
  • 1.只能针对相同的key进行限制,无法对大量的key进行限制。
  • 2.会导致redis中,存在大量的空数据问题,占用redis内存。
    //从缓存中获取
    obj = redis.getkey(id);
    if(obj != null){
        //(缓存空对象)
        if(obj instanceOf 空对象){
            return "查无数据";
        }
        return "查询成功"+obj;
    }
    
    try{
        //从数据库中获取
        obj = dao.select(id);
        if(obj != null){
            redis.setKey(id, obj);
            return "查询成功"+obj;
        }else{
            //(缓存空对象)
            redis.setKey(id, 空对象);
        }
    }
    
    return "查无数据";
  • 2.布隆过滤器 (代码复杂、效果很好)
    //布隆过滤器
    if (!bloomFilter.mightContain(id)) {
        //不存在
        return "查无数据";
    }

    //从缓存中获取
    obj = redis.getkey(id);
    if(obj != null){
        return "查询成功"+obj;
    }
    
    try{
        //从数据库中获取
        obj = dao.select(id);
        if(obj != null){
            //加入布隆过滤器
            bloomFilter.put(id);
            return "查询成功"+obj;
        }
    }
    
    return "查无数据";
  • 缺点
  • 1.布隆过滤器维护比较麻烦,不能删除,只能重构一个新的。
  • 2.布隆过滤器需要先初始化,不然第一次查询都不存在了。
  • 原理
一个bit位数组,1个key会通过多个hash算法得到hash值,存到bit位数组中;那就可能出现当1个不存在的key,通过
多个hash算法得到的hash值,对应的index上面有数据,那就可能出现误判的情况。
  • 分布式布隆过滤器
//guava依赖
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.0.1-jre</version>    
</dependency>


private static int size = 1000000;

/**
 * size 预计要插入多少数据
 * fpp  容错率--->出现误判的概率是多少
 * list 创建的是object数组
 * bit  数组
 *
 * 位数组 21亿  JVM内存  数据不会进行持久化 256M
 * redis  42亿  redis内存 redis的持久化数据 512M
 */
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.001);


public static void main(String[] args) {

    for (int i = 1; i <= size; i++) {
        bloomFilter.put(i);
    }
    List<Integer> list = new ArrayList<>(10000);
    //故意取10000个不存在过滤里的值,看看有多少个会被认为在过滤器里
    for (int i = size + 10000; i < size + 20000; i++) {
        if (bloomFilter.mightContain(i)) {
            //误判(明明不存在,误判为存在)
            list.add(i);
        }
    }
    System.out.println("误判数量:" + list.size());
}

2.缓存穿透
指数据库中有数据,缓存没有(这条数据没人访问过;数据刚好失效)。并发访问会导致所有查询都访问数据库

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

我们知道,使用缓存,如果获取不到,才会去数据库里获取。但是如果是热点 key,访问量非常的大,数据库在重建缓存的时候,会出现很多线程同时重建的情况。因为高并发导致的大量热点的 key 在重建还没完成的时候,不断被重建缓存的过程,由于大量线程都去做重建缓存工作,导致服务器拖慢的情况。

解决方法:

  • 1.互斥锁

第一次获取缓存的时候,加一个锁,然后查询数据库,接着是重建缓存。这个时候,另外一个请求又过来获取缓存,发现有个锁,这个时候就去等待,之后都是一次等待的过程,直到重建完成以后,锁解除后再次获取缓存命中。

public String getKey(String key){
    String value = redis.get(key);
    if(value == null){
        String mutexKey = "mutex:key:"+key; //设置互斥锁的key
        if(redis.set(mutexKey,"1","ex 180","nx")){ //给这个key上一把锁,ex表示只有一个线程能执行,过期时间为180秒
          value = db.get(key);
          redis.set(key,value);
          redis.delete(mutexKety);
  }else{
        // 其他的线程休息100毫秒后重试
        Thread.sleep(100);
        getKey(key);
  }
 }
 return value;
}

互斥锁的优点是思路非常简单,具有一致性,但是互斥锁也有一定的问题,就是大量线程在等待的问题。存在死锁的可能性。

  • 2.分布式锁

可以使用redisson框架提供的redis分布式锁

3.缓存雪崩问题

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

解决方法:

  • 1.在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  • 2.做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
  • 3.不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
  • 4.如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
4.缓存与数据库数据一致性问题

在这里插入图片描述

5.缓存粒度控制

通俗来讲,缓存粒度问题就是我们在使用缓存时,是将所有数据缓存还是缓存部分数据?

数据类型 通用性 空间占用(内存空间+网络码率) 代码维护
全部数据 简单
部分数据 较为复杂

缓存粒度问题是一个容易被忽视的问题,如果使用不当,可能会造成很多无用空间的浪费,可能会造成网络带宽的浪费,可能会造成代码通用性较差等情况,必须学会综合数据通用性、空间占用比、代码维护性 三点评估取舍因素权衡使用。

redis集群搭建

1.安装好redis到/opt/myredis/redis下面

2.新建redis7000,redis7001,redis7002,redis7003,redis7004,redis7005文件夹

3.将redis.conf文件复制在redis7000下

4.分别修改个目录下的redis.conf文件

vi redis7000/redis.conf

1.bind 127.0.0.1   指定本机ip(内网ip)

2.port 7000

3.daemonize yes

4.pidfile /opt/myredis/redis7000/redis_7000.pid

5.logfile /opt/myredis/redis7000/redis_7000.log

6.dir /opt/myredis/redis7000

7.requirepass 123456

(集群环境参数)
8.cluster-enabled yes(启动集群模式)

9.cluster-config-file nodes-7000.conf(这里800x最好和port对应上)

10.cluster-node-timeout 15000

11.appendonly yes

4.把redis7000/redis.conf文件复制到redis7001,redis7002,redis7003,redis7004,redis7005下

将redis7000/redis.conf文件复制到redis7001下,并同时将7000字符批量替换成70001

sed 's/7000/7001/g' redis7000/redis.conf > redis7001/redis.conf 

sed 's/7000/7002/g' redis7000/redis.conf > redis7002/redis.conf 

sed 's/7000/7003/g' redis7000/redis.conf > redis7003/redis.conf 

sed 's/7000/7004/g' redis7000/redis.conf > redis7004/redis.conf 

sed 's/7000/7005/g' redis7000/redis.conf > redis7005/redis.conf 

5.分别启动7000,7001,7002,7003,7004,7005实例

/opt/myredis/redis/bin/redis-server /opt/myredis/redis7000/redis.conf 

/opt/myredis/redis/bin/redis-server /opt/myredis/redis7001/redis.conf

/opt/myredis/redis/bin/redis-server /opt/myredis/redis7002/redis.conf 

/opt/myredis/redis/bin/redis-server /opt/myredis/redis7003/redis.conf 

/opt/myredis/redis/bin/redis-server /opt/myredis/redis7004/redis.conf 

/opt/myredis/redis/bin/redis-server /opt/myredis/redis7005/redis.conf  

#查看是否启动成功 
ps -ef | grep redis

6.构建集群,主从分配并指派槽位

/opt/myredis/redis/bin/redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 
127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1

这里有6个ip,表示前3个ip为主节点,后面3个为从节点
1,表示主从比例为1:1(3:3)
2,表示主从比例为2:4(2:4),但是主节点必须要有3个以上,那这里就会报错

这里的槽位是平均分配:16384

7.验证集群

1.连接任意一个客户端即可:(-c是redis-cli可以帮我们自动计算槽位,并进行重定向到对应的redis)
/opt/myredis/redis/bin/redis-cli -c -h 127.0.0.1 -p 7000  (-c表示集群模式,指定ip地址和端口号)

2.进行验证:
cluster info(查看集群信息)、cluster nodes(查看节点列表)

3.进行数据操作验证
set key llsydn

4.关闭集群则需要逐个进行关闭,使用命令:
/opt/myredis/redis/bin/redis-cli -c -h 127.0.0.1 -p 700* shutdown 

redis内容扩展

1.Pipeline

注意:使用Pipeline的操作是非原子操作

2.GEO

GEOADD locations 116.419217 39.921133 beijin

GEOPOS locations beijin

GEODIST locations tianjin beijin km 计算距离

GEORADIUSBYMEMBER locations beijin 150 km 通过距离计算城市

注意:没有删除命令 它的本质是zset (type locations)

所以可以使用zrem key member 删除元素

zrange key 0 -1 表示所有 返回指定集合中所有value

3.hyperLogLog

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

PFADD 2017_03_06:taibai 'yes' 'yes' 'yes' 'yes' 'no'

PFCOUNT 2017_03_06:taibai 统计有多少不同的值

1.PFADD 2017_09_08:taibai uuid9 uuid10 uu11

2.PFMERGE 2016_03_06:taibai 2017_09_08:taibai 合并

注意:本质还是字符串 ,有容错率,官方数据是0.81%

4.bitmaps

setbit taibai 5000 0 (将key为taibai的5000bit位设置为0)

getbit taibai 5000 (将key为taibai的5000bit位的数据)

bitcount taibai (统计有多少个1)

Bitmap本质是string,是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset)。
string(Bitmap)最大长度是512 MB,所以它们可以表示2 ^ 32=4294967296个不同的位。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
14天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
15天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
8天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
25 5
|
23天前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
132 22
|
22天前
|
缓存 NoSQL 中间件
redis高并发缓存中间件总结!
本文档详细介绍了高并发缓存中间件Redis的原理、高级操作及其在电商架构中的应用。通过阿里云的角度,分析了Redis与架构的关系,并展示了无Redis和使用Redis缓存的架构图。文档还涵盖了Redis的基本特性、应用场景、安装部署步骤、配置文件详解、启动和关闭方法、systemctl管理脚本的生成以及日志警告处理等内容。适合初学者和有一定经验的技术人员参考学习。
121 7
|
26天前
|
缓存 监控 安全
检测 Webpack 5 持久化缓存是否存在安全漏洞
【10月更文挑战第23天】通过全面、系统地检测和评估,能够及时发现 Webpack 5 持久化缓存的安全漏洞,并采取有效的措施进行修复,保障项目的安全稳定运行。同时,要持续关注安全技术的发展和变化,不断提升安全检测能力,以应对日益复杂的安全挑战。
|
26天前
|
存储 缓存 监控
配置 Webpack 5 持久化缓存时需要注意哪些安全问题?
【10月更文挑战第23天】通过全面、系统地分析和应对安全问题,能够更好地保障 Webpack 5 持久化缓存的安全,为项目的成功构建和运行提供坚实的安全基础。同时,要保持对安全技术的关注和学习,不断提升安全防范能力,以应对日益复杂的安全挑战。
|
26天前
|
存储 缓存 前端开发
利用 Webpack 5 的持久化缓存来提高构建效率
【10月更文挑战第23天】利用 Webpack 5 的持久化缓存是提高构建效率的有效手段。通过合理的配置和管理,我们可以充分发挥缓存的优势,为项目的构建和开发带来更大的便利和效率提升。你可以根据项目的实际情况,结合以上步骤和方法,进一步优化和完善利用持久化缓存的策略,以达到最佳的构建效果。同时,不断探索和实践新的方法和技术,以适应不断变化的前端开发环境和需求。
|
6月前
|
NoSQL Redis
03- Redis的数据持久化策略有哪些 ?
Redis的数据持久化包括两种策略:RDB(全量快照)和AOF(增量日志)。RDB在指定时间间隔将内存数据集保存到磁盘,而AOF记录所有写操作形成日志。从Redis 4.0开始,支持RDB和AOF的混合持久化,通过设置`aof-use-rdb-preamble yes`。
57 1
|
4月前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
下一篇
无影云桌面