java技术点整理

Dubbo: 2.7.X
1.Dubbo配置优先级: jvm -> xml -> properties
2.xml中配置覆盖优先级:
a. 方法级优先,接口级次之,全局配置再次之
b. 如果级别一样,则消费方优先,提供方次之
3.重要配置介绍
a. 超时时间: 默认1秒
b. 重试次数: 默认2次,不包含第一次调用,幂等性操作才配置(查询,删除,更新)
c. 多版本: version, 实现灰度发布
d. 本地存根: stub, 调用服务之前,做一些操作(参数校验)
4.高可用
a. zookeeper宕机与dubbo直连
现象: zookeeper注册中心宕机,还可以消费dubbo暴露的服务
原因: 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
b. 负载均衡机制
随机方式+权重(默认)
轮询方式+权重
最少活跃数
一致性哈希
c. 服务降级
屏蔽(force): return+null
容错(fail): return+null
d. 集群容错: hystrix, sentinel
5.通信方式: netty
6.架构设计
a.整体架构
config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
b.标签解析
DubboNamespaceHandler--init(): 初始化对应的标签class对象
DubboBeanDefinitionParser--parse(): 根据对应的beanClass做相应的处理
c.服务暴露
解析配置文件
创建dubbo标签解析器
解析dubbo标签
serviceBean解析
容器创建完成,触发ContextRefreshedEvent
暴露服务: export() -> doExport() -> doExportUrls() -> doExportUrlsFor1Protocol()
getInvoker()
protocol.export(wrapperInvoker)
openServer(url): new NettyServer
ProviderConsumerRegTable.registerProvider()
d.服务引用
ReferenceBean ---> FactoryBean.getObject() -> get() -> init()
createProxy()
refer(): 引用远程服务
getClients(): 连接获取客户端 new NettyClient
doRefer(): 订阅服务
ProviderConsumerRegTable.registerConsumer
返回invoker
e.服务调用
Proxy: ProxyFactory
Filter: mock,cache
Invoker: Cluster(failover,failfast)
LoadBalance: random
Filter: count, monitor
Invoker: Protocol(dubbo)
Client: Transporter(netty)
7.Dubbo中的Invoker概念了解吗?
Invoker是Dubbo领域模型中非常重要的一个概念,是Dubbo对远程调用的抽象,Invoker会屏蔽掉远程调用的细节
8.Dubbo的SPI机制了解吗? 如何扩展Dubbo中的默认实现
SPI机制被大量用在开源项目中,它可以帮助我们动态寻找服务/功能的实现,
原理:我们将接口的实现类放在配置文件中,我们在程序运行过程中读取配置文件,通过反射加载实现类。这样,我们可以在运行的时候,动态替换接口的实现类。
扩展:创建对应的实现类,将实现类的路径写入到resources目录下的具体文件中
9.Dubbo支持哪些序列化方式
JDK自带的序列化,hessian2(默认), JSON, Kryo, FST, Protostuff, ProtoBuf

Spring5:
1.IOC
概念: 控制反转,把对象创建和对象之间的调用过程,交给spring进行管理(为了降低耦合度)
实现:
BeanFactory: spring内部使用接口,加载配置文件时,不创建对象,使用时才创建
ApplicationContext: BeanFactory的子接口,开发人员使用,加载配置文件时,创建对象
Bean管理:
spring创建对象
spring注入属性
bean的作用域: scope属性值
singleton(单例,默认)
prototype(多例,getBean创建对象)
request(一次request 一个实例)
session(当前session内有效)
global Session(全局http session有效)
bean的生命周期
1. InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()
2. bean实例化(构造函数)**********
3. InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()
4. InstantiationAwareBeanPostProcessor.postProcessProperties()
5. bean设置属性值 **********
6. Aware相关接口设置(beanName, beanFactory, ApplicationContext)
7. BeanPostProcessor.postProcessBeforeInitialization()
8. InitializingBean.afterPropertiesSet()
9. init-method属性配置的初始化方法 **********
10. BeanPostProcessor.postProcessAfterInitialization()
11. 返回bean给用户,并存入缓存池
12. DisposableBean.destroy()
13. 调用destroy-method属性配置的销毁方法 **********
自动装配: autowire属性值
1. no: 不进行自动装配,显示设置ref
2. byName: 通过参数名自动装配
3. byType: 通过参数类型自动装配
4. constructor: 类似于byType,需提供给构造器参数
5. autodetect: 先使用constructor来自动装配,如果无法工作,则使用byType方式
创建对象注解
@Component
@Repository
@Service
@Contoller
属性注入注解
@value: 普通类型
@Autowired
@Qualifier: 指定名称
@Resource: 注解如果名字没有配对成功,会按照类型进行注入
2.AOP(AspectJ)
常用术语
a. 连接点: 类中可以被增强的方法
b. 切入点: 实际被增强的方法
1. execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
2. execution(* com.shizhuang.dupp.*(..))
3. @annotation(注解全路径)
c. 通知: 实际增强的逻辑部分(前置,后置,环绕,异常,最终)
通知顺序: 环绕通知前->前置通知->方法执行->环绕通知后->后置通知->最终通知
代理优先级: @Order(1) 加载顺序不受影响,只影响程序的执行顺序
d. 切面: 把通知应用到切入点的过程
JDK动态代理:
a. 实现InvocationHandler接口
b. Proxy.newProxyInstance
CGLIB动态代理
3.事务(底层使用AOP)
概念: 数据库操作的基本单元,逻辑上一组操作(转账)
方式: 编程式事务管理,声明式事务管理
注解:@Transactional
事务传播行为:propagation
1. never:方法不应该运行在事务中,有则抛异常
2. required:有事务就在事务中运行,无则启动新事务
3. required_new:必须启动新事务,有则挂起
4. supports:有事务就在事务中运行,无则不在事务中运行
5. not _support:不在事务中运行,有则挂起
6. mandatory:必须运行在事务中,无则抛异常
7. nested:有事务就在事务的嵌套事务中运行,无则启动新事务
事务隔离级别
脏读:一个未提交事务读取另一个未提交事务数据
不可重复读:一个未提交事务读取另一个已提交事务数据(update)
幻读:一个未提交事务读取另一个已提交事务数据(insert,delete)
1. 读未提交
2. 读已提交
3. 可重复读
4. 串行化
4.spring中经典的9种设计模式
1.简单工厂
BeanFactory
2.工厂方法
FactoryBean
3.单例模式

4.适配器模式
HandlerAdatper
5.装饰器模式
Wrapper,Decorator
6.代理模式
Aop底层
7.观察者模式:Observable
spring的事件驱动模型
8.策略模式
Resource
9.模板方法模式
IOC初始化容器
10. 原型模式
原型bean的创建

5.SpringWebflux
a.异步非阻塞
b.响应式编程(reactor):观察者模式(Observer,Observable)
c.Reactor: 两个核心类,实现Publisher
1.Mono: 实现发布者,返回n个元素
2.Flux: 实现发布者,返回0或者1个元素
3.数据信号:元素值,错误信号,完成信号
d.核心控制器:DispatcherHandler 实现 WebHandler
HandlerMapping: 请求查询到处理的方法
HandlerAdapter: 真正请求处理
HandlerResultHandler: 响应结果处理
e.重要接口:RouterFunction(路由处理)和HandlerFunction(函数处理)


面试官:”Spring是如何解决的循环依赖?“

答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

面试官:”为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?“

答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

Spring AOP和AspectJ AOP有什么区别?
Spring AOP属于运行时增强,而AspectJ是编译时增强
Spring AOP基于代理, 而AspectJ基于字节码操作
当切面太多的话,最好选择AspectJ, 会快很多
Spring管理事务的方式
编程式事务,在代码中硬编码
声明式事务,在配置文件中配置
基于XML的声明式事务
基于注解的声明式事务
@Component和@Bean的区别是什么?
作用对象不同:@Component注解作用于类,而@Bean注解作用于方法
@Component是自动装配到Spring容器中,@Bean是在标有该注解的方法中定义产生这个bean
@Bean注解比@Component注解的自定义性更强

Nacos: 1.4.x
1.配置管理
四大特性
1.服务发现与服务健康检查
2.动态配置管理
3.动态DNS服务
4.服务和元数据管理
常用概念
Namespace: 环境
Group: 项目
Data id: 工程
配置的优先级
1.通过内部相关规则(应用名、扩展名)
2.通过ext-config[n]方式扩展,n的值越大,优先级越高
3.通过shared-dataids支持多个Data Id的配置
2.服务发现
负载均衡
1.服务端负责均衡: nginx
2.客户端负责均衡: ribbon


