MYSQL alter操作

alter应该不会锁表吧 就会加一个MDL锁 innoDB支持在线DDL alter的时候不会阻塞对表的读写,然后百度 alert千万数据表的时候需要使用percona的 pt-online-schema-change ,他会建立一个与原来表结构相同的新表,然后把数据全部复制过去,最后替换表来完成大数据量表新增一个字段。

volatile

这个是为了各个核心数据一致性的,有修改缓冲区和失效队列,但是修改缓冲区的内容什么时候刷新到主内存,核心什么时候操作失效队列,这个是核心自己来控制的在并发场景下,核心自己控制就会出问题所以需要屏障来告诉核心什么时候操作。java提供了关键字对底层原理封装,由开发人员来决定怎么做,volatile就是屏障,来告诉核心这个时候该刷新主内存,操作失效队列了

7.常见的MESI协议就是基于总线嘎探实现的。

8.MESI解决了缓存一致性问题,但是还是不能将CPU性能压榨到极致。

9.为了进一步压榨CPU,所以引入了 store buffer 和invalidate queue。

10.store buffer和invalidate queue的引入导致不满足全局有序,所以需要有写屏障和读屏障。

redis锁过期了,但是任务没有执行完毕怎么处理

续期 定时器续期 Lock开启锁的时候 会开启一个定时任务 每隔5s检测有没有达到锁过期时间的额2/3 如果达到了就续期.可以使用Sping中的定时多线程 Redission就是使用了这种看门狗机制

可重复锁的话 设置Redis value是Map类型 Map的key为线程ID value为重入次数 加锁使用Lua 脚本判断Redis的key有没有值 没有直接加锁 设置次数为1 有的话判断是否是当前线程 是的话hincrby 1 然后重置过期时间 不是当前线程 则返回false

Map的value是为了记录重入次数 使用Map的 hincrby自增value,如果把次数也放到String的Value里面 还需要解析Value 前半部分为线程ID 后半部分为重入次数,来一个线程解析一下是否为当前线程 在读一下次数给加1 效率比较低

Java线程的状态有哪几种

Java1.5之前延续了 操作系统 有5种状态 新建 就绪 执行 阻塞 终止

1.5之后 Java重新定义了线程状态 新建 运行 阻塞 等待 超时等待 终止

缓存击穿应对措施

1 如果缓存数据基本不会发生改变,可以设置缓存失效时间为永不过期

2 如果缓存数据变化很少,或者缓存刷新对整个流程影响不大,可采用redis 分布式锁 或者本地锁

3 如果缓存变化比较频繁 或者 刷新缓存比较耗时 ,可采用定时任务在缓存失效前重构缓存或者延长缓存失效时间

threadlocal内存储的对象是强引用还是弱引用

在 ThreadLocal 的实现中,其内部使用了一个 ThreadLocalMap 来存储每个线程的变量副本。ThreadLocalMap 的键是 ThreadLocal 对象的弱引用(Weak Reference),值则是线程变量的实际值,通常是强引用(Strong Reference)

因为线程的生命周期很长 当方法执行完 栈帧销毁 ThreadLocal的引用对象 已经销毁(ThreadLocal aa = new ThreadLocal ) 这个aa销毁了 但是线程中的ThreadLocalMap中的key还引用的这个对象 导致这个对象没法被回收

image-20241017110947726

ThreadLocal如果不remove ThreadLocal对象回收了 ThreadLocalmap中的key是null 但是value不为null 但是ThreadLocal对象都回收了 没法定位到这个value了 导致内存泄露 当调用ThreadLocalMap的 get set 时候会判断entry中key有没有空的 有的话回收整个enrty

但是实际用ThreadLocal的时候 ThreadLocal一直是全局静态常量 publisc static final ThreadLocal aa = new ThreadLocal 这样ThreadLocal基本是永远不会被回收的 因为一直有全局强引用,这样跟是不是弱引用没关系 都不会回收ThreadLocal对象(ThreadLocal对象引用是全局的), 这样key value永远不会删除 会内存泄露 甚至后面的线程可能读取到上个线程遗留下来的value值 所以必须手动调用 remove

image-20241017111026640

使用的时候都是这样的 这个ThredLocal对象永远不会回收,弱应用只能回收一些 局部ThredLocal 在方法里新建的那种

Innodb的三大特性 知道吗

插入缓存 Double Write双写机制 自适应Hash索引

  • 讲一个 插入缓存 chage buffer吧

插入或更新不直接更新到磁盘,而是加入缓冲区,最后统一写磁盘

