# Mysql
## Mysql的索引类型有那些
1. ### `从数据结构`
- **`B+树索引`**
- 适用于**范围查询** Between 和 **精确查询** in
- 支持有序数字的查 排 聚合操作 是**Mysql的默认索引**
- **`哈希索引`**
- 基于哈希表的结构
- **不适用于范围查询 **但是适用于精确查询 & **查的非常快**
- **`倒排索引`** ->全文索引
- **把“文档→单词”的形式变为“单词→文档”的形式**
- **`R-树索引`** ->多维空间树
- 为空间(多维)查询准备的
- 计算地理位置 区域查询
2. **```从InnoDB B+树来看```**
- **`聚簇索引`**
- 也叫**主键索引**
- 名字由来-> 索引的叶子节点存储完整的数据行数据 -翻译->索引的结构和数据的物理存储是**深度绑定**的 —-—> 索引的叶子节点本身就是数据行的物理存储位置。
- 拿到了主键也就拿到了 这个数据所有
- 索引的叶子节点存储的是数据行 可以直接访问所有数据
- 适合 范围查 &排序
- **`非~ `** -->>二级索引
- 仅仅和主键绑定 拿到了也只是拿到了主键和非~的 数值 想要找到其他的字段 你还要根据主键找(这一步也叫回表)
3. **`索引的性质`**
- **`普通索引`**
- **非主键**&**非唯一**索引
- ```mysql
ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
```
- **`联合索引`**
- 多个列合在一起的普通索引
- 为了提高多个查询条件的性能
- 要按照顺序
- ```mysql
ALTER TABLE `table_name` ADD INDEX index_name ( `column1` ,`column2` )
```
- **`主键索引`**
- 每一行数据都有唯一的主键
- 每个表只能有一个
- 不能为NULL
- ```MYSQL
ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
```
- **`唯一索引`**
- 索引中的值是唯一的 防止数据重复
- 可以为NULL
- ```mysql
ALTER TABLE `table_name` ADD UNIQUE (`column`)
```
- **`全文索引`** 写过了
- ```mysql
ALTER TABLE `table_name` ADD FULLTEXT index_name (`column`)
```
- **`空间索引`** 通常是R-树结构
- ```mysql
CREATE SPATIAL INDEX idx_location ON places(location);
```
### 补充
#### 关于倒排索引
我们一般搜索文章 都是通过 文章名字 ->找里面的单词 这种结构,但是如果我们想要通过文章的内容(一篇文章里面有苹果这个词)我们如何找到对应的文章???? 很简单就是**倒排索引** 这个索引将原先的 (各位可以想象成Map的KV数值)原先的存储方式是K(文章)V(苹果) 但是现在不一样了 我们改变了KV的内容 将一个K(苹果)V(包含苹果的文章1,2,3....)
冷知识 (AI询问 正确与否代考就)未建索引时,搜索 “苹果” 需要逐篇扫描所有文章(全表扫描),时间复杂度是 **O (N)**(N 为文档总数),数据量大时会非常慢;而倒排索引通过 “关键词→文档列表” 的直接映射,查询时只需定位到关键词对应的倒排列表,时间复杂度接近 ***O (1)(定位关键词)+ O (M)(M 为匹配的文档数)***,M 通常远小于 N,因此速度会极大提升。
#### 为什么使用B树
B-树是一种**多路自平衡的搜索树** 它类似普通的平衡二叉树,不同的一点是B-树允许每个节点有更多的子节点。B-Tree 相对于 AVLTree 缩减了节点个数,使每次磁盘 I/O 取到内存的数据都发挥了作用,从而提高了查询效率。I/O渐进复杂度为O(h)
树非常矮,通常为 3~4 层,能存放千万到上亿的排序数据。树矮意味着访问效率高,从千万或上亿数据里查询一条数据,只用 3、4 次 I/O。
## Mysql的 存储引擎有哪些??
1. InnoDB
- 支持事务/行级锁&外键
- 提供包并发性能 适用于高附载的OLTP(联机事务处理)应用
- 数据以聚集索引存储 提高检索效率
2. MyISAM (早期的存储引擎 已逐渐被 InnoDB )
- 不支持事物和外键 使用表级锁
- 适合读取多 更新少 ->>数据仓库
- 较高的读取性能 和 快的 表级锁定
3. MEMORY
- 存在内存中 快 但是重启丢失
- 适用于临时数据|快速缓存
4. NDB
- 高可用性和数据分布 适合大规模分布式应用
- 行级锁&自动分区
5. ARCHIVE
- 适用于存储大量历史数据 支持高效的插入&压缩
- 不支持索引 适合日志数据的存储
### 补充
#### 什么是聚集索引存储 为什么就提高了检索效率
就是聚簇索引
## 为什么用Mysql作为B+树的索引
1. 高效查找
- B+树是一个AVL树 每个叶子节点到根节点的长度相同 插入、查找、删除的时间复杂度为**O(logN)**
2. 树的高度增长不快,使得磁盘的I/O次数减少
- B+树是一个多叉树,*** 非叶子节点仅仅保存主键或索引值和页面指针***,使得每一页都能容纳更多的记录 因此内存放的索引就多 更容易命中
3. 范围查询能力强
- B+树适合范围查询,因为叶子节点通过**链表链接**
### 扩展
1. B树的每个节点都存储的是完整的数据 而B+树则是存储的key&指针 完整的数据存储在叶子节点上,因此B+树可以存放更多的索引页 减少磁盘查询次数。
2. B+树叶子组成了链表 而B树智能化每一层查找
3. B+树查找的平均时间更加稳定 B树因为非叶子节点就保存了数据因此 返回的快慢取决于节点到根的距离
4. 为什么不用二叉树?
- 可能会成为斜树 --> 一个链表 索引失效
5. 为什么不用红黑树?
- 红黑树毕竟还是二叉树,一个结点点最多拥有两个直接子结点,当我们插入大量数据的时候,会导致的树的高度过高,I/O渐进复杂度为O(h)。所以还是没有有效的减少查找过程中磁盘I/O的存取次数。
## 索引的最左前缀匹配原则是什么?
1. 索引的底层是一颗B+树,那么联合索引的底层也就是一颗B+树,只不过联合索引的B+树节点中存储的是键值。由于构建一棵B+树只能根据一个值来确定索引关系,所以数据库依赖联合索引最左的字段来构建。
2. 只有联合索引才能谈最左前缀匹配
3. 查询的顺序也是 按照从左到右的顺序查询 所以
- **原则**:联合索引必须从最左列开始匹配,且列之间连续。
- **范围条件的影响**:范围条件会阻断后续列的索引使用。
但是 如果
```mysql
ALTER TABLE staffs ADD INDEX index_staffs_nameAgePos(name,age,pos);
explain select *from staffs where name='z3'and age=22 and pos='manager';
explain select *from staffs where pos='manager' and name='z3'and age=22;
explain select *from staffs where age=22 and pos='manager' and name='z3';
```
这样**三个互换了顺序** ***也会进行联合索引*** 因为最主要是因为MySQL中有查询优化器***explain,***所以sql语句中字段的顺序不需要和联合索引定义的字段顺序相同,查询优化器会判断纠正这条SQL语句以什么样的顺序执行效率高,最后才能生成真正的执行计划
**部分索引顺序**
如果缺胳膊少腿(有最左的 但是中间的可能少了),也是按照正常的索引
即使跳过了中间的索引,也是可以使用到索引去查询
但是如果只查最后的索引(没有大哥了)直接整个表的查询了
**模糊索引**
类似模糊索引就会使用到like的语句 如果复合最左前缀的话,会使用到range或者是index的类型进行索引
**范围索引**
遇到范围索引就会停止
## 三层B+树结构
B+树的默认数据页的大小为**16KB**
- 每个节点页的大小为16KB
- 假设每个数据结构的主键和数据大小为1KB
- 每个内部节点 存储的是指向子节点的指针和索引键
- 第一层 根节点 指向1170个中间节点
- 第二层 假设每个指针是6字节 +8字节的索引键(bigint)=14字节 (一个节点的大小) 那么每个中间节点可以指向1170个叶子节点 **16KB/14Byte=1170**个
- 每个叶子节点可以存储16条数据结构
## 什么是回表
上文已经提到 由于在非聚簇索引中 索引存储的值 是索引字段的值和对应的主键的值 而并没有数据 所以当拿到这个索引的时候需要返回表中 重新去取叶子节点的数据 这就是**回表**
``回表会带来随机IO`` 因为这个索引可能是无序的 所以查起来不好查 只能随机查询
## 索引失效
- 联合索引却不符合**最左前缀**
- 索引使用了计算
- 索引上函数
- like的随意使用
- or的随意使用
- 只有name有索引但是却使用了 select * from user where name="xxx" or id="xxx"
- 随意字段的使用
- 也就是要查询的字段和匹配的字段不是一个类型 那么myslq就会做转换 **隐式字符编码转换**
- 参数不同
- 表中两个不同的字段进行比较
- select * form user where id>name
- 使用了ORDER BY
- order by 后面如果不是**主键** 或者 不是**联合索引 **那么就不会走索引
神奇的地方就是 mysql查询都是按照索引查询的 但是问题就是这个索引是否生效 (有意思)
## 什么时候要建立索引
- 索引不是越多越好 毕竟建立索引需要空间 而且每次修改的时候还要额外去维护
- 有大量重复的地方 不建议上索引
- 长字段不要上索引 `but个人建议` 也是一个小tip 如果你做的是文章网站 那么好像 大概 可以给文章上倒排索引
- 当修改频率》》》查询 还是那句话**每次修改的时候还要额外去维护**
- 通过建立联合索引 减少单个索引的建立 毕竟管理一个表 总比管理一批表合理(笑)
## 索引越多越好?
空间角度
你每次建立一个索引 就会建立一个索引表 一般来说一个索引表的大小就是16KB
时间角度
每次你写入一个数据 是不是需要在表中增加 这个插入要不要时间 ?? 你加完了 树的结构要改变 这个 为了维持B+树所作出的变形需不需要时间??
而且 你mysql使用索引的时候是不是需要通过评估器来评估哪个索引更好 是不是需要时间
## 如何使用explain语句进行分析

