【详解】后端研发常用的服务端之间通信的五种方式

五种方式总结

HTTP/HTTPS

基于网络请求,调用方直接与服务端通信。

RESTful API、跨平台调用

RPC【使用多】

基于二进制协议,调用方通过框架(如 Dubbo、gRPC)与服务端通信。

微服务架构

SDK(Maven)【使用多】

提供封装好的库,调用方通过依赖管理工具引入并直接调用。

第三方服务集成、工具类封装

WebSocket

全双工通信,适合实时性要求高的场景。

实时通信(如聊天、推送)

MQ【使用多】

基于异步通信,适合解耦和高并发场景。

分布式系统、异步任务处理

下面依次介绍常用的三种方式:SDK(maven)、RPC(Dubbo)、MQ(RocketMQ)。

一、以 Maven 包形式提供给对方的方式

概述

以 Maven 包的形式提供给对方是一种常见的 SDK 接入方式。这种方式将功能封装成一个可复用的库(Library),并通过 Maven 等依赖管理工具分发给调用方。调用方只需在项目中引入该包即可使用其中的功能。

特点

  1. 封装性好: 调用方无需关心底层实现细节,只需调用提供的 API。
  2. 易于分发: 通过 Maven 中央仓库或私有仓库发布,方便调用方引入。
  3. 版本管理: 支持语义化版本控制(如 1.0.0、1.1.0),便于升级和维护。
  4. 开发效率高: 调用方只需引入依赖即可使用,减少了重复开发的工作量。

适用场景

  • 需要为第三方开发者提供一组功能接口。
  • 希望简化调用方的集成过程。
  • 功能相对独立且通用,适合封装成库。

优缺点

优点

  • 封装性好:调用方无需关心底层实现细节。
  • 易于分发:通过 Maven 中央仓库或私有仓库发布。
  • 版本管理:支持语义化版本控制。
  • 开发效率高:减少重复开发工作量。

缺点

  • 耦合性:调用方需要依赖提供方的包,可能会受到版本更新的影响。
  • 安全性:如果 SDK 包含敏感逻辑或数据,可能存在泄露风险。
  • 灵活性不足:调用方无法轻易修改 SDK 的内部实现。

实现步骤

1. 提供方:创建 Maven 项目并打包

1.1 创建 Maven 项目

  • 使用 IDE(如 IntelliJ IDEA)创建一个 Maven 项目。
  • 编写核心功能代码。

1.2 配置 pom.xml

pom.xml 中定义项目的元信息(如 groupIdartifactIdversion)。

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>example-sdk</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
</project>

1.3 打包

使用 Maven 打包工具生成 JAR 文件:

mvn clean package

1.4 发布到 Maven 仓库

  • 中央仓库:遵循 Maven Central Repository 的发布流程。
  • 私有仓库:上传到公司内部的 Nexus 或 Artifactory 私有仓库。

2. 调用方:引入 Maven 依赖

2.1 在 pom.xml 中引入依赖

调用方在自己的项目中通过 pom.xml 引入该依赖:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>example-sdk</artifactId>
    <version>1.0.0</version>
</dependency>

2.2 配置私有仓库(如果需要)

如果是私有仓库,还需要配置仓库地址:

<repositories>
    <repository>
        <id>private-repo</id>
        <url>https://repo.example.com/repository/maven-releases/</url>
    </repository>
</repositories>

3. 调用方:使用 SDK 功能

调用方可以直接使用 SDK 提供的类和方法。例如:

import com.example.sdk.ExampleService;

public class Main {
    public static void main(String[] args) {
        ExampleService service = new ExampleService();
        String result = service.doSomething("input");
        System.out.println(result);
    }
}

分类

根据 SDK 的实现内容,可以进一步细分为以下几种:

  1. 功能型 SDK:提供一组功能接口,调用方可以直接调用这些接口完成特定任务。示例:支付 SDK(如支付宝、微信支付)、云服务 SDK(如阿里云、AWS)。
  2. 协议型 SDK:封装了与外部系统的通信协议(如 HTTP 请求、RPC 调用)。调用方通过 SDK 间接与外部系统交互,而无需关心底层通信细节。示例:Dubbo 客户端 SDK、gRPC 客户端 SDK。
  3. 工具型 SDK:提供一组工具类或工具方法,帮助调用方完成某些通用任务。示例:日志工具、加密工具、文件处理工具。

总结

