redis实现并发资源控制--如抢红包、抢优惠券机制

[b]场景:

如 抢红包、 抢优惠券,都是先到先得[/b]

[color=blue]抢红包是把发出来的红包先分成预设的份数,预先处理好了每个红包的金额大小,然后
将分配好的红包装进一个队列当中,等待哄抢(并发的可能)

抢优惠券也是预先生成了若干的优惠券,然后将所有生成的优惠券码放进一个队列当中,等待领取(并发的可能)[/color]


[color=red][b]现用redis集合操作实现队列的控制[/b][/color]


首先实现一个资源的可访问次数接口:



/**
* 是否可以继续访问某一资源(时间second内只能访问visitTotal次 资源)
*
* @param visitTotal 访问次数限制
* @param second 时间周期
* @param key 某一资源访问限制的key
* @return
* @author xll
*/
private boolean canAccess(int visitTotal, int second, String key) {

int total = NumberUtils.toInt(redis.get(key));

if (total <= 0)
redis.del(key);// 有可能是之前访问留下的痕迹

if (total < visitTotal) {// 小于限制值,继续计数
redis.incr(key);// 增加key的值
if (total <= 0)
redis.expire(key, second);// 限定时间内的第一次访问设置过期时间
return true;
} else {
return false;
}
}





[b]
所有相同资源装进一个集合当中[/b]



//key为资源集合的缓存key
redis.sadd(key, members);//members为一个string数组,里面为目标资源集合




[b]单个请求获取资源:[/b]



if (canAccess(VISIT_TOTAL, SECOND, frequencyKey)) {//我领取过了吗

//key为资源集合的缓存key
String code = redis.spop(key);
}else{
return "";
}


or


//lockKey为需要锁的资源key
RedisLock lock = new RedisLock(redis, lockKey, 5);

// 防并发
if (lock.lock()) {
//key为资源集合的缓存key
String code = redis.spop(key);
}else{
return "";
}




redis分布式锁实现:




import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* 使用 redis 实现分布式锁
*
*
* @see <a href="http://www.jeffkit.info/2011/07/1000/">http://www.jeffkit.info/2011/07/1000/</a>
*/
public class RedisLock {

private Logger logger = LoggerFactory.getLogger(RedisLock.class);

private Redis redis;

/**
* 锁的key
*/
private String key;

/** 锁的超时时间(秒),过期删除 */
private int expire = 0;

// 锁状态标志
private boolean locked = false;

/**
* 其他人锁的 timestamp,仅用于debug
*/
private String lockTimestamp = "";

/**
* RedisLock构造函数,默认锁的过期时间是480秒
* @param redis - Redis 实例
* @param key - 要锁的key
*/
public RedisLock(Redis redis, String key) {
this(redis, key, 8 * 60);
}

/**
* RedisLock构造函数
* @param redis - Redis 实例
* @param key - 要锁的key
* @param expire - 过期时间,单位秒,必须大于0
*/
public RedisLock(Redis redis, String key, int expire) {
if (redis == null || key == null) {
throw new IllegalArgumentException("redis和key不能为null");
}
if (expire <= 0) {
throw new IllegalArgumentException("expire必须大于0");
}
this.redis = redis;
this.key = getLockKey(key);
this.expire = expire;
}

/**
* 尝试获得锁,只尝试一次,如果获得锁成功,返回true,否则返回false。
* 如果锁已经被其他线程持有,本操作不会等待锁。
* @return 成功返回true
*/
public boolean lock() {
long now = System.currentTimeMillis() / 1000;
// 保存超时的时间
String time = (now + expire + 1) + "";
if (tryLock(time)) {
// lock success, return
lockTimestamp = time;
}
else {
// 锁失败,看看 timestamp 是否超时
String value = redis.get(key);
if (now > transformValue(value)) {
// 锁已经超时,尝试 GETSET 操作
value = redis.getSet(key, time);
// 返回的时间戳如果仍然是超时的,那就说明,如愿以偿拿到锁,否则是其他进程/线程设置了锁
if (now > transformValue(value)) {
this.locked = true;
}
else {
logger.error("GETSET 锁的旧值是:" + value + ", key=" + key);
}
}
else {
logger.error("GET 锁的当前值是:" + value + ", key=" + key);
}
this.lockTimestamp = value;

}
return this.locked;
}

/**
* 释放已经获得的锁,
* 只有在获得锁的情况下才会释放锁。
* 本方法不会抛出异常。
*/
public void unlock() {
if (this.locked) {
try {
redis.del(key);
this.locked = false;
} catch (Exception e) {
logger.error("EXCEPTION when delete key: ", e);
}
}
}

private long transformValue(String value) {
if (StringUtils.isNotEmpty(value)) {
return NumberUtils.toLong(value, 0L);
}
return 0L;
}

/**
* 尝试获得锁
* @param time - 锁超时的时间(秒)
* @return true=成功
*/
private boolean tryLock(String time) {
if (this.locked == false && redis.setnx(key, time) == 1) {
// redis.expire(key, EXPIRE);
this.locked = true;
}
return this.locked;
}

public String getLockTimestamp() {
return lockTimestamp;
}

/**
* 获得Redis锁的key
* @param key - 要锁的key
* @return key
*/
private static String getLockKey(String key) {
return "R_lock4_" + key;
}

/**
* 清除key
* @param redis - Redis 实例
* @param key - 要清除锁的key
*/
public static void clearLock(Redis redis, final String key) {
String tmp = getLockKey(key);
redis.del(tmp);
}
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis可以通过使用事务或Lua脚本来实现并发下的购/秒杀功能。 1. 使用事务:购/秒杀的过程可以看做是一个先检查库存是否充足,再扣减库存的过程。使用Redis的事务可以保证这个过程是原子性的,即要么全部成功,要么全部失败。 具体实现方法如下: - 使用MULTI命令开启一个事务。 - 使用WATCH命令对库存进行监视(即设置监视器)。 - 使用GET命令获取当前库存。 - 判断库存是否充足,如果充足,则使用DECRBY命令扣减库存。 - 使用EXEC命令提交事务,如果提交成功,则说明扣减成功,否则说明被其他线程先扣减了库存。 示例代码如下: ```python def decrease_stock(redis_conn, stock_key): with redis_conn.pipeline() as pipeline: while True: try: pipeline.watch(stock_key) stock = int(pipeline.get(stock_key)) if stock > 0: pipeline.multi() pipeline.decr(stock_key) pipeline.execute() return True else: return False except WatchError: continue ``` 2. 使用Lua脚本:Lua脚本可以在Redis端原子性地执行多个命令,可以减少网络开销和锁竞争的问题。 具体实现方法如下: - 编写一个Lua脚本,该脚本首先使用GET命令获取当前库存,如果库存充足,则使用DECRBY命令扣减库存,否则返回0。 - 在Python中使用Redis的EVAL命令执行该Lua脚本。 示例代码如下: ```python def decrease_stock(redis_conn, stock_key): script = """ local stock = tonumber(redis.call('GET', KEYS[1])) if stock > 0 then redis.call('DECRBY', KEYS[1], 1) return 1 else return 0 end """ result = redis_conn.eval(script, 1, stock_key) return bool(result) ``` 以上两种方法都可以实现并发下的购/秒杀功能,但是使用Lua脚本的方法效率更高,因为在Redis端执行命令可以减少网络开销和锁竞争的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值