(Java面试题分享)万里长征-02-跟谁学

问题

1.LeetCode662 二叉树最长直径

662. 二叉树最大宽度 - 力扣(LeetCode)

2.LeetCode215 TOPK

215. 数组中的第K个最大元素 - 力扣(LeetCode)

3.秒杀场景设计

GPT问答

Spring cloud项目现在要做一个秒杀场景的需求,给我从app端到服务端设计一套可用的方案

在设计一个秒杀系统的时候,需要考虑以下几个关键因素:高并发、系统可用性和数据一致性。

下面是一个基于Spring Cloud的秒杀系统的设计方案:

  1. 前端(App端):

    使用请求缓冲和请求限流策略,例如令牌桶算法,防止大量请求同时涌向服务端造成服务崩溃。同时可以考虑使用CDN和浏览器缓存技术降低服务器负载。

  2. 后端(服务端):

    服务端设计需要多层缓冲和优化:

    • Nginx负载均衡: Nginx可以实现反向代理和负载均衡,将流量合理分配到各个服务节点。
    • Spring Cloud Gateway: 使用Spring Cloud Gateway进行路由转发和请求过滤。
    • 分布式Session: 可以使用Spring Session将Session存储在Redis中,实现分布式Session。
    • 分布式锁: 在秒杀核心业务处理时,为了防止超卖,需要使用分布式锁。可以使用Redisson实现基于Redis的分布式锁。
    • 消息队列: 将创建订单的请求进行异步处理,提高系统处理能力。可以使用RabbitMQ或Kafka。
    • 数据库: 为了防止数据库的读写压力过大,可以使用读写分离的策略。在写方面,可以使用数据库的乐观锁防止超卖。在读方面,可以将商品的库存数量提前预热到Redis中,请求直接从Redis读取数据。

具体的秒杀流程可以设计如下:

  1. 用户发起秒杀请求,请求首先经过Nginx,由Nginx做负载均衡将请求转发到Spring Cloud Gateway。
  2. Spring Cloud Gateway根据路由规则将请求转发到具体的服务。
  3. 服务收到请求后,首先从Redis中获取用户的Session信息,然后从Redis中获取商品的库存信息。
  4. 如果库存充足,服务先使用Redisson获取分布式锁,然后在Redis中减少商品的库存数量。
  5. 减库存成功后,服务将创建订单的请求通过消息队列异步发送到订单服务。
  6. 订单服务收到消息后,使用数据库的乐观锁机制创建订单。
  7. 订单创建成功后,返回秒杀成功的消息给用户。

这只是一个大概的框架,具体的实现需要根据实际的业务需求进行调整。


网上资料

建议直接看极海方案,埋个坑输出一个博客
https://bothsavage.github.io/article/240415-secondkill

【高级请进】核心业务挑战与方案落地!技术与业务如何权衡!P7级项目如何阐述!_哔哩哔哩_bilibili

总结

  • gpt又给我玩假大空
  • AC在高并发的保证,又要可用,又要一致,当然我们追求最终一致
  • 相同实例支撑的接口能缓存就要缓存
  • 分布式锁嘛,这个都说烂了
  • app也要做限流,在app做令牌桶限流确实是一个很好的方案,也可以当作成一个通用方案,还能防止app的死循环请求bug
  • 详细说明等我再把极海的视频梳理为文章

4.数据量并发度扩大十倍性能瓶颈

GPT问答

我的Spring cloud + mysql + redis项目,目前用户激增,访问量和数据量都扩大了100倍,你认为我的系统瓶颈在哪,怎么解决

在你的情况下,可能会遇到以下瓶颈:

  1. 硬件资源限制:例如,服务器 CPU、内存、I/O 等,可能会在高并发的情况下,成为瓶颈。
  2. 数据库瓶颈:当访问量和数据量增加时,数据库可能会变得过于繁忙,导致系统性能下降。这可能是由于查询效率低下、索引不合理、表设计不合理等原因造成的。
  3. Redis 瓶颈:Redis 是单线程的,因此在高并发场景下,可能会成为性能瓶颈。
  4. 网络瓶颈:如果网络带宽不足,或者网络延迟太高,也可能会成为瓶颈。

以下是可能的解决方案:

  1. 优化硬件资源:根据系统的需求增加硬件资源,例如增加服务器的 CPU、内存等。
  2. 数据库优化
    • 索引优化:检查查询是否都有合适的索引支持,没有的话,添加相应索引。
    • 查询优化:避免全表扫描,尽可能减少 JOIN 操作,避免使用 NOT IN 和 <> 操作符等。
    • 分库分表:当单表数据量过大时,可以考虑分库分表,以减少单个数据库的负载。
    • 读写分离:将读和写的操作分开,可以有效减轻数据库的压力。
    • 使用数据库中间件:例如 MyCAT、Sharding-Sphere 等。
  3. 优化 Redis
    • 使用 Redis 集群:通过分片将数据存储在多个 Redis 实例中,可以提高系统的并发处理能力。
    • 优化 Redis 配置:根据实际需求,优化 Redis 的配置参数。
  4. 服务的弹性扩展
    • 使用 Spring Cloud 的微服务架构:可以根据需求,动态地增加或减少服务实例。
    • 使用负载均衡:将请求均匀地分摊到多个服务器上,降低了每个服务器的负载。
  5. 使用 CDN 和缓存:使用 CDN 可以减少服务器的流量压力,使用缓存可以减少数据库的访问压力。
  6. 网络优化:提高网络带宽,优化网络结构,减少网络延迟。

每个系统的瓶颈可能都不同,所以需要结合实际情况,定位并解决瓶颈。


总结

  • 差不多但是说的比较宽泛
  • 就是RT要继续维持不要增高,分析热点请求加缓存等
  • 还有就是要减少io,其中mysql的优化是io减少的重要方式
  • 还要要追寻一致性问题,许多代码在写的时候没有考虑到高并发,该优化的优化,像支付订单等追求一致性的地方要提高RT的同时去追求最终一致性

5.MQ保证不丢失kafka快重平衡

GPT问答

rabbitmq是如何保证消息不丢失的,他的持久化策略是啥

