redis-缓存设计-统计1秒 5秒 1分钟 访问数量

记录统计

主要是通过精度算出时间各个时间片的开始时间 作为hash 相同时间片开始时间是一致的 天统计 时间片都是从日期的早8点开始

 /**
     * 毫秒为单位 统计1秒 5秒 1分钟 1小时 5小时 1天的统计信息
     */
   static Integer[] preisions = new Integer[]{1000, 5000, 60000, 300000, 3600000, 18000000, 86400000};

    public static void updateCounter(Jedis conn, int productId, int count) {
        Long currentDate = System.currentTimeMillis();
        for (int i = 0; i < preisions.length; i++) {
            Integer index = preisions[i];
            //算出指定时间维度的开始时间片
            Long startDate = (Long) (currentDate / index) * index;
            String hash = "product:" + productId;
            //指定时间片的精度+1
            conn.hincrBy(hash, startDate.toString(), count);
            //将清理的key加入到一个回收的set 存储key和精度
            conn.zadd("recovery", 0, String.format("%s_%s_%s", hash, index, startDate));
        }
    }

获取统计

通过精度算出开始时间时间片 然后再hash获取统计信息

 /**
     * 获得指定精度的统计数量
     * @param conn
     * @param productId
     * @param preisions
     * @return
     */
    public static String getCounter(Jedis conn,int productId,Integer preisions){
        Long startDate = (Long) (System.currentTimeMillis() / preisions) * preisions;
        String hash = "product:" + productId;
        return conn.hget(hash,startDate.toString());
    }

数据清理

随着时间的增长 hash时间片会越来越多,清理老的时间片

   /**
     * 这里应该使用管道,因为方便打印日志 所以没有使用管道
     * @param conn
     */
    public static void clearCounter(Jedis conn)  {
        new Thread(new Runnable() {
            @Override
            public void run() {
                String recoveryKey = "recovery";
                int index = 0;
                while (true) {
                    if (conn.zcard("recovery") <= 0) {
                        try {
                            Thread.sleep(1000);//没有可回收的时候直接等待 休息一会儿
                            continue;
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //每次检查回收50个
                    Set<String> hashs = conn.zrange(recoveryKey, 0, 50);
                    for (String hash :
                            hashs) {
                        String[] hashArray = hash.split("_");
                        int preision = Integer.valueOf(hashArray[1]);//取得精度
                        Long startDate = Long.valueOf(hashArray[2]);
                        String productKey = hashArray[0];//取得数据hash key
                        //开始时间加上精度 如果小于当前时间 表示时间片过了 执行删除
                        final Calendar calendar = Calendar.getInstance();
                        calendar.setTime(new Date(startDate));
                        calendar.add(Calendar.MILLISECOND, preision);
                        //在时间片之内
                        if (calendar.getTimeInMillis() > System.currentTimeMillis()) {
                            continue;
                        } else {
                            //执行删除
                            Long result = conn.hdel(productKey, startDate.toString());
                            conn.zrem(recoveryKey,hash);
                            System.out.println(String.format("移除了key:%s,过期数据%s,删除结果:%s", productKey, startDate.toString(), result));
                        }
                    }
                }
            }
        }).start();
    }

main方法

  public static void main(String[] args)
            throws Exception {
        Jedis conn = new Jedis("127.0.0.1", 6379);
        Jedis conn2 = new Jedis("127.0.0.1", 6379);
        Jedis conn3 = new Jedis("127.0.0.1", 6379);
        conn.flushDB();
        //启动清理器
        clearCounter(conn2);
        for(int i=0;i<65;i++){
            Thread.sleep(1000);
            updateCounter(conn,1,1);
        }
        //获得 1分钟的统计数量
       System.out.println("一分钟的统计数量:"+getCounter(conn3,1,60000));

    }

打印

真实案例

文章访问数量,

某个通过时间片为key 某个时间范围内的都incr 累加 

然后写入list或set

后续时间片过了 再将这个key的数值写入到数据库 避免数据库每次访问都i++

 

posted @ 2020-07-23 11:12  意犹未尽  阅读(1785)  评论(0编辑  收藏  举报