面试漫谈:缓存的三大场景与分布式锁

缓存三大问题 缓存穿透 缓存穿透指的是客户端请求查询一个在缓存中和数据库中都“绝对不存在”的数据。 由于缓存中没有(缓存未命中),请求会“穿透”缓存层,直接打到数据库层。如果这种请求量很大(例如,恶意的攻击者使用大量不存在的ID进行查询),数据库将承受巨大的压力,可能导致服务宕机。 做法 (Solutions) 缓存空值 (Cache Null Values) 做法: 当数据库查询返回为空(null)时,我们依然将这个“空结果”缓存起来,但为其设置一个较短的过期时间(TTL),例如60秒。 优点: 实现简单,立竿见影。 缺点: 会占用缓存空间(尽管很短)。 存在短暂的数据不一致性:如果在这60秒内,数据库中恰好创建了这条数据,缓存返回的依然是“空”,直到缓存过期。 布隆过滤器 (Bloom Filter) 做法: 布隆过滤器是一种高效的、概率性的数据结构,它可以用极小的空间判断一个元素“一定不存在”或“可能存在”。 流程: 启动时,将数据库中所有合法的Key加载到布隆过滤器中。 当请求到来时,先去布隆过滤器查询Key是否存在。 如果布隆过滤器判断“一定不存在”,则立即返回空,绝对不会去查缓存和数据库。 如果布隆过滤器判断“可能存在”,则继续执行后续的查询缓存、查询DB的流程。 优点: 空间效率和查询效率极高,完美解决了穿透问题。 缺点: 实现相对复杂。 存在“误判率”(False Positive):它判断“可能存在”时,数据实际上可能不存在(但它绝不会把“存在”的判为“不存在”)。 数据新增时,需要同步更新布隆过滤器(删除操作不支持或很麻烦)。 面试要点 Q: 什么是缓存穿透?它和击穿有什么区别? A: 穿透是查不存在的数据,导致请求绕过缓存直达DB。击穿是查存在的数据,但这个热点数据刚过期,导致请求直达DB。 Q: 两种解决方案(缓存空值 vs 布隆过滤器)如何选择? A: 缓存空值简单,适用于恶意攻击不频繁、允许短暂不一致的场景。布隆过滤器更可靠,适用于数据“黑名单”明确、查询QPS极高、对恶意攻击防护要求高的场景(例如,防止用户ID的恶意爬取)。 缓存击穿 缓存击穿指的是某一个访问量极高的热点Key (Hotspot Key),在它失效的那一瞬间,海量的并发请求同时涌入,导致这些请求全部“击穿”缓存,直接访问数据库,造成数据库瞬时压力剧增,甚至宕机。 做法 (Solutions) 互斥锁 / 分布式锁 (Mutex Lock / Distributed Lock) 做法: 这是最经典的解决方案。当缓存未命中时,不是所有线程都去查数据库。 线程A尝试获取该Key的分布式锁(例如 lock:product:123)。 线程A获取锁成功,开始查询数据库、重建缓存。 线程B、C、D… 发现缓存未命中,尝试获取锁,但失败。 线程B、C、D… 不去查数据库,而是进入“等待”状态(例如,自旋或休眠后重试)。 线程A重建缓存后,释放锁。 线程B、C、D… 再次(或被唤醒)查询时,直接命中缓存。 优点: 强一致性,只允许一个请求“穿透”到DB,有效保护数据库。 缺点: 实现复杂,需要引入分布式锁(如Redis或Zookeeper)。 性能下降,大量线程在锁上等待,导致系统吞吐量降低。 逻辑过期 (Logical Expiration)...

November 14, 2025 · 501 words · Kurong

信也实习内容整理

第一章 全面的风险业务流程分析 为了深入理解海外风险中台的技术架构,首先必须对其服务的核心业务领域建立清晰的认知。本章将从业务视角出发,解构平台所支撑的完整风险管理生命周期,并以三个典型的业务场景——同步决策的“展期”流程、异步决策的“授信”(“戳额”)流程以及人机结合的“人工审核”流程——作为案例,详细剖析业务逻辑与系统执行之间的精密互动。 核心业务概念与范畴 在金融科技领域,风险是指在未来时间范围内,可能发生某种损失的不确定性。对于公司的海外借贷业务而言,风险特指借款人到期无力或无意偿还债务,从而造成债权人本金与收益蒙受部分或全部损失的可能性。风险控制(风控)的目标并非消除风险,而是通过一系列技术和策略手段,将潜在的风险转化为实际损失的可能性降至最低。 该海外风险中台正是为实现这一目标而构建的核心系统,其服务范围覆盖了菲律宾、巴基斯坦、墨西哥、印尼等多个国家站点,支撑着包括现金贷(Cashloan)、电商消费分期(Lazada FC, TikTok, PPL)、针对海外务工人员的专项贷款(OFW-CL)在内的多种业务线。平台处理的业务事件贯穿用户生命周期的各个关键节点,包括但不限于用户注册、授信申请、发标、提现、还款行为分析以及贷款展期等。 案例研究一:同步决策 — 贷款“展期”流程 同步决策流程适用于那些要求系统在短时间内给出明确答复、用户正在实时等待结果的业务场景。贷款展期便是一个绝佳的例子,它代表了一种低延迟、高确定性的交互模式。 根据系统时序图的分析,一个典型的展期风控流程调用链路如下: 请求发起:上游业务系统(client)因用户申请展期,向风险中台的展期服务(PhiLoanExtension)发起一个同步API调用。 流程实例化:PhiLoanExtension服务接收到请求后,立即组装并实例化一个名为RiskFlow的核心流程对象。这个对象将作为本次展期风控任务的上下文,贯穿整个执行过程。 数据扩充(变量计算):为了做出决策,系统需要获取与该用户和该笔贷款相关的风险特征。PhiLoanExtension服务会调用内部的“字段变量中心”服务(Irs)的/calVars接口,请求计算展期场景所需的特定字段变量值。Irs服务完成计算后,将结果同步返回。 规则引擎预处理:获得所有必需的变量后,PhiLoanExtension服务根据预设的模板,将这些变量和值组装成规则引擎(drools)能够理解的数据格式。 决策执行与审计:在调用规则引擎之前,系统会将格式化后的请求数据写入日志表risk_flow_log,以备审计和问题追溯。随后,PhiLoanExtension向drools服务发起决策请求。 结果处理与返回:drools引擎执行相应的规则集,并返回决策结果(如“批准展期”、“拒绝展期”)。PhiLoanExtension服务在接收到响应后,再次将其写入risk_flow_log,完成审计闭环。最后,将最终决策结果通过API响应同步返回给最初的调用方client。 案例研究二:异步决策 — “授信”(“戳额”)流程 与展期不同,新用户的授信审批是一个计算密集型且更为复杂的过程。它可能需要调用多个第三方数据源、运行多个复杂的机器学习模型,并可能涉及人工审核环节。这类操作耗时较长,无法在一次同步API调用中完成。因此,系统采用异步处理模式,以优化用户体验和系统吞吐量。 一个典型的异步授信流程如下: 请求接收:用户通过前端应用提交授信申请。上游业务系统(client)将请求发送至风险中台的异步流程入口服务(phi-quechao)。 任务入队:phi-quechao服务在接收到请求后,进行初步的校验和处理,然后将该授信任务封装成一个消息,投递到消息队列(如RabbitMQ)中。投递成功后,它会立即向上游返回一个“处理中”的响应,此时用户界面可以显示“审核中,请耐心等待”,从而将前端交互与后端复杂的处理流程解耦。 任务调度与执行:后台的异步任务批处理服务(phi-job)作为消息队列的消费者,会拉取授信任务。phi-job将任务分发给异步风控业务的调度中心(phi-risk)。 核心风控流程编排:phi-risk服务是整个异步流程的大脑,它依据预定义的Pipeline(管道),按顺序调度执行一系列子任务,这通常包括: 调用Irs服务计算上百个甚至上千个风险变量。 调用模型编排服务(phi-modelschedual),执行多个评分模型。 调用drools引擎,根据变量和模型分执行复杂的策略规则。 决策生成与通知:所有计算和规则执行完毕后,决策服务(phi-riskdecision)汇总结果,生成最终的授信决策(如授信额度、费率、评级或拒绝)。这个结果会被持久化到数据库,并通过另一个消息队列主题,将“决策完成”的事件通知给下游系统,例如更新用户状态、发送通知等。 案例研究三:人机结合 — “人工审核”流程 对于机器决策无法明确判断的“灰色”用户,系统会将其转入人工审核流程,结合人类专家的经验进行最终裁定。这是一个典型的“人机结合”场景,对系统的任务调度、状态管理和外部通信提出了更高的要求。 以巴基斯坦站的人审流程为例,其技术实现如下: 触发与任务生成:当自动化风控流程(如授信)的规则引擎决策结果为“转人工”(manualFlag: 1)时,risk-decision服务会将一个包含用户ID、业务ID和流程ID的人审任务写入任务表(tb_risk_flow_task)中。 后台任务调度:系统采用xxlJob作为分布式任务调度框架。一个名为RiskFlowTaskNonExecutionHandler的任务执行器会定时扫描任务表,捞取待处理的人审任务。 任务执行与数据准备:任务执行器获取任务后,会调用Decision服务,准备该用户用于人工审核所需的各类数据(如基础资料、风险标签等)。 结果持久化与通知:审核员在人审系统(Capital Pata)中完成审核并提交结果。该结果会被Decision服务接收并持久化到人审记录表(tb_risk_user_record)中。该表根据user_id进行了分表处理(如_00到_05),以分散写入压力。 下游通知:决策完成后,Decision服务会将最终的人审结果(如manualTags)通过消息队列(MQ)广播出去。上游业务系统或其他相关方订阅相应的主题(如prod-drools-manual-result),即可获取审核结果并进行后续处理。 失败重试:为了保证流程的健壮性,系统还配置了RiskFlowTaskFailRetryHandler,用于处理执行失败的人审任务,进行自动重试。 业务模式背后的架构考量 系统之所以同时支持同步、异步和人机结合三种处理模式,是基于对不同业务场景下延迟、吞吐量和计算复杂度三者之间权衡的结果。 展期这类操作,业务逻辑相对简单,依赖的数据大部分已存在于系统内部,决策路径短。用户正在界面上等待操作结果,因此低延迟是首要目标。采用同步模型可以提供最直接、最快速的用户反馈。 相比之下,新用户授信是典型的重决策场景。它需要从外部拉取征信数据,处理用户设备信息(SDK数据),运行复杂的反欺诈和信用评分模型。整个过程可能耗时数秒甚至数分钟。如果采用同步模型,不仅会导致用户长时间等待,造成糟糕的体验,还会长时间占用API网关和前端服务的连接资源,极大地限制系统的并发处理能力和整体吞吐量。异步架构通过消息队列作为缓冲,将前端请求的接收与后端耗时的处理彻底分离。这种设计使得系统能够平稳地处理流量洪峰,并通过水平扩展消费者(phi-job)来提高整体处理能力,保证了系统的伸缩性和鲁棒性。 人工审核流程则体现了系统处理复杂、非确定性问题的能力。通过引入任务调度系统(xxlJob)和人工干预节点,风控决策不再是非黑即白的二元判断,而是形成了一个“自动化初筛 -> 机器决策 -> 人工复核”的完整闭环,在效率和准确性之间取得了最佳平衡。 这种多模架构的设计,体现了平台在满足多样化业务需求与保证系统高性能、高可用性之间的精妙平衡,是其能够支撑多国家、多业务线快速发展的关键架构决策之一。 第二章 海外风险平台的统一架构 尽管海外风险中台服务于菲律宾、巴基斯坦等多个国家,且经历了从Python到Go语言的技术栈演进,但其核心架构思想和功能模块划分具有高度的一致性。本章将通过对不同站点的架构图进行综合分析,提炼出一个统一的、具有代表性的平台架构蓝图,并明确各个核心服务模块的职责定位。 统一架构蓝图 通过综合分析菲律宾和巴基斯坦的风控系统架构图,可以描绘出一个通用的分层架构模型。该模型自上而下分为接入层、编排层、执行层和基础支撑层。 接入层 (Entry Layer):作为风险系统的统一入口,负责接收和校验来自上游业务系统(如merchant、Rating服务)的各类风控请求。它将外部请求转化为内部标准化的风控任务,并根据请求类型(同步/异步)将其路由到相应的处理流程。 编排与调度层 (Orchestration/Scheduling Layer):这是风险决策流程的核心控制器。它负责解析风控任务,并根据预设的业务流程(Pipeline)或工作流配置,有序地调度执行层中的各个原子能力(如变量计算、模型调用、规则执行)。该层确保了复杂决策逻辑能够被准确、高效地执行。 执行层 (Execution Layer):由一系列功能高度内聚的专业化服务组成,提供风控决策所需的原子计算能力。主要包括: 变量计算服务:负责根据原始数据计算生成衍生特征(即风险变量)。 模型执行服务:负责调用和执行部署在模型平台上的各类机器学习或评分卡模型。 规则引擎服务:负责执行预定义的业务规则集(如反欺诈规则、授信策略)。 决策与持久化层 (Decision & Persistency Layer):该层负责汇总来自执行层的各种结果(变量值、模型分、规则命中情况),并做出最终的业务决策。同时,它还将整个决策过程的输入、中间结果和最终输出完整地记录下来,用于后续的审计、分析和模型迭代。 基础支撑层 (Infrastructure Layer):为整个风险中台提供稳定运行所需的通用技术组件。这包括: 数据库:采用关系型数据库(如MySQL, PolarDB)存储结构化业务数据,并使用NoSQL数据库(如MongoDB)存储三方数据、SDK埋点日志等半结构化或非结构化数据。 消息中间件:使用RabbitMQ进行服务间的异步通信和任务解耦,同时引入Kafka用于大规模数据的实时流式处理。 配置中心:利用Apollo实现所有微服务配置的集中化、动态化管理,提高运维效率和系统灵活性。 数据服务:统一的数据中台(data-hub)提供对外的标准数据查询接口,屏蔽底层数据源的复杂性。 核心微服务及其功能职责 下表详细梳理了风险中台在技术栈演进过程中,核心功能模块与具体微服务之间的映射关系。它清晰地展示了从Python实现的旧版服务到Go语言重构的新版服务,其 underlying 的功能职责是如何传承和演进的。...

