什么是最大努力通知?

典型回答 所谓最大努力通知,换句话说就是并不保证100%通知到。这种分布式事务的方案,通常也是借助异步消息进行通知的。 发送者将消息发送给消息队列,接收者从消息队列中消费消息。在这个过程中,如果出现了网络通信故障或者消息队列发生了故障,就有可能导致消息传递失败,即消息被丢失。因此,最大努力通知无法保证每个接收者都能成功接收到消息,但是可以尽最大努力去通知。 下面是一个简单的例子来说明最大努力通知的过程。假设有一个在线商城系统,顾客可以下订单购买商品。当顾客成功下单后,通知顾客订单已经确认。这个通知就可以采用最大努力通知的方式。 顾客下单后,商城订单系统会生成订单并记录订单信息。 商城订单系统通过最大努力通知机制,将订单确认通知发送给用户通知服务。 用户通知服务把下单消息通过电子邮件发送给用户。 商城系统不会等待顾客的确认,而是将通知放入消息队列中,并尽力发送通知。 如果通知发送成功,那就很好,顾客会尽快收到订单确认邮件。但如果由于网络问题、电子邮件服务器问题或其他原因导致通知发送失败,商城系统可能会做一些尝试,尽可能的通知,重试多次后还是不成功,则不再发送 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @startuml autonumber actor "用户" as User User -> 商城系统: 下单 商城系统 -> 订单系统: 创建订单 订单系统 --> 消息中间件: 下单成功消息 alt 消息发送成功 消息中间件 --> 用户通知服务: 下单成功消息 else 消息发送失败 订单系统 --> 消息中间件: 重试3次 end 用户通知服务 -> User: 邮件通知 @enduml 需要注意的是,在最大努力通知的过程中,可能会出现消息重复发送的情况,也可能会出现消息丢失的情况。因此,在设计最大努力通知系统时,需要根据实际业务需求和风险承受能力来确定最大努力通知的策略和重试次数,以及对消息进行去重等处理。 ...

March 22, 2026 · 1 min · santu

分布式ID生成方案都有哪些?

典型回答 在单体应用中,我们可以通过数据库的主键ID来生成唯一的ID,但是如果数据量变大,就需要进行分库分表,在分库分表之后,如何生成一个全局唯一的ID,就是一个关键的问题。 通常情况下,对于分布式ID来说,我们一般希望他具有以下几个特点: 全局唯一:必须保证全局唯一性,这个是最基本的要求。 高性能&高可用:需要保证ID的生成是稳定且高效的。 递增:根据不同的业务情况,有的会要求生成的ID呈递增趋势,也有的要求必须单调递增(后一个ID必须比前一个大),也有的没有严格要求。 通常,在分布式ID的生成方案主要有以下6种: UUID 数据库自增ID 号段模式 基于Redis 实现 雪花算法 第三方ID生成工具 扩展知识 UUID UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。 标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12),共32个字符,通常由以下几部分的组合而成:当前日期和时间,时钟序列,全局唯一的IEEE机器识别号 UUID的优点就是他的性能比较高,不依赖网络,本地就可以生成,使用起来也比较简单。 但是他也有两个比较明显的缺点,那就是长度过长和没有任何含义。长度自然不必说,他有32位16进制数字。对于"550e8400-e29b-41d4-a716-446655440000"这个字符串来说,我想任何一个程序员都看不出其表达的含义。一旦使用它作为全局唯一标识,就意味着在日后的问题排查和开发调试过程中会遇到很大的困难。 ✅什么是UUID,能保证唯一吗? 用UUID当做分布式ID,存在着不适合范围查询、不方便展示以及查询效率低等问题。 ✅uuid和自增id做主键哪个好,为什么? 数据库自增 分布式ID也可以使用数据库的自增ID,但是这种实现中就要求一定是一个单库单表才能保证ID自增且不重复,这就带来了一个单点故障的问题。 一旦这个数据库挂了,那整个分布式ID的生成服务就挂了。而且还存在一个性能问题,如果高并发访问数据库的话,就会带来阻塞问题。 号段模式 号段模式是在数据库的基础上,为了解决性能问题而产生的一种方案。他的意思就是每次去数据库中取ID的时候取出来一批,并放在缓存中,然后下一次生成新ID的时候就从缓存中取。这一批用完了再去数据库中拿新的。 ✅详细介绍下号段模式生成分布式ID的原理和优缺点? 而为了防止多个实例之间发生冲突,需要采用号段的方式,即给每个客户端发放的时候按号段分开,如客户端A取的号段是1-1000,客户端B取的是1001-2000,客户端C取的是2001-3000。当客户端A用完之后,再来取的时候取到的是3001-4000。 号段模式的好处是在同一个客户端中,生成的ID是顺序递增的。并且不需要频繁的访问数据库,也能提升获取ID的性能。缺点是没办法保证全局顺序递增,也存在数据库的单点故障问题。 其实很多分库分表的中间件的主键ID的生成,主要采用的也是号段模式,如TDDL Sequence Redis 实现 基于数据库可以实现,那么基于Redis也是可以的,我们可以依赖Redis的incr命令实现ID的原子性自增。 Redis的好处就是可以借助集群解决单点故障的问题,并且他基于内存性能也比较高。 但是Redis存在数据丢失的情况,无论是那种持久化机制,都无法完全避免。 ✅Redis的持久化机制是怎样的? 雪花算法 什么是雪花算法,怎么保证不重复的? 第三方工具 除了以上方案以外,还有一些第三方的工具可以用来实现分布式ID,如百度的UidGenerator、美团的Leaf以及滴滴的Tinyid等等。 这些框架在功能上有的是整合了我们前面提到的多种实现方式,有的是针对不同的方式做了改进,如解决雪花算法的时钟拨回问题等。

