Redis事务、lua脚本

一、缓存的原子性

单条命令是原子性,这是由 redis 单线程保障的,多条命令能否用multi + exec来保证其原子性呢?

对 Redismulti + exec的认识

1.multi + exec并不支持回滚,例如       

        set a 1000,set b 1000 set c a

        multi,decraincrbincrcexec

multi相当于开始事务,他不会立刻执行命令,而且将其加入队列,当你执行exec才会执行,我们看到第三条命令,c的值并不是一个值,是一个字符串,命令加入队列时并不会去检查。

2.multi + exec中的读操作没有意义

get命令没有意思,redis事务不存在临时变量说法

3.既然multi + exec中读没有意义,就无法保证+的原子性,例如

        set a 1000set b 1000

        get a,get b(分别为1000),现在转账500

        multi,set a 500set b 1500exec

        但如果在 get 与multi之间其它客户端修改了ab,会造成丢失更新

在红线之前另一个客户端执行incr  a,后面发现被覆盖了

二、乐观锁保证原子性

watch命令,用来盯住key(一到多个),如果这些key在事务期间:

        没有被别的客户端修改,则 exec 才会成功

        被别的客户端改了,则 exec 返回nil

例如

set a 1000set b 1000

watch a b

multi

set a 500set b 1500

exec

 

三、lua 脚本保证原子性

redis 支持lua 脚本,能保证lua 脚本执行的原子性,可以取代multi + exec

例如: eval "local a = tonumber ( redis.call ('GET',KEYS[1]));local b = tonumber ( redis.call ('GET',KEYS[2]));local c = tonumber (ARGV[1]); if(a >= c) then redis.call ('SET', KEYS[1], a-c); redis.call ('SET', KEYS[2], b+c ); return 1;else return 0; end" 2 a b 500

四、LRU Cache(基于自写链表实现)

public class LruCache1 {
    static class Node {
        Node prev;
        Node next;
        String key;
        Object value;

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

        // (prev <- node -> next)
        public String toString() {
            StringBuilder sb = new StringBuilder(128);
            sb.append("(");
            sb.append(this.prev == null ? null : this.prev.key);
            sb.append("<-");
            sb.append(this.key);
            sb.append("->");
            sb.append(this.next == null ? null : this.next.key);
            sb.append(")");
            return sb.toString();
        }
    }

    public void unlink(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    public void toHead(Node node) {
        node.prev = this.head;
        node.next = this.head.next;
        this.head.next.prev = node;
        this.head.next = node;
    }

    int limit;
    Node head;
    Node tail;
    Map<String, Node> map;
    public LruCache1(int limit) {
        this.limit = Math.max(limit, 2);
        this.head = new Node("Head", null);
        this.tail = new Node("Tail", null);
        head.next = tail;
        tail.prev = head;
        this.map = new HashMap<>();
    }

    public void remove(String key) {
        Node old = this.map.remove(key);
        unlink(old);
    }

    public Object get(String key) {
        Node node = this.map.get(key);
        if (node == null) {
            return null;
        }
        unlink(node);
        toHead(node);
        return node.value;
    }

    public void put(String key, Object value) {
        Node node = this.map.get(key);
        if (node == null) {
            node = new Node(key, value);
            this.map.put(key, node);
        } else {
            node.value = value;
            unlink(node);
        }
        toHead(node);
        if(map.size() > limit) {
            Node last = this.tail.prev;
            this.map.remove(last.key);
            unlink(last);
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.head);
        Node node = this.head;
        while ((node = node.next) != null) {
            sb.append(node);
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        LruCache1 cache = new LruCache1(5);
        System.out.println(cache);
        cache.put("1", 1);
        System.out.println(cache);
        cache.put("2", 1);
        System.out.println(cache);
        cache.put("3", 1);
        System.out.println(cache);
        cache.put("4", 1);
        System.out.println(cache);
        cache.put("5", 1);
        System.out.println(cache);
        cache.put("6", 1);
        System.out.println(cache);
        cache.get("2");
        System.out.println(cache);
        cache.put("7", 1);
        System.out.println(cache);
    }

}

1.如何断开节点链接

    public void unlink(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

2.如何链入头节点 

    public void toHead(Node node) {
        node.prev = this.head;
        node.next = this.head.next;
        this.head.next.prev = node;
        this.head.next = node;
    }

3、删除逻辑

        ①map删除

        ②断开节点链接

    public void remove(String key) {
        Node old = this.map.remove(key);
        unlink(old);
    }

4、查询逻辑

        ①map获取

        ②断开节点链接

        ③链入头节点

    public Object get(String key) {
        Node node = this.map.get(key);
        if (node == null) {
            return null;
        }
        unlink(node);
        toHead(node);
        return node.value;
    }

5、新增逻辑

        ①map获取,没有新建节点,存入map

        ②有则更新节点值,断开链接

        ③①、② 完成后均链入头节点

        ④检查是否超过限额,是则删除最后一个节点,并断开它的链接

    public void put(String key, Object value) {
        Node node = this.map.get(key);
        if (node == null) {
            node = new Node(key, value);
            this.map.put(key, node);
        } else {
            node.value = value;
            unlink(node);
        }
        toHead(node);
        if(map.size() > limit) {
            Node last = this.tail.prev;
            this.map.remove(last.key);
            unlink(last);
        }
    }

五、LRU(LinkedHashMap实现)

public class LruCache2 extends LinkedHashMap<String, Object> {

    private int limit;

    public LruCache2(int limit) {
        // 1 2 3 4 false
        // 1 3 4 2 true
        super(limit * 4 /3, 0.75f, true);
        this.limit = limit;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
        if (this.size() > this.limit) {
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        LruCache2 cache = new LruCache2(5);
        System.out.println(cache);
        cache.put("1", 1);
        System.out.println(cache);
        cache.put("2", 1);
        System.out.println(cache);
        cache.put("3", 1);
        System.out.println(cache);
        cache.put("4", 1);
        System.out.println(cache);
        cache.put("5", 1);
        System.out.println(cache);
        cache.put("6", 1);
        System.out.println(cache);
        cache.get("2");
        System.out.println(cache);
        cache.put("7", 1);
        System.out.println(cache);
    }
}

六、LRU Cache

Least Recently Used,将最近最少使用的key从缓存中淘汰掉

        时间上,新的留下,老的淘汰

        如果访问了某个 key,则它就变成最新的

实现策略:

        ①链表法,最近访问的key移动到链表头,不常访问的自然靠近链表尾,如果超过容量、个数限制,移除尾部的

放入1 

 

get 43

        ②随机取样法,链表法占用内存较多,redis 使用的是随机取样法,每次只抽5key,每个key记录了它们的最近访问时间,在这5个里挑出最老的移除

160已经满容量,添加一个a进去。先从其中挑五个值,36时间最早。 

36会被淘汰 

 

 


版权声明:本文为weixin_43162044原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。