RabbitMQ 提供了多种保证消息不会丢失的机制,包括消息持久化,队列持久化,消息确认等。

  1. 消息持久化:消息持久化可以通过设置 delivery_mode=2 来实现,当消息被持久化后,即使 RabbitMQ 服务重启,消息也不会丢失,因为他们被存储在磁盘上。但是,这并不能完全保证消息的持久性。因为,当你发布一个消息并且 RabbitMQ 返回一个确认,这个消息可能还没被保存到磁盘。RabbitMQ 可能只是将消息保存在缓存中,并没有真正的写入磁盘。
  2. 队列持久化:要持久化队列,你需要在声明队列的时候,将 durable 参数设置为 true。这样,即使 RabbitMQ 服务重启,队列的定义也不会丢失。
  3. 消息确认:RabbitMQ 提供了消息确认的机制,包括发布确认和消费确认。当消息被成功发布到一个队列,或者被一个消费者成功消费,RabbitMQ 会返回一个确认给发布者或者消费者。
  4. 镜像队列:RabbitMQ 还提供了镜像队列的功能,可以将队列的所有内容都复制到其他节点,如果一个节点挂掉,消息依然可以从其他节点获取。

以上都是 RabbitMQ 保证消息不丢失的方式,但是需要注意的是,开启持久化、消息确认等功能都会增加 RabbitMQ 服务的负载,可能会影响到 RabbitMQ 服务的性能。因此,需要根据实际业务情况进行权衡。


给我一个java代码例子,并且如果confirm不及时要及时重发

以下是一个简单的使用 RabbitMQ Confirm 机制的 Java 代码示例。在这个示例中,我们创建了一个生产者,发送消息到 RabbitMQ 服务器。如果消息没有被 RabbitMQ 服务器确认,我们会尝试重新发送。

import com.rabbitmq.client.*;

import java.io.IOException;

public class ConfirmProducer {
    private final static String QUEUE_NAME = "confirm_queue";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置 RabbitMQ 地址
        factory.setHost("localhost");
        // 创建一个新的连接
        Connection connection = factory.newConnection();
        // 创建一个通道
        Channel channel = connection.createChannel();
        // 声明一个队列
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);

        // 开启 publisher confirm
        channel.confirmSelect();

        String message = "Hello World!";
        for (int i = 0; i < 5; i++) {
            try {
                // 发布消息
                channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
                System.out.println("Sent '" + message + "'");
                // 等待消息确认
                if (!channel.waitForConfirms()) {
                    System.out.println("Message sent failed");
                }
            } catch (Exception e) {
                System.out.println("Message send failed, retry...");
                // 如果发送消息失败,重新发送
                channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
            }
        }

        // 关闭通道和连接
        channel.close();
        connection.close();
    }
}

注意:这个例子使用了 waitForConfirms() 方法来等待 RabbitMQ 服务器的确认,如果没有收到确认,我们会尝试重新发送消息。这是一个简单的示例,实际应用中可能需要考虑更多的因素,例如:消息的重试次数、错误处理等。


kafka怎么保证消息不丢失的,他为什么快呢,kafka重平衡是啥

Kafka也提供了一些机制来保证消息不会丢失:

  1. 副本机制:Kafka的每条消息都会在多个副本上进行存储,这样即使其中某个副本发生故障,也能从其他副本中读取到数据,保证数据的可靠性。
  2. 持久化:Kafka的消息会被写入硬盘,保证了即使在Kafka服务器宕机的情况下,数据也不会丢失。
  3. 消息确认机制:Kafka提供了消息确认机制,当消息被成功写入副本后,生产者会收到一个ACK确认,这样可以确保消息已经成功提交。

Kafka为什么快呢?

  1. 零拷贝技术:Kafka使用了零拷贝技术,可以减少系统调用的次数,大大提高了数据读写的效率。
  2. 批处理:Kafka可以将多条消息打包在一起进行处理,这样可以减少网络请求的次数,提高了处理效率。
  3. 顺序读写:Kafka的消息是顺序写入的,而且大部分读操作也是顺序的,这样在磁盘进行IO操作时,可以大大提高效率。

Kafka的重平衡是什么?

重平衡(Rebalance)是Kafka的一种机制,它可以在消费者的数量发生变化时(例如新消费者加入,或者旧消费者退出),动态地将Topic的Partition分配给消费者,保证系统的负载均衡。这是Kafka保证高可用和负载均衡的


那么意味着kafka是cp的,假如集群有5个节点,我们打开confirm机制,即A消息在send的时候必须要有3个以上的节点落库才能cofirm是吧。如果是这样的给我用java代码写出他的底层实现,并且把rebalance的底层实现用java代码给我

首先,Kafka 是 AP 的,不是 CP 的。Kafka 的设计目标是高吞吐量和数据持久化,而不是强一致性。

其次,Kafka 的确认机制有三种级别:

  • acks=0:生产者不等待来自 broker 的确认。
  • acks=1:只等待 leader 的确认,不等待 follower 的确认。如果 leader 在确认后立即崩溃,消息将会丢失。
  • acks=all:等待所有的副本都确认。只要有一个副本存活,消息就不会丢失。

对于你说的“至少有 3 个节点确认”,你可能是指将 Kafka 的 min.insync.replicas 配置项设置为 3,这样,一个消息至少需要被 3 个副本确认才会被认为是已提交,进而生产者才会收到 ack。

下面是一个使用 Kafka 生产者的 Java 代码示例:

import org.apache.kafka.clients.producer.*;

import java.util.Properties;

public class ProducerExample {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("acks", "all"); // 等待所有副本确认
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        Producer<String, String> producer = new KafkaProducer<>(props);
        for(int i = 0; i < 100; i++) {
            producer.send(new ProducerRecord<String, String>("my-topic", Integer.toString(i), Integer.toString(i)),
                          new Callback() {
                              public void onCompletion(RecordMetadata metadata, Exception e) {
                                  if(e != null) {
                                      e.printStackTrace();
                                  } else {
                                      System.out.println("The offset of the record we just sent is: " + metadata.offset());
                                  }
                              }
                          });
        }
        producer.close();
    }
}

至于 Kafka 的 Rebalance 机制,这是由 Kafka 的消费者组协调器(Group Coordinator)和消费者组成员(Group Member)共同完成的一个过程,涉及到的逻辑比较复杂,涉及到 Kafka 的消费者 API、协调器、网络协议等多个方面,很难用一个简单的 Java 代码示例来展示。在使用 Kafka 消费者 API 的时候,Rebalance 是自动进行的,你不需要自己去实现这个过程。如果你想要了解更多关于 Rebalance 的底层实现,我建议你阅读 Kafka 的源代码和相关文档。


acks=all就表示cp,acks=0就表示ap是吧。还有零拷贝技术是啥,为什么说kafka的消息为顺序读写,rabbitmq不是顺序读写吗,他为啥不设计为顺序读写

