高并发环境下查看产品详情功能的需求分析及部分代码演示(redis缓存+mq异步实现数据同步)

本文介绍了一种针对商品详情的高效缓存策略,利用Redis存储高频访问且数据稳定的商品信息,通过分片处理大量数据,确保快速响应及数据一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

商品详情:1.重复访问过高 2.页面内容多 考虑使用缓存

缓存:可以为高频操作的数据,进行缓存处理

推荐的技术:Redis
 

缓存数据的要求:
1.高频访问
2.数据不频繁改变
3.数据不敏感 对安全要求不高


商品详情缓存策略:
1.查询商品详情-先检索Redis
2.Redis存在 直接返回
3.Redis不存在--查询数据库----存入Redis一份(缓存)---结果返回
4.再有请求--->Redis--->返回
Redis如何存储
1.抉择数据类型 5种
不需要有效期 永久有效 下架的时候,记得删除缓存
Hash:类型 Key: micro:product:detail
键值对:键:商品的id 值:对应商品详情信息:商品信息、图册信息、详情信息、属性信息等(字符串
(json)、序列化(类的结构不要改变))
ps:如果数据量过大,可以考虑用多个Hash来存储,数据分片:
比如可以设置一个Hash的存储上限(比如1000)。现在有88000条商品,可以对商品id进行取余对1000,根据余
数选择对应的Hash,Hash的Key的格式:micro:product:detail:余数
如果id不连续,可以考虑在Redis新增一个String类型:key:product:detail:count 值:缓存的商品详情的数量
incr
分片存储的逻辑:
1.缓存数据-先获取product:detail:count 存在:自增,不存在:新增 1
2.获取product:detail:count 的值
3.根据上面的值对1000取余
4.根据余数选择对应的Hash,Hash的Key的格式:micro:product:detail:余数
如果Mysql发送数据变化,那么就会带来Redis和Mysql的数据不一致
1.简单 通用
后台系统更改商品信息的时候,修改成功之后,校验该商品是否有缓存,有的话直接修改
2.定时任务
指定间隔时间,cron表达式 间隔一定的时间,重复执行
定时任务触发的时候,查询商品修改流水表 根据时间进行查询:betweeb 当前时间-间隔时间 and 当前时间
查询间隔时间内修改的商品id(注意去重)
查询修改商品的详情信息,去Redis校验该商品是否做缓存,如果缓存,就需要更新缓存数据
缺点:
1.间隔时间 数据可能不一致
2.临界时间点
3.定时任务挂掉
MQ异步实现数据的同步
后台系统更改商品信息的时候,修改成功之后,发送MQ消息(商品信息)
发送指定的队列,消息监听器-监听消息变化,验证Redis中商品是否做缓存,有缓存更新
 

代码演示:
商品服务:

package com.lxm.productservice.service.impl;

import com.lxm.common.config.RedisKeyConfig;
import com.lxm.common.dto.PmsProductDetailDto;
import com.lxm.common.dto.PmsProductDto;
import com.lxm.common.utils.RUtil;
import com.lxm.common.utils.RedissionCore;
import com.lxm.common.vo.R;
import com.lxm.productservice.dao.PmsProductDao;
import com.lxm.productservice.service.PmsProductService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

/**
 * @Author LXM
 * @Date 2020/7/15 0015
 */
@Service
public class PmsProductServiceImpl implements PmsProductService {
    @Resource
    private RedissionCore redissionCore;
    @Resource
    private PmsProductDao dao;

    /**
     * 按条件查询商品列表
     * @param parms
     * @return
     */
    @Override
    public R searchList(Map<String, Object> parms) {
        List<PmsProductDto> pmslist=dao.querylist(parms);
        return RUtil.ok(pmslist);
    }

