2020年java开发最全面的面试题与答案详解

ZooKeeper

1.CAP定理
答:CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
2.ZAB协议
答:ZAB协议包括两种基本的模式:崩溃恢复和消息广播。当整个 Zookeeper 集群刚刚启动或者Leader服务器宕机、重启或者网络故障导致不存在过半的服务器与 Leader 服务器保持正常通信时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。当集群中超过半数机器与该 Leader 服务器完成数据同步之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。Zab协议 的全称是 Zookeeper Atomic Broadcast (Zookeeper原子广播)。
Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。是一种特别为ZooKeeper的设计的崩溃可恢复的原子消息广播算法
3.Leader选举算法和流程
答: 概念:
· 1. Zookeeper的服务器三种角色:Leader,Follower,Observer。Leader提供读和写,Follower提供读,参与过半投票,Observer只提供读,不参与投票,可以提高读性能。
· 2. ZXID,事务ID,用来唯一标识一次服务器状态的变更
· 3. myid,服务器SID,一个数字,通过配置文件配置,唯一
选举有两种情况,一是服务器启动的投票,二是运行期间的投票。

Redis

1.Redis的应用场景
答:redis适合的场景有:1、缓存;2、排行榜;3、计数器;4、分布式会话;5、分布式锁;6、 社交网络;7、最新列表;8、消息系统。9.共享Session 10.消息队列系统

2.Redis支持的数据类型(必考)
答:1、string(字符串) 2、hash(哈希)3、list(列表)4、set(集合)5、zset(sorted set:有序集合)
3.zset跳表的数据结构(必考)
答:ZSet(sorted set-有序集合 数据结构类似于Set结构,只是ZSet结构中,在set基础上加入了一个score字段,通过利用score和index来进行相关的排序。每个元 素都会有一个分值(score),然后所有元素按照分值的大小进行排列,相当于是一个进行了排序的链表。
4.Redis的数据过期策略(必考)
答:1. 定时删除 2. 惰性删除 3.定期删除
redis 过期策略是:定期删除+惰性删除

5.Redis的LRU过期策略的具体实现

答: 什么是LRU: 简而言之,就是每次淘汰最近最少使用的元素 。
1.get(key) - 如果该元素(总是正数)存在,将该元素移动到lru头部,并返回该元素的值,否则返回-1。
2.set(key,value) - 设置一个key的值为value(如果该元素存在),并将该元素移动到LRU头部。否则插入一个key,且值为value。如果在设置前检查到,该key插入后,会超过cache的容量,则根据LRU策略,删除最近最少使用的key。

6.如何解决Redis缓存雪崩,缓存穿透问题
答:1.Redis缓存雪崩 :数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机。
解决思路:1.缓存的高可用性 2.缓存降级 3.redis备份和快速预热 4.提前演练
2.缓存穿透问题 :缓存穿透是指查询一个一不存在的数据。解决思路:如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。设置一个过期时间或者当有值的时候将缓存中的值替换掉即可。
可以给key设置一些格式规则,然后查询之前先过滤掉不符合规则的Key。

7.Redis的持久化机制(必考)
答:Redis 的持久化机制有两种,第一种是快照,第二种是 AOF 日志。
区别:
快照是一次全量备份,AOF 日志是连续的增量备份。
快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。
AOF 日志在长期的运行过程中会变得无比庞大,数据库重启时需要加载 AOF 日志进行指令重放,这个时间就会无比漫长,所以需要定期进行 AOF 重写,给 AOF 日志进行瘦身。

8.Redis的管道pipeline
答:Redis的管道可以在大量数据需要一次性操作完成的时候,使用Pipeline进行批处理,将一大队的操作合并成一次操作,可以减少链路层的时间消耗,毕竟频繁操作是不好的嘛.

Mysql

1.事务的基本要素
1.原子性:事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行
2.一致性:事务开始前和结束后,数据库的完整性约束没有被破坏。
3.隔离性:同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。
4.持久性:事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

2.事务隔离级别(必考)
答:
事务隔离级别 脏读 不可重复读 幻读
读未提交 是 是 是
不可重复读 否 是 是
可重复读 否 否 是
串行化 否 否 否
在MySQL可重复读的隔离级别中并不是完全解决了幻读的问题,而是解决了读数据情况下的幻读问题。而对于修改的操作依旧存在幻读问题,就是说MVCC对于幻读的解决时不彻底的。 通过索引加锁,间隙锁,next key lock可以解决幻读的问题。
3.如何解决事务的并发问题(脏读,幻读)(必考)
1)脏读:(重点在于未提交)事务A修改了某个值,但是未提交,这时候事务A又读取了这个值,事务A可能又把该值撤销(回滚),这时候的数据可能就是无用数据。这就叫脏读。这里有些同学可能就要问了,既然事务A没提交,事务B是怎么读取到的?如果MYSQL隔离级别设置ReadUnCommitted,这时候其他事务就可以读取到未提交的事务。
2)幻读:(重点在于新增和删除,数据条数对比)相同的查询在事务执行后,发现得到的结果不一样,明明执行了5个操作,却发现多了N个,或少了N个,就好像发生了幻觉一样。
3)不可重复读:(读取数据本身的对比)一个事务在读取某些数据后的一段时间后,再次读取这个数据,发现其读取出来的数据内容已经发生了改变,就是不可重复读。
并发处理带来的问题中,更新丢失可以完全避免,由应用对数据加锁即可。脏读、不可重读度、幻读,其实都是数据库的一致性问题,必须由一定的事务隔离机制来解决。
其中一种方法是:不用加锁,通过一定的机制生成一个数据请求时间点的一致性快照,并用这个快照来提供一个界别的一致性读取。从用户的角度看,好像是数据库提偶拱了统一数据的多个版本。这种技术叫做:数据库多版本并发控制,MVCC 多版本数据库。事务隔离的本质是使事务在一定程度上串行化执行,显然和并发机制是矛盾的。数据库的事务隔离越严格,并发负作用越小,代价越高(影响并发访问了呗)。
  为了解决隔离和并大的矛盾,IOS SQL92规定了4个隔离级别。(隔离==串行)

4.MVCC多版本并发控制(必考)
答:MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读

