学习

认证授权基础概念详解

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

认证 (Authentication) 和授权 (Authorization)的区别是什么? #

这是一个绝大多数人都会混淆的问题。首先先从读音上来认识这两个名词,很多人都会把它俩的读音搞混,所以我建议你先先去查一查这两个单词到底该怎么读,他们的具体含义是什么。

说简单点就是:

  • 认证 (Authentication): 你是谁。[ɔːˌθentɪˈkeɪʃn] 身份验证
  • 授权 (Authorization): 你有权限干什么。[ˌɔːθəraɪˈzeɪʃn] 授权

稍微正式点(啰嗦点)的说法就是 :

  • Authentication(认证)验证您的身份的凭据(例如用户名/用户 ID 和密码),通过这个凭据,系统得以知道你就是你,也就是说系统存在你这个用户。所以,Authentication 被称为身份/用户验证。
  • Authorization(授权) 发生在 Authentication(认证) 之后。授权嘛,光看意思大家应该就明白,它主要掌管我们访问系统的权限。比如有些特定资源只能具有特定权限的人才能访问比如 admin,有些对系统资源操作比如删除、添加、更新只能特定人才具有。

认证 :

img

授权:

img

这两个一般在我们的系统中被结合在一起使用,目的就是为了保护我们系统的安全性。

RBAC 模型了解吗? #

系统权限控制最常采用的访问控制模型就是 RBAC 模型

什么是 RBAC 呢?

RBAC 即基于角色的权限访问控制Role-Based Access Control)。这是一种通过角色关联权限,角色同时又关联用户的授权的方式。

简单地说:一个用户可以拥有若干角色,每一个角色又可以被分配若干权限,这样就构造成“用户-角色-权限” 的授权模型。在这种模型中,用户与角色、角色与权限之间构成了多对多的关系,如下图

image.png

在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。

本系统的权限设计相关的表如下(一共 5 张表,2 张用户建立表之间的联系):

ly-20241212142021291.png

通过这个权限模型,我们可以创建不同的角色并为不同的角色分配不同的权限范围(菜单)。

ly-20241212142021421

通常来说,如果系统对于权限控制要求比较严格的话,一般都会选择使用 RBAC 模型来做权限控制。

img

...

单元测试

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

何谓单元测试? #

维基百科是这样介绍单元测试的:

在计算机编程中,单元测试(Unit Testing)是针对程序模块(软件设计的最小单位)进行的正确性检验测试工作。

程序单元是应用的 最小可测试部件 。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

由于每个单元有独立的逻辑,在做单元测试时,为了隔离外部依赖,确保这些依赖不影响验证逻辑,我们经常会用到 Fake、Stub 与 Mock 。

关于 Fake、Mock 与 Stub 这几个概念的解读,可以看看这篇文章: 测试中 Fakes、Mocks 以及 Stubs 概念明晰 - 王下邀月熊 - 2018

为什么需要单元测试? #

为重构保驾护航 #

我在 重构这篇文章中这样写到:

单元测试可以为重构提供信心,降低重构的成本。我们要像重视生产代码那样,重视单元测试。

每个开发者都会经历重构,重构后把代码改坏了的情况并不少见,很可能你只是修改了一个很简单的方法就导致系统出现了一个比较严重的错误。

如果有了单元测试的话,就不会存在这个隐患了。写完一个类,把单元测试写了,确保这个类逻辑正确;写第二个类,单元测试…..写 100 个类,道理一样,每个类做到第一点“保证逻辑正确性”,100 个类拼在一起肯定不出问题。你大可以放心一边重构,一边运行 APP;而不是整体重构完,提心吊胆地 run。

提高代码质量 #

由于每个单元有独立的逻辑,做单元测试时需要隔离外部依赖,确保这些依赖不影响验证逻辑。因为要把各种依赖分离,单元测试会促进工程进行组件拆分,整理工程依赖关系,更大程度减少代码耦合。这样写出来的代码,更好维护,更好扩展,从而提高代码质量。

减少 bug #

一个机器,由各种细小的零件组成,如果其中某件零件坏了,机器运行故障。必须保证每个零件都按设计图要求的规格,机器才能正常运行。

