JAVA面试题Part2

二、数据存储和消息队列

2.1、数据库

2.1.1 MySQL 索引使用的注意事项

索引的类型:

• UNIQUE(唯一索引):不可以出现相同的值,可以有NULL值

• INDEX(普通索引):允许出现相同的索引内容

• PROMARY KEY(主键索引):不允许出现相同的值

• fulltext index(全文索引):可以针对值中的某个单词,但效率确实不敢恭维

• 组合索引:实质上是将多个字段建到一个索引里,列值的组合必须唯一

索引虽然好处很多,但过多的使用索引可能带来相反的问题,索引也是有缺点的:

•虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT,UPDATE和DELETE。因为更新表时,mysql不仅要保存数据,还要保存一下索引文件

•建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在要给大表上建了多种组合索引,索引文件会膨胀很宽

使用索引时,有一些技巧

1.索引不会包含有NULL的列

只要列中包含有NULL值,都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此符合索引就是无效的。

2.使用短索引

对串列进行索引,如果可以就应该指定一个前缀长度。例如,如果有一个char(255)的列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

3.索引列排序

mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作,尽量不要包含多个列的排序,如果需要最好给这些列建复合索引。

4.like语句操作

一般情况下不鼓励使用like操作,如果非使用不可,注意正确的使用方式。like ‘%aaa%’不会使用索引,而like ‘aaa%’可以使用索引。

5.不要在列上进行运算

6.不使用NOT IN 、<>、!=操作,但<,<=,=,>,>=,BETWEEN,IN是可以用到索引的

7.索引要建立在经常进行select操作的字段上。

这是因为,如果这些列很少用到,那么有无索引并不能明显改变查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。

8.索引要建立在值比较唯一的字段上。

9.对于那些定义为text、image和bit数据类型的列不应该增加索引。因为这些列的数据量要么相当大,要么取值很少。

10.在where和join中出现的列需要建立索引。

11.where的查询条件里有不等号(where column != …),mysql将无法使用索引。

12.如果where字句的查询条件里使用了函数(如:where DAY(column)=…),mysql将无法使用索引。

13.在join操作中(需要从多个数据表提取数据时),mysql只有在主键和外键的数据类型相同时才能使用索引,否则及时建立了索引也不会使用。

2.1.2 DDL、DML、DCL分别指什么

2.1.3 explain命令

EXPLAIN可以帮助开发人员分析SQL问题,explain显示了mysql如何使用索引来处理select语句以及连接表,可以帮助选择更好的索引和写出更优化的查询语句。

2.1.4 left join,right join,inner join

A INNER JOIN B ON……:内联操作,将符合ON条件的A表和B表结果均搜索出来,然后合并为一个结果集。

A LEFT JOIN B ON……:左联操作,左联顾名思义是,将符合ON条件的B表结果搜索出来, 然后左联到A表上,然后将合并后的A表输出。

A RIGHT JOIN B ON……:右联操作,右联顾名思义是,将符合ON条件的A表结果搜索出来, 然后右联到B表上,然后将合并后的B表输出。

2.1.5 数据库事物ACID(原子性、一致性、隔离性、持久性)

原子性

原子性是指事务是一个不可再分割的工作单元,事务中的操作要么都发生,要么都不发生。 可采用“A向B转账”这个例子来说明解释 在DBMS中,默认情况下一条SQL就是一个单独事务,事务是自动提交的。只有显式的使用start transaction开启一个事务,才能将一个代码块放在事务中执行。

一致性

一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。 如A给B转账,不论转账的事务操作是否成功,其两者的存款总额不变(这是业务逻辑的一致性,至于数据库关系约束的完整性就更好理解了)。 保障机制(也从两方面着手):数据库层面会在一个事务执行之前和之后,数据会符合你设置的约束(唯一约束,外键约束,check约束等)和触发器设置;此外,数据库的内部数据结构(如 B 树索引或双向链表)都必须是正确的。业务的一致性一般由开发人员进行保证,亦可转移至数据库层面。

隔离性

多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。 在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。 事务最复杂问题都是由事务隔离性引起的。完全的隔离性是不现实的,完全的隔离性要求数据库同一时间只执行一条事务,这样会严重影响性能。 关于隔离性中的事务隔离等级(事务之间影响),参见相应博文

持久性

