如何使用redis缓存来实现用户最近浏览的商品列表

  如何使用redis缓存来实现用户最近浏览的商品列表

首先,我们要弄明白两个要点:最近浏览的商品肯定是一个存和取的两个操作.好了目前摆在我们面前的有以下几个问题:

          1,最近浏览的记录肯定是需要失效时间的

                确定使用缓存,缓存可以设置失效时间(最大设置为一个月,但是这已经足够了),如果使用mysql等数据库,还需要定时任务清除,很明显是不切合实际的,

          2,最近浏览的记录肯定是有个数限制的,不可能记录所有的浏览记录

                目前使用的主流缓存有 memached和redis两种(原谅无知的我并不清楚其他的),redis有LTRM来修剪,保证存储的浏览的条数;

          3,我们需要在哪里添加保存浏览商品的方法

                用户最近浏览的商品,肯定是再用户最近打开商品详情页的时候才算浏览,这点毋庸置疑;

          4,怎么保证每次添加的浏览的商品列表按着浏览的先后顺序排序?

                每次用户的浏览商品的ID,可以以用户的ID作为key,以List作为value,储存在redis中,而List是有序的,而且,在使用LRANGE的时候能保证先进后出,后进先出的原则,已达到排列在最前面的商品始终是里当前最近浏览的那个商品;

          5,怎么保证用户在连续浏览同一个商品的时候,不会重复保存商品?

              可以使用redis中LREM来移除列表中与参数 value(该商品ID) 相等的元素。同时在使用Lpush重新再List插入最新的浏览商品;

          6,读取缓存的时候,又该如何保证分页?

             redis中的LRANGE可以指定获取指定长度的元素,能够满足需要;

 

下面是简单的实现思路:

      1,储存用户浏览的商品:

 

           用户在打开详情页的时候,以用户ID作key,商品的ID做值,以List存入redi缓存中;

           在加入添加缓存之前,为了保证浏览商品的 唯一性,每次添加前,使用lrem将缓存的list中该商品ID去掉,在加入,以保证其浏览的最新的商品在最前面;

           在lpush到redis的List中之后,根据产品需求还需要将该list的前60个数据之外的缓存修剪掉;

           最后添加缓存失效时间30天;

     2.获取用户最近浏览的商品列表:

         根据用户的ID及当前的页数和每页的个数,来获取商品缓存;

下面是最后实现的代码:

    1,根据用户ID和商品Id存入到缓存中:

  public void addMemberResentGoods(Long memberId, Long templateId) {
        String key = RedisKeyUtil.generteKeyWithPlaceholder(RedisKeys.MEMBER_RECENT_GOODS, memberId);
        //为了保证浏览商品的 唯一性,每次添加前,将list 中该 商品ID去掉,在加入,以保证其浏览的最新的商品在最前面
        redisService.lrem(key, 1, templateId.toString());
        //将value push 到该key下的list中
        redisService.lpush(key,templateId.toString());
        //使用ltrim将60个数据之后的数据剪切掉
        redisService.lTrim(key,0,59);
        //设置缓存时间为一个月
        redisService.expire(key,60*60*24*30);
    }

    2,根据用户的ID,分页获取最近浏览的商品:

  public Map<String,Object> queryMemberResentGoods(Long memberId, int page, int pageSize) {
        String key = RedisKeyUtil.generteKeyWithPlaceholder(RedisKeys.MEMBER_RECENT_GOODS, memberId);
        //获取用户的浏览的商品的总页数;
        long pageCount = redisService.llen(key);
        //根据用户的ID分頁获取该用户最近浏览的50个商品信息
        List<String> result = redisService.lrange(key,(page-1)*pageSize,page*pageSize-1);
        //拼装返回
        Map<String,Object> map = new HashMap<>();
        map.put("result",result);
        map.put("pageCount",(pageCount%pageSize == 0 ? pageCount/pageSize : pageCount/pageSize+1));
        return map;
    }