一个可单元测试的工程,会把业务功能分割成规模更小、有独立的逻辑部件,称为单元。单元测试的目标,就是保证各个单元的逻辑正确性。单元测试保障工程各个“零件”按“规格”(需求)执行,从而保证整个“机器”(项目)运行正确,最大限度减少 bug。

快速定位 bug #

如果程序有 bug,我们运行一次全部单元测试,找到不通过的测试,可以很快地定位对应的执行代码。修复代码后,运行对应的单元测试;如还不通过,继续修改,运行测试…..直到测试通过

持续集成依赖单元测试 #

持续集成需要依赖单元测试,当持续集成服务自动构建新代码之后,会自动运行单元测试来发现代码错误。

谁逼你写单元测试? #

领导要求 #

有些经验丰富的领导,或多或少都会要求团队写单元测试。对于有一定工作经验的队友,这要求挺合理;对于经验尚浅的、毕业生,恐怕要死要活了,连代码都写不好,还要写单元测试,are you kidding me?

...

代码重构指南

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

前段时间重读了 《重构:改善代码既有设计》,收货颇多。于是,简单写了一篇文章来聊聊我对重构的看法。

img

何谓重构? #

学习重构必看的一本神书《重构:改善代码既有设计》从两个角度给出了重构的定义:

  • 重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性降低其修改成本
  • 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

用更贴近工程师的语言来说: 重构就是利用设计模式(如组合模式、策略模式、责任链模式)、软件设计原则(如 SOLID 原则、YAGNI 原则、KISS 原则)和重构手段(如封装、继承、构建测试体系)来让代码更容易理解,更易于修改。

软件设计原则指导着我们组织和规范代码,同时,重构也是为了能够尽量设计出尽量满足软件设计原则的软件。

正确重构的核心在于 步子一定要小,每一步的重构都不会影响软件的正常运行,可以随时停止重构。

常见的设计模式如下

img

更全面的设计模式总结,可以看 java-design-patterns 这个开源项目。

常见的软件设计原则如下

img

更全面的设计原则总结,可以看 java-design-patternshacker-laws-zh 这两个开源项目。

为什么要重构? #

在上面介绍重构定义的时候,我从比较抽象的角度介绍了重构的好处:重构的主要目的主要是提升代码&架构灵活性/可扩展性以及复用性。

如果对应到一个真实的项目,重构具体能为我们带来什么好处呢?

  1. 让代码更容易理解 : 通过添加注释、命名规范、逻辑优化等手段可以让我们的代码更容易被理解;
  2. 避免代码腐化 :通过重构干掉坏味道代码;
  3. 加深对代码的理解 :重构代码的过程会加深你对某部分代码的理解;
  4. 发现潜在 bug :是这样的,很多潜在的 bug ,都是我们在重构的过程中发现的;
  5. ……

看了上面介绍的关于重构带来的好处之后,你会发现重构的最终目标是 提高软件开发速度和质量

重构并不会减慢软件开发速度,相反,如果代码质量和软件设计较差,当我们想要添加新功能的话,开发速度会越来越慢。到了最后,甚至都有想要重写整个系统的冲动。

