后端研发1——基础知识

一 数据库

1.1 MySQL

1.1.1 SQL语句

select col1 as c1, col2 as c2, col3
from t1
where col1 = 2 and clol2 != 'null' and col3 in (1, 2, 3)
limit 0,2
# 查找各学校的平均提问数和平均答题数
select university, 
       avg(question_cnt) as avg_question_cnt,
       avg(answer_cnt) as avg_answer_cnt
from user_profile
group by university
having avg_question_cnt < 5 or avg_answer_cnt < 20
R (A, B, C)
(a1, b1, c1)
(a2, b2, c2)
S (D, E)
(d1, e1)
(d2, e2)
笛卡尔积R×S (A, B, C, D, E)
(a1, b1, c1, d1, e1)
(a1, b1, c1, d2, e2)
(a2, b2, c2, d1, e1)
(a2, b2, c2, d2, e2)

连接
连接操作是从两个关系的广义笛卡尔积中选择属性满足一定条件的元组

内连接
等值连接:从两表的笛卡尔积中选择指定值相等的元组
非等值连接:从两表的笛卡尔积中选择指定值满足一定大小关系的元组
自然连接:在等值连接的结果中去掉重复的属性列,要求两个表中有相同的属性列
# 等值连接
SELECT A.*, B.*
FROM student_info A inner join student_score B
ON A.student_id = B.student_id

# 非等值连接
SELECT A.*, B.*
FROM student_info A inner join student_score B
ON A.student_id > B.student_id

# 自然连接
SELECT A.*, B.*
FROM student_info A natural join student_score B
外连接

左外连接:把左表中要舍弃的元组保留,而在右表相应列中填null
		 返回内连接 + 只存在左表中的数据
右外连接:把右表中要舍弃的元组保留,而在左表相应列中填null
		 返回内连接 + 只存在右表中的数据
全外连接:把舍弃的元组也保存在结果关系中,在其他的属性填null
		 返回内连接 + 其他所有不匹配项,可以看做左右集合的并集
SELECT A.*, B.*
FROM student_info A left join student_score B
ON A.student_id = B.student_id

SELECT A.*, B.*
FROM student_info A right join student_score B
ON A.student_id = B.student_id
左外连接和右外连接的结果是全外连接结果的子集
MySQL不支持全外连接
SQLite不支持右外连接和全外连接

R
(A, B, C)
(a1, b1, c1)
(a2, b2, c2)
(a4, b4, c4)
S
(B, E)
(b1, e1)
(b2, e2)
(b3, e3)

外连接
(A, B, C, E)
(a1, b1, c1, e1)
(a2, b2, c2, e2)	
(a4, b4, null, null)
(null, null, b3, e3)

左外连接
(A, B, C, E)
(a1, b1, c1, e1)
(a2, b2, c2, e2)	
(a4, b4, null, null)

右外连接
(A, B, C, E)
(a1, b1, c1, e1)
(a2, b2, c2, e2)	
(null, null, b3, e3)

1.1.2 InnoDB 和 MyISAM 的区别?

数据库管理系统(DBMS)使用数据引擎进行增删改查操作
不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能
使用不同的存储引擎,还可以获得特定的功能


1 InnoDB 支持事务和外键,MyISAM 不支持。
2 InnoDB 的索引是聚簇索引,MyISAM 的索引是非聚簇索引。
3 InnoDB 表必须要有主键,如果没有MySQL会自动生成一个用户不可见的row_id列作为主键。
  MyISAM 允许表中没有主键。
4 InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁
5 InnoDB 不保存表的具体行数,获取表的行数时时需要全表扫描。
  MyISAM 用一个变量保存了整个表的行数,获取行数时只需要读出该变量即可。
  执行 select count(*) from table_name
6 每个InnoDB表在磁盘上存储成2个文件,.frm文件存储表定义,.idb文件存储数据和索引。
  每个MyISAM表在磁盘上存储成3个文件,.frm文件存储表定义,.MYD文件存储数据文件,.MYI文件存储索引文件
	
如何选择
1 是否要支持事务,如果要请选择 InnoDB,如果不需要可以考虑 MyISAM;
2 如果表中绝大多数都只是读查询,可以考虑 MyISAM,如果写频繁,请使用InnoDB;

MyISAM中使用表锁,一个更新语句会锁住整张表,
导致其他查询和更新都会被阻塞,因此并发访问受限。
这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;

1.1.3 聚簇索引和非聚簇索引的区别?

InnoDB 采用聚簇索引,数据记录存放在主键索引的叶子节点上,
因此InnoDB表必须要有主键,通过主键索引效率很高。
但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。
因此,主键不应该过大,因为主键太大,其他的辅助索引都会很大。

如果用户使用InnoDB存储引擎建立表的时候,没有指定主键,
则Mysql会自动的帮你找到一个合适的唯一索引作为主键,
若找不到符合条件唯一索引条件的字段时,
会生成类似于ROW_ID的虚拟列充当该InnoDB表的主键。	

MyISAM 采用非聚集索引,数据记录文件和索引文件分离,
索引的叶子节点保存的是数据文件的地址。
主键索引和辅助索引是独立的。

1.1.4 MySQL事务的ACID *

1、ACID特性
A 原子性: 原子性是指一个事务中的操作,要么全部成功,要么全部失败,如果失败,就回滚到事务开始前的状态。
C 一致性: 事务前后数据的完整性必须保持一致。比如转账,转账前后A、B两个账户的金额总数应该是不变的。
I 隔离性: 多个用户的并发事务访问同一个数据库时,一个用户的事务不应该被其他用户的事务干扰,多个并发事务之间要相互隔离。
D 持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

2、MySQL原子性怎么保证?(undo log 回滚日志)
MySQL利用 undo log 保证原子性。undo log 记录了要回滚的相应日志信息。
当事务执行失败或调用了rollback,导致事务需要回滚,
就可以利用 undo log 中的信息将数据回滚到修改之前的样子。

undo log 是逻辑日志。
可以认为当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,
反之亦然,当 update 一条记录时,它记录一条相反的 update 记录。
如果要回滚,按照 undo log 的回滚日志执行一遍就可以了。

undo log 还有一个作用是提供多个行版本控制(MVCC)。

3、MySQL的持久性怎么保证?
MySQL先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。	
如果此时突然宕机,内存中的数据就会丢失。

MySQL利用 redo log 保证持久性。redo log中记载对每个页做了什么修改。
当由于发生宕机而导致数据丢失时,就可以通过redo log来把这部分数据恢复到内存。

redo log是物理日志,它记录的是对于每个页的修改。
事务提交时,先把修改写入redo log,然后再修改物理页;
redo log由两部分组成:内存中的redo log buffer 和 磁盘上的redo log file。
为了减少IO操作,MySQL先把修改写入redo log buffer中,
之后再存入redo log file进行持久化。
可以通过设置参数决定何时刷盘。

