Java面试题六:消息队列与数据库

七、消息队列

1、消息队列的使用场景。

答:应用解耦、服务通信、异步任务、削峰填谷、消息广播。

2、Kafka架构。

答:Kafka集群由多个server组成,每个server成为一个Broker,为消息代理;Kafka中的消息是按topic进行划分的,一个topic就是一个queue;为了提高并行能力,Kafka为每个topic维护了分布式的Partition日志文件。

3、Kafka消息生产/消费流程。

答:producer使用push模式,consumer使用pull模式。consumer使用pull模式的好处是可以由消费者自己控制消费速度,以及消费的进度,或者按照任意偏移量进行消费。

4、Partition。

答:Kafka的消息是一个个键值对,键可以作为消息的附加信息,也可以用来决定被写到哪个主题分区(通过散列),如果键值为null,则会被均匀分布到各个分区。Kafka为每个topic维护了分布式的partition日志文件,每个partition在Kafka存储层面是Append Log。消息会被追加到Log文件的尾部,并分配到一个单调递增的顺序编号Offset。

5、如何实现幂等exactly-once?

答:0.10之前要Consumer自行实现幂等(可以使用redis的set)。0.11.0两个策略:

1)单Producer单Topic:每个Producer在初始化的时候都会被分配一个唯一的PID。对于每个唯一的PID,Producer向指定的Topic中某个Partition发送的消息会携带一个从0单调递增的Sequence Number,Broker端也会维护这个序列。

当消息序号比Broker维护的序号大1以上,则说明发生了乱序,Broker拒绝该消息,Producer抛出InvalidSequenceNumber。

当消息序号小与等于维护序号,则说明是重复消息,Broker丢弃该消息,Producer抛出DuplicateSequenceNumber。

刚好大一,合法。

2)事务(保证原子操作):在Kafka的事务中,应用程序必须提供一个唯一的事务ID(Transaction ID),并且宕机重启也不会改变,一个Transaction ID与一个PID对应。每次Producer通过Transaction ID拿到PID的同时,还会获取一个单调递增的Epoch,由于旧的Produce Epoch比新的小,Kafka会拒绝其请求。Kafka引入一个Transaction Coordinator服务端模块,用于管理Producer发送消息的事务性,维护Transation Log,Producer通过与Transaction Coordinator通信,由其将的状态插入相应的Transaction Log。

6、Kafka如何实现高吞吐率性能?

答:写消息:

1)消息顺序写入磁盘,新来的消息只能追加到已有消息的末尾,并且不支持随机删除、随机访问;

2)为避免过频繁的I/O,Kafka会把消息集合起来一起批量发送;

3)为避免无效率的字节复制、转换,Kafka采用Producer、broker和consumer共享的标准化二进制消息格式;

4)Kafka重度依赖底层操作系统提供的PageCache功能。当上层有写操作时,操作系统将数据写入PageCache,同时标记PageCache为Dirty(当空闲内存低于一个阈值,内核会将脏页写回磁盘,释放内存)。当读操作时,先从PageCache中查找,如果发生缺页才进行磁盘调度。

读消息:

5)使用sendfile,将数据直接从页缓存发送到网络上,减少了内核空间到应用空间的来回拷贝;

6)批量压缩,批量的消息可以通过压缩的形式传输,并且在日志中也可以保持压缩格式,直到被消费者解压缩。

7、Kafka高可用副本机制。

答:副本中,分为leader副本和follower副本,leader副本负责数据的读写请求,follower副本负责从leader副本复制数据到follower副本中备份数据。

ISR(in-Sync replicas )里面维护的是所有与leader数据差异在阈值范围内的副本所在的broker id列表,是leader的候选人。当数据差异在(replica.lag.time.max.ms)长的时间内副本数据依然没有和leader数据达成一致,那么这个follower 的broker id就会从isr维护的列表中剔除出去

基本的分配思路就是:

1)将n Broker和待分配的i个Partition排序 
2)将第i个Partition分配到第(i mod n)个Broker上 
2)将第i个Partition的第j个副本分配到第((i + j) mod n)个 Broker上. 

每个副本中都存有LEO(log end offset) 和HW(high watermark) 