sentinel从已下线的的master所有slave中挑选⼀个,将其转换为master这个选择的标准是什么

第一步是配置的他们各自的优先级 如果优先级高的话就选他 默认都是一样的,第二步是 优先级一样 看看哪个offset比较高 这表示同主库同步的进度 越高的表示同步了更多数据 就选他 第三步如果有一样的offset 应该是选一个ID比较小的

静态常量池和运行时常量池有啥关系

静态常量池是在字节码文件里的,加载文件时 静态称量池被加载成运行时常量池,符号引用改为直接引用。放在元空间,是类私有的。还有字符串常量池,放在堆,是公有的

MySQL主从延迟的原因是什么?如何解决主从延迟问题?

从节点接收主节点的binlog,写入relay log,再生成数据需要时间,如果中间遇到大事务操作可能耗时更久

要求严格的场景可以走主查询,不严格的可以没有数据可以二次查询

主从延迟有这几种办法

1 分库 将主库拆分为多个主库,每个主库写并发就减少了几倍

2 开启MYSQL并行复制 5.6版本就有并行概念 但是基于库级别的 MYSQL5.7真正实现了基于组提交的并行复制。

3 重写代码 插入之后不要立即查询,如果非要立即查询 可以把该操作走主库,但是不建议这么做

4 避免数据库进行大量运算。

MYSQL更新一条数据:

  1. 先加载数据到Bufferpool,对这条数据加独占锁
  2. 把原来的值存到UndoLog中
  3. 更新bufferpool中的值
  4. 对bufferpool中的修改更新到RedoLogBuffer中
  5. 提交事务的时候将redolog 写入磁盘(三种刷盘策略)

MYSQL两阶段提交

redolog日志实时写入到redolog buffer,可以配置写入os cache和磁盘的策略,但是无论怎么配置,都会有一个后台线程1秒刷一次盘。

binlog实时写入binlog buffer,只有在commit是才会写入os cache,可配置刷盘策略。

为了配合binlog,实现数据恢复,redolog有两阶段提交标志 redolog 预提交 -> commit触发binlog -> redolog完成

通过两阶段标志和binlog判断事务的完整性

使用两阶段提交后,写入binlog时发生异常也不会有影响,因为MySQL根据redo log日志恢复数据时,发现redolog还处于prepare阶段,并且没有对应binlog日志,就会回滚该事务。发现redolog是处于prepare阶段,但是能通过事务id找到对应的binlog日志,所以MySQL认为是完整的,就会提交事务恢复数据。发现redolog还处于commit阶段,则在内存恢复数据不用回滚。若没有对应的binlog,恢复binlog

MYSQL主从

主节点为每一个从节点创建一个IO dump线程,dump线程会监听MYSQL中binlog的变化,如果binlog发生了变化,则将binlog发送给从节点,从节点与主节点建立连接后会创建IO线程 IO线程会读取主节点发送过来的数据存到中继日志中,SQL线程会执行中继日志中的日志进行SQL重放

你们线上使用的什么垃圾收集器

我们用的G1垃圾收集器 G1的步骤有四步 初始标记 并发标记 最终标记 筛选回收 G1将内存分为大小相等的Region 用户可以控制垃圾收集的时间,优先收集垃圾比较多的区域,减少停顿时间

G1新生代采用复制算法 老年待采用标记整理算法,不会有内存碎片 CMS是老年代的垃圾收集器,垃圾收集的步骤 初始标记 并发标记 重新标记 并发清理 采用的标记清除算法,可能会产生内存碎片,所以需要设置多少次标记清除之后来一次标记整理,而且如果并发清理阶段如果有大量对象晋升到老年代的预留空间,导致预留空间满了 会导致退化为 serial Old单线程GC

非唯一索引等值查询且查询的值存在会加什么锁

非唯一索引查询的时候 如果存在记录会在非唯一索引上和主键上加锁,不存在的话 不会只会在非唯一索引上加锁(主键上只会加记录锁) 非唯一索引等值查询存在记录的时候会在前面范围加next-key lock临键锁 左开右闭 在记录右边加间隙锁 左开右开

dubbo默认的策略,是基于什么方式实现的

Dubbo会记录当前访问的时候 然后记录一下上次重置的时间,如果下次访问超过时间间隔了 重置计数器,没有的话判断超没超过计数器的上限,Dubbo是设置一个时间间隔多少次访问 每次访问减1 减到0就不让访问了

了解处理器缓存行的伪共享问题吗

