mysql

- 什么情况下索引失效#

  1. 不符合联合最左匹配
  2. like前导模糊查询 (可以通过 REVERSE()函数来创建一个函数索引解决)
  3. 字段类型不匹配
  4. 索引字段施加函数
  5. or的多个字段都要索引(只要有一个没有索引,就无法使用索引)
  6. != , <> is null, is not null, not in都会使索引失效
  7. 索引情况不好(索引列数据区分度太小,例如性别列),范围太大,扫表.

- InnoDB 支持的索引类型#

  1. B+树索引:
  2. 全文索引:就是倒排索引
    仅能再char、varchar、text类型的列上面创建全文索引
    但是面对高级的搜索还是略显简陋,且性能问题也是担忧。
  3. 哈希索引:
    哈希索引在InnoDB中只是一种系统自动优化的功能
    Hash 索引仅仅能满足”=”,”IN”和”<>”查询,不能使用范围查询。
    Hash 索引无法被用来避免数据的排序操作。
    在MySQL运行的过程中,如果InnoDB发现,有很多SQL存在这类很长的寻路,并且有很多SQL会命中相同的页面(page),InnoDB会在自己的内存缓冲区(Buffer)里,开辟一块区域,建立自适应哈希所有AHI,以加速查询。InnoDB的自使用哈希索引,更像“索引的索引”

- innodb 锁#

innodb 锁

- 平时怎么创建索引的#

  1. 选择区分度高的列做索引

  2. 根据查询场景、查询语句的查询条件设计索引

  3. 如果要为多列去创建索引,遵循最左匹配原则使用联合索引去创建

  4. 在长字符类型的字段上使用前缀索引,减少空间占用

  5. 扩展索引的时候尽量做追加,不是新建

- B+树和 B 树的区别,和红黑树的区别#

为什么不用B树?:
因为B树的所有节点都是包含键和值的,这就导致了每个几点可以存储的内容就变少了,出度就少了,树的高度会增高,查询的 时候磁盘I/O会增多,影响性能。由于B+Tree内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。

和红黑树:
B+树跟红黑树不用比,B+树的高很低,红黑树比不了。

- 如何优化sql#

  1. 让sql使用索引,如果查看sql使用索引情况,explain查看执行计划
    通过explain命令可以得到下面这些信息: 表的读取顺序,数据读取操作的操作类型哪些索引可以使用哪些索引被实际使用,表之间的引用,每张表有多少行被优化器查询等信息。 rows是核心指标,绝大部分rows小的语句执行一定很快。
    Extra字段几种需要优化的情况
    Using filesort 需要优化,MYSQL需要进行额外的步骤来对返回的行排序。
    Using temporary需要优化,发生这种情况一般都是需要进行优化的。mysql需要创建一张临时表用来处理此类查询
    Using where 表示 MySQL 服务器从存储引擎收到行后再进行“后过滤”
  2. 如果让sql使用到索引(符合建索引的几个原则)
    • 最左前缀匹配原则
    • 选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*)
    • 索引列不能参与计算,保持列“干净”, 原因很简单,b+树中存的都是数据表中的字段值
    • 尽量的扩展索引,不要新建索引
    • =和in可以乱序

- 什么是覆盖索引#

覆盖索引就是把要查询出的列和索引是对应的,不做回表操作

- 为什么尽量不要用select *?#

  1. 返回了太多不需要的数据
  2. 无法覆盖索引(select * 走的是聚簇索引),需要回表
  3. 一个好的应用程序设计应当能够在 sql 中有准确的定义,从而减少歧义或者不必要的更改

- 百万级数据分页查询优化#

  1. 纯扫表:记录主键游标,记录上次最大主键Id,从该Id处开始扫

    1
    select * from t where id > max_id limit 100;
  2. 不记录游标只根据主键扫库, 不带where条件, 主键覆盖索引 + 子查询

    1
    select * from t where id in (select id from usertb limit 7000000,100);
  3. 带where条件,联合覆盖索引 key(type, id) + 子查询

    1
    select * from t where id in (select id from usertb where type=1 limit 7000000,100);
  4. 带where和orderby条件,联合覆盖索引key(a, b) + 子查询

    1
    select * from t where id in (select id from usertb where a=1 order by b limit 7000000,100);
  5. 尽量保证不要出现大的offset,加一些条件过滤一,不应该使用limit跳过已查询到的数据,offset做无用功。实际工程中,要避免出现大页码的情况,尽量引导用户做条件过滤。

mysql隔离级别#

- MVCC, 什么是快照读,什么是当前读#

快照读: 即普通SELECT语句,既然是快照读,故 SELECT 的时候,会生成一个快照。

生成快照的时机:事务中第一次调用SELECT语句的时候才会生成快照,在此之前事务中执行的update、insert、delete操作都不会生成快照。

