MESI缓存一致性协议
前🌽:
高速缓存底层数据结构:拉链散列表的结构
bucket - cache entry - tag主内存地址 cache line缓存数据 flag缓存行状态
cache line64字节 有效引用主内存地址,连续的相邻的数据结构 读取特别快
处理器读写高速缓存,据变量名执行内存地址解码的操作,解析出:
index 定位到拉链散列表中的某个bucket
tag定位 cache entry
offerset定位变量在 cache line位置
每核CPU有自己的L1 L2缓存,多线程编程 另一核想访问当前核内L1 L2数据?
mesi缓存不命中且数据块在另一个缓存时,容许缓存到缓存的数据复制:高效
状态转换
初始 :没有加载任何数据,I
本地写local write :本地处理器写数据到I的缓存行,M
本地读local read:本地处理器读I状态缓存行,此缓存没有数据可读
其他处理器缓存里没有此行数据,从内存加载数据到缓存行 设成E,只有me有
其他处理器有,此缓存行S(M状态的缓存行 由本地处理器写入/读出 状态不改)
远程读:c1 c2,c2需要c1缓存行数据,c1通过内存控制器memory controller发送c2,内存从总线存该份数据,c2将相应缓存行=S
远程写 :c2得到c1数据,c2发RFO(request for owner) 拥有该份数据的权限,其他处理器相应缓存行=I 无权再操作该份数据,性能消耗大,什么情况下出现FRO,修改共享数据的时候
两个线程修改同一个cache line中不同的数据,轮番发送RFO占用缓存行拥有权,切换I ,而且其他线程读取此行数据L1 L2都是失效的,L3最新但是读取耗性能 更别说跨槽读 only内存上加载
涉及到一个伪共享问题,解决:让不同线程操作对象处于不同缓存行--》缓存行填充
缓存行64字节,java对象头markwork固定8字节(32)/12字节(64压缩不压16),填充6个无用长整形6*8字节,不同对象不同缓存行
对象头与锁状态
对象头:hotspot
markWord:存储hashCode,分代年龄 锁标志位 ,非固定结构 存储尽量多数据,动态调整,对象的状态复用自己存储空间,随着锁标志位的变化而变化
Klass point元数据指针,通过指针确定对象是哪个类的实例
无锁:25bit存对象头hashcode,4bit存放对象分代年龄,1bit是否偏向锁的标识位 2bit锁标识位01
偏向锁:25bit空间中 23bit存放线程id 2bit存放epoch 4bit分代年龄 1bit是否偏向锁标识 0无锁 1偏向锁 锁标识位01
轻量级锁:30bit指向栈中锁记录的指针 2bit锁标识位:00
重量级锁:30bit指向重量级锁的指针,2bit存放锁标识位:11
GC标记:开辟30bit内存空间没有用上,2bit存放锁标志位11
内存分配:
age_bits:分代回收的标识,4字节
lock_bits:锁标志位,2字节
biased_lock_bits:是否偏向锁标识,1字节
max_hash_bits:无锁计算的hashcode占用字节数量,32位虚拟机 32-4-2-1= 25,64位64-4-2-1=57,25字节不用,hashcode占用31位
hash_bits:64位虚拟机,最大字节>31,取31 否则真实字节数
cms_bits:不是64位虚拟机占0byte,否则1byte
epoch_bits:epoch所占有字节大小,2字节
monitor:
线程私有的数据结构,每个线程都有一个可用monitor record列表 全局可用列表;每个被锁住的对象都会和一个monitor关联,monitor有owner字段存放拥有锁的线程的唯一标识
synchronized通过对象内部监视器锁monitor实现,本质依赖底层操作系统mutex lock互斥锁实现,操作系统实现线程间切换需要用户态转换到核心态,成本高,状态转换时间长:重量级锁
-XX:-UseBiasedLocking=false来禁用偏向锁
锁分类
无锁:循环内进行修改,不断尝试修改共享资源, 只有一个能修改成功 其他循环重试
偏向锁 :第一次synchronized,修改对象头锁标志位:偏向锁,执行完同步代码块后 第二次达到同步代码块 线程会判断持有锁是不是自己,是 则正常执行
线程访问同步代码块,markwork获取线程id CAS判断
撤销:等待全局安全点,没有字节码执行,暂停拥有偏向锁的线程,判断对象是否被锁定,对象头无锁状态01或00轻量级锁 撤销偏向锁
轻量级锁:关闭偏向锁/多个线程竞争偏向锁导致锁升级
锁竞争:线程尝试获取锁,该锁被占有,自旋 等待释放,cas修改对象头锁标志位
长时间自旋非常耗费资源,其他线程原地空耗cpu 执行不了有效任务:忙等
重量级锁:忙等有限度(计数器记录自旋次数10),锁竞争严重达到最大自旋次数的线程,轻量级转重量级CAS修改锁标志位不改线程id,后续线程发现是重量级锁 将自己挂起 等待唤醒
控制权交给操作系统,操作系统负责线程间调度和线程状态变更
锁的五种状态
JVM:
cpu使用过高:方法导致
磁盘IO高:栈
内存过高:堆
GC:堆
YGC:eden区满了
动态分配规则:
相同age的对象的大小之和 > 1/2的存活区大小 sum(age.mem)>=1/2S0, >=age的对象挪动到老年代
redis缓存三大问题:
缓存穿透:不存在的数据,布隆过滤器或者黑白名单 缓存预热
缓存击穿 :热点数据失效,互斥锁/分布式锁 预热失效时快速刷新缓存
互斥锁/分布式锁:
永不过期:redis不设置过期时间,后台异步线程刷新缓存
存过期时间,快过期去更新
缓存雪崩:大量数据同时失效,随机过期时间 限流 预热 自动刷新
多级缓存 / 缓存预热 / 限流
数据一致性问题
不重要业务:先更数据库再删缓存(暂时性不一致)
缓存更新成功返回,异步线程去更新数据库(缓存挂了,数据丢失)
双写:先写myql在写redis,事务
数据同步:定期mysql同步到redis,定时任务 变更数据时触发同步
实时数据流:实时数据流 消息队列 ,mysql变更同步到redis
设计模式
工厂模式:
创建对象的接口,具体的对象创建延迟到子类
单例模式:
饿汉式:类加载时初始化,线程安全
懒汉式:用到的时候实例对象并赋值属性 返回
反射:强制访问
序列化破坏单例:序列化写入磁盘 使用时从磁盘读取对象反序列化
反序列化对象重新分配内存,readResolve解决了被破坏的问题
枚举单例模式:无序列号问题 不能被反射获取
daj
这个有人知道吗?什么位图定时任务?
:的ack
ack=0 不等待确认
ack=1 leader确认入盘,默认
ack=all,等待isr所有的副本确认
消息丢失:
生产者producer :默认send异步发送,改成get同步发送,等待被broker成功接收再发下一条
或者注册回调函数:重试直到成功
重试机制 :网络/broker故障,尝试重试 设置acks
性能:客户端使用buffer累积一定数量才发送
ISR:与leader保持同步到副本列表,被isr列表副本成功接收才消息被成功存 储
broker:数据持久化到磁盘
默认写入pageCache后向producer响应
改为同步刷盘 flush.messages写*条fsync,flush.ms*毫秒fsync刷盘
副本机制:分区配副本
消费者consumer:关闭自动提交offset,消费逻辑幂等性
默认:自动提交 开始poll先看是否auth.commit.interval.ms满足先提上一批消息的位移再处理下一批消息:重复消费
enable.auto.commit=false手动提交,消费完准备提 宕机了 重启再次消费:至少消费一次
spring-kafka@KafkaListener enable.auth.commit=false ackmode=manual手动提交