以 Maven 包的形式提供给对方属于 SDK 方式,是一种常见的接入方式。它通过封装功能为库的形式,简化了调用方的集成过程,同时提供了良好的版本管理和分发能力。

二、以 RPC 方式(如 Dubbo)提供给对方的方式

概述

Dubbo 是阿里巴巴开源的一款高性能 Java RPC 框架,广泛应用于微服务架构中。通过 Dubbo 提供服务的方式,通常是基于服务注册与发现的机制。调用方和提供方通过 Dubbo 框架进行远程服务调用,而无需关心底层通信细节。

特点

  1. 透明性: 调用方像调用本地方法一样调用远程服务,无需关心底层通信细节。
  2. 高性能: 使用高效的二进制协议(默认为 Dubbo 协议),支持高吞吐量的服务调用。
  3. 服务治理: 支持负载均衡、熔断、限流等高级功能。
  4. 分布式支持: 适合微服务架构,能够轻松实现服务间的解耦。

适用场景

  • 微服务架构中的服务间通信。
  • 需要高性能、低延迟的服务调用。
  • 分布式系统中需要服务注册与发现的场景。
  • 内部系统之间的交互。

优缺点

优点

  • 透明调用:调用方像调用本地方法一样调用远程服务。
  • 高性能:使用高效的二进制协议,减少网络开销。
  • 服务治理:支持负载均衡、熔断、限流等功能。
  • 分布式支持:适合微服务架构,能够轻松实现服务间的解耦。

缺点

  • 复杂性增加:需要引入服务注册中心(如 ZooKeeper、Nacos)和 Dubbo 框架。
  • 运维成本:需要维护服务注册中心和 Dubbo 框架。
  • 学习曲线:需要熟悉 Dubbo 的使用。

实现步骤

1. 提供方:发布服务

1.1 引入 Dubbo 依赖

pom.xml 中引入 Dubbo 和注册中心(如 Nacos 或 ZooKeeper)的 Maven 依赖。

<dependencies>
    <!-- Dubbo 核心依赖 -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>3.2.0</version>
    </dependency>

    <!-- 注册中心依赖(以 Nacos 为例) -->
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
        <version>2.2.3</version>
    </dependency>
</dependencies>

1.2 定义服务接口

定义一个服务接口,供调用方使用。

public interface HelloService {
    String sayHello(String name);
}

1.3 实现服务接口

提供方实现服务接口,并将其发布为 Dubbo 服务。

import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;

@DubboService(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

1.4 配置 Dubbo

application.propertiesapplication.yml 中配置 Dubbo 和注册中心。

# Dubbo 配置
dubbo.application.name=dubbo-provider
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

# 注册中心配置(以 Nacos 为例)
dubbo.registry.address=nacos://127.0.0.1:8848

1.5 启动服务

启动 Spring Boot 应用程序,服务将自动注册到注册中心。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableDubbo // 启用 Dubbo
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

2. 调用方:调用服务

2.1 引入 Dubbo 依赖

调用方同样需要引入 Dubbo 和注册中心的 Maven 依赖。

<dependencies>
    <!-- Dubbo 核心依赖 -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>3.2.0</version>
    </dependency>

    <!-- 注册中心依赖(以 Nacos 为例) -->
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
        <version>2.2.3</version>
    </dependency>
</dependencies>

2.2 引用远程服务

调用方通过 Dubbo 框架获取远程服务的代理对象,并调用其方法。

import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component;

@Component
public class HelloServiceConsumer {

    @DubboReference(version = "1.0.0")
    private HelloService helloService;

    public void callRemoteService() {
        String result = helloService.sayHello("World");
        System.out.println(result);
    }
}

2.3 配置 Dubbo

application.propertiesapplication.yml 中配置 Dubbo 和注册中心。

# Dubbo 配置
dubbo.application.name=dubbo-consumer

# 注册中心配置(以 Nacos 为例)
dubbo.registry.address=nacos://127.0.0.1:8848

2.4 启动调用方

启动 Spring Boot 应用程序,调用方将从注册中心获取服务并调用。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ConsumerApplication implements CommandLineRunner {

    @Autowired
    private HelloServiceConsumer consumer;

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        consumer.callRemoteService();
    }
}

3. 配置服务注册中心

3.1 使用 Nacos 或 ZooKeeper

Dubbo 通常依赖服务注册中心(如 Nacos 或 ZooKeeper)来实现服务注册与发现。

  • Nacos:轻量级服务注册中心,功能更加强大。
  • ZooKeeper:经典的服务注册中心,比较老了。