- `id` 就是查询的执行顺序的标识符 数字越大优先级越高
- *select_type* 查询的类型 有simple primary subquery
- table 查询的数据表
- type 访问的类型 比如ALL index range 等等 一般来说 性能从好到垃圾是**const > eq_ref>ref>range>index>ALL**
- possible_keys 可能用到的索引
*key* `实际用到的索引`
- key_len 索引长度
- ref 索引的哪一列被使用
- *rows* 估计要扫描的行数 数值越小越好
- filtered 希纳是查询条件过滤掉行的百分比
- *Extra* 额外信息 Using + index --》 使用覆盖索引 +where 条件过滤 +temporary 临时表 + filesort 额外的排序步骤
## 如何进行mysql的优化
1)合理设计索引,利用联合索引进行覆盖索引的优化,避免回表的发生,减少一次查询和随机 I/O
2)避免 SELECT *,只查询必要的字段
3)避免在 SQL 中进行函数计算等操作,使得无法命中索引
4)避免使用 % LIKE,导致全表扫描
5)注意联合索引需满足最左匹配原则
6)不要对无索引字段进行排序操作
7)连表查询需要注意不同字段的字符集是否一致,否则也会导致全表扫描
除此之外,还可以利用缓存来优化,一些变化少或者访问频繁的数据设置到缓存中,减轻数据库的压力,提升查询的效率。
还可以通过业务来优化,例如少展示一些不必要的字段,减少多表查询的情况,将列表查询替换成分页分批查询等等。
补充的话就是三点: 命中索引 减少IO 减少回表
## B+树查询的全过程
先从根节点找起 然后根据数据键与节点中的**索引值(二分)**然后找分支
叶子节点存储的数据行记录 但是一页有**16KB**的大小 存储的数据行不止一行
叶子节点中的**数据行** 利用页目录结构 利用二分 找到对应的组
定位后 利用链表找到对应的行
###
## mysql中的count小知识
count(1)==count(0) 统计所有行数 **(包括null)**
count(字段名)--》返回字段的行数 **不包括null**
### char&varchar
char **定长** 当不满的时候会用空格填满
varchar **变长** 需要记录额外长度 1-2字节 支持的最大长度是65535 如果值为NULL 需要一个额外的1bit进行标记
双路排序 select字段多 放在StringBuffer里面 进行排序后 返回给客户端
单路排序 select字段少 放在StringBuffer里面
## Mysql是如何实现事务的
- 锁 主要服务于**隔离性**
- 利用`锁的机制` 使用数据并发修改的控制 来满足事物的`隔离性`
- Redo Lock 主要服务于**持久性**,同时辅助原子性
- 重做日志 主要就是`记录数据库的所有操作` 当mysql崩溃的时候 就重放一遍redolock 就可以恢复
- Undo Lock回滚日志 主要服务于**原子性**,同时辅助隔离性(MVCC)
- `保存数据的历史版本` 用于事物的回滚 使得事务执行失败以后能够回到之前的样子 实现原子性
- MVCC 多版本并发控制 主要服务于**隔离性**
- 满足了非锁定读的需求 提高并发度 实现`读已提交`和`可重复读` 两中隔离级别 实现了事务的隔离性
`MySQL 事务的实现是**锁、Redo Log、Undo Log、MVCC 等机制协同作用的结果**:`
- ### 什么是事务?
- 事务就是用户定义的一系列执行SQL语句的操作, 这些`操作要么完全地执行,要么完全地都不执行`, 它是一个不可分割的工作执行单元。
**事物的四大特性**
- 原子性(Atomicity)
- 个事务中的所有操作要么全部提交成功,要么全部失败回滚
- 一致性(Consistency)
- 数据库总是从一个一致性的状态转换到另一个一致性的状态
- 隔离性(Isolation)
- 通常来说,一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的。
- 持久性(Durability)
- 一旦事务提交,则其所做的修改会永久保存到数据库。
## 什么是MVCC 多版本并发控制
**是一种并发控制机制** 允许多个事务`同时读取`和`写入数据` 无需等待
### 这么做的?
1. 数据库为每一个事物创建一个数据快照。每当数据被修改的时候,mysql不会立刻覆盖原有的数据,而不是生成新版本的记录。每个记录都保留了对应的版本号和时间戳
2. 多个版本串联起来形成了一条版本链 这样可以不同时刻启动的事务可以`无锁`的获得不同版本的数据。
3. 写操作可以继续 无非就是创建新的数据版本
### 其实并不是存了很多个版本的数据
而是通过undolock的`保存数据的历史版本`来保留所有的版本 通过undolock来回溯到上面的版本
只是借助 undolog 记录每次写操作的反向操作
## Mysql的日志类型
### binlog 逻辑日志 二进制日志文件
- 用于记录mysql中的所有的DDL&DML
- 在事务提交后产生的 所以可以用来回复数据库
- 记录的是操作
- 因此他可以跨平台使用
### redolog 物理日志
- 用于恢复数据 保持数据的`一致性`和`持久性`
- 而且只是 保持数据的`一致性`和`持久性`
- 记录的是数据页的修改
### undolog 回滚操作
- 作用
- 事务回滚
- MVCC支持 :通过版本链实现多版本的并发控制
## Mysql的事务级别
### 读未提交
最低级的
可以读到其他事务为提交的数据 可能产生`脏读`
### 读已提交
可以读取其他事务已经提交的数据
但是可能会产生 `不可重读 `
### 可重复读
确保数据在多个查询下的返回是一样的
但是可能会产生`幻读`
mysql的默认的隔离级别
### 串行化
并发执行的SQL事务的操作 其效果与这些SQL事务按某种顺序串行执行的效果相同
串行执行:sql事务在开启下一个事务之前要完成全部的操作
## Mysql中有哪些锁
1. **行级锁**
- 仅仅对特定的`行`加锁,允许其他事务**并发`访问`不同的行**,适用于高并发场景
2. **表级锁**
- 对整个表枷锁 其他事物**不能**对表进行任何**读写操作** 适用于保证完整性的小型表
3. 意向锁
- 表锁 表示某个事物对某行数据加锁的意图,分为意向共享锁和意向排他锁,主要用户行级锁的与表级锁的结合
4. **共享锁**
- 允许事物**并发的读取同一资源** **不能修改** 智能释放锁后 才能获得锁
5. **排他锁**
- 只允许**一个事务**对资源进行**读写** 其他只有拿到锁才可以
6. 元数据锁
- 用于保护数据对象的元数据 防止DDL操作时其他事务对这些对象进行修改
7. **间隙锁**
- 针对索引中的两个记录之间的间隙枷锁 防止其他事务在间隙中插入数据---》防止幻读
8. **临键锁**
- 行级锁+间隙锁 锁定具体的行和前面的间隙 确保在一个范围内不会出现幻读
9. 插入意向锁
- 等待间隙的锁 用于指示事务在某个间隙中插入记录 允许其他事务进行共享锁 但在插入时会组织其他锁
10. 自增锁
- 插入自增列时枷锁 保证自增的唯一性
##
## Mysql事务的二阶段提交的是什么
为了确保**redolock binlog**之间的一致性 使用的一种机制。
Mysql通过二阶段提交来保证在**crash recovery** 崩溃恢复 时 不会出现**数据丢失**和**数据不一致**的情况
### 二阶段的两个阶段
- `准备阶段` 在事务提交时,Mysql的InnoDB引擎会先写入**redolog 将其状态标记为prepare**,表示事务已经准备提交但是还未真正完成 此时的redolog是预提交状态 还未标记为完成提交
- `提交阶段` redolog标记成prepare的时候 mysql server 会写入binlog 写入成功后 mysql会通知innodb 将redolog状态改为commit 完成整个事务的提交过程
## Mysql如果发生死锁会怎么办
#### 自动检索与回滚
MySQL自带死锁检测机制 ,当检测到死锁时,数据库会自动回滚到其中一个事务,以解除死锁。通常会回滚到事务持有的资源最少的那时候。
### 可以手动的kill发生死锁的语句
可以通过命令 来手动kill它
```mysql
SHOW ENGINE INNODB STATUS;//展示执行的事务和事务相关的锁
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;//获取事务id
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;//获取线程id
KILL ;//手动中止事务
```
## Mysql是如何解决深度分页问题
什么是深度分页? 数据量很大的时候,按照分页访问后面的数据。
比如
```mysql
`SELECT * FROM mianshiya WHERE name = 'yupi' ORDER BY id LIMIT 99999990, 10;`
```
这样的一条查询语句,
可以优化成:
```mysql
sql SELECT * FROM mianshiya
WHERE name = 'yupi'
AND id >= (
SELECT id FROM mianshiya
WHERE name = 'yupi'
ORDER BY id
LIMIT 99999990, 1
)
ORDER BY id LIMIT 10;
```
name 有索引的情况下,这样的查询直接扫描 name 的二级索引,二级索引的数据量少,且在子查询中能直接得到 id 不需要回表。将子查询得到的 id 再去主键索引查询,速度很快,数据量也小。
**如果直接扫描主键索引的话,数据量就比较大**,因为**主键索引包含全部的数据**。
当然上面的 SQL 改成 Join 也行,本质上是一样的。
比如
```mysql
select * from mianshiya
inner join
(select id from mianshiya where name = 'yupi' order by id limit 99999990,10)
as mianshiya1 on mianshiya.id = mianshiya1.id
```
其实本质上就是先查找在排序 而不是直接写成一句话 导致一边查找一边排序
方法二 记录id
每次分页返回当前最大的id 然后下次查询的时候 带上这个id 就可以利用id》maxid 过滤了
这种方式只适合 连续查询的时候 如果跳页就失效了
## 什么是mysql的主从机制?是如何实现的?
主从机制 就是mysql的一种数据复制技术 将主数据库上面的数据同步到一个或者多个从服务器中
主要是用binlog(用于记录mysql中的所有的DDL&DML ) 把主数据库的binlog放到其他的数据库中 让其他的数据库重放就行了
### 具体流程
1. 线程创立 --> 服务器开始主从机制后 会创建i/o sql线程
2. 连接建立 -->服务器io线程与主服务器建立连接,主服务器的binlog dump线程与之交互
3. 同步位置告知 --> 服务器的io线程 告诉主服务器的dump线程从何处开始接受binlog
4. 主服务器操作 --> 主服务器更新时 将更改的记录保存到binlog中
5. binlog传输 --> 主服务器的dump线程检测到binlog变化 从指定的位置读取 由从服务器io线程拉取 采用
6. 中继日志存储 --> 从服务器的io线程中 将接受的内存保存到relaylog中
7. 数据写入 --> 从服务器的sql线程读取relaylog内容 解析成具体操作后写入自身数据表
### 主从复制的类型
- 异步复制 主库不需要从库中相应
- 同步复制 主库同步等待所有的库确认收到数据
- 半同步复制 主库等待至少一个库收到数据
## 主从同步延迟怎么处理
首先确定一点 这个延迟啊 无法避免只能尽力减少
- 二次查询:如果库查不到数据 那么就去主库查一遍 但是就把压力给到主库了 有点违法主从原则了
- 强制将写之后立马读的操作转移到主库上: 将代码写死了 比如一些写入之后立马查询的操作,绑定在一起,写死都走主库
- 关键业务读写都走主库 :
- 使用缓存: 主库写入后同步到缓存中 这样查询时就可以先查询缓存,避免了延迟的问题
- 提升从库配置:这没什么可解释的 金钱的力量
# Redis
## Redis常见的数据结构
- String 最大长度是512MB
- 缓存:存session
- 计数器:统计访问量
- Hash 底层是哈希表
- 键值对,存储对象的属性。适合小规模数据
- 商品详情
- List 双向链表
- 消息队列
- 历史消息
- Set 无序且不重复 用哈希表实现
- 标签系统
- 唯一用户集合
- Sorted Set
- 有序集合 但是每个集合都有一个分数(score)用于排序 底层是跳表实现
## 高级的
- BItmap
- 用位为单位存储的数据的高效的方式
- 用户在线
- HyperLongLong
- 主要用于估算基数 适合估算网站的独立的用户数量
- GEO
- 存储地理信息的位置
- Stream
- 日志数据结构 支持高效的信息生产和消费模式
- 具有持久性和序列性化特性
- 消息队列
## Redis为什么这么快
数据存储在内存
优秀的线程模型&IO模型
- redis采用了IO多路复用技术
- redis采用的单线程来执行命令 无需线程的切换 避免了 上下文切换所带来的开销
高效的数据结构
## Redis为毛使用单线程?6.0以后又引入多线程
单线程的原因是因为
1. Redis的操作是基于内存的 其大多数操作的性能瓶颈 不是CPU导致的
2. 使用单线程模式 减少线程上下文切换所带来的性能开销
3. redis在单线程的情况下,使用IO多路分用模型 就可以提高redis的IO利用率
6.0使用多线程的原因是因为
随着数据规模的增多 请求量增多 Redis的瓶颈主要存在于网络IO,引入多线处理,可以提高网络IO处理速度
## Redis的跳表的底层原理
多路链表 底层保留所有的元素 每一层链表都是下一层的子集
## Redis中的Hash
是一种键值对集合
可以将多个字段和值存储在一个键中
以便于管理一些数据
适合存一些小数据 支持快速的增删改查 非常适合存储对象的属性
## Redis Zset的实现原理
是一种`跳表`+`哈希表`
跳表 -->存储数据结构的排序和快速查找
哈希表 -->成员与分数的映射,提供快速查找
当Zset的元素过于少的时候 将使用``zip list`` 来节省内存
- 元素个数<128
- 元素成员名和分值长度<64字节
## 如何保证redis和Mysql的数据一致性
1. 先更新缓存 再跟新数据库
2. 先更新数据库 再更新缓存
3. 删缓存 更新数据库 等后续查询数据库把数据回种到缓存
以上都不建议
1. 先更新数据库 再删缓存 等后续查询数据库把数据回种到缓存
2. **缓存双删策略** 更新数据库之前先删一次缓存。更新换数据库以后 再延迟删缓存
3. 使用**binlgo异步更新**缓存,监听到数据库的binlog的变化,通过异步的方式更新Redis缓存
## 击穿 穿透 雪崩
### 击穿
热点数据失效 导致大量的请求直接访问数据库
**解决方案**:
1. 热点数据永不过时 -->比如双十一的时候 就可以把一些热点的关键词 给设置一个不可能过期的时间
2. 热点数据预热 --> 预加载
3. 动态缓存 --> 使用互斥锁,确保一个时间只能由一个请求去查询数据库查询并且更新缓存
**穿透**:查询一个不存在的数据 然后每次都查询
1. 分布式锁
2. 即使是一个空的key 也存入redis中
**雪崩**:多个缓存数据在同一时间内失效 导致大量的请求同时访问数据库
1. 采用随机过期时间策略
2. 双缓存机制
## String的底层逻辑
SDS结构,并结合int,embstr,raw等不同的编码实现的
根据长度不同编码的也不同 以44字节为节点 分embstr和raw
## 分布式锁
1. set en nx命令+Lua脚本
- 加锁&解锁
2. 集群模式下 使用redlock
## 实现分布式锁的问题
1. 业务未到期 ,锁失效
2. 单点故障
3. 主从不重复的问题
4. 网络分区的问题
5. 时钟漂移的问题
6. 锁的可重复入的问题
7. 误解放锁的问题
## Redis持久化机制
- RDB快照
通过某一时刻的数据快照来实现持久化,可以在特定时间间隔内保存数据的快照
适合灾难恢复和备份 能生成紧凑的二进制文件 但是可能在崩溃后丢失最后一次快照
- AOF日志
AOF通过将操作写道日志中 来实现持久化,支持将所有的写操作记下来以回复
文件更加准确 但是文件比较大 重写时会使用更多的资源
## 主从复制的实现原理
复制流程
- 开始同步
- 从节点通过主节点发送PSYNC命令发起同步
- 全量复制
- 如果第一次链接或者之前的连接失效,从节点请求全量复制,主节点会将当前的数据快照 发送给从节点
- 增量复制
- 全量复制完成后,主从之间回保持一个长连接,然后主节点会通过这个连接 将后续的读写操作传递给从节点
主从架构 就是 主操作写入 从节点来读取
## 数据过期后的删除策略是什么
- 定期删除
- 定期(1000ms默认)会随机检查一定数量的键,如果发现过期键 就删除
- 主要应用在后台持续清理过期数据
- 惰性删除
- 在每次访问的键的时候,redis检查键是否过期,过期就删掉。这种策略保证了在使用过程中 只删除不要的数据,不访问就不删除。
## 如何解决热点key的问题
- 热点key的拆分 比如在key的前面加一些前缀 然后放到不同的实例上
- 多级缓存 (CDN 本地缓存)
- 读写分离 通过主从复制 将请求分到其他的节点
- 降级和限流 在热点key过高的时候 应该采取限流的方法 减少对于redis的请求 必要时要使用降级的数据或者空值
## 集群的实现原理是什么
采用hash slot 机制来分配数据 将整个空间分为16384个槽 每个redis负责一定范围的哈希槽 数据的key经过哈希计算后 分配到对应的节点
## Big key问题
Redis 中的 "big Key" 是指一个内存空间占用比较大的键(Key),它的危害如下:
- 内存分布不均。在集群模式下,不同 slot 分配到不同实例中,如果大 key 都映射到一个实例,则分布不均,查询效率也会受到影响。
- 由于 Redis 单线程执行命令,操作大 Key 时耗时较长,从而导致 Redis 出现其它命令**阻塞**的问题。
- 大 Key 对资源的占用巨大,在进行网络 I/O 传输时,会导致获取过程中产生的**网络流量较大**,进而产生网络传输时间延长甚至网络传输阻塞的现象,例如一个 key 2MB,请求 1000 次就会产生 2000 MB 流量。
- 客户端超时。因为操作大 Key **时耗时较长**,可能导致客户端等待超时。
### 如何解决
开发方面:
- 对存储的数据进行压缩
- 将大的key分成一些小的对象
- 使用适合的数据结构
业务层面:
- 可以根据实际情况 减少存储的内容
- 优化业务逻辑
数据分布方面:
- 采用redis集群方式 进行redis的部署 然后将大的key拆分到不同的服务器上面
# 设计模式
## 单例模式哪几种实现方式 ?如何保证线程安全
- 饿汉式 --> 类加载的时候就加载
- ```java
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
```
- 懒汉式 --> 用到才创建
- ```java
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
- 双重检查锁定 --> 懒汉式+ 只在第一次检查实例为空的时候加锁
- ```java
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
```
- 内部静态类 --> 利用类加载机制 实现懒加载和线程安全
- ```java
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
```
- 枚举单例 --> 通过枚举实现单例 简单防止反射和序列化攻击
```java
public enum Singleton {
INSTANCE;
public void bizMethod() {
// 一些业务逻辑方法
}
}
//使用
Singleton singleton = Singleton.INSTANCE;
singleton.bizMethod();
```
## 策略模式
一种行为设计模式,将每种算法封装起来,让他们可以相互替换,让算法独立于使用的客户端而变化
### 特点
- 算法封装
- 动态替换 在运行时选择和替换算法
- 遵循开闭原则
### 组成
- 策略接口:定义算法的通用接口
- 具体策略:实现算法的具体实现
- 上下文类:持有策略接口的使用,调用具体策略的方法
## 模板方式
抽象类定义了一个算法骨架,具体的步骤由子类提供
- 抽象类 定义模板
- 具体类 实现抽象方法
- 模板方法 调用一系列步骤方法
```java
// 抽象类
abstract class DataProcessor {
// 模板方法
public final void process() {
readData();
processData();
writeData();
}
protected abstract void readData(); // 读取数据
protected abstract void processData(); // 处理数据
protected void writeData() { // 写入数据
System.out.println("Writing data to output.");
}
}
// 具体实现类A
class CSVDataProcessor extends DataProcessor {
@Override
protected void readData() {
System.out.println("Reading data from CSV file.");
}
@Override
protected void processData() {
System.out.println("Processing CSV data.");
}
}
// 具体实现类B
class JSONDataProcessor extends DataProcessor {
@Override
protected void readData() {
System.out.println("Reading data from JSON file.");
}
@Override
protected void processData() {
System.out.println("Processing JSON data.");
}
}
// 客户端
public class Main {
public static void main(String[] args) {
DataProcessor csvProcessor = new CSVDataProcessor();
csvProcessor.process();
DataProcessor jsonProcessor = new JSONDataProcessor();
jsonProcessor.process();
}
}
```
## 常见的设计模式
1. 单例模式
- 保证系统中对象只有一个实例
- 连接池 全局配置
2. 简单工厂
- 获取不同对象的时候可以使用
- 将对象的创建逻辑抽离复用
3. 策略
- 封装一组算法让他们之间能够相互替代 能有效避免if sles
- 选择支付策略
4. 模板
- 提炼核心流程封装成一个模板方法
## 工厂模式&抽象工厂模式
工厂模式关注的是 **创建单一类型的对象**
定义一个抽象方法 由子类实现具体对象的实例化
抽象工厂模式 关注的是 **创建一族相关对象 **
提供一个接口来创建一组相关的或者相互依赖的对象 无需指定他们的具体类
# 计网
## 常见的Http状态码
- 1XX 消息响应
- 2XX 成功
- 3XX 重定向
- 4XX 客户端错误
- 403权限
- 5XX 服务器错误
## HTTP 请求包含哪些内容 请求头和请求体有哪些内容
组成部分
- 请求行
- GRT POST 之类的
- 请求路径
- HTTP版本
- 请求头
- 各种键值对
- 客户端环境
- 请求内容
- 认证信息
- 空行
- 分割体&头
- 请求体
- 要发送的数据
## GET和POST的区别
GET
- 请求数据
- 参数通过URL拼接 直接暴露于URL中不是很安全
- 有长度限制 2048字节
- 幂等性 重复请求不会改变服务器的状态
POST
- 提交数据
- 参数在请求体中
- 非幂等
## Http1.0
**无状态,无连接** :每次请求都要建立新的TCP
**队头阻塞** :每个请求都按照顺序解决
**不支持持久连接**: 每次请求都要建立新的TCP 服务器消耗大
## Http2.0
**二进制分帧:**将http报文分割成更小的二进制,提高了传输效率,支持**多路分用**
**头部压缩**:减少HTTP头部的大小,降低网络的开销
**服务器推送**: 服务器主动向 客户端发送请求 减少客户端的请求次数
**多路分用**:在一个TCP连接上可以同时发送多个请求,解决了HTTP1.1的对头阻塞问题
## Http3.0
基于QUIC协议:使用UDP协议,相当于TCP可靠性,QUIC通过自身实现可靠传输,减少了RTT
多路分用:在一个QUIC连接上可以同时传输多个请求和相应,并支持流优先级
更快的连接:减少了TLS和三握手的时间
更少的延迟
HTTP&HTTPS
**1.数据传输安全性**
- **HTTP**:数据以明文传输,容易被窃听、篡改。
- **HTTPS**:通过 SSL/TLS 协议对数据进行加密传输,提供数据机密性和完整性保障。
2. **端口号**
- **HTTP**:默认使用端口 80。
- **HTTPS**:默认使用端口 443。
3. **性能**
- **HTTP**:无加密过程,连接建立速度稍快。
- **HTTPS**:基于 HTTP 上又加了 SSL或 TLS协议来实现的加密传输,加解密过程增加了计算开销,握手时间较长,但现代硬件和协议优化已使性能差距减小。
**4. SEO 影响**
- **HTTP**:搜索引擎一般会降低未加密站点的排名。
- **HTTPS**:搜索引擎更倾向于优先展示 HTTPS 网站。
## Tcp和Upd的区别
TCP
- 可靠连接
- 面向连接
- 提供流量控制和拥塞控制
- 保持数据顺序
- 头部较大
- 性能较低 延迟大
- 数据传输模式 -->以字节流传输模式
- 适用于 文件传输 web 邮件
UDP
- 不可靠
- 无连接
- 没有流量控制和拥塞控制
- 不保证数据顺序
- 头部节点较小(8字节)
- 性能高 延迟小
- 数据报传输模式
- 实时通讯 语音 视频 游戏
## 经典TCP三报文
客户端首先发送一个SYN(同步序列编号)消息给服务器,服务器收到后回复一个SYN-ACK(同步序列编号-确认)消息,最后客户端再发送一个ACK(确认)消息确认服务器已经收到SYN-ACK消息,从而完成三次握手,建立起一个可靠的TCP连接。
### 为什么三握手
- 避免历史错误连接的建立 减少双方错误的不必要的资源的消耗
- 帮助双方同步初始化
## TCP是用来决解什么问题
可靠传输:TCP确保数据包在网络传输过程中不丢失,不重复
流量控制:TCP通过滑动窗口机制调节发送方的数据发送速率 ,防止因接收方的处理能力而被数据流所淹没
拥塞控制:TCP通过拥塞避免算法 (慢启动 拥塞避免 快速重传 快速回复) 来防止网络过载 确保网络资源的公平使用和稳定性
连接管理:TCP是面向连接的协议 赛用三次握手 和四次挥手 机制来管理会话,确保通信的可靠性和状态的同步
解决了数据不可靠的IP网络上的问题
## 四挥手
四次挥手的作用就是 用于安全关闭一个已经建立的连接的过程 它确保双方都能完成数据传输并安全的释放连接资源
### 为什么挥手需要四次?
主要是为了确保数据完整性。
TCP 是一个全双工协议,也就是说双方都要关闭,每一方都向对方发送 FIN 和回应 ACK。客户端发起连接断开,代表客户端没数据要发送的,但是服务端可能还有数据没有返回给客户端。就像我对你说我数据发完了,然后你回复好的你收到了。然后你对我说你数据发完了,然后我向你回复我收到了。这样才能保证数据不会丢失。
所以一个 FIN + ACK 代表一方结束数据的传输,因此需要两对 FIN +ACK,加起来就是四次通信。
## 粘包和拆包
粘包:在Tcp传输中发送方的多个数据包在接收方被**合并成一个包接受**,导致多条数据粘在一起,接收方无法正确区分这些消息的边界
拆包:发送方的一个数据包在接受方被拆成了多个包接受,导致一个完整的消息被拆分成多个部分,接收方无法一次性接收到完整的数据
### why
粘包:Tcp是面向字节流的协议 他不关心数据边界
拆包:可能由于网络传输中的MUT(最大传输单元)限制或发送缓冲区大小的限制,一个大包被拆分成了多个小包传输
### 解决:
使用特定长度
添加消息分隔符
使用消息头
## Tcp拥塞控制的步骤
### 
## TCP/IP四层模型
- 应用层
- 通过各种协议提供网络应哟个程序的功能
- 传输层
- 在两个主机之间提供端口到端口的通信服务
- 网络层
- 通过Ip协议提供数据包的路由和转发
- 网络接口层
- 在计算机和网络硬件之间传输数据
## Cookie Session Token之间的区别
### Cookie
存储在用户浏览器端的一个小型数据文件,用于跟踪和保存用户的状态信息
用途: 保持用户登陆状态,跟踪用户行为,存储用户偏好
**存在客户端**
### Session
Session是服务器端保存用户状态的机制 每一个用户会话都会有已给唯一的SessionId
主要用于跟踪用户在服务器上的状态信息 -> 登陆状态和购物车内容
**存储在服务器端**
### Token
**加密字符串,用于身份认证和授权**,可以包含用户信息和权限,用于验证用户身份和授权访问资源
认证后,后端服务会返回Token,**存储在客户端** 后续可无端访问服务端需要带上这个Token
- Cookie-->客户端状态的简单存储和追踪
- Session--> 服务器端的复杂状态管理,需要存储大量会话数据
- Token--> 无状态的认证和授权,特别是在分布式和跨域环境下
## 输入一个网站发生了什么
1. 浏览器解析URL
2. DNS解析成最终的ip
3. 浏览器选择TCP/UDP
4. IP 在TCP数据包的基础上 在封装源地址IP和目标地址IP等信息
5. MAC 通过MAC确保子网内设备两点之间的通信寻址
6. 网卡 网卡将二进制数据转化为电信号
7. 交换机 工作在MAC层,他会将数据包的MAC头找到另一个设备连接在交换机的哪个端口
8. 路由器 转发 三层网络设备 包含IP层
9. 层层验证
10. 服务器处理
11. 浏览器接受并且渲染
# 计算机组成原理
## 线程和进程的区别
进程:资源分配的基本单位 每一个进程都有一个内存空间。
线程:是CPU的调度的基本单位 属于进程 一个进程可以包含多个线程 线程共享进程的测村空间和资源
## 进程之间的通信方式
- 管道 单向通信方式
- 命名管道 与匿名管道类似
- 信息队列 允许进程向另一个进程发送消息 消息顺序存储
- 共享内存
- 信息量
- 信号 异步通信方式
- Socket 在网络上不同主机上的进程通信
- 文件 通过读写文件俩来进行通信
### 调度算法
- 先来先服务
- 短作业有限
- 有限级调度
- 时间片轮转
- 最高相应比优先 计算影响比
- 多级反馈队列调度 结合多个调度策略