5.binlog,redolog,undolog都是什么,起什么作用
答:MySQL中有六种日志文件,分别是:二进制日志(binlog)、重做日志(redo log)、回滚日志(undo log)、错误日志(errorlog)、慢查询日志(slow query log)、一般查询日志(general log),中继日志(relay log)。
其中重做日志和回滚日志与事务操作息息相关,二进制日志也与事务操作有一定的关系,这三种日志,对理解MySQL中的事务操作有着重要的意义。
二进制日志(binlog):作用:1,用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。2,用于数据库的基于时间点的还原。
重做日志(redo log):作用:确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。重做日志通过不止一种方式写入到磁盘,重做日志的写盘,并不一定是随着事务的提交才写入重做日志文件的,而是随着事务的开始,逐步开始的。
回滚日志(undo log):作用:保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。
6.InnoDB的行锁/表锁
答:mysql常用引擎有MYISAM和InnoDB,而InnoDB是mysql默认的引擎。MYISAM不支持行锁,而InnoDB支持行锁和表锁。在mysql 的 InnoDB引擎支持行锁,与Oracle不同,mysql的行锁是通过索引加载的,即是行锁是加在索引响应的行上的,要是对应的SQL语句没有走索引,则会全表扫描,行锁则无法实现,取而代之的是表锁。
表锁:不会出现死锁,发生锁冲突几率高,并发低。
行锁:会出现死锁,发生锁冲突几率低,并发高。
7.myisam和innodb的区别,什么时候选择myisam
答: MyISAM:索引文件和数据文件是分离的,索引文件仅保存记录所在页的指针(物理位置),通过这些地址来读取页,进而读取被索引的行。下图是MyISAM的索引原理图:(为了简化,一个页内只存放了两条记录。)
InnoD: 与 MyISAM相同的一点是,InnoDB 也采用 B+Tree这种数据结构来实现 B-Tree索引。而很大的区别在于,InnoDB 存储引擎采用“聚集索引”的数据存储方式实现B-Tree索引,所谓“聚集”,就是指数据行和相邻的键值紧凑地存储在一起,注意 InnoDB 只能聚集一个叶子页(16K)的记录(即聚集索引满足一定的范围的记录),因此包含相邻键值的记录可能会相距甚远。
注意: innodb来说,
1: 主键索引 既存储索引值,又在叶子中存储行的数据
2: 如果没有主键, 则会Unique key做主键
3: 如果没有unique,则系统生成一个内部的rowid做主键.
4: 像innodb中,主键的索引结构中,既存储了主键值,又存储了行数据,这种结构称为”聚簇索引”
什么时候选用myisam
myisam的主键索引的叶子节点只存放数据在物理磁盘上的指针,其他次索引也是一样的;
innodb的主键索引的叶子节点下面直接存放数据,其他次索引的叶子节点指向主键id;
由此可以挖掘出一个问题,就是如果Innodb有大数据列,比如 varchar(300),这种比较多的话,那么排序的时候用主键id排序会比较慢,因为id主键下面放着所有数据列,而Myisam就不需要扫描数据列,要解决这个问题的话可以再建一个和主键id一起的联合索引;
MyISAM表索引在处理文本索引时更具优势,而INNODB表索引在其它类型上更具效率优势。比如全文索引一般在CHAR、VARCHAR或TEXT列上创建,MyISAM表支持而INNODB表不支持,常见主要针对文本进行索引。同时MySQL高并发需要事务场景时,只能使用INNODB表。
8.为什么选择B+树作为索引结构(必考)
答:因为B+Tree所有索引数据都在叶子节点上,并且增加了顺序访问指针,每个叶子节点都有指向相邻叶子节点的指针。进一步降低了树的高度,这样做是为了提高区间效率,所以选择它。

在MySQL中,主要有四种类型的索引,分别为:B-Tree索引,Hash索引,Fulltext索引(MyISAM 表)和R-Tree索引。
B-树(B树):有序数组+平衡多叉树;
B+树:有序数组链表+平衡多叉树;
一、Mysql索引主要有两种结构:B+Tree索引和Hash索引
(a) Inodb存储引擎 默认是 B+Tree索引
(b) MyISAM 存储引擎 默认是Fulltext索引;
©Memory 存储引擎 默认 Hash索引;
Hash索引
mysql中,只有Memory(Memory表只存在内存中,断电会消失,适用于临时表)存储引擎显示支持Hash索引,是Memory表的默认索引类型,尽管Memory表也可以使用B+Tree索引。Hash索引把数据以hash形式组织起来,因此当查找某一条记录的时候,速度非常快。但是因为hash结构,每个键只对应一个值,而且是散列的方式分布。所以它并不支持范围查找和排序等功能。
B+Tree索引
B+Tree是mysql使用最频繁的一个索引数据结构,是Inodb和Myisam存储引擎模式的索引类型。相对Hash索引,B+Tree在查找单条记录的速度比不上Hash索引,但是因为更适合排序等操作,所以它更受欢迎。毕竟不可能只对数据库进行单条记录的操作。带顺序访问指针的B+Tree,B+Tree所有索引数据都在叶子节点上,并且增加了顺序访问指针,每个叶子节点都有指向相邻叶子节点的指针。进一步降低了树的高度,这样做是为了提高区间效率,例如查询key为从18到49的所有数据记录,当找到18后,只要顺着节点和指针顺序遍历就可以以此向访问到所有数据节点,极大提高了区间查询效率。大大减少磁盘I/O读取数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点需要一次I/O就可以完全载入。
二叉查找树:解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表。
平衡二叉树:通过旋转解决了平衡的问题,但是旋转操作效率太低。
红黑树:通过舍弃严格的平衡和引入红黑节点,解决了 AVL旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多。
什么是索引
索引(Index)是帮助数据库高效获取数据的数据结构。索引是在基于数据库表创建的,它包含一个表中某些列的值以及记录对应的地址,并且把这些值存储在一个数据结构中。最常见的就是使用哈希表、B+树作为索引。
一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重。说起加速查询,就不得不提到索引了。
9.索引B+树的叶子节点都可以存哪些东西(必考)
答:可能存储的是整行数据,也有可能是主键的值。B+树的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引。而索引B+ Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引
10.查询在什么时候不走(预期中的)索引(必考)
1.答:模糊查询 %like
2.索引列序
3.where对null判断
4.where不等于
5.or操作有至少一个字段没有索引
6.需要回表的查询结果集过大(超过配置的范围)
11.sql如何优化
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
1 select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
1 select id from t where num=0
3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

1 select id from t where num=10 or num=20
可以这样查询:
1
2
3 select id from t where num=10
union all
select id from t where num=20
5.in 和 not in 也要慎用,否则会导致全表扫描,如:
1 select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
1 select id from t where num between 1 and 3
6.下面的查询也将导致全表扫描:
1 select id from t where name like ‘%abc%’
7.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
1 select id from t where num/2=100
应改为:
1 select id from t where num=100*2
8.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
1 select id from t where substring(name,1,3)=‘abc’–name以abc开头的id
应改为:
1 select id from t where name like ‘abc%’
9.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
10.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,
否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
11.不要写一些没有意义的查询,如需要生成一个空表结构:
1 select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
1 create table #t(…)