[img

《重构:改善代码既有设计》这本书中这样说:

重构的唯一目的就是让我们开发更快,用更少的工作量创造更大的价值。

何时进行重构? #

重构在是开发过程中随时可以进行的,见机行事即可,并不需要单独分配一两天的时间专门用来重构。

提交代码之前 #

《重构:改善代码既有设计》这本书介绍了一个 营地法则 的概念:

编程时,需要遵循营地法则:保证你离开时的代码库一定比来时更健康

这个概念表达的核心思想其实很简单:在你提交代码的之前,花一会时间想一想,我这次的提交是让项目代码变得更健康了,还是更腐化了,或者说没什么变化?

项目团队的每一个人只有保证自己的提交没有让项目代码变得更腐化,项目代码才会朝着健康的方向发展。

当我们离开营地(项目代码)的时候,请不要留下垃圾(代码坏味道)!尽量确保营地变得更干净了!

开发一个新功能之后&之前 #

在开发一个新功能之后,我们应该回过头看看是不是有可以改进的地方。在添加一个新功能之前,我们可以思考一下自己是否可以重构代码以让新功能的开发更容易

...

代码命名指南

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

我还记得我刚工作那一段时间, 项目 Code Review 的时候,我经常因为变量命名不规范而被 “diss”!

究其原因还是自己那会经验不足,而且,大学那会写项目的时候不太注意这些问题,想着只要把功能实现出来就行了。

但是,工作中就不一样,为了代码的可读性、可维护性,项目组对于代码质量的要求还是很高的!

前段时间,项目组新来的一个实习生也经常在 Code Review 因为变量命名不规范而被 “diss”,这让我想到自己刚到公司写代码那会的日子。

于是,我就简单写了这篇关于变量命名规范的文章,希望能对同样有此困扰的小伙伴提供一些帮助。

确实,编程过程中,有太多太多让我们头疼的事情了,比如命名、维护其他人的代码、写测试、与其他人沟通交流等等。

据说之前在 Quora 网站,由接近 5000 名程序员票选出来的最难的事情就是“命名”。

大名鼎鼎的《重构》的作者老马(Martin Fowler)曾经在 TwoHardThings这篇文章中提到过CS 领域有两大最难的事情:一是 缓存失效 ,一是 程序命名

img

这个句话实际上也是老马引用别人的,类似的表达还有很多。比如分布式系统领域有两大最难的事情:一是 保证消息顺序 ,一是 严格一次传递

img

今天咱们就单独拎出 “命名” 来聊聊!

这篇文章配合我之前发的 《编码 5 分钟,命名 2 小时?史上最全的 Java 命名规范参考!》 这篇文章阅读效果更佳哦!

为什么需要重视命名? #

咱们需要先搞懂为什么要重视编程中的命名这一行为,它对于我们的编码工作有着什么意义。

为什么命名很重要呢? 这是因为 好的命名即是注释,别人一看到你的命名就知道你的变量、方法或者类是做什么的!

简单来说就是 别人根据你的命名就能知道你的代码要表达的意思 (不过,前提这个人也要有基本的英语知识,对于一些编程中常见的单词比较熟悉)。

简单举个例子说明一下命名的重要性。

《Clean Code》这本书明确指出:

好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。

若编程语言足够有表达力,就不需要注释,尽量通过代码来阐述。

举个例子:

去掉下面复杂的注释,只需要创建一个与注释所言同一事物的函数即可

// check to see if the employee is eligible for full benefits
if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))

应替换为

...

软件工程简明教程

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

大部分软件开发从业者,都会忽略软件开发中的一些最基础、最底层的一些概念。但是,这些软件开发的概念对于软件开发来说非常重要,就像是软件开发的基石一样。这也是我写这篇文章的原因。

何为软件工程? #

1968 年 NATO(北大西洋公约组织)提出了软件危机Software crisis)一词。同年,为了解决软件危机问题,“软件工程”的概念诞生了。一门叫做软件工程的学科也就应运而生。

随着时间的推移,软件工程这门学科也经历了一轮又一轮的完善,其中的一些核心内容比如软件开发模型越来越丰富实用!

什么是软件危机呢?

简单来说,软件危机描述了当时软件开发的一个痛点:我们很难高效地开发出质量高的软件。

Dijkstra(Dijkstra算法的作者) 在 1972年图灵奖获奖感言中也提高过软件危机,他是这样说的:“导致软件危机的主要原因是机器变得功能强大了几个数量级!坦率地说:只要没有机器,编程就完全没有问题。当我们有一些弱小的计算机时,编程成为一个温和的问题,而现在我们有了庞大的计算机,编程也同样成为一个巨大的问题”。

说了这么多,到底什么是软件工程呢?

工程是为了解决实际的问题将理论应用于实践。软件工程指的就是将工程思想应用于软件开发

上面是我对软件工程的定义,我们再来看看比较权威的定义。IEEE 软件工程汇刊给出的定义是这样的: (1)将系统化的、规范的、可量化的方法应用到软件的开发、运维护中,即将工程化方法应用于软件。 (2)在(1)中所述方法的研究。

总之,软件工程的终极目标就是:在更少资源消耗的情况下,创造出更好、更容易维护的软件。

