定时器:消息机制

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相互没有影响。

你可能感兴趣的:(C#,定时器,消息机制)