LEO:对于leader副本而言,leo在新消息写入时leo更新;对于follower而言,在向leader副本发起一次同步数据请求时,leader有新消息写入并且没同步给follower,这时将消息返回给follower,follower的leo更新。

HW:在hw之前的offset消息都是可以消费的,也就是消息是已经提交的概念。

对于leader而言,hw更新需要根据producer设置的ack模式来确定。 
1.ack设置为0表示不需要任何副本确认,这时候消息在leader副本写入之后直接更新leader分区hw,表示消息是已提交可以消费。 
2.ack设置为1表示只需要leader副本确认消息,这时候消息在leader副本写入之后直接更新leader分区hw,并且向producer发送确认ack,表示消息是已提交可以消费。 
3.ack设置为-1表示消息需要n个副本确认消息,这时候需要leader收到n个副本的同步消息成功的ack之后才更新leader的hw并且发送ack给producer确认,表示消息是已提交可以消费。 
对于follower而言,hw用来在重新选举leader时,表明哪些消息是可以消费的。

8、选主机制、Kafka Partition Leader选主机制。

答:大数据领域常用的有 以下两种:

Zab(zookeeper使用):比如3个节点选举leader,编号为1,2,3。1先启动,选择自己为leader,然后2启动首先也选择自己为 leader,由于1,2都没过半,选择编号大的为leader,所以1,2都选择2为leader,然后3启动发现1,2已经协商好且数量过半,于是3也选择2为leader,leader选举结束。

Raft:启动时在集群中指定一些机器为Candidate ,然后Candidate开始向其他机器(尤其是Follower)拉票,当某一个Candidate的票数超过半数,它就成为leader。

Kafka Partition选主机制:

Kafka 的Leader Election方案解决了上述问题,它在所有broker中选出一个controller,所有Partition的Leader选举都由controller决定。controller会将Leader的改变直接通过RPC的方式(比ZooKeeper Queue的方式更高效)通知需为此作为响应的Broker。

Kafka 集群controller的选举过程如下 :

每个Broker都会在Controller Path (/controller)上注册一个Watch,则 controller 会通过 watch 得到该 topic 的 partition/replica。

当前Controller失败时,对应的Controller Path会自动消失(因为它是ephemeral Node),此时该Watch被fire,所有“活”着的Broker都会去竞选成为新的Controller(创建新的Controller Path),但是只会有一个竞选成功(这点由Zookeeper保证)。

竞选成功者即为新的Leader,竞选失败者则重新在新的Controller Path上注册Watch。因为Zookeeper的Watch是一次性的,被fire一次之后即失效,所以需要重新注册。

Kafka partition leader的选举过程如下 (由controller执行):

从Zookeeper中读取当前分区的所有ISR(in-sync replicas)集合
调用配置的分区选择算法选择分区的leader。

使用Controller可以防止:

split-brain (脑裂): 这是由ZooKeeper的特性引起的,虽然ZooKeeper能保证所有Watch按顺序触发,但并不能保证同一时刻所有Replica“看”到的状态是一样的,这就可能造成不同Replica的响应不一致 ;

herd effect (羊群效应): 早期每一个broker都会在zookeeper注册Watcher,如果宕机的那个Broker上的Partition比较多,会造成多个Watch被触发,造成集群内大量的调整;

ZooKeeper负载过重 : 每个Replica都要为此在ZooKeeper上注册一个Watch,当集群规模增加到几千个Partition时ZooKeeper负载会过重

9、消息丢失怎么办?

答:rabbitmq:

生产者丢失:使用confirm机制,异步回调重试;

中间件丢失:对消息做持久化,将deliveryMode设置为2,对queue和数据都进行持久化,而且要跟confirm结合起来,只有持久化了以后才通知生产者ack;

消费者丢失:关闭autoack,等处理完再发送ack。

kafka:

消费端丢失数据:关闭自动提交offset,等处理完后手动提交offset,此时也要自行保证幂等性;

kafka丢失:设置主从,至少2个副本,只有一个leader会导致失败,并将ack模式设置为所有副本都写入才成功,并在producer端设置无限重试(很大次数)。

11、如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时呢?

答:大量消息在积压几小时:紧急扩容,新建1个topic,10倍partition,同时征用10台机器部署consumer,并起多线程处理;

消息队列过期失效:手写程序恢复数据;