软件开发过程 #

维基百科是这样定义软件开发过程的:

软件开发过程(英语:software development process),或软件过程(英语:software process),是软件开发的开发生命周期(software development life cycle),其各个阶段实现了软件的需求定义与分析设计实现测试、交付和维护。软件过程是在开发与构建系统时应遵循的步骤,是软件开发的路线图。

  • 需求分析 :分析用户的需求,建立逻辑模型。
  • 软件设计 : 根据需求分析的结果对软件架构进行设计。
  • 编码 :编写程序运行的源代码。
  • 测试 : 确定测试用例,编写测试报告。
  • 交付 :将做好的软件交付给客户。
  • 维护 :对软件进行维护比如解决 bug,完善功能。

软件开发过程只是比较笼统的层面上,一定义了一个软件开发可能涉及到的一些流程。

软件开发模型更具体地定义了软件开发过程,对开发过程提供了强有力的理论支持。

软件开发模型 #

软件开发模型有很多种,比如瀑布模型(Waterfall Model)快速原型模型(Rapid Prototype Model)V模型(V-model)W模型(W-model)敏捷开发模型。其中最具有代表性的还是 瀑布模型敏捷开发

瀑布模型 定义了一套完成的软件开发周期,完整地展示了一个软件的的生命周期。

ly-20241212142018153

敏捷开发模型 是目前使用的最多的一种软件开发模型。 MBA智库百科对敏捷开发的描述是这样的:

敏捷开发 是一种以人为核心、迭代、循序渐进的开发方法。在敏捷开发中,软件项目的构建被切分成多个子项目,各个子项目的成果都经过测试,具备集成可运行的特征。换言之,就是把一个大项目分为多个相互联系,但也可独立运行的小项目,并分别完成,在此过程中软件一直处于可使用状态。

像现在比较常见的一些概念比如 持续集成重构小版本发布低文档站会结对编程测试驱动开发 都是敏捷开发的核心。

...

restFul

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

img

这篇文章简单聊聊后端程序员必备的 RESTful API 相关的知识。

开始正式介绍 RESTful API 之前,我们需要首先搞清 :API 到底是什么?

# 何为 API? #

img

API(Application Programming Interface) 翻译过来是应用程序编程接口的意思。

我们在进行后端开发的时候,主要的工作就是为前端或者其他后端服务提供 API 比如查询用户数据的 API

ly-20241212142017877

但是, API 不仅仅代表后端系统暴露的接口,像框架中提供的方法也属于 API 的范畴。

为了方便大家理解,我再列举几个例子 🌰:

  1. 你通过某电商网站搜索某某商品,电商网站的前端就调用了后端提供了搜索商品相关的 API
  2. 你使用 JDK 开发 Java 程序,想要读取用户的输入的话,你就需要使用 JDK 提供的 IO 相关的 API
  3. ……

你可以把 API 理解为程序与程序之间通信的桥梁,其本质就是一个函数而已。另外,API 的使用也不是没有章法的,它的规则由(比如数据输入输出的格式)API 提供方制定。

# 何为 RESTful API? #

RESTful API 经常也被叫做 REST API,它是基于 REST 构建的 API。这个 REST 到底是什么,我们后文在讲,涉及到的概念比较多。

如果你看 RESTful API 相关的文章的话一般都比较晦涩难懂,主要是因为 REST 涉及到的一些概念比较难以理解。但是,实际上,我们平时开发用到的 RESTful API 的知识非常简单也很容易概括!

...

性能测试入门

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

性能测试一般情况下都是由测试这个职位去做的,那还需要我们开发学这个干嘛呢?了解性能测试的指标分类以及工具等知识有助于我们更好地去写出性能更好的程序,另外作为开发这个角色,如果你会性能测试的话,相信也会为你的履历加分不少。

这篇文章是我会结合自己的实际经历以及在测试这里取的经所得,除此之外,我还借鉴了一些优秀书籍,希望对你有帮助。

本文思维导图:

img

# 一 不同角色看网站性能 #

# 1.1 用户 #

