Redis(二)原理

Java中操作使用jedis

Redis高级特性,发布订阅、事务、Lua脚本
底层工作原理,单线程工作机制、内存回收、持久化

发布订阅模式

publish subscribe
unsubscribe
这个模式了解一下,专业还是用mq的

Redis事务

在这里插入图片描述
multi开启事务,exec执行事务,discard取消事务。watch监视变化

Lua脚本

c语言编写的,为什么用Lua?
好处,
1.多个命令一次发送执行,
2.原子性
3.复杂命令放到文件
在这里插入图片描述
在这里插入图片描述
写入文件中
通过ip访问次数限制
在这里插入图片描述
在这里插入图片描述

Redis为什么这么快

每秒钟接收请求10万·

  1. 纯内存中,kv结构,时间复杂度o(1)
  2. 单线程,无需创建销毁线程,避免上下文切换导致的消耗,无需关心多线程资源问题。单线程就够用了
  3. 异步非阻塞i/o,多路复用

主存是内存,辅存是磁盘
在这里插入图片描述

内存回收

expire过期策略,达到上限触发内存回收策略

定时过期 到了时间主动淘汰
惰性过期 下一次访问判断是否需要淘汰,被动淘汰。或者set的时候发现内存多,释放内存
定期过期 拿一些出来进行删除,设置扫描时间

redis使用的是惰性过期+定期过期两种方式结合起来
达到最大内存上限,会进行删除。还有就是访问的时候会调用expireifneed方法进行删除

如果不设置ttl,达到内存上限如何处理?
内存上限可以在redis中进行设置maxmemory,不设置不限制

淘汰策略

在这里插入图片描述
lru算法,最近最少使用的优先淘汰
lfu,最不常用的优先淘汰
random,随机算法,随机删除,一般不用
ttl,最近要过期的删除
noeviction,不做淘汰

LRU算法实现,有个采样的配置,从数据库随机选择5个key,热度低的删除
在这里插入图片描述
redisObject中有lru,redis有serverCron方法,隔100毫秒时间改变全局变量global lru_lock的值,评估热度根据lru_lock减去对象中lru差值的结果判断,越大热度低
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
LFU算法,频率
redisObject中lru8位会记录频率
基于概率的对数计数器
在这里插入图片描述
使用updateLFU进行更新
在这里插入图片描述
控制计数器增长和减少
在这里插入图片描述
LRU和LFU最常见的。

Redis持久化机制

重启后数据不丢失,和modb区别

RDB

redis database (默认持久化机制) dump.rdb文件
什么时候触发生成文件。
在这里插入图片描述
通过配置操作
在这里插入图片描述

触发生成dump.rdb文件方式

停机,flushall,多少秒内被修改多少key修改会触发

手动触发生成文件,执行save命令,会阻塞
bgsave,异步,子进程进行操作
可以使用lastsave查看快照生成时间

备份这个文件,防止数据丢失

如果刚好时间间隔之内数据丢失,就会使用aof机制

AOF

append only file

通过配置appendonly yes开启
生成appendonly.aof,里面会保存指令,可以恢复数据
也不是实时写入,多久从缓存写入磁盘?
在这里插入图片描述
推荐第二种,每一秒种把缓存种数据写入磁盘
如果文件越来越大,因为存储的hi指令,我们将aof文件中的内容变成可以恢复文件的最小的指令集。
通过命令进行重写
在这里插入图片描述
如果可以忍受一些数据丢失的话,RDB性能要比AOF好

也可以两种方式结合使用

总结

redis事务,不保证原子性。不支持回滚

严格的说Redis的命令是原子性的,而事务是非原子性的,我们要让Redis事务完全具有事务回滚的能力,需要借助于命令WATCH来实现。

Redis使用WATCH命令来决定事务是继续执行还是回滚,那就需要在MULTI之前使用WATCH来监控某些键值对,然后使用MULTI命令来开启事务,执行对数据结构操作的各种命令,此时这些命令入队列。

当使用EXEC执行事务时,首先会比对WATCH所监控的键值对,如果没发生改变,它会执行事务队列中的命令,提交事务;如果发生变化,将不会执行事务中的任何命令,同时事务回滚。当然无论是否回滚,Redis都会取消执行事务前的WATCH命令。

在事务开始前用WATCH监控k1,之后修改k1为11,说明事务开始前k1值被改变,MULTI开始事务,修改k1值为12,k2为22,执行EXEC,发回nil,说明事务回滚;查看下k1、k2的值都没有被事务中的命令所改变。
multi开启事务,exec执行事务,discard取消事务。watch监视变化
语法错误,编译错误,失败
运行错误,跳过错误的继续执行

为什么Redis不支持事务回滚?

多数事务失败是由语法错误或者数据结构类型错误导致的,语法错误说明在命令入队前就进行检测的,而类型错误(运行错误)是在执行时检测的,Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。

Redis为什么快

  1. 纯内存中,kv结构,时间复杂度o(1)
  2. 单线程,无需创建销毁线程,避免上下文切换导致的消耗,无需关心多线程资源问题。单线程就够用了
  3. 异步非阻塞i/o,多路复用

redis最大内存超了怎么样

如果不设置最大内存大小或者设置最大内存带笑为0,64位系统不限制内存大小。
redis推荐设置为最大内存的四分之三
如何设置,一种修改配置文件,maxmemory 104857600.另一种动态修改,通过命令config set maxmemory 1024

