在做电商系统时,库存是一个非常严格的数据,根据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();
}
}
}
我觉得第二种实现不能直接放入业务代码中,稍作修改应该可以。