October 2, 2025 · 559 words · Kurong

apollo简单入门

在现代微服务架构中,服务数量的激增使得传统的配置文件管理方式(如本地文件、Git 仓库)变得越来越难以维护。配置的修改、发布、回滚、权限控制以及实时生效等需求,催生了分布式配置中心这一关键中间件。 Apollo (阿波罗) 是由携程框架部门研发的开源配置管理中心,它功能强大、生态丰富,能够集中化管理应用在不同环境、不同集群的配置。本文将带你深入了解 Apollo 的世界,从核心概念到架构原理,再到 Go 语言的实战集成。 一、Apollo 核心概念 要用好 Apollo,首先需要理解它的几个核心概念,这些概念构成了其灵活、强大的配置管理模型。 1. Application (应用) 这是配置管理的基本单元,代表一个独立的应用或服务。每个应用都有一个全局唯一的 AppId,这是客户端与服务端交互时的身份标识。 2. Environment (环境) 用于隔离不同部署环境的配置,如 DEV (开发环境)、FAT (测试环境)、UAT (预发布环境)、PRO (生产环境)。Apollo 的权限管理也是基于环境的,可以精细控制不同环境的配置修改和发布权限。 3. Cluster (集群) 一个应用在同一个环境下,可以部署在不同的集群中。这个概念主要用于支持多数据中心(IDC)或者隔离部署。例如,你可以为上海机房和北京机房创建两个不同的 Cluster,为它们配置不同的数据库地址。默认情况下,会有一个 default 集群。 4. Namespace (命名空间) 这是配置项的集合,是配置隔离的最小单元。通过 Namespace,可以把不同类型的配置分门别类地管理。 私有 Namespace:其作用域仅限于当前应用。每个应用创建时都会有一个默认的 application 命名空间。 公共 Namespace:可以被多个应用关联和共享。常用于存储一些公共的配置,如数据库连接信息、中间件地址等。 关联 Namespace (继承):应用可以关联(继承)公共 Namespace 的配置,实现配置的复用。如果私有配置与公共配置有相同的 Key,私有配置会覆盖公共配置。 格式支持:Namespace 支持多种格式,如 properties (默认)、XML、JSON、YAML、TXT。 5. Release (发布) 当一个 Namespace 的配置修改完成后,需要通过“发布”操作才能被客户端感知到。每一次发布都会生成一个唯一的版本号,且发布后的配置是 不可修改的。如果需要变更,只能创建一个新的发布。这个机制保证了配置的可追溯和快速回滚。 6. Gray Release (灰度发布) 这是 Apollo 的一大亮点。它允许你将新发布的配置先生效于指定的 部分 客户端实例上。你可以根据客户端的 IP 地址或预设的标签来圈定灰度范围。待验证无误后,再进行全量发布。这极大地提高了配置发布的安全性。...

September 14, 2025 · 539 words · Kurong

golang单元测试与冒烟测试入门

