Notes:先看题思考一下,再看图片下的答案!!!
- 分布式事务都有哪些,你们用的什么?
- 说说ThreadLocal原理,使用场景,内存泄漏?
- 高并发下怎么保障缓存和数据库一致性问题?
- 说说Synchronized锁升级过程?
- 线程池核心参数,线程池底层工作原理,应该开多少个线程?
- 说说对AQS的理解?
- 说说BIO、NIO、AIO的理解?有没有用过Netty?
- Mysql索引什么情况下会失效?怎么优化索引,如何设计索引更高效?
- Redis为什么快?
- Redis的持久化方式?
一、分布式事务:2PC、3PC、TCC、本地消息表、消息事务、最大努力通知。
我们自己用的阿里的Seata AT模式。
二、loading…
三、缓存一致性问题:
1、双写模式:不管先写谁都可以出现数据不一致:
先写mysql,再写redis。后续代码出现异常,mysql会回滚,redis不会回滚,导致数据不一致
先写redis,再写mysql。写redis成功,写mysql失败,数据不一致。
2、失效模式:
先删redis还是后删redis都会出现,事务还未提交,另一个线程读取数据,重新把旧数据放入缓存,此时再去提交,就会出现数据不一致。
3、延迟双删模式
先删redis
再写mysql
再延迟删除redis
4、以上方案其实都会有问题,真的要落地就使用canal中间件吧。
四、对象在内存中的存储结构主要有对象头、实例数据、对齐填充数据组成,对象的锁信息是存在markword里的。
当线程访问同步代码快,首先查看当前锁状态是否是偏向锁,如果是偏向锁,检查markwork中的记录的线程是否是当前线程id,如果是当前线程id,则获得偏向锁执行同步代码块。如果不是当前线程id,CAS操作替换线程id,替换成功,则获得偏向锁(线程复用),替换失败,锁撤销,升级为轻量级锁,偏向锁在JDK1.6以上默认开启,开机后程序启动几秒后才会被激活,偏向锁的撤销是需要在safe_point,也就是线程安全点的时候进行,这个时候是stop the world的,所以偏向锁的撤销开销是很大的,如果明确了项目中竞争情况比较多,那么关闭偏向锁可以减少一些偏向锁撤销的开销。当有另外一个线程来竞争锁的时候,线程会在原地循环这就是自旋锁,自旋是相当消费cpu的,默认自旋次数为10升级为悲观锁。引入这么多锁算是对synchronized的锁优化,因为直接采用synchronized给对象加锁会使线程阻塞,因而会造成线程状态的切换,而线程状态的切换必须要操作系统来执行,因此需要将用户态切换为内核态,这个切换过程十分耗时的都需要操作系统来帮忙,有可能比用户执行代码的时间还要长。synchronized是JVM级别的锁,它在不断被优化着,从目前来看Synchronized已经远没有以前那么“重”了,也大概就是JUC包源码(如ConcurrentHashMap)中大量使用Synchronized的原因吧。
五、7个重要参数:
corePoolSize:核心线程数;
maximumPoolSize:最大可扩展线程数;
keepAliveTime:生存时间;
TimeUnit:时间单位;
workQueue:阻塞队列:阻塞队列会对当前线程产生阻塞,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞直到阻塞队列中有了元素。当队列中有元素后,被阻塞的线程会自动被唤醒(不需要我们编写代码去唤醒);
threadFactory:线程工厂;
handler:拒绝策略;
线程池底层工作原理:
初始化线程池,线程数为0;
通过submit、execute方法向线程池提交任务时,线程会做如下判断:
判断核心线程数是否已满,未满则创建新的线程处理任务;
如果核心线程数已满,再去判断阻塞队列是否已满,未满则放入阻塞队列;
如果阻塞队列已满,再去判断最大可扩展线程数是否已满,未满则创建新的线程处理任务;
如果最大可扩展已满,则交给拒绝策略(四个拒绝策略:AbortPolicy:一旦触发就会抛出异常,CallerRunsPolicy:调用者执行,DisCardOldestPolicy:丢弃等待最久的,DisCardPolicy:默默的丢弃任务,自定义拒绝策略:实现RejectedExecutionHandler接口);
一个线程处理完任务之后,从阻塞队列中获取新的任务;
一个线程空闲时间到达生存时间,判断当前线程数是否大于核心线程数。大于则销毁该线程,直到回缩至核心线程数。
六、loading…
七、loading…
八、loading…
九、Redis为什么快(待补充)
a、redis是单线程的,redis是基于内存操作,cpu不是redis性能瓶颈,而是根据机器的内存和网络带宽,高性能的服务器并不都是多线程的,因为多线程cpu上下文切换会耗时,redis将所有的数据放入内存中,所以单线程操作效率是最高的,多线程操作对于内存系统来说会有cpu上下文切换耗时(读写速度:cpu>内存>硬盘)
b、6.x版本出现了IO多线程,无论什么版本,工作线程就一个,使用上来说没有变化,IO多线程的好处是把输入/输出放到更多的线程去并行,好处是:执行时间缩短,更快,更好的压榨系统及硬件的资源。
十、Redis支持RDB和AOF两种持久化机制,持久化功能能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前的持久化文件即可实现数据恢复。
RDB持久化:RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发(save,bgsave)和自动触发。(可能丢失最后一次保存)
save 触发:该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。执行完成时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。
bgsave触发方式:具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。
自动触发是由我们的配置文件来完成的:
save 900 1 //在900秒之后,如果至少有1个key发生变化,则dump内存快照。
save 300 10
save 60 10000
AOF做增量备份,每一个写命令都通过write函数追加到appendonly.aof 中。AOF也有三种触发机制:
每修改同步always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
每秒同步everysec:异步操作,每秒记录 如果一秒内宕机,有数据丢失
不同步no:从不同步
优点:AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
缺点:对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大,AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的,以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。
RDB+AOF混合持久化:
4.x 新增更有性能模式:把重写方式换成直接RDB放到aof文件的头部,比2.1的方法快了,再追加日志



