redis 单进程单线程的模式, 决定了多客户端连接间不会产生竞态, 其所有命令都是原子性的, 用来实现一些队列 锁等服务, 简直再合适不过了.

另有一姊妹篇 : 基于redis的悲观锁实现 说来也很简单, 只是利用了redis 的 watch/unwatch + mulit/exec 两组命令 就可以实现一个check at set机制的乐观锁

就拿当前流行的秒杀场景, 举个栗子 :

// 仅仅举例用, 显然此处乐观锁并不太适用

// 顾名思义 : 观察12号商品

WATCH goods_12 
GET goods_12 
// 返回 values_of_goods_12, 数量减 1 
values_of_goods_12 -= 1 
// 开启事务 
MULTI 
SET goods_12  value_of_goods_12 
SET goods_12_detail  goods_detail_12 
EXEC

如此一来, 多个客户端均可以操作 goods_12, 但是只有一个客户端可以操作成功, 别的客户 端保存时 发现内容已经改变, 就会放弃继续操作. 正是这种 check at set 的机制 实现了一个 分布式乐观锁 that’s all ~



redis使用watch完成秒杀抢购功能 redis使用watch完成秒杀抢购功能:
使用redis中两个key完成秒杀抢购功能,mywatchkey用于存储抢购数量和mywatchlist用 户存储抢购列表。
它的优点如下:

  1. 首先选用内存数据库来抢购速度极快。
  2. 速度快并发自然没不是问题。
  3. 使用悲观锁,会迅速增加系统资源。
  4. 比队列强的多,队列会使你的内存数据库资源瞬间爆棚。
  5. 使用乐观锁,达到综合需求。
    我觉得以下代码肯定是你想要的。
[php] view plain copy print? 
 <?php   
 header("content-type:text/html;charset=utf-8");  
 $redis = new redis();   
  $result = $redis->connect('10.10.10.119', 6379);   
   $mywatchkey = $redis->get("mywatchkey");   
   $rob_total = 100;   //抢购数量   
   if($mywatchkey<$rob_total){   
   $redis->watch("mywatchkey");   
    $redis->multi();   10.        
             //设置延迟,方便测试效果。   
      sleep(5);   
                     //插入抢购数据   
     $redis->hSet("mywatchlist","user_id_".mt_rand(1, 9999),time());   
      $redis->set("mywatchkey",$mywatchkey+1);   
     $rob_result = $redis->exec();  
    if($rob_result){   
           $mywatchlist = $redis->hGetAll("mywatchlist");   
           echo "抢购成功!<br/>";   
           echo "剩余数量:".($rob_total-$mywatchkey-1)."<br/>";   
             echo "用户列表:<pre>";   
               var_dump($mywatchlist);   
         }else{   
                echo "手气不好,再抢购!";exit;   
                 }   
      }   
?>

---------------------------------------------------------------------------------------------------------------
本文使用redis来实现乐观锁,并以秒杀系统为实例来讲解整个过程 乐观锁
大多数是基于数据版本(version)的记录机制实现的。即为数据增加一个版本标识, 在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个”version”字段来实 现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据 的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当 前版本号,则予以更新,否则认为是过期数据。redis中可以使用watch命令会监视给定的 key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以 调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。注意watch的key是对 整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了 exec,discard,unwatch命令都会清除连接中的所有监视。
Redis事务
Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单 位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在 本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。Redis的事务是下面4 个命令来实现

  1. multi,开启Redis的事务,置客户端为事务态。
  2. exec,提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。
  3. discard,取消事务,置客户端为非事务态。
    4.watch,监视键值对,作用时如果事务提交exec时发现监视的监视对发生变化,事务将被取 消。

下面笔者简单实现一个用redis乐观锁实现的秒杀系统 和上文的使用悲观锁相比,
乐观锁的实现更加的简单,并发性能也会更好。

1、分布式锁
分布式锁在是一种用来安全访问分式式机器上变量的安全方案,一般用在全局id生成, 秒杀系统,全局变量共享、分布式事务等。一般会有两种实现方案,一种是悲观锁的实现, 一种是乐观锁的实现。悲观锁的并发性能差,但是能保证不会发生脏数据的可能性小一点。

2、Redis命令介绍 使用Redis实现分布式锁,有两个重要函数需要介绍
SETNX命令(SET if Not eXists)
语法:
SETNX key value
功能: 当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

GETSET命令(这是一个原子命令!)
语法: GETSET key value
功能: 将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串 类型时,返回一个错误,当key不存在时,返回nil。

GET命令
语法: GET key
功能: 返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。

DEL命令
语法: DEL key [KEY …]
功能: 删除给定的一个或多个 key ,不存在的 key 会被忽略