不同事务隔离级别下,快照读的区别:

READ COMMITTED 隔离级别下,每次读取都会重新生成一个快照,所以每次快照都是最新的,也因此事务中每次SELECT也可以看到其它已commit事务所作的更改;

REPEATED READ 隔离级别下,快照会在事务中第一次SELECT语句执行时生成,只有在本事务中对数据进行更改才会更新快照,因此,只有第一次SELECT之前其它已提交事务所作的更改你可以看到,但是如果已执行了SELECT,那么其它事务commit数据,你SELECT是看不到的。

RC的本质:每一条SELECT都可以看到其他已经提交的事务对数据的修改,只要事务提交,其结果都可见,与事务开始的先后顺序无关。
RR的本质:第一条SELECT生成ReadView前,已经提交的事务的修改可见。

- 什么是当前读#

https://mp.weixin.qq.com/s/w1DwsDDSxKfmFxcLOgG3Dw

- mysql数据库事务隔离级别,分別解決什么问题,next-key锁原理、如何解决幻读?#

READ-UNCOMMITTED(未提交读): 可能会导致脏读、幻读或不可重复读
READ-COMMITTED(提交读): 可以阻止脏读,但是幻读或不可重复读仍有可能发生
REPEATABLE-READ(可重复读,mysql默认隔离级别): 可以阻止脏读和不可重复读,幻读通过mvcc解决了快照读,next-key锁解决了当前读
SERIALIZABLE(串行化读): 该级别可以防止脏读、不可重复读以及幻读

- RR RC区别#

区别就在于rr解决了不可重复读和幻读,怎么解决的,通过MVCC和next-key锁.

不可重复读:rc级别下的mvcc总是读取数据行的最新快照,而rr级别下的mvcc,会在事务第一次select的时候,为数据行生成一个快照,后面每次都读这个快照,除非自己更新,所以rr下是可重复读,别的事务提交也无法影响你的事务。

幻读:快照读下rr级别不会出现幻读,因为rr级别的mvcc读的是事务第一次读取时的快照;在当前读下rr级别使用了next-key锁(临键锁),临键锁包括行锁+间隙锁, 来避免两个当前读时有其它事务插入数据,所以当前读使用next-key锁解决的幻读。 最后备注下:如果是先快照读再当前读,影响行数不一致是否属于幻读,是有争议的但大多认为并不是幻读。

RC的本质:每一条SELECT都可以看到其他已经提交的事务对数据的修改,只要事务提交,其结果都可见,与事务开始的先后顺序无关。
RR的本质:第一条SELECT生成ReadView前,已经提交的事务的修改可见。

mysql其他#

- 分库分表#

根据userid取模做的分库分表

两个场景,业务解耦垂直拆分,读写性能瓶颈做水平拆分。

  1. 垂直切分

业务维度切分,解耦

  1. 水平切分

读写性能遇到瓶颈,分库分表

- mysql的乐观锁、悲观锁实现#

MySQL乐观锁的实现完全是逻辑的,也就是自己去实现。 比如给每条数据附带版本号或者timestamp。更新引起数据的版本号改变,两次select判断版本号是否一致可以判断是否发生改变

MySQL悲观锁的实现需要借助于MySQL的锁机制。
行锁
常见的增删改(INSERT、DELETE、UPDATE)语句会自动对操作的数据行加写锁,查询的时候也可以明确指定锁的类型,SELECT … LOCK IN SHARE MODE 语句加的是读锁,SELECT … FOR UPDATE 语句加的是写锁
行锁的实现方式
1) Record lock 锁记录
2) Gap lock 锁两个记录之间的 GAP,防止记录插入
3) Next-key lock 锁一条记录及其之前的间隙

- 主从延迟怎么办#

主从延迟的原因:
主库写入数据并且生成binlog文件, 从库异步读取更新

解决方案:
一、更新操作,做SQL优化,减少批量更新操作
二、查询场景,

  1. 强制读主,对主库压力大,谨慎使用
  2. 延迟读从, 将要更新的key先放到一个本地延迟队列中,做延迟处理。
  3. 聚合表, 如果是1:1的两张数据,可以先订阅更新到一张聚合表,再订阅聚合表的binlog
  4. 订阅全部从库的binlog, todo

- binlog、redolog、undolog#

binlog 一致性。用于主从复制和指定时间范围的数据恢复
redolog 保证持久性。log用于保证持久化,恢复在内存更新后,还没来得及刷到磁盘的数据
undolog 原子性。用于实现事务回滚和mvcc多版本并发控制

jvm

- Java内存区域#

线程私有的

程序计数器
存放当前线程所执行的字节码的行号指示器。如果线程执行的是一个Java程序,计数器记录正在执行的虚拟机字节码指令地址;正在执行的是native方法,则计数器的值为空

