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用 户存储抢购列表。
它的优点如下:
- 首先选用内存数据库来抢购速度极快。
- 速度快并发自然没不是问题。
- 使用悲观锁,会迅速增加系统资源。
- 比队列强的多,队列会使你的内存数据库资源瞬间爆棚。
- 使用乐观锁,达到综合需求。
我觉得以下代码肯定是你想要的。
[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 个命令来实现
- multi,开启Redis的事务,置客户端为事务态。
- exec,提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。
- 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 会被忽略