SpringCloud-Gateway
1.网关解决了什么问题
统一接入
高性能、高并发
负载均衡、容灾切换
流量控制
服务降级
熔断
协议适配
前端(http)、后端(rpc)
长短链接支持
安全防护
黑名单
风控
2.网关应当具备的功能
性能:api高可用、负载均衡、容错机制
安全:权限身份验证、脱敏、流量清洗、后端签名、黑名单
日志:全链路
缓存:数据缓存
监控:记录请求响应数据、api耗时分析、性能监控
限流:流量控制、错峰流控
灰度:线上灰度部署
路由:动态路由规划
3.Gateway工作原理
客户端向Spring Cloud Gateway发出请求,再由网关处理程序Gateway Handler Mapping
映射确定与请求相匹配的路由,将其发送到网关Web处理程序Gateway Web Handler,该处理程序通过
指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。pre过滤器(之前)、post过滤器(返回)
4.路由规则
Path: 请求路径
Query: 请求参数,值可以使用正则匹配
Method: 请求方式
Datetime: 指定时间之前,之后,之间
RemoteAddr: 远程地址
Header: 指定请求头
动态获取URI: lb://根据服务名称从注册中心获取服务请求地址
服务名称转发:
discovery - locator
enabled(true): 开启基于服务发现的路由规则
lower-case-service-id(true): 是否将服务名称转小写
5.过滤器
Path路径过滤器: 实现URL重写,添加指定前缀,剥离路径个数,路径模板参数
参数过滤器
状态过滤器
全局过滤器(统一鉴权)
6.网关限流
计数器算法: 临界问题,资源浪费
漏桶算法: 队列机制
令牌桶算法: 恒定的速度生成令牌(redis+lua)
限流、降级、熔断


Sentinel 1.8.x
1.简介
Sentinel的核心骨架是ProcessorSlotChain,其将不同的Slot按照顺序串在一起,
系统会为每个资源创建一套SlotChain。
2.slot介绍
NodeSelectorSlot: 负责收集资源的路径,并将这些资源的调用路径以树状结构存储起来
ClusterBuilderSlot: 用于存储资源的统计信息和调用者信息
StatisticSlot: 用于记录、统计不同维度的runtime指标监控信息
3.Node间的关系
Node: 用于完成数据统计的接口
StatisticNode: 统计节点,是Node接口的实现类,用于完成数据统计
EntranceNode: 入口节点,一个Context会有一个,用于统计当前Context的总体流量数据
DefaultNode: 默认节点,用于统计一个资源在当前Context中的流量数据
ClusterNode: 集群节点,用于统计一个资源在所有Context中的总体流量数据
4.源码分析流程
1.创建资源操作对象
SentinelResourceAspect: 切面为SentinelResource注解,环绕通知
entryWithPriority: 资源对象,统计值(默认1),是否优先级
从ThreadLocal中获取context, 一个线程绑定一个context
若context是NullContext类型,即访问请求的数量已经超出阈值,返回无需校验的资源操作对象
若当前线程没有context,创建一个context("sentinel_default_context")放入ThreadLocal
如全局开关是关闭的,返回无需校验的资源操作对象
2.创建context
从ThreadLocal中再获取一次
ThreadLocal中不存在,双重检测从缓存(context名称,EntranceNode)中获取
缓存超过阈值,返回NULL_CONTEXT
缓存不超过阈值,创建EntranceNode,并加到root和缓存中
3.获取chain(单向链表)
双重检测从缓存中获取chain,没有则新建
没有SlotChainBuilder则通过Spi方式创建
通过Spi方式填充ProcessorSlot
4.slot执行流程
NodeSelectorSlot: 双重检测获取或者创建DefaultNode,并添加到调用树中
StatisticSlot:
调用SlotChain中后续的所有slot(会抛异常)
通过则增加线程数和通过的请求数
DegradeSlot:
异常熔断器,响应时间熔断器
如果熔断器关闭,直接通过
如果开启,时间大于下个窗口开始时间且是CAS半开启状态成功,再次判断
5.时间窗口
固定时间窗口:跨时间窗口的长度大于阈值
滑动时间窗口:重复计算问题(新增固定样本窗口)
窗口实现:ArrayMetric - LeapArray<MetricBucket> - AtomicReferenceArray<WindowWrap<T>>
WindowWrap代表样本窗口对象,MetricBucket是样本窗口数据,MetricEvent枚举6种类型数据通过数组保存
获取窗口: 根据样本窗口id 和 窗口起始时间获取样本窗口(新建,当前,清空复用)
6.降级、熔断、限流
限流:控制某段时间内请求总量(计数器,滑动窗口,漏桶,令牌桶)
熔断:一般是指依赖的外部接口出现故障的情况,断绝和外部接口的关系
降级:保证核心功能的可用性,选择性的降低一些功能的可用性,或者直接关闭该功能

Seata分布式事务(读未提交)
CAP理论: Consistency(一致性), Availability(可用性), Partition tolerance(分区容错性)
CA: 单点的传统关系型数据库mysql、oracle
CP: 保证分区容错和一致性zk、redis、mongoDB、Hbase
AP: 保证分区容错和可用性CoachDB, 秒杀

BASE理论
BA: 基本可用
S: 柔性状态,运行数据同步存在延迟
E: 最终一致性
二段提交协议
投票阶段: undo和redo日志写入, 不提交事务
提交阶段: 提交事务操作,成功返回ack
三段提交协议
Can-Commit: 是否可以执行
Pre-Commit: undo和redo日志写入, 不提交事务
Do-Commit: 提交事务操作,成功返回ack
第三阶段,如果协调者无法通信,参与者也会提交事务
分布式事务解决方案
TCC: 事务补偿(2pc)
全局消息
基于可靠消息服务的分布式事务
最大努力通知
Seata术语
TC: 事务协调者,维护全局和分支事务的状态,驱动全局事务提交或回滚
TM: 事务管理器,定义全局事务范围,开始、提交或回滚全局事务
RM: 资源管理器,管理分支事务处理的资源,驱动分支事务提交或回滚
AT模式
TC相关的表解析
global_table: 全局事务,每当有一个全局事务发起后,就会在该表中记录全局事务的ID
branch_table: 分支事务,记录每一个分支事务的ID,分支事务操作的数据库等信息
lock_table: 全局锁
for update操作在未获取到数据的时候,mysql不进行锁
获取到数据的时候,进行对约束字段进行判断,存在有索引的字段则进行行锁,否则进行表锁
当使用< > like等关键字时,进行for update操作时,mysql进行的是表锁

源码分析:spi方式装配,@GlobalTransactional
入口: GlobalTransactionScanner, 继承AbstractAutoProxyCreator, 实现InitializingBean
重写父类wrapIfNecessary()
创建GlobalTransactionalInterceptor
对象执行invoke()
执行handleGlobalTransaction()
TransactionalTemplate.execute()
开启事务:底层通过netty调用seata客户端开启事务,并通过session方式,获取xid,最终通过jdbc方式写入到数据库
执行业务:写入undo_log日志,beforeImg, afterImg
提交事务