这是最好理解的一个特性:持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。(完成的事务是系统永久的部分,对系统的影响是永久性的,该修改即使出现致命的系统故障也将一直保持) write ahead logging:SQL Server中使用了WAL(Write-Ahead Logging)技术来保证事务日志的ACID特性,在数据写入到数据库之前,先写入到日志,再将日志记录变更到存储器中。

2.1.6 事物的隔离级别(读未提交、读以提交、可重复读、可序列化读)

2.1.7 脏读、幻读、不可重复读

2.1.8 数据库的几大范式

2.1.9 数据库常见的命令

2.1.10 说说分库与分表设计

对于访问极为频繁且数据量巨大的单表来说,我们首先要做的就是减少单表的记录条数,以便减少数据查询所需要的时间,提高数据库的吞吐,这就是所谓的分表!在分表之前,首先需要选择适当的分表策略,使得数据能够较为均衡地分不到多张表中,并且不影响正常的查询!

场景:分表能够解决单表数据量过大带来的查询效率下降的问题,但是,却无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库master,服务器无法承载写操作压力时,不管如何扩展slave服务器,此时都没有意义了。因此,我们必须换一种思路,对数据库进行拆分,从而提高数据库写入能力,这就是所谓的分库!

2.1.11 分库与分表带来的分布式困境与应对之策(如何解决分布式下的分库分表,全局表?)

数据迁移与扩容问题
前面介绍到水平分表策略归纳总结为随机分表和连续分表两种情况。连续分表有可能存在数据热点的问题,有些表可能会被频繁地查询从而造成较大压力,热数据的表就成为了整个库的瓶颈,而有些表可能存的是历史数据,很少需要被查询到。连续分表的另外一个好处在于比较容易,不需要考虑迁移旧的数据,只需要添加分表就可以自动扩容。随机分表的数据相对比较均匀,不容易出现热点和并发访问的瓶颈。但是,分表扩展需要迁移旧的数据。针对于水平分表的设计至关重要,需要评估中短期内业务的增长速度,对当前的数据量进行容量规划,综合成本因素,推算出大概需要多少分片。对于数据迁移的问题,一般做法是通过程序先读出数据,然后按照指定的分表策略再将数据写入到各个分表中。

表关联问题 在单库单表的情况下,联合查询是非常容易的。但是,随着分库与分表的演变,联合查询就遇到跨库关联和跨表关系问题。在设计之初就应该尽量避免联合查询,可以通过程序中进行拼装,或者通过反范式化设计进行规避。

分页与排序问题 一般情况下,列表分页时需要按照指定字段进行排序。在单库单表的情况下,分页和排序也是非常容易的。但是,随着分库与分表的演变,也会遇到跨库排序和跨表排序问题。为了最终结果的准确性,需要在不同的分表中将数据进行排序并返回,并将不同分表返回的结果集进行汇总和再次排序,最后再返回给用户。

分布式事务问题 随着分库与分表的演变,一定会遇到分布式事务问题,那么如何保证数据的一致性就成为一个必须面对的问题。目前,分布式事务并没有很好的解决方案,难以满足数据强一致性,一般情况下,使存储数据尽可能达到用户一致,保证系统经过一段较短的时间的自我恢复和修正,数据最终达到一致。

分布式全局唯一ID 在单库单表的情况下,直接使用数据库自增特性来生成主键ID,这样确实比较简单。在分库分表的环境中,数据分布在不同的分表上,不能再借助数据库自增长特性。需要使用全局唯一 ID,例如 UUID、GUID等。关于如何选择合适的全局唯一 ID,我会在后面的章节中进行介绍。

当前主要有两类解决方案

1 基于应用程序层面的DDAL(分布式数据库访问层)

比较典型的就是淘宝半开源的TDDL,当当网开源的Sharding-JDBC等。分布式数据访问层无需硬件投入,技术能力较强的大公司通常会选择自研或参照开源框架进行二次开发和定制。对应用程序的侵入性一般较大,会增加技术成本和复杂度。通常仅支持特定编程语言平台(Java平台的居多),或者仅支持特定的数据库和特定数据访问框架技术(一般支持MySQL数据库,JDBC、MyBatis、Hibernate等框架技术)。

