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

本文介绍了在电商系统中如何使用 Java 实现库存扣减,提供了基于 Redis 的解决方案和自实现的 CAS(Compare and Swap)原理。通过 Jedis 工具类进行 Redis 操作,演示了在多线程环境下如何保证库存操作的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

标签:

在做电商系统时,库存是一个非常严格的数据,根据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 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();

}

}

}

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

标签:

### Java 实现订单库存扣减时的并发控制与加锁机制最佳实践 #### 1. 使用 Redis 进行库存扣减 为了确保高并发环境下的库存准确性,可以使用 Redis 来预先扣除库存。由于 Redis 是单线程模型并具备原子操作特性,因此非常适合用来处理这种场景。 ```java public class InventoryService { private final Jedis jedis; public boolean preDeductInventory(String productId, int quantity) { Long result = jedis.decrBy(productId, quantity); return result >= 0; } } ``` 当商品ID对应的键值小于零时表示库存足[^1]。 #### 2. 数据库层面使用乐观锁 通过本号字段 `version` 或者时间戳字段 `update_time` 对记录进行更新时增加条件判断,只有满足特定条件下才允许修改数据,从而避免脏读和幻读现象的发生。 ```sql UPDATE inventory SET stock=stock-?, version=version+1 WHERE product_id=? AND version=? ``` 对应到Java代码中: ```java @Transactional public void deductStockWithOptimisticLock(Long id, Integer count){ String sql = "UPDATE t_goods SET num=num-? , version=version+1 where id=? and version=?"; // 执行SQL语句... } ``` 如果影响行数为0,则说明当前存在其他事务正在对该条目做更改,此时应该抛出异常或者重试业务逻辑[^2]。 #### 3. 分布式锁控制并发访问 对于跨服务之间的竞争资源情况,可借助于像 Zookeeper、Redisson 提供的分布式互斥锁功能来协调多个实例间的同步问题。 利用 Redission 客户端创建公平锁 FairLock: ```java import org.redisson.api.RLock; import org.redisson.api.RedissonClient; @Autowired private RedissonClient redisson; public void processOrder(Order order) throws InterruptedException{ RLock lock = redisson.getFairLock("order:" + orderId); try { if (lock.tryLock(10, TimeUnit.SECONDS)) { // 处理下单流程... } else { throw new RuntimeException("Failed to acquire distributed lock"); } } finally { lock.unlock(); } } ``` 此方法能够有效防止因多节点同时请求而导致的数据混乱状况发生。 #### 4. 利用消息队列降低压力 引入 MQ 中间件作为缓冲区,将瞬时高峰流量平滑化分布至较长时间内逐步消化吸收掉,进而减轻后端服务器负载强度以及减少超卖风险。 生产者发送消息给 RabbitMQ 队列之后立即返回成功响应给前端页面展示已购买提示信息;消费者监听指定主题接收到来自生产者的指令后再去执行实际的商品数量变更动作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值