RocketMQ
1.MQ的用途:限流削峰,异步解耦,数据收集
2.MQ常见协议:JMQ, STOMP,AMQP,MQTT
3.路由发现模型:push模型,pull模型,Long Polling模型
4.topic的创建模式: 集群模式,broker模式
5.读/写队列的目的: 方便topic的Queue的缩容
6.broker集群模式:Master配置RAID10磁盘阵列,然后再为其配置一个slave
7.磁盘阵列RAID
技术:镜像,数据条带(自动将I/O操作负载均衡到多个物理磁盘上的技术),数据校验
目的:高性能,高可靠,容错能力,扩展性
分类:软RAID(功能有操作系统和CPU来完成), 硬RAID(控制处理芯片,I/O处理芯片,阵列缓冲)和混合RAID(有控制处理芯片)
等级:RAID10(先做条带,再做镜像,容错率更高),RAID01(先做镜像,再做条带)
8.JBOD: 磁盘簇/磁盘柜,表示一个没有控制软件提供协调控制的磁盘集合
9.配置文件参数
brokerClusterName: 整个broker集群的名称
brokerName: master-slave集群的名称
brokerId:master的brokerId为0
deleteWhen: 删除过期文件的时刻
fileReservedTime: 为发生更新的消息存储文件的保留时长
brokerRole: 复制策略
flushDiskType: 刷盘策略
namesrvAddr: Name Server的地址
10.重要概念
路由表:是一个Map,key是topic名称,value是一个QueueData实例(一个broker的所有该topic的queue)列表
broker列表:是一个map,key是brokerName, value是BrokerData(master-slave集群)
11.Queue选择算法:针对无序消息,轮询算法,最小投递延迟算法
12.store目录简介
abort: 该文件在broker启动后会自动创建,正常关闭broker,该文件会自动消失
checkpoint: 储存着commitlog,consumequeue,index文件的最后刷盘时间戳
commitlog: mappedFile文件(小于等于1G)
a.消息在broker中存放时,并没有按照topic进行分类存放
b.消息单元中包含queue相关属性
config: 存放着broker运行期间的一些配置数据
consumequeue: consumequeue文件是commitlog的索引文件
a.每个文件包含30W个索引条目
b.索引条目: commitlog Offset, 消息长度,消息Tag的hashCode值
c.三个属性固定20个字节,文件大小30W*20字节
index:根据key进行消息查询功能(时间戳命名)
a.索引条目
1.indexHeader(40字节,第一条和最后一条消息存储时间,在commitlog中的偏移量,已填充index的slot数量,索引个数: 2000W)
2.slot槽(500W)
3.indexes索引数据
b.index索引单元(20个字节)
1.keyHash
2.phyOffset
3.timeDiff
4.preIndexNo
lock: 运行期间使用到的全局资源锁
13.性能提升:对文件的读写操作是通过mmap零拷贝,数据是顺序存放的(引入了PageCache的预读取机制)
14.Rebalance机制
目的:提升消息的并行消费能力
危害:消费暂停,重复消费,消费突刺
过程:每个consumer实例根据queue分配算法,自主完成
TopicConfigManager: key是topic名称,value是topicConfig(topic中所有Queue的数据)
ConsumerManager: key是Consumer Group Id, value是ConsumerGroupId(Group中所有的Consumer实例数据)
ConsumerOffsetManager: key为Topic@Group的组合,value是Map(key:QueueId,value: offset)
15.Queue分配算法
a.平均分配策略
b.环形平均策略
c.一致性hash策略
d.同机房策略
16.重试队列:key是%RETRY%开头的ConsumerOffsetManager
17.消息的清理: 默认过期时间72小时
a.文件过期,凌晨4点清理
b.文件过期,磁盘空间占有率75%
c.最老的文件,磁盘空间占有率85%
d.拒绝消息写入, 磁盘空间占有率90%
18.延时消息:消息写入到broker后,在指定的时长后才可被消费的消息
a.如果有延迟等级
b.修改消息的topic为SCHEDULE_TOPIC_XXXX
c.根据等级delayLevel创建对应的queueId目录与consumequeue文件
d.索引单元,消息Tag的hashCode值修改为消息的投递时间(存储时间+延时时间)
e.queue中的消息是按照投递时间排序的
f.broker内部有个延迟消息服务类ScheuleMessageService,会消费修改消息的topic为SCHEDULE_TOPIC_XXXX中的消息
g.将原来的延迟等级设置为0,再次投递到目标Topic中
19.事务消息(XA模式)
半事务消息:不能被消费者看到
三剑客:TC(Broker), TM(Producer), RM(Broker和Producer)
不支持延时消息
要做好幂等性检查

20.消息过滤:Tag过滤,SQL过滤(默认关闭)
21.消息重试:顺序消息没有重试机制
默认重试次数:16(2个小时)
处理:通过延时消息实现
消费:消费者会默认订阅重试队列
22.死信队列:重试失败的消息
Topic:%DLQ%consumerGroup@consumerGroup


Netty:4.1.x
1.介绍
a.netty是由JBOSS提供的一个java开源框架
b.netty是一个异步的,基于事件驱动的网络应用框架
c.netty主要针对在TCP协议下,面向Clients端的高并发应用
d.netty本质是一个NIO框架
2.I/O模型:
a.BIO: 同步并阻塞
b.NIO: 同步非阻塞(channel,buffer,selector)
c.AIO: 异步非阻塞
3.buffer属性:mark, position, limit, capacity
4.channel: FileChannel, ServerSocketChannel, SocketChannel, DatagramChannel(用于UDP的数据读写)
5.MappedByteBuffer: DirectByteBuffer是它的子类,可让文件直接在堆外内存修改,操作系统无需拷贝一次
6.Selector:socketChannel注册后返回SelectionKey
监听方法
a.Selector.select() 阻塞
b.Selector.select(1000) 阻塞1000毫秒
c.Selector.wakeUp 唤醒Selector
d.Selector.selectNow 不阻塞
事件分类
a.OP_ACCEPT 16
b.OP_CONNECT 8
c.OP_WRITE 4
d.OP_READ 1
7.零拷贝: DMA(直接内存访问)-> SG-DMA
早期I/O操作,内存与磁盘的数据传输工作都是由CPU完成的,而此时CPU不能执行其他任务
mmap(内存映射)+write:cpu把内核缓冲区的数据拷贝到socket缓冲区,4次上下文切换
sendFile:2次上下文调用,3次数据copy
pageCache: 缓存最近被访问的数据,预读功能
传输大文件:
异步IO+直接IO
不能使用零拷贝:由于PageCache被大文件占据,导致热点小文件无法利用到PageCache
减少了两次上下文切换和数据拷贝次数
8.Reactor模式:
reactor: 监听事件,分发
handler: 处理事件
9.Netty工作原理
1.两组线程池
BossGroup: 负责接收客户端的连接
WorkerGroup: 负责网络的读写
2.两组线程池类型都是NioEventLoopGroup
3.NioEventLoopGroup: 事件循环组,事件循环是NioEventLoop(线程)
4.BossGroup的NioEventLoop
轮询accept事件
处理事件,与client建立连接
生成NioSocketChannel并注册到WorkerGroup的NioEventLoop上的selector
处理任务队列的任务
5.WorkerGroup的NioEventLoop
轮询read,write事件
处理i/o事件
处理任务队列的任务
6.每个Worker NioEventLoop处理业务时,会使用pipeline(管道),管道维护了很多处理器(handler)和 channel
10.NioEventLoop的taskQueue作用,用来异步处理耗时比较久的任务, scheduledTaskQueue用来处理定时任务
11.ChannelFuture(异步模型)
12.ChannelPipeline维护的双向链表的元素是ChannelHandlerContext(里面包含具体handler)
13.Unpooled: 操作缓冲区(Netty的数据容器)的工具类
copiedBuffer: 返回一个ByteBuf对象
ByteBuf: 不需要使用flip进行反转(维护了 readerIndex和writerIndex)
14.IdleStateHandler: netty提供的处理空闲状态的处理器
readerIdleTime: 多长时间没有读,发送一个心跳检测
WriterIdleTime: 多长时间没有写,发送一个心跳检测
allIdleTime: 多长时间没有读写,发送一个心跳检测
15.Handler类中的userEventTriggered()方法可以接收上一个handler传递过来的事件
16.Protobuf: Google发布的开源项目
结构化数据存储
结构化数据串行化
跨平台,跨语言
使用message管理数据
17. MessageToByteEncoder: 编码器
编码前会对类型校验: acceptOutBoundMessage(msg), 不通过则直接发送下个节点
18.tcp粘包拆包解决:自定义协议+编解码器


线程池相关
1.只要创建的总线程数 >= maximumPoolSize 的时候,线程池就不会继续执行任务了而会去执行拒绝策略的逻辑
2.线程池运行状态-runState
RUNNING:接收新任务,并且也能处理阻塞队列中的任务。
SHUTDOWN:不接收新任务,但是却可以继续处理阻塞队列中的任务。
STOP:不接收新任务,同时也不处理队列任务,并且中断正在进行的任务。
TIDYING:所有任务都已终止,workercount(有效线程数)为0,线程转向 TIDYING 状态将会运行 terminated() 钩子方法
TERMINATED:terminated() 方法调用完成后变成此状态。
3.一个变量维护两个值:运行状态(runState)和线程数量 (workerCount)
高3位代表运行状态(runState ),而低29位代表工作线程数(workerCount)
4.线程池整体流程
a.任务的非空校验
b.核心线程数校验
c.线程池状态校验(2次),添加队列,池中没有线程,创建一个新的线程(不传任务)
d.队列满了,创建新的线程,失败则执行拒绝策略
5.addWorker(Runnable firstTask, boolean core)流程
a.最外层for循环是不断校验当前的线程池状态是否能接受新任务
b.检查工作线程数,CAS增加线程数成功跳出循环(break retry;), 否则继续判断线程池状态
c.创建Worker, 加锁判断线程池状态, 将 worker 实例添加进线程池,修改largestPoolSize(出现过的最大线程数)
d.线程添加成功,执行start,添加失败,线程池移除worker实例,CAS将工作线程数减1
6.Worker类详情:ThreadPoolExecutor的一个内部类,继承AQS(实现独占锁), 实现Runnable
a.firstTask不为空立即执行任务,为空则去执行队列中的任务(while条件判断)
b.runWorker(Worker w)方法逻辑
1.allowCoreThreadTimeOut 为true允许核心线程关闭
2.获取任务-getTask, 如果允许核心线程关闭,通过poll超时拉取, 否则通过take阻塞拉取