当用户打开一个网站的时候,最关注的是什么?当然是网站响应速度的快慢。比如我们点击了淘宝的主页,淘宝需要多久将首页的内容呈现在我的面前,我点击了提交订单按钮需要多久返回结果等等。

所以,用户在体验我们系统的时候往往根据你的响应速度的快慢来评判你的网站的性能。

# 1.2 开发人员 #

用户与开发人员都关注速度,这个速度实际上就是我们的系统处理用户请求的速度

开发人员一般情况下很难直观的去评判自己网站的性能,我们往往会根据网站当前的架构以及基础设施情况给一个大概的值,比如:

  1. 项目架构是分布式的吗?
  2. 用到了缓存消息队列没有?
  3. 高并发的业务有没有特殊处理?
  4. 数据库设计是否合理?
  5. 系统用到的算法是否还需要优化?
  6. 系统是否存在内存泄露的问题?
  7. 项目使用的 Redis 缓存多大?服务器性能如何?用的是机械硬盘还是固态硬盘
  8. ……

# 1.3 测试人员 #

测试人员一般会根据性能测试工具来测试,然后一般会做出一个表格。这个表格可能会涵盖下面这些重要的内容:

  1. 响应时间
  2. 请求成功率
  3. 吞吐量;
  4. ……

# 1.4 运维人员 #

运维人员会倾向于根据基础设施资源的利用率来判断网站的性能,比如我们的服务器资源使用是否合理、数据库资源是否存在滥用的情况、当然,这是传统的运维人员,现在 Devpos 火起来后,单纯干运维的很少了。我们这里暂且还保留有这个角色。

# 二 性能测试需要注意的点 #

几乎没有文章在讲性能测试的时候提到这个问题,大家都会讲如何去性能测试,有哪些性能测试指标这些东西。

# 2.1 了解系统的业务场景 #

性能测试之前更需要你了解当前的系统的业务场景。 对系统业务了解的不够深刻,我们很容易犯测试方向偏执的错误,从而导致我们忽略了对系统某些更需要性能测试的地方进行测试。比如我们的系统可以为用户提供发送邮件的功能,用户配置成功邮箱后只需输入相应的邮箱之后就能发送,系统每天大概能处理上万次发邮件的请求。很多人看到这个可能就直接开始使用相关工具测试邮箱发送接口,但是,发送邮件这个场景可能不是当前系统的性能瓶颈,这么多人用我们的系统发邮件, 还可能有很多人一起发邮件,单单这个场景就这么人用,那用户管理可能才是性能瓶颈吧!

# 2.2 历史数据非常有用 #

当前系统所留下的历史数据非常重要,一般情况下,我们可以通过相应的些历史数据初步判定这个系统哪些接口调用的比较多哪些 service 承受的压力最大,这样的话,我们就可以针对这些地方进行更细致的性能测试与分析。

另外,这些地方也就像这个系统的一个短板一样,优化好了这些地方会为我们的系统带来质的提升。

# 三 性能测试的指标 #

# 3.1 响应时间 #

响应时间就是用户发出请求到用户收到系统处理结果所需要的时间。 重要吗?实在太重要!

...

超时&重试详解

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

由于网络问题、系统或者服务内部的 Bug、服务器宕机、操作系统崩溃等问题的不确定性,我们的系统或者服务永远不可能保证时刻都是可用的状态。

为了最大限度的减小系统或者服务出现故障之后带来的影响,我们需要用到的 超时(Timeout)重试(Retry) 机制。

想要把超时和重试机制讲清楚其实很简单,因为它俩本身就不是什么高深的概念。

虽然超时和重试机制的思想很简单,但是它俩是真的非常实用。你平时接触到的绝大部分涉及到远程调用的系统或者服务都会应用超时和重试机制。尤其是对于微服务系统来说,正确设置超时和重试非常重要。单体服务通常只涉及数据库缓存第三方 API中间件等的网络调用,而微服务系统内部各个服务之间还存在着网络调用

# 超时机制 #

# 什么是超时机制? #

超时机制说的是当一个请求超过指定的时间(比如 1s)还没有被处理的话,这个请求就会直接被取消并抛出指定的异常或者错误(比如 504 Gateway Timeout)。