处理器访问内存的时候会将变量先加载到缓存行中 缓存行占64个字节 多线程操作同一缓存行的不同变量时 如果某一个变量被修改 CPU会使当前缓存行设置为失效 通过填充变量来避免伪共享 或者使用 @Contended 注解

LinkedTransferQueue 使用字节填充来解决伪共享问题

垃圾回收并发标记阶段,用户线程重新发起了一个黑色标记指向的新引用,怎么保证这个被引用的对象不被回收的

并发标记新增对象 不参与垃圾回收 等下一次再来,G1 中SATB 标记过程中新生成的对象 会设置为 已完成扫描和标记

CMS写屏障技术拦截黑色的新引用,重新标记阶段再处理一次

为什么能指针压缩?原理是什么?

JVM可以通过位移(shifting)和掩码(masking)操作来压缩这些指针,只保留必要的位数来表示对象的位置

JVM会记录一个基地址(base address),所有的对象地址都是相对于这个基地址的偏移量 这样,对象的实际地址可以通过基地址加上偏移量得到

为了支持指针压缩,垃圾回收器(GC)在移动对象时需要更新基地址以及所有指向这些对象的指针

也是不小的性能开销,所以如果内存充足的话可以关闭

MySQL中的隐式锁

隐式锁是一种乐观锁,只在必要的时候加锁。比如insert的时候,默认是不会加锁的,InnoDB的每条记录中都一个隐含的tx_id字段,这个字段存在于聚簇索引的B+Tree中。插入后会保存当前事务ID。如果后面有其他事务要对这个记录加排他锁时,将由这个事务将这个记录增加前面一个事务的显示锁。

  • insert如何加锁?

insert加隐式锁,也就是在主键索引里加上当前事务ID。如果其他事务要获取这个记录的×或者S锁,那么就会显示加×锁。

MySQL中的隐式锁(Implicit Locks)是在事务处理过程中,由数据库管理系统自动施加的锁。这类锁不同于显式锁(Explicit Locks),后者是由应用程序通过SQL语句显式地加锁和解锁的。隐式锁通常是行级别的锁,主要用于保证事务的隔离性和一致性

Redis怎么防止脑裂啊

Zookeeper根据过半机制不会产生两个Leader节点

Redis没有过半机制,哨兵和Cluster都会有脑裂问题,但是看到的都是 哨兵情况的,哨兵监测不到Master就会重新投票,实际上Master还在呢 同时提供服务 数据不一致,所以就脑裂了,通过配置与主节点通信的从节点数量必须大于集群半数节点来保证只有一个Mater能够写入数据 另外一个就不能写入数据 此时应该是两个Master 但是只有一个能提供服务

Kafka做不到一条消息不丢

broker写数据只写到PageCache中,而pageCache位于内存。这部分数据在断电后是会丢失的

Broker配置刷盘机制,是通过调用fsync函数接管了刷盘动作。从单个Broker来看,pageCache的数据会丢失。 Kafka没有提供同步刷盘的方式。同步刷盘在RocketMQ中有实现,实现原理是将异步刷盘的流程进行阻塞,等待响应,类似ajax的callback或者是java的future

Kafka哪里用到了零拷贝

传统IO需要两次系统调用 read() wirte() 一次系统调用两次上下文切换 read 需要从用户态切换到内核态 内核态读取磁盘文件后在切换到用户态 write也是一样 然后read把数据从内磁盘拷贝到pagecache 然后再从pagecache拷贝到用户进程 write需要从用户进程拷贝到socket缓存区 然后到网卡 传统IO 四次拷贝 四次上下文切换

mmap是采用 mmap() 函数+ wirte() 不用 read 系统调用 这样读取文件的时候只需要拷贝到pagecache然后采用内存映射技术把用户空间和内核空间映射到同一个地方

Kafka中根据offset 读取记录时 会把索引文件使用mmap技术加载到pagecache 中 找到映射到的数据 然后在使用sendfile 零拷贝技术 拷贝到网卡

索引文件是使用的mmap 发送数据使用的 sendfile

sendfile只需要一次系统调用sendfile()(mmap需要两次mmap + wirte )直接从内核拷贝到pagecache 然后CPU拷贝数据到socket 缓存区 然后到网卡

sendfile +DMA gather优化了 sendfile拷贝 不需要CPU拷贝了 从内核拷贝到pagecache 然后 pagecache 到网卡

这是我的理解 索引文件需要读取 不需要拷贝 所以使用了 mmap

