disgare 的博客
首页
博客
分类
标签
首页
博客
分类
标签
  • 网络

    • 计算机网络学习笔记
    • 网络安全相关
    • 域名和子网掩码
    • CORS 跨域资源共享
    • DNS、HTTP 与 HTTPS
    • Server-Sent Events (SSE)
    • WebSocket 长连接
  • 计算机基础

    • 操作系统 IO 相关知识
    • 操作系统学习笔记
    • 程序的机器级表示
    • 音频文件基础
    • 正则表达式相关概念
    • ffmpeg 的安装以及实现音频切分功能
    • Hex 和 Base64 编码
    • XML 的使用
  • 数据结构与算法

    • 动态规划算法学习笔记
    • 基于比较的排序算法的最坏情况下的最优下界为什么是O(nlogn)
    • 集合与数据结构学习笔记
    • 面试常见算法总结
    • 算法导论第二部分排序学习笔记
    • 算法导论第一部分学习笔记
  • Java

    • 对象之间的映射与转换
    • 反射学习笔记
    • 泛型相关概念
    • 关于 boolean 类型的坑
    • 如何使用 lambda 表达式实现排序
    • CompletableFuture 相关用法
    • CompletableFuture 源码浅要阅读
    • FutureTask 源码阅读
    • Guava 常用 API
    • Guava 源码阅读:Multimap 相关
    • Jackson 的各种使用
    • Java 的 Excel 相关操作
    • java 的常见性能问题分析以及出现场景
    • java 基础知识
    • JAVA 枚举的基础和原理
    • Java 图片文件上传下载处理
    • Java 序列化
    • Java 异常
    • Java 语法糖
    • Java 中关于字符串处理的常用方法
    • Java 中强、软、弱、虚引用
    • JAVA 注解小结
    • Java Http 访问框架
    • Java Stream 的使用
    • Java8 新特性
    • netty 学习笔记
    • Scanner 的各种用法
    • Servlet 学习笔记
    • String、StringBuffer、StringBuilder 学习笔记
  • JVM

    • 虚拟机执行子系统
    • JVM 自动内存管理
    • Linux 中 JVM 常用工具以及常见问题解决思路
  • Linux

    • crontab 表达式
    • Linux 常见命令
    • Linux 文件系统
  • 中间件

    • 关于定时任务原理
    • 详解 kafka
    • ES 搜索引擎
    • flink 提交流程
    • Grape-RAG
    • Hadoop 基础原理
  • 多线程

    • 多线程基础学习笔记
    • 简单了解并发集合
    • 如何手写单例
    • 深入理解 java 多线程安全
    • 生产者消费者问题
    • 线程池作用、用法以及原理
    • AQS 组件
    • ThreadLocal 原理以及使用
  • 非关系型数据库

    • Redis 集群
    • Redis 数据结构、对象与数据库
    • Redis 学习笔记
  • 关系型数据库

    • B+ 树的插入、删除和数据页分裂机制
    • MySQL 的 binglog、redolog、undolog
    • MySQL 的记录存储结构、存储引擎与 Buffer Pool
    • MySQL 基本的特性
    • MySQL 开发规范
    • MySQL 事务与锁与 MVCC
    • MySQL 数据类型、字符集相关内容
    • MySQL 索引与索引优化
    • PostgreSQL 更新数据时 HOT优化
    • PostgreSQL 相关用法
  • Python

    • Python 基础语法
    • Python 学习
  • Spring 项目

    • Lombok 的常用注解
    • maven 小结
    • MyBatis 框架的使用
    • MyBatis 重要知识点总结
    • MybatisPlus 的使用
    • Spring 框架基础使用
    • Spring 事务相关
    • Spring IOC 的原理及源码
    • Spring AOP 的使用和原理
    • SpringBoot 的原理
    • SpringBoot 基础使用
    • SpringWeb 重要知识点
  • 分布式

    • 初步了解 docker
    • 从 ACID 到 BASE 事务处理的实现
    • 访问远程服务
      • 进程间通信
      • 关于 RPC 的三个基本问题
      • REST 设计风格
        • 基本概念
        • REST 的六大原则
        • 客户端与服务端分离
        • 无状态
        • 可缓存
        • 分层系统
        • 统一接口
        • 按需代码
        • RMM
        • The Swamp of Plain Old XML:完全不 REST
        • Resources:开始引入资源的概念
        • HTTP Verbs:引入统一接口
        • Hypermedia Controls:超媒体控制(超文本驱动)
      • RPC 框架的超时实现原理
      • RPC 框架的熔断实现原理
    • 分布式 id
    • 分布式缓存相关问题
    • 分布式集群理论和分布式事务协议
    • 分布式架构的观测
    • 分布式一致性算法
    • 负载均衡 Load Balancing
    • 关于分布式系统 RPC 中高可用功能的实现
    • 集群间数据同步的目的
    • 三高问题下的系统优化
    • 数据库分库分表
    • 详解 Spring Cloud
    • Dubbo 基础概念
    • Gossip 协议
    • nginx 学习笔记
    • Protobuf 通信协议
    • Zookeeper 基础学习
  • 架构设计

    • 参数校验与异常处理
    • 抽象方法与设计模式
    • 代码整洁之道
    • 权限系统设计
    • 用低内存处理大量数据
    • 设计模式——策略模式
    • 设计模式——过滤器模式在 Spring 中的实践
    • 状态模式
    • 统一结果返回
    • 为什么要打日志?怎么打日志?打什么日志?
    • 运维监控常见指标含义
    • 资深研发进阶
    • DDD 架构学习笔记
    • Java 常用的规则引擎
    • MVC 架构学习笔记
  • AI

    • 如何编写 Prompt
    • Agent 工程架构
    • LLM 相关内容
    • NLP 相关知识
    • vibe coding 最佳实践
    • windows 下 ollama 迁移到 D 盘
  • 开发工具

    • 如何画时序图、流程图、状态流转图
    • excel 关于 =vlookup 的用法
    • git 的学习以及使用
    • IDEA 插件推荐
    • IDEA 常用快捷键以及调试
    • Shell 脚本
    • swagger 的使用
  • 前端

    • 简单了解前端页面开发
    • 伪静态是什么
    • GitHub Pages 部署教程
    • Vercel 部署教程
    • vue-admin-template 简单使用
    • VuePress 博客搭建指南
  • 项目

    • 面试刷题网——技术方案
    • 影视资源聚合站——技术方案
  • 问题记录

    • 定时任务单线程消费 redis 中数据导致消费能力不足
    • 提供可传递的易受攻击的依赖项
    • Liteflow 在 SpringBoot 启动时无法注入组件问题 couldn‘t find chain with the id[THEN(NodeComponent)]
  • 金融

    • 股票分析——关于电力
    • 股票技术面——量价关系
    • 股票技术面——盘口
    • 股票技术面——基础
    • 基础的金融知识
    • 基金与股票
    • 韭菜的自我总结
    • 聊聊价值投资
  • 其他

    • 程序员职场工作需要注意什么
    • 创业全链路SOP:从灵光一现到系统化增长的实战指南
    • 观罗翔讲刑法随笔
    • 价格和价值
    • 立直麻将牌效益理论
    • 梅花易数学习笔记
    • 压力管理
