Java知识总结
2023-12-29 11:01:47 32 举报
AI智能生成
登录查看完整内容
Java是一种广泛使用的面向对象编程语言,具有跨平台、安全性高、易于维护等特点。Java语言的设计目标是让程序员“一次编写,处处运行”,即编写的Java程序可以在任何支持Java虚拟机(JVM)的设备上运行。Java的基本数据类型包括整型、浮点型、字符型和布尔型等,还有字符串、数组和集合等复杂数据类型。Java的控制流程语句包括条件判断、循环控制和异常处理等。Java还提供了丰富的类库,包括基础类库、集合类库、输入输出类库、网络类库等,方便程序员进行开发。此外,Java还支持多线程编程,可以通过继承Thread类或实现Runnable接口来实现。
作者其他创作
大纲/内容
ProcessFunction
DataStream API
SQL
flink开发方式
flink用于做一计算的时候数据采集的区间范围就是窗口
窗口概念
第一个属于这个时间窗口的元素到达,到第一个不属于这个窗口的元素到达
窗口的生命周期
1.时间驱动
2.事件驱动
窗口的驱动方式
相同时间间隔分配窗口,每个窗口之间没有数据重叠
滚动窗口
有固定的时间间隔,每个窗口之间是由时间重叠的,重叠时间是小于整个窗口的时间;也就说一个元素可以分配到多个窗口之中;
滑动窗口
没有固定的时间,通常是以一个事件进行窗口化
用户活跃的时候作为一个窗口时间,用户不活跃的时候作为活跃之间的间隙;
时间窗口
没有固定的时间,通常是以一个事件进行窗口化;
会话窗口
将所有的相同的key或者含有相同元素的放在一个窗口里面做计算;
全局窗口
count窗口
窗口分类
窗口
ProcessWindowFunction
AggregateFunction
ReduceFunction
区别
窗口函数
针对数据做不同的实际算计的函数,比如说聚合,分组,切割,关联;
算子
计算过程
flink
大数据
CORBA规范是一种工业标准,之后JavaEE的规范也参考了CORBA规范,这篇文章,我们看看CORBA规范都定义了哪些内容
CORBA规范
美团开发的sql优化工具
SQLAdvisor
一种关系型数据库
CouchBase
On-Line Transaction Processing联机事务处理过程(OLTP),也称为面向交易的处理过程,其基本特征是前台接收的用户数据可以立即传送到计算中心进行处理
特点:读多写少
OLTP应用
网络词汇
Tomcat组件
最小空闲连接线程数,用于提高系统处理性能
默认值为 10
minProcessors
能够处理的最大并发请求数量
默认值75;
maxProcessors
TCP三次握手结束后就绪的的最大允许连接数量;如果超过该连接数量,那么连接就会被放弃
默认100;
acceptCount
最大连接数
超过这个连接
MaxConnections
端口号,根据实际情况设置应用服务的端口号
port
应用层通讯协议
IO模型默认是BIO
可以设置成NIO
protocol
连接超时时间
connectionTimeOut
当用户以HTTP请求方式去请求HTTPS资源的时候;这个时候Tomcat就会重定向以该端口重新发送HTTPS请求
重定向的通讯端口
redirectPort
服务器以何种编码方式接收客户端的请求数据
UriEncoding
基础配置
最大线程个数,如果Executor标签配置了线程池;那么该配置就失效
maxThreads
子主题 2
子主题 3
线程配置
connector
属于非必须配置标签;该配置可以不用配置;
最大线程个数
实际处理请求的最大线程数量
允许客户端连接的最大线程数
默认值200
初始时创建的线程个数;随着后面的请求增多,线程也会慢慢增多
默认值4;
minSpareThreads
线程空闲时间;空闲的线程超过了这个时间,就会被销毁
默认值:1分钟
maxIdleTime
请求被执行前,最大排队数目
默认值为int最大值
maxQueueSize
线程池是否开启最小线程数量配置;也就是minSpareThreads配置是否生效
prestartminSpareThreads
线程池中线程优先级
默认值为5,值从1到10
threadPriority
线程池实现类,可以指定线程池实现类
如果没有指定实现类:默认是org.apache.catalina.core.StandardThreadExecutor
className
属性值
Executor线程池
connector是服务器负责与客户端建立连接的管理者;
线程池是连接器接受了客户端的请求,之后就会从线程池中拿一个线程去服务这些连接;
connector和线程池关系
server.xml配置文件
子主题 5
子主题 6
子主题 8
Tomcat核心参数说明
默认的工作模式,性能低下,没有经过任何优化
bio
比bio新能更高
nio
解决大量异步IO问题
apr
tomcat运行模式
降低相应时间
提高服务吞吐量
提高服务可用性
性能优化指标
具体情况具体分析
积少成多
优化原则
JCONSOLE
JMETER
性能优化工具
设置Tomcat有所运行的jvm的内存配置
Tomcat本省就是Java语言写的,运行在JVM之上,所以也可以从Tomcat本身所运行JVM上优化
设置JVM的垃圾回收参数
catalina.bat
Windows
catalina.sh
Linux
针对不同操作系统
内存优化
//NIO protocol="org.apache.coyote.http11.Http11NioProtocol" //NIO2 protocol="org.apache.coyote.http11.Http11Nio2Protocol"
设置成nio形式
protocol="org.apache.coyote.http11.Http11AprProtocol"
设置ARP
是否启用gzip压缩
compression
启用压缩后,输出的内容大小,默认2kb
compressionMinSize
压缩类型
compressableMimeType
connector优化
监听器优化
合理设置线程池参数
线程池优化
IO方式优化
大量的静态资源会占用内存空间,导致GC;
只处理动态资源
配置优化
使用APR组件
不建议优化
server
service组件
Engine
修改不同的io方式
设定自己的线程池大小
Connector连接器优化
自动部署,如果包有更新,就会自动部署
Host
Context
组件优化
为了提升性能,首先就要对代码进行动静分离,让 Tomcat 只负责 jsp 文件的解析工作。如采用 Apache 和 Tomcat 的整合方式,他们之间的连接方案有三种选择,JK、http_proxy 和 ajp_proxy。相对于 JK 的连接方式,后两种在配置上比较简单的,灵活性方面也一点都不逊色。但就稳定性而言不像JK 这样久经考验,所以建议采用 JK 的连接方式
工作方式选择
Tomcat优化方式
tomcat性能优化
tomcat
客户端先给自己NGINX服务发送请求,NGINX再把请求转发给目标服务器
正向代理
客户端发送的请求到服务器,是先经过服务端的代理服务器NGINX,然后再转发给实际提供服务的服务器
反向代理
1.正向代理:客户端和代理服务器在同一个网络中;反向代理:实际的服务器和代理服务器数据一个网络
2.正向代理,服务器是透明的,知道具体的服务器地址;反向代理,服务器是不透明的,不知道代理服务器请求的是具体哪台服务器
正反代理区别
代理
NGINX本身也是一个静态资源服务器,请求可以获取到NGINX上的静态资源,所以可以做静态资源服务器
动静分离
(默认)轮询算法:请求一台一台的给机器分发
根据IP地址做hash
权重算法
NGINX自带负载均衡算法
根据相应时间长短来分配,相应时间短的请求可以获取到更多的请求
根据URL做hash路由,特定的请求会路由到特定的服务器上
第三方插件负载均衡算法
负载均衡
功能
工作进程是单线程
单工作进程
工作进程是多线程
多工作进程
NGINX工作模式
1.接受用户请求异步,无需长连接
2.接受一定了的请求,再全部一次转发给相应的服务器,减少连接消耗;
3.接受数据和发送请求给具体服务器可以同时进行
工作特点
主进程是管理工作进程;
主进程
工作进程
nginx.conf的worker_ processes 可以配置工作进程个数
进程
NGINX工作方式
主进程会按照配置fork出相应个数的工作进程,工作进程至少会有一个;
多进程概述
1.每个工作进程都会有自己独立的连接池(连接池中最大连接个数就是worker_connections)
2.连接池中连接存储的空闲连接链表,需要用到连接的时候从链表中获取这些连接
工作进程连接池
最大连接数量=工作进程数*worker_connections
最大并发数量=进程数*worker_connections/2(因为NGINX是客户端和实际服务器的桥梁,一个请求会占用两个连接)
NGINX连接数说明
主进程和工作进程之间是单向通信,只有主进程给工作进程发送消息;工作进程是无法给主进程发送消息;
通信方式
工作进程的编号;工作进程的进程索引;文件描述符
通信类容
1.多个进程之间是相互独立,一个进程挂了不会影响其他进程工作;
2.进程绑定了CPU,减少CPU切换造成的时间消耗;
1.master进程接到命令,重新加载配置文件;
2.建立新的进程,再停掉所有的老进程;
3.老的进程在停掉之前会把已接受的请求处理完再退出;
3.热启动,修改配置,无需重启NGINX服务器
多进程好处
所有的工作进程都是从主进程中fork出来,所有的工作进程都会去监听相同的服务器;所有的工作进程在获取请求的时候都会有一个共享锁,只有一个进程会获取到这个请求;
多进程工作
多进程
1.启动的时候解析配置文件,监听对应的服务IP,端口;
2.在主进程中初始化对被监听机器的socket连接;
3.fork出多个子进程,用于处理客户端发送的请求(客户端也是需要经历三次握手才能与NGINX服务器建立连接);
4.子进程竞争到了请求之后,就开始对socket连接进行封装,设置处理函数,读写事件来与客户端进行数据交互;
5.NGINX服务器或者客户端主动关闭连接,一个连接就结束
处理请求过程
异步,时间驱动方式处理连接
处理请求特点
处理请求
线程池
Nginx的事件处理机制,也就是网络io方式都是多路复用
实现的形式都是epoll形式,和Redis,RocketMq,Dubbo的网络io方式是一样的;
epoll
nginx
服务器
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 流)
canal工作原理
binlog日志本身是顺序的。但是写入到MQ之后,就会有顺序性问题
设置canal的路由方式:1.单topic单分区,就是严格按照binlog日志一样的顺序性,性能比较差,也就是能够保证每一个库每一个表的数据都是不乱的。2多topic单分区,保证表级别顺序性,一张表,一个库所有的数据都写入到一个分区里面去。
mq顺序性问题
canal
1、分库分表,这个操作是开发来做的事情,目前框架是不能自动分库分表,这个框架只是帮助我们去管理这个
限制
以jar包的形式提供服务,无需外部部署和依赖,目前已经和springBoot做了整合
在框架中使用insert ,select语句的时候和当前使用的持久层框架是一样的,只不过表名是逻辑表名;执行的时候根据相关的配置,生成真实的sql去做查询,所以说select就会生成多条数据,然后把查询的结构及合并在一起;
使用方式
表关联的时候查询,如果两个表没有绑定关系,那么关联查询会有笛卡尔积;
注意事项
一般说是每一个库都加上这个公共表
sharding-jdbc去管理这个表,crud的操作会映射到每一个公共表中,所以说一个添加操作所有的表中都会有这个数据;
公共表的处理
读写分离:sharding-jdbc提供了一主多从的配置
读写分离处理
使用
1.sql在连接数据库之前在sharding-jdbc中需要做解析(把sql语句分解),
2.路由:这个逻辑sql语句,最终是需要在哪些数据库的节点上执行;
3.sql改写:逻辑的表名就会被改写成真实的表名;
1.内存限制模式:会根据需要操作的真实的表,创建对应个数的数据库连接;OLAP 面向效率操作
2.连接限制模式:不管需要操作多少个真实的表,都只会有一个数据库连接;如果表分在多个数据上,那么就会每一个数据库创建一个数据库连接;OLTP面向事物操作;
4.sql执行:分为两种模式,根据性能消耗,可做自动切换
1.内存归并:拿到所有的结果集,在内存中处理
2.流失归并:通过数据库的游标,一遍查询仪表做归并操作;
5.结果归并:把结果集合并处理成需要的
sharding-jdbc执行步骤
sharding-jdbc
使用C&C++语言开发
概述
1. 垂直分库
2. 水平分表
3. Proxy集群
4. 读高可用
5. 读写分离(master不参与读,支持一主多从,也就是会有多台从机
6. 读写分离(master参与读)
7. 写高可用
8. 读写随机
9. SQL检查
10. SQL统计【暂无文档】
11. 任务队列监控【暂无文档】
12. 连接池管理【暂无文档】
OneProxy主要功能
SQL白名单(防SQL注入)
IP白名单功
SQL防火墙软件
oneProxy的安全性
oneProxy
数据库中间件
子主题
示意图
mycat目前支持的数据库包括:MySQL、SQL Server、Oracle、DB2、PostgreSQL
mycat分库分表概述
按照不同的业务,分配不同的库
数据库的垂直拆分
相同的业务,将数据扩展到不同的库
水平拆分
水平+垂直拆分
读写分离
主要功能
1、拦截应用发出的sql语句请求
2、对sql语句做 分片分析,路由分析,读写分离分许,缓存分析
3、再把sql发往真实的数据库做sql操作
可以把mycat看做是看做是对数据库的操作租了一层代理拦截
工作原理
假设user_info做了分库操作,分了三个库:
场景
这个sql语句会向三个DB 发送数据请求,
mycat会把sql转成select * from db1.user_infoselect * from db2.user_infoselect * from db3.user_info
然后mycat 在把结果集封装在一起,返回给客户端;
1.select * from user_info
mycat 就会根据分片规则,定位到具体的数据库,然后直接只给一个数据库发送请求;
2.select * from db1.user_info where id =1 ;
往三个库里面发三个select请求 获取三对 六条结果
随机抽取一对返回给客户端
先发送三个select 每个都是最大的两条 然后返回给mycat 进行综合评选拿出最大的俩 返回给客户端
sql解析,也就是跳过前面5条,取6,7条数据;
场景演示
mycat 就会做sql改写操作
解决方式
关联的查询的表,需要把能够关联在一起的数据放在一个库里面,如果不能保证这点,那么就需要 放弃这两个表数据分库
6.任意表的join操作
工作场景
查询
1、Mycat不会立即把命令发送到DB节点上,等后续下发SQL时,Mycat从连接池获取非自动提交的连接去执行
2、Mycat会等待各个节点的返回结果,如果都执行成功,Mycat给该连接标识为 Prepare Ready 状态,如果有一个节点执行失败,则标识为 Rollback 状态
3、执行完成后Mycat等待前端发送 commit 或 rollback 命令。发送 commit 命令时,Mycat检测当前连接是否为 Prepare Ready 状态,若是,则将 commit 命令发送到各个DB节点
更新
工作场景过程说明
mycat线程模型
mycat
Cobar
TDDL
atlas
分库分表
每个字段都是原子,不可再分割
关系型数据库最基本要求
第一范式
满足第一范式的前提下,每一行的数据只能与其中一列相关,记录需要有唯一标记
第二范式
满足第二范式的前提下,每个字段都是直接与主键过关系,其他字段之间不能存在传递关系
第三范式
范式
数据存储
关系型数据基础概念
表结构文件
FRM文件
索引数据+表数据
IBD文件
文件结构
支持行锁,表锁
通过锁定索引记录来实现;锁定某一个索引记录
只用通过索引条件查询数据的时候,才会使用到
使用场景
作用
记录锁
通过索引记录来实现
对一定范围内数据进行加锁,即使这数据不存在也是会加锁
Select * from emp where empid > 100 for update;会对empid大于100的都加上间隙锁,即使记录不存在也会添加
防止幻读
间隙锁
相当于一个记录锁+间隙锁
避免幻读
Next-key锁
行锁
给数据行加行级锁之前需要取道该表的意向共享锁
意向共享锁
给数据行家排他锁之前需要取到该表的意向排他锁
意向排他锁
表锁
锁
记录后事物执行之后的状态,用来记录未写入 datafile 的已成功的事物更新的数据;
redolog日志类容
在MySQL宕机时候,还有数据没有写入磁盘,可以通过重做日志重做,从而达到数据的持久化
日志作用
事物开始的时候就会有日志生成,随着事物的进行的过程中,日志逐渐写入redolog中
日志生成
当某个事物对应的数据写入 datafile中后,redolog的使命已经完成了,重做日志的空间就会被重用
日志清理
redolog(innerDB引擎独有)
undolog生成时间
记录信息修改之前和修改之后的数据
undolog日志内容
根据undolog日志做数据库的逆向操作,原来的insert操作变成delete操作,delete变成insert,update变成逆向update
undolog日志回滚操作
undolog
日志
innerDB
存储的是表结构信息
frm文件
存储的是数据行记录信息
MYD文件
存储索引对应的数据信息
MYI文件
根据索引
myisam工作流程
支持表锁,不支持行锁
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支持表数据压缩,压缩后支持只读,修改操作需要加压才能操作;
是否支持表压缩
innerDB和Myisam区别
在内存中存在表数据,数据库重启之后,该引擎对应的数据就会消失
默认是使用hash索引
memory
存储引擎允许将一组使用MyISAM存储引擎的并且表结构相同的数据表合并为一个表,方便了数据的查询
Merge
其他存储引擎包括CSV(引用由逗号隔开的用作数据库表的文件)
csv
为大量很少引用的历史、归档、或安全审计信息的存储和检索提供了完美的解决方案。
ARCHIVE
用于临时禁止对数据库的应用程序输入
BLACKHOLE
能够将多个分离的MySQL服务器链接起来,从多个物理服务器创建一个逻辑数据库。十分适合于分布式环境或数据集市环境。
FEDERATED
可为快速创建定制的插件式存储引擎提供帮助
EXAMPLE
可替代InnoDB的事务引擎,支持COMMIT、ROLLBACK和其他事务特性。
BDB
MySQL的簇式数据库引擎,尤其适合于具有高性能查找要求的应用程序,这类查找需求还要求具有最高的正常工作时间和可用性
Cluster/NDB
数据库引擎
根据主键排序做的索引,特殊的唯一索引
主键索引
强制让表中某个字段值不能重复
唯一索引
根据某一列字段建立的索引
普通单列索引
单列索引
把建立索引的字段值全部兼容在一起组成的索引
组合索引
使用方式分类
索引的顺序和数据存储的顺序是保持一致
主键索引就是数据聚簇索引
聚簇索引(一级索引)
索引的存储顺序和数据的存储顺序不是一致
非主键索引全部都是属于聚簇索引
非聚簇索引(二级索引)
聚簇非聚簇
多条数据可以对应一条索引数据
非主键索引和有可能就是稀疏索引
稀疏索引
数据和索引是一一对应的
主键索引就是密集索引
密集索引
稀疏密集
索引分类
数据库引擎不同B+树存储的数据不完全相同
innerDB的的B+树叶子节点存储的是全部的数据或者是主键id;
myisam的B+树叶子节点存储的时候该条数据地址值(不管是主键索引还是其他索引)
B+树的数据存储结构
B+树的非叶子节点存储的是节点之间的前后指向关系,并不存储实际的数据值;B树的只要是节点不管是叶子节点还是非叶子节点都会存储数据。
B+树的节点存储兄弟节点的地址值,可以直接指向兄弟节点;而B树是不可以的。
在相同数据层级,B+树能够存储更多的数据;而B树存储的数据少很多;
B+树和B树区别
myisam和innerDB索引都是使用B+树
B+树
基于hash算法的散列索引
查询单条数据查询时间的复杂度为01;
范围查询速度比较低
特点
memory引擎默认使用该中索引
hash索引
MySQL 5.6 以前的版本,只有 MyISAM 存储引擎支持全文索引;
只有字段的数据类型为 char、varchar、text 及其系列才可以建全文索引。
全文索引
创建和维护索引都需要耗费时间和磁盘空间
修改语句操作的时候,需要对索引进行重新构建
索引开销
查询的时候根据组合索引去查询,从索引中获取到的数据并未覆盖所需要 查询的全部字段,所以就需要那到索引中的id再回到表中去查询需要的字段,这就是回表
回表查询
查询的时候,先根据组合索引中最左索引查询,拿到这些查询之后的数据的主键,再回表查询出整条数据,再从整条数据从按照其他条件做筛选;
非索引下推
如果查询条件中包含了组合索引,那么就会优先使用组合索引查询,查询满足组合索引中最左索引的值,从这些筛选一遍的数据中再过滤出其他符合条件的数据,不用再次回表查询;
索引下推
组合索引中,如果查询条件中并没有带上组合索引中最左边的索引,那么这个查询是不会先去查询组合索引
最左匹配原则
查询的是主键索引字段,也就是查询的数据本身就是索引,直接从索引中拿数据,不需要再去根据主键回表查询其他数据
覆盖索引
就是普通的select
一次性非锁定读
在select 语句后面中加 for update 或者是 lock in share mode
一次性锁定读
索引相关概念
查询需走索引
索引
数据库的存储形式,最常见的就是将每一行的数据组织称为一条记录;通常每页会存储一条或者多条记录;
字段可以是定长也可以是不定长的;所以记录也存在两种方式:定长记录,不定长记录
记录的存储格式
记录头包含了记录的整体信息,指向表的指针,记录长度,时间戳,锁标记,字段数,各字段类型等等
记录存储示意图
记录的存储形式
定长记录在页中的存储比不定长记录的存储结构稍微简单一些,只有页头,记录
页的类型,记录数,记录长度,与前后也的链接等信息;
页头存储数据
由于所有的记录都是定长的,所以是可以在页中进行连续存储,各记录
可以通过记录的偏移量(每条记录的存储空间都是一样的) X 记录次序;
每页的空间不一定正好用完,最后的不够一条记录的空间将会被空余出来;
定长记录存储示意图
定长记录
页头,目录区,剩余空间,记录区
不定长记录主要有四个部分
目录区主要存放的是每条记录的偏移量(记录每条都是不定长的,需要去记录每条记录所占空间的大小)
记录区与定长记录的存储是不一样的,不定长记录是从页末开始往前存储的;
每条记录的获取可以通过目录区里面每条记录的偏移量和次序计算出 总的偏移量,获取目标记录;
不定长存储示意图
不定长记录
记录在页面的寻址可以通过 偏移量和次序来计算出来
整个寻址包括:页寻址+记录寻址;
记录寻址
表数据存储
不会完全的冗余该条数据的所有数据,只会冗余对应的索引字段的数据
非叶子节点
b+树中的叶子节点存储的数据有索引本身已经排好序的数据,以及索引对应的主键
根据非主键索引查询得到主键;再根据主键去做查询;
非主键索引查询
非主键索引存储
是按照主键排序 B+树的数据结构中,叶子节点存储的具体的数据(该表有几个字段,这个几个字段都会被一起来存储),中间节点存储的时候索引的相关数据
主键索引存储
存储索引数据
存储兄弟节点的地址信息
叶子节点
一个节点可以存储多个元素;b+的一个节点的数据基本都是在一页中
1.B+tree 的兄弟叶子节点上会存储对方节点的磁盘地址值;
2.B+tree只有叶子节点上才会存储列的数据;
和B树区别
节点默认的存储空间大小是16kB
主键索引的非叶子节点都是存储的是下一层元素的指针以及当前主键的值,指针的大小是6个字节,主键的大小如果是bigInt类型就是8个字节
每一个非叶子节点可以存储16*1000/(6+8) = 1170 单位
三层B+tree最多可以存储1170*1170*16 也就是2100多万的数据
索引存储大小
B+树数据存储
关系型数据库基础
全局锁就是对整个数据库实例加锁
MySQL提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,其他线程的数据更新(数据的增删改)、数据定义(包括建表、修改表结构等)和更新类事务的提交会被阻塞
整库每个表都select出来存成文本
全库逻辑备份
库锁
表锁是由MySQL本身来实现
意向锁,锁是由innerDB自动添加,无需用户手动添加
表锁具体实现
页锁
永远是去锁定索引记录的(主键,唯一索引);记录锁(Record Lock)是针对索引记录(index record)的锁定
也是通过锁定索引记录的(主键,唯一索引);间隙锁(Gap Lock)锁定的是索引记录之间的间隙、第一个索引之前的间隙或者最后一个索引之后的间隙
作用是防止其他事物做插入操作
Next-key 锁(Next-key Lock)相当于一个索引记录锁加上该记录之前的一个间隙锁。
行锁(innerDB实现)
innerDB的行锁是在索引上加锁,如果一张表没有索引,那么加的是表锁; innerDB默认加的就是行锁
innerDB如果没有主键,那么还会有一个rowId的唯一表示;
innerDB行锁实现
(粒度越大,并发性越差,开销越少;粒度越小,开销越大,并发越好)
按照粒度划分
一次性非锁定读:就是普通的select
在select 语句后面中加 for update 或者是 lock in share mode
锁的分类
乐观锁,悲观锁都是在并发事物的一种控制方式,是一种思想,不是实实在在的锁
行锁,表锁也并非实实在在的锁,只是锁的粒度来划分的锁
锁分类说明
关系型数据库的一种加锁原则;把锁的操作划分为两个阶段,加锁阶段,解锁阶段
加锁阶段,解锁阶段互不相交,加锁阶段,只加锁,不解锁;解锁阶段,只解锁,不加锁
实际过程
二阶段锁
1.事物阻塞,等待
事物各自持有对方想要的锁,并且都在相互等待对方释放锁
1.一次封锁发,事物一次就占据所有字资源,要么全部封锁,要么全不封锁
2.顺序加锁,定义锁的加锁顺序,只能这么来
3.时间戳
2.死锁
加锁导致问题
当并发的访问,修改某一张表的时候,会触发表锁;
引发表锁的原因
事物中发生多件事情,多件事情要么都成功,要么都失败
原子性
一种状态到另外一种状态必须保证数据逻辑上的一致
A给B转1000块钱,A的账户扣了1000块,B的账户多了1000块
一致性
当前事物不受其他事物影响
隔离性
数据能够被持久化的存储在磁盘上
事物提交之后,该事物对应的修改数据都会被存储到磁盘上
持久性
基础概念
因为需要在失败的时候做数据回滚操作,所以原子性是通过undolog来实现的
逆向记录mysql执行的sql语句;insert 语句生成对应的delete语句,delete语句生成insert语句,update更新语句生成更新回来的语句;
undo log
redolog来实现
先写日志,再写磁盘
每次操作数据库之前都会把更新的操作记录在redo log中
会记录事物开启之后对数据做的修改
redo log 记录的时候新数据的备份数据;数据都是先记录在redo log中做持久化,然后再记录在内存中,内存积累到一定的数据量的时候才会写进磁盘。假设这个时候断点了,那么内存中的sql语句都找不到了,那么这个时候,redo log的日志就可以用来恢复数据了这个数据已经持久化到了磁盘了,
锁,mvcc来控制
事物之间的写写操作,通过锁来实现,事物之间的读写操作通过mvcc
写-写操作隔离性,通过锁来实现
读-写操作隔离性,通过MVCC来实现
ACID实现
ACID
A事物可以读取B事物没有事物提交的数据
同时开启AB两个事物,A事物可以看到B事物没有做事物提交的数据
A事物读取B事物更新的数据,B又做了回滚,A事物读到的是脏数据;
脏读
读未提交
A事物反复读取B事物事物更前之前和之后的数据,读取的数据不一致;
一个事务从开始到提交前所做的任何改变都是不可见的,事务只能读取到已经提交的事务所做的改变。
读已提交
不可重复读
A事物反复读取B事物事物更前之前和之后的数据,读取的数据数据依旧是第一次读取的数据;
mysql默认的隔离级别就是这个
可重复读
同开启AB事物,A事物没有提交,B事物无法对A事物已经操作的过的任何其他数据进行操作
序列化SERIALIZABLE
事物隔离级别
多个事物同时执行
并行
事物必须是一个一个的去执行,不能并发的去执行
串行
事物调度
每次访问数据的时候,都认为是查询操作,其他事物也能访问当前事物的数据;造成的数据不一致性,采取的方式是修改数据之后,对修改的数据进行回滚操作
概念
1.读操作过多的情况下,能够显著提高效率
2.采取时候回滚操作来来解决事物冲突问题
通过在需要更新的表中设立相关的数据版本号来做操作
具体实现
可在分布式场景下控制多个服务对相同数据的修改是线性执行的;
数据库的版本锁
MySQL乐观锁使用场景
乐观控制
每次访问数据的时候,都会对数据进行加锁操作
1.采取的是提前预防冲突
2.严格的控制其他事物对当前事物的访问,避免大量的数据回滚
3.其他事物,在某些情况下还是可以访问当前数据
通过数据库本身的锁机制来实现悲观控制
单体服务的时候就实现对并发事物的悲观控制
mysql悲观锁使用场景
悲观控制
对并发(交叉)事物的控制的方式;数据本身对并发事物的控制都是悲观控制
并发事物控制
某个时间所触发的操作;一种特殊的存储过程;
主要用作:实现主键和外键的一致性完整性;
触发器
就是被用户定义的 sql语句的集合。(语句一般都是多条数据)
存储过程
1.可以指定表开始的主键的值;
2.主键自增可以设置相应的长度;
序列就是表对应的主键;
序列
就是基于基表的一种逻辑表或者虚拟表。
视图
数据库对象
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 ,三个隐式字段
MVCC实现原理
MVCC版本控制
id相同,从上到下顺序执行
id不同,id值越大执行优先级越高,越先被执行
id(sql执行顺序)
简单的单表select查询
SIMPLE
子主题 1
PRIMARY
表示select或者是where中含有子查询
SUBQUERY
表示where后面的in条件有子查询
MATERIALIZED
SUBQUERY/MATERIALIZED
被关联查询的语句;也就是关联查询的union中的第二个语句或者是union后面的select语句
UNION
表示关联查询的结果
UNION RESULT
select_type查询类型
直接显示表明
有N表产生的中间结果集维表
<subqueryN>
table(查询涉及到的表)
查询的是系统表
system
单表查询直接命中主键查询
explain select * from user where id=1;
demo
const
关联查询用到主键索引做为关联字段
eq_ref
关联查询用到了非主键索引作为关联字段
ref
全文索引检索,要注意,全文索引的优先级很高,若全文索引和普通索引同时存在时,mysql不管代价,优先选择使用全文索引
fulltext
与ref方法类似,只是增加了null值的比较。实际用的不多
ref_or_null
用于where中的in形式子查询,子查询返回不重复值唯一值
unique_subquery
用于in形式子查询使用到了辅助索引或者in常数列表,子查询可能返回重复值,可以使用索引将子查询去重
index_subquery
在索引上的范围查询
range
表示查询使用了两个以上的索引,最后取交集或者并集
常见and ,or的条件使用了不同的索引
index_merge
explain count (*) from user;
索引全表扫描,把索引从头到尾扫一遍,常见于使用索引列就可以处理不需要读取数据文件的查询、可以使用索引排序或者分组的查询
index
全表扫描
ALL
type (重点,查询性能指标)
查询的结果只的好坏顺序system > const > eq_ref > ref > range > index > ALL
查询过程中有可能用到的索引
possible_key
实际使用的索引,如果为 NULL ,则没有使用索引
key
展示索引字段的实际长度
key_len
显示该表的索引字段关联了哪个表的哪个字段
估算查询所需要的记录需要读取的行数
rows
返回的结果集行数占读取行数的百分比,越大越好
filtered
展示其他重要信息
extra
执行计划详解
被用户定义的 sql语句的集合
基于基表的一种逻辑表或者虚拟表
其他组件
管理用户对数据库的连接
用户权限管理
连接器
根据语句对sql语句中能够做优化的SQL自动做优化
基于优化成本来做优化(主流的数据库都是这样)
CBO
基于规则,SQL语句经过分析器之后,会有多种执行方式,程序会判断选择一个效率最高的SQL
RBO
优化器分类
优化器
包括词法分析,语法分析
分析器
mysql缓存查询到的数据永远是最新的数据,如果中件表有发生变化,则该缓存相关的数据就会被清空
缓存命中率比较低,最理想的情况下命中率最高13%
查询必须是完全相同的(逐字节相同)才能被认为是相同的
同样的字符串由于其他原因也可能会被认为是不一样的
缓存工作
query_cache_type系统变量的值是ON或DEMAND,查询结果被缓存
前提是缓存是出于开启的状态
指定从缓存中查询
指定不从缓存中查询
缓存相关查询
表示当前是否有使用缓存
have_query_cache
如果设置为0,则认为是禁用缓存
查询缓存大小
query_cache_size
默认值1MB
被缓存的查询结果最大值
query_cache_limit
默认值是4KB
系统变量给查询缓存分配最小值
query_cache_min_res_unit
缓存相关参数
8.0之后就移除缓存模块
1.缓存命中率低
2.内存空间宝贵
移除原因
缓存模块
SQL语句真正的执行者
执行器
五大组件
server层
主要负责数据的读取和存储,采用可替代式插件架构,支持各种数据库引擎。
存储引擎
MySQL主要分为server层和存储引擎层
MYSQL架构
mysql基础
存储库相关信息
schemata
库名
表名
数据库引擎类型
数据库版本
表行数
平均每行数据长度
数据长度
最大数据长度
索引数据大小
存储信息
tables表
字段相关信息
columns
索引相关信息
statistics
用户权限数据存储
user_privileges
库权限信息
schema_privilege
表权限信息
table_privileges
字符集信息
character_sets
collations
mysql元数据信息表
操作系统的页 4kb
默认的页的大小是:16kb 这个是可以修改
1、页是innerDB磁盘存储中最小的一个单位,每次查询至少都会查询出一页数据
如果一条一条的拿数据,io的次数会几何倍数的增加
1、可以降低磁盘的io次数
目的
2、查询的时候,会把某条数据所在的页对应的数据都拿出来,然后再去拿具体的数据
哪怕是要操作一条数据,也是需要将包含这条数据的页全部读到内存当前去进行处理,所以说,操作一条和操作一页的数据的时间差可以忽略不计,时间的消耗主要磁盘和内存之间的数据IO操作
特别说明
查询规则
mysql页
中间,可能是经历过多个节点的读取才能最终定位到具体的索引数据在哪个叶子节点上,所以中间过程可能有多次io
1、先从B+树的根节点开始,寻找定位索引数据拿到数据
2、如果这个时候刚好根据索引就能拿到全部所需要查询的数据,那么就直接返回数据
1、拿到索引数据中的主键,根据主键去从主键索引树中拿到数据,这个过程也会有多次io
3、但是如果这个时候根据索引没有拿到全部所需要的数据,那么进行回表操作
索引查询流程
磁盘在工作的时候,以恒定的速度旋转,当磁盘有读或者写的请求的时候,刺头必须移动到所请求的磁道上,等待所要读取的扇区旋转到刺头下面;然后才能做读写操作
1.寻道时间:磁头移动到指定磁道的时间;
2.旋转时间:磁头等待指定扇区移动到磁头下方时间;
3.传输时间:读写的时间片;
由于磁盘始终是告诉旋转的,移动磁头到磁道上,需要启动,停止的操作,所以上述的操作,1的时间是最长的,2,3的时间都比较短;
磁盘读写操作
磁盘读写
一个磁盘有两次盘面
盘面个数X磁道个数X每个磁道的扇区个数X每个扇区储存的空间大小
磁盘的存储大小计算公式
磁盘的结构
mysql磁盘读写
1.主机每次完成实事物提交,完成数据更新之前,都会把变更的数据记录在binlog日志中
2.从机开启一个IO线程去从主机的binlog日志中读取数据,记录到自己的中继日志中;
3.从机开启线程,解析中继日志的事件,并执行,来做数据主从复制
主从复制过程
主从复制;主从复制就是为了读写分离
数据库基于时间还原数据
读写分离;读写分离的前提就是主从复制
缓存一致性;模拟成从机,拉取binlog日志异步更新到缓存中的数据;
数据多备份
binlog日志作用
以mysql事件形式记录的是实际执行的sql语句
statment模式
5.0之前
以MySQL事件形式记录字段前后变化的值
row模式
5.0之后
binlog中主要记录的都是都是事件,mysql定义了30多种事件
binlog日志格式
日志文件的大小,默认1G
max_binlog_size
日志保留事件,默认是0,永久保留
expire_logs_days
binlog日志配置
记录所有的更新操作
binlog日志(二进制日志)
记录建立的客户端连接和执行的语句。
只记录查询相关的日志
查询日志
记录更改数据的语句。不赞成使用该日志。
5.1版本之前的日志,5.1版本之后被binlog日志替代
更新日志
记录mysql启动关闭,以及运行过程中发生的错误信息
记录内容
错误日志默认是关闭
设置
错误日志
记录的是查询时间超过10秒(默认值)的sql语句
慢查询日志默认是关闭
慢查询日志
场景用在主从复制中从服务器读取到主服务器的二进制日志存储起来就是中继日志,日志格式和二进制日志格式相同,都可以使用相同的binlog解析程序解析
中继日志
压缩MyISAM表以产生更小的只读表
myisampack
交互式输入SQL语句或从文件以批处理模式执行它们的命令行工具
mysql
检查访问主机名、用户名和数据库组合的权限的脚本
mysqlaccess
执行管理操作的客户程序,例如创建或删除数据库,重载授权表,将表刷新到硬盘上,以及重新打开日志文件
mysqladmin
从二进制日志读取语句的工具。在二进制日志文件中包含的执行过的语句的日志可用来帮助从崩溃中恢复
mysqlbinlog
检查、修复、分析以及优化表的表维护客户程序
mysqlcheck
将MySQL数据库转储到一个文件(例如SQL语句或tab分隔符文本文件)的客户程序
mysqldump
当服务器在运行时,快速备份MyISAM或ISAM表的工具
mysqlhotcopy
使用LOAD DATA INFILE将文本文件导入相关表的客户程序
mysql import
显示数据库、表、列以及索引相关信息的客户程序
mysqlshow
显示系统或MySQL错误代码含义的工具
perror
更改文件中或标准输入中的字符串的实用工具
replace
mysql客户端脚本和使用工具
Mysql事件
ReadView
从服务器本身也可以当做主服务器
主从复制中,从机又是另外一台的主机;
mysql链式复制
mysql异样工作场景
性能模式
系统模式
mysql模式
MYSQL其他知识点
1、对原始表加写锁
2、按照原始表和执行语句的定义,重新定义一个空的临时表
3、对临时表进行添加索引(如果有
4、再将原始表中的数据逐条Copy到临时表中
5、当原始表中的所有记录都被Copy临时表后,将原始表进行删除。再将临时表命名为原始表表名
5.6之前版本
2、按照原始表和执行语句的定义,重新定义一个空的临时表。并申请rowlog的空间
3、拷贝原表数据到临时表,此时的表数据修改操作(增删改)都会存放在rowlog中。此时该表客户端可以进行操作的
4、原始表数据全部拷贝完成后,会将rowlog中的改动全部同步到临时表,这个过程客户端是不能操作的
5、当原始表中的所有记录都被Copy临时表,并且Copy期间客户端的所有增删改操作都同步到临时表。再将临时表命名为原始表表名
5.6版本以之后版本
mysql添加表字段会不会锁表
肯定是会的
前期设计的时候想好设计表的索引
myslq添加索引会不会锁表
1、从机读取主机binlog日志的线程只有一个,如果主机有大量更新操作。从机是无法快速将这些binlog读取到从机
2、从机如果有执行慢的sql,也会导致无法快速处理读取到的binlog日志。
以上两点导致主从不一致的原因
1、延迟原因
1、将从机的binlog日志取消,提高从机sql执行效率
2、将主从服务器放在同一个网络下,减少网络延迟
3、具体的硬件优化
4、调参
2、解决方案
mysql主从同步延迟解决方案
1.在经常当做查询条件的字段上设置索引
查询尽量覆盖到索引
2.前模糊查询
3.查询语句中在索引字段上做函数操作
4.关联字段虽然都是索引字段,如果数据类型不一致,走不了索引
5.where后面不带有效查询条件
6.建立联合索引,查询的时候不满足最左原则;
7.查询中对索引字段做运算操作
未覆盖到索引情况
在字符串类型的字段上建立索引,需要指定索引的长度,一般10个就够了;
索引长度限制
1、必须要有主键索引
表的关联字段建立索引的时候确保两个表的数据类型是一样的
2、在经常做为查询条件,排序,分组,关联的字段上建立索引;
3、索引建立的字段应该是区分度非常高的字段;
索引建立字段选取
1、索引的数量不是越多越好,多了会增加索引构建维护成本;
2、不再使用或者使用非常少的索引需要删除索引
3、新建单列索引的时候,考虑和其他字段一起建立联合索引,减少索引个数
索引数量
索引建立原则
查询需要覆盖到索引
1.sql调优
1、表的字段类型,字段长度合理
2、创建高性能的索引
2.调整库,表结构
mysqld --verbose --help命令生成所有mysqld选项和可配置变量的列表
查询服务器默认缓存区大小
查看当前运行的mysql服务器实际运行的值
SHOW VARIABLES;
运行服务器的统计和状态指标
SHOW STATUS;
mysqladmin variables
mysqladmin extended-status
系统变量和状态信息
相关操作命令
MYISAM引擎下面的索引缓存所占内存大小
key_buffer_size
mysql同时打开表的数量
table_cache
线程的缓冲区,注意不是线程的栈内存;
对GROUP BY或ORDER BY读取数据的时候会暂存读取的数据,
read_rnd_buffer_size
默认151
可以适当设置大一些
max_connections
线程池线程大小
thread_cache_size
默认为2MB
sort_buffer_size
为排序或者分组的线程设置的缓冲区大小,可以提高排序分组的效率
排序缓冲区
默认为8Mb
join_buffer_size
为每个链接做联合查询操作的时候缓冲区的大小,提高关联查询效率
关联查询缓冲区
默认64k
read_buffer_size
每个线程扫描每一个表的锁分配的缓冲区大小
表的缓冲区
查询相关
可以缓存表数据还有索引数据
innerDB引擎下表和索引的最大缓存
innodb_buffer_pool_size
innerDB日志刷盘策略
innodb_flush_log_at_trx_commit
innerDB的失误日志缓冲区
innodb_log_buffer_size
innerDB相关
优化核心参数
3.系统配置优化
单个磁盘每秒大约1000次搜索
将数据分布在多个磁盘上,提高单位时间内可以并行搜索磁盘次数
提高搜索效率
提高读写吞吐量
数据分散到多个磁盘
提高cpu频率
提高cpu缓存大小
4.硬件优化
概述:以上四个方向,效果最好的是SQL调优,以此往下效果越来越差,成本越来越高
调优方向
select* from test_news limit 10000,10;
查询每页是10条,查询低10000页的数据;这种查询完全就是没有用到索引;需要扫描到10000条数据,再开始查询
原始查询方式
2.拿到查询出来的id,获取到后面10个id,再用这10个id直接可以直接回表拿到所有的数据;
解析
优化方式1
相当于自关联的方式获取到相应的id;
再通过id回表获取所有的数据
优化方式2
limit优化
分组取前几条
having
sql优化实战
SQLAdvisor系统
SQL自动优化
MySQL调优
mysql的主从复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新、删除等等)
并且主服务器开启了二进制日志
实现原理
复制过程中有三个线程在执行任务:一个线程在主服务器,负责将二进制日志的内容发送到从服务器(binlog dump线程);
主从复制开始;从服务器上会创建一个io线程,连接主线程并记录二进制日志中的数据到中继日志;中间需要识别binlog dump线程 以获取二进制日志
从服务器会开启一个sql线程(第三个线程),从中继日志中读取日志,并执行日志,以同步数据;
实现过程
1、主服务器开启了二进制日志
1、二进制日志格式不一样
2、字符集,函数,时区处理不一样
由于历史原因:
2、主从服务器的mysql版本之间是可以相互兼容
3、主服务器上设置了相应的从服务器
主从复制条件
假设从库插入的时候异常
1、主从数据库数据一致性问题
2、主从复制的延迟
存在问题
降低数据库读写压力
写操作比较耗时,读操作时间很短;
读写分离,解决了数据库写入时候,影响查询的效率;通过读写分离提供系统的高并发
主从复制解决问题
mysql主从复制
left join 做关联,以左表为主表,去右表当中匹配,如果左表中的数据未能匹配到右表中的数据,结果集中依旧会将左表的数据展示出来,未能匹配到的字段全部为空
right join 做关联,以右表为主表,去左表当中匹配,如果右表中的数据未能匹配到左表中的数据,结果集中依旧会将右表的数据展示出来,未能匹配到的字段全部为空
inner join 做关联,最明显就是左右两个表中只有关联上的数据才会展示出来,没有关联上的数据不会展示出来;
原始数据
.inner join
.left join
.right join
full join
关联语句
SQL语句
mysql实战问题
https://github.com/cookieY/Yearning
项目地址
https://guide.yearning.io/
项目简介
一款可满足大部分公司 SQL 审核需求的 web 端可视化 SQL 审核平台。在实现常规的 sql 审核功能外还添加了诸如数据查询等一系列便捷的功能
自动化SQL语句审核,可对SQL进行自动检测并执行。
DDL/DML语句执行后自动生成回滚语句
审核/查询 审计功能
支持LDAP登录/钉钉及邮件消息推送
支持自定义审核工作流
支持细粒度权限分配
主要功能:
颜值高,使用流畅。
部分审核规则可自定义
部署简单,依赖项少
功能丰富,既能审核执行SQL又能查询
作者在持续维护,有社区支持
优点
Yearning
https://github.com/hhyo/Archery
https://archerydms.com/
使用文档
Archery 定位于 SQL 审核查询平台,旨在提升 DBA 的工作效率,支持多数据库的 SQL 上线和查询,同时支持丰富的 MySQL 运维功能,所有功能都兼容手机端操作
集成 SQL 查询、审核、执行、备份
权限区分明确,审核执行分离
SQL 工单自动审批、高危语句驳回
快速上线其他实例
慢日志管理、SQL 优化等运维功能
支持会话管理及参数配置
可集成其他工具插件
除 MySQL 外,还支持多种主流数据库
功能更加丰富,真正做到一平台多用
具备 SQL 优化、慢日志管理等运维功能,对 DBA 更加友好
基于 Python 及 Django,利于二次开发改造
Archery
Yearning 只适用于 MySQL ,部署简单,功能清晰明了,上手容易
Archery 支持多种数据库,部署稍微复杂些,功能更加丰富,支持诸多高级运维功能
Yearning与Archery比较
SQL 审核 web 工具
1、binlog 解析工具在 GitHub 上可以搜索到许多
2、利用 binlog 解析工具我们可以清楚看到数据库执行过的历史内容,并且可以得到反向内容可用于回滚
https://github.com/Meituan-Dianping/MyFlash
MyFlash 是由美团点评公司技术工程部开发维护的一个回滚 DML 操作的工具
解析 binlog ,回滚各类 DML 语句
提供原生的基于库、表、SQL类型、位置、时间等多种过滤方式
支持 MySQL 多个版本,支持 GTID 格式
MyFlash
https://github.com/danfengcao/binlog2sql
基于 Python 开发,源码入门简单
可以解析出原始 SQL 、回滚 SQL 、去除主键的 INSERT SQL 等
数据快速回滚(闪回)
主从切换后新 master 丢数据的修复
binlog2sql
binlog 解析工具
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:校验主从复制一致性
几款常用工具的功能
Percona Toolkit 工具包
mysql工具
pg
关系型数据库
关系型数据库的一行数据,在mongondb中标示就是需要一个json对象来标示
{\"site\":\"www.runoob.com\"}
1.文档
就是把不同的文档类型的数据集中在一起就是集合
{\"site\":\"www.baidu.com\"}{\"site\":\"www.google.com\
2.集合
存储的数据类型:
字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的
String
整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位
Integer
布尔值。用于存储布尔值(真/假)
Boolean
双精度浮点值。用于存储浮点值。
Double
将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比
Min/Max keys
用于将数组或列表或多个值存储为一个键。
Array
时间戳。记录文档修改或添加的具体时间。
Timestamp
用于内嵌文档。
Object
用于创建空值。
Null
符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。
Symbol
日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息
Date
对象 ID。用于创建文档的 ID。
Object ID
二进制数据。用于存储二进制数据。
Binary Data
代码类型。用于在文档中存储 JavaScript 代码。
Code
正则表达式类型。用于存储正则表达式。
Regular expression
基本数据类型
id user_name email age city1 tom tom@qq.com 23 wuhan2 jerry jerry@qq.com 24 beijing
关系型数据库的两条数据
字段还可以再分割
{\"_id\":ObjectId(\"13123123\
关系型数据库的数据转成mongondb数据库的数据格式形式如下
对比mysql
只存在单个集合的事务,不存在多个集合一起的事务;
多个集合的事物处理,就需要自己手动做一个事物的回滚;
事务
mongonDB在任意时刻,对某一个集合的操作只能是一个写操作或者是多个读操作
主要就是内存中的操作优先于在硬盘上的操作
锁策略:1.数据库知道那个数据(文档)是在内存当中的,也就相应的知道了那些数据是在硬盘上的,机制就是让不在内存中的操作,让步于其他在内存中的操作;也就是当同时又两个操作,一个是数据在内存中,一个是数据在硬盘上, 这个时候会优先去执行数据在内存中的那个,直到数据加载进内存中的,才有可能去执行这个操作,原因就是操作执行数据,先需要从硬盘上加载数据,读取到内存之中。在内存中操作数据。2.就是写操作(增,删,改)比较耗时,查询操作时间比较短,当有写操作占用了锁,并且这个时候检测到有一个都操作等待写操作释放锁,这个时候写操作就会直接释放锁,让读操作先执行。
并发性
并发性,原子性,隔离性
mongonDB对单个文档内存的最大限制16M
mongonDB
非关系型数据库
字段不会被分词
keyword
字段会被分词
text
字符串
数据类型
number_of_replicas 是数据备份数,如果只有一台机器,设置为0
number_of_shards 是数据分片数,默认为5,有时候设置为3
系统参数说明
条件不会被分词,表中字段也不会被分词;
需要条件和表中字段数据完全匹配才会有结果
条件不会被分词,表中字段会被分词;
查询条件必须和是表中字段分词后的某一个字段相同才会获取到数据;
term
条件会被分词,表中字段不会被分词
条件和表中字段完全一致,才会查询到数据
条件会被分词,表中字段也会被分词
条件的分词和表中字段的分词有匹配的就会被查询出来
match
GET /test/student/_search{ \"query\": { \"match_phrase\": { \"description\": \"He is\" } }}
会去匹配出包含He is的数据,并且顺序要是一致,且中间没有其他词汇
短语查询-词组查询
条件和表中数据一致,才会查询出数据
必须要条件的词组顺序和表中字段该词组顺序一致才会查询出来
match_phrase
query_string
multi_match
ES查询语句
POST idx_hippo_sku_statistics_index/_update_by_query{ \"script\": { \"source\": \"ctx._source['distributionChannel']=1\
条件更新
PUT /idx_product-fat/doc/10006145{\"minPrice\
全部覆盖更新
POST idx_product-fat/doc/100101/_update { \"doc\" : { \"minPrice\
根据id更新某些字段
update语句
DELETE idx_hippo_sku_statistics_index
删除某一个索引
Delete /idx_product-fat/doc/10006306
根据id删除某条数据
POST idx_hippo_sku_statistics_index/_delete_by_query{ \"query\": { \"term\": { \"skuId\": { \"value\": 10005956 } }}}
条件删除
delete语句
POST idx_hippo_shop_sku_index/shop_sku_index/{\"title\":\"java架构师\
新增数据-不指定id
POST idx_hippo_shop_sku_index/shop_sku_index/3{\"title\":\"java架构师\
新增数据-指定id
insert语句
GET /idx_hippo_sku_statistics_index/sku_statistics_index/_search{ \"from\
范围+in查询
GET /idx_hippo_sku_statistics_index/sku_statistics_index/_search{ \"query\": {\"term\": { \"cityId\": { \"value\
排序
POST idx_hippo_shop_sku_index/shop_sku_index/_search{ \"query\" : { \"bool\" : { \"filter\" : [ { \"term\" : { \"shopId\" : { \"value\
DSL语句
CollapseBuilder collapseBuilder = new CollapseBuilder(EsQueryConstants.THIRD_CATEGORY_ID);sourceBuilder.collapse(collapseBuilder);EsQueryConstants.THIRD_CATEGORY_ID 需要被去重的字段的对应的字符串;
java代码
按照某个字段去重查询
GET /idx_hippo_sku_statistics_index/sku_statistics_index/_search{\"from\
范围+精确+排序+固定字段返回
范围查询+精确匹配+排序
GET /idx_hippo_shop_sku_index/shop_sku_index{\"query\":{\"match_all\":{}}}
方式1
GET /idx_hippo_shop_sku_index/_search
方式2
查询所有数据
GET /idx_hippo_shop_sku_index/shop_sku_index/_search{\"query\": { \"bool\": { \"must\": [ { \"match\": { \"city\": \"上海\
DEMO1
DEMO2
多条件查询
GET /idx_hippo_shop_sku_index/shop_sku_index/_search{\"query\":{ \"match\":{ \"name\":\"美团\" }}}
GET /idx_hippo_sku_statistics_index/sku_statistics_index/_search{ \"query\": { \"term\": { \"cityId\": { \"value\": 136 } }}}
单条件查询
GET /idx_hippo_shop_sku_index/shop_sku_index/1
根据id查询
GET index/type/_search{ \"query\": { \"bool\": { \"must\": { \"exists\": { \"field\": \"字段名\" } } } }}
查询某个字段存在
GET index/type/_search{ \"query\": { \"bool\": { \"must_not\": { \"exists\": { \"field\": \"字段名\" } } } }}
GET /my_index/posts/_search{ \"query\" : { \"constant_score\" : { \"filter\": { \"missing\" : { \"field\" : \"tags\" } } } }}
查询某个字段不存在
POST /idx_hippo_shop_sku_index_fat/shop_sku_index/_search{ \"from\
对应java代码
or条件查询
GET /my-index-000001/_search?from=40&size=20{ \"query\": { \"term\": { \"user.id\": \"kimchy\" } }}
嵌套条件
条件查询
JAVA代码
去重查询
聚合查询
select语句
GET my_index/_count
查询索引下面文档数量
GET /idx_hippo_sku_statistics_index
获取索引字段信息
system-select语句
PUT idx_hippo_shop_sku_index{\"mappings\": {\"shop_sku_index\": { \"properties\": { \"text\": { \"id\": \"string\
创建索引
PUT idx_hippo_shop_sku_index/_mapping/shop_sku_index{\"properties\": {\"poolId\": { \"type\": \"long\
添加字段
DDL语句
分词查询://组合查询对象,//如搜索条件为“小米手机”这里must会分词为“小米”和“手机”这两个词是or关系//加operator(Operator.AND)可以把or改为and关系BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();boolQueryBuilder.must(QueryBuilders.matchQuery(\"name\
//组合查询对象BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//filter指不分词查询boolQueryBuilder.filter(QueryBuilders.termQuery(\"brandName\
不分词查询:must和 filter 的效果一样;
must与filter区别
语法解析
ES查询
适合字母语言
标准分析器
使用了将所有大写转小写的分词器
简单分析器
按照空白来分割
空白分析器
能够过滤停用词
停用分析器
字段不做分词
关键词分析器
允许指定分词切分模式
模拟分析器
按照特定语言分词
语言分析器
雪球分析器
分析器种类
分词器
过滤器
ES分析器
实际应用中需要根据属性的值来查找记录
倒排索引历史来源
索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引
倒排索引概念
根据主键id获取数据
正排索引特点
字段分词后的单个词语,单词
Term
对当前分词字段上所有的Term做排序,构建成B+树数据结构;
Term Dictionary
字典树
针对Term Dictionary做前缀提取,通过Term Index可以快速定位到Term Dictionary中某一个term的具体存储位置;
Term index
存储符合某个term(分词)的所有文档id;
Posting List
相关基础概念
整体示意图
字典树/前缀树示意图
倒排索引数据结构示意图
倒排索引
类似mysqlDB
类似mysql的表
type(ES6.0之后被废弃,es7中完全删除)
类似于mysql的行
document
类似mysql的clounm
field(可以再进行拆分成更小的字段)
ES技术概念
ES工作原理
内置分词器
做最细粒度分词,拆分出更多的词语;
ik_max_word
做最粗粒度的分词,已经分出来的词不再会被其他词占有;
ik_smart
IK分词器
中文分词器:
修改ES 的配置文件ES 目录/plugins/ik/config/ik/IKAnalyzer.cfg.xml1.可停掉想停止的分词;2.可以拓展自己想要的分词;3.可以把远程的停止词典作为自己的停止词典;4.可以把远程的拓展词典当做自己的拓展词典;
ES分词
ES搜索
搜索
数据库
Redis的多线程部分只是用来处理网络数据的读写和协议解析。在做某些删除操作的时候开启异步线程去做删除
redis对内存操作,性能的瓶颈点不在对内存的io;而是对网络的io
6.0版本之后可以多线程的io的,但是真正处理请求的线程依旧是一个
单线程工作
磁盘IO的时间是毫秒级别;
内存IO的时间是纳秒级别;
磁盘与内存IO差别
redis性能分析
Redis是一个开源的使用ANSI C语言编写的、支持网络、 基于内存的、支持持久化的日志型、Key-Value数据库
决定redis的服务器性能以及可维护性取决于对 键的设计与构造
在搭建服务器的时候如果我们使用的是32位的版本那么对应的键就会比64位的键储存长度小。
64位版本的redis数据读取效率都比32位的强。所以能使用64位就使用64位
redis64位版本与32位版本的区别
redis简述
分支主题
大量的请求在redis缓存中没有拿到数据,直接请求数据库,数据库也没有拿到数据;大量的请求直接怼到了数据库,给数据库造成了巨大压力
1.Redis缓存从数据库也没有查询到的空数据
2.在业务系统和缓存之间使用布隆过滤器,直接过滤掉一些缓存以及数据库都没有的请求,直接返回null
布隆过滤器,guav提供的。
缓存穿透
Redis缓存的是失效时间到了或者是Redis服务挂了,同一时间大量的请求过来,就会访问数据库,从数据库里面获取数据,这样对数据库的压力太大了
在Redis和数据库之间再做一层中间缓存;
通过定时job,给快要失效的缓存数据做一个再次续期的操作;
redis和数据库之前的代码中自己手动做限流措施
缓存击穿
大量的缓存数据在同一时间过期,引起大量的数据直接访问数据库,引起数据压力过大甚至宕机
错开缓存的过期时间
搭建高可用redis集群
做好熔断操作
缓存雪崩
缓存穿透,就是访问了根本就不存在的数据;缓存击穿,缓存中不存在,但是数据库存在;
缓存击穿缓存穿透区别
做两次更新,一次更新redis,一次更新数据库。
双更模式不管是redis先更新,还是数据库先更新,如果只要其中一个发生了问题,那么就会导致数据不一致;
双更模式下,数据不一致的概率很大;
双更模式
删除模式,就是删除数据库的缓存,然后后续查询的数据来覆盖缓存;
A请求删除了缓存但是还未更新数据库,B请求过来缓存击穿请求得到老的数据,然后再给缓存赋值,缓存的是老数据;A请求再更新数据库,导致数据不一致
先删除缓存
A请求获取数据库的旧值,B请求更新数据库,删除了缓存,A请求才去将旧值赋值给缓存,导致数据不一致;
后删除缓存
删除模式
为了删除旧缓存
1、删除一次缓存
2、更新数据库
延迟的目的:为等待数据库更新完成,2、为了让在中间其他线程在之前的查询到的旧数据写入缓存的,在最后再把缓存数据清除
3、间隔一定时间再删除缓存
操作步骤
延时双删依旧无法保证数据一致性,只能降低脏数据出现的概率;需要做额外的对数据库更新的一个限制操作,就是对数据的更新操作不能太过频繁;
延时双删
以上的所有方式都不能完全解决这个问题;
使用分布式锁,将缓存数据更新和数据库更新设置成同步方法
改进版本
缓存数据一致性
在redis集群模式下,数据会按照一定的分布规则分散到不同的实例上;如果由于业务数据特殊性,按照指定的分布规则,可能导致不同的实例上数据分布不均匀
数据倾斜解释
在集群中的各个实例上数据类大小不一致
数据量倾斜
对集群中某些实例的访问频率大大超过了其他实例
数据访问倾斜
数据倾斜分类
大key导致占据的缓存空间过大,实际上可能这些大key并没有多少;
将大key拆分成其他缓存
业务层面避免大key创建
存在大key
某些实例分配到的槽位到,相应的得到数据量也会多
初始分配的时候,将槽位分配均匀
如果已经造成,则可以进行实例之间的数据迁移
solt分配不均
在hash设置key的时候不要带上画括号
hashTag使用不当
原因
存在热key
主要原因
1、将热点数据复制多份,在每一个数据副本的key前面增加一个特定的前缀,让这些数据分散不同的节点上;热key取数据的时候随机从拿到某一个特定前缀,然后获取数据,这样就可以将热点数据分散请求了,这种方式只能针对读操作;
数据倾斜问题
比如说:扣库存操作,读取库存和扣减库存,不是一个原子操作
redis也有自增自减的api,将两个不是原子操作的操作合并成一个原子操作;
redis本身执行读写操作命令式单线程执行的,但是从调用redis的客户端来说,使用的时候会存在线程安全问题
1、客户端加锁
2、客户端使用单线程执行
lua脚本的开销非常低
好处
Lua脚本只能保证原子性,不保证事务性,当Lua脚本遇到异常时,已经执行过的逻辑是不会回滚的
缺点
3、将客户端的逻辑放在lua脚本中执行
解决此类由客户端导致的并发问题方式
并发问题
最小使用频率做缓存数据的淘汰
记录了最近访问的时间
记录了请求访问的次数
实现
这种肯定不行
不设置任何淘汰机制
这种也不行
随机淘汰
这种也不行,如果是一个高频使用的缓存,缓存周期到了也被淘汰了
生命周期淘汰
不能够完全说明这个问题;短期之内使用的比较少,但是整个周期内使用的次数比较多,这种也会被淘汰
最近最少使用算法
最终选择LFU
相对其他算法
使用LFU算法
如何保证缓存中的数据都是热键数据
缓存相关问题
1、使用redssion工具包
2、需要初始化redis中商品的库存数量,以及对应的数据库表中的库存数据
3、秒杀开始后将商品id和本次的业务名称拼接作为key的一部分;
5、获取成功,那么就执行相关的库存判断检验操作;
6、如果库存不够,那么就直接返回友好提示;
7、如果库存够,那么就扣减redis和数据库中的库存数据;
4、做tryLock获取锁操作
5、获取失败,那么就是直接返回友好提示
大体步骤
使用redis做秒杀相关问题
1.普通hash算法的伸缩性差,解决了分布式系统中机器的加入和退出
2.解决了数据倾斜问题
解决的问题
增设了很多虚拟节点
一致性hash
jedis已经支持了hash槽位的计算
redis默认的缓存分片是hash槽位,槽位的最大个数是16384个。每一个主节点都会分配到相应的槽位;根据计算key的对16384取模计算,当前的key应该属于哪个槽位。就知道落在了哪个节点
hash槽
缓存分片hash问题
2、redis集群值允许一个相同的key;
3、将设置key成功作为获取分布式锁的标记,设置失败表示没有获取到锁
4、将删除key,或者 key过期作为释放分布式锁的标记
5、最终都是redis调用lua脚本执行,lua脚本最终来保证原子性
分布式锁实现原理
jedis实现的方式比较麻烦
1.从redission获取锁
2.加锁
3.finnal里面解锁
防死锁,锁续期,以及可重入的锁的功能
请求一个分布式锁的时候,成功了,从机还没复制主机的锁,主机挂了,应用继续请求锁,会从继任了master的原slave上申请,也会成功;这就会导致,同一个锁被获取了不止一次
redssion红锁解决问题
1、获取当前的时间(单位是毫秒)
2、使用相同的key在这个集群中所有节点请求锁
3、只有在大多数节点获取到锁,且获取锁的时间小于锁超时时间
4、如果成功,就继续
5、失败了,就会在设置了key的节点上删除key
红锁工作过程
引入红锁
redssion实现方式
分布式锁
由于网络原因哨兵节点无法感知主节点的存在,那么就会从从从节点中选取一个主节点;那么这个时候就会存在两个主节点了;
脑裂出现原因
新的master节点就不会同步原来老master节点的数据;当网络原因解决之后,原来的主节点就会变成从节点,新的master节点由于没有同步数据,就会造成缓存数据的丢失;
集群脑裂问题
设置连接到master的最少从机数量;从机连接主机最大延迟时间
集群脑裂
实战
1.一主一备,主机写,从机读;主机挂了不影响从机读
2.主机挂了,系统还能提供读服务,并不能提供写服务,从机不会变成主机
主库挂了,那么就不能提供写服务,只能提供读服务
主从模式
1.建立在主从模式之上,哨兵节点本身不做数据存储;
2.主节点挂了,哨兵节点就会从所有从节点中选取一个节点做为主节点;
3.挂掉的主节点重启之后,就作为从节点;
4.哨兵节点也可以组成哨兵集群
1.客户端连接的是哨兵节点,由哨兵节点来提供Redis的各种服务
工作机制
哨兵模式
哨兵模式就能保证高可用了,但是如果数据量过大,一台服务器存不下所有数据,就需要搭建高可以用集群
高可用集群可以说是哨兵模式和主从模式结合体
集群至少有三个主节点,每个主节点至少有一个从节点(副本节点),所有一个高可用集群必须是由6个节点
可在线添加,删除节点
高可用集群模式
redis集群
默认淘汰策略
不做任何处理,写入超过限制后,会返回操作错误;读操作还是可以正常进行
noeviction
设置了过期时间,清除最近最少使用的键值对
volatile-lru
未设置过期时间,清除最近最少的键值对
allkeys-lru
1、需要存储缓存数据之外额外的时间数据
设置每一次要被淘汰的key的个数,个数如果=10比较,对热键数据影响比较小
解决方案
2、可能会删除热key
缓存对象中会存储这个缓存最近被访问的时间戳
淘汰的时候会根据当前的时间戳-缓存对象中的时间戳,差值最大的就被淘汰
LRU最近最少使用淘汰
设置了过期时间,清除某段时间内使用次数最少的键值对
volatile-lfu
未设置过期时间,清除某段时间使用次数最少的键值对
allkeys-lfu
LFU最小使用频率淘汰
设置了过期时间,清除过期时间最早的键值对
volatile-ttl
TTL生命周期结束淘汰
未设置过期时间,随机清除键值对
allkeys-random
设置了过期时间,随机清除键值对
volatile-random
random随机淘汰
缓存淘汰类型
1.后台根据删除任务的执行频率,默认是每秒10次;
2.删除key的时间限制;
3.从数据库的过期列表中随机选择20个key,判断是否过期,过期就清理;
4.如果有5个以上的key过期,那么就会重复再次选择20个;
5.清理的时间不能超过设定的时间;
删除过程:分为同步删除,异步删除
过期策略的实现(针对过期key回收)
遍历每一个数据库,从每一个数据库随机抽取一批数据,默认是五个,如果对比这批数据的最近的使用时间,删除最远使用时间的键值对;
LRU实现(每次命令处理的时候触发内存清理)
内存清理
1.如果缓存有明显的热点分部,那么就选择LFU算法
2.如果缓存没有明显热点分部,那么就选择随机
缓存淘汰机制选择
1.Redis的每一次命令处理的时候,都会去判断当前redis是否已经达到最大缓存极限,如果达到极限,就会启用相应算法去处理需要清除的键值对;
1.访问key的时候,key是否过期,过期就删除;
2.Redis启动时候的定时时间,默认是每秒十次的检测过期的key,过期就清理;清理的时间只有CPU执行时间的四分之一。
2.过期key的回收
内存回收时间
缓存淘汰机制
redis数据集改动事件之后对客户端的一种通知行为
事件通知概述
键空间通知
键事件通知
事件通知类别
删除,设置过期时间,重命名等一些和数据类型无关的操作的通知
字符串命令通知
列表命令通知
集合命令通知
哈希命令通知
有序集合命令通知
过期事件通知
缓存驱逐事件通知
不管发生什么事件都通知
通知类型
该功能默认是关闭;需要在config配置文件中开启该功能
配置形式:notify-keyspace-events +事件通知类别和通知类型;notify-keyspace-events "Ex"表示对过期事件进行通知发送
事件通知使用
事件通知是不可靠的,服务器采用的是发送即忘,如果当订阅事件发生的时候;客户端掉线了,那么这个事件就不会通知到客户端,所有事件订阅是不可靠的
事件订阅缺陷
事件通知机制
1.记录每一个redis的写命令以日志的形式进行存储
1.有命令就刷盘一次
2.一秒刷盘一次(推荐,也是默认的)
3.由系统决定刷盘时间间隔
2.AOF刷盘时间间隔
3.为啥需要设置刷盘时间:持久化的目的是把数据记录在磁盘上,所以当数据在内存中的时候,就需要把内存中的数据放到磁盘上,放到磁盘上的时间间隔就是刷盘时间;
机制说明
1.持久化实时性比较高(可以设置间隔多少秒追加一次日志,也就是间隔时间越短,丢失的数据就是越少)
1.AOF文件的体积通常大于RDB
2.数据恢复比rdb慢
优缺点
1.当AOF文件过大时,后台会去优化AOF文件;
1.可以使用修复程序修改AOF文件;
2.为AOF文件创建一个备份文件
当AOF文件出错(以下两者方式都是可以解决AOF文件出错了,数据该怎么恢复的问题,最终还是需要重启redis服务器去载入AOF文件)
AOF机制
AOF
机制说明:就是以内存快照的形式缓存内存中的数据
缺点:1.实时性比较低,单独使用该持久化机制,服务器宕机导致数据丢失较多;
1.实时性比较低,单独使用该持久化机制,容易导致数据丢失;
2.从主进程fork子进程的时候会被阻塞,
1.rdb文件大小紧凑;可以设置间隔时间备份,还原到不同历史时期的数据状态
2.持久化的时候可以由子进程去完成所有的数据保存工作;父进程无需任何的io操作;
3.数据恢复比AOF快
数据存储:存储在dump.rdb文件中
RDB(默认方式)
1.可以在不重启的情况下切换RDB到AOF模式
2.当RDB,AOF都打开的时候,程序默认使用AOF方式持久化
持久化机制
1.定期的把RDB文件备份到其他位置
容灾措施
epoll模式的多路复用
多路复用机制
网络IO
单线程性能瓶颈
多线程只是用来处理网络数据读写和协议的解析,执行Redis命令依旧是单线程去执行
多线程
一组命令的集合,要么所有的命令都执行成功,要么都执行失败
事物本质
一个事物所有的命令都会放在队列中缓存,执行的时候会去串行执行队列中的命令
事物执行过程
开启一个事物
MULTI
执行这个事物的所有命令
EXEC
取消事物
discard
监视某些key
watch
放弃监视某些key
unwatch
事物相关命令
配置事物一起使用,只有被监视的key没有发生任务数据变化的时候,事物才会被执行,否则是不会被执行
使用方式:在事物开始之前监听某些key
watch命令特别说明
2.6.5之前版本,忽略入队失败的命令,可以继续执行事物
2.6.5开始版本,入队失败,执行事物的时候会自动放弃执行该事物
入队时候的语法错误
exec事物开始执行的命令开始了,事物队列中某条或者某些命令执行失败了,Redis依旧会接着执行命令,不会放弃执行命令
执行事物调用之后错误;比如说错误的用string数据结构的命令操作list数据结构的数据
事物中的错误类型
不支持回滚,即使事物队列开始执行后,有命令执行失败了也不会回滚
redis事物与数据库事物最大差别
事物机制
作用:把从服务器数据的状态更新到和主服务器状态一致;
就是从机在初始化的时候,把主机中所有的数据都赋值到从机上
使用场景:一般都刚刚搭建服从服务的时候
1.数据量较大时候,主从节点的网络开销很大
全量复制
1.当主服务器收到写命令的时候,为了保持从服务器与主服务器的数据一致;就会让从服务器也去执行主服务器的命令;这个过程就是增量赋值的过程
2.对全量复制方式的工作方式弥补,当主从断开了连接,就不需要做全量复制,只需要执行断开期间主服务器的写命令
已经初始化完成的从服务器,需要做增量赋值主机的数据
增量复制
概述:复制分为全量复制,增量复制,也就是对应着同步操作,命令行操作;
复制分类
1.各自彼此都模拟成对方的客户端发送心跳信息
2.主节点默认间隔10秒给从节点 发送链接信息
3.从节点默认间隔1秒给主节点发送偏移量
心跳检测
1.从服务器给主服务器发送同步命令;
2.主服务器开启后台进程,生产快照数据,发送给客户端,并缓存当前主服务器的写命令
3.从服务器清空之前缓存的所有数据
4.从服务器收到rdb文件保存在磁盘,从磁盘读取数据同步数据;
5.主服务器把缓存的写命令传递给从服务器,从服务器同步这些写命令;
1.主节点处理完命令之后,会把命令字节长度累加记录起来,一个记录在命令表,一个记录在偏移量表
2.从节点收到主节点的命令,也会累计自身节点的复制的偏移量;
3.从节点每秒钟把自己的偏移量发送给主节点,主节点对比偏移量,
4.主节点就知道从节点的数据是否和主节点数据一致;
复制原理
1.从服务器在同步时,会清空所有数据
2.Redis不支持主主复制
3.主从复制不会阻塞master
4.主节点的处理完写命令就会直接给客户端返回,然后异步将命令传递给从服务器
复制注意事项
1、主从刚刚连接的时候,进行全量同步
2、全同步结束后,进行增量同步
如果有需要,slave 在任何时候都可以发起全量同步
无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步
Redis 主从同步策略
主从复制机制
1.数据存储在内存
2.数据结构简单
3.单线程不存在锁
4.io多路复用
redis为啥快
Redis最大存储大小
为0的时候表示可以无限制使用redis内存
maxmemory
配置内存清理策略
maxmemory-policy
maxmemory-samples
内存相关
Redis配置文件说明
APPEND myphone "nokia"
在来的字符串后面追加拼接
GETRANGE greeting 0 4
返回指定字符串的值中间几位对应的字符串
GETSET db mongodb
重新设置key的值返回老的key的值
failure_times对应的数值自减1
DECR failure_times
count对应的数值指定自减的数量
DECRBY count 20
page_view的值自增1;
INCR page_view
rank对应的数据自增20
INCRBY rank 20
mykey对应的值自鞥指定的浮点数值
INCRBYFLOAT mykey 0.1
自增自减
位图操作
key为website
value中field为Google,value为"www.g.cn"
HSET website google "www.g.cn"
设置值
HGET site redis
获取指定value指定field对应的值
获取people所有field和值
HGETALL people
获取key的所有field的值
HDEL abbr a
删除指定key的指定的field的值
HEXISTS phone myphone
指定field是否存在
对key 为counter 中field字段难为page_view的自增200
HINCRBY counter page_view 200
HINCRBYFLOAT mykey field 0.1
HMSET website google www.google.com yahoo www.yahoo.com OK
1) "google"2) "yahoo"
操作结果
HKEYS website
返回所有的field
1) "www.google.com"2) "www.yahoo.com"
HVALS website
返回所有的域的值
Hash表操作
可以重复添加
LPUSH languages python
往列表的表头添加
LPUSHX greet "hello"
往列表的表尾添加
RPUSH languages c
指定key中在指定元素的前面或者后面添加元素
LINSERT mylist BEFORE "World" "There"
往列表中添加
获取表头元素并删除
LPOP course
获取表尾元素并删除
RPOP mylist
获取表头元素,如果没有元素就会阻塞,阻塞的时间为指定时间
blpop key timeout
获取表尾元素,如果没有元素就会阻塞,阻塞的时间为指定时间
brpop key timeout
返回list中指定某个索引位置的数据
LRANGE fp-language 0 1
返回对应索引位置的值
LINDEX mylist 3
从列表中获取
根据key中value的值删除指定个数
lrem key count value
删除指定区间的值
ltrim key start stop
删除
List操作
不能被重复添加
SADD bbs "discuz.net"
添加单个
批量存储hash
hmset
存储
移除 languages 中的ruby元素
SREM languages ruby
移除key的所有元素
SMEMBERS not_exists_key
单个移除
SPOP db
删除集合并随机返回一个元素
移除
获取tool集合长度
SCARD tool
获取集合长度
SMEMBERS db
获取集合中所有元素
SINTER group_1 group_2
返回两个集合的交集
SUNION songs my_songs
返回两个集合的并集
SDIFF peter's_movies joe's_movies
返回两个集合的差集
获取
判断key为 joe's_movies 中是否含有"bet man"
SISMEMBER joe's_movies "bet man"
判断某个元素是不是当前set集合的元素
判断
Set
往key为page_rank集合中添加数值为10 的google.com 元素
ZADD 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
移除按照排名指定区间的数据
获取salary集合长度
ZCARD salary
正序从小到大
ZRANGE salary 200000 3000000 WITHSCORES
整个集合从小到大排序
ZRANGE salary 0 -1 WITHSCORES
递减排列
ZREVRANGE salary 0 -1 WITHSCORES
返回指定区间的元素
获取salary集合中分数在2000到5000之间的分数
ZCOUNT salary 2000 5000
获取指定分数区间的元素个数
获取salary集合 peter对应的分数
ZSCORE salary peter
获取指定元素的分数
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]
指定分数区间分页查询
排序按照分数从小到大
zrank key member
排序按照分数从大到小
zrevrank key member
排序获取元素排名
Zset
setnx key value
setex key seconds value
重新设置 nosql 中指定field对应的数据
HSETNX nosql key-value-store redis
hash
将songs 集合中的"Believe Me"元素移动到my_songs
SMOVE songs my_songs "Believe Me"
set集合
把 source的list集合尾部元素添加到 目标元素的头部;并把值返回给客户端
rpoplpush source destination
上一个命令的阻塞版本
brpoplpush source destination timeout
list
移动元素到另外集合
MSET date "2012.3.30" time "11:00 a.m." weather "sunny" OK
Hash
批量存储
MGET date time weather
获取key 为pet中对应的field对应的数据
HMGET pet dog cat fake_pet
批量获取
MSETNX rmdbs "MySQL" nosql "MongoDB" key-value-store "redis"
批量设置
批量操作
返回mykey对应的value的长度
STRLEN mykey
返回key对应的field的个数
hlen key
list集合对应的长度
LLEN job
返回长度
相同操作
数据类型操作
没有设置存活时间
-1
还存活 10084秒
10084
TTL key
返回值是key存活的毫秒值
PTTL key
获取key存活还有多少存活时间
设置的时间为毫秒值
EXPIRE cache_page 30000
生存时间为1500毫秒值
PEXPIRE mykey 1500
移除key的生存时间
PERSIST mykey
设置生存时间
存活时间
指定key删除
DEL name
清除整个redis的数据
FLUSHDB
删除key
EXISTS phone
判断key是否存在
先批量设置key,value MSETone1two2three3four4
KEYS *o*
"two"
KEYS t??
KEYS t[w]*
模糊匹配获取key
返回值为随机的一个key
RANDOMKEY
随机返回一个key
把key为song 的值移动到数据库1里面;Redis默认的存放在第一个数据库
MOVE song 1
移动key到其他数据库
0,key不存在
1,成功
RENAME message greeting
新的key不存在的时候才会成功
renamenx key newkey
重命名key
TYPE weather
根据key获取value的数据类型
返回指定list,有序集合,无需集合拍过排序之后的结果
排序方式按照 数字大小,字母的自然排序
SORT
DUMP
RESTORE
序列,反序列key
key操作
List
说明,只有Zset和List支持分页查询;
分页查询操作
获取经纬度的geoHash值
计算地理位置
特殊命令
redis命令
redis数据处理是单线程,memcache是多线程处理
线程操作
Redis支持更多更复杂的数据结构,memcache只支持keyvalue的字符串数据;
数据结构
Redis支持数据的持久化,会把数据同步到磁盘上;memcache不支持数据的持久化
数据安全性
Redis支持数据备份,需要开启主从模式;memcache不支持数据备份
数据备份
REDIS支持更多的过期策略;memcache支持的过期策略少
过期策略
Redis与MemCache的区别
jedis
redisson
本质还是springBoot整合了jedis
springBoot+整合redis
开发模式
redis
缓存
kafka
来一条信息就刷盘一次
等缓冲池满了刷盘
超过固定时间刷盘
具体刷盘策略无外乎以下三种类型
刷盘策略
数据
netty
多路复用
零拷贝
eurake和各个微服务之间通过心跳检测,检查微服务是否还在线
eurake
nacos
主从节点之间的心跳
zk
应用服务器是否可以提供接口,让监控服务器来监控应用服务器状态?
监控设计
rocketMq
rabbitMq
默认的都是消费者去消息中间件服务器拉取数据,也可以设置成服务器向消费者主动推送;
消息队列
监控数据默认都是从各个被监控对象拉去数据;也可以设置从被监控对象主动给普罗米修斯推送
普罗米修斯
消息中间件如果做成主动推送,消息推送可能会把消息中间件服务器造成资源压力
采取pull的方式,生产者这边难道就不会有消息存储的内存长期占用??不也是会造成生产者服务器压力????
TODO
拉取数据设置好处
pull数据
1、提高系统容错性,提高可用性, 避免单点故障
2、提高系统可扩展性,便于在原有系统中新增机器;
3、提供用户体验,文件系统多地部署,提高用户体验
使用复制的目的
如果是主机写,从机读,容易出现数据延迟到数据一致性问题;
如果是主机写,主机读,会导致主机压力过大
数据写入不具备可扩展性
1、新增新的从节点
从节点需要追赶式回复数据
1、从节点失效
切换新主节点
存在两个主节点
存在脑裂情况
2、主节点失效
2、处理失效节点能力
3、异步复制导致数据一致性问题
需要支持的能力
新提交的数据需要覆盖所有节点的数据,其他节点也有提交的数据,两者都需要去覆盖对应的数据,中间需要选取最新的数据。
数据版本冲突
1、让用户去解决
2、以时间为标准,后提交的数据作为最终的数据
解决
1、多数据中心
2、离线客户端操作
3、协同编辑
多主节点模式
任何副本都能接受来自客户端的写请求
1、处理失效节点
只能是最终一致性
写和读的副本不一样,读的副本可能还没有复制完成新数据
导致问题原因
2、写读一致性
需要支持能力
无主节点模式
常见模式
以后在类似的系统设计中可以使用到这些复制模型,选择对应的数据模型,就需要有相关的能力支持
感想
常见数据复制模型
通信
消息丢失
消息顺序消费
消息重复消费
消息积压
java应用
集群部署
1、如果这个系统本身使用java写的,肯定需要从这个系统的jvm优化方面可以入手
网络io,磁盘io一般是瓶颈点
2、从这个系统的本身所在机器的cpu,内存,磁盘io,等硬件优化
3、系统本身的某个重点功能的性能瓶颈点,寻找优化突破,代码优化,
解决思路
系统优化问题
共同问题
提供admin管理平台
有自己的监控平台
springboot
查看注册中心有哪些服务已经注册
有对应的监控平台
hystrix
查看资源配置,修改配置的图形界面,监控平台
sentinel
springCloud组件
各种参数设置
有admin平台
kakfa
队列管理平台
k8s可视化操作平台
linux可视化操作平台
操作平台
界面如何和对应的服务做相关的命令数据交互
对应的问题
各种需要进行命令进行操作的组件或者引用开发对应的可视化平台
猜想一个简化软件开发的思路
logstash
可视化
Elk
granfan
基础建设相关
可视化管理平台
spring是否也有
各种dao层框架是否也有
猜想
springBoot提供查询当前服务运行各项指标接口
unsafe类
对系统内存操作类
MBean-jvm层级监控
jvm也提供系统系统级别的接口
猜想,为啥可以去拉取mysql的二进制日志,是否也是mysql提供了对外暴露接口
mysql直接通过sql语句可以获取相关库表,磁盘占用数据;慢sql语句
mysql对外暴露接口
可视化操作平台也表明了对应的引用是否也提供了对外暴露的系统级别的接口?
kafka提供也提供当前系统查询指标接口
rabbitMq 也提供了相关接口
rokcetMq 提供了mqadmin 命令行工具来操作
redis自带的info命令和monitor命令的相关信息
系统级别接口
常用应用通用设计总结
CAT(Central Application Tracking)是一个实时和接近全量的监控系统
侧重于Java应用的监控
产品定位
mvc框架
rpc框架
持久层框架
分布式缓存框架
提供各项性能监控,健康检查,自动报警
应用场景
时间越久,监控的信息价值会锐减
实时处理
监控的是所有的请求数据
应用服务挂了,监控还在,可以辅助排查定位问题
高可用
全量数据的接收和处理能力
高吞吐
全量数据
监控本身的故障不会影响业务代码的正常运行
故障容忍
支持分布式,跨IDC部署,横向扩展的监控系统
可扩展
cat监控系统的可靠性可以做到四个九
不保证可靠
cat系统的设计要求
应用应用埋点的底层sdk,的客户端
CAT-client
实时消费,处理客户端提供的数据
CAT-consumer
给用户展示的控制端平台
CAT-home
结构展示
主要分为三个模块
1、为每一个线程创建一个ThreadLocal(线程局部变量);
请求对应的上下文其实是一个监控树的结构
2、执行业务逻辑的时候,就把请求对应的监控信息存储在线程的局部变量中
3、业务线程执行结束之后,将监控对象放入一个异步内存队列中;
4、cat会有一个消费线程将异步队列中的信息发送给服务端;
客户端信息收集
一段代码运行时间,次数
Transaction
一行代码的执行次数
Event
jvm内部的一些状态信息,Memory.Thread等
Heartbeat
一个请求调用的链路统计
Metric
核心监控对象
cat序列化协议是cat自己自定义的协议
序列化
netty来实现nio
序列化和通信设计
cat报表数据
cat原始logview数据
存储设计
设计图
1、客户端向服务端发送消息基于netty-nio实现
2、服务端接受消息放入内存队列,开起一个线程消费来分发这个内存队列中的消息
3、消息解析完成站会,存入本地磁盘,然后再异步上传到HDFS
流程说明
总个数
总和
均值
最大,最小
吞吐
95线,99线,999线
实时分析
整体架构设计图
cat整体设计
cat
skyWalking
pinpoint
Spring Cloud Sleuth 是 Spring 团队提供的链路追踪技术,大量借用了 Google Dapper 的设计
Spring Cloud Sleuth
应用
jdk1.5版本之后引入的特性
jdk1.6可以支持更加强大的Instrument
历史
在class被加载之前对其进行拦截,然后插入自定义的监听代码
无需对原有的应用做出任何代码修改,可以实现对类的动态修改和增强
可以理解是jvm级别的AOP
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接口来实现
java-agent实现
https://blog.csdn.net/m0_69305074/article/details/124504419
实战demo
java agent技术
链路追踪实现原理
链路追踪工具
链路追踪
通俗来讲,ELK就是
ELK
分布式日志系统
线上监控诊断产品
大大提升线上问题排查效率
产品自我定位
性能剖析(performance profiling)和代码优化
Perf
新名词学习
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
指标参数说明参考文档
jit编译花费的总时间
java.ci.totalTime
统计指标项目学习
logger
查看 logger 信息,更新 logger level
perfcounter
查看当前 JVM 的 Perf Counter 信息
profiler
生成发放火焰图
heapdump /tmp/dump.hprof
下载当前内存信息到某个目录下
heapdump
下载当前内存信息
jvm
查看当前 JVM 信息
查看jvm当前内存信息
查看 JVM 内存信息
最忙的几个
所有
查看当前线程信息,查看线程的堆栈
dump
dump 已加载类的 bytecode 到特定目录
反编译指定已加载类的源码
编译.java文件生成.class
sc
查看 JVM 已加载的类信息
sm
查看已加载类的方法信息
vmtool 利用JVMTI接口,实现查询内存对象,强制 GC 等功能。
编译文件相关
monitor
方法执行监控
stack
输出当前方法被调用的调用路径
trace
方法内部调用路径,并输出方法路径上的每个节点上耗时
tt
方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
函数执行数据观测
方法监控
查看当前 JVM 的环境属性
查看当前 JVM 的系统属性
vmoption
查看指定参数
更新指定的 option
查看,更新 VM 诊断相关的参数
参数查看
常用命令
dashboard
当前系统的实时数据面板,按 ctrl+c 退出
当前系统的实时数据面板
getstatic
预览
查看当前类静态属性
查看 classloader 的继承树,urls,类加载信息
输出当前目标 Java 进程所加载的 Arthas 版本号
预览命令
打印文件内容,和 linux 里的 cat 命令类似
history
打印命令历史
文件相关
命令列表
和linux系统类似
一段采样间隔时间内,当前 JVM 里各个线程的增量 cpu 时间与采样间隔时间的比例
首先第一次采样,获取所有线程的 CPU 时间(调用的是java.lang.management.ThreadMXBean#getThreadCpuTime()及sun.management.HotspotThreadMBean.getInternalThreadCpuTimes()接口)
然后睡眠等待一个间隔时间(默认为 200ms,可以通过-i指定间隔时间)
再次第二次采样,获取所有线程的 CPU 时间,对比两次采样数据,计算出每个线程的增量 CPU 时间
线程 CPU 使用率 = 线程增量 CPU 时间 / 采样间隔时间 * 100%
具体步骤
把统计的时间拉长可以降低命令本身执行的时间损耗
cpu 使用率是如何统计出来的?
1:查看应用 load、内存、gc、线程的状态信息
查看方法调用的出入参
查看方法异常
监测方法执行耗时
类加载信息
2:可在不修改应用代码的情况下,对业务问题进行诊断
功能概述
arthas-阿尔萨斯
诊断工具
1、快速发现故障
2、快速定位故障
3、辅助进行程序性能优化
监控系统的要求
Overview
Threading
GC
CPU
Heap
jian
jvm监控指标
本质都是通过JMX来实现对jvm数据的采集
java.management工具包
MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean(); map.put(\"堆内存\
code -demo
https://blog.csdn.net/qq_17522211/article/details/117552950
指标数据采集
jvm监控
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.4</version></dependency>
https://blog.csdn.net/u014295903/article/details/125557810
文档
方式1:
<dependency> <groupId>eu.bitwalker</groupId> <artifactId>UserAgentUtils</artifactId> <version>1.20</version> </dependency>
https://www.uoften.com/article/213906.html
参考文档
方式2:
获取操作系统静态信息
系统调用(System Call)是操作系统提供的服务,是应用程序与内核通信的接口
1、图形界面操作接口,Linux系统一般提供KDE、Gnome等图形界面接口,目的是方便普通用户操作计算机
2、控制台接口,即终端接口,Linux系统一般提供bash shell、cshell等等终端接口,目的是方便系统管理员操作计算机,事实上Linux功能的强大也主要体现在终端接口
TODO 猜测此类接口可以获取Linux系统当前cpu水位,内存消耗情况
3、API接口:即apllication interface,这是应用程序接口,从编程角度Linux系统就是一个大的程序调用库,它提供大量的API函数,目的是方便程序员开发应用程序
linux系统
获取操作系统运行时信息
计算机资源监控
基础资源监控
连接池中链接数
连接池链接数峰值
池中连接数峰值时间
活跃连接数
活跃连接数峰值
数据源监控
执行数
执行时间
错误数
读取行数
更行行数
慢SQL
JDBC访问监控
异常类型
异常方法:
异常时间
异常数量
堆栈信息
Exception监控
如何实现TODO
接口路径
调用总数
最大并发
平均QPS
总耗时
平均耗时
99线
95线
999线
最快
最慢
接口链路
接口入参回参
接口调用指标
接口调用监控
应用监控
订单数量,支持成功数,点击次数,下载次数
Cache命中率
队列大小
业务自定义
业务监控
监控维度
应用自监控,就是每个应用实例的监控数据存放在应用本身,比如一个Map。然后通过JMX或者其他方式暴露出去。然后开发人员可以通过JConsole或者API(一般是Web界面)得到这些监控数据。比如Druid就是这种做法。访问: hk01-xxxx-mob03.hk01:8090/druid/index.html 得到hk01-xxxx-mob03.hk01:8090这个应用的监控数据。
自监控
统一上报监控方式,就是所有的应用监控数据都上报到监控中心,由监控中心负责接收、分析、合并、存储、可视化查询、报警等逻辑。这种方式是瘦客户端模型,客户端的职责就是埋点上报监控数据。所有的监控逻辑都在中心处理
统一监控
自监控的话实现起来简单,并且没有与监控中心的网络交互,性能也会好很多。但是缺点就是缺乏全局的统计和监控。从实用角度来说还是集中式监控好一些。
结论
1、每个应用自监控或者统一上报监控?
一个物理机上的多台服务器通过一个统一的agent向监控中心上报监控数据
agent上报
一个物理机上的多台服务器各自向监控中心上报统计数据
独立上报
网络通常的情况下,直接独立上报
2、监控中心与客户端应用之间要不要通过本地Agent上报?
经过业务逻辑的处理,得到相关的统计数据,然后再做储存
最终状态
直接就存储上报的统计信息本身的数据
事件序列
最终状态还是弱了一些,事件序列会好一些,存储可以采用HBase这样的分布式存储系统,性能问题可以采用预聚合等方式解决
3、存储最终状态还是事件序列
因为Events或者Metrics的特殊性,一般都会采用一种专门的存储结构——Distributed time series database
RRD(round-robin-database): RRDtool使用的底层存储。C语言编写的。性能比较高
prometheus: An open-source service monitoring system and time series database. 目前只有单机版本。
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,需要另外处理。
4、数据存储
前期可以先丢弃,后续要缓存起来。受影响比较大的是counter接口。
5、如果服务器挂掉了,统计数据怎么处理?缓存本地,等服务器起来再发送?还是丢弃?
同时要考虑同步和异步接口
如何高性能的接收大量客户端的上报请求。以及使用什么通讯协议
6、网络通信和协议
监控方案设计
Prometheus是一个开源的系统监控和警报工具包
最初由SoundCloud构建。自2012年成立以来,许多公司和组织都采用了Prometheus,该项目拥有非常活跃的开发人员和用户社区。它现在是一个独立的开源项目,独立于任何公司进行维护
Prometheus于2016年加入了云原生计算基金会,成为Kubernetes之后的第二个托管项目
1、一个多维数据模型,其时间序列数据由度量名称和键/值对标识
2、有自己独有的的查询语言,PromQL
Prometheus有具体的接口可以提供查询指标数据
对应的第三方的支持将数据导入到第三方
但是可以把采集到的数据再次被同步到其他平台,或者数据存储库
3、不依赖分布式存储;单个服务器节点是自治的
4、(统计指标)收集通过HTTP上的拉模型进行
5、通过中间网关支持推送(统计指标)时间序列
6、被采集的服务是通过服务发现或者静态配置的
7、支持多种模式的图形化和仪表板
1、主服务器,用于采集存储统计指标数据
2、搭载在各种客户端库数据采集器
3、数据推送网关
4、各种导出组件
5、告警组件
6、支持各种第三方工具
具体组件
架构图
架构
1、以机器为核心的监控
2、多维度手机和查询数据
3、可靠性
Prometheus收集到的数据并不是非常详细完整
1、需要百分之百保证数据准确性
不适合的场景
Prometheus相关介绍
例如,您可以使用计数器来表示服务的请求、完成的任务或错误的数量
计数器是一种累积度量,表示一个单调递增的计数器,其值只能在重新启动时增加或重置为零
总和 Counter
内存使用的最大子,最小值
度量是指表示单个数值的度量,该数值可以任意上下波动
Gauge
直方图对观测值进行采样(通常是请求持续时间或响应大小),并将它们计数在可配置的桶中
Histogram
与直方图类似,摘要对观察结果进行抽样(通常是请求持续时间和响应大小)。虽然它也提供了观测的总数和所有观测值的总和,但它可以在滑动时间窗口上计算可配置的分位数。
Summary
Prometheus客户端库提供了四种核心指标类型
Prometheus统计指标
广义上讲所有可以向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,适合于一些需要更多自定义监控指标需求的项目。
集成到应用中
导出器的运行方式
需要在自定义的程序中引入prometheus的依赖包,且需要和prometheus定义通讯连接
自定义export
Exporter
这种方式一般都是新项目
方式1、使用官方提供的jar包,然后嵌入到应用中
方式2、prometheus的jmx_exporter
https://blog.csdn.net/qq_25934401/article/details/82185236
https://blog.csdn.net/penngo/article/details/126982183
具体监控实施文档
监控方式
Prometheus 监控 Java 应用
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 容器网络累积传输数据总量(单位:字节)
典型指标
先启动cadvisor
Prometheus配置文件中配置cadvisor的ip地址和端口
然后启动Prometheus
和Prometheus集成
cAdvisor
k8s集群
node节点
pods
node-exporter(节点导出器)
被监控对象
监控容器
该引用并不是和mysql整合在一起
1、先部署MySQL_Exporter
2、在Prometheus配置MySQL_Exporter的IP和端口
监控步骤
监控mysql数据库
同mysql监控类似,通过redis_export
redis监控
消费堆积的计算,一般是通过生产者的消息偏移量和消费者消息偏移量对比;
消息中间件监控,消息消费堆积这种情况,需要自己做定制化开发才行
消息中间件对应的 exporter
消息中间件监控
jmx
Prometheus监控
将2小时作为一个时间窗口产生的数据放在一个数据块里面,保存在本地磁盘上
单个Prometheus Server基本上能够应对大部分用户监控规模的需求。
核心设计思想:内置了一个本地数据序列数据库
1、可降低部署和管理复杂性
2、减少高可用带来的复杂性
本地存储无法将数据持久化,无法存储大量的数据,无法做到灵活扩展和迁移
Prometheus数据序列数据库设计
本地存储
解决数据无法持久化问题
Prometheus定义两个标准接口(读写),让用户基于这两个接口保存到任意第三方存储服务,这就是远程存储
远程写
远程读
读写
远程存储
数据的采集和数据存储是分开的
利用数据是做远程存储,可以在每一个数据监控中安装一个监控实例,这个监控实例从公用的远程存储中心获取数据。
一个实例已经处理处理上千规模的集群
1、在每一个数据监控中心部署一个实例
2、由中心的实力负责聚合多个数据中心的监控数据
原理
联邦集群
Prometheus数据存储
prometheus本身有自己的大盘数据,但是不太好用,一般是与grafana集成在一起
所以prometheus可以将数据导入到grafana大盘中
grafana
数据可视化
1、部署多台服务器
2、多台服务器都会向同样的一批被监控的应用或者服务采集数据
3、数据展示的时候通过nginx负载均衡后从其中一台服务器拉取数据
确保Promthues服务的可用性问题
Prometheus Server之间的数据一致性问题
持久化问题(数据丢失后无法恢复)
无法进行动态的扩展
适合监控规模不大
Promthues Server也不会频繁发生迁移的情况
保存短周期监控数据
适用场景
基本HA:服务可用性
相对于基础的服务可用性,此种部署方式,让数据在远程
确保了数据持久化,增加了系统可扩展性
Promthues宕机后,可以快速回复
可进行数据迁移
用户监控规模不大,但是希望能够将监控数据持久化,同时能够确保Promthues Server的可迁移性的场景
基本HA + 远程存储
将不同的采集任务划分到不同的Promthues
场景一:单数据中心 + 大量的采集任务
场景二:多数据中心
基本HA + 远程存储 + 联邦集群
极端情况下,采集的目标数量过大,通过联邦集群进行功能分区,也无法有效处理,考虑按照被采集的对象的级别,进行区分采集
按照实例进行功能分区
Promthues高可用部署
服务注册中心存储所有监控目标的访问信息,Promthues只需要去访问服务注册中心就知道需要对那些目标进行监控,这种模式成为服务发现
Promthues服务发现概述
很多云平台的服务,可以创建销毁应用,在这个过程被监控的对象的信息都是在变化中,但是Promthues无法动态感知到这些信息的变化,需要有一个外部注册中心来感知这个,然后Promthues通过统一的调用注册中心的信息得到这些变化的信息;
为什么需要有服务发现注册
不同的场景下,会有不同的东西来扮演注册中心
AWS公有云平台
平台级别的公有云
OpenStack的私有云平台
平台级别的私有云
Kubernetes容器管理平台
容易云
文件
DNS解析
注册中心举例
注册中心
Promthues通过读取文件中的被监控者的信息,来动态更新监控对象的信息
可以不依赖第三方的平台
基于文件的服务发现
Consul本身是一个支持多数据中心的分布式服务发现和键值对存储服务的开源软件
被监控的服务的信息会被注册到Consul
Promthues去Consul拉取对应的被监控的信息
基于Consul的服务发现
通过自定义标签,来区分不同环境的被监控对象的信息
服务发现与Relabeling
服务发现实战
Promthues服务发现
Prometheus
spring 公司开源的一款对于微服务监控的应用
简单介绍
提供计时器、仪表、计数器、分布摘要和长任务计时器等指标与中立的接口
应用接口指标监控
提供对缓存检测、类加载器、垃圾收集、处理器利用率、线程池接口
可拓展个性化指标
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 关系
Micrometer
Nagios
Zabbix
开源监控系统
Java Profiler
java内存分析
MAT
偏向jvm监控
VisualVM
Java Mission Control与Java Flight Recorder一起,允许分析和事件收集有关Java虚拟机(JVM)和Java应用程序行为的低级信息。与Oracle JDK一起打包的这组工具还提供了对收集的数据的详细分析。
Oracle Java Mission Control
监控相关工具
全称Java Management Extensions,jdk5引进的技术
获取类装载信息,已装载、已卸载量
ClassLoadingMXBean
获取编译器信息
CompilationMXBean
获取GC信息,但他仅仅提供了GC的次数和GC花费总时间
GarbageCollectionMXBean
提供了内存管理和内存池的名字信息
MemoryManagerMXBean
提供整个虚拟机中内存的使用情况
MemoryMXBean
提供获取各个内存池的使用信息
MemoryPoolMXBean
提供操作系统的简单信息
OperatingSystemMXBean
提供运行时当前JVM的详细信息
RuntimeMXBean
提供对线程使用的状态信息
ThreadMXBean
接口功能
java.management包下提供接口
JMX 提供了简单、标准的监控和管理资源的方式
包含 MBean 及其可管理的资源
提供了实现 JMX 技术可管理资源的规范
资源层
充当 MBean 和应用程序之间的中介
代理层
为远程程序提供Connector 和 Adapter访问 MBean Server
远程管理层
分层
JMX架构
实现对运行时应用程序动态资源查询
利用JMX创建javaBean规则
修改对运行时应用程序动态资源配置
ManagementFactory.getOperatingSystemMXBean() 方式获取
获取操作系统相关的信息,机器名称、内存使用、CPU使用
ManagementFactory.getRuntimeMXBean()方式获取
获取当前 JVM 的信息,包括 JVM 参数和 JVM 相关的系统参数
ManagementFactory.getMemoryMXBean()获取
获取当前 JVM 的内存使用,包括堆内存和非堆内存
ManagementFactory.getThreadMXBean() 获取
获取 JVM 线程使用情况,包括活动线程、守护线程、线程峰值
ManagementFactory.getClassLoadingMXBean() 获取
获取 JVM 类加载情况,包括已加载类、未加载类等
GarbageCollectorMXBean
ManagementFactory.getGarbageCollectorMXBeans() 获取
这里获取到的是一个集合,因为垃圾收集器分为老年代和新生代两个。
获取 JVM 垃圾收集器的情况,包括使用的哪种垃圾收集器以及回收次数
核心api
1、创建需要被存入进程的对象;
2、对象必须是接口,且必须以MBean结尾
具体规则
public interface BlackListMBean { // 获取黑名单列表 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(); }}
实现接口
// 获取 MBean ServerMBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();// 创建 MBean 初始黑名单用户为 a 和 bBlackList blackList = new BlackList();blackList.addBlackItem(\"a\");blackList.addBlackItem(\"b\");// 注册ObjectName objectName = new ObjectName(\
MBean 注册到 MBeanServer
String hostname = \"localhost\
演示
开通远程接口调用权限
-Dcom.sun.management.jmxremote.port=8888 --表示远程jmx的端口-Dcom.sun.management.jmxremote.authenticate=false --是否要使用用户名和口令验证-Dcom.sun.management.jmxremote.ssl=false --是否使用安全socket
添加jvm配置
登录远程jvm
JMX创建javaBean规则
Java内置的实现监控工具 jconsole
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
JMX
jvm内存监控实现原理
CPU状态(user、system、iowait&idle percentages
内存使用率(used、buffered、cached & free percentages)
装载上用于节点数据目录的可用磁盘空间
beam.smp使用的文件描述符与最大系统限制
网络延迟(集群中所有RabbitMQ节点之间以及客户端之间)
基础设施和核心指标
请求集群中的任意节点获取
该节点在声场响应之前,需要收集组合来自其他节点的数据
集群指标获取
cluster_name
集群名称
message_stats
集群范围的消息速率
object_totals.connections
生产者,消费者和服务端的链接数量
总连接数量
object_totals.channels
消息通道是轻量级的连接
总消息通道数量
object_totals.queues
总队列数量
object_totals.consumers
总的消费者数量
queue_totals.messages
消息总数(ready+unacked)
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
Is a memory alarm in effect?
mem_alarm
当内存使用超过阈值时将触发报警memory alarm
disk_free_limit
剩余磁盘空间阈值
当空闲磁盘空间低于配置的限制时,将触发报警
可用文件描述符总数
已经使用的文件描述符大小
尝试打开的文件描述符数量
socket 系统连接最大值
socket 连接已经使用数量
?
Message store disk reads
Message store disk writes
Inter-node communication links
垃圾回收次数
垃圾回收内存大小
erlang 最大进程数量
已经使用的erlang 进程数量
正在运行的队列
节点指标
节点指标获取
内存大小
消息总数(ready+unacknowledged)
总的消息大小
准备传送的消息数量
未确认的消息数量
最近发布的消息数量
消息发布速度
最近交付的消息数量
消息传送速度
队列指标
单个队列指标获取
返回集群范围的指标
GET /api/overview
返回单个节点的状态
GET /api/nodes/{node}
返回所有集群成员节点的状态
GET /api/nodes
单个队列的指标
GET /api/queues/{vhost}/{qname}
本质都是通过RabbitMq提供的Http api接口获取
集群监控指标
监控的rabbitMq系统本身指标
应用程序跟踪的指标可以是特定于系统
客户端库和框架提供了注册指标收集器或收集现成指标的方法
RabbitMQ Java客户端和Spring AMQP就是两个例子。其它开发人员必须跟踪应用程序代码中的指标。
应用程序级指标
可以在MQ中专门创建一个监控的队列,定时的发送和消费队列中的消息,并且通过的如下的代码去监控队列是否已经堵塞,如果监听到队列已经堵塞,就立即发送告警的短信和邮件
监听堵塞消息
集群中是否有资源报警
rabbitMq是否正常运行
当前节点是否有报警
监控检测
监控指标
频率太高,容易对系统产生负面影响
生产系统建议收集间隔30秒-60秒
监控频率
公开了节点、连接、队列、消息速率等的RabbitMQ指标
监控系统与被监控系统交织在一起
占用系统一定的开销
它只存储最近的数据(最多一天,不是几天天或几个月)
它有一个基本的用户界面
它的设计强调易用性,而不是最佳可用性
管理UI访问通过RabbitMQ权限标记系统(或JWT令牌作用域的约定)进行控制
RabbitMq自带管理平台
后端命令行工具
rabbitmqctl命令
Http接口
REST API
监控数据来源
RabbitMQ自带的(Management插件
监控系统与被监控系统分离
降低服务开销
长期存储指标
方便关联聚合对比各个相关指标
更强大和可定制的用户界面
易于分享的指标数据
更健全的访问权限
针对不同节点的数据收集更具弹性
Prometheus & Grafana
监控工具
rabbitMq监控
监控系统不是很完善,目前主要依赖rokcetMq mqadmin命令行工具
rocketMq监控
clusterList
查看集群列表
clusterRT
测试集群的响应耗时
集群相关
brokerStatus
获取broker的运行时状态数据
getBrokerConfig
获取broker的配置信息
broker相关
topicRoute
获取topic的路由信息
topicStatus
获取topic的当前状态信息
topicClusterList
获取topic对应的集群信息
topicList
查看集群中的topic列表
statsAll
查看所有topic已经对应的consumer的消费进度
topic相关:
queryMsgById
通过message的id查询消息
queryMsgByKey
通过message的key查询消息
queryMsgByUniqueKey
通过message的UniqueKey查询消息
queryMsgByOffset
通过偏移查询message 、
printMsg
打印某条message的详情
printMsgByQueue
打印某个queue(队列)里的消息的详情
producerConnection
查询producer的信息(socket连接,客户端版本)
message相关
consumerConnection
查询consumer的信息(socker连接,客户端版本,消费组)
consumerProgress
查询consumer的消费进度,tps
onsumerStatus
查询consumer的的内部数
brokerConsumeStats
查看所有topic对应的消费数据(broker的offset,consumer的offset,是否有diff)
checkMsgSendRT
测试消费发生的响应耗时
queryCq
查询consumer指定队列和索引位置的消费信息
消费相关:
getNamesrvConfig
获取nameserver的配置
nameserver相关:
clusterAclConfigVersion
查看集群的acl配置版本
etAccessConfigSubCommand
查看acl的配置信息
acl相关:
需要通过reocketMq自带的mqadmin
监控指标数据获取
本质依赖的是rokcetMq mqadmin命令行工具
1、官方提供的一个console web监控工具
将rockeMq的数据源导入到Prometheus上做相关监控
1、系统rocketMq服务
1、通过 RocketMQ Exporter 工具从rokcetMq 上获取到相关的指标数据;
2、配置Prometheus 从RocketMQ Exporter 获取到对应的指标数据
大致流程
2、Prometheus
Rocketmq监控
因为kafka是java写的
kafka官方也是提倡使用jmx并且提供了jmx的调用给用户以监控kafka.
监控实现原理
实例消息生产流量(bytes/s)
实例消息消费流量(bytes/s)
实例磁盘使用率(%)-实例各节点中磁盘使用率的最大值
实例监控
Topic 消息生产流量(bytes/s)
Topic 消息消费流量(bytes/s)
topic
Group 未消费消息总数(个)
group监控
资源类型监控
在运行正常集群中,同步副本(ISR)数量应等于副本总数。如果分区副本远远落后于 Leader,则从 ISR 池中删除这个 follower。如果代理不可用,则 UnderReplicatedPartitions 指标急剧增加。Tips:UnderReplicatedPartitions 较长时间内大于零,需要进行排查。
未复制的分区数
如果某副本在一段时间内未联系 Leader 或者 follower 的 offset 远远落后于 Leader,则将其从 ISR 池中删除。因此,需要关注 IsrShrinksPerSec / IsrExpandsPerSec 的相关波动。IsrShrinksPerSec 增加,不应该造成 IsrExpandsPerSec 增加。在扩展 Brokers 集群或删除分区等特殊情况以外,特定分区同步副本(ISR)数量应保持相对稳定。
同步副本(ISR)池缩小/扩展的速率
主要统计没有活跃 Leader 的分区数。Tips:由于所有读写操作仅在分区引导程序上执行,因此该指标出现非零值,就需要进行关注,防止服务中断。
离线分区数(仅控制器)
所有 brokers 中 ActiveControllerCount 总和始终等于 1,如出现波动应及时告警。Kafka 集群中启动的第一个节点将自动成为Controller且只有一个。Kafka 集群中的Controller负责维护分区 Leader 列表,并协调 Leader 变更(比如某分区 leader 不可用)。
集群中活动控制器的数量
在可用性和一致性之间,Kafka 默选了可用性。当 Kafka Brokers 的分区 Leader 不可用时,就会发生 unclean 的 leader 选举。当作为分区 Leader 的代理脱机时,将从该分区的 ISR 集中选举出新的 Leader。Tips:UncleanLeaderElectionsPerSec 代表着数据丢失,因此需要进行告警。
每秒 UncleanLeader 选举次数
TotalTimeMs 作为一个指标族,用来衡量服务请求(包括生产请求,获取消费者请求或获取跟随者请求)的用时,其中涵盖在请求队列中等待所花费的时间 Queue,处理所花费的时间 Local,等待消费者响应所花费的时间 Remote(仅当时requests.required.acks=-1)发送回复的时间 Response。
特定请求(生产/提取)用时
传入/传出字节率
每秒请求数
Kafka-emitted 指标
消耗磁盘空间消耗与可用磁盘空间
页面缓存读取与磁盘读取的比率
CPU 使用率
网络字节发送/接收
JVM 执行垃圾回收进程总数
JVM 执行垃圾收集进程用时
Host 基础指标 & JVM 垃圾收集指标
Broker 指标
每秒收到的平均响应数
每秒发送的平均请求数
平均请求等待时长
每秒平均传出/传入字节数
I / O 线程等待的平均时长
每个分区每个请求发送的平均字节数
Producer 指标
Consumer 在此分区上滞后于 Producer 的消息数
特定 Topic 每秒平均消耗的字节数
特定 Topic 每秒平均消耗的记录数
Consumer 每秒获取的请求数
Consumer 指标
Kafka Manager
Kafka Eagle
Logi-KafkaManager
kafka监控
直接通过接口查询各种相关的监控信息
监控工具实现
mqadmin–提供一套命令行工具
RocketMQ
消息队列监控实现原理
log-bin=mysql-bin #[必须]启用二-进制日志server-id=100 #[必须]服务器唯一ID,如果是用于多台MySQL,ID不要重复就行binlog-format = ROW
1、修改配置
<dependency> <groupId>com.zendesk</groupId> <artifactId>mysql-binlog-connector-java</artifactId> <version>0.27.1</version> <!--2022.09.17版的--> </dependency>
2、引入依赖包
//为什么甚至路径都一样,还是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\
3、具体代码demo
https://blog.csdn.net/qq_45821251/article/details/127490460
https://blog.csdn.net/chengsw1993/article/details/119328869
其实这个最大的作用还是主从备份数据;对实际上的mysql自身的基础资源监控几乎没有任何帮助;可以在应用监控,和业务监控上做一些文章。但是其实如果要在应用监控,业务监控用这种方式去做会显得很重,其实可以直接在java应用中对sql语句进行拦截然后做统计,没必要还要通过主从备份数据来做;
说明
java监听Mysql数据变化
数据库都有一个核心的information_schema元数据库,元数据库中间会有存储库表基本信息,这些基本信息会记录相关的库表索引数据大小
TABLE_SCHEMA : 数据库名TABLE_NAME:表名ENGINE:所使用的存储引擎TABLES_ROWS:记录数DATA_LENGTH:数据大小INDEX_LENGTH:索引大小
information_schema库TABLES表
数据来源
表所占空间大小
某个表所占用空间大小
索引所占用空间大小
索引占表的空间大小比例
某个库所占空间大小
所有的库所占用的大小
某个库索引大小
具体指标
库表索引空间大小监控
显示mysql运行时状态连接数相关指标
show status 命令
显示mysql运行时状态
extended-status 命令
方式
Threads_connected
当前已经连接数量
Connections
试图连接到(不管是否成功)MySQL服务器的连接数
Max_used_connections
服务器启动后已经同时使用的连接的最大数量
由于客户没有正确关闭连接已经死掉,已经放弃的连接数量
尝试已经失败的MySQL服务器的连接的次数
当执行语句时,已经被创造了的隐含临时表的数
正在使用的延迟插入处理器线程的数量
用INSERT DELAYED写入的行数
用INSERT DELAYED写入的发生某些错误(可能重复键值)的行数
执行FLUSH命令的次数
请求从一张表中删除行的次数
请求读入表中第一行的次数
请求数字基于键读行
请求读入基于一个键的一行的次数
请求读入基于一个固定位置的一行的次数
请求更新表中一行的次数
请求向表中插入一行的次数
用于关键字缓存的块的数量
请求从缓存读入一个键值的次数
从磁盘物理读入一个键值的次数
请求将一个关键字块写入缓存次数
将一个键值块物理写入磁盘的次数
同时使用的连接的最大数目
在键缓存中已经改变但是还没被清空到磁盘上的键块
在INSERT DELAY队列中等待写入的行的数量
打开表的数量
打开文件的数量
打开流的数量(主要用于日志记载)
已经打开的表的数量
发往服务器的查询的数量
要花超过long_query_time时间的查询数量
当前打开的连接的数量
不在睡眠的线程数量
服务器工作了多少秒
https://blog.csdn.net/weixin_36123300/article/details/113236131
连接数线程监控
指标
SigNoz
MySQL 企业版附带了 MySQL 企业级监视器
基于云的远程监控
查询分析可视化
支持 MySQL 集群监控
实时健康状态上报、可用性监控
易于配置
特性
MySQL Enterprise Monitor
Paessler PRTG Network Monitor
Sematext
Solarwinds
mysql监控
数据库监控
监控的实现
redis自带命令monitor的输出结果做分析的python脚本
redis-faina
RedisLive是一款用Python编写的Redis图形监控工具
监控脚本来利用Redis提供的MONITOR命令从被监控Redis实例中获取数据并存储到Redis的监控实例中来做数据分析
RedisLive以可视化的方式展示了Redis实例中的数据,分析查询模式和峰值
redis-live
Munin插件
nagios
监控插件
redis监控平台
连接失败检测
执行 info clients 命令获取 connected_clients 就是客户端连接数
客户端连接数
连接检测
此命令可以查询吞吐量相关的监控项
info stats
从 Redis 启动以来总计处理的命令数,可以通过前后两次监控组件获取的差值除以时间差,以得到 QPS
total_commands_processed
总执行命令数量
instantaneous_ops_per_sec
当前 Redis 实例的 OPS
total_net_input_bytes
网络总入量
total_net_output_bytes
网络总出量
instantaneous_input_kbps
每秒输入量,单位是kb/s
instantaneous_output_kbps
每秒输出量,单位是kb/s
吞吐量监控
通过 info memory 获取,表示 Redis 真实使用的内存
used_memory
已使用内存大小
通过 config get maxmemory 获取。
最大内存大小
两个参数均通过 info memory 获取
计算方式:used_memory_rss/used_memory
大于 1 表示有内存碎片,越大表示越多;小于 1 表示正在使用虚拟内存,虚拟内存其实就是硬盘,性能会下降很多。一般内存碎片率在 1 - 1.5 之间比较健康。
内存碎片
内存监控
相关参数均为执行完 info Persistence 的结果
持久化可以防止数据丢失
rdb_last_bgsave_status
上一次 RDB 持久化状态
rdb_last_bgsave_time_sec
上一次 RDB 持久化的持续时间
aof_current_size
查看 AOF 文件大小
持久化监控
单个key对应的value值非常大
什么是大key
可以找出五种数据类型中各自最大的key;
不能帮助我们找到整个数据里的最大的key
一般不适用该种方式监控
redis自带的redis-cli --bigkeys
结果的准确性不高,集合类型的扫描只能扫描出集合的长度,而不是value的大小
还会阻塞redis正常的执行
通过脚本或者命令扫描所有的key
通过解析工具解析rdb文件,获取大key
很多第三方的解析工具
常用解析工具
解析rdb文件
通过代码aop的方式,异步每一次key的大小
大key
必须使用Least Frequently Used 最近最少使用的缓存淘汰策略
执行命令的途中肯定会阻塞正常的redis命令的执行
redis自带的redis-cli --hotkeys命令
每一个链接redis的客户端,都做相应的热key监控
客户端监控
监控示意图
代理端监控
利用monitor命令的结果就可以统计出一段时间内的热点key排行榜、命令排行榜、客户端分布
monitor命令的执行影响正常的reids命令的执行
只能统计单机的热key
服务端监控
机器
四种方式优缺点对比
自建对热key监控
热key
key监控
slowlog-log-slower-than
默认10000微妙
执行时间超过了多久,会被记录到慢查询日志中
当慢查询日志达到最大条数时,如果有新的慢查询,会移除最大的慢查询。
slowlog-max-len
慢日志的长度
慢查询涉及参数
查询所有慢查询
slowlog get
显示最新的一条慢查询
slowlog get 1
具体命令
慢查询监控
cluster_state
集群状态
cluster_slots_fail
hash槽状态
cluster_known_nodes
节点数量
cluster info 命令中可以获取集群的状态
集群监控
keyspace_hits 表示 Redis 请求键被命中的次数
keyspace_misses 表示 Redis 请求键未被命中的次数
HitRate = keyspace_hits / (keyspace_hits + keyspace_misses)
info stats 可以获取两个指标
命中率越低,导致越多的缓存穿透,对mysql容易造成影响
建议缓存命中率不要低于90%,越高越好
缓存命中率
redis监控指标
redis监控实现原理
缓存监控
getActiveCount()
线程池中正在执行任务的线程数量
getCompletedTaskCount()
线程池已完成的任务数量,该值小于等于taskCount
getCorePoolSize()
线程池的核心线程数量
getLargestPoolSize
线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过,也就是达到了maximumPoolSize
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
前沿
模糊查询 节点名称中带有 socail的节点详细信息
microservice-payment-server-59cdc58dcc-fd7sw 1/1 Running 0 40m 10.39.0.51 k8s-test-worker-84 <none> <none>
可以看到对应的ip信息,运行状态
得到的信息
kubectl get pods -nsite -owide | grep social
1、模糊查询节点
k8s是在docker基础上又封装了一层
k8s命令操作
k8s命令操作docker
k8s命令
tail -f 日志文件名称
1、查看所有日志
查询日志信息中带有my name信息的日志
default log 是日志文件名称呢过
cat default.log | grep 'my name'
从日志的压缩文件中查询带有my name信息的日志
default.log.2022-12-07_00.0.gz 日志压缩文件
zcat default.log.2022-12-07_00.0.gz | grep 'my name'
2、精确搜索查询日志
test.log 新建文件名称
touch test.log
3、生成文件命令
1、查看日志命令
常用linux命令
查看当前所有的镜像
docker iamges
搜索镜像
docker search 镜像名
查看当前docker容器下运行了哪些镜像
docker ps
默认会拉取最新的镜像
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常用命令
docker远程仓库,类似于maven仓库,所有的docker软件的下载都是通过从远程的docker下载
dockerhub可以类似于maven一样构建自己私有的dokcer仓库
如何上传本地包到docker仓库?
dockerhub可以将自己的包上传到dockerbub上,所有人都可以下载使用
仓库dockerhub
docker 镜像可以理解成maven的本地仓库,所有的从dockerhub上下载的软件都是存放在本地docker 镜像仓库
通过images 中的镜像文件来创建docker容器运行环境
存储位置取决于images文件的保存形式
远程镜像仓库保存
此种方式,images保存在本地
本地保存
镜像时保存方式
容器运行时保存方式
images有四种保存形式
image 存储位置
镜像 images
docker从物理机上虚拟化出来的一个程序运行环境,容器与容器之间是相互隔离的
容器 container
基础
dokcer有势
docker容器编排技术
生产环境中的每一次的发布都需要拉取镜像,启动容器,如果需要启动很多歌容器,那么就是一个非常费时费力的事情,所以就诞生了docker compose技术,
通过compoase,可以使用YML文件配置中创建并启动所有的服务
compose实现的快速编排站在项目角度将一组相关的容器整合在一起,对这组容器按照指定顺序进行启动,也让项目使用到的容器组合更加清晰,方便不同服务器之间的迁移部署
解决的痛点问题
docker compose技术
docker
k8s自动化部署扩展管理运维多个跨机器docker程序的集群
自动根据物理机的资源环境来配置容器相关的运行环境
自动装箱
容器失败会自动重启
部署节点有问题时候,会对容器进行重新部署和重新调度
当容器未通过监控检查时,会关闭此容器直到容器正常运行时,才会对外提供服务。
自我修复
通过简单的命令、用户UI界面或基于CPU等资源使用情况,对应用容器进行规模扩大或规模裁剪
水平扩展
用户不需要使用额外的服务发现机制,就能够基于k8s自身能力实现服务发现和负载均衡
基于service这个统一入口对外提供服务、完成服务,实现服务发现和pod内部的负载均衡
服务发现
可以根据应用的变化,对应用容器运行的应用进行一次性或批量式更新
等应用更新的时候会先检测之前更新是否成功,成功则进行下一个应用的更新
滚动更新
可以根据应用部署情况,对应用容器运行的应用,进行历史版本即时回退
版本回退
在不需要重新构建镜像的情况下,可以部署和更新密钥和应用配置,类似热部署
密钥和配置管理
自动实现存储系统挂载及应用,特别对有状态应用实现数据持久化非常重要。存储系统可以来自于本地目录、网络存储(NFS、Cluster、Ceph等)、公共云存储服务
存储编排
提供一次性任务、定时任务;满足批量数据处理和分析的场景
批处理
k8s核心特性
k8s
运维
将同一模块的数据相关的数据进行操作,将整个项目分成多个小模块,高内聚低耦合
分割
分布式引用服务,分布式静态资源,分布式数据和存储,分布式计算(分配到多台服务器上进行计算);
分布式
分层,分割后的模块单独部署成一个服务集群;
集群
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.数据库读写分离:使用了缓存之后,大多数数据库的读操作都可以不通过数据库就能完成,但是依旧会有一部分读操作(没有命中缓存的,缓存过期的)和全部的写操作需要访问数据库;
将静态资源部署在CDN服务器上,反向代理服务器存放着用户需要的很多信息,可以直接返回这些数据给用户
5.使用反向代理和CDN加速
常用架构设计
缓存本身就是一个内存io密集型应用
缓存是通过牺牲强一致性来提高性能的
80%的缓存命中率就算很高了。
如果业务允许不是很实事的数据情况下,可以使用缓存解决
缓存使用原则
1、提高缓存命中率
过长,缓存长期没有被访问,浪费内存
过段,需要不断将缓存数据从数据库添加到内存
2、缓存时间周期
需要匹配当前系统的最佳的缓存淘汰策略
3、缓存淘汰算法
此情况相当于是在紧急情况下给缓存添加的一个后门操作
与程序的缓存系统分开,但是可以控制程序的缓存数据情况
4、设计缓存管理系统
设计重点
热点缓存
并发更新-数据一致性
缓存架构的问题
缓存架构设计
出于灾难备份目的,数据库都会建设两个或者多个数据中心
主数据中心承担用户业务,备数据中心对主数据中心进行备份
主数据中心挂了,备数据中心接管主数据中心业务
热备
主数据中心承担用户业务,备数据中心不会对主数据中心进行实时备份,可能周期性备份,或者干脆不备份
如果主数据中心挂了,用户业务也会断掉
冷备
主备两个数据中心同时承担用户业务,数据相互为备份,且主数据中心承担大多数业务。
双活
数据中心之间关系
本地+异地
两地
主数据中心/灾备数据中心,
双运营数据中心
双活数据中心
本地中心+本地容灰中信+异地备份中心
三中心
两地三中心
数据库架构
大型项目技术架构
1.数据库轮询,查数据库的表,查看转台;
代码中不断的轮询,延迟队列中超时的任务就会被poll方法
拿出来执行;基本都是用while(true)这种死循环去扫描这些超时的任务;缺点:都只是存储在内存中,集群模式下,单机只能处理单机的任务;
2.DelayQueue 延迟队列,把这些任务都放在延迟队列中
缺点和方法2的缺点是一样的;
3.循环队列
也是循环遍历要超时的任务,然后把这个任务做了,然后再把这个任务删除从Zset中删除;这些方法Jedis这些都是用方法的;
优点:解决集群模式下的任务;
1.数据量大了,也会造成性能问题;
2.zset集合中的数据越来越多的时候,zset的性能也会越来越低
存在的问题:
4.redis 的zSet数据结构;让Zset这个数据按照超时时间排序
一秒钟查询一次,然后才处理这一秒钟产生的订单信息;缺点:已经超时的订单将得不到处理;1秒内无法处理完这些数据,那么就会超时,下面就会有没有处理的订单
5.把数据存储redis的set集合中,key是时间戳,value是这个时间戳产生的数据集合
超时订单:就是延迟任务
超时订单处理
全局业务中必须要是唯一
全局唯一
这个id最好是能递增
趋势递增
接入方便
分布式Id要求
简单
无网路消耗
无业务意义
不是递增
存储消息空间大
UUID作为数据库主键,性能低下
UUID
获取数据库自增的主键作为全局ID
方式概述
实现简单
id是递增
数值类型查询速度快
如果数据是单节点,无法完全保证数据的高可以用
数据库自增id
给几台数据库服务器自增时候设置相应的步长(比如说,自增3,自增4),获取到自增id
解决单节点,并不能高可用问题
不利于后续扩容
数据库多主模式
从数据库批量获取自增ID
批量减少数据库压力
号段模式
号段方式是推荐的使用方式
数据库实现方式
通过node节点版本生成序列号
可以生成32位,64位
实现概述
ZK
利用的incr命令实现原子的自增
正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 数据中心(占5比特)+ 自增值(占12比特),总共64比特组成的一个Long类型。
雪花算法
和雪花算法类似
可自定义时间戳,机器ID,序列号
百度uid-generator
同时支持号段模式,雪花算法模式,可以切换
美团Leaf
号段模式的实现
滴滴(Tinyid)
全局唯一id生成方式
全局唯一id生成
1.先去查询数据是否存在
2.在做更新的时候对比当前数据的版本是否和第一次查询的时候版本号一致,一致才更新;
基于第一次查询和第二次做修改时候查询的数据来判断中间的数据有没有被修改过
利用在表中加入一个版本号
做修改操作钱先去表中查询,获取到相应的数据和版本号信息
在做更新的时候,拿之前查询到的版本和数据库当前的版本号对比,是否相同,如果相同才让做修改操作;否则就表示这个数据已经被其他线程修改过了,就不让做修改操作;
mysql实现
数据库乐观锁
通过RedisClient对象获取到RedissionLock对象
1.在try catch外面获取锁
通过RedissionLock对象进行加锁
2.在try catch中加锁
3.在finally里面解锁(防止死锁,解不了锁)
使用redisson连接方式
设置成功,说明当前就获取到了Redis的锁
设置失败,说明其他请求已经向Redis中设置过了当前值,其他线程获取到了锁,当前线程会阻塞等待获取锁;
1、添加成功相当于获取到redis锁
2、添加失败相当于未获取到redis锁
3、redis删除该key,相当于释放了锁
Redis的SETNX命令给向reids中添加键值对,key不存在就是添加成功,key存在就添加失败
1.先判断key是否存在,不存在才会加锁;
2.如果key存在,那么通过lua脚本判断,获取锁的客户端id是否和当前客户端的id相同,也就是进行可重入判断;
3.如果客户端id也不一样,那么会返回一个当前获取锁需要等待的时间,并且进入while死循环中等待获取锁;
Lua脚本加锁过程
未设置,程序异常,导致当前代码永远死锁
1、锁的释放操作需要放在finnaly代码中;
防止获取到锁的程序,由于某种原因一直不释放锁,阻塞后续线程执行
2、给key设置超时时间;(相当于设置锁的有效期时常)
适时释放锁
超时时间需要足够长,来确保执行的任务的时间远远小于超时时间
超时时间设置
防止sentx命令执行完成,再去执行设置超时时间中间,reids宕机;
该操作由redis提供相关的操作命令
确保sentx命令还有设置超时命令是同一个原子操作;
防止,自己设置的锁被其他业务误删;
加锁,解锁两个操作都是同一个线程
锁需要具备可重入性
获取不到锁的线程,需要让这些线程做获取的锁的等待从而可以执行锁中间的代码,而不是直接返回到客户端
死循环不断的尝试获取锁操作,获取到锁操作后,跳出死循环;
锁需要具备阻塞和非阻塞特性
锁失效后,其他线程可以直接获取到锁对象,两个线程同时执行锁中间的代码,无法保证串行执行;
需要给锁有效期续命
锁失效
设计需要注意的点
分而治之,类ConcurrentHashMap种的分段锁
将需要被并发访问的数据拆分成多份,每一份数据对应设置一个分布式锁
核心思想
高并发下秒杀商品,商品库存有1000个,将商品库存缓存到10个key,value中,每一个key value的缓存只有100个,这样并发度就是原来的10倍
当获取的key对应的库存数据为0时候,需要释放当前锁,且需要再次获取其他库存不为0的锁;
特别注意:
案例
高并发分布式锁设计
运营后台需要针对线下商品的批量操作,一个商品可以绑定200-300个门店,批量100个商品就会有2W-3W条数据更新
2W-3W条数据,使得程序响应过慢,运用以为操作失败,然后接着继续狂点,导致服务器内存溢出宕机;
背景说明
2、后面重复操进来的数据,发现锁已经被获取,就放弃执行当前操作,显示已经在执行中;
1、批量操作防止重复操作
以鸡蛋,大白菜,食盐等商品做1毛钱秒杀活动,达到线上引流目的
key为商品id,value为hash表,hash表中的字段就是各个子锁key,值为库存数据
1、初始化分段锁信息
2、用原子类的Long记录请求请求数量
3、对请求数量按照锁的个数取模,以此来确定需要访问具体的是哪个子锁
5、获取到锁之后,执行业务代码,然后在最后更新hash表中的库存数据,也更新当前子锁对应库存数据
6、最后解锁
7、未获取到锁的线程,在做死循环自旋的时候,每次sentx操作之前都是需要获取hash表中库存数据;确保获取的库存数据是最新数据
2、显示抢购商品秒杀活动
分布式锁使用场景
Redis分布式锁
1.通过zkClient对象创建临时节点,作为加锁
2.通过zkClient对象删除临时节点,作为解锁
1.zk是不允许创建相同的临时节点
2.如果zkclient对象发现已经相同的临时节点已经存在了,那么就会阻塞被阻塞;
3.阻塞实际上就是等待创建了这个节点的客户端去删除这个临时节点;
4.阻塞结束之后,创建临时节点,就获取到了锁
zk不允许重复创建临时节点
有创建临时节点,就会客户端就会被阻塞
本质
ZK分布式锁
分布式锁对比
强一致性
Consistency
Availability
分区容错性
Partition tolerance
CAP
这个理论中的一致性指强一致性,而不是弱一致性
CAP只能保证两点
一般情况下保证AP和最终一致性
CAP选择
分布式事物概述
集成了4中分布式事物解决方案
AT 模式、TCC 模式、Saga 模式和 XA 模式
seata解决方案
事物的第一阶段其实已经提交了;事物的回滚操作是在原来正向操作的基础上做的一个逆向操作,这个逆向操作seata已经帮你做了;
原来的一个insert操作变成delete操作;delete操作变成insert操作;update修改操作会修改回来;
seata
对代码无侵入性的分布式解决方案
业务无需编写各类补偿操作,回滚由框架自动完成
AT事物特点
超链接
AT模式
适用于那些需要处理复杂业务流程、同时涉及多个独立事务单元、具有复杂性和不确定性的场景
对性能有很⾼要求的场景
高性能分布式事物的解决方案
基于2pc协议
TCC设计
预留业务资源
Try
确认执行提交事物
Confirm
执行事物回滚
Cancel
事物3个操作
资源预留阶段
事物提交阶段/事物回滚阶段
TCC可以分成两个阶段
TCC概述
需要标记Try,confirm,cancel方法
Hmily轻量级分布式事务的框架, 无需搭建Hmily服务器
Seata
ByteTCC
EasvTransaction
TCC事物实现方式
并发度较高,无长期锁定事物资源
一致性好
TCC适用于订单类业务,对中间状态有约束
TCC事物特点
TCC模式
同一项目中直连两个数据库,做修改操作
mysql oracle等关系型数据都是支持两阶段提交协议的,也就是有一个支持多个事物的接口;
两阶段提交主要保证了分布式事务的原子性:即所有结点要么全做要么全不做)
备注:主要是这些数据库都遵守2PC协议
主流数据库:mysql、oracle、sqlserver、postgre都是支持XA事物
依赖于数据库支持XA事物模型
第一阶段每个事物完成预提交并通知把结果告诉事物协调器;事物协调器等所有分支事物都操作完成,预提交之后;第二阶段协调器通知每个事物进行逐步的提交或者是逐步的回滚操作;
2PC协议
事物协调器向所有的事物发起者询问是否可以执行提交操作,并等待各个参与者节点的响应
参与者节点执行询问发起为止的所有事物操作,并记录到redo,undo日志中;
事物的各个参与者对事务管理器响应自己的事物是执行成功还是执行失败;
准备阶段
协调者只要收到一个事物失败或者超时,那么就会给每一个参与者发送回滚消息否则发送提交消息;
提交阶段
二阶段详细过程
理论基础
事物过程
事物粒度大,高并发环境下,系统可用性比较低
<!-- atomikos 多数据源管理 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jta-atomikos</artifactId></dependency>
springBoot整合atomikos
<dependency><groupId>com.atomikos</groupId><artifactId>transactions-jdbc</artifactId><version>4.0.6</version></dependency>
spring整合atomikos
无整合开发
1、AtomMikos
当前系统调用了内部的多个数据库,或者是多个服务,在调用方这边直接做数据回滚;
2、finnally里面做事物回滚
XA模式是分布式强一致性的解决方案;
简单易理解,开发较容易
对资源进行了长时间的锁定,并发度低
XA模式优缺点
XA模式
将长事物拆分成多个本地事物,由Saga事物协调器协调,正常结束就正常完成,如果某个步骤失败,则根据相反顺序以此调用补偿操作;
saga事物理论基础
流程比较长且只需要保证事物最终一致性的长事物解决方案
事物的参与者是其他公司的服务
第一阶段已经提交了事物,性能比较高;
事物回滚操作基于时间异步执行,效率高;
逆向补偿服务,易于理解,易于实现;
事物在第一阶段已经提交了,也就是没有预留动作保证隔离性。
给调用方提供接口回调,和不断地回调调用者,最大程度上让调用方知道被调用者这边是否执行结果
最大努力通知
公司内部系统直接相互推送某个事物是否执行成功;相当于给其他系统通知这个事物是否成功。
消息队列通知
实现方式
事物并发度高,不会长时间锁定资源
需要定义补偿相关操作,代码量大
一致性弱,采用补偿机制实现事物一致性
每个事物都有其正向的操作和逆向的事物回滚操作
正向服务,逆向补偿服务都是开发者来实现的,对业务造成了很大的侵入性
saga事物特点
最大努力通知型,消息通知方需要不断的通知消息发送方,来保证最终一致性
可靠消息一致性,实用通知方保证把消息发送出去,一致性是由消息发送方来保证的;
1、思路不一样
最大努力通知,解决的是消息接收到了之后的一致性;
可靠消息,解决的是消息发出到接收的一致性;
2、技术解决方向不同
最大努力通知型,关注的交易后的通知事物;
可靠消息一致性,关注的整个交易过程的事物;
3、使用场景不一样
方案差别
1、中间事物参与者提供查询事物是否成功接口;2、后续事物参与者调用该接口,获取事物是否成功消息
利用接口查询做补偿
流程图
事物流程图
最大努力通知型
1、中间事物参与者发送事物成功或者失败的消息;2后续事物参与者订阅消息完成最终事物
利用消息队列来通知消息
消息最终一致型
具体实现方案
saga事物解决方案
Saga模式
saga
分布式事物模型
最初是 ebay 架构师 Dan Pritchett 在 2008 年发表给 ACM 的文章
需要分布式处理的任务通过消息的方式来异步确保执行
写本地消息和业务操作放在一个事务里,保证了业务和发消息的原子性,要么他们全都成功,要么全都失败
流程
长事务仅需要分拆成多个任务,使用简单
生产者需要额外的创建消息表
每个本地消息表都需要进行轮询
消费者的逻辑如果无法通过重试成功,那么还需要更多的机制,来回滚操作
本地消息表的特点
扣减余额事务 失败时,事务直接回滚,无后续步骤
轮序生产消息失败, 增加余额事务失败都会进行重试
容错机制
本地消息表示意图
适用于可异步执行的业务,且后续操作无需回滚的业务
适用范围
本地消息表
分布式事物
单点登录
分布式系统解决方案
一种原子承诺协议,一种分布式算法,协调参与分布式事物的所有应用提交事物,或者回滚事物
协议定义
1、协调者向所有事物参与者发送事物内容,并等待所有参与者执行的结果答复
2、各参与者执行事物操作,并且记录事物日志,但是不提交事物;
3、参与者向协调者反馈事物执行的结果;
阶段1:准备阶段
1、协调者向所有参与者发送事物提交消息;
2、参与者提交事物,释放事物锁定期间的资源;
3、参与者向协调者反馈事物执行成功消息
4、协调者收到各个参与者执行成功的消息,事物就成功
提交事物
1、协调者向所有参与者发送事物回滚消息;
2、参与者做回滚操作,释放事物锁定期间的资源;
4、协调者收到各个参与者执行成功的消息,事物就中断
回滚事物
阶段2:提交阶段(回滚阶段)
2PC协议过程
阶段1:
阶段2:
2PC协议示意图
最大问题
参与者向协调者反馈事物执行的情况,直到等到协调者反馈,是做提交还是回滚操作;之间的时间参与者都是处于阻塞状态;
同步阻塞问题
如果参与者或者协调者,一方不可以用,导致事物数据不一致;
数据不一致问题
2PC协议问题
主要是为了解决2PC中同步阻塞问题;
背景来源
非阻塞协议,在实体提交或者终止之前增加一种超时机制,当超过这个时间上线事物还未提交,就会把该事物绑定的资源释放
特点:
备注:如果协调者在接收事务请求时出现故障或者不可用,协调者将直接中止事务
1、协调者接收到事务请求后,向所有参与者发送能否提交请求,等待参与者返回信息
2、参与者接收到协调者的的是否提交事物请求后,发送是否可以提交事物请求
阶段1:投票
备注:如果参与者都同意后会向协调者回复yes消息并进入准备状态,如果参与者获取资源失败或者出现不可用会回复no中止事务
1、协调者,在超时时间内收到了各个参与者返回的可以提交按钮
2、协调者,向各个参与者发送预提交消息,参与者执行事物,并且记录日志,但是不做提交操作,并将执行结果返回给协调者;
阶段2:预提交
1、协调者接受到各个参与者发送回来的消息;
2、根据消息,做出是提交事物,还是回滚事物的决定,然后将将消息发送给各个参与者;各个参与者再根据协调者发送的消息,做回滚或者提交,并释放事物资源,向协调者反馈消息;
阶段3:提交或者回滚
3PC过程
1、阶段1如果有返回不能提交事物
2、阶段2事物执行失败
3、预提交阶段中,协调者超时未收到消息
事物回滚情况
阶段3参与者未收到协调者发送出来的提交或者回滚消息,那么就会自动提交事物
自动提交事物
阶段3参与者未收到协调者发送出来的事物回滚的消息,那么参与者都是自动提交,就会造成事物不一致的情况;
3PC问题
3PC协议
协调者直接终止事物,向事物参与者发送事物回滚信息
分布式基础概念
分布式理论
根据业务的不同实现分片的算法,让一个定时任务分散到不同的机器上执行,减少单台机器的压力
dangdang网开源的elastic-job 框架借助zookeeper注册中心,实现分布式任务调度
<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>
1、引入相关依赖包
2、zk,job相关配置
/*** @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\
3、创建zk注册中心配置
@Component@Slf4jpublic class ClockRemindJob implements SimpleJob {public static int count = 1;@Overridepublic void execute(ShardingContext shardingContext){ String jobParameter = shardingContext.getJobParameter(); String time = DateUtil.formatDate(new Date()); System.out.println(\"当前时间是:\"+time+\
4、定时任务操作类
@Configuration@Datapublic 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}\
5、定时任务配置类
6、整合类
elastic-job分片
在定时任务开启的之后,根据当前服务的台数,和总的需要执行任务的数量,相除,得到单个服务器需要执行的任务数量。然后再根据
设计一套根据服务器mac地址或者其他唯一标记的数据做hash算法,让服务集群之内的所有机器都有有一个对应的hash值(比如说服务集群有五个服务,那么每一个机器得到一个1-5的数值。)将需要执行的任务的数量,分成5分,每一个机器只执行属于他的那些任务;
定时任何分区
定时任务
秒杀系统特点
正常流量
羊毛党流量
瞬间流量激增
商品定时上架
秒杀商品库存少
商品购买流程简单
1.读多写少
限流
队列
2.高并发
原子操作
3.资源冲突
秒杀系统业务特点
请求过多
磁盘IO压力大
瞬间流量高,对服务压力造成冲击
商品可能被超卖
秒杀系统存在问题
浏览器缓存,本地缓存
验证码
应用层
CDN
网络层
系统上游拦截请求,降低下游服务器压力
负载均衡,动静分离,反向代理缓存,限流
负载层
后台请求可以做削峰处理
利用缓存替代磁盘IO,提高读写下效率
动态页面静态化,应用缓存|分布式缓存,异步,队列,限流,原子操作保证
服务层
商品超卖问题,加全局锁
乐观锁,悲观锁
设计思路
秒杀时间未到,按钮置灰
定时上架
秒杀请求发送前,校验用户是否已经登录
用户已经登录,需做验证码校验
验证码校验完成,按钮置灰,不让发送二次请求
前端削峰处理
秒杀页面做成静态页面,放在CDN节点上,避免页面刷新请求后台服务
使用LVS 服务做第一层请求转发服务器
使用nginx 服务接受LVS请求,让后再做请求转发
硬件削峰处理
上游削峰处理
网关层,做黑名单校验,防刷校验,不满足条件请求,直接返回
秒杀系统服务集群接受到请求,发送mq消息,到真正做秒杀的服务器
下游削峰处理
使用redission工具包
将库存量大小作为信号量的构造参数,
高并发的情况下, 悲观锁比乐观锁效率高
相应速度,冲突频率,重试代价
乐观锁基于重试的代价太高了
数据库悲观锁,乐观锁
使用redis分布式锁
库存防止超卖
1.验证码;
2.容器限流
3.数据库相关的参数优化
一秒钟可以产生多少令牌,只要拿到令牌的请求才能接着往下走秒杀流程
Guava 可以做令牌桶,ateLimiter一秒钟可以产生多少令牌,只要拿到令牌的请求才能去接着往下走;
令牌桶
限制一秒多少个请求
Nginx 有自带的漏桶算法限流;Nginx.conf 的配置文件就可以配置这个;
漏桶
限流算法
4.手动限制某些接口的访问量;
5.nginx限流
6.消息队列
技术方案实现
秒杀系统设计
1.我方放出信息,对方也能接受到信息;并且能够正确将相应信息返回来
2.我方虽然执行了发送信息,但是信息并没有发送出去
3.我方虽然将信息发送出去了,但是对方并没有接受到信息;
4.我们将信息发送出去了,对方也接受了,但是我方并没有收到对方的相应信息;
由于网络原因导致三种坏的情况
支付的网络传输分为四种结果
1、提供一个异步回调接口,让对方在支付完成之后毁掉我们接口
1、对方异步回调
让对方提供一个查询接口,方便查询订单实际支付的情况
2、对方提供查询接口
容错处理
支付系统
解决数据量过大,导致的性能问题,有原来的单库单表,拆分成多库多表,解决单库单表的数据量过大的问题,从而解决性能问题
解决问题
按照业务来分库,不同的业务放在不同的数据库
垂直切分
单表的数据分在多个库(1.把单表的数据拆成多个单表(字段不变)
水平切分
在实际引用过程中可以是垂直拆分+水平拆分结合
不同的业务进行垂直拆分,成多个库,相同的业务(如果这个业务很繁忙)又可以分为多个库;分库分表的时候,需要注意是按照那个字段进行分库的
1.分布式事物的一致性
分多次查询,合并查询结果集
2.跨节点关联查询
针对关联查询,有些在分库分表的时候,有些数据会对应分在一个库中,(比如一个商品表,主要信息和详细信息都会存放商品id)那么分库的时候就会把相对应的数据存放在一个数据库中,那么就不用夸节点查询了;
3.跨节点分页,排序
全局主键
垂直分:1.按照不同的业务分开;2.将单表字段拆分到多个表(常见的查分标准:常用和不常用;大字段,小字段;组合查询的放在一起) 如果把这些表都放在一个库上,就是垂直分表,库在一个服务器上,存在对计算机cpu资源竞争,所以把这些表放在不同的机器上,避免资源竞争,这样就是垂直分库;
4.主键避重
分库分表造成问题
库表设计
全局异常统一处理
统一处理
全局修改操作记录到日志表
管理系统
项目设计
同一业务下的定时任务模块
直接查询属于本业务的库
直接调用本业务的原子微服务
直接调用本服务的非原子微服务
获取本业务数据
1.调用其他原子的微服务
2.调用其他服务的非原子微服务
获取其他业务数据
Job
同一业务下消息队列模块
MQ
同一业务下pc端接口
web
同一业务下对APP接口
App
同一业务下原子的微服务接口
Ms
同一业务下非原子的微服务接口
可以聚合其他业务的原子的微服务接口
Api
2.同一业务下不同的功能划分
1.按照不同的业务划分不同的项目
服务拆分
平台化出现原因
复用性强
研发效率高
降低复杂性
稳定性
平台化优点
嵌入式
接口依赖式
中台式
平台化建设方式
DSL领域特性语言
Specification规约模式
异构
统一存储
平台化建设常用模式
平台建设
自动回滚
测试环境可部署多套环境
依赖包版本控制
自动化发布系统
数据表结构变更,从测试环境,自动变更到预发布环境,自动变更到线上环境
生产环境禁止使用delete,update操作
权限控制
mysql操作平台
自动化将低环境配置同步到高环境中去
配置中心操作平台
redis操作平台
rocketMq ,kafka操作平台
流计算操作平台
大数据计算平台
ES-kibana操作平台
日志搜索平台
对各个操作平台的系统权限控制平台
常见操作平台
系统架构
性能调优
场景类
代码复用优化
业务调优
设计层面优化师优化手段的上层,根据系统的难点和潜在的问题,评估设计出合理的设计方案
缓存使用
常量池
连接池
对象池设计
IO优化,选择多路复用
消息队列根据具体业务,设置对应的确认机制
使用序列化效率高的组件
io多路复用
具体方式
设计层面
减少对象的创建,减少内存消耗
减少对象的频繁创建
单例设计模式
合理使用设计模式
字符串操作避免内存泄露
切割查找选择最快方式
字符串优化
根据当前业务查询多还是修改多,合理选用对应的容器
局部变量的容器,选择对应的操作快速的线程不安全的容器
容器的初始化长度可以,根据预估的数据量
容器优化
位运算替代除法操作
避免二维数组使用
GC不会回收静态方法占用的元空间数据
减少静态方法
对象需要开辟堆内存,基本类型需不要堆内存
满足业务的情况下使用基本类型替代对象
代码中减少对第三方循环调用,第三方提批量接口
合理多使用switch 语句 替代if else
时间复杂度,空间复杂度
合理的使用空间换时间,或者时间换空间的思想
锁对象使用合理
乐观锁
降低资源竞争
io操作释放对应的资源
代码中可以手动将不再使用的大对象手动回收
同步变成异步
算法优化
针对搜索时间,内存大小设计合理的数据结构
代码层面优化方式
代码层面
可以调整减少执行次数,再进行热点编译,针对一些执行频率很高的代码,可以在前期优化时间;
编译优化
根据对内存,cpu告警信息做适当调整
cpu核数,内存调整
JVM层面
1、合理设置索引
2、合理的提前生成统计数据
3、连接池优化
4、减少关联查询
sql优化
1、合理分库分表
2、合理冗余字段
库表结构优化
内存更大, 磁盘更大, cpu核数更多,运算速度更快
数据硬件优化
数据库日志刷盘策略
缓冲池大小
数据库参数优化
数据库层面
会影响io多路复用
文件最大句柄数量
操作系统层面
调优层面
cpu时间
内存分配
磁盘吞吐量
网络吞吐量
并发量
优化评定指标
明确调优目的,不要为了调优而调优,盲目调优可能适得其反
调优前注意事项
调优角度
1、在同一个时刻,每个cpu核心只能运行一个线程,如果有多个线程需要执行,cpu只能是通过上线文切花的方式来执行不同的线程
记录线程执行的状态
让处于等待中的线程执行
cpu上下文切换做的事情
过多的上下文切换,占用了过多的cpu资源,导致无法去执行用户进程中的cpu指令,导致用户进程中cpu相应速度过慢
1、文件io操作
2、网络io
3、锁等待
4、线程阻塞
上下文切换过多的场景
多查询一些线程,是否处于阻塞状态中
统一给这些io设置线程池,不让他们占用过多的系统资源
如果是io导致,不管是网络,还是文件,
1、cpu上线文切换过多
1、创建了大量线程
2、有线程一直占用cpu资源,死循环
过度消耗原因
2、cpu资源过度消耗
导致cpu使用率飙升的原因
1、通过linux系统的top命令来找到cpu利用率过高的进程
查询进程信息,查看线程
2、ps -mp 进程号
通过jsatck工具根据线程id去下载线程的dump日志,然后定位到具体的代码
cpu消耗过高的是同一个线程
结果1、程序正常,在cpu飙高那一刻,用户访问量增大
做针对性调整
结果2、也有可能程序不正常,线程池的创建相关有问题。
说明线程创建的比较多,拿几个线程id去下载线程的dump日志,
cpu消耗过高的是不同的线程
出现两种情况
2、shirt +H来找到进程中CPU消耗过高的线程
linux系统解决
1、查看docker的cpu占用率:
docker stats
2、进入cpu占用高的docker容器
docker exec -it 容器编号 /bin/bash
查看容器中具体进程cpu占用率,执行top
查看进程中线程cpu占用率:top -H -p 进程号
将异常线程号转化为16进制: printf “%x\” 线程号
查看线程异常的日志信息:jstack 进程号|grep 16进制异常线程号 -A90
各种监控系统辅助我们排查
CPU突然飙升,系统反应慢,怎么排查?
让jvm发生内存溢出的时候自动生成dump文件,并且存放在某个固定路径下面
1、通过设置jvm参数
2、通过执行dump命令来获取当前jvm的内存快照
1、先获取堆内存快照文件
专门用来分析dump文件的
1、mat工具
2、jprpfile工具
dump文件分析工具
2、通过mat工具分析dump文件
3、解析dump文件,得到内存统计信息总览
dump文件中内存占用情况
4、点击dominator Tree
5、获取到内存占用最高的类
根据代码的情况惊醒调整
6、然后再查看这个类中相信的内存占用情况
排查问题方式
线上服务内存溢出如何定位排查
调优实战
系统调优
信息摘要算法5
单向不可逆,无法解密
产生一个固定长度的散列码
密码存储
信息完成性校验
MD5
散列消息鉴别码
HMAC系列
SHA系列
加密方式
不可逆特点
不可逆加密
公钥加密,私钥解密
RSA
DSA/DSS
非对称加密方式
1.比对称加密更加安全可靠;
2.加密解密比对称加密慢很多
HTTPS建立SSL连接中有一个步骤就是,将服务端的公钥加密用于对称加密的密钥;也就是把数据传输的对称加密的密钥发送给服务端,这样客户端和服务端都知道了实际做数据传输的对称密钥了
非对称加密
加密解密都是用的同一个密钥
DES
3DES
AES
对称加密方式
1.加密解密速度快;
2.适用于大量数据加密
3.由于需要把密钥传递给需要用的对接方,在传递的密钥的过程中容易被人抓包,造成数据不安全;
HTTPS建立SSL连接之后,客户端和服务端使用的就是对称加密来传输数据;主要就是利用对称加密解密速度快的优势
对称加密
对称加密性能高于非对称加密
性能
非对称加密安全性比对称加密安全性高
对称加密,加密解密都是用的同一个密钥;非对称加密,加密解密密钥是分开的;
加密解密方式
对称/非对称加密区别
可逆加密
可逆:可以通过解密的密钥获取到加密之前的数据;不可逆:数据是没有办法做解密操作,无法拿到解密之前的数据;
可逆/不可逆区别
增加数据被破解的难度
让相同的数据在被加密之后显示的数据是不一样的;
加密盐
数据加密算法
查询和修改操作都是幂等;添加和删除不是幂等
分布式环境下怎么保证接口的幂等性
1.用户对于同一操作发起的一次请求或者多次请求结果都是一样。
天然幂等的
查询操作
删除一次和删除多次可能返回的数据不是一样
删除操作
如果把某个字段的值设置为1,那么不管多少次都是幂等
如果把某个字段值加1,那么就不是幂等
更新操作
重复提交会有幂等性问题
添加操作
具体操作
幂等性概述
一般场景:代码中通过代码逻辑判断
支付场景:通过唯一的订单id来判断重复
1.通过代码逻辑判断实现
支付场景:页面跳转的时候获取全局唯一的token,把token缓存起来;等到提交到后台的时候,再校验token是否存在,第一次提交成功就会删除token
使用场景:分布式环境
2.使用token机制实现
分布式环境中,做重复校验,直接用乐观锁实现
3.乐观锁
幂等性解决方案
接口幂等性
通过Redis的key的事件通知机制
Redis的事件通知,消息只会发送一次,不管客户端是否有收到(无消息确认机制)
需要对某些key的改变做监控
RocketMQ设置固定延时时间发送消息
延时的消息时间只能是十几个固定的延时时间
1.正常队列不设置消费者,给单个消息设置超时时间;
2.设置死信队列和死信队列的消费者,把超时未发送的消息发送到死信队列;
RabbitMQ
时间轮定时任务
先对数据先进行落库,再对数据库进行定时轮询
对数据库压力大
实现Java的延时队列,这是固定的延时时间;
java延时队列
延时任务
NGINX
Tomcat
容器限流
手动控制某些接口访问
gateway
Semaphore类控制并发访问数量
任务线程池控制方式
服务器限流
限流方式
漏斗桶
计数器
流量计算方式
接口限流
3.资源访问冲突
秒杀系统的特点及对应解决方式
点击之后,一段时间之后才能再次被点击
按钮控制
需要收到验证码才能点击
验证码控制
控制方式
降低用户秒杀系统的点击频率
控制目的
负载均衡,反向代理,限流
降低服务器访问压力
缓存,异步,队列,限流,原子操作
降低对数据库的压力
不同层级对应解决方案
CDN作用
访问的是redis的某一个节点,当对这个节点中lua脚本加锁的时候,所有的请求到了lua这里都是需要做等待同步。lua脚本的执行肯定是在单个redis服务器上执行,所以可以保持同步;如果这个访问的是多个节点
lua脚本为啥可以原子性执行
zk也是用java写的中间件,在正常的访问中就是通过创建临时节点是否成功表示是否获取到节点,正常的访问zk的时候,只会访问到zk的主节点,给主节点中的创建临时节点加上synchronized同步就可以使得zk在分布式同步;
zk为啥可以原子性实现
分布式锁实现
高并发问题
List集合
banner
三个豆腐块
为你推荐条件
首页
Hash表
骨架星级
注册地
车龄
里程
车辆级别
排放标准
车源属性
燃料类型
筛选条件
场次
拍场模式
所有品牌车系
热门品牌
业务城市数据
查询条件
查询结果集
竞拍大厅
通过zset排序用户出价的品牌车系;每次出价的时候异步去更新redis的记录以及数据库
为你推荐
车源id存放在Zset缓存,从缓存中拿到车源id;再去ES中获取数据;
心愿单的查询条件
心愿单
一个月前未中标
近一个月未中标
交易失败
交易成功
我的出价
账户余额
保证金
我的账户
做自增操作
INCR
出价次数
关注次数
数据存入缓存场景
redis缓存
模块拆分方式
微服务项目
定义:测试各个逻辑是否正确
更早发现问题
缩短产品周期
更早发现底层问题
接口压测
逐步增加系统租在,测试系统的性能变化,并在最终满足性能指标的情况下,测试系统所能承受的最大负载测试
定义
检测系统运行的最大上限,使系统能够在最大压力下正常运行。获取最大上限数据
目的:
不断地在单位时间内增加请求个数,直到服务器的某一个资源(cpu,内存,网络延迟,等指标)达到饱和或者临界值
方法:
这个是最重要的指标
并发用户数
持续运行时间
数据量
系统负载指标
负载测试
检查系统是否有并发问题导致的内存泄露,线程锁,资源争用问题
确定用户并发数,必须直到系统锁承载的在线用户数,然后在单位时间内 同事发起一定量的请求
方法
login seesion 数量* session 时间 / 考察时间段
平均并发用户数量
平均并发用户数量 + 3(平均并发用户数量 开平方根)
巅峰用户数量
一个oa系统有3000个用户, 平均每天大约有400人访问系统, 一天之内用户从登录到退出系统平均时间4小时,一天时间内,用户只在8个小时使用该系统
400*4/8 = 200
平均并发用户量
200+ 3(200的整数平方根)
确定用户数的方法
并发测试
不断给应用增加并发数量,强制让其在极限情况下运行,观察其运行到何种程度,从而发现其性能缺陷
查看系统能够承受的最大并发量,达到多少的时候系统才会奔溃
以负载测试,并发测试为依据,
接口响应时间
接口并发数量
内存,cpu,等指标
压力测试
1、通过负载,并发,压力测试,可以知道系统的极限,可以更加合理的设置系统的各项告警阈值
2、核心,频繁使用的接口,不做相关的测试,贸然上线,对系统的稳定性是一个极大的考验,并不知道接口,相关的极限值,容易把系统搞崩
内存,cpu数量和接口响应数据,并发数所呈现的函数关系,可以方便调节系统的内存,cpu
3、获取到相关运行数据,可以方便之后的调优
收获
性能测试
系统在各项指标比如说cpu,内存在极限状态下所能达到最大的QPS,吞吐量,并发量,用户的最小响应耗时
什么是承载能力
通过相同的机器数量可以得到该服务的总的最大并发数量
如何计算系统的承载能力
系统峰值呈现28原则, 百分之80的流量会在20%的时间内到达
峰值qps= PV* 80% /(60*60*24*20%)
pv = page view 页面浏览次数
uv = unique vistor 不同用户访问数量
如果业务方可以给出
根据往年的数据量,做出相应的评估结合当下的数据
访问量数据
机器数量 = 峰值qps / (单台压测得到的极限的qps)
机器数量评估
评估系统的承载能力
服务器台数,服务的硬件,核数,jvm内存设置,磁盘大小,垃圾回收怎么设置
需要单台服务器极限下的qps
当前单台机器的qps
当前水位 = 当前所有机器的总的qps / (单台机器的极限qps *服务器数量 )
系统当前水位评估
应用正常的使用率0%-75%之间
90%及以上cpu使用过高
cpu使用率
一天有1000w点击量
1000*10000 *80% / (60*60*24*20%) = 450
峰值的qps = 450
二八原则估算
qps峰值估算
qps的估算需要结合接口的99线数据,已经cpu的核数,服务器个数
获取单个接口的平均响应时间,1s除以这个99线,得到1s中单个cpu的qps,乘以cpu的总核数,乘以cpu利用率80%得到单个接口的qps数值
实际情况下,因为线程之间的切换消耗cpu资源还有cpu等待,等等原因,实际的qps数量 并不会像这种线性关系;实际还需要进行压测才行
单个接口的qps
每一个接口调用频率差别很大,每一个接口的相应时间也很大,根据调用频率还有响应时间得到一个综合状态的平均响应时间,然后再通过1s除以这个综合平均响应时间,再乘以80%,得到单个cpu的qps,然后再计算总得qps
实际情况下,还是需要通过压测得到对应的关系曲线
项目整体qps
qps估算
一般情况下 8核16gb的机器,qps可以达到300-400,如果使用了缓存,qps达到1000没有啥问题
qps计算
系统在单位时间内处理请求的数量
TPS
QPS
量化指标
请求对cpu的消耗,外部接口,网络磁盘IO
系统吞吐量要素
吞吐量
QPS*平均响应时间
系统并发数估算
需要结合qps的峰值,以及平均响应时间
qps的峰值2000,平均响应时间50ms
2000/((1000ms/50ms)*80%)/ 4 = 2000/16 = 128核
计算中间需要加上cpu 饱和使用率 80%
实际情况下:128核 服务器,可以拆分成几个32和或者几个16核的服务器;可以做对应的服务器拆分;
demo:
cpu核数估算
是密切相关的,可以根据qps数量,平均响应时间预估得到cpu总的核数,然后再根据核数可以拆分成几台对应的服务器
服务器的台数和cpu核数估算
根据系统的qps,每一个请求会占用多少内存,机器的选型,年轻代应该给多少,老年代给多少,对应进入老年代速度,Full gc的频率
尽量让对象都保留在年轻代,不让进如老年代,减少full gc
预先预估
根据压测环境JVM运行状况,如发现对象过快进入老年代,就需要采用之前介绍的优化思路,合理调整新生代、老年代、Eden、Survivor各个区域的内存大小,保证对象尽量留在年轻代,不要过快进入老年代。当对压测环境下的系统优化好JVM参数之后,观察Young GC和Full GC频率都很低,此时就可部署上线
系统压测jvm优化
天高峰、低峰期观察线上系统的JVM运行是否正常,有无频繁Full GC的问题。有就优化,没有就平时每天都定时或每周都看看
监控jvm内存
jvm内存预估
cpu核数
内存
有大量的数据库日志写入,以及mysql本身数据的写入
IOPS
写入延迟时间
io相关的性能指标
磁盘随机度的极限100MB/S
磁盘io和带宽需要达到一致相匹配的数值才能发挥当前硬件配置下,最大的性能
磁盘io性能
网络之间的数据传输都是需要占用带宽,如果带宽不够也会影响传输效率和时间
带宽
性能瓶颈点
8核16G每秒1-2千个并发
8核32G每秒2-3个并发
性能测试评估
mysql服务的最大并发连接数量16384
mysql数据库性能评估
系统承载能力评估
kafka节点挂了
消费者消费速度过慢,生产者生产速度过快
导致原因
如果有,需要针对型处理
1、检查生产者发送消息是否正常,是否有大量重复消息,或者不必要发送的消息;检查消费者消费是否正常,是否有性能瓶颈,
1、开启多线程消费
2、添加消费者服务器节点
3、批量拉取消息
4、消息消费快速入库,快速返回成功,开启后台线程去消费消息;
2、提高消费者消费速度
消息堆积
消费者未能提交正确的消费成功消息,或者服务端没有接受到此消息
代码逻辑中保证幂等性,使用消息id,获取业务id来做幂等性判断;
消费重复消费
生产者自己异常,导致消费发送失败,生产者会有重试机制,重新发送消息
消息重试机制
ACK =0
ACK=1
ACK=all
消息确认等级
设计上是比较折中的在一定程度上能够保证消息的不丢失,也能保证一定的吞吐量
ACK=1(默认设置),只要分区的leader副本接受到了消息,就会给生产者发送消息接受成功(其他副本再回去同步leader的数据)。
缺点:网络宕机的时候,消息会丢失
优点:满足大吞吐量的数据发送;
ACK=0,生产者给服务端发送消息,不等服务端是否有接收到消息,发送完了就认为消息到被服务端接受了;而实际情况是,消息会生产者的缓冲池中待一段时间然后才会被发送到服务端,生产者就不知道消息具体在啥时候发送到服务端;
优点:最大程度上保证服务器节点接受到消息;
缺点:极度影响性能,导致数据的吞吐量低
ACK=all,消息的分区leader还有所有的副本都接受到消息,才会给生产者发送消息已经被接受了。
消息确认相应机制
消息确认机制
RoekctMQ 事物机制来确保消息都能发送到节点上
消息重投机制,消息投递失败,会再次投递
将生产者消息发送设置成同步发送,根据发送结果来判断是否需要做重复发送,最大程度保证消息发送到了服务器
发送端把消息发送个服务端,服务端接收到消息并且把消息持持久化到磁盘就会给发送端一个异步的confirm应答
1、消息确认机制
发送端开启一个事物,再推送消息,如果投递失败;进行事物回滚,然后重新发送消息;如果服务端收到消息,发送端就提交事务。
2、事物消息机制
1.ACK=all,让所有的服务器节点都获取到数据;
参数设置:min.insync.replica
2.合理的设置从机的个数,设置数据的备份
参数设置:unclean.leader.election.enable=false 本身默认就是flase
3.禁止主机挂掉,选从机作为新的主机
将异步持久化到磁盘,修改成同步持久化到磁盘
RocketMq消息本身默认的持久化盘刷机制是异步刷盘
1、修改节点的消息持久化刷盘机制
确保主机挂了,从机上还有消息备份数据
2、RocketMQ采用主从机构
服务端设置交换机,队列,数据的持久化;服务器宕机后,重启会读取磁盘上的持久化的数据;
问题:由于消息的持久化是一批的持久化,可能宕机了,这一批数据还持久化到磁盘
1、服务端收到生产者发送过来的消息,会做消息的持久化
2、当服务宕机后, 会从磁盘当中读取相应的消息,最大程度上保证消息不在服务节点上丢失
消息的持久化
使用confirm机制,可以让发送者的数据持久化到磁盘,再确认
rabbitMQ
也可以尝试修改刷盘策略
服务端保存消息失败(写入磁盘失败)
设置消息消费提交偏移量为手动提交偏移量,通过代码在finnaly里面手动设置消息异常的那个前一条的偏移量做提交;
需要正确提交自己的消费正常的偏移量
ack机制,设置消息成功消费之后,再通知节点消息已经成功消费
1.消费者在处理消息的时候出现异常了,那么这条消息就是没有被正常的消费;如果不采取措施,那么这个消息就会丢失
丢失原因
消息只有正常消费后,反馈给服务端;服务端才会从队列里面把该条消息删除
ack机制概述
不会发送ack确认消息
不确认
服务端发送完消息就自动认为该消息被成功消费
缺点:由于网络原因,造成数据从服务端发送到消费者消息丢失
自动确认
消费者消费成功之后,显示的给服务端ack信号;服务端只有收到该信号才会把数据从队列里面删除
设置手动ack,尽可能减少消费端的数据丢失问题;正常就是发送ack,异常就记录日志,然后发送nack
手动确认
ack机制三种模式
如果消费者异常没法发ack消息,服务端会认为这些数据都是没有被正常消费;就会堆积在队列当中,造成内存没法回收,内存泄露;
内存泄露
1.设置手动应答,如果异常,捕获异常记录日志,给服务端发送正常消费;
2.设置重试次数(默认是3次,三次不消费成功就会放入到默认的死信队列)
内存泄露解决方案
ack机制弊端
ack机制默认打开,而且是自动确认
ack机制
消费者消费消息,都应该向服务器节点正确提交消费的实际情况;
统一概述
消费方消费丢失
消息丢失原因
消费丢失
1、将前后有逻辑的消息合并成一个消息,发送,这样不管消费者是多个,不管消费者有几个线程,消费的时候都能保证消费是顺序被消费
2、同一个topic下,将有先后逻辑顺序的消息发送到同一个队列(rocketMQ,rabbitMQ),或者同一个分区(kafka),且保证消费者只有一个也只有一个线程来消费
通用方式
1、将有前后逻辑顺序的消息发送到同一个分区
1、一个topic只有一个分区,对应的也只有一个消费者
1、将有前后逻辑顺序的消费发送到同一个topic下的同一个队列,且保证消费方只有一个线程去消费
某个Topic下的所有消息都要保证顺序
2、设置发送的消息是全局顺序消息
RocketMq
RabbitMq
共性问题及解决思路
2、将消息按照用户id或者其他id来做hash,将消息放到不同的表。开启多线程,每一个线程消费固定的某一个表,来消费消息;
消息堆积,且需要保证消息顺序消费
1、Unsafe类计算
2、使用<dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-core</artifactId><version>4.0.0</version></dependency> 包下面的 RamUsageEstimator 类种的方法计算
1、通过工具类计算
1、对象头数据,实例数据,对齐数据
1、计算对象实体数据
计算对象都数据,数组长度,实体数据,对齐数据
2、计算数组数据
1.对象的属性;(定义多少字段,需要分配相应的字段) 如果没有字段,内存分配这个也是没有的;2.对象头;(固定大小的12个字节,这是64位操作系统,32位系统的是4个字节)3.对齐数据(假如一个对象的实际存储空间的数据,不是8位的整数倍, 但是需要填充空位,填充到8 的整数倍,(这个是64位的虚拟机)) 对齐数据 有时候 有,有时候没有
2、通过手动计算
怎么计算一个对象的内存大小?
可以,造成堆内存的线程会被销毁,所以这个线程创建的内存对象也会被回收,其他线程还是可以获取到充足的内存空间来支持程序运行
内存溢出其他线程还能工作吗?
两者一样快,两者最终执行的时候语句是一样的。但是比count(列名)快
直接遍历索引来查询;
1、如果是innerdb 数据库引擎
count(列名)只统计不为null的数据,需要遍历整个表
2、myisam数据库引擎
count(*)和count(1) 谁快
sql性能对比
16个库,每个库8个表,共128张表
按照用户id取模,128,分配到不同的库不同的表;
1、库表设计
sharding jdbc配置;
2、dao层
每一个项目连接池5-10个
整个对dao层的服务器数量80多个
3、连接池
设计
分库分表分的越细,数据倾斜发生的可能就越小;
造成倾斜的主要原因还是分库分表的那个字段的生成规则如果有问题,那么数据倾斜的概率就大;如果用户id都是自增的,那么数据倾斜的概率就很低
1、数据倾斜问题
超过一定期限的的权益数据会被DBA清除,且不会再被查询
2、单表日增近8万数据,不到100天数据量就超过了mysql性能极限
问题
之前待过的公司的权益平台日增数据1000万
大量数据方案设计
java实战
默认都是长连接
HTTP1.1特点
1.数据都是二进制传输
2.多路复用:多个请求可以复用一个TCP连接
3.请求头压缩
4.服务器可以给客户端推送消息
5.HTTP2.0对安全协议做了升级,数据传输更加安全
HTTP2.0特点
HTTP2.0服务器客户端都会维护一个请求头索引表;对于之前出现过的请求头,再次发送的时候会直接传递请求头的索引键;HTTP2.0没有请求头压缩
请求头压缩
http2.0服务器可以主动给客户端推送数据;HTTP2.0之前服务器是不能主动给客户端推送数据;
服务端推送
Http2.0之前都是文本直接传输,文本的格式有很多种;HTTP2.0之后内容都是转成2进制进行传输
传输格式不同
Http1.1所有请求都是串行执行;如果前面的请求阻塞了,那么后面的请求也是跟着阻塞,这就是头阻塞问题。Http2.0多路复用也就是多个请求可以在同一个链接上并行执行,根据数据的帧就知道对应某个请求;某个任务阻塞或者是资源消耗大不会影响其他任务
头阻塞问题
HTTP2.0升级了很多安全协议,使数据传输更加安全
安全性更高
HTTP1.0一次http请求就需要建立一次链接,请求完成链接断开
http1.1默认的是长连接,传输层的一次连接可以做多次HTTP请求
Http1.0和Http1.1区别
请求方法
路径
协议版本
内容
回车符+换行符
换行符
请求头结束之后会有一空行和请求正文隔开
请求行
请求头字段名称以及对应的值(一个键值对占一行)
请求头
请求正文
请求报文
状态码
状态码描述符
状态行
常见头部
响应头部
响应正文
相应报文
报文数据结构
有太多的无用的请求头数据
HTTP的消息冗长繁琐
容易遭到黑客攻击
http协议弊端
一个客户端的多个Http请求复用一个tcp连接
与Tcp复用的区别:tcp复用是多个客户端的请求复用一个tcp连接:http复用是一个客户端的多个http请求复用一个tcp连接;
http复用
把热点图片,静态页面等数据缓存在负载均衡服务器上;
http内容缓存
客户端服务端都支持压缩,服务端把数据压缩,再发送给客户端,客户端再解压;
优点:减少了数据包在网络传输的大小
注意事项:一般情况下都是使用负载均衡服务器来做数据的压缩处理,减少web服务器压力;
Http压缩
特点:客户端定时给服务器发送请求,不管是否有数据,都会立即相应
缺点:会浪费大量的请求资源,而获取不到数据
轮询polling
特点:客户端给服务器发送HTTP请求,直到服务端有数据才会相应
长轮询 long polling
Http获取数据方式
标记请求方式为长连接
Connection:keep-alive
升级为https请求
Upgrade-lnsecure-Requests
表示请求消息正文的长度
Content-Length
浏览器可接受的字符集
Accept-Charset
HTTP请求头
允许的请求方法
Allow
文档的编码(Encode)方法
Content-Encoding
表示内容长度
HTTP响应头
HTTP重要头信息
向特定的资源发出请求
GET
向指定资源提交数据进行处理请求
PSOT
请求服务器删除Request-URL所标识的资源
DELETE
向指定资源位置上传其最新内容
PUT
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
CONNECT
向服务器索与GET请求相一致的响应,只不过响应体将不会被返回
HEAD
返回服务器针对特定资源所支持的HTTP请求方法
OPTIONS
回显服务器收到的请求,主要用于测试或诊断
TARCE
种类
从语义角度说get请求获取数据;post请求是提交数据
get请求回退的时候无害,也就是再获取一次数据;post请求就会重新提交一次表单
get请求会被浏览器主动缓存;post请求不会被主动缓存;
get请求只能进行URL编码;post请求支持多种编码方式
get请求的请求参数有长度限制;post请求参数没有长度限制;
get请求相对post请求来说不安全,get请求把请求参数拼接在请求路径中;post请求是把参数request body 里面;
get请求,post请求都有各自特定的请求头和响应头
post/get区别
HTTP请求方式
200 正常
204 请求已经处理,但是没有数据返回
206 请求部分数据
301 资源的路径已经更新了,
302 资源的uri已经临时定位到其他位置了
304 资源已经找到,但是不符合请求条件
307 重定向
400 表示请求的报文中存在语法错误,发生这种错误的时候,需要修改请求报文中的数据才行
404 服务器上没有这个请求的资源,很有可能是请求的路径不对
405 请求方式错误,需要更换请求方式;
500 服务器出问题了,
503服务器正忙,请稍后再做请求
HTTP请求响应状态码
HTTP
SSL协议是web浏览器与web服务器之间进行信息交换的协议
保密:在握手协议中定义了会话密钥后,所有的消息都被加密。
鉴别:可选的客户端认证,和强制的服务器端认证。
完整性:传送的消息包括消息完整性检查(使用MAC)。
SSL协议特点
SSL
在SSL基础之上做了优化,弥补了安全漏洞;
TSL
1.客户端请求建立SSL连接,把自己支持的加密方式发送给服务器
2.网站把自己的公钥和证书发送给客户端
3.客户端把检查证书是否合法;
4.客户端随机生成一个数字作为之后数据交互的密钥;
5.用服务器发送过来的公钥加密生成的密钥,并发送给服务器
6.服务器用客户端生成发送过去的密钥加密发送内容;
7.客户端收到数据类容,然后用生成的密钥解密
工作流程
1.用非对称的方式加密数据传输的密钥
2.用对称加密的方式加密传输的数据
HTTPS工程概述
负载均衡服务器上SSL加速芯片专门来处理SSL加密信息;节省了大量的服务器资源
SSL加速器
HTTPS
1.只需要一次握手就可以建立持久化的通信
2.客户端和服务器完成连接之后;数据传输不需要再经过中间的代理服务器
推送数据实现方式,回调客户端
3.服务器可以主动给客户端推送数据
websocket特点
利用HTTP建立连接
告诉服务器应用层服务器换成了websocket协议
Upgrade: websocket
告诉中间代理服务器,建立连接之后,数据传输不需要在经过代理服务器,直接发送给客户端
Connection: Upgrade
请求头设置
webSocket协议使用
websocket协议建立连接和断开连接的时候都是依赖于HTTP协议;中间的数据传输与HTTP协议已经没有关系;
websocket+http协议关系
webSocket的存在简化了tcp连接的三次握手,只需要一次握手就可以建立http数据连接,连接完成之后,再就是以webSocket的协议进行通信,服务端不必等待请求,就可以直接向客户端发送数据。
绝大多数持久化请求都是通过管线化方式进行的,就是一个请求不用等待响应就可以发送下一个请求,,这样就可以并发的同时发送多个请求
管线化
webSocket协议解决http性能问题
WebSocket
应用层协议
TCP是传输层协议,HTTP是应用层协议;应用层协议是需要依赖传输层协议传输数据
Http和tcp关系
socket通信是传输层协议的一种实现;
传输层协议是TCP/UDP协议
客户端发送请求只会必须等到服务器响应才能发送下一个请求
同步
客户端发送请求,比不等到响应,就可以发送下一个请求
通信的同步异步
客户端发送请求,直到服务端有数据才会返回,否则就会一直阻塞在网路读取
阻塞
客户端发送请求,即使得不到结果,也哦度会都直接返回;
非阻塞
数据读写阻塞非阻塞
socket通信的时候可以设置参数
tcp和Socket关系
socket连接是传输层协议的一种实现;HTTP协议是需要建立在传输层实现之上。
Http和socket关系
1.传输协议简单;
2.传输速度快;
3.网络拥塞少;
udp相对于TCP的优势
1.TCP传输需要建立稳定的连接,消耗系统资源多;udp不需要简历稳定连接,消耗系统资源少
2.TCP数据传输可靠,避免数据丢失,数据重复问题;udp是尽最大努力交付,不能避免数据乱序丢失
3.UDP更适合做高速传输和实时性强的通信;UDP传输速度大于TCP传输
4.TCP只能是一对一通信;UDP可以是一对一,多对一,一对多通信
tcp/udp区别
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 状态
4.客户端收到服务端发送的信息之后,会回复 服务员, ACK= 服务端发送的ACK数据,序列号为 服务端的 ack 编号,以及自己的一个序列号;此时,客户端还会延迟 一个两倍 的报文寿命时间,再结束,服务端收到了数据后,再会结束;
挥手步骤
保证数据的传输的完整性
为啥需要四次
四次挥手
tcp三次握手/四次挥手
客户端的多个http请求的数据传输是建立在通过一个tcp连接上
一个客户端和代理服务器连接,代理服务器再去请求真实服务器;此时代理服务器会和真实服务器建立长连接,其他客户端发送到代理服务器的请求,直接复用这个长连接和真实服务器数据交互;
tcp连接复用
解决的问题:后端服务器网速与客户端网速不匹配,造成服务端资源浪费。客户端网速慢,后端网速快,客户端的慢网速请求,容易拖垮后端服务器;
把客户端的tcp请求全部缓冲在负载均衡服务器里面;将客户端与服务器的一个tcp连接变成了两个tcp连接,分别来适应客户端,服务端的不同的网速;
tcp缓冲
每次Http请求,都是先建立tcp连接,用完就断开连接;下次请求再建立连接;这样一个tcp连接只是为一个http请求服务,不能复用,就是冷连接;
冷连接
多个http请求复用一个tcp连接,这就是热连接;Http复用,tcp复用的tcp连接都是热连接;
热连接
tcp冷热连接
服务器和客户端之间完成一次请求操作客户端或者服务器都可以断开TCP连接
存在连接都是有用连接,不需要额外的控制手段
需要频繁的建立连接,销毁连接
短连接
服务器和客户端之间完成一次请求之后不会主动断开连接,之后的读写操作都可以复用这个连接
服务器会提供报货功能,当客户端已经消失且连接没有断开,服务端会保留一个半连接,永远等待客户端的数据。
长连接保活
避免频繁的建立连接,断开连接,造成性能好时间的消耗
建立过多的长连接对服务器的资源也是一种消耗,长连接越多,服务器资源消耗也越大;
长连接
tcp长连接短连接
传输协议
http1.1之后一个TCP链接可以复用,发送多个http请求
HTTP1.1之前是一个http请求需要对应一个TCP链接
1.建立TCP连接后是否会在一个 HTTP 请求完成后断开?
HTTP1.1之前是一个TCP对应一个HTTP;HTTP1.1之后默认是长连接,一个TCP可以用多个HTTP请求
2.一个 TCP 连接可以对应几个 HTTP 请求?
HTTP2.0一个Tcp连接可以处理多个请求,多路复用且不阻塞;HTTP1.1也可以,请求都是线性去执行的,会存在头阻塞问题;
3.一个 TCP 连接中多个HTTP 请求发送是否可以一起发送?
1.使用长连接
2.一个页面建立多个连接
4.HTTP/1.1 时代,浏览器是如何提高页面加载效率的?
因为TCP可以是不断开的,所以就不需要重新建立SSL连接
5.为什么有的时候刷新页面不需要重新建立 SSL 连接
谷歌浏览器默认一个host最多建立6个TCP连接;
可以建立多个连接,不同的浏览器有不同的最大限制个数;
6.浏览器对同一 Host 建立 TCP 连接到数量有没有限制?
协议常见问题
2c协议
AMQP协议
ZAB协议是paxos协议的简化版本
确保已经在leader服务器上提交的失误最终被所有的服务器提交
确保再leader重启之后继续同步之前没有完成的数据
实现了leader选举
实现了在最快速度同步到半数节点,并会尽快的把数据同步到所有节点
实现的功能
ZAB协议
其他协议
协议
堆内存剩余空间小于该对象需要分配的空间
内存溢出
对象一直没有被垃圾回收,造成可用的堆内存越来越少
堆内存溢出
递归程序中没有出口,线程加载过多栈帧到栈,导致内存溢出
1.栈内存中栈帧过多,栈帧的总内存和超过了当前线程的栈内存大小
2.线程分配过度,导致总的栈内存不足以给下一个线程分配栈内存;
栈内存溢出
存储的字节码文件大小超过了方法区内存大小
动态创建了大量java类,这些类需要被存储到方法区,导致方法区内存不够
1.方法区
程序中动态的创建了大量的基础数据类型和字符串,导致常量池不够分配新创建的常量
2.常量池内存溢出
方法区+运行时常量池内存溢出
频繁调用本地方法,创建对象导致本机直接内存不够分配
本机内存溢出
对象优先在eden区域分配内存
eden区域内存满了,再存放from suvivor区域
对象优先在eden区域分配
新生代对象经历过一定的垃圾回收次数还存活,就会被放到老年代内存中去;
长期存活对象进入老年代
内存大小超过一定大小的对象直接放入老年代
相同年纪大小的对象的内存综合超过survivor区域内存一半,那么大于这个年纪的对象都会放在老年代
大对象直接进入老年代
新生代每一次gc之后,都有可能把存储不下的对象放在老年代;所以老年代会留出一些空间给这些新生代
老年代每次测量进入老年代对象的年纪大小,去做评估预测,老年代剩余空间是否能足够装下下次进入老年代的新生代对象,如果不够,就会做老年代的垃圾回收
内存分配担保原则
对象内存分配原则
java的多线程是通过计算机内核线程来回相互切换的,java的线程执行到了某一步,cpu执行权被切换到其他线程上时候,这个程序计数器的作用来了, 就是去记录它所属的线程执行到了哪一步,哪一个指令,执行权再次切换回来的时候,这个计数器就会帮助线程准确无误的接着切换之前的代码接着执行。
1.是线程所独有的
2.生命周期和线程周期相同
不存在内存异常的情况
3.永远都不会有异常
程序计数器
native方法运行的时候用到的内存空间就是本地方法栈
为java语言调用本地方法,也就是调用native修饰的方法服务的。
本地方法栈
方法区被各个线程所共享
也称作永久代,存储的都是,经过虚拟机加载之后的字节码文件,类的信息,常量池,静态变量
运行时常量池,存储了编译期的各种字面量(字面量都是常量池的一部分)
存储内容
方法区由于比较稳定:一般不会进行垃圾回收
1.如果当前系统没有任何位置引用这个常量,那么这个常量就会被常量池清除
2.如果一个类在堆内存都没有他的引用,对应的加载该类的类加载器也被回收,该类对应的字节码文件没有任何地方法被引用,那么该类就会可以被回收。是否被回收还需要虚拟机的参数配置。
但是通常方法区的垃圾回收主要是两部分内容:1.运行时常量池中的常量2.无用的类
内存回收
方法区
栈帧
线程所独有,存储的都是临时数据
和线程生命周期一样
3.方法在执行的时候栈内存都会去创建这个方法对应的栈帧,栈栈中存储了这个方法的局部变量表,方法返回值,方法出口等等。我们在调用方法的时候通过方法当中嵌套方法,那么栈内存,同样会为这些方法都去创建对应的栈帧,线程去执行这个栈帧(方法),执行完一个栈帧,这个栈帧对应的内存就会被回收,这就是所谓的弹栈。线程永远都只会在栈内存中最上层的栈帧上执行。并且由于前后调用的方法之间存在着返回值的原因,对应的栈内存中的上下两个栈帧之间也并不是完全割裂的,他们需要返回值的传递。每个栈内存最多可以存储1000-2000个栈帧。所以说。
给栈内存分配128kb
-Xss128k
栈内存大小设置
操作系统会限制线程的数量,从而达到限制总的栈内存大小
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
永久代说明
堆内存
运行时数据区域
对象的hashCode值
分代年纪
锁的标记位
是否偏向
无锁
偏向线程的id
偏向锁时间戳
偏向锁
指向锁的指针
轻量级锁,重量级锁
标记位
Gc标记
记录内容和锁的状态有关
Mark Word
指向类的指针
数组长度
对象头
实例数据
对齐填充字节
分为三部
Java对象在内存中存储
基础数据类型拷贝值, 非基础数据类型,新创建对象,并用老对象给新对象字段赋值;
深拷贝
基础数据类型拷贝值,非基础数据类型拷贝对应的引用
浅拷贝
拷贝
GcRoot对象作为起点,向下搜索,搜索走过的路径称为引用链;一个对象到GcRoot没有任何引用链,那么这个对象就是不可达的。
可达性算法过程
栈内存中栈帧引用的对象
方法区中引用的对象
本地方法区中引用的对象
软引用,弱引用,虚引用
GcRoot对象
三色标记算法是一种垃圾回收的标记算法
减少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()方法有没有被重写,有没有被执行过;如果该方法有被重写并且也没有被执行过,那么该对象就不会被再一次标记,否则就会被第二次标记(再一次标记),只要被第二次标记的对象,那么他就会被垃圾回收器回收。
并不是没有被引用的对象就会被回收
垃圾收集算法
单线程回收,用户线程需要停止
运行的过程:是以单线程形式去收集,收集开始时候,所有的用户线程全部停止,gc线程开始去判断对象是否存活,新生代采用复制算法
运行过程示意图
serial
多线程回收,用户线程需要停止
parNew收集器是serial收集器的多线程版本,收集算法,停止用户线程,对象分配规则,回收策略都是与serial回收器是一样的
parNew是多线程回收,但是如果遇到的是单核的cpu,那么单个cpu在多个线程之间来回切换回收,那么效率反而还会比单线程的serial的回收器效率还要低
开启垃圾回收时候,是将所有的所有的用户线程全部停止,然后再开启多个回收线程去执行回收的过程。目前能够搭配的老年代垃圾回收器,有cms
特征:用户线程与gc线程无法并发执行;多线程回收
回收过程示意图
PN
以高吞吐量标准设计的:用户线程时间/(用户线程时间+垃圾回收时间)
特征:用户线程与gc线程无法并发执行;多线程回收;以高吞吐量为标准
回收过程
PS
新生代垃圾回收器
老年代的单线程垃圾回收,用户线程需停止
用户线程与gc线程无法并发执行;单线程回收
serial-old
老年代多线程回收,用户线程需要停止
以高吞吐量为原则设计:
用户线程与gc线程无法并发执行;多线程回收;以高吞吐量为标准
PS-old
多线程回收,以用户线程暂停时间最短为设计标准
回收的时候,用户线程有一段时间不需要停止
初始标记:判断对象的引用链;
并发标记:用户线程不停止的情况下,gc线程去判断对象是否存活
重新标记:用户线程停止,多线程去重新标记在并发标记过程中的实际需要手机的对象
并发清除:用户线程在重新标记完成之后重新启动,用户线程与gc线程并发执行
1.因为cms的gc线程是在并发标记的时候用户线程也是在执行的。这样就会占用cpu执行用户线程的资源;
2.无法在当次gc处理并发清除时候,用户线程创建的对象,而且还要在gc之前就要预留一部分内存空间出来,给并发清除时候,用户线程去存储对象;
3.CMS垃圾回收算法是标记-清除,这样就会有一个后果就是,会用内存碎片,无法存储大对象,容易引发二次gc.
CMS的三大缺点:
CMS
CMS垃圾回收器工作流程图
老年代垃圾回收器
垃圾回收的时候几乎没有stop the world 时间
新生代,老年代都可以回收
可将内存分成很多个大小相同的region区域,根据区域之间使用标记-整理算法,区域内部使用标记复制算法;
很重要的一个特点
可预测垃圾回收时间
对region区域做选择性回收,回收价值高的region区域
1.初始标记:停顿所有的用户线程,标记各个region区域中能被gcroot关联到的对象。
2.并发标记:用户线程和gc线程并行,gc线程根据可达性算法找出存活的对象。
3.最终标记:停顿用户线程,并发执行gc线程去标记刚才用户线程操作引用对象的那部分内存。
4.筛选标记:根据可停顿时间,计算出最优的region区域,并发的对最优的区域进行回收(这个时候用户线程和gc线程是可以并发的)。
垃圾回收过程
垃圾回收过程示意图
将java的堆内存分成2048个大小相同的region块
region 区域大小在jvm运行期间都是不会被改变
每个region区域的大小都是相等的
region大小特点
每一个region区域只会属于Eden, Survivo,old其中的一种
Eden, Survivo,老年代的区域并不是连续;
新增一种新的内存区域,Humongous内存区域,超过0.5个region对象就会被放Humongous区域
存储特点
region特点
分区region
Young gc
mixed gc
FGc
三个过程
最早出现在jdk7;jdk9以后就是默认的垃圾回收器
G1回收器历史
G1回收器缺点
设定region 区域大小
-XX :G1HeapRegionSize
手动设置G1垃圾回收器;JDK9默认就是G1
-XX:+UseG1GC
-XX:MaxGCPauseMillis
相关JVM参数
G1回收器
停顿时间不超过10ms;
垃圾回收器停顿时间,不会随着活跃对象增加而增加
支持8MB-16TB堆内存
ZGC设计目标
jdk11引入
JDK15正式发布
ZGC历史
已经没有分代收集的概念了
只能运行在64位操作系统上
ZGC相对以往垃圾回收器特点
也会像之前的回收器一样,将内存划分成很多个小的块
固定为2MB
用于存放256KB以下的小对象
小页面
固定位32MB
用于存放256KB以上,4MB以下的对象
中页面
大小不固定,但必须是2MB的2次幂的大小
只会存放大于或者等于4MB的大对象,每一个大页面,只会存储一个大对象
大页面
具体划分
ZGC内存空间划分
统一内存访问
cpu都整合到同一个芯片上,各个cpu对于内存的访问会出现争抢,会导致内存访问瓶颈;为了解决这个,将内存和cpu集成在一个单元上,这个就是非统一内存访问
支持非统一内存访问
内存访问方式
在NUMA内存访问模式下,cpu访问本地存储器比访问非本地存储器速度快一些;
分配速度很快,收回也很快
ZGC支持NUMA,会优先将小页面分配在本地内存,本地内存不够,然后再从远端的内存进行分配。
中页面,大页面分配不会分配在cpu的本地内存
ZGC支持NUMA
标记阶段
回收阶段
重定位
处理过程
染色指针?
小页面优先回收,中页面,大页面尽量不回收
回收策略
1、多线程并发标记
2、多线程再次并发标记,处理中间并发阶段(用户线程GC线程同时运行)时候,遗漏的对象
1、将原来活跃的对象复制到新的内存空间上,并将原来的内存空间回收;如果发现某一个页全部是垃圾对象,直接全部回收该区域
2、重定位,新的地址值换到原来的对象上面
转移阶段
具体过程
ZGC垃圾回收
当对象的内存地址被转移的时候,刚号在并发阶段应用程序有需要访问这个对象
JVM向应用代码插入一小段代码的技术
相当于有两个操作要一起做的原子操作
解释
1、对已经转移但是还没重定位的对象进行对象的重定位
2、删除对应对象再转发表中的记录的指针的新旧关系
过程
读屏障
jvm启动预热,如果从未发生过GC,那么就会在堆内存超过10%,20%,30%的时候触发一次GC,来收集GC的数据
预热规则
ZGC根据近期对象的分配速度以及GC时间,计算当内存占用达到什么样的阈值的时候出发下一次GC
基于分配速率的自适应算法
流量平稳的时候,自适应需要堆内存占用达到百分之95的时候才会触发
基于固定时间间隔
和固定时间规则类似。
主动触发
内存已经无法再给新的对象分配内存触发
内存分配不够
代码中直接System.gc()触发
外部触发
元数据区不足时导致触发
元数据分配触发
ZGC的GC时机
估算当前的堆内存分配速率,速度估计越快,GC来的越早,速度估算越慢,GC来的越迟
ZAllocationSpikeTolerance
定 GC 发生的间隔,以秒为单位触发 GC
ZCollectionInterval
GC触发时机
Stw阶段,GC线程的数量
ParallelGCThreads
并发阶段,cpu的数量
ConcGCThreads
GC线程
ZGC调优
ZGC
将垃圾回收的停顿时间控制在10ms以内
与G1回收器的设计目标一直:低延时为主要目标
设计目标
jdk12版本以及以后版本
由redhat公司开发
Shenandoah开发使用了很多G1回收器的代码
基于region的内存布局
标记阶段都是并发标记
Shenandoah与G1相同点
在最终的回收阶段,采用的是并发整理,由于和用户线程并发执行,因此这一过程不会造成STW,这大大缩短了整个垃圾回收过程中系统暂停的时间
默认情况下不使用分代收集,也就是Shenandoah不会专门设计新生代和老年代,因为Shenandoah认为对对象分代的优先级并不高,不是非常有必要实现
采用“连接矩阵”代替记忆集。在G1以及其他经典垃圾回收器中均采用了记忆集来实现跨分区或者跨代引用的问题,每个Region中都维护了一个记忆集,浪费了很多内存,且导致系统负载也更重,「因此在Shenandoah中摒弃了这种实现方式,而是采用连接矩阵来解决跨分区引用的问题」
Shenandoah与G1不同点
Shenandoah
JDK11正式
只负责内存分配,不负责内存回收
工作
内存压力测试
虚拟机接口测试
极短寿命的工作
极端延迟敏感的应用
-XX:+UnlockExperimentalVMOptions
-XX:+UseEpsilonGC
参数配置
Epsilon GC
old serial
serial +old serial
par new
PN + CMS
PO
PS +PO
垃圾回收器搭配使用
几十兆
几个G
20G
上百G
G1
4T
垃圾回收器最佳回收内存大小
XX:+UseSerialGc
XX:+SurvivorRatio
XX:+PreTenureSizeThreshold
XX:+MaxTenuingThreshold
并行回收垃圾线程数量
XX:+ParallelGCThreads
自动选择各区大小比例
-XX:+UseApaptiveSizePolicy
Parallel
使用标记清除算法
XX:+UseConcMarkSweepGC
-XX:ParallelCMSThreads=n
-XX:CMSInitialingOccupancyFraction
-XX:+UseCMSCompactAtFullCollection
设置几次FullGC后进行一次碎片整理
-XX:CMSFullGCsBeforeCompaction=n
允许对类 元数据进行回收
span style=\
-XX:CMSInitiatingPermOccupancyFraction=n
-XX:+UseCMSinitiatingOccupancyOnly
gc时间占用运行时间比例
-XX:GCTimeRatio
停顿时间,是一个建议时间,GC会尝试使用各种手段达到这个时间,比如减小年轻代
-XX:MaxGcPauseMills
新生代调整young区域的块的个数达到这个值
新生代回收时间大小
-XX:+MaxGCPauseMillis
GC间隔时间
-XX:+MaxGCPauseIntervalMillis
分区大小,size越大,垃圾回收时间越长,GC间隔时间长,也会导致每次GC时间长
-XX:+G1HeapRegionSize
新生代最小比例,默认5%
-XX:G1NewSizePercent
新生代最大比例,默认60%
-XX:G1MaxNewSizePercent
GC时间的建议比例,G1会根据这个值调整整个堆内存大小
线程数量
-XX:ConcGcThreads
启动G1的堆内存占用比例
-XX:InitiatingHeapOccupancyPercent
使用tlab
-XX:UseTLAB
打印tlab情况
-XX:PrintTLAB
设置tlab大小
-XX:TLABSize
通用常数
垃圾回收器常用参数
垃圾收集器
垃圾回收器是垃圾回收算法的实现
在新生代就使用复制算法,老年代使用标记整理算法
serial回收器
只在新生代使用,复制算法
新生代复制算法;老年代标记整理算法
新老年代都可以
只在老年代使用,标记清除算法
JDK8及之前版本的垃圾回收器必须遵循,新生代的回收器必须使用复制算法;老年的垃圾回收器使用复制整理,或者复制清除算法;
垃圾回收器和垃圾回收算法关系
线程运行到安全点,安全区域会进行gc信号检查,也就是是否需要做垃圾回收操作;如果需要做,那么线程都会停止在安全点或者安全区域,等待垃圾回收完成;如果不需要垃圾回收,那么就不会停留在安全点,或者安全区域
设置在一些执行时间长的指令上
安全点设置
相对于安全点来说,就是指令跨度大的安全点;
安全区域设置
垃圾回收时间点
当Eden区或者S区不够用了
当老年代空间不够用了
当方法区不够用了
jdk8及以前版本
根据垃圾回收器的不同,会有所不同
System.gc()(通知jvm进行一次垃圾回收,具体执行还要看JVM,另外在代码中尽量不要用,毕竟GC一次还是很消耗资源的)
jdk9开始
垃圾回收时间
GC日志demo
Full GC
表示的是年轻代使用Parallel Scavenge这种垃圾回收器回收的
如果是“DefNew”,表示的是serial回收器回收的
如果是“ParNew”,表示的是parNew这种回收期回收的
回收前新生代已经试用的内存大小
1375253K
回收后新生代内存的使用大小
0K
新生代区域可使用的最大内存
1387008K
[PSYoungGen: 1375253K->0K(1387008K)]
表示老年代使用Parallel old这种垃圾回收器回收的
回收前老年代已经使用的内存大小
2796146K
回收后老年代使用的内存大小
2049K
老年代可使用的最大内存大小
1784832K
[ParOldGen: 2796146K->2049K(1784832K)]
回收前,整个内存区域(新生代+老年代)所使用的内存大小
4171400K
回收后整个内存区域(新生代+老年代)使用的大小
整个内存区域(新生代+老年代)能够使用的内存有多大
3171840K
整个区域也可以解释为整个堆内存
4171400K->2049K(3171840K)
def new generation
tenured
永久代
perm
新生代的GC
minor GC
老年代的GC
full GC 或者是Major GC
详细说明
GC日志分析
垃圾回收
jdk1.7 默认垃圾收集器PS(新生代)+PO(老年代)
jdk1.8 默认垃圾收集器PS(新生代)+PO(老年代)
2、常见的HotSpot垃圾回收器组合有哪些?
1、提高用户线程吞吐量
2、提高用户线程相应时间
3、所谓调优,到底是在调什么?
扩大JVM内存
加大新生代区间比例
提高去Survivor区域比例
提高新生代到老年代的年纪
调整内存比例
避免代码内存泄露
4、PN +CMS 怎样才能让系统基本不产生FGC
5、PS +PO 怎样才能让系统基本不产生FGC
不分代收集,在G1回收器概念里面,已经没有新生代,老年代的概念,所有的堆内存区域划分为不同的区域
会发生FGC
6、G1回收器是否分代?G1回收器会产生FGC吗?
1、扩大内存
2、提高CPU性能(可以提高回收效率)
3、降低MixedGC 触发的阈值,让MixedGc提早发生
7、如果G1回收器发生FGC?
小的堆内存影响不大;大的堆内存会有服务器卡顿
8、生产环境可以随随便便dump吗?
栈,堆,方法区直接内存溢出
9、常见OOM问题有哪些?
1.线程在等待外部资源:数据库,网络资源,设备资源
2.死循环,
3.锁等待,死锁情况。
10、造成线程停顿的原因
调优常见问题
top 命令
找出哪个进程的CPU高
top -hp命令
找出该进程中哪个线程cpu高
jstack
导出该线程的堆栈信息
线程线程占比和垃圾回收线程的占比对比
解决步骤
案例1、
1、找到java的线程,看有哪些线程正在运行,然后拷贝出来运行的线程的唯一标记
2、再去等待线程中,找这些线程等到的是哪个线程,拿出这些线程id和运行的线程对比,,可以找到持有锁的线程;
假如100个线程很多线程都在等待,找到持有这把锁的线程
案例2、
线程
文档加载斤内存中行程的java对象导致内存不足,频繁GC,导致stw时间过长
1、为啥原网站很慢
从PS垃圾回收器,设置成PN+CMS 或者G1回收器
2、内存加大之后更卡顿
案例1
线程池使用不当,导致OOM
案例3、
jvm调优实战
1、吞吐量
1、减少垃圾回收时候的stop the world的时间
2、响应时间
调优前需要明确是以高吞吐量为要求,还是以响应时间低为要求;还是说满足一定的相应时间的情况下,达到多少的吞吐量
PS+PO回收器
数据计算
数据挖掘
吞吐量优先
网站
API
追求响应时间
垃圾回收器选择
JVM调优的目的
jvm性能调优
1.各种不同平台的虚拟机,与所有平台都同一使用的是程序存储格式--字节码
2.java语言中的关键字,变量,运算符符号的最终语义最终都会编译成多条字节码指令;
3.可使用工具打开字节码文件可以看到字节码文件的严谨的格式
编译概述
class文件存储的是java类的字段,方法,类的路径,名称,常量
字段,常量之间会有一些相互引用的关系,这个关系是在class文件在存储的时候动态绑定
14种表的首位都是标示位,标示该表示属于哪种类型的表,也就是说该表示属于哪种常量类型
类的方法,方法的入参,回参;实现的接口,继承的类,字段名称,字段数据类型,等等数据都会被分们别类的存储到对应的表中。他们之间又会有相应的数据依赖的关系,比如说一个方法的入参是什么类型,方法的修饰符这些都不存在方法表中,而是存在对应的类型表,字段名称表,等等
在存储class字节码的时候,储存的常量池中的数据是以表(这里的表和数据库的表不是一个概念,但是从从关联关系上也很像关系型数据库)的形式存储,虚拟机提供了14种表的结构来存储包括所有的常量池的数据
类存储概述
1.java文件能够被虚拟机编译并不是靠着以“java”为后缀的文件名来识别,而是这类文件在创建的时候就已经赋予了它“魔数”,java文件的魔数是“0xCAFFBABE”,文件在创建的时候这个魔数已经就确定了,任凭我们怎么修改文件的后缀,这样的文件也是能被jvm虚拟机编译的
2.1前四个字节:存储的是文件的魔数,虚拟机在加载java文件的时候不是以后缀名为识别,而是以文件中前4个字节这个魔数是不是java对应的魔数来识别,识别中了,那么该文件就能被虚拟机编译
2.2接下的四个字节:第五,第六位是次版本号;第七第八是主版本号,主版本号是和jjdk的版本有关的,jdk1.1开始主版本号是45,现在的版本号是52+
常量池存的是:字面量和符号引用
字面量:可以理解为java的常量,比如字符串,final修饰的常量
1.类,接口的全路径
2.字段的名称和描述
3.方法的名称和描述
符号引用存储的数据可以反编译获得到类的方法和字段,已经类名,接口名,路径
符号引用:
2.3再接着就是字节码的常量池(字节码的常量池,说明每个class文件都有自己的常量池,有个疑问常量池是共有的,这个看以后是怎么解释这个的):
2.class文件的储存内容
class文件概述
class文件存储
1.编译器优化重排序
编译器对代码进行空间复杂度时间复杂度的优化,导致了代码实际的执行顺序和书写的不一样;
程序先执行下面的代码,后执行上面的代码
现象
重排序的代码没有先后逻辑关系
重排序不影响结果
指令重排原则
2.指令重排
3.内存系统重排序
重排序
对java的字节码进行修改,增强功能
修改二进制的class文件
操作
减少冗余代码,提高性能,加密代码
修改java字节码工具类
作用时间:编译期
AspectJ
修改java字节码的工具库
可以直接生成一个类
在已经编译的类里面添加新方法,修改方法
修改时间:运行期
Javassist
ASM是一个java字节码操作框架
直接生成class文件
拦截java文件被类加载器加载之前修改类
修改时间:编译期
ASM
注解编译器;
APT
字节码增强技术
加载java核心库 java.*构建ExtClassLoade,AppClassLoader
BootstrapClassLoader
加载扩展库,如classpath中的jre ,javax.*
ExtClassLoader
加载项目代码所在目录的class文件
AppClassLoader
自定义加载器
加载器的区别
类加载器种类
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机制就是打破了双亲委派机制;
双亲委派机制缺陷
JAVA内置一种服务发现机制
spi
根据实际需求替换,扩展框架源码的实现策略
调用ServiceLoad.load方法,传入接口字节码对象
根据字节码类型到meta-info包下面找到对应的配置文件
读取配置文件中的类全路径
获得类的全路径,通过反射获取到对象
把对象存储到linkedHashMap中
spi实现过程
能够让接口更方便找到拓展的实现类
不能单独获取这一个实现类,获取了接口的所有实现类,造成了性能消耗
spi优缺点
实现某个想要实现的接口
resource包下配置相关的包路径和文件名
文件中配置类的全路径
spi配置过程
spi机制创建对象的类加载器是classLoad,和传统的类加载器不存在父子关系
spi机制加载接口实现类的加载器默认的都是ClassLoader(java lang 包下面)
spi机制破坏了双亲委派机制
spi机制
1.将对象的字节码文件加载进内存
2.栈内存给对象分配一个引用变量
3.堆内存开辟一块内存空间
4.给堆内存对象属性做默认初始化赋值
5.给对象属性做显示初始化
1.父类构造代码块
2.当前类的构造代码块
3.父类的构造方法
4.当前类的构造方法
6.对代码块进行初始化
7.将堆内存的地址赋值给栈内存的引用
对象创建过程
类加载机制
将频繁调用的代码直接加载进内存,无需从硬盘上去加载这些文件
jit热点编译
客户端编译器
C1
服务端编译器
C2
Graal 编译器
从jdk10之后,JIT有三款
这三款编译器都是热点编译器,jdk17已经删除了Graal 编译器
sun公司hotspot
还有其他公司开发的,也就是其他有自己开发的openjdk
主流编译器
parseCompilationUnit 方法做了这个解析过程
1、规定哪些词是符合java语言规范的(com.sun.tools.javac.parser.JavacParser )
2、逐个读取java源文件中的单个字符,归类不同的语法(com.sun.tools.javac.parser.Scanner )
3、规定了所有java语言的合法关键词;(包括了所有的关键字,符号com.sun.tools.javac.parser.Token )
4、用来存储和表示解析后的词法(com.sun.tools.javac.util.Name.Table类种 );形成语法树
词法分析器作用
package 语法,import语法,类定义,field定义,method定义,变量定义,表达式定义,除了这些规则就是用户自定义的包名,类名,方法名,字段名,变量名,等等。判断这些字符能否组成一个合法的Token 是由Token类种的nextToken规定的
规定java代码解析规则
词法解析后回输出token流
token类中字符集合
词法解析
词法分析后通过TreeMaker类生成的每一个Name对象生成,都会造成一个对应节点的语法树
所有的关键词,都会有一个对应的语法树对象,所有的语法树对象都是com.sun.tools.javac.tree.JCTree类的子类
语法解析器工作
1、解析Package节点
2、解析import节点
3、解析整个class节点:包括 变量,方法,内部类解析,整个类体的解析的结果都会被保存在一个list集合中,然后把这些节点都填机到 com.sun.tools.javac.tree.JCTree.JCClassDecl语法树中去
所有节点解析完成之后都会被合并到com.sun.tools.javac.tree.JCTree.JCCompilationUnit里面去
语法解析内容
语法分析器最后输出抽象语法树
最终处处经过注解的抽象语法树
添加默认方法带代码
通过com.sun.tools.javac.comp.Enter类完成符号表的构建
1.1把类种出现的富豪输入到自身的符号表中,把所有的类符号,类的参数类型符号,超类符号,和继承符号都存储到一个未处理的列表中
1.2将这个未处理的猎豹中所有的类都解析到各自类符号列表中去,这个操作是在MemberEnter.complete();在这个步骤又做了一件很重要的时候就是添加默认的构造函数,(com.sun.tools.javac.comp.MemberEnter类来完成);
1.构建符号表
注解是由com.sun.tools.javac.processing.JavacProcessingEnvironment类来完成;
2.处理注解
1标注是由com.sun.tools.javac.comp.Attr类来完成,主要检查:1.变量的类型是否匹配,2.变量在使用前是否已经初始化,3.推导出方法泛型的参数类型,4.字符串常量的合并;
3检查变量,方法,类是否的访问是否合法,变量是否是静态,变量是否已经初始化(com.sun.tools.javac.comp.Resolve)
4.优化合并变量,(com.sun.tools.javac.comp.ConstFold)
5.推导出方法的泛型的参数类型(com.sun.tools.javac.comp.Infer)
3.标注
1.变量在使用前是否已经被正确的赋值了,原始数据类型是否已经有默认初始化值了;
2.保证final修饰的变量不会被重新赋值,经过final修饰的变量只能被赋值一次,否则就在编译的时候报错;变量如果是静态的,就必须保证这个变量在定义的时候就被复制;
3.确定方法的返回值,以及检查方法的返回值是否正确;检查接受这个方法的数据类型是否正确。
4.异常是否都抛出或者是被捕捉了;
5.检查return 后面是否有语句;
6.去掉无用的代码,比如说只有永远为假的if;
7.变量的自动拆装箱;
8.去除语法糖,比如说:将foreach形式编程更简单的for循环;
由于语法分析后的语法树,还比较粗糙,距离目标的字节码文件还有一些距离,需要在这个语法树上做一些优化操作
4.数据流分析
语义解析
字节码文件
1.将java方法中的代码块转成进栈,出栈的符合jvm语法的命令形式
2.按照jvm字节码文件的标准输出到以class文件为拓展名的文件中
调用com.sun.tools.javac.jvm.Gen类来遍历语法树,生成最终的字节码文件
工作过程
这个add方法是如何生成字节码文件的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工具。
第三方工具
java是跨平台的,需要同一的内存模型来兼容不同不同的操作系统的差异,硬件差异等等。不能因为操作系统硬件的差异导致相同的程序出现不一样的结果
内存模型概述
线程都有自己的工作内存;每个线程的工作内存之间都是相互屏蔽;
线程操作变量,都是先通过工作内存,然后复制到主内存;其他线程才能再主内存中访问这个变量;
主内存,是可以被所有的线程访问。
主内存+工作内存概述
1.线程的工作内存去访问主内存,获取变量值
2.内存之间的基本操作都是原子操作,不可再分割
1.交互概述
8大原子操作
所有的操作必须符合前后逻辑关系
操作之前必须要满足前后依赖关系
配对操作之前含有其他操作关系,但是必须保证操作前后逻辑关系
变量被加锁多少次,就要被解锁多少次
交互原则
主内存+工作内存之间数据交互
线程内部:线程一定会按照串行的方式去执行指令
线程之间:由于cpu的执行权问题,多线程之间执行的任何代码都可能是交叉进行的,除了volatile,synchronized
理解
有序性
主内存和工作内存之间的基本操作都是原子操作
共享变量被一个线程操作,操作后的记过能被其他线程直到
volatile修饰的变量,修改的之后会立即从工作内存同步到主内存之中;实现其他线程对该变量的可见性
volatile
解锁之前必须把变量的值从工作内存传递到主内存
synchronized
对象的引用是不变的,所以说对所有线程来说都是可见的
final
java可见性实现
可见性
内存模型三大特点
并发情况下,两个操作是否存在冲突的情况;判断数据是否存在并发问题,以及线程是否安全的重要依据
1.锁定规则:同一个锁,只有被释放之后才能被另外一个线程再次占用;
2.读写原则:读写是一对操作,下一的读操作必定在写操作之后;
3.对象终结原则:对象被回收之前必须先要被初始化
4.传递性:a操作优先于b操作,b操作优先于c操作,那么a操作也有限与b操作
具体原则
java先行发生原则
定义各种变量的访问规则,以及这些变量底层实现的细节
java内存模型
java线程模型
\t\t\t用户线程调用内核IO操作,需要等IO彻底结束之后才能返回到用户空间,因此IO过程是阻塞的。
\t\t概念
\t\t\t\t用户线程无延迟,就可以拿到IO之后的数据
\t\t\t优点
\t\t\t\t用户线程处于等待之中需要消耗性能;
\t\t\t缺点
\t\t优缺点
\t\t应用场景
BIO (同步阻塞IO)
\t\t\tIO调用后,如果内核这个时候没有把数据准备好,那么就会直接返回一个状态,这个时候用户的IO线程就不会一直等待内核把数据准备好,而是间隔时间一段时间去轮询内核数据是否已经准备好了。最终是内核把数据准备好了,等待下一次用户线程的询问,就把数据从内核复制到用户内存中
\t\t\t\t无需阻塞等待内核准备数据,这个期间可以做其他的事情;
\t\t\t\t需要去轮询内核,内核无法以最短的时间把数据复制到用户内存中
NIO (同步非阻塞IO)
\t\t\t用户线程对内核发起了IO操作,用户线程没有任何组阻塞;内核准备好数据并复制到用户内存后,会给用户线程发送一个信号,表明数据IO操作已经完成了;
\t\t\t\t内核会把数据复制到用户内存中,减少了用户线程去复制的过程;
\t异步IO(AIO)
\t\t\tIO多路复用就是通过一个线程可以同事监视多个文件描述符(读就绪或者写接续,也就是可以监视多个IO操作),如果有一个描述符就绪,那么就可以通知程序做相应的读写操作;
\t\tIO多路复用概述
\t\t\t\t\t调用TCP文件系统的poll函数,不停的查询,直到有一个连接有想要的数据为止
\t\t\t\t概念
\t\t\t\t\t每次调用select函数都需要需要把文件描述符从用户内传递到内核态,内核再不断的去轮询这些文件描述符对应的io操作
\t\t\t\t过程
\t\t\t\t\t文件描述符数量比较少,只有1024个
\t\t\t\t缺点
\t\t\tselect
\t\t\t\t\t和select类似, 也是把用户传入的文件描述符数组复制到内核中,然后中再去不断的去做轮询操作;
\t\t\t\t\t没有文件描述符限制
\t\t\t\t优点
\t\t\t\t\t用户态到内核态的文件描述符的复制
\t\t\tpoll
\t\t\t\t\t用户的 FD集合和计算机操作系统有一块共有的内核空间,就不需要把FD集合复制到内核空间了;
\t\t\t\t\t没有最大并发限制,
\t\t\t\t\t减少了从内核空间到用户空间的拷贝过程
\t\t\tepoll
\t\tIO多路复用实现方式
\t\t\t\t组件名称
\t\t\t\t组件功能
\t\t\tNIO组件
\t\t\tNIO工作流程
\t\tjava-NIO
\tIO多路复用
信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程。 异步I/O是进程执行I/O系统调用(读或写)告知内核启动某个I/O操作,内核启动I/O操作后立刻返回到进程,进程在I/O操作发生期间继续执行,当操作完成或遭遇错误时,内核以进程在I/O系统调用中指定的某种方式通知进程,
信号驱动IO
各种IO模型
\t\t\t阻塞,如果内核没有准备好数据,那么线程就会一直阻塞直到有数据返回为止;非阻塞,如果内核没有准备数据,那么就会直接返回,并且还会不断的去轮询内核是否有准备好数据;
\t\t区别
\t阻塞非阻塞
\t\t\t用户线程进行IO操作,数据是被动复制到用户内存还是主动被复制到用户内存
\t\t\t同步IO是用户线程主动调用内核才能把数据从内核复制到用户空间;而异步IO是内核把数据复制到了用户空间,然后通知用户线程数据已经准备好了
\t异步同步
IO基础概念
\t1.用户代码向操作系统发出IO请求
\t2.轮询设备是否可以进行操作
\t3.将数据复制到用户内存之中
IO操作步骤
读取和写入文件IO操作都是调用操作系统提供的接口;应用程序要访问磁盘必须要通过操作系统的调用才行;
标准的访问方式:程序先调用操作系统的接口,操作系统先检查内核高速缓存找那个有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回,如果没有吗,则从磁盘中读取,然后缓存在操作系统的缓存中;
标准写入方式:用户程序调用写接口将数据从用户空间赋值到内核地址空间中,至于什么时候再写入磁盘,这个是由操作系统决定的;
1.标准访问文件方式
操作系统直接去访问磁盘数据,而不是经过缓存;这种方式减少了从内核缓存到用户缓存的复制;缺点就是每次都去访问磁盘,会导致数据加载极慢;
2.直接IO
数据的读取,写入都是同步操作;与标准方式不同的就是只有当数据成功的写入磁盘时才会给应用程序成功的标记;只有在一个对数据安全性比较高的场景会使用这种情况;
3.同步访问文件方式
异步访问文件的方式,就是当访问数据的线程发出请求字后,这个线程会去接着处理其他事情,而不是阻塞等待,当请求的数据返回后,这个线程再继续处理下面的事情。特点:这种方式可以明显的提供应用程序的效率(线程可以在阻塞的时候去做其他事情,不用干等着),但是不会改变访问文件的效率;
4.异步访问文件的方式
操作系统内存中某块区域与磁盘中的文件相关联,当要访问内存中的一段数据的时候,转换为访问磁盘中一段数据。特点:减少数据从内核缓存空间到用户缓存空间的数据复制操作;
5.内存映射的方式
不同机制
磁盘IO基本工作机制
网络传输
磁盘读取
IO的瓶颈
1.设置合理的Tcp网络访问参数;
2.减少网络交互次数(在网络交互的两端都设置缓存)
3.减少网络传输数据量大小(先压缩数据包在出传输)
4.尽量以字节的方式传送,减少字符到字节的转换
网络io优化
1.添加磁盘缓存,减少磁盘访问次数;
2.优化磁盘的管理系统(磁盘寻址策略);
3.设计合理的磁盘存储数据块(设计数据索引);
4.设计合理的RAID策略提升磁盘io;
磁盘io优化
优化手段
IO优化
IO
阻塞队列也是一种先进先出的队列
在并发包下
线程安全队列
带有阻塞功能
如果队列满,继续入队列时,入队列操作就会阻塞,直到队列不满才能完成入队列
如果队列空,继续出队列时,出队列操作就会阻塞,直到队列不空才能完成出队列
代码“解耦合”
“削峰填谷”
生产者消费者模型
引用场景
1.对队列中元素的操作,都是基于 入列,出列这两个基本的方法
2.都会提供添加,取出的阻塞方法。
3.是否包含这个方法的实现都是通过遍历去比较的方式实现
4.toStirng的实现都是遍历,StringBuilder来实现的
5.clear的实现,都是遍历,为每个元素复制为null
6.都会引入迭代器作为内部类类实现一些功能
7.构造方法都可以传递一个集合来变成队列
方法特性
这个是双端队列实现类,队列的大小默认也是Integer的最大值,也可以自定义队列的长度。
这个队列的实现,自定义了两个方法,一个是linkFirst,一个是linkLast方法,这两个方法就是为做添加操作的,unLinkFirst,unLinkLast,方法是为了从对列中取出元素准备的。
双端队列也提供了:阻塞存取;不阻塞出去;指定阻塞时间的存取;
LinkedBlockingDeque
队列不能完全算是一个容器,存数据就立马把该数据交给取数据,就是中间不存在能够缓冲数据的容器
取数据操作必须等待存数据的操作;
存数据时候,必须等待取数据的把之前一条数据取完
SynchronousQueue
延迟队列,至于元素到了延迟期以后才能够被使用
用作需要进行延时操作的场景
DelayQueue
优先队列
优先队列的默认初始队列长度为11;最大长度Integer最大值-8;
实现的数据结构是数组
队列中元素锁存储的对象必须是实现了比较器接口。队列的存储,取出都是按照比较器的的比较的顺序来存和取的
PriorityBlockingQueue
以链表的数据结构实现了阻塞队列
虽然是阻塞队列,提供了添加,取出,能延迟,规定延时的方法,但也提供了不阻塞的取出,添加的方法
如果没有给定阻塞队列的元素个数,那么默认的就是Integer值的最大值为这个队列所能存储的最多元素个数
同理取元素的时候,也是通过此种方式
储存元素时候,如果队列已经满了,那么就会导致延迟,延迟的实现还是通过了,lock和condition来实现,就是当元素已经满了的时候,就让当前存放元素的这个线程进行休眠,等到队列的状态是未满的时候,再去用condition去唤醒存储元素的线程。
基本的操作
LinkedBlockingQueue
字面翻译是转换队列,是阻塞队列实现的一种
具备阻塞队列的一切性质
就是如果生产这线程生产出来的数据,当前没有一个消费者线程是空闲状态能够去接受生产者生产的数据,那么这个数据就会被放入队列中
TransferQueue
和SynchronousQueue有些像
LinkedTransferQueue
阻塞队列继承实现关系图
阻塞队列概览
阻塞队列具体实现
双端队列的父接口,BlockingDeque接口也是实现了BlockingQueue接口的,所以双端队列也是阻塞队列的一种。所以也能在存取的时候进行阻塞
主要就是定义:“取出元素”,“放入元素”是可以给定时间进行延迟的
BlockingDeque
\t阻塞队列
并发队列继承实现关系图
就是在往链表中插入元素的时候,如果链表中元素为空,那么此时就会链表就会把第一个这个元素作为链表的头结点,尾结点
并发的链表;
如果插入元素的时候,链表不为空,那么新添加进来的元素就会作为尾节点,而且就是如果该元素是第二个被加进来的元素,其实也是一样的,把新添加的元素替代第一个元素作为尾节点。
链表的每个节点在存储的时候,会存储自己的节点也会存储他的下一个节点
ConcurrentLinkedQueue
其实可以看做是一个能够排序的单列集合类,可以支持各种排序。其实本质就是一个方便排序的单列集合类。适用于排序场景下
如果不在比较器中实现排序的规则,那么就是默认排序的规则,也就是升序
ConcurrentSkipListSet
利用cas命令实现的一个工具类。适用的场景是双列集合的排序操作。如果不指明排序的规则,就是自然排序,就是升序排序
ConcurrentSkipListMap
也是针对集合做过滤,添加,删除的集合工具类,是利用CopyOnWriteArrayList 来实现的。添加删除的时候还是需要去创建一个新的数组来实现这个
CopyOnWriteArraySet
主要就是对List集合的添加删除的工具类,实现还是使用的数组来实现,添加删除的时候还是使用lock锁来锁,每个添加删除都会创建一个新的数组来接受原来的数据
CopyOnWriteArrayList
并发队列
数组实现的双向队列,线程不安全;
ArrayDeque
链表实现的对象队列
LinkedList
基于链表实现的线程安全的双向队列;
ConcurrentLinkedDeque
基于链表实现的阻塞双向队列
实现类
双端队列
ConcurrentHashMap (使用场景,修改多,读取多)ConcurrentSkipListMapConcurrentSkipListMapCopyOnWriteArrayList (使用场景,迭代多,修改少)CopyOnWriteArraySet(使用场景,迭代多,修改少)
并发
HashTable
Vector
Collections.synchronizedList(list)、(使用场景,修改多,读取多)Collections.synchronizedSet(set)、(使用场景,修改多,读取多)Collections.synchronizedMap(map)(使用场景,修改多,读取多)
同步集合器:以托管的方式
集合
BlockingQueue接口下的类:LinkedBlockingQueueArrayBlockingQueuePriorityBlockingQueue SynchronizedQueue
ArrayDeque:LinkedBlockingDeque:
消费者-生产者模式
ConcurrentLinkedQueue ConcurrentLinkedDeque
并发-非阻塞
ConcurrentHashMap
拷贝集合
大量的查询,少量的增加删除操作
队列集合使用场景总结
网络之间传输数据,做持久化;
1.实现序列化后,可以把这个对象可以从一个虚拟机传递到另外一个虚拟机上(序列化就是将类的字段和他当前对应的字段值以序列化的格式生成一个数据流(这个数据流的格式不是字节码),这个数据流持久化到硬盘上)
序列化作用
jdk自带Serializable
json序列化
xml序列化
message pack
Protocol Buffers
Marshalling
序列化方式
java序列化
1.它与特定的版本号。持久化到本地硬盘后,我们可以根据序列化后的数据进行反序列化,生成对应的java类,并且java类字段的具体的值都有。
2.一旦一个类实现了序列化之后,就相当于把这个类变成了一个api。一旦这个类被大量的引用,那么这个类就必须永远的支持这种序列化形式;你就无法轻易的去改变这个类里面的属性;
3、方法不能被序列化
4、static,transient修饰的都不会被序列化
将对象转换成二进制的字节数组,通过报错或者转移这些二进制数据达到持久化的目的,注意对象序列化保存的是对象的成员变量。
反序列化和序列化是相反的过程,就是把二进制的数组转化成对象的过程,但是在反序列化的时候,必须有原始类的模板才能将对象还原,这个模板就是对象的序列化文件
序列化过程
Externalizable接口是Serializable接口的子接口
Externalizable可以指定被序列化的字段
externalizable与serializable的区别
Serializable
这个是编译期报的错误,就是找不到这个类对应的字节码文件,也就是会在启动的时候就报错,就需要将该类的字节码文件放在对应的包路径下。
ClassNotFoundException
IncompatibleClassChangeError
AbstractMethodError.
NoSuchFieldError
IllegalAccessError
InstantiationError
和方法执行,字段获取相关错误
VirtualMachineError
该类是内存溢出,并不是Exception,不能被程序捕获的,直接回导致程序中断
OutOfMemoryError
ZipError
InternalError
栈内存异常,这个错误(不是异常)不能被程序捕获的,直接回导致程序中断
StackOverflowError
UnknownError
虚拟机相关
ThreadDeath
LinkageError
VerifyError
ClassCircularityError
ExceptionInInitializerError
UnsatisfiedLinkError
ClassFormatError
UnsupportedClassVersionError
BootstrapMethodError
编译期是能够找到这个类,但是在运行的时候发现这个类不可用。导致的原因可能是jar有问题,或者是jar就没有引入。解决方法是把jar包重新导入一下。
原因:这个异常的,说明有些类没有被加载到;
发生的场景:一般情况下抛出这个异常都是源码中的类抛出了这个异常;
解决方案:1.在项目上右击 - 点击 properties --Deployment Assembly -- 点击 add--选择 java build path entries 这个把这个加进来; 再重启tomcat;2.在maven仓库中把相关的jar包删除,再maven update 3.在pom文件中,删除该 依赖,然后编译;报错之后,再回复依赖,再编译,启动试试看就行了;
NoClassDefFoundError
包相关
Error
异常
当前线程进行相应时间的休眠,并且不释放锁对象
sleep
当前线程放弃cpu执行权,让其他线程去执行,放弃的时间不确定,可能放弃后,又获得了执行权;
yield
获取当前执行程序的线程
currentThread
静态方法
isAlive() 方法的功能是判断当前线程是否处于活动状态
getId() 方法的作用是取得线程的唯一标识
isDaeMon、setDaemon(boolean on) 方法,是否是守护线程,设置守护线程
线程可以开始运行
start()
线程真正执行
run()
设置优先级,获取优先级
getPriority()和setPriority(int newPriority)
中断线程
interrupt()
应用在就是主线程主要在子线程生命周期结束之后再结束
那么就是用线程对象 调用 join //方法,让主线程等待自己
线程加入,抢占cpu执行权
join()
实例方法
Thread中的方法
线程状态转换
新创建的未启动状态的线程
NEW
线程准备或者运行中
RUNNABLE
发生IO阻塞
发生锁的争用阻塞
线程阻塞
BLOCKED
Object.wait,不带超时时间
等待终止
Thread.join不带超时时间
LockSupport.park
未指定时间的线程等待
WAITING
Thread.sleep 方法
指定超时值的 Object.wait 方法
指定超时值的 Thread.join 方法
LockSupport.parkNanos
LockSupport.parkUntil
指定时间的线程等待
TIMED_WAITING
线程终止
TEMINATED
线程各种转态
\t\t\t线程的状态
\t\tThread
\t\tRunable
继承Thread类,重写run方法
实现runnable接口,重写run方法
线程池类
开启线程的方式
cpu的执行权由系统去分配
java就是采用的抢占式,可以设置线程的优先级,但是并不会有太好的结果;
抢占式
协同式
线程调度
\t线程
线程相当于于一个轻量级进程,一个轻量级进程对应一个内核
线程的切换调度完全依赖内核
内核线程实现
用户实现对线程的创建消耗,和内核没有关系
线程和进程的比例为1:n
用户线程实现
轻量级进程是线程和内核之间沟通的桥梁
线程创建消耗会是由用户来控制
java就是这种方式
用户线程轻量级进程实现
线程模型
严格的定义:如果一个类被多个线程访问,并且不用考虑这些线程在运行下的调度和交替执行,也不需要增加额外的同步,或者在调用方法进行任何其他的协调操作,这个对象的操作都能得到正确的结果,那么这个对象就是线程安全的。
非严格的定义:如果一个对象可以同时被多个对象同时使用,那么这个类就是线程安全的;
线程安全定义
也就是被final修饰的字段或者对象,他们都是不可变,即使是通过被他们修饰的字段或者对象引申出其他的字段或者对象,原来的字段,对象还是不变的,只是新返回去了一个字段或者是对象。这类情况,保证字段和对象的绝对不会变化。
1.不可变
线程绝对安全,指的是一些类中所有的字段和方法都添加了同步的synchronized来进行修饰;
我们在单个调用这些类的某个方法的时候,这个时候线程是安全的,但是如果我们在一个类中使用了这个类的多个所谓线程安全的方法的时候,就很可能导致线程不安全。在很多条件下是无法将这多个所谓的线程安全的方法都同一起来让他们变得线程安全。我们需要通过额外的方法添加同步修饰来确保线程安全。
2.绝对线程安全
相对线程安全,就是我们传统意义上的线程安全。
就是对某个对象单独的操作是线程安全的。调用的时候不需要额外的添加保障措施。
3.相对线程安全
这段代码本身不是线程安全的,但是可以通过调用添加同步的方式来保证这个段代码的执行时线程安全的。
4.线程兼容
5.线程对立
线程安全等级
线程的调度是为线程分配处理器的使用权的过程;主要有两种调度方式:协同式线程调度,抢占式线程调度。
协同式:线程的执行完他所需要执行的代码, 执行完成之后,会通知系统将cpu的执行权切换到另外一个线程上。最大好处是,实现简单,没有什么线程同步问题,并发问题,线程对于对cpu的切换是可以感知的。缺点,就是如果是一个死循环这期其他形式的问题,就会导致不让出cpu的执行权,导致整个系统崩溃。
抢占式:cpu的执行权是有系统去分配的。但是我们可以手动去调用一些方法,比如yield()方法让出cpu的使用权,还可以设置线程执行的优先级,10个等级,等级越高,被分配的概率会越大,但是设置优先级根本就靠谱,因为最终那个线程会被分配到执行权,还是由操作系统决定的。
只是实现线程安全的一种方式,就是保证数据在同一时刻只能被一个线程或者是一些线程(使用信号量的时候)使用。就是一个锁对象只能被一个线程占有,其他线程想要获取到这个锁对象就必须是等待这个线程释放了锁对象才性,所以造成了其他线程等待这个线程的执行。
互斥同步(阻塞同步,可以理解为:悲观锁)
代码编译成字节码文件会有两个字节码指令,一个是“加锁指令”,一个是“解锁指令”,这两个指令在操作内存的时候,又是对应着内存模型中的lock,unlock着两个原子操作,所以这就从内存层面保证的代码的线程安全。
Lock这个java来实现的,做的也是同步互斥,但是Lock通过硬件操作系统的CAS指令来的。
Lock
互斥同步
线程安全实现方式
1.一个进程可以包含多个线程,线程只是进程一个具体去执行任务的实体。
线程和进程的区别
Thread
固定线程数量的线程池
有限线程数的线程池
\t\t\tnewFixedThreadPool\t
不保证任务的执行顺序
相当于创建了和当前空闲cpu数量的线程来执行
工作窃取线程池
\t\t\tnewWorkStealingPool
特点一个核心线程都没有
线程数量最大可以是int的最大值
无限线程数的线程池
\t\t\tnewCachedThreadPool\t
一个线程,来周期性的执行任务
单线程定时任务线程
\t\t\tnewSingleThreadScheduledExecutor
线程数量固定,支持周期性执行任务
定时任务线程
\t\t\tnewScheduledThreadPool
只有一个线程来执行任务
单线程线程池
\t\t\tnewSingleThreadExecutor\t
使用ForkJoinPool 可以将数据计算分配到多个cpu上进行计算。
和其他线程类一样,就是将任务都放入线程池类种,该类是的入参是ForkJoinTask 的子类,有返回值的是RecursiveTask,没有返回值的是RecursiveAction。 工作窃取算法,就是一个线程对应一个双端队列,一个线程一个使用双端队列,将任务放在双端队列中,一个线程从另外一个任务的队列的末尾取出任务。并发包也提供了,开多个线程跑任务的方式
\t\tForkJoinPool
\t\tExecutors
\t\tThreadPoolExecutor\t
\t\tScheduledThreadPoolExecutor
线程池对象
除非创建线程池后,调用了prestartAllCoreThreads(),prestartCoreThread(),也就是创建线程池后会创建核心线程数量
1.线程池创建之后,线程池中没有一个线程
2.如果线程池中添加任务的时候,线程个个数小于核心线程个数(即使这是有空闲线程),那么就会去创建线程
3.当线程池中线程的个数大小等于核心线程数,再添加任务的时候就会把任务放在阻塞队列中,等待线程池调度
4.如果阻塞队列放满了,再添加任务的时候就会去创建核心线程之外的线程
5.当线程池中线程的个数等于最大线程数的时候,再添加任务就会执行拒绝策略
工作流程示意图
线程池工作过程
核心线程个数
非核心线程多久不做任务就会被回收
非核心线程存活时间的单位
ArrayBlockingQueue
阻塞队列中传递Runnable
工作队列
线程工厂,主要用来创建线程;
线程工厂
丢弃任务并抛出RejectedExecutionException异常。线程池默认的拒绝策略
\t\t\tAbortPolicy
丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
\t\t\tDiscardPolicy
由调用线程(提交任务的线程)处理该任务
\t\t\tCallerRunsPolicy
该任务有提交这个任务的线程去执行。
\t\t\tDiscardOldestPolicy
自定义
表示当拒绝处理任务时的策略
\t\t拒绝策略
线程池参数
自然运行状态
RUNNING
不接受新任务,执行已添加任务
SHUTDOWN
不接受新任务,也不执行已添加任务
STOP
所有任务已终止,任务数量为0
TIDYING
线程彻底终止
TERMINATED
线程状态说明
线程状态扭转
线程池状态
线程池中线程数=cpu核心数*cpu使用率*(1+等待时间/计算时间)
线程池中线程数=cpu核心数*cpu使用率*(总时间/计算时间)
计算公式
线程处于就绪状态,到运行状态中间的时间(处于就绪的时候,如果线程被阻塞,那么他将不会立即去执行自己代码)
等待时间
线程从开始运行到线程把代码执行完(代码执行完,线程是回到线程池而不是死亡)的时间
计算时间
在0-1之间因为线程等待的时间会远远大于计算的时间,我们就需要多穿件百分之多少的数目的线程去执行程序,才能尽可能的减少请求的阻塞。
计算公式说明
线程池的线程数=cpu的核心数+1
实际过程中线程数量
计算密集型
线程池线程数=cpu可用核心数/(1-阻塞系数)
线程池线程数=cpu可用核心数*(总时间/计算时间)
阻塞系数的取值在0-1之间
阻塞时间占总时间的百分比
阻塞系数=阻塞时间/(阻塞时间+计算时间)
假设任务有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
计算demo
线程池的线程数=cpu的核心数*2+1
IO密集型
线程池线程数量预估计算
线程池继承实现关系图
闭锁,栅栏,信号量,都是一种共享锁
同一个时刻,允许访问资源的线程是有限的
AQS实现共享锁的原理,就是:当队列中的一个线程获取到了这个共享锁,那么这个线程将唤醒和他一起共享当前锁资源的其他线程节点。 这种是实现闭锁,栅栏,信号量,读锁的基础类
共享锁概念
和闭锁,栅栏的功能类似,但是比闭锁,栅栏的功能更加强大,可以支持等待其他线程,也可以不等待其他线程
阻塞唤醒的实现还是基于 LockSupport的,在java这层实现最底层还是Unsfae
Phaser
让所有的线程做完任务之后(先做完任务的线程会等待后做完任务的线程),再统一的结束任务;
1.线程调用stat方法之前,就用CountDownLantch 对象调用 countDown()方法,这样才能拦住所有线程的执行;
2.无需深入到线程具体执行的任务里面调用 countDown();
3.执行完成之后再调用await()方法,唤醒所有的线程;
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和线程池一起使用
CountDownLantch和Thread一起使用
使用demo
CountDownLantch
就是让所有的线程都准备完毕(先准备完毕的线程会等待没有准备完毕的线程直到他准备好),然后再开启任务,让所有的线程一起开始做任务;这个时候,线程先昨晚任务的线程不会等待后做完任务的线程
1.需要在线程执行的具体任务里面调用 await()方法;
2.等所有的线程的任务都调用了await()方法,再调用 线程的start()方法;(这一点和CountDownLantch是一致的;)
CyclicBarrier使用的要点
CyclicBarrier使用demo
CyclicBarrier
默认 的是非公平的获取执行许可,非公平比公平的情况,效率会更高;我们也可以在构造的时候设置这个是否是公平的。
对信号量的占用可以设置占用时间,底层还是通过AQS来实现的,信号量过多的时候,被阻塞,其实也就是在AQS中被迫的休眠了。等释放一个信号量的时候,就会唤醒一个线程(默认非公平,可以设置公平)
Semaphore
共享锁
读锁是使用的AQS中的共享模式的共享锁;
写锁是使用的AQS中的独占模式的独占锁。
读写锁
线程获取到写锁之后,就可以获取读锁,然后再释放写锁,这个时候锁就由写锁变成了读锁了;
锁降级
获取到读锁之前,不能直接获取到写锁,必须是释放了读锁,才能再去获取到写锁;
不可升级
ReentrantReadWriteLock
对读写锁的一种优化,避免写锁一直被阻塞
当读线程执行的时候,如果有些线程在操作,那么读线程如果发现了数据不对,就会再执行一次读。避免了读写线程之间的相互阻塞,但是写和写线程之间还是阻塞的
StampedLock
该类的方法都是静态方法,直接使用类名来调用。用来阻塞线程的。park阻塞线程,unpark,解除阻塞。可以用用来让线程阻塞,阻塞多久
是让其他线程来唤醒这个线程。以下方法是在参数中传递一个需要被唤醒的那个线程的名称。
LockSupport
Condition是针对在锁的操作上休眠某个线程,或者唤醒某个线程;
Object休眠和唤醒针对的是所有的线程,而不只单单的在锁操作上的线程
Condition和Object唤醒,休眠的区别
Condition
lock包
AtomicIntegerArray,int数组AtomicLongArray,long数组AtomicReference,对象AtomicReferenceArray,数组对象
引用数据类型
AtomicStampedReference 维护对象引用以及可以原子更新的标记位。AtomicMarkableReference 维护对象引用以及整数“印记”,可以原子更新
维护对象标记原子类
AtomicLongFieldUpdater 对指定类的的指定Integer类型字段(字段必须是volatile修饰)进行原子操作的托管。AtomicIntegerFieldUpdater 对指定类的指定的Long类型字段(字段必须是volatile修饰的)进行原子操作的托管。AtomicReferenceFieldUpdater 对指定类中的某个字段(字段必须是volatile修饰)进行原子操作托管。
以托管的形式管理其他类的原子类
DoubleAccumulatorLongAccumulator同时托管多个数据,对这些数据进行原子性求和DoubleAdderLongAdder同时托管多个数据,这几个数据的初始化和值为0。对这些进行求和
以托管形式的计数器类
频繁的多简单的数据更新操作
ABA的问题就是,线程1把变量从A修改成了B;线程2把变量从B修改会A;线程3把变量从A可以修改成其他某个值;
也就是,这种修改操作,无法避免修改版本的先后顺序问题;这种情况下,可以使用AtomicStampedReference 可以解决上面的问题,因为修改的时候,会有一个版本号,没修改一次就会有一个版本号,
无法解决ABA的问题;
问题:
1、AtomicInteger的自增
2、AtomicInteger 自增方法调用了Unsafe 类的getAndAddInt
3、incrementAndGet方法对应的字节码指令如下
4、本地方法对应的c++实现如下
5、C++ 中不同操作平台的实现
6、调用汇编代码
所以总结一句话:并发包的实现,是调用了Unsafe类的比较与交换的方法实现的,最终去实现这个原子操作的还是操作系统去实现的;
AtomicInteger原子性的原理
对应了三个字节码指令
i++ 的自增
AtomicInteger的自增对应了 一个字节码指令;
自增
AtomicInteger
原子类
这个两个类区别就是,AbstractQueuedSynchronizer 这个主要是针对int类型的数据操作的,AbstractQueuedLongSynchronizer 是针对int 数据类型的做操作的
AbstractQueuedSynchronize和AbstractQueuedLongSynchronizer区别
就是将线程放入队列当中,去维护线程的状态。根据线程的状态去调用这个原子操作的方法。其实还是一利用队列进行分流的思想。
AQS实现的思想
AQS
调用Unsafe 类的比较与交换方法是否能执行成功;成功,加锁;不成功,加锁失败
1.把执行获取锁的代码的线程全部放入阻塞的线程队列中;
2.获取锁的初始状态和传入的是否一致;
3.使用比较与交换方法;
4.将获取到锁的线程从阻塞的队列中剔除来;
5.将当前获取到锁的线程对象赋值给获取到锁的线程这个值;
6.执行成功,就退出加锁的方法,执行失败,就被park阻塞;
加锁操作过程
cas加锁
释放锁,如果持有锁的线程刚好就是当前线程,那么就可以释放锁
1.判断当前线程是否是持有锁的线程;
2.初始化状态是否已经改变;
3.需要使用比较与交换方法把改变后的值再改变回去;
4.把获取到锁对象的线程数据设置成null;
5.让当前线程unpark;
解锁操作过程
释放锁
1.初始化状态,如果传入的状态值和初始化状态值不一样,那么就不能执行比较与交换的方法;
2.需要有一个队列能够保存被阻塞在锁的线程
3.持有锁的线程是哪个;
锁对象需要维护的数据
阻塞自旋
cas
阻塞队列特点
阻塞队列
并发容器
双端,意味着从头节点开始查找也行,从尾节点查找也行。
并发双端链表
存储的时候:存储当前元素的节点,下一个元素的节点,上一个元素的节点。
既然是双端,那么可以从头,尾都可以添加或者删除元素
concurrent包
jvm以字节流的方式从jvm的老年代或者是硬盘的元数据区域读取该类的字节码文件,通过一系列的加载转换后,得到java类的映射结构图(属性,方法),再通过反序列化流,创建对象。
反射在jvm中实现的机制
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]; //属性表}
二进制的字节码会存储如下关于类的数据
.我们再通过类的全路径,可以获取到类的对象。jvm是通过类的全路径在元空间或者是永久代中找到对应的字节码,通过反序列化生成这个对象,再讲这些方法,字段存储在反射对应的缓存中。
反射数据存取
Class
Comparator 比较器,接口,重写比较的方法时候需要传递进来两个对象进行比较; Comparable 可比较,接口,实现接口,重写比较方法,就是那其他对象和当前的对象进行比较,自定义比较的方法 jdk也提供了Comparators类,实现一些比较的方法
1.两者都是可以做比较的。
所以此时,这个compareTo(Object o)这个方法应该写在这个Object对象中,才能使对象调用compareTo(Object o)去比较的是相同属性的对象。
3.Comparable比较器是被实体类所实现;Comaprator这个比较就是定义比较的算法,比较的方式。
4.两者的差别就是,Comaprator可以纯粹的定义比较的算法,而不需要实际的实体类对象。
差别
Comparable和Comaprator
使用场景:可以用来封装一些线程不安全的类,在一个线程中改变了该类,不会影响到其他线程对该对象的使用;
ThreadLocal
这个包的功能,其实就是将Class这个类功能进行细化拆分了,已经一些是否能访问的一些控制;
提供了接口可供继承(继承了这些接口,可以使用一些顶层父类的一些反射方法)相关包装类,丰富了反射的功能。并提供了InvocationHandler 这个接口,反射执行方法;
相比Class 这个类:1.丰富了反射的方法;2.提供InvocationHandler 反射执行方法;
reflect包
一台计算机能够调用到另外一台计算机上的远程数据服务。
使用方式:1.服务提供者1.1方法类实现Remote接口,并且继承UnicastRemoteObject类;1.2注册通信端口,绑定通信路径;2.服务消费者2.1方法类实现Remote接口2.2获取远程方法的接口对象,2.3接口调用方法
rmi
外层是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内部单独判断的,判断是否超过阈值
每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
扩容操作
操作方法
摒弃了Segment的概念,也就是摒弃了分段锁
并发控制使用Synchronized和CAS来操作
synchronized+CAS+HashEntry+红黑树
数组中任意一个链表的长度超过8个
数组长度大于64个时
两个条件必须达到
链表转红黑树
链表转红黑树条件
默认情况下,每个cpu可以负责16个元素的长度进行扩容
node数组的长度为32,那么线程A负责0-16下标的数组扩容;线程B负责17-31下标的扩容,并发扩容在transfer方法中进行
2个线程分别负责高16位和低16位的扩容,不管怎样都不会产生冲突
扩容过程
扩容示意图
扩容
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的并发度是提高了。
并发度
1.7 先获取锁,再根据key的hash值订到segment,再根据key的hash值找到具体的HashEntry ,再进行插入或者覆盖操作,最后释放锁
1.8根据key值hash定位到具体的Nodej节点,再判断首节点是否为空,空的花通过cas去复制首节点,非空,会用Synchronized去锁住首节点,再去操作
put操作
1.7需要调用unlock()操作
锁释放
1.7和1.8的区别
1.8的锁粒度比1.7的锁粒度更细
数据查询插入的时间复杂度更低,原来是O(n),现在是0(n) 或者O(logn)的情况
put方法效率更高
优化点
\tConcurrentHashMap
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个槽位上面。
1.7的元素是放在单向链表的头部,1.8是放在单向链表的尾部
1.8元素存储可以转换成红黑树,提高了,元素读取的效率;1.7是没有将链表转成红黑树的功能
1.7的存储形式是外层是数组,内层是链表;1.8是外层是数组,内层是链表或者红黑树
1.7外层是Entry数组;1.8外层是Node数组,
1.8重写了Hash方法,降低了hash冲突,提高了Hash表的存,去效率(增加了异或运算,位运算)
因为 1.7 头插法扩容时,头插法会使链表发生反转,多线程环境下会产生环。
1.7扩容后添加元素,1.8扩容前添加元素
提高扩容效率
提高hash效率
1.7的散列函数做了多次位移,异或运算,1.8只做一次
JDK1.7和JDK1.8的区别
多线程扩容,引起死锁问题
多线程put的时候可能导致元素丢失
1.7和1.8都存在的问题
put非null元素后get出来的却是null
HashMap并发环境下的问题
在第一次put元素的时候创建的,也就是和懒加载机制很像
1.HashMap的散列表是什么时候创建?
1.当前链表的长度大于8;
2.外层Hash桶的数组长度达到64个;
2.链表转红黑树的前提条件?
1.如果hash冲突严重,那么就会导致Hash槽下面的数据的链表长度过长;查询数据的复杂度就是0(n),换成红黑树之后就变成了O(log)
3.为啥要使用红黑树存储?
比如说当前是16个hash槽,扩容之后就会变成32个hash槽;当前第一个槽位中需要被迁移的数据会迁移到第17个槽位上;也就是以此类推,当前第16个槽位的需要被迁移的数据被迁移到第32个上;
扩容之后的hash槽计算?
当前实际存储元素的个数超过一定百分比(默认的75%,这个比例可以通过构造函数设置)
什么情况下回触发扩容?
也就是没有需要被迁移的数据
1.hash槽没有数据
根据新计算出来的hash槽位把数据存放过去
2.hash槽里面只有一个数据
3.hash槽里面是链表
4.hash槽里面是红黑树
扩容之后的hash槽计算?比如说当前是16个hash,在第1个槽位,在扩容之后,需要迁移的数据就会落在1+16 =17 也就是在第17个槽位上面。
扩容之后的老数组中的数据怎么往新的数组里面迁移?
假设扩容因子=1,也就是每一次扩容都前集合的元素个数和当前集合的最大容量相同,无形之中就增加了hash碰撞,hash碰撞之后,就会形成对应的链表过长,红黑树高度过高,这样就会导致在查询的时候耗时过高。
如果扩容引子过小,map集合在存储元素的时候,就会经常发生扩容操作,导致添加元素耗时过高。
0.75是经过很多科学计算之后,平衡了添加和查询操作之后,得出来最佳的数值
扩容因子为啥是0.75
4.扩容相关
5.get死循环,put闭环
代码截图
key已经存在,需要修改HashEntry对应的value;
key不存在,在HashEntry中做插入
原因分析
不安全原因
常见问题
1.如果key为null,那么hash值就是为1;
1.获取当前key的hashCode值与value的Code值做异或运算
2.拿到第一步的值,让当前值和当前值的与16做位运算之后的值做异或运算
2.如果key不为null
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);}
hash方法
1.第一次put的时候,如果散列数组为空,那么就会进行扩容散列操作;
2.如果hash桶只要单个数据,那么先对比数据是否一样,一样就覆盖原来的值;
4.如果hash桶的数据是红黑树,如果key不重复,那么就把数据放在key的末尾;如果key重复了,那么就替代源码的value值;
3.如果hash桶的数据,是链表,如果key不重复,那么就把数据放在key的末尾;如果key重复了,那么就替代源码的value值;
Put方法解析
源码解析
HashMap
双列集合
存储过程示意图
双向链表,加数组实现,其实最终存储的时候还是 外层是数组,内层是链表;和HashMap一样,不过就是LinkedHashMap 是有序的。
\tLinkedHashMap
\tSkipList
LinkedList是双向链表实现的;ArrayList是数组实现的
查询的时候
如果都是在尾部添加,那么添加的效率是一样的,
如果都是在头部添加,那么ArrayList效率很低,因为需要有数组元素的复制跃迁;LinkedList集合只是需要把引用之前新的元素就行了。
中间添加,效率都很低,ArrayList需要做数据复制移动;LinkedList需要先遍历查询到具体的位置,然后再前后引用的断开与重新连接;
添加删除修改操作
LinkedList,ArrayList
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;}
存取方法
源码分析
Stack
TreeSet集合的构造方法是需要传递 1.NavigableMap接口,而NavigableMap的实现类是TreeMap类;2.不传就是直接默认是 TreeMap类来实现;3.或者是比较器;4.其他单列集合;结论,就是构造函数使用的是哪一种,最终都会把这个map集合变成TreeMap集合;所以帮助TreeSet去完成增删改查工作的都是TreeMap来实现的; 也就是
/ Dummy value to associate with an Object in the backing Mapprivate static final Object PRESENT = new Object();
1.TreeSet集合的功能都是由TreeMap来实现的
2.TreeSet集合拥有和TreeMap集合的一切特性;
TreeSet
HashSet的构造方法中都是包含一个HashMap集合来来实现的,也就是Hashset的所有方法都是HashMap来帮助实现的;还是以key value 的形式来存储的,
源码中有如下一个Object 就是做了key value 的value的值
1.HashSet是借助于HashMap来完成集合的,
2.HashSet和HashMap都是无顺序的存储的。数据结构和HashMap一致;
HashSet的实现是通过HashMap来实现
HashSet
实现通过加锁实现;
线程安全
1.一个是容量的数组(默认是10个,除非自己给定长度);2.一个容量大小;3.一个是容量增量常数(每次扩容的时候增加的长度,默认值是0。1.如果该值为0,那么每次扩容后的长度是扩容之前长度的两倍;2.如果不为0,该值的大小就是每次扩容增加的长度) protected Object[] elementData;protected int elementCount;protected int capacityIncrement;
字段
构造函数
扩容代码
在构造方法中就有一个参数是,就是每次数组满了,数组扩容的时候是扩容多少个元素;如果该参数不传入,那么该值就是默认为0;那么在扩容的时候就会默认是按照原有数组长度两倍进行扩容;
增加:就在原有数组后面新加一个元素;元素个数+1;删除:就是将元素置空,然后长度减1;查询:根据元素查询,就是将数组中元素遍历,利用equal方法一个一个比较;根据索引查询,直接就是返回数组对应的元素;
集成实现关系示意图
Copy集合
单列集合
util包
总结
线程安全是因为每个方法都加了sychronized;和字符串做拼接的时候,调用的拼接的方法还是String 的拼接方法
StringBuffer
线程不安全,和字符串做拼接的时候还是调用的是字符串的拼接的方法;如果是和StringBuffer做拼接, 那么调用的拼接方法就是StringBuffer的
StringBuilder
jdk定义的注解:注解上的注解
元注解
类
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机制(后期再做解释)
程序执行的顺序是按照代码的顺序一致
java特性
单例设计模式,其实就是为了在一个域中只有一个该类的对象,节约内存空间,方法对对象进行统一管理;
一种饿汉式,一种懒汉式,区别在于,饿汉式在该类被加载的时候就创建了该类的对象;懒汉式,是需要用到的时候才会去创建这个类的对象;在不主动使用同步的情况下, 因为类只会被加载一次,所以饿汉式也只会创建一次这个类的对象;而懒汉式,就有可能在同时判断为null的时候去创建这个类的对象;
创建方式
1.将构造方法私有化;2.提供一个对外的静态获取该类对象的方法,
单例设计模式的创建过程
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; }}
懒汉式
\t单例
把一个类对象的构造过程交给一个专门构建对象的类去处理
定义和使用
建造者模式
创建型
代理类和被代理类实现统一接口,然后在代理类中引入代理对象,通过被代理对象去实现代理对象的功能;
最终我们去调用方法的对象还是代理对象
因为代理对象的构造方法的参数就是被代理的对象
所以说代理对象的创建是根据被代理对象而来的
也就是说,如果有多个对象需要被代理,我们就要创建多个代理对象,而多个代理对象就除了调用的方法不同之外。其他都一样
也就是一个代理对象只能代理一个类。无法同时代理多个类
代码冗余
静态代理
被代理类必须要实现接口;
JDK的Proxy类在我们调用方法newProxyInstance时,生成一个代理对象在内存中,该对象根据参数实现了被代理类的接口,重写了接口中的方法。在此方法内,调用了我们创建的InvocationHandler的invoke()方法。实际调用的就是InvocationHandler中我们重写的invoke方法。所以动态代理在运行时会根据我们重写的代码进行增强
底层实现反射
缺陷是:需要代理类和被代理类实现同样的接口,如果被代理对象没有实现接口,那么代理将无法完成;
\t\tjdk代理
\t\t\tASM框架
通过cglib包来实现cglib代理,cglib包下最终也是调用的asm,字节码修改技术来相当于生成了一个新的类来替代原有的类执行方法。
代理类需要设置父类字节码文件对象,以及实际执行方的类的对象,再通过create方法获取到被代理的对象,被代理类的对象去执行相关的方法,最终方法的执行都会走 最终那个实现类的intercept 方法。
\t\tcglib代理
实现
jdk1.6之前cglib效率高,由于jdk1.6之后对jdk代理有优化,在1.6之后少量的调用次数比较的时候jdk效率高,只有大量的调用的时候cglib高;在1.8之后,jdk代理的效率超过了cglib代理
1.性能
jdk<cglib
2.创建对象的时间
动态代理的两种方式对比
动态代理
1.静态代理是静态编译,动态代理是动态编译
2.静态代理,只能代理一个类;动态代理,可以代理多个
动静态代理的区别
动态代理和静态代理的最大区别就是:代理的对象创建的时间不同;静态大力是编译之前就已经完成了对象的创建;动态代理运行的时候去创建对象的;
有时候一个对象不能直接引用另外一个对象,我们就用代理对象去替代这个不能被引用的对象
中介作用
可以在代理对象上再扩展一些新的功能,而不用去修改被代理对象
增加了可扩展性
对象引用的是被代理的对象,我们只需要去修改被代理的对象,而原对象无需做修改
对原有的对象代码无侵入性
代理模式好处
\t代理模式
享元模式其实就是为了最大程度的降低内存的消耗,而设计的,最大程度上减少对象的创建,
设计思想
String 对象就是 享元 模式的设计方式,就是为了最大程度的降低内存的消耗。
String常量池、数据库连接池、缓冲池
享元模式
最底层的实现类可以比他继承的接口,父类获取到更多的可以执行的方法。并且装饰类在实现接口功能的时候,可以借用相同接口下另外一个类的的实现来实现这个接口;
装饰者的构造方法中提供以顶层接口为入参的构造方法,由于java的多态特性,这个顶层接口可以调用任意实现类的接口方法,大大增强了当前类的功能
1.装饰者和被装饰者都必须要实现同一个接口;
2.被装饰者的有参构造器的参数必须是这个顶层接口这个类,这样因为装饰者实现了这个顶层接口,那么传入装饰者作为这个有参构造的话,就可以拿到被装饰者的对象,从而使用被装饰者的方法。
设计要点
装饰者模式
结构型
用一个类来封装一系列的对象交互,这个类就是中介者
本质就是中介者将者一系列的对象都封装在自己的这个类中,在自己的这个类种来进行交互。
中介模式
说简单一些,就是有些类似于击鼓传花,将事物的处理交给对象的下一家,下一家可以处理,也可以接着往下传
写法,就是定义顶层抽象处理接口,多个具体处理的接口继承了这个抽象接口
在具体执行的时候,指定每个处理对象的下一个处理对象
责任链模式
行为型
设计模式
加锁指令
monitorenter
解锁指令
monitorentexit
编译后生成了加锁,解锁指令
字节码层面
jvm调用了操作系统提供的同步方法;
操作系统本身有一些同步的操作
操作系统和硬件
Synchronized实现
锁对象
Synchronize 内部,有一个锁升级的过程;jdk1.6自动打开;1.jvm会偏向第一个执行的线程,给他加上偏向锁;
2.如果这个时候有其他线程来竞争,这个偏向锁就会升级成轻量级锁,这轻量级锁,多数情况下是自旋锁;
3.如果并发程度高,这个自旋锁会自旋很多次,如果超过十次,还没有拿到锁对象,那么锁又会升级成重量级锁(悲观锁);
Synchronized 锁升级过程
锁状态转换过程
无锁状态
偏向锁:适用于单线程适用锁的情况,如果线程争用激烈,那么应该禁用偏向锁。
偏向锁状态
轻量级锁:适用于锁竞争较不激烈的情况(这和乐观锁的使用范围类似)
轻量级锁状态
重量级锁:适用于竞争激烈的情况
重量级锁状态
锁状态
锁升级
编译器发现某段代码有连续的加锁解锁操作就会进行锁粗化,将连续的加锁解锁变成一个加锁解锁,提高代码执行效率;
锁粗化
编译器发现某段代码不存在多线程竞争共享资源,编译之后就会把锁消除掉,减少加锁解锁的性能消耗
锁消除
锁优化,都是针对锁的优化技术,都是为了在线程之间能够高效的共享数据,解决竞争问题。
锁优化概述
锁优化
Lock 只需要jvm层面来实现锁;Synchronize 需要jvm和操作系统交互才行
cas 的实现是需要cpu 硬件的支持,修改内存地址的偏移量
公平锁机制:synchronized和lock锁默认都是非公平的,非公平锁就是不能按照申请锁的顺序来获取到锁对象,而lock可以将锁机制设置为公平的,就是按照等待获取锁独享的次序来获取锁对象。就是公平锁。
多条件的绑定多对象:synchronized锁对象就是休眠或者唤醒的时候只需要一个条件,就是wait()条件等待,notify(),notifyall()方法唤醒线程;lock可以在这些条件之外再条件,等待,或者是唤醒线程。newCondion()方法就可以,只有当所有的条件都满意的时候才会等待,或者是唤醒线程。
Lock锁可以可以等待中断,就是一个线程获取不到锁的时候(已经有线程已经占用了锁对象),可以放弃等待,去执行其他代码。
Synchronize和Lock对比
1. 线程执行完了该执行的代码,线程就释放了该锁
2. 线程执行代码的时候发生异常,此时jvm就会让该线程自动释放锁
synchronized释放锁的方式
1. 如果线程阻塞或者是sleep()了,线程不会释放锁对象,那么其他线程还会等待这个对象去执行完代码。影响效率
Synchronized缺陷
超高并发 synchronized
低频并发 Lock
\tsynchronized
瞬时态
用此修饰符的字段是不会被序列化
类中的字段是可以通过其他字段被推导出来,此字段不需要做序列化
一些安全性的信息,一般情况下是不能离开JVM的
需要使用一些类,但是这个类对程序逻辑没有影响,也可以用来修饰
\ttransient
标志被该关键字修饰的方法是调用系统的本地方法,也就是调用不是java代码,调用的是系统的c语言的方法。比如Thread类,Object类,System类由于很多功能并不是java语言所能完成的,需要借用到计算机操作系统的代码来实现这些功能。
\tnative
\t\t\t变量被修改之后,jmm自己的工作内存复制到主内存当中
先线程工作内存副本中的值
从工作内存复制到主内存
线程写volatile变量
从主内存中获取最新值到工作内存中
从工作内存中读取volatile变量的副本
线程读volatile变量的过程
\t\t可见性
\t\t\t该关键字修改的变量,在被使用的时候都会先去主内存中获取到该变量的最新值,复制到工作内存当中
\t\t\t不存在并发问题但是又被很多地方同时使用。
\t\t使用场景
\t\t\t并发环境下使用会有线程安全问题;java的操作并不是原子操作
\t\t缺陷
\t\t\t能够屏蔽指令重排序
\t\t\t数据一致性
\t\t\t一致性的开销小于锁的开销
\t\tvolatile关键字特点
给字段加上了ACC_volatile标记
StoreStoreBarrier
StoreLoadBarrier
读取该变量的操作在读屏障之后执行,保证获取的数据是最新的数据
LoadLoadBarrier
LoadStoreBarrier
写入共享变量之后,所有的写操作已经全部完成
写屏障
编译后给该字段加上读写屏障
翻译成对应的汇编指令,然后给读写加锁
\tvolatile
default关键之jdk1.8提出来的关键字,1.8jdk中的接口可以用default关键来修饰,用此关键字修饰后的接口,有自己默认的接口实现。也就是能够像实现类一样有自己的方法实现了。
default
protected 修改的方法,本类,子孙类,同一个包下可以调用
protected
静态的使用特性是:直接就是用类名来调用,并没有像其他普通方法一样去创建对象,再用对象去调用方法。
因为静态的属于类,不属于类的某个实例(对象),所以静态就不能像普通方法和普通变量那样存储在栈内存(会弹栈消除),也不会存在堆内存(会被垃圾回收器回收)。静态存在方法区中,永久的不会被消除或者被垃圾回收器回收。
为什么会有这个特性:原因就是静态的东西不属于对象,而是属于类,对象只是类的实例化而已,类会被装载到方法区中,而对象只是在虚拟机的堆内存中,对象的成员变量,对象的方法则会被存储在栈内存中,用完就弹栈消除。当对象已经不存在对成员变量和方法的引用的时候就会被标记这个对象可以被删除,而垃圾回收会被不确定的时间去回收这些。
所以说栈内存的生命周期很短,堆内存的生命周期比栈内存长,但是比方法区的生命周期短。
static的既然是属于类,不属于对象,那么在对象序列化的时候不会去序列化被statis修饰的字段
存在的一个问题:因为静态的都属于类,在内存中只会存储一份,而且会被所有线程共享,所以会存在线程不安全的问题。
static
if else 是每一个分支的判断条件都需要执行到,而switch case 就是直接会跳跃到满足相关条件的 分支上去值代码,这样就if else 的效率更高;
switch-case与if-elseif的根本区别在于汇编时,switch-case会生成一个跳转表来指示实际的case分支的地址,而这个跳转表的索引号与switch变量的值是相等的。从而,switch-case不用像if-elseif那样遍历条件分支直到命中条件,而只需访问对应索引号的表项从而到达定位分支的目的。
switch case
关键字
公平锁是指多个线程按照申请锁的顺序来获取锁
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象
Synchronized 非公平锁; ReentrantLock 默认也是非公平锁,可以设置成公平锁
公平锁/非公平锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例
Synchronized ; ReentrantLock 都是可重入锁;
可重入锁
独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。
Synchronized ; ReentrantLock 独占锁;ReadWriteLock 读操作是共享锁,写操作是独占锁;
独享锁/共享锁
ReentrantLock ,Synchronized
互斥锁
ReadWriteLock
互斥锁/读写锁
从并发角度来给锁分类
悲观的认为,不加锁的并发操作一定会出问题。悲观锁在Java中的使用,就是利用各种锁
悲观锁
则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。
乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新
乐观锁/悲观锁
锁的一种设计理念
ConcurrentHashMap 就是这种设计理念
分段锁
偏向锁会偏向第一个获取到它的线程进行偏向
就是偏向锁会偏向第一个获取到它的线程进行偏向,如果在这个线程获取到了这个偏向锁后,没有其他线程再获取这个锁,则持有偏向锁的线程将永远不再需要同步就能执行代码了。如果存在线程竞争,偏向锁就会失效
轻量级锁和偏向锁,其实都是需要再没有多线程竞争的时候才会发挥出他们的作用,将重量级的锁,变成那种不需要进行同步就能执行的代码。
所谓的轻量级锁,并不是替代什么重量级锁的,是一种锁的优化机制,只是针对于没有数据不存在多线程竞争的时候,减少传统的重量级锁使用互斥量的时候造成的性能消耗
就是如果一个线程第一次获取了这个锁对象,只要不存在线程竞争的时候。只要是这个锁对象的同步的代码块,那么这个线程就不需要再进行加锁的操作,直接就执行代码块,也不需要解锁的操作。如果存在多线程竞争这个锁,那么轻量级锁就会变成重量级锁,然后同步的时候需要加锁解锁
实现的方式,大概就是在虚拟机的对象头中标记获取轻量级锁的标记
轻量级锁
偏向锁/轻量级锁/重量级锁
自旋锁就是线程等待的时间不让线程挂起,浪费系统并发资源
就让这个等待的线程做一个忙循环(自旋操作),这项技术就是自旋锁
让一个线程等待另外一个线程释放锁对象,等待的时候线程的挂起然后再回复线程都是需要再内核中完成的。这个给系统会造成很多的并发性能压力。获取锁的过程是一个很短的过程,没必要为了这个很短的时间去挂起,恢复线程(其实也就是避免了内核线程在不同的java线程之间来还切换,内核线程来java线程之间的来还切换是内核的调度器完成的,来还切换还是很浪费时间的)。
为何需要自选
自旋锁本身是避免了线程之间切换的开销,这个和阻塞还是有区别的。我们可以设置根据获取锁的等待的自旋的次数,合理的调节等待的时间,如果自旋次数晚完了,还没有获取到锁对象,那么就会将线程挂起。
1.6的jdk中加入了自适应的自旋锁,自旋的时间不再是固定了,是由之前自旋在这个锁上的时间,以及锁的拥有者的状态来决定的。就是有一个不断修正自旋时间的过程。
自旋锁与自适应自旋
特性分类
根据锁的状态,特性和设计理念,并不是实实在在的锁
Synchronized
类型分类
锁分类
\tjava中的锁
http请求是2.0版本
interface IMyInterface { public void test0();}
jdk7
interface IMyInterface { public static void test1 () { System.out.println(\"静态方法\"); } default void test2() { // 可以被实现类覆写 System.out.println(\"默认方法\"); } public void test0();}
jdk8
interface IMyInterface { private void test() { System.out.println(\"接口中的私有方法\"); } public static void test1 () { System.out.println(\"静态方法\"); } default void test2() { System.out.println(\"默认方法\"); } public void test0();}
jdk9
接口可有默认的实现
在某个版本的java程序运行的时候可以选择不同版本的class版本
多版本jar包兼容
在终端开发中尤其明显
1、jvm启动的需要加载整个jra包,导致不需要使用的类,模块也被加在到内存中,浪费不必要浪费的内存;
暂时无法理解这些
不同版本的类库交叉依赖;每个公共类可以被类路径下任何其他的公共类所访问到,会导致使用并不想开放的api;类路径可能重复等
降低启动所需要的内存,按需加载对应的包
简化类库,方便开发维护
模块化好处
模块化实际上就是包外面在包一层,来管理包。
模块化实现
模块化
可以在dos窗口,终端中进行编程
提供类似python交互式编程环境
jshell命令
mac系统已经支持
多分辨率图像api
引入轻量级的JSON API
新的货币api
暂时还无法看到的api
新增api
Applet和appletviewer的api都被标记为废弃
主流浏览器已经取消了对java浏览器插件的支持
弃用api
具体解释
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); } }}
对应结果
对应的demo
钻石操作符(泛型)的升级
传统语句
需要将资源的实例化放在try 的小括号里面
优化后,jvm自动释放资源
一次性可以释放多个资源
try语句升级
底层是char 数组
不同的编码字符集一个字符所需要的字节个数不一致,1-2个
相对于jdk8可以减少字符串内存空间消耗
底层是byte数组
String实现
提高编码效率
所创建的集合都只能是做读取操作,无法做添加删除操作
快速创建只读集合
只是做筛选,并不会修改list集合本身元素
Stream.ofNullable(null).forEach(System.out::println);
类似下面代码
for (int i = 0; i < 5; i++) { System.out.println(i);}
Optional.ofNullable(list).stream().forEach(System.out::println);
增强的流api
改进
AOT(Ahead of time)
jdk9中提出来,jdk10正式发布该新特性
java引用在jvm启动钱就被编译成二进制代码
动态编译器
jdk9版本采用单线程回收
G1回收期新老年代都可以做回收
jdk1.9 默认垃圾收集器G1
jdk9新特性
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 可以作为局部变量
局部变量类型推断
jdk9的时候单线程回收
发生full gc 的时候可以使用多个线程并行回收
G1引入并行 Full
减少不必要的内存消耗
同一个机器下的多个jvm可以实现数据共享
应用程序类数据共享
ThreadLocal 握手交互。在不进入到全局 JVM 安全点 (Safepoint) 的情况下,对线程执行回调。优化可以只停止单个线程,而不是停全部线程或一个都不停
线程本地握手
在备用存储装置上进行堆内存分配
基于Java的JIT编译器,目前还属于实验阶段
预先把 Java 代码编译成本地代码来提升效能
新增加Graal编译器
javah 用于生成C语言的头文件
JDK8开始,javah的功能已经集成到了javac中。去掉javah工具
删除javah工具
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); }}
List、Set、Map新增加了一个静态方法 copyOf
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);
transferTo方法复制文件
IO流大家族添加Charset参数的方法
JDK10给 ByteArrayOutputStream 新增重载 toString(Charset charset) 方法,通过指定的字符集编码字节,将缓冲区的内容转换为字符串
ByteArrayOutputStream新增toString方法
jdk10新特性
String s1 = \"\\t \\";System.out.println(s1.isBlank()); // 判断是否为空白 true
判断是否是空白字符串
s1 = \"\\t sss\\";System.out.println(s1.trim().length()); // 去除首尾空白,不能去除全角空格System.out.println(s1.strip().length()); // 去除首尾空白,可以去除全角空格System.out.println(s1.stripLeading()); // 去除头部空白System.out.println(s1.stripTrailing()); // 去除尾部空白
去除首尾空白
方法可以对字符串每一行进行流式处理
ASCCCCWWW
\"asc\ccc\www\".lines().map(str -> str.toUpperCase()).forEach(System.out::println);
lines()
bbException 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)
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()); // 如果为空,排除异常
Optional方法增强
try (var inputStream = new FileInputStream(\"D:/test.jar\"); var outputStream = new FileOutputStream(\"test2.jar\")) { inputStream.transferTo(outputStream);} catch (Exception e) { e.printStackTrace();}
transferTo()方法
String类新增方法
-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模块
移除废弃api
解释:如果java文件里没有使用别的文件里的自定义类,那么就可以直接使用java就可以编译运行java文件,也不会输出class文件
更简化的编译运行程序
Unicode10加入了8518个字符,4个脚本和56个新的emoji表情符号
Unicode编码
jdk11以前的java应用程序在docker中运行的性能会下降,但现在此问题在容器控制组(cgroups)的帮助下得以解决,使JVM和docker配合得更加默契
完全支持linux容器
通过JVMTI的SampledObjectAlloc回调提供一个低开销的堆分析方式
免费的低耗能分析
ChaCha20-Poly1305这种更加高效安全的加密算法,代替RC4;采用新的默认根权限证书集,跟随最新的HTTPS安全协议TLS1.3
新的加密算法
Java 层面的事件,如线程事件、锁事件,以及 Java 虚拟机内部的事件,如新建对象,垃圾回收和即时编译事件
记录运行过程中发生的一系列事件
启用设置 -XX:StartFilghtRecording
Flight Recorder
处理内存分配但不负责内存回收的垃圾回收器,堆内存用完,JVM即刻OOM退出
性能测试(过滤GC引起的性能消耗,相当于控制变量)
内存压力测试(看看不回收的情况下,到底能不能消耗指定大小的内存)
执行非常短的任务(GC反而是浪费时间)
VM接口测试、延迟吞吐量的改进等实验性质的调优
EpsilonGC使用场景
ZGC是一个并发、基于区域(region)、标记压缩算法的GC
不管堆内存多大,STW 时间不会超过10ms
启用ZGC的方法:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC目前ZGC只能用于64位的linux操作系统下
G1的完全并行GC
新增垃圾回收器
jdk11新特性
雪兰多收集器使用的内存结构和G1类似,都是将内存划分为区域,整体流程也和G1相似
最大的区别在于雪兰多收集器实现了并发疏散环节,引入的Brooks Forwarding Pointer技术使得GC在移动对象时,对象的引用仍然可以访问,这样就降低了延迟
其工作周期如下:1)、初始标记,并启动并发标记阶段2)、并发标记遍历堆阶段3)、并发标记完成阶段4)、并发整理回收无活动区域阶段5)、并发疏散,整理内存区域6)、初始化更新引用阶段7)、并发更新引用8)、完成引用更新阶段9)、并发回收无引用区域阶段
启用方法:XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
新增Shenandoah GC
现有微基准测试的运行和新微基准测试的创建过程
新增一套微基准测试
简化表达式
可以同时处理多个case
改进点
switch表达式
jdk12中的G1将在应用程序不活动期间定期生成或持续循环检测整体的java堆使用情况,以便更及时地将java堆中不使用的内存返回给OS。这一改进带来的优势在云平台的容器环境中更加明显,此时内存利用率的提高会直接降低经济成本
空闲时候G1回收器会自动将java堆内存返回给操作系统
把回收集分为必须部分和可选部分,优先处理必须部分
必须部分主要包括G1不能递增处理的部分(如年轻代),也可以包含老年代以提高效率
优先处理必须部分时,会维护可选部分的一些数据,但产生的CPU开销不会超过1%,而且会增加本机内存使用率;处理完必须部分后,如果还有时间,就处理可选部分,如果剩下时间不够,就可能只处理可选部分的一个子集。处理完一个子集后,G1会根据剩余时间来决定是否继续收集
G1 垃圾回收器的回收超过暂停目标,则能中止垃圾回收过程.
G1PeriodicGCInterval
G1PeriodicGCSystemLoadThreshold
以上两个参数值都为0表示禁用此功能
定期GC的类型,默认是Full GC,如果设置值了,就会继续上一个或启动一个新的并发周期
G1PeriodicInvokesConcurrent
参数设置
jdk12新特性
支持java应用执行之后进行动态归档,以后执行java程序后一些类就可以直接从这些归档文件中加载了
动态CDS档案
ZGC在jdk11中引入的收集器,jdk13中使能了向OS提交未使用的堆内存
ZGC中的区域称之为ZPage,当ZGC压缩堆时,ZPage被释放,然后变成ZPageCache,最后使用LRU算法对PageCache区域进行定时清除。时间间隔默认为5分钟,用户自定义时间间隔尚未实现,而如果-Xms和-Xmx相等的话,这个功能就相当于没有
ZGC提交未使用的堆内存
重新实现旧版套接字api
switch表达式中引入yield
String s = \"\"\" <html> <head> <meta charset=\"utf-8\"/> </head> <body> <p>aaa</p> </body> </html> \"\"\";
s = \"\"\
文本块
jdk13新特性
Object obj = \"kuaidi100\";if(obj instanceof String){ String str = (String) obj;}
jdk14版本之前写法
Object obj = \"kuaidi100\";if(obj instanceof String str){ //直接使用str}
if (obj instanceof String s) { // can use s here} else { // can't use s here}
jdk14版本的写法
instanceof省去了强制类型转换的过程
移除 CMS(Concurrent Mark Sweep)垃圾收集器:删除并发标记清除 (CMS) 垃圾收集器
MacOs windows 实现 ZGC
弃用 Parallel Scavenge 和 Serial Old 垃圾回收算法的组合
NUMA是啥?
这段话怎么理解
G1 NUMA感知内存分配:现在尝试跨垃圾收集在年轻一代的同一NUMA节点上分配并保留对象。这类似于并行GC NUMA意识。G1尝试使用严格的交错在所有可用的NUMA节点上均匀分配Humongous和Old区域。从年轻一代复制到老一代的对象的放置是随机的。这些新的NUMA感知内存分配试探法通过使用-XX:+UseNUNMA命令行选项自动启用
垃圾回收器
参数-XX:+ShowCodeDetailsInExceptionMessages
更详细地显示空指针异常
更详细的空指针异常
一个打包工具,可以将java应用直接打包成rpm,dmg或者exe在各自平台可以点击直接运行
Packaging Tool (Incubator)
之前是不可以用于实时监控
jdk14之后可以实时获取到JVM的运行情况
对飞行记录器功能升级
JFR Event Streaming JFR事件流
需要验证下对应的版本是否有这个方法?
涉及的线程挂起Thread的方法已经在jdk14版本种废弃
不建议使用线程挂起、删除
椭圆曲线:
弃用
jdk14新特性
仅考虑最大堆大小。旧的计算还考虑了初始堆大小,但是当未设置堆大小时,这可能会产生意外的行为
区域大小四舍五入到最接近的2的幂,而不是减小。在最大堆大小不是2的幂的情况下,这将返回更大的区域大小
两点改进
优化G1回收器
使用-XX:+UseZGC命令行选项启用ZGC
ZGC可以正式使用
一个低暂停时间的垃圾收集器
Shenandoah正式使用
简化了编写 Java 程序的任务,同时避免了常见情况下的转义序列
增强 Java 程序中表示用非 Java 语言编写的代码的字符串的可读性。
二次优化
instanceof
优化
禁用和弃用偏向锁定
移除 Nashorn JavaScript 引擎
弃用 RMI 激活以进行删除
引入 API 以允许 Java 程序安全有效地访问 Java 堆之外的外部内存
引入隐藏类,即不能被其他类的字节码直接使用的类
隐藏类旨在供在运行时生成类并通过反射间接使用它们的框架使用。隐藏类可以定义为访问控制嵌套的成员,并且可以独立于其他类卸载
隐藏类
使用密封的类和接口增强 Java 编程语言。密封的类和接口限制了哪些其他类或接口可以扩展或实现它们。
密封类(第一版预览)
爱德华兹曲线数字签名算法
新增API
jdk15新特性
最终版本
Records
弹性的元空间
元空间中未使用的 class 元数据内存更及时地返回给操作系统,以减少元空间的内存占用空间
Elastic Metaspace
ZGC 支持并发栈处理
不太懂ZGC回收过程?
把 ZGC 中的线程栈处理从安全点移到了并发阶段
HotSpot 子系统可以通过该机制延迟处理线程栈
使用统一的api有利于以后对系统化版本的升级
好处是
模块外部的代码只能访问该模块导出的包的公共和受保护元素
protected 修饰的元素只能由定义它的类已经它的子类访问
Java 9中,我们通过利用模块来限制对JDK内部元素的访问,从而提高了JDK的安全性和可维护性。模块提供了强封装
强封装适用于编译时和运行时,包括已编译代码试图在运行时通过反射访问元素时。导出包的非公共元素和未导出包的所有元素都被称为是强封装的
对内存操作api
jdk16新特性
public abstract sealed class Student permits NameChangeRecordService {}
类 Student 被 sealed 修饰,说明它是一个密封类,并且只允许指定的 3 个子类继承
密封类
回复严格模式的浮点数定义
伪随机数生成器增强
新的api Apple Metal
老的api Apple OpenGL
mac系统平面渲染api更换
通过配置过滤器,通过一个 JVM 范围的过滤器工厂,用来为每个单独的反序列化操作选择一个过滤器。
上下文特定反序列化过滤器
增强
static String formatter(Object o) { String formatted = \"unknown\"; if (o instanceof Integer i) { formatted = String.format(\"int %d\
老代码写法
static String formatterPatternSwitch(Object o) { return switch (o) { case Integer i -> String.format(\"int %d\
新代码写法
直接在 switch 上支持 Object 类型,这就等于同时支持多种类型,简化代码
switch(暂未发布)
API 可以调用本地库和处理本地数据,与java环境之外的代码和数据交互
外部函数和内存 API(孵化中)
增强的 API 允许以一种在运行时,可靠地编译为支持的 CPU 架构上的最佳向量指令的方式表达向量计算
矢量 API(二次孵化中)
AOT Graal 编译器 移除
删除远程方法调用 (RMI) 激活机制,同时保留 RMI 的其余部分。
弃用安全管理器
jdk17新特性
jdk新特性
\t\t\t发出IO操作,如果内核没有准备好数据,用户线程是直接返回还是等待直到获取到数据才返回
java基础
ServerBootstrap
EventLoop
EventLoopGroup
channel
ChannelPipeline
ChannelContextHandler
Netty的组件
建立连接
连接关闭
收到消息
读完成
channel被注册到EventLoop
channel从EventLoop注销
可写状态改变
出现异常
收到一个用户自定义事件
netty事件类型
粘包拆包原因
固定长度的拆包器
行拆包器
分隔符拆包器
基于数据包长度的拆包器
自定义拆包方案
Netty粘包拆包
Reactor单线程模型
Reactor多线程模型
主从Reactor多线程模型
接受客户端请求线程池
处理IO的线程池
线程池职责
netty线程模型
jdk自带
netty序列化
netty零拷贝
netty对象池
类似线程池,避免大量创建对象,避免需要使用的时候才去创建对象;
由于io操作时候,有大量的buffer对象的创建和回收,netty对这些buffer对象进行池化而降低系统的开销
netty内存池
支持的通信协议
序列化协议
dubbo
rpc
ibatis
mybatis的一级缓存是基于sqlseesion的,mybatis是通过sqlsession 对数据库做操作的
同一个sqlsession生命周期内,相同的查询语句除了第一次之外,后面的查询都是从一级缓存中读取数据的;myabtis的一级缓存是默认开启的;
一级缓存仅仅是本地回话缓存,仅仅就是对一次回话中的数据做缓存,java程序使用mybatis调用mysql服务的时候,线程多次调用了同样的一模一样的slq查询的时候,会做缓存,也就是说:mybatis框架下一个线程第一次调用sql会做缓存,第二次又调用了一样的sql查询,那么这个时候就是直接去缓存中拿数据的;而不是走数据库的;一级缓存也就是本地缓存,会被修改操作(新增,删除,插入)操作,事物提交,事物关闭,关闭Sqlsession都会清除一级缓存;当这个线程结束完成之后,这个缓存也失效了;
一级缓存
1.当前文件中的所有查询sql都会做缓存
二级缓存
缓存淘汰算法
DefaultIdentifierGenerator类
根据时间戳,数据中心,机器标识部分,序列化,做位于运算
图示
实现逻辑
自带雪花算法
乐观锁插件
mybatis-plus
这个文件为mybatis的全局配置文件
配置mybatis的环境信息
也就是sql映射文件
这些文件配置了操作数据库的sql语句
2、加载xml映射文件
3、通过mybatis环境信息构造回话工厂SqlSessionFactory
该对象中包含了执行sql语句的所有方法
4、由会话工厂SqlSessionFactory对象创建sqlSeesion回话对象
5、Excutor执行器根据传入的参数动态生成需要执行的sql语句;同事负责查询缓存的维护
6、在Excutor接口执行的过程中MappedStatement类型参数改参数是对映射信息的封装
7、输入参数映射
8、输入结果映射
详细步骤
mybatis工作流程
mybatis
为了简化项目开发而且有自己生态圈的一个开源框架
控制反转
ioc
切面编程
Aop
存储bean对象且控制bean生命周期的容器
容器
spring是一个ioc和aop的容器框架
spring的核心是什么
2.@Bean直接给定义bean对象
1.@Import 导入一个对象
步骤1:public class Myclass implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"com.yc.Test.TestDemo3"}; }}
2.在@Import注解中导入ImportSelector接口的实现类(一次导入多个对象);
3.ImportBeanDefinitionRegistrar
ImportSelector接口和ImportBeanDefinitionRegistrar最终都是需要使用@Import 来做实现的导入,才能实现对组件的添加
差异和区别
3.@import相关注解给容器添加组件
4.使用FactoryBean往工厂里面手动添加bean
spring容器添加组件
spring容器启动过程
spring容器的理解
spring容器
1、是spring整个生态圈的核心基石
1、基于pojo的轻量级和最小的侵入性编程
2、通过依赖注入还有切面接口实现松耦合
3、通过切面编程减少模板代码
2、简化企业级开发
1、Spring框架之外还存在一个构建在核心框架之上的庞大生态圈
2、低侵入式设计,代码的污染极低
4、Spring的loC容器降低了业务对象替换的复杂性,提高了组件之间的解耦
5、Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用
6、Spring的ORM和DAO提供了与第三方持久层框架的的良好整合,并简化了底层的数据库访问
spring使用的优势
spring理解
什么是ApplicationContext
ApplicationContext
FactoryBean
BeanFactory
BeanDefinition
spring核心类
后置处理器功能
后置处理器是什么
后置处理器的处理过程
后置处理器
1、required
requires_new
newsted
support
not_support
never
madatory
某一个事务嵌套另一个事务的时候怎么办?
A方法调用B方法,AB方法都有事务,并且传播特性不同,那么A如果有异常,B怎么办,B如果有异常,A怎么办?
核心处理逻辑非常简单:1、判断内外方法是否是同一个事务:是:异常统一在外层方法处理.不是:内层方法有可能影响到外层去,但是外层方法是不会影响内层方法的
场景问题
spring的事物传播特性
由spring aop通过一个TransactionInterceptor实现的
总
1、spring 先解析当前事物的属性信息(根据具体的属性判断是否要开启事物)
2、当前需要开启的时候,获取数据库连接,关闭自动提交的功能,开起事物
3、执行具体的sql逻辑
4、在操作过程中,如果执行失败嘞,会通过completeTeanscationAfterThrowing执行回滚操作,然后再通过doRollBack方法来完成具体的事物的回滚操作,最终还是调用的jdbc中的rollbcak方法做回滚操作
5、在操作过程,如果正常,那么通过commitTransactionAfterReturning来完成事物的提交,具体的提交逻辑通过doCommint方法执行实现的时候先获取jdbc链接,然后通过调用jdbc的事物提交方法提交事物;
6、当事物执行完毕之后,需要通过cleanTransactionInfo方法清理事物相关的信息
事物的实现
bean对象没有被spring容器管理
方法修饰符不是public
实际中失效最多的
事物的实现是通过aop实现的,aop的实现是通过代理来实现;如果不能形成其他类的接口调用,那么就无法生效
方法是本类调用
数据元没有配置事物管理器
数据库本身不支持事物
数据库不支持事物
异常被捕获了
捕获的异常和配置的异常并不一直
spring事物失效
和当前项目使用的数据的隔离级别是一致的
spring的事物隔离级别
spring的事物管理
多例;每次需要用到的bean的时候就会去创建这个bean,也就是在一个request请求中可能会多次创建bean对象;
prototype
单例,整个容器都会只用这一个对象,作用域就是整个容器
singleton
同一次请求创建一个对象;bean对象的作用域就在一起请求当中
request
同一次会话创建一个对象;这个Bean对象的作用域就在这次会话当中
session
global session
通过配置scope属性值
bean对象作用域
懒加载
true
非懒加载,也就是提前加载
false
通过设置lazy-init
懒加载只是针对多例bean才生效;单实例的bean都是提前加载;多实例的bean默认都是懒加载的形式加载,也就是首次需要用到的时候才会加载;
懒加载概述
spring默认是提前加载,所以在启动的时候控制台就会有很多对bean对象的加载
懒加载在第一需要用到这个对象的时候加载;非懒加载就是在环境启动的时候就提前把对象加载;
懒加载可以节约内存,但是不利于提前发现问题;非懒加载,可以提前发现加载的问题;
懒加载和非懒加载区别
注意事项:
bean对象属性说明
什么是spring bean对象
注解是spring提供的;
1.@AutoWried
注解是JDK提供的
2.@Resource
1.字段和setter都生效
相同点:
1.一个是spring提供的,一个是JDK提供的
2.注解中的属性不一样;
不同点:
相同点不同点
bean注入的注解
2.然后在遍历集合中类的对象,把对象的相应的属性都存储到BeanDefintion 中,也就是每一个被遍历的要被构建bean对象的对象都会有一个专属的BeanDefintion 对象;这个BeanDefintion对象存储当前被遍历对象的一切属性,这些属性也是构建bean对象的基础;
3.每一个BeanDefintion 会被存储到 BeanDefintionMap 对象之中;
4.然后还会调用beanFactoryPostProcessor后置处理器去修改BeanDefintionMap,执行对bean的修改拓展操作(spring内置还有我们自定义的后置处理器)来参与到生命周期的过程;
6.根据BeanDefintion对象中的信息中的构造方法信息,推断出构造方法;
7.再通过构造方法再去通过反射实例化对象;
8.缓存相关注解信息,合并到BeanDefintion对象中去;
9.在三级缓存中暴露一个工厂对象;
10.判断是否需要完成属性注入;
11.完成属性注入;属性注入(还需要考虑到循环依赖问题)
12回调Aware接口,也就是spring内置的Aware接口(也是修改bean对象的属性)
13、再调用生命周期初始化回调方法(在类中间被@PostConstruct注解修饰,还有实现了InitilizingBean接口的afterPropertiesSet方法,还有xml中配置的afterPropertiesSet方法)
14.完成aop代理,事件分发,事件监听;
16.关闭spring容器,DisposableBean接口,那么这些bean对象也会跟着被销毁;
bean的装配过程
装配过程示意图
bean对象装配
1、获取ABean对象,从Abean的各级缓存中获取
2、发现没有,那么就开始创建Abean实例,且把ABean放在三级缓存中
3、ABean对象需要进行实例化,然后给字段赋值
4、发现BBean数据不存在,然后也是从各级缓存中获取
5、发现没有,那么就进行Bbean创建,将BBean放在三级缓存当中;
7、把ABean从三级缓存中删除,并放入二级缓存中;
8、然后BBean的实例化完成,将三级缓存中BBean删除,且放入一级缓存中;
9、BBean已经创建完成,这个时候接着进行ABean的创建,此时ABean依赖的BBean已经在一级缓存中;直接获取到BBean;
10、ABean也创建完成,将ABean从二级缓存中删除,放入一级缓存;
属性赋值
13.完成aop代理,事件分发,事件监听;
bean增强修改
加载过程示意图
bean加载过程(是bean装配过程的一部分)
根据BeanDefintion对象中存储的构造信息,通过反射创建对象的过程
实例化
完成bean对象的增强操作
初始化
bean实例化和初始化
属性注入(最常用的一种方式)
构造器注入
方法上注入
参数上注入
bean注入方式
什么是Spring的嵌入beans
BeanA依赖BeanB;而BeanB又依赖BeanA
什么是循环依赖
单例池
使用ConcurrentHashMap
存放构建完成后的完整的bean对象
存放属性字段不完全的bean
也只有处于循环引用中的bean才会被存放在这个二级缓存
特殊说明
存放用于ObjectFactory对象,这个对象是用于来构建bean对象;所以刚刚被创建出来的bean对象都是放在三级缓存
存储刚刚被实例化的bean对象,这个时候已经有了对应的引用
三级缓存
三级缓存各自作用
1、反射创建bean实例的时候,将bean对象对应ObjectFactory放在三级缓存
3、bean初始化完成,生命周期的相关方法都执行了,就会把bean对象放入一级缓存,然后删除二级缓存;
有循环依赖
无循环依赖
也就是有循环依赖:三级缓存到二级缓存,再到一级缓存
没有循环依赖就是三级缓存,到一级缓存
三级缓存流转顺序
图片展示
循环依赖加载bean属性获取过程
spring是怎么解决循环依赖
循环依赖
spring没有对bean对象做多线程处理;bean对象是否安全,主要看定义者怎么定义bean;如果这个bean对象中定义了线程不安全的全局变量,那么这个bean对象就是不安全的;如果定义的对象就是安全,那么这个bean就是安全的;
单例bean是否是线程安全
定义bean为无状态的bean(对象的时候不要定义全局变量),这样才能再多线程环境下被访问,使用ThreadLoacl来对bean进行线程封闭处理;如果不是无状态的,最好就是把bean定义成多例,每一个线程,或者每一个请求,每一个会话都是一个单独的bean对象
bean的线程并发问题
@Resource是jdk提供的注解
@Autowired
来源不一样
@Resource默认通过byname注入,也就数已通过名称注入,通过的接口名称来做注入;也可以指定byType注入
@Autowired默认是通过byType(也就是类型注入),也就是默认注入接口的子类;如果接口有多个子类,搭配使用@Qualifier注解指定某一个特定子类
注入方式
如果接口都只有一个子类,那么没有使用上的区别,如果接口有多个实现类,那么只能用@Autowried 搭配@Qualifier 来使用
使用差别
都可以完成bean对象的注入标识
@Resource和@Autowired
bean
容器:存储对象,使用map结构来存储
总体回答
IOC的理解
IOC容器理解
IOC原理
工作原理,过程,数据结构,
IOC的实现
依赖注入
控制反转:理论思想,原来的对象是由使用者来进行控制,有了spring之后,可以把整个对象交给spring来帮我们进行管理容器的生命周期
整个bean的生命年期,从创建到使用到销毁的过程全部都是由容器来管理(bean的生命周期)
1、先准备一个基本的容器对象,包含一些map结构的集合,用来方便后续过程中存储具体的对象
2、进行配置文件的读取工作或者注解的解析工作,将需要创建的bean对象都封装成BeanDefinition对象存储在容器中
3、容器将封装好的BeanDefinition对象通过反射的方式进行实例化,完成实例化工作
4、进行对象的初始化操作,也就是给类中的对应属性值就行设置,也就是进行依赖注入,完成整个对象创建一个完整的bean对象,存储在容器的某个map结构中
5、通过容器对象来获取对象,进行对象的获取和逻辑处理工作
6、提供销毁操作,当对象不用或者容器关闭的时候,将无用的对象进行销毁
如何实现一个ioc容器
IOC
AOP全称叫做Aspect Oriented Programming面向切面编程.它是为解耦而生;
任何一个系统都是由不同的组件组成的,每个组件负责一块特定的功能,当然会存在很多组件是跟业务无关的,例如日志、事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此我们需要将这些公共的代码逻辑抽象出来变成一个切面,然后注入到目标对象(具体业务)中去,AOP正是基于这样的一个思路实现的,通过动态代理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改原有业务的逻辑代码,只需要在原来的业务逻辑基础之上做一些增强功能即可。
切点的集合
将多个类的通用行为封装在一起的可重用的模块
切面( Aspect)
切点增强之前所执行的拦截方法
被AOP拦截到需要增强的方法
连接点Uoin point)
被增强方法上执行的增强处理
通知(Advice)
真正要被增强的方法
切点( Pointcut)
在不修改代码的前提下,在运行期动态的给被代理的类增加功能
引入( Introduction
被一个或者多个切面所通知的对象.也被称作被通知(advised) 对象。既然 SpringAOP是通过运行时代理实现的,那么这个对象永远是一个被代理( proxied) 的对象。
目标对象(Target object
AOP框架创建的对象,用来实现切面契约(aspect contract) (包括通知方法执行
Aop代理
完成所有的切点切入,对应的目标对象完成代理对象的创建
织人(Weaving):
AOP相关概念
AOP的理解
日志记录
性能统计
安全控制
AOP的使用场景
1、通过配置的切面信息,得到具体的具体的连接点,通过对连接点中方法的拦截,得到需要具体被增强的方法
2、然后在不改变代码的情况下,通过需要被增强方法的类通过jdk代理,或者cglib代理得到代理对象;
这个过程其实就是通过原有对象的字节码文件,在原有的基础之上增加了新的功能对应的字节码指令
3、在生成代理对象过程中,需要将增强的信息添加到代理对象中去
java程序在执行的过程中都是通过字节码文件中的字节码指令来执行程序的。
4、在实际对对象的执行过程中,都是通过代理对象来做功能的执行,以达到对应的前置,后置,环绕,返回后,抛出异常后得有一些增强操作;
AOP执行流程
cglib代理,或者jdk代理
本质都是通过原有对象的字节码文件,在此基础之上给字节码文件中添加被增强的字节码指令
小贴士:这个在jvm - jvm编译一节有详细说明,动态代理要理解到这一层,才算是有一个大概的理解;
AOP的实现
AspectJ 是第三方实现AOP功能的一个框架
和AspectJ AOP区别
1、before
2、after
3、AfterReturning
4、AfterThrowing
5、Around
切面的通知类型
AOP
封装对象
bean组件(bean对象组件)
发现,构建,维护每一个bean关系所有需要的一些列的关系
core组件(核心组件)
发现,构建,并且维护每一个bean对象之间依赖关系;可以看成是bean对象的集合,
context组件(上下文组件)
核心组件
spring框架组件
各种BeanFactory,Application创建中都用了
1、工厂模式
各种BeanFactory,Application创建中都用到,中间有很多空实现,方便子类去增加方法
2、模板模式
aop的实现用的动态代理
3、代理模式
4、策略模式
bean对象默认都是单利
5、单利模式
6、观察者模式
7、适配器模式
8、装饰者模式
spring框架设计模式
importSelector
import
ImportBeanDefinitionRegistrar
引入bean对象
@Repository
@Service
@Controller
@Bean
bean对象
@conditional
属性值默认是true,一定要被注入;也可以设置成reqiured=false,告诉当前类可以不用注入这个对象;
@AutoWried
重要注解
spring
1.可以以jar包的形式独立启动
2.内嵌了tomcat/jetty/Undertow服务器。
3.在spring的基础上简化了maven的配置,引入spring-boot-starter-web 依赖;
4.springBoot可以根据类路径中的jar包,类自动配置成bean;
5.有运行时的监控系统
6.快速构建项目,提高开发效率,部署效率;
7.天然与云计算集成;
8.天然的已经整合了很多第三方的框架
springBoot优势
表示是springboot的配置类
@SpringBootConfiguration
开启自动配置注解
去mate-info/spring.factiories文件中加载需要被自动注入的java类
@EnableAutoConfiguration
表示要扫描的当前包以及子包下的所有被标记成spring bean对象的java类
@ComponentScan
包含以下注解
@SpringBootApplication最核心注解
手动给spring添加bean对象,导入一些第三方的配置类
@importResource,@Import
提升@CompontentScan性能的注解
@Indexed
开启异步方法
@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
springBoot自动装配原理
1、通过main方法启动
2、达成jar包通过 java -jar的命令启动,或者达成war包掉到web容器中启动
3、使用maven/gradle 插件来运行
springBoot启动方式
springBoor是基于spring创建的一个上层应用框架,14年发布1.0会比以前那种ssm,给予配置的方式效率高很多
springBoot简单介绍
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、在配置文件中配置相关的需要的配置
如果想引入第三方的配置需要的步骤
提供了SpringMVC+内嵌了Tbmcat容器
spring boot-starter-web
提供了 SpringJPA和Hibernate框架整合
spring-boot-starterdata-jpa
redis数据源整合
sprinq-boot-starter-data-redis
solr搜索
spring-boot-starter-solr
mybatis-springBoot整合
mybatis-spring-boot-starter
常用的starter包
实际开发
springBoot中starter(启动器)的理解
一般不需要
springBoot需要web容器
1、类似通过jsonp类解决跨域问题,但是支持Get方式请求
2、SpringBoot中我们可以通过WebMvcConfigurer重写addCorsMapping方法,在这个方法中添加允许进行跨域的相关请求
还是为了浏览器访问数据安全
1、浏览器的同源策略限制:两个页面的协议,ip地址,端口号必须要是一样的才能请求
跨域就是:协议不一致,ip地址,端口中只要有一个不一致就是跨域
什么是跨域问题
springBoot项目中如何解决跨域问题
springBoot默认使用的是logback
1、先排除logback的包
2、pom文件中添加log4j依赖
4、配置log4j相关配置
soringBoot项目如何使用log4j
1、pom文件中引入springBoot与第三方组件的得整合启动包:spring-boot-starter-redis
2、再在配置文件中引入该组件的必须要配置的配置项
通用方法
springBoot怎么集成第三方组件
application.properties
application.xml
application.yml
application.yaml
springBoot默认支持的属性文件
springBoot默认是不支持这类配置文件
在springCloud环境下才支持,作用是在springBoot项目启动之前启动一个父容器;
父容器可以在springBoot容器启动之前完成一些加载初始化的操作:比如说加载配置中心的信息
springBoot中bootstrap.yml文件作用
1、微服务环境下帮助我们快速定位问题
2、可以对整个系统做出性能监控
3、统一日志管理
信息的监控可以在springBoot自带的springBoot admin监控平台上看到;
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency>
1、引入依赖
# 访问端口management.server.port=8081# 根路径management.endpoints.web.base-path=/actuator/z# web端允许的路径management.endpoints.web.exposure.include=*
2、配置路径
/actuator/z里还有端口都是配置文件中配置的
http://127.0.0.1:8081/actuator/z
可以得到当前所有的统计信息项目
{ \"_links\":{ \"self\":{ \"href\":\"http://127.0.0.1:8081/actuator/z\
返回请求路径信息
http://127.0.0.1:8081/actuator/z/prometheus
当前用哪些bean对象
http://127.0.0.1:8081/actuator/z/beans
健康信息
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
接口请求路径有哪些
a href=\"http://127.0.0.1:8081/actuator/z/mappings\" target=\"_blank\" class=\"json_link\" style=\
http://127.0.0.1:8081/actuator/z/refresh
http://127.0.0.1:8081/actuator/z/features
3、请求获取当前可用的统计接口信息
使用步骤
springBoot中Actuator的理解
开启AspectJ自动代理支持
@EnableAspectJAutoProxy
开启web mvc配置
@EnableWebMvc
开启事物
@enabletransactionmanagement
开启方法重试功能
@EnableRetry
开启方法熔断功能
EnableCircuitBreaker
spring开启特殊功能
spring-boot-starter 核心启动器,包括自动配置支持,日志记录和YAMLspring-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-webspring-boot-starter-jooq 使用jOOQ访问SQL数据库的初学者。替代spring-boot-starter-data-jpa或spring-boot-starter-jdbcspring-boot-starter-jta-atomikos 使用Atomikos进行JTA交易的入门者spring-boot-starter-jta-bitronix 使用Bitronix进行JTA事务的入门者spring-boot-starter-jta-narayana Spring Boot Narayana JTA Starterspring-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 LinkedInspring-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应用程序的初学者
springBoot官方提供的starter
springBoot
1、spring是一个一站式轻量级的java开发框架,核心是控制反转和切面编程,针对web层,业务层,持久层等都提供了多种配置解决方案
2. springMvc是spring基础之上的一个MVC框架,主要处理web开发的路径映射和视图演染,属于spring框架中
spring和springMvc
1、springMvc属于一个企业WEB开发的MVC框架,涵盖面包括前端视图开发、文件配置、后台接口逻辑开发等,XML、config等配置相对比较繁琐复杂;
springMvc和springBoot
1、Spring框架就像一^家族,有众多衍生产品例如boot、security、jpa等等。但他们的基础都是Spring的ioc. aop等.ioc提供了依赖注入的容器,aop解决了面向横切面编程,然后在此两者的基础上实现了其他延伸产品的高级功能;
2、springMvc主要解决WEB开发的问题,是基于Servlet的fMVC框架,通过XML配置,统T发前端视图和后端逻辑;
针对于动态数据,对于服务器的加入,或者删除是可以动态感知的;
注册中心概述
服务提供方想注册中心发送的心跳时间和续约时间
服务注册
动态追踪服务注册信息
如果15分钟之内超过85%的客户端节点都没有正常的心跳信息,那么Eureka就会认为客户端和注册中心发生了网络故障,那么EURKEA就会自动进入自我保护机制
Eureka 的自我保护机制工作
不再从注册列表中剔除长时间没有发送心跳的客户端
继续接受新的服务注册和服务查询,但是这些数据都不会被同步到其他的注册中心;
网络稳定之后,Eureka注册信息才会被同步到其他机器上;
Eureka 的自我保护机制下Eureka工作情况
防止因为分区网络,导致心跳信息没有发送或者没有接受到信条信息,而错误的把一个正常服务的机器从服务注册列表中剔除;
Eureka 的自我保护模式是有意义
自我保护机制
通过客户端给服务端间隔一定时间发送心跳还有发送续约信息;
健康检查
注册中心核心功能
客户端,服务端ip地址解耦合
实现服务的动态扩容或者动态删除服务
可监控各个服务运行情况
注册中心好处
Zookeeper
1、启动eureka服务集群
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不会剔除已经挂掉的服务,会认为这个服务在尝试重新连接
1.客户端拉服务列表,注册中心会从缓存中拿,并不每次都是从服务注册列表中获取;
实时更新
registry
readWriteCacheMap
周期更新
readOnlyCacheMap
eureka服务端信息三级缓存
localRegionApps
upServerListZoneMap
客户端信息缓存
服务端,客户端缓存
客户端是从只读缓存还是从读写缓存获取配置信息
读写缓存更新只读缓存
默认60秒;间隔60秒会去清理不可用的节点信息
清理未续约节点周期
默认90秒;
清理未续约节点超时时间
服务端配置
服务续约周期
增量更新周期
负载均衡周期
客户端配置
缓存相关配置
2.客户端会缓存服务注册列表;
3.负载均衡器也会缓存服务列表
eureka缓存
这点和zk的差别很大,zk在同一时间点只能是由一个主节点对外提供服务发现注册功能
集群中的每一个节点都是可以堆外提供注册和服务发现功能
集群中的每一个节点也是一个微服务,也可以做相互的注册
eureka注册中心本身支持集群化
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
超过这个时间没有向注册中发送心跳,那么表示我这个服务已经挂了,请把我剔除服务注册列表
续约时间
客户端
自我保护策略
eureka高可用
eureka架构
1:服务注册
2:服务发现
3:动态跟踪服务注册信息
zk只有主节点才能提供服务,如果主节点挂了,就不能服务了,所以可用性较差,但是一致性和容错可以有保障
zk保证CP(一致性,容错性)
eurake集群每一个节点都可以对外提供服务,多台机器存在数据一致性问题。只能保证可用性和容错性
eruake是ap (可用性,容错性)
eurake 每一个节点都是平等都可以对外提供服务注册和发现,而zk只有集群中的主节点才能堆外提供服务注册和发现
zk和eurake区别
注册中心实现
系统启动需要的参数和依赖的各类外部信息;
1、配置中心server端承担起配置刷新的职责
2、提交配置出发post请求给server端的bus/refreah接口
3、server端接受到请求并发送给spring cloud bus总线
4、springcloud bus 接收到消息并通知其他连接到总线的客户端
5、其他客户端接收到通知,请求server端获取最新的配置
6、全部客户端均获取到最新的配置
配置中心自动刷新时限
1、各个环境做到数据配置隔离
2、配置中心所有的配置信息都进行加密处理
配置中心如何保证数据安全
springCloud config
是携程架构部门研发的分布式配置中心
apollo由来
1、统一管理不同环境,不同集群的配置
热发布,修改配置,客户端可以在1s接收到最新的配置
2、配置修改实时生效
配置修改都有版本的概念,可以方便回滚到历史某个版本
3、配置版本管理
支持发布后,配置只对部分客户端实例生效,等观察一段时间再对所有实例生效
4、支持灰度发布
配置编辑,发布都有详细的管理机制以及操作日志,方便追踪问题
5、发布操作审计
可以在见面上查看配置被哪些实例使用
6、客户端配置信息监控
7、提供了java和.net原生客户端
8、提供开发平台api
标识配置所属项目应用
应用层级维度
一级维度
DEV
FAT
uat
pro
区分一个应用下不同的环境
环境维度
二级维度
比如说,北京分组,上海分组,配置可能不一样
相同实力的不同分组
集群维度
三级维度
类似同一个项目下针对比如说数据库,redis等等不同的配置文件
namespace维度
四级维度
配置管理维度
Apollo客户端会把从服务端获取到的配置在本地文件系统缓存一份,用于在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置,不影响应用正常运行
本地缓存
1、在管理平台上做配置的修改和发布
2、配置中心通知Apollp客户端有配置更新
3、apollpo客户端从配置中心拉去最新配置,更新本地配置并通知都应用
大致工作流程
配置更新基础示意图
1、客户端和服务端保持了一个长连接,能第一时间获得配置更新的推送
这是一个备用机制
客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回 304 - Not Modified
定时频率默认为每 5 分钟拉取一次,客户端也可以通过在运行时指定 apollo.refreshInterval来覆盖,单位为分钟
2、客户端还会定时从 Apollo 配置中心服务端拉取应用的最新配置
3、客户端从 Apollo 配置中心服务端获取到应用的最新配置后,会保存在内存中
4、客户端会把从服务端获取到的配置在本地文件系统缓存一份,在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
5、应用程序从 Apollo 客户端获取最新的配置、订阅配置更新通知
详细工作流程
详细工作示意流程图
配置更新详细示意图
1、Config Service 提供配置的读取、推送等功能,服务对象是各个微服务
2、Admin Service 提供配置的修改、发布等功能,服务对象是管理界面
1、Config Service 和 Admin Service 都是多实例、无状态部署,所以需要将自己注册到 Eureka 中并保持心跳
3、集群部署
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、错误重试
4、服务注册与发现
工作流程说明
apollo整体设计
apollo设计图
1、客户端和服务端的长连接保持长连接,能在第一时间获取配置更新的推送
2、客户端还会定时拉取服务端配置是否有更新
1、阿波罗如何保证配置实时更新
最大程度上保证微服务和服务端配置获取通信畅通
1、configservice 集群部署,且都会注册到注册中心
配置管理平台和服务端保持配置修改通信畅通
2、admin service 集群部署
当配置中心集群挂了,会从本地读取配置
3、客户端会缓存配置信息到本机
4、admin sevcie 和conig service 集群工作相互不受对方影响
2、阿波罗如何保证可用性
apollo相关问题
apollo
配置中心
当大量的请求请求上层服务,上层服务所依赖的底层服务,在处理请求的时候,无法承受大量的请求,导致底层服务无法快速相应,导致上层服务请求堆积,导致上层服务无法快速处理请求,导致请求堆积,从而导致A服务不可用
demo解释
由于底层的服务某种原因,导致整个上层服务都不可用,有一种连锁反应
服务熔断,服务降级,服务限流
服务雪崩
单位时间内限制对服务器的访问量
在高并发情况下,保护系统不被大量请求导致服务雪崩
服务限流
底层服务不可用的时候,上层服务直接不再调用这个底层服务,而直接返回一个结果;
隔离上层和底层服务之间的级联影响,避免系统崩溃
服务熔断
服务压力激增的情况下,主动对一些服务不处理或者简单处理,来释放服务器压力,保证服务器核心功能正常运行
保证整个系统核心功能呢个稳定性和可用性
服务降级
服务熔断是调用链路上的某个服务不可以用引起的;
服务降级从整体负载考虑
触发原因
熔断是框架层次的处理
降级是业务层次的处理
目标
熔断主要是在客户端进行处理,书写兜底处理
降级需要在服务端进行兜底处理
服务降级是对整个系统资源的再次分配(区分核心服务,非核心服务)
服务熔断是服务降级的一种特殊方式,防止服务雪崩而采取的措施;
服务熔断,服务降级区别
相关概念
流量控制
熔断降级
系统负载保护
sentinel作用
服务接口
接口中调用的其他服务
资源定义
某个资源出现不稳定的时候,降低对该资源的调用进行限制并快速失败,避免影响到其他系统造成整个系统雪崩
流量控制规则
熔断降级规则
系统自我保护规则
规则
Sentinel概念
1.对资源显示定义,来标记资源
2.提供可修改规则接口
3.对资源适时统计,流量适时监控
Sentinel工作机制
滑动窗口统计中,1秒内的平均响应时间超过了阈值,就会熔断
平均响应时间
滑动窗口的统计中当资源每秒的请求次数超过5个,且每秒的异常比例超过一定阈值;那么这个方法就会自动返回
异常比例
滑动窗口在1分钟的异常数目超过一定的阈值就会进行熔断。
异常数
熔断一般是因为某个服务故障了,降级一般是为了降低系统负载
熔断一般是框架就处理,粒度比较大;降级粒度比较小,可以正对某个具体的资源
熔断策略
线程数量在某个资源上堆积了数量,那么新的请求就会被拒绝;等堆积的线程完成任务后,才开始接受新的请求;
线程并发数
当依赖的资源出现相应时间过长,那么对该资源的访问都会直接被拒绝,等到一定的时间窗口之后再回复正常访问;
资源相应时间
降级策略
资源名,资源名是限流规则的作用对象
resource
限流阈值
count
降级模式,根据 RT 降级还是根据异常比例降级
grade
降级的时间
timeWindow
降级规则
熔断就是直接不能访问该接口,该接口直接返回错误的执行方法;降级,一段时间之内不能再次访问这个接口,等高峰期过了才会再次访问该接口;
熔断-降级区别
直接限流
冷启动
排队
控制效果
系统负载
运行指标
一个资源可能调用其他资源,形成一个调用链路,通过调用链路可以衍生出更多的流量控制手段
根据调用方限流
根据调用链路入口限流:链路限流
关系资源流量控制
资源调用关系
流量控制角度
资源当前请求的线程个数,如果超过一定的阈值那么,对该资源的请求就会被立即拒绝;
保护业务线程不被耗尽,当某个资源超过一定阈值数量的线程排队的时候,对该资源的请求就会直接被拒绝,等到没有排队线程就会接受新的请求
(避免不断的创建线程,造成线程的堆积;hystrix是通过对特定资源创建一定大小的线程池,不管请求再多,都只能在这个规定大小的线程池内活动)
1、线程数量控制
当QPS超过任意规则的阈值,新的请求就会被立即拒绝,拒绝方式抛出FlowException
2、直接拒绝
通过漏斗算法严格控制请求通过的间隔时间
3、均速器
系统长期处于低活跃度,短期流量猛增,直接把系统拉大高水位可能直接把系统冲垮;让流量缓慢的增加,给系统一个预热时间,避免系统被冲垮;
4、冷启动
控制手段
限流规则作用对象
限流阈值类型(QPS或者并发线程数)
是否区分调用来源
limitApp
调用关系限流策略
strategy
流控控制效果(直接决绝,冷启动,均速排队)
controlBehavior
限流控制规则配置项
限流规则统计项目
1.通过网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去
2.让系统的入口流量和系统的负载达到一个平衡,保证在系统能力范围之内处理最多请求;
系统保护规则
系统保护
commandKey
代表了某一个底层的依赖服务;这个服务可以有多个接口
逻辑上是组织起一堆的command key的调用,统计信息,成功次数,timeout次数,失败次数,可以看到某一些服务整体的访问情况
如果不配置
groupKey
线程池配置
threadPoolKey
同时配置了groupKey 和 threadPoolKey,具有相同的threadPoolKey的使用同一个线程池
如果只配置了groupKey ,那么具有相同的groupKey 的使用同一个线程池
以上三者实战说明
一般来说command group 是对应了一个底层服务,多个command key对应这个底层服务的多个接口,这多个接口按照 command group 共享一个线程池
如果想单独给某一个底层接口也就是某一个command key设置线程池,直接配置上threadpool key 就可以
commandKey,groupKey,thread key重点说明
报错之后,执行的默认方法
回滚的方法名称
fallbackMethod
可以配置执行的隔离策略是线程隔离,还是信号量隔离
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)的线程池的一小部分。
commandProperties
线程池相关的配置
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
threadPoolProperties
忽略一些异常,这些异常不被统计到熔断中
ignoreExceptions
定义hystrix observable command的模式;
observableExecutionMode
任何不可忽略的异常都包含在HystrixRuntimeException中;
raiseHystrixExceptions
默认的回调函数,该函数的函数体不能有入参, 返回值类型与@HystrixCommand修饰的函数体的返回值一致。如果指定了fallbackMethod,则fallbackMethod优先级更高。
defaultFallback
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指标的计算和跟踪,默认truehystrix.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
其他参数
@HystrixCommand
Hystrix使用
熔断请求判断机制算法:使用无锁循环队列计数,每个熔断器默认维护10个bucket,每1秒一个bucket,每个blucket记录请求的成功、失败、超时、拒绝的状态,默认错误超过50%且10秒内超过20个请求进行中断拦截。
熔断恢复:对于被熔断的请求,每隔5s允许部分请求通过,若请求都是健康的(RT<250ms)则对请求健康恢复
熔断报警:对于熔断的请求打日志,异常请求超过某些设定则报警
hystrix的熔断设计
对每一个设置了线程池的资源(理解成一个接口或者一类接口,或者针对某一个底层服务的所有接口),都会开启一个专属的线程池来处理这些资源对应的请求,线程池来处理这些资源对应的请求,堆积的请求也会进入线程池对应的阻塞队列
项目在启动的时候会有一些资源消耗,但是会在大批量的请求到来的时候,可以游刃有余的处理
线程池隔离
信号量来记录当前有多少线程在运行,请求进来的时候先判断是否会超过最大的信号量(最大线程数量),如果超过那么就会丢弃最新的请求,不超过就会让信号量+1,严格控制当前正在执行的线程数量
无法应对大量的请求,大量的请求过来,就是直接放弃执行了,
信号量隔离
线程池因为有阻塞队列的原因,所以在一定程度上可以保存一分部请求,慢慢处理;但是信号量就不行,需要严格控制当前执行的线程个数
对比
hystrix的隔离设计
设置任务入列最大时间
判断阻塞队列的头部任务入列时间是否大于超时时间。大于则放弃任务
等待超时
直接可以使用线程池的get方法
运行超时
hystrix的超时机制设置
hystrix设计
1、当出现调用错误的时候,开起一个时间窗口(默认十秒)
如果没有达到,则重置统计信息,回到第一步
如果没有达到,即使请求全部失败,也回到第一步
2、在这个时间串口内,统计调用次数是否达到最小次数
如果没有达到,则重置统计数据,回到第一步
3、如果次数达到了,统计失败占总请求的百分比阈值是否达到
4、如果达到则跳闸
如果失败,回到第三步继续计算错误百分比
5、开起一个默认时间为5秒的活动窗口,每间隔5秒钟,让一个请求通过去访问已经跳闸的服务;
6、如果调用成功,重置断路器
熔断流程
简易工作流程图
hystrix工作流程
Sentinel 基于信号量隔离,Hystrix 基于线程池/信号量隔离
隔离策略
两者都可以基于异常比例来降级,但是sentinel可以基于相应时间,异常比例,异常数量熔断降级
熔断降级策略
都是基于滑动窗口
实时指标实现
都可以支持多种数据量
规则配置
sentinel扩展性强Hystrix区别
sentinel基于QPS支持基于调用关系的限流;Hystrix对限流支持比较有限
Sentinel支持慢启动、匀速器模式
不支持
流量整形
Sentinel 支持
系统负载保护
sentinel,Hystrix都支持注解开发
注解
都是通过滑动窗口来做指标统计
实时统计实现
sentinel Hystrix都支持多数据源
动态规则配置
相同点
都是开源,hystrix 的网飞团队已经不维护了;sentinel还在接着维护
开源和维护
Hystrix只能监控查看,不能动态修改规则
sentinel控制台可以查询规则,动态修改配置规则且无需重启,监控情况
是否可以动态修改配置
sentinel和Hystrix对比
服务降级熔断
网关概念
网关工作流程
zuul
熔断
Spring Cloud Gateway 底层使用了高性能的通信框架Netty
网关
负载均衡概念
无需部署,直接嵌入客户端做为负载均衡器使用
Ribbon
spring cloud LoadBalance
1.调用端引入ribbon的包
1.负载均衡默认算法是轮询
2.通过把负载均衡对象定义成bean修改负载均衡算法
按照一定的顺序依次调用服务实例
1、轮询策略
根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低
刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大
2、权重策略
从服务提供者的列表中随机选择一个服务实例
3、随机策略
遍历服务提供者列表,选取连接数最小的⼀个服务实例;如果有相同的最小连接数,那么会调用轮询策略进行选取
4、最小连接数策略
可以看做是轮询策略的一种
按照轮询策略来获取服务
5、重试策略
可用敏感性策略
先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例
6、可用性敏感策略
根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似
7、区域敏感策略
负载均衡算法
1、ribbon拦截所有的远程调用
2、解析调用路径中的host,获取服务名称
1、先从本地获取服务实例列表
2、本地不存在,那么就从注册中心拉去服务列表,缓存本地
3、根据服务名称获取服务实例列表
4、根据负载均衡策略获取某一个具体的服务ip和端口
3、再通过http请求框架请求服务获取结果
工作流程简述
ribbon
客户端实现
lvs
F5 硬件实现
服务端实现
客户端服务端负载均衡示意图
1、需要服务端有强的流量控制权
客户端这边就可以实现这个,避免这个问题;
2、无法满足不同调用方使用不同的负载均衡策略需求
服务端实现负载均衡问题
负载均衡实现方式
feign是ribbon调用方式的封装
联系
两者的区别联系
轮询
随机
过滤故障,连接数超过一定阈值机器,剩下进行轮询
过滤故障机器,轮询
过滤掉故障机器,选择并发最小访问
根据平均响应时间加权选择服务
根据机器性能,可以用性选择服务
算法种类
请求重试机制
1.调用端引入feign包
2.调用端定义服务接口
3.用定义的服务接口调用
配置文件中定义修改后的负载均衡算法
feign
rest template
open feign
http请求调用的轻量级框架
1、简化springcloud 对远程接口开发以及调用
2、整合spring ribbon,hystrix实现接口负载均衡和短路
JDK自带
未实现连接池;需要自己手动实现连接池
HttpURLConnection
自带Http连接池
HttpClient
OKHttp拥有共享Socket,减少对服务器请求次数
自带http连接池
OKHttp
目前支持
默认使用
Feign的HTTP客户端
OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解
OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类做负载均衡和服务调用
feign和openfeign区别
feign理论
1、本地接口A调用远程接口B
2、通过接口的动态代理实现了接口B
1、解析@FeignClient注解获取服务名称,获取请求路径
2、解析请求路径还有参数信息
3、序列化操作,非必要 ?
3、接口B读取解析springMvc相关注解,生成http请求
1、获取去注册中心获取这个服务对应的服务列表
2、根据负载均衡算法从注册列表中选择一个
4、接口B将请求相关信息交给Ribbon对应的负载均衡器
5、负载均衡器生成最终的请求地址
6、交给http组件发送请求
feign接口调用流程
1、启动类启动的时候扫描启动类上的注解@EnableFeignClients
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented// 引入FeignClientsRegistrar 来扫描@FeignClient注解下的类@Import(FeignClientsRegistrar.class)public @interface EnableFeignClients { ...}
2、这个注解中@EnableFeignClients中还有一个@Import注解,这个注解引入了一个FeignClientsRegistrar类
3、这个类实现了ImportBeanDefinitionRegistrar接口,也就是可以想spring容器中手动注入bean对象
4、FeignClientsRegistrar类中有一个方法registerBeanDefinitions ,做bean相关的操作
feign的一些默认配置将通过这里注册的信息中取获取
5、registerBeanDefinitions方法,读取@EnableFeignClients,@FeignClient注解的类;将这些信息注册到一个 BeanDefinitionRegistry 里面去
1、先扫描相关包路径
2、通过@FeiginClient注解信息向BeanDefinitionRegistry里面注册bean
registerFeignClients流程图
5、registerBeanDefinitions方法,扫描所有@FeignClient注解的类
1、实际构建的FeignClientFactoryBean
6、registerBeanDefinitions方法构建bean
5、registerBeanDefinitions方法做两件事情:
feign接口启动注册流程
feign工作流程
调用方式
springBoot admin
监控中心
sleuth
skywalking
微服务组件
一组小的服务
独立的进程
轻量级通信
基于业务能力
独立部署
无集中式管理
微服务架构特点
单体架构是每做一次修改,就必须要将单体服务进行全量的重新部署;
微服务是,修改某一块业务只需要部署这某一块业务对应的微服务就可以,发布快捷轻便;
1、服务部署灵活性提高
单体引用对同一类型的技术栈,往往只选择一种技术栈,如果要做多个技术栈选择往往会相互影响;
而微服务可以是针对每一个拆分出来的服务做针对性的技术栈选型,而可以相互不影响;
2、技术更加灵活
大型单体应用zhong
3、应用性能得到提高
单体服务,就是所有开发都在开发一个单体服务,团队协作,分支合并要求很高;还需要团队的每个成员对整个系统都需要有一定的了解;
每次做修改,都需要沟通团队中的相关其他开发;通知影响范围;
团队协作性要求高
需要对整个系统都有一定了解
单体服务包含了各个业务代码,开发,改动起来影响范围更大
开发上手难度大
单体服务
一个开发组,只需要开发属于他那一块的微服务,不需要很高的团队协作性;
降低对团队协作性要求
快速开发只需要熟悉他那块的微服务就可以,不需要对整个系统都很了解;
降低开发上手难度
微服务
4、简化开发
涉及到复合业务的时候,往往都是在其他的业务中书写相关的业务代码;业务边界,责任不够清晰;
对同一类型业务会,都已封装成对应的接口,由单独的团队或者开发来负责;业务边界清晰,责任分明
5、业务边界清晰
微服务还可以提高代码复用性;
5、代码复用性
利
网络问题
容错问题
负载问题
服务调用复杂性
事物一致性问题
单体服务只需要维护一个服务;
微服务需要维护很多个服务
运维复杂性提高
微服务调用链路变长了
测试复杂性提高
弊端
微服务利弊
引入微服务的时间点
什么样的组织结构更适合微服务架构
IAAS
paas层
核心业务层
渠道接入
业务前台
业务中台
业务后台
中台战略
依赖外接的负载均衡器(F5,NGINX)
传统lb模式
在每主机上部署一个负载均衡器
主机内lb模式
负载均衡移动到应用服务内
性能好,
进程内lb模式
服务发现模式
前置路由过滤器
路由过滤器
后置路由过滤器
可动态插拔
网关上设计防爬虫
zuul网关
服务分层
服务网关
服务之间是怎么相互发现
外面流量怎么通过网关访问微服务
相互之间同步路由表
基础服务
聚合服务
服务
RPC
REST
服务调用方式
配置集成
统一异常处理
自动生产服务端客户端
代码生成
REST/RPC
安全访问控制
限流熔断
调用链埋点
Metrics
负载路由
服务注册发现
服务治理
日志监控
调用链监控
告警系统
metrics监控
ELK -- log监控
微服务监控系统分层监控架构
CAT
ZIPKIN
字节码增强的方式做
PINPOINT
子主题 4
调用链监控选型
路由发现体现
微服务实用性
环境一致性问题
镜像部署问题
容器集群调度平台
容器发布
发布体系
按照系统业务功能进行划分,例如对于电商系统,按功能维度咱们能够拆分为商品中心,订单中心,用户中心,购物车,结算等功能模块
1、功能维度
功能模块如果能够按照不一样的业务状态再进行划分,就好比电商中的优惠券,可以分成建立优惠券,领券,使用优惠券,优惠券失效
相同的业务从开始到结束的不同状态的区分
2、状态维度
按照读写压力拆分
商品中心读压力大,那么就将商品读写分成两个服务,以提高系统安全性,可用性
3、读写维度
按照业务纵深维度,最底层的业务模块和上层的业务模块拆分开
比如说:订单需要用到商品,用户,等信息;但是用户模块,商品模块是不会用到订单信息;此时商品模块和用户模块就是相当于订单模块就是下层模块
4、纵深维度
根据访问特征,按照aop拆分
商品详情页能够分为CDN、页面渲染模块等。而CDN就是一个AOP
5、AOP维度
维度(拆分方式)
1、当前微服务只能访问当前微服务对应的数据库;想访问其他服务的数据源,必须通过远程接口访问
2、当前服务不能包含其他服务业务接口,且当前服务接口需要对外暴露提供接口服务;
3、服务之间不能有业务交叉
原则(方法)
微服务业务拆分维度和原则
也就是服务拆分的一个总的指向原则
三个维度示意图
水平复制
将服务多部署几台,做负载均衡
X轴维度
基于不同的业务模块拆分
Y轴维度
数据分区维度,将数据拆分成多个份,或者多个集群
Z轴维度
AKF拆分原则
2、前后端分离
不能在服务本地内存中缓存数据
3、无状态服务
降低服务之间通讯协议沟通成本
4、无状态通讯原则
微服务应用4个架构原则
微服务是有一个martin fowler大师提出来的,是一种架构风格,通过将大型单体应用划分成较小服务单元,从而降低整个系统复杂度
微服务产生
Spring Cloud Config
提供了一个服务注册中心、服务发现的客户端,还有一个方便的查看所有注册的服务的界面。 所有的服务使用Eureka的服务发现客户端来将自己注册到Eureka的服务器上
Eureka
服务客户端,服务之间如果需要相互访问,可以使用RestTemplate,也可以使用Feign客户端访问
Feign
远程调用
提供可视化界面,监控各个服务上服务调用所花费的时间
Hystrix Dashboard
通过快速的失败,降低服务的级联报错
监控和断路器
Hystrix
客户端的请求都需要先通过网关后,再请求后台服务,通过配置的路由来判断请求需要访问哪个问题,再从注册中心获取注册服务来转发请求
Zuul
可以查看所有的服务实力,都聚集到统一的一个地方查看;
聚合监控平台
Turbine
监控
spring cloud netflix
注册中心-配置中心
服务调用
Sentinel
Gateway
Sleuth
调用链路监控
分布式事物处理
springCloud alibaba
springCloud和springCloudalibaba都有哪些组件
提供了构建微服务系统所需要的一组通用开发模式;以及一系列快速实现这些开发模式的工具
springCloud
通常说的springCloud是指的是springCloud netflix;和springcloud cloud alibaba都是springcloud这一系列开发模式的具体实现
dubbo数据传输直接使用传输层的二进制传输,对象直接转成二进制数据
spring cloud 是使用应用层http协议进行传输,数据最终会转成二进制,中件会有性能消耗
springcloudh和dubbo区别
上世纪90年代出现的一个面向服务的体系结构
SOA历史
ESB(服务总线)是单体结构,很容易出现单点故障
SOA不能解决部署速度
SOA很容易出现功能直接相互依赖
SOA缺点
SOA
soa是一种架构设计风格,是为了将单体服务拆分成一些具有特定业务目标的较小模块
微服务也是soa架构思想的一种形式
SOA依赖消息传递协议在服务之间进行通信
服务通讯方式
SOA类似单体服务,通常都是共享使用一个数据库
微服务,每一个微服务都有自己业务的服务
虽然都是较小服务组成,但是SOA服务的粒度比较粗,微服务粒度比较小
规模和范围
微服务之间是通过与语言无关的http协议通信
SOA通过SOA上层的ESB,通过消息传递协议进行服务之间通信
微服务的耦合性更低
SOA耦合性更高
松耦合和高内聚
左边是SOA,右边是微服务
两者区别
soa和微服务
分布式架构,将打的系统分为多个业务模块,部署到不同机器上,通过接口进行数据交互;
微服务可以部署在同一台机器上也可以分散部署到不同的机器上
部署方式
微服务架构也算是分布式架构的一种
两者联系
分布式和微服务
SOA架构,微服务架构都可以看做是分布式架构思想的一种表现
三者之间的联系
微服务概论
spring-cloud
springframework 是spring 里面的一个基础开源框架
springframework
提供对各种数据源操作的封装
spring data
Spring Cloud Data Flow是用于构建数据集成和实时数据处理管道的工具包
Spring Cloud Data Flow
spring 对图数据结构的处理
Spring for GraphQL
spring-生态
项目发布的云平台
spring cloud 为项目部署提供的一套环境
可以为项目提供生命周期管理,监控诊断,配置管理,服务发现,CI/CD集成,蓝绿部署
Azure
提供一整套分布式项目的各种技术解决的框架
Spring Cloud Alibaba
Spring Cloud netflux
可简化与托管Amazon Web Services的集成
Spring Cloud for Amazon Web Services
用于实现微服务之间的通信
spring cloud bus整合 java的事件处理机制和消息中间件消息的发送和接受,主要由发送端、接收端和事件组成
spring cloud bus
该工具为Spring Boot CLI提供了一组命令行增强功能,有助于进一步抽象和简化Spring Cloud部署
Spring Boot Cloud CLI
Spring Cloud for Cloudfoundry可以轻松地在Cloud Foundry(平台即服务)中运行Spring Cloud应用程序。 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的配置中心
统一管理微服务配置的一个组件,具有集中管理、不同环境不同配置、运行期间动态调整配置参数、自动刷新等功能。
https://blog.csdn.net/u011066470/article/details/106741430
简化了云平台(如Cloud Foundry和Heroku)中连接服务和获取操作环境感知的过程,尤其适用于Spring应用程序
它是为可扩展性而设计的:您可以使用提供的云连接器之一或为您的云平台编写一个,并且您可以使用内置支持常用服务(关系数据库,MongoDB,Redis,RabbitMQ)或扩展Spring 云连接器可与您自己的服务配合使用。
Spring Cloud Connectors
Consul是一套开源的分布式服务发现和配置管理系统
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案
SpringCloud Consul
为通过CDC(Customer Driven Contracts)开发基于JVM的应用提供了支持。它为TDD(测试驱动开发)提供了一种新的测试方式 - 基于接口
传统自测
Spring Cloud Contract
通过函数促进业务逻辑的实现
将业务逻辑的开发生命周期与任何特定运行时目标分离,以便相同的代码可以作为Web端点,流处理器或任务运行
支持无服务器提供商之间的统一编程模型,以及独立运行(本地或PaaS)的能力
在无服务器提供商上启用Spring Boot功能(自动配置,依赖注入,指标)
就像Spring一直在推广基于普通java对象(POJO)的编程模型一样,Spring Cloud Function也基于普通的函数来推广编程模型。我们指的是java.util.function包中定义的核心接口:Function,Consumer和Supplier。
Spring Cloud Function
Spring Cloud Gateway
Spring 数据是用于在众多存储技术中存储和检索 POJO 的抽象。 Spring Cloud GCP 在数据存储模式下为Google Cloud Firestore添加了 Spring Data 支持。
与谷歌的数据处理的整合
Spring Cloud GCP
Spring Cloud Open Service Broker 是一个框架,用于构建实现Open Service Broker API 的Spring Boot应用程序。
Spring Cloud Open Service Broker
该项目试图解决以下问题: 创建公共部署管道 传播良好的测试和部署实践 加快将功能部署到生产所需的时间
Spring Cloud Pipelines
对不同的项目统一数据编码格式
Schema Registry
权限
Spring Cloud Security
Skipper是一种工具,允许您在多个云平台上发现应用程序并管理其生命周期。
Skipper是一个工具,允许您发现Spring Boot应用程序并管理其在多个云平台上的生命周期。您可以单独使用Skipper或将其与Continuous Integration管道集成,以帮助实现应用程序的持续部署。
Spring Cloud Skipper
Spring Cloud Sleuth是对Zipkin的一个封装
简化了消息队列的开发上手难度
目前只支持 RabbitMQ ,Kafka
微服务应用构建消息驱动能力的框架
Spring Cloud Stream
SpringBoot应用程序提供创建短运行定时任务
Task中,我们可以灵活地动态运行任何任务,按需分配资源并在任务完成后检索结果。Tasks是Spring Cloud Data Flow中的一个基础项目,允许用户将几乎任何SpringBoot应用程序作为一个短期任务执行。
Spring Cloud Task
Spring Cloud Task Application Starters是独立的可执行应用程序,可用于按需用例,例如数据库迁移,机器学习和计划操作
特性 独立运行作为Spring Boot应用程序 编排为短暂的数据微服务 将数据微服务应用程序用作maven或docker工件 通过命令行,环境变量或YAML文件覆盖配置参数 提供基础架构以单独测试应用程序 从此版本的Spring Initializr下载为初学者
Spring Cloud Task App Starters
辅助spting boot程序保护一些敏感配置信息
Spring Cloud Vault
SpringCloud——Zookeeper
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 App Broker
Spring Cloud Circuit Breaker
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公共接口实现”
https://www.likecs.com/show-203352078.html#sc=586
Spring Cloud Kubernetes
基于http请求的接口调用
Spring Cloud OpenFeign
生态中各个开源项目简单说明
spring-cloud生态
spring生态
框架
物理结构就是数据结构在计算机中的存储形式;
就是数据结构中的元素都存放在地址连续的存储单元中,数据之间的逻辑关系和存储关系是一致的;也就是存储关系就是逻辑关系;其实就是以数组的形式存储
连续存储
就是数据结构中的元素存放在任意的存储单元之中,元素之间可以是连续存储,也可以不连续的存储;其实就是以链表的形式进行存储
非连续存储
物理结构分为两种形式
物理结构
数据元素之间的相互关系
就是数据中的元素除了同属于一个集合之外,他们没有其他关系
集合(背包)
数据中元素之间存在一对一的关系;(线性结构的数据:队列,栈,数组,链表)
线性结构
就是数据元素之间存在一对多的关系;(二叉树)
树形结构
图形结构数据是多对多的关系
图结构
具体分为
逻辑结构
每当设计出一种新的数据结构的时候,就应该考虑该数据结构在做查询,增加删除时候的效率问题
数据结构概述
二叉树是属性结构中一种比较特殊的数据结构,就是一个元素的下一个元素最多只有两个元素。
二叉树概念
就是所有的根节点都在同一层,且非根节点都有两个子节点
满二叉树
就是二叉树中的元素的编号与其在二叉树中的位置完全一致,就是完全二叉树
完全二叉树
就是数据在储存的只是严格的按照数据比对进行左右两个分支的平衡存储
平衡二叉树
二叉树
数据结构详解
数据结构中,算法执行的时间与该数据结构中数据元素个数之间存在着一种函数关系。当数据结构中元素的增长速度一定,这个函数增长的最慢的函数称之为最有算法
时间复杂度基本上与数据结构中的个数无关了
o(1)常数阶
单层循环,都是线性阶
o(n)线性阶
内外层循环次数都一样那么就是,平方阶
o(n*n)平方阶
对数阶
内外层循环的次数不一样,那么就是n*m阶
o(n*m)平方阶
时间复杂度分类
时间复杂度
排序算法
搜索算法
算法
数据结构与算法
数据发布/订阅
zk是采用cs架构(有客户端,服务端)
服务器启动的时候把当前机器提供的服务信息注册到zk(服务端)上的临时节点上;然后zk的客户端从zk上获取最新的服务节点信息;本地再使用相关的负载均衡的算法, 随机分配服务器
负载均衡过程
轮询:将请求顺序的发送给每个服务器
比例:给每个服务器分配一个加权比例
优先权:给每个服务器分组,每组定义优先权,优先权高的持续接受请求处理请求;优先权低的只有等到他前面优先权高的服务群组挂了,才能接受请求处理请求
最少连接数:最少连接的会获取到请求
最快相应时间:最小相应时间的接受请求
hash算法:通过客户端的ip地址端口请求hash算法
基于策略的负债均衡
基于数据包的内容分发
k会形成服务注册列表,这个列表记录了服务域名和ip的绑定关系
命名服务
Master 选举
集群管理
实现分布式协调与通知功能,不同的客户端都对ZooKeeper上同一个数据节点进行Watcher注册,监听数据节点的变化(包括数据节点本身及其子节点),如果数据节点发生变化,那么所有订阅的客户端都能够接收到相应的 Watcher 通知,并做出相应的处理
分布式协调/通知
跨进程,跨主机,跨网络的数据共享
zk是通过内部的顺序节点来实现分布式队列
分布式队列
zk的作用
就是利用zk不能重复创建临时节点的特性,能创建的线程相当于获取到了锁,不能创建的线程相当于是阻塞在创建临时节点的那一步,等待去创建临时节点(等待获取锁)
主要思想
服务端创建节点同步方法
1、服务端代码里面创建节点的方法是同步的
2、分布式环境下,创建节点的请求都会发送到 zookeeper 的master节点上
3、然后再同步到其他zk节点
特别说明:所有的写请求都只会请求一台服务器,这个方法还是同步方法,所以zk也是能做分布式锁的
创建节点方法
创建节点说明
“惊群”就是在一个节点删除的时候,大量对这个节点的删除动作有订阅Watcher的线程会进行回调
这对Zk集群是十分不利的。所以需要避免这种现象的发生
zk分布式的问题
持久化本质就是内存中的数据已二进制的方式存储在磁盘上
过程需要经历数据序列化,这个序列化是zk自己的jute序列化;存储的时候需要今次那个序列化操作,取出的时候需要进过反序列化操作
持久化本质
每次的修改操作都会记录在日志;
日志的固定格式:log.zxid,zxid 表示起始的事物id;
日志文件
快照数据是记录zk内部在某一时刻的全部的数据(这些数据就是znode节点,和seesion)内容,并指定出入磁盘之中
快照数据
持久化方式
系统宕机,或者重启导致数据丢失,做了持久化就不怕数据丢失
持久化作用
持久化
请求处理的平均延时是否超过300ms
连接数使用率是否超过80%
服务状态不正常
存在未处理的告警信息
不健康的指标
zk健康检查指标
1.客户端向服务端注册Watcher
2.服务端事件发生触发Watcher;
3.客户端回调Watch得到触发事件情况(客户端一直监听)
watcher过程
只会触发一次
通知需要封装成事件发送给服务端
时间通知是异步从服务端发送到客户端
watcher特点
zk允许用户在指定的节点上注册一些watcher (事件监听器);当数据节点发生变化的时候zk服务会这个变化通知到所有的观察者
zk的很多功能都是基于watcher来设计的,比如说
比如说节点的数据更新了,事件监听器会把变化这个事件通知到对应的客户端,然后客户端就会主动拉取变化之后的数据
zk的观察者模式是什么
watcher
临时节点
永久节点
节点的类型在创建的时候就确定
znode节点分类
临时节点依赖于session会话,一次会话结束,这个临时节点生命周期结束;永久节点不依赖于session会话就,只有在客户端显示执行删除操作的时候才会结束;
持久化目录节点持久化顺序编号目录节点临时目录节点临时顺序编号目录节点
区别就是zk是否会给节点名称进行顺序标记
节点区别
1.stat:节点的版本,权限信息;
2.data: 与该节点关联的数据;
3.children该节点的子节点;
节点存储信息
znode数据结构
zk的数据结构
初始化的时候选举出master节点;
初始化选举
原来的master节点挂了,需要选出新的master节点,这个是主要根据主要每一个机器的日志中的事物id数值的大小,数值越大,表明该节点的数据完整性最好;
中期选举
选举
健康检查指标
zookeeper
haproxy是一款功能强大、灵活好用反向代理软件,提供了高可用、负载均衡、后端服务器代理的功能,它在7层负载均衡方面的功能很强大
haProxy
基于AMQP协议
通信协议
(默认的方式)轮询poll的方式拉取消息
生产者推送消息给消费者
消费者消息获取方式
RabbitMq书写语言:erlang
:从主机中虚拟出来的一个虚拟主机;每个虚拟主机都是一个相对独立的rabbitmq服务器
一个虚拟主机里面可以有多个不同的交换机和不同的队列
VirtualHost
概念:1.数据从生产者到消费者之间的数据转换层;2.隔离了一个虚拟主机下面不同数据之间的推送;3.生产者消费者隔离;
不绑定route-key;交换机和queue名称一样
Headers Exchange默认交换机
把消息发送到绑定了该交换机的所有队列上
不需要指定routeing-key
Fanout Exchange广播交换机
数据会被发送到指定路由的queue上去
Direct Exchange直连交换机
消息会被转发到所有满足route-key的队列,以及bingkey模糊匹配到的队列
Topic Exchange主题交换机
交换机种类
exchange
消息队列,实际存储消息数据
交换机名称
name
是否持久化,true持久化
值集True flase
Durability
所有的消费者完成消费后自动删除
ture,所有的消费者消费完成之后,自动删除
Auto-delete
消息生存时间
时间单位毫秒
消息在被抛弃前可以存活多久
Message TTL
队列生存时间
时间单位是毫秒
队列在指定时间内没有被使用,就会自动被删除
Auto expire
队列容纳的消息的最大条数
超过设定条数就会默认放弃队列头部数据
Max length
队列可容纳最大字节数量
超过设定的长度的数据,那么就会默认放弃头部消息
Max length byte
Arguments(拓展参数)
Queue
消息中间件的服务节点。
Broker:
生产端消费端都需要和服务端建立Connection连接,也就是tcp连接
Connection
Channel是轻量级的Connection,减少了tcp频繁连接断开的开销
Channel实际上就是Tcp的连接复用
标记当消息发送出去,找不到路由的处理方式
表示作用
true:消息返回给服务端,服务端可以做后续的处理
false:消息返回服务端,服务端直接删除
处理方式
mandatory标志
Channel
未被正常消费的消息存放的队列;
死信队列定义
拒绝一条消息
拒绝多条消息
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 beanspring.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消息机制
生产者投递消息后,如果 RabbitMQ节点收到消息,则会给我们生产者一个应答
生产者进行接收应答,用来确定这条消息是否正常的发送到 Broker
我们采用的是异步 confirm 模式:提供一个回调方法,服务端 confirm 了一条或者多条消息后 Client 端会回调这个方法。除此之外还有单条同步 confirm 模式、批量同步 confirm 模式
此种方式现实场景中很少使用
生产端Confirm消息确认机制
实现原理:AMQP协议
事务确实能够解决producer与RabbitMq节点之间消息确认的问题,只有消息成功被节点接受,事务提交才能成功,否则我们便可以在捕获异常进行事务回滚操作同时进行消息重发,但是使用事务机制的话会降低RabbitMQ的性能.
事物消息机制
发送端-服务端
单条应答
批量应答
confirm种类
confirm消息机制
消费端发生异常或者无响应,都会通知服务端消费成功,会丢失数据
自动确认,如果发生异常,就会给服务端发送不确认信息;那么消费就会回到消息队列尾部
这种方式效率较高,当时如果在发送过程中,如果网络中断或者连接断开,将会导致消息丢失
消费者成功消费完消息之后,会显式发回一个应答(ack信号),RabbitMQ只有成功接收到这个应答消息,才将消息从内存或磁盘中移除消息
这种方式效率较低点,但是能保证绝大部分的消息不会丢失,当然肯定还有一些小概率会发生消息丢失的情况。
ack种类模式
ack消息机制
服务端-消费端
消费者异常的情况下,能够让生产者重新发送该消息;保证消息的最大程度被正常消费
消息重试目的
开启重试机制
enabled
最大重试次数
max-attempts
重试间隔时间
initial-interval
最大间隔时间(不能超过这个时间间隔)
max-interval
间隔时间乘法数(重试的时间间隔在上一次的倍数)
multiplier
所以设置重试机制的时候,第一种情况重试,第二种就不重试,做好日志相关错误的日志记录
消息重试配置说明:以springBoot整合RabbitMQ说明
消息被消费的时候会被监听,当抛出异常的时候,就会执行补偿机制;
实现的原理还是建立在消费端的ack机制之上
底层使用Aop拦截,如果程序(消费者)没有抛出异常,自动提交事务如果Aop使用异常通知拦截获取到异常后,自动实现补偿机制,消息缓存在RabbitMQ服务器端
重试机制原理
消息在信道内的唯一标记,拒绝的就是当前消息
消息拒绝之后的处理形式是删除,还是重新放入队列
单条拒绝
多条拒绝
消费端手动拒绝
消费者消息处理失败;当前不能处理该消息
消息拒绝机制作用:给服务端发送拒绝消息,让服务端把消息丢弃或者重新放入队列中
消息拒绝机制
消息路由不成功的消息,可以配置相关的死信队列;消息可以发送到死信队列
消息重新入队机制
此机制需要开发做拓展
批量消息发送机制
并不能完全解决消息丢失问题
持久化会降低rabbtimq性能
所有队列的消息都会写入到磁盘的中间中去;当写入的数据大小超过了文件大小,那么就会关闭此文件,再新建一个文件存储;
持久化概述
消息本身推送到消费端的时候在服务端需要存入磁盘
内存资源少,需要把队列中的数据存入磁盘
持久化时间节点
消息并不是来一条消息就往磁盘上存储一条,而是先把消息都放入到一个缓冲池;等一定的条件才会缓存的消息写入磁盘
1.缓冲池缓冲的数据大小超过缓冲池本身
2.超过固定的刷盘时间25ms,不管缓冲池是否满了,都会刷盘
3.消息写入缓冲区后,没有其他后续请求写入,那么也会刷盘
消息刷盘条件
持久化过程
根据消息ID,找到消息所在文件,根据消息在文件中的偏移量,找到该消息;
读取持久化数据过程
收到消费者的ack消息的时候,并不是马上去删除消息,而是先给消息做一个删除的标记
删除说明
后台进程检车到垃圾数据比例超过50%,并且文件不少于3个,的时候就会触发持久化数据的垃圾回收;找到符合要求的左右两个文件,先整理左文件中的有效数据,然后再把有文件中有消息数据复制到左文件;再把又文件删除;
删除过程
1.所有文件中垃圾数据达到50%的比例;
2.存储的文件必须至少有三个;
删除条件
持久化消息删除
把交换机的属性持久化;在宕机或者重启之后服务器可以自动的去创建交换机,避免手动或者跑程序创建
设置durable=true
交换机
把队列的属性持久化,在宕机或者重启之后可以自动的去创建队列,避免手动创建
消息的持久化是建立在队列的持久化之上,如果队列没有持久化,那么消息也不能持久化
设置 deliveryMode =2 ; deliveryMode =1 是不进行持久化
消息
持久化的对象
同步刷盘:消息追加到内存中,就立马刷到文件中存储;类似强一致的,保证消息存储到文件中的同步策略
同步刷盘
异步刷盘:消息追加到内存中,并不是马上刷到文件中,而是在后台任务中进行异步操作;提交到内存中就算存储成功,在后台异步进行刷盘的异步策略
异步刷盘
RabbitMQ 默认采用异步刷盘的策略;
消息持久化
客户端内部主要是采用负载均衡算法
服务端主要是采用代理服务器
负载均衡方式
根据服务器的配置参数,在客户端自己用代码做一套负载均衡算法
轮询:将请求轮流到发送到后端的机器,不关系节点的实际连接数和负载能力
加权轮询:对轮询的优化,考虑每个节点的性能,配置高的机器分配较高的权重,配置低的机器分配较低的权重,并将请求按照权重分配到后端节点
随机法:通过随机算法,在众多节点中随机挑选一个进行请求。随着客户端调用服务端的次数增多,其实际效果越接近轮询
加权随机法:对随机的优化,根据机器性能分配权重,按照权重访问后端节点
源地址哈希法:根据客户端的IP地址,通过hash函数获取一个数值,用这个数值对后端节点数进行取模,这样在后端节点数保持不变的情况下,同一个客户端访问的 后端节点也是同一个
最小连接数:根据后端节点的连接情况,动态选举一个连接积压最小的节点进行访问,尽可能的提高节点的利用率。
haproxy的时候它会将这个请求进行具体的转发到m1或者m2上进行分工,比如安装简单的算法轮询模式,将任务进行均摊,这样资源就会被合理的利用了,对于Java客户端直接可以配置proxy的地址了,而haproxy可以通过心跳的感知哪些服务器是可以发送消息的,比如遇到m2的机器宕机了,它就会自动的将我们的服务退出,来使用其他的节点也进行提供服务
通过中间件方式
消息轨迹是指一条消息从生产者发送到消息队列 RocketMQ 版服务端,再到消费者消费处理,整个过程中的各个相关节点的时间、状态等数据汇聚而成的完整链路信息
该轨迹可作为生产环境中排查问题强有力的数据支持。本文介绍消息轨迹的使用场景、查询步骤以及查询结果的参数说明。
消息轨迹的默认保存时间只有3天。
消息轨迹
消息追踪Firehose
消息队列设计机制
RabbitMq本身并没有延迟队列;
消费在队列中存活时间;当时间超过了消息就会被抛弃;设置死信交换机,被抛弃的消息就会落入到死信交换机;
设置消息的存活时间
1.不设置消费者,就可以让消息一直堆积,直到超过存活时间
核心点
1.创建死信交换机
2.创建死信路由
3.新建消费者队列绑定死信路由
具体解步骤
1.死信交换机就是普通交换机
2.死信交换机被动接受其他交换机或者无法消费的消息
3.创建生产的交换机的时候就需要设置对应的死信交换机
消息延迟发送
1.由于网络原因导致数据丢包
2.交换机的路由没有被队列绑定,消息直接丢失
缺点:事物消息造成发送端阻塞,发送端只有等到服务端回应之后,才会发送下一条数据;生产者的消息吞吐量大大降低;
1.事物消息机制
缺点:效率比较低
1.串行确认
发送端每发送一批,才会确认
缺点:重新发送消息的时候需要把同一批消息再次发送
2.批量确认
服务端接受到了一条或者多条之后,会异步回调发送端的异步确认方法;
发送端发送完消息,可以接着发送其他消息,不会阻塞;
3.异步确认
确认方式
任何一种确认方式,服务端接受到消息之后不是立马给发送端确认;而是需要等待批量数据持久化之后再发送确认消息;
在发送消息之前把消息用排序的Map集合保存起来;如果消息发送失败,那么就会从map集合中读取消息再次发送
整体流程
2.消息确认机制
针对1:
交换机找不到相应的队列就会把消息返回被生产者
1.设置mandatory 设置true
交换机找不到消息,消息会发给备用的交换机
2.alternate-exchange设置备用交换机
针对2:
生产者发送消息-服务端
丢失原因:客户端在处理消息的时候突然机器挂了,导致消息丢失了;
服务端丢失
消费端丢失
消息丢失类型
消费者的消费速度低于生产者生产的速度
消息堆积的本质
生产者突然发送大量信息
生产者原因
消费者消费失败
消费者出现性能瓶颈
消费者直接挂掉
消费者原因
堆积的实际原因
队列溢出,新消息无法进入队列
消息无法被消费
阻塞时间超过消息存活时间
等待消费时间超过业务时间
消息堆积后果
默认是单线程消费
设置多个线程同时处理消费消息
默认是每次拉取一条消息
设置一次从服务端拉取多条消息
优化消费者消费参数
增加消费者数量
取消消费端ack确认机制
新增生产这队列,把消息推送另外的机器上
排查性能瓶颈,针对性改造
消息堆积解决方案
1.生产者消费者一对多
2.生产者消费者一对一,消费者多线程消费
有前后逻辑的顺序的消息,被不同的消费者,或者是同一个消费者中不同的线程消费;都会造成消费顺序错乱的问题;
顺序错乱场景
生产者拆分成多个,让生产者和消费者一对一生产消费(消费者内部可以开多线程消费)
生产者设置不同的队列,每一个消费者绑定不同的队列消费;将消息发送到同一个队列中;
把前后有逻辑关联的数据合并成一个,发送到消费方
针对1
针对2
顺序消费
1.消费端异常没有给服务端发送消息成功消费的标记;
2.服务端没有接收到消费端发送的消费成功的标记;
只要是服务端没有接收到消费成功的标记,服务端都会再次给消费端发送消息;
消息重复消费原因
1.全局消息id做幂等性判断
2.全局业务id做幂等性判断
1.在消费端做幂等性判断
2.消费端代码做限制,无论如何都会发送消费确认消息
RabbitMq队列问题+解决方案
1.一主一备;也可以是一主多备
2.主节点提供读写,从节点备份主节点数据
3.主节点挂了,从节点就会变成主节点;原来的从节点回复之后,就会变成备用节点
1.并发和数据量不高的情况下;
1.需要使用haproxy作为中间件
搭建过程
1.主备模式
概述:数据进行复制,跨地域让两个MQ集群复制和通信;如果当前集群MQ服务超过设定的阈值,那么消息就会被转移到远程的MQ上做分担处理;
说明:需要使用到shovel插件,让跨地域的集群通信
2.远程模式
概述:集群模式,一般2-3个节点实现数据同,主节点收到发送过来的数据,然后同步到其他节点上。
需要搭配haProxy做高可用负载均衡器
3.镜像模式
概述:多中心模式,多套数据中心部署相同的MQ集群;一个集群中通过负载均衡器使得只有一个节点接受消息
各个中心需要配置插件 federation,可以使一个集群节点与另外一个集群节点做通信
4.多活模式
集群模式:
复制策略,是在集群环境下,主阶段从节点之间消息复制的策略,最大程度上是保证集群的可用性
主节点,从节点都写成功后返回成功状态,好处是主节点宕机了,从节点上还有全部的备份数据,容易恢复。缺点同步复制增大了延迟,降低了吞吐量;
同步复制
主节点只要写成功了,就会返回成功状态,好处降低了延迟,提高吞吐量,缺点,主节点写入了,但是数据还没有写入从节点,那么数据就会有丢失
异步复制
消息复制
RabbitMQ集群
区分不同类别信息别称
kafka服务器或者服务集群
broker
每个主题在创建时会要求制定它的副本数(默认1)
副本
分区也就是让kafka相同的topic在不同机器,也就是同一个消息可以在不同的kafka节点上;这样就天然的让kafka变成队列集群
同一个topic会有不同的分区,分区可在不同的机器
同一个topic可以有一个或者多个分区
所以一个节点上面可以有来自多个topic对应的分区
每一个分区都是一个有序队列,分区中的消息都会被分配上一个有序的id(偏移量)
分区工作机制
分区策略
partition(分区)
生产者向某个topic发送的消息
message
消息在日志文件中存储的位置
offest偏移量
日志分段
Segment
消费者
Consumer
消费者组
Consumer Group
生产者api
消费这api
stream-api
connectior-api
管理台对应的api
admin-api
kafka消息核心api
速率和leader相差低于10秒的follower的集合
kafka中与leader副本保持一定同步程度的副本(包括leader)组成ISR
ISR
速率和leader相差大于10秒的follower
OSR
全部分区的follower
AR
ISR(InSyncRepli)、OSR(OutSyncRepli)、AR(AllRepli)
HW:高水位,指消费者只能拉取到这个offset之前的数据
LEO:标识当前日志文件中下一条待写入的消息的offset
HW、LEO
broker在集群中的标识
默认值-1
broker.id
无默认值
listeners
1.节点自身属性设置
zookeeper.connect
连接zookeeper超时时间(毫秒)
无默认超时时间
zookeeper.connection.timeout.ms
连接ZK会话超时时间
zookeeper.session.timeout.ms
zk的从机落后zk主机的最长时间
zookeeper.sync.time.ms
消费者有多少个未确认的消息,才会导致阻塞
zookeeper.max.in.flight.requests
2.连接zk配置
log.dirs
日志存放目录(当log.dirs为null时)
默认值/tmp/kafka-logs
log.dir
将消息刷新到磁盘之前,日志分区上累计的消息数量
默认值:9223372036854775807
log.flush.interval.messages
刷盘前在内存中最长存在时间
log.flush.interval.ms
日志文件的最大容量
默认值-1,也就是可以无穷大
log.retention.bytes
日志文件保存的最长时间
默认是1周时间
log.retention.hours
日志保存的最长分钟
默认为null
log.retention.minutes
log.retention.ms
日志保存时间
默认一周
log.roll.hours
log.roll.ms
分区最大容量
默认1g
log.segment.bytes
分区等待删除时间
默认60000ms
log.segment.delete.delay.ms
日志分区
3.日志配置
拉取的批量消息的最大内存大小
默认值:0.9M
message.max.bytes
消息配置
第一次发动消息时,自动创建topic。
默认值:true;
auto.create.topics.enable
是否可以删除topic
默认值:true
如果为Flase,那么管理工具将不能删除主题
delete.topic.enable
auto.leader.rebalance.enable
主题相关配置
分区重平衡检查的频率
leader.imbalance.check.interval.seconds
触发重平衡比例
默认值100%
leader.imbalance.per.broker.percentage
rebalance配置
后台处理线程个数
默认值10;
background.threads
处理请求线程数量
默认值:8
num.io.threads
处理网络请求网络相应线程数量
默认值3
num.network.threads
日志恢复和日志关闭时刷新的线程数
默认值1
num.recovery.threads.per.data.dir
日志之间移动副本线程数
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
日志索引文件大小
默认值100M
offsets.topic.segment.bytes
偏移量
leader挂了,是否会选举其他副本作为leader
默认值;false
unclean.leader.election.enable
按照给定的压缩方式压缩数据
值集:“gzip”、“snappy”、“lz4”、“zstd”
compression.type
压缩
事务执行最长时间,超时则抛出异常
900000ms
transaction.max.timeout.ms
1.服务器配置
服务器节点配置
bootstrap.servers
1.连接配置
消息缓冲区大小
默认值:33554432 =32M
生产者最大可以用缓存;生产者可以用来缓冲等待发送到服务器的记录的总内存字节
buffer.memory
指定消息的key的序列化类(需要实现Serializer接口)
key.serializer
指定消息内容的序列化类(需要实现Serializer接口)
value.serializer
消息序列化
批量发送的最大容量
默认值16384 =16k;缓存到本地内存批量发送大小;每当消息的数据量达到16k才会把数据发送给服务器
消息不是一条一条的发送,而是积累到一定量才会发送
batch.size
生产者将请求传输之间到达的任何记录组合到一个批处理请求中的时间
默认值0
消息发送延迟时间,也就是在一个延迟时间内所有的消息都是被同一批次的发送出去;
linger.ms
batch.size和liger.size只要满足一个,消息就会被发送
消息发送条件
消息发送到具体分区的阻塞时间
默认值:60000ms,一分钟
阻塞原因:缓冲池已经满了,或者是系统元数据不可用,导致这个问题;
max.block.ms
消息发送阻塞
生产者请求发出后,获取相应的最长时间,如果超过了该时间,那么客户端就会重新发送
默认值:30000 ,30秒
request.timeout.ms
消息请求阻塞时间
生产者发送最大直接数量
默认值:1M
max.request.size:
消息发送大小
消息发送
生产者要求领导者在考虑完成请求之前收到的确认数量
配置消息发送发到服务的消息确认机制
用途:
0:表示producer无需等待leader的确认;
1:代表需要leader确认写入它的本地log并立即确认;
-1(all):代表所有的备份都完成后确认
值集
acks
生产者发送完消息,接受服务器消息确认的时间
默认值120000ms,120秒
delivery.timeout.ms
消息确认
消息发送失败消息重试次数
默认值是int的最大值
retries
消息重新发送中间间隔时间
默认值100ms
retry.backoff.ms
消息重试
消息以怎么的压缩格式进行压缩
消息压缩
关闭空闲连接时间(生产者和服务器最大失联时间)
默认540000
connections.max.idle.ms
单个连接,可接受的最大未确认数量
默认值5;也就是消息发送需要服务端确认,这个就是在发送消息之前需要确认发送如果没有确认的消息大于等于该参数,那么就会发送失败。
max.in.flight.requests.per.connection:
和服务器连接
参数修改之后发送通知的类
metric.reporters
消息拦截器
发送消息之前消息会被拦截,消息还可以做相应的处理
interceptor.classes
自定义操作类
TCP连接接受方缓冲区大小
默认值:32K
receive.buffer.bytes
TCP连接发送方缓冲区大小
默认值:128
send.buffer.bytes
数据传输设置
2.消息相关配置
transactional.id
事务超时时间
transaction.timeout.ms
3.事物消息相关配置
2.生产者配置
服务器连接地址
消费者组的ID
group.id
消费者ID
client.id
2.消费者本身配置
初始偏移量当前偏移量不存在的时候,消费者消费的起始点
自动到最早的偏移量位置
earliest
自动把偏移量充值为最新偏移量
latest
如果没有找到以前的偏移量,那么就会抛出异常
none
直接抛出异常
anything else
earliest\t
默认值
auto.offset.reset
是否公开topic内部的元数据信息
默认值:true;
exclude.internal.topics
隔离级别
isolation.level
自动拉取消息的个数
默认值:500
max.poll.records
自动拉取消息的频率
默认值:5分钟
max.poll.interval.ms
拉取消息的最大数据量
默认值:50M
fetch.max.bytes
拉取最小字节数
默认值:1字节
如果服务器没有数据,那么就会阻塞,直到服务器有数据才会相应
fetch.min.bytes
拉取消息阻塞时间
默认值:500ms
fetch.max.wait.ms
生产者数据拉取配置
消费者是否是自动提交偏移量
enable.auto.commit
消费者自动提交偏移量的间隔时间
auto.commit.interval.ms
生产者自动提交配置
指定消息的key的反序列化类(需要实现Deserializer接口)
key.deserializer
指定消息内容的反序列化类(需要实现Deserializer接口)
value.deserializer
生产者序列化配置
超过多久关闭服务器和消费者的连接
默认值:540000
消费者给服务端发送请求超时时间
默认值:30秒
心跳发送相应超时时间
说明:消费者是会主动向服务器发送心跳,以此来正面自己是存活的
session.timeout.ms
心跳时间:消费者心跳消息发送到消费者协调器的期望时间
默认值:3秒,设置必须是小于session超时时间的三分之一
heartbeat.interval.ms
生产者连接配置
3.消息配置
3.消费者配置
kafka参数设置
DMA直接内存访问;现代计算机就是允许硬件之间直接进行数据交互;DMA将一个地址空间复制到另外一个地址空间,然后数据的传输是DMA设置之间完成
直接内存访问
DMA
能够不经过cpu直接相互直接就能进行数据交互的硬件设备
DMA设备
用户态直接应用内核态的文件句柄
概述(kafka就是mmap实现方式)
mmap方式,用户态和内核态共享内核态数据缓冲区,数据不需要从内核态复制到用户态空间;用户态发送数据的时候,就直接应用内核态的文件句柄就行(无需把数据从内核态拷贝到用户态,再从用户态拷贝到socket套接字的内核空间);
mmap
数据不需要经历从内核态拷贝用户态;数据直接从DMA设备直接发送对应的做网络传输的DMA设备,由这个设备直接传输数据
sendfile
两种实现方式
零拷贝的实现
Kafka的零拷贝并不是说完全不存在拷贝,而是避免不必要的拷贝
从磁盘把数据拷贝到内核空间;
从内核空间中直接把数据发送到网卡;
零拷贝过程
1.从磁盘去读到内核空间缓存页;
2.应用从内核缓存页读取到用户空间缓存区;
3.应用程序将用户缓冲区的数据放入socket缓冲区;
4.操作系统将socket里面的数据复制到网卡接口,发送数据;
传统拷贝方式步骤
1.kafka的零拷贝从获取数据到最终把数据发送出去只需要经历一次拷贝;
2.传统拷贝方式从获取数据到最终把数据发送出去,需要经历4次拷贝;
kafka的拷贝方式大大降低了数据在不同的内存空间中复制的次数,提高了系统io效率
零拷贝和传统拷贝方式对比
Kafka零拷贝
基于磁盘的线性的读写(操作系统做了大量的IO技术优化),甚至会被随机的内存读写更快
read-ahead
write-behind
io优化技术
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 ,设置当需要被压缩的数据超过百分之多少的比例的时候,就进行压缩;
压缩过程
日志压缩
日志清理两种方式
kafka持久化机制
1.消息可以指定分区发送
2.消息可以通过负载均衡方式发送到不同的分区
3.通过指定key进行hash运算后确定让哪个分区发送
消息发送方式
消费者集群的各个消费者只能消费不同的分区
一个topic消息可以发送给多个消费者集群
一个消费者可以消费多个集群的消息
多个消费者集群可以消费一个topic下面的消息
消息消费方式
消息发送/消费方式assign
设置多个消费者消费一个分区的消息-订阅
一个消费组中的一个消费者只订阅一个分区的消息-点对点
订阅队列模式设置subscribe
队列工作方式
同一管理所有的服务器
注册服务节点
记录topic的分区信息与对应的服务器节点对应关系
注册topic
消费者启动的时候,都会去zk创建自己的节点
注册消费者
注册中心服务治理
可以通过zk的配置文件动态感受来自服务器节点的新增减少,来实现相应的负载均衡
生产者负载均衡
zk动态感受消费者新增减少,来合理的实现负载均衡
消费者负载均衡
将分区和消费者id绑定记录到临时节点上
记录分区与消费者关系
记录每个分区中消费者消费的偏移量会发送给zk,方便在消费者重启之后,或者是重新分配消息分区,能够继续之前的消费
记录消费中的偏移量
记录数据
此负载均衡是kafka自带的
缺点是无法动态感知服务器节点的新增减少,从而在服务器新增减少的时候,不能根据服务器做负载均衡
四层负载均衡
kafka集群
生产者发送的多条消息需要组成事物,对所有消费者同时可见,或者同时不可见
生产者发给多个topic,多个分区发送消息,要么都成功,要么都失败
事务场景
生产者不需要等到kafka节点的ack;
0
kafka节点上的leader副本收到消息就发送ack;
不需要等所有follow副本确认
1
kafka所有的follow副本接收到消息,leader服务才会发送ack
-1
ack 的三种机制
kafka事务
有新的分区上线就重新选leader
OfflinePartition Leader
运行重新分区命令,重新选择leader
ReassignPartition Leader
运行重新选择leader命令
PreferredReplicaPartition Leader
服务正常关闭之后,重启重新选择leader
ControlledShutdownPartition Leader
策略
leader选取策略
leader副本的数据和其他副本数据都不一致,读写分离容易导致数据不一致
数据一致性问题
leader副本数据到从副本数据有数据延迟;
延时问题
不支持读写分离
producer 将消息推送到 broker
如果是kafka节点向消费这push消息,可能会造成消费者消费积压,或者是消费者性能浪费
consumer 从broker 拉取消息
消费者是pull(拉)还是push(推)
1、存储kafka元数据
2、集群不同节点之间通信
3、leader 检测、分布式同步、配置管理、识别新节点何时离开或连接、集群、节点实时状态等等。
1、节点和zk之间心跳检测正常
2、follow节点可以即时同步leader的写操作,且不能延时过高
kafka判断一个节点还活着的有那两个条件
zookeeper对于kafka的作用
1、索引和日志文件读写
2、数据传输
1、0拷贝
2、对log文件进行分段处理,且分段数据会简历索引文件
3、本身就是天然的分布式
对下一页数据的读取是从缓存中读取
4、页缓存
5、对磁盘的写入顺序写入
能够让消费者处于消费者机器自身资源相符的消费速度
6、消费采取pull模式
kafka高性能原因
读写都是从leader副本操作的
follow副本的数据都是从lader服务同步
leader副本和Follow副本区别
同一日志同一收集,然后以同一服务的形式发放给各种消费者
日志收集
消息系统(削峰,异步处理)
用户日活跟踪
收集生产者各种生产数据,同一做报表处理
运营指标
流式处理
1.需要导哪些包
2.有哪些核心API
实际的开发方式
主要原因就是消息消费时候提交的偏移量,服务器并不知情
概述:
1.强行杀掉线程,导致偏移量没有提交
2.消费了,还没有提交偏移量,分区就掉线了,触发重平衡,然后消息就会重复消费
3.消费者重新分配分区,导致消费者数据重新消费
4.消息消费时间过长,让zk觉得机器宕机了,触发了重平衡
具体情况分类
最稳定的方式就是在代码中根据消息唯一id做幂等性判断
重复消费
kafka消息一致性指的是分区中的leader和多个副本的消息数据保持一致性;
副本同步leader数据
根据一定的时间间隔副本同步leader数据
rerplica.lag.time.max.ms=10000
当副本数据和leader数据查了多少条也会同步数据
rerplica.lag.max.messages=4000
同步参数
ISR机制
ACK=all机制,生产者给服务器发送消息的时候,直到所有的副本都收到消息才会通知生产者,服务端这边已经收到消息
消息一致性解决方式
消息一致性
时间轮
延迟消息的实现
消息延迟消费
1.消息重试机制会导致消息乱序(一个分区对应一个消费者)
2.多个分区对应多个消费者, 需要顺序消费的数据被分配到了不同的消费者
消息乱序原因
针对1:max.in.flight.requests.per.connection=1禁止生产者向服务器响应前再次发送请求,也就是消息的重试必须是在上次失败之后,里面发起重试
可以设置一个topic只有一个分区,只有一个消费者
生产者把需要顺序消费的消息发送到指定的分区上
消息有序性
发送消息的程序异常,导致消息压根没有发送出去
消息发送了,由于中间网络原因,以及服务器接收原因,导致数据服务器没有正常接收到数据
生产者发送消息给节点丢失
ACk设置=1,主机拿到了数据,从机还没有同步主机数据,这时候主机挂了,从机无法同步主机数据
主机拿到数据,主机就挂了,没有设置相应的从节点,来备份数据;
主机挂了,从机被选为主机,主机中还有部分数据没有被同步到从机
节点保存消息丢失
消息在消费的时候,消息消费的自动确认提交偏移量;如果批量的消息有20条,消费到10条消息的时候异常了,那么就会自动提交消息的偏移量是20,也就是会导致后面10条消息是没有被正常消费,也相当于消息丢失;
消费者丢失消息
消息丢失情况分类
生产者发送消息异常,然后会重新给服务器发送消息
也就是当重试的次数超过了设定次数,那么也不会发送的;
1.重试的次数小于retries指定的次数
2.异常的类型是RetriableException或者事务管理器允许重新发送
消息重试前提条件(两者同时满足才能重试)
消息重试次数,默认次数为int的最大值
重试的间隔时间
消息重试机制参数设置
消息确认机制主要是针对消息发送到服务器正常接收这个过程的处理
消息重试机制,主要是针对消息发送之前生产者内部自己发送消息异常的兜底处理
消息确认机制和重试机制区别
生产者发送丢失
节点数据丢失
消费者消费数据丢失
针对不同丢失情况对策
消费者消费能力不足
消费者处理不及时
积压原因
添加分区个数和消费者个数
合理增大每次拉取的消息数量
不支持死信队列
kafka的一个分区的数据只会被消费者组中的一个消费者消费;一个消费者可以消费来自多个分区的数据;
分区数量- 3,消费者数量- 3Kafka 将一个分区分配给一个使用者。除非某些使用者发生故障并且发生使用者重新平衡(将分区重新分配给使用者),否则所有使用者都将映射到其分区,并按顺序使用这些分区的事件。分区数量 - 1,使用者 - 3如果消费者多于分区数量,Kafka就没有足够的分区来分配消费者。因此,该组中的一个消费者被分配给分区,而该组中的其他消费者将处于闲置状态。分区- 4,消费者- 3在此方案中,其中一个使用者获得 2 个分区,而在使用者重新平衡期间,另一个使用者可能会获得 2 个分区。
原则
消息如何控制只被消费群组中一个消费者消费
消息队列实际问题
kafka监控平台
对于mqtt协议不支持
不支持物联网传感数据直接接入
可以通过代码控制顺序
仅支持统一分区内消息有序,无法实现全局消息有序
监控不完善,需要安装插件
依赖zookeeper进行元数据管理
kafka缺点
https://zhuanlan.zhihu.com/p/109814155
http://events.jianshu.io/p/869464e66cfb
kafka最全面试题
RocketMQ通信方式
最大程度上确保消息的不丢失
重要的消息通知
短信通知
同步消息
对业务的效应时间非常敏感的业务
异步消息
不是特别关注发送结果的场景
日志发送
单向消息
消息种类划分
同步消息,异步消息会有消息的重新发送,单向消息消息发送失败不会重新发送
同步消息,异步消息发送的时候需要服务器节点返回消息接收的确认信息,而单向消息没有
生产发送消息类型
默认的消费方式,但是实时性不高,但是不会造成消息消费堆积
消费者从服务节点上拉取消息消费
拉取式消费
消息消费实时性高
消费者来不及消费过多消息,容易造成消费者消息堆积
消息推送本质上还是消息拉取
服务器节点主动给消费者推送消息消费
推动式消费
消费方式
1、服务器路由提供者,
2、生产者,消费者能够通过名称服务查询各主题的相应元数据信息
1、多个Name Serve 组成集群
2、集群中各个Name Server相互独立,没有信息交互
工作模式
Name Server
同一类Producer的集合,生产者发送消息逻辑一致
生产者组
同一类Consumer的集合,这类Consumer通常消费同一类消息且消费逻辑一致
集群消费模式下,相同的消费者组,每一个消费者平摊消息;
集群消费
消费者集群中的每一个消费者,都是都会受到消息;
广播消费
1、消费者通过同一个消息队列(topic分区)收到的消息是有序的
2、不同的消息队列收到的消息可能是无序的
工作特性
生产者发送消息快速
同一个消费者消费的不同队列之间的消息,是无序的
对程序性能要求高,但是顺序消费要求不高
普通顺序消费
消费者收到的所有消息均是有顺序的
最大程度上确保了消息的有序性
消息发送的吞吐量大大降低
对消费有顺序要求,且对程序性能要求不高
严格顺序消费
基本概念解释
全局顺序消费(严格顺序消费)
部分顺序消息只要保证每一组消息被顺序消费即可
分区顺序消费(普通顺序消费)
消息顺序
发送消息的时候设置tag,消费的时候根据对应的tag做相关的过滤处理
服务器节点过滤
完全可以自定义去过滤消息;缺点就是无用的消息发送到消费者
消费端过滤
过滤方式
消息过滤
1、节点非正常关闭
2、节点宕机
3、节点所在服务宕机
4、服务器断电,但是能立即供电
5、机器无法开机
6、磁盘设备损坏
影响消息可靠性几种情况
1、前四种可以立即恢复,可能会有少量的数据丢失
2、后面两种,如果服务器是单点,那么消息将全部丢失,如果不是单点,消息还可以恢复绝大部分消息
影响范围
消息可靠性
Consumer先Pull消息到本地,消费完成后,才向服务器返回ack,如果没有消费一定不会ack消息
至少一次
回溯消费是指Consumer已经消费成功的消息,由于业务上需求需要重新消费,要支持此功能,Broker在向Consumer投递成功消息后,消息仍然需要保留。并且重新消费一般是按照时间维度,例如由于Consumer系统故障,恢复后需要重新消费1小时前的数据,那么Broker要提供一种机制,可以按照时间维度来回退消费进度
RocketMQ支持按照时间回溯消费,时间维度精确到毫秒,可以向前回溯,也可以向后回溯
消息回溯
应用本地事务和发送消息操作可以被定义到全局事务中,要么同时成功,要么同时失败
1、生产者发送消息时,同步消息,异步消息的发送如果失败了,生产者会重新发送
2、单向发送发送失败,生产者无法重新发送消息;
消息重投
指消息发送到broker后,不会立即被消费,等待特定时间投递给真正的topic
定时消息
生产这发送消息过多,服务器节点处理这些消息达到性能瓶颈
消息不会重投
控制副作用
生产者流量控制
消费者这边接收到的消息,消息处理不过来达到性能瓶颈
降低拉取频率
消费者流量控制
降低服务器节点压力,降低消费者节点压力
消息重试达到最大次数后,依旧无法正常消费,死信队列就会接受到该消息;
可以通过RocketMQ的控制台,对死信队列中的数据重新消费;
接受不能被处理的消息,放在以后再做处理
保证消息能够被正常消费
作用:
发送者为了保证消息肯定消费成功,只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功
如果没有确认,那么服务端还是会接着给消费方发送消息
1、消费者消费消息失败后,令消息再消费一次;
消费失败后的消息会进入消息重试队列
1、消息反序列化失败
2、程序异常
2、消费者依赖的服务不可用
消息消费失败原因
RocketMQ消息工作特性
生产者生产速度过快;
短时间的业务高峰期;
Producer原因
Broker同步策略导致消费堆积
Broker消息堆积
消费者消费速度过慢
消费者宕机
Consumer原因
消息堆积的原因
生产者限流
调整刷盘策略
多线程消费消息
设置数据开关,开关打开消息直接放入数据库,或者直接返回,最大程度上降低消费者程序时间
Producer发送消息的速率监控
Consumer消费消息的速率监控
的差别值与给定的消息堆积数值告警值对比,若是差别 值大于数据告警值,则存在消息堆积,不然不存在消息堆积
Producer发送消息的最大偏移量(maxOffset)与Consumer消费消息的当前偏移量(currOffset)
判断MQ是否存在消息堆积场景方式
1、消费者进行扩容操作
2、提高消费者消费速度;
3、对生产者限流操作;
producer消息的发送速度大于consumer的消息消费速度
这种状况基本上是能够肯定是RocketMQ自己的故障造成的,需要提高Broken节点自身的服务器配置,和相关参数;
producer的生产速率无明显增长,consumer的消费速率无明显增长
producer生产速率正常,RocketMQ服务器性能正常,consumer消费速率下降
差别值呈现增大趋势
最佳工作模式:RocketMQ自己的服务性能,必要的时候能够对RocketMQ 进行扩容,提升消息堆积能力。
差别值呈现平稳趋势或者降低趋势
消息堆积场景
某些特殊场景下,发送出去的消息,消费者需要按照顺序来消费
问题出现原因
发送出去的多条消息,都是走的同一个topic发送
顺序消费的前提
不需要考虑消费顺序
大多数业务场景不需要考虑消息的顺序性
消费者和消费者同时都只有一台机器
消息系统吞吐量不大
生产者消费消息的时候,把消息发送到同一个topic下同一个队列;可以保证消费方只有一个线程去消费消息
将需要消费的顺序消息,合并成一条消息发送出去,这样消费的时候就是有顺序;
设置发送的消息为全局顺序消息
其他场景
问题具体场景
1、消费者没有发出
2、网络原因导致数据丢失
3、rocketMQ节点没有收到
网络问题,导致消息消费的确认消息,rocketMQ节点没有收到
问题原因
消费者代码逻辑中保持幂等性
1、代码层面
消费者记录消费过的消息的唯一id,接收到消息的时候,发现有此id已经消费,那么就不做处理
msgId
消息设置的key
消息体重的唯一标记
唯一编号
通过每条消息的唯一编号来保证
2、消息发送层面
有些业务场景,重复接受到消息,也不会影响到业务,所以不处理也行
3、重复消息不处理
消息重复消费(消息幂等)
网络抖动导致消息丢失
1、生产者发送到队列节点消息丢失
消息还未持久化到磁盘,节点宕机
已经持久化到磁盘,磁盘损坏,但是没有备份
2、RocketMQ节点消息未能持久化到磁盘
消息还未消费完成,就通知节点消息已经消费完了,此时消费者宕机,导致当前正在消费消息丢失;
3、RoekctMq节点消费者丢失
主要有三种场景
消息丢失场景
针对场景1处理
针对场景2处理
针对场景3处理
性能和吞吐量也将大幅下降
导致问题
1、耗费性能,导致消息发送速率降低
使用事务机制传输消息
刷盘操作更为频繁,导致刷盘效率低下
主机需要把数据同步到从机,消耗主机网络io,和cpu
主从机制
消费者消费消息速度降低
消费完再通知节点
优化机制
处理方式带来的问题
Rocket消息问题
1.JDBC
2、AMQ日志
3、KaHaDB(基于AMQ)
4、LevelIDB (谷歌kv数据库)
ActiveMQ
性能对比
Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长使用短轮询方式,实时性取决于轮询间隔时间;消费失败不支持重试;支持消息顺序,但是一台代理宕机后,就会产生消息乱序;社区更新较慢;
Kafka
由于erlang语言的特性,mq 性能较好,高并发;吞吐量到万级,MQ功能比较完备健壮、稳定、易用、跨平台、支持多种语言、文档齐全;开源提供的管理界面非常棒,用起来很好用社区活跃度高;
erlang开发,很难去看懂源码,基本职能依赖于开源社区的快速维护和修复bug,不利于做二次开发和维护。RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。需要学习比较复杂的接口和协议,学习和维护成本较高
单机吞吐量:十万级可用性:非常高,分布式架构消息可靠性:经过参数优化配置,消息可以做到0丢失功能支持:MQ功能较为完善,还是分布式的,扩展性好支持10亿级别的消息堆积,不会因为堆积导致性能下降源码是java,我们可以自己阅读源码,定制自己公司的MQ,可以掌控
支持的客户端语言不多,目前是java及c++,其中c++不成熟;社区活跃度一般没有在 mq 核心中去实现JMS等接口,有些系统要迁移需要修改大量代码
各自优缺点
日志收集,实时计算
RoketMQ在稳定性上可能更值得信赖,业务有并发场景,建议可以选择RocketMQ
数据量没有那么大,小公司优先选择功能比较完备的RabbitMQ
不需要做二次开的情况下;
技术选型
MQ对比
消息中间件
垂直拆分前
垂直拆分后
X 表从 原来的库里面单独拆分出来放在另外一个数据库了。也就是做了垂直拆分
水平拆分前
水平拆分后
在垂直拆分的基础上,又做了水平拆分,把X表的数据又分散到另外一个服务组上面去了
工作机制示意图
可以吧mycat看做是看做是对数据库的操作租了一层代理拦截
中间件
Java总结
0 条评论
回复 删除
下一页