商品详情: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);
}
}