java面试
2025-06-21 01:19:27 1 举报
AI智能生成
通俗易懂,精炼的java面试题,内含消息队列、redis缓存、分布式事务、数据库优化等等面试知识,正逐渐完善
作者其他创作
大纲/内容
基础
面向对象
概括
面向过程
步骤、顺序
性能更佳
面向对象
参与者,对象本身
易于复用,扩展
特性
封装
隐藏内部实现,开放对外访问
继承
多态
消息队列
队列入门
kafka
activeMQ
rabbitMQ
direct
子主题
rocketMQ
特点
Producer、Consumer队列都可以分布式
能够保证严格的消息顺序(因为性能原因,不能保证消息不重复,
因为总有网络不可达的情况发生,需业务端保证)。
因为总有网络不可达的情况发生,需业务端保证)。
丰富的消息拉取模式
较少的依赖
亿级消息堆积能力
实时的消息订阅机制
高效的订阅者水平扩展能力
支持集群部署,保证了高可用,数据不会丢失
概念
Name Server
它是一个几乎无状态节点,可集群部署,节点之间无任何信息同步
Broker
Broker分为Master与Slave
一个Master可以对应多个Slave,但是一个Slave只能对应一个Master
Master与Slave的对应关系通过指定相同的BrokerName
BrokerId为0表示Master,非0表示Slave
每个Broker与Name Server 集群中的所有节点建立长连接,
定时注册Topic信息到所有Name Server。
定时注册Topic信息到所有Name Server。
Consumer
Consumer与Name Server集群中的其中一个节点
(随机选择,但不同于上一次)建立长连接
(随机选择,但不同于上一次)建立长连接
定期从Name Server 取Topic路由信息
向提供Topic服务的Master、Slave建立长连接
定时向Master、Slave发送心跳
Producer
Producer 与Name Server集群中的其中一个节点
(随机选择,但不同于上一次)建立长连接
(随机选择,但不同于上一次)建立长连接
定期从Name Server取Topic路由信息
向提供Topic服务的Master建立长连接
且定时向Master发送心跳
问题
为什么使用消息队列
解耦
有一个服务专门给设备推送数据的
数据以Protobuf形式放入到消息队列中
公司不定期会有新的定制设备需要推送数据
这样新的设备就可以从消息队列订阅数据
不需要更改原推送代码
异步
推送消息到设备
其中有一次 一次性要给几十万台设备推送消息
要是同步一条条去推送,那要等多久才能完成,最后推送的设备很久才能收到消息
如果使用了消息队列,我就可以把消息都放到多个mq中,
可以同时从多个消息队列中消费,大大提高了推送时间
可以同时从多个消息队列中消费,大大提高了推送时间
削峰
有一些特殊时段,服务器请求的并发量如果暴增,也就所谓的请求高峰期
服务器处理能力有限,如果高峰期并发量超过限定,服务器就会崩溃,所以先把请求都压在MQ中,然后分批消费
往往高峰期,只是某个时段,或者很短的时间
消息队列有什么优点和缺点
缺点
系统可用性降低
引入外部依赖越多,系统越容易挂掉
mq挂掉
系统复杂度提高
加个MQ进来,你考虑消息重复、丢失、传输顺序等问题
一致性问题
如果向多个系统写入数据,其中某个失败了,怎么保证这些系统的一致性
优点
解耦、异步、削峰
Kafka、ActiveMQ、RabbitMQ、RocketMQ
有什么优缺点?
有什么优缺点?
activeMQ
一最早大家都用 ActiveMQ,
现在用的不多了,
社区也不是很活跃,不推荐用这个
现在用的不多了,
社区也不是很活跃,不推荐用这个
RabbitMQ
erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,
对公司而言,几乎处于不可控的状态,但是确实人家是开源的,
比较稳定的支持,活跃度也高
对公司而言,几乎处于不可控的状态,但是确实人家是开源的,
比较稳定的支持,活跃度也高
RocketMQ
越来越多的公司会去用 RocketMQ,确实很不错,
毕竟是阿里出品,但社区可能有突然黄掉的风险
毕竟是阿里出品,但社区可能有突然黄掉的风险
kafka
大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的
中小型公司一般使用rabbitMQ,技术挑战不是特别高
大型公司基础架构研发实力较强,使用rocketMQ
如何保证消息队列的高可用?
RabbitMQ 的高可用性
基于主从(非分布式)做高可用性的
集群搭建
普通集群模式
无高可用
rabbitMQ部署多个实例分别在一个服务器上
每个rabbitMQ对应一个queue
只有一个queue放消息,其他queue只同步queue元数据
镜像集群模式
你创建的 queue,无论元数据还是 queue 里的消息
都会存在于多个实例上
都会存在于多个实例上
每次你写消息到 queue 的时候,
都会自动把消息同步到多个实例的 queue 上
都会自动把消息同步到多个实例的 queue 上
如何开启这个镜像集群模式
后台新增 镜像集群模式的策略
指定的时候是可以要求数据同步到所有节点的,
也可以要求同步到指定数量的节点
也可以要求同步到指定数量的节点
镜像的配置是通过 policy 策略的方式
all 所有的节点都将被同步
exactly 指定个数的节点被同步
nodes 指定的名称的节点被同步
exactly 指定个数的节点被同步
nodes 指定的名称的节点被同步
Kafka 的高可用性
天然的分布式消息队列
架构
kafka8.0后,提供了HA 机制(副本机制)
每个 partition 的数据都会同步到其它机器上,形成自己的多个副本(replica )
副本(replica )会选举一个 leader 出来,其他 副本 就是 follower
当leader挂掉时,从follower中重新选举出leader,读和写只由leader负责,follower只用来备份数据
在写的时候,leader 会负责把数据同步到所有 follower 上去
所有 follower 都同步成功返回 ack
搭建集群
如何保证消息不被重复消费
(使用消息队列如何保证幂等性)
(使用消息队列如何保证幂等性)
kafka消费数据的过程
consumer 消费了数据之后,每隔一段时间(定时定期),
会把自己消费过的消息的 offset 提交到zookeeper,又给kafka
会把自己消费过的消息的 offset 提交到zookeeper,又给kafka
幂等性
通俗点说,就一个数据,或者一个请求,给你重复来多次,
你得确保对应的数据是不会改变的,不能出错。
你得确保对应的数据是不会改变的,不能出错。
解决方案
如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看
例
需要让生产者发送每条数据的时候,里面加一个全局唯一的 id
先根据这个 id 去比如 Redis 里查一下,如果存在跳过消费,如果不存在,消费并将id写入redis
如何处理消息丢失的问题
场景
绝对不会把计费消息给弄丢,弄丢会造成经济损失
消息丢失图解
消息丢失的情况
生产者弄丢数据
消息传入MQ过程中丢失
由于网络原因等问题
解决方案
rabbitMQ
方案一 事务机制(同步,损耗性能吞吐量下降)
使用 RabbitMQ 提供的事务功能
生产者发送数据之前开启 RabbitMQ 事务channel.txSelect
发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错
此时就可以回滚事务channel.txRollback,然后重试发送消息
如果收到了消息,那么可以提交事务channel.txCommit
方案二 confirm机制(异步,推荐使用)
生产者那里设置开启 confirm 模式
每次写的消息都会分配一个唯一的 id
如果写入了 RabbitMQ 中,RabbitMQ
会给你回传一个 ack(回执) 消息告诉你说这个消息 ok
会给你回传一个 ack(回执) 消息告诉你说这个消息 ok
如果 RabbitMQ 没能写入这个消息,会回调你的一个 ack 接口
告诉你这个消息接收失败,你可以重试
告诉你这个消息接收失败,你可以重试
kafka
acks=all
MQ弄丢数据
MQ挂掉还没消费的消息会丢失
解决方案
rabbitMQ
丢失可能性不大,可以开启rabbitMQ的持久化
设置持久化
创建queue的时候,设置其持久化
持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的
发送消息的时候将消息的 deliveryMode 设置为 2
当写入rabbitMQ后,还没持久化就挂掉会丢数据
可以使用confirm机制,等持久化后在返回ack
kafka
场景
follower同步leader数据时,leader挂掉了
方案
给 topic 设置 replication.factor 参数:这个值必须大于 1,
要求每个 partition 必须有至少 2 个副本
要求每个 partition 必须有至少 2 个副本
Kafka 服务端设置 min.insync.replicas 参数
这个值必须大于 1
leader 至少感知到有至少一个 follower正常
producer 端设置 acks=all :消息写入所有 replica 之后,才能认为是写成功了
在 producer 端设置 retries=MAX,很大很大很大的一个值,无限次重试的意思
消费者弄丢数据
消费者消费消息的过程中挂掉,导致消息丢失
解决方案
rabbitMQ
ack机制
刚消费到,还没处理,结果进程挂了
使用 RabbitMQ 提供的 ack 机制
必须关闭 RabbitMQ 的自动 ack,可以通过一个 api 来调用就行
然后每次你自己代码里确保处理完的时候,再在程序里手动 ack
kafka
场景
自动提交了 offset消息还没处理完,消费者就挂掉了
关闭自动提交 offset
在处理完之后自己手动提交 offset
保证消息的顺序性
顺序会错乱的场景
rabbitMQ
场景1
一个queue,有多个consumer去消费
consumer从MQ里面读取数据是有序的
每个consumer的执行时间是不固定的
造成数据顺序错误
场景2
一个queue对应一个consumer,
但是consumer里面进行了多线程消费,
这样也会造成消息消费顺序错误。
但是consumer里面进行了多线程消费,
这样也会造成消息消费顺序错误。
kafka
场景
kafka一个topic,一个partition,一个consumer,
但是consumer内部进行多线程消费
但是consumer内部进行多线程消费
解决方案
方案一
在消费者处可以把消息写入多个内存queue,
需要顺序执行的消息写入同一个queue
然后N个线程分别消费一个内存queue
需要顺序执行的消息写入同一个queue
然后N个线程分别消费一个内存queue
方案二
把需要顺序执行的消息放入同一个queue中,然后一个queue对应一个消费者去消费
如何解决消息队列的延时导致消息大量积压
场景
消费者出现问题,或者直接挂掉,消息累积,大量积压
解决方案
临时紧急扩容
先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉
新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量
然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,
消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue
消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue
接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据
等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息
MQ中的消息过期失效了
场景
RabbtiMQ 是可以设置过期时间TTL
消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉
解决方案
批量重导
写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去
死信队列
搜索引擎
Lucene入门
图解使用过程
关键词
field
ield(域)可以类比关系型数据库中的字段的概念。Field包含三个部分:名称、类型、值。
Document
Document(文档)是Field的集合。Document可以类比为关系型数据库中的记录
一个Document可以描述一个人的信息,可能包含姓名、年龄等字段;
Directory
Directory是Document的集合,描述了Lucene索引的存放位置,可类比关系型数据库中的数据库
概念
倒排索引
正排索引描述的是每个文档包含哪些词
子主题倒排索引描述的是某个词出现在哪些文档中。
elasticsearch入门
应用场景
海量数据分析引擎
站内搜索引擎
数据仓库
基本概念
集群和节点
master slave1 slave2 像这样的一组节点构成了一个集群
索引
含相同属性的文档集合。≈ MySQL database
文档
索引可以定义一个或者多个类型,文档必须属于一个类型。=MySQL 表
类型
可以被索引的基本数据单位。 = MySQL 一行记录
实例解释
假设现在有个信息查询系统,用ES来存储
这样分布,汽车索引,家具索引,图书索引
图书索引分为很多类型,科普类,玄幻类
科普类具体到每一本书籍,就是文档。最小的存储单位
分片、备份
分片
一个索引有多个分片,每个分片都是一个Lucene的索引
如果一个索引的数据量很大,比如图书索引数据量太大,
硬盘压力大,就可以创建多个分片
硬盘压力大,就可以创建多个分片
备份
拷贝一份分片就完成了分片的备份,除了主分片挂掉以后备份,还可以分担搜索的压力。
ES创建索引默认5个分片,1个备份
分片只能在创建索引的时候指定
备份可以后期动态修改
索引过程
获取内容
建立文档
获取的原始内容需要转换成专用部件(文档)才能供搜索引擎使用
一般来说,一个网页、一个PDF文档、一封邮件或一条日志信息可以作为一个文档。
文档由带“值(Value)”的“域(Field)”组成
文档由带“值(Value)”的“域(Field)”组成
文档分析
搜索引擎不能直接对文本进行索引,确切地说,
必须首先将文本分割成一系列被称为语汇单元(token)的独立原子元素,此过程即为文档分析
必须首先将文本分割成一系列被称为语汇单元(token)的独立原子元素,此过程即为文档分析
每个token大致能与自然语言中的“单词”对应起来
此即为切词,或分词。
文档索引
文档将被加入到索引列表
分布式缓存
redis入门
问题
为什么要用缓存
高性能
对于一些需要复杂操作耗时查出来的结果
且确定后面不怎么变化,但是有很多读请求
直接将查询出来的结果放在缓存中,后面直接读缓存
节省查询时间,提高性能
用了缓存之后会有什么不良后果?
缓存与数据库双写不一致
缓存雪崩、缓存穿透
缓存并发竞争
redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发?
单线程模型
为什么不用多线程?
一次I/O读写的过程
I/O操作一般分为两个阶段
等待I/O准备就绪
在磁盘上数据是分磁道、分簇存储的
数据往往并不是连续排列在同一磁道上
磁头在读取数据时往往需要在磁道之间反复移动,因此这里就有一个寻道耗时
盘面旋转将请求数据所在扇区移至读写头下方也是需要时间,这里还存在一个旋转耗时
真正操作I/O资源
在等待I/O准备就绪阶段,线程阻塞,等待磁盘就绪,
此时操作系统可以将这个空闲的CPU核心用于服务其他线程
此时操作系统可以将这个空闲的CPU核心用于服务其他线程
因此在I/O操作中,使用多线程效率更高
但redis是基于内存,不涉及I/O操作,
使用多线程不仅不能提高效率,而且多线程上下文切换还需要耗费时间
使用多线程不仅不能提高效率,而且多线程上下文切换还需要耗费时间
redis瓶颈
内存大小
网络带宽
redis运行机制
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件
IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队
事件分派器每次从队列中取出一个 socket,
根据 socket 的事件类型交给对应的事件处理器进行处理
根据 socket 的事件类型交给对应的事件处理器进行处理
redis 单线程模型也能高效率
纯内存操作
基于非阻塞的I/O多路复用机制
节省了线程上下文切换时间
redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
数据类型
string
普通的 set 和 get
hash
类似 map 的一种结构
value里的对象不能嵌套对象
hset person name bingo
hset person age 20
hset person id 1
hget person name
hset person age 20
hset person id 1
hget person name
list
有序列表
lpush mylist 1
lpush mylist 2
lpush mylist 3 4 5
# 1
rpop mylist
lpush mylist 2
lpush mylist 3 4 5
# 1
rpop mylist
set
无序集合,自动去重
sorted set
排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。
redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?
内存过期策略
定期删除
redis默认每隔100ms就随机抽取一些设置了过期时间的key,
检查其是否过期,如果过期就删除
检查其是否过期,如果过期就删除
惰性删除
过期的key如果没有被定期删除删掉,当你再次获取这个key时,
redis会检查是否过期,过期则删除key,不返回任何东西
redis会检查是否过期,过期则删除key,不返回任何东西
内存淘汰机制
noeviction
当内存不足以容纳新写入数据时,新写入操作会报错
allkeys-lru
当内存不足以容纳新写入数据时,
在键空间中,移除最近最少使用的key
在键空间中,移除最近最少使用的key
最常用
allkeys-random
当内存不足以容纳新写入数据时,
在键空间中,随机移除某个key
在键空间中,随机移除某个key
volatile-lru
当内存不足以容纳新写入数据时,
在设置了过期时间的键空间中,
移除最近最少使用的key
在设置了过期时间的键空间中,
移除最近最少使用的key
volatile-random
当内存不足以容纳新写入数据时,
在设置了过期时间的键空间中,
随机移除某个key
在设置了过期时间的键空间中,
随机移除某个key
volatile-ttl
当内存不足以容纳新写入数据时,
在设置了过期时间的键空间中,
有更早过期的时间的key优先移除
在设置了过期时间的键空间中,
有更早过期的时间的key优先移除
如何保证 redis 的高并发和高可用?
高并发
主从架构
概括
一主多从,主负责写
并且将数据复制到其它的 slave 节点
采用异步方式复制数据到 slave 节点
slave node 在做复制的时候,会用旧的数据集来提供服务
复制完成的时候,需要删除旧数据集,
加载新数据集,这个时候就会暂停对外服务
加载新数据集,这个时候就会暂停对外服务
从节点负责读
特点
轻松实现水平扩容,支撑读高并发
注意
建议必须开启 master node 的持久化
master 宕机重启的时候,会重新同步数据到slave上,导致slave数据也丢失
对master node进行备份
rdb
高可用
高可用架构
叫做 failover 故障转移,也可以叫做主备切换
基于哨兵的高可用
redis 的主从复制原理能介绍一下么?
过程
slave node首次连接到master node,触发全量复制(full resynchrornization)
启动后台线程,生成一份RDB快照文件
新收到的命令缓存在内存中
master将生成的RDB发送给slave
slave会先写入本地磁盘,然后再从本地磁盘加载到内存中
接着master将内存中缓存的命令发送给slave,进行同步
结构图
功能
断点续传
挂掉重连后,接着上次复制的地方,继续复制下去
master node 会在内存中维护一个 backlog
master 和 slave 都会保存一个 replica offset 还有一个 master run id
根据run id来 定位 master node
如果 master 和 slave 网络连接断掉了,
slave 会让 master 从上次 replica offset 开始继续复制
slave 会让 master 从上次 replica offset 开始继续复制
没有找到对应的 offset,那么就会执行一次 resynchronization
redis 的哨兵原理能介绍一下么?
介绍
sentinel,中文名哨兵
功能
集群监控
负责监控 redis master 和 slave 进程是否正常工作
消息通知
如果某个 redis 实例有故障,
那么哨兵负责发送消息 作为报警通知 给管理员
那么哨兵负责发送消息 作为报警通知 给管理员
故障转移
如果 master node 挂掉了,会自动转移到 slave node 上
选举一个slave node为master node
配置中心
如果故障转移发生了,通知 client 客户端新的 master 地址
核心知识
哨兵至少需要 3 个实例,来保证自己的健壮性
经典的 3 节点哨兵集群
需要 >=majority(majority=voters/2+1),
也就是大多数哨兵都是运行的,才能执行故障转移
也就是大多数哨兵都是运行的,才能执行故障转移
哨兵 + redis主从的部署架构,是不会保证数据零丢失的,只能保证redis集群的高可用性
故障转移时,判断一个master node是宕机了,需要大部分的哨兵都同意才行,
涉及到了分布式选举的问题
涉及到了分布式选举的问题
重要原理
sdown 和 odown 转换机制
sdown
主观宕机
一个哨兵认为master宕机了
odown
客观宕机
quorum数量的哨兵认为master宕机
哨兵集群的自动发现机制
通过 redis 的 pub/sub 系统实现
每个哨兵都会往 __sentinel__:hello 这个 channel 里发送一个消息
所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在
slave 配置的自动纠正
slave->master 选举算法
quorum 和 majority
configuration epoch
configuration 传播
问题
redis 哨兵主备切换的数据丢失问题
情况
异步复制导致的数据丢失
master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,
master 就宕机了,此时这部分数据就丢失了
master 就宕机了,此时这部分数据就丢失了
脑裂导致的数据丢失
脑裂
某个 master无法正常连接,其实还运行着,只是暂时网络问题。哨兵以为master宕机,
重新选举出一个新master,这个时候旧master又恢复正常,有了两个master
重新选举出一个新master,这个时候旧master又恢复正常,有了两个master
客户端还和旧master连接,发送数据。但旧master成为新的master的slave,
由于主从复制,首次全量复制,会丢失新发送的数据
由于主从复制,首次全量复制,会丢失新发送的数据
解决办法
min-slaves-to-write 1
至少有 1 个 slave
min-slaves-max-lag 10
数据复制和同步的延迟都超过了 10 秒钟,master 就不会再接收任何请求
减少异步复制数据的丢失
减少脑裂的数据丢失
Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?
持久化机制具体底层是如何实现的?
持久化机制具体底层是如何实现的?
两种持久化方式
RDB持久化机制
每隔一段时间生成一次全量的数据快照
特点
优点
重启恢复速度快
对redis读写操作影响很小
让子进程执行磁盘 IO 操作来进行 RDB 持久化
缺点
数据文件过大会导致redis读写服务暂停
会丢失一定量的数据
AOF持久化机制
对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中
在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建数据集
特点
优点
丢失数据较少
一般 AOF 会每隔 1 秒,通过一个后台线程执行一次fsync操作,
最多丢失 1 秒钟的数据
最多丢失 1 秒钟的数据
AOF 日志文件以 append-only 模式写入
写入性能非常高,而且文件不容易破损
日志文件即使过大的时候,出现后台重写操作,
也不会影响客户端的读写
也不会影响客户端的读写
因为在 rewrite log 的时候,会对其中的指令进行压缩,
创建出一份需要恢复数据的最小日志出来
创建出一份需要恢复数据的最小日志出来
缺点
容易有 bug
支持的写 QPS 会比 RDB 支持的写 QPS 低
AOF 日志文件通常比 RDB 数据快照文件更大
了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?
系统该如何应对这种情况?如何处理 Redis 的穿透?
系统该如何应对这种情况?如何处理 Redis 的穿透?
缓存雪崩
缓存服务崩溃或宕机,所有请求都落到数据库,数据库扛不住挂掉
解决方案
事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃
事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
缓存穿透
黑客恶意攻击,缓存中查不到,数据库也查不到,也就不会添加缓存,请求每次都“视缓存于无物”
解决方法
从数据库中只要没查到,就写一个空值到缓存里去
下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
缓存击穿
某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,
当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库
当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库
解决方法
将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,
等待第一个请求构建完缓存之后,再释放锁
等待第一个请求构建完缓存之后,再释放锁
如何保证缓存与数据库的双写一致性?
问题场景
缓存与数据库双存储双写,就一定会有数据一致性的问题
使用场景
你的系统严格要求 “缓存+数据库” 必须保持一致性
串行化可以保证一定不会出现不一致的情况,
但是它也会导致系统的吞吐量大幅度降低,
用比正常情况下多几倍的机器去支撑线上的一个请求。
但是它也会导致系统的吞吐量大幅度降低,
用比正常情况下多几倍的机器去支撑线上的一个请求。
解决方法
最初级的,常用
读的时候,先读缓存,缓存没有的话,就读数据库,
然后取出数据后放入缓存,同时返回响应
然后取出数据后放入缓存,同时返回响应
更新的时候,先更新数据库,然后再删除缓存
为什么删除缓存而不是更新
因为如果短时间大量更新请求,每次都要更新。
但是缓存很少被读,如果你只是删除的话,缓存开销会大幅度降低
但是缓存很少被读,如果你只是删除的话,缓存开销会大幅度降低
高并发解决
读请求和写请求串行化,串到一个内存队列里去
最初级的缓存不一致问题及解决方案
问题
先更新数据库,再删除缓存。
如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据
数据就出现了不一致。
解决
先删除缓存,再更新数据库。
比较复杂的数据不一致问题分析
高并发场景
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改
一个请求过来,去读缓存,发现缓存空了,去查询数据库,
查到了修改前的旧数据,放到了缓存中
查到了修改前的旧数据,放到了缓存中
随后数据变更的程序完成了数据库的修改,数据库和缓存中的数据不一样了
解决
更新数据的时候,将相同唯一标识的数据操作放入一个队列中执行
一个队列对应一个工作线程,这样就能保证一个数据读写顺序
读超时的问题
可能数据更新很频繁,导致队列中积压了大量更新操作在里面,
然后读请求会发生大量的超时
然后读请求会发生大量的超时
Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?
场景
多客户端同时并发写一个 key,可能本来应该先到的数据后到了,导致数据版本错了
解决
从 mysql 查出来的时候,带上时间戳
每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新
如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据
生产环境中的 Redis 是怎么部署的?
redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务
每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s
机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存
5 台机器对外提供读写,一共有 50g 内存
每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移
商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。
200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%
目前高峰期每秒就是 3500 左右的请求量
分布式系统
核心理念
CAP
C一致性
A可用性
P分区容错性
BASE
强调系统的可用性和可伸缩性,而不是强一致性
提供了一种在可用性和最终一致性之间进行平衡的设计思路
BASE模型可以看作是CAP模型中选择AP模型的一种具体实现方式
负载均衡和故障转移
解决方案
NGINX
优点
高性能:NGINX 以其高并发处理能力和低资源占用著称,适用于高流量网站和应用。
灵活性:支持多种负载均衡算法(如轮询、最小连接数、IP 哈希等)和丰富的配置选项。
反向代理功能:除了负载均衡,NGINX 还能作为反向代理、静态文件服务器和缓存服务器。
模块化设计:支持各种第三方模块,功能扩展性强。
广泛应用:有丰富的社区资源和文档,易于获取支持和解决方案。
灵活性:支持多种负载均衡算法(如轮询、最小连接数、IP 哈希等)和丰富的配置选项。
反向代理功能:除了负载均衡,NGINX 还能作为反向代理、静态文件服务器和缓存服务器。
模块化设计:支持各种第三方模块,功能扩展性强。
广泛应用:有丰富的社区资源和文档,易于获取支持和解决方案。
缺点
复杂性:配置文件较为复杂,需要一定的学习曲线。
缺少原生健康检查:虽然可以通过配置进行健康检查,但没有 HAProxy 那样灵活和强大。
缺少原生健康检查:虽然可以通过配置进行健康检查,但没有 HAProxy 那样灵活和强大。
HAProxy
优点
高性能:同样以高性能和低资源消耗著称,适用于高并发场景。
灵活性:提供多种负载均衡算法和高级的健康检查功能,支持 TCP 和 HTTP 多层负载均衡。
透明代理:支持透明代理模式,可以保留客户端的真实 IP 地址。
详细监控:内置监控界面,提供丰富的统计信息,便于管理和调试。
稳定性:多年发展积累了大量的生产环境经验和优化,稳定性高。
灵活性:提供多种负载均衡算法和高级的健康检查功能,支持 TCP 和 HTTP 多层负载均衡。
透明代理:支持透明代理模式,可以保留客户端的真实 IP 地址。
详细监控:内置监控界面,提供丰富的统计信息,便于管理和调试。
稳定性:多年发展积累了大量的生产环境经验和优化,稳定性高。
缺点
配置复杂:需要较高的配置技巧,配置文件较为复杂。
功能单一:专注于负载均衡和代理,不具备 NGINX 那样丰富的功能(如反向代理和静态文件服务)
功能单一:专注于负载均衡和代理,不具备 NGINX 那样丰富的功能(如反向代理和静态文件服务)
各种云服务提供商提供的方案
缺点
收费
分布式系统中数据的存储、分布、复制
数据分片
hash分片
一致性hash分片
单一热点问题
范围分片
解决单一热点问题
数据复制
数据一致性
分布式锁
问题
可用性问题
死锁问题
脑裂问题
一个锁被两个线程同时持有
解决方案
redission
分布式事务
事务(ACID特性)
原子性
所有操作要么全部完成,要么全部撤销
一致性
事务执行前后,数据库都处于合法状态,保证数据的完整性和正确性
隔离性
不受其他事务影响
持久性
一旦事务提交,其结果应该永久保存在数据库中
解决方案
二阶段提交(2PC)
一种用于分布式事务处理的协议,旨在确保分布式系统中所有参与者要么全部提交事务,要么全部回滚事务,以保证事务的原子性
2PC 协议包含两个阶段
准备阶段
在准备阶段,协调者向所有参与者发送准备请求,并等待所有参与者的响应。只有在收到所有参与者的响应后,协调者才会决定提交或回滚事务,并向所有参与者发送相应的请求。期间,协调者处于阻塞状态,无法处理其他请求
提交阶段
在提交阶段,协调者向所有参与者发送提交请求,并等待所有参与者的响应。只有在收到所有参与者的提交成功响应后,协调者才会确认事务提交。如果有任何一个参与者未能提交事务,协调者将向所有参与者发送回滚请求。期间,协调者同样处于阻塞状态,无法处理其他请求。
总结
2PC 的同步阻塞协议保证了事务的原子性,但也存在一些问题,如单点故障、阻塞和超时等,因此在实际应用中需要权衡考虑,并结合其他机制来提高系统的性能和可靠性。
图解
三阶段提交(3PC)
二阶段提交(2PC)的改进版本
组成
准备阶段
协调者向所有参与者发送 CanCommit 请求,询问它们是否可以提交事务。参与者在收到请求后,会检查自己是否可以执行事务,并在准备好后向协调者发送 Ready 或 Abort 响应。如果所有参与者都准备好了,则协调者会向所有参与者发送 DoCommit 请求,否则会向所有参与者发送 DoAbort 请求。
预提交阶段
协调者收到所有参与者的 Ready 响应后,会向所有参与者发送 PreCommit 请求,告知它们即将提交事务。参与者收到 PreCommit 请求后,会进入预提交状态,并在事务提交前执行一些准备工作,如持久化日志。如果有任何一个参与者在超时时间内未响应或者发送了 Abort 响应,则协调者会向所有参与者发送 DoAbort 请求。
提交阶段
在收到所有参与者的 Ack 响应后,协调者会向所有参与者发送 DoCommit 请求,要求它们正式提交事务。参与者收到请求后,会执行事务提交操作,并在提交成功后向协调者发送 Ack 响应。协调者收到所有参与者的 Ack 响应后,确认事务提交,并向所有参与者发送确认消息。如果有任何一个参与者在超时时间内未响应,则协调者会向所有参与者发送 DoAbort 请求。
图解
补偿事务(TCC)
一种分布式事务模式,旨在解决分布式系统中的事务一致性问题。
组成
尝试
确认
取消
图解
目前流行解决方案实例
Seata分布式事务框架
XA(两阶段提交协议)
AT(自动事务)
TCC(尝试-确认-取消)
SAGA(长事务)
jvm
问题
JVM内存管理机制:有哪些区域,每个区域做了什么
内存区
程序计数器
虚拟机栈
本地方法栈
堆
方法区
JVM垃圾回收机制:垃圾回收算法 垃圾回收器 垃圾回收策略
判断对象已死的算法
引用计数法
可达性分析法
垃圾回收算法
标记-清除
复制
标记-整理
分代收集算法
类加载机制的步骤,每一步做了什么,static和final修改的成员变量的加载时机
什么是类加载机制
类加载流程
加载
链接
验证
准备
解析
初始化
使用
卸载
类加载与初始化时机
类加载时机
类初始化时机
mysql数据库
概念
分区
分区类型
range分区
list分区
hash分区
key分区
索引
索引种类
树
b+树
hash
sql优化
sql语句优化
不要在 where 子句中的让索引列参加运算
模糊查询,字符首位为%,不使用索引
-如果条件中有or,即使其中有条件带索引也不会使用
只要列中包含有NULL值,都将不会被包含在索引中
where的查询条件 不使用NOT IN 、<>、!=操作
in 和 not in 也要慎用,否则会导致全表扫描
很多时候用 exists 代替 in 是一个好的选择:
尽量使用数字型字段(对于数字型而言只需要比较一次就够了)
不要使用 select * from t ,用具体的字段列表代替“*”
尽量使用多表连接(join)查询(避免子查询)
优化思路
通过explain分析低效sql的执行过程
通过show profile分析sql,了解时间都耗费到哪去了
事务
隔离级别
读未提交
读已提交
可重复读
可串行化
一组操作,要么全执行,要么全不执行
数据库优化实战
分表与分库使用场景以及设计方式
分表
作用
分表能够解决单表数据量过大带来的查询效率下降的问题
分表策略
分库
并发量大,数据库写压力大
分库策略
分库分表
分库分表的路由策略
分库详细策略
mysql 读写分离
设计模式
概念
单例模式
实现方式
饿汉模式
懒汉模式
双重校验锁
静态内部类
枚举模式
项目中使用
数据库连接对象
属性配置文件的读取对象
抽象工厂模式
创建者模式
命令模式
外观模式
策略模式
观察者模式
装饰者模式
适配器模式
代理模式
动态代理和静态代理
静态代理
每个代理类只能为一个接口服务
动态代理
通过一个代理类完成全部的代理功能
什么时候使用动态代理
日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理。
设计模式实战
java基础
集合框架
HashMap
概括
HashMap在初始化时,初始容量与装载因子默认为16和0.75f
工作原理
问题
重新调整HashMap大小存在什么问题
并发的HashMap为什么会引起死循环?
HashMap,LinkedHashMap,TreeMap,ConcurrentHashMap,WeakReferenceHashMap
LinkedHashMap
元素的有序存储
TreeMap
ConcurrentHashMap
1.7
为什么Set、List、map不实现Cloneable和Serializable接口
Concurrenthashmap的实现,1.7和1.8的实现
1.7
size实现
1.8
size实现
Arrays.sort的实现
什么时候使用CopyOnArrayList
优点
java反射机制
优点
可以实现动态创建对象和编译
动态绑定
java网络编程
计算机基础
http协议
超文本传输协议
基于TCP/IP进行数据传输
HTTP的十字诀
无连接
每次链接只处理一个请求
无状态
http它本身就是一个无状态协议,对于后续需要前面的相关信息就会需要重新传送
媒体独立
只要服务端和客户端知道处理数据的方式,那么任何类型的数据都能通过http发送
HTTP的请求方法
get请求
在数据传输方面,get传输的数据量小,因为受url长度限制,但效率较高
从安全性方面来看,get相对来说不安全,因为url是可见的
post请求
post可以传输大量数据,所以上传文件时只能用post方式
tcp/ip 协议
三次握手
发送请求
返回响应
传输数据
四次挥手
网络协议
UDP
面向无连接,数据不安全,速度快。不区分客户端与服务端。
TCP
面向连接(三次握手),数据安全,速度略低。分为客户端和服务端。
三次握手: 客户端先向服务端发起请求, 服务端响应请求, 传输数据
三次握手: 客户端先向服务端发起请求, 服务端响应请求, 传输数据
thread
情景
某事件内大量重复执行某动作,为了提高效率,节省时间,使用多线程
大量目标执行同一事件
概念
线程安全
java中线程安全的体现
多thread对同一java实例的访问不相互干扰,主要体现在synchronized关键字
每个线程都有自己的字段,而线程之间不共享,主要体现在ThreadLocal类
ThreadLocal
hash冲突
总结
线程的生命周期
竞态条件
volatile
volatile
共享变量操作的实际过程
保证读取数据时只从主存空间读取,修改数据直接修改到主存空间中去,
这样就保证了这个变量对多个操作线程的可见性
这样就保证了这个变量对多个操作线程的可见性
作用
volatile关键字的作用是:保证变量的可见性
被volatile修饰的变量,能保证该变量的 单次读或者单次写 操作是原子的。
volatile和synchronized
volatile轻量级,只能修饰变量;synchronized重量级,还可修饰方法。
volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞。
ReentantLock
第一、等待可中断
第二、公平锁
第三、锁绑定多个条件
第二、公平锁
第三、锁绑定多个条件
锁
公平锁
保证线程安全
乐观锁
具体实现细节
冲突检测
数据更新
乐观锁是一种思想。CAS是这种思想的一种实现方式
悲观锁
AQS
AQS基于状态的标识以及FIFO(先进先出)等待队列方面的工作原理
CAS
操作
ABA问题
死锁
产生死锁的四个必要条件
原子操作
原子操作是不可分割的操作,一个原子操作中间是不会被其他线程打断的,所以不需要同步一个原子操作
对long和double类型的单次读写操作并不是原子的,注意使用volitile使他们成为原子操作
JDK5中的并发包
CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。
两个方法countDown()和await()
两个方法countDown()和await()
实现最大的并行性
开始执行前等待n个线程完成各自任务
死锁检测
CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
死锁与活锁的区别,死锁与饥饿的区别
问题解决
解决生产者——消费者问题
BlockingQueue阻塞队列方法
用wait() / notify()方法
用Lock的多Condition方法
为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
Java中你怎样唤醒一个阻塞的线程?
会抛出InterruptedException的方法:wait、sleep、join、Lock.lockInterruptibly等,针对这类方法,我们在线程内部处理好异常(要不完全内部处理,要不把这个异常抛出去),然后就可以实现唤醒。
不会抛InterruptedException的方法:Socket的I/O,同步I/O,Lock.lock等。对于I/O类型,我们可以关闭他们底层的通道,比如Socket的I/O,关闭底层套接字,然后抛出异常处理就好了;比如同步I/O,关闭底层Channel然后处理异常。
对于Lock.lock方法,我们可以改造成Lock.lockInterruptibly方法去实现。
Java中阻塞队列的几种实现方式
在多线程环境中遇到的常见的问题是什么
多线程和并发程序中常遇到的有Memory-interface、竞争条件、死锁、活锁和饥饿
线程使用
异步交互
方法超时控制
SqlAnalyzerImpl
CallableExecSqlWorker
AsynUpdateRunnable
线程池
线程池入门
概念
不可重入锁和可重入锁
可重入锁
只锁住当前工作线程的对象
不可重入锁
锁住所有需要获取锁的线程对象
阻塞队列
实现类
ArrayBlockingQueue
一锁两条件
LinkedBlockingQueue
两锁两条件(一锁new一个)
添加和获取是两个不同的锁,所以并发添加/获取效率更高些
Executors类里面提供了一些静态工厂
newSingleThreadExecutor
newFixedThreadPool
newCachedThreadPool
newScheduledThreadPool
使用线程池的过程
1、设置关键参数
核心
参数介绍
2、根据处理的任务类型选择不同的阻塞队列
要求高吞吐量的,可以使用 SynchronousQueue 队列
对执行顺序有要求,可以使用 PriorityBlockingQueue
最大积攒的待做任务有上限,LinkedBlockingQueue
3、创建自己的 ThreadFactory
4、然后就可以创建线程池了
主要方法
两种提交任务的方法(ExecutorService提供)
execute():提交不需要返回值的任务
submit():提交需要返回值的任务
关闭线程池
shutdown()
将线程池的状态设置为 SHUTDOWN,然后中断所有 没有执行 的线程,无法再添加线程。
shutdownNow()
将线程池设置为 STOP,然后尝试停止 所有线程,并返回等待执行任务的列表
总结
锁
java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁
ReenTrantLock可重入锁(和synchronized的区别)总结
ReenTrantLock
可以指定是公平锁还是非公平锁
提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程
能够中断等待锁的线程的机制,通过lock.lockInterruptibly()
synchronized
是非公平锁(随机唤醒等待的线程)
随机唤醒一个线程要么唤醒全部线程
高并发案例场景
0 条评论
下一页