复习-JavaGuide-Distributed_system

web-real-time-message-push

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

原文地址:https://juejin.cn/post/7122014462181113887,JavaGuide 对本文进行了完善总结。

我有一个朋友做了一个小破站,现在要实现一个站内信 Web 消息推送的功能,对,就是下图这个小红点,一个很常用的功能。

站内信 Web 消息推送

不过他还没想好用什么方式做,这里我帮他整理了一下几种方案,并简单做了实现。

# 什么是消息推送? #

推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用。

消息推送通常是指网站的运营工作等人员,通过某种工具对用户当前网页或移动设备 APP 进行的主动消息推送。

消息推送一般又分为 Web 端消息推送和移动端消息推送。

移动端消息推送示例 :

移动端消息推送示例

Web 端消息推送示例:

Web 端消息推送示例

在具体实现之前,咱们再来分析一下前边的需求,其实功能很简单,只要触发某个事件(主动分享了资源或者后台主动推送消息)Web 页面的通知小红点就会实时的 +1 就可以了。

通常在服务端会有若干张消息推送表,用来记录用户触发不同事件所推送不同类型的消息,前端主动查询(拉)或者被动接收(推)用户所有未读的消息数。

ly-20241212142026150

消息推送无非是推(push)和拉(pull)两种形式,下边我们逐个了解下。

# 消息推送常见方案 #

# 短轮询 #

轮询(polling) 应该是实现消息推送方案中最简单的一种,这里我们暂且将轮询分为短轮询长轮询

短轮询很好理解,指定的时间间隔由浏览器向服务器发出 HTTP 请求服务器实时返回未读消息数据给客户端,浏览器再做渲染显示

一个简单的 JS 定时器就可以搞定,每秒钟请求一次未读消息数接口,返回的数据展示即可。

setInterval(() => {
  // 方法请求
  messageCount().then((res) => {
      if (res.code === 200) {
          this.messageCount = res.data
      }
  })
}, 1000);

效果还是可以的,短轮询实现固然简单,缺点也是显而易见,由于推送数据并不会频繁变更,无论后端此时是否有新的消息产生,客户端都会进行请求,势必会对服务端造成很大压力,浪费带宽和服务器资源。

# 长轮询 #

长轮询是对上边短轮询的一种改进版本,在尽可能减少对服务器资源浪费的同时,保证消息的相对实时性。长轮询在中间件中应用的很广泛,比如 Nacos 和 Apollo 配置中心,消息队列 Kafka、RocketMQ 中都有用到长轮询。

...

Java定时任务详解

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

为什么需要定时任务? #

我们来看一下几个非常常见的业务场景:

  1. 某系统凌晨要进行数据备份
  2. 某电商平台,用户下单半个小时未支付的情况下需要自动取消订单。
  3. 某媒体聚合平台,每 10 分钟动态抓取某某网站的数据为自己所用。
  4. 某博客平台,支持定时发送文章
  5. 某基金平台,每晚定时计算用户当日收益情况并推送给用户最新的数据
  6. ……

这些场景往往都要求我们在某个特定的时间去做某个事情。

单机定时任务技术选型 #

Timer #

java.util.Timer是 JDK 1.3 开始就已经支持的一种定时任务的实现方式。

Timer 内部使用一个叫做 TaskQueue 的类存放定时任务,它是一个基于最小堆实现的优先级队列TaskQueue 会按照任务距离下一次执行时间的大小将任务排序,保证在堆顶的任务最先执行。这样在需要执行任务时,每次只需要取出堆顶的任务运行即可!

Timer 使用起来比较简单,通过下面的方式我们就能创建一个 1s 之后执行的定时任务。

// 示例代码:
TimerTask task = new TimerTask() {
    public void run() {
        System.out.println("当前时间: " + new Date() + "n" +
                "线程名称: " + Thread.currentThread().getName());
    }
};
System.out.println("当前时间: " + new Date() + "n" +
        "线程名称: " + Thread.currentThread().getName());
Timer timer = new Timer("Timer");
long delay = 1000L;
timer.schedule(task, delay);


//输出:
当前时间: Fri May 28 15:18:47 CST 2021n线程名称: main
当前时间: Fri May 28 15:18:48 CST 2021n线程名称: Timer

