第七章 缓存库存

问题:下单操作需要多次调用数据库,查询商品信息,用户信息,修改库存数据,造成性能瓶颈。

优化方向:读取数据改为从缓存读取,修改库存数据改为修改缓存数据在用消息队列异步修改数据库。可以用rocketmq的异步事务型消息来保证redis和数据库数据同步,在缓存异常情况可以用数据库数据来恢复。

1.交易验证优化

(1)校验商品是否存在,改为缓存

之前是直接从数据库根据itemId获取商品数据

现在改为从redis缓存中获取,没有在从数据库获取

     @Override
	public ItemModel getItemByIdInCache(Integer id) {
		ItemModel itemModel = (ItemModel)redisTemplate.opsForValue().get("item_validate_"+id);
		if(itemModel == null) {
			itemModel = this.getItemById(id);
			redisTemplate.opsForValue().set("item_validate_"+id, itemModel);
		}
		return itemModel;
	}

(2)校验用户信息是否存在,改为缓存

之前是直接从数据库根据userId获取用户数据

现在改为从redis缓存中获取,没有在从数据库获取

    @Override
	public UserModel getUserByIdInCache(Integer id) {
		UserModel userModel = (UserModel)redisTemplate.opsForValue().get("user_"+id);
		if(userModel == null) {
			userModel = this.getUserById(id);
			redisTemplate.opsForValue().getAndSet("user_"+id, userModel);
		}
		return userModel;
	}

2. 修改库存数据改为修改缓存

库存修改表数据因为itemId为库存表主键,所以会有行锁,防止并发,但会影响效率

所以在抢购活动发布时,将库存存入redis中,下单时先修改redis缓存数据,再用rocketmq同步修改库存来保证可靠性

a.发布活动,缓存库存

    //发布活动,这个应该是运维操作   @RequestMapping(value = "/publishpromo",method = {RequestMethod.GET})
    @ResponseBody
    public CommonReturnType publisPromo(Integer id) {
    	promoService.publishPromo(id);
    	return CommonReturnType.create(null);
    } 

  @Override
  public void publishPromo(Integer promoId) {
    // 通过活动ID获取活动信息
    PromoDO promoDO = promoDOMapper.selectByPrimaryKey(promoId);
    if(promoDO == null || promoDO.getItemId().intValue() == 0) {
      return;
    }

    ItemModel itemModel = itemService.getItemById(promoDO.getItemId());
    //将库存同步到redis中
    redisTemplate.opsForValue().getAndSet("promo_item_stock_"+promoDO.getItemId(), itemModel.getStock());
  }

b.下单减库存

@Override
    @Transactional
    public boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException {
        /*int affectedRow =  itemStockDOMapper.decreaseStock(itemId,amount);
        if(affectedRow > 0){
            //更新库存成功
            return true;
        }else{
            //更新库存失败
            return false;
        }*/
    	Long result = redisTemplate.opsForValue().increment("promo_item_stock_"+itemId, amount.intValue()*-1);
    	// 发送消息队列修改数据库库存     boolean sendResult = producer.asyncReduceStock(itemId, amount);
    	// 还剩的数据
    	if(result > 0 && sendResult){
            //更新库存成功
            return true;
        }else {
            //更新库存失败
        	redisTemplate.opsForValue().increment("promo_item_stock_"+itemId, amount.intValue());
            return false;
        }
    }

消息队列生产者发送

@Component
public class MQProducer {
	Log log = LogFactory.getLog(getClass());
	@Value("${mq.nameserver.addr}")
	private String nameServer;

	@Value("${mq.topicname}")
	private String topicName;

	DefaultMQProducer producer;
	@PostConstruct
	public void init() throws MQClientException {
		producer = new DefaultMQProducer("producer");
		producer.setNamesrvAddr(nameServer);
		producer.start();
	}

	// 同步扣减库存消息
	public boolean asyncReduceStock(Integer itemId, Integer amount) {
		Map<String,Object> bodyMap = new HashMap<String,Object>();
		bodyMap.put("itemId", itemId);
		bodyMap.put("amount", amount);
		Message msg = new Message(topicName,"increase",
				JSON.toJSON(bodyMap).toString().getBytes(Charset.forName("UTF-8")));
		try {
			log.error("begin to send msg");
			producer.send(msg);
			log.error("finish send msg");
		} catch (MQClientException e) {
			e.printStackTrace();
			return false;
		} catch (RemotingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		} catch (MQBrokerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
		return true;
	}
}

 消息队列消费者减库存数据库 

@Component
public class MQConsumer {
	@Value("${mq.nameserver.addr}")
	private String nameServer;

	@Value("${mq.topicname}")
	private String topicName;

	DefaultMQPushConsumer consumer;
	@Autowired
    private ItemStockDOMapper itemStockDOMapper;

	private Log log = LogFactory.getLog(getClass());

	@PostConstruct
	public void init() throws MQClientException {
		consumer = new DefaultMQPushConsumer("stock_consumer_group");
		consumer.setNamesrvAddr(nameServer);
		consumer.subscribe(topicName, "*");
		consumer.registerMessageListener(new MessageListenerConcurrently() {

			@Override
			public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
				log.error("begin to cosume msg");
				Message msg = msgs.get(0);
				String jsonStr = new String(msg.getBody());
				Map<String,Object> map = JSON.parseObject(jsonStr, Map.class);
				Integer itemId= (Integer)map.get("itemId");
				Integer amount= (Integer)map.get("amount");
				log.error("itemId:"+itemId+",amount:"+amount);
				int affectedRow =  itemStockDOMapper.decreaseStock(itemId,amount);
				if(affectedRow > 0){
		                  //更新库存成功
					return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
		             }else{
		                //更新库存失败
		        	  return ConsumeConcurrentlyStatus.RECONSUME_LATER;
		             }
			}
             });
		consumer.start();
	}
}

  