消息队列满:先直接丢弃,后续再手动恢复。

12、kafka如何实现非阻塞性重试?

答:消息处理失败时将消息发送到retry_topic,并设置一定的等待时间进行重试,如果重试多次失败,则将消息存储在failed_topic。

13、rabbitmq和kafka区别。

答:rabbitmq:1)吞吐量万级;2)延迟是微秒级;3)提供管理界面;4)使用erlang;5)不可拓展

kafka:1)吞吐量十万级;2)延迟毫秒级;3)可拓展

14、如何保证kafka消息的顺序执行?

答:生产者分发时设置相同的key,保证消息被分发到同一个partition;消费者使用多线程时对id做hash,保证相同id的消息分发到同一个内存队列中。

15、RabbitMQ与Redis队列性能对比。

答:入队出队各执行100万次,每10万次记录一次执行时间

吞吐量对比:

 入队出队
 128B512B1K10K128B512B1K10K
Redis16088197831709499211595520449180989355
RabbitMQ106279916937023663219317429821588

 

16、Rebalance

答:基于一个消费组订阅特定主题的情况下,消费者数量或主题分区数量发生变化都会引起分区再均衡。在再均衡期间,消费者无法读取消息,会造成整个群组一小段时间的不可用。

分区策略:

range策略:基于范围的思想,将单个topic的所有分区按照顺序排列,然后把这些分区划分成固定大小的分区段并依次分配给每个消费者。
round-robin策略:把所有topic的所有分区顺序白开,轮训式地分配给每个消费者
sticky策略:就是前两个策略的集合,刚开始的时候,以段来分配,然后如果其中某个消费者挂了之后,那么以前他所管理的分区就以滚动分区的方式分配给其他存活的消费者。

 

八、数据库

  • 其它

1、MySQL的锁机制。

答:1)MyISAM表锁:分为表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock),MyISAM默认对SELECT加读锁,对更新加写锁。写锁请求默认优先与读锁请求,可以通过通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级,或者使用max_write_lock_count,当读锁达到这个值后,给予读请求优先的权利。

MyISAM支持查询和插入操作并发进行。

MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。

当concurrent_insert设置为0时,不允许并发插入。

当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。

当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。

2)InnoDB锁:支持事务,采用行级锁。也分为共享读锁和排它写锁,只有通过索引条件检索数据,InnoDB才使用行级锁,否则InnoDB将使用表锁。

对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据。 

排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。

间隙锁(Next-Key锁)

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁。

2、数据库会死锁吗,举一个死锁的例子,mysql怎么解决死锁?

答:如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁。

避免死锁:

1)以固定的顺序访问表和行;比如两个更新数据的事务,事务A 更新数据的顺序 为1,2;事务B更新数据的顺序为2,1。这样更可能会造成死锁。

2)大事务拆小。

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

4)降低隔离级别。

5)为表添加合理的索引。

解决死锁:

1)锁等待超时自动回滚事务。

2)innodb还提供了wait-for graph算法来主动进行死锁检测。

3、SQL优化的一般步骤是什么?

答:1)在表中建立索引;

2)不要使用select *;

3)不要在where使用左右两边都是‘%’或者'%abc'的like查询,会导致数据库引擎放弃索引进行全表扫描;

4)(5.5之后会走索引)尽量不要使用in和not in,会导致全表扫描,连续的值使用between,子查询使用exists;

5)尽量不要使用or,会导致全表扫描,使用union代替or;

如下:

SELECT * FROM t_order WHERE id = 1

UNION

SELECT * FROM t_order WHERE id = 3

6)尽量不要在where子句中对字段进行表达式操作,会导致全表扫描;

7)where条件里尽量不要进行null值的判断,会导致全表扫描,定义字段时尽量设置不允许为空;

8)尽量使用小事务;

9)一个表的索引不要超过6个。

10)并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。

11)联合索引部分使用时会遵循最左匹配原则,按顺序生效索引,中间缺失不会对后面的索引生效。

4、怎么看执行计划,如何理解其中各个字段的含义?

答:查看执行计划的作用:在查询前预估查询涉及多少行、使用哪些索引、运行时间。explain select...

type 这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const(主键索引)、eq_reg(唯一索引)、ref(普通索引)、range(范围索引)、index(全表索引)和ALL(未使用索引)

  • 索引

