博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
信用算力基于 RocketMQ 实现金融级数据服务的实践
阅读量:4620 次
发布时间:2019-06-09

本文共 5001 字,大约阅读时间需要 16 分钟。

  微服务架构已成为了互联网的热门话题之一,而这也是互联网技术发展的必然阶段。然而,微服务概念的提出者 Martin Fowler 却强调:分布式调用的第一原则就是不要分布式。

  
  纵观微服务实施过程中的弊端,可以推断出作者的意图,就是希望系统架构者能够谨慎地对待分布式调用,这是分布式系统自身存在的缺陷所致。但无论是 RPC 框架,还是 REST 框架,都因为驻留在不同进程空间的分布式组件,而引入了额外的复杂度。因而可能对系统的效率、可靠性、可预测性等诸多方面带来负面影响。
  
  信用算力自2016年开始实施微服务改造,通过消息队列(Message Queue),后文简称MQ,来规避微服务存在的缺陷,实现金融级数据服务。以下是一些使用场景和心得。
  
  为什么需要 MQ
  
  一、案例介绍
  
  先来看一个当前的真实业务场景。
  
  对于通过信息流获客的企业而言,当用户注册时,因业务需求会调用用户服务,然后执行一系列操作,注册 -> 初始化账户信息 -> 邀友奖励发放 -> 发放优惠券 -> ... -> 信息流数据上报。
  
  用户服务的开发人员压力非常大,因为需要调用非常多的服务,业务耦合严重。如果当时账户服务正在执行发版操作,那么初始化账户动作会失败。然而平台经过不断的迭代更新,后续又新增了一个签到业务,新注册用户默认签到一次。这就需要修改用户服务,增加调用签到服务的接口。每当遇到此种情况,开发用户服务的同学就非常不爽了,为什么总是我?新增签到业务和用户服务又有什么关系?
  
  为解决此类重度依赖的问题,我们在架构层面引入了 MQ,用来规避微服务之间重度耦合调用的弊端。新架构如下图:
  
  用户完成注册动作后,只需要往 MQ 发送一个用户注册的通知消息,下游业务如需要依赖注册相关的数据,订阅注册消息的 topic 即可,从而实现了业务的解耦。
  
  看完上述真实的案例后,大家可能产生疑惑,到底什么是 MQ,使用 MQ 又有什么好处?适合使用 MQ 的场景和不适合使用 MQ 的场景有哪些不同?
  
  二、什么是 MQ?
  
  简单来说,MQ(MessageQueue)是一种跨进程的通信机制,用于上下游传递消息。
  
  适合使用 MQ 的场景有:
  
  1、上游不关心下游执行结果,例如上述案例中用户注册后,我们并不关心账户是否初始化,是否上报了信息流等;
  
  2、异步返回执行时间长:例如上述案例中,当邀友奖励发放,需要经历很多风控规则,执行时间比较长,但是用户并不关注奖励何时发放。
  
  不适合使用MQ场景
  
  调用方实时关注执行结果,例如用户发起注册动作后,需要立刻知道,注册结果是成功还是失败,这种需要实时知道最终执行结果的场景,就不适合使用MQ。
  
  三、使用MQ的好处:
  
  1、解耦
  
  2、可靠投递
  
  3、广播
  
  4、最终一致性
  
  5、流量削峰
  
  6、消息投递保证
  
  7、异步通信(支持同步)
  
  8、提高系统吞吐、健壮性
  
  MQ 的技术选型
  
  目前业内比较主流的 MQ 包括 RocketMQ、ActiveMQ、RabbitMQ、Kafka等,关于性能、存储、社区活跃度等各方面的技术对比已经很多,本文不再重复。
  
  但我们发现通过简单的选型对比,很难抉择到底选择哪款MQ产品。因为金融行业对于数据一致性以及服务可用性的要求非常高,所以任何关于技术的选项都显得尤为重要。
  
  经调研,如微众银行、民生银行、平安银行等国内知名的互联网银行和直销银行代表,都在使用 RocketMQ,且 RocketMQ 出生在阿里系,经受过各种生产压力的考验,非常稳定。并且,目前此项技术已经捐增给 Apache 社区,社区活跃度非常高。另外 RocketMQ 开发语言是Java,开发同学遇到解决不了的问题点,或者不清楚的概念,可以直接 Debug 源码。经过多方面的比较,我们选择 RocketMQ 作为规避微服务弊端的利器。
  
  MQ 在微服务下的使用场景
  
  MQ 是一种跨进程的通信机制,用于上下游传递消息,目前信用算力将 RocketMQ 应用于解耦、流量削峰、分布式事务的处理等几个场景。
  
  一、解耦
  
  通常解耦的做法是生产者发送消息到 MQ,下游订阅 MQ 的特定 topic,当下游接收到消息后开始处理业务逻辑。
  
  那么,消息发送方到底应该是由谁来承担?是服务提供者在处理完RPC请求后,根据业务需求开始发送消息吗?但此刻开发人员就会抱怨为什么总是我?为什么处理完业务后需要发送 MQ?
  
  为此,在解耦的过程中通过订阅数据库的 BinLog 日志,开发了一套 BinLog 日志解析模块,专门解析日志,然后生成 JSON 字符串后发送消息到 MQ,下游订阅 MQ 即可。流程如下:
  
  目前所有需要依赖下游服务的业务线,其数据变动都采用此方案。
  
  方案优缺点:
  
  优点:
  
  1、服务之间依赖完全解耦,任何基于注册行为的业务变更,都无需依赖上游,只需订阅MQ即可;
  
  2、系统的稳定性和吞吐量增加了,用户注册的响应时间缩短了;
  
  缺点:
  
  1、引入MQ后系统复杂性增加,维护成本增加;
  
  2、从注册开始到全部数据初始化结束的整体时间增加了;
  
  二、流量削峰
  
  每逢遇到会员日的时候,平台会发送大量的会员福利活动通知,以短信、站内信、PUSH 消息的方式通知注册用户。所有的消息会在很短的时间全部推送到消息中心,同时正常的业务通知任然有大量业务消息推送到消息中心。为保障平台的稳定性和可靠性,在消息中心前置了多种 topic,如短信、推送、站内提醒。消息中心接收到消息后会全部写入不同 topic 的 MQ,多个消费者来消费并把信息推送给终端用户。
  
  三、分布式事务
  
  用户在平台上支付他订购某种业务的时候,需要涉及到支付服务、账户服务、优惠券服务、积分服务,在单体模式下这种业务非常容易实现,通过事务即可完成,伪代码如下:
  
  然而,在微服务的情况下,原本通过简单事务处理的却变得非常复杂,若引入两阶段提交(2PC)或者补偿事务(TCC)方案,则系统的复杂程度会增加。
  
  信用算力的做法是通过本地事务 + MQ 消息的方式来解决, 虽然 RocketMQ 也支持事务消息,但是其他主流 MQ 并没有此项功能,所以综合考虑采用如下方案:
  
  消息上游:需要额外建一个tc_message表,并记录消息发送状态。消息表和业务数据在同一个数据库里面,而且要在一个事务里提交。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送;
  
  消息上游:开启定时任务扫描tc_message表,如果超过设置的时间内状态没有变更,会再次发送消息到MQ,如重试次数达到上限则发起告警操作;
  
  消息下游:需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理完成了,需要发起业务回调通知业务方;
  
  方案优缺点:
  
  优点:
  
  1、用最小的代价实现分布式事物,以达到数据最终一致性;
  
  2、方案非常灵活,任何环节都可以人为控制;
  
  缺点:
  
  1、复杂性增加了,业务操作的时候需要写入 tc_message 表以及发送 MQ,同时还需要考虑状态超时未变更的补发机制以及告警处理机制;
  
  2、用户看到的数据,存在有短暂不一致的情况;
  
  心得体会
  
  使用 RocketMQ 3年多了,总体来说运行的非常稳定,基本上没有发生过生产事故,下面说说这几年使用下来的心得体会:
  
  1、一个应用尽可能用一个 Topic,消息子类型用 tags 来标识。Topic 名称和 Tags 名称可以自行设置。Producer,Consumer都需要规范,要做到见名知意。发送消息时候必须携带 Tags,消费方在订阅消息时,才可以利用 Tags 在 Broker 做消息过滤。
  
  2、每条消息在业务层面有唯一标识码,方便在系统出现异常的情况,可以通过业务维度查询。举个栗子,当用户在平台注册成功后,会以 Topic 和 UserID 作为唯一标识码(topic_user_10011),服务器会为每个消息创建索引,该消息会持久化入库,以防将来定位消息丢失等问题。下游收到消息后会以 Topic+Key 方式来记录消费行为,包括消息日期、当前机器IP地址、处理结果等;也可以通过 Topic+Key 的方式来查询这条消息内容,包括消息被谁消费,以及这条 MQ 在每个环节的处理状态。

