SaaS 系统架构设计思路
SaaS 多租户难点: 1. 数据隔离(用户担心数据安全) 2. 定制化(和通用设计的冲突,客户要求加字段)
数据隔离分为两种:
软隔离 数据在一起,带着租户 ID 查询 更好实现,更好维护
硬隔离 直接按租户分库 缺点:库会越来越多,可能成本高
软隔离可以通过架构来实现硬隔离。
数据库层封装:把所有的数据操作带着租户 ID。 是在底层驱动上封装,强制隔离租户。 具体做法:可以在驱动源码上修改;比较方便的做法应该是在驱动包上封一层(增强代理)。 (在 DAO 层用 AOP 也可以,但是可能被误删。所以不如在框架层面实现,避免写错。)
消息队列封装:同理,在驱动包上强制检查租户 ID(没租户 ID 不让发消息),或者强制拼接上当前的租户 ID。
Redis 封装:同理,拼装 key 的时候直接把租户 ID 拼接上。
租户 ID 怎么自动拼装建议方案:上下文例如使用 ThreadLocal注意中间别开线程,如果中间有线程池的话,开新线程时需要把 ThreadLocal 复制过去(上下文传递)
这样就避免了调用链路上的每个方法都需要传租户 ID
设计联想:Threa ...
📝《深入理解 Java 虚拟机》笔记
2 Java 内存区域与内存溢出异常2.1 概述2.2 运行时数据区域
2.2.1 程序计数器Program Counter Register,是一块较小的内存空间,线程私有,可以看作是当前线程所执行的字节码的行号指示器。
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
如果线程: * 正在执行 Java 方法 -> 这个计数器记录的是正在执行的虚拟机字节码指令的地址 * 正在执行 Native 方法 -> 这个计数器为空(Undefined)。
此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
2.2.2 Java 虚拟机栈Java Virtual Machine Stacks,线程私有,它的生命周期与线程相同。
描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的 ...
(待更新)📝JVM-2:JVM-垃圾回收
如何判断对象可被回收垃圾回收算法判断垃圾可被回收:引用计数法当前对象被一个变量所引用,则该对象引用计数加一当对象的引用计数为 0 时 -> 应该回收
缺陷:这种情况会导致内存泄漏
判断垃圾可被回收:可达性分析算法这是 JVM 判断对象是否为垃圾的算法
一些根对象(GC Root):一定不会被回收的对象JVM 进行垃圾回收时,会先去堆中将所有对象扫描一遍,看是否能够沿着 GC Root 为起点的引用链找到该对象:如果找不到 -> 表示可以回收;如果找得到 -> 不回收
阅读扩展:对象的存活与毁灭(待优化)要经历两次标记:
第一次标记:是否在 GC Root 的引用链上(是否可达)
(在对象被判断为不可达之后不会立即回收,还会有第二次标记)将第一次标记为需要回收的对象放入一个 F-Queue 队列中,然后让一个优先级较低的线程来调用队列中要回收的对象的 finalize()
第二次标记(对象的自救)
通过判断是否重写了 finalize() / 是否已经被调用过 finalize(),来决定是否还要调用该方法-> 只有未被调用过 finalize ...
📝Redis-原理篇-4:内存回收
1 Key Expiration (Key 过期)DB 结构
Redis 如何知道一个 key 过期?-> 一个库中有两个 Dict 分别记录 key-value 对、key-ttl 对
是不是 TTL 到期就立刻删除了?-> 不是,具体取决于过期策略
数据过期策略
惰性删除:
在访问一个 key 时检查它的存活时间,如果过期 -> 执行删除
定期删除:
通过一个定时任务周期性地抽样部分 key,删除其中过期的 key-> 能确保最终所有 Dict 中的数据都被抽到-> 不会出现过期 key 一直没清理的情况
定期删除分为两种执行模式:
SLOW 模式:低频、高时长的清理
Redis 会设置一个定时任务 serverCron(),按照 server.hz 的频率来执行过期 key 清理,模式为SLOW
SLOW 模式规则:
执行频率受 server.hz 影响,默认为 10(即每秒执行 10 次,每个执行周期 100 ms)
执行清理的耗时不超过一次执行周期的 25%
逐个遍历 db,逐个遍历 db 中的 bucket,抽取 20 个 key ...
📝Redis-原理篇-3:通信协议
Redis 采用 C/S 架构通信一般分为两部(不包括 pipeline 和 pubsub):
client 向 server 发送一条命令
server 解析并执行命令,返回响应结果给 client
Redis 采用 RESP(Redis Serialization Protocol) 协议
📝《MySQL 技术内幕:InnoDB 存储引擎》笔记
1 MySQL 体系结构和存储引擎1.1 定义数据库和实例
数据库和数据库实例是两个不同的概念数据库:是文件的集合实例:是程序,是真正用于操作数据库文件的
关于配置文件读取MySQL 以读取到的最后一个配置文件中的参数为准
1.2 MySQL 体系结构
区别于其他数据库最重要的一个特点:插件式存储引擎
存储引擎是基于表的,而不是数据库。
1.3 MySQL 存储引擎1.3.1 InnoDB 存储引擎1.3.2 MyISAM 存储引擎
不支持事务、表锁,支持全文索引,主要面向一些 OLAP 数据库应用
它的缓冲池只缓存索引文件,而不缓冲数据文件,这和大多数数据库非常不同
2 InnoDB 存储引擎2.1 InnoDB 存储引擎概述特点:行锁设计、支持 MVCC、支持外键、提供一致性非锁定读
2.3 InnoDB 体系架构InnoDB 存储引擎有多个内存块,组成一个大的内存池,负责:
维护所有线程/线程需要访问的多个内部数据结构
缓存磁盘上的数据,方便快速读取,同时在对磁盘文件的数据修改之前在这里缓存
redo log 缓冲
…
后台线程的主要作用是负责刷新 ...
📝Redis-原理篇-2:网络模型
五种 I/O 模型用户空间和内核空间在操作系统中 CPU 有两种运行状态:用户态(User Mode)、内核态(Kernel Mode)。当进程运行在内核空间时称为内核态,当进程运行在用户空间时称为用户态。应用程序运行在用户态,一些操作系统的程序运行在内核态。用户态下只能执行一些非特权指令,如果应用程序需要执行一些特权指令(如磁盘的 IO、网络通信等),必须通过中断机制从用户态切换到内核态,并将处理权交给操作系统,由操作系统来完成特权指令。为此,操作系统向用户空间提供了一组标准接口,即系统调用(System Call),这是用户程序访问内核服务的唯一通道。
Linux 的五种 IO 模型
BIO = Biocking IO (阻塞 IO)
NIO = Nonblocking (非阻塞 IO)
IO Multiplexing (IO 多路复用)
Signal Driven IO (信号驱动 IO)
AIO = Asynchronous IO (异步 IO)
应用程序想要读取数据,是无法直接读取磁盘数据的,需要先在内核中等待内核操作硬件拿到数据。
...
(待更新)📝JVM-1:JVM 简介 & 运行时数据区
JVM 简介Java 程序的运行环境(Java 二进制字节码的运行环境)
好处:
一次编写,到处运行
自动内存管理,垃圾回收功能
数组下标越界检查
多态的基石
运行时数据区PC Register(程序计数器)PC Register,Program Counter Register,程序计数器
Java 中代码的执行流程Java 代码首先被编译成字节码(JVM 指令),然后这些字节码交由 JVM 执行引擎的解释器进行解释。解释器将字节码转换为机器码,最终交由 CPU 执行。
程序计数器的作用负责记住下一条 JVM 指令的执行地址,从而保证程序执行的有序性
负责记录下一条 JVM 指令的执行地址,从而确保程序执行的有序性。
在多线程环境中,每个线程都有自己的程序计数器,记录该线程上次执行结束的位置。当线程被调度时,程序计数器指示从上次停止的位置继续执行。
程序计数器的实现程序计数器是通过“寄存器”实现的。寄存器是 CPU 中访问速度最快的存储单元,Java 将寄存器作为程序计数器来存储和读取指令的内存地址,因为指令的读取频率很高。
程序计数器的特点
线程私有
唯一一个不会存在内存 ...
📝Redis-高级篇-1-4:分布式缓存-分片集群
1 概述主从和哨兵可以解决 Redis 高可用、高并发读的问题。但是仍有两个问题没有解决:
海量数据存储
高并发写
使用分片集群可以解决上述问题,分片集群的特征是:
集群中有多个 master,每个 master 保存不同数据。
每个 master 都可以有多个 slave 节点
master 之间通过 ping 监测彼此健康状态
客户端请求可以访问集群任意节点,最终都会被转发到正确节点
2 hash slot (散列插槽)Redis 会把每一个 master 节点映射到 0~16383 共 16384 个插槽上
数据 key 不是与节点绑定,而是与插槽绑定。Redis 会根据 key 的有效部分计算插槽值,分两种情况:
key 中包含 “{}”,且 “{}” 中至少包含 1 个字符,”{}” 中的部分是有效部分
key 中不包含 “{}”,整个 key 都是有效部分
例如:key 是 num,那么就根据 num 计算,如果是 {hello}num,则根据 hello 计算。计算方式是利用 CRC16 算法得到一个 hash 值,然后对 16384 取余,得到的结果就是 s ...
📝Redis-高级篇-1-3:分布式缓存-哨兵机制
1 哨兵的作用和原理哨兵,Sentinel,作用是实现主从集群的自动故障恢复。
哨兵的结构和作用
监控:Sentinel 会不断检查 master 和 slave 是否按预期工作
自动故障恢复:如果 master 故障,Sentinel 会将一个 slave 提升为 master。当故障实例恢复后也以新的 master 为主
通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给 Redis 的客户端
服务状态监控Sentinel 基于心跳机制监测服务状态,每隔 1 秒向集群的每个实例发送 ping 命令:
主观下线:如果 sentinel 节点发现某实例未在规定时间响应,则认为该实例主管下线。
客观下线:如果超过指定数量(quorum)的 sentinel 都认为该实例主观下线,则该实例客观下线,quorum 值最好超过 Sentinel 实例数量的一半。
选举新的 master一旦发现 master 故障,sentinel 需要在 slave 中选择一个作为新的 master,选择依据是这样的:
首先判断 slave 节点与 ...