5、为什么需要索引?

答:没有索引的时候只能逐行扫描读取文件,建立索引后可以快速定位到磁盘位置读取。

6、Mysql的索引原理,索引的类型有哪些。

答:Mysql采用B+树。优点是:查询时间是O(log n),因为是排序的,并且有链表相连;树的高度较低(除根节点外每个节点至少有m/2个子);由于数据都在叶子节点,所以只需要遍历叶子节点就能实现整棵树的遍历,不用跨层;不存在hash碰撞。

索引类型:唯一索引、主键索引、普通索引、联合索引、全文索引;

7、覆盖索引。

答:要查询的列刚好与使用的索引列完全一致,mysql直接扫描索引就可返回数据。

8、聚簇索引与非聚簇索引区别。

聚簇索引:数据行的物理顺序(磁盘中的物理地址)与列值(一般是主键列)的逻辑顺序相同,一个表只能拥有一个聚簇索引;查询效率高、修改慢,因为要进行数据重排序。InnoDB引擎自增的主键会把数据自动向后插入,避免插入过程中的排序。

非聚簇索引:物理顺序和逻辑顺序不一致。

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

MyISAM对表存储三个文件:索引文件、数据文件、表字段结构文件;使用非聚集索引,索引的叶节仍然是索引节点,节点定位到数据文件,查询时要多次回表。

InnoDB对表存储两个文件:数据文件、表字段结构文件;使用聚集索引,叶子节点直接存储所有数据。

9、最左前缀匹配。

答:联合索引从最左边开始匹配,因此把辨识度高的放左边。

10、MySQL 5.6中,对索引做了哪些优化吗? 

答:索引下推,例:

people表中(zipcode,lastname,firstname)构成一个索引

SELECT * FROM people WHERE zipcode='95054' AND lastname LIKE '%etrunia%' AND address LIKE '%Main Street%';

如果没有使用索引下推技术,则MySQL会通过zipcode='95054'从存储引擎中查询对应的数据,返回到MySQL服务端,然后MySQL服务端基于lastname LIKE '%etrunia%'和address LIKE '%Main Street%'来判断数据是否符合条件。

如果使用了索引下推技术,则MYSQL首先会返回符合zipcode='95054'的索引,然后根据lastname LIKE '%etrunia%'和address LIKE '%Main Street%'来判断索引是否符合条件。如果符合条件,则根据该索引来定位对应的数据,如果不符合,则直接reject掉。有了索引下推优化,可以在有like条件查询的情况下,减少回表次数。

11、ACID 是什么。

A,atomic,原子性,要么都提交,要么都失败,不能一部分成功,一部分失败。 
C,consistent,一致性,事务开始及结束后,数据的一致性约束没有被破坏 
I,isolation,隔离性,并发事务间相互不影响,互不干扰。 
D,durability,持久性,已经提交的事务对数据库所做的更新必须永久保存。即便发生崩溃,也不能被回滚或数据丢失

12、mysql中in 和exists 区别。

答:IN()查询适合B表数据比A表数据小的情况,IN()查询是从缓存中取数据;外大内小

EXISTS()查询是将主查询的结果集放到子查询中做验证,根据验证结果是true或false来决定主查询数据结果是否得以保存。外小内大。

13、超大分页怎么处理(不用缓存的情况下)?
答:利用索引,将select * from table where age>20 limit 1000000,10改为select * from table a, (select id from table where age>20 limit 100000,10) b where  a.id=b.id;

14、有一个一百万行的文件,内部是购买的商品ID,如何获取到购买最多的前一百个商品?

答:•思路:首先考察的肯定是大数据处理方案,这些数据肯定不能一次性读取到内存,那就需要拆分,将数据分隔处理。假设要分隔为 n 个文件。

•分隔:如果 ID 是整型的话,可以直接采用取模(id % n)的方式;如果 ID 是字符串可以先计算 hash 值然后再取模(hash(x) % n)的方式,将相同 ID 的商品分到同一个文件中。

•针对每个小文件进行 top100的排序,返回购买最多的100个商品 ID

•根据 n 个文件中的100个 ID,在进行一次排序,即可得到需要的数据。

15、数据库如何实现 rollback 的?

