`
xiaoliang330
  • 浏览: 114471 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

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

 
阅读更多
场景:

如 抢红包、 抢优惠券,都是先到先得


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

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



现用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);
	}
}







分享到:
评论

相关推荐

    tomcat-redis-session-manager-1.2-tomcat-7-java-7

    标题中的"tomcat-redis-session-manager-1.2-tomcat-7-java-7"表明这是一个针对Tomcat服务器,用于管理session的组件,版本为1.2,适配Tomcat 7和Java 7环境。这个组件的主要作用是将Tomcat的会话管理功能与Redis...

    redis+redis-desktop-manager-0.8.3.3850+笔记

    1. 下载源码包:`redis-2.8.13.tar.gz` 是Redis的源码包,解压后进行编译和安装。 2. 解压:`tar -zxvf redis-2.8.13.tar.gz` 3. 编译:`cd redis-2.8.13`,然后`make` 4. 安装:`sudo make install` 5. 启动Redis...

    redis 免安装 redis客户端 redis-desktop-manager-0.8.8.384

    接下来,关于“redis-desktop-manager-0.8.8.384.exe”文件,这是一个 Redis 客户端工具,名为 Redis Desktop Manager。它提供了一个图形用户界面(GUI),使得用户可以方便地管理 Redis 服务器,包括查看键值、执行...

    tomcat-redis-session-manager-2.0.0.zip

    `tomcat-redis-session-manager-2.0.0`就是为了解决这个问题而诞生的一个开源项目,它将Tomcat容器中的Session数据存储到Redis缓存系统中,实现了跨服务器的Session共享。 Redis是一种高性能的键值数据库,特别适合...

    tomcat-redis-session-manager-2.0.0.jar

    tomcat-redis-session-manager-2.0.0.jar,可用于Tomcat8下Redis的Session共享,亲测可用,还需要下载另外两个jar包:commons-pool2-2.4.2.jar和jedis-2.9.0.jar,maven仓库有,此处不再上传

    php_redis-2.2.5-5.6-ts-vc11-x64

    本文将深入探讨PHP的Redis扩展,特别是针对“php_redis-2.2.5-5.6-ts-vc11-x64”这一版本,它专为PHP 5.6版本、线程安全(TS)、Visual C++ 11编译器以及64位(x64)系统设计。 首先,我们来理解PHP Redis扩展的...

    flink-connector-redis_2.11-1.1-SNAPSHOT

    这意味着数据一旦写入Redis,如果没有其他机制,它将永久存储,无法自动删除。而“flink-connector-redis_2.11-1.1-SNAPSHOT”这个版本的连接器修复了这个问题,现在支持为写入的数据设置过期时间。这不仅提高了数据...

    redis-desktop-manager-0.9.3.817.rar

    标题中的"redis-desktop-manager-0.9.3.817.rar"表明这是Redis Desktop Manager的一个特定版本,版本号为0.9.3.817,通常这个软件会被压缩成RAR格式的文件以便于下载和分发。RAR是一种流行的压缩格式,能够有效地...

    tomcat-redis-session-manager-1.2-tomcat-6&7

    标题 "tomcat-redis-session-manager-1.2-tomcat-6&7" 指的是一个用于在Tomcat服务器中集成Redis作为session管理器的组件。这个组件使得Web应用程序可以利用Redis分布式缓存系统来存储和管理用户的会话数据,从而...

    php_redis-2.2.7-5.6-ts-vc11-x64.zip.zip

    - 分布式锁:通过setnx()和expire()实现跨服务器的锁机制。 5. **注意事项**: - PHP版本兼容性:确保使用的PHP Redis扩展与PHP版本匹配,否则可能导致运行错误。 - PHP线程安全:TS版本的扩展只能在TS(线程...

    redis-desktop-manager-0.9.3.817

    标题中的“redis-desktop-manager-0.9.3.817”指的是一个Redis桌面管理工具的特定版本,用于帮助用户更方便地管理和操作Redis服务器。 Redis Desktop Manager是一款跨平台的应用程序,它为开发人员和管理员提供了...

    linux中redis安装包和redis-desktop-manager-0.9.3.817

    2. 下载Redis Desktop Manager的Windows版本(如提供的`redis-desktop-manager-0.9.3.817.exe`)。 3. 使用Wine运行安装程序:`wine redis-desktop-manager-0.9.3.817.exe` 4. 安装完成后,通过Wine启动Redis ...

    redis-spring-boot-starter.rar

    《Redis-Spring-Boot-Starter深度解析》 在现代Java Web开发中,Spring Boot框架以其高效、便捷的特点深受开发者喜爱。而Redis作为一种高性能的键值数据存储系统,常被用作缓存、消息队列等多种场景,与Spring Boot...

    Redis稳定版 Redis-x64-5.0.14.1.zip

    8. **并发控制**: Redis使用单线程模型处理客户端的请求,通过I/O多路复用技术如epoll或kqueue,实现高效的并发处理。 9. **网络协议**: Redis基于简单高效的RESP(REdis Serialization Protocol)协议,易于实现...

    PyPI 官网下载 | redis-py-cluster-1.1.0.tar.gz

    5. **多线程支持**:Redis-Py-Cluster可以很好地在多线程或多进程环境中工作,每个线程或进程有自己的连接池,以避免竞态条件和提高并发性能。 6. **扩展性**:随着集群的扩展,`redis-py-cluster`可以轻松适应新的...

    Redis-7.0.12-Windows-x64

    (CVE-2022-24834) A specially crafted Lua script executing in Redis can trigger a heap overflow in the cjson and cmsgpack libraries, and result in heap corruption and potentially remote code execution....

    tomcat-redis-session-manager-1.2-tomcat-6.jar

    用于配置 tomcat-redis-session-manager

    tomcat-redis-session-manager-master

    描述中的"tomcat-redis-session-manager-master" 重复了标题内容,暗示这是一个Git仓库的主分支,可能包含了源代码、配置文件和其他资源,用于集成Redis作为Tomcat的session存储解决方案。这个项目可能涉及到如何...

    Redis-x64-3.2.100&Redis-3.2.100-Windows-32

    Redis-x64-3.2.100&Redis-3.2.100-Windows-32下载整理的windows 64位&32位包。 欢迎下载。Redis-x64-3.2.100&Redis-3.2.100-Windows-32下载整理的windows 64位&32位包。 欢迎下载。Redis-x64-3.2.100&Redis-3.2.100-...

    Redis-x64-3.0.504及php_redis-3.1.4-7.2-ts-vc15-x64

    通过 `redis-server` 命令启动服务,并可以使用 `redis-cli` 进行客户端操作。 4. **Redis 持久化**:Redis 提供了两种主要的持久化方式:RDB(快照)和 AOF(追加日志)。RDB 是定期保存数据库状态的文件,AOF ...

Global site tag (gtag.js) - Google Analytics