2023-05-04
分布式
目录

访问远程服务

为了将我们的系统设计的更加复杂一点,让它的功能更加多一些,让用户的体验更好一些,我们应该将单机的服务慢慢转换成分布式的系统,而分布式的系统有一个非常重要的前提条件:各个主机之间是如何交互的

# 进程间通信

远程服务调用的出现是为了让计算机能够跟调用本地方法一样去调用远程方法,而刚好 Socket 是网络栈的统一接口,各个系统中也都有提供标准接口可以直接使用

那我们先从先从进程间通信(Inter-Process Communication,本地 IPC 通讯)讲起,在计算机操作系统中,进程间的通信方式应该大致分为四类,加上进程间同步,一个可以数出以下几种方式:

  • 共享内存:两个进程共享同一块物理内存空间,这种方式需要依靠某种同步操作,如互斥锁和信号量等,因为可能会出现修改丢失问题,所以两个进程对内存空间的访问必须是互斥的

  • 管道通信:管道是一种在内存中具有一定空间的缓冲区,在 Linux 系统中叫 pipe 文件,与在磁盘中的 .txt 等文件相似,只能一个进程向文件中写入数据,另一个进程从文件中读取数据,一个进程写数据时另外一个进程不能读数据,两个进程需要互斥访问管道;管道又分匿名管道和有名管道。我们经常使用的命令行中的 “ | ” 操作符,就是典型的管道

  • 消息传递:直接将消息从一个进程传到另一个进程中,消息(message)是一种封装了数据的东西,比如报文;这种通信方式又分直接通信(通过系统源语实现,一个进程通过发送源语将消息挂到另一个进程的消息访问队列中,进程通过接受源语接受)和间接通信(消息队列),信号和消息队列应该是属于消息传递的

  • 套接字接口:主要实现为套接字(一个存放了目的地址、目的端口、传输层协议、自己地址的数据结构)、RPC,主要用与网络间进程的通信,不过也可以在同一台主机上通信(原始套接字)

  • 信号:信号用于通知目标进程有某种事件发生,比如 kill 命令,由 shell 进程向指定的 pid 发送消息

  • 信号量:信号量用于在两个进程之间同步协作,相当与操作系统提供的特殊变量,程序可以通过这个进行 wait 与 notify