Java虚拟机栈

描述Java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧来存储局部变量表(编译期可知的各种基本数据类型,对象引用【reference类型】,内存空间的分配是在编译期完成的,方法运行期间不会改变局部变量表的大小)、操作数栈、动态链接、方法出口等。

本地方法栈

为虚拟机使用到的native方法服务

线程公有的

Java堆(GC堆)

存放对象实例,所有的对象实例和数组都在堆上分配。程序运行时分支可能不一样,只有运行期间才能知道创建哪些对象,这部分内存的分配和回收是动态的

metaspace

存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码

直接内存

不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存。NIO使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,避免了Java堆和Native堆来回复制数据。直接内存的回收是在虚拟机进行full gc的时候顺带进行的,并不会自己触发垃圾回收

- 谈谈java的类加载机制#

  1. 过程

    • 加载
      通过全类名获取定义此类的二进制字节流
      将字节流所代表的静态存储结构转换为方法区的运行时数据结构
      在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口
    • 连接
      验证
      文件格式、元数据、字节码、符号引用验证
    • 准备
      正式为类变量分配内存并设置类变量初始值的阶段,设置数据类型默认的零值
    • 解析
      解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
    • 初始化
      真正执行类中定义的 Java 程序代码
      初始化阶段是执行类构造器 ()方法的过程
  2. 双亲委派模式
    Java的类加载使用双亲委派模式,即一个类加载器在加载类时,先把这个请求委托给自己的父类加载器去执行,如果父类加载器还存在父类加载器,就继续向上委托,直到顶层的启动类加载器。如果父类加载器能够完成类加载,就成功返回,如果父类加载器无法完成加载,那么子加载器才会尝试自己去加载。这种双亲委派模式的好处,一个可以避免类的重复加载,另外也避免了java的核心API被篡改。

    BootstrapClassLoader
    ExtensionClassLoader
    AppClassLoader(应用程序类加载器)

- gc对象存活判定算法#

1.1引用计数算法

给对象添加一个引用计数器,当有地方引用它时加1,当引用失效时计数减1,任何时刻计数器为0时的对象都不会再被引用。

缺点:存在两个对象不会被继续访问,但是两者循环调用的情况,由于存在循环调用导致两个对象都不会被回收。

1.2可达性分析算法

通过一系列被称作gc roots对象作为起点,从这些起点向下搜索,搜索所走过的路径成为引用链,当一个对象到gc roots没有任何引用链时证明此对象不可用。

Java中gc roots的对象包括:

虚拟机栈中引用的对象
native方法中引用的对象
类静态属性、常量中引用的对象

对于一个 Java 程序而言,对象都位于堆内存块中,存活的那些对象都被根节点引用着,即根节点 GC Roots 是一些引用类型,自然不在堆里,那它们位于哪呢?它们能放在哪呢?
答案是放在栈里,包括:
Local variables 本地变量
Static variables 静态变量
JNI References JNI引用等

- gc垃圾收集器及其优缺点#

垃圾回收器种类:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS(ConCurrent Mark Sweep)、G1(Garbage First)

- 标记算法和复制算法的区別,用在什么场合#

CMS 标记清除、Serial Old,Parallel old 标记整理:适用于存活对象比较多的场景
Serial、ParNew、PS 收集器 复制算法:用在那种不可达对象比较多的场合

- cms垃圾收集器的回收步骤及其优缺点?#

cms回收器的标记过程:

CMS以获取最短回收停顿时间为目标的收集器,使用“标记-清除”算法,分为以下6个步骤

1.STW initial mark:第一次暂停,初始化标记,从root标记old space存活对象(the set of objects reachable from roots (application code))

2.Concurrent marking:运行时标记,从上一步得到的集合出发,遍历old space,标记存活对象 (all live objects that are transitively reachable from previous set)

3.Concurrent precleaning:并发的标记前一阶段被修改的对象(card table)

4.STW remark:第二次暂停,检查,标记,检查脏页的对象,标记前一阶段被修改的对象 (revisiting any objects that were modified during the concurrent marking phase)

5.Concurrent sweeping:运行过程中清理,扫描old space,释放不可到达对象占用的空间

6.Concurrent reset:此次CMS结束后,重设CMS状态等待下次CMS的触发

或者4个大步骤:
1,initial mark 2,concurrent mark 3,remark 4,concurrent sweep

CMS缺点:cpu敏感,浮动垃圾,空间碎片

1.CMS收集器对cpu资源非常敏感,在并发阶段对染不会导致用户线程停顿,但是会因为占用一部分线程导致应用程序变慢,总吞吐量会降低。CMS默认启动的收集线程数=(CPU数量+3)/4,在cpu数比较少的情况下,对性能影响较大。

