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个不同的位。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

llsydn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值