首先,关于 CAP 理论,它描述的是一个分布式系统在面对分区容忍性(P)、一致性(C)和可用性(A)这三种特性时,最多只能满足其中的两项。Kafka 的 acks 参数的设置并不能直接决定 Kafka 是 CP 还是 AP。事实上,Kafka 的设计更偏向于 AP,因为 Kafka 允许在一定程度上牺牲一致性(例如,当部分副本失效时,仍然允许读写)。

零拷贝技术是指在进行数据传输时,不通过 CPU,直接从内存或者磁盘读取数据并发送到网络,或者从网络接收数据并写入内存或者磁盘,从而提高数据传输的效率,并减少 CPU 的使用。

Kafka 的消息是顺序读写的,这是因为 Kafka 的数据结构是日志(Log),每条消息都被追加到日志的尾部,消费者按顺序从日志中读取数据,这样可以充分利用磁盘的顺序读写特性,提高数据读写的效率。

至于 RabbitMQ,它是一个基于 AMQP 协议的消息队列,其设计目标主要是实现可靠的消息传输和路由,而不是高吞吐量。RabbitMQ 的消息并非总是顺序读写的,例如,在处理消息确认、消息路由等操作时,可能需要进行随机访问。此外,RabbitMQ 中的消息可以被多个消费者并发消费,这也使得消息的读取并非总是顺序的。


总结

  • 零拷贝技术指的是CPU占用为0
  • rebalance就是自动负载均衡嘛
  • kafka可以指定ack服务器的数目来动态调节可用性和一致性
  • 还有就是kafka的数据结构是顺序存储,为了满足rabbitMQ的高级特性没有办法设计顺序存储的数据结构

6.SpringCloud

GPT问答

我用的是SpringCloudAlibaba这一套,给我详细介绍下gateway,seata,openfeign,nacos,sentinel等组件

Spring Cloud Alibaba是Spring Cloud与阿里巴巴中间件团队的深度合作的一个项目,主要是将阿里巴巴微服务解决方案与Spring Cloud技术体系无缝集成。这一套技术栈包括了Nacos、Sentinel、Seata、RocketMQ、Dubbo、SkyWalking等组件。

  1. Gateway
    Spring Cloud Gateway是Spring Cloud官方推出的二代网关框架,目的在于替代Zuul。它是基于Netty服务器的异步非阻塞模型开发的,因此在性能上相较Zuul有很大的提升。Gateway提供了路由转发、过滤器、限流、熔断等网关基本特性。
  2. Seata
    Seata(Simple Extensible Autonomous Transaction Architecture)是一种简单、易扩展、自治的事务解决方案。Seata 是阿里巴巴开源的一款分布式事务解决方案,提供高性能和简单易用的分布式事务服务。
  3. OpenFeign
    Spring Cloud OpenFeign是一种声明式的WebService客户端。使用Feign能使编写HTTP客户端变得更加简单。它的一个优点是通过注解可以自动化地创建HTTP请求。
  4. Nacos
    Nacos(Dynamic Naming and Configuration Service)是阿里巴巴开源的一款更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos能够帮助您发现、配置和管理微服务。Nacos 提供了一套简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据和流量管理。
  5. Sentinel
    Sentinel 是阿里巴巴开源的一款流量控制组件,主要用于防止服务雪崩和服务保护。它含有流量控制、熔断降级、系统负载保护等多种控制手段,可以在系统压力过大的情况下进行自动的保护和降级。

以上就是这些组件的基本介绍,他们在微服务架构中都扮演了非常重要的角色,帮助开发者更好地构建和维护复杂的微服务系统。


geteway是在网关入口层面做的限流熔断,sentinel是在服务调用方面做的限流熔断吗。我在feign里面写的降级策略是配合熔断用的是不是。还有熔断的意义在哪里,服务都挂了,都出生产事故了

  1. Gateway和Sentinel在限流熔断中的角色
    Gateway 主要在调用链的入口处进行限流熔断,主要用于流量整形和保护下游服务。而 Sentinel 则可用于微服务内部的每一次服务调用,包括服务间的调用以及与外部系统的交互。

  2. Feign的降级策略
    是的,你在 Feign 里面配置的降级策略是用于配合熔断的。当服务调用失败或超时时,熔断器会跳闸,调用方会执行降级策略,而不再等待服务的响应。这样可以快速响应用户,避免服务长时间阻塞。

  3. 熔断的意义
    熔断的目的不仅仅是为了响应服务挂掉的情况。更重要的是,当服务出现延迟或者错误率上升时,通过熔断机制,可以防止这种情况继续恶化,形成雪崩效应。简单来说,熔断器的作用就像是电路中的保险丝,当检测到过多的服务调用失败,熔断器会跳闸,阻止进一步的服务调用,从而保护整个系统的稳定性。

    熔断之后,服务可能会自我恢复,或者通过人工干预恢复。在这个过程中,熔断器可以通过“半开”状态,尝试放行部分服务调用,如果这些调用成功,那么熔断器会自动闭合,恢复服务调用。这样,既可以快速响应服务问题,又可以保护系统的稳定性,避免因为一个服务的问题影响到整个系统。


你说seata是简单、易扩展、自治的事务解决方案。说出理由,自治是什么意思。再给我一个java代码的例子

Seata的特性

  • 简单:Seata提供了简单易用的API和注解,使得开发者可以很容易地在业务代码中使用Seata来进行分布式事务控制。同时,Seata支持Spring Cloud、Dubbo和Motan等多种微服务框架。
  • 易扩展:Seata采用模块化的设计,使得各个模块可以单独升级和扩展,而且Seata提供了丰富的SPI扩展点,开发者可以通过实现SPI接口来定制自己的扩展模块。
  • 自治:在Seata中,每个服务参与者都能够自主决定其本地事务的提交或回滚,而全局事务的提交或回滚则由全局事务协调者来决定。这种模式下,服务参与者只需要关注自身的业务逻辑,而不需要关注其他服务参与者的状态和行为,从而实现了自治。

Seata的Java代码示例

// 引入Seata的全局事务注解
import io.seata.spring.annotation.GlobalTransactional;

@Service
public class BusinessService {

    @Autowired
    private OrderService orderService;

    @Autowired
    private StorageService storageService;

    @Autowired
    private AccountService accountService;

    /**
     * 减库存,下订单,扣余额,全局事务控制
     */
    @GlobalTransactional // 开启全局事务
    public void purchase(String userId, String commodityCode, int orderCount) {
        // 扣减库存
        storageService.deduct(commodityCode, orderCount);

        // 创建订单
        orderService.create(userId, commodityCode, orderCount);

        // 扣减账户余额
        int orderMoney = calculate(commodityCode, orderCount);
        accountService.debit(userId, orderMoney);
    }
}

