专栏首页架构说专栏RPC实战与核心原理-第三天学习

专栏RPC实战与核心原理-第三天学习

09健康检测:这个节点都挂了,为啥还要疯狂发请求?

服务发现的作用就是实时感知集群 IP 的变化,实现接口跟服务集群节点 IP 的映射。

在超大规模集群实战中,我们更多需要考虑的是保证最终一致性

终极的解决方案是让调用方实时感知到节点的状态变化,这样他们才能做出正确的选择

画外音:就是心跳检查,这里面有什么特别地方吗?我确实想不到有什么注意地方。

在进一步讲解服务健康检测之前,我想先和你分享一个我曾经遇到过的线上问题

接口调用某台机器的时候已经出现不能及时响应了,

那为什么 RPC 框架还会继续把请求发到这台有问题的机器上呢?

RPC 框架还会把请求发到这台机器上,也就是说从调用方的角度看,它没有觉得这台服务器有问题。”

我们发现,其实更大的问题是我们的服务检测机制有问题,有的服务本来都已经病危了,但我们还以为人家只是个感冒。

健康检测的逻辑

我又发现了新的麻烦:

  • 调用方每个接口的调用频次不一样,有的接口可能 1 秒内调用上百次,有的接口可能半个小时才会调用一次,所以我们不能把简单的把总失败的次数当作判断条件。
  • 服务的接口响应时间也是不一样的,有的接口可能 1ms,有的接口可能是 10s,所以我们也不能把 TPS 到来当作判断条件。

和同事讨论之后,我们找到了可用率这个突破口,应该相对完美了。

可用率的计算方式是某一个时间窗口内接口调用成功次数的百分比(成功次数 / 总调用次数)。

当可用率低于某个比例就认为这个节点存在问题,把它挪到亚健康列表,

这样既考虑了高低频的调用接口,也兼顾了接口响应时间不同的问题。

10 | 路由策略:怎么让请求按照设定的规则发到不同的节点上?

关键字:RPC 中的路由策略

为什么选择路由策略?

  • 问题:

服务提供方是以集群的方式对外提供服务,那就要考虑一些实际问题。

要知道我们每次上线应用的时候都不止一台服务器会运行实例,那上线就涉及到变更,只要变更就可能导致原本正常运行的程序出现异常,尤其是发生重大变动的时候,导致我们应用不稳定的因素就变得很多。

  • 疑问

那对于我们的 RPC 框架来说,有什么的办法可以减少上线变更导致的风险吗?

这就不得不提路由在 RPC 中的应用。具体好在哪里,怎么实现,我们接着往下看。

如何实现路由策略?参数路由

  • 改造注册中心服务发现 不合适
  • 灰度发布功能作为 RPC 路由功能的一个典型应用场景

其核心思想都是一样的,就是让请求按照我们设定的规则发送到目标节点上, 从而实现流量隔离的效果。

其关键点就是调用端收集服务端每个节点的指标数据,再根据各方面的指标数据进行计算打分,最后根据每个节点的分数,将更多的流量打到分数较高的节点上

12 | 异常重试:在约定时间内安全可靠地重试

问题描述:为什么需要异常重试?

我们可以考虑这样一个场景。

我们发起一次 RPC 调用,去调用远程的一个服务,比如用户的登录操作,我们会先对用户的用户名以及密码进行验证,验证成功之后会获取用户的基本信息。

当我们通过远程的用户服务来获取用户基本信息的时候,恰好网络出现了问题,比如网络突然抖了一下,导致我们的请求失败了,而这个请求我们希望它能够尽可能地执行成功,那这时我们要怎么做呢?

我们需要重新发起一次 RPC 调用,那我们在代码中该如何处理呢?

是在代码逻辑里 catch 一下,失败了就再发起一次调用吗?这样做显然不够优雅吧。

这时我们就可以考虑使用 RPC 框架的重试

机制。

重试机制是在设置的超时时间到了之后没有返回结果或者服务端出现异常后服务调用端进行再次调用。 首先,不是所有接口都适合重试,如果一个服务是不等幂,那么不适合重试的机制,因为会存在重复提交的问题

Dubbo 集群容错策略 ?

  • Failover - 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
  • Failfast - 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
  • Failsafe - 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
  • Failback - 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
  • Forking - 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
  • Broadcast -调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

RPC 框架的重试机制

在使用 RPC 框架的时候,我们要确保被调用的服务的业务逻辑是幂等的,

这样我们才能考虑根据事件情况开启 RPC 框架的异常重试功能。

这一点你要格外注意,这算是一个高频误区了。

只有符合重试条件的异常才能触发重试,比如网络超时异常、网络连接异常等等。

画外音:网络异常 请求没有发送成功

根据异常触发重试,重新通过负载均衡选择一个节点发送请求消息,并且记录请求的重试次数,

当重试次数达到用户配置的重试次数的时候,就返回给调用端动态代理一个失败异常,否则就一直重试下去

问题2

有没有想到连续重试对请求超时时间的影响?

继续考虑这样一个场景:我把调用端的请求超时时间设置为 5s,结果连续重试 3 次,每次都耗时 2s,那最终这个请求的耗时是 6s,那这样的话,调用端设置的超时时间是不是就不准确了呢?

在每次重试后都重置一下请求的超时时间

