Java面试——2021校招腾讯客户端开发三面

腾讯面试问题:

Q:   如何保证消息不会丢失?

第一种:生产者弄丢了数据。生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的.

第二种:RabbitMQ 弄丢了数据。MQ还没有持久化自己挂了

第三种:消费端弄丢了数据。刚消费到,还没处理,结果进程挂了,比如重启了。

Java面试——2021校招腾讯客户端开发三面_第1张图片

消息的丢失在分在的MQ中的三个部分:1生产者到MQ中生产者消息有丢失,这个时候MQ采用的消息确认的机制保证消息能够发送到MQ中confirm机制,2 MQ中本身发生消息的丢失 这个时候采用的是消息的持久化操作。3消息到消费者的时候消息丢失,采用的是消息的手动确认的机制。

Java面试——2021校招腾讯客户端开发三面_第2张图片

Q:如何保证事务的一致性?能具体说说原理吗?

银行转账的例子,保证数据的最终一致性

Q:在引入XX组件之前,你是怎么解决这个问题的?

一直同步的操作的。但是当人数注册太多时候,可能出现注册失败的情况。

Q:为什么选用这个组件呢?用XX不行吗?

这个MQ的组件能够实现异步的操作和解耦。不用这个也行,但是容易出现问题。

Q:表模型?

表模型有一对一  一对多 多对多的关系。

Q:sql是怎么优化的?

4.1数据的字段和表的优化

1尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED

2VARCHAR的长度只分配真正需要的空间

3使用枚举或整数代替字符串类型

4尽量使用TIMESTAMP而非DATETIME,

5单表不要有太多字段,建议在20以内

6避免使用NULL字段,很难查询优化且占用额外索引空间

7用整型来存IP

8将大字段、访问频率低的字段拆分到单独的表中存储,分离冷热数据。

9禁止在数据库中存储明文密码。

10表必须有主键,推荐使用UNSIGNED自增列作为主键。

11禁止冗余索引。

12禁止重复索引

13不在低基数列上建立索引,例如“性别”。

14合理使用覆盖索引减少IO,避免排序。

15用IN代替OR。SQL语句中IN包含的值不应过多,应少于1000个。

16表字符集使用UTF8,必要时可申请使用UTF8MB4字符集。

17建议使用合理的分页方式以提高分页效率。

18 SELECT只获取必要的字段,禁⽌止使用SELECT *

19 采用合适的分库分表策略。例如千库十表、十库百表等。

20 减少与数据库交互次数,尽量采用批量SQL语句。

21 采用合适的分库分表策略。例如千库十表、十库百表等。

4.2数据的索引优化

索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描。

应避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引进行全表扫描。

值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段

字符字段只建前缀索引

字符字段最好不要做主键

不用外键,由程序保证约束

尽量不用UNIQUE,由程序保证约束

使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引

引擎目前广泛使用的是MyISAM和InnoDB两种引擎:

MyISAM:MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:

1不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁

2不支持事务

3不支持外键

4不支持崩溃后的安全恢复

5在表有读取查询的同时,支持往表中插入新纪录

6支持BLOB和TEXT的前500个字符索引,支持全文索引

7支持延迟更新索引,极大提升写入性能

8对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用

InnoDB:InnoDB在MySQL 5.5后成为默认索引,它的特点是:

1支持行锁,采用MVCC来支持高并发

2支持事务

3支持外键

4支持崩溃后的安全恢复

5不支持全文索引 总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表

4.3数据库的查询优化

1可通过开启慢查询日志来找出较慢的SQL

2不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边

3sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库