# 关于 RPC 的三个基本问题

远程服务调用是指位于互不重合的内存地址空间中的两个程序,在语言层面上,以同步的方式使用带宽有限的信道来传输程序控制信息

在实现这个的过程中我们慢慢发现 RPC 一定绕不开三个问题,几乎所有的 RPC 协议都是围绕着解决以下三个基本问题:

  • 如何表示数据:这里的数据包括传递给方法的参数以及方法执行后的返回值在,这些在网络中如何表示呢。在不同的硬件指令集、操作系统下,同样的数据有2不同的表现实现,可行的做法是将两边的数据定义事先约定好的中立数据结构来传递。对,就是序列化
  • 如何传递数据:我们有了数据在网络中的表示,那这些数据该如何传输呢,我们是用 http 还是 tcp?发生异常、超时、事务等情况的时候,网络如何处理呢
  • 如何确定方法:在本地调用方法的时候,编译器会根据指针确定方法的位置,但是在 rpc 里,每种语言的方法签名都有可能有差别,如何表示同一个方法需要有一个统一的跨语言的标准才行,比如用一个 UUID 来确定方法

# REST 设计风格

很多人会拿 rest 与 rpc 来比较,REST 是一种面向资源编程的风格(REST 是 Representational State Transfer 表征状态转移的缩写),是风格而不是协议,具体如何实现他们没有限定死

REST 无论是在思想上、概念上,还是使用范围上,与 RPC 都不尽相同,即面向资源的编程思想与面向过程的编程思想两者之间的区别

由于绑定 HTTP 协议,性能瓶颈也明显,但在移动端、桌面端或者分布式服务端的节点之间通讯这一块,REST 照样有宽阔的用武之地

# 基本概念