概念解析:单元测试 vs. 冒烟测试 1. 单元测试 (Unit Testing) 单元测试是针对程序中最小的可测试单元进行的测试。在 Go 语言中,这个“单元”通常指一个函数(Function)或一个方法(Method)。 核心目标:验证单个函数或方法的逻辑是否正确。它确保给定特定的输入,该函数能返回预期的输出。 关键原则:隔离。单元测试应该与其依赖项(如数据库、文件系统、外部API等)隔离开来。如果一个函数依赖了这些外部服务,我们通常会使用“模拟(Mock)”技术来创建一个假的依赖项,从而保证测试的稳定性和速度。 特点: 速度快:因为不涉及外部I/O操作,通常毫秒级就能完成。 粒度细:精确到每个函数,能够快速定位问题。 编写频繁:是开发过程中最常编写的测试类型。 2. 冒烟测试 (Smoke Testing) 冒烟测试,又称“构建验证测试(Build Verification Test)”,是一种非常基础的测试,用于确定软件的关键功能是否能够正常工作。它像是在给一个新设备通电后,看看它是否会冒烟——如果不冒烟,说明最基本的功能没问题,可以进行更详细的测试。 核心目标:验证应用程序的核心、关键流程是否能够跑通,确保程序在主要路径上不会崩溃。 关键原则:快速、宽泛、但不深入。它不关心业务逻辑的细枝末节,只关心“主干”是否健康。 特点: 速度较快:比完整的端到端测试快,但比单元测试慢。 覆盖面广:覆盖的是最关键的业务流程。 执行时机:通常在新版本构建完成后、进行全面测试之前执行。对于后端服务来说,可能是在服务启动后,立即调用几个核心API(如健康检查/health,登录接口等)看看是否返回200 OK。 Go 中的测试库 标准库 Go 语言内置了强大的测试支持,主要通过 testing 包来实现。这是所有 Go 测试的基础,你必须首先掌握它。 testing: 提供了编写测试的基本框架,例如 *testing.T 类型用于报告测试状态。 net/http/httptest: 专门用于测试 HTTP 客户端和服务器,无需真正监听端口即可模拟 HTTP 请求和响应,非常适合后端 API 测试。 流行的第三方库 虽然标准库很强大,但在某些方面,社区提供了更高效、更易读的工具。 stretchr/testify: 这是 Go 社区中最受欢迎的断言库。它提供了丰富的断言函数(如 assert.Equal(), assert.NoError()),让你的测试代码更简洁、更具可读性。 golang/mock (Gomock): Google 官方开发的 Mock 框架。当你需要隔离测试单元与它的依赖时,Gomock 可以根据接口自动生成模拟对象,让你完全控制依赖项的行为。 h2non/gock: 如果你的代码需要调用外部 HTTP API,这个库可以轻松地模拟这些外部请求,让你的测试不依赖于网络和第三方服务的稳定性。 从单元测试到冒烟测试入门 Part 1: 什么是单元测试?为什么需要它? 想象一下你正在组装一台复杂的机器,比如一台汽车。单元测试就像是分别检查每一个零件(螺丝、齿轮、活塞)是否合格。只有确保每个最小零件都工作正常,你才能对最终组装好的汽车有信心。...

September 14, 2025 · 569 words · Kurong

Kafka简单入门

Apache Kafka 是一个开源的分布式事件流平台,被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用。无论你是后端开发者、数据工程师,还是希望构建实时数据系统的架构师,掌握 Kafka 都是一项至关重要的技能。 本文将带你系统地学习 Kafka,从核心概念到命令行实战,再到面试高频考点,最后用 Go 语言构建一个简单的生产者和消费者应用。 一、Kafka 核心概念解析 要理解 Kafka,首先必须掌握其核心术语。我们可以把 Kafka 想象成一个高度有序、可分区、可复制的数字“邮局系统”。 1. Broker Kafka 集群中的一台服务器就是一个 Broker。你可以把它想象成邮局系统中的一个“邮局分拣中心”。多个 Broker 共同组成一个 Kafka 集群,协同工作,提供高可用和负载均衡。 2. Topic(主题) Topic 是消息的类别。所有发布到 Kafka 集群的消息都有一个指定的 Topic。这就像邮局里的不同“信件类型”,比如“平信”、“挂号信”或“国际邮件”。生产者将消息发送到特定的 Topic,消费者订阅特定的 Topic 来接收消息。 3. Partition(分区) 每个 Topic 都可以被分成一个或多个 Partition。Partition 是 Kafka 实现高吞吐量和水平扩展的关键。你可以将 Partition 理解为某个信件类型(Topic)的专用“处理通道”。 有序性:在单个 Partition 内,消息是严格有序的。消息被追加到 Partition 的末尾,并被分配一个唯一的、递增的 ID,称为 Offset。 并发性:不同的 Partition 可以分布在不同的 Broker 上,这使得多个消费者可以并行地从一个 Topic 中读取数据,极大地提高了读取效率。 4. Offset(偏移量) Offset 是 Partition 中每条消息的唯一标识符,是一个单调递增的整数。消费者通过 Offset 来追踪自己已经消费到 Partition 中的哪个位置。这个机制非常强大,因为它允许消费者自由地控制消费进度,可以重复消费或跳过某些消息。 5. Producer(生产者) 负责创建消息并将其发布(发送)到 Kafka Topic 的应用程序。生产者在发送消息时,可以指定 Topic,也可以指定消息的 Key。如果指定了 Key,Kafka 会使用哈希算法将这个 Key 映射到某个特定的 Partition,确保所有具有相同 Key 的消息都进入同一个 Partition,从而保证了这部分消息的顺序性。...

September 13, 2025 · 1137 words · Kurong

xxl-job简单入门

在任何复杂的业务系统中,定时任务、延时任务、周期性报表生成等调度需求都不可或令。虽然 Linux 的 cron 和编程语言内置的定时器可以解决简单问题,但在微服务架构下,它们面临着单点故障、无法统一管理、不支持集群和分片等诸多挑战。 XXL-JOB 是一个由大众点评开源的轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。凭借其清晰的架构、丰富的功能和极低的接入成本,XXL-JOB 已成为国内最流行的任务调度解决方案之一。 本文将带你全面解析 XXL-JOB,从核心概念、架构原理,到面试中的高频问题,并最终使用 Go 语言构建一个可以实际运行的执行器。 一、XXL-JOB 核心概念 理解 XXL-JOB 的工作模式,需要先掌握它的几个基本组件和术语。 1. 调度中心 (Admin) 调度中心是 XXL-JOB 的“大脑”,是一个独立部署的 Web 应用。它负责: 任务管理:提供可视化的 UI,用于创建、编辑、启动/停止任务。 调度触发:内置调度器,根据任务配置的 Cron 表达式或固定延时,准时地向执行器发起调度请求。 注册管理:维护在线的执行器列表,感知执行器的上下线。 日志监控:汇集所有任务的执行日志,方便开发者查看任务执行情况和排查问题。 2. 执行器 (Executor) 执行器是集成在业务应用中的一个组件(通常是一个 SDK 或库)。它负责: 自我注册:启动后,执行器会周期性地向调度中心发送心跳,报告自己的地址,完成“服务注册”。 接收调度:内置一个嵌入式的 HTTP 服务器,用于接收来自调度中心的任务触发请求。 执行任务:根据请求中的参数,调用应用内具体的业务代码(JobHandler)。 上报结果:将任务的执行结果(成功/失败)和执行日志回调给调度中心。 3. 任务 (Job) 与 JobHandler 任务:在调度中心 UI 上创建的一个调度单元,包含了 Cron 表达式、路由策略、负责人、超时时间等元数据。 JobHandler:在执行器项目中,具体承载任务业务逻辑的代码块。在 Java 中通常是一个方法,在 Go 中则是一个注册的函数。一个任务必须绑定一个唯一的 JobHandler 名称。 4. 路由策略 (Route Strategy) 当一个执行器以集群模式部署(即多个应用实例)时,路由策略决定了调度中心如何从集群中选择一个或多个实例来执行任务。常用策略包括: 第一个/最后一个:选择集群中第一个或最后一个在线的实例。 轮询 (Round Robin):按顺序轮流选择。 一致性哈希 (Consistent HASH):根据任务 ID 计算哈希值,选择固定的一个实例,可用于实现粘性调度。 分片广播 (Sharding):核心功能。将任务“分片”给集群中的所有实例并行执行。例如,处理 100 万条数据,可以分给 10 个实例,每个实例处理 10 万条。调度中心会向每个实例传递分片参数(分片总数、当前分片索引)。 5....

September 13, 2025 · 493 words · Kurong

sync包详解

一、sync 包概述 sync 包是 Go 标准库中用于处理并发和同步的核心工具包。在 Go 语言中,我们通过 Goroutine 实现并发,而当多个 Goroutine 需要访问共享资源时,为了避免数据竞争(Data Race)等问题,就需要使用 sync 包提供的同步原语来保证并发安全。 面试官通过 sync 包相关的问题,主要考察你对 Go 并发模型的理解、处理并发问题的能力以及对底层原理的掌握程度。 二、sync.Mutex与sync.RWMutex (互斥锁与读写锁) 1. 作用与目的 这是最基础和最常用的同步原语。 sync.Mutex (互斥锁): 保证在同一时刻,只有一个 Goroutine 可以访问被其保护的共享资源。当一个 Goroutine 获取了锁,其他试图获取该锁的 Goroutine 都会被阻塞,直到锁被释放。 sync.RWMutex (读写锁): Mutex 的一种更细粒度的实现,它将访问分为“读”和“写”。 多个读:可以共存,多个 Goroutine 可以同时获取读锁。 写与任何操作:互斥,当一个 Goroutine 获取了写锁后,其他任何 Goroutine(无论是读还是写)都必须等待。 核心解决的问题: RWMutex 旨在优化“读多写少”的场景。如果共享资源的读取频率远高于写入频率,使用 RWMutex 可以显著提高并发性能,因为它允许多个读操作并行执行。 2. 实现原理简介 Mutex: 内部通过一个 state 字段和信号量 sema 实现。state 字段的比特位分别表示锁的锁定状态、是否有被唤醒的 Goroutine、是否有等待的 Goroutine,以及是否处于饥饿模式。 正常模式: 新来的 Goroutine 会和等待队列头部的 Goroutine 竞争锁,这种模式吞吐量高,但可能导致队列中的 Goroutine 长时间等待(饥饿)。 饥饿模式: 当一个 Goroutine 等待锁超过 1ms,锁会切换到饥饿模式。在此模式下,锁会直接交给等待队列头部的 Goroutine,保证公平。 RWMutex: 内部组合了一个 sync....

August 30, 2025 · 605 words · Kurong

TCP的三次握手与四次挥手