2 数据库中间件,比较典型的像mycat(在阿里开源的cobar基础上做了很多优化和改进,属于后起之秀,也支持很多新特性),基于Go语言实现kingSharding,比较老牌的Atlas(由360开源)等。这些中间件在互联网企业中大量被使用。另外,MySQL 5.x企业版中官方提供的Fabric组件也号称支持分片技术,不过国内使用的企业较少。

2.1.12 说说 SQL 优化之道

参考链接:http://www.sohu.com/a/158748062_505827

2.1.13 MySQL遇到的死锁问题、如何排查与解决

死锁一般是事务相互等待对方资源,最后形成环路造成的。下面简单讲下造成相互等待最后形成环路的例子。死锁成因如下:

  • 不同表相同记录行锁冲突 :事务A和事务B操作两张表,但出现循环等待锁情况.

  • 相同表记录行锁冲突 :这种情况比较常见,之前遇到两个job在执行数据批量更新时,jobA处理的的id列表为[1,2,3,4],而job处理的id列表为[8,9,10,4,2],这样就造成了死锁.

  • 不同索引锁冲突 : 这种情况比较隐晦,事务A在执行时,除了在二级索引加锁外,还会在聚簇索引上加锁,在聚簇索引上加锁的顺序是[1,4,2,3,5],而事务B执行时,只在聚簇索引上加锁,加锁顺序是[1,2,3,4,5],这样就造成了死锁的可能性。
  • gap锁冲突 : innodb在RR级别下,如下的情况也会产生死锁,比较隐晦。不清楚的同学可以自行根据上节的gap锁原理分析下。

如何尽可能避免死锁

1)以固定的顺序访问表和行。比如对第2节两个job批量更新的情形,简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;又比如对于3.1节的情形,将两个事务的sql顺序调整为一致,也能避免死锁。

2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。

3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。

4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。

5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。

如何定位死锁成因

1)通过应用业务日志定位到问题代码,找到相应的事务对应的sql;因为死锁被检测到后会回滚,这些信息都会以异常反应在应用的业务日志中,通过这些日志我们可以定位到相应的代码,并把事务的sql给梳理出来。 2)确定数据库隔离级别。

执行select @@global.tx_isolation,可以确定数据库的隔离级别,我们数据库的隔离级别是RC,这样可以很大概率排除gap锁造成死锁的嫌疑;

3)找DBA执行下show InnoDB STATUS看看最近死锁的日志。

这个步骤非常关键。通过DBA的帮忙,我们可以有更为详细的死锁信息。通过此详细日志一看就能发现

参考连接:https://www.cnblogs.com/LBSer/p/5183300.html

2.1.14 存储引擎的 InnoDB与MyISAM区别,优缺点,使用场景

Innodb引擎

Innodb引擎提供了对数据库ACID事务的支持,并且实现了SQL标准的四种隔离级别,关于数据库事务与其隔离级别的内容请见数据库事务与其隔离级别这篇文章。该引擎还提供了行级锁和外键约束,它的设计目标是处理大容量数据库系统,它本身其实就是基于MySQL后台的完整数据库系统,MySQL运行时Innodb会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎不支持FULLTEXT类型的索引,而且它没有保存表的行数,当SELECT COUNT(*) FROM TABLE时需要扫描全表。当需要使用数据库事务时,该引擎当然是首选。由于锁的粒度更小,写操作不会锁定全表,所以在并发较高时,使用Innodb引擎会提升效率。但是使用行级锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表。

MyIASM引擎

MyIASM是MySQL默认的引擎,但是它没有提供对数据库事务的支持,也不支持行级锁和外键,因此当INSERT(插入)或UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。不过和Innodb不同,MyIASM中存储了表的行数,于是SELECT COUNT(*) FROM TABLE时只需要直接读取已经保存好的值而不需要进行全表扫描。如果表的读操作远远多于写操作且不需要数据库事务的支持,那么MyIASM也是很好的选择。

两种引擎的选择

大尺寸的数据集趋向于选择InnoDB引擎,因为它支持事务处理和故障恢复。数据库的大小决定了故障恢复的时间长短,InnoDB可以利用事务日志进行数据恢复,这会比较快。主键查询在InnoDB引擎下也会相当快,不过需要注意的是如果主键太长也会导致性能问题,关于这个问题我会在下文中讲到。大批的INSERT语句(在每个INSERT语句中写入多行,批量插入)在MyISAM下会快一些,但是UPDATE语句在InnoDB下则会更快一些,尤其是在并发量大的时候。