4、redo log 和 binlog 的区别
1)redo log 是 InnoDB 引擎特有的;binlog 是MySQL自带的,所有引擎都可以使用。
2)redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;
   binlog 是逻辑日志,以二进制的形式记录语句的原始逻辑。
   binlog有两种模式,statement 格式记录执行的sql语句, 
   row格式会记录行的内容,记两条,更新前和更新后。
3)redo log 的日志空间大小固定,记录满了就从头循环写;
   binlog采用追加写,一个文件写到一定大小的时候会更换下一个文件,不会覆盖。
4)redo log可以用于崩溃恢复。
   binlog可以用于搭建集群、数据备份、恢复数据。

bin log 采用追加写保存所有日志。
MySQL无法判断哪些数据已经刷入磁盘了,哪些数据还没有。
redo log 不一样,已经刷入磁盘的数据,都会从 redo log 中删除,
数据库重启后,直接把 redo log 中的数据都恢复至内存就可以了。
这就是为什么说 redo log 具有崩溃恢复的能力,而 bin log 不具备。

5、什么是二阶段提交?
二阶段提交:把redo log 的写入分成 prepare 和 commit 两个步骤,
是为了让redo log 和bin log的数据保持一致。
阶段1:把 InnoDB 的 redo log 写入磁盘,InnoDB 事务进入 prepare 状态
阶段2:如果前面prepare成功,把 bin log 写入磁盘,如果成功,InnoDB 事务进入 commit 状态
(实际是在redo log里面写上一个commit记录)

MySQL以binlog的写入与否作为事务成功的标记
每个事务的binlog的末尾会记录一个 XID event,标志着事务是否提交成功。
恢复过程中,binlog 最后一个 XID event 之后的内容都会被清除。

使用二阶段提交后,写入binlog时发生异常也不会有影响,
因为MySQL根据redo log日志恢复数据时,
发现redo log还处于prepare阶段,并且没有对应binlog日志,就会回滚该事务。

1.1.5 隔离级别 *

1、不考虑隔离性,会发生三种情况
脏读: 指一个事务读取了另外一个事务未提交的数据。
不可重复读: 在一个事务内读取表中的某一行数据,多次读取结果不同。
		   这是由于在查询过程中,数据被另外一个事务修改并提交了。
幻读: 在一个事务内读取到了别的事务插入的数据,导致前后读取数量总量不一致。

幻读和不可重复读都是读取了另一条已经提交的事务,
所不同的是不可重复读查询的是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

2、MySQL数据库支持的四种隔离级别
读未提交: 一个事务还没提交时,它做的变更就能被别的事务看到。
		这种事务隔离级别下,脏读、不可重复读、幻读均无法保证。
读已提交(RC): 一个事务提交之后,它做的变更才会被其他事务看到。
		即一个事务所做的修改在提交之前对其它事务是不可见的。
		可避免 “脏读” 。
可重复读(RR): 保证在同一个事务中多次读取同一数据的结果是一样的。
		MySQL的默认隔离级别。可避免 “脏读” 和 “不可重复读“。
可串行化: 强制事务串行执行,对于同一行记录,“写” 会加 “写锁”,“读” 会加 “读锁”,
		当出现读写冲突时,后访问的事务必须等前一个事务执行完成,才能继续执行。
		这样多个事务互不干扰,不会出现并发一致性问题。
		可避免 “脏读”、“不可重复读”、”幻读”。
		可通过锁表实现:在访问数据前,对其加锁,阻止其他事务访问数据。

3、MySQL的默认隔离级别如何实现
MySQL默认隔离级别的实现依赖于MVCC和锁,避免了脏读、幻读、不可重复读。

利用MVCC实现一致性非锁定读,
确保在同一个事务中多次读取相同的数据返回的结果是一样的,
解决了不可重复读的问题。

利用Gap Locks和Next-Key Locks可以阻止其它事务在锁定区间内插入数据,避免幻读
	记录锁:Record Locks:锁定一行,禁止访问,避免不可重复读
	间隙锁:Gap Locks:锁定一个范围,禁止插入,避免幻读
	临键锁:Next-Key Locks:锁定一行并锁定一个范围

快照读:读取的是快照版本,普通的SELECT采用快照读。
当前读:读取的是最新版本,即加锁读。UPDATE、DELETE、INSERT采用当前读。

RC:每个select都生成一个Read View,因此可能读到另一个事务修改的数据
RR:事务开始时生成一个Read View


MVCC:多版本并发控制
实现:通过3个隐藏字段、Read View、undo log
DB_TRX_ID:一个6byte的标识,每处理一个事务,其值自动+1

总的来说
系统保持数据的多个版本,每开始一个新的事务,系统版本号都会自动递增,
事务开始时刻的系统版本号会作为事务的ID号,
用来和查询到的每行记录的版本号进行比较,判断哪些数据可见,哪些不可见。

详细来说
进行查询操作时事务会生成一个Read View,ReadView是一个事务快照,
记录当前时间点系统内活跃事务的ID列表,即系统内所有未提交的事务。
事务根据Read View来判断哪些数据可见,哪些不可见,
具体的select查询过程是:
1)先查看undo log中的最新数据行,如果该行的版本号比Read View中的最小值小,
说明数据版本对应的事务已提交,数据对当前数据库可见,可直接作为结果返回;
2)如果数据版本号比Read View中的最大值大,
说明这条数据是由一个新的事务修改的,对当前事务不可见,
那么顺着undo log的版本链往下寻找第一条满足条件的;
版本链:行记录中回滚指针指向老版本数据位置,形成一条版本链
3)如果数据版本号等于Read View中的某个值,说明该行数据仍然处于活跃状态,
对当前事务不可见。否则说明操作该数据的事务在当前事务开始前就已提交,对当前事务可见。

1.1.6 锁 *

1、按实现思想:悲观锁和乐观锁(不是真实存在的锁,只是一种机制或者说是理论)
悲观锁:总是假设最坏情况,每次访问数据都认为别人会修改,所以每次访问数据都加锁。
乐观锁:总是假设最好情况,每次访问数据都认为别人不会修改,所有每次访问数据都不加锁,
在更新数据的时候会判断数据有没有被修改,如果没有被修改才更新,否则就重试或失败。

2、按锁的粒度:行锁和表锁
MySQL的行锁
1)记录锁:锁住一行,使其它事务无法访问该行数据,但是无法限制 INSERT 操作。
2)间隙锁:锁住一个范围,不包含记录本身,使其它事务无法在该范围内插入数据。
3)临键锁:记录锁+间隙锁,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,可以避免幻读。
	