补充:  下面是一些根据业务需要用 jedis 封装的 上面的 redisService 

  public interface  RedisService{
        
    /**
     * 根据参数 count 的值,移除列表中与参数 value 相等的元素。
     * <p>
     * count 的值可以是以下几种:
     * <p>
     * count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
     * count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
     * count = 0 : 移除表中所有与 value 相等的值。
     * 返回值:
     * 被移除元素的数量。
     * 因为不存在的 key 被视作空表(empty list),所以当 key 不存在时, LREM 命令总是返回 0 。
     */
    public Long lrem(String key, long count, String value);

    /**
     * Add the string value to the head (LPUSH) or tail (RPUSH) of the list
     * stored at key. If the key does not exist an empty list is created just
     * before the append operation. If the key exists but is not a List an error
     * is returned.
     * <p>
     * Time complexity: O(1)
     *
     * @param key
     * @param string
     * @return Integer reply, specifically, the number of elements inside the
     * list after the push operation.
     */
    public Long lpush(final String key, final String string);

    /**
     * LTRIM操作
     * <p>
     * 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
     * <p>
     * 举个例子,执行命令 LTRIM list 0 2 ,表示只保留列表 list 的前三个元素,其余元素全部删除。
     * <p>
     * 下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。
     * <p>
     * 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
     * <p>
     * 当 key 不是列表类型时,返回一个错误。
     */

    String lTrim(String key, final long start, final long end);


    /**
     * 设置一个键的相对过期时间,O(1)
     *
     * @param key     键
     * @param seconds 过期秒数
     * @return true 设置成功,false 设置失败
     * @throws JedisConnectionException 若和redis服务器的连接不成功
     */
    boolean expire(String key, int seconds);

    /**
     * Return the specified elements of the list stored at the specified key.
     * Start and end are zero-based indexes. 0 is the first element of the list
     * (the list head), 1 the next element and so on.
     * <p>
     * For example LRANGE foobar 0 2 will return the first three elements of the
     * list.
     * <p>
     * start and end can also be negative numbers indicating offsets from the
     * end of the list. For example -1 is the last element of the list, -2 the
     * penultimate element and so on.
     * <p>
     * <b>Consistency with range functions in various programming languages</b>
     * <p>
     * Note that if you have a list of numbers from 0 to 100, LRANGE 0 10 will
     * return 11 elements, that is, rightmost item is included. This may or may
     * not be consistent with behavior of range-related functions in your
     * programming language of choice (think Ruby's Range.new, Array#slice or
     * Python's range() function).
     * <p>
     * LRANGE behavior is consistent with one of Tcl.
     * <p>
     * <b>Out-of-range indexes</b>
     * <p>
     * Indexes out of range will not produce an error: if start is over the end
     * of the list, or start > end, an empty list is returned. If end is over
     * the end of the list Redis will threat it just like the last element of
     * the list.
     * <p>
     * Time complexity: O(start+n) (with n being the length of the range and
     * start being the start offset)
     *
     * @param key
     * @param start
     * @param end
     * @return Multi bulk reply, specifically a list of elements in the
     * specified range.
     */
    public List<String> lrange(String key, final long start, final long end);

    /**
     * Return the length of the list stored at the specified key. If the key
     * does not exist zero is returned (the same behaviour as for empty lists).
     * If the value stored at key is not a list an error is returned.
     * <p>
     * Time complexity: O(1)
     *
     * @param key
     * @return The length of the list.
     */
    public long llen(String key);
    
    }

 redisServiceImpl 实现类,这里没有必要完全按照这个来,自己可以直接可以直接 看看 Jedis 里的 方法,自己封装一些自己适合的方法

public class RedisServiceImpl implements RedisService {
        private  final Logger logger = LoggerFactory.getLogger(com.mamahao.eb.framework.redis.RedisServiceImpl.class);
        // redis lua scripts(supportted since redis 2.6)
        private static final String CAS_CMD = "local v=redis.call('get',KEYS[1]);local r=v;local n=#ARGV-1;local tb=ARGV[#ARGV];local succ='F';for i=1, n do if(v==ARGV[i]) then redis.call('set',KEYS[1],tb); r=tb; succ='T' break;end end;return {succ,r}";
        private static final String SUBS_CMD = "local v=redis.call('incrby',KEYS[1],0);local r=ARGV[1]-v;redis.call('set',KEYS[1],ARGV[1]);return r;";
        private volatile String CAS_KEY;
        private volatile String SUBS_KEY;
        private String redisAddr;
        private String[] addrInfo;
        private Pool<Jedis> jedisPool;
        private String sentinels;
        private long redisSubMaintainInterval = 10000;
        private int maxActive = 10;
        private int maxIdle = 5;
        private int timeOut = 2000;
        private String password;