答:数据库在写入数据之前是先将对数据的改动写入 redo log 和 undo log,然后在操作数据,如果成功提交事务就会讲操作写入磁盘;如果失败就会根据redo log 和 undo log 逆向还原到事务操作之前的状态。

16、数据库三范式。

答:第一范式:数据库表的每一列都是不可分割的原子数据项;第二范式:非主属性必须完全依赖于主属性(1NF基础上消除非主属性对主属性的部分依赖);第三范式:非主属性不依赖于其它非主属性(2NF基础上消除传递依赖)。

17、sql。

答:1)一个订单表统计今日有多个客户购买:

select count(distinct customer) from order_table where to_days(order_time) = to_days(now());  

2)将用户购买的多笔订单合成一条数据:

select group_concat(order_id) as orderlist from order_table where user_id=:userId group by user_id;

3)统计今日交易额:

select sum(order_money) from order_table where to_days(order_time) = to_days(now());  

18、java事务中抛Exception会触发回滚吗?

答:取决于何种类型的Exception,只有在RuntimeException时才会触发。

19、版本链。

答:InnoDB聚簇索引有两个隐藏列,一个是记录事务id,另一个是记录上个版本undo log的地址,由此在undo log中就形成了版本链。

20、MVCC。

答:MVCC是一种多版本并发控制机制,锁机制可以控制并发操作,但是其系统开销较大,而MVCC可以在大多数情况下代替行级锁。类似乐观锁的实现机制,每行数据都存在一个版本号,更新时修改,修改时copy当前版本,保存时比较版本号,成功则覆盖,失败则放弃copy。

MVCC只适合读已提交和可重复读,串行化本身会加表锁。

Select时MVCC在读已提交的和可重复的readView策略不同,读已提交会每次查询都会生成一个独立的ReadView,所以会读取到最新的已提交的记录;但是可重复读则在第一次读时生成一个ReadView,之后复用。

21、数据库性能的瓶颈

答:连接数、处理能力、存储容量。

22、数据库优化方案

答:SQL优化、建索引、分区、主从复制/读写分离、集群、向上扩展(硬件扩展)、分库分表、大数据。

23、什么时候考虑切分

答:能不切分尽量不要切分、数据量过大(单表超过500W~1000W  2G数据)、数据增长过快、需要对某些字段进行切分、处于安全性和可用性的考虑。

24、数据切分以后的问题以及解决方案

答:1)事务一致性问题,XA规范;2)跨节点的关联查询问题,全局表、字段冗余、ER分片(订单表,订单明细表落同一个库  )、数据组装;3)跨节点排序、分页,结果汇总做二次计算(解决办法一般都放弃使用它自身的这种排序或者分页,使用nosql工具如 MongoDB,es,solr等创建二级索引。);4)全局主键,uuid、数据表维护(锁表)、全局ID生成系统;5)数据迁移和扩容。

25、mycat的一些问题:

答:1)非分片字段查询:资源消耗极大;2)分页排序:资源消耗极大;3)任意表join:必须确保数据落在同一个库;4)分布式事务:弱XA一致性。

26、mysql默认事务隔离级别?

答:mysql在5.0版本之前,binlog只支持只支持STATEMENT这种格式,而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有bug的:

就是在master上执行的顺序为先删后插!而此时binlog为STATEMENT格式,它记录的顺序为先插后删!从(slave)同步的是binglog,因此从机执行的顺序和主机不一致!就会出现主从不一致!

因此Mysql将可重复读(Repeatable Read)作为默认的隔离级别。

之后的版本可以将binlog修改为row格式,可以使用读已提交作为默认隔离级别,原因:

1)在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多!

2)在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行

3)在RC隔离级别下,半一致性读(semi-consistent)特性增加了update操作的并发性

所谓半一致性读就是,一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,由MySQL上层判断此版本是否满足update的where条件。若满足(需要更新),则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)!

27、mysql如何实现可重复度?

答:使用MVCC的控制方式 ,即Mutil-Version Concurrency Control,多版本并发控制,类似于乐观锁的一种实现方式。

此时如果其他写事务修改了这条数据,那么这条数据的版本号就会加1,从而比当前读事务的版本号高,读事务自然而然的就读不到更新后的数据了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值