3、按锁的模式:共享锁和排它锁
共享锁:读锁,不阻塞,多个用户可以同时读一个资源,互不干扰。
排他锁:写锁,一个写锁会阻塞其他的读锁和写锁,只允许一个用户进行写入,防止其他用户读取正在写入的资源。
一个事务获取读锁后,允许其他事务获取读锁,但不允许其他事务获取写锁。
一个事务获取写锁后,不允许其他事务获取读锁和写锁,必须等待该事务释放锁。
	
5、死锁及需要满足的条件
多个进程竞争资源,并且互相拥有对方所需要的资源,造成每个进程都无法继续下去而阻塞等待。

互斥条件:即一个资源的使用是排他的,只能由一个进程占用。
请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求。
不剥夺条件:指进程已获得的资源不可被剥夺,只能在使用完时由自己释放。
循环等待条件:一个进程所需的资源被其他进程占用,进程间互相等待对方释放资源。	

1.1.7 MySQL索引 *

1、索引的概念
数据库查询是数据库的最主要功能之一。但是每种查找算法都只能应用于特定的数据结构之上。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,
这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
索引在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构。	
索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要。

常用的索引:B-树索引、B+ 树索引、hash 索引
2、B+Tree索引 
数据库系统的设计者利用磁盘预读原理,将一个B+Tree节点的大小设为等于一页。
对应在物理存储上是一个连续的物理块,这样每个节点只需要一次I/O就可以完全载入。
由于根节点常驻内存,B+Tree中一次检索最多需要高度h-1次I/O。
一般,在实际应用中,节点的出度是非常大的数字,通常超过100,因此h非常小(通常不超过3)。
因此,用B+Tree作为索引结构效率是非常高的。

3、为什么不用hash索引?
1)不支持范围查询,因为Hash 索引指向的数据是无序的,
	而 B+ 树可以, B+ 树的叶子节点是个有序的链表。
2)不支持 ORDER BY 排序,因为 Hash 索引指向的数据是无序的
	而 B+ 树索引数据是有序的,可以起到对该字段 ORDER BY 排序优化的作用。
3)容易出现Hash冲突,查找需要遍历链表中所有的行指针,逐行进行比较,比较耗时
	另外当HASH冲突特别多的时候,插入删除操作的维护成本也会变大

4、为什么不用B-树索引(B+Tree 和 B-Tree的区别)?
B-Tree的每个节点中不仅包含索引值key,还包含数据data。
而每一个页的存储空间是有限的,如果data较大时将会导致每个节点能存储的key较少,
这样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。
在B+Tree中,非叶子节点只存储key值,可以大大增加每个节点能存储的key值数量,降低B+Tree的高度。
因此选择使用B+树而不是原始的B-树


5、为什么不用红黑树(平衡二叉查找树的变体)?
1)红黑树是一种这二叉树结构,相比于B+树它的树高明显要深的多。
2)红黑树中逻辑上很近的节点(如父子节点)物理上可能很远,无法利用局部性,
   所以红黑树的I/O效率明显比B+Tree差很多。

红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,
所以红黑树不是严格意义上的平衡二叉树,但对之进行平衡的代价较低,
其平均统计性能要强于平衡二叉树。 
由于每一棵红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,
可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。

6、索引优化

1.2 Redis

1.2.1 Redis介绍

Redis是基于内存的 key-value 数据库,此外单个value的最大限制是1GB。
整个数据库统统加载在内存当中进行操作,定期把数据库数据刷到硬盘上进行保存。
因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过10万次读写操作。
Redis 除了做缓存之外,也经常用来做分布式锁,甚至是消息队列。

分布式锁 :基于 Redisson 来实现分布式锁。分布式事务和分布式事务锁
消息队列 :基于Redis 自带的 list 数据结构作为一个简单的队列使用。Redis5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。

Redis应用场景
1 缓存热点数据(经常会被查询,但是不经常被修改或者删除的数据)



消息队列	
kafka?

1.2.2 Redis是单线程还是多线程?单线程为什么快? *

在6.0之前,Redis是单线程的
不过单线程指的是网络请求模块和数据操作采用单线程(所以不需考虑并发安全性),
一个线程处理所有网络请求,其他如持久化存储模块仍用了多线程。

Redis 通过IO 多路复用来监听来自客户端的大量连接(或者说是监听多个 socket),
实现一个线程处理大量连接。

为什么快?
1)绝大部分请求是纯粹的内存操作,速度非常快,
2)采用高效的数据结构,如哈希、跳表等
2)采用单线程, 避免了不必要的上下文切换和资源竞争
3)采用 I/O 多路复用机制处理Socket请求,非阻塞I/O,Redis可以高效进行网络通信

Redis6.0引入了多线程的特性,这个多线程是在哪里呢?
是对处理网络请求过程采用了多线程。
Redis 6.0采用多个IO线程来处理网络请求,网络请求的解析可以由其他线程完成,
然后把解析后的请求交由主线程进行实际的内存读写。
提升网络请求处理的并行度,进而提升整体性能。

1.2.3 Redis有哪些数据类型 *

string、list、hash、set、zset


1、string 的 SDS 结构
	int len; 		当前长度
	int free; 		未使用空间
	char buf[]; 	指针
优点
开发者不用担心字符串变更造成的内存溢出问题。
常数时间复杂度获取字符串长度len字段。
空间预分配free字段,会默认留够一定的空间防止多次重分配内存。

2、list 
在双向链表的基础上扩展了表头、表尾、节点数等属性
优点
可以直接获得头、尾节点。
常数时间复杂度得到链表长度。
是双向链表,在两端插入删除都很快

3、hash
在数组+单链表的基础上,进行了一些rehash优化,使用链地址法处理冲突
当哈希表的键对太多或者太少,,程序需要对哈希表的大小进行相应的扩容和收缩,使负载因子维持在一个合理的范围内

rehash
hash有一个大小为2的dictht数组ht,即两个hash表,用h[0]用来存储数据,ht[1]在rehash时使用。
1、rehash的时候,先为ht[1]哈希表分配空间,大小取决于ht[0]当前使用的情况。
2、然后将保存在ht[0]中的数据rehash(重新计算哈希值)到ht[1]上。
3、当ht[0]中所有键值对都迁移到ht[1]后,释放ht[0],将ht[1]设置为ht[0],并ht[1]初始化,为下一次rehash做准备。

渐进式rehash
采用分而治之的思想,将庞大的迁移工作量划分到每一次CURD中,避免了服务繁忙。
1、为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表,并将rehashidx的值设置为0,表示rehash工作正式开始。
2、在rehash期间,每次对字典执行增删改查操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成以后,rehashidx的值+1
3、着字典操作的不断执行,最终ht[0]的所有键值对都会被rehash到ht[1],此时把rehashidx的值设为-1,表示rehash操作结束。

