一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 这篇文章是关于Java面试中的分布式架构问题的笔记,包括分布式架构下的Session共享方案、RPC和RMI的理解、分布式ID生成方案、分布式锁解决方案以及分布式事务解决方案。

这里是参考B站上的大佬做的面试题笔记。大家也可以去看视频讲解!!!

文章目录

  • 1、分布式架构下,Session共享有什么方案
  • 2、简述你对RPC、RMI的理解
  • 3、分布式id生成方案
  • 4、分布式锁解决方案
  • 5、分布式事务解决方案

1、分布式架构下,Session共享有什么方案

1、采用无服务状态,抛弃session

2、存入cookie(有安全风险)

3、服务器之间进行session同步,这样可以保证每个服务器上都有全部的session信息,不过当服务器数量比较多的时候,同步是会有延迟甚至同步失败

4、IP绑定策略
使用Nginx(或其他复杂负载均衡硬件)中的IP绑定策略,同一个IP只能在指定的同一个机器访问,但是这样做失去了负载均衡的意义,当挂掉一台服务器的时候,会影响一批用户的使用,风险很大;

5、使用Redis存储
把Session放到Redis中存储,虽然架构上变得复杂,并且访问需要多访问一次Redis,但是这种方案带来的好处也是很大的:

  • 实现了session共享
  • 可以水平扩展(增加Redis服务器)
  • 服务器重启Session不丢失(不过也要注意Session在Redis中的刷新/失效机制);
  • 不仅可以跨服务器Session共享,甚至可以跨平台(例如网页端和APP端)

2、简述你对RPC、RMI的理解

RPC:在本地调用远程的函数,远程过程调用,可以跨语言实现, httpClient

RMI:远程方法调用,java中用于实现RPC的一种机制,RPC的java版本,是J2EE的网路调用机制,跨JVM调用对象的方法,面向对象的思维方式

直接或间接实现接口 java.rmi.Remove 成为存在于服务端的远程对象,供客户端访问并提供一顶的服务
远程对象必须实现java.rmi.server.UniCastRemoteObject类,这样才能保证客户端访问获得远程对象时,该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端。此时客户端所获得的这个拷贝称为”存根“,而服务器端本身已存在的远程对象则称之为”骨架“,其实此时的存根是客户端的一个代理,用于服务器端的通信,而骨架也可以认位是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。

3、分布式id生成方案

uuid

  • 1、当前日期和时间 时间戳
  • 时钟序列。 计数器
  • 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。

优点:代码简单,性能好(本地生成,没有网络消耗),保证唯一(相对而言,重复概率极低可以忽略)

缺点:

  • 每次生成的ID都是无序的,而且不是全数字,且无法保证趋势递增
  • UUID生成的是字符串,字符串存储性能差,查询效率慢,写的时候由于不能产生顺序的append操作,需要进行insert操作,导致频繁的页分裂,这种操作在记录占用空间比较大的情况下,性能下降比较大,还会增加读取磁盘次数
  • UUID长度过长,不适用于存储,耗费数据库性能。
  • ID无一定业务含义,可读性差。
  • 有信息安全问题,有可能泄漏mac地址

数据库自增序列

单机模式:
优点:

  • 实现简单,依靠数据库即可,成本小。
  • ID数字化,单调自增,满足数据库存储和查询性能。
  • 具有一定的业务可读性。(结合业务code)

缺点:

  • 强依赖DB,存在单点问题,如果数据库宕机,则业务不可用。
  • DB生成ID性能有限,单点数据库压力大,无法抗高并发场景。
  • 信息安全问题,比如暴露订单量,url查询改一下id查到别人的订单
    数据库高可用:多主模式做负载,基于序列的起始值和步长设置,不同的初始值,相同的步长,步长大于节点数。

优点:解决了ID生成的单点问题,同时平衡了负载。
缺点:

  • 系统扩容困难:系统定义好步长之后,增加机器之后调整步长困难。
  • 数据库压力大:每次获取一个ID都必须读写一次数据库。
  • 主从同步的时候,电商下单---->支付insert master db select 数据,因为数据同步延迟导致查不到这个数据。加cache(不是最好的解决方式)数据要求比较严谨的话查master主库