March 22, 2026 · 1 min · santu

怎么实现分布式Session?

扩展知识 在分布式系统中,我们的应用可能是以集群形式对外提供服务的,有可能出现在A服务器登录后,用户下一次访问的时候请求到B服务器,就需要有一个分布式的Sesssion来告诉B服务器用户是登录过的,并且需要拿到用户的登录信息。 在业内,实现分布式Session通常有以下几个方案: 客户端存储:用户登录后,将Session信息保存在客户端,用户在每次请求的时候,通过客户端的cookie把session信息带过来。这个方案因为要把session暴露给客户端,存在安全风险。 基于分布式存储(最常用):将Session数据保存在分布式存储系统中,如分布式文件系统、分布式数据库等。不同服务器可以共享同一个分布式存储,通过Session ID查找对应的Session数据。唯一的缺点就是需要依赖第三方存储,如Redis、数据库等。 粘性Session:这个方案指的是把一个用户固定的路由到指定的机器上,这样只需要这台服务器中保存了session即可,不需要做分布式存储。但是这个存在的问题就是可能存在单点故障的问题。 Session复制:当用户的Session在某个服务器上产生之后,通过复制的机制,将他同步到其他的服务器中。这个方案的缺点是有可能有延迟。 Tomcat支持Session复制,配置方式可以参考官方文档:https://tomcat.apache.org/tomcat-8.0-doc/cluster-howto.html Spring中也提供了对Session管理的支持——Spring Session,他集成了很多Session共享的方案,如基于Redis、基于数据库等。

March 22, 2026 · 1 min · santu

分布式命名方案都有哪些?