设计模式:
1.设计模式的七大原则
a.开闭原则:对扩展开放,对修改关闭
b.里氏替换原则: 所有引用基类的地方必须能透明地使用其子类的对象
c.依赖倒置原则: 上层模块依赖抽象, 细节依赖抽象
d.接口隔离原则: 客户端不应该依赖它不需要的接口
e.迪米特法则(最少知道原则):只与你的直接朋友交谈,不跟“陌生人”说话。
f.单一职责原则: 一个类应该只负责一项职责
g.合成复用原则: 尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的
2.常用的设计模式
a.单例模式:饿汉式,懒汉式,双重校验,静态内部类,枚举
JDK: Runtime类(饿汉式)
b.工厂模式
简单工厂:工厂类直接创建对象(JDK, Calendar类)
工厂方法:工厂类抽象创建对象方法,工厂子类实现创建逻辑
c.抽象工厂模式
区别:工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个
d.原型模式
实现Cloneable接口
默认clone()方法(浅拷贝)
深拷贝:重写clone()方法,序列化(输出,输入流)
e.建造者模式
Product: 最终要生成的对象
Builder: 构建者的抽象基类
ConcreteBuilder: Builder的实现类
Director: 决定如何构建最终产品的算法(构造函数传入builder的实现类对象)
f.适配器模式(SpringMVC中的HandlerAdapter):讲一个类的接口转换成客户希望的另外一个接口
类适配器:Java是单继承机制,要求适配对象必须是接口
对象适配器:适配器聚合被适配对象
接口适配器:设计一个抽象类实现接口,空方法, 抽象类的子类可有选择覆盖实现需求
g.装饰者模式:动态的将新功能附加到对象上(IO源码 FilterInputStream)
被装饰者和装饰者基类都实现了ICoffee接口
装饰者基类:实现ICoffee接口, 并持有一个ICoffee引用
装饰者子类:继承装饰者基类
h.代理模式
静态代理:代理对象和目标对象实现同一个接口
动态代理:Proxy.newProxyInstance(目标对象类加载器,目标对象接口,InvocationHandler)
Cglib代理: 通过字节码处理框架ASM来转换字节码并生成新的类
实现MethodInterceptor接口
重写intercept方法实现拦截
i.策略模式(Arrays的Comparator)
使用者通过聚合策略接口
传入具体策略,使用策略方法
j.模板方法模式
定义抽象类,模板方法固定代码执行逻辑,定义final
定义钩子方法,执行可选步骤
k.观察者模式
Subject接口:注册,移除,通知Observer方法
Observer接口:更新数据方法
l.责任链模式:为请求创建了一个接收者对象的链
定义抽象类,包含处理方法, 组合抽象类对象
3.UML类的关系
a.依赖:通过方法参数传入
b.关联:1对多
c.继承
d.实现
e.聚合:成员变量,通过set方法传递
f.组合:成员变量,通过构造函数new


JUC
1.使用栈和队列时,优先ArrayDeque(循环数组), 次选LinkedList
2.优先队列(最小堆),添加时,添加到末尾,再向上调整, 删除时,删除第一个,末尾补位,再向下调整
3.Map集合
HashMap:
添加元素:头插法(1.7), 尾插法(1.8)
死循环出现条件:
JDK1.7版本
多线程put数据(addEntry)
触发扩容操作(resize)
移动数据阶段(transfer)
挂起代码行:Entry<K,V> next = e.next;
查询循环链表中不存在的元素
还存在丢失数据问题
LinkedHashMap实现LRU
accessOrder 访问顺序
removeEldestEntry() 返回true
TreeMap: 红黑树实现
1.每个节点要么是黑色,要么是红色
2.根节点必须是黑色
3.红色节点不能连续
4.从某个节点出发的所有路径,黑色个数相同
5.所有的叶子节点都是空节点, 并且是黑色的
4.线程
线程状态
新建(New)
可运行(Runnable)
阻塞(Blocking)
无限期等待(Waiting)
限期等待(Timed Waiting)
死亡(Terminated)
线程使用方式
实现Runnable接口
实现Callable接口
继承Thread类
守护线程:Daemon, 垃圾回收线程
sleep():让出cpu资源,不会释放锁资源
wait(): 让出cpu资源和锁资源
sleep()和yield()是Thread类的静态方法
join()是由线程对象来调用
wait(),notify(),notifyAll()是Object的方法
yield()只是使线程回到可执行状态
说说进程和线程的区别?
进程是程序的一次执行,是系统进行资源分配和调度的独立单位,作用是程序能并发执行提高资源利用率和吞吐率
因为进程的创建、销毁、切换产生大量的时间和空间的开销,进程的数量不能太多。
线程是比进程更小的能独立运行的基本单元,是进程的一个实体,可以减少程序并发执行时的时间和空间的开销,使操作系统具有更好的并发性。

5.synchronized(原子类内置锁, 监视器锁, 同步代码块前后加上monitorenter和monitorexit字节码指令)
synchronized同步锁的四种状态
无锁:当有一个线程访问同步块时升级成偏向锁
偏向锁:有锁竞争时升级为轻量级锁
轻量级锁:自旋十次失败,升级为重量级锁
重量级锁:
synchronized用的锁是存在Java对象头里(Mark Word)
Contention List: 所有请求锁的线程将被首先放置到该竞争队列
Entry List: Contention List中那些有资格成为候选人的线程被移到Entry List
Wait Set: 那些调用wait方法被阻塞的线程被放置到Wait Set
monitorexit退出两次,第一次正常同步代码块结束释放锁,第二次是防止同步代码块出现异常

6.happens-before
程序顺序规则
监视器锁规则:加锁先发生于解锁
volatile变量原则:对一个volatitle域的写先发生于后续对这个volatile域的读
传递性
start()原则:子线程能够看到主线程在启动子线程前的操作
join()原则

7.volatile
内存屏障(禁止处理器重排序)
StoreStore屏障 volatile写 StoreLoad屏障
volatile读 LoadLoad屏障 LoadStore屏障

8.final
对final类型的类扩展: 使用组合
final方法是可以被重载的
不是所有的final修饰的字段都是编译期常量,只是在初始化后无法被修改
final修饰的字段可以在构造器中赋值(static同时修饰的除外)
写final域的重排序可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了
读final域的重排序可以确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用

9.原子类
ABA问题解决:
AtomicStampedReference(版本号)
AtomicMarkableReference(boolean类型标记是否修改)

10.LockSupport
Condition.await()底层是调用LockSupport.park()来实现阻塞
LockSupport.park() 不会释放锁资源
LockSupport先调用unpark,再调用park也能实现同步
先调用notify,后调用wait会出现阻塞

11.AbstractQueuedSynchronizer
内部维护一个state状态位,尝试加锁的时候,通过CAS修改值
AQS是一个用来构建锁和同步器的框架
使用AQS能简单且高效地构建出应用广泛的大量的同步器
两种资源获取方式:独占,共享
AQS中的队列是CLH变体的虚拟双向队列
addWaiter就是一个在双端链表添加尾结点
双端链表的头结点是一个无参构造函数的头结点
跳出当前循环的条件是“当前置节点是头结点,且当前线程获取锁成功”
判断前置节点的状态(waitStatus为-1)来决定是否将当前线程挂起

12. ReentrantLock实现了Lock接口
默认使用非公平锁
Sync、NonfairSync、FairSync三个内部类

13. ReentrantReadWriteLock
ReentrantReadWriteLock实现了ReadWriteLock接口
锁降级指的是写锁降级成为读锁

14. ConcurrentHashMap
HashTable : 使用了synchronized关键字对put等操作进行加锁
ConcurrentHashMap JDK1.7: 使用分段锁机制实现
ConcurrentHashMap JDK1.8: 则使用数组+链表+红黑树数据结构和CAS原子操作实现

15. FutureTask:获取任务执行结果(get)和取消任务(cancel)

16. CountDownLatch
使用场景:电商的详情页,由众多的数据拼接组成,并发获取数据,并封装数据
await的调用会转发为对Sync的acquireSharedInterruptibly(从AQS继承的方法)方法的调用
主要方法:await(),countDown()