参考链接:https://www.cnblogs.com/0201zcr/p/5296843.html

2.1.15 索引类别(B+树索引、全文索引、哈希索引)、索引的原理

2.1.16 什么是自适应哈希索引(AHI)

2.1.17 为什么要用 B+tree作为MySQL索引的数据结构

在 MySQL 中,主要有四种类型的索引,分别为: B-Tree 索引, Hash 索引, Fulltext 索引和 R-Tree 索引。我们主要分析B-Tree 索引。

B-Tree 索引是 MySQL 数据库中使用最为频繁的索引类型,除了 Archive 存储引擎之外的其他所有的存储引擎都支持 B-Tree 索引。Archive 引擎直到 MySQL 5.1 才支持索引,而且只支持索引单个 AUTO_INCREMENT 列。

不仅仅在 MySQL 中是如此,实际上在其他的很多数据库管理系统中B-Tree 索引也同样是作为最主要的索引类型,这主要是因为 B-Tree 索引的存储结构在数据库的数据检索中有非常优异的表现。

一般来说, MySQL 中的 B-Tree 索引的物理文件大多都是以 Balance Tree 的结构来存储的,也就是所有实际需要的数据都存放于 Tree 的 Leaf Node(叶子节点) ,而且到任何一个 Leaf Node 的最短路径的长度都是完全相同的,所以我们大家都称之为 B-Tree 索引。当然,可能各种数据库(或 MySQL 的各种存储引擎)在存放自己的 B-Tree 索引的时候会对存储结构稍作改造。如 Innodb 存储引擎的 B-Tree 索引实际使用的存储结构实际上是 B+Tree,也就是在 B-Tree 数据结构的基础上做了很小的改造,在每一个Leaf Node 上面出了存放索引键的相关信息之外,还存储了指向与该 Leaf Node 相邻的后一个 LeafNode 的指针信息(增加了顺序访问指针),这主要是为了加快检索多个相邻 Leaf Node 的效率考虑。

参考链接:https://www.cnblogs.com/xyxxs/p/4440187.html

2.1.18 聚集索引与非聚集索引的区别

聚集索引(Clustered Index)特点

  • 聚集索引的叶节点就是实际的数据页
  • 聚集索引中的排序顺序仅仅表示数据页链在逻辑上是有序的。而不是按照顺序物理的存储在磁盘上
  • 行的物理位置和行在索引中的位置是相同的
  • 每个表只能有一个聚集索引
  • 聚集索引的平均大小大约为表大小的5%左右

非聚集索引 (Unclustered Index) 特点

  • 非聚集索引的页,不是数据,而是指向数据页的页。
  • 若未指定索引类型,则默认为非聚集索引。
  • 叶节点页的次序和表的物理存储次序不同
  • 每个表最多可以有249个非聚集索引
  • 在非聚集索引创建之前创建聚集索引(否则会引发索引重建)

使用索引的代价

  • 索引需要占用数据表以外的物理存储空间
  • 创建索引和维护索引要花费一定的时间
  • 当对表进行更新操作时,索引需要被重建,这样降低了数据的维护速度。

参考链接:https://blog.csdn.net/single_wolf_wolf/article/details/52915862

2.1.19 遇到过索引失效的情况没,什么时候可能会出现,如何解决

2.1.20 limit 20000 加载很慢怎么解决

2.1.21 如何选择合适的分布式主键方案

2.1.22 选择合适的数据存储方案

2.1.23 常见的几种分布式ID的设计方案

2.1.24 常见的数据库优化方案,在你的项目中数据库如何进行优化的

2.2、Redis

2.2.1 Redis 有哪些数据类型,可参考《Redis常见的5种不同的数据类型详解》

1.String(字符串)

string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M

2.Hash(哈希)

Redis hash 是一个键值对集合。 Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 类似Java里面的Map<String,Object>