典型回答 命名服务,就是帮助我们对资源进行命名的服务,命名的目的当然是为了更好的定位了。这里所提到的资源在不同场景中包括但不限于计算机(主机)名和地址、应用提供的服务的地址或者远程对象等。 一些比较常见的分布式框架(RPC、RMI)等都需要用到命名服务,如何解决分布式场景中的统一命名是一个至关重要的话题。 常见的命名方案有JNDI、数据库自增ID、UUID以及基于zookeeper的命名服务来实现。 扩展知识 JNDI 要介绍命名服务,不得不提 Java 命名和目录接口(Java Naming and Directory Interface,JNDI),他是J2EE中重要的规范之一,标准的J2EE容器都提供了对JNDI规范的实现。 在没有JNDI的场景中,我们要配置一个JDBC驱动链接数据库时我们需要做以下操作: 1 2 Class.forName("com.mysql.jdbc.Driver"); Connection conn=DriverManager.getConnection("jdbc:mysql://DBName?user=hollis&password=hollischuang"); 上面的代码中,把数据库链接相关的字符串直接写到了代码中,这不是一个好的做法。有过web开发经验的人都知道,在真正的web开发中我们并不需要这样定义JDBC的连接,我们一般都是把哪些固定的字符串配置到配置文件中,然后在代码中直接从配置中读取。甚至有很多数据库处理的框架(Hibernate\mybatis)会帮我们把创建数据库链接等操作全部都封装好。 使用 JNDI 得到数据源: 1 2 3 4 Context ctx=new InitialContext(); Object datasourceRef=ctx.lookup("java:comp/env/jdbc/mydatasource"); DataSource ds=(Datasource)datasourceRef; Connection c=ds.getConnection(); 为了让 JNDI 解析 java:comp/env/jdbc/mydatasource 引用,部署人员必须把 标签插入 web.xml 文件(Web 应用程序的部署描述符)。 标签的意思就是“这个组件依赖于外部资源”。 1 2 3 4 5 6 <resource-ref> <description>Dollys DataSource</description> <res-ref-name>jdbc/mydatasource</res-ref-name> <res-ref-type>javax.sql.DataSource</res-ref-type> <res-auth>Container</res-auth> </resource-ref> 上面介绍的JNDI是一种Java的命名服务。他充分的反映出命名服务的特点——对某一资源进行命名,然后通过名称来定位唯一的资源。 到这里,我们可以确定的是:命名服务的目的是定义一个唯一的名字。这个名字的作用是可以用来定义唯一的资源。那么,我们想一想,在日常开发中我们如何给一组资源中的每一个某一个进行一个唯一的命名呢?在数据库开发中,通常有两种方案:自增的ID和UUID。 数据库自增ID 在数据库中,为了标识唯一记录,可以使用自增ID,只要指定某个字段是自增的,那么数据库就会帮我们维护这个字段的自增。不同数据库的实现原理不一样,即使是MySql数据库,不同的引擎的实现方式也不尽相同。InnoDB 中AUTO_INCREMENT的实现原理可以参考:innodb-auto-increment-handling ...

March 22, 2026 · 1 min · santu

如何解决接口幂等的问题?

典型回答 所谓幂等 ,一般分为两种,请求幂等和业务幂等,我们在软件开发领域,一般都是说的业务幂等。 请求幂等:每次请求,如果参数一样,结果也要一样。 业务幂等:同一次业务请求,在拿到最终状态之后的每次请求,结果要保证一样。在没拿到最终状态之前,每一次请求需要正常执行业务逻辑,直到推进到最终状态。 比如我们通常是这么做业务幂等的:一次支付请求,如果支付返回处理中,或者系统异常等,我们需要重试,继续调用,直到他明确的返回支付成功,或者明确的无法成功的支付失败结果。 一般来说,在幂等请求中,应该如下返回: 1 2 success = true responseCode = DUPLICATED 这样既能让别人知道是成功了,也能知道是因为幂等而导致的成功。 想要实现幂等,最简单的办法就是先查询一把本次操作是否处理过,如果已经处理成功,则直接返回查询到的结果,否则就去处理业务操作。 但是这个方案存在一个关键的问题,那就是如果出现了并发,就会出现多个接口查询的时候发现都没处理过,然后就都去执行业务操作,这时候就出现了并发问题。 根据并发出现的概率大小,我们分为不同的方案介绍。如果想要完整方案,直接跳过低并发,去看"一锁、二判、三更新“这个高并发方案就好了。 低并发场景下的方案 如果业务是低并发的话,有一种方案,那就是基于数据库的唯一性约束做幂等处理,简单点的伪代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public OrderResponse apply(OrderRequest request) { OrderResponse response = new OrderResponse(); try { OrderDTO orderDTO = orderService.create(request); response.setSuccess(true); response.setData(orderDTO); return response; } catch (DuplicateKeyException e) { // 捕获唯一约束冲突,说明该请求已经处理过 OrderDTO orderDTO = orderService.queryOrder(request.getProduct(), request.getIdentifier()); response.setSuccess(true); response.setResponseCode("DUPLICATED"); response.setData(orderDTO); } } 但是这个方案他有几个缺点,比如他依赖你的操作中需要有insert,这样才能触发唯一性约束的冲突。他需要依靠异常来控制业务流程等等。 ...

