一.并发相关
1.ThreadLocal
threadLocal主要提供让每个线程有独立一份存储从而减少并发。
涉及的核心API比较简单就是:get()与set(T obj)。
threadLocal整体结构图:
为什么使用弱引用:
简述:
threadLocalMap中的key为弱引用,主要是解决当threadLocal已经没有对应的强引用,但线程还未结束,此时垃圾回收时能够回收掉threadLocal对象实例。
反之如果此时对于threadLocalMap中的key为强引用,那么由于线程还未结束此时垃圾回收是回收不掉的。
详细分析:
假设在主线程中运行以下代码:
public static void main(String[] args) {
ThreadLocal<String> t=new ThreadLocal<String>();
t.set("a");
}
则整个运行时数据存储如下图:
从图中看,假设entry元素中key是强引用的话,即使后面我们对于主线程中的t变量做强制释放:t=null。
此时的运行时空间如下:
可以看到由于threadLocalMap中的key是强引用,此时由于main线程还是存活的所以从可达路径上还是存在对于ThreadLocal对象实例的引用,所以会导致ThreadLocal不能释放,除非线程没有了。
所以key必须是弱引用,这样在垃圾回收时根据弱引用规则就会释放当前threadLocal对象(不存在对它的强引用)。
二.分布式理论
CAP理论:
CAP适合的场景一定是结点互联并有数据共享的场景。
P是分区容忍性,这个指的是在分布式系统中如果存在网络分区则要保证系统的继续运行,不能全部挂掉。分布式系统情况网络分区长期肯定必现,这时我们在做架构的时候就需要明确考虑这点。
A是可用性。
C是一致性。
在P存在的情况下,在设计分布式系统时需要抉择A和C。
考虑一个情况:a,b是两个网络的服务器结点,业务操作需要写a和写b,写完a后此时a,b出现网络异常,这时就需要决策保可用性A还是保一致性C。
zk做为服务注册中心是CP架构,而spring cloud的eureka则是AP架构。
BASE理论:
base包含了基本可用(BA),软状态(S),以及最终一致性(E)。
基本可用就是说当系统出现问题时我们充许牺牲一部分性能或一部分功能,如对于缓存节点出现异常此时可降级到数据库,虽然RT变长了,但这个功能还是基本可用。
软状态指的是对于这个业务来讲存在中间态的数据,如交易完成库存扣减及支付,此时需要去做积分增加,这个时候积分的增加可以使用异步去做,此时就会存在一个业务数据的中间态。
最终一致性则约束了软状态的时间要有一个期限,最终要达到一个一致性状态。
三.负载均衡
四.限流算法
1.计数
简单对请求做计数,无法应对峰值流理。
2.漏斗算法
出口是固定速率,适和用于下游系统有明确qps限制的场景,保护下游系统。
比如1秒流量可通过100,那这1秒中如果超过110则会失败,多出的部分会进入队列只要不超过这个超时时间就可以处理。
3.令牌梳算法
入口固定速率产生令牌,能够允许一定的流量峰值出口。因此能保护自身系统。
比如1秒产生100个token,然后可能在第5秒才有峰值流量访问,此时前4秒共产生了400 token。则第5秒允许的峰值是500qps。
五.mysql
1.B+树,B-树
B+树内部结点不存储数据,叶子结点才存,并且叶子结点有指针可以指向下一个叶子结点从而高效支持范围查找。
叶子结点实现存储的是对应id值以及对应记录磁盘的物理地址。所以一个查询如果涉及到记录其它数据需要2次查询:1次定位到索引树中的叶子结点,一次是根据叶子结点中的磁盘物理结点去做回表查询。
B+树:
2.fileSort排序
mysql若排序字段不包含在where条件索引中时,此时就会导致fileSort。因为需要做额外的排序。
如select * from A where create_time>='2022-01-01 00:00:00' and create_time<='2022-02-01 00:00' order by id;
这个语句mysql底层就会先按创建时间create_time索引进行查找,查找完结果后再将结果集放到缓存中然后按id进行排序。
这里优化的思路:建立组合索引create_time+id,这样就只需要查找索引树然后对应查询出来的结果就是按create_time+id排序的,所以也就不需要再做额外的排序了。
另外fileSort有2种排序算法:
a.双路排序
根据索引条件查询到叶子结点,然后从磁盘拿到排序列,然后在内存中进行排序,排序好后再进行一次磁盘检索拿到其它数据。
所以这种排序一条数据进行了2次磁盘IO。并且最后一次取最终数据时原先的顺序io就变成了随机io。
优点是使用的排序内存不会很大。
b.单路排序
即mysql4.1后出现。如果当前要排序数据小于max_length_for_sort_data,则使用单路排序。
单路排序在处理完索引树查询后,将符合的索引结果通过回表最终从碰盘上查询记录值,所有符合条件的记录值会放在mysql buffer中,然后进行排序处理。
优点是这样能够利用磁盘的顺序读,并且读磁盘单条数据只需要一次。
3.mysql深分页问题
如果是普通索引查询,并且用了limit 100,200这样的语句,则会有深分页问题。
eg: select * from A where create_time>='2022-01-02 00:00:00' and create_time<='2022-02-02 00:00:00' order by create_time limit 10001,100;
整体深分页流程:
a.查询非聚蔟索引create_time,找到符合条件的行指针放入缓存buffer
b.根据行指针回表查询出对应的数据,这里会查询出这个区间所有的数据
c.10001前的数据都丢失,取10001到10100 100条数据。
解决办法:
1.在条件上加上id条件
select * from A where id>max(id) and create_time>='2022-01-02 00:00:00' and create_time<='2022-02-02 00:00:00' order by id limit 100;
id及createTime需要建立联合索引:create_time&id,否则这里会有fileSort。
这样每次会取出100条数据,下次再取的时候直接用id>max(id)的条件去做了过滤。
这里的max(id)是上一批查询出的批次id最后一条记录id。
整体流程:
a.从create_time索引树取出create_time区间的数据,并且丢弃掉id<maxin(id)的数据
2.延迟关联
即第一次只取id
select id from A where create_time>='2022-01-02 00:00:00' and create_time<='2022-02-02 00:00:00' order by id limit 10001,100;
这样语句就可以在非聚蔟索引中直接完成过滤。
拿到id后取其它数据做下集合查询,语句如下:
select * from A where id in(select id from A where create_time>='2022-01-02 00:00:00' and create_time<='2022-02-02 00:00:00' order by id limit 10001,100)
4.事务
这篇文章不错:
五分钟了解Mysql脏读、幻读、不可重复读、mvcc - 知乎
mysql架构:
5.redo,undo,binglog
redo log 和 undo log 是保证本地事务的,binlog 是用于主从复制的。
redo log实现事务的持久性用于:
如果mysql挂了的时候如果,存在事务已提交但数据未刷新到磁盘的记录,此时在下次mysql恢复启动,根据redo log进行数据恢复。
undo log:用于回滚,同时可用于MVCC下多版本的读取能力。
binglog:用于主从恢复。
整体redo及binglog写入顺序:
5.MVCC
mysql mvcc主要是为了提供事务的隔离级别能力,如读已提交,可重复读及幻读。
每开启一个事务会生成一个ReadView,ReadView包含了当前事务id,这个事务在隔离级别是已提交的情况下是能看到当前最新的已提交事务的数据
六.垃圾回收
G1:
当堆内存大于8G,整体回收性能会下降较多,如此一个解法就是将堆内存划分成n个小块,每个小块可以是年轻代,也可以是老年代。这样就能防止回收时因为大块堆内存导致的回收停顿问题。