天天看点

使用redis实现页面缓存

本文是<<redis in action>>一书 第二章的读书笔记

我们要缓存的就是形式如下面的url

​​ http://test.com/shwoItem?item=itemX

​​

设计方案

第一个域,是zset类型的------key是viewed:userSessionid

存放的是商品id与用户查看此商品时的timestamp

第二个域,是zset类型的------可以是viewed:

member是商品id(全局范围内),score是商品被用户(全局范围内)浏览的次数(是负数)

第三个域,是hash类型的------key是item:itemId

存放的是商品的信息

用户查看了某件商品

​​  public void viewItem(Jedis conn, String token, String user, String item) {
    long timestamp = System.currentTimeMillis() / 1000;


    if (item != null) {
            //用户user在某时浏览了某件商品
      conn.zadd("viewed:" + token, timestamp, item);
      //只记录用户最近查看的25件商品
      conn.zremrangeByRank("viewed:" + token, 0, -26);
      //有序set里 score的越小排名越靠前
      conn.zincrby("viewed:", -1, item); //-1还是1?
    }
  }​​      

试着缓存这个页面

首先我们要判断,某个url是否能缓存

​​  /**
   * 如果request 不符合规范 就不缓存 返回false
   * 如果商品还没有被访问过 自然不能缓存 返回false
   * 如果商品被访问过 但是访问的排名在10000之后 也就不缓存了 返回false
   * 如果商品被访问过 且访问的排名在10000之内 就说明可以缓存 返回true
   * 
   * @param conn
   * @param request
   * @return
   */
  public boolean canCache(Jedis conn, String request) {
    //从类似http://test.com/shwoItem?item=itemX
    //这样的字符串中获得paramter 代码就不赘述了
    Map<String, String> params = getParams(request);
    if (params == null) 
      return false;
    
    //获得itemX这个信息
    String itemId = extractItemId(params);
    //params.containsKey("_") 这个条件只是示例
    //用户可以加上自己的判别条件
    if (itemId == null || params.containsKey("_")) {
      return false;
    }
    // viewed:这个有序集里存放的是 商品的访问次数
    Long rank = conn.zrank("viewed:", itemId);
    // 查看这个商品的访问量是否在前10000内
    //如果rank==null 说明商品还没有被访问过
    return rank != null && rank < 10000;


  }​​      

实现缓存request

public String cacheRequest(Jedis conn, String request, Callback callback) {
    if (!canCache(conn, request)) {
      return callback != null ? callback.call(request) : null;
    }


    String pageKey = "cache:" + hashRequest(request);
    //这个只是示例 真实的情况 应该是复杂的业务逻辑产生最后的反馈页面
    String content = conn.get(pageKey);  


    if (content == null && callback != null) {
      content = callback.call(request);
      //我们可以认为这个content就是html
      //我们将要显示的数据缓存5分钟
      conn.setex(pageKey, 300, content);
    }


    return content;
  }​​      

传入的callback如下:

​​    Callback callback = new Callback() {
      public String call(String request) {
        return "content for " + request;
      }
    };​​      

我们看看测试代码

​​package redisinaction;


import org.junit.BeforeClass;
import org.junit.Test;


import redis.clients.jedis.Jedis;
import redisinaction.Chapter02.Callback;


/**   
 * This class is used for ...   
 * @author  dlf([email protected])
 * @version 1.0, 2016年10月18日 下午9:34:27   
 */
public class Chapter02Test {
  
  
  static Jedis conn = null;
  static Chapter02 ch2=null;
  
  @BeforeClass        
  public static void initConn(){
    System.out.println("test before");
    ch2=new Chapter02();    
    conn = new Jedis("10.150.0.80");
    conn.auth("dlf123123");
  }  
  
  @Test
  public void testCacheRequest() {
    
    Callback callback = new Callback() {
      public String call(String request) {
        return "content for " + request;
      }
    };
    //必须先viewItem 否则不管调用几次cacheRequest 都不能缓存
    //为什么? 自己想
    ch2.viewItem(conn, "dlf_session_id", "dlf", "itemX");


    String url = "http://test.com/?item=itemX";
    String result = ch2.cacheRequest(conn, url, callback);
    System.out.println( result);
    
    String result3 = ch2.cacheRequest(conn, url, null);
    System.out.println(result3);
    
    String result4 = ch2.cacheRequest(conn, url, null);
    System.out.println(result4);
  }      

全局范围内被用户查看最多的n件产品(和上一章那个文章排序差不多)

​​    private static final int ARTICLES_PER_PAGE = 25;
    public List<Map<String,String>>  getMostPopulate(Jedis conn, int page) {
        int start = (page - 1) * ARTICLES_PER_PAGE;
        int end = start + ARTICLES_PER_PAGE - 1;
        Set<String> ids = conn.zrevrange("viewed:", start, end);
        
        
        List<Map<String,String>> items = new ArrayList<Map<String,String>>();
        for (String id : ids){
            Map<String,String> itemData = conn.hgetAll(id);
            itemData.put("id", id);
            items.add(itemData);
        }
        return items;
    }​​      

在viewItem里,我们已经记录了所有商品的浏览次数(负数)

conn.zincrby("viewed:", -1, item);

那么经过n天后,这个viewed里的数据项就很多了,而且其实也没有必要一直保存着所有商品的浏览次数

所以

​​public class RescaleViewedThread implements  Runnable{
    private Jedis conn;
    private boolean quit;


    public RescaleViewedThread(int limit) {     
      conn = new Jedis("10.150.0.80");
          conn.auth("dlf123123");
    }
    public void shutDown(){
      quit=true;
    }
    
    @Override
    public void run() {
      while (!quit) {
        try {
          //移除排名20000之后的商品浏览信息
          conn.zremrangeByRank("viewed:", 0, -20001);
          //将所有商品的浏览数量降低一半
          conn.zinterstore("viewed","viewed:0.5");
          Thread.sleep(300);
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
        
      }
      
    }
    
  }​​      

系统启动后就,一直运行这个线程。

那么怎么做呢?怎么集成到我的项目里呢?我的项目是使用struts2开发的。

哥哥,您不知道有个东西叫拦截器么?interceptor?

看这个:​​http://www.2cto.com/kf/201506/408009.html

这个是springmvc的?struts怎么用?

笨死!