March 22, 2026 · 1 min · santu

定时任务扫表的缺点有什么?

典型回答 ✅如何基于本地消息表实现分布式事务? 本地消息表的分布式事务方案是依赖本地消息表,然后通过定时任务扫表的方式来实现的最终一致性。那么这个方案,整体上来看,在以下几个方面是有问题的: 1、消息堆积扫表慢 2、集中式扫表会影响正常业务 3、定时扫表存在延迟问题 那么,这几个问题,该如何解决呢? 消息堆积,扫表慢 随着本地消息表中的数据量越来越大,通过定时任务扫表的方式会越来越慢,那么想要解决这个问题,首先可以考虑加索引。 我们可以在state字段上增加一个索引,虽然这个字段的区分度不高,但是一般来说,这张表中,SUCCESS的数据量占90%,而INIT的数据量只占10%,而我们扫表的时候只关心INIT即可,所以增加索引后,扫表的效率是可以大大提升的。 ✅区分度不高的字段建索引一定没用吗? 其次,可以考虑多线程并发扫表,这里可以考虑采用线程池,在任务中开多个线程并发的从数据库中扫描数据进行处理。 但是这样做,会带来一个问题,那就是多个线程之间如何做好隔离,如何确保不会出现并发导致同一条记录被多个线程执行多次呢? 首先最基本的保障,扫表之后的处理逻辑要做好幂等控制,一旦出现了重复的情况,下游也能因为做了幂等而不会重复处理。 除此以外,在扫表的时候,可以通过分段的思想进行数据隔离。举个例子: 1 2 3 4 5 6 7 8 9 10 11 Long minId = messageService.getMinInitId(); for(int i=1;i<= threadPool.size();i++){ Long maxId = minId + segmentSize()*i; List<Message> messages = messageService.scanInitMessages(minId,maxId); proccee(messages); minId = maxId + 1; } 像上面的例子中,假设有10个线程,那么第一个线程就扫描ID处于0-1000的数据,第二个线程扫描1001-2000的数据,第三个线程扫描2001-3000的数据。这样以此类推,线程之间通过分段的方式就做好了隔离,可以避免同一个数据被多个线程扫描到。 这个做法,有个小问题,那就是INIT的数据的ID可能不是连续的,那么就需要考虑其他的分段方式,比如在时间表中增加一个业务ID,然后根据这个biz_id做分片也可以。 ...

March 22, 2026 · 2 min · santu

实现一个分布式锁需要考虑哪些问题?

典型回答 想要实现一个分布式锁,一般需要考虑哪些问题?一般来说应该从以下几个方面来考虑: 互斥性 一个分布式锁,最基本的要求,就是要具备互斥性,同一时间只能有一个线程获取到锁。否则就会出现并发问题。 在分布式场景中,想要实现一个分布式锁,必须要依赖一个第三方的分布式组件,比如数据库、Redis或者Zookeeper。借助自带的锁机制、单线程、互斥性等特点来实现互斥性。 避免死锁 锁的死锁问题使我们不得不考虑的。尤其在在分布式环境中,由于网络延迟、节点故障等原因,会出现死锁的概率就会更高。所以我们在设计分布式锁的时候,一般都会设置一定的超时机制和死锁检测策略。 阻塞&非阻塞 根据加锁失败后是否阻塞持续自旋加锁,分布式锁可以分为阻塞锁和非阻塞锁。一般来说非阻塞锁用的比较多。像我们常用的Redis的分布式锁就是非阻塞锁。而使用数据库悲观锁 for update实现的可能就是个阻塞锁, 这个要根据业务的具体情况来做选择和设计 可重入 一个线程,拿到锁之后,是否可以在未释放时重新获得锁。这就是可重入的特性了。可重入的锁不仅可以提升加锁效率,也能降低死锁的概率。 而且在有些业务场景中,对是否可以重入也会有一些要求。所以这个也是需要重点考量的。 锁的性能 加锁性能是很重要的,尤其是分布式锁。因为在用分布式锁的场景一般并发较高,而如果分布式锁自身的性能差的话,对业务来说也是不可接受的。所以好的性能更重要。 可靠性 一个分布式锁是否可靠,很重要,一旦他不可靠了,就可能会出现重复加锁导致并发问题。这也是为什么Redis的分布式锁从SetNX到Redisson再到RedLock的重要原因。 其他 实现一个分布式锁,除了上面这些,还有一些其他的东西需要考虑,比如实现的复杂度、易用性等,都很重要的。