leaf-sagmnet
-采用每次获取一个ID区间段的方式来解决,区间段用完之后再去数据库获取新的号段,这样一来就可以大大减轻数据库的压力

核心字段:biz_tag, max_id ,step

biz_tag用来区分业务,max_id表示该biz_tag目前所被分配的ID号段的最大值,step表示每次分配的号段长度,原来每次获取ID都需要访问数据库,现在只需要把Step设置的足够合理如1000,那么现在可以在1000个ID用完之后再去访问数据库

优点:

  • 扩展灵活,性能强能撑起大部分业务场景。
  • ID号码是趋势递增的,满足数据库存储和查询性能要求
  • 可用性高,即使ID生成服务器不可用,也能够使得业务在短时间内可用,为排查问题争取时间。

缺点:

  • 可能存在多个节点同时请求ID区间的情况,依赖DB

对buffer:将获取一个号段的方式优化成获取两个号段,在一个号段用完之后不用立马去更新号段,还有一个缓存号段备用,这样能够有效解决这种冲突问题,而且采用双buffer的方式,在当前号段消耗了10%的时候就去检查下一个号段有没有准备好,如果没有准备好就去更新下一个号段,当前号段用完了就切换到下一个已经缓存好的号段去使用,同时在下一个号段消耗到10%的时候,又去检测下一个号段有没有准备好,如此往复。

优点:

  • 基于JVM存储双buffer的号段,减少了数据库查询,减少了网络依赖,效率更高。

缺点
segment号段长度是固定的,业务量大时可能会频繁更新号段,因为原本分配的号段会一下用完,如果号段长度设置的过长,但凡缓存中有号段没有消耗完,其他节点重新获取的号段与之前相比可能跨度会很大,动态调整Step

基于redis、mongodb、zk等中间件生成

雪花算法:
生成一个64bit的整性数字
第一位符号位固定为0,41位时间戳,10位workid,12位序列号
位数可以有不同实现

优点

  • 每个毫秒值包含的ID值很大,不够可以变动位数来增加,性能佳(依赖workId的实现)。
  • 时间戳值在高位,中间是固定的机器码,自增的序列在低位,整个ID是趋势递增的。
  • 能够根据业务场景数据库节点布置灵活挑战bit位划分,灵活度高。

缺点

  • 强依赖于机器时钟,如果时钟回拨,会导致重复的ID生成,所以一般基于此的算法发现时钟回拨,都会抛异常处理,阻止ID生成,这可能导致服务不可用。

4、分布式锁解决方案

需要这个锁独立于每一个服务之外,而不是在服务里面。
数据库:利用主键冲突控制一次只有一个线程能获取锁,非阻塞、不可重入、单点、失效时间

Zookeeper分布式锁:
zk通过临时节点,解决了死锁的问题,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除,其他客户端自动获取锁。临时顺序节点解决惊群效应。

Redis分布式锁:setNX,单线程处理网络请求,不需要考虑并发安全性
所有服务节点设置相同的key,返回为0,则锁获取失败

setnx问题:

  • 1、早期版本没有超时参数,需要单独设置,存在死锁问题(中途宕机)
  • 2、后期版本提供加锁与设置原子性操作,但是存在任务超时,锁自动释放,导致并发问题,加锁与释放锁不是同一线程问题。

删除锁:判断线程唯一标志,再删除

可重入性及锁续期没有 实现,通过redisson解决(类似AQS的实现,看门狗机制)

redlock:意思的机制都只操作单节点、即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况(redis同步设置可能数据丢失)。redlock从多个节点申请锁,当一半以上节点获取成功,锁才算获取成功,redission有相应的实现。

5、分布式事务解决方案

XA规范:分布式事务规范,定义了分布式事务模型

四个角色:事务管理器(协调者TM)、资源管理器(参与者RM)、应用程序AP、通信资源管理器CRM

全局事务:一个横跨多个数据库的事务,要么全部提交、要么全部回滚

JTA事务时java对XA规范的实现,对应JDBC的单库事务。

两阶段协议:
在这里插入图片描述
第一阶段(prepare):每个参与者执行本地事务但不提交,进入ready状态,并通知协调者已经准备就绪。
第二阶段(commit)当协调者确认每个参与者都read后,通知参与者进行commit操作,如果有参与者fail,则发送rollback指令,各参与者做回滚。

