小厂后端十连问(附答案)(二)
7. ThreadLocal的使用场景有哪些?原理?内存泄漏?
ThreadLocal,即线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。
ThreadLocal的应用场景
- 数据库连接池
- 会话管理中使用
ThreadLocal内存结构图:
ThreadLocal原理
- Thread对象中持有一个ThreadLocal.ThreadLocalMap的成员变量。
- ThreadLocalMap内部维护了Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。
- 每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
ThreadLocal 内存泄露问题
先看看一下的TreadLocal的引用示意图哈,
ThreadLocalMap中使用的 key 为 ThreadLocal 的弱引用,如下
弱引用:只要垃圾回收机制一运行,不管JVM的内存空间是否充足,都会回收该对象占用的内存。
弱引用比较容易被回收。因此,如果ThreadLocal(ThreadLocalMap的Key)被垃圾回收器回收了,但是因为ThreadLocalMap生命周期和Thread是一样的,它这时候如果不被回收,就会出现这种情况:ThreadLocalMap的key没了,value还在,这就会造成了内存泄漏问题。
如何解决内存泄漏问题?使用完ThreadLocal后,及时调用remove()方法释放内存空间。
8、kafka是如何保证消息的有序性?
kafka这样保证消息有序性的:
- 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。(全局有序性)
- 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。
大家可以看下消息队列的有序性是怎么推导的哈:
消息的有序性,就是指可以按照消息的发送顺序来消费。有些业务对消息的顺序是有要求的,比如先下单再付款,最后再完成订单,这样等。假设生产者先后产生了两条消息,分别是下单消息(M1),付款消息(M2),M1比M2先产生,如何保证M1比M2先被消费呢。
为了保证消息的顺序性,可以将将M1、M2发送到同一个Server上,当M1发送完收到ack后,M2再发送。如图:
这样还是可能会有问题,因为从MQ服务器到服务端,可能存在网络延迟,虽然M1先发送,但是它比M2晚到。
那还能怎么办才能保证消息的顺序性呢?将M1和M2发往同一个消费者,且发送M1后,等到消费端ACK成功后,才发送M2就得了。
消息队列保证顺序性整体思路就是这样啦。比如Kafka的全局有序消息,就是这种思想的体现: 就是生产者发消息时,1个Topic只能对应1个Partition,一个 Consumer,内部单线程消费。
但是这样吞吐量太低,一般保证消息局部有序即可。在发消息的时候指定Partition Key,Kafka对其进行Hash计算,根据计算结果决定放入哪个Partition。这样Partition Key相同的消息会放在同一个Partition。然后多消费者单线程消费指定的Partition。
9、Nacos的选举机制了解嘛?说下Raft算法?
Nacos作为配置中心的功能是基于Raft算法来实现的。
Raft 算法是分布式系统开发首选的共识算法,它通过“一切以领导者为准”的方式,实现一系列值的共识和各节点日志的一致。
Raft选举过程涉及三种角色和任期(Term):
- Follower:默默地接收和处理来自Leader的消息,当等待Leader心跳信息超时的时候,就主动站出来,推荐自己当Candidate。
- Candidate:向其他节点发送投票请求,通知其他节点来投票,如果赢得了大多数(N/2+1)选票,就晋升Leader。
- Leader:负责处理客户端请求,进行日志复制等操作,每一轮选举的目标就是选出一个领导者;领导者会不断地发送心跳信息,通知其他节点“我是领导者,我还活着,你们不要发起新的选举,不用找个新领导者来替代我。”
- Term:这跟民主社会的选举很像,每一届新的履职期称之为一届任期
领导选举过程
1.在初始时,集群中所有的节点都是Follower状态,都被设定一个随机选举超时时间(一般150ms-300ms):
2.如果Follower在规定的超时时间,都没有收到来自Leader的心跳,它就发起选举:将自己的状态切为 Candidate,增加自己的任期编号,然后向集群中的其它Follower节点发送请求,询问其是否选举自己成为Leader:
3.其他节点收到候选人A的请求投票消息后,如果在编号为1的这届任期内还没有进行过投票,那么它将把选票投给节点A,并增加自己的任期编号:
4.当收到来自集群中过半节点的接受投票后,A节点即成为本届任期内 Leader,他将周期性地发送心跳消息,通知其他节点我是Leader,阻止Follower发起新的选举:
10、聊一聊TCC补偿机制
TCC是分布式事务的一种解决方案。它采用了补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC(Try-Confirm-Cancel)包括三段流程:
- try阶段:尝试去执行,完成所有业务的一致性检查,预留必须的业务资源。
- Confirm阶段:该阶段对业务进行确认提交,不做任何检查,因为try阶段已经检查过了,默认Confirm阶段是不会出错的。
- Cancel 阶段:若业务执行失败,则进入该阶段,它会释放try阶段占用的所有业务资源,并回滚Confirm阶段执行的所有操作。
下面再拿用户下单购买礼物作为例子来模拟TCC实现分布式事务的过程:
假设用户A余额为100金币,拥有的礼物为5朵。A花了10个金币,下订单,购买10朵玫瑰。余额、订单、礼物都在不同数据库。
TCC的Try阶段:
- 生成一条订单记录,订单状态为待确认。
- 将用户A的账户金币中余额更新为90,冻结金币为10(预留业务资源)
- 将用户的礼物数量为5,预增加数量为10。
- Try成功之后,便进入Confirm阶段
- Try过程发生任何异常,均进入Cancel阶段
TCC的Confirm阶段: - 订单状态更新为已支付
- 更新用户余额为90,可冻结为0
- 用户礼物数量更新为15,预增加为0
- Confirm过程发生任何异常,均进入Cancel阶段
- Confirm过程执行成功,则该事务结束
TCC的Cancel阶段: - 修改订单状态为已取消
- 更新用户余额回100
- 更新用户礼物数量为5
- TCC的优点是可以自定义数据库操作的粒度,降低了锁冲突,可以提升性能
- TCC的缺点是应用侵入性强,需要根据网络、系统故障等不同失败原因实现不同的回滚策略,实现难度大,一般借助TCC开源框架,ByteTCC,TCC-transaction等。
文章转自公众号:小白debug