2.CMS收集器无法处理浮动垃圾,可能会出现“Concurrent Mode Failure”失败而导致另一次Full GC,原因是CMS的并发清除阶段用户线程还是在运行,所以还会有新的垃圾不断产生,这些垃圾CMS只能在下次GC时再清理掉,这部分垃圾被称为“浮动垃圾”。所以CMS不能像其他收集器那样在老年代几乎完全被填满了在开始收集,需要预留一部分空间。JDK1.6中CMS将这个阈值提高到了92%,要是CMS运行期间预留的内存不足,会出现一次“Concurrent Mode Failure”,这是虚拟机启动备用方案,临时启用Serial Old收集器充满新进行老年代垃圾收集,所以这个阈值不宜设置的过高

3.CMS基于标记-清除算法,这意味着垃圾收集结束后会有大量的空间碎片,空间碎片过多会造成老年代有很大空间空余但是无法存放大对象的情况。通过参数UseCMSCompactAtFullCollection(默认开启)开关参数来开启内存碎片的合并整理。

- G1回收器的特点#

G1的出现就是为了替换jdk1.5种出现的CMS,这一点已经在jdk9的时候实现了,jdk9默认使用了G1回收器,移除了所有CMS相关的内容。G1和CMS相比,有几个特点:

  1. G1把内存划分为多个独立的区域Region
  2. G1仍然保留分代思想,保留了新生代和老年代,但他们不再是物理隔离,而是一部分Region的集合
  3. G1能够充分利用多CPU、多核环境硬件优势,尽量缩短STW
  4. G1整体采用标记整理算法,局部是采用复制算法,不会产生内存碎片
  5. 控制回收垃圾的时间:这个是G1的优势,可以控制回收垃圾的时间,还可以建立停顿的时间模型,选择一组合适的Regions作为回收目标,达到实时收集的目的。控制G1回收垃圾的时间 -XX:MaxGCPauseMillis=200 (默认200ms)
  6. G1跟踪各个Region里面垃圾的价值大小,会维护一个优先列表,每次根据允许的时间来回收价值最大的区域,从而保证在有限事件内高效的收集垃圾
  7. 大对象的处理: 在CMS内存中,如果一个对象过大,进入S1、S2区域的时候大于改分配的区域,对象会直接进入老年代。G1处理大对象时会判断对象是否大于一个Region大小的50%,如果大于50%就会横跨多个Region进行存放

- G1执行步骤#

  1. 初始标记阶段:暂停应用程序,标记可由根直接引用的对象。
  2. 并发标记阶段:与应用程序并发进行,扫描 1 中标记的对象所引用的对象。
  3. 最终标记阶段:暂停应用程序,扫描 2 中没有标记的对象。本步骤结束后,堆内所有存活对象都会被标记。
  4. 筛选回收(首先对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分Region)

- 内存泄漏与内存溢出的区别#

  • 溢出: OOM,除了 PC 剩下的区域都会发生 OOM,是由于内存不够,或者是代码中错误的分配太多对象导致的。
  • 泄漏:是指 内存分配后没有回收,导致内存占有一直增加,最后会导致溢出。

- 有几种gc fail?#

Allocation Failure 新生代没有足够的空间分配对象 Young GC

GCLocker Initiated GC 如果线程执行在JNI临界区时,刚好需要进行GC,此时GC locker将会阻止GC的发生,同时阻止其他线程进入JNI临界区,直到最后一个线程退出临界区时触发一次GC。 GCLocker Initiated GC

Promotion Failure 老年代没有足够的连续空间分配给晋升的对象(即使总可用内存足够大)

Concurrent Mode Failure CMS GC运行期间,老年代预留的空间不足以分配给新的对象

- 什么情况会触发fullgc?#

1.metaspace空间不足

2.Promotion Failure 老年代没有足够的连续空间分配给晋升的对象(即使总可用内存足够大)

3.Concurrent Mode Failure CMS GC运行期间,老年代预留的空间不足以分配给新的对象

4.System.gc

5.jmap -histo:live [pid]

- 栈内存溢出#

对虚拟机栈这个区域规定了两种异常状况:

(1)如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;
(2)如果虚拟机栈可以动态扩展(当前大部分的 Java 虚拟机都可动态扩展,只不过 Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。
(3)与虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和OutOfMemoryError 异常。

- 系统内存多大,留给操作系统2G,够吗?#

16G内存 虚拟机 推荐配置12G堆, 更大的堆容易引起SWAP,SWAP使用过多会造成宕机

各分区的大小对GC的性能影响很大。如何将各分区调整到合适的大小,分析活跃数据的大小是很好的切入点。

活跃数据的大小是指,应用程序稳定运行时长期存活对象在堆中占用的空间大小,也就是Full GC后堆中老年代占用空间的大小

例如,根据GC日志获得老年代的活跃数据大小为300M,

总堆:1200MB = 300MB × 4 新生代:450MB = 300MB × 1.5 老年代: 750MB = 1200MB - 450MB

https://tech.meituan.com/2017/12/29/jvm-optimize.html

其他

- tcp如何保证可靠传输,tcp、udp区别#

tcp与udp区别

  1. 是否建立连接, udp不建立连接,tcp三次握手
  2. 是否可靠,udp不需要确认, tcp会有确认、重传、窗口、拥塞等机制
  3. 应用场景, udp一般用于即时通信,qq,直播; tcp用于文件传输,收发邮件、登录等

- 为什么要三次握手,四次挥手#

为什么要三次握手
确认双发收发功能都正常

为什么要四次挥手
确认双方都没有数据再发送

- 四次挥手的closewait, timewait分别在哪,为什么timewait要等待2msl#

2MSL是两倍的MSL(Maximum Segment Lifetime),MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间

如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接

TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK

- 设计模式六大原则#

其中设计模式的SOLID原则(Principles)如下:

单一职责原则(Single Responsibility)
开闭原则(Open Closed)
里氏代换原则(Liskov Substitution)
接口隔离原原则(Interface Segregation)
依赖倒置原则(Dependency Inversion)

- 线上需要关注哪些机器参数#

指标 阈值

cpu.iowait 所有进程因为等待IO完成而被阻塞,导致CPU idle所花的时间的百分比

disk.io.util 如果这个指标较高,代表io遇到瓶颈

cpu.busy 60

cpu.load 1以下比较好,1.5 会引起程序响应时间变慢,应触发报警

mem.swapused 一般情况下使用swap,代表物理内存已不足,当系统没有足够物理内存来应付所有请求的时候就会用到 swap 设备。使用 swap 的代价非常大,如果系统没有物理内存可用,就会频繁 swapping,如果 swap 设备和程序正要访问的数据在同一个文件系统上,那会碰到严重的 IO 问题,最终导致整个系统迟缓。

cms gc后的老年代大小

jvm.fullgc.count 5

jvm.yonggc.count 70

jvm.yonggc.meantime(一分钟内的每次年轻代gc的平均时间) 500

- 有哪些处理线上问题的经验#

实际是在考雪崩,限流,降级等措施

xxx ES导致雪崩,bc端未分离,b端下游超时引发的血案
事件: 调用ES持续大量超时,服务bc端都在调用,b端超时时间设置长,占用了很多线程资源,影响C端服务响应,造成C端上游雪崩,影响二十度个服务
(调ES超时的原因?查询请求的返回响应太大了)
事中: 无熔断降级错误,现上线加熔断ES降级,
(降级措施是?返回有损服务,创建活动失败)
事后: bc端分离,对下游添加熔断降级,根本上还是要让es调用查询粒度更小一点,减少es调用的返回量

xxx 慢查询引发的血案1
事件:下单的时候先去删除用户未支付订单并归还库存,但未支付订单表竟然没有给orderid添加索引,导致delete操作锁全表,在当天有大量未支付订单,导致下单接口越来越慢,最终把订单db中的连接数占满,db的整体不可用。

分析原因: DB的客户端在超时的时候会断开连接,但DB服务器还是在执行该操作,或阻塞着,客户端新的调用再申请新的连接,直到把DB的连接池打满,DB完全阻塞在这个查询上,导致不可用。

事前: 1. 学习数据库知识,尤其是索引和锁相关 2. 定时检查服务的索引是否覆盖全 3. 提前做好限流熔断等降级措施
事中: 1. 限流降级, 2. 问题排查,数据库压力没有增加很多,但数据库响应缓慢,应该分析出是慢查询的原因,当很多的慢sql出来时
事后: 1. 对服务整体重新排查

xxx 慢查询引发问题,慢sql导致mq积压
事件: 新表去更新库存,但索引创建的时候没覆盖全场景,后续开发有sql未走到索引,某个周六出现MQ积压告警,看监控发现是慢sql更新,有条update语句未使用到索引,导致更新时锁全表
事中: 临时增加索引
事后: 检查了新表所有设计的sql没有未使用索引的情况。
(慢查询是有可能占用过多的DB资源,把整个DB打垮)(是有可能出现卖超的)

一次线上OOM问题排查

一、问题

一个查询 服务 jvm 偶现oom,容器异常退出,过会儿自动重新拉起进程,容器不变
​二、时间线
20210308 怀疑是cpu飙升导致
观察cpu出现飙升, 通过 top 命令查到进程 pid 通过 top -H -p , 看到下图

耗cpu的线程都为GC线程,排除是cpu飙升的原因

20210311 复现oom 分析dump文件
本想通过sz 将dump文件拉到本地,但太费劲了,dump文件为16G, 即使分段也十分费劲,询问基础架构同学,提供了公司的dump文件分析工具 jifa
jifa 一个可以快速将dump文件快速上传到云端,且在远端使用MAT进行解析的工具,非常好使
将dump文件上传到S3云端,再根据返回的链接,直接跳转到jifa 平台,默认弹出刚才文件从S3导入的选择,选择导入,即会从S3导入到公司 jifa 平台。
选择对应机器的dump文件,进行在线分析,加载后即可得到线上MAT分析结果
从Dominator Tree中可看到有一个线程占用了96%的内存,且是线程中的一个ArrayList 占用着几乎这96%内存,点开发现全是 DotVO实体

该ArrayList 中存在 1.4 亿 个 DotVO实体

查该问题线程的堆栈,发现停在了 AssetsSecondInfoVO.result2VO

此处的逻辑为一个时间范围的中间数据补齐

看DotVO 中的时间 竟然还有50多万s 的时间。。。

查hive表数据,确实是有很大的时长的数据,比如视频时长为28s, 但是hive表中时间数据有16亿s的。。此时做中间时间补齐,则数据量会爆炸
询问数仓同学,是脏数据,是个已知问题
fix: 添加对下游数据正确性的校验逻辑 & 数仓同学推动上报源改造

spring架构

pcursor基于游标的分页

一、基于偏移量分页&基于游标分页#

1.基于偏移量的分页#

适用于内容是静态的,或者不用实时返回数据最新的变化。特点:1.只要有新增或删除,就会有大量的数据偏移量的改变,造成重复展示或者漏展示。2.存在数据量大时的offset慢查询问题

img

2.基于游标的分页#

适用于查询结果在用户浏览过程中是变化的。特点:1.时间线里出现新的数据或删除数据,这些变化都可以在 “前一页” 或者 “后一页” 体现出来。 2.没有offset问题

eg: 发现页 下拉获取最新,上拉获取更久

二、接口实现#

请求参数

参数名 类型 逻辑
参数名 类型 逻辑
pcursor String 初始值传 “” , 后续值使用后端返回值
count Integer 本次想获取的数据量

响应信息

参数名 类型 逻辑
pcursor String 透传给下一页查询 调用方判断是否为 “no_more”, 若是则不再调用,否则永远停不下来了

三、底层实现#

根据更新时间排序

排序规则:order by update_time desc, id desc 原因: 更新时间 + id 的排序, 可以为所有数据确定顺序,即使更新时间相同,即确定每条记录的游标,游标为更新时间+id,只要查询条件不变,即游标可在该顺序下唯一确定一个位置

1. pcursor游标如何拼接#

  • 根据上面排序规则查询出数据
  • 取最后一个数据last,即为该排序下此次查询的终止位置
  • 根据last数据拼接成pcursor
    pcursor = encrypt ( (“updateTime”, long, 1589439219430); (“id”, long, 95424 ); )

2.pcursor游标如何使用#

  • pcursor解析为 上次查询last的 last.updateTime, last.id
  • 本次查询where条件中,updateTime < last.updateTime or (update_time = last.updateTime and id < last.id) order by update_time desc, id desc;
    如下表格,为order by update_time desc, id desc,上次查询游标为last游标位置,则下次查询 updateTime < 1555500001 or (updateTime = 1555500001 and id < 32) order by update_time desc, id desc, 即为后三行数据
update_time id
1555500001 33
1555500001 32 last 游标位置
1555500001 31
1555500000 44
1555500000 42

3. 生成查询SQL#

若查询条件为 userId, status, updateTime 则查询语句为

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT *
FROM
ad_xxx_info
WHERE
user_id = xxx
AND STATUS = xxx
AND (
update_time < xxx
OR ( update_time = xxx AND id < xxx )
)
ORDER BY
update_time DESC, id DESC
LIMIT 20;

创建联合索引 (user_id, status, update_time) 查询即可全部走索引。注:主键也是索引的一部分,Extra为Using where。using where is fine https://stackoverflow.com/questions/9533841/mysql-fix-using-where

mybatis疑问解答

Mybatis的点#

通过动态代理,自动生成一些参数的映射、sql的执行、结果的映射,减少了大量重复的代码,且对sqlsession,jdbc操作,事务,一二级缓存,做了很好的封装

MyBatis的工作原理#

1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

mybaits中如何维护一级缓存、一级缓存的生命周期、一级缓存何时失效#

BaseExecutor成员变量之一的PerpetualCache,是对Cache接口最基本的实现, 其实现非常简单,内部持有HashMap,对一级缓存的操作实则是对HashMap的操作。

一级缓存的生命周期和SqlSession一致,即一次会话

何时失效
a. MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
b. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
c. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;
d.SqlSession中执行了任何一个update操作update()、delete()、insert() ,都会清空PerpetualCache对象的数据

一级缓存的工作流程?#

a.对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;
b. 判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;
c. 如果命中,则直接将缓存结果返回;
d. 如果没命中:去数据库中查询数据,得到查询结果;将key和查询到的结果分别作为key,value对存储到Cache中;将查询结果返回.

mybatis sqlsession、connection、transition的理解#

一个sqlsession会话,如果是多个更新操作

自动提交: 代码层是一个connection、一个transition事务配置, db层是一个connection,多个事务

手动提交: 代码层是一个connection、一个transition事务配置,db层也是一个connection,一个事务

Mybatis动态sql是做什么的?都有哪些动态sql?简述一下动态sql的执行原理?#

动态sql是用一定规则方便的拼接sql。

动态sql:

执行原理:获取当前节点的子节点,判断子节点的类型是否为文本或者CDATA,如果是则没有动态sql,如果节点类型为元素,则表明是动态sql,根据动态sql标签类型,给到不同的handler做处理,最终拼成一个sql

Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?#

支持。

实现原理是动态代理。在resultSetHandler做结果集映射的时候,判断下属性是否配置了嵌套查询,如果配置了则先创建代理对象,实现代理对象的invoke方法,在对下get操作的时候,再去执行该字段的获取方法。

Mybatis都有哪些Executor执行器?它们之间的区别是什么?#

SimpleExecutor, ReuseExecutor , BatchExecutor , CachingExecutor

SimpleExecutor:每一次执行都会创建一个新的Statement对象.

ReuseExecutor: 重复使用Statement对象

ReuseExecutor比SimpleExecutor 性能更好一些

BatchExecutor:批量statement提交。其事务无法自动提交

CachingExecutor:用来做二级缓存,装饰者模式。

实际生产环境中建议使用 ReuseExecutor

简述下Mybatis的一级、二级缓存(分别从存储结构、范围、失效场景。三个方面来作答)?#

存储结构:一级、二级缓存的存储结构都为hashMap

范围:一级缓存的范围是sqlsession级别,二级缓存的范围是namespace级别

失效场景:

当sqlsession做插入、更新、删除操作时

简述Mybatis的插件运行原理,以及如何编写一个插件?#

拦截器,对mybatis的四大核心对象做拦截,做功能增强,底层动态代理实现,四大对象创建时,都会走到interceptorChain.pluginAll(), 返回增强后的对象

编写插件:
1.实现一个interceptor, 加上注解, @Intercepts, 配置要增强的四大对象的接口和方法名
2.sqlMapperConfig中添加该自定义插件标签

参考文献:
https://juejin.im/post/6844904101587714061
https://www.shangmayuan.com/a/cfc40f96680e49e9b692a4e7.html

mybatis架构流程

架构图
架构图

架构分层
架构分层

主要构件
主要构件

层次结构
层次结构

总体流程
总体流程

设计模式
设计模式

gc调优(parnew+cms)

1如何做gc调优#

调优目的:减少系统停顿时间
优化目标:减少younggc、减少cmsgc、减少fullgc
Old GC:只清理老年代空间的 GC 事件,只有 CMS 的并发收集是这个模式。
Full GC:清理整个堆的 GC 事件,包括新生代、老年代、元空间等 。

1.1younggc原因#

1.请求中生成的对象多。新生代空间较小,Eden区很快被填满,就会导致频繁young GC

1.1如何减少younggc#

1.增大新生代空间
2.减少请求中生成对象大小
1.2cmsgc原因
1.有过多的对象到达了老年代,频繁触发cms gc
1.2如何减少cmsgc
1.增大老年代大小
2.减少进入老年代的对象大小, 提高晋升年龄阈值(如果小的话,比如改成了3、4)

1.3fullgc原因#

1.metaspace扩容

2.CMS GC时出现promotion failed(晋升失败)和concurrent mode failure 触发串行full gc。

promotion failed: 当新生代发生垃圾回收,老年代有足够的空间可以容纳晋升的对象,但是由于空闲空间的碎片化,导致晋升失败,此时会触发单线程且带压缩动作的Full GC
concurrent mode failure: 发生的原因一般是CMS正在进行,但是由于老年代空间不足,需要尽快回收老年代里面的不再被使用的对象,这时停止所有的线程,同时终止CMS,直接进行Serial Old GC

3.统计得到的Young GC晋升到老年代的平均大小大于老年代的剩余空间;

4.主动触发Full GC,

程序代码执行System.gc(); 
命令行中dump整个堆: 执行jmap -histo:live [pid]

1.3如何减少fullgc#

原因1. 启动时设置好metaspace,避免因扩容导致fullgc
“-XX:MetaspaceSize=256M”,
“-XX:MaxMetaspaceSize=256M”

原因2&3. 减少cms gc

•降低触发CMS GC的阈值, 以保证有足够的空间。即参数-XX:CMSInitiatingOccupancyFraction的值,让CMS GC尽早执行。
•增大老年代空间
•让对象尽量在新生代回收,避免进入老年代

原因4. 避免使用System.gc()

2.Jvm如何内存分配#

eg: 16G内存机器,9G老年代,4G新生代,3G直接内存

3.gc调优案例#

3.1younggc#

1.请求中生成的对象大,eg:历史数据因历史原因对应很多或很大的返回结果,频繁访问造成gc较多,可以在场景允许的情况下将这部分数据下掉或者做个截断处理。减少younggc的频率和复制对象所花费的时间。
2.增大新生代大小

3.2fullgc#

1.服务启动时metaspace扩容触发fullgc,导致刚启动时服务不稳定,jvm启动参数中设置好Metaspace的size和最大size。
2.服务偶尔发生concurrent mode failure。服务瞬间的可用性下降严重。修改jvm参数,降低触发cms gc的阈值(90%->75%),避免触发串行full gc。

3.3cmsgc#

1.增大old区。肯定是有用的,至少能减少触发的频率
2.也可能是晋升年龄阈值比较小。-XX:MaxTenuringThreshold,默认15, 可能有人改小了,让年龄小的就直接到old区了,比如改成3、4。

备注:cms内存碎片问题#

通常 CMS 的 GC 过程基于标记清除算法,不带压缩动作,导致越来越多的内存碎片需要压缩。常见以下场景会触发内存碎片压缩
新生代 Young GC 出现新生代晋升担保失败(promotion failed))

