基于Redis的List实现特价商品列表功能


Posted in Redis onAugust 30, 2021

 1、场景分析

淘宝京东的特价商品列表,

商品特点:

  • 商品有限,并发量非常的大。
  • 考虑分页

传统解决方案:数据库db,

但是在如此大的并发量的情况下,不可取。

一般会采用redis来处理。这些特价商品的数据不多,而且redis的list本身也支持分页。是天然处理这种列表的最佳选择解决方案。

2、分析

采用list数据,因为list数据结构有:lrange key 0 -1 可以进行数据的分页。

127.0.0.1:6379> lpush products p1 p2 p3 p4 p5 p6 p7 p8 p9 p10
(integer) 10
127.0.0.1:6379> lrange products 0 1
1) "p10"
2) "p9"
127.0.0.1:6379> lrange products 2 3
1) "p8"
2) "p7"
127.0.0.1:6379> lrange products 4 5
1) "p6"
2) "p5"

3 、具体实现

淘宝,京东的热门商品在双11的时候,可能有100多w需要搞活动:程序需要5分钟对特价商品进行刷新。

3.1 ProductListService类

  •  初始化的活动的商品信息100个(从数据库去查询)

@PostContrcut使用

  •  查询产品列表信息

换算的分页的起始位置和结束位置

package com.example.service;

import com.example.entity.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @Auther: 长颈鹿
 * @Date: 2021/08/29/18:00
 * @Description:
 */
@Service
@Slf4j
public class ProductListService {

    @Autowired
    private RedisTemplate redisTemplate;

    // 数据热加载
    @PostConstruct
    public void initData(){
        log.info("启动定时加载特价商品到redis的list中...");
        new Thread(() -> runCourse()).start();
    }

