使用PHP操作Redis进行简单的项目开发
简单的字符串缓存
分别使用set/hSet方法将对象用json_encode解析成json字符串以String/Hash的数据类型存储在Redis缓存中,并用get/hGet取出数据,用json_decode解码后var_dump输出对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38<?php
$redis = new Redis();
$redis->connect('39.108.210.229',6379);
$strCacheKey = 'stringCacheTest';
//SET应用 存储String类型数据
//所缓存的对象
$arrCacheData = [
'name' => 'harvie',
'sex' => 'male',
'age' => '22'
];
//将对象解析成json字符串存储
$redis->set($strCacheKey, json_encode($arrCacheData));
//设置缓存有效期 30S过期失效
$redis->expire($strCacheKey, 30);
//从缓存中取出数据
$json_data = $redis->get($strCacheKey);
//将取出的json字符串解码成对象形式
$data = json_decode($json_data);
var_dump($data);
//HSET应用 存储Hash类型数据
$hashCacheKey = 'hashCacheTest';
//所缓存的由数组组成的对象
$arrWebSite = [
'google' => [
'google.com',
'google.com.hk'
],
];
//添加一个Value到Hash中
$redis->hSet($hashCacheKey, 'google', json_encode($arrWebSite));
$json_data = $redis->hGet($hashCacheKey, 'google');
$data = json_decode($json_data);
var_dump($data);
在cli运行后输出为
简单队列
使用lpush入队,使用lrange查看队列数据,使用rpop出队1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26<?php
$redis = new Redis();
$redis->connect('39.108.210.229',6379);
$strQueueName = 'queueName';
//进队列
$redis->lpush($strQueueName, json_encode(['uid' => 1,'name' => 'harvie']));
$redis->lpush($strQueueName, json_encode(['uid' => 2,'name' => 'ryan']));
$redis->lpush($strQueueName, json_encode(['uid' => 3,'name' => 'henry']));
echo "---- 进队列成功 ---- <br /><br />";
//查看队列
$strCount = $redis->lrange($strQueueName, 0, -1);
echo "当前队列数据为: <br />";
print_r($strCount);
//出队列
$redis->rpop($strQueueName);
echo "<br /><br /> ---- 出队列成功 ---- <br /><br />";
//查看队列
$strCount = $redis->lrange($strQueueName, 0, -1);
echo "当前队列数据为: <br />";
print_r($strCount);
由于linux对中文适配不好,我采用web远程访问,输入如下
订阅发布系统
pub.php中使用publish推送给订阅的客户端消息1
2
3
4
5
6
7
8
9
10
11<?php
ini_set('default_socket_timeout', -1); //不超时
$redis = new Redis();
$redis->connect('39.108.210.229',6379);
$strChannel = 'HarvieChannel';
//发布 推送给订阅的客户端
$redis->publish($strChannel,"From ".$strChannel." Channel Msg!");
echo "---- {$strChannel} ---- Channel Msg Push Success ! <br/>";
$redis->close();
sub.php中使用subscribe方法等待订阅频道的推送1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<?php
ini_set('default_socket_timeout', -1); //不超时
$redis = new Redis();
$redis->connect('39.108.210.229',6379);
$strChannel = 'HarvieChannel';
echo "Wait {$strChannel} Msg Push......";
//订阅 使用subscribe方法接受订阅频道的消息
$redis->subscribe([$strChannel], 'callBackFun');
function callBackFun($redis, $channel, $msg)
{
print_r([
'redis' => $redis,
'channel' => $channel,
'msg' => $msg
]);
}
运行sub.php客户端,等待消息推送
另外再打开一个终端,运行pub.php服务端推送消息
此时可以看到sub.php终端中可以接收到服务器推送的消息
计数器
使用INCR方法对指定的Key的Value+11
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<?php
$redis = new Redis();
$redis->connect('39.108.210.229',6379);
$strKey = 'commentsCount';
//设置初始值
$redis->set($strKey, 0);
//使用INCR方法指定Key增加Value
$redis->INCR($strKey); //+1
$redis->INCR($strKey); //+1
$redis->INCR($strKey); //+1
$strNowCount = $redis->get($strKey);
echo "Now the num is {$strNowCount}";
排行榜
使用zSet有序集合数据类型实现排行榜的自动排序,ZREVRANGE/ZRANGE方法取特定范围内从大到小/从小到大排序,可指定是否带分数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<?php
$redis = new Redis();
$redis->connect('39.108.210.229',6379);
$strKey = 'rankTest';
//zadd向zSet集合添加数据
$redis->zadd($strKey, '50', json_encode(['name' => 'Tom']));
$redis->zadd($strKey, '70', json_encode(['name' => 'John']));
$redis->zadd($strKey, '90', json_encode(['name' => 'Jerry']));
$redis->zadd($strKey, '30', json_encode(['name' => 'Job']));
$redis->zadd($strKey, '100', json_encode(['name' => 'LiMing']));
//ZREVRANGE取特定范围内从大到小排序的数组,最后一个参数为是否带分数显示
$dataOne = $redis->ZREVRANGE($strKey, 0, -1, true);
echo "---- {$strKey}由大到小的排序 ---- <br /><br />";
print_r($dataOne);
//ZRANGE取特定范围内从小到大排序的数组,最后一个参数为是否带分数显示
$dataTwo = $redis->ZRANGE($strKey, 0, -1, true);
echo "<br /><br />---- {$strKey}由小到大的排序 ---- <br /><br />";
print_r($dataTwo);
字符串悲观锁
确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性,以及数据库的统一性,乐观锁和悲观锁是并发控制主要采用的技术手段
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
- 实现机制:查询完数据的时候就把事务锁起来,直到提交事务
- 实现方式:数据库中的锁机制
- 实现特点:适合强一致场景,效率较低,特别是并发读的效率低
PHP+Redis实现悲观锁:利用redis中的setnx方法的原子性操作,设置一个LockKey,加锁的实质就是向redis中添加一个这个LockKey和过期时间,每次多个并发事务需要访问数据时,均先获取锁(就是用setnx方法设置LockKey字段),只有首先设置了字段的事务才会得到锁(就是返回Ture),进行接下来的数据处理操作,没有设置字段的事务就会阻塞直到锁过期或者获取锁的事务释放锁(就是删除LockKey字段)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59<?php
$redis = new Redis();
$redis->connect('39.108.210.229',6379);
/**
* 获取锁
* @param String $key 锁标识
* @param Int $expire 锁过期时间
* @return Boolean
*/
function lock($key = '', $expire = 5) {
//php函数无法调用外部变量
global $redis;
//获取锁
$isLock = $redis->setnx($key, time()+$expire);
//不能获取锁
if(!$isLock){
//判断锁是否过期
$lockTime =$redis->get($key);
//锁已过期,删除锁,重新获取
if (time() > $lockTime) {
unlock($key);
$isLock = $redis->setnx($key, time() + $expire);
}
}
return $isLock? true : false;
}
/**
* 释放锁
* @param String $key 锁标识
* @return Boolean
*/
function unlock($key = ''){
//php函数无法调用外部变量
global $redis;
return $redis->del($key);
}
// 定义锁标识
$key = 'PessimisticLock';
// 获取锁
$isLock = lock($key, 10);
while(!$isLock){
//获取锁失败 等待锁释放或者超时再获取
echo 'wait..';
sleep(1);
$isLock = lock($key, 10);
}
//获取锁成功开始执行事务
echo 'get lock success<br>';
echo 'do sth..<br>';
sleep(5);
echo 'success<br>';
unlock($key);
模拟测试:我们打开两个终端模拟并发过程,一个终端先获取锁执行事务,另一个终端获取锁失败之后每隔1s再次获取,直到第一个终端执行完事务(5s)释放锁或者锁超时(10s),这个实例里面是执行完事务就释放锁,另一个终端才开始获取到锁执行事务
第一个终端获取锁开始执行事务
第二个终端获取锁失败开始等待
第一个终端执行完成事务释放锁
第二个终端获取锁开始执行事务最后完成
字符串乐观锁
乐观锁:假定不会发生并发冲突,只在提交操作时检查是否违反数据完整性
- 实现机制:在修改数据时将事务锁起来,通过version方式进行锁定
- 实现方式:使用version版本或者时间戳
- 实现特点:适合多读写少,并发冲突少的场景
PHP+Redis实现乐观锁:利用redis中的watch($Key)方法(watch可以监视一个KEY在事务提交exec()时是否改变,若改变则exec()会失败),事务使用multi()开启和exec()批量提交
1 | //optimisticLock.php |
1 |
|
先试试无并发情况直接执行
可以看到版本号没改变而且事务执行设置ID成功,我们先删除所有键
可以看到ID键已经清空了,然后试试有并发情况,执行事务时另一个并发程序改变了乐观锁(Version)的值
可以看到若有并发程序改变了版本号,则事务并不能执行成功,ID键值设置失败了