• redis持久化机制与过期策略


    RDB的持久化策略

    (快照方式,默认持久化方式): 按照规则定时将内存中的数据同步到磁盘,它有以下4个触发场景。

      1. 自己配置的快照规则  vim /redis/bin/ redis.conf;按照save <seconds> <changes>这个规则自己添加或修改规则。

        

      2. save或者bgsave命令

        save:将内存的数据同步到磁盘中,这个操作会阻塞客户端的请求(不建议用,太耗时了)。

          bgsave:在后台异步执行快照操作,这个操作不会阻塞客户端的请求。

      3. 执行flushall清除内存的所有数据时,只要save <seconds> <changes>规则不为空,就会执行快照。

      4. 执行复制的时候(集群中 )

      快照的实现原理与优缺点:

        redis使用fork函数复制出一份当前进程的副本(子进程);父进程继续处理客户端指令,子进程开始将内存的数据写入硬盘中的临时文件,写入完成后就会替换之前的旧文件。优点:1. 多进程处理,性能最快。缺点:1. 若子进程数据未写完而redis异常退出,就会丢失最后一次快照以后更改的所有数据; 2. 如果数据集比较大的时候,fork可以能比较耗时,造成服务器在一段时间内停止处理客户端的请求。

    AOF的持久化策略

      开启AOF持久化vim /redis/bin/ redis.conf  将appendonly默认值no改为yes; 重启后执行对数据的变更命令, 会在bin目录下生成对应的.aof文件, aof文件中会记录所有的操作命令!

        

      AOF优化:

         auto-aof-rewrite-percentage 100  表示当前aof文件大小超过上一次aof文件大小的百分之多少的时候会进行重写。如果之前没有重写过,以启动时aof文件大小为准

         auto-aof-rewrite-min-size 64mb   限制允许重写最小aof文件大小,也就是文件大小小于64mb的时候,不需要进行重写。

        

       aof重写原理:

        执行redis命令的时候,会把命令写入到.aof文件中,.aof文件变得过大时后台会对它进行重写。在将命令写入到.aof时,即使重写过程发生了停机,现有的.aof文件也不会丢失,所以它是绝对安全的。

       同步磁盘数据:

        redis在变更数据时,命令会实时记录到.aof文件。但是由于操作系统的缓存机制,数据不是直接写入到硬盘,而是写入硬盘缓存,再通过硬盘缓存机制刷新保存到文件。

          appendfsync always  每次执行写入都会进行同步  , 这个是最安全但是是效率比较低的方式

          appendfsync everysec   每一秒执行

          appendfsync no  不主动进行同步操作,由操作系统去执行,这个是最快但是最不安全的方式

          

      2.4 aof文件损坏以后如何修复:

        例如在执行set name wulei时候,文件只记录了set name  ,这种不完整的命令是无法被写入磁盘的。我们可以先将.aof文件备份,然后通过redis-check-aof --fix命令修复,它会将一些不合法的命令清理掉,这样就能被持久化了。

      如果要保证数据绝对安全: 可以使用  RDB+AOF 方式来持久化。如果同时使用的话, 那么Redis重启时,会优先使用AOF文件来还原数据。

      如果可以接受几分钟的数据丢失:可以使用RDB方式,因为AOF速度没有RDB快。

    ============================  手动分割线  ==============================

    Redis的过期策略

      redis过期是(定期删除+惰性删除+内存淘汰机制)来实现的。reids是用内存来缓存的,内存资源很宝贵。所以我们 set key value 的时候一般都会给它设置过期时间。假设redis里放了10万个数据,redis默认会每隔100ms随机抽取设置了过期时间的key来删除(如果一次性检查所有数据,cpu负载过高redis早就死了),这种方式就叫定期删除。这种策略就会导致很多过期的key并没有被删除,所以还有惰性删除, 也就是在你获取某个key的时候,redis会检查一下,这个key是否过期了,如果是就直接删除不返回任何东西!

      但是这种方式还是有问题的,比如10万条全过期了,而redis只定期删除了部分,而客户端有没有对剩下的key操作,key就不会被删除,还是会造成内存的积压。这时候就会走内存淘汰机制,例如:

        allkeys-lru:当内存不足以容纳新写入数据时,移除最近最少使用的key。(最常用)

        volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键中,将更早过期时间的key优先移除。(还凑合吧)

        noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。(脑残的策略,没人用)

        allkeys-random:当内存不足以容纳新写入数据时,随机移除某个key。(脑残的策略,没人用)

        volatile-lru:当内存不足以容纳新写入数据时,在设了过期时间的键中,移除最近最少使用的key。(脑残的策略,没人用)

        volatile-random:当内存不足以容纳新写入数据时,在设了过期时间的键中,随机移除某个key。(脑残的策略,没人用)

    缓存淘汰算法LFU算法

      lfu算法是根据数据的历史访问频率来淘汰数据的,其核心思想是"如果数据过去被访问多次,那么将来被访问的频率也更高".
      缺点:
      1. 有存储成本,需要维护队列的引用计数,2.最后添加的数据容易被移除
    LFU算法原理剖析
      1. 新加入的数据插入到队列尾部(因为引用计数为1)
      2. 队列中的数据被访问后,其引用计数增加,队列重新排序
      3. 当需要淘汰数据时,将已经排序的列表最后的数据块删除

      

    缓存淘汰算法LRU算法

      根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”
    LRU算法原理剖析
      1. lru按照一定顺序维护着队列
      2. 队列中的数据被访问后,数据会被排列到最前面
      3. 当需要淘汰数据时,将已经排序的列表最后的数据块删除
      

    手写LRU算法

    import java.util.LinkedHashMap;
    import java.util.Map;
    
    // lru是有序的,所以这里继承 LinkedHashMap
    public class LRUCache<K,V> extends LinkedHashMap<K,V> {
    
        private final int CACHE_SIZE;
        /**
         * 传递进来最多能缓存多少数据
         *
         * @param cacheSize 缓存大小
         */
        public LRUCache(int cacheSize) {
            /*
                cacheSize是map的初始大小
                0.75f是加载因子,容量使用率达到0.75就会扩容一倍
                true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。
             */
            super(cacheSize, 0.75f, true);
            CACHE_SIZE = cacheSize;
        }
    
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            // 当前map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
            boolean b = size() > CACHE_SIZE;
            if(b){
                System.out.println("清除缓存key:"+eldest.getKey());
            }
            return b;
        }
        public static void main(String args[]){
            LRUCache<String, String> lruCache = new LRUCache<String, String>(3);
            lruCache.put("1", "1");
            lruCache.put("2", "2");
            lruCache.put("3", "3");
            lruCache.put("4", "4");
            System.out.println("初始化完成:"+lruCache.keySet());
            System.out.println(lruCache.get("3"));
            System.out.println("访问3之后:"+lruCache.keySet());
            System.out.println(lruCache.get("2"));
            System.out.println("访问2之后:"+lruCache.keySet());
        }
    }
  • 相关阅读:
    阶段一-01.万丈高楼,地基首要-第2章 单体架构设计与准备工作-2-1 单体架构阶段概述与项目演示
    阶段一-01.万丈高楼,地基首要-第1章 学习指南-1-4 架构师所需要具备的技术栈与能力
    阶段一-01.万丈高楼,地基首要-第1章 学习指南-1-3 大型网站架构演变历程
    Spring cloud微服务安全实战-8-1课程总结
    Spring cloud微服务安全实战-7-13章节总结
    Spring cloud微服务安全实战-7-12整合链路追踪和日志监控
    Spring cloud微服务安全实战-7-11PinPoint+SpringBoot环境搭建
    Spring cloud微服务安全实战-7-10ELK日志采集架构优化
    Spring cloud微服务安全实战-7-9自定义日志采集的格式和内容
    Spring cloud微服务安全实战-7-8ELK+SpringBoot环境搭建
  • 原文地址:https://www.cnblogs.com/wlwl/p/9842691.html
Copyright © 2020-2023  润新知