深入理解TCP:三次握手与四次挥手(含面试高频考点) TCP (Transmission Control Protocol) 作为互联网核心协议簇中的基石,保证了数据传输的可靠性和顺序性。而要理解 TCP 的可靠性,就必须掌握其经典的连接管理机制——三次握手与四次挥手。 本文将用通俗易懂的比喻和清晰的技术图解,带你彻底搞懂这两个过程,并深入剖析面试中关于它们的高频考点。 一、三次握手:建立可靠的连接 在进行数据传输之前,通信双方(客户端和服务器)必须建立一个可靠的连接。这个建立连接的过程,就是“三次握手”。 一个生动的比喻:打电话 想象一下你给朋友打电话确认双方都能正常通信的过程: 你:“喂,能听到我说话吗?” (发送 SYN) 朋友:“我能听到你,你能听到我吗?” (回复 SYN+ACK) 你:“我也能听到你,那我们可以开始聊天了。” (发送 ACK) 经过这三步,双方都确认了彼此的发送和接收能力都正常,通话(连接)才正式建立。 技术流程详解 第一次握手 (SYN) 从哪到哪:客户端 -> 服务器 做什么:客户端发送一个 SYN (Synchronize) 报文段,其中包含一个随机生成的初始序列号 seq = x。 状态变化:客户端进入 SYN_SENT 状态,等待服务器的确认。 第二次握手 (SYN+ACK) 从哪到哪:服务器 -> 客户端 做什么:服务器收到客户端的 SYN 后,如果同意连接,则回复一个报文段。该报文段包含两个关键信息: 自己的 SYN 标志位,以及一个随机生成的初始序列号 seq = y。 一个 ACK (Acknowledgment) 标志位,以及确认号 ack = x + 1,表示已成功收到客户端的第一个报文。 状态变化:服务器进入 SYN_RCVD 状态。 第三次握手 (ACK) 从哪到哪:客户端 -> 服务器 做什么:客户端收到服务器的 SYN+ACK 后,会发送最后一个 ACK 报文段作为确认。该报文段包含确认号 ack = y + 1。 状态变化:此报文发送后,客户端和服务器都进入 ESTABLISHED 状态,标志着 TCP 连接正式建立成功,可以开始双向传输数据。 三次握手的核心目标 确保客户端和服务器双方都确认了自己的发送能力和对方的接收能力都正常。...

August 30, 2025 · 275 words · Kurong

WebSocket面试详解

第一部分:核心概念 1.1 什么是 WebSocket? WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它诞生于 HTML5,旨在解决浏览器与服务器之间低延迟、实时、双向通信的需求。 全双工 (Full-duplex):与 HTTP 的“请求-响应”模式不同,WebSocket 连接一旦建立,客户端和服务器双方可以随时主动向对方发送数据,无需等待对方的请求。 单个 TCP 连接:所有通信都在一个 TCP 连接上完成,减少了重复建立和关闭连接的开销。 协议标识符:WebSocket 的 URL 方案是 ws://(非加密)和 wss://(加密,运行在 TLS 之上)。 1.2 为什么需要 WebSocket?(解决了什么问题?) 在 WebSocket 出现之前,为了模拟实时通信,开发者通常采用以下技术,但它们都有明显的缺陷: 轮询 (Polling):客户端定时向服务器发送 HTTP 请求,询问是否有新数据。 缺点:大量无效请求,浪费带宽和服务器资源;数据延迟高,实时性差。 长轮询 (Long Polling):客户端发送一个请求,服务器“挂起”这个连接,直到有新数据时才返回响应。客户端处理完响应后,立即发送下一个请求。 缺点:仍然有连接开销;服务器长时间持有连接会消耗资源;实现相对复杂。 服务器发送事件 (Server-Sent Events, SSE):一种允许服务器向客户端单向推送数据的技术。 缺点:只能实现服务器到客户端的单向通信,客户端无法向服务器发送数据(除非通过另一个 HTTP 请求)。 WebSocket 解决的核心问题:提供了一种低开销、低延迟、真正意义上的双向实时通信方案,完美适用于即时通讯、在线游戏、实时数据更新(股票行情、体育比分)等场景。 1.3 WebSocket 与 HTTP 的关系和区别 特性 HTTP (HyperText Transfer Protocol) WebSocket 通信模式 半双工 (Half-duplex)。客户端发起请求,服务器响应,一次完整的事务结束。 全双工 (Full-duplex)。连接建立后,双方可随时互相发送数据。 连接状态 无状态 (Stateless)。每个请求都是独立的,服务器不保留之前请求的信息。 有状态 (Stateful)。连接在整个会话期间保持,服务器和客户端都维护连接状态。 协议开销 较高。每个请求都包含完整的 HTTP 头部,通常有几百字节甚至更多。 极低。握手之后,数据帧的头部非常小(通常 2-10 字节),开销很小。 连接建立 每次通信都需要建立 TCP 连接(或在 Keep-Alive 下复用,但仍是请求-响应模式)。 借用 HTTP 协议进行一次“握手”,之后升级为 WebSocket 协议,后续通信与 HTTP 无关。 应用场景 文档、图片、API 数据等一次性资源获取。 即时聊天、在线协作、实时数据推送、在线游戏等需要高实时性的场景。 协议标识 http://, https:// ws://, wss:// 1....

August 30, 2025 · 691 words · Kurong

搞懂BIO与AIO

彻底搞懂 AIO 与 epoll 维度一:必须厘清的两个核心概念 在深入 I/O 模型之前,我们必须先区分两组完全独立的概念,很多人在这里就已混淆: 阻塞 (Blocking) vs. 非阻塞 (Non-blocking) 关注点:应用程序在等待结果时的状态。 阻塞:发起 I/O 操作后,如果数据未就绪,当前线程会被挂起,直到操作完成。 非阻塞:发起 I/O 操作后,如果数据未就绪,函数会立刻返回一个错误码,线程不会被挂起,可以继续执行其他任务。 同步 (Synchronous) vs. 异步 (Asynchronous) 关注点:内核与应用程序之间如何交付 I/O 结果。 同步:应用程序自己负责发起 I/O 操作,并且主动去查询或等待结果。即便是非阻塞轮询,只要是“我”去问内核“好了没”,都算同步。 异步:应用程序发起 I/O 操作后便立即返回。内核会独立完成整个 I/O 过程(包括将数据从内核空间拷贝到用户空间),当一切完成后,内核会通知应用程序。 经典烧水比喻: 同步阻塞 (BIO):你守在水壶边,一直等到水烧开。 同步非阻塞 (NIO):你边看电视边烧水,每隔几分钟就跑去厨房看水开了没。 异步非阻塞 (AIO):你用一个智能水壶,告诉它水开后提醒你,然后你就可以安心做任何事,直到水壶通知你。 I/O 模型的演进之路 模型一:BIO (同步阻塞 I/O) - 一夫当关 这是最古老、最简单的模型:一个连接对应一个处理线程。 工作方式:主线程 accept 一个新连接,然后创建一个新线程来处理这个连接的 read() 和 write()。 致命缺点: 资源开销巨大:一个连接一个线程,线程的创建和上下文切换成本极高。 性能瓶颈:当成千上万个线程大部分时间都在 read() 等待数据而阻塞时,CPU 大量时间被浪费在无效的上下文切换上。它无法应对 C10K (单机一万并发) 的挑战。 模型二:I/O 多路复用 - 一人看多路 为了解决 BIO 的问题,诞生了 I/O 多路复用技术。其核心思想是:用一个(或少量)线程来监视和管理大量的网络连接(文件描述符 File Descriptor, fd)。...

August 30, 2025 · 314 words · Kurong

pprof 指南