如何在约定时间内安全可靠地重试?

  • 当调用端发起 RPC 请求时,如果发送请求发生异常并触发了异常重试,我们可以先判定下这个请求是否已经超时,如果已经超时了就直接返回超时异常,否则就先重置下这个请求的超时时间,之后再发起重试。
  • 比如这个场景:服务端的业务逻辑是对数据库某个数据的更新操作,更新失败则抛出个更新失败的异常,调用端可以再次调用,来触发服务端重新执行更新操作。那这个时候对于调用端来说,它接收到了更新失败异常,虽然是服务端抛回来的业务异常,但也是可以进行重试的。
  • RPC 框架是不会知道哪些业务异常能够去进行异常重试的,我们可以加个重试异常的白名单,用户可以将允许重试的异常加入到这个白名单中
  • 只有 RPC 框架中特定的异常才会如此,比如连接异常、超时异常。
  • 在使用 RPC 框架的重试机制时,我们要确保被调用的服务的业务逻辑是幂等的,这样才能考虑是否使用重试,这一点至关重要。

画外音:

那如果这个服务业务逻辑不是幂等的,比如插入数据操作,那触发重试的话会不会引发问题呢?会的。

面试题

分布式服务接口的幂等性如何设计(比如不能重复扣款)?

面试官心理分析

从这个问题开始,面试官就已经进入了实际的生产问题的面试了。

一个分布式系统中的某个接口,该如何保证幂等性?这个事儿其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题。啥意思呢?

你看,假如你有个服务提供一些接口供外部调用,这个服务部署在了 5 台机器上,接着有个接口就是付款接口。然后人家用户在前端上操作的时候,不知道为啥,总之就是一个订单不小心发起了两次支付请求,然后这俩请求分散在了这个服务部署的不同的机器上,好了,结果一个订单扣款扣两次。

或者是订单系统调用支付系统进行支付,结果不小心因为网络超时了,然后订单系统走了前面我们看到的那个重试机制,咔嚓给你重试了一把,好,支付系统收到一个支付请求两次,而且因为负载均衡算法落在了不同的机器上,尴尬了。。。

所以你肯定得知道这事儿,否则你做出来的分布式系统恐怕容易埋坑。

面试题剖析

这个不是技术问题,这个没有通用的一个方法,这个应该结合业务来保证幂等性。

所谓幂等性,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款、不能多插入一条数据、不能将统计值多加了 1。这就是幂等性。

其实保证幂等性主要是三点:

  • 对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。
  • 每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。
  • 每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。

实际运作过程中,你要结合自己的业务来,比如说利用 Redis,用 orderId 作为唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣款。

要求是支付一个订单,必须插入一条支付流水,order_id 建一个唯一键 unique key 。你在支付一个订单之前,先插入一条支付流水,order_id 就已经进去了。你就可以写一个标识到 Redis 里面去, set order_id payed ,下一次重复请求过来了,先查 Redis 的 order_id 对应的 value,如果是 payed 就说明已经支付过了,你就别重复支付了。

本文分享自微信公众号 - 架构说(JiaGouS),作者:王传义

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 系统设计题(1) 连续5天登录用户(快手)

    但是,由于每一行的 id%100 的结 果是无序的,所以我们就需要有一个临时表,来记录并统计结果。

    程序员小王
  • 平安证券Kubernetes容器集群的DevOps实践

    在前面众多微信的分享系列中,对k8s的体系构成,各个概念的定义,各组件的作用等都已介绍多次,此处就不再重复这些内容。在这篇文章中,主要和大家分享一些我们平安证券...

    程序员小王
  • 本周阅读:深度探索C++对象模型

    深度探索C++对象模型 1.3 章节 https://github.com/wangcy6/weekly/blob/master/reading-not...

    程序员小王
  • (十一)c#Winform自定义控件-列表

    GitHub:https://github.com/kwwwvagaa/NetWinformControl

    冰封一夏
  • C++ 多进程并发框架FFLIB之Tutorial

          FFLIB框架是为简化分布式/多进程并发而生的。它起始于本人尝试解决工作中经常遇到的问题如消息定义、异步、多线程、单元测试、性能优化等。基本介绍可以...

    知然
  • 物联网|2018世界杯,资深球迷必知的6点!

    足球作为全世界最多人关注的运动,赛场上每个运动员的汗水挥洒都牵动着每个球迷的心,每次的进球都刺激着无数观众的荷尔蒙。现在正值世界杯火热举办期间,这项四年一届举世...

    用户2356481
  • c++之this指针

    绝命生
  • 微信小游戏 —— 关系链数据使用(排行榜的显示)

    微信小游戏属于微信小程序的一个类目,小游戏对比于普通的h5游戏,其很大的一个特点是微信提供的关系链数据的使用,你可以获得同玩这个游戏的微信好友的数据,或者你在某...

    :::::::
  • 微信小游戏关系链的使用(排行榜的显示)

    微信小游戏属于微信小程序的一个类目,小游戏对比于普通的h5游戏,其很大的一个特点是微信提供的关系链数据的使用,你可以获得同玩这个游戏的微信好友的数据,或者你在某...

    bering
  • Bootstrap 源码分析

    Netty 源码分析: Bootstrap 1. 结构 先看一个这个类的类层次结构, ? 好,这个结构还是比较明晰的,然后看他的主要字段,因为这些字段比较重...

    lwen

扫码关注云+社区

领取腾讯云代金券