3.List(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。 它的底层实际是个链表

4.Set(集合)

Redis的Set是string类型的无序集合。它是通过HashTable实现实现的,

5.zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。 不同的是每个元素都会关联一个double类型的分数。 redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。

2.2.2 Redis 内部结构

各功能模块说明如下:

  • File Event: 处理文件事件(在多个客户端中实现多路复用,接受它们发来的命令请求(读事件),并将命令的执行结果返回给客户端(写事件))
  • Time Event: 时间事件(更新统计信息,清理过期数据,附属节点同步,定期持久化等)
  • AOF: 命令日志的数据持久化
  • RDB:实际的数据持久化
  • Lua Environment : Lua 脚本的运行环境. 为了让 Lua 环境符合 Redis 脚本功能的需求, Redis 对 Lua 环境进行了一系列的修改, 包括添加函数库、更换随机函数、保护全局变量, 等等
  • Command table(命令表):在执行命令时,根据字符来查找相应命令的实现函数。
  • Share Objects(对象共享):主要存储常见的值:a.各种命令常见的返回值,例如返回值OK、ERROR、WRONGTYPE等字符;b. 小于 redis.h/REDIS_SHARED_INTEGERS (默认1000)的所有整数。通过预分配的一些常见的值对象,并在多个数据结构之间共享对象,程序避免了重复分配的麻烦。也就是说,这些常见的值在内存中只有一份。
  • Databases:Redis数据库是真正存储数据的地方。当然,数据库本身也是存储在内存中的。

2.2.3 Redis 使用场景

一:缓存——热数据

热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存,毕竟强大到冒泡的QPS和极强的稳定性不是所有类似工具都有的,而且相比于memcached还提供了丰富的数据类型可以使用,另外,内存中的数据也提供了AOF和RDB等持久化机制可以选择,要冷、热的还是忽冷忽热的都可选。

二:计数器

诸如统计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且100%毫秒级性能!爽。命令:INCRBY ;当然爽完了,别忘记持久化,毕竟是redis只是存了内存!

三:队列

相当于消息系统,ActiveMQ,RocketMQ等工具类似,但是个人觉得简单用一下还行,如果对于数据一致性要求高的话还是用RocketMQ等专业系统。

由于redis把数据添加到队列是返回添加元素在队列的第几位,所以可以做判断用户是第几个访问这种业务

队列不仅可以把并发请求变成串行,并且还可以做队列或者栈使用

四:位操作(大数据处理)

用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。

五:分布式锁与单线程机制

验证前端的重复请求(可以自由扩展类似情况),可以通过redis进行过滤:每次请求将request Ip、参数、接口等hash作为key存储redis(幂等性请求),设置多长时间有效期,然后下次请求过来的时候先在redis中检索有没有这个key,进而验证是不是一定时间内过来的重复提交

秒杀系统,基于redis是单线程特征,防止出现数据库“爆破”

全局增量ID生成,类似“秒杀”

六:最新列表

例如新闻列表页面最新的新闻列表,如果总数量很大的情况下,尽量不要使用select a from A limit 10这种low货,尝试redis的 LPUSH命令构建List,一个个顺序都塞进去就可以啦。不过万一内存清掉了咋办?也简单,查询不到存储key的话,用mysql查询并且初始化一个List到redis中就好了。

七:排行榜

谁得分高谁排名往上。命令:ZADD(有续集,sorted set)

2.2.4 Redis 持久化机制,可参考《使用快照和AOF将Redis数据持久化到硬盘中》

即参考链接:https://blog.csdn.net/xlgen157387/article/details/61925524

2.2.5 Redis 集群方案与实现

参考链接:https://blog.csdn.net/u011277123/article/details/55002024

2.2.6 Redis 为什么是单线程的?

  • 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
  • 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
  • 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  • 使用多路I/O复用模型,非阻塞IO;
  • 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。

参考链接:https://blog.csdn.net/chenyao1994/article/details/79491337

2.2.7 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级

一、缓存雪崩

缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

二、缓存穿透

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

三、缓存预热

缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

四、缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

(1)定时去清理过期的缓存;

(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

五、缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

参考链接:https://www.cnblogs.com/leeSmall/p/8594542.html

2.2.8 使用缓存的合理性问题

  • 热点数据,缓存才有价值
  • 频繁修改的数据,看情况考虑使用缓存
  • 数据不一致性
  • 缓存更新机制
  • 缓存可用性
  • 缓存服务降级
  • 缓存预热
  • 缓存穿透

参考链接:https://blog.csdn.net/diyhzp/article/details/54892358

2.2.9 Redis常见的回收策略

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据

2.3、消息队列

2.3.1 消息队列的使用场景

异步处理,应用解耦,流量削锋和消息通讯四个场景

参考链接:https://blog.csdn.net/seven______7/article/details/70225830

2.3.2 消息的重发补偿解决思路

1.如果消息接收者在处理完一条消息的处理过程后没有对MOM进行应答,则该消息将由MOM重发。需要注意的是,如果采用非事务持久化消息加Session.CLIENT_ACKNOWLEDGE应答模式,当消费者在处理完消息后没有主动调用Message#acknowledge()方法时,MOM不会主动重发,如果这时候MOM宕机了,当重启MOM后,将消费者机器也重启后MOM才会重发消息,但此时的消息不会有重发标记,因为MOM都不记得自己有宕机过,也不知道这些消息被发送过。

2.如果我们队某个队列设置了预读参数(consumer.prefetchSize),如果消息接收者在处理第一条消息时(没向MOM发送消息接收确认)就宕机了,则预读数量的所有消息都将被重发。

3.如果Session是事务的,则只要消息接收者有一条消息没有确认,或发送消息期间MOM或客户端某一方突然宕机了,则该事务范围中的所有消息MOM都将重发。

参考链接:http://manzhizhen.iteye.com/blog/2092136

2.3.3 消息的幂等性解决思路

上半场:MQ-client生成inner-msg-id,保证上半场幂等。这个ID全局唯一,业务无关,由MQ保证。

下半场:业务发送方带入biz-id,业务接收方去重保证幂等。这个ID对单业务唯一,业务相关,对MQ透明。

参考链接:https://www.jianshu.com/p/8b77d4583bab?utm_campaign

https://blog.csdn.net/yzhou86/article/details/79156458

2.3.4 消息的堆积解决思路

处理消息堆积的方法就是把它存下来。只是这个存储可以做成很多方式。比如存储在内存里,存储在分布式KV里,存储在磁盘里,存储在数据库里等等。但归结起来,主要有持久化和非持久化两种。 持久化的形式能更大程度地保证消息的可靠性(如断电等不可抗外力),并且理论上能承载更大限度的消息堆积(外存的空间远大于内存)。

但并不是每种消息都需要持久化存储。很多消息对于投递性能的要求大于可靠性的要求,且数量极大(如日志)。这时候,消息不落地直接暂存内存,尝试几次failover,最终投递出去也未尝不可。 市面上的消息队列普遍两种形式都支持。当然具体的场景还要具体结合公司的业务来看。

参考链接:https://www.cnblogs.com/doit8791/p/8570759.html

2.3.5 自己如何实现消息队列

参考链接:消息队列设计精要

2.3.6 如何保证消息的有序性

参考链接:RocketMQ(三)如何解决消息的顺序&重复两大硬伤?

三、开源框架和容器

3.1 SSM/Servlet

3.1.1 Servlet的生命周期

  • Servlet 通过调用 init () 方法进行初始化。
  • Servlet 调用 service() 方法来处理客户端的请求。
  • Servlet 通过调用 destroy() 方法终止(结束)。
  • 最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

3.1.2 转发与重定向的区别

转发是服务器行为,重定向是客户端行为

  1. 转发在服务器端完成的;重定向是在客户端完成的
  1. 转发的速度快;重定向速度慢
  1. 转发的是同一次请求;重定向是两次不同请求
  1. 转发不会执行转发后的代码;重定向会执行重定向之后的代码
  1. 转发地址栏没有变化;重定向地址栏有变化
  1. 转发必须是在同一台服务器下完成;重定向可以在不同的服务器下完成

3.1.3 BeanFactory 和 ApplicationContext 有什么区别

BeanFacotry是spring中比较原始的Factory。如XMLBeanFactory就是一种典型的BeanFactory。原始的BeanFactory无法支持spring的许多插件,如AOP功能、Web应用等。

ApplicationContext接口,它由BeanFactory接口派生而来,因而提供BeanFactory所有的功能。ApplicationContext以一种更向面向框架的方式工作以及对上下文进行分层和实现继承,ApplicationContext包还提供了以下的功能: • MessageSource, 提供国际化的消息访问
• 资源访问,如URL和文件
• 事件传播
• 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层

BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样,我们就不能发现一些存在的Spring的配置问题。而ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误。

BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册

3.1.4 Spring Bean 的生命周期

说明一个Bean的生命周期活动:

  • Bean的建立, 由BeanFactory读取Bean定义文件,并生成各个实例
  • Setter注入,执行Bean的属性依赖注入
  • BeanNameAware的setBeanName(), 如果实现该接口,则执行其setBeanName方法
  • BeanFactoryAware的setBeanFactory(),如果实现该接口,则执行其setBeanFactory方法
  • BeanPostProcessor的processBeforeInitialization(),如果有关联的processor,则在Bean初始化之前都会执行这个实例的processBeforeInitialization()方法
  • InitializingBean的afterPropertiesSet(),如果实现了该接口,则执行其afterPropertiesSet()方法
  • Bean定义文件中定义init-method
  • BeanPostProcessors的processAfterInitialization(),如果有关联的processor,则在Bean初始化之前都会执行这个实例的processAfterInitialization()方法
  • DisposableBean的destroy(),在容器关闭时,如果Bean类实现了该接口,则执行它的destroy()方法
  • Bean定义文件中定义destroy-method,在容器关闭时,可以在Bean定义文件中使用“destory-method”定义的方法

3.1.5 Spring IOC 如何实现

IoC 是一个很大的概念,可以用不同的方式来实现。主要的实现形式有两种 :

依赖查找:容器提供回调接口和上下文环境给组件。 EJB 和 Apache Avalon 都是使用这种方式。

依赖注入:组件不做定位查询,只是提供普通的 Java 方法让容器去决定依赖关系。容器全权负责组件的装配,它会把符合依赖关系的对象通过 JavaBean 属性或者构造子传递给需要的对象。通过 JavaBean 属性注射依赖关系的做法称为设值方法注入( Setter Injection );将依赖关系作为构造子参数传入的做法称为构造子注入( Constructor Injection )。

3.1.6 Spring中Bean的作用域,默认的是哪一个

  • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
  • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
  • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
  • session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
  • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效

其中比较常用的是singleton和prototype两种作用域。Spring默认使用singleton作用域。

3.1.7 说说 Spring AOP、Spring AOP 实现原理

关于动态代理和CGLIB这两种方式的简要总结如下:

JDK动态代理(Dynamic Proxy) :基于标准JDK的动态代理功能;只针对实现了接口的业务对象;

CGLIB : 通过动态地对目标对象进行子类化来实现AOP代理,上面截图中的SampleBean1767dd4b即为动态创建的一个子类 需要指定@EnableAspectJAutoProxy(proxyTargetClass = true)来强制使用;当业务对象没有实现任何接口的时候默认会选择CGLIB

3.1.8 动态代理(CGLib 与 JDK)、优缺点、性能对比、如何选择

1)JDK动态代理:

1、通过实现InvocationHandlet接口创建自己的调用处理器

2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理

3、通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型

4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入

JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,

Spring通过java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

2)CGLib动态代理

CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。

两者对比:

JDK动态代理是面向接口,在创建代理实现类时比CGLib要快,创建代理速度快。

CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败),在创建代理这一块没有JDK动态代理快,但是运行速度比JDK动态代理要快。

使用注意:

如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制)

如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

3.1.9 Spring 事务实现方式、事务的传播机制、默认的事务类别

参考链接:Spring事务配置的五种方式和spring里面事务的传播属性和事务隔离级别

3.1.10 Spring 事务底层原理

参考链接:深入理解 Spring 事务原理

3.1.11 Spring事务失效(事务嵌套),JDK动态代理给Spring事务埋下的坑

可参考《JDK动态代理给Spring事务埋下的坑!》

3.1.12 如何自定义注解实现功能

3.1.13 Spring MVC 运行流程

  1. 用户发送请求到DispatchServlet
  1. DispatchServlet根据请求路径查询具体的Handler
  1. HandlerMapping返回一个HandlerExcutionChain给DispatchServlet,HandlerExcutionChain:Handler和Interceptor集合
  1. DispatchServlet调用HandlerAdapter适配器
  1. HandlerAdapter调用具体的Handler处理业务
  1. Handler处理结束返回一个具体的ModelAndView给适配器,ModelAndView:model–>数据模型,view–>视图名称
  1. 适配器将ModelAndView给DispatchServlet
  1. DispatchServlet把视图名称给ViewResolver视图解析器
  1. ViewResolver返回一个具体的视图给DispatchServlet
  1. 渲染视图
  1. 展示给用户