        public void setPassword(String password) {
            this.password = password;
        }

        public void setRedisAddr(String redisAddr) {
            this.redisAddr = redisAddr;
        }

        public void setMaxActive(int maxActive) {
            this.maxActive = maxActive;
        }

        public void setMaxIdle(int maxIdle) {
            this.maxIdle = maxIdle;
        }

        public void setSentinels(String sentinels) {
            this.sentinels = sentinels;
        }

        public void init() {
            if (redisAddr == null || "".equals(redisAddr.trim()) || "NULL".equals(redisAddr.trim())) {
                logger.warn("no redis addr was configured,this redis service will be unavaliable");
                return;
            }
            JedisPoolConfig cfg = new JedisPoolConfig();
            cfg.setMaxIdle(maxIdle);// 设置最大连接数
            cfg.setMaxTotal(maxActive); //add by chenxinchao 20151016
            if (!StringUtil.isBlank(sentinels)) {
                // addr info is a name rather than ip:port
                String[] sentinelsInfo = sentinels.split(",");
                Set<String> sentinelSet = new HashSet<>();
                for (String s : sentinelsInfo) {
                    sentinelSet.add(s.trim());
                }
                if(StringUtils.isNotBlank(password)){
                    jedisPool = new JedisSentinelPool(redisAddr, sentinelSet, cfg,timeOut,password);
                }else {
                    jedisPool = new JedisSentinelPool(redisAddr, sentinelSet, cfg,timeOut);
                }
            } else {
                addrInfo = redisAddr.split(":");
                jedisPool = new JedisPool(cfg, addrInfo[0], Integer.valueOf(addrInfo[1]), timeOut, password);
            }
            // load cas scripts
            Jedis jedis = jedisPool.getResource();
            try {
                CAS_KEY = jedis.scriptLoad(CAS_CMD);
                SUBS_KEY = jedis.scriptLoad(SUBS_CMD);
            } finally {
                jedis.close();
            }
        }

        public void stop() {
            if (jedisPool != null) {
                jedisPool.destroy ();
            }
        }

        @Override
        public long llen(String key) {
            Jedis jedis = jedisPool.getResource();
            try {
                return jedis.llen(key);
            } catch (JedisConnectionException e) {
                jedis.close();
                throw e;
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }

        @Override
        public List<String> lrange(String key, final long start, final long end) {
            Jedis jedis = jedisPool.getResource();
            try {
                return jedis.lrange(key, start, end);
            } catch (JedisConnectionException e) {
                jedis.close();
                throw e;
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }


        @Override
        public boolean expire(String key, int seconds) {
            Jedis jedis = jedisPool.getResource();
            try {
                long result = jedis.expire(key, seconds);
                return result > 0;
            } catch (JedisConnectionException e) {
                jedis.close();
                throw e;
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }


        @Override
        public String lTrim(String key, long start, long end) {
            Jedis jedis = jedisPool.getResource();
            try {
                return jedis.ltrim(key, start, end);
            } catch (JedisConnectionException e) {
                jedis.close();
                throw e;
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }


        @Override
        public Long lpush(String key, String string) {
            Jedis jedis = jedisPool.getResource();
            try {
                return jedis.lpush(key, string);
            } catch (JedisConnectionException e) {
                jedis.close();
                throw e;
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }


        @Override
        public Long lrem(String key, long count, String value) {
            Jedis jedis = jedisPool.getResource();
            try {
                return jedis.lrem(key, count, value);
            } catch (JedisConnectionException e) {
                jedis.close();
                throw e;
            } finally {
                if (jedis != null) {
                    jedis.close();
                }
            }
        }



    }

 

  • 12
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值