12.explain是如何解析sql的
答: 使用 EXPLAIN 关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。通过explain命令可以得到:
1. 表的读取顺序
2. 数据读取操作的操作类型
3. 哪些索引可以使用
4. 哪些索引被实际使用
5. 表之间的引用
6. 每张表有多少行被优化器查询
EXPLAIN字段解析:
1、table:显示这一行的数据是关于哪张表的
2、type:这是最重要的字段之一,显示查询使用了何种类型。
从最好到最差的连接类型为system、const、eq_reg、ref、range、index和ALL,一般来说,得保证查询至少达到range级别,最好能达到ref。
type中包含的值:
system、const: 可以将查询的变量转为常量. 如id=1; id为 主键或唯一键.
eq_ref: 访问索引,返回某单一行的数据.(通常在联接时出现,查询使用的索引为主键或惟一键)
ref: 访问索引,返回某个值的数据.(可以返回多行) 通常使用=时发生
range: 这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西,并且该字段上建有索引时发生的情况(注:不一定好于index)
index: 以索引的顺序进行全表扫描,优点是不用排序,缺点是还要全表扫描
ALL: 全表扫描,应该尽量避免
3、possible_keys:显示可能应用在这张表中的索引。如果为空,表示没有可能应用的索引。
4、key:实际使用的索引。如果为NULL,则没有使用索引。
MySQL很少会选择优化不足的索引,此时可以在SELECT语句中使用FORCE INDEX(index_name)来强制使用一个索引或者用IGNORE INDEX(index_name)来强制忽略索引。
MySQL强制使用和不使用索引:https://www.cnblogs.com/lcngu/p/6023179.html
5、key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好
6、ref:显示索引的哪一列被使用了,如果可能的话,是一个常数
7、rows:MySQL认为必须检索的用来返回请求数据的行数
8、Extra:关于MySQL如何解析查询的额外信息,主要有以下几种
Extra中包含的值:
using index: 只用到索引,可以避免访问表,性能很高。
using where: 使用到where来过滤数据, 不是所有的where clause都要显示using where. 如以=方式访问索引。
using tmporary: 用到临时表去处理当前的查询。
using filesort: 用到额外的排序,此时mysql会根据联接类型浏览所有符合条件的记录,并保存排序关键字和行指针,然后排序关键字并按顺序检索行。(当使用order by v1,而没用到索引时,就会使用额外的排序)。
range checked for eache record(index map:N): 没有好的索引可以使用。
Using index for group-by:表明可以在索引中找到分组所需的所有数据,不需要查询实际的表。explain select user_id from t_order group by user_id;
见到Using temporary和Using filesort,就意味着MySQL根本不能使用索引,结果是检索会很慢,需要优化sql了。
13.order by原理
1.利用索引的有序性获取有序数据
2.利用内存/磁盘文件排序获取结果
1) 双路排序:是首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行指针信息,然后在
sortbuffer 中进行排序。
2)单路排序:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序.
14.事务的并发问题
3.脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
4.不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
5.幻读:A事务读取了B事务已经提交的新增数据。注意和不可重复读的区别,这里是新增,不可重复读是更改(或删除)。select某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。
15.SQL执行顺序
SQL的执行顺序:from—where–group by—having—select—order by
JVM

1.运行时数据区域(内存模型)(必考)
1.程序计数器:程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。是线程私有”的内存。
2.Java虚拟机栈:与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧 ,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
3.本地方法栈:本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
4.Java堆:对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
2.垃圾回收机制(必考)
1.引用计数法:引用计数法是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用计数器,当有引用连接至对象时,引用计数加1。当引用离开作用域或被置为null时,引用计数减1。虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象引用计数为0时,就释放其占用的空间。
2.可达性分析算法:这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

3.垃圾回收算法(必考)
1.停止-复制:先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的对象全部都是垃圾。当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按前述方法简单,直接的分配了。缺点是一浪费空间,两个堆之间要来回倒腾,二是当程序进入稳定态时,可能只会产生极少的垃圾,甚至不产生垃圾,尽管如此,复制式回收器仍会将所有内存自一处复制到另一处。
2.标记-清除:同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当它找到一个存活的对象,就会给对象一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象会被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器如果要希望得到连续空间的话,就得重新整理剩下的对象。
3.标记-整理:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
4.分代收集算法:把Java堆分为新生代和老年代,然后根据各个年代的特点采用最合适的收集算法。新生代中,对象的存活率比较低,所以选用复制算法,老年代中对象存活率高且没有额外空间对它进行分配担保,所以使用“标记-清除”或“标记-整理”算法进行回收。

4.Minor GC和Full GC触发条件
答:Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
i.调用System.gc时,系统建议执行Full GC,但是不必然执行
ii.老年代空间不足
iii.方法区空间不足
iv.通过Minor GC后进入老年代的平均大小大于老年代的可用内存
v.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
5.GC中Stop the world(STW)
答:在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起。此时,系统只能允许GC线程进行运行,其他线程则会全部暂停,等待GC线程执行完毕后才能再次运行。这些工作都是由虚拟机在后台自动发起和自动完成的,是在用户不可见的情况下把用户正常工作的线程全部停下来,这对于很多的应用程序,尤其是那些对于实时性要求很高的程序来说是难以接受的。
但不是说GC必须STW,你也可以选择降低运行速度但是可以并发执行的收集算法,这取决于你的业务。

6.各垃圾回收器的特点及区别
各垃圾收集器的特点和作用:如图
2020年java开发最全面的面试题与答案详解
相关名词解释
Stop The World
在垃圾回收器进行回收之前,JVM会对内存中的对象进行一次可达性分析,也就是哪些是可回收的,哪些是不可回收的,但是在这个判断的过程中,要求JVM中的对象是不可变得,也就是要求一个快照,所以在这个时候就会暂停所有的工作线程,也就是所说的Stop The World。
垃圾收集算法
JVM中常用的垃圾收集算法有标记清除算法,复制算法,标记整理算法,具体有哪些特点,就不一一列举,自行了解。
7.双亲委派模型
答:双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。
8.JDBC和双亲委派模型关系
问题一:双亲委派模型是什么
如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
问题二:JDBC为什么要破坏双亲委派模型
因为类加载器受到加载范围的限制,在某些情况下父类加载器无法加载到需要的文件,这时候就需要委托子类加载器去加载class文件。JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包。DriverManager 类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,根据类加载机制,当被装载的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。我们知道,BootStrap类加载器默认只负责加载 $JAVA_HOME中jre/lib/rt.jar 里所有的class,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。
9.JVM锁优化和锁膨胀过程
1.答:自旋锁:自旋锁其实就是在拿锁时发现已经有线程拿了锁,自己如果去拿会阻塞自己,这个时候会选择进行一次忙循环尝试。也就是不停循环看是否能等到上个线程自己释放锁。自适应自旋锁指的是例如第一次设置最多自旋10次,结果在自旋的过程中成功获得了锁,那么下一次就可以设置成最多自旋20次。
2.锁粗化:虚拟机通过适当扩大加锁的范围以避免频繁的拿锁释放锁的过程。
3.锁消除:通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),或者同步块内进行的是原子操作,而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。
4.偏向锁:在大多数的情况下,锁不仅不存在多线程的竞争,而且总是由同一个线程获得。因此为了让线程获得锁的代价更低引入了偏向锁的概念。偏向锁的意思是如果一个线程获得了一个偏向锁,如果在接下来的一段时间中没有其他线程来竞争锁,那么持有偏向锁的线程再次进入或者退出同一个同步代码块,不需要再次进行抢占锁和释放锁的操作。
5.轻量级锁:当存在超过一个线程在竞争同一个同步代码块时,会发生偏向锁的撤销。当前线程会尝试使用CAS来获取锁,当自旋超过指定次数(可以自定义)时仍然无法获得锁,此时锁会膨胀升级为重量级锁。
6.重量级锁:重量级锁依赖对象内部的monitor锁来实现,而monitor又依赖操作系统的MutexLock(互斥锁)。当系统检查到是重量级锁之后,会把等待想要获取锁的线程阻塞,被阻塞的线程不会消耗CPU,但是阻塞或者唤醒一个线程,都需要通过操作系统来实现。

