Skip to content

先来分析下淘宝聚划算的特点,访问量大但是更新频率不高且更新频率固定,但是数据是分页的所以必然要支持分页才行。

TIP

redis的list数据结构天然支持这种高并发的分页查询功能,具体的技术方案采用lpush和lrange来实现。

  • lpush:将插入的数据放在列表的开头,假设列表是从左往右放置的,每次使用LPUSH命令插入元素就是把这个元素放在最左侧
  • lrange:返回指定索引范围的元素,列表的第一个元素索引是0,第二个元素索引是1,依次类推,也可以使用负数索引,-1表示最后一个元素索引,-2表示倒数第二个元素的索引。
shell
## 先用定时器把数据刷新到list中
127.0.0.1:6379> lpush jhs p1 p2 p3 p4 p5 p6 p7 p8 p9 p10
(integer) 10
## 用lrange来实现分页
127.0.0.1:6379> lrange jhs 0 5
1) "p10"
2) "p9"
3) "p8"
4) "p7"
5) "p6"
6) "p5"
127.0.0.1:6379> lrange jhs 6 10
1) "p4"
2) "p3"
3) "p2"
4) "p1"

步骤1:采用定时器把特价商品都刷入redis缓存中

java
@Service
@Slf4j
public class TaskService {

    @Autowired
    private RedisTemplate redisTemplate;

    @PostConstruct
    public void initJHS(){
        log.info("启动定时器..........");
        new Thread(()->runJhs()).start();
    }

    /**
     * 模拟定时器,定时把数据库的特价商品,刷新到redis中
     */
    public void runJhs() {
        while (true){
            // 模拟从数据库读取100件特价商品,用于加载到聚划算的页面中
            List<Product> list = this.products();
            // 采用redis list数据结构的lpush来实现存储
            this.redisTemplate.delete(Constants.JHS_KEY);
            // lpush命令
            this.redisTemplate.opsForList().leftPushAll(Constants.JHS_KEY,list);
            try {
                //间隔一分钟 执行一遍
                Thread.sleep(1000*60);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("runJhs定时刷新..............");
        }
    }


    /**
     * 模拟从数据库读取100件特价商品,用于加载到聚划算的页面中
     */
    public List<Product> products() {
        List<Product> list=new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Random rand = new Random();
            int id= rand.nextInt(10000);
            Product obj=new Product((long) id,"product"+i,i,"detail");
            list.add(obj);
        }
        return list;
    }
}

步骤2:redis分页查询

分页查询:在高并发的情况下,只能走redis查询,走db的话必定会把db打垮

java
@GetMapping(value = "/find")
public List<Product> find(int page, int size) {
    List<Product> list=null;
    long start = (page - 1) * size;
    long end = start + size - 1;
    try {
        // 采用redis list数据结构的lrange命令实现分页查询
        list = this.redisTemplate.opsForList().range(Constants.JHS_KEY, start, end);
        if (CollectionUtils.isEmpty(list)) {
            //TODO 走DB查询
        }
        log.info("查询结果:{}", list);
    } catch (Exception ex) {
        //这里的异常,一般是redis瘫痪 ,或 redis网络timeout
        log.error("exception:", ex);
        //TODO 走DB查询
    }
    return list;
}

TIP

当查询QPS= 1000的时候,这时定时器更新redis,先删除再添加就会出现缓存击穿的问题。就必定导致大量的请求都打到数据库上面,从而把数据库打垮,这就出现了缓存击穿问题,那么该如何解决呢?