很多- java 面试题汇总 -带答案,
问:Java特性
答:
1. 一次编写到处运行
2. 一次编写到处运行,通过GC机制解决内存泄漏的问题
3. 面向对象,(封装,继承,多态)
4. 平台无关(JVM负责运行.class(IL))
5. 语言:泛型,lambda,GC
6. 类库:并发,集合,网络,IO/NIO
7. JRE: Java运行环境
8. JDK: Java开发环境,包括JRE,javac,诊断工具等
问:keepalived 原理
1. 高可用:两台业务系统启动着相同的服务,如果有一台故障,另一台自动接管,我们将将这个称之为高可用
2. Keepalived高可用服务对之间的故障切换转移,是通过 VRRP (Virtual Router Redundancy Protocol ,虚拟路由器冗余协议)来实现的。
3. Keepalived工作方式:抢占式、非抢占式
4. 原理: VRRP的出现是为了解决静态路由的单点故障。在 Keepalived服务正常工作时,主 Master节点会不断地向备节点发送(多播的方式)心跳消息,用以告诉备Backup节点自己还活看,当主 Master节点发生故障时,就无法发送心跳消息,备节点也就因此无法继续检测到来自主 Master节点的心跳了,于是调用自身的接管程序,接管主Master节点的 IP资源及服务。而当主 Master节点恢复时,备Backup节点又会释放主节点故障时自身接管的IP资源及服务,恢复到原来的备用角色。
5. nginx+keepalived
a) 实现思路:将keepalived 中的vip作为nginx负载均衡的监听地址,并且域名绑定的也是vip的地址。
b) 备注. keepalived需要分别运行在nginx的机器上, 为了防止脑裂现象的产生,需要编写一个检测nginx的存活状态的脚本,如果nginx不存活,则kill掉宕掉的nginx主机上面的keepalived。(所有的keepalived都要配置)
6. 其他实例: keepalived+codis proxy
问:MongoDB为啥使用ObjectId而不是常规的主键ID
答: 因为在多个服务器上同步自动增加主键值既费力还费时.
问:主要中间件端口?
答: redis 6379, mongoDB 27017, zk 5672/15672, tidb:4000, mysql 3306, ssh 20, sqlserver 1433,
分布式事务相关
分布式事务的四种解决方案
1. 两阶段提交(2PC)时效性高,多用于DB
2. 补偿事务(TCC),多用于应用层
3. 本地消息表(异步确保),本地事务双写(如数据库),远程端处理成功后更改数据状态
4. MQ 事务消息,如RocketMQ
2PC相关
1. 当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果,
2. 分为两个阶段
a) 第一阶段:准备阶段
b) 第二阶段:提交阶段
3. 2pc问题
a) 同步阻塞 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
b) 单点问题 协调者在2PC中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
c) 数据不一致 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
d) 太过保守 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
4. 小结: 只适合数据库层面的事务. 因为是一种强一致性事务,并且是同步阻塞的, 效率低,并且存在单点故障(协调者是故障源)的问题
2PC三阶段提交
1. 由于二阶段提交存在以上诸多问题,所以研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。
2. 三阶段提交有2个改动:
a) 在协调者和参与者中都引入了超时机制。
b) 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
c) 三阶段提交分为三个阶段: CanCommit、PreCommit、DoCommit
3. 与2PC比较,
a) 相对于2PC,3PC主要解决单点故障问题,并减少阻塞(一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit,而不会一直持有事务资源并处于阻塞状态)。
b) 但是3PC也有数据一致性问题: 比如,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作,这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致了。
TCC相关
1. 分为三个阶段:
a) Try 阶段主要是对业务系统做检测及资源预留
b) Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
c) Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
2. Demo Bob要向Smith转账,思路大概是:
a) Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
b) Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
c) 如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
3. 优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
4. 缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
5. 小结: Tcc: (try-confirm-cancel),能保证业务层面的事务. 对业务耦合性比较大,并且confirm和cancel要保证幂等性
问: jvm虚拟机对象分配流程:
答: 首先如果开启栈上分配,JVM会先进行栈上分配,如果没有开启栈上分配或则不符合条件的则会进行TLAB分配,如果TLAB分配不成功,再尝试在eden区分配,如果对象满足了直接进入老年代的条件,那就直接分配在老年代.
问: spark如何实现容错性:
答案: 重算一遍,因为所有计算都是确定的,RDD 是一个只读,分区的集合,RDD 的来源要么是不可变的外部文件, 要么是上一层计算结果
问: hadoop几个主要产品的架构设计
答: 都是一主多从方案. HDFS:一个NameNode,多个DataNode;MapReduce 1:一个JobTracker,多个TaskTracker;Yarn:一个ResourceManager,多个NodeManager。
很多大数据产品都是这样的架构方案:Storm,一个Nimbus,多个Supervisor;Spark,一个Master,多个Slave
小结:大数据领域的架构模式: 集中管理,分布式计算与存储
问: 一个页面的请求过程:
问: 现在系统都讲究去中心化, 请说下去中心化的问题:
答: 去中心后, 自组织的成本会更高;中心可靠高效的情况下,有中心效率更高. 所以大数据领域保留一定的中心化需求.
问: ThreadLocal对象原理
答:
1. Thread类有一个成员变量是ThreadlocalMap , key是threadlocal对象(this),value为实际的值.
2. Entry<ThreadLocal, T>继承WeakReference <ThreadLocal<?>>,是一个弱引用. 如果threadlocal对象被回收后(如被设置null), 则对应的Entry变成(null,value), 所以 ThreadLocal定时会清理key为null的数据.
3. 每个线程独自拥有一个变量,并非共享或者拷贝.
4. Entry为什么被设计为弱引用
a) 假如使用强引用,当ThreadLocal不再使用需要回收时,发现某个线程中ThreadLocalMap存在该ThreadLocal的强引用,无法回收,造成内存泄漏,
b) 使用弱引用可以防止长期存在的线程(通常使用了线程池)导致ThreadLocal无法回收造成内存泄漏
5. 最佳实践: 在ThreadLocal使用前后都调用remove清理,不及时清理可能会造成严重的业务逻辑问题(如存储集合元素膨胀等),同时对异常情况也要在finally中清理,
6. 解决方式:key为WeakReference类型,当key为null,即ThreadLocalMap<null, Object>的情况时,在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value
7. 每个Thread维护着一个ThreadLocalMap的引用,同一个Thread内部的threadlocal都是由自己的ThreadlocalMap来实现的.
8. ThreadLocalMap是ThreadLocal的内部类,用Entry[],初始长度为16,每次2倍扩展,
9. 每个Entry继承自WeakReference,key为theadlocal自身引用(GC后变为null),
a) set时,使用ThreadLocal自己的hash算法(unsafe+地址)与长度求余作为自己的位置,如果位置被占则位置向后延
b) get时,同上
10. 注意事项:
a) Threadlocal使用不当会造成脏数据或者内存泄漏,如线程池中, 线程会被复用,所以线程内部的threadlocalMap也会被复用,
b) Thread对象内部包含两个localTheadMap(threadLocals和inheritableThreadLocals),当inheritableThreadLocals时,可以由Parent向子线程传递.
问: 说下软引用,弱引用
答:
1. 软引用用来描述有用但不是必需的对象,只有内存不足时才会回收引用.JVM通常会在OOM之前清理软引用指向的对象
2. 弱引用用来描述非必须的对象,如:threadlocal对象已出作用域(或设置为null)后,ThreadLocalMap中弱引用key就会被设置为null(垃圾回收). Threadlocal会定时清理key为null的数据.
3. 小结: 在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。所以被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收
问: 四种引用类型
1. 强引用,只要指向不为空GC就不会回收,即使抛出OOME警告。
2. 软引用,拥有强引用的属性,又更加安全,jvm即将抛出oom的时候,垃圾回收器才会将该引用对象进行回收,适合当作缓存使用,如加载的图片等。
3. 弱引用,GC扫描到时就会被回收。例如Java的TheardLocal与动态代理都是基于这样的一个引用实现的,一般针对那些比较敏感的数据。网络回调里用的比较多
4. 幻想引用是针对那些已经执行完析构函数之后,仍然需要在执行一些其它操作的对象:比如资源对象的关闭就可以用到这个引用。
5. ReferenceQueue: 构造Reference时传入,当一个obj被gc掉之后,其相应的包装类-即ref对象会被放入queue中。我们可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理等
问: OkHTTP了解
答: OkHttp库的设计和实现的首要目标是高效。这也是选择OkHttp的重要理由之一。
1. OkHttp提供了对最新的HTTP协议版本HTTP/2 和 SPDY 的支持,这使得对同一个主机发出的所有请求都可以共享相同的套接字连接。
2. 如果 HTTP/2和SPDY不可用,OkHttp 会使用连接池来复用连接以提高效率。
3. OkHttp提供了对GZIP的默认支持来降低传输内容的大小。OkHttp也提供了对HTTP响应的缓存机制,可以避免不必要的网络请求。当网络出现问题时,OkHttp 会自动重试一个主机的多个IP地址
问: 消息队列作用:
答: 解耦,异步,削峰填谷
问: 消息队列缺点,
答: 见底可用性(调用链变长), 增加系统复杂性(消息可靠性传输, 消息一致性).
RabbitMQ相关:
RabbitMQ 重要的组件
1、 ConnectionFactory(连接管理器):应用程序与Rabbit之间建立连接的管理器,程序代码中使用。
2、 Channel(信道):消息推送使用的通道。
3、 Exchange(交换器):用于接受、分配消息。
4、 Queue(队列):用于存储生产者的消息。
5、 RoutingKey(路由键):用于把生产者的数据分配到交换器上。
6、 BindingKey(绑定键):用于把交换器的消息绑定到队列上。
7、 Connection.因为TCP连接的创建和释放是十分昂贵的,所以一个connection可以包含多个channel,但是每个线程要单独有自己的channel
exchanges, queues, and bindings是三个基础的概念,
1. exchanges: 事实上,相当多的生产者甚至根本不知道一个消息是否已经传递给了一个队列。相反,生产者只能将消息发送给一个exchange。exchange是一个很简单的东西。一边它接收来自生产者的消息,另一边它将这些消息推送到队列。exchagne必须明确地知道拿它收到的消息来做什么。把消息附在一个特定的队列上?把消息附在很多队列上?或者把消息丢弃掉。
2. consumer和producer都可以创建Queue,
3. 作用是:exchanges are where producers publish their messages, queues are where the messages end up and are received by consumers, and bindings are how the messages get routed from the exchange to particular queues.
在AMQP模型中,Exchange是接受生产者消息并将消息路由到消息队列的关键组件。ExchangeType和Binding决定了消息的路由规则
1. Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。
2. Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
在RabbitMQ中,有三种类型的Exchange:direct ,fanout和topic
1. Direct,将消息中的RoutingKey与该Exchange关联的所有Binding中的BindingKey进行比较,如果相等,则发送到该Binding对应的Queue中。
2. Fanout,将消息发送给所有与该 Exchange 定义过 Binding 的所有 Queues 中去,其实是一种广播行为。
3. Topic,按照正则表达式,对RoutingKey与BindingKey进行匹配,如果匹配成功,则发送到对应的Queue中。
消费者订阅消息
1. 一种是通过basic.consume命令,订阅某一个队列中的消息,channel会自动在处理完上一条消息之后,接收下一条消息。(同一个channel消息处理是串行的)。除非关闭channel或者取消订阅,否则客户端将会一直接收队列的消息。
2. 另外一种方式是通过basic.get命令主动获取队列中的消息,但是绝对不可以通过循环调用basic.get来代替basic.consume,这是因为basic.get RabbitMQ在实际执行的时候,是首先consume某一个队列,然后检索第一条消息,然后再取消订阅。如果是高吞吐率的消费者,最好还是建议使用basic.consume。
持久化
Rabbit MQ默认是不持久队列、Exchange、Binding以及队列中的消息的
只有durable为true的Exchange和durable为ture的Queues才能绑定,否则在绑定时,RabbitMQ都会抛错的
RabbitMQ 节点的类型
1. 磁盘节点:将所有的队列,交换器,绑定关系,用户,权限,和vhost的元数据信息保存在磁盘。
2. 内存节点:将所有的队列,交换器,绑定关系,用户,权限,和vhost的元数据信息保存在内存中,重启服务器消息丢失,性能高于磁盘类型。
3. 集群中必须有一个磁盘节点
RabbitMQ 怎么保证消息的稳定性?
1、 提供了事务的功能。
2、 通过将 channel 设置为 confirm(确认)模式。
问: 现有主要消息队列
1. RabbitMQ,吞吐量:万级,时效性us级,特点:并发能力强,延时低(us),管理界面方便,中小型公司首选
2. RocketMQ,特点:阿里出品,参考了kafka思想,并发性极高, 支持事务,
3. Kafka吞吐量极高,为大数据准备的, 尤其适合日志收集.
问: 现有消息队列幂等性实现:
1. RabbitMQ ack机制.
2. RocketMQ 返回一个CONSUME_SUCCESS成功状态,
3. Kafka有offset标记
问: 消息队列可靠性传输:
答: 消息可靠性传输包括三个部分:
1. 生产者可靠性投送:
a) Kafka: request.required.acks[1:只leader,-1 leader+slave,0 不需要确认]; delivery guarantee[at most once, at least once, exactly once]
b) RabbitMQ:
i. 使用tracsaction(txSelect,txCommit,txRollback), 和confirm机制(普通确认,批量确认,异步确认),
ii. 路由机制包括:Direct, topic, fanout(广播)
c) RocketMQ:
i. sendStatus (FLUSH_DISK_TIMEOUT,FLUSH_SLAVE_TIMEOUT,SLAVE_NOT_AVAILABLE,SEND OK)
ii. 发送消息模式包括: 普通发送,异步发送,oneWay发送
iii. 支持事务(半消息回查): 需要生产者提供消息回查接口
2. 消息队列中可靠性存储
a) RabbitMQ支持死信队列(消息过期,队列过长,被拒绝).
3. 消费者可靠性消费
a) Kafka: 负载最小的broker作为coordinator,负责rebalance, offset存储消费者的消费位置
b) RabbitMQ: autoAck改为手动.
c) RocketMQ,数据已经落盘, 直接拉取
问: 说下spark on yarn
答: 简而言之,Spark on Yarn 模式就是将Spark应用程序跑在Yarn集群之上,通过Yarn资源调度将executor启动在container中,从而完成driver端分发给executor的各个任务。
1. 将Spark作业跑在Yarn上,首先需要启动Yarn集群,然后通过spark-shell或spark-submit的方式将作业提交到Yarn上运行。
2. Yarn的俩种模式:一种为 client;一种为 cluster,可以通过- -deploy-mode 进行指定,也可以直接在 - -master 后面使用 yarn-client和yarn-cluster进行指定
3. 俩种模式的区别:在于driver端启动在本地(client),还是在Yarn集群内部的AM中(cluster)
问: spark分哪几种模式
答: spark模式包括:
1. 本地模式: 我们只需要在安装Spark时不进行hadoop和Yarn的环境配置,只要将Spark包解压即可使用.
2. Standalone模式(Spark On Local Cluster), spark自己实现调度框架,分为master,worker节点.
3. yarn模式,
4. mesos模式
问: 总结spark启动流程
答: Spark在执行每个Application的过程中会启动Driver和Executor两种JVM进程,
1. Driver进程为主控进程,负责执行用户Application中的main方法,提交Job,并将Job转化为Task,在各个Executor进程间协调Task的调度。
2. 而运行在Worker上的Executor进程负责执行Task,并将结果返回给Driver,同时为需要缓存的RDD提供存储功能spark的核心启动类是driver.
3. Driver进程本身会根据我们设置的参数,占有一定数量的内存和CPU core。
a) Driver进程要做的第一件事情,就是向集群管理器申请运行Spark作业需要使用的资源,这里的资源指的就是Executor进程。YARN集群管理器会根据我们为Spark作业设置的资源参数,在各个工作节点Worker上,启动一定数量的Executor进程,每个Executor进程都占有一定数量的内存和CPU core。
b) 在申请到了作业执行所需的资源之后,Driver进程就会开始调度和执行我们编写的作业代码了。Driver进程会将我们编写的Spark作业代码分拆为多个stage,每个stage执行一部分代码片段,并为每个stage创建一批task,然后将这些task分配到各个Executor进程中执行。
c) Task是最小的计算单元,负责执行一模一样的计算逻辑(也就是我们自己编写的某个代码片段),只是每个task处理的数据不同而已。一个stage的所有task都执行完毕之后,会在各个节点本地的磁盘文件中写入计算中间结果,然后Driver就会调度运行下一个stage。下一个stage的task的输入数据就是上一个stage输出的中间结果。如此循环往复,直到将我们自己编写的代码逻辑全部执行完,并且计算完所有的数据,得到我们想要的结果为止。
问: spark shuffle的根本原因是啥
答: shuffle的根本原因是相同的key存在不同的节点上,按key进行聚合的时候不得不进行shuffle。shuffle是非常影响网络的,它要把所有的数据混在一起走网络,然后它才能把相同的key走到一起。
问: Spark Client 和 Spark Cluster的区别
答: 在YARN中,每个Application实例都有一个ApplicationMaster进程,它是Application启动的第一个容器。
1. 它负责和ResourceManager打交道并请求资源,获取资源之后告诉NodeManager为其启动Container。从深层次的含义讲YARN-Cluster和YARN-Client模式的区别其实就是ApplicationMaster进程的区别
a) YARN-Cluster模式下,Driver(sparkcontext)运行在AM(Application Master)中,它负责向YARN申请资源,并监督作业的运行状况。当用户提交了作业之后,就可以关掉Client,作业会继续在YARN上运行,因而YARN-Cluster模式不适合运行交互类型的作业;
b) YARN-Client模式下,Driver(sparkcontext,包括DAGScheduler,taskScheduler)运行在Client端 ,Application Master(运行在第一个容器中)仅仅向YARN请求Executor,Client会和请求的Container通信来调度他们工作,也就是说Client不能离开。
问: Spark Streaming 与 Storm比较
答:
1. spark(ApplicationMaster,Executor)使用的微批,属于准实时流计算, 相比storm有数倍甚至十倍的吞吐量,
2. storm(Nimbus,Supervisor)是每条数据都要处理, 速度慢, 优点:可以动态的调整并行度,动态提高并发处理能力
3. 概述: 通常在对实时性要求特别高,而且实时数据量不稳定,比如在白天有高峰期的情况下,可以选择使用Storm。但是如果是对实时性要求一般,允许1秒的准实时处理,而且不要求动态调整并行度的话,选择Spark Streaming是更好的选择
问: Spark中shuffle几种算法:
答:
1. Hash Shuffle, 1.6之前的默认算法,不需要排序,计算结果会产生M*R个文件,缺点会生成大量文件,合并时需要使用HashMap容易OOM
2. Hash Shuffle 引入File Consolidation机制,每个executor上最多只生成N个分区文件
3. Sort Shuffle 借鉴Hadoop MapReduce机制,引入基于排序的shuffle写操作, 特点将所有结果写入一个文件, 并添加索引,缺点,所有记录都要排序,损失性能
4. Unsafe Shuffle,直接使用二进制方式存储,极大优化排序性能,
5. 小结: 从spark-2.0之后hash shuffle已刨除, sort shuffle, unsafe shuffle统一到sort shuffle,有系统自己控制使用那种shuffle方式.
问: Linux定时器
答: 编辑crontab -e -u root, 查看crontab -l -u root
问: ES索引为啥比MySQL快
答:
1. es使用倒排索引,对多条件查询支持特别好
2. mysql索引只有一层(Term Dictionary),查找时要多次随机磁盘查找, es在term dictionary上又抽象了一个term index ,term index以树的形式缓存在内存中。从term index查到对应的term dictionary的block位置之后,再去磁盘上找term,大大减少了磁盘的random access次数
3. 利用skip list/bitset实现多条件快速结果合并
问: Java类加载过程
答: 包括三个阶段: 加载,验证,连接,初始化
1. 加载, jvm将.class二进制流加载到内存,
2. 连接: 验证类符合规范,为静态成员分配空间,符号引用替换为直接引用, 放入JVM方法区
3. 初始化: 为对象分配空间, 属性初始化,调用构造函数等
4. Java类加载工具class-loader包括: BootStrap,Extension class-loader, Application等
5. 常用的垃圾回收器包括,SerialGC, ParallelGC,CMS,G1,ZGC等
6. Java支持解释执行和编译执行两种方式,也支持混合模式(-Mmixed), 内部支持多种JIT(Just-In-Time)编辑器
a) 默认采用分层编译的方式
b) client模式启动快
c) Server模式为长时间运行在服务端进行优化
问:JDK、JRE、JVM关系是什么?
1. JDK(Java Development Kit)即为Java开发工具包,包含编写Java程序所必须的编译、运行等开发工具以及JRE。
开发工具如:用于编译java程序的javac命令、用于启动JVM运行java程序的java命令、用于生成文档的javadoc命令以及用于打包的jar命令等等。
2. JRE(Java Runtime Environment)即为Java运行环境,提供了运行Java应用程序所必须的软件环境,包含有Java虚拟机(JVM)和丰富的系统类库。系统类库即为java提前封装好的功能类,只需拿来直接使用即可,可以大大的提高开发效率。
3. JVM(Java Virtual Machines)即为Java虚拟机,提供了字节码文件(.class)的运行环境支持。 简单说,就是JDK包含JRE包含JVM。
JVM相关
一些概念
1、 程序计数器: 是线程私有的,因为每个线程的执行操作不一样, 用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令,
2、 局部变量表: 局部变量表中存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等,局部变量表是在编译时就已经确定 好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变
3、 本地方法栈: 本地方法栈是用来执行native方法, 本地方法栈也是线程私有的。
4、 方法区: 方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等,垃圾收集主要是针对 常量池的内存回收和对已加载类的卸载,jdk1.8之后,位于元空间
5、 常量池: 用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用,还有运行时间产生的常量(如String.intern)
6、 直接内存: 直接内存并不是JVM管理的内存,就是JVM以外的机器内存,JDK中有一种基于通道(Channel)和缓冲区 (Buffer)的内存分配方式,用存储在JVM堆中的DirectByteBuffer来引用
7、 栈上分配: 包括标量分配, 标量类型即原子类型,表示单个值,可以是基本类型或String等
8、 内存分配方法 => 静态申请(编译时申请),动态申请(堆区运行时产生的各种对象,需要GC介入管理)
9、 垃圾检测算法 => 计数器,可达性分析法
10、 垃圾回收算法 => 包括:标记清除,标记整理,复制
11、 回收器 => 新生代包括:serial,ParNew,Parallel Scavang,老年代包括:CMS,Serial Old,Parallel,备注老年代都用标记整理算法
Eden区
1、 是连续的内存空间,因此在其上分配内存极快;
2、 在Eden区,HotSpot虚拟机使用了两种技术来加快内存分配。分别是bump-the-pointer(指针碰撞)和TLAB(Thread- Local Allocation Buffers),这两种技术的做法分别是:由于Eden区是连续的,因此bump-the-pointer技术的核心就是跟踪最后创建的一个对象,在对 象创建时,只需要检查最后一个对象后面是否有足够的内存即可,从而大大加快内存分配速度;
3、 而对于TLAB技术是对于多线程而言的,将Eden区分为若干 段,每个线程使用独立的一段,避免相互影响。TLAB结合bump-the-pointer技术,将保证每个线程都使用Eden区的一段,并快速的分配内存
JVM永久代,元空间
jdk1.8后 元数据区取代了永久代, 元空间不在JVM虚拟机中, 而是直接放到了直接内存中.
问: JAVA是解释执行吗?
答: 不完全正确
1. Java源代码要被javac编译为.class文件
2. .class文件经过JVM解释或者编译运行
a) 解析: 由JVM内嵌解释器解释执行
b) 编译: 存在JIT(及时编译器),将经常运行的热点代码编程成机器码
c) AOT(ahead-of-time complication)编译器: JAVA9提供的多层编译器,将所有代码编译为机器码执行.
HotSpot虚拟机对象包括对象头,实例数据,对其填充
1. 对象头(markword): 包括哈希值,GC年龄,锁状态,偏向锁状态
2. 实例数据: 成员变量的值
3. 对其填充: 确保对象为8字节整数倍
对象创建过程
1. 检查并加载类对象
2. 为新对象分配内存
A. 内存规整时(复制算法,标记整理算法),指针碰撞法分配内存
B. 内存不规整(标记清除法), jvm内部维护空闲地址列表,从空闲列表中分配对象
3. 初始化: 调用构造方法初始化
对象访问方法
1. 句柄访问方式, 直接访问的是句柄,JVM中有专门句柄池,存储对象堆中地址和方法区中的地址,十分稳定
2. 直接指针访问: reference存储的就是对象实际中堆中地址,优势是速度快,HotSpot默认方式.,
问: 如何判断对象是否存活
答: 两种方法
1. 引用计数器: 难解决对象间循环引用的问题
2. 可达性分析: GCRoot包括
A. 栈中引用对象
B. 常量
C. 类静态属性对象
问: 垃圾收集算法:
1. 标记-清除算法:
A. 没有GC-root的对象被清除”,
B. 缺点:内存碎片严重
2. 复制算法(新生代使用)
A. 内存分为大小两份,每次使用一份
B. 优点:没有碎片,缺点:内存使用率只有一半,浪费空间
3. 标记整理(老年代使用)
A. 标记算法与之上一致
B. 整理时移动存活对象
问: HotSpot垃圾收集器
答:
1. Serial垃圾收集器(单机单线程), stop the world
2. parNew多线程收集器
3. CMS收集器:并发标记清除,
a)
b) 初始化标记(stw,找出ROOT),并发标记,再次标记(只针对有变动的,所以stw影响很小),并发清理
c) 对CPU敏感, 最大限度减少对用户线程的影响,无法处理浮动垃圾(需要定时碎片整理),新生代使用标记清除算法
4. G1收集器
a) 没有新生代,老年代区分,将堆划分为多个Region
问: java异常处理两个比较昂贵的地方
答:
1. try-catch会产生额外的性能开销,影响JVM对代码进行优化,所以尽量不要一个大的try包住整段代码
2. Java每实例化一个Exception,都会对堆栈进行快照,这是个比较重的操作.
问: Java Exception与error的区别
1. 二者都继承了Throwable类,
2. Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
3. Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常
问: finalize作用
1. finalize被设计为在对象被垃圾回收前的调用,所以成了GC快速回收的障碍,导致你的对象在多个回收周期才能被回收,造成OOM
2. fnalize一旦实现了非空的fnalize方法,就会导致相应对象回收呈现数量级上的变慢.
问:匿名内部类访问局部变量是为啥要用final来修饰
答: 匿名内部类实际上会copy局部变量而不是使用原变量,加final是防止出现数据不一致的现象.
问: Java String笔记?
1. 不可变特性:由于String在Java世界中使用过于频繁,Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池.
a) 直接赋值表达式走常量池 String str1 = "123",
b) 直接创建对象,不放常量池 new String()
2. 优点
a) 安全,高效:线程安全,不存在并发问题
b) 可以放到常量池中,提高内存使用效率
3. String类因为不可变,所以可是直接使用Hashcode做比较
4. 扩展: StringBuffer线程安全,StringBuilder非线程安全,效率更好
问: 永久代和方法区的关系
1. JMM模型中包括虚拟机栈,堆,程序计数器,本地方法区,方法区(存储在永久区)
2. 方法区(method area)是jvm虚拟规范,永久区(PermGen)是HotSpot针对规范的实现(JRocket就没有永久区的概念)
3. jdk1.8之前, 永久区存在Heap中,所以永久区的大小受堆大小的限制,
4. jdk1.8之后取消了永久代,代替实现的是MeteSpace,元空间存在本地内存中, 不受Heap大小限制(即只要本地内存够,就不会出现OOM(PermGen space错误))
5.
问: JDK proxy与cglib动态代理的区别
1. JDK Proxy需要额外接口支持: Proxy.newProxyInstance(classloaser,interfalce,handler)
2. 优点:
a) JDK原生支持,最小化依赖,简化开发和维护
b) 代码实现简单,侵入性小,JDK版本升级平滑,
3. 缺点: 只能对接口进行代理
1. cglib:特点,使用的是字节码ASM,直接生成子类,
2. 优点:不需要额外接口限制, 高性能
3. 缺点:代码有侵入性,高性能
问: java基本类型(primitive type)小计
1. 基本类型包括8个:int,byte,float,long,double,bool,short,char(Character)
2. 默认拆装箱操作在编译阶段完成,
a) javac替我们自动把装箱转换为Integer.valueOf(),把拆箱替换为Integer.intValue()
b) java5之后使用valueOf会使用缓存, 缓存范围默认是(~128,127)
3. 缓存意义:
a) 大部分数据操作都集中在有限的,较小的数据范围,所以提前构建缓存,提升后续构建对象的性能
b) 缓存的最大位可配置: -XX:AutoBoxCacheMax=128
4. 线程安全性:
a) 基本类非线程安全,特别的是比较宽的数据类型如float,double,甚至不保证更新的原子性,可能程序只读到更新一半的数据位的数值
b) 建议使用AutomicInteger,AutomicLong类
Zookeeper相关:
可以用来实现哪些功能?
1. 集群管理:主要包括两点:是否有机器加入和退出, 选举master, 监控节点存活状态、运行请求并且时刻注意节点的增删。
2. 主节点选举:主节点挂掉了之后可以从备用的节点开始新一轮选主,主节点选举说的就是这个选举的过程,使用 zookeeper 可以协助完成这个过程。
3. 分布式锁:zookeeper 提供两种锁:独占锁、共享锁。zookeeper可以对分布式锁进行控制。
a) 第一种方式是所有请求都去创建同一个ZNode,创建成功的标识获取锁成功
b) 第二种方式时,创建临时编号目录节点,编号最小的获得锁.
4. 命名服务(nameserver):在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。
5. 配置管理: 类似appollo,如果配置变化时,需要将配置同步到所有watcher,利用通知功能
Zookeeper角色
1. Leader: 负责写,更新系统状态
2. Follower: 投票,客户读请求,转发写请求到leader
3. Observer: 客户读请求,转发写请求到leader
leader选举过程
1. leader选举算法采用了Paxos协议;paxos核心思想:只有过半选票操作才能通过,所有写操作都在master中进行排队.同一时刻只有一个写被批准.
2. Paxos算法解决的什么问题呢,解决的就是保证每个节点执行相同的操作序列
3. 为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。
4. zxid是一个64位的数字,它高32位是epoch用来标识(leader变化),低32位用于递增计数
5. Demo
a) A提案说,我要选自己,B你同意吗?C你同意吗?B说,我同意选A;C说,我同意选A。
(注意,这里超过半数了,其实在现实世界选举已经成功了。但是计算机世界是很严格,另外要理解算法,要继续模拟下去。)
b) 接着B提案说,我要选自己,A你同意吗;A说,我已经超半数同意当选,你的提案无效;C说,A已经超半数同意当选,B提案无效。
c) 接着C提案说,我要选自己,A你同意吗;A说,我已经超半数同意当选,你的提案无效;B说,A已经超半数同意当选,C的提案无效。
d) 选举已经产生了Leader,后面的都是follower,只能服从Leader的命令。而且这里还有个小细节,就是其实谁先启动谁当头。
每个Server在工作过程中有三种状态:
1. LOOKING:当前Server不知道leader是谁,正在搜寻
2. LEADING:当前Server即为选举出来的leader
3. FOLLOWING:leader已经选举出来,当前Server与之同步
Znode有四种形式的目录节点
1. PERSISTENT(持久的)
2. EPHEMERAL(暂时的)
3. PERSISTENT_SEQUENTIAL(持久化顺序编号目录节点)
4. EPHEMERAL_SEQUENTIAL(暂时化顺序编号目录节点)
相关知识点:
1. ZK首先是一个文件系统,并支持通知机制
2. 环境:使用java开发,需要Java环境.
3. Znode具有原子操作的特点:被原子的读写.
4. 拥有临时节点,当session结束时,临时节点也被删除.
5. 安装方式包括:单机模式,集群模式,伪分布式集群方式(单机模拟集群)
6. 各端口机制,配置: server.1=IP1:2887:3887(服务器IP:leader选举端口,服务器通讯端口)
7. zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 zab 协议。 zab 协议有两种模式,分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 server 具有相同的系统状态。
8. 通知机制,客户端端会对某个 znode 建立一个 watcher 事件,当该 znode 发生变化时,这些客户端会收到 zookeeper 的通知,然后客户端可以根据 znode 变化来做出业务上的改变。
9. 更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行(因为所有写操作都在master队列中)
10. 数据更新原子性,一次数据更新要么成功,要么失败
11. 全局唯一数据视图,client无论连接到哪个server,数据视图都是一致的
12. 实时性,在一定事件范围内,client能读到最新数据
问: ==和equals的区别
答:
1. ==如果是基本类型比较的是值,如果是引用类型,比较的是引用是否相同
2. equals本质上是==, 不过String和Integer重写了equals方法,把它变成了值比较.
问: 接口与抽象类的区别
1. 接口方法只能定义不能有方法体
2. 接口是公开的,不得有私有方法及属性,
3. 接口可以有成员变量,不过成员默认是public static final,所以必须初始化
4. 接口可以多继承
5. 抽象类可以有私有方法,私有属性,抽象方法不能使用final修饰,也不能是private
6. 抽象类是对类的抽象,继承关系是is-a关系, 接口是对行为的抽象,has-a的关系
问IO流分类?
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按8位传输以字节为单位输入输出数据,字符流按16位传输以字符为单位输入输出数据
HashSet实现原理:
HashSet是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成,HashSet不允许重复的值。
Iterator特点:
Iterator的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。不过可以使用iterator删除集合元素.
问: return,finally的执行顺序
答: 方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不存在了,因此,return在返回的时候不是直接返回变量的值,而是复制一份,然后返回。
因此,对于基本类型的数据,在finally块中改变return放入值对返回值没有任何影响,而对于引用类型的数据,就有影响
如:
public static void main(String[] args) {
int resultVal = testFinally1();
System.out.println(resultVal); //2
StringBuffer buffer = testFinally2();
System.out.println(buffer); //hello World
}
private static int testFinally1() {
int result = 1;
try {
result = 2;
return result;
} catch (Exception e) {
return 0;
} finally {
result = 3;
//如果在finally中直接return 是生效的
// return result;
}
}
private static StringBuffer testFinally2() {
StringBuffer s = new StringBuffer("hello");
try {
return s;
} catch (Exception e) {
return null;
} finally {
s.append(" World");
}
}
问: 面向对象:
答: 面向对象三个要素:抽象,继承,封装,多态
1. 封装: 保证类的独立及隔离,封装实现依赖修饰符(public,protect,private等)
2. 继承: 实现类的复用,继承包括类的继承和接口实现
3. 多态: 在继承的基础上,多态的三个要素:继承、重写和父类引用指向子类对象。父类引用指向不同的子类对象时,调用相同的方法,呈现出不同的行为;就是类多态特性。多态可以分成编译时多态(编译时确定)和运行时多态(运行时确地)
4. 备注: 静态方法不能用于多态,“重写”只能适用于实例方法,不能用于静态方法。对于静态方法,只能隐藏,重载,继承。
5. 备注: 对于运行时多态时父类引用指向子类对象,在调用实例方法时,调用的是子类重写之后的,并且不能调用子类新增的方法,对于属性和static方法来说,还是执行父类原有的。
问: Base64算法
答: Base64的作用是为了不可见的二进制编码为可打印的(64)个字符,在网上传输, 编码规则为把三个字节变成四个字节,3*8=4*6, 其中4组六位在头部补全两个0变成8位一个字节,
问:B树B+树
B树, 数据存储在各个节点上, 搜索时可能在非叶子节点结束(搜索不稳定),搜索等价于二分查找
B+树, 只有叶子节点保存数据,并且增加了顺序访问指针,叶子节点按照大小顺序连接,所有非叶子节点可以看做索引
B+树相对于B树的优势
1. b+树的中间节点不保存数据,所以磁盘页能容纳更多节点元素,更“矮胖”;
2. b+树查询必须查找到叶子节点,b树只要匹配到即位置不固定,,因此b+树查找更稳定(并不慢);
3. 对于范围查找来说,b+树只需遍历叶子节点链表即可,b树却需要重复地中序遍历
问:重载和重写的区别
重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时(编译时多态)。
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类;如果父类方法访问修饰符为private则子类中就不是重写。用于实现运行多态
问:ConcurrentHashMap 1.7和1.8的不同实现
1. 相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。
2. 数据结构:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
3. 保证线程安全机制:segment继承自ReentrantLock。JDK1.8采用CAS+Synchronized性能更好,。
4. 锁的粒度:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)。
5. 链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。
6. 查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)。
问: 如果使用Interrupt停掉一个线程
问: synchronized和Lock的区别
1. Lock是一个接口,操作更加灵活,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;使用Lock时需要在finally块中释放锁;
3. Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4. Lock可以知道有没有成功获取锁(trylock),而synchronized却无法办到。
5. Lock支持重入锁,中断锁,读写锁等。
6. 备注:
a) 在Java1.5中,synchronize是重量级锁性能低效的。Java1.6,发生了变化。synchronize内部进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。建议尽可能去使用synchronized而不要去使用LOCK
synchronize锁升级原理
1、 背景:在Java6之前,synchronized 是由一对 monitorenter/monitorexit 指令实现的,完全是依靠操作系统内部的互斥锁,性能低。Java6 实现三种不同的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。
2、 在被锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将threadid设置为其线程 id,
3、 再次进入的时候会先判断 threadid 是否一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,
4、 执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
元空间,永久代
在jdk1.8之后MetaSpace代替永久代,
元空间从原来的JVM内存剥离,单独存在于本地内存中, 里面存储字节码,静态变量,常量池
常量池包括
1. 静态常量池(class文件常量): 类,接口的全限定名,字段,方法名,
2. 动态常量池(运行时常量):编译时动态加载的类信息,cglib信息等
3. 字符串常量池: inter(),
4. 整型常量池: 8中基本类型常量
问: 类加载器相关
背景:
1. 类加载器是Java语言的一个创新,也是Java语言流行的重要原因之一。它使得Java类可以被动态加载到Java虚拟机中并执行。类加载器从JDK 1.0就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java Applet需要从远程下载Java类文件到浏览器中并执行。
2. java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类。
3. 除此之外,ClassLoader 还负责加载Java应用所需的资源,如图像文件和配置文件等。
4. 不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。
5. 每个 Java 类都维护着一个指向定义它的类加载器的引用,通过 getClassLoader() 方法就可以获取到此引用
6. 自定义加载器时,务必复写findClass类而不是loadClass类,因为loadClass封装了双亲委托代理(调用父类加载器).
从Java虚拟机的角度来说,只存在两种不同的类加载器:
1. 一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机中),是虚拟机自身的一部分;
2. 另一种就是所有其他的类加载器,这些类加载器都有Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader。
从开发者的角度,类加载器可以细分为:
1. 启动(Bootstrap)类加载器:负责将 Java_Home/lib下面的类库(核心库)加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
2. 标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
3. 应用程序(Application)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般称为系统(System)加载器。AppClassLoader is typically the class loader used to start the application,并且被设置为启动线程的context-loader
双亲委托模型
1. 双亲委派模式是在Java 1.2后引入的,其工作原理的是,若类加载器要加载类,首先会委派父类去加载,父类递归向上委托父类的父类去加载,如果都加载不了的话,自己才去加载,
2. 双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器.
3. 双亲委派模型是一种组合关系(使用parent联系),而不是继承关系
4. 双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系,保证 Java 核心库的类型安全
5. 双亲委派模式很好地解决了各个类加载器的基础类统一问题,越基础的类由越上层的类加载器进行加载,但是这个基础类统一有一个不足,当基础类想要调用回下层的用户代码时无法委派子类加载器进行类加载。为了解决这个问题JDK引入了ThreadContext线程上下文,通过线程上下文的setContextClassLoader方法可以设置线程上下文类加载器
线程上下文加载器
1. SPI接口,Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,虽然SPI接口是核心库的一部分,按理说应该由BootStrap-classloader来加载,很明显这些SPI的实现类(第三方实现)Bootstrap-classloader找不到.
2. 线程上下文类加载器正好解决了这个问题。线程上下文类加载器是从 JDK 1.2 开始引入的,如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。
3. 线程上下文加载器没有遵循双亲委托模型
4. web容器的特殊加载器, web容器(tomcat等)类加载器的实现逻辑与一般的应用不同,每个web应用都有自己对应的类加载器实例(保证隔离),该加载器会首先尝试加载某个类,等加载不到时再代理给父类加载器,这与一般加载器的顺序是相反的,目的是"保证Web应用自己类的优先级高于Web容器提供的类"(JAVA核心类库除外)
5. web容器加载器也没有遵从双亲委托模型
java.lang.ClassLoader#loadClass加载类的顺序
1. 首先调用findLoadedClass判断class是否已经加载
2. 若calss未加载时调用parent.loadClass,
3. 若parent为null时,调用JVM自带的加载器即BootStrapClassLoader.findBootstrapClass,注意BootStrapClassLoader不是JAVA类,而是C++写的native方法
4. 若父类和启动类不能完成加载任务,才调用自己的加载功能.(tomcat是先自己加载,自己加载不了的再交给父类,核心库除外)
5. 同理ClassLoader另一个重要作用是加载Resource,其加载资源的方式相同,都是调用parent.getResources(name),如果parent为null时,调用getBootstrapResources(name)
常用的几种架构模式
1. 分层架构
a) 最常用的架构方式,对层的数量没有具体限制,一般包括:展现层,业务层,持久化层,数据库层.
b) 优点: 各个层之间交换很少,支持可移植性,复用性
c) 缺点: 分层导致性能下降,增加系统复杂性,多用于小型简单的应用程序(网站)
2. 管道 - 过滤器架构
a) 最常用于管道-过滤器(filter)模式, Filters包含一组filter组件.
b) 用途:多用于简单单项处理的任务,如ETL,工具, 编译器语法,词义分析等.
3. 客户端 - 服务器架构
a) 对共享资源,服务的大量分布式调用,如外业务线的接口,邮件服务器,共享文档等.
b) 弱点:服务器会成为性能瓶颈,单点故障的问题
4. 模型 - 视图 - 控制器架构
a) 典型的MVC架构,用户页面被频繁修改, model层就是用户要展示的数据,view可以使用多种方式展示model数据(table,柱状图,饼图)等
b) 控制器用来在model与view层进行切换,
c) 适合稍微复杂的网站及移动应用程序的开发.
5. 事件驱动架构
a) 为事件处理单独部署事件进程或处理器, 调度器从队列中拉取事件分配给合适的事件处理器
b) 弱点:性能和错误恢复是问题
c) Demo: netty的事件处理
6. 微服务架构
a) 将应用拆分为服务套件,每个服务单独部署和扩展,甚至不同的服务可以使用不同的语言,管理不同的数据库.
b) 弱点:必须容忍服务失败,需要更多的监控, 服务编排和事件协作开销较大
Redis相关
Redis是单线程的,Redis速度为啥这么快?
1、 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、 数据结构简单,Redis中的数据结构是专门设计的,如没有使用C字符串,而是使用简单动态字符串(Simple Dynamic String),加入了free,len字段;
3、 基于内存,CPU不是Redis的瓶颈,采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、 使用多路I/O复用模型,非阻塞IO;这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程
5、 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
Redis持久化机制:AOF,RDB;
1、 RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小,缺点是做不到实时
2、 AOF:之策秒级持久化,兼容性好,缺点是文件大,速度慢
3、 Redis 4.0混合持久化: AOF不在存储全量数据,而是定时将AOF数据与RDB合并作为新的RDB, AOF只存储增量数据.
4、 redis缓存击穿的处理: 使用互斥锁mutex(setnx),加锁排队,或者及时数据库查不到也要写null到redis,只不过过期时长设置短一点.
5、 redis管道:将命令一次性的传输到redis中执行, 不过不保证原子性.
Redis底层数据类型
1. String类型采用SOS(有长度数据,字符串扩展方便)
2. 列表,3.2之后使用quicklist: 之前使用ziplist,linkedlist
a) ziplist:类似数组适用于整数和短字符串,内存占用小.内存连续,插入删除效率低
b) linkedList:双向链表,插入删除简单
c) 列表存储使用quickList(ZipList和linkedList的组合)每个节点都ziplist的,类似使用链表将数组串一块
3. map:数据小用ziplist,数据多用hashtable
4. set:使用Hashtable
redis过期策略:
1、 定时删除:在设置键的过期时间的同时,创建一个定时任务,当键达到过期时间时,立即执行对键的删除操作
2、 惰性删除:放任键过期不管,但在每次从键空间获取键时,都检查取得的键是否过期,如果过期的话,就删除该键,如果没有过期,就返回该键
3、 定期删除:每隔一点时间,程序就对数据库进行一次检查,删除里面的过期键,至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
redis淘汰策略,参数:maxmemory-policy
1. noeviction:不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
2. volatile-lru:尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。
3. volatile-ttl:跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。
4. volatile-random:跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。
5. allkeys-lru:区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
6. allkeys-random:跟上面一样,不过淘汰的策略是随机的 key。
7. 备注: volatile-xxx 策略只会针对带过期时间的 key 进行淘汰,allkeys-xxx 策略会对所有的 key 进行淘汰
缓存一些问题
缓存雪崩: 大量缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。解决方案:永不过期,或者过期时间设置不同
缓存击穿: 指的是热点key在某个特殊的场景时间内恰好失效了,恰好有大量并发请求过来了,造成DB压力。解决方案:使用锁
缓存穿透: 布隆过滤器做第一个拦截,穿透后及时补充
redis一些问题:
1. redis数据一致性保证: 全量预热+增量双写redis; 读取binlog,利用消息队列异步更新redis缓存(类似mysql主从复制)
2. redis统计日活:使用set,使用bitmap(偏移量),100W数据就是100W/8=125K个字节.
jedis 和 Redisson 区别
1、 jedis:提供了比较全面的 Redis 命令的支持。
2、 Redisson:是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象,大量使用lua脚本,与 jedis 相比 Redisson 的功能相对简单,不支持排序、事务、管道、分区等 Redis 特性。
集群相关
@Value不能给静态变量赋值
变通方式: 单独设置一个非静态方法,方法内对变量进行赋值, 并且当前类要交给spring管理(@componet注解)
@Value(value="${local.file.temp.dir}")
public void setSavePath(String savePath){
SAVE_PATH = savePath;
}
问: reactor模型:
实际上的Reactor模式,是基于Java NIO的,在他的基础上,抽象出来两个组件——Reactor和Handler两个组件:
1)Reactor:负责响应IO事件,当检测到一个新的事件,将其发送给相应的Handler去处理;新的事件包含连接建立就绪、读就绪、写就绪等。
2)Handler:将自身(handler)与事件绑定,负责事件的处理,完成channel的读入,完成处理业务逻辑后,负责将结果写出channel。
3) IO多路复用,即Reactor设计模式,与同步阻塞相似,唯一区别是操作系统为用户提供同时轮询多个IO句柄来查看是否有IO事件的接口(select),从跟不上是允许用户可以使用单个线程管理多个IO句柄
线程模型分以下三种:
1. 单线程Reactor: Reactor模式下的accecpt,I/O操作都由一个线程来执行.
2. 多线程Reactor: Acceptor用来监听客户端请求,I/O操作由单独的线程池来执行.
3. 主从多线程Reactor:Acceptor由线程池来执行,I/O操作由另一个线程池来执行.(Acceptor仅仅完成登录、握手和安全认证等操作,IO操作和业务处理依然在后面的从线程中完成)
问:Netty Reactor线程模型
Netty中的Reactor模型:包括两个NIOEventloop: "Acceptor pool","IO Thread Pool",通过配置线程池个数,是否共享线程池来切换不同模式.
1. Acceptor中的NioEventLoop用于接收TCP连接,初始化参数
2. I/O线程池中的NioEventLoop
1) 异步读取通信对端的数据报,
2) 发送读事件到channel
3) 异步发送消息到对端,调用channel的消息发送接口
4) 执行系统调用Task
5) 执行定时Task
private EventLoopGroup boss = new NioEventLoopGroup(100);
private EventLoopGroup work = new NioEventLoopGroup(100);
ServerBootstrap bootstrap = new ServerBootstrap()
.group(boss,work).childHandler(new HeartbeatInitializer());
问: select,poll,epoll
select,poll,epoll都是IO多路复用的机制。
I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd(当管理数十万个连接时,仅有少数连接时活跃的),这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
select函数:改函数允许进程指示内核等待多个事件中的任何一个发生的时候或者在一定时间之后被唤醒,select有个致命的缺点即在多路复用中文件描述符的数量有限制,
poll函数:poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多,区别是poll没有最大描述符限制。
epoll函数:epoll在linux2.6内核中被提出来,是之前的select和poll的增强版本。epoll也没有文件描述符数量限制,而且是用一个文件描述符来管理多个描述符。在性能上相比上面两种有了很大的优化。
epoll优势总结:
1) select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
2) select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
问: selector:
1. Selector 可使一个单独的线程管理多个 Channel,
2. open 方法可创建 Selector,register 方法向多路复用器器注册通道,
3. 可以监听的事件类型:读、写、连接、accept。
4. 注册事件后会产生一个 SelectionKey:它表示 SelectableChannel 和 Selector 之间的注册关系
NIO 的服务端建立过程:
1. Selector.open():打开一个 Selector;
2. ServerSocketChannel.open():创建服务端的 Channel;
3. bind():绑定到某个端口上。并配置非阻塞模式;
4. register():注册Channel 和关注的事件到 Selector 上;
5. select(): 轮询拿到已经就绪的事件
Netty特点:
1. 线程:单线程多任务的Reactor模型,大量使用了 volitale、使用了 CAS 和原子类、线程安全类的使用、读写锁的使用,
2. 单线程多任务,串行无锁化设计,消息的处理尽可能在同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优。
3. 网络层:使用高效socket底层,重写的byteBuff,支持零拷贝,解决epoll空转cpu高的bug,采用多种 decoder/encoder 支持,对 TCP 粘包/分包进行自动化处理
4. GC方面: TCP 接收和发送缓冲区使用直接内存代替堆内存,通过内存池的方式循环利用 ByteBuf通过引用计数器及时申请释放不再引用的对象,降低了 GC 频率
Netty零拷贝:
Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝.
问: event loop selector过程:
首先oldWakenUp = wakenUp.getAndSet(false)
如果队列中有任务, selectNow()
如果没有select(),直达channel准备就绪,但此过程中循环次数超过限值也将rebuidSelectoror退出循环(避免了CPU空转的bug)
执行processSelectedKeys和runAllTasks
问: NioEventLoop
答:
NioEventLoop是Netty的Reactor线程,它在Netty Reactor线程模型中的职责如下:
1) NioEventLoop是一个单线程的,所以,一个 NioEventLoop 其实和一个特定的线程绑定, 并且在其生命周期内, 绑定的线程都不会再改变。
2) boss reactor处理accept事件,将新连接封装成channel对象扔给worker reactor,worker reactor处理连接的读写事件。
3) eventLoop是一个Executor,可以调用execute给eventLoop提交任务,NioEventLoop会在runAllTasks执行。NioEventLoop内部分为普通任务和定时任务,在执行过程中,NioEventLoop会把过期的定时任务从scheduledTaskQueue转移到taskQueue中,然后执行taskQueue中的任务,同时每隔64个任务检查是否该退出任务循环。
4) 作为服务端Acceptor线程,负责处理客户端的请求接入
5) 作为客户端Connecor线程,负责注册监听连接操作位,用于判断异步连接结果
6) 作为IO线程,监听网络读操作位,负责从SocketChannel中读取报文
7) 作为IO线程,负责向SocketChannel写入报文发送给对方,如果发生写半包,会自动注册监听写事件,用于后续继续发送半包数据,直到数据全部发送完成
8) NioEventLoop的处理链是由一组Handler来执行的,处理链中的处理方法是串行化执行的
9) 一个客户端连接只注册到一个NioEventLoop上,避免了多个IO线程并发操作
问: Netty Reactor线程模型中的task有几种
答:
有两种Task,系统Task和定时Task
1. 系统Task:创建它们的主要原因是,当IO线程和用户线程都在操作同一个资源时,为了防止并发操作时锁的竞争问题,将用户线程封装为一个Task,在IO线程负责执行,实现局部无锁化
2. 定时Task:主要用于监控和检查等定时动作
问: EventExecutorGroup:
答:
1) 提供管理EevntLoop的能力,他通过next()来为任务分配执行线程,同时也提供了shutdownGracefully这一优雅下线的接口
2) EventLoopGroup的实现中使用next().register(channel)来完成channel的注册,即将channel注册时就绑定了一个EventLoop,然后EvetLoop将channel注册到EventLoop的Selector上。
3) 线程池对IO线程进行资源管理,是通过EventLoopGroup实现的。线程池平均分配channel到所有的线程(循环方式实现,不是100%准确),一个线程在同一时间只会处理一个通道的IO操作,这种方式可以确保我们不需要关心同步问题。
4) BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护着一个注册了ServerSocketChannel的Selector(boss)实例,
5) BoosEventLoop不断轮询Selector将连接事件分离出来,通常是OP_ACCEPT事件,然后将accept得到的SocketChannel交给WorkerEventLoopGroup,WorkerEventLoopGroup会由next选择其中一个EventLoopGroup来将这个SocketChannel注册到其维护的Selector[work eventloop]并对其后续的IO事件进行处理。在Reactor模式中BossEventLoopGroup主要是对多线程的扩展,而每个EventLoop的实现涵盖IO事件的分离,和分发(Dispatcher)。
6) 小结
a) NioEventLoopGroup 实际上就是个线程池,一个 EventLoopGroup 包含一个或者多个 EventLoop;
b) 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
c) 所有有 EnventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
d) 一个 Channel 在它的生命周期内只注册于一个 EventLoop;
e) 每一个 EventLoop 负责处理一个或多个 Channel
Netty核心组件:
1) Bootstrap:netty的辅助启动器,netty客户端和服务器的入口,Bootstrap是创建客户端连接的启动器,ServerBootstrap是监听服务端端口的启动器,跟tomcat的Bootstrap类似,程序的入口。
2) Channel:关联jdk原生socketchannel的组件,常用的是NioServerSocketChannel和NioSocketChannel,NioServerSocketChannel负责监听一个tcp端口,有连接进来通过boss reactor创建一个将socketchanlel包装为NioSocketChannel将其绑定到worker reactor,然后worker reactor负责这个NioSocketChannel的读写等io事件。
3) EventLoop:netty最核心的几大组件之一,就是我们常说的reactor,人为划分为boss reactor和worker reactor。通过EventLoopGroup(Bootstrap启动时会设置EventLoopGroup)生成,最常用的是nio的NioEventLoop,就如同EventLoop的名字,EventLoop内部有一个无限循环,维护了一个selector,处理所有注册到selector上的io操作,在这里实现了一个线程维护多条连接的工作。
4) ChannelPipeline:netty最核心的几大组件之一,ChannelHandler的容器,netty处理io操作的通道,与ChannelHandler组成责任链。write、read、connect等所有的io操作都会通过这个ChannelPipeline,依次通过ChannelPipeline上面的ChannelHandler处理,这就是netty事件模型的核心。ChannelPipeline内部有两个节点,head和tail,分别对应着ChannelHandler链的头和尾。
5) ChannelHandler:netty最核心的几大组件之一,netty处理io事件真正的处理单元,开发者可以创建自己的ChannelHandler来处理自己的逻辑,完全控制事件的处理方式。ChannelHandler和ChannelPipeline组成责任链,使得一组ChannelHandler像一条链一样执行下去。ChannelHandler分为inBound和outBound,分别对应io的read和write的执行链。ChannelHandler用ChannelHandlerContext包裹着,有prev和next节点,可以获取前后ChannelHandler,read时从ChannelPipeline的head执行到tail,write时从tail执行到head,所以head既是read事件的起点也是write事件的终点,与io交互最紧密。
6) Unsafe:顾名思义这个类就是不安全的意思,但并不是说这个类本身不安全,而是不要在应用程序里面直接使用Unsafe以及他的衍生类对象,实际上Unsafe操作都是在reactor线程中被执行。Unsafe是Channel的内部类,并且是protected修饰的,所以在类的设计上已经保证了不被用户代码调用。Unsafe的操作都是和jdk底层相关。EventLoop轮询到read或accept事件时,会调用unsafe.read(),unsafe再调用ChannelPipeline去处理事件;当发生write事件时,所有写事件都会放在EventLoop的task中,然后从ChannelPipeline的tail传播到head,通过Unsafe写到网络中。
问: BootsStrapping如何使用
答: 它有两种类型,一种用于Client端:BootsStrap,另一种用于Server端:ServerBootstrap,要想区别如何使用它们,你仅需要记住一个用在Client端,一个用在Server端。
1. ServerBootstrap用于Server端,通过调用bind()方法来绑定到一个端口监听连接;
2. Bootstrap用于Client端,需要调用connect()方法来连接服务器端,但我们也可以通过调用bind()方法返回的ChannelFuture中获取Channel去connect服务器端。
3. 客户端的Bootstrap一般用一个EventLoopGroup,而服务器端的ServerBootstrap会用到两个(这两个也可以是同一个实例)。为何服务器端要用到两个EventLoopGroup呢?这么设计有明显的好处,如果一个ServerBootstrap有两个EventLoopGroup,那么就可以把第一个EventLoopGroup用来专门负责绑定到端口监听连接事件,而把第二个EventLoopGroup用来处理每个接收到的连接,
Selector BUG:
1. 若 Selector 的轮询结果为空,也没有 wakeup 或新消息处理,则发生空轮询,CPU 使用率 100%,
2. netty解决方式:
a) 对 Selector 的 select 操作周期进行统计,每完成一次空的 select 操作进行一次计数,若在某个周期内连续发生 N 次空轮询,则触发了 epoll 死循环 bug。
b) 重建Selector,将原 SocketChannel 从旧的Selector 上去除注册,重新注册到新的 Selector 上,并将原来的 Selector 关闭。
问: netty的byteBuf
Netty 使用自建的 buffer API,而不是使用 NIO 的 ByteBuffer 来表示一个连续的字节序列。与 ByteBuffer 相比这种方式拥有明显的优势。
netty中ByteBuf的缓冲区的优势:
需要的话,可以自定义buffer类型(),通过组合buffer类型,可实现透明的零拷贝;
1) 堆内存字节缓冲区(HeapByteBuf),内存分配回收受JVM控制,多了一次内存复制(内核空间/用户空间)
2) 直接内存字节缓冲区(DirectByteBuf)
ByteBuf和Jdk自带的ByteBuffer区别
有读和写两个指针,而ByteBuffer只有一个指针,需要通过flip()方法在读和写之间进行模式切换,需要操作的越多往往犯错的概率就越大。ByteBuf将读和写进行了分离,使用者不用再关心现在是读还是写的模式,可以把更多的精力用在具体的业务上
ByteBuf主要是通过两个指针进行数据的读和写,分别是 readerIndex 和 writerIndex ,并且整个ByteBuf被这两个指针最多分成三个部分,分别是可丢弃部分,可读部分和可写部分
提供动态的buffer类型,如StringBuffer一样,容量是按需扩展;无需调用flip()方法;
通常比ByteBuffer快。
Netty的zero-copy则是完全在用户态,Netty通过ByteBuf.slice以及Unpooled.wrappedBuffer等方法拆分、合并Buffer无需拷贝数据
问:BIO,NIO区别
1. BIO: 是面向流(stream)的,流是单向的 一个连接,一个线程,BIO的各种流是阻塞的
2. NIO:是面向缓冲区的,请求都注册到多路复用器上(Selector),NIO是面向通道的,通道都是双向的
NIO 的特点:
1. 基于 block 的传输比基于流的传输更高效,
2. Channel:channel双向通讯,表示 IO 源与目标打开的连接,是双向的,但不能直接访问数据,只能与 Buffer进行交互
3. 事件驱动模型、单线程处理多任务、非阻塞 I/O,I/O 读写不再阻塞,而是返回0、
4. 更高级的 IO 函数 zero-copy、IO 多路复用大大提高了 Java 网络应用的可伸缩性和实用性。
基于 Reactor 线程模型。
在 Reactor 模式中,事件分发器等待某个事件或者可应用或个操作的状态发生,事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。
如在 Reactor 中实现读:注册读就绪事件和相应的事件处理器、事件分发器等待事件、事件到来,激活分发器,分发器调用事件对应的处理器、事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
问: 压缩文件格式
GZIP比较适合对单个文件压缩,
ZIP适合对多个文件进行压缩。
问: volatile变量
确保所有线程看见相同的值,不允许保持volatile变量到本地副本.
valatile只是在线程和内存之间同步变量的值,速度快,代价低.而同步方法则需要线程所有变量与内存之间提供同步,代价更高
缓存一致性问题:当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
volatile只保证并发的可见性,单不保证操作的原子性(如自增操作)
volatile会保证有序性. volatile语句前及后的语句可能会重排, 但volatile前的语句不能重排的v之后.[原理是汇编是加入lock前缀,相当于内存栅栏,内存栅栏钱的数据会立即存入主存,]
问:线程安全的集合
ConcurrentHashMap;
ConcurrentSkipListMap;
ConcurrentSkipListSet;
ConcurrentLikedQueue;
ArrayList;HashMap 使用包装器变成线程安全的.Collections.synchronizedList(new ArrayList<E>()); 推荐使用Concurrent类里的集合,而不是类包装器的集合
问: 线程同步器
CyclicBarrier(同步屏障): 当一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续执行
Semaphone(信号量): 用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源
CountDownLatch : 倒计时门闩计数为0时,开门, 一次性使用.
Exchanger :变量交换值
SynchronousQueue: 阻塞队列
问: JVM
GC Root包括如下几种对象:
局部变量表中引用对象,
静态属性引用的对象,
方法区常量引用的对象,
本地方法栈中(JNI)引用的对象.
内存分配方法:
1. 静态申请(编译时申请),
2. 动态申请(堆区运行时产生的各种对象,需要GC介入管理)
垃圾检测算法:
计数器,
可达性分析法
垃圾回收算法包括:
标记清除,标记整理,复制,分代收集
回收器:
新生代包括:serial,ParNew,Parallel Scavange
老年代包括:CMS,Serial Old,Parallel,备注老年代都用标记整理算法
CMS:
对CPU敏感, 最大限度减少对用户线程的影响,无法处理浮动垃圾(需要定时碎片整理),新生代使用标记清除算法
过多垃圾清理方法
1. CMS是一款基于“标记—清除”算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。
2. 为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),
a) fullGC时开启内存碎片合并整理,
b) XX:CMSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)
运行过程包括:
对象内存布局包括: 对象头+对象实例+对齐填充
对象头包括:markword+class指针,其中markword包括:GC信息,锁状态,持有锁,偏向锁线程id,
对象内存分配包括:
指针碰撞法(用于规整的内存-如标记整理后的内存,复制算法后的内存等),
空闲列表:由JVM维护一个空闲列表,在表中查找合适位置,伺机而入.
问: classloader相关
1. 类加载器的意义: 优化性能,可以按需加载类,可实现资源回收,对于装载很多web应用的程序,可以做到类的隔离
2. 双亲委派模型:避免加载相同名称的类,优先使用父类的classloader加载
3. classloader顺序:BootStrapClassLoader > ExtensionClassLoader > AppClassLoader > 自定义ClassLoader
4. .class语法来获取类的引用不会引发初始化,Class.forName() 立即就进行初始化。
类的生命周期:
问:ServletContext对象
1. 一个WEB应用中的所有Servlet共享同一个ServletContext对象(spring mvc中表现为ApplicationContext),因此Servlet对象之间可以通过ServletContext对象来实现通讯。
2. ServletContext对象通常也被称之为context域对象。
3. ServletContext,是一个全局的储存信息的空间,服务器开始,其就存在,服务器关闭,其才释放。
this.getServletConfig().getServletContext();
context.setAttribute("data", data);
问: 拦截件(interceptor)和过滤器(filter区别)
答:
1 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
2 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
3 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
4 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
5 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
CAP理论:
1. 一致性(Consistency):任何一个读操作总是能读取到之前完成的写操作结果,也就是在分布式环境中,多点的数据是一致的;
2. 可用性(Availability):每一个操作总是能够在确定的时间内返回,也就是系统随时都是可用的。
3. 分区容忍性(Partition Tolerance): 在出现网络分区(比如断网)的情况下,分离的系统也能正常运行。
CAP 并不是一个普适性原理和指导思想,它仅适用于原子读写的NoSql 场景中,并不适用于数据库系统。
Base理论
Basically available,soft-state,Eventually Consistent.、系统基本可用、软状态、数据最终一致性
相对于CAP 来说,它大大降低了我们对系统的要求。
倒排索引
相当于正排索引, 倒排索引是把原来作为值的内容拆分作为索引的key,而原来作为索引的key则变成了倒排索引的值value, [key,value互换位置了], 这也是倒排索引比数据库的like更高效的原因..而倒排索引的关键是, 需要对搜索的内容进行分词.
灰度发布
灰度发布是对新应用进行分批发布,持续时间可能会比较长.....
常用限流算法
漏桶算法: 无预留容量,能保证输出数据稳定,容量满之后直接抛弃
令牌桶算法: 有预留容量. 可以容纳一定的突发容量.
使用方式: 使用谷歌Guava库
KeepAlived
Keepalived高可用对之间是通过 VRRP进行通信的, VRRP是遑过竞选机制来确定主备的,主的优先级高于备,因此,工作时主会优先获得所有的资源,备节点处于等待状态,当主挂了的时候,备节点就会接管主节点的资源,然后顶替主节点对外提供服务。
推荐使用方式: LVS-DR+keepalived方案,推荐最优方案,简单、易用、高效.
问:java的输出流都有哪些?
答:
OutputStream, 是其他输出流的基类,提供把数据写到输出设备的基本协议。
PrintStream, 适合输出文本数据 ,System.out是PrintStream类的一个实例
BUfferedOutputStream 对缓冲提供支持
DataOutputStream 用于输出原始数据类型,如整点或者浮点数
FileOutputStream 对数据输出到文件提供支持
FileOutputStream/FileInputStrem读取的是二进制流,
FileReader/FileWriter是针对文字做处理
Java 装载顺序
在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,
其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
1. 装载:查找和导入类或接口的二进制数据
2. 链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的
3. 校验:检查导入类或接口的二进制数据的正确性;
4. 准备:给类的静态变量分配并初始化存储空间;
5. 解析:将符号引用转成直接引用;
6. 初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
初始化类中属性是静态代码块的常用用途,但只能使用一次。
对象初始化顺序:
静态代码块内容先执行,接着执行父类非静态代码块和构造方法,然后执行子类非静态代码块和构造方法。
问:匿名内部类
匿名内部类是唯一一种没有构造器的类。
正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。
匿名内部类在编译的时候由系统自动起名为Outter$1.class。
一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
内部类相关
1. 主要是解决了多继承的问题,继承具体或抽象类
2. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
3. 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口或继承同一个类。
4. 创建内部类对象的时刻并不依赖于外围类对象的创建。
5. 内部类并没有令人迷惑的is-a关系,它就是一个独立的实体。
6. 在方法间定义的非静态内部类:
a) 外围类和内部类可互相访问自己的私有成员。
b) 内部类中不能定义静态成员变量。
c) 在外部类作用范围之外向要创建内部类对象必须先创建其外部类对象
7. 在方法间定义的静态内部类:
a) 只能访问外部类的静态成员。
b) 静态内部类没有了指向外部的引用
问:为什么局部内部类和匿名内部类只能访问局部final变量
1. 局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。
2. 如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。
3. 局部变量要用final修饰,这样在内部类编译时会创建一个内部copy,防止了数据不一致
4. 形参也要用final修饰,这要求不允许对形参变量进行修改,防止数据不一致的问题。
问:接口中如何定义字段
答:接口中的常量隐式声明为“public,statistic,final”的,且必须要初始化
问:数据库连接池HiKariCP
1. HiKariCP是数据库连接池的一个后起之秀,号称性能最好,可以完美地PK掉其他连接池,是一个高性能的JDBC连接池,基于BoneCP做了不少的改进和优化
优点:
2. 字节码精简 :优化代码,直到编译后的字节码最少,这样,CPU缓存可以加载更多的程序代码;
3. 优化代理和拦截器 :减少代码,例如HikariCP的Statement proxy只有100行代码,只有BoneCP的十分之一;
4. 自定义数组类型(FastStatementList)代替ArrayList :避免每次get()调用都要进行range check,避免调用remove()时的从头到尾的扫描;
5. 自定义集合类型(ConcurrentBag :提高并发读写的效率;
6. 其他针对BoneCP缺陷的优化。
问: HikariCP与Druid相比哪个更好?
一个追求性能,一个偏向监控
web服务器选择:
互联网公司tomcat多
银行和有钱的单位用weblogic和WebSphere多
ClassLoader不会初始化类,
使用ClassLoader类的 loadClass方法来加载类时,只是加载该类,而不会执行该类的初始化!!使用Class的forName()静态方法,才会导致强制初始化该类。
问: j2se, j2ee
J2SE,Java 2 Platform Standard Edition,我们经常说到的JDK,就主要指的这个,它是三者的基础,属于桌面级应用开发
springboot内置服务器有哪些?
SpringBoot包括对内置Tomcat、Jetty和Undertow服务器的支持
问:什么是跨域
答:跨域问题是由于javascript语言安全限制中的同源策略造成的.
简单来说,同源策略是指一段脚本只能读取来自同一来源的窗口和文档的属性,这里的同一来源指的是主机名、协议和端口号的组合.
build
是指一个项目build的过程
validate 验证项目是否正确以及必须的信息是否可用
compile 编译源代码
test 测试编译后的代码,即执行单元测试代码
package 打包编译后的代码,在target目录下生成package文件
integration-test 处理package以便需要时可以部署到集成测试环境
verify 检验package是否有效并且达到质量标准
install 安装package到本地仓库,方便本地其它项目使用
deploy 部署,拷贝最终的package到远程仓库和替他开发这或项目共享,在集成或发布环境完成
Goal代表一个特殊的任务
mvn package表示打包的任务,通过上面的介绍我们知道,这个任务的执行会先执行package phase之前的phase
mvn deploy表示部署的任务
mvn clean install则表示先执行clean的phase(包含其他子phase),再执行install的phase
问: varchar, nvarchar
varchar(N)。存储变长数据,N代表最大字节长度(注意一个中文要占用两个字节),存储效率没有CHAR高。VARCHAR类型的实际长度是它的值的实际长度+1。多出一个字节用于保存实际使用了多大的长度,
nvarchar(N),N代表存储字符的个数(都是unicode字符,每个字符串占两个字节) ,好处:不用担心输入的字符是英文还是汉字,较为方便,但在存储英文时数量上有些损失,最多存储4000个字符。
小结:
varchar在SQL Server中是采用单字节来存储数据的,nvarchar是使用Unicode来存储数据的(两个字节).中文字符存储到SQL Server中会保存为两个字节(一般采用Unico编码),英文字符保存到数据库中,如果字段的类型为varchar,则只会占用一个字节,而如果字段的类型为nvarchar,则会占用两个字节.
所以在Design的时候应该尽量使用nvarchar来存储数据.只有在你确保该字段不会保存中文的时候,才采用varchar来存储.的时候也不会有问题
举例:
CREATE Table tmp( [topic_name] varchar(10), [topic_name2] nvarchar(10))
INSERT tmp(topic_name,topic_name2)VALUES('这款车的造','是这款车的造型很好')
INSERT tmp(topic_name,topic_name2)VALUES('1234567890','1234567890')
问: 分布式集群中为什么一定要有主节点;
答: 在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,其他的机器可以共享这个结果,这样可以大大减少重复计算,提高性能,所以就需要主节点。
问: JAVA对象分配流程:
栈上分配 -> TLAB(线程本地分配缓存) -> 老年代 -> eden分区
名词解释:
1. 栈上分配: 优点:分配速度快,不用GC参与,技术基础是逃逸分析,(栈上分配依赖于逃逸分析和标量替换)
2. 逃逸分析: 判断对象的作用域是否超出函数体
3. TLAB: 全称Thread Local Allocation Buffer, 即:线程本地分配缓存。这是一块线程专用的内存分配区域。TLAB占用的是eden区的空间。在TLAB启用的情况下(默认开启),JVM会为每一个线程分配一块TLAB区域
4. 标量替换: 没有发生逃逸的对象,对象内的变量被常量替换,该对象不再创建, 相当于该对象被优化为几个分离的常量,单独使用
5. 锁消除: 线程私有的对象不存在资源竞争,虚拟机会自动消除这个对象上的锁。
小结;
1. 没有发生逃逸的对象可以直接分配在虚拟机栈上,随着线程的结束一并回收;
2. 若开启了标量替换规则,且逃逸分析后判定不需要为某个对象分配连续的存储空间,则会将对象进行标量拆分,然后分别存储在栈或寄存器上;
3. 未发生逃逸的对象是线程私有的,不会出现并发竞争资源的情况,所以不需要为对象加锁,对象上的锁会被虚拟机忽略。
--开启逃逸分析,开启标量分配,优先使用栈上分配,关闭tlab
-server -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
//不使用逃逸分析,关闭tlab
-server -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
//使用逃逸分析,关闭标量替换,(未使用栈上分配)
-server -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:-EliminateAllocations
结论;栈上分配依赖逃逸分析+标量替换
问: spring mvc初始化过程
1. 启动方法 x.ContextLoaderListener#contextInitialized,注意传递进来的事件ServletContextEvent,
2. 调用 x.web.context.ContextLoader#initWebApplicationContext初始化监听器
3. ContextLoader#createWebApplicationContext(javax.servlet.ServletContext)实例化XmlWebApplicationContext, 并赋值给ContextLoaderListener.context属性
4. 调用configureAndRefreshWebApplicationContext方, 将SevletContext对象赋值给xmlWebApplication.servletContext属性
5. 调用XmlWebApplicationContext.refresh方法, 重新载入所有bean
spring mvc 运行流程?
1. spring mvc 先将请求发送给 DispatcherServlet。
2. DispatcherServlet 查询一个或多个 HandlerMapping,找到处理请求的 Controller。
3. DispatcherServlet 再把请求提交到对应的 Controller。
4. Controller 进行业务逻辑处理后,会返回一个ModelAndView。
5. Dispathcher 查询一个或多个 ViewResolver 视图解析器,找到 ModelAndView 对象指定的视图对象,视图对象负责渲染返回给客户端。
DispatcherServlet:
DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处
DispatcherServlet作用:
1. 文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
2. 通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
3. 通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);
4. 通过ViewResolver解析逻辑视图名到具体视图实现;
5. 本地化解析;
6. 渲染具体的视图等;
7. 如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。
APM相关
答: APM的全称是Application Performance Monitor,帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题,这就是APM系统。
APM主要解决的问题:
1. 集中式度量系统
2. 分布式全链接追踪系统
3. 集中式日志系统(elk)
常用的APM系统:
1. cat: 大众点评开源,实现方式为代码埋点,自带报表系统,集成时需要入侵代码,地址
2. pinpoint: 韩国团队开发,通过JavaAgent实现,功能丰富,但收集数据过多,性能较差,地址
3. zipkin :推特开源,与spring cloud集成较好,但也需修改代码地址
4. skywalking : 华为吴晟开源,通过JavaAgent实现(本系列使用)
问: SkyWalking相关
答: SkyWalking是apache基金会下面的一个开源APM项目,为微服务架构和云原生架构系统设计。它通过探针自动收集所需的指标,并进行分布式追踪,
SkyWalking主要由三大部分组成:Agent,Collector,Web;
1. Agent:作用是使用JavaAgent字节码增强技术将Agent的代码织入程序中,完成Agent无代码侵入地获取程序内方法的上下文并进行增强和收集信息并发送给Collector;与Collector进行心跳,表明Agent客户端的存活。
2. Collector:作用是维护存活的Agent实例,并收集从Agent发送至Collector的数据,进行处理及持久化(存入es,inluxdb);
3. Web:作用是将Collector收集的参数进行不同维度的展示
部署分两个部分: agent单独部署到应用程序端,用来抓取数据, collector和web部署到一起
agent端配置参数(collector地址)
#collector的端口服务地址;
collector.servers=127.0.0.1:10800 //不用改变
collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:xxxx:11800}
collector+web部署
core:
selector: ${SW_CORE:default}
default:
gRPCHost: ${SW_CORE_GRPC_HOST:0.0.0.0}
gRPCPort: ${SW_CORE_GRPC_PORT:11800}
storage:
selector: ${SW_STORAGE:elasticsearch}
elasticsearch:
clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:lf-log-public1.es6.autohome.com.cn:80}
启动Agent
1. Java agent定义了一些类作为入口点。 这些作为入口点的类需要包含一个静态方法,这些方法会在你原本的Java程序的main方法调用之前被调用:
2. 基于Tomcat的服务(SpringMvc)
a) 在tomcat的bin目录下的setenv.sh中增加如下命令行JAVA_OPTS="$JAVA_OPTS -javaagent:/usr/local/skywalking-agent/skywalking-agent.jar"
b) /usr/local/skywalking-agent/skywalking-agent.jar 这个代表的是skywalking-agent的jar的绝对路径
3. 基于JAR file的服务(SpringBoot)
a) 在启动你的应用程序的命令行中添加 -javaagent 参数. 并确保在-jar参数之前添加它. 例如:
b) java -javaagent:/path/to/skywalking-agent/skywalking-agent.jar -jar yourApp.jar
skywalking性能日志一些概念
1、Trace(追踪):
在广义上,一个trace代表了一个事务或者流程在(分布式)系统中的执行过程。在OpenTracing标准中,trace是多个span组成的一个有向无环图(DAG),每一个span代表trace中被命名并计时的连续性的执行片段。
2、Span(跨度):一个span代表系统中具有开始时间和执行时长的逻辑运行单元。span之间通过嵌套或者顺序排列建立逻辑因果关系。
3、Logs:每个span可以进行多次Logs操作,每一次Logs操作,都需要一个带时间戳的时间名称,以及可选的任意大小的存储结构。
4、Tags:每个span可以有多个键值对(key:value)形式的Tags,Tags是没有时间戳的,支持简单的对span进行注解和补充。
Segment
实质上就是Span数组的封装,为的是更好的表示Java中跨线程间的调用(后续文章将会详细讲到)
- Trace = Segment1 + Segment2 + ...... + SegmentN
- 其中每个Segment所包含的数据:Segment = Span1 + Span2 + ...... + SpanN
HTTP相关
HTTP1.0和HTTP1.1的区别
在HTTP1.0协议中,客户端与web服务器建立连接后,只能获得一个web资源。
在HTTP1.1协议,允许客户端与web服务器建立连接后,在一个连接上获取多个web资源。
客户端在使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示。
(1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
(2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
(3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站(非对称加密)。
(5)Web服务器利用自己的私钥解密出会话密钥。
(6)Web服务器利用会话密钥加密与客户端之间的通信(有了专有秘钥后对称加密DES)。
HTTPS缺点:
1、 存在CA根证书安全的问题,
2、 SSL证书需要额外的费用,功能越强大,费用越高
3、 HTTPS握手阶段比较耗时,并且占用资源比较多
cookie
1、 包括:名字,值,过期时间,路径和域.
2、 若不设置过期时间(会话cookis),则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。
3、 会话cookie一般不存储在硬盘上而是保存在内存里.
4、 服务端可以直接设置cookie,方式:响应头中设置Set-Cookie
HTTP协议组成:
1、 请求报文包括:
a) 请求行 => 包含请求方式,URI,HTTP版本信息
b) 请求头 => 包括Cookie
c) 空白行
d) 请求内容实体
2、 响应报文包括:
a) 状态行:包括HTTP版本,状态吗,状态码短语
b) 响应头
c) 空白行
d) 响应内容
TCP,UDP区别
1.基于连接与无连接,TCP传输前必须建立连接(三次握手),UDP不需要;
2.对系统资源的要求(TCP较多,包头包括20个字节,UDP少-包头包括8字节);
3.UDP程序结构较简单:不需要建立连接,不需要维护状态,不受控制算法控制(TCP为滑动窗口);
4.流模式与数据报模式:UDP面向报文传输,尽最大努力交付,不保证可靠性. ;
5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。
问: 使用LB实现加速HTTP应用
1. TCP复用: LB前端接客户端,后端连接server,同时将多个前台客户端请求都复用一个后端的TCP连接
HTTP复用的是HTTP1.1的协议,意思是同一个client的多次请求在同一个TCP中执行
2. LB实现内容缓存(如常用静态资源等)
3. LB对突发的大流量请求添加缓冲机制,防止全部打到后台Server引起宕机
4. LB代替Server实现HTTP压缩(HTTP1.1协议),提高效率
5. LB+SSL硬件芯片,提高SSL请求的处理,LB与后台Server还是使用HTTP请求
问: HttpCache相关
答:
1.使用 Last-Modified ,日期, 如果服务器日期没有修改,直接 返回304
2.使用ETag ,ETag是一个文件 唯一标识符,只要这个文件发生改变,etag就会变.
3.使用Expries过期时间
额外标签:
Cache-control: public 表示缓存的版本可以被代理服务器或者其他中间服务器识别。
Cache-control: private 意味着这个文件对不同的用户是不同的。只有用户自己的浏览器能够进行缓存,公共的代理服务器不允许缓存。
Cache-control: no-cache 意味着文件的内容不应当被缓存。这在搜索或者翻页结果中非常有用,因为同样的URL,对应的内容会发生变化。
问: Kafka存在丢消息的问题
答:
1. Kafka存在丢消息的问题,消息丢失会发生在Broker,Producer和Consumer三种
2. Broker丢失消息是由于Kafka本身的原因造成的,kafka为了得到更高的性能和吞吐量,将数据异步批量的存储在磁盘中。消息的刷盘过程,为了提高性能,减少刷盘次数,kafka采用了批量刷盘的做法。即按照一定的消息量,和时间间隔进行刷盘。这种机制也是由于linux操作系统决定的。将数据存储到linux操作系统种,会先存储到页缓存(Page cache)中,按照时间或者其他条件进行刷盘(从page cache到file),或者通过fsync命令强制刷盘。数据在page cache中时,如果系统挂掉,数据会丢失
3. 理论上,要完全让kafka保证单个broker不丢失消息是做不到的,只能通过调整刷盘机制的参数缓解该情况。比如,减少刷盘间隔,减少刷盘数据量大小。时间越短,性能越差,可靠性越好(尽可能可靠)。这是一个选择题。
4. producer与broker同步关系分三种,
a) acks=0,producer不等待broker的响应,效率最高,但是消息很可能会丢
b) acks=1,leader broker收到消息后,不等待其他follower的响应,即返回ack
c) acks=-1,leader broker收到消息后,挂起,等待所有ISR列表中的follower返回结果后,再返回ack。-1等效与all
5. Consumer的消费方式主要分为两种:
a) 自动提交offset,Automatic Offset Committing
b) 手动提交offset,Manual Offset Control
问: Mybatis相关
主要组件
1. SqlSessionFactory:SqlSession工厂类,以工厂形式创建SqlSession对象
2. SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;
3. Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护,SimpleExecutor、ReuseExecutor、BatchExecutor
a) SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
b) ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
c) BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。;
4. StatementHandler:封装了JDBC Statement操作,,如设置参数、将Statement结果集转换成List集合。
5. ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数;
6. ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
7. TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换;
8. MappedStatement:MappedStatement维护了一条< select|update|delete|insert>节点的封装;
9. SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回;
10. BoundSql:表示动态生成的SQL语句以及相应的参数信息;
11. Configuration:MyBatis所有的配置信息都维持在Configuration对象之中;
12. MapperProxy: Mapperxy对象实现了JDK动态代理中的InvocationHandler接口,用户定义的Mapper接口被MapperProxy对象代理了,用户Mapper方法被调用时会被mapperProxy拦截, myBatis会自动生成MapperProxy实例, 动态调用sqlsession完成连接数据库任务, 与mapper接口的实例压根没有关系,所以也就不需要再定义Mapper接口的实现类,
MyBatis 中#{}和${}的区别是什么
#{}是预编译处理,${}是字符替换。 在使用 #{}时,MyBatis 会将 SQL 中的 #{}替换成“?”,配合 PreparedStatement 的 set 方法赋值,这样可以有效的防止 SQL 注入,保证程序的运行安全。
MyBatis包括物理分页和逻辑分页:
1、 逻辑分页:一次性读取所有结果,然后在内存中进行分页
2、 物理分页:直接从数据库中查询指定范围数据.
3、 实现方式: RowBounds或者自定义分页控件pagehelper
mybatis一级缓存和二级缓存
1、 一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,它的声明周期是和 SQLSession一致的,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认一级缓存是开启的。
2、 二级缓存:也是基于 PerpetualCache 的 HashMap 本地缓存,不同在于其存储作用域为 Mapper 级别的,如果多个SQLSession之间需要共享缓存,则需要使用到二级缓存,并且二级缓存可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)。
SQL执行过程:
1. DefaultSqlSession根据id在configuration中找到MappedStatement对象(要执行的语句)
2. Executor调用MappedStatement对象的getBoundSql得到可执行的sql和参数列表
3. StatementHandler根据Sql生成一个Statement
4. ParameterHandler为Statement设置相应的参数
5. Executor中执行sql语句
a) 如果是更新(update/insert/delete)语句,sql的执行工作得此结束
b) 如果是查询语句,ResultSetHandler再根据执行结果生成ResultMap相应的对象返回。
其他:
Mybatis的所有操作都是基于一个SqlSession的,而SqlSession是由SqlSessionFactory来产生的,SqlSessionFactory又是由SqlSessionFactoryBuilder来生成的。但是Mybatis-Spring是基于SqlSessionFactoryBean的。
问: 死锁相关
答:
产生死锁的四个必要条件:
一. 互斥条件:所谓互斥就是进程在某一时间内独占资源。
二.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
三.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
四.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
死锁如何避免
1. 打破互斥,允许并行访问资源,如添加读写锁
2. 加锁顺序: 所有线程按照相同的顺序获得锁
3. 加锁超时: 获得锁时加上超时时间.
问:synchronized和Lock的区别
答;
主要相同点:Lock能完成synchronized所实现的所有功能
主要不同点:
1. Lock有比synchronized更精确的线程语义和更好的性能。
2. Lock的锁定是通过代码实现的,而synchronized是在JVM层面上实现的,
3. synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。
4. Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。
5. Lock锁的范围有局限性,块范围,而synchronized可以锁住块、对象、类
问: 多线程如何进行信息交互
答:
1. JVM会为一个使用内部锁(synchronized)的对象维护两个集合,Entry Set(同步队列)和Wait Set(等待队列),也有人翻译为锁池和等待池,意思基本一致。
2. 每一同步对象都包含两个队列(EntrySet(同步锁),waitSet(等待队列)),这两个队列是属于同步对象的,跟线程状态无关
3. wait()和notify()一系列的方法,是属于对象的,不是属于线程的。它们用在线程同步时,synchronized语句块中。
4. 调用同步对象的wait/notify方法时必须获得该对象的锁,并且在Sychronized同步代码块中
5. 当拥有该对象的线程释放锁时(wait),同步队列(EntrySet)中所有线程开始竞争获得锁,
6. notify:从等待队列中唤醒一个到同步队列中,
7. notifyAll:所有等待队列(waitSet)中的线程全部转移到同步队列,同步队列中线程开始竞争获得对象锁
8. notify可能会导致死锁,而notifyAll则不会
9. wait() 应配合while循环使用,不应使用if,
10. notify() 是对notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死锁。正确的场景应该是 WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果唤醒的线程无法正确处理,务必确保继续notify()下一个线程,并且自身需要重新回到WaitSet中.
11. 换言之:
a) wait()意思是说,我等会儿再用这把锁,CPU也让给你们,我先休息一会儿!
b) notify()意思是说,我用完了,你们谁用?
Object.wait, Object.signal,Object.signalall
多线程下使用if而不是while容易造成类似过度消费或者过度生产的情况.所以所有的java书籍都会建议开发者永远都要把wait()放到循环语句里面。
多线程下,wait/notify队列会变的混乱,可能会出现死锁情况(notify连着调用,导致多个线程都处于wait状态),之所以我们应该尽量使用notifyAll()的原因就是,notify()非常容易导致死锁
调用await方法后,将当前线程加入Condition等待队列中。当前线程释放锁。否则别的线程就无法拿到锁而发生死锁。自旋(while)挂起,不断检测节点是否在同步队列中了,如果是则尝试获取锁,否则挂起。当线程被signal方法唤醒,被唤醒的线程将从await()方法中的while循环中退出来,然后调用acquireQueued()方法竞争同步状态。
对于Entry Set:如果线程A已经持有了对象锁,此时如果有其他线程也想获得该对象锁的话,它只能进入Entry Set,并且处于线程的BLOCKED状态。
对于Entry Set中的线程,当对象锁被释放的时候,JVM会唤醒处于Entry Set中的某一个线程,这个线程的状态就从BLOCKED转变为RUNNABLE。
AQS:
1. AQS是java并发包的基础类。比如:ReetrantLock ,ReentrantReadWriteLock 都是基于AQS来实现的。ReentrantLock 实现加锁和锁释放就是通过AQS来实现的。
2. AQS内部实现了两个队列,一个同步队列,一个条件队列。
a) 同步队列的作用是:当线程获取资源失败之后,就进入同步队列的尾部保持自旋等待,不断判断自己是否是链表的头节点,如果是头节点,就不断参试获取资源,获取成功后则退出同步队列。
b) 条件队列是为Lock实现的一个基础同步器,并且一个线程可能会有多个条件队列,只有在使用了Condition才会存在条件队列,等待队列满足条件后,放到同步队列
3. AQS 中维护了一个很重要的变量 state, 它是int型的,表示加锁的状态,初始状态值为0;变量:exclusiveOwnerThread维护独占锁状态
4. 当线程1调用lock方法时,首先看 AQS 的 state 是否为0,如果是0的话,通过cas操作将state置为1,并且设置独占线程为当前线程
5. 如果这时候线程1 要调用另外一个lock方法,就像我上面的例子那样,那么线程1会发现 state = 1,它再去看独占线程是不是就是自己,如果是的话 state + 1 ,获取锁成功。
6. 加锁过程:
7. 如果线程1 执行的方法还没有完成即锁还没有释放,此时线程2调用lock方法,由于线程1没有释放锁,那么state不会等于0,且独占线程是线程1而不是自己(线程2),所以AQS会把线程2放到等待队列的尾部,如果线程2的前置结点是头结点head,那么线程2会通过死循环一直去获取锁,如果还是获取不到锁,那么会阻塞住线程2,下面的图有点问题啊。如果不是头结点那么就会阻塞线程2,等待线程1释放锁且唤醒它。
Condition:
1. await()对应于Object#wait(),signal()对应于Object#notify(),signalAll()对应于Object#notifyAll()。
2. Condition是与Lock结合使用的,通过Lock.newCondition()方法能够创建与Lock绑定的Condition实例。
3. Lock和Condition的关系就如同 Object.wait(),Object.notify()方法和synchronized一样,它们都可以配合使用完成对多线程协作的控制。
4. Condition内部有一个等待队列,await是将线程加入等待队列,signal是取队列第一个node(线程)transfer到AQS的同步列表(wait列表),等待竞争对象锁.
问:sleep和wait的区别
1. sleep()方法是Thread类中方法,而wait()方法是Object类中的方法。
2. sleep是让当前线程指定休眠时间,然后继续工作不释放锁,
线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行
3. wait则是等待,直到有线程通知notify()唤醒他才会重新工作。释放锁
问:线程池相关
答:
线程池举例子
假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;
当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;
如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;然后就将任务也分配给这4个临时工人做;
如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。
当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。
线程池执行过程
1. 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
2. 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
3. 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
4. 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
备注: 注意线程池的队列是属于线程池的, 其余线程都要从队列中获取线程,所以这个队列阻塞并发队列ArrayBlockingQueue/LinkedBlockingQueue
线程池所有任务都在阻塞队列中,
每个线程被包装为work类, 继承自AQS,实Runnable接口,启动之后一直在while循环中等待,并被阻塞在getTask()方法中.
创建线程池
1. newCachedThreadPool使用的是SynchronousQueue
2. newFixedThreadPool使用的是LinkedBlockingQueue
3. newScheduledThreadPool使用的是DelayedWorkQueue(PriorityQueue(内部为小头堆))
4. ScheduledThreadPoolExecutor包括两个核心内部类
a) DelayedWorkQueue: 是一个延时blocking队列,内部使用二叉树(小头堆来实现按照到期时间排序),类似priorityBlockingQueue
b) ScheduledFutureTask:将callable包装为此类,并重写了run方法,每次执行之后判断task是否是循环(isPeriodic),是的话重新计算时间并重新加入队列
问: 如何停止一个线程
答:
1. 正常退出,也就是当run方法完成后线程终止。
2. 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
3. 使用interrupt方法中断线程,线程内部要判断注销状态isInterrupted()
线程五个状态: 新建,就绪,执行,阻塞,死亡
线程安全类别:
1. 不可变对象一定是线程安全的,如:Integer,String类
2. 内置线程安全的对象,如concurrentHashMap
3. 手动使用线程安全synchronized,或lock
问: HashMap和HashTable
答:
1. 都是使用同样是通过链表法解决冲突;
2. HashTable的方法前面都有synchronized来同步,是线程安全的;HashMap未经同步,是非线程安全的。
3. HashTable不允许null值(key和value都不可以) ;HashMap允许null值(key和value都可以)。
4. HashTable使用Enumeration进行遍历;HashMap使用Iterator进行遍历。
5. HashTable中hash数组默认大小是11,增加的方式是old*2+1;HashMap中hash数组的默认大小是16,而且一定是2的指数。
6. 哈希值的使用不同,HashTable直接使用对象的hashCode; HashMap重新计算hash值,而且用与代替求模。
7. HashMap, 旧版本的新节点插入再Node列表的头部, 1.8版本之后的插入到Node列表的尾部. 如果是红黑树时,单独处理.
问: ConcurrentHashMap相关
答:
HashTable是线程安全的, HashMap非线程安全, HashTable底层使用synchornized保证线程安全,弊端是所有线程争一个锁,效率低
ConcurrentHashMap
1. 1.7版本ConcurrentHashMap使用锁分段技术效率更高(分为16个段,每个段都有自己的锁),每个segment相当于一个hashtable,不足:每次操作都要两次hash,一次要找到segment,一次找到对应的value,查询列表效率太低
2. 1.7版本ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
3. 不足:那么在插入和获取元素的时候,必须先通过哈希算法定位到Segment,然后二次hash找到对应列表,所以效率太低
4. 1.8在1.7的数据结构上做了大的改动,版本摒弃segment,加入红黑树,取消了ReentrantLock改为了synchronized,使用CAS乐观锁机制,底层使用数组+链表+红黑树,
5. JDK1.8ConcurrentHashMap和JDK1.8的HashMap是很相似的 (JDK 1.8 对 HashMap进行了比较大的优化,底层实现由之前的 “数组+链表” 改为 “数组+链表+红黑树”)
问:描述一下ConcurrentHashMap中remove操作,有什么需要注意的?
第一,当要删除的结点存在时,删除的最后一步操作要将count的值减一。这必须是最后一步操作,否则读取操作可能看不到之前对段所做的结构性修改。
第二,remove执行的开始就将table赋给一个局部变量tab,这是因为table是volatile变量,读写volatile变量的开销很大。编译器也不能对volatile变量的读写做任何优化,直接多次访问非volatile实例变量没有多大影响,编译器会做相应优化
问: Servlet相关
答:
Servlet是sun公司提供的一门用于开发动态web资源的技术,Servlet程序不能单独执行,必须由其他JAVA程序(Servlet引擎)调用,
特点:
1. 针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
2. 由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上.
3. Servlet容器启动时,读取web.xml配置文件中的信息,构造指定的Servlet对象,创建ServletConfig对象,同时将ServletConfig对象作为参数来调用Servlet对象的init方法。在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
4. 当多个客户端并发访问同一个Servlet时,web服务器(Servlet引擎)会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题., 注意此处的多线程来自于web容器
5. 服务器关闭时,才会销毁这个servlet对象,执行destroy()方法。
6. Java Servlet API 是Servlet容器(tomcat)和servlet之间的接口,它定义了serlvet的各种方法,还定义了Servlet容器传送给Servlet的对象类,其中最重要的就是ServletRequest和ServletResponse
一些主要对象:
1. GenericServlet实现了ServletConfig对象,封装了如下属性ServletName、ServletContext、InitParameter、InitParameterNames
2. HttpServlet类继承了GenericServlet类,这个类主要的功能肯定是实现service方法的各种细节和设计(put,delete,get,post等方法)
3. Listener(监听器)用来监听一些对象的事件,当事件发生时可以进入到定义监听器中进行处理,主要有ServletContextListener、HttpSessionListener 和 ServletRequestListener。
4. ServletConfig对象
getServletName(); //获取servlet的名称,也就是我们在web.xml中配置的servlet-name
getServletContext(); //获取ServletContext对象,该对象的作用看下面讲解
getInitParameter(String); //获取在servlet中初始化参数的值。这里注意与全局初始化参数的区分。这个获取的只是在该servlet下的初始化参数
ServerContext对象(ServerContext是一个接口,默认实现是ApplicationContext):
1. web容器(如:tomcat)为每个web项目都创建一个ServletContext实例,tomcat在启动时创建,服务器关闭时销毁,在一个web项目中共享数据,管理web项目资源,为整个web配置公共信息等
2. 是一个全局的储存信息的空间,服务器开始,其就存在,服务器关闭,其才释放。
3. 由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。
4. 所有用户共用一个。所以,为了节省空间,提高效率,ServletContext中要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。
5. 一些主要方法
web项目中共享数据,getAttribute(String name)、setAttribute(String name, Object obj)、removeAttribute(String name)
web项目初始化参数 getInitPatameter(String name) //通过指定名称获取初始化值
获取web项目资源
1. getServletContext().getRealPath("/WEB-INF/web.xml") //获取web项目下指定资源的路径:
2. InputStream getResourceAsStream(java.lang.String path)
3. getResourcePaths(java.lang.String path) 指定路径下的所有内容。
问: 拦截件(interceptor)和过滤器(filter区别)
答:
1 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
2 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
3 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
4 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
5 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
Filter相关
1、 Filter也称之为过滤器,它是Servlet技术中最实用的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
2、 Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,
原理:
WEB服务器每次在调用web资源之前,都会先调用一下filter的doFilter方法,调用时会传递一个filterChain对象进来,filterChain是一个责任链模式, 如果fitlerChain不调用的话, 资源访问被限制
生命周期:
1、 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法进行初始化(注:filter对象只会创建一次,init方法也只会执行一次)
2、 doFilter(ServletRequest,ServletResponse,FilterChain)每次filter进行拦截都会执行
3、 destroy():在Web容器卸载 Filter 对象之前被调用。
Filter 配置
<filter-mapping>元素用于设置一个 Filter 所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径
(2)<url-pattern>设置 filter 所拦截的请求路径(过滤器关联的URL样式)
(3)<servlet-name>指定过滤器所拦截的Servlet名称。
<dispatcher> 子元素可以设置的值及其意义,包括四种方式:REQUEST、FORWARD、INCLUDE、ERROR
Filter常见应用
对request请求头做第一步验证: 身份验证(限制指定URL路径等),cookies验证,字符编码选择
对response响应头的附件操作:字符编码选择,cookies设置,资源过期时间设置
问: Grafana相关
答:
1. Grafana支持许多不同的时间序列数据(数据源)存储后端。每个数据源都有一个特定查询编辑器。官方支持以下数据源:Graphite、infloxdb、opensdb、prometheus、elasticsearch、cloudwatch。每个数据源的查询语言和功能明显不同。你可以将来自多个数据源的数据组合到一个仪表板上,但每个面板都要绑定到属于特定组织的特定数据源
2. Grafana中的警报允许您将规则附加到仪表板面板上。保存仪表板时,Gravana会将警报规则提取到单独的警报规则存储中,并安排它们进行评估。报警消息还能通过钉钉、邮箱等推送至移动端。但目前grafana只支持graph面板的报警。
问: java.util.concurrent 并发包诸类概览
阻塞队列:
BlockingQueue,阻塞队列接口
BlockingDeque,双端阻塞队列接口
ArrayBlockingQueue,阻塞队列,数组实现,有界
LinkedBlockingDeque,阻塞双端队列,链表实现,有界
LinkedBlockingQueue,阻塞队列,链表实现
DelayQueue,阻塞队列,内部包含一个阻塞队列内部包含一个PriorityQueue,并且元素是 Delay 的子类,无界,保证元素在达到一定时间后才可以取得到
PriorityBlockingQueue,优先级阻塞队列,无界,
SynchronousQueue,同步队列,但是队列长度为 0,生产者放入队列的操作会被阻塞,直到消费者过来取,所以这个队列根本不需要空间存放元素;有点像一个独木桥,一次只能一人通过,还不能在桥上停留
非阻塞队列:
ConcurrentLinkedDeque,非阻塞双端队列,链表实现
ConcurrentLinkedQueue,非阻塞队列,链表实现
转移队列:
TransferQueue,转移队列接口,生产者要等消费者消费的队列,生产者尝试把元素直接转移给消费者
LinkedTransferQueue,转移队列的链表实现,它比 SynchronousQueue 更快
其它容器:
ConcurrentMap,并发 Map 的接口,定义了 putIfAbsent(k,v)、remove(k,v)、replace(k,oldV,newV)、replace(k,v) 这四个并发场景下特定的方法
ConcurrentHashMap,并发 HashMap
ConcurrentNavigableMap,NavigableMap 的实现类,返回最接近的一个元素
ConcurrentSkipListMap,它也是 NavigableMap 的实现类(要求元素之间可以比较),同时它比 ConcurrentHashMap 更加 scalable——ConcurrentHashMap 并不保证它的操作时间,并且你可以自己来调整它的 load factor;但是 ConcurrentSkipListMap 可以保证 O(log n) 的性能,同时不能自己来调整它的并发参数,只有你确实需要快速的遍历操作,并且可以承受额外的插入开销的时候,才去使用它
ConcurrentSkipListSet,和上面类似,只不过 map 变成了 set
CopyOnWriteArrayList,是一个线程安全、并且在读操作时无锁的ArrayList。特点: 这里有可能脏读。但是效率非常高,每当需要插入元素,不在原 list 上操作,而是会新建立一个 list,适合读远远大于写并且写时间并苛刻的场景
CopyOnWriteArraySet,和上面类似,list 变成 set 而已
一些锁
ReadWriteLock,读写锁,读写分开,读锁是共享锁,写锁是独占锁;对于读-写都要保证严格的实时性和同步性的情况,并且读频率远远大过写,使用读写锁会比普通互斥锁有更好的性能。
ReentrantLock,可重入锁(lock 行为可以嵌套,但是需要和 unlock 行为一一对应),有几点需要注意:
1、 构造器支持传入一个表示是否是公平锁的 boolean 参数,公平锁保证一个阻塞的线程最终能够获得锁,因为是有序的,所以总是可以按照请求的顺序获得锁;
2、 不公平锁意味着后请求锁的线程可能在其前面排列的休眠线程恢复前拿到锁,这样就有可能提高并发的性能,
3、 还提供了一些监视锁状态的方法,比如 isFair、isLocked、hasWaiters、getQueueLength 等等
ReentrantReadWriteLock,可重入读写锁
Condition,使用锁的 newCondition 方法可以返回一个该锁的 Condition 对象,如果说锁对象是取代和增强了 synchronized 关键字的功能的话,那么 Condition 则是对象 wait/notify/notifyAll 方法的替代。
Fork/Join相关
Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
Fork/Join框架要完成两件事情:
1. 任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割
2. 执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里,启动一个线程从队列里取数据,然后合并这些数据。
主要类
1. ForkJoinTask:我们要使用Fork/Join框架,首先需要创建一个ForkJoin任务,分为两种
RecursiveAction:用于没有返回结果的任务
RecursiveTask:用于有返回结果的任务
2. ForkJoinPool: ForkJoinTask需要通过ForkJoinPool来执行
3. 任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务(工作窃取算法)。
Fork/Join框架的实现原理
1. ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,ForkJoinTask数组负责将存放程序提交给ForkJoinPool,而ForkJoinWorkerThread负责执行这些任务。
2. fork方法,程序会把任务放在ForkJoinWorkerThread的pushTask的workQueue中,异步地执行这个任务,然后立即返回结果,
pushTask方法把当前任务存放在ForkJoinTask数组队列里。然后再调用ForkJoinPool的signalWork()方法唤醒或创建一个工作线程来执行任务
3. join方法用于让当前线程阻塞,直到对应的子任务完成运行并返回执行结果
问: 面向过程与面向对象的区别
面向过程
1. 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
2. 缺点:没有面向对象易维护、易复用、易扩展
面向对象
1. 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
2. 缺点:性能比面向过程低
问: MySql相关
在快照读(snapshot read)的情况下,MySQL通过MVCC(多版本并发控制)来避免幻读
在当前读(current read)的情况下,MySQL通过next-key lock来避免幻读
事务的隔离性(Isolation): 数据库系统提供一定的隔离机制,保证事务在不受外部并发 操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不 可见的,反之亦然。
1、 脏读: 发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
2、 不可重复读: 发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。简单说A事务读取了B事务已经提交的修改数据,不符合隔离性(事务内为独立环境)。
3、 幻读: 与不可重复读类似,两次读取的数据行数不一致。重新读取以前检索过的数据时,数据增多或减少。
4、 不可重复读的重点是修改: 同样的条件, 你读取过的数据, 再次读取出来发现值不一样了, 解决方案是使用MVCC
5、 幻读的重点在于新增或者删除: 操作的是一个范围
innoDB支持三种行锁定方式:
行锁(Record Lock):锁直接加在索引记录上面(无索引项时演变成表锁)。
间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录的间隙不变。间隙锁是针对事务隔离级别为可重复读或以上级别的。
Next-Key Lock :行锁和间隙锁组合起来就是 Next-Key Lock。Innodb对于行的查询都采用这种算法(为了解决幻读)。
1. innoDB的间隙锁只存在于 RR 隔离级别
2. 间隙锁在innoDB中的唯一作用就是在一定的“间隙”内防止其他事务的插入操作,以此防止幻读的发生.
3. innoDB默认的隔离级别是可重复读(Repeatable Read),并且会以Next-Key Lock的方式对数据行进行加锁。Next-Key Lock是行锁和间隙锁的组合,当InnoDB扫描索引记录的时候,会首先对索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录。
4. 查询的索引含有唯一属性(主键或唯一索引)时,innodb存储引擎会对next-key lock进行优化,将其降级为Record Lock。即仅锁住索引本身,而不是范围。
5. 若是辅助索引则会分别对当前辅助索引及聚集索引加锁定。对聚集索引采用Record Lock锁定,而辅助索引则使用Next-Key Lock锁定。需要注意的是如果是等值更新,innodb会对辅助索引值与前后值构成的范围加上gap lock,而如果该辅助索引值不存在,则在该值所在区间上加上gap锁。
6. 区间的划分和辅助索引包含的键值有关,如一个辅助索引包含了{1,3,5},则对应的区间有(-∞,1),(1,3),(3,5),(5,+∞)。例如更新值为2,则锁定(1,3)这两个区间,而如果更新值为3则锁住(1,3),[3],(3,5)这个范围。
7. 如果是范围查询的话,则锁定的是该SQL涉及的范围内的记录和间隙。确切地说,其实辅助索引的叶子节点都包含了对应的聚集索引值,在使用gap锁划分区间的时候,其实是根据[辅助索引,聚集索引]组成的二维数组来划分的。
加锁的基本原则(RR隔离级别下)
1. 原则1:加锁的对象是next-key lock。(是一个前开后闭的区间)
2. 原则2:查找过程中访问到的对象才加锁
3. 优化1:唯一索引加锁时(索引命中),next-key lock退化为行锁。未命中时退化为间隙锁;
4. 索引上的等值查询,向右遍历时最后一个不满足等值条件的时候,next-key lock 退化为间隙锁
5. 使用普通索引检索时,不管是何种查询,只要加锁,都会产生间隙锁。
6. 同时使用唯一索引和普通索引时,由于数据行是优先根据普通索引排序,再根据唯一索引排序,所以也会产生间隙锁。
7. 唯一索引和普通索引在范围查询的时候 都会访问到不满足条件的第一个值为止
8. mySql使用select .. for Update时如果找不到聚集索引,行锁会膨胀为表锁.
MySql临时表相关
1. 临时表与普通表的主要区别在于是否在实例,会话,或语句结束后,自动清理数据
2. 创建临时表,多一个关键字 TEMPORARY
直接创建表结构. CREATE TEMPORARY TABLE tmp_table () ,如果是内存临时表,使用TYPE = HEAP
直接把查询结果存入表CREATE TEMPORARY TABLE tmp_table select * from users
MySql binlog相关
binlog是记录所有数据库表结构变更(例如CREATE、ALTER TABLE…)以及表数据修改(INSERT、UPDATE、DELETE…)的二进制日志
Binlog常用格式有
statement: 记录维修记录,原sql语句,,优点:日志小,缺点环境无法完全一致,导致master-slave数据不准确
row:记录每行实际数据变更,优点:数据准确,缺点:日志太大
mixed:statment和row混合,优点:大小适中,缺点:有可能主从不一致问题
innodb事务日志包括redo log和undo log,事务日志的目的:实例或者介质失败,事务日志文件就能派上用场
undo log(旧数据备份)指事务开始之前, 在操作任何数据之前,首先将需操作的数据备份到一个地方
redo log(新数据)指事务中操作的任何数据,将最新的数据备份到一个地方
redo log设置刷盘时间
取值 0 每秒提交 Redo buffer --> Redo log OS cache -->flush cache to disk[可能丢失一秒的事务数据]
取值 1 默认值,每次事务提交执行Redo buffer --> Redo log OS cache -->flush cache to dis[最安全,性能最差的方式]
取值 2 每次事务提交执行Redo buffer --> Redo log OS cache 再每一秒执行 ->flush cache tdisk操作
mySql数据同步
第三方开源框架(如CDC,canal等):模拟salve同步master数据,框架模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
mysql master收到dump请求,开始推送binary log给slave(也就是canal)
框架解析binary log对象(原始为byte流)
mysql数据同步方式:
1、Master将数据改变记录到二进制日志(binary log)中,也就是配置文件log-bin指定的文件,这些记录叫做二进制日志事件(binary log events)
2、Slave通过I/O线程读取Master中的binary log events并写入到它的中继日志(relay log)
3、Slave重做中继日志中的事件,把中继日志中的事件信息一条一条的在本地执行一次,完成数据在本地的存储,从而实现将改变反映到它自己的数据(数据重放)
三种复制方式
1、基于语句的复制. 在Master上执行的SQL语句,在Slave上执行同样的语句。MySQL默认采用基于语句的复制,效率比较高。一旦发现没法精确复制时,会自动选着基于行的复制
2、基于行的复制. 把改变的内容复制到Slave,而不是把命令在Slave上执行一遍。从MySQL5.0开始支持
3、混合类型的复制. 默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制
explain type类型:性能由低到高ALL、index、range、 ref、eq_ref、const、system、NULL
1. ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
2. index: Full Index Scan,index与ALL区别为index类型只遍历索引树
3. range:只检索给定范围的行,使用一个索引来选择行
4. ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
5. eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
6. const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system
7. NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
参照demo:https://blog.csdn.net/weixin_43732955/article/details/104697551
问: MySql innoDB与MyISAM存储引擎区别
在MySQL 5.1之前的版本中,默认的搜索引擎是MyISAM,从MySQL 5.5之后的版本中,默认的搜索引擎变更为InnoDB
MyISAM:
1. 特点:支持全文索引,表级锁、不支持事务和行级锁,大并发性能稍差(表锁降低了读写的吞吐量),MyISAM在磁盘上存储为三个文件(表定义,索引定义,数据文件)
2. 适合 做很多count 的计算;
3. 适合 插入不频繁,查询非常频繁,如果执行大量的SELECT,MyISAM是更好的选择;
4. 适合 没有事务。
InnoDB:
1. 特点:行级锁,事务安全(ACID),支持外键,为处理大并发设计,
a) 行级锁特点: 每次获取或者释放要消耗更多的资源,但是更精细,并发量更大.
2. 适合 可靠性要求比较高,或者要求事务;
3. 适合 表更新和查询都相当的频繁,并且表锁定的机会比较大的情况指定数据引擎的创建;
4. 适合 如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表;
5. 适合 DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的 删除;
6. 适合 LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。
数据库相关:
数据库NULL的缺点分析
1、 空间相关 =>“”不占空间,NULL占空间
2、 索引相关 => B树不存NULL,因此索引用不到NULL
3、 count函数相关 => count(*)包含NULL,count(id)不包含NULL
问: JTA
答: 根据用于管理事务的底层实现,Spring中的事务策略可以分为两个主要部分:
1. 单连接器策略(相当于本地事务管理器) - 底层技术使用单连接器。例如,JDBC使用连接级事务、Hibernate以及JDO使用会话级事务。可以应用使用AOP和拦截器的声明式事务管理。
2. 多连接器策略(相当于全局事务管理器) - 底层技术具有使用多个连接器的能力。当有这方面需求时,JTA是最好的选择。此策略需要启用JTA的数据源实例。JBossTS、Atomikos、Bitronix都是开源的JTA实现。
3. JTA的真正强大之处在于它能够在单个事务中管理多个资源(如数据库jdbs,消息服务mq等)。
简述Spring的优缺点?
Spring 的优点??
1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦
2.可以使用容易提供的众多服务,如事务管理,消息服务等
3.容器提供单例模式支持
4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能
5.容器提供了众多的辅助类,能加快应用的开发
6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等
7.spring属于低侵入式设计,代码的污染极低
8.独立于各种应用服务器
9.spring的DI机制降低了业务对象替换的复杂性
10.Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可以自由选择spring的部分或全部
缺点:使用到了大量反射机制。反射机制非常占内存
Spring事务
1. Spring的事务策略是通过PlateformTransactionManager接口实现的,对于不同的底层,spring可以配置不同的该接口的实现类,事务策略与事务资源相分离
2. 它为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。
3. PlateformTransactionManager的方法
a) TransactionStatus getTransaction(TransactionDefinition definition)
b) commit(TransactionStatus status)
c) rollback(status)
4. TransactionDefinition 定义了事务的规则,事务的规则,五角星
a) 事务隔离 isolation 隔离级别
b) 事务超时timeout
c) 事务可读 readonly
d) 事务传播 propagation
e) 回滚规则 rollback 对特定的异常进行回滚 no-rollback
5. TransactionStatus 代表事务本身,提供了控制事务执行和查询事务状态的方法
a) 判断是否为新建的事务 isNewTransaction()
b) 设置事务回滚 setRollbackOnly()
c) 查询事务是否有回滚标志 isRollBackOnly()
6. 底层TransactionManager,规则为
a) 前缀 org.springframework
b) 类型 jdbc orm transaction等spring的七大组件
c) 小类为 datasource hibernate4 jta 类名开头
d) 名字为DataSourceTransactionManager
e) HibernateTransactionManager
f) JtbTransactionManager
Spring Bean生命周期
Bean核心生命周期分四个部分
实例化 Instantiation
属性赋值 Populate
初始化 Initialization
销毁Destruction
核心继承接口InstantiationAwareBeanPostProcessor, 用户一版继承这个接口
1. //为Aop/proxy做适配
1. AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
2. 调用InstantiationAwareBeanPostProcessor处理两个process,观察是否生成bean,如果生成bean的话, 就不往下继续生成bean
2. 前面失败,继续生成bean AbstractAutowireCapableBeanFactory#doCreateBean
1.生成bean org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance
2.添加属性 populateBean
2.1 InstantiationAwareBeanPostProcessor.postProcessProperties
2.2 InstantiationAwareBeanPostProcessor.postProcessPropertyValues
2.3 为属性赋值org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues
3. 调用BeanNameAware,BeanFactoryAware
4. 调用 init-method(@postConstruct) initializeBean
3.1 调用BeanPostProcessor.postProcessBeforeInitialization
3.2 调用 init-method
3.3 调用bean自己的.afterPropertiesSet()
3.3 调用调用BeanPostProcessor.postProcessAfterInitialization
demo
MyInstantiationAwareBeanPostProcessor >> postProcessBeforeInstantiation 方法
【构造器】调用Person的构造器实例化
MyInstantiationAwareBeanPostProcessor >> postProcessAfterInstantiation
MyInstantiationAwareBeanPostProcessor >> postProcessProperties
MyInstantiationAwareBeanPostProcessor 调用 postProcessPropertyValues方法
【BeanNameAware接口】调用BeanNameAware.setBeanName()
【BeanFactoryAware接口】调用BeanFactoryAware.setBeanFactory()
MyInstantiationAwareBeanPostProcessor >> postProcessBeforeInitialization
【init-method】调用<bean>的init-method属性指定的初始化方法
【InitializingBean接口】调用InitializingBean.afterPropertiesSet()
MyInstantiationAwareBeanPostProcessor 调用 postProcessAfterInitialization方法
实例化 Instantiation createBeanInstance()
属性赋值 Populate populateBean
初始化 Initialization initializeBean
销毁 Destruction
在Spring中有5中标准的事件:
1. 上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布,包括手动掉refresh()方法。
2. 上下文开始事件(ContextStartedEvent): 当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时被触发。
3. 上下文停止事件(ContextStoppedEvent): 当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
4. 上下文关闭事件(ContextClosedEvent): 当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
5. 请求处理事件(RequestHandledEvent): 在Web应用中,当一个http请求(request)结束触发该事件。
问: ACID
1、 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
2、 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
3、 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
4、 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
Spring AOP术语
1. 连接点(Joinpoint):程序执行的某个特定位置,具体被代理的方法。
2. 切点(Pointcut):AOP通过“切点”定位特定的连接点,用于搜索哪些类/方法满足条件。
3. 增强/通知(Advice):执行点的方位,before,after,around等
4. 目标对象(Target):需要织入增强代码的目标类。
5. 切面(Aspect):切面由切点和增强组成,既包括了横切逻辑的定义,也包括了连接点的定义.
6. 织入(Weaving):织入是将增强添加到目标类上具体连接点的过程,动态代理(jdk,cglib)
Spring自动代理机制,
内部原理是使用BeanPostProcessor后处理Bean自动地完成这项工作基于BeanPostProcessor的自动代理创建器的实现类,这些代理创建器可以分为以下三类。
1. 基于Bean配置名规则的自动代理创建器:允许为一组特定配置名的Bean自动创建代理。实现类为BeanNameAutoProxyCreator
2. 基于Advisor匹配机制的自动代理创建器:它会对容器中所有的Advisor进行扫描,自动将这些切面应用到匹配的Bean中,实现类为DefaultAdvisorAutoProxyCreator
3. 基于Bean中AspectJ注解标签的自动创建代理器:为包含AspectJ注解的Bean自动创建代理。实现类是AnnotationAwareAspectJAutoProxyCreator
Spring注解相关
1. Spring定义的@PostConstruct和@PreDestroy两个注解相当于bean的init-method和destory-method属性的功能
数据库连接池相关
原理:一般来说,java应用程序访问数据库的过程是:
1、 装载数据库驱动程序;
2、 通过jdbc建立数据库连接;
3、 访问数据库,执行sql语句;
4、 断开数据库连接。
数据库连接是稀缺资源: 数据库连接模式正是为了解决资源的频繁分配﹑释放所造成的性能问题。
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
数据库连接池需要考虑的问题:
1、 获取/释放连接时,线程并发问题.
2、 事务处理问题: connection本身提供了对事务的支持
3、 连接池内线程连接状态的判断及维护
4、 连接池最大,最小连接数维护(单独线程动态监测,或者请求时再监测)
问: 乐观锁/悲观锁
答:
悲观锁:对数据修改前首先加上排它锁.执行的是"先取锁再访问"策略,保证数据安全,开销大,降低并行性,不适合只读环境
乐观锁:读数据不上锁,直到提交的时候才去锁定,常用实现方式是VCC.,所以不会产生任何锁和死锁.
DDOS相关
DoS是Denial of Service的简写就是拒绝服务
DDOS: Distributed Denial of Service 分布式拒绝服务, 原理说白了就是黑客通过掌控的肉鸡来"群殴"
问:maven相关
Maven管理的方式就是“自动下载项目所需要的jar包,统一管理jar包之间的依赖关系”。
Maven项目目录结构
MavenProjectRoot(项目根目录)
|----src
| |----main
| | |----java ——存放项目的.java文件
| | |----resources ——存放项目资源文件,如spring, hibernate配置文件
| |----test
| | |----java ——存放所有测试.java文件,如JUnit测试类
| | |----resources ——存放项目资源文件,如spring, hibernate配置文件
|----target ——项目输出位置
|----pom.xml ----用于标识该项目是一个Maven项目
scope用来控制依赖和编译,测试,运行的classpath的关系. 主要的是三种依赖关系如下:
1. compile: 默认编译依赖范围。对于编译,测试,运行三种classpath都有效
2. test:测试依赖范围。只对于测试classpath有效
3. provided:已提供依赖范围。对于编译,测试的classpath都有效,但对于运行无效。因为由容器已经提供,例如servlet-api
4. runtime:运行时提供。例如:jdbc驱动
maven插件
1. maven的核心仅仅定义了抽象的生命周期,具体的任务都是交由插件完成的。
2. 每个插件都能实现多个功能,每个功能就是一个插件目标。
3. Maven的生命周期与插件目标相互绑定,以完成某个具体的构建任务,例如compile就是插件maven-compiler-plugin的一个插件目标。
pom是指project object Model。
1. pom是一个xml,在maven2里为pom.xml。是maven工作的基础,在执行task或者goal时,maven会去项目根目录下读取pom.xml获得需要的配置信息
2. pom文件中包含了项目的信息和maven build项目所需的配置信息,通常有项目信息(如版本、成员)、项目的依赖、插件和goal、build选项等等
3. pom是可以继承的,通常对于一个大型的项目或是多个module的情况,子模块的pom需要指定父模块的pom
问: TCP粘包
1. tcp 粘包可能发生在发送端或者接收端,分别来看两端各种产生粘包的原因:
2. 发送端粘包:发送端需要等缓冲区满才发送出去,造成粘包;
3. 接收方粘包:接收方不及时接收缓冲区的包,造成多个包接收。
问: XSS攻击,CSRF攻击,如何避免?
XSS 攻击:
1. 即跨站脚本攻击,它是 Web 程序中常见的漏洞。原理是攻击者往 Web 页面里插入恶意的脚本代码(css 代码、Javascript 代码等),当用户浏览该页面时,嵌入其中的脚本代码会被执行,从而达到恶意攻击用户的目的,如盗取用户 cookie、破坏页面结构、重定向到其他网站等。
2. 预防 XSS 的核心是必须对输入的数据做过滤处理。
CSRF:Cross-Site Request Forgery(中文:跨站请求伪造),
1. 可以理解为攻击者盗用了你的身份,以你的名义发送恶意请求,比如:以你名义发送邮件、发消息、购买商品,虚拟货币转账等。
2. 防御手段:
验证请求来源地址;
关键操作添加验证码;
在请求地址添加 token 并验证。
几个简单的设计模式
1. 设计模式的几大原则:对扩展开放,对修改关闭,使用接口隔离,能使用聚合的尽量不使用继承.
2. 策略模式:需要一个接口,定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,
3. 模板方法:就是指:一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用,先看个关系图
4. 观察者模式(Observer):主类中维护通知列表,当主类状态变化时,通知所有列表中对象
5. 迭代模式: 可以迭代方法对象中元素
6. 责任链模式: 有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这个链条中传递,知道有一个对象去处理这个请求.但是调用者不知道具体是哪个对象完成的请求处理.
7. 命令模式:举个例子,司令员下令让士兵去干件事情,并且只要最终结果,对于士兵怎么实现不关注.invoker->command->executor
8. 工厂模式
a) 普通工厂:使用字符串判断生成不同的对象
b) 静态工厂:使用不同的静态方法创建不同的具体对象,问题:扩展时要改源代码
c) 抽象工厂:从依赖角度考虑,抽象出抽象工厂,不同抽象工厂实现类生产不同的具体对象
9. 单例模式:实现方式包括双锁,静态内部类,饿汉模式直接生成等,还有深度复制序列话时可能会打破单例模式
10. 建造者模式:用来创建负责对象.
11. 适配器模式:将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题,主要分为三类:类的适配器模式、对象的适配器模式(组合模式)、接口的适配器模式.
12. 装饰器模式:装饰模式就是给一个对象增加一些新的功能,增强,支持多重装饰,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,
13. 代理模式:代理模式就是多一个代理类出来,替原对象进行一些操作,还有权限验证等,类似装饰器模式
14. 外观模式:解决类与类之间的依赖关系,将多个内部类行为组合起来,对外提供一个接口,举例:cpu,内存,硬盘与电脑主机的关系.电脑开机时启动所有子系统.
15. 桥接模式:将抽象化与实现化解耦,使得二者可以独立变化, 类似工厂模式, 同步设置不同抽象实现类,调用具体类的方法,使得同一个类可以应付不同的环境,类似搭桥一般,如不同的数据库都实现相同的JDBC接口.
16. 组合模式:部分-整体模式,将多个对象组合在一起操作
17. 享元模式:主要目的是实现对象的共享,即共享池,举个例子:数据库连接池
18. 状态模式:通过不同的状态,产生不同的行为.
Git相关
git是一套内容寻址的文件系统,它存储的也是key-value键值对,然后根据key值来查找value
git 和其他版本控制系统(vss,TFS等)的主要差别在于,Git 只关心文件数据的整体是否发生变化,,而大多数其他系统则只关心文件内容的具体差Git 并不保存这些前后变化的差异数据。
实际上, Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。
git底层一共有四种对象
1. commit存储一次提交的信息,包括所在的tree,parent是谁,以及提交的作者是谁等信息,记录了每次提交到本地仓库的文件快照(注意是所有文件的objectId),。
2. tag:标签,实际可当做是commit的别名。
3. tree:代表的是目录结构,或者简单理解为代表一个目录,记录了文件快照中各个目录和文件的结构关系
4. blob:用来存储文件内容,或者说表示一个文件,对应的就是文件快照中那些发生变化的文件内容
JAVA IO相关
字节流主要用于处理诸如图像,音频视频等二进制格式数据,而字符流主要用于处理文本字符等类型的输入输出
输入流InputStream负责从各种数据/文件源产生输入,输入源包括:数组,字符串,文件,管道,一系列其他类型的流,以及网络连接产生的流等等。
常用字节输入流的主要类型:
ByteArrayInputStream字节数组输入流:允许内存缓存作为输入流。
FileInputStream文件输入流:从文件系统中的某个文件获得输入字节,用于读取诸如图像数据子类的原始字节流。若要读取字符流,请使用FileReader。
PipedInputStream管道输入流:和管道输出流一起构成一个输入输出的管道,是管道的数据输入端.
SequenceInputStream顺序输入流:将两个或多个输入流对象转换为一个单个输入流对象.它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止
FilterInputStream过滤输入流:包含其他一些输入流,将这些被包含的输入流用作其基本数据源,它可以直接传输数据或者提供一些额外的功能。常用的FilterInputStream是DataInputStream数据输入流,主要用于允许程序以与机器无关的方式从底层输入流中读取java基本数据类型。其常用的方法有readInt(),readBoolean(),readChar()等等。
常用的字节输出流
ByteArrayOutputStream:实现了一个输出流,其中的数据被写入一个byte数组中。缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()方法获取数据。
FileOutputStream文件输出流:将数据写入到指定文件中。文件输出流是用于将数据写入File或FIleDescriptor的输出流,用于写入诸如图像数据之类的原始字节流,若要写入字符流,请使用FileWriter。
PipedOutputStream管道输出流:连接管道输入流用来创建通信管道,管道输出流是管道数据输出端。
FilterOutputStream过滤输出流:用于将已存在的输出流作为其基本数据接收器,可以直接传输数据或提供一些额外的处理。
字符流
字符流相关常用类如下:
(1).Reader:用于读取字符串流的抽象类,子类必须实现的方法只有reader(char[],int, int)和close()。
(2).InputStreamReader:是将字节输入流转换为字符输入流的转换器,它使用指定的字符集读取字节并将其解码为字符。即:字节——>字符。
(3).Writer:用于写入字符流的抽象类,子类必须实现的方法只有write(char[],int, int)和close()。
(4).OutputStreamWriter:是将字符输出流转换为字节输出流的转换器,它使用指定的字符集将要写入流的字符编码成字节。即:字符——>字节。
深复制导致单例失效的解决方法
1. 不过当序列化遇到单例时,出了问题: 从内存读出而组装的对象破坏了单例的规则. 单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来.
2. 解决方案,JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证.
public class Singleton implements Serializable {
public static Singleton INSTANCE = new Singleton();
private Object readResolve() {
return INSTANCE;
}
}
JVM编译策略相关:
javac编译器为前端编译器,负责将*.java编译为*.class, 不在本次讨论范围.
JVM启动过程:
当虚拟机启动时,解释器启动快,可以首先发挥作用,而不必等待编译器全部编译完成再执行,这样可以省去许多不必要的编译时间。并且随着程序运行时间的推移,编译器逐渐发挥作用,根据热点探测功能,,将有价值的字节码编译为本地机器指令,以换取更高的程序执行效率。
JVM运行有两种策略,解释器+JIT编译器.
1. 解释器好处:更轻量,节省内存,启动速度更快.
2. JIT编译器分两种Client Compiler,Server Compiler,又称为C1编译器和C2编译器。
a) C1编译器特点:较为轻量,只做少量性能开销比较高的优化,它占用内存较少,适合于桌面交互式应用,主要有方法内联、去虚拟化、冗余消除等。
b) C2编译器特点;较为重量,采用了大量传统编译优化的技巧来进行优化,占用内存相对多一些,适合服务器端的应用。会收集运行信息,优化范围更全局化,包括逃逸分析,标量替换,栈上分配等
Java7默认开启分层编译(tiered compilation)策略,由C1编译器和C2编译器相互协作共同来执行编译任务。C1编译器会对字节码进行简单和可靠的优化,以达到更快的编译速度;C2编译器会启动一些编译耗时更长的优化,以获取更好的编译质量。
(1)解释器不再收集运行状态信息,只用于启动并触发C1编译
(2)C1编译后生成带收集运行信息的代码
(3)C2编译,基于C1编译后代码收集的运行信息进行激进优化,当激进优化的假设不成立时,再退回使用C1编译的代码
SunJDK之所以未选择在启动时即编译成机器码的原因如下:
1. 静态编译并不能根据程序的运行状态来优化执行的代码,
2. C2这种方式是根据运行状态来进行动态编译的,例如分支判断、逃逸分析等,这些措施会对提升程序执行的性能起到很大的帮助,在静态编译的情况下是无法实现的,
3. 给C2收集运行数据越长的时间,编译出来的代码会越优。
自定义序列化/反序列化 相关
原因:
1. 默认序列化会对整个对象图序列化,容易导致StackOverflowError
2. 为了确保序列化的安全性,对于一些敏感信息加密
3. 确保对象的成员变量符合正确的约束条件
4. 优化序列化的性能(之前的那个例子已经解释了这种情况)
自定义序列化需要实现接口Serializable/Externalizable
Transfer-Encoding/Content-Length相关
1. Content-Length 当response长度固定时,用这个头, 他不能与Transfer-Encoding同时出现,否则被忽略
2. Transfer-Encoding: chunked 这个头信息的的意思是response的内容会被Tomcat分成一块一块的发送,客户端也就不需要等到内容都传输完毕了才解析其中的内容。因为这个时候被传送的数据长度是无法预计的,所以存在Tansfer-Encoding:chunked的话也没有存在Content-Length 的意义了。
Log4J 相关
Log4j有三个主要的组件:Loggers(记录器),Appender (输出源)和Layout(布局)。综合使用这三个组件可以轻松的记录信息的类型和级别,并可以在运行时控制日志输出的样式和位置。下面对三个组件分别进行说明:
1. Logger对象是用来取代System.out或者System.err的日志写出器,用来供程序员输出日志信息
2. Appender 输出目的地 Log4j日志系统允许把日志输出到不同的地方,如控制台(Console)、文件(Files)、根据天数或者文件大小产生新的文件、以流的形式发送到其它地方等
Appenders:包含以下标签
a) FileAppender 普通地输出到本地文件
b) FlumeAppender 将几个不同源的日志汇集、集中到一处
c) RewriteAppender 对日志事件进行掩码或注入信息
d) RollingFileAppender 对日志文件进行封存
e) RoutingAppender 在输出地之间进行筛选路由
f) SMTPAppender 将LogEvent发送到指定邮件列表
g) SocketAppender 将LogEvent以普通格式发送到远程主机
h) SyslogAppender 将LogEvent以RFC 5424格式发送到远程主机
i) AsynchAppender 将一个LogEvent异步地写入多个不同输出地
j) ConsoleAppender 将LogEvent输出到控制台
k) FailoverAppender 维护一个队列,系统将尝试向队列中的Appender依次输出LogEvent,直到有一个成功为止
Logger介绍:
1. name: 用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点。
2. AppenderRef: Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root.如果指定了,那么会在指定的这个Appender和Root的Appender中都会输出,此时我们可以设置Logger的additivity="false"只在自定义的Appender中进行输出。
3. 备注:
a) 在Log4j2中,logger是有继承关系的,root是根节点,logger中有个additivity属性, 它是子Logger 是否继承Logger的 输出源(appender)的标志位。具体说,默认情况下子Logger会继承父Logger的appender,也就是说子Logger会在父Logger的appender里输出。若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出, 只有未被拦截的日子才会落到root缺省logger中.
Elastic-Job相关
1. 分布式定时任务框架,基于成熟的开源产品Quartz和Zookeeper及其客户端Curator进行二次开发。
2. 提供3种作业类型,分别是OneOff, Perpetual和SequencePerpetual。需要继承相应的抽象类
3. 方法参数shardingContext包含作业配置,分片和运行时信息。可通过getShardingTotalCount(),getShardingItems()等方法分别获取分片总数,运行在本作业服务器的分片序列号集合等
4. OneOff作业类型比较简单,需要继承AbstractOneOffElasticJob,该类只提供了一个方法用于覆盖,此方法将被定时执行。用于执行普通的定时任务,与Quartz原生接口相似,只是增加了弹性扩缩容和分片等功能。
5. Perpetual类型作业,更适用于流式不间歇的数据处理
Thread状态装换
Stream相关
1. Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator,
2. Stream支持并行计算,内部是依赖1.7的 Fork/Join 框架来实现的
3. 对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)
4. Java 的并行 API 演变历程基本如下:
a) 1.0-1.4 中的 java.lang.Thread
b) 5.0 中的 java.util.concurrent
c) 6.0 中的 Phasers 等
d) 7.0 中的 Fork/Join 框架
e) 8.0 中的Lambda
5. Demo
a) Stream.of("A", "B", "C", "D").reduce("", String::concat);
b) Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); //求最小值
c) int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); //求和
Lettuce相关
Lettuce是一个高性能基于Java编写的Redis驱动框架,底层集成了Project Reactor提供天然的反应式编程,通信框架集成了Netty使用了非阻塞IO,5.x版本之后融合了JDK1.8的异步编程特性,
1. 支持Redis Streams。
2. 支持异步的主从连接。
3. 支持异步连接池。
4. 默认是连接是StatefulRedisConnection<String,String>,必要定制编码解码器RedisCodec<K,V>
5. Lettuce主要提供三种API:
a) 同步(sync):RedisCommands。
b) 异步(async):RedisAsyncCommands。
c) 反应式(reactive):RedisReactiveCommands。
JMX(Java Management Extensions)
1. 是一个为应用程序植入管理功能的框架。JMX是一套标准的代理和服务,实际上,用户可以在任何Java应用程序中使用这些代理和服务实现管理。
2. JMX让程序有被管理的功能,例如你开发一个WEB网站,它是在24小时不间断运行,那么你肯定会对网站进行监控,如每天的UV、PV是多少;又或者在业务高峰的期间,你想对接口进行限流,就必须去修改接口并发的配置值。
3. 程序高手则懂得物为我所用,用JMX把需要配置的属性集中在一个类中,然后写一个MBean,再进行相关配置.
4. 访问方式有:使用jconsole,使用web浏览器,使用agent客户端
TIDB相关:
TiDB 是一个分布式数据库。特点:它支持水平弹性扩展、TiDB 兼容 MySQL,支持无限的水平扩展,存储海量数据,具备强一致性和高可用性,支持标准的 ACID 事务.
TiDB 集群主要分为三个组件:
1. TiDB Server:iDB Server 负责接收 SQL 请求,处理 SQL 相关的逻辑,并通过 PD 找到存储计算所需数据的 TiKV 地址,与 TiKV 交互获取数据,最终返回结果
2. TiKV是一个集群,通过Raft协议保持数据的一致性(副本数量可配置,默认保存三副本),并通过 PD 做负载均衡调度.
3. PD Server:Placement Driver (简称 PD) 是整个集群的管理模块,其主要工作有三个: 一是存储集群的元信息(某个 Key 存储在哪个 TiKV 节点);二是对 TiKV 集群进行调度和负载均衡(如数据的迁移、Raft group leader的迁移等);三是分配全局唯一且递增的事务 ID。
4. TiKV Server:TiKV Server 负责存储数据,从外部看 TiKV 是一个分布式的提供事务的 Key-Value 存储引擎。存储数据的基本单位是 Region,每个 Region 负责存储一个 Key Range (从 StartKey 到EndKey 的左闭右开区间)的数据,每个 TiKV 节点会负责多个 Region 。
alibaba Sentinel相关
Sentinel 底层采用高性能的滑动窗口数据结构 LeapArray 来统计实时的秒级指标数据,可以很好地支撑写多于读的高并发场景。
Entry 资源:可以使用方法签名,URL,甚至服务名称作为资源名来标示资源,是所有统计的基准
规则包括:流量控制规则(包括QPS,线程池等)、熔断降级规则以及系统保护规则
熔断升级:调用链路中某个资源出现不稳定,设置快速失败
原理:核心使用leapArray滑动窗口统计,分别记录每个资源每个统计维度(滑动窗口)中的实时数据.其余流浪控制,熔断控制皆有此基础数据做依靠
Apoll配置中心 核心逻辑
最原始结构
ConfigService是一个独立的微服务,服务于Client进行配置获取。
Client和ConfigService保持长连接,通过一种拖拉结合(push & pull)的模式,实现配置实时更新的同时,保证配置更新不丢失。
AdminService是一个独立的微服务,服务于Portal进行配置管理。Portal通过调用AdminService进行配置管理和发布。
ConfigService和AdminService共享ConfigDB,ConfigDB中存放项目在某个环境的配置信息。ConfigService/AdminService/ConfigDB三者在每个环境(DEV/FAT/UAT/PRO)中都要部署一份。
Protal有一个独立的PortalDB,存放用户权限、项目和配置的元数据信息。Protal只需部署一份,它可以管理多套环境。
升级到集群后,需要注册发现机制,引入Eureka
后续加入Eurek做服务注册发现(Eureka只支持java,又引入MetaServer用作多语言适配)
InfluxDb
1. 基于时间序列,支持与时间有关的相关函数(如最大,最小,求和等)
2. 可度量性:你可以实时对大量数据进行计算
3. 极少出现删除,更新数据的情况,都是插入新数据,删除数据基本都是清理过期数据
4. 绝大多数写入是针对最新时间戳的数据,并且数据按时间升序添加
5. 支持类SQL查询,简单,依赖少
6. 适用于物联网,传感器监控,DevOps监控,实时分析等.
ES :
Memcache一样,是非关系型数据库。是一个接近实时的搜索平台,2013年初,GitHub抛弃了Solr,采取ElasticSearch来做PB级的搜索
1. 为用户提供按关键字查询的全文搜索功能。
2. 实现企业海量数据的处理分析的解决方案。大数据领域的重要一份子
3. 天然支持分片,把数据分成多个shard,多个shard可以组成一份完整的数据,这些shard可以分布在集群中的各个机器节点中。随着数据的不断增加,集群可以增加多个分片,把多个分片放到多个机子上,已达到负载均衡,横向扩展。
4. ES 所有数据都是默认进行索引的,这点和mysql正好相反,mysql是默认不加索引,要加索引必须特别说明,ES只有不加索引才需要说明。
5. 倒排索引是对termIndex做的一层索引,并且存在内存中.
6. lucene是一个工具包是类似于发动机,而搜索引擎软件(ES,Solr)就是汽车
MyPerf4J是什么?
一个针对高并发、低延迟应用设计的高性能、无侵入的Java方法性能监控和统计工具。
ASM 字节码修改框架在 JVM 加载类时修改 Java 方法的字节码:
在方法的开头加入 long start = System.nanoTime();
在方法的结尾加入 ProfilingAspect.profiling(start, methodId);,其中 methodId 为类加载时为每一个方法分配的唯一 ID
底层算法为将1s拆分为1000长度数组,计算个数组对应的响应次数...
如:top80的计算, 若当前总请求书为1000,则从小到大计算1000*80%=800是落在那个数组里,那个数据就是top80的值
Mysql RedoLog相关:
名词解释:
落盘:调用操作系统fsync将操作系统IO缓存刷入磁盘
先持久化日志的策略叫做Write Ahead Log,先写日志,再落盘,。
redo log用来保证事务的持久性,undo log用来帮助事务回滚及Mvcc的功能。
redo log是InnoDB存储引擎层的日志,又称重做日志文件,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。在实例和介质失败(media failure)时,redo log文件就能派上用场,如数据库掉电,InnoDB存储引擎会使用redo log恢复到掉电前的时刻,以此来保证数据的完整性。
redo log通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
undo日志用于记录事务开始前的状态,用于事务失败时的回滚操作;redo日志记录事务执行后的状态,用来恢复未写入data file的已成功事务更新的数据。
1.redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
2.redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1 ”。
3.redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用
梳理下事务执行的各个阶段:
总览:先把记录写到redo log里面,并更新内存,时机合适落盘(三种落盘策略)
(1)写undo日志到log buffer;
(2)执行事务,并写redo日志到log buffer;
(3)如果innodb_flush_log_at_trx_commit=1,则将redo日志写到log file,并刷新落盘。
(4)提交事务。
undo_log:两个作用:事务数据回滚+MVCC(读完事务之前的版本完成快照读),
binlog是MySQL数据库的二进制日志,用于记录用户对数据库操作的SQL语句((除了数据查询语句)信息。
binlog的三种格式statement:row、mixed(前两种格式的混合)。
MySql RedoLog刷盘机制
1 :写入 + 同步; 每次提交事物都写盘,也就是执行写入和同步两个动作,类比redis的always策略。
0 :不写入 + 每秒写入一次;每一秒调用fsync落盘,兼功能和性能。
2 :写入 + 不同步, redo_buf写入redo_log,但由OS调用fsync落盘。
Redis写入aof过程
always:写入+同步;每次操作都调用fsync落盘,效率最低,安全性最高。
everysec:写入+每秒同步一次,每秒掉fysnc落盘,此种情况效率和安全性折中。
no:写入+ 不同步;写入aof,由OS觉得什么时候调用fsync落盘。
Elasticsearch
Elasticsearch是一个基于Lucene的搜索引擎。它提供了具有HTTP Web界面和无架构JSON文档的分布式,多租户能力的全文搜索引擎,兼有搜索引擎和NoSQL数据库功能的开源系统
可以用于全文搜索,结构化搜索以及近实时分析。
Elasticsearch中的文档是不可变的,删除和更新也都是写操作,因此不能被删除或者改动以展示其变更;删除时在.del文件中被标记为删除
es搜索过程:搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch;query时返回小的结果,使用coodinitor本地排序后再去多个分片上单独取数据.
在并发情况下,Elasticsearch如果保证读写一致?可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖
G1收集器
1. G1:是一款面向服务端应用的垃圾收集器,
2. G1收集器将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合
3. 运行过程:初始标记,并发标记,最终标记,筛选回收
卡表(Card Table)
老年代的对象可能引用新生代的对象,JVMs使用卡表加快对GC Roots的扫描,而不用每次遍历老年代
与CMS比较优点:
1. 并行与并发:G1充分发挥多核性能,使用多CPU来缩短Stop-The-world的时间,
2. 分代收集:G1能够自己管理不同分代内已创建对象和新对象的收集。
3. 空间整合:G1从整体上来看是基于‘标记-整理’算法实现,从局部(相关的两块Region)上来看是基于‘复制’算法实现,这两种算法都不会产生内存空间碎片。
4. 可预测的停顿:它可以自定义停顿时间模型,可以指定一段时间内消耗在垃圾回收商的时间不大于预期设定值。
CMS收集器优点:
并发收集、低停顿。
缺点:
CMS收集器对CPU资源非常敏感。
CMS收集器无法处理浮动垃圾(Floating Garbage)。
CMS收集器是基于标记-清除算法,该算法的缺点都有。
Codis
1. codis是一个分布式Redis解决方案,Codis采用的是Proxy-based的方案
2. codis使用 codis-ha保证代理下面的的redis(master-slave)高可用,进而替代sentinel,
3. codis支持1024个slot,有dashboard,支持codis-ha,分片存储在ZK中.
4. Twitter与之相对应的解决方案是:Twmeproxy,缺点是动态扩容比较差
三次握手,四次挥手
1. 序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。
2. 确认号ack:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。
3. 确认ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效
4. 同步SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。
5. 终止FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接
6. PS:ACK、SYN和FIN这些大写的单词表示标志位,其值要么是1,要么是0;ack、seq小写的单词表示序号。
四次挥手
问:为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
网络是不可靠的, client发出的ack请求如果server没有收到时,server会重新发送fin请求,这时间一来一回正好两个报文周期. 如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
为什么不能用两次握手进行连接
3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认,两次握手有可能导致死锁.假设C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
秒杀系统
秒杀系统原则: 数据尽量小,请求尽量小,路径尽量短,依赖尽量少,
直接动静分离,刷新页面而不刷新接口,(静态页面走浏览器缓存,走cdn.走nginx服务器,不使用java)
1.接口尽量使用本地缓存+redis(参考ES搜索时使用的QueryAndFetch机制)
2.保护机制:一定要加入流量控制,
3.网关(nginx,f5)限制,只有少数真正进后台服务器,
4.错峰,高峰期(头几秒)数据直接略过,取后续的数据
5.作弊攻防,
一个账号只允许一个请求(使用redis做校验,一个用户只能访问一次), 使用布隆过滤,
多账号同一个IP,加二次验证码
多账号,多IP,使用风控
总的流浪限制规则为:
1.大部分数据和流量都在CDN获取,拦截了大部分读的数据。
2.第二层(前台读系统)尽量走Cache。
3.到第三层(后台写系统),做数据校验、限流,进一步减少数据量和请求。
4.最后在数据层完成强一致性校验。
问: Redis,mysql数据一致性
1.肯定要先双写,但是无论是先删除缓存再更新DB,还是更新DB后再更新缓存,在高并发下都会出现数据不一致的问题,
2.更新数据库后走MQ 二次更新缓存,这样有一定的时间延迟.
3.使用Canal中间件订阅mysql的binlog完成数据二次更新(binlog日志格式为row)
4.使用Spark每天晚上跑增量数据.
Elasticsearch相关
Elasticsearch和MongoDB/Redis/Memcache一样,是非关系型数据库。是一个接近实时的搜索平台,从索引这个文档到这个文档能够被搜索到只有一个轻微的延迟,企业应用定位:采用Restful API标准的可扩展和高可用的实时数据分析的全文搜索工具。
1. 非关系数据库,存储是Json类型,接近实时的搜索平台,高可用, 支持RestfulApi,通过http接口服务.
2. es作为一个搜索引擎.
a) es搜索使用的是倒排索引,具体是分词后,对所有term排序(termDictionary),类似B-tree,,再次对term做index, 也叫做termIndex,全部放置在内存中
b) 查询方式包括term,match,phrase,match_phrase,multi_match,match_all,组合查询,过滤,sort等
写入数据时
1. es分片使用routing(默认为文档_id)作为key生成一个hash,然后确定分片位置,没有一个统一的Master做代理
2. 好处是无论请求到了哪一个分片,该分片都可以作为(coodinitor),并将请求转发到实际主分片上
3. 弊端是,创建索引时,如果确定好了分片数量以后就不得改变.
es包含一个主分片和多个副本,不同于kafka,副本也支持读请求(所以暑假冗余越大,搜索吞吐量越大),
1. 主分片数量一单固定就不能修改,副本数量可以随时调整.
2. ES为了提高并发写的能力,在写副本时添加版本号,使用乐观锁来控制.副本全部写入成功后向coodinitor报告成功.
ES索引具有不可变性,
1. 好处:不需要锁,索引文件一单进入文件缓存,就可以被搜索到,并且不需要读磁盘,性能大幅提升
2. 不足:删除时只是在.del文件中标记删除. 查询时也会遍历,只是在返回结果时filter掉.
ES如何在保持不可变同时更新倒排索引?
答案是使用多个索引.不是重写整个倒排索引,而是增加额外的索引反映最近的变化。每个倒排索引都可以按顺序查询,从最老的开始,最后把结果聚合。
延迟写策略,
1. 新的文档首先写入内存区的索引缓存,这时不可检索。
2. 时不时(默认 1s 一次),内存区的索引缓存被 refresh 到文件系统缓存(该过程比直接到磁盘代价低很多),成为一个新的段(segment)并被打开,这时可以被检索。
3. 新的段提交,写入磁盘,提交后,新的段加入提交点,缓存被清除,等待接收新的文档(由tranlog推动)。
4. 分片下的索引文件被拆分为多个子文件,每个子文件叫作段, 每一个段本身都是一个倒排索引,并且段具有不变性,一旦索引的数据被写入硬盘,就不可再修改。
5. 段被写入到磁盘后会生成一个提交点,提交点是一个用来记录所有提交后段信息的文件。一个段一旦拥有了提交点,就说明这个段只有读的权限,失去了写的权限
6. ES被称为进实时搜索:文档的改动不会立即被搜索,但是会在一秒内可见。
持久化:
1. 当有文档被索引时,数据既进入(内存->被刷入新的segment->文件缓存(不一定落盘,但可以被搜索到)),同时也刷入tranlog()
2. 当tranlog大于512或者超时30S,会进行一次全提交,调用fsync,所有数据落盘,生成一个提交点,tranlog清空.迎接下一个周期
3. 生成的小segment会合并..
kafka相关:
1. 所有读写操作均有mater来完成, slave副本不参与读写,只做备份.
2. slave数量要小于broker数量
3. LEO:Log End Offset的缩写,它表示了当前日志文件中下一条待写入消息的offset
4. LW: Low Watermark的缩写,俗称“低水位”,代表AR集合中最小的logStartOffset值,也是consumer能消费到的最大Offset
5. 每个follwer副本会周期的从主分片上pull数据.超时没有拉取的会被从IRS中移除.
6. kafka的同步既不是完全同步,也不是完全异步,而是一种ISR(In Sync Replica)机制.
7. kafka存储机制,
a) 每个分区内部使用segment存储.存储大小默认为1G,命名规范为上个segment的最后一条offset.
i. 好处:通过设置为小文件段,可以方便定期清理和删除已消费完文件
b) 每个segment有与之匹配的index文件,内部是稀疏索引,存储本分区的offset及物理偏移量
i. 稀疏索引大幅度降低index文件大小,缺点是查找时要额外消耗时间.
8. 查找过程:根据offset二次查找定位到segment(index),根据index文件查找到最近的物理偏移量,然后从这开始遍历log文件,顺序查找到对应offset数据.
JVM一些调试命令
1. jps 查看java进程
2. jinfo -flags pid 查看pid的jvm信息
3. jmap -heap pid 查看pid的堆栈信息
4. jstat -gc pid 查看pid的统计信息
5. jconsole java的GUI监事工具