Java基础

1.HashMap和ConcurrentHashMap区别(必考)
答:由于HashMap是线程不同步的,虽然处理数据的效率高,但是在多线程的情况下存在着安全问题,因此设计了CurrentHashMap来解决多线程安全问题。
HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
HashMap的环:若当前线程此时获得ertry节点,但是被线程中断无法继续执行,此时线程二进入transfer函数,并把函数顺利执行,此时新表中的某个位置有了节点,之后线程一获得执行权继续执行,因为并发transfer,所以两者都是扩容的同一个链表,当线程一执行到e.next = new table[i] 的时候,由于线程二之前数据迁移的原因导致此时new table[i] 上就有ertry存在,所以线程一执行的时候,会将next节点,设置为自己,导致自己互相使用next引用对方,因此产生链表,导致死循环。
在JDK1.7版本中,ConcurrentHashMap维护了一个Segment数组,Segment这个类继承了重入锁ReentrantLock,并且该类里面维护了一个 HashEntry<K,V>[] table数组,在写操作put,remove,扩容的时候,会对Segment加锁,所以仅仅影响这个Segment,不同的Segment还是可以并发的,所以解决了线程的安全问题,同时又采用了分段锁也提升了并发的效率。在JDK1.8版本中,ConcurrentHashMap摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap。
2.ConcurrentHashMap的数据结构(必考)
答:由于HashMap是线程不同步的,虽然处理数据的效率高,但是在多线程的情况下存在着安全问题,因此设计了CurrentHashMap来解决多线程安全问题。
HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。在JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发

3.高并发HashMap的环是如何产生的
答:若当前线程此时获得ertry节点,但是被线程中断无法继续执行,此时线程二进入transfer函数,并把函数顺利执行,此时新表中的某个位置有了节点,之后线程一获得执行权继续执行,因为并发transfer,所以两者都是扩容的同一个链表,当线程一执行到e.next = new table[i] 的时候,由于线程二之前数据迁移的原因导致此时new table[i] 上就有ertry存在,所以线程一执行的时候,会将next节点,设置为自己,导致自己互相使用next引用对方,因此产生链表,导致死循环。
4.volatile作用(必考)
答:volatile在多处理器开发中保证了共享变量的“ 可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。(共享内存,私有内存)
5.Atomic类如何保证原子性(CAS操作)(必考)
答:CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。如 Intel 处理器,比较并交换通过指令的 cmpxchg 系列实现。
6.synchronized和Lock的区别(必考)
1.答:首先synchronized是java内置关键字在jvm层面,Lock是个java类。
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁,并且可以主动尝试去获取锁。
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁。
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了。
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
7.为什么要使用线程池(必考)
1.答:减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线程的数目,放置因为消耗过多的内存,而把服务器累趴下
8.核心线程池ThreadPoolExecutor的参数(必考)
3.答:corePoolSize:指定了线程池中的线程数量
4.maximumPoolSize:指定了线程池中的最大线程数量
5.keepAliveTime:线程池维护线程所允许的空闲时间
6.unit: keepAliveTime 的单位。
7.workQueue:任务队列,被提交但尚未被执行的任务。
8.threadFactory:线程工厂,用于创建线程,一般用默认的即可。
9.handler:拒绝策略。当任务太多来不及处理,如何拒绝任务。
9.ThreadPoolExecutor(线程池)的工作流程(必考)
一个新的任务到线程池时,线程池的处理流程如下:
1.线程池判断核心线程池里的线程是否都在执行任务。 如果不是,创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2.线程池判断阻塞队列是否已满。 如果阻塞队列没有满,则将新提交的任务存储在阻塞队列中。如果阻塞队列已满,则进入下个流程。
3.线程池判断线程池里的线程是否都处于工作状态。 如果没有,则创建一个新的工作线程来执行任务。如果已满,则交给饱和策略来处理这个任务。
线程池的核心实现类是ThreadPoolExecutor类,用来执行提交的任务。因此,任务提交到线程池时,具体的处理流程是由ThreadPoolExecutor类的execute()方法去完成的。
1.如果当前运行的线程少于corePoolSize,则创建新的工作线程来执行任务(执行这一步骤需要获取全局锁)。
2.如果当前运行的线程大于或等于corePoolSize,而且BlockingQueue未满,则将任务加入到BlockingQueue中。
3.如果BlockingQueue已满,而且当前运行的线程小于maximumPoolSize,则创建新的工作线程来执行任务(执行这一步骤需要获取全局锁)。
4.如果当前运行的线程大于或等于maximumPoolSize,任务将被拒绝,并调用RejectExecutionHandler.rejectExecution()方法。即调用饱和策略对任务进行处理。
5.工作线程(Worker): 线程池在创建线程时,会将线程封装成工作线程Woker。Woker在执行完任务后,不是立即销毁而是循环获取阻塞队列里的任务来执行。

ThreadPoolExecutor的执行主要围绕Worker,Worker 实现了 AbstractQueuedSynchronizer 并继承了 Runnable
1.ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
2.ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