不过其缺陷较多,比如一个 Timer 一个线程,这就导致 Timer 的任务的执行只能串行执行,一个任务执行时间过长的话会影响其他任务(性能非常差),再比如发生异常时任务直接停止(Timer 只捕获了 InterruptedException

...

敏感词过滤方案总结

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

系统需要对用户输入的文本进行敏感词过滤如色情、政治、暴力相关的词汇。

敏感词过滤用的使用比较多的 Trie 树算法DFA 算法

算法实现 #

Trie 树 #

Trie 树 也称为字典树单词查找树哈系树(这里是不是写错了,哈希树?)的一种变种,通常被用于字符串匹配,用来解决在一组字符串集合中快速查找某个字符串的问题。像浏览器搜索的关键词提示一般就是基于 Trie 树来做的。

img

假如我们的敏感词库中有以下敏感词:

  • 高清有码
  • 高清 AV
  • 东京冷
  • 东京热

我们构造出来的敏感词 Trie 树就是下面这样的:

ly-20241212142025337

当我们要查找对应的字符串“东京热”的话,我们会把这个字符串切割成单个的字符“东”、“京”、“热”,然后我们从 Trie 树的根节点开始匹配

可以看出, Trie 树的核心原理其实很简单,就是通过公共前缀来提高字符串匹配效率。

Apache Commons Collecions 这个库中就有 Trie 树实现:

ly-20241212142025461.png

Trie<String, String> trie = new PatriciaTrie<>();
trie.put("Abigail", "student");
trie.put("Abi", "doctor");
trie.put("Annabel", "teacher");
trie.put("Christina", "student");
trie.put("Chris", "doctor");
Assertions.assertTrue(trie.containsKey("Abigail"));
assertEquals("{Abi=doctor, Abigail=student}", trie.prefixMap("Abi").toString());
assertEquals("{Chris=doctor, Christina=student}", trie.prefixMap("Chr").toString());

Aho-Corasick(AC)自动机是一种建立在 Trie 树上的一种改进算法,是一种多模式匹配算法,由贝尔实验室的研究人员 Alfred V. Aho 和 Margaret J.Corasick 发明。

...

权限系统设计详解

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

作者:转转技术团队

原文:https://mp.weixin.qq.com/s/ONMuELjdHYa0yQceTj01Iw

ly:比较繁琐,大概看了前面的部分

老权限系统的问题与现状 #

转转公司在过去并没有一个统一的权限管理系统,权限管理由各业务自行研发或是使用其他业务的权限系统,权限管理的不统一带来了不少问题:

  1. 各业务重复造轮子,维护成本高
  2. 各系统只解决部分场景问题,方案不够通用,新项目选型时没有可靠的权限管理方案
  3. 缺乏统一的日志管理审批流程,在授权信息追溯上十分困难

基于上述问题,去年底公司启动建设转转统一权限系统,目标是开发一套灵活、易用、安全的权限管理系统,供各业务使用。

业界权限系统的设计方式 #

目前业界主流的权限模型有两种,下面分别介绍下:

  • 基于角色的访问控制(RBAC)
  • 基于属性的访问控制(ABAC)

RBAC 模型 #

基于角色的访问控制(Role-Based Access Control,简称 RBAC) 指的是通过用户的角色(Role)授权其相关权限,实现了灵活的访问控制,相比直接授予用户权限,要更加简单、高效、可扩展。

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

用一个图来描述如下:

ly-20241212142024163

当使用 RBAC模型 时,通过分析用户的实际情况,基于共同的职责和需求,授予他们不同角色。这种 用户 -> 角色 -> 权限 间的关系,让我们可以不用再单独管理单个用户权限,用户从授予的角色里面获取所需的权限

以一个简单的场景(Gitlab 的权限系统)为例,用户系统中有 AdminMaintainerOperator 三种角色,这三种角色分别具备不同的权限,比如只有 Admin 具备创建代码仓库、删除代码仓库的权限,其他的角色都不具备。我们授予某个用户 Admin 这个角色,他就具备了 创建代码仓库删除代码仓库 这两个权限。

通过 RBAC模型 ,当存在多个用户拥有相同权限时,我们只需要创建好拥有该权限的角色,然后给不同的用户分配不同的角色,后续只需要修改角色的权限,就能自动修改角色内所有用户的权限。

ABAC 模型 #

基于属性的访问控制(Attribute-Based Access Control,简称 ABAC) 是一种比 RBAC模型 更加灵活的授权模型,它的原理是通过各种属性来动态判断一个操作是否可以被允许。这个模型在云系统中使用的比较多,比如 AWS,阿里云等。

考虑下面这些场景的权限控制:

  1. 授权某个人具体某本书的编辑权限
  2. 当一个文档的所属部门用户的部门相同时,用户可以访问这个文档
  3. 用户是一个文档的拥有者并且文档的状态是草稿,用户可以编辑这个文档
  4. 早上九点前禁止 A 部门的人访问 B 系统
  5. 除了上海以外的地方禁止以管理员身份访问 A 系统
  6. 用户对 2022-06-07 之前创建的订单有操作权限

可以发现上述的场景通过 RBAC模型 很难去实现,因为 RBAC模型 仅仅描述了用户可以做什么操作,但是操作的条件,以及操作的数据,RBAC模型 本身是没有这些限制的。但这恰恰是 ABAC模型 的长处,ABAC模型 的思想是基于用户、访问的数据的属性、以及各种环境因素去动态计算用户是否有权限进行操作。

...

sso单点登录

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

本文授权转载自 : https://ken.io/note/sso-design-implement 作者:ken.io

SSO 介绍 #

什么是 SSO? #

SSO 英文全称 Single Sign On,单点登录。SSO 是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

例如你登录网易账号中心(https://reg.163.com/ )之后访问以下站点都是登录状态。

SSO 有什么好处? #

  1. 用户角度 :用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。
  2. 系统管理员角度 : 管理员只需维护好一个统一的账号中心就可以了,方便。
  3. 新系统开发角度: 新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。

SSO 设计与实现 #

本篇文章也主要是为了探讨如何设计&实现一个 SSO 系统

以下为需要实现的核心功能:

  • 单点登录
  • 单点登出
  • 支持跨域单点登录
  • 支持跨域单点登出

核心应用与依赖 #

单点登录(SSO)设计

应用/模块/对象说明
前台站点需要登录的站点
SSO 站点-登录提供登录的页面
SSO 站点-登出提供注销登录的入口
SSO 服务-登录提供登录服务
SSO 服务-登录状态提供登录状态校验/登录信息查询的服务
SSO 服务-登出提供用户注销登录的服务
数据库存储用户账户信息
缓存存储用户的登录信息,通常使用 Redis

用户登录状态的存储与校验 #

常见的 Web 框架对于 Session 的实现都是生成一个 SessionId 存储在浏览器 Cookie 中。然后将 Session 内容存储在服务器端内存中,这个 ken.io 在之前 Session 工作原理中也提到过。整体也是借鉴这个思路。

...

jwt身份认证优缺点

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

JWT 基本概念详解这篇文章中,我介绍了:

  • 什么是 JWT?
  • JWT 由哪些部分组成?
  • 如何基于 JWT 进行身份验证?
  • JWT 如何防止 Token 被篡改?
  • 如何加强 JWT 的安全性?

这篇文章,我们一起探讨一下 JWT 身份认证的优缺点以及常见问题的解决办法

JWT 的优势 #

相比于 Session 认证的方式来说,使用 JWT 进行身份认证主要有下面 4 个优势。

无状态 #

JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。

不过,也正是由于 JWT 的无状态,也导致了它最大的缺点:不可控!

就比如说,我们想要在 JWT 有效期内废弃一个 JWT 或者更改它的权限的话,并不会立即生效,通常需要等到有效期过后才可以。再比如说,当用户 Logout 的话,JWT 也还有效。除非,我们在后端增加额外的处理逻辑比如将失效的 JWT 存储起来,后端先验证 JWT 是否有效再进行处理。具体的解决办法,我们会在后面的内容中详细介绍到,这里只是简单提一下。

有效避免了 CSRF 攻击 #

[ˈfɔːdʒəri] forgery 伪造

CSRF(Cross Site Request Forgery) 一般被翻译为 跨站请求伪造,属于网络攻击领域范围。相比于 SQL 脚本注入、XSS 等安全攻击方式,CSRF 的知名度并没有它们高。但是,它的确是我们开发系统时必须要考虑的安全隐患。就连业内技术标杆 Google 的产品 Gmail 也曾在 2007 年的时候爆出过 CSRF 漏洞,这给 Gmail 的用户造成了很大的损失。

...

jwt-intro

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

image.png

什么是 JWT? #

JWT (JSON Web Token) 是目前最流行的跨域认证解决方案,是一种基于 Token 的认证授权机制。 从 JWT 的全称可以看出,JWT 本身也是 Token,一种规范化之后的 JSON 结构的 Token。

跨域认证的问题
互联网服务离不开用户认证。一般流程是下面这样。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享每台服务器都能够读取 session

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种解决方案是 session 数据持久化写入数据库别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。

ly:我觉得这里的重点就是,服务器不存储Session以维护"用户"和cookie(session id)的关系了

可以看出,JWT 更符合设计 RESTful API 时的「Stateless(无状态)」原则

并且, 使用 JWT 认证可以有效避免 CSRF 攻击,因为 JWT 一般是存在在 localStorage 中,使用 JWT 进行身份验证的过程中是不会涉及到 Cookie 的。

我在 JWT 优缺点分析这篇文章中有详细介绍到使用 JWT 做身份认证的优势和劣势。

下面是 RFC 7519 对 JWT 做的较为正式的定义。

...

认证授权基础概念详解

转载自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

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

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

何时进行重构? #

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

提交代码之前 #

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

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

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

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

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

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

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

...