4不用`SELECT *``

5OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内

不用函数和触发器,在应用程序实现

应尽量避免在 where子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

应尽量避免在where子句中使用or来连接条件,将导致引擎放弃使用索引而进行全表扫描

in 和 not in 也要慎用,否则会导致全表扫描

尽量避免大事务操作,提高系统并发能力。

不要写一些没有意义的查询,这类代码不会返回任何结果集,但是会消耗系统资源的。

4.4数据的读写分离

也是目前常用的优化,从库读主库写,一般不要采用双主或多主引入很多复杂性,尽量采用文中的其他方案来提高性能。同时目前很多拆分的解决方案同时也兼顾考虑了读写分离。

4.5数据库的分库分表

表分区:MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,应用是透明的无需修改代码

水平切用于分表:分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库,做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与“微服务治理”的做法相似,每个微服务使用单独的一个数据库。

垂直分表分库操作:是基于数据库中的列进行,某个表字段较多,可以新建一张扩展表,将不经常用或者字段长度较大的字段拆出到扩展表中。在字段很多的情况下,通过大表拆小表,更便于开发与维护,也能避免跨页问题。

Java面试——2021校招腾讯客户端开发三面_第3张图片

Java面试——2021校招腾讯客户端开发三面_第4张图片

分库分表的策略:

1最简单的都是可以通过取模的方式进行路由

2 Reange范围:按照时间id 年份的来划分

分片原则

能不分就不分,参考单表优化

分片数量尽量少,分片尽量均匀分布在多个数据结点上,因为一个查询SQL跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,在必要的时候进行扩容,增加分片数量

分片规则需要慎重选择做好提前规划,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,最近的分片策略为范围分片,枚举分片,一致性Hash分片,这几种分片都有利于扩容

尽量不要在一个事务中的SQL跨越多个分片,分布式事务一直是个不好处理的问题

查询条件尽量优化,尽量避免Select * 的方式,大量数据结果集下,会消耗大量带宽和CPU资源,查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引。

通过数据冗余和表分区赖降低跨库Join的可能。

这里特别强调一下分片规则的选择问题,如果某个表的数据有明显的时间特征,比如订单、交易记录等,则他们通常比较合适用时间范围分片,因为具有时效性的数据,我们往往关注其近期的数据,查询条件中往往带有时间字段进行过滤,比较好的方案是,当前活跃的数据,采用跨度比较短的时间段进行分片,而历史性的数据,则采用比较长的跨度存储。

总体上来说,分片的选择是取决于最频繁的查询SQL的条件,因为不带任何Where语句的查询SQL,会遍历所有的分片,性能相对最差,因此这种SQL越多,对系统的影响越大,所以我们要尽量避免这种SQL的产生。

Java面试——2021校招腾讯客户端开发三面_第5张图片

Java面试——2021校招腾讯客户端开发三面_第6张图片

Java面试——2021校招腾讯客户端开发三面_第7张图片

分库分表线上部署:

1停机迁移方案

2简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,都除了对老库增删改,都加上对新库的增删改,这就是所谓双写,同时写俩库,老库和新库。

分库分表存在的问题是:

1跨库关联查询:

字段冗余:比如我们查询合同库的合同表的时候需要关联客户库的客户表,我们可以直接把一些经常关联查询的客户字段放到合同表,通过这种方式避免跨库关联查询的问题

数据同步:比如商户系统要查询产品系统的产品表,我们干脆在商户系统创建一张产品表,通过ETL 或者其他方式定时同步产品数据。

全局表(广播表):比如行名行号信息被很多业务系统用到,如果我们放在核心系统,每个系统都要去关联查询,这个时候我们可以在所有的数据库都存储相同的基础数据。

ER 表(绑定表):将一下存在逻辑外键的一下表格放置在同一个数据库中。

2分布式事务

全局事务(比如XA 两阶段提交;应用、事务管理器(TM)、资源管理器(DB))

基于可靠消息服务的分布式事务

柔性事务TCC

3排序、翻页、函数计算问题

跨节点多库进行查询时,会出现limit 分页,order by 排序的问题。max、min、sum、count 之类的函数在进行计算的时候,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回

4全局主键避重问题:(MySQL 的数据库里面字段有一个自增的属性,Oracle 也有Sequence 序列。如果是一个数据库,那么可以保证ID 是不重复的,但是水平分表以后,每个表都按照自己的规律自增,肯定会出现ID 重复的问题,这个时候我们就不能用本地自增的方式了):

利用UUID(Universally Unique Identifier 通用唯一识别码)

把序号维护在数据库的一张表中。这张表记录了全局主键的类型、位数、起始值。

雪花算法Snowflake(64bit)

4.6数据的缓存实现

缓存可以发生在这些层次:MySQL内部:在系统调优参数介绍了相关设置

数据访问层:比如MyBatis针对SQL语句做缓存,而Hibernate可以精确到单个记录,这里缓存的对象主要是持久化对象Persistence Object

应用服务层:这里可以通过编程手段对缓存做到更精准的控制和更多的实现策略,这里缓存的对象是数据传输对象Data Transfer Object

Web层:针对web页面做缓存

浏览器客户端:用户端的缓存

直写式(Write Through):在数据写入数据库后,同时更新缓存,维持数据库与缓存的一致性。这也是当前大多数应用缓存框架如Spring Cache的工作方式。这种实现非常简单,同步好,但效率一般。

回写式(Write Back):当有数据要写入数据库时,只会更新缓存,然后异步批量的将缓存数据同步到数据库上。这种实现比较复杂,需要较多的应用逻辑,同时可能会产生数据库与缓存的不同步,但效率非常高。

4.7数据库集群方案

使用的数据库集群的方案来提高和优化数据库。基于HA的机制的mycat的高可用

Java面试——2021校招腾讯客户端开发三面_第8张图片

4.8升级硬件

这个不多说了,根据MySQL是CPU密集型还是I/O密集型,通过提升CPU和内存、使用SSD,都能显著提升MySQL性能

Q:你认为什么时候建立索引?

应该使用索引的情况

1.较频繁地作为查询条件的字段

2.经常用连接(join)的字段

3.经常需要根据范围进行搜索的字段

4.需要排序的字段

不应该使用索引的情况

唯一性很小的情况(select count(discount(column))/count(column) )

表数据需要频繁修改

字段不在where语句出现时不要添加索引

数据量少的表不要使用索引

Q:MySQL中连表的方式有哪几种?特点是什么?

内连接:AB中相交的数据

左连接:A 中全部,并在B中找到A中所有的符合的数据

右连接:B中全部,并在A中找到B中所有的符合的数据

全连接 :AB 中所有的表的数据的集合

Q:索引会在什么时候失效?

1.有or必全文索引;

2.复合索引未用左列字段;

3.like以%开头;

4.需要类型转换;

5.where中索引列有运算;

6.where中索引列使用了函数;

7.如果mysql觉得全表扫描更快时(数据少);

什么时没必要用

1.唯一性差;

2.频繁更新的字段不用(更新索引消耗);

3.where中不用的字段;

4.索引使用<>时,效果一般;

Q:说说sql语句从输入到执行的过程?

Java面试——2021校招腾讯客户端开发三面_第9张图片

一、SQL语句执行原理:
第一步:客户端把语句发给服务器端执行当我们在客户端执行 select 语句时,客户端会把这条 SQL 语句发送给服务器端,让服务器端的进程来处理这语句。
第二步:语句解析当客户端把 SQL 语句传送到服务器后,服务器进程会对该语句进行解析。同理,这个解析的工作,也是在服务器端所进行的。虽然这只是一个解析的动作,但是,其会做很多“小动作”。

1. 查询高速缓存(library cache)。服务器进程在接到客户端传送过来的 SQL 语句时,不会直接去数据库查询。而是会先在数据库的高速缓存中去查找,是否存在相同语句的执行计划。如果在数据高速缓存中,则服务器进程就会直接执行这个 SQL 语句,省去后续的工作。所以,采用高速数据缓存的话,可以提高 SQL 语句的查询效率。一方面是从内存中读取数据要比从硬盘中的数据文件中读取数据效率要高,另一方面,也是因为这个语句解析的原因。不过这里要注意一点,这个数据缓存跟有些客户端软件的数据缓存是两码事。有些客户端软件为了提高查询效率,会在应用软件的客户端设置数据缓存。由于这些数据缓存的存在,可以提高客户端应用软件的查询效率。但是,若其他人在服务器进行了相关的修改,由于应用软件数据缓存的存在,导致修改的数据不能及时反映到客户端上。从这也可以看出,应用软件的数据缓存跟数据库服务器的高速数据缓存不是一码事。
2. 语句合法性检查(data dict cache)。当在高速缓存中找不到对应的 SQL 语句时,则服务器进程就会开始检查这条语句的合法性。这里主要是对 SQL 语句的语法进行检查,看看其是否合乎语法规则。如果服务器进程认为这条 SQL 语句不符合语法规则的时候,就会把这个错误信息,反馈给客户端。这个语法检查的过程中,不会对 SQL 语句中所包含的表名、列名等等进行 SQL 他只是语法
上的检查。
3. 语言含义检查(data dict cache)。若 SQL 语句符合语法上的定义的话,则服务器进程接下去会对语句中的字段、表等内容进行检查。看看这些字段、表是否在数据库中。如果表名与列名不准确的话,则数据库会就会反馈错误信息给客户端。所以,有时候我们写 select 语句的时候,若语法与表名或者列名同时写错的话,系统是先提示说语法错误,等到语法完全正确后,再提示说列名或表名错误
4. 获得对象解析锁(control structer)。当语法、语义都正确后,系统就会对我们需要查询的对象加锁。这主要是为了保障数据的一致性,防止我们在查询的过程中,其他用户对这个对象的结构发生改变。
5. 数据访问权限的核对(data dict cache)。当语法、语义通过检查之后,客户端还不一定能够取得数据。服务器进程还会检查,你所连接的用户是否有这个数据访问的权限。若你连接上服务器的用户不具有数据访问权限的话,则客户端就不能够取得这些数据。有时候我们查询数据的时候,辛辛苦苦地把 SQL 语句写好、编译通过,但是,最后系统返回个 “没有权限访问数据”的错误信息,让我们气半死。这在前端应用软件开发调试的过程中,可能会碰到。所以,要注意这个问题,数据库服务器进程先检查语法与语义,然后才会检查访问权限。
6. 确定最佳执行计划 ?。当语句与语法都没有问题,权限也匹配的话,服务器进程还是不会直接对数据库文件进行查询。服务器进程会根据一定的规则,对这条语句进行优化。不过要注意,这个优化是有限的。一般在应用软件开发的过程中,需要对数据库的 sql 语言进行优化,这个优化的作用要大大地大于服务器进程的自我优化。所以,一般在应用软件开发的时候,数据库的优化是少不了的。当服务器进程的优化器确定这条查询语句的最佳执行计划后,就会将这条 SQL 语句与执行计划保存到数据高速缓存(library cache)。如此的话,等以后还有这个查询时,就会省略以上的语法、语义与权限检查的步骤,而直接执行 SQL 语句,提高 SQL 语句处理效率。
第三步:语句执行
语句解析只是对 SQL 语句的语法进行解析,以确保服务器能够知道这条语句到底表达的是什么意思。等到语句解析完成之后,数据库服务器进程才会真正的执行这条 SQL 语句。这个语句执行也分两种情况。
一是若被选择行所在的数据块已经被读取到数据缓冲区的话,则服务器进程会直接把这个数据传递给客户端,而不是从数据库文件中去查询数据。若数据不在缓冲区中,则服务器进程将从数据库文件中查询相关数据,并把这些数据放入到数据缓冲区中(buffer cache)。
第四步:提取数据
当语句执行完成之后,查询到的数据还是在服务器进程中,还没有被传送到客户端的用户进程。所以,在服务器端的进程中,有一个专门负责数据提取的一段代码。他的作用就是把查询到的数据结果返回给用户端进程,从而完成整个查询动作。从这整个查询处理过程中,我们在数据库开发或者应用软件开发过

程中,需要注意以下几点:
一是要了解数据库缓存跟应用软件缓存是两码事情。数据库缓存只有在数据库服务器端才存在,在客户端是不存在的。只有如此,才能够保证数据库缓存中的内容跟数据库文件的内容一致。才能够根据相关的规则,防止数据脏读、错读的发生。而应用软件所涉及的数据缓存,由于跟数据库缓存不是一码事情,所以,应用软件的数据缓存虽然可以提高数据的查询效率,但是,却打破了数据一致性的要求,有时候会发生脏读、错读等情况的发生。所以,有时候,在应用软件上有专门一个功能,用来在必要的时候清除数据缓存。不过,这个数据缓存的清除,也只是清除本机上的数据缓存,或者说,只是清除这个应用程序的数据缓存,而不会清除数据库的数据缓存。
二是绝大部分 SQL 语句都是按照这个处理过程处理的。我们 DBA 或者基于 Oracle 数据库的开发人员了解这些语句的处理过程,对于我们进行涉及到 SQL 语句的开发与调试,是非常有帮助的。有时候,掌握这些处理原则,可以减少我们排错的时间。特别要注意,数据库是把数据查询权限的审查放在语法语义的后面进行检查的。所以,有时会若光用数据库的权限控制原则,可能还不能满足应用软件权限控制的需要。此时,就需要应用软件的前台设置,实现权限管理的要求。而且,有时应用数据库的权限管理,也有点显得繁琐,会增加服务器处理的工作量。因此,对于记录、字段等的查询权限控制,大部分程序涉及人员喜欢在应用程序中实现,而不是在数据库上实现。

DBCC DROPCLEANBUFFERS
从缓冲池中删除所有清除缓冲区。
DBCC FREEPROCCACHE
从过程缓存中删除所有元素。
DBCC FREESYSTEMCACHE
从所有缓存中释放所有未使用的缓存条目
SQL语句中的函数、关键字、排序等执行顺序:
1. FROM 子句返回初始结果集。
2. WHERE 子句排除不满足搜索条件的行。
3. GROUP BY 子句将选定的行收集到 GROUP BY 子句中各个唯一值的组中。
4. 选择列表中指定的聚合函数可以计算各组的汇总值。
5. 此外,HAVING 子句排除不满足搜索条件的行。
6. 计算所有的表达式;
7. 使用 order by 对结果集进行排序。
8. 查找你要搜索的字段。

Q:为什么使用WebSocket协议?有参考过业界其它更好的协议吗?

websocket与http

WebSocket是HTML5中的协议,支持持久连接;而Http协议不支持持久连接。

首先HTMl5指的是一系列新的API,或者说新规范,新技术。WebSocket是HTML5中新协议、新API.跟HTTP协议基本没有关系。

Http协议本身只有1.0和1.1,也就是所谓的Keep-alive,把多个Http请求合并为一个。

Websocket是什么样的协议,具体有什么优点

首先,Websocket是一个持久化的协议,相对于HTTP这种非持久的协议来说

HTTP的生命周期通过 Request 来界定,也就是一个 Request 一个 Response ,那么在 HTTP1.0 中,这次HTTP请求就结束了。

在HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。但是请记住 Request = Response , 在HTTP中永远是这样,也就是说一个request只能有一个response。而且这个response也是被动的,不能主动发起。

Websocket的作用

(1)ajax轮询

ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。

(2)long poll(长轮询)

long poll 其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端(对于PHP有最大执行时间,建议没消息,执行到一定时间也返回)。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。

从上面可以看出其实这两种方式,都是在不断地建立HTTP连接,关闭HTTP协议,由于HTTP是非状态性的,每次都要重新传输 identity info (鉴别信息),来告诉服务端你是谁。然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性。

何为被动性呢,其实就是,服务端不能主动联系客户端,只能有客户端发起。从上面很容易看出来,不管怎么样,上面这两种都是非常消耗资源的。

ajax轮询 需要服务器有很快的处理速度和资源。(速度)long poll 需要有很高的并发,也就是说同时接待客户的能力。(场地大小)

(3)WebSocket

Websocket解决了HTTP的这几个难题。首先,被动性,当服务器完成协议升级后(HTTP->Websocket),服务端就可以主动推送信息给客户端啦。解决了上面同步有延迟的问题。

解决服务器上消耗资源的问题:其实我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(php等)来处理。简单地说,我们有一个非常快速的 接线员(Nginx) ,他负责把问题转交给相应的 客服(Handler) 。Websocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员在统一转交给客户。

由于Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息。

目前唯一的问题是:不兼容低版本的IE

Q:系统的并发量大概有多少?数据量呢?

 

Q:假如明天是活动高峰?QPS预计会翻10倍,你要怎么做?

 

Q:你刚刚说到的限流算法,它们都有什么优缺点呢?

1 计数器算法、 欢动窗口算法、漏桶算法、 令牌算法

计数器算法是使用计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数。此算法在单机还是分布式环境下实现都非常简单,使用redis的incr原子自增性和线程安全即可轻松实现。

https://img-blog.csdnimg.cn/20190716091143141.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTg0NjMyMA==,size_16,color_FFFFFF,t_70 这个算法通常用于QPS限流和统计总访问量,对于秒级以上的时间周期来说,会存在一个非常严重的问题,那就是临界问题,如下图:

https://img-blog.csdnimg.cn/20190716091413825.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTg0NjMyMA==,size_16,color_FFFFFF,t_70

假设1min内服务器的负载能力为100,因此一个周期的访问量限制在100,然而在第一个周期的最后5秒和下一个周期的开始5秒时间段内,分别涌入100的访问量,虽然没有超过每个周期的限制量,但是整体上10秒内已达到200的访问量,已远远超过服务器的负载能力,由此可见,计数器算法方式限流对于周期比较长的限流,存在很大的弊端。

滑动窗口算法是将时间周期分为N个小周期,分别记录每个小周期内访问次数,并且根据时间滑动删除过期的小周期。

如下图,假设时间周期为1min,将1min再分为2个小周期,统计每个小周期的访问数量,则可以看到,第一个时间周期内,访问数量为75,第二个时间周期内,访问数量为100,超过100的访问则被限流掉了  

https://img-blog.csdnimg.cn/20190716091612718.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTg0NjMyMA==,size_16,color_FFFFFF,t_70

由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。此算法可以很好的解决固定窗口算法的临界问题。

漏桶算法是访问请求到达时直接放入漏桶,如当前容量已达到上限(限流值),则进行丢弃(触发限流策略)。漏桶以固定的速率进行释放访问请求(即请求通过),直到漏桶为空。

https://img-blog.csdnimg.cn/20190716090944456.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTg0NjMyMA==,size_16,color_FFFFFF,t_70

令牌桶算法是程序以r(r=时间周期/限流值)的速度向令牌桶中增加令牌,直到令牌桶满,请求到达时向令牌桶请求令牌,如获取到令牌则通过请求,否则触发限流策略。

https://img-blog.csdnimg.cn/20190716090944463.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTg0NjMyMA==,size_16,color_FFFFFF,t_70

Java面试——2021校招腾讯客户端开发三面_第10张图片

Q:除了限流还有别的方式吗?

Java面试——2021校招腾讯客户端开发三面_第11张图片

合法性限流

先看一下第一层限流,也就是合法性限流,首先,什么是合法性限流?它说的是仅仅限制那些合法的用户请求能够抵达到秒杀服务器,而将一些非法的请求全部进行拦截掉。因此这里就需要注意了,在请求合法性限流以前,就得先知道哪些请求是合法的,哪些是非法的。我举一些非法的例子,比如在秒杀活动期间,实际参与秒杀活动的用户可能是人,也可能是机器人,并且还可能存在同一用户反复购买同一件商品的行为,也就是我们说的刷单行为。那么显然机器人和用户刷单都是一种不合理的行为,这种行为会影响到其他正常用户的购物体验,因此就属于不合法的请求。而关于如何限制这些不合法的请求,那么就得具体问题具体分析和讨论了。

比如说,如果非法请求的发起者是机器人,那么最容易想到的方法就是使用验证码,并且验证码还有一个作用,它可以拉长用户的访问时间。举个例子,假设某一秒钟有100万个用户同时下单,但如果使用了验证码,那么用户从输入验证码到整个下单的整个过程就可能需要三秒钟,也就是说下单量仍然是100万不变,但下单的总体时间可能从一秒钟拉长到了三秒,那么原来需要一秒的时间,现在就需要三秒,原来100万的请求,现在每秒钟就只需要处理33万,因此也可以降低流量的峰值。

再来看一下IP限制,如果通过网络技术监测到了某个IP的下单频率,在毫秒级别或者反复购买同一件商品,那么就能断定下单的是机器人或者是不合法的用户,这样我们就可以将IP加到黑名单之中,从而减少不合法的流量。还有一种做法是隐藏秒杀的入口地址,他指的是在秒杀开始之前,服务器并不会向外界暴露秒杀服务的地址,当秒杀服务开始之后才开放地址。接下来我们再看一下第二层限流,也就是负载性限流。

Java面试——2021校招腾讯客户端开发三面_第12张图片

负载限流

先看一下负载限流的理论基础是什么,一个是集群,一个是网络7层模型,我们在搭建集群时经常会用到一些工具,比如说Nginx和LVS,这些都可以用于负载限流。假设经过了第1层合法性限流以后,还是有33万的请求,如果通过集群搭建了三台服务器,那么每台服务器也就只需要承载11万个请求量了,这样也能降低请求的并发量。

但是根据网络7层模型,Nginx处于第7层,除此以外,在网络7层模型之中的其他层也可以进行负载,比方说我们在第2层的数据链路层,也可以通过MAC地址进行负载,比如我们可以生成一个虚拟MAC,然后将MAC地址映射到其他三个真实的服务器上,同样的也可以在网络第3层通过IP进

Java面试——2021校招腾讯客户端开发三面_第13张图片

那么能否进行级联负载呢,我们假设当请求到来时,能否先在第2层进行负载,然后再在第3层,之后再在第4层、第7层分别都进行一次负载。如果这样做在功能上肯定是可以实现的,但这种级联的做法也会同时增加请求的路径。每增加一次负载就会增加一个转发路径,而每增加一个转发路径就可能带来网络延迟问题,因此太多的级联负载也是不推荐的。那么对于级联负载常见的做法有哪一些?我认为单独的使用Nginx,或者Nginx和LVS来实行二级负载,就已经对于大部分系统足够了。

刚才提到的LVS是处于第4层,它是通过网络端口进行的负载,而Nginx是第7层应用级别的负载,还有我们这里说的负载都是通过软件进行的负载,也就是软负载。除此以外我们还可以购买一些硬件工具进行负载,也就是硬负载,常见的硬负载工具,有F5、Array等。大家可能已经发现了,前两层限流都是想办法将请求拦截在抵达服务器之前,但是如果请求已经抵达到了服务器,又该如何进行限流?其实就是我们马上要讲到第3层限流。,也就是服务限流。

服务限流

首先我们可以通过Web 服务器本身进行限流,比方说Tomcat是一款比较熟悉的Web服务器,如果连接Tomcat的数量太多,就可能造成Tomcat的不稳定,该怎么办呢?我们可以把Tomcat最大连接数设置为一个合理的值,比方说我们可以设置Tomcat最大连接数值为300,如果超过300的连接请求就会被Tomcat无条件拒绝,这样就可以保证Tomcat稳定性了。再比如我们也可以在服务器的内部,通过编写一些算法来进行限流。常见的算法比如说令牌桶算法、漏桶算法。对于这些算法,如果你的编写有些困难,我们也可以直接调用一些类库里边已经存在的API。

除了刚才讲的服务器配置参数以及限流算法以外,我们在服务器之中还可以使用队列来进行限流。这个说的队列主要是消息队列。这里我们拿一个例子来说,假设每秒钟有10万的请求量,并且系统里边有A、B、C三个子系统,每秒钟能够处理的极限分别是2万请求、3万请求和4万请求。在不使用消息队列的情况下,如果这10万请求分别平均分给这三个子系统,那么每个子系统就需要处理3.3万的请求。很显然在每秒钟之内,系统A只能处以2万请求,如果接收到了3.3万请求,就可能导致系统A延迟甚至崩溃的情况,而如果使用消息队列就可以很好的解决这种问题。消息队列本质是一种缓冲区,当10万请求到来时消息队列可以将这10万请求临时存储,然后三个子系统再分别根据自己的性能,分别去消息队列中针对性的去拉取特定数量的请求。比方说系统A的极限是2万,那么他每次最多就只需要从队列之中取2万数据就够了,这样就可以避免超额请求对系统造成的压力的情况了。

除了前面介绍的服务器限流以及队列限流以外,我们还可以使用第3个服务限流,也就是缓存限流,限流的本质是为了不断的削减请求的数量,而缓存的作用是为了减少用户请求服务端的数量,因此缓存也可以作为限流的一个实现方案,但为了有效的使用缓存进行限流,我们需要先将系统设计成前后端分离或者动静分离的结构,然后分别的对静态以及动态缓存进行限流。

先看一下对静态请求如何进行缓存,当客户端第1次请求服务端的时候,服务端会将网页的基本结构代码显示给客户端,比如我们第1次访问某个网站时,我让服务器就会将搭建此网站的html、JavaScript脚本等代码响应给客户端,那么客户端就可以将这些html、JavaScript代码缓存到客户端浏览器之中。那么这样一来,当用户以后再次访问这个网站时,就可以直接从本地浏览器的缓存中获取html、JavaScript代码了。对于html这种体系比较小的代码,我们可以直接将其缓存的浏览器之中,但是如果体积较大的图片,我们最好将它们缓存的Nginx或者通过Nginx转发在OSS等云服务器之中。而如果是视频等一些体积特别大的静态资源,也可以叫它缓存在CDN中,利用CDN区域部署、就近访问的特点,来提高用户的访问速度。并且我们知道各个缓存并不是独立的,也可以相互补充,比如说OSS也可以作为CDN的回源站点。

接下来再看一下动态缓存,对于动态缓存一般先建议缓存在本地的服务器之中,如果本地服务器的缓存失效,我们再缓存到由Redis组成的远程集群之中进行二次的查询,也就是说我们可以搭建本地缓存以及远程缓存组成的二级结构进行动态请求的缓存,需要注意的是缓存的级别也并不是越多越好。我们可以在CPU、内存、硬盘、网络等节点上分别设置缓存,并且每个节点里边还可以再次细分出多级缓存来。如果这样做就必须要考虑多级缓存带来的一致性问题了,缓冲的级别越多,一致性的问题就越严重,而解决这种一致性问题又会增加系统的开发成本以及系统的额外开销。还要知道的是我们缓存的级别越多,请求在系统内部的跳转路径也会越长,而这也就类似于多级负载带来的问题。

Java面试——2021校招腾讯客户端开发三面_第14张图片

那么对于大部分项目而言,我们使用静态缓存加上二级动态缓存已经完全足够了。总的来说,我们静态缓存可以将大量的静态资源缓存在服务器以外的地方,而动态缓存可以很大程度上减少请求抵达数据库的次数。

最后我们再来看一下监控限流,我们知道CPU、内存、并发量等都是衡量系统稳定性的重要指标,如果他们的使用频率过高,也可能造成系统的不稳定。因此我们也可以创建一些线程专门用于监控这些指标,比方说我们可以建立一个线程,专门用于监控CPU的利用率,如果CPU利用率达到了极限,就可以临时性的采取服务降级或拒绝策略。这里说的服务降级实际上与精兵简政的思想类似,它指的是当系统资源不足时,我们就可以把查看三个月以前的历史订单、历史评论等一些非核心的服务临时关闭,从而为系统节约出一部分的资源来。在采用服务降级或拒绝策略一段时间之后,CPU等资源利用率就会恢复到正常状态,我们就可以重新接收并处理新的请求了。

这里介绍了合法性限流、负载性流、服务限流,其中合法性限流可以拦截大量的非法请求,而负载限流可以通过集群技术抵抗大规模的流量冲击。服务限流则是通过对服务器的参数配置、限流算法、MQ缓存以及监控等手段进行限流。

Q:Redis都有哪几种持久化方式?各自有什么优缺点?

Redis的持久化的方式

redis提供两种方式进行持久化,一种是RDB持久化(原理是将Reids在内存中的数据库记录定时 dump到磁盘上的RDB持久化),另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件)

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储

Java面试——2021校招腾讯客户端开发三面_第15张图片

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

Java面试——2021校招腾讯客户端开发三面_第16张图片

 

AOF

RDB

优点

1AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次fsync操作,最多丢失 1 秒钟的数据

2AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。

3AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写.

4AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。

1RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去

2RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis 保持高性能

3相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。

缺点

1对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大

2AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式

1如果想要在 redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好

2RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。

Q:假设master节点宕机了,你会怎么进行数据恢复?

采用的是的哨兵机制的做的,能够在在master中的宕机后,能够迅速的选择内用节点作为主节点。并在这个接下来做数据的同步

1、两种数据丢失的情况

主备切换的过程,可能会导致数据丢失

(1)异步复制导致的数据丢失

因为master -> slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了

(2)脑裂导致的数据丢失

脑裂,也就是说,某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着

此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master这个时候,集群里就会有两个master,也就是所谓的脑裂

此时虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧master的数据可能也丢失了

因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据

2、解决异步复制和脑裂导致的数据丢失

min-slaves-to-write 1
min-slaves-max-lag 10

要求至少有1个slave,数据复制和同步的延迟不能超过10秒

如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了

上面两个配置可以减少异步复制和脑裂导致的数据丢失

(1)减少异步复制的数据丢失

有了min-slaves-max-lag这个配置,就可以确保说,一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了,那么就拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内

(2)减少脑裂的数据丢失

如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失上面的配置就确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己ack,那么就拒绝新的写请求。因此在脑裂场景下,最多就丢失10秒的数据。

那么对于client,我们可以采取降级措施,将数据暂时写入本地缓存和磁盘中,在一段时间后重新写入master来保证数据不丢失;也可以将数据写入kafka消息队列,隔一段时间去消费kafka中的数据。

Redis的主从复制:

1、redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

2、通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。

1:当一个从数据库启动时,会向主数据库发送sync命令,

2:主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来

3:当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。

4:从数据库收到后,会载入快照文件并执行收到的缓存的命令。

Q:假设Redis的一个key对应的list数据非常多?你会怎么解决?(重复问,问到不会为止)

可见,当存储量特别大的时候,可以将key进行hash分散处理,可以减少存储内存。并且当key的数量很大的时候,redis取值性能还是很高的。

Q:除了缓存和限流还有别的方式吗?

消息中间件 ,合法性限制 分布式的限流。

Q:读写分离会有哪些问题?

1.复制数据延迟
可能会出现 slave 延迟导致读写不一致等问题,当然你也可以使用监控偏移量 offset,如果 offset 超出范围就切换到 master 上,逻辑切换,而具体延迟多少,可以通过 info replication 的 offset 指标进行排查。

2.从节点故障问题
对于从节点的故障问题,需要在客户端维护一个可用从节点可用列表,当从节点故障时,立刻切换到其他从节点或主节点,redis Cluster 可以解决这个问题

3.配置不一致

主机和从机不同,经常导致主机和从机的配置不同,并带来问题。

数据丢失:主机和从机有时候会发生配置不一致的情况,例如 maxmemory 不一致,如果主机配置 maxmemory 为8G,从机 slave 设置为4G,这个时候是可以用的,而且还不会报错。但是如果要做高可用,让从节点变成主节点的时候,就会发现数据已经丢失了,而且无法挽回。

4.规避全量复制
全量复制指的是当 slave 从机断掉并重启后,runid 产生变化而导致需要在 master 主机里拷贝全部数据。这种拷贝全部数据的过程非常耗资源。

全量复制是不可避免的,例如第一次的全量复制是不可避免的,这时我们需要选择小主节点,且maxmemory 值不要过大,这样就会比较快。同时选择在低峰值的时候做全量复制。

造成全量复制的原因
(1)是主从机的运行 runid 不匹配。解释一下,主节点如果重启,runid 将会发生变化。如果从节点监控到 runid 不是同一个,它就会认为你的节点不安全。当发生故障转移的时候,如果主节点发生故障,那么从机就会变成主节点。我们会在后面讲解哨兵和集群。

(2)复制缓冲区空间不足,比如默认值1M,可以部分复制。但如果缓存区不够大的话,首先需要网络中断,部分复制就无法满足。其次需要增大复制缓冲区配置(relbacklogsize),对网络的缓冲增强。

解决方案
在一些场景下,可能希望对主节点进行重启,例如主节点内存碎片率过高,或者希望调整一些只能在启动时调整的参数。如果使用普通的手段重启主节点,会使得runid发生变化,可能导致不必要的全量复制。

为了解决这个问题,Redis提供了debug reload的重启方式:重启后,主节点的runid和offset都不受影响,避免了全量复制。

(3)当一个主机下面挂了很多个 slave 从机的时候,主机 master 挂了,这时 master 主机重启后,因为 runid 发生了变化,所有的 slave 从机都要做一次全量复制。这将引起单节点和单机器的复制风暴,开销会非常大。

解决方案:
可以采用树状结构降低多个从节点对主节点的消耗

Java面试——2021校招腾讯客户端开发三面_第17张图片

从节点采用树状树非常有用,网络开销交给位于中间层的从节点,而不必消耗顶层的主节点。但是这种树状结构也带来了运维的复杂性,增加了手动和自动处理故障转移的难度

(4)单机器的复制风暴
由于 Redis 的单线程架构,通常单台机器会部署多个 Redis 实例。当一台机器(machine)上同时部署多个主节点(master)时,如果每个 master 主机只有一台 slave 从机,那么当机器宕机以后,会产生大量全量复制。这种情况是非常危险的情况,带宽马上会被占用,会导致不可用。

Java面试——2021校招腾讯客户端开发三面_第18张图片

解决方案:
应该把主节点尽量分散在多台机器上,避免在单台机器上部署过多的主节点。
当主节点所在机器故障后提供故障转移机制,避免机器恢复后进行密集的全量复制

Q:你之前说到的websocket?能具体讲讲整个链路模型是怎么样的吗?

 

Q:讲一讲TCP连接过程?少一次握手会造成什么问题?

 

Q:TCP的keepalive了解吗?说一说它和http的keepalive的区别?

 

Q:你这个websocket会不会丢失消息?

 

Q:你要怎么解决这个消息丢失?能具体说说吗?

第一种:生产者弄丢了数据。生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的.

第二种:RabbitMQ 弄丢了数据。MQ还没有持久化自己挂了

第三种:消费端弄丢了数据。刚消费到,还没处理,结果进程挂了,比如重启了。

Java面试——2021校招腾讯客户端开发三面_第19张图片

消息的丢失在分在的MQ中的三个部分:1生产者到MQ中生产者消息有丢失,这个时候MQ采用的消息确认的机制保证消息能够发送到MQ中confirm机制,2 MQ中本身发生消息的丢失 这个时候采用的是消息的持久化操作。3消息到消费者的时候消息丢失,采用的是消息的手动确认的机制。

Java面试——2021校招腾讯客户端开发三面_第20张图片

Q:介绍下HashMap?(后来问了都是HashMap一系列问题,都比较基础,这里就不细说了)

Q:讲一讲线程池?

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

1线程池状态

在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:

volatile int runState;

static final int RUNNING    = 0;

static final int SHUTDOWN   = 1;

static final int STOP       = 2;

static final int TERMINATED = 3;

runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;

下面的几个static final变量表示runState可能的几个取值。

  当创建线程池后,初始时,线程池处于RUNNING状态;

  如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

  如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

  当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

2任务的执行

ThreadPoolExecutor类中其他的一些比较重要成员变量:

rivate final BlockingQueue workQueue; //任务缓存队列,用来存放等待执行的任务

private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小//、runState等)的改变都要使用这个锁

private final HashSet workers = new HashSet();  //用来存放工作集

private volatile long  keepAliveTime;    //线程存货时间  

private volatile boolean allowCoreThreadTimeOut//是否允许为核心线程设置存活时间

private volatile int   corePoolSize;    //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)

private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数

private volatile int   poolSize;          //线程池中当前的线程数

private volatile RejectedExecutionHandler handler; //任务拒绝策略

private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程

private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数

private long completedTaskCount;   //用来记录已经执行完毕的任务个数

1)首先,要清楚corePoolSize和maximumPoolSize的含义;

2)其次,要知道Worker是用来起到什么作用的;

3)要知道任务提交给线程池之后的处理策略,这里总结一下主要有4点:

如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;

如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务

如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;

如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

3线程池中的线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

4任务缓存队列及排队策略

在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务

5任务拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

6线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

    shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

    shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

7线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

setCorePoolSize:设置核心池大小

setMaximumPoolSize:设置线程池最大能创建的线程数目大小

当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

Java面试——2021校招腾讯客户端开发三面_第21张图片

如果是不采用是这个那就在队列中的线程是不可能出队列的,就是如果是的非公平的锁的话那就永远不能出队列。那可能能执行不到该线程。

Q:你在项目哪方面用到线程池?

Q:讲一讲Java内存模型?

  1. 线程池的使用场景有哪些
    线程池适合单系统的大量的异步任务处理,比如发送短信、保存日志。

  2. 说说创建线程池的重要参数

    • corePoolSize:线程池的大小。线程池创建之后不会立即去创建线程,而是等待线程的到来。当前执行的线程数大于该值时,线程会加入到缓冲队列。
    • maximumPoolSize:线程池中创建的最大线程数。
    • keepAliveTime:空闲的线程多久时间后被销毁。默认情况下,该值在线程数大于corePoolSize时,对超出corePoolSize值的这些线程起作用。
    • unit:TimeUnit枚举类型的值,代表keepAliveTime时间单位。
    • handler:线程拒绝策略。
  3. 这些参数怎么设置,线程池调优怎么做

  • 基本思想:
    • 并发高、业务执行时间长,在于整体架构的设计,能否使用中间件对任务进行拆分和解耦。
    • 并发不高、任务执行时间长的业务要区分开:
      1. IO密集型的任务,因为IO操作并不占用CPU,可以加大线程池中的线程数目,让CPU处理更多的业务
      2. CPU密集型任务,线程池中的线程数设置得少一些,减少线程上下文的切换。
    • 高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换。
    • 具体设置:

      1. corePoolSize = 每秒需要多少个线程处理
        threadcount = tasks/(1/taskcost) =taskstaskcout =  (500~1000)0.1 = 50~100 个线程。corePoolSize设置应该大于50。根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可。
      2. queueCapacity = (coreSizePool/taskcost) * responsetime
        计算可得 queueCapacity = 80/0.1*1 = 80,意思是队列里的线程可以等待1s,超过了的需要新开线程来执行。切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
      3. maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)(最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数。计算可得 maxPoolSize = (1000-80)/10 = 92。
      4. rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理。
      5. keepAliveTime和allowCoreThreadTimeout:采用默认能满足。

Q:讲一讲你了解的GC的知识?

1 JVM 内存模型

2GC垃圾的算法

3常用的垃圾回收机器

4堆内存模型。

Q:C/C++熟吗?

Q:考了三道简单题,一个是双指针,一个是dp。最后还问了一道外排序?

Q:再考了两道IQ题:烧绳子和洗牌问题。

Q:讲一讲线程和进程?

进程是资源分配的最小的单位,线程是执行的最小的单位。

Q:进程的通信方式?

五种:消息队列 信号量 共享内存 socket 管道通信

Q:线程的通信方式?

wait/notify 等待

Volatile 内存共享

CountDownLatch 并发工具

CyclicBarrier 并发工具

Q:网络字节序和CPU字节序?

网络字节序:网络字节序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节序采用big endian排序方式。

Q:讲一讲中间人伪造证书的攻击?

SSL,即Secure Sockets Layer,是为网络通信提供安全及数据完整性的一种安全协议。SSL是介于传输层和应用层之间的网络协议,为了提供足够的安全性和数据完整性,SSL主要采用了一下几种措施:

1. 使用证书确认用户和服务器;

2. 在数据尾部添加校验码,防止中途篡改;

3. 加密传输数据和校验码,防止中途窃取。

可以看出,SSL协议被设计的十分安全,要攻破它并不容易。但是我们可以利用浏览器对服务器证书检查的缺陷,通过伪造CA证书的方式,进行SSL中间人攻击。

Q:如果这边安排你转移动端开发?你这边有兴趣吗?

Q:先来个自我介绍?

Q:介绍一下你的项目是做什么?

Q:你建立连接时的认证是怎么实现的?

Q:消息的整个链路是怎么样的?

Q:为什么要设置keepaalive?

Q:为什么keepalive时间要设置成这样?

Q:讲一讲TCP的TIME_WAIT?

Q:在心跳包的发送间隔上是怎么考虑的?

Q:服务器过时断开和客户端断开它们的优缺点?

Q:客户端断开连接的过程都发了哪些包?

Q:说一说BIO/NIO/AIO?

Q:说一说Reactor的模型?

Q:你的项目都是服务端开发,你对移动端开发有经验吗?

Q:C/C++会吗?

Q:有学过汇编吗?有了解NDK吗?

Q:STL有了解过吗?

Q:C++的指针有多少个字节?

Q:C++的内存管理?

Q: C++从代码到可执行二进制文件的过程?

Q:一个可执行的二进制文件,里面应该包含哪些内容?

Q:如何让应用只允许打开一个窗口?

Q:局部变量在内存哪个位置?静态变量呢?

Q:系统默认有多少个端口?为什么有这么多个端口?

Q:443端口默认是干嘛的?

443端口bai:即网页浏览端口,主要是用于HTTPS服务,是提供加密du和通过安全端口zhi传输的另一种HTTP。

Q:80端口默认是干嘛的?

80端口:为HTTP(HyperText Transport Protocol)即超文本传输协议开放的,此为上网冲浪使用次数最多的协议,主要用于WWW(World Wide Web)即万维网传输信息的协议。

1、443端口:HTTPS服务一般是通过SSL(安全套接字层)来保证安全性的,但是SSL漏洞可能会受到黑客的攻击,比如可以黑掉在线银行系统,盗取信用卡账号等。

2、80端口:木马程序可以利用80端口来攻击计算机的,例如Executor、RingZero等。

Q:讲一讲Https?

建立连接获取证书

1) SSL 客户端通过 TCP 和服务器建立连接之后(443 端口),并且在一般的 tcp 连接协商(握手)过程中请求证书。即客户端发出一个消息给服务器,这个消息里面包含了自己可实现的算法列表和其它一些需要的消息, SSL 的服务器端会回应一个数据包,这里面确定了这次通信所需要的算法,然后服务器向客户端返回证书。(证书里面包含了服务器信息:域名。申请证书的公司,公共秘钥)

证书验证

2) Client 在收到服务器返回的证书后,判断签发这个证书的公共签发机构,并使用这个机构的公共秘钥确认签名是否有效,客户端还会确保证书中列出的域名就是它正在连接的域名。

数据加密和传输

3) 如果确认证书有效,那么生成对称秘钥并使用服务器的公共秘钥进行加密。然后发送给服务器,服务器使用它的私钥对它进行解密,这样两台计算机可以开始进行对称加密进行通信。

Java面试——2021校招腾讯客户端开发三面_第22张图片

Q:Https有什么不好的地方吗?

大家都知道现在的网站分为两种,一种是传统的http协议,另外一种则是加密的https,其实这两种是一脉相承的,https就是基于http基础上实现的,加入了SSL或者TLS。相对于Https,http简单方便,开发起来也方便,但是却有个重要的缺点。那就是其用户端和客户端之间的数据一旦被抓包,其相关的交互信息就会被泄露,因为在传输中它们是不加密的,相当于在“裸奔”,除非服务器自己对关键信息进行了加密,否则数据保密性根本无从谈起。
1、SSL/TLS协议的主要作用

     SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密(来自百度百科)

     SSL/TLS协议的主要作用就是:

      1、认证用户和服务器,保证各自的数据都发送到正确的位置上去。

      2、对发送的数据进行加密,保护数据。

      3、保证数据在发送过程中的完整性。
2、https传输过程简述

      1、首先https的服务端必须要拥有一个CA认证合法授权的证书,没有这个证书,客户端在访问该服务器时就会提醒用户这个网站是不受信任的。只有通过CA认证的服务器才是可靠的,这保证了用户在访问服务器的安全性。浏览器会保持一个信任的CA机构列表,通过这些机构出查询所访问的服务器提供的证书是否合法。

      2、如果此时发现证书是合法OK的,那么就从这个服务器端的证书中获取到了加密秘钥,这个加密秘钥会沟通商议出一个随机的对称秘钥,服务端在传输信息使用该秘钥进行加密。而客户端在收到这部分信息后,在浏览器侧通过之前得到的对称秘钥进行解密,相反如果客户端想要向服务端发送消息时也是如此。

3、Https相比于Http协议的优点和缺点

1、优点:相比于http,https可以提供更加优质保密的信息,保证了用户数据的安全性,此外https同时也一定程度上保护了服务端,使用恶意攻击和伪装数据的成本大大提高。

 2、缺点: 缺点也同样很明显,第一https的技术门槛较高,多数个人或者私人网站难以支撑,CA机构颁发的证书都是需要年费的,此外对接Https协议也需要额外的技术支持;其二,目前来说大多数网站并不关心数据的安全性和保密性,其https最大的优点对它来说并不适用;其三,https加重了服务端的负担,相比于http其需要更多的资源来支撑,同时也降低了用户的访问速度;第四,目前来说Http网站仍然大规模使用,在浏览器侧也没有特别大的差别,很多用户不关心的话根本不感知。

Q:说一说进程通信、线程通信?

进程是资源分类的最小的单位 线程是的执行的最小的单位,在一个进程中有多个线程,且多个线程资源是共享的,但在不同的的进程资源是不能共享的。

线程:使用volatile关键字、使用Object类的wait() 和 notify() 方法、使用JUC工具类 CountDownLatch、基本LockSupport实现线程间的阻塞和唤醒。

进程:信号量 socket 共享内存 消息队列 管道通信

Q:可以说说大端小端吗?

Q:进程空间从高位到低位都有些什么?

Q:一张600*600的图片加载到内存会占多少空间?

Q:字节码可以跨平台运行吗?

            可是在实现跨平台运行的

Q:讲一讲缺页中断?

进程线性地址空间里的页面不必常驻内存,在执行一条指令时,如果发现他要访问的页没有在内存中(即存在位为0),那么停止该指令的执行,并产生一个页不存在的异常,对应的故障处理程序可通过从外存加载该页的方法来排除故障,之后,原先引起的异常的指令就可以继续执行,而不再产生异常。

Q:有了解过一些加密算法和编码算法吗?

            Brcpt加密,加密和验证的原理

Q:(之后就问了一道简单算法,技术问题就结束了)

Q:你在学校学的什么课程?

Q:为什么选你现在的这个方向呢?

Q:你最近有看什么书吗?

Q:你还有什么问题想问我的?

你可能感兴趣的:(实际面试问题和答案解答,java)