我们平时接触到的超时可以简单分为下面 2 种:

  • 连接超时(ConnectTimeout) :客户端与服务端建立连接的最长等待时间。
  • 读取超时(ReadTimeout) :客户端和服务端已经建立连接客户端等待服务端处理完请求的最长时间。实际项目中,我们关注比较多的还是读取超时

一些连接池客户端框架中可能还会有获取连接超空闲连接清理超时**。

如果没有设置超时的话,就可能会导致服务端连接数爆炸大量请求堆积的问题。

这些堆积的连接和请求会消耗系统资源影响新收到的请求的处理。严重的情况下,甚至会拖垮整个系统或者服务

我之前在实际项目就遇到过类似的问题,整个网站无法正常处理请求,服务器负载直接快被拉满。后面发现原因是项目超时设置错误加上客户端请求处理异常,导致服务端连接数直接接近 40w+,这么多堆积的连接直接把系统干趴了。

# 超时时间应该如何设置? #

超时到底设置多长时间是一个难题!超时值设置太高或者太低都有风险。如果设置太高的话,会降低超时机制的有效性,比如你设置超时为 10s 的话,那设置超时就没啥意义了,系统依然可能会出现大量慢请求堆积的问题。如果设置太低的话,就可能会导致在系统或者服务在某些处理请求速度变慢的情况下(比如请求突然增多),大量请求重试(超时通常会结合重试)继续加重系统或者服务的压力,进而导致整个系统或者服务被拖垮的问题。

通常情况下,我们建议读取超时设置为 1500ms ,这是一个比较普适的值。如果你的系统或者服务对于延迟比较敏感的话,那读取超时值可以适当在 1500ms 的基础上进行缩短。反之,读取超时值也可以在 1500ms 的基础上进行加长,不过,尽量还是不要超过 1500ms 。连接超时可以适当设置长一些,建议在 1000ms ~ 5000ms 之内。

没有银弹!超时值具体该设置多大,还是要根据实际项目的需求和情况慢慢调整优化得到。

更上一层,参考 美团的Java线程池参数动态配置open in new window思想,我们也可以将超时弄成可配置化的参数而不是固定的,比较简单的一种办法就是将超时的值放在配置中心中。这样的话,我们就可以根据系统或者服务的状态动态调整超时值了。

# 重试机制 #

# 什么是重试机制? #

重试机制一般配合超时机制一起使用,指的是多次发送相同的请求避免瞬态故障偶然性故障

瞬态故障可以简单理解为某一瞬间系统偶然出现的故障,并不会持久。偶然性故障可以理解为哪些在某些情况下偶尔出现的故障,频率通常较低。

重试的核心思想是通过消耗服务器的资源来尽可能获得请求更大概率被成功处理。由于瞬态故障和偶然性故障是很少发生的,因此,重试对于服务器的资源消耗几乎是可以被忽略的。

# 重试的次数如何设置? #

重试的次数不宜过多,否则依然会对系统负载造成比较大的压力。

...

服务限流详解

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

针对软件系统来说,限流就是对请求的速率进行限制避免瞬时的大量请求击垮软件系统。毕竟,软件系统的处理能力是有限的。如果说超过了其处理能力的范围,软件系统可能直接就挂掉了。

限流可能会导致用户的请求无法被正确处理,不过,这往往也是权衡了软件系统的稳定性之后得到的最优解

现实生活中,处处都有限流的实际应用,就比如排队买票是为了避免大量用户涌入购票而导致售票员无法处理

排队示意图

常见限流算法有哪些? #

简单介绍 4 种非常好理解并且容易实现的限流算法!

图片来源于 InfoQ 的一篇文章 《分布式服务限流实战,已经为你排好坑了》

固定窗口计数器算法 #

固定窗口其实就是时间窗口。固定窗口计数器算法 规定了我们单位时间处理的请求数量

假如我们规定系统中某个接口 1 分钟只能访问 33 次的话,使用固定窗口计数器算法的实现思路如下:

  • 给定一个变量 counter记录当前接口处理的请求数量,初始值为 0(代表接口当前 1 分钟内还未处理请求)。
  • 1 分钟之内每处理一个请求之后就将 counter+1 ,当 counter=33 之后(也就是说在这 1 分钟内接口已经被访问 33 次的话),后续的请求就会被全部拒绝。
  • 等到 1 分钟结束后,将 counter 重置 0,重新开始计数。

