42 KiB
Mysql
Mysql的索引类型有那些
-
从数据结构B+树索引- 适用于范围查询 Between 和 精确查询 in
- 支持有序数字的查 排 聚合操作 是Mysql的默认索引
哈希索引- 基于哈希表的结构
- **不适用于范围查询 **但是适用于精确查询 & 查的非常快
倒排索引->全文索引- 把“文档→单词”的形式变为“单词→文档”的形式
R-树索引->多维空间树- 为空间(多维)查询准备的
- 计算地理位置 区域查询
-
从InnoDB B+树来看聚簇索引- 也叫主键索引
- 名字由来-> 索引的叶子节点存储完整的数据行数据 -翻译->索引的结构和数据的物理存储是深度绑定的 —-—> 索引的叶子节点本身就是数据行的物理存储位置。
- 拿到了主键也就拿到了 这个数据所有
- 索引的叶子节点存储的是数据行 可以直接访问所有数据
- 适合 范围查 &排序
非~-->>二级索引- 仅仅和主键绑定 拿到了也只是拿到了主键和非~的 数值 想要找到其他的字段 你还要根据主键找(这一步也叫回表)
-
索引的性质-
普通索引-
非主键&非唯一索引
-
ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
-
-
-
联合索引-
多个列合在一起的普通索引
-
为了提高多个查询条件的性能
-
要按照顺序
-
ALTER TABLE `table_name` ADD INDEX index_name ( `column1` ,`column2` )
-
-
主键索引-
每一行数据都有唯一的主键
-
每个表只能有一个
-
不能为NULL
-
ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
-
-
唯一索引-
索引中的值是唯一的 防止数据重复
-
可以为NULL
-
ALTER TABLE `table_name` ADD UNIQUE (`column`)
-
-
全文索引写过了-
ALTER TABLE `table_name` ADD FULLTEXT index_name (`column`)
-
-
空间索引通常是R-树结构-
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的 存储引擎有哪些??
- InnoDB
- 支持事务/行级锁&外键
- 提供包并发性能 适用于高附载的OLTP(联机事务处理)应用
- 数据以聚集索引存储 提高检索效率
- MyISAM (早期的存储引擎 已逐渐被 InnoDB )
- 不支持事物和外键 使用表级锁
- 适合读取多 更新少 ->>数据仓库
- 较高的读取性能 和 快的 表级锁定
- MEMORY
- 存在内存中 快 但是重启丢失
- 适用于临时数据|快速缓存
- NDB
- 高可用性和数据分布 适合大规模分布式应用
- 行级锁&自动分区
- ARCHIVE
- 适用于存储大量历史数据 支持高效的插入&压缩
- 不支持索引 适合日志数据的存储
补充
什么是聚集索引存储 为什么就提高了检索效率
就是聚簇索引
为什么用Mysql作为B+树的索引
- 高效查找
- B+树是一个AVL树 每个叶子节点到根节点的长度相同 插入、查找、删除的时间复杂度为O(logN)
- 树的高度增长不快,使得磁盘的I/O次数减少
- B+树是一个多叉树,*** 非叶子节点仅仅保存主键或索引值和页面指针***,使得每一页都能容纳更多的记录 因此内存放的索引就多 更容易命中
- 范围查询能力强
- B+树适合范围查询,因为叶子节点通过链表链接
扩展
- B树的每个节点都存储的是完整的数据 而B+树则是存储的key&指针 完整的数据存储在叶子节点上,因此B+树可以存放更多的索引页 减少磁盘查询次数。
- B+树叶子组成了链表 而B树智能化每一层查找
- B+树查找的平均时间更加稳定 B树因为非叶子节点就保存了数据因此 返回的快慢取决于节点到根的距离
- 为什么不用二叉树?
- 可能会成为斜树 --> 一个链表 索引失效
- 为什么不用红黑树?
- 红黑树毕竟还是二叉树,一个结点点最多拥有两个直接子结点,当我们插入大量数据的时候,会导致的树的高度过高,I/O渐进复杂度为O(h)。所以还是没有有效的减少查找过程中磁盘I/O的存取次数。
索引的最左前缀匹配原则是什么?
- 索引的底层是一颗B+树,那么联合索引的底层也就是一颗B+树,只不过联合索引的B+树节点中存储的是键值。由于构建一棵B+树只能根据一个值来确定索引关系,所以数据库依赖联合索引最左的字段来构建。
- 只有联合索引才能谈最左前缀匹配
- 查询的顺序也是 按照从左到右的顺序查询 所以
- 原则:联合索引必须从最左列开始匹配,且列之间连续。
- 范围条件的影响:范围条件会阻断后续列的索引使用。
但是 如果
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语句的操作, 这些
操作要么完全地执行,要么完全地都不执行, 它是一个不可分割的工作执行单元。
- 事务就是用户定义的一系列执行SQL语句的操作, 这些
事物的四大特性
- 原子性(Atomicity)
- 个事务中的所有操作要么全部提交成功,要么全部失败回滚
- 一致性(Consistency)
- 数据库总是从一个一致性的状态转换到另一个一致性的状态
- 隔离性(Isolation)
- 通常来说,一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的。
- 持久性(Durability)
- 一旦事务提交,则其所做的修改会永久保存到数据库。
什么是MVCC 多版本并发控制
是一种并发控制机制 允许多个事务同时读取和写入数据 无需等待
这么做的?
- 数据库为每一个事物创建一个数据快照。每当数据被修改的时候,mysql不会立刻覆盖原有的数据,而不是生成新版本的记录。每个记录都保留了对应的版本号和时间戳
- 多个版本串联起来形成了一条版本链 这样可以不同时刻启动的事务可以
无锁的获得不同版本的数据。 - 写操作可以继续 无非就是创建新的数据版本
其实并不是存了很多个版本的数据
而是通过undolock的保存数据的历史版本来保留所有的版本 通过undolock来回溯到上面的版本
只是借助 undolog 记录每次写操作的反向操作
Mysql的日志类型
binlog 逻辑日志 二进制日志文件
-
用于记录mysql中的所有的DDL&DML
-
在事务提交后产生的 所以可以用来回复数据库
-
记录的是操作
-
因此他可以跨平台使用
redolog 物理日志
- 用于恢复数据 保持数据的
一致性和持久性 - 而且只是 保持数据的
一致性和持久性 - 记录的是数据页的修改
undolog 回滚操作
- 作用
- 事务回滚
- MVCC支持 :通过版本链实现多版本的并发控制
Mysql的事务级别
读未提交
最低级的
可以读到其他事务为提交的数据 可能产生脏读
读已提交
可以读取其他事务已经提交的数据
但是可能会产生 不可重读
可重复读
确保数据在多个查询下的返回是一样的
但是可能会产生幻读
mysql的默认的隔离级别
串行化
并发执行的SQL事务的操作 其效果与这些SQL事务按某种顺序串行执行的效果相同
串行执行:sql事务在开启下一个事务之前要完成全部的操作
Mysql中有哪些锁
- 行级锁
- 仅仅对特定的
行加锁,允许其他事务并发访问不同的行,适用于高并发场景
- 仅仅对特定的
- 表级锁
- 对整个表枷锁 其他事物不能对表进行任何读写操作 适用于保证完整性的小型表
- 意向锁
- 表锁 表示某个事物对某行数据加锁的意图,分为意向共享锁和意向排他锁,主要用户行级锁的与表级锁的结合
- 共享锁
- 允许事物并发的读取同一资源 不能修改 智能释放锁后 才能获得锁
- 排他锁
- 只允许一个事务对资源进行读写 其他只有拿到锁才可以
- 元数据锁
- 用于保护数据对象的元数据 防止DDL操作时其他事务对这些对象进行修改
- 间隙锁
- 针对索引中的两个记录之间的间隙枷锁 防止其他事务在间隙中插入数据---》防止幻读
- 临键锁
- 行级锁+间隙锁 锁定具体的行和前面的间隙 确保在一个范围内不会出现幻读
- 插入意向锁
- 等待间隙的锁 用于指示事务在某个间隙中插入记录 允许其他事务进行共享锁 但在插入时会组织其他锁
- 自增锁
- 插入自增列时枷锁 保证自增的唯一性
Mysql事务的二阶段提交的是什么
为了确保redolock binlog之间的一致性 使用的一种机制。
Mysql通过二阶段提交来保证在crash recovery 崩溃恢复 时 不会出现数据丢失和数据不一致的情况
二阶段的两个阶段
准备阶段在事务提交时,Mysql的InnoDB引擎会先写入redolog 将其状态标记为prepare,表示事务已经准备提交但是还未真正完成 此时的redolog是预提交状态 还未标记为完成提交提交阶段redolog标记成prepare的时候 mysql server 会写入binlog 写入成功后 mysql会通知innodb 将redolog状态改为commit 完成整个事务的提交过程
Mysql如果发生死锁会怎么办
自动检索与回滚
MySQL自带死锁检测机制 ,当检测到死锁时,数据库会自动回滚到其中一个事务,以解除死锁。通常会回滚到事务持有的资源最少的那时候。
可以手动的kill发生死锁的语句
可以通过命令 来手动kill它
SHOW ENGINE INNODB STATUS;//展示执行的事务和事务相关的锁
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;//获取事务id
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;//获取线程id
KILL <thread_id>;//手动中止事务
Mysql是如何解决深度分页问题
什么是深度分页? 数据量很大的时候,按照分页访问后面的数据。
比如
`SELECT * FROM mianshiya WHERE name = 'yupi' ORDER BY id LIMIT 99999990, 10;`
这样的一条查询语句,
可以优化成:
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 也行,本质上是一样的。
比如
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放到其他的数据库中 让其他的数据库重放就行了
具体流程
- 线程创立 --> 服务器开始主从机制后 会创建i/o sql线程
- 连接建立 -->服务器io线程与主服务器建立连接,主服务器的binlog dump线程与之交互
- 同步位置告知 --> 服务器的io线程 告诉主服务器的dump线程从何处开始接受binlog
- 主服务器操作 --> 主服务器更新时 将更改的记录保存到binlog中
- binlog传输 --> 主服务器的dump线程检测到binlog变化 从指定的位置读取 由从服务器io线程拉取 采用
- 中继日志存储 --> 从服务器的io线程中 将接受的内存保存到relaylog中
- 数据写入 --> 从服务器的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以后又引入多线程
单线程的原因是因为
- Redis的操作是基于内存的 其大多数操作的性能瓶颈 不是CPU导致的
- 使用单线程模式 减少线程上下文切换所带来的性能开销
- redis在单线程的情况下,使用IO多路分用模型 就可以提高redis的IO利用率
6.0使用多线程的原因是因为
随着数据规模的增多 请求量增多 Redis的瓶颈主要存在于网络IO,引入多线处理,可以提高网络IO处理速度
Redis的跳表的底层原理
多路链表 底层保留所有的元素 每一层链表都是下一层的子集
Redis中的Hash
是一种键值对集合
可以将多个字段和值存储在一个键中
以便于管理一些数据
适合存一些小数据 支持快速的增删改查 非常适合存储对象的属性
Redis Zset的实现原理
是一种跳表+哈希表
跳表 -->存储数据结构的排序和快速查找
哈希表 -->成员与分数的映射,提供快速查找
当Zset的元素过于少的时候 将使用zip list 来节省内存
- 元素个数<128
- 元素成员名和分值长度<64字节
如何保证redis和Mysql的数据一致性
- 先更新缓存 再跟新数据库
- 先更新数据库 再更新缓存
- 删缓存 更新数据库 等后续查询数据库把数据回种到缓存
以上都不建议
- 先更新数据库 再删缓存 等后续查询数据库把数据回种到缓存
- 缓存双删策略 更新数据库之前先删一次缓存。更新换数据库以后 再延迟删缓存
- 使用binlgo异步更新缓存,监听到数据库的binlog的变化,通过异步的方式更新Redis缓存
击穿 穿透 雪崩
击穿
热点数据失效 导致大量的请求直接访问数据库
解决方案:
- 热点数据永不过时 -->比如双十一的时候 就可以把一些热点的关键词 给设置一个不可能过期的时间
- 热点数据预热 --> 预加载
- 动态缓存 --> 使用互斥锁,确保一个时间只能由一个请求去查询数据库查询并且更新缓存
穿透:查询一个不存在的数据 然后每次都查询
- 分布式锁
- 即使是一个空的key 也存入redis中
雪崩:多个缓存数据在同一时间内失效 导致大量的请求同时访问数据库
- 采用随机过期时间策略
- 双缓存机制
String的底层逻辑
SDS结构,并结合int,embstr,raw等不同的编码实现的
根据长度不同编码的也不同 以44字节为节点 分embstr和raw
分布式锁
- set en nx命令+Lua脚本
- 加锁&解锁
- 集群模式下 使用redlock
实现分布式锁的问题
- 业务未到期 ,锁失效
- 单点故障
- 主从不重复的问题
- 网络分区的问题
- 时钟漂移的问题
- 锁的可重复入的问题
- 误解放锁的问题
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拆分到不同的服务器上面
设计模式
单例模式哪几种实现方式 ?如何保证线程安全
-
饿汉式 --> 类加载的时候就加载
-
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
-
-
懒汉式 --> 用到才创建
-
public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
-
-
双重检查锁定 --> 懒汉式+ 只在第一次检查实例为空的时候加锁
-
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; } }
-
-
内部静态类 --> 利用类加载机制 实现懒加载和线程安全
-
public class Singleton { private Singleton() {} private static class Holder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }
-
-
枚举单例 --> 通过枚举实现单例 简单防止反射和序列化攻击
public enum Singleton {
INSTANCE;
public void bizMethod() {
// 一些业务逻辑方法
}
}
//使用
Singleton singleton = Singleton.INSTANCE;
singleton.bizMethod();
策略模式
一种行为设计模式,将每种算法封装起来,让他们可以相互替换,让算法独立于使用的客户端而变化
特点
- 算法封装
- 动态替换 在运行时选择和替换算法
- 遵循开闭原则
组成
- 策略接口:定义算法的通用接口
- 具体策略:实现算法的具体实现
- 上下文类:持有策略接口的使用,调用具体策略的方法
模板方式
抽象类定义了一个算法骨架,具体的步骤由子类提供
- 抽象类 定义模板
- 具体类 实现抽象方法
- 模板方法 调用一系列步骤方法
// 抽象类
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();
}
}
常见的设计模式
- 单例模式
- 保证系统中对象只有一个实例
- 连接池 全局配置
- 简单工厂
- 获取不同对象的时候可以使用
- 将对象的创建逻辑抽离复用
- 策略
- 封装一组算法让他们之间能够相互替代 能有效避免if sles
- 选择支付策略
- 模板
- 提炼核心流程封装成一个模板方法
工厂模式&抽象工厂模式
工厂模式关注的是 创建单一类型的对象
定义一个抽象方法 由子类实现具体对象的实例化
抽象工厂模式 关注的是 **创建一族相关对象 **
提供一个接口来创建一组相关的或者相互依赖的对象 无需指定他们的具体类
计网
常见的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 协议对数据进行加密传输,提供数据机密性和完整性保障。
- 端口号
- HTTP:默认使用端口 80。
- HTTPS:默认使用端口 443。
- 性能
- 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--> 无状态的认证和授权,特别是在分布式和跨域环境下
输入一个网站发生了什么
- 浏览器解析URL
- DNS解析成最终的ip
- 浏览器选择TCP/UDP
- IP 在TCP数据包的基础上 在封装源地址IP和目标地址IP等信息
- MAC 通过MAC确保子网内设备两点之间的通信寻址
- 网卡 网卡将二进制数据转化为电信号
- 交换机 工作在MAC层,他会将数据包的MAC头找到另一个设备连接在交换机的哪个端口
- 路由器 转发 三层网络设备 包含IP层
- 层层验证
- 服务器处理
- 浏览器接受并且渲染
计算机组成原理
线程和进程的区别
进程:资源分配的基本单位 每一个进程都有一个内存空间。
线程:是CPU的调度的基本单位 属于进程 一个进程可以包含多个线程 线程共享进程的测村空间和资源
进程之间的通信方式
- 管道 单向通信方式
- 命名管道 与匿名管道类似
- 信息队列 允许进程向另一个进程发送消息 消息顺序存储
- 共享内存
- 信息量
- 信号 异步通信方式
- Socket 在网络上不同主机上的进程通信
- 文件 通过读写文件俩来进行通信
调度算法
- 先来先服务
- 短作业有限
- 有限级调度
- 时间片轮转
- 最高相应比优先 计算影响比
- 多级反馈队列调度 结合多个调度策略