先来了解一些 REST 的概念:

  • 资源:也就是内容本身,比如你正在读一篇<<REST设计风格>>的文章,这篇文章的内容称之为资源。可以理解为信息、数据

  • 表征:资源的表达形式,如 HTML,PDF 等

  • 状态:上下文状态。当你读完这篇文章,想看后面是什么内容时,你向服务端发出给我下一篇文章的请求。但是下一篇是个相对的概念,必须依赖你当前正在阅读的文章是哪一篇才能正确回应,这类在特定语境中产生的上下文信息被称为状态

  • 转移:无论状态是由服务端还是客户端来提供,取下一篇文章这个行为逻辑只能由服务端来提供,因为只有服务端拥有该资源及其表征形式。服务端通过某种方式,把用户当前阅读的文章转变成下一篇文章,这就被称为表征状态转移

  • 统一接口:HTTP 请求方法就是统一接口

  • 超文本驱动:PC 软件客户端一般都是有专门的控制器来驱动状态转移,而 web 端是由服务器发出响应来驱动的,也叫超文本驱动。任何网站的导航(状态转移)行为都不是预置于浏览器代码中的,而是通过服务器发出的请求响应信息(超文本)来驱动的

  • 自描述消息:资源可能有多种表征,所以消息中一般有自描述信息,即告知服务器该消息的类型的消息,如 “Content-Type”

# REST 的六大原则

一套理想的、完全满足 REST 的系统应该满足以下六大原则:

# 客户端与服务端分离

就是我们通常说的网页,前后端分离,将用户界面所关注的逻辑和数据存储所关注的逻辑分离开来。与之对应的是以前完全基于服务端控制和渲染(如 JSF 这类)框架

# 无状态

无状态是 REST 的核心原则,REST 风格的服务设计的别扭很可能的一个原因是服务端持有着比较重的状态。REST 希望服务器不要去负责维护状态,每一次从客户端发送的请求中,应包括所有的必要的上下文信息,会话信息也由客户端负责保存维护,服务端依据客户端传递的状态来执行业务处理逻辑,驱动整个应用的状态变迁

可以将 Cookie 的使用当成无状态的实践之一,但是极大部分系统都不可能达到真正的无状态,因为大量的上下文状态会膨胀到客户端无法承受的程度

# 可缓存

REST 希望软件系统能够如同万维网一样,允许客户端和中间的通讯传递者(譬如代理)将部分服务端的应答缓存起来。缓存整个应答的资源数据

# 分层系统

这里所指的并不是表示层、服务层、持久层这种意义上的分层。而是指客户端一般不需要知道是否直接连接到了最终的服务器,抑或连接到路径上的中间服务器。中间服务器可以通过负载均衡和共享缓存的机制提高系统的可扩展性,这样也便于缓存、伸缩和安全策略的部署。该原则的典型的应用是内容分发网络 CDN,比如 GitHub Pages 的源服务器与国内的 CDN 服务器

分层系统一定程度上是可缓存的实现,而可缓存是分层系统的思想

# 统一接口

REST 希望开发者面向资源编程,希望软件系统设计的重点放在抽象系统该有哪些资源上,而不是抽象系统该有哪些行为(服务)上。面向资源编程的抽象程度通常更高。抽象程度高意味着坏处是往往距离人类的思维方式更远,而好处是往往通用程度会更好,简单来说是每个资源有唯一 URI 标识

那到底什么是统一接口呢,行为与资源的区别是什么呢?

行为是指我们提供一个接口,这个接口实现了用户下单的操作,但是接口要求将操作的每一个数据都尽可能传给后端来处理这个下单操作,比如要传入:产品的标题、数量、金额、属性等等等等,同时要传入购买人的属性,比如姓名、身份证啥的。而面向资源则是推荐将产品浓缩成资源 ID,联系人也使用 user id 来表示,其他的信息后端从数据库中获取

要在架构设计中合理恰当地利用统一接口,Fielding 建议:

  • 系统应能做到每次请求中都包含资源的 ID,所有操作均通过资源 ID 来进行(当然在实际中不用这样,可以将一些比较重的数据用 ID 表示,比如订单信息、产品信息等,而三四个字段就直接传给后端就可以了)
  • 每个资源都应该是自描述的消息
  • 通过超文本来驱动应用状态的转移

# 按需代码

按需代码被 Fielding 列为一条可选原则。它是指任何按照客户端(譬如浏览器)的请求,将可执行的软件程序从服务器发送到客户端的技术,按需代码赋予了客户端无需事先知道所有来自服务端的信息应该如何处理、如何运行的宽容度

