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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
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个不同的位。

目录
相关文章
|
1月前
|
NoSQL 安全 关系型数据库
Redis:持久化的两种方式
Redis持久化机制主要包括RDB和AOF两种方式。RDB通过生成数据快照进行持久化,支持手动或自动触发,具有加载速度快、文件紧凑等特点,但无法实时保存数据。AOF则记录每个操作命令,保障数据更安全,支持多种写入策略,并可通过重写机制优化文件大小。两者各有优劣,常结合使用以兼顾性能与数据安全。
|
1月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
131 1
Redis专题-实战篇二-商户查询缓存
|
24天前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
|
26天前
|
缓存 运维 监控
Redis 7.0 高性能缓存架构设计与优化
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Redis 7.0高性能缓存架构,探索函数化编程、多层缓存、集群优化与分片消息系统,用代码在二进制星河中谱写极客诗篇。
|
1月前
|
存储 监控 NoSQL
Redis高可用架构全解析:从主从复制到集群方案
Redis高可用确保服务持续稳定,避免单点故障导致数据丢失或业务中断。通过主从复制实现数据冗余,哨兵模式支持自动故障转移,Cluster集群则提供分布式数据分片与水平扩展,三者层层递进,保障读写分离、容灾切换与大规模数据存储,构建高性能、高可靠的Redis架构体系。
|
1月前
|
存储 缓存 NoSQL
Redis持久化深度解析:数据安全与性能的平衡艺术
Redis持久化解决内存数据易失问题,提供RDB快照与AOF日志两种机制。RDB恢复快、性能高,但可能丢数据;AOF安全性高,最多丢1秒数据,支持多种写回策略,适合不同场景。Redis 4.0+支持混合持久化,兼顾速度与安全。根据业务需求选择合适方案,实现数据可靠与性能平衡。(238字)
|
1月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
5月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
5月前
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
823 0

热门文章

最新文章