Java知识总结
2023-12-29 11:01:47 32 举报
AI智能生成
Java是一种广泛使用的面向对象编程语言,具有跨平台、安全性高、易于维护等特点。Java语言的设计目标是让程序员“一次编写,处处运行”,即编写的Java程序可以在任何支持Java虚拟机(JVM)的设备上运行。Java的基本数据类型包括整型、浮点型、字符型和布尔型等,还有字符串、数组和集合等复杂数据类型。Java的控制流程语句包括条件判断、循环控制和异常处理等。Java还提供了丰富的类库,包括基础类库、集合类库、输入输出类库、网络类库等,方便程序员进行开发。此外,Java还支持多线程编程,可以通过继承Thread类或实现Runnable接口来实现。
作者其他创作
大纲/内容
大数据
flink
flink开发方式
ProcessFunction
DataStream API
SQL
窗口
窗口概念
flink用于做一计算的时候数据采集的区间范围就是窗口
窗口的生命周期
第一个属于这个时间窗口的元素到达,到第一个不属于这个窗口的元素到达
窗口的驱动方式
1.时间驱动
2.事件驱动
窗口分类
滚动窗口
相同时间间隔分配窗口,每个窗口之间没有数据重叠
滑动窗口
有固定的时间间隔,每个窗口之间是由时间重叠的,重叠时间是小于整个窗口的时间;
也就说一个元素可以分配到多个窗口之中;
时间窗口
没有固定的时间,通常是以一个事件进行窗口化
用户活跃的时候作为一个窗口时间,用户不活跃的时候作为活跃之间的间隙;
会话窗口
没有固定的时间,通常是以一个事件进行窗口化;
用户活跃的时候作为一个窗口时间,用户不活跃的时候作为活跃之间的间隙;
全局窗口
将所有的相同的key或者含有相同元素的放在一个窗口里面做计算;
count窗口
窗口函数
ProcessWindowFunction
AggregateFunction
ReduceFunction
区别
算子
针对数据做不同的实际算计的函数,比如说聚合,分组,切割,关联;
计算过程
网络词汇
CORBA规范
CORBA规范是一种工业标准,之后JavaEE的规范也参考了CORBA规范,这篇文章,我们看看CORBA规范都定义了哪些内容
SQLAdvisor
美团开发的sql优化工具
CouchBase
一种关系型数据库
OLTP应用
On-Line Transaction Processing联机事务处理过程(OLTP),也称为面向交易的处理过程,其基本特征是前台接收的用户数据可以立即传送到计算中心进行处理
特点:读多写少
服务器
tomcat
Tomcat组件
Tomcat核心参数说明
minProcessors
最小空闲连接线程数,用于提高系统处理性能
默认值为 10
maxProcessors
能够处理的最大并发请求数量
默认值75;
acceptCount
TCP三次握手结束后就绪的的最大允许连接数量;如果超过该连接数量,那么连接就会被放弃
默认100;
MaxConnections
最大连接数
超过这个连接
server.xml配置文件
connector
基础配置
port
端口号,根据实际情况设置应用服务的端口号
protocol
应用层通讯协议
IO模型默认是BIO
可以设置成NIO
connectionTimeOut
连接超时时间
redirectPort
重定向的通讯端口
当用户以HTTP请求方式去请求HTTPS资源的时候;这个时候Tomcat就会重定向以该端口重新发送HTTPS请求
UriEncoding
服务器以何种编码方式接收客户端的请求数据
线程配置
maxThreads
最大线程个数,如果Executor标签配置了线程池;那么该配置就失效
子主题 2
子主题 3
Executor线程池
属于非必须配置标签;该配置可以不用配置;
属性值
maxThreads
最大线程个数
实际处理请求的最大线程数量
允许客户端连接的最大线程数
默认值200
minSpareThreads
初始时创建的线程个数;随着后面的请求增多,线程也会慢慢增多
默认值4;
maxIdleTime
线程空闲时间;空闲的线程超过了这个时间,就会被销毁
默认值:1分钟
maxQueueSize
请求被执行前,最大排队数目
默认值为int最大值
prestartminSpareThreads
线程池是否开启最小线程数量配置;也就是minSpareThreads配置是否生效
threadPriority
线程池中线程优先级
默认值为5,值从1到10
className
线程池实现类,可以指定线程池实现类
如果没有指定实现类:默认是org.apache.catalina.core.StandardThreadExecutor
connector和线程池关系
connector是服务器负责与客户端建立连接的管理者;
线程池是连接器接受了客户端的请求,之后就会从线程池中拿一个线程去服务这些连接;
子主题 5
子主题 6
子主题 8
tomcat运行模式
bio
默认的工作模式,性能低下,没有经过任何优化
nio
比bio新能更高
apr
解决大量异步IO问题
tomcat性能优化
性能优化指标
降低相应时间
提高服务吞吐量
提高服务可用性
优化原则
具体情况具体分析
积少成多
性能优化工具
JCONSOLE
JMETER
Tomcat优化方式
内存优化
设置Tomcat有所运行的jvm的内存配置
Tomcat本省就是Java语言写的,运行在JVM之上,所以也可以从Tomcat本身所运行JVM上优化
针对不同操作系统
Windows
catalina.bat
设置JVM的垃圾回收参数
Linux
catalina.sh
设置JVM的垃圾回收参数
配置优化
connector优化
protocol
设置成nio形式
//NIO protocol="org.apache.coyote.http11.Http11NioProtocol" //NIO2 protocol="org.apache.coyote.http11.Http11Nio2Protocol"
设置ARP
protocol="org.apache.coyote.http11.Http11AprProtocol"
compression
是否启用gzip压缩
compressionMinSize
启用压缩后,输出的内容大小,默认2kb
compressableMimeType
压缩类型
监听器优化
线程池优化
合理设置线程池参数
IO方式优化
修改IO方式为NIO,或者APR
只处理动态资源
大量的静态资源会占用内存空间,导致GC;
组件优化
使用APR组件
server
不建议优化
service组件
不建议优化
Engine
不建议优化
Connector连接器优化
修改不同的io方式
设定自己的线程池大小
Host
自动部署,如果包有更新,就会自动部署
Context
工作方式选择
为了提升性能,首先就要对代码进行动静分离,让 Tomcat 只负责 jsp 文件的解析工作。如采用 Apache 和 Tomcat 的整合方式,他们之间的连接方案有三种选择,JK、http_proxy 和 ajp_proxy。相对于 JK 的连接方式,后两种在配置上比较简单的,灵活性方面也一点都不逊色。但就稳定性而言不像JK 这样久经考验,所以建议采用 JK 的连接方式
nginx
功能
代理
正向代理
客户端先给自己NGINX服务发送请求,NGINX再把请求转发给目标服务器
反向代理
客户端发送的请求到服务器,是先经过服务端的代理服务器NGINX,然后再转发给实际提供服务的服务器
正反代理区别
1.正向代理:客户端和代理服务器在同一个网络中;反向代理:实际的服务器和代理服务器数据一个网络
2.正向代理,服务器是透明的,知道具体的服务器地址;反向代理,服务器是不透明的,不知道代理服务器请求的是具体哪台服务器
动静分离
NGINX本身也是一个静态资源服务器,请求可以获取到NGINX上的静态资源,所以可以做静态资源服务器
负载均衡
NGINX自带负载均衡算法
(默认)轮询算法:请求一台一台的给机器分发
根据IP地址做hash
权重算法
第三方插件负载均衡算法
根据相应时间长短来分配,相应时间短的请求可以获取到更多的请求
根据URL做hash路由,特定的请求会路由到特定的服务器上
NGINX工作方式
NGINX工作模式
单工作进程
工作进程是单线程
多工作进程
工作进程是多线程
工作特点
1.接受用户请求异步,无需长连接
2.接受一定了的请求,再全部一次转发给相应的服务器,减少连接消耗;
3.接受数据和发送请求给具体服务器可以同时进行
进程
主进程
主进程是管理工作进程;
工作进程
单工作进程
多工作进程
nginx.conf的worker_ processes 可以配置工作进程个数
多进程
多进程概述
主进程会按照配置fork出相应个数的工作进程,工作进程至少会有一个;
工作进程连接池
1.每个工作进程都会有自己独立的连接池(连接池中最大连接个数就是worker_connections)
2.连接池中连接存储的空闲连接链表,需要用到连接的时候从链表中获取这些连接
NGINX连接数说明
最大连接数量=工作进程数*worker_connections
最大并发数量=进程数*worker_connections/2(因为NGINX是客户端和实际服务器的桥梁,一个请求会占用两个连接)
master,work进程通信
通信方式
主进程和工作进程之间是单向通信,只有主进程给工作进程发送消息;工作进程是无法给主进程发送消息;
通信类容
工作进程的编号;工作进程的进程索引;文件描述符
多进程好处
1.多个进程之间是相互独立,一个进程挂了不会影响其他进程工作;
2.进程绑定了CPU,减少CPU切换造成的时间消耗;
3.热启动,修改配置,无需重启NGINX服务器
1.master进程接到命令,重新加载配置文件;
2.建立新的进程,再停掉所有的老进程;
3.老的进程在停掉之前会把已接受的请求处理完再退出;
多进程工作
所有的工作进程都是从主进程中fork出来,所有的工作进程都会去监听相同的服务器;所有的工作进程在获取请求的时候都会有一个共享锁,只有一个进程会获取到这个请求;
处理请求
处理请求过程
1.启动的时候解析配置文件,监听对应的服务IP,端口;
2.在主进程中初始化对被监听机器的socket连接;
3.fork出多个子进程,用于处理客户端发送的请求(客户端也是需要经历三次握手才能与NGINX服务器建立连接);
4.子进程竞争到了请求之后,就开始对socket连接进行封装,设置处理函数,读写事件来与客户端进行数据交互;
5.NGINX服务器或者客户端主动关闭连接,一个连接就结束
处理请求特点
异步,时间驱动方式处理连接
线程池
epoll
Nginx的事件处理机制,也就是网络io方式都是多路复用
实现的形式都是epoll形式,和Redis,RocketMq,Dubbo的网络io方式是一样的;
数据库
数据库中间件
canal
canal简述
java 语言写的
目前可以把从mysql主机dump到数据 发送到 mysql,kafka,es,hBase,rocketMQ,普罗米修斯(数据分析系统)
canal作用
基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费
canal工作
1、mysql服务需要开启 binlog日志(默认是不开binlog日志)
2、在主库的配置中给 canal 链接授权,让他有mysql 从机权限,也就是可以伪装成mysql的从机
canal工作原理
1.canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送 dump 协议
2.MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
3.canal 解析 binary log 对象(原始为 byte 流)
mq顺序性问题
binlog日志本身是顺序的。但是写入到MQ之后,就会有顺序性问题
设置canal的路由方式:
1.单topic单分区,就是严格按照binlog日志一样的顺序性,性能比较差,
也就是能够保证每一个库每一个表的数据都是不乱的。
2多topic单分区,保证表级别顺序性,一张表,一个库所有的数据都写入到一个分区里面去。
分库分表
sharding-jdbc
使用
限制
1、分库分表,这个操作是开发来做的事情,目前框架是不能自动分库分表,这个框架只是帮助我们去管理这个
使用方式
以jar包的形式提供服务,无需外部部署和依赖,目前已经和springBoot做了整合
在框架中使用insert ,select语句的时候和当前使用的持久层框架是一样的,只不过表名是逻辑表名;
执行的时候根据相关的配置,生成真实的sql去做查询,所以说select就会生成多条数据,然后把查询的结构及合并在一起;
注意事项
表关联的时候查询,如果两个表没有绑定关系,那么关联查询会有笛卡尔积;
公共表的处理
一般说是每一个库都加上这个公共表
sharding-jdbc去管理这个表,crud的操作会映射到每一个公共表中,所以说一个添加操作
所有的表中都会有这个数据;
读写分离处理
读写分离:sharding-jdbc提供了一主多从的配置
sharding-jdbc执行步骤
1.sql在连接数据库之前在sharding-jdbc中需要做解析(把sql语句分解),
2.路由:这个逻辑sql语句,最终是需要在哪些数据库的节点上执行;
3.sql改写:逻辑的表名就会被改写成真实的表名;
4.sql执行:分为两种模式,根据性能消耗,可做自动切换
1.内存限制模式:会根据需要操作的真实的表,创建对应个数的数据库连接;OLAP 面向效率操作
2.连接限制模式:不管需要操作多少个真实的表,都只会有一个数据库连接;如果表分在多个数据
上,那么就会每一个数据库创建一个数据库连接;OLTP面向事物操作;
5.结果归并:把结果集合并处理成需要的
1.内存归并:拿到所有的结果集,在内存中处理
2.流失归并:通过数据库的游标,一遍查询仪表做归并操作;
oneProxy
概述
使用C&C++语言开发
OneProxy主要功能
1. 垂直分库
2. 水平分表
3. Proxy集群
4. 读高可用
5. 读写分离(master不参与读,支持一主多从,也就是会有多台从机
6. 读写分离(master参与读)
7. 写高可用
8. 读写随机
9. SQL检查
10. SQL统计【暂无文档】
11. 任务队列监控【暂无文档】
12. 连接池管理【暂无文档】
oneProxy的安全性
SQL白名单(防SQL注入)
IP白名单功
SQL防火墙软件
mycat
mycat分库分表概述
数据库中间件
示意图
子主题
mycat目前支持的数据库包括:MySQL、SQL Server、Oracle、DB2、PostgreSQL
mycat分库分表的时候 ,应用想去连接mycat的,然后mycat再去连接底层的各个数据库;mycat的默认端口是8066,使用的时候需要自己部署mycat相关的服务
主要功能
数据库的垂直拆分
按照不同的业务,分配不同的库
水平拆分
相同的业务,将数据扩展到不同的库
水平+垂直拆分
读写分离
工作原理
1、拦截应用发出的sql语句请求
2、对sql语句做 分片分析,路由分析,读写分离分许,缓存分析
3、再把sql发往真实的数据库做sql操作
可以把mycat看做是看做是对数据库的操作租了一层代理拦截
示意图
子主题
工作场景过程说明
查询
场景
假设user_info做了分库操作,分了三个库:
工作场景
1.select * from user_info
这个sql语句会向三个DB 发送数据请求,
mycat会把sql转成
select * from db1.user_info
select * from db2.user_info
select * from db3.user_info
然后mycat 在把结果集封装在一起,返回给客户端;
2.select * from db1.user_info where id =1 ;
mycat 就会根据分片规则,定位到具体的数据库,然后直接只给一个数据库发送请求;
3.select * from user_info limit 0,2
往三个库里面发三个select请求 获取三对 六条结果
随机抽取一对返回给客户端
4.select * from user_info order by id limit 0,2
先发送三个select 每个都是最大的两条 然后返回给mycat 进行综合评选拿出最大的俩 返回给客户端
5.select * from table order by id limit 5,2;
sql解析,也就是跳过前面5条,取6,7条数据;
如果mycat 向三个数据库发送都是 limit5,2 则会出现问题,
场景演示
解决方式
mycat 就会做sql改写操作
limit m,n 的SQL语句时会对其进行改写,改写成 limit 0, m+n 来保证查询结果的逻辑正确性
所以真正想每个数据库发送的请求是:
select * from table order by id limit 0,7;
所以真正想每个数据库发送的请求是:
select * from table order by id limit 0,7;
6.任意表的join操作
关联的查询的表,需要把能够关联在一起的数据放在一个库里面,如果不能保证这点,
那么就需要 放弃这两个表数据分库
更新
1、Mycat不会立即把命令发送到DB节点上,等后续下发SQL时,Mycat从连接池获取非自动提交的连接去执行
2、Mycat会等待各个节点的返回结果,如果都执行成功,Mycat给该连接标识为 Prepare Ready 状态,
如果有一个节点执行失败,则标识为 Rollback 状态
如果有一个节点执行失败,则标识为 Rollback 状态
示意图
3、执行完成后Mycat等待前端发送 commit 或 rollback 命令。发送 commit 命令时,
Mycat检测当前连接是否为 Prepare Ready 状态,若是,则将 commit 命令发送到各个DB节点
Mycat检测当前连接是否为 Prepare Ready 状态,若是,则将 commit 命令发送到各个DB节点
示意图
mycat线程模型
示意图
Cobar
TDDL
atlas
关系型数据库
关系型数据库基础
关系型数据基础概念
范式
第一范式
每个字段都是原子,不可再分割
关系型数据库最基本要求
第二范式
满足第一范式的前提下,每一行的数据只能与其中一列相关,记录需要有唯一标记
第三范式
满足第二范式的前提下,每个字段都是直接与主键过关系,其他字段之间不能存在传递关系
数据存储
数据库引擎
innerDB
文件结构
FRM文件
表结构文件
IBD文件
索引数据+表数据
锁
支持行锁,表锁
行锁
记录锁
通过锁定索引记录来实现;锁定某一个索引记录
使用场景
只用通过索引条件查询数据的时候,才会使用到
作用
间隙锁
通过索引记录来实现
对一定范围内数据进行加锁,即使这数据不存在也是会加锁
Select * from emp where empid > 100 for update;会对empid大于100的都加上间隙锁,即使记录不存在也会添加
使用场景
作用
防止幻读
Next-key锁
相当于一个记录锁+间隙锁
使用场景
作用
避免幻读
表锁
意向共享锁
给数据行加行级锁之前需要取道该表的意向共享锁
意向排他锁
给数据行家排他锁之前需要取到该表的意向排他锁
日志
redolog(innerDB引擎独有)
redolog日志类容
记录后事物执行之后的状态,用来记录未写入 datafile 的已成功的事物更新的数据;
日志作用
在MySQL宕机时候,还有数据没有写入磁盘,可以通过重做日志重做,从而达到数据的持久化
日志生成
事物开始的时候就会有日志生成,随着事物的进行的过程中,日志逐渐写入redolog中
日志清理
当某个事物对应的数据写入 datafile中后,redolog的使命已经完成了,重做日志的空间就会被重用
undolog
undolog生成时间
数据库的每一次修改(insert,update,delete)操作,在落到磁盘之前都会先记录到undolog
undolog日志内容
记录信息修改之前和修改之后的数据
undolog日志回滚操作
根据undolog日志做数据库的逆向操作,原来的insert操作变成delete操作,delete变成insert,update变成逆向update
Myisam
文件结构
frm文件
存储的是表结构信息
MYD文件
存储的是数据行记录信息
MYI文件
存储索引对应的数据信息
myisam工作流程
根据索引
锁
支持表锁,不支持行锁
innerDB和Myisam区别
索引数据结构
myisam索引的B+树的叶子节点存储的是该条数据的地址值
innerDB索引的B+树的叶子节点存储的是该条数据的全部字段值(主键索引)
innerDB索引的非主键索引叶子节点存储的是id值(非主键索引)
锁区别
innerDB支持行锁,表锁;myisam支持表锁
事物
innerDB支持事物;myisam不支持事物(原因其实主要是myisam不支持行锁,间接导致不能支持事物)
是否支持全文索引
MySQL 5.6 以前的版本,只有 MyISAM 存储引擎支持全文索引
MySQL 5.6 及以后的版本,MyISAM 和 InnoDB 存储引擎均支持全文索引;
是否支持外键
innerDB支持外键;myisam不支持外键
主键是否必须
innerDB支持必须有主键;myisam可以没有主键
数据存储不同
innerDB数据存储frm、ibd文件;frm是表定义文件,ibd是数据文件
Myisam是frm、MYD、MYI;frm是表定义文件,myd是数据文件,myi是索引文件
innerDB数据索引数据和表数据是存储在一起;而myisam引擎索引数据和表数据是分开存储在两个文件
是否存储具体行数
innerDB不存储表的具体行数;myisam存储表的具体行数
是否支持表压缩
innerDB不支持表数据压缩;myisam支持表数据压缩,压缩后支持只读,修改操作需要加压才能操作;
memory
锁
支持表锁,不支持行锁
在内存中存在表数据,数据库重启之后,该引擎对应的数据就会消失
默认是使用hash索引
Merge
存储引擎允许将一组使用MyISAM存储引擎的并且表结构相同的数据表合并为一个表,方便了数据的查询
csv
其他存储引擎包括CSV(引用由逗号隔开的用作数据库表的文件)
ARCHIVE
为大量很少引用的历史、归档、或安全审计信息的存储和检索提供了完美的解决方案。
BLACKHOLE
用于临时禁止对数据库的应用程序输入
FEDERATED
能够将多个分离的MySQL服务器链接起来,从多个物理服务器创建一个逻辑数据库。十分适合于分布式环境或数据集市环境。
EXAMPLE
可为快速创建定制的插件式存储引擎提供帮助
BDB
可替代InnoDB的事务引擎,支持COMMIT、ROLLBACK和其他事务特性。
Cluster/NDB
MySQL的簇式数据库引擎,尤其适合于具有高性能查找要求的应用程序,这类查找需求还要求具有最高的正常工作时间和可用性
索引
索引分类
使用方式分类
单列索引
主键索引
根据主键排序做的索引,特殊的唯一索引
唯一索引
强制让表中某个字段值不能重复
普通单列索引
根据某一列字段建立的索引
组合索引
把建立索引的字段值全部兼容在一起组成的索引
聚簇非聚簇
聚簇索引(一级索引)
索引的顺序和数据存储的顺序是保持一致
主键索引就是数据聚簇索引
非聚簇索引(二级索引)
索引的存储顺序和数据的存储顺序不是一致
非主键索引全部都是属于聚簇索引
稀疏密集
稀疏索引
多条数据可以对应一条索引数据
非主键索引和有可能就是稀疏索引
密集索引
数据和索引是一一对应的
主键索引就是密集索引
索引数据结构
B+树
B+树的数据存储结构
数据库引擎不同B+树存储的数据不完全相同
innerDB的的B+树叶子节点存储的是全部的数据或者是主键id;
myisam的B+树叶子节点存储的时候该条数据地址值(不管是主键索引还是其他索引)
B+树和B树区别
B+树的非叶子节点存储的是节点之间的前后指向关系,并不存储实际的数据值;B树的只要是节点不管是叶子节点还是非叶子节点都会存储数据。
B+树的节点存储兄弟节点的地址值,可以直接指向兄弟节点;而B树是不可以的。
在相同数据层级,B+树能够存储更多的数据;而B树存储的数据少很多;
使用场景
myisam和innerDB索引都是使用B+树
hash索引
基于hash算法的散列索引
特点
查询单条数据查询时间的复杂度为01;
范围查询速度比较低
使用场景
memory引擎默认使用该中索引
全文索引
MySQL 5.6 以前的版本,只有 MyISAM 存储引擎支持全文索引;
MySQL 5.6 及以后的版本,MyISAM 和 InnoDB 存储引擎均支持全文索引;
使用
只有字段的数据类型为 char、varchar、text 及其系列才可以建全文索引。
索引开销
创建和维护索引都需要耗费时间和磁盘空间
修改语句操作的时候,需要对索引进行重新构建
索引相关概念
回表查询
查询的时候根据组合索引去查询,从索引中获取到的数据并未覆盖所需要 查询的全部字段,所以就需要那到索引中的id再回到表中去查询需要的字段,这就是回表
非索引下推
查询的时候,先根据组合索引中最左索引查询,拿到这些查询之后的数据的主键,再回表查询出整条数据,再从整条数据从按照其他条件做筛选;
索引下推
如果查询条件中包含了组合索引,那么就会优先使用组合索引查询,查询满足组合索引中最左索引的值,从这些筛选一遍的数据中再过滤出其他符合条件的数据,不用再次回表查询;
最左匹配原则
组合索引中,如果查询条件中并没有带上组合索引中最左边的索引,那么这个查询是不会先去查询组合索引
覆盖索引
查询的是主键索引字段,也就是查询的数据本身就是索引,直接从索引中拿数据,不需要再去根据主键回表查询其他数据
一次性非锁定读
就是普通的select
一次性锁定读
在select 语句后面中加 for update 或者是 lock in share mode
查询需走索引
表数据存储
记录的存储形式
概述
数据库的存储形式,最常见的就是将每一行的数据组织称为一条记录;通常每页会存储一条或者多条记录;
记录的存储格式
字段可以是定长也可以是不定长的;所以记录也存在两种方式:定长记录,不定长记录
记录头包含了记录的整体信息,指向表的指针,记录长度,时间戳,锁标记,字段数,各字段类型等等
记录存储示意图
子主题
定长记录
定长记录在页中的存储比不定长记录的存储结构稍微简单一些,只有页头,记录
页头存储数据
页的类型,记录数,记录长度,与前后也的链接等信息;
由于所有的记录都是定长的,所以是可以在页中进行连续存储,各记录
可以通过记录的偏移量(每条记录的存储空间都是一样的) X 记录次序;
每页的空间不一定正好用完,最后的不够一条记录的空间将会被空余出来;
定长记录存储示意图
子主题
不定长记录
不定长记录主要有四个部分
页头,目录区,剩余空间,记录区
目录区主要存放的是每条记录的偏移量(记录每条都是不定长的,需要去记录每条记录所占空间的大小)
记录区与定长记录的存储是不一样的,不定长记录是从页末开始往前存储的;
每条记录的获取可以通过目录区里面每条记录的偏移量和次序计算出 总的偏移量,获取目标记录;
不定长存储示意图
子主题
记录寻址
记录在页面的寻址可以通过 偏移量和次序来计算出来
整个寻址包括:页寻址+记录寻址;
B+树数据存储
非叶子节点
不会完全的冗余该条数据的所有数据,只会冗余对应的索引字段的数据
叶子节点
存储索引数据
非主键索引存储
b+树中的叶子节点存储的数据有索引本身已经排好序的数据,以及索引对应的主键
非主键索引查询
根据非主键索引查询得到主键;再根据主键去做查询;
主键索引存储
是按照主键排序 B+树的数据结构中,叶子节点存储的具体的数据(该表有几个字段,这个几个字段都会被一起来存储),中间节点存储的时候索引的相关数据
存储兄弟节点的地址信息
一个节点可以存储多个元素;b+的一个节点的数据基本都是在一页中
和B树区别
1.B+tree 的兄弟叶子节点上会存储对方节点的磁盘地址值;
2.B+tree只有叶子节点上才会存储列的数据;
索引存储大小
节点默认的存储空间大小是16kB
主键索引的非叶子节点都是存储的是下一层元素的指针以及当前主键的值,
指针的大小是6个字节,主键的大小如果是bigInt类型就是8个字节
每一个非叶子节点可以存储16*1000/(6+8) = 1170 单位
三层B+tree最多可以存储1170*1170*16 也就是2100多万的数据
mysql
mysql基础
锁
锁的分类
按照粒度划分
库锁
全局锁就是对整个数据库实例加锁
MySQL提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,其他线程的数据更新(数据的增删改)、数据定义(包括建表、修改表结构等)和更新类事务的提交会被阻塞
使用场景
全库逻辑备份
整库每个表都select出来存成文本
表锁
表锁是由MySQL本身来实现
表锁具体实现
意向共享锁
给数据行加行级锁之前需要取道该表的意向共享锁
意向排他锁
给数据行家排他锁之前需要取到该表的意向排他锁
概述
意向锁,锁是由innerDB自动添加,无需用户手动添加
页锁
行锁(innerDB实现)
记录锁
永远是去锁定索引记录的(主键,唯一索引);记录锁(Record Lock)是针对索引记录(index record)的锁定
间隙锁
也是通过锁定索引记录的(主键,唯一索引);间隙锁(Gap Lock)锁定的是索引记录之间的间隙、第一个索引之前的间隙或者最后一个索引之后的间隙
作用是防止其他事物做插入操作
Next-key锁
Next-key 锁(Next-key Lock)相当于一个索引记录锁加上该记录之前的一个间隙锁。
innerDB行锁实现
innerDB的行锁是在索引上加锁,如果一张表没有索引,那么加的是表锁; innerDB默认加的就是行锁
innerDB如果没有主键,那么还会有一个rowId的唯一表示;
(粒度越大,并发性越差,开销越少;粒度越小,开销越大,并发越好)
一次性非锁定读
一次性非锁定读:就是普通的select
一次性锁定读
在select 语句后面中加 for update 或者是 lock in share mode
锁分类说明
乐观锁,悲观锁都是在并发事物的一种控制方式,是一种思想,不是实实在在的锁
行锁,表锁也并非实实在在的锁,只是锁的粒度来划分的锁
二阶段锁
概述
关系型数据库的一种加锁原则;把锁的操作划分为两个阶段,加锁阶段,解锁阶段
实际过程
加锁阶段,解锁阶段互不相交,加锁阶段,只加锁,不解锁;解锁阶段,只解锁,不加锁
加锁导致问题
1.事物阻塞,等待
2.死锁
事物各自持有对方想要的锁,并且都在相互等待对方释放锁
解决方式
1.一次封锁发,事物一次就占据所有字资源,要么全部封锁,要么全不封锁
2.顺序加锁,定义锁的加锁顺序,只能这么来
3.时间戳
引发表锁的原因
当并发的访问,修改某一张表的时候,会触发表锁;
ACID
基础概念
原子性
事物中发生多件事情,多件事情要么都成功,要么都失败
场景
A给B转钱,A成功的把钱给了B,B也成功的收到了A的钱
一致性
一种状态到另外一种状态必须保证数据逻辑上的一致
场景
A给B转1000块钱,A的账户扣了1000块,B的账户多了1000块
隔离性
当前事物不受其他事物影响
持久性
数据能够被持久化的存储在磁盘上
事物提交之后,该事物对应的修改数据都会被存储到磁盘上
ACID实现
原子性
因为需要在失败的时候做数据回滚操作,所以原子性是通过undolog来实现的
undo log
逆向记录mysql执行的sql语句;insert 语句生成对应的delete语句,delete语句生成insert语句,update更新语句生成更新回来的语句;
持久性
redolog来实现
每次操作数据库之前都会把更新的操作记录在redo log中
先写日志,再写磁盘
会记录事物开启之后对数据做的修改
redo log 记录的时候新数据的备份数据;数据都是先记录在redo log中做持久化,然后再记录在内存中,内存积累到一定的数据量的时候才会写进磁盘。假设这个时候断点了,那么内存中的sql语句都找不到了,那么这个时候,redo log的日志就可以用来恢复数据了这个数据已经持久化到了磁盘了,
隔离性
锁,mvcc来控制
事物之间的写写操作,通过锁来实现,事物之间的读写操作通过mvcc
写-写操作隔离性,通过锁来实现
读-写操作隔离性,通过MVCC来实现
一致性
通过原子性,持久性,隔离性来保证一致性,也就是通过redo log ,undo log ,锁 来实现
事物
事物隔离级别
读未提交
A事物可以读取B事物没有事物提交的数据
脏读
同时开启AB两个事物,A事物可以看到B事物没有做事物提交的数据
A事物读取B事物更新的数据,B又做了回滚,A事物读到的是脏数据;
不可重复读
A事物反复读取B事物事物更前之前和之后的数据,读取的数据不一致;
一个事务从开始到提交前所做的任何改变都是不可见的,事务只能读取到已经提交的事务所做的改变。
读已提交
可重复读
A事物反复读取B事物事物更前之前和之后的数据,读取的数据数据依旧是第一次读取的数据;
mysql默认的隔离级别就是这个
序列化SERIALIZABLE
同开启AB事物,A事物没有提交,B事物无法对A事物已经操作的过的任何其他数据进行操作
示意图
事物调度
并行
多个事物同时执行
串行
事物必须是一个一个的去执行,不能并发的去执行
并发事物控制
乐观控制
概念
每次访问数据的时候,都认为是查询操作,其他事物也能访问当前事物的数据;造成的数据不一致性,采取的方式是修改数据之后,对修改的数据进行回滚操作
特点
1.读操作过多的情况下,能够显著提高效率
2.采取时候回滚操作来来解决事物冲突问题
具体实现
通过在需要更新的表中设立相关的数据版本号来做操作
MySQL乐观锁使用场景
可在分布式场景下控制多个服务对相同数据的修改是线性执行的;
数据库的版本锁
悲观控制
概念
每次访问数据的时候,都会对数据进行加锁操作
特点
1.采取的是提前预防冲突
2.严格的控制其他事物对当前事物的访问,避免大量的数据回滚
3.其他事物,在某些情况下还是可以访问当前数据
具体实现
通过数据库本身的锁机制来实现悲观控制
mysql悲观锁使用场景
单体服务的时候就实现对并发事物的悲观控制
概述
对并发(交叉)事物的控制的方式;数据本身对并发事物的控制都是悲观控制
数据库对象
触发器
某个时间所触发的操作;一种特殊的存储过程;
作用
主要用作:实现主键和外键的一致性完整性;
存储过程
就是被用户定义的 sql语句的集合。(语句一般都是多条数据)
序列
序列就是表对应的主键;
1.可以指定表开始的主键的值;
2.主键自增可以设置相应的长度;
视图
就是基于基表的一种逻辑表或者虚拟表。
MVCC版本控制
MVCC作用
1、解决读写冲突;
2、保证在事物并发条件下的读操作的效率,写操作的准确性。
MVCC概述
记录每一行的多个版本,来避免在多个事务之间的竞争。以空间换时间的思路,极大地提高了读写性能
MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作
MVCC控制分类
快照读
概述
读取记录的可见版本(可能是历史版本,也可能是当前版本)不用加锁
使用场景
不加锁的select操作
当前读
当前读就是读取的最新数据,而且还要保证其他事物不能修改当前数据
场景
加锁的读操作
select * from table where ? for update;
select * from table where ? lock in share mode;
当前写
概述
加锁读取记录的最新版本,保证其他事物不会并发修改这条记录;
使用场景
特殊的读操作
插入/更新/删除
insert into table values (…);
update table set ? where ?;
delete from table where ?;
因为修改操作之前,都是先需要查询到目标记录;
当前读,快照读,Mvcc关系
子主题
MVCC实现原理
依靠的时候undo日志,read view ,三个隐式字段
执行计划详解
id(sql执行顺序)
id相同,从上到下顺序执行
id不同,id值越大执行优先级越高,越先被执行
select_type查询类型
SIMPLE
简单的单表select查询
PRIMARY
子主题 1
SUBQUERY/MATERIALIZED
SUBQUERY
表示select或者是where中含有子查询
MATERIALIZED
表示where后面的in条件有子查询
UNION
被关联查询的语句;也就是关联查询的union中的第二个语句或者是union后面的select语句
UNION RESULT
表示关联查询的结果
table(查询涉及到的表)
直接显示表明
<unionM,N>
由M,N两个表关联产生的结果
<subqueryN>
有N表产生的中间结果集维表
type (重点,查询性能指标)
system
查询的是系统表
const
单表查询直接命中主键查询
demo
CREATE TABLE `user` ( `id` int(11) NOT NULL, `NAME` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into user values(1,'shenjian');insert into user values(2,'zhangsan');insert into user values(3,'lisi');
explain select * from user where id=1;
eq_ref
关联查询用到主键索引做为关联字段
demo
CREATE TABLE `user` ( `id` int(11) NOT NULL, `NAME` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into user values(1,'shenjian');insert into user values(2,'zhangsan');insert into user values(3,'lisi');CREATE TABLE `user_ex` ( `id` int(11) NOT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into user_ex values(1,18);insert into user_ex values(2,20);insert into user_ex values(3,30);insert into user_ex values(4,40);insert into user_ex values(5,50);
EXPLAIN SELECT * FROM USER,user_ex WHERE user.id=user_ex.id;
ref
关联查询用到了非主键索引作为关联字段
demo
EXPLAIN SELECT * FROM USER,user_ex WHERE user.id=user_ex.id;
fulltext
全文索引检索,要注意,全文索引的优先级很高,若全文索引和普通索引同时存在时,mysql不管代价,优先选择使用全文索引
ref_or_null
与ref方法类似,只是增加了null值的比较。实际用的不多
unique_subquery
用于where中的in形式子查询,子查询返回不重复值唯一值
index_subquery
用于in形式子查询使用到了辅助索引或者in常数列表,子查询可能返回重复值,可以使用索引将子查询去重
range
在索引上的范围查询
demo
CREATE TABLE `user` ( `id` int(11) NOT NULL, `name` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into user values(1,'shenjian');insert into user values(2,'zhangsan');insert into user values(3,'lisi');insert into user values(4,'wangwu');insert into user values(5,'zhaoliu');
explain select * from user where id between 1 and 4;explain select * from user where id in(1,2,3);explain select * from user where id > 3;
index_merge
表示查询使用了两个以上的索引,最后取交集或者并集
常见and ,or的条件使用了不同的索引
index
demo
CREATE TABLE `user` ( `id` int(11) NOT NULL, `name` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into user values(1,'shenjian');insert into user values(2,'zhangsan');insert into user values(3,'lisi');insert into user values(4,'wangwu');insert into user values(5,'zhaoliu');
explain count (*) from user;
索引全表扫描,把索引从头到尾扫一遍,常见于使用索引列就可以处理不需要读取数据文件的查询、可以使用索引排序或者分组的查询
ALL
全表扫描
demo
CREATE TABLE `user` ( `id` int(11) DEFAULT NULL, `name` varchar(20) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into user values(1,'shenjian');insert into user values(2,'zhangsan');insert into user values(3,'lisi');CREATE TABLE `user_ex` ( `id` int(11) DEFAULT NULL, `age` int(11) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into user_ex values(1,18);insert into user_ex values(2,20);insert into user_ex values(3,30);insert into user_ex values(4,40);insert into user_ex values(5,50);
explain select * from user,user_ex where user.id=user_ex.id;
查询的结果只的好坏顺序system > const > eq_ref > ref > range > index > ALL
CREATE TABLE `user` ( `id` int(11) DEFAULT NULL, `name` varchar(20) DEFAULT NULL, KEY `id` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into user values(1,'shenjian');insert into user values(2,'zhangsan');insert into user values(3,'lisi');CREATE TABLE `user_ex` ( `id` int(11) DEFAULT NULL, `age` int(11) DEFAULT NULL, KEY `id` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into user_ex values(1,18);insert into user_ex values(2,20);insert into user_ex values(3,30);insert into user_ex values(4,40);insert into user_ex values(5,50);
possible_key
查询过程中有可能用到的索引
key
实际使用的索引,如果为 NULL ,则没有使用索引
key_len
展示索引字段的实际长度
ref
显示该表的索引字段关联了哪个表的哪个字段
rows
估算查询所需要的记录需要读取的行数
filtered
返回的结果集行数占读取行数的百分比,越大越好
extra
展示其他重要信息
MYSQL架构
server层
概述
所有跨存储引擎的功能都是在server层来实现的;包括存储过程,触发器,视图,函数,binlog日志
其他组件
存储过程
被用户定义的 sql语句的集合
触发器
某个时间所触发的操作;一种特殊的存储过程;
视图
基于基表的一种逻辑表或者虚拟表
五大组件
示意图
子主题
连接器
管理用户对数据库的连接
用户权限管理
优化器
根据语句对sql语句中能够做优化的SQL自动做优化
优化器分类
CBO
基于优化成本来做优化(主流的数据库都是这样)
RBO
基于规则,SQL语句经过分析器之后,会有多种执行方式,程序会判断选择一个效率最高的SQL
分析器
包括词法分析,语法分析
缓存模块
mysql缓存查询到的数据永远是最新的数据,如果中件表有发生变化,则该缓存相关的数据就会被清空
缓存命中率比较低,最理想的情况下命中率最高13%
缓存工作
查询必须是完全相同的(逐字节相同)才能被认为是相同的
同样的字符串由于其他原因也可能会被认为是不一样的
缓存相关查询
指定从缓存中查询
SELECT SQL_CACHE id, name FROM customer;
前提是缓存是出于开启的状态
query_cache_type系统变量的值是ON或DEMAND,查询结果被缓存
指定不从缓存中查询
SELECT SQL_NO_CACHE id, name FROM customer;
缓存相关参数
have_query_cache
表示当前是否有使用缓存
query_cache_size
查询缓存大小
如果设置为0,则认为是禁用缓存
query_cache_limit
被缓存的查询结果最大值
默认值1MB
query_cache_min_res_unit
系统变量给查询缓存分配最小值
默认值是4KB
8.0之后就移除缓存模块
移除原因
1.缓存命中率低
2.内存空间宝贵
执行器
SQL语句真正的执行者
存储引擎
概述
主要负责数据的读取和存储,采用可替代式插件架构,支持各种数据库引擎。
概述
MySQL主要分为server层和存储引擎层
mysql元数据信息表
schemata
存储库相关信息
子主题
tables表
存储信息
库名
表名
数据库引擎类型
数据库版本
表行数
平均每行数据长度
数据长度
最大数据长度
索引数据大小
columns
字段相关信息
statistics
索引相关信息
user_privileges
用户权限数据存储
schema_privilege
库权限信息
table_privileges
表权限信息
character_sets
字符集信息
collations
mysql磁盘读写
mysql页
默认的页的大小是:16kb 这个是可以修改
操作系统的页 4kb
查询规则
1、页是innerDB磁盘存储中最小的一个单位,每次查询至少都会查询出一页数据
2、查询的时候,会把某条数据所在的页对应的数据都拿出来,然后再去拿具体的数据
目的
1、可以降低磁盘的io次数
如果一条一条的拿数据,io的次数会几何倍数的增加
特别说明
哪怕是要操作一条数据,也是需要将包含这条数据的页全部读到内存当前去进行处理,所以说,操作一条和操作一页的数据的时间差可以忽略不计,时间的消耗主要磁盘和内存之间的数据IO操作
索引查询流程
1、先从B+树的根节点开始,寻找定位索引数据拿到数据
中间,可能是经历过多个节点的读取才能最终定位到具体的索引数据在哪个叶子节点上,所以中间过程可能有多次io
2、如果这个时候刚好根据索引就能拿到全部所需要查询的数据,那么就直接返回数据
3、但是如果这个时候根据索引没有拿到全部所需要的数据,那么进行回表操作
1、拿到索引数据中的主键,根据主键去从主键索引树中拿到数据,这个过程也会有多次io
磁盘读写
磁盘在工作的时候,以恒定的速度旋转,当磁盘有读或者写的请求的时候,刺头必须移动到所请求的磁道上,等待所要读取的扇区旋转到刺头下面;然后才能做读写操作
磁盘读写操作
1.寻道时间:磁头移动到指定磁道的时间;
2.旋转时间:磁头等待指定扇区移动到磁头下方时间;
3.传输时间:读写的时间片;
由于磁盘始终是告诉旋转的,移动磁头到磁道上,需要启动,停止的操作,所以上述的操作,1的时间是最长的,2,3的时间都比较短;
磁盘的结构
一个磁盘有两次盘面
磁盘的存储大小计算公式
盘面个数X磁道个数X每个磁道的扇区个数X每个扇区储存的空间大小
日志
binlog日志(二进制日志)
binlog日志作用
主从复制;主从复制就是为了读写分离
主从复制过程
1.主机每次完成实事物提交,完成数据更新之前,都会把变更的数据记录在binlog日志中
2.从机开启一个IO线程去从主机的binlog日志中读取数据,记录到自己的中继日志中;
3.从机开启线程,解析中继日志的事件,并执行,来做数据主从复制
数据库基于时间还原数据
读写分离;读写分离的前提就是主从复制
缓存一致性;模拟成从机,拉取binlog日志异步更新到缓存中的数据;
数据多备份
binlog日志格式
5.0之前
statment模式
以mysql事件形式记录的是实际执行的sql语句
5.0之后
row模式
以MySQL事件形式记录字段前后变化的值
binlog中主要记录的都是都是事件,mysql定义了30多种事件
binlog日志配置
max_binlog_size
日志文件的大小,默认1G
expire_logs_days
日志保留事件,默认是0,永久保留
记录所有的更新操作
查询日志
记录建立的客户端连接和执行的语句。
只记录查询相关的日志
更新日志
记录更改数据的语句。不赞成使用该日志。
5.1版本之前的日志,5.1版本之后被binlog日志替代
错误日志
记录内容
记录mysql启动关闭,以及运行过程中发生的错误信息
设置
错误日志默认是关闭
慢查询日志
记录内容
记录的是查询时间超过10秒(默认值)的sql语句
设置
慢查询日志默认是关闭
中继日志
场景用在主从复制中从服务器读取到主服务器的二进制日志存储起来就是中继日志,日志格式和二进制日志格式相同,都可以使用相同的binlog解析程序解析
mysql客户端脚本和使用工具
myisampack
压缩MyISAM表以产生更小的只读表
mysql
交互式输入SQL语句或从文件以批处理模式执行它们的命令行工具
mysqlaccess
检查访问主机名、用户名和数据库组合的权限的脚本
mysqladmin
执行管理操作的客户程序,例如创建或删除数据库,重载授权表,将表刷新到硬盘上,以及重新打开日志文件
mysqlbinlog
从二进制日志读取语句的工具。在二进制日志文件中包含的执行过的语句的日志可用来帮助从崩溃中恢复
mysqlcheck
检查、修复、分析以及优化表的表维护客户程序
mysqldump
将MySQL数据库转储到一个文件(例如SQL语句或tab分隔符文本文件)的客户程序
mysqlhotcopy
当服务器在运行时,快速备份MyISAM或ISAM表的工具
mysql import
使用LOAD DATA INFILE将文本文件导入相关表的客户程序
mysqlshow
显示数据库、表、列以及索引相关信息的客户程序
perror
显示系统或MySQL错误代码含义的工具
replace
更改文件中或标准输入中的字符串的实用工具
MYSQL其他知识点
Mysql事件
ReadView
mysql异样工作场景
mysql链式复制
主从复制中,从机又是另外一台的主机;
从服务器本身也可以当做主服务器
mysql模式
性能模式
系统模式
mysql实战问题
mysql添加表字段会不会锁表
5.6之前版本
1、对原始表加写锁
2、按照原始表和执行语句的定义,重新定义一个空的临时表
3、对临时表进行添加索引(如果有
4、再将原始表中的数据逐条Copy到临时表中
5、当原始表中的所有记录都被Copy临时表后,将原始表进行删除。再将临时表命名为原始表表名
5.6版本以之后版本
1、对原始表加写锁
2、按照原始表和执行语句的定义,重新定义一个空的临时表。并申请rowlog的空间
3、拷贝原表数据到临时表,此时的表数据修改操作(增删改)都会存放在rowlog中。此时该表客户端可以进行操作的
4、原始表数据全部拷贝完成后,会将rowlog中的改动全部同步到临时表,这个过程客户端是不能操作的
5、当原始表中的所有记录都被Copy临时表,并且Copy期间客户端的所有增删改操作都同步到临时表。再将临时表命名为原始表表名
myslq添加索引会不会锁表
肯定是会的
解决方式
前期设计的时候想好设计表的索引
mysql主从同步延迟解决方案
1、延迟原因
1、从机读取主机binlog日志的线程只有一个,如果主机有大量更新操作。从机是无法快速将这些binlog读取到从机
2、从机如果有执行慢的sql,也会导致无法快速处理读取到的binlog日志。
以上两点导致主从不一致的原因
2、解决方案
1、将从机的binlog日志取消,提高从机sql执行效率
2、将主从服务器放在同一个网络下,减少网络延迟
3、具体的硬件优化
4、调参
MySQL调优
调优方向
1.sql调优
查询尽量覆盖到索引
1.在经常当做查询条件的字段上设置索引
未覆盖到索引情况
1.反向查询,!=,<> ,not in
2.前模糊查询
3.查询语句中在索引字段上做函数操作
4.关联字段虽然都是索引字段,如果数据类型不一致,走不了索引
5.where后面不带有效查询条件
6.建立联合索引,查询的时候不满足最左原则;
7.查询中对索引字段做运算操作
索引建立原则
索引长度限制
在字符串类型的字段上建立索引,需要指定索引的长度,一般10个就够了;
索引建立字段选取
1、必须要有主键索引
2、在经常做为查询条件,排序,分组,关联的字段上建立索引;
表的关联字段建立索引的时候确保两个表的数据类型是一样的
3、索引建立的字段应该是区分度非常高的字段;
索引数量
1、索引的数量不是越多越好,多了会增加索引构建维护成本;
2、不再使用或者使用非常少的索引需要删除索引
3、新建单列索引的时候,考虑和其他字段一起建立联合索引,减少索引个数
查询需要覆盖到索引
2.调整库,表结构
1、表的字段类型,字段长度合理
2、创建高性能的索引
3.系统配置优化
相关操作命令
查询服务器默认缓存区大小
mysqld --verbose --help命令生成所有mysqld选项和可配置变量的列表
SHOW VARIABLES;
查看当前运行的mysql服务器实际运行的值
SHOW STATUS;
运行服务器的统计和状态指标
系统变量和状态信息
mysqladmin variables
mysqladmin extended-status
优化核心参数
key_buffer_size
MYISAM引擎下面的索引缓存所占内存大小
table_cache
mysql同时打开表的数量
read_rnd_buffer_size
线程的缓冲区,注意不是线程的栈内存;
对GROUP BY或ORDER BY读取数据的时候会暂存读取的数据,
max_connections
最大连接数
默认151
可以适当设置大一些
thread_cache_size
线程池线程大小
查询相关
排序缓冲区
sort_buffer_size
默认为2MB
为排序或者分组的线程设置的缓冲区大小,可以提高排序分组的效率
关联查询缓冲区
join_buffer_size
默认为8Mb
为每个链接做联合查询操作的时候缓冲区的大小,提高关联查询效率
表的缓冲区
read_buffer_size
默认64k
每个线程扫描每一个表的锁分配的缓冲区大小
innerDB相关
innodb_buffer_pool_size
innerDB引擎下表和索引的最大缓存
可以缓存表数据还有索引数据
innodb_flush_log_at_trx_commit
innerDB日志刷盘策略
innodb_log_buffer_size
innerDB的失误日志缓冲区
4.硬件优化
数据分散到多个磁盘
提高搜索效率
将数据分布在多个磁盘上,提高单位时间内可以并行搜索磁盘次数
单个磁盘每秒大约1000次搜索
提高读写吞吐量
提高cpu频率
提高cpu缓存大小
概述:以上四个方向,效果最好的是SQL调优,以此往下效果越来越差,成本越来越高
sql优化实战
limit优化
demo
CREATE TABLE `test_news` (`id` int(11) NOT NULL AUTO_INCREMENT,`title` varchar(100) DEFAULT NULL COMMENT '文章标题',`content` longtext COMMENT '文章内容',`channel` int(11) DEFAULT NULL COMMENT '文章频道',`status` int(11) DEFAULT NULL COMMENT '状态,1正常,0关闭',`create_time` datetime DEFAULT NULL COMMENT '文章发布时间',PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=513 DEFAULT CHARSET=utf8;
原始查询方式
select* from test_news limit 10000,10;
查询每页是10条,查询低10000页的数据;这种查询完全就是没有用到索引;需要扫描到10000条数据,再开始查询
优化方式1
select* from test_news where id >( select id from test_news limit10000,1) limit 10;
解析
1.子查询select id from test_news where limit10000,1;只查询一个id,根据主键就能查询到,不用回表查询;
2.拿到查询出来的id,获取到后面10个id,再用这10个id直接可以直接回表拿到所有的数据;
优化方式2
select* from test_news a join ( select id from test_news limit10000,10) b on a.id = b.id ;
解析
相当于自关联的方式获取到相应的id;
再通过id回表获取所有的数据
分组取前几条
demo
CREATE TABLE `test_news` (`id` int(11) NOT NULL AUTO_INCREMENT,`title` varchar(100) DEFAULT NULL COMMENT '文章标题',`content` longtext COMMENT '文章内容',`channel` int(11) DEFAULT NULL COMMENT '文章频道',`status` int(11) DEFAULT NULL COMMENT '状态,1正常,0关闭',`create_time` datetime DEFAULT NULL COMMENT '文章发布时间',PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=513 DEFAULT CHARSET=utf8;
SELECT a.id, a.title, a.channel,a.create_time FROMtest_news AS a LEFT JOIN test_news AS b ON a.channel = b.channelAND a. STATUS = b. STATUS AND a.id < b.id WHEREa. STATUS = 1 GROUP BY a.channel, a.id HAVINGcount(1) < 5 ORDER BY a.channel ASC, a.id DESC;
having
demo
sELECT CLASS,SUM(TOTAL_SCORES) FROM student_score GROUP BY CLASS HAVING SUM(TOTAL_SCORES)>200;
SQL自动优化
SQLAdvisor系统
mysql主从复制
实现原理
mysql的主从复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新、删除等等)
并且主服务器开启了二进制日志
实现过程
复制过程中有三个线程在执行任务:一个线程在主服务器,负责将二进制日志的内容发送到从服务器(binlog dump线程);
主从复制开始;从服务器上会创建一个io线程,连接主线程并记录二进制日志中的数据到中继日志;中间需要识别binlog dump线程 以获取二进制日志
从服务器会开启一个sql线程(第三个线程),从中继日志中读取日志,并执行日志,以同步数据;
主从复制条件
1、主服务器开启了二进制日志
2、主从服务器的mysql版本之间是可以相互兼容
由于历史原因:
1、二进制日志格式不一样
2、字符集,函数,时区处理不一样
3、主服务器上设置了相应的从服务器
存在问题
1、主从数据库数据一致性问题
假设从库插入的时候异常
2、主从复制的延迟
主从复制解决问题
降低数据库读写压力
写操作比较耗时,读操作时间很短;
读写分离,解决了数据库写入时候,影响查询的效率;通过读写分离提供系统的高并发
SQL语句
关联语句
left join ,inner join ,right join 的区别
left join 做关联,以左表为主表,去右表当中匹配,如果左表中的数据未能匹配到右表中的数据,结果集中依旧会将左表的数据展示出来,未能匹配到的字段全部为空
right join 做关联,以右表为主表,去左表当中匹配,如果右表中的数据未能匹配到左表中的数据,结果集中依旧会将右表的数据展示出来,未能匹配到的字段全部为空
inner join 做关联,最明显就是左右两个表中只有关联上的数据才会展示出来,没有关联上的数据不会展示出来;
left join ,inner join ,right join full join 演示
原始数据
子主题
.inner join
子主题
.left join
子主题
.right join
子主题
full join
子主题
mysql工具
SQL 审核 web 工具
Yearning
项目地址
https://github.com/cookieY/Yearning
项目简介
https://guide.yearning.io/
概述
一款可满足大部分公司 SQL 审核需求的 web 端可视化 SQL 审核平台。在实现常规的 sql 审核功能外还添加了诸如数据查询等一系列便捷的功能
主要功能:
自动化SQL语句审核,可对SQL进行自动检测并执行。
DDL/DML语句执行后自动生成回滚语句
审核/查询 审计功能
支持LDAP登录/钉钉及邮件消息推送
支持自定义审核工作流
支持细粒度权限分配
优点
颜值高,使用流畅。
部分审核规则可自定义
部署简单,依赖项少
功能丰富,既能审核执行SQL又能查询
作者在持续维护,有社区支持
Archery
项目地址
https://github.com/hhyo/Archery
使用文档
https://archerydms.com/
概述
Archery 定位于 SQL 审核查询平台,旨在提升 DBA 的工作效率,支持多数据库的 SQL 上线和查询,同时支持丰富的 MySQL 运维功能,所有功能都兼容手机端操作
主要功能
集成 SQL 查询、审核、执行、备份
权限区分明确,审核执行分离
SQL 工单自动审批、高危语句驳回
快速上线其他实例
慢日志管理、SQL 优化等运维功能
支持会话管理及参数配置
可集成其他工具插件
优点
除 MySQL 外,还支持多种主流数据库
功能更加丰富,真正做到一平台多用
具备 SQL 优化、慢日志管理等运维功能,对 DBA 更加友好
基于 Python 及 Django,利于二次开发改造
Yearning与Archery比较
Yearning 只适用于 MySQL ,部署简单,功能清晰明了,上手容易
Archery 支持多种数据库,部署稍微复杂些,功能更加丰富,支持诸多高级运维功能
binlog 解析工具
概述
1、binlog 解析工具在 GitHub 上可以搜索到许多
2、利用 binlog 解析工具我们可以清楚看到数据库执行过的历史内容,并且可以得到反向内容可用于回滚
MyFlash
项目地址
https://github.com/Meituan-Dianping/MyFlash
概述
MyFlash 是由美团点评公司技术工程部开发维护的一个回滚 DML 操作的工具
主要功能
解析 binlog ,回滚各类 DML 语句
提供原生的基于库、表、SQL类型、位置、时间等多种过滤方式
支持 MySQL 多个版本,支持 GTID 格式
binlog2sql
项目地址
https://github.com/danfengcao/binlog2sql
概述
基于 Python 开发,源码入门简单
主要功能
可以解析出原始 SQL 、回滚 SQL 、去除主键的 INSERT SQL 等
数据快速回滚(闪回)
主从切换后新 master 丢数据的修复
Percona Toolkit 工具包
官网地址
https://www.percona.com/doc/percona-toolkit/3.0/index.html#
概述
Percona Toolkit 工具包是一组高级的管理 MySQL 的工具包集
主要功能包括检查主从复制的数据一致性、检查重复索引、归档数据、在线DDL等
几款常用工具的功能
pt-archiver:主要用于清理、归档历史数据
pt-duplicate-key-checker:列出并删除重复的索引和外键
pt-kill:杀掉符合条件的数据库连接
pt-online-schema-change:在线修改表结构,常用于大表 DDL
pt-query-digest:分析 MySQL 日志,并产生报告,常用于慢日志分析
pt-table-checksum:校验主从复制一致性
pg
非关系型数据库
mongonDB
基础概念
关系型数据库的一行数据,在mongondb中标示就是需要一个json对象来标示
存储的数据类型:
1.文档
{"site":"www.runoob.com"}
2.集合
就是把不同的文档类型的数据集中在一起就是集合
{"site":"www.baidu.com"}
{"site":"www.google.com","name":"Google"}
{"site":"www.runoob.com","name":"菜鸟教程","num":5}
基本数据类型
String
字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的
Integer
整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位
Boolean
布尔值。用于存储布尔值(真/假)
Double
双精度浮点值。用于存储浮点值。
Min/Max keys
将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比
Array
用于将数组或列表或多个值存储为一个键。
Timestamp
时间戳。记录文档修改或添加的具体时间。
Object
用于内嵌文档。
Null
用于创建空值。
Symbol
符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。
Date
日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息
Object ID
对象 ID。用于创建文档的 ID。
Binary Data
二进制数据。用于存储二进制数据。
Code
代码类型。用于在文档中存储 JavaScript 代码。
Regular expression
正则表达式类型。用于存储正则表达式。
对比mysql
关系型数据库的两条数据
id user_name email age city
1 tom tom@qq.com 23 wuhan
2 jerry jerry@qq.com 24 beijing
关系型数据库的数据转成mongondb数据库的数据格式形式如下
{
"_id":ObjectId("13123123"),
"age":23,
"city":wuhan,
"email":tom@qq.com
"age":23
}
{
"_id":ObjectId("42342"),
"age":24,
"city":beijing,
"email":jerry@qq.com
"age":24
}
字段还可以再分割
事务
只存在单个集合的事务,不存在多个集合一起的事务;
多个集合的事物处理,就需要自己手动做一个事物的回滚;
并发性,原子性,隔离性
原子性
mongonDB在任意时刻,对某一个集合的操作只能是一个写操作或者是多个读操作
并发性
主要就是内存中的操作优先于在硬盘上的操作
锁策略:1.数据库知道那个数据(文档)是在内存当中的,也就相应的知道了那些数据是在硬盘上的,机制就是让不在内存中的操作,让步于其他在内存中的操作;
也就是当同时又两个操作,一个是数据在内存中,一个是数据在硬盘上, 这个时候会优先去执行数据在内存中的那个,直到数据加载进内存中的,才有可能去执行这个操作,原因就是操作执行数据,先需要从硬盘上加载数据,读取到内存之中。在内存中操作数据。
2.就是写操作(增,删,改)比较耗时,查询操作时间比较短,当有写操作占用了锁,并且这个时候检测到有一个都操作等待写操作释放锁,这个时候写操作就会直接释放锁,让读操作先执行。
mongonDB对单个文档内存的最大限制16M
搜索
ES搜索
数据类型
字符串
keyword
字段不会被分词
text
字段会被分词
系统参数说明
number_of_replicas 是数据备份数,如果只有一台机器,设置为0
number_of_shards 是数据分片数,默认为5,有时候设置为3
ES查询
ES查询语句
term
keyword
条件不会被分词,表中字段也不会被分词;
需要条件和表中字段数据完全匹配才会有结果
text
条件不会被分词,表中字段会被分词;
查询条件必须和是表中字段分词后的某一个字段相同才会获取到数据;
match
keyword
条件会被分词,表中字段不会被分词
条件和表中字段完全一致,才会查询到数据
text
条件会被分词,表中字段也会被分词
条件的分词和表中字段的分词有匹配的就会被查询出来
match_phrase
短语查询-词组查询
GET /test/student/_search
{
"query": {
"match_phrase": {
"description": "He is"
}
}
}
{
"query": {
"match_phrase": {
"description": "He is"
}
}
}
会去匹配出包含He is的数据,并且顺序要是一致,且中间没有其他词汇
keyword
条件会被分词,表中字段不会被分词
条件和表中数据一致,才会查询出数据
text
条件会被分词,表中字段也会被分词
必须要条件的词组顺序和表中字段该词组顺序一致才会查询出来
query_string
子主题
multi_match
DSL语句
update语句
条件更新
POST idx_hippo_sku_statistics_index/_update_by_query
{
"script": {
"source": "ctx._source['distributionChannel']=1"
},
"query": {
"term":{
"cityId":138
}
}
}
{
"script": {
"source": "ctx._source['distributionChannel']=1"
},
"query": {
"term":{
"cityId":138
}
}
}
全部覆盖更新
PUT /idx_product-fat/doc/10006145
{
"minPrice": 1,
"price": 30
}
{
"minPrice": 1,
"price": 30
}
根据id更新某些字段
POST idx_product-fat/doc/100101/_update
{
"doc" : {
"minPrice" : 3,
"price" : 2
}
}
{
"doc" : {
"minPrice" : 3,
"price" : 2
}
}
delete语句
删除某一个索引
DELETE idx_hippo_sku_statistics_index
根据id删除某条数据
Delete /idx_product-fat/doc/10006306
条件删除
POST idx_hippo_sku_statistics_index/_delete_by_query
{
"query": {
"term": {
"skuId": {
"value": 10005956
}
}
}
}
POST idx_hippo_sku_statistics_index/_delete_by_query
{
"query": {
"term": {
"skuId": {
"value": 10005956
}
}
}
}
insert语句
新增数据-不指定id
POST idx_hippo_shop_sku_index/shop_sku_index/
{
"title":"java架构师",
"salary_min":30000,
"city":"上海",
"name":"美团",
"publish_date":"2017-4-16",
"comments":20
}
{
"title":"java架构师",
"salary_min":30000,
"city":"上海",
"name":"美团",
"publish_date":"2017-4-16",
"comments":20
}
新增数据-指定id
POST idx_hippo_shop_sku_index/shop_sku_index/3
{
"title":"java架构师",
"salary_min":30000,
"city":"上海",
"name":"美团",
"publish_date":"2017-4-16",
"comments":20
}
{
"title":"java架构师",
"salary_min":30000,
"city":"上海",
"name":"美团",
"publish_date":"2017-4-16",
"comments":20
}
select语句
条件查询
范围+in查询
GET /idx_hippo_sku_statistics_index/sku_statistics_index/_search
{
"from" : 0,
"size" : 100,
"query" : {
"bool" : {
"must":[
{
"range" : {
"salePrice" : {
"from" : 1.9,
"to" : 2.1,
"include_lower": true,
"include_upper": true
}
}
}
,
{
"bool": {
"must": {
"terms": {
"cityId": [136]
}
}
}
}
]
}
},
"sort": [
{
"salePrice": "asc"
}
],
"_source": ["productName","salePrice"]
}
{
"from" : 0,
"size" : 100,
"query" : {
"bool" : {
"must":[
{
"range" : {
"salePrice" : {
"from" : 1.9,
"to" : 2.1,
"include_lower": true,
"include_upper": true
}
}
}
,
{
"bool": {
"must": {
"terms": {
"cityId": [136]
}
}
}
}
]
}
},
"sort": [
{
"salePrice": "asc"
}
],
"_source": ["productName","salePrice"]
}
排序
GET /idx_hippo_sku_statistics_index/sku_statistics_index/_search
{
"query": {
"term": {
"cityId": {
"value": 136
}
}
},
"sort": [ { "salePrice": "asc" }
]
}
{
"query": {
"term": {
"cityId": {
"value": 136
}
}
},
"sort": [ { "salePrice": "asc" }
]
}
按照某个字段去重查询
DSL语句
POST idx_hippo_shop_sku_index/shop_sku_index/_search
{
"query" : {
"bool" : {
"filter" : [
{
"term" : {
"shopId" : {
"value" : 10000051
}
}
}
]
}
},
"collapse":{
"field":"categoryId1"
}
,
"_source": ["categoryId1"]
}
{
"query" : {
"bool" : {
"filter" : [
{
"term" : {
"shopId" : {
"value" : 10000051
}
}
}
]
}
},
"collapse":{
"field":"categoryId1"
}
,
"_source": ["categoryId1"]
}
java代码
CollapseBuilder collapseBuilder = new CollapseBuilder(EsQueryConstants.THIRD_CATEGORY_ID);
sourceBuilder.collapse(collapseBuilder);
EsQueryConstants.THIRD_CATEGORY_ID 需要被去重的字段的对应的字符串;
sourceBuilder.collapse(collapseBuilder);
EsQueryConstants.THIRD_CATEGORY_ID 需要被去重的字段的对应的字符串;
范围+精确+排序+固定字段返回
GET /idx_hippo_sku_statistics_index/sku_statistics_index/_search
{
"from" : 0,
"size" : 100,
"query" : {
"bool" : {
"must":[
{
"range" : {
"salePrice" : {
"from" : 1.9,
"to" : 2.1,
"include_lower": true,
"include_upper": true
}
}
}
,
{
"bool": {
"must": {
"term": {
"cityId": 136
}
}
}
}
]
}
},
"sort": [
{
"salePrice": "asc"
}
],
"_source": ["productName","salePrice"]
}
{
"from" : 0,
"size" : 100,
"query" : {
"bool" : {
"must":[
{
"range" : {
"salePrice" : {
"from" : 1.9,
"to" : 2.1,
"include_lower": true,
"include_upper": true
}
}
}
,
{
"bool": {
"must": {
"term": {
"cityId": 136
}
}
}
}
]
}
},
"sort": [
{
"salePrice": "asc"
}
],
"_source": ["productName","salePrice"]
}
范围查询+精确匹配+排序
GET /idx_hippo_sku_statistics_index/sku_statistics_index/_search
{
"from" : 0,
"size" : 100,
"query" : {
"bool" : {
"must":[
{
"range" : {
"salePrice" : {
"from" : 1.9,
"to" : 2.1,
"include_lower": true,
"include_upper": true
}
}
}
,
{
"bool": {
"must": {
"term": {
"cityId": 136
}
}
}
}
]
}
},
"sort": [
{
"salePrice": "asc"
}
]
}
{
"from" : 0,
"size" : 100,
"query" : {
"bool" : {
"must":[
{
"range" : {
"salePrice" : {
"from" : 1.9,
"to" : 2.1,
"include_lower": true,
"include_upper": true
}
}
}
,
{
"bool": {
"must": {
"term": {
"cityId": 136
}
}
}
}
]
}
},
"sort": [
{
"salePrice": "asc"
}
]
}
查询所有数据
方式1
GET /idx_hippo_shop_sku_index/shop_sku_index
{
"query":{"match_all":{}}
}
{
"query":{"match_all":{}}
}
方式2
GET /idx_hippo_shop_sku_index/_search
多条件查询
DEMO1
GET /idx_hippo_shop_sku_index/shop_sku_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"city": "上海"
}
},
{
"match": {
"name": "美团"
}
}
]
}
}
}
{
"query": {
"bool": {
"must": [
{
"match": {
"city": "上海"
}
},
{
"match": {
"name": "美团"
}
}
]
}
}
}
DEMO2
GET /idx_hippo_sku_statistics_index/sku_statistics_index/_search
{
"from" : 0,
"size" : 100,
"query" : {
"bool" : {
"filter" : [
{
"term" : {
"cityId" : {
"value" : 136,
"boost" : 1.0
}
}
},
{
"term" : {
"saleArea" : {
"value" : 1,
"boost" : 1.0
}
}
},
{
"terms" : {
"skuCode" : [
"2013001000146186085"
],
"boost" : 1.0
}
}
],
"disable_coord" : false,
"adjust_pure_negative" : true,
"boost" : 1.0
}
}
}
{
"from" : 0,
"size" : 100,
"query" : {
"bool" : {
"filter" : [
{
"term" : {
"cityId" : {
"value" : 136,
"boost" : 1.0
}
}
},
{
"term" : {
"saleArea" : {
"value" : 1,
"boost" : 1.0
}
}
},
{
"terms" : {
"skuCode" : [
"2013001000146186085"
],
"boost" : 1.0
}
}
],
"disable_coord" : false,
"adjust_pure_negative" : true,
"boost" : 1.0
}
}
}
单条件查询
GET /idx_hippo_shop_sku_index/shop_sku_index/_search
{
"query":{
"match":{
"name":"美团"
}
}
}
{
"query":{
"match":{
"name":"美团"
}
}
}
GET /idx_hippo_sku_statistics_index/sku_statistics_index/_search
{
"query": {
"term": {
"cityId": {
"value": 136
}
}
}
}
{
"query": {
"term": {
"cityId": {
"value": 136
}
}
}
}
根据id查询
GET /idx_hippo_shop_sku_index/shop_sku_index/1
查询某个字段存在
GET index/type/_search
{
"query": {
"bool": {
"must": {
"exists": {
"field": "字段名"
}
}
}
}
}
{
"query": {
"bool": {
"must": {
"exists": {
"field": "字段名"
}
}
}
}
}
查询某个字段不存在
方式1
GET index/type/_search
{
"query": {
"bool": {
"must_not": {
"exists": {
"field": "字段名"
}
}
}
}
}
{
"query": {
"bool": {
"must_not": {
"exists": {
"field": "字段名"
}
}
}
}
}
方式2
GET /my_index/posts/_search
{
"query" : {
"constant_score" : {
"filter": {
"missing" : { "field" : "tags" }
}
}
}
}
{
"query" : {
"constant_score" : {
"filter": {
"missing" : { "field" : "tags" }
}
}
}
}
or条件查询
DSL语句
POST /idx_hippo_shop_sku_index_fat/shop_sku_index/_search
{
"from":0,
"size":1,
"query" : {
"bool" : {
"should" : [
{
"bool" : {
"must" : [
{
"terms" : {
"categoryId1" : [
30,
79
]
}
}
]
}
},
{
"bool" : {
"must" : [
{
"terms" : {
"categoryId2" : [
30,
79
]
}
}
]
}
},
{
"bool" : {
"must" : [
{
"terms" : {
"categoryId3" : [
30,
79
]
}
}
]
}
}
]
}
},
"_source" : {
"includes" : [
"skuId"
],
"excludes" : [ ]
}
}
{
"from":0,
"size":1,
"query" : {
"bool" : {
"should" : [
{
"bool" : {
"must" : [
{
"terms" : {
"categoryId1" : [
30,
79
]
}
}
]
}
},
{
"bool" : {
"must" : [
{
"terms" : {
"categoryId2" : [
30,
79
]
}
}
]
}
},
{
"bool" : {
"must" : [
{
"terms" : {
"categoryId3" : [
30,
79
]
}
}
]
}
}
]
}
},
"_source" : {
"includes" : [
"skuId"
],
"excludes" : [ ]
}
}
对应java代码
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder shouldBuilder = QueryBuilders.boolQuery();
//or 条件
BoolQueryBuilder categoryId1BoolBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termsQuery(EsQueryConstants.FIRST_CATEGORY_ID,categoryIdList));
BoolQueryBuilder categoryId2BoolBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termsQuery(EsQueryConstants.SECOND_CATEGORY_ID,categoryIdList));
BoolQueryBuilder categoryId3BoolBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termsQuery(EsQueryConstants.THIRD_CATEGORY_ID,categoryIdList));
shouldBuilder.should(categoryId1BoolBuilder).should(categoryId2BoolBuilder).should(categoryId3BoolBuilder);
sourceBuilder.query(shouldBuilder);
BoolQueryBuilder shouldBuilder = QueryBuilders.boolQuery();
//or 条件
BoolQueryBuilder categoryId1BoolBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termsQuery(EsQueryConstants.FIRST_CATEGORY_ID,categoryIdList));
BoolQueryBuilder categoryId2BoolBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termsQuery(EsQueryConstants.SECOND_CATEGORY_ID,categoryIdList));
BoolQueryBuilder categoryId3BoolBuilder = QueryBuilders.boolQuery().must(QueryBuilders.termsQuery(EsQueryConstants.THIRD_CATEGORY_ID,categoryIdList));
shouldBuilder.should(categoryId1BoolBuilder).should(categoryId2BoolBuilder).should(categoryId3BoolBuilder);
sourceBuilder.query(shouldBuilder);
嵌套条件
GET /my-index-000001/_search?from=40&size=20
{
"query": {
"term": {
"user.id": "kimchy"
}
}
}
{
"query": {
"term": {
"user.id": "kimchy"
}
}
}
聚合查询
去重查询
DSL语句
POST /idx_hippo_shop_sku_index_fat/shop_sku_index/_search
{
"from":0,
"size":1,
"query" : {
"bool" : {
"should" : [
{
"bool" : {
"must" : [
{
"terms" : {
"categoryId1" : [
30,
79
]
}
}
]
}
}
]
}
},
"_source" : {
"includes" : [
"skuId"
],
"excludes" : [ ]
},
"aggregations" : {
"skuIdGroup" : {
"terms" : {
"field" : "skuId",
"size" : 1000,
"min_doc_count" : 1,
"shard_min_doc_count" : 0,
"show_term_doc_count_error" : false,
"order" : [
{
"_count" : "desc"
},
{
"_term" : "asc"
}
]
}
}
}
}
{
"from":0,
"size":1,
"query" : {
"bool" : {
"should" : [
{
"bool" : {
"must" : [
{
"terms" : {
"categoryId1" : [
30,
79
]
}
}
]
}
}
]
}
},
"_source" : {
"includes" : [
"skuId"
],
"excludes" : [ ]
},
"aggregations" : {
"skuIdGroup" : {
"terms" : {
"field" : "skuId",
"size" : 1000,
"min_doc_count" : 1,
"shard_min_doc_count" : 0,
"show_term_doc_count_error" : false,
"order" : [
{
"_count" : "desc"
},
{
"_term" : "asc"
}
]
}
}
}
}
JAVA代码
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
TermsAggregationBuilder agg = AggregationBuilders.terms(StringConstant.SKU_ID_GROUP).field(EsQueryConstants.SKU_ID).size(1000);
sourceBuilder.aggregation(agg);
指定EsQueryConstants.SKU_ID 对应的字段分组,且查询后结果集的别名是StringConstant.SKU_ID_GROUP
去重后条数结构集的获取;
SearchResponse responseResult = restHighLevelClient.search(searchRequest);
//解析聚合函数的结果集
Aggregations aggregations = responseResult.getAggregations();
Map<String, Aggregation> asMap = aggregations.getAsMap();
ParsedLongTerms aggregation = (ParsedLongTerms)asMap.get(StringConstant.SKU_ID_GROUP);
List<? extends Terms.Bucket> buckets = aggregation.getBuckets();
Integer count = buckets.size();
TermsAggregationBuilder agg = AggregationBuilders.terms(StringConstant.SKU_ID_GROUP).field(EsQueryConstants.SKU_ID).size(1000);
sourceBuilder.aggregation(agg);
指定EsQueryConstants.SKU_ID 对应的字段分组,且查询后结果集的别名是StringConstant.SKU_ID_GROUP
去重后条数结构集的获取;
SearchResponse responseResult = restHighLevelClient.search(searchRequest);
//解析聚合函数的结果集
Aggregations aggregations = responseResult.getAggregations();
Map<String, Aggregation> asMap = aggregations.getAsMap();
ParsedLongTerms aggregation = (ParsedLongTerms)asMap.get(StringConstant.SKU_ID_GROUP);
List<? extends Terms.Bucket> buckets = aggregation.getBuckets();
Integer count = buckets.size();
system-select语句
查询索引下面文档数量
GET my_index/_count
获取索引字段信息
GET /idx_hippo_sku_statistics_index
DDL语句
创建索引
PUT idx_hippo_shop_sku_index
{
"mappings": {
"shop_sku_index": {
"properties": {
"text": {
"id": "string",
"poolId": "long",
"shopId":"long",
"skuId":"long",
"productId":"long",
"price":"string",
"categoryId1":"long",
"categoryId2":"long",
"categoryId3":"long",
"mainImage":"string",
"headImages":"string",
"title":"string",
"titleLetter":"string",
"standard":"string",
"skuCode":"string",
"dimensionValues":"string",
"stock":"string",
"salesNum":"string"
}
}
}
}
}
{
"mappings": {
"shop_sku_index": {
"properties": {
"text": {
"id": "string",
"poolId": "long",
"shopId":"long",
"skuId":"long",
"productId":"long",
"price":"string",
"categoryId1":"long",
"categoryId2":"long",
"categoryId3":"long",
"mainImage":"string",
"headImages":"string",
"title":"string",
"titleLetter":"string",
"standard":"string",
"skuCode":"string",
"dimensionValues":"string",
"stock":"string",
"salesNum":"string"
}
}
}
}
}
添加字段
PUT idx_hippo_shop_sku_index/_mapping/shop_sku_index
{
"properties": {
"poolId": {
"type": "long"
},
"productId": {
"type": "long"
}
}
}
{
"properties": {
"poolId": {
"type": "long"
},
"productId": {
"type": "long"
}
}
}
语法解析
must与filter区别
分词查询:
//组合查询对象,//如搜索条件为“小米手机”这里must会分词为“小米”和“手机”这两个词是or关系
//加operator(Operator.AND)可以把or改为and关系
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("name",searchmap.get("keywords")).operator(Operator.AND));
//组合查询对象,//如搜索条件为“小米手机”这里must会分词为“小米”和“手机”这两个词是or关系
//加operator(Operator.AND)可以把or改为and关系
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("name",searchmap.get("keywords")).operator(Operator.AND));
//组合查询对象
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//filter指不分词查询
boolQueryBuilder.filter(QueryBuilders.termQuery("brandName",searchmap.get("brand")));
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//filter指不分词查询
boolQueryBuilder.filter(QueryBuilders.termQuery("brandName",searchmap.get("brand")));
不分词查询:must和 filter 的效果一样;
ES分析器
分析器种类
标准分析器
适合字母语言
简单分析器
使用了将所有大写转小写的分词器
空白分析器
按照空白来分割
停用分析器
能够过滤停用词
关键词分析器
字段不做分词
模拟分析器
允许指定分词切分模式
语言分析器
按照特定语言分词
雪球分析器
子主题
分词器
过滤器
倒排索引
子主题
倒排索引历史来源
实际应用中需要根据属性的值来查找记录
倒排索引概念
索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引
正排索引特点
根据主键id获取数据
相关基础概念
Term
字段分词后的单个词语,单词
Term Dictionary
对当前分词字段上所有的Term做排序,构建成B+树数据结构;
B+树
Term index
针对Term Dictionary做前缀提取,通过Term Index可以快速定位到Term Dictionary中某一个term的具体存储位置;
字典树
Posting List
存储符合某个term(分词)的所有文档id;
倒排索引数据结构示意图
整体示意图
字典树/前缀树示意图
ES技术概念
index
类似mysqlDB
type(ES6.0之后被废弃,es7中完全删除)
类似mysql的表
document
类似于mysql的行
field(可以再进行拆分成更小的字段)
类似mysql的clounm
ES工作原理
ES分词
内置分词器
主要是对字母文字有比较好的分词效果,很多内置的分词器不支持中文分词,而且支持的分词器分出来的词都是会被分掉的
中文分词器:
IK分词器
ik_max_word
做最细粒度分词,拆分出更多的词语;
ik_smart
做最粗粒度的分词,已经分出来的词不再会被其他词占有;
修改ES 的配置文件ES 目录/plugins/ik/config/ik/IKAnalyzer.cfg.xml
1.可停掉想停止的分词;
2.可以拓展自己想要的分词;
3.可以把远程的停止词典作为自己的停止词典;
4.可以把远程的拓展词典当做自己的拓展词典;
1.可停掉想停止的分词;
2.可以拓展自己想要的分词;
3.可以把远程的停止词典作为自己的停止词典;
4.可以把远程的拓展词典当做自己的拓展词典;
缓存
redis
redis性能分析
单线程工作
redis对内存操作,性能的瓶颈点不在对内存的io;而是对网络的io
Redis的多线程部分只是用来处理网络数据的读写和协议解析。在做某些删除操作的时候开启异步线程去做删除
6.0版本之后可以多线程的io的,但是真正处理请求的线程依旧是一个
磁盘与内存IO差别
磁盘IO的时间是毫秒级别;
内存IO的时间是纳秒级别;
内存的io和磁盘的io相差了2,3个数量级
redis简述
Redis是一个开源的使用ANSI C语言编写的、支持网络、 基于内存的、支持持久化的日志型、Key-Value数据库
redis64位版本与32位版本的区别
决定redis的服务器性能以及可维护性取决于对 键的设计与构造
在搭建服务器的时候如果我们使用的是32位的版本那么对应的键就会比64位的键储存长度小。
64位版本的redis数据读取效率都比32位的强。所以能使用64位就使用64位
实战
缓存相关问题
缓存穿透
概述
大量的请求在redis缓存中没有拿到数据,直接请求数据库,数据库也没有拿到数据;大量的请求直接怼到了数据库,给数据库造成了巨大压力
分支主题
解决方式
1.Redis缓存从数据库也没有查询到的空数据
分支主题
2.在业务系统和缓存之间使用布隆过滤器,直接过滤掉一些缓存以及数据库都没有的请求,直接返回null
分支主题
布隆过滤器,guav提供的。
缓存击穿
概述
Redis缓存的是失效时间到了或者是Redis服务挂了,同一时间大量的请求过来,就会访问数据库,从数据库里面获取数据,这样对数据库的压力太大了
分支主题
解决方式
在Redis和数据库之间再做一层中间缓存;
通过定时job,给快要失效的缓存数据做一个再次续期的操作;
redis和数据库之前的代码中自己手动做限流措施
缓存雪崩
概述
大量的缓存数据在同一时间过期,引起大量的数据直接访问数据库,引起数据压力过大甚至宕机
解决方式
错开缓存的过期时间
搭建高可用redis集群
做好熔断操作
缓存击穿缓存穿透区别
缓存穿透,就是访问了根本就不存在的数据;缓存击穿,缓存中不存在,但是数据库存在;
缓存数据一致性
双更模式
做两次更新,一次更新redis,一次更新数据库。
双更模式不管是redis先更新,还是数据库先更新,如果只要其中一个发生了问题,那么就会导致数据不一致;
双更模式下,数据不一致的概率很大;
删除模式
删除模式,就是删除数据库的缓存,然后后续查询的数据来覆盖缓存;
先删除缓存
A请求删除了缓存但是还未更新数据库,B请求过来缓存击穿请求得到老的数据,然后再给缓存赋值,缓存的是老数据;A请求再更新数据库,导致数据不一致
后删除缓存
A请求获取数据库的旧值,B请求更新数据库,删除了缓存,A请求才去将旧值赋值给缓存,导致数据不一致;
延时双删
操作步骤
1、删除一次缓存
为了删除旧缓存
2、更新数据库
3、间隔一定时间再删除缓存
延迟的目的:为等待数据库更新完成,2、为了让在中间其他线程在之前的查询到的旧数据写入缓存的,在最后再把缓存数据清除
子主题
子主题
延时双删依旧无法保证数据一致性,只能降低脏数据出现的概率;需要做额外的对数据库更新的一个限制操作,就是对数据的更新操作不能太过频繁;
以上的所有方式都不能完全解决这个问题;
改进版本
使用分布式锁,将缓存数据更新和数据库更新设置成同步方法
数据倾斜问题
数据倾斜解释
在redis集群模式下,数据会按照一定的分布规则分散到不同的实例上;如果由于业务数据特殊性,按照指定的分布规则,可能导致不同的实例上数据分布不均匀
数据倾斜分类
数据量倾斜
在集群中的各个实例上数据类大小不一致
示意图
子主题
数据访问倾斜
对集群中某些实例的访问频率大大超过了其他实例
示意图
子主题
数据量倾斜
原因
存在大key
大key导致占据的缓存空间过大,实际上可能这些大key并没有多少;
解决方式
将大key拆分成其他缓存
业务层面避免大key创建
solt分配不均
某些实例分配到的槽位到,相应的得到数据量也会多
解决方式
初始分配的时候,将槽位分配均匀
如果已经造成,则可以进行实例之间的数据迁移
hashTag使用不当
在hash设置key的时候不要带上画括号
数据访问倾斜
主要原因
存在热key
解决方式
1、将热点数据复制多份,在每一个数据副本的key前面增加一个特定的前缀,让这些数据分散不同的节点上;热key取数据的时候随机从拿到某一个特定前缀,然后获取数据,这样就可以将热点数据分散请求了,这种方式只能针对读操作;
并发问题
redis本身执行读写操作命令式单线程执行的,但是从调用redis的客户端来说,使用的时候会存在线程安全问题
比如说:扣库存操作,读取库存和扣减库存,不是一个原子操作
redis也有自增自减的api,将两个不是原子操作的操作合并成一个原子操作;
解决此类由客户端导致的并发问题方式
1、客户端加锁
2、客户端使用单线程执行
3、将客户端的逻辑放在lua脚本中执行
好处
lua脚本的开销非常低
缺点
Lua脚本只能保证原子性,不保证事务性,当Lua脚本遇到异常时,已经执行过的逻辑是不会回滚的
如何保证缓存中的数据都是热键数据
使用LFU算法
最小使用频率做缓存数据的淘汰
实现
记录了最近访问的时间
记录了请求访问的次数
相对其他算法
不设置任何淘汰机制
这种肯定不行
随机淘汰
这种也不行
生命周期淘汰
这种也不行,如果是一个高频使用的缓存,缓存周期到了也被淘汰了
最近最少使用算法
不能够完全说明这个问题;短期之内使用的比较少,但是整个周期内使用的次数比较多,这种也会被淘汰
最终选择LFU
使用redis做秒杀相关问题
大体步骤
1、使用redssion工具包
2、需要初始化redis中商品的库存数量,以及对应的数据库表中的库存数据
3、秒杀开始后将商品id和本次的业务名称拼接作为key的一部分;
4、做tryLock获取锁操作
5、获取成功,那么就执行相关的库存判断检验操作;
6、如果库存不够,那么就直接返回友好提示;
7、如果库存够,那么就扣减redis和数据库中的库存数据;
5、获取失败,那么就是直接返回友好提示
缓存分片hash问题
一致性hash
解决的问题
1.普通hash算法的伸缩性差,解决了分布式系统中机器的加入和退出
2.解决了数据倾斜问题
增设了很多虚拟节点
hash槽
jedis已经支持了hash槽位的计算
redis默认的缓存分片是hash槽位,槽位的最大个数是16384个。每一个主节点都会分配到相应的槽位;根据计算key的对16384取模计算,当前的key应该属于哪个槽位。就知道落在了哪个节点
分布式锁
分布式锁实现原理
1、设置任意key value;,
2、redis集群值允许一个相同的key;
3、将设置key成功作为获取分布式锁的标记,设置失败表示没有获取到锁
4、将删除key,或者 key过期作为释放分布式锁的标记
5、最终都是redis调用lua脚本执行,lua脚本最终来保证原子性
jedis实现的方式比较麻烦
redssion实现方式
1.从redission获取锁
2.加锁
3.finnal里面解锁
防死锁,锁续期,以及可重入的锁的功能
引入红锁
redssion红锁解决问题
请求一个分布式锁的时候,成功了,从机还没复制主机的锁,主机挂了,应用继续请求锁,会从继任了master的原slave上申请,也会成功;这就会导致,同一个锁被获取了不止一次
红锁工作过程
1、获取当前的时间(单位是毫秒)
2、使用相同的key在这个集群中所有节点请求锁
3、只有在大多数节点获取到锁,且获取锁的时间小于锁超时时间
4、如果成功,就继续
5、失败了,就会在设置了key的节点上删除key
集群脑裂
脑裂出现原因
由于网络原因哨兵节点无法感知主节点的存在,那么就会从从从节点中选取一个主节点;那么这个时候就会存在两个主节点了;
集群脑裂问题
新的master节点就不会同步原来老master节点的数据;当网络原因解决之后,原来的主节点就会变成从节点,新的master节点由于没有同步数据,就会造成缓存数据的丢失;
解决方式
设置连接到master的最少从机数量;从机连接主机最大延迟时间
redis集群
主从模式
特点
1.一主一备,主机写,从机读;主机挂了不影响从机读
2.主机挂了,系统还能提供读服务,并不能提供写服务,从机不会变成主机
缺点
主库挂了,那么就不能提供写服务,只能提供读服务
哨兵模式
特点
1.建立在主从模式之上,哨兵节点本身不做数据存储;
2.主节点挂了,哨兵节点就会从所有从节点中选取一个节点做为主节点;
3.挂掉的主节点重启之后,就作为从节点;
4.哨兵节点也可以组成哨兵集群
工作机制
1.客户端连接的是哨兵节点,由哨兵节点来提供Redis的各种服务
高可用集群模式
概述
哨兵模式就能保证高可用了,但是如果数据量过大,一台服务器存不下所有数据,就需要搭建高可以用集群
特点
高可用集群可以说是哨兵模式和主从模式结合体
集群至少有三个主节点,每个主节点至少有一个从节点(副本节点),所有一个高可用集群必须是由6个节点
可在线添加,删除节点
工作机制
缓存淘汰机制
缓存淘汰类型
不设置任何淘汰机制
noeviction
不做任何处理,写入超过限制后,会返回操作错误;读操作还是可以正常进行
默认淘汰策略
LRU最近最少使用淘汰
volatile-lru
设置了过期时间,清除最近最少使用的键值对
allkeys-lru
未设置过期时间,清除最近最少的键值对
存在问题
1、需要存储缓存数据之外额外的时间数据
2、可能会删除热key
解决方案
设置每一次要被淘汰的key的个数,个数如果=10比较,对热键数据影响比较小
实现
缓存对象中会存储这个缓存最近被访问的时间戳
淘汰的时候会根据当前的时间戳-缓存对象中的时间戳,差值最大的就被淘汰
子主题
LFU最小使用频率淘汰
volatile-lfu
设置了过期时间,清除某段时间内使用次数最少的键值对
allkeys-lfu
未设置过期时间,清除某段时间使用次数最少的键值对
TTL生命周期结束淘汰
volatile-ttl
设置了过期时间,清除过期时间最早的键值对
random随机淘汰
allkeys-random
未设置过期时间,随机清除键值对
volatile-random
设置了过期时间,随机清除键值对
内存清理
过期策略的实现(针对过期key回收)
1.后台根据删除任务的执行频率,默认是每秒10次;
2.删除key的时间限制;
3.从数据库的过期列表中随机选择20个key,判断是否过期,过期就清理;
4.如果有5个以上的key过期,那么就会重复再次选择20个;
5.清理的时间不能超过设定的时间;
删除过程:分为同步删除,异步删除
LRU实现(每次命令处理的时候触发内存清理)
遍历每一个数据库,从每一个数据库随机抽取一批数据,默认是五个,如果对比这批数据的最近的使用时间,删除最远使用时间的键值对;
缓存淘汰机制选择
1.如果缓存有明显的热点分部,那么就选择LFU算法
2.如果缓存没有明显热点分部,那么就选择随机
内存回收时间
1.Redis的每一次命令处理的时候,都会去判断当前redis是否已经达到最大缓存极限,如果达到极限,就会启用相应算法去处理需要清除的键值对;
2.过期key的回收
1.访问key的时候,key是否过期,过期就删除;
2.Redis启动时候的定时时间,默认是每秒十次的检测过期的key,过期就清理;清理的时间只有CPU执行时间的四分之一。
事件通知机制
事件通知概述
redis数据集改动事件之后对客户端的一种通知行为
事件通知类别
键空间通知
键事件通知
通知类型
删除,设置过期时间,重命名等一些和数据类型无关的操作的通知
字符串命令通知
列表命令通知
集合命令通知
哈希命令通知
有序集合命令通知
过期事件通知
缓存驱逐事件通知
不管发生什么事件都通知
事件通知使用
该功能默认是关闭;需要在config配置文件中开启该功能
配置形式:notify-keyspace-events +事件通知类别和通知类型;notify-keyspace-events "Ex"表示对过期事件进行通知发送
事件订阅缺陷
事件通知是不可靠的,服务器采用的是发送即忘,如果当订阅事件发生的时候;客户端掉线了,那么这个事件就不会通知到客户端,所有事件订阅是不可靠的
持久化机制
AOF
机制说明
1.记录每一个redis的写命令以日志的形式进行存储
2.AOF刷盘时间间隔
1.有命令就刷盘一次
2.一秒刷盘一次(推荐,也是默认的)
3.由系统决定刷盘时间间隔
3.为啥需要设置刷盘时间:持久化的目的是把数据记录在磁盘上,所以当数据在内存中的时候,就需要把内存中的数据放到磁盘上,放到磁盘上的时间间隔就是刷盘时间;
优缺点
优点
1.持久化实时性比较高(可以设置间隔多少秒追加一次日志,也就是间隔时间越短,丢失的数据就是越少)
缺点
1.AOF文件的体积通常大于RDB
2.数据恢复比rdb慢
AOF机制
1.当AOF文件过大时,后台会去优化AOF文件;
当AOF文件出错(以下两者方式都是可以解决AOF文件出错了,数据该怎么恢复的问题,最终还是需要重启redis服务器去载入AOF文件)
1.可以使用修复程序修改AOF文件;
2.为AOF文件创建一个备份文件
RDB(默认方式)
机制说明:就是以内存快照的形式缓存内存中的数据
缺点:1.实时性比较低,单独使用该持久化机制,服务器宕机导致数据丢失较多;
优缺点
缺点
1.实时性比较低,单独使用该持久化机制,容易导致数据丢失;
2.从主进程fork子进程的时候会被阻塞,
优点
1.rdb文件大小紧凑;可以设置间隔时间备份,还原到不同历史时期的数据状态
2.持久化的时候可以由子进程去完成所有的数据保存工作;父进程无需任何的io操作;
3.数据恢复比AOF快
数据存储:存储在dump.rdb文件中
持久化机制
1.可以在不重启的情况下切换RDB到AOF模式
2.当RDB,AOF都打开的时候,程序默认使用AOF方式持久化
容灾措施
1.定期的把RDB文件备份到其他位置
多路复用机制
epoll模式的多路复用
多线程
单线程性能瓶颈
网络IO
多线程只是用来处理网络数据读写和协议的解析,执行Redis命令依旧是单线程去执行
事物机制
事物本质
一组命令的集合,要么所有的命令都执行成功,要么都执行失败
事物执行过程
一个事物所有的命令都会放在队列中缓存,执行的时候会去串行执行队列中的命令
事物相关命令
MULTI
开启一个事物
EXEC
执行这个事物的所有命令
discard
取消事物
watch
监视某些key
unwatch
放弃监视某些key
watch命令特别说明
配置事物一起使用,只有被监视的key没有发生任务数据变化的时候,事物才会被执行,否则是不会被执行
使用方式:在事物开始之前监听某些key
事物中的错误类型
入队时候的语法错误
2.6.5之前版本,忽略入队失败的命令,可以继续执行事物
2.6.5开始版本,入队失败,执行事物的时候会自动放弃执行该事物
执行事物调用之后错误;比如说错误的用string数据结构的命令操作list数据结构的数据
exec事物开始执行的命令开始了,事物队列中某条或者某些命令执行失败了,Redis依旧会接着执行命令,不会放弃执行命令
redis事物与数据库事物最大差别
不支持回滚,即使事物队列开始执行后,有命令执行失败了也不会回滚
主从复制机制
复制分类
全量复制
作用:把从服务器数据的状态更新到和主服务器状态一致;
TODO,全量复制过程
使用场景:一般都刚刚搭建服从服务的时候
就是从机在初始化的时候,把主机中所有的数据都赋值到从机上
缺点
1.数据量较大时候,主从节点的网络开销很大
增量复制
作用
1.当主服务器收到写命令的时候,为了保持从服务器与主服务器的数据一致;就会让从服务器也去执行主服务器的命令;这个过程就是增量赋值的过程
2.对全量复制方式的工作方式弥补,当主从断开了连接,就不需要做全量复制,只需要执行断开期间主服务器的写命令
已经初始化完成的从服务器,需要做增量赋值主机的数据
概述:复制分为全量复制,增量复制,也就是对应着同步操作,命令行操作;
心跳检测
1.各自彼此都模拟成对方的客户端发送心跳信息
2.主节点默认间隔10秒给从节点 发送链接信息
3.从节点默认间隔1秒给主节点发送偏移量
主从复制过程
1.从服务器给主服务器发送同步命令;
2.主服务器开启后台进程,生产快照数据,发送给客户端,并缓存当前主服务器的写命令
3.从服务器清空之前缓存的所有数据
4.从服务器收到rdb文件保存在磁盘,从磁盘读取数据同步数据;
5.主服务器把缓存的写命令传递给从服务器,从服务器同步这些写命令;
复制原理
1.主节点处理完命令之后,会把命令字节长度累加记录起来,一个记录在命令表,一个记录在偏移量表
2.从节点收到主节点的命令,也会累计自身节点的复制的偏移量;
3.从节点每秒钟把自己的偏移量发送给主节点,主节点对比偏移量,
4.主节点就知道从节点的数据是否和主节点数据一致;
复制注意事项
1.从服务器在同步时,会清空所有数据
2.Redis不支持主主复制
3.主从复制不会阻塞master
4.主节点的处理完写命令就会直接给客户端返回,然后异步将命令传递给从服务器
Redis 主从同步策略
1、主从刚刚连接的时候,进行全量同步
2、全同步结束后,进行增量同步
如果有需要,slave 在任何时候都可以发起全量同步
无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步
redis为啥快
1.数据存储在内存
2.数据结构简单
3.单线程不存在锁
4.io多路复用
Redis配置文件说明
内存相关
maxmemory
Redis最大存储大小
为0的时候表示可以无限制使用redis内存
maxmemory-policy
配置内存清理策略
maxmemory-samples
作为LRU,LFU,TTL内存回收策略,检查数量的key
redis命令
数据类型操作
String
在来的字符串后面追加拼接
APPEND myphone "nokia"
返回指定字符串的值中间几位对应的字符串
GETRANGE greeting 0 4
重新设置key的值返回老的key的值
GETSET db mongodb
把key为db 的数据的值设置成 mongodb,返回老的值
自增自减
DECR failure_times
failure_times对应的数值自减1
DECRBY count 20
count对应的数值指定自减的数量
INCR page_view
page_view的值自增1;
INCRBY rank 20
rank对应的数据自增20
INCRBYFLOAT mykey 0.1
mykey对应的值自鞥指定的浮点数值
位图操作
Hash表操作
设置值
HSET website google "www.g.cn"
key为website
value中field为Google,value为"www.g.cn"
获取指定value指定field对应的值
HGET site redis
获取key的所有field的值
HGETALL people
获取people所有field和值
删除指定key的指定的field的值
HDEL abbr a
指定field是否存在
HEXISTS phone myphone
自增自减
HINCRBY counter page_view 200
对key 为counter 中field字段难为page_view的自增200
HINCRBYFLOAT mykey field 0.1
返回所有的field
HKEYS website
HMSET website google www.google.com yahoo www.yahoo.com OK
操作结果
1) "google"2) "yahoo"
返回所有的域的值
HVALS website
1) "www.google.com"2) "www.yahoo.com"
List操作
往列表中添加
LPUSH languages python
可以重复添加
LPUSHX greet "hello"
往列表的表头添加
RPUSH languages c
往列表的表尾添加
LINSERT mylist BEFORE "World" "There"
指定key中在指定元素的前面或者后面添加元素
从列表中获取
LPOP course
获取表头元素并删除
RPOP mylist
获取表尾元素并删除
blpop key timeout
获取表头元素,如果没有元素就会阻塞,阻塞的时间为指定时间
brpop key timeout
获取表尾元素,如果没有元素就会阻塞,阻塞的时间为指定时间
LRANGE fp-language 0 1
返回list中指定某个索引位置的数据
LINDEX mylist 3
返回对应索引位置的值
删除
lrem key count value
根据key中value的值删除指定个数
ltrim key start stop
删除指定区间的值
Set
存储
添加单个
SADD bbs "discuz.net"
不能被重复添加
hmset
批量存储hash
移除
单个移除
SREM languages ruby
移除 languages 中的ruby元素
SMEMBERS not_exists_key
移除key的所有元素
删除集合并随机返回一个元素
SPOP db
获取
获取集合长度
SCARD tool
获取tool集合长度
获取集合中所有元素
SMEMBERS db
返回两个集合的交集
SINTER group_1 group_2
返回两个集合的并集
SUNION songs my_songs
返回两个集合的差集
SDIFF peter's_movies joe's_movies
判断
判断某个元素是不是当前set集合的元素
SISMEMBER joe's_movies "bet man"
判断key为 joe's_movies 中是否含有"bet man"
Zset
添加
添加单个元素
ZADD page_rank 10 google.com
往key为page_rank集合中添加数值为10 的google.com 元素
添加多个元素
ZADD page_rank 9 baidu.com 8 bing.com
给指定的元素添加分数
ZINCRBY salary 2000 tom
移除
移除一个或者对个元素
ZREM page_rank google.com
ZREM page_rank baidu.com bing.com
移除按照排名指定区间的数据
ZREMRANGEBYRANK salary 0 1
移除指定索引区间的值
ZREMRANGEBYSCORE salary 1500 3500
移除指定分数区间的值
获取
获取集合长度
ZCARD salary
获取salary集合长度
返回指定区间的元素
ZRANGE salary 200000 3000000 WITHSCORES
正序从小到大
ZRANGE salary 0 -1 WITHSCORES
整个集合从小到大排序
ZREVRANGE salary 0 -1 WITHSCORES
递减排列
获取指定分数区间的元素个数
ZCOUNT salary 2000 5000
获取salary集合中分数在2000到5000之间的分数
获取指定元素的分数
ZSCORE salary peter
获取salary集合 peter对应的分数
指定分数区间分页查询
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]
排序获取元素排名
zrank key member
排序按照分数从小到大
zrevrank key member
排序按照分数从大到小
相同操作
设置
字符串
setnx key value
setex key seconds value
hash
HSETNX nosql key-value-store redis
重新设置 nosql 中指定field对应的数据
移动元素到另外集合
set集合
SMOVE songs my_songs "Believe Me"
将songs 集合中的"Believe Me"元素移动到my_songs
list
rpoplpush source destination
把 source的list集合尾部元素添加到 目标元素的头部;并把值返回给客户端
brpoplpush source destination timeout
上一个命令的阻塞版本
批量操作
批量存储
字符串
MSET date "2012.3.30" time "11:00 a.m." weather "sunny" OK
Hash
HMSET website google www.google.com yahoo www.yahoo.com OK
批量获取
字符串
MGET date time weather
Hash
HMGET pet dog cat fake_pet
获取key 为pet中对应的field对应的数据
批量设置
字符串
MSETNX rmdbs "MySQL" nosql "MongoDB" key-value-store "redis"
返回长度
字符串
STRLEN mykey
返回mykey对应的value的长度
hash
hlen key
返回key对应的field的个数
list
LLEN job
list集合对应的长度
key操作
存活时间
获取key存活还有多少存活时间
TTL key
-1
没有设置存活时间
10084
还存活 10084秒
PTTL key
返回值是key存活的毫秒值
设置生存时间
EXPIRE cache_page 30000
设置的时间为毫秒值
PEXPIRE mykey 1500
生存时间为1500毫秒值
PERSIST mykey
移除key的生存时间
删除key
DEL name
指定key删除
FLUSHDB
清除整个redis的数据
判断key是否存在
EXISTS phone
模糊匹配获取key
先批量设置key,value MSETone1two2three3four4
KEYS *o*
返回值 four,two,one
KEYS t??
"two"
KEYS t[w]*
KEYS t[w]*
随机返回一个key
RANDOMKEY
返回值为随机的一个key
移动key到其他数据库
MOVE song 1
把key为song 的值移动到数据库1里面;Redis默认的存放在第一个数据库
重命名key
RENAME message greeting
0,key不存在
1,成功
renamenx key newkey
新的key不存在的时候才会成功
根据key获取value的数据类型
TYPE weather
排序
SORT
返回指定list,有序集合,无需集合拍过排序之后的结果
排序方式按照 数字大小,字母的自然排序
序列,反序列key
DUMP
RESTORE
特殊命令
分页查询操作
Zset
List
说明,只有Zset和List支持分页查询;
计算地理位置
获取经纬度的geoHash值
Redis与MemCache的区别
线程操作
redis数据处理是单线程,memcache是多线程处理
数据结构
Redis支持更多更复杂的数据结构,memcache只支持keyvalue的字符串数据;
数据安全性
Redis支持数据的持久化,会把数据同步到磁盘上;memcache不支持数据的持久化
数据备份
Redis支持数据备份,需要开启主从模式;memcache不支持数据备份
过期策略
REDIS支持更多的过期策略;memcache支持的过期策略少
开发模式
jedis
redisson
springBoot+整合redis
本质还是springBoot整合了jedis
常用应用通用设计总结
数据
数据存储
刷盘策略
mysql
kafka
具体刷盘策略无外乎以下三种类型
来一条信息就刷盘一次
等缓冲池满了刷盘
超过固定时间刷盘
通信
多路复用
redis
netty
零拷贝
netty
kafka
心跳检测
eurake
eurake和各个微服务之间通过心跳检测,检查微服务是否还在线
nacos
eurake和各个微服务之间通过心跳检测,检查微服务是否还在线
zk
主从节点之间的心跳
监控设计
应用服务器是否可以提供接口,让监控服务器来监控应用服务器状态?
pull数据
消息队列
kafka
rocketMq
rabbitMq
默认的都是消费者去消息中间件服务器拉取数据,也可以设置成服务器向消费者主动推送;
普罗米修斯
监控数据默认都是从各个被监控对象拉去数据;也可以设置从被监控对象主动给普罗米修斯推送
拉取数据设置好处
消息中间件如果做成主动推送,消息推送可能会把消息中间件服务器造成资源压力
TODO
采取pull的方式,生产者这边难道就不会有消息存储的内存长期占用??不也是会造成生产者服务器压力????
常见数据复制模型
使用复制的目的
1、提高系统容错性,提高可用性, 避免单点故障
2、提高系统可扩展性,便于在原有系统中新增机器;
3、提供用户体验,文件系统多地部署,提高用户体验
常见模式
主从模式
缺点
如果是主机写,从机读,容易出现数据延迟到数据一致性问题;
如果是主机写,主机读,会导致主机压力过大
数据写入不具备可扩展性
需要支持的能力
1、新增新的从节点
2、处理失效节点能力
1、从节点失效
从节点需要追赶式回复数据
2、主节点失效
切换新主节点
子主题
存在脑裂情况
存在两个主节点
3、异步复制导致数据一致性问题
多主节点模式
缺点
数据版本冲突
新提交的数据需要覆盖所有节点的数据,其他节点也有提交的数据,两者都需要去覆盖对应的数据,中间需要选取最新的数据。
解决
1、让用户去解决
2、以时间为标准,后提交的数据作为最终的数据
使用场景
1、多数据中心
2、离线客户端操作
3、协同编辑
无主节点模式
任何副本都能接受来自客户端的写请求
需要支持能力
1、处理失效节点
2、写读一致性
只能是最终一致性
导致问题原因
写和读的副本不一样,读的副本可能还没有复制完成新数据
感想
以后在类似的系统设计中可以使用到这些复制模型,选择对应的数据模型,就需要有相关的能力支持
共同问题
消息队列
消息丢失
消息顺序消费
消息重复消费
消息积压
集群部署
redis集群
数据库
消息队列
java应用
系统优化问题
解决思路
1、如果这个系统本身使用java写的,肯定需要从这个系统的jvm优化方面可以入手
2、从这个系统的本身所在机器的cpu,内存,磁盘io,等硬件优化
网络io,磁盘io一般是瓶颈点
3、系统本身的某个重点功能的性能瓶颈点,寻找优化突破,代码优化,
可视化管理平台
springboot
提供admin管理平台
有自己的监控平台
springCloud组件
eurake
查看注册中心有哪些服务已经注册
hystrix
有对应的监控平台
sentinel
查看资源配置,修改配置的图形界面,监控平台
消息队列
kakfa
有admin平台
各种参数设置
rabbitMq
队列管理平台
各种参数设置
rocketMq
队列管理平台
各种参数设置
操作平台
k8s可视化操作平台
linux可视化操作平台
猜想一个简化软件开发的思路
各种需要进行命令进行操作的组件或者引用开发对应的可视化平台
对应的问题
界面如何和对应的服务做相关的命令数据交互
基础建设相关
Elk
可视化
logstash
普罗米修斯
可视化
granfan
系统级别接口
springBoot提供查询当前服务运行各项指标接口
猜想
spring是否也有
各种dao层框架是否也有
jvm也提供系统系统级别的接口
unsafe类
对系统内存操作类
MBean-jvm层级监控
mysql对外暴露接口
猜想,为啥可以去拉取mysql的二进制日志,是否也是mysql提供了对外暴露接口
mysql直接通过sql语句可以获取相关库表,磁盘占用数据;慢sql语句
可视化操作平台也表明了对应的引用是否也提供了对外暴露的系统级别的接口?
消息队列
kafka提供也提供当前系统查询指标接口
rabbitMq 也提供了相关接口
rokcetMq 提供了mqadmin 命令行工具来操作
redis
redis自带的info命令和monitor命令的相关信息
链路追踪监控诊断
链路追踪
链路追踪工具
cat
产品定位
CAT(Central Application Tracking)是一个实时和接近全量的监控系统
侧重于Java应用的监控
应用场景
mvc框架
rpc框架
持久层框架
分布式缓存框架
提供各项性能监控,健康检查,自动报警
cat系统的设计要求
实时处理
时间越久,监控的信息价值会锐减
全量数据
监控的是所有的请求数据
高可用
应用服务挂了,监控还在,可以辅助排查定位问题
高吞吐
全量数据的接收和处理能力
故障容忍
监控本身的故障不会影响业务代码的正常运行
可扩展
支持分布式,跨IDC部署,横向扩展的监控系统
不保证可靠
cat监控系统的可靠性可以做到四个九
cat整体设计
主要分为三个模块
CAT-client
应用应用埋点的底层sdk,的客户端
CAT-consumer
实时消费,处理客户端提供的数据
CAT-home
给用户展示的控制端平台
结构展示
客户端信息收集
1、为每一个线程创建一个ThreadLocal(线程局部变量);
2、执行业务逻辑的时候,就把请求对应的监控信息存储在线程的局部变量中
请求对应的上下文其实是一个监控树的结构
3、业务线程执行结束之后,将监控对象放入一个异步内存队列中;
4、cat会有一个消费线程将异步队列中的信息发送给服务端;
核心监控对象
Transaction
一段代码运行时间,次数
Event
一行代码的执行次数
Heartbeat
jvm内部的一些状态信息,Memory.Thread等
Metric
一个请求调用的链路统计
序列化和通信设计
序列化
cat序列化协议是cat自己自定义的协议
通信
netty来实现nio
存储设计
cat报表数据
cat原始logview数据
整体架构设计图
设计图
子主题
流程说明
1、客户端向服务端发送消息基于netty-nio实现
2、服务端接受消息放入内存队列,开起一个线程消费来分发这个内存队列中的消息
3、消息解析完成站会,存入本地磁盘,然后再异步上传到HDFS
实时分析
总个数
总和
均值
最大,最小
吞吐
95线,99线,999线
skyWalking
使用
实现
作用
pinpoint
Spring Cloud Sleuth
Spring Cloud Sleuth 是 Spring 团队提供的链路追踪技术,大量借用了 Google Dapper 的设计
链路追踪实现原理
java agent技术
应用
zipkin,skywalking,pinpoint链路追踪等都是由这个来实现的
历史
jdk1.5版本之后引入的特性
jdk1.6可以支持更加强大的Instrument
作用
在class被加载之前对其进行拦截,然后插入自定义的监听代码
无需对原有的应用做出任何代码修改,可以实现对类的动态修改和增强
可以理解是jvm级别的AOP
java-agent实现监听流程
详细流程
1、jvm启动的时候会伴随一个java-agent的jar包附加程序启动
2、这个java agent包中的配置文件中指定代理类,代理类中会有一个premain方法
3、jvm在类加载的时候会执行代理类的premain方法,再执行java程序本身的main方法
4、prmain方法可以对加载前的class文件进行修改
详细流程图
子主题
java-agent实现
1、通过Java Instrumentation 接口进行变成
2、Instrumentation接口的实现是通过jvm虚拟机提供的native接口来实现
实战demo
https://blog.csdn.net/m0_69305074/article/details/124504419
分布式日志系统
ELK
通俗来讲,ELK就是
可视化
诊断工具
arthas-阿尔萨斯
产品自我定位
线上监控诊断产品
大大提升线上问题排查效率
新名词学习
Perf
性能剖析(performance profiling)和代码优化
指标参数说明参考文档
https://blog.csdn.net/web18224617243/article/details/123953692
https://blog.csdn.net/Cr1556648487/article/details/126816451
https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html
https://arthas.aliyun.com/doc/getstatic.html
统计指标项目学习
java.ci.totalTime
jit编译花费的总时间
命令列表
常用命令
查看 logger 信息,更新 logger level
logger
logger
查看当前 JVM 的 Perf Counter 信息
perfcounter
子主题
生成发放火焰图
profiler
内存相关
下载当前内存信息
heapdump
heapdump /tmp/dump.hprof
下载当前内存信息到某个目录下
子主题
查看jvm当前内存信息
jvm
jvm
查看当前 JVM 信息
查看 JVM 内存信息
memory
memory
查看当前线程信息,查看线程的堆栈
最忙的几个
子主题
所有
子主题
编译文件相关
dump 已加载类的 bytecode 到特定目录
子主题
dump
反编译指定已加载类的源码
子主题
编译.java文件生成.class
子主题
查看 JVM 已加载的类信息
sc
子主题
查看已加载类的方法信息
sm
子主题
vmtool 利用JVMTI接口,实现查询内存对象,强制 GC 等功能。
子主题
方法监控
方法执行监控
monitor
输出当前方法被调用的调用路径
stack
方法内部调用路径,并输出方法路径上的每个节点上耗时
trace
方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
tt
函数执行数据观测
watch
参数查看
查看当前 JVM 的环境属性
子主题
查看当前 JVM 的系统属性
子主题
查看,更新 VM 诊断相关的参数
vmoption
子主题
查看指定参数
更新指定的 option
子主题
子主题
预览命令
当前系统的实时数据面板
dashboard
dashboard
子主题
当前系统的实时数据面板,按 ctrl+c 退出
查看当前类静态属性
getstatic
预览
子主题
查看 classloader 的继承树,urls,类加载信息
输出当前目标 Java 进程所加载的 Arthas 版本号
文件相关
打印文件内容,和 linux 里的 cat 命令类似
子主题
打印命令历史
history
实现原理
cpu 使用率是如何统计出来的?
一段采样间隔时间内,当前 JVM 里各个线程的增量 cpu 时间与采样间隔时间的比例
和linux系统类似
具体步骤
首先第一次采样,获取所有线程的 CPU 时间(调用的是java.lang.management.ThreadMXBean#getThreadCpuTime()及sun.management.HotspotThreadMBean.getInternalThreadCpuTimes()接口)
然后睡眠等待一个间隔时间(默认为 200ms,可以通过-i指定间隔时间)
再次第二次采样,获取所有线程的 CPU 时间,对比两次采样数据,计算出每个线程的增量 CPU 时间
线程 CPU 使用率 = 线程增量 CPU 时间 / 采样间隔时间 * 100%
注意事项
命令本身也是需要消耗时间的,会对结果有一定干扰
把统计的时间拉长可以降低命令本身执行的时间损耗
功能概述
1:查看应用 load、内存、gc、线程的状态信息
2:可在不修改应用代码的情况下,对业务问题进行诊断
查看方法调用的出入参
查看方法异常
监测方法执行耗时
类加载信息
子主题
监控系统
监控系统的要求
1、快速发现故障
2、快速定位故障
3、辅助进行程序性能优化
监控维度
基础资源监控
jvm监控
jvm监控指标
Overview
Threading
GC
CPU
Heap
jian
指标数据采集
本质都是通过JMX来实现对jvm数据的采集
java.management工具包
code -demo
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
map.put("堆内存", memorymbean.getHeapMemoryUsage());
map.put("方法区内存", memorymbean.getNonHeapMemoryUsage());
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
map.put("运行时设置的JVM参数", inputArgs);
// 总的内存量
long totle = Runtime.getRuntime().totalMemory();
// 空闲的内存量
long free = Runtime.getRuntime().freeMemory();
// 最大的内存量
long max = Runtime.getRuntime().maxMemory();
Map<String, Long> params = new HashMap<>();
params.put("totalMemory", totle);
params.put("freeMemory", free);
params.put("maxMemory", max);
map.put("运行时内存情况", params);
map.put("堆内存", memorymbean.getHeapMemoryUsage());
map.put("方法区内存", memorymbean.getNonHeapMemoryUsage());
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
map.put("运行时设置的JVM参数", inputArgs);
// 总的内存量
long totle = Runtime.getRuntime().totalMemory();
// 空闲的内存量
long free = Runtime.getRuntime().freeMemory();
// 最大的内存量
long max = Runtime.getRuntime().maxMemory();
Map<String, Long> params = new HashMap<>();
params.put("totalMemory", totle);
params.put("freeMemory", free);
params.put("maxMemory", max);
map.put("运行时内存情况", params);
// 堆内存
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
// 方法区内存
MemoryUsage heapMemoryUsage = memorymbean.getHeapMemoryUsage();
long heapInit = heapMemoryUsage.getInit() / conversion;
long heapCommitted = heapMemoryUsage.getCommitted() / conversion;
long heapUsed = heapMemoryUsage.getUsed() / conversion;
long heapMax = heapMemoryUsage.getMax() / conversion;
Map<String, Long> heapMap = new HashMap<>();
heapMap.put("init", heapInit);
heapMap.put("committed", heapCommitted);
heapMap.put("used", heapUsed);
heapMap.put("max", heapMax);
map.put("堆内存", heapMap);
MemoryUsage nonHeapMemoryUsage = memorymbean.getNonHeapMemoryUsage();
long noHeapInit = nonHeapMemoryUsage.getInit() / conversion;
long noHeapCommitted = nonHeapMemoryUsage.getCommitted() / conversion;
long noHeapUsed = nonHeapMemoryUsage.getUsed() / conversion;
long noHeapMax = nonHeapMemoryUsage.getMax() / conversion;
Map<String, Long> noHeapMap = new HashMap<>();
noHeapMap.put("init", noHeapInit);
noHeapMap.put("committed", noHeapCommitted);
noHeapMap.put("used", noHeapUsed);
noHeapMap.put("max", noHeapMax);
map.put("方法区内存", noHeapMap);
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
map.put("运行时设置的JVM参数", inputArgs);
// 总的内存量
long totle = Runtime.getRuntime().totalMemory();
// 空闲的内存量
long free = Runtime.getRuntime().freeMemory();
// 最大的内存量
long max = Runtime.getRuntime().maxMemory();
Map<String, Long> params = new HashMap<>();
params.put("totalMemory", totle / conversion);
params.put("freeMemory", free / conversion);
params.put("maxMemory", max / conversion);
map.put("运行时内存情况", params);
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
// 方法区内存
MemoryUsage heapMemoryUsage = memorymbean.getHeapMemoryUsage();
long heapInit = heapMemoryUsage.getInit() / conversion;
long heapCommitted = heapMemoryUsage.getCommitted() / conversion;
long heapUsed = heapMemoryUsage.getUsed() / conversion;
long heapMax = heapMemoryUsage.getMax() / conversion;
Map<String, Long> heapMap = new HashMap<>();
heapMap.put("init", heapInit);
heapMap.put("committed", heapCommitted);
heapMap.put("used", heapUsed);
heapMap.put("max", heapMax);
map.put("堆内存", heapMap);
MemoryUsage nonHeapMemoryUsage = memorymbean.getNonHeapMemoryUsage();
long noHeapInit = nonHeapMemoryUsage.getInit() / conversion;
long noHeapCommitted = nonHeapMemoryUsage.getCommitted() / conversion;
long noHeapUsed = nonHeapMemoryUsage.getUsed() / conversion;
long noHeapMax = nonHeapMemoryUsage.getMax() / conversion;
Map<String, Long> noHeapMap = new HashMap<>();
noHeapMap.put("init", noHeapInit);
noHeapMap.put("committed", noHeapCommitted);
noHeapMap.put("used", noHeapUsed);
noHeapMap.put("max", noHeapMax);
map.put("方法区内存", noHeapMap);
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
map.put("运行时设置的JVM参数", inputArgs);
// 总的内存量
long totle = Runtime.getRuntime().totalMemory();
// 空闲的内存量
long free = Runtime.getRuntime().freeMemory();
// 最大的内存量
long max = Runtime.getRuntime().maxMemory();
Map<String, Long> params = new HashMap<>();
params.put("totalMemory", totle / conversion);
params.put("freeMemory", free / conversion);
params.put("maxMemory", max / conversion);
map.put("运行时内存情况", params);
https://blog.csdn.net/qq_17522211/article/details/117552950
计算机资源监控
获取操作系统静态信息
方式1:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.4</version>
</dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.4</version>
</dependency>
文档
https://blog.csdn.net/u014295903/article/details/125557810
方式2:
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.20</version>
</dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.20</version>
</dependency>
参考文档
https://www.uoften.com/article/213906.html
获取操作系统运行时信息
linux系统
系统调用(System Call)是操作系统提供的服务,是应用程序与内核通信的接口
1、图形界面操作接口,Linux系统一般提供KDE、Gnome等图形界面接口,目的是方便普通用户操作计算机
2、控制台接口,即终端接口,Linux系统一般提供bash shell、cshell等等终端接口,目的是方便系统管理员操作计算机,事实上Linux功能的强大也主要体现在终端接口
3、API接口:即apllication interface,这是应用程序接口,从编程角度Linux系统就是一个大的程序调用库,它提供大量的API函数,目的是方便程序员开发应用程序
TODO 猜测此类接口可以获取Linux系统当前cpu水位,内存消耗情况
应用监控
数据源监控
连接池中链接数
连接池链接数峰值
池中连接数峰值时间
活跃连接数
活跃连接数峰值
JDBC访问监控
SQL语句
执行数
执行时间
错误数
读取行数
更行行数
慢SQL
Exception监控
异常类型
异常方法:
异常时间
异常数量
堆栈信息
如何实现TODO
接口调用监控
接口调用指标
接口路径
调用总数
最大并发
平均QPS
总耗时
平均耗时
99线
95线
999线
最快
最慢
错误数
接口链路
接口入参回参
业务监控
业务自定义
订单数量,支持成功数,点击次数,下载次数
Cache命中率
队列大小
如何实现TODO
监控方案设计
1、每个应用自监控或者统一上报监控?
自监控
应用自监控,就是每个应用实例的监控数据存放在应用本身,比如一个Map。然后通过JMX或者其他方式暴露出去。然后开发人员可以通过JConsole或者API(一般是Web界面)得到这些监控数据。比如Druid就是这种做法。访问: hk01-xxxx-mob03.hk01:8090/druid/index.html 得到hk01-xxxx-mob03.hk01:8090这个应用的监控数据。
统一监控
统一上报监控方式,就是所有的应用监控数据都上报到监控中心,由监控中心负责接收、分析、合并、存储、可视化查询、报警等逻辑。这种方式是瘦客户端模型,客户端的职责就是埋点上报监控数据。所有的监控逻辑都在中心处理
结论
自监控的话实现起来简单,并且没有与监控中心的网络交互,性能也会好很多。但是缺点就是缺乏全局的统计和监控。从实用角度来说还是集中式监控好一些。
2、监控中心与客户端应用之间要不要通过本地Agent上报?
agent上报
一个物理机上的多台服务器通过一个统一的agent向监控中心上报监控数据
独立上报
一个物理机上的多台服务器各自向监控中心上报统计数据
结论
网络通常的情况下,直接独立上报
3、存储最终状态还是事件序列
最终状态
经过业务逻辑的处理,得到相关的统计数据,然后再做储存
事件序列
直接就存储上报的统计信息本身的数据
结论
最终状态还是弱了一些,事件序列会好一些,存储可以采用HBase这样的分布式存储系统,性能问题可以采用预聚合等方式解决
4、数据存储
因为Events或者Metrics的特殊性,一般都会采用一种专门的存储结构——Distributed time series database
RRD(round-robin-database): RRDtool使用的底层存储。C语言编写的。性能比较高
whisper: Graphite底层的存储,Python写的
prometheus: An open-source service monitoring system and time series database. 目前只有单机版本。
InfluxDB: 开源distributed time series, metrics, and events database。Go语言编写, 不依赖于其他外部服务。底层支持多种存储引擎,目前是LevelDB, RocksDB, HyberLevelDB和LMDB(0.9之后将只支持Bolt)。
OpenTSDB: 基于HBase编写的Time Series Database
结论
如果要存储事件序列,那么InfluexDB和OpenTSDB是个非常不错的选择。都是可扩展,分布式存储,文档很详细,还是开源的。 influexDB 0.9.0之后支持tag,使用风格跟Google Cloud Monitor很相似,而且支持String类型。并且最重要的是不需要额外搭建HBase(Thus Hadoop & Zookeeper),看起来非常值得期待,不过截至今天0.9.0还是RC阶段(非Stable)。OpenTSDBvalue不支持String类型,这意味着日志不能上报到OpenTSDB,需要另外处理。
5、如果服务器挂掉了,统计数据怎么处理?缓存本地,等服务器起来再发送?还是丢弃?
前期可以先丢弃,后续要缓存起来。受影响比较大的是counter接口。
存储的话,可以考虑使用本地存储在RRD文件或者BDB中,或者消息队列中(RabbitMQ, ie.),最后再异步批量上报给中心的TSDB。
6、网络通信和协议
如何高性能的接收大量客户端的上报请求。以及使用什么通讯协议
有几种选择:
HTTP
TCP
UDP: fire and forget, 主要需要注意MTU问题。
HTTP
TCP
UDP: fire and forget, 主要需要注意MTU问题。
同时要考虑同步和异步接口
开源监控系统
Prometheus
Prometheus相关介绍
概述
Prometheus是一个开源的系统监控和警报工具包
历史
最初由SoundCloud构建。自2012年成立以来,许多公司和组织都采用了Prometheus,该项目拥有非常活跃的开发人员和用户社区。它现在是一个独立的开源项目,独立于任何公司进行维护
Prometheus于2016年加入了云原生计算基金会,成为Kubernetes之后的第二个托管项目
特点
1、一个多维数据模型,其时间序列数据由度量名称和键/值对标识
2、有自己独有的的查询语言,PromQL
3、不依赖分布式存储;单个服务器节点是自治的
但是可以把采集到的数据再次被同步到其他平台,或者数据存储库
Prometheus有具体的接口可以提供查询指标数据
对应的第三方的支持将数据导入到第三方
4、(统计指标)收集通过HTTP上的拉模型进行
5、通过中间网关支持推送(统计指标)时间序列
6、被采集的服务是通过服务发现或者静态配置的
7、支持多种模式的图形化和仪表板
架构
具体组件
1、主服务器,用于采集存储统计指标数据
2、搭载在各种客户端库数据采集器
3、数据推送网关
4、各种导出组件
5、告警组件
6、支持各种第三方工具
架构图
使用场景
1、以机器为核心的监控
2、多维度手机和查询数据
3、可靠性
不适合的场景
1、需要百分之百保证数据准确性
Prometheus收集到的数据并不是非常详细完整
Prometheus统计指标
Prometheus客户端库提供了四种核心指标类型
总和 Counter
计数器是一种累积度量,表示一个单调递增的计数器,其值只能在重新启动时增加或重置为零
例如,您可以使用计数器来表示服务的请求、完成的任务或错误的数量
Gauge
度量是指表示单个数值的度量,该数值可以任意上下波动
内存使用的最大子,最小值
Histogram
直方图对观测值进行采样(通常是请求持续时间或响应大小),并将它们计数在可配置的桶中
Summary
与直方图类似,摘要对观察结果进行抽样(通常是请求持续时间和响应大小)。虽然它也提供了观测的总数和所有观测值的总和,但它可以在滑动时间窗口上计算可配置的分位数。
Prometheus监控
Exporter
广义上讲所有可以向Prometheus提供监控样本数据的程序都可以被称为一个Exporter,Exporter的一个实例称为target
导出器的工作
工作示意图
导出器请求应用的数据查询接口,获取到数据,然后将数据整理成prometheus需要的数据格式,然后看数据是以导出器推送给prometheus还是prometheus来拉取相关的数据;
导出器来源
社区
用户自定义
需要根据基于Prometheus提供的Client Library创建自己的Exporter程序
Promthues社区官方提供了对以下编程语言的支持:Go、Java/Scala、Python、Ruby
导出器的运行方式
独立使用
需要独立部署的
MySQL Exporter、Redis Exporter等都是通过这种方式实现
集成到应用中
为了能够更好的监控系统的内部运行状态,有些开源项目如Kubernetes,ETCD等直接在代码中使用了Prometheus的Client Library,提供了对Prometheus的直接支持。这种方式打破的监控的界限,让应用程序可以直接将内部的运行状态暴露给Prometheus,适合于一些需要更多自定义监控指标需求的项目。
自定义export
需要在自定义的程序中引入prometheus的依赖包,且需要和prometheus定义通讯连接
Prometheus 监控 Java 应用
监控方式
方式1、使用官方提供的jar包,然后嵌入到应用中
这种方式一般都是新项目
方式2、prometheus的jmx_exporter
具体监控实施文档
https://blog.csdn.net/qq_25934401/article/details/82185236
https://blog.csdn.net/penngo/article/details/126982183
监控容器
cAdvisor
介绍
CAdvisor是Google开源的一款用于展示和分析容器运行状态的可视化工具
通过在主机上运行CAdvisor用户可以轻松的获取到当前主机上容器的运行统计信息,并以图表的形式向用户展示
CAdvisor默认只保存2分钟的监控数据
CAdvisor已经内置了对Prometheus的支持
相对于docker命令行工具
用户不用再登录到服务器中即可以可视化图表的形式查看主机上所有容器的运行状态
典型指标
container_cpu_load_average_10s gauge 过去10秒容器CPU的平均负载
container_cpu_usage_seconds_total counter 容器在每个CPU内核上的累积占用时间 (单位:秒)
container_cpu_system_seconds_total counter System CPU累积占用时间(单位:秒)
container_cpu_user_seconds_total counter User CPU累积占用时间(单位:秒)
container_fs_usage_bytes gauge 容器中文件系统的使用量(单位:字节)
container_fs_limit_bytes gauge 容器可以使用的文件系统总量(单位:字节)
container_fs_reads_bytes_total counter 容器累积读取数据的总量(单位:字节)
container_fs_writes_bytes_total counter 容器累积写入数据的总量(单位:字节)
container_memory_max_usage_bytes gauge 容器的最大内存使用量(单位:字节)
container_memory_usage_bytes gauge 容器当前的内存使用量(单位:字节
container_spec_memory_limit_bytes gauge 容器的内存使用量限制
machine_memory_bytes gauge 当前主机的内存总量
container_network_receive_bytes_total counter 容器网络累积接收数据总量(单位:字节)
container_network_transmit_bytes_total counter 容器网络累积传输数据总量(单位:字节)
container_cpu_usage_seconds_total counter 容器在每个CPU内核上的累积占用时间 (单位:秒)
container_cpu_system_seconds_total counter System CPU累积占用时间(单位:秒)
container_cpu_user_seconds_total counter User CPU累积占用时间(单位:秒)
container_fs_usage_bytes gauge 容器中文件系统的使用量(单位:字节)
container_fs_limit_bytes gauge 容器可以使用的文件系统总量(单位:字节)
container_fs_reads_bytes_total counter 容器累积读取数据的总量(单位:字节)
container_fs_writes_bytes_total counter 容器累积写入数据的总量(单位:字节)
container_memory_max_usage_bytes gauge 容器的最大内存使用量(单位:字节)
container_memory_usage_bytes gauge 容器当前的内存使用量(单位:字节
container_spec_memory_limit_bytes gauge 容器的内存使用量限制
machine_memory_bytes gauge 当前主机的内存总量
container_network_receive_bytes_total counter 容器网络累积接收数据总量(单位:字节)
container_network_transmit_bytes_total counter 容器网络累积传输数据总量(单位:字节)
和Prometheus集成
先启动cadvisor
Prometheus配置文件中配置cadvisor的ip地址和端口
然后启动Prometheus
被监控对象
k8s集群
node节点
pods
node-exporter(节点导出器)
监控mysql数据库
监控步骤
1、先部署MySQL_Exporter
该引用并不是和mysql整合在一起
2、在Prometheus配置MySQL_Exporter的IP和端口
redis监控
同mysql监控类似,通过redis_export
消息中间件监控
消息中间件监控,消息消费堆积这种情况,需要自己做定制化开发才行
消费堆积的计算,一般是通过生产者的消息偏移量和消费者消息偏移量对比;
消息中间件对应的 exporter
jvm监控
jmx
Prometheus数据存储
本地存储
将2小时作为一个时间窗口产生的数据放在一个数据块里面,保存在本地磁盘上
单个Prometheus Server基本上能够应对大部分用户监控规模的需求。
Prometheus数据序列数据库设计
核心设计思想:内置了一个本地数据序列数据库
优缺点
优点
1、可降低部署和管理复杂性
2、减少高可用带来的复杂性
缺点
本地存储无法将数据持久化,无法存储大量的数据,无法做到灵活扩展和迁移
远程存储
Prometheus定义两个标准接口(读写),让用户基于这两个接口保存到任意第三方存储服务,这就是远程存储
解决数据无法持久化问题
读写
远程写
远程读
联邦集群
特点
数据的采集和数据存储是分开的
利用数据是做远程存储,可以在每一个数据监控中安装一个监控实例,这个监控实例从公用的远程存储中心获取数据。
一个实例已经处理处理上千规模的集群
原理
1、在每一个数据监控中心部署一个实例
2、由中心的实力负责聚合多个数据中心的监控数据
示意图
数据可视化
概述
prometheus本身有自己的大盘数据,但是不太好用,一般是与grafana集成在一起
grafana
Grafana官方提供了对:Graphite, InfluxDB, OpenTSDB, Prometheus, Elasticsearch, CloudWatch的支持
所以prometheus可以将数据导入到grafana大盘中
Promthues高可用部署
基本HA:服务可用性
示意图
子主题
概述
1、部署多台服务器
2、多台服务器都会向同样的一批被监控的应用或者服务采集数据
3、数据展示的时候通过nginx负载均衡后从其中一台服务器拉取数据
优缺点
优点
确保Promthues服务的可用性问题
缺点
Prometheus Server之间的数据一致性问题
持久化问题(数据丢失后无法恢复)
无法进行动态的扩展
适用场景
适合监控规模不大
Promthues Server也不会频繁发生迁移的情况
保存短周期监控数据
基本HA + 远程存储
示意图
子主题
概述
相对于基础的服务可用性,此种部署方式,让数据在远程
优缺点
优点
确保了数据持久化,增加了系统可扩展性
Promthues宕机后,可以快速回复
可进行数据迁移
适用场景
用户监控规模不大,但是希望能够将监控数据持久化,同时能够确保Promthues Server的可迁移性的场景
基本HA + 远程存储 + 联邦集群
示意图
子主题
概述
将不同的采集任务划分到不同的Promthues
使用场景
场景一:单数据中心 + 大量的采集任务
场景二:多数据中心
按照实例进行功能分区
示意图
子主题
概述
极端情况下,采集的目标数量过大,通过联邦集群进行功能分区,也无法有效处理,考虑按照被采集的对象的级别,进行区分采集
Promthues服务发现
Promthues服务发现概述
服务注册中心存储所有监控目标的访问信息,Promthues只需要去访问服务注册中心就知道需要对那些目标进行监控,这种模式成为服务发现
为什么需要有服务发现注册
很多云平台的服务,可以创建销毁应用,在这个过程被监控的对象的信息都是在变化中,但是Promthues无法动态感知到这些信息的变化,需要有一个外部注册中心来感知这个,然后Promthues通过统一的调用注册中心的信息得到这些变化的信息;
示意图
子主题
注册中心
不同的场景下,会有不同的东西来扮演注册中心
注册中心举例
平台级别的公有云
AWS公有云平台
平台级别的私有云
OpenStack的私有云平台
容易云
Kubernetes容器管理平台
文件
DNS解析
服务发现实战
基于文件的服务发现
Promthues通过读取文件中的被监控者的信息,来动态更新监控对象的信息
可以不依赖第三方的平台
基于Consul的服务发现
Consul本身是一个支持多数据中心的分布式服务发现和键值对存储服务的开源软件
被监控的服务的信息会被注册到Consul
Promthues去Consul拉取对应的被监控的信息
服务发现与Relabeling
通过自定义标签,来区分不同环境的被监控对象的信息
Micrometer
简单介绍
spring 公司开源的一款对于微服务监控的应用
功能
应用接口指标监控
提供计时器、仪表、计数器、分布摘要和长任务计时器等指标与中立的接口
jvm监控
提供对缓存检测、类加载器、垃圾收集、处理器利用率、线程池接口
可拓展个性化指标
支持第三方那个监控系统
AppOptics、Azure Monitor、Netflix、Atlas、CloudWatch、Datadog、Dynatrace、Elastic、Ganglia、Graphite、Humio、 Influx /Telegraf、JMX、KairosDB、New Relic、Prometheus、SignalFx、Google Stackdriver、StatsD 和 Wavefront
所以可将Micrometer监控数据导入到Prometheus
核心功能
可以将Micrometer采集到的指标数据,转成progranmetheus的数据格式,且可以把数据传送给prometheus,然后在grafana展示
acturator 关系
Nagios
Zabbix
各种应用监控
jvm监控
监控相关工具
Java Profiler
jvm监控
MAT
java内存分析
VisualVM
偏向jvm监控
Oracle Java Mission Control
Java Mission Control与Java Flight Recorder一起,允许分析和事件收集有关Java虚拟机(JVM)和Java应用程序行为的低级信息。与Oracle JDK一起打包的这组工具还提供了对收集的数据的详细分析。
jvm内存监控实现原理
JMX
概述
全称Java Management Extensions,jdk5引进的技术
java.management包下提供接口
接口功能
ClassLoadingMXBean
获取类装载信息,已装载、已卸载量
CompilationMXBean
获取编译器信息
GarbageCollectionMXBean
获取GC信息,但他仅仅提供了GC的次数和GC花费总时间
MemoryManagerMXBean
提供了内存管理和内存池的名字信息
MemoryMXBean
提供整个虚拟机中内存的使用情况
MemoryPoolMXBean
提供获取各个内存池的使用信息
OperatingSystemMXBean
提供操作系统的简单信息
RuntimeMXBean
提供运行时当前JVM的详细信息
ThreadMXBean
提供对线程使用的状态信息
JMX 提供了简单、标准的监控和管理资源的方式
JMX架构
分层
资源层
包含 MBean 及其可管理的资源
提供了实现 JMX 技术可管理资源的规范
代理层
充当 MBean 和应用程序之间的中介
远程管理层
为远程程序提供Connector 和 Adapter访问 MBean Server
架构图
核心功能
实现对运行时应用程序动态资源查询
修改对运行时应用程序动态资源配置
利用JMX创建javaBean规则
核心api
获取操作系统相关的信息,机器名称、内存使用、CPU使用
OperatingSystemMXBean
ManagementFactory.getOperatingSystemMXBean() 方式获取
获取当前 JVM 的信息,包括 JVM 参数和 JVM 相关的系统参数
RuntimeMXBean
ManagementFactory.getRuntimeMXBean()方式获取
获取当前 JVM 的内存使用,包括堆内存和非堆内存
MemoryMXBean
ManagementFactory.getMemoryMXBean()获取
获取 JVM 线程使用情况,包括活动线程、守护线程、线程峰值
ThreadMXBean
ManagementFactory.getThreadMXBean() 获取
获取 JVM 类加载情况,包括已加载类、未加载类等
ClassLoadingMXBean
ManagementFactory.getClassLoadingMXBean() 获取
获取 JVM 垃圾收集器的情况,包括使用的哪种垃圾收集器以及回收次数
GarbageCollectorMXBean
ManagementFactory.getGarbageCollectorMXBeans() 获取
这里获取到的是一个集合,因为垃圾收集器分为老年代和新生代两个。
JMX创建javaBean规则
具体规则
1、创建需要被存入进程的对象;
2、对象必须是接口,且必须以MBean结尾
demo
创建接口
public interface BlackListMBean {
// 获取黑名单列表
public String[] getBlackList();
// 在黑名单列表中添加一个用户
public void addBlackItem(String uid);
// 判断某个用户是否在黑名单中
public boolean contains(String uid);
// 获取黑名单大小
public int getBlackListSize();
}
// 获取黑名单列表
public String[] getBlackList();
// 在黑名单列表中添加一个用户
public void addBlackItem(String uid);
// 判断某个用户是否在黑名单中
public boolean contains(String uid);
// 获取黑名单大小
public int getBlackListSize();
}
实现接口
public class BlackList implements BlackListMBean {
private Set<String> uidSet = new HashSet<>();
@Override
public String[] getBlackList() {
return uidSet.toArray(new String[0]);
}
@Override
public void addBlackItem(String uid) {
uidSet.add(uid);
}
@Override
public boolean contains(String uid) {
return uidSet.contains(uid);
}
@Override
public int getBlackListSize() {
return uidSet.size();
}
}
private Set<String> uidSet = new HashSet<>();
@Override
public String[] getBlackList() {
return uidSet.toArray(new String[0]);
}
@Override
public void addBlackItem(String uid) {
uidSet.add(uid);
}
@Override
public boolean contains(String uid) {
return uidSet.contains(uid);
}
@Override
public int getBlackListSize() {
return uidSet.size();
}
}
MBean 注册到 MBeanServer
// 获取 MBean Server
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
// 创建 MBean 初始黑名单用户为 a 和 b
BlackList blackList = new BlackList();
blackList.addBlackItem("a");
blackList.addBlackItem("b");
// 注册
ObjectName objectName = new ObjectName("com.common.example.jmx:type=BlackList, name=BlackListMBean");
platformMBeanServer.registerMBean(blackList, objectName);
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
// 创建 MBean 初始黑名单用户为 a 和 b
BlackList blackList = new BlackList();
blackList.addBlackItem("a");
blackList.addBlackItem("b");
// 注册
ObjectName objectName = new ObjectName("com.common.example.jmx:type=BlackList, name=BlackListMBean");
platformMBeanServer.registerMBean(blackList, objectName);
演示
String hostname = "localhost";
int port = 9000;
// 循环接收
while (true) {
// 简单从 Socket 接收字符串模拟接收到的用户Id
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(hostname, port), 0);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
char[] buffer = new char[8012];
int bytes;
while ((bytes = reader.read(buffer)) != -1) {
String result = new String(buffer, 0, bytes);
String uid = result;
// 去掉换行符
if (result.endsWith("\n")) {
uid = result.substring(0, result.length() - 1);
}
if (blackList.contains(uid)) {
System.out.println("[INFO] uid " + uid + " is in black list");
} else {
System.out.println("[INFO] uid " + uid + " is not in black list");
}
}
}
}
Thread.sleep(3000);
System.out.println("[INFO] 休眠 3s ..............");
}
int port = 9000;
// 循环接收
while (true) {
// 简单从 Socket 接收字符串模拟接收到的用户Id
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(hostname, port), 0);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
char[] buffer = new char[8012];
int bytes;
while ((bytes = reader.read(buffer)) != -1) {
String result = new String(buffer, 0, bytes);
String uid = result;
// 去掉换行符
if (result.endsWith("\n")) {
uid = result.substring(0, result.length() - 1);
}
if (blackList.contains(uid)) {
System.out.println("[INFO] uid " + uid + " is in black list");
} else {
System.out.println("[INFO] uid " + uid + " is not in black list");
}
}
}
}
Thread.sleep(3000);
System.out.println("[INFO] 休眠 3s ..............");
}
添加jvm配置
-Dcom.sun.management.jmxremote.port=8888 --表示远程jmx的端口
-Dcom.sun.management.jmxremote.authenticate=false --是否要使用用户名和口令验证
-Dcom.sun.management.jmxremote.ssl=false --是否使用安全socket
-Dcom.sun.management.jmxremote.authenticate=false --是否要使用用户名和口令验证
-Dcom.sun.management.jmxremote.ssl=false --是否使用安全socket
开通远程接口调用权限
登录远程jvm
已经实现的应用
jconsole
Java内置的实现监控工具 jconsole
参考文档
https://blog.csdn.net/weixin_35346265/article/details/114064440
https://blog.csdn.net/SunnyYoona/article/details/125154198
https://blog.csdn.net/weixin_42741805/article/details/116855285
https://blog.csdn.net/vincentff7zg/article/details/54582549
消息中间件监控
rabbitMq监控
监控指标
基础设施和核心指标
CPU状态(user、system、iowait&idle percentages
内存使用率(used、buffered、cached & free percentages)
虚拟内存统计信息(dirty page flushes, writeback volume)
磁盘I/O(operations & amount of data transferred per unit time, time to service operations)
装载上用于节点数据目录的可用磁盘空间
beam.smp使用的文件描述符与最大系统限制
按状态列出的TCP连接(ESTABLISHED,CLOSE_WAIT,TIME_WATT)
网络吞吐量(bytes received,bytes sent) & 最大网络吞吐量
网络延迟(集群中所有RabbitMQ节点之间以及客户端之间)
集群监控指标
集群信息获取
集群指标获取
请求集群中的任意节点获取
该节点在声场响应之前,需要收集组合来自其他节点的数据
集群指标
集群名称
cluster_name
集群范围的消息速率
message_stats
总连接数量
生产者,消费者和服务端的链接数量
object_totals.connections
总消息通道数量
消息通道是轻量级的连接
object_totals.channels
总队列数量
object_totals.queues
总的消费者数量
object_totals.consumers
消息总数(ready+unacked)
queue_totals.messages
准备交付的消息数量
queue_totals.messages_ready
未确认消息总量
queue_totals.messages_unacknowledged
最近发送消息数量
message_stats.publish
消息发布的速度
message_stats.publish_details.rate
最近给消费者消息数量
message_stats.deliver_get
消息发送速度
message_stats.deliver_get.rate
Other message stats
节点指标获取
可以对任意节点请求获取统计指标
节点指标
总的内存使用大小
mem_used
Memory usage high watermark
内存使用大小的最高水位
统计历史
内存使用阈值
以上两种理解?如何选择
当内存使用超过阈值时将触发报警memory alarm
Is a memory alarm in effect?
mem_alarm
剩余磁盘空间阈值
disk_free_limit
当空闲磁盘空间低于配置的限制时,将触发报警
可用文件描述符总数
已经使用的文件描述符大小
尝试打开的文件描述符数量
socket 系统连接最大值
socket 连接已经使用数量
Message store disk reads
?
Message store disk writes
?
Inter-node communication links
垃圾回收次数
垃圾回收内存大小
erlang 最大进程数量
已经使用的erlang 进程数量
正在运行的队列
单个队列指标获取
队列指标
内存大小
消息总数(ready+unacknowledged)
准备交付的消息数量
总的消息大小
准备传送的消息数量
未确认的消息数量
最近发布的消息数量
消息发布速度
最近交付的消息数量
消息传送速度
Other message stats
本质都是通过RabbitMq提供的Http api接口获取
GET /api/overview
返回集群范围的指标
GET /api/nodes/{node}
返回单个节点的状态
GET /api/nodes
返回所有集群成员节点的状态
GET /api/queues/{vhost}/{qname}
单个队列的指标
应用程序级指标
监控的rabbitMq系统本身指标
应用程序跟踪的指标可以是特定于系统
客户端库和框架提供了注册指标收集器或收集现成指标的方法
RabbitMQ Java客户端和Spring AMQP就是两个例子。其它开发人员必须跟踪应用程序代码中的指标。
监听堵塞消息
可以在MQ中专门创建一个监控的队列,定时的发送和消费队列中的消息,并且通过的如下的代码去监控队列是否已经堵塞,如果监听到队列已经堵塞,就立即发送告警的短信和邮件
监控检测
集群中是否有资源报警
rabbitMq是否正常运行
当前节点是否有报警
监控频率
生产系统建议收集间隔30秒-60秒
频率太高,容易对系统产生负面影响
监控数据来源
RabbitMq自带管理平台
公开了节点、连接、队列、消息速率等的RabbitMQ指标
限制
监控系统与被监控系统交织在一起
占用系统一定的开销
它只存储最近的数据(最多一天,不是几天天或几个月)
它有一个基本的用户界面
它的设计强调易用性,而不是最佳可用性
管理UI访问通过RabbitMQ权限标记系统(或JWT令牌作用域的约定)进行控制
rabbitmqctl命令
后端命令行工具
REST API
Http接口
监控工具
RabbitMQ自带的(Management插件
Prometheus & Grafana
优点
监控系统与被监控系统分离
降低服务开销
长期存储指标
方便关联聚合对比各个相关指标
更强大和可定制的用户界面
易于分享的指标数据
更健全的访问权限
针对不同节点的数据收集更具弹性
Rocketmq监控
rocketMq监控
监控系统不是很完善,目前主要依赖rokcetMq mqadmin命令行工具
监控指标数据获取
集群相关
查看集群列表
clusterList
测试集群的响应耗时
clusterRT
broker相关
获取broker的运行时状态数据
brokerStatus
获取broker的配置信息
getBrokerConfig
topic相关:
获取topic的路由信息
topicRoute
获取topic的当前状态信息
topicStatus
获取topic对应的集群信息
topicClusterList
查看集群中的topic列表
topicList
查看所有topic已经对应的consumer的消费进度
statsAll
message相关
通过message的id查询消息
queryMsgById
通过message的key查询消息
queryMsgByKey
通过message的UniqueKey查询消息
queryMsgByUniqueKey
通过偏移查询message 、
queryMsgByOffset
打印某条message的详情
printMsg
打印某个queue(队列)里的消息的详情
printMsgByQueue
查询producer的信息(socket连接,客户端版本)
producerConnection
消费相关:
查询consumer的信息(socker连接,客户端版本,消费组)
consumerConnection
查询consumer的消费进度,tps
consumerProgress
查询consumer的的内部数
onsumerStatus
查看所有topic对应的消费数据(broker的offset,consumer的offset,是否有diff)
brokerConsumeStats
测试消费发生的响应耗时
checkMsgSendRT
查询consumer指定队列和索引位置的消费信息
queryCq
nameserver相关:
获取nameserver的配置
getNamesrvConfig
acl相关:
查看集群的acl配置版本
clusterAclConfigVersion
查看acl的配置信息
etAccessConfigSubCommand
需要通过reocketMq自带的mqadmin
监控工具
1、官方提供的一个console web监控工具
本质依赖的是rokcetMq mqadmin命令行工具
2、Prometheus
将rockeMq的数据源导入到Prometheus上做相关监控
大体步骤
1、系统rocketMq服务
大致流程
1、通过 RocketMQ Exporter 工具从rokcetMq 上获取到相关的指标数据;
2、配置Prometheus 从RocketMQ Exporter 获取到对应的指标数据
kafka监控
监控实现原理
jmx
kafka官方也是提倡使用jmx并且提供了jmx的调用给用户以监控kafka.
因为kafka是java写的
监控指标
资源类型监控
实例监控
实例消息生产流量(bytes/s)
实例消息消费流量(bytes/s)
实例磁盘使用率(%)-实例各节点中磁盘使用率的最大值
topic
Topic 消息生产流量(bytes/s)
Topic 消息消费流量(bytes/s)
group监控
Group 未消费消息总数(个)
Broker 指标
Kafka-emitted 指标
未复制的分区数
UnderReplicatedPartitions(可用性)kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions
在运行正常集群中,同步副本(ISR)数量应等于副本总数。如果分区副本远远落后于 Leader,则从 ISR 池中删除这个 follower。如果代理不可用,则 UnderReplicatedPartitions 指标急剧增加。Tips:UnderReplicatedPartitions 较长时间内大于零,需要进行排查。
同步副本(ISR)池缩小/扩展的速率
IsrShrinksPerSec / IsrExpandsPerSec(可用性)kafka.server:type=ReplicaManager,name=IsrShrinksPerSec
如果某副本在一段时间内未联系 Leader 或者 follower 的 offset 远远落后于 Leader,则将其从 ISR 池中删除。因此,需要关注 IsrShrinksPerSec / IsrExpandsPerSec 的相关波动。IsrShrinksPerSec 增加,不应该造成 IsrExpandsPerSec 增加。在扩展 Brokers 集群或删除分区等特殊情况以外,特定分区同步副本(ISR)数量应保持相对稳定。
离线分区数(仅控制器)
OfflinePartitionsCount(可用性)kafka.controller:type=KafkaController,name=OfflinePartitionsCount
主要统计没有活跃 Leader 的分区数。Tips:由于所有读写操作仅在分区引导程序上执行,因此该指标出现非零值,就需要进行关注,防止服务中断。
集群中活动控制器的数量
ActiveControllerCount(可用性)kafka.server:type=ReplicaManager,name=IsrShrinksPerSec
所有 brokers 中 ActiveControllerCount 总和始终等于 1,如出现波动应及时告警。Kafka 集群中启动的第一个节点将自动成为Controller且只有一个。Kafka 集群中的Controller负责维护分区 Leader 列表,并协调 Leader 变更(比如某分区 leader 不可用)。
每秒 UncleanLeader 选举次数
UncleanLeaderElectionsPerSec(可用性)kafka.controller:type=ControllerStats,name=UncleanLeaderElectionsPerSec
在可用性和一致性之间,Kafka 默选了可用性。当 Kafka Brokers 的分区 Leader 不可用时,就会发生 unclean 的 leader 选举。当作为分区 Leader 的代理脱机时,将从该分区的 ISR 集中选举出新的 Leader。Tips:UncleanLeaderElectionsPerSec 代表着数据丢失,因此需要进行告警。
特定请求(生产/提取)用时
TotalTimeMs 作为一个指标族,用来衡量服务请求(包括生产请求,获取消费者请求或获取跟随者请求)的用时,其中涵盖在请求队列中等待所花费的时间 Queue,处理所花费的时间 Local,等待消费者响应所花费的时间 Remote(仅当时requests.required.acks=-1)发送回复的时间 Response。
传入/传出字节率
每秒请求数
Host 基础指标 & JVM 垃圾收集指标
消耗磁盘空间消耗与可用磁盘空间
页面缓存读取与磁盘读取的比率
CPU 使用率
网络字节发送/接收
JVM 执行垃圾回收进程总数
JVM 执行垃圾收集进程用时
Producer 指标
每秒收到的平均响应数
每秒发送的平均请求数
平均请求等待时长
每秒平均传出/传入字节数
I / O 线程等待的平均时长
每个分区每个请求发送的平均字节数
Consumer 指标
Consumer 在此分区上滞后于 Producer 的消息数
特定 Topic 每秒平均消耗的字节数
特定 Topic 每秒平均消耗的记录数
Consumer 每秒获取的请求数
监控工具
Kafka Manager
Kafka Eagle
Logi-KafkaManager
消息队列监控实现原理
rabbitMq
监控工具实现
RabbitMq自带管理平台
公开了节点、连接、队列、消息速率等的RabbitMQ指标
限制
监控系统与被监控系统交织在一起
占用系统一定的开销
它只存储最近的数据(最多一天,不是几天天或几个月)
它有一个基本的用户界面
它的设计强调易用性,而不是最佳可用性
管理UI访问通过RabbitMQ权限标记系统(或JWT令牌作用域的约定)进行控制
rabbitmqctl命令
后端命令行工具
REST API
直接通过接口查询各种相关的监控信息
RocketMQ
mqadmin–提供一套命令行工具
数据库监控
mysql监控
java监听Mysql数据变化
具体步骤
1、修改配置
log-bin=mysql-bin #[必须]启用二-进制日志
server-id=100 #[必须]服务器唯一ID,如果是用于多台MySQL,ID不要重复就行
binlog-format = ROW
server-id=100 #[必须]服务器唯一ID,如果是用于多台MySQL,ID不要重复就行
binlog-format = ROW
binlog-format = ROW,binlog_format 设置为 ROW可以保证数据的一致, 因为在 STATEMENT 或 MIXED 模式下, Binlog 只会记录和传输 SQL 语句(以减少日志大小),而不包含具体数据,我们也就无法保存了。
2、引入依赖包
<dependency>
<groupId>com.zendesk</groupId>
<artifactId>mysql-binlog-connector-java</artifactId>
<version>0.27.1</version> <!--2022.09.17版的-->
</dependency>
<groupId>com.zendesk</groupId>
<artifactId>mysql-binlog-connector-java</artifactId>
<version>0.27.1</version> <!--2022.09.17版的-->
</dependency>
3、具体代码demo
//为什么甚至路径都一样,还是com.github.shyiko.***,
// 因为com.zendesk这个包,里面包了个com.github.shyiko.***这玩意,我怀疑是收购关系
import com.github.shyiko.mysql.binlog.BinaryLogClient;
import com.github.shyiko.mysql.binlog.event.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
//此类可以监控MySQL库数据的增删改
@Component
@Slf4j //用于打印日志
//在SpringBoot中,提供了一个接口:ApplicationRunner。
//该接口中,只有一个run方法,他执行的时机是:spring容器启动完成之后,就会紧接着执行这个接口实现类的run方法。
public class MysqlBinLogClient implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
//项目启动完成连接bin-log
new Thread(() -> {
connectMysqlBinLog();
}).start();
}
/**
* 连接mysqlBinLog
*/
public void connectMysqlBinLog() {
log.info("监控BinLog服务已启动");
//自己MySQL的信息。host,port,username,password
BinaryLogClient client = new BinaryLogClient("localhost", 3306, "root", "root");
/**因为binlog不是以数据库为单位划分的,所以监控binglog不是监控的单个的数据库,而是整个当前所设置连接的MySQL,
*其中任何一个库发生数据增删改,这里都能检测到,
*所以不用设置所监控的数据库的名字(我也不知道怎么设置,没发现有包含这个形参的构造函数)
*如果需要只监控指定的数据库,可以看后面代码,可以获取到当前发生变更的数据库名称。可以根据名称来决定是否监控
**/
client.setServerId(100); //和自己之前设置的server-id保持一致,但是我不知道为什么不一致也能成功
//下面直接照抄就行
client.registerEventListener(event -> {
EventData data = event.getData();
if (data instanceof TableMapEventData) {
//只要连接的MySQL发生的增删改的操作,则都会进入这里,无论哪个数据库
TableMapEventData tableMapEventData = (TableMapEventData) data;
//可以通过转成TableMapEventData类实例的tableMapEventData来获取当前发生变更的数据库
System.out.println("发生变更的数据库:"+tableMapEventData.getDatabase());
System.out.print("TableID:");
//表ID
System.out.println(tableMapEventData.getTableId());
System.out.print("TableName:");
//表名字
System.out.println(tableMapEventData.getTable());
}
//表数据发生修改时触发
if (data instanceof UpdateRowsEventData) {
System.out.println("Update:");
System.out.println(data.toString());
//表数据发生插入时触发
} else if (data instanceof WriteRowsEventData) {
System.out.println("Insert:");
System.out.println(data.toString());
//表数据发生删除后触发
} else if (data instanceof DeleteRowsEventData) {
System.out.println("Delete:");
System.out.println(data.toString());
}
});
try {
client.connect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 因为com.zendesk这个包,里面包了个com.github.shyiko.***这玩意,我怀疑是收购关系
import com.github.shyiko.mysql.binlog.BinaryLogClient;
import com.github.shyiko.mysql.binlog.event.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
//此类可以监控MySQL库数据的增删改
@Component
@Slf4j //用于打印日志
//在SpringBoot中,提供了一个接口:ApplicationRunner。
//该接口中,只有一个run方法,他执行的时机是:spring容器启动完成之后,就会紧接着执行这个接口实现类的run方法。
public class MysqlBinLogClient implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
//项目启动完成连接bin-log
new Thread(() -> {
connectMysqlBinLog();
}).start();
}
/**
* 连接mysqlBinLog
*/
public void connectMysqlBinLog() {
log.info("监控BinLog服务已启动");
//自己MySQL的信息。host,port,username,password
BinaryLogClient client = new BinaryLogClient("localhost", 3306, "root", "root");
/**因为binlog不是以数据库为单位划分的,所以监控binglog不是监控的单个的数据库,而是整个当前所设置连接的MySQL,
*其中任何一个库发生数据增删改,这里都能检测到,
*所以不用设置所监控的数据库的名字(我也不知道怎么设置,没发现有包含这个形参的构造函数)
*如果需要只监控指定的数据库,可以看后面代码,可以获取到当前发生变更的数据库名称。可以根据名称来决定是否监控
**/
client.setServerId(100); //和自己之前设置的server-id保持一致,但是我不知道为什么不一致也能成功
//下面直接照抄就行
client.registerEventListener(event -> {
EventData data = event.getData();
if (data instanceof TableMapEventData) {
//只要连接的MySQL发生的增删改的操作,则都会进入这里,无论哪个数据库
TableMapEventData tableMapEventData = (TableMapEventData) data;
//可以通过转成TableMapEventData类实例的tableMapEventData来获取当前发生变更的数据库
System.out.println("发生变更的数据库:"+tableMapEventData.getDatabase());
System.out.print("TableID:");
//表ID
System.out.println(tableMapEventData.getTableId());
System.out.print("TableName:");
//表名字
System.out.println(tableMapEventData.getTable());
}
//表数据发生修改时触发
if (data instanceof UpdateRowsEventData) {
System.out.println("Update:");
System.out.println(data.toString());
//表数据发生插入时触发
} else if (data instanceof WriteRowsEventData) {
System.out.println("Insert:");
System.out.println(data.toString());
//表数据发生删除后触发
} else if (data instanceof DeleteRowsEventData) {
System.out.println("Delete:");
System.out.println(data.toString());
}
});
try {
client.connect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
参考文档
https://blog.csdn.net/qq_45821251/article/details/127490460
https://blog.csdn.net/chengsw1993/article/details/119328869
说明
其实这个最大的作用还是主从备份数据;对实际上的mysql自身的基础资源监控几乎没有任何帮助;可以在应用监控,和业务监控上做一些文章。但是其实如果要在应用监控,业务监控用这种方式去做会显得很重,其实可以直接在java应用中对sql语句进行拦截然后做统计,没必要还要通过主从备份数据来做;
指标
基础资源监控
库表索引空间大小监控
数据来源
数据库都有一个核心的information_schema元数据库,元数据库中间会有存储库表基本信息,这些基本信息会记录相关的库表索引数据大小
information_schema库TABLES表
TABLE_SCHEMA : 数据库名
TABLE_NAME:表名
ENGINE:所使用的存储引擎
TABLES_ROWS:记录数
DATA_LENGTH:数据大小
INDEX_LENGTH:索引大小
TABLE_NAME:表名
ENGINE:所使用的存储引擎
TABLES_ROWS:记录数
DATA_LENGTH:数据大小
INDEX_LENGTH:索引大小
具体指标
表所占空间大小
某个表所占用空间大小
select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from TABLES where table_schema='jishi' and table_name='a_ya';
索引所占用空间大小
SELECT CONCAT(ROUND(SUM(index_length)/(1024*1024), 2), ' MB') AS 'Total Index Size' FROM TABLES WHERE table_schema = 'test' and table_name='a_yuser';
索引占表的空间大小比例
某个库所占空间大小
select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from TABLES where table_schema='jishi';
所有的库所占用的大小
select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from TABLES;
某个库索引大小
SELECT CONCAT(ROUND(SUM(index_length)/(1024*1024), 2), ' MB') AS 'Total Index Size' FROM TABLES WHERE table_schema = 'jishi';
连接数线程监控
方式
show status 命令
显示mysql运行时状态连接数相关指标
extended-status 命令
显示mysql运行时状态
具体指标
当前已经连接数量
Threads_connected
试图连接到(不管是否成功)MySQL服务器的连接数
Connections
服务器启动后已经同时使用的连接的最大数量
Max_used_connections
由于客户没有正确关闭连接已经死掉,已经放弃的连接数量
尝试已经失败的MySQL服务器的连接的次数
当执行语句时,已经被创造了的隐含临时表的数
正在使用的延迟插入处理器线程的数量
用INSERT DELAYED写入的行数
用INSERT DELAYED写入的发生某些错误(可能重复键值)的行数
执行FLUSH命令的次数
请求从一张表中删除行的次数
请求读入表中第一行的次数
请求数字基于键读行
请求读入基于一个键的一行的次数
请求读入基于一个固定位置的一行的次数
请求更新表中一行的次数
请求向表中插入一行的次数
用于关键字缓存的块的数量
请求从缓存读入一个键值的次数
从磁盘物理读入一个键值的次数
请求将一个关键字块写入缓存次数
将一个键值块物理写入磁盘的次数
同时使用的连接的最大数目
在键缓存中已经改变但是还没被清空到磁盘上的键块
在INSERT DELAY队列中等待写入的行的数量
打开表的数量
打开文件的数量
打开流的数量(主要用于日志记载)
已经打开的表的数量
发往服务器的查询的数量
要花超过long_query_time时间的查询数量
当前打开的连接的数量
不在睡眠的线程数量
服务器工作了多少秒
参考文档
https://blog.csdn.net/weixin_36123300/article/details/113236131
子主题
子主题
监控工具
SigNoz
Prometheus
MySQL Enterprise Monitor
MySQL 企业版附带了 MySQL 企业级监视器
特性
基于云的远程监控
查询分析可视化
支持 MySQL 集群监控
实时健康状态上报、可用性监控
易于配置
Paessler PRTG Network Monitor
Sematext
Solarwinds
缓存监控
redis监控
监控的实现
redis自带的info命令和monitor命令的相关信息
redis监控平台
redis-faina
redis自带命令monitor的输出结果做分析的python脚本
redis-live
RedisLive是一款用Python编写的Redis图形监控工具
原理
监控脚本来利用Redis提供的MONITOR命令从被监控Redis实例中获取数据并存储到Redis的监控实例中来做数据分析
RedisLive以可视化的方式展示了Redis实例中的数据,分析查询模式和峰值
监控插件
Munin插件
nagios
redis监控指标
连接检测
连接失败检测
客户端连接数
执行 info clients 命令获取 connected_clients 就是客户端连接数
吞吐量监控
info stats
此命令可以查询吞吐量相关的监控项
具体指标
总执行命令数量
从 Redis 启动以来总计处理的命令数,可以通过前后两次监控组件获取的差值除以时间差,以得到 QPS
total_commands_processed
当前 Redis 实例的 OPS
instantaneous_ops_per_sec
网络总入量
total_net_input_bytes
网络总出量
total_net_output_bytes
每秒输入量,单位是kb/s
instantaneous_input_kbps
每秒输出量,单位是kb/s
instantaneous_output_kbps
内存监控
已使用内存大小
used_memory
通过 info memory 获取,表示 Redis 真实使用的内存
最大内存大小
maxmemory
通过 config get maxmemory 获取。
内存碎片
计算方式:used_memory_rss/used_memory
两个参数均通过 info memory 获取
大于 1 表示有内存碎片,越大表示越多;小于 1 表示正在使用虚拟内存,虚拟内存其实就是硬盘,性能会下降很多。一般内存碎片率在 1 - 1.5 之间比较健康。
持久化监控
持久化可以防止数据丢失
相关参数均为执行完 info Persistence 的结果
上一次 RDB 持久化状态
rdb_last_bgsave_status
上一次 RDB 持久化的持续时间
rdb_last_bgsave_time_sec
查看 AOF 文件大小
aof_current_size
key监控
大key
什么是大key
单个key对应的value值非常大
监控方式
redis自带的redis-cli --bigkeys
作用
可以找出五种数据类型中各自最大的key;
缺点
不能帮助我们找到整个数据里的最大的key
一般不适用该种方式监控
通过脚本或者命令扫描所有的key
缺点
结果的准确性不高,集合类型的扫描只能扫描出集合的长度,而不是value的大小
还会阻塞redis正常的执行
解析rdb文件
通过解析工具解析rdb文件,获取大key
常用解析工具
很多第三方的解析工具
通过代码aop的方式,异步每一次key的大小
热key
监控方式
redis自带的redis-cli --hotkeys命令
缺点
必须使用Least Frequently Used 最近最少使用的缓存淘汰策略
执行命令的途中肯定会阻塞正常的redis命令的执行
自建对热key监控
客户端监控
每一个链接redis的客户端,都做相应的热key监控
代理端监控
监控示意图
子主题
服务端监控
监控示意图
子主题
原理
利用monitor命令的结果就可以统计出一段时间内的热点
key排行榜、命令排行榜、客户端分布
key排行榜、命令排行榜、客户端分布
缺点
monitor命令的执行影响正常的reids命令的执行
只能统计单机的热key
机器
监控示意图
四种方式优缺点对比
慢查询监控
慢查询涉及参数
执行时间超过了多久,会被记录到慢查询日志中
slowlog-log-slower-than
默认10000微妙
慢日志的长度
当慢查询日志达到最大条数时,如果有新的慢查询,会移除最大的慢查询。
slowlog-max-len
具体命令
slowlog get
查询所有慢查询
slowlog get 1
显示最新的一条慢查询
集群监控
具体指标
集群状态
cluster_state
hash槽状态
cluster_slots_fail
节点数量
cluster_known_nodes
具体命令
cluster info 命令中可以获取集群的状态
缓存命中率
HitRate = keyspace_hits / (keyspace_hits + keyspace_misses)
keyspace_hits 表示 Redis 请求键被命中的次数
keyspace_misses 表示 Redis 请求键未被命中的次数
info stats 可以获取两个指标
建议缓存命中率不要低于90%,越高越好
命中率越低,导致越多的缓存穿透,对mysql容易造成影响
redis监控实现原理
redis自带的info命令和monitor命令的相关信息
线程池监控
线程池自带查询当前线程状态的方法
线程池中正在执行任务的线程数量
getActiveCount()
线程池已完成的任务数量,该值小于等于taskCount
getCompletedTaskCount()
线程池的核心线程数量
getCorePoolSize()
线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过,也就是达到了maximumPoolSize
getLargestPoolSize
线程池的最大线程数量
getMaximumPoolSize
线程池当前的线程数量
getPoolSize
线程池已经执行的和未执行的任务总数
getTaskCount
线程池动态修改参数
具体可修改参数
核心线程数
ThreadPoolExecutor的api setCorePoolSize(int corePoolSize)
最大线程数
ThreadPoolExecutor的api setMaximumPoolSize(int maximumPoolSize)
等待队列大小
自定一个阻塞队列继承阻塞队类,然后在队列长度字段设置可以做修改。此队列作为线程池的阻塞队列
是否自动扩容
都是需要在线程池的运行期做相关的操作进行修改
监控任务
任务分类
执行任务前后全量统计任务排队时间和执行时间
定时任务,定时获取活跃线程数,队列中的任务数,核心线程数,最大线程数等数据
线程池告警
告警指标
任务超时时间告警阈值
任务执行超时时间告警阈值
等待队列排队数量告警阈值
线程池定时监控时间间隔
告警实现
1、ThreadPoolExecutor线程池中beforeExecute和afterExecute方法是空实现
2、自定义线程池继承线程池类,实现beforeExecute和afterExecute方法
3、通过线程池的beforeExecute和afterExecute方法队线程池相关可查询参数进行监控
4、当相关的监控参数达到了告警阈值,那么就做出相关的告警操作;
概述
针对各种应用的各种指标的监控,本质上还是需要被监控的应用可以提供相关的指标查询接口,或者是通过二次开发能够通过对代码的监控可以获取或者计算处相关的监控指标;
prometheus之所以可以监控得到应用的相关监控数据信息,本质上还是得通过应用本身有这种指标查询接口,或者通过二次开发获取得到。exporter导出器,就是通过对这些接口的调用获取数据,然后将数据格式转成prometheus所需要的格式;
前沿
领域渠道设计
实践
比如说一个商品的信息储存
一般设计
需要把商品相关的属性都放在一个表
DDD设计
将商品的价格对应的信息,商品物流相关的信息,分别放在不同的业务实体模型中去;
子主题
子主题
和一般开发区别
一般开发都是由下至上的开发
先设计表,
DDD开发都是从上至下
云原生
云原生应用
跨编程语言使用整合成同一套微服务架构
将不同的语言的应用整理成像spring cloud 那样的微服务架构
k8s+service mesh
Service mesh
服务网格
运维
常用命令
k8s命令
1、模糊查询节点
kubectl get pods -nsite -owide | grep social
模糊查询 节点名称中带有 socail的节点详细信息
得到的信息
microservice-payment-server-59cdc58dcc-fd7sw 1/1 Running 0 40m 10.39.0.51 k8s-test-worker-84 <none> <none>
可以看到对应的ip信息,运行状态
k8s命令操作docker
k8s是在docker基础上又封装了一层
k8s命令操作
常用linux命令
1、查看日志命令
1、查看所有日志
tail -f 日志文件名称
2、精确搜索查询日志
cat default.log | grep 'my name'
查询日志信息中带有my name信息的日志
default log 是日志文件名称呢过
zcat default.log.2022-12-07_00.0.gz | grep 'my name'
从日志的压缩文件中查询带有my name信息的日志
default.log.2022-12-07_00.0.gz 日志压缩文件
3、生成文件命令
touch test.log
test.log 新建文件名称
docker常用命令
docker iamges
查看当前所有的镜像
docker search 镜像名
搜索镜像
docker ps
查看当前docker容器下运行了哪些镜像
拉取镜像
docker pull mysql
默认会拉取最新的镜像
docker pull mysql:5.7.30
指定拉取版本
运行镜像
docker run mysql
docker run mysql:tag
指定运行的镜像版本
删除镜像
docker rmi -f 镜像名/镜像id
命令参考文档
https://blog.csdn.net/leilei1366615/article/details/106267225
docker
基础
仓库dockerhub
docker远程仓库,类似于maven仓库,所有的docker软件的下载都是通过从远程的docker下载
dockerhub可以类似于maven一样构建自己私有的dokcer仓库
dockerhub可以将自己的包上传到dockerbub上,所有人都可以下载使用
如何上传本地包到docker仓库?
镜像 images
docker 镜像可以理解成maven的本地仓库,所有的从dockerhub上下载的软件都是存放在本地docker 镜像仓库
通过images 中的镜像文件来创建docker容器运行环境
image 存储位置
存储位置取决于images文件的保存形式
images有四种保存形式
远程镜像仓库保存
本地保存
此种方式,images保存在本地
镜像时保存方式
容器运行时保存方式
容器 container
docker从物理机上虚拟化出来的一个程序运行环境,容器与容器之间是相互隔离的
dokcer有势
docker compose技术
docker容器编排技术
解决的痛点问题
生产环境中的每一次的发布都需要拉取镜像,启动容器,如果需要启动很多歌容器,那么就是一个非常费时费力的事情,所以就诞生了docker compose技术,
通过compoase,可以使用YML文件配置中创建并启动所有的服务
compose实现的快速编排站在项目角度将一组相关的容器整合在一起,对这组容器按照指定顺序进行启动,也让项目使用到的容器组合更加清晰,方便不同服务器之间的迁移部署
k8s
k8s自动化部署扩展管理运维多个跨机器docker程序的集群
k8s核心特性
自动装箱
自动根据物理机的资源环境来配置容器相关的运行环境
自我修复
容器失败会自动重启
部署节点有问题时候,会对容器进行重新部署和重新调度
当容器未通过监控检查时,会关闭此容器直到容器正常运行时,才会对外提供服务。
水平扩展
通过简单的命令、用户UI界面或基于CPU等资源使用情况,对应用容器进行规模扩大或规模裁剪
服务发现
用户不需要使用额外的服务发现机制,就能够基于k8s自身能力实现服务发现和负载均衡
基于service这个统一入口对外提供服务、完成服务,实现服务发现和pod内部的负载均衡
滚动更新
可以根据应用的变化,对应用容器运行的应用进行一次性或批量式更新
等应用更新的时候会先检测之前更新是否成功,成功则进行下一个应用的更新
版本回退
可以根据应用部署情况,对应用容器运行的应用,进行历史版本即时回退
密钥和配置管理
在不需要重新构建镜像的情况下,可以部署和更新密钥和应用配置,类似热部署
存储编排
自动实现存储系统挂载及应用,特别对有状态应用实现数据持久化非常重要。存储系统可以来自于本地目录、网络存储(NFS、Cluster、Ceph等)、公共云存储服务
批处理
提供一次性任务、定时任务;满足批量数据处理和分析的场景
系统架构
大型项目技术架构
架构模式
分层
将controller,servie,dao层数据分开处理
分割
将同一模块的数据相关的数据进行操作,将整个项目分成多个小模块,高内聚低耦合
分布式
分布式引用服务,分布式静态资源,分布式数据和存储,分布式计算(分配到多台服务器上进行计算);
集群
分层,分割后的模块单独部署成一个服务集群;
缓存
cdn;反向代理;本地缓存;分布式缓存;
异步
提高系统可用性,加快网站相应速度,消除并发访问高峰;
冗余
就是服务器在宕机的情况下,网站依旧是可用的,可以继续服务;
需要一定数量的冗余服务器,以及冗余的数据备份,需要数据定期进行备份,存档。(冷备份)
架构核心要素
高性能
性能指标
1.响应时间
单个请求需要的时间
2.并发数
3.吞吐量
单位时间内系统能处理的请求的数量
4.性能计数器
不同视角下的性能
1.用户视角下的性能
2.开发人员视角下的性能
3.运维视角下的性能
性能优化
前端优化
1.减少http请求;
2.使用浏览器缓存;
3.对文件内容进行压缩;
4.减少cookie的传输;
5.css代码放在js代码上面;
服务器优化
1.cdn加速;
2.反向代理服务器缓存;
后端优化
代码层面优化
1.消息队列,使用异步操作,节省时间;
2.开多线程跑程序;
3.资源复用:连接池,线程池,常量池什么的都用起来;
4.设计查询,存储效率比较高的数据结构;
5.jvm的参数设置,减少垃圾回收时间次数;
分布式缓存
缓存热点数据
存储优化
1.固态硬盘比机械硬盘读写速度快
2.存储使用B+树,lsm树。
3.廉价磁盘冗余数据;
可用性
伸缩性
扩展性
水平扩展
水平扩展就是增加服务器的个数,降低服务器价格的成本
垂直扩展
垂直扩展:就是提高服务器本身的性能,服务器个数就极少
安全性
常用架构设计
1.应用服务器和数据服务分离;引申出来需要将文件服务器也分离出来
2.使用缓存改善网络性能
缓存也分为:缓存在应用服务器上缓存,远程分布式服务器上的缓存;
其调用顺序是:应用服务器现在自己本地的缓存中寻找数据,如果应用服务器本地没有数据,那么就会去远程分布式文件服务器上寻找;
3.使用负载均衡调度服务器,也就是nigx;进行调度,如果请求量数量比较大,使用f5硬件再对nigx最负载均衡了;
4.数据库读写分离:使用了缓存之后,大多数数据库的读操作都可以不通过数据库就能完成,但是依旧会有一部分读操作(没有命中缓存的,缓存过期的)和全部的写操作需要访问数据库;
方式:通过主从热备功能,通过配置两台数据库主从关系,通过通过一台数据库服务器的数据同步到另外一台服务器上。通过这一功能实现数据库读写分离。
写操作:访问的是主数据库,主数据库将通过主从复制将数据更新到从数据库上;
读操作:访问的从数据库,因为写操作的数据已经复制到从数据库上了;
5.使用反向代理和CDN加速
将静态资源部署在CDN服务器上,反向代理服务器存放着用户需要的很多信息,可以直接返回这些数据给用户
缓存架构设计
概述
缓存本身就是一个内存io密集型应用
缓存是通过牺牲强一致性来提高性能的
80%的缓存命中率就算很高了。
缓存使用原则
如果业务允许不是很实事的数据情况下,可以使用缓存解决
设计重点
1、提高缓存命中率
2、缓存时间周期
过长,缓存长期没有被访问,浪费内存
过段,需要不断将缓存数据从数据库添加到内存
3、缓存淘汰算法
需要匹配当前系统的最佳的缓存淘汰策略
4、设计缓存管理系统
与程序的缓存系统分开,但是可以控制程序的缓存数据情况
此情况相当于是在紧急情况下给缓存添加的一个后门操作
缓存架构的问题
缓存穿透
缓存击穿
缓存雪崩
热点缓存
并发更新-数据一致性
数据库架构
出于灾难备份目的,数据库都会建设两个或者多个数据中心
数据中心之间关系
热备
主数据中心承担用户业务,备数据中心对主数据中心进行备份
主数据中心挂了,备数据中心接管主数据中心业务
冷备
主数据中心承担用户业务,备数据中心不会对主数据中心进行实时备份,可能周期性备份,或者干脆不备份
如果主数据中心挂了,用户业务也会断掉
双活
主备两个数据中心同时承担用户业务,数据相互为备份,且主数据中心承担大多数业务。
两地三中心
两地
本地+异地
三中心
本地中心+本地容灰中信+异地备份中心
主数据中心/灾备数据中心,
双运营数据中心
双活数据中心
分布式
分布式系统解决方案
超时订单处理
超时订单:就是延迟任务
解决方案
1.数据库轮询,查数据库的表,查看转台;
2.DelayQueue 延迟队列,把这些任务都放在延迟队列中
代码中不断的轮询,延迟队列中超时的任务就会被poll方法
拿出来执行;基本都是用while(true)这种死循环去扫描这些超时的任务;
缺点:都只是存储在内存中,集群模式下,单机只能处理单机的任务;
3.循环队列
缺点和方法2的缺点是一样的;
4.redis 的zSet数据结构;让Zset这个数据按照超时时间排序
也是循环遍历要超时的任务,然后把这个任务做了,然后再把这个任务删除从Zset中删除;这些方法Jedis这些都是用方法的;
优点:解决集群模式下的任务;
存在的问题:
1.数据量大了,也会造成性能问题;
2.zset集合中的数据越来越多的时候,zset的性能也会越来越低
5.把数据存储redis的set集合中,key是时间戳,value是这个时间戳产生的数据集合
一秒钟查询一次,然后才处理这一秒钟产生的订单信息;
缺点:已经超时的订单将得不到处理;1秒内无法处理完这些数据,那么就会超时,下面就会有没有处理的订单
全局唯一id生成
分布式Id要求
全局唯一
全局业务中必须要是唯一
趋势递增
这个id最好是能递增
高性能
高可用
接入方便
全局唯一id生成方式
UUID
优点
简单
无网路消耗
缺点
无业务意义
不是递增
存储消息空间大
UUID作为数据库主键,性能低下
使用场景
数据库实现方式
数据库自增id
方式概述
获取数据库自增的主键作为全局ID
优点
实现简单
id是递增
数值类型查询速度快
缺点
如果数据是单节点,无法完全保证数据的高可以用
数据库多主模式
方式概述
给几台数据库服务器自增时候设置相应的步长(比如说,自增3,自增4),获取到自增id
优点
解决单节点,并不能高可用问题
缺点
不利于后续扩容
号段模式
方式概述
从数据库批量获取自增ID
优点
批量减少数据库压力
号段方式是推荐的使用方式
ZK
实现概述
通过node节点版本生成序列号
可以生成32位,64位
redis
实现概述
利用的incr命令实现原子的自增
雪花算法
实现概述
正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 数据中心(占5比特)+ 自增值(占12比特),总共64比特组成的一个Long类型。
百度uid-generator
和雪花算法类似
优点
可自定义时间戳,机器ID,序列号
美团Leaf
同时支持号段模式,雪花算法模式,可以切换
滴滴(Tinyid)
号段模式的实现
分布式锁
数据库乐观锁
操作步骤
1.先去查询数据是否存在
2.在做更新的时候对比当前数据的版本是否和第一次查询的时候版本号一致,一致才更新;
原理
基于第一次查询和第二次做修改时候查询的数据来判断中间的数据有没有被修改过
mysql实现
利用在表中加入一个版本号
做修改操作钱先去表中查询,获取到相应的数据和版本号信息
在做更新的时候,拿之前查询到的版本和数据库当前的版本号对比,是否相同,如果相同才让做修改操作;否则就表示这个数据已经被其他线程修改过了,就不让做修改操作;
Redis分布式锁
使用redisson连接方式
操作步骤
1.在try catch外面获取锁
通过RedisClient对象获取到RedissionLock对象
2.在try catch中加锁
通过RedissionLock对象进行加锁
分支主题
3.在finally里面解锁(防止死锁,解不了锁)
原理
因为redis的crud都是单线程的,通过想Redis中设置key,value的方式
设置成功,说明当前就获取到了Redis的锁
设置失败,说明其他请求已经向Redis中设置过了当前值,其他线程获取到了锁,当前线程会阻塞等待获取锁;
Redis的SETNX命令给向reids中添加键值对,key不存在就是添加成功,key存在就添加失败
1、添加成功相当于获取到redis锁
2、添加失败相当于未获取到redis锁
3、redis删除该key,相当于释放了锁
Lua脚本加锁过程
1.先判断key是否存在,不存在才会加锁;
2.如果key存在,那么通过lua脚本判断,获取锁的客户端id是否和当前客户端的id相同,也就是进行可重入判断;
3.如果客户端id也不一样,那么会返回一个当前获取锁需要等待的时间,并且进入while死循环中等待获取锁;
设计需要注意的点
适时释放锁
1、锁的释放操作需要放在finnaly代码中;
未设置,程序异常,导致当前代码永远死锁
2、给key设置超时时间;(相当于设置锁的有效期时常)
防止获取到锁的程序,由于某种原因一直不释放锁,阻塞后续线程执行
超时时间设置
超时时间需要足够长,来确保执行的任务的时间远远小于超时时间
确保sentx命令还有设置超时命令是同一个原子操作;
该操作由redis提供相关的操作命令
防止sentx命令执行完成,再去执行设置超时时间中间,reids宕机;
加锁,解锁两个操作都是同一个线程
防止,自己设置的锁被其他业务误删;
锁需要具备可重入性
子主题
锁需要具备阻塞和非阻塞特性
原因
获取不到锁的线程,需要让这些线程做获取的锁的等待从而可以执行锁中间的代码,而不是直接返回到客户端
解决方式
死循环不断的尝试获取锁操作,获取到锁操作后,跳出死循环;
锁失效
原因
锁失效后,其他线程可以直接获取到锁对象,两个线程同时执行锁中间的代码,无法保证串行执行;
解决方式
需要给锁有效期续命
高并发分布式锁设计
核心思想
分而治之,类ConcurrentHashMap种的分段锁
将需要被并发访问的数据拆分成多份,每一份数据对应设置一个分布式锁
案例
高并发下秒杀商品,商品库存有1000个,将商品库存缓存到10个key,value中,每一个key value的缓存只有100个,这样并发度就是原来的10倍
特别注意:
当获取的key对应的库存数据为0时候,需要释放当前锁,且需要再次获取其他库存不为0的锁;
分布式锁使用场景
1、批量操作防止重复操作
背景说明
运营后台需要针对线下商品的批量操作,一个商品可以绑定200-300个门店,批量100个商品就会有2W-3W条数据更新
2W-3W条数据,使得程序响应过慢,运用以为操作失败,然后接着继续狂点,导致服务器内存溢出宕机;
解决方式
方式1
1、获取到批量操作的商品id,按照大小排序,获取对应的uuid值,作为key,也就是redis的锁
2、后面重复操进来的数据,发现锁已经被获取,就放弃执行当前操作,显示已经在执行中;
2、显示抢购商品秒杀活动
背景说明
以鸡蛋,大白菜,食盐等商品做1毛钱秒杀活动,达到线上引流目的
解决方式
1、初始化分段锁信息
key为商品id,value为hash表,hash表中的字段就是各个子锁key,值为库存数据
2、用原子类的Long记录请求请求数量
3、对请求数量按照锁的个数取模,以此来确定需要访问具体的是哪个子锁
4、尝试sentx操作,key为子锁key,为第三步中获取的value为库存
5、获取到锁之后,执行业务代码,然后在最后更新hash表中的库存数据,也更新当前子锁对应库存数据
6、最后解锁
7、未获取到锁的线程,在做死循环自旋的时候,每次sentx操作之前都是需要获取hash表中库存数据;确保获取的库存数据是最新数据
ZK分布式锁
操作步骤
1.通过zkClient对象创建临时节点,作为加锁
2.通过zkClient对象删除临时节点,作为解锁
原理
1.zk是不允许创建相同的临时节点
2.如果zkclient对象发现已经相同的临时节点已经存在了,那么就会阻塞被阻塞;
3.阻塞实际上就是等待创建了这个节点的客户端去删除这个临时节点;
4.阻塞结束之后,创建临时节点,就获取到了锁
本质
zk不允许重复创建临时节点
有创建临时节点,就会客户端就会被阻塞
分布式锁对比
子主题
分布式缓存
Redis与MemCache的区别
线程操作
redis数据处理是单线程,memcache是多线程处理
数据结构
Redis支持更多更复杂的数据结构,memcache只支持keyvalue的字符串数据;
数据安全性
Redis支持数据的持久化,会把数据同步到磁盘上;memcache不支持数据的持久化
数据备份
Redis支持数据备份,需要开启主从模式;memcache不支持数据备份
过期策略
REDIS支持更多的过期策略;memcache支持的过期策略少
分布式事物
分布式事物概述
CAP
Consistency
强一致性
Availability
可用性
Partition tolerance
分区容错性
CAP只能保证两点
这个理论中的一致性指强一致性,而不是弱一致性
CAP选择
一般情况下保证AP和最终一致性
金融业,CA,强一致性+可用性
seata解决方案
集成了4中分布式事物解决方案
AT 模式、TCC 模式、Saga 模式和 XA 模式
分布式事物模型
AT模式
特点
事物的第一阶段其实已经提交了;事物的回滚操作是在原来正向操作的基础上做的一个逆向操作,这个逆向操作seata已经帮你做了;
原来的一个insert操作变成delete操作;delete操作变成insert操作;update修改操作会修改回来;
解决方案
seata
使用场景
对代码无侵入性的分布式解决方案
AT事物特点
业务无需编写各类补偿操作,回滚由框架自动完成
TCC模式
使用场景
适用于那些需要处理复杂业务流程、同时涉及多个独立事务单元、具有复杂性和不确定性的场景
对性能有很⾼要求的场景
高性能分布式事物的解决方案
TCC设计
基于2pc协议
TCC概述
事物3个操作
Try
预留业务资源
Confirm
确认执行提交事物
Cancel
执行事物回滚
TCC可以分成两个阶段
资源预留阶段
事物提交阶段/事物回滚阶段
TCC事物实现方式
Hmily轻量级分布式事务的框架, 无需搭建Hmily服务器
需要标记Try,confirm,cancel方法
Seata
ByteTCC
EasvTransaction
TCC事物特点
并发度较高,无长期锁定事物资源
开发量较大,需要自己提供try,confirm,cancel接口
一致性好
TCC适用于订单类业务,对中间状态有约束
XA模式
使用场景
同一项目中直连两个数据库,做修改操作
实现原理
mysql oracle等关系型数据都是支持两阶段提交协议的,也就是有一个支持多个事物的接口;
两阶段提交主要保证了分布式事务的原子性:即所有结点要么全做要么全不做)
主流数据库:mysql、oracle、sqlserver、postgre都是支持XA事物
备注:主要是这些数据库都遵守2PC协议
依赖于数据库支持XA事物模型
理论基础
2PC协议
第一阶段每个事物完成预提交并通知把结果告诉事物协调器;事物协调器等所有分支事物都操作完成,预提交之后;第二阶段协调器通知每个事物进行逐步的提交或者是逐步的回滚操作;
二阶段详细过程
准备阶段
事物协调器向所有的事物发起者询问是否可以执行提交操作,并等待各个参与者节点的响应
参与者节点执行询问发起为止的所有事物操作,并记录到redo,undo日志中;
事物的各个参与者对事务管理器响应自己的事物是执行成功还是执行失败;
提交阶段
协调者只要收到一个事物失败或者超时,那么就会给每一个参与者发送回滚消息否则发送提交消息;
事物过程
第一阶段每个事物完成预提交并通知把结果告诉事物协调器;事物协调器等所有分支事物都操作完成,预提交之后;第二阶段协调器通知每个事物进行逐步的提交或者是逐步的回滚操作;
特点
事物粒度大,高并发环境下,系统可用性比较低
解决方案
1、AtomMikos
使用方式
springBoot整合atomikos
<!-- atomikos 多数据源管理 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jta-atomikos</artifactId></dependency>
spring整合atomikos
<dependency><groupId>com.atomikos</groupId><artifactId>transactions-jdbc</artifactId><version>4.0.6</version></dependency>
无整合开发
<dependency><groupId>com.atomikos</groupId><artifactId>transactions-jdbc</artifactId><version>4.0.6</version></dependency>
2、finnally里面做事物回滚
使用场景
当前系统调用了内部的多个数据库,或者是多个服务,在调用方这边直接做数据回滚;
XA模式优缺点
XA模式是分布式强一致性的解决方案;
简单易理解,开发较容易
对资源进行了长时间的锁定,并发度低
saga
saga事物理论基础
将长事物拆分成多个本地事物,由Saga事物协调器协调,正常结束就正常完成,如果某个步骤失败,则根据相反顺序以此调用补偿操作;
Saga模式
使用场景
流程比较长且只需要保证事物最终一致性的长事物解决方案
事物的参与者是其他公司的服务
优缺点
优点
第一阶段已经提交了事物,性能比较高;
事物回滚操作基于时间异步执行,效率高;
逆向补偿服务,易于理解,易于实现;
缺点
事物在第一阶段已经提交了,也就是没有预留动作保证隔离性。
实现方式
最大努力通知
使用场景
给调用方提供接口回调,和不断地回调调用者,最大程度上让调用方知道被调用者这边是否执行结果
消息队列通知
使用场景
公司内部系统直接相互推送某个事物是否执行成功;相当于给其他系统通知这个事物是否成功。
saga事物特点
优点
事物并发度高,不会长时间锁定资源
缺点
需要定义补偿相关操作,代码量大
一致性弱,采用补偿机制实现事物一致性
特点
每个事物都有其正向的操作和逆向的事物回滚操作
正向服务,逆向补偿服务都是开发者来实现的,对业务造成了很大的侵入性
saga事物解决方案
方案差别
1、思路不一样
最大努力通知型,消息通知方需要不断的通知消息发送方,来保证最终一致性
可靠消息一致性,实用通知方保证把消息发送出去,一致性是由消息发送方来保证的;
2、技术解决方向不同
最大努力通知,解决的是消息接收到了之后的一致性;
可靠消息,解决的是消息发出到接收的一致性;
3、使用场景不一样
最大努力通知型,关注的交易后的通知事物;
可靠消息一致性,关注的整个交易过程的事物;
具体实现方案
最大努力通知型
1、中间事物参与者提供查询事物是否成功接口;2、后续事物参与者调用该接口,获取事物是否成功消息
特点
利用接口查询做补偿
事物流程图
流程图
消息最终一致型
1、中间事物参与者发送事物成功或者失败的消息;2后续事物参与者订阅消息完成最终事物
特点
利用消息队列来通知消息
事物流程图
流程图
saga事物理论基础
将长事物拆分成多个本地事物,由Saga事物协调器协调,正常结束就正常完成,如果某个步骤失败,则根据相反顺序以此调用补偿操作;
本地消息表
理论基础
最初是 ebay 架构师 Dan Pritchett 在 2008 年发表给 ACM 的文章
核心思想
需要分布式处理的任务通过消息的方式来异步确保执行
流程
写本地消息和业务操作放在一个事务里,保证了业务和发消息的原子性,要么他们全都成功,要么全都失败
本地消息表的特点
长事务仅需要分拆成多个任务,使用简单
生产者需要额外的创建消息表
每个本地消息表都需要进行轮询
消费者的逻辑如果无法通过重试成功,那么还需要更多的机制,来回滚操作
容错机制
扣减余额事务 失败时,事务直接回滚,无后续步骤
轮序生产消息失败, 增加余额事务失败都会进行重试
本地消息表示意图
示意图
适用范围
适用于可异步执行的业务,且后续操作无需回滚的业务
单点登录
分布式理论
分布式基础概念
2PC协议
协议定义
一种原子承诺协议,一种分布式算法,协调参与分布式事物的所有应用提交事物,或者回滚事物
2PC协议过程
阶段1:准备阶段
1、协调者向所有事物参与者发送事物内容,并等待所有参与者执行的结果答复
2、各参与者执行事物操作,并且记录事物日志,但是不提交事物;
3、参与者向协调者反馈事物执行的结果;
阶段2:提交阶段(回滚阶段)
提交事物
1、协调者向所有参与者发送事物提交消息;
2、参与者提交事物,释放事物锁定期间的资源;
3、参与者向协调者反馈事物执行成功消息
4、协调者收到各个参与者执行成功的消息,事物就成功
回滚事物
1、协调者向所有参与者发送事物回滚消息;
2、参与者做回滚操作,释放事物锁定期间的资源;
3、参与者向协调者反馈事物执行成功消息
4、协调者收到各个参与者执行成功的消息,事物就中断
2PC协议示意图
阶段1:
阶段2:
2PC协议问题
最大问题
同步阻塞问题
参与者向协调者反馈事物执行的情况,直到等到协调者反馈,是做提交还是回滚操作;之间的时间参与者都是处于阻塞状态;
数据不一致问题
如果参与者或者协调者,一方不可以用,导致事物数据不一致;
3PC协议
背景来源
主要是为了解决2PC中同步阻塞问题;
特点:
非阻塞协议,在实体提交或者终止之前增加一种超时机制,当超过这个时间上线事物还未提交,就会把该事物绑定的资源释放
3PC过程
阶段1:投票
1、协调者接收到事务请求后,向所有参与者发送能否提交请求,等待参与者返回信息
备注:如果协调者在接收事务请求时出现故障或者不可用,协调者将直接中止事务
2、参与者接收到协调者的的是否提交事物请求后,发送是否可以提交事物请求
阶段2:预提交
1、协调者,在超时时间内收到了各个参与者返回的可以提交按钮
备注:如果参与者都同意后会向协调者回复yes消息并进入准备状态,如果参与者获取资源失败或者出现不可用会回复no中止事务
2、协调者,向各个参与者发送预提交消息,参与者执行事物,并且记录日志,但是不做提交操作,并将执行结果返回给协调者;
阶段3:提交或者回滚
1、协调者接受到各个参与者发送回来的消息;
2、根据消息,做出是提交事物,还是回滚事物的决定,然后将将消息发送给各个参与者;各个参与者再根据协调者发送的消息,做回滚或者提交,并释放事物资源,向协调者反馈消息;
事物回滚情况
1、阶段1如果有返回不能提交事物
2、阶段2事物执行失败
3、预提交阶段中,协调者超时未收到消息
自动提交事物
阶段3参与者未收到协调者发送出来的提交或者回滚消息,那么就会自动提交事物
3PC问题
阶段3参与者未收到协调者发送出来的事物回滚的消息,那么参与者都是自动提交,就会造成事物不一致的情况;
实战
定时任务
elastic-job分片
根据业务的不同实现分片的算法,让一个定时任务分散到不同的机器上执行,减少单台机器的压力
dangdang网开源的elastic-job 框架借助zookeeper注册中心,实现分布式任务调度
实战
1、引入相关依赖包
<dependency>
<artifactId>elastic-job-lite-core</artifactId>
<groupId>com.dangdang</groupId>
<version>2.1.5</version>
</dependency>
<dependency>
<artifactId>elastic-job-lite-spring</artifactId>
<groupId>com.dangdang</groupId>
<version>2.1.5</version>
</dependency>
<artifactId>elastic-job-lite-core</artifactId>
<groupId>com.dangdang</groupId>
<version>2.1.5</version>
</dependency>
<dependency>
<artifactId>elastic-job-lite-spring</artifactId>
<groupId>com.dangdang</groupId>
<version>2.1.5</version>
</dependency>
2、zk,job相关配置
reg-center:
# 连接zookeeper服务器列表,多个地址用逗号分隔
server-list: 192.168.28.200:2181
# zk 的命名空间
namespace: elastic-job-lite-springboot
# 等待重试的间隔时间的初始值 默认1000,单位:毫秒
baseSleepTimeMilliseconds: 1000
# 等待重试的间隔时间的最大值 默认3000,单位:毫秒
maxSleepTimeMilliseconds: 3000
# 最大重试次数 默认3
maxRetries: 3
# 会话超时时间 默认60000,单位:毫秒
sessionTimeoutMilliseconds: 60000
# 连接超时时间 默认15000,单位:毫秒
connectionTimeoutMilliseconds: 15000
clockRemindJob:
job-name: clock
# cron表达式,用于控制作业触发时间,默认每间隔10秒钟执行一次 0 */10 * * * ?
cron: 0/10 * * * * ?
# 作业分片总数
sharding-total-count: 1
# 分片序列号和参数用等号分隔,多个键值对用逗号分隔.分片序列号从0开始,不可大于或等于作业分片总数
sharding-item-parameters: 0=全部
# 作业自定义参数,可通过传递该参数为作业调度的业务方法传参,用于实现带参数的作业
job-parameter: "my_parameter_clock"
# 是否开启任务执行失效转移,开启表示如果作业在一次任务执行中途宕机,允许将该次未完成的任务在另一作业节点上补偿执行, 默认为false
failover: true
# 是否开启错过任务重新执行 默认为true
misfire: true
# 作业描述信息
job-description: "clock Task every ten second"
# 连接zookeeper服务器列表,多个地址用逗号分隔
server-list: 192.168.28.200:2181
# zk 的命名空间
namespace: elastic-job-lite-springboot
# 等待重试的间隔时间的初始值 默认1000,单位:毫秒
baseSleepTimeMilliseconds: 1000
# 等待重试的间隔时间的最大值 默认3000,单位:毫秒
maxSleepTimeMilliseconds: 3000
# 最大重试次数 默认3
maxRetries: 3
# 会话超时时间 默认60000,单位:毫秒
sessionTimeoutMilliseconds: 60000
# 连接超时时间 默认15000,单位:毫秒
connectionTimeoutMilliseconds: 15000
clockRemindJob:
job-name: clock
# cron表达式,用于控制作业触发时间,默认每间隔10秒钟执行一次 0 */10 * * * ?
cron: 0/10 * * * * ?
# 作业分片总数
sharding-total-count: 1
# 分片序列号和参数用等号分隔,多个键值对用逗号分隔.分片序列号从0开始,不可大于或等于作业分片总数
sharding-item-parameters: 0=全部
# 作业自定义参数,可通过传递该参数为作业调度的业务方法传参,用于实现带参数的作业
job-parameter: "my_parameter_clock"
# 是否开启任务执行失效转移,开启表示如果作业在一次任务执行中途宕机,允许将该次未完成的任务在另一作业节点上补偿执行, 默认为false
failover: true
# 是否开启错过任务重新执行 默认为true
misfire: true
# 作业描述信息
job-description: "clock Task every ten second"
3、创建zk注册中心配置
/**
* @date 2020-6-7
* zookeeper 注册中心配置类
*/
@Configuration
@ConditionalOnExpression("'${reg-Center.serverList}'.length()>0")
public class ZkRegistryCenterConfig {
@Value("${reg-center.server-list}")
private String serverList;
@Value("${reg-center.namespace}")
private String namespace;
@Bean(initMethod = "init")
public ZookeeperRegistryCenter regCenter(){
return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList,namespace));
}
}
* @date 2020-6-7
* zookeeper 注册中心配置类
*/
@Configuration
@ConditionalOnExpression("'${reg-Center.serverList}'.length()>0")
public class ZkRegistryCenterConfig {
@Value("${reg-center.server-list}")
private String serverList;
@Value("${reg-center.namespace}")
private String namespace;
@Bean(initMethod = "init")
public ZookeeperRegistryCenter regCenter(){
return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList,namespace));
}
}
4、定时任务操作类
@Component
@Slf4j
public class ClockRemindJob implements SimpleJob {
public static int count = 1;
@Override
public void execute(ShardingContext shardingContext){
String jobParameter = shardingContext.getJobParameter();
String time = DateUtil.formatDate(new Date());
System.out.println("当前时间是:"+time+" ,job执行的总次数为:"+count++);
}
}
5、定时任务配置类
@Configuration
@Data
public class ClockRemindJobConfig {
@Value("${clockRemindJob.cron}")
private String cron;
@Value("${clockRemindJob.sharding-total-count}")
private int shardingTotalCount;
@Value("${clockRemindJob.sharding-item-parameters}")
private String shardingItemParameters;
@Value("${clockRemindJob.job-description}")
private String jobDescription;
@Value("${clockRemindJob.job-parameter}")
private String jobParameter;
@Value("${clockRemindJob.job-name}")
private String jobName;
@Autowired
private ClockRemindJob clockRemindJob;
public LiteJobConfiguration liteJobConfiguration(){
JobCoreConfiguration.Builder builder =
JobCoreConfiguration.newBuilder(clockRemindJob.getClass().getName(),cron,shardingTotalCount);
JobCoreConfiguration jobCoreConfiguration = builder
.shardingItemParameters(shardingItemParameters)
.description(jobDescription)
.jobParameter(jobParameter)
.build();
SimpleJobConfiguration simpleJobConfiguration = new
SimpleJobConfiguration(jobCoreConfiguration,clockRemindJob.getClass().getCanonicalName());
return LiteJobConfiguration
.newBuilder(simpleJobConfiguration)
.overwrite(true)
.build();
}
}
6、整合类
/**
* job 的配置,在此类中启动job
*/
@Configuration
public class JobConfig {
@Autowired
private ZookeeperRegistryCenter regCenter;
@Autowired
private ClockRemindJob alarmRemindJob;
@Autowired
private ClockRemindJobConfig clockRemindJobConfig;
// 容器启动的时候初始化该实例化bean
@Bean(initMethod = "init")
public JobScheduler simpleJobScheduler(){
// 把这三个配置组合起来
new SpringJobScheduler(alarmRemindJob,regCenter,clockRemindJobConfig.liteJobConfiguration()).init();
return null;
}
}
定时任何分区
在定时任务开启的之后,根据当前服务的台数,和总的需要执行任务的数量,相除,得到单个服务器需要执行的任务数量。然后再根据
设计一套根据服务器mac地址或者其他唯一标记的数据做hash算法,让服务集群之内的所有机器都有有一个对应的hash值(比如说服务集群有五个服务,那么每一个机器得到一个1-5的数值。)将需要执行的任务的数量,分成5分,每一个机器只执行属于他的那些任务;
秒杀系统设计
秒杀系统特点
秒杀系统特点
瞬间流量激增
正常流量
羊毛党流量
商品定时上架
秒杀商品库存少
商品购买流程简单
秒杀系统业务特点
1.读多写少
缓存
2.高并发
限流
缓存
异步
队列
3.资源冲突
原子操作
异步
秒杀系统存在问题
瞬间流量高,对服务压力造成冲击
请求过多
磁盘IO压力大
商品可能被超卖
设计思路
应用层
浏览器缓存,本地缓存
验证码
网络层
CDN
负载层
负载均衡,动静分离,反向代理缓存,限流
系统上游拦截请求,降低下游服务器压力
服务层
动态页面静态化,应用缓存
|分布式缓存,异步,队列,限流,原子操作保证
后台请求可以做削峰处理
利用缓存替代磁盘IO,提高读写下效率
数据库
乐观锁,悲观锁
商品超卖问题,加全局锁
技术方案实现
定时上架
秒杀时间未到,按钮置灰
上游削峰处理
前端削峰处理
秒杀请求发送前,校验用户是否已经登录
用户已经登录,需做验证码校验
验证码校验完成,按钮置灰,不让发送二次请求
硬件削峰处理
秒杀页面做成静态页面,放在CDN节点上,避免页面刷新请求后台服务
使用LVS 服务做第一层请求转发服务器
使用nginx 服务接受LVS请求,让后再做请求转发
下游削峰处理
网关层,做黑名单校验,防刷校验,不满足条件请求,直接返回
秒杀系统服务集群接受到请求,发送mq消息,到真正做秒杀的服务器
库存防止超卖
使用redis分布式锁
使用redission工具包
将库存量大小作为信号量的构造参数,
数据库悲观锁,乐观锁
高并发的情况下, 悲观锁比乐观锁效率高
相应速度,冲突频率,重试代价
乐观锁基于重试的代价太高了
限流
1.验证码;
2.容器限流
3.数据库相关的参数优化
4.手动限制某些接口的访问量;
限流算法
令牌桶
一秒钟可以产生多少令牌,只要拿到令牌的请求才能接着往下走秒杀流程
Guava 可以做令牌桶,ateLimiter一秒钟可以产生多少令牌,只要拿到令牌的请求才能去接着往下走;
漏桶
限制一秒多少个请求
Nginx 有自带的漏桶算法限流;Nginx.conf 的配置文件就可以配置这个;
5.nginx限流
6.消息队列
设计思路
应用层
浏览器缓存,本地缓存
验证码
网络层
CDN
负载层
负载均衡,动静分离,反向代理缓存,限流
系统上游拦截请求,降低下游服务器压力
服务层
动态页面静态化,应用缓存
|分布式缓存,异步,队列,限流,原子操作保证
后台请求可以做削峰处理
利用缓存替代磁盘IO,提高读写下效率
数据库
乐观锁,悲观锁
商品超卖问题,加全局锁
支付系统
支付的网络传输分为四种结果
1.我方放出信息,对方也能接受到信息;并且能够正确将相应信息返回来
由于网络原因导致三种坏的情况
2.我方虽然执行了发送信息,但是信息并没有发送出去
3.我方虽然将信息发送出去了,但是对方并没有接受到信息;
4.我们将信息发送出去了,对方也接受了,但是我方并没有收到对方的相应信息;
容错处理
1、对方异步回调
1、提供一个异步回调接口,让对方在支付完成之后毁掉我们接口
针对1,4这两种情况
2、对方提供查询接口
让对方提供一个查询接口,方便查询订单实际支付的情况
针对2,3这两种情况
库表设计
分库分表
解决问题
解决数据量过大,导致的性能问题,有原来的单库单表,拆分成多库多表,解决单库单表的数据量过大的问题,从而解决性能问题
方式
垂直切分
按照业务来分库,不同的业务放在不同的数据库
水平切分
单表的数据分在多个库(1.把单表的数据拆成多个单表(字段不变)
在实际引用过程中可以是垂直拆分+水平拆分结合
不同的业务进行垂直拆分,成多个库,相同的业务(如果这个业务很繁忙)又可以分为多个库;
分库分表的时候,需要注意是按照那个字段进行分库的
分库分表造成问题
1.分布式事物的一致性
2.跨节点关联查询
分多次查询,合并查询结果集
3.跨节点分页,排序
分多次查询,合并查询结果集
针对关联查询,有些在分库分表的时候,有些数据会对应分在一个库中,(比如一个商品表,主要信息和详细信息都会存放商品id)那么分库的时候就会把相对应的数据存放在一个数据库中,那么就不用夸节点查询了;
4.主键避重
全局主键
垂直分:1.按照不同的业务分开;2.将单表字段拆分到多个表(常见的查分标准:常用和不常用;大字段,小字段;组合查询的放在一起) 如果把这些表都放在一个库上,就是垂直分表,库在一个服务器上,存在对计算机cpu资源竞争,所以把这些表放在不同的机器上,避免资源竞争,这样就是垂直分库;
mysql主从复制
实现原理
mysql的主从复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新、删除等等)
并且主服务器开启了二进制日志
实现过程
复制过程中有三个线程在执行任务:一个线程在主服务器,负责将二进制日志的内容发送到从服务器(binlog dump线程);
主从复制开始;从服务器上会创建一个io线程,连接主线程并记录二进制日志中的数据到中继日志;中间需要识别binlog dump线程 以获取二进制日志
从服务器会开启一个sql线程(第三个线程),从中继日志中读取日志,并执行日志,以同步数据;
主从复制条件
1、主服务器开启了二进制日志
2、主从服务器的mysql版本之间是可以相互兼容
由于历史原因:
1、二进制日志格式不一样
2、字符集,函数,时区处理不一样
3、主服务器上设置了相应的从服务器
存在问题
1、主从数据库数据一致性问题
假设从库插入的时候异常
2、主从复制的延迟
主从复制解决问题
降低数据库读写压力
写操作比较耗时,读操作时间很短;
读写分离,解决了数据库写入时候,影响查询的效率;通过读写分离提供系统的高并发
项目设计
统一处理
全局异常统一处理
管理系统
全局修改操作记录到日志表
服务拆分
2.同一业务下不同的功能划分
Job
同一业务下的定时任务模块
获取本业务数据
直接查询属于本业务的库
直接调用本业务的原子微服务
直接调用本服务的非原子微服务
获取其他业务数据
1.调用其他原子的微服务
2.调用其他服务的非原子微服务
MQ
同一业务下消息队列模块
获取本业务数据
直接查询属于本业务的库
直接调用本业务的原子微服务
直接调用本服务的非原子微服务
获取其他业务数据
1.调用其他原子的微服务
2.调用其他服务的非原子微服务
web
同一业务下pc端接口
获取本业务数据
直接查询属于本业务的库
直接调用本业务的原子微服务
直接调用本服务的非原子微服务
获取其他业务数据
1.调用其他原子的微服务
2.调用其他服务的非原子微服务
App
同一业务下对APP接口
获取本业务数据
直接查询属于本业务的库
直接调用本业务的原子微服务
直接调用本服务的非原子微服务
获取其他业务数据
1.调用其他原子的微服务
2.调用其他服务的非原子微服务
Ms
同一业务下原子的微服务接口
Api
同一业务下非原子的微服务接口
可以聚合其他业务的原子的微服务接口
1.按照不同的业务划分不同的项目
平台建设
平台化出现原因
子主题
平台化优点
复用性强
研发效率高
降低复杂性
稳定性
平台化建设方式
嵌入式
接口依赖式
中台式
平台化建设常用模式
DSL领域特性语言
Specification规约模式
异构
统一存储
常见操作平台
自动化发布系统
自动回滚
测试环境可部署多套环境
依赖包版本控制
mysql操作平台
数据表结构变更,从测试环境,自动变更到预发布环境,自动变更到线上环境
权限控制
生产环境禁止使用delete,update操作
子主题
SQL 审核 web 工具
Yearning
项目地址
https://github.com/cookieY/Yearning
项目简介
https://guide.yearning.io/
概述
一款可满足大部分公司 SQL 审核需求的 web 端可视化 SQL 审核平台。在实现常规的 sql 审核功能外还添加了诸如数据查询等一系列便捷的功能
主要功能:
自动化SQL语句审核,可对SQL进行自动检测并执行。
DDL/DML语句执行后自动生成回滚语句
审核/查询 审计功能
支持LDAP登录/钉钉及邮件消息推送
支持自定义审核工作流
支持细粒度权限分配
优点
颜值高,使用流畅。
部分审核规则可自定义
部署简单,依赖项少
功能丰富,既能审核执行SQL又能查询
作者在持续维护,有社区支持
Archery
项目地址
https://github.com/hhyo/Archery
使用文档
https://archerydms.com/
概述
Archery 定位于 SQL 审核查询平台,旨在提升 DBA 的工作效率,支持多数据库的 SQL 上线和查询,同时支持丰富的 MySQL 运维功能,所有功能都兼容手机端操作
主要功能
集成 SQL 查询、审核、执行、备份
权限区分明确,审核执行分离
SQL 工单自动审批、高危语句驳回
快速上线其他实例
慢日志管理、SQL 优化等运维功能
支持会话管理及参数配置
可集成其他工具插件
优点
除 MySQL 外,还支持多种主流数据库
功能更加丰富,真正做到一平台多用
具备 SQL 优化、慢日志管理等运维功能,对 DBA 更加友好
基于 Python 及 Django,利于二次开发改造
Yearning与Archery比较
Yearning 只适用于 MySQL ,部署简单,功能清晰明了,上手容易
Archery 支持多种数据库,部署稍微复杂些,功能更加丰富,支持诸多高级运维功能
配置中心操作平台
自动化将低环境配置同步到高环境中去
redis操作平台
rocketMq ,kafka操作平台
流计算操作平台
大数据计算平台
ES-kibana操作平台
日志搜索平台
链路追踪
cat
skyWalking
对各个操作平台的系统权限控制平台
java实战
场景类
性能调优
系统调优
调优角度
业务调优
代码复用优化
性能调优
调优层面
设计层面
设计层面优化师优化手段的上层,根据系统的难点和潜在的问题,评估设计出合理的设计方案
具体方式
缓存使用
对象池设计
常量池
连接池
线程池
IO优化,选择多路复用
消息队列根据具体业务,设置对应的确认机制
使用序列化效率高的组件
零拷贝
io多路复用
代码层面
代码层面优化方式
合理使用设计模式
单例设计模式
减少对象的创建,减少内存消耗
减少对象的频繁创建
字符串优化
字符串操作避免内存泄露
切割查找选择最快方式
容器优化
根据当前业务查询多还是修改多,合理选用对应的容器
局部变量的容器,选择对应的操作快速的线程不安全的容器
容器的初始化长度可以,根据预估的数据量
位运算替代除法操作
避免二维数组使用
减少静态方法
GC不会回收静态方法占用的元空间数据
满足业务的情况下使用基本类型替代对象
对象需要开辟堆内存,基本类型需不要堆内存
代码中减少对第三方循环调用,第三方提批量接口
合理多使用switch 语句 替代if else
合理的使用空间换时间,或者时间换空间的思想
时间复杂度,空间复杂度
降低资源竞争
锁对象使用合理
乐观锁
io操作释放对应的资源
代码中可以手动将不再使用的大对象手动回收
同步变成异步
算法优化
针对搜索时间,内存大小设计合理的数据结构
JVM层面
编译优化
可以调整减少执行次数,再进行热点编译,针对一些执行频率很高的代码,可以在前期优化时间;
cpu核数,内存调整
根据cpu数量,内存大小和各项极限指标(qps,99线,并发量)的函数关系,做适当调整
根据对内存,cpu告警信息做适当调整
数据库层面
具体方式
sql优化
1、合理设置索引
2、合理的提前生成统计数据
3、连接池优化
4、减少关联查询
库表结构优化
1、合理分库分表
2、合理冗余字段
数据硬件优化
内存更大, 磁盘更大, cpu核数更多,运算速度更快
数据库参数优化
数据库日志刷盘策略
缓冲池大小
操作系统层面
磁盘,内存,cpu,
文件最大句柄数量
会影响io多路复用
优化评定指标
95线,99线,999线
cpu时间
内存分配
磁盘吞吐量
网络吞吐量
并发量
调优前注意事项
明确调优目的,不要为了调优而调优,盲目调优可能适得其反
调优实战
CPU突然飙升,系统反应慢,怎么排查?
导致cpu使用率飙升的原因
1、cpu上线文切换过多
1、在同一个时刻,每个cpu核心只能运行一个线程,如果有多个线程需要执行,cpu只能是通过上线文切花的方式来执行不同的线程
cpu上下文切换做的事情
记录线程执行的状态
让处于等待中的线程执行
过多的上下文切换,占用了过多的cpu资源,导致无法去执行用户进程中的cpu指令,导致用户进程中cpu相应速度过慢
上下文切换过多的场景
1、文件io操作
2、网络io
3、锁等待
4、线程阻塞
解决方式
多查询一些线程,是否处于阻塞状态中
如果是io导致,不管是网络,还是文件,
统一给这些io设置线程池,不让他们占用过多的系统资源
2、cpu资源过度消耗
过度消耗原因
1、创建了大量线程
2、有线程一直占用cpu资源,死循环
解决方式
linux系统解决
1、通过linux系统的top命令来找到cpu利用率过高的进程
2、ps -mp 进程号
查询进程信息,查看线程
2、shirt +H来找到进程中CPU消耗过高的线程
出现两种情况
cpu消耗过高的是同一个线程
通过jsatck工具根据线程id去下载线程的dump日志,然后定位到具体的代码
cpu消耗过高的是不同的线程
说明线程创建的比较多,拿几个线程id去下载线程的dump日志,
结果1、程序正常,在cpu飙高那一刻,用户访问量增大
结果2、也有可能程序不正常,线程池的创建相关有问题。
做针对性调整
docker
docker stats
1、查看docker的cpu占用率:
docker exec -it 容器编号 /bin/bash
2、进入cpu占用高的docker容器
查看容器中具体进程cpu占用率,执行top
查看进程中线程cpu占用率:top -H -p 进程号
将异常线程号转化为16进制: printf “%x\n” 线程号
查看线程异常的日志信息:jstack 进程号|grep 16进制异常线程号 -A90
各种监控系统辅助我们排查
线上服务内存溢出如何定位排查
排查问题方式
1、先获取堆内存快照文件
1、通过设置jvm参数
让jvm发生内存溢出的时候自动生成dump文件,并且存放在某个固定路径下面
2、通过执行dump命令来获取当前jvm的内存快照
2、通过mat工具分析dump文件
dump文件分析工具
1、mat工具
专门用来分析dump文件的
2、jprpfile工具
3、解析dump文件,得到内存统计信息总览
4、点击dominator Tree
dump文件中内存占用情况
5、获取到内存占用最高的类
6、然后再查看这个类中相信的内存占用情况
根据代码的情况惊醒调整
数据加密算法
不可逆加密
加密方式
MD5
概述
信息摘要算法5
特点
单向不可逆,无法解密
产生一个固定长度的散列码
使用场景
密码存储
信息完成性校验
HMAC系列
概述
散列消息鉴别码
特点
SHA系列
特点
单向不可逆,无法解密
产生一个固定长度的散列码
使用场景
密码存储
信息完成性校验
不可逆特点
单向不可逆,无法解密
产生一个固定长度的散列码
可逆加密
非对称加密
概述
公钥加密,私钥解密
非对称加密方式
RSA
DSA/DSS
特点
1.比对称加密更加安全可靠;
2.加密解密比对称加密慢很多
使用场景
HTTPS建立SSL连接中有一个步骤就是,将服务端的公钥加密用于对称加密的密钥;也就是把数据传输的对称加密的密钥发送给服务端,这样客户端和服务端都知道了实际做数据传输的对称密钥了
对称加密
概述
加密解密都是用的同一个密钥
对称加密方式
DES
3DES
AES
特点
1.加密解密速度快;
2.适用于大量数据加密
3.由于需要把密钥传递给需要用的对接方,在传递的密钥的过程中容易被人抓包,造成数据不安全;
场景
HTTPS建立SSL连接之后,客户端和服务端使用的就是对称加密来传输数据;主要就是利用对称加密解密速度快的优势
对称/非对称加密区别
性能
对称加密性能高于非对称加密
安全性
非对称加密安全性比对称加密安全性高
加密解密方式
对称加密,加密解密都是用的同一个密钥;非对称加密,加密解密密钥是分开的;
可逆/不可逆区别
可逆:可以通过解密的密钥获取到加密之前的数据;不可逆:数据是没有办法做解密操作,无法拿到解密之前的数据;
加密盐
作用
增加数据被破解的难度
让相同的数据在被加密之后显示的数据是不一样的;
接口幂等性
查询和修改操作都是幂等;添加和删除不是幂等
分布式环境下怎么保证接口的幂等性
幂等性概述
1.用户对于同一操作发起的一次请求或者多次请求结果都是一样。
具体操作
查询操作
天然幂等的
删除操作
删除一次和删除多次可能返回的数据不是一样
更新操作
如果把某个字段的值设置为1,那么不管多少次都是幂等
如果把某个字段值加1,那么就不是幂等
添加操作
重复提交会有幂等性问题
幂等性解决方案
1.通过代码逻辑判断实现
一般场景:代码中通过代码逻辑判断
支付场景:通过唯一的订单id来判断重复
2.使用token机制实现
支付场景:页面跳转的时候获取全局唯一的token,把token缓存起来;等到提交到后台的时候,再校验token是否存在,第一次提交成功就会删除token
使用场景:分布式环境
3.乐观锁
分布式环境中,做重复校验,直接用乐观锁实现
使用场景:分布式环境
延时任务
redis
实现方式
通过Redis的key的事件通知机制
缺点
Redis的事件通知,消息只会发送一次,不管客户端是否有收到(无消息确认机制)
使用场景
需要对某些key的改变做监控
消息队列
RocketMQ
RocketMQ设置固定延时时间发送消息
缺点
延时的消息时间只能是十几个固定的延时时间
使用场景
RabbitMQ
实现方式
1.正常队列不设置消费者,给单个消息设置超时时间;
2.设置死信队列和死信队列的消费者,把超时未发送的消息发送到死信队列;
使用场景
kafka
实现方式
时间轮定时任务
使用场景
定时任务
实现方式
先对数据先进行落库,再对数据库进行定时轮询
缺点
对数据库压力大
使用场景
java延时队列
实现方式
实现Java的延时队列,这是固定的延时时间;
高并发问题
接口限流
限流方式
验证码
使用场景
容器限流
NGINX
Tomcat
队列
手动控制某些接口访问
gateway
使用场景
服务器限流
Semaphore类控制并发访问数量
任务线程池控制方式
锁
限流算法
令牌桶
实现
使用场景
漏斗桶
实现
使用场景
滑动窗口
实现
使用场景
计数器
实现
使用场景
子主题 5
流量计算方式
滑动窗口
子主题 2
子主题 3
秒杀系统设计
秒杀系统的特点及对应解决方式
1.读多写少
缓存
2.高并发
限流
负载均衡
缓存
异步
队列
3.资源访问冲突
不同层级对应解决方案
应用层
控制方式
按钮控制
点击之后,一段时间之后才能再次被点击
验证码控制
需要收到验证码才能点击
控制目的
降低用户秒杀系统的点击频率
网络层
CDN
负载层
负载均衡,反向代理,限流
降低服务器访问压力
服务层
缓存,异步,队列,限流,原子操作
降低对数据库的压力
数据库
乐观锁,悲观锁
CDN作用
分布式锁实现
lua脚本为啥可以原子性执行
访问的是redis的某一个节点,当对这个节点中lua脚本加锁的时候,所有的请求到了lua这里都是需要做等待同步。lua脚本的执行肯定是在单个redis服务器上执行,所以可以保持同步;如果这个访问的是多个节点
zk为啥可以原子性实现
zk也是用java写的中间件,在正常的访问中就是通过创建临时节点是否成功表示是否获取到节点,正常的访问zk的时候,只会访问到zk的主节点,给主节点中的创建临时节点加上synchronized同步就可以使得zk在分布式同步;
redis缓存
数据存入缓存场景
首页
banner
List集合
三个豆腐块
List集合
为你推荐条件
List集合
竞拍大厅
筛选条件
骨架星级
Hash表
注册地
Hash表
车龄
Hash表
里程
Hash表
车辆级别
Hash表
排放标准
Hash表
车源属性
Hash表
燃料类型
Hash表
查询条件
场次
Hash表
拍场模式
Hash表
所有品牌车系
List集合
热门品牌
Hash表
业务城市数据
List集合
查询结果集
为你推荐
通过zset排序用户出价的品牌车系;每次出价的时候异步去更新redis的记录以及数据库
心愿单
心愿单的查询条件
车源id存放在Zset缓存,从缓存中拿到车源id;再去ES中获取数据;
我的出价
一个月前未中标
车源id存放在Zset缓存,从缓存中拿到车源id;再去ES中获取数据;
近一个月未中标
车源id存放在Zset缓存,从缓存中拿到车源id;再去ES中获取数据;
交易失败
车源id存放在Zset缓存,从缓存中拿到车源id;再去ES中获取数据;
交易成功
车源id存放在Zset缓存,从缓存中拿到车源id;再去ES中获取数据;
我的账户
账户余额
String
保证金
String
出价次数
String
INCR
做自增操作
关注次数
String
INCR
做自增操作
微服务项目
模块拆分方式
1.按照不同的业务划分不同的项目
2.同一业务下不同的功能划分
Job
同一业务下的定时任务模块
获取本业务数据
直接查询属于本业务的库
直接调用本业务的原子微服务
直接调用本服务的非原子微服务
获取其他业务数据
1.调用其他原子的微服务
2.调用其他服务的非原子微服务
MQ
同一业务下消息队列模块
获取本业务数据
直接查询属于本业务的库
直接调用本业务的原子微服务
直接调用本服务的非原子微服务
获取其他业务数据
1.调用其他原子的微服务
2.调用其他服务的非原子微服务
web
同一业务下pc端接口
获取本业务数据
直接查询属于本业务的库
直接调用本业务的原子微服务
直接调用本服务的非原子微服务
获取其他业务数据
1.调用其他原子的微服务
2.调用其他服务的非原子微服务
App
同一业务下对APP接口
获取本业务数据
直接查询属于本业务的库
直接调用本业务的原子微服务
直接调用本服务的非原子微服务
获取其他业务数据
1.调用其他原子的微服务
2.调用其他服务的非原子微服务
Ms
同一业务下原子的微服务接口
Api
同一业务下非原子的微服务接口
可以聚合其他业务的原子的微服务接口
性能测试
接口压测
定义:测试各个逻辑是否正确
目的
更早发现问题
缩短产品周期
更早发现底层问题
负载测试
定义
逐步增加系统租在,测试系统的性能变化,并在最终满足性能指标的情况下,测试系统所能承受的最大负载测试
目的:
检测系统运行的最大上限,使系统能够在最大压力下正常运行。获取最大上限数据
方法:
不断地在单位时间内增加请求个数,直到服务器的某一个资源(cpu,内存,网络延迟,等指标)达到饱和或者临界值
系统负载指标
并发用户数
这个是最重要的指标
持续运行时间
数据量
并发测试
目的
检查系统是否有并发问题导致的内存泄露,线程锁,资源争用问题
方法
确定用户并发数,必须直到系统锁承载的在线用户数,然后在单位时间内 同事发起一定量的请求
确定用户数的方法
平均并发用户数量
login seesion 数量* session 时间 / 考察时间段
巅峰用户数量
平均并发用户数量 + 3(平均并发用户数量 开平方根)
demo
一个oa系统有3000个用户, 平均每天大约有400人访问系统, 一天之内用户从登录到退出系统平均时间4小时,一天时间内,用户只在8个小时使用该系统
平均并发用户量
400*4/8 = 200
巅峰用户数量
200+ 3(200的整数平方根)
压力测试
定义
不断给应用增加并发数量,强制让其在极限情况下运行,观察其运行到何种程度,从而发现其性能缺陷
目的
查看系统能够承受的最大并发量,达到多少的时候系统才会奔溃
方法
以负载测试,并发测试为依据,
指标
接口响应时间
接口并发数量
内存,cpu,等指标
收获
1、通过负载,并发,压力测试,可以知道系统的极限,可以更加合理的设置系统的各项告警阈值
2、核心,频繁使用的接口,不做相关的测试,贸然上线,对系统的稳定性是一个极大的考验,并不知道接口,相关的极限值,容易把系统搞崩
3、获取到相关运行数据,可以方便之后的调优
内存,cpu数量和接口响应数据,并发数所呈现的函数关系,可以方便调节系统的内存,cpu
系统承载能力评估
评估系统的承载能力
什么是承载能力
系统在各项指标比如说cpu,内存在极限状态下所能达到最大的QPS,吞吐量,并发量,用户的最小响应耗时
如何计算系统的承载能力
通过负载,并发,压力测试得到单台机器的在极限状态下的接口影响时间,并发量数量,内存,cpu,等指标信息
通过相同的机器数量可以得到该服务的总的最大并发数量
机器数量评估
系统峰值呈现28原则, 百分之80的流量会在20%的时间内到达
峰值qps= PV* 80% /(60*60*24*20%)
pv = page view 页面浏览次数
uv = unique vistor 不同用户访问数量
访问量数据
如果业务方可以给出
根据往年的数据量,做出相应的评估结合当下的数据
机器数量 = 峰值qps / (单台压测得到的极限的qps)
服务器台数,服务的硬件,核数,jvm内存设置,磁盘大小,垃圾回收怎么设置
系统当前水位评估
需要单台服务器极限下的qps
当前单台机器的qps
当前水位 = 当前所有机器的总的qps / (单台机器的极限qps *服务器数量 )
cpu使用率
应用正常的使用率0%-75%之间
90%及以上cpu使用过高
qps计算
qps峰值估算
二八原则估算
峰值qps= PV* 80% /(60*60*24*20%)
demo
一天有1000w点击量
1000*10000 *80% / (60*60*24*20%) = 450
峰值的qps = 450
qps估算
qps的估算需要结合接口的99线数据,已经cpu的核数,服务器个数
单个接口的qps
获取单个接口的平均响应时间,1s除以这个99线,得到1s中单个cpu的qps,乘以cpu的总核数,乘以cpu利用率80%得到单个接口的qps数值
实际情况下,因为线程之间的切换消耗cpu资源还有cpu等待,等等原因,实际的qps数量 并不会像这种线性关系;实际还需要进行压测才行
项目整体qps
每一个接口调用频率差别很大,每一个接口的相应时间也很大,根据调用频率还有响应时间得到一个综合状态的平均响应时间,然后再通过1s除以这个综合平均响应时间,再乘以80%,得到单个cpu的qps,然后再计算总得qps
实际情况下,还是需要通过压测得到对应的关系曲线
一般情况下 8核16gb的机器,qps可以达到300-400,如果使用了缓存,qps达到1000没有啥问题
吞吐量
系统在单位时间内处理请求的数量
量化指标
TPS
QPS
系统吞吐量要素
请求对cpu的消耗,外部接口,网络磁盘IO
系统并发数估算
QPS*平均响应时间
cpu核数估算
需要结合qps的峰值,以及平均响应时间
demo:
qps的峰值2000,平均响应时间50ms
2000/((1000ms/50ms)*80%)/ 4 = 2000/16 = 128核
计算中间需要加上cpu 饱和使用率 80%
实际情况下:128核 服务器,可以拆分成几个32和或者几个16核的服务器;可以做对应的服务器拆分;
服务器的台数和cpu核数估算
是密切相关的,可以根据qps数量,平均响应时间预估得到cpu总的核数,然后再根据核数可以拆分成几台对应的服务器
jvm内存预估
预先预估
根据系统的qps,每一个请求会占用多少内存,机器的选型,年轻代应该给多少,老年代给多少,对应进入老年代速度,Full gc的频率
预估完成之后,给设定一些初始的jvm参数,堆内存,栈内存,Eden,survivor比例,新生代,老年代大小,大对象阈值,大对象进入老年代的阈值
尽量让对象都保留在年轻代,不让进如老年代,减少full gc
系统压测jvm优化
根据压测环境JVM运行状况,如发现对象过快进入老年代,就需要采用之前介绍的优化思路,合理调整新生代、老年代、Eden、Survivor各个区域的内存大小,保证对象尽量留在年轻代,不要过快进入老年代。
当对压测环境下的系统优化好JVM参数之后,观察Young GC和Full GC频率都很低,此时就可部署上线
当对压测环境下的系统优化好JVM参数之后,观察Young GC和Full GC频率都很低,此时就可部署上线
监控jvm内存
天高峰、低峰期观察线上系统的JVM运行是否正常,有无频繁Full GC的问题。有就优化,没有就平时每天都定时或每周都看看
mysql数据库性能评估
性能瓶颈点
cpu核数
内存
磁盘io性能
有大量的数据库日志写入,以及mysql本身数据的写入
io相关的性能指标
IOPS
吞吐量
写入延迟时间
磁盘随机度的极限100MB/S
磁盘io和带宽需要达到一致相匹配的数值才能发挥当前硬件配置下,最大的性能
带宽
网络之间的数据传输都是需要占用带宽,如果带宽不够也会影响传输效率和时间
性能测试评估
8核16G每秒1-2千个并发
8核32G每秒2-3个并发
mysql服务的最大并发连接数量16384
消息队列
kafka节点挂了
共性问题及解决思路
消息堆积
导致原因
消费者消费速度过慢,生产者生产速度过快
解决方式
1、检查生产者发送消息是否正常,是否有大量重复消息,或者不必要发送的消息;检查消费者消费是否正常,是否有性能瓶颈,
如果有,需要针对型处理
2、提高消费者消费速度
1、开启多线程消费
2、添加消费者服务器节点
3、批量拉取消息
4、消息消费快速入库,快速返回成功,开启后台线程去消费消息;
消费重复消费
导致原因
消费者未能提交正确的消费成功消息,或者服务端没有接受到此消息
解决方式
代码逻辑中保证幂等性,使用消息id,获取业务id来做幂等性判断;
消费丢失
消息丢失原因
生产者向服务端发送消息失败(网络原因,或者生产者异常)
kafka
消息重试机制
生产者自己异常,导致消费发送失败,生产者会有重试机制,重新发送消息
消息确认机制
消息确认等级
ACK =0
ACK=1
ACK=all
消息确认相应机制
ACK=1(默认设置),只要分区的leader副本接受到了消息,就会给生产者发送消息接受成功(其他副本再回去同步leader的数据)。
设计上是比较折中的在一定程度上能够保证消息的不丢失,也能保证一定的吞吐量
ACK=0,生产者给服务端发送消息,不等服务端是否有接收到消息,发送完了就认为消息到被服务端接受了;而实际情况是,消息会生产者的缓冲池中待一段时间然后才会被发送到服务端,生产者就不知道消息具体在啥时候发送到服务端;
缺点:网络宕机的时候,消息会丢失
优点:满足大吞吐量的数据发送;
ACK=all,消息的分区leader还有所有的副本都接受到消息,才会给生产者发送消息已经被接受了。
优点:最大程度上保证服务器节点接受到消息;
缺点:极度影响性能,导致数据的吞吐量低
RocketMQ
RoekctMQ 事物机制来确保消息都能发送到节点上
将生产者消息发送设置成同步发送,根据发送结果来判断是否需要做重复发送,最大程度保证消息发送到了服务器
消息重投机制,消息投递失败,会再次投递
rabbitMq
1、消息确认机制
发送端把消息发送个服务端,服务端接收到消息并且把消息持持久化到磁盘就会给发送端一个异步的confirm应答
2、事物消息机制
发送端开启一个事物,再推送消息,如果投递失败;进行事物回滚,然后重新发送消息;如果服务端收到消息,发送端就提交事务。
服务端保存消息失败(写入磁盘失败)
kafka
1.ACK=all,让所有的服务器节点都获取到数据;
2.合理的设置从机的个数,设置数据的备份
参数设置:min.insync.replica
3.禁止主机挂掉,选从机作为新的主机
参数设置:unclean.leader.election.enable=false 本身默认就是flase
rocketMq
1、修改节点的消息持久化刷盘机制
将异步持久化到磁盘,修改成同步持久化到磁盘
RocketMq消息本身默认的持久化盘刷机制是异步刷盘
2、RocketMQ采用主从机构
确保主机挂了,从机上还有消息备份数据
rabbitMQ
服务端设置交换机,队列,数据的持久化;服务器宕机后,重启会读取磁盘上的持久化的数据;
问题:由于消息的持久化是一批的持久化,可能宕机了,这一批数据还持久化到磁盘
消息的持久化
1、服务端收到生产者发送过来的消息,会做消息的持久化
2、当服务宕机后, 会从磁盘当中读取相应的消息,最大程度上保证消息不在服务节点上丢失
使用confirm机制,可以让发送者的数据持久化到磁盘,再确认
也可以尝试修改刷盘策略
消费方消费丢失
kafka
设置消息消费提交偏移量为手动提交偏移量,通过代码在finnaly里面手动设置消息异常的那个前一条的偏移量做提交;
需要正确提交自己的消费正常的偏移量
rocketMq
ack机制,设置消息成功消费之后,再通知节点消息已经成功消费
rabbitMq
丢失原因
1.消费者在处理消息的时候出现异常了,那么这条消息就是没有被正常的消费;如果不采取措施,那么这个消息就会丢失
解决方案
ack机制
ack机制概述
消息只有正常消费后,反馈给服务端;服务端才会从队列里面把该条消息删除
ack机制三种模式
不确认
不会发送ack确认消息
自动确认
服务端发送完消息就自动认为该消息被成功消费
缺点:由于网络原因,造成数据从服务端发送到消费者消息丢失
手动确认
消费者消费成功之后,显示的给服务端ack信号;服务端只有收到该信号才会把数据从队列里面删除
设置手动ack,尽可能减少消费端的数据丢失问题;正常就是发送ack,异常就记录日志,然后发送nack
ack机制弊端
内存泄露
如果消费者异常没法发ack消息,服务端会认为这些数据都是没有被正常消费;就会堆积在队列当中,造成内存没法回收,内存泄露;
内存泄露解决方案
1.设置手动应答,如果异常,捕获异常记录日志,给服务端发送正常消费;
2.设置重试次数(默认是3次,三次不消费成功就会放入到默认的死信队列)
ack机制默认打开,而且是自动确认
统一概述
消费者消费消息,都应该向服务器节点正确提交消费的实际情况;
消息顺序消费
通用方式
1、将前后有逻辑的消息合并成一个消息,发送,这样不管消费者是多个,不管消费者有几个线程,消费的时候都能保证消费是顺序被消费
2、同一个topic下,将有先后逻辑顺序的消息发送到同一个队列(rocketMQ,rabbitMQ),或者同一个分区(kafka),且保证消费者只有一个也只有一个线程来消费
kafka
1、将有前后逻辑顺序的消息发送到同一个分区
1、一个topic只有一个分区,对应的也只有一个消费者
RocketMq
1、将有前后逻辑顺序的消费发送到同一个topic下的同一个队列,且保证消费方只有一个线程去消费
2、设置发送的消息是全局顺序消息
某个Topic下的所有消息都要保证顺序
RabbitMq
1、将有前后逻辑顺序的消费发送到同一个topic下的同一个队列,且保证消费方只有一个线程去消费
共性问题及解决思路
消息堆积
导致原因
消费者消费速度过慢,生产者生产速度过快
解决方式
1、检查生产者发送消息是否正常,是否有大量重复消息,或者不必要发送的消息;检查消费者消费是否正常,是否有性能瓶颈,
如果有,需要针对型处理
2、提高消费者消费速度
1、开启多线程消费
2、添加消费者服务器节点
3、批量拉取消息
4、消息消费快速入库,快速返回成功,开启后台线程去消费消息;
消费重复消费
导致原因
消费者未能提交正确的消费成功消息,或者服务端没有接受到此消息
解决方式
代码逻辑中保证幂等性,使用消息id,获取业务id来做幂等性判断;
消费丢失
消息丢失原因
生产者向服务端发送消息失败(网络原因,或者生产者异常)
kafka
消息重试机制
生产者自己异常,导致消费发送失败,生产者会有重试机制,重新发送消息
消息确认机制
消息确认等级
ACK =0
ACK=1
ACK=all
消息确认相应机制
ACK=1(默认设置),只要分区的leader副本接受到了消息,就会给生产者发送消息接受成功(其他副本再回去同步leader的数据)。
设计上是比较折中的在一定程度上能够保证消息的不丢失,也能保证一定的吞吐量
ACK=0,生产者给服务端发送消息,不等服务端是否有接收到消息,发送完了就认为消息到被服务端接受了;而实际情况是,消息会生产者的缓冲池中待一段时间然后才会被发送到服务端,生产者就不知道消息具体在啥时候发送到服务端;
缺点:网络宕机的时候,消息会丢失
优点:满足大吞吐量的数据发送;
ACK=all,消息的分区leader还有所有的副本都接受到消息,才会给生产者发送消息已经被接受了。
优点:最大程度上保证服务器节点接受到消息;
缺点:极度影响性能,导致数据的吞吐量低
RocketMQ
RoekctMQ 事物机制来确保消息都能发送到节点上
将生产者消息发送设置成同步发送,根据发送结果来判断是否需要做重复发送,最大程度保证消息发送到了服务器
消息重投机制,消息投递失败,会再次投递
rabbitMq
1、消息确认机制
发送端把消息发送个服务端,服务端接收到消息并且把消息持持久化到磁盘就会给发送端一个异步的confirm应答
2、事物消息机制
发送端开启一个事物,再推送消息,如果投递失败;进行事物回滚,然后重新发送消息;如果服务端收到消息,发送端就提交事务。
服务端保存消息失败(写入磁盘失败)
kafka
1.ACK=all,让所有的服务器节点都获取到数据;
2.合理的设置从机的个数,设置数据的备份
参数设置:min.insync.replica
3.禁止主机挂掉,选从机作为新的主机
参数设置:unclean.leader.election.enable=false 本身默认就是flase
rocketMq
1、修改节点的消息持久化刷盘机制
将异步持久化到磁盘,修改成同步持久化到磁盘
RocketMq消息本身默认的持久化盘刷机制是异步刷盘
2、RocketMQ采用主从机构
确保主机挂了,从机上还有消息备份数据
rabbitMQ
服务端设置交换机,队列,数据的持久化;服务器宕机后,重启会读取磁盘上的持久化的数据;
问题:由于消息的持久化是一批的持久化,可能宕机了,这一批数据还持久化到磁盘
消息的持久化
1、服务端收到生产者发送过来的消息,会做消息的持久化
2、当服务宕机后, 会从磁盘当中读取相应的消息,最大程度上保证消息不在服务节点上丢失
使用confirm机制,可以让发送者的数据持久化到磁盘,再确认
也可以尝试修改刷盘策略
消费方消费丢失
kafka
设置消息消费提交偏移量为手动提交偏移量,通过代码在finnaly里面手动设置消息异常的那个前一条的偏移量做提交;
需要正确提交自己的消费正常的偏移量
rocketMq
ack机制,设置消息成功消费之后,再通知节点消息已经成功消费
rabbitMq
丢失原因
1.消费者在处理消息的时候出现异常了,那么这条消息就是没有被正常的消费;如果不采取措施,那么这个消息就会丢失
解决方案
ack机制
ack机制概述
消息只有正常消费后,反馈给服务端;服务端才会从队列里面把该条消息删除
ack机制三种模式
不确认
不会发送ack确认消息
自动确认
服务端发送完消息就自动认为该消息被成功消费
缺点:由于网络原因,造成数据从服务端发送到消费者消息丢失
手动确认
消费者消费成功之后,显示的给服务端ack信号;服务端只有收到该信号才会把数据从队列里面删除
设置手动ack,尽可能减少消费端的数据丢失问题;正常就是发送ack,异常就记录日志,然后发送nack
ack机制弊端
内存泄露
如果消费者异常没法发ack消息,服务端会认为这些数据都是没有被正常消费;就会堆积在队列当中,造成内存没法回收,内存泄露;
内存泄露解决方案
1.设置手动应答,如果异常,捕获异常记录日志,给服务端发送正常消费;
2.设置重试次数(默认是3次,三次不消费成功就会放入到默认的死信队列)
ack机制默认打开,而且是自动确认
统一概述
消费者消费消息,都应该向服务器节点正确提交消费的实际情况;
消息顺序消费
通用方式
1、将前后有逻辑的消息合并成一个消息,发送,这样不管消费者是多个,不管消费者有几个线程,消费的时候都能保证消费是顺序被消费
2、同一个topic下,将有先后逻辑顺序的消息发送到同一个队列(rocketMQ,rabbitMQ),或者同一个分区(kafka),且保证消费者只有一个也只有一个线程来消费
kafka
1、将有前后逻辑顺序的消息发送到同一个分区
1、一个topic只有一个分区,对应的也只有一个消费者
RocketMq
1、将有前后逻辑顺序的消费发送到同一个topic下的同一个队列,且保证消费方只有一个线程去消费
2、设置发送的消息是全局顺序消息
某个Topic下的所有消息都要保证顺序
RabbitMq
1、将有前后逻辑顺序的消费发送到同一个topic下的同一个队列,且保证消费方只有一个线程去消费
消息堆积,且需要保证消息顺序消费
子主题
2、将消息按照用户id或者其他id来做hash,将消息放到不同的表。开启多线程,每一个线程消费固定的某一个表,来消费消息;
分布式
分布式系统解决方案
超时订单处理
超时订单:就是延迟任务
解决方案
1.数据库轮询,查数据库的表,查看转台;
2.DelayQueue 延迟队列,把这些任务都放在延迟队列中
代码中不断的轮询,延迟队列中超时的任务就会被poll方法
拿出来执行;基本都是用while(true)这种死循环去扫描这些超时的任务;
缺点:都只是存储在内存中,集群模式下,单机只能处理单机的任务;
3.循环队列
缺点和方法2的缺点是一样的;
4.redis 的zSet数据结构;让Zset这个数据按照超时时间排序
也是循环遍历要超时的任务,然后把这个任务做了,然后再把这个任务删除从Zset中删除;这些方法Jedis这些都是用方法的;
优点:解决集群模式下的任务;
存在的问题:
1.数据量大了,也会造成性能问题;
2.zset集合中的数据越来越多的时候,zset的性能也会越来越低
5.把数据存储redis的set集合中,key是时间戳,value是这个时间戳产生的数据集合
一秒钟查询一次,然后才处理这一秒钟产生的订单信息;
缺点:已经超时的订单将得不到处理;1秒内无法处理完这些数据,那么就会超时,下面就会有没有处理的订单
全局唯一id生成
分布式Id要求
全局唯一
全局业务中必须要是唯一
趋势递增
这个id最好是能递增
高性能
高可用
接入方便
全局唯一id生成方式
UUID
优点
简单
无网路消耗
缺点
无业务意义
不是递增
存储消息空间大
UUID作为数据库主键,性能低下
使用场景
数据库实现方式
数据库自增id
方式概述
获取数据库自增的主键作为全局ID
优点
实现简单
id是递增
数值类型查询速度快
缺点
如果数据是单节点,无法完全保证数据的高可以用
数据库多主模式
方式概述
给几台数据库服务器自增时候设置相应的步长(比如说,自增3,自增4),获取到自增id
优点
解决单节点,并不能高可用问题
缺点
不利于后续扩容
号段模式
方式概述
从数据库批量获取自增ID
优点
批量减少数据库压力
号段方式是推荐的使用方式
ZK
实现概述
通过node节点版本生成序列号
可以生成32位,64位
redis
实现概述
利用的incr命令实现原子的自增
雪花算法
实现概述
正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 数据中心(占5比特)+ 自增值(占12比特),总共64比特组成的一个Long类型。
百度uid-generator
和雪花算法类似
优点
可自定义时间戳,机器ID,序列号
美团Leaf
同时支持号段模式,雪花算法模式,可以切换
滴滴(Tinyid)
号段模式的实现
分布式锁
数据库乐观锁
操作步骤
1.先去查询数据是否存在
2.在做更新的时候对比当前数据的版本是否和第一次查询的时候版本号一致,一致才更新;
原理
基于第一次查询和第二次做修改时候查询的数据来判断中间的数据有没有被修改过
mysql实现
利用在表中加入一个版本号
做修改操作钱先去表中查询,获取到相应的数据和版本号信息
在做更新的时候,拿之前查询到的版本和数据库当前的版本号对比,是否相同,如果相同才让做修改操作;否则就表示这个数据已经被其他线程修改过了,就不让做修改操作;
Redis分布式锁
使用redisson连接方式
操作步骤
1.在try catch外面获取锁
通过RedisClient对象获取到RedissionLock对象
2.在try catch中加锁
通过RedissionLock对象进行加锁
分支主题
3.在finally里面解锁(防止死锁,解不了锁)
原理
因为redis的crud都是单线程的,通过想Redis中设置key,value的方式
设置成功,说明当前就获取到了Redis的锁
设置失败,说明其他请求已经向Redis中设置过了当前值,其他线程获取到了锁,当前线程会阻塞等待获取锁;
Redis的SETNX命令给向reids中添加键值对,key不存在就是添加成功,key存在就添加失败
1、添加成功相当于获取到redis锁
2、添加失败相当于未获取到redis锁
3、redis删除该key,相当于释放了锁
Lua脚本加锁过程
1.先判断key是否存在,不存在才会加锁;
2.如果key存在,那么通过lua脚本判断,获取锁的客户端id是否和当前客户端的id相同,也就是进行可重入判断;
3.如果客户端id也不一样,那么会返回一个当前获取锁需要等待的时间,并且进入while死循环中等待获取锁;
设计需要注意的点
适时释放锁
1、锁的释放操作需要放在finnaly代码中;
未设置,程序异常,导致当前代码永远死锁
2、给key设置超时时间;(相当于设置锁的有效期时常)
防止获取到锁的程序,由于某种原因一直不释放锁,阻塞后续线程执行
超时时间设置
超时时间需要足够长,来确保执行的任务的时间远远小于超时时间
确保sentx命令还有设置超时命令是同一个原子操作;
该操作由redis提供相关的操作命令
防止sentx命令执行完成,再去执行设置超时时间中间,reids宕机;
加锁,解锁两个操作都是同一个线程
防止,自己设置的锁被其他业务误删;
锁需要具备可重入性
子主题
锁需要具备阻塞和非阻塞特性
原因
获取不到锁的线程,需要让这些线程做获取的锁的等待从而可以执行锁中间的代码,而不是直接返回到客户端
解决方式
死循环不断的尝试获取锁操作,获取到锁操作后,跳出死循环;
锁失效
原因
锁失效后,其他线程可以直接获取到锁对象,两个线程同时执行锁中间的代码,无法保证串行执行;
解决方式
需要给锁有效期续命
高并发分布式锁设计
核心思想
分而治之,类ConcurrentHashMap种的分段锁
将需要被并发访问的数据拆分成多份,每一份数据对应设置一个分布式锁
案例
高并发下秒杀商品,商品库存有1000个,将商品库存缓存到10个key,value中,每一个key value的缓存只有100个,这样并发度就是原来的10倍
特别注意:
当获取的key对应的库存数据为0时候,需要释放当前锁,且需要再次获取其他库存不为0的锁;
分布式锁使用场景
1、批量操作防止重复操作
背景说明
运营后台需要针对线下商品的批量操作,一个商品可以绑定200-300个门店,批量100个商品就会有2W-3W条数据更新
2W-3W条数据,使得程序响应过慢,运用以为操作失败,然后接着继续狂点,导致服务器内存溢出宕机;
解决方式
方式1
1、获取到批量操作的商品id,按照大小排序,获取对应的uuid值,作为key,也就是redis的锁
2、后面重复操进来的数据,发现锁已经被获取,就放弃执行当前操作,显示已经在执行中;
2、显示抢购商品秒杀活动
背景说明
以鸡蛋,大白菜,食盐等商品做1毛钱秒杀活动,达到线上引流目的
解决方式
1、初始化分段锁信息
key为商品id,value为hash表,hash表中的字段就是各个子锁key,值为库存数据
2、用原子类的Long记录请求请求数量
3、对请求数量按照锁的个数取模,以此来确定需要访问具体的是哪个子锁
4、尝试sentx操作,key为子锁key,为第三步中获取的value为库存
5、获取到锁之后,执行业务代码,然后在最后更新hash表中的库存数据,也更新当前子锁对应库存数据
6、最后解锁
7、未获取到锁的线程,在做死循环自旋的时候,每次sentx操作之前都是需要获取hash表中库存数据;确保获取的库存数据是最新数据
ZK分布式锁
操作步骤
1.通过zkClient对象创建临时节点,作为加锁
2.通过zkClient对象删除临时节点,作为解锁
原理
1.zk是不允许创建相同的临时节点
2.如果zkclient对象发现已经相同的临时节点已经存在了,那么就会阻塞被阻塞;
3.阻塞实际上就是等待创建了这个节点的客户端去删除这个临时节点;
4.阻塞结束之后,创建临时节点,就获取到了锁
本质
zk不允许重复创建临时节点
有创建临时节点,就会客户端就会被阻塞
分布式锁对比
子主题
分布式缓存
Redis与MemCache的区别
线程操作
redis数据处理是单线程,memcache是多线程处理
数据结构
Redis支持更多更复杂的数据结构,memcache只支持keyvalue的字符串数据;
数据安全性
Redis支持数据的持久化,会把数据同步到磁盘上;memcache不支持数据的持久化
数据备份
Redis支持数据备份,需要开启主从模式;memcache不支持数据备份
过期策略
REDIS支持更多的过期策略;memcache支持的过期策略少
分布式事物
分布式事物概述
CAP
Consistency
强一致性
Availability
可用性
Partition tolerance
分区容错性
CAP只能保证两点
这个理论中的一致性指强一致性,而不是弱一致性
CAP选择
一般情况下保证AP和最终一致性
金融业,CA,强一致性+可用性
seata解决方案
集成了4中分布式事物解决方案
AT 模式、TCC 模式、Saga 模式和 XA 模式
分布式事物模型
AT模式
特点
事物的第一阶段其实已经提交了;事物的回滚操作是在原来正向操作的基础上做的一个逆向操作,这个逆向操作seata已经帮你做了;
原来的一个insert操作变成delete操作;delete操作变成insert操作;update修改操作会修改回来;
解决方案
seata
使用场景
对代码无侵入性的分布式解决方案
AT事物特点
业务无需编写各类补偿操作,回滚由框架自动完成
TCC模式
使用场景
适用于那些需要处理复杂业务流程、同时涉及多个独立事务单元、具有复杂性和不确定性的场景
对性能有很⾼要求的场景
高性能分布式事物的解决方案
TCC设计
基于2pc协议
TCC概述
事物3个操作
Try
预留业务资源
Confirm
确认执行提交事物
Cancel
执行事物回滚
TCC可以分成两个阶段
资源预留阶段
事物提交阶段/事物回滚阶段
TCC事物实现方式
Hmily轻量级分布式事务的框架, 无需搭建Hmily服务器
需要标记Try,confirm,cancel方法
Seata
ByteTCC
EasvTransaction
TCC事物特点
并发度较高,无长期锁定事物资源
开发量较大,需要自己提供try,confirm,cancel接口
一致性好
TCC适用于订单类业务,对中间状态有约束
XA模式
使用场景
同一项目中直连两个数据库,做修改操作
实现原理
mysql oracle等关系型数据都是支持两阶段提交协议的,也就是有一个支持多个事物的接口;
两阶段提交主要保证了分布式事务的原子性:即所有结点要么全做要么全不做)
主流数据库:mysql、oracle、sqlserver、postgre都是支持XA事物
备注:主要是这些数据库都遵守2PC协议
依赖于数据库支持XA事物模型
理论基础
2PC协议
第一阶段每个事物完成预提交并通知把结果告诉事物协调器;事物协调器等所有分支事物都操作完成,预提交之后;第二阶段协调器通知每个事物进行逐步的提交或者是逐步的回滚操作;
二阶段详细过程
准备阶段
事物协调器向所有的事物发起者询问是否可以执行提交操作,并等待各个参与者节点的响应
参与者节点执行询问发起为止的所有事物操作,并记录到redo,undo日志中;
事物的各个参与者对事务管理器响应自己的事物是执行成功还是执行失败;
提交阶段
协调者只要收到一个事物失败或者超时,那么就会给每一个参与者发送回滚消息否则发送提交消息;
事物过程
第一阶段每个事物完成预提交并通知把结果告诉事物协调器;事物协调器等所有分支事物都操作完成,预提交之后;第二阶段协调器通知每个事物进行逐步的提交或者是逐步的回滚操作;
特点
事物粒度大,高并发环境下,系统可用性比较低
解决方案
1、AtomMikos
使用方式
springBoot整合atomikos
<!-- atomikos 多数据源管理 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jta-atomikos</artifactId></dependency>
spring整合atomikos
<dependency><groupId>com.atomikos</groupId><artifactId>transactions-jdbc</artifactId><version>4.0.6</version></dependency>
无整合开发
<dependency><groupId>com.atomikos</groupId><artifactId>transactions-jdbc</artifactId><version>4.0.6</version></dependency>
2、finnally里面做事物回滚
使用场景
当前系统调用了内部的多个数据库,或者是多个服务,在调用方这边直接做数据回滚;
XA模式优缺点
XA模式是分布式强一致性的解决方案;
简单易理解,开发较容易
对资源进行了长时间的锁定,并发度低
saga
saga事物理论基础
将长事物拆分成多个本地事物,由Saga事物协调器协调,正常结束就正常完成,如果某个步骤失败,则根据相反顺序以此调用补偿操作;
Saga模式
使用场景
流程比较长且只需要保证事物最终一致性的长事物解决方案
事物的参与者是其他公司的服务
优缺点
优点
第一阶段已经提交了事物,性能比较高;
事物回滚操作基于时间异步执行,效率高;
逆向补偿服务,易于理解,易于实现;
缺点
事物在第一阶段已经提交了,也就是没有预留动作保证隔离性。
实现方式
最大努力通知
使用场景
给调用方提供接口回调,和不断地回调调用者,最大程度上让调用方知道被调用者这边是否执行结果
消息队列通知
使用场景
公司内部系统直接相互推送某个事物是否执行成功;相当于给其他系统通知这个事物是否成功。
saga事物特点
优点
事物并发度高,不会长时间锁定资源
缺点
需要定义补偿相关操作,代码量大
一致性弱,采用补偿机制实现事物一致性
特点
每个事物都有其正向的操作和逆向的事物回滚操作
正向服务,逆向补偿服务都是开发者来实现的,对业务造成了很大的侵入性
saga事物解决方案
方案差别
1、思路不一样
最大努力通知型,消息通知方需要不断的通知消息发送方,来保证最终一致性
可靠消息一致性,实用通知方保证把消息发送出去,一致性是由消息发送方来保证的;
2、技术解决方向不同
最大努力通知,解决的是消息接收到了之后的一致性;
可靠消息,解决的是消息发出到接收的一致性;
3、使用场景不一样
最大努力通知型,关注的交易后的通知事物;
可靠消息一致性,关注的整个交易过程的事物;
具体实现方案
最大努力通知型
1、中间事物参与者提供查询事物是否成功接口;2、后续事物参与者调用该接口,获取事物是否成功消息
特点
利用接口查询做补偿
事物流程图
流程图
消息最终一致型
1、中间事物参与者发送事物成功或者失败的消息;2后续事物参与者订阅消息完成最终事物
特点
利用消息队列来通知消息
事物流程图
流程图
saga事物理论基础
将长事物拆分成多个本地事物,由Saga事物协调器协调,正常结束就正常完成,如果某个步骤失败,则根据相反顺序以此调用补偿操作;
本地消息表
理论基础
最初是 ebay 架构师 Dan Pritchett 在 2008 年发表给 ACM 的文章
核心思想
需要分布式处理的任务通过消息的方式来异步确保执行
流程
写本地消息和业务操作放在一个事务里,保证了业务和发消息的原子性,要么他们全都成功,要么全都失败
本地消息表的特点
长事务仅需要分拆成多个任务,使用简单
生产者需要额外的创建消息表
每个本地消息表都需要进行轮询
消费者的逻辑如果无法通过重试成功,那么还需要更多的机制,来回滚操作
容错机制
扣减余额事务 失败时,事务直接回滚,无后续步骤
轮序生产消息失败, 增加余额事务失败都会进行重试
本地消息表示意图
示意图
适用范围
适用于可异步执行的业务,且后续操作无需回滚的业务
单点登录
分布式理论
分布式基础概念
2PC协议
协议定义
一种原子承诺协议,一种分布式算法,协调参与分布式事物的所有应用提交事物,或者回滚事物
2PC协议过程
阶段1:准备阶段
1、协调者向所有事物参与者发送事物内容,并等待所有参与者执行的结果答复
2、各参与者执行事物操作,并且记录事物日志,但是不提交事物;
3、参与者向协调者反馈事物执行的结果;
阶段2:提交阶段(回滚阶段)
提交事物
1、协调者向所有参与者发送事物提交消息;
2、参与者提交事物,释放事物锁定期间的资源;
3、参与者向协调者反馈事物执行成功消息
4、协调者收到各个参与者执行成功的消息,事物就成功
回滚事物
1、协调者向所有参与者发送事物回滚消息;
2、参与者做回滚操作,释放事物锁定期间的资源;
3、参与者向协调者反馈事物执行成功消息
4、协调者收到各个参与者执行成功的消息,事物就中断
2PC协议示意图
阶段1:
阶段2:
2PC协议问题
最大问题
同步阻塞问题
参与者向协调者反馈事物执行的情况,直到等到协调者反馈,是做提交还是回滚操作;之间的时间参与者都是处于阻塞状态;
数据不一致问题
如果参与者或者协调者,一方不可以用,导致事物数据不一致;
3PC协议
背景来源
主要是为了解决2PC中同步阻塞问题;
特点:
非阻塞协议,在实体提交或者终止之前增加一种超时机制,当超过这个时间上线事物还未提交,就会把该事物绑定的资源释放
3PC过程
阶段1:投票
1、协调者接收到事务请求后,向所有参与者发送能否提交请求,等待参与者返回信息
备注:如果协调者在接收事务请求时出现故障或者不可用,协调者将直接中止事务
2、参与者接收到协调者的的是否提交事物请求后,发送是否可以提交事物请求
阶段2:预提交
1、协调者,在超时时间内收到了各个参与者返回的可以提交按钮
备注:如果参与者都同意后会向协调者回复yes消息并进入准备状态,如果参与者获取资源失败或者出现不可用会回复no中止事务
2、协调者,向各个参与者发送预提交消息,参与者执行事物,并且记录日志,但是不做提交操作,并将执行结果返回给协调者;
阶段3:提交或者回滚
1、协调者接受到各个参与者发送回来的消息;
2、根据消息,做出是提交事物,还是回滚事物的决定,然后将将消息发送给各个参与者;各个参与者再根据协调者发送的消息,做回滚或者提交,并释放事物资源,向协调者反馈消息;
事物回滚情况
1、阶段1如果有返回不能提交事物
2、阶段2事物执行失败
3、预提交阶段中,协调者超时未收到消息
自动提交事物
阶段3参与者未收到协调者发送出来的提交或者回滚消息,那么就会自动提交事物
3PC问题
阶段3参与者未收到协调者发送出来的事物回滚的消息,那么参与者都是自动提交,就会造成事物不一致的情况;
实战
定时任务
elastic-job分片
根据业务的不同实现分片的算法,让一个定时任务分散到不同的机器上执行,减少单台机器的压力
dangdang网开源的elastic-job 框架借助zookeeper注册中心,实现分布式任务调度
实战
1、引入相关依赖包
<dependency>
<artifactId>elastic-job-lite-core</artifactId>
<groupId>com.dangdang</groupId>
<version>2.1.5</version>
</dependency>
<dependency>
<artifactId>elastic-job-lite-spring</artifactId>
<groupId>com.dangdang</groupId>
<version>2.1.5</version>
</dependency>
<artifactId>elastic-job-lite-core</artifactId>
<groupId>com.dangdang</groupId>
<version>2.1.5</version>
</dependency>
<dependency>
<artifactId>elastic-job-lite-spring</artifactId>
<groupId>com.dangdang</groupId>
<version>2.1.5</version>
</dependency>
2、zk,job相关配置
reg-center:
# 连接zookeeper服务器列表,多个地址用逗号分隔
server-list: 192.168.28.200:2181
# zk 的命名空间
namespace: elastic-job-lite-springboot
# 等待重试的间隔时间的初始值 默认1000,单位:毫秒
baseSleepTimeMilliseconds: 1000
# 等待重试的间隔时间的最大值 默认3000,单位:毫秒
maxSleepTimeMilliseconds: 3000
# 最大重试次数 默认3
maxRetries: 3
# 会话超时时间 默认60000,单位:毫秒
sessionTimeoutMilliseconds: 60000
# 连接超时时间 默认15000,单位:毫秒
connectionTimeoutMilliseconds: 15000
clockRemindJob:
job-name: clock
# cron表达式,用于控制作业触发时间,默认每间隔10秒钟执行一次 0 */10 * * * ?
cron: 0/10 * * * * ?
# 作业分片总数
sharding-total-count: 1
# 分片序列号和参数用等号分隔,多个键值对用逗号分隔.分片序列号从0开始,不可大于或等于作业分片总数
sharding-item-parameters: 0=全部
# 作业自定义参数,可通过传递该参数为作业调度的业务方法传参,用于实现带参数的作业
job-parameter: "my_parameter_clock"
# 是否开启任务执行失效转移,开启表示如果作业在一次任务执行中途宕机,允许将该次未完成的任务在另一作业节点上补偿执行, 默认为false
failover: true
# 是否开启错过任务重新执行 默认为true
misfire: true
# 作业描述信息
job-description: "clock Task every ten second"
# 连接zookeeper服务器列表,多个地址用逗号分隔
server-list: 192.168.28.200:2181
# zk 的命名空间
namespace: elastic-job-lite-springboot
# 等待重试的间隔时间的初始值 默认1000,单位:毫秒
baseSleepTimeMilliseconds: 1000
# 等待重试的间隔时间的最大值 默认3000,单位:毫秒
maxSleepTimeMilliseconds: 3000
# 最大重试次数 默认3
maxRetries: 3
# 会话超时时间 默认60000,单位:毫秒
sessionTimeoutMilliseconds: 60000
# 连接超时时间 默认15000,单位:毫秒
connectionTimeoutMilliseconds: 15000
clockRemindJob:
job-name: clock
# cron表达式,用于控制作业触发时间,默认每间隔10秒钟执行一次 0 */10 * * * ?
cron: 0/10 * * * * ?
# 作业分片总数
sharding-total-count: 1
# 分片序列号和参数用等号分隔,多个键值对用逗号分隔.分片序列号从0开始,不可大于或等于作业分片总数
sharding-item-parameters: 0=全部
# 作业自定义参数,可通过传递该参数为作业调度的业务方法传参,用于实现带参数的作业
job-parameter: "my_parameter_clock"
# 是否开启任务执行失效转移,开启表示如果作业在一次任务执行中途宕机,允许将该次未完成的任务在另一作业节点上补偿执行, 默认为false
failover: true
# 是否开启错过任务重新执行 默认为true
misfire: true
# 作业描述信息
job-description: "clock Task every ten second"
3、创建zk注册中心配置
/**
* @date 2020-6-7
* zookeeper 注册中心配置类
*/
@Configuration
@ConditionalOnExpression("'${reg-Center.serverList}'.length()>0")
public class ZkRegistryCenterConfig {
@Value("${reg-center.server-list}")
private String serverList;
@Value("${reg-center.namespace}")
private String namespace;
@Bean(initMethod = "init")
public ZookeeperRegistryCenter regCenter(){
return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList,namespace));
}
}
* @date 2020-6-7
* zookeeper 注册中心配置类
*/
@Configuration
@ConditionalOnExpression("'${reg-Center.serverList}'.length()>0")
public class ZkRegistryCenterConfig {
@Value("${reg-center.server-list}")
private String serverList;
@Value("${reg-center.namespace}")
private String namespace;
@Bean(initMethod = "init")
public ZookeeperRegistryCenter regCenter(){
return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList,namespace));
}
}
4、定时任务操作类
@Component
@Slf4j
public class ClockRemindJob implements SimpleJob {
public static int count = 1;
@Override
public void execute(ShardingContext shardingContext){
String jobParameter = shardingContext.getJobParameter();
String time = DateUtil.formatDate(new Date());
System.out.println("当前时间是:"+time+" ,job执行的总次数为:"+count++);
}
}
5、定时任务配置类
@Configuration
@Data
public class ClockRemindJobConfig {
@Value("${clockRemindJob.cron}")
private String cron;
@Value("${clockRemindJob.sharding-total-count}")
private int shardingTotalCount;
@Value("${clockRemindJob.sharding-item-parameters}")
private String shardingItemParameters;
@Value("${clockRemindJob.job-description}")
private String jobDescription;
@Value("${clockRemindJob.job-parameter}")
private String jobParameter;
@Value("${clockRemindJob.job-name}")
private String jobName;
@Autowired
private ClockRemindJob clockRemindJob;
public LiteJobConfiguration liteJobConfiguration(){
JobCoreConfiguration.Builder builder =
JobCoreConfiguration.newBuilder(clockRemindJob.getClass().getName(),cron,shardingTotalCount);
JobCoreConfiguration jobCoreConfiguration = builder
.shardingItemParameters(shardingItemParameters)
.description(jobDescription)
.jobParameter(jobParameter)
.build();
SimpleJobConfiguration simpleJobConfiguration = new
SimpleJobConfiguration(jobCoreConfiguration,clockRemindJob.getClass().getCanonicalName());
return LiteJobConfiguration
.newBuilder(simpleJobConfiguration)
.overwrite(true)
.build();
}
}
6、整合类
/**
* job 的配置,在此类中启动job
*/
@Configuration
public class JobConfig {
@Autowired
private ZookeeperRegistryCenter regCenter;
@Autowired
private ClockRemindJob alarmRemindJob;
@Autowired
private ClockRemindJobConfig clockRemindJobConfig;
// 容器启动的时候初始化该实例化bean
@Bean(initMethod = "init")
public JobScheduler simpleJobScheduler(){
// 把这三个配置组合起来
new SpringJobScheduler(alarmRemindJob,regCenter,clockRemindJobConfig.liteJobConfiguration()).init();
return null;
}
}
定时任何分区
在定时任务开启的之后,根据当前服务的台数,和总的需要执行任务的数量,相除,得到单个服务器需要执行的任务数量。然后再根据
设计一套根据服务器mac地址或者其他唯一标记的数据做hash算法,让服务集群之内的所有机器都有有一个对应的hash值(比如说服务集群有五个服务,那么每一个机器得到一个1-5的数值。)将需要执行的任务的数量,分成5分,每一个机器只执行属于他的那些任务;
秒杀系统设计
秒杀系统特点
秒杀系统特点
瞬间流量激增
正常流量
羊毛党流量
商品定时上架
秒杀商品库存少
商品购买流程简单
秒杀系统业务特点
1.读多写少
缓存
2.高并发
限流
缓存
异步
队列
3.资源冲突
原子操作
异步
秒杀系统存在问题
瞬间流量高,对服务压力造成冲击
请求过多
磁盘IO压力大
商品可能被超卖
设计思路
应用层
浏览器缓存,本地缓存
验证码
网络层
CDN
负载层
负载均衡,动静分离,反向代理缓存,限流
系统上游拦截请求,降低下游服务器压力
服务层
动态页面静态化,应用缓存
|分布式缓存,异步,队列,限流,原子操作保证
后台请求可以做削峰处理
利用缓存替代磁盘IO,提高读写下效率
数据库
乐观锁,悲观锁
商品超卖问题,加全局锁
技术方案实现
定时上架
秒杀时间未到,按钮置灰
上游削峰处理
前端削峰处理
秒杀请求发送前,校验用户是否已经登录
用户已经登录,需做验证码校验
验证码校验完成,按钮置灰,不让发送二次请求
硬件削峰处理
秒杀页面做成静态页面,放在CDN节点上,避免页面刷新请求后台服务
使用LVS 服务做第一层请求转发服务器
使用nginx 服务接受LVS请求,让后再做请求转发
下游削峰处理
网关层,做黑名单校验,防刷校验,不满足条件请求,直接返回
秒杀系统服务集群接受到请求,发送mq消息,到真正做秒杀的服务器
库存防止超卖
使用redis分布式锁
使用redission工具包
将库存量大小作为信号量的构造参数,
数据库悲观锁,乐观锁
高并发的情况下, 悲观锁比乐观锁效率高
相应速度,冲突频率,重试代价
乐观锁基于重试的代价太高了
限流
1.验证码;
2.容器限流
3.数据库相关的参数优化
4.手动限制某些接口的访问量;
限流算法
令牌桶
一秒钟可以产生多少令牌,只要拿到令牌的请求才能接着往下走秒杀流程
Guava 可以做令牌桶,ateLimiter一秒钟可以产生多少令牌,只要拿到令牌的请求才能去接着往下走;
漏桶
限制一秒多少个请求
Nginx 有自带的漏桶算法限流;Nginx.conf 的配置文件就可以配置这个;
5.nginx限流
6.消息队列
设计思路
应用层
浏览器缓存,本地缓存
验证码
网络层
CDN
负载层
负载均衡,动静分离,反向代理缓存,限流
系统上游拦截请求,降低下游服务器压力
服务层
动态页面静态化,应用缓存
|分布式缓存,异步,队列,限流,原子操作保证
后台请求可以做削峰处理
利用缓存替代磁盘IO,提高读写下效率
数据库
乐观锁,悲观锁
商品超卖问题,加全局锁
支付系统
支付的网络传输分为四种结果
1.我方放出信息,对方也能接受到信息;并且能够正确将相应信息返回来
由于网络原因导致三种坏的情况
2.我方虽然执行了发送信息,但是信息并没有发送出去
3.我方虽然将信息发送出去了,但是对方并没有接受到信息;
4.我们将信息发送出去了,对方也接受了,但是我方并没有收到对方的相应信息;
容错处理
1、对方异步回调
1、提供一个异步回调接口,让对方在支付完成之后毁掉我们接口
针对1,4这两种情况
2、对方提供查询接口
让对方提供一个查询接口,方便查询订单实际支付的情况
针对2,3这两种情况
库表设计
分库分表
解决问题
解决数据量过大,导致的性能问题,有原来的单库单表,拆分成多库多表,解决单库单表的数据量过大的问题,从而解决性能问题
方式
垂直切分
按照业务来分库,不同的业务放在不同的数据库
水平切分
单表的数据分在多个库(1.把单表的数据拆成多个单表(字段不变)
在实际引用过程中可以是垂直拆分+水平拆分结合
不同的业务进行垂直拆分,成多个库,相同的业务(如果这个业务很繁忙)又可以分为多个库;
分库分表的时候,需要注意是按照那个字段进行分库的
分库分表造成问题
1.分布式事物的一致性
2.跨节点关联查询
分多次查询,合并查询结果集
3.跨节点分页,排序
分多次查询,合并查询结果集
针对关联查询,有些在分库分表的时候,有些数据会对应分在一个库中,(比如一个商品表,主要信息和详细信息都会存放商品id)那么分库的时候就会把相对应的数据存放在一个数据库中,那么就不用夸节点查询了;
4.主键避重
全局主键
垂直分:1.按照不同的业务分开;2.将单表字段拆分到多个表(常见的查分标准:常用和不常用;大字段,小字段;组合查询的放在一起) 如果把这些表都放在一个库上,就是垂直分表,库在一个服务器上,存在对计算机cpu资源竞争,所以把这些表放在不同的机器上,避免资源竞争,这样就是垂直分库;
mysql主从复制
实现原理
mysql的主从复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新、删除等等)
并且主服务器开启了二进制日志
实现过程
复制过程中有三个线程在执行任务:一个线程在主服务器,负责将二进制日志的内容发送到从服务器(binlog dump线程);
主从复制开始;从服务器上会创建一个io线程,连接主线程并记录二进制日志中的数据到中继日志;中间需要识别binlog dump线程 以获取二进制日志
从服务器会开启一个sql线程(第三个线程),从中继日志中读取日志,并执行日志,以同步数据;
主从复制条件
1、主服务器开启了二进制日志
2、主从服务器的mysql版本之间是可以相互兼容
由于历史原因:
1、二进制日志格式不一样
2、字符集,函数,时区处理不一样
3、主服务器上设置了相应的从服务器
存在问题
1、主从数据库数据一致性问题
假设从库插入的时候异常
2、主从复制的延迟
主从复制解决问题
降低数据库读写压力
写操作比较耗时,读操作时间很短;
读写分离,解决了数据库写入时候,影响查询的效率;通过读写分离提供系统的高并发
内存
怎么计算一个对象的内存大小?
1、通过工具类计算
1、Unsafe类计算
2、使用<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.0.0</version>
</dependency> 包下面的 RamUsageEstimator 类种的方法计算
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.0.0</version>
</dependency> 包下面的 RamUsageEstimator 类种的方法计算
2、通过手动计算
1、计算对象实体数据
1、对象头数据,实例数据,对齐数据
2、计算数组数据
子主题
计算对象都数据,数组长度,实体数据,对齐数据
1.对象的属性;(定义多少字段,需要分配相应的字段) 如果没有字段,内存分配这个也是没有的;
2.对象头;(固定大小的12个字节,这是64位操作系统,32位系统的是4个字节)
3.对齐数据(假如一个对象的实际存储空间的数据,不是8位的整数倍,
但是需要填充空位,填充到8 的整数倍,(这个是64位的虚拟机)) 对齐数据 有时候 有,有时候没有
2.对象头;(固定大小的12个字节,这是64位操作系统,32位系统的是4个字节)
3.对齐数据(假如一个对象的实际存储空间的数据,不是8位的整数倍,
但是需要填充空位,填充到8 的整数倍,(这个是64位的虚拟机)) 对齐数据 有时候 有,有时候没有
内存溢出其他线程还能工作吗?
可以,造成堆内存的线程会被销毁,所以这个线程创建的内存对象也会被回收,
其他线程还是可以获取到充足的内存空间来支持程序运行
其他线程还是可以获取到充足的内存空间来支持程序运行
数据库
sql性能对比
count(*)和count(1) 谁快
1、如果是innerdb 数据库引擎
两者一样快,两者最终执行的时候语句是一样的。但是比count(列名)快
直接遍历索引来查询;
2、myisam数据库引擎
count(*)快于或者等于count(1), myisam存储了表的总行数,可以直接获取总条数;
count(1)如果是第一列不为null,也是直接可以获取总条数,如果可以为null,那么就需要统计不为空的条数
count(列名)只统计不为null的数据,需要遍历整个表
大量数据方案设计
demo
之前待过的公司的权益平台日增数据1000万
设计
1、库表设计
16个库,每个库8个表,共128张表
按照用户id取模,128,分配到不同的库不同的表;
2、dao层
sharding jdbc配置;
3、连接池
每一个项目连接池5-10个
整个对dao层的服务器数量80多个
问题
1、数据倾斜问题
分库分表分的越细,数据倾斜发生的可能就越小;
造成倾斜的主要原因还是分库分表的那个字段的生成规则如果有问题,那么数据倾斜的概率就大;如果用户id都是自增的,那么数据倾斜的概率就很低
2、单表日增近8万数据,不到100天数据量就超过了mysql性能极限
超过一定期限的的权益数据会被DBA清除,且不会再被查询
基础
协议
应用层协议
HTTP
HTTP1.1特点
默认都是长连接
HTTP2.0特点
1.数据都是二进制传输
2.多路复用:多个请求可以复用一个TCP连接
3.请求头压缩
4.服务器可以给客户端推送消息
5.HTTP2.0对安全协议做了升级,数据传输更加安全
http1.1,2.0版本区别
请求头压缩
HTTP2.0服务器客户端都会维护一个请求头索引表;对于之前出现过的请求头,再次发送的时候会直接传递请求头的索引键;HTTP2.0没有请求头压缩
服务端推送
http2.0服务器可以主动给客户端推送数据;
HTTP2.0之前服务器是不能主动给客户端推送数据;
传输格式不同
Http2.0之前都是文本直接传输,文本的格式有很多种;
HTTP2.0之后内容都是转成2进制进行传输
头阻塞问题
Http1.1所有请求都是串行执行;如果前面的请求阻塞了,那么后面的请求也是跟着阻塞,这就是头阻塞问题。Http2.0多路复用也就是多个请求可以在同一个链接上并行执行,根据数据的帧就知道对应某个请求;某个任务阻塞或者是资源消耗大不会影响其他任务
安全性更高
HTTP2.0升级了很多安全协议,使数据传输更加安全
Http1.0和Http1.1区别
HTTP1.0一次http请求就需要建立一次链接,请求完成链接断开
http1.1默认的是长连接,传输层的一次连接可以做多次HTTP请求
报文数据结构
请求报文
请求行
内容
请求方法
路径
协议版本
换行符
回车符+换行符
请求头结束之后会有一空行和请求正文隔开
请求头
请求头字段名称以及对应的值(一个键值对占一行)
换行符
回车符+换行符
请求正文
相应报文
状态行
内容
协议版本
状态码
状态码描述符
换行符
回车符+换行符
响应头部
请求头字段名称以及对应的值(一个键值对占一行)
常见头部
换行符
回车符+换行符
响应正文
http协议弊端
HTTP的消息冗长繁琐
有太多的无用的请求头数据
容易遭到黑客攻击
http复用
一个客户端的多个Http请求复用一个tcp连接
与Tcp复用的区别:tcp复用是多个客户端的请求复用一个tcp连接:http复用是一个客户端的多个http请求复用一个tcp连接;
http内容缓存
把热点图片,静态页面等数据缓存在负载均衡服务器上;
Http压缩
客户端服务端都支持压缩,服务端把数据压缩,再发送给客户端,客户端再解压;
优点:减少了数据包在网络传输的大小
注意事项:一般情况下都是使用负载均衡服务器来做数据的压缩处理,减少web服务器压力;
Http获取数据方式
轮询polling
特点:客户端定时给服务器发送请求,不管是否有数据,都会立即相应
缺点:会浪费大量的请求资源,而获取不到数据
长轮询 long polling
特点:客户端给服务器发送HTTP请求,直到服务端有数据才会相应
HTTP重要头信息
HTTP请求头
Connection:keep-alive
标记请求方式为长连接
Upgrade-lnsecure-Requests
升级为https请求
Content-Length
表示请求消息正文的长度
Accept-Charset
浏览器可接受的字符集
HTTP响应头
Allow
允许的请求方法
Content-Encoding
文档的编码(Encode)方法
Content-Length
表示内容长度
HTTP请求方式
种类
GET
向特定的资源发出请求
PSOT
向指定资源提交数据进行处理请求
DELETE
请求服务器删除Request-URL所标识的资源
PUT
向指定资源位置上传其最新内容
CONNECT
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
HEAD
向服务器索与GET请求相一致的响应,只不过响应体将不会被返回
OPTIONS
返回服务器针对特定资源所支持的HTTP请求方法
TARCE
回显服务器收到的请求,主要用于测试或诊断
post/get区别
从语义角度说get请求获取数据;post请求是提交数据
get请求回退的时候无害,也就是再获取一次数据;post请求就会重新提交一次表单
get请求会被浏览器主动缓存;post请求不会被主动缓存;
get请求只能进行URL编码;post请求支持多种编码方式
get请求的请求参数有长度限制;post请求参数没有长度限制;
get请求相对post请求来说不安全,get请求把请求参数拼接在请求路径中;post请求是把参数request body 里面;
get请求,post请求都有各自特定的请求头和响应头
HTTP请求响应状态码
200 正常
204 请求已经处理,但是没有数据返回
206 请求部分数据
301 资源的路径已经更新了,
302 资源的uri已经临时定位到其他位置了
304 资源已经找到,但是不符合请求条件
307 重定向
400 表示请求的报文中存在语法错误,发生这种错误的时候,需要修改请求报文中的数据才行
404 服务器上没有这个请求的资源,很有可能是请求的路径不对
405 请求方式错误,需要更换请求方式;
500 服务器出问题了,
503服务器正忙,请稍后再做请求
HTTPS
SSL
概述
SSL协议是web浏览器与web服务器之间进行信息交换的协议
SSL协议特点
保密:在握手协议中定义了会话密钥后,所有的消息都被加密。
鉴别:可选的客户端认证,和强制的服务器端认证。
完整性:传送的消息包括消息完整性检查(使用MAC)。
TSL
在SSL基础之上做了优化,弥补了安全漏洞;
工作流程
1.客户端请求建立SSL连接,把自己支持的加密方式发送给服务器
2.网站把自己的公钥和证书发送给客户端
3.客户端把检查证书是否合法;
4.客户端随机生成一个数字作为之后数据交互的密钥;
5.用服务器发送过来的公钥加密生成的密钥,并发送给服务器
6.服务器用客户端生成发送过去的密钥加密发送内容;
7.客户端收到数据类容,然后用生成的密钥解密
HTTPS工程概述
1.用非对称的方式加密数据传输的密钥
2.用对称加密的方式加密传输的数据
SSL加速器
负载均衡服务器上SSL加速芯片专门来处理SSL加密信息;节省了大量的服务器资源
WebSocket
websocket特点
1.只需要一次握手就可以建立持久化的通信
2.客户端和服务器完成连接之后;数据传输不需要再经过中间的代理服务器
3.服务器可以主动给客户端推送数据
推送数据实现方式,回调客户端
webSocket协议使用
利用HTTP建立连接
请求头设置
Upgrade: websocket
告诉服务器应用层服务器换成了websocket协议
Connection: Upgrade
告诉中间代理服务器,建立连接之后,数据传输不需要在经过代理服务器,直接发送给客户端
websocket+http协议关系
1.同属于应用层协议;都是基于tcp,udp传输协议
websocket协议建立连接和断开连接的时候都是依赖于HTTP协议;中间的数据传输与HTTP协议已经没有关系;
webSocket协议解决http性能问题
webSocket的存在简化了tcp连接的三次握手,只需要一次握手就可以建立http数据连接,连接完成之后,再就是以webSocket的协议进行通信,服务端不必等待请求,就可以直接向客户端发送数据。
管线化
绝大多数持久化请求都是通过管线化方式进行的,就是一个请求不用等待响应就可以发送下一个请求,,这样就可以并发的同时发送多个请求
Http,tcp,Socket关系
Http和tcp关系
TCP是传输层协议,HTTP是应用层协议;应用层协议是需要依赖传输层协议传输数据
tcp和Socket关系
socket通信是传输层协议的一种实现;
socket通信的时候可以设置参数
传输层协议是TCP/UDP协议
通信的同步异步
同步
客户端发送请求只会必须等到服务器响应才能发送下一个请求
异步
客户端发送请求,比不等到响应,就可以发送下一个请求
数据读写阻塞非阻塞
阻塞
客户端发送请求,直到服务端有数据才会返回,否则就会一直阻塞在网路读取
非阻塞
客户端发送请求,即使得不到结果,也哦度会都直接返回;
Http和socket关系
socket连接是传输层协议的一种实现;HTTP协议是需要建立在传输层实现之上。
传输协议
udp相对于TCP的优势
1.传输协议简单;
2.传输速度快;
3.网络拥塞少;
tcp/udp区别
1.TCP传输需要建立稳定的连接,消耗系统资源多;udp不需要简历稳定连接,消耗系统资源少
2.TCP数据传输可靠,避免数据丢失,数据重复问题;udp是尽最大努力交付,不能避免数据乱序丢失
3.UDP更适合做高速传输和实时性强的通信;UDP传输速度大于TCP传输
4.TCP只能是一对一通信;UDP可以是一对一,多对一,一对多通信
tcp保证消息可靠性
防丢失:
1.数据包会被拆分成合适大小的数据块发送
3.消息接收方有消息接收确认机制
2.发送超时,发送丢失会重新发送
防阻塞
1.拥塞机制:减少数据量的发送
2.滑动窗口流量控制,接收方来不及接收数据,会让发送方降低发送的速度
3.停止等待协议:发送方发送完一组数据之后,会停下来等到接收方确认消息再发送
防篡改
1.校验和,确认数据在传输过程中没有被篡改
确保消息顺序性
1.发送方发送消息会有一个包的编号,通过包的编号排序,确保消息顺序性
tcp粘包/拆包
粘包拆包原因概述:
TCP协议数据传输没有消息保护边界;UDP是由消息边界保护,所以TCP会存在粘包,拆包问题
粘包
粘包原因
每次数据的发送都必须先发送到固定大小的缓冲池,如果发送的消息大小小于缓冲池大小,那么就会是多条消息集中一起直到缓冲池满了,才发送出去,这样就是一次数据传输发送了多条消息,就是粘包了
发送端粘包
接收方就必须要要学会怎么拆包才能获取到目标数据
拆包
拆包原因
每次数据的发送都必须先发送到固定大小的缓冲池,如果数据包大于缓冲池大小,那么数据包就会被拆分成多个,分词发送,这样就会造成拆包
发送端拆包
接受方就必须要知道怎么粘包来获取一个完整的数据
粘包拆包解决方式
解决思路:通过应用层协议的设置解决
具体解决方式
1.固定每次发送数据包的大小,客户端每次只按照长度读取;
2.设置消息边界,在消息末尾设置特殊标记字符,表示消息结束;
把消息拆成消息头和消息体,在消息头里面设置消息的长度大小
tcp三次握手/四次挥手
三次握手
1.客户端先发送Tcp的 syn (synchronize 标示)的数据包 已经一个初始化seq 序列号 给 服务端;
2.服务端接受到了 syn (synchronize) 数据包 和序列号数据包后,会给客户端把刚刚发送过来的syn(synchronize 同步) 标示的数据包,以及ack(acknowledement回函) 数据包,确认号 seq+1 ,自己初始化的seq 序列号 这些数据发送给客户端。
3.客户端收到所有的数据之后,把客户端发送过来的 ack acknowledement(回函) 数据包 ;服务端发送给客户端的回函 作为序列号 ;服务端发送给客户端的序列号 + 作为回函 发送给客户端
四次挥手
挥手步骤
1.客户端给 服务端发送 Fin (finish结束) 标示 的数据, 以及一个seq序列号数据发送给服务端;此时客户端是 Fin-wait -1 状态
2.服务端接收 到数据后, 给客户端发送,ACK(回函,Fin 的数据作为ack的数据),以及回函号 = seq+1,以及自身的 seq服务端回去通知应用进程,服务端的状态编程 close-wait状态;在这个中间,服务器如果还有一些数据没有发送给客户端,服务器还是可以给客户端发送数据的。
3.服务端没有数据要发送给客户端的时候,服务端会发出, Fin ,ACK 数据, ack 的编号为之前发过的编号,以及一个序列号给客户端,此时客户端状态编程 Fin-wait -2 状态;
4.客户端收到服务端发送的信息之后,会回复 服务员, ACK= 服务端发送的ACK数据,序列号为 服务端的 ack 编号,以及自己的一个序列号;此时,客户端还会延迟 一个两倍 的报文寿命时间,再结束,服务端收到了数据后,再会结束;
为啥需要四次
保证数据的传输的完整性
tcp连接复用
概述
客户端的多个http请求的数据传输是建立在通过一个tcp连接上
实现方式
一个客户端和代理服务器连接,代理服务器再去请求真实服务器;此时代理服务器会和真实服务器建立长连接,其他客户端发送到代理服务器的请求,直接复用这个长连接和真实服务器数据交互;
tcp缓冲
解决的问题:后端服务器网速与客户端网速不匹配,造成服务端资源浪费。客户端网速慢,后端网速快,客户端的慢网速请求,容易拖垮后端服务器;
把客户端的tcp请求全部缓冲在负载均衡服务器里面;将客户端与服务器的一个tcp连接变成了两个tcp连接,分别来适应客户端,服务端的不同的网速;
tcp冷热连接
冷连接
每次Http请求,都是先建立tcp连接,用完就断开连接;下次请求再建立连接;这样一个tcp连接只是为一个http请求服务,不能复用,就是冷连接;
热连接
多个http请求复用一个tcp连接,这就是热连接;Http复用,tcp复用的tcp连接都是热连接;
tcp长连接短连接
短连接
服务器和客户端之间完成一次请求操作客户端或者服务器都可以断开TCP连接
优点
存在连接都是有用连接,不需要额外的控制手段
缺点
需要频繁的建立连接,销毁连接
长连接
服务器和客户端之间完成一次请求之后不会主动断开连接,之后的读写操作都可以复用这个连接
长连接保活
服务器会提供报货功能,当客户端已经消失且连接没有断开,服务端会保留一个半连接,永远等待客户端的数据。
优点
避免频繁的建立连接,断开连接,造成性能好时间的消耗
缺点
建立过多的长连接对服务器的资源也是一种消耗,长连接越多,服务器资源消耗也越大;
协议常见问题
1.建立TCP连接后是否会在一个 HTTP 请求完成后断开?
http1.1之后一个TCP链接可以复用,发送多个http请求
HTTP1.1之前是一个http请求需要对应一个TCP链接
2.一个 TCP 连接可以对应几个 HTTP 请求?
HTTP1.1之前是一个TCP对应一个HTTP;HTTP1.1之后默认是长连接,一个TCP可以用多个HTTP请求
3.一个 TCP 连接中多个HTTP 请求发送是否可以一起发送?
HTTP2.0一个Tcp连接可以处理多个请求,多路复用且不阻塞;HTTP1.1也可以,请求都是线性去执行的,会存在头阻塞问题;
4.HTTP/1.1 时代,浏览器是如何提高页面加载效率的?
1.使用长连接
2.一个页面建立多个连接
5.为什么有的时候刷新页面不需要重新建立 SSL 连接
因为TCP可以是不断开的,所以就不需要重新建立SSL连接
6.浏览器对同一 Host 建立 TCP 连接到数量有没有限制?
谷歌浏览器默认一个host最多建立6个TCP连接;
可以建立多个连接,不同的浏览器有不同的最大限制个数;
其他协议
2c协议
AMQP协议
ZAB协议
概述
ZAB协议是paxos协议的简化版本
实现的功能
确保已经在leader服务器上提交的失误最终被所有的服务器提交
确保再leader重启之后继续同步之前没有完成的数据
实现了leader选举
实现了在最快速度同步到半数节点,并会尽快的把数据同步到所有节点
子主题
jvm
内存
内存溢出
堆内存溢出
内存溢出
堆内存剩余空间小于该对象需要分配的空间
内存泄露
对象一直没有被垃圾回收,造成可用的堆内存越来越少
栈内存溢出
1.栈内存中栈帧过多,栈帧的总内存和超过了当前线程的栈内存大小
递归程序中没有出口,线程加载过多栈帧到栈,导致内存溢出
2.线程分配过度,导致总的栈内存不足以给下一个线程分配栈内存;
方法区+运行时常量池内存溢出
1.方法区
存储的字节码文件大小超过了方法区内存大小
动态创建了大量java类,这些类需要被存储到方法区,导致方法区内存不够
2.常量池内存溢出
程序中动态的创建了大量的基础数据类型和字符串,导致常量池不够分配新创建的常量
本机内存溢出
频繁调用本地方法,创建对象导致本机直接内存不够分配
对象内存分配原则
对象优先在eden区域分配
对象优先在eden区域分配内存
eden区域内存满了,再存放from suvivor区域
长期存活对象进入老年代
新生代对象经历过一定的垃圾回收次数还存活,就会被放到老年代内存中去;
大对象直接进入老年代
内存大小超过一定大小的对象直接放入老年代
相同年纪大小的对象的内存综合超过survivor区域内存一半,那么大于这个年纪的对象都会放在老年代
内存分配担保原则
新生代每一次gc之后,都有可能把存储不下的对象放在老年代;所以老年代会留出一些空间给这些新生代
老年代每次测量进入老年代对象的年纪大小,去做评估预测,老年代剩余空间是否能足够装下下次进入老年代的新生代对象,如果不够,就会做老年代的垃圾回收
运行时数据区域
程序计数器
作用
java的多线程是通过计算机内核线程来回相互切换的,java的线程执行到了某一步,cpu执行权被切换到其他线程上时候,这个程序计数器的作用来了, 就是去记录它所属的线程执行到了哪一步,哪一个指令,执行权再次切换回来的时候,这个计数器就会帮助线程准确无误的接着切换之前的代码接着执行。
特点
1.是线程所独有的
2.生命周期和线程周期相同
3.永远都不会有异常
不存在内存异常的情况
本地方法栈
native方法运行的时候用到的内存空间就是本地方法栈
为java语言调用本地方法,也就是调用native修饰的方法服务的。
方法区
特点
方法区被各个线程所共享
存储内容
也称作永久代,存储的都是,经过虚拟机加载之后的字节码文件,类的信息,常量池,静态变量
运行时常量池,存储了编译期的各种字面量(字面量都是常量池的一部分)
内存回收
方法区由于比较稳定:一般不会进行垃圾回收
但是通常方法区的垃圾回收主要是两部分内容:1.运行时常量池中的常量2.无用的类
1.如果当前系统没有任何位置引用这个常量,那么这个常量就会被常量池清除
2.如果一个类在堆内存都没有他的引用,对应的加载该类的类加载器也被回收,该类对应的字节码文件没有任何地方法被引用,那么该类就会可以被回收。是否被回收还需要虚拟机的参数配置。
栈内存
栈帧
特点
线程所独有,存储的都是临时数据
和线程生命周期一样
3.方法在执行的时候栈内存都会去创建这个方法对应的栈帧,栈栈中存储了这个方法的局部变量表,方法返回值,方法出口等等。我们在调用方法的时候通过方法当中嵌套方法,那么栈内存,同样会为这些方法都去创建对应的栈帧,线程去执行这个栈帧(方法),执行完一个栈帧,这个栈帧对应的内存就会被回收,这就是所谓的弹栈。线程永远都只会在栈内存中最上层的栈帧上执行。并且由于前后调用的方法之间存在着返回值的原因,对应的栈内存中的上下两个栈帧之间也并不是完全割裂的,他们需要返回值的传递。每个栈内存最多可以存储1000-2000个栈帧。所以说。
栈内存大小设置
-Xss128k
给栈内存分配128kb
JVM没有设置总的栈内存大小
操作系统会限制线程的数量,从而达到限制总的栈内存大小
默认大小为1MB
堆内存
堆内存区域划分
新生代
eden
from suvivor
to suvivor
老年代
内存比例
默认的新生代:老年代=1:2
默认eden:from suvivor:to suvivor=8:1:1
比例大小可以通过jvm参数调整
堆内存作用
java存储对象的主要区域
永久代说明
堆内存所说的永久代,只是在jdk1.8版本之前有这个概念,1.8就完全摒弃了这个概念,采用本地硬盘的方式来存储这些数据,有效防止了java这个永久代内存溢出。
jdk1.7
永久代也是属于内存,必须制定大小,大小受限制与所分配的内存大小
jdk1.8
存放在磁盘,可以不指定大小,大小受限制与磁盘
Java对象在内存中存储
分为三部
对象头
Mark Word
记录内容和锁的状态有关
无锁
对象的hashCode值
分代年纪
锁的标记位
是否偏向
偏向锁
偏向线程的id
偏向锁时间戳
分代年纪
锁的标记位
是否偏向
轻量级锁,重量级锁
指向锁的指针
锁的标记位
Gc标记
标记位
指向类的指针
数组长度
实例数据
对齐填充字节
拷贝
深拷贝
基础数据类型拷贝值, 非基础数据类型,新创建对象,并用老对象给新对象字段赋值;
浅拷贝
基础数据类型拷贝值,非基础数据类型拷贝对应的引用
垃圾回收
垃圾收集算法
判断对象是否存活算法
可达性算法
可达性算法过程
GcRoot对象作为起点,向下搜索,搜索走过的路径称为引用链;一个对象到GcRoot没有任何引用链,那么这个对象就是不可达的。
GcRoot对象
new出来的对象,堆内存中的对象
栈内存中栈帧引用的对象
方法区中引用的对象
本地方法区中引用的对象
软引用,弱引用,虚引用
三色标记算法(并发的可达性算法)
概述
三色标记算法是一种垃圾回收的标记算法
作用
减少jvm 的STW(Stop The World),从而达到清除JVM内存垃圾的目的
使用范围
JVM中的CMS、G1垃圾回收器 所使用垃圾回收算法即为三色标记法。
三色标记法过程
黑色:代表该对象以及该对象下的属性全部被标记过了。(程序需要用到的对象,不应该被回收)
灰色:对象被标记了,但是该对象下的属性未被完全标记。(需要在该对象中寻找垃圾)
白色:对象未被标记(需要被清除的垃圾)
三色标记存在问题
并发标记的时候,存在漏标的情况
引用计数算法(被废弃了)
概述
给对象添加一个引用计数器,当程序有地方用到这个对象,计数器+1;引用失效就会-1,任何时候如果引用计数器为0,那么就表示该对象要被回收了
问题
循环依赖,造成内存泄露,最后导致内存溢出
垃圾回收算法
分代收集算法
根据新生代,老年代情况的不同,针对新生代,老年代会有不同的垃圾回收算法;
复制算法(新生代)
复制算法(新生代)
eden区域和一块存有对象的survivor的区域中还存活的对象,会复制到另外一块空闲的survivor区域;如果这块survivor区域内存大小不够,那么还会放在老年代当中;
注意:由于老年代中可以存放新生代的对象,如果此时老年代内存也不够,就会触发老年代的fullgc
新生代使用复制算法原因:新生代的对象存活率比较低,复制起来成本低
复制算法示意图
回收前
回收后
标记-整理算法(老年代)
标记-整理算法(老年代)
老年代不适用复制算法原因:老年代的对象存活率高,如果使用复制算法成本太高
根据可达性算法,把存活的对象会向内存区域的一边做迁移跃动,最后会有一个迁移末端;末端之外的对象就是需要被回收的对象;
标记-整理示意图
回收前
回收后
标记-清除算法示意图
标记-清除算法(老年代)
根据可达性算法需要被回收的对象会被标记,然后堆标记的对象的存错回收;
缺点:
1.标记-清除的效率都不高
2.清除会造成很多内存碎片,有可能导致二级gc;
标记-清除算法示意图
回收前
回收后
综合概述:老年代垃圾回收算法还是选择标记-整理算法
并不是没有被引用的对象就会被回收
如果对象在可达性分析算法的时候,发现从对象到GC Roots对象根本就没有就没有引用链。一般人以为这样,这个对象就会被回收了。其实不然,当一个对象没有到GC Roots的引用链的时候,就会第一次标记,标记之后,还要再进行一次筛选,判断对象有没有的finalize()方法有没有被重写,有没有被执行过;如果该方法有被重写并且也没有被执行过,那么该对象就不会被再一次标记,否则就会被第二次标记(再一次标记),只要被第二次标记的对象,那么他就会被垃圾回收器回收。
垃圾收集器
新生代垃圾回收器
serial
单线程回收,用户线程需要停止
可以搭配的老年代回收器有CMS,serial old
运行的过程:是以单线程形式去收集,收集开始时候,所有的用户线程全部停止,gc线程开始去判断对象是否存活,新生代采用复制算法
运行过程示意图
子主题
PN
多线程回收,用户线程需要停止
parNew收集器是serial收集器的多线程版本,收集算法,停止用户线程,对象分配规则,回收策略都是与serial回收器是一样的
parNew是多线程回收,但是如果遇到的是单核的cpu,那么单个cpu在多个线程之间来回切换回收,那么效率反而还会比单线程的serial的回收器效率还要低
开启垃圾回收时候,是将所有的所有的用户线程全部停止,然后再开启多个回收线程去执行回收的过程。
目前能够搭配的老年代垃圾回收器,有cms
特征:用户线程与gc线程无法并发执行;多线程回收
回收过程示意图
PS
多线程回收,用户线程需要停止
以高吞吐量标准设计的:用户线程时间/(用户线程时间+垃圾回收时间)
特征:用户线程与gc线程无法并发执行;多线程回收;以高吞吐量为标准
回收过程
老年代垃圾回收器
serial-old
老年代的单线程垃圾回收,用户线程需停止
特点
用户线程与gc线程无法并发执行;单线程回收
示意图
子主题
PS-old
老年代多线程回收,用户线程需要停止
以高吞吐量为原则设计:
特点
用户线程与gc线程无法并发执行;多线程回收;以高吞吐量为标准
回收过程示意图
子主题
CMS
多线程回收,以用户线程暂停时间最短为设计标准
回收的时候,用户线程有一段时间不需要停止
回收过程
初始标记:判断对象的引用链;
并发标记:用户线程不停止的情况下,gc线程去判断对象是否存活
重新标记:用户线程停止,多线程去重新标记在并发标记过程中的实际需要手机的对象
并发清除:用户线程在重新标记完成之后重新启动,用户线程与gc线程并发执行
CMS的三大缺点:
1.因为cms的gc线程是在并发标记的时候用户线程也是在执行的。这样就会占用cpu执行用户线程的资源;
2.无法在当次gc处理并发清除时候,用户线程创建的对象,而且还要在gc之前就要预留一部分内存空间出来,给并发清除时候,用户线程去存储对象;
3.CMS垃圾回收算法是标记-清除,这样就会有一个后果就是,会用内存碎片,无法存储大对象,容易引发二次gc.
CMS垃圾回收器工作流程图
示意图
G1回收器
特点
垃圾回收的时候几乎没有stop the world 时间
新生代,老年代都可以回收
可将内存分成很多个大小相同的region区域,根据区域之间使用标记-整理算法,区域内部使用标记复制算法;
可预测垃圾回收时间
很重要的一个特点
对region区域做选择性回收,回收价值高的region区域
垃圾回收过程
1.初始标记:停顿所有的用户线程,标记各个region区域中能被gcroot关联到的对象。
2.并发标记:用户线程和gc线程并行,gc线程根据可达性算法找出存活的对象。
3.最终标记:停顿用户线程,并发执行gc线程去标记刚才用户线程操作引用对象的那部分内存。
4.筛选标记:根据可停顿时间,计算出最优的region区域,并发的对最优的区域进行回收(这个时候用户线程和gc线程是可以并发的)。
垃圾回收过程示意图
示意图
分区region
示意图
region特点
将java的堆内存分成2048个大小相同的region块
region大小特点
每个region 区域的大小都是2的N次幂;即1Mb,2Mb,4MB,
region 区域大小在jvm运行期间都是不会被改变
每个region区域的大小都是相等的
存储特点
每一个region区域只会属于Eden, Survivo,old其中的一种
Eden, Survivo,老年代的区域并不是连续;
新增一种新的内存区域,Humongous内存区域,超过0.5个region对象就会被放Humongous区域
三个过程
Young gc
mixed gc
FGc
G1回收器历史
最早出现在jdk7;jdk9以后就是默认的垃圾回收器
G1回收器缺点
G1回收器本身运行垃圾回收程序时相对cms垃圾回收器需要占用更多的系统cpu,内存资源
在内存小于6G时,cms表现优于G1;大于8G,则G1回收器表现更好;6-8G之间,差不多;
相关JVM参数
-XX :G1HeapRegionSize
设定region 区域大小
-XX:+UseG1GC
手动设置G1垃圾回收器;JDK9默认就是G1
-XX:MaxGCPauseMillis
ZGC
ZGC设计目标
停顿时间不超过10ms;
垃圾回收器停顿时间,不会随着活跃对象增加而增加
支持8MB-16TB堆内存
ZGC历史
jdk11引入
JDK15正式发布
ZGC相对以往垃圾回收器特点
已经没有分代收集的概念了
只能运行在64位操作系统上
ZGC内存空间划分
概述
也会像之前的回收器一样,将内存划分成很多个小的块
具体划分
小页面
固定为2MB
用于存放256KB以下的小对象
中页面
固定位32MB
用于存放256KB以上,4MB以下的对象
大页面
大小不固定,但必须是2MB的2次幂的大小
只会存放大于或者等于4MB的大对象,每一个大页面,只会存储一个大对象
示意图
ZGC支持NUMA
内存访问方式
统一内存访问
以前在64位的计算上,内存控制器没有整合进cpu,对内存的访问都必须要通过北桥芯片来控制,任何cpu对内存的访问速度都是一致。
示意图
支持非统一内存访问
cpu都整合到同一个芯片上,各个cpu对于内存的访问会出现争抢,会导致内存访问瓶颈;为了解决这个,将内存和cpu集成在一个单元上,这个就是非统一内存访问
示意图
在NUMA内存访问模式下,cpu访问本地存储器比访问非本地存储器速度快一些;
ZGC支持NUMA,会优先将小页面分配在本地内存,本地内存不够,然后再从远端的内存进行分配。
分配速度很快,收回也很快
中页面,大页面分配不会分配在cpu的本地内存
染色指针?
作用
处理过程
标记阶段
回收阶段
重定位
ZGC垃圾回收
回收策略
小页面优先回收,中页面,大页面尽量不回收
回收过程
回收过程示意图
具体过程
标记阶段
1、多线程并发标记
2、多线程再次并发标记,处理中间并发阶段(用户线程GC线程同时运行)时候,遗漏的对象
转移阶段
1、将原来活跃的对象复制到新的内存空间上,并将原来的内存空间回收;如果发现某一个页全部是垃圾对象,直接全部回收该区域
2、重定位,新的地址值换到原来的对象上面
读屏障
使用场景
当对象的内存地址被转移的时候,刚号在并发阶段应用程序有需要访问这个对象
解释
JVM向应用代码插入一小段代码的技术
相当于有两个操作要一起做的原子操作
过程
1、对已经转移但是还没重定位的对象进行对象的重定位
2、删除对应对象再转发表中的记录的指针的新旧关系
ZGC的GC时机
预热规则
jvm启动预热,如果从未发生过GC,那么就会在堆内存超过10%,20%,30%的时候触发一次GC,来收集GC的数据
基于分配速率的自适应算法
ZGC根据近期对象的分配速度以及GC时间,计算当内存占用达到什么样的阈值的时候出发下一次GC
基于固定时间间隔
流量平稳的时候,自适应需要堆内存占用达到百分之95的时候才会触发
主动触发
和固定时间规则类似。
内存分配不够
内存已经无法再给新的对象分配内存触发
外部触发
代码中直接System.gc()触发
元数据分配触发
元数据区不足时导致触发
ZGC调优
GC触发时机
ZAllocationSpikeTolerance
估算当前的堆内存分配速率,速度估计越快,GC来的越早,速度估算越慢,GC来的越迟
ZCollectionInterval
定 GC 发生的间隔,以秒为单位触发 GC
GC线程
ParallelGCThreads
Stw阶段,GC线程的数量
ConcGCThreads
并发阶段,cpu的数量
Shenandoah
设计目标
将垃圾回收的停顿时间控制在10ms以内
与G1回收器的设计目标一直:低延时为主要目标
历史
jdk12版本以及以后版本
由redhat公司开发
Shenandoah开发使用了很多G1回收器的代码
Shenandoah与G1相同点
基于region的内存布局
标记阶段都是并发标记
Shenandoah与G1不同点
在最终的回收阶段,采用的是并发整理,由于和用户线程并发执行,因此这一过程不会造成STW,这大大缩短了整个垃圾回收过程中系统暂停的时间
默认情况下不使用分代收集,也就是Shenandoah不会专门设计新生代和老年代,因为Shenandoah认为对对象分代的优先级并不高,不是非常有必要实现
采用“连接矩阵”代替记忆集。在G1以及其他经典垃圾回收器中均采用了记忆集来实现跨分区或者跨代引用的问题,每个Region中都维护了一个记忆集,浪费了很多内存,且导致系统负载也更重,「因此在Shenandoah中摒弃了这种实现方式,而是采用连接矩阵来解决跨分区引用的问题」
Epsilon GC
历史
JDK11正式
工作
只负责内存分配,不负责内存回收
使用场景
性能测试
内存压力测试
虚拟机接口测试
极短寿命的工作
极端延迟敏感的应用
参数配置
-XX:+UnlockExperimentalVMOptions
-XX:+UseEpsilonGC
垃圾回收器搭配使用
serial +old serial
新生代
serial
老年代
old serial
PN + CMS
新生代
par new
老年代
CMS
PS +PO
新生代
PS
老年代
PO
垃圾回收器最佳回收内存大小
serial
几十兆
PS
几个G
CMS
20G
G1
上百G
ZGC
4T
垃圾回收器常用参数
Parallel
XX:+UseSerialGc
XX:+SurvivorRatio
XX:+PreTenureSizeThreshold
XX:+SurvivorRatio
XX:+MaxTenuingThreshold
XX:+SurvivorRatio
XX:+ParallelGCThreads
并行回收垃圾线程数量
-XX:+UseApaptiveSizePolicy
自动选择各区大小比例
CMS
XX:+UseConcMarkSweepGC
使用标记清除算法
-XX:ParallelCMSThreads=n
-XX:CMSInitialingOccupancyFraction
设置GC触发阈值,百分比
-XX:+UseCMSCompactAtFullCollection
再Full GC后,进行一次碎片整理
-XX:CMSFullGCsBeforeCompaction=n
设置几次FullGC后进行一次碎片整理
-XX:+CMSClassUnloadingEnabled
允许对类 元数据进行回收
-XX:CMSInitiatingPermOccupancyFraction=n
当永久区占用达到百分比时,启动CMS回收 (CMS还会根据历史回收情况,判定启动回收不一定会按照百分比阈值)
————————————————
版权声明:本文为CSDN博主「t0mCl0nes」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fly_leopard/article/details/79180783
————————————————
版权声明:本文为CSDN博主「t0mCl0nes」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fly_leopard/article/details/79180783
-XX:+UseCMSinitiatingOccupancyOnly
表示只在达到CMSInitiatingPermOccupancyFraction阈值时,才 进行CMS回收,此时CMS不会根据历史回收情况进行自动回收,只有 达到阈值才会回收
-XX:GCTimeRatio
gc时间占用运行时间比例
-XX:MaxGcPauseMills
停顿时间,是一个建议时间,GC会尝试使用各种手段达到这个时间,比如减小年轻代
G1
-XX:+UseG1GC
-XX:+MaxGCPauseMillis
新生代调整young区域的块的个数达到这个值
新生代回收时间大小
-XX:+MaxGCPauseIntervalMillis
GC间隔时间
-XX:+G1HeapRegionSize
分区大小,size越大,垃圾回收时间越长,GC间隔时间长,也会导致每次GC时间长
1M,2M,4M,8m,16M,32M
-XX:G1NewSizePercent
新生代最小比例,默认5%
-XX:G1MaxNewSizePercent
新生代最大比例,默认60%
-XX:GCTimeRatio
GC时间的建议比例,G1会根据这个值调整整个堆内存大小
-XX:ConcGcThreads
线程数量
-XX:InitiatingHeapOccupancyPercent
启动G1的堆内存占用比例
通用常数
-XX:UseTLAB
使用tlab
-XX:PrintTLAB
打印tlab情况
-XX:TLABSize
设置tlab大小
垃圾回收器和垃圾回收算法关系
垃圾回收器是垃圾回收算法的实现
JDK8及之前版本的垃圾回收器必须遵循,新生代的回收器必须使用复制算法;老年的垃圾回收器使用复制整理,或者复制清除算法;
serial回收器
在新生代就使用复制算法,老年代使用标记整理算法
PN
只在新生代使用,复制算法
PS
新老年代都可以
新生代复制算法;老年代标记整理算法
CMS
只在老年代使用,标记清除算法
垃圾回收时间点
线程运行到安全点,安全区域会进行gc信号检查,也就是是否需要做垃圾回收操作;如果需要做,那么线程都会停止在安全点或者安全区域,等待垃圾回收完成;如果不需要垃圾回收,那么就不会停留在安全点,或者安全区域
安全点设置
设置在一些执行时间长的指令上
安全区域设置
相对于安全点来说,就是指令跨度大的安全点;
垃圾回收时间
jdk8及以前版本
当Eden区或者S区不够用了
当老年代空间不够用了
当方法区不够用了
子主题
jdk9开始
根据垃圾回收器的不同,会有所不同
System.gc()(通知jvm进行一次垃圾回收,具体执行还要看JVM,另外在代码中尽量不要用,毕竟GC一次还是很消耗资源的)
GC日志分析
GC日志demo
[Full GC (Ergonomics) [PSYoungGen: 1375253K->0K(1387008K)] [ParOldGen: 2796146K->2049K(1784832K)] 4171400K->2049K(3171840K), [Metaspace: 3134K->3134K(1056768K)], 0.0571841 secs] [Times: user=0.02 sys=0.04, real=0.06 secs]
Total time for which application threads were stopped: 0.0572646 seconds, Stopping threads took: 0.0000088 seconds
Total time for which application threads were stopped: 0.0572646 seconds, Stopping threads took: 0.0000088 seconds
详细说明
Full GC
表示进行老年代进行了一次GC;老年代进行一次GC,一般情况下,新生代也会进行一次GC
[PSYoungGen: 1375253K->0K(1387008K)]
表示的是年轻代使用Parallel Scavenge这种垃圾回收器回收的
如果是“DefNew”,表示的是serial回收器回收的
如果是“ParNew”,表示的是parNew这种回收期回收的
1375253K
回收前新生代已经试用的内存大小
0K
回收后新生代内存的使用大小
1387008K
新生代区域可使用的最大内存
[ParOldGen: 2796146K->2049K(1784832K)]
表示老年代使用Parallel old这种垃圾回收器回收的
2796146K
回收前老年代已经使用的内存大小
2049K
回收后老年代使用的内存大小
1784832K
老年代可使用的最大内存大小
4171400K->2049K(3171840K)
4171400K
回收前,整个内存区域(新生代+老年代)所使用的内存大小
2049K
回收后整个内存区域(新生代+老年代)使用的大小
3171840K
整个内存区域(新生代+老年代)能够使用的内存有多大
整个区域也可以解释为整个堆内存
def new generation
新生代
tenured
老年代
perm
永久代
minor GC
新生代的GC
full GC 或者是Major GC
老年代的GC
jvm性能调优
调优常见问题
1、jdk1.7,1.8,1.9默认的垃圾回收器是什么?
jdk1.7 默认垃圾收集器PS(新生代)+PO(老年代)
jdk1.8 默认垃圾收集器PS(新生代)+PO(老年代)
G1回收器
2、常见的HotSpot垃圾回收器组合有哪些?
serial +old serial
新生代
serial
老年代
old serial
PN + CMS
新生代
par new
老年代
CMS
PS +PO
新生代
PS
老年代
PO
3、所谓调优,到底是在调什么?
1、提高用户线程吞吐量
2、提高用户线程相应时间
4、PN +CMS 怎样才能让系统基本不产生FGC
扩大JVM内存
调整内存比例
加大新生代区间比例
提高去Survivor区域比例
提高新生代到老年代的年纪
避免代码内存泄露
5、PS +PO 怎样才能让系统基本不产生FGC
避免代码内存泄露
6、G1回收器是否分代?G1回收器会产生FGC吗?
不分代收集,在G1回收器概念里面,已经没有新生代,老年代的概念,所有的堆内存区域划分为不同的区域
会发生FGC
7、如果G1回收器发生FGC?
1、扩大内存
2、提高CPU性能(可以提高回收效率)
3、降低MixedGC 触发的阈值,让MixedGc提早发生
8、生产环境可以随随便便dump吗?
小的堆内存影响不大;大的堆内存会有服务器卡顿
9、常见OOM问题有哪些?
栈,堆,方法区直接内存溢出
10、造成线程停顿的原因
1.线程在等待外部资源:数据库,网络资源,设备资源
2.死循环,
3.锁等待,死锁情况。
jvm调优实战
线程
案例1、
CPU经常飙升100%,如何调优
解决步骤
找出哪个进程的CPU高
top 命令
找出该进程中哪个线程cpu高
top -hp命令
导出该线程的堆栈信息
jstack
线程线程占比和垃圾回收线程的占比对比
案例2、
假如100个线程很多线程都在等待,找到持有这把锁的线程
1、找到java的线程,看有哪些线程正在运行,然后拷贝出来运行的线程的唯一标记
2、再去等待线程中,找这些线程等到的是哪个线程,拿出这些线程id和运行的线程对比,,可以找到持有锁的线程;
内存
案例1、
案例1
1、为啥原网站很慢
文档加载斤内存中行程的java对象导致内存不足,频繁GC,导致stw时间过长
2、内存加大之后更卡顿
内存大,然后FGC时间过长,导致用户线程暂停
从PS垃圾回收器,设置成PN+CMS 或者G1回收器
子主题
案例2、
内存消耗不超过10%,频繁发生FGC?
子主题
案例3、
线程池使用不当,导致OOM
JVM调优的目的
JVM调优的目的
1、吞吐量
2、响应时间
1、减少垃圾回收时候的stop the world的时间
调优前需要明确是以高吞吐量为要求,还是以响应时间低为要求;还是说满足一定的相应时间的情况下,达到多少的吞吐量
垃圾回收器选择
吞吐量优先
PS+PO回收器
场景
数据计算
数据挖掘
追求响应时间
G1回收器
场景
网站
API
jvm编译
编译概述
1.各种不同平台的虚拟机,与所有平台都同一使用的是程序存储格式--字节码
2.java语言中的关键字,变量,运算符符号的最终语义最终都会编译成多条字节码指令;
3.可使用工具打开字节码文件可以看到字节码文件的严谨的格式
class文件存储
class文件存储的是java类的字段,方法,类的路径,名称,常量
字段,常量之间会有一些相互引用的关系,这个关系是在class文件在存储的时候动态绑定
类存储概述
14种表的首位都是标示位,标示该表示属于哪种类型的表,也就是说该表示属于哪种常量类型
类的方法,方法的入参,回参;实现的接口,继承的类,字段名称,字段数据类型,等等数据都会被分们别类的存储到对应的表中。他们之间又会有相应的数据依赖的关系,比如说一个方法的入参是什么类型,方法的修饰符这些都不存在方法表中,而是存在对应的类型表,字段名称表,等等
在存储class字节码的时候,储存的常量池中的数据是以表(这里的表和数据库的表不是一个概念,但是从从关联关系上也很像关系型数据库)的形式存储,虚拟机提供了14种表的结构来存储包括所有的常量池的数据
class文件概述
1.java文件能够被虚拟机编译并不是靠着以“java”为后缀的文件名来识别,而是这类文件在创建的时候就已经赋予了它“魔数”,java文件的魔数是“0xCAFFBABE”,文件在创建的时候这个魔数已经就确定了,任凭我们怎么修改文件的后缀,这样的文件也是能被jvm虚拟机编译的
2.class文件的储存内容
2.1前四个字节:存储的是文件的魔数,虚拟机在加载java文件的时候不是以后缀名为识别,而是以文件中前4个字节这个魔数是不是java对应的魔数来识别,识别中了,那么该文件就能被虚拟机编译
2.2接下的四个字节:第五,第六位是次版本号;第七第八是主版本号,主版本号是和jjdk的版本有关的,jdk1.1开始主版本号是45,现在的版本号是52+
2.3再接着就是字节码的常量池(字节码的常量池,说明每个class文件都有自己的常量池,有个疑问常量池是共有的,这个看以后是怎么解释这个的):
常量池存的是:字面量和符号引用
字面量:可以理解为java的常量,比如字符串,final修饰的常量
符号引用:
1.类,接口的全路径
2.字段的名称和描述
3.方法的名称和描述
符号引用存储的数据可以反编译获得到类的方法和字段,已经类名,接口名,路径
重排序
1.编译器优化重排序
2.指令重排
原因
编译器对代码进行空间复杂度时间复杂度的优化,导致了代码实际的执行顺序和书写的不一样;
现象
程序先执行下面的代码,后执行上面的代码
指令重排原则
重排序的代码没有先后逻辑关系
重排序不影响结果
3.内存系统重排序
类加载机制
字节码增强技术
概述
作用
对java的字节码进行修改,增强功能
操作
修改二进制的class文件
目的
减少冗余代码,提高性能,加密代码
实现方式
AspectJ
概述
修改java字节码工具类
作用时间:编译期
Javassist
概述
修改java字节码的工具库
功能
可以直接生成一个类
在已经编译的类里面添加新方法,修改方法
修改时间:运行期
ASM
概述
ASM是一个java字节码操作框架
功能
直接生成class文件
拦截java文件被类加载器加载之前修改类
修改时间:编译期
子主题
APT
注解编译器;
双亲委派机制
类加载器种类
BootstrapClassLoader
加载java核心库 java.*构建ExtClassLoade,AppClassLoader
ExtClassLoader
加载扩展库,如classpath中的jre ,javax.*
AppClassLoader
加载项目代码所在目录的class文件
自定义加载器
用户自定义的类加载器,可加载指定路径的class文件
加载器的区别
双亲委派机制作用
1.防止类被多个类加载器加载
2.保证核心的class文件不被篡改
双亲委派机制
类加载器需要加载类的时候,会先去让他的父类去加载;父类如果没有加载这个类,也没有权限加载,那么就会子类加载;如果有加载权限,那么就会接着让他的父类再做次判断;
双亲委派机制流程
三个重要判断
当前类是否已经被加载
当前类加载器是否还有父类加载器
当前类加载器是否有权限加载这个类
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException
示意图
子主题
双亲委派机制缺陷
对功能的实现有时候会比较局限,spi机制就是打破了双亲委派机制;
spi机制
spi
JAVA内置一种服务发现机制
使用场景
根据实际需求替换,扩展框架源码的实现策略
spi实现过程
调用ServiceLoad.load方法,传入接口字节码对象
根据字节码类型到meta-info包下面找到对应的配置文件
读取配置文件中的类全路径
获得类的全路径,通过反射获取到对象
把对象存储到linkedHashMap中
spi优缺点
优点
能够让接口更方便找到拓展的实现类
缺点
不能单独获取这一个实现类,获取了接口的所有实现类,造成了性能消耗
spi配置过程
实现某个想要实现的接口
resource包下配置相关的包路径和文件名
文件中配置类的全路径
spi机制破坏了双亲委派机制
spi机制创建对象的类加载器是classLoad,和传统的类加载器不存在父子关系
spi机制加载接口实现类的加载器默认的都是ClassLoader(java lang 包下面)
对象创建过程
1.将对象的字节码文件加载进内存
2.栈内存给对象分配一个引用变量
3.堆内存开辟一块内存空间
4.给堆内存对象属性做默认初始化赋值
5.给对象属性做显示初始化
6.对代码块进行初始化
1.父类构造代码块
2.当前类的构造代码块
3.父类的构造方法
4.当前类的构造方法
7.将堆内存的地址赋值给栈内存的引用
编译器
jit热点编译
将频繁调用的代码直接加载进内存,无需从硬盘上去加载这些文件
主流编译器
sun公司hotspot
从jdk10之后,JIT有三款
C1
客户端编译器
C2
服务端编译器
Graal 编译器
这三款编译器都是热点编译器,jdk17已经删除了Graal 编译器
还有其他公司开发的,也就是其他有自己开发的openjdk
编译器组成
词法解析
词法分析器作用
1、规定哪些词是符合java语言规范的(com.sun.tools.javac.parser.JavacParser )
parseCompilationUnit 方法做了这个解析过程
2、逐个读取java源文件中的单个字符,归类不同的语法(com.sun.tools.javac.parser.Scanner )
3、规定了所有java语言的合法关键词;(包括了所有的关键字,符号com.sun.tools.javac.parser.Token )
4、用来存储和表示解析后的词法(com.sun.tools.javac.util.Name.Table类种 );形成语法树
规定java代码解析规则
package 语法,import语法,类定义,field定义,method定义,变量定义,表达式定义,除了这些规则就是用户自定义的包名,类名,方法名,字段名,变量名,等等。判断这些字符能否组成一个合法的Token 是由Token类种的nextToken规定的
词法解析后回输出token流
子主题
token类中字符集合
public static enum TokenKind implements Formattable, Filter<Tokens.TokenKind> {
EOF,
ERROR,
IDENTIFIER(Tokens.Token.Tag.NAMED),
ABSTRACT("abstract"),
ASSERT("assert", Tokens.Token.Tag.NAMED),
BOOLEAN("boolean", Tokens.Token.Tag.NAMED),
BREAK("break"),
BYTE("byte", Tokens.Token.Tag.NAMED),
CASE("case"),
CATCH("catch"),
CHAR("char", Tokens.Token.Tag.NAMED),
CLASS("class"),
CONST("const"),
CONTINUE("continue"),
DEFAULT("default"),
DO("do"),
DOUBLE("double", Tokens.Token.Tag.NAMED),
ELSE("else"),
ENUM("enum", Tokens.Token.Tag.NAMED),
EXTENDS("extends"),
FINAL("final"),
FINALLY("finally"),
FLOAT("float", Tokens.Token.Tag.NAMED),
FOR("for"),
GOTO("goto"),
IF("if"),
IMPLEMENTS("implements"),
IMPORT("import"),
INSTANCEOF("instanceof"),
INT("int", Tokens.Token.Tag.NAMED),
INTERFACE("interface"),
LONG("long", Tokens.Token.Tag.NAMED),
NATIVE("native"),
NEW("new"),
PACKAGE("package"),
PRIVATE("private"),
PROTECTED("protected"),
PUBLIC("public"),
RETURN("return"),
SHORT("short", Tokens.Token.Tag.NAMED),
STATIC("static"),
STRICTFP("strictfp"),
SUPER("super", Tokens.Token.Tag.NAMED),
SWITCH("switch"),
SYNCHRONIZED("synchronized"),
THIS("this", Tokens.Token.Tag.NAMED),
THROW("throw"),
THROWS("throws"),
TRANSIENT("transient"),
TRY("try"),
VOID("void", Tokens.Token.Tag.NAMED),
VOLATILE("volatile"),
WHILE("while"),
INTLITERAL(Tokens.Token.Tag.NUMERIC),
LONGLITERAL(Tokens.Token.Tag.NUMERIC),
FLOATLITERAL(Tokens.Token.Tag.NUMERIC),
DOUBLELITERAL(Tokens.Token.Tag.NUMERIC),
CHARLITERAL(Tokens.Token.Tag.NUMERIC),
STRINGLITERAL(Tokens.Token.Tag.STRING),
TRUE("true", Tokens.Token.Tag.NAMED),
FALSE("false", Tokens.Token.Tag.NAMED),
NULL("null", Tokens.Token.Tag.NAMED),
UNDERSCORE("_", Tokens.Token.Tag.NAMED),
ARROW("->"),
COLCOL("::"),
LPAREN("("),
RPAREN(")"),
LBRACE("{"),
RBRACE("}"),
LBRACKET("["),
RBRACKET("]"),
SEMI(";"),
COMMA(","),
DOT("."),
ELLIPSIS("..."),
EQ("="),
GT(">"),
LT("<"),
BANG("!"),
TILDE("~"),
QUES("?"),
COLON(":"),
EQEQ("=="),
LTEQ("<="),
GTEQ(">="),
BANGEQ("!="),
AMPAMP("&&"),
BARBAR("||"),
PLUSPLUS("++"),
SUBSUB("--"),
PLUS("+"),
SUB("-"),
STAR("*"),
SLASH("/"),
AMP("&"),
BAR("|"),
CARET("^"),
PERCENT("%"),
LTLT("<<"),
GTGT(">>"),
GTGTGT(">>>"),
PLUSEQ("+="),
SUBEQ("-="),
STAREQ("*="),
SLASHEQ("/="),
AMPEQ("&="),
BAREQ("|="),
CARETEQ("^="),
PERCENTEQ("%="),
LTLTEQ("<<="),
GTGTEQ(">>="),
GTGTGTEQ(">>>="),
MONKEYS_AT("@"),
CUSTOM;
public final String name;
public final Tokens.Token.Tag tag;
private TokenKind() {
this((String)null, Tokens.Token.Tag.DEFAULT);
}
private TokenKind(String var3) {
this(var3, Tokens.Token.Tag.DEFAULT);
}
private TokenKind(Tokens.Token.Tag var3) {
this((String)null, var3);
}
private TokenKind(String var3, Tokens.Token.Tag var4) {
this.name = var3;
this.tag = var4;
}
public String toString() {
switch(this) {
case IDENTIFIER:
return "token.identifier";
case CHARLITERAL:
return "token.character";
case STRINGLITERAL:
return "token.string";
case INTLITERAL:
return "token.integer";
case LONGLITERAL:
return "token.long-integer";
case FLOATLITERAL:
return "token.float";
case DOUBLELITERAL:
return "token.double";
case ERROR:
return "token.bad-symbol";
case EOF:
return "token.end-of-input";
case DOT:
case COMMA:
case SEMI:
case LPAREN:
case RPAREN:
case LBRACKET:
case RBRACKET:
case LBRACE:
case RBRACE:
return "'" + this.name + "'";
default:
return this.name;
}
}
public String getKind() {
return "Token";
}
public String toString(Locale var1, Messages var2) {
return this.name != null ? this.toString() : var2.getLocalizedString(var1, "compiler.misc." + this.toString(), new Object[0]);
}
public boolean accepts(Tokens.TokenKind var1) {
return this == var1;
}
}
EOF,
ERROR,
IDENTIFIER(Tokens.Token.Tag.NAMED),
ABSTRACT("abstract"),
ASSERT("assert", Tokens.Token.Tag.NAMED),
BOOLEAN("boolean", Tokens.Token.Tag.NAMED),
BREAK("break"),
BYTE("byte", Tokens.Token.Tag.NAMED),
CASE("case"),
CATCH("catch"),
CHAR("char", Tokens.Token.Tag.NAMED),
CLASS("class"),
CONST("const"),
CONTINUE("continue"),
DEFAULT("default"),
DO("do"),
DOUBLE("double", Tokens.Token.Tag.NAMED),
ELSE("else"),
ENUM("enum", Tokens.Token.Tag.NAMED),
EXTENDS("extends"),
FINAL("final"),
FINALLY("finally"),
FLOAT("float", Tokens.Token.Tag.NAMED),
FOR("for"),
GOTO("goto"),
IF("if"),
IMPLEMENTS("implements"),
IMPORT("import"),
INSTANCEOF("instanceof"),
INT("int", Tokens.Token.Tag.NAMED),
INTERFACE("interface"),
LONG("long", Tokens.Token.Tag.NAMED),
NATIVE("native"),
NEW("new"),
PACKAGE("package"),
PRIVATE("private"),
PROTECTED("protected"),
PUBLIC("public"),
RETURN("return"),
SHORT("short", Tokens.Token.Tag.NAMED),
STATIC("static"),
STRICTFP("strictfp"),
SUPER("super", Tokens.Token.Tag.NAMED),
SWITCH("switch"),
SYNCHRONIZED("synchronized"),
THIS("this", Tokens.Token.Tag.NAMED),
THROW("throw"),
THROWS("throws"),
TRANSIENT("transient"),
TRY("try"),
VOID("void", Tokens.Token.Tag.NAMED),
VOLATILE("volatile"),
WHILE("while"),
INTLITERAL(Tokens.Token.Tag.NUMERIC),
LONGLITERAL(Tokens.Token.Tag.NUMERIC),
FLOATLITERAL(Tokens.Token.Tag.NUMERIC),
DOUBLELITERAL(Tokens.Token.Tag.NUMERIC),
CHARLITERAL(Tokens.Token.Tag.NUMERIC),
STRINGLITERAL(Tokens.Token.Tag.STRING),
TRUE("true", Tokens.Token.Tag.NAMED),
FALSE("false", Tokens.Token.Tag.NAMED),
NULL("null", Tokens.Token.Tag.NAMED),
UNDERSCORE("_", Tokens.Token.Tag.NAMED),
ARROW("->"),
COLCOL("::"),
LPAREN("("),
RPAREN(")"),
LBRACE("{"),
RBRACE("}"),
LBRACKET("["),
RBRACKET("]"),
SEMI(";"),
COMMA(","),
DOT("."),
ELLIPSIS("..."),
EQ("="),
GT(">"),
LT("<"),
BANG("!"),
TILDE("~"),
QUES("?"),
COLON(":"),
EQEQ("=="),
LTEQ("<="),
GTEQ(">="),
BANGEQ("!="),
AMPAMP("&&"),
BARBAR("||"),
PLUSPLUS("++"),
SUBSUB("--"),
PLUS("+"),
SUB("-"),
STAR("*"),
SLASH("/"),
AMP("&"),
BAR("|"),
CARET("^"),
PERCENT("%"),
LTLT("<<"),
GTGT(">>"),
GTGTGT(">>>"),
PLUSEQ("+="),
SUBEQ("-="),
STAREQ("*="),
SLASHEQ("/="),
AMPEQ("&="),
BAREQ("|="),
CARETEQ("^="),
PERCENTEQ("%="),
LTLTEQ("<<="),
GTGTEQ(">>="),
GTGTGTEQ(">>>="),
MONKEYS_AT("@"),
CUSTOM;
public final String name;
public final Tokens.Token.Tag tag;
private TokenKind() {
this((String)null, Tokens.Token.Tag.DEFAULT);
}
private TokenKind(String var3) {
this(var3, Tokens.Token.Tag.DEFAULT);
}
private TokenKind(Tokens.Token.Tag var3) {
this((String)null, var3);
}
private TokenKind(String var3, Tokens.Token.Tag var4) {
this.name = var3;
this.tag = var4;
}
public String toString() {
switch(this) {
case IDENTIFIER:
return "token.identifier";
case CHARLITERAL:
return "token.character";
case STRINGLITERAL:
return "token.string";
case INTLITERAL:
return "token.integer";
case LONGLITERAL:
return "token.long-integer";
case FLOATLITERAL:
return "token.float";
case DOUBLELITERAL:
return "token.double";
case ERROR:
return "token.bad-symbol";
case EOF:
return "token.end-of-input";
case DOT:
case COMMA:
case SEMI:
case LPAREN:
case RPAREN:
case LBRACKET:
case RBRACKET:
case LBRACE:
case RBRACE:
return "'" + this.name + "'";
default:
return this.name;
}
}
public String getKind() {
return "Token";
}
public String toString(Locale var1, Messages var2) {
return this.name != null ? this.toString() : var2.getLocalizedString(var1, "compiler.misc." + this.toString(), new Object[0]);
}
public boolean accepts(Tokens.TokenKind var1) {
return this == var1;
}
}
语法解析
语法解析器工作
词法分析后通过TreeMaker类生成的每一个Name对象生成,都会造成一个对应节点的语法树
所有的关键词,都会有一个对应的语法树对象,所有的语法树对象都是com.sun.tools.javac.tree.JCTree类的子类
语法解析内容
1、解析Package节点
2、解析import节点
JCTree importDeclaration() {
int var1 = this.token.pos;
this.nextToken();
boolean var2 = false;
if (this.token.kind == TokenKind.STATIC) {
this.checkStaticImports();
var2 = true;
this.nextToken();
}
JCExpression var3 = (JCExpression)this.toP(this.F.at(this.token.pos).Ident(this.ident()));
do {
int var4 = this.token.pos;
this.accept(TokenKind.DOT);
if (this.token.kind == TokenKind.STAR) {
var3 = (JCExpression)this.to(this.F.at(var4).Select(var3, this.names.asterisk));
this.nextToken();
break;
}
var3 = (JCExpression)this.toP(this.F.at(var4).Select(var3, this.ident()));
} while(this.token.kind == TokenKind.DOT);
this.accept(TokenKind.SEMI);
return this.toP(this.F.at(var1).Import(var3, var2));
}
int var1 = this.token.pos;
this.nextToken();
boolean var2 = false;
if (this.token.kind == TokenKind.STATIC) {
this.checkStaticImports();
var2 = true;
this.nextToken();
}
JCExpression var3 = (JCExpression)this.toP(this.F.at(this.token.pos).Ident(this.ident()));
do {
int var4 = this.token.pos;
this.accept(TokenKind.DOT);
if (this.token.kind == TokenKind.STAR) {
var3 = (JCExpression)this.to(this.F.at(var4).Select(var3, this.names.asterisk));
this.nextToken();
break;
}
var3 = (JCExpression)this.toP(this.F.at(var4).Select(var3, this.ident()));
} while(this.token.kind == TokenKind.DOT);
this.accept(TokenKind.SEMI);
return this.toP(this.F.at(var1).Import(var3, var2));
}
3、解析整个class节点:包括 变量,方法,内部类解析,整个类体的解析的结果都会被保存在一个list集合中,然后把这些节点都填机到 com.sun.tools.javac.tree.JCTree.JCClassDecl语法树中去
protected JCClassDecl classDeclaration(JCModifiers var1, Comment var2) {
int var3 = this.token.pos;
this.accept(TokenKind.CLASS);
Name var4 = this.ident();
List var5 = this.typeParametersOpt();
JCExpression var6 = null;
if (this.token.kind == TokenKind.EXTENDS) {
this.nextToken();
var6 = this.parseType();
}
List var7 = List.nil();
if (this.token.kind == TokenKind.IMPLEMENTS) {
this.nextToken();
var7 = this.typeList();
}
List var8 = this.classOrInterfaceBody(var4, false);
JCClassDecl var9 = (JCClassDecl)this.toP(this.F.at(var3).ClassDef(var1, var4, var5, var6, var7, var8));
this.attach(var9, var2);
return var9;
}
int var3 = this.token.pos;
this.accept(TokenKind.CLASS);
Name var4 = this.ident();
List var5 = this.typeParametersOpt();
JCExpression var6 = null;
if (this.token.kind == TokenKind.EXTENDS) {
this.nextToken();
var6 = this.parseType();
}
List var7 = List.nil();
if (this.token.kind == TokenKind.IMPLEMENTS) {
this.nextToken();
var7 = this.typeList();
}
List var8 = this.classOrInterfaceBody(var4, false);
JCClassDecl var9 = (JCClassDecl)this.toP(this.F.at(var3).ClassDef(var1, var4, var5, var6, var7, var8));
this.attach(var9, var2);
return var9;
}
所有节点解析完成之后都会被合并到com.sun.tools.javac.tree.JCTree.JCCompilationUnit里面去
语法分析器最后输出抽象语法树
子主题
子主题
语义解析
最终处处经过注解的抽象语法树
添加默认方法带代码
if ((var2.flags() & 512L) == 0L && !TreeInfo.hasConstructors(var5.defs)) {
List var46 = List.nil();
List var48 = List.nil();
List var50 = List.nil();
long var18 = 0L;
boolean var20 = false;
boolean var21 = true;
JCNewClass var22 = null;
if (var2.name.isEmpty()) {
var22 = (JCNewClass)var4.next.tree;
if (var22.constructor != null) {
var21 = var22.constructor.kind != 63;
Type var23 = this.types.memberType(var2.type, var22.constructor);
var46 = var23.getParameterTypes();
var48 = var23.getTypeArguments();
var18 = var22.constructor.flags() & 17179869184L;
if (var22.encl != null) {
var46 = var46.prepend(var22.encl.type);
var20 = true;
}
var50 = var23.getThrownTypes();
}
}
if (var21) {
MethodSymbol var52 = var22 != null ? (MethodSymbol)var22.constructor : null;
JCTree var24 = this.DefaultConstructor(this.make.at(var5.pos), var2, var52, var48, var46, var50, var18, var20);
var5.defs = var5.defs.prepend(var24);
}
}
作用
1.构建符号表
通过com.sun.tools.javac.comp.Enter类完成符号表的构建
1.1把类种出现的富豪输入到自身的符号表中,把所有的类符号,类的参数类型符号,超类符号,和继承符号都存储到一个未处理的列表中
1.2将这个未处理的猎豹中所有的类都解析到各自类符号列表中去,这个操作是在MemberEnter.complete();
在这个步骤又做了一件很重要的时候就是添加默认的构造函数,(com.sun.tools.javac.comp.MemberEnter类来完成);
2.处理注解
注解是由com.sun.tools.javac.processing.JavacProcessingEnvironment类来完成;
3.标注
1标注是由com.sun.tools.javac.comp.Attr类来完成,主要检查:1.变量的类型是否匹配,2.变量在使用前是否已经初始化,3.推导出方法泛型的参数类型,4.字符串常量的合并;
2.变量类型是否匹配,(com.sun.tools.javac.comp.Check);
3检查变量,方法,类是否的访问是否合法,变量是否是静态,变量是否已经初始化(com.sun.tools.javac.comp.Resolve)
4.优化合并变量,(com.sun.tools.javac.comp.ConstFold)
5.推导出方法的泛型的参数类型(com.sun.tools.javac.comp.Infer)
4.数据流分析
(检查异常是否捕捉或者是抛出,去掉无用代码,自动拆装箱,去除语法糖)(com.sun.tools.javac.comp.Flow)
1.变量在使用前是否已经被正确的赋值了,原始数据类型是否已经有默认初始化值了;
2.保证final修饰的变量不会被重新赋值,经过final修饰的变量只能被赋值一次,否则就在编译的时候报错;变量如果是静态的,就必须保证这个变量在定义的时候就被复制;
3.确定方法的返回值,以及检查方法的返回值是否正确;检查接受这个方法的数据类型是否正确。
4.异常是否都抛出或者是被捕捉了;
5.检查return 后面是否有语句;
6.去掉无用的代码,比如说只有永远为假的if;
7.变量的自动拆装箱;
8.去除语法糖,比如说:将foreach形式编程更简单的for循环;
由于语法分析后的语法树,还比较粗糙,距离目标的字节码文件还有一些距离,需要在这个语法树上做一些优化操作
字节码生成器
字节码文件
作用
1.将java方法中的代码块转成进栈,出栈的符合jvm语法的命令形式
2.按照jvm字节码文件的标准输出到以class文件为拓展名的文件中
工作过程
调用com.sun.tools.javac.jvm.Gen类来遍历语法树,生成最终的字节码文件
demo
public class DaiMa {
public static void main(String[] args) {
int rt = add(a+b);
}
public static void add(Integer a ,Integer b){
return a+b ;
}
}
public static void main(String[] args) {
int rt = add(a+b);
}
public static void add(Integer a ,Integer b){
return a+b ;
}
}
这个add方法是如何生成字节码文件的
1.把成左边表达式的记过,将结果转成int类型;
2.把上面的结果放在当前栈中;
3.计算右表表达式的结果,把他转成int类型;
4.把上述的结果放在当前栈中,
5.弹出 + 操作符;
6.把操作结果返回给当前栈栈顶;
1.把成左边表达式的记过,将结果转成int类型;
2.把上面的结果放在当前栈中;
3.计算右表表达式的结果,把他转成int类型;
4.把上述的结果放在当前栈中,
5.弹出 + 操作符;
6.把操作结果返回给当前栈栈顶;
编译过程
1、从java源码中找出一些符合java规范的一些关键词,比如说:if,else 等等,通过词法分析器最终输出的是数据称作是Token流
2、从Token流中检查这些关键词是否符合java语法规范,然后语法分析器将词法结构化的组织起来形成一个抽象语法树;
3、语义分析器将象语法树中的一些难懂的,复杂的语法转换成更加简单的语法,这个经过注解过的语法树更加接近目标语言的语法规则
4、代码生成器将将经过注解的抽象语法树,生成对应的字节码
过程示意图
子主题
.java的编译器,是java写的。那么java 编译器的代码 是 自己编译自己??
java代码要能执行必须要被编译才行,所以就是自己编译自己后,再去编译其他需要执行的代码,将代码转换成计算机能够识别的代码
jvm参数
jvm日志信息参数
即时编译参数
多线程相关参数
类型加载参数
性能参数
调试参数
内存管理参数
jvm监控
监控工具
jdk自带的命令行工具。
visualvm工具。
第三方工具
cat
普罗米修斯
java内存模型
内存模型概述
java是跨平台的,需要同一的内存模型来兼容不同不同的操作系统的差异,硬件差异等等。不能因为操作系统硬件的差异导致相同的程序出现不一样的结果
主内存+工作内存概述
线程都有自己的工作内存;每个线程的工作内存之间都是相互屏蔽;
线程操作变量,都是先通过工作内存,然后复制到主内存;其他线程才能再主内存中访问这个变量;
主内存,是可以被所有的线程访问。
主内存+工作内存之间数据交互
1.交互概述
1.线程的工作内存去访问主内存,获取变量值
2.内存之间的基本操作都是原子操作,不可再分割
8大原子操作
1.lock,表示主内存中变量已经被某个线程占有;
2.unlock,表示主内存中变量已经被某个线程释放,其他线程可以去获取这个变量
3.read,表示从主内存中读取变量到工作内存
4.load,表示主内存中读取的值赋值给工作内存中变量
5.use,表示工作内存中的值传递给工作引擎让工作引擎去做操作;
6.assign,表示工作引擎中计算后的值赋值到工作内存中;
7.store,表示工作内存把变量传递到主内存;
8.write,表示工作内存的值存入到主内存中;
交互原则
所有的操作必须符合前后逻辑关系
操作之前必须要满足前后依赖关系
配对操作之前含有其他操作关系,但是必须保证操作前后逻辑关系
变量被加锁多少次,就要被解锁多少次
内存模型三大特点
有序性
理解
线程内部:线程一定会按照串行的方式去执行指令
线程之间:由于cpu的执行权问题,多线程之间执行的任何代码都可能是交叉进行的,除了volatile,synchronized
原子性
主内存和工作内存之间的基本操作都是原子操作
可见性
共享变量被一个线程操作,操作后的记过能被其他线程直到
java可见性实现
volatile
volatile修饰的变量,修改的之后会立即从工作内存同步到主内存之中;实现其他线程对该变量的可见性
synchronized
解锁之前必须把变量的值从工作内存传递到主内存
final
对象的引用是不变的,所以说对所有线程来说都是可见的
java先行发生原则
解决问题
并发情况下,两个操作是否存在冲突的情况;判断数据是否存在并发问题,以及线程是否安全的重要依据
具体原则
1.锁定规则:同一个锁,只有被释放之后才能被另外一个线程再次占用;
2.读写原则:读写是一对操作,下一的读操作必定在写操作之后;
3.对象终结原则:对象被回收之前必须先要被初始化
4.传递性:a操作优先于b操作,b操作优先于c操作,那么a操作也有限与b操作
作用
定义各种变量的访问规则,以及这些变量底层实现的细节
java线程模型
java基础
类
IO
各种IO模型
BIO (同步阻塞IO)
概念
用户线程调用内核IO操作,需要等IO彻底结束之后才能返回到用户空间,因此IO过程是阻塞的。
优缺点
优点
用户线程无延迟,就可以拿到IO之后的数据
缺点
用户线程处于等待之中需要消耗性能;
应用场景
NIO (同步非阻塞IO)
概念
IO调用后,如果内核这个时候没有把数据准备好,那么就会直接返回一个状态,这个时候用户的IO线程就不会一直等待内核把数据准备好,而是间隔时间一段时间去轮询内核数据是否已经准备好了。最终是内核把数据准备好了,等待下一次用户线程的询问,就把数据从内核复制到用户内存中
优缺点
优点
无需阻塞等待内核准备数据,这个期间可以做其他的事情;
缺点
需要去轮询内核,内核无法以最短的时间把数据复制到用户内存中
应用场景
异步IO(AIO)
概念
用户线程对内核发起了IO操作,用户线程没有任何组阻塞;内核准备好数据并复制到用户内存后,会给用户线程发送一个信号,表明数据IO操作已经完成了;
优缺点
优点
内核会把数据复制到用户内存中,减少了用户线程去复制的过程;
缺点
应用场景
IO多路复用
IO多路复用概述
IO多路复用就是通过一个线程可以同事监视多个文件描述符(读就绪或者写接续,也就是可以监视多个IO操作),如果有一个描述符就绪,那么就可以通知程序做相应的读写操作;
本质上还是同步IO;相对于普通的同步IO,多路复用就可以让一个用户线程监视多个IO操作,普通的同步IO,一个用户线程只能只能监听一个IO操作
IO多路复用实现方式
select
概念
调用TCP文件系统的poll函数,不停的查询,直到有一个连接有想要的数据为止
过程
每次调用select函数都需要需要把文件描述符从用户内传递到内核态,内核再不断的去轮询这些文件描述符对应的io操作
缺点
文件描述符数量比较少,只有1024个
poll
概念
和select类似, 也是把用户传入的文件描述符数组复制到内核中,然后中再去不断的去做轮询操作;
优点
没有文件描述符限制
缺点
用户态到内核态的文件描述符的复制
过程
epoll
概念
用户的 FD集合和计算机操作系统有一块共有的内核空间,就不需要把FD集合复制到内核空间了;
优点
没有最大并发限制,
减少了从内核空间到用户空间的拷贝过程
过程
子主题
java-NIO
NIO组件
组件名称
组件功能
NIO工作流程
信号驱动IO
信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程。
异步I/O是进程执行I/O系统调用(读或写)告知内核启动某个I/O操作,内核启动I/O操作后立刻返回到进程,进程在I/O操作发生期间继续执行,当操作完成或遭遇错误时,内核以进程在I/O系统调用中指定的某种方式通知进程,
异步I/O是进程执行I/O系统调用(读或写)告知内核启动某个I/O操作,内核启动I/O操作后立刻返回到进程,进程在I/O操作发生期间继续执行,当操作完成或遭遇错误时,内核以进程在I/O系统调用中指定的某种方式通知进程,
IO基础概念
阻塞非阻塞
概念
区别
阻塞,如果内核没有准备好数据,那么线程就会一直阻塞直到有数据返回为止;非阻塞,如果内核没有准备数据,那么就会直接返回,并且还会不断的去轮询内核是否有准备好数据;
异步同步
概念
用户线程进行IO操作,数据是被动复制到用户内存还是主动被复制到用户内存
区别
同步IO是用户线程主动调用内核才能把数据从内核复制到用户空间;而异步IO是内核把数据复制到了用户空间,然后通知用户线程数据已经准备好了
IO操作步骤
1.用户代码向操作系统发出IO请求
2.轮询设备是否可以进行操作
3.将数据复制到用户内存之中
磁盘IO基本工作机制
概述
读取和写入文件IO操作都是调用操作系统提供的接口;应用程序要访问磁盘必须要通过操作系统的调用才行;
不同机制
1.标准访问文件方式
标准的访问方式:程序先调用操作系统的接口,操作系统先检查内核高速缓存找那个有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回,如果没有吗,则从磁盘中读取,然后缓存在操作系统的缓存中;
标准写入方式:用户程序调用写接口将数据从用户空间赋值到内核地址空间中,至于什么时候再写入磁盘,这个是由操作系统决定的;
2.直接IO
操作系统直接去访问磁盘数据,而不是经过缓存;这种方式减少了从内核缓存到用户缓存的复制;缺点就是每次都去访问磁盘,会导致数据加载极慢;
3.同步访问文件方式
数据的读取,写入都是同步操作;与标准方式不同的就是只有当数据成功的写入磁盘时才会给应用程序成功的标记;只有在一个对数据安全性比较高的场景会使用这种情况;
4.异步访问文件的方式
异步访问文件的方式,就是当访问数据的线程发出请求字后,这个线程会去接着处理其他事情,而不是阻塞等待,当请求的数据返回后,这个线程再继续处理下面的事情。特点:这种方式可以明显的提供应用程序的效率(线程可以在阻塞的时候去做其他事情,不用干等着),但是不会改变访问文件的效率;
5.内存映射的方式
操作系统内存中某块区域与磁盘中的文件相关联,当要访问内存中的一段数据的时候,转换为访问磁盘中一段数据。特点:减少数据从内核缓存空间到用户缓存空间的数据复制操作;
IO优化
IO的瓶颈
网络传输
磁盘读取
优化手段
网络io优化
1.设置合理的Tcp网络访问参数;
2.减少网络交互次数(在网络交互的两端都设置缓存)
3.减少网络传输数据量大小(先压缩数据包在出传输)
4.尽量以字节的方式传送,减少字符到字节的转换
磁盘io优化
1.添加磁盘缓存,减少磁盘访问次数;
2.优化磁盘的管理系统(磁盘寻址策略);
3.设计合理的磁盘存储数据块(设计数据索引);
4.设计合理的RAID策略提升磁盘io;
队列
阻塞队列
概述
阻塞队列也是一种先进先出的队列
在并发包下
特性
线程安全队列
带有阻塞功能
如果队列满,继续入队列时,入队列操作就会阻塞,直到队列不满才能完成入队列
如果队列空,继续出队列时,出队列操作就会阻塞,直到队列不空才能完成出队列
引用场景
生产者消费者模型
代码“解耦合”
“削峰填谷”
方法特性
1.对队列中元素的操作,都是基于 入列,出列这两个基本的方法
2.都会提供添加,取出的阻塞方法。
3.是否包含这个方法的实现都是通过遍历去比较的方式实现
4.toStirng的实现都是遍历,StringBuilder来实现的
5.clear的实现,都是遍历,为每个元素复制为null
6.都会引入迭代器作为内部类类实现一些功能
7.构造方法都可以传递一个集合来变成队列
阻塞队列具体实现
LinkedBlockingDeque
这个是双端队列实现类,队列的大小默认也是Integer的最大值,也可以自定义队列的长度。
这个队列的实现,自定义了两个方法,一个是linkFirst,一个是linkLast方法,这两个方法就是为做添加操作的,unLinkFirst,unLinkLast,方法是为了从对列中取出元素准备的。
双端队列也提供了:
阻塞存取;
不阻塞出去;
指定阻塞时间的存取;
SynchronousQueue
队列不能完全算是一个容器,存数据就立马把该数据交给取数据,就是中间不存在能够缓冲数据的容器
取数据操作必须等待存数据的操作;
存数据时候,必须等待取数据的把之前一条数据取完
DelayQueue
延迟队列,至于元素到了延迟期以后才能够被使用
用作需要进行延时操作的场景
PriorityBlockingQueue
优先队列
优先队列的默认初始队列长度为11;最大长度Integer最大值-8;
实现的数据结构是数组
队列中元素锁存储的对象必须是实现了比较器接口。
队列的存储,取出都是按照比较器的的比较的顺序来存和取的
LinkedBlockingQueue
以链表的数据结构实现了阻塞队列
特点
虽然是阻塞队列,提供了添加,取出,能延迟,规定延时的方法,但也提供了不阻塞的取出,添加的方法
如果没有给定阻塞队列的元素个数,那么默认的就是Integer值的最大值为这个队列所能存储的最多元素个数
储存元素时候,如果队列已经满了,那么就会导致延迟,延迟的实现还是通过了,lock和condition来实现,就是当元素已经满了的时候,就让当前存放元素的这个线程进行休眠,等到队列的状态是未满的时候,再去用condition去唤醒存储元素的线程。
同理取元素的时候,也是通过此种方式
基本的操作
enqueue(Node,e);存入队列;
dequeue(Node,e); 从队列中取出元素;
在每次对队列的取出,添加的时候对元素的个数用Integer来修饰。
TransferQueue
字面翻译是转换队列,是阻塞队列实现的一种
具备阻塞队列的一切性质
就是如果生产这线程生产出来的数据,当前没有一个消费者线程是空闲状态能够去接受生产者生产的数据,那么这个数据就会被放入队列中
LinkedTransferQueue
和SynchronousQueue有些像
阻塞队列继承实现关系图
阻塞队列概览
BlockingDeque
双端队列的父接口,BlockingDeque接口也是实现了BlockingQueue接口的,所以双端队列也是阻塞队列的一种。所以也能在存取的时候进行阻塞
主要就是定义:“取出元素”,“放入元素”是可以给定时间进行延迟的
并发队列
并发队列继承实现关系图
ConcurrentLinkedQueue
并发的链表;
就是在往链表中插入元素的时候,如果链表中元素为空,那么此时就会链表就会把第一个这个元素作为链表的头结点,尾结点
如果插入元素的时候,链表不为空,那么新添加进来的元素就会作为尾节点,而且就是如果该元素是第二个被加进来的元素,其实也是一样的,把新添加的元素替代第一个元素作为尾节点。
链表的每个节点在存储的时候,会存储自己的节点也会存储他的下一个节点
ConcurrentSkipListSet
其实可以看做是一个能够排序的单列集合类,可以支持各种排序。其实本质就是一个方便排序的单列集合类。适用于排序场景下
如果不在比较器中实现排序的规则,那么就是默认排序的规则,也就是升序
ConcurrentSkipListMap
利用cas命令实现的一个工具类。适用的场景是双列集合的排序操作。如果不指明排序的规则,就是自然排序,就是升序排序
CopyOnWriteArraySet
也是针对集合做过滤,添加,删除的集合工具类,是利用CopyOnWriteArrayList 来实现的。添加删除的时候还是需要去创建一个新的数组来实现这个
CopyOnWriteArrayList
主要就是对List集合的添加删除的工具类,实现还是使用的数组来实现,添加删除的时候还是使用lock锁来锁,每个添加删除都会创建一个新的数组来接受原来的数据
双端队列
实现类
ArrayDeque
数组实现的双向队列,线程不安全;
LinkedList
链表实现的对象队列
ConcurrentLinkedDeque
基于链表实现的线程安全的双向队列;
LinkedBlockingDeque
基于链表实现的阻塞双向队列
队列集合使用场景总结
集合
并发
ConcurrentHashMap (使用场景,修改多,读取多)
ConcurrentSkipListMap
ConcurrentSkipListMap
CopyOnWriteArrayList (使用场景,迭代多,修改少)
CopyOnWriteArraySet(使用场景,迭代多,修改少)
同步
HashTable
Vector
同步集合器:以托管的方式
Collections.synchronizedList(list)、(使用场景,修改多,读取多)
Collections.synchronizedSet(set)、(使用场景,修改多,读取多)
Collections.synchronizedMap(map)(使用场景,修改多,读取多)
队列
消费者-生产者模式
BlockingQueue接口下的类:
LinkedBlockingQueue
ArrayBlockingQueue
PriorityBlockingQueue
SynchronizedQueue
LinkedBlockingQueue
ArrayBlockingQueue
PriorityBlockingQueue
SynchronizedQueue
ArrayDeque:
LinkedBlockingDeque:
并发-非阻塞
ConcurrentLinkedQueue
ConcurrentLinkedDeque
ConcurrentLinkedDeque
并发
ConcurrentHashMap
排序
ConcurrentSkipListMap
ConcurrentSkipListSet
大量的查询,少量的增加删除操作
拷贝集合
CopyOnWriteArrayList
CopyOnWriteArraySet
Serializable
java序列化
序列化作用
网络之间传输数据,做持久化;
1.实现序列化后,可以把这个对象可以从一个虚拟机传递到另外一个虚拟机上(序列化就是将类的字段和他当前对应的字段值以序列化的格式生成一个数据流(这个数据流的格式不是字节码),这个数据流持久化到硬盘上)
序列化方式
jdk自带Serializable
json序列化
xml序列化
message pack
Protocol Buffers
Marshalling
注意事项
1.它与特定的版本号。持久化到本地硬盘后,我们可以根据序列化后的数据进行反序列化,生成对应的java类,并且java类字段的具体的值都有。
2.一旦一个类实现了序列化之后,就相当于把这个类变成了一个api。一旦这个类被大量的引用,那么这个类
就必须永远的支持这种序列化形式;你就无法轻易的去改变这个类里面的属性;
3、方法不能被序列化
4、static,transient修饰的都不会被序列化
序列化过程
将对象转换成二进制的字节数组,通过报错或者转移这些二进制数据达到持久化的目的,注意对象序列化保存的是对象的成员变量。
反序列化和序列化是相反的过程,就是把二进制的数组转化成对象的过程,但是在反序列化的时候,必须有原始类的模板才能将对象还原,这个模板就是对象的序列化文件
externalizable与serializable的区别
Externalizable接口是Serializable接口的子接口
Externalizable可以指定被序列化的字段
子主题
异常
ClassNotFoundException
这个是编译期报的错误,就是找不到这个类对应的字节码文件,也就是会在启动的时候就报错,就需要将该类的字节码文件放在对应的包路径下。
Error
和方法执行,字段获取相关错误
IncompatibleClassChangeError
AbstractMethodError.
NoSuchFieldError
IllegalAccessError
InstantiationError
虚拟机相关
VirtualMachineError
OutOfMemoryError
该类是内存溢出,并不是Exception,不能被程序捕获的,直接回导致程序中断
ZipError
InternalError
StackOverflowError
栈内存异常,这个错误(不是异常)不能被程序捕获的,直接回导致程序中断
UnknownError
ThreadDeath
包相关
LinkageError
VerifyError
ClassCircularityError
ExceptionInInitializerError
UnsatisfiedLinkError
ClassFormatError
UnsupportedClassVersionError
BootstrapMethodError
ExceptionInInitializerError
NoClassDefFoundError
编译期是能够找到这个类,但是在运行的时候发现这个类不可用。导致的原因可能是jar有问题,或者是jar就没有引入。解决方法是把jar包重新导入一下。
原因:这个异常的,说明有些类没有被加载到;
发生的场景:一般情况下抛出这个异常都是源码中的类抛出了这个异常;
解决方案:
1.在项目上右击 - 点击 properties --Deployment Assembly -- 点击 add--
选择 java build path entries 这个把这个加进来; 再重启tomcat;
2.在maven仓库中把相关的jar包删除,再maven update
3.在pom文件中,删除该 依赖,然后编译;报错之后,再回复依赖,再编译,启动试试看就行了;
1.在项目上右击 - 点击 properties --Deployment Assembly -- 点击 add--
选择 java build path entries 这个把这个加进来; 再重启tomcat;
2.在maven仓库中把相关的jar包删除,再maven update
3.在pom文件中,删除该 依赖,然后编译;报错之后,再回复依赖,再编译,启动试试看就行了;
Thread
Thread中的方法
静态方法
sleep
当前线程进行相应时间的休眠,并且不释放锁对象
yield
当前线程放弃cpu执行权,让其他线程去执行,放弃的时间不确定,可能放弃后,又获得了执行权;
currentThread
获取当前执行程序的线程
实例方法
isAlive() 方法的功能是判断当前线程是否处于活动状态
getId() 方法的作用是取得线程的唯一标识
isDaeMon、setDaemon(boolean on) 方法,是否是守护线程,设置守护线程
start()
线程可以开始运行
run()
线程真正执行
getPriority()和setPriority(int newPriority)
设置优先级,获取优先级
interrupt()
中断线程
join()
线程加入,抢占cpu执行权
应用在就是主线程主要在子线程生命周期结束之后再结束
那么就是用线程对象 调用 join //方法,让主线程等待自己
线程的状态
线程状态转换
示意图
线程各种转态
NEW
新创建的未启动状态的线程
RUNNABLE
线程准备或者运行中
BLOCKED
线程阻塞
发生IO阻塞
发生锁的争用阻塞
WAITING
未指定时间的线程等待
Object.wait,不带超时时间
Object.notify,Object.notifyAll 唤醒线程
Thread.join不带超时时间
等待终止
LockSupport.park
子主题
TIMED_WAITING
指定时间的线程等待
Thread.sleep 方法
指定超时值的 Object.wait 方法
指定超时值的 Thread.join 方法
LockSupport.parkNanos
LockSupport.parkUntil
TEMINATED
线程终止
线程
Thread
Runable
开启线程的方式
继承Thread类,重写run方法
实现runnable接口,重写run方法
线程池类
线程调度
抢占式
cpu的执行权由系统去分配
java就是采用的抢占式,可以设置线程的优先级,但是并不会有太好的结果;
协同式
线程在完成某个任务之后通知cpu,进行线程的切换
线程模型
内核线程实现
线程相当于于一个轻量级进程,一个轻量级进程对应一个内核
线程的切换调度完全依赖内核
用户线程实现
用户实现对线程的创建消耗,和内核没有关系
线程和进程的比例为1:n
用户线程轻量级进程实现
轻量级进程是线程和内核之间沟通的桥梁
线程创建消耗会是由用户来控制
java就是这种方式
sun公司提供的jdk,使用的线程模型都是内核模型;有的公司提供的方式不是使用内核模型;
线程安全定义
严格的定义:如果一个类被多个线程访问,并且不用考虑这些线程在运行下的调度和交替执行,也不需要增加额外的同步,或者在调用方法进行任何其他的协调操作,这个对象的操作都能得到正确的结果,那么这个对象就是线程安全的。
非严格的定义:如果一个对象可以同时被多个对象同时使用,那么这个类就是线程安全的;
线程安全等级
1.不可变
也就是被final修饰的字段或者对象,他们都是不可变,即使是通过被他们修饰的字段或者对象引申出其他的字段或者对象,原来的字段,对象还是不变的,只是新返回去了一个字段或者是对象。这类情况,保证字段和对象的绝对不会变化。
2.绝对线程安全
线程绝对安全,指的是一些类中所有的字段和方法都添加了同步的synchronized来进行修饰;
我们在单个调用这些类的某个方法的时候,这个时候线程是安全的,但是如果我们在一个类中使用了这个类的多个所谓线程安全的方法的时候,就很可能导致线程不安全。在很多条件下是无法将这多个所谓的线程安全的方法都同一起来让他们变得线程安全。我们需要通过额外的方法添加同步修饰来确保线程安全。
3.相对线程安全
相对线程安全,就是我们传统意义上的线程安全。
就是对某个对象单独的操作是线程安全的。调用的时候不需要额外的添加保障措施。
4.线程兼容
这段代码本身不是线程安全的,但是可以通过调用添加同步的方式来保证这个段代码的执行时线程安全的。
5.线程对立
无论调用端是否采用了同步措施,都无法在多线程环境下使用并发的代码。就是那些中断线程suspend(),恢复线程resume()的方法,不管怎样,调用了就会存在死锁风险,这两个方法已经被弃用了。类似的还用System类的setIn(),setOut(),runFinalieersOnExit()这类方法。都是线程对立的。
线程调度
线程的调度是为线程分配处理器的使用权的过程;主要有两种调度方式:协同式线程调度,抢占式线程调度。
协同式:线程的执行完他所需要执行的代码, 执行完成之后,会通知系统将cpu的执行权切换到另外一个线程上。最大好处是,实现简单,没有什么线程同步问题,并发问题,线程对于对cpu的切换是可以感知的。缺点,就是如果是一个死循环这期其他形式的问题,就会导致不让出cpu的执行权,导致整个系统崩溃。
抢占式:cpu的执行权是有系统去分配的。但是我们可以手动去调用一些方法,比如yield()方法让出cpu的使用权,还可以设置线程执行的优先级,10个等级,等级越高,被分配的概率会越大,但是设置优先级根本就靠谱,因为最终那个线程会被分配到执行权,还是由操作系统决定的。
线程安全实现方式
互斥同步
互斥同步(阻塞同步,可以理解为:悲观锁)
只是实现线程安全的一种方式,就是保证数据在同一时刻只能被一个线程或者是一些线程(使用信号量的时候)使用。就是一个锁对象只能被一个线程占有,其他线程想要获取到这个锁对象就必须是等待这个线程释放了锁对象才性,所以造成了其他线程等待这个线程的执行。
具体实现
synchronized
代码编译成字节码文件会有两个字节码指令,一个是“加锁指令”,一个是“解锁指令”,这两个指令在操作内存的时候,又是对应着内存模型中的lock,unlock着两个原子操作,所以这就从内存层面保证的代码的线程安全。
Lock
Lock这个java来实现的,做的也是同步互斥,但是Lock通过硬件操作系统的CAS指令来的。
子主题
线程和进程的区别
1.一个进程可以包含多个线程,线程只是进程一个具体去执行任务的实体。
线程池
线程池对象
Executors
newFixedThreadPool
有限线程数的线程池
固定线程数量的线程池
newWorkStealingPool
工作窃取线程池
不保证任务的执行顺序
相当于创建了和当前空闲cpu数量的线程来执行
newCachedThreadPool
无限线程数的线程池
特点一个核心线程都没有
线程数量最大可以是int的最大值
newSingleThreadScheduledExecutor
单线程定时任务线程
一个线程,来周期性的执行任务
newScheduledThreadPool
定时任务线程
线程数量固定,支持周期性执行任务
newSingleThreadExecutor
单线程线程池
只有一个线程来执行任务
ForkJoinPool
使用ForkJoinPool 可以将数据计算分配到多个cpu上进行计算。
和其他线程类一样,就是将任务都放入线程池类种,
该类是的入参是ForkJoinTask 的子类,有返回值的是RecursiveTask,没有返回值的是RecursiveAction。 工作窃取算法,就是一个线程对应一个双端队列,一个线程一个使用双端队列,将任务放在双端队列中,一个线程从另外一个任务的队列的末尾取出任务。
并发包也提供了,开多个线程跑任务的方式
ThreadPoolExecutor
ScheduledThreadPoolExecutor
线程池工作过程
1.线程池创建之后,线程池中没有一个线程
除非创建线程池后,调用了prestartAllCoreThreads(),prestartCoreThread(),也就是创建线程池后会创建核心线程数量
2.如果线程池中添加任务的时候,线程个个数小于核心线程个数(即使这是有空闲线程),那么就会去创建线程
3.当线程池中线程的个数大小等于核心线程数,再添加任务的时候就会把任务放在阻塞队列中,等待线程池调度
4.如果阻塞队列放满了,再添加任务的时候就会去创建核心线程之外的线程
5.当线程池中线程的个数等于最大线程数的时候,再添加任务就会执行拒绝策略
工作流程示意图
线程池参数
核心线程个数
最大线程个数
非核心线程多久不做任务就会被回收
非核心线程存活时间的单位
工作队列
阻塞队列中传递Runnable
ArrayBlockingQueue
LinkedBlockingQueue
SynchronousQueue
线程工厂
线程工厂,主要用来创建线程;
拒绝策略
AbortPolicy
丢弃任务并抛出RejectedExecutionException异常。
线程池默认的拒绝策略
线程池默认的拒绝策略
DiscardPolicy
丢弃任务,但是不抛出异常。
如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
CallerRunsPolicy
由调用线程(提交任务的线程)处理该任务
DiscardOldestPolicy
该任务有提交这个任务的线程去执行。
自定义
表示当拒绝处理任务时的策略
线程池状态
线程状态说明
RUNNING
自然运行状态
SHUTDOWN
不接受新任务,执行已添加任务
STOP
不接受新任务,也不执行已添加任务
TIDYING
所有任务已终止,任务数量为0
TERMINATED
线程彻底终止
线程状态扭转
示意图
线程池线程数量预估计算
计算密集型
计算公式
线程池中线程数=cpu核心数*cpu使用率*(1+等待时间/计算时间)
线程池中线程数=cpu核心数*cpu使用率*(总时间/计算时间)
计算公式说明
等待时间
线程处于就绪状态,到运行状态中间的时间(处于就绪的时候,如果线程被阻塞,那么他将不会立即去执行自己代码)
计算时间
线程从开始运行到线程把代码执行完(代码执行完,线程是回到线程池而不是死亡)的时间
cpu使用率
在0-1之间
因为线程等待的时间会远远大于计算的时间,我们就需要多穿件百分之多少的数目的线程去执行程序,才能尽可能的减少请求的阻塞。
实际过程中线程数量
线程池的线程数=cpu的核心数+1
IO密集型
计算公式
线程池线程数=cpu可用核心数/(1-阻塞系数)
线程池线程数=cpu可用核心数*(总时间/计算时间)
计算公式说明
阻塞系数的取值在0-1之间
阻塞系数=阻塞时间/(阻塞时间+计算时间)
阻塞时间占总时间的百分比
计算demo
假设任务有50%的时间处于阻塞当中,则程序所需要的线程数为处理器可用核心数的两倍(这点其实和方式1的表达是一样的。)如果任务的阻塞时间少于50%,即这些任务是计算密集型的,则程序所需要的线程数随之减少,最少也不能少于处理器的核心数。
计算密集型任务的阻塞系数是0,而io任务的系数则接近1(io读取时比较慢的,一个线程在做读取操作时候,执行时非常缓慢,随之其他线程都会处于等待的状态,所以说io读取的阻塞率是1)
假设阻塞系数是0.9,就是每个人物的90%的时间是处于阻塞的状态,只有10%的时间是在干活,那么双核cpu就需要开 2/(1-0.9)=20 就是要在线程池开启20个线程
如果是8核的就需要开 8/(1-0.9) = 80
实际过程中线程数量
线程池的线程数=cpu的核心数*2+1
线程池继承实现关系图
concurrent包
共享锁
共享锁概念
同一个时刻,允许访问资源的线程是有限的
闭锁,栅栏,信号量,都是一种共享锁
实现都是通过AQS,来实现的,共享锁和独占锁的区别就是,资源在同一个时刻能否被多个线程所共享。
AQS实现共享锁的原理,就是:当队列中的一个线程获取到了这个共享锁,那么这个线程将唤醒和他一起共享当前锁资源的其他线程节点。 这种是实现闭锁,栅栏,信号量,读锁的基础类
Phaser
和闭锁,栅栏的功能类似,但是比闭锁,栅栏的功能更加强大,可以支持等待其他线程,也可以不等待其他线程
阻塞唤醒的实现还是基于 LockSupport的,在java这层实现最底层还是Unsfae
CountDownLantch
作用
让所有的线程做完任务之后(先做完任务的线程会等待后做完任务的线程),再统一的结束任务;
CountDownLantch使用要点
1.线程调用stat方法之前,就用CountDownLantch 对象调用 countDown()方法,这样才能拦住所有线程的执行;
2.无需深入到线程具体执行的任务里面调用 countDown();
3.执行完成之后再调用await()方法,唤醒所有的线程;
使用demo
CountDownLantch和线程池一起使用
public class ExcutorCountDownLantch {
static CountDownLatch countDownLantch = new CountDownLatch(5);
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
for (int i =0;i<5;i++){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
};
countDownLantch.countDown();
threadPool.execute(runnable);
}
countDownLantch.await();
System.out.println("一起返回");
}
}
CountDownLantch和Thread一起使用
public class CountDownlantchTest {
static CountDownLatch lantch = new CountDownLatch(4);
public static void main(String[] args) throws InterruptedException {
MyThread myThread1 = new MyThread();
MyThread2 myThread2 = new MyThread2();
MyThread3 myThread3 = new MyThread3();
MyThread4 myThread4 = new MyThread4();
Thread array [] ={myThread1,myThread2,myThread3,myThread4};
for(int i =0; i<array.length;i++){
lantch.countDown();
array[i].start();
}
lantch.await();
System.out.println("最后一起返回");
}
}
CyclicBarrier
特点
就是让所有的线程都准备完毕(先准备完毕的线程会等待没有准备完毕的线程直到他准备好),然后再开启任务,让所有的线程一起开始做任务;
这个时候,线程先昨晚任务的线程不会等待后做完任务的线程
这个时候,线程先昨晚任务的线程不会等待后做完任务的线程
CyclicBarrier使用的要点
1.需要在线程执行的具体任务里面调用 await()方法;
2.等所有的线程的任务都调用了await()方法,再调用 线程的start()方法;(这一点和CountDownLantch是一致的;)
CyclicBarrier使用demo
public class TestCyclicBarrier {
static CyclicBarrier cyclicBarrier = new CyclicBarrier(4);
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
ArrayList<Long> list = new ArrayList<>();
for(int i =0; i<4 ;i++){
Thread iii= new Thread(()->{
long l = System.currentTimeMillis();
list.add(l);
//System.out.println(System.currentTimeMillis());
try {
int await = cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
});
iii.start();
}
//System.out.println("都返回来了,开始做事情了");
System.out.println("都返回来了,开始做事情了"+list.size());
}
}
Semaphore
默认 的是非公平的获取执行许可,非公平比公平的情况,效率会更高;
我们也可以在构造的时候设置这个是否是公平的。
对信号量的占用可以设置占用时间,底层还是通过AQS来实现的,信号量过多的时候,被阻塞,其实也就是在AQS中被迫的休眠了。等释放一个信号量的时候,就会唤醒一个线程(默认非公平,可以设置公平)
lock包
ReentrantReadWriteLock
读写锁
读锁是使用的AQS中的共享模式的共享锁;
写锁是使用的AQS中的独占模式的独占锁。
锁降级
线程获取到写锁之后,就可以获取读锁,然后再释放写锁,这个时候锁就由写锁变成了读锁了;
不可升级
获取到读锁之前,不能直接获取到写锁,必须是释放了读锁,才能再去获取到写锁;
StampedLock
对读写锁的一种优化,避免写锁一直被阻塞
当读线程执行的时候,如果有些线程在操作,那么读线程如果发现了数据不对,就会再执行一次读。避免了读写线程之间的相互阻塞,但是写和写线程之间还是阻塞的
LockSupport
该类的方法都是静态方法,直接使用类名来调用。用来阻塞线程的。park阻塞线程,unpark,解除阻塞。可以用用来让线程阻塞,阻塞多久
是让其他线程来唤醒这个线程。以下方法是在参数中传递一个需要被唤醒的那个线程的名称。
Condition
Condition和Object唤醒,休眠的区别
Condition是针对在锁的操作上休眠某个线程,或者唤醒某个线程;
Object休眠和唤醒针对的是所有的线程,而不只单单的在锁操作上的线程
原子类
引用数据类型
AtomicIntegerArray,int数组
AtomicLongArray,long数组
AtomicReference,对象
AtomicReferenceArray,数组对象
AtomicLongArray,long数组
AtomicReference,对象
AtomicReferenceArray,数组对象
维护对象标记原子类
AtomicStampedReference 维护对象引用以及可以原子更新的标记位。
AtomicMarkableReference 维护对象引用以及整数“印记”,可以原子更新
以托管的形式管理其他类的原子类
AtomicLongFieldUpdater 对指定类的的指定Integer类型字段(字段必须是volatile修饰)进行原子操作的托管。
AtomicIntegerFieldUpdater 对指定类的指定的Long类型字段(字段必须是volatile修饰的)进行原子操作的托管。
AtomicReferenceFieldUpdater 对指定类中的某个字段(字段必须是volatile修饰)进行原子操作托管。
以托管形式的计数器类
DoubleAccumulator
LongAccumulator
同时托管多个数据,对这些数据进行原子性求和
DoubleAdder
LongAdder
同时托管多个数据,这几个数据的初始化和值为0。对这些进行求和
AtomicInteger
使用场景
频繁的多简单的数据更新操作
问题:
无法解决ABA的问题;
ABA的问题就是,
线程1把变量从A修改成了B;线程2把变量从B修改会A;
线程3把变量从A可以修改成其他某个值;
也就是,这种修改操作,无法避免修改版本的先后顺序问题;
这种情况下,可以使用AtomicStampedReference 可以解决上面的问题,
因为修改的时候,会有一个版本号,没修改一次就会有一个版本号,
AtomicInteger原子性的原理
1、AtomicInteger的自增
2、AtomicInteger 自增方法调用了Unsafe 类的getAndAddInt
3、incrementAndGet方法对应的字节码指令如下
4、本地方法对应的c++实现如下
5、C++ 中不同操作平台的实现
6、调用汇编代码
所以总结一句话:并发包的实现,是调用了Unsafe类的比较与交换的方法实现的,最终去实现这个原子操作的还是操作系统去实现的;
自增
i++ 的自增
对应了三个字节码指令
AtomicInteger的自增对应了 一个字节码指令;
AQS
AbstractQueuedSynchronize和AbstractQueuedLongSynchronizer区别
这个两个类区别就是,AbstractQueuedSynchronizer 这个主要是针对int类型的数据操作的,
AbstractQueuedLongSynchronizer 是针对int 数据类型的做操作的
AbstractQueuedLongSynchronizer 是针对int 数据类型的做操作的
AQS实现的思想
就是将线程放入队列当中,去维护线程的状态。根据线程的状态去调用这个原子操作的方法。其实还是一利用队列进行分流的思想。
cas
cas加锁
调用Unsafe 类的比较与交换方法是否能执行成功;成功,加锁;不成功,加锁失败
加锁操作过程
1.把执行获取锁的代码的线程全部放入阻塞的线程队列中;
2.获取锁的初始状态和传入的是否一致;
3.使用比较与交换方法;
4.将获取到锁的线程从阻塞的队列中剔除来;
5.将当前获取到锁的线程对象赋值给获取到锁的线程这个值;
6.执行成功,就退出加锁的方法,执行失败,就被park阻塞;
释放锁
释放锁,如果持有锁的线程刚好就是当前线程,那么就可以释放锁
解锁操作过程
1.判断当前线程是否是持有锁的线程;
2.初始化状态是否已经改变;
3.需要使用比较与交换方法把改变后的值再改变回去;
4.把获取到锁对象的线程数据设置成null;
5.让当前线程unpark;
锁对象需要维护的数据
1.初始化状态,如果传入的状态值和初始化状态值不一样,那么就不能执行比较与交换的方法;
2.需要有一个队列能够保存被阻塞在锁的线程
3.持有锁的线程是哪个;
子主题
阻塞自旋
1.使用lockSupport的park,unpark
lockSupport调用的也是Unsafe 类的park,unpark本地方法
并发容器
ConcurrentLinkedQueue
并发的链表;
就是在往链表中插入元素的时候,如果链表中元素为空,那么此时就会链表就会把第一个这个元素作为链表的头结点,尾结点
如果插入元素的时候,链表不为空,那么新添加进来的元素就会作为尾节点,而且就是如果该元素是第二个被加进来的元素,其实也是一样的,把新添加的元素替代第一个元素作为尾节点。
链表的每个节点在存储的时候,会存储自己的节点也会存储他的下一个节点
ConcurrentSkipListSet
其实可以看做是一个能够排序的单列集合类,可以支持各种排序。其实本质就是一个方便排序的单列集合类。适用于排序场景下
如果不在比较器中实现排序的规则,那么就是默认排序的规则,也就是升序
ConcurrentSkipListMap
利用cas命令实现的一个工具类。适用的场景是双列集合的排序操作。如果不指明排序的规则,就是自然排序,就是升序排序
CopyOnWriteArraySet
也是针对集合做过滤,添加,删除的集合工具类,是利用CopyOnWriteArrayList 来实现的。添加删除的时候还是需要去创建一个新的数组来实现这个
CopyOnWriteArrayList
主要就是对List集合的添加删除的工具类,实现还是使用的数组来实现,添加删除的时候还是使用lock锁来锁,每个添加删除都会创建一个新的数组来接受原来的数据
阻塞队列
BlockingDeque
双端队列的父接口,BlockingDeque接口也是实现了BlockingQueue接口的,所以双端队列也是阻塞队列的一种。所以也能在存取的时候进行阻塞
主要就是定义:“取出元素”,“放入元素”是可以给定时间进行延迟的
阻塞队列特点
1.对队列中元素的操作,都是基于 入列,出列这两个基本的方法
2.都会提供添加,取出的阻塞方法。
3.是否包含这个方法的实现都是通过遍历去比较的方式实现
4.toStirng的实现都是遍历,StringBuilder来实现的
5.clear的实现,都是遍历,为每个元素复制为null
6.都会引入迭代器作为内部类类实现一些功能
7.构造方法都可以传递一个集合来变成队列
阻塞队列具体实现
LinkedBlockingDeque
这个是双端队列实现类,队列的大小默认也是Integer的最大值,也可以自定义队列的长度。
这个队列的实现,自定义了两个方法,一个是linkFirst,一个是linkLast方法,这两个方法就是为做添加操作的,unLinkFirst,unLinkLast,方法是为了从对列中取出元素准备的。
双端队列也提供了:
阻塞存取;
不阻塞出去;
指定阻塞时间的存取;
SynchronousQueue
队列不能完全算是一个容器,存数据就立马把该数据交给取数据,就是中间不存在能够缓冲数据的容器
取数据操作必须等待存数据的操作;
存数据时候,必须等待取数据的把之前一条数据取完
DelayQueue
延迟队列,至于元素到了延迟期以后才能够被使用
用作需要进行延时操作的场景
PriorityBlockingQueue
优先队列
优先队列的默认初始队列长度为11;最大长度Integer最大值-8;
实现的数据结构是数组
队列中元素锁存储的对象必须是实现了比较器接口。
队列的存储,取出都是按照比较器的的比较的顺序来存和取的
LinkedBlockingQueue
以链表的数据结构实现了阻塞队列
特点
虽然是阻塞队列,提供了添加,取出,能延迟,规定延时的方法,但也提供了不阻塞的取出,添加的方法
如果没有给定阻塞队列的元素个数,那么默认的就是Integer值的最大值为这个队列所能存储的最多元素个数
储存元素时候,如果队列已经满了,那么就会导致延迟,延迟的实现还是通过了,lock和condition来实现,就是当元素已经满了的时候,就让当前存放元素的这个线程进行休眠,等到队列的状态是未满的时候,再去用condition去唤醒存储元素的线程。
同理取元素的时候,也是通过此种方式
基本的操作
enqueue(Node,e);存入队列;
dequeue(Node,e); 从队列中取出元素;
在每次对队列的取出,添加的时候对元素的个数用Integer来修饰。
TransferQueue
字面翻译是转换队列,是阻塞队列实现的一种
具备阻塞队列的一切性质
就是如果生产这线程生产出来的数据,当前没有一个消费者线程是空闲状态能够去接受生产者生产的数据,那么这个数据就会被放入队列中
ConcurrentLinkedDeque
并发双端链表
双端,意味着从头节点开始查找也行,从尾节点查找也行。
存储的时候:存储当前元素的节点,下一个元素的节点,上一个元素的节点。
既然是双端,那么可以从头,尾都可以添加或者删除元素
Object
Class
反射在jvm中实现的机制
jvm以字节流的方式从jvm的老年代或者是硬盘的元数据区域读取该类的字节码文件,通过一系列的加载转换后,得到java类的映射结构图(属性,方法),再通过反序列化流,创建对象。
反射获取对象
Class c1 = Class.forName("Employee");
Class c2 = Employee.class;
Employee e = new Employee();
Class c3 = e.getClass(); //这中方式已经失去了反射的意义
反射数据存取
jvm在启动的时候都会加载每个类,让每个都生成二进的字节码文件,存储在jvm的元空间或者是永久代中。
二进制的字节码会存储如下关于类的数据
ClassFile {
u4 magic; //模数
u2 minor_version; //次版本号
u2 major_version; //主版本号
u2 constant_pool_count; //常量池大小
cp_info constant_pool[constant_pool_count-1]; //常量池
u2 access_flags; //类和接口层次的访问标志(通过|运算得到)
u2 this_class; //类索引(指向常量池中的类常量)
u2 super_class; //父类索引(指向常量池中的类常量)
u2 interfaces_count; //接口索引计数器
u2 interfaces[interfaces_count]; //接口索引集合
u2 fields_count; //字段数量计数器
field_info fields[fields_count]; //字段表集合
u2 methods_count; //方法数量计数器
method_info methods[methods_count]; //方法表集合
u2 attributes_count; //属性个数
attribute_info attributes[attributes_count]; //属性表
}
u4 magic; //模数
u2 minor_version; //次版本号
u2 major_version; //主版本号
u2 constant_pool_count; //常量池大小
cp_info constant_pool[constant_pool_count-1]; //常量池
u2 access_flags; //类和接口层次的访问标志(通过|运算得到)
u2 this_class; //类索引(指向常量池中的类常量)
u2 super_class; //父类索引(指向常量池中的类常量)
u2 interfaces_count; //接口索引计数器
u2 interfaces[interfaces_count]; //接口索引集合
u2 fields_count; //字段数量计数器
field_info fields[fields_count]; //字段表集合
u2 methods_count; //方法数量计数器
method_info methods[methods_count]; //方法表集合
u2 attributes_count; //属性个数
attribute_info attributes[attributes_count]; //属性表
}
.我们再通过类的全路径,可以获取到类的对象。jvm是通过类的全路径在元空间或者是永久代中找到对应的字节码,通过反序列化生成这个对象,再讲这些方法,字段存储在反射对应的缓存中。
Comparable和Comaprator
Comparator 比较器,接口,重写比较的方法时候需要传递进来两个对象进行比较;
Comparable 可比较,接口,实现接口,重写比较方法,就是那其他对象和当前的对象进行比较,自定义比较的方法
jdk也提供了Comparators类,实现一些比较的方法
Comparable 可比较,接口,实现接口,重写比较方法,就是那其他对象和当前的对象进行比较,自定义比较的方法
jdk也提供了Comparators类,实现一些比较的方法
差别
1.两者都是可以做比较的。
2.Comparable接口的方法compareTo(Object o);Comaprator接口的比较方法compare(Object o1, Object o2);
注意两个接口对应的两个方法,一个只能传一个参数,一个可以传递两个参数;
所以说,调用compareTo(Object o)的对象如果要实现比较的功能那么,该方法的调用者也是和Object 相同属性的对象。
注意两个接口对应的两个方法,一个只能传一个参数,一个可以传递两个参数;
所以说,调用compareTo(Object o)的对象如果要实现比较的功能那么,该方法的调用者也是和Object 相同属性的对象。
所以此时,这个compareTo(Object o)这个方法应该写在这个Object对象中,才能使对象调用compareTo(Object o)去比较的是相同属性的对象。
Comaprator接口的compare(Object o1, Object o2);方法传递的是两个对象,两个拥有相同属性的对象,所以这里就不存在和上面
Comparable接口中的比较方法的问题。这里只需要新建立一个比较的算法类去实现compare(Object o1,Object 02);这个方法就行了。
3.Comparable比较器是被实体类所实现;Comaprator这个比较就是定义比较的算法,比较的方式。
4.两者的差别就是,Comaprator可以纯粹的定义比较的算法,而不需要实际的实体类对象。
ThreadLocal
使用场景:可以用来封装一些线程不安全的类,在一个线程中改变了该类,不会影响到其他线程对该对象的使用;
reflect包
这个包的功能,其实就是将Class这个类功能进行细化拆分了,已经一些是否能访问的一些控制;
提供了接口可供继承(继承了这些接口,可以使用一些顶层父类的一些反射方法)相关包装类,丰富了反射的功能。并提供了InvocationHandler 这个接口,反射执行方法;
相比Class 这个类:1.丰富了反射的方法;2.提供InvocationHandler 反射执行方法;
rmi
一台计算机能够调用到另外一台计算机上的远程数据服务。
使用方式:
1.服务提供者
1.1方法类实现Remote接口,并且继承UnicastRemoteObject类;
1.2注册通信端口,绑定通信路径;
2.服务消费者
2.1方法类实现Remote接口
2.2获取远程方法的接口对象,
2.3接口调用方法
1.服务提供者
1.1方法类实现Remote接口,并且继承UnicastRemoteObject类;
1.2注册通信端口,绑定通信路径;
2.服务消费者
2.1方法类实现Remote接口
2.2获取远程方法的接口对象,
2.3接口调用方法
util包
集合
双列集合
ConcurrentHashMap
jdk1.7
数据结构
外层是segment数组,每个segment对象都是一个ReentrantLock锁,可以把每一个segment看做是一个HashMap
子主题
实现
Segment+HashEntry数组实现,外层Segment是Reentrant的子类,内层是HashEntry数组,每一个HashEntry元素又是一个链表
并发控制
外层的Segment就是一个锁,所有多少个Segment就相当于有多少个分段锁,Map的并发程度=Segment个数
读写操作步骤
读操作
步骤
1、根据key,计算出hashCode;
2、根据步骤1计算出的hashCode定位segment,如果segment不为null && segment.table也不为null,跳转到步骤3,否则,返回null,该key所对应的value不存在;
3、根据hashCode定位table中对应的hashEntry,遍历hashEntry,如果key存在,返回key对应的value;
4、步骤3结束仍未找到key所对应的value,返回null,该key锁对应的value不存在
读操作方法
https://upload-images.jianshu.io/upload_images/3994601-d54765fbf88f74f4.png?imageMogr2/auto-orient/strip|imageView2/2/w/656/format/webp
写操作
https://upload-images.jianshu.io/upload_images/3994601-595d017873607cb0.png?imageMogr2/auto-orient/strip|imageView2/2/w/564/format/webp
1、参数校验,value不能为null,为null时抛出NPE;
2、计算key的hashCode;
3、定位segment,如果segment不存在,创建新的segment;
4、调用segment的put方法在对应的segment做插入操作。
示意图
子主题
扩容操作
每一个Segment都相当于是一个Map
每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
扩容的时候判断也是每个Segment内部单独判断的,判断是否超过阈值
1.8
数据结构
子主题
读写操作步骤
读操作
步骤
子主题
操作方法
子主题
写操作
示意图
子主题
实现
摒弃了Segment的概念,也就是摒弃了分段锁
并发控制
并发控制使用Synchronized和CAS来操作
synchronized+CAS+HashEntry+红黑树
链表转红黑树条件
数组中任意一个链表的长度超过8个
数组长度大于64个时
两个条件必须达到
链表转红黑树
子主题
扩容
扩容过程
默认情况下,每个cpu可以负责16个元素的长度进行扩容
node数组的长度为32,那么线程A负责0-16下标的数组扩容;线程B负责17-31下标的扩容,并发扩容在transfer方法中进行
子主题
2个线程分别负责高16位和低16位的扩容,不管怎样都不会产生冲突
扩容示意图
子主题
集成实现关系图
1.7和1.8的区别
底层实现
1.7 数组+链表
1.8数组+链表/红黑树
数据结构
1.7 Segment数组 +HashEntry节点
1.8 Node节点
并发度
1.7分段锁,默认并发度是16,一旦初始化,segment数组大小固定,后面不能再扩容;所以并发度是16
1.8CAS+ synchronized 来保证安全性,加锁的位置是每一个Node节点,这个Node节点数量会随着扩容操作变成原来2倍,所以1.8的并发度是提高了。
put操作
1.7 先获取锁,再根据key的hash值订到segment,再根据key的hash值找到具体的HashEntry ,再进行插入或者覆盖操作,最后释放锁
1.8根据key值hash定位到具体的Nodej节点,再判断首节点是否为空,空的花通过cas去复制首节点,非空,会用Synchronized去锁住首节点,再去操作
锁释放
1.7需要调用unlock()操作
优化点
1.8的锁粒度比1.7的锁粒度更细
数据查询插入的时间复杂度更低,原来是O(n),现在是0(n) 或者O(logn)的情况
put方法效率更高
HashMap
1.HashMap的散列表是什么时候创建的?在第一次put元素的时候创建的,也就是和懒加载机制很像;2.链表转红黑树的前提条件? 链表的长度要达到8,当前散列数组(最外层的数组)长度达到64个;3.hash算法的理解?把任意长度的值转化为固定长度的输出,会有hash冲突,hash冲突是很难避免的。4.为啥要使用红黑树结果?如果hash冲突严重,那么就会导致hash槽下面的数据的链表过长,对于查询数据效率低查询的时间复杂度从 O(n) 变成 O(log)5.扩容规则?也就是扩容之后的容量是扩容之前的两倍6.扩容之后的老数组中的数据怎么往新的数组里面迁移??因为hash槽里面存在四种情况:1.hash槽完全没有数据这个其实没啥说的;2.hash槽里面只有一个数据;根据新计算出来的tableSize,存放过去;3.hash槽里面是链表;4.hash槽里面是红黑树;7.扩容之后的hash槽计算?比如说当前是16个hash,在第1个槽位,在扩容之后,需要迁移的数据就会落在1+16 =17 也就是在第17个槽位上面。
JDK1.7和JDK1.8的区别
1.7的元素是放在单向链表的头部,1.8是放在单向链表的尾部
1.7的存储形式是外层是数组,内层是链表;1.8是外层是数组,内层是链表或者红黑树
1.8元素存储可以转换成红黑树,提高了,元素读取的效率;1.7是没有将链表转成红黑树的功能
1.7外层是Entry数组;1.8外层是Node数组,
1.8重写了Hash方法,降低了hash冲突,提高了Hash表的存,去效率(增加了异或运算,位运算)
1.7扩容后添加元素,1.8扩容前添加元素
因为 1.7 头插法扩容时,头插法会使链表发生反转,多线程环境下会产生环。
1.7扩容,需要将元素重新hash然后把数据存放,1.8扩容后不需要重新hash,新位置不变或者新位置是索引+扩容大小
提高扩容效率
1.7的散列函数做了多次位移,异或运算,1.8只做一次
提高hash效率
HashMap并发环境下的问题
多线程扩容,引起死锁问题
多线程put的时候可能导致元素丢失
put非null元素后get出来的却是null
1.7和1.8都存在的问题
常见问题
1.HashMap的散列表是什么时候创建?
在第一次put元素的时候创建的,也就是和懒加载机制很像
2.链表转红黑树的前提条件?
1.当前链表的长度大于8;
2.外层Hash桶的数组长度达到64个;
3.为啥要使用红黑树存储?
1.如果hash冲突严重,那么就会导致Hash槽下面的数据的链表长度过长;查询数据的复杂度就是0(n),换成红黑树之后就变成了O(log)
4.扩容相关
扩容之后的hash槽计算?
比如说当前是16个hash槽,扩容之后就会变成32个hash槽;当前第一个槽位中需要被迁移的数据会迁移到第17个槽位上;也就是以此类推,当前第16个槽位的需要被迁移的数据被迁移到第32个上;
什么情况下回触发扩容?
当前实际存储元素的个数超过一定百分比(默认的75%,这个比例可以通过构造函数设置)
扩容之后的老数组中的数据怎么往新的数组里面迁移?
1.hash槽没有数据
也就是没有需要被迁移的数据
2.hash槽里面只有一个数据
根据新计算出来的hash槽位把数据存放过去
3.hash槽里面是链表
4.hash槽里面是红黑树
扩容之后的hash槽计算?比如说当前是16个hash,在第1个槽位,在扩容之后,需要迁移的数据就会落在1+16 =17 也就是在第17个槽位上面。
扩容因子为啥是0.75
假设扩容因子=1,也就是每一次扩容都前集合的元素个数和当前集合的最大容量相同,无形之中就增加了hash碰撞,hash碰撞之后,就会形成对应的链表过长,红黑树高度过高,这样就会导致在查询的时候耗时过高。
如果扩容引子过小,map集合在存储元素的时候,就会经常发生扩容操作,导致添加元素耗时过高。
0.75是经过很多科学计算之后,平衡了添加和查询操作之后,得出来最佳的数值
5.get死循环,put闭环
不安全原因
代码截图
原因分析
key已经存在,需要修改HashEntry对应的value;
key不存在,在HashEntry中做插入
源码解析
hash方法
1.如果key为null,那么hash值就是为1;
2.如果key不为null
1.获取当前key的hashCode值与value的Code值做异或运算
2.拿到第一步的值,让当前值和当前值的与16做位运算之后的值做异或运算
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value);}
Put方法解析
1.第一次put的时候,如果散列数组为空,那么就会进行扩容散列操作;
2.如果hash桶只要单个数据,那么先对比数据是否一样,一样就覆盖原来的值;
4.如果hash桶的数据是红黑树,如果key不重复,那么就把数据放在key的末尾;如果key重复了,那么就替代源码的value值;
3.如果hash桶的数据,是链表,如果key不重复,那么就把数据放在key的末尾;如果key重复了,那么就替代源码的value值;
单列集合
LinkedHashMap
存储过程示意图
子主题
双向链表,加数组实现,其实最终存储的时候还是 外层是数组,内层是链表;和HashMap一样,不过就是LinkedHashMap 是有序的。
SkipList
LinkedList,ArrayList
LinkedList是双向链表实现的;ArrayList是数组实现的
查询的时候
ArrayList查询的时间复杂度都是O1,LinkedList查询的复杂度是On(因为是从表头或者表位做一个一个遍历查询),
添加删除修改操作
如果都是在尾部添加,那么添加的效率是一样的,
如果都是在头部添加,那么ArrayList效率很低,因为需要有数组元素的复制跃迁;LinkedList集合只是需要把引用之前新的元素就行了。
中间添加,效率都很低,ArrayList需要做数据复制移动;LinkedList需要先遍历查询到具体的位置,然后再前后引用的断开与重新连接;
Stack
实现
vector来实现的,本质就是vector类,但是是具有栈这种数据结构的一切性质
源码分析
构造方法
public E push(E item) {
//本类没有添加的方法,让后就是去调用他的父类的添加的方法
addElement(item);
return item;
}
存取方法
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
public boolean empty() {
return size() == 0;
}
public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}
TreeSet
源码解析
构造方法
TreeSet集合的构造方法是需要传递 1.NavigableMap接口,而NavigableMap的实现类是TreeMap类;
2.不传就是直接默认是 TreeMap类来实现;
3.或者是比较器;
4.其他单列集合;
结论,就是构造函数使用的是哪一种,最终都会把这个map集合变成TreeMap集合;
所以帮助TreeSet去完成增删改查工作的都是TreeMap来实现的; 也就是
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
存储
都是以key ,value的形式来存储的,Key其实就是TreeSt集合的值,而这个value值就是 这个集合本身给的一个默认值
/ Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
结论
1.TreeSet集合的功能都是由TreeMap来实现的
2.TreeSet集合拥有和TreeMap集合的一切特性;
HashSet
源码解析
构造方法
HashSet的构造方法中都是包含一个HashMap集合来来实现的,也就是
Hashset的所有方法都是HashMap来帮助实现的;还是以key value 的形式来存储的,
源码中有如下一个Object 就是做了key value 的value的值
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
结论
1.HashSet是借助于HashMap来完成集合的,
2.HashSet和HashMap都是无顺序的存储的。数据结构和HashMap一致;
实现
HashSet的实现是通过HashMap来实现
Vector
线程安全
实现通过加锁实现;
源码解析
字段
1.一个是容量的数组(默认是10个,除非自己给定长度);
2.一个容量大小;
3.一个是容量增量常数(每次扩容的时候增加的长度,默认值是0。1.如果该值为0,那么每次扩容后的长度是扩容之前长度的两倍;2.如果不为0,该值的大小就是每次扩容增加的长度)
protected Object[] elementData;
protected int elementCount;
protected int capacityIncrement;
构造函数
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//如果给定了长度,那么就是这个给定的初始化长度;
//给定义的数组的长度赋值
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
//默认是10个
public Vector() {
this(10);
}
扩容代码
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
在构造方法中就有一个参数是,就是每次数组满了,数组扩容的时候是扩容多少个元素;
如果该参数不传入,那么该值就是默认为0;那么在扩容的时候就会默认是按照原有数组
长度两倍进行扩容;
增加:就在原有数组后面新加一个元素;元素个数+1;
删除:就是将元素置空,然后长度减1;
查询:根据元素查询,就是将数组中元素遍历,利用equal方法一个一个比较;
根据索引查询,直接就是返回数组对应的元素;
Copy集合
集成实现关系示意图
字符串
总结
String ,StringBuilder,StringBuffer三者,不管是谁拼接谁,调用的拼接的方法,都是 被拼接的那个对象的拼接方法;
StringBuffer
线程安全是因为每个方法都加了sychronized;和字符串做拼接的时候,调用的拼接的方法还是String 的拼接方法
StringBuilder
线程不安全,和字符串做拼接的时候还是调用的是字符串的拼接的方法;如果是和StringBuffer做拼接, 那么调用的拼接方法就是StringBuffer的
元注解
jdk定义的注解:注解上的注解
java特性
泛型
泛型实现的原理
java代码的执行都是需要class文件中指令的,而编译器将java文件编译成class文件的时候,就是不做泛型的检查;直接可以存储任意类型;
使用jdk8实测过,字节码反编译之后还是有泛型的代码在里面;
方法参数传递类型
1.基础数据类型在方法的传递过程传递的都是对象本身;
2.非基础数据类型在方法的传递过程中传递是对象的引用;
静态
静态是属于类,,而不属于对象,所以会优先于对象被加载,所以说静态的是最先开始被加载的
类加载
类的加载顺序是:先去父类中查看有没有静态代码块,有就去执行父类的静态代码块,由于静态优先于类的加载,所以说,父类的静态代码块执行完了,就会再去执行子类的静态代码块,子类的静态代码了执行完了,再去执行父类的非静态代码块,执行完后,执行父类的构造方法,完成之后再去执行子类的非静态代码块,最后才是子类的构造方法。
类部类
使用类部类好处
1.1.1我们可以将和主类相关的副类都放在一个包下,然后这个主类需要使用副类的时候,是需要需要new对象的,这个就造成了类和类之间的耦合性增强了。如果我们直接将这些副类作为内部类写在主类当中,主要就可以直接通过内部类来使用这些副类,减少了类与类之间的耦合性。
1.1.2如果我们确定A类只有在B类中用到,那么我们可以把A类作为B类的内部类,而不是单独使用一个类来写A类,防止A类被误用,还可以减少类的数量。
1.1.3主类中的每个内部类,可以实现接口,或者继承类,丰富了主类的方法。
1.1.4主类不止有一种接口实现方法,如果我们使用类内部类,可以让内部类去实现这两种不同的方法。
从设计角度
Java的设计原则是组合优先于继承,如果仅仅就是为了父类或者接口中的方法,那么就直接抛弃继承或者实现,直接改用上面这种内部类,也就是“组合”的形式。
我们需要使用接口或者父类中的方法,组合就是将需要使用的接口父类直接作为内部类写在主类当中。
在内部类当中个,有时候我们也是要去将接口作为内部类,那我们只有我们这个代码写法大概就是:new接口对象{实例化接口中的抽象方法}
可见性
工作缓存:对变量进行操作的时候,先把变量从主缓存中拷贝过来,再进行操作。如果这个被操作的变量是用了volatile这个关键词修饰,那么将不会把主缓存中的变量复制到工作缓存中,而是直接就能在主缓存的数据上进行操作。确保了数据的可见性(当前操作的数据的大小就是这个)
当变量没有被volatile修饰,要确保数据的可见性,就必须是每次被复制到工作缓存的数据被修改完成后,这个线程结束才会将修改的数据回写到主缓存中,该数据又会被其他线程复制。
java的内存的内存模型有两类:主缓存(被所有线程所共享)
原子性
概念
当一个线程在修改某个值得时候,从取值到写入值之间没有其他线程对这个值进行操作。可以通过锁机制或者CAS机制(后期再做解释)
有序性
程序执行的顺序是按照代码的顺序一致
设计模式
创建型
单例
概述
单例设计模式,其实就是为了在一个域中只有一个该类的对象,节约内存空间,方法对对象进行统一管理;
创建方式
一种饿汉式,一种懒汉式,区别在于,饿汉式在该类被加载的时候就创建了该类的对象;懒汉式,是需要用到的时候才会去创建这个类的对象;在不主动使用同步的情况下, 因为类只会被加载一次,所以饿汉式也只会创建一次这个类的对象;而懒汉式,就有可能在同时判断为null的时候去创建这个类的对象;
单例设计模式的创建过程
1.将构造方法私有化;
2.提供一个对外的静态获取该类对象的方法,
demo
饿汉式
public class Singleton1 {
// 指向自己实例的私有静态引用,主动创建
private static Singleton1 singleton1 = new Singleton1();
// 私有的构造方法
private Singleton1(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton1 getSingleton1(){
return singleton1;
}
}
懒汉式
public class Singleton2 {
// 指向自己实例的私有静态引用
private static Singleton2 singleton2;
// 私有的构造方法
private Singleton2(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton2 getSingleton2(){
// 被动创建,在真正需要使用时才去创建
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
子主题
建造者模式
定义和使用
把一个类对象的构造过程交给一个专门构建对象的类去处理
结构型
代理模式
静态代理
实现
代理类和被代理类实现统一接口,然后在代理类中引入代理对象,通过被代理对象去实现代理对象的功能;
特点
最终我们去调用方法的对象还是代理对象
因为代理对象的构造方法的参数就是被代理的对象
所以说代理对象的创建是根据被代理对象而来的
缺点
代码冗余
也就是说,如果有多个对象需要被代理,我们就要创建多个代理对象,而多个代理对象就除了调用的方法不同之外。其他都一样
也就是一个代理对象只能代理一个类。无法同时代理多个类
动态代理
实现
jdk代理
被代理类必须要实现接口;
底层实现反射
JDK的Proxy类在我们调用方法newProxyInstance时,生成一个代理对象在内存中,该对象根据参数实现了被代理类的接口,重写了接口中的方法。在此方法内,调用了我们创建的InvocationHandler的invoke()方法。实际调用的就是InvocationHandler中我们重写的invoke方法。所以动态代理在运行时会根据我们重写的代码进行增强
缺陷是:需要代理类和被代理类实现同样的接口,如果被代理对象没有实现接口,那么代理将无法完成;
cglib代理
ASM框架
通过cglib包来实现cglib代理,cglib包下最终也是调用的asm,字节码修改技术来相当于生成了一个新的类来替代原有的类执行方法。
代理类需要设置父类字节码文件对象,以及实际执行方的类的对象,再通过create方法获取到被代理的对象,被代理类的对象去执行相关的方法,最终方法的执行都会走 最终那个实现类的intercept 方法。
动态代理的两种方式对比
1.性能
jdk1.6之前cglib效率高,由于jdk1.6之后对jdk代理有优化,在1.6之后少量的调用次数比较的时候jdk效率高,只有大量的调用的时候cglib高;在1.8之后,jdk代理的效率超过了cglib代理
2.创建对象的时间
jdk<cglib
动静态代理的区别
1.静态代理是静态编译,动态代理是动态编译
2.静态代理,只能代理一个类;动态代理,可以代理多个
动态代理和静态代理的最大区别就是:代理的对象创建的时间不同;静态大力是编译之前就已经完成了对象的创建;动态代理运行的时候去创建对象的;
代理模式好处
中介作用
有时候一个对象不能直接引用另外一个对象,我们就用代理对象去替代这个不能被引用的对象
增加了可扩展性
可以在代理对象上再扩展一些新的功能,而不用去修改被代理对象
对原有的对象代码无侵入性
对象引用的是被代理的对象,我们只需要去修改被代理的对象,而原对象无需做修改
享元模式
设计思想
享元模式其实就是为了最大程度的降低内存的消耗,而设计的,最大程度上减少对象的创建,
String 对象就是 享元 模式的设计方式,就是为了最大程度的降低内存的消耗。
String常量池、数据库连接池、缓冲池
装饰者模式
特点
最底层的实现类可以比他继承的接口,父类获取到更多的可以执行的方法。并且装饰类在实现接口功能的时候,可以借用相同接口下另外一个类的的实现来实现这个接口;
装饰者的构造方法中提供以顶层接口为入参的构造方法,由于java的多态特性,这个顶层接口可以调用任意实现类的接口方法,大大增强了当前类的功能
设计要点
1.装饰者和被装饰者都必须要实现同一个接口;
2.被装饰者的有参构造器的参数必须是这个顶层接口这个类,这样因为装饰者实现了这个顶层接口,那么传入装饰者作为这个有参构造的话,就可以拿到被装饰者的对象,从而使用被装饰者的方法。
行为型
中介模式
用一个类来封装一系列的对象交互,这个类就是中介者
本质就是中介者将者一系列的对象都封装在自己的这个类中,在自己的这个类种来进行交互。
责任链模式
说简单一些,就是有些类似于击鼓传花,将事物的处理交给对象的下一家,下一家可以处理,也可以接着往下传
写法,就是定义顶层抽象处理接口,多个具体处理的接口继承了这个抽象接口
在具体执行的时候,指定每个处理对象的下一个处理对象
关键字
synchronized
Synchronized实现
字节码层面
编译后生成了加锁,解锁指令
monitorenter
加锁指令
monitorentexit
解锁指令
JVM层面
jvm调用了操作系统提供的同步方法;
操作系统和硬件
操作系统本身有一些同步的操作
锁对象
锁优化
锁升级
Synchronized 锁升级过程
Synchronize 内部,有一个锁升级的过程;jdk1.6自动打开;1.jvm会偏向第一个执行的线程,给他加上偏向锁;
2.如果这个时候有其他线程来竞争,这个偏向锁就会升级成轻量级锁,这轻量级锁,多数情况下是自旋锁;
3.如果并发程度高,这个自旋锁会自旋很多次,如果超过十次,还没有拿到锁对象,那么锁又会升级成重量级锁(悲观锁);
锁状态转换过程
示意图
锁状态
无锁状态
偏向锁状态
偏向锁:适用于单线程适用锁的情况,如果线程争用激烈,那么应该禁用偏向锁。
轻量级锁状态
轻量级锁:适用于锁竞争较不激烈的情况(这和乐观锁的使用范围类似)
重量级锁状态
重量级锁:适用于竞争激烈的情况
锁粗化
编译器发现某段代码有连续的加锁解锁操作就会进行锁粗化,将连续的加锁解锁变成一个加锁解锁,提高代码执行效率;
锁消除
编译器发现某段代码不存在多线程竞争共享资源,编译之后就会把锁消除掉,减少加锁解锁的性能消耗
锁优化概述
锁优化,都是针对锁的优化技术,都是为了在线程之间能够高效的共享数据,解决竞争问题。
Synchronize和Lock对比
Lock 只需要jvm层面来实现锁;Synchronize 需要jvm和操作系统交互才行
cas 的实现是需要cpu 硬件的支持,修改内存地址的偏移量
公平锁机制:synchronized和lock锁默认都是非公平的,非公平锁就是不能按照申请锁的顺序来获取到锁对象,而lock可以将锁机制设置为公平的,就是按照等待获取锁独享的次序来获取锁对象。就是公平锁。
多条件的绑定多对象:synchronized锁对象就是休眠或者唤醒的时候只需要一个条件,就是wait()条件等待,notify(),notifyall()方法唤醒线程;lock可以在这些条件之外再条件,等待,或者是唤醒线程。newCondion()方法就可以,只有当所有的条件都满意的时候才会等待,或者是唤醒线程。
Lock锁可以可以等待中断,就是一个线程获取不到锁的时候(已经有线程已经占用了锁对象),可以放弃等待,去执行其他代码。
synchronized释放锁的方式
1. 线程执行完了该执行的代码,线程就释放了该锁
2. 线程执行代码的时候发生异常,此时jvm就会让该线程自动释放锁
Synchronized缺陷
1. 如果线程阻塞或者是sleep()了,线程不会释放锁对象,那么其他线程还会等待这个对象去执行完代码。影响效率
使用场景
超高并发 synchronized
低频并发 Lock
transient
瞬时态
特点
用此修饰符的字段是不会被序列化
使用场景
类中的字段是可以通过其他字段被推导出来,此字段不需要做序列化
一些安全性的信息,一般情况下是不能离开JVM的
需要使用一些类,但是这个类对程序逻辑没有影响,也可以用来修饰
native
标志被该关键字修饰的方法是调用系统的本地方法,也就是调用不是java代码,调用的是系统的c语言的方法。
比如Thread类,Object类,System类由于很多功能并不是java语言所能完成的,需要借用到计算机操作系统的代码来实现这些功能。
volatile
可见性
变量被修改之后,jmm自己的工作内存复制到主内存当中
线程写volatile变量
先线程工作内存副本中的值
从工作内存复制到主内存
线程读volatile变量的过程
从主内存中获取最新值到工作内存中
从工作内存中读取volatile变量的副本
有序性
该关键字修改的变量,在被使用的时候都会先去主内存中获取到该变量的最新值,复制到工作内存当中
使用场景
不存在并发问题但是又被很多地方同时使用。
缺陷
并发环境下使用会有线程安全问题;java的操作并不是原子操作
volatile关键字特点
能够屏蔽指令重排序
数据一致性
一致性的开销小于锁的开销
实现
字节码层面
给字段加上了ACC_volatile标记
JVM层面
编译后给该字段加上读写屏障
读屏障
StoreStoreBarrier
读操作
StoreLoadBarrier
读取该变量的操作在读屏障之后执行,保证获取的数据是最新的数据
写屏障
LoadLoadBarrier
写操作
LoadStoreBarrier
写入共享变量之后,所有的写操作已经全部完成
操作系统和硬件
翻译成对应的汇编指令,然后给读写加锁
default
default关键之jdk1.8提出来的关键字,1.8jdk中的接口可以用default关键来修饰,用此关键字修饰后的接口,有自己默认的接口实现。也就是能够像实现类一样有自己的方法实现了。
protected
protected 修改的方法,本类,子孙类,同一个包下可以调用
static
静态的使用特性是:直接就是用类名来调用,并没有像其他普通方法一样去创建对象,再用对象去调用方法。
为什么会有这个特性:原因就是静态的东西不属于对象,而是属于类,对象只是类的实例化而已,类会被装载到方法区中,而对象只是在虚拟机的堆内存中,对象的成员变量,对象的方法则会被存储在栈内存中,用完就弹栈消除。当对象已经不存在对成员变量和方法的引用的时候就会被标记这个对象可以被删除,而垃圾回收会被不确定的时间去回收这些。
因为静态的属于类,不属于类的某个实例(对象),所以静态就不能像普通方法和普通变量那样存储在栈内存(会弹栈消除),也不会存在堆内存(会被垃圾回收器回收)。静态存在方法区中,永久的不会被消除或者被垃圾回收器回收。
所以说栈内存的生命周期很短,堆内存的生命周期比栈内存长,但是比方法区的生命周期短。
static的既然是属于类,不属于对象,那么在对象序列化的时候不会去序列化被statis修饰的字段
存在的一个问题:因为静态的都属于类,在内存中只会存储一份,而且会被所有线程共享,所以会存在线程不安全的问题。
switch case
if else 是每一个分支的判断条件都需要执行到,而switch case 就是直接会跳跃到满足相关条件的 分支上去值代码,这样就if else 的效率更高;
原理
switch-case与if-elseif的根本区别在于汇编时,switch-case会生成一个跳转表来指示实际的case分支的地址,而这个跳转表的索引号与switch变量的值是相等的。从而,switch-case不用像if-elseif那样遍历条件分支直到命中条件,而只需访问对应索引号的表项从而到达定位分支的目的。
锁
锁分类
特性分类
公平锁/非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象
Synchronized 非公平锁; ReentrantLock 默认也是非公平锁,可以设置成公平锁
可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例
Synchronized ; ReentrantLock 都是可重入锁;
独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。
Synchronized ; ReentrantLock 独占锁;ReadWriteLock 读操作是共享锁,写操作是独占锁;
互斥锁/读写锁
互斥锁
ReentrantLock ,Synchronized
读写锁
ReadWriteLock
乐观锁/悲观锁
从并发角度来给锁分类
悲观锁
悲观的认为,不加锁的并发操作一定会出问题。悲观锁在Java中的使用,就是利用各种锁
乐观锁
则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。
乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新
分段锁
锁的一种设计理念
ConcurrentHashMap 就是这种设计理念
偏向锁/轻量级锁/重量级锁
偏向锁
偏向锁会偏向第一个获取到它的线程进行偏向
就是偏向锁会偏向第一个获取到它的线程进行偏向,如果在这个线程获取到了这个偏向锁后,没有其他线程再获取这个锁,则持有偏向锁的线程将永远不再需要同步就能执行代码了。如果存在线程竞争,偏向锁就会失效
轻量级锁和偏向锁,其实都是需要再没有多线程竞争的时候才会发挥出他们的作用,将重量级的锁,变成那种不需要进行同步就能执行的代码。
轻量级锁
所谓的轻量级锁,并不是替代什么重量级锁的,是一种锁的优化机制,只是针对于没有数据不存在多线程竞争的时候,减少传统的重量级锁使用互斥量的时候造成的性能消耗
就是如果一个线程第一次获取了这个锁对象,只要不存在线程竞争的时候。只要是这个锁对象的同步的代码块,那么这个线程就不需要再进行加锁的操作,直接就执行代码块,也不需要解锁的操作。如果存在多线程竞争这个锁,那么轻量级锁就会变成重量级锁,然后同步的时候需要加锁解锁
实现的方式,大概就是在虚拟机的对象头中标记获取轻量级锁的标记
自旋锁与自适应自旋
概述
自旋锁就是线程等待的时间不让线程挂起,浪费系统并发资源
就让这个等待的线程做一个忙循环(自旋操作),这项技术就是自旋锁
为何需要自选
让一个线程等待另外一个线程释放锁对象,等待的时候线程的挂起然后再回复线程都是需要再内核中完成的。这个给系统会造成很多的并发性能压力。获取锁的过程是一个很短的过程,没必要为了这个很短的时间去挂起,恢复线程(其实也就是避免了内核线程在不同的java线程之间来还切换,内核线程来java线程之间的来还切换是内核的调度器完成的,来还切换还是很浪费时间的)。
自旋锁本身是避免了线程之间切换的开销,这个和阻塞还是有区别的。我们可以设置根据获取锁的等待的自旋的次数,合理的调节等待的时间,如果自旋次数晚完了,还没有获取到锁对象,那么就会将线程挂起。
1.6的jdk中加入了自适应的自旋锁,自旋的时间不再是固定了,是由之前自旋在这个锁上的时间,以及锁的拥有者的状态来决定的。就是有一个不断修正自旋时间的过程。
特性分类
根据锁的状态,特性和设计理念,并不是实实在在的锁
类型分类
Synchronized
Lock
java中的锁
jdk新特性
jdk9新特性
http请求是2.0版本
接口可有默认的实现
jdk7
interface IMyInterface {
public void test0();
}
public void test0();
}
jdk8
interface IMyInterface {
public static void test1 () {
System.out.println("静态方法");
}
default void test2() { // 可以被实现类覆写
System.out.println("默认方法");
}
public void test0();
}
public static void test1 () {
System.out.println("静态方法");
}
default void test2() { // 可以被实现类覆写
System.out.println("默认方法");
}
public void test0();
}
jdk9
interface IMyInterface {
private void test() {
System.out.println("接口中的私有方法");
}
public static void test1 () {
System.out.println("静态方法");
}
default void test2() {
System.out.println("默认方法");
}
public void test0();
}
private void test() {
System.out.println("接口中的私有方法");
}
public static void test1 () {
System.out.println("静态方法");
}
default void test2() {
System.out.println("默认方法");
}
public void test0();
}
多版本jar包兼容
在某个版本的java程序运行的时候可以选择不同版本的class版本
模块化
解决的问题
1、jvm启动的需要加载整个jra包,导致不需要使用的类,模块也被加在到内存中,浪费不必要浪费的内存;
在终端开发中尤其明显
不同版本的类库交叉依赖;每个公共类可以被类路径下任何其他的公共类所访问到,会导致使用并不想开放的api;类路径可能重复等
暂时无法理解这些
模块化好处
降低启动所需要的内存,按需加载对应的包
简化类库,方便开发维护
模块化实现
模块化实际上就是包外面在包一层,来管理包。
jshell命令
提供类似python交互式编程环境
可以在dos窗口,终端中进行编程
新增api
多分辨率图像api
mac系统已经支持
暂时还无法看到的api
引入轻量级的JSON API
新的货币api
弃用api
Applet和appletviewer的api都被标记为废弃
主流浏览器已经取消了对java浏览器插件的支持
改进
钻石操作符(泛型)的升级
具体解释
对应的demo
public class DiamondOperatorTest {
public static void main(String[] args) {
new DiamondOperatorTest().test();;
}
public void test() {
Set<String> set = new HashSet<>(){
@Override
public boolean add(String s) {
return super.add(s + "..");
}
};
set.add("1");
set.add("2");
set.add("3");
set.add("4");
for (String s : set) {
System.out.println(s);
}
}
}
public static void main(String[] args) {
new DiamondOperatorTest().test();;
}
public void test() {
Set<String> set = new HashSet<>(){
@Override
public boolean add(String s) {
return super.add(s + "..");
}
};
set.add("1");
set.add("2");
set.add("3");
set.add("4");
for (String s : set) {
System.out.println(s);
}
}
}
对应结果
try语句升级
jdk8
传统语句
优化后,jvm自动释放资源
需要将资源的实例化放在try 的小括号里面
jdk9
一次性可以释放多个资源
String实现
jdk8
底层是char 数组
jdk9
底层是byte数组
不同的编码字符集一个字符所需要的字节个数不一致,1-2个
相对于jdk8可以减少字符串内存空间消耗
快速创建只读集合
提高编码效率
所创建的集合都只能是做读取操作,无法做添加删除操作
增强的流api
List<Integer> list = Arrays.asList(11, 32, 44, 29, 52, 72, 82, 64, 4, 19);
list.stream().takeWhile(integer -> integer < 50).forEach(System.out::println);
list.stream().takeWhile(integer -> integer < 50).forEach(System.out::println);
只是做筛选,并不会修改list集合本身元素
List<Integer> list = Arrays.asList(11, 32, 44, 29, 52, 72, 82, 64, 4, 19);
list.stream().dropWhile(x -> x < 50).forEach(System.out::println);
list.stream().dropWhile(x -> x < 50).forEach(System.out::println);
只是做筛选,并不会修改list集合本身元素
Stream.ofNullable(null).forEach(System.out::println);
Stream.iterate(0, x -> x < 5, x -> x + 1).forEach(System.out::println);
类似下面代码
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
System.out.println(i);
}
Optional.ofNullable(list).stream().forEach(System.out::println);
动态编译器
AOT(Ahead of time)
java引用在jvm启动钱就被编译成二进制代码
jdk9中提出来,jdk10正式发布该新特性
jdk1.9 默认垃圾收集器G1
jdk9版本采用单线程回收
G1回收期新老年代都可以做回收
替换了jdk7,8 Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk10新特性
局部变量类型推断
var 可以作为局部变量
var str = "abc"; // 推断为 字符串类型
var l = 10L; // 推断为long 类型
var flag = true; // 推断为 boolean 类型
var list = new ArrayList<String>(); // 推断为 ArrayList<String>
var stream = list.stream(); // 推断为 Stream<String>
var l = 10L; // 推断为long 类型
var flag = true; // 推断为 boolean 类型
var list = new ArrayList<String>(); // 推断为 ArrayList<String>
var stream = list.stream(); // 推断为 Stream<String>
但是不可以作为全局变量,还有参数,已经方法返回值
G1引入并行 Full
发生full gc 的时候可以使用多个线程并行回收
jdk9的时候单线程回收
应用程序类数据共享
同一个机器下的多个jvm可以实现数据共享
减少不必要的内存消耗
线程本地握手
ThreadLocal 握手交互。在不进入到全局 JVM 安全点 (Safepoint) 的情况下,对线程执行回调。优化可以只停止单个线程,而不是停全部线程或一个都不停
在备用存储装置上进行堆内存分配
新增加Graal编译器
基于Java的JIT编译器,目前还属于实验阶段
预先把 Java 代码编译成本地代码来提升效能
删除javah工具
javah 用于生成C语言的头文件
JDK8开始,javah的功能已经集成到了javac中。去掉javah工具
新增api
List、Set、Map新增加了一个静态方法 copyOf
class Main {
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>(3);
list.add(1);
list.add(2);
list.add(3);
List<Integer> temp = List.copyOf(list);
}
}
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>(3);
list.add(1);
list.add(2);
list.add(3);
List<Integer> temp = List.copyOf(list);
}
}
IO
transferTo方法复制文件
JDK10 给 InputStream 和 Reader 类中新增了 transferTo 方法
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("a.txt")));
long nums = reader.transferTo(new BufferedWriter(new OutputStreamWriter(new FileOutputStream("b.txt"))));
System.out.println("一共复制的字节数量: "+nums);
long nums = reader.transferTo(new BufferedWriter(new OutputStreamWriter(new FileOutputStream("b.txt"))));
System.out.println("一共复制的字节数量: "+nums);
IO流大家族添加Charset参数的方法
ByteArrayOutputStream新增toString方法
JDK10给 ByteArrayOutputStream 新增重载 toString(Charset charset) 方法,通过指定的字符集编码字节,将缓冲区的内容转换为字符串
jdk11新特性
新增api
String类新增方法
判断是否是空白字符串
String s1 = "\t \n";
System.out.println(s1.isBlank()); // 判断是否为空白 true
System.out.println(s1.isBlank()); // 判断是否为空白 true
去除首尾空白
s1 = "\t sss\n";
System.out.println(s1.trim().length()); // 去除首尾空白,不能去除全角空格
System.out.println(s1.strip().length()); // 去除首尾空白,可以去除全角空格
System.out.println(s1.stripLeading()); // 去除头部空白
System.out.println(s1.stripTrailing()); // 去除尾部空白
System.out.println(s1.trim().length()); // 去除首尾空白,不能去除全角空格
System.out.println(s1.strip().length()); // 去除首尾空白,可以去除全角空格
System.out.println(s1.stripLeading()); // 去除头部空白
System.out.println(s1.stripTrailing()); // 去除尾部空白
lines()
方法可以对字符串每一行进行流式处理
"asc\nccc\nwww".lines().map(str -> str.toUpperCase()).forEach(System.out::println);
ASC
CCC
WWW
CCC
WWW
Optional方法增强
System.out.println(Optional.ofNullable(null).orElse("b")); // 如果为空,返回"b"
System.out.println(Optional.ofNullable(null).orElseGet(() -> "b")); // 也可以使用函数式接口实现orElse()
System.out.println(Optional.ofNullable(null).orElseThrow()); // 如果为空,排除异常
System.out.println(Optional.ofNullable(null).orElseGet(() -> "b")); // 也可以使用函数式接口实现orElse()
System.out.println(Optional.ofNullable(null).orElseThrow()); // 如果为空,排除异常
b
b
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.orElseThrow(Optional.java:382)
at com.szc.Main.main(Main.java:42)
b
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.orElseThrow(Optional.java:382)
at com.szc.Main.main(Main.java:42)
transferTo()方法
try (var inputStream = new FileInputStream("D:/test.jar");
var outputStream = new FileOutputStream("test2.jar")) {
inputStream.transferTo(outputStream);
} catch (Exception e) {
e.printStackTrace();
}
var outputStream = new FileOutputStream("test2.jar")) {
inputStream.transferTo(outputStream);
} catch (Exception e) {
e.printStackTrace();
}
移除废弃api
废弃
jvm参数
-XX:+AggressiveOpts、-XX:UnlockCommercialFeatures、-XX:+LogCommercialFeatures
废弃 Nashorn js引擎,可以考虑使用GraalVM
废弃 pack200和unpack200,这是以前压缩jar包的工具
移除
com.sun.awt.AWTUtilities、sum.misc.Unsafe.defineClass(被java.lang.invoke.MethodHandles.Lookup.defineClass替代)、Thread.destroy()、Thread.stop(Throwable)、sun.nio.disableSystemWideOverlappingFileLockCheck属性、sun.locale.formatasdefault属性、jdk.snmp模块、javafx模块、javaMissionControl等
JavaEE和CORBA模块
更简化的编译运行程序
解释:如果java文件里没有使用别的文件里的自定义类,那么就可以直接使用java就可以编译运行java文件,也不会输出class文件
Unicode编码
Unicode10加入了8518个字符,4个脚本和56个新的emoji表情符号
完全支持linux容器
jdk11以前的java应用程序在docker中运行的性能会下降,但现在此问题在容器控制组(cgroups)的帮助下得以解决,使JVM和docker配合得更加默契
免费的低耗能分析
通过JVMTI的SampledObjectAlloc回调提供一个低开销的堆分析方式
新的加密算法
ChaCha20-Poly1305这种更加高效安全的加密算法,代替RC4;采用新的默认根权限证书集,跟随最新的HTTPS安全协议TLS1.3
Flight Recorder
记录运行过程中发生的一系列事件
Java 层面的事件,如线程事件、锁事件,以及 Java 虚拟机内部的事件,如新建对象,垃圾回收和即时编译事件
启用设置 -XX:StartFilghtRecording
新增垃圾回收器
Epsilon GC
处理内存分配但不负责内存回收的垃圾回收器,堆内存用完,JVM即刻OOM退出
EpsilonGC使用场景
性能测试(过滤GC引起的性能消耗,相当于控制变量)
内存压力测试(看看不回收的情况下,到底能不能消耗指定大小的内存)
执行非常短的任务(GC反而是浪费时间)
VM接口测试、延迟吞吐量的改进等实验性质的调优
ZGC
ZGC是一个并发、基于区域(region)、标记压缩算法的GC
特性
不管堆内存多大,STW 时间不会超过10ms
只在根节点扫描阶段发生stw ,所以停顿事件不会随着堆内存增长和激活对象的增长而增长
启用ZGC的方法:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
目前ZGC只能用于64位的linux操作系统下
目前ZGC只能用于64位的linux操作系统下
G1的完全并行GC
jdk12新特性
新增Shenandoah GC
雪兰多收集器使用的内存结构和G1类似,都是将内存划分为区域,整体流程也和G1相似
最大的区别在于雪兰多收集器实现了并发疏散环节,引入的Brooks Forwarding Pointer技术使得GC在移动对象时,对象的引用仍然可以访问,这样就降低了延迟
工作流程
其工作周期如下:
1)、初始标记,并启动并发标记阶段
2)、并发标记遍历堆阶段
3)、并发标记完成阶段
4)、并发整理回收无活动区域阶段
5)、并发疏散,整理内存区域
6)、初始化更新引用阶段
7)、并发更新引用
8)、完成引用更新阶段
9)、并发回收无引用区域阶段
1)、初始标记,并启动并发标记阶段
2)、并发标记遍历堆阶段
3)、并发标记完成阶段
4)、并发整理回收无活动区域阶段
5)、并发疏散,整理内存区域
6)、初始化更新引用阶段
7)、并发更新引用
8)、完成引用更新阶段
9)、并发回收无引用区域阶段
启用方法:
XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
新增一套微基准测试
现有微基准测试的运行和新微基准测试的创建过程
改进
switch表达式
demo
public class Main {
public static void main(String[] args) {
Fruit f = Fruit.APPLE;
switch (f) {
case APPLE -> System.out.println(1);
case ORANGE, GRAPE -> System.out.println(2);
case PEAR, MANGO, WATERMALLON -> System.out.println(3);
default ->
System.out.println("No such fruit");
}
}
}
enum Fruit {
APPLE, ORANGE, GRAPE, PEAR, MANGO, WATERMALLON
}
public static void main(String[] args) {
Fruit f = Fruit.APPLE;
switch (f) {
case APPLE -> System.out.println(1);
case ORANGE, GRAPE -> System.out.println(2);
case PEAR, MANGO, WATERMALLON -> System.out.println(3);
default ->
System.out.println("No such fruit");
}
}
}
enum Fruit {
APPLE, ORANGE, GRAPE, PEAR, MANGO, WATERMALLON
}
改进点
简化表达式
可以同时处理多个case
G1回收器
空闲时候G1回收器会自动将java堆内存返回给操作系统
jdk12中的G1将在应用程序不活动期间定期生成或持续循环检测整体的java堆使用情况,以便更及时地将java堆中不使用的内存返回给OS。这一改进带来的优势在云平台的容器环境中更加明显,此时内存利用率的提高会直接降低经济成本
G1 垃圾回收器的回收超过暂停目标,则能中止垃圾回收过程.
把回收集分为必须部分和可选部分,优先处理必须部分
必须部分主要包括G1不能递增处理的部分(如年轻代),也可以包含老年代以提高效率
优先处理必须部分时,会维护可选部分的一些数据,但产生的CPU开销不会超过1%,而且会增加本机内存使用率;处理完必须部分后,如果还有时间,就处理可选部分,如果剩下时间不够,就可能只处理可选部分的一个子集。处理完一个子集后,G1会根据剩余时间来决定是否继续收集
参数设置
G1PeriodicGCInterval
G1PeriodicGCSystemLoadThreshold
以上两个参数值都为0表示禁用此功能
G1PeriodicInvokesConcurrent
定期GC的类型,默认是Full GC,如果设置值了,就会继续上一个或启动一个新的并发周期
jdk13新特性
动态CDS档案
支持java应用执行之后进行动态归档,以后执行java程序后一些类就可以直接从这些归档文件中加载了
改进
ZGC提交未使用的堆内存
ZGC在jdk11中引入的收集器,jdk13中使能了向OS提交未使用的堆内存
过程
ZGC中的区域称之为ZPage,当ZGC压缩堆时,ZPage被释放,然后变成ZPageCache,最后使用LRU算法对PageCache区域进行定时清除。时间间隔默认为5分钟,用户自定义时间间隔尚未实现,而如果-Xms和-Xmx相等的话,这个功能就相当于没有
重新实现旧版套接字api
switch表达式中引入yield
文本块
String s = """
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<p>aaa</p>
</body>
</html>
""";
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<p>aaa</p>
</body>
</html>
""";
s = """
select * from students
where id in (12, 13, 14, 15)
order by grade desc
""";
select * from students
where id in (12, 13, 14, 15)
order by grade desc
""";
jdk14新特性
instanceof省去了强制类型转换的过程
jdk14版本之前写法
Object obj = "kuaidi100";
if(obj instanceof String){
String str = (String) obj;
}
if(obj instanceof String){
String str = (String) obj;
}
jdk14版本的写法
Object obj = "kuaidi100";
if(obj instanceof String str){
//直接使用str
}
if(obj instanceof String str){
//直接使用str
}
if (obj instanceof String s) {
// can use s here
} else {
// can't use s here
}
// can use s here
} else {
// can't use s here
}
垃圾回收器
移除 CMS(Concurrent Mark Sweep)垃圾收集器:删除并发标记清除 (CMS) 垃圾收集器
ZGC
MacOs windows 实现 ZGC
弃用 Parallel Scavenge 和 Serial Old 垃圾回收算法的组合
G1 NUMA感知内存分配:现在尝试跨垃圾收集在年轻一代的同一NUMA节点上分配并保留对象。这类似于并行GC NUMA意识。G1尝试使用
严格的交错在所有可用的NUMA节点上均匀分配Humongous和Old区域。从年轻一代复制到老一代的对象的放置是随机的。这些新的NUMA感知
内存分配试探法通过使用-XX:+UseNUNMA命令行选项自动启用
严格的交错在所有可用的NUMA节点上均匀分配Humongous和Old区域。从年轻一代复制到老一代的对象的放置是随机的。这些新的NUMA感知
内存分配试探法通过使用-XX:+UseNUNMA命令行选项自动启用
NUMA是啥?
这段话怎么理解
更详细的空指针异常
参数-XX:+ShowCodeDetailsInExceptionMessages
更详细地显示空指针异常
Packaging Tool (Incubator)
一个打包工具,可以将java应用直接打包成rpm,dmg或者exe在各自平台可以点击直接运行
改进
JFR Event Streaming JFR事件流
对飞行记录器功能升级
之前是不可以用于实时监控
jdk14之后可以实时获取到JVM的运行情况
弃用
不建议使用线程挂起、删除
涉及的线程挂起Thread的方法已经在jdk14版本种废弃
Thread.suspend(),Thread.
resume(),ThreadGroup.suspend(),ThreadGroup.resume(),ThreadGroup.allowThreadSuspension(boolean)这些方法将在
将来的版本中删除。
resume(),ThreadGroup.suspend(),ThreadGroup.resume(),ThreadGroup.allowThreadSuspension(boolean)这些方法将在
将来的版本中删除。
需要验证下对应的版本是否有这个方法?
椭圆曲线:
security-libs / javax.crypto,已过时的旧椭圆曲线去除
jdk15新特性
垃圾回收器
优化G1回收器
默认的堆区域大小计算已更改为默认情况下返回较大的区域,计算仍以2048个区域为目标
两点改进
仅考虑最大堆大小。旧的计算还考虑了初始堆大小,但是当未设置堆大小时,这可能会
产生意外的行为
产生意外的行为
区域大小四舍五入到最接近的2的幂,而不是减小。在最大堆大小不是2的幂的情况下,
这将返回更大的区域大小
这将返回更大的区域大小
ZGC可以正式使用
使用-XX:+UseZGC命
令行选项启用ZGC
令行选项启用ZGC
Shenandoah正式使用
一个低暂停时间的垃圾收集器
优化
文本块
简化了编写 Java 程序的任务,同时避免了常见情况下的转义序列
增强 Java 程序中表示用非 Java 语言编写的代码的字符串的可读性。
instanceof
二次优化
弃用
禁用和弃用偏向锁定
移除 Nashorn JavaScript 引擎
弃用 RMI 激活以进行删除
新增API
引入 API 以允许 Java 程序安全有效地访问 Java 堆之外的外部内存
隐藏类
引入隐藏类,即不能被其他类的字节码直接使用的类
隐藏类旨在供在运行时生成类并通过反射间接使用它们的框架使用。隐藏类可以定义为访问控制嵌套的成员,并且可以独立于其他类卸载
密封类(第一版预览)
使用密封的类和接口增强 Java 编程语言。密封的类和接口限制了哪些其他类或接口可以扩展或实现它们。
爱德华兹曲线数字签名算法
jdk16新特性
instanceof
最终版本
Records
最终版本
内存相关
Elastic Metaspace
弹性的元空间
元空间中未使用的 class 元数据内存更及时地返回给操作系统,以减少元空间的内存占用空间
垃圾回收器
ZGC 支持并发栈处理
把 ZGC 中的线程栈处理从安全点移到了并发阶段
不太懂ZGC回收过程?
HotSpot 子系统可以通过该机制延迟处理线程栈
不太懂ZGC回收过程?
对内存操作api
默认对内存的操作的api需要使用强封装的类来调用,提高系统的安全性
好处是
使用统一的api有利于以后对系统化版本的升级
说明
Java 9中,我们通过利用模块来限制对JDK内部元素的访问,从而提高了JDK的安全性和可维护性。模块提供了强封装
模块外部的代码只能访问该模块导出的包的公共和受保护元素
protected 修饰的元素只能由定义它的类已经它的子类访问
强封装适用于编译时和运行时,包括已编译代码试图在运行时通过反射访问元素时。导出包的非公共元素和未导出包的所有元素都被称为是强封装的
jdk17新特性
增强
密封类
在jdk15,16中是预览版本,17正式上线
demo
public abstract sealed class Student
permits NameChangeRecordService {
}
permits NameChangeRecordService {
}
类 Student 被 sealed 修饰,说明它是一个密封类,并且只允许指定的 3 个子类继承
回复严格模式的浮点数定义
伪随机数生成器增强
mac系统平面渲染api更换
新的api Apple Metal
老的api Apple OpenGL
上下文特定反序列化过滤器
通过配置过滤器,通过一个 JVM 范围的过滤器工厂,用来为每个单独的反序列化操作选择一个过滤器。
改进
switch(暂未发布)
老代码写法
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
新代码写法
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
直接在 switch 上支持 Object 类型,这就等于同时支持多种类型,简化代码
外部函数和内存 API(孵化中)
API 可以调用本地库和处理本地数据,与java环境之外的代码和数据交互
改进14,15引进的一些api
矢量 API(二次孵化中)
增强的 API 允许以一种在运行时,可靠地编译为支持的 CPU 架构上的最佳向量指令的方式表达向量计算
删除
AOT Graal 编译器 移除
删除远程方法调用 (RMI) 激活机制,同时保留 RMI 的其余部分。
弃用
弃用安全管理器
发出IO操作,如果内核没有准备好数据,用户线程是直接返回还是等待直到获取到数据才返回
框架
netty
Netty的组件
ServerBootstrap
EventLoop
EventLoopGroup
channel
ChannelPipeline
ChannelContextHandler
netty事件类型
建立连接
连接关闭
收到消息
读完成
channel被注册到EventLoop
channel从EventLoop注销
可写状态改变
出现异常
收到一个用户自定义事件
Netty粘包拆包
粘包拆包原因
粘包拆包解决方式
固定长度的拆包器
行拆包器
分隔符拆包器
基于数据包长度的拆包器
自定义拆包方案
netty线程模型
Reactor单线程模型
Reactor多线程模型
主从Reactor多线程模型
线程池职责
接受客户端请求线程池
处理IO的线程池
netty序列化
Marshalling
Protocol Buffers
message pack
jdk自带
netty零拷贝
子主题 1
netty对象池
netty内存池
类似线程池,避免大量创建对象,避免需要使用的时候才去创建对象;
由于io操作时候,有大量的buffer对象的创建和回收,netty对这些buffer对象
进行池化而降低系统的开销
rpc
dubbo
支持的通信协议
序列化协议
子主题 3
mybatis
ibatis
缓存
一级缓存
mybatis的一级缓存是基于sqlseesion的,mybatis是通过sqlsession 对数据库做操作的
同一个sqlsession生命周期内,相同的查询语句除了第一次之外,后面的查询都是从一级缓存中
读取数据的;myabtis的一级缓存是默认开启的;
详细说明
一级缓存仅仅是本地回话缓存,仅仅就是对一次回话中的数据做缓存,java程序使用mybatis调用mysql服务的时候,线程多次调用了同样的一模一样的slq查询的时候,会做缓存,也就是说:mybatis框架下一个线程第一次调用sql会做缓存,第二次又调用了一样的sql查询,那么这个时候就是直接去缓存中拿数据的;而不是走数据库的;一级缓存也就是本地缓存,会被修改操作(新增,删除,插入)操作,事物提交,事物关闭,关闭Sqlsession都会清除一级缓存;当这个线程结束完成之后,这个缓存也失效了;
二级缓存
1.当前文件中的所有查询sql都会做缓存
2.修改语句(delete,update,insert)都会去刷新这个缓存
缓存淘汰算法
mybatis-plus
自带雪花算法
实现逻辑
DefaultIdentifierGenerator类
根据时间戳,数据中心,机器标识部分,序列化,做位于运算
图示
子主题
乐观锁插件
mybatis工作流程
详细流程图
详细步骤
1、读取mybatis配置文件-mybatis-config,xml
这个文件为mybatis的全局配置文件
配置mybatis的环境信息
2、加载xml映射文件
也就是sql映射文件
这些文件配置了操作数据库的sql语句
3、通过mybatis环境信息构造回话工厂SqlSessionFactory
4、由会话工厂SqlSessionFactory对象创建sqlSeesion回话对象
该对象中包含了执行sql语句的所有方法
5、Excutor执行器根据传入的参数动态生成需要执行的sql语句;同事负责查询缓存的维护
6、在Excutor接口执行的过程中MappedStatement类型参数改参数是对映射信息的封装
7、输入参数映射
类型可以是map,list类型,实体类,基本数据类型
8、输入结果映射
结构映射可以是基础数据类型,pojo,list,Map
spring
spring
spring的核心是什么
为了简化项目开发而且有自己生态圈的一个开源框架
spring是一个ioc和aop的容器框架
ioc
控制反转
Aop
切面编程
容器
存储bean对象且控制bean生命周期的容器
spring容器
spring容器添加组件
1.包扫描(包下面的类全部都是使用@Controller,@Service,@Respository,@Component注解)
2.@Bean直接给定义bean对象
3.@import相关注解给容器添加组件
1.@Import 导入一个对象
@Import({ 类名.class , 类名.class... })public class TestDemo {}
2.在@Import注解中导入ImportSelector接口的实现类(一次导入多个对象);
步骤1:public class Myclass implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"com.yc.Test.TestDemo3"}; }}
步骤2:@Import({TestDemo2.class,Myclass.class})public class TestDemo { @Bean public AccountDao2 accountDao2(){ return new AccountDao2(); }}
3.ImportBeanDefinitionRegistrar
步骤1:public class Myclass2 implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { //指定bean定义信息(包括bean的类型、作用域...) RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestDemo4.class); //注册一个bean指定bean名字(id) beanDefinitionRegistry.registerBeanDefinition("TestDemo4444",rootBeanDefinition); }}
步骤2:@Import({TestDemo2.class,Myclass.class,Myclass2.class})public class TestDemo { @Bean public AccountDao2 accountDao222(){ return new AccountDao2(); }}
差异和区别
ImportSelector接口和ImportBeanDefinitionRegistrar最终都是需要使用@Import 来做实现的导入,才能实现对组件的添加
4.使用FactoryBean往工厂里面手动添加bean
spring容器启动过程
TODO
spring容器的理解
TODO
spring理解
1、是spring整个生态圈的核心基石
2、简化企业级开发
1、基于pojo的轻量级和最小的侵入性编程
2、通过依赖注入还有切面接口实现松耦合
3、通过切面编程减少模板代码
3、ioc,aop的容器框架
spring使用的优势
1、Spring框架之外还存在一个构建在核心框架之上的庞大生态圈
2、低侵入式设计,代码的污染极低
3、独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺
4、Spring的loC容器降低了业务对象替换的复杂性,提高了组件之间的解耦
5、Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用
6、Spring的ORM和DAO提供了与第三方持久层框架的的良好整合,并简化了底层的数据库访问
7、Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部
spring核心类
ApplicationContext
什么是ApplicationContext
FactoryBean
BeanFactory
BeanDefinition
后置处理器
后置处理器功能
后置处理器是什么
后置处理器的处理过程
spring的事物管理
spring的事物传播特性
种类
1、required
requires_new
newsted
support
not_support
never
madatory
场景问题
某一个事务嵌套另一个事务的时候怎么办?
A方法调用B方法,AB方法都有事务,并且传播特性不同,那么A如果有异常,B怎么办,B如果有异常,A怎么办?
核心处理逻辑非常简单:
1、判断内外方法是否是同一个事务:
是:异常统一在外层方法处理.
不是:内层方法有可能影响到外层去,但是外层方法是不会影响内层方法的
1、判断内外方法是否是同一个事务:
是:异常统一在外层方法处理.
不是:内层方法有可能影响到外层去,但是外层方法是不会影响内层方法的
事物的实现
总
由spring aop通过一个TransactionInterceptor实现的
详细步骤
1、spring 先解析当前事物的属性信息(根据具体的属性判断是否要开启事物)
2、当前需要开启的时候,获取数据库连接,关闭自动提交的功能,开起事物
3、执行具体的sql逻辑
4、在操作过程中,如果执行失败嘞,会通过completeTeanscationAfterThrowing执行回滚操作,然后再通过doRollBack方法来完成具体的事物的回滚操作,最终还是调用的jdbc中的rollbcak方法做回滚操作
5、在操作过程,如果正常,那么通过commitTransactionAfterReturning来完成事物的提交,具体的提交逻辑通过doCommint方法执行实现的时候先获取jdbc链接,然后通过调用jdbc的事物提交方法提交事物;
6、当事物执行完毕之后,需要通过cleanTransactionInfo方法清理事物相关的信息
spring事物失效
bean对象没有被spring容器管理
方法修饰符不是public
方法是本类调用
事物的实现是通过aop实现的,aop的实现是通过代理来实现;如果不能形成其他类的接口调用,那么就无法生效
实际中失效最多的
数据元没有配置事物管理器
数据库不支持事物
数据库本身不支持事物
异常被捕获了
捕获的异常和配置的异常并不一直
spring的事物隔离级别
和当前项目使用的数据的隔离级别是一致的
bean
bean对象属性说明
bean对象作用域
通过配置scope属性值
prototype
多例;每次需要用到的bean的时候就会去创建这个bean,也就是在一个request请求中可能会多次创建bean对象;
singleton
单例,整个容器都会只用这一个对象,作用域就是整个容器
request
同一次请求创建一个对象;bean对象的作用域就在一起请求当中
session
同一次会话创建一个对象;这个Bean对象的作用域就在这次会话当中
global session
该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例
懒加载
通过设置lazy-init
true
懒加载
false
非懒加载,也就是提前加载
懒加载概述
懒加载只是针对多例bean才生效;单实例的bean都是提前加载;多实例的bean默认都是懒加载的形式加载,也就是首次需要用到的时候才会加载;
spring默认是提前加载,所以在启动的时候控制台就会有很多对bean对象的加载
懒加载和非懒加载区别
懒加载在第一需要用到这个对象的时候加载;非懒加载就是在环境启动的时候就提前把对象加载;
懒加载可以节约内存,但是不利于提前发现问题;非懒加载,可以提前发现加载的问题;
注意事项:
1.如果懒加载依赖了非懒加载的bean,那么这个懒加载就会失效;
什么是spring bean对象
TODO
bean注入的注解
1.@AutoWried
注解是spring提供的;
2.@Resource
注解是JDK提供的
相同点不同点
相同点:
1.字段和setter都生效
不同点:
1.一个是spring提供的,一个是JDK提供的
2.注解中的属性不一样;
bean对象装配
bean的装配过程
1.实例化spring容器,根据扫描对应的包,把标记是bean对象的类存储在一个集合之中;
2.然后在遍历集合中类的对象,把对象的相应的属性都存储到BeanDefintion 中,也就是每一个被遍历的要被构建bean对象的对象都会有一个专属的BeanDefintion 对象;这个BeanDefintion对象存储当前被遍历对象的一切属性,这些属性也是构建bean对象的基础;
3.每一个BeanDefintion 会被存储到 BeanDefintionMap 对象之中;
4.然后还会调用beanFactoryPostProcessor后置处理器去修改BeanDefintionMap,执行对bean的修改拓展操作(spring内置还有我们自定义的后置处理器)来参与到生命周期的过程;
5.遍历BeanDefintionMap 中BeanDefintion对象,做相关的是否单例,是否抽象,注入模型,循环依赖,是不是懒加载,是不是需要有DepondsOn
6.根据BeanDefintion对象中的信息中的构造方法信息,推断出构造方法;
7.再通过构造方法再去通过反射实例化对象;
8.缓存相关注解信息,合并到BeanDefintion对象中去;
9.在三级缓存中暴露一个工厂对象;
10.判断是否需要完成属性注入;
11.完成属性注入;属性注入(还需要考虑到循环依赖问题)
12回调Aware接口,也就是spring内置的Aware接口(也是修改bean对象的属性)
13、再调用生命周期初始化回调方法(在类中间被@PostConstruct注解修饰,还有实现了InitilizingBean接口的afterPropertiesSet方法,还有xml中配置的afterPropertiesSet方法)
14.完成aop代理,事件分发,事件监听;
15.生成了Bean,把bean放在单例池;
16.关闭spring容器,DisposableBean接口,那么这些bean对象也会跟着被销毁;
装配过程示意图
子主题
bean加载过程(是bean装配过程的一部分)
属性赋值
1、获取ABean对象,从Abean的各级缓存中获取
2、发现没有,那么就开始创建Abean实例,且把ABean放在三级缓存中
3、ABean对象需要进行实例化,然后给字段赋值
4、发现BBean数据不存在,然后也是从各级缓存中获取
5、发现没有,那么就进行Bbean创建,将BBean放在三级缓存当中;
6、BBean然后开始实例化,进行字段赋值,发现需要用到AABean,会从三级缓存拿到ABean的引用;
7、把ABean从三级缓存中删除,并放入二级缓存中;
8、然后BBean的实例化完成,将三级缓存中BBean删除,且放入一级缓存中;
9、BBean已经创建完成,这个时候接着进行ABean的创建,此时ABean依赖的BBean已经在一级缓存中;直接获取到BBean;
10、ABean也创建完成,将ABean从二级缓存中删除,放入一级缓存;
bean增强修改
12回调Aware接口,也就是spring内置的Aware接口(也是修改bean对象的属性)
13、再调用生命周期初始化回调方法(在类中间被@PostConstruct注解修饰,还有实现了InitilizingBean接口的afterPropertiesSet方法,还有xml中配置的afterPropertiesSet方法)
13.完成aop代理,事件分发,事件监听;
加载过程示意图
子主题
bean实例化和初始化
实例化
根据BeanDefintion对象中存储的构造信息,通过反射创建对象的过程
初始化
完成bean对象的增强操作
12回调Aware接口,也就是spring内置的Aware接口(也是修改bean对象的属性)
13、再调用生命周期初始化回调方法(在类中间被@PostConstruct注解修饰,还有实现了InitilizingBean接口的afterPropertiesSet方法,还有xml中配置的afterPropertiesSet方法)
14.完成aop代理,事件分发,事件监听;
bean注入方式
属性注入(最常用的一种方式)
构造器注入
方法上注入
参数上注入
什么是Spring的嵌入beans
循环依赖
什么是循环依赖
BeanA依赖BeanB;而BeanB又依赖BeanA
spring是怎么解决循环依赖
三级缓存
三级缓存各自作用
一级缓存
单例池
使用ConcurrentHashMap
存放构建完成后的完整的bean对象
二级缓存
作用
存放属性字段不完全的bean
特殊说明
也只有处于循环引用中的bean才会被存放在这个二级缓存
三级缓存
作用
存放用于ObjectFactory对象,这个对象是用于来构建bean对象;所以刚刚被创建出来的bean对象都是放在三级缓存
存储刚刚被实例化的bean对象,这个时候已经有了对应的引用
子主题
三级缓存流转顺序
有循环依赖
1、反射创建bean实例的时候,将bean对象对应ObjectFactory放在三级缓存
2、有循环依赖时,从三级缓存中获取ObjectFactory实例,然后通过ObjectFactory实例的getObject方法方法来获取早期的bean还没有完全构建完成的bean,且放入二级缓存,从三级缓存中将ObjectFactory实例删除,
3、bean初始化完成,生命周期的相关方法都执行了,就会把bean对象放入一级缓存,然后删除二级缓存;
无循环依赖
1、反射创建bean实例的时候,将bean对象对应ObjectFactory放在三级缓存
3、bean初始化完成,生命周期的相关方法都执行了,就会把bean对象放入一级缓存,然后删除二级缓存;
总结
也就是有循环依赖:三级缓存到二级缓存,再到一级缓存
没有循环依赖就是三级缓存,到一级缓存
循环依赖加载bean属性获取过程
具体过程
1、获取ABean对象,从Abean的各级缓存中获取
2、发现没有,那么就开始创建Abean实例,且把ABean放在三级缓存中
3、ABean对象需要进行实例化,然后给字段赋值
4、发现BBean数据不存在,然后也是从各级缓存中获取
5、发现没有,那么就进行Bbean创建,将BBean放在三级缓存当中;
6、BBean然后开始实例化,进行字段赋值,发现需要用到AABean,会从三级缓存拿到ABean的引用;
7、把ABean从三级缓存中删除,并放入二级缓存中;
8、然后BBean的实例化完成,将三级缓存中BBean删除,且放入一级缓存中;
9、BBean已经创建完成,这个时候接着进行ABean的创建,此时ABean依赖的BBean已经在一级缓存中;直接获取到BBean;
10、ABean也创建完成,将ABean从二级缓存中删除,放入一级缓存;
图片展示
子主题
单例bean是否是线程安全
spring没有对bean对象做多线程处理;bean对象是否安全,主要看定义者怎么定义bean;如果这个bean对象中定义了线程不安全的全局变量,那么这个bean对象就是不安全的;如果定义的对象就是安全,那么这个bean就是安全的;
bean的线程并发问题
定义bean为无状态的bean(对象的时候不要定义全局变量),这样才能再多线程环境下被访问,使用ThreadLoacl来对bean进行线程封闭处理;如果不是无状态的,最好就是把bean定义成多例,每一个线程,或者每一个请求,每一个会话都是一个单独的bean对象
@Resource和@Autowired
区别
来源不一样
@Resource是jdk提供的注解
@Autowired
注入方式
@Resource默认通过byname注入,也就数已通过名称注入,通过的接口名称来做注入;也可以指定byType注入
@Autowired默认是通过byType(也就是类型注入),也就是默认注入接口的子类;如果接口有多个子类,搭配使用@Qualifier注解指定某一个特定子类
使用差别
如果接口都只有一个子类,那么没有使用上的区别,如果接口有多个实现类,那么只能用@Autowried 搭配@Qualifier 来使用
作用
都可以完成bean对象的注入标识
IOC
IOC的理解
总体回答
容器:存储对象,使用map结构来存储
IOC容器理解
TODO
IOC原理
IOC的实现
工作原理,过程,数据结构,
依赖注入
DI:依赖注入,把对应的属性的值注入到具体的对象中,(SJAutowired, populateBean完成属性值的注入
控制反转
控制反转:理论思想,原来的对象是由使用者来进行控制,有了spring之后,可以把整个对象交给spring来帮我们进行管理
容器的生命周期
容器的生命周期
整个bean的生命年期,从创建到使用到销毁的过程全部都是由容器来管理(bean的生命周期)
如何实现一个ioc容器
1、先准备一个基本的容器对象,包含一些map结构的集合,用来方便后续过程中存储具体的对象
2、进行配置文件的读取工作或者注解的解析工作,将需要创建的bean对象都封装成BeanDefinition对象存储在容器中
3、容器将封装好的BeanDefinition对象通过反射的方式进行实例化,完成实例化工作
4、进行对象的初始化操作,也就是给类中的对应属性值就行设置,也就是进行依赖注入,完成整个对象创建一个完整的bean对象,存储在容器的某个map结构中
5、通过容器对象来获取对象,进行对象的获取和逻辑处理工作
6、提供销毁操作,当对象不用或者容器关闭的时候,将无用的对象进行销毁
AOP
AOP的理解
AOP全称叫做Aspect Oriented Programming面向切面编程.它是为解耦而生;
任何一个系统都是由不同的组件组成的,每个组件负责一块特定的功能,当然会存在很多组件是跟业务无关
的,例如日志、事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每
一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此我们需要将这些公共的代码逻辑抽象出
来变成一个切面,然后注入到目标对象(具体业务)中去,AOP正是基于这样的一个思路实现的,通过动态代
理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改
原有业务的逻辑代码,只需要在原来的业务逻辑基础之上做一些增强功能即可。
的,例如日志、事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每
一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此我们需要将这些公共的代码逻辑抽象出
来变成一个切面,然后注入到目标对象(具体业务)中去,AOP正是基于这样的一个思路实现的,通过动态代
理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改
原有业务的逻辑代码,只需要在原来的业务逻辑基础之上做一些增强功能即可。
AOP相关概念
切面( Aspect)
将多个类的通用行为封装在一起的可重用的模块
切点的集合
连接点Uoin point)
被AOP拦截到需要增强的方法
切点增强之前所执行的拦截方法
通知(Advice)
被增强方法上执行的增强处理
切点( Pointcut)
真正要被增强的方法
引入( Introduction
在不修改代码的前提下,在运行期动态的给被代理的类增加功能
目标对象(Target object
被一个或者多个切面所通知的对象.也被称作被通知(advised) 对象。既然 SpringAOP是通过运行时代理实现的,那么这个对象永远是一个被代理( proxied) 的对象。
Aop代理
AOP框架创建的对象,用来实现切面契约(aspect contract) (包括通知方法执行
织人(Weaving):
完成所有的切点切入,对应的目标对象完成代理对象的创建
AOP的使用场景
日志记录
性能统计
安全控制
事物
AOP执行流程
1、通过配置的切面信息,得到具体的具体的连接点,通过对连接点中方法的拦截,得到需要具体被增强的方法
2、然后在不改变代码的情况下,通过需要被增强方法的类通过jdk代理,或者cglib代理得到代理对象;
3、在生成代理对象过程中,需要将增强的信息添加到代理对象中去
这个过程其实就是通过原有对象的字节码文件,在原有的基础之上增加了新的功能对应的字节码指令
4、在实际对对象的执行过程中,都是通过代理对象来做功能的执行,以达到对应的前置,后置,环绕,返回后,抛出异常后得有一些增强操作;
java程序在执行的过程中都是通过字节码文件中的字节码指令来执行程序的。
AOP的实现
动态代理
cglib代理,或者jdk代理
本质都是通过原有对象的字节码文件,在此基础之上给字节码文件中添加被增强的字节码指令
小贴士:这个在jvm - jvm编译一节有详细说明,动态代理要理解到这一层,才算是有一个大概的理解;
和AspectJ AOP区别
AspectJ 是第三方实现AOP功能的一个框架
切面的通知类型
1、before
2、after
3、AfterReturning
4、AfterThrowing
5、Around
spring框架组件
核心组件
bean组件(bean对象组件)
封装对象
core组件(核心组件)
发现,构建,维护每一个bean关系所有需要的一些列的关系
context组件(上下文组件)
发现,构建,并且维护每一个bean对象之间依赖关系;可以看成是bean对象的集合,
spring框架设计模式
1、工厂模式
各种BeanFactory,Application创建中都用了
2、模板模式
各种BeanFactory,Application创建中都用到,中间有很多空实现,方便子类去增加方法
3、代理模式
aop的实现用的动态代理
4、策略模式
5、单利模式
bean对象默认都是单利
6、观察者模式
7、适配器模式
8、装饰者模式
重要注解
引入bean对象
importSelector
import
ImportBeanDefinitionRegistrar
bean对象
@Repository
@Service
@Controller
@Bean
@conditional
@AutoWried
属性值默认是true,一定要被注入;也可以设置成reqiured=false,告诉当前类可以不用注入这个对象;
springBoot
springBoot优势
1.可以以jar包的形式独立启动
2.内嵌了tomcat/jetty/Undertow服务器。
3.在spring的基础上简化了maven的配置,引入spring-boot-starter-web 依赖;
4.springBoot可以根据类路径中的jar包,类自动配置成bean;
5.有运行时的监控系统
6.快速构建项目,提高开发效率,部署效率;
7.天然与云计算集成;
8.天然的已经整合了很多第三方的框架
springBoot核心注解
@SpringBootApplication最核心注解
包含以下注解
@SpringBootConfiguration
表示是springboot的配置类
@EnableAutoConfiguration
开启自动配置注解
去mate-info/spring.factiories文件中加载需要被自动注入的java类
@ComponentScan
表示要扫描的当前包以及子包下的所有被标记成spring bean对象的java类
@importResource,@Import
手动给spring添加bean对象,导入一些第三方的配置类
@Indexed
提升@CompontentScan性能的注解
@EnableAsync
开启异步方法
@EnableScheduling
开启定时任务
springBoot自动装配大致过程
1、springBoot启动类上面是@SpringBootApplication注解
3、这个注解中又包含了另外一个@Import注解
4、import注解又实现了ImportSelector接口的类型,
5、ImportSelector可以给spring容器中手动添加bean对象
6、所以在spring启动的时候通过ImportSelector中的方法去读取meta/info目录下面的spring.factories文件中需要被自动转配的所有配置类
7、然后再通过meate/info下的springautoconfigure-metadata.properties文件做条件过滤,过滤掉不需要被自动装配的类
8、得到最终需要被装配的类,然后加载到spring容器
springBoot自动装配原理
https://www.bilibili.com/video/BV1TV4y157yz?p=28&vd_source=e6fc2faffd817786a7a38aeaab56c2a1
TODO
springBoot启动方式
1、通过main方法启动
2、达成jar包通过 java -jar的命令启动,或者达成war包掉到web容器中启动
3、使用maven/gradle 插件来运行
springBoot简单介绍
springBoor是基于spring创建的一个上层应用框架,14年发布1.0会比以前那种ssm,给予配置的方式效率高很多
springBoot中starter(启动器)的理解
springBoot启动的时候默认将springBoot自身的bean对象,项目中开发定义的bean对象,以及sptingBoot默认的一些bean对象加载到bean容器
starter会去读取meta-info目录下提供的一个spring.factories文件,将需要默认加载进spring容器的类进行一个读取加载操作
如果第三方的插件组件也需要在启动的时候加载进spring容器,可以自身和spring做一个整合包。但是通常springBoot已经提高了很多整合包了,整合报的命名通常都是 spring-boot-starter-xxx pom依赖
实际开发
如果想引入第三方的配置需要的步骤
1、添加spring-boot-starter-xxx的依赖包到pom文件
2、在配置文件中配置相关的需要的配置
常用的starter包
spring boot-starter-web
提供了SpringMVC+内嵌了Tbmcat容器
spring-boot-starterdata-jpa
提供了 SpringJPA和Hibernate框架整合
sprinq-boot-starter-data-redis
redis数据源整合
spring-boot-starter-solr
solr搜索
mybatis-spring-boot-starter
mybatis-springBoot整合
springBoot需要web容器
一般不需要
spring boot-starter-web
提供了SpringMVC+内嵌了Tbmcat容器
springBoot项目中如何解决跨域问题
解决方式
1、类似通过jsonp类解决跨域问题,但是支持Get方式请求
2、SpringBoot中我们可以通过WebMvcConfigurer重写addCorsMapping方法,在这个方法中添加允许进行跨域的相关请求
什么是跨域问题
1、浏览器的同源策略限制:两个页面的协议,ip地址,端口号必须要是一样的才能请求
还是为了浏览器访问数据安全
跨域就是:协议不一致,ip地址,端口中只要有一个不一致就是跨域
springBoot怎么集成第三方组件
soringBoot项目如何使用log4j
springBoot默认使用的是logback
1、先排除logback的包
2、pom文件中添加log4j依赖
4、配置log4j相关配置
通用方法
1、pom文件中引入springBoot与第三方组件的得整合启动包:spring-boot-starter-redis
2、再在配置文件中引入该组件的必须要配置的配置项
springBoot中bootstrap.yml文件作用
springBoot默认支持的属性文件
application.properties
application.xml
application.yml
application.yaml
springBoot默认是不支持这类配置文件
在springCloud环境下才支持,作用是在springBoot项目启动之前启动一个父容器;
父容器可以在springBoot容器启动之前完成一些加载初始化的操作:比如说加载配置中心的信息
springBoot中Actuator的理解
作用
1、微服务环境下帮助我们快速定位问题
2、可以对整个系统做出性能监控
3、统一日志管理
信息的监控可以在springBoot自带的springBoot admin监控平台上看到;
使用步骤
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、配置路径
# 访问端口
management.server.port=8081
# 根路径
management.endpoints.web.base-path=/actuator/z
# web端允许的路径
management.endpoints.web.exposure.include=*
management.server.port=8081
# 根路径
management.endpoints.web.base-path=/actuator/z
# web端允许的路径
management.endpoints.web.exposure.include=*
3、请求获取当前可用的统计接口信息
http://127.0.0.1:8081/actuator/z
/actuator/z里还有端口都是配置文件中配置的
可以得到当前所有的统计信息项目
返回请求路径信息
{
"_links":{
"self":{
"href":"http://127.0.0.1:8081/actuator/z",
"templated":false
},
"prometheus":{
"href":"http://127.0.0.1:8081/actuator/z/prometheus",
"templated":false
},
"beans":{
"href":"http://127.0.0.1:8081/actuator/z/beans",
"templated":false
},
"caches-cache":{
"href":"http://127.0.0.1:8081/actuator/z/caches/{cache}",
"templated":true
},
"caches":{
"href":"http://127.0.0.1:8081/actuator/z/caches",
"templated":false
},
"health-path":{
"href":"http://127.0.0.1:8081/actuator/z/health/{*path}",
"templated":true
},
"health":{
"href":"http://127.0.0.1:8081/actuator/z/health",
"templated":false
},
"info":{
"href":"http://127.0.0.1:8081/actuator/z/info",
"templated":false
},
"conditions":{
"href":"http://127.0.0.1:8081/actuator/z/conditions",
"templated":false
},
"configprops":{
"href":"http://127.0.0.1:8081/actuator/z/configprops",
"templated":false
},
"configprops-prefix":{
"href":"http://127.0.0.1:8081/actuator/z/configprops/{prefix}",
"templated":true
},
"env":{
"href":"http://127.0.0.1:8081/actuator/z/env",
"templated":false
},
"env-toMatch":{
"href":"http://127.0.0.1:8081/actuator/z/env/{toMatch}",
"templated":true
},
"loggers":{
"href":"http://127.0.0.1:8081/actuator/z/loggers",
"templated":false
},
"loggers-name":{
"href":"http://127.0.0.1:8081/actuator/z/loggers/{name}",
"templated":true
},
"heapdump":{
"href":"http://127.0.0.1:8081/actuator/z/heapdump",
"templated":false
},
"threaddump":{
"href":"http://127.0.0.1:8081/actuator/z/threaddump",
"templated":false
},
"metrics":{
"href":"http://127.0.0.1:8081/actuator/z/metrics",
"templated":false
},
"metrics-requiredMetricName":{
"href":"http://127.0.0.1:8081/actuator/z/metrics/{requiredMetricName}",
"templated":true
},
"scheduledtasks":{
"href":"http://127.0.0.1:8081/actuator/z/scheduledtasks",
"templated":false
},
"mappings":{
"href":"http://127.0.0.1:8081/actuator/z/mappings",
"templated":false
},
"refresh":{
"href":"http://127.0.0.1:8081/actuator/z/refresh",
"templated":false
},
"features":{
"href":"http://127.0.0.1:8081/actuator/z/features",
"templated":false
}
}
}
"_links":{
"self":{
"href":"http://127.0.0.1:8081/actuator/z",
"templated":false
},
"prometheus":{
"href":"http://127.0.0.1:8081/actuator/z/prometheus",
"templated":false
},
"beans":{
"href":"http://127.0.0.1:8081/actuator/z/beans",
"templated":false
},
"caches-cache":{
"href":"http://127.0.0.1:8081/actuator/z/caches/{cache}",
"templated":true
},
"caches":{
"href":"http://127.0.0.1:8081/actuator/z/caches",
"templated":false
},
"health-path":{
"href":"http://127.0.0.1:8081/actuator/z/health/{*path}",
"templated":true
},
"health":{
"href":"http://127.0.0.1:8081/actuator/z/health",
"templated":false
},
"info":{
"href":"http://127.0.0.1:8081/actuator/z/info",
"templated":false
},
"conditions":{
"href":"http://127.0.0.1:8081/actuator/z/conditions",
"templated":false
},
"configprops":{
"href":"http://127.0.0.1:8081/actuator/z/configprops",
"templated":false
},
"configprops-prefix":{
"href":"http://127.0.0.1:8081/actuator/z/configprops/{prefix}",
"templated":true
},
"env":{
"href":"http://127.0.0.1:8081/actuator/z/env",
"templated":false
},
"env-toMatch":{
"href":"http://127.0.0.1:8081/actuator/z/env/{toMatch}",
"templated":true
},
"loggers":{
"href":"http://127.0.0.1:8081/actuator/z/loggers",
"templated":false
},
"loggers-name":{
"href":"http://127.0.0.1:8081/actuator/z/loggers/{name}",
"templated":true
},
"heapdump":{
"href":"http://127.0.0.1:8081/actuator/z/heapdump",
"templated":false
},
"threaddump":{
"href":"http://127.0.0.1:8081/actuator/z/threaddump",
"templated":false
},
"metrics":{
"href":"http://127.0.0.1:8081/actuator/z/metrics",
"templated":false
},
"metrics-requiredMetricName":{
"href":"http://127.0.0.1:8081/actuator/z/metrics/{requiredMetricName}",
"templated":true
},
"scheduledtasks":{
"href":"http://127.0.0.1:8081/actuator/z/scheduledtasks",
"templated":false
},
"mappings":{
"href":"http://127.0.0.1:8081/actuator/z/mappings",
"templated":false
},
"refresh":{
"href":"http://127.0.0.1:8081/actuator/z/refresh",
"templated":false
},
"features":{
"href":"http://127.0.0.1:8081/actuator/z/features",
"templated":false
}
}
}
路径
http://127.0.0.1:8081/actuator/z
http://127.0.0.1:8081/actuator/z/prometheus
http://127.0.0.1:8081/actuator/z/beans
当前用哪些bean对象
http://127.0.0.1:8081/actuator/z/health
健康信息
http://127.0.0.1:8081/actuator/z/caches/{cache}
内存信息
http://127.0.0.1:8081/actuator/z/heapdump
当前对内存信息
http://127.0.0.1:8081/actuator/z/threaddump
当前线程内存信息
http://127.0.0.1:8081/actuator/z/metrics
统计信息的具体项目
http://127.0.0.1:8081/actuator/z/scheduledtasks
当前项目有哪些定时任务
http://127.0.0.1:8081/actuator/z/refresh
http://127.0.0.1:8081/actuator/z/features
spring开启特殊功能
@EnableAspectJAutoProxy
开启AspectJ自动代理支持
@EnableAsync
开启异步方法
@EnableScheduling
开启定时任务
@EnableWebMvc
开启web mvc配置
@enabletransactionmanagement
开启事物
@EnableRetry
开启方法重试功能
EnableCircuitBreaker
开启方法熔断功能
springBoot官方提供的starter
spring-boot-starter 核心启动器,包括自动配置支持,日志记录和YAML
spring-boot-starter-activemq 使用Apache ActiveMQ进行JMS消息传递的入门者
spring-boot-starter-amqp 使用Spring AMQP和Rabbit MQ的入门者
spring-boot-starter-aop 使用Spring AOP和AspectJ进行面向方面编程的入门者
spring-boot-starter-artemis 使用Apache Artemis进行JMS消息传递的入门者
spring-boot-starter-batch 使用Spring Batch的入门者
spring-boot-starter-cache 使用Spring Framework的缓存支持的初学者
spring-boot-starter-cloud-connectors 使用Spring Cloud Connectors的初学者简化了Cloud Foundry和Heroku等云平台中的服务连接
spring-boot-starter-data-cassandra 使用Cassandra分布式数据库和Spring Data Cassandra的入门者
spring-boot-starter-data-couchbase 使用Couchbase面向文档的数据库和Spring Data Couchbase的初学者
spring-boot-starter-data-elasticsearch 使用Elasticsearch搜索和分析引擎以及Spring Data Elasticsearch的初学者
spring-boot-starter-data-gemfire 使用GemFire分布式数据存储和Spring Data GemFire的初学者
spring-boot-starter-data-jpa 将Spring Data JPA与Hibernate一起使用的初学者
spring-boot-starter-data-ldap 使用Spring Data LDAP的入门者
spring-boot-starter-data-mongodb 使用MongoDB面向文档的数据库和Spring Data MongoDB的初学者
spring-boot-starter-data-neo4j 使用Neo4j图形数据库和Spring Data Neo4j的入门者
spring-boot-starter-data-redis 与Spring Data Redis和Jedis客户端一起使用Redis键值数据存储的初学者
spring-boot-starter-data-rest 使用Spring Data REST通过REST公开Spring Data存储库的初学者
spring-boot-starter-data-solr 使用Apache Solr搜索平台和Spring Data Solr的初学者
spring-boot-starter-freemarker 使用FreeMarker视图构建MVC Web应用程序的入门者
spring-boot-starter-groovy-templates 使用Groovy模板视图构建MVC Web应用程序的入门者
spring-boot-starter-hateoas 使用Spring MVC和Spring HATEOAS构建基于超媒体的RESTful Web应用程序的初学者
spring-boot-starter-integration 使用Spring Integration的入门者
spring-boot-starter-jdbc 将JDBC与Tomcat JDBC连接池一起使用的入门者
spring-boot-starter-jersey 使用JAX-RS和Jersey构建RESTful Web应用程序的初学者。替代spring-boot-starter-web
spring-boot-starter-jooq 使用jOOQ访问SQL数据库的初学者。替代spring-boot-starter-data-jpa或spring-boot-starter-jdbc
spring-boot-starter-jta-atomikos 使用Atomikos进行JTA交易的入门者
spring-boot-starter-jta-bitronix 使用Bitronix进行JTA事务的入门者
spring-boot-starter-jta-narayana Spring Boot Narayana JTA Starter
spring-boot-starter-mail 使用Java Mail和Spring Framework的电子邮件发送支持的初学者
spring-boot-starter-mobile 使用Spring Mobile构建Web应用程序的入门者
spring-boot-starter-mustache 使用Mustache视图构建MVC Web应用程序的入门者
spring-boot-starter-security 使用Spring Security的入门者
spring-boot-starter-social-facebook 使用Spring Social Facebook的初学者
spring-boot-starter-social-linkedin Stater使用Spring Social LinkedIn
spring-boot-starter-social-twitter 使用Spring Social Twitter的初学者
spring-boot-starter-test 使用JUnit,Hamcrest和Mockito等库来测试Spring Boot应用程序的初学者
spring-boot-starter-thymeleaf 使用Thymeleaf视图构建MVC Web应用程序的入门者
spring-boot-starter-validation 使用Java Bean Validation和Hibernate Validator的初学者
spring-boot-starter-web 使用Spring MVC构建Web(包括RESTful)应用程序的入门者。使用Tomcat作为默认嵌入式容器
spring-boot-starter-web-services 使用Spring Web Services的入门者
spring-boot-starter-websocket 使用Spring Framework的WebSocket支持构建WebSocket应用程序的初学者
spring-boot-starter 核心启动器,包括自动配置支持,日志记录和YAML
spring-boot-starter-activemq 使用Apache ActiveMQ进行JMS消息传递的入门者
spring-boot-starter-amqp 使用Spring AMQP和Rabbit MQ的入门者
spring-boot-starter-aop 使用Spring AOP和AspectJ进行面向方面编程的入门者
spring-boot-starter-artemis 使用Apache Artemis进行JMS消息传递的入门者
spring-boot-starter-batch 使用Spring Batch的入门者
spring-boot-starter-cache 使用Spring Framework的缓存支持的初学者
spring-boot-starter-cloud-connectors 使用Spring Cloud Connectors的初学者简化了Cloud Foundry和Heroku等云平台中的服务连接
spring-boot-starter-data-cassandra 使用Cassandra分布式数据库和Spring Data Cassandra的入门者
spring-boot-starter-data-couchbase 使用Couchbase面向文档的数据库和Spring Data Couchbase的初学者
spring-boot-starter-data-elasticsearch 使用Elasticsearch搜索和分析引擎以及Spring Data Elasticsearch的初学者
spring-boot-starter-data-gemfire 使用GemFire分布式数据存储和Spring Data GemFire的初学者
spring-boot-starter-data-jpa 将Spring Data JPA与Hibernate一起使用的初学者
spring-boot-starter-data-ldap 使用Spring Data LDAP的入门者
spring-boot-starter-data-mongodb 使用MongoDB面向文档的数据库和Spring Data MongoDB的初学者
spring-boot-starter-data-neo4j 使用Neo4j图形数据库和Spring Data Neo4j的入门者
spring-boot-starter-data-redis 与Spring Data Redis和Jedis客户端一起使用Redis键值数据存储的初学者
spring-boot-starter-data-rest 使用Spring Data REST通过REST公开Spring Data存储库的初学者
spring-boot-starter-data-solr 使用Apache Solr搜索平台和Spring Data Solr的初学者
spring-boot-starter-freemarker 使用FreeMarker视图构建MVC Web应用程序的入门者
spring-boot-starter-groovy-templates 使用Groovy模板视图构建MVC Web应用程序的入门者
spring-boot-starter-hateoas 使用Spring MVC和Spring HATEOAS构建基于超媒体的RESTful Web应用程序的初学者
spring-boot-starter-integration 使用Spring Integration的入门者
spring-boot-starter-jdbc 将JDBC与Tomcat JDBC连接池一起使用的入门者
spring-boot-starter-jersey 使用JAX-RS和Jersey构建RESTful Web应用程序的初学者。替代spring-boot-starter-web
spring-boot-starter-jooq 使用jOOQ访问SQL数据库的初学者。替代spring-boot-starter-data-jpa或spring-boot-starter-jdbc
spring-boot-starter-jta-atomikos 使用Atomikos进行JTA交易的入门者
spring-boot-starter-jta-bitronix 使用Bitronix进行JTA事务的入门者
spring-boot-starter-jta-narayana Spring Boot Narayana JTA Starter
spring-boot-starter-mail 使用Java Mail和Spring Framework的电子邮件发送支持的初学者
spring-boot-starter-mobile 使用Spring Mobile构建Web应用程序的入门者
spring-boot-starter-mustache 使用Mustache视图构建MVC Web应用程序的入门者
spring-boot-starter-security 使用Spring Security的入门者
spring-boot-starter-social-facebook 使用Spring Social Facebook的初学者
spring-boot-starter-social-linkedin Stater使用Spring Social LinkedIn
spring-boot-starter-social-twitter 使用Spring Social Twitter的初学者
spring-boot-starter-test 使用JUnit,Hamcrest和Mockito等库来测试Spring Boot应用程序的初学者
spring-boot-starter-thymeleaf 使用Thymeleaf视图构建MVC Web应用程序的入门者
spring-boot-starter-validation 使用Java Bean Validation和Hibernate Validator的初学者
spring-boot-starter-web 使用Spring MVC构建Web(包括RESTful)应用程序的入门者。使用Tomcat作为默认嵌入式容器
spring-boot-starter-web-services 使用Spring Web Services的入门者
spring-boot-starter-websocket 使用Spring Framework的WebSocket支持构建WebSocket应用程序的初学者
spring ,springMvc ,springBoot区别
spring和springMvc
1、spring是一个一站式轻量级的java开发框架,核心是控制反转和切面编程,针对web层,业务层,持久层等都提供了多种配置解决方案
2. springMvc是spring基础之上的一个MVC框架,主要处理web开发的路径映射和视图演染,属于spring框架中
springMvc和springBoot
1、springMvc属于一个企业WEB开发的MVC框架,涵盖面包括前端视图开发、文件配置、后台接口逻辑开发
等,XML、config等配置相对比较繁琐复杂;
等,XML、config等配置相对比较繁琐复杂;
2、springBoot框架相对于springMvc框架来说,更专注于开发微服务后台接口,不开发前端视图,同时遵循默认优于配置,简化了插件配置流程,不需要配置xml,相对springmvc,大大简化了配置流程;
总结
1、Spring框架就像一^家族,有众多衍生产品例如boot、security、jpa等等。但他们的基础都是Spring的ioc. aop等.ioc提供了依赖注入的容器,aop解决了面向横切面编程,然后在此两者的基础上实现了其他延伸产
品的高级功能;
品的高级功能;
2、springMvc主要解决WEB开发的问题,是基于Servlet的fMVC框架,通过XML配置,统T发前端视图和
后端逻辑;
后端逻辑;
3、由于Spring的配置非常复杂,各种XML、JavaConfig、servlet处理起来匕啜繁琐,为了简化开发者的使用,
从而创造性地推出了springB。。唯架,默认优于配置,简化了springMvc的配置流程;但区别于springMvc的是,
springBoot专注于单体微8艮务接口开发,和前端解耦,虽然springBoot也可以做成springMvc前后台一起开发,
但是这就有点不符合springBoot框架的初衷了 ;
从而创造性地推出了springB。。唯架,默认优于配置,简化了springMvc的配置流程;但区别于springMvc的是,
springBoot专注于单体微8艮务接口开发,和前端解耦,虽然springBoot也可以做成springMvc前后台一起开发,
但是这就有点不符合springBoot框架的初衷了 ;
spring-cloud
微服务组件
注册中心
注册中心概述
针对于动态数据,对于服务器的加入,或者删除是可以动态感知的;
注册中心核心功能
服务注册
服务提供方想注册中心发送的心跳时间和续约时间
服务发现
服务提供方想注册中心发送的心跳时间和续约时间
动态追踪服务注册信息
自我保护机制
Eureka 的自我保护机制工作
如果15分钟之内超过85%的客户端节点都没有正常的心跳信息,那么Eureka就会认为客户端和注册中心发生了网络故障,那么EURKEA就会自动进入自我保护机制
Eureka 的自我保护机制下Eureka工作情况
不再从注册列表中剔除长时间没有发送心跳的客户端
继续接受新的服务注册和服务查询,但是这些数据都不会被同步到其他的注册中心;
网络稳定之后,Eureka注册信息才会被同步到其他机器上;
Eureka 的自我保护模式是有意义
防止因为分区网络,导致心跳信息没有发送或者没有接受到信条信息,而错误的把一个正常服务的机器从服务注册列表中剔除;
健康检查
通过客户端给服务端间隔一定时间发送心跳还有发送续约信息;
注册中心好处
客户端,服务端ip地址解耦合
实现服务的动态扩容或者动态删除服务
可监控各个服务运行情况
注册中心实现
Zookeeper
eurake
工作流程
1、启动eureka服务集群
2、eureka客户端通过配置的eureka的服务ip,发送ip,端口,还有服务接口信息
3、Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常
4、服务调用的时候会向注册中心获取服务列表,然后从服务列表中获取具体信息,发送服务
工作机制
服务注册
服务注册调用示意图
示意图
各个服务启动时,Eureka Client都会将服务注册到Eureka Server,Eureka Server 内部有二层缓存机制来维护整个注册表
作用
Eurake client 想节点提供自身的元数据,比如 IP 地址、端口
服务续约
过程
Eureka Client 会每隔 30 秒会向eurake节点发送一次心跳来续约,告诉节点服务还在正常运行;
重要属性
服务失效时间
默认30秒
子主题
续约间隔时间
如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的续约,Server 端会将实例从其注册表中删除
服务剔除
Eureka Client 和 Eureka Server 不再有心跳时,Eureka Server 会将该服务实例从服务注册列表中删除,即服务剔除
服务下线
服务关闭时候,想eurake节点发送取消请求,然后该客户端对应的注册信息就会在注册列表中被删除
获取注册列表信息
Eureka客户端从eurake服务节点上获取服务注册列表信息,通过ribbon负载均衡进行远程调用,并将服务注册列表信息缓存在本地。
重要属性
开启还是关闭eurake客户端从节点拉取注册信息
拉取注册列表时间间隔
自我保护机制
eureka不会剔除已经挂掉的服务,会认为这个服务在尝试重新连接
eureka缓存
1.客户端拉服务列表,注册中心会从缓存中拿,并不每次都是从服务注册列表中获取;
服务端,客户端缓存
eureka服务端信息三级缓存
registry
实时更新
readWriteCacheMap
实时更新
readOnlyCacheMap
周期更新
客户端信息缓存
localRegionApps
upServerListZoneMap
缓存相关配置
服务端配置
客户端是从只读缓存还是从读写缓存获取配置信息
读写缓存更新只读缓存
默认30秒
清理未续约节点周期
默认60秒;间隔60秒会去清理不可用的节点信息
清理未续约节点超时时间
默认90秒;
客户端配置
服务续约周期
增量更新周期
负载均衡周期
2.客户端会缓存服务注册列表;
3.负载均衡器也会缓存服务列表
eureka高可用
eureka注册中心本身支持集群化
集群中的每一个节点都是可以堆外提供注册和服务发现功能
这点和zk的差别很大,zk在同一时间点只能是由一个主节点对外提供服务发现注册功能
集群中的每一个节点也是一个微服务,也可以做相互的注册
eureka集群中某一节点挂了,其他节点持续对外提供服务
eureka高可用集群,中注册中心都是对等的,每个注册中心都会把自己的数据同步给其他注册中心;其他注册中心在收到心跳信息的时候会判断是客户端注册信息还是注册中心注册信息,如果是客户端信息那么注册中心就把数据同步到其他注册中心,如果是注册中心的注册信息那么就不做任何操作;
自我保护策略
服务端
默认情况下,注册中心不会剔除已经挂掉的服务;认为挂掉的服务正在尝试连接注册中心
设置
eureka.server.enable-self-preservation=false
关闭该默认机制;确保注册中心可以剔除不可用实例
eureka.server.eviction-interval-timer-in-ms=5000
剔除失效服务的间隔时间
客户端
心跳时间
lease-renewal-interval-in-seconds
每间隔多久发送一次心跳时间,表示当前服务还活着
续约时间
lease-expiration-duration-in-seconds
超过这个时间没有向注册中发送心跳,那么表示我这个服务已经挂了,请把我剔除服务注册列表
子主题 6
eureka架构
核心功能
1:服务注册
2:服务发现
3:动态跟踪服务注册信息
zk和eurake区别
zk保证CP(一致性,容错性)
zk只有主节点才能提供服务,如果主节点挂了,就不能服务了,所以可用性较差,但是一致性和容错可以有保障
eruake是ap (可用性,容错性)
eurake集群每一个节点都可以对外提供服务,多台机器存在数据一致性问题。只能保证可用性和容错性
eurake 每一个节点都是平等都可以对外提供服务注册和发现,而zk只有集群中的主节点才能堆外提供服务注册和发现
nacos
配置中心
概述
系统启动需要的参数和依赖的各类外部信息;
配置中心自动刷新时限
1、配置中心server端承担起配置刷新的职责
2、提交配置出发post请求给server端的bus/refreah接口
3、server端接受到请求并发送给spring cloud bus总线
4、springcloud bus 接收到消息并通知其他连接到总线的客户端
5、其他客户端接收到通知,请求server端获取最新的配置
6、全部客户端均获取到最新的配置
配置中心如何保证数据安全
1、各个环境做到数据配置隔离
2、配置中心所有的配置信息都进行加密处理
具体实现
springCloud config
apollo
apollo由来
是携程架构部门研发的分布式配置中心
特性
1、统一管理不同环境,不同集群的配置
2、配置修改实时生效
热发布,修改配置,客户端可以在1s接收到最新的配置
3、配置版本管理
配置修改都有版本的概念,可以方便回滚到历史某个版本
4、支持灰度发布
支持发布后,配置只对部分客户端实例生效,等观察一段时间再对所有实例生效
5、发布操作审计
配置编辑,发布都有详细的管理机制以及操作日志,方便追踪问题
6、客户端配置信息监控
可以在见面上查看配置被哪些实例使用
7、提供了java和.net原生客户端
8、提供开发平台api
基础概念
配置管理维度
一级维度
应用层级维度
标识配置所属项目应用
二级维度
环境维度
区分一个应用下不同的环境
DEV
FAT
uat
pro
三级维度
集群维度
相同实力的不同分组
比如说,北京分组,上海分组,配置可能不一样
四级维度
namespace维度
类似同一个项目下针对比如说数据库,redis等等不同的配置文件
本地缓存
Apollo客户端会把从服务端获取到的配置在本地文件系统缓存一份,用于在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置,不影响应用正常运行
apollo设计图
配置更新基础示意图
大致工作流程
1、在管理平台上做配置的修改和发布
2、配置中心通知Apollp客户端有配置更新
3、apollpo客户端从配置中心拉去最新配置,更新本地配置并通知都应用
示意图
子主题
配置更新详细示意图
详细工作流程
1、客户端和服务端保持了一个长连接,能第一时间获得配置更新的推送
2、客户端还会定时从 Apollo 配置中心服务端拉取应用的最新配置
这是一个备用机制
客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回 304 - Not Modified
定时频率默认为每 5 分钟拉取一次,客户端也可以通过在运行时指定 apollo.refreshInterval来覆盖,单位为分钟
3、客户端从 Apollo 配置中心服务端获取到应用的最新配置后,会保存在内存中
4、客户端会把从服务端获取到的配置在本地文件系统缓存一份,在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
5、应用程序从 Apollo 客户端获取最新的配置、订阅配置更新通知
详细工作示意流程图
子主题
apollo整体设计
示意图
工作流程说明
1、Config Service 提供配置的读取、推送等功能,服务对象是各个微服务
2、Admin Service 提供配置的修改、发布等功能,服务对象是管理界面
3、集群部署
1、Config Service 和 Admin Service 都是多实例、无状态部署,所以需要将自己注册到 Eureka 中并保持心跳
4、服务注册与发现
1、在 Eureka 之上我们架了一层 Meta Server 用于封装Eureka的服务发现接口
2、客户端通过域名访问 Meta Server 获取Config Service服务列表(IP+Port),而后直接通过 IP+Port 访问服务,同时在 Client 侧会做 load balance 错误重试
3、Portal 通过域名访问 Meta Server 获取 Admin Service 服务列表(IP+Port),而后直接通过 IP+Port 访问服务,同时在 Portal 侧会做 load balance、错误重试
apollo相关问题
1、阿波罗如何保证配置实时更新
1、客户端和服务端的长连接保持长连接,能在第一时间获取配置更新的推送
2、客户端还会定时拉取服务端配置是否有更新
2、阿波罗如何保证可用性
1、configservice 集群部署,且都会注册到注册中心
最大程度上保证微服务和服务端配置获取通信畅通
2、admin service 集群部署
配置管理平台和服务端保持配置修改通信畅通
3、客户端会缓存配置信息到本机
当配置中心集群挂了,会从本地读取配置
4、admin sevcie 和conig service 集群工作相互不受对方影响
nacos
实现原理
服务降级熔断
相关概念
服务雪崩
demo解释
当大量的请求请求上层服务,上层服务所依赖的底层服务,在处理请求的时候,无法承受大量的请求,导致底层服务无法快速相应,导致上层服务请求堆积,导致上层服务无法快速处理请求,导致请求堆积,从而导致A服务不可用
解释
由于底层的服务某种原因,导致整个上层服务都不可用,有一种连锁反应
解决方式
服务熔断,服务降级,服务限流
服务限流
解释
单位时间内限制对服务器的访问量
作用
在高并发情况下,保护系统不被大量请求导致服务雪崩
服务熔断
解释
底层服务不可用的时候,上层服务直接不再调用这个底层服务,而直接返回一个结果;
作用
隔离上层和底层服务之间的级联影响,避免系统崩溃
服务降级
解释
服务压力激增的情况下,主动对一些服务不处理或者简单处理,来释放服务器压力,保证服务器核心功能正常运行
作用
保证整个系统核心功能呢个稳定性和可用性
服务熔断,服务降级区别
触发原因
服务熔断是调用链路上的某个服务不可以用引起的;
服务降级从整体负载考虑
目标
熔断是框架层次的处理
降级是业务层次的处理
实现方式
熔断主要是在客户端进行处理,书写兜底处理
降级需要在服务端进行兜底处理
服务降级是对整个系统资源的再次分配(区分核心服务,非核心服务)
服务熔断是服务降级的一种特殊方式,防止服务雪崩而采取的措施;
具体实现
sentinel
sentinel作用
流量控制
熔断降级
系统负载保护
Sentinel概念
资源定义
服务接口
接口中调用的其他服务
熔断降级
某个资源出现不稳定的时候,降低对该资源的调用进行限制并快速失败,避免影响到其他系统造成整个系统雪崩
规则
流量控制规则
熔断降级规则
系统自我保护规则
Sentinel工作机制
1.对资源显示定义,来标记资源
2.提供可修改规则接口
3.对资源适时统计,流量适时监控
熔断降级
熔断策略
平均响应时间
滑动窗口统计中,1秒内的平均响应时间超过了阈值,就会熔断
异常比例
滑动窗口的统计中当资源每秒的请求次数超过5个,且每秒的异常比例超过一定阈值;那么这个方法就会自动返回
异常比例范围 [0.0, 1.0],也就是0%-100%;
异常数
滑动窗口在1分钟的异常数目超过一定的阈值就会进行熔断。
熔断一般是因为某个服务故障了,降级一般是为了降低系统负载
熔断一般是框架就处理,粒度比较大;降级粒度比较小,可以正对某个具体的资源
降级策略
线程并发数
线程数量在某个资源上堆积了数量,那么新的请求就会被拒绝;等堆积的线程完成任务后,才开始接受新的请求;
资源相应时间
当依赖的资源出现相应时间过长,那么对该资源的访问都会直接被拒绝,等到一定的时间窗口之后再回复正常访问;
降级规则
resource
资源名,资源名是限流规则的作用对象
count
限流阈值
grade
降级模式,根据 RT 降级还是根据异常比例降级
timeWindow
降级的时间
熔断-降级区别
熔断就是直接不能访问该接口,该接口直接返回错误的执行方法;降级,一段时间之内不能再次访问这个接口,等高峰期过了才会再次访问该接口;
流量控制
流量控制角度
控制效果
直接限流
冷启动
排队
运行指标
QPS
系统负载
线程池
资源调用关系
概述
一个资源可能调用其他资源,形成一个调用链路,通过调用链路可以衍生出更多的流量控制手段
控制方式
根据调用方限流
根据调用链路入口限流:链路限流
关系资源流量控制
控制手段
1、线程数量控制
资源当前请求的线程个数,如果超过一定的阈值那么,对该资源的请求就会被立即拒绝;
保护业务线程不被耗尽,当某个资源超过一定阈值数量的线程排队的时候,对该资源的请求就会直接被拒绝,等到没有排队线程就会接受新的请求
(避免不断的创建线程,造成线程的堆积;hystrix是通过对特定资源创建一定大小的线程池,不管请求再多,
都只能在这个规定大小的线程池内活动)
2、直接拒绝
当QPS超过任意规则的阈值,新的请求就会被立即拒绝,拒绝方式抛出FlowException
3、均速器
通过漏斗算法严格控制请求通过的间隔时间
4、冷启动
系统长期处于低活跃度,短期流量猛增,直接把系统拉大高水位可能直接把系统冲垮;让流量缓慢的增加,给系统一个预热时间,避免系统被冲垮;
限流控制规则配置项
resource
限流规则作用对象
count
限流阈值
grade
限流阈值类型(QPS或者并发线程数)
limitApp
是否区分调用来源
strategy
调用关系限流策略
controlBehavior
流控控制效果(直接决绝,冷启动,均速排队)
限流规则统计项目
系统保护
系统保护规则
1.通过网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去
2.让系统的入口流量和系统的负载达到一个平衡,保证在系统能力范围之内处理最多请求;
hystrix
Hystrix使用
@HystrixCommand
commandKey,groupKey,thread key重点说明
commandKey
代表一类command,代表底层依赖的一个接口
groupKey
代表了某一个底层的依赖服务;这个服务可以有多个接口
逻辑上是组织起一堆的command key的调用,统计信息,成功次数,timeout次数,失败次数,可以看到某一些服务整体的访问情况
如果不配置
threadPoolKey
线程池配置
以上三者实战说明
同时配置了groupKey 和 threadPoolKey,具有相同的threadPoolKey的使用同一个线程池
如果只配置了groupKey ,那么具有相同的groupKey 的使用同一个线程池
总结
一般来说command group 是对应了一个底层服务,多个command key对应这个底层服务的多个接口,这多个接口按照 command group 共享一个线程池
如果想单独给某一个底层接口也就是某一个command key设置线程池,直接配置上threadpool key 就可以
fallbackMethod
回滚的方法名称
报错之后,执行的默认方法
commandProperties
可以配置执行的隔离策略是线程隔离,还是信号量隔离
参数设置
hystrix.command.default.execution.isolation.strategy 隔离策略,默认是Thread, 可选Thread|Semaphore
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令执行超时时间,默认1000ms
hystrix.command.default.execution.timeout.enabled 执行是否启用超时,默认启用true
hystrix.command.default.execution.isolation.thread.interruptOnTimeout 发生超时是是否中断,默认true
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。
emaphore应该占整个容器(tomcat)的线程池的一小部分。
threadPoolProperties
线程池相关的配置
参数设置
hystrix.threadpool.default.coreSize 并发执行的最大线程数,默认10
hystrix.threadpool.default.maxQueueSize BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默认-1。
hystrix.threadpool.default.queueSizeRejectionThreshold 即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝。因为maxQueueSize不能被动态修改,这个参数将允许我们动态设置该值。if maxQueueSize == -1,该字段将不起作用
hystrix.threadpool.default.keepAliveTimeMinutes 如果corePoolSize和maxPoolSize设成一样(默认实现)该设置无效。如果通过plugin(https://github.com/Netflix/Hystrix/wiki/Plugins)使用自定义实现,该设置才有用,默认1.
hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds 线程池统计指标的时间,默认10000
hystrix.threadpool.default.metrics.rollingStats.numBuckets 将rolling window划分为n个buckets,默认10
ignoreExceptions
忽略一些异常,这些异常不被统计到熔断中
observableExecutionMode
定义hystrix observable command的模式;
raiseHystrixExceptions
任何不可忽略的异常都包含在HystrixRuntimeException中;
defaultFallback
默认的回调函数,该函数的函数体不能有入参,
返回值类型与@HystrixCommand修饰的函数体的返回值一致。如果指定了fallbackMethod,则fallbackMethod优先级更高。
返回值类型与@HystrixCommand修饰的函数体的返回值一致。如果指定了fallbackMethod,则fallbackMethod优先级更高。
其他参数
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 如果并发数达到该设置值,请求会被拒绝和抛出异常并且fallback不会被调用。默认10
hystrix.command.default.fallback.enabled 当执行失败或者请求被拒绝,是否会尝试调用hystrixCommand.getFallback() 。默认true
Circuit Breaker相关的属性
hystrix.command.default.circuitBreaker.enabled 用来跟踪circuit的健康性,如果未达标则让request短路。默认true
hystrix.command.default.circuitBreaker.requestVolumeThreshold 一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10秒)收到19个请求,即使19个请求都失败,也不会触发circuit break。默认20
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内都会拒绝request,也就是5000毫秒后才会关闭circuit。默认5000
hystrix.command.default.circuitBreaker.errorThresholdPercentage错误比率阀值,如果错误率>=该值,circuit会被打开,并短路所有请求触发fallback。默认50
hystrix.command.default.circuitBreaker.forceOpen 强制打开熔断器,如果打开这个开关,那么拒绝所有request,默认false
hystrix.command.default.circuitBreaker.forceClosed 强制关闭熔断器 如果这个开关打开,circuit将一直关闭且忽略circuitBreaker.errorThresholdPercentage
Metrics相关参数
hystrix.command.default.metrics.rollingStats.timeInMilliseconds 设置统计的时间窗口值的,毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒,则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数的统计信息。默认10000
hystrix.command.default.metrics.rollingStats.numBuckets 设置一个rolling window被划分的数量,若numBuckets=10,rolling window=10000,那么一个bucket的时间即1秒。必须符合rolling window % numberBuckets == 0。默认10
hystrix.command.default.metrics.rollingPercentile.enabled 执行时是否enable指标的计算和跟踪,默认true
hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds 设置rolling percentile window的时间,默认60000
hystrix.command.default.metrics.rollingPercentile.numBuckets 设置rolling percentile window的numberBuckets。逻辑同上。默认6
hystrix.command.default.metrics.rollingPercentile.bucketSize 如果bucket size=100,window=10s,若这10s里有500次执行,只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序的开销。默认100
hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds 记录health 快照(用来统计成功和错误绿)的间隔,默认500ms
Request Context 相关参数
hystrix.command.default.requestCache.enabled 默认true,需要重载getCacheKey(),返回null时不缓存
hystrix.command.default.requestLog.enabled 记录日志到HystrixRequestLog,默认true
Collapser Properties 相关参数
hystrix.collapser.default.maxRequestsInBatch 单次批处理的最大请求数,达到该数量触发批处理,默认Integer.MAX_VALUE
hystrix.collapser.default.timerDelayInMilliseconds 触发批处理的延迟,也可以为创建批处理的时间+该值,默认10
hystrix.collapser.default.requestCache.enabled 是否对HystrixCollapser.execute() and HystrixCollapser.queue()的cache,默认true
hystrix.command.default.fallback.enabled 当执行失败或者请求被拒绝,是否会尝试调用hystrixCommand.getFallback() 。默认true
Circuit Breaker相关的属性
hystrix.command.default.circuitBreaker.enabled 用来跟踪circuit的健康性,如果未达标则让request短路。默认true
hystrix.command.default.circuitBreaker.requestVolumeThreshold 一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10秒)收到19个请求,即使19个请求都失败,也不会触发circuit break。默认20
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内都会拒绝request,也就是5000毫秒后才会关闭circuit。默认5000
hystrix.command.default.circuitBreaker.errorThresholdPercentage错误比率阀值,如果错误率>=该值,circuit会被打开,并短路所有请求触发fallback。默认50
hystrix.command.default.circuitBreaker.forceOpen 强制打开熔断器,如果打开这个开关,那么拒绝所有request,默认false
hystrix.command.default.circuitBreaker.forceClosed 强制关闭熔断器 如果这个开关打开,circuit将一直关闭且忽略circuitBreaker.errorThresholdPercentage
Metrics相关参数
hystrix.command.default.metrics.rollingStats.timeInMilliseconds 设置统计的时间窗口值的,毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒,则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数的统计信息。默认10000
hystrix.command.default.metrics.rollingStats.numBuckets 设置一个rolling window被划分的数量,若numBuckets=10,rolling window=10000,那么一个bucket的时间即1秒。必须符合rolling window % numberBuckets == 0。默认10
hystrix.command.default.metrics.rollingPercentile.enabled 执行时是否enable指标的计算和跟踪,默认true
hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds 设置rolling percentile window的时间,默认60000
hystrix.command.default.metrics.rollingPercentile.numBuckets 设置rolling percentile window的numberBuckets。逻辑同上。默认6
hystrix.command.default.metrics.rollingPercentile.bucketSize 如果bucket size=100,window=10s,若这10s里有500次执行,只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序的开销。默认100
hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds 记录health 快照(用来统计成功和错误绿)的间隔,默认500ms
Request Context 相关参数
hystrix.command.default.requestCache.enabled 默认true,需要重载getCacheKey(),返回null时不缓存
hystrix.command.default.requestLog.enabled 记录日志到HystrixRequestLog,默认true
Collapser Properties 相关参数
hystrix.collapser.default.maxRequestsInBatch 单次批处理的最大请求数,达到该数量触发批处理,默认Integer.MAX_VALUE
hystrix.collapser.default.timerDelayInMilliseconds 触发批处理的延迟,也可以为创建批处理的时间+该值,默认10
hystrix.collapser.default.requestCache.enabled 是否对HystrixCollapser.execute() and HystrixCollapser.queue()的cache,默认true
hystrix设计
hystrix的熔断设计
熔断请求判断机制算法:使用无锁循环队列计数,每个熔断器默认维护10个bucket,每1秒一个bucket,每个blucket记录请求的成功、失败、超时、拒绝的状态,默认错误超过50%且10秒内超过20个请求进行中断拦截。
熔断恢复:对于被熔断的请求,每隔5s允许部分请求通过,若请求都是健康的(RT<250ms)则对请求健康恢复
熔断报警:对于熔断的请求打日志,异常请求超过某些设定则报警
hystrix的隔离设计
线程池隔离
对每一个设置了线程池的资源(理解成一个接口或者一类接口,或者针对某一个底层服务的所有接口),都会开启一个专属的线程池来处理这些资源对应的请求,线程池来处理这些资源对应的请求,堆积的请求也会进入线程池对应的阻塞队列
项目在启动的时候会有一些资源消耗,但是会在大批量的请求到来的时候,可以游刃有余的处理
信号量隔离
信号量来记录当前有多少线程在运行,请求进来的时候先判断是否会超过最大的信号量(最大线程数量),如果超过那么就会丢弃最新的请求,不超过就会让信号量+1,严格控制当前正在执行的线程数量
无法应对大量的请求,大量的请求过来,就是直接放弃执行了,
对比
线程池因为有阻塞队列的原因,所以在一定程度上可以保存一分部请求,慢慢处理;但是信号量就不行,需要严格控制当前执行的线程个数
hystrix的超时机制设置
等待超时
设置任务入列最大时间
判断阻塞队列的头部任务入列时间是否大于超时时间。大于则放弃任务
运行超时
直接可以使用线程池的get方法
hystrix工作流程
熔断流程
1、当出现调用错误的时候,开起一个时间窗口(默认十秒)
2、在这个时间串口内,统计调用次数是否达到最小次数
如果没有达到,则重置统计信息,回到第一步
如果没有达到,即使请求全部失败,也回到第一步
3、如果次数达到了,统计失败占总请求的百分比阈值是否达到
如果没有达到,则重置统计数据,回到第一步
4、如果达到则跳闸
5、开起一个默认时间为5秒的活动窗口,每间隔5秒钟,让一个请求通过去访问已经跳闸的服务;
如果失败,回到第三步继续计算错误百分比
6、如果调用成功,重置断路器
简易工作流程图
流程图
sentinel和Hystrix对比
隔离策略
Sentinel 基于信号量隔离,Hystrix 基于线程池/信号量隔离
熔断降级策略
两者都可以基于异常比例来降级,但是sentinel可以基于相应时间,异常比例,异常数量熔断降级
实时指标实现
都是基于滑动窗口
规则配置
都可以支持多种数据量
扩展性
sentinel扩展性强Hystrix区别
限流
sentinel基于QPS支持基于调用关系的限流;Hystrix对限流支持比较有限
流量整形
Sentinel支持慢启动、匀速器模式
不支持
系统负载保护
Sentinel 支持
注解
sentinel,Hystrix都支持注解开发
相同点
实时统计实现
都是通过滑动窗口来做指标统计
动态规则配置
sentinel Hystrix都支持多数据源
开源和维护
都是开源,hystrix 的网飞团队已经不维护了;sentinel还在接着维护
是否可以动态修改配置
Hystrix只能监控查看,不能动态修改规则
sentinel控制台可以查询规则,动态修改配置规则且无需重启,监控情况
网关
网关概念
网关工作流程
具体实现
zuul
gateway
限流
负载均衡
熔断
Spring Cloud Gateway 底层使用了高性能的通信框架Netty
负载均衡
负载均衡概念
具体实现
Ribbon
特点
无需部署,直接嵌入客户端做为负载均衡器使用
spring cloud LoadBalance
ribbon
使用
1.调用端引入ribbon的包
注意事项
1.负载均衡默认算法是轮询
2.通过把负载均衡对象定义成bean修改负载均衡算法
负载均衡算法
1、轮询策略
按照一定的顺序依次调用服务实例
2、权重策略
根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低
实现
刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大
3、随机策略
从服务提供者的列表中随机选择一个服务实例
4、最小连接数策略
遍历服务提供者列表,选取连接数最小的⼀个服务实例;如果有相同的最小连接数,那么会调用轮询策略进行选取
5、重试策略
可以看做是轮询策略的一种
按照轮询策略来获取服务
如果服务实力失效或者服务实例为null,指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null
6、可用性敏感策略
可用敏感性策略
先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例
7、区域敏感策略
根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似
工作流程简述
1、ribbon拦截所有的远程调用
2、解析调用路径中的host,获取服务名称
3、根据服务名称获取服务实例列表
1、先从本地获取服务实例列表
2、本地不存在,那么就从注册中心拉去服务列表,缓存本地
4、根据负载均衡策略获取某一个具体的服务ip和端口
3、再通过http请求框架请求服务获取结果
工作流程示意图
示意图
负载均衡实现方式
客户端实现
ribbon
服务端实现
nginx
lvs
F5 硬件实现
客户端服务端负载均衡示意图
子主题
服务端实现负载均衡问题
1、需要服务端有强的流量控制权
2、无法满足不同调用方使用不同的负载均衡策略需求
客户端这边就可以实现这个,避免这个问题;
调用方式
两者的区别联系
区别
feign是ribbon调用方式的封装
联系
子主题
负载均衡
算法种类
轮询
随机
过滤故障,连接数超过一定阈值机器,剩下进行轮询
过滤故障机器,轮询
过滤掉故障机器,选择并发最小访问
根据平均响应时间加权选择服务
根据机器性能,可以用性选择服务
请求重试机制
具体实现
feign
使用
1.调用端引入feign包
2.调用端定义服务接口
3.用定义的服务接口调用
注意事项
1.负载均衡默认算法是轮询
配置文件中定义修改后的负载均衡算法
rest template
open feign
feign理论
简单介绍
http请求调用的轻量级框架
作用
1、简化springcloud 对远程接口开发以及调用
2、整合spring ribbon,hystrix实现接口负载均衡和短路
Feign的HTTP客户端
目前支持
HttpURLConnection
JDK自带
未实现连接池;需要自己手动实现连接池
HttpClient
封装http请求头,参数,内容,响应体,方便开发
自带Http连接池
OKHttp
OKHttp拥有共享Socket,减少对服务器请求次数
自带http连接池
默认使用
HttpURLConnection
feign和openfeign区别
OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类做负载均衡和服务调用
feign工作流程
feign接口调用流程
详细流程图
流程图
详细流程
1、本地接口A调用远程接口B
2、通过接口的动态代理实现了接口B
3、接口B读取解析springMvc相关注解,生成http请求
1、解析@FeignClient注解获取服务名称,获取请求路径
2、解析请求路径还有参数信息
3、序列化操作,非必要 ?
4、接口B将请求相关信息交给Ribbon对应的负载均衡器
1、获取去注册中心获取这个服务对应的服务列表
2、根据负载均衡算法从注册列表中选择一个
5、负载均衡器生成最终的请求地址
6、交给http组件发送请求
feign接口启动注册流程
详细流程
1、启动类启动的时候扫描启动类上的注解@EnableFeignClients
2、这个注解中@EnableFeignClients中还有一个@Import注解,这个注解引入了一个FeignClientsRegistrar类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 引入FeignClientsRegistrar 来扫描@FeignClient注解下的类
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
...
}
@Target(ElementType.TYPE)
@Documented
// 引入FeignClientsRegistrar 来扫描@FeignClient注解下的类
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
...
}
3、这个类实现了ImportBeanDefinitionRegistrar接口,也就是可以想spring容器中手动注入bean对象
**
* spring boot 启动时会自动调用 ImportBeanDefinitionRegistrar 入口方法
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata
, BeanDefinitionRegistry registry) {
// 读取 @EnableFeignClients 注解中信息
registerDefaultConfiguration(metadata, registry);
// 扫描所有@FeignClient注解的类
registerFeignClients(metadata, registry);
}
* spring boot 启动时会自动调用 ImportBeanDefinitionRegistrar 入口方法
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata
, BeanDefinitionRegistry registry) {
// 读取 @EnableFeignClients 注解中信息
registerDefaultConfiguration(metadata, registry);
// 扫描所有@FeignClient注解的类
registerFeignClients(metadata, registry);
}
4、FeignClientsRegistrar类中有一个方法registerBeanDefinitions ,做bean相关的操作
5、registerBeanDefinitions方法做两件事情:
5、registerBeanDefinitions方法,读取@EnableFeignClients,@FeignClient注解的类;将这些信息注册到一个 BeanDefinitionRegistry 里面去
feign的一些默认配置将通过这里注册的信息中取获取
5、registerBeanDefinitions方法,扫描所有@FeignClient注解的类
1、先扫描相关包路径
2、通过@FeiginClient注解信息向BeanDefinitionRegistry里面注册bean
registerFeignClients流程图
6、registerBeanDefinitions方法构建bean
1、实际构建的FeignClientFactoryBean
详细流程图
流程图
监控中心
springBoot admin
链路追踪
sleuth
skywalking
pinpoint
微服务架构特点
一组小的服务
独立的进程
轻量级通信
基于业务能力
独立部署
无集中式管理
微服务利弊
利
1、服务部署灵活性提高
单体架构是每做一次修改,就必须要将单体服务进行全量的重新部署;
微服务是,修改某一块业务只需要部署这某一块业务对应的微服务就可以,发布快捷轻便;
2、技术更加灵活
单体引用对同一类型的技术栈,往往只选择一种技术栈,如果要做多个技术栈选择往往会相互影响;
而微服务可以是针对每一个拆分出来的服务做针对性的技术栈选型,而可以相互不影响;
3、应用性能得到提高
大型单体应用zhong
4、简化开发
单体服务
单体服务,就是所有开发都在开发一个单体服务,团队协作,分支合并要求很高;还需要团队的每个成员对整个系统都需要有一定的了解;
团队协作性要求高
每次做修改,都需要沟通团队中的相关其他开发;通知影响范围;
开发上手难度大
需要对整个系统都有一定了解
单体服务包含了各个业务代码,开发,改动起来影响范围更大
微服务
降低对团队协作性要求
一个开发组,只需要开发属于他那一块的微服务,不需要很高的团队协作性;
降低开发上手难度
快速开发只需要熟悉他那块的微服务就可以,不需要对整个系统都很了解;
5、业务边界清晰
单体服务
涉及到复合业务的时候,往往都是在其他的业务中书写相关的业务代码;业务边界,责任不够清晰;
微服务
对同一类型业务会,都已封装成对应的接口,由单独的团队或者开发来负责;业务边界清晰,责任分明
5、代码复用性
微服务还可以提高代码复用性;
弊端
服务调用复杂性
网络问题
容错问题
负载问题
高并发问题
事物一致性问题
运维复杂性提高
单体服务只需要维护一个服务;
微服务需要维护很多个服务
测试复杂性提高
微服务调用链路变长了
微服务实用性
引入微服务的时间点
什么样的组织结构更适合微服务架构
中台战略
分层
IAAS
paas层
应用
核心业务层
渠道接入
业务前台
业务中台
业务后台
服务发现模式
传统lb模式
依赖外接的负载均衡器(F5,NGINX)
主机内lb模式
在每主机上部署一个负载均衡器
进程内lb模式
负载均衡移动到应用服务内
性能好,
zuul网关
前置路由过滤器
路由过滤器
后置路由过滤器
可动态插拔
网关上设计防爬虫
路由发现体现
服务分层
服务网关
服务之间是怎么相互发现
外面流量怎么通过网关访问微服务
服务
基础服务
相互之间同步路由表
聚合服务
服务调用方式
RPC
REST
服务治理
配置集成
文档
统一异常处理
代码生成
自动生产服务端客户端
序列化
REST/RPC
安全访问控制
限流熔断
调用链埋点
Metrics
日志
负载路由
服务注册发现
后台服务集成,DB,MQ,CACHE
微服务监控系统分层监控架构
日志监控
健康检查
调用链监控
告警系统
metrics监控
子主题 6
ELK -- log监控
nagios
调用链监控选型
CAT
ZIPKIN
PINPOINT
字节码增强的方式做
子主题 4
容器
解决的问题
环境一致性问题
镜像部署问题
容器集群调度平台
容器发布
发布体系
k8s集群
微服务业务拆分维度和原则
维度(拆分方式)
1、功能维度
按照系统业务功能进行划分,例如对于电商系统,按功能维度咱们能够拆分为商品中心,订单中心,用户中心,购物车,结算等功能模块
2、状态维度
功能模块如果能够按照不一样的业务状态再进行划分,就好比电商中的优惠券,可以分成建立优惠券,领券,使用优惠券,优惠券失效
相同的业务从开始到结束的不同状态的区分
3、读写维度
按照读写压力拆分
商品中心读压力大,那么就将商品读写分成两个服务,以提高系统安全性,可用性
4、纵深维度
按照业务纵深维度,最底层的业务模块和上层的业务模块拆分开
比如说:订单需要用到商品,用户,等信息;但是用户模块,商品模块是不会用到订单信息;此时商品模块和用户模块就是相当于订单模块就是下层模块
5、AOP维度
根据访问特征,按照aop拆分
商品详情页能够分为CDN、页面渲染模块等。而CDN就是一个AOP
原则(方法)
1、当前微服务只能访问当前微服务对应的数据库;想访问其他服务的数据源,必须通过远程接口访问
2、当前服务不能包含其他服务业务接口,且当前服务接口需要对外暴露提供接口服务;
3、服务之间不能有业务交叉
微服务应用4个架构原则
AKF拆分原则
概述
也就是服务拆分的一个总的指向原则
三个维度示意图
示意图
X轴维度
水平复制
将服务多部署几台,做负载均衡
Y轴维度
基于不同的业务模块拆分
Z轴维度
数据分区维度,将数据拆分成多个份,或者多个集群
2、前后端分离
3、无状态服务
不能在服务本地内存中缓存数据
4、无状态通讯原则
降低服务之间通讯协议沟通成本
微服务概论
微服务产生
微服务是有一个martin fowler大师提出来的,是一种架构风格,通过将大型单体应用划分成较小服务单元,从而降低整个系统复杂度
springCloud和springCloudalibaba都有哪些组件
spring cloud netflix
配置中心
Spring Cloud Config
注册中心
Eureka
提供了一个服务注册中心、服务发现的客户端,还有一个方便的查看所有注册的服务的界面。 所有的服务使用Eureka的服务发现客户端来将自己注册到Eureka的服务器上
远程调用
Feign
服务客户端,服务之间如果需要相互访问,可以使用RestTemplate,也可以使用Feign客户端访问
负载均衡
Ribbon
熔断降级
Hystrix
Hystrix Dashboard
提供可视化界面,监控各个服务上服务调用所花费的时间
监控和断路器
通过快速的失败,降低服务的级联报错
网关
Zuul
客户端的请求都需要先通过网关后,再请求后台服务,通过配置的路由来判断请求需要访问哪个问题,再从注册中心获取注册服务来转发请求
监控
Turbine
聚合监控平台
可以查看所有的服务实力,都聚集到统一的一个地方查看;
springCloud alibaba
注册中心-配置中心
nacos
负载均衡
Ribbon
服务调用
Feign
熔断降级
Sentinel
网关
Gateway
调用链路监控
Sleuth
分布式事物处理
Seata
springCloud
提供了构建微服务系统所需要的一组通用开发模式;以及一系列快速实现这些开发模式的工具
通常说的springCloud是指的是springCloud netflix;和springcloud cloud alibaba都是springcloud这一系列开发模式的具体实现
springcloudh和dubbo区别
dubbo开始只是一个rpc调用框架,核心是解决服务调用问题,侧重也是服务调用,没有springcloud功能全面;springcloud 是一个大而且全的框架,提供了一些列的解决方案;dub
dubbo数据传输直接使用传输层的二进制传输,对象直接转成二进制数据
spring cloud 是使用应用层http协议进行传输,数据最终会转成二进制,中件会有性能消耗
soa,分布式,微服务之间的关系和区别
SOA
SOA历史
上世纪90年代出现的一个面向服务的体系结构
SOA缺点
ESB(服务总线)是单体结构,很容易出现单点故障
SOA不能解决部署速度
SOA很容易出现功能直接相互依赖
soa和微服务
soa是一种架构设计风格,是为了将单体服务拆分成一些具有特定业务目标的较小模块
微服务也是soa架构思想的一种形式
两者区别
服务通讯方式
SOA依赖消息传递协议在服务之间进行通信
数据存储
SOA类似单体服务,通常都是共享使用一个数据库
微服务,每一个微服务都有自己业务的服务
规模和范围
虽然都是较小服务组成,但是SOA服务的粒度比较粗,微服务粒度比较小
通信
微服务之间是通过与语言无关的http协议通信
SOA通过SOA上层的ESB,通过消息传递协议进行服务之间通信
松耦合和高内聚
微服务的耦合性更低
SOA耦合性更高
SOA,和微服务架构图
左边是SOA,右边是微服务
分布式和微服务
两者区别
部署方式
分布式架构,将打的系统分为多个业务模块,部署到不同机器上,通过接口进行数据交互;
微服务可以部署在同一台机器上也可以分散部署到不同的机器上
两者联系
微服务架构也算是分布式架构的一种
三者之间的联系
SOA架构,微服务架构都可以看做是分布式架构思想的一种表现
spring生态
spring-生态
springframework
springframework 是spring 里面的一个基础开源框架
spring data
提供对各种数据源操作的封装
Spring Cloud Data Flow
Spring Cloud Data Flow是用于构建数据集成和实时数据处理管道的工具包
Spring for GraphQL
spring 对图数据结构的处理
spring-cloud生态
生态中各个开源项目简单说明
Azure
spring cloud 为项目部署提供的一套环境
项目发布的云平台
可以为项目提供生命周期管理,监控诊断,配置管理,服务发现,CI/CD集成,蓝绿部署
Spring Cloud Alibaba
提供一整套分布式项目的各种技术解决的框架
Spring Cloud netflux
提供一整套分布式项目的各种技术解决的框架
Spring Cloud for Amazon Web Services
可简化与托管Amazon Web Services的集成
spring cloud bus
用于实现微服务之间的通信
spring cloud bus整合 java的事件处理机制和消息中间件消息的发送和接受,主要由发送端、接收端和事件组成
Spring Boot Cloud CLI
该工具为Spring Boot CLI提供了一组命令行增强功能,有助于进一步抽象和简化Spring Cloud部署
Spring Cloud Cloud Foundry
Spring Cloud for Cloudfoundry可以轻松地在Cloud Foundry(平台即服务)中运行Spring Cloud应用程序。 Cloud Foundry具有“服务”的概念,即“绑定”到应用程序的中间件,实质上为其提供包含凭据的环境变量(例如,用于服务的位置和用户名)
Spring Cloud - Cloud Foundry Service Broker
Spring Cloud Cluster
Spring Cloud Cluster提供了一组用于在分布式系统中构建“集群”功能的功能。 例如领导选举,集群状态的一致存储,全局锁和一次性令牌。
Spring Cloud Commons
Spring Cloud Commons模块是为了对微服务中的服务注册与发现、负载均衡、熔断器等功能提供一个抽象层代码,可以自定义相关基础组件的实现
Spring Cloud Config
spring的配置中心
统一管理微服务配置的一个组件,具有集中管理、不同环境不同配置、运行期间动态调整配置参数、自动刷新等功能。
参考文档
https://blog.csdn.net/u011066470/article/details/106741430
Spring Cloud Connectors
简化了云平台(如Cloud Foundry和Heroku)中连接服务和获取操作环境感知的过程,尤其适用于Spring应用程序
它是为可扩展性而设计的:您可以使用提供的云连接器之一或为您的云平台编写一个,并且您可以使用内置支持常用服务(关系数据库,MongoDB,Redis,RabbitMQ)或扩展Spring 云连接器可与您自己的服务配合使用。
SpringCloud Consul
Consul是一套开源的分布式服务发现和配置管理系统
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案
Spring Cloud Contract
为通过CDC(Customer Driven Contracts)开发基于JVM的应用提供了支持。它为TDD(测试驱动开发)提供了一种新的测试方式 - 基于接口
传统自测
示意图
Spring Cloud Contract
示意图
Spring Cloud Function
通过函数促进业务逻辑的实现
将业务逻辑的开发生命周期与任何特定运行时目标分离,以便相同的代码可以作为Web端点,流处理器或任务运行
支持无服务器提供商之间的统一编程模型,以及独立运行(本地或PaaS)的能力
在无服务器提供商上启用Spring Boot功能(自动配置,依赖注入,指标)
就像Spring一直在推广基于普通java对象(POJO)的编程模型一样,Spring Cloud Function也基于普通的函数来推广编程模型。我们指的是java.util.function包中定义的核心接口:Function,Consumer和Supplier。
Spring Cloud Gateway
网关
Spring Cloud GCP
Spring 数据是用于在众多存储技术中存储和检索 POJO 的抽象。 Spring Cloud GCP 在数据存储模式下为Google Cloud Firestore添加了 Spring Data 支持。
与谷歌的数据处理的整合
Spring Cloud Open Service Broker
Spring Cloud Open Service Broker 是一个框架,用于构建实现Open Service Broker API 的Spring Boot应用程序。
Spring Cloud Pipelines
该项目试图解决以下问题:
创建公共部署管道
传播良好的测试和部署实践
加快将功能部署到生产所需的时间
创建公共部署管道
传播良好的测试和部署实践
加快将功能部署到生产所需的时间
Schema Registry
对不同的项目统一数据编码格式
Spring Cloud Security
权限
Spring Cloud Skipper
Skipper是一种工具,允许您在多个云平台上发现应用程序并管理其生命周期。
Skipper是一个工具,允许您发现Spring Boot应用程序并管理其在多个云平台上的生命周期。您可以单独使用Skipper或将其与Continuous Integration管道集成,以帮助实现应用程序的持续部署。
Spring Cloud Sleuth
链路追踪
Spring Cloud Sleuth是对Zipkin的一个封装
Spring Cloud Stream
微服务应用构建消息驱动能力的框架
简化了消息队列的开发上手难度
目前只支持 RabbitMQ ,Kafka
Spring Cloud Task
SpringBoot应用程序提供创建短运行定时任务
Task中,我们可以灵活地动态运行任何任务,按需分配资源并在任务完成后检索结果。Tasks是Spring Cloud Data Flow中的一个基础项目,允许用户将几乎任何SpringBoot应用程序作为一个短期任务执行。
Spring Cloud Task App Starters
Spring Cloud Task Application Starters是独立的可执行应用程序,可用于按需用例,例如数据库迁移,机器学习和计划操作
特性
独立运行作为Spring Boot应用程序
编排为短暂的数据微服务
将数据微服务应用程序用作maven或docker工件
通过命令行,环境变量或YAML文件覆盖配置参数
提供基础架构以单独测试应用程序
从此版本的Spring Initializr下载为初学者
独立运行作为Spring Boot应用程序
编排为短暂的数据微服务
将数据微服务应用程序用作maven或docker工件
通过命令行,环境变量或YAML文件覆盖配置参数
提供基础架构以单独测试应用程序
从此版本的Spring Initializr下载为初学者
Spring Cloud Vault
辅助spting boot程序保护一些敏感配置信息
SpringCloud——Zookeeper
注册中心
Spring Cloud App Broker
Spring Cloud App Broker是一个用于构建Spring Boot应用程序的框架,该应用程序实现Open Service Broker API以将应用程序部署到平台。
Open Service Broker API项目允许开发人员为云本地平台(如Cloud Foundry,Kubernetes和OpenShift)中运行的应用程序提供服务。 Spring Cloud App Broker提供了一个基于Spring Boot的框架,使您能够快速创建服务代理,在配置托管服务时将应用程序和服务部署到平台。
Spring Cloud Circuit Breaker
熔断
Spring Cloud Kubernetes
Spring Cloud Kubernetes提供使用Kubernetes原生服务的Spring Cloud公共接口实现。此代码仓库中提供的项目是促进在Kubernetes中运行的Spring Cloud和Spring Boot应用程序的集成
Kubernetes就是帮助大家更快的搭建使用Kubernetes原生服务的微服务项目,并与Spring Cloud集成
Spring Cloud Kubernetes意味着我们想要将服务部署到Kubernetes集群,Spring Cloud Kubernetes为我们实现了Spring Cloud的一些接口,让我们可以快速搭建Spring Cloud微服务项目框架,并能使用Kubernetes云原生服务。
Kubernetes提供服务注册和发现、以及配置中心的实现,我们完全没有必要再自己部署一套注册中心、配置中心,因此Spring Cloud Kubernetes为我们提供使用这些原生服务的接口实现。除注册中心和配置中心之外,如果我们还想使用istio,Spring Cloud Kubernetes也提供了支持,这些无非就是解释文章开头的那句话“Spring Cloud Kubernetes提供使用Kubernetes原生服务的Spring Cloud公共接口实现”
Kubernetes提供服务注册和发现、以及配置中心的实现,我们完全没有必要再自己部署一套注册中心、配置中心,因此Spring Cloud Kubernetes为我们提供使用这些原生服务的接口实现。除注册中心和配置中心之外,如果我们还想使用istio,Spring Cloud Kubernetes也提供了支持,这些无非就是解释文章开头的那句话“Spring Cloud Kubernetes提供使用Kubernetes原生服务的Spring Cloud公共接口实现”
参考文档
https://www.likecs.com/show-203352078.html#sc=586
Spring Cloud OpenFeign
基于http请求的接口调用
数据结构与算法
数据结构
数据结构概述
物理结构
物理结构就是数据结构在计算机中的存储形式;
物理结构分为两种形式
连续存储
就是数据结构中的元素都存放在地址连续的存储单元中,数据之间的逻辑关系和存储关系是一致的;也就是存储关系就是逻辑关系;其实就是以数组的形式存储
非连续存储
就是数据结构中的元素存放在任意的存储单元之中,元素之间可以是连续存储,也可以不连续的存储;其实就是以链表的形式进行存储
逻辑结构
数据元素之间的相互关系
具体分为
集合(背包)
就是数据中的元素除了同属于一个集合之外,他们没有其他关系
线性结构
数据中元素之间存在一对一的关系;(线性结构的数据:队列,栈,数组,链表)
树形结构
就是数据元素之间存在一对多的关系;(二叉树)
图结构
图形结构数据是多对多的关系
每当设计出一种新的数据结构的时候,就应该考虑该数据结构在做查询,增加删除时候的效率问题
数据结构详解
二叉树
二叉树概念
二叉树是属性结构中一种比较特殊的数据结构,就是一个元素的下一个元素最多只有两个元素。
满二叉树
就是所有的根节点都在同一层,且非根节点都有两个子节点
完全二叉树
就是二叉树中的元素的编号与其在二叉树中的位置完全一致,就是完全二叉树
平衡二叉树
就是数据在储存的只是严格的按照数据比对进行左右两个分支的平衡存储
算法
基础概念
时间复杂度
数据结构中,算法执行的时间与该数据结构中数据元素个数之间存在着一种函数关系。当数据结构中元素的增长速度一定,这个函数增长的最慢的函数称之为最有算法
时间复杂度分类
o(1)常数阶
时间复杂度基本上与数据结构中的个数无关了
o(n)线性阶
单层循环,都是线性阶
o(n*n)平方阶
内外层循环次数都一样那么就是,平方阶
对数阶
o(n*m)平方阶
内外层循环的次数不一样,那么就是n*m阶
算法
排序算法
子主题
搜索算法
中间件
zookeeper
zk的作用
数据发布/订阅
负载均衡
负载均衡过程
zk是采用cs架构(有客户端,服务端)
服务器启动的时候把当前机器提供的服务信息注册到zk(服务端)上的临时节点上;然后zk的客户端从zk上获取最新的服务节点信息;本地再使用相关的负载均衡的算法, 随机分配服务器
负载均衡算法
轮询:将请求顺序的发送给每个服务器
比例:给每个服务器分配一个加权比例
优先权:给每个服务器分组,每组定义优先权,优先权高的持续接受请求处理请求;
优先权低的只有等到他前面优先权高的服务群组挂了,才能接受请求处理请求
最少连接数:最少连接的会获取到请求
最快相应时间:最小相应时间的接受请求
hash算法:通过客户端的ip地址端口请求hash算法
基于策略的负债均衡
基于数据包的内容分发
命名服务
k会形成服务注册列表,这个列表记录了服务域名和ip的绑定关系
Master 选举
集群管理
分布式协调/通知
实现分布式协调与通知功能,不同的客户端都对ZooKeeper上同一个数据节点进行Watcher注册,监听数据节点的变化(包括数据节点本身及其子节点),如果数据节点发生变化,那么所有订阅的客户端都能够接收到相应的 Watcher 通知,并做出相应的处理
分布式队列
跨进程,跨主机,跨网络的数据共享
zk是通过内部的顺序节点来实现分布式队列
分布式锁
主要思想
就是利用zk不能重复创建临时节点的特性,能创建的线程相当于获取到了锁,
不能创建的线程相当于是阻塞在创建临时节点的那一步,等待去创建临时节点(等待获取锁)
服务端创建节点同步方法
创建节点说明
1、服务端代码里面创建节点的方法是同步的
2、分布式环境下,创建节点的请求都会发送到 zookeeper 的master节点上
3、然后再同步到其他zk节点
特别说明:所有的写请求都只会请求一台服务器,这个方法还是同步方法,所以zk也是能做分布式锁的
创建节点方法
zk分布式的问题
“惊群”就是在一个节点删除的时候,大量对这个节点的删除动作有订阅Watcher的线程会进行回调
这对Zk集群是十分不利的。所以需要避免这种现象的发生
持久化
持久化本质
持久化本质就是内存中的数据已二进制的方式存储在磁盘上
过程需要经历数据序列化,这个序列化是zk自己的jute序列化;
存储的时候需要今次那个序列化操作,取出的时候需要进过反序列化操作
存储的时候需要今次那个序列化操作,取出的时候需要进过反序列化操作
持久化方式
日志文件
每次的修改操作都会记录在日志;
日志的固定格式:log.zxid,zxid 表示起始的事物id;
快照数据
快照数据是记录zk内部在某一时刻的全部的数据(这些数据就是znode节点,
和seesion)内容,并指定出入磁盘之中
持久化作用
系统宕机,或者重启导致数据丢失,做了持久化就不怕数据丢失
健康检查
zk健康检查指标
不健康的指标
请求处理的平均延时是否超过300ms
连接数使用率是否超过80%
服务状态不正常
存在未处理的告警信息
watcher
watcher过程
1.客户端向服务端注册Watcher
2.服务端事件发生触发Watcher;
3.客户端回调Watch得到触发事件情况(客户端一直监听)
watcher特点
只会触发一次
通知需要封装成事件发送给服务端
时间通知是异步从服务端发送到客户端
概述
zk的观察者模式是什么
zk允许用户在指定的节点上注册一些watcher (事件监听器);当数据节点发生变化的时候
zk服务会这个变化通知到所有的观察者
zk的很多功能都是基于watcher来设计的,比如说
比如说节点的数据更新了,事件监听器会把变化这个事件通知到对应的客户端,
然后客户端就会主动拉取变化之后的数据
示意图
zk的数据结构
子主题
znode数据结构
znode节点分类
临时节点
永久节点
节点的类型在创建的时候就确定
节点区别
临时节点依赖于session会话,
一次会话结束,这个临时节点生命周期结束;永久节点不依赖于session会话就,只有在
客户端显示执行删除操作的时候才会结束;
区别就是zk是否会给节点名称进行顺序标记
持久化目录节点
持久化顺序编号目录节点
临时目录节点
临时顺序编号目录节点
节点存储信息
1.stat:节点的版本,权限信息;
2.data: 与该节点关联的数据;
3.children该节点的子节点;
选举
初始化选举
初始化的时候选举出master节点;
中期选举
原来的master节点挂了,需要选出新的master节点,这个是主要根据
主要每一个机器的日志中的事物id数值的大小,数值越大,表明该节点的数据完整性最好;
健康检查
健康检查指标
haProxy
作用
haproxy是一款功能强大、灵活好用反向代理软件,提供了高可用、负载均衡、后端服务器代理的功能,它在7层负载均衡方面的功能很强大
特点
使用场景
消息中间件
RabbitMQ
通信协议
基于AMQP协议
基础概念
消费者消息获取方式
(默认的方式)轮询poll的方式拉取消息
生产者推送消息给消费者
RabbitMq书写语言:erlang
基础概念
VirtualHost
:从主机中虚拟出来的一个虚拟主机;每个虚拟主机都是一个相对独立的rabbitmq服务器
一个虚拟主机里面可以有多个不同的交换机和不同的队列
exchange
概念:1.数据从生产者到消费者之间的数据转换层;2.隔离了一个虚拟主机下面不同数据之间的推送;3.生产者消费者隔离;
交换机种类
Headers Exchange默认交换机
不绑定route-key;交换机和queue名称一样
Fanout Exchange广播交换机
把消息发送到绑定了该交换机的所有队列上
不需要指定routeing-key
Direct Exchange直连交换机
数据会被发送到指定路由的queue上去
Topic Exchange主题交换机
消息会被转发到所有满足route-key的队列,以及bingkey模糊匹配到的队列
Queue
消息队列,实际存储消息数据
参数配置
name
交换机名称
Durability
是否持久化,true持久化
值集True flase
Auto-delete
所有的消费者完成消费后自动删除
ture,所有的消费者消费完成之后,自动删除
值集True flase
Arguments(拓展参数)
Message TTL
消息生存时间
时间单位毫秒
消息在被抛弃前可以存活多久
Auto expire
队列生存时间
时间单位是毫秒
队列在指定时间内没有被使用,就会自动被删除
Max length
队列容纳的消息的最大条数
超过设定条数就会默认放弃队列头部数据
Max length byte
队列可容纳最大字节数量
超过设定的长度的数据,那么就会默认放弃头部消息
Broker:
消息中间件的服务节点。
Connection
生产端消费端都需要和服务端建立Connection连接,也就是tcp连接
Channel
消息通道,在客户端的每个Connection连接里,可建立多个channel.
Channel是轻量级的Connection,减少了tcp频繁连接断开的开销
Channel实际上就是Tcp的连接复用
mandatory标志
表示作用
标记当消息发送出去,找不到路由的处理方式
处理方式
true:消息返回给服务端,服务端可以做后续的处理
false:消息返回服务端,服务端直接删除
死信队列
死信队列定义
未被正常消费的消息存放的队列;
死信队列数据来源
1.拒绝消息
拒绝一条消息
拒绝多条消息
2.超时消息
超过消息本身设置的存活时间还没有被消息
超过消息发送时候队列设置的存活时间还没有被消息
3.溢出消息
超过队列的最大长度
超过了队列的最大容量
RocketMQ 中的消息重试默认超过16次之后,就会把这个消息发送到私信队列中
死信队列使用场景
延时操作
消息优先级
RocketMQ 没有特意支持消息的优先级。消息的优先级会比较消耗性能;
可以通过单独配置 优先级高的队列,和优先级低的队列;这样就是优先级高的队列的数据会优先投递出去;
队列工作模式
简单模式
生产者消费者一对一
work模式
生产者消费一对多;每个消费者获取的消息都是唯一
订阅模式
生产者消费者一对多,同样的消息会被订阅的消费者都消费到
路由模式
生产者指定发给一个消费者
主题模式
生产者指定发送给某一类消费者
消息种类
1.普通消息;
2.顺序消息;
3.事物消息;
工作流程
可设置参数
spring.rabbitmq.addresses= # 以逗号分隔的客户端应连接的地址列表
spring.rabbitmq.cache.channel.checkout-timeout= # 如果已达到缓存大小,则等待获取通道的持续时间
spring.rabbitmq.cache.channel.size= # 要在缓存中保留的通道数
spring.rabbitmq.cache.connection.mode=channel # 连接工厂缓存模式
spring.rabbitmq.cache.connection.size= # 缓存的连接数
spring.rabbitmq.connection-timeout= # 连接超时。将其设置为 0 以永远等待
spring.rabbitmq.dynamic=true # 是否创建 AmqpAdmin bean
spring.rabbitmq.host=localhost # RabbitMQ 主机
spring.rabbitmq.listener.direct.acknowledge-mode= # 确认容器的模式
spring.rabbitmq.listener.direct.auto-startup=true # 是否在启动时自动启动容器
spring.rabbitmq.listener.direct.consumers-per-queue= # 每个队列的消费者数量
spring.rabbitmq.listener.direct.default-requeue-rejected= # 默认情况下,拒绝交付是否重新排队
spring.rabbitmq.listener.direct.idle-event-interval= # 应该多久发布一次空闲容器事件
spring.rabbitmq.listener.direct.missing-queues-fatal=false # 如果容器声明的队列在代理上不可用,则是否失败
spring.rabbitmq.listener.direct.prefetch= # 每个消费者可能未完成的最大未确认消息数
spring.rabbitmq.listener.direct.retry.enabled=false # 是否启用发布重试
spring.rabbitmq.listener.direct.retry.initial-interval=1000ms # 第一次和第二次尝试传递消息之间的持续时间
spring.rabbitmq.listener.direct.retry.max-attempts=3 # 传递消息的最大尝试次数
spring.rabbitmq.listener.direct.retry.max-interval=10000ms # 最长尝试次数
spring.rabbitmq.listener.direct.retry.multiplier=1 # 乘数应用于先前的重试间隔
spring.rabbitmq.listener.direct.retry.stateless=true # 重试是无国籍还是有状态
spring.rabbitmq.listener.simple.acknowledge-mode= # 确认容器的模式
spring.rabbitmq.listener.simple.auto-startup=true # 是否在启动时自动启动容器
spring.rabbitmq.listener.simple.concurrency= # 侦听器调用者线程的最小数量
spring.rabbitmq.listener.simple.default-requeue-rejected= # 默认情况下,拒绝交付是否重新排队
spring.rabbitmq.listener.simple.idle-event-interval= # 应该多久发布一次空闲容器事件
spring.rabbitmq.listener.simple.max-concurrency= # 侦听器调用者线程的最大数量。
spring.rabbitmq.listener.simple.missing-queues-fatal=true # 如果容器声明的队列在代理上不可用,则是否失败和/或如果在运行时删除一个或多个队列,是否停止容器
spring.rabbitmq.listener.simple.prefetch= # 每个消费者可能未完成的未确认消息的最大数量
spring.rabbitmq.listener.simple.retry.initial-interval=1000ms # 第一次和第二次尝试传递消息之间的持续时间
spring.rabbitmq.listener.simple.retry.max-attempts=3 # 传递消息的最大尝试次数
spring.rabbitmq.listener.simple.retry.max-interval=10000ms # 尝试之间的最长持续时间
spring.rabbitmq.listener.simple.retry.multiplier=1 # 乘数应用于上一个重试间隔
spring.rabbitmq.listener.simple.retry.stateless=true # 重试是无状态还是有状态
spring.rabbitmq.listener.simple.transaction-size= # 确认模式为AUTO时要在acks之间处理的消息数。如果大于预取,则预取将增加到此值
spring.rabbitmq.listener.type=simple # Listener 容器类型
spring.rabbitmq.password=guest # 登录以对代理进行身份验证
spring.rabbitmq.port=5672 # RabbitMQ 端口
spring.rabbitmq.publisher-confirms=false # 是否启用发布者确认
spring.rabbitmq.publisher-returns=false # 是否启用发布者返回
spring.rabbitmq.requested-heartbeat= # 请求心跳超时;零,没有。如果未指定持续时间后缀,则将使用秒
spring.rabbitmq.ssl.algorithm= # SSL算法使用。默认情况下,由Rabbit客户端库配置
spring.rabbitmq.ssl.enabled=false # 是否启用SSL支持
spring.rabbitmq.ssl.key-store= # 保存SSL证书的密钥库的路径
spring.rabbitmq.ssl.key-store-password= # 用于访问密钥库的密码
spring.rabbitmq.ssl.key-store-type=PKCS12 # 密钥库类型
spring.rabbitmq.ssl.trust-store= # 持有SSL证书的信任存储
spring.rabbitmq.ssl.trust-store-password= # 用于访问信任库的密码
spring.rabbitmq.ssl.trust-store-type=JKS # 信托商店类型
spring.rabbitmq.ssl.validate-server-certificate=true # 是否启用服务器端证书验证
spring.rabbitmq.ssl.verify-hostname=true # 是否启用主机名验证
spring.rabbitmq.template.default-receive-queue= # 从明确指定none时接收消息的默认队列的名称
spring.rabbitmq.template.exchange= # 用于发送操作的默认交换的名称
spring.rabbitmq.template.mandatory= # 是否启用强制消息
spring.rabbitmq.template.receive-timeout= # receive()操作的超时时间
spring.rabbitmq.template.reply-timeout= # sendAndReceive()操作的超时时间
spring.rabbitmq.template.retry.enabled=false # 是否启用发布重试
spring.rabbitmq.template.retry.initial-interval=1000ms # 第一次和第二次尝试传递消息之间的持续时间
spring.rabbitmq.template.retry.max-attempts=3 # 传递消息的最大尝试次数
spring.rabbitmq.template.retry.max-interval=10000ms # 尝试之间的最长持续时间
spring.rabbitmq.template.retry.multiplier=1 # 乘数应用于先前的重试间隔
spring.rabbitmq.template.routing-key= # 用于发送操作的默认路由密钥的值
spring.rabbitmq.username=guest # 登录用户以对代理进行身份验证
spring.rabbitmq.virtual-host= # 连接到代理时使用的虚拟主机
spring.rabbitmq.cache.channel.checkout-timeout= # 如果已达到缓存大小,则等待获取通道的持续时间
spring.rabbitmq.cache.channel.size= # 要在缓存中保留的通道数
spring.rabbitmq.cache.connection.mode=channel # 连接工厂缓存模式
spring.rabbitmq.cache.connection.size= # 缓存的连接数
spring.rabbitmq.connection-timeout= # 连接超时。将其设置为 0 以永远等待
spring.rabbitmq.dynamic=true # 是否创建 AmqpAdmin bean
spring.rabbitmq.host=localhost # RabbitMQ 主机
spring.rabbitmq.listener.direct.acknowledge-mode= # 确认容器的模式
spring.rabbitmq.listener.direct.auto-startup=true # 是否在启动时自动启动容器
spring.rabbitmq.listener.direct.consumers-per-queue= # 每个队列的消费者数量
spring.rabbitmq.listener.direct.default-requeue-rejected= # 默认情况下,拒绝交付是否重新排队
spring.rabbitmq.listener.direct.idle-event-interval= # 应该多久发布一次空闲容器事件
spring.rabbitmq.listener.direct.missing-queues-fatal=false # 如果容器声明的队列在代理上不可用,则是否失败
spring.rabbitmq.listener.direct.prefetch= # 每个消费者可能未完成的最大未确认消息数
spring.rabbitmq.listener.direct.retry.enabled=false # 是否启用发布重试
spring.rabbitmq.listener.direct.retry.initial-interval=1000ms # 第一次和第二次尝试传递消息之间的持续时间
spring.rabbitmq.listener.direct.retry.max-attempts=3 # 传递消息的最大尝试次数
spring.rabbitmq.listener.direct.retry.max-interval=10000ms # 最长尝试次数
spring.rabbitmq.listener.direct.retry.multiplier=1 # 乘数应用于先前的重试间隔
spring.rabbitmq.listener.direct.retry.stateless=true # 重试是无国籍还是有状态
spring.rabbitmq.listener.simple.acknowledge-mode= # 确认容器的模式
spring.rabbitmq.listener.simple.auto-startup=true # 是否在启动时自动启动容器
spring.rabbitmq.listener.simple.concurrency= # 侦听器调用者线程的最小数量
spring.rabbitmq.listener.simple.default-requeue-rejected= # 默认情况下,拒绝交付是否重新排队
spring.rabbitmq.listener.simple.idle-event-interval= # 应该多久发布一次空闲容器事件
spring.rabbitmq.listener.simple.max-concurrency= # 侦听器调用者线程的最大数量。
spring.rabbitmq.listener.simple.missing-queues-fatal=true # 如果容器声明的队列在代理上不可用,则是否失败和/或如果在运行时删除一个或多个队列,是否停止容器
spring.rabbitmq.listener.simple.prefetch= # 每个消费者可能未完成的未确认消息的最大数量
spring.rabbitmq.listener.simple.retry.initial-interval=1000ms # 第一次和第二次尝试传递消息之间的持续时间
spring.rabbitmq.listener.simple.retry.max-attempts=3 # 传递消息的最大尝试次数
spring.rabbitmq.listener.simple.retry.max-interval=10000ms # 尝试之间的最长持续时间
spring.rabbitmq.listener.simple.retry.multiplier=1 # 乘数应用于上一个重试间隔
spring.rabbitmq.listener.simple.retry.stateless=true # 重试是无状态还是有状态
spring.rabbitmq.listener.simple.transaction-size= # 确认模式为AUTO时要在acks之间处理的消息数。如果大于预取,则预取将增加到此值
spring.rabbitmq.listener.type=simple # Listener 容器类型
spring.rabbitmq.password=guest # 登录以对代理进行身份验证
spring.rabbitmq.port=5672 # RabbitMQ 端口
spring.rabbitmq.publisher-confirms=false # 是否启用发布者确认
spring.rabbitmq.publisher-returns=false # 是否启用发布者返回
spring.rabbitmq.requested-heartbeat= # 请求心跳超时;零,没有。如果未指定持续时间后缀,则将使用秒
spring.rabbitmq.ssl.algorithm= # SSL算法使用。默认情况下,由Rabbit客户端库配置
spring.rabbitmq.ssl.enabled=false # 是否启用SSL支持
spring.rabbitmq.ssl.key-store= # 保存SSL证书的密钥库的路径
spring.rabbitmq.ssl.key-store-password= # 用于访问密钥库的密码
spring.rabbitmq.ssl.key-store-type=PKCS12 # 密钥库类型
spring.rabbitmq.ssl.trust-store= # 持有SSL证书的信任存储
spring.rabbitmq.ssl.trust-store-password= # 用于访问信任库的密码
spring.rabbitmq.ssl.trust-store-type=JKS # 信托商店类型
spring.rabbitmq.ssl.validate-server-certificate=true # 是否启用服务器端证书验证
spring.rabbitmq.ssl.verify-hostname=true # 是否启用主机名验证
spring.rabbitmq.template.default-receive-queue= # 从明确指定none时接收消息的默认队列的名称
spring.rabbitmq.template.exchange= # 用于发送操作的默认交换的名称
spring.rabbitmq.template.mandatory= # 是否启用强制消息
spring.rabbitmq.template.receive-timeout= # receive()操作的超时时间
spring.rabbitmq.template.reply-timeout= # sendAndReceive()操作的超时时间
spring.rabbitmq.template.retry.enabled=false # 是否启用发布重试
spring.rabbitmq.template.retry.initial-interval=1000ms # 第一次和第二次尝试传递消息之间的持续时间
spring.rabbitmq.template.retry.max-attempts=3 # 传递消息的最大尝试次数
spring.rabbitmq.template.retry.max-interval=10000ms # 尝试之间的最长持续时间
spring.rabbitmq.template.retry.multiplier=1 # 乘数应用于先前的重试间隔
spring.rabbitmq.template.routing-key= # 用于发送操作的默认路由密钥的值
spring.rabbitmq.username=guest # 登录用户以对代理进行身份验证
spring.rabbitmq.virtual-host= # 连接到代理时使用的虚拟主机
消息队列设计机制
消息确认机制
发送端-服务端
return消息机制
发送端把消息发送到服务器,结果找不到对应的交换机,路由队列;return消息机制就是应对这种情况
发送消息时候给Channel参数manDetory设置为true;消费就会返回到发送端可以做后续处理;如果为false,服务端就会直接把该条消息删除
生产端Confirm消息确认机制
生产者投递消息后,如果 RabbitMQ节点收到消息,则会给我们生产者一个应答
生产者进行接收应答,用来确定这条消息是否正常的发送到 Broker
注意事项
我们采用的是异步 confirm 模式:提供一个回调方法,服务端 confirm 了一条或者多条消息后 Client 端会回调这个方法。除此之外还有单条同步 confirm 模式、批量同步 confirm 模式
此种方式现实场景中很少使用
事物消息机制
实现原理:AMQP协议
事务确实能够解决producer与RabbitMq节点之间消息确认的问题,只有消息成功被节点接受,事务提交才能成功,否则我们便可以在捕获异常进行事务回滚操作同时进行消息重发,但是使用事务机制的话会降低RabbitMQ的性能.
RabbitMQ中与事务机制有关的方法有三个:txSelect(), txCommit()以及txRollback(), txSelect用于将当前channel设置成transaction模式,txCommit用于提交事务,txRollback用于回滚事务
服务端
confirm消息机制
发送端把消息发送个服务端,服务端接收到消息并且把消息持持久化到磁盘就会给发送端一个异步的confirm应答
confirm种类
单条应答
批量应答
服务端-消费端
ack消息机制
ack种类模式
不确认
消费端发生异常或者无响应,都会通知服务端消费成功,会丢失数据
自动确认
自动确认,如果发生异常,就会给服务端发送不确认信息;那么消费就会回到消息队列尾部
这种方式效率较高,当时如果在发送过程中,如果网络中断或者连接断开,将会导致消息丢失
手动确认
消费者成功消费完消息之后,会显式发回一个应答(ack信号),RabbitMQ只有成功接收到这个应答消息,才将消息从内存或磁盘中移除消息
这种方式效率较低点,但是能保证绝大部分的消息不会丢失,当然肯定还有一些小概率会发生消息丢失的情况。
示意图
消息重试机制
消息重试目的
消费者异常的情况下,能够让生产者重新发送该消息;保证消息的最大程度被正常消费
消息重试配置说明:以springBoot整合RabbitMQ说明
enabled
开启重试机制
max-attempts
最大重试次数
initial-interval
重试间隔时间
max-interval
最大间隔时间(不能超过这个时间间隔)
multiplier
间隔时间乘法数(重试的时间间隔在上一次的倍数)
注意事项
所以设置重试机制的时候,第一种情况重试,第二种就不重试,做好日志相关
错误的日志记录
错误的日志记录
重试机制原理
消息被消费的时候会被监听,当抛出异常的时候,就会执行补偿机制;
实现的原理还是建立在消费端的ack机制之上
底层使用Aop拦截,如果程序(消费者)没有抛出异常,自动提交事务
如果Aop使用异常通知拦截获取到异常后,自动实现补偿机制,消息缓存在RabbitMQ服务器端
如果Aop使用异常通知拦截获取到异常后,自动实现补偿机制,消息缓存在RabbitMQ服务器端
消息拒绝机制
消费端手动拒绝
单条拒绝
消息在信道内的唯一标记,拒绝的就是当前消息
消息拒绝之后的处理形式是删除,还是重新放入队列
多条拒绝
消息在信道内的唯一标记,拒绝的就是当前消息
消费者消息处理失败;当前不能处理该消息
消息拒绝机制作用:给服务端发送拒绝消息,让服务端把消息丢弃或者重新放入队列中
消息重新入队机制
消息路由不成功的消息,可以配置相关的死信队列;消息可以发送到死信队列
批量消息发送机制
此机制需要开发做拓展
消息持久化
概述
并不能完全解决消息丢失问题
持久化会降低rabbtimq性能
持久化过程
持久化概述
所有队列的消息都会写入到磁盘的中间中去;当写入的数据大小超过了文件大小,那么就会关闭此文件,再新建一个文件存储;
持久化时间节点
消息本身推送到消费端的时候在服务端需要存入磁盘
内存资源少,需要把队列中的数据存入磁盘
消息刷盘条件
消息并不是来一条消息就往磁盘上存储一条,而是先把消息都放入到一个缓冲池;等一定的条件才会缓存的消息写入磁盘
1.缓冲池缓冲的数据大小超过缓冲池本身
2.超过固定的刷盘时间25ms,不管缓冲池是否满了,都会刷盘
3.消息写入缓冲区后,没有其他后续请求写入,那么也会刷盘
读取持久化数据过程
根据消息ID,找到消息所在文件,根据消息在文件中的偏移量,找到该消息;
持久化消息删除
删除说明
收到消费者的ack消息的时候,并不是马上去删除消息,而是先给消息做一个删除的标记
删除过程
后台进程检车到垃圾数据比例超过50%,并且文件不少于3个,的时候就会触发持久化数据的垃圾回收;找到符合要求的左右两个文件,先整理左文件中的有效数据,然后再把有文件中有消息数据复制到左文件;再把又文件删除;
删除条件
1.所有文件中垃圾数据达到50%的比例;
2.存储的文件必须至少有三个;
持久化的对象
交换机
把交换机的属性持久化;在宕机或者重启之后服务器可以自动的去创建交换机,避免手动或者跑程序创建
设置durable=true
队列
把队列的属性持久化,在宕机或者重启之后可以自动的去创建队列,避免手动创建
设置durable=true
消息
消息的持久化是建立在队列的持久化之上,如果队列没有持久化,那么消息也不能持久化
设置 deliveryMode =2 ; deliveryMode =1 是不进行持久化
刷盘策略
同步刷盘
同步刷盘:消息追加到内存中,就立马刷到文件中存储;
类似强一致的,保证消息存储到文件中的同步策略
类似强一致的,保证消息存储到文件中的同步策略
异步刷盘
异步刷盘:消息追加到内存中,并不是马上刷到文件中,而是在后台任务中
进行异步操作;
提交到内存中就算存储成功,在后台异步进行刷盘的异步策略
RabbitMQ 默认采用异步刷盘的策略;
负载均衡
负载均衡方式
客户端
客户端内部主要是采用负载均衡算法
服务端
服务端主要是采用代理服务器
具体实现
客户端
根据服务器的配置参数,在客户端自己用代码做一套负载均衡算法
具体方式
轮询:将请求轮流到发送到后端的机器,不关系节点的实际连接数和负载能力
加权轮询:对轮询的优化,考虑每个节点的性能,配置高的机器分配较高的权重,配置低的机器分配较低的权重,并将请求按照权重分配到后端节点
随机法:通过随机算法,在众多节点中随机挑选一个进行请求。随着客户端调用服务端的次数增多,其实际效果越接近轮询
加权随机法:对随机的优化,根据机器性能分配权重,按照权重访问后端节点
源地址哈希法:根据客户端的IP地址,通过hash函数获取一个数值,用这个数值对后端节点数进行取模,这样在后端节点数保持不变的情况下,同一个客户端访问的 后端节点也是同一个
最小连接数:根据后端节点的连接情况,动态选举一个连接积压最小的节点进行访问,尽可能的提高节点的利用率。
服务端
通过中间件方式
haproxy的时候它会将这个请求进行具体的转发到m1或者m2上进行分工,比如安装简单的算法轮询模式,将任务进行均摊,这样资源就会被合理的利用了,对于Java客户端直接可以配置proxy的地址了,而haproxy可以通过心跳的感知哪些服务器是可以发送消息的,比如遇到m2的机器宕机了,它就会自动的将我们的服务退出,来使用其他的节点也进行提供服务
消息轨迹
消息轨迹是指一条消息从生产者发送到消息队列 RocketMQ 版服务端,再到消费者消费处理,整个过程中的各个相关节点的时间、状态等数据汇聚而成的完整链路信息
作用
该轨迹可作为生产环境中排查问题强有力的数据支持。本文介绍消息轨迹的使用场景、查询步骤以及查询结果的参数说明。
消息轨迹的默认保存时间只有3天。
消息追踪Firehose
RabbitMq队列问题+解决方案
消息延迟发送
RabbitMq本身并没有延迟队列;
解决方案
设置消息的存活时间
消费在队列中存活时间;当时间超过了消息就会被抛弃;设置死信交换机,被抛弃的消息就会落入到死信交换机;
核心点
1.不设置消费者,就可以让消息一直堆积,直到超过存活时间
具体解步骤
1.创建死信交换机
2.创建死信路由
3.新建消费者队列绑定死信路由
特别说明
1.死信交换机就是普通交换机
2.死信交换机被动接受其他交换机或者无法消费的消息
3.创建生产的交换机的时候就需要设置对应的死信交换机
消息丢失
消息丢失类型
生产者发送消息-服务端
丢失原因
1.由于网络原因导致数据丢包
2.交换机的路由没有被队列绑定,消息直接丢失
解决方案
针对1:
1.事物消息机制
发送端开启一个事物,再推送消息,如果投递失败;进行事物回滚,然后重新发送消息;如果服务端收到消息,发送端就提交事务。
缺点:事物消息造成发送端阻塞,发送端只有等到服务端回应之后,才会发送下一条数据;生产者的消息吞吐量大大降低;
2.消息确认机制
发送端把消息发送个服务端,服务端接收到消息并且把消息持持久化到磁盘就会给发送端一个异步的confirm应答
确认方式
1.串行确认
发送一条确认一条;服务器返回flase,会重新发送
缺点:效率比较低
2.批量确认
发送端每发送一批,才会确认
缺点:重新发送消息的时候需要把同一批消息再次发送
3.异步确认
服务端接受到了一条或者多条之后,会异步回调发送端的异步确认方法;
发送端发送完消息,可以接着发送其他消息,不会阻塞;
整体流程
任何一种确认方式,服务端接受到消息之后不是立马给发送端确认;而是需要等待批量数据持久化之后再发送确认消息;
在发送消息之前把消息用排序的Map集合保存起来;如果消息发送失败,那么就会从map集合中读取消息再次发送
针对2:
1.设置mandatory 设置true
交换机找不到相应的队列就会把消息返回被生产者
2.alternate-exchange设置备用交换机
交换机找不到消息,消息会发给备用的交换机
服务端丢失
丢失原因
丢失原因:客户端在处理消息的时候突然机器挂了,导致消息丢失了;
解决方案
服务端设置交换机,队列,数据的持久化;服务器宕机后,重启会读取磁盘上的持久化的数据;
问题:由于消息的持久化是一批的持久化,可能宕机了,这一批数据还持久化到磁盘
消息的持久化
1、服务端收到生产者发送过来的消息,会做消息的持久化
2、当服务宕机后, 会从磁盘当中读取相应的消息,最大程度上保证消息不在服务节点上丢失
消费端丢失
丢失原因
1.消费者在处理消息的时候出现异常了,那么这条消息就是没有被正常的消费;如果不采取措施,那么这个消息就会丢失
解决方案
ack机制
ack机制概述
消息只有正常消费后,反馈给服务端;服务端才会从队列里面把该条消息删除
ack机制三种模式
不确认
不会发送ack确认消息
自动确认
服务端发送完消息就自动认为该消息被成功消费
缺点:由于网络原因,造成数据从服务端发送到消费者消息丢失
手动确认
消费者消费成功之后,显示的给服务端ack信号;服务端只有收到该信号才会把数据从队列里面删除
设置手动ack,尽可能减少消费端的数据丢失问题;正常就是发送ack,异常就记录日志,然后发送nack
ack机制弊端
内存泄露
如果消费者异常没法发ack消息,服务端会认为这些数据都是没有被正常消费;就会堆积在队列当中,造成内存没法回收,内存泄露;
内存泄露解决方案
1.设置手动应答,如果异常,捕获异常记录日志,给服务端发送正常消费;
2.设置重试次数(默认是3次,三次不消费成功就会放入到默认的死信队列)
ack机制默认打开,而且是自动确认
消息堆积
消息堆积的本质
消费者的消费速度低于生产者生产的速度
堆积的实际原因
生产者原因
生产者突然发送大量信息
消费者原因
消费者消费失败
消费者出现性能瓶颈
消费者直接挂掉
消息堆积后果
队列溢出,新消息无法进入队列
消息无法被消费
阻塞时间超过消息存活时间
等待消费时间超过业务时间
消息堆积解决方案
优化消费者消费参数
设置多个线程同时处理消费消息
默认是单线程消费
设置一次从服务端拉取多条消息
默认是每次拉取一条消息
增加消费者数量
取消消费端ack确认机制
新增生产这队列,把消息推送另外的机器上
排查性能瓶颈,针对性改造
顺序消费
顺序错乱场景
1.生产者消费者一对多
2.生产者消费者一对一,消费者多线程消费
有前后逻辑的顺序的消息,被不同的消费者,或者是同一个消费者中不同的线程消费;都会造成消费顺序错乱的问题;
解决方案
针对1
生产者拆分成多个,让生产者和消费者一对一生产消费(消费者内部可以开多线程消费)
场景演示
生产者设置不同的队列,每一个消费者绑定不同的队列消费;将消息发送到同一个队列中;
把前后有逻辑关联的数据合并成一个,发送到消费方
针对2
把前后有逻辑关联的数据合并成一个,发送到消费方
场景演示
消息重复消费
消息重复消费原因
1.消费端异常没有给服务端发送消息成功消费的标记;
2.服务端没有接收到消费端发送的消费成功的标记;
只要是服务端没有接收到消费成功的标记,服务端都会再次给消费端发送消息;
解决方案
1.在消费端做幂等性判断
1.全局消息id做幂等性判断
2.全局业务id做幂等性判断
2.消费端代码做限制,无论如何都会发送消费确认消息
RabbitMQ集群
集群模式:
1.主备模式
特点:
1.一主一备;也可以是一主多备
2.主节点提供读写,从节点备份主节点数据
3.主节点挂了,从节点就会变成主节点;原来的从节点回复之后,就会变成备用节点
使用场景
1.并发和数据量不高的情况下;
搭建过程
1.需要使用haproxy作为中间件
2.远程模式
概述:数据进行复制,跨地域让两个MQ集群复制和通信;如果当前集群MQ服务超过设定的阈值,那么消息就会被转移到远程的MQ上做分担处理;
说明:需要使用到shovel插件,让跨地域的集群通信
3.镜像模式
概述:集群模式,一般2-3个节点实现数据同,主节点收到发送过来的数据,然后同步到其他节点上。
需要搭配haProxy做高可用负载均衡器
4.多活模式
概述:多中心模式,多套数据中心部署相同的MQ集群;一个集群中通过负载均衡器使得只有一个节点接受消息
各个中心需要配置插件 federation,可以使一个集群节点与另外一个集群节点做通信
消息复制
复制策略,是在集群环境下,主阶段从节点之间消息复制的策略,最大程度上是保证集群的可用性
同步复制
主节点,从节点都写成功后返回成功状态,好处是主节点宕机了,从节点上还有全部的备份
数据,容易恢复。缺点同步复制增大了延迟,降低了吞吐量;
异步复制
主节点只要写成功了,就会返回成功状态,好处降低了延迟,提高吞吐量,
缺点,主节点写入了,但是数据还没有写入从节点,那么数据就会有丢失
kafka
基础概念
topic
区分不同类别信息别称
broker
kafka服务器或者服务集群
副本
TODO
每个主题在创建时会要求制定它的副本数(默认1)
partition(分区)
特点
分区也就是让kafka相同的topic在不同机器,也就是同一个消息可以在不同的kafka节点上;这样就天然的让kafka变成队列集群
概述
同一个topic会有不同的分区,分区可在不同的机器
同一个topic可以有一个或者多个分区
所以一个节点上面可以有来自多个topic对应的分区
分区工作机制
每一个分区都是一个有序队列,分区中的消息都会被分配上一个有序的id(偏移量)
分区策略
message
生产者向某个topic发送的消息
offest偏移量
消息在日志文件中存储的位置
Segment
日志分段
Consumer
消费者
Consumer Group
消费者组
kafka消息核心api
生产者api
消费这api
stream-api
connectior-api
admin-api
管理台对应的api
ISR(InSyncRepli)、OSR(OutSyncRepli)、AR(AllRepli)
ISR
速率和leader相差低于10秒的follower的集合
kafka中与leader副本保持一定同步程度的副本(包括leader)组成ISR
OSR
速率和leader相差大于10秒的follower
AR
全部分区的follower
HW、LEO
HW:高水位,指消费者只能拉取到这个offset之前的数据
LEO:标识当前日志文件中下一条待写入的消息的offset
kafka参数设置
1.服务器配置
1.节点自身属性设置
broker.id
broker在集群中的标识
默认值-1
listeners
监听的服务地址(多个用,隔开)
无默认值
2.连接zk配置
zookeeper.connect
连接的zookeeper地址(多个地址用,隔开)
zookeeper.connection.timeout.ms
连接zookeeper超时时间(毫秒)
无默认超时时间
zookeeper.session.timeout.ms
连接ZK会话超时时间
zookeeper.sync.time.ms
zk的从机落后zk主机的最长时间
zookeeper.max.in.flight.requests
消费者有多少个未确认的消息,才会导致阻塞
3.日志配置
log.dirs
日志存放目录(有多个目录分布时使用,隔开)
无默认值
log.dir
日志存放目录(当log.dirs为null时)
默认值/tmp/kafka-logs
log.flush.interval.messages
将消息刷新到磁盘之前,日志分区上累计的消息数量
默认值:9223372036854775807
log.flush.interval.ms
刷盘前在内存中最长存在时间
log.retention.bytes
日志文件的最大容量
默认值-1,也就是可以无穷大
日志保存时间
log.retention.hours
日志文件保存的最长时间
默认是1周时间
log.retention.minutes
日志保存的最长分钟
默认为null
log.retention.ms
日志保存的最长分钟
默认为null
日志分区
log.roll.hours
新分区产生时间,以小时为单位
默认一周
log.roll.ms
子主题 1
子主题 2
log.segment.bytes
分区最大容量
默认1g
log.segment.delete.delay.ms
分区等待删除时间
默认60000ms
消息配置
message.max.bytes
拉取的批量消息的最大内存大小
默认值:0.9M
子主题 2
主题相关配置
auto.create.topics.enable
第一次发动消息时,自动创建topic。
默认值:true;
delete.topic.enable
是否可以删除topic
默认值:true
如果为Flase,那么管理工具将不能删除主题
auto.leader.rebalance.enable
rebalance配置
auto.leader.rebalance.enable
leader.imbalance.check.interval.seconds
分区重平衡检查的频率
leader.imbalance.per.broker.percentage
触发重平衡比例
默认值100%
线程配置
background.threads
后台处理线程个数
默认值10;
num.io.threads
处理请求线程数量
默认值:8
num.network.threads
处理网络请求网络相应线程数量
默认值3
num.recovery.threads.per.data.dir
日志恢复和日志关闭时刷新的线程数
默认值1
num.replica.alter.log.dirs.threads
日志之间移动副本线程数
无默认值
num.replica.fetchers
主节点数据复制到副本的线程数
偏移量
offset.metadata.max.bytes
与偏移量提交管道的元数据最大大小
offsets.commit.timeout.ms
偏移量超时时间
offsets.topic.num.partitions
偏移量提交主题分区的数量
offsets.topic.replication.factor
子主题 1
offsets.topic.segment.bytes
日志索引文件大小
默认值100M
子主题 6
unclean.leader.election.enable
leader挂了,是否会选举其他副本作为leader
默认值;false
压缩
compression.type
按照给定的压缩方式压缩数据
值集:“gzip”、“snappy”、“lz4”、“zstd”
事物
transaction.max.timeout.ms
事务执行最长时间,超时则抛出异常
900000ms
2.生产者配置
1.连接配置
bootstrap.servers
服务器节点配置
2.消息相关配置
buffer.memory
消息缓冲区大小
默认值:33554432 =32M
生产者最大可以用缓存;生产者可以用来缓冲等待发送到服务器的记录的总内存字节
消息序列化
key.serializer
指定消息的key的序列化类(需要实现Serializer接口)
无默认值
value.serializer
指定消息内容的序列化类(需要实现Serializer接口)
无默认值
消息发送
消息发送条件
batch.size
批量发送的最大容量
默认值16384 =16k;缓存到本地内存批量发送大小;每当消息的数据量达到16k才会把数据发送给服务器
作用
消息不是一条一条的发送,而是积累到一定量才会发送
linger.ms
生产者将请求传输之间到达的任何记录组合到一个批处理请求中的时间
默认值0
作用
消息发送延迟时间,也就是在一个延迟时间内所有的消息都是被同一批次的发送出去;
batch.size和liger.size只要满足一个,消息就会被发送
消息发送阻塞
max.block.ms
消息发送到具体分区的阻塞时间
默认值:60000ms,一分钟
阻塞原因:缓冲池已经满了,或者是系统元数据不可用,导致这个问题;
消息请求阻塞时间
request.timeout.ms
生产者请求发出后,获取相应的最长时间,如果超过了该时间,那么客户端就会重新发送
默认值:30000 ,30秒
消息发送大小
max.request.size:
生产者发送最大直接数量
默认值:1M
消息确认
acks
生产者要求领导者在考虑完成请求之前收到的确认数量
默认值1
用途:
配置消息发送发到服务的消息确认机制
值集
0:表示producer无需等待leader的确认;
1:代表需要leader确认写入它的本地log并立即确认;
-1(all):代表所有的备份都完成后确认
delivery.timeout.ms
生产者发送完消息,接受服务器消息确认的时间
默认值120000ms,120秒
消息重试
retries
消息发送失败消息重试次数
默认值是int的最大值
retry.backoff.ms
消息重新发送中间间隔时间
默认值100ms
消息压缩
compression.type
消息以怎么的压缩格式进行压缩
值集:“gzip”、“snappy”、“lz4”、“zstd”
和服务器连接
connections.max.idle.ms
关闭空闲连接时间(生产者和服务器最大失联时间)
默认540000
max.in.flight.requests.per.connection:
单个连接,可接受的最大未确认数量
默认值5;也就是消息发送需要服务端确认,这个就是在发送消息之前需要确认发送如果没有确认的消息大于等于该参数,那么就会发送失败。
自定义操作类
metric.reporters
参数修改之后发送通知的类
interceptor.classes
消息拦截器
发送消息之前消息会被拦截,消息还可以做相应的处理
数据传输设置
receive.buffer.bytes
TCP连接接受方缓冲区大小
默认值:32K
send.buffer.bytes
TCP连接发送方缓冲区大小
默认值:128
3.事物消息相关配置
transactional.id
事务ID(当有多个生产者时,标识哪个生产者的事务,可用于消息幂等)
transaction.timeout.ms
事务超时时间
3.消费者配置
1.连接配置
bootstrap.servers
服务器连接地址
2.消费者本身配置
group.id
消费者组的ID
client.id
消费者ID
3.消息配置
auto.offset.reset
初始偏移量当前偏移量不存在的时候,消费者消费的起始点
值集
earliest
自动到最早的偏移量位置
latest
自动把偏移量充值为最新偏移量
none
如果没有找到以前的偏移量,那么就会抛出异常
anything else
直接抛出异常
默认值
earliest
exclude.internal.topics
是否公开topic内部的元数据信息
默认值:true;
事物
isolation.level
隔离级别
生产者数据拉取配置
max.poll.records
自动拉取消息的个数
默认值:500
max.poll.interval.ms
自动拉取消息的频率
默认值:5分钟
fetch.max.bytes
拉取消息的最大数据量
默认值:50M
fetch.min.bytes
拉取最小字节数
默认值:1字节
如果服务器没有数据,那么就会阻塞,直到服务器有数据才会相应
fetch.max.wait.ms
拉取消息阻塞时间
默认值:500ms
生产者自动提交配置
enable.auto.commit
消费者是否是自动提交偏移量
auto.commit.interval.ms
消费者自动提交偏移量的间隔时间
生产者序列化配置
key.deserializer
指定消息的key的反序列化类(需要实现Deserializer接口)
value.deserializer
指定消息内容的反序列化类(需要实现Deserializer接口)
生产者连接配置
connections.max.idle.ms
超过多久关闭服务器和消费者的连接
默认值:540000
request.timeout.ms
消费者给服务端发送请求超时时间
默认值:30秒
session.timeout.ms
心跳发送相应超时时间
说明:消费者是会主动向服务器发送心跳,以此来正面自己是存活的
heartbeat.interval.ms
心跳时间:消费者心跳消息发送到消费者协调器的期望时间
默认值:3秒,设置必须是小于session超时时间的三分之一
工作机制
零拷贝
零拷贝的实现
概述
DMA直接内存访问;现代计算机就是允许硬件之间直接进行数据交互;DMA将一个地址空间复制到另外一个地址空间,然后数据的传输是DMA设置之间完成
DMA
直接内存访问
DMA设备
能够不经过cpu直接相互直接就能进行数据交互的硬件设备
两种实现方式
mmap
概述(kafka就是mmap实现方式)
用户态直接应用内核态的文件句柄
mmap方式,用户态和内核态共享内核态数据缓冲区,数据不需要从内核态复制到用户态空间;用户态发送数据的时候,就直接应用内核态的文件句柄就行(无需把数据从内核态拷贝到用户态,再从用户态拷贝到socket套接字的内核空间);
sendfile
数据不需要经历从内核态拷贝用户态;数据直接从DMA设备直接发送对应的做网络传输的DMA设备,由这个设备直接传输数据
Kafka零拷贝
概述
Kafka的零拷贝并不是说完全不存在拷贝,而是避免不必要的拷贝
零拷贝过程
从磁盘把数据拷贝到内核空间;
从内核空间中直接把数据发送到网卡;
传统拷贝方式步骤
1.从磁盘去读到内核空间缓存页;
2.应用从内核缓存页读取到用户空间缓存区;
3.应用程序将用户缓冲区的数据放入socket缓冲区;
4.操作系统将socket里面的数据复制到网卡接口,发送数据;
零拷贝和传统拷贝方式对比
1.kafka的零拷贝从获取数据到最终把数据发送出去只需要经历一次拷贝;
2.传统拷贝方式从获取数据到最终把数据发送出去,需要经历4次拷贝;
kafka的拷贝方式大大降低了数据在不同的内存空间中复制的次数,提高了系统io效率
kafka持久化机制
消息持久化原理
概述
基于磁盘的线性的读写(操作系统做了大量的IO技术优化),甚至会被随机的内存读写更快
io优化技术
read-ahead
write-behind
和其他数据缓存的差异
kafka是直接把数据写入日志文件;其他几乎都是先把数据缓存在内存中然后再间隔刷盘
持久化读写
读写操作
写操作
将数据顺序追加到文件末尾
文件写入超过一定大小会被滚动到新的文件中
写操作参数设置
操作系统积累多少条数据就一定要被刷到磁盘
操作系统积累了多少秒的数据就一定要被刷到磁盘
关于日志丢失
也就是根据设置最多丢失多少秒或者多少条数据
读操作
从文件中读取
读操作参数设置
最大消息大小
缓冲区大小
读取过程
1.缓冲区大小大于消息大小就可以直接读取成功
2.如果缓冲区大小小于消息大小,那么就会读取失败,缓冲区大小翻倍知道成功读取完整条消息;
读写概述
读写都是顺序写入顺序消费,能保持较高的效率
好处
1.读操作不会组阻塞写操作
2.不受内存大小限制
3.线性的读取速度依旧很快
4.相对于内存保存时间更长
删除
删除策略
删除策略是可以配置
常见删除策略
超过一定时常
保留最近多少磁盘大小文件
删除内容
日志文件中的消息和日志文件本身都会被删除
删除操作阻塞读操作
读操作读取的是要被删除文件的副本
持久化文件构成
日志文件
日志文件特点
1.topic的每一个分区都会专属的append-only日志文件;
3.每条消息在文件的位置称之为offset(偏移量)
2.属于分区的消息会被追加到日志文件的末尾
日志条目
概述
日志文件由日志条目组成
日志条目内容
消息头(4字节整形数,表示消息体有多长)
消息体
包含消息内容
消息偏移量(用来表示消息的起始位置)
日志文件名称
该文件第一条数据偏移量+.kafka
索引文件
记录每一个segment下包含的日志条目偏移量范围
日志清理
消息有效期
在消息有效期内,是允许消费者重复消费;
日志清理两种方式
日志删除
根据保留策略删除日志分段
参数配置log.cleanup.policy = delete
日志删除策略
基于时间
log.retention.hours、log.retention.minutes、log.retention.ms
最长时间7天
基于日志大小
log.segment.bytes,每个日志分段大小
og.retention.bytes ,总的日志大小
扫描,某个分段超过日志分段大小,那么就删除;如果总的文件大小超过了设定,那么就删除时间距离现在最久的日志
基于日志起始偏移量
logStartOffset;删除偏移量小于这个设定的偏移量大小的日志
日志压缩
根据消息的key进行压缩,相同的key的消息,只会保留一个副本;这个key就是业务消息中的key,需要去手动指定这个key对应的是业务中的那个字段
参数设定
log.cleanup.policy = compact
log.cleaner.enable = true
压缩过程
压缩线程会根据日志分段中需要被清理压缩的占比最高的日志分段开始压缩清理;根据业务中的key去做删除,相同的key只会保留一条消息;
log.cleaner.min.cleanable.ratio ,设置当需要被压缩的数据超过百分之多少的比例的时候,就进行压缩;
队列工作方式
消息发送/消费方式assign
消息发送方式
1.消息可以指定分区发送
2.消息可以通过负载均衡方式发送到不同的分区
3.通过指定key进行hash运算后确定让哪个分区发送
消息消费方式
消费者集群的各个消费者只能消费不同的分区
一个topic消息可以发送给多个消费者集群
一个消费者可以消费多个集群的消息
多个消费者集群可以消费一个topic下面的消息
订阅队列模式设置subscribe
设置多个消费者消费一个分区的消息-订阅
一个消费组中的一个消费者只订阅一个分区的消息-点对点
kafka集群
zk的作用
注册中心服务治理
注册服务节点
同一管理所有的服务器
注册topic
记录topic的分区信息与对应的服务器节点对应关系
注册消费者
消费者启动的时候,都会去zk创建自己的节点
负载均衡
生产者负载均衡
可以通过zk的配置文件动态感受来自服务器节点的新增减少,来实现相应的负载均衡
消费者负载均衡
zk动态感受消费者新增减少,来合理的实现负载均衡
记录数据
记录分区与消费者关系
将分区和消费者id绑定记录到临时节点上
记录消费中的偏移量
记录每个分区中消费者消费的偏移量会发送给zk,方便在消费者重启之后,或者是重新分配消息分区,能够继续之前的消费
负载均衡
四层负载均衡
此负载均衡是kafka自带的
缺点是无法动态感知服务器节点的新增减少,从而在服务器新增减少的时候,不能根据服务器做负载均衡
kafka事务
事务场景
生产者发送的多条消息需要组成事物,对所有消费者同时可见,或者同时不可见
生产者发给多个topic,多个分区发送消息,要么都成功,要么都失败
子主题 3
子主题 4
ack 的三种机制
0
生产者不需要等到kafka节点的ack;
1
kafka节点上的leader副本收到消息就发送ack;
不需要等所有follow副本确认
-1
kafka所有的follow副本接收到消息,leader服务才会发送ack
leader选取策略
策略
OfflinePartition Leader
有新的分区上线就重新选leader
ReassignPartition Leader
运行重新分区命令,重新选择leader
PreferredReplicaPartition Leader
运行重新选择leader命令
ControlledShutdownPartition Leader
服务正常关闭之后,重启重新选择leader
子主题
不支持读写分离
主要原因
数据一致性问题
leader副本的数据和其他副本数据都不一致,读写分离容易导致数据不一致
延时问题
leader副本数据到从副本数据有数据延迟;
消费者是pull(拉)还是push(推)
producer 将消息推送到 broker
consumer 从broker 拉取消息
如果是kafka节点向消费这push消息,可能会造成消费者消费积压,或者是消费者性能浪费
zookeeper对于kafka的作用
1、存储kafka元数据
2、集群不同节点之间通信
3、leader 检测、分布式同步、配置管理、识别新节点何时离开或连接、集群、节点实时状态等等。
kafka判断一个节点还活着的有那两个条件
1、节点和zk之间心跳检测正常
2、follow节点可以即时同步leader的写操作,且不能延时过高
kafka高性能原因
1、0拷贝
1、索引和日志文件读写
2、数据传输
2、对log文件进行分段处理,且分段数据会简历索引文件
3、本身就是天然的分布式
4、页缓存
对下一页数据的读取是从缓存中读取
5、对磁盘的写入顺序写入
6、消费采取pull模式
能够让消费者处于消费者机器自身资源相符的消费速度
leader副本和Follow副本区别
读写都是从leader副本操作的
follow副本的数据都是从lader服务同步
实际开发
引用场景
日志收集
同一日志同一收集,然后以同一服务的形式发放给各种消费者
消息系统(削峰,异步处理)
用户日活跟踪
运营指标
收集生产者各种生产数据,同一做报表处理
流式处理
和flink,spark,strom做流计算处理
实际的开发方式
1.需要导哪些包
2.有哪些核心API
消息队列实际问题
重复消费
消息重复消费原因
概述:
主要原因就是消息消费时候提交的偏移量,服务器并不知情
具体情况分类
1.强行杀掉线程,导致偏移量没有提交
2.消费了,还没有提交偏移量,分区就掉线了,触发重平衡,然后消息就会重复消费
3.消费者重新分配分区,导致消费者数据重新消费
4.消息消费时间过长,让zk觉得机器宕机了,触发了重平衡
解决方式
最稳定的方式就是在代码中根据消息唯一id做幂等性判断
消息一致性
概述
kafka消息一致性指的是分区中的leader和多个副本的消息数据保持一致性;
消息一致性解决方式
ISR机制
副本同步leader数据
同步参数
rerplica.lag.time.max.ms=10000
根据一定的时间间隔副本同步leader数据
rerplica.lag.max.messages=4000
当副本数据和leader数据查了多少条也会同步数据
ACK=all机制,生产者给服务器发送消息的时候,直到所有的副本都收到消息才会通知生产者,服务端这边已经收到消息
消息延迟消费
时间轮
延迟消息的实现
消息有序性
消息乱序原因
1.消息重试机制会导致消息乱序(一个分区对应一个消费者)
2.多个分区对应多个消费者, 需要顺序消费的数据被分配到了不同的消费者
解决方案
针对1:max.in.flight.requests.per.connection=1禁止生产者向服务器响应前再次发送请求,也就是消息的重试必须是在上次失败之后,里面发起重试
针对2:
可以设置一个topic只有一个分区,只有一个消费者
生产者把需要顺序消费的消息发送到指定的分区上
消息丢失
消息丢失情况分类
生产者发送消息给节点丢失
消息丢失原因
发送消息的程序异常,导致消息压根没有发送出去
消息发送了,由于中间网络原因,以及服务器接收原因,导致数据服务器没有正常接收到数据
节点保存消息丢失
消息丢失原因
ACk设置=1,主机拿到了数据,从机还没有同步主机数据,这时候主机挂了,从机无法同步主机数据
主机拿到数据,主机就挂了,没有设置相应的从节点,来备份数据;
主机挂了,从机被选为主机,主机中还有部分数据没有被同步到从机
消费者丢失消息
消息丢失原因
消息在消费的时候,消息消费的自动确认提交偏移量;如果批量的消息有20条,消费到10条消息的时候异常了,那么就会自动提交消息的偏移量是20,也就是会导致后面10条消息是没有被正常消费,也相当于消息丢失;
针对不同丢失情况对策
生产者发送丢失
消息确认机制
消息确认机制
消息确认等级
ACK =0
ACK=1
ACK=all
消息确认相应机制
ACK=1(默认设置),只要分区的leader副本接受到了消息,就会给生产者发送消息接受成功(其他副本再回去同步leader的数据)。
设计上是比较折中的在一定程度上能够保证消息的不丢失,也能保证一定的吞吐量
ACK=0,生产者给服务端发送消息,不等服务端是否有接收到消息,发送完了就认为消息到被服务端接受了;而实际情况是,消息会生产者的缓冲池中待一段时间然后才会被发送到服务端,生产者就不知道消息具体在啥时候发送到服务端;
缺点:网络宕机的时候,消息会丢失
优点:满足大吞吐量的数据发送;
ACK=all,消息的分区leader还有所有的副本都接受到消息,才会给生产者发送消息已经被接受了。
优点:最大程度上保证服务器节点接受到消息;
缺点:极度影响性能,导致数据的吞吐量低
消息重试机制
概述
生产者发送消息异常,然后会重新给服务器发送消息
消息重试前提条件(两者同时满足才能重试)
1.重试的次数小于retries指定的次数
也就是当重试的次数超过了设定次数,那么也不会发送的;
2.异常的类型是RetriableException或者事务管理器允许重新发送
消息重试机制参数设置
retries
消息重试次数,默认次数为int的最大值
retry.backoff.ms
重试的间隔时间
消息确认机制和重试机制区别
消息确认机制主要是针对消息发送到服务器正常接收这个过程的处理
消息重试机制,主要是针对消息发送之前生产者内部自己发送消息异常的兜底处理
节点数据丢失
1.ACK=all,让所有的服务器节点都获取到数据;
2.合理的设置从机的个数,设置数据的备份
参数设置:min.insync.replica
3.禁止主机挂掉,选从机作为新的主机
参数设置:unclean.leader.election.enable=false 本身默认就是flase
消费者消费数据丢失
设置消息消费提交偏移量为手动提交偏移量,通过代码在finnaly里面手动设置消息异常的那个前一条的偏移量做提交;
消息积压
积压原因
消费者消费能力不足
消费者处理不及时
解决方式
针对1
添加分区个数和消费者个数
针对2
合理增大每次拉取的消息数量
死信队列
不支持死信队列
消息如何控制只被消费群组中一个消费者消费
原则
kafka的一个分区的数据只会被消费者组中的一个消费者消费;
一个消费者可以消费来自多个分区的数据;
详细说明
分区数量- 3,消费者数量- 3
Kafka 将一个分区分配给一个使用者。除非某些使用者发生故障并且发生使用者重新平衡(将分区重新分配给使用者),否则所有使用者都将映射到其分区,并按顺序使用这些分区的事件。
分区数量 - 1,使用者 - 3
如果消费者多于分区数量,Kafka就没有足够的分区来分配消费者。因此,该组中的一个消费者被分配给分区,而该组中的其他消费者将处于闲置状态。
分区- 4,消费者- 3
在此方案中,其中一个使用者获得 2 个分区,而在使用者重新平衡期间,另一个使用者可能会获得 2 个分区。
Kafka 将一个分区分配给一个使用者。除非某些使用者发生故障并且发生使用者重新平衡(将分区重新分配给使用者),否则所有使用者都将映射到其分区,并按顺序使用这些分区的事件。
分区数量 - 1,使用者 - 3
如果消费者多于分区数量,Kafka就没有足够的分区来分配消费者。因此,该组中的一个消费者被分配给分区,而该组中的其他消费者将处于闲置状态。
分区- 4,消费者- 3
在此方案中,其中一个使用者获得 2 个分区,而在使用者重新平衡期间,另一个使用者可能会获得 2 个分区。
kafka监控平台
kafka缺点
对于mqtt协议不支持
不支持物联网传感数据直接接入
仅支持统一分区内消息有序,无法实现全局消息有序
可以通过代码控制顺序
监控不完善,需要安装插件
依赖zookeeper进行元数据管理
kafka最全面试题
https://zhuanlan.zhihu.com/p/109814155
http://events.jianshu.io/p/869464e66cfb
RocketMQ
RocketMQ通信方式
示意图
基础概念
生产发送消息类型
消息种类划分
同步消息
最大程度上确保消息的不丢失
使用场景
重要的消息通知
短信通知
异步消息
使用场景
对业务的效应时间非常敏感的业务
单向消息
使用场景
不是特别关注发送结果的场景
日志发送
优缺点
同步消息,异步消息会有消息的重新发送,单向消息消息发送失败不会重新发送
同步消息,异步消息发送的时候需要服务器节点返回消息接收的确认信息,而单向消息没有
消费方式
拉取式消费
消费者从服务节点上拉取消息消费
默认的消费方式,但是实时性不高,但是不会造成消息消费堆积
推动式消费
服务器节点主动给消费者推送消息消费
优点
消息消费实时性高
缺点
消费者来不及消费过多消息,容易造成消费者消息堆积
本质
消息推送本质上还是消息拉取
基本概念解释
Name Server
功能
1、服务器路由提供者,
2、生产者,消费者能够通过名称服务查询各主题的相应元数据信息
工作模式
1、多个Name Serve 组成集群
2、集群中各个Name Server相互独立,没有信息交互
生产者组
同一类Producer的集合,生产者发送消息逻辑一致
消费者组
同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致
集群消费
集群消费模式下,相同的消费者组,每一个消费者平摊消息;
广播消费
消费者集群中的每一个消费者,都是都会受到消息;
普通顺序消费
工作特性
1、消费者通过同一个消息队列(topic分区)收到的消息是有序的
2、不同的消息队列收到的消息可能是无序的
优缺点
优点
生产者发送消息快速
缺点
同一个消费者消费的不同队列之间的消息,是无序的
使用场景
对程序性能要求高,但是顺序消费要求不高
严格顺序消费
消费者收到的所有消息均是有顺序的
优缺点
优点
最大程度上确保了消息的有序性
缺点
消息发送的吞吐量大大降低
使用场景
对消费有顺序要求,且对程序性能要求不高
RocketMQ消息工作特性
消息顺序
全局顺序消费(严格顺序消费)
某个Topic下的所有消息都要保证顺序
分区顺序消费(普通顺序消费)
部分顺序消息只要保证每一组消息被顺序消费即可
消息过滤
发送消息的时候设置tag,消费的时候根据对应的tag做相关的过滤处理
过滤方式
服务器节点过滤
可以通过消息 tag, header,body 过滤
消费端过滤
完全可以自定义去过滤消息;缺点就是无用的消息发送到消费者
消息可靠性
影响消息可靠性几种情况
1、节点非正常关闭
2、节点宕机
3、节点所在服务宕机
4、服务器断电,但是能立即供电
5、机器无法开机
6、磁盘设备损坏
影响范围
1、前四种可以立即恢复,可能会有少量的数据丢失
2、后面两种,如果服务器是单点,那么消息将全部丢失,如果不是单点,消息还可以恢复绝大部分消息
至少一次
Consumer先Pull消息到本地,消费完成后,才向服务器返回ack,如果没有消费一定不会ack消息
消息回溯
工作机制
回溯消费是指Consumer已经消费成功的消息,由于业务上需求需要重新消费,要支持此功能,Broker在向Consumer投递成功消息后,消息仍然需要保留。并且重新消费一般是按照时间维度,例如由于Consumer系统故障,恢复后需要重新消费1小时前的数据,那么Broker要提供一种机制,可以按照时间维度来回退消费进度
RocketMQ支持按照时间回溯消费,时间维度精确到毫秒,可以向前回溯,也可以向后回溯
事物消息机制
应用本地事务和发送消息操作可以被定义到全局事务中,要么同时成功,要么同时失败
消息重投
工作机制
1、生产者发送消息时,同步消息,异步消息的发送如果失败了,生产者会重新发送
2、单向发送发送失败,生产者无法重新发送消息;
定时消息
指消息发送到broker后,不会立即被消费,等待特定时间投递给真正的topic
流量控制
生产者流量控制
生产这发送消息过多,服务器节点处理这些消息达到性能瓶颈
控制副作用
消息不会重投
消费者流量控制
消费者这边接收到的消息,消息处理不过来达到性能瓶颈
控制副作用
降低拉取频率
作用
降低服务器节点压力,降低消费者节点压力
死信队列
消息重试达到最大次数后,依旧无法正常消费,死信队列就会接受到该消息;
可以通过RocketMQ的控制台,对死信队列中的数据重新消费;
接受不能被处理的消息,放在以后再做处理
ack机制
作用:
保证消息能够被正常消费
发送者为了保证消息肯定消费成功,只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功
如果没有确认,那么服务端还是会接着给消费方发送消息
消息重试
工作机制
1、消费者消费消息失败后,令消息再消费一次;
消费失败后的消息会进入消息重试队列
消息消费失败原因
1、消息反序列化失败
2、程序异常
2、消费者依赖的服务不可用
集群部署
Rocket消息问题
消息堆积
消息堆积的原因
Producer原因
生产者生产速度过快;
短时间的业务高峰期;
Broker消息堆积
Broker同步策略导致消费堆积
Consumer原因
消费者消费速度过慢
消费者宕机
解决方式
Producer原因
生产者限流
Broker消息堆积
调整刷盘策略
Consumer原因
多线程消费消息
设置数据开关,开关打开消息直接放入数据库,或者直接返回,最大程度上降低消费者程序时间
判断MQ是否存在消息堆积场景方式
Producer发送消息的速率监控
Consumer消费消息的速率监控
Producer发送消息的最大偏移量(maxOffset)与Consumer消费消息的当前偏移量(currOffset)
的差别值与给定的消息堆积数值告警值对比,若是差别 值大于数据告警值,则存在消息堆积,不然不存在消息堆积
消息堆积场景
差别值呈现增大趋势
producer消息的发送速度大于consumer的消息消费速度
处理方式
1、消费者进行扩容操作
2、提高消费者消费速度;
3、对生产者限流操作;
producer的生产速率无明显增长,consumer的消费速率无明显增长
处理方式
这种状况基本上是能够肯定是RocketMQ自己的故障造成的,需要提高Broken节点自身的服务器配置,和相关参数;
producer生产速率正常,RocketMQ服务器性能正常,consumer消费速率下降
差别值呈现平稳趋势或者降低趋势
最佳工作模式:RocketMQ自己的服务性能,必要的时候能够对RocketMQ 进行扩容,提升消息堆积能力。
消息顺序消费
问题出现原因
某些特殊场景下,发送出去的消息,消费者需要按照顺序来消费
顺序消费的前提
发送出去的多条消息,都是走的同一个topic发送
问题具体场景
大多数业务场景不需要考虑消息的顺序性
不需要考虑消费顺序
消息系统吞吐量不大
消费者和消费者同时都只有一台机器
其他场景
生产者消费消息的时候,把消息发送到同一个topic下同一个队列;可以保证消费方只有一个线程去消费消息
将需要消费的顺序消息,合并成一条消息发送出去,这样消费的时候就是有顺序;
设置发送的消息为全局顺序消息
消息重复消费(消息幂等)
问题原因
网络问题,导致消息消费的确认消息,rocketMQ节点没有收到
1、消费者没有发出
2、网络原因导致数据丢失
3、rocketMQ节点没有收到
解决方式
1、代码层面
消费者代码逻辑中保持幂等性
2、消息发送层面
通过每条消息的唯一编号来保证
消费者记录消费过的消息的唯一id,接收到消息的时候,发现有此id已经消费,那么就不做处理
唯一编号
msgId
消息设置的key
消息体重的唯一标记
3、重复消息不处理
有些业务场景,重复接受到消息,也不会影响到业务,所以不处理也行
消息丢失
消息丢失场景
主要有三种场景
1、生产者发送到队列节点消息丢失
网络抖动导致消息丢失
2、RocketMQ节点消息未能持久化到磁盘
消息还未持久化到磁盘,节点宕机
已经持久化到磁盘,磁盘损坏,但是没有备份
3、RoekctMq节点消费者丢失
消息还未消费完成,就通知节点消息已经消费完了,此时消费者宕机,导致当前正在消费消息丢失;
处理方式
针对场景1处理
RoekctMQ 事物机制来确保消息都能发送到节点上
消息重投机制,消息投递失败,会再次投递
将生产者消息发送设置成同步发送,根据发送结果来判断是否需要做重复发送,最大程度保证消息发送到了服务器
针对场景2处理
1、修改节点的消息持久化刷盘机制
将异步持久化到磁盘,修改成同步持久化到磁盘
RocketMq消息本身默认的持久化盘刷机制是异步刷盘
2、RocketMQ采用主从机构
确保主机挂了,从机上还有消息备份数据
针对场景3处理
ack机制,设置消息成功消费之后,再通知节点消息已经成功消费
处理方式带来的问题
导致问题
性能和吞吐量也将大幅下降
优化机制
使用事务机制传输消息
1、耗费性能,导致消息发送速率降低
同步刷盘
刷盘操作更为频繁,导致刷盘效率低下
主从机制
主机需要把数据同步到从机,消耗主机网络io,和cpu
消费完再通知节点
消费者消费消息速度降低
ActiveMQ
持久化方式
1.JDBC
2、AMQ日志
3、KaHaDB(基于AMQ)
4、LevelIDB (谷歌kv数据库)
MQ对比
性能对比
示意图
各自优缺点
Kafka
优点
性能卓越,单机写入TPS约在百万条/秒,最大的优点,就是吞吐量高。
时效性:ms级
可用性:非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
消费者采用Pull方式获取消息, 消息有序, 通过控制能够保证所有消息被消费且仅被消费一次;
有优秀的第三方Kafka Web管理界面Kafka-Manager;
在日志领域比较成熟,被多家公司和多个开源项目使用;
功能支持:功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用
时效性:ms级
可用性:非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
消费者采用Pull方式获取消息, 消息有序, 通过控制能够保证所有消息被消费且仅被消费一次;
有优秀的第三方Kafka Web管理界面Kafka-Manager;
在日志领域比较成熟,被多家公司和多个开源项目使用;
功能支持:功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用
缺点
Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长
使用短轮询方式,实时性取决于轮询间隔时间;
消费失败不支持重试;
支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
社区更新较慢;
使用短轮询方式,实时性取决于轮询间隔时间;
消费失败不支持重试;
支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
社区更新较慢;
RabbitMQ
优点
由于erlang语言的特性,mq 性能较好,高并发;
吞吐量到万级,MQ功能比较完备
健壮、稳定、易用、跨平台、支持多种语言、文档齐全;
开源提供的管理界面非常棒,用起来很好用
社区活跃度高;
吞吐量到万级,MQ功能比较完备
健壮、稳定、易用、跨平台、支持多种语言、文档齐全;
开源提供的管理界面非常棒,用起来很好用
社区活跃度高;
缺点
erlang开发,很难去看懂源码,基本职能依赖于开源社区的快速维护和修复bug,不利于做二次开发和维护。
RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。
需要学习比较复杂的接口和协议,学习和维护成本较高
RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。
需要学习比较复杂的接口和协议,学习和维护成本较高
RocketMQ
优点
单机吞吐量:十万级
可用性:非常高,分布式架构
消息可靠性:经过参数优化配置,消息可以做到0丢失
功能支持:MQ功能较为完善,还是分布式的,扩展性好
支持10亿级别的消息堆积,不会因为堆积导致性能下降
源码是java,我们可以自己阅读源码,定制自己公司的MQ,可以掌控
可用性:非常高,分布式架构
消息可靠性:经过参数优化配置,消息可以做到0丢失
功能支持:MQ功能较为完善,还是分布式的,扩展性好
支持10亿级别的消息堆积,不会因为堆积导致性能下降
源码是java,我们可以自己阅读源码,定制自己公司的MQ,可以掌控
缺点
支持的客户端语言不多,目前是java及c++,其中c++不成熟;
社区活跃度一般
没有在 mq 核心中去实现JMS等接口,有些系统要迁移需要修改大量代码
社区活跃度一般
没有在 mq 核心中去实现JMS等接口,有些系统要迁移需要修改大量代码
技术选型
Kafka
日志收集,实时计算
RocketMQ
RoketMQ在稳定性上可能更值得信赖,业务有并发场景,建议可以选择RocketMQ
RabbitMQ
数据量没有那么大,小公司优先选择功能比较完备的RabbitMQ
不需要做二次开的情况下;
共性问题及解决思路
消息堆积
导致原因
消费者消费速度过慢,生产者生产速度过快
解决方式
1、检查生产者发送消息是否正常,是否有大量重复消息,或者不必要发送的消息;检查消费者消费是否正常,是否有性能瓶颈,
如果有,需要针对型处理
2、提高消费者消费速度
1、开启多线程消费
2、添加消费者服务器节点
3、批量拉取消息
4、消息消费快速入库,快速返回成功,开启后台线程去消费消息;
消费重复消费
导致原因
消费者未能提交正确的消费成功消息,或者服务端没有接受到此消息
解决方式
代码逻辑中保证幂等性,使用消息id,获取业务id来做幂等性判断;
消费丢失
消息丢失原因
生产者向服务端发送消息失败(网络原因,或者生产者异常)
kafka
消息重试机制
生产者自己异常,导致消费发送失败,生产者会有重试机制,重新发送消息
消息确认机制
消息确认等级
ACK =0
ACK=1
ACK=all
消息确认相应机制
ACK=1(默认设置),只要分区的leader副本接受到了消息,就会给生产者发送消息接受成功(其他副本再回去同步leader的数据)。
设计上是比较折中的在一定程度上能够保证消息的不丢失,也能保证一定的吞吐量
ACK=0,生产者给服务端发送消息,不等服务端是否有接收到消息,发送完了就认为消息到被服务端接受了;而实际情况是,消息会生产者的缓冲池中待一段时间然后才会被发送到服务端,生产者就不知道消息具体在啥时候发送到服务端;
缺点:网络宕机的时候,消息会丢失
优点:满足大吞吐量的数据发送;
ACK=all,消息的分区leader还有所有的副本都接受到消息,才会给生产者发送消息已经被接受了。
优点:最大程度上保证服务器节点接受到消息;
缺点:极度影响性能,导致数据的吞吐量低
RocketMQ
RoekctMQ 事物机制来确保消息都能发送到节点上
将生产者消息发送设置成同步发送,根据发送结果来判断是否需要做重复发送,最大程度保证消息发送到了服务器
消息重投机制,消息投递失败,会再次投递
rabbitMq
1、消息确认机制
发送端把消息发送个服务端,服务端接收到消息并且把消息持持久化到磁盘就会给发送端一个异步的confirm应答
2、事物消息机制
发送端开启一个事物,再推送消息,如果投递失败;进行事物回滚,然后重新发送消息;如果服务端收到消息,发送端就提交事务。
服务端保存消息失败(写入磁盘失败)
kafka
1.ACK=all,让所有的服务器节点都获取到数据;
2.合理的设置从机的个数,设置数据的备份
参数设置:min.insync.replica
3.禁止主机挂掉,选从机作为新的主机
参数设置:unclean.leader.election.enable=false 本身默认就是flase
rocketMq
1、修改节点的消息持久化刷盘机制
将异步持久化到磁盘,修改成同步持久化到磁盘
RocketMq消息本身默认的持久化盘刷机制是异步刷盘
2、RocketMQ采用主从机构
确保主机挂了,从机上还有消息备份数据
rabbitMQ
服务端设置交换机,队列,数据的持久化;服务器宕机后,重启会读取磁盘上的持久化的数据;
问题:由于消息的持久化是一批的持久化,可能宕机了,这一批数据还持久化到磁盘
消息的持久化
1、服务端收到生产者发送过来的消息,会做消息的持久化
2、当服务宕机后, 会从磁盘当中读取相应的消息,最大程度上保证消息不在服务节点上丢失
使用confirm机制,可以让发送者的数据持久化到磁盘,再确认
也可以尝试修改刷盘策略
消费方消费丢失
kafka
设置消息消费提交偏移量为手动提交偏移量,通过代码在finnaly里面手动设置消息异常的那个前一条的偏移量做提交;
需要正确提交自己的消费正常的偏移量
rocketMq
ack机制,设置消息成功消费之后,再通知节点消息已经成功消费
rabbitMq
丢失原因
1.消费者在处理消息的时候出现异常了,那么这条消息就是没有被正常的消费;如果不采取措施,那么这个消息就会丢失
解决方案
ack机制
ack机制概述
消息只有正常消费后,反馈给服务端;服务端才会从队列里面把该条消息删除
ack机制三种模式
不确认
不会发送ack确认消息
自动确认
服务端发送完消息就自动认为该消息被成功消费
缺点:由于网络原因,造成数据从服务端发送到消费者消息丢失
手动确认
消费者消费成功之后,显示的给服务端ack信号;服务端只有收到该信号才会把数据从队列里面删除
设置手动ack,尽可能减少消费端的数据丢失问题;正常就是发送ack,异常就记录日志,然后发送nack
ack机制弊端
内存泄露
如果消费者异常没法发ack消息,服务端会认为这些数据都是没有被正常消费;就会堆积在队列当中,造成内存没法回收,内存泄露;
内存泄露解决方案
1.设置手动应答,如果异常,捕获异常记录日志,给服务端发送正常消费;
2.设置重试次数(默认是3次,三次不消费成功就会放入到默认的死信队列)
ack机制默认打开,而且是自动确认
统一概述
消费者消费消息,都应该向服务器节点正确提交消费的实际情况;
消息顺序消费
通用方式
1、将前后有逻辑的消息合并成一个消息,发送,这样不管消费者是多个,不管消费者有几个线程,消费的时候都能保证消费是顺序被消费
2、同一个topic下,将有先后逻辑顺序的消息发送到同一个队列(rocketMQ,rabbitMQ),或者同一个分区(kafka),且保证消费者只有一个也只有一个线程来消费
kafka
1、将有前后逻辑顺序的消息发送到同一个分区
1、一个topic只有一个分区,对应的也只有一个消费者
RocketMq
1、将有前后逻辑顺序的消费发送到同一个topic下的同一个队列,且保证消费方只有一个线程去消费
2、设置发送的消息是全局顺序消息
某个Topic下的所有消息都要保证顺序
RabbitMq
1、将有前后逻辑顺序的消费发送到同一个topic下的同一个队列,且保证消费方只有一个线程去消费
消息堆积,且需要保证消息顺序消费
子主题
2、将消息按照用户id或者其他id来做hash,将消息放到不同的表。开启多线程,每一个线程消费固定的某一个表,来消费消息;
数据库中间件
canal
canal简述
java 语言写的
目前可以把从mysql主机dump到数据 发送到 mysql,kafka,es,hBase,rocketMQ,普罗米修斯(数据分析系统)
canal作用
基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费
canal工作
1、mysql服务需要开启 binlog日志(默认是不开binlog日志)
2、在主库的配置中给 canal 链接授权,让他有mysql 从机权限,也就是可以伪装成mysql的从机
canal工作原理
1.canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送 dump 协议
2.MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
3.canal 解析 binary log 对象(原始为 byte 流)
mq顺序性问题
binlog日志本身是顺序的。但是写入到MQ之后,就会有顺序性问题
设置canal的路由方式:
1.单topic单分区,就是严格按照binlog日志一样的顺序性,性能比较差,
也就是能够保证每一个库每一个表的数据都是不乱的。
2多topic单分区,保证表级别顺序性,一张表,一个库所有的数据都写入到一个分区里面去。
分库分表
sharding-jdbc
使用
限制
1、分库分表,这个操作是开发来做的事情,目前框架是不能自动分库分表,这个框架只是帮助我们去管理这个
使用方式
以jar包的形式提供服务,无需外部部署和依赖,目前已经和springBoot做了整合
在框架中使用insert ,select语句的时候和当前使用的持久层框架是一样的,只不过表名是逻辑表名;
执行的时候根据相关的配置,生成真实的sql去做查询,所以说select就会生成多条数据,然后把查询的结构及合并在一起;
注意事项
表关联的时候查询,如果两个表没有绑定关系,那么关联查询会有笛卡尔积;
公共表的处理
一般说是每一个库都加上这个公共表
sharding-jdbc去管理这个表,crud的操作会映射到每一个公共表中,所以说一个添加操作
所有的表中都会有这个数据;
读写分离处理
读写分离:sharding-jdbc提供了一主多从的配置
sharding-jdbc执行步骤
1.sql在连接数据库之前在sharding-jdbc中需要做解析(把sql语句分解),
2.路由:这个逻辑sql语句,最终是需要在哪些数据库的节点上执行;
3.sql改写:逻辑的表名就会被改写成真实的表名;
4.sql执行:分为两种模式,根据性能消耗,可做自动切换
1.内存限制模式:会根据需要操作的真实的表,创建对应个数的数据库连接;OLAP 面向效率操作
2.连接限制模式:不管需要操作多少个真实的表,都只会有一个数据库连接;如果表分在多个数据
上,那么就会每一个数据库创建一个数据库连接;OLTP面向事物操作;
5.结果归并:把结果集合并处理成需要的
1.内存归并:拿到所有的结果集,在内存中处理
2.流失归并:通过数据库的游标,一遍查询仪表做归并操作;
oneProxy
概述
使用C&C++语言开发
OneProxy主要功能
1. 垂直分库
垂直拆分前
垂直拆分后
X 表从 原来的库里面单独拆分出来放在另外一个数据库了。也就是做了垂直拆分
2. 水平分表
水平拆分前
水平拆分后
在垂直拆分的基础上,又做了水平拆分,把X表的数据又分散到另外一个服务组上面去了
3. Proxy集群
4. 读高可用
5. 读写分离(master不参与读,支持一主多从,也就是会有多台从机
6. 读写分离(master参与读)
7. 写高可用
8. 读写随机
9. SQL检查
10. SQL统计【暂无文档】
11. 任务队列监控【暂无文档】
12. 连接池管理【暂无文档】
oneProxy的安全性
SQL白名单(防SQL注入)
IP白名单功
SQL防火墙软件
工作机制示意图
子主题
子主题
mycat
mycat分库分表概述
数据库中间件
示意图
子主题
mycat目前支持的数据库包括:MySQL、SQL Server、Oracle、DB2、PostgreSQL
mycat分库分表的时候 ,应用想去连接mycat的,然后mycat再去连接底层的各个数据库;mycat的默认端口是8066,使用的时候需要自己部署mycat相关的服务
主要功能
数据库的垂直拆分
水平拆分
水平+垂直拆分
读写分离
工作原理
1、拦截应用发出的sql语句请求
2、对sql语句做 分片分析,路由分析,读写分离分许,缓存分析
3、再把sql发往真实的数据库做sql操作
可以吧mycat看做是看做是对数据库的操作租了一层代理拦截
示意图
子主题
工作场景过程说明
查询
场景
假设user_info做了分库操作,分了三个库:
工作场景
1.select * from user_info
这个sql语句会向三个DB 发送数据请求,
mycat会把sql转成
select * from db1.user_info
select * from db2.user_info
select * from db3.user_info
然后mycat 在把结果集封装在一起,返回给客户端;
2.select * from db1.user_info where id =1 ;
mycat 就会根据分片规则,定位到具体的数据库,然后直接只给一个数据库发送请求;
3.select * from user_info limit 0,2
往三个库里面发三个select请求 获取三对 六条结果
随机抽取一对返回给客户端
4.select * from user_info order by id limit 0,2
先发送三个select 每个都是最大的两条 然后返回给mycat 进行综合评选拿出最大的俩 返回给客户端
5.select * from table order by id limit 5,2;
sql解析,也就是跳过前面5条,取6,7条数据;
如果mycat 向三个数据库发送都是 limit5,2 则会出现问题,
场景演示
解决方式
mycat 就会做sql改写操作
limit m,n 的SQL语句时会对其进行改写,改写成 limit 0, m+n 来保证查询结果的逻辑正确性
所以真正想每个数据库发送的请求是:
select * from table order by id limit 0,7;
所以真正想每个数据库发送的请求是:
select * from table order by id limit 0,7;
6.任意表的join操作
关联的查询的表,需要把能够关联在一起的数据放在一个库里面,如果不能保证这点,
那么就需要 放弃这两个表数据分库
更新
1、Mycat不会立即把命令发送到DB节点上,等后续下发SQL时,Mycat从连接池获取非自动提交的连接去执行
2、Mycat会等待各个节点的返回结果,如果都执行成功,Mycat给该连接标识为 Prepare Ready 状态,
如果有一个节点执行失败,则标识为 Rollback 状态
如果有一个节点执行失败,则标识为 Rollback 状态
示意图
3、执行完成后Mycat等待前端发送 commit 或 rollback 命令。发送 commit 命令时,
Mycat检测当前连接是否为 Prepare Ready 状态,若是,则将 commit 命令发送到各个DB节点
Mycat检测当前连接是否为 Prepare Ready 状态,若是,则将 commit 命令发送到各个DB节点
示意图
mycat线程模型
示意图
Cobar
TDDL
atlas
0 条评论
下一页