在以上代码中,@GlobalTransactional注解表示该方法需要进行全局事务控制。当方法执行时,Seata会自动开启一个全局事务,这个全局事务会包含所有的本地事务。如果所有的本地事务都执行成功,那么Seata会提交全局事务,否则Seata会回滚全局事务,从而保证所有本地事务的一致性。


我无法理解自治这个概念,在我思想中,全局事务就该是这样的,本地事务只管自己,全局协调者发现任何1个事务失败就马上会滚,全部成功才成功。还有其他分布式事务解决方案不是这样的吗

你理解的没有错,"自治"在这个上下文中的确是指的服务参与者(或者说是本地事务参与者)只需要关注自己的业务逻辑,而不需要关注其他服务参与者的状态和行为。这是大部分分布式事务解决方案的基本思路,包括两阶段提交(2PC)、三阶段提交(3PC)、TCC(Try-Confirm-Cancel)等。

"自治"这个词在这里更多的是强调的一种设计理念,即服务的自治性,服务应该尽可能地去除对外部的依赖,使得每个服务都能够独立完成自己的任务,这样可以提高服务的可用性和可维护性。

至于其他的分布式事务解决方案,确实也是类似的思路,但在具体的实现上可能会有所不同。例如,2PC和3PC虽然都是基于协调者和参与者的模式,但在事务提交过程中的具体协议是不同的;TCC则是通过业务逻辑来确保事务的最终一致性,与2PC和3PC的预提交机制有所不同。

这也是为什么Seata会强调自己的"自治"特性,因为在Seata的设计中,服务参与者不仅仅是事务的执行者,还需承担回滚日志的记录和存储,这样可以在出现问题时由本地事务参与者自己完成事务的回滚,而不需要依赖于全局事务协调者或者其他服务。


给我讲解下2PC,3PC,TCC,并给我代码例子

2PC(Two-Phase Commit)

2PC是一种强一致性的分布式事务协议,它分为两个阶段:预提交阶段和提交阶段。

  • 预提交阶段:协调者向所有参与者发送事务内容,询问是否可以提交事务,并开始等待所有参与者的响应。
  • 提交阶段:如果协调者收到所有参与者都同意提交事务的消息,那么就向所有参与者发送提交事务的请求,事务被正式提交。

2PC协议的主要问题是同步阻塞问题和单点问题。如果有任何一个节点在第二阶段未返回确认信息,那么协调者会一直阻塞等待。如果协调者在第二阶段崩溃,那么所有参与者会一直阻塞等待。

3PC(Three-Phase Commit)

3PC是在2PC的基础上进行改进的协议,它增加了超时机制和可查询状态,分为CanCommit、PreCommit、doCommit三个阶段。

  • CanCommit阶段:事务询问阶段,协调者向所有参与者发送CanCommit请求,询问是否可以执行事务提交操作。
  • PreCommit阶段:事务预提交阶段,协调者向所有参与者发送PreCommit请求,开始执行事务操作。
  • doCommit阶段:事务正式提交阶段,协调者向所有参与者发送doCommit请求,完成事务提交。

3PC协议解决了2PC的同步阻塞问题,但是在网络分区的情况下,仍可能导致数据不一致。

TCC(Try-Confirm-Cancel)

TCC是一种基于业务逻辑的分布式事务协议,它分为Try、Confirm和Cancel三个阶段。

  • Try阶段:尝试执行业务,写预备日志。
  • Confirm阶段:确认执行业务,根据预备日志完成业务操作。
  • Cancel阶段:取消执行业务,根据预备日志撤销在Try阶段执行的业务操作。

TCC协议适合于业务逻辑强一致性要求不高,但需要长时间运行的场景。

由于这三种协议都涉及到分布式系统的底层通信和协调机制,因此在实际的Java代码中不容易直接实现。在实际应用中,我们通常会使用像Seata这样的分布式事务中间件来处理分布式事务,这些中间件内部已经实现了相应的协议。

例如,Seata的AT模式就是基于2PC协议的,而TCC模式则是基于TCC协议的。在Seata中使用AT或者TCC模式,只需要在业务方法上加上@GlobalTransactional注解即可,Seata会自动进行分布式事务的协调和控制,开发者只需要关注业务逻辑。


协调者只是告知是否可以提交,参与者才是真正提交事务的。2PC加一个超时机制不就完了吗,为啥要引入3PC?2PC的预提交阶段是在事务执行后最后commit的时候才去问协调者,3PC的CanCommit也是啊。后面PreCommit开始执行事务是什么意思?为什么3PC可以解决同步阻塞的问题,所有事务不都等着提交吗。还有为啥会数据不一致?

2PC和3PC之间的区别确实可以看作是对超时机制的不同处理。但是,需要注意的是,单纯的超时机制可能无法满足所有场景。比如,在2PC中,一旦协调者宕机,参与者在没有收到协调者的最终决定之前,会一直处于等待状态,这就导致了阻塞。即使设置了超时,参与者也无法决定究竟是提交还是回滚事务,因为它们无法确定协调者的最终决定。

为了解决这个问题,3PC引入了第三个阶段。在CanCommit阶段,协调者询问参与者是否可以提交,参与者回复Yes或No后,会进入等待状态。此时参与者已经准备好提交,但并未真正提交。然后在PreCommit阶段,协调者会发出预提交请求,参与者收到请求后会执行事务操作并将状态切换为PreCommit,然后回复ACK。在最后的doCommit阶段,一旦协调者收到所有参与者的ACK,就会发出正式提交,所有参与者在收到doCommit请求后,才会正式提交事务。

这个设计的好处是,参与者在等待协调者的doCommit请求时,如果超时了,那么可以直接提交事务,而不用一直阻塞等待。因为如果参与者已经收到了PreCommit请求,那么说明协调者已经决定要提交事务(因为协调者在发出PreCommit请求之前,已经收到了所有参与者的Yes响应)。所以即使后续协调者宕机了,参与者也可以自己做出决定,不会一直阻塞。这也是3PC能够解决2PC同步阻塞问题的原因。

至于可能导致数据不一致的问题,主要是因为在某些情况下,如网络分区,协调者和部分参与者可能无法通信。假设在PreCommit阶段,协调者发出了PreCommit请求,但只有部分参与者收到了这个请求并回复了ACK,而其他由于网络问题未收到请求的参与者可能仍处于初始状态。这时,如果协调者和收到PreCommit的参与者之间的网络恢复了,协调者可能会以为所有参与者都已经准备好提交,从而发出doCommit请求,导致只有部分参与者提交了事务,造成数据不一致。

2PC会有一直阻塞等待的风险,3PC有数据不一致的缺点,TCC你也说会导致数据不一致。那在金融系统要求强一致性的服务该怎么办呢