问题:

  • 单点故障:一旦事务管理器出现故障,整个系统不可用(参与者都会阻塞住)
  • 数据不一致:在阶段二,如果事务管理器只发送了部分commit消息,此时网络发生异常,那么只有部分参与者接收到commit信息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
  • 响应时间较长:参与者和协调者资源都被锁住,提交或者回滚之后才能释放
  • 不确定性:当协调者管理器发送commit之后,并且此时只有一个参与者收到了commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。

三阶段协议:主要是针对两阶段的优化,解决了2PC单点故障的问题,但是性能问题和不一致问题仍然没有根本解决
在这里插入图片描述
引入了超时机制解决参与者阻塞的问题,超时后本地提交,2pc只有协调者有超时机制

  • 第一阶段:CanCommit阶段,协调者询问事务参与者,是否有能力完成此次事务。
    如果都返回yes,则进入第二阶段
    有一个返回no或等待响应超时,则中断事务,并向所有参与者发送abort请求

  • 第二阶段:precommit阶段,此时协调者会向所有的参与者发送precommit请求,参与者收到后开始执行事务操作。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈”Ack“表示我已经准备好提交了,并等待协调者的下一步指令。

  • 第三阶段:DoCommit阶段,在阶段二中如果所有的参与者节点都返回Ack,那么协调者就会从”预提交状态“转变为”提交状态“。然后向所有的参与者节点发送”doCommit"请求,参与者节点在收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的Ack消息后完成事务。相反,如果有一个参与者节点未完成PreCommit的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送abort请求,从而中断事务。

TCC(补偿事务):Try、Confirm、Cancel

  • 针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作
    Try操作做业务检查及资源预留,Confirm做业务确认,Cancel实现一个与Try相反的操作既回滚操作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试。

TCC模型对业务的侵入性较强,改造的难度较大,每个操作都需要try、confirm、cancel三个接口实现confirm和cancel接口还必须实现幂等性。

消息队列的事务信息:

  • 发送prepare消息到消息中间件
  • 发送成功后,执行本地事务
    如果事务执行成功,则commit,消息中间件将消息下发至消费端(commit前,消息不会被消费)
    如果事务执行失败,则回滚,消息中间件将这条prepare消息删除
  • 消费端接收到消息进行消费,如果消费失败,则不断重试
相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
英伟达新一代GPU架构(50系列显卡)PyTorch兼容性解决方案
本文记录了在RTX 5070 Ti上运行PyTorch时遇到的CUDA兼容性问题,分析其根源为预编译二进制文件不支持sm_120架构,并提出解决方案:使用PyTorch Nightly版本、更新CUDA工具包至12.8。通过清理环境并安装支持新架构的组件,成功解决兼容性问题。文章总结了深度学习环境中硬件与框架兼容性的关键策略,强调Nightly构建版本和环境一致性的重要性,为开发者提供参考。
385 7
英伟达新一代GPU架构(50系列显卡)PyTorch兼容性解决方案
java面试-基础语法与面向对象
本文介绍了 Java 编程中的几个核心概念。首先,详细区分了方法重载与重写的定义、发生阶段及规则;其次,分析了 `==` 与 `equals` 的区别,强调了基本类型和引用类型的比较方式;接着,对比了 `String`、`StringBuilder` 和 `StringBuffer` 的特性,包括线程安全性和性能差异;最后,讲解了 Java 异常机制,包括自定义异常的实现以及常见非检查异常的类型。这些内容对理解 Java 面向对象编程和实际开发问题解决具有重要意义。
56 15
地铁站内导航系统解决方案:技术架构与核心功能设计解析
本文旨在分享一套地铁站内导航系统技术方案,通过蓝牙Beacon技术与AI算法的结合,解决传统导航定位不准确、路径规划不合理等问题,提升乘客出行体验,同时为地铁运营商提供数据支持与增值服务。 如需获取校地铁站内智能导航系统方案文档可前往文章最下方获取,如有项目合作及技术交流欢迎私信我们哦~
101 1
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
178 14
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
78 13
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
146 9
|
2月前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
203 60
【Java并发】【线程池】带你从0-1入门线程池
|
15天前
|
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。建议先了解ThreadLocal。
53 4
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
92 23