基于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 sentinel 频繁主备切换的问题
Apr 12 Redis
redis限流的实际应用
Apr 24 Redis
Redis可视化客户端小结
Jun 10 Redis
Redis基于Bitmap实现用户签到功能
Jun 20 Redis
嵌入式Redis服务器在Spring Boot测试中的使用教程
Jul 21 Redis
为什么RedisCluster设计成16384个槽
Sep 25 Redis
浅谈Redis的keys命令到底有多慢
Oct 05 Redis
Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题
Feb 12 Redis
分布式架构Redis中有哪些数据结构及底层实现原理
Mar 13 Redis
一文搞懂Redis中String数据类型
Apr 03 Redis
Redis全局ID生成器的实现
Jun 05 Redis
一文教你快速生成MySQL数据库关系图
Jun 28 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中的串行化变量和序列化对象
2006/09/05 PHP
PHP mcrypt可逆加密算法分析
2011/07/19 PHP
PHP利用header跳转失效的解决方法
2014/10/24 PHP
yii分页组件用法实例分析
2015/12/28 PHP
jQuery 淡入淡出 png图在ie8下有黑色边框的解决方法
2013/03/05 Javascript
JsRender for object语法简介
2014/10/31 Javascript
浅谈jQuery中对象遍历.eq().first().last().slice()方法
2014/11/26 Javascript
举例详解Python中smtplib模块处理电子邮件的使用
2015/06/24 Javascript
js焦点文字滚动效果代码分享
2015/08/25 Javascript
Bootstrap每天必学之前端开发框架
2015/11/19 Javascript
很不错的两款Bootstrap Icon图标选择组件
2016/01/28 Javascript
jQuery插件FusionCharts实现的2D饼状图效果【附demo源码下载】
2017/03/03 Javascript
Vue 实用分页paging实例代码
2017/04/12 Javascript
详解AngularJS 路由 resolve用法
2017/04/24 Javascript
使用mint-ui开发项目的一些心得(分享)
2017/09/07 Javascript
ES6扩展运算符用法实例分析
2017/10/31 Javascript
解决在Vue中使用axios用form表单出现的问题
2019/10/30 Javascript
浅谈vue-router路由切换 组件重用挖下的坑
2019/11/01 Javascript
vue开发简单上传图片功能
2020/06/30 Javascript
Python语言实现百度语音识别API的使用实例
2017/12/13 Python
利用python将xml文件解析成html文件的实现方法
2017/12/22 Python
python爬取Ajax动态加载网页过程解析
2019/09/05 Python
tensorflow实现tensor中满足某一条件的数值取出组成新的tensor
2020/01/04 Python
python 使用递归回溯完美解决八皇后的问题
2020/02/26 Python
Python 跨.py文件调用自定义函数说明
2020/06/01 Python
Python数据库封装实现代码示例解析
2020/09/05 Python
基于python模拟bfs和dfs代码实例
2020/11/19 Python
HTML5 Canvas标签使用收录
2009/07/07 HTML / CSS
高清屏中使用Canvas绘图出现模糊的问题及解决方法
2019/06/03 HTML / CSS
经济实惠的豪华家具:My-Furniture
2019/03/12 全球购物
函授本科自我鉴定
2013/11/03 职场文书
女大学生自我鉴定
2013/12/09 职场文书
2014年结对帮扶工作总结
2014/12/17 职场文书
大学生村官工作心得体会
2016/01/23 职场文书
python实现的人脸识别打卡系统
2021/05/08 Python
MySQL 服务和数据库管理
2021/11/11 MySQL