17. CyclicBarrier:ReentrantLock和Condition的组合使用
使用场景:用于多线程计算数据,最后合并计算结果的场景
CountDownLatch减计数,CyclicBarrier加计数
CountDownLatch是一次性的,CyclicBarrier可以重用
CountDownLatch的下一步的动作实施者是主线程,具有不可重复性;而CyclicBarrier的下一步动作实施者还是“其他线程”本身,具有往复多次实施动作的特点

18. Semaphore(信号量):通常用于那些资源有明确访问数量限制的场景,常用于限流
令牌没有重入的概念。你只要调用一次acquire方法,就需要有一个令牌才能继续运行
release会添加令牌,并不会以初始化的大小为准
Semaphore中release方法的调用并没有限制要在acquire后调用

19. Exchanger
用于进行两个线程之间的数据交换

20. ThreadLocal
ThreadLocal是一个将在多线程中为每一个线程创建单独的变量副本的类
多个线程互相不可见,因此多线程操作该变量不必加锁,适合不同线程使用不同变量值的场景
ThreadLocal被提到应用最多的是session管理和数据库链接管理
每个Thread维护了ThreadLocal.ThreadLocalMap对象,对象中包含的以ThreadLocal为key的entry
ThreadLocal对象是采用弱引用, value变量值本身是通过强引用引入
在代码逻辑中使用完ThreadLocal,都要调用remove方法,及时清理
ThreadLocal一般加static修饰,同时要遵循第一条及时清理
使用场景:用来保存session, 数据库连接


JVM
1.类字节码
class文件本质上是一个以8位字节为基础单位的二进制流
每个class文件的头4个字节成为魔数(cafe babe),用于确定文件是一个class文件
2.类加载机制
加载:
1.获取二进制字节流
2.转化方法区的运行时数据结构
3.Java堆中生成这个类的对象
验证:
文件格式验证,元数据验证,字节码验证,符号引用验证(不是必须的)
准备:
为类的静态变量分配内存(方法区),并将其初始化为默认值
解析:
把类中的符号引用转换为直接引用
初始化:
为类的静态变量赋予正确的初始值
只有当对类的主动使用的时候才会导致类的初始化

类加载器:
启动类加载器(Bootstrap ClassLoade),无法被Java程序员直接引用
扩展类加载器(Extension ClassLoader)
应用程序类加载器(Application ClassLoade)
自定义类加载器

全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入
缓存机制:缓存机制将会保证所有加载过的Class都会被缓存
双亲委派机制

双亲委派优势:
1.系统类防止内存汇总出现多份同样的字节码
2.保证Java程序安全稳定运行

3.JVM内存结构
线程私有:程序计数器、虚拟机栈、本地方法区
线程共享:堆、方法区、堆外内存

程序计数器
1.线程私有,记录JVM字节码指令地址
2.唯一一个在JVM规范中没有规定任何 OOM 情况的区域
虚拟机栈
1.栈不存在垃圾回收问题
2.栈中的数据是以栈帧的格式存在
3.栈帧中存储着:局部变量表、操作数栈、动态链接、方法返回地址、一些附加信息
本地方法区
与Java环境外交互、与操作系统交互
堆内存
1.新生代、老年代、元空间
2.-Xms设置初始内存、-Xmx设置最大内存, 通常-Xms和-Xmx配置相同的值,目的是垃圾回收完不再重新分隔计算堆的大小,提高性能
3.TLAB(快速分配策略),避免线程安全问题,提升内存分配的吞吐量
4.逃逸分析:栈上分配(成员变量赋值,方法返回值,实例引用传递)、标量替换、锁消除
方法区
1.用来存储类信息、常量池、静态变量、编译器编译后的代码缓存

4.java内存模型
重排序:编译器重排序、处理器重排序
happens-before 仅仅要求前一个操作(执行的结果)对后一个操作可见
as-if-serial 语义保证不管怎么重排序,单线程程序的 执行结果不能被改变

5.垃圾回收
a.判断一个对象是否可被回收
1.引用计数算法
2.可达性分析算法
b.引用类型:强引用,软引用,弱引用,虚引用
c.垃圾回收算法
1.标记-清除
2.标记-整理
3.复制
4.分代收集(新生代使用复制算法, 老年代使用标记清除/整理)
d.垃圾收集器
1.Serial收集器(串行,单线程)
2.ParNew收集器(串行,多线程)
3.Parallel Scavenge收集器(串行,多线程,吞吐量优先)
4.Serial Old收集器
5.Parallel Old收集器
6.CMS收集器(初始标记,并发标记,重新标记,并发清除)
7.G1收集器(初始标记,并发标记,最终标记,筛选回收)
每个Region都有一个Remembered Set, 用来记录该Region对象的引用对象所在的Region
整体来看是基于”标记-整理“算法实现的,从局部看是基于”复制“算法实现的
e.内存分配策略
1.对象优先在Eden分配
2.大对象直接进入老年代
3.长期存活的对象进入老年代
4.动态对象年龄判定
5.空间分配担保:老年代的最大可用连续空间是否大于历次晋升到老年代的对象的平均大小
f.Full GC的触发条件
1.调用System.gc()
2.老年代空间不足
3.空间分配担保失败
4.JDK1.7及以前的永久代空间不足
5.Concurrent Mode Failure

6.垃圾回收器G1
默认将整堆划分为2048个分区
每个分区内部分成若干大小为512Byte的卡片
并发标记算法(三色标记法)
G1的收集都是根据收集集合(CSet)进行操作的
产生漏标的原因:黑色对象指向了白色对象,灰色对象指向白色对象的引用消失
7.JVM参数调优
-Xms,-Xmx(最小,最大堆内存,一般两个值相等)
-Xmn(新生代大小,一般为整个堆的1/4或者1/3)
-Xss(堆栈大小,默认为1M)
-XX:NewRatio(新生代与老年代比值)
-XX:MaxPermSize(持久代最大值 64M)
-XX:MaxTenuringThreshold(新生代对象存活次数 15)
8.垃圾回收机制
minor gc: 年轻代发生的
full gc: 老年代的连续空间小于新生代对象的总大小(担保机制)
永久代(Java虚拟机): 不属于堆内存,存放jvm运行时需要的类, full gc也会回收永久代
元空间(代替永久代, 使用本地内存)
9.Java OOM分析
Java堆内存溢出:
配置-XX:+HeapDumpOutofMemoryErorr
分析dump文件的堆栈信息
10.Java线程Dump分析: jstack [-l ] <pid> | tee -a jstack.log(获取ThreadDump)
CPU飙高,load高,响应很慢
1.一个请求过程中多次dump
2.对比多次dump文件的runnable线程,观察是否在执行同一个方法
查找占用CPU最多的线程
1.使用命令:top -H -p pid,找到导致CPU高的线程ID
2.在thread dump中,查找对应的线程堆栈信息
CPU使用率不高但是响应很慢
进行dump,查看是否有很多线程struct在了i/o、数据库等地方
请求无法响应
多次dump, 对比是否所有的runnable线程都一直在执行相同的方法
11.Java问题排查:Linux命令
文本查找 - grep (-i 忽略大小写, -c 统计匹配到的行数)
文本分析 - awk (NR, FNR, NF)
文本处理 - sed (文本打印、替换、插入、删除)
网络接口属性 - ifconfig
防火墙设置 - iptables -L
路由表 - route -n
监听端口 - netstat -lntp
内存使用 - free -m
分区使用情况 - df -h
指定目录大小 - du -sh
12.Java问题排查:工具单
jps: 查看当前Java进程(原理Java程序在启动后会在java.io.tmpdir目录下生成一个临时文件夹,展示的就是文件夹里文件名)
jstack: jdk自带的线程堆栈分析工具
jinfo: 查看正在运行的Java应用程序的扩展参数
jmap: 可以生成java程序的dump文件(jmap -dump:live,format=b,file=/tmp/heap2.bin 2815)
jstat: jstat -gcutil 2815 1000

