1.实例数线程数控制
定时器每次启动的就是一个线程。可用防止重入机制,以免线程过多资源被其耗尽。此时是单实例单线程。
或者使用计数器来限定线程数量。此时是单实例多线程。
2.多实例单线程并发
(1)消息机制对消息的争抢问题(并发,数据库更新锁)
说明:在消息的处理中,消息均插入一个表,而定时器在两个实例上同时运行,因此必须防止对消息的竞争。
方案:利用数据库记录的更新结果作为可处理标志。
A实例和B实例均读取了消息M,然后去更新消息记录附加字段status,返回更新的记录数R,哪个实例的R==1,则为抢到资源,表示可以处理此条消息。否则放弃。这是利用了数据库更新数据时本身的锁机制。简单高效。
示例:string sql = @"update crm.tb_cus_msg set status = 1 where status = 0 and msgID = xxx";
int affectedRows = DACommon.ExecuteNoQuery(sql);
if(affectedRows > 0){ 后续的消息处理逻辑...... }
拓展:如果实时性非常高,则可以使用第三方服务,将消息进行分配。例如将A实例给3条,记录字段InstanceType = 'asA',B实例给3条,记录字段InstanceType = 'asB',A实例再给3条,则第四次分配一定分给B实例3条,保证A实例和B实例大致相同。读取时A实例的服务(利用配置文件作为标识flag,例如flag='asA’表示A实例)仅读取InstanceType = 'asA',B实例一样,可以保证两个实例大致处理数量相同消息。最重要的是不会发生争抢消息的问题。但是为了解决以下问题(2)对同一业务ID的同时重复处理问题,必须对所有消息(不能区分InstanceType )进行标志位控制,即不能存在小于本消息ID的正在处理中的消息ID。
(2)消息机制对同一个业务ID的同时重复处理问题 (并发)
说明:业务ID的处理正在处理中,且耗时较长,若下一条消息还是同业务ID。必须等上一同业务ID处理完成,后续同ID才能处理。
方案:若存在同一个业务ID在处理(status=1),则另一个实例不能处理此业务ID。 即利用了消息的中间状态(0,1,2,-1分别表示初始状态,处理中状态,处理成功状态,处理失败状态)。
实例:
select msgID from (
select msgID from crm.tb_cus_msg re
where status = 0
and not exists(
select 1 from crm.tb_cus_msg t
where t.objectID = re.objectID and t.status = 1 and t.msgID < re.msgID
)
order by msgID --msgID为自增主键字段
) where rownum < 2
(3)消息机制的单次消息数量限定问题(并发)
说明:处理消息可以批量处理,比如一次取50条到内存依次处理,但这仅限于单实例单线程处理。多实例则很有可能会重复处理同一业务ID,比如一次性取50条,A实例成功更新30条,B实例成功更新20条,但是同一业务ID可能分别出现在A和B实例中被同时处理。实际业务无法去限死同一业务ID不能重复插入,否则失去兼容性,另外,一般业务也要求不能同时处理同一业务ID,否则可能产生数据覆盖问题。
说明:另外一个情况就是服务宕掉问题。如果正在处理50条的第三条,服务挂掉,则后续47条全部无法处理。因为这47条并不是错误标志(status=-1),而是全部为正在处理中(status=1),消息错误处理机制也无法处理到这部分数据。导致大量消息丢失的问题。
方案:多实例中,每个实例一次仅能处理一条消息ID,消息中仅包含一个业务ID(前提是此业务ID不能由多实例同时处理)。
(4)消息处理失败问题
说明:如果消息处理失败,异常捕获后应该将标志位status置位-1,同时,下一次获取待处理消息时,应该同时能够获取此部分消息。但是有一点需要注意:错误消息的获取必须限定重入次数,也就是重复处理的次数比如三次,之后仍然失败则丢弃说明此消息可能有硬伤,处理逻辑中每处理一次,将此消息重入次数加一。
------------------------取待处理消息-------------------------------
select msgID from (
select msgID from crm.tb_cus_msg re
where ( status = 0 or ( status = -1 and errorTryTimes < 3 ) )
and not exists(
select 1 from crm.tb_cus_msg t
where t.objectID = re.objectID and t.status = 1 and t.msgID < re.msgID
)
order by msgID --msgID为自增主键字段
) where rownum < 2
----------------异常捕获要更新重试次数-------------------------
catch(exception ex){
string sql = @"update crm.tb_cus_msg set status = -1, errorTryTimes = errorTryTimes + 1 where msgID = :MSGID"
......}
(5)多线程的使用问题
说明:多线程使用在业务ID层,还是用在业务ID的下一级业务ID,要看哪一层处理慢,比如一家客户下面有1000个账号,账号之间相互独立,每个账号都要一个sql执行,肯定是处理账号慢,这时要将多线程用在账号上,两个实例每个开启五个线程,就能缩短近十倍的处理时间。
方案:根据实际情况,将线程用到响应的业务层级上。前提是此业务层级每个业务ID相互没有影响。