我有一个问题,当我们从第三方(Twilio)启动REST资源时,服务响应如此之快,我们没有时间将SID写入数据库。我们不能告诉服务等待,因为它仅在服务启动时才返回SID。应用程序本身无法保持状态,因为无法保证RESTful回调将到达我们应用程序的同一实例。
我们通过将SID写入数据库中的缓冲表来缓解此问题,并且我们尝试了一些策略来强制Web响应等待,但是使用Thread.Sleep似乎阻止了其他不相关的Web响应,通常会在高峰负载时降低服务器速度。
在检查数据库时,如何优雅地要求Web响应暂停一分钟?最好不要用阻塞的线程来破坏整个服务器。
这是启动服务的代码:
currenText()
这是响应的代码:
private static void SendSMS(Shift_Offer so, Callout co,testdb2Entities5 db)
{
co.status = CalloutStatus.inprogress;
db.SaveChanges();
try
{
CallQueue cq = new CallQueue();
cq.offer_id = so.shift_offer_id;
cq.offer_finished = false;
string ShMessage = getNewShiftMessage(so, co, db);
so.offer_timestamp = DateTime.Now;
string ServiceSID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
var message = MessageResource.Create
(
body: ShMessage,
messagingServiceSid: ServiceSID,
to: new Twilio.Types.PhoneNumber(RCHStringHelpers.formatPhoneNumber(so.employee_phone_number)),
statusCallback: new Uri(TwilioCallBotController.SMSCallBackURL)
);
cq.twilio_sid = message.Sid;
db.CallQueues.Add(cq);
db.SaveChanges();
so.offer_status = ShiftOfferStatus.OfferInProgress;
so.status = message.Status.ToString();
so.twillio_sid = message.Sid;
db.SaveChanges();
}
catch (SqlException e) //if we run into any problems here, release the lock to prevent stalling;
//note to self - this should all be wrapped in a transaction and rolled back on error
{
Debug.WriteLine("Failure in CalloutManager.cs at method SendSMS: /n" +
"Callout Id: " + co.callout_id_pk + "/n"
+ "Shift Offer Id: " + so.shift_offer_id + "/n"
+ e.StackTrace);
ResetCalloutStatus(co, db);
ReleaseLock(co, db);
}
catch (Twilio.Exceptions.ApiException e)
{
ReleaseLock(co, db);
ResetCalloutStatus(co, db);
Debug.WriteLine(e.Message + "/n" + e.StackTrace);
}
}
这是延迟的代码:
public ActionResult TwilioSMSCallback()
{
//invalid operation exception occurring here
string sid = Request.Form["SmsSid"];
string status = Request.Form["SmsStatus"];
Shift_Offer shoffer;
CallQueue cq = null;
List<Shift_Offer> sho = db.Shift_Offers.Where(s => s.twillio_sid == sid).ToList();
List<CallQueue> cqi = getCallQueueItems(sid, db);
if (sho.Count > 0)
{
shoffer = sho.First();
if (cqi.Count > 0)
{
cq = cqi.First();
}
}
else
{
if (cqi.Count > 0)
{
cq = cqi.First();
shoffer = db.Shift_Offers.Where(x => x.shift_offer_id == cq.offer_id).ToList().First();
}
else
{
return new Twilio.AspNet.Mvc.HttpStatusCodeResult(HttpStatusCode.NoContent);
}
}
Callout co = db.Callouts.Where(s => s.callout_id_pk == shoffer.callout_id_fk).ToList().First();
shoffer.status = status;
if (status.Contains("accepted"))
{
shoffer.offer_timestamp = DateTime.Now;
shoffer.offer_status = ShiftOfferStatus.SMSAccepted + " " + DateTime.Now;
}
else if (status.Contains("queued") || status.Contains("sending"))
{
shoffer.offer_timestamp = DateTime.Now;
shoffer.offer_status = ShiftOfferStatus.SMSSent + " " + DateTime.Now;
}
else if (status.Contains("delivered") || status.Contains("sent"))
{
shoffer.offer_timestamp = DateTime.Now;
shoffer.offer_status = ShiftOfferStatus.SMSDelivered + " " + DateTime.Now;
setStatus(co);
if (cq != null){
cq.offer_finished = true;
}
CalloutManager.ReleaseLock(co, db);
}
else if (status.Contains("undelivered"))
{
shoffer.offer_status = ShiftOfferStatus.Failed + " " + DateTime.Now;
setStatus(co);
if (cq != null){
cq.offer_finished = true;
}
CalloutManager.ReleaseLock(co, db);
}
else if (status.Contains("failed"))
{
shoffer.offer_status = ShiftOfferStatus.Failed + " " + DateTime.Now;
setStatus(co);
if (cq != null){
cq.offer_finished = true;
}
cq.offer_finished = true;
CalloutManager.ReleaseLock(co, db);
}
db.SaveChanges();
return new Twilio.AspNet.Mvc.HttpStatusCodeResult(HttpStatusCode.OK);
}
答案 0 :(得分:3)
Good APIs™使消费者可以指定他们希望消息与之关联的ID。我自己从未使用过Twilio,但是现在我已经阅读了Creating a Message Resource的API参考,可悲的是,似乎他们没有为此提供参数。但是仍然有希望!
即使没有明确的参数,也许您可以为创建的每条消息指定略有不同的回调URL?假设您的CallQueue
实体具有唯一的Id
属性,则可以让每条消息的回调URL包含指定此ID的查询字符串参数。然后,您可以在不知道消息Sid的情况下处理回调。
要实现此目的,您将在SendSMS
方法中对事物进行重新排序,以便在调用Twilio API之前保存CallQueue
实体:
db.CallQueues.Add(cq);
db.SaveChanges();
string queryStringParameter = "?cq_id=" + cq.id;
string callbackUrl = TwilioCallBotController.SMSCallBackURL + queryStringParameter;
var message = MessageResource.Create
(
[...]
statusCallback: new Uri(callbackUrl)
);
您还将修改回调处理程序TwilioSMSCallback
,以便它通过其ID从CallQueue
查询字符串参数中检索其cq_id
实体。
某些云服务仅允许与预配置列表中的条目之一完全匹配的回调URL。对于此类服务,使用不同的回调URL的方法将不起作用。如果Twilio是这种情况,那么您应该可以使用以下思路解决您的问题。
与另一种方法相比,此方法需要对代码进行较大的更改,因此,我仅作简要说明,让您确定详细信息。
想法是即使TwilioSMSCallback
实体在数据库中尚不存在,也要使CallQueue
方法有效:
如果数据库中没有匹配的CallQueue
实体,TwilioSMSCallback
应该只将接收到的消息状态更新存储在新的实体类型MessageStatusUpdate
中,以便可以待会儿处理。
“稍后”位于SendSMS
的最后:您将在此处添加代码以获取和处理任何具有匹配MessageStatusUpdate
的未处理twilio_sid
实体。
实际处理消息状态更新(更新关联的Shift_Offer
等)的代码应从TwilioSMSCallback
移开,并放在单独的方法中,该方法也可以称为摘自SendSMS
末尾的新代码。
使用这种方法,您还必须引入某种锁定机制,以避免试图处理同一twilio_sid
的更新的多个线程/进程之间的竞争状况。
答案 1 :(得分:2)
您真的不应该延迟RESTful通话。将其分为两步,一个用于启动它,一个用于获取状态。您可以多次调用后者,直到操作安全完成为止,它是轻量级的,并且如果需要,还可以向呼叫者提供进度指示器或状态反馈。
答案 2 :(得分:-1)
异步/等待可以帮助您避免阻塞线程。
您可以尝试用int[] my_array = {1, 2, 5, 5, 6, 6, 7, 2};
for (int i = 0; i < my_array.length; i++) {
boolean dup = false;
for (int j = 0; j < my_array.length; j++) {
if (i != j && my_array[i] == my_array[j]) {
dup = true;
break;
}
}
if (! dup) {
System.out.println(my_array[i]);
}
}
代替1
7
更新
我看到您在await Task.Delay(...).ConfigureAwait(false)
中有一些长期运行的逻辑,并且我相信此回调应尽可能快地执行,因为它来自Twilio服务(可能会受到惩罚)。
我建议您将短信状态处理逻辑移至Thread.Sleep()
方法的末尾,并使用TwilioSMSCallback
在那里轮询数据库,直到获得短信状态为止。但是,这将使SendSMS
请求在调用方保持活动状态,因此最好有单独的服务,该服务将轮询数据库并在发生更改时调用您的api。