March 22, 2026 · 1 min · santu

什么是Canal,他的工作原理是什么?

典型回答 Canal是阿里巴巴开源的数据同步工具,他是一个用于数据库的数据变更捕获,它可以捕获数据库中的变更操作(如插入、更新、删除),并将这些变更以实时流的方式发布给其他系统进行消费。主要应用场景之一是数据库的增量数据同步,通常在数据仓库、缓存、搜索引擎等系统中使用。 我们经常会在数据迁移、数据同步的场景中需要用到canal,比如分库分表时买家表同步出一张卖家表来,比如我们要把mysql中的数据同步到es中等等,这些场景,canal都能大显神威。 Canal的实现原理其实挺简单的: Canal会模拟 MySQL slave 的交互协议,把自己伪装成为一个 MySQL slave ,向 MySQL master 发送dump 协议,MySQL master 收到 dump 请求后,会被这个伪装的slave ( canal )拉取这些 binlog ,canal 把 binlog 解析成流,然后对接到各个后续的消费者中,如ES、数据库等。 MySQL主从复制: MySQL主从复制的过程

March 22, 2026 · 1 min · santu

什么是分布式数据库,有什么优势?

典型回答 分布式数据库,即所谓的NewSQL。主要代表TiDB(pingcap)、OceanBase(蚂蚁)、Spanner(google)。与传统关系型数据库相比: 性能高:在亿级别以上,读和写都会高于传统关系型数据库(MySQL、SQLServer等),在亿级别以下略逊色与传统关系型数据库 可维护性:TiDB、OceanBase都以开源的形式存在,其生态也比较贴近于现代数据库的需求(如完全支持云原生),有较好的社区文档、企业免费的培训课程 可靠性:由于分布式的特性,通过副本冗余的方式提升整个集群可靠性。同样由于分布式的特性,无法严格且完全的实现真正意义上的ACID,从事务的角度来看可靠性降低了。 可扩展性:分布式的特性就在于近乎无限的水平可扩展,增加集群节点数量可大幅度提高集群的QPS和存储能力。Spanner甚至实现了全球部署。 用户体验:兼容大部分SQL标准,但也是由于分布式的原因,很多传统关系型数据库的特性(SQL语法、其他功能)无法支持,如:无法保证自增id的连续性,有限的支持事务的强一致性(对性能略有损失),天生分布式不支持单机部署(小型业务无法使用)。TiDB与MySQL思想类似,也存在存储引擎概念,通过存储引擎实现了OLAP和OLTP两种模式,插入数据能随时进行在线事务操作,也可以进行实时的离线分析操作。 OLTP (联机事务处理):支持日常业务运营中的高频、短时、原子性的事务操作。常见OLTP数据库: MySQL, PostgreSQL, Oracle, SQL Server, MariaDB, DB2 (关系型数据库为主)。 OLAP (联机分析处理):支持复杂的数据分析、数据挖掘、商业智能和决策支持。常见的OLAP数据库: 数据仓库 (如 Amazon Redshift, Google BigQuery, Snowflake, Teradata)、列式数据库 (如 Vertica)、OLAP 多维数据库 (如 Microsoft Analysis Services - SSAS, SAP BW) 扩展知识 有了MySQL为啥还要有分布式数据库? 其实能用上分布式数据库的公司数据量和QPS已经非常庞大了。很难通过MySQL解决数据量和高QPS的问题了。 MySQL在应对大数据量的时候通过采用分库分表的方案,应对大量读QPS的场景方案是进行读写分离,而写TPS很难提高,分库分表会带来诸多问题。 ✅分库分表后会带来哪些问题? 读写分离会带来的问题: 主从复制存在延迟,存在写后读依赖的场景,只能强制读主,进而对主产生了压力 读写分离需要应用感知主和从的存在 使用cluster方案维护主从切换成本高 纯主从复制方案单实例写tps有限,从只能提高读的并发度 因此,MySQL官方提供了Cluster方案,但对主从选举时采用了复杂的paxos算法,可维护性和性能都大幅度降低,业内并不买账,依旧使用传统的分库分表、主从复制。 总的来说MySQL可维护性和可扩展性较差。 在分布式数据库中,从TiDB来说,重点解决了可维护性和可扩展性的问题。 TiDB通过pd节点管理数据的自动分片,调度,倾斜自动迁移,热点自动迁移,自动选主(采用轻量的raft协议)等功能。数据节点单机使用LSM树作为底层存储结构,大幅度提升机械磁盘写的IOPS。 本质上内部分片采用范围分片的策略,然后整个集群构成了一个树状结构,进行聚合,分页查询。分布式中时间是一个很重要的东西,无论是事务的id生成,还是MVCC(多版本并发控制)都依托于时间,在计算机中即使是采用NTP服务也无法解决时间漂移的问题。 TiDB通过pd节点管理时间,计算节点、数据节点都从pd节点进行同步。而Spanner采用了一个硬件设备(艳原子钟)确保数据中心的时间非常精准,从而实现了全球数据库部署,即跨国际的时间同步策略保证了事务的正确。 题外话: Leslie Lamport 在 1978年提出了分布式逻辑时钟算法,有兴趣可以读读其论文 由于是分布式数据库,有限支持id自增,有限支持强一致的事务(对性能略有损失)。其弊端: 天生分布式,不支持单机部署,基本不适合小业务量的应用 由于分布式,事务隔离级别弱,无法完全实现ACID 实测在亿级以下的读写请求比mysql的性能略低一些 总的来说,虽然tidb承诺金融级别的分布式数据库,但是选择用在核心链路上还要深思熟虑。用在支付链路上曾遇到过因为tidb 慢查询导致整个集群崩溃,其原因是tidb查询分析器有bug导致选择错了查询计划。 分布式数据库是技术和商业发展的必然,人类产生的数据量越来越大,传统关系型数据库也在逐渐往分布式数据库上过度(如MySQL的Cluster方案),人们应该逐渐接受分布式带来的问题,接受并能够解决事务不是那么强一致。 值得一读的论文: ...