如果使用 mmap拷贝的话 他需要 mmap+ write 但是他只是程序解析这个文件 不需要拷贝到网卡

当然了 Kafka写索引文件也是用的 mmap技术

mmap函数返回的是消息的内容 应用层能够做一些处理 sendfile 直接就发到网卡去了把文件 只能返回一些发了多少字节数 哪些文件啥的 所以索引文件只能用mmap

这是sendfile 应用程序完全不知道消息内容是啥 直接搞走了

mmap是可以的 就一个内存映射

image-20241017115252770

这是sendfile 应用程序完全不知道消息内容是啥 直接搞走了

image-20241017115302240

mmap是可以的 就一个内存映射

raft和zab的本质区别是什么

raft是基于term(任期) + 日志index来进行选举的,zab基于zxid(epoch + 递增事务id) raft有candidate角色,在主节点不可靠时follower变更角色,发起选举 数据同步raft 通过 appendentrys rpc 调用来完成,zab基于zxid事务id判断事物顺序 其实也差别不大感觉。

I/O多路复用

这个跟netty的实现类似,基于 reactor 事件驱动模型,客户端请求跟channel绑定,多路复用器 selector 将有事件发生的channel 列表交由业务线程处理,linux系统使用底层 epoll 模型,主要epoll_create 创建epoll对象,epoll_wait 将创建的链接加入列表(红黑树维护) ,epoll_cli 将事件集合传递给应用层完成事件处理

jdk的SPI和dubbo的SPI有什么不同,或者说Dubbo为什么要实现一套自己的SPI

Dubbo采用分层架构,集群 负载均衡 网络协议 序列化等都有对应的接口,用户可以自定义实现,促使Dubbo需要可插拔的接口实现发现机制 SPI就是干这个用的 Java spI无法做到按需获取 必须遍历 Java spi加载过程中创建该接口所有实例对象 不管是否使用 Java spi 没有处理并发安全 DUBBO SPI解决了上面的问题 还提供了 类似Spring aop功能 对拓展点进行增强

MYSQL原理 那了解过 单路排序,双路排序吗

单路排序:一次性取出满足行的全部字段,然后再内存中排序,然后返回结果

双路排序:是先取出id、排序字段。在内存中排完序以后,再根据id去回表查出其他所需字段,然后返回结果。

如果 字段的总长度小于max_length_for_sort_data ,那么使用 单路排序模式 否则 双路排序

image-20241017115455804

MYSQL主从复制有几种模式

MYSQL主从 之前是 异步复制 5.5新增半同步复制 5.6新增GTID复制 5.7新增 并行复制和 多源复制

了解过 GTID复制吗

是的 本质一样 1.GTID同步时开启多个SQL线程,每一个库同步时开启一个线程,由原本的串行sql线程变成并行开启多个sql线程,加快读取中继日志速度。

2.binlog在rows模式下,binlog内容比寻常的主从更加简洁

3.GTID主从复制会记录主从信息,不需要手动配置binlog和位置点

Slave 接收到这些binlog事件后,会首先将它们存储在中继日志中,从服务器会跟踪并记录所有已经在其上执行过的事务的GTID,这些信息通常被存储在mysql.gtid_executed表中

  • SQL线程在从服务器的中继日志中读取binlog事件时,会检查每个事件的GTID。
  • 如果该GTID已经存在于mysql.gtid_executed表中,说明该事务在从服务器上已经执行过,因此会被忽略。
  • 如果该GTID不存在于mysql.gtid_executed表中,SQL线程会执行该事务,并将其GTID记录到mysql.gtid_executed表中,以确保不会重复执行。

1、当一个事务在主库端执行并提交时,会产生GITD,一同记录到binlog日志中

2、binlog传输到slave,并存储到slave的relay-log(中继日志)中,,读取GTID的这个值设置gtid_next变量,即告诉Slave,下一个要执行的GTID值

3、sql线程从relay log中获取GTID,然后对比slave端的binlog是否有该GTID

4、如果有记录,说明该GTID的事务已经执行,slave会忽略

5、如果没有记录,slave就会执行该GTID事务,并记录该GTID到自身的binlog。在读取执行事务前会先检查其他session持有该GTID,确保不被重复执行。

6、在解析过程中会判断是否有主键,如果没有就用二级索引,如果没有就用全部扫描

数据重放+GTID的管理怎么保证原子的 我百度应该是 每个事务在重放时都被视为一个原子操作

遇到错误需要回滚 会识别这个GTID 然后回滚

kafka发送消息流程了解吗