对于金融系统这种强一致性要求非常高的场景,通常会采取其他的一些技术手段和架构设计来解决分布式事务问题。

  1. 单体架构/垂直划分:在一些非常关键的业务场景,如支付、转账等,可能会采用单体架构或者进行垂直的业务划分,将整个业务流程放在一个服务或者一个数据库中,这样就可以使用传统的ACID事务来保证一致性。
  2. 最终一致性:在一些不需要即时强一致性的场景,可以采用最终一致性的方式,通过消息队列、补偿事务等方式,在一定时间范围内达到数据一致性。例如,银行的对账系统就是一个典型的最终一致性应用。
  3. 分布式锁/悲观锁:在并发量不是特别高,对性能要求不是特别高的场景,可以使用分布式锁或者悲观锁来保证一致性。但是这种方式可能会影响系统的性能和可用性。
  4. Saga模式:Saga模式是一种长事务的解决方案,它将一个长事务拆分为多个本地事务,并通过事件驱动的方式来协调这些本地事务。如果某个本地事务失败,Saga会执行相应的补偿事务来保证数据的一致性。

需要注意的是,不同的业务场景需要选择不同的方案,没有一种方案可以解决所有的问题,需要根据具体的业务需求和系统条件来选择最合适的方案。


现在2PC和3PC我大概懂了,TCC模式我还是不明白

TCC (Try-Confirm-Cancel) 是一种两阶段型的补偿型事务模式,它主要用于处理分布式系统中的业务逻辑,以保证分布式事务在各个服务间的一致性。

TCC的每个阶段解释如下:

  • Try 阶段:尝试执行业务,执行所有业务检查,以及预留必要的业务资源。
  • Confirm 阶段:真正执行业务,不做任何业务检查,只使用Try阶段预留的业务资源。如果确认操作失败,只能人工干预处理。
  • Cancel 阶段:在业务执行失败时,取消执行业务,释放在Try阶段预留的业务资源。

一个简单的TCC模式的例子是银行转账服务:

public interface TransferService {

    // Try阶段
    void tryTransfer(TransferInfo transferInfo) throws BusinessException;

    // Confirm阶段
    void confirmTransfer(TransferInfo transferInfo) throws BusinessException;

    // Cancel阶段
    void cancelTransfer(TransferInfo transferInfo) throws BusinessException;

}

在这个例子中,tryTransfer方法会尝试执行转账操作,例如检查账户余额是否足够,预留转账金额等。如果tryTransfer成功,那么会进入confirmTransfer阶段,真正执行转账操作。如果在tryTransfer或confirmTransfer阶段发生异常,那么会调用cancelTransfer方法,取消转账操作,释放预留的转账金额。

TCC模式的优点是业务逻辑清晰,容易理解和实现。但是也有一些缺点,例如需要为每个操作实现对应的确认操作和取消操作,这可能会增加开发的复杂性。另外,如果在Confirm阶段或Cancel阶段出现异常,可能需要人工介入处理。

TCC模式适合于需要进行复杂业务操作、业务检查、资源预留的分布式事务场景,例如电商系统的订单创建、支付系统的资金转账等。


2pc模式和3pc模式,其实不关心业务逻辑的,主要是关心最后commit,所以从单体事务迁移过来的时候不用做太大的代码修改。但是如果我使用TCC的话,意味着我需要重新写一遍业务逻辑是吧。我还是无法理解TCC模式的分布式事务思想体现,TCC也有协调者吗,需要多个tryTransfer都成功才能confirm是吧。如果任意节点tryTransfer不成功,或者协调者挂了也会同步阻塞是吧

你的理解是正确的。TCC模式是需要对业务逻辑进行改造,需要为每个操作实现Try、Confirm和Cancel三个操作。它的主要思想是把一个全局事务划分为多个本地的Try、Confirm和Cancel操作,通过业务逻辑来保证全局事务的一致性。

关于协调者,TCC模式一般也需要一个事务协调者来协调和控制全局事务的执行。协调者会记录每个参与者的状态,如果所有参与者的Try操作都成功,那么协调者会通知所有参与者执行Confirm操作。如果有任何一个参与者的Try操作失败,或者在一定的超时时间内没有收到参与者的响应,那么协调者会通知所有参与者执行Cancel操作。

如果协调者挂了,一般会有以下处理方式:

  1. 状态恢复:协调者挂了之后再次恢复,需要恢复其状态,这需要依赖持久化机制。协调者需要将事务的所有阶段和参与者的状态等信息持久化,挂掉后再次恢复时,可以通过这些信息恢复状态。
  2. 超时处理:为了防止长时间阻塞,TCC事务一般会设计超时机制。例如,如果在一定时间内协调者没有通知参与者进行Confirm或Cancel操作,那么参与者可以自动进行超时处理,如执行Cancel操作。

至于同步阻塞问题,如果采用了超时处理机制,那么可以避免由于等待协调者导致的长时间阻塞。但是,由于TCC模式涉及到业务逻辑,业务执行的时间可能比较长,因此可能会有一定程度的阻塞。但这种阻塞一般可通过适当的系统设计和优化来缓解。


前文你提到Seata提供了丰富的SPI扩展点,开发者可以通过实现SPI接口来定制自己的扩展模块,给我几个具体的例子。还有我理解SPI就是运行时的动态代理,我的理解正确吗

Seata 的 SPI 扩展点主要有以下几个例子:

  1. ResourceManager:负责管理一类资源(例如数据库连接)的逻辑,用户可以根据自己的需求来实现这个接口。
  2. TransactionManager:负责事务的生命周期管理,包括开始事务、提交事务和回滚事务等。
  3. LockManager:负责锁的管理,包括获取锁、释放锁等。
  4. Store:负责事务日志的存储,用户可以根据自己的需求来实现这个接口,例如实现基于文件的存储、基于数据库的存储、基于分布式存储系统的存储等。

开发者可以通过实现以上这些接口,来根据自己的需求定制自己的扩展。

对于你的第二个问题,SPI (Service Provider Interface) 并不是运行时的动态代理。SPI 是一种服务发现机制。它通过在类路径下的 META-INF/services 目录中查找文件,然后加载文件所指定的类,这是一种运行时动态加载的机制。这与动态代理是两个不同的概念,动态代理主要是用来在运行时动态地创建一个接口的实现类。