    /**
     * 查询商品详情
     * @param id
     * @return
     */
    @Override
    public R searchDetail(int id) {
        //先检查redis中是否有商品详情的缓存
        if(redissionCore.checkMap(RedisKeyConfig.PRODUCT_DETAIL,id+"")){
            //redis中有缓存,取出缓存,返回结果
            return RUtil.ok(redissionCore.getMap(RedisKeyConfig.PRODUCT_DETAIL,id+""));

        }else {
            //redis中没有缓存,向数据库中查询
            PmsProductDetailDto detailDto=dao.querydetail(id);
            //判断返回结果是否为空
            if(detailDto!=null){
                //如果商品数据量过大,可以考虑对商品进行分片存储
                //将查询的结果存入redis缓存中
                redissionCore.addMap(RedisKeyConfig.PRODUCT_DETAIL,id+"",detailDto);
                //返回查询结果
                return RUtil.ok(detailDto);
            }else{
                //在数据库没有查询到商品,可能商品下架或者其他情况
                return RUtil.error("商品走丢了");
            }

        }
    }
}
package com.lxm.productservice.dao;

import com.lxm.common.dto.PmsProductDetailDto;
import com.lxm.common.dto.PmsProductDto;

import java.util.List;
import java.util.Map;

public interface PmsProductDao {
    /**
     * 查询商品详情
     * @param id
     * @return
     */
    PmsProductDetailDto querydetail(int id);

    /**
     * 按条件查询商品
     * @param parms
     * @return
     */
    List<PmsProductDto> querylist(Map<String, Object> parms);
}


同步代码:



/**
 * @Author LXM
 * @Date 2020/7/16 0016
 */
@Configuration
public class RabbitMqConfig {
    @Bean
    public Queue createProductDetail(){
        return new Queue("micro:product:detail");
    }
   
}
package com.lxm.productservice.listener;

import com.lxm.common.config.RedisKeyConfig;
import com.lxm.common.dto.MqMessageDto;
import com.lxm.common.dto.PmsProductDetailDto;
import com.lxm.common.utils.RedissionCore;
import com.lxm.productservice.dao.PmsProductDao;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @Author LXM
 * 当后台修改商品信息的时候,修改成功之后,发送mq消息
 * 消息监听器监听队列,判断redis中是否有缓存,如果有缓存,更新数据
 * @Date 2020/7/16 0016
 */
@Component
@RabbitListener(queues = "micro:product:detail")
public class ProductDetailListener {
    @Resource
    private RedissionCore redissionCore;
    @Resource
    private PmsProductDao dao;

    /**
     * 当后台修改商品信息的时候,修改成功之后,发送mq消息
     * 消息监听器监听队列,判断redis中是否有缓存,如果有缓存,更新数据
     * @param obj
     */
    @RabbitHandler
    public void handler(Object obj){
        //校验消息
        if(obj instanceof MqMessageDto){
            MqMessageDto dto=(MqMessageDto) obj;
            //获取队列中的信息--商品id
            int id=(Integer) dto.getMsg();
            //校验redis中是否有缓存
            if (redissionCore.checkKey(RedisKeyConfig.PRODUCT_DETAIL)){
                if (redissionCore.checkMap(RedisKeyConfig.PRODUCT_DETAIL,id+"")){
                    //redis中有缓存,对redis中的缓存进行更新
                    //查询数据库中的数据,将数据添加到redis缓存中
                    PmsProductDetailDto detailDto=dao.querydetail(id);
                    redissionCore.addMap(RedisKeyConfig.PRODUCT_DETAIL,id+"",detailDto);
                }
            }
        }
    }

}

封装的工具类

package com.lxm.common.utils;

import com.lxm.common.config.RedisKeyConfig;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author LXM
 * @Date 2020/7/11 0011
 */
public class RedissionCore {
    private RedissonClient client;
    public RedissionCore(String host,int port,String pwd){
        Config config=new Config();
        config.useSingleServer().setAddress("redis://"+host+":"+port).setPassword(pwd);
        client= Redisson.create(config);
    }