什么命令查看redis内存使用情况?info memory

这些偏运维

redis打满了怎么样,超出最大内存设置怎么样?
超出set就会报错OOM
redis内存淘汰策略,即缓存淘汰策略

内存回收

Redis的内存回收主要分为过期删除策略和内存淘汰策略两部分。

过期删除策略

删除达到过期时间的key。
如果一个键过期,那到了过期之后是不是马上从内存中删除呢?
不是,

1、定时删除

对于每一个设置了过期时间的key都会创建一个定时器,一旦到达过期时间就立即删除。该策略可以立即清除过期的数据,对内存较友好,但是缺点是占用了大量的CPU资源去处理过期的数据,会影响Redis的吞吐量和响应时间。

2、惰性删除

当访问一个key时,才判断该key是否过期,过期则删除。该策略能最大限度地节省CPU资源,但是对内存却十分不友好。有一种极端的情况是可能出现大量的过期key没有被再次访问,因此不会被清除,导致占用了大量的内存。

在计算机科学中,懒惰删除(英文:lazy deletion)指的是从一个散列表(也称哈希表)中删除元素的一种方法。在这个方法中,删除仅仅是指标记一个元素被删除,而不是整个清除它。被删除的位点在插入时被当作空元素,在搜索之时被当作已占据。

3、定期删除

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

在Redis中,同时使用了定期删除和惰性删除。
定时删除,对cpu不友好,用处理器性能换取存储空间,拿时间换空间
惰性删除,数据达到过期时间,不处理,等下次访问该数据时,如果未过期,返回数据,如果已经过期,删除,返回不存在。缺点是对内存不友好。如果不用呢,比方好多数据都不用了,但是还一直在内存中

上面都极端,两者结合后
定期删除,每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对cpu时间的影响。
服务器需要根据情况,合理设置删除操作的执行时长和执行频率。

内存淘汰策略

Redis的内存淘汰策略,是指内存达到maxmemory极限时,使用某种算法来决定清理掉哪些数据,以保证新数据的存入。

Redis的内存淘汰机制

noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错。(默认是这个)

allkeys-lru:当内存不足以容纳新写入数据时,在键空间(server.db[i].dict)中,移除最近最少使用的 key(这个是最常用的)。

allkeys-random:当内存不足以容纳新写入数据时,在键空间(server.db[i].dict)中,随机移除某个 key。

volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间(server.db[i].expires)中,移除最近最少使用的 key。

volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间(server.db[i].expires)中,随机移除某个 key。

volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间(server.db[i].expires)中,有更早过期时间的 key 优先移除。

在配置文件中,通过maxmemory-policy可以配置要使用哪一个淘汰机制。
Redis会在每一次处理命令的时候(processCommand函数调用freeMemoryIfNeeded)判断当前redis是否达到了内存的最大限制,如果达到限制,则使用对应的算法去处理需要删除的key。

8种平时用那种,最多的是用allkeys-lru。配置中配置 maxmemory-policy allkeys-lru

LRU实现原理

在淘汰key时,Redis默认最常用的是LRU算法(Latest Recently Used)。Redis通过在每一个redisObject保存lru属性来保存key最近的访问时间,在实现LRU算法时直接读取key的lru属性。

具体实现时,Redis遍历每一个db,从每一个db中随机抽取一批样本key,默认是3个key,再从这3个key中,删除最近最少使用的key。

如果一个建过期,到期后不是马上删除,而是看你的过期删除策略

最近最少使用的数据进行淘汰。因为缓存毕竟有限,需要删除

插入删除快,且需要排序,时间复杂度o(1)
算法核心是hash+链表,本质是哈希表+双向链表的结合体

手写LRU算法

第一种,LinkedHashMap
第二种,HashMap+双向链表

public class LRUCache {

    private Node head;
    private Node tail;

    private final HashMap<String,Node> nodeHashMap;

    private int capacity;//rongliang

    public LRUCache(int capacity){
        this.capacity = capacity;
        nodeHashMap = new HashMap<>();
        tail = new Node();
        head = new Node();
        head.next = tail;
        tail.prev = head;
    }

    class Node{
        private String key;
        private String value;
        Node prev;
        Node next;

        public Node(){ }
        public Node(String key,String value){
            this.key = key;
            this.value = value;
        }

    }

    public String get(String key){
        Node node = nodeHashMap.get(key);
        if(node == null){
            return null;
        }
        //刷新当前key位置
        moveNodeToHead(node);
        return node.value;
    }

    private void moveNodeToHead(Node node){
        removeNode(node);
        addNodeToHead(node);
    }

    private void removeNode(Node node){
        if(node == tail){
            tail = tail.prev;
            tail.next = null;
        }else if (node == head){
            head = head.next;
            head.prev = null;
        }else{
            node.prev.next = node.next;
            node.next.prev = node.prev;
        }
    }

    private void addNodeToHead(Node node){
        node.next = head.next;
        head.next.prev = node;
        node.prev = head;
        head.next = node;
    }


    public void put(String key,String value){
        Node node = nodeHashMap.get(key);
        if(node == null){
            if(nodeHashMap.size() > capacity){
                removeNode(tail);
                nodeHashMap.remove(tail.key);
            }
            node = new Node(key,value);
            nodeHashMap.put(key,node);
            addNodeToHead(node);
        }else {
            node.value = value;
            moveNodeToHead(node);
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值