3.1.14 Spring MVC 启动流程

tomcat web容器启动时会去读取web.xml这样的部署描述文件,相关组件启动顺序为: 解析 => 解析 => 解析 => 解析,具体初始化过程如下:

1、解析里的键值对。

2、创建一个application内置对象即ServletContext,servlet上下文,用于全局共享。

3、将的键值对放入ServletContext即application中,Web应用内全局共享。

4、读取标签创建监听器,一般会使用ContextLoaderListener类,如果使用了ContextLoaderListener类,Spring就会创建一个WebApplicationContext类的对象,WebApplicationContext类就是IoC容器,ContextLoaderListener类创建的IoC容器是根IoC容器为全局性的,并将其放置在appication中,作为应用内全局共享,键名为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,可以通过以下两种方法获取

WebApplicationContext applicationContext = (WebApplicationContext) application.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

WebApplicationContext applicationContext1 = WebApplicationContextUtils.getWebApplicationContext(application); 这个全局的根IoC容器只能获取到在该容器中创建的Bean不能访问到其他容器创建的Bean,也就是读取web.xml配置的contextConfigLocation参数的xml文件来创建对应的Bean。

5、listener创建完成后如果有则会去创建filter。 6、初始化创建,一般使用DispatchServlet类。 7、DispatchServlet的父类FrameworkServlet会重写其父类的initServletBean方法,并调用initWebApplicationContext()以及onRefresh()方法。 8、initWebApplicationContext()方法会创建一个当前servlet的一个IoC子容器,如果存在上述的全局WebApplicationContext则将其设置为父容器,如果不存在上述全局的则父容器为null。 9、读取标签的配置的xml文件并加载相关Bean。 10、onRefresh()方法创建Web应用相关组件。