程序主动执行System.gc()

可通过参数 CMSFullGCsBeforeCompaction 的值,设置多少次 Full GC 触发一次压缩。默认值为 0,代表每次进入 Full GC 都会触发压缩,带压缩动作的算法为上面提到的单线程 Serial Old 算法,暂停时间(STW)时间非常长,需要尽可能减少压缩时间。

innodb事务隔离级别

一、定义#

未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
提交读(Read Committed):只能读取到已经提交的数据. 可以阻止脏读,但是可能发生幻读或不可重复读
可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的. 可以阻止脏读和不可重复读,幻读通过mvcc解决了快照读,next-key锁解决了当前读. mysql默认隔离级别.
串行读(Serializable):读加共享锁,写加排他锁,读写互斥.

可重复读指的是同一行在同一个事务下无论怎么读取都是同一个结果(除非自己把它改了).

幻读指的是在同一事务下,连续执行两次同样的SQL语句第二次的SQL语句可能返回之前不存在的行;

二、什么是快照读,什么是当前读#

快照读:就是select

1
select * from table ….;

当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。

1
2
3
4
5
select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update ;
delete;

事务的隔离级别实际上都是定义了当前读的级别,MySQL为了减少锁处理(包括等待其它锁)的时间,提升并发能力,引入了快照读的概念,使得select不用加锁。而update、insert这些“当前读”,就需要另外的模块来解决了