March 22, 2026 · 1 min · santu

锁和分布式锁的核心区别是什么?

典型回答 锁和分布式锁的核心区别主要在于作用范围和适用场景。 锁,我们常用的就是synchronized,ReentrantLock等,他们通常是在单个进程内起作用的同步机制,用于控制对共享资源的访问。多个线程或进程之间可以使用锁来确保对关键资源的互斥访问。 这种锁,主要适用于单体应用,确保在同一JVM进程内多个线程同步共享资源的访问。 分布式锁,作用范围更广泛,可以跨越多个计算机或多个进程,用于协调分布式系统中的不同节点,以确保在全局范围内对共享资源的互斥访问。 分布式锁适用于分布式系统中,各个节点之间需要协调共享资源访问的情况。在分布式环境中,传统的锁机制可能无法有效地处理多节点之间的同步问题,因此需要分布式锁来确保一致性。 所以,普通的锁主要用于单机环境,用于控制同一JVM进程中多个线程对共享资源的互斥访问。而分布式锁则适用于分布式环境,用于协调多个计算机或多个进程之间对共享资源的互斥访问,确保分布式系统的一致性。 ✅分布式锁有几种实现方式? ✅Redis实现分布锁的时候,哪些问题需要考虑?

March 22, 2026 · 1 min · santu

留言给博主