热点数据的实现

在系统中有一些数据是大多数线程都会访问的,这个时候就会产生竞争。例如库存。商品库存主要的库存的要求:首先是无论怎么减都不能小于0,第二点是尽可能的高效。

实现方式一:对于不能小于0,可以通过sql命令来实现。

   update account
        set
        count = count -1
        where name = "cloth" AND  count>0

这样一条命令能够实现保证count数不会小于一。但是在数据库里update时会加锁,当大量的线程过来的时候,就会变成是单线程的。影响系统的横向扩展。
第二种方式:无锁的方式是每一次扣库存的时候并不减库存,而是王数据库里写一条扣的交易记录。然后找一个时间点刷新库存。

系统启动的时候,读取到商品的库存主要是通过库存+库存消耗信息计算出库存。将库存使用Atomic变量stock表示库存。使用一个Atomic值cost表示消耗数量。
当扣减的时候,首先往数据库中插入写入一条消耗的信息。然后将cost+1.然后检查 stock+cost <0如果小于0添加一条补偿的记录进去。
这里先扣然后检查余量如果非法再加回去的做法可能会出现错报库存不足的情况。例如库存 = 10,ThreadA扣了11个,Thread扣B了9个。在ThreadA写补偿记录的时候ThreadB也会写入补偿记录。导致两个线程都扣库存失败。但是不会出现余额为负的情况。换个思路,这个商品如此受欢迎,先提前拒绝一些线程也是可以接受的。

数据结构:

消耗库存记录:这里也可以添加更多数据,例如和订单绑定等。

public class StockDetailPO {

    private String productName;//商品名称

    private Integer count;//扣减数量

    private Date operatetime;//插入时间
}

库存数据:

public class StockPO {

    private String productName;//商品名称

    private Integer stock;//库存

    private Date lasttime;//上一次统计时间
}

代码实现:

@Component
public class DetailStock implements Stock {

    private String productName;

    private volatile AtomicInteger stock;

    private AtomicInteger cost = new AtomicInteger(0);


    @Autowired
    private StockDao stockDao;

    @Autowired
    private StockDetailDAO stockDetailDAO;

    @PostConstruct
    private void init() {
        generateStock(productName);
    }

    public int getStock() {
        return stock.get() + cost.get();
    }

    public boolean updateStock(int num) {
        StockDetailPO stockDetailPO = new StockDetailPO();
        stockDetailPO.setOperatetime(new Date());
        stockDetailPO.setCreateTime(new Date());
        stockDetailPO.setCount(num);
        stockDetailPO.setProductName(productName);
        int insert = stockDetailDAO.insert(stockDetailPO);
        cost.addAndGet(num);
        if ((stock.get() + cost.get()) < 0) {
            int needRenew = -1 * num;
            StockDetailPO dStockDetailPO = new StockDetailPO();
            dStockDetailPO.setOperatetime(new Date());
            dStockDetailPO.setCreateTime(new Date());
            dStockDetailPO.setCount(needRenew);
            dStockDetailPO.setProductName(productName);
            stockDetailDAO.insert(dStockDetailPO);
            cost.addAndGet(needRenew);
            return false;
        }
        return insert > 0;
    }
    public void setProductName(String productName) {
        this.productName = productName;
    }

    /**
     *  初始化库存
     * @param productName
     * @return
     */
    private void generateStock(String productName) {
        if (stock == null) {
            StockPO account = stockDao.getStock(productName);
            Integer amountAfter = stockDetailDAO.getStockAfter(productName, account.getLasttime());
            stock = new AtomicInteger(account.getStock() + (amountAfter == null ? 0 : amountAfter));
        }
    }
}

注意

由于使用了内存来缓存库存和消耗,所以一个商品库存信息只能在一台机器上执行,这个时候就需要使用一致性哈希算法来根据商品信息来进行loadbalance,当一台机器挂了,可以移动到其他的机器上重建库存。

你可能感兴趣的:(并发)