基于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哨兵常用命令和监控示例详解
May 27 Redis
浅谈Redis主从复制以及主从复制原理
May 29 Redis
深入理解redis中multi与pipeline
Jun 02 Redis
redis requires ruby version2.2.2的解决方案
Jul 15 Redis
Redis源码阅读:Redis字符串SDS详解
Jul 15 Redis
Redis Cluster 集群搭建你会吗
Aug 04 Redis
在项目中使用redis做缓存的一些思路
Sep 14 Redis
浅谈Redis跟MySQL的双写问题解决方案
Feb 24 Redis
面试分析分布式架构Redis热点key大Value解决方案
Mar 13 Redis
高并发下Redis如何保持数据一致性(避免读后写)
Mar 18 Redis
浅谈Redis变慢的原因及排查方法
Jun 21 Redis
Redis sentinel哨兵集群的实现步骤
Jul 15 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
德生PL450的电路分析和低放电路的改进办法
2021/03/02 无线电
你可能不知道PHP get_meta_tags()函数
2014/05/12 PHP
php+ajax 文件上传代码实例
2019/03/18 PHP
jquery 日期分离成年月日的代码
2010/05/14 Javascript
深入理解JavaScript系列(8) S.O.L.I.D五大原则之里氏替换原则LSP
2012/01/15 Javascript
javascript针对DOM的应用分析(二)
2012/04/15 Javascript
jQuery去掉字符串起始和结尾的空格(多种方法实现)
2013/04/01 Javascript
jQuery中:eq()选择器用法实例
2014/12/29 Javascript
JavaScript中的Primitive对象封装介绍
2014/12/31 Javascript
jQuery事件绑定on()与弹窗实现代码
2016/04/28 Javascript
有关JavaScript中call()和apply() 的一些理解
2016/05/20 Javascript
JS触发服务器控件的单击事件(详解)
2016/08/06 Javascript
vue中渐进过渡效果实现
2016/10/27 Javascript
原生js二级联动效果
2017/06/20 Javascript
nodejs开发微信小程序实现密码加密
2017/07/11 NodeJs
js遍历添加栏目类添加css 再点击其它删除css【推荐】
2018/06/12 Javascript
微信小程序支付功能 php后台对接完整代码分享
2018/06/12 Javascript
vue两个组件间值的传递或修改方式
2018/07/04 Javascript
JS回调函数简单易懂的入门实例分析
2019/09/29 Javascript
vue中在vuex的actions中请求数据实例
2019/11/08 Javascript
Vue-cli3多页面配置详解
2020/03/22 Javascript
Node.js设置定时任务之node-schedule模块的使用详解
2020/04/28 Javascript
ant-design-vue中tree增删改的操作方法
2020/11/03 Javascript
js中实现继承的五种方法
2021/01/25 Javascript
使用SAE部署Python运行环境的教程
2015/05/05 Python
python获取指定路径下所有指定后缀文件的方法
2015/05/26 Python
编写类String的构造函数、析构函数和赋值函数
2012/05/29 面试题
求两个数的乘积和商数,该作用由宏定义来实现
2013/03/13 面试题
使用Vue.js和MJML创建响应式电子邮件
2021/03/23 Vue.js
师范大学生求职信
2014/06/13 职场文书
缅怀先烈演讲稿
2014/09/03 职场文书
2014副局长群众路线对照检查材料思想汇报
2014/09/22 职场文书
2015年新农村建设工作总结
2015/05/22 职场文书
干部理论学习心得体会
2016/01/21 职场文书
2019年行政人事个人工作总结范本!
2019/07/19 职场文书
德劲DE1107指针试高灵敏度全波段收音机机评
2022/04/05 无线电