btrace:
1.监控谁调用了方法
2.监控方法被调用的返回值和请求参数
Greys:
sc -df xxx: 输出当前类的详情,包括源码位置和classloader结构
trace class method: 打印出当前方法调用的耗时情况
Arthas: 是基于Greys
javOSize: classes 通过修改了字节码,改变了类的内容,即时生效
JProfiler:
13.评判GC的两个核心指标
延迟:最大停顿时间,越短越好
吞吐量:程序执行时间占系统总运行时间的百分比
一次停顿的时间不超过应用服务的TP9999, GC的吞吐量不小于99.99%
14.9中常见的CMS GC问题分析与解决
1.动态扩容引起的空间动荡
现象:刚启动时GC次数较多,经历一次GC,堆内各个空间的大小会被调整
解决:尽量将成对出现的空间大小配置参数设置成固定的,如 -Xms 和 -Xmx
2.MetaSpace区OOM
现象:MetaSpace 的已使用大小在持续增长,同时每次 GC 也无法释放,调大 MetaSpace 空间也无法彻底解决
解决:基本都集中在反射、Javasisit 字节码增强、CGLIB 动态代理、OSGi 自定义类加载器等的技术点上,及时给 MetaSpace 区的使用率加一个监控
3.过早晋升
现象:分配速率接近于晋升速率,对象晋升年龄较小;Full GC比较频繁,经历过一次GC后Old区的变化比例非常大
解决:根据业务合理调整Young区和old比例,两次Young GC的时间间隔要大于Tp9999时间
4.CMS Old GC频繁
现象:Old区频繁的做CMS GC
解决:分析Dump文件, 分析Dump Diff, 分析Leak Suspects, 分析Top Component, 分析Unreachable
5.单次CMS Old GC耗时长
现象:CMS GC单次STW最大超过1000ms
解决:大部分问题都出现在Final Remark过程,基本都集中在Reference和Class等元数据处理上,Reference方面分析dump快照(Socket的SocksSocketImpl, Jersey 的 ClientRuntime、MySQL 的 ConnectionImpl),Class方面关闭类卸载开关
6.内存碎片&收集器退化
现象:并发的CMS GC算法,退化为Foreground单线程串行GC模式
解决:
1.内存碎片,配置-XX:UseCMSCompactAtFullCollection=true 来控制 Full GC的过程中是否进行空间的整理
以及 -XX:CMSFullGCsBeforeCompaction=n 来控制多少次 Full GC 后进行一次压缩
2.增量收集:降低触发CMS GC的阈值,-XX:CMSInitiatingOccupancyFraction,让CMS GC尽早执行
3.浮动垃圾:缩短每次CMS GC的时间,必要时可调节NewRatio的值
7.堆外内存OOM
现象:内存使用率不断上升
解决:主动申请的堆外内存及时释放,通过JNI调用的Native Code申请的内存及时释放
8.JNI引发的GC问题
现象:出现GCLocker Initiated GC
解决:慎用JNI

Redis
1.Redis的数据结构
字符串String(动态字符串SDS, 空间预分配,惰性空间释放,二进制安全)
字典Hash
列表List
集合Set
有序集合SortedSet
HyperLogLog: 能够提供不精确的去重计数(伯努利实验),可以用来统计UV
Geospatial: 实时经纬度,geohash技术就是将经纬度转换成12个字符的字符串
Pub/Sub:发布订阅
bitmap: 本质是String,512MB, 基数小的时候浪费空间
RoaringBitmap:
.高16位作为索引,低16位存储数据,最多有2^16个块,每个块最多存2^16个数据
.ArrayContainer, BitmapContainer, RunContainer(通过runOptimize方法装换)
BloomFilter:检索一个元素是否在一个集合中(redis4.0通过module形式使用)
Pipeline: 可以批量执行一组指令,一次性返回全部结果
Lua:Redis支持提交Lua脚本来执行一系列的功能(类型Redis事务)
2.常见问题实现
.分布式锁使用set方法,并设置过期时间
.使用keys指令扫出指定模式的key列表(keys会阻塞服务, scan无阻塞)
.使用list作为队列,rpush生产消息, lpop消费消息, blpop阻塞到消息到来
.pub/sub主题订阅模式,可以实现1:N的消息队列(消费者下线的情况,生产的消息会丢失,使用专业的消息队列)
.sortedSet实现延时队列,时间戳作为score, 消息内容作为key调用zadd生产消息,zrangebyscore获取数据
.RDB做镜像全量持久化,AOF做增量持久化,AOF日志sync属性控制同步频次
.RDB的原理是:fork和cow, fork是通过创建子线程来进行RDB操作,cow指的是 copy on write
.PipeLine可以将多次IO往返的时间缩减为一次
.Redis可以使用主从同步,从从同步,第一次同步,主节点做一次bgsave,并同时将后续操作记录到内存buffer,RDB文件全量同步到复制节点,
复制节点加载完成数据后再同步期间的修改操作,后续的增量数据通过AOF日志同步即可。
.Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master
.Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储
3.Bloom Filter分析
原理:当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把他们置为1, 如果这些点有任何一个0,被检元素一定不在
应用:爬虫过滤,垃圾邮件过滤
4.Redis哨兵、持久化、主从
.哨兵集群sentinel:哨兵必须要用三个实例去保证健壮性
.Redis的过期策略,是有定期删除+惰性删除两种
.内存淘汰机制LRU
.主从模式下,当主服务器宕机后,需要人工干预,还会造成一段时间服务不可用(内存可用性较低,较难支持在线扩容)
.哨兵模式下,哨兵会自动选举master并将其他的slave指向新的master。 哨兵是一个独立的进程
.当哨兵监测到master宕机,会自动将slave切换到master,通过发布订阅模式通知其他从服务器,修改配置文件,切换主机
.集群模式(redis3.0):实现redis的分布式存储,对数据进行分片, 插槽(0-16383),CRC16算法
.如果master1和它的从节点slave1都宕机了,整个集群就会进入fail状态,因为集群的slot映射不完整。
如果集群超过半数以上的master挂掉,无论是否有slave,集群都会进入fail状态。
.一致性hash算法的Java实现是使用TreeMap
5.Redis双写一致性、并发竞争、线程模型
.最经典的缓存+数据库读写模式
先读缓存,没有的话读数据库,然后取出放缓存,返回响应
更新的时候,先更新数据库,然后再删除缓存
.Redis和Memcached有啥区别
Redis支持复杂的数据结构
Redis原生支持集群模式
存储小数据Redis性能高,存储100k以上大数据Memcached性能高
.Redis的线程模型
Redis内部使用文件事件处理器file event handler, 是单线程的。采用IO多路复用机制同时监听多个Socket
文件事件处理器的结构
.多个Socket
.IO多路复用程序
.文件事件分派器
.事件处理器(链接应答处理器、命令请求处理器、命令回复处理器)
6.Memcache优缺点
.处理请求时使用多线程异步IO的方式,可以合理使用CPU多核的优势
.key不能超过250个字节
.value不能超过1M字节
.key的最大失效时间是30天
.只支持k-v结构,不提供持久化和主从同步功能
7.Redis分布式锁-红锁
set分布式锁(2.6.12以后)问题:业务执行时间大于锁超时时间,被其他线程提前获取锁或者删锁,不可重入, 无法自动续期
解决:value设置UUID随机数,删除锁之前先进行判断(Lua脚本)
问题:请求一个分布式锁的时候,成功了,但是slave还没复制我们的锁,master挂掉了,再请求锁的时候,也会成功。同一个锁获取了不止一次。
原理:Redisson
.获取当前的时间
.使用相同的key和随机值在N个节点上请求锁
.只有大多数节点获取到了锁,且总的获取时间小于锁的超时时间情况下,锁获取成功
.如果锁获取成功了,锁的超时时间是最初的超时时间减去获取锁的总耗时时间
.如果获取锁失败了,将都已经设置了key的master上的key删除
.通过看门狗检查并续期(开启Watch Dog机制必须使用默认的加锁时间30秒, 自定义时间不会自动续期)

8.秒杀系统设计
问题:高并发,超卖,恶意请求,链接暴露,数据库
解决:服务单一职责,秒杀链接加盐,Redis集群,Nginx, 资源静态化,按钮控制,限流,风控,库存预热, 限流&降级&熔断&隔离,削峰填谷,分布式事务
9.缓存必问三个问题
缓存穿透:访问数据库不存在的数据(参数校验,空数据缓存,布隆过滤器)
缓存击穿:热点key过期,设置热点key永不过期
缓存雪崩:同一时间点大量key过期,过期时间加随机数
10.跳表
每隔一个节点就提取出来一个元素到上层,把这一层称作为索引
构建跳表:N个节点构建跳表将需要logn层索引,包括自身那层链表层
11.分布式锁
利用setnx+expire命令:不具有原子性,会出现锁无法过期(Lua脚本来保证原子性)
利用set命令(2.6.12):value必须具有唯一性,释放锁时,对value进行验证