参考链接:SpringMVC 启动流程及相关源码分析

3.1.15 Spring 的单例实现原理

Spring对bean实例的创建是采用单例注册表的方式进行实现的,而这个注册表的缓存是HashMap对象,如果配置文件中的配置信息不要求使用单例,Spring会采用新建实例的方式返回对象实例

参考链接:Spring的单例模式底层实现

3.1.16 Spring 框架中用到了哪些设计模式

3.1.17 Spring 其他产品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等)

3.1.18 有没有用到Spring Boot,Spring Boot的认识、原理

参考链接:《02.Spring Boot连载:Spring Boot实战.Spring Boot核心原理剖析》

3.1.19 MyBatis的原理

mybatis底层还是采用原生jdbc来对数据库进行操作的,只是通过 SqlSessionFactory,SqlSession Executor,StatementHandler,ParameterHandler,ResultHandler和TypeHandler等几个处理器封装了这些过程

执行器:Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 参数处理器: ParameterHandler (getParameterObject, setParameters) 结构处理器 ResultSetHandler (handleResultSets, handleOutputParameters) sql查询处理器:StatementHandler (prepare, parameterize, batch, update, query)

Mybatis工作过程

  • 创建SqlSessionFacotry的过程
  • 创建SqlSession的过程
  • 创建Mapper的过程
  • 执行CRUD过程

3.2、Netty

3.2.1 为什么选择 Netty

3.2.2 说说业务中,Netty 的使用场景

3.2.3 原生的 NIO 在 JDK 1.7 版本存在 epoll bug

3.2.4 什么是TCP 粘包/拆包

3.2.5 TCP粘包/拆包的解决办法

3.2.6 Netty 线程模型

3.2.7 说说 Netty 的零拷贝

3.2.8 Netty 内部执行流程

3.2.9 Netty 重连实现

3.3、Tomcat

3.3.1 Tomcat的基础架构(Server、Service、Connector、Container)

(1)Tomcat中只有一个Server,一个Server可以有多个Service,一个Service可以有多个Connector和一个Container;

(2) Server掌管着整个Tomcat的生死大权;

(3)Service 是对外提供服务的;

(4)Connector用于接受请求并将请求封装成Request和Response来具体处理;

(5)Container用于封装和管理Servlet,以及具体处理request请求;

3.3.2 Tomcat如何加载Servlet的

3.3.3 Pipeline-Valve机制

可参考:《四张图带你了解Tomcat系统架构!》

(转载本站文章请注明作者和出处 个人博客[www.ilovevismin.com])

猜你喜欢

转载自blog.csdn.net/cyyingsun/article/details/80116062