需要注意的一点是在渐进式rehash的过程中进行增删改查操作时,
如果index大于rehashidx,访问ht[0],否则访问ht[1]。

哈希表怎么实现?
使用数组加单链表的形式实现
怎么解决哈希冲突?
1、开放寻址法:发生了冲突后,就去寻找下一个空的散列地址
2、拉链法:被hash到同一个位置的值使用单链表连接起来
3、再哈希法:有多个不同的Hash函数,当发生冲突时,使用第二个,第三个哈希函数计算地址,直到无冲突。虽然不易发生聚集,但是增加了计算时间。

当一个哈希只包含少量键值对, 
且每个键值对的键和值要么就是小整数值,要么就是长度比较短的字符串,
那么Redis就会使用压缩列表来做哈希的底层实现。

4、set
集合使用intset和hashtable实现,
当集合中只有整数且元素个数小于512(这个值可以设置)时,
redis使用intset作为set的底层实现。
否则使用hashtable作为集合的内部实现。

5、zset
当数据比较少(元素个数小于 128 ),且保存的元素成员的长度小于 64 字节,zset使用ziplist(压缩列表) 作为底层实现,否则使用 skiplist(跳表) 进行存储。

ziplist 压缩列表
使用压缩列表是为了节省内存。
一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。entry中含有entry长度等信息。
保留了数组占用一片连续的空间可以很好的利用CPU缓存访问数据的优势,又避免了使用数组存储字符串时对空间的浪费。

skiplist 跳表
跳跃表在链表的基础上增加了多级索引以提升查找的效率,以空间换时间,索引占内存
如果原始链表中存储的是很大的对象,就适合使用跳表,
因为索引结点只需要存储关键值和几个指针,并不需要存储对象,
当节点本身比较大或者元素数量比较多的时候,跳表的优势必然会被放大,
而索引占用内存的缺点则可以忽略。
//跳跃表节点结构体
typedef struct SkipListNode{
    int key;
    int data;
    struct SkipListNode *forward[10];
}SkipListNode;
 
//跳跃表链表结构
typedef struct SkipList{
    int level;
    struct SkipListNode *head;
}SkipList;

// 查找函数
SkipListNode* find(SkipList *list, int key){
	p = list->head;
	for(int i=9; i>=0; i--){
		while(p->forward[i] != NULL && head->forward[i]->key < key){
			p = p->forward[i];
		}
	}
	if(p.forwards[0] != NULL && p.forwards[0]->key == key){
		return p.forward[0];
	}
	return NULL;
}
什么数据结构适合做索引,为什么适合做索引?
跳表、B+树、hash
查找速度快

1.2.4 Redis持久化 *

持久化的目的主要是做灾难恢复,数据恢复。由于Redis的数据全都放在内存里面,如果Redis挂了,没有配置持久化的话,重启的时候数据会全部丢失。

RDB持久化:定时获取快照,写入磁盘,快照就是当前时间点key-value数据的副本
实际操作过程是fork一个子进程,先将数据写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储 。
优点
1、只有一个文件,可以轻松转移到其他存储介质,易于灾难恢复
2、性能高,由fork出的子进程完成持久化的工作,主进程继续处理命令;
3、大数据集比AOF的恢复效率高
缺点是数据安全性低,RDB是间隔一段时间进行持久化,若期间redis发生故障,可能发生数据丢失。

AOF持久化:所有的命令日志以追加的方式写入AOF文件,在 Redis 重启的时候,
可以通过回放 AOF 文件中的指令来重新构建整个数据集。
可以通过配置appendfsync参数决定何时将内存缓存同步到硬盘中的 AOF 文件。
appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显式地将多个写命令同步到硬盘
appendfsync no        #让操作系统决定何时进行同步,操作系统自动调度刷磁盘
优点
1、数据安全,AOF 持久化可以配置 appendfsync 属性为always,每执行一个命令就记录到 AOF 文件中一次。
2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof工具解决数据一致性问题。
3、AOF持久化以易于理解的日志文件形式记录所有的修改操作。
缺点是AOF的持久化文件比RDB的持久化文件大,在恢复大数据集时的速度比RDB慢。
并且数据集大的时候,比 RDB 启动效率低。

1.2.5 缓存 *

缓存穿透:在高并发下,查询一个不存在的key值,缓存不会被命中,导致大量请求直接落到数据库上。这会给持久层数据库造成很大的压力。
解决方案: 1.对空值做缓存;2.设置key的规则;

缓存击穿:在高并发下,对一个特定的key进行查询,但是这个时候缓存正好过期了,缓存没有命中,导致大量请求直接落到数据库上。导使数据库瞬间压力过大。

缓存雪崩:在高并发下,大量的缓存key在同一时间失效,导致大量的请求落到数据库上,数据库瞬时压力过重而宕机。
解决方案:设置一个失效时间的随机数,防止缓存同时失效。设置缓存永不过期等。

缓存失效机制
作为缓存系统都要定期清理无效数据,就需要一个主键失效和淘汰策略.

在Redis当中,有生存期的key被称为volatile。
在创建缓存时,要为给定的key设置生存期,
当key过期的时候(生存期为0),它可能会被删除。

redis 提供 6种数据淘汰策略:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据

1.3 SQL注入

1.3.1 SQL注入是什么?

程序接受用户输入的变量或URL传递的参数,
并且这个变量或参数是组成SQL语句的一部分,
程序对于用户输入的内容或传递的参数不加甄别直接拼接到SQL语句里,
攻击者利用这一点执行自己编写的SQL语句,
实现无账号登录,甚至篡改数据库等。
这就是SQL注入。

1.3.2 开发时要注意哪些安全问题?

解决SQL注入的方法:
1、检查输入格式,过滤特殊符号。
2、绑定变量,使用预编译语句。
预编译也是防止SQL注入的最佳方式,
使用预编译的SQL语句语义不会发生改变,
参数只会作为值来匹配数据库中的数据,而不会被当做SQL执行。

没有预编译,拼接的值如果包含sql关键字,那么可能作为关键字处理。比如,or。
如果是预编译,参数的值就是作为值,哪怕包含了sql关键字,仍然是作为值。
比如,' or '1=1这个是作为一个值来匹配数据库里的数据。

预编译的作用
1)防止sql注入
2)sql复用,一次编译,多次执行

二 网络

2.1 七层模型

七层模型: 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
五层模型: 物理层、数据链路层、网络层、传输层、应用层

2.2 TCP和UDP

2.2.1 TCP和UDP的区别?

TCP:有连接,提供可靠交付,面向字节流,提供一对一的全双工通信,相比于UDP传输效率低。
UDP:无连接,尽最大可能交付,不保证可靠,面向报文,支持一对一、一对多、多对一和多对多的交互通信,相比于TCP传输效率更高。

TCP分段对报文进行分段,到传输层时按照之前分段的序列号seq进行报文重组。
UDP对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部。

