一线码农 2016-02-20
场景:
如抢红包、抢优惠券,都是先到先得
抢红包是把发出来的红包先分成预设的份数,预先处理好了每个红包的金额大小,然后
将分配好的红包装进一个队列当中,等待哄抢(并发的可能)
抢优惠券也是预先生成了若干的优惠券,然后将所有生成的优惠券码放进一个队列当中,等待领取(并发的可能)
现用redis集合操作实现队列的控制
首先实现一个资源的可访问次数接口:
/** * 是否可以继续访问某一资源(时间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; } }
所有相同资源装进一个集合当中
//key为资源集合的缓存key redis.sadd(key, members);//members为一个string数组,里面为目标资源集合
单个请求获取资源:
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); } }