@RunWith(SpringRunner.class)

@SpringBootTest
public class ProducerServiceApplicationTests {
private static final Logger LOGGER = LoggerFactory.getLogger(www.tianjiuyule178.com ProducerServiceApplicationTests.class);
@Autowired
private DefaultMQProducer defaultMQProducer;

@Test

public void send(www.gouyiflb.cn/) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, UnsupportedEncodingException {
for (int i = 0; i < 100; i++) {
User user = new User();
user.setUsername("用户" + i);
user.setPassword("密码" + i);
user.setSex(i % 2);
user.setBirthday(new Date());
Message message www.hengtongyoule.com= new Message("user-topic", "user-tag", JSON.toJSONString(user)www.qwert888.com/.getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = defaultMQProducer.send(message);
LOGGER.info(sendResult.toString(www.gaozhuoyiqi.com));
  
  3、消息发送成功或者失败,都需要记录 log 日志,且必须打印 sendresult、MsgID、唯一标识码。
  
  4、由于上游会做消息重试机制,所以下游消息必须要做幂等处理。
  
  5、需要封装 MQ 的 API 在封装后,API 需屏蔽底层 MQ 的特性,开发人员无需关注到底是用的哪个 MQ 来支持本地分布式事物、MQ 消息自动入库、自动打印日志,减少开发人员操作成本。
  
  总的来说,MQ 是一个互联网架构中常见的解耦利器,在这3年中,信用算力在微服务中一直使用 MQ 来为金融客户提供高质量的数据服务。虽然 MQ 不是唯一方案,但是从目前阶段来看,的确是一种非常不错的解决方案。
  
  本文作者:
  
  潘志伟:
  
  信用算力技术总监,QCon 演讲嘉宾,十多年 Java 从业经验,精通微服务架构,精通大数据。拥有亿级用户平台架构经验,万级并发的API网关经验。

转载于:https://www.cnblogs.com/qwangxiao/p/10620485.html

你可能感兴趣的文章
LeetCode 1135. Connecting Cities With Minimum Cost
查看>>
LeetCode 1102. Path With Maximum Minimum Value
查看>>
LeetCode 1061. Lexicographically Smallest Equivalent String
查看>>
LeetCode 841. Keys and Rooms
查看>>
LeetCode 1043. Partition Array for Maximum Sum
查看>>
LeetCode 923. 3Sum With Multiplicity
查看>>
LeetCode 750. Number Of Corner Rectangles
查看>>
LeetCode 983. Minimum Cost For Tickets
查看>>
LeetCode 723. Candy Crush
查看>>
LeetCode 881. Boats to Save People
查看>>
LeetCode 334. Increasing Triplet Subsequence
查看>>
LeetCode 877. Stone Game
查看>>
LeetCode 712. Minimum ASCII Delete Sum for Two Strings
查看>>
LeetCode 931. Minimum Falling Path Sum
查看>>
LeetCode 718. Maximum Length of Repeated Subarray
查看>>
LeetCode 446. Arithmetic Slices II - Subsequence
查看>>
LeetCode 740. Delete and Earn
查看>>
LeetCode 813. Largest Sum of Averages
查看>>
LeetCode 1143. Longest Common Subsequence
查看>>
LeetCode 1000. Minimum Cost to Merge Stones
查看>>