    public void runCourse() {
        while (true) {
            // 从数据库中查询出特价商品
            List<Product> productList = this.findProductsDB();
            // 删除原来的特价商品
            this.redisTemplate.delete("product:hot:list");
            // 把特价商品添加到集合中
            this.redisTemplate.opsForList().leftPushAll("product:hot:list", productList);
            try {
                // 每隔一分钟执行一次
                Thread.sleep(1000 * 60);
                log.info("定时刷新特价商品....");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * 数据库中查询特价商品
     *
     * @return
     */
    public List<Product> findProductsDB() {
        //List<Product> productList = productMapper.selectListHot();
        List<Product> productList = new ArrayList<>();
        for (long i = 1; i <= 100; i++) {
            Product product = new Product();
            product.setId((long) new Random().nextInt(1000));
            product.setPrice((double) i);
            product.setTitle("特价商品" + (i));
            productList.add(product);
        }
        return productList;
    }

}

3.2 商品的数据接口的定义和展示及分页

package com.example.controller;

import com.example.entity.Product;
import com.example.service.ProductListService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Auther: 长颈鹿
 * @Date: 2021/08/29/18:04
 * @Description:
 */
@RestController
public class ProductListController {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private ProductListService productListService;

    @GetMapping("/findProducts")
    public List<Product> findProducts(int pageNo, int pageSize) {

        // 从那个集合去查询
        String key = "product:hot:list";
        // 分页的开始结束的换算
        if (pageNo <= 0) pageNo = 1;
        int start = (pageNo - 1) * pageSize;
        // 计算分页的结束页
        int end = start + pageSize - 1;

        // 根据redis的api去处理分页查询对应的结果
        try {
            List<Product> productList = this.redisTemplate.opsForList().range(key, start, end);
            if (CollectionUtils.isEmpty(productList)) {
                //todo: 查询数据库,存在缓存击穿的情况,大量的并发请求进来,可能把数据库冲
                productList = productListService.findProductsDB();
            }
            return productList;

        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

}

3.3 定时任务

@Configuration      // 主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 开启定时任务
public class SaticScheduleTask {
    // 添加定时任务
    @Scheduled(cron = "* 0/5 * * * ?")
    // 或直接指定时间间隔,例如:5秒
    // @Scheduled(fixedRate=5000)
    private void configureTasks() {
        System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
    }
}

4、解决商品列表存在的缓存击穿问题

 4.1 如何引起的缓存击穿的情况

public void runCourse() {
        while (true) {
            // 从数据库中查询出特价商品
            List<Product> productList = this.findProductsDB();
            // 删除原来的特价商品
            this.redisTemplate.delete("product:hot:list");
            // 把特价商品添加到集合中 需要时间
            this.redisTemplate.opsForList().leftPushAll("product:hot:list", productList);
            try {
                // 每隔一分钟执行一遍
                Thread.sleep(1000 * 60);
                log.info("定时刷新特价商品....");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

出现原因:

  • 特价商品的数据更换需要时间,刚好特价商品还没有放入到redis缓存中。
  • 查询特价商品的并发量非常大,可能程序还正在写入特价商品到缓存中,这时查询缓存根本没有数据,就会直接冲入数据库中去查询特价商品。可能造成数据库冲垮。这个就叫做:缓存击穿

4.2 解决方案

主从轮询

可以开辟两块redis的集合空间A和B。定时器在更新缓存的时候,先更新B缓存然后再更新A缓存

一定要按照特定顺序来处理。

package com.example.service;

import com.example.entity.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @Auther: 长颈鹿
 * @Date: 2021/08/29/18:00
 * @Description:
 */
@Service
@Slf4j
public class ProductListService {

    @Autowired
    private RedisTemplate redisTemplate;

    // 数据热加载
    @PostConstruct
    public void initData(){
        log.info("启动定时加载特价商品到redis的list中...");
        new Thread(() -> runCourse()).start();
    }

    public void runCourse() {
        while (true) {
            // 从数据库中查询出特价商品
            List<Product> productList = this.findProductsDB();

            // 删除原来的特价商品
            this.redisTemplate.delete("product:hot:slave:list");
            // 把特价商品添加到集合中
            this.redisTemplate.opsForList().leftPushAll("product:hot:slave:list", productList);// 删除原来的特价商品

            this.redisTemplate.delete("product:hot:master:list");
            // 把特价商品添加到集合中
            this.redisTemplate.opsForList().leftPushAll("product:hot:master:list", productList);

//            // 删除原来的特价商品
//            this.redisTemplate.delete("product:hot:list");
//            // 把特价商品添加到集合中
//            this.redisTemplate.opsForList().leftPushAll("product:hot:list", productList);
            try {
                // 每隔一分钟执行一次
                Thread.sleep(1000 * 60);
                log.info("定时刷新特价商品....");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * 数据库中查询特价商品
     *
     * @return
     */
    public List<Product> findProductsDB() {
        //List<Product> productList = productMapper.selectListHot();
        List<Product> productList = new ArrayList<>();
        for (long i = 1; i <= 100; i++) {
            Product product = new Product();
            product.setId((long) new Random().nextInt(1000));
            product.setPrice((double) i);
            product.setTitle("特价商品" + (i));
            productList.add(product);
        }
        return productList;
    }

}
package com.example.controller;

import com.example.entity.Product;
import com.example.service.ProductListService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Auther: 长颈鹿
 * @Date: 2021/08/29/18:04
 * @Description:
 */
@RestController
public class ProductListController {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private ProductListService productListService;

    @GetMapping("/findProducts")
    public List<Product> findProducts(int pageNo, int pageSize) {

        // 从那个集合去查询

        String master_key = "product:hot:master:list";
        String slave_key = "product:hot:slave:list";

        String key = "product:hot:list";
        // 分页的开始结束的换算
        if (pageNo <= 0) pageNo = 1;
        int start = (pageNo - 1) * pageSize;
        // 计算分页的结束页
        int end = start + pageSize - 1;

        // 根据redis的api去处理分页查询对应的结果
        try {

            List<Product> productList = this.redisTemplate.opsForList().range(master_key, start, end);

//            List<Product> productList = this.redisTemplate.opsForList().range(key, start, end);
            if (CollectionUtils.isEmpty(productList)) {
                // todo: 查询数据库,存在缓存击穿的情况,大量的并发请求进来,可能把数据库冲

                productList = this.redisTemplate.opsForList().range(slave_key, start, end);

//                productList = productListService.findProductsDB();
            }
            return productList;

        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

}

到此这篇关于基于Redis的List实现特价商品列表的文章就介绍到这了,更多相关redis list商品列表内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
redis限流的实际应用
Apr 24 Redis
使用Redis实现秒杀功能的简单方法
May 08 Redis
Redis Cluster 字段模糊匹配及删除
May 27 Redis
详解Redis基本命令与使用场景
Jun 01 Redis
Redis Cluster 集群搭建你会吗
Aug 04 Redis
Redis字典实现、Hash键冲突及渐进式rehash详解
Sep 04 Redis
详解Redis在SpringBoot工程中的综合应用
Oct 16 Redis
redis sentinel监控高可用集群实现的配置步骤
Apr 01 Redis
使用Redis实现分布式锁的方法
Jun 16 Redis
基于Redission的分布式锁实战
Aug 14 Redis
Redis 常见使用场景
Aug 30 #Redis
Redis入门教程详解
Redis如何实现分布式锁
Aug 23 #Redis
Redisson实现Redis分布式锁的几种方式
Redis分布式锁Redlock的实现
Aug 07 #Redis
关于redisson缓存序列化几枚大坑说明
Aug 04 #Redis
Redis Cluster 集群搭建你会吗
Aug 04 #Redis
You might like
php全排列递归算法代码
2012/10/09 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(十四)
2014/06/26 PHP
解读PHP中上传文件的处理问题
2016/05/29 PHP
浅析PHP中json_encode与json_decode的区别
2020/07/15 PHP
Js+XML 操作
2006/09/20 Javascript
网页设计常用的一些技巧
2006/12/22 Javascript
在html页面上拖放移动标签
2010/01/08 Javascript
javascript学习笔记(四) Number 数字类型
2012/06/19 Javascript
JS刷新框架外页面七种实现代码
2013/02/18 Javascript
JS实现字体选色板实例代码
2013/11/20 Javascript
Jquery操作js数组及对象示例代码
2014/05/11 Javascript
jQuery前端开发35个小技巧
2016/05/24 Javascript
js中window.open的参数及注意注意事项
2016/07/06 Javascript
jQuery基于BootStrap样式实现无限极地区联动
2016/08/26 Javascript
Bootstrap字体图标无法正常显示的解决方法
2016/10/08 Javascript
vue中component组件的props使用详解
2017/09/04 Javascript
深入浅出webpack教程系列_安装与基本打包用法和命令参数详解
2017/09/10 Javascript
vue项目开发中setTimeout等定时器的管理问题
2018/09/13 Javascript
angular 表单验证器验证的同时限制输入的实现
2019/04/11 Javascript
JavaScript动态添加数据到表单并提交的几种方式
2019/06/26 Javascript
javascript中可能用得到的全部的排序算法
2020/03/05 Javascript
Python标准库之collections包的使用教程
2017/04/27 Python
Flask实现跨域请求的处理方法
2018/09/27 Python
实例讲解Python脚本成为Windows中运行的exe文件
2019/01/24 Python
pytorch 常用线性函数详解
2020/01/15 Python
Python底层封装实现方法详解
2020/01/22 Python
QML用PathView实现轮播图
2020/06/03 Python
matplotlib绘制鼠标的十字光标的实现(内置方式)
2021/01/06 Python
印度在线杂货店:bigbasket
2018/08/23 全球购物
机电一体化毕业生求职信
2013/11/02 职场文书
校园创业策划书
2014/01/14 职场文书
简单的大学生自我鉴定
2014/02/18 职场文书
2014年基层党组织公开承诺书
2014/03/29 职场文书
详解Js模块化的作用原理和方案
2021/04/29 Javascript
Python多个MP4合成视频的实现方法
2021/07/16 Python
CSS+HTML 实现顶部导航栏功能
2021/08/30 HTML / CSS