自实现CAS原理JAVA版,模拟下单库存扣减

Smileyou 2014-04-02

在做电商系统时,库存是一个非常严格的数据,根据CAS(check and swap)原来下面对库存扣减提供两种方法,一种是redis,一种用java实现CAS。

第一种 redis实现:

以下这个类是工具类,稍作修改就可运行

import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCommands;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.util.Pool;

import com.maowu.commons.conf.FileUpdate;
import com.maowu.commons.conf.NotifyFileUpdate;
import com.maowu.commons.logutil.LogProxy;
import com.maowu.commons.util.LogicUtil;

public class JedisPoolFactory
{
    private static Logger log = LogProxy.getLogger(JedisPoolFactory.class);

    private static String configFile = "jedisconf";
    /**
     * 主服务器数据源连接池,主要用于写操作
     */
    private static JedisPool jedisPool = null;

    /**
     * 数据源共享连接池,主要用于读取数据
     */
    private static ShardedJedisPool shardedJedisPool = null;

    static
    {
        loadXmlConfig();

        NotifyFileUpdate.registInterface(new FileUpdate()
        {
            @Override
            public void updateFile(String fileName)
            {
                if (fileName.startsWith(configFile))
                {
                    log.debug("updateFile = " + fileName);
                    loadXmlConfig();
                }
            }
        });
    }

    /**
     * @author: smartlv
     * @date: 2014年2月17日下午3:36:30
     */
    private static void loadXmlConfig()
    {
        DefaultListableBeanFactory context = new DefaultListableBeanFactory();
        BeanDefinitionReader reader = new XmlBeanDefinitionReader(context);
        reader.loadBeanDefinitions("classpath:autoconf/jedisconf.xml");

        initJedisPool(context);
        initShardedJedisPool(context);
    }

    private static void initJedisPool(DefaultListableBeanFactory context)
    {
        JedisConf conf = (JedisConf) context.getBean("jedisConf");

        JedisShardInfo jsInfo = null;
        if (LogicUtil.isNullOrEmpty(conf.getJsInfo()))
        {
            return;
        }
        jsInfo = conf.getJsInfo().get(0);

        jedisPool = new JedisPool(conf.getPoolConfig(), jsInfo.getHost(), jsInfo.getPort(), jsInfo.getTimeout(),
                jsInfo.getPassword());
    }

    private static void initShardedJedisPool(DefaultListableBeanFactory context)
    {
        JedisConf conf = (JedisConf) context.getBean("shardedJedisConf");

        shardedJedisPool = new ShardedJedisPool(conf.getPoolConfig(), conf.getJsInfo(), conf.getAlgo(),
                Pattern.compile(conf.getPattern()));
    }

    public static JedisPool getJedisPool()
    {
        return jedisPool;
    }

    public static ShardedJedisPool getShardedJedisPool()
    {
        return shardedJedisPool;
    }

    /**
     * 打开一个普通jedis数据库连接
     *
     * @param jedis
     */
    public static Jedis openJedis() throws Exception
    {
        if (LogicUtil.isNull(jedisPool))
        {
            return null;
        }

        return jedisPool.getResource();
    }

    /**
     * 打开一个分布式jedis从数据库连接
     *
     * @throws
     * @author: yong
     * @date: 2013-8-30下午08:41:23
     */
    public static ShardedJedis openShareJedis() throws Exception
    {
        if (LogicUtil.isNull(shardedJedisPool))
        {
            return null;
        }

        return shardedJedisPool.getResource();
    }

    /**
     * 归还普通或分布式jedis数据库连接(返回连接池)
     *
     * @param p
     *        连接池
     * @param jedis
     *        客户端
     */

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void returnJedisCommands(Pool p, JedisCommands jedis)
    {
        if (LogicUtil.isNull(p) || LogicUtil.isNull(jedis))
        {
            return;
        }

        try
        {
            p.returnResource(jedis);// 返回连接池
        }
        catch (Exception e)
        {
            log.error("return Jedis or SharedJedis to pool error", e);
        }
    }

