去哪儿网已经有四年多使用消息驱动架构风格构建大型交易系统的经验,现在整个交易链路基本上都是靠消息来驱动完成,在这个过程中我们也不断地的摸索前进,积累了一些消息处理的模式,而且我们还将这种模式以内置的方式提供出来,以期达到开箱即用。本文就根据过去我们使用消息驱动积累的经验总结,并结合我们内部的消息队列QMQ作为讲解示例。 1 consumer消费到重复消息怎么办? 1.1 消息消费模式 消息消费一般存在三种模式:最多一次,最少一次和有且仅有一次。 1.1.1 最多一次 这种可靠性最低,不管消费是否成功,投递一次就算完了。这种类型一般用在可靠性不高的场景中,比如我们一个对日志分析展示的场景,如果这种日志分析出现一定的缺失对业务也影响不大,那我们可以使用这种方式,这种方式性能最高(QMQ的非可靠消息)。 1.1.2 最少一次 基本上所有追求可靠性的消息队列都会采用这种模式。因为网络是不可靠的,要在不可靠的网络基础上构建可靠的业务,就必须使用重试来实现,那么重试就有可能引入重复的消息(QMQ的可靠消息)。 1.1.3 有且仅有一次 这是人们最期望的方式。也就是我如果真正的处理失败了(业务失败)你才给我重发,如果仅仅是因为网络等原因导致的超时不能给我重发消息。但是这种仅仅靠消息队列自身是很难保证的。不过借助一些其他手段,是能达到有且仅有一次的『效果』(QMQ的幂等检查)。 通过上面的描述,我们知道有且仅有一次的消息投递模式是很难达到的,那如果我们需要消息的可靠性,就必须接受重复消息这个事实。那么对于重复消息到底该怎么办呢?下面会列出一些场景和解决方案: 1.2 处理方式 1.2.1 不处理 这也算解决方案么?我当然不是说所有的重复消息都可以不处理的,但是是有场景是可以的。比如我们有一个缓存,数据库更新之后我们发送一条消息去将缓存删掉,等下一次数据访问的时候缓存没有命中,从数据库重新加载新数据并更新缓存。这里的消息就是删除缓存的消息,那么删除缓存这个消息就是可以接受重复消息的,这种重复消息对业务几乎没有影响(影响也是有,可能会稍微降低缓存命中率),我们权衡一下处理重复消息的成本和这个对业务的影响,那么不处理就是个最佳方案了。可能有同学说,降低缓存命中率也不行啊,还是得解决。那么我们看这个重复消息会降低多少命中率呢?那就得看重复消息多不多呢?重复消息一般是网络不稳定导致的,这在内网里这种情况其实并不常见,所以我觉得是可以接受的。 1.2.2 业务处理 有同学讲这不是废话么?重复消息当然是我们业务处理啊。我这里说的业务处理是说有很多业务逻辑自身就是能处理重复消息的,也就是有很多业务逻辑本来就是幂等的。这里有个题外话:即使我们不使用消息,也要尽量将我们的接口设计为幂等的。比如我们有一个创建新订单的消息,接到消息后会向数据库保存新订单。那么如果我们接到了重复订单(订单号相同),这样的订单肯定是不能保存的,但是这里切记一点,虽然最终我们不会保存两个一样的订单,但是收到重复订单的时候你就回复成功就可以了,不要抛出异常,因为抛出异常一般会认为是消息消费失败,又会重发。这在早期我们很多同学犯这个错误,就直接将DuplicateKeyException异常抛出了(其实对于接口幂等设计时也是一样,第二次重复调用的时候你返回成功的响应就行了,如果要告诉人家是重复的也在另外的字段告诉,而不是标识成功或失败响应的地方标识,这样会让请求方的处理代码更舒服些)。 1.2.3 去重表 如果我们不能接受重复消息,但是我们的业务逻辑自身又没办法处理重复消息该怎么办呢?那就得借助额外的手段了。也就是引入一个去重表,我们在从消息里提取一个或多个字段作为我们去重表的唯一索引,在消息处理成功的时候我们在去重表记录,如果又接到重复的消息先查去重表,如果已经成功消费过这个消息则直接返回成功就行了。而且我们还可以根据我们对去重这个事情要求的可靠等级选择将去重表建在不同的位置:数据库或redis等。放在redis里那么去重的可靠性就是redis的可靠性,一般达到99.9%应该是没有问题的,而且放在redis里我们可以设置一个过期时间,因为重复消息这个东西一般会在一个短期时间区间内发生,比如很少几个小时后甚至是几分钟后还出现重复消息。那么如果我们对可靠性要求更高则可以将去重表放在数据库里,但使用数据库成本也更高,而且数据库一般没有自动过期机制,所以可能还需要一个自动的『垃圾回收』处理机制,将多久之前的去重表里的数据删除掉。看起来引入额外的去重机制是不是很麻烦?不用担心,QMQ已经为你提供了幂等检查器这种机制,只要简单的配置一下就ok了: 上面三种方法基本上就可以解决绝大多数问题了。但是别离开,你真的以为去重就真的这么简单么?并不是。我们再来仔细看一下,假设我们收到一个消息后我们处理变更一下数据库,然后我们还要请求其他服务,如果现在的情况是我们的数据库更新成功了,但是服务请求失败了,最后引起消息重发,这该怎么办?我们能再次调用这个服务么?并不确定。所以以后如果有人给你提供服务,除了理解清楚这个服务的功能外,最重要的一点是这个服务是不是幂等的,如果不是幂等的你应该要求服务提供方提供幂等的服务,这样你好他好大家都好(幂等性是服务设计的重要原则之一)。 另外一点,如果我们的消费逻辑只涉及我自己的数据库操作,并不调用其他服务,但是因为我自己的业务逻辑不能处理重复消息,所以我要借助去重表,但是我怎么保证去重表和我的业务库操作是原子的呢?就是我的业务库操作成功了,去重表没有记录成功怎么办?这就要引入事务了。放心,QMQ已经为你考虑到了这一点提供了带事务的去重逻辑。关于QMQ的幂等检查请参照QMQ使用文档。 2 消息顺序 消息投递的顺序是消费者关心的第二个问题。很遗憾,实现顺序消费的成本也是非常高的,所以大多数消息队列没有提供顺序消费模式。Kafka因为它独特的存储模型,所以提供了顺序消费这种方式,但是也是有其他限制的。那么如果我要求顺序消费该怎么办呢? 2.1 处理方式 2.1.1 不处理 这个我就不嗦了,其实这个场景可以直接借用上面重复消息里的场景,删除缓存的消息先发的后到是没有多大关系的。 2.1.2 业务处理 和上面一样,绝大多数业务逻辑是本身就是能处理顺序的。比如我们的交易系统里有很多很多状态机,状态机有严格的状态扭转流程。比如我们的支付状态机,我们从待支付- |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|