    /**
     * 添加字符串
     * @param key
     * @param val
     */
    public void addStr(String key,String val){
        client.getBucket(key).set(val);
    }

    /**
     * 添加字符串并设置过期时间
     * @param key
     * @param val
     * @param seconds
     */
    public void addStr(String key,String val,long seconds){
        client.getBucket(key).set(val,seconds, TimeUnit.SECONDS);
    }

    /**
     * 向map集合中添加数据
     * @param key
     * @param f
     * @param v
     */
    public void addMap(String key,String f,String v){
        client.getMap(key).put(f,v);
    }
    public void addMap(String key,String f,Object v){
        client.getMap(key).put(f,v);
    }
    /**
     * 向map集合中一次添加整个集合
     * @param key
     * @param map
     */
    public void addMap(String key, Map<String,String> map){
        client.getMap(key).putAll(map);
    }

    /**
     * 删除map集合中的某个键值对
     * @param key
     * @param f
     */
    public void delMap(String key,String f){
        client.getMap(key).remove(f);
    }

    /**
     * 删除某个key
     * @param key
     */
    public void delKey(String key){
        client.getKeys().delete(key);
    }

    /**
     * 删除多个key
     * @param keys
     */
    public void delKeys(String... keys){
        client.getKeys().delete(keys);
    }

    /**
     * 从字符串中获取值
     * @param key
     * @return
     */
    public String getStr(String key){
        return client.getBucket(key).get().toString();
    }

    /**
     * 从map中获取值
     * @param key
     * @param f
     * @return
     */
    public String getMap(String key,String f){
        return client.getMap(key).get(f).toString();
    }
    public Map<Object, Object> getMap(String key){
        return client.getMap(key);
    }

    /**
     * 检查map集合中是否存在key
     * @param key
     * @param f
     * @return
     */
    public boolean checkMap(String key,String f){
        return client.getMap(key).containsKey(f);
    }

    /**
     * 检查该key是否存在
     * @param key
     * @return
     */
    public boolean checkKey(String key){
        return client.getKeys().countExists(key)>0;
    }

    /**
     * 检查set集合中该key是否存在
     * @param key
     * @param val
     * @return
     */
    public boolean checkSet(String key,String val){
        return client.getSet(key).contains(val);
    }

    /**
     * 给某个key设置有效期
     * @param key
     * @param seconds
     */
    public void setTime(String key,long seconds){
        client.getKeys().expire(key,seconds,TimeUnit.SECONDS);
    }

    /**
     * 给某个key上锁
     * @param key
     * @return
     */
    public RLock lock(String key){
        RLock rl=client.getLock(key);
        rl.lock(RedisKeyConfig.LOCK_Time,TimeUnit.SECONDS);
        return rl;
    }

    /**
     * 给某个key手动解锁
     * @param key
     */
    public void unlock(String key){
        client.getLock(key).unlock();
    }
}
package com.lxm.common.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author LXM
 * @Date 2020/7/9 0009
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class R {
    private Integer code;
    private String msg;
    private Object data;
}
package com.lxm.common.utils;


import com.lxm.common.config.ResultCodeConfig;
import com.lxm.common.vo.R;

/**
 * @program: HungryApi
 * @description: 封装对统一结果的操作
 * @author: Feri(邢朋辉)
 * @create: 2020-06-18 10:28
 */
public class RUtil {
    public static R ok(String msg, Object data){
        return new R(ResultCodeConfig.R_SUCCESS,msg,data);
    }

    public static R ok(Object data){
        return new R(ResultCodeConfig.R_SUCCESS,"OK",data);
    }

    public static R ok(){
        return new R(ResultCodeConfig.R_SUCCESS,"OK",null);
    }

    public static R error(String msg){
        return new R(ResultCodeConfig.R_ERROR,msg,null);
    }
    public static R error(){
        return new R(ResultCodeConfig.R_ERROR,"失败",null);
    }
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值