    /**
     * 释放redis对象
     *
     * @param p
     *        连接池
     * @param jedis
     *        redis连接客户端
     * @throws
     * @author: yong
     * @date: 2013-8-31下午02:12:21
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void returnBrokenJedisCommands(Pool p, JedisCommands jedis)
    {
        if (LogicUtil.isNull(p) || LogicUtil.isNull(jedis))
        {
            return;
        }

        try
        {
            p.returnBrokenResource(jedis); // 获取连接,使用命令时,当出现异常时要销毁对象
        }
        catch (Exception e)
        {
            log.error(e.getMessage(), e);
        }
    }

}

redis 保证CAS的一个方法:

    @Override
    public long decrLastActiSkuCount(String actiId, String skuId, long decrCount)
    {
        int NOT_ENOUGH = -2;// 库存不足
        long lastCount = -1;// 剩余库存
        int maxTryTime = 100;// 最大重试次数

        Jedis jedis = null;
        JedisPool pool = JedisPoolFactory.getJedisPool();
        try
        {
            jedis = pool.getResource();

            String key = actiId + Constants.COLON + skuId;
            byte[] bkey = SerializeUtil.serialize(key);
            LogUtil.bizDebug(log, "decr sku count key=[%s]", key);

            for (int i = 0; i < maxTryTime; i++)
            {
                try
                {
                    jedis.watch(bkey);// 添加key监视
                    byte[] r = jedis.get(bkey);
                    long c = Long.valueOf(new String(r, Protocol.CHARSET));
                    if (c < decrCount)// 判断库存不充足
                    {
                        jedis.unwatch();// 移除key监视
                        return NOT_ENOUGH;// 库存不足
                    }
                    Transaction t = jedis.multi();// 开启事务,事务中不能有查询的返回值操作
                    t.decrBy(bkey, decrCount);// 修改库存数,该函数不能立即返回值
                    List<Object> trs = t.exec();// 事务执行结果
                    LogUtil.bizDebug(log, "transaction exec result=[%s]", trs);
                    if (LogicUtil.isNotNullAndEmpty(trs))
                    {
                        lastCount = (Long) trs.get(0);// 剩余库存
                        break;// 在多线程环境下,一次可能获取不到库存,当提前获取到库存时,跳出循环
                    }
                }
                catch (Exception e)
                {
                    log.error("watched key's value has changed", e);
                }

                if (i == maxTryTime - 1)
                {
                    log.error("arrived max try time:" + maxTryTime);
                }
            }
        }
        catch (Exception e)
        {
            log.error("decr sku stock count error", e);
            JedisPoolFactory.returnBrokenJedisCommands(pool, jedis);
        }
        finally
        {
            JedisPoolFactory.returnJedisCommands(pool, jedis);
        }

        return lastCount;
    }

第二种实现

数据类:

public class D
{
    public final static int INIT_VERSION = 0;
    volatile int c = 0;
    volatile int v = INIT_VERSION;

    public synchronized int add(int c, int v)
    {
        try
        {
            Thread.sleep(1000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        if (this.v == v)
        {
            this.c = this.c + c;
            this.v++;
            return this.c;
        }
        return -1;
    }

    public int[] get()
    {
        try
        {
            Thread.sleep(1000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        return new int[] { c, v };
    }
}

访问类:访问库存

public class T implements Runnable
{
    D d;
    int i = 0;

    public T(D d, int i)
    {
        this.d = d;
        this.i = i;
    }

    public void changeStock(D d, int c)
    {
        for (int i = 0; i < 100; i++)
        {
            int g[] = d.get();
            if (g[0] < Math.abs(c))
            {
                System.out.println("库存不足");
                break;
            }
            else
            {
                int a = d.add(c, g[1]);
                if (a >= 0)
                {
                    System.out.println("待扣库存-->" + c + " 剩余库存-->" + a);
                    break;
                }
                else
                {
                    System.out.println("待扣库存-->" + c + " 版本号不对");
                }
            }
        }
    }

    @Override
    public void run()
    {
        changeStock(d, i);
    }

    public static void main(String[] args)
    {
        // 初始化库存
        D d = new D();
        int c = d.add(200, D.INIT_VERSION);
        System.out.println("---初始化" + c + "库存---");

        Thread th[] = new Thread[10];
        for (int i = 0; i < th.length; i++)
        {
            th[i] = new Thread(new T(d, -i), "i=" + i);
        }

        for (int i = 0; i < th.length; i++)
        {
            th[i].start();
        }
    }
}

我觉得第二种实现不能直接放入业务代码中,稍作修改应该可以。欢迎大家拍砖。。。

相关推荐