TCP粘包现象?
多个数据包被连续存储于一段缓存中,在对数据包进行读取时无法确定数据包的边界。

TCP粘包原因?
TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。
若连续几次发送的数据都很少,
通常TCP会根据优化算法把这些数据合成一包后一次发送出去,
这样接收方就收到了粘包数据。

TCP粘包解决方法?
1)发送方给每个包添加包长度信息,接收端在接收到数据后,通过读取包长度字段,就知道每一个数据包的实际长度了。
2)发送方将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3)在数据包之间设置边界,如添加特殊符号,接收端通过边界可以将不同的数据包拆分开。

2.2.2 TCP如何保证可靠传输? *

TCP通过确认和重传机制来保证可靠传输,确保数据包不丢、不乱。
确认:使用累计确认机制,收到一个确认号为n的报文段表示n之前的报文段都已收到。
重传:收到超时和冗余ACK时重传对方所需的报文段。

2.2.3 TCP如何进行流量控制? *

流量控制是为了控制发送方发送速率,保证接收方来得及接收。
TCP通过接收方发送的报文中的接收窗口字段来告知发送方接收方的接收窗口大小,
从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。

发送方的发送窗口的大小取接收方的接收窗口和拥塞窗口的最小值
接收窗口:接收方能接收的报文段数量
拥塞窗口:发送方一次性能发送的报文数量

2.2.4 TCP如何进行拥塞控制? *

流量控制是为了控制发送方发送速率,保证接收方来得及接收。
而拥塞控制是为了降低整个网络的拥塞程度。

TCP通过慢开始、拥塞避免、快重传、快恢复等机制进行拥塞控制
慢开始:拥塞窗口初始值为1,每经过一个传输轮次,拥塞窗口大小加倍。直到达到一个预先设定的门限值,开始使用拥塞避免。
拥塞避免:每经过一个传输轮次,拥塞窗口大小加1,出现超时时,拥塞窗口大小减半。
快重传:发送方连续收到3个重复的ACK时,重传对方所需要的报文段。
快恢复:发送方连续收到三个冗余ACk时,把慢开始门限值设置为拥塞窗口的一半,并使用拥塞避免算法。

2.2.5 TCP的三次握手和四次挥手 *

三次握手
第一次握手: 客户端发送一个SYN=1,序号字段=x的TCP包表示请求连接服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手: 服务器发回SYN=1,ACK=1,序号字段=y,确认字段=x+1的确认包应答,表示同意连接,并进入SYN_RECV状态
第三次握手: 客户端向服务器发送ACK=1,序号字段=x+1,确认字段=y+1的数据包,连接建立成功,客户端和服务器进入established状态。
客户端服务器
第一次握手SYN=1、SYN_SEND
第二次握手SYN=1,ACK=1、SYN_RECV
第三次握手ACK=1、ESTABLISHEDESTABLISHED
四次挥手     序号和确认字段就不说了,就是一方发送序号字段为x的数据包,另一方发送确认字段为x+1的数据包表示x及其之前的数据都收到了,主要说一下双方的状态变化
第一次挥手: 客户端发送FIN=1的报文,关闭客户端到服务器的数据传送,客户端进入FIN_WAIT_1状态。
第二次挥手: 服务器收到FIN报文后,发送ACK=1的数据包给客户端,并进入CLOSE_WAIT状态。
第三次挥手: 服务器发送FIN=1,ACK=1的报文给客户端,关闭服务器到客户端的数据传输,进入LASK_ACK状态。
第四次挥手: 客户端收到FIN=1的报文后,进入TIME_WAIT状态,接着发送一个ACK=1的报文给服务器,服务器收到后进入CLOSED状态,完成四次挥手。
客户端服务器
第一次挥手FIN=1、FIN_WAIT_1
第二次挥手ACK=1、CLOSE_WAIT
第三次挥手FIN=1、LAST_ACK
第四次挥手ACK=1、TIME_WAIT(2MSL)、CLOSEDCLOSED
为什么需要三次握手?
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。主要防止资源的浪费。
当客户端发出第一个连接请求报文段时并没有丢失,而是在某个网络节点出现了长时间的滞留。
超时后客户端认为其已丢失,重发连接请求报文,和服务器建立连接,通信后断开连接。
此时,滞留报文到达服务器,这应该是一个早已失效的报文段,但是服务器在收到此失效的连接请求报文段后,以为是客户端的一个新请求,就会向客户端发出确认报文段,同意建立连接。
假设不采用三次握手,那么只要服务器发出确认后,新的连接就可以建立了。
但是由于客户端没有发出建立连接的请求,因此不会管服务器的确认,也不会向服务器发送数据,
但服务器却以为新的运输连接已经建立,一直在等待,服务器的资源就白白浪费掉了。

为什么要四次挥手?
关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
服务器收到客户端的 FIN 报文时,可能还有数据需要处理和发送
因此先回一个 ACK 应答报文,等服务端不再发送数据时,才发送 FIN 报文给客户端。
由于ACK和FIN要分开发送,因此需要四次挥手。

2.2.6 TIME_WAIT和CLOSE_WAIT *

1、为什么要有TIME_WAIT状态?(客户端接受服务器FIN包后的状态)
1)确保有足够的时间让服务器收到ACK包。
TIME_WAIT的时间设置为2MSL,即报文最大存活时间的2倍。
如果这个最终的ACK丢失,服务器将重发最终的FIN,
因此客户端必须维护状态信息允许它重发最终的ACK。
2)让本次连接期间产生的所有报文段从网络中消失,避免新旧连接混淆
TCP数据包可能由于路由器异常而“迷途”,在迷途期间,这个数据包可能会被重发。
迷途的数据包在路由器修复后也会被送到最终目的地,
如果这个数据包在连接关闭后到达,就会重新建立起一个相同的IP地址和端口之间的TCP连接,
为了避免这种情况,TCP不允许处于TIME_WAIT状态的连接启动一个新的连接,因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个TCP连接的时候,来自先前连接的重复分组已经在网络中消失。
	
2、服务器出现大量CLOSE_WAIT的原因和解决方式?
原因:客户端发出FIN后,服务器没有及时关闭连接
解决方案:检查代码,特别是释放资源的代码

2.2.7 I/O多路复用

I/O是指网络I/O,多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。
IO多路复用指使用一个线程来检查多个文件描述符(socket)的就绪状态,用户将想要监视的文件描述符添加到select/poll/epoll,如果文件描述符就绪,则函数返回

1、select:当监听到文件描述符就绪,select函数就会返回,遍历整个文件描述符数组来找到就绪的描述符fd,然后进行对应的IO操作。
优点:几乎在所有的平台上支持,跨平台支持性好
缺点
1)单进程可以打开fd有限制,32位机器默认1024个,64位机器默认2048个;
2)采用轮询方式全盘扫描,会随着文件描述符数量增多而性能下降。
3)每次调用 select(),需要把 fd 集合从用户态拷贝到内核态,非常消耗资源;	