Mysql
1.索引
数据结构:hash(等值查询,只有KV的情况), 有序数组(静态数据), 二叉树(树会很高), B+(提高磁盘IO效率,范围查询效率,有序)
一个B+树的节点中到底存多少个元素最合适?
结论:B+树中一个节点为一页(16KB)或页的倍数最为合适(1200叉)
原理:mysql的基本数据结构是页,各个数据页组成一个双向链表, 数据页中的记录组成一个单向列表, 每个页有一个页目录,通过主键可以二分查找
回表:回到主键索引树搜索的过程(通过覆盖索引避免)
覆盖索引:一个查询语句的执行只从索引中就能取到,不必从数据表中读取,可以减少树的搜索次数
索引选择原则:最左前缀匹配原则,区分度高的列作为索引,索引不能参与计算,尽可能的扩展索引
聚集索引:以主键创建的索引
非聚集索引:除主键以外的索引
磁盘IO: 寻道,寻点,拷贝到内存(预读会把相邻的数据也读取到内存缓冲区中。索引目的尽量减少磁盘的IO操作)
全文索引:MyISAM支持,用于查找文本中的关键字,使用倒排索引实现
空间数据索引:用于地理数据存储,会从所有维度来索引数据
前缀索引:对于BLOB, TEXT和VARCHAR类型的列,必须使用前缀索引,只索引开始的部分字符(定义好长度,既节省空间,又不用增加太多查询成本)
索引下推:在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数
2.锁
MVCC版本控制
作用:用于实现读已提交和可重复读这两种隔离级别
基础概念:系统版本号(递增的数字),事务版本号(事务开始时的系统版本号)
Undo日志:MVCC的快照存储在Undo日志中,通过回滚指针把一个个数据行的所有快照连接起来
gap锁(锁左右间隙,开区间)
语句:SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
作用:保证可重复读的情况下不出现幻读
原理:锁住索引树的叶子节点之前的间隙,不让新的记录插入到间隙之中
Next-Key锁(前开后闭)
是Record锁和gap锁的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙
SELECT操作的不可重复读问题通过MVCC解决
UPDATE、DELECT的不可重复读通过Record锁解决
INSERT的不可重复读是通过Next-Key锁解决

锁问题
脏读:当前事务读取到另外事务未提交的数据
不可重复读:同一个事务内多次读取同一个数据集合,读取到的数据是不一样的情况
幻读:读到不存在的行(新插入的行)
丢失更新:一个事务的更新操作会被另一个事务的更新操作所覆盖
3.存储引擎
InnoDB: 支持事务,支持行级锁,支持外键,支持在线热备份
MyISAM: 不支持事务,支持表级锁,支持压缩表和空间数据索引
4.事务
原子性(A),一致性(C),隔离性(I),持久性(D)
隔离级别:读未提交(返回最新值),读已提交(每个sql开始时创建视图),可重复读(事务启动时创建视图,MVCC + Next-Key Locking防止幻读),序列化
5.查询性能优化
使用explain分析select查询语句,索引查询类型(system, const, ref, range, index, all)
优化数据查询:减少请求的数据量(只查询必要的列,只返回必要的行,缓存重复查询的数据),减少数据库扫描的行数
重构查询方式:做分页,分批查询
6.分库分表数据切分
水平切分:Sharding, 分区
垂直切分:分表,分库
7.主从复制
binlog线程:负责将主服务器上的数据更改写入二进制日志binlog中
I/O线程:负责从主服务器上读取二进制文件,并写入从服务器的中继日志Relay log
SQL线程:负责读取中继日志,解析已经执行的数据更改并在从服务器中重放
8.读写分离:主服务器处理写操作以及实时性要求比较高的读操作,从服务器处理读操作,代理方式实现
9.架构
server层
连接器:管理连接,权限验证
查询缓存:命中则直接返回结果
分析器:词法分析,语法分析
优化器:执行计划生成,索引选择
执行器:操作引擎,返回结果
存储引擎:InnoDB, MyISAM, Memory
10.重要的日志模块
.redo log(InnoDB,独有): 先写日志,再写磁盘, 4个文件共4G, write pos是当前记录要写的位置,checkpoint是当前要擦除的位置,保证数据库异常重启,之前提交记录不丢失(crash-safe)
.binlog(Server层,格式:statement(会出现主备不一致),row): 追加写, 事务执行过程中先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中
11.前缀索引重复太多解决:倒序存储,使用hash字段(不支持范围扫描)
12.统计数量效率 count(字段) < count(主键id) < count(1) ~ count(*)
13.常见使用不到索引的情况
.对索引字段做函数操作
.字段隐式类型转换
.字段隐式字符编码转换
14.GTID模式(复制方式)
通过GTID保证了每个在主库上提交的事务在集群中有一个唯一的ID,强化了数据库的主备一致性,故障恢复以及容错能力

单点登录:在多个系统中,用户只需要一次登录,各个系统即可感知该用户已经登录
登录功能抽取为一个系统
将用户信息存在Redis
输入网址后,期间发生了什么?
.解析url
.真实地址查询-DNS(本地DNS服务器,如果有缓存直接返回, 根域名服务器, 顶级域名服务器,权威域名服务器)
.建立TCP连接
.使用HTTP协议请求网页内容
.渲染
TCP三次握手的作用
.防止历史连接的建立
.减少双方不必要的资源开销
.帮助双方同步初始化序列号
TCP四次挥手,主动关闭连接的为什么需要TIME_WAIT状态?
防止具有相同四元组的旧数据包被收到
保证被动关闭连接的一方能被正确的关闭
TCP的重传机制、滑动窗口、流量控制、拥塞控制
重传机制:
超时重传:超时重传时间RTO的值应该略大于报文往返RTT的值
快速重传:当收到三个相同的ACK报文时,会在定时器过期之前,重传丢失的报文段
SACK方法:需要在TCP头部字段里加一个SACK的东西,它可以将缓存的地图发送给发送方
Duplicate SACK: 可以让发送方知道是发出去的包丢了,还是接收方回应的ACK包丢了
滑动窗口:
窗口大小:无需等待确认应答,而可以继续发送数据的最大值
方案:3个指针来跟踪在四个传输类别中的每一个类别中的字节
流量控制:让发送方根据接收方的实际接收能力控制发送的数据量
窗口关闭
拥塞控制:避免发送方的数据填满整个网络(拥塞窗口)
慢启动:发包个数是指数性的增长
拥塞避免:当拥塞窗口cwnd超过慢启动门限ssthresh(65535字节)就会进入拥塞避免算法, 每当收到一个ACK,cwnd增加1/cwnd
拥塞发生:超时重传(ssthresh设为cwnd/2, cwnd重置为1),快速重传(cwnd设为cwnd/2, ssthresh = cwnd)
快速恢复:cwnd = ssthresh+3, 重传丢失的数据包, 如果再收到重复的ACK, cwnd增加1, 如果收到新数据的ACK, cwnd = ssthresh
TCP头的格式:TCP是面向连接的,可靠的,基于字节流的传输层通信协议
序列号:在建立连接时由计算机生成的随机数作为其初始化值, 用来解决网路包乱序问题
确认应答号:用来解决不丢包问题
网络七层协议:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
Zookeeper投票规则:
当其他节点的记元比自己高投它
纪元相同比较自身的zxid的大小(提交事务最大的id)
如果epoch和zxid都相等,则比较服务的serverId, 最大的当选
Zookeeper是如何保证数据一致性的
通过ZAB原子广播协议来实现数据的最终顺序一致性
Kafka为什么那么快?
顺序写:partition划分为多个Segment, 每个Segment对应一个物理文件,Kafka对segment文件追加写
Memory Mapped Files: 直接利用操作系统的Page来实现文件到物理内存的映射
零拷贝:sendfile
批量与压缩:压缩算法lz4, snappy,gzip
分区并发:
文件结构:稀疏索引
Kafka如何保证消息队列不丢失
ACK机制:0 发送即算成功 1 leader接收并写入分区算成功 -1 所有同步副本接收并写入成功
分区副本
关闭unclean leader选举
面对百亿数据,Hbase为什么查询速度依然非常快
数据存储在Hbase集群上(多个数据节点,每个数据节点上有若干个Region)
根据主键Rowkey查询对应记录,Hbase的Master快速定位到记录所在的数据节点和节点中的Region
由于Hbase存储数据是按照列簇存储的,只查询对应的列簇数据
列簇底层包含多个HFile文件(100M)
每个HFile中,是以键值对方式存储,遍历文件中的key位置即可

网络性能指标
带宽:表示链路的最大传输速率,单位是b/s,带宽越大,其传输能力越强
延时:表示请求数据包发送以后,收到对端响应,所需要的时间延迟
吞吐率:表示单位时间内成功传输的数据量,单位是b/s
PPS:表示以网络包为单位的传输速率

