计算机基础
进程间通信方式
- 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
- 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
- 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
死锁条件
互斥,占有等待,循环等待,不可剥夺
预防
- 打破互斥条件。即允许进程同时访问某些资源。
- 打破不可抢占条件。即允许进程强行从占有者那里夺取某些资源。
- 打破占有且申请条件。可以实行资源预先分配策略。
避免
- 安全序列
- 银行家算法
检测与恢复
- 循环等待
- 抢占资源
- 会退执行
- 杀掉线程
虚拟内存与页面置换算法
- 在内存记录页号与偏移量,为每个线程实现从页号到物理块号的地址映射,减少寻址次数。
- 算法: FIFO(先进先出),LRU(最近最少被使用)
线程为什么比进程快
进程切换和线程切换的区别最主要的一个区别在于进程切换涉及虚拟地址空间的切换而线程不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。有的同学可能还是不太明白,为什么虚拟地址空间切换会比较耗时呢?现在我们已经知道了进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射,这样可以加速页表查找,这个cache就是TLB(translation Lookaside Buffer,我们不需要关心这个名字只需要知道TLB本质上就是一个cache,是用来加速页表查找的)。由于每个进程都有自己的虚拟地址空间,那么显然每个进程都有自己的页表,那么当进程切换后页表也要进行切换,页表切换后TLB就失效了,cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢,而线程切换则不会导致TLB失效,因为线程线程无需切换地址空间,因此我们通常说线程切换要比较进程切换块,原因就在这里。
KILL信号量
- KILL 操作系统释放SIGTERM信号,优雅退出,程序可选择立即退出,释放内存,响应请求。
- KILL -9 操作系统释放SIGKILL 立即退出
Java 基础
类加载
- 加载(Loading)
- 链接(Linking)
- 初始化(Initialization)
其中链接(Linking)又可以分为: 验证(Verification)、准备(Preparation)、解析(Resolution)。
JAVA代理
- 静态代理,编码实现
- 动态代理,proxy反射reflect
- cglib,基于字节码修改class文件
什么时候会发生FullGC
- System.gc()方法的调用
- 老年代空间不足
- 永生区空间不足
- 堆中分配很大的对象
接口与抽象类
- 都是上层的抽象层
- 都不能被实例化
- 都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,但是不比提供具体的实现
区别如下: - 在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势;接口中只能有抽象的方法
- 一个类只能继承一个直接父类,这个父类可以是具体的类也可是抽象类;但是一个类可以实现多个接口
runnable 和 callable 有什么区别?
相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
不同点 - Runnable没有返回值;Callable可以返回执行结果,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
- Callable接口的call()方法允许抛出异常;Runnable的run()方法异常只能在内部消化,不能往上继续抛
线程有哪些状态?
- NEW 未开始的线程状态
- RUNABLED 可执行状态可能在等待其他资源
- BLOCKED 阻塞 不能进入同步代码块
- WAITTING 等待 显式调用Object.wait()、join()、LockSupport.park()
- TIME_WAITTING 超时等待
- TERMINATED 完成
什么是反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,
都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
容器
ConcurrnetHashMap
整体结构
1.7:Segment + HashEntry + Unsafe
1.8: 移除Segment,使锁的粒度更小,Synchronized + CAS + Node + Unsafe
put()
1.7:先定位Segment,再定位桶,put全程加锁,没有获取锁的线程提前找桶的位置,并最多自旋64次获取锁,超过则挂起。
1.8:由于移除了Segment,类似HashMap,可以直接定位到桶,拿到first节点后进行判断,1、为空则CAS插入;2、为-1则说明在扩容,则跟着一起扩容;3、else则加锁put(类似1.7)
get()
基本类似,由于value声明为volatile,保证了修改的可见性,因此不需要加锁。
resize()
1.7:跟HashMap步骤一样,只不过是搬到单线程中执行,避免了HashMap在1.7中扩容时死循环的问题,保证线程安全,Segment个数concurrencyLevel(16)不会变化。
1.8:支持并发扩容,HashMap扩容在1.8中由头插改为尾插(为了避免死循环问题),ConcurrentHashmap也是,迁移也是从尾部开始,扩容前在桶的头部放置一个hash值为-1的节点,这样别的线程访问时就能判断是否该桶已经被其他线程处理过了。
size()
1.7:计算两次,如果不变则返回计算结果,若不一致,则锁住所有的Segment求和。
1.8:CAS获取值,如果一直失败用数组countCells存储当前数量,最终用baseCount来统计总数。
多线程
AbstractQueuedSynchronizer
- 获取锁操作 维护一个State CAS修改State 记录当前线程
- 头尾节点双向队列 未获取到锁放入FIFO队列中
- 队列节点自旋判断前驱是否为头结点,如果为头结点尝试获取锁
synchronized锁升级
- 偏向锁 对象头记录线程id
- 轻量级锁 对象头记录栈中锁记录的指针
- 重量级锁 monitorenter monitorexit 用户态->内核态->用户态
Volite
- 可见性 线程模型的变量会从主存中刷新
- 有序性 禁止指令重排序
Integer的赋值操作
1、分配内存
2、初始化成员变量
3、指向分配的内存空间
2、3两个步骤顺序不一定 需要用volite修饰
分布式系统底层如何保证多模块数据的一致性和安全性
两段式提交
- 强一致性性 XA机制 (try confirm rollback)
- 最终一致性 柔性TCC (try confirm cancel)
Synchronized和ReenterLock
共同点
- 都是用来协调多线程对共享对象、变量的访问
- 都是可重入锁,同一线程可以多次获得同一个锁
- 都保证了可见性和互斥性
不同点 - ReentrantLock 显示的获得、释放锁,synchronized 隐式获得释放锁
- ReentrantLock 可响应中断、可轮回,synchronized 是不可以响应中断的,为处理锁的不可用性提供了更高的灵活性
- ReentrantLock 可以实现公平锁
- ReentrantLock 通过 Condition 可以绑定多个条件
- 底层实现不一样, synchronized 是同步阻塞,使用的是悲观并发策略,lock 是同步非阻塞,采用的是乐观并发策略
- Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现。
- synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,
如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁。 - Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
网络
网络4/7层协议
- 第一层 物理层
作用:负责最后将信息编码成电流脉冲或其它信号用于网上传输。它由计算机和网络介质之间的实际界面组成,可定义电气信号、符号、线的状态和时钟要求、数据编码和数据传输用的连接器。所有比物理层高的层都通过事先定义好的接口而与它通话。 - 第二层 数据链路层
作用:数据链路层通过物理网络链路提供可靠的数据传输。 - 第三层 网络层
作用:这层对端到端的包传输进行定义,他定义了能够标识所有结点的逻辑地址,还定义了路由实现的方式和学习的方式。为了适应最大传输单元长度小于包长度的传输介质,网络层还定义了如何将一个包分解成更小的包的分段方法。
协议:IP,IPX等
通常指:ip地址等 - 第四层 传输层
作用:传输层向高层提供可靠的端到端的网络数据流服务。传输层的功能一般包括流控、多路传输、虚电路管理及差错校验和恢复。流控管理设备之间的数据传输,确保传输设备不发送比接收设备处理能力大的数据;多路传输使得多个应用程序的数据可以传输到一个物理链路上;虚电路由传输层建立、维护和终止;差错校验包括为检测传输错误而建立的各种不同结构;而差错恢复包括所采取的行动(如请求数据重发),以便解决发生的任何错误。
协议:TCP,UDP,SPX。 - 第五层 会话层
作用:会话层建立、管理和终止表示层与实体之间的通信会话。通信会话包括发生在不同网络应用层之间的服务请求和服务应答,这些请求与应答通过会话层的协议实现。它还包括创建检查点,使通信发生中断的时候可以返回到以前的一个状态。
协议:RPC,SQL等 - 第六层 表示层
作用:这一层的主要功能是定义数据格式及加密。
协议:FTP,加密 - 第七层 应用层
http
TCP与UDP的区别:
- 基于连接与无连接;
- 对系统资源的要求(TCP较多,UDP少);
- UDP程序结构较简单;
- 流模式与数据报模式 ;
- TCP保证数据正确性,UDP可能丢包;
- TCP保证数据顺序,UDP不保证。
TCP三次握手四次挥手
三次握手
CLOSED CLOSED
SYN-SEND SYN-RCVD
ESTABLISHED ESTABLISHED
四次挥手
ESTABLISHED ESTABLISHED
FIN-WAIT-1 CLOSE-WAIT
FIN-WAIT-2 LAST-ACK
TIME-WAIT CLOSED
CLOSEDs
TCP有序性保证:
- 数据包编号
- 确认和重发机制(发送方缓存)
Spring/Spring MVC/Spring Boot/Spring Cloud
SpringBean创建过程
- createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
- populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
- initializeBean:调用spring xml中的init 方法。
解决循环依赖的三级缓存
- SingletonFactories : 单例对象工厂的 cache
- EarlySingletonObjects :提前暴光的单例对象的
- CachesingletonObjects:单例对象的cache
不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”
因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
SpringBoot解决了什么问题
- 对第三方插件封装和整合,提供第三方接口
- 无需配置复杂的xml
- 核心功能,自动配置
- 内嵌式web服务器(Tomcat\jetty)等
- 提供POM,简化maven配置
Spring事务
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
事务传递
- PROPAGATION_REQUIRED Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行
- PROPAGATION_REQUES_NEW 该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
- PROPAGATION_SUPPORT 如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
- PROPAGATION_NOT_SUPPORT 该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
- PROPAGATION_NEVER 该传播机制不支持外层事务,即如果外层有事务就抛出异常
- PROPAGATION_MANDATORY 与NEVER相反,如果外层没有事务,则抛出异常
- PROPAGATION_NESTED 该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。
SpringCloud组件
Eureka服务发现
Feign 服务调用
Ribbon 负载均衡
hystrix 熔断 (状态机加线程池)
Zuul 网关
数据库
bin log 与 redo log
bin log 比 redo log后执行
redo log | binlog | |
---|---|---|
文件大小 | redo log 的大小是固定的。 | binlog 可通过配置参数 max_binlog_size 设置每个binlog 文件的大小。 |
实现方式 | redo log 是 InnoDB 引擎层实现的,并不是所有引擎都有。 | binlog 是 Server 层实现的,所有引擎都可以使用 binlog 日志 |
记录方式 | redo log 采用循环写的方式记录,当写到结尾时,会回到开头循环写日志。 | binlog通过追加的方式记录,当文件大小大于给定值后,后续的日志会记录到新的文件上 |
适用场景 | redo log 适用于崩溃恢复(crash-safe) | binlog 适用于主从复制和数据恢复 |
隔离级别
- “读未提及”级别下,没有一致性视图
- “读已提交”级别下,会在 每个SQL开始执行的时候 创建一致性视图
- “可重复读”级别下,会在 每个事务开始的时候 创建一致性视图
- “串行化”级别下,直接通过加锁避免并发问题
间隙锁解决RR级别下非唯一索引,某个事务执行中另一个事务同步修改或者新增导致的问题会导致其他事务阻塞
索引失效的情景
- 存储为NULL或者查询为NULL
- or条件索引没有完全覆盖
- like以“%”开头
- 组合索引没有使用第一列
- 语句索引字段类型隐式转换
- not、<>、或!=
- 当全表扫描速度比索引速度快时,mysql会使用全表扫描,此时索引失效
什么是 ORM 框架?
ORM(Object Relation Mapping)对象关系映射,即通过类与数据库表的映射关系,将对象持久化到数据库中
缓存
缓存穿透、缓存击穿、缓存雪崩
- 缓存穿透是指缓存和数据库中都没有的数据
- 缓存击穿指缓存中没有但数据库中有的数据
- 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机
Redis
基础类型
string、list、set、hash、sort set
实现
- ziplist压缩列表(数组)
- linkedlist编码 (多级链表,优化查询,复杂度logn)
- hashtable编码(String,String)
Redis OOM
Redis大key问题key超过1G导致数据倾斜
Codis根据key进行散列要保证流量均匀打在分布式机器上
- key值足够随机
- value大小均匀
过期策略
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
Redis持久化
官方建议两个策略同时使用
- RDB:文件快照,重写快照,丢失两次快照之间的数据,数据不敏感场景
- AOF:copy-on-write对磁盘IO依赖较高
缓存延迟双删
A淘汰缓存,B查询无数据,B查库更新缓存,A查库更新缓存导致缓存与主存不一致。
- 先淘汰缓存
- 再写数据库(这两步和原来一样)
- 休眠1秒,再次淘汰缓存,删除可能出现的不一致缓存
消息队列
Kafka
如何保证高吞吐量
- 顺序读写
- 零拷贝
- 分区
- 批量发送
- 数据压缩
分布式协调服务
zookeeper
基于ZAB分布式应用程序协调服务
- 崩溃恢复
- 原子广播
工作流程
- 在Client向Follwer发出一个写的请求
- Follwer把请求发送给Leader
- Leader接收到以后开始发起投票并通知Follwer进行投票
- Follwer把投票结果发送给Leader
- Leader将结果汇总后如果需要写入,则开始写入同时把写入操作通知给Leader,然后commit;
- Follwer把请求结果返回给Client
RabbitMQ
基于AMQP(broker:exchange、queue、binding),支持广播、点对点、正则等topic匹配方式
JVM
JVM优化
- -Xms和-Xmx的值设置成相等,堆大小默认为-Xms指定的大小,默认空闲堆内存小于40%时,JVM会扩大堆到-Xmx指定的大小;空闲堆内存大于70%时,JVM会减小堆到-Xms指定的大小。如果在Full GC后满足不了内存需求会动态调整,这个阶段比较耗费资源。
- 新生代尽量设置大一些,让对象在新生代多存活一段时间,每次Minor GC 都要尽可能多的收集垃圾对象,防止或延迟对象进入老年代的机会,以减少应用程序发生Full GC的频率。
- 老年代如果使用CMS收集器,新生代可以不用太大,因为CMS的并行收集速度也很快,收集过程比较耗时的并发标记和并发清除阶段都可以与用户线程并发执行。方法区大小的设置,1.6之前的需要考虑系统运行时动态增加的常量、静态变量等,1.7只要差不多能装下启动时和后期动态加载的类信息就行。
代码实现方面,性能出现问题比如程序等待、内存泄漏除了JVM配置可能存在问题,代码实现上也有很大关系:
- 避免创建过大的对象及数组:过大的对象或数组在新生代没有足够空间容纳时会直接进入老年代,如果是短命的大对象,会提前出发Full GC。
- 避免同时加载大量数据,如一次从数据库中取出大量数据,或者一次从Excel中读取大量记录,可以分批读取,用完尽快清空引用。当集合中有对象的引用,这些对象使用完之后要尽快把集合中的引用清空,这些无用对象尽快回收避免进入老年代。
- 可以在合适的场景(如实现缓存)采用软引用、弱引用,比如用软引用来为ObjectA分配实例:SoftReference objectA=new SoftReference(); 在发生内存溢出前,会将objectA列入回收范围进行二次回收,如果这次回收还没有足够内存,才会抛出内存溢出的异常。
- 避免产生死循环,产生死循环后,循环体内可能重复产生大量实例,导致内存空间被迅速占满。尽量避免长时间等待外部资源(数据库、网络、设备资源等)的情况,缩小对象的生命周期,避免进入老年代,如果不能及时返回结果可以适当采用异步处理的方式等。