Go 性能分析利器 pprof 指南 pprof 是 Go 语言生态中功能最强大的性能分析工具,它内置于 Go 的标准库中,能够帮助开发者精准定位程序中的性能瓶颈,无论是 CPU 的过度消耗、内存的异常增长,还是并发程序中的各种“慢”问题。 本文将在 kurongtohsaka 的 pprof 指南基础上,深入探讨两个额外的关键领域:Goroutine 泄漏 和 锁竞争。 第一部分:PProf 基础与开启 要在 Web 服务中开启 pprof,我们只需引入 net/http/pprof 包。对于使用 Gin 等框架的服务,可以将其 Handler 包装后注册到路由中。 一个关键的准备步骤是,为了能分析到 阻塞 和 锁竞争,我们需要在程序启动时设置采样率。如果不设置,相关的 profile 文件将是空的。 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 36 package main import ( "log" "net/http" _ "net/http/pprof" // 关键:匿名导入pprof包,它会自动注册handler "runtime" "github....

August 27, 2025 · 808 words · Kurong

常见linux命令

好的,这个命令非常重要,尤其是在排查 “too many open files” 这类经典问题时。我会把它加上,并放在一个新的“高级调试与诊断”分类里,让整个指南的结构更清晰。 这是更新后的完整版本,你可以直接替换掉之前的博客文章。 后端实习Linux命令指南 一、文件与目录管理 (基础中的基础) 这是最基本的操作,你需要像在Windows图形界面里操作文件夹一样熟练。 命令 完整名称 作用 常用示例 ls list 列出目录内容 ls -lha (列出所有文件,含隐藏文件,以人类可读的格式显示详细信息) cd change directory 切换目录 cd /var/log (切换到日志目录), cd .. (返回上一级), cd ~ (返回家目录), cd - (返回上次所在目录) pwd print working directory 显示当前所在路径 pwd cp copy 复制文件或目录 cp source.txt dest.txt, cp -r sourcedir destdir (递归复制整个目录) mv move 移动或重命名文件/目录 mv oldname.txt newname.txt (重命名), mv file.txt /tmp/ (移动到/tmp目录) rm remove 删除文件或目录 rm file.txt, rm -rf somedir (递归强制删除目录,慎用!) mkdir make directory 创建目录 mkdir logs, mkdir -p /data/app/logs (-p可以递归创建多层不存在的目录) touch touch 创建空文件或更新时间戳 touch app....

August 27, 2025 · 383 words · Kurong

《Redis深度历险》记录

《Redis深度历险》记录 基础与应用 分布式锁 分布式锁用于高并发或分布式事务中保护临界资源。 最常见的实现方式是使用 SET 命令的 NX (不存在时设置)和 PX (设置过期时间)选项: 1 SET lock_name my_random_value NX PX 30000 lock_name : 锁的名称。 my_random_value : 唯一标识(通常使用 UUID)。 NX : 只有键不存在时才设置。 PX 30000 : 设置 30 秒的过期时间。 释放分布式锁时需要验证唯一标识是否匹配,防止误删其他客户端的锁。 超时问题 超时问题有三类: 锁过期,可以通过锁续约机制,即获取锁后启动一个守护线程,定期检查锁是否仍由自己持有,如果是则延长锁的过期时间。 客户端获取锁后崩溃,没有主动释放锁,锁就只能等待超时后才能被其他客户端获取。所以要设置合理的锁超时时间。 Redis 服务器和客户端之间的时钟不同步,可能导致锁提前或延后过期。尽量确保服务器和客户端时钟同步(NTP),同时适当增加锁的超时时间作为缓冲。 分布式锁的实现有 Redlock 算法、go-redis 中的简易 mutex 分布式锁。 延时队列 通过 List 来作为简易的消息队列使用。 通过 rpush , lpush 控制入队,rpop , lpop 控制出队。 使用 List 实现延时队列会出现一个问题,假设 List 为空,那么之后的 pop 操作就是死循环。此时可以使用阻塞读来避免这种情况,即使用 blpop, brpop 两个命令代替之前的出队命令。阻塞读在队列没有数据的时候,会立即进入休眠状态。 同时需要注意,List 长时间为空,Redis 客户端连接可能会因为空闲而被回收。所以需要加入重试机制。...

April 29, 2025 · 914 words · Kurong

《Kubernetes编程》记录

《Kubernetes编程》记录 概论 控制器和 Operator 控制器实现了一个控制循环,通过 API 服务器监测集群中的共享状态,进行必要的变更。Operator 也是一种控制器,但是包含了一些运维逻辑,如应用程序的生命周期管理。 控制循环的基本流程如下: 读取资源的状态,通常采用事件驱动模式,使用 Watch 来实现。 改变集群中的对象状态或集群外部系统的状态。 通过 API 服务器更新第 1 步中所提到的资源的状态,状态放在 etcd 中。 循环执行这些逻辑。 控制器通常会使用以下的数据结构: Informer :Informer 负责观察资源的目标状态,它还负责实现重新同步机制从而保证定时地解决冲突。 工作队列:事件处理器把状态变化情况放入工作队列。 多个相互独立的控制循环通过 API 服务器上的对象状态变化相互通信,对象状态的变化由 Informer 以事件的方式通知到控制器。这些通过 Watch 机制发送的事件对用户来说几乎不可见,在 API 服务器的审计机制中也看不到这些事件,只能看到对象更新的动作。控制器在处理事件时,以输出日志的方式记录它做的动作。 Watch 事件:在 API 服务器与控制器之间通过 HTTP 流的方式发送,用于驱动 Informer 。 Event 对象:是一种用户可见的日志机制,如 kubelet 通过 Event 来汇报 Pod 的生存周期事件。 乐观并发 Kubernetes 采用乐观并发机制,假设多用户操作同一资源的冲突概率较低,允许并发修改,仅在提交时检测冲突。 每个 Kubernetes 资源对象(如 Pod、Deployment)的元数据中都有一个 resourceVersion 字段,用于标识资源的当前版本号(由 etcd 生成)。这是乐观并发的核心: 读取资源时:客户端获取对象的 resourceVersion(例如 123)。 更新资源时:客户端必须在请求中携带该版本号(通过 metadata.resourceVersion 字段)。 服务器端校验:API Server 比较请求中的 resourceVersion 与当前存储中的版本: 匹配:允许更新,resourceVersion 递增。 不匹配:返回 409 Conflict 错误,拒绝更新(说明资源已被其他客户端修改过)。 当发生冲突时,客户端需重新获取资源的最新状态,然后基于最新数据重新提交修改。...

April 9, 2025 · 212 words · Kurong

《Kafka权威指南》记录

《Kafka权威指南》记录 初识 Kafka 消息和批次 消息有一个可选的元数据,也称为键。键是一个字节数组,可以用来实现如分布式键等应用。 为了提高效率,消息会被分批次写入 Kafka,批次包含了一组同属于一个主题和分区的消息。 模式 模式就是消息的格式,如Json、XML。 主题和分区 消息通过主题进行分类,主题又被分为若干个分区。 生产者和消费者 默认情况下,生产者会把消息均衡地分布到主题的所有分区中,也可以通过自定义设置决定消息发布到哪里。 消费者通过检查消息的偏移量来区分已经读取过的消息。 broker 和集群 一台单独的 Kafka 服务器称为 broker,单个 broker 可以处理数千个分区和每秒百万级的消息量。 集群由 broker 组成,同时也有集群中都有的分布式共识算法。Kafka 使用 ZAB 作为共识算法,2.8 版本后支持 KRaft 管理元数据。 Kafka 与其他消息队列的区别 Kafka 与其他消息队列相比,有以下几个显著的区别: 高吞吐量与低延迟 Kafka 设计上支持高吞吐量和低延迟,能够处理海量数据和高并发写入、读取场景。 其他消息队列(如 RabbitMQ、ActiveMQ)通常适用于较小规模或实时性要求较高的场景,但在海量数据和高并发场景下可能会遇到性能瓶颈。 持久化与分布式存储 Kafka 使用磁盘持久化和日志分段技术,并通过分区(Partition)和副本(Replica)机制实现分布式存储和容错。 其他消息队列有的采用内存队列(例如 RabbitMQ 默认使用内存),虽然也支持持久化,但在大规模数据存储和高可靠性需求下不如 Kafka 灵活。 消息模型与消费模式 Kafka 基于发布-订阅模型,采用拉取(Pull)的消费模式。消费者主动拉取消息,可以灵活控制消费速率和消息重放。 其他消息队列(例如 RabbitMQ)多采用推送(Push)模式,消息直接推送给消费者,实时性较好,但可能在处理慢消费者时需要额外机制(如 ACK、流控等)。 数据持久性和消息重放 Kafka 中的消息存储在日志中,并且消息不会在被消费后自动删除(可以根据保留策略删除),这使得消费者可以根据需求重放消息。 其他消息队列通常在消息被消费确认后就删除,无法轻易重放历史消息。 扩展性与容错性 Kafka 天生支持分布式集群部署,通过增加 Broker 节点来横向扩展,且内置副本机制保证容错。 其他消息队列虽然也有集群部署方案,但在大规模扩展和高容错要求下往往不如 Kafka 那样简单高效。 应用场景不同 Kafka 更适用于日志收集、流处理、大数据实时分析等场景。 其他消息队列则更适合需要复杂路由、即时响应、任务队列等场景,比如电商下单、实时通知等。 Kafka 生产者 消息发布方式:...

March 26, 2025 · 132 words · Kurong

《MongoDB 权威指南》记录

《MongoDB 权威指南》记录 MongoDB 简介 MongoDB 是面向文档的非关系型数据库。 MongoDB 具有以下特点: 易于使用:没有“行记录”的概念,而使用“文档”模型取代之。同时也没有预定义模式,文档键值的类型和大小是动态的。 易于扩展:MongoDB 采用了横向扩展方式,面向文档的数据模型使得跨多台服务器拆分数据更加容易。 性能强大:使用了机会锁,提高并发和吞吐量。同时会尽可能多的使用内存作为缓存,并尝试为查询自动选择合适的索引。 入门指南 文档 文档是一组有序键值的集合,如以下形式: 1 {"q": "ans"} 集合 集合就是一组文档,相当于关系型数据库的表结构。 集合具有动态模式的特性,每个文档的长度大小、键数量都可以不一样。不过开发中并不推荐这样做,还是将“形状”相同的文档存在同一个集合中好,因为这样查询起来性能不会受到影响。 在 MongoDB 中通常会用 . 字符分隔不同命名空间的子集合,如 blog.posts 。 数据库 MongoDB 使用数据库对集合进行分组。 数据类型 基本数据类型:除了 int 等,还有 Regex、Array、ObjectID(12字节的文档的唯一ID 标识)、Bytes、JSCode。 Array: {"things": ["pie", 3.14]},数组内可以混有不同类型的数据。 内嵌文档:文档作为值。 ObjectID:每个文档都有一个 _id ,默认是 ObjectID ,这个 ID 在集合内是唯一的。可以看到这个 ID 不是自增的,类似于 UUID,这说明 MongoDB 在设计之初就是作为分布式数据库存在的。以下是它的生成规则: 前 4 字节为时间戳,中间 5 个字节是随机值,后 3 个字节是计数器(起始值随机)。 索引 MongoDB 中可以设置单一索引和复合索引,方式如下: 1 db.users.createIndex({"age": 1, "username": 1}) # 复合索引 MongoDB 的默认存储引擎是 WiredTiger ,索引是 B+ 树实现的。所以在索引的优化上,可以参照相关关系型数据库的索引优化经验与原则。下面是部分经验:...

March 22, 2025 · 143 words · Kurong

《HTTP权威指南》记录

《HTTP权威指南》记录 本书中的 HTTP 协议版本为 HTTP/1.1 。 URL 与资源 URL 语法 在 URL 中以分号 ; 作为参数的分隔符,如下: 1 http://xxx.xxxx.com/hammers;sale=false/index.html;graphics=true 有些资源需要通过查询字符串来缩小查找范围,查询字符串通过 ? 分割,如常见的 GET 请求中的查询参数。 URL 支持使用片段组建来表示一个资源内部的片段,以 # 进行分割: 1 http://xxx.xxx.com/index.html#head Web 服务器 Web 服务器应该做的: 接受客户端连接:客户端收到一条连接之后,那么它将会把新连接添加到现存web服务器连接列表中,用于监视当前连接上的数据传输情况。期间服务器还应该做到通过一定的设备机制阻止未认证或已知恶意黑名客户端的连接。 接收请求报文: 这一步需要解析请求报文: 解析请求行,查找请求方式、URI 、版本号以及 CRLF 分隔符。 读取以 CRLF 结尾的报文首部。 检测到以 CRLF 结尾的,标识首部结尾的空行。 解析得到请求体。 处理请求:其他章节会详解。 对资源的映射及访问:找到客户端请求资源在服务器的上的目录路径。 Web 服务器存放内容的文件夹称为文档的根目录(doc root),Web 服务器会从请求报文中获取 URI ,并将其附加在 doc root 的后面。 在一个服务器上挂载多个 Web 站点,那么这样当请求的资源路径相同时,服务器应该从请求报文首部的 HOST 和 URI 字段找出真正的资源目录,这些目录都可以更改配置。 构建响应: 正确设置响应主体的长度(content-length)。 设置报文的 MIME 类型(content-type)。 有时候资源不在原地,需要进行重定向。 发送响应。 记录事务日志。 代理 Web 的中间实体 Web 代理服务器是代表客户端对事务请求处理的中间人,代理分为私有代理(只代理一个客户端)和公共代理(代理多个客户端)。 代理和网关的对比:代理的两端使用相同的协议,而网关的两端使用不同的协议,网关负责协议转换。 代理应用 内容过滤。 文档访问控制。 安全防火墙。 Web 缓存:缓存资源的副本。 反向代理:反向代理伪装成原始服务器,不过与服务器不同的是反向代理还可以向其他服务器发送请求,以便实现按需定位所请求的内容。 ……....

March 14, 2025 · 315 words · Kurong

《深入理解分布式共识算法》记录

《深入理解分布式共识算法》记录 从 ACID 和 BASE 到 CAP BASE 理论 BASE 包含; BA(Basically Available):基本可用,当分布式系统出现故障的时候,允许损失部分可用性。这里损失指响应时间的损失和功能上降级,前者是在增加响应时间上来提供服务,后者是在高峰期不请求后端而直接返回降级数据、以保证系统的稳定性。 S(Soft State):软状态,允许系统中的数据存在中间状态,并认为该状态不会影响系统的整体可用性,即允许节点之间的数据同步存在延迟。 E(Eventually Consistent):最终一致性。 为应用 BASE 理论,可以采用异步补偿机制。如果用这个机制,需要明确哪些操作属于非关键操作,如果非关键操作失败,应允许业务流程继续执行,然后在异步补偿非关键操作。 补偿指事务补偿:在分布式事务中,由于各个服务可能分布在不同的节点上,无法像单机事务那样简单地通过“回滚”来撤销操作。因此,事务补偿通过执行与原始操作相反的操作来达到类似的效果。 补偿操作需要注意两点: 幂等性:补偿操作可能需要多次执行,因此需要保证其幂等性,即多次执行的效果与一次执行相同。 最终一致性:补偿机制通常用于实现最终一致性,而不是强一致性,因此需要权衡一致性和性能。 CAP 理论 CAP 包含: C(Consistency)一致性:分布式系统的全序线性一致性。 A(Availability)可用性:任何情况都能够处理客户端的每个请求。 P(Partition Tolerance)分区容错性:发生网络分区时,系统仍能提供服务。 任何分布式系统都可分为三类架构: CP(锁定事务资源):所有节点的数据需要实时保持一致性,这需要锁定各分支事务的资源。当发生分区时,此期间为确保返回客户端数据的准确性且不破坏一致性,可能会因为无法响应最新数据而拒绝响应,即放弃 A 。 AP(尽力提供服务):要求每个节点拥有集群的所有能力或数据,能独立处理客户端的每个请求。发生分区时,可以通过缓存后本地副本来处理请求,以达到可用性,但会出现各节点不一致性,即放弃 C 。 CA(本地一致性):在不发生分区时,C、A 正常满足。一旦发生分区,会保证各子分区满足 CA 。 但是网络分区不可避免,CAP 理论就变成了在 C 和 A 中的权衡问题。 2PC、3PC,分布式事务的解决方案 2PC 2PC 中的字母含义是 P(Participant,参与者),C(Coordinator,协调者)。 2PC 即两阶段提交协议,用于解决分布式事务问题。第一阶段用于各个分支事务的资源锁定,第二阶段用于全局事务的提交或回滚: 第一阶段,准备: 开启全局事务。当协调者收到客户端的请求后,它将各个分支事务需要处理的内容通过 Prepare 请求发送给所有参与者,并询问能否正常处理自己的分支事务,然后等待各个参与者的响应。 处理分支事务。当参与者收到 Prepare 请求后便锁定事务资源,然后尝试执行,记录 Undo 和 Redo 信息,但不提交分支事务。 汇报分支事务状态。参与者根据第 2 步执行的结果,向协调者汇报各自的分支事务状态,Yes 表示可以提交,No 表示不能提交。 第二阶段,提交/回滚:...

March 7, 2025 · 298 words · Kurong

《数据密集型系统设计》记录

《数据密集型系统设计》记录 本书的电子版本链接:Vonng/ddia: 《Designing Data-Intensive Application》DDIA中文翻译 3. 存储与检索 本章围绕两大类存储引擎:日志结构(log-structured)的存储引擎,以及面向页面(page-oriented)的存储引擎(例如 B 树)。 数据库核心:数据结构 这里所使用的“日志”是一个更一般性的概念:一个支持 append-only 仅追加操作的记录序列。 为了高效查找数据库中特定键的值,需要用到索引。索引是从主要数据中衍生的额外结构,维护它会产生额外开销,特别是写入时。任何类型的索引通常都会减慢写入速度,因为每次写入数据时都需要更新索引。这是存储系统中一个重要的权衡:精心选择的索引加快了读查询的速度,但是每个索引都会拖慢写入速度。因为这个原因,数据库默认并不会索引所有的内容,而需要程序员或数据库管理员(DBA),基于对应用的典型查询模式的了解来手动选择索引。 SSTable 把键值对的序列按照键进行排序,这个格式称为排序字符串表(Sorted String Table, SSTable)。此时再要求每个键值在每个合并的段文件中出现一次。与使用散列索引的日志段相比,SSTable 有几个大的优势: 即使文件大于可用内存,合并段的操作仍然是简单高效: 一开始并排读取多个输入文件,查看每个文件中的第一个键,复制最低的键(根据排序顺序)到输出文件,不断重复此步骤,将产生一个新的合并段文件,而且它也是也按键排序的。 如果在几个输入段中出现相同的键,该怎么办?请记住,每个段都包含在一段时间内写入数据库的所有值。这意味着一个输入段中的所有值一定比另一个段中的所有值都更近(假设我们总是合并相邻的段)。当多个段包含相同的键时,我们可以保留最近段的值,并丢弃旧段中的值。 为了在文件中找到一个特定的键,你不再需要在内存中保存所有键的索引: 假设你正在内存中寻找键 handiwork,但是你不知道这个键在段文件中的确切偏移量。然而,你知道 handbag 和 handsome 的偏移,而且由于排序特性,你知道 handiwork 必须出现在这两者之间。这意味着你可以跳到 handbag 的偏移位置并从那里扫描,直到你找到 handiwork(或没找到,如果该文件中没有该键)。 由于读取请求无论如何都需要扫描所请求范围内的多个键值对,因此可以将这些记录分组为块(block),并在将其写入硬盘之前对其进行压缩。稀疏内存索引中的每个条目都指向压缩块的开始处。除了节省硬盘空间之外,压缩还可以减少对 I/O 带宽的使用。 那么,可以构建 SSTable 了,可以让存储引擎这样工作: 有新写入时,将其添加到内存中的平衡树数据结构(例如红黑树),这个内存树有时被称为内存表(memtable)。 当内存表大于某个阈值(通常为几兆字节)时,将其作为 SSTable 文件写入硬盘。这可以高效地完成,因为树已经维护了按键排序的键值对。新的 SSTable 文件将成为数据库中最新的段。当该 SSTable 被写入硬盘时,新的写入可以在一个新的内存表实例上继续进行。 收到读取请求时,首先尝试在内存表中找到对应的键,如果没有就在最近的硬盘段中寻找,如果还没有就在下一个较旧的段中继续寻找,以此类推。 后台进程周期性地执行合并和压缩过程,以合并段文件,并将已覆盖或已删除的值丢弃。 这样的索引结构被称为 LSM-Tree(Log-Structured Merge-Tree)。 当查找数据库中不存在的键时,LSM 树算法可能会很慢。为了优化这种访问,存储引擎通常会使用布隆过滤器。还可以使用大小分级和分层压缩。对于大小分级,较新和较小的 SSTables 相继被合并到较旧的和较大的 SSTable 中。对于分层压缩,键的范围被拆分到多个较小的 SSTables,而较旧的数据被移动到单独的层级,这使得压缩能够逐步进行并且使用较少的硬盘空间。 B-Tree 前面看到的日志结构索引将数据库分解为可变大小的段,通常是几兆字节或更大的大小,并且总是按顺序写入段。相比之下,B 树将数据库分解成固定大小的块或分页,传统上大小为 4KB(有时会更大),并且一次只能读取或写入一个页面。这种设计更接近于底层硬件,因为硬盘空间也是按固定大小的块来组织的。 在 B 树的底层中,写操作是用新数据覆写硬盘上的页面,并假定覆写不改变页面的位置。即当页面被覆写时,对该页面的所有引用保持完整。这与日志结构索引(如 LSM 树)形成鲜明对比,后者只追加到文件(并最终删除过时的文件),但从不修改文件中已有的内容。...

February 7, 2025 · 585 words · Kurong

《Kubernetes微服务实战》记录

《Kubernetes微服务实战》记录 面向开发人员的 Kubernetes 简介 部分核心概念 Node:Node 是 Kubernetes 集群中的一个工作单元,表示集群中的一台物理机或虚拟机。Node 的作用是为运行容器化应用提供计算资源,一个 Node 可以运行多个 Pod。Node 分为: Master Node:负责集群的控制平面,管理所有工作节点。 Worker Node:运行实际的应用程序工作负载,实际上就是 Pod。 Pod:Pod 是 Kubernetes 中最小的可部署单位,表示一个或多个容器的集合。有以下特性: Pod 内的容器共享一个 IP 地址,可以通过 localhost 相互通信。 Pod 内的容器可以共享挂载的存储卷(Volume)。 Pod 通常包含运行同一应用程序组件的容器,如主应用程序容器、日志采集容器等。 Namespace:用于逻辑上隔离资源。可以在同一集群中运行多个项目或团队的工作负载。 Service:用于将一组 Pod 的访问暴露给外部或集群内部的其他 Pod。 Deployment:用于声明和管理 Pod 的期望状态,例如副本数、滚动更新等。 Volume:提供持久化存储,允许 Pod 重启后数据仍然保留。 Kubernetes 架构 每个集群都有一个控制平面和数据平面,数据平面由多个节点组成,控制平面将在这些节点上部署并运行 Pod(容器组),监控变更并做出响应。 控制平面包含以下组件: API 服务器。 etcd 存储。 调度器:kube 调度器负责将 Pod 调度到工作节点。 控制器管理器:kube 控制器管理器是包含多个控制器的单个进程,这些控制器监控着集群事件和对集群的更改做出响应。 节点控制器:负责在节点出现故障时进行通知和响应。 副本控制器:确保每个副本集(replica set)或副本控制器对象中有正确数量的 Pod 。 端点控制器:为每个服务分配一个列出该服务的 Pod 的端点对象。 服务账户和令牌控制器:使用默认服务账户和响应的 API 访问令牌对新的命名空间进行初始化。 数据平面是集群中将容器化工作负载转为 Pod 运行的节点的集合。...

January 25, 2025 · 1376 words · Kurong

minikube:Ubuntu部署本地集群踩坑

minikube:Ubuntu 部署本地集群踩坑 官方安装教程 minikube start | minikube 再次感叹 homebrew 的伟大,一行命令就安装、后续也没有问题。 仪表盘 使用 minikube dashboard 启动仪表盘。如果在服务器上启动、本地访问,就运行类似于的下面命令: 1 kubectl proxy --address='0.0.0.0' --disable-filter=true 然后把 http://127.0.0.1:46749/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ 这样的 URL 改为 http://ServerIP:8001/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ ,就可以外网直接访问了(注意服务器开端口)。 Helm 安装 直接使用 snap 安装,方便快捷: 1 sudo snap install helm --classic kubectl 安装 snap 上大分: 1 sudo snap install kubectl --classic 安装可能遇到的问题的解决方案 The “docker” driver should not be used with root privileges 使用命令 minikube start 启动,参数 driver 默认为 docker 。如果以 root 用户运行该命令会出现如标题所示的错误,下面是解决方法: minikube - Why The “docker” driver should not be used with root privileges - Stack Overflow...

January 25, 2025 · 219 words · Kurong

《云原生开发实践》记录

《云原生开发实践》记录 本书会包含大量的实践内容,现阶段应着重看理论内容、实践内容暂且忽略,实践内容在理论补充完毕后用到哪块补哪块。 容器化 DOCKERFILE 的多阶段构建在部署阶段很有用,可以大幅减少镜像的体积。 Docker 可以自建网络,这样就可以把很多容器纳入到同一个网络下,实现容器间按容器名访问对方。 容器编排 对于一个中大型的应用,会有很多的容器组件。而有些容器如Redis、RabbitMQ、Kafka等需要紧密的配合,这时候就需要用到容器编排。 Docker Compose 组件用于实现本地单个节点上的容器编排,它会从 docker-compose.yaml 文件中读取所需的全部容器的定义,然后运行 docker-compose up 命令启动容器编排。Docker Swarm 可以管理多个节点上的容器,即管理 Docker 集群。 云原生软件生产流程 云计算的能力: 弱化了传统 IT 硬件概念,革命性地降低了企业在基础设施上的建设成本。 实现了弹性获取计算资源,用户可以按需使用。 云计算的成熟时云原生发展的基石,使云原生的许多概念得以落地。云原生应用一般有以下特点: 在应用的设计和开发阶段就为部署到云上做适配。 应用由多个松耦合的小模块构成而不是一个庞大的单体项目,即微服务架构。 通过容器来交付和发布应用,应用代码中会加入容器化需要的文件。 和传统的软件生产方式相比,云原生的优势主要在塔提高了应用发布和运维时的效率,显著降低了运维的复杂性。 云原生基础设施 Kubernetes Kubernetes 是开源的容器编排平台,支持集群环境下部署和管理容器化应用。目前已经是容器编排领域的事实标准,成为云原生的操作系统。Kubernetes 也叫 K8s ,8指中间的8个字母。K8s 2013年由 Google 开源,相比于 Docker Swarm K8s 提供更加复杂强大的功能。 K8s 集群中包含两种节点,一种是 Control Plane 节点(master 节点),另一种是 worker 节点。K8s 中的容器运行在 Pod 中,Pod 运行在 Node 中。每个集群默认至少有一个 Pod ,否则 Pod 无法调度和运行。Control Plane 是 K8s 的容器编排层,通过它提供的 API ,可以定义和部署容器及管理容器的整个生命周期。Control Plane 有以下组件:...

January 22, 2025 · 107 words · Kurong

《Go微服务实战》记录

《Go微服务实战》记录 本书包含很多的 Go 基础和部分进阶内容,这里只选取对现阶段有帮助的内容,毕竟 Go 已经入门过一遍了。 Go 基础 关于指针,如果涉及到修改变量本身就使用指针作为函数输入变量等,典型的如单例模式下的变量都是指针。反之如果不修改变量本身,就直接使用复制变量作为函数输入变量等,函数返回值自然也是某个新的变量。 关于 Go 中的循环,请看 code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package main func main() { for i := 0; i < 10; i++ { ... } // 最标准的循环 for { ... } // 就是没有显式跳出条件的 while arr := []int{1, 2, 3, 4, 5} for i, v := range arr { ... } // 这里的 range 是关键字,用法有些类似于 Python 中的 enumerate 函数 } Go 的垃圾回收策略使用的三色标记法,具体内容会单独进行学习。...

January 18, 2025 · 2955 words · Kurong

《PostgreSQL指南》记录

《PostgreSQL指南》记录 Why choose PostgreSQL, or not MySQL ? SQL 标准支持更好:PostgreSQL 对 SQL 标准的支持更加全面,而 MySQL 在某些情况下依赖非标准实现(如分组函数的行为)。 高级功能更强: JOSNB 的性能更优:PostgreSQL 的 JSONB 数据类型支持更高效的索引和操作,而 MySQL 的 JSON 功能较为有限。 复杂查询:支持递归查询、窗口函数等复杂功能,而 MySQL 在这些方面功能较弱或支持有限。 并行查询:PostgreSQL 提供原生的并行查询能力,能够充分利用多核 CPU,而 MySQL 直到 8.0 版本才有一定程度的改进。 扩展性更好: PostgreSQL 支持自定义数据类型、函数和插件,更适合需要扩展或复杂业务逻辑的项目。 PostGIS 插件使其在地理信息领域表现出色,而 MySQL 的 GIS 功能相对简单。 更高的性能和灵活性: 支持多种索引类型(如 GIN、GiST、BRIN 等),适合高性能全文搜索、地理查询等场景。 支持更复杂的查询优化器策略,适合复杂查询和数据分析。 更可靠的数据一致性: PostgreSQL 的 MVCC 机制更成熟,避免了常见的锁争用问题。 MySQL 的 MVCC 在某些情况下(如高并发)性能欠佳,容易导致死锁。 PostgreSQL 适用于有复杂数据分析需求、地理信息系统、高并发和大数据量等应用场景,相比于 MySQL 的应用场景更复杂。 数据库集簇、数据库和数据表 数据库集簇的逻辑结构 数据库集簇是一组数据库的集合,由一个 PostgreSQL 服务器管理。而数据库是数据对象的集合,数据库对象用于存储或引用数据的数据结构。 在 PosgreSQL 中,所有的数据库对象都通过相应的对象标识符(oid)进行管理,这些标识都是无符号 4 字节整型。...

December 22, 2024 · 768 words · Kurong

《高效使用Redis》记录

《高效使用Redis》记录 基础数据结构解析 对象 Redis中的 RedisObject 的 c 定义: 1 2 3 4 5 6 7 8 #define LRU_BITS 24 typedef struct redisObject { unsigned type:4; // 数据类型 unsigned encoding:4; // 底层数据结构 unsigned lru:LRU_BITS; // 缓存淘汰时使用 int refcount; // 引用计数 void *ptr; // 指向实际存储位置 } robj; RedisObject 是 Redis 中数据结构的一个抽象,用它来存储所有的 key-value 数据。 下面是结构体中各个属性的。说明: type:用来表示对象类型; encoding:表示当前对象底层存储所采用的数据结构,如int、字典、压缩列表、跳跃表等等; lru:用于在配置文件中通过 maxmemory-policy 配置已用内存到最大内存限制时的缓存淘汰策略。 以 GET 命令为例,简单看看 Redis 中的 LRU 实现。使用 GET 后,会执行这段代码更新对象的 lru 属性: 1 2 3 4 5 if (server....

December 5, 2024 · 1410 words · Kurong

《gRPC与云原生应用开发》记录

《gRPC与云原生应用开发》记录 gRPC 入门 gRPC 的定义 gRPC 是一项进程间通信技术,可以用来连接、调用、操作和调试分布式异构应用程序。 开发 gRPC 应用程序时,要先定义服务接口:消费者消费信息的方式、消费者能够远程调用的方法以及调用方法所使用的参数和消息格式等。在服务定义中使用的语言叫作接口定义语言(IDL)。 下面是使用 protocol buffers 作为 IDL 来定义服务接口: 服务定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // ProductInfo.proto syntax = "proto3"; package ecommerce; // 防止协议消息类型之间发生命名冲突 // 定义服务接口 service ProductInfo { rpc addProduct(Product) returns (ProductID); rpc getProduct(ProductID) returns (Product); } // 定义消息格式 message Product { string id = 1; string name = 2; string description = 3; } message ProductID { string value = 1; } 上面定义完成之后,就使用 protocol buffers 编译器 protoc 生成服务器端和客户端代码。...

November 30, 2024 · 963 words · Kurong

《深入RabbitMQ》记录

《深入RabbitMQ》记录 RabbitMQ 基础 AMQP 协议 AMQP,即高级消息队列协议(Advanced Meesage Queuing Protocal)。它定义了一个 AMQ 应该有以下三部分: 交换器:一个接收器接收发送到 MQ 中的消息并决定把它们投递到何处; 队列:存储接收到的消息; 绑定:定义队列和交换器之间的关系。在 RabbitMQ 中,绑定或者叫绑定键即告知一个叫交换器应该将消息投递到哪些队列中。 使用 AMQ 协议与 Rabbit 进行交互 AMQP 帧类型 AMQP 规定了五种类型的帧: 协议头帧:用于连接到 RabbitMQ,仅使用一次; 方法帧:携带发送到 RabbitMQ 或从 RabbitMQ 接收到的 RPC 请求或响应; 内容头帧:包含一条消息的大小和属性; 消息体帧:包含消息的内容; 心跳帧:确保连接,一种校验机制。 将消息编组成帧 首先是方法帧、内容头帧,这两种帧比较好理解。 而消息体帧会根据消息体的大小切分成一到多个消息体帧。 要发送消息,首先发送的是方法帧,之后是内容帧,最后是若干个消息体帧。 帧结构 方法帧携带构建 RPC 请求所需的类、方法和参数。 内容头帧包含了消息的大小和其他对消息起描述作用的属性。 消息体帧可以传输很多类型的数据,可以是二进制、文本,也可以是二进制化的图片和序列化后的json、xml。 使用协议 包含下面一个基本流程: 声明交换器 声明队列 绑定队列到交换器 发布消息到 RabbitMQ 从 RabbitMQ 消费消息 消息属性详解 使用 content-type 创建显式的消息契约 在不同的语言的消费者框架中,框架可以自动的根据 content-type 类型将其反序列化为该语言中的数据结构。 使用 gzip 和 content-encoding 压缩消息大小 通过指定 content-encoding 属性可以在消息体上应用特殊的编码。...

November 28, 2024 · 192 words · Kurong

华为云部署踩坑

华为云部署踩坑 Docker 国内镜像源(截止到2024.11.1) DockerHub 镜像仓库 是否正常 hub.xdark.top 正常 hub.littlediary.cn 正常 dockerpull.org 新增 hub.crdz.gq 正常 docker.1panel.live 正常 docker.unsee.tech 新增 docker.m.daocloud.io 正常 docker.kejilion.pro 正常 registry.dockermirror.com 正常 hub.rat.dev 正常 dhub.kubesre.xyz 正常 docker.nastool.de 正常 docker.hpcloud.cloud 失效 docker.hlyun.org 失效 doublezonline.cloud 失效 docker.chenby.cn 失效 ginger20240704.asia 失效 lynn520.xyz 失效 hub.docker-ttc.xyz 失效 noohub.ru 失效 docker.nat.tf 失效 dockerproxy.cn 失效 freeno.xyz 失效 docker.registry.cyou 失效 hub.yuzuha.cc 失效 docker-cf.registry.cyou 失效 docker.mrxn.net 失效 dockerproxy.github.io 失效 docker.wget.at 失效 atomhub.openatom.cn 失效 ccr.ccs.tencentyun.com 失效 dockerproxy.com 失效 dislabaiot....

November 5, 2024 · 327 words · Kurong

《计组KG》课题开发过程(三)

牢骚 时隔一个多月才完成了基本的可视化系统的搭建,中间有着各种各样的原因:Vue3第一次用、Django-Ninja不熟悉等等再加上一些闲杂原因就一直拖到了现在,效率多少有点堪忧。 总之不算怎么说,也算是曲折的完成了原定计划,下面将从后端、前端的技术选择、功能介绍、成品展示等几个章节大致的讲述下。 后端 项目地址 KurongTohsaka/PCCKGVisualization 技术栈 Web 框架自然是选相对擅长且好用的 Django ,但是 Django 本身使用起来又过于繁重,不太适合开发Restful类型的接口,所以一般会搭配着 Django REST Framework (DRF) 使用。但是这一次我打算尝试一个新的 Django 扩展,它是 FastAPI 的 Django 版:Django-Ninja 。 Web 框架订好了以后,就该选数据库了。既然是存储图数据,那肯定是 Neo4j 。而网站数据就使用简单的 Sqlite 吧,主要就是省事。 功能与接口 下面的所有接口的最上级请求路径:pcc_kg_vs 功能主要分为两部分:认证和可视化。 首先是认证,包含以下基本功能: 用户认证:用户登陆、注册、登出 API 鉴权:token 验证 CSRF 验证:CSRF token 验证 下面是简易的接口标准: 登陆 先检查该用户是否已注册,若注册则从数据库中根据信息查询到用户,然后登陆该用户。若未注册则登陆失败 1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "method": "POST", "path": "/login", "params": { "username": "", "password": "" }, "return": { "successful": bool, "code": int, "token": str, //登陆令牌 'info': str } } 登出 HTTPBearer...

November 4, 2024 · 247 words · Kurong

MAC上用docker配置neo4j以及apoc扩展的过程

拉取 neo4j Docker image 在 MAC 上用的 Docker Desktop,确实很方便操作。 直接在 Image Tab 页搜 Neo4j 就有一个官方 image,我选的 5.24.1版本,pull 下来。 这里之所以要选择一个特定的版本,原因是之后要启用的 apoc 扩展需要用对应 neo4j 版本的 jar 包。 配置容器 因为用的 Docker Desktop,很多没必要的步骤都可以直接忽略。配置好转发端口、挂载位置后,就启动容器。 Neo4j 容器启动后默认直接运行,登陆 Web 管理界面配置后用户名密码后就完成了基本配置。 Docker 真 tm 好用( 配置 apoc 这里需要注意一点,neo4j 5.x 之后的版本中,apoc 本身的相关配置需要写在 neo4j/conf/apoc.conf 里,而不是 neo4j/conf/neo4j.conf 中,不然在启动服务时会报错!!! 首先是 apoc 的下载地址:Index of /doc/neo4j-apoc/ 。 下载好后,把它放进 neo4j/plugins,并改名为 apoc.jar (啥名都行,这里是方面后面改配置)。 接下来修改 neo4j/conf/neo4j.conf ,修改以下两行并取消该行注释: 1 2 dbms.security.procedures.unrestricted=apoc.* dbms.security.procedures.allowlist=apoc.coll.*,apoc.load.* 最后修改 neo4j/conf/apoc.conf : 1 2 apoc.import.file.enabled=true apoc....

October 22, 2024 · 72 words · Kurong