Project URL
https://github.com/merikbest/twitter-spring-reactjs
正确的安装和启动方法
原作者很懒, 没写清楚, 所以我来写
Installation
- Install maven: link
- Install Java: link
- Install Postgresql: link
- Install Intellij IDEA Ultimate: link
- Install Docker and Docker Desktop
- Add Lombok plugin to the Intellij IDEA: link
- Make sure Java 17 is selected: link
- Build the project with Maven: link
- Sign up for a new AWS account: link
- Create a new AWS S3 bucket: link
- Change access from private to public in the AWS S3 bucket
- Add a public access policy to the AWS S3 bucket (!!!important!!! see:
doc,
github examle or
my example) - Get AWS keys: link and add to the application.properties file: link
- In the image-service.yml config file add bucket, access-key, secret-key properties
- Sign up for gmail
- Create google API keys: link
- Add google API key to the tweet-service.yml config file
- Add gmail account and password to the email-service.yml config file
- Go to link (important) and change to: “Allow less secure apps: ON”
Run
- (only for the first time) run
docker compose up --build
- run these services on your Docker Desktop:
postgres
,pgadmin
,zipkin
,rabbitmq
,zookeeper
,broker
(kafka) link - (only for the first time) Open http://localhost:5050/browser/ and create DBs:
user
,tweet
,chat
,lists
,notification
,tag
,topic
,scheduler
,localization
- Run services in this order:
- eureka-server
- config-server
- api-gateway
- user-service
- and then all other services in any order link
- Open terminal in frontend directory and run these commands step by step:
cd ./frontend/
nvm install v16.16.0
nvm alias default 16.16.0
nvm use 16.16.0
npm install -g npm@8.17.0
node -v
npm -v
npm config set legacy-peer-deps true
npm uninstall node-sass
npm install sass
rm -rf node_modules package-lock.json
npm install --legacy-peer-deps
npm list ajv
npm install ajv@^8 ajv-keywords
npm list ajv
- Open terminal in frontend directory and type:
npm start
, then “Wait almost a minute for its boot-up.” 等差不多一分钟让它启动。 - Navigate to http://localhost:3000/home
To enter the application you can register or login:
- Login:
user2016@gmail.com
- Password:
qwerty123
项目里使用了哪些SpringCloud的东西
你看pom.xml
里如果有类似于引入spring-cloud
开头的依赖就是用到了SpringCloud的东西<artifactId>spring-cloud-starter-zipkin</artifactId>
1. Spring Cloud OpenFeign
在这个 Twitter 克隆项目里,Spring Cloud 提供了一系列组件,为构建微服务架构提供了强大的支持。结合代码片段和 README.md
文件,项目中涉及的 Spring Cloud 相关组件如下:
- 功能:Spring Cloud OpenFeign 是一个声明式的 HTTP 客户端,它使得编写 Web 服务客户端变得更加简单。通过定义接口和使用注解,就可以轻松地调用其他微服务的 RESTful API。
- 代码体现:多个服务的主类上都添加了
@EnableFeignClients
注解,启用了 Feign 客户端功能。// twitter-spring-reactjs/user-service/src/main/java/com/gmail/merikbest2015/UserServiceApplication.java
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
2. Spring Cloud Circuit Breaker (Resilience4j)
- 功能:Spring Cloud Circuit Breaker 是一个抽象层,用于在微服务之间实现熔断、限流等容错机制。Resilience4j 是其中一种具体的实现,它可以帮助微服务在出现故障时快速失败,避免级联故障。
- 代码体现:在多个服务的
pom.xml
文件中引入了spring-cloud-starter-circuitbreaker-resilience4j
依赖。<!-- twitter-spring-reactjs/tag-service/pom.xml -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
同时,在配置文件中配置了熔断实例的参数,例如 resilience4j.circuitbreaker.instances
。# twitter-spring-reactjs/config-server/src/main/resources/config/topic-service.yml
resilience4j:
circuitbreaker:
instances:
user-service:
minimum-number-of-calls: 5
3. Spring Cloud Config
- 功能:Spring Cloud Config 为微服务架构中的应用提供了集中化的外部配置支持。它允许开发者将配置文件存储在远程仓库(如 Git)中,各个微服务可以从配置服务器获取配置信息,实现配置的统一管理和动态更新。
- 代码体现:各个服务的
bootstrap.yml
文件中都配置了配置服务器的地址。# twitter-spring-reactjs/tweet-service/src/main/resources/bootstrap.yml
spring:
application:
name: tweet-service
cloud:
config:
uri: http://${CONFIG_SERVER_HOST:localhost}:8888/
配置服务器的主类添加了 @EnableConfigServer
注解,启用了配置服务器功能。// twitter-spring-reactjs/config-server/src/main/java/com/gmail/merikbest2015/ConfigServerApplication.java
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
4. Spring Cloud Netflix Eureka
- 功能:Spring Cloud Netflix Eureka 是一个服务发现组件,它提供了服务注册和发现的功能。微服务可以将自己注册到 Eureka 服务器,其他微服务可以从 Eureka 服务器获取服务列表,实现服务之间的调用。
- 代码体现:Eureka 服务器的主类添加了
@EnableEurekaServer
注解,启用了 Eureka 服务器功能。// twitter-spring-reactjs/eureka-server/src/main/java/com/gmail/merikbest2015/EurekaServerApplication.java
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
各个服务的配置文件中都配置了 Eureka 服务器的地址,并设置了是否注册和获取服务列表的参数。# twitter-spring-reactjs/config-server/src/main/resources/config/topic-service.yml
eureka:
client:
service-url:
defaultZone: http://${EUREKA_HOST:localhost}:8761/eureka
fetch-registry: true
register-with-eureka: true
instance:
hostname: ${EUREKA_HOST:localhost}
5. Spring Cloud Gateway
- 功能:Spring Cloud Gateway 是一个基于 Spring WebFlux 的 API 网关,它提供了路由、过滤等功能。可以将客户端的请求路由到不同的微服务,并对请求进行预处理和后处理。
- 代码体现:API 网关服务的主类排除了数据源自动配置,并启用了 Feign 客户端。
// twitter-spring-reactjs/api-gateway/src/main/java/com/gmail/merikbest2015/ApiGatewayApplication.java
(exclude = {DataSourceAutoConfiguration.class})
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
同时,还配置了 WebSocket 相关的 Bean,以支持 WebSocket 通信。// twitter-spring-reactjs/api-gateway/src/main/java/com/gmail/merikbest2015/configuration/WebSocketConfig.java
public class WebSocketConfig {
public RequestUpgradeStrategy requestUpgradeStrategy() {
return new TomcatRequestUpgradeStrategy();
}
WebSocketClient tomcatWebSocketClient() {
return new TomcatWebSocketClient();
}
}
综上所述,这个项目中使用的 Spring Cloud 组件包括 OpenFeign、Circuit Breaker、Config、Eureka 和 Gateway,它们共同构成了一个完整的微服务架构,提供了服务发现、配置管理、容错、API 网关等功能。
Prometheus和Grafana和Actuator和Zipkin
zipkin如何是啥?zipkin是springboot自带的吗?
注: 本项目twitter clone中似乎没有引入这俩依赖, 所以并没有完全接入zipkin的功能
在 Spring Boot 项目中接入和使用 Zipkin 可以帮助你实现分布式系统的链路追踪,以下是详细的接入和使用步骤,结合代码库中的信息来进行说明:
项目 | 是否 Spring Boot 自带 | 说明 |
---|---|---|
Zipkin | 否 | 独立系统,用于收集和查询链路追踪数据(如请求调用链) |
Spring Cloud Sleuth | 否(需引入) | Spring 官方提供的链路追踪库,用于生成 trace/span 数据,并可发送给 Zipkin |
Spring Boot Actuator | 是(部分模块) | 用于监控、指标收集,不包含 Zipkin 或 Sleuth |
1. 添加依赖
在 pom.xml
文件中添加 Spring Cloud Sleuth 和 Zipkin 相关的依赖,不过代码库中未展示各服务具体依赖添加情况,但一般需添加如下依赖:<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
2. 配置 Zipkin 服务地址
在每个 Spring Boot 服务的配置文件中,需要配置 Zipkin 的服务地址。以 config-server/src/main/resources/config/lists-service.yml
为例:spring:
zipkin:
base-url: http://${ZIPKIN_HOST:localhost}:9411
上述配置指定了 Zipkin 服务的基础 URL,这里使用了环境变量 ${ZIPKIN_HOST}
,默认值为 localhost
,端口为 9411
。
3. 启动 Zipkin 服务
在 docker-compose.yml
文件中可以看到 Zipkin 服务的配置:zipkin:
image: openzipkin/zipkin
container_name: zipkin
ports:
- "9411:9411"
networks:
- spring
使用 Docker Compose 启动该配置,即可启动 Zipkin 服务,它会监听 9411
端口。
4. 服务注册与发现(可选)
如果项目使用了服务注册与发现机制(如 Eureka),各服务需要在配置文件中配置 Eureka 相关信息,以确保 Zipkin 能正确识别和追踪服务。以 config-server/src/main/resources/config/lists-service.yml
为例:eureka:
client:
service-url:
defaultZone: http://${EUREKA_HOST:localhost}:8761/eureka
fetch-registry: true
register-with-eureka: true
instance:
hostname: ${EUREKA_HOST:localhost}
5. 验证接入
启动所有 Spring Boot 服务和 Zipkin 服务后,当服务之间进行调用时,Sleuth 会自动收集链路追踪信息,并将其发送到 Zipkin 服务。你可以通过访问 http://localhost:9411
打开 Zipkin 的 Web 界面,在该界面中可以查看和分析链路追踪数据。
6. 示例代码分析
在代码库的多个服务配置文件中都有 Zipkin 相关配置,如 chat-service
、user-service
等,这表明这些服务都接入了 Zipkin 进行链路追踪。以下是 chat-service
的配置示例:spring:
zipkin:
base-url: http://${ZIPKIN_HOST:localhost}:9411
这说明 chat-service
会将链路追踪数据发送到指定的 Zipkin 服务地址。
总结
通过以上步骤,你可以在 Spring Boot 项目中成功接入和使用 Zipkin 进行分布式系统的链路追踪。在实际项目中,各服务会根据配置自动收集和发送链路追踪数据,开发人员可以通过 Zipkin 的 Web 界面查看和分析这些数据,从而更好地理解系统的调用关系和性能瓶颈。
用表格说明: prometheus和grafana和actuator区别
比较项 | Prometheus | Grafana | Actuator |
---|---|---|---|
定位 | 开源的系统监控和警报工具包,专注于收集和存储时间序列数据。 | 开源的可视化分析平台,用于将收集到的数据以直观的图表、仪表盘等形式展示出来。 | Spring Boot 提供的比较简单原始的监控和管理工具,用于监控和管理 Spring Boot 应用程序内部的运行状态。 |
功能 | 1. 数据收集:从各种数据源(如 Actuator 暴露的端点)收集指标数据。 2. 数据存储:将收集到的数据以时间序列的形式存储在本地磁盘。 3. 查询语言:提供 PromQL 查询语言,用于对存储的数据进行查询和分析。 4. 警报功能:支持设置警报规则,当指标数据满足特定条件时触发警报。 |
1. 数据可视化:支持多种数据源(如 Prometheus、InfluxDB 等),可以将数据以图表、表格、仪表盘等形式进行可视化展示。 2. 多数据源支持:可以同时连接多个不同类型的数据源,并在同一个仪表盘上展示不同数据源的数据。 3. 交互式仪表盘:提供丰富的交互功能,如缩放、过滤、钻取等,方便用户深入分析数据。 4. 警报通知:支持设置警报规则,并通过多种方式(如邮件、Slack 等)发送警报通知。 |
1. 端点暴露:提供一系列的端点(Endpoints),通过 HTTP 或者 JMX 来访问应用的运行状态信息,如健康状况、内存使用情况、线程池状态等。 2. 自定义扩展:开发者可以根据自己的需求自定义 Actuator 端点,以满足特定的监控和管理需求。 |
使用场景 | 1. 监控大规模分布式系统的性能指标,如 CPU 使用率、内存使用率、网络带宽等。 2. 对系统的运行状态进行实时监控和分析,及时发现潜在的问题。 3. 设置警报规则,当系统出现异常时及时通知相关人员。 |
1. 将 Prometheus 收集到的数据进行可视化展示,方便用户直观地了解系统的运行状态。 2. 创建交互式仪表盘,用于展示不同维度的数据,帮助用户进行数据分析和决策。 3. 对多个数据源的数据进行整合和展示,提供统一的监控视图。 |
1. 监控 Spring Boot 应用程序的内部状态,如健康检查、指标监控等。 2. 对应用程序进行管理和配置,如关闭应用、刷新配置等。 3. 自定义监控指标,满足特定的业务需求。 |
在代码库中的体现 | 在 twitter-spring-reactjs/dashboard/prometheus.yml 文件中,定义了多个服务的抓取配置,指定了抓取间隔、指标路径和目标地址,用于从各个服务的 /actuator/prometheus 端点收集指标数据。 |
在 twitter-spring-reactjs/docker-compose.yml 文件中,定义了 Grafana 服务的配置,包括端口映射、环境变量等。同时,在 twitter-spring-reactjs/dashboard/grafana/provisioning/micrometer-spring-throughput_rev2.json 文件中,定义了 Grafana 的仪表盘配置,用于展示从 Prometheus 收集到的数据。 |
在 twitter-spring-reactjs/dashboard/prometheus.yml 文件中,多个服务的 metrics_path 都设置为 /actuator/prometheus ,表明这些服务使用了 Spring Boot Actuator 来暴露指标数据,供 Prometheus 进行采集。 |
Actuator已经很好, 为啥还需要 Prometheus?
你可以直接用 Actuator 查看指标,但它只是“原始数据源”,不是完整监控方案。
用 Actuator 直接访问的缺点:
功能 | Actuator | Prometheus + Grafana |
---|---|---|
实时查看数据 | ✅ | ✅ |
数据持久化 | ❌(仅返回当前快照) | ✅(自动存历史) |
图表展示 | ❌(纯文本) | ✅(灵活图形化) |
告警设置 | ❌ | ✅(如 CPU 超限发通知) |
多服务统一管理 | ❌(每个服务要单独访问) | ✅(集中式平台) |
数据查询/聚合 | ❌(不支持查询) | ✅(支持 PromQL 查询) |
⸻
结论:
如果你只想调试一个服务、临时看看运行状态,Actuator 是够用的。
但如果你要:
• 持续观察趋势
• 多服务统一监控
• 设置告警
• 做性能优化
那就必须用 Prometheus + Grafana。Actuator 是数据源,不是仪表盘或监控系统。
一句话总结:
Actuator 是喇叭,Prometheus + Grafana 才是耳朵 + 眼睛 + 脑子。
Prometheus 为啥依赖 Actuator?
因为 Spring Boot Actuator 是官方提供的监控和管理模块,负责暴露各种运行时指标、健康检查、环境变量、线程信息等。你要通过 Grafana + Prometheus 监控 Spring Boot 应用,核心就是这些运行时指标,而这些指标就是由 Actuator 提供并暴露出来的。
为什么 Prometheus 要依赖 Actuator?
Prometheus 抓取的 /actuator/prometheus 接口就是 Actuator 提供的,里面包含了 Micrometer 采集到的各种系统、JVM、Spring 应用运行数据,比如:
• JVM 内存、GC、线程数
• HTTP 请求次数、耗时
• 数据库连接池状态
• 自定义业务指标(如果你添加了)
如果你不加 spring-boot-starter-actuator,Spring Boot 就不会暴露这些信息,Prometheus 根本无从抓取。
⸻
核心依赖关系如下:
Spring Boot 应用
└── Actuator 暴露应用运行指标
└── Micrometer 提供统一指标采集接口
└── micrometer-registry-prometheus 把指标转成 Prometheus 能读懂的格式
└── Prometheus 抓取数据
└── Grafana 展示数据
⸻
结论:
要想 Prometheus 能监控 Spring Boot,必须依赖 Actuator 提供 /actuator/prometheus 端点,这是数据的来源,没有 Actuator 就没有监控数据。你可以把它理解为监控系统的“数据出口”。
Actuactor整体如何配置里开启以及代码里如何接入?
Spring Boot Actuator 是一个内建模块,用来暴露运行时的系统信息(健康检查、Bean、线程、内存、指标等),用于监控、报警、分析系统状态。要完整启用并接入,分为两步:
⸻
✅ 一、配置文件里开启 Actuator 功能
- 引入依赖
pom.xml 中添加(Spring Boot 2.2+ 推荐使用 spring-boot-starter-actuator):<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
⸻
- application.yml 中开启端点
management:
endpoints:
web:
exposure:
include: "*" ## 暴露所有端点,也可以是 ["health", "info", "metrics"]
endpoint:
health:
show-details: always ## 显示详细健康信息
metrics:
export:
prometheus:
enabled: true
server:
port: 8081 ## 可选,独立起一个 actuator 管理端口
⸻
✅ 二、代码中接入并使用
你不需要显式写任何代码来获取 actuator 功能。Spring Boot 自动装配这些端点:
功能 访问路径 描述
- Health Check /actuator/health 查看服务是否正常
- Info /actuator/info 展示项目信息(需配置)
- Beans /actuator/beans 所有 Spring Bean 列表
- Metrics /actuator/metrics 所有可用的系统指标
- Prometheus /actuator/prometheus Prometheus 格式的监控数据
- Env /actuator/env 当前系统环境变量
- Loggers /actuator/loggers 动态调整日志级别
- Mappings /actuator/mappings 所有请求路径和控制器映射
🔐 如果你用了 Spring Security,还要额外开放 Actuator 的访问权限(我可以给你配置模板)。
⸻
✅ 四、可选:与 Prometheus + Grafana 联动
1. 保证 Prometheus 能访问 /actuator/prometheus
2. Prometheus 配置中加 target:
- job_name: 'user-service' |
3. Grafana 添加 Prometheus 数据源即可。
⸻
Grafana本身不需要接入到springboot对吗? 他只需要到网页端配置数据源就行了
是的,完全正确。Grafana 本身不需要接入 Spring Boot 应用,它只是一个可视化平台,通过配置数据源(如 Prometheus、InfluxDB、Loki 等)拉取数据并展示图表。
⸻
✅ 你只需做两件事:
- Spring Boot 应用负责暴露监控数据
例如通过 Actuator 暴露:
http://localhost:8080/actuator/prometheus
- Grafana 配置数据源(Prometheus)
• 在浏览器访问 Grafana(通常是 http://localhost:3000)
• 左侧菜单 → ⚙️ Configuration → Data Sources
• 选择 Prometheus → 填写 URL(如 http://localhost:9090)
• 点击 Save & Test
⸻
🔁 之后的链路如下:
Spring Boot (Actuator) –> Prometheus –> Grafana Dashboard
Spring Boot 不需要感知 Grafana 的存在,完全解耦。只要你的 Prometheus 能抓到数据,Grafana就能展示。你也可以把多个微服务接入同一个 Prometheus,然后在 Grafana 中统一展示。
项目里Kafka咋用的
在这个 Twitter 克隆项目中,
- Kafka 作为消息中间件,用于在各个微服务之间实现异步通信和解耦,
- Feign用作微服务之间的同步通信。
以下是 Kafka 在项目中的具体使用方式:
1. 配置 Kafka
1.1 生产者配置
多个服务中都有 Kafka 生产者的配置,以 user-service
为例,在 KafkaProducerConfiguration
类中:
public class KafkaProducerConfiguration {
"${spring.kafka.bootstrap-servers}") (
private String bootstrapServers;
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
return props;
}
public KafkaTemplate<String, UpdateUserEvent> kafkaUpdateUserTemplate() {
return new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(producerConfigs()));
}
// 其他 KafkaTemplate Bean 定义...
}
这里配置了 Kafka 生产者的基本属性,如 bootstrap.servers
、键和值的序列化器。通过创建不同的 KafkaTemplate
Bean,可以向不同的主题发送不同类型的消息。
在提供的代码中,涉及到了 Apache Kafka 相关的配置,其中 bootstrap.servers
、键和值的序列化器以及 KafkaTemplate
Bean 都是 Kafka 配置和使用过程中的重要概念,下面为你详细解释:
1. bootstrap.servers
bootstrap.servers
是 Kafka 客户端(生产者或消费者)用于连接 Kafka 集群的初始服务器列表。客户端使用这些服务器来发现整个 Kafka 集群的元数据信息,包括所有的 broker 节点、主题分区等。在代码里,这个配置通常从 Spring 的配置文件中读取,然后用于创建 Kafka 生产者或消费者的配置。
示例代码:"${spring.kafka.bootstrap-servers}") (
private String bootstrapServers;
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
// 其他配置...
return props;
}
在上述代码中,bootstrapServers
从 spring.kafka.bootstrap-servers
配置项中获取,然后添加到生产者配置 props
中。
2. 键和值的序列化器
在 Kafka 中,消息是以字节形式存储和传输的。因此,在生产者将消息发送到 Kafka 之前,需要将消息的键和值序列化为字节数组;而在消费者接收到消息后,需要将字节数组反序列化为对象。这就需要使用序列化器和反序列化器。
序列化器
- 键序列化器(
KEY_SERIALIZER_CLASS_CONFIG
):用于将消息的键序列化为字节数组。 - 值序列化器(
VALUE_SERIALIZER_CLASS_CONFIG
):用于将消息的值序列化为字节数组。
示例代码:
public Map<String, Object> producerConfigs() {
Map<String, Object> props = new HashMap<>();
// ...
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
return props;
}
在上述代码中,键使用 StringSerializer
进行序列化,值使用 JsonSerializer
进行序列化。
反序列化器
在消费者端,需要使用相应的反序列化器将字节数组反序列化为对象。
示例代码:
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>();
// ...
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
return props;
}
在上述代码中,键使用 StringDeserializer
进行反序列化,值使用 JsonDeserializer
进行反序列化。
3. KafkaTemplate
Bean
KafkaTemplate
是 Spring Kafka 提供的一个高级抽象,用于简化 Kafka 生产者的使用。通过创建 KafkaTemplate
Bean,可以方便地向 Kafka 主题发送消息。
示例代码:
public KafkaTemplate<String, UpdateTweetCountEvent> kafkaUpdateTweetCountTemplate() {
return new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(producerConfigs()));
}
在上述代码中,创建了一个 KafkaTemplate
Bean,用于发送键为 String
类型,值为 ListsNotificationEvent
类型的消息。可以通过注入这个 KafkaTemplate
Bean,在代码中使用它来发送消息:private final KafkaTemplate<String, UpdateTweetCountEvent> kafkaTemplate;
public void sendUpdateTweetCountEvent(Long authUserId, boolean hasRetweeted) {
kafkaTemplate.send(getUpdateTweetCountEvent(KafkaTopicConstants.UPDATE_USER_TWEETS_COUNT_TOPIC, authUserId, hasRetweeted));
}
综上所述,bootstrap.servers
用于连接 Kafka 集群,键和值的序列化器用于消息的序列化和反序列化,KafkaTemplate
Bean 用于简化 Kafka 生产者的使用。这些配置和组件共同构成了项目中 Kafka 消息传递的基础。
1.2 消费者配置
以 tweet-service
为例,在 KafkaConsumerConfiguration
类中:
public class KafkaConsumerConfiguration {
"${spring.kafka.bootstrap-servers}") (
private String bootstrapServers;
"${spring.kafka.consumer.group-id}") (
private String groupId;
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
return props;
}
public ConsumerFactory<String, UpdateUserEvent> userConsumerFactory() {
return new DefaultKafkaConsumerFactory<>(
consumerConfigs(),
new StringDeserializer(),
new JsonDeserializer<>(UpdateUserEvent.class)
);
}
// 其他 ConsumerFactory Bean 定义...
}
这里配置了 Kafka 消费者的基本属性,如 bootstrap.servers
、消费者组 ID、键和值的反序列化器。通过创建不同的 ConsumerFactory
Bean,可以从不同的主题消费不同类型的消息。
2. 发送消息(生产者)
项目中有多个生产者类,用于向 Kafka 主题发送不同类型的消息。例如,PinTweetProducer
类:
public class PinTweetProducer {
private final KafkaTemplate<String, PinTweetEvent> kafkaTemplate;
public void sendPinTweetEvent(Long tweetId, Long authUserId) {
PinTweetEvent pinTweetEvent = PinTweetEvent.builder()
.tweetId(tweetId)
.build();
kafkaTemplate.send(ProducerUtil.authHeaderWrapper(KafkaTopicConstants.PIN_TWEET_USER_TOPIC, pinTweetEvent, authUserId));
}
}
该类使用 KafkaTemplate
向 PIN_TWEET_USER_TOPIC
主题发送 PinTweetEvent
类型的消息。
3. 接收消息(消费者)
虽然代码片段中没有展示完整的消费者监听器,但从配置文件可以推测,每个服务会根据需要创建相应的消费者监听器来处理接收到的消息。例如,在 tweet-service
中配置了多个 ConsumerFactory
,可以用于创建消费者监听器来处理不同类型的事件。
4. 主题和事件类型
项目中定义了多个 Kafka 主题和对应的事件类型,用于不同的业务场景。例如:
KafkaTopicConstants.PIN_TWEET_USER_TOPIC
:用于处理固定推文的事件。KafkaTopicConstants.UPDATE_USER_TOPIC
:用于处理用户信息更新的事件。
不同的服务根据业务需求向不同的主题发送和接收消息,实现了微服务之间的解耦和异步通信。
5. 容器化部署
在 docker-compose.yml
文件中,配置了 Kafka 容器:broker:
image: confluentinc/cp-kafka:7.0.1
container_name: broker
ports:
- "9092:9092"
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_INTERNAL:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,PLAINTEXT_INTERNAL://broker:29092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
networks:
- spring
通过 Docker 容器化部署 Kafka,方便在开发和生产环境中使用。
综上所述,Kafka 在项目中用于微服务之间的异步通信,通过配置生产者和消费者,不同的服务可以向不同的主题发送和接收消息,实现了系统的解耦和扩展性。
RabbitMQ 和 Kafka区别
注: 项目中并没有实际用到rabbitmq, 没有相关代码, 只是docker-compose.yml里有而已
在微服务架构中选择消息中间件,RabbitMQ 和 Kafka 各有优劣。以下是 RabbitMQ 相对于 Kafka 的几个核心优势,适用于一些特定的微服务场景:
✅ RabbitMQ 的优势(对比 Kafka)
最重要的优势: 低延迟,适合实时请求处理
- • RabbitMQ 延迟低,适合请求-响应模型或对消息实时性要求高的场景。
- • Kafka 更适合吞吐优先的批量处理或数据管道场景。
🎯 原因分析:- • RabbitMQ 是推模型:生产者投递后,broker 立即推送给消费者,几乎没有消费延迟。
- • Kafka 是拉模型:消费者轮询 poll(),加上批处理机制,会带来一定延迟。
- • Kafka 强一致性 + 持久化机制开销更大:Kafka 的写入一般需要刷盘(acks=all),而 RabbitMQ 可配置为内存优先。
📌 实际经验建议: - • 对低延迟有极致要求(<10ms) → RabbitMQ 更合适。
- • 对延迟不敏感但吞吐要求高(如日志处理、大规模流式处理)→ Kafka 明显更强。
指标 | RabbitMQ | Kafka |
---|---|---|
端到端延迟(低负载) | 1–5 毫秒 | 10–100 毫秒 |
端到端延迟(中高负载) | 5–20 毫秒 | 50–200 毫秒 |
写入延迟 | 1–2 毫秒 | 5–50 毫秒(取决于 acks 策略、flush 等) |
消费延迟 | 实时推送(基本无延迟) | 拉取模式(取决于 poll 周期) |
更强的消息路由能力(AMQP)
• RabbitMQ 支持多种 Exchange 类型(Direct、Topic、Fanout、Headers),可以灵活路由消息。
• Kafka 的消息路由主要依赖于 topic 和 key,灵活性较低。成熟的消息确认与重试机制
• RabbitMQ 支持 per-message ack、dead-letter queue(DLQ)、retry、延迟队列等,适合微服务间复杂的消息投递保障。
• Kafka 虽然支持 consumer offset 和手动提交,但重试和死信处理需额外实现。资源开销更低,部署简单
• RabbitMQ 比 Kafka 更轻量,部署简单,适合中小型服务。
• Kafka 对 ZooKeeper、磁盘 IO、网络吞吐要求更高,复杂度更高。支持多种协议(AMQP、MQTT、STOMP、HTTP)
• RabbitMQ 协议层灵活,可以适配各种客户端,利于异构系统集成。适合命令、事件混合型系统
• 微服务之间不仅仅传递日志/事件,还包括任务指令(Command);RabbitMQ 更适合这种场景。
🟨 使用 RabbitMQ 的典型场景
• RPC(远程过程调用)
• 实时任务分发(如订单处理)
• 异步事务补偿(Saga 模式)
• 高可靠性消息传递(确保每条消息都处理)
⚠️ 什么时候不要用 RabbitMQ?
• 需要极高吞吐量(如百万级 TPS) → 用 Kafka。
• 用作事件溯源(Event Sourcing)、日志聚合、大数据采集 → Kafka 更合适。
• 大量消费者并行处理历史消息 → Kafka 优势明显。
总结一句话:
RabbitMQ 适合服务间高可靠、低延迟、灵活路由的消息传递;Kafka 适合高吞吐、可持久化、大数据驱动的事件流架构。
Resilience4j? Feign 是啥? 默认同步调用的话, 不会卡死吗?
1. Resilience4j 是什么
Resilience4j 是一个轻量级、易于使用的容错库,专为 Java 8 及以上版本设计,主要用于增强分布式系统中服务调用的稳定性和弹性。它提供了多种容错模式,例如断路器(Circuit Breaker)、限流(Rate Limiter)、重试(Retry)、限时器(Time Limiter)等。
在代码库中的体现
在 twitter - spring - reactjs
项目里,多个服务的 pom.xml
文件都引入了 spring - cloud - starter - circuitbreaker - resilience4j
依赖,像 notification - service/pom.xml
、lists - service/pom.xml
等,表明项目使用 Resilience4j 来实现服务调用的容错功能。
<dependency> |
同时,在多个服务的配置文件(如 topic - service.yml
、tag - service.yml
等)中配置了断路器的参数,例如:resilience4j:
circuitbreaker:
instances:
user - service:
minimum - number - of - calls: 5
2. Feign 是什么
Feign 是一个声明式的 Web 服务客户端,由 Netflix 开发并开源。它使得编写 Web 服务客户端变得更加简单,只需创建一个接口并在接口上添加注解,Feign 就会自动创建该接口的实现类并完成 HTTP 请求的发送。
在代码库中的体现
在 twitter - spring - reactjs
项目中,有大量使用 Feign 的示例。例如 scheduler - service/src/main/java/com/gmail/merikbest2015/client/TweetClient.java
文件中: (name = FeignConstants.TWEET_SERVICE, path = PathConstants.API_V1_TWEETS, configuration = FeignConfiguration.class)
public interface TweetClient {
(PathConstants.USER_BATCH_JOB)
void runImportUsersBatchJob();
}
这里定义了一个 Feign 客户端接口 TweetClient
,用于调用 tweet - service
的相关接口。
3. 默认同步调用是否会卡死
默认情况下,Feign 是同步调用的,即客户端在发送请求后会阻塞,直到收到服务端的响应。在某些情况下,同步调用可能会导致线程阻塞,甚至出现卡死的情况,例如:
- 服务端响应慢:如果服务端处理请求的时间过长,客户端线程会一直等待,可能会导致线程资源耗尽,影响系统的性能。
- 服务端故障:如果服务端出现故障,无法正常响应请求,客户端线程会一直阻塞,直到超时。
解决方法
为了避免同步调用带来的问题,可以结合使用 Resilience4j 提供的容错机制:
断路器(Circuit Breaker):当服务调用失败的次数达到一定阈值时,断路器会打开,后续的请求将直接返回错误,而不是继续等待服务端的响应,避免线程长时间阻塞。例如在
tag - service/src/main/java/com/gmail/merikbest2015/client/TweetClient.java
中:"defaultEmptyTweetList") (name = FeignConstants.TWEET_SERVICE, fallbackMethod =
(value = FeignConstants.TWEET_SERVICE, path = PathConstants.API_V1_TWEETS, configuration = FeignConfiguration.class)
public interface TweetClient {
(PathConstants.IDS)
List<TweetResponse> getTweetsByIds(@RequestBody IdsRequest request);
default ArrayList<TweetResponse> defaultEmptyTweetList(Throwable throwable) {
return new ArrayList<>();
}
}限时器(Time Limiter):可以为每个服务调用设置一个超时时间,当调用时间超过该时间时,会抛出超时异常,避免线程无限期阻塞。
通过这些容错机制,可以有效提高系统的稳定性和可靠性,避免同步调用可能带来的卡死问题。
Lombok 是 @Entity 的替代品吗? 优势如何?
Lombok 并不是 @Entity
的替代品,它们在 Java 开发中承担着不同的角色,下面从它们各自的功能、区别以及 Lombok 的优势等方面进行详细分析。
1. @Entity
和 Lombok 的功能
@Entity
:@Entity
是 JPA(Java Persistence API)中的一个注解,用于将一个 Java 类映射到数据库中的一张表。它主要用于定义实体类,使得该类可以与数据库中的表进行交互,实现数据的持久化操作。在代码库中,像twitter-spring-reactjs/tweet-service/src/main/java/com/gmail/merikbest2015/model/User.java
文件里的User
类就使用了@Entity
注解:
"id") (of =
"users") (name =
public class User {
// 类的属性和方法
}Lombok:Lombok 是一个 Java 库,它通过注解的方式,在编译阶段自动生成 Java 类的 getter、setter、构造函数、toString 等方法,从而减少样板代码的编写。例如,上述
User
类中使用的@Getter
、@Setter
、@NoArgsConstructor
等注解都是 Lombok 提供的。
2. 为什么 Lombok 不是 @Entity
的替代品
- 功能侧重点不同:
@Entity
的核心功能是建立 Java 类与数据库表之间的映射关系,侧重于数据持久化方面的操作;而 Lombok 主要是为了简化 Java 类的编写,减少重复代码,提高开发效率。 - 相互配合使用:在实际开发中,
@Entity
和 Lombok 通常是相互配合使用的。例如,在定义实体类时,既需要使用@Entity
注解将类映射到数据库表,又可以使用 Lombok 注解来自动生成一些必要的方法。
3. Lombok 的优势
- 减少样板代码:使用 Lombok 可以显著减少 Java 类中的样板代码。例如,在没有使用 Lombok 的情况下,需要手动编写 getter、setter、构造函数等方法:
public class User {
private Long id;
private String fullName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public User() {
}
}
而使用 Lombok 后,只需要添加相应的注解即可:import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
public class User {
private Long id;
private String fullName;
}
- 提高代码的可读性和可维护性:减少了大量的样板代码后,代码变得更加简洁,开发者可以更专注于业务逻辑的实现,提高了代码的可读性和可维护性。
- 降低出错概率:手动编写 getter、setter 等方法时,容易出现拼写错误或逻辑错误。使用 Lombok 自动生成这些方法可以避免这些问题,降低出错概率。
综上所述,Lombok 和 @Entity
在 Java 开发中起着不同的作用,它们相互配合可以提高开发效率和代码质量。Lombok 的主要优势在于减少样板代码、提高代码的可读性和可维护性以及降低出错概率。
Eureka是类似于etcd那种的第三方进程吗? 如何接入, 如何注册?接入了k8s还需要eureka吗
- Eureka 是 Netflix 开源的服务发现组件,在 Spring Cloud 体系中被广泛使用,用于实现服务的注册与发现。它和 etcd 不同, 他可以加个注解
@EnableEurekaServer
就把一个springboot进程变成一个 eureka注册中心 - etcd 是一个go语言的分布式键值存储系统,常用于服务发现、配置管理等场景;而 Eureka 专门用于服务注册与发现。
Eureka 作为第三方进程的使用方式
在 Spring Cloud 项目里,Eureka 通常以独立服务(Eureka Server)的形式运行,就像一个单独的第三方进程。从 twitter-spring-reactjs
项目的配置来看,Eureka Server 是通过 Docker 容器来启动的,相关配置如下:eureka-server:
image: merikbest/twitter-spring-reactjs:eureka-server
container_name: eureka-server
hostname: eureka-server
environment:
EUREKA_HOST: eureka-server
ZIPKIN_HOST: zipkin
ports:
- "8761:8761"
networks:
- spring
depends_on:
- zipkin
从这个配置能够看出,Eureka Server 运行在端口 8761 上。
接入 Eureka Server
要让服务接入 Eureka Server,需要在服务的配置文件里指定 Eureka Server 的地址。以 tweet-service
为例,其配置文件 config-server/src/main/resources/config/tweet-service.yml
中有如下配置:eureka:
client:
service-url:
defaultZone: http://${EUREKA_HOST:localhost}:8761/eureka
fetch-registry: true
register-with-eureka: true
instance:
hostname: ${EUREKA_HOST:localhost}
这里的 defaultZone
指定了 Eureka Server 的地址,fetch-registry
表示是否从 Eureka Server 获取服务注册信息,register-with-eureka
表示是否将当前服务注册到 Eureka Server。
注册服务到 Eureka Server
要把服务注册到 Eureka Server,除了在配置文件中指定 Eureka Server 的地址之外,还需要在服务的启动类上添加相应的注解。以下是 Eureka Server 的启动类示例:package com.gmail.merikbest2015;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
对于需要注册的服务,在启动类上添加 @EnableEurekaClient
注解(Spring Cloud Edgware 及更高版本也可以使用 @EnableDiscoveryClient
),同时确保配置文件中 register-with-eureka
为 true
。例如,在 tweet-service
里添加相应注解后,服务启动时就会自动注册到 Eureka Server。
总结
- Eureka Server:以独立服务的形式运行,通过 Docker 容器启动。
- 接入 Eureka Server:在服务的配置文件中指定 Eureka Server 的地址。
- 注册服务:在服务的启动类上添加
@EnableEurekaClient
注解,同时确保配置文件中register-with-eureka
为true
。
接入Kubernetes(k8s)后,是否还需要Eureka取决于具体的业务场景和需求。以下是两种不同情况的分析:
有k8s不需要Eureka的情况
- 服务发现功能可由k8s替代:Kubernetes本身提供了强大的服务发现机制。通过使用Kubernetes的Service资源,服务之间可以通过服务名进行通信,Kubernetes会自动处理服务的负载均衡和端点管理。例如,在一个使用k8s部署的微服务应用中,各个服务可以通过定义的Service名称来相互访问,无需额外的服务发现组件。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
在上述配置中,其他服务可以通过my-service
这个名称来访问对应的应用实例。
- 简化架构和维护成本:引入额外的组件(如Eureka)会增加系统的复杂性和维护成本。使用k8s的原生功能可以减少对第三方服务发现组件的依赖,降低系统的复杂度。例如,减少了对Eureka服务器的部署、配置和监控工作。
需要Eureka的情况
- 已有基于Eureka的架构:如果项目已经基于Spring Cloud和Eureka构建了成熟的微服务架构,并且有大量的业务逻辑和配置依赖于Eureka,迁移到Kubernetes的原生服务发现机制可能需要付出较大的成本。在这种情况下,可以考虑在Kubernetes环境中继续使用Eureka。
- 跨集群或混合云场景:在跨多个Kubernetes集群或混合云环境中,Kubernetes的原生服务发现机制可能无法满足需求。Eureka可以提供跨集群和跨云环境的服务发现功能,确保不同环境中的服务可以相互发现和通信。例如,一个企业在公有云和私有云都有部署服务,使用Eureka可以方便地实现服务的统一管理和发现。
- 特定的业务需求:Eureka提供了一些Kubernetes原生服务发现机制没有的功能,如服务的自我保护机制、服务的健康检查和监控等。如果项目对这些功能有特定的需求,那么可以继续使用Eureka。
综上所述,接入Kubernetes后是否还需要Eureka需要根据具体的业务场景、技术架构和需求来决定。在大多数情况下,如果没有特殊需求,使用Kubernetes的原生服务发现机制可以简化架构和维护成本;但在一些特定场景下,Eureka仍然可以发挥重要作用。
项目里的api-gateway干啥的
在这个 Twitter 克隆项目中,API Gateway(API 网关)起着至关重要的作用,它是系统对外的统一入口,负责接收客户端的请求,并将这些请求路由到对应的微服务。以下结合代码和文档详细介绍其功能:
1. 路由请求
API 网关的核心功能之一是根据请求的路径将其路由到相应的微服务。在 config-server/src/main/resources/config/api-gateway.yml
文件中,定义了多个路由规则,例如:spring:
cloud:
gateway:
routes:
- id: topic-service
uri: lb://topic-service
predicates:
- Path=/ui/v1/topics/**
filters:
- AuthFilter
- id: tweet-service
uri: lb://tweet-service
predicates:
- Path=/ui/v1/tweets/**, /api/v1/tweets/**
filters:
- AuthFilter
上述配置表明,当客户端请求路径匹配 /ui/v1/topics/**
时,API 网关会将请求路由到 topic-service
微服务;当请求路径匹配 /ui/v1/tweets/**
或 /api/v1/tweets/**
时,会将请求路由到 tweet-service
微服务。
2. 服务发现与负载均衡
API 网关集成了服务发现机制,配置文件末尾还配置了借助 Eureka 来查找可用的微服务实例。配置文件中关于 Eureka 的部分如下:eureka:
client:
service-url:
defaultZone: http://${EUREKA_HOST:localhost}:8761/eureka
fetch-registry: true
register-with-eureka: true
instance:
hostname: ${EUREKA_HOST:localhost}
这里的 defaultZone
指定了 Eureka Server 的地址,API 网关会从 Eureka 中获取服务注册信息,并使用 lb://
前缀实现负载均衡。例如 uri: lb://topic-service
表示将请求负载均衡到 topic-service
的多个实例上。
3. 跨域资源共享(CORS)配置
API 网关还负责处理跨域请求,通过全局 CORS 配置允许特定来源的请求访问 API。配置如下:spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/ui/v1/**]':
allowedOrigins: "http://localhost:3000"
exposedHeaders: "page-total-count"
allowedHeaders: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- PATCH
- OPTIONS
- HEAD
上述配置允许来自 http://localhost:3000
的请求访问以 /ui/v1/
开头的 API,并且指定了允许的请求方法和暴露的响应头。
4. 过滤器功能
API 网关使用过滤器对请求进行预处理和后处理,例如身份验证、日志记录等。在配置文件中,多个路由规则都使用了 AuthFilter
进行身份验证:- id: topic-service
uri: lb://topic-service
predicates:
- Path=/ui/v1/topics/**
filters:
- AuthFilter
AuthFilter
可以验证请求的合法性,确保只有经过授权的用户才能访问受保护的 API。
5. 集成分布式追踪
项目中集成了 Zipkin 进行分布式追踪,API 网关会将请求信息发送到 Zipkin 进行分析和监控。配置文件中的相关部分如下:zipkin:
base-url: http://${ZIPKIN_HOST:localhost}:9411
这里的 base-url
指定了 Zipkin Server 的地址,API 网关会将请求的追踪信息发送到该地址。
综上所述,API 网关在项目中承担着路由请求、服务发现与负载均衡、跨域处理、过滤器功能以及分布式追踪集成等重要职责,是整个系统的关键组件。
WebSocket和HTTP长链接和TCP
WebSocket 能够实现实时通信,这和它的原理以及与 TCP 长连接的关系密切相关,下面为你详细解释:
WebSocket 实现实时通信的原理
1. 握手阶段
HTTP 升级请求:WebSocket 通信始于一个 HTTP 请求,客户端向服务器发送一个特殊的 HTTP 请求,请求中包含
Upgrade: websocket
和Connection: Upgrade
头部字段,表明客户端希望将当前的 HTTP 连接升级为 WebSocket 连接。GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13服务器响应:如果服务器支持 WebSocket,会返回一个状态码为 101 Switching Protocols 的响应,表示同意升级连接。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=连接建立:经过这个握手过程,客户端和服务器之间的 HTTP 连接就升级为了 WebSocket 连接,后续的数据传输将在这个新的连接上进行。
2. 数据传输阶段
全双工通信:WebSocket 连接建立后,客户端和服务器可以同时独立地发送和接收数据,这种全双工通信模式使得双方能够实时地交换信息。例如,在 Twitter 克隆项目中,当有新的推文发布、用户点赞或回复推文时,服务器可以立即将相关信息推送给客户端,客户端也可以随时向服务器发送消息,如发送新的推文。
// tweet-service 中发送推文更新消息
webSocketClient.send(WebsocketConstants.TOPIC_FEED_ADD, tweet);帧协议:WebSocket 使用帧协议来传输数据,数据被分割成多个帧进行传输。每个帧包含一个头部和一个数据负载,头部包含了帧的类型、长度等信息。这种机制使得数据可以以较小的单元进行传输,提高了传输效率。
3. 连接关闭阶段
- 显式关闭:当通信结束时,客户端或服务器可以发送一个关闭帧来关闭连接。关闭帧包含一个状态码和可选的关闭原因。对方收到关闭帧后,会发送一个确认关闭帧,然后双方关闭连接。
WebSocket 与 TCP 长连接的关系
相同点
- 长连接特性:WebSocket 和 TCP 长连接都保持一个持久的连接,避免了频繁建立和断开连接的开销。在长连接上,双方可以在一段时间内持续地交换数据,提高了通信效率。
- 实时通信能力:由于连接一直保持打开状态,两者都能够实现实时通信。服务器可以在有新数据时立即发送给客户端,客户端也可以及时向服务器发送请求。
不同点
- 协议层次:TCP 是传输层协议,它提供了可靠的、面向连接的字节流服务,但不处理应用层的数据。WebSocket 是应用层协议,它建立在 TCP 之上,定义了一套完整的通信规范,包括握手、数据传输和关闭连接等过程。
- 兼容性:WebSocket 是基于 HTTP 协议的,它可以通过 HTTP 端口(如 80 和 443)进行通信,因此更容易穿越防火墙和代理服务器。而 TCP 长连接可能需要专门配置防火墙和代理服务器才能正常工作。
- 应用场景:TCP 长连接通常用于需要高性能和低延迟的场景,如游戏、金融交易等。WebSocket 更适合于 Web 应用,它可以在浏览器和服务器之间实现实时通信,如在线聊天、实时通知等。在 Twitter 克隆项目中,WebSocket 用于实现实时通知、在线聊天和推文实时更新等功能,使得用户能够及时获取最新的信息。
综上所述,WebSocket 通过握手升级、全双工通信和帧协议等机制实现了实时通信,它和 TCP 长连接有相似的长连接特性,但在协议层次、兼容性和应用场景等方面存在差异。
HTTP长链接与 WebSocket 的对比
是的,HTTP 也有保持长连接的类型,下面为你详细介绍。
HTTP 长连接的概念
在早期的 HTTP 1.0 协议中,每次请求 - 响应完成后,连接就会关闭。如果需要进行多次请求,就需要重新建立连接,这会带来一定的开销,比如 TCP 握手和挥手的时间。为了减少这种开销,HTTP 引入了长连接机制。
HTTP 1.1 长连接
- 机制原理:HTTP 1.1 默认采用持久连接(长连接)。通过在请求头和响应头中添加
Connection: keep - alive
字段(在 HTTP 1.1 中,即使不明确指定该字段,也默认使用长连接),客户端和服务器之间可以在一次 TCP 连接上进行多次请求 - 响应交互,而不需要每次都重新建立连接。GET /example HTTP/1.1
Host: example.com
Connection: keep - alive
服务器响应示例:HTTP/1.1 200 OK
Content - Type: text/html
Connection: keep - alive
Content - Length: 1234
<!DOCTYPE html>
<html>
...
</html>
- 应用场景及局限性:这种长连接适用于需要频繁进行小数据量请求的场景,比如网页中多个资源的加载。然而,HTTP 1.1 的长连接是半双工的,同一时间只能有一方发送数据,并且存在队头阻塞问题,即如果一个请求的响应没有及时返回,后续的请求都需要等待。
HTTP 2 长连接
- 机制原理:HTTP 2 在 HTTP 1.1 的基础上进行了改进,它采用二进制分帧层,将请求和响应数据分割成更小的帧,并在一个 TCP 连接上以多路复用的方式并行传输。这意味着多个请求和响应可以同时在一个连接上进行,避免了队头阻塞问题。
- 应用场景及优势:在高并发的 Web 应用中,HTTP 2 的长连接能显著提高性能。例如,在加载一个包含大量图片和脚本的网页时,HTTP 2 可以更高效地并行加载这些资源,减少用户等待时间。
HTTP 3 长连接
- 机制原理:HTTP 3 基于 QUIC 协议,QUIC 是一种在 UDP 之上构建的新传输协议。它解决了 TCP 协议中存在的一些问题,如 TCP 握手延迟、队头阻塞等。HTTP 3 的长连接在建立连接时更快,并且可以提供更好的网络适应性。
- 应用场景及优势:对于对延迟敏感的应用,如实时视频流、在线游戏等,HTTP 3 的长连接能提供更流畅的体验。
与 WebSocket 的对比
虽然 HTTP 长连接可以减少连接建立的开销,但它仍然是基于请求 - 响应模型的。而 WebSocket 是全双工的,服务器可以主动向客户端推送数据,更适合实时通信场景,如在线聊天、实时通知等。在 Twitter 克隆项目中,选择 WebSocket 而非 HTTP 长连接,正是因为需要实现服务器主动向客户端推送新的推文、通知等实时信息。
综上所述,HTTP 有不同版本的长连接机制,它们在不同的场景中发挥着重要作用,但在实时通信方面,WebSocket 具有更明显的优势。
项目里WebSocket有啥用?
1. 实时通知
项目支持用户在他人订阅、转推或点赞推文时收到通知。通过WebSocket,这些通知可以实时推送给用户,而无需用户手动刷新页面。
在notification-service
模块的NotificationHandlerServiceImpl
类中,当处理各种通知事件(如ListsNotificationEvent
、FollowUserNotificationEvent
等)时,会调用WebSocketClient
的send
方法将通知消息发送到对应的WebSocket主题。// notification-service/src/main/java/com/gmail/merikbest2015/service/impl/NotificationHandlerServiceImpl.java
public void handleListsNotification(ListsNotificationEvent event) {
// ...
NotificationResponse response = notificationHandlerMapper.convertToNotificationListResponse(
notification, event.isNotificationCondition());
webSocketClient.send(WebsocketConstants.TOPIC_NOTIFICATIONS + response.getNotifiedUser().getId(), response);
// ...
}
2. 在线聊天功能
项目实现了基于WebSocket的在线聊天功能,用户可以实时收发消息。在chat-service
模块的ChatMessageController
类中,当用户发送消息时,会将消息通过WebSocketClient
发送到对应的聊天主题。// chat-service/src/main/java/com/gmail/merikbest2015/controller/ChatMessageController.java
(PathConstants.ADD_MESSAGE)
public ResponseEntity<Void> addMessage(@RequestBody ChatMessageRequest request) {
chatMessageMapper.addMessage(request)
.forEach((userId, message) -> webSocketClient.send(WebsocketConstants.TOPIC_CHAT + userId, message));
return ResponseEntity.ok().build();
}
综上所述,WebSocket在项目中用于实现实时通知、在线聊天、推文实时更新和定时推文发布通知等功能,提升了用户体验和应用的实时性。
PostgresSQL和MySQL优劣势
在当前项目中,使用了PostgreSQL作为数据库。下面以表格形式详细对比PostgreSQL和MySQL的优劣势:
对比项 | PostgreSQL | MySQL |
---|---|---|
优势 | 1. 复杂查询处理能力强:支持复杂的SQL查询,包括递归查询、窗口函数等,适合处理复杂的业务逻辑。例如在处理用户关系(如关注、订阅等)和推文数据关联查询时,PostgreSQL能更好地完成复杂查询需求。 2. 数据类型丰富:提供了更多的数据类型,如数组、JSON、XML等,方便存储和处理复杂的数据结构。在项目中可以更方便地存储用户的个性化设置等数据。 3. 开源且社区活跃:开源免费,拥有庞大的社区支持,能及时获取相关的文档、教程和解决方案。 4. 支持事务和并发控制:具备强大的事务处理能力和并发控制机制,确保数据的一致性和完整性,适合高并发的业务场景。 5. 数据完整性和约束:提供了丰富的数据完整性约束,如唯一约束、外键约束等,有助于保证数据的准确性和一致性。 |
1. 性能高效:在处理简单查询和高并发读写操作时,MySQL的性能表现较为出色,能快速响应请求。 2. 易于安装和配置:安装过程相对简单,配置选项也较为直观,适合初学者快速上手。 3. 广泛应用和生态系统:在Web开发领域有广泛的应用,拥有丰富的第三方工具和插件,生态系统成熟。 4. 商业支持:有官方的商业支持,适合对技术支持有较高要求的企业级应用。 |
劣势 | 1. 性能开销:由于功能丰富,在处理简单查询时,可能会存在一定的性能开销。 2. 学习成本较高:复杂的功能和丰富的数据类型导致学习曲线较陡,对于初学者来说,掌握和使用的难度较大。 3. 配置复杂:在大规模分布式系统中,PostgreSQL的配置可能会变得复杂,需要考虑多个节点之间的集群配置、数据同步等问题。 |
1. 数据类型相对较少:相比于PostgreSQL,MySQL的数据类型相对较少,在处理一些复杂的数据结构时可能不够灵活。 2. 复杂查询处理能力弱:在处理复杂的SQL查询时,MySQL的表现不如PostgreSQL,可能需要更多的优化和调整。 3. 事务处理能力有限:在某些版本中,MySQL的事务处理能力相对较弱,对于一些对事务要求较高的业务场景,可能无法满足需求。 |
综上所述,选择PostgreSQL还是MySQL取决于项目的具体需求。如果项目需要处理复杂的业务逻辑、存储复杂的数据结构,并且对数据完整性和事务处理有较高要求,那么PostgreSQL是一个不错的选择;如果项目更注重简单查询的性能和易于安装配置,MySQL可能更适合。在当前的Twitter克隆项目中,由于业务逻辑相对复杂,使用PostgreSQL可以更好地满足项目的需求。