2、poll: 基本原理与select一致;唯一的区别就是poll没有最大文件描述符限制,其使用链表而非数组的方式存储fd。

3、epoll:同样没有fd个数限制,并且fd从用户态拷贝到内核态只需要一次,同时通过回调函数通知是哪一个fd就绪,无需遍历,效率更高

select和poll只能通知有fd已经就绪了,但不能知道究竟是哪个fd就绪,
所以select和poll需要通过遍历找到就绪的fd。
而epoll不但可以知道有fd就绪了,而且还知道就绪的fd编号。

当连接数较多,且有很多不活跃连接时,epoll效率更高。
但是当连接数较少,且十分活跃时,由于epoll需要频繁回调,性能可能低于select和poll。



int epoll_create(int size);
建立一个 epoll 对象,并传回它的id
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
事件注册函数,将需要监听的事件和需要监听的fd交给epoll对象
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待注册的事件被触发或者timeout发生


epoll在用epoll_ctl函数进行事件注册的时候,已经将fd复制到内核中,所以不需要每次都重新复制一次。
epoll是被动触发方式,给fd注册了相应事件的时候,也为fd指定了一个回调函数,
当数据准备好之后,就会把就绪的fd加入一个就绪的队列中,
epoll_wait的工作方式实际上就是在这个就绪队列中查看有没有就绪的fd,
如果有,就唤醒就绪队列上的等待者,然后调用回调函数。


BIO 同步阻塞IO:发起IO请求后,阻塞等待IO完成
NIO 同步非阻塞IO:发起IO后返回,需要不断发IO请求看数据是否准备好
	如果有大量文件描述符都要等,那么就得一个一个的read。
	会带来大量的Context Switch
	(read是系统调用,每调用一次就得在用户态和核心态切换一次)
	等待时间设的太长,程序响应延迟就过大;
	设的太短,就会造成过于频繁的重试,浪费CPU。
AIO 异步IO:发起IO请求后返回,内核准备好数据后通知用户线程直接使用

2.3 HTTP和HTTPS

2.3.1 HTTP和HTTPS的请求过程 *

DNS的过程?
1、浏览器在自己的DNS缓存中查找
2、操作系统在自己的DNS缓存和hosts文件中查找
3、在本地DNS域名服务器进行查询
4、本地域名服务器采用转发模式或迭代模式向其他DNS服务器查询
	根域名服务器
	顶级域名服务器
	权限域名服务器	

提交一个URL后的过程/浏览器输入网址以后经历了什么?HTTP的请求过程?
1、浏览器解析URL,根据域名请求IP地址,域名系统返回域名对应的IP地址
2、浏览器与服务器建立TCP连接
3、浏览器发送HTTP请求,GET对应资源
4、服务器响应HTTP请求,返回响应结果,浏览器得到HTML代码
5、浏览器解析HTML代码,并请求HTML代码中的资源
	 浏览器解析HTML代码,遇到静态资源时,向服务器端去请求下载。
6、浏览器和服务器关闭TCP连接
7、浏览器对页面进行渲染呈现给用户

HTTPS的请求过程:
1、客户端发送请求到服务端;
2、服务器返回证书和公钥;
3、客户端验证证书和公钥,如果有效,生成对称密钥并使用公钥加密发送到服务端;
4、服务端使用私钥解密报文,并使用收到的对称密钥加密报文,发送到客户端;
5、客户端使用对称密钥解密报文;
6、SSL加密建立


对称加密:加密和解密使用同一个密钥,速度快,但由于需要将密钥在网络传输,所以安全性不高。
非对称加密:加密和解密使用不同的秘钥,一把作为公开的公钥,另一把作为私钥。所以安全性更高,但加密与解密速度慢。
解决方法:传输数据用对称加密,并用非对称加密传输对称加密的密钥

HTTPS使用对称加密
非对称加密的加解密效率低,而 http 的应用场景中,端与端之间存在大量的交互,非对称加密的效率是无法接受的。
HTTPS采用的方法是使用非对称加密传输对称加密的秘钥,并用对称加密传输数据

2.3.2 HTTP和HTTPS的区别?

1、HTTP 的信息是明文传输,数据都是未加密的,安全性较差。
   HTTPS在HTTP的基础上加入了SSL加密协议,数据是加密的,安全性较好。 
2、HTTP 和 HTTPS建立连接的方式不同,且前者使用80端口,后者使用443端口。
3、HTTP 页面响应速度比 HTTPS 快,
   主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,  
   HTTPS除了 TCP 的三个包,还要加上 SSL 握手需要的 9 个包,所以一共是 12 个包。
4、使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。

2.3.3 HTTP 1.0,1.1,2.0,3.0的区别?

http 1.0: 使用短连接,每次发送请求都要重新建立tcp请求。
	并且下一个请求的发送必须要等到上一个请求返回后才会进行,
	否则后面的请求会阻塞等待。
http 1.1: 引入持久连接,TCP连接默认不关闭,
	在一个TCP连接里,客户端可以发送多个请求。
http 2.0: 引入服务器推送,允许服务器推送资源给客户端。
	使用头部压缩,双方各自维护一个header的索引表,使得不需要直接发送值,通过发送key缩减头部大小。
	多路复用,使得一个tcp连接能够处理多个http请求。实现上是从1.x的基于文本到基于二进制流,将 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装。
	帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流。
	流就是多个帧组成的数据流。
http 3.0: 放弃了tcp协议而是改用了谷歌的 QUIC协议,此协议是基于UDP的。
	UDP协议无需三次握手四次挥手,所以传输速率更高。

短链接
长连接
服务器推送、首部压缩、多路复用
基于QUIC协议实现HTTP(QUIC协议基于UDP实现)

2.3.4 HTTP 状态码,1~5开头各是什么作用?

1**: 信息,服务器收到请求,需要请求者继续执行操作
2**: 成功,操作被成功接收并处理
	200 响应成功
3**: 重定向,需要进一步的操作以完成请求
	301 永久重定向,资源URL已更新
	302 临时重定向,本次使用新URL
	304 原来缓存的文档可继续使用
4**: 客户端错误,请求包含语法错误或无法完成请求
	400 请求错误,如URL错误
	403 禁止请求,请求被服务器拒绝
	404 资源未找到
5**: 服务器错误,服务器在处理请求的过程中发生了错误
	500 服务器错误,服务器端执行请求时发生错误
	503 无服务,服务器超负荷或停机维护,无法处理请求

2.3.5 Get和Post方法有什么区别?

GET和POST是什么?HTTP协议中的两种发送请求的方法。