3.2 配置服务地址

application.properties 或代码中配置服务注册中心的地址。

# 配置 Nacos 地址
dubbo.registry.address=nacos://127.0.0.1:8848

# 或配置 ZooKeeper 地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

总结

通过 Dubbo 提供服务的方式属于 RPC 接入方式,它本质上是一种同步通信机制。这种方式适合微服务架构中的服务间通信,同时提供了高性能和服务治理能力。

三、以 MQ 方式(如 RocketMQ)提供给对方的方式

概述

RocketMQ 是阿里巴巴开源的一款分布式消息中间件,广泛应用于高并发、分布式系统中。通过 RocketMQ 提供服务的方式,通常是基于消息队列的异步通信机制。调用方和提供方通过消息队列进行解耦,实现高效、可靠的消息传递。

特点

  1. 异步通信: 调用方和提供方之间通过消息队列进行解耦。适合高并发、分布式系统的场景。
  2. 高性能: RocketMQ 提供了高效的二进制协议,支持高吞吐量的消息传递。
  3. 可靠性: 支持消息持久化、重试机制,确保消息不会丢失。
  4. 灵活性: 支持多种消息模式(如点对点、发布/订阅)。

适用场景

  • 需要实现异步通信的系统。
  • 高并发、分布式架构中的服务解耦。
  • 数据流处理(如日志收集、事件驱动架构)。
  • 不需要实时响应的场景。

优缺点

优点

  • 异步解耦:调用方和提供方无需直接交互,降低了耦合度。
  • 高并发支持:RocketMQ 能够处理大量消息,适合高并发场景。
  • 可靠性高:支持消息持久化、重试机制,确保消息不丢失。
  • 灵活的消息模式:支持点对点、发布/订阅等多种模式。

缺点

  • 延迟较高:由于是异步通信,不适合实时性要求高的场景。
  • 复杂性增加:需要引入消息队列组件,增加了系统复杂性。
  • 运维成本:需要维护 RocketMQ 集群。

实现步骤

1. 提供方:生产消息

1.1 引入 RocketMQ 依赖

pom.xml 中引入 RocketMQ 的 Maven 依赖:

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.9.3</version>
</dependency>

1.2 编写生产者代码

提供方通过 RocketMQ 生产消息,并将消息发送到指定的 Topic。

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;

public class RocketMQProducer {
    public static void main(String[] args) throws Exception {
        // 创建生产者实例
        DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
        producer.setNamesrvAddr("localhost:9876"); // 设置 NameServer 地址
        producer.start();

        // 创建消息
        Message message = new Message("TopicTest", "TagA", "Hello RocketMQ".getBytes());

        // 发送消息
        producer.send(message);

        // 关闭生产者
        producer.shutdown();
    }
}

2. 调用方:消费消息

2.1 编写消费者代码

调用方通过 RocketMQ 消费消息,并根据业务逻辑处理消息内容。

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class RocketMQConsumer {
    public static void main(String[] args) throws Exception {
        // 创建消费者实例
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");
        consumer.setNamesrvAddr("localhost:9876"); // 设置 NameServer 地址

        // 订阅 Topic
        consumer.subscribe("TopicTest", "*");

        // 注册消息监听器
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.printf("Receive Message: %s%n", new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; // 返回消费成功状态
            }
        });

        // 启动消费者
        consumer.start();
        System.out.println("Consumer Started.");
    }
}

3. 配置 RocketMQ 环境

3.1 安装 RocketMQ

  • 下载 RocketMQ 并解压。
  • 启动 NameServer 和 Broker:

3.2 验证 RocketMQ

  • 使用 RocketMQ 自带的工具验证是否正常运行:

4. 消息模式

4.1 点对点模式

  • 每条消息只能被一个消费者消费。
  • 适用于任务分配、订单处理等场景。

4.2 发布/订阅模式

  • 每条消息可以被多个消费者消费。
  • 适用于广播通知、日志收集等场景。

总结

通过 RocketMQ 提供服务的方式属于 消息队列接入方式,它本质上是一种异步通信机制。这种方式适合高并发、分布式系统中的服务解耦,同时提供了高可靠性和灵活性。

关注【码间烟火录】,获取更多技术干货呀!🚀

#Java##后端#
全部评论
RPC和MQ选型思考
点赞 回复 分享
发布于 02-13 00:08 四川

相关推荐