这种限流算法无法保证限流速率,因而无法保证突然激增的流量。

就比如说我们限制某个接口 1 分钟只能访问 1000 次,该接口的 QPS 为 500,前 55s 这个接口 1 个请求没有接收,后 1s 突然接收了 1000 个请求。然后,在当前场景下,这 1000 个请求在 1s 内是没办法被处理的,系统直接就被瞬时的大量请求给击垮了。

固定窗口计数器算法

...

冗余设计

转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!

titlecategory
冗余设计详解高可用

冗余设计是保证系统和数据高可用的最常的手段。

对于服务来说,冗余的思想就是相同的服务部署多份,如果正在使用的服务突然挂掉的话,系统可以很快切换到备份服务上,大大减少系统的不可用时间,提高系统的可用性。

对于数据来说,冗余的思想就是相同的数据备份多份,这样就可以很简单地提高数据的安全性

实际上,日常生活中就有非常多的冗余思想的应用。

拿我自己来说,我对于重要文件的保存方法就是冗余思想的应用。我日常所使用的重要文件都会同步一份在 Github 以及个人云盘上,这样就可以保证即使电脑硬盘损坏,我也可以通过 Github 或者个人云盘找回自己的重要文件。

高可用集群(High Availability Cluster,简称 HA Cluster)同城灾备异地灾备同城多活异地多活是冗余思想在高可用系统设计中最典型的应用。

  • 高可用集群 : 同一份服务部署两份或者多份,当正在使用的服务突然挂掉的话,可以切换到另外一台服务,从而保证服务的高可用。
  • 同城灾备 :一整个集群可以部署在同一个机房,而同城灾备中相同服务部署在同一个城市不同机房中。并且,备用服务不处理请求。这样可以避免机房出现意外情况比如停电、火灾。
  • 异地灾备 :类似于同城灾备,不同的是,相同服务部署在异地(通常距离较远,甚至是在不同的城市或者国家)的不同机房
  • 同城多活 :类似于同城灾备,但备用服务可以处理请求,这样可以充分利用系统资源,提高系统的并发。
  • 异地多活 : 将服务部署在异地的不同机房中,并且,它们可以同时对外提供服务

高可用集群单纯是服务的冗余,并没有强调地域。同城灾备、异地灾备、同城多活和异地多活实现了地域上的冗余。

同城和异地的主要区别在于机房之间的距离。异地通常距离较远,甚至是在不同的城市或者国家。

和传统的灾备设计相比,同城多活和异地多活最明显的改变在于**“多活”,即所有站点都是同时在对外提供服务的。异地多活是为了应对突发状况比如火灾**、地震等自然或者人为灾害。

光做好冗余还不够,必须要配合上 故障转移 才可以! 所谓故障转移,简单来说就是实现不可用服务快速且自动地切换到可用服务,整个过程不需要人为干涉

举个例子:哨兵模式的 Redis 集群中,如果 Sentinel(哨兵) 检测到 master 节点出现故障的话, 它就会帮助我们实现故障转移,自动将某一台 slave 升级为 master,确保整个 Redis 系统的可用性。整个过程完全自动,不需要人工介入。我在 《Java 面试指北》的「技术面试题篇」中的数据库部分详细介绍了 Redis 集群相关的知识点&面试题,感兴趣的小伙伴可以看看。

再举个例子:Nginx 可以结合 Keepalived 来实现高可用。如果 Nginx 主服务器宕机的话,Keepalived 可以自动进行故障转移,备用 Nginx 主服务器升级为主服务。并且,这个切换对外是透明的,因为使用的虚拟 IP,虚拟 IP 不会改变。我在 《Java 面试指北》的「技术面试题篇」中的「服务器」部分详细介绍了 Nginx 相关的知识点&面试题,感兴趣的小伙伴可以看看。

异地多活架构实施起来非常难,需要考虑的因素非常多。本人不才,实际项目中并没有实践过异地多活架构,我对其了解还停留在书本知识。

...