1、一般来说,GET是用于获取数据的,POST一般用于将数据发给服务器。
2、GET把参数包含在URL中,POST通过body传递参数。
3、发送时,GET方式产生一个TCP数据包,浏览器把header和data一并发送;
   POST方式产生两个TCP数据包。浏览器先发送header,服务器响应100,浏览器再发送data,服务器响应200。
   在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。
   而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。

大家一般认为的区别
1、Get方式对URL的长度有限制,而POST的请求信息放在body里,所以无长度限制
实际上RFC 2616 文档中对 URL 的长度并没有限制。
但是各大浏览器厂家在实现的时候限制了URL的长度。
比如IE对长度限制为2083;谷歌和火狐为4098B。
5、安全
一般认为POST比GET更安全,但实际上安全不安全取决于是基于HTTP还是HTTPS,
HTTP是明文的,所以本质来说,在HTTP下二者都不安全。

2.3.6 cookie和session的区别?cookie使用的场景?

cookie和session都是用来跟踪浏览器用户身份的会话方式。
1、cookie机制在客户端存储数据,而session机制在服务器端保存储数据
2、单个cookie保存的数据小于等于4KB,一个站点最多保存20个cookie;
   而session无限制,但是出于对服务器端的性能考虑,session内尽量不要存放过多的东西,并且要设置session删除机制;
3、cookie对客户端可见,是不安全的,session存储在服务器上,对客户端是透明的,不存在敏感信息泄露的危险,更安全;
4、session 的运行依赖 session id,而 session id 是存在 cookie 中的,
   如果浏览器禁用了 cookie ,session 会失效,
   此时可以通过在 url 中传递 session_id 使用 session

三 操作系统

3.1 进程和线程

3.1.1 进程线程协程的区别?为什么要有多线程? *

进程和线程区别
1、进程是操作系统资源分配的基本单位,线程是处理器任务调度和执行的基本单位。	
2、多个进程间不能共享资源,每个进程有自己的堆、栈等信息,
   而同一进程的各线程可以共享本进程的资源,如寄存器、堆、全局变量等。
3、线程的创建和上下文切换开销比进程小。
4、一个进程包含至少一个线程,也可以包含多个线程。
  多进程要比多线程健壮。
  各个进程是互相独立的,互不影响。
  同一进程的多个线程相互影响,一个线程崩溃可能整个进程都死掉。
  
线程和协程切换区别
1、线程切换是用户态-内核态-用户态,协程切换是用户态-用户态
2、协程是串行执行,大多是IO阻塞才让渡执行时间,不需要复杂的调度算法
	线程切换需要操作系统进程调度,根据优先级或者时间片轮转
3、协程切换只涉及基本的CPU上下文切换,
	所谓的 CPU 上下文,就是一堆寄存器,里面保存了CPU运行任务所需要的信息
	线程切换不仅涉及CPU上下文,还有线程私有的栈和寄存器等,


为什么要有多线程?
主要原因是进一步提高系统的并发性。
 1、多线程模型可以共享同一个进程的地址空间和数据
 2、线程的创建和切换的开销更小,当多个任务需要频繁切换时,这一特性很有用
 3、在IO密集型任务上效率更高
多线程可以进一步提高系统的并发性

多进程和多线程的选择? 更健壮 or 更高效
1、对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
2、要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。
	
进程的状态有哪几个?
创建状态、就绪态、运行态、阻塞态、终止状态

僵尸进程:子进程先结束,但父进程没有回收子进程的资源。僵尸进程不可用kill杀死。
孤儿进程:父进程先于子进程结束,子进程被 init.d 进程托管。
保护进程:在后台一直运行的执行某些特定任务的进程。



进程地址空间的划分?
1、.text: 代码段,保存程序运行时产生的指令,同时也保存只读数据
2、.data: 存放程序显式初始化的全局变量和静态变量
3、.bss: 存放未初始化的全局变量和静态变量
4、堆(Heap):动态内存区,用于存储生存期与函数调用无关的数据
5、内存映射段:栈与堆之间。内核通过这一段将文件的内容直接映射到内存,进程可以通过 mmap 系统调用请求这种映射。内存映射是一种方便高效的文件 I/O 方式,所以它也被用来加载动态链接库。Windows下是 .dll,Linux下是 .so
6、栈(Stack):存储局部,临时变量,在程序块开始时自动分配内存,结束时自动释放内存。位于用户空间的顶部,是一个可以动态增长和收缩的内存段落,由栈帧组成,进程每调用一次函数,都将为该函数分配一个栈帧,栈帧中保存了该函数的局部变量、参数值和返回值。栈帧会在函数返回时被清理掉。
7、命令行参数和环境变量,命令行参数如main函数传参,环境变量如搜索头文件、库文件时默认的路径


进程内存布局
kernal space(1G)  0 ~ 1G
				命令行参数和环境变量
				stack
				共享映射段 .so  .dll
user space 		heap
(3G)			.bss
(1G ~ 4G)		.data
				.text .rodata

3.1.2 线程什么是私有的, 什么是共用的?

私有
1、栈(局部变量,函数的参数)
2、寄存器 (执行流的基本数据)
3、线程局部存储,一旦一个全局变量被定义成线程局部存储,
	虽然该变量可以被所有线程访问,但该变量在每个线程中都有一个副本,
	一个线程对改变量的修改不会影响到其它线程。
	gcc中使用 _thread int x; 进行声明

公有
1、堆上的数据
2、全局变量
3、静态变量
4、程序代码

3.1.3 进程和线程调度

进程调度
1、先来先服务:先请求CPU的进程先分配到CPU。平均等待时间长。
2、时间片轮转调度算法:每个进程都以固定的时间片轮流占用CPU执行。
3、优先级调度算法:可抢占调度,在进程队列中选择优先级最高的来执行。
4、短作业优先:执行时间短的进程可抢占CPU,优先执行
5、高响应比优先调度算法:根据等待时间和执行时间计算响应比

线程调度
1、分时调度:所有线程轮流使用 CPU,平均分配每个线程占用 CPU 的时间。
2、抢占式调度:优先让优先级高的线程使用 CPU。

3.1.3 进程通信方式

1、管道 pipe
无名管道,只能父子进程间通信,且半双工,数据只能单向流动。
有名管道,也是半双工,但是允许无亲缘关系进程间的通信。
2、共享映射区 mmap
映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,
但多个进程都可以访问,因为是内存,所以可以使用指针操作,较为方便。
3、信号 signal
主要用于通知接收进程某个事件已经发生。
4、套接字 socket
可用于不同机器间的进程通信。
5、文件 file
6、消息队列 

3.2 内存管理

3.2.1 操作系统内存管理