Kafka发送消息首先经过拦截器 序列化器 和分区器 然后发送消息到BuffPool 默认大小为32M 在Buffpool中会发送消息追加到 批次对象中 批次对象是个双端队列 当批次大小到了默认16K时候 会把批次通过Sender线程发送到Broker leader副本收到消息写入本地log follower从leader中拉取消息 写入本地之后像leader发送ACK Leader收到ISR中所有副本ACK之后 增加HW,像生产者发送ACK

这个HW不是在收到ACK的时候更新的,而且也不是等所有的ISR副本同步完成才更新的, 是每次从副本去主副本同步消息时带着自身的HW,LEO,主副本按照收到的LEO取最小的更新自己HW,然后会返回给从副本,从副本更新自己的HW

image-20241017115758408

可以参考下这个图,HW可以理解为同步数据最慢的那个副本的LEO,也是对消费端可见的最大数据offset,

从副本的HW相对主副本的HW是存在滞后的,因为需要从副本同步数据时才能确定自己的HW

而且还有一个leader epoch的概念,新的主副本产生后不用按照HW截断数据,减少数据丢失。

消息发送失败重试,同一个partion的会导致乱序吗。如果会乱序,能防止吗

会的 Kafka可以通过消息幂等性来处理, 消息发送到Broker之后会把消息放在在途缓存区中,默认是5,表示同一时间最多只能有5个小时 生产者发送消息的时候会给每个消息一个生产者标号+序列号的标记 序列号是自增的,假设 现在在途缓存区中序列号有 5,6,7,8,9 那么下一个消息的序列号只能是10,如果不是10 就直接写入失败,重新来

ConcurrentHashMap用了悲观锁还是乐观锁

悲观锁和乐观锁都有用到 CAS+ synchronized 添加元素时首先会判断容器是否为空:如果容器为空 CAS初始化 容器不为空 没有Hash冲突 使用 CAS增加元素,出现Hash冲突 使用synchronized新增元素。 如果没有Hash冲突 默认hash碰撞的几率较低 使用CAS较少的自旋来完成具体的hash落槽操作,如果已经存在Hash冲突了 大概率来说是线程竞争比较强烈 使用synchronized

直接内存分配的对象怎么回收

Java GC管不了直接内存 需要主动调用Unsafe的释放对象方法 Java使用 Cleaner虚引用监听ByteBuffer对象, 当直接内存的引用被置为空时,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用Unsafe的freeMemory来释放直接内存

创建还是回收 都是通过 Unsafe类的方法

回收的时候关联了一个 虚引用对象 那个虚引用里 会开一个线程释放 太巧妙了

image-20241017115900267

image-20241017115903951

image-20241017115933692

我的理解是 双亲委派是说一个类的加载,就他自己 DriverManager 由启动类加载 符合双亲委派 JDBC也是应用类加载器加载,符合双亲委派 但是 整体看 没有符合 全盘负责委托机制

image-20241017115946076

MYSQL分库分表之后怎么关联查询啊

1,数据冗余 直接建关联表 可以使用MQ来做关联表的冗余数据修改

2.应用层查询 这种方式会增加应用层的复杂性,性能不太好

3.用中间件支持 sharding sphere 本身提供了关联表的功能 专门用来解决这种多表关联的查询,只需要配置一下关联规则 如果按照业务拆分数据库,合理的情况下,极少的情况下 会出现跨库关联表的查询 哪怕出现了 建议使用 数据冗余的方式

XA

传统XA两阶段提交 有三个角色 AP 应用程序 RM 资源管理器(数据库) TM 事务管理器

TM向AP提供编程接口 AP向TM提交以及回滚事务

TM通过XA接口 通知 RM 提交还是回滚事务

准备阶段:RM各自执行业务操作 但是不提交事务

提交阶段:TM接受各RM执行反馈 如果有一个执行失败则全部回滚,全部执行成功则提交

问题:

需要本地数据库支持XA协议

资源锁需要两阶段结束才释放,性能低

AT

AT模式有脏写的问题 100改成90 然后别人改成80了 你回滚到100了又 AT模式引入了全局锁,会有一个表记录 当前哪个事务在执行这条记录,第二个来获取的话就获取不到了,需要重试 等第一个来释放,这个全局锁是TC记录的,XA模式是执行事务不提交,数据库的锁,数据库的锁力度比较大,任何人都没法访问。 全局锁非Seata管理的事务可以访问 如果发现Seata回滚的时候发现跟快照不一样,会报错。