当指尖敲下最后一个字符,为学妹学弟们准备的分享&nbsp;PPT&nbsp;终于完成时,整个世界仿佛都安静了下来。夜色下的文广,空旷而肃穆,只有几盏路灯散发着昏黄的光晕,洒在冰凉的石板路上。思绪飘回大一下的那个起点,至今已是两年有余。还清晰记得第一次遇见&nbsp;Java&nbsp;的那个下午。当看到“一切皆对象”的优雅,摆脱了C++那恼人的指针和引用,一套&nbsp;Spring&nbsp;Boot&nbsp;仿佛就能“梭哈”整个世界时,内心那份惊喜与震撼,如同第一次窥见宇宙的奥秘,感觉自己仿佛掌握了世界上最美妙的语言,一种近乎信仰的狂热。那时的西三教学楼,水泥地面沾染着岁月的痕迹,又脏又破,一排六人的长桌,肩并肩,空气中弥漫着泡面的味道和青春特有的躁动。我像一块海绵,疯狂吸收着韩顺平老师视频里的&nbsp;Java&nbsp;基础知识,黑马点评、瑞吉外卖……这些如今看来或许简单的项目,在当时,却是我认知里的高山。当第一个项目历经无数次调试,终于在屏幕上成功跑起来的那一刻,那感觉,比第一次登录心心念念的赛尔号还要强烈。第一次鼓起勇气参加腾讯犀牛鸟开源项目,仿佛一脚踏入了另一个世界。名单上,南大的学生赫然过半,其余也皆是顶尖&nbsp;985&nbsp;的身影,博士、研究生比比皆是。看着自己的“211”标签,渺小得几乎看不见,是靠着填报院长名字才勉强挤入的“直通车”。群里的讨论飞速进行,各种高深的术语如同天书,每一次弹出新消息,都让我心头一紧,强烈的焦虑感如同潮水般涌来,我生怕这难得的机会因自己的无知而付诸东流。我索性,把“脸皮”这个东西暂时丢开,&nbsp;怀着忐忑的心情,挨个去“骚扰”群里的各位大佬,小心翼翼地请教。虽然知识储备远远不及,但我凭着一股“东拼西凑”的蛮劲,把大佬们的指点和&nbsp;GPT&nbsp;生成的内容,像炖大杂烩一样,硬生生地整合起来。最终“顺利结业”四个字传来时,悬着的心终于落地,长舒一口气。紧接着,我又一头扎进了字节跳动青训营。这一次,我成为了组长,肩上多了份沉甸甸的责任。带着三个朋友,我们摸索着前进。开发过程异常艰难。如何调试?如何协调团队进度?这些都成了我夜不能寐的难题。最令人崩溃的是,一个队友直接用覆盖的方式将代码&nbsp;push&nbsp;到了我的分支,那一刻,我看着屏幕上消失的代码,感觉整个晚上的心血瞬间消失。但放弃?从未想过。我们咬着牙,在麦当劳嘈杂的人声里,在院楼深夜寂静的灯光下,在星巴克咖啡香气的陪伴中,一次次加班加点,奋力前行。当项目最终成功自测跑起来时,我们几个疲惫的脸上终于露出了笑容。现在回想,那个从零到一,从需求分析到数据库设计,再到代码测试一手包办的项目,虽然界面简陋得有些可笑,但那确确实实是我,一个技术小白,用无数个不眠之夜和笨拙的双手,一点点打磨出的、充满心血的结晶。尽管最后因为疏于项目部署,比赛方无法验收,未能顺利结业,留下了深深的遗憾。但那种第一次当团队owner的感觉,却是难以言表的。大二上的那小半年,现在回想起来,像是一段迷雾笼罩的时光。每天看似坐在图书馆,书本摊开,目光却常常失焦,&nbsp;说是在学习,但知识仿佛都从指缝间溜走,没有真正沉淀进脑海。图书馆安静得能听到自己的心跳,窗外阳光明媚,我内心却是一片阴霾。看着那些埋头苦读的身影,再对比自己低效的“奋斗”,一种时间的空耗感和深深的自我怀疑不断啃噬着我。那时总在想,如果当时选择去实习,去真实的项目中摔打,而不是在校园的象牙塔里“沉淀”,会不会是另一番景象?幸运的是,大二下,一道光照了进来。通过继续教育学院的技术负责人&nbsp;Leo&nbsp;哥,我获得了一个宝贵的机会,加入了后端开发团队。这是我的第一段正式实习,带着一丝忐忑,更多的是对未知的兴奋。团队的氛围出乎意料地好,每个人都非常&nbsp;nice。两位直系学长更是耐心得如同兄长,手把手地带着我修改项目中的&nbsp;Bug。我惊叹于他们搭建的系统架构,那份精妙与优雅,让我看到了更高层次的技术世界。那段时间,我像上满了发条,每天都是第一个到达工位,办公室的灯光常常因我而亮起;晚上,当周围的键盘声渐渐平息,人影散去,我却依然留在空荡的办公室里,贪婪地阅读着他们的代码,试图理解每一个设计背后的深意。那是学习和生活都稳步向前的日子。清晨,我会迎着朝阳去操场跑步,感受汗水浸湿衣衫的畅快;回到宿舍,热一杯香浓的咖啡,开启一天的学习。体重不知不觉降了下来,整个人仿佛脱胎换骨,精神面貌焕然一新,算是一次成功的“爆改”。四月,恰逢江南梅雨季,天空总是灰蒙蒙的,暴雨说来就来,倾盆而下。我常常踩着湿漉漉的鞋,带着一身水汽冲进办公室,但内心却燃烧着一团火,仿佛拥有永远也用不完的精力。暑假,又一份幸运降临,参与了中国移动的线上实习。虽然隔着屏幕,沟通和协作多了些阻碍,项目最终的完成度也并非完美,&nbsp;但这段经历,无疑又为我的开发经验库里增添了宝贵的一笔。大三上,我意识到,是时候真正走向社会,去争取一份中大厂的实习了。我小心翼翼地“包装”着自己的项目经历,像准备一件珍贵的展品,&nbsp;然后开始海投简历。内心深处,对&nbsp;BAT&nbsp;那样高不可攀的名字,还存着一丝胆怯,&nbsp;只敢在&nbsp;Boss&nbsp;直聘上,一遍遍地“骚扰”着中厂的&nbsp;HR。每一次点击发送,都伴随着期待与不安。而那些石沉大海的“已读不回”,刺痛着我敏感的心。当小红书的面试官,语速飞快地说出一连串我闻所未闻的技术名词时,我坐在屏幕前,大脑一片空白,强烈的挫败感和自我怀疑瞬间将我包围:我真的可以吗?我还能有机会进入梦想中的大厂吗?就在这时,货拉拉向我伸出了橄榄枝,那封&nbsp;Offer&nbsp;邮件,如同黑夜中的灯塔,瞬间点亮了我的希望。&nbsp;更令人兴奋的是,我将有机会参与到优惠券、活动这些核心业务中。我收拾行囊,踏上了南下的列车,成为了万千“深漂”中的一员。初到深圳,这座年轻而庞大的城市,对我来说全然陌生。目之所及,是鳞次栉比、直插云霄的高楼大厦,压迫感与繁华感并存。&nbsp;而现实的骨感也迅速袭来:30&nbsp;元一碗的猪脚饭,动辄&nbsp;2000&nbsp;元起步、半年起租的房租,都像一盆冷水,浇灭了我初来乍到的热情,让我望而却步。为了节省开支,抓紧一切时间学习,我买了张简易的床垫,在夜深人静、所有同事都下班后,偷偷铺在空旷的会议室冰凉的地板上。&nbsp;那段日子,睡梦中都充满了警惕,生怕被巡逻的保安发现,更怕被同事撞见这窘迫的一幕。耳边时常传来深夜加班同事匆匆的脚步声,提醒着我这座城市永不停歇的节奏和自己的格格不入。我的工位,不幸地紧挨着厕所。每天下午固定的清洁时间,那刺鼻的消毒水味便会准时弥漫开来,&nbsp;浓烈得让人无法忽视,我只能紧皱眉头,屏住呼吸,默默忍受。&nbsp;而更让我焦虑的是,我的&nbsp;Mentor&nbsp;迟迟没有给我分配具体的开发需求。每天的工作,似乎都围绕着服务治理、权限申请、填写各种表格打转,我感觉自己快要变成一个“Excel&nbsp;Boy”。心中的迷茫和落差感越来越大,一度,强烈的跑路念头在脑海中盘旋。&nbsp;但一想到自己几乎忘光的“八股文”,想到来之不易的机会,我不得不强行按捺住那颗躁动且迷茫的心。于是,我开始了疯狂的自救。我一头扎进了组内的文档库,&nbsp;凭着一股不服输的劲头,几乎把货拉拉整个中台组的技术文档翻了个底朝天。&nbsp;账号、会员、营销活动的核心链路,被我一点点摸索、串联起来;批量派发权益卡、活动接口的二级缓存设计、定时任务的原理、核心接口的&nbsp;Token&nbsp;降级方案……这些原本晦涩难懂的系统设计,都被我硬生生地“扒”了遍,理解了遍。至今还记得,那些独自留在工位,对着电脑屏幕刷力扣到深夜&nbsp;11&nbsp;点的夜晚,窗外城市的霓虹璀璨,室内只有键盘敲击的单调声响;&nbsp;还记得,周末清晨,天还未亮,便爬起来去附近的公园跑山,用大汗淋漓对抗内心的焦虑;&nbsp;还记得,每天雷打不动灌下两大杯黑咖啡,依靠咖啡因强行提神的苦涩与亢奋;&nbsp;更记得,每个周末,拖着疲惫的身躯,往返于广州与深圳之间,高速公路上那仿佛永无止境的塞车,一堵就是两个小时,当终于抵达公司宿舍时,漆黑的夜幕早已低垂,仿佛宣告着新的一天又在奔波中开始了。不知不觉,三个月如白驹过隙。我的大三上,就在这紧张、充实,充满了汗水与挣扎的实习生活中,落下了帷幕。当&nbsp;2&nbsp;月&nbsp;20&nbsp;日,我郑重地投出第一份腾讯的简历时,我知道,我的暑期实习战役,已经提前打响。至今,我已经历了&nbsp;19&nbsp;场面试的洗礼。期间,也曾收到过两份其他大厂的&nbsp;Offer,但我都婉言谢绝了。&nbsp;留在广州,这个念头如同一种宿命,深深地烙印在我心里。可是,广州的大厂,又有多少呢?竞争的激烈可想而知。&nbsp;但我依然坚信,付出了这么多努力,经历了这么多波折的我,一定能做到!此刻,我独自站在文广的中央,仰望着天上那轮清冷的下弦月。月光如水,静静地洒在这片熟悉的广场上,也洒在我的身上。&nbsp;“明月曾照古人”,也照亮过无数个像我一样,心怀梦想与忧愁的人吧。思绪不由得飘回四年前,那个复读的夏天,我也是无数次站在这里,&nbsp;或是放声歌唱,宣泄着青春的迷茫与希望;或是沉默不语,在夜风中细数着内心的焦虑与期盼。四年时光,弹指一挥间。&nbsp;我仿佛还能看到当年那个青涩、懵懂、却眼神坚定的自己。转眼,我的大学时光已接近尾声,留在这片校园的日子,屈指可数。​
点赞 评论 收藏
分享
04-11 09:14
已编辑
门头沟学院 Java
感觉问题都好难,还是太菜了#牛客AI配图神器#1、Spring中的@SpringBootApplication注解的原理是什么?由哪些组合注解组成?2、Spring启动过程中需要多少个Bean3、@Component和@Bean的区别是什么?4、Bean的生命周期?5、Bean的作用域有哪些?BeanFactory和FactoryBean有什么区别?6、Spring中最重要的两个概念是什么?(AOP和IOC)7、Spring管理事务的方式有哪些?8、Spring事务中哪些事务传播行为?9、@Transactional的实现原理?10、Java有开发框架了解哪些?11、Hibernate了解过嘛?使用场景?12、Java中的Socket编程有了解过嘛?13、Lua脚本有了解过嘛?有哪些注意事项?缺点是什么?14、常见的线程池有哪些?15、线程池的执行原理?16、ThreadLocal的实现原理,需要注意什么?缺点是什么?17、JUC包知道哪些?怎么使用?18、ConcurrentHashmap了解过嘛?扩容机制呢?19、ConcurrentHashmap实现原理是什么?并发机制是什么?20、SQL和noSQL的优缺点分别是什么?21、Mysql中有哪些索引,场景分别是什么?22、在哪些场景下使用过redis?23、Redis怎么保证与Mysql数据一致性?24、除了Redis还有哪些noSQL?25、Mongodb是什么?优缺点?26、Gradle是什么?怎么使用?使用场景?27、关心过业务系统里面的sql耗时嘛?统计过慢查询嘛?对慢查询都是怎么优化的?28、Mysql中模糊查询的%和_的区别?29、MySQL中的binlog知道原理嘛?30、项目中怎么去进行SQL调优?31、多线程中哪些参数?start()和run()的区别是什么?32、Volatile和synchronized的区别是什么?
点赞 评论 收藏
分享
评论
4
4
分享

创作者周榜

更多
牛客网
牛客企业服务