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

目录
  •  1、场景分析
  • 2、分析
  • 3 、具体实现
    • 3.1 productlistservice类
    • 3.2 商品的数据接口的定义和展示及分页
    • 3.3 定时任务
  • 4、解决商品列表存在的缓存击穿问题
    •  4.1 如何引起的缓存击穿的情况
    • 4.2 解决方案

 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商品列表内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

(0)
上一篇 2022年3月21日
下一篇 2022年3月21日

相关推荐