JAVA高级学习路线
2022-02-05 18:23:41 0 举报
AI智能生成
多线程、高并发、MYSQL、redis、大厂面试
作者其他创作
大纲/内容
MySQL
事务、锁
锁
锁是数据库实现并发控制的基础,事务隔离性是采用锁来实现,对相应操作加不同的锁,就可以防止其他事务同时对数据进行读写操作
类型
共享锁(读锁S)
其他事务可以读,不能写
select ... lock in share mode
排它锁(写锁X)
其他事务不能读取,也不能写
select ... for update、update、delete、insert
粒度
行锁(聚集索引)
特点:
InnoDB
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
条件列:索引 中生效
算法
记录锁RecordLock
RC、RR
锁定单个行
间隙锁GapLock(共享锁)
RR隔离级别
对于键值在条件范围内但并不存在的记录,称之为“间隙”。InnoDB【也会】对间隙进行加锁,这种锁机制称之为“间隙锁”。
防止幻读
Next-key Lock 锁
RR
记录锁和间隙锁【组合】,同时锁住数据,并且锁住数据前后范围
左开右闭
死锁
原因1
查询没有适用索引,全表扫描/表锁
解决:不使用关联多表的查询;建立索引
原因2
两个事务分别想拿到对方持有的锁
T1:
id=1 for update;
id=2 for update;
T2:
id=2 for update;
id=1 for update;
id=1 for update;
id=2 for update;
T2:
id=2 for update;
id=1 for update;
T1:
id=1 lock in share mode;
update where id=1;
T2:
id=1 lock in share mode;
update where id=1;
id=1 lock in share mode;
update where id=1;
T2:
id=1 lock in share mode;
update where id=1;
解决:一个事务中尽可能一次锁定所需的资源;按id对资源排序依次处理;乐观锁
排查命令
表锁
InnoDB、BDB、MyISAM、MEMORY
开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
条件列:非索引
页面锁
BDB
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
实现方式
解决 读读、读写、写读冲突
MVCC
解决【写写冲突】
悲观锁(更新前->对整个数据处理工程锁定)->并发低
1.select num from (库存) where id = 100 【for update(此行及join到的行阻塞)//或者通过代码整体外层加锁】
2.num>0,生成订单信息
3.update (库存) set num = num-1 where id = 100;
2.num>0,生成订单信息
3.update (库存) set num = num-1 where id = 100;
乐观锁(更新时【检查/版本号、时间戳...】)->并发高,
频繁的操作数据库,数据库资源有限,还是存在性能(行锁)问题;
对于客户端而言,并发高时下单失败
频繁的操作数据库,数据库资源有限,还是存在性能(行锁)问题;
对于客户端而言,并发高时下单失败
1.select num,version from (库存) where id = 100
2.num>0,生成订单信息
3.update (库存) set num = num-1,version=version+1 where id = 100 and version=#{version};
2.num>0,生成订单信息
3.update (库存) set num = num-1,version=version+1 where id = 100 and version=#{version};
事务
四大特性ACID
原子性Atomicity
要么都成功、要么都不成功
一致性Consistency
将数据从一个‘正确’状态转变到另一个‘正确’状态
隔离性Isolation
多个事务之间互补干扰
持久性Durability
事务提交后,对数据的修改就是永久的,及时系统故障也不会丢失
事务日志->持久性、原子性
【日志:缓冲区->磁盘】修改数据->记日志到缓冲区->commit->日志写磁盘(commit成功)->将数据写入表
Redo log(重做日志)
保证数据持久化
COMMIT提交成功之后,但是服务器挂了,数据还没有写入磁盘,在mysql重启服务之后会 重新执行redo log写入数据。
Undo log(回滚日志)
保证事务原子性,只要其中一个操作失败就回退最初状态
ROLLBACK成功后,系统发生崩溃、数据库进程直接被杀死后,当用户再次启动数据库进程时;还能够立刻通过查询undo log将之前未完成的事务进行回滚
事务并发控制的整体解决方案,本质上是对锁和MVCC使用的封装,隐藏了底层细节
并发事务下保证写数据不会出问题
两个或多个事务更新同一行记录,回滚覆盖和提交覆盖。(修改时默认加写锁,写之前默认读最新)
并发事务下如何保证读数据不会出现问题呢?
并发事务读数据问题
脏读
个事务读取到了另一个事务修改但未提交的数据
幻读
后续查询的结果和面前查询结果不同,多了或少了几行记录。
不可重复读
一个事务中多次读取同一行记录不一致
隔离级别
隔离级别
READ_UNCOMMITTED
更新丢失
读->不加任何锁;写->排他锁
READ_COMMITTED
更新丢失、脏读
读->MVCC,写加互斥锁;读读、读写并行,写写互斥
REPEATABLE_READ(默认)
更新丢失、脏读、不可重复读、没有完全解决幻读(当前读、更新之后快照读会出现幻读;【Next-Key锁-间隙】解决幻读问题)
读->MVCC,写加互斥锁;读读、读写并行,写写互斥
SERIALIZABLE
更新丢失、脏读、不可重复读、幻读
读->共享锁、写排他锁;读写、写读互斥;写写互斥
事务启动时机
begin/start transaction 并不是事务的起点,在执行第一个操作语句才启动
想立即启动事务:start transaction with consistent snapshot
当前读
加锁,当获得该锁的其他事务没有提交时,读操作会阻塞,保证每次读到的是最新的记录
读取的是记录的最新版本,并且当前读返回的记录,都会加锁,保证其他事务不会再并发修改这条记录。
(select... for update 或lock in share mode,insert/delete/update)
(select... for update 或lock in share mode,insert/delete/update)
MVCC(多版本并发控制)
RC/RR级别通过MVCC实现读写并行操作
读不加锁,读写不冲突->读读、读写、写读并行;
通过不加锁的方式就能解决脏读、可重复读、幻读(部分解决)
读不加锁,读写不冲突->读读、读写、写读并行;
通过不加锁的方式就能解决脏读、可重复读、幻读(部分解决)
快照读
读取时不加锁,其他事务可以写
读取的不一定是记录最新版本(有可能是历史版本),取决于读取记录后该记录有没有被其他事务修改提交
核心原理
核心:undo log日志版本链和一致性视图read view
undo log日志版本链
聚集索引中每行(每个表的每行)有额外隐藏字段(trx_id,roll_pointer)
在update记录时
1.用排他锁锁定该行;记录 Redo log
2.把该行修改前的值复制到 Undo log
3.修改当前行的值,记当前事务编号trx_id,使回滚指针roll_pointer指向 Undo log 中修改前的行
2.把该行修改前的值复制到 Undo log
3.修改当前行的值,记当前事务编号trx_id,使回滚指针roll_pointer指向 Undo log 中修改前的行
read view
consistent Read View一致性读视图(creator_trx_id,未提交事务itrx_ids,未提交最大up_limit_id,最小low_limit_id)
RC级别
每次select都重新生成最新read view,根据视图及undolog分析找出已提交的记录
eg.
t1修改一条记录,但是不提交;t2执行查询这条记录
1、t2执行select生成view视图
2、t2从视图看到这条记录正在被t1修改,但是没有提交
3、通过记录中的roll_pointer去undo log找到上一个版本
4、如果上一个版本的trx_id事务ID<=t2自己的trx_id,说明是已经commit或者是自己修改的,就直接返回
RR级别
只会创建一次且在第一次SELECT时创建view,解决不可重复读、部分幻读
eg.
t1多次selecet,即使期间有其他事务修改,还是通过第一次那个view分析得到的记录
存储引擎
innodb
事务
行锁、表锁
B+Tree
索引页16K
数据恢复
必须有主键
myisam
表锁
B+Tree
索引文件.MYI与数据文件分离(非聚集索引)
索引页1K
binlog(binary log)
目的
数据恢复,增量备份,主主复制和主从复制等等
操作
show variables like '%log_bin%';
索引
innodb
分类
聚集索引(主键索引)
叶子节点存整行数据
修改聚集索引的列是一项昂贵的操作,所以选择很少或从不更新的主列
稠密、效率高、空间大
非聚集索引(二级索引)
叶子节点存主键值
都包含该行的主键列以及为辅助索引指定的列
回表
稀疏、相对稠密效率低、空间小
eg.alter table add index idx(a,b,c)
selec * from t where a=1 and c =2;可以使用索引
using index condition,a=1匹配索引再过滤c=2,不是回表过滤;这样的话select * 只回表一次
using index condition,a=1匹配索引再过滤c=2,不是回表过滤;这样的话select * 只回表一次
数据结构
B+Tree
页16k
叶节点存数据(整行或主键)
物理存储
索引和数据存储在同一个文件.ifrm,.ibd
myisam
数据结构
B+Tree
叶节点存地址指针
物理存储
索引和数据存储在不同文件.ifrm,.MYD,.MYI
数据结构
b+tree
page做节点16k
非叶子节点存【主键、指向子节点地址】
叶子节点放【数据】,非叶子节点提供索引的作用
相邻叶子节点通过链表指针连接起来
eg.
假设一行记录1kb,主键int占4b,指针6b,估算3层b+树存多少记录?
1、mysql中1次磁盘IO的page(16kb)相当于一个节点
2、由于非叶节点存的是(主键+指向子节点的指针,记为T)
3、第一层只有一个根节点(page)可存16*1024/(4+6)=1638个T
4、第二层每个节点(page)也可存1638个T
5、第三层的话就是叶子节点了,会存整行记录,每个叶子节点(page)可以存16k/1k=16
6、所以大约3层的b+树结构可存一行记录1kb的记录数大约为1638*1638*16=42928704
为什么推荐自增id做主键
由于所有记录维护在主键索引中
索引数据文件按主键自增存储
新增记录时不用调整索引,提高插入性能
sql优化
需要什么字段查什么
explain检查有无用索引
作用:模拟mysql优化器执行SQL
explain各字段说明
eg.alter table add index idx(a,b,c)
eg.alter table add index idx(a,b,c)
id
表示:操作表的顺序
id相同,执行顺序从上往下
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
select_type
SIMPLE
查询中不包含子查询或者union
PRIMARY
查询中若包含任何复杂的子部分,最外层的查询则被标记为PRIMARY
SUBQUERY
在select或where列表中包含了子查询
DERIVED
在from列表中包含的子查询被标记为DERIVED(衍生)
mysql会递归执行这些子查询,把结果放到临时表中
mysql会递归执行这些子查询,把结果放到临时表中
UNION
若第二个select出现在union之后,则被标记为UNION;
若union包含在select的字句中,则外层select标记为DERIVED
若union包含在select的字句中,则外层select标记为DERIVED
UNION RESULT
从UNION表获取结果的select
table
显示这一行数据是关于哪张表的
type
分类
all
全表扫描
index
索引扫描
只遍历索引树
range
where语句中出现了between、< 、>、in等查询
只需要开始与【索引的某一点,先=,再找】,而结束于另一点,不用扫描全部索引
ref
非唯一性索引扫描,匹配到多行
eq_ref
唯一索引扫描,表中只有一条记录匹配
const
表示通过索引一次就找到了
system
表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计
system > const > eq_ref > ref > range > index > all
possible_keys
key
实际使用的索引
key_len
索引中使用的字节数
在不损失精度性的情况下,长度越长越好
key_len显示的值为索引字段的最大可能长度,并非实际使用的长度,即key_len是根据表定义计算而得,不是通过表内检索出来的。
ref
哪些列或常量被用于查找索引列上的值。
extra
using index
覆盖索引,要查的字段就在索引里
如果同时出现using where,表明索引用来执行索键值的查找;
using index condition
使用了索引条件过滤
回表
using filesort 内存中排序
'文件排序',不是按照表内的索引顺序进行读取
select * from t order by a,b,c ;
由于*,mysql选择直接全表扫描,而不是先走索引再一个一个回表
由于*,mysql选择直接全表扫描,而不是先走索引再一个一个回表
不使用filesort情况
select b from t order by a,b,c ;
由于索引覆盖b,及索引abc自然有序
由于索引覆盖b,及索引abc自然有序
using temporary
使用了临时表保存中间结果,mysql在对查询结果排序时使用了临时表
常见于order by 、 group by
using where
表明使用了where过滤
using join buff
表明使用了连接缓存
impossible where
where字句的值总是false,不能用来获取任何元组
distinct
优化distinct操作,表示在找到第一匹配的元组后停止找同样值的动作
rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
filtered
索引失效
mysql自身优化,觉得走索引慢于全表
不满足索引最左原则
类型转换
a varchar
where a=1,先全表扫描a,转数字再比较
where a=1,先全表扫描a,转数字再比较
索引字段使用函数,b+1=3,也算
分库分表
数据越多,层数越多,io次数越多;一般超过三层(2000万以上数据)应该考虑分库分表
b+树节点-数据页(多个记录),基础
每行最多65535字节
每页至少2行,意味着每行16/2=8k=8192
eg.单纯只存varchar,不标记null或非空及额外信息
varchar可变长字符,需2个字节记录实际长度
则varchar设置超过(8192-2=8190)就会成为溢出行
则varchar设置超过(8192-2=8190)就会成为溢出行
溢出行
溢出部分存储其他页,指针相连
行格式
compact
Compact格式的实现思路是:当列的类型为VARCHAR、 VARBINARY、 BLOB、TEXT时,该列超过768byte的数据放到其他数据页中去。
redundant
集群
好处
高可用性:故障检测及迁移,多节点备份
可伸缩性:新增数据节点便利,方便扩容
负载均衡:切换某服务访问某节点,分摊数据库压力
风险
网络分裂:集群还可能由于网络故障拆分为多个部分,每个部分节点连接,但各个部分失去联系
脑裂:导致数据库节点彼此独立运行的集群故障成为“脑裂”。这种情况可能导致数据不一致,并且无法修复;
例如,当两个数据库节点独立更新同一表上的同一行时。
例如,当两个数据库节点独立更新同一表上的同一行时。
架构
Mysql主从复制(MySQL Replication)
原理:通过重放binlog实现主库数据的异步复制。即在主库执行了一条sql写命令,则在从库同样执行一遍
过程
1、master对数据的写操作会记入二进制文件(binlog)中,同时启动一个log dump线程
2、log dump线程会通知slave节点的i/o线程过来取log数据
3、slave的IO线程将取到的数据写到中继日志中(relaylog)中
4、slave的sql线程再读取relaylog解析成具体的操作写到自己库中
优点
读写分离
数据删除可以从binlog恢复
缺点
- 异步binlog到slave重放,存在数据延迟
2、对master和slave的网络延迟要求较高
1、单一的master节点挂了就不能对外提供写服务
Mysql织物(MySQL Fabirc)
在MySQL Replication的基础上,增加了故障检测与转移、自动数据分片功能
优势
master挂了能够自动从slave选一个成为master,继续提供写服务
劣势
数据延迟
网络延迟要求较高
事务及查询只支持在同一个分片内,事务中更新的数据不能跨分片,查询语句返回的数据也不能跨分片
节点故障恢复30秒或者更长(采用Innodb引擎存储的都这样)
Mysql集群(MySQL Cluster)
多主多从结构
优势
高可用性优秀
可伸缩性优秀,能自动切分数据,方便数据库的水平扩展
负载均衡优秀,可同时用于读操作、写操作都很密集的应用
多个master,没有单点故障的问题,节点故障恢复通常小于1秒
劣势
架构模式和原理很复杂
只能使用存储引擎NDB,如事务隔离级别只支持read commited,与平常使用的InnoDB有很明显的差距
对节点之间内部互联网带宽要求比较高
Data Node数据会尽量放在内存中,对内存要求大,而且重启的时候,数据节点将数据load到内存需要很长的时间
分库分表
垂直分库分表
将一个表按照字段分散在多个表、库,每个表、库存储一部分字段;专库专用
原则
1、把不常用的字段单独放在一张表
2、把text、blob等大字段拆分来放在附表中
3、经常组合查询的列放在一张表
优点
1、避免IO争夺并减少锁表的几率,eg.查看详情的用户与商品信息浏览互不影响
2、充分发挥热门数据的操作效率,商品信息的操作的高效率不会被商品描述的低效率所拖累
水平分库分表
当一个应用难以再细粒度的垂直切分,或切分后数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平分库了;
需要额外进行数据操作的路由工作,因此大大提升了系统复杂度。
需要额外进行数据操作的路由工作,因此大大提升了系统复杂度。
方法
1、range
eg.user表,id=[1,10000]存user1,id=[10001,20000]存user2,...
保证ID的不重复唯一性,可以使用Redis的incr操作
优点:扩容简单,提前建好库表就可以
缺点:大部分读和写都会访问新的数据,有IO瓶颈,这样子造成新库压力过大,不建议使用
2、hash取模
可以采用根据用户ID hash取模的方式进行分库分表,这样就可以将数据分散在不同的表库中,避免了IO瓶颈的问题
优点:能够保证数据较均匀的分布在不同的库、表中,减轻了数据库压力
缺点:扩容麻烦,迁移数据每次需要全量重新计算hash值分配到不同的库、表中
3、一致性Hash
普通hash算法在分布式应用的不足
1、通常在分布式存储系统中,要将数据存储在具体的节点上
2、采用普通的hash算法将数据路由映射在具体的节点上,如key%n,key为数据key,n为节点数
3、如果有一个节点加入或者退出集群,则之前所有的数据映射就失效了
3.1、如果是持久化存储还需要做数据迁移,如果是分布式缓存,则其他缓存就失效了
优点
1、通过虚拟节点方式能够保证数据较均匀的分散在不同的数据库、表中;
2、新增、删除节点不影响其他的节点的数据,高可用、容灾性强
Java
JVM
架构
类加载器ClassLoader
类加载机制
虚拟机把描述类的数据从.class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被jvm直接使用的java类型Class对象
类加载器
通过一个类全限定名称来获取其二进制文件(.class)流的工具。
Bootstrap ClassLoader
负责加载$JAVA_HOME中 jre/lib/rt.jar 里所有的class或Xbootclassoath选项指定的jar包。由C++实现,不是ClassLoader子类。
Extension ClassLoader
加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。
App ClassLoader
加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和jar包。
Custom ClassLoader自定义类加载器
继承java.lang.ClassLoader,重写findClass(String name)
java热部署实现
创建自己的classLoader来加载需要监听的class,这样就能控制类加载的时机,从而实现热部署
如tomcat、jboss都会根据j2ee规范自行实现ClassLoader
加载过程/生命周期
1、装载(load)
时机
1、生成该类对象的时候,会加载该类及所有该类父类
2、访问该类的静态成员变量
3、Class.forName(“类全限定名称”);
过程
1、通过类的全限定名获取.class二进制文件
2、将这个二进制文件静态数据结构读取转换为方法区中的运行时数据结构(元数据信息)
3、在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口
可控阶段:开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。
2、链接
1、验证,确保类的安全与正确;包括文件格式验证、元数据验证、字节码验证、符号引用验证
2、准备,为类的静态变量分配内存,并将其初始化为默认值(0值,非java代码显示默认的值)
3、解析,把类中的符号引用(一组符号来描述目标,可以是任何字面量)转换为直接引用(直接指向目标的指针、相对偏移量)
3、初始化
根据程序员自己写的代码逻辑去初始化类变量和其他资源
①声明类变量是指定初始值。
②使用静态代码块为类变量指定初始值。
何时触发
1、创建新对象实例,比如new、反射
2、调用一个类的静态方法、静态字段
3、调用javaAPI中反射方法,如java.lang.Class中的方法、java.lang.reflect包中的反射方法
4、初始化一个类的派生类时,初始化子类必须先初始超类
5、jvm启动包含main方法的启动类
不触发情况
1、子类引用父类中的静态变量,子类不会被初始化
2、通过数组定义来引用类,不会触发引用类的初始化
MyClass[] myClass = new MyClass[5];
3、使用一个类中定义的常量时,不会初始化这个类
编译期间将这个常量的值存储在这个类的常量池,使用时直接使用Class自身常量池引用
4、使用
5、卸载(unload)
java.lang.Class对象被GC回收
满足条件
该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例
加载该类的ClassLoader被GC
该类的java.lang.Class对象没有任何地方被引用,如不能在任何地方通过反射访问该类的方法
双亲委派
优先用父加载器加载
过程
1、如果parent不存在,则查询下类文件是否被Bootstrap ClassLoader加载过,找到就返回否则返回null
2、如果parent存在,则让父加载器加载,这个过程是往上递推的,如parent->parent->parent
3、以上流程处理完毕后,如果发现返回的是null,发现递推到Bootstrap ClassLoader仍不能加载
4、该类文件不符合所有的父类加载器的加载条件,于是便轮到自己来加载了
反射
本质:使用Class对象,反向获取Student对象的各种信息
特点
读取的是方法区中的class对象
只能读class对象,不能写class对象;(通过class实例构造对象,写实例化后的)
读取类信息时需要进行类初始化过程
eg.
Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动
SPI(Service Provider Interface)
使用/原理
定义一个接口
可以多个实现
在classpath下创建META-INF/services/目录
在该目录下创建一个文件配置文件com.qd.demo.spi.XXXX(该文件名为SPI接口全路径)
【java.util.ServiceLoader】类加载器加载,通过配置文件找到具体厂家的实现
作用
服务提供发现机制,寻找某个接口的具体实现
可以用来启用框架扩展和替换组件
比如java.sql.Driver,其他不同厂商可以针对同一接口做出不同实现
核心思想:【解耦】,将装配的控制权移交程序之外,Common-Logging、JDBC
面向接口编程,不关注具体的实现,由配置文件配置具体要实例化的厂商接口
运行时数据区Runtime Data Area
JVM内存结构/运行时内存分布
定义
由java虚拟机规范定义
描述的是java程序执行过程中,由jvm管理的不同数据区域
划分
线程私有
程序计数器
用途
当前线程代码所执行的字节码行号指示器,这个值可以选取下一条需要执行的字节码指令,如分支,循环
cpu时间片到,线程挂起记录执行位置,下次再从这里执行
特点
线程独有
占用内存很小,jvm内存计算时可以忽略不计
唯一一个在jvm规范中没有规定任何OOM err的区域
VM Stack/虚拟机栈
随着线程的创建而创建,用于描述java方法执行的内存模型,里面有‘栈帧’,
每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
每个java【方法】执行时,会创建一个“栈帧(stack frame)”,Main()方法先入栈
局部变量表
一片连续的内存空间,用来存放方法参数、方法内定义的局部变量
存放着预编译期已知的数据类型(八大基本数据类型和对象引用reference类型)方法出口returnAddress类型(指向一条字节码指令的地址)
操作数栈
方法调用时创建栈帧,并压入【该线程的虚拟机栈】
方法执行完毕,栈帧出栈并被销毁
eg.新开启Thread执行方法
1、给该线程开启一块虚拟机栈空间
2、线程main方法执行到的方法会创建栈帧压入虚拟机栈
3、方法执行完意味着栈帧都出栈
4、主线程方法结束后虚拟机栈销毁
error
若单个线程请求的栈深度大于虚拟机允许的深度,则会抛出StackOverFlowError【函数递归】
(-Xss128k),当整个虚拟机栈内存耗尽,并且无法再申请到新的内存时抛出OutOfMemoryError【一直new Thread】
本地方法栈
本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常
不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法
线程共享
堆
jvm虚拟机管理内存最大的一块区域
虚拟机规定:所有实例对象和数组都在堆上分配内存
注意:创建出来的对象只包含属于各自的成员变量(对象的属性),并不包括成员方法
方法区(jvm规范定义)
存放虚拟机类加载器加载的类信息,常量池存放着常量(字面量)、静态变量等信息
具体实现
jdk1.7
java.lang.OutOfMemoryError: heap space
PermGen(永久代)
永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
jdk1.8
metaspace(元数据空间)
本地内存,不由虚拟机内存管理
默认情况下,只受本地内存的影响,可以设置-XX:MetaspaceSize、-XX:MaxMetaspaceSize
java.lang.OutOfMemoryError: Metaspace
直接内存
直接向系统申请内存,指向java堆外不隶属于java堆
在nio中是通过堆中的【DirectByteBuffer】【实现对直接内存】的访问,能在一些场景中显著提高性能
执行引擎Execution Engine
任务
将Class的字节码指令解释/编译为对应平台的本地机器指令执行
将java高级语言翻译为机器语言
解释器(interpreter)
将字节码指令翻译解析为对应平台机器指令
JIT(just in time compiler)即时编译器
解释器执行比较低效,即时编译器的目的是为了避免函数被解释执行
而是将整个函数体编译为机器码,使执行效率大幅度提升
本地库接口Native Inference
Java对象模型
1、对象头(mark word)
32位系统8字节,64位未开启指针压缩16字节,开启后12字节
很像网络协议报文头,划分为多个区域,并且会根据对象状态复用自己的存储空间
2、实例数据
存放对象程序中各种类型的字段类型,不管是从父类中继承的还是在子类中定义的
分配策略:相同宽度的字段总是放在一起,比如double和long
3、对其填充
仅仅起到占位符作用,满足jvm要求
GC(垃圾回收机制)
哪些区域需要回收
程序计数器、虚拟机栈、本地方法栈,由线程生而生,线程灭而灭;当方法或线程执行完毕内存就随着回收,无需关心
堆
GC的重点区域,基本存放着所有对象实例
新生代(Young Generation)
默认比例 8:1:1
Eden
From Survivor
To Survivor
老年代(Old Generation)
永久代(Permanent Generation)
方法区的具体实现
jdk1.8采用元空间,不在堆中了
方法区
什么时候回收
Minor GC
年轻代young space(包括Eden和Survivor区域)的垃圾回收
eden区分配已满触发
Major GC
old gc,也可以和full gc等价
Full GC
收集整个堆,包括young gen、old gen、perm gen(如果存在的话)、元空间(1.8及以上)
触发时机
1、手动调用System.gc();
2、perm gen(如果存在永久代的话)需分配空间,但已经没有足够空间
3、老年代空间不足,比如新生代的大对象、大数组晋升老年代可能导致老年代空间不足
4、cms GC时出现Promoton Faield
Promotion Failed:survivor space放不下[满了或对象太大],对象只能放到老年代,而老年代也放不下会导致这个错误
5、统计得到的Monor GC晋升到老年代的平均大小大于老年代的剩余空间
mixed gc
G1特有,混合GC,收集整个young gen及部分old gen
怎么回收
判断对象是否存活算法
1、引用计数器法
早起大多已这种方法,算法判断很简单,简单来说就是给对象添加一个引用计数器,被引用+1,失效-1
优点:实现简单效率高,被广泛使用在如python和游戏脚本语言上
缺点:难以解决循环引用问题,如两个对象互相引用且没有被其他对象引用,导致引用计数器不为0
2、可达性分析算法
需要停掉java执行线程
基本思路:通过一个成为“GC ROOT”对象为起点,搜索所经过的路径成为引用链,当对象不在任何一个引用链上说明无用
有效解决循环引用的弊端
枚举根节点算法
如果方法区几百兆,一个个检查里面的引用,将耗费大量资源
实际上系统停下来jvm不需要一个一个检查引用,而是通过OopMap数据结构(HotSpot的叫法)来标记对象引用
可以作为GC ROOT的对象
1、虚拟机栈(栈帧中的本地变量表)中引用的对象
2、方法区中类静态属性引用的对象,一般指static修饰引用的对象
3、方法区中常量引用的对象
4、本地方法栈中JNI(native方法)引用的对象
垃圾回收算法
复制算法
新生代
适合用于存活率低的内存区域,优化了标记/清除算法的效率和内存碎片问题
过程
1、对象优先分配Eden区域,若eden区域满了,则触发young GC将存活对象复制到其中一个survivor区域,分代年龄+1;另一个为空;
2、下次GC时将存活对象复制到另一个空的survivor区域,分代年龄+1,之前的那个survivor为空;交替使用
3、若复制时存活对象年龄达到阈值(默认15,对象头),则移动到老年代
标记清除
标记整理
垃圾收集器
如果说垃圾回收算法是内存回收的方法论,那么垃圾收集器就是具体实现。
jvm会结合针对不同场景及用户的配置使用不同的垃圾收集器。
jvm会结合针对不同场景及用户的配置使用不同的垃圾收集器。
年轻代收集器
Serial
单线程收集器,只使用一个线程进行收集工作
需要停掉其他用户线程线程,stop the world
适用于单核CPU
能配合CMS一起使用
ParNew
多线程收集器,stop the world
支持多线程并发收集,多核CPU能够充分利用cpu资源
运行在server模式下年轻代首选的收集器
能配合CMS一起使用
Parallel Scavenge
支持多线程,注重吞吐量,‘吞吐量’优先收集器
吞吐量=代码运行时间/(代码运行时间+垃圾收集时间)
高吞吐量可以高效利用cpu尽快完成运算任务,适合后台运算
老年代收集器
Serial Old
采用标记-整理算法
jdk5前和Parallel Scavenge搭配使用
CMS收集器的后备
Parallel Old
标记-整理
支持多线程,jdk6开始出现,Parallel Scavenge的老年代版本
Parallel Scavenge + Parallel Old结合,真正形成吞吐量优先的收集器组合
CMS
Concurrent Mark Sweep,并发标记清除
一种获取最短停顿时间为目标的垃圾收集器,重视响应,可以带来好的用户体验,被sun成为并发低停顿收集器
启动CMS:-XX:+UseConcMarkSweepGC
4个阶段
1、初始标记
stop the world,停掉其他java线程
标记一下GC Roots能直接关联到的对象,速度很快
2、并发标记
GC Roots Tracing过程,即可达性分析
3、重新标记
stop the world
为了修正因并发标记期间用户程序运作而产生变动的那一部分对象的标记记录,会有些许停顿
标记时间上:初始标记 < 重新标记 < 并发标记
4、并发清除
缺点
默认启动垃圾线程数为(cpu数量+3)/4,性能容易受cpu核数的影响
无法处理浮动垃圾,可能导致Concurrent Mode Failure(并发模式故障)而出发full gc
浮动垃圾:cms支持运行的时候用户线程同时运行,这时产生的新的垃圾就是浮动垃圾;cms无法当次处理,得下次
由于是标记清除算法,因此会存在垃圾碎片问题
为了解决,提供了-XX:+UseCMSCompactAtFullCollection(默认开启)
用于cms顶不住要进行full gc时开启内存碎片合并整理,整理过程无法并发,开启这个会影响性能(比如停顿时间变长)
特殊收集器
G1
garbage first,尽可能多收垃圾,避免full gc;jdk1.7后才有,强化了分区,弱化了分代的概念,不在年轻、老年范畴内
同cms一样关注低延迟,解决了cms产生内存碎片等一系列问题
用到的算法:标记-清除、复制算法
区域化:将java堆划分为若干个大小相同的区域(region)
收集方式
通过并发(并行)标记老年代存活对象,通过并行复制压缩存活对象(可以省出连续空间供大对象使用)
将一组或者多组区域中存活对象以增量并行的方式复制到不同的区域进行压缩,从而减少内存碎片,
目标是尽可能多回收堆空间(垃圾优先),且尽可能不超出暂停目标以达到低延迟目的
目标是尽可能多回收堆空间(垃圾优先),且尽可能不超出暂停目标以达到低延迟目的
参数
开启选项:+XX:+UseG1GC,jdk1.7、1.8默认关闭
-XX:G1HeapRegionSize=n,设置G1 region大小,不设置的话自己会根据堆大小算,目的是根据最小堆大小划分2048个区域
-XX:MaxGCPauseMillis=200,最大停顿时间,默认200毫秒
使用场景
1、用户体验好的场景;像cms能与用户线程并发执行,gc停顿短(短而可控)
2、面向服务端,大内存、高cpu的应用机器
3、应用在运行过程中经常会产生大量内存碎片,需要压缩空间(比cms好的地方之一,g1自带压缩功能)
查看gc日志
jvm参数:-XX:+PrintGCDetails
jdk自带可视化工具jvisualvm
频繁fullGC
1、首先看fullgc后能不能有效的把内存给回收掉,
同时dump堆,分析gc的原因
同时dump堆,分析gc的原因
不能有效回收,可能存在内存泄漏,优化代码
能有效回收
可能堆内存配置不合理导致老年代很快被塞满了
缺失业务量太大了,服务器自身扛不住了,升级硬件配置
修改垃圾回收器,如把cms升级到g1,降低gc停顿时间
2、调整堆大小,临时降低了fullgc的频率,
但是同时fullgc的耗时也会变长,不能完全解决问题
但是同时fullgc的耗时也会变长,不能完全解决问题
JMM
是一种规范,是解决由于多线程通过共享内存进行通信时,存在 本地的内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。
调优
什么时候需要
full gc次数频繁
gc停顿时间超长(超过1s)
应用出现outOfMemory等应用异常
系统吞吐量与响应性能不高或下降
目标
GC低停顿和GC低频率
低内存占用
高吞吐量
量化目标
Heap内存使用率<=70%
Old generation内存使用率<=70%
avg pause<=1s
Full gc次数为0 或者 avg pause interval >= 24小时
步骤
分析gc日志及dump文件,判断是否需要优化,确定瓶颈问题点
确定jvm调优量化目标
确定jvm调优参数
依次调优内存、延迟、吞吐量等指标
对比观察调优先后的差异
不断的分析和调整,直到找到合适的jvm参数配置
找到合适的参数,将这些参数应用到所有服务器,并进行后续跟踪
常用策略
选择合适的垃圾收集器
调整内存大小(垃圾收集频率非常频繁,可能是内存设置太小)
调整内存区域大小比例(某一区域GC频繁,其他区域正常)
调整对象升老年代的年龄(老年代gc频繁,每次回收的对象很多)
调整大对象的标准(老年代gc频繁,每次回收对象多,且单个对象的体积比较大)
调整GC的触发机制(CMS,G1经常Full GC,程序卡顿严重)
调整JVM本地内存大小(GC的次数、时间和回收的对象都正常,堆内存空间充足,但是报OOM)
配置参数
堆栈配置相关
-Xms512
初始堆
-Xmx512m
最大堆
-Xmn128m
年轻代大小
-Xss128k
每个线程的堆栈大小为128k
-XX:MaxPermSize=16m
设置持久代大小为16m
-XX:NewRato=4
设置年轻代(包括Eden和两个Survivor区)与老年代的比值(除去持久代)
-XX:SurvivorRatio=4
设置年轻代中Eden区与Survivor区的大小比值,
设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxTenuringThreshold=0
设置垃圾最大年龄,如果设置为0的话则年轻代不进入Survivor区,直接进入老年代
垃圾收集器相关
年轻代-老年代搭配
-XX:+UseSerialGC(复制算法) + -XX:+UseSerialOldGC 标整
单cpu,小内存 单机程序
-XX:UseParNewGC(复制) + -XX:+UseSerialOldGC(标整)
-XX:+UseParallelGC(复制) + -XX:+UseParallelOldGC(标记整理)
多CPU 需要最大吞吐量,如后台计算型应用
-XX:+UseParNewGC(复制算法) + -XX:+UseConcMarkSweepGC(标记清除算法)
追求低停顿,需要快速响应,如互联网应用
CMS:1.initial mark,2.concurrent mark,3.remark,4.concurrent sweep
-XX:+UseG1GC
整体上采用标记整理算法,局部是通过复制算法,不会产生内存碎片
-XX:ParallelGCThreads=20
配置并行收集器的线程数
-XX:CMSFullGCsBeforeCompaction=5
由于并发收集器不对内存进行压缩整理,运行一段时间会产生内存碎片
此值设置运行多少次GC后对内存空间进行压缩。、整理
-XX:+UseCMSCompactAtFullCollection
打开对老年代的压缩整理,可以消除内存碎片,但是可能会影响性能
辅助信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError、-XX:HeapDumpPath=/home/liuke/jvmlogs/
出现oom时dump生成内存文件
常见问题排查
定位死锁
1、jps -l 列出所有运行的java进程,可以看到pid
2、jstack pid
3、查看内容:java.lang.Thread.State: BLOCKED位置,下面线程栈内容可以发现执行代码位置
cpu过高定位
1. jps ps 找出pid
2.定位线程
ps -mp 进程id -o THREAD,tid,time
-p pid 进程使用cpu的时间
-m 显示所有的线程
-m 显示所有的线程
或者top -Hp pid
3、将需要的线程ID转换为16进制格式
printf "%x\n" 线程tid #\n用于换行
4.jstack 进程id |grep tid(16进制线程id小写英文) -A60
5、然后就可以看到是代码的哪一行
OOM问题
1、dump 堆文件
jvm参数 -XX:+HeapDumpOnOutOfMemoryError,-XX:HeapDumpPath=/home/liuke/jvmlogs/ 出现 OOM 时dump堆
jmap -dump:format=b,file=/home/admin/logs/heap.hprof {PID}
2、使用堆文件分析工具分析
MAT
Reports -> Leak Suspects 猜测泄露位置
高并发
线程/进程
生命周期
创建、就绪、运行、阻塞、终止
线程
进程中执行运算的最小单位
cpu调度的最小单位
可与共处在一个进程的其他线程共享进程拥有的全部资源
多线程:wx边聊天边下载文件
进程
资源分配的最小单位
一个进程包含1-n个线程
多进程:qq、微信
子进程和父进程有不同的数据空间(redis/rdb时fork子进程),而多线程共享数据空间
通信方式
管道pipe
信号signal
消息队列message queue
共享内存shared memory
依靠某种同步操作,如互斥锁和信号量等
信号量semaphore
套接字socket
同步&互斥
在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的
完成某个特定任务,多线程之间需要同步/先后
修改同一个共享资源时,线程之间需要互斥
通过锁实现线程之间的同步/互斥
并发&并行
并发:偏重于多个任务交替执行,有可能还是串行的
并行:真正意义上的同时执行
多核 CPU
优势
速度:能同时处理多个任务,响应更快
设计:某些情况下程序设计有更多的选择
资源利用:cpu能在等待IO时做一些其他事情
风险
安全性:多个线程共享数据时可能产生与预期不相符的结果
活跃性:某个操作无法进行下去时就会发生活跃性问题,比如死锁、饥饿等
性能:线程过多时会使得 CPU频繁切换、调度时间增多、同步机制消耗过多内存
编程三要素
原子性
一个操作一旦开始,就不会被其他线程干扰
可见性
一个线程修改了共享变量,其他线程能立即知道
volatile(缓存一致性协议)
有序性
因为程序在执行时,可能进行【指令重排】,可能会与原指令顺序不一样
volatile(禁止指令重排)
java内存模型/JMM
JMM是针对多线程环境下对【共享变量】操作约定的抽象概念,它规定了线程何时及如何访问【共享变量】,以及在必须同步时如何同步这些变量;
概念
与java并发编程有关,定义了多个线程如何访问操作【共享变量】,以及如何同步访问这些共享变量
描述的是一组规则或规范;定义了程序中各个变量的访问方式;
一个线程何时和如何能看到其他线程共享变量的值,以及在必须时如何同步访问这些【共享变量】
一个线程何时和如何能看到其他线程共享变量的值,以及在必须时如何同步访问这些【共享变量】
java的【多线程之间通过共享内存通信】,会存在一系列问题如原子性、可见性有序性等问题,
JMM就是围绕着多线程通信及相关的一系列特性而建立的模型;
JMM就是围绕着多线程通信及相关的一系列特性而建立的模型;
规定
所有变量存在【主内存】,每个线程都有自己的【工作内存{(虚拟机栈部分区域)】,工作内存中保存了该线程使用的变量【主内存副本的拷贝】
线程中对变量的操作(读取。赋值)都必须在工作内存进行
主内存:对应于java堆中的对象实例数据
工作内存:抽象概念不是真实存在,对应于虚拟机栈中的部分区域
8种同步操作及8种同步原则
JMM定义了一些语法集,映射到java语言中就是volatile、synchronized等关键字
volatile
use之前必须先load,即每次在工作内存使用值必须先从主内存取最新值;保证能看到其他线程最新的修改
assign之后必须store,即每次工作内存为变量赋值后必须将变量值同步回主内存;保证其他线程看到最新修改
修饰的变量不会被指令重排序优化
synchronized
对象头 mark word
同步器
Lock
synchronized
volatile
线程不安全的类
非线程安全的类
StringBuilder,SimpleDateFormat,ArrayList,HashSet,HashMap
线程安全的类
StringBuffer,DateTimeFormatter,CopyOnWriteArrayList
并发容器
HashMap —> ConcurrentHashMap
通过锁分段技术保证并发环境下的写操作
通过 HashEntry的不变性、Volatile变量的内存可见性和加锁重读机制保证高效、安全的读操作;
通过不加锁和加锁两种方案控制跨段操作的的安全性
TreeMap —> ConcurrentSkipListMap
跳表是一种用于快速查询的,类似于平衡树
平衡树的插入和删除往往可能导致对其进行全局调整,而跳表的插入和删除只需要对整个数据结构的局部进行调整
ArrayList —> CopyOnWriteArrayList
在读多写少的场合,List 性能非常好,远远好于 Vector
通过可重入锁ReentrantLock保证同步
只有写和写之间需要同步等待
HashSet—> CopyOnWriteArraySet
CopyOnWriteArraySet 底层实现 CopyOnWriteArrayList
TreeSet —> ConcurrentSkipListSet
ConcurrentSkipListSet 底层实现是 ConcurrentSkipListMap
BlockingQueue
JDK 内部通过链表,数组等方式实现,表示阻塞队列,非常适合作为数据共享的通道。
ConcurrentLinkedQueue
高效读写队列,线程安全没有任何锁操作,完全由cas操作和队列算法保证
Collections.synchronizedXXX[List、Set、Map]
包装后的集合,读写操作加上synchronized关键字
锁的优化及jvm对锁的优化锁做的努力
锁优化
1.减小锁持有时间,关键代码需同步的方法加锁
2.减小锁粒度
所谓减小锁粒度,就是缩小锁对象的范围,从而减少锁冲突的可能性,进而提高系统的并发能力。
eg.(ConcurrentHashMap);
不是对整个hashMap加锁,分段hashEntry、segment,写时只对某个段加锁不影响其他段的操作
不是对整个hashMap加锁,分段hashEntry、segment,写时只对某个段加锁不影响其他段的操作
3.读写分离锁类代替独占锁
ReentrantReadWriteLock,读读并行,读写互斥
减小锁粒度的一种特殊情况
在读多写少的场合,使用读写锁可以有效提升系统并发能力。
4.锁分离
将读写锁思想进一步延伸,就是锁分离;根据操作功能的不同两把锁
eg.LinkedBlockingQueue,在jdk思想中采用的是分离锁思想,使用两把不同的锁分离take()和put()操作;
只能一个线程操作,empty或者full时阻塞,根据condition通知唤醒操作
只能一个线程操作,empty或者full时阻塞,根据condition通知唤醒操作
5.锁粗化
jvm在遇到一连串连续地对同一锁不断请求和释放的操作时,
便会把所有的锁操作整合成对锁的一次性请求,从而减少对锁的请求同步次数
便会把所有的锁操作整合成对锁的一次性请求,从而减少对锁的请求同步次数
锁粗化的思想和减少锁持有时间是相反的,但在不同场合,他们效果并不相同,所以大家需要根据实际情况进行权衡。
jvm对锁优化所做的努力
使用synchronized时并不是直接就是重量级锁;
无锁状态->偏向锁状态->轻量级锁(自旋)->重量级锁(操作系统的互斥)
无锁状态->偏向锁状态->轻量级锁(自旋)->重量级锁(操作系统的互斥)
对象头(Object Header)
Mark Word:存储对象自身运行时数据,如hashCode、GC分代年龄、【锁信息(是否偏向锁、锁标志位)】
根据对象状态复用存储空间;
根据对象状态复用存储空间;
存储指向方法区对象class类型数据的指针,如果是数组的话,还额外存储数组的长度
偏向锁
核心思想
1.当线程请求到锁对象后,将锁对象的状态标志位改为 01,即偏向模式
2.然后使用 CAS 操作将线程的 ID 记录在锁对象的 Mark Word 中
3.以后该线程可以直接进入同步块,连CAS操作都不需要
4.但是,一旦有第二条线程需要竞争锁,那么偏向模式立即结束,进入轻量级锁的状态。
与轻量级锁的区别
轻量级锁是在无竞争的情况下使用CAS操作来代替互斥量的使用,从而实现同步;
而偏向锁是在无竞争的情况下完全取消同步原语。
作用
偏向锁是为了消除无竞争情况下的同步原语,进一步提升程序性能
优点
偏向锁可以提高有同步但没有竞争的程序性能
但是如果锁对象时常被多条线程竞争,那偏向锁就是多余的。
轻量级锁
核心思想
1.将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有锁对象
2.如果线程获得轻量级锁成功,则可以顺利进入【临界区】
3.如果获得轻量级锁失败,则表示其他线程先抢到了锁,那么线程的锁请求就会膨胀为重量级锁
前提
轻量级锁比重量级锁性能更高的前提是,在轻量级锁被占用的整个同步周期内,不存在其他线程的竞争。
若在该过程中一旦有其他线程竞争,那么就会膨胀成重量级锁,从而除了使用互斥量以外,还额外发生了CAS操作,因此更慢!
与重量级锁比较
重量级
悲观锁,它认为总是有多条线程在竞争锁
每次处理共享数据时,不管系统是否真的有线程在竞争锁,它都会使用互斥同步保证线程的安全
轻量级锁
乐观锁,认为锁存在竞争的概率比较小
不使用互斥同步,使用CAS来操作获得锁,这样能减少互斥同步所使用的『互斥量』带来的性能开销
eg.synchronized同步代码块,升级过程
1、线程1检查锁对象的mark word,通过标志位看此时是无锁状态
2、线程1用cas操作,mark word记录自己的线程ID,获得偏向锁
3、线程2过来竞争执行代码块,首先cas修改自己线程ID失败
4、此时锁升级为轻量级锁,拷贝锁对象mark word到线程1的锁记录,mark word锁记录指针指向线程1的锁记录
5、线程2继续cas操作,尝试将对象头mark word锁记录指针修改为自己的锁记录地址
6、cas自旋操作达到一定次数后没有修改成功则会升级为重量级锁,依赖操作系统的互斥实现同步
自旋锁
eg.锁膨胀后,JVM 为避免线程真正在操作系统层面挂起,JVM 做了最后的努力 — 自旋锁
核心思想:自旋cas
优点
由于自旋等待锁的过程中并不会引起上下文切换,因此比较高效
缺点
CPU资源浪费:自旋过程中一直占用cpu但不执行任何任务,此过程过长就会造成浪费
自适应自旋
自适应自旋可以根据以往自旋等待时间的经验,计算出一个较为合理的本次自旋等待时间。
锁消除
更彻底的锁优化
jvm在JIT编译时,通过对运行上下文的扫描,去除不可能存在的共享资源竞争的锁
通过锁消除,可以节省毫无意义的请求锁的时间
如果不可能产生竞争,为什么还要加锁?
在 Java 开发中,我们必然会使用 JDK 内置的 API,如 StringBuffer、Vector 等。
在使用这些类时,也许根本不会考虑这些对象到底内部是如何实现的。
但是 Vector 内部使用了 synchronized 请求锁。
JDK(rt.jar)
java.lang
object
基类
int hashCode()
boolean equals(Object obj)
Object clone()
浅拷贝,对象里面的field不会clone一个新的实例
String toString()
void wait()
void notify()
Throwable
Exception
表示程序可以处理的异常,遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
catch里return,finally先还是return先
在try和catch中有return,finally中没有return
try、catch要return的计算后的值保存起来,先finally再把值return
返回的数据为基本数据类型,则finally中对要返回数据操作无影响
返回的数据为引用数据类型,finally中如果改变了返回对象的属性则影响结果,
如果改变的是对象的引用则和基本数据类型一样不改变结果
如果改变的是对象的引用则和基本数据类型一样不改变结果
在try和catch中有return,finally中也有return
try/catch中return先存起来没有返回,而finally执行时return了
在try中有return,在catch中新抛出异常,finally中有return
catch中的throw会失效,上级捕获不到,finally返回
两类
运行时异常(Runtime Exception)
编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止
eg.ArithmaticException、IllegalArgumentException
受检查的异常(Checked Exception)
要么try{}catch()捕获,要么用throws抛给父类处理,否则编译不会通过
eg.ArrayIndexOutOfBoundsException、NullPointerException
Error
一般指和虚拟机相关的问题,如系统奔溃、虚拟机错误、内存空间不足、方法调用栈溢出等
可以捕获,但是仅靠程序本身无法恢复和预防,遇到这样的错误建议让程序终止
Runnable
接口
void run()
Thread
实现了Runnable接口,并进行扩展
创建线程方式
继承Thread类,重写run方法
实现Runnable接口,通过new Thread(Runnable)新建
void run()
.start()
String
不可变,final修饰,内部维护final的字符数组
String a = “111” + “222”
常量池中直接创建新的字符串,再将引用赋值给a
String s1 = "abc";
String s2 = "def";
String s4 = "abc" + s2;
String s5 = s1 + s2;
String s2 = "def";
String s4 = "abc" + s2;
String s5 = s1 + s2;
如果含有字符串变量的拼接,在java中其实是通过StringBuilder创建了一个新的String对象
java.io
面向流Stream、阻塞
直接用来传输数据,一个一个byte流动
单向(InputStream、OutputStream)
java.nio
jdk1.4引入
面向缓冲区(Buffer)基于通道(channel)的io操作
channel
打开到IO设备(文件、套接字)的【连接】
负责传输,必须有缓冲区
双向、一个channel既可以读,也可以写
主要实现类
FileChannel
SocketChannel
ServerSocketChannel
主要方法getChannel()
主要方法
read(buffer)
write(buffer)
read(buffer[])
分散读取(Scattering reads)
channel中读取的数据分散到多个buffer
write(buffer[])
聚集写入(Gathering writes)
将多个buffer中的数据聚集到channel
buffer
负责存储
子类
ByteBuffer
CharBuffer
IntBuffer
主要方法
allocate
allocateDirect
isDirect
clear
put
flip(切换为读)
get
SelectableChannel+Selector(NonBlocking IO)
SelectableChannel
可通过Selector被多路复用的Channel
ServerSocketChannel、SocketChannel
阻塞/非阻塞模式
阻塞
每一个I/O操作在所属Channel调用会阻塞,直到完成
非阻塞
所属channel调用I/O操作不会阻塞,可以传送比被要求字节更少的字节
Selector
提供选择执行已经就绪的任务的能力,使得多元 I/O 成为可能;使得单线程可以同时管理多个IO channe
当并发量大,单个线程不能及时处理就绪事件,可能出现拒绝连接的情况;更好的办法是将就绪的Channel事件委托给其他线程处理
Selector.open()
SelectionKey SelectableChannel.register(Selector sel, int ops)
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
SelectionKey
封装了特定的channel与selector的注册关系
Object attachment
eg.客户端/服务端通讯
1、服务器端创建ServerSocketChannel,绑定端口,设置为非阻塞模式
2、Selector.open();创建一个多路复用事件选择器,ServerSocketChannel将感兴趣的ACCEPT注册到Selector中
3、服务器端轮休调用selector.select()>0判断有没有注册的事件就绪
4、当有客户端请求服务器连接进来时,这时服务器端感兴趣的ACCEPT就会就绪,拿到这个事件交给连接处理器处理
5、连接处理器成功处理完SocketChannel建立请求的操作后,将SocketChannel感兴趣的READ事件->Selector中
6、当客户端有请求数据发过来时,SocketChannel关心的READ事件就绪,然后就可以交给专门的读处理器处理
7、读处理器通过SocketChannel读取到客户端发来的数据后,处理得到结果后,再注册个自己感兴趣的WRITE事件->Selector
8、当SocketChannel关心的WRITE事件就绪,可以专门交给响应处理器,响应处理器处理完后就可以关闭连接了
java.net
Socket(Blocking IO)
是什么
是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元,网络通信端点的抽象表示
socket,实际上是对TCP/IP协议的封装,本身并不是协议,而是一个调用接口(API),通过它我们才能使用TCP/IP协议
通信流程
1、服务器端创建ServerSocket,监听一个端口(0-65536),accept等待连接
2、客户端想要与服务端通信,首先要根据服务器的IP端口创建一个Socket
3、服务器端监听到有客户端socket连接了会建立连接
4、然后双方就可以通过输入流输出流进行数据的交流
最基本的函数接口,比如create、listen、accept、send、read和write等等
accept是阻塞的,只有新连接进来了,才能返回,主线程才能继续
read是阻塞的,只有请求消息来了,才能返回,read线程才能继续
write是阻塞的,只有客户端把消息接收了,才能返回,write线程才能继续处理下一个
java.security
java.sql
java.time
java.util
遍历
for
for each(E e : list)
编译为iterator
forEach->iterable
还是会转成for each
collection
Collections
HashMap
数据结构
数组+链表
Entry(key,v,hash,next指针)
通过hash值求出entry位于数组的index,index相同就会形成一个链表
数组存放的就是index相同的链表的头节点指针
方法
new
默认1<<4,0.75
put
数组为空的话新建entry数组,size=大于等于初始化hashmap的size的2的幂次方
1、通过key计算hash值,由hashSeed和key.hashCode()得到
2、通过hash值和数组长度计算出下标index(hash&(length-1))
为什么数组大小为2的幂次方,为了使用&位运算得到下标,而不是取余,性能好
3、通过index找到entry链表,遍历,如果key重复则覆盖并返回旧的val
4、key不重复时,需不需要扩容
5、扩容
1、需要:当前使用的数组size>=threshold(阈值,数组length(16)*负载因子loadfactor0.75),
以及要插入的index不为空
以及要插入的index不为空
2、新建数组,2*table.length
3、transfer转移元素
rehash(initHashSeedAsNeeded,改变hashSeed),
满足capacity>阈值(通过jvm参数-Djdk.map.althashing.threshold=n设置,
默认为Inter.MAX_VALUE)
默认为Inter.MAX_VALUE)
遍历entrytable数组
遍历table[i]链表
计算index
1.7
&位运算,hash&(table.length-1)
1.8
按照规律直接知道index
插入新table
1.7死循环
【头插法】可能循环链表,引起死循环,cpu一直飙高
1、ThreadA读到副本oldTable[1]{A->B->C},执行扩容,执行代码{e=A,next=e.next=B}此时线程A挂起
2、Thread2开始执行读到,副本oldTable[1]{A->B->C},也会扩容,假如并且完成了扩容,newBTable[2]={C->B->A}
3、ThreadA开始继续执行,继续执行代码{e.next=newATable[2];头插;newATable[2]=e;e=next=B},
由于ThreadB完成扩容使得B->A,继续执行下去ThreadA就会产生一个新的循环链表
由于ThreadB完成扩容使得B->A,继续执行下去ThreadA就会产生一个新的循环链表
4、当ThreadC通过get(key)落在newTable[2]这条链遍历就会无限循环
1.8扩容使用【尾插法】不会出现循环链表
6、插入节点,new entry ()节点,next指向头的next,然后头的next指向新的
modCount
修改次数
ConcurrentModificationException
用来检查多线程在使用hashmap的数据同步问题,有问题抛异常
eg.一个线程遍历,另一个线程修改,就会跑异常
map
concurrent
线程池ThreadPool
生命周期
running
当线程池创建的初始状态
能接受任务,能执行阻塞任务,能执行正在执行的任务
shutdown
调用shutdown()方法,平缓关闭
不接受新任务,能执行阻塞任务,能执行正在执行的任务
stop
调用shutdownNow()
不接受新任务,打断正在执行的任务,丢弃阻塞任务
tidying
中间状态,整理
任务全部执行完成,活动线程也没了
terminated
终结状态
线程池终结
ExecutorService创建方式
new ThreadPoolExecutor
int corePoolSize
设置多大合适
IO密集型任务
io会阻塞,如果线程数太少任务越多越慢(eg.10个任务让1个线程执行与让10个线程执行)
(1+线程等待时间/线程CPU时间 )* CPU占有率 * CPU核心数 (0 < CPU占有率 <=1)
CPU密集型
涉及计算多,快速;设置线程太多会频繁切换上下文,增加额外开销
CPU核数+1
先预估值,通过线上进行监控日志,通过实际情况来调整试验,寻求高吞吐量
int maximumPoolSize
long keepAliveTime
>corePoolSize线程的存活时间
TimeUnit unit
BlockingQueue(Runnable) workQueue
ArrayBlockingQueue
数组(连续空间),FIFO
有界,构造方法必须设置size
一个对象数组+一把锁+两个条件(notEmpty/notFull)
在只有入队高并发或出队高并发的情况下,因为操作数组,且不需要扩容,性能很高
LinkedBlockingQueue
链表,FIFO
无界,构造函数可以不设置size
一个单向链表+两把锁(takeLock/putLock)+两个条件(notEmpty/notFull)
吞吐量高,在入队与出队都高并发的情况下,性能比ArrayBlockingQueue高很多
SynchronousQueue
不存储元素
LockSupport
每个插入操作必须等到另一个线程调用移除操作
吞吐量高于LinkedBlockingQueue
PriorityBlockingQueue
具有优先级的阻塞队列
无界
通过二叉树最小堆实现优先级
自定义优先级->元素对象实现Comparable重写compareTo
ThreadFactory threadFactory
默认线程名称,pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)
程序创建新线程时的工程,自定义线程名称
RejectedExecutionHandler handler
AbortPolicy(默认),抛出异常
CallerRunsPolicy,用提交任务的线程运行任务
DiscardOldestPolicy,丢弃一个将要立即执行的任务,并尝试再次提交任务
DiscardPolicy,不处理丢弃掉
Executors
不支持自定义拒绝策略
newFixedThredPool
workQueue=LinkedBlockingQueue,不可设置大小 -> OOM
corePoolSize=maximumPoolSize=n,keepAliveTime=0ms
适用于任务量已知,相对耗时的任务
newCachedThreadPool
corePoolSize=1,keepAliveTime=60s
最大线程数maximumPoolSize=Integer.MAX_VALUE -> OOM
workQueue=SynchronousQueue
newScheduledThreadPool
corePoolSize=maximumPoolSize=n,keepAliveTime=0ns
workQueue=DelayedWorkQueue, 无界 -> OOM
newSingleThreadExecutor
corePoolSize=maximumPoolSize=1,keepAliveTime=0ms
workQueue=LinkedBlockingQueue,不可设置大小 -> OOM
ScheduledThreadPoolExecutor
该接口定义了可延时执行异步任务和可周期执行异步任务的特有功能
scheduleAtFixedRate
延迟3s,然后每隔2s启动个线程去执行
scheduleWithFixedDelay
延迟3s,任务执行完毕后2s后再继续这样执行后面的
执行流程
1.是否=coreThreadNum,若否则创建新线程执行任务,否则执行2
2.是否=blockingqueue,若否则放入队列等待执行,否则执行3
3.是否=maxThreadNum,若否则新建线程执行任务,否则拒绝
CompletableFuture
传统:future.get()阻塞,否则得轮询判断isDone()
Jdk1.8新引入
针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
Future 实现阻塞等待获取结果的原理?
Future<?> = (excutorService.submit(Task))
表示一个任务的生命周期
get()、isDone()、isCanceled()、、、
FutureTask ----> RunnableFuture ——> Runnable、Future
内部维护了任务状态state、任务执行结果Object outcome;
threadA调用future.get()首先根据任务state判断有没有完成,没有的话调用LockSupport.park(this)阻塞等待
run()异步执行线程池.submit(task)的任务,执行完将结果set到outcome,同时调用LockSupport.unpark(t)通知阻塞的线程获得结果
使用场景
1.订单、产品详情
2.其他并行场景、、、、
实现原理
worker
queue
CAS
概念
Compare and Swap
Unsafe.getUnsafe();native方法,unsafe.compareAndSwapInt
乐观锁技术;非阻塞算法的一种常见实现,相对于synchronized这种阻塞算法,性能更好
当多个线程尝试通过CAS更新一个变量时,只有一个线程能更新成功,其他线程更新失败会再次尝试(非阻塞)
并发高耗cpu
原理
三个操作数:内存位置(V),预期原值(A)和新值(B)
当前仅当预期原值A和内存位置V的值相同时,将V上的值修改为新值B
ABA问题
eg.如果内存位置V的值先由A变为B,再由B变为A,那么仍然要认为是发生了变化
解决办法:再加一个版本号,更新地址V的值时同时把版本号+1
atomic
AtomicInteger(CAS)
getAndIncrement()
getAndDecrement()
LongAdder
方法
increment()
decrement()
add(1L)
高并发情况下,性能优于atomic
原理:这样热点就进行了有效的分离,提高了并行度。将竞争分散到热点key;
类似ConcurrentHashMap思想
类似ConcurrentHashMap思想
1.将AtomicInteger的内部核心数据value分离成一个cell数组
2.每个线程访问时,通过哈希等算法映射到其中一个数字进行计数
3.而最终的计数结果,则为这个数组的求和累加
locks
AQS(AbstractQueuedSynchronizer)
state状态的维护。
volatile保证线程可见性
getState()、setState()方法用final修饰,禁止AQS的子类重写
compareAndSetState(this,stateOffset,expect, update)采用乐观锁思想CAS算法,也用final修饰
CLH队列
同步队列,FIFO双向链表,tail、head
队列元素类型为Node
当前线程A使用CAS修改state失败后创建Node加入队列(cas)等待,当state变更释放时,first被唤醒(公平锁)再次尝试修改state
ConditionObject通知
实现了Condition接口,给AQS提供条件变量的支持
维护了一个单独的等待队列,节点类型也是Node
同步模式:独占与共享模式
独占
同一时刻仅有一个线程持有同步状态,如ReentrantLock。又可分为公平锁和非公平锁。
共享式
多个线程可同时执行,如Semaphore/CountDownLatch等都是共享式的产物。
模板方法设计模式
Condition
lock.newCondition();获取对象
有await()、signal()、signalAll()方法可以实现等待通知机制;等待队列
LockSupport
park()、unpark(thread)对一个线程阻塞和唤醒,底层实现机制是调用native的park和unpark
ReentrantLock
jdk提供的api
底层基于AQS实现,尝试获得锁(修改state),获取不到放到等待队列等待,锁释放,队列里的线程再次去尝试获取
手动加锁和释放锁
可以指定公平锁还是非公平锁
synchronized
由编译器保证锁的加锁和释放
只能非公平锁
自从进行了优化(无锁、偏向锁、轻量级锁、重量级锁),性能提升了不少
原理
monitor:操作系统级别的同步机制;重量级
同步代码块(编译后字节码代码块monitorenter、monitorexit),代码执行到monitorenter需要获得monitor锁
同步方法是通过中设置ACCSYNCHRONIZED标志来实现,当线程执行有ACCSYNCHRONI标志的方法,需要获得monitor锁
Object的wait()(进入线程等待池等待,释放已占资源),notify(),notifyAll()可以实现等待通知模式
sleep()属于Thread的方法,不会释放获得的锁资源
ReentrantReadWriteLock
ConcurrentHashMap
jdk7
内部采用Segment(粒度:多个HashEntry)的数据结构
两次hash操作,第一次定位到segment,第二次定位到数组元素头结点位置
非查询操作会锁住相应的segment;假如有n个segment,那它可以支持n个线程同时对该map执行写操作
jdk8
不采用segment;采用CAS算法;降低锁粒度到单个HashEntry(链表头结点)
1、根据keyHashCode及tableLength&操作算出来index
2、如果table[index]为空,则使用cas操作设置值;如果不为空的话用synchronized锁住table[index]这条链的头结点
使用红黑树进行优化链表;当链表长度达到阈值8就会转为红黑树,查询效率高
辅助扩容
缺陷
读操作完全非阻塞,更新发现不及时
优点
相比HashTable并发度高,synchronized对整个table锁住,get/put同时只允许一个线程
局部锁
CopyOnWriteArrayList
ForkJoinPool
CountDownLatch
功能
到计数门闩
控制线程等待;可以让某个线程等待其他线程任务执行完成之后再执行
基本方法
new CountDownLatch(Integer)
countDown();-1
await();阻塞,直到countDown()减为0
await(long timeout, TimeUnit unit)
CyclicBarrier
功能
一组线程执行相同的几个任务;让这些先同时执行前几个任务;等他们把前几个任务都执行完毕后再同时去执行最后一个
一般用于一组线程互相等待至某个状态,然后这组线程再同时执行
方法
new CylicBarrier(Integer)
new CylicBarrier(Integer,Runnable)
/当四个线程都到达 barrier 状态后,会从四个线程中选择一个线程去执行 Runnable。
await();+1;每个线程执行再这里会阻塞,直到所有线程都执行到这步
await(long timeout, TimeUnit unit)
Semaphore
功能
信号量
限流
和锁有点类似,它一般用于控制对某组资源的访问权限
eg.5个机器8个人操作,一个机器只能一个人操作
方法
Semaphore(int permits);可以重用,交给另一组线程用
acquire();获取一个许可
acquire(int permits);获取几个许可
release();释放一个许可
release(int permits);释放几个许可
availablePermits();得到可用的许可数目
function
spi
stream
zip
关键字
final
可修饰 类(不能被继承),方法(不能被重写),变量(不能被改变)
好处
提高性能,jvm和java应用都会缓存final变量
多线程环境下共享不需要额外同步开销
使用final关键字,jvm会对方法变量及类进行优化
extends、implements
接口可以多继承接口
类只能单继承另外一个类,可实现多个接口
synchronized
volatile
运算符
>>n
右移,低位去除n位,相当于/2的n次
>>>n
无符号右移
<<n
左移,低位补充n位0,相当于*2的n次
<<<n
无符号左移
类图uml
泛化(Generalization),继承
空心三角+实线
实现(Realization)
空心三角+虚线
依赖(Dependence)
箭头+虚线,箭头指向被依赖对象
关联(Association)
单向关联
箭头+实线,箭头指向被引用或者被包含类
双向关联
不带箭头的实线
聚合(Aggregation),特殊的关联
空心菱形+实线,菱形指向整体
强调部分与整体的关系,部分可以脱离整体存在
组合(Composition),特殊关联
实心菱形+实线
部分无法脱离整体存在
动态代理
JDK动态代理
interface
InvocationHandler
Proxy
CgLib动态代理
字节码处理框架ASM
为代理类创建子类
代理对象性能比JDK强,但是创建代理对象花费的时间比JDK长
实际应用
安全
密码学
对称/非对称加、解密
对称:加、解密使用同一个秘钥(私钥)
AES、DES
非对称:加、解密使用不同的秘钥,一把公开的秘钥作为公钥,另一把私有的秘钥作为私钥;
公钥加密的数据只能私钥解密,同样私钥加密的信息也只能公钥解密
公钥加密的数据只能私钥解密,同样私钥加密的信息也只能公钥解密
RSA
摘要算法
验证篡改、完整性
MD5
子主题
网络攻击
注入式攻击
如SQL注入
跨站点脚本式攻击
攻击者劫持用户会话修改网站,插入恶意内容,进行网络钓鱼和恶意软件的攻击等等
,获取恶意用户发的文章帖子含有恶意脚本;当正常用户浏览到该帖子他的cookie等会话信息就可能被恶意收集到
eg.如一个论坛网站有存在一个GET请求方式的转账操作
接口参数是toUser=100&money=1000
接口参数是toUser=100&money=1000
1、A用户登录了自己的论坛,此时cookie会话信息已保存
2、B用户发了一个帖子,里面有一个脚本,可以直接调用转账的GET接口地址
(如一个<script>window.open(“”)</script>或者一个<img src=“URL”/>标签 )
(如一个<script>window.open(“”)</script>或者一个<img src=“URL”/>标签 )
3、A用户浏览了这个帖子,此时访问到这个标签后浏览器就会携带者会话信息成功请求接口
用户提交数据时检查有没有恶意脚本
接口设计
对外提供接口的接入规范
ip白名单
获取令牌
通过派发三方appid、secret
生成令牌ticket并返回,设置过期时间7200s
业务接口
通过请求头携带令牌来调接口;
验证令牌有效性,获得三方用户身份;
参数签名验证;
用户身份有效性验证;
执行业务方法
验证令牌有效性,获得三方用户身份;
参数签名验证;
用户身份有效性验证;
执行业务方法
RESTful API 设计
核心概念:URL定位具体资源,不出现动词,具体对这个资源干什么用HTTP请求方式动词(GET,POST,DELETE,DETC)描述操作。
eg.URL:api/orders
GET:获取订单
POST:新建订单
PUT:更新订单
DELETE:删除订单
各种辅助图
流程图
业务流程图
功能流程图
页面流程图
数据/程序流程图
时序图
作用
展示对象之间的交互顺序
相比其他uml图,时序图更强调交互的时间顺序
可以直观的描述并发进程
画法
划清边界
将角色和对象梳理出来
依次画出消息,标上参数
泳道图
技术栈
spring相关
spring容器
需要实例哪些
判断有没有那些注解,如@Component、@Service、@Configuration。。。。。
ioc
依赖注入
注入方式
构造函数依赖注入
xml方式<bean id="">
注解标识组件(@Component、@Service。。。);自动推断构造器进行new实例化
Setter方法依赖注入
自动装配依赖注入
@autowired注解依赖注入等多种实现方式
技术
jdk反射
jdk1.8以下通过反射得不到具体的参数名称
ASM
反射?
asm直接处理.class字节码,非class对象
可以对class进行写
不需要将类加载到内存;反射需要
反射相对于asm来说使用方便,想直接操作ASM的话需要有jvm指令基础
作用
可以在代码运行的过程中读取响应的信息
修改字节码
AOP,cglib等都需要ASM读取、操作java类。
eg.
如图;HttpServer.class 在classpath路径下不存在;
如果通过反射的话,需要先将EmbeddedWebServerFactoryCustomizerAutoConfiguration类加载到内存;
而箭头指向的HttpServer.class并不存在,所以会报错
使用asm技术就可以直接读取.class字节码,就能得到对应的属性
@Resource和@Autowired
@Resource:默认通过名称注入,名称没有找到则通过类型
@Autowired:默认通过类型注入,类型有多个实现类则通过变量名称
循环依赖
setter注入,无参构造器
三级缓存
1、一级缓存,单例池(singletonObjects),存储初始化之后的最终bean
2、二级缓存,(earlySingletonObjects)存储还没有经过完整生命周期的单例bean
3、三级缓存,singletonFactories(beanName,Supplier(该函数返回普通实例还是代理实例))
实例化和属性赋值是分开的
构造方法属性注入
会产生循环依赖
解决:构造函数加@Lazy
作用域为原型模式且为构造器属性注入
会产生循环
@Async为什么会导致循环依赖解决不了?
spring初始化创建bean时循环依赖已经通过三级缓存存储了bean的普通(或@AspectJ代理)对象的函数,
之后会通过这个函数拿到对象放入二级缓存
之后会通过这个函数拿到对象放入二级缓存
加了@Async注解的方法,也是需要生成代理对象,最后两个bean不一样了,不符合单例bean
@Lazy加在需要异步方法类的产生循环的注入属性上,不会出现循环依赖
解决原理
三级缓存
一级缓存 : Map<String,Object> singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例
二级缓存 : Map earlySingletonObjects,早期曝光对象,用于保存实例化完成的 bean 实例
三级缓存 : Map> singletonFactories,早期曝光对象工厂,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象。
eg.对象A与对象B循环依赖
1、创建A实例,实例化的时候把A对象⼯⼚放⼊三级缓存,表示A开始实例化了
2、A注⼊属性时,发现依赖B,此时B还没有被创建出来,所以去实例化B
3、B注⼊属性时发现依赖A,它就会从缓存里找A对象。依次从⼀级到三级缓存查询A,从三级缓存通过对象⼯⼚拿到A,发现A虽然不太完善,但是存在,把A放⼊⼆级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入⼀级缓存。
4、接着A继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除⼆级缓存中的A,同时把A放⼊⼀级缓存
5、最后,⼀级缓存中保存着实例化、初始化都完成的A、B对象
aop
使用
使用
开启动态代理@EnableAspectJAutoProxy
切面处理类
类@Aspect
方法@Before
原理
cglib
事物底层
事物注解失效
eg.类中普通方法调事务方法;事务方法调事务,后者失效
区分是谁在调事物方法;target是普通对象
解决
把被调用方法新建其他类bean
把调用方法所在类也注入到该类,自己注入自己
@Configuration
springAOP与AspectJ
spring
运行时增强
是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 的那么好。
AspectJ
编译时增强
AspectJ 属于静态织入,通过修改代码来实现,在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的
bean对象
bean作用域
singleton(单例)模式
默认
spring ioc容器只会存在一个共享的bean
多线程下不安全,不建议多线程去修改bean属性值
prototype(原型)模式
每次使用时都会创建一个新的bean
根据经验,对有状态的bean使用该模式,无状态用单例
request
同一个http请求使用同一个bean,不同的http请求会产生新的bean在那个请求中一直使用
该bean仅在当前Http Request中有效,请求结束会销毁
session
同一次http session使用同一个bean,不同session创建新bean
global session
在一个全局的http session中,容器会返回该bean的同一个实例,仅在使用portlet context时有效
bean生命周期
1.实例化,构造方法new实例
2.IOC依赖注入
3.setBeanName实现
如果这个bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法
4.BeanFactoryAware实现
若实现了BeanFactoryAware,则会调用它实现的setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用它获取其他bean做一些事情)
5.ApplicationContextAware实现
如果实现了这个类,则会调用setApplicationContext(ApplicationContext)传入Spring上下文(可以实现步骤4的功能,是BeanFactory的子接口,有更多的实现)
6.postProcessBeforeInitializion 接口实现-初始化预处理
如果这个Bean关联了BeanPostProcessor接口将会调用该方法,Bean初始化结束时调用(可以被应用于内存或缓存技术)
7.init-method
如果 Bean 在 Spring 配置文件中配置了 init-method 属性会自动调用其配置的初始化方法。
8.postProcessAfterInitialization
如果这个bean关联了BeanPostProcessor接口,将会调用此方法
9.Destroy过期自动清理阶段
当bean不需要时,会经过清理阶段,如果bean实现了DisposableBean接口,会调用其那个实现的destory()方法
10.destroy-method自配置清理
最后,如果这个bean的spring配置文件中配置了destroy-method属性,会自动调用其配置的方法
11.bean 标签有两个重要的属性(init-method 和 destroy-method)
用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct 和@PreDestroy)。
如何创建bean对象
doCreateBean()
doCreateBean()
1、实例化:推断构造方法创建【普通对象】
当类中有多个构造方法
1、有无参用无参
2、没有无参报错
3、@Autowired加哪个用哪个
1、有无参用无参
2、没有无参报错
3、@Autowired加哪个用哪个
当类中只有一个构造方法,就用这个
构造方法的参数注入,先bytype,若多个类型相同再byname
2、初始化
1、依赖属性注入:反射扫描哪些字段有@Autowired等注解,反射给依赖属性set值;bytype,byname
2、初始化前:@PostConstruct加在操作方法上,反射遍历找到该注解方法,反射执行方法
3、初始化bean:impl InitializingBean overwrit afterPropertiesSet
4、初始化后:有aop则会产生【代理对象】
3、初始化后将【普通/代理对象】放入单例池map
启动
先启动tomcat,tomcat读取web.xml创建spring容器,
web.xml
【创建spring容器】
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
createApplicationContext();
servlet,使用spring-webmvc,创建servlet容器
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/conf/spring/web/web-main.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>(负数或无请求时初始化,否则程序启动时初始化)
</servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/conf/spring/web/web-main.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>(负数或无请求时初始化,否则程序启动时初始化)
</servlet>
springmvc
底层请求原理
1.tomcat启动
2.解析web.xml,根据listener初始化spring父容器
3.实例化dispatcherServlet,执行init()进行初始化创建spring容器
构建项目
1、引入spring和Springmvc相关依赖jar
2、webapp/WEB-INF/web.xml;
>配置listener,使用spring的ContextLoaderListener并配置参数指向spring配置文件,初始化spring父容器【可以不需要,放一个配置文件】
>配置一个servlet指向springmvc的DispatcherServlet,设置初始化参数指向Springmvc配置文件,并配置servlet的url映射路径;
>配置一个servlet指向springmvc的DispatcherServlet,设置初始化参数指向Springmvc配置文件,并配置servlet的url映射路径;
3、创建springmvc配置文件,扫描某个包下的java类,即controller
4、用tomcat启动运行项目
springboot
是什么
spring的引导,是spring的一套快速配置脚手架,可以基于springboot快速开发单个微服务
为简化spring项目开发而设计的框架,在需要用到某些功能(如aop,redis等)时不需要额外配置很多配置文件,只需要引入官方提供的starter或者三方starter
内置tomcat、jetty
eg.回顾传统搭建SSM项目
1、配置web.xml,加载spring容器和springMvc容器
2、配置数据库连接、配置spring事物
3、配置加载配置文件的读取,开启注解等等
4、配置完成后需要先启动tomcat程序才能运行
而通过springBoot则只需要引入相应的starter以及通过一些注解直接运行启动类就可以搭建一个SSM项目了
主要特性
自动装配
@SpringBootApplication
1. @Configuration
配置类
2. @EnableAutoConfiguration
为了实现【自动装配】
3. @ComponentScan
相当于 xml 配置文件中的<context:component-scan>
@EnableAutoConfiguration
作用
帮助springboot应用把所有符合@Configuration都加载到当前springboot创建的IOC容器
原理
借助spring框架提供的SpringFactoriesLoader(spi)
用到了 Spring 提供的条件注解@Conditional,选择性的针对需要加载的 bean 进行条件过滤
@Import(AutoConfigurationImportSelector.class)将第三方提供的 bean 的配置类AutoConfigurationImportSelector导入IOC。
spring的SPI扩展机制
SpringFactoriesLoader具体加载配置文件实例化
这个工具类和JDK的SPI原理一样,比jdk不一样的是不是一次性加载全部而是根据key进行加载
先扫描 spring-autoconfiguration-metadata.properties文件 -> 元数据
从CLASSPATH下的每个jar包中搜寻META-INF/spring.factories文件
结合前面的元数据进行过滤,如果当前的 classpath 环境下没有相关联的依赖,则意味着这些类没必要进行加载
根据key加载对应的类到spring IOC容器中
SpringBoot Starter
spring-boot
spring-boot-starter-tomcat
spring-boot-starter-web
spring-boot-starter-aop
spring-boot-starter-cache
spring-boot-starter-redis
spring-boot-starter-freemarker
spring-boot-starter-jdbc
spring-boot-starter-json
spring-boot-starter-mail
三方
mybatis-plus-boot-starter
druid-spring-boot-starter
spring-boot-klock-starter
启动
启动类
@SpringBootApplication ---> @EnableAutoConfiguration
new SpringApplication.run() ---> createApplicationContext();先创建spring容器,再启动tomcat
jar包中META-INF/MANIFEST.MF中有【Main-Class: org.springframework.boot.loader.JarLauncher】
Start-Class: com.ibeidiao.cloud.auth.AuthApplication
Start-Class: com.ibeidiao.cloud.auth.AuthApplication
一旦定义了该属性即可通过 java -jar x.jar来运行该jar文件
常见问题
如何选择tomcat还是jetty
引入的是哪个包
@ConditionalOnClass等注解底层原理
jar是已编译好的字节码class,即使缺某些类,本地项目编译不会报错
a s m工具读取class字节码,读取注解参数设置的条件类
用类加载器去判断能不能加载条件类
零配置底层实现
spring-boot-autoconfigre模块
eg.spring-boot-starter-web,该starter默认引入tomcat,
更换的话修改pom,在<dependency>下使用<exclusions>排除tomcat,引入其他jar
更换的话修改pom,在<dependency>下使用<exclusions>排除tomcat,引入其他jar
eg.修改配置文件.properties更改bean初始属性
底层使用spring后置处理器设置
springcloud
是什么
快速开发分布式系统的组件
基于SpringBoot,统一管理单个SpringBoot应用,关注全局的服务治理(配置中心、注册中心。。。)框架;
核心概念
配置中心
分布式系统中,存在很多功能开关和各种参数配置项,传统的配置文件无法满足不方便管理
特点
统一管理
配置中心【服务端】负责配置的管理(新增,修改,删除,发布),
集成了配置中心【客户端】的微服务程序可以统一从服务端拉取配置
集成了配置中心【客户端】的微服务程序可以统一从服务端拉取配置
区分环境
微服务应用在不同环境(开发,测试,生产)能拉取对应的配置
实时刷新
客户端可以监听服务端配置项的变更,不用重新部署就能获取最新配置
权限控制
配置中心的管理可以针对不同用户角色设置不同权限(查看,新增,修改等)
版本控制
配置过程中出现误操作,可以回退到变更之前的配置
灰度发布
支持配置更新只发布到集群的部分实例,待测试通过再发布全部实例
常用配置中心组建
spring cloud config
阿里的nacos
携程的apollo
谷歌的consul
注册中心
微服务架构中的地址“通讯录”,服务提供者将自己的地址注册,消费者从上面拿
特点
自动注册
微服务应用启动,通过注册中心客户端组件将服务相关信息【自动注册到服务端】
健康检查
已经注册的服务实例宕机后,注册中心能发现不可用并从注册中心删除
自动发现
消费者事实收到注册中心的服务变更,以便调用时不会出错
常用组建
zookeeper
eureka
nacos
consul
服务网关
所有微服务对外的统一入口,隔离外部访问和内部系统,
特点
高并发
外部访问统一入口,必须承担高并发,具备高性能
安全
通常具有权限认证、黑名单、白名单等保证网关安全的功能
路由转发
根据请求和配置将请求转发到对应的后端微服务去
监控与限流
作为整个系统流量的入口,网关要能够监控流量情况,遇到突发情况及时限流,保障系统稳定
灰度发布
当某个微服务有新版本上线,可以利用服务网关进行流量的切换,实现该微服务的灰度发布
服务重试
调用某个微服务失败,可以根据设置的重试策略重新调用
服务别名
给某个或某些微服务设置别名,从而屏蔽微服务内部信息
常用组件
kong
zuul子主题
spring cloud gateway
负载均衡
将访问流量根据负载均衡算法分发到后端服务器的【流量分发控制服务】,提高微服务的可用性及性能
常见算法
简单轮询
将请求按顺序分发,不关心服务器性能当前负载等
加权轮询
根据服务器的性能给服务器设置不同的权重,将请求按顺序和权重分发给后端服务器;
可以让性能高的服务器处理更多的请求
可以让性能高的服务器处理更多的请求
子主题
简单随机
加权随机
根据性能设置不同的权重,按权重随机分发
一致性哈希
根据请求的客户端ip或器请求参数通过哈希算法得到一个数值,
利用该数值取模映射出对应的后端服务器
利用该数值取模映射出对应的后端服务器
能保证同一个客户端ip或请求参数每次请求都使用同一台服务器
最小活跃数
统计当前每台服务器处理的请求数,将新请求分发给活跃数最少的服务器
RPC调用
一个服务调用另一个服务
与http调用区别
http调用使用http协议,网络层面属于应用层协议;
http协议规定了数据传输的格式,restful风格就可以使用http协议来实现
http协议规定了数据传输的格式,restful风格就可以使用http协议来实现
rpc协议不是网络层面的协议,而是更上层更灵活的通讯协议;
可以自定义传输格式,传输数据的方式,只要能保证调用到远程方法即可;
eg.使用netty建立tcp连接传输套接字,自定义字节流格式通信解析
可以自定义传输格式,传输数据的方式,只要能保证调用到远程方法即可;
eg.使用netty建立tcp连接传输套接字,自定义字节流格式通信解析
常用框架
dubbo
gRpc
thrift
feign
本身不支持springmvc的注解,有一套自己的注解
openFeign
在feign的基础上支持了springmvc注解,如@RequestMapping等
@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
ribbon做负载策略
服务熔断
当服务A调用的某个服务B不可用时,A为了保证自己不受影响,从而不再调用服务B,直接返回一个结果;
减轻服务A和服务B的压力,直到服务B恢复
减轻服务A和服务B的压力,直到服务B恢复
熔断器
实现熔断功能的叫熔断器
代表组件
Hystrix
Sentinel
三种状态
closed
当调用失败次数达到阈值时启动熔断器
open
此时不会真正调用下游服务,而是直接返回,等过了某段时间后,熔断器会进入half-open状态
half-open半打开
此时会有部分请求访问下游服务,如果这些请求都调用成功了,则认为下游服务器恢复了,进入closed状态;否则进入open状态
服务降级
当发现系统压力过载时,可以通过关闭某个服务或限流某个服务,来减轻系统压力
与服务熔断
共同
都是为了防止系统服务奔溃
都让用户体验到某些功能暂时不可用
不同
熔断是下游服务故障触发的
降级是为了降低系统负载
服务雪崩
A调用B,B调用C,若C扛不住请求压力,从而堆积,导致B也堆积,从而A不可用
服务限流
在高并发情况下,为了保护系统,可以对访问服务的请求进行数量上的限制,从而防止系统被大量请求压垮;
在秒杀系统中,限流是非常重要的
在秒杀系统中,限流是非常重要的
常用算法
固定窗口计数器
滑动窗口计数器
令牌桶
漏桶
全局锁
分布式锁,分布式系统中互斥使用共享资源
实现原理
zookeeper:watch机制与临时节点特性
redis:消息订阅机制,setnx命令及过期时间
组件
redis:redisson
zookeeper:curator
控制总线
也称消息总线,是微服务系统中用来连接所有服务节点的,微服务中所有服务节点可以通过控制总线来进行通讯。
应用场景
spring cloud bus 就是控制总线的具体实现, 某个微服务可以通过bus来广播事件,
其他微服务可以收到事件进行相应的处理
其他微服务可以收到事件进行相应的处理
分布式事务
一次请求中,所涉及的分散在多个微服务上的操作要保证要么同时成功或同时失败;
比如创建订单减库存,银行转账等
比如创建订单减库存,银行转账等
实现方式
通过数据库
通过消息队列
二阶段提交
三阶段提交
三个角色
事务协调器
事务管理者
资源管理者
常用框架
seata
lcn
bytetcc
服务安全
服务认证和授权
特性
可扩展、可配置的认证和授权
单点登录
防止会话固定,点击劫持,跨网站请求伪造等攻击
与servlet api集成
组件
spring cloud security
链路追踪
为微服务系统提供了完整的调用链路还原,调用请求量统计,链路拓扑,应用依赖分析等功能,
可以帮助开发者快速分析和诊断微服务架构下的性能瓶颈
可以帮助开发者快速分析和诊断微服务架构下的性能瓶颈
功能
分布式调用链路查询和诊断
应用性能实时汇总
分布式拓扑动态发现
多语言开发程序接入
丰富的下游对接场景
常用框架
sleuth
zipkin
集群管理
spring cloud cluster
功能
领导者选举
一致性存储
集群状态管理
一次性tokens
事件驱动
就是消息驱动,spring cloud stream可以来实现事件驱动,
有了事件驱动,在微服务系统中可以更方便的通过发送消息来进行通信
有了事件驱动,在微服务系统中可以更方便的通过发送消息来进行通信
概念
目标绑定器,目标指的是kafka或rabbitmq
绑定桥梁
连接消息系统和应用程序
消息,应用程序和消息系统直接传递的数据
特点
异步处理
流量削峰
服务解耦
任务调度
常用框架
xxl-job
轻量级分布式任务调度平台
特性
简单:支持通过web页面对任务进行CRUD
动态:动态修改任务状态、启动/停止任务
注册中心:执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行,每30秒清理一次注册表中的无效机器。
同时,也支持手动录入执行器地址;
同时,也支持手动录入执行器地址;
失败处理策略:每10s检测失败任务,报警和重试
一致性:“调度中心”通过DB锁保证集群分布式调度的一致性,一次任务调度只会触发一次执行
云连接器
可以用来更方便的连接部署在云上的各种服务,spring cloud connectors就是云连接器的组件实现
目前支持的云平台
spring cloud cloud foundry
spring cloud heroku
函数计算
springsecurity+oauth2
oauth2
目前流行的一种授权机制,用来授权第三方应用(内部微服务),获取用户数据。
应用场景
使用了 OAuth2 的授权码模式,利用第三方的权威平台实现用户身份的认证
实现服务的单点登录,只做一次登录,就可以在多个服务中自由穿行
四种授权模式
密码模式
流程
可以做
企业内部的单点登录
微服务的安全校验
适用
高度受信用的客户端应用(手机app),一般是自己公司开发的app项目
授权码模式
最安全的模式
流程
业务场景
第三方不授信的,搭建自己的开发能力的平台
简化模式
开发基本不用
流程
客户端模式
开发中几乎用不到,这种模式 用户都没有参与过程
流程
四种实体
mybatis
#{}&${}
#{变量名}
可以进行预编译、类型匹配等规则
会转化为jdbc的类型名,如果类型为字符串就会加引号
防止sql注入
${变量名}
不进行变量类型匹配,直接替换
可以被sql注入
主要成员
Configuration
MyBatis所有的配置信息
SqlSession
主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能
Executor
执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler
封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等
ParameterHandler
负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
ResultSetHandler
将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler
负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换
方法
setParameter
getResult
eg.入库加密,出库解密
MappedStatement
维护一条<select|update|delete|insert>节点的封装
SqlSource
负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql
表示动态生成的SQL语句以及相应的参数信息
基本原理
1、应用程序启动->加载mybatis全局配置文件(数据源,mapper映射文件等)->解析配置文件->生成Configuration、一个个MappedStatement(包括参数配置、动态sql、结果映射集;其对应着<select | update | delete | insert>标签项)
2、SqlSessionFactorBuilder通过Configuration对象生成SqlSessionFactory,用来开启SqlSession(调用openSession())
3、SqlSession对象完成和数据库的交互
1、用户程序调用Mapper接口里的方法
2、SqlSession通过Mapper里的接口方法找到对应的MappedStatement对象
3、通过Excutor(负责动态SQL的生成和查询缓存的维护)将MappedStatement对象进行解析,sql参数转化,动态sql拼接,生成jdbc Statement对象
4、mybatis通过ParamterHandler填充参数,使用StatementHandler绑定参数,然后jdbc执行sql
5、借助MappedStatement的结果映射关系,使用ResultSetHandler将jdbc返回结果转换成HashMap、JavaBean等存储结构并返回应用程序
4、关闭SqlSession会话
dubbo
RPC调用与传统HTTP请求调用
都是远程服务调用;
RPC的话用上去感觉直接调用接口对象的方法一样;
http的话一般是一个远程的服务地址,设置请求头请求体调用
RPC的话用上去感觉直接调用接口对象的方法一样;
http的话一般是一个远程的服务地址,设置请求头请求体调用
dubbo可以配置选择netty还是http;
http是在tcp之上已经封装好的协议,但使用netty你可以自己封装协议
dubbo底层通过netty自定义传输格式提高性能
http是在tcp之上已经封装好的协议,但使用netty你可以自己封装协议
dubbo底层通过netty自定义传输格式提高性能
基本用法
消费者引入【接口】jar,@Reference注解依赖注入给对应接口生成代理对象
链路
服务暴露
服务提供者通过tomcat,netty启动服务,暴露出来远程可以访问的主机端口使得消费者可以访问到
服务注册
保存服务名与服务器地址映射关系(服务名:List<URL>)
消费者拉取后缓存,服务地址变动主动通知消费者
向提供者发心跳,验证服务在线
服务调用
消费者
启动时根据接口名从注册中心拉取服务地址并缓存
根据负载均衡策略选出一个服务地址进行调用
调用
消费者那边只有接口没有实现类,调用方法时spring采用动态代理生成代理对象,【代理类中invoke】方法实现远程调用解析
调用远程传参:1、接口名2、方法名3、方法参数类型列表4、方法参数值列表5、版本号
服务容错
服务重试
netty
可以干什么
可以实现自己的HTTP服务器、FTP服务器、UDP服务器、websocket服务器、redis的proxy服务器、mysql的proxy服务器
使用netty你可以自定义编解码协议,实现自己的特定协议的服务器
传统HTTP多线程服务器
原理
1、创建一个ServerSocket,监听并绑定一个端口
2、一系列客户端来访问这个端口
3、ServerSocket调用accept()获得一个客户端的Socket连接对象
4、启动一个新线程处理连接
1、读取Socket的字节流
2、解码协议,转换成HttpRequest对象
3、处理HttpRequest,得到一个结果,封装成HttpResponse对象
4、编码协议,将结果序列化为字节流
5、写Socket,将字节流发送给客户端
问题
高并发情况下,会创建大量的线程,使得操作系统任务调度压力变大,系统负载高,性能下降
线程间上下文切换开销很大且每个线程都要占用系统资源
是什么
基于java NIO技术(IO多路复用)封装的一套框架,更高层次的抽象
原生java nio使用起来不方便,而且还有bug
netty将它封装为供开发者易于使用的模式和接口,开发者实现自定义通讯协议服务器很方便
提供封装
核心组件
Bootstrap/ServerBootStrap
启动引导类,使用Netty需要把相关配置配置到引导类
Bootstrap:客户端引导类,核心方法是connect,传入一个EventLoopGroup即可
ServerBootstrap:服务端引导类,核心方法是bind,需要传入两个EventLoopGroup,一个负责接收连接,一个复制处理具体连接上的I/O任务
Channel
Netty对网络操作的抽象,包含一些常用的网络I/O操作,比如read、write等等
最常见的实现类
NioServerSocketChannel、NioSocketChannel
ServerSocketChannel、SocketChannel
EventLoop
负责监听网络事件并调用事件处理器进行相应的处理
EventLoop/EventLoopGroup的关系
EventLoop可以理解为线程,那么EventLoopGroup就可以理解为线程组/线程池
Netty的线程模型中,每一个channel需要绑定到一个固定的EventLoop进行后续的处理
Netty会根据某一算法从EventLoopGroup(线程组)选择一个EventLoop(线程)将channel进行绑定
eg.Accept、Read、Write可以交由单独的线程池去处理
ChannelFuture
Netty是异步的,所有对channel的操作都可以通过ChannelFuture来实现绑定一个监听器然后执行结果成功与否的业务逻辑
或者通过调用channelFuture.sync(),把异步变成同步的
ChannelHandler和ChannelPipeline
ChannelPipeline addLast(ChannelHandler... handlers);
Netty底层的处理器是一个处理链,链上的每一个处理器都可以对消息进行处理,选择继续传递或到此为止
一般我们业务会实现自己的解码器/心跳处理器->实际的业务处理器,然后按照顺序绑定到链条上
长连接保活
IdeStateHandler中的回调方法来实现心跳机制
解决粘包拆包问题
TCP的粘包拆包
发送数据时,多条消息会在一个包中发送,或者一个消息分两个包发送。我们需要正确处理,把每条消息拆分或者合并起来
自带四种解决粘包拆包的解码器
LineBasedFrameDecoder,基于换行符编解码器
DelimiterBasedFrameDecoder,基于分隔符的解码器
FixedLengthFrameDecoder,固定长度的解码
LengFieldthBasedFrameDecoder,基于消息中的消息长度字段进行解码的解码器
核心就是传入长度字节的长度和起始位置
生命周期回调
提供请求到达、关闭、建立。。。的回调方法,开发者可以进行相应的逻辑处理
同时管理多个端口
NIO客户端模型,对于RPC服务很有必要
TCP Socket、UDP Socket
对ByteBuf在性能和使用的便捷上进行了优化和抽象
零拷贝技术
操作系统提供的sendFile系统调用(节省了内核到用户空间和用户空间到内核的2次拷贝)
应用
Dubbo协议默认使用netty进行节点间通信
RocketMQ的生产者和消费者之间采用Netty进行高性能、异步通信
Redisson
适用于并发量要求很高、性能要求很高、而可靠性问题可以通过其他方案弥补的场景
与Jedis对比
Jedis是redis的java实现的客户端,其API提供了比较全面的Redis命令的支持
Redission实现了分布式和可扩展的java数据结构,和Jedis相比功能较为简单,不支持字符串操作,不支持事物、管道、分区等Redis特性
Jedis使用阻塞的I/O,其方法调用都是同步的,程序流程要等到socket处理完I/O才能执行,不支持异步;
Jedis客户端实例不是线程安全的,所以需要使用连接池来使用Jedis
Jedis客户端实例不是线程安全的,所以需要使用连接池来使用Jedis
Redission使用非阻塞的I/O和基于Netty框架的事件驱动的通信层,其方法调用是异步的;
Redission的API是线程安全的
Redission的API是线程安全的
Redission在redis的基础上实现了java缓存标准规范;Redission还提供了Spring Session会话管理器的实现
特点
支持单节点(single)模式、哨兵(sentinel)模式、主从(master/slave)模式以及集群(Redis-Cluster)模式
程序接口调用方式采用异步执行和异步流执行两种方式
数据序列化,Redission的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在redis里的读取和存储
提供多种分布式对象,如:Object Bucket,Bitset,AtomicLong,Bloom Filter和HyperLogLog等
提供丰富的分布式集合,如:Map,MultiMap,Set,SortedSet,List,Deque,Queue等
分布式锁和同步器的实现,可重入锁(Reentrant Lock),公平锁(Fair Lock),联锁(MultiLock),红锁(Red Lock),信号量(Semaphore),可过期性信号锁(PermitExpirableSemaphore)等
提供先进的分布式服务,如:分布式远程服务(Remote Service),分布式实时对象(Live Object)服务,分布式执行服务(Executor Service),分布式任务调度服务(Schedule Service)和分布式映射归纳(jdk stream reduce)服务(MapReduce)
分布式锁
部署
集群(AP模型)
红锁,超过一半加锁成功
单机
STW导致的锁过期问题
描述
1、clien1t获得锁,执行业务,此时发送fullGC,锁过期自动释放了
2、client2成功获得锁,也执行业务
3、造成client1和client2同时执行成功,产生数据问题
解决
1: 模拟CAS乐观锁的方式,增加版本号,修改数据时检查版本号
2:watch dog自动延期机制(后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。)
单机版的watch dog 并不能解决 STW的过期问题, 需要分布式版本的 watch dog, 独立的看门狗服务。
基于jedis的api实现分布式锁工具
原子操作-lua实现:redis.setNx(key,value,expireTime)
原理
基于netty实现,高性能;lua脚本
Redis数据结构:hash(key 字段1 值1 字段2 值2);key表示锁的名称,也可以理解为临界资源,字段表示当前获得锁的线程,值记录加锁次数,重入的话值+1
获取锁
redis.lock();
1、尝试通过lua脚本原子操作获得锁-tryAcquire(-1, leaseTime, unit, threadId);
非公平锁RedissionLock
1、判断锁名称key是否存在,不存在的话 - >hset 设置key、加锁线程ID、value=1,并设置过期时间(默认30s),返回null
2、key存在的话,当前key下线程ID存在的话,(重入锁),则线程ID对应的重入次数value+1,重置过期时间,返回null
2.1、否则返回当前key的剩余存活时间
公平锁RedissionFairLock
继承RedissionLock,重写tryLockInnerAsync
用到的redis的数据结构
LIST(redisson_lock_queue:{lockName}),先进先出特性,顺序存储threadID
ZSET(redisson_lock_timeout:{lockName}),存储(threadID,threadWaitTime),控制锁的超时
当出现锁竞争,会把当前threadID入队,同时设置一个过期时间
2、获取成功则return;
3、获得不成功的话,当前线程会向redis订阅一个channel通道,等待释放锁的通知
当资源可用时可以及时知道,并抢占(会发生羊群效应)
4、等通知的同时会循环轮询尝试获取锁,等待ttl时间(加锁的剩余存活时间)获取,通过信号量new Semaphore(0);限制让线程等待
防止无效的轮询而浪费资源
watch dog自动延期机制
问题:客户端加锁的默认生存时间是30s,假如业务执行超过30s,锁释放会有问题
加锁成功后会启动一个watch dog后台守护线程,每隔10s检查一下客户端是否还持有锁,如果持有的话就会继续延长锁时间(默认30s)
注意
只有在为显示指定加锁时间(leaseTimeout)才生效
lockWatchdogTimeout(默认延期时间)设定的时间不要太小,比如我之前设置的是 100毫秒,由于网络直接导致加锁完后,watchdog去延期时,这个key在redis中已经被删除了。
每 lockWatchdogTimeout/3时间,去延时
羊群效应
高并发情况下,一个锁释放,其他客户端大量请求争抢锁资源,造成redis瞬时压力大增
解决方案:削峰->在客户端(资源请求方)添加随机数延迟,使多端每次请求的时间间隔不一致,减缓请求的瞬时压力
释放锁
redis.unlock();
非公平锁
通过lua脚本执行
1、如果持有锁的不是当前线程ID自己,返回null,代码中会直接返回释放锁失败
2、否则将当前线程ID的重入次数value-1,如果减后value>0是重入锁,重置过期时间,返回0(还没有完全释放)
3、如果减后value=0成功释放锁,删除该锁,向通道channel发布消息(通知其他线程资源可用抢锁)
公平锁
核心也是一段lua脚本
类型
可重入锁(Reentrant Lock)
redisson.getLock("anyLock");
可重入公平锁(Fair Lock)
保证了多个Redission客户端线程同时请求加锁时,优先分配给先发出请求的线程
redisson.getFairLock("anyLock");
联锁(MultiLock)
可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。
RLock lock1 = redisson1.getLock("lock1");
RLock lock2 = redisson2.getLock("lock2");
RLock lock3 = redisson3.getLock("lock3");
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
RLock lock2 = redisson2.getLock("lock2");
RLock lock3 = redisson3.getLock("lock3");
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
红锁(RedLock)
RLock lock1 = redisson1.getLock("lock1");
RLock lock2 = redisson2.getLock("lock2");
RLock lock3 = redisson3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
RLock lock2 = redisson2.getLock("lock2");
RLock lock3 = redisson3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
读写锁(ReadWriteLock)
RReadWriteLock rwlock = redisson.getLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();
信号量(Semaphore)
RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire(permits);
semaphore.release(permits);
semaphore.acquire(permits);
semaphore.release(permits);
可过期性信号量(PermitExpirableSemaphore)
在RSemaphore对象的基础上,为每个信号增加了一个过期时间。每个信号可以通过独立的ID来辨识,释放时只能通过提交这个ID才能释放
RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
String permitId = semaphore.acquire();
// 获取一个信号,有效期只有2秒钟。
String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
// ...
semaphore.release(permitId);
String permitId = semaphore.acquire();
// 获取一个信号,有效期只有2秒钟。
String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
// ...
semaphore.release(permitId);
闭锁/倒数闩(CountDownLatch)
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();
// 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();
latch.trySetCount(1);
latch.await();
// 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();
redis分布式锁的高可用
问题
1、master-slave集群架构中
2、client1加lock成功,主从复制过程中master宕机,此时还没有同步完成,slave晋升为master;
3、client2同一个锁加锁也成功;这样就会造成业务上出现问题
这样就使得redis分布式锁不够高可用
为了让redis分布式锁更加高可用,
redission实现了RedLock(红锁)
redission实现了RedLock(红锁)
前提
为了分布式锁的高可用,采用master-slave架构,并配置多个master,来避免一个master挂掉且未同步的的情况
算法思想
不能在一个redis实例上创建锁,应该是多个redis实例master(n/2+1,大于一半)上都创建锁成功,才能算加锁成功
缺陷
会导致性能降低
使用redis分布式锁,追求的是高性能,在cap理论中追求的是AP而不是CP,
而通过CP模型让分布式锁更加高可用,势必会产生性能的代价
而通过CP模型让分布式锁更加高可用,势必会产生性能的代价
如果要追求分布式锁的高可用,建议使用zookeeper,在cap理论中采用CP模型达到近乎一致性(实际是最终一致)
联锁MultiLock
基于RedLock思想,依次在每个实例加锁,根据最后统计的成功次数(不一定是n/2+1,看配置)决定是否加锁成功
微信接口
公众号/服务号
配置Portal,指向自己的服务器,可接受用户关注/取消/发消息/点击菜单事件,然后在自己接口响应处理
binary/weixin-java-tools
单机或者redis,token缓存
accesstoken获取失败自动重试
封装各种微信api接口,只需要配置appid这些信息,然后代码直接用就可以
中间件
tomcat
servlet容器(servelt可以处理响应http请求)
eg.想要从客户端发送一个请求并在服务端作出响应的响应;
为该动作定义一个servlet类继承HttpServlet,重写doGet,doPost方法,
方法的入参有HttpServletRequest和HttpServletResponse,
从request里可以拿到客户端发来的请求数据,根据发来的数据在执行完处理逻辑之后使用response对象响应客户端
为该动作定义一个servlet类继承HttpServlet,重写doGet,doPost方法,
方法的入参有HttpServletRequest和HttpServletResponse,
从request里可以拿到客户端发来的请求数据,根据发来的数据在执行完处理逻辑之后使用response对象响应客户端
server.xml
Engine
List<Host> hosts
Host
List<Context> contexts
Context
List<Wrapper> wrappers
Wrapper
List<Servlet> servlets
StandardWrapperValve
Servlet实例.service(request,response)
doGet
doPost
解析HTTP请求的原理
1、浏览器将请求数据封装成http协议的数据
2、调用操作系统api发送数据(操作系统基于TCP协议传输,socket连接字节传输)
3、tomcat解析字节数据,转成Request对象
根据http协议,换行符等,得到请求头,请求参数,请求体等信息
4、tomcat根据url交给对应的servlet处理,.service(doGet,doPost)
使用的是自定义的类加载器
WebappClassLoader
webapps多应用的类的隔离
由于webapps下可以放多个应用,每个应用下都有User.class类,
使用jvm默认加载器的话,加载了其中一个应用下的user类,其他应用的user类就不加载了;
使用自定义的WebappClassLoader类加载器,tomcat会为每个应用实例一个该自定义的类加载器
使用jvm默认加载器的话,加载了其中一个应用下的user类,其他应用的user类就不加载了;
使用自定义的WebappClassLoader类加载器,tomcat会为每个应用实例一个该自定义的类加载器
mq
RocektMQ
应用场景
应用解耦
1、订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户下单成功
2、库存系统:订单下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行
库存操作
库存操作
流量削峰
秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。
可以控制活动的人数
缓解短时间内高流量应用压垮应用
用户请求,服务器接收后,首先写入消息队列,假如消息队列长度超过最大数量,则直接
抛弃用户请求或跳转到错误页面
抛弃用户请求或跳转到错误页面
秒杀业务根据消息队列中的请求信息,再做后续处理
数据分发
只需要将数据发送到消息队列,数据使用方直接在消息队列中获取数据即可
异步处理
类似事件
日志处理
是指将消息队列用在日志处理中,比如kafka的应用,解决大量日志传输问题
架构
1、日志采集客户端负责日志数据采集,定时写入kafka队列
2、kafka消息队列,负责日志数据的接收、存储和转发
3、日志处理,订阅并消费kafka队列中的日志数据
消息类型
事务消息
顺序消息
批量消息
定时消息
系统组成及关系
系统组成
Producer
消息生产者
和nameServer集群中的随机一台建立长连接,得知当前这个topic存在哪台broker-master上,
然后再与其建立长连接,支持多种负载均衡模式发送消息
然后再与其建立长连接,支持多种负载均衡模式发送消息
Consumer
消息消费者
它也会先和 NameServer 集群中的随机一台建立长连接,得知当前要消费消息的 Topic 存在哪台 Broker Master、Slave上,
然后它们建立长连接,支持集群消费和广播消费消息。
然后它们建立长连接,支持集群消费和广播消费消息。
Broker
主要负责消息的存储、查询供消费,支持主从部署,master支持读写,slave只支持读
会向集群中的每一台nameServer注册自己的路由信息
NameServer
一个很简单的topic路由注册中心,支持broker的动态注册和发现,保存topic和broker之间的关系
关系
1、先启动nameServer集群,各个nameServer之间无任何数据交互,个nameServer都有完整的路由信息,即无状态
2、broker启动之后会向所有nameServer定期(30s)发送心跳包,包括:ip、port、topicInfo
3、nameServer会定期扫描broker存活列表,如果超过120s没有心跳则移除次broker信息,代表下线
4、producer上线从nameServer就可以得知它要发送的某topic消息在哪个broker上,然后与其broker-master建立连接发送消息
5、consumer上线也可以从nameServer得知它要接收的topic消息在哪个broker上,然后与broker建立连接接收消息
java class
DefaultMQProducer
应用发送消息的基类,封装一些通用的方法方便开发者在更多场景中使用。属于线程安全类,在配置启动后可在多个线程使用
内部属性
DefaultMQProducerImpl defaultMQProducerImpl
生产者内部默认实现类
String producerGroup
Producer组名,默认为DEFAULT_PRODUCER。
多个Producer如果属于一个应用,发送同样的消息,则应该将他们归为同一组
多个Producer如果属于一个应用,发送同样的消息,则应该将他们归为同一组
String createTopicKey
自动创建测试的topic名称, 默认值为TBW102;
在发送消息时,自动创建服务器不存在的topic,需要指定Key。
broker必须开启isAutoCreateTopicEnable
在发送消息时,自动创建服务器不存在的topic,需要指定Key。
broker必须开启isAutoCreateTopicEnable
volatile int defaultTopicQueueNums
创建默认topic的queue数量。默认4
int sendMsgTimeout
发送消息超时时间,默认值10000,单位毫秒
int compressMsgBodyOverHowmuch
消息体压缩阈值,默认为4k(Consumer收到消息会自动解压缩)
int retryTimesWhenSendFailed
同步模式,返回发送消息失败前内部重试发送的最大次数。可能导致消息重复。默认2
int retryTimesWhenSendAsyncFailed
异步模式,返回发送消息失败前内部重试发送的最大次数。可能导致消息重复。默认2
boolean retryAnotherBrokerWhenNotStoreOK
声明发送失败时,下次是否投递给其他Broker,默认false
int maxMessageSize
最大消息大小。默认4M; 客户端限制的消息大小,超过报错,同时服务端也会限制
int randomSign
boolean addExtendUniqInfo
boolean useDefaultTopicIfNotFound
DefaultMQPushConsumer
客户端消费者的实现,broker往消费者推送数据,其内部实现了流控,消费位置上报等等。
内部属性
DefaultMQPushConsumerImpl defaultMQPushConsumerImpl
消费者实现类,所有的功能都委托给DefaultMQPushConsumerImpl来实现
String consumerGroup
消费者组名,必须设置,参数默认值是:DEFAULT_CONSUMER
(需要注意的是,多个消费者如果具有同样的组名,那么这些消费者必须只消费同一个topic)
(需要注意的是,多个消费者如果具有同样的组名,那么这些消费者必须只消费同一个topic)
MessageModel messageModel = MessageModel.CLUSTERING
消费的方式,支持以下两种 1、集群消费 2、广播消费。
BROADCASTING 广播模式,即所有的消费者可以消费同样的消息;
CLUSTERING 集群模式,即所有的消费者平均来消费一组消息
BROADCASTING 广播模式,即所有的消费者可以消费同样的消息;
CLUSTERING 集群模式,即所有的消费者平均来消费一组消息
ConsumeFromWhere consumeFromWhere;
消费者从那个位置消费,分别为:
CONSUME_FROM_LAST_OFFSET:第一次启动从队列最后位置消费,后续再启动接着上次消费的进度开始消费 ;
CONSUME_FROM_FIRST_OFFSET:第一次启动从队列初始位置消费,后续再启动接着上次消费的进度开始消费;
CONSUME_FROM_TIMESTAMP:第一次启动从指定时间点位置消费,后续再启动接着上次消费的进度开始消费
CONSUME_FROM_LAST_OFFSET:第一次启动从队列最后位置消费,后续再启动接着上次消费的进度开始消费 ;
CONSUME_FROM_FIRST_OFFSET:第一次启动从队列初始位置消费,后续再启动接着上次消费的进度开始消费;
CONSUME_FROM_TIMESTAMP:第一次启动从指定时间点位置消费,后续再启动接着上次消费的进度开始消费
AllocateMessageQueueStrategy allocateMessageQueueStrategy;
消息分配策略,用于集群模式下,消息平均分配给所有客户端;默认实现为AllocateMessageQueueAveragely
Map<String, String> subscription;
topic对应的订阅tag
MessageListener messageListener;
消息监听器 ,处理消息的业务就在监听里面。目前支持的监听模式包括:
MessageListenerConcurrently,对应的处理逻辑类是MessageListener messageListener ;
ConsumeMessageConcurrentlyService MessageListenerOrderly 对应的处理逻辑类是ConsumeMessageOrderlyService;
两者使用不同的ACK机制。。RocketMQ提供了ack机制,以保证消息能够被正常消费。只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功。中途断电,抛出异常等都不会认为成功——即都会重新投递。
MessageListenerConcurrently,对应的处理逻辑类是MessageListener messageListener ;
ConsumeMessageConcurrentlyService MessageListenerOrderly 对应的处理逻辑类是ConsumeMessageOrderlyService;
两者使用不同的ACK机制。。RocketMQ提供了ack机制,以保证消息能够被正常消费。只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功。中途断电,抛出异常等都不会认为成功——即都会重新投递。
OffsetStore offsetStore;
子主题
push、pull
实际应用
数据埋点服务
单独的微服务用户埋点相关业务
背调API服务-订单最新状态推送
交付系统->RocketMQ->推送服务消费通知第三方
分布式事务
nginx
es
是什么
全文检索;开源的分布式搜索引擎,可以近乎实时的存储检索数据
java开发+Lucene,通过简单的RestfulApi来隐藏操作Lucene的复杂性
Lucene
开源全文检索引擎工具包,不是一个完整的搜索引擎,而是一个全文搜索引擎的架构
使用倒排索引,比传统的关系型数据库检索效率高,这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址
eg.通过关键词搜索文档
正向索引
遍历所有文档,再匹配关键词,返回符合的文档
倒排索引
遍历所有关键词,匹配到符合的关键词,再找出文档
基本概念
NRT
Near Realtime,近实时
一是从写入一条数据到这条数据可以被搜索,有一段非常小的延迟(大约1秒左右
二是基于Elasticsearch的搜索和分析操作,耗时可以达到秒级
集群(cluster)
集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。
es的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的
节点(node)
单个 ElasticSearch 实例. 通常一个节点运行在一个隔离的容器或虚拟机中
分片(shard)
索引分片
完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索
分片的数量只能在索引创建前指定,并且索引创建后不能更改
ES自动管理和组织分片, 并在必要的时候对分片数据进行再平衡分配
副本(replica)
索引副本,完全拷贝shard的内容
shard与replica的关系可以是一对多,同一个shard可以有一个或多个replica,并且同一个shard下的replica数据完全一样
replica作为shard的数据拷贝,承担以下三个任务:
shard故障或宕机时,其中一个replica可以升级成shard。
replica保证数据不丢失(冗余机制),保证高可用。
replica可以分担搜索请求,提升整个集群的吞吐量和性能。额外的副本能给带来更大的容量, 更高的呑吐能力及更强的故障恢复能力。
Recovery
据恢复或叫数据重新分布
es在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。
索引(index)
具有相同结构的文档集合
一个集群里可以定义多个索引,如客户信息索引、商品分类索引、商品索引、订单索引、评论索引等等,分别定义自己的数据结构。
索引命名要求全部使用小写,建立索引、搜索、更新、删除操作都需要用到索引名称。
Document
Elasticsearch最小的数据存储单元,JSON数据格式,类似于关系型数据库的表记录(一行数据)
结构定义多样化,同一个索引下的document,结构尽可能相同
工作应用
1、i背调证明人库,检索优化
背景
证明人数据(供背景调查用,包括姓名、手机号、公司名等等),数据来源,候选人授权采集->入库
之后,在调查其他候选人时,候选人提供的证明人不够充分时;就可以从这个证明人库根据候选人提供的公司名找到这些证明人去打电话调查
最开始,由于数据量少,为满足业务做搜索直接是利用mysql的select的like完全模糊条件,后来性能越来越低
优化
同步ES
与关系数据库的对比
类型(Type) -------- Table(表)
文档(Document)---- Row(记录)
字段(Field)---- Columns(列)
使用
基础API
ES客户端,RestHighLevelClient
1、创建new SearchRequest(indexs【类似于表名】),指定要查哪个索引(证明人索引、订单索引、、、)
2、创建查询条件构造器,new SearchSourceBuilder()
使用方法.query(queryBuilder)传入查询条件
使用方法.aggregation(aggregationBuilder);处理聚合操作((类似sql的group by之后sum))
分页?
3、es客户端实例请求,SearchResponse searchResponse = restHighLevelClient.searchRequest(SearchRequest);
查询(NativeSearchQueryBuilder)
是一个原生的查询条件类,用来和ES的一些原生查询方法进行搭配,实现一些比较复杂的查询。
条件查询(.withQuery(queryBuilder))
BoolQueryBuilder
RangeQueryBuilder
MatchPhraseQueryBuilder
分页(.withPageable(pageRequest))
PageRequest.of(pageNum - 1, pageSize)
聚合查询(.addAggregation(aggregationBuilder))
TermsAggregationBuilder = AggregationBuilders.terms
相当于sql中的group by
.subAggregation
DateHistogramAggregationBuilder =
AggregationBuilders.dateHistogram("createDate").field("createTime").calendarInterval(DateHistogramInterval.MONTH).minDocCount(1)
AggregationBuilders.dateHistogram("createDate").field("createTime").calendarInterval(DateHistogramInterval.MONTH).minDocCount(1)
eg.按categoryId分组,各分组内按月份分组统计
spring
org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate
org.springframework.data.elasticsearch.repository.ElasticsearchRepository
缓存
重要:缓存是通过强一致性来提高性能的。
Redis(NOSQL)
设计Redis key
分段设计发
eg.根据用户 id 查询用户邮箱地址
set user:id:1:email 156577812@qq.com;
set user:id:2:email 156577812@qq.com;
。。。
根据实际条件动态拼接 key,然后get
方便实现搜索
数据结构
string
基本命令
set key val;get key
逻辑结构:key唯一,value为字符串
应用场景
存储具体值
对象:json字符串、其他格式化字符串
生成自增id
1.set id 1000;//不设置 incr默认从0开始
2.incr id;->2
3.decr id;->1
2.incr id;->2
3.decr id;->1
hash
基本命令
hmset key k1 v1 k2 v2 ...;hmget key k1;
逻辑结构:key唯一值,value为map(kv)结构
相对string存josn字符串,hash灵活添加删除字段
应用场景
非常适合存储对象数据
hmset user:1 name zj email 156577812@qq.com
list
基本命令
lpush,lpop;rpush,rpop
逻辑结构:链表,插入顺序排序,可头尾插入
应用场景
实现【队列服务】的最经济,最简单的方式
获取最新内容
查询两端附近的数据性能非常好
适合一些需要获取最新数据的场景,比如新闻类应用的 “最近新闻”。
set
基本命令
sadd key v1 v2 v3 ...
sinter key1 key2 ... // 交集
sunion key1 key2 ... // 并集
sdiff key1 key2 ... // 补集 ,第一个与其他集合的差异
数据结构
无序,不重复
交集、并集、补集,redis内部完成效率很高
应用场景
eg.共同好友列表
1.sadd user:wade james melo paul kobe
sadd user:james wade melo paul kobe
sadd user:paul wade james melo kobe
sadd user:melo wade james paul kobe
sadd user:james wade melo paul kobe
sadd user:paul wade james melo kobe
sadd user:melo wade james paul kobe
2.sinter user:wade user:james
zset(sorted set)
基本命令
zadd key weight1 v1 weight2 v2 ...
zrevrange key 0 -1 // 逆序
zincrby key w v1;// 增加权重
数据结构
在set基础上给每个元素关联一个权重,插入元素会根据这个排序
应用场景
eg.文章的阅读量或点赞量对文章列表排序
eg.与好友亲密度显示还有列表
特性
原子性
[单操作]原子性
[多操作]通过事物保证原子性
很快、高吞吐
纯内存操作,高速读写,每秒万级别
单线程,非阻塞IO多路复用技术,避免多线程频频上下文切换和线程竞争问题
线程模型
6.0之前真的是单线程吗???
严格上讲,不是
生成rdb文件,会fork一个子线程执行
version>4.0,
除主线程外,后台线程处理一些较为缓慢的操作,如:
清理脏数据、无用连接的释放、大key的删除
除主线程外,后台线程处理一些较为缓慢的操作,如:
清理脏数据、无用连接的释放、大key的删除
单线程一般说的是io多路复用技术的事件处理上,
包括获取(Socket 读)、解析、执行、内容返回(Socket 写)等都由一个顺序串行的主线程处理,
这就是所谓的“单线程”。
包括获取(Socket 读)、解析、执行、内容返回(Socket 写)等都由一个顺序串行的主线程处理,
这就是所谓的“单线程”。
为什么不使用多线程?
几乎不存在CPU成为瓶颈的情况,主要瓶颈在内存和网络
单线程使得redis内部实现的复杂度大大降低,Hash 的惰性 Rehash、Lpush 等等 “线程不安全” 的命令都可以无锁进行。
文件事件处理器(file event handler)
io多路复用技术
reactor设计模式
多个socket连接共用一个线程调度
主要三种技术:select、poll、epoll(最新最好)
结构
IO多路复用程序
监听socket,给socket注册事件(readable、writable)
事件分派器
监听就绪socket事件,分派对应事件处理器
事件处理器
连接应答处理器
命令请求处理器
命令回复处理器
网络IO事件(请求的建立、数据读写解析)
单线程(主线程),6.0之前
多线程(IO线程) 6.0
默认不开启;
redis.conf->io-threads-do-reads no;io-threads n(小于cpu核心数);
redis.conf->io-threads-do-reads no;io-threads n(小于cpu核心数);
优势
性能提升至少一倍以上
相比【分布式架构中对数据进行分区】并采用多服务器,维护成本低,经济实惠;
数据分区 无法解决热点key读/写;
数据分区 无法解决热点key读/写;
原理
从 Redis 自身角度来说,因为读写网络的 Read/Write 系统调用占用了 Redis 执行期间大部分 CPU 时间,瓶颈主要在于网络的 IO 消耗。
分摊 Redis 同步 IO 读写负荷
分摊 Redis 同步 IO 读写负荷
网络io相对慢,开io线程去处理,避免阻塞其他客户端
线程安全问题?
不会:仅仅在处理io请求读写;命令处理如lpush等还是单线程处理
memcached比较
相同点:都采用了master-worker线程模型
不同点:memcache真正线程隔离,主命令逻辑也交给worker处理;redis还是master线程处理逻辑,虽然一定程度上增加了模型复杂度,但也不会存在线程安全问题
eg:客户端与redis通信完整的过程,set key value
redis启动时已经初始一个【服务端server socket】,将这个socket的readable事件(类似java中accept()等待客户端建立连接)关联到【连接应答处理器】并注册到【多路复用程序】
1、客户端socket01与redis请求建立连接,此时server socket 的readable就绪,然后【事件分派器】将它的readable分派给【连接应答处理器】
2、【连接应答处理器】成功处理socket01的连接请求,再给socket01注册一个readable事件到【多路复用程序】,并关联到【命令处理器】
3、socket01的readable事件就绪后,【事件分派器】将事件分派给【命令处理器】
4、【命令处理器】解析socket01的命令‘set key value’ ,在内存中set值,完成后给socket01注册一个writable事件到【多路复用程序】并将此事件关联【命令回复处理器】
5、【事件分派器】将socket01的writable事件分派给【命令回复处理器】
6、【命令回复处理器】将set成功标记write到socket01,然后释放掉socket01这个客户端
实现消息队列
消息队列
特点
支持阻塞等待拉取消息
支持发布订阅模式
消费失败,可重新消费,消息不丢失
实例宕机,消息不丢失,可持久化
消息可堆积
核心:宁可重复消息,也不可丢失
解决丢失:消费成功确认,生产失败重试
重复原因
1、生产者重复生产(1代码bug,2消息队列保存消息成功后由于网络问题生产者没有收到应答)
2、消费者消费超时,新新消费者重复消费
解决重复办法
消费者业务代码做幂等性逻辑操作
使用List数据结构
强调‘拉’模型,从list里pop
非阻塞式命令
lpush,rpop
需消费者自旋拉取消息,没有消息会cpu空转,如果解决空转又会带来消费不及时
阻塞式命令
lpush,brpop阻塞式取消息
缺点
不能重复消费
pop后list就会移除这个消息
消息丢失
消费者pop后宕机
使用发布/订阅模型(pub/sub)
强调‘推’模型,推msg到缓冲区
阻塞式命令
subscribe queue1;publish queue1 msg1;
优点
重复消费:多端订阅,多端消费
缺点
消息丢失
消费者下线:拿到消息后宕机
redis宕机:没有持久化发布在缓冲区的消息
消息堆积:消费跟不上生产速度,redis可配置堆积内存超过限制会将消费者踢下线
eg.
解决在服务端集群模式下使用websocket实现消息交互问题
问题描述
1、client01,02,03分别与server_node01,02,03建立了连接
2、client01发送了消息,业务上需要client02,03都能收到消息
3、由于node01只与client01建立了连接,所以它不能把消息推送给client02,03
解决方案
1、node01,02,03都通过redis订阅一个消息到来事件
2、某个一个node收到客户端来的信息,就向redis的这个事件发布一个消息
3、每个node收到这个事件后就去检查哪些websocket与自己建立的连接,然后发消息给它
趋于成熟的数据结构,stream队列 5.0版本
满足以上五个特点
支持阻塞式
xadd queue1 * name1 zhangshan;//*生成序列号唯一id
xread count 5 streams queue1 0-0;//从序号0读取5个消息
xread count 5 block 0 streams queue1 0-0;//block 0 阻塞式拉取,0s
xread count 5 streams queue1 0-0;//从序号0读取5个消息
xread count 5 block 0 streams queue1 0-0;//block 0 阻塞式拉取,0s
发布订阅模式
xgroup、xreadgroup
消息处理异常,重复消费
xack,告知消费成功
作为新数据结构,会通过rdb、aof持久化
指定消息长度,maxlen
与专业中间件比较
面对消息堆积
数据完整性
redis的aof和rdb,主从切换本身无法严格保证数据完整性,存在数据丢失可能
rabbitmq、kafka集群模式下,发布消息会写多个节点,数据不丢失,,
设计场景
redis设计上针对缓存场景设计
rabbitmq、kafka针对队列场景
持久化
记录磁盘,防止内存数据丢失
RDB
指定时间间隔内将内存数据集快照存入磁盘,二进制文件dump.rdb
快照触发机制
发save命令:阻塞redis其他命令,直到rdb过程完成
发bgsave命令
fork一个redis子进程,rdb过程由子进程负责
fork时会短暂阻塞,但很快
redis.conf配置自动策略触发bgsave
eg.save 60 10000;60s内10000个key发生变化就触发
AOF
集群
模式
主从复制
原理
1,、slave连接到master,会发送sync命令
2、master收到sync命令,执行bgsave命令生成rdb快照文件,并用缓冲区记录此后执行的写命令
3、master执行bgsave完毕后,向所有slave发送rdb文件,并在发送期间继续记录写命令到缓冲区
4、slave收到rdb后丢弃旧的rdb,载入新收到的rdb
5、master发送rdb完毕后,向slave发送缓冲区的写命令
6、slave完成rdb载入后接受master的缓冲区的写命令
7、slave初始化完成
8、之后master每执行一个写命令都会发送给slave同步数据
优点
可以进行读写分离
master同步给slave后,slave也可以继续同步给其他slave,分单master同步压力
master->slave非阻塞同步,同步期间客户端仍然可以发送查询和修改请求
slave->master同样非阻塞同步,同步期间客户端仍然可以发送查询请求
缺点
不具备自动容灾和回复功能
master宕机,需要手动修改配置更改master
master宕机,可能会存在部分数据未同步到slave
较难支持在线扩容,在集群容量达到上限时在线扩容变得很复杂
哨兵模式
不用哨兵模式,master服务中断后,需要人工手动操作将slave升级为master
哨兵
作用
监控master和slave是否正常运行
master出现故障后自动将slave转为master,然后通过发布订阅模式通知其他slave修改配置文件
原理
sentinel通过发送命令,等待redis服务器的响应,从而监控运行的多个实例
故障切换(failover)过程
1、假设master宕机,sentinel1监控到这个结果
2、系统并不会马上进行failover,仅仅是sentinel1会认为这个instance主观下线(sdown)
3、当有足够数量(配置文件指定)的sentinel认为服务不可用,master就会被标记为客观下线odown
6、由一个sentinel发起所有sentinel之间会进行一次【投票】,投票通过后
之后进行failover操作,切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的slave实现切换主机
缺点
不能动态扩容
Redis-Cluster集群
无中心结构(每个节点都有整个集群的信息);分布式存储(分片);至少3组小集群
特点
所有节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
节点的fail是通过集群中超过半数的节点检测失效时才生效
客户端连接集群中任意一个可用节点即可
redis cluster节点分配
1、集群中每个节点上,都有插槽(slot,取值范围0-16383,具体看怎么分配)、cluster(一个集群管理的插件)
2、当某节点接收到请求的key时,redis根据哈希槽算法:CRC16(key) mod 16384得到对应的slot位置,然后跳转到这个slot所在的节点
3、新增节点,redis cluster会从原有的节点各拿一部分slot放入新增的节点;删除节点,情况类似
Redis Cluster主从模式
为了保证数据的高可用,一个主节点可以设置一个或多个从节点
这种模式,如果master和slave都挂了,也就不能继续服务了
当slave发现自己的master变为FAIL状态时,
便尝试发起选举(一定延迟:确保其他master意识到fail了),以期成为新的master
便尝试发起选举(一定延迟:确保其他master意识到fail了),以期成为新的master
1、slave发现自己的master变为FAIL
2、将自己记录的集群currentEpoch(选举轮次标记)加1,并广播信息给集群中其他节点
3、其他节点收到该信息,只有master响应,判断请求者的合法性,并发送结果
4、尝试选举的slave收集master返回的结果,收到超过半数master的同意后变成新Master
5、广播Pong消息通知其他集群节点
redis.conf->cluster-require-full-coverage=no;当某分片不可用其他分配继续可服务
脑裂问题
概念
集群部署在不同的网络分区,由于网络故障使得两个分区失去联系,另一个分区会failover(故障转移)也选出master
数据就不一致了,基于setNX指令的分布式锁,可能会拿到相同的锁;
基于incr生成的全局唯一id,也可能出现重复。
基于incr生成的全局唯一id,也可能出现重复。
哨兵模式下
master和(slave、sentinel)不同分区,网络问题,sentinel所在的分区重新选出master;而可能有一部分应用还和之前master连接
cluster模式下
各分片下都主从模式;主从分区不同,分片分区不同有可能发生
解决
手动
调试网络,重启旧的master
自动
redis.conf->min-replicas-to-write=3;min-replicas-max-lag=10
第一个参数表示连接到master的最少slave数量
第二个参数表示slave连接到master的最大延迟时间
第一个参数表示连接到master的最少slave数量
第二个参数表示slave连接到master的最大延迟时间
如果发生集群脑裂,原先的master节点接收到客户端的写入请求会拒绝,就可以减少数据同步之后的数据丢失
高并发问题
一致性
若一致性要求很高,不使用
缓存穿透
概念
查不到
缓存和持久数据库都未命中
请求很多时,数据库压力
解决
缓存前置 布隆过滤器
一种数据结构
对所有可能查询的值以hash形式存储
先访问过滤器,未命中直接返回
缓存空对象
未命中也缓存,并设置过期时间
问题
浪费空间:恶意请问未命中key
过期时间:此时间窗口数据不同步
缓存击穿
概念
某些热点key,高并发
过期一瞬间,穿破缓存打到数据库
60s过期,60.01s恢复,0.1s内无缓存
解决方案
设置热点key永不过期
加互斥锁
保证同时只能一个线程查库
再次查缓存
double check:判断缓存-加锁-判断缓存-查数据库
缓存雪崩
概念
多数的缓存某时间段集中过期
节点宕机
压垮数据库
解决方案
redis高可用,多加节点
限流降级/互斥锁
数据预热:即将大并发前受到访问所有数据缓存起来,过期时间尽可能均匀
如何保证与数据库mysql双写一致性
策略
延时双删策略
eg.问题
ThreadA执行更新操作、ThreadB执行读操作
1、ThreadA先del缓存 -> ThreadB读取缓存为null -> ThreadB查询db -> ThreadB更新缓存 -> ThreadA更新db
2、此时缓存中的值是ThreadA更新前的值
1、先删除缓存 2、再更新数据库 3、休眠一会儿(比如1s,这个休眠时间 = 读业务逻辑处理的耗时 + 几百毫秒。),再次删除缓存
为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。
删除缓存重试机制
删除缓存失败?
写请求更新数据库 -> 缓存因为某些情况,删除失败 -> 把删除失败的key放到消息队列 -> 消费者服务收到消息删除缓存
读取binlog异步删除缓存
可以使用阿里的canal将binlog日志采集发送到MQ队列里,消费者接收消息删除
策略总结
1、读取缓存中是否有相关数据
2、如果缓存中有相关数据value,则返回
3、如果缓存中没有value时,则从数据库读取后放入缓存,再返回
4、如果有更新数据,则先更新数据库,再删除缓存
5、为了保证第四部删除缓存成功,使用binlog异步删除
6、如果是主从数据库,binlog取自于从库
7、如果是一主多从,每个从库都要采集binlog,然后消费端收到最后一台binlog数据才删除缓存,或者为了简单,收到一次更新binlog,删除一次缓存
jetcache
Canal
用途
基于mysql数据库增量日志解析(数据库日志收集),提供增量数据订阅和消费
原理
伪装成mysql的slave订阅mysql-master的binlog,实现数据同步的中间件
1、canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送 dump 协议
2、MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
3、canal 解析 binary log 对象(原始为 byte 流)
实战
Canal+RocketMQ同步MySQL到Redis/ES
注册中心
zookeeper
是什么
分布式协调服务
“数据库”
经典的分布式数据一致性解决方案,致力于为分布式应用提供一个
高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务
高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务
为什么适合注册中心
心跳检测
感知服务提供者实例是不是正常
监听机制
节点变化立即通知消费者最新可用实例
原理
server:Map<path,客户端连接list>
client:向server注册事件后维护本地path事件
使用
启动
bin/zkServer.sh start
使用客户端连接
bin/zkCli.sh -server 127.0.0.1:2181
查询当前根节点下的Znode节点
ls /
查询当前节点的状态
stat /zookeeper
数据模型
【树结构】,znode[value,state]
节点(znode)
维护着一个 【stat 】结构,znode的元数据,节点状态
Zxid(事务ID)
cZxid(创建)
mZxid(更新)
pZxid(添加和移除子节点)
time
ctime创建时间
mtime更改时间
cversion
当前节点子节点版本号
dataVersion
当前节点的数据版本号
aclVersion
当前节点ACL权限版本
ephemeralOwner
如果是临时节点,该属性是临时节点事物ID
如果不是,这个值为0
dataLength(当前节点数据长度)
numChildren(当前节点的子节点数)
创建节点
create /locks ""
每一层级用斜杠(/)分隔开
1、生成日志
2、再去操作内存的数据(DateTree)
节点类型
持久节点
一旦创建,一直存在
create /school qinghua
get /school
临时节点
不会一直存储在zk服务器上
当创建该节点的客户端会话因超时或异常关闭时,该节点被删除
create -e /School/Teacher EphemeralTeachers
“-e”表示创建临时节点
顺序节点
持久节点、临时节点基础上增加有序性质
create -s /sequence-node- ""
使用“-s”参数表示创建顺序节点:
节点监听(watch)
客户端可以监听任意节点,节点变化了客户端会收到通知
不用轮询去查看实时状态,减少无谓的性能损耗
eg.消费者可以订阅感兴趣的服务,收到最新变更就缓存自己本地
eg.分布式锁实现上可以让后续阻塞的线程监听前一个节点,每一个线程释放了锁后一个线程就能知道,避免羊群效应
集群
主备系统架构模型
leader处理外部的写事物请求,然后leader将数据同步到follower节点
zk客户端请求
zk客户端随机链接到zk集群中的一个节点
如果是读请求,就直接从当前节点读取数据
如果是写请求,那么节点就会向leader提交事物
leader接收到【事物提交】,写log,再广播该事物,只要【超过半数】节点写入(记日志,收到ack确认)成功,【该事物】就会被提交(写内存,发送commit)
zab协议
是什么
Zookeeper Atomic Broadcast(Zookeeper原子广播)
基于zap,zk实现了主备模型(leader和follower)的系统架构来保证集群中各个副本之间数据的一致性
Paxos一致性算法、zab最终一致性
【最终】一致性(尽量想保证强一致性)
两种基本模式
奔溃恢复模式——快速领导者选举
当集群启动过程中,或当leader出现网络中断奔溃退出或重启等异常时
特征
确保已经在leader提交(commit)的事物最终被所有节点提交
确保丢弃那些只在leader上提出但没有被提交的事物
过程
每个zk都有一个属性,记录投票给谁,zk启动先投票给自己
相互之间建立socket,与其他启动的zk“交流”,互相通信告知对方选票
zk接收到对方的选票直接根据票的新旧策略(zxid,myid)决定选谁然后更新选票
当某个zk的选票大于zk节点总数的一半它就是leader
消息广播——二阶段提交2PC
先记日志,同步日志,收到过半ack,再写内存,广播commit让follower写内存
过程
1、leader节点先记日志持久化到磁盘,然后广播所有follower发送日志
2、follower接收到了会向leader发回去ack确认
3、leader收到超过半数的ack后,首先写内存,然后广播发送commit命令(异步,消息队列LinkedBlockingQueue)给follower,(有观察者节点的话同时把数据包发送它们)
4、follower收到commit命令然后写内存
观察者节点
既能提高读的性能,也不怎么影响写的性能
不参与领导者选举,不参与二阶段提交过程
eg.为了提高读的性能,增加普通节点的话,由于二阶段提交的协议,会额外影响zk写的性能。
所以这边额外定义了一个观察者节点
所以这边额外定义了一个观察者节点
集群为什么建议用奇数台机器
利用率
ack及选举超过一半
eg.5个节点,超过一半为3,最多挂掉2台;而6个节点,超过一半为4,也是最多挂掉2台
脑裂问题
不会发生
即在网络异常时各自网络中会选举产生多个leader,一旦网络恢复就无法确定到底已哪个leader数据为准
配置文件定义好节点总个数
eg.两个机房(不同网络分区),部署5个节点
情况1、一个机房3个一个机房2个(有leader),机房之间网络断掉,机房1会重新选出来leader,机房2不会
情况1、一个机房3个一个机房2个(有leader),机房之间网络断掉,机房1会重新选出来leader,机房2不会
分布式锁
适用于高可靠(高可用)而并发量不是太大的场景
部署
集群(CP模型)
原理
ZK特性
临时节点(客户端宕机没有释放也不会出现死锁,redis中通过expireTime来解决死锁)
节点监听机制(锁竞争时,每一个客户端监听前一个加锁节点;并且高并发下不会产生羊群效应给服务器带来瞬时压力)
顺序节点(通过查看自己是不是第一个节点来判断有没有获取锁成功;天生可实现公平锁)
过程
1、client1尝试加锁,调用ZK的api创建临时顺序节点路径(/test/lock/seq-000000001)并保存(create -e -s /../{lockName} {_UUID})
2、client1获取该锁的所有子节点,判断(sort-childs,get0==curPath)自己创建的顺序节点是不是第一个节点,如果是的话就获取锁成功执行自己的业务
3、此时如果有client2来竞争锁,则同样在该锁节点下创建一个临时顺序节点(/test/lock/seq-000000002)
4、client2获取锁的所有子节点,判断自己不是第一个节点,则会调用ZK的api给前一个节点添加一个监听器并等待
5、client1执行完业务释放锁,调用api删除它的临时节点
6、ZK通知监听节点删除的client2,client2去获取锁
可重入
可重入,确保同一线程可以重复加锁
ZkLock实例中用一个原子类AtomicInteger记录重入次数
优点
Zookeeper分布式锁(如InterProcessMutex),能有效解决分布式问题,不可重入问题
缺点
性能并不太高
为了保证最终一致性;zk写数据需要由leader通过二阶段提交过程来同步到followers,这种同步方式势必影响一定的性能
三方框架
curator的InterProcessMutex 可重入锁
定时任务
分库分表
Apache ShardingSphere
分布式
CAP
概念
针对分布式系统存储数据的
一致性(Consistency,C)
数据更新成功后,所有节点的数据完全一致
对客户端而言,多客户端【并发访问不同节点】所得到的数据应该一样
可用性(Availability,A)
保证服务一直是可用状态
用户访问数据时,系统是否能在正常响应时间内返回结果(不会因为oom等程序问题影响)
系统能够很好的为用户服务,不出现访问超时等用户体验不好的情况
分区容错性(Partition,P)
在不同网络分区的机器部分节点故障或丢包的情况下,集群系统仍然能够提供服务,完成数据的访问
三者不可兼得
在【分布式系统】中,CAP三者不可能兼得,只能保证AP、CP
分布式系统策略
CA
放弃分区容错性;
单机部署,可以保证一致性和高可用性,但节点一旦宕机就不能继续提供服务了
单机部署,可以保证一致性和高可用性,但节点一旦宕机就不能继续提供服务了
CP
放弃高可用性;
要求分布式系统保证一致性和分区容错;
显然为了保证所有请求访问节点的数据一致性,这就需要分布式系统【加锁】花更多的时间去做数据同步,间接就不能保证高可用性了
要求分布式系统保证一致性和分区容错;
显然为了保证所有请求访问节点的数据一致性,这就需要分布式系统【加锁】花更多的时间去做数据同步,间接就不能保证高可用性了
AP
放弃数据一致性;
保证高可用性和分区容错性;
可能导客户端并发访问时某一时刻得到的数据不一样
保证高可用性和分区容错性;
可能导客户端并发访问时某一时刻得到的数据不一样
在实践中,可根据实际情况进行权衡,或者在软件层面提供配置方式,让用户决定使用哪种策略
分布式与集群
分布式
多个子业务部署在不同的服务器上,提供统一的整体服务
集群
同一个业务,部署在多个服务器上
操作系统
I/O过程
DMA(Direct Memory Access)控制器
传统I/O
read:把数据从磁盘读到内核缓冲区,再拷贝到用户缓存区
1、用户应用进程调用read()函数,向操作系统发起I/O调用,上下文从用户态->内核态(切换1)
2、DMA控制器把数据从磁盘中,读取到内核缓存区
3、CPU把内核缓冲区数据,拷贝到用户应用缓存区,上下文从内核态->用户态(切换2),read()函数返回
write:把数据从用户缓存区写到内核socket缓冲区,再写入到网卡设备
1、用户应用程序通过write()函数发起I/O调用,上下文从用户态->切换为内核态(切换3)
2、CPU将用户缓冲区的数据,拷贝到内核socket缓冲区
3、DMA控制器把socket缓冲区的数据,拷贝到网卡设备,上下文再从内核态->用户态(切换4)
4、write()函数返回
4次上下文切换(用户态/内核态)
4次数据拷贝(两次CPU拷贝以及两次DMA拷贝)
零拷贝
概念
零拷贝并不是没有拷贝数据,而是减少用户态/内核态的切换次数以及CPU的拷贝次数
实现方式
mmap+write
应用了虚拟内存这个特点,它将内核中的读缓存区与用户空间的缓存进行映射,以减少数据拷贝次数
I/O发生了4次用户空间与内核空间的上下文切换,以及3次数据拷贝(包括了2次DMA拷贝和1次CPU拷贝)。
sendfile
表示在两个文件描述符之间传输数据,它是在操作系统内核中操作的,避免了数据在内核缓冲区和用户缓冲区之间的拷贝操作
I/O发生了2次内核空间/用户空间的上下文切换,以及3次数据拷贝(两次DMA拷贝,一次CPU在内核间的拷贝)。
带有DMA收集拷贝功能的sendfile
(sendfile+DMA scatter/gather)
(sendfile+DMA scatter/gather)
Linux2.4版本之后,对sendfile做了优化升级,引入SG-DMA技术,其实就是对DMA拷贝加入scatter/gather操作,它可以直接从内核空间缓冲区将数据读取到网卡
I/O发生了2次用户空间与内核空间的上下文切换,以及2次数据拷贝(都是DMA拷贝)。
这就是真正的零拷贝(Zero-Copy)技术,全称都没有通过CPU来搬运数据,所有的数据都是通过DMA进行传输的。
I/O模型
阻塞I/O模型
非阻塞I/O模型
原理
应用进程向操作系统内核,发起recvfrom读取数据
内核没有准备好会返回错误码(不会一直等准备好),准备就从内核拷贝数据到用户态
问题
频繁的轮询系统调用,会消耗大量的CPU资源
I/O多路复用模型
核心思路
系统给我们提供一类函数(如select,poll,epoll)多路复用器,阻塞调用,它们可以同时监控多个fd的操作,任何一个返回内核数据就绪,应用程序再发起recvfrom系统调用
较非阻塞模型中,需要N(N>=1)次recvfrom系统调用,而这个只需要接收到就绪状态调用一次,大大优化了性能
select
应用程序通过阻塞调用select()函数,只要有监控的fd数据状态准备就绪,select()就会返回就绪的fd,这时应用程序再去调用recvfrom去读取数据
缺点
监听的IO最大连接数有限,在Linux系统一般为1024
仅知道有IO事件就绪,但不知道是哪几个,所以需要遍历所有的fdSet,找到就绪的fd
poll
针对select可监听的IO最大连接数有限,进行改进解决了连接数限制问题
缺点
还是需要遍历所有的fdSet找到就绪的fd,如果同时连接大量客户端且只有小部分fd就绪,效率也会线性下降
信号驱动模型(epoll)
应用程序通过epoll_ctl()来注册一个fd(文件描述符),一旦某个fd就绪,内核会采用回调机制迅速激活这个fd,当应用进程调用epoll_wait()便得到通知
采用监听事件的回调机制,避免了效率低下地遍历所有fdSet找到就绪fd
I/O模型之异步IO(AIO)
缓存
页面置换算法
LRU(Least Recently Used)
淘汰最长时间没有被使用的页面
适合:较大的文件比如游戏客户端(最近加载的地图文件);
消耗CPU资源较少
LFU(Least Frequently Used)
淘汰一段时间内使用频次最少的页面
适合:较小的文件和零碎的文件比如系统文件、应用程序文件
消耗CPU资源较多
eg.内存块大小3,若所需页面顺序依次如下:2 1 2 1 2 3 4
LRU
2
2 1
1 2
2 1
1 2
1 2 3
2 3 4
LFU
2
2 1
1 2
2 1
1 2
3 1 2
1 2 4
计算机网络
网络协议
TCP/IP
传输层协议,主要解决数据如何在网络中传输
短连接/长连接
连接:TCP协议中如果两端想要传递数据,首先需要通过三次握手建立连接,握手完毕,连接就建立完毕,但是这个过程是比较消耗网络资源的。
短:一轮数据传输完成就断开连接,实现和管理方便,但频繁的建立连接比较消耗网络资源
长:数据传输完毕后,不断开连接,下次有数据传输还是使用这个连接,省去了握手过程
如何实现长连接
保活机制,心跳机制
HTTP
应用层协议,主要解决如何包装数据
请求方式(Request Method)
HEAD
能提供很多有用的信息,特别是在有限的速度和带宽下
和GET功能基本一样,只是只返回请求头部,不返回响应实体
检查URL的有效性
GET
通常用于请求服务器发送某个资源
安全:规定使用get方式请求的资源只用于获取,就像数据查询一样,不影响数据的状态
幂等:同一个URL多次请求应该返回同样的结果
POST
向服务器提交数据,比如提交表单信息到服务器处理
TRACE
服务器端会将它收到的请求信息返回给客户端,用于客户端验证传输过程中有没有被篡改
OPTIONS
用于获取URL锁支持的方法,若请求成功,会在响应头包含一个Allow的头,值就是所支持的方法,如POST,GET
报文
请求报文
结构
1、请求行;请求方式 URL HTTP协议版本
2、请求头信息
3、空行
4、请求实体数据
GET/POST
GET,请求的数据会拼在URL上,请求数据对应于HTTP报文的请求行和URL一起的
POST,请求的数据会放在请求报文的实体数据里;formData(浏览器会生成一个分隔符来标识每个kv)、json、html
GET,不安全,如登录接口是get方式,用户名和密码就直接暴露出来了,有别人使用你电脑查看你的浏览记录时就能看到这个URL
COOKIE
数据结构
树
b树
平衡多叉树
一个节点有多个元素
节点值有序,left<根<right
所有的叶子节点在同一层
b+树
b树的改进
非叶子节点不包含完整的数据记录,只做索引的作用
叶子会包含到所有元素
相邻的叶子节点之间有链表指针相连
数组
连续空间,首地址,根据位置index计算元素位置
链表
Node{data、nextNode}
队列
FIFO
实现
数组
链表
栈
FILO
实现
数组
链表
算法
BitMap
数组,元素是0或者1,表示有无
java中有BitSet
BitSet bitSet = new BitSet()
bitSet.set(5);
bitSet.get(5) == true
由于bitSet(int index),实际需求可能超过int的最大长度,可以采用分桶的设计方法
Map<String, BitMap>
eg.日均10亿次访问,统计访问前十ip/大数据量去重
1、内存新建数据长度为100亿,初始化元素为0
2、根据ip里面的数字组成一个数字,根据这个数字标记数组对应下标为1
一致性Hash算法
普通hash
eg.分布式存储系统,节点N,根据KEY存储相应的节点上,KEY%N
如果一个节点加入或者退出集群,之前所有的映射会失效
如果是分布式缓存,则缓存失效;如果是持久化存储系统,则要做数据迁移
实现
1、环形hash空间
按照常用的hash算法将对应的key哈希到一个具有2^32次方个节点空间中,即0 ~ (2^32)-1的数字空间中
我们可以将这些数字头尾相连,想象成一个闭合的环形。
2、映射服务器节点
将各个服务器使用hash进行一个hash,具体可以选择服务器ip或者唯一主机名作为key
eg.将4台服务器使用ip地址hash在环空间
3、映射数据
1、将objectA、objectB、objectC、objectD四个对象通过特定的Hash函数计算出对应的key值
2、然后散列到hash环上
3、然后从数据所在位置沿环"顺时针行走",第一台服务器就是其应该定位到的服务器
4、服务器的删除与添加
删除:eg.NodeC宕机
此时ObjectA、B、D不会受到影响,只有ObjectC会被重新分配到NodeD
添加:eg.在环境中添加一台节点Node X,通过hash算法将Node X映射到环中,加入映射到了ObjectC与NodeC之间
通过按顺时针迁移的规则,仅仅ObjectC会被迁移到Node X中
数据迁移达到最小,减小服务器大数据量迁移压力
5、虚拟节点
有一个问题还要解决,平衡性;当服务器节点比较少的时候,大量数据会映射到一个节点上面
实现
对每一个节点计算多个hash;
Node A#1”、“Node A#2”、“Node A#3”;“Node B#1”、“Node B#2”、“Node B#3”
Node A#1”、“Node A#2”、“Node A#3”;“Node B#1”、“Node B#2”、“Node B#3”
解决服务节点少数据倾斜问题
设计模式
模板方法
概念
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中
可以使得子类在不改变算法结构的情况下,重新定义算法中的某些步骤
观察者模式
概念
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都能得到通知并自动更新
这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
优点
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
目标与观察者之间建立了一套触发机制。
应用
Spring:ApplicationEventPublisher
void publishEvent(ApplicationEvent event)
实现
主要角色
抽象主题(Subject)角色
也叫抽象目标类
它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
具体主题(Concrete Subject)角色
也叫具体目标类
它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
抽象观察者(Observer)角色
它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
具体观察者(Concrete Observer)角色
实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
代码
思考模式
问题
解决什么问题
怎么解决的
结果怎么样
还有什么问题
介绍
STAR
运维
linux
基本命令
ifconfig
查看ip地址网络信息
top
显示当前运行的进程情况,cpu、内存....
eg.安装一个软件
wget {address}
下载
tar -zxf {file}
解压
cd {/usr}
打开文件夹
ls
展示当前目前文件
mv {soure} {dest}
移动文件
cat {file}
查看文件
vi {file}
编辑文件wq:保存退出
vi /etc/profile 修改环境变量
source {/etc/profile}
立即生效文件
进程查找/停止
ps -ef | grep {name}
查找所有和{name}相关的进程
-e 全部进程、-f 全部列
kill -9 {name}的进程号
停止进程
查看日志
cd {dir]
ls
ls -lt
按列表展示,最新的放最前面
ls | grep {file-name-keyWord}
按文件名关键字搜索文件
tail -f -n {n} {log.name}
-f,自动刷新查看最新n条记录
cat yeah1-alone.log | grep -C10 "服务器内部错误,请联系管理员" | tail -n 20
根据关键字查找某个日志的前后10行,并显示最新的20行
删除文件
find -mtime +21 -name "*.*" -exec rm -rf {} \;
删除21天前名称为*.*这样的文件
rm -rf *2022-03-29.*.log
删除*2022-03-29.*.log这样名称的文件,*为统配符号
远程工具
xshell
winSCP
传文件
docker
基本命令
镜像
docker pull {name}
docker images
docker rmi -f {name}
容器
docker ps (-a)
docker run {参数} --name {容器名称} {镜像ID}
参数
{-d(后台运行)-it(终端运行)}
{-p(端口隐射) 宿主机PORT:容器PORT}
-v 宿主机文件夹:容器文件夹;挂载
docker exec -it -u root jenkins_01 /bin/bash
进入容器
docker stop {container ID}
docker restart {container ID}
控制台日志
docker logs -f --tail 1000 {container ID}
k8s
项目下要有deploy.yml
后台操作
更改配置文件
平台管理 -> 集群管理 -> 配置 -> 配置字典 -> 筛选项目 -> 编辑设置
项目部署
重启
平台管理 -> 集群管理 -> 应用负载 -> 工作负载 -> 筛选项目 -> 进入项目 -> 左上角‘更多操作’-> 重新创建
切换仓库分支重启
系统组件 -> DevOps
项目修改分支
maven
命令
mvn clean install
devOps
CI/CD
开发工具
git
本地代码与远程关联
idea
git->manage remotes
merge into current
将选中的分支代码合并到当前checkout的分支
rebase current onto selected
已【选中的分支】作为基重新调整当前checkout分支;用于某些分支合并主干最新代码
错误:Rebase Error error: update_ref failed for ref 'refs/heads/
1、右下角栏git(有黄色感叹号!)点开
2、abort/continue rebase
已提交远程的代码恢复到之前的代码
1、找到之前的主干代码
2、查看log定位到想要恢复的版本,Reset Current Branch To Here(Hard模式)
3、去文件夹复制出来目前的代码到临时文件temp中
4、再次恢复到主干代码,然后将目标文件夹删除,将temp中的代码复制进去
5、提交代码
maven
optional=true
scope
抓包工具
fidder
ios抓包
区块链
是什么
区块链是一种源自于“比特币”的底层技术。换句话说,比特币是区块链技术的第一个大获成功的应用。
区块链是数字世界中进行“价值表示”和“价值转移”的技术。
区块链硬币一面是表示价值的加密数字货币或通证,另一面是进行价值转移的分布式账本与去中心网络。
分布式账本与去中心网络也常被称为“链”,它可被视为一个软件平台;而表示价值的通证常被称为“币”。
通证存储在链上,通过链上的代码(主要形式的智能合约)来管理,它是可编程的。
价值互联网
区块链信用层协议
信息互联网
www协议
收藏
0 条评论
下一页