任务队列和异步接口的正确打开方式(.NET Core版本)
任务队列和异步接口的正确打开方式
什么是异步接口?
Asynchronous Operations
Certain types of operations might require processing of the request in an asynchronous manner (e.g. validating a bank account, processing an image, etc.) in order to avoid long delays on the client side and prevent long-standing open client connections waiting for the operations to complete. For such use cases, APIs MUST employ the following pattern:
For POST requests:
Return the
202 AcceptedHTTP response code.In the response body, include one or more URIs as hypermedia links, which could include:
- The final URI of the resource where it will be available in future if the ID and path are already known. Clients can then make an HTTP
GETrequest to that URI in order to obtain the completed resource. Until the resource is ready, the final URI SHOULD return the HTTP status code404 Not Found.
{ "rel": "self", "href": "/v1/namespace/resources/{resource_id}", "method": "GET" }- A temporary request queue URI where the status of the operation may be obtained via some temporary identifier. Clients SHOULD make an HTTP
GETrequest to obtain the status of the operation which MAY include such information as completion state, ETA, and final URI once it is completed.
{ "rel": "self", "href": "/v1/queue/requests/{request_id}, "method": "GET" }"- The final URI of the resource where it will be available in future if the ID and path are already known. Clients can then make an HTTP
For PUT/PATCH/DELETE/GET requests:
Like POST, you can support PUT/PATCH/DELETE/GET to be asynchronous. The behaviour would be as follows:
Return the
202 AcceptedHTTP response code.In the response body, include one or more URIs as hypermedia links, which could include:
- A temporary request queue URI where the status of the operation may be obtained via some temporary identifier. Clients SHOULD make an HTTP
GETrequest to obtain the status of the operation which MAY include such information as completion state, ETA, and final URI once it is completed.
{ "rel": "self", "href": "/v1/queue/requests/{request_id}, "method": "GET" }"- A temporary request queue URI where the status of the operation may be obtained via some temporary identifier. Clients SHOULD make an HTTP
APIs that support both synchronous and asynchronous processing for an URI:
APIs that support both synchronous and asynchronous operations for a particular URI and an HTTP method combination, MUST recognize the Prefer header and exhibit following behavior:
- If the request contains a
Prefer=respond-asyncheader, the service MUST switch the processing to asynchronous mode. - If the request doesn't contain a
Prefer=respond-asyncheader, the service MUST process the request synchronously.
It is desirable that all APIs that implement asynchronous processing, also support webhooks as a mechanism of pushing the processing status to the client.
资料引自:paypal/API Design Patterns And Use Cases:asynchronous-operations
用人话来说
简单来说就是请求过来,直接返回对应的resourceId/request_id,然后可以通过resourceId/request_id查询处理结果
处理过程可能是队列,也可能直接是异步操作
如果还没完成处理,返回404,如果处理完成,正常返回对应数据
好像也没什么讲了....
全文结束吧.
样例代码部分啦
实现逻辑
创建任务,生成"request-id"存储到对应redis zset队列中
同时往redis channel发出任务消息, 后台任务处理服务自行处理此消息(生产者-消费者模式)
任务处理服务处理完消息之后,将处理结果写入redis,request-id为key,结果为value,然后从从redis zset从移除对应的"request-id"
获取request-id处理结果时:如果request-id能查询到对应的任务处理结果,直接返回处理完的数据; 如果request-id还在sortset队列则直接返回404 + 对应的位置n,表示还在处理中,前面还有n个请求;
时序图大概长这样:

喜闻乐见代码时间
RequestService.cs
// RequestService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CorrelationId;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using StackExchange.Redis;
using static StackExchange.Redis.RedisChannel;
namespace MTQueue.Service
{
public class RequestService
{
private readonly ICorrelationContextAccessor _correlationContext;
private readonly ConnectionMultiplexer _redisMultiplexer;
private readonly IServiceProvider _services;
private readonly ILogger<RequestService> _logger;
public RequestService(ICorrelationContextAccessor correlationContext,
ConnectionMultiplexer redisMultiplexer, IServiceProvider services,
ILogger<RequestService> logger)
{
_correlationContext = correlationContext;
_redisMultiplexer = redisMultiplexer;
_services = services;
_logger = logger;
}
public long? AddRequest(JToken data)
{
var requestId = _correlationContext.CorrelationContext.CorrelationId;
var redisDB = _redisMultiplexer.GetDatabase(CommonConst.DEFAULT_DB);
var index = redisDB.SortedSetRank(CommonConst.REQUESTS_SORT_SETKEY, requestId);
if (index == null)
{
data["requestId"] = requestId;
redisDB.SortedSetAdd(CommonConst.REQUESTS_SORT_SETKEY, requestId, GetTotalSeconds());
PushRedisMessage(data.ToString());
}
return redisDB.SortedSetRank(CommonConst.REQUESTS_SORT_SETKEY, requestId);
}
public static long GetTotalSeconds()
{
return (long)(DateTime.Now.ToLocalTime() - new DateTime(1970, 1, 1).ToLocalTime()).TotalSeconds;
}
private void PushRedisMessage(string message)
{
Task.Run(() =>
{
try
{
using (var scope = _services.CreateScope())
{
var multiplexer = scope.ServiceProvider.GetRequiredService<ConnectionMultiplexer>();
multiplexer.GetSubscriber().PublishAsync(CommonConst.REQUEST_CHANNEL, message);
}
}
catch (Exception ex)
{
_logger.LogError(-1, ex, message);
}
});
}
public Tuple<JToken, long?> GetRequest(string requestId)
{
var redisDB = _redisMultiplexer.GetDatabase(CommonConst.DEFAULT_DB);
var keyIndex = redisDB.SortedSetRank(CommonConst.REQUESTS_SORT_SETKEY, requestId);
var response = redisDB.StringGet(requestId);
if (response.IsNull)
{
return Tuple.Create<JToken, long?>(default(JToken), keyIndex);
}
return Tuple.Create<JToken, long?>(JToken.Parse(response), keyIndex);
}
}
}
// RedisMQListener.cs
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MTQueue.Model;
using MTQueue.Service;
using Newtonsoft.Json.Linq;
using StackExchange.Redis;
using static StackExchange.Redis.RedisChannel;
namespace MTQueue.Listener
{
public class RedisMQListener : IHostedService
{
private readonly ConnectionMultiplexer _redisMultiplexer;
private readonly IServiceProvider _services;
private readonly ILogger<RedisMQListener> _logger;
public RedisMQListener(IServiceProvider services, ConnectionMultiplexer redisMultiplexer,
ILogger<RedisMQListener> logger)
{
_services = services;
_redisMultiplexer = redisMultiplexer;
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
Register();
return Task.CompletedTask;
}
public virtual bool Process(RedisChannel ch, RedisValue message)
{
_logger.LogInformation("Process start,message: " + message);
var redisDB = _services.GetRequiredService<ConnectionMultiplexer>()
.GetDatabase(CommonConst.DEFAULT_DB);
var messageJson = JToken.Parse(message);
var requestId = messageJson["requestId"]?.ToString();
if (string.IsNullOrEmpty(requestId))
{
_logger.LogWarning("requestId not in message.");
return false;
}
var mtAgent = _services.GetRequiredService<ZhihuClient>();
var text = mtAgent.GetZhuanlan(messageJson);
redisDB.StringSet(requestId, text.ToString(), CommonConst.RESPONSE_TS);
_logger.LogInformation("Process finish,requestId:" + requestId);
redisDB.SortedSetRemove(CommonConst.REQUESTS_SORT_SETKEY, requestId);
return true;
}
public void Register()
{
var sub = _redisMultiplexer.GetSubscriber();
var channel = CommonConst.REQUEST_CHANNEL;
sub.SubscribeAsync(channel, (ch, value) =>
{
Process(ch, value);
});
}
public void DeRegister()
{
// this.connection.Close();
}
public Task StopAsync(CancellationToken cancellationToken)
{
// this.connection.Close();
return Task.CompletedTask;
}
}
}
// RequestsController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CorrelationId;
using Microsoft.AspNetCore.Mvc;
using MTQueue.Service;
using Newtonsoft.Json.Linq;
namespace MTQueue.Controllers
{
[Route("v1/[controller]")]
[ApiController]
public class RequestsController : ControllerBase
{
private readonly ICorrelationContextAccessor _correlationContext;
private readonly RequestService _requestService;
private readonly ZhihuClient _mtAgentClient;
public RequestsController(ICorrelationContextAccessor correlationContext,
RequestService requestService, ZhihuClient mtAgentClient)
{
_correlationContext = correlationContext;
_requestService = requestService;
_mtAgentClient = mtAgentClient;
}
[HttpGet("{requestId}")]
public IActionResult Get(string requestId)
{
var result = _requestService.GetRequest(requestId);
var resource = $"/v1/requests/{requestId}";
if (result.Item1 == default(JToken))
{
return NotFound(new { rel = "self", href = resource, method = "GET", index = result.Item2 });
}
return Ok(result.Item1);
}
[HttpPost]
public IActionResult Post([FromBody] JToken data, [FromHeader(Name = "Prefer")]string prefer)
{
if (!string.IsNullOrEmpty(prefer) && prefer == "respond-async")
{
var index = _requestService.AddRequest(data);
var requestId = _correlationContext.CorrelationContext.CorrelationId;
var resource = $"/v1/requests/{requestId}";
return Accepted(resource, new { rel = "self", href = resource, method = "GET", index = index });
}
return Ok(_mtAgentClient.GetZhuanlan(data));
}
}
}
完整代码见:https://github.com/liguobao/TaskQueueSample
任务队列和异步接口的正确打开方式(.NET Core版本)的更多相关文章
- C++11随机数的正确打开方式
C++11随机数的正确打开方式 在C++11之前,现有的随机数函数都存在一个问题:在利用循环多次获取随机数时,如果程序运行过快或者使用了多线程等方法,srand((unsigned)time(null ...
- iOS开发小技巧--相机相册的正确打开方式
iOS相机相册的正确打开方式- UIImagePickerController 通过指定sourceType来实现打开相册还是相机 UIImagePickerControllerSourceTypeP ...
- Xcode 的正确打开方式——Debugging(转载)
Xcode 的正确打开方式——Debugging 程序员日常开发中有大量时间都会花费在 debug 上,从事 iOS 开发不可避免地需要使用 Xcode.这篇博客就主要介绍了 Xcode 中几种能 ...
- C#语法——泛型的多种应用 C#语法——await与async的正确打开方式 C#线程安全使用(五) C#语法——元组类型 好好耕耘 redis和memcached的区别
C#语法——泛型的多种应用 本篇文章主要介绍泛型的应用. 泛型是.NET Framework 2.0 版类库就已经提供的语法,主要用于提高代码的可重用性.类型安全性和效率. 泛型的定义 下面定义了 ...
- InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式
InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式 https://mp.weixin.qq.com/s/HGa_90XvC22anabiBF8AbQ 在这篇文章里,我将讨论在MySQL 5 ...
- Console控制台的正确打开方式
Console控制台的正确打开方式 console对象提供了访问浏览器调试模式的信息到控制台 -- Console对象 |-- assert() 如果第一个参数断言为false,则在控制台输出错误信息 ...
- (一)Redis for Windows正确打开方式
目录 (一)Redis for Windows正确打开方式 (二)Redis for 阿里云公网连接 (三)Redis for StackExchange.Redis 下载地址 官网.中文网1 及 中 ...
- List的remove()方法的三种正确打开方式
转: java编程:List的remove()方法的三种正确打开方式! 2018年08月12日 16:26:13 Aries9986 阅读数 2728更多 分类专栏: leetcode刷题 版权声 ...
- Pull Request的正确打开方式(如何在GitHub上贡献开源项目)
Pull Request的正确打开方式(如何在GitHub上贡献开源项目) GitHub的官方帮助如下: Fork A Repo: https://help.github.com/articles/f ...
随机推荐
- gluoncv faster_rcnn 参数修改
https://github.com/dmlc/gluon-cv/blob/master/gluoncv/model_zoo/faster_rcnn/faster_rcnn.py 对你选用的模块,修改 ...
- 「NOIP2018 保卫王国」
题目 强制选点我们可以把那个点权搞成\(-inf\),强制不选我们搞成\(inf\),之后就真的成为动态\(dp\)的板子题了 由于不想像板子那样再写一个最大独立集的方程,之后利用最小点覆盖=总点权- ...
- 【[HEOI2014]大工程 】
可能是虚树板子题了 首先先把虚树建出来,但是这里和那道虚树的入门题不一样,这里所有的询问点都得在虚树里,所以不会存在那种直接不如栈的点 之后我们考虑一下这个三个要求的东西 第一个操作我们需要统计虚树上 ...
- 【[JSOI2009]火星藏宝图】
这里是\(sb\)的\(O(nm)\)做法 上一篇题解里写的\(O(nm)\)做法并没有看懂,我真是好菜啊 这是一个用了斜率优化,但是复杂度仍然是\(O(nm)\)的做法 我们还是先写出简单的\(dp ...
- oracle数据库之用户管理
转载 Oracle创建用户.角色.授权.建表 一.oracle数据库的权限系统分为系统权限与对象权限: 系统权限( database system privilege )可以让用户执行特定的命令集 ...
- Jquery mobile 自定义 返回按钮 data-rel="back"
data-rel="back" 第一个页面 主页面 studentmaster.html 通过下面js脚本跳转到详情页面 window.location.href="s ...
- RedHat(小红帽)下 yum用不了的解决办法
由于RedHat是商业版的,通常由于没有注册,导致yum程序无法使用(linux下面,yum是个安装软件的“神器”).此时可用CentOS的地址进行替换.下面将一步步说明如何处理: 一.删除RedHa ...
- Linux中PATH环境变量的作用和使用方法
关于PATH的作用:PATH说简单点就是一个字符串变量,当输入命令的时候LINUX会去查找PATH里面记录的路径.比如在根目录/下可以输入命令ls,在/usr目录下也可以输入ls,但其实ls这个命令根 ...
- 基于maven的JavaWeb项目构建部署
需要准备的安装文件: 1 JDk http://www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.html jdk-8 ...
- Notes 20180309 : String第一讲_char的可读序列
实际上在写本文之前,我曾考虑是先探讨面向对象,还是先选择String和Arrays,最后还是选择了后者,并非是面向对象对我们不重要,相反它是Java的灵魂所在,之所以这样的安排是因为这两个是在是我们程 ...