三、rr级别下mvcc解决不可重复读和快照读之间的幻读#

一致非锁定读,也可以称为快照读,即普通SELECT语句,既然是快照读,故 SELECT 的时候,会生成一个快照。

生成快照的时机:事务中第一次调用SELECT语句的时候才会生成快照,在此之前事务中执行的update、insert、delete操作都不会生成快照

REPEATED READ 隔离级别下,快照会在事务中第一次SELECT语句执行时生成,只有在本事务中对数据进行更改才会更新快照,因此,只有第一次SELECT之前其它已提交事务所作的更改你可以看到,但是如果已执行了SELECT,那么其它事务commit数据,你SELECT是看不到的。

四、rr级别下next-key锁解决当前读之间的幻读#

在当前读下rr级别使用了next-key锁(临键锁),临键锁包括行锁+间隙锁, 来避免两个当前读时有其它事务插入数据,所以当前读使用next-key锁解决的幻读。

最后备注下:如果是先快照读再当前读,影响行数不一致是否属于幻读,是有争议的但大多认为并不是幻读。

五、rr和rc区别#

rc级别下的mvcc总是读取数据行的最新快照,而rr级别下的mvcc,会在事务第一次select的时候,为数据行生成一个快照,后面每次都读这个快照,除非自己更新

如果问,rr为什么是默认的隔离级别,就说rr相比rc来说没有不可重复读和幻读问题. 后面再深入研究

六、todo学习#

  1. rr为什么是默认的隔离级别
  2. mvcc在rc隔离级别下,读最新的快照,为什么不直接读行记录呢,rc级别是怎么解决脏读的

参考文章#

https://tech.meituan.com/2014/08/20/innodb-lock.html

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×