原文地址:https://www.cnblogs.com/t96fxi/p/12082884.html

时间: 2024-10-04 17:00:49

第七章 缓存库存的相关文章

操作系统思考 第七章 缓存

第七章 缓存 作者:Allen B. Downey 原文:Chapter 7 Caching 译者:飞龙 协议:CC BY-NC-SA 4.0 7.1 程序如何运行 为了理解缓存,你需要理解计算机如何运行程序.你应该学习计算机体系结构来深入理解这个话题.这一章中我的目标是给出一个程序执行的简单模型. 当程序启动时,代码(或者程序文本)通常位于硬盘上.操作系统创建新的进程来运行程序,之后"加载器"将代码从存储器复制到主存中,并且通过调用main来启动程序. 在程序运行之中,它的大部分数据

oracle基本语句(第七章、数据库逻辑对象管理)

索引.实体化视图.簇.散列簇.序列.同义词 1.创建表 CREATE TABLE <表名>(<列名1> <数据类型>,--); CREATE GLOBAL TEMPORARY TABLE <表名>(<列名1> <数据类型>,--) ON COMMIT DELETE ROWS TABLESPACE <临时表空间名>;--创建事务级临时表,事务提交后删除临时表中数据 CREATE GLOBAL TEMPORARY TABLE

《Programming in Go》第七章并发编程译文

中文译名 Go 编程 外文原文名 Programming in Go 外文原文版出处 the United States on recycled paper at RR Donnelley in Crawfordsville, Indiana. 译 文: 第七章 并发编程 7.1主要概念 7.2例子 7.2.1:过滤器 7.2.2:并发查找 7.2.3:线程安全表 7.2.4:Apache 报告 7.2.5:找重复 并发编程能够让开发者实现并行算法,以及利用多处理器和多核写程序.但是在大多主流变

第七章——DMVs和DMFs(1)

原文:第七章--DMVs和DMFs(1) 简介: 从SQLServer2005开始,微软引入了一个名叫DMO(动态管理对象)的新特性,DMO可以分为DMFs(Dynamic Manage Functions,动态管理函数)和DMVs(Dynamic Manage Views,动态管理视图)两部分.这些函数和视图用于查找SQLServer实例内部统计信息以供性能监控所用.它们提供实时的,关于SQLServer内部工作的,能用于性能分析和性能故障排除的各种统计信息. 所有的DMO都属于sys架构,并

第七章——DMVs和DMFs(4)——用DMV和DMF监控磁盘IO

原文:第七章--DMVs和DMFs(4)--用DMV和DMF监控磁盘IO 前言: 本文为本系列最后一篇,作为DBA,你必须经常关注磁盘的I/O问题,一旦出现问题,要尽快分析出是什么问题.SQLServer同样提供了一些列与I/O相关的DMO来做监控. 本文介绍如何使用DMO来监控I/O子系统的性能并找到I/O瓶颈.通过本文,可以区分不同数据库的I/O使用模式.一旦发现有数据库的I/O很高,可能需要考虑把数据库迁移到单独的磁盘,或者深入研究I/O产生的问题. 准备工作: 本文将演示如何监控数据库文

第七章、中间件

目录 第七章.中间件 一.什么是中间件 二.中间件可以做什么 三.再回顾一下django请求生命周期 四.五个可以自定义的方法 第七章.中间件 一.什么是中间件 django 中间件 就类似于是 django 的门户,请求来的时候需要先经过 中间件 才能到达 django 后端(urls),响应走的时候也需要经过 中间件 才能到达 web服务网关接口(wsgif 模块) 二.中间件可以做什么 用户访问频率限制 用户是否是黑名单 白名单 所有用户登录校验 只要是涉及到网址全局的功能 你就应该考虑使

第七章、中间件续写

目录 第七章.中间件续写 一.中间件的执行顺序 测试思路: 自定义中间件 代码 二.跨站请求伪造 三.csrf装饰器 四.post请求提交数据通过 csrf 校验 五.自我拷问 分FBV和CBV 未注释掉 csrf 中间件时 单功能取消 csrf 校验:csrf_exempt 注释掉 csrf 中间件时 单功能开启 csrf 校验:csrf_protect 第七章.中间件续写 一.中间件的执行顺序 测试思路: 在 settings.py 里注册不同中间件,探究默认的执行顺序 在不同中间件的 pr

CSAPP第七章概念

CSAPP第七章概念 1.虚拟地址数=2^(虚拟地址位数) 最大可能的虚拟空间=虚拟地址数-1 2. 使用虚拟寻址,CPU需要将虚拟地址转换成物理地址,这样才能访问真实的物理内存 concepts:1)VM on disk,PM (DRAM cache) 2)page--cache blocks 3.page tables :an array of PTEs(page table entry) that map VP to PP 虚拟内存空间被组织为一个存放在硬盘上的M个连续的字节大小的单元组成

第七章

第七章 控制发光二极管. 尽管linux 驱动直接和硬件打交道,但并不是linux驱动直接向硬件中的内存写数据,而是与本机的i/o内存进行交互.所谓I/O内存是通过各种接口(PCI, USB.蓝牙以太网等)连接到主机的硬件在主机的内存映射.Linux内核提供了多个与I/O内存交互的函数.Linux内核的内存管理模块负责同步I/O内存与硬件的数据. 每一个连接Linux 的硬件在I/O内存中都会有映射首地址.在使用ioread 32.ioread32等函数读写I/O内存时需要指定这些首地址.Led