10.线程之间如何通信
答:合理地使用wait()、notify()和notifyAll()方法确实能够很好地解决线程间通信的问题。但是,也应该了解到这些方法是更复杂的锁定、排队和并发性代码的构件。尤其是使用 notify()来代替notifyAll()时是有风险的。除非确实知道每一个线程正在做什么,否则最好使用notifyAll()。
11.Boolean占几个字节
答:未精确定义字节。Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位。
12.jdk1.8/jdk1.7都分别新增了哪些特性
jdk1.7新特性
1、泛型实例的创建可以通过类型推断来简化,可以去掉后面new部分的泛型类型,只用<>就可以了。
2、并发工具增强: fork-join框架最大的增强,充分利用多核特性,将大问题分解成各个子问题,由多个cpu 可以同时 解决多个子问题,最后合并结果,继承RecursiveTask,实现compute方法,然后调用fork计算,最后用join合并结果。
3、try-with-resources语句是一种声明了一种或多种资源的try语句。资源是指在程序用完了之后必须要关闭的对象。try-with-resources语句保证了每个声明了的资源在语句结束的时候都会被关闭。任何实现了java.lang.AutoCloseable接口的对象,和实现了java .io .Closeable接口的对象,都可以当做资源使用。
4、Catch多个异常:在Java 7中,catch代码块得到了升级,用以在单个catch块中处理多个异常。如果你要捕获多个异常并且它们包含相似的代码,使用这一特性将会减少代码重复度。下面用一个例子来理解。
catch(IOException | SQLException | Exception ex){
logger.error(ex);
throw new MyException(ex.getMessage());
}
dk1.8新特性知识点:
1 jdk1.8对hashMap等map集合的优化
2 Lambda表达式
3 函数式接口
4 方法引用和构造器调用
5 Stream API
6 并行流和串行流
7 Optional容器
Java 8引入Optional类来防止空指针异常,Optional类最先是由Google的Guava项目引入的。Optional类实际上是个容器:它可以保存类型T的值,或者保存null。使用Optional类我们就不用显式进行空指针检查了。
8 接口中的默认方法和静态方法
9 新时间日期API
10 定义可重复的注解
在Java 5中使用注解有一个限制,即相同的注解在同一位置只能声明一次。Java 8引入重复注解,这样相同的注解在同一地方也可以声明多次。重复注解机制本身需要用@Repeatable注解。Java 8在编译器层做了优化,相同注解会以集合的方式保存,因此底层的原理并没有变化。
11 扩展注解的支持
Java 8扩展了注解的上下文,几乎可以为任何东西添加注解,包括局部变量、泛型类、父类与接口的实现,连方法的异常也能添加注解。
12 jvm中的方法区变成了元数据区(PermGen变成了Metaspace)
13 更好的类型推测机制(不需要太多的强制类型转换了)
14 编译器优化:Java 8 将方法的参数名加入了字节码中,这样在运行时 通过反射 就能获取到参数名,只需要在编译时使用-parameters参数。

13.Exception和Error
答:Exception和Error都是继承了Throwable类,在java中只有Throwable类型的实例才可以被抛出(throw)或者捕获(catch),他是异常处理机制的基本组成类型。
Exception和Error体现了java平台设计者对不同异常情况的分类,Exception是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应的处理。
Error是指正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(比如JVM自身)处于非正常状态,不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如OutOfMemoryError之类,都是Error的子类。
Exception又分为可检查(checked)异常和不检查(unchecked)异常,可检查异常在源码里必须显示的进行捕获处理,这里是编译期检查的一部分。前面我们介绍的不可查的Error,是Throwable不是Exception。
不检查异常就是所谓的运行时异常,类似NullPointerException,ArrayIndexOutOfBoundsExceptin之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译器强制要求。
Spring

