1.SEMQ流量控制(已修改trunk 2014-7-22)
DAS测试过程中,5.17下午开始就没有新的单据导入了,tb_0031在此后没有新的记录生成。检查日志:发现 没有可用数据库连接。
[2014-05-17 23:59:41:685][线程6152][1][20][0][][33]连接id=33,使用状态=已使用,取用时间=66986(秒),拥有者线程=5384.
[2014-05-17 23:59:41:685][线程6152][1][20][0][][34]连接id=34,使用状态=已使用,取用时间=66986(秒),拥有者线程=4028.
检查占用连接的线程(如4028),看为什么没有释放。
int CBBoxPlugin::OnRespInquiry(CWrappedMsg<> *in,vector<CWrappedMsg<> *> &out,DISPATCH_RESULT &or) {
if (ack==0)
result = semq_.Resend(ref_object_id);
4028线程占用了数据库连接.
从日志看,该线程阻塞在semq_.Resend调用上。
////////////////////////////////////////////////////////////////////////////////
int CSEMQ::SendRecord(CQQ_OBJECT_ID object_id) {
CSendTaskItem *sti = new CSendTaskItem;
sti->action_ = 1;
sti->type_ = 1;
sti->object_id_ = object_id;
ACE_Message_Block *mb = 0;
ACE_NEW_RETURN(mb,ACE_Message_Block(sizeof(CSendTaskItem*)),-1);
memcpy(mb->rd_ptr(),&sti,sizeof(CSendTaskItem*));
pre_send_task_.putq(mb);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
int CSEMQ::Resend(CQQ_OBJECT_ID object_id) {
string sql = LogMsg("update %s set f009n_0031=0 where object_id=%I64d",this->GetTableName(),object_id);
USEDBC(pdbor,this->dbc_name_.c_str());
int ret = pdbor->Execute(adCmdText,sql.c_str()) ? 0 : -1;
if (ret==0) {
SendRecord(object_id);
}
return ret;
}
////////////////////////////////////////////////////////////////////////////////
int CPreSendTask::handle_message_block(ACE_Message_Block *mb) {
CSendTaskItem *sti = 0;
memcpy(&sti, mb->rd_ptr(), sizeof(CSendTaskItem*));
mb->rd_ptr(sizeof(CSendTaskItem*));
if (sti->action_==1) {
string sql;
if (sti->type_==1) {//////< 直接根据object_id发送
sql = LogMsg("select object_id,dest_type,dest_id,f018c_0031,f005n_0031,f019n_0031,f023v_0031 from %s where object_id=%I64d and f009n_0031=0 order by object_id",semq_->GetTableName(),sti->object_id_);
}
else {
sql = sti->sql_;
}
///< @note semq_.GetData必须成功.
short report_flag = 0;
while(semq_->GetData(sql)) { ///< GetData失败只有在数据库故障,或与数据库服务器通信的网络故障时才发生
///< 此时间检查数据库服务是否有效
short db_svc_flag = 0; ///< 数据库服务是否有效
do {
USEDBC(pdbor,semq_->GetDBCName());
db_svc_flag = pdbor->GetDBExt()->CheckDBService();
if (db_svc_flag!=0&&report_flag==0) { ///< @todo 报告给监控者
GetLogger()->log(LO_STDOUT|LO_FILE,SEVERITY_EMERGENCY,"CPreSendTask::handle_message_block检测到%s数据库故障.\n",semq_->GetDBCName());
report_flag = 1;
}
ACE_OS::sleep(5); ///< 出现一次故障后至少等待5秒
if (db_svc_flag==0) {
break;
}
} while(1);
}
}
delete sti;
return 0;
}
阻塞在GetData函数上,此函数一直没有结束.
int CSEMQ::GetData(string &sql)
和发送队列有关。
int cnt = send_task_[st_index].putq(mb);
处理很慢,处于堆积状态。但仍在执行。
[2014-05-19 13:44:11:093][线程7000][2][20][0][]CSEMQ::GetData 发送队列序号0, 队列个数32769
为什么send_task_这么慢呢?
int CSEMQ::Send(SHORT_SEMQ_RECORD *ssr)---此函数慢
每次TraceAction要6分钟。流量控制导致的,大量的消息没有返回。
SEMQ的默认水标:300。超时300秒(5分钟)。
测试环境下bbox.conf的配置
<inquiry_timer_interval>60</inquiry_timer_interval>
<inquiry_interval>1</inquiry_interval>
问询间隔为每分种一次。(这是在调试时为了加快速度设置的参数)
3091299 [2014-05-17 23:46:10:901][线程4832][1][20][0][]CSEMQ::TraceAction ok,num of action_trace:1 .
3091310 [2014-05-17 23:46:10:913][线程4832][1][20][0][]CSEMQ::TraceAction acquire 1_1_1_47656...
3092410 [2014-05-17 23:52:10:901][线程7096][1][20][0][]CSEMQ::TraceAction ok,num of action_trace:1 .
3092420 [2014-05-17 23:52:10:902][线程7096][1][20][0][]CSEMQ::TraceAction acquire 2_1_1_49277...
3093482 [2014-05-17 23:58:10:901][线程4832][1][20][0][]CSEMQ::TraceAction ok,num of action_trace:1 .
3093493 [2014-05-17 23:58:10:909][线程4832][1][20][0][]CSEMQ::TraceAction acquire 1_1_1_47657...
可见acquire要执行6分钟。流量控制下没有可用的信号量,只有在超时后才释放。(为什么不是默认的5分钟呢?)
int CSEMQ::TraceAction(char action,MQ_LOC_KEY *lock_key) {
char key[128];
sprintf(key,ACTION_KEY_FORMAT,action,lock_key->mq_id_,lock_key->mq_db_id_,lock_key->record_id_);
DEBUG_LOG(GetLogger(),(LO_STDOUT|LO_FILE,SEVERITY_DEBUG,"CSEMQ::TraceAction acquire %s...\n",key));
fc_sema_->acquire();
CActionTraceItem *ati = new CActionTraceItem;
ati->ts_ = clock();
action_trace_.bind(key,ati);
DEBUG_LOG(GetLogger(),(LO_STDOUT|LO_FILE,SEVERITY_DEBUG,"CSEMQ::TraceAction ok,num of action_trace:%d .\n",action_trace_.current_size()));
return 0;
}
修改后的代码: (已修改trunk 2014-7-22)
int CSEMQ::TraceAction(char action,MQ_LOC_KEY *lock_key) {
char key[128];
sprintf(key,ACTION_KEY_FORMAT,action,lock_key->mq_id_,lock_key->mq_db_id_,lock_key->record_id_);
DEBUG_LOG(GetLogger(),(LO_STDOUT|LO_FILE,SEVERITY_DEBUG,"CSEMQ::TraceAction acquire %s...\n",key));
fc_sema_->acquire();
CActionTraceItem *ati = new CActionTraceItem;
ati->ts_ = clock();
if (action_trace_.bind(key,ati)==1) { ///< 已经存在跟踪项
fc_sema_->release();
}
DEBUG_LOG(GetLogger(),(LO_STDOUT|LO_FILE,SEVERITY_DEBUG,"CSEMQ::TraceAction ok,num of action_trace:%d .\n",action_trace_.current_size()));
return 0;
}
2.SEMQ 记录状态
故障现象:测试中发现f009n_0031=100的记录,会变成4.
与消息的处理顺序有关。
由于测试时问询的频率比较短。发送后,目标接收方返回的810-Indication已经被处理,f009n_0031状态为100.但此时,已经发出了809-Request问询。
在目标方处理809-Request时还没有接收到单据,返回的消息指示“没有收到”。发送方接收到809-Confirmation后,把状态修改为0,重新发送。
***机制不严谨,有可能出现重复发送。(可以在Resend时排除已经是100状态的记录)
流量控制:
控制有多少个等待确认的消息。
int TraceAction(char action,MQ_LOC_KEY *lock_key); ///< 跟踪一个活动
action | 含义 | TraceAck时机 |
1 | 发送数据,跟踪tb_0031 | 接收到810-Indication |
2 | 问询(809-Request),跟踪tb_0031 | 接收到809-Confirmation |
3 | 反向确认(808-Indication),跟踪tb_0030 | 接收到800-Response |
action=1:
如果网络原因或者接收方处理失败,没有接收到810-Indiction:超时会删除
如果接收到2个810-Indication(分别来自平台和目标接收方):只能释放一个信号量
action=2
如果问询速度非常快(如压力或破坏性测试时),远大于返回消息的速度。
则同一条记录可能会发送多次,但信号量只能有一个。
如果同一个消息执行多次问询,每次执行fc_sema_->acquire();
但第1个该消息的确认返回时,只执行1次fc_semq_->release();----这会导致可用信号量减少,直到没有信号量可用。只能等待超时,释放信号量后才可继续。
---这和每6分钟执行一次是吻合的。
actino=3,
int CBBoxPlugin::OnRespInquiry(CWrappedMsg<> *in,vector<CWrappedMsg<> *> &out,DISPATCH_RESULT &or) {
result = semq_.Resend(ref_object_id);
Resend函数增加参数force,表示是否强制发送。force=false,如果消息已经确认则不发送 。
semq.h
int Resend(CQQ_OBJECT_ID object_id,bool force); ///< 重发一条消息
semq.cpp
问询条件表达式(部分条件项)
inquiry_expr_ = "((f016c_0031=3 and f009n_0031=4) or (f017c_0031=1 and f009n_0031=6))"; ///< f017c_0031=1实际上包含了f016c_0031(必须为3)
seqctrl_expr_ = "((f016c_0031=3 and f009n_0031 in (4,10,12)) or (f017c_0031=1 and f009n_0031<>100))"; ///< 时序控制时判定是否还有未处理的先前记录的过滤条件(如一张单据的2次改变产生的2条记录,第1条未确认则后面的不发送)
--->
inquiry_expr_ = "((f016c_0031=3 and f009n_0031 in (4,5,6)))"; ///< 忽略,废弃f017c_0031
seqctrl_expr_ = "((f016c_0031=3 and f009n_0031 in (4,5,6,10,12)))"; ///< 时序控制时判定是否还有未处理的先前记录的过滤条件(如一张单据的2次改变产生的2条记录,第1条未确认则后面的不发送)
*** f017c_0031此字段已不使用.(废弃此字段)
f016c_0031的含义如下:
enum {
SF_NONE=1,SF_SAVE_FWD,SF_ACK,
RL_DISCARDABLE=1,///< 目标不在线时丢弃
RL_NODISCARDABLE,///< 目标不在线时保存在待发送消息队列中
RL_STRICT,///< 要求确认(已送达)
};
UMXT默认RL_NODISCARDABLE.
假设发送给机构A的用户100681004的某个消息,不同消息可靠属性的含义分别如下:
- 对于RL_DISCARDABLE,消息到达机构A时,如果用户在线则发送给用户,如果不在线则丢弃。***发送端并不需要知道用户是否在线,在发送端就决定是否删除.
- 对于RL_NODISCARDABLE,消息到达机构A时,如果用户在线则发送给拥护,否则,保存在机构A的数据库中,等待用户上线后发送。***不能保证消息被送达
- 对于RL_STRICT,必须确保目标接收到.
对于UMXT消息,传输采用TCP。没有属性指示采用TCP或UDP。
CSEMQ::Resend实现:
////////////////////////////////////////////////////////////////////////////////
int CSEMQ::Resend(CQQ_OBJECT_ID object_id,bool force) {
string sql;
if (force)
sql = LogMsg("update %s set f009n_0031=0 where object_id=%I64d",this->GetTableName(),object_id);
else
sql = LogMsg("update %s set f009n_0031=0 where object_id=%I64d and %s",this->GetTableName(),object_id,inquiry_expr_);
USEDBC(pdbor,this->dbc_name_.c_str());
int ret = pdbor->Execute(adCmdText,sql.c_str()) ? 0 : -1;
if (ret==0) {
SendRecord(object_id);
}
return ret;
}
要求目标为平台的消息最后的状态应该为100.(已送达),而不能是6。
810-Indication增加flag参数:
int CBBoxPlugin::OnAck(CWrappedMsg<> *in,vector<CWrappedMsg<> *> &out,DISPATCH_RESULT &or)
///< 发送给PT_APP的消息,只要已到达平台(写入网关应用队列),就算已经送达
// if(src_t == PT_CENTRAL && qe.dest_type_ != PT_CENTRAL && qe.dest_type_!=PT_PAPP){ ///< 如果确认消息来自平台,但是队列记录的目标不是平台,表示可能并没有发送到目标方(非平台)
if (handle_flag==1||handle_flag==2) {
status = ISEMQ::SS_ACK; ///< @note 不区分平台是转发还是存储了----平台代码目前不支持
}
else {
status = ISEMQ::SS_ACK2;
}