REST 的以资源为主体的服务设计风格,可以带来不少好处:

  • 降低的服务接口的学习成本:统一接口(Uniform Interface)是 REST 的重要标志,将对资源的标准操作都映射到了标准的 HTTP 方法上去
  • 资源天然具有集合与层次结构
  • REST 绑定于 HTTP 协议。面向资源编程不是必须构筑在 HTTP 之上,但 REST 是,这是缺点,也是优点。因为 HTTP 本来就是面向资源而设计的网络协议

# RMM

《RESTful Web APIs》和《RESTful Web Services》的作者 Leonard Richardson 曾提出过一个衡量“服务有多么 REST”的 Richardson 成熟度模型(Richardson Maturity Model),便于那些原本不使用 REST 的系统,能够逐步地导入 REST

# The Swamp of Plain Old XML:完全不 REST

即 RPC 的调用方式,例子如下

医院开放了一个/appointmentService的 Web API,传入日期、医生姓名作为参数,可以得到该时间段该名医生的空闲时间,该 API 的一次 HTTP 调用如下所示:

POST /appointmentService?action=query HTTP/1.1
{date: "2020-03-04", doctor: "mjones"}
1
2

然后服务器会传回一个包含了所需信息的回应:

HTTP/1.1 200 OK
[
	{start:"14:00", end: "14:50", doctor: "mjones"},
	{start:"16:00", end: "16:50", doctor: "mjones"}
]
1
2
3
4
5

得到了医生空闲的结果后,我觉得 14:00 的时间比较合适,于是进行预约确认,并提交了我的基本信息:

POST /appointmentService?action=comfirm HTTP/1.1
{
	appointment: {date: "2020-03-04", start:"14:00", doctor: "mjones"},
	patient: {name: icyfenix, age: 30, ……}
}
1
2
3
4
5

如果预约成功,那我能够收到一个预约成功的响应。如果发生了问题,譬如有人在我前面抢先预约了,那么我会在响应中收到某种错误信息:

HTTP/1.1 200 OK
{
	code: 0,
	message: "Successful confirmation of appointment"
}

HTTP/1.1 200 OK
{
	code: 1
	message: "doctor not available"
}
1
2
3
4
5
6
7
8
9
10
11

# Resources:开始引入资源的概念

第 0 级是 RPC 的风格,如果需求永远不会变化,也不会增加,那它完全可以良好地工作下去。但是,如果你不想为预约医生之外的其他操作、为获取空闲时间之外的其他信息去编写额外的方法,或者改动现有方法的接口,那还是应该考虑一下如何使用 REST 来抽象资源

通往 REST 的第一步是引入资源的概念,在 API 中基本的体现是围绕着资源而不是过程来设计服务,说的直白一点,可以理解为服务的 Endpoint 应该是一个名词而不是动词。此外,每次请求中都应包含资源的 ID,所有操作均通过资源 ID 来进行(主键 ID)

POST /doctors/mjones HTTP/1.1
{date: "2020-03-04"}
1
2

然后服务器传回一组包含了 ID 信息的档期清单,注意,ID 是资源的唯一编号,有 ID 即代表“医生的档期”被视为一种资源:

HTTP/1.1 200 OK
[
	{id: 1234, start:"14:00", end: "14:50", doctor: "mjones"},
	{id: 5678, start:"16:00", end: "16:50", doctor: "mjones"}
]
1
2
3
4
5

我还是觉得 14:00 的时间比较合适,于是又进行预约确认,并提交了我的基本信息:

POST /schedules/1234 HTTP/1.1
{name: icyfenix, age: 30, ……}
1
2

# HTTP Verbs:引入统一接口

第 1 级遗留三个问题都可以靠引入统一接口来解决。HTTP 协议的七个标准方法是经过精心设计的,只要架构师的抽象能力够用,它们几乎能涵盖资源可能遇到的所有操作场景。REST 的做法是把不同业务需求抽象为对资源的增加、修改、删除等操作来解决第一个问题;使用 HTTP 协议的 Status Code,可以涵盖大多数资源操作可能出现的异常,而且 Status Code 也是可以自定义扩展,以此解决第二个问题;依靠 HTTP Header 中携带的额外认证、授权信息来解决第三个问题,这个在实战中并没有体现,请参考安全架构中的“ 凭证 ”相关内容

