个人技术栈准备面试题
索引sql优化
最左前缀法则,如果创建是联合索引,就要遵守该法则,使用索引时,where后面需要从索引最左列开始使用,不能跳过索引列使用。
不要在索引列上做任何计算,字符串不加单引号,需要进行隐式转换转向全表扫描。
范围查询之后全失效
避免使用is null is not null ,!= <> or
like 以% 开头会使索引失效,两边都有%索引也会失效。
like失效原理,由于b+树索引顺序按照首字母大小进行排序,%在右匹配又是首字母可以在b树进行有序查找,符合要求。
%在左是匹配尾部的数据,尾部的字母是没有顺序的,所以不能按照索引顺序查找,所以用不到索引。
%%是查询任意位置字母满足条件即可,只有首字母是进行索引排序的,其他位置的字母相对无序的,查找任意位置就用不到索引。
数据库死锁
表死锁:
用户a访问表a锁了表a,又访问表b,另一个用户访问表b锁了表b,又去访问表a,a等b释放锁,b等a释放锁。
是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序
的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操
作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任何时刻都应该按
照相同的顺序来锁定资源。
行死锁:
如果在事务中执行了一条没有索引条件的查询,引发全表扫描,把行级锁上升为全表记录锁定(等价于
表级锁),多个这样的事务执行后,就很容易产生死锁和阻塞,最终应用系统会越来越慢,发生阻塞或
死锁。
解决方案1:
SQL语句中不要使用太复杂的关联多表的查询;使用explain“执行计划"对SQL语句进行分析,对于有全
表扫描和全表锁定的SQL语句,建立相应的索引进行优化。
产生原因2:
两个事务分别想拿到对方持有的锁,互相等待,于是产生死锁。、
每个事务只有一个SQL,但是有些情况还是会发生死锁.比如两个session操作同一个表的同一个id,一个查询修改,一个删除,同时占用对方的id
但是加锁时发现跟事务1的加锁顺序正好相反,两个Session恰好都持有了第一把锁,请求加第二
把锁,死锁就发生了。
对索引加锁顺序的不一致很可能会导致死锁,所以如果可以,尽量
以相同的顺序来访问索引记录和表。在程序以批量方式处理数据的时候,如果事先对数据排序,保证每
个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能;
死锁总结
1. 对索引加锁顺序的不一致很可能会导致死锁, 所以如果可以, 尽量以相同的顺序来访问索引记录和
表. 在程序以批量方式处理数据的时候, 如果事先对数据排序, 保证每个线程按固定的顺序来处理记
录, 也可以大大降低出现死锁的可能.
2. 间隙锁往往是程序中导致死锁的真凶, 由于默认情况下 MySQL 的隔离级别是 RR,所以如果能确定
幻读和不可重复读对应用的影响不大, 可以考虑将隔离级别改成 RC, 可以避免 Gap 锁导致的死锁.
3. 为表添加合理的索引, 如果不走索引将会为表的每一行记录加锁, 死锁的概率就会大大增大.
4. 避免大事务, 尽量将大事务拆成多个小事务来处理. 因为大事务占用资源多, 耗时长, 与其他事务冲
突的概率也会变高.
5. 避免在同一时间点运行多个对同一表进行读写的脚本, 特别注意加锁且操作数据量比较大的语句.
6. 设置锁等待超时参数:innodb_lock_wait_timeout,在并发访问比较高的情况下,如果大量事务
因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。
我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。
数据库的锁种类
读写锁:
读写锁就是进一步细化锁的颗粒度,区分读操作和写操作,让读和读之间不加锁,这样下面的两个事务
就可以同时被执行了。
读写锁,可以让读和读并行,而读和写、写和读、写和写这几种之间还是要加排他锁。
读写锁:
读和写操作:读读、写写、读写、写读。
共享锁,又称之为读锁,简称S锁,当事务对数据加上读锁后,其他事务只能对该数据加读锁,不能做任何修改操作,也就是不能添加写锁。只有当数据上的读锁被释放后,其他事务才能对其添加写锁。共享锁主要是为了支持并发的读取数据而出现的,读取数据时,不允许其他事务对当前数据进行修改操作,从而避免”不可重读”的问题的出现。
排它锁,又称之为写锁,简称X锁,当事务对数据加上写锁后,其他事务既不能对该数据添加读写,也不能对该数据添加写锁,写锁与其他锁都是互斥的。只有当前数据写锁被释放后,其他事务才能对其添加写锁或者是读锁。写锁主要是为了解决在修改数据时,不允许其他事务对当前数据进行修改和读取操作,从而可以有效避免”脏读”问题的产生。
全局锁是对整个数据库实例加锁,添加全局锁后,以下语句会被阻塞:数据更新语句(增删改)、
数据定义语句(建表、修改表结构等)和更新类事务的提交语句。
表级锁:表级锁是mysql中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分mysql引擎支持。
行级锁. 行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。.
行级锁能大大减少数据库操作的冲突。. 其加锁粒度最小,但加锁的开销也最大。. 行级锁分为 共享锁 和 排他锁 。.
页级锁:每次锁定相邻的一组记录,锁定粒度界于表锁和行锁之间,开销和加锁时间界于表锁和行
锁之间,并发度一般。
乐观锁:一般的实现方式是对记录数据版本进行比对,在数据更新提交的时候才会进行冲突检测,
如果发现冲突了,则提示错误信息。
悲观锁:在对一条数据修改的时候,为了避免同时被其他人修改,在修改数据之前先锁定,再修改
的控制方式。共享锁和排他锁是悲观锁的不同实现,但都属于悲观锁范畴。
乐观锁实现原理
使用版本字段(version)
先给数据表增加一个版本(version) 字段,每操作一次,将那条记录的版本号加 1。version
是用来查看被读的记录有无变化,作用是防止记录在业务处理期间被其他事务修改。
使用时间戳(Timestamp)
与使用version版本字段相似,同样需要给在数据表增加一个字段,字段类型使用timestamp时
间戳.也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行
对比,如果一致则提交更新,否则就是版本冲突,取消操作。
SQL优化思路
慢查询SQL优化思路,当我们拿到sql之后可以进行以下分析:
等待时间长,锁表导致查询一直处于等待状态
执行时间长
1.查询sql写的烂
2.索引失效
3.关联查询太多的join
4.服务器调优以及各个参数的设置
优化思路,优先选择优化高并发的sql,因为并发高的sql带来的后果更加严重
然后从explain执行计划入手
1. 用小结果集驱动大的结果集
2.尽可能在索引中完成排序
3.只获取自己需要的列
4.只是用有效的过滤条件
5.避免复杂的join和子查询
6.合理设计并利用索引
如果子查询得出的结果集记录较少,主查询中的表较大且又有索引时应该用 in
如果主查询得出的结果集记录较少,子查询中的表较大且又有索引时应该用 exists
一句话: in后面跟的是小表,exists后面跟的是大表。
索引排序: 通过有序索引顺序扫描直接返回有序数据
额外排序: 对返回的数据进行文件排序
ORDER BY优化的核心原则: 尽量减少额外的排序,通过索引直接返回有序数据。
ORDER BY排序字段要么全部正序排序,要么全部倒序排序,否则无法利用索引排序。
说说数据库事务
说说数据库事务
说说数据库事务
acid
原子性,一致性,隔离性,持久性
原子性,事务是一个原子性操作,对修改的数据要么全部执行,要么全都不执行。
事务提交之后redo log刷入磁盘,挂机后根据redo log回复事务修改的缓存数据。
如果要回滚事务,根据undo log回滚。
一致性:事务开始前和事务开始之后,数据的完整性未被破坏,包括约束一致性和数据一致性。
隔离性:事务执行不能被其他事务干扰,事务内操作以及使用对其他的事务是隔离的。
不考虑隔离性会引发,脏读,一个事务读取到另一个事务修改但未提交的数据。
不可重复度,一个事务中多次读取同一行记录的结果不一致。
幻读:幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。
innodb支持的隔离有四种:读未提交,读提交,可重复读,串行化,锁和多版本控制。
持久性:指的是一个事务一旦提交,它对数据库中数据的改变就应该是永久性的,后续的操作或故障不
应该对其有任何影响,不会丢失。
MySQL持久性的保证依赖两个日志文件: redo log 和 binlog
Explain性能分析
通过explain我们可以获得以下信息:
表的读取顺序。(对应id)
数据读取操作的操作类型。(对应select_type)
哪些索引可以使用。(对应possible_keys)
哪些索引被实际使用。(对应key)
表直接的引用。(对应ref)
每张表有多少行被优化器查询。(对应rows)
2) ID字段说明
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
id相同,执行顺序由上至下
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
3) select_type和table字段说明
3) select_type和table字段说明
表示查询类型,主要用于区别普通查询,联合查询,子查询等的复杂查询
union : union连接的两个select查询,第一个查询是dervied派生表,除了第一个表外,第二个
以后的表select_type都是union
derived : 在from列表中包含的子查询被标记为derived(派生表),MySQL会递归执行这些子
查询,把结果放到临时表中
union result : UNION 的结果
4) type字段说明
system: 表中就仅有一行数据的时候. 这是const连接类型的一个特例,很少出现。
const: const表示命中主键索引(primary key) 或者唯一索引(unique),表示通过索引一次就找到数
据记录.
eq_ref : 对于前一个表中的每个一行,后表只有一行被扫描。除了system和const类型之外,这是
最好的连接类型。只有当联接使用索引的部分都是主键或惟一非空索引时,才会出现这种类型。
ref : 非唯一性索引扫描(使用了普通索引), 对于前表的每一行(row),后表可能有多于一行的数据被
扫描,它返回所有匹配某个单独值的行.
range : 索引上的范围查询,检索给定范围的行,between,in函数,> 都是典型的范围(range)查询,
例如以下查询:
index : 出现index 是 SQL 使用了索引, 但是没有通过索引进行过滤,需要扫描索引上的全部数据 (
查找所有索引树,比ALL快一些,因为索引文件要比数据文件小 ), 一般是使用了索引进行排序分组.
ALL : 没有使用到任何索引, 连接查询时对于前表的每一行,后表都要被全表扫描。
5) possible_keys 与 key说明
possible_keys
显示可能应用到这张表上的索引, 一个或者多个. 查询涉及到的字段上若存在索引, 则该索引将被列
出, 但不一定被查询实际使用.
key
实际使用的索引,若为null,则没有使用到索引。(两种可能,1.没建立索引, 2.建立索引,但索引
失效)。查询中若使用了覆盖索引,则该索引仅出现在key列表中。
6) key_len字段说明
表示索引中使用的字节数, 可以通过该列计算查询中使用索引的长度.
key_len 字段能够帮你检查是否充分利用了索引 ken_len 越长, 说明索引使用的越充分
7) ref 字段说明
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值
8) rows 字段说明
表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
9) filtered 字段说明
它指返回结果的行占需要读到的行(rows列的值)的百分比
10) extra 字段说明
Extra 是 EXPLAIN 输出中另外一个很重要的列,该列显示MySQL在查询过程中的一些详细信息.
Using filesort
执行结果Extra为 Using filesort ,这说明,得到所需结果集,需要对所有记录进行文件排序。
这类SQL语句性能极差,需要进行优化。
Using temporary
表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询
Using where
意味着全表扫描或者在查找使用索引的情况下,但是还有查询条件不在索引字段当中.
需要注意的是:
1. 返回所有记录的SQL,不使用where条件过滤数据,大概率不符合预期,对于这类SQL往往需
要进行优化;
2. 使用了where条件的SQL,并不代表不需要优化,往往需要配合explain结果中的type(连接
类型)来综合判断。例如本例查询的 age 未设置索引,所以返回的type为ALL,仍有优化空
间,可以建立索引优化查询。
Using index
表示直接访问索引就能够获取到所需要的数据(覆盖索引) , 不需要通过索引回表.
Using join buffer
使用了连接缓存, 会显示join连接查询时,MySQL选择的查询算法 .
Using index condition
查找使用了索引 (但是只使用了一部分,一般是指联合索引),但是需要回表查询数.
Extra主要指标的含义(有时会同时出现)
using index :使用覆盖索引的时候就会出现
using where :在查找使用索引的情况下,需要回表去查询所需的数据
using index condition :查找使用了索引,但是需要回表查询数据
using index & using where :查找使用了索引,但是需要的数据都在索引列中能找到,所以
不需要回表查询数据
mysql的索引
索引会存储在数据文件中,可以加快检索速度,同时会降低增删改的效率,维护需要代价。
MySQL中索引的常用数据结构有两种: 一种是BTree,另一种则是Hash.
hash是用hash表来实现,适合等值,对每行数据计算一个hash码,如果相同会拉取一条链表,等值查询效率高,不适合范围查找和排序。
btree索引,分为聚簇索引和非聚簇索引。
聚簇索引是数据和索引放在一块。
非聚簇索引是数据和索引分开存储,索引结构的叶子节点指向数据行的位置。
InnoDB的表要求必须要有主键索引:
如果表定义了主键,则主键索引就是聚簇索引
如果表没有定义主键,那么 InnoDB 会使用第一个唯一索引作为聚簇索引
否则InnoDB会创建一个隐藏的row-id作为聚簇索引,使用主键索引最好使用int类型的自增,对树的影响最小,主键占用空间越大辅助索引保存的主键索引也会跟着变大,占用存储空间。
不建议使用uuid,因为uuid的值太过离散,不适合排序且可能会插入在索引树中间的位置。
二级索引:叶子节点中存储主键值,每次查找数据时,根据索引找到叶子节点中的主键值,根据主键值再到聚簇索引中得到完整的一行记录。
一个表InnoDB只能创建一个聚簇索引,但可以创建多个辅助索引。
常用的二级索引包括:
唯一索引(Unique Key) :唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。
普通索引(Index) :普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。
前缀索引(Prefix) :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引。
组合索引:将多个列作为一个索引,最左前缀原则进行匹配、
回表: 先根据普通索引查询到主键值,再根据主键值在聚集索引中获取行记录,这就是回表.
什么是覆盖索引:如果一个索引包含了所有需要查询的字段的值 ,不需要查询聚集索引,这个索引就是覆盖索引。
mysql的数据结构
MySQL默认使用B+树结构管理索引,B+树中的B代表平衡(balance ),在讲B+树之前必须先了解二叉树、
平衡二叉树(AVL) 和 B-Tree,因为B+Tree即由这些树逐步优化而来。
二叉查找树的缺点:
二叉查找树是要求左低右高,如果数据一个比一个大,就会形成单向链表,层数过深,查找效率过慢。
平衡二叉树:在符合二叉查找树的条件下还满足任何节点的两个子树的高度最大差为1,通过旋转的方式保证两个子树的平衡。
优点:叶子节点层级减少了,形态上保持平衡,查询效率提升了,大量的顺序插入也不会导致性能降低
缺点:
一个节点最多分裂2个子节点,,数据量大的时候数的高度太高,导致io次数过多。
节点保存着一个关键字,每次操作获取目标数据太少。
b+树是由二叉查找树演变而来的,b树第一层是根节点,第二层是分支节点,第三层是叶子节点,减少树的层级深度,允许一个节点存放多个数据
键值对[key, data] ,key为记录的键值,对应表中的主键值(聚
簇索引),data为一行记录中除主键外的数据。
优点: B树可以在内部节点存储键值和相关记录数据,因此把频繁访问的数据放在靠近根节点的位
置将大大提高热点数据的查询效率。
缺点: B树中每个节点不仅包含数据的key值,还有data数据. 所以当data数据较大时,会导致每个节点
存储的key值减少,并且导致B树的层数变高.增加查询时的IO次数.
b+树
非叶子节点只存储键值信息.
所有叶子节点之间都有一个链指针.
数据记录都存放在叶子节点中.
1.B+Tree是B Tree的变种,B Tree能解决的问题,B+Tree也能够解决
2.B+Tree排序能力更强,B+Tree天然具有排序功能。
3.B+Tree查询效率更加稳定,每次查询数据,查询IO次数一定是稳定的。
4.+Tree磁盘读写能力更强,他的根节点和支节点不保存数据区,所有根节点和支节点同样大小的
情况下,保存的关键字要比B Tree要多。
5.B+Tree扫库和扫表能力更强,如果我们要根据索引去进行数据表的扫描,对B Tree进行扫描,需
要把整棵树遍历一遍,而B+Tree只需要遍历他的所有叶子节点即可
MVCC多版本并发控制
mvcc为了解决数据库并发访问实现的事务内存,做到即使有冲突时也不加锁,非阻塞并发读。
维持了一个数据的多个版本,使得读写操作没有冲突,实现原理就是由 undo日志,readview,
innodb 行记录会记录一些重要字段,行版本号,事务id,回滚指针指向undo log的信息。
行版本号:每一行数据在进行修改时,都会生成一个新的版本,并将该版本与修改事务的事务ID关联起来。这样的版本号通常使用隐藏的额外列或者额外的存储空间来保存。
当事务在开始执行的时候,会给每个事务生成一个 ReadView。这个 ReadView 会记录 4 个非常重要的属性:
当前事务id,
最大事务id(下一个事务id),
最小事务id,
当前系统所有还没有提交的事务id。
MVCC(多版本并发控制)是一种实现数据库隔离级别的机制, MVCC需要至少使用RC级别以上的隔离级别来避免脏读和不可重复读等问题。
RR级别(Repeatable Read):
在RR级别下,每个事务在开始时会创建一个独立的Read View,该事务将会看到启动时刻已经存在的所有行的一个一致版本。这意味着在事务执行期间,其他事务对数据的修改不会被该事务看到。其他事务对于已修改但未提交的数据也不可见。
RC级别(Read Committed):
在RC级别下,每个事务在开始时同样会创建一个独立的Read View,但它只能看到已提交的数据。与RR级别不同的是,如果其他事务对数据进行修改并提交后,该事务可以看到修改后的数据。但RC级别下仍然可以发生非重复读(non-repeatable read)问题。
缓存雪崩
缓存雪崩是指缓存中有大批数据过期了,导致数据库压力过大甚至宕机。
解决方案:
缓存的过期时间设置随机值,避免同一时间都过期。
热点数据不设置失效时间。
缓存穿透
用户不断访问并不存在的数据,数据库和缓存中都没有,这些用户可能是一些攻击者,对数据库造成巨大压力
解决方案:
布隆过滤器:
将所有可能不存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免对底层存储系统的查询压力。
布隆过滤器原理是什么?
bitmap+哈希函数
布隆过滤器的缺点是什么
缺点不能删除元素
布隆过滤器的替代品是什么?
布谷鸟过滤器
将key的vlue作为null:从缓存取不到数据,数据库也没取到,这样可以将key-value做写入,key-null,设置有效时间为30秒左右,可以防止攻击用户反复用一个id暴力攻击。
缓存击穿
缓存击穿一般都是缓存中没有但是数据库有,一般都是缓存过期了,由于并发用户特别多,又没读到数据,去读数据,大量数据读取数据库导致数据库有巨大的压力。
解决方案:
设置热点数据永不过期
使用互斥锁
内存管理策略
redis中内存超过设置maxmemory-policy的限制时执行的LRU算法
从设置过期的时间key集合中删除最近没有被使用的key
通过lru算法删除最近没有在使用的key
从设置过期时间的key集合中删除最近使用频率最少的key
在所有key的集合中,删除最近使用频率最少的key
在设置过期时间的key集合中随机删除一个key
在所有key集合中随机删除一个key
在设置过期时间的key集合中删除即将过期的key
超过最大容量时不会删除任何key,返回一个错误(默认)
Redis的过期键的删除策略
过期策略通常有以下三种:
定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
Redis中同时使用了惰性过期和定期过期两种过期策略。
Redis 的持久化机制是什么?各自的优缺点?
Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制:
RDB:是Redis DataBase缩写快照
RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。
优点:
1、只有一个文件 dump.rdb,方便持久化。
2、容灾性好,一个文件可以保存到安全的磁盘。
3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
4.相对于数据集大时,比 AOF 的启动效率更高。
缺点:
1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
2、AOF(Append-only file)持久化方式: 是指所有的命令行记录以 redis 命令请 求协议的格式完全持久化存储)保存为 aof 文件。
AOF:持久化
AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
优点:
1、数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次 命令操作就记录到 aof 文件中一次。
2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))
缺点:
1、AOF 文件比 RDB 文件大,且恢复速度慢。
2、数据集大的时候,比 rdb 启动效率低。
优缺点是什么?
AOF文件比RDB更新频率高,优先使用AOF还原数据。
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了优先加载AOF
Redis线程模型
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。
文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。
springboot的自动装配
Spring Boot 的自动装配(Auto Configuration)是 Spring Boot 提供的一种机制,通过在类路径下扫描和检测相关的依赖,自动配置和装配 Spring 应用程序的各种组件和功能。
自动装配的原理是基于条件注解和条件匹配。Spring Boot 在启动时会根据一系列的条件来判断是否需要自动配置某个组件或功能,如果满足条件,则自动配置相应的 Bean 对象。这样可以大大减少开发者的工作量,让开发者只关注核心业务逻辑的实现。
Spring Boot 自动装配的具体步骤如下:
Spring Boot 在启动时,会扫描 classpath 下的 META-INF/spring.factories 文件,该文件中配置了各个 Starter 的自动配置类。
Spring Boot 根据 Starter 的引入情况,选择相应的自动配置类进行自动配置。
自动配置类中使用了一系列的条件注解,如@ConditionalOnClass、@ConditionalOnProperty等,根据条件判断是否需要进行自动配置。
如果条件满足,Spring Boot 会根据自动配置类中的逻辑,创建并注册相应的 Bean 对象。
Spring Boot 的自动装配功能非常强大,常见的示例包括自动配置数据库连接池、Web MVC 配置、日志配置等。当然,如果开发者需要自定义配置,可以通过编写自己的配置类或在 application.properties/application.yml 文件中进行配置覆盖。
spring的三级缓存
在Spring框架中,有一个对象实例化的缓存机制,称为"三级缓存"(three-level cache)。这个缓存机制用于管理Spring容器中的单例Bean对象。
在Spring的三级缓存中,存在以下三个级别:
1. `singletonObjects` 级别缓存:这是最高级别的缓存,用于存储完全初始化并准备好的单例Bean对象。当Spring容器需要获取某个单例Bean时,首先会尝试从这个缓存级别中获取。如果对象已经存在,则直接返回,否则继续创建新的对象。
2. `earlySingletonObjects` 级别缓存:如果某个Bean正在创建过程中,但还未完成初始化,那么它会被放入这个缓存级别。当其他Bean依赖于正在创建的Bean时,Spring会从这个缓存级别中获取正在创建的Bean的代理对象。这样可以避免循环依赖问题。
3. `singletonFactories` 级别缓存:如果某个Bean正在创建过程中,并且已经完成了实例化和部分初始化,但尚未完成全部初始化(例如,还未执行所有的属性注入),那么它会被放入这个缓存级别。类似于`earlySingletonObjects`级别,当其他Bean依赖于正在创建的Bean时,Spring会从这个缓存级别中获取正在创建的Bean的代理对象。
这个三级缓存机制在Spring容器中起到了重要的作用,可以解决循环依赖等问题。在Bean的完整生命周期中,它会从`singletonFactories`级别逐渐升级到`earlySingletonObjects`级别,最终达到`singletonObjects`级别。
bean的创建
基于XML配置:使用XML配置文件来定义Bean。在配置文件中使用<bean>元素来描述Bean的信息,包括类名、属性值、依赖关系等。容器在初始化时解析配置文件,并根据配置创建和管理Bean对象。
基于注解:使用注解来标记Bean。通过在Bean的类上添加特定的注解,如@Component、@Service、@Repository等,告诉容器需要将该类实例化为Bean。容器在扫描到带有注解的类时,会自动创建相应的Bean对象。
基于Java配置:使用Java类来配置Bean。通过编写一个配置类,使用特定的注解(如@Configuration、@Bean)来描述Bean的创建逻辑。容器在初始化时读取配置类,并根据配置类中的定义来创建和管理Bean对象。
使用工厂方法:使用工厂方法来创建Bean。通过在配置文件或配置类中定义一个工厂方法,并在方法中创建并返回Bean对象。容器在需要创建Bean时,会调用工厂方法来获取Bean对象。
使用外部资源:通过配置文件或其他方式将Bean的创建交给外部资源,例如使用JNDI或Java EE容器提供的资源。在此情况下,容器会从外部资源中获取Bean对象。
多线程中 synchronized 锁升级的原理
偏向锁:在锁对象的对象头中记录一下获取到该锁的线程id,该线程下次如果又来获取锁就可以直接获取到了。
轻量级锁:一个线程获取锁后,如果这个锁是偏向锁,有第二个线程竞争就会升级为轻量级锁。
重量级锁:轻量级锁是通过自旋来完成的不会阻塞线程,如果自旋多次没有获取到锁,就会升级重量级锁,会阻塞线程
锁粗化:如果JVM检测到有一连串操作都是对同一对象的加锁,将会扩大加锁同步的范围
JVM的内存模型
程序计数器(Program Counter):程序计数器是每个线程私有的,它记录了当前线程执行的字节码指令的地址或索引。在线程切换或异常处理时,程序计数器会被保存和恢复。
Java堆(Java Heap):Java堆是被所有线程共享的内存区域,用于存储对象实例和数组。它是垃圾回收器管理的主要区域,包括新生代和老年代等不同的内存区域。
方法区(Method Area):方法区也被所有线程共享,用于存储类的信息、常量、静态变量、即时编译器编译后的代码等。在HotSpot JVM中,方法区被称为"永久代"(Permanent Generation),但在Java 8及之后的版本中被元空间(Metaspace)所取代。
运行时常量池(Runtime Constant Pool):运行时常量池是每个类或接口的常量池的运行时表示形式。它包含了类加载过程中解析的符号引用、字符串常量以及直接引用的对象等。
Java栈(Java Stack):每个线程都有自己的Java栈,用于存储局部变量、方法参数、返回值和操作数栈等。每个方法执行时,都会创建一个栈帧(Stack Frame),栈帧包含了方法的局部变量表、操作数栈和动态链接信息等。
本地方法栈(Native Method Stack):与Java栈类似,本地方法栈用于执行本地方法(即用其他语言编写的方法)。
程序内存模型(Program Memory Model):程序内存模型规定了线程之间如何共享数据和进行同步。它定义了内存屏障、原子性操作、可见性和顺序性等规则,保证多线程程序的正确性和一致性。
谈谈你对volatile的理解?
线程会对volatile修饰的变量,执行写操作时会立刻把写入的值刷新到主存内,同时副本失效,同步数据
保证有序性,增加屏障防止指令重排序。
不保证原子性,变量被修饰了每次读取的时候是最新的,但是自增这样非原子性操作不保证原子性。
可见性,happen-before原则。ab线程执行ahappen-before b 保证a操作后,a对b可见,执行写操作的时候jvm会给cpu发送lock前缀指令,cpu会立即将这个值写到主存,使得其他cpu缓存的副本生效,触发mesi协议。
CAS
CAS(Compare and Swap)是一种原子操作,常用于多线程编程和并发控制中,用于实现无锁的同步操作。CAS操作可以解决多个线程对同一个共享变量进行并发修改时可能引发的数据竞争和不一致性的问题。
:共享变量的内存地址、旧值和新值
CAS操作是一个原子操作,即在执行过程中不会被其他线程的干扰。如果多个线程同时执行CAS操作,只有其中一个线程会成功,其他线程会重新尝试CAS操作直到成功或达到某个限定次数。
线程池,创建方式
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。Java 5+中的 Executor 接口定义一个执行线程的工具。它的子类型即线程池接口是 ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类 Executors 面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:
(1)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
(2)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。如果希望在服务器上使用线程池,建议使用 newFixedThreadPool方法来创建线程池,这样能获得更好的性能。
(3) newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
(4)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
线程池的核心参数
时间单位,线程工厂
线程池的核心参数包括以下几个:
1. 核心线程数(corePoolSize):线程池中保持活动状态的最小线程数量。即使线程没有任务可以执行,核心线程也不会被回收。当提交任务时,如果线程池中的线程数少于核心线程数,就会创建一个新的线程来执行任务。
2. 最大线程数(maximumPoolSize):线程池中允许存在的最大线程数量。当提交的任务多于核心线程数并且任务队列已满时,线程池会创建新的线程,直到达到最大线程数。超过最大线程数的任务可能会被拒绝执行。
3. 任务队列(workQueue):用于存储待执行的任务的队列。线程池会按顺序从队列中取出任务进行执行。常见的任务队列类型包括有界队列(如ArrayBlockingQueue)、无界队列(如LinkedBlockingQueue)和优先级队列(如PriorityBlockingQueue)等。
4. 空闲线程存活时间(keepAliveTime):当线程池中的线程数超过核心线程数,并且空闲时间达到指定时间时,多余的线程会被回收销毁,保持线程池的大小不超过核心线程数。
5. 拒绝策略(RejectedExecutionHandler):当线程池无法接受新的任务时的处理策略。常见的拒绝策略包括直接丢弃任务、抛出异常、丢弃队列中最老的任务和由调用者自己执行任务等。
拒绝策略有哪些
AbortPolicy(默认策略):当线程池无法接受新的任务时,会抛出一个RejectedExecutionException异常。
CallerRunsPolicy:当线程池无法接受新的任务时,会由调用线程来执行该任务。也就是说,如果线程池已满,任务会在提交者的线程中执行。
DiscardPolicy:当线程池无法接受新的任务时,直接丢弃该任务,不做任何处理。
DiscardOldestPolicy:当线程池无法接受新的任务时,会丢弃队列中最老的一个任务,然后尝试将新的任务添加到队列中。
除了上述常用的拒绝策略,还可以通过实现RejectedExecutionHandler接口自定义拒绝策略,根据具体需求进行处理。自定义的拒绝策略可以包括将任务丢入其他队列、记录日志、发送消息等操作。
AQS
AQS(AbstractQueuedSynchronizer)是Java中的一个抽象类,它提供了一种实现同步器(synchronizer)的框架,用于构建基于锁或者其他同步机制的组件。
1.aqs核心是state,int类型表示加锁状态,初始值0
2.aqs内部还有变量,用来记录加锁的是哪个线程,初始化状态,null
3.线程1跑过来调用reentrantlock的lock方法尝试加锁,这个加锁的过程,就是cas操作state的值从0编程1,设置当前加锁的线程是1
4.线程2一看state的值不是0,说明有人加过锁了,线程看是不是自己加的锁,不是加锁失败,会将自己放入等待队列中,如果是自己之前加的锁,将aqs里面的state加1.
5.线程1执行完自己的业务逻辑代码后,就会释放锁,将aqs的state减一,如果state是0,就彻底释放锁,将加锁线程变量也设置null,等待队列其他线程尝试加锁。
---附录--
事务执行没有索引
当在一个事务中执行一条没有索引条件的查询时,数据库引擎可能会选择执行全表扫描,从而锁定整张表的记录,而不仅仅是查询涉及的行。这意味着其他并发事务无法对该表进行更新操作,直到当前事务提交或回滚。
假设有一个包含大量数据的表 orders,其中没有为列 customer_id 创建索引:
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
...
);
在一个事务中执行以下查询语句,没有使用索引条件:
BEGIN TRANSACTION;
SELECT * FROM orders WHERE customer_id = 123;
COMMIT;
由于没有针对 customer_id 列的索引,数据库引擎可能会选择执行全表扫描来找到所有匹配的行。在执行全表扫描期间,会锁定整个 orders 表的记录,以防止其他事务对表进行修改。
这意味着其他并发事务无法在此期间向 orders 表插入、更新或删除任何记录,因为表级记录锁定会阻止这些操作。只有在当前事务提交或回滚后,其他事务才能继续对表进行正常的修改操作。
需要注意的是,具体的锁定行为取决于使用的数据库管理系统和其事务隔离级别设置。不同的数据库系统和配置可能会有所不同,因此在实际应用开发中,请根据所使用的数据库系统和需求进行详细的测试和调优。
间隙锁(Gap Lock)是一种用于防止幻读问题的锁机制
间隙锁在索引范围内的"空隙"(即两个索引键之间的区域)上进行加锁,以防止其他事务在该范围内插入新的记录。这可以防止幻读,即一个事务在两次读取之间有其他事务插入了新的记录,导致第二次读取结果不一致。
然而,由于间隙锁的特性,当多个事务同时操作相同的索引范围时,可能会导致死锁的发生。这种情况通常发生在以下场景中:
- 交错的间隙锁:如果两个事务同时请求对相邻的间隙进行插入或更新操作,它们可能会形成一个交错的锁序列,导致死锁。
- 范围锁定扫描:如果一个事务执行了一个范围锁定扫描操作,而另一个事务在同一个范围内进行了插入或更新操作,它们也可能发生死锁。
要解决这个问题,数据库管理系统通常使用死锁检测和超时机制来处理死锁情况。当发生死锁时,系统会选择一个事务进行回滚,解除死锁并允许其他事务继续执行。
当涉及到间隙锁和死锁的情况,下面是一个简单的示例来说明:
假设有一个包含大量数据的表 products,其中有一个索引列 product_id:
CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
...
);
现在有两个并发的事务,分别执行以下操作:
事务1:
BEGIN TRANSACTION;
SELECT * FROM products WHERE product_id > 100 FOR UPDATE;
-- 在这个范围内插入新记录
INSERT INTO products (product_id, product_name) VALUES (105, 'New Product');
COMMIT;
事务2:
BEGIN TRANSACTION;
SELECT * FROM products WHERE product_id BETWEEN 100 AND 200 FOR UPDATE;
-- 在同一个范围内插入不同的新记录
INSERT INTO products (product_id, product_name) VALUES (150, 'Another New Product');
COMMIT;
在这个示例中,两个事务都执行了范围查询,并对返回的记录加上了间隙锁。然后,它们在相同的索引范围内插入了不同的新记录。这可能会导致死锁的发生,因为每个事务都需要获取另一个事务持有的间隙锁才能继续执行。
例如,事务1获取了 product_id > 100 的间隙锁,并尝试在该范围内插入新记录。同时,事务2获取了 product_id BETWEEN 100 AND 200 的间隙锁,并尝试在同一范围内插入不同的新记录。由于它们需要互相等待对方持有的间隙锁,可能形成死锁。
为了解决这个问题,数据库管理系统会使用死锁检测和超时机制来处理死锁情况。一旦发现死锁,系统会选择一个事务回滚,解除死锁并允许其他事务继续执行。
以上只是一个简单的示例,实际情况可能更为复杂。在开发中,需要根据具体的应用场景和数据库系统来分析和调整,以减少间隙锁导致死锁的可能性。
---------------------------------------------------------------------------
国之殇,未敢忘!
南京大屠杀!
731部队!
(有关书籍《恶魔的饱食》)以及核污染水排海等一系列全无人性的操作,购买他们的食品和为它们提供帮助只会更加变本加厉的害你,呼吁大家不要购买日本相关产品
昭昭前事,惕惕后人
吾辈当自强,方使国不受他人之侮!
---------------------------------------------------------------------------
作者:三号小玩家
出处:https://www.cnblogs.com/q1359720840/
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。 版权信息

浙公网安备 33010602011771号