精华内容
下载资源
问答
  • 包括 jquery.expire.js 调用$.expire(); 使用startTime 、 endTime和expireAction 选项 开始时间日期必需的您希望页面变为活动状态的日期。 如果timeStamp在startTime之前,将调用expireAction 。 时间结束日期...
  • npm install expire-array 示例用法 // Initialize with a timeout of 10 seconds var arr = require ( 'expire-array' ) ( 1000 * 10 ) // Add elements to the array using .push() arr . push ( 1 ) // Retrive...
  • Expire旨在使使用缓存尽可能方便。 有三种创建缓存的方法,分别是MemoryCache,RedisCache或MemcachedCache。 如何使用? 安装 跑步: pip install expire pip install git+...
  • 本节,让我们来一起学习一下如何实现类似 redis 中的 expire 过期功能。 过期是一个非常有用的特性,比如我希望登录信息放到 redis 中,30min 之后失效;或者单日的累计信息放在 redis 中,在每天的凌晨自动清空。 ...

    前言

    我们在 从零手写 cache 框架(一)实现固定大小的缓存 中已经初步实现了我们的 cache。

    本节,让我们来一起学习一下如何实现类似 redis 中的 expire 过期功能。

    image

    过期是一个非常有用的特性,比如我希望登录信息放到 redis 中,30min 之后失效;或者单日的累计信息放在 redis 中,在每天的凌晨自动清空。

    代码实现

    接口

    我们首先来定义一下接口。

    主要有两个:一个是多久之后过期,一个是在什么时候过期。

    public interface ICache<K, V> extends Map<K, V> {
    
        /**
         * 设置过期时间
         * (1)如果 key 不存在,则什么都不做。
         * (2)暂时不提供新建 key 指定过期时间的方式,会破坏原来的方法。
         *
         * 会做什么:
         * 类似于 redis
         * (1)惰性删除。
         * 在执行下面的方法时,如果过期则进行删除。
         * {@link ICache#get(Object)} 获取
         * {@link ICache#values()} 获取所有值
         * {@link ICache#entrySet()} 获取所有明细
         *
         * 【数据的不一致性】
         * 调用其他方法,可能得到的不是使用者的预期结果,因为此时的 expire 信息可能没有被及时更新。
         * 比如
         * {@link ICache#isEmpty()} 是否为空
         * {@link ICache#size()} 当前大小
         * 同时会导致以 size() 作为过期条件的问题。
         *
         * 解决方案:考虑添加 refresh 等方法,暂时不做一致性的考虑。
         * 对于实际的使用,我们更关心 K/V 的信息。
         *
         * (2)定时删除
         * 启动一个定时任务。每次随机选择指定大小的 key 进行是否过期判断。
         * 类似于 redis,为了简化,可以考虑设定超时时间,频率与超时时间成反比。
         *
         * 其他拓展性考虑:
         * 后期考虑提供原子性操作,保证事务性。暂时不做考虑。
         * 此处默认使用 TTL 作为比较的基准,暂时不想支持 LastAccessTime 的淘汰策略。会增加复杂度。
         * 如果增加 lastAccessTime 过期,本方法可以不做修改。
         *
         * @param key         key
         * @param timeInMills 毫秒时间之后过期
         * @return this
         * @since 0.0.3
         */
        ICache<K, V> expire(final K key, final long timeInMills);
    
        /**
         * 在指定的时间过期
         * @param key key
         * @param timeInMills 时间戳
         * @return this
         * @since 0.0.3
         */
        ICache<K, V> expireAt(final K key, final long timeInMills);
    
    }
    

    代码实现

    为了便于处理,我们将多久之后过期,进行计算。将两个问题变成同一个问题,在什么时候过期的问题。

    核心的代码,主要还是看 cacheExpire 接口。

    @Override
    public ICache<K, V> expire(K key, long timeInMills) {
        long expireTime = System.currentTimeMillis() + timeInMills;
        return this.expireAt(key, expireTime);
    }
    
    @Override
    public ICache<K, V> expireAt(K key, long timeInMills) {
        this.cacheExpire.expire(key, timeInMills);
        return this;
    }
    

    缓存过期

    这里为了便于后期拓展,对于过期的处理定义为接口,便于后期灵活替换。

    接口

    其中 expire(final K key, final long expireAt); 就是我们方法中调用的地方。

    refershExpire 属于惰性删除,需要进行刷新时才考虑,我们后面讲解。

    public interface ICacheExpire<K,V> {
    
        /**
         * 指定过期信息
         * @param key key
         * @param expireAt 什么时候过期
         * @since 0.0.3
         */
        void expire(final K key, final long expireAt);
    
        /**
         * 惰性删除中需要处理的 keys
         * @param keyList keys
         * @since 0.0.3
         */
        void refreshExpire(final Collection<K> keyList);
    
    }
    

    expire 实现原理

    其实过期的实思路也比较简单:我们可以开启一个定时任务,比如 1 秒钟做一次轮训,将过期的信息清空。

    过期信息的存储

    /**
     * 过期 map
     *
     * 空间换时间
     * @since 0.0.3
     */
    private final Map<K, Long> expireMap = new HashMap<>();
    
    @Override
    public void expire(K key, long expireAt) {
        expireMap.put(key, expireAt);
    }
    

    我们定义一个 map,key 是对应的要过期的信息,value 存储的是过期时间。

    轮询清理

    我们固定 100ms 清理一次,每次最多清理 100 个。

    /**
     * 单次清空的数量限制
     * @since 0.0.3
     */
    private static final int LIMIT = 100;
    
    /**
     * 缓存实现
     * @since 0.0.3
     */
    private final ICache<K,V> cache;
    /**
     * 线程执行类
     * @since 0.0.3
     */
    private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
    public CacheExpire(ICache<K, V> cache) {
        this.cache = cache;
        this.init();
    }
    /**
     * 初始化任务
     * @since 0.0.3
     */
    private void init() {
        EXECUTOR_SERVICE.scheduleAtFixedRate(new ExpireThread(), 100, 100, TimeUnit.MILLISECONDS);
    }
    

    这里定义了一个单线程,用于执行清空任务。

    image

    清空任务

    这个非常简单,遍历过期数据,判断对应的时间,如果已经到期了,则执行清空操作。

    为了避免单次执行时间过长,最多只处理 100 条。

    /**
     * 定时执行任务
     * @since 0.0.3
     */
    private class ExpireThread implements Runnable {
        @Override
        public void run() {
            //1.判断是否为空
            if(MapUtil.isEmpty(expireMap)) {
                return;
            }
            //2. 获取 key 进行处理
            int count = 0;
            for(Map.Entry<K, Long> entry : expireMap.entrySet()) {
                if(count >= LIMIT) {
                    return;
                }
                expireKey(entry);
                count++;
            }
        }
    }
    
    /**
     * 执行过期操作
     * @param entry 明细
     * @since 0.0.3
     */
    private void expireKey(Map.Entry<K, Long> entry) {
        final K key = entry.getKey();
        final Long expireAt = entry.getValue();
        // 删除的逻辑处理
        long currentTime = System.currentTimeMillis();
        if(currentTime >= expireAt) {
            expireMap.remove(key);
            // 再移除缓存,后续可以通过惰性删除做补偿
            cache.remove(key);
        }
    }
    

    清空的优化思路

    如果过期的应用场景不多,那么经常轮训的意义实际不大。

    比如我们的任务 99% 都是在凌晨清空数据,白天无论怎么轮询,纯粹是浪费资源。

    那有没有什么方法,可以快速的判断有没有需要处理的过期元素呢?

    答案是有的,那就是排序的 MAP。

    我们换一种思路,让过期的时间做 key,相同时间的需要过期的信息放在一个列表中,作为 value。

    然后对过期时间排序,轮询的时候就可以快速判断出是否有过期的信息了。

    public class CacheExpireSort<K,V> implements ICacheExpire<K,V> {
    
        /**
         * 单次清空的数量限制
         * @since 0.0.3
         */
        private static final int LIMIT = 100;
    
        /**
         * 排序缓存存储
         *
         * 使用按照时间排序的缓存处理。
         * @since 0.0.3
         */
        private final Map<Long, List<K>> sortMap = new TreeMap<>(new Comparator<Long>() {
            @Override
            public int compare(Long o1, Long o2) {
                return (int) (o1-o2);
            }
        });
    
        /**
         * 过期 map
         *
         * 空间换时间
         * @since 0.0.3
         */
        private final Map<K, Long> expireMap = new HashMap<>();
    
        /**
         * 缓存实现
         * @since 0.0.3
         */
        private final ICache<K,V> cache;
    
        /**
         * 线程执行类
         * @since 0.0.3
         */
        private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
    
        public CacheExpireSort(ICache<K, V> cache) {
            this.cache = cache;
            this.init();
        }
    
        /**
         * 初始化任务
         * @since 0.0.3
         */
        private void init() {
            EXECUTOR_SERVICE.scheduleAtFixedRate(new ExpireThread(), 1, 1, TimeUnit.SECONDS);
        }
    
        /**
         * 定时执行任务
         * @since 0.0.3
         */
        private class ExpireThread implements Runnable {
            @Override
            public void run() {
                //1.判断是否为空
                if(MapUtil.isEmpty(sortMap)) {
                    return;
                }
    
                //2. 获取 key 进行处理
                int count = 0;
                for(Map.Entry<Long, List<K>> entry : sortMap.entrySet()) {
                    final Long expireAt = entry.getKey();
                    List<K> expireKeys = entry.getValue();
    
                    // 判断队列是否为空
                    if(CollectionUtil.isEmpty(expireKeys)) {
                        sortMap.remove(expireAt);
                        continue;
                    }
                    if(count >= LIMIT) {
                        return;
                    }
    
                    // 删除的逻辑处理
                    long currentTime = System.currentTimeMillis();
                    if(currentTime >= expireAt) {
                        Iterator<K> iterator = expireKeys.iterator();
                        while (iterator.hasNext()) {
                            K key = iterator.next();
                            // 先移除本身
                            iterator.remove();
                            expireMap.remove(key);
    
                            // 再移除缓存,后续可以通过惰性删除做补偿
                            cache.remove(key);
    
                            count++;
                        }
                    } else {
                        // 直接跳过,没有过期的信息
                        return;
                    }
                }
            }
        }
    
        @Override
        public void expire(K key, long expireAt) {
            List<K> keys = sortMap.get(expireAt);
            if(keys == null) {
                keys = new ArrayList<>();
            }
            keys.add(key);
    
            // 设置对应的信息
            sortMap.put(expireAt, keys);
            expireMap.put(key, expireAt);
        }
    }
    

    看起来是切实可行的,这样可以降低轮询的压力。

    这里其实使用空间换取时间,觉得后面可以做一下改进,这种方法性能应该还是不错的。

    不过我并没有采用这个方案,主要是考虑到惰性删除的问题,这样会麻烦一些,后续考虑持续改善下这个方案。

    惰性删除

    出现的原因

    类似于 redis,我们采用定时删除的方案,就有一个问题:可能数据清理的不及时。

    那当我们查询时,可能获取到到是脏数据。

    于是就有一些人就想了,当我们关心某些数据时,才对数据做对应的删除判断操作,这样压力会小很多。

    算是一种折中方案。

    image

    需要惰性删除的方法

    一般就是各种查询方法,比如我们获取 key 对应的值时

    @Override
    @SuppressWarnings("unchecked")
    public V get(Object key) {
        //1. 刷新所有过期信息
        K genericKey = (K) key;
        this.cacheExpire.refreshExpire(Collections.singletonList(genericKey));
        return map.get(key);
    }
    

    我们在获取之前,先做一次数据的刷新。

    刷新的实现

    实现原理也非常简单,就是一个循环,然后作删除即可。

    这里加了一个小的优化:选择数量少的作为外循环。

    循环集合的时间复杂度是 O(n), map.get() 的时间复杂度是 O(1);

    @Override
    public void refreshExpire(Collection<K> keyList) {
        if(CollectionUtil.isEmpty(keyList)) {
            return;
        }
        // 判断大小,小的作为外循环。一般都是过期的 keys 比较小。
        if(keyList.size() <= expireMap.size()) {
            for(K key : keyList) {
                expireKey(key);
            }
        } else {
            for(Map.Entry<K, Long> entry : expireMap.entrySet()) {
                this.expireKey(entry);
            }
        }
    }
    

    测试

    上面的代码写完之后,我们就可以验证一下了。

    ICache<String, String> cache = CacheBs.<String,String>newInstance()
            .size(3)
            .build();
    cache.put("1", "1");
    cache.put("2", "2");
    
    cache.expire("1", 10);
    Assert.assertEquals(2, cache.size());
    
    TimeUnit.MILLISECONDS.sleep(50);
    Assert.assertEquals(1, cache.size());
    
    System.out.println(cache.keySet());
    

    结果也符合我们的预期。

    小结

    到这里,一个类似于 redis 的 expire 过期功能,算是基本实现了。

    当然,还有很多优化的地方。

    比如为了后续添加各种监听器方便,我对所有需要刷新的地方调整为使用字节码+注解的方式,而不是在每一个需要的方法中添加刷新方法。

    下一节,我们将共同学习下如何实现各种监听器。

    对你有帮助的话,欢迎点赞评论收藏关注一波走起~

    你的鼓励,是我最大的动力~

    深入学习

    原文地址

    Cache Travel-09-从零手写 cache 之 redis expire 过期实现原理

    展开全文
  • expire

    2017-04-18 05:51:32
  • redis expire命令

    2020-11-30 15:54:47
    redis expire可以设置key的过期时间,用法:expire $key $seconds ttl key获取key的过期时间 127.0.0.1:3178> set test 1 OK 127.0.0.1:3178> get test "1" 127.0.0.1:3178> expire test (error) ERR ...

    redis expire可以设置key的过期时间,用法:expire $key $seconds

    ttl key获取key的过期时间

     

    127.0.0.1:3178> set test 1
    OK
    127.0.0.1:3178> get test
    "1"
    127.0.0.1:3178> expire test
    (error) ERR wrong number of arguments for 'expire' command   --expire必须加时间
    127.0.0.1:3178> expire test 5   --test过期时间为5s
    (integer) 1
    127.0.0.1:3178> ttl test   --ttl获取过期时间
    (integer) 2
    127.0.0.1:3178> ttl test
    (integer) -2    ---负2表示ttl没有获取到key
    127.0.0.1:3178> ttl test

    127.0.0.1:3178>  expire test 10   --test已经不再了,不能再次expire
    (integer) 0
    127.0.0.1:3178> ttl test
    (integer) -2

     


    127.0.0.1:3178> set test -1
    OK
    127.0.0.1:3178> get test
    "-1"
    127.0.0.1:3178> expire test 10   --再次测试expire的倒计时
    (integer) 1
    127.0.0.1:3178> ttl test
    (integer) 7
    127.0.0.1:3178> ttl test
    (integer) 6
    127.0.0.1:3178> ttl test
    (integer) 5
    127.0.0.1:3178> ttl test
    (integer) 4
    127.0.0.1:3178> ttl test
    (integer) 3
    127.0.0.1:3178> ttl test
    (integer) 2
    127.0.0.1:3178> ttl test
    (integer) 1
    127.0.0.1:3178> ttl test
    (integer) 0
    127.0.0.1:3178> ttl test
    (integer) -2
    127.0.0.1:3178> ttl test
    (integer) -2

     

     

    总结:

    1.redis的expire用于使key过期,过期就是使key不可用,key相当于被del

    2.ttl可以查看key的过期倒计时

    展开全文
  • 这里写目录标题12.1 EXPIRE、PEXPIRE:设置生存时间12.1.1 更新键的生存时间12.1.2 其他信息参考目录 12.1 EXPIRE、PEXPIRE:设置生存时间         用户可以通过执行EXPIRE...

    12.1 EXPIRE、PEXPIRE:设置生存时间

            用户可以通过执行EXPIRE命令或者PEXPIRE命令为键设置一个生存时 间(Time To Live,TTL)键的生存时间在设置之后就会随着时间的 流逝而不断地减少,当一个键的生存时间被消耗殆尽时,Redis就会移 除这个键。

            Redis提供了EXPIRE命令用于设置秒级精度的生存时间,它可以让键在 指定的秒数之后自动被移除:

    在这里插入图片描述

            而PEXPIRE命令则用于设置毫秒级精度的生存时间,它可以让键在指定 的毫秒数之后自动被移除:

    在这里插入图片描述

            EXPIRE命令和PEXPIRE命令在生存时间设置成功时返回1;如果用户给 定的键并不存在,那么命令返回0表示设置失败。

            以下是一个使用EXPIRE命令的例子:
    在这里插入图片描述

            上面的代码通过执行EXPIRE命令为msg键设置了5s的生存时间:

            ·如果我们在5s之内访问msg键,那么Redis将返回msg键的值"hello world"。

            ·如果我们在5s之后访问msg键,那么Redis将返回一个空值,因为msg键 已经自动被移除了。

    在这里插入图片描述

            以下则是一个使用PEXPIRE命令的例子:
    在这里插入图片描述

            表12-2展示了number键从设置生存时间到被移除的整个过程。

    在这里插入图片描述

    12.1.1 更新键的生存时间

            当用户对一个已经带有生存时间的键执行EXPIRE命令或PEXPIRE命令 时,键原有的生存时间将会被移除,并设置新的生存时间。

            举个例子,如果我们执行以下命令,将msg键的生存时间设置为10s:

    在这里插入图片描述

            然后在10s之内执行以下命令:

    在这里插入图片描述

            那么msg键的生存时间将被更新为50s,并重新开始倒数,表12-3展示了 这个更新过程。

    在这里插入图片描述

    12.1.2 其他信息

            复杂度:EXPIRE命令和PEXPIRE命令的复杂度都为O(1)。

            版本要求:EXPIRE命令从Redis 1.0.0版本开始可用,PEXPIRE命令从 Redis 2.6.0版本开始可用。

    参考目录

    绝大多数 内容来自 Redis使用手册 (黄健宏 著) 第12章 自动过期

    展开全文
  • redis的expire总结

    2021-01-18 19:28:17
    String和其他redis数据结构expire是不一样的。 String 有点不一样: 具体见我置顶的博文redis手写总结 1.2.3 容器型数据结构的通用规则 list,set,hash,zset 是容器型数据结构 create if not exists drop if ...
  • Redis之EXPIRE

    千次阅读 2018-04-29 15:58:57
    Redis之EXPIRE通过expire或者pexpire命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间(Time To Live,TTL),在经过指定的秒数或者毫秒数之后,服务器就会自动删除生存时间为0的键。与expire和...
  • 若在启动时binlog_expire_logs_seconds和expire_logs_days参数都设置为非0值则使用binlog_expire_logs_seconds值,expire_logs_days值则失效并对其发出告警信息。 若要关闭自动清除binlog文件的功能则需要显示指定...
  • Expire-开源

    2021-04-27 06:21:49
    Expire允许您根据欧洲指令INSPIRE根据ISO 19139,ISO19115和ISO19110输入和导出元数据文件。 该应用程序生成XML,然后可以将其直接导入GeoSou
  • Oracle 监听SQLNET.EXPIRE_TIME

    万次阅读 2018-08-01 14:55:59
    在这边数据库加固有如下一个加固项,使用SQLNET.EXPIRE_TIME可以来断开在session里面超时的状态为inactive的连接。 检查是否设置超时时间 注意事项及影响: 作用:非活动会话超过10分钟,连接断开 该项需要与...
  • redis之expire命令详解

    2020-06-30 09:19:17
    expire是设置redis过期时间的命令,需要注意的点有以下几点 expire设置过期时间的单位是秒,如设置name的过期时间为1000秒 expire name 1000 超过时间后会自动删除key,但是不一定是立即删除,因为redis的过期...
  • Redis键的生存时间(expire)

    千次阅读 2019-06-22 11:03:18
    一、redis中可以使用expire命令设置一个键的生存时间,到时间后redis会自动删除它。 expire 设置生存时间(单位/秒) pexpire设置生存时间(单位/毫秒) ttl/pttl 查看键的剩余生存时间 persist 取消生存时间 ...
  • taskwarrior-tag_expire-hook 这个 taskwarrior 钩子脚本旨在 弄湿我的脚,学习钩子脚本是如何工作的,从一个基本的开始 为用户提供一个简单的替代方法来分配一个直到:日期值,即在到期:日期之后,对于那些在到期...
  • nginx的expire实践

    2020-04-03 16:44:20
    img src='test.jpg'></img>标签和jpg文件,修改nginx默认配置文件增加 location ~* \.(jpg|jpeg|png|gif){ expires 5m; } ...设置图片过期时间为5分钟(注意如果修改expires的值,除了重新加载ngin...
  • Redisson中expire用法

    千次阅读 2020-05-14 19:06:19
    list2.expire(3, TimeUnit.MINUTES); //失效 list2.add("two"); RList list3 = redissonClient.getList("myExpiredKey3"); list3.add("three"); list3.expire(3, TimeUnit.MINUTES); //有效...
  • redis设置expire以及删除机制

    千次阅读 2019-03-23 13:45:32
    Redis无论有没有设置expire,他都会遵循redis的配置好的删除机制,在配置文件里设置: redis最大内存不足"时,数据清除策略,默认为"volatile-lru"。 volatile-lru ->对"过期集合"中的数据采取LRU(近期最少使用)...
  • redis expire方式设置缓存时间的坑

    千次阅读 2019-10-01 14:20:45
    最近入职后一直很忙,今天国庆值班,...但是redis对于hash类型,并没有提供直接设置超时时间的支持,于是设计采用expire一个key的过期的时间方式来实现当日过期。 2、问题 通过观察发现,隔天的数据,在共用一个k...
  • 作者:胡呈清 ...如果你正在使用 MySQL8.0 ,并且在使用物理热备工具,那么 binlog_expire_logs_seconds 可能不会如你预想的那样生效。 binlog_expire_logs_seconds 为了防止 binlog 文件过大导致无.
  • MySQL expire_logs_days 参数用于控制Binlog文件的保存时间,当Binlog文件存在的时间超过该参数设置的阈值时,Binlog文件就会被自动清除,该参数的时间单位是天,设置为0,表示Binlog文件永不过期,即不自动清除...
  • persist用来设置某个key永不过期,但是这个key要求是有效的key即该key还未过期, persist修改过期的key是无效的 expire修改过期的key也是无效的
  • Expire Redis Expire 命令用于设置 key 的过期时间。key 过期后将不再可用。 用法:Expire key 127.0.0.1:6379 [8] > set keyname keyvalue OK 127.0.0.1:6379 [8] > expire keyname 30 (integer) 1 设置了...
  • Expire 为key设置过期时间 Setex 为key设置value值,并且,设置过期时间 区别 Setex是一个原子操作 设置值,设置过期时间两个动作,会在同一时间完成 在Redis缓存中,非常实用 补充知识:redis之setnx、setex、...
  • 自知对Redis的知识了解的还算不错,但当面试官问到expire是怎么实现的时候我突然懵了,虽然最后凭借了猜测也猜出了定期+惰性删除,但总感觉这块之前复习遗漏了,现在来重新梳理一下。 面试官:你知道expire设置过期...
  • EXPIRE

    2014-07-23 15:23:02
    EXPIRE命令的使用方法为EXPIRE key seconds
  • 线上时不时会报出类似的报错,看... nested exception is io.lettuce.core.RedisCommandExecutionException: ERR invalid expire time in setex org.springframework.data.redis.RedisSystemException: Error in ex..
  • Redis无论有没有设置expire,他都会遵循redis的配置好的删除机制,在配置文件里设置: redis最大内存不足"时,数据清除策略,默认为"volatile-lru"。 volatile-lru ->对"过期集合"中的数据采取L...
  • EXPIRE key seconds 设置key的生存时间还剩多少秒 EXPIREAT key timestamp 设置key生存到什么时候,接受的参数是时间戳 redis命令参考:http://doc.redisfans.com/
  • Redis - Expire & Setex

    2020-03-30 21:25:53
    Expire 为 key 设置过期时间 Setex 为 key 设置 value 值,以及设置过期时间 区别 Setex是一个原子操作。设置值,设置过期时间两个动作,会在同一时间完成 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 151,571
精华内容 60,628
关键字:

expire