总结

  • 熔断是为了防止情况继续恶化甚至雪崩,如果代码健壮,熔断后还可以自治,自恢复
  • 2PC,3PC,TCC仅仅是协议/思想,seata可以自由切换,支持3PC?
  • 2PC有协调者崩溃造成同步等待的情况,没有超时概念,因为无法知道其他节点收到什么样的命令
  • 3PC在二阶段prepare的时候就可以提交了,有可能造成数据不一致,比如A节点收到了prepare_cmd,B节点没收到或者超时收到prepare_cmd。就会造成数据不一致,但是总体而言解决了同步等待的问题,还有can_cmd从一定程度上规避了风险了,can_cmd都确认了其实后两步都很快。do_cmd只是做及时确认,由于有超时机制,其实默认60s就do了
  • TCC很烦的是要改代码,太恶心了。
  • 一致性问题到最后都是追寻最终一致性,银行对账系统存在说明大家都不放心数据一致性问题,都在想方设法的保证最终一致性

7.事务隔离级别MVCC三个日志文件

GPT问答

mysql事务隔离级别有哪些,分别解决了什么问题。MVCC机制是什么

MySQL事务隔离级别有四种,分别为:

  1. 读未提交(Read Uncommitted):这是最低的事务隔离级别,它允许事务读取尚未提交的改变。在这个级别,可能会出现脏读(Dirty Read)、不可重复读(Nonrepeatable Read)和幻读(Phantom Read)的情况。
  2. 读已提交(Read Committed):这是大多数DBMS(数据库管理系统)的默认隔离级别(但不是MySQL默认的)。它不允许脏读,但可能会出现不可重复读和幻读的情况。
  3. 可重复读(Repeatable Read):这是MySQL的默认隔离级别。它确保同一事务的多次读取结果是一致的。但是,它仍然可能出现幻读的情况。
  4. 串行化(Serializable):这是最高的隔离级别。它完全阻止了脏读、不可重复读和幻读。但是,这种隔离级别通常对性能有影响,因为它在读取时会阻止其他事务进行写入。

解决的问题:

  • 脏读:某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,那么后一个事务获取的数据就会是不正确的。
  • 不可重复读:在一个事务内,多次读同一数据。在这个事务还没有结束时,另一个事务也访问了该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。 这就是不可重复读。
  • 幻读:一个事务按照某种搜索条件获取了某些值,而这时另一个事务却插入了一些满足该搜索条件的新值,第一个事务再次使用同一搜索条件获取值,会发现多了一些原本不存在的记录,就像发生了幻觉一样,这就叫幻读。

MVCC,即多版本并发控制(Multi-Version Concurrency Control)。它是一种并发控制的方法。当多个用户访问同一份数据时,每个用户都会看到自己的一份数据快照,而这份数据快照是在事务开始时创建的。这样,用户就不会看到其他用户在事务期间所做的修改。MVCC既可以实现读已提交和可重复读隔离级别,又可以通过在每行记录上保存两个隐藏的列来实现,一个用来记录该行的创建时间(或者说是版本号),一个用来记录该行的过期时间(或者说是版本号)。


只有for update加锁读才是当前读,其余都是快照读。MVCC引入目的,就是解决不可重复读的问题。但是如果在RR模式下,使用select * from xx for update 才会出现幻读,快照读是不会产生幻读的

是的,你说的对。MVCC的目标主要是解决“不可重复读”问题,并且在大多数情况下,它可以防止“幻读”问题。

在MySQL中,只有SELECT...FOR UPDATE才是当前读,它会对选定的行加锁。其余的查询都是快照读,它们工作在一个快照版本上,不会阻塞其他事务。

在可重复读(Repeatable Read)隔离级别下,快照读确实不会产生幻读,因为同一事务的所有快照读都是基于相同的快照版本。但在同一事务中,如果你使用SELECT...FOR UPDATE(即当前读),那么由于此时会锁定被选中的行,如果此时有其他事务插入了满足该搜索条件的新行,当你再次执行相同的SELECT...FOR UPDATE语句时,你会看到这些新插入的行,这就可能导致幻读。

为了解决这个问题,一种可能的解决方案是在事务开始时就将所有可能会访问的行都锁定,但这可能会导致性能问题。在实践中,如果需要防止幻读,通常会使用更高的隔离级别,如串行化(Serializable)。


mysql在innodb引擎里面的三大日志文件是啥,分别有啥用

MySQL的InnoDB存储引擎有三大重要的日志文件:

  1. 重做日志(Redo Log):重做日志是InnoDB存储引擎层实现的一种日志,主要用于确保事务的原子性和持久性。在事务进行中,首先会把数据的修改写入到重做日志中,并刷新到磁盘,然后再修改内存中的数据。如果此时系统崩溃,那么在重启数据库后,InnoDB引擎可以通过重做日志重做(redo)数据的修改,从而保证数据的一致性。
  2. 回滚日志(Undo Log):回滚日志是InnoDB存储引擎层实现的一种日志,主要用于在事务失败或取消时,撤销(undo)已经进行的修改,确保事务的一致性。同时,回滚日志也用于实现多版本并发控制(MVCC),提供了从旧版本数据到新版本数据的视图。
  3. 二进制日志(Binary Log):二进制日志是MySQL服务器层实现的一种日志,记录了所有修改数据的SQL语句。主要用于实现MySQL的复制功能(将主服务器上的数据复制到从服务器),以及数据的恢复。需要注意的是,二进制日志和InnoDB的重做日志不同,二进制日志记录的是SQL语句级别的修改,而重做日志记录的是数据页级别的修改。

这三种日志都是为了实现ACID(原子性、一致性、隔离性、持久性)事务特性,以及数据库的高可用性和数据的恢复。

总结

  • acid和4个隔离级别都是老生常谈问题
  • 当前对于MVCC底层和XS行表锁,gap,next-key,意向锁不是很清楚,极客时间的mysql45讲再深入研究

8.操作系统中的阻塞异步BIONIOAIO

GPT问答

操作系统中的阻塞,非阻塞,异步,同步,BIO,NIO,AIO,给我解释下

这些术语都是用来描述输入/输出 (I/O) 模型的,下面是每个术语的简单解释:

  1. 阻塞(Blocking):阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
  2. 非阻塞(Non-blocking):非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
  3. 同步(Synchronous):同步意味着在一个操作完成之前,请求者必须等待。无论操作是阻塞的还是非阻塞的,只要是同步的,那么请求者都需要等待操作的完成。
  4. 异步(Asynchronous):异步意味着请求者在发出请求后就可以进行其他操作,不需要等待操作的完成。当操作完成时,通常会通过回调(callback),事件,通知等机制来通知请求者。
  5. BIO(Blocking I/O):这是一个阻塞的同步I/O模型。在这个模型中,当一个I/O操作在进行时,调用者必须等待其完成。
  6. NIO(Non-blocking I/O):这是一个非阻塞的同步I/O模型。在这个模型中,调用者可以在I/O操作完成前进行其他任务,但仍需要定期检查I/O操作是否完成。
  7. AIO(Asynchronous I/O):这是一个非阻塞的异步I/O模型。在这个模型中,调用者完全不需要关心I/O操作,当操作完成时,系统会自动通知调用者。

