redis watch 秒杀功能哪个版本

&  我们都知道redis追求的是简单,快速,高效,在这种情况下也就拒绝了支持window平台,学sqlserver的时候,我们知道事务还算是个比较复杂的东西,
所以这吊毛要是照搬到redis中去,理所当然redis就不是那么简单纯碎的东西了,但是呢,事务是我们写程序无法逃避的场景,所以redis作者折衷的写了个简
化版的事务机制,下面我来扯一下它的蛋蛋。
一: 事务实战
  具体到事务是什么,要保证什么。。。这个我想没必要说了,先不管三七二十一,看一下redis手册,领略下它的魔力。
1. multi,exec
& &还记得sqlserver是怎么玩的吗?一般都是这样的三个步骤,生成事务,产生命令,执行事务,对吧,而对应redis呢??multi就是生成事务,然后
输入redis命令,最后用exec执行命令,就像下面这样:
可以看到,我set完命令之后,反馈信息是QUEUED,最后我再执行exec,这些命令才会真正的执行,就是这么的简单,一切执行的就是那么的顺利,
一点都不拖泥带水,牛逼的不要不要的,可能有些人说,其实事务中还有一个rollback操作,但好像在redis中没有看到,哈哈,牛逼哈,很遗憾是
redis中没有rollback操作,比如下面这样。
在图中我故意用lpush命令去执行string,可想而知自然不会执行成功,但从结果中,你看到什么了呢?两个OK,一个Error,这就是违反了事务
的原子性,对吧,但是我该怎么反驳呢??? 我会说,错你妹啊。。。连个基本的命令都写错了,你搞个毛啊。。。还写个吊毛代码,reids仅仅
是个数据结构服务器,多简单的一件事情,退一万步说,很明显的错误命令它会直接返回的,比如我故意把lpush写成lpush1:
  不知道你看完multi后面的三条set命令之后,有没有一种心虚的感觉,怎么说呢,就是只要命令是正确的,redis保证会一并执行,誓死完成
任务,虽然说命令是一起执行的,但是谁可以保证我在执行命令的过程中,其他client不会修改这些值呢???如果修改了这些值,那我的exec
还有什么意义呢???没关系,这种烂大街的需求,redis怎可能袖手旁观???这里的watch就可以助你一臂之力。
WATCH key [key ...]
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
 上面就是redis手册中关于watch的解释,使用起来貌似很简单,就是我在multi之前,用watch去监视我要修改的key,如果说我在exec之前,
multi之后的这段时间,key被其他client修改,那么exec就会执行失败,返回(nil),就这么简单,我还是来举个例子:
二:原理探索
  关于事务操作的源代码,大多都在redis源码中的multi.c 文件中,接下来我会一个一个的简单剖析一下:
  在redis的源代码中,它大概是这么写的:
1 void multiCommand(redisClient *c) {
if (c-&flags & REDIS_MULTI) {
addReplyError(c,"MULTI calls can not be nested");
c-&flags |= REDIS_MULTI;
addReply(c,shared.ok);
从这段代码中,你可以看到multi只是简单的把redisClient的REDIS_MULTI状态打开,告诉这个redis客户端已经进入事务模式了,对吧。
2. 生成命令
在redisClient中,里面有一个multiState命令:
typedef struct redisClient {
/* MULTI/EXEC state */
从注释中你大概也看到了这个命令和multi/exec肯定有关系,接下来我很好奇的看看multiState的定义:
typedef struct multiState {
multiCmd *
/* Array of MULTI commands */
/* Total number of MULTI commands */
/* MINREPLICAS for synchronous replication */
time_t minreplicas_ /* MINREPLICAS timeout as unixtime. */
从multiState这个枚举中,你可以看到下面有一个*command命令,从注释中可以看到它其实指向的是一个数组,这个数组我想你闭着眼睛都
能想得到吧。。。它就是你的若干条命令啦。。。下面还有一个count,可以看到是实际的commands的总数。
& & 为了方便说到后面的exec,这里想说一下watch大概是怎么实现的,在multi.c源代码中是这样写的。
1 typedef struct watchedKey {
4 } watchedK
6 void watchCommand(redisClient *c) {
if (c-&flags & REDIS_MULTI) {
addReplyError(c,"WATCH inside MULTI is not allowed");
for (j = 1; j & c-& j++)
watchForKey(c,c-&argv[j]);
addReply(c,shared.ok);
18 /* Watch for the specified key */
19 void watchForKey(redisClient *c, robj *key) {
list *clients = NULL;
listNode *
watchedKey *
/* Check if we are already watching for this key */
listRewind(c-&watched_keys,&li);
while((ln = listNext(&li))) {
wk = listNodeValue(ln);
if (wk-&db == c-&db && equalStringObjects(key,wk-&key))
return; /* Key already watched */
/* This key is not already watched in this DB. Let's add it */
clients = dictFetchValue(c-&db-&watched_keys,key);
if (!clients) {
clients = listCreate();
dictAdd(c-&db-&watched_keys,key,clients);
incrRefCount(key);
listAddNodeTail(clients,c);
/* Add the new key to the list of keys watched by this client */
wk = zmalloc(sizeof(*wk));
wk-&db = c-&
incrRefCount(key);
listAddNodeTail(c-&watched_keys,wk);
这段代码中大概最核心的一点就是:
/* This key is not already watched in this DB. Let's add it */
clients = dictFetchValue(c-&db-&watched_keys,key);
就是通过dicFetchValue这个字典方法,从watched_keys中找到指定key的value,而这个value是一个clients的链表,说明人家其实是想找到
关于这个key的所有client,对吧,最后还会将本次key塞入到redisclient的watched_keys字典中,如下代码:
/* Add the new key to the list of keys watched by this client */
wk = zmalloc(sizeof(*wk));
wk-&db = c-&
incrRefCount(key);
listAddNodeTail(c-&watched_keys,wk);
如果非要画图,大概就是这样:
其中watched_key是个字典结构,字典的键为上面的key1,key2。。。,value为client的链表,这样的话,我就非常清楚某个key
中是被哪些client监视着的,对吧。
& & 这个命令里面大概做了两件事情:
&1&: & 判断c-&flags=REDIS_DIRTY_EXEC 打开与否,如果是的话,取消事务discardTransaction(c),也就是说这个key已经
& & & & & 被别的client修改了。
&2&: & 如果没有修改,那么就for循环执行comannd[]中的命令,如下图中的两处信息:
好了,大概就这么说了,希望对你有帮助哈~~~
阅读(...) 评论()问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
watch 好象只有一个参数那应该就是普通的key不知道能不能watch hash 里的一个key呢?
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
watch只能针对一个key,hash里面的key不行的
同步到新浪微博
分享到微博?
Hi,欢迎来到 SegmentFault 技术社区!⊙▽⊙ 在这里,你可以提出编程相关的疑惑,关注感兴趣的问题,对认可的回答投赞同票;大家会帮你解决编程的问题,和你探讨技术更新,为你的回答投上赞同票。
明天提醒我
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:
扫扫下载 App
SegmentFault
一起探索更多未知突然想写点关于redis业务实现的一些东西,想起来很早之前看过一个关于用watch完成秒杀功能的案例,然后又翻出来看了看,不看还好, 一看发现这个实现逻辑是有问题了,随便改吧改吧,希望不要被误导$redis = new Redis();$redis-&pconnect('127.0.0.1', );echo &Connection to server sucessfully&;//check whether server is running or not// echo &Server is running: & . $redis-&ping();$rob_total = 100;
//抢购数量
$mywatchkey = $redis-&get(&mywatchkey&);if($mywatchkey === false){
$redis-&set(&mywatchkey&, $rob_total);
$mywatchkey = $rob_}if($mywatchkey &= 0){
file_put_contents('message.txt', &you are late, red packet was gone/n&, FILE_APPEND );}$redis-&watch(&mywatchkey&);
$ret = $redis-&multi();
$mywatchkey = $ret-&get(&mywatchkey&);
if($mywatchkey & 0 ){
//插入抢购数据
[不考虑去重的情况下]
$ret-&hSet(&mywatchlist&,&user_id_&.mt_rand(1, 9999),time());
$ret-&decr(&mywatchkey&);
$rob_result = $ret-&exec();
if($rob_result){
$mywatchlist = $redis-&hGetAll(&mywatchlist&);
echo &抢购成功!&br/&&;
echo &剩余数量:&. ($mywatchkey-1).&&br/&&;
echo &用户列表:&pre&&;
var_dump($mywatchlist);
file_put_contents('message.txt', &bug sucess! remaining num:&. $redis-&get(&mywatchkey&) . &/n&, FILE_APPEND );
// $redis-&discard();
file_put_contents('message.txt', &bug failure! unlucky go on /n&, FILE_APPEND );
$redis-&discard();
file_put_contents('message.txt', &red packet was gone/n&, FILE_APPEND );}// $redis-&close();首先连接 这里用的pconnet 并设置2.5s的超时限制 &抢购肯定是在短时间内大量的请求 所以还是持久化好点然后这个mywatchkey取值的点也要注意下还有这里采用的hash存放用户id,以前我有 &json(['userid'=&00001, 'time'=&time()]) 后放list的情况, 还是跟业务吧,思路多样化对于这种实现思路 一定注意:大量的用户涌入, 不是说你是前【$rob_total = 100】个就一定能抢到,后面的用户就没有红包可抢了,这种要基于并发考虑, 比如说前100个用户同时涌入,其中一个用户获得这个 watch key &其他用户就只能 &bug failure! unlucky go on& 继续自主加入抢的队列或者放弃了而且watch mulit exec 会拖慢性能, 需要根据业务量 考虑相应的策略如果对于前100用户的抢购业务策略,可以考虑list+set方式,不过也有一定的局限性首先有个红包队列, 这里简单模拟下://note 预存红包for ($i=0; $i & 1000; $i++) {
$redis-&lpush('000|redpacket', time(). sprintf(&%04d&, mt_rand(0, 1000)));}然后给就是等待放出时间开始抢的业务了://note begin buy$userid = mt_rand(0, 10000);$redOne = $redis-&rpop('000|redpacket');if($redOne){
if($redis-&sadd('000|redusers', $userid)){
$redis-&lpush('000|consumeredlist', json_encode(['redOne'=&$redOne, 'userid'=&$userid, 'time'=&microtime(true)]));
file_put_contents('message.txt', &SUCESS: You bug success/n&, FILE_APPEND );
//note: if user have already bought, Put the red packet back list
if(!$redis-&lpush('000|redpacket', $redOne)) $redis-&lpush('000|redpacket', $redOne);
file_put_contents('message.txt', &NOTICE: You have already bought/n&, FILE_APPEND );
file_put_contents('message.txt', &NOTICE: you are late, red packet was gone/n&, FILE_APPEND );
}其实主要就是要根据业务场景,考虑具体实现思路,这里只是对其中一些点做些浅显的介绍
最新教程周点击榜
微信扫一扫Redis 教程
Redis Unwatch 命令
Redis Unwatch 命令用于取消 WATCH 命令对所有 key 的监视。
redis Unwatch 命令基本语法如下:
redis 127.0.0.1:6379& UNWATCH
总是返回 OK 。
redis 127.0.0.1:6379& WATCH lock lock_times
redis 127.0.0.1:6379& UNWATCH
反馈内容(*必填)
截图标记颜色
联系方式(邮箱)
联系邮箱:
投稿页面:
记住登录状态
重复输入密码**redis需单点部署(非集群)**
redis的乐观锁可用于:
1.防止并发重复注册
2.防止同一账号并发请求(只接受一个请求)
抢购实现:
1、使用watch,采用乐观锁
2、不使用悲观锁,因为等待时间非常长,响应慢
3、不使用队列,因为并发量会让队列内存瞬间升高
import .util.concurrent.ExecutorS
import java.util.concurrent.E
import redis.clients.jedis.J
* redis测试抢购
public class RedisTest {
public static void main(String[] args) {
final String watchkeys = "watchkeys";
ExecutorService executor = Executors.newFixedThreadPool(20);
final Jedis jedis = new Jedis("192.168.3.202", 6379);
jedis.set(watchkeys, "0");// 重置watchkeys为0
jedis.del("setsucc", "setfail");// 清空抢成功的,与没有成功的
jedis.close();
for (int i = 0; i & 10000; i++) {// 测试一万人同时访问
executor.execute(new MyRunnable());
executor.shutdown();
import java.util.L
import java.util.UUID;
import redis.clients.jedis.J
import redis.clients.jedis.T
public class MyRunnable implements Runnable {
String watchkeys = "watchkeys";// 监视keys
Jedis jedis = new Jedis("192.168.3.202", 6379);
public MyRunnable() {
public void run() {
jedis.watch(watchkeys);// watchkeys
String val = jedis.get(watchkeys);
int valint = Integer.valueOf(val);
String userifo = UUID.randomUUID().toString();
if (valint & 10) {
Transaction tx = jedis.multi();// 开启事务
tx.incr("watchkeys");
List&Object& list = tx.exec();// 提交事务,如果此时watchkeys被改动了,则返回null
if (list != null) {
.out.println("用户:" + userifo + "抢购成功,当前抢购成功人数:"
+ (valint + 1));
/* 抢购成功业务逻辑 */
jedis.sadd("setsucc", userifo);
System.out.println("用户:" + userifo + "抢购失败");
/* 抢购失败业务逻辑 */
jedis.sadd("setfail", userifo);
System.out.println("用户:" + userifo + "抢购失败");
jedis.sadd("setfail", userifo);
// Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.unwatch();
jedis.close();
Redis Java jedis 乐观锁测试实例**
com.redis.
import mons.pool.impl.GenericObjectPool.C
import redis.clients.jedis.J
import redis.clients.jedis.JedisP
import redis.clients.jedis.T
public class JedisTransactionTest {
public static JedisP
pool = new JedisPool(new Config(), "127.0.0.1", 6379);
public static void main(String[] args) {
Jedis jedis = pool.getResource();
jedis.set("name", "songr");
new ThreadClient3(pool).start(); // 模拟1
new ThreadClient4(pool).start(); // 模拟客户端2
new ThreadClient5(pool).start(); // 获取对象
class ThreadClient3 extends Thread {
public ThreadClient3(JedisPool pool) {
jedis = pool.getResource();
public void run() {
if("OK".equals(jedis.watch("name"))){
System.out.println("key:name 被监视");
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
Transaction t = jedis.multi();
t.set("name", "songrt");
if(t.exec()==null){
System.out.println("中的name 已经被修改,ThreadClient3无法set
jedis.unwatch();
// pool.returnResource(jedis);
class ThreadClient4 extends Thread {
public ThreadClient4(JedisPool pool) {
jedis = pool.getResource();
public void run() {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
if("OK".equals(jedis.set("name", "songru"))){
System.out.println("ThreadClient4 set name 成功");
// pool.returnResource(jedis);
class ThreadClient5 extends Thread {
public ThreadClient5(JedisPool pool) {
jedis = pool.getResource();
public void run() {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
String name = jedis.get("name");
System.out.println("ThreadClient5 获取name 的值为:"+name);
// pool.returnResource(jedis);
运行结果:
key:name 被监视
ThreadClient4 set name 成功
数据库中的name 已经被修改,ThreadClient3无法set
ThreadClient5 获取name 的值为:songru
通过watch 方法监控 key(name), 如果发生变化 ,不进行任何操作 返回null,否则进行set

我要回帖

更多关于 redis unwatch 的文章

 

随机推荐