订单超时,活动过期解决方案:php监听redis键重复触发引发事件
Redis的280版本之后可用,键空间消息(Redis Keyspace Notifications),配合200版本之后的SUBSCRIBE 可以完成这个定时任务的 *** 作了,定时的单位是秒。
1我们先订阅频道称为 redisChat
2现在,我们重新开启个redis客户端,然后在同一个频道redisChat发布消息,订阅者可以接收到消息。
接收到的消息如下:
3Key过期事件的Redis配置
需要这里配置notify-keyspace-events的参数为“EX” X代表了过期事件。notify-keyspace-events “Ex”保存配置后,重启Redis的服务,使配置生效。
PHP Redis实现订阅键空间通知
redis实例化类:
redisclassphp
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
//遇到类别重复的报错,所有叫Redis2
classRedis2
{
private$redis;
publicfunction__construct($host= '127001', $port= 6379)
{
$this->redis = newRedis();
$this->redis->connect($host, $port);
}
publicfunctionsetex($key, $time, $val)
{
return$this->redis->setex($key, $time, $val);
}
publicfunctionset($key, $val)
{
return$this->redis->set($key, $val);
}
publicfunctionget($key)
{
return$this->redis->get($key);
}
publicfunctionexpire($key= null, $time= 0)
{
return$this->redis->expire($key, $time);
}
publicfunctionpsubscribe($patterns= array(), $callback)
{
$this->redis->psubscribe($patterns, $callback);
}
publicfunctionsetOption()
{
$this->redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
}
}
过期事件的订阅:
psubscribephp
1个
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require_once'/Redisclassphp';
$redis= new\Redis2();
// 解决Redis客户端订阅时候超时情况
$redis->setOption();
$redis->psubscribe(array('__keyevent@0__:expired'), 'keyCallback');
// 回调函数,这里写处理逻辑
functionkeyCallback($redis, $pattern, $chan, $msg)
{
echo"Pattern: $pattern\n";
echo"Channel: $chan\n";
echo"Payl
oad: $msg\n\n";
//keyCallback为订阅事件后的回调函数,这里写业务处理逻辑,
//比如前面提到的商品不支付自动撤单,这里就可以根据订单id,来实现自动撤单
}
设置过期事件:
indexphp
1个
2
3
4
require_once'/Redisclassphp';
$redis= new\Redis2();
$order_id= 123;
$redis->setex('order_id',10,$order_id);
先用命令行模式执行 psubscribephp
在浏览器访问 indexphp
效果如下:
队列是一种线性表,内部的元素是有序的,具有先进先出的特性。
延时队列,顾名思义,它是一个队列,但更重要的是具有延时的特性,与普通队列的先进先出不同,延时队列可以指定队列中的消息在某个时间点被消费。
DelayQueue是无界的延时阻塞队列,内部是使用优先级队列PriorityQueue实现的,其是按时间来定优先级的延时阻塞队列,只有在延迟期满时才能从队列中提取元素,先过期的元素会在队首,每次从队列里取出来都是最先要过期的元素, 当执行队列take *** 作元素未过期时会阻塞当前线程到元素过期为止 ;PriorityQueue是通过二叉小顶堆实现, 其任意一个非叶子节点的权值,都不大于其左右子节点的权值。
示例
队列中的元素必须实现Delayed接口
redis key的过期事件是通过redis 280之后版本提供的订阅发布功能(pub/sub)下发的,当key过期后系统自动Pub,应用程序只需订阅(sub)该事件即可。
实现步骤
示例
存在的问题
key的失效通知无法保证时效性。redis过期策略有一下三种:
默认情况下,Redis 使用的是 惰性删除 + 定期删除 的策略;每隔一段时间(可通过hz参数设置每秒执行的次数),Redis 会分别从各个库随机选取部分测试设置了过期时间的 Key,判断它们是否过期,过期则删除;如果 key 已过期,但没有被定期删除,由于惰性删除策略,在下次请求获取该数据时会将该数据删除。
可通过如下方式提高时效性
redis zset 结构是一个有序集合,每个元素都会关联一个 double 类型的分数,通过分数来为集合中的成员进行从小到大的排序;有序集合的成员是唯一的,但分数(score)却可以重复。
实现思路
将任务id作为member,到期时间作为score存入到zset中,然后不断轮询获取第一个元素,判断其是否过期,过期后删除并执行任务即可。
也可以通过lua脚本将 zrangebyscore 和 zrem *** 作变成原子 *** 作,避免了多线程时同一个me mber多次zrem。
存在的问题
RabbitMQ本身没有直接支持延迟队列功能,但是可以通过ttl及dlx(Dead Letter Exchanges)特性模拟出延迟队列的功能。
绑定在死信交换机上的队列。RabbitMQ的Queue(队列)可以配置两个参数x-dead-letter-exchange(死信交换机)和x-dead-letter-routing-key(指定routing-key发送,可选),当消息在一个队列中变成死信 (dead message) 之后,按照这两个参数可以将消息重新路由到另一个DLX Exchange(死信交换机),让消息重新被消费。
队列出现Dead Letter的情况有:
RabbitMQ可以对消息和队列设置TTL,为队列设置时,队列中所有消息都有相同的过期时间;对消息进行单独设置,每条消息过期时间可以不同;如果同时设置了队列的ttl和消息的ttl以两者之间TTL较小的那个数值为准。消息超过设置的ttl值未被消费,将会变为死信,消费者将无法再收到该消息。
ttl消息按照入发送顺序排列在队列中,且rabbitMQ只会判断队列头消息是否失效,失效后才会加入到死信队列中,如果发送多个过期时间不一致的消息,有可能后面的消息已经过期了,但队列头消息没有过期,导致其他消息不能及时加入到死信队列被消费。
针对上述的问题,可以使用 rabbitmq_delayed_message_exchang 插件来解决。
安装该插件后会生成新的Exchange类型 x-delayed-message ,该类型消息支持延迟投递机制,接收到消息后并未立即将消息投递至目标队列中,而是存储在 mnesia (一个分布式数据系统)表中,检测消息延迟时间(通过消息头的x-delay指定),如达到可投递时间时并将其通过 x-delayed-type 类型标记的交换机类型投递至目标队列。
插件的安装
使用示例
插件的局限
时间轮的应用广泛,包括linux内核的调度、zookeeper、netty、kafka、xxl-job、quartz等均有使用时间轮。
图中的圆盘可以看作是钟表的刻度。比如一圈round长度为24秒,刻度数为8,那么每一个刻度表示3秒。那么时间精度就是3秒。每个刻度为一个bucket(实际上就是TimerTaskList),TimerTaskList是环形双向链表,在其中链表项TimeTaskEntry封装了真正的定时任务TimerTask。TimerTaskList使用expiration字段记录了整个TimerTaskList的超时时间。TimeTaskEntry中的expirationMs字段记录了超时时间戳,timerTask字段指向了对应的TimerTask任务;根据每个TimerTaskEntry的过期时间和当前时间轮的时间,选择一个合适的bucket,把这个TimerTaskEntry对象放进去;对于延迟超过时间轮所能表示的范围有两种处理方式,一是通过增加一个字段-轮数,Netty 就是这样实现的;二是多层时间轮,Kakfa 是这样实现的。
下面介绍下kafka的多层时间轮,层数越高时间跨度越大。
每个使用到的TimerTaskList都会加入到DelayQueue中,DelayQueue会根据TimerTaskList对应的超时时间expiration来排序,最短expiration的TimerTaskList会被排在DelayQueue的队头,通过一个线程获取到DelayQueue中的超时的任务列表TimerTaskList之后,既可以根据TimerTaskList的expiration来推进时间轮的时间,也可以就获取到的TimerTaskList执行相应的 *** 作,TimerTaskEntry该执行过期 *** 作的就执行过期 *** 作,该降级时间轮的就降级时间轮。
假设现在有一个任务在445ms后执行,默认情况下,各个层级的时间轮的时间格个数为20,第一层时间轮每一个时间格跨度为1ms,整个时间轮跨度为20ms,跨度不够。第二层时间轮每一个时间格跨度为20ms,整个时间轮跨度为400ms,跨度依然不够,第三层时间轮每一个时间格跨度为400ms,整个时间轮跨度为8000ms,现在跨度够了,此任务就放在第三层时间轮的第一个时间格对应的TimerTaskList,等待被执行,此TimerTaskList到期时间是400ms,随着时间的流逝,当此TimerTaskList到期时,距离该任务到期时间还有45ms,不能执行该任务,将重新提交到时间轮,此时第一层时间轮跨度依然不够,不能执行任务,第二层时间轮时间格跨度为20,整个世间轮跨度为400,跨度足够,放在第三个时间格等待执行,如此往复几次,高层时间轮最终会慢慢移动到低层时间轮上,最终任务到期执行。
//向redis里存入数据和设置缓存时间
//val做-1 *** 作
//根据key获取缓存中的val
//val +1
//根据key获取过期时间
//根据key获取过期时间并换算成指定单位
//根据key删除缓存
//检查key是否存在,返回boolean值
//向指定key中存放set集合
//设置过期时间
//根据key查看集合中是否存在指定数据
//根据key获取set集合
//验证有效时间
memcached 和 redis 的set命令都有expire参数,可以设置key的过期时间。但是redis是一个可以对数据持久化的key-value database,它的key过期策略还是和memcached有所不同的。
redis通过expire命令来设置key的过期时间。
语法:redisexpire(key, expiration)
1 在小于213的redis版本里,只能对key设置一次expire。redis213和之后的版本里,可以多次对key使用expire命令,更新key的expire time。
2 redis术语里面,把设置了expire time的key 叫做:volatile keys。 意思就是不稳定的key。
3 如果对key使用set或del命令,那么也会移除expire time。尤其是set命令,这个在编写程序的时候需要注意一下。
4 redis213之前的老版本里,如果对volatile keys 做相关写入 *** 作(LPUSH,LSET),和其他一些触发修改value的 *** 作时,redis会删除 该key。 也就是说 :
redisexpire(key,expiration);
redislpush(key,field,value);
redisget(key) //return null
redis213之后的版本里面没有这个约束,可以任意修改。
redisset(key,100);
redisexpire(key,expiration);
redisincr(key)
redisget(key)
//redis222 return 101; redis<213 return 1;
5 redis对过期键采用了lazy expiration:在访问key的时候判定key是否过期,如果过期,则进行过期处理。其次,每秒对volatile keys 进行抽样测试,如果有过期键,那么对所有过期key进行处理。
Redis 中数据过期策略采用定期删除+惰性删除策略。
定期删除策略:Redis 启用一个定时器定时监视所有的 key,判断key是否过期,过期的话就删除。这种策略可以保证过期的 key 最终都会被删除,但是也存在严重的缺点:每次都遍历内存中所有的数据,非常消耗 CPU 资源,并且当 key 已过期,但是定时器还处于未唤起状态,这段时间内 key 仍然可以用。
惰性删除策略:在获取 key 时,先判断 key 是否过期,如果过期则删除。这种方式存在一个缺点:如果这个 key 一直未被使用,那么它一直在内存中,其实它已经过期了,会浪费大量的空间。
你访问数据之前会检查数据是否过期,过期的话就会回收,保证你不会读取到过期的数据。
redis里会有一个遍历器不停的遍历所有的key发现过期后就会回收内存。
内存不足时会加大过期数据回收的力度。
EXPIRE key second
设置超时 key 。超时过期后,key将自动删除。在Redis术语中,通常认为具有超时的key是 不稳定的 。
只有删除或覆盖key内容的命令才能清除超时,包括 DEL , SET , GETSET 和所有 STORE 命令。这意味着 改变 存储在key中的值而不用新key替换它的所有 *** 作将使超时保持不变。举例来说,增加一键的值 INCR ,往列表推新值的,包括 LPUSH ,或改变了哈希的字段值 HSET ,都不会改变超时。
也可以清除超时,使用 PERSIST 命令将key重新转换为持久key。
如果使用 RENAME 重命名key,则相关的生存时间将转移到新key名称。
如果一个key被 RENAME 覆盖,比如,已经存在key Key_A ,被如下命令所覆盖, RENAME Key_B Key_A ,新key Key_A 将继承所有的 Key_B 的属性,与原始 Key_A 否有超时没有关系。
请注意,使用非正数的超时时间调用 EXPIRE / PEXPIRE 或 使用一个过去时间调用 EXPIREAT / PEXPIREAT ,将导致键被 删除 而不是过期(因此,发出的 键事件 将是 del ,而不是 expired )。
可以使用已经设置超时的key作为参数调用 EXPIRE 。在这种情况下,key的生存时间将 更新 为新值。有许多有用的应用程序,下面的 导航会话 模式部分中记录了一个示例 。
在之前的Redis 213版本中 ,使用命令更改key来使用超时设置更改key,具有完全删除key的效果。由于复制层中的限制现在已得到修复,因此需要使用此语义。
EXPIRE 将返回0并且不会更改具有超时设置的key的超时。
整数回复 ,具体为:
假如你有一个web服务,你需要记录用户最近浏览的N个页面,每个相邻页面的浏览时间不超过60s。
你可以用redis轻松实现,每次用户打开一个页面,执行如下命令:
如果用户60s没有 *** 作,页面就会被删除,只有60s以内的页面会被记录。
通常,创建Redis key时没有相关的过期时间。key只会永远存在,除非用户以明确的方式将其 删除 ,例如使用 DEL 命令。
EXPIRE 家族命令能够给key设置超时时间,但是会使用一些额外的内存成本。当key设置了过期时,Redis将确保在指定的时间过后删除key。
可以使用 EXPIRE 和 PERSIST 命令(或其他严格相关的命令)更新或完全删除key生存时间。
在Redis 24中,到期可能不精确,误差可能在0到1秒之间。
从Redis 26开始,到期误差从0到1毫秒。
Key到期信息存储为绝对Unix时间戳(在Redis 26或更高版本的情况下以毫秒为单位)。这意味着即使Redis实例未处于活动状态,时间也在流动。
为了使到期效果良好,计算机时间必须稳定。如果您从两台计时器中移动RDB文件并在其时钟中使用大型desync,则可能会发生有趣的事情(例如加载时加载的所有键都将在加载时过期)。
即使运行实例也会检查计算机时钟,例如,如果您设置的key的生存时间为1000秒,然后将计算机时间设置为当前时间向后2000秒,key将立即过期,而不是持续1000秒。
Redis key以两种方式过期:被动方式和主动方式。
当某个客户端尝试访问key时,如果发现key超时,key被动过期。
当然这还不够,因为有过期的key永远不会被再次访问。这些key无论如何都应该过期,所以周期性地Redis会在具有过期设置的key中随机测试几个key。已经过期的所有key都将从key空间中删除。
具体来说,这就是Redis每秒做10次的事情:
这是一个简单的概率算法,基本上假设我们的样本代表整个key空间,我们继续到期,直到可能过期的key百分比低于25%
这意味着在任何给定时刻,使用内存的已经过期的最大key数量最大等于每秒最大写入 *** 作量除以4。
为了在不牺牲一致性的情况下获得正确的行为,当key到期时,在AOF文件中合成 DEL *** 作并通知所有副本节点。这样,到期过程集中在主实例中,并且不存在一致性错误。
但是,连接到主服务器的副本节点不会单独地使key过期,(但会等待来自主服务器的 DEL ),但它们仍将采用数据集中存在的过期的完整状态,因此当副本被选为主服务器时它将能够独立地使密钥到期,充分充当主人。
以上就是关于订单超时,活动过期解决方案:php监听redis键重复触发引发事件全部的内容,包括:订单超时,活动过期解决方案:php监听redis键重复触发引发事件、延时队列常用实现详解、redis设置过期时间等等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)