在理解这些术语的时候,要注意阻塞/非阻塞和同步/异步之间的区别。阻塞/非阻塞描述的是程序在等待调用结果(可能是I/O操作)时的状态,而同步/异步描述的是程序在发出调用请求后的行为。


3大io模型就是对于是否阻塞和的是否同步的具体实践。给我写出3大io模型的java例子。

好的,以下是针对 Java 的 BIO, NIO和 AIO 的简单示例。

  1. BIO (Blocking I/O)
// Server example
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
    Socket socket = serverSocket.accept(); // blocked until a connection is made
    DataInputStream in = new DataInputStream(socket.getInputStream());
    System.out.println(in.readUTF()); // blocked until all bytes are read
    socket.close();
}

// Client example
Socket socket = new Socket("localhost", 8888);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeUTF("Hello Server"); // blocked until all bytes are written
socket.close();

  1. NIO (Non-blocking I/O)
// Server example
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
serverSocketChannel.configureBlocking(false); // non-blocking mode
while (true) {
    SocketChannel socketChannel = serverSocketChannel.accept();
    if (socketChannel != null) {
        // do something with socketChannel...
    }
}

// Client example
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); // non-blocking mode
socketChannel.connect(new InetSocketAddress("localhost", 8888));
while (!socketChannel.finishConnect()) {
    // do something else...
}

  1. AIO (Asynchronous I/O)
// Server example
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8888));
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
    public void completed(AsynchronousSocketChannel socketChannel, Void attachment) {
        // do something with socketChannel...
        serverSocketChannel.accept(null, this); // accept the next connection
    }

    public void failed(Throwable throwable, Void attachment) {
        throwable.printStackTrace();
    }
});

// Client example
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8888), null, new CompletionHandler<Void, Void>() {
    public void completed(Void result, Void attachment) {
        // do something on successful connection...
    }

    public void failed(Throwable throwable, Void attachment) {
        throwable.printStackTrace();
    }
});

以上代码仅供示例,可能还需要进一步的错误处理和资源管理。


再给我写一个异步阻塞的例子

在 Java 中,异步阻塞这个概念并不常见,因为异步通常都意味着非阻塞。但是可以通过使用 Future 来模拟异步阻塞的行为。具体来说,我们可以启动一个新的线程来执行任务(异步),然后在主线程中调用 Future.get() 方法来等待结果(阻塞)。下面是一个简单的示例:

import java.util.concurrent.*;

public class AsyncBlockingExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Future<String> future = executorService.submit(() -> {
            // Simulate long running task
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Hello, World!";
        });

        // Do something else here...

        // Wait for the task to finish (block)
        String result = future.get();

        System.out.println(result);

        executorService.shutdown();
    }
}

在这个例子中,我们首先创建一个 ExecutorService,它是一个可以管理多线程的服务。然后我们提交一个任务到 ExecutorService,这个任务在一个新的线程中异步执行。主线程继续执行其他任务,然后在需要结果的时候调用 Future.get() 方法来等待结果。如果在这个时候任务还没有完成,那么 Future.get() 方法会阻塞,直到任务完成为止。


再用烧水后喝水给我解释BIO,NIO,AIO。异步阻塞一起解释

好的,我们可以把烧水喝水的过程类比为 I/O 操作。

  1. BIO(Blocking I/O):你把水壶放在炉子上,然后就站在那里,什么也不做,直到水烧开。这就是阻塞,你正在等待 I/O 操作(烧水)的完成。这就像是你在等待一个网络请求的响应,直到它完成,你才能继续。
  2. NIO(Non-blocking I/O):你把水壶放在炉子上,然后你去做其他事情(比如读书)。但是每过一段时间你都得回来检查一下水是否烧开。这就是非阻塞,你并没有阻塞在 I/O 操作上,而是在等待 I/O 操作时做其他事情。但是你需要不断地检查 I/O 操作是否完成。
  3. AIO(Asynchronous I/O):你把水壶放在炉子上,然后你去做其他事情。你的水壶很聪明,当水烧开的时候,它会响铃通知你。这就是异步,你不需要去检查 I/O 操作是否完成,因为当它完成的时候,你会得到通知。
  4. 异步阻塞:这个概念在实际中并不常见,但是为了解释,我们可以这样想象:你把水壶放在炉子上,然后你去做其他事情。你的水壶很聪明,当水烧开的时候,它会响铃通知你。但是你在做其他事情的时候,每完成一项任务,你都会去看看水壶是否响铃。这就是异步阻塞,你在等待 I/O 操作的同时做其他事情,但是你要不断地检查是否有通知。

以上就是 BIO,NIO,AIO 以及异步阻塞的解释。希望这个类比可以帮助你理解这些概念。

总结

  • 基本上理解异步阻塞的例子,就差不多了
  • 阻塞就意味着等待

9.Redis跟MySQL缓存一致性

GPT问答

Redis跟MySQL缓存一致性问题,目前业界常用的方案是哪些

在构建高并发、高性能的互联网应用时,缓存是不可或缺的一部分。然而,引入缓存后,缓存与数据库的数据一致性问题就会出现。Redis 作为一种非常流行的缓存解决方案,同 MySQL 数据库的一致性问题也是业界面临的重要挑战。

以下是一些常见的解决方案:

1. 缓存穿透:

缓存穿透是指查询一个数据库中不存在的数据,结果缓存没有命中,然后去数据库查询,数据库也没有,这样就形成了缓存穿透。解决方案是对于查找的字段做一个空值的缓存。这样即使数据库没有,缓存也会命中,从而避免了对数据库的查询。

2. 缓存击穿:

缓存击穿是指访问一个热点数据,当该数据过期的瞬间,大量的请求就会穿破缓存,直接请求数据库,造成数据库的压力。解决方案是设置热点数据永不过期,或者在代码层面,使用互斥锁或者分布式锁,保证缓存的单线程(进程)写,避免大量并发线程对数据库进行访问。

3. 缓存雪崩:

缓存雪崩是指缓存中大量的数据同时过期,导致大量的请求直接打到数据库,从而导致数据库压力过大。解决方案是对缓存的过期时间进行随机,避免同一时间大量的数据过期,请求打到数据库。

4. 读写一致性:

这是比较常见的一种场景,即读取数据后,立即写入缓存。但是,如果写入缓存失败,会导致下次读取的时候,读到的是旧数据。解决方案是使用"先删除缓存,再更新数据库"的原则。

5. 双写不一致:

双写不一致是指应用先将数据写入数据库,然后再写入缓存,这期间数据可能会出现不一致的情况。解决方案是使用消息队列,将需要写入的数据写入消息队列,然后由消费端去更新数据库和缓存,保证了顺序性。

以上这些方法并不能保证完全的一致性,只能在一定程度上降低不一致的可能性。在某些对一致性要求非常高的场景下,可能需要牺牲一些性能,采取更加严格的一致性策略。


读写一致性的解决方案,你提供的是旁路缓存的模式,但是删除缓存到数据库落库还有一段时间有可能会重新生成缓存,还需要延时双删

你说得很对,"先删除缓存,再更新数据库"的原则确实存在问题,这是因为在删除缓存后,到数据库更新结束这段时间内,如果有其他线程查询这个数据,会查询不到缓存,然后去数据库查询到旧数据并将旧数据写入缓存,这样当数据库更新完成后,缓存中的数据还是旧的,这就出现了数据不一致的情况。

目前,针对这个问题,一种比较成熟的解决方案是“延时双删”,具体步骤如下:

  1. 先删除缓存
  2. 再更新数据库
  3. 等待一段时间(比如100ms),再次删除缓存

这种方法的基本思想是:因为可能会有其他线程在这段时间内把数据库旧数据查询出来并且写入缓存,所以通过等待一段时间后再次删除缓存,以此来保证缓存中的数据是最新的。

然而,这种方案并不能完全解决问题,因为这个等待的时间并不能保证是最优的,这个时间长了可能会导致缓存利用率下降,时间短了可能会导致数据不一致。但是在实际操作中,这种方案可以在一定程度上缓解读写一致性问题。


追求强一致性就要加锁了,性能牺牲太大

是的,你的理解是正确的。在计算机科学中,一致性和性能往往是一对矛盾的存在。强一致性意味着系统的每一次读操作都将返回最新的写操作结果,这就需要在读写操作之间加锁以确保操作的顺序性,但这样做会极大地牺牲系统的性能。

在实际应用中,我们往往会根据业务需求在一致性和性能之间做出权衡。对于一些对数据一致性要求非常高的业务,我们可能会选择牺牲一部分性能来确保数据的强一致性。反之,对于一些对性能要求非常高,而对数据一致性要求相对较低的业务,我们可能会选择牺牲一部分数据一致性来提高系统的性能。

另外,对于缓存一致性问题,除了上述的一些解决方案,还有一些其他的技术可以帮助我们,如读写分离、分布式锁等。这些技术在不同的业务场景下有着不同的应用,可以根据具体的业务需求选择合适的方案。

总结

  • 缓存击穿可以用双重检查锁
  • 旁路缓存+延时双删
  • 上面方案还是很小几率不一致,就需要加锁了。这个要业务容忍程度

10.Dubbo底层理解

GPT问答

谈谈你对Dubbo底层理解

Dubbo是一种高性能的Java RPC框架,它使用了许多高级技术和设计模式来实现其性能和可扩展性。

  1. 服务提供者和消费者模型:Dubbo使用了服务提供者和消费者模型。服务提供者提供服务并注册到注册中心,服务消费者从注册中心订阅服务并发起调用请求。这种模型使得服务的提供者和消费者可以在不知道对方的情况下进行通信。
  2. 负载均衡:Dubbo内部实现了多种负载均衡算法,如随机、轮询、最少活跃调用、一致性Hash等。当服务消费者从多个服务提供者中选择一台进行调用时,会根据负载均衡策略进行选择。
  3. 服务路由和过滤:Dubbo支持自定义路由规则和过滤器,可以对服务调用进行灵活的控制。例如,可以根据服务提供者的IP或者服务消费者的应用名进行路由,或者在服务调用前后进行特定的处理。
  4. 异步通信:Dubbo支持异步调用,消费者无需同步等待服务调用的结果,可以提高系统的并发处理能力。
  5. 协议和序列化:Dubbo支持多种协议(如Dubbo、HTTP、Thrift等)和序列化方式(如Hessian、Java、Kryo、FST等),可以根据实际需求进行选择。

底层实现层面,Dubbo主要是基于Netty来实现网络通信,利用Java的NIO(非阻塞IO)来处理高并发的网络请求。同时Dubbo也使用了线程池来并发处理服务调用,提高系统的吞吐量。

以上是我对Dubbo的一些理解,如果你有任何问题或者想要深入了解某个部分,欢迎随时向我提问。


总结

  • 01-贝壳中说了dubbo的对比springcloud
  • 底层用的netty,NIO模型,线程池模型,这个太底层了,要细聊还是要看代码

11.DDD的理解

GPT问答

谈谈你对DDD的理解

领域驱动设计(Domain-Driven Design,简称DDD)是一种软件开发方法论,由Eric Evans在他的书《领域驱动设计:软件核心复杂性对策》中首次提出。DDD的主要目标是在复杂的设计和模型中找到和实现业务领域的连续性。

DDD主要包含以下几个核心概念:

  • 领域(Domain): 这是一个问题区域,我们需要在这个区域中找到解决方案。
  • 子域(Subdomain): 一个大的领域可以分为几个子域,每个子域都是独立的,可以单独处理和开发。
  • 限界上下文(Bounded Context): 这是一个显式定义的边界,它限定了特定的语义上下文,其中的模型只在这个上下文中有意义。
  • 领域模型(Domain Model): 这是一个模型,它封装了特定领域的知识和业务规则。
  • 实体(Entity): 在领域模型中有唯一标识的对象。
  • 值对象(Value Object): 在领域模型中没有唯一标识的对象。
  • 聚合(Aggregate): 一组具有一致性边界的关联对象,由一个根实体(Aggregate Root)来管理。
  • 领域服务(Domain Service): 当领域模型的某些操作无法归类到实体或值对象上时,可以定义为领域服务。
  • 领域事件(Domain Event): 表示领域中重要的事件,它会影响领域的状态。
  • 仓库(Repository): 提供对聚合的存储和检索。

DDD强调的是,我们应该尽可能地理解业务领域,并且在设计和代码中反映这个理解。这样可以使得软件更加符合业务需求,更易于维护和演进。


总结

  • 我其实很反感,因为需求和需求设计不可能一开始就合理,考虑到各种设计模式,熟悉的controller到service到mapper的方式,考虑到不同开发的编码习惯,很烦

写到最后

欢迎访问:https://bothsavage.github.io
请添加图片描述

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值