按这个思路,获取医生档期,应采用具有查询语义的 GET 操作进行:

GET /doctors/mjones/schedule?date=2020-03-04&status=open HTTP/1.1
1

然后服务器会传回一个包含了所需信息的回应:

HTTP/1.1 200 OK
[
	{id: 1234, start:"14:00", end: "14:50", doctor: "mjones"},
	{id: 5678, start:"16:00", end: "16:50", doctor: "mjones"}
]
1
2
3
4
5

我仍然觉得 14:00 的时间比较合适,于是又进行预约确认,并提交了我的基本信息,用以创建预约,这是符合 POST 的语义的:

POST /schedules/1234 HTTP/1.1
{name: icyfenix, age: 30, ……}
1
2

如果预约成功,那我能够收到一个预约成功的响应:

HTTP/1.1 201 Created
Successful confirmation of appointment
1
2

如果发生了问题,譬如有人在我前面抢先预约了,那么我会在响应中收到某种错误信息:

HTTP/1.1 409 Conflict
doctor not available
1
2

# Hypermedia Controls:超媒体控制(超文本驱动)

第 2 级是目前绝大多数系统所到达的 REST 级别(这是有原因的)。但仍不是完美的,至少还存在一个问题:你是如何知道预约 mjones 医生的档期是需要访问/schedules/1234这个服务 Endpoint 的?也许你甚至第一时间无法理解为何我会有这样的疑问,这当然是程序代码写的呀!但 REST 并不认同这种已烙在程序员脑海中许久的想法。RMM 中的 Hypermedia Controls、Fielding 论文中的 HATEOAS 和现在提的比较多的“超文本驱动”,所希望的是除了第一个请求是有你在浏览器地址栏输入所驱动之外,其他的请求都应该能够自己描述清楚后续可能发生的状态转移,由超文本自身来驱动。所以,当你输入了查询的指令之后:

GET /doctors/mjones/schedule?date=2020-03-04&status=open HTTP/1.1
1

服务器传回的响应信息应该包括诸如如何预约档期、如何了解医生信息等可能的后续操作:

HTTP/1.1 200 OK
{
	schedules:[
		{
			id: 1234, start:"14:00", end: "14:50", doctor: "mjones",
			links: [
				{rel: "comfirm schedule", href: "/schedules/1234"}
			]
		},
		{
			id: 5678, start:"16:00", end: "16:50", doctor: "mjones",
			links: [
				{rel: "comfirm schedule", href: "/schedules/5678"}
			]
		}
	],
	links: [
		{rel: "doctor info", href: "/doctors/mjones/info"}
	]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

如果做到了第 3 级 REST,那服务端的 API 和客户端也是完全解耦的,你要调整服务数量,或者同一个服务做 API 升级将会变得非常简单。但是缺点是将原先在客户端就可以使用的行为放到了服务端,并且用大量的数据来处理状态转移这件事就很有争议

# RPC 框架的超时实现原理

consumer 调用 provider,如果不设置超时时间,则 consumer 的响应时间肯定会大于 provider 的响应时间。当 provider 性能变差时,consumer 的性能也会受到影响,因为它必须无限期地等待 provider 的返回。因此服务间调用会有一些降级、熔断、重试、超时机制存在,这些机制保证了分布式系统的健壮性,现在很多大厂都有一个统一的平台来管理系统间调用的配置,那在底层他们是任何实现超时配置的呢?我们以 dubbo 底层实现机制做讲解

dubbo 的每个调用端设置的超时时间都不一样,每个被调用端也可以设置超时时间,dubbo 会以下面的优先级来设置时长:消费端方法级 > 服务端方法级 > 消费端接口级 > 服务端接口级 > 消费端全局 > 服务端全局。当调用端发起请求时,它会将超时时间作为请求的一部分传递给被调用端。被调用端在处理请求时会根据这个超时时间来进行超时判断

接口级配置:

  <dubbo:reference interface="com.example.DemoService" timeout="5000" />
1

方法级配置:

  <dubbo:reference id="demoService" interface="com.example.DemoService">
      <dubbo:method name="specificMethod" timeout="3000" />
  </dubbo:reference>
1
2
3

dubbo 的超时时间判断实现逻辑在被调用端,调用端代码只是打了一个 warn 日志

    public Result invoke(...) throws RpcException {
        // 执行真正的逻辑调用,并统计耗时
        long start = System.currentTimeMillis();
        Result result = invoker.invoke(invocation);
        long elapsed = System.currentTimeMillis() - start;

        // 判断是否超时
        if (invoker.getUrl() != null && elapsed > timeout) {
            // 打印warn日志
            logger.warn("invoke time out...");
        }

        return result;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

被调用端的代码如下,注意到下面有个重试次数的判断,之前我以为重试是调用端行为,现在看是将重试次数作为参数一起穿给被调用端了:

public class FailoverClusterInvoker {

    public Result doInvoke(...)  {
        ...
        // 循环调用设定的重试次数
        for (int i = 0; i < retryTimes; ++i) {
            ...
            try {
                Result result = invoker.invoke(invocation);
                return result;
            } catch (RpcException e) {
                // 如果是业务异常,终止重试
                if (e.isBiz()) {
                    throw e;
                }

                le = e;
            } catch (Throwable e) {
                le = new RpcException(...);
            } finally {
                ...
            }
        }

        throw new RpcException("...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

被调用端的超时判断如下:

public Object get(int timeout) {
    if (timeout <= 0) {
        timeout = 1000;
    }

    if (!isDone()) {
        long start = System.currentTimeMillis();
        this.lock.lock();

        try {
            // 循环判断
            while(!isDone()) {
                // 放弃锁,进入等待状态
                done.await((long)timeout, TimeUnit.MILLISECONDS);

                // 判断是否已经返回结果或者已经超时
                long elapsed = System.currentTimeMillis() - start;
                if (isDone() || elapsed > (long)timeout) {
                    break;
                }
            }
        } catch (InterruptedException var8) {
            throw new RuntimeException(var8);
        } finally {
            this.lock.unlock();
        }

        if (!isDone()) {
            // 如果未返回结果,则抛出超时异常
            throw new TimeoutException(...);
        }
    }

    return returnFromResponse();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

这里新起了一个线程来占用 CPU 资源,来判断请求是否超时,还加了一把锁,可能是为了防止多个重复请求。这里可以使用类似延迟消息的思路,起个时间轮管理每个接口的超时时间

# RPC 框架的熔断实现原理

也来简单聊一下熔断机制的实现吧:

  • 1,关闭状态(Closed):正常情况下,服务处于关闭状态,允许请求通过
  • 2,打开状态(Open):当服务出现故障(如连续多次调用失败)时,切换到打开状态,直接返回错误结果,不向后端服务发送请求
  • 3,半开状态(Half-Open):经过一段时间后,从打开状态切换到半开状态,允许少量请求通过,以检测服务是否恢复。如果请求成功,则切换回关闭状态;否则,重新切换到打开状态

熔断器的计数器通常是在客户端的熔断器实例中维护的,而不是注册中心,注册中心不负责熔断的统计,时间窗口(滑动窗口或固定窗口)是熔断策略的一部分,比如Hystrix使用滑动窗口来统计最近10秒的请求,失败率超过50%就触发熔断

#远程服务调用
最后更新: 2/24/2026, 12:56:39 PM
从 ACID 到 BASE 事务处理的实现
分布式 id

← 从 ACID 到 BASE 事务处理的实现 分布式 id→

最近更新
01
vibe coding 最佳实践
02-24
02
立直麻将牌效益理论
02-23
03
伪静态是什么
02-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式