虚拟内存是计算机系统内存管理的一种技术。
操作系统为了管理内存,给每个进程都分配独立的地址空间,
对32位的系统而言,这个空间的大小是4GB,
这4GB并不是实际的物理内存,因此有虚拟内存这一名称。
它通常被分隔成多个物理内存碎片,
还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

最大值不能超过当前硬盘的剩余空间值,
同时也不能超过32位操作系统的内存寻址范围——4GB。

3.2.2 逻辑地址到物理地址

物理地址:物理块号 + 块内偏移
逻辑地址:页号 + 页内偏移
每个物理块对应一页,逻辑地址通过页表实现与物理地址的转换

3.2.3 页面置换算法

局部页面置换算法:只置换本进程内的物理页面,
进程中一个页面进内存,就代表一个页面已经被替换出内存,
所以一个进程所占用的物理页面的总数是确定的。

全局页面置换算法:置换内存中所有可换出的物理页面,
即换进内存的是进程A的页面,换出内存的可能是进程B的页面,
所以一个进程在内存中占用的物理页面总数是可变的。

局部算法
1、随机替换算法:用软件或硬件随机数产生器确定替换的页面。 	
2、先进先出:先调入主存的页面先替换。 
3、LRU 最近最久未使用算法:缺页时,替换最长时间不用的页面。
   利用局部性原理,过去一段时间里不曾被访问过的页面,在最近的将来可能也不会再被访问。
4、LFU 最近最少使用算法:缺页时,替换访问次数最少的页面
5、最优算法:替换最长时间以后才使用的页面。这是理想化的算法,只能作为衡量其他各种算法优劣的标准。

BeLady现象:随着进程被分配的物理页越多,缺页次数反而增加的现象

全局算法
1、工作集置换算法:每次访问内存时,把不在工作集中的页面从内存中换出
工作集:一个进程当前正在使用的页面集合
驻留集:当前时刻进程实际驻留在内存当中的页面集合
2、缺页率置换算法:当缺页率高的时候则增加常驻集,以分配更多物理页面
				  当缺页率低的时候则减少常驻集,以减少分配给进程的物理页面
				  
页面抖动:
分配给一个进程的物理页面太少,不能包含整个工作集,进程经常出现缺页,
需要频繁的在内存和外存之间替换页面,这种情况称为页面抖动。

产生抖动的原因:
随着驻留内存的进程数目增加,
分配给每个进程的物理页面数不断减小,导致进程的缺页率上升。

所以操作系统要选择一个适当的进程数目和分配给进程的物理帧数,以便在并发水平和缺页率之间达到一个平衡。

3.2.4 多级页表

优势:
1.使用多级页表,页表可以在内存中离散存储。
2.在某种意义上节省页表内存空间。只存储进程实际使用的那些虚拟内存页面的页表

使用一级页表,需要连续的内存空间来存放所有的页表项。
多级页表可以通过只存储为进程实际使用的那些虚拟地址内存区请求页表来减少内存使用量。

劣势:
1、增加寻址次数,从而延长访存时间。
如二级页表第一次访问页目录项,第二次访问页表项,第三次访问要读取的页表。

3.3 中断处理

3.3.1 用户态和内核态

内核态:运行操作系统程序,操作硬件
用户态:运行用户程序

内核态与用户态是操作系统的两种运行级别。
Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低。
当程序运行在3级特权级上时,就可以称之为运行在用户态。
当程序运行在0级特权级上时,就可以称之为运行在内核态。	

内核态和用户态最重要的差别就在于特权级的不同,即权力的不同。
运行在用户态下的程序不能直接访问操作系统内核数据结构和程序,所能访问的内存空间和对象受到限制,且其所处于占有的处理器是可被抢占的
处于内核态执行时,则能访问所有的内存空间和对象,且所占有的处理器是不允许被抢占的。


用户态切换到内核态的三种方式
1、系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作
2、异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
3、外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,
这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,
如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。
比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。	 

3.3.2 中断处理

1、中断请求阶段
2、中断判优阶段
CPU一次只能接受一个中断源的请求,当多个中断源同时向CPU提出中断请求时,CPU必须找出中断优先级最高的中断源,这一过程称为中断判优。有硬件判优和软件判优两种方法;
3、中断响应阶段
中断响应时,CPU向中断源发出中断响应信号,同时进行以下工作:
	1)保护硬件现场;
	2)关中断;
	3)保护断点;
	4)获得中断服务程序的入口地址。
4、中断服务阶段
	1)保护现场。 在中断服务程序的起始部分安排若干条入栈指令,将各寄存器的内容压入堆栈保存。
	2)开中断。 在中断服务程序执行期间允许级别更高的中断请求中断现 行的中断服务程序,实现中断嵌套。
	3)中断服务。 完成中断源的具体要求。
	4)恢复现场。 中断服务程序结束前,必须恢复主程序的中断现场。通常是将保存在堆栈中的现场信息弹出到原来的寄存器中。
	5)中断返回。 返回到原程序的断点处,继续执行原程序。
5、中断返回阶段
返回到原程序的断点处,恢复硬件现场,继续执行原程序

四 分布式

4.1 Raft

leader选举
raft将时间分为若干个不等长的任期,在每一个任期内至多存在一个leader,
leader负责接收client的请求并在日志中添加新日志项并同步给其他的follower。
当follower发现leader可能断线时,也就是一段时间内没有收到leader的心跳请求时,
follower会转变为candidate,向其他所有server发起拉票请求,竞选下一轮的leader。
当一个candidate收到大多数server的选票时,candidate当选为leader,
在新的一轮中发起心跳请求,组织分布式系统的工作。


follower的逻辑时钟到期后转换为Candidate,这时Term加1。
follower只能向大于自己term的candidate投票,
在term相等的情况下,选择记录了最新log的candidate


日志同步
当leader添加一个新日志项后,
leader会在appendEntry心跳请求中通知各个follower添加该日志项。
等待大多数节点写入日志并响应Leader之后,
leader提交该新日志项,并且也通知follower提交该新日志项。


当follower发现和leader的日志项不一致时怎么办?
leader对整个系统中的日志拥有绝对掌控权,当follower发现和leader的日志不一致时,
以leader的日志为准,即follower会从不一致的位置截断日志,
并追加leader发送过来的日志项。

如何保证已提交的日志项不会被覆盖?
首先规定,leader不会删除和覆盖自己的日志项。
其次,只有拥有最新日志项的candidate可以当选为leader。
前一项规定保证了leader不会修改已提交的日志项。
后一项规定保证了leader必定拥有已提交的日志项。
从而保证follower的已提交日志项也不会被修改。

怎么保证拥有最新日志项的candidate当选为leader?
candidate只有收到超过一半的投票才能当选,
而一个新日志一定被超过一般的follower保存了,
保证投票者中一定有人拥有最新的日志信息,
它投票给candidate,则当选的candidate一定拥有最新的日志信息
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值