温州网站的优化,wordpress 注册邮箱验证失败,昌邑做网站,网站错误代码500背景 相信我们或多或少的会遇到类似下面这样的需求#xff1a;第三方给了一批数据给我们处理#xff0c;我们处理好之后就通知他们处理结果。大概就是下面这个图说的。本来在处理完数据之后#xff0c;我们就会马上把处理结果返回给对方#xff0c;但是对方要求我们处理速度… 背景 相信我们或多或少的会遇到类似下面这样的需求第三方给了一批数据给我们处理我们处理好之后就通知他们处理结果。大概就是下面这个图说的。本来在处理完数据之后我们就会马上把处理结果返回给对方但是对方要求我们处理速度不能过快要有一种人为处理的效果。换句话就是说就算是处理好了也要晚一点再执行通知操作。这就是一个典型的延时任务。延时那还不简单执行完之后让它Sleep一下就好了这样就达到目标了。Sleep一下确定是最容易实现的一种方案但是试想一下数据的数量不断的增加这样Sleep真的好吗答案是否定的。延时队列是处理这个场景最为妥当的方案。RabbitMQRocketMQCmq等都可以直接或间接的达到相应的效果。如果不具备队列条件又要怎么处理呢还可以借助Redis来完成这项工作。MQ不一定每个公司都会用但Redis应该80%以上的都会用吧。处理方案 Redis这边可用的方案有两种下面分别来介绍一下。#1 键的过期时间在设置缓存的时候我们比较多情况下都会设置一个缓存的过期时间这个时间过期后会重新去数据源拿数据回来。可以基于这个过期时间结合Redis的keyspace notifications共同完成。keyspace notifications里面包含了非常多的事件这里只关注EXPIRE这个是和过期有关的。只要订阅了__keyevent0__:expired这个主题当有key过期的时候就会收到对应的信息。主题后面的0指的是db 0.要想使用这个特性必不可少的一步是修改Redis默认的配置把notify-keyspace-events设置成Ex。############################# Event notification ############################### Redis can notify Pub/Sub clients about events happening in the key space.
# This feature is documented at http://redis.io/topics/notifications
#
# .........
#
# By default all notifications are disabled because most users dont need
# this feature and the feature has some overhead. Note that if you dont
# specify at least one of K or E, no events will be delivered.
notify-keyspace-events Ex
其中 E 指的是键事件通知x 指的是过期事件。根据这个特性重新调整一下流程图应该也比较好懂下面通过简单的代码来实现一下这种方案。首先是处理完数据及往Redis写数据。public async Task DoTaskAsync()
{// 数据处理// ...// 后续操作要延时把Id记录下来var taskId new Random().Next(1, 10000);// 要延迟的时间int sec new Random().Next(1, 5);// 可以加个重试机制预防单次执行失败。await RedisHelper.SetAsync($task:{taskId}, 1, sec);
}
还需要回传结果的后台任务这个任务就是去订阅上面说的键过期事件然后回传结果。这里可以借助BackgroundService来订阅处理。public class SubscribeTaskBgTask : BackgroundService
{protected override Task ExecuteAsync(CancellationToken stoppingToken){stoppingToken.ThrowIfCancellationRequested();var keyPrefix task:;RedisHelper.Subscribe((__keyevent0__:expired, arg {var msg arg.Body;Console.WriteLine($recive {msg});if (msg.StartsWith(keyPrefix)){// 取到任务Idvar val msg.Substring(keyPrefix.Length);Console.WriteLine(${DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss)} begin to do task {val});// 回传处理结果给第三方这里可以考虑这个并发锁避免多实例都处理了这个任务。// ....}}));return Task.CompletedTask;}
}
这里有一个要注意的地方要在key里面包含任务的Id因为订阅处理的时候只能拿到一个key后续能做的操作也只是基于这个key。上面的例子是用了task:任务Id的形式所以在订阅处理的时候只处理以task:开头的那些key。效果如下这种方案直观上是非常简单的不过这种方案会遇到一个小问题。当一个key过期后并不一定会马上收到通知这个也是会有一定的延时的取决于Redis的内部机制。Redis Keyspace Notifications文档的最后一段也提到了这个问题。所以用这种方案的时候要考虑一下你的延时是不是要及时~~#2 有序集合有序集合是Redis中一种十分有用的数据结构它的本质其实就是集合加了一个排序的功能每个集合里面的元素还会有一个分值的属性。它提供了一个可以获取指定分值范围内的元素这个也就是我们的出发点。在这个场景下什么东西可能作为这个分值呢现在只有一个处理任务的Id还有一个延迟的时间Id肯定不行那么也只能是延迟时间来作这个分值了。延迟1秒5秒1分钟这个都是比较大粒度的时间这里要转化一下用时间戳来代替这些延迟的时间。假设现在的时间戳是 1584171520, 要延迟5秒执行那么执行任务的时间就是 1584171525在当前时间戳的基础上加个5秒就是最终要执行的了。到时有序集合中存的元素就会是这样的任务Id-1 1584171525
任务Id-2 1584171528
任务Id-3 1584171530
接下来就是要怎么取出这些任务的问题了把当前时间戳当成是取数的最大分值0作为最小分值这个时候取出的元素就是应该要执行回传的任务了。根据这个方案重新调整一下流程图交代清楚了思路再来点代码加深一下理解。首先还是处理完数据后往Redis写数据。public async Task DoTaskAsync()
{// 数据处理// ...// 后续操作要延时把Id记录下来var taskId new Random().Next(1, 10000);var cacheKey task:delay;int sec new Random().Next(1, 5);// 要执行这个任务的时间戳var time DateTimeOffset.Now.AddSeconds(sec).ToUnixTimeSeconds();await RedisHelper.ZAddAsync(cacheKey, (time, taskId));Console.WriteLine(${DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss)} done {taskId} here - {sec});
}
后面就是轮训有序集合里面的元素了这里同样是借助BackgroundService来处理。public class SubscribeTaskBgTask : BackgroundService
{protected override async Task ExecuteAsync(CancellationToken stoppingToken){stoppingToken.ThrowIfCancellationRequested();var cacheKey task:delay;while (true){// 先取后删不具备原子性可考虑用lua脚本来保证原子性。var vals await RedisHelper.ZRangeByScoreAsync(cacheKey, -1, DateTimeOffset.Now.ToUnixTimeSeconds(), 1, 0);if (vals ! null vals.Length 0){var val vals[0];var rmCount await RedisHelper.ZRemAsync(cacheKey, vals);if (rmCount 0){// 要把这个元素先删除成功了再执行任务不然会重复Console.WriteLine(${DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss)} begin to do task {val});// 回传处理结果给第三方这里可以考虑这个并发锁避免多实例都处理了这个任务。// ....}}else{// 没有数据休眠500ms避免CPU空转await Task.Delay(500);}}}
}
效果如下参考文章 https://redis.io/topics/notificationshttps://zhuanlan.zhihu.com/p/87113913