1.Spring的IOC/AOP的实现(必考)
答:IOC:控制反转,就是把对象的创建交给Spring来做
SpringIoc所使用的技术
1、xml配置文件
2、dom4j解析XML文件
3、工厂设计模式
4、反射
AOP(Aspect-Oriented Programming,面向切面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需 要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日 志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种 散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为; 那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手 将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横 切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的 方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。然而殊途同归,实现AOP的技术特性却是相同的,分别为:
1、join point(连接点):是程序执行中的一个精确执行点,例如类中的一个方法。它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。
2、point cut(切入点):本质上是一个捕获连接点的结构。在AOP中,可以定义一个point cut,来捕获相关方法的调用。
3、advice(通知):是point cut的执行代码,是执行“方面”的具体逻辑。
4、aspect(方面):point cut和advice结合起来就是aspect,它类似于OOP中定义的一个类,但它代表的更多是对象间横向的关系。
5、introduce(引入):为对象引入附加的方法或属性,从而达到修改对象结构的目的。有的AOP工具又将其称为mixin。
AOP应用到的横切技术,通常分为两种类型:动态横切和静态横切。
2.动态代理的实现方式(必考)
答:无需声明代理类。是使用反射和字节码的技术,在运行期创建指定接口或类的子类(即动态代理类)以及其实例对象的技术。通过动态代理技术可以无侵入地对代码进行增强。
Java领域中,常用的动态代理实现方式有两种,一种是利用JDK反射机制生成代理,另外一种是使用CGLIB代理。
jdk动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。JDK代理必须要提供接口,而CGLIB则不需要,可以直接代理类。下面分别举例说明
CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
3.Spring如何解决循环依赖(三级缓存)(必考)
1.第一级缓存:单例缓存池singletonObjects。
2.第二级缓存:早期提前暴露的对象缓存earlySingletonObjects。(属性还没有值对象也没有被初始化)
3.第三级缓存:singletonFactories单例对象工厂缓存。
4.Spring的后置处理器
答:BeanPostProcessor也就是后置处理器的作用是在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的。
1.ApplicationContextAware
类实现了ApplicationContextAware接口,可以取得上下文ApplicationContex,用于自己业务操作。ApplicationContextAware对应的后置处理器是ApplicationContextAwareProcessor。
2.BeanValidationPostProcessor
BeanValidationPostProcessor用来做数据校验的,比如对加了@Validated类按照JSR提供的校验注解(比如@Null)进行校验。
3.InitDestroyAnnotationBeanPostProcessor
后置处理器继承于BeanPostProcessor,主要在实例化bean前后工作; AOP创建代理对象就是通过该接口实现。是对@PostConstruct, @PreDestroy进行处理。
4.BeanFactoryPostProcessor
Bean工厂的后置处理器,在bean定义(bean definitions)加载完成后,bean尚未初始化前执行。
5.BeanDefinitionRegistryPostProcessor
继承于BeanFactoryPostProcessor。其自定义的方法postProcessBeanDefinitionRegistry会在bean定义(bean definitions)将要加载,bean尚未初始化前真执行,即在BeanFactoryPostProcessor的postProcessBeanFactory方法前被调用。

5.Spring的@Transactional如何实现的(必考)
答:Transactional是spring中定义的事务注解,在方法或类上加该注解开启事务。主要是通过反射获取bean的注解信息,利用AOP对编程式事务进行封装实现。

6.Spring的事务传播级别
1.REQUIRED(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。
2.SUPPORTS:支持使用当前事务,如果当前事务不存在,则不使用事务。
3.MANDATORY:强制,支持使用当前事务,如果当前事务不存在,则抛出Exception。
4.REQUIRES_NEW:创建一个新事务,如果当前事务存在,把当前事务挂起。
5.NOT_SUPPORTED:无事务执行,如果当前事务存在,把当前事务挂起。
6.NEVER:无事务执行,如果当前有事务则抛出Exception。
7.NESTED:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。
7.BeanFactory和ApplicationContext的联系和区别
1.BeanFactory是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。
2.ApplicationContext应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能。如国际化,访问资源,载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,消息发送、响应机制,AOP等。
3.BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化。ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化

消息队列

1.为什么需要消息队列
答:解耦,异步处理,削峰/限流 (海量)日志处理,消息通讯

2.Kafka的文件存储机制
答:Kafka中消息是以topic进行分类的,生产者通过topic向Kafka broker发送消息,消费者通过topic读取数据。然而topic在物理层面又能以partition为分组,一个topic可以分成若干个partition。partition还可以细分为segment,一个partition物理上由多个segment组成,segment文件由两部分组成,分别为“.index”文件和“.log”文件,分别表示为segment索引文件和数据文件。这两个文件的命令规则为:partition全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值。
3.Kafka 如何保证可靠性
答:如果我们要往 Kafka 对应的主题发送消息,我们需要通过 Producer 完成。前面我们讲过 Kafka 主题对应了多个分区,每个分区下面又对应了多个副本;为了让用户设置数据可靠性, Kafka 在 Producer 里面提供了消息确认机制。也就是说我们可以通过配置来决定消息发送到对应分区的几个副本才算消息发送成功。可以在定义 Producer 时通过 acks 参数指定。这个参数支持以下三种值:
1.acks = 0:意味着如果生产者能够通过网络把消息发送出去,那么就认为消息已成功写入 Kafka 。在这种情况下还是有可能发生错误,比如发送的对象无能被序列化或者网卡发生故障,但如果是分区离线或整个集群长时间不可用,那就不会收到任何错误。在 acks=0 模式下的运行速度是非常快的(这就是为什么很多基准测试都是基于这个模式),你可以得到惊人的吞吐量和带宽利用率,不过如果选择了这种模式, 一定会丢失一些消息。
2.acks = 1:意味若 Leader 在收到消息并把它写入到分区数据文件(不一定同步到磁盘上)时会返回确认或错误响应。在这个模式下,如果发生正常的 Leader 选举,生产者会在选举时收到一个 LeaderNotAvailableException 异常,如果生产者能恰当地处理这个错误,它会重试发送悄息,最终消息会安全到达新的 Leader 那里。不过在这个模式下仍然有可能丢失数据,比如消息已经成功写入 Leader,但在消息被复制到 follower 副本之前 Leader发生崩溃。
3.acks = all(这个和 request.required.acks = -1 含义一样):意味着 Leader 在返回确认或错误响应之前,会等待所有同步副本都收到悄息。如果和min.insync.replicas 参数结合起来,就可以决定在返回确认前至少有多少个副本能够收到悄息,生产者会一直重试直到消息被成功提交。不过这也是最慢的做法,因为生产者在继续发送其他消息之前需要等待所有副本都收到当前的消息。

4.Kafka消息是采用Pull模式,还是Push模式
答:Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers将消息推送到consumer,也就是pull还push。在这方面,Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到broker,consumer从broker拉取消息。push模式下,当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了。最终Kafka还是选取了传统的pull模式。Pull模式的另外一个好处是consumer可以自主决定是否批量的从broker拉取数据。Pull有个缺点是,如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到t达。为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达。

5.Kafka是如何实现高吞吐率的
1.顺序读写:kafka的消息是不断追加到文件中的,这个特性使kafka可以充分利用磁盘的顺序读写性能
2.零拷贝:跳过“用户缓冲区”的拷贝,建立一个磁盘空间和内存的直接映射,数据不再复制到“用户态缓冲区”
3.文件分段:kafka的队列topic被分为了多个区partition,每个partition又分为多个段segment,所以一个队列中的消息实际上是保存在N多个片段文件中
4.批量发送:Kafka允许进行批量发送消息,先将消息缓存在内存中,然后一次请求批量发送出去
5.数据压缩:Kafka还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩
6.Kafka判断一个节点还活着的两个条件
1.节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接
2.如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太久

Dubbo

1.Dubbo的容错机制
1.失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数
2.快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
3.失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
4.失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
5.并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。
6.广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息
2.Dubbo注册中心挂了还可以继续通信么
可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信。
3.Dubbo框架设计结构
1.服务接口层:该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。
2.配置层:对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。
3.服务代理层:服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。
4.服务注册层:封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。
5.集群层:封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只需要与一个服务提供方进行交互。
6.监控层:RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。
7.远程调用层:封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
8.信息交换层:封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。
9.网络传输层:抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
10.数据序列化层:可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。

操作系统

1.进程和线程
1.进程是操作系统资源分配的最小单位,线程是CPU任务调度的最小单位。一个进程可以包含多个线程,所以进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同。
2.不同进程间数据很难共享,同一进程下不同线程间数据很易共享。
3.每个进程都有独立的代码和数据空间,进程要比线程消耗更多的计算机资源。线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。
4.进程间不会相互影响,一个线程挂掉将导致整个进程挂掉。
5.系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
2.进程的组成部分
进程由进程控制块(PCB)、程序段、数据段三部分组成。
3.进程的通信方式
1.无名管道:半双工的,即数据只能在一个方向上流动,只能用于具有亲缘关系的进程之间的通信,可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
2.FIFO命名管道:FIFO是一种文件类型,可以在无关的进程之间交换数据,与无名管道不同,FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
3.消息队列:消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
4.信号量:信号量是一个计数器,信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
5.共享内存:共享内存指两个或多个进程共享一个给定的存储区,一般配合信号量使用。
4.进程间五种通信方式的比较
1.管道:速度慢,容量有限,只有父子进程能通讯。
2.FIFO:任何进程间都能通讯,但速度慢。
3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。
4.信号量:不能传递复杂消息,只能用来同步。
5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存。
5.死锁的4个必要条件
1.互斥条件:一个资源每次只能被一个线程使用;
2.请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放;
3.不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺;
4.循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
6.如何避免(预防)死锁
1.破坏“请求和保持”条件:让进程在申请资源时,一次性申请所有需要用到的资源,不要一次一次来申请,当申请的资源有一些没空,那就让线程等待。不过这个方法比较浪费资源,进程可能经常处于饥饿状态。还有一种方法是,要求进程在申请资源前,要释放自己拥有的资源。
2.破坏“不可抢占”条件:允许进程进行抢占,方法一:如果去抢资源,被拒绝,就释放自己的资源。方法二:操作系统允许抢,只要你优先级大,可以抢到。
3.破坏“循环等待”条件:将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序提出(指定获取锁的顺序,顺序加锁)。
计算机网路

1.tcp和udp区别
1.TCP面向连接,UDP是无连接的,即发送数据之前不需要建立连接。
2.TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
3.TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流,UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4.每一条TCP连接只能是点到点的,UDP支持一对一,一对多,多对一和多对多的交互通信。
5.TCP首部开销20字节,UDP的首部开销小,只有8个字节。
6.TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
2.Http请求的完全过程
1.浏览器根据域名解析IP地址(DNS),并查DNS缓存
2.浏览器与WEB服务器建立一个TCP连接
3.浏览器给WEB服务器发送一个HTTP请求(GET/POST):一个HTTP请求报文由请求行(request line)、请求头部(headers)、空行(blank line)和请求数据(request body)4个部分组成。
4.服务端响应HTTP响应报文,报文由状态行(status line)、相应头部(headers)、空行(blank line)和响应数据(response body)4个部分组成。
5.浏览器解析渲染
3.tcp和udp的优点
TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。 TCP的缺点: 慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。
UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击…… UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。 基于上面的优缺点,那么: 什么时候应该使用TCP: 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输。什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP。
4.Get和Post区别
1.Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。
2.Get传送的数据量较小,这主要是因为受URL长度限制;Post传送的数据量较大,一般被默认为不受限制。
3.Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。
4.Get执行效率却比Post方法好。Get是form提交的默认方法。
5.GET产生一个TCP数据包;POST产生两个TCP数据包。(非必然,客户端可灵活决定)
5.三次握手
第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
6.为什么不能两次握手
TCP是一个双向通信协议,通信双方都有能力发送信息,并接收响应。如果只是两次握手, 至多只有连接发起方的起始***能被确认, 另一方选择的***则得不到确认
7.四次挥手
1.客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其***为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2.服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的***seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3.客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4.服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的***为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5.客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的***是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6.服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些
8.为什么连接的时候是三次握手,关闭的时候却是四次握手
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

其他

1.高并发系统的限流如何实现
答:在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。
1.缓存:缓存比较好理解,在大型高并发系统中,如果没有缓存数据库将分分钟被爆,系统也会瞬间瘫痪。使用缓存不单单能够提升系统访问速度、提高并发访问量,也是保护数据库、保护系统的有效方式。大型网站一般主要是“读”,缓存的使用很容易被想到。在大型“写”系统中,缓存也常常扮演者非常重要的角色。比如累积一些数据批量写入,内存里面的缓存队列(生产消费),以及HBase写数据的机制等等也都是通过缓存提升系统的吞吐量或者实现系统的保护措施。甚至消息中间件,你也可以认为是一种分布式的数据缓存。
2.降级:服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别,面临不同的异常等级执行不同的处理。根据服务方式:可以拒接服务,可以延迟服务,也有时候可以随机服务。根据服务范围:可以砍掉某个功能,也可以砍掉某些模块。总之服务降级需要根据不同的业务需求采用不同的降级策略。主要的目的就是服务虽然有损但是总比没有好。
3.限流:限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。
2.RPC简介
HTTP协议
http协议是基于tcp协议的,tcp协议是流式协议,包头部分可以通过多出的\r\n来分界,包体部分如何分界呢?这是协议本身要解决的问题。目前一般有两种方式,第一种方式就是在包头中有个content-Length字段,这个字段的值的大小标识了POST数据的长度,服务器收到一个数据包后,先从包头解析出这个字段的值,再根据这个值去读取相应长度的作为http协议的包体数据。
浏览器connect 80端口

RPC
进程间通信(IPC,Inter-Process Communication),指至少两个进程或线程间传送数据或信号的一些技术或方法。进程是计算机系统分配资源的最小单位。每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。这些进程可以运行在同一计算机上或网络连接的不同计算机上。 进程间通信技术包括消息传递、同步、共享内存和远程过程调用。 IPC是一种标准的Unix通信机制。
有两种类型的进程间通信(IPC)。
本地过程调用(LPC)LPC用在多任务操作系统中,使得同时运行的任务能互相会话。这些任务共享内存空间使任务同步和互相发送信息。
远程过程调用(RPC)RPC类似于LPC,只是在网上工作。RPC开始是出现在Sun微系统公司和HP公司的运行UNIX操作系统的计算机中。
为什么RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如比如不同的系统间的通讯,甚至不同的组织间的通讯。由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。
RPC的核心并不在于使用什么协议。RPC的目的是让你在本地调用远程的方法,而对你来说这个调用是透明的,你并不知道这个调用的方法是部署哪里。通过RPC能解耦服务,这才是使用RPC的真正目的。RPC的原理主要用到了动态代理模式,至于http协议,只是传输协议而已。简单的实现可以参考spring remoting,复杂的实现可以参考dubbo。
简单的说,
RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯) RPC 是一个请求响应模型。
客户端发起请求,服务器返回响应(类似于Http的工作方式) RPC 在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。

3.SpringCloud与Dubbo区别
SpringCloud和Dubbo都是当下流行的RPC框架,各自都集成了服务发现和治理组件。SpringCloud用Eureka,Dubbo用Zookeeper,这篇博客就将将这两个组件在各自系统中的作用机制的区别。
区别:
1.注册的服务的区别
Dubbo是基于java接口及Hession2序列化的来实现传输的,Provider对外暴露接口,Consumer根据接口的规则调用。也就是Provider向Zookeeper注册的是接口信息,Consumer从Zookeeper发现的是接口的信息,通过接口的name,group,version来匹配调用。Consumer只关注接口是否匹配,而对此接口属于什么应用不关心。当然接口的注册信息里会包含应用的ip,hostname等。
SpringCloud的服务发现是基于Http协议来实现的,Provider对外暴露的是应用信息,比如应用名称,ip地址等等,Consumer发现的是应用的信息,当调用的时候随机选择一个Provider的IP地址,应用名称,然后依据Http协议发送请求。Consumer关注的是应用名称,根据应用名称来决定调用的是哪个服务集群,然后对此名称对应的服务集群做负载均衡。Provider接受到请求后,根据内置的SpringMVC来匹配路由处理请求。
2 . Server集群服务信息同步的区别
Dubbo使用Zookeeper做服务发现和治理,Zookeeper是一个分布式协调框架,其有很多很实用的功能,服务发现仅仅是其中的一个。Zookeeper基于著名的CAP理论中的C(一致性),P(分区可用性)实现,它的ZAB(zookeeper atomic broadcast protocol)协议,保证了集群里状态的一致性。Client的每一个事务操作都由Leader广播给所有Follower,当超过半数的Follower都返回执行成功后,才执行事务的ack。对于因网络崩溃或者宕机等问题而执行失败的zookeeper节点,zookeeper会基于zab的崩溃恢复机制来处理,这里不再讲述。每一个操作都需要过半数的zookeeper节点执行成功才确认成功,那么当zookeeper集群过半数节点出现问题时,服务发现功能就不可用。
SpringCloud使用Eureka做服务发现和治理,它是一个专门用于服务发现和治理的框架,其基于CAP理论中的A(可用性),P(分区可用性)实现。EurekaServer节点间的服务信息同步是基于异步Http实现的。每隔Server节点在接收Client的服务请求时,立即处理请求,然后将此次请求的信息拷贝,封装成一个Task,存入Queue中。Server初始化时会启动一个线程定期的从TaskQueue中批量提取Task,然后执行。服务同步不保证一定成功,虽然有失败重试,但超过一定时限后就放弃同步。当然其有一个特性,当服务丢失后,同步的操作返回400,404后会立即将最新的服务信息同步过去,因此即使中途同步失败,不会对后续的同步有影响。
3 . 服务更新机制的区别
Dubbo使用Zookeeper做服务发现和治理,订阅Zookeeper下相应的znode。当节点发生变化,比如有新的元素增加,或者旧的元素移除,Zookeeper会通知所有订阅此节点的Client,将当前的全量数据同步给各Client,Dubbo里根据最新的数据来做相应处理,移除下线的,初始化新增的。每次更新都同步全量数据。
Eureka在启动时向Server进行一次全量拉取,获取所有的可用服务信息,之后默认情况下都是进行增量拉取。Server会将有变化的服务信息放在一个Queue里,Client每次同步时仅获取增量信息,根据信息里的操作类型,服务信息来对当前持有的服务做相应的处理,移除下线的,初始化新增的等。每次更新仅同步增量数据,也就是更新的数据。
4 . 服务更新反馈机制的区别
Dubbo订阅Zookeeper下相应的节点,当节点的状态发生改变时,Zookeeper会立即反馈订阅的Client,实时性很高。
Eureka Server在接收到Client的更新操作,或者移除服务信息时,仅仅会将更新消息存放入recentlyChangedQueue中,不会主动的反馈其他Client。其他Client只有在拉取服务增量信息时才会感知到某个服务的更新,延时最大为30S,也就是拉取周期。
5 . 服务信息回收机制的区别
Dubbo Provider初始化时会创建一个Zookeeper Client,专门用于与Zookeeper集群交互。维持与集群间的长连接,定时发送心跳,维护Zookeeper上自身节点的存在。节点类型是临时节点,也就是当心跳超时或者长连接断开时,会立即移除Provider对应的节点。
Dubbo Consumer初始化时也会创建一个Zookeeper Client,专门用于与Zookeeper集群交互。维持长连接,创建EvenetListener,监听Provider节点的变动情况。当Provider节点新增或者移除时,Zookeeper会广播这个事件,然后将此节点的当前值(剩下的所有接口信息)发送给那些注册了此节点监听器的Client。Consumer获取到对应Provider节点下的所有接口信息后,移除已下线的,创建新增的。
Zookeeper对服务信息的维护实时性和一致性比较高,但也可能因为网络问题或者集群问题导致服务不可用。
SpringCloud的服务信息回收仅基于心跳超时,与长连接无关。当心跳超时后,EurekaServer回收服务信息,然后将此动作同步给其他Server节点。当然可能一个服务信息会存在多个Server上,多次回收操作的同步具备幂等性。也就是说服务回收只需要通知一个Server节点就可以了,回收动作会通过Server节点传播开来。EurekaServer能够回收服务信息由个重要前提:上一分钟内正常发送心跳的服务的比列超过总数的85%,如果因为网络波动等原因造成大量服务的心跳超时,那么EurekaServer会触发自我保护机制,放弃回收那些心跳超时的服务信息。服务发现组件应该优先保证可用性,Consumer能够发现Provider,即使发现的是非可用的Provider,但因为Conusmer一般具备容错机制,不会对服务的正常调用有太多影响。从这点上看Eureka的服务发现机制要比Zookeeper稍微合理一点的。
6 . 节点性质的区别
Dubbo只有Consumer订阅Provider节点,也就是Consumer发现Provider节点信息
Eureka不区分Consumer或者Provider,两者都统称为Client,一个Client内可能同时含有Provider,Consumer,通过服务发现组件获取的是其他所有的Client节点信息,在调用时根据应用名称来筛选节点
7 . 使用方式的区别
Dubbo使用Zookeeper作为服务发现和治理的组件,所以需要搭建Zookeeper集群作为依赖。
SpringCloud使用Eureka作为服务发现和治理组件,在Spring应用中整合Eureka还是很简单的,引入依赖,加个注解,指定集群Server的serviceUrl,其他的都可以使用默认配置即可,启动应用,Eureka集群就搭建好了。同时配合SpringCloudConfg,能够统一管理Eureka的集群配置信息,可以动态的增加或减少EurekaServer的集群节点。Eurerka会每隔15分钟根据配置上的集群信息重新生成集群节点,覆盖之前的。这种机制比Zookeeper要更优秀一些,毕竟Eureka算是Spring生态里的一环,已经被整合的非常好了,能够以很多匪夷所思的方式来使用。

4.try catch finally执行顺序
 异常处理中,try、catch、finally的执行顺序,大家都知道是按顺序执行的。即,如果try中没有异常,则顺序为try→finally,如果try中有异常,则顺序为try→catch→finally。但是当try、catch、finally中加入return之后,就会有几种不同的情况出现,下面分别来说明一下。也可以跳到最后直接看总结。

一、try中带有return

1 private int testReturn1() {
2 int i = 1;
3 try {
4 i++;
5 System.out.println(“try:” + i);
6 return i;
7 } catch (Exception e) {
8 i++;
9 System.out.println(“catch:” + i);
10 } finally {
11 i++;
12 System.out.println(“finally:” + i);
13 }
14 return i;
15 }

输出:
try:2
finally:3
2
  因为当try中带有return时,会先执行return前的代码,然后暂时保存需要return的信息,再执行finally中的代码,最后再通过return返回之前保存的信息。所以,这里方法返回的值是try中计算后的2,而非finally中计算后的3。但有一点需要注意,再看另外一个例子:

1 private List testReturn2() {
2 List list = new ArrayList<>();
3 try {
4 list.add(1);
5 System.out.println(“try:” + list);
6 return list;
7 } catch (Exception e) {
8 list.add(2);
9 System.out.println(“catch:” + list);
10 } finally {
11 list.add(3);
12 System.out.println(“finally:” + list);
13 }
14 return list;
15 }

输出:
try:[1]
finally:[1, 3]
[1, 3]
  看完这个例子,可能会发现问题,刚提到return时会临时保存需要返回的信息,不受finally中的影响,为什么这里会有变化?其实问题出在参数类型上,上一个例子用的是基本类型,这里用的引用类型。list里存的不是变量本身,而是变量的地址,所以当finally通过地址改变了变量,还是会影响方法返回值的。

二、catch中带有return

1 private int testReturn3() {
2 int i = 1;
3 try {
4 i++;
5 System.out.println(“try:” + i);
6 int x = i / 0 ; s
7 } catch (Exception e) {
8 i++;
9 System.out.println(“catch:” + i);
10 return i;
11 } finally {
12 i++;
13 System.out.println(“finally:” + i);
14 }
15 return i;
16 }

输出:
try:2
catch:3
finally:4
3
  catch中return与try中一样,会先执行return前的代码,然后暂时保存需要return的信息,再执行finally中的代码,最后再通过return返回之前保存的信息。所以,这里方法返回的值是try、catch中累积计算后的3,而非finally中计算后的4。

三、finally中带有return

1 private int testReturn4() {
2 int i = 1;
3 try {
4 i++;
5 System.out.println(“try:” + i);
6 return i;
7 } catch (Exception e) {
8 i++;
9 System.out.println(“catch:” + i);
10 return i;
11 } finally {
12 i++;
13 System.out.println(“finally:” + i);
14 return i;
15 }
16 }

输出:
try:2
finally:3
3
  当finally中有return的时候,try中的return会失效,在执行完finally的return之后,就不会再执行try中的return。这种写法,编译是可以编译通过的,但是编译器会给予警告,所以不推荐在finally中写return,这会破坏程序的完整性,而且一旦finally里出现异常,会导致catch中的异常被覆盖。

总结:

1、finally中的代码总会被执行。
2、当try、catch中有return时,也会执行finally。return的时候,要注意返回值的类型,是否受到finally中代码的影响。
3、finally中有return时,会直接在finally中退出,导致try、catch中的return失效。