RockletMQ高并发项目——下订单与库存的乐观锁机制、分库分表介绍

关于乐观锁的概念介绍摘自https://www.cnblogs.com/sheseido/p/5038562.html,特此声明 !   

  

一.下订单与库存的乐观锁机制

       乐观锁,大多是基于数据版本   Version 记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来 实现。 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提 交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。 

假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。

  1.  操作员 A 此时将其读出(version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
  2.  在操作员 A 操作的过程中,操作员 B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
  3. 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大 于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
  4. 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数 据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须大于记 录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。这样,就避免了操作员 B 用基于version=1 的旧数据修改的结果覆盖操作 员 A 的操作结果的可能。 

       从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员 A和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。 需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途 径,而不是将数据库表直接对外公开)。

   

       下面我们来看看 代码,这里其实运用了前面所讲的接口抽出和服务之间的调用,但由于篇幅,我只将业务层的业务逻辑代码和其中关键sql贴出来,理解乐观锁也只需要这些代码,完整代码大家可以去我github上看:github。具体业务逻辑如下:

  1. 将传过来的订单信息放入order对象中
  2. 查询出当前操作对象的版本号
  3. 更新库存——如果步骤2查询的版本号与此时的数据库版本号一致,且库存大于0,则我们更新库存(数量减1,修改更新时间等),返回1。否则不更新库存返回0
  4. 如果更新库存结果为1(成功),则将第一步的order对象存入数据库(入库);为0则可能是库存为0或乐观锁机制生效(及版本号错误),我们通过查询库存来判断是那个原因,然后返回相应的值给用户。
package com.yy.order.service.impl;

import com.alibaba.dubbo.config.annotation.Reference;

import com.yy.order.constants.OrderStatus;
import com.yy.order.entity.Order;
import com.yy.order.mapper.OrderMapper;
import com.yy.order.service.OrderService;
import com.yy.store.service.api.StoreServiceApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class OrderServiceImpl implements OrderService{

    @Autowired
    private OrderMapper orderMapper;
	
    @Reference(version = "1.0.0",
               application = "${dubbo.application.id}",
               interfaceName = "com.bfxy.store.service.StoreServiceApi",
               check = false,
               timeout = 3000,
               retries = 0
    )
    private StoreServiceApi storeServiceApi;

	
    @Override
    public boolean createOrder(String cityId, String platformId, String userId, String supplierId, String goodsId) {
	    boolean flag = true;
	    try {
		    Order order = new Order();
		    order.setOrderId(UUID.randomUUID().toString().substring(0, 32)); //订单号
		    order.setOrderType("1");   //订单的类型
		    order.setCityId(cityId);
		    order.setPlatformId(platformId);
		    order.setUserId(userId);
		    order.setSupplierId(supplierId);
		    order.setGoodsId(goodsId);
		    order.setOrderStatus(OrderStatus.ORDER_CREATED.getValue());   //订单的状态
		    order.setRemark("");		
		    Date currentTime = new Date();
		    order.setCreateBy("admin");    //创建人
		    order.setCreateTime(currentTime);  //时间
		    order.setUpdateBy("admin");
		    order.setUpdateTime(currentTime);

		    //乐观锁更新
		    // 查询当前的版本号
		    int currentVersion = storeServiceApi.selectVersion(supplierId, goodsId);
		    // 更新库存
		    int updateRetCount = storeServiceApi.updateStoreCountByVersion(currentVersion, supplierId, goodsId, "admin", currentTime);
			
		    if(updateRetCount == 1) {
		    	// DOTO:	如果出现SQL异常入库失败, 那么要对"库存的数量"和"版本号"进行回滚操作(即catch住回滚)
		    	orderMapper.insertSelective(order);  //入库
		    }
		    else if (updateRetCount == 0){    //	没有更新成功 1 高并发时乐观锁生效   2 库存不足
		    	flag = false;	//	下单标识失败
		    	int currentStoreCount = storeServiceApi.selectStoreCount(supplierId, goodsId); //查询库存
		    	if(currentStoreCount == 0) {
		    		//{flag:false , messageCode: 003 , message: 当前库存不足}  -----返回给客户(实际操作)
		    		System.err.println("-----当前库存不足...");
		    	} else {
		    		//{flag:false , messageCode: 004 , message: 乐观锁生效}  -----再来一次(实际操作)
		    		System.err.println("-----乐观锁生效...");
		    	}
		    }
	    } catch (Exception e) {
	    	e.printStackTrace();
	    	// 	具体捕获的异常是什么异常
	    	flag = false;
	    }
		
	    return flag;
    }

}

 更新库存:updateStoreCountByVersion

<update id="updateStoreCountByVersion">
    update t_store ts
    set 
      store_count = store_count - 1,   #库存减1
      version = version + 1,           #版本加1
      update_by = #{updateBy,jdbcType=VARCHAR},
      update_time = #{updateTime,jdbcType=TIMESTAMP}
    where  ts.supplier_id = #{supplierId,jdbcType=VARCHAR}
    	   and
    	   ts.goods_id = #{goodsId,jdbcType=VARCHAR}
    	   and
    	   ts.version = #{version,jdbcType=INTEGER}     #版本号必须相同!!!
    	   and	
           ts.store_count > 0                           #库存必须大于0!!!
  </update>

入库:insertSelective 

<insert id="insertSelective" parameterType="com.yy.order.entity.Order" >
    insert into t_order
    <trim prefix="(" suffix=")" suffixOverrides="," >
      <if test="orderId != null" >
        order_id,
      </if>
      <if test="orderType != null" >
        order_type,
      </if>
      <if test="cityId != null" >
        city_id,
      </if>
      <if test="platformId != null" >
        platform_id,
      </if>
      <if test="userId != null" >
        user_id,
      </if>
      <if test="supplierId != null" >
        supplier_id,
      </if>
      <if test="goodsId != null" >
        goods_id,
      </if>
      <if test="orderStatus != null" >
        order_status,
      </if>
      <if test="remark != null" >
        remark,
      </if>
      <if test="createBy != null" >
        create_by,
      </if>
      <if test="createTime != null" >
        create_time,
      </if>
      <if test="updateBy != null" >
        update_by,
      </if>
      <if test="updateTime != null" >
        update_time,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
      <if test="orderId != null" >
        #{orderId,jdbcType=VARCHAR},
      </if>
      <if test="orderType != null" >
        #{orderType,jdbcType=VARCHAR},
      </if>
      <if test="cityId != null" >
        #{cityId,jdbcType=VARCHAR},
      </if>
      <if test="platformId != null" >
        #{platformId,jdbcType=VARCHAR},
      </if>
      <if test="userId != null" >
        #{userId,jdbcType=VARCHAR},
      </if>
      <if test="supplierId != null" >
        #{supplierId,jdbcType=VARCHAR},
      </if>
      <if test="goodsId != null" >
        #{goodsId,jdbcType=VARCHAR},
      </if>
      <if test="orderStatus != null" >
        #{orderStatus,jdbcType=VARCHAR},
      </if>
      <if test="remark != null" >
        #{remark,jdbcType=VARCHAR},
      </if>
      <if test="createBy != null" >
        #{createBy,jdbcType=VARCHAR},
      </if>
      <if test="createTime != null" >
        #{createTime,jdbcType=TIMESTAMP},
      </if>
      <if test="updateBy != null" >
        #{updateBy,jdbcType=VARCHAR},
      </if>
      <if test="updateTime != null" >
        #{updateTime,jdbcType=TIMESTAMP},
      </if>
    </trim>
  </insert>

二.  分库分表

发布了114 篇原创文章 · 获赞 199 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/qq_36582604/article/details/90023996