redis-缓存设计-自动延迟调度,最热商品缓存(二) 需求 自动延迟调度 最热商品缓存 实际使用例子

1.实现任意数据行的可以设计不同的延迟周期进行刷新或者同步任务

2.最热的2000个商品缓存

自动延迟调度

加入调度列表

/**
     * 将需要主动更新的的数据加入自动调度列表
     * @param conn
     * @param row_id
     * @param delay
     */
    public static void  scheduleRowCache(Jedis conn,String row_id,int delay){
           //记录此id周期多少秒执行一次 刷新
           conn.zadd("delay:",delay,row_id);
           //加入定时执行列表 并设置立即执行
           conn.zadd("schedule:",System.currentTimeMillis(),row_id);
    }

调度方法

    public static  void  run(Jedis conn){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    //获取需要立即执行的列表
                    Set<String> ids = conn.zrangeByScore("schedule:", 0, System.currentTimeMillis());
                    //没有则释放时间片 同时暂停500毫秒
                    if (ids == null || ids.size() <= 0) {
                        Thread.yield();
                        try {
                            Thread.sleep(500L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    for (String id :
                            ids) {

                        System.out.println(id + "执行了数据刷新");
                        //获得此数据延迟时间
                        Double delay = conn.zscore("delay:", id);
                        //重新加入延迟队列
                        //加入定时执行列表 并设置立即执行
                        conn.zadd("schedule:", System.currentTimeMillis() + (delay * 1000), id);
                    }
                }
            }
        }).start();;
    }

测试

  public static final void main(String[] args)
            throws Exception {
        Jedis conn = new Jedis("127.0.0.1", 6379);
        conn.select(15);
        //5秒刷新一次
        scheduleRowCache(conn,"1",5);
        run(conn);
        Thread.sleep(1000000);
    }

最热商品缓存

访问商品

/**
     * 更新rank
     * @param conn
     * @param productId
     * @return
     * @throws InterruptedException
     */
    public static String viewProduct(Jedis conn,String productId) throws InterruptedException {
        //更新评分
        conn.zincrby("viewed:",-1,productId);
        //获得排名
        double rank= conn.zrank("viewed:",productId);
        String html=null;
        //如果<=2000表示走缓存
        if(rank<=2000){
            //先查缓存
            html=  conn.get("product:"+productId);
            if(html==null){
                html=select(productId);
                conn.set("product:"+productId,html);
            }
        }else{
            html =select(productId);
        }
        return html;
    }
  /**
     * 模拟db 和生成html
     * @return
     * @throws InterruptedException
     */
    public static String select(String productId) throws InterruptedException {
        Thread.sleep(2000);
        return String.format("<html>我是商品%s的商品详情html</html>",productId);
    }

定时任务清除非热点数据

 /**
     * 此方法应该定时任务5秒调用一次
     * @param conn
     */
    public static void clearCache(Jedis conn){
                conn.zremrangeByRank("viewed:",0,-2001);
        //将所有商品的浏览数量降低一半 未测试通过,,,不造咋减半的 书上py是这样写
        conn.zinterstore("viewed:","{'viewed:':0.5");
    }

2020-08-21:已解决 通过 WEIGHTS 设置乘法因子 默认是1  我们设置0.5 就会在原有的基础上* 0.5

参考:http://doc.redisfans.com/sorted_set/zunionstore.html#zunionstore

测试

 public static   void main(String[] args)
            throws Exception {
        Jedis conn = new Jedis("127.0.0.1", 6379);
        conn.select(15);
        viewProduct(conn,"3");
        viewProduct(conn,"3");
        viewProduct(conn,"3");
        viewProduct(conn,"3");
        viewProduct(conn,"3");
        viewProduct(conn,"3");
        //清空2000排名以外的 同时重置2000以内的排名 conn.zscore("viewed:","3")
        clearCache(conn);
        Thread.sleep(1000000);
    }

实际使用例子

比如我们线上几千家门店,用户进入门店首页,为了追求性能会在redis增加缓存,但是每个门店都按照传统的 设置缓存加失效时间那么redis内存消耗会很大。

实际中有些偏远的门店,用户数会很少,所以统计最热门店,使用定时任务每10分钟剔除缓存 然后将最热门店的程序主动刷新。而不那么热门的门店则不使用缓存