Hbase规格配置
Master节点信息:8核32G, 2个节点
Core节点信息:8核32G, 8个节点, 单节点容量1520G

ClickHouse规格配置
规格:S104 104核384GB, 节点组个数:10, 磁盘空间 3000GB


排序算法 时间复杂度 空间复杂度 稳定性
插入排序 O(n^2) O(1) 稳定
冒泡排序 O(n^2) O(1) 稳定
选择排序 O(n^2) O(1) 不稳定
快速排序 O(nlog2n) O(log2n) 不稳定
堆排序 O(nlog2n) O(1) 不稳定
归并排序 O(nlog2n) O(n) 稳定

堆排序:把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出
归并排序:通过先递归分解数组,再合并数组

用户标签信息bitmap存储: 标签名,标签值,bitmap数据(user_id)
不使用ES的原因
新增或修改标签,不能实时进行,涉及到ES文档结构的变化
ES对资源消耗比较大,属于豪华型配置
ES的DSL语法对用户不太友好,用户学习成本高

人群接口服务,rt波动较大问题解决
jvm预热:类加载过程完毕后,所有需要的类会进入JVM cache, 这样就可以被快速的实时访问,当然,还有很多其他与JVM启动无关的类此时并未被加载
当应用的第一个请求到来,会触发逻辑相关类的第一次加载,会影响第一次调用的实时响应,这主要是因为JVM的懒加载及JIT机制(即时编译技术)
因此对于低延迟应用,必须采用特定的策略来处理第一次的预加载逻辑,以保障第一次请求的快速响应(改变consul健康检查路径,预热200次)

优化代码(日志缩减):
logback异步日志性能没有log4j2异步日志性能高,因为log4j2的异步日志有Disruptor环形无锁队列支持
不要使用System.currentTimeMillis()来获取时间, 使用虚拟时钟来进行时间获取SystemLocalTime.millisClock().now()
GC参数调整:hbase开启堆外内存,使用混合回收模式
undertow工作线程数增加:增加到2048


-XX:+UseG1GC 使用G1垃圾回收器
-XX:+UnlockExperimentalVMOptions 允许使用实验性参数(低版本jdk想使用高级参数)
-XX:G1MaxNewSizePercent=60 年轻代大小最大值的堆大小百分比
-XX:InitiatingHeapOccupancyPercent=30 触发标记周期的Java堆占用率阈值
-XX:MaxGCPauseMillis=200 为所需的最长暂停时间设置目标值
-XX:G1HeapRegionSize=16m 设置G1区域的大小
-XX:+PrintGCDetails 打印GC时的内存,并且在程序结束时打印堆内存使用情况
-XX:+PrintGCDateStamps 输出GC的时间戳
-XX:+PrintHeapAtGC 打印GC前后堆的概况
-XX:+PrintGCApplicationConcurrentTime 打印每次GC时程序运行的时间

日志系统:log4j2
让日志不影响系统性能的方式:
减少日志量
不在日志中做字符串拼接,而使用占位符的方式来拼接日志
将日志的记录方式从同步变成异步
log4j2三个组件
Logger: 在程序中用来记录日志的对象
Layout: 用来对日志信息进行格式化
Appender: 用来配置日志的展现形式(控制台打印,存储文件,数据库,消息队列)
log4j2只在Logger这块使用了队列(Disruptor),进行异步处理

Disruptor: 环形无锁内存队列
环形数组结构
元素位置定位(位运算)
无锁设计:每个生产者或者消费者线程,会先申请可以操作的元素在数组中的位置

 


事务一般指的是逻辑上的一组操作,或者单个逻辑单元执行的一系列操作。这些操作要么执行成功,要么执行失败。
分布式事务指将海量数据分散的存储到多台服务器的多台数据库中,同时要具备 ACID 特性。

C(Consistency)一致性:对所有的数据副本在进行增删查操作时,要么全部执行成功,要么全部执行失败。
A(Availability)可用性:指客户端访问数据的时候,能够快速得到响应。所有的请求都会被响应,不会存在响应超时或响应错误情况
P(Partition Tolerance)分区容忍性:一个节点挂掉后,不影响其他节点对外提供服务。

Base 理论是对 CAP 理论中 AP 理论的一个扩展,它通过牺牲强一致性来获得可用性。
Base 理论是 基本可用(Basically Available)、软状态(Soft State)、和最终一致性(Eventually Consistent)的缩写。当系统出现故障时,Base 理论允许部分数据不可用,但是会保证核心功能能用;允许数据在一段时间内不一致,但是经过一段时间,数据最终是一致的。

数据库隔离级别
未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读
串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

mybatis的一级缓存是默认开启的,它将数据存放在同一个SqlSession对象中。该对象可以粗略的将其理解为"封装好的数据库连接"。
mybatis的二级缓存是默认关闭的,如果想要使用二级缓存,我们需要在mybatis-config.xml文件中手动配置开启。二级缓存的数据缓存在同一个namespace生成的映射对象中

Full GC的触发条件
1.调用System.gc()
2.老年代空间不足
3.空间分配担保失败
4.JDK1.7及以前的永久代空间不足
5.Concurrent Mode Failure

Java String类为什么是final的?
1.为了实现字符串池
2.为了线程安全
3.为了实现String可以创建HashCode不可变性


JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个属性和方法
这种动态获取的信息以及动态调用对象的方法的功能称为 java语言的反射机制
原理:Java在编译之后会生成一个class文件,反射通过字节码文件找到其类中的方法和属性

内存泄露是指对象不再被应用使用,但是GC却不能将其从内存中释放
一种非常普遍的内存泄露场景是静态的域成员持有对象引用
一种非常普遍的内存泄露场景就是对要放入HashSet中的对象,缺少hashCode或者equals方法

(1)如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder(推荐使用)


Docker:是指容器化技术,用于创建和使用Linux容器
作用:借助docker,可将容器当做轻巧、模块化的虚拟机使用。同时,还将获得高度的灵活性,从而实现对容器的高效创建,部署和复制,并能将其从一个环境顺利迁移到另一个环境,从而有助于针对云来优化应用。
原理:docker技术使用Linux内核和内核功能来分隔进程,以便各进程相互独立运行。这种独立性正式采用容器的目的所在。

redis集群为什么有16384个槽?
1.如果槽位是65536,发送心跳信息的消息头达8k,过于庞大
2.redis的集群主节点数量基本不可能超过1000个
3.槽位越小,节点少的情况下,压缩比高

脑裂:同一个集群中的不同节点,对于集群的状态有了不一样的理解
哨兵模式造成的redis脑裂现象原因?
由于网络原因或者一些特殊原因,哨兵失去了对master节点器的感知,将会通过选举进行故障转移,将slave节点提升为master节点,这就导致了当前集群中有两个master,这就是脑裂现象的体现
解决方案:通过活跃slave节点数和数据同步延迟时间来限制master节点的写入操作

synchronized锁升级过程(无锁01,偏向锁01,轻量级锁00,重量级锁11)
特点:锁升级不能降级的策略,为了提高获得锁和释放锁的效率

三次握手与四次挥手
三次握手是为了建立可靠的数据传输通道,四次挥手则是为了保证等数据完全的被接收完再关闭连接
第一次握手:同步报文SYN=1, 随机序列号seq=x (确认服务端是否有接收和发送数据的能力)
第二次握手:同步报文SYN=1, ACK=1, seq=y, ack=x+1 (确认客户端是否有接收数据的能力)
第三次握手:ACK=1, seq=x+1, ack=y+1 (回复服务端自己有接收数据的能力)
大写的 ACK 表示报文的类型是确认报文,小写的 ack 是报文里面的确认号

第一次挥手:FIN=1, seq=u(客户端发起关闭连接的请求)
第二次挥手:ACK=1, seq=v, ack=u+1(服务端知道了客户端要关闭连接,数据还没传输完,需要等待)
第三次挥手:FIN=1,ACK=1,seq=w,ack=u+1(数据传输完了,服务端这边准备关闭连接)
第四次挥手:ACK=1, seq=u+1, ack=w+1(客户端知道服务端要关闭连接了,等一会)

为什么握手要三次,挥手却要四次呢?
因为握手的时候没有数据传输,所以服务端的SYN和ACK报文可以一起发送,挥手的时候有数据在传输,ACK和FIN报文不能同时发送

为什么客户端在第四次挥手以后还会等待2MSL?
等待2MSL是因为保证服务端接收到了ACK报文,如果服务端没接收到ACK报文的话,会重新发送FIN报文

AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。
AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。

Java中可以作为GC Roots的对象
1.虚拟机栈中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中引用的对象

posted on   菜鸟28  阅读(181)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示