Java全体系精华(下):从消息中间件到高并发优化,全面掌握核心技术
文章目录
- 1. 消息中间件RocketMQ、RabbitMQ、ActiveMQ
- 2. 大数据量消息中间件Kafka、ElasticSearch、ZooKeeper
- 3. 分布式、高并发、线程安全
- 4. Linux 运维、Tomcat、Nginx、Docker、K8s、Kuboard
- 5. 版本管理Maven、Git、SVN
- 6. 配置中心
- 7. 安全规约
1. 消息中间件RocketMQ、RabbitMQ、ActiveMQ
1.1 消息队列
作用:错峰流控(业务削峰场景)、服务解耦、数据最终一致性、消息广播
消息队列(Message Queue, MQ)在现代分布式系统中扮演着至关重要的角色。它通过解耦生产者和消费者、提供异步通信机制以及确保数据的可靠传输等方式,解决了多个复杂问题,并极大提升了系统的灵活性和可扩展性。
- 1、数据缓冲
将上游数据接收到消息队列中,暂时存放,下游服务可以按照自己的节奏进行处理。上游数据一旦有大量的突发流量,保证了下游服务不会受到影响。 - 2、系统解耦
将上游服务和下游服务通过消息队列进行通信。消息队列可以作为一个接口层,只要上游服务以及下游服务遵守接口的规范,便可以随意添加新的服务器。 - 3、冗余及健壮性
一个消息被保存为多个副本,多个下游服务可以消息同一个消息。比如消息队列 Kafka 支持副本 - 4、异步通信
数据暂时存放在消息队列中,下游服务可以在需要的时候再去处理它。 - 5、负载均衡
采用集群部署,避免单点故障影响整个业务系统,保证高可用。增加消费者实例来分担资源压力。 - 6、事务一致性
结合两阶段提交(2PC)、TCC 或 Saga 等协议,可以在跨服务调用时保持数据的一致性。
| MQ类型 | 吞吐量 | 高可用 | 消息可靠性 |
|---|---|---|---|
| ActiveMQ | 万 | 主从架构高可用 | 有较低的概率丢失数据 |
| RabbitMQ | 万 | 主从架构高可用 | |
| RocketMQ | 十万 | 分布式架构 | 经过参数优化配置,消息可以做到0丢失 |
| Kafka | 百万 | 分布式架构 | 通过控制能够保证所有消息被消费且仅被消费一次,0丢失 |
1.2 ActiveMQ
ActiveMQ 是一个开源消息中间件,实现了 JMS(Java Message Service)规范,支持复杂的异步通信。
1.2.1 组件
- 1、Broker(代理)
作为消息代理的核心部分,负责接收来自生产者的消息并将其分发给消费者。 - 2、Producer (生产者)
负责生成并发送消息到 Broker 中。 - 3、Consumer (消费者)
订阅特定 Topic 或 Queue 并处理从中接收到的消息 - 4、 Message (消息)
承载业务数据的基本单元,包含消息体(Body)、头部信息(Headers)等 - 5、Destination (目的地)
定义消息的目标位置,即消息应该发送到哪个 Queue 或 Topic 上。 - 6、Transport Connector (传输连接器)
负责与客户端建立网络连接,并选择合适的传输协议进行通信。
1.2.2 工作流程
- 1、启动 Broker
(1)启动 ActiveMQ Broker 实例,它会监听指定端口等待客户端连接。 - 2、创建 Topic
(1)根据应用需求,在 Broker 中创建所需的 Queue 或 Topic。 - 3、生产者发送消息
(1)生产者应用程序通过相应的客户端库与 Broker 建立连接。
(2)将消息封装成适当的格式(如 JMS 消息对象),并通过指定的目的地发送出去。
(3)如果启用了持久化,则消息会被写入磁盘;否则保存在内存中。 - 4、Broker 处理消息
(1)Broker 接收到消息后,根据目的地类型(Queue 或 Topic)和路由规则,将消息放入相应的队列或广播给所有订阅者。
(2)对于持久化消息,还会更新索引文件以便快速检索。 - 5、消费者接收消息
(1)消费者应用程序同样通过客户端库与 Broker 连接,并订阅感兴趣的目的地。
(2)当有新消息到达时,Broker 会按照预定策略(如轮询、负载均衡)分发给消费者。
(3)消费者完成消息处理后向 Broker 发送 ACK 确认,表示该消息已被成功消费。 - 6、故障恢复
(1)如果某个 Broker 出现故障,生产者和消费者可以自动切换到其他健康的 Broker 实例继续工作。
(2)对于设置了 HA 模式的 Slave Broker,它会在 Master 故障时接管其职责,确保服务连续性。
1.2.3 特性
- 1、高性能
优化了内存管理和 I/O 操作,能够在高并发环境下保持出色的性能表现。 - 2、可靠性
提供消息持久化、事务支持和重试机制,确保消息不会丢失且能够可靠传递。 - 3、灵活性
支持多种消息传递模式(P2P 和 Pub/Sub),以及不同的传输协议,适应各种应用场景。 - 4、易用性
提供了 Web 控制台、命令行工具等多种管理界面,方便用户监控和操作 - 5、扩展性
插件机制使得 ActiveMQ 可以轻松集成其他技术和框架,如 Spring、Camel 等。
1.2.4 代码示例
1>生产者
import javax.jms.*;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Producer {
private static final String BROKER_URL = "tcp://localhost:61616";
private static final String QUEUE_NAME = "TEST_QUEUE";
public static void main(String[] args) throws JMSException {
// 创建连接工厂
ConnectionFactory factory = new ActiveMQConnectionFactory(BROKER_URL);
try (Connection connection = factory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) {
// 创建目的地(队列)
Destination destination = session.createQueue(QUEUE_NAME);
// 创建消息生产者
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// 发送消息
TextMessage message = session.createTextMessage("Hello, ActiveMQ!");
producer.send(message);
System.out.println("Sent: " + message.getText());
}
}
}
2>消费者
import javax.jms.*;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Consumer {
private static final String BROKER_URL = "tcp://localhost:61616";
private static final String QUEUE_NAME = "TEST_QUEUE";
public static void main(String[] args) throws JMSException {
// 创建连接工厂
ConnectionFactory factory = new ActiveMQConnectionFactory(BROKER_URL);
try (Connection connection = factory.createConnection()) {
connection.start();
try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) {
// 创建目的地(队列)
Destination destination = session.createQueue(QUEUE_NAME);
// 创建消息消费者
MessageConsumer consumer = session.createConsumer(destination);
// 接收并处理消息
Message message = consumer.receive(1000); // 超时时间 1 秒
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
System.out.println("Received: " + textMessage.getText());
} else {
System.out.println("Received: <non-text-message>");
}
}
}
}
}
1.2.5 ActiveMQ 支持哪些消息传递模式
点对点(Point-to-Point, P2P)模式和发布/订阅(Publish/Subscribe, Pub/Sub)模式。这两种模式分别通过 Queue 和 Topic 来实现。
| 类型 | 实现方式 | 工作机制 | 适用场景 |
|---|---|---|---|
| 点对点模型 | 基于Queue实现 | 每条消息只能被一个消费者消费 | 适用于需要保证消息只处理一次的场景 |
| 发布订阅模型 | 基于Topic实现 | 所有订阅者都可以接收到相同的消息副本 | 适用于广播通知或多个消费者都需要接收同一消息的场景 |
// 1、创建队列
Destination destination = session.createQueue(QUEUE_NAME);
// 创建消息生产者
MessageProducer producerP2P = session.createProducer(destination);
producerP2P.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
TextMessage message = session.createTextMessage("Hello, P2P!");
producerP2P.send(message);
// 2、创建主题
Destination destination = session.createTopic(TOPIC_NAME);
// 创建消息生产者
MessageProducer producerPubSub = session.createProducer(destination);
producerPubSub.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
message = session.createTextMessage("Hello, Pub/Sub!");
producerPubSub.send(message);
1.2.6 如何保证 ActiveMQ 消息的可靠性
- 1、消息持久化
设置消息为持久化(persistent),这样即使 ActiveMQ 服务重启,未处理的消息也不会丢失。 - 2、事务支持
使用事务来确保消息的一致性和完整性。在事务提交之前,消息不会被确认或移除。 - 3、消息确认机制(ACK)
消费者成功处理完消息后向 ActiveMQ 发送 ACK 确认,确保消息只被消费一次。如果消费者未能发送 ACK,则消息会被重新入队。 - 4、重试机制
配置消息重试策略,允许失败的消息在一定条件下自动重试。 - 5、集群和主从复制
通过搭建集群和配置主从复制模式,提高系统的可用性和容错能力。
1.2.7 ActiveMQ 的持久化机制
持久化配置可以通过 activemq.xml 文件中的 <persistenceAdapter> 元素指定。
- 1、KahaDB
(1)这是 ActiveMQ 默认的持久化存储引擎,基于日志文件进行数据持久化。
(2)每个消息都会被追加到日志文件末尾,同时维护索引以便快速检索。 - 2、LevelDB
(1)持久化存储引擎,提供了更好的性能和一致性保障。
(2)采用日志结构化合并树(LSM Tree)技术来高效地处理读写操作。 - 3、数据库存储
(1)使用关系型数据库(如 MySQL、PostgreSQL)作为持久化后端,适合已有数据库基础设施的企业环境。
1.2.8 ActiveMQ 的事务
- 1、本地事务
(1)在同一个 Session 内部,所有发送和接收的操作都可以包含在一个事务中。
(2)事务开始后,直到显式调用 commit() 方法前,任何消息都不会被确认或移除。
(3)如果出现错误,可以通过调用 rollback() 方法回滚整个事务。 - 2、XA 事务
(1)支持 XA 协议,允许多个资源(如数据库、其他消息队列)参与同一个全局事务。
1.3 RabbitMQ
RabbitMQ 是一个基于 AMQP 协议的消息代理(Message Broker),它通过发布/订阅模型实现生产者和消费者之间的解耦。
1.3.1 组件
- 1、Producer (生产者)
负责生成并发送消息的应用程序。 - 2、Consumer (消费者)
负责接收并处理消息的应用程序。 - 3、Message (消息)
承载业务数据的基本单元,包含消息体(Body)、属性(Properties)等信息。 - 4、Exchange (交换机)
用于接收来自生产者的消息,并根据路由规则将其分发到一个或多个队列中。
| 交换机类型 | 说明 | 应用场景 |
|---|---|---|
| Direct Exchange | 精确匹配路由键 | 只有当路由键完全匹配时,消息才会被发送到对应的队列。适用于一对一的消息分发。 |
| Fanout Exchange | 广播所有消息给所有绑定的队列 | 适用于需要将相同消息发送给多个消费者的场景。 |
| Topic Exchange | 基于通配符模式匹配路由键 | 适用于灵活的消息过滤和多条件匹配。 |
| Headers Exchange | 根据消息头属性进行路由 | 适用于复杂的消息路由需求,例如根据多个字段组合来决定消息去向。 |
- 5、Queue (队列)
存储待处理消息的地方,消费者从中拉取消息进行处理。 - 6、Binding (绑定)
定义了交换机和队列之间的关系,包括路由键等参数。 - 7、Virtual Host (虚拟主机)
类似于命名空间的概念,用于隔离不同的应用环境,每个虚拟主机都有自己独立的一套用户、权限、交换机、队列等资源。
1.3.2 工作流程
- 1、生产者发送消息到交换机
(1)生产者不需要知道消息会发送到哪个队列,只需要指定路由键(Routing Key)。 - 2、交换机转发消息到队列
(1)根据路由键和绑定键(Binding Key)的匹配规则将消息转发给一个或多个队列(Queue)。 - 3、队列存储待处理的消息
(1)每个队列可以被一个或多个消费者订阅。 - 4、消费者消费消息
(1)消费者从队列中拉取消息并进行处理。
(2)消费者完成消息处理后向 RabbitMQ 发送确认(ACK),表示该消息已被成功消费。
1.3.3 高级特性
- 1、发布/订阅模型
生产者将消息发布到交换机(Exchange),交换机根据路由键(Routing Key)和绑定键(Binding Key)的匹配规则将消息转发给一个或多个队列(Queue),消费者从队列中获取并处理消息。 - 2、持久化与确认机制
RabbitMQ 支持消息的持久化存储,确保即使服务器重启后消息也不会丢失;同时,提供了消息确认机制(ACK),只有当消费者成功处理完消息后才会从队列中移除。 - 3、事务支持
RabbitMQ 提供了基本的事务功能,但推荐使用更高效的 Publisher Confirms 模式来替代传统事务。 - 4、集群与镜像队列
为了提高系统的可用性和容错能力,RabbitMQ 可以部署为集群形式,并支持镜像队列(Mirrored Queues),使得队列中的消息可以在多个节点间同步复制。
1.3.4 代码示例
1>生产者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 声明队列(如果不存在则创建)
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
// 发送消息到指定队列
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'");
}
}
}
2>消费者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
public class Consumer {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 声明队列(如果不存在则创建)
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" + message + "'");
};
// 接收消息并处理
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
}
}
}
1.3.5 如何保证 RabbitMQ 消息的可靠性
- 1、消息持久化
设置消息为持久化(durable),这样即使 RabbitMQ 服务重启,未处理的消息也不会丢失。 - 2、队列持久化
声明队列时设置 durable=true,确保队列在服务器重启后仍然存在。 - 3、消息确认机制(ACK)
消费者成功处理完消息后向 RabbitMQ 发送 ACK 确认,确保消息只被消费一次。如果消费者未能发送 ACK,则消息会被重新入队。 - 4、Publisher Confirms
启用 Publisher Confirms 模式,生产者可以在消息被交换机接收或拒绝后得到通知,从而及时重试失败的消息。 - 5、镜像队列(Mirrored Queues)
在集群环境中配置镜像队列,使得队列中的消息可以在多个节点间同步复制,提高系统的可用性和容错能力。
1.3.6 RabbitMQ 如何实现高可用性
- 1、集群部署
通过搭建 RabbitMQ 集群,将多个 RabbitMQ 实例组成一个逻辑上的整体,提供更高的吞吐量和服务连续性。集群内的节点可以相互备份,防止单点故障。 - 2、主从复制
在集群中配置主从复制模式,使得数据可以在多个节点间同步。当主节点发生故障时,从节点可以接管其职责,继续提供服务。 - 3、镜像队列(Mirrored Queues)
在集群环境中配置镜像队列,确保队列中的消息能够在多个节点间冗余存储,避免因某个节点故障而导致消息丢失。 - 4、负载均衡
利用 HAProxy 或 Consul 等工具实现客户端请求的负载均衡,分散流量压力,并自动切换到健康的服务实例。 - 5、自动恢复
配置自动恢复策略,使 RabbitMQ 能够在网络分区或其他异常情况恢复正常后自动修复连接和状态。
1.4 RocketMQ
RocketMQ 是一个分布式消息中间件,以其高性能、低延迟和高可靠性而闻名。

1.4.1 组件
- 1、NameServer 担任路由消息的提供者
(1)作为路由服务,提供轻量级的命名服务。NameServer 不保存任何状态信息,所有的元数据都是通过心跳机制动态更新的。 - 2、Broker消息中转角色,负责存储消息、转发消息
(1)负责存储消息、管理 Topic 和 Consumer Group 等核心业务逻辑。 - 3、Producer负责生产消息
(1)负责生成并发送消息到指定的 Topic 中。 - 4、Cosumer负责消费消息
(1)订阅特定 Topic 并消费其中的消息。 - 5、Message消息
(1)每个消息都有唯一的 MsgId,便于追踪和调试。
(2)支持全局有序或分区有序的消息传递
1.4.2 工作流程
- 1、首先启动NameServer
(1)NameServer启动后监听端口,等待Broker、Producer以及Consumer连上来 - 2、启动Broker
(1)启动之后,会跟所有的NameServer建立并保持一个长连接,定时发送心跳包。
(2)心跳包中包含当前Broker信息(ip、port等)、Topic信息以及Borker与Topic的映射 - 3、创建Topic
(1)创建时需要指定该Topic要存储在哪些Broker上,也可以在发送消息时自动创建Topic - 4、Producer发送消息
(1)启动时先跟NameServer集群中的其中一台建立长连接,并从NameServer中获取当前发送的Topic所在的Broker;
(2)消息根据 Topic 和 Queue 分配策略存放到相应的队列中。 - 5、Consumer消费消息
(1)跟其中一台NameServer建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,进行消息的消费
(2)Push Consumer 会接收来自 Broker 的消息推送,而 Pull Consumer 则定时轮询拉取消息。
(3)消费完成后,Consumer 会向 Broker 发送 ACK 确认,表示该消息已被成功处理。 - 6、故障转移
(1)如果某个 Broker 出现故障,Producer 和 Consumer 可以自动切换到其他健康的 Broker 继续工作。
(2)对于设置了 HA 模式的 Slave Broker,它会在 Master 故障时接管其职责,确保服务连续性。
1.4.3 代码示例
1>生产者
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
public class Producer {
public static void main(String[] args) throws Exception {
// 创建生产者实例并指定组名
DefaultMQProducer producer = new DefaultMQProducer("example_producer_group");
// 设置 NameServer 地址
producer.setNamesrvAddr("localhost:9876");
// 启动生产者
producer.start();
for (int i = 0; i < 10; i++) {
// 创建消息实例,指定主题、标签和消息体
Message msg = new Message("TopicTest", "TagA", ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
// 发送消息并获取返回结果
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
}
// 关闭生产者
producer.shutdown();
}
}
2>消费者
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 Consumer {
public static void main(String[] args) throws Exception {
// 创建消费者实例并指定组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_consumer_group");
// 设置 NameServer 地址
consumer.setNamesrvAddr("localhost:9876");
// 订阅一个或多个 Topic,并指定 Tag 过滤条件
consumer.subscribe("TopicTest", "*");
// 注册回调函数来处理接收到的消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.printf("Receive New Messages: %s %n", new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动消费者
consumer.start();
System.out.printf("Consumer Started.%n");
}
}
1.4.4 优点
具有集群消费、⼴播消费、消息积压能⼒强、防⽌消息丢失、顺序消息、事务型消 息、保证⾼可⽤、⾼性能读写数据等优点的消息中间件
- 吞吐量高:单机吞吐量可达十万级
- 可用性高:分布式架构
- 消息可靠性高:经过参数优化配置,消息可以做到0丢失
- 功能支持完善:MQ功能较为完善,还是分布式的,扩展性好
- 支持10亿级别的消息堆积:不会因为堆积导致性能下降
- 源码是java:方便我们查看源码了解它的每个环节的实现逻辑,并针对不同的业务场景进行扩展
- 可靠性高:天生为金融互联网领域而生,对于要求很高的场景,尤其是电商里面的订单扣款,以及业务削峰,在大量交易涌入时,后端可能无法及时处理的情况
- 稳定性高:RoketMQ在上可能更值得信赖,这些业务场景在阿里双11已经经历了多次考验
1.4.5 高性能的存储机制
RocketMq采用文件系统存储消息,和Kafka一样采用顺序写的方式写入消息,使用零拷贝发送消息,这三者的结合极大地保证了RocketMq的性能;
相对于ActiveMq采用关系型数据库进行存储的方式更高效。
- 1、顺序写
Kafka、RocketMQ 采用顺序写,直接追加数据到末尾,减少了操作系统到磁盘的寻址动作。 - 2、零拷贝
直接由内核缓冲区Read Buffer将数据复制到网卡;传统方式有四层复制,磁盘文件->读取到操作系统内核缓冲区Read Buffer;将内核缓冲区的数据->复制到应用程序缓冲区Application Buffer;将应用程序缓冲区Application Buffer中的数据->复制到socket网络发送缓冲区;将Socket buffer的数据->复制到网卡,由网卡进行网络传输
零拷贝技术采用了MappedByteBuffer内存映射技术,采用这种技术有一些限制,其中有一条就是传输的文件不能超过2G,这也就是为什么RocketMq的存储消息的文件CommitLog的大小规定为1G的原因
1.4.6 集群部署
master节点的brokerId为0,slave节点的brokerId为1(大于0即可);
同一组broker的broker-Name相同,如master1和slave1都为broker-a;
每个broker节点配置相同的NameServer;
复制方式配置:master节点配置为ASYNC-MASTER,slave节点配置为SLAVE即可;
刷盘方式分为同步刷盘和异步刷盘,为了保证性能而不去考虑少量消息的丢失,因此同意配置为异步刷盘
- 1、单Master
单机模式, 即只有一个Broker, 如果Broker宕机了, 会导致RocketMQ服务不可用, 不推荐使用 - 2、多Master模式
组成一个集群, 集群每个节点都是Master节点, 配置简单, 性能也是最高, 某节点宕机重启不会影响RocketMQ服务
(1)缺点:如果某个节点宕机了, 会导致该节点存在未被消费的消息在节点恢复之前不能被消费 - 3、【推荐】多Master多Slave模式,异步复制
每个Master配置一个Slave, 多对Master-Slave, Master与Slave消息采用异步复制方式, 主从消息一致只会有毫秒级的延迟
(1)优点:弥补了多Master模式(无slave)下节点宕机后在恢复前不可订阅的问题。在Master宕机后, 消费者还可以从Slave节点进行消费。采用异步模式复制,提升了一定的吞吐量。总结一句就是,采用多Master多Slave模式,异步复制模式进行部署,系统将会有较低的延迟和较高的吞吐量
(2)缺点:如果Master宕机, 磁盘损坏的情况下, 如果没有及时将消息复制到Slave, 会导致有少量消息丢失 - 4、【推荐】多Master多Slave模式,同步双写
与多Master多Slave模式,异步复制方式基本一致,唯一不同的是消息复制采用同步方式,只有master和slave都写成功以后,才会向客户端返回成功
(1)优点:数据与服务都无单点,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高
(2)缺点:会降低消息写入的效率,并影响系统的吞吐量
1.4.7 RocketMQ 高可用
集群部署NameServer。集群部署Broker,master挂掉,则会自动切换到其他的master。设置同步复制方式broker,Role:SYNC_MASTER;即使master节点挂了,slave上也有当前master的所有备份数据,保证消息完整性。
- 1、异步复制
就是消息发送到master节点,只要master写成功,就直接向客户端返回成功,后续再异步写入slave节点 - 2、同步复制
就是等master和slave都成功写入内存之后,才会向客户端返回成功
1.4.8 负载均衡
- 1、producer发送消息的负载均衡
默认会轮询向Topic的所有queue发送消息,以达到消息平均落到不同的queue上;而由于queue可以落在不同的broker上,就可以发到不同broker上 - 2、consumer订阅消息的负载均衡
假设有5个队列,两个消费者,则第一个消费者消费3个队列,第二个则消费2个队列,以达到平均消费的效果。而需要注意的是,当consumer的数量大于队列的数量的话,根据rocketMq的机制,多出来的队列不会去消费数据,因此建议consumer的数量小于或者等于queue的数量,避免不必要的浪费
1.4.9 保证消息不丢失
- 1、Producer
同步发送+重试机制+多个master节点,尽可能减小消息丢失的可能性。
同步发送模式,即消息发送到,消息发送 broker 后等待 broker 响应结果完成。producer 支持失败重试,默认3次。多 master broker模式,即使某个主机宕机,仍可保证消息发送到其他 broker 节点 - 2、Consumer:ACK机制
Consumer先pull 消息到本地,消费完成后,才向服务器返回ack;即先消费,消费成功后再提交
1.4.10 保证消息不重复消费
RocketMQ保证了同一个消费组只能消费一次,但会被不同的消费组重复消费,因此这种重复消费的情况不可避免;
(1)业务逻辑保持幂等性,相同的消息多次执行,结果一致
(2)消息主键作为表主键约束,消息先入库在消费,相同消息无法入库。
(3)失败重试,消息处理失败支持重试三次
(4)消息确认ack机制
1.4.11 如何保证幂等
幂等性(Idempotence)是指一个操作可以被多次执行而不会产生不同于第一次执行的结果
- 1、message_id
消息唯一标识作为主键,入库保证消息不重复性 - 2、is_succeed
消息给个消费标识,只执行一次,执行过更新标识。 - 3、version
数据表中添加 version 字段,在每次更新时递增此字段,要求客户端传最新的版本号,比对版本号不一致则不处理。 - 4、fail_times
失败重试机制,3次失败重试,消费成功,刷新是否消费标识及重试次数。 - 5、保证方法的幂等
对于查询操作一直都是幂等的,对于写操作,保证同一条消息重复执行结果一致。同一个消息避免短时间内重复执行。 - 6、分布式锁
保证方法在同一时刻只有一个线程可以访问,业务执行完毕后是否锁。 - 7、事务补偿
针对重复消费的情况,应该设计一套完整的补偿逻辑,包括撤销之前的更改、回滚资源分配等
2. 大数据量消息中间件Kafka、ElasticSearch、ZooKeeper
2.1 Kafka
Apache Kafka 是一个分布式流处理平台,消息队列Kafka版广泛用于日志收集、监控数据聚合、流式数据处理、在线和离线分析等大数据领域,已成为大数据生态中不可或缺的部分。
2.1.1 组件
- 1、Producer
(1)负责生成并发送消息到 Kafka 集群中的特定 Topic
(2)支持批量发送以减少网络开销
(3)提供异步和同步两种发送模式
(4)可配置消息的分区策略 - 2、Broker
(1)作为 Kafka 集群的基本单元,负责存储消息、管理 Topic 和 Consumer Group 等核心业务逻辑
(2)每个 Broker 可以管理多个 Partition(分区),并且每个 Partition 内的消息是有序的
(3)支持多副本机制来保证高可用性和数据冗余 - 3、Consumer
(1)订阅特定 Topic 并消费其中的消息
(2)支持广播模式和集群模式
广播模式:所有消费者都能收到所有消息
集群模式:同一 Consumer Group 内的多个实例共享消息
(3)支持手动或自动确认机制(ACK),确保消息只被处理一次
(4)类型:
High-Level API:简化版 API,自动处理分区分配和位移提交等复杂任务。
Low-Level API:提供更细粒度的控制,允许开发者自定义位移管理和分区分配策略。 - 4、Topic
(1)用于组织不同类型的消息流。每个 Topic 可以进一步划分为多个 Partition(分区)
(2)分区内的消息是有序的,而不同分区之间的消息顺序不保证
(3)支持多副本机制,提高系统的容错能力和读写性能 - 5、Partition(分区)
(1)将 Topic 划分成多个子集,每个 Partition 是一个有序的日志文件序列
(2)支持并发读写操作,提升吞吐量
(3)可以跨多个 Broker 进行分布,实现水平扩展 - 6、Replica(副本)
(1)为每个 Partition 创建多个副本,分布在不同的 Broker 上,以确保数据的安全性和可靠性。
(2)支持 Leader 和 Follower 角色,只有 Leader 接受读写请求,Follower 同步数据。
(3)当 Leader 故障时,选举新的 Leader 来接管服务。
2.1.2 工作流程
Producer将消息经过KeySerializer、valueSerializer序列化后,经过Partition分区器处理,决定消息落到topic具体哪个分区中,最后将消息发送到客户端的消息缓冲池accumulator中,这个缓冲池的最大值默认为32M(buffer.memory)并交由一个Sender线程进行发送。消息在缓存池中会被分为一个个batch,每个batch默认大小为16KB(batch.size),消息一旦攒够该大小或超过最大空闲时间(linger.ms)将会被发送到broker。Producer通过参数(retries)控制消息发送的重试次数,由于网络抖动等原因,消息将会重新进行发送。
- 1、启动 Kafka 集群
(1)首先启动 ZooKeeper 实例,然后启动多个 Kafka Broker 构成集群
(2)Broker 之间通过心跳机制保持通信,并向 ZooKeeper 注册自己的状态 - 2、创建 Topic
(1)管理员或应用程序可以通过命令行工具或 API 创建一个新的 Topic,并指定其分区数和副本因子 - 3、Producer 发送消息
(1)生产者应用程序连接到任意一个可用的 Broker
(2)根据配置选择合适的 Partitioner 对消息进行分区
(3)将消息追加到相应的 Partition 日志文件末尾 - 4、Broker 处理消息
(1)Broker 接收到消息后将其写入磁盘,并更新索引以便快速检索
(2)如果启用了多副本,则会将消息同步复制到其他 Broker 上的副本 - 5、Consumer 订阅消息
(1)消费者应用程序连接到任意一个可用的 Broker,并订阅感兴趣的 Topic
(2)消费者从指定的 Partition 开始拉取消息,并根据需要调整拉取频率和批量大小
(3)消费者成功处理完消息后向 Broker 发送 ACK 确认,表示该消息已被成功消费 - 6、故障恢复
(1)如果某个 Broker 出现故障,生产者和消费者可以自动切换到其他健康的 Broker 实例继续工作
(2)对于设置了 HA 模式的 Replica,当 Leader 故障时,Follower 会接管其职责,确保服务连续性
2.1.3 代码示例
1>生产者
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import java.util.Properties;
import java.util.concurrent.Future;
public class KafkaProducerExample {
public static void main(String[] args) {
// 配置 Producer 属性
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 创建 Producer 实例
try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
// 构建消息记录
ProducerRecord<String, String> record = new ProducerRecord<>("test-topic", "key", "Hello, Kafka!");
// 发送消息并获取返回结果
Future<RecordMetadata> future = producer.send(record);
try {
// 等待发送完成并打印元数据
RecordMetadata metadata = future.get();
System.out.printf("Sent message to topic %s, partition %d at offset %d%n",
metadata.topic(), metadata.partition(), metadata.offset());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2>消费者
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class KafkaConsumerExample {
public static void main(String[] args) {
// 配置 Consumer 属性
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
// 创建 Consumer 实例
try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
// 订阅 Topic
consumer.subscribe(Collections.singletonList("test-topic"));
// 拉取消息并处理
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("Received message: key = %s, value = %s, partition = %d, offset = %d%n",
record.key(), record.value(), record.partition(), record.offset());
}
}
}
}
}
2.1.3 Kafka 版队列消息
当 Kafka 被用作队列时,每个 Topic 可以视为一个逻辑上的队列。为了确保消息的一次性处理,通常会配置单一分区(Partition),因为 Kafka 的消息在同一个分区内是有序的,并且同一 Consumer Group 内只有一个消费者可以读取该分区的消息。
这意味着即使有多个消费者实例,它们也只会依次处理消息,而不会同时处理同一条消息。
2.1.4 kafka中ack值0,1,-1
- 0代表生产者发送的消息一旦发送出去,不需要等待消息的确认,传输效率最高,但是可靠性无法保证
- 1代表生产者将消息发送出去,需要等待leader接收成功的消息确认,kafka默认为该值。
- -1代表生产者将消息发送出去,需要等待ISR中所有的副本都接收成功。
2.1.5 kafka的unclean
kafka中的一个配置unclean.leader.election.enable,新版本该值默认为false。
一旦开启该配置,意味着在leader宕掉的情况下,非ISR中的follower也可以参与leader选举,这样就会造成数据的不一致情况的发生,消费者拿到的消息会重复。
2.1.6 kafka为什么不支持读写分离
kafka中消息的读写都会直接与leader进行交互,不会与follower进行交互,follower只是为了保证消息的可用性。
数据的延时问题以及因此导致的数据不一致。follower在向leader进行数据拉取时,需要一定的时间,在这段时间内,follower内的数据与leader中的数据时不一致的。
2.1.7 kafka是否可以保证消息的顺序性
kafka不能保证整个topic中的消息是有序的,只能保证每个partition中的消息是有序的。
2.1.8 kafka中的consumer group
consumer group是一个逻辑上的概念,多个消费者可以构成一个消费组。
一个partition只能被消费组中的一个消费者所消费。
多个消费组可以消费同一个partition。
2.1.9 什么情况下broker会从ISR中被踢出
leader负责维护ISR列表,每个Partition都会有一个ISR,如果其中一个follower超过10s(replica.lag.time.max.ms)没有向leader发起数据复制请求,则会被leader将其从ISR中移除。
2.1.10 如何避免消费端Rebalance
消费端的Rebalance就是让一个消费组内的所有消费者就如何消费topic的所有分区达成共识的过程,在Rebalance过程中,所有的Consumer实例都会停止消费,等待Rebalance的完成,严重影响消费端的TPS,所以应当尽量避免。
Rebalance发生的条件有三种:
(1)消费组内的消费者成员数量发生变化
(2)消费主题的数量发生变化
(3)消费主题的分区数量发生变化
后面两种情况是不可避免的,所以我们能做的就是尽量避免第一种情况的发生。
组内成员数量发生变化无非就两种情况,一种是为了提高消费速度增加了消费者组内的消费者数量,这种情况是正常的。另外一种情况是有消费者退出,这种情况需要避免。
正常情况下,每个消费者都会定期向组协调器发送心跳,表明自己还存活,如果消费者不能及时的发送心跳,组协调器会被认为该消费者已经“死”了,就会导致消费者离组引发Rebalance问题。
这里涉及到两个参数:session.timeout.ms和heartbeat.interval.ms分别为组协调器认为消费组存活的期限和消费者发送心跳的时间间隔。max.poll.interval.ms为consumer两次拉取数据的最大时间间隔,超过这个时间,也会导致消费者离组。
另外,如果Consumer端频繁进行FullGC也会导致消费端长时间停顿,引发Rebalance。为了解决这个问题,主要从以下方面入手:
(1)合理配置session.timeout.ms和heartbeat.interval.ms,避免因心跳发送超时导致的Rebalance。
(2)调整max.poll.interval.ms的大小
(3)监控消费这的GC情况
2.1.11 kafka是否有消息丢失以及消息重复的情况,如何解决
- 1、消息丢失
(1)request.required.acks属性设置为0,当网络异常,缓冲区满等情况,消息可能丢失
(2)属性设置为1,leader确认接收,但是在副本进行同步之前挂掉了,数据可能丢失
解决方法:同步模式下,将属性设置为-1;异步模式下,不设置阻塞超时时间,当缓冲区满时让生产者一直处于阻塞状态
- 2、消息重复
(1)主要针对的是消费者可能会重复读取相同消息的情况,消费者如果在处理了消息但是没有更新offset的情况下宕掉了,那么在它重启之后就会读取到之前已经处理过的数据。
解决方法:想要解决这种情况,就要保证消费端的幂等性,综合实际的情况来考虑。比如,将数据插入到数据库中可以先判断是否已经存在,如果存在,就不做插入处理等。
2.2 ElasticSearch
Elasticsearch 是一个分布式搜索和分析引擎,基于 Apache Lucene 构建,支持 RESTful API 和多种编程语言客户端。
1>Luence 倒排索引
Luence 是全文检索工具,包括信息检索、分词原理、词项、倒排索引、排序算法等内容。
Lucene是支持实时查询的,原理是每次索引之后都做一次刷新。
倒排索引是相对于正序索引来说的,根据文档查找词项是正排索引,按照词项查找文档是倒排索引。
词项是索引的最小单位;
2>数据存储格式为JSON
{
"name" : "Jay",
"desc" : "Is Jay",
"gender" : "male",
"birth" : "2000-10-10"
}
2.2.1 组件
| ElasticSearch | 传统数据库 |
|---|---|
| Index 索引 | Database 数据库 |
| Type 类型 | Table 表 |
| Field 字段/域 | Column 列/字段 |
| Document 文档 | Row 行/记录 |
| Mappings 映射 | Schema 数据库对象集合 |
| Settings 设置 | |
| 一切皆索引 | 指定字段创建索引 |
| Query DSL | SQL 数据库语句 |
| GET http:// GET请求 | Select 查询 |
| POST http:// POST请求 | Add 新增 |
| PUT http:// PUT请求 | Update 更新 |
| DELETE http:// DELETE请求 | Delete 删除 |
- 1、Index 索引
(1)索引是文档的集合;
(2)对应传统数据库中的数据库 - 2、Document 文档
(1)文档是信息检索的对象;
(2)对应数据库中的行记录 - 3、Field 字段
(1)字段对应关系型数据库中的字段 - 4、Shards 分片
(1)将 Index 分割成更小的部分,每个 Shard 是一个独立的 Lucene 索引
(2)简单理解为分表;分片的原理是在物理上拆分,不同的分片支持放在不同的服务器,解决单台服务器性能限制问题。副本,是对分片的备份,主分片损坏或异常丢失时,副本可以顶上来代替主分片,保证功能可用数据不丢失。
(3)支持并发读写操作,提升系统吞吐量
(4)可以跨多个 Data Node 进行分布,实现负载均衡 - 5、Replicasedit 副本
(1)为每个 Shard 创建多个副本,分布在不同的 Data Node 上,以确保数据的安全性和可靠性
(2)支持 Leader 和 Follower 角色,只有 Leader 接受写入请求,Follower 同步数据
(3)当 Leader 故障时,选举新的 Leader 来接管服务 - 6、Template 模板设置
模板设置包括 settings 和 mappings,定义了索引的基本设置和 field 的映射关系,支持配置分词器等,多个索引可通过模板匹配的方式共用一个模板。
2.2.2 工作流程
- 1、启动集群
(1)首先启动 Elasticsearch 实例,这些实例可以配置为不同类型的 Node(如 Master Node、Data Node)
(2)通过内置的发现机制,Node 之间会自动组成一个 Cluster - 2、创建索引
(1)管理员或应用程序可以通过 REST API 创建一个新的 Index,并指定分片数和副本因子 - 3、索引文档
(1)客户端应用程序通过 HTTP POST 请求向指定的 Index 发送 JSON 格式的 Document
(2)Elasticsearch 将 Document 分配给相应的 Shard,并将其写入磁盘
(3)如果启用了多副本,则会将 Document 同步复制到其他 Data Node 上的 Replica - 4、查询文档
(1)客户端应用程序通过 HTTP GET 请求发送查询语句(Query DSL)
(2)Elasticsearch 解析查询条件,确定需要访问哪些 Shard。
(3)搜索结果被汇总并返回给客户端。 - 5、更新和删除文档
(1)更新操作实际上是对现有 Document 执行删除后重新索引的过程。
(2)删除操作标记 Document 为已删除,直到后续优化过程中彻底移除。 - 6、维护和优化
(1)定期合并小的 Segments(段),减少磁盘 I/O 和内存占用。
(2)执行快照和恢复操作,确保数据安全。
2.2.3 代码示例
1>pom.xml
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2>创建索引和文档
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
public class ElasticsearchExample {
private static final String INDEX_NAME = "test-index";
public static void main(String[] args) throws IOException {
// 创建 RestHighLevelClient 实例
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));
// 构建索引请求
IndexRequest request = new IndexRequest(INDEX_NAME);
String jsonString = "{" +
"\"user\":\"kimchy\"," +
"\"postDate\":\"2013-01-30\"," +
"\"message\":\"trying out Elasticsearch\"" +
"}";
request.source(jsonString, XContentType.JSON);
// 执行索引操作
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
System.out.println("Indexed document with ID: " + response.getId());
// 关闭客户端连接
client.close();
}
}
3>查询文档
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
public class ElasticsearchSearchExample {
private static final String INDEX_NAME = "test-index";
public static void main(String[] args) throws IOException {
// 创建 RestHighLevelClient 实例
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));
// 构建搜索请求
SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);
// 执行搜索操作
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("Search hits: " + searchResponse.getHits().getTotalHits().value);
// 关闭客户端连接
client.close();
}
}
2.2.4 环境配置 elasticsearch.yml
ES 启动时可能会出现bootstrap启动问题,这个问题主要原因是服务器本身系统配置默认参数大小设置偏低,适当调大即可;
编辑/安装路径/config/ jvm.options配置文件,根据服务器本身配置去合理修改堆内存分配。默认分配-Xms4g,服务器配置低的话,就调小2或1;配置高就适当调大多次测试ES的吞吐量找到最优配置参数。
# ---------------------------------- Cluster ----------------------------------#
# Use a descriptive name for your cluster:
# 集群名称自定义, 集群内多个节点的集群名称唯一
cluster.name: log_elk
# ------------------------------------ Node ---------------------------------#
# Use a descriptive name for the node:
# 节点名称自定义,集群内多个节点名称不重复
node.master: false
node.name: node-135
# ----------------------------------- Paths ----------------------------------
# Path to directory where to store the data (separate multiple locations by comma):
path.data: /opt/elkf/esData/data
path.logs: /opt/elkf/esData/logs
# ----------------------------------- Memory --------------------------------
#
# Lock the memory on startup:
#
bootstrap.memory_lock: false
bootstrap.system_call_filter: false
# ---------------------------------- Network ---------------------------------
network.host: 192.168.15.135
http.port: 9200
# 配置集群自动发现节点
discovery.zen.ping.unicast.hosts:["192.168.15.135", "192.168.15.134"]
# 配置最大master节点数目
discovery.zen.minimum_master_nodes: 1
http.cors.enabled: true
http.cors.allow-origin: "*"
# 解决bootstrap启动问题
bootstrap.memory_lock: false
bootstrap.system_call_filter: false
2.2.5 ES查询
ES查询关键字 term、terms、match、query、range、wildcard、fuzzy
- 1、term 查询某个字段包含某个关键词的文档
term 不分词查询,意为包含,包含则返回结果;查询是在分词结果中匹配,大写要转换成小写,不推荐驼峰属性,推荐下划线属性。 - 2、terms 查询某个字段里包含多个关键词的文档
- 3、match_all 查询所有文档
match、query 分词查询,会对field进行分词操作,然后再查询 - 4、multi_match 可以指定多个字段
- 5、match_phrase 短语匹配查询,ElasticSearch引擎首先分析(analyze)查询字符串,从分析后的文本中构建短语查询,这意味着必须匹配短语中的所有分词,并且保证各个分词的相对位置不变
GET lib3/user/_search
{
"query":{"match":{"age": 20}}
}
GET lib3/user/_search
{
"query":{
"match_all": {}
}
}
GET lib3/user/_search
{
"query":{
"multi_match": {
"query": "youyong",
"fields":["interests","name"]
}
}
}
- 6、range 范围查询
from,to,include_lower,include_upper,boost
include_lower:是否包含范围的左边界,默认是true
include_upper:是否包含范围的右边界,默认是true
GET /lib3/user/_search
{
"query": {
"range": {
"birthday": {
"from": "1990-10-10",
"to": "2000-05-01",
"include_lower": true,
"include_upper": false
}
}
}
}
- 7、wildcard 通配符查询
GET /lib3/user/_search
{
"query": {
"wildcard": {
"name": "wang*"
}
}
}
GET /lib3/user/_search
{
"query": {
"wildcard": {
"name": "li?i"
}
}
}
- 8、fuzzy 模糊查询
fuzzy 查询是 term 查询的模糊等价
GET /lib3/user/_search
{
"query": {
"fuzzy": {
"interests": "chagge"
}
}
}
2.2.6 ES 数据保证不丢失
较多技术实现上保证数据不丢失方式,常见的有两种
- 1、异步刷新
比如 MySQL 主从复制,Redis 持久化方式、MQ的异步刷新、ES 的 index.translog.durability: “aync” - 2、同步刷新
MySQL的binlog、Redis 的AOF 保证更新操作就纪录日志、MQ 轮询调度消费者阻塞得到响应才认为消费成功,ES 的 translog
2.2.7 ES 调优
(1)配置合适的JVM内存和合适的分片副本大小;
(2)bulk 批量写入提高吞吐量
(3)多线程写入 ES 数据,最大化 ES 集群下 bulk 方式带来性能的提升
(4)调整索引刷新时间,降低内存数据刷入到磁盘的频率来提高性能
(5)锁定JVM内存,禁止swap内存和磁盘的交换来提高性能
index.refreash_interval: 300s # 索引刷新时间
bootstrap.memory_lock: true # 锁定JVM内存,禁止swap磁盘和内存交换,保证ES性能
index.merge.scheduler.max_thread_count: 1 # 索引 merge 最大线程数
index.translog.durability: async # 异步写硬盘,提升效率
index.translog.sync_interval: 120s # 异步写硬盘间隔时间
indices.memory.index_buffer_size: 30 # 内存
discovery.zen.ping_timeout: 120s # 心跳超时时间
discovery.zen.fd.ping_interval: 120s # 心跳检测时间
discovery.zen.fd.ping_timeout: 120s # ping超时时间
discovery.zen.fd.ping_retries: 6 # 心跳重试次数
thread_pool.bulk.size:20 # 写入线程个数
thread_pool.bulk.queue_size:20 # 写入线程队列大小
合适的分片和副本大小:
对于数据量较小(100GB以下)的index,往往写入压力查询压力相对较低,一般设置35个shard,number_of_replicas设置为1即可(也就是一主一从,共两副本)。对于数据量较大(100GB以上)的index:一般把单个shard的数据量控制在(20GB50GB)让index压力分摊至多个节点:可通过index.routing.allocation.total_shards_per_node参数,强制限定一个节点上该index的shard数量,让shard尽量分配到不同节点上综合考虑整个index的shard数量,如果shard数量(不包括副本)超过50个,就很可能引发拒绝率上升的问题,此时可考虑把该index拆分为多个独立的index,分摊数据量,同时配合routing使用,降低每个查询需要访问的shard数量
2.2.8 ES 集群
# 开启集群,配置当前节点名称,在集群中是否作为主节点
cluster.name: niaonao_elk
node.master: false
node.name: node-1
network.host: 192.168.200.81
http.port: 9200
# 是否锁定内存
bootstrap.memory_lock: false
bootstrap.system_call_filter: false
# 配置集群自动发现节点
discovery.zen.ping.unicast.hosts:["192.168.200.81", "192.168.200.83"]
# 配置最大master节点数目
discovery.zen.minimum_master_nodes: 1
# 解决bootstrap启动问题
bootstrap.memory_lock: false
bootstrap.system_call_filter: false
2.2.9 ES 高可用
- 1、多副本机制
为每个 Primary Shard 创建多个 Replica,分布在不同的 Data Node 上。当 Primary Shard 出现故障时,Replica 可以接管其职责。 - 2、自动故障检测与恢复
内置的 Master Node 负责监控集群状态,并在检测到故障时自动进行重新分配和恢复。 - 3、Master Election
当现有 Master Node 不可用时,集群中的其他符合条件的 Node 会参与选举新的 Master。 - 4、快照与恢复
定期创建集群或特定 Index 的快照,并存储备份。在需要时可以从备份中恢复数据。 - 5、跨数据中心复制(CCR)
允许将数据从一个集群异步复制到另一个远程集群,提高地理分布环境下的数据安全性和可用性。
2.3 ZooKeeper
Apache ZooKeeper 是一个高效的分布式协调服务,广泛应用于分布式系统中解决一致性问题。它提供了简单易用的接口来管理配置信息、命名、提供分布式同步以及组服务等功能
2.3.1 组件
- 1、Server(服务器)
(1)Zookeeper 集群中的每个节点都是一个 Server,负责处理客户端请求并维护状态
(2)类型
Leader:集群中选举产生的主节点,负责事务性操作(如写入)的协调和执行
Follower:跟随 Leader 的节点,处理非事务性操作(如读取),并在 Leader 发起投票时参与决策
Observer:只读节点,不参与选举或投票过程,主要用于分担读负载 - 2、Client(客户端)
(1)与 Zookeeper Server 进行交互的应用程序端点,通过 API 发送命令、查询数据等
(2)支持会话机制,确保在网络抖动情况下能够自动重连
(3)提供 Watcher 机制,允许监听特定事件的变化通知 - 3、Znode(节点)
(1)Zookeeper 内部的数据结构单元,类似于文件系统的目录和文件
(2)类型
持久节点(Persistent):创建后不会因客户端断开而消失
临时节点(Ephemeral):当创建该节点的客户端会话结束时自动删除
顺序节点(Sequential):在创建时会在名称后面附加一个递增的序列号
持久顺序节点(Persistent Sequential) 和 临时顺序节点(Ephemeral Sequential):结合了上述两种类型的特性 - 4、Watcher(监视器)
(1)用于监控 Znode 的变化(如创建、删除、更新等),并在发生变化时触发回调函数。
2.3.2 工作流程
- 1、启动集群
(1)首先启动多个 Zookeeper 实例,这些实例通过内部的发现机制组成一个集群
(2)集群中的第一个启动的实例会被选为 Leader,其余为 Follower 或 Observer - 2、Leader 选举
(1)如果现有 Leader 不可用,集群将发起新一轮的选举,所有 Server 参与投票选出新的 Leader - 3、处理请求
(1)对于读操作(如获取数据、列出子节点),可以直接由任意 Follower 或 Observer 处理
(2)对于写操作(如设置数据、创建/删除节点),必须经过 Leader 协调,确保全局一致性 - 4、事件通知
(1)当被监控的 Znode 发生变化时,相关联的 Watcher 会触发回调函数,通知客户端进行相应的处理 - 5、会话管理
(1)Zookeeper 使用心跳检测机制维持客户端与 Server 之间的连接。如果一段时间内没有收到心跳包,则认为会话失效,并根据节点类型决定是否清理资源
2.3.3 代码示例
1>pom.xml
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
2>创建连接处理Znode
import org.apache.zookeeper.*;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
public class ZookeeperExample implements Watcher {
private static final String ZK_ADDRESS = "localhost:2181";
private static final int SESSION_TIMEOUT = 5000;
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
private ZooKeeper zk;
public void connect() throws IOException, InterruptedException {
zk = new ZooKeeper(ZK_ADDRESS, SESSION_TIMEOUT, this);
connectedSemaphore.await();
}
@Override
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected) {
connectedSemaphore.countDown();
}
}
public void createNode(String path, byte[] data) throws KeeperException, InterruptedException {
String createdPath = zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("Created node: " + createdPath);
}
public void getNodeData(String path) throws KeeperException, InterruptedException {
byte[] data = zk.getData(path, false, null);
System.out.println("Node data: " + new String(data));
}
public void close() throws InterruptedException {
zk.close();
}
public static void main(String[] args) throws Exception {
ZookeeperExample zkExample = new ZookeeperExample();
zkExample.connect();
// 创建节点
zkExample.createNode("/test-node", "Hello, Zookeeper!".getBytes());
// 获取节点数据
zkExample.getNodeData("/test-node");
zkExample.close();
}
}
2.3.4 Leader 选举
Zookeeper 的 Leader 选举机制,可以用来确定集群中的某个成员作为协调者。
通常通过创建临时顺序节点的方式实现。所有参与者竞争创建具有最小序列号的节点,成功者即为 Leader。
2.3.5 Zookeeper 实现分布式锁
分布式锁的核心思想是通过创建一个唯一的临时顺序节点来标识当前持有锁的客户端。
2.3.5.1 实现分布式锁
利用临时顺序节点,只有拥有最小序列号的客户端才能获得锁。其他客户端则等待,直到当前持有锁的客户端释放。
- 1、当一个客户端想要获取锁时,它会尝试创建一个带有特定前缀的临时顺序节点。
- 2、如果该客户端创建的节点是当前目录下序列号最小的那个,则认为它成功获得了锁。
- 3、其他试图获取锁的客户端则需要等待,直到它们创建的节点成为序列号最小的那个。
- 4、持有锁的客户端完成操作后应删除其对应的节点,以便释放锁给下一个等待的客户端。
1>分布式锁类 DistributedLock.java
import org.apache.zookeeper.*;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.Collections;
import java.util.List;
public class DistributedLock implements Watcher {
private static final String ZK_ADDRESS = "localhost:2181";
private static final int SESSION_TIMEOUT = 5000;
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
private ZooKeeper zk;
private String lockPathPrefix = "/lock_";
public void connect() throws IOException, InterruptedException {
zk = new ZooKeeper(ZK_ADDRESS, SESSION_TIMEOUT, this);
connectedSemaphore.await();
}
@Override
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected) {
connectedSemaphore.countDown();
}
if (event.getType() == Event.EventType.NodeDeleted) {
synchronized (this) {
notifyAll(); // 唤醒等待中的线程
}
}
}
public boolean tryLock() throws KeeperException, InterruptedException {
String myLockPath = zk.create(lockPathPrefix, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> children = zk.getChildren("/", false);
Collections.sort(children);
// 获取当前节点的序列号
int mySequence = Integer.parseInt(myLockPath.substring(lockPathPrefix.length()));
// 检查是否为最小序列号
if (children.get(0).equals(myLockPath)) {
System.out.println("Acquired lock: " + myLockPath);
return true;
} else {
// 等待前一个节点消失
watchPreviousNode(children, mySequence);
return false;
}
}
private void watchPreviousNode(List<String> children, int mySequence) throws KeeperException, InterruptedException {
for (String child : children) {
if (child.startsWith(lockPathPrefix)) {
int sequence = Integer.parseInt(child.substring(lockPathPrefix.length()));
if (sequence < mySequence && sequence == mySequence - 1) {
Stat stat = zk.exists("/" + child, true); // 设置 watcher
if (stat != null) {
synchronized (this) {
wait(); // 等待被唤醒
}
}
break;
}
}
}
}
public void unlock(String lockPath) throws KeeperException, InterruptedException {
zk.delete(lockPath, -1);
System.out.println("Released lock: " + lockPath);
}
}
2>测试类 MyDistributedLock.java
public class MyDistributedLock {
public static void main(String[] args) throws Exception {
DistributedLock lock = new DistributedLock();
lock.connect();
// 1、尝试获取锁
if (lock.tryLock()) {
try {
// 模拟长时间操作
Thread.sleep(5000);
} finally {
// 2、释放锁
lock.unlock(lock.lockPathPrefix + lock.mySequence);
}
} else {
System.out.println("Failed to acquire lock.");
}
lock.close();
}
}
2.3.5.2 Zookeeper 分布会锁中的故障规避
- 1、会话超时
(1)Zookeeper 使用心跳检测机制维持客户端与 Server 之间的连接
(2)如果一段时间内没有收到心跳包,则认为会话失效,并根据节点类型决定是否清理资源。对于临时节点,一旦会话结束,节点会被自动删除,从而防止死锁 - 2、Leader 选举
(1)如果 Leader Server 出现故障,集群将发起新一轮的选举,所有 Server 参与投票选出新的 Leader - 3、Watchers 和通知机制
(1)当被监控的 Znode 发生变化时,相关联的 Watcher 会触发回调函数,通知客户端进行相应的处理。这种方式确保了客户端能够在最短时间内响应锁状态的变化 - 4、幂等性设计
(1)确保每次获取锁和释放锁的操作都是幂等的,即重复执行相同的操作不会产生不同的结果
2.3.5.3 保证Zookeeper 分布式锁的公平性
- 1、顺序节点
利用 Zookeeper 的顺序节点特性,按创建时间先后顺序分配锁。这样可以确保最早请求锁的客户端优先获得锁。 - 2、监听前驱节点
每个客户端在等待锁的过程中只监听前一个节点的变化,一旦前一个节点消失,立即尝试获取锁。这种机制保证了锁的分配按照请求顺序进行。 - 3、超时控制
为获取锁设置合理的超时时间,防止某个客户端长时间占用锁而影响其他客户端的正常工作。
2.3.5.4 Zookeeper 分布式锁中的“活锁”问题
“活锁”是指多个客户端不断尝试获取锁但都无法成功的情况。为了避免这种情况的发生,可以采取以下策略
- 1、随机退避
当客户端未能获取锁时,不是立即重试,而是等待一段随机的时间后再尝试。这有助于打破循环依赖,提高锁获取的成功率。 - 2、限制重试次数
设定最大重试次数,超过限定次数后放弃获取锁,转而执行其他逻辑。这样做不仅可以防止无限循环,还能及时反馈失败原因。 - 3、心跳保持
对于已经持有锁的客户端,定期发送心跳信号以延长锁的有效期,防止因网络波动等原因导致锁提前释放。
2.3.5.5 优化 Zookeeper 分布式锁的性能
- 1、减少不必要的锁竞争
尽量缩小临界区范围,降低并发冲突的概率;采用分片锁或其他非阻塞算法,分散热点资源的压力 - 2、批量操作
对于一系列相关的锁操作,尽可能合并为一次请求发送给 Zookeeper,减少网络往返次数
-3、缓存机制
利用客户端缓存减少对 Zookeeper 的直接调用频次,例如缓存锁状态或配置信息 - 4、异步编程模型
采用异步 API 进行 Zookeeper 操作,充分利用多线程的优势,加快程序执行速度 - 5、选择合适的节点类型
根据实际需求选择持久节点还是临时节点,权衡资源占用和容错能力
2.3.6 Zookeeper 服务注册和发现
每个服务在启动时向 Zookeeper 注册自己的地址信息;其他服务可以通过查询 Zookeeper 来找到目标服务的位置。
2.3.7 Zookeeper 消息队列
使用持久顺序节点来模拟队列元素,消费者按顺序消费这些节点,确保任务的一次性处理。
3. 分布式、高并发、线程安全
3.1 分布式与集群
(1)分布式缓存(Redis、MemCache)
(2)分布式事务(Seata:AT、PCC、Sage、XA)
(3)分布式数据库(数据分片、数据同步)
3.1.1 分布式与集群
分布式是把一个大业务拆分成多个子业务,每个子业务都是一套独立的系统,子业务之间相互协作最终完成整体的大业务。
集群是把处理同一个系统部署为多个节点 。
1> 分布式ID算法
| 算法 | 雪花算法 | UUID |
|---|---|---|
| 长度 | 64bit 的长整型数据 | 36bit字符型数据 |
| 原理 | 41bit时间戳+10bit机器ID+12bit序列号 | 基于MAC地址生成UUID的算法 |
| 优点 | 按时间趋势递增;高性能:内存生成;容量大,查询效率高 | 高性能:本地生成 |
| 缺点 | 依赖与系统时间的一致性,修改系统时间可能会出现重复值 | UUID太长不易存储:36长度的字符串;信息不安全:可能会造成MAC地址泄露; |
雪花算法:
在同一个进程中,它首先是通过时间位保证不重复,如果时间相同则是通过序列位保证。 同时由于时间位是单调递增的,且各个服务器如果大体做了时间同步,那么生成的主键在分布式环境可以认为是总体有序的,这就保证了对索引字段的插入的高效性。 例如 MySQL 的 Innodb 存储引擎的主键
其他算法:
美团(Leaf)、滴滴(Tinyid)、百度(uid-generator)
3.1.2 分布式缓存 Redis、MemCache
本地缓存不支持持久化、无法共享、无法扩展;
分布式缓存技术常用 Redis、MemCache、JBossCache
支持缓存共享(分布式缓存)、支持缓存扩展(集群主从复制)、支持持久化(Redis的rdb、aof)
1>缓存数据一致性策略
(1)分布式缓存数据一致性解决方案先操作数据库再删除key;数据强一致性要求的,可以加上队列,进行异步删除key
(2)更新缓存加锁,应对并发场景数据安全
(3)较短的过期时间
3.1.3 分布式事务 SEATA
Simple Extensible Autonomous Transaction Architecture 简单可扩展自治事务框架
事务即一批操作全部成功或全部失败;
ACID特性包括原子性、一致性、隔离性、持久性;
分布式事务用于在分布式系统中保证不同节点之间的数据一致性。
3.1.3.1 事务数据一致性
强一致性、弱一致性、最终一致性
微服务系统,多个子系统在某个业务上的操作也要求具备事务性,不让会出现,上游操作成功,下游操作失败的情况,存在数据错乱。两边的数据要一致。
单机服务,通过单机事务就可控制数据的一致性,保证整个操作的成功或失败回滚。而微服务就需要引入分布式事务进行管理全局事务以保证数据的一致性。
3.1.3.2 分布式事务重要概念
- 1、TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚 - 2、TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务 - 3、RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
3.1.3.3 二阶段提交 (2PC two-phase commit protocol)
第一阶段询问各事务数据源是否准备好;第二阶段才真正将数据提交给具体数据源
2pc是经典的强一致、中心化的原子提交协议。包含一个中心化协调者节点(coordinator)和N个参与者节点(partcipant)
(1)协调者向所有参与者发送事务内容,询问是否可以提交事务
(2)参与者执行事务操作,但不提交。返回执行结果,yes或no
(3)协调者收到失败消息或超时信息,则给所有参与者发送回滚消息;各参与者基于undo信息回滚,并向协调者完成ack确认
(4)协调者未收到失败消息,发送提交消息;各参与者提交事务,并向协调者完成ack应答
(5)释放所有事务处理过程中使用的锁资源
遗留问题:性能问题(执行过程需阻塞等待)、协调者单点故障问题、丢失消息导致的数据不一致问题
3.1.3.4 三阶段提交(3PC)
CanCommit-PreCommit-DoCommit,解决协调者和参与者同时挂掉的问题
相交二阶段提交,引入了canCommit和参与者超时机制;
(1)首先协调者向所有参与者发出canCommit请求,询问是否可以提交事务,并等待答复
(2)参与者收到canCommit请求后,可以则返回yes进入准备提交阶段。不可以返回no;
(3)所有参与者都返回yes,协调者预执行事务
(4)向所有参与者发送preCommit请求进入准备阶段,参与者收到preCommit请求,执行事务操作记录undo和redo信息,但不提交事务
各参与者返回ack相应或no,并等待最终指令
(5)第三阶段真正提交事务
3.1.3.5 SEATA 支持四种模式AT、TCC、Sage、XA,常用模式AT
| 模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| AT | 无侵入的分布式事务解决方案 | 业务升级成本低,无侵入,对业务改造影响较小 | |
| TCC补偿事务(TryConfirmCancel) | 高性能 | 核心系统对性能要求很高的场景 | |
| Sage | 长事务解决方案 | 业务流较长且保证事务最终一致性的场景 | |
| XA | 强一致性 | 强一致性,性能低 | 不推荐使用 |
1>AT(Auto Transaction)模式原理
TM端使用@GolableTranscation进行全局事务开启、提交、回滚
TM开始RPC调用远程服务
RM端seata-client通过扩展DataSourceProxy,实现自动生成undo-log与TC上报
TM告知TC提交/回滚全局事务
TC通知RM各自执行commit/rollback操作,同时清除undo-log
2>一阶段
一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
3>二阶段
二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
4>优点
AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案
5>缺点
当操作的数据是共享型数据,会存在脏写的问题,所以如果是 用户独有数据可以使用AT模式。
3.1.4 分布式数据库
由多个不同站点的多个数据文件组成
允许多个用户访问和操作数据
某个站点故障,其他站点可顶上来,数据支持恢复
多个站点数据一致性
3.1.5 分布式锁

- 锁的持有者
- 锁的标识
(1)用state状态标识锁,对锁的占用和释放通过状态值来标识。实现资源访问互斥,解决多个节点的线程并发问题 - 锁的超时时间
(1)用来设置过期时间,防止持有者长期持有不释放锁,导致死锁
3.2 高并发、线程安全
3.2.1 线程安全工具
并发集合(JUC 包),线程池(ThreadPoolExecutor),阻塞队列(先进先出BlockingQueue,有界队列ArrayBlockingQueue,LinkedBlockingQueue ),CAS与原子操作(CAS、AQS、ABA),无锁并发框架Disruptor,锁状态(无锁-偏向锁-轻量级锁-重量级锁),锁优化(自旋、去除、粗化)
3.2.2 并发注意事项
- 1、单例对象类的方法要保证方法的线程安全;
- 2、并发下,考虑锁优化与资源消耗,能用轻量级锁使用轻量级锁,能无锁就无锁;
- 3、并发下,考虑线程安全集合类、阻塞队列,JUC.atomic、JUC.locks等;
- 4、ThreadLocal 变量及时回收,线程池场景下,线程会复用,不及时清理变量,存在OOM的风险
- 5、线程池推荐使用 ThreadPoolExecutor 去创建,避免显式创建线程,避免OOM
- 6、对多个资源,数据库,表,对象等加锁时要注意加锁顺序,避免出现锁问题
- 7、对于轻量级锁 ReentrantLock 注意加锁和释放锁中间不要抛异常,避免无法是否锁造成其他线程无法获取锁资源。
- 8、对于尝试机制获取锁资源的场景,需要先判断当前对象是否持有锁,lock.tryLock();避免 AQS 抛异常
- 9、对于并发访问下线程出现冲突的概率小于 20% 的情况推荐使用乐观锁,乐观锁重试次数不超出 3 次;
- 10、资金流水相关业务,推荐使用悲观锁策略
- 11、避免方法级别的同步,使用代码块同步代替,有助于业务扩展维护
- 12、ABA 问题,可以通过乐观锁、互斥锁进行控制;防止在 CAS 时出现 ABA 问题而继续完成操作的场景。
3.2.3 AQS
1>数据结构
- 1、状态量(volatile state)
(1)AQS 使用一个整数值来表示同步状态,该状态由子类定义并根据需要进行修改
(2)在 ReentrantLock 中,这个值代表当前持有锁的次数
(3)在 Semaphore 中,则表示剩余可用许可的数量 - 2、CLH 队列
(1)AQS 内部维护着一个双向链表形式的 FIFO 等待队列,称为 CLH(Craig, Landin, and Hagersten)队列
(2)当多个线程竞争同一个资源时,未获得资源的线程会被加入到这个队列中,并在条件满足后按顺序被唤醒
2>模式
- 共享模式
(1)允许多个线程同时访问资源
(2)调用 acquireShared(int arg) 方法尝试获取锁
(3)适用于读锁 - 独占模式
(1)一次只有一个线程能够访问资源
(2)调用 acquire(int arg) 方法尝试获取锁
(3)适用于互斥锁
3>主要方法
- 1、isHeldExclusively()
判断当前线程是否以独占模式持有同步状态。 - 2、tryAcquire(int arg) 和 tryRelease(int arg)
尝试获取或释放独占锁,返回布尔值表示操作是否成功。 - 3、tryAcquireShared(int arg) 和 tryReleaseShared(int arg)
尝试获取或释放共享锁,返回负数表示失败,零表示等待,正数表示成功。 - 4、getState(), setState(int newState) 和 compareAndSetState(int expect, int update)
用于获取、设置或原子更新同步状态。
4>工作流程
- 1、获取同步状态
(1)调用 acquire(int arg)/acquireShared(int arg) 方法尝试获取锁。
(2)如果获取失败,当前线程将被封装为 Node 并插入到 CLH 队列尾部,然后进入休眠状态直到被唤醒。 - 2、释放同步状态
(1)调用 release(int arg)/releaseShared(int arg) 方法尝试释放锁。
(2)如果释放成功且有其他线程正在等待,则唤醒下一个节点对应的线程。
(3)共享模式唤醒多个等待中的线程。 - 3、唤醒线程
(1)当某个线程释放了同步状态后,AQS 会检查是否有其他线程在等待,并按照 FIFO 原则唤醒符合条件的线程。
(2)被唤醒的线程会重新尝试获取同步状态,如果成功则继续执行,否则再次进入休眠状态。
5>代码示例
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class SimpleLock {
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
private static class Sync extends AbstractQueuedSynchronizer {
// 尝试获取锁
@Override
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 尝试释放锁
@Override
protected boolean tryRelease(int releases) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 是否处于独占状态
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
}
6>注意事项
避免循环依赖,降低死锁风险
应减少不必要的锁竞争、采用分片锁等方式来提高效率。
务必妥善处理各种异常情况,包括但不限于锁的非法使用、线程中断等,确保程序的健壮性和稳定性。
3.2.4 死锁
多个线程互相阻塞等待锁资源,就会死锁。对多个数据库,表,对象操作时,注意加锁的顺序,避免出现死锁。
(1)避免嵌套锁
尽量减少锁的嵌套层级,避免死锁的发生。
(2)锁顺序
如果必须嵌套锁,确保所有线程按照相同的顺序获取锁。
(3)超时机制
使用 tryLock 或 lockInterruptibly 方法设置锁的获取超时,防止无限期等待
3.2.5 锁状态
锁只会升级不会降级,由偏向锁升级为轻量级锁,轻量级锁升为重量级锁。
当不存在资源竞争时,默认使用偏向锁;JVM 会使用 CAS 来保证线程安全,在对象头部 Mark Word 设置线程ID,标识偏向于该线程;当其他线程试图锁定某个已被偏向过的对象时,JVM 就会撤销偏向锁,升级为轻量级锁;轻量级锁依赖 CAS 操作 Mark Word 试图获取锁,获取得到就是轻量级锁,获取不到时升级为重量级锁 synchronized
| 锁 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 无锁 | \ | \ | \ |
| 偏向锁 | 加锁释放锁不需要额外的消耗 | 线程间存在锁竞争 | 适用于只有一个线程访问同步块的场景 |
| 轻量级锁 | 竞争的线程不会阻塞 | 始终得不到锁竞争的线程,会自旋消耗资源 | 同步块执行速度快 |
| 重量级锁 | 线程竞争不使用自旋机制 | 线程会阻塞 | 同步块执行速度慢,追求并发量 |
3.2.6 锁优化机制
自旋锁在存在竞争的情况下,让当前线程自旋(忙循环)等待后,尝试获取对象的锁。
锁消除是对于不存在共享资源的代码块去除同步措施;
锁粗化是对于频繁加锁解锁操作适当扩大加锁范围;
轻量级锁在无竞争的情况下,使用 CAS 操作做同步;
偏向锁在无竞争的情况下,不再任何同步操作;
3.2.7 volatile、synchronized
1>volatile
具有可见性,适用于一写多读场景
2>synchronized
具有原子性、可见性、有序性,互斥同步锁;修饰实例方法,作用于实例对象;修饰静态方法,作用于类对象;修饰代码块;
原理:对象头里的标记字段 Mark Word,存储了对象的锁信息;一个对象获取锁资源后,会变更 Mark Word 中的锁标识;
一个对象拥有一个监控器 Monitor,MonitorEnter 和 MonitorExit 指令分别标识进去互斥同步操作。只有退出时,释放锁资源,其他阻塞对象才可尝试获取对象的锁。
3.2.8 线程池
通过 ThreadPoolExecutor 创建线程池,避免资源浪费耗尽的风险;
1>核心参数
corePoolSize:核心线程数
maximumPoolSize:最大线程数,线程池中允许的最大线程数
keepAliveTime:存活时间,当线程数大于核心时,这是空闲线程在终止前等待新任务的最长时间。
TimeUnit:存活时间单位
workQueue:阻塞队列类型
Executors.defaultThreadFactory():线程工厂方法,用于创建线程
defaultHandler:默认的拒绝策略
2>拒绝策略
当工作队列满且线程个数达到maximunPoolSize后所采取的策略
| 类型 | 说明 |
|---|---|
| AbortPolicy | 抛异常 throws a RejectedExecutionException. |
| DiscardPolicy | 丢弃新的任务 |
| DiscardOldestPolicy | 调用poll方法丢弃工作队列队头的任务 |
| CallerRunsPolicy | 既不抛弃任务也不抛出异常,把队列中的任务放在调用者调用者线程中运行 |
3.2.9 阻塞队列
可以解决线程的安全问题。因为阻塞队列是线程安全的,所以生产者和消费者都可以是多线程的,不会发生线程安全问题;内部通过轻量级锁 ReentrantLock 和 Codition 实现;
| 类型 | 说明 |
|---|---|
| ArrayBlockingQueu | 一个由数组结构组成的有界阻塞队列。创建 ArrayBlockingQueue 时,需要初始化队列的大小。不支持空元素 |
| LinkedBlockingQueue | 一个由链表结构组成的有界阻塞队列。添加和删除方法都通过 Lock 做了并发处理,添加和删除不互斥,Condition 用来挂起和唤醒其他线程 |
| PriorityBlockingQueue | 一个支持优先级排序的无界阻塞队列 |
| DelayQueue | 一个使用优先级队列实现的无界阻塞队列 |
| SynchronousQueue | 一个不存储元素的阻塞队列 |
| LinkedTransferQueue | 一个由链表结构组成的无界阻塞队列 |
| LinkedBlockingDeque | 一个由链表结构组成的双向阻塞队列 |
3.3 设计模式
3.3.1 单例模式
单例模式+双锁检查+volatile
/**
* @author niaonao
* 线程安全的单例模式
* Double Check Locking 双锁机制,对单例进行两层加锁
* 使用使用了volatile关键字来保证其线程间的可见性
* 同步代码块中使用二次检查,以保证其不被重复实例化
*/
public class GFriendSingleMultithread {
volatile private static GFriendSingleMultithread grilfriend = null;
//默认的构造方法通过private 修饰为私有方法,该单例类就只允许通过getInstance() 获取创建实例。
private GFriendSingleMultithread(){}
public static GFriendSingleMultithread getInstance() {
if(null == grilfriend){ //第一层检查
//synchronized 修饰,同步代码块,处理多线程的关键字
synchronized (GFriendSingleMultithread.class) {
if(null == grilfriend){//第二层检查
grilfriend = new GFriendSingleMultithread();
}
}
}
return grilfriend;
}
}
3.3.2 工厂模式
Spring IOC就是通过工厂模式来实现的,将对象的创建和管理过程交由Spring去完成,主要通过beanFactory来实现,根据传入bean的名字来获取对象。
1>BeanFactory
// Object 一个以所给名字注册的bean的实例
public static <T> T getBean(String name) throws BeansException {
return (T) beanFactory.getBean(name);
}
public static <T> T getBean(Class<T> clz) throws BeansException {
T result = (T) beanFactory.getBean(clz);
return result;
}
3.3.3 策略模式
策略模式针对不同的场景封装不同的算法,用于应对不同的业务场景。Spring中获取资源时会用到Resource接口,它用很多的实现类,像UrlResource访问网络资源,ClassPathResource方法类加载本地资源,InputStreamResource访问输入流中的资源等。有一个方法叫做DefaultResourceLoader,其中包含一个getResource方法,可以根据资源路径选取不同的Resource
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
3.3.4 责任链模式
FilterChain 过滤器责任链
public interface FilterChain {
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
3.3.5 代理模式
AOP(面向切面编程)的底层就是使用代理模式实现的,将一些公共操作,如权限校验、日志打印、提交事务等操作封装成功公共模块,作为切面;将其放在切点前后,用于对切点做一系列的增强操作。
当类实现接口时,想要对其实现代理用的是JDK的方式,具体用Proxy类的new ProxyInstance方法获取代理对象,重写InvocationHandler接口的invoke方法实现具体的代理操作
当类没有实现接口时,利用CGLIB的方式完成代理,通过Enhancer创建代理对象,重写MethodInterceptor接口的intercept方法实现代理操作
可以看到其中很重要的一点就是获取代理对象,在Spring中AOP的核心类是AbstractAutoProxyCreator,它实现了InstantiationAwareBeanPostProcessor接口,重写postProcessBeforeInstantiation和getEarlyBeanReference、postProcessAfterInitialization方法来创建具体的代理对象
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
Object cacheKey = this.getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} else {
return null;
}
}
3.3.6 观察者模式
观察者模式中包括三个部分 事件、事件源、监听器
ApplicationEvent 事件,通过继承该类,可以自定义自己的事件
ApplicationEventMulticaster,通过multicastEvent方法将事件广播给所有的事件监听器
ApplicationListener事件监听器,继承该类重写其中的onApplicationEvent方法(或者直接使用@EventListener注解),完成对监听事件的处理
3.3.7 适配器模式
1>实现方式
SpringMVC中的适配器HandlerAdatper。
2>实现原理
HandlerAdatper根据Handler规则执行不同的Handler。
3>实现过程
DispatcherServlet根据HandlerMapping返回的handler,向HandlerAdatper发起请求,处理Handler。
HandlerAdapter根据规则找到对应的Handler并让其执行,执行完毕后Handler会向HandlerAdapter返回一个ModelAndView,最后由HandlerAdapter向DispatchServelet返回一个ModelAndView。
适配器模式可以通过新增一个适配器让两个原本不兼容的接口一起工作。比较常见的例如MethodBeforeAdviceInterceptor,它实现了MethodInterceptor接口,并且其中有一个MethodBeforeAdvice类型实例,
在inoke方法中调用advice的before方法,将这两个接口组合在一起工作,在方法执行之前完成一些操作,
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return new MethodBeforeAdviceInterceptor(advice);
}
}
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {
private MethodBeforeAdvice advice;
/**
* Create a new MethodBeforeAdviceInterceptor for the given advice.
* @param advice the MethodBeforeAdvice to wrap
*/
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
return mi.proceed();
}
}
3.3.8 装饰者模式
1>实现方式
Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。
2>实质
动态地给一个对象添加一些额外的职责。
就增加功能来说,Decorator模式相比生成子类更为灵活
4. Linux 运维、Tomcat、Nginx、Docker、K8s、Kuboard
1>轮询与心跳
| 服务监控方式 | 说明 |
|---|---|
| 轮询 | 在轮询方式中,主机逐个查询的方式是主动向从机发送一条查询信息,等待从机应答,无应答则任务服务已挂或不存在 |
| 心跳 | 从机服务主动交互主机,更新在主机的心跳时间和信息 |
4.1 Linux

Linux 系统的核心是内核。内核控制着计算机系统上的所有硬件和软件,在必要时分配硬件,并根据需要执行软件。
包括用户空间和内核空间,用户空间(User Space)又包括用户的应用程序(User Applications)、C 库(C Library) ;内核空间(Kernel Space)包括系统调用接口(System Call Interface)、内核(Kernel)、平台架构相关的代码(Architecture-Dependent Kernel Code) 。
Linux 中一切皆文件,包含普通文件、目录文件、链接文件、设备文件、命名管道;
1>目录结构
/bin:存放二进制可执行文件(ls,cat,mkdir 等),常用命令一般都在这里;
/etc:存放系统管理和配置文件;
/home:存放所有用户文件的根目录,是用户主目录的基点,比如用户 user 的主目录就是/home/user,可以用~user 表示;
/usr:用于存放系统应用程序/opt:额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把 tomcat 等都安装到这里;
/proc:虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息;
/root:超级用户(系统管理员)的主目录(特权阶级 o);
/sbin: 存放二进制可执行文件,只有 root 才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如 ifconfig 等;
/dev:用于存放设备文件
/mnt:系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统;
/boot:存放用于系统引导时使用的各种文件;
/lib :存放着和系统运行相关的库文件 ;
/tmp:用于存放各种临时文件,是公用的临时文件存储点;
/var:用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等;
/lost+found:这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows 下叫什么.chk)就在这里。
2>命令
- 1、系统资源监控
| 命令 | 说明 |
|---|---|
| top | 显示系统的整体状态,包括 CPU 使用率、内存使用情况、进程列表等 |
| free | 显示系统中物理内存和交换空间的使用情况。可以加上 -h 参数以人类可读的方式显示 |
| vmstat | 报告虚拟内存统计信息,同时提供关于进程、CPU、I/O 等方面的数据 |
| iostat | 展示 CPU 使用率以及磁盘 I/O 统计,帮助分析磁盘性能瓶颈 |
| mpstat | 提供每个 CPU 核心的详细统计信息,有助于多核处理器的性能调优 |
| sar | 收集并报告各种系统活动信息,如 CPU、内存、网络、I/O 等的历史数据 |
- 2、文件系统与磁盘管理
| 命令 | 说明 |
|---|---|
| df | 显示已挂载文件系统的磁盘空间使用情况。可以加上 -h 参数以人类可读的方式显示 |
| du | 估算文件和目录的空间占用量。结合 -sh 参数可以得到简洁的汇总结果 |
| lsblk | 列出所有块设备的信息,包括硬盘、分区、光驱等 |
| fdisk 或 parted | 分区管理工具,允许创建、删除、调整分区大小等操作 |
| mount 和 umount | 挂载和卸载文件系统,通常用于访问外部存储设备或网络共享 |
- 3、服务管理
| 命令 | 说明 |
|---|---|
| systemctl | 控制 systemd 服务,可以启动、停止、重启、启用或禁用服务 |
| service | 传统方式控制服务,但在现代 Linux 系统中逐渐被 systemctl 取代 |
| ps | 列出正在运行的进程,结合 aux 参数可以显示更多信息 |
| kill 和 pkill | 向进程发送信号,通常用于终止进程 |
- 4、日志查看
| 命令 | 说明 |
|---|---|
| journalctl | 查看 systemd 日志,包含启动日志、服务日志等 |
| cat, less, tail | 用于查看文本文件内容,特别是日志文件 |
| grep | 在文件或标准输入中搜索指定模式的内容,非常适合从大量日志中提取关键信息 |
- 5、用户与权限
| 命令 | 说明 |
|---|---|
| whoami | 显示当前用户的用户名 |
| id | 显示用户 ID、组 ID 及其所属的所有组 |
| su 和 sudo | 切换用户身份,su 直接切换,而 sudo 允许临时执行管理员权限命令 |
| chmod 和 chown | 修改文件或目录的权限(chmod)和所有权(chown) |
cat vi more less head echo 标准输出查看文件,向前向后查看文件,用于查看日志文件或配置文件
mkdir touch vi 创建文件
diff 比对文件不同
cp mv 复制源文件、移动源文件或目录
rm 删除
wc 统计文件字数行数等信息并打印
uniq 去重复行
grep 查找文件,支持正则表达式查询 ps -ef | grep nginx
find 查找文件 find / -name nginx
cd pwd ls ll 切换目录、遍历当前目录下文件
chmod chown 修改文件所属用户权限组 chmod 777 nginx.conf
ps kill 查看杀死进程
gzip unzip tar 解压缩命令
du -h [目录名]:查看指定文件夹下的所有文件大小
df -hl 查看磁盘空闲信息
uptime 查看服务器负载情况
job -l 查看后台任务
ifconfig 查看ip信息
netstat 测试网络是否连通
yum wget 下载资源
lrzsz 文件上传下载
3>sheel
BASH 是 Bourne Again SHell 的缩写。它由 Steve Bourne 编写,作为原始 Bourne Shell(由/ bin / sh 表示)的替代品。它结合了原始版本的 Bourne Shell 的所有功能,以及其他功能,使其更容易使用。从那以后,它已被改编为运行 Linux 的大多数系统的默认 shell。
4.2 Docker 容器管理
部署方便、部署安全、隔离性好、扩展性好、快速回滚、成本低;
1>Docker与虚拟机的区别
| 特性 | 容器 | 虚拟机 |
|---|---|---|
| 启动速度 | 秒级 | 分钟 |
| 性能 | 接近原生 | 弱 |
| 内存占用 | 很少 | 较多 |
| 硬盘占用 | MB | GB |
| 运行密度 | 单机运行上千个容器 | 单机运行几十个虚拟机 |
| 迁移性 | 优秀 | 一般 |

4.2.1 Docker
Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖打包到一个独立的、轻量级的容器中。通过这种方式,可以确保应用在任何环境中都能一致地运行。
1>特性
- 1、环境一致性
- 2、资源隔离
为每个应用提供独立的运行环境,避免进程间的干扰。 - 3、快速部署
简化了应用的分发和部署流程,支持持续集成/持续交付(CI/CD)管道。 - 4、可移植性
容器可以在不同的硬件、操作系统甚至云平台上无缝迁移
4.2.2 Docker Image 镜像
包含了一个基本操作系统环境,相当于一个 root 文件系统;
可以从仓库中拉取指定镜像;可以根据现有容器创建镜像;可以通过 DockerFile 构建镜像
4.2.3 Docker Container 容器
镜像运行后的实例就是容器;
容器包含运行、已暂停、重新启动、已退出状态
4.2.4 Docker Repository 仓库
集中存放镜像的地方
仓库 Docker hub、阿里云镜像仓库
4.2.5 命令
1>镜像
| 命令 | 说明 |
|---|---|
| docker pull imagename | 拉取镜像 |
| docker images | 遍历本地镜像 |
| docker rmi imageid | 删除镜像 |
| docker rmi image:tagname | |
| docker tag imageid new-repo:tagname | 镜像tag变更 |
| docker tag local-image:tagnam new-repo:tagname | |
| docker push new-repo:tagname | 推送镜像到仓库 |
2>容器
| 命令 | 说明 |
|---|---|
| docker container ls | 遍历运行中的容器 |
| docker container ps -all | 遍历已创建的容器 |
| docker stop containerid | 停止运行中的容器 |
| docker start containerid | 启动运行中的容器 |
| docker rm containerid | 删除容器 |
| docker run | 运行镜像为容器,支持指定端口 |
| docker exec | 进入容器内部 |
| docker inspect | 查看镜像信息 |
Docker提供docker stats和docker事件等工具来监控生产中的Docker
4.2.6 Dockerfile制作镜像
1>通过已有容器创建
docker commit containerid
2>通过 Dockerfile 制作镜像
docker build
3>Dockerfile制作镜像
以 ActiveMQ 为例,创建一个 Dockerfile 文件,Dockerfile作为文件名称且无后缀名;
准备文件 jetty-realm.properties 变更管理账号。然后通过 Dockerfile 制作镜像,替换 ActiveMQ 配置文件,生成的镜像运行后,管理账号就是我们配置的新账号。
jetty-realm.properties
admin: niaonao, admin
user:user, user
Dockerfile
FROM webcenter/activemq:latest # 基于镜像制作新镜像
WORKDIR /opt/activemq/conf # 当前工作路径
ENV FILE_NAME jetty-realm.properties # 设置环境变量
RUN rm -rf $FILE_NAME # 存在文件则删除,添加准备好的文件到当前工作路径
COPY ./$FILE_NAME ./
RUN chmod 644 $FILE_NAME # 修改文件权限
RUN chown activemq.activemq -R $FILE_NAME
CMD ["sh", "-c", "/app/run.sh run"]

COPY 和 ADD 命令的区别
- COPY 都有文件复制的功能
- ADD会对压缩文件自动解压缩
4.2.7 Docker 容器的安全性
- 1、最小权限原则
以非 root 用户身份运行容器,限制容器对宿主机系统的访问 - 2、定期更新镜像
保持基础镜像和应用程序依赖的最新状态,及时修补已知漏洞 - 3、扫描镜像漏洞
使用工具(如 Clair、Trivy)定期检查镜像是否存在安全隐患 - 4、网络隔离
通过防火墙规则或 Docker 内置的网络功能限制容器之间的通信 - 5、卷挂载保护
谨慎使用 -v 参数挂载宿主机目录,避免泄露敏感信息 - 6、秘密管理
不要将密码、API 密钥等敏感信息硬编码在 Dockerfile 中,推荐使用环境变量或专用的秘密管理服务
4.2.8 监控 Docker 容器的状态和性能
- 1、内置命令
使用 docker stats 实时查看各个容器的 CPU、内存、网络 I/O 和磁盘 I/O 使用情况。 - 2、第三方工具
集成 Prometheus、Grafana、cAdvisor 等监控工具,收集详细的指标并生成可视化报告。 - 3、日志分析
利用 ELK Stack(Elasticsearch, Logstash, Kibana)或 Splunk 分析容器日志,捕捉异常行为。 - 4、健康检查
通过 Docker 的 HEALTHCHECK 指令定期探测容器内部服务的健康状况,确保其正常运行。
4.3 Kubernetes 容器编排工具
Kubernetes(K8s)是⼀个开源容器管理⼯具,负责容器部署,容器扩缩容以及负载平衡。
通过K8s,开发者可以轻松地在多个主机上调度和运行容器,确保高可用性和灵活性的同时简化运维工作。K8s进行跨主机通信容器,Docker 构建运行容器,K8s编排管理容器。
容器编排是指,容器自动化部署、管理、扩容、联网;对多个容器进行编排化管理,按照编排组织部化部署应用。
| 特性 | 复杂度 | |
|---|---|---|
| K8s | 支持复杂的调度策略、自动伸缩等 | 功能更强大,配置较复杂 |
| Docker Swarm | 提供基本的服务发现、负载均衡等功能 | 更加贴近 Docker 生态系统,容易上手 |
4.3.1 架构组件
- 1、Master Node
(1)API Server:提供 RESTful API 接口,是集群内外与 Kubernetes 交互的主要入口
(2)Scheduler:负责将 Pod 分配到合适的 Worker Nodes 上执行
(3)Controller Manager:管理控制器实例,如节点控制器、复制控制器等,确保集群状态符合预期
(4)etcd:分布式键值存储系统,保存集群的所有配置数据 - 2、Worker Node
(1)Kubelet:负责管理本机上的容器,与 Master Node 通信以接收指令
(2)Kube-proxy:实现服务发现和负载均衡功能,维护网络规则以保证 Pod 之间的正常通信
(3)Container Runtime:如 Docker 或 containerd,用于实际运行容器
4.3.2 特性
- 1、自动部署和回滚
(1)支持滚动更新策略,能够在不影响服务的情况下逐步替换旧版本的应用程序;
(2)如果新版本出现问题,还可以快速回滚到之前的稳定版本 - 2、自我修复能力
(1)当某个 Pod 发生死机或异常时,Kubernetes 会自动重启该 Pod 或将其迁移到其他健康的节点上 - 3、水平伸缩
(1)根据预定义的指标(如 CPU 使用率)自动增加或减少副本数量,以适应流量变化 - 4、服务发现和负载均衡
(1)为每个服务分配一个稳定的 IP 地址,并内置 DNS 支持,方便跨服务间的调用
(2)同时提供多种类型的负载均衡机制来分发请求 - 5、存储编排
(1)能够动态地挂载持久卷(Persistent Volumes),支持不同类型的存储后端,如本地磁盘、云存储等 - 6、密钥和配置管理
(1)使用 Secrets 和 ConfigMaps 来安全地存储敏感信息和应用程序配置参数
4.3.3 Pod、Service 和 Ingress
- 1、Pod
是 Kubernetes 中最基本的部署单位,代表一个或多个紧密相关的容器集合。每个 Pod 共享同一个网络命名空间和存储卷,这意味着它们可以相互通信而无需额外配置。`
(1)它封装了一个完整的应用环境,包括所有必要的依赖和服务
(2)Kubernetes 只对 Pod 进行调度、启动、停止等操作,而不是单独处理单个容器
(3)在同一 Pod 内的容器之间共享文件系统和端口空间,便于协作 - 2、Service
Service 是一种抽象层,用于定义一组逻辑上的 Pod 以及访问这些 Pod 的策略。
(1)它为内部服务提供了稳定的 IP 地址和 DNS 名称,支持多种类型的负载均衡机制,如 ClusterIP、NodePort 和 LoadBalancer。 - 3、Ingress
Ingress 是一种 API 对象,用于定义进入集群的 HTTP(S) 流量路由规则;
(1) Ingress Controller 则实现了这些规则,提供外部访问入口。常见的 Ingress Controller 实现有 NGINX Ingress Controller 和 Traefik 等。
4.3.4 K8s 应用滚动更新和回滚
- 1、Rolling Update
(1)通过 kubectl rollout 命令或 YAML 文件中的 .spec.strategy.type=RollingUpdate 字段定义滚动更新策略。
(2)这种方式会在不影响现有服务的情况下逐步替换旧版本的 Pod,确保零停机时间。 - 2、Rollback
(1)如果新版本出现问题,可以通过 kubectl rollout undo 命令快速回滚到之前的稳定版本
(2)Kubernetes 会记录每次部署的历史记录,方便随时恢复
4.3.5 Helm
Helm 是 Kubernetes 的包管理工具,允许用户以 Chart(一组 YAML 文件)的形式打包、发布和安装复杂的 Kubernetes 应用
- 1、简化部署流程
将多资源对象的创建过程抽象为一个统一的 Chart,减少了手动编写 YAML 文件的工作量 - 2、版本控制
支持 Chart 的版本化管理,便于追踪变更历史并进行升级或降级 - 3、依赖解析
自动处理 Chart 之间的依赖关系,确保所有必要的组件都能正确部署
4.3.6 Horizontal Pod Autoscaler (HPA)
Horizontal Pod Autoscaler (HPA) 是 Kubernetes 的一项特性,用于根据实时监控CPU、内存使用率等数据自动调整 Pod 的副本数,以应对流量波动
1>工作原理
- 1、Metrics Server
收集性能指标,如 CPU、内存使用情况等 - 2、HPA 控制器
定期查询 Metrics Server 获取最新的指标值,并根据预设的目标值计算所需的 Pod 数量 - 3、扩缩容操作
当当前 Pod 数量低于或高于目标值时,HPA 会触发相应的扩容或缩容操作,直到达到期望的状态
4.3.7 Custom Resource Definitions (CRDs)
Custom Resource Definitions (CRDs) 是 Kubernetes 提供的一种扩展机制,允许开发者定义自定义资源类型,从而扩展 Kubernetes API,满足特定业务需求。
4.3.8 保证 K8s 的应用安全性
- 1、RBAC 授权
基于角色的访问控制(Role-Based Access Control, RBAC),严格限制用户的操作权限,提高安全性 - 2、Network Policies
定义 Pod 之间的网络通信策略,限制哪些 Pod 可以相互通信,增强了安全性 - 3、Secrets 和 ConfigMaps
使用 Secrets 来安全地存储敏感信息,如密码、API 密钥等;使用 ConfigMaps 存储应用程序配置参数 - 4、镜像扫描
利用工具(如 Clair、Trivy)定期检查镜像是否存在安全隐患,及时修补已知漏洞 - 5、最小权限原则
以非 root 用户身份运行容器,限制容器对宿主机系统的访问 - 6、日志和监控
部署 ELK Stack 或 Prometheus + Grafana 等解决方案,收集详细的运行日志和性能指标,及时发现问题并优化系统表现,触发告警到负责人等
4.4 Nginx
一款高性能的 HTTP 和反向代理服务器
广泛应用于 Web 服务的前端负载均衡、静态内容分发以及 API 网关等领域
1>特点
- 1、异步非阻塞 I/O 模型
能够高效地处理大量并发连接,特别适合高流量网站 - 2、轻量级
资源占用少,启动速度快,配置简单 - 3、模块化设计
支持多种功能模块,如 SSL/TLS 加密、缓存、压缩等,并且可以通过第三方模块扩展其能力 - 4、丰富的生态系统
拥有庞大的用户群体和开发者社区,提供了大量的文档、教程和支持资源 - 5、CDN 集成
利用 CDN 服务加速静态资源的分发,进一步减轻源站压力
http {
upstream backend_servers {
server backend1.example.com;
server backend2.example.com;
}
server {
listen 80;
server_name example.com;
# 静态资源转发并设置缓存有效期
location /static/ {
alias /var/www/example.com/static/;
expires 30d;
}
location /api/ {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://backend_app;
proxy_set_header Host $host;
}
}
}
4.4.1 组件工作流程
- 1、Master Process
(1)负责读取配置文件并启动 Worker 进程
(2)通常只占用少量资源,运行在后台,不直接处理请求 - 2、Worker Processes
(1)实际处理客户端请求的进程
(2)每个 Worker 进程是独立的,可以并发处理多个连接;数量一般根据 CPU 核数设置 - 3、Connection Listener
(1)监听来自客户端的新连接,并将它们分配给可用的 Worker 进程
(2)由 Master Process 创建,但由所有 Worker 共享 - 4、Event Modules
(1)管理 I/O 操作(如接受新连接、读写数据),支持多种事件驱动模型(如 epoll、kqueue)以提高效率
(2)根据操作系统选择最优的事件处理机制 - 5、HTTP Modules
(1)处理 HTTP 协议相关的任务,包括解析请求、生成响应等
(2)提供了丰富的功能模块,如 ngx_http_proxy_module、ngx_http_fastcgi_module 等 - 6、Stream Modules
(1)处理非 HTTP 流量,如 TCP/UDP 协议的数据包转发
(2)适用于数据库、消息队列等应用场景
4.4.2 配置
1>Events 配置
| 配置项 | 说明 |
|---|---|
| worker_connections | 每个 Worker 进程允许的最大同时连接数,影响系统能处理的最大并发量 |
| multi_accept | 如果开启,则 Worker 进程会尽可能多地接受新连接,而不是每次只接受一个 |
events {
worker_connections 1024;
multi_accept on;
}
2>HTTP 配置
| 配置项 | 说明 |
|---|---|
| include | 引入 MIME 类型定义文件,确保正确识别各种文件格式 |
| default_type | 当无法确定文件类型时使用的默认 MIME 类型 |
| log_format | 自定义访问日志格式,记录更多有用信息 |
| access_log | 设定访问日志的位置和格式 |
| sendfile | 启用零拷贝技术,加快文件传输速度 |
| tcp_nopush | 防止 Nagle 算法导致的小包合并问题,适合小文件下载 |
| tcp_nodelay | 立即发送数据包,避免延迟,特别适用于长轮询或 WebSocket |
| keepalive_timeout | 保持长连接的时间,减少握手开销 |
| gzip | 开启压缩功能,减小传输体积,提升页面加载速度;可以通过 gzip_types 指定需要压缩的内容类型 |
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml+rss;
}
3>Server配置
| 配置项 | 说明 |
|---|---|
| listen | 监听的端口号,可附加 IP 地址限制访问来源 |
| server_name | 绑定域名,支持通配符和正则表达式匹配 |
| location | 定义 URL 路径模式及其对应的处理逻辑,例如静态文件服务或反向代理 |
| root 和 index | 指定文档根目录和默认索引文件 |
| proxy_pass | 将请求转发给后端服务器,常用于构建微服务架构中的网关层 |
| proxy_set_header | 修改或添加 HTTP 请求头字段,传递原始客户端信息给后端服务 |
server {
listen 80;
server_name example.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /api/ {
proxy_pass http://backend_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
4>Upstream 配置
| 配置项 | 说明 |
|---|---|
| upstream | 定义一组后端服务器,供 proxy_pass 使用,支持负载均衡算法(如 round-robin, least_conn, ip_hash) |
| server | 指定具体的后端服务器地址,可以是 IP:port 形式或者 Unix 域套接字;还可以通过 weight 参数调整权重 |
upstream backend_servers {
server backend1.example.com weight=5;
server backend2.example.com;
server unix:/tmp/backend3.socket;
}
5>SSL/TLS 配置
| 配置项 | 说明 |
|---|---|
| ssl_certificate 和 ssl_certificate_key | 指向 SSL 证书和私钥文件的位置 |
| ssl_protocols | 启用的安全协议版本,建议至少使用 TLSv1.2 及以上 |
| ssl_ciphers | 指定允许使用的加密套件,遵循现代最佳实践 |
| ssl_prefer_server_ciphers | 优先使用服务器端配置的加密套件,增强安全性 |
| add_header | 添加 HTTP 响应头,如 HSTS (HTTP Strict Transport Security) 强制浏览器仅通过 HTTPS 访问 |
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000" always;
}
6>限流熔断
| 配置项 | 说明 |
|---|---|
| limit_req_zone | 创建限流区域,基于客户端 IP 地址限制每秒请求数 |
| limit_req | 应用限流规则,允许短时间内的突发流量(burst)而不延迟响应(nodelay) |
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /login/ {
limit_req zone=one burst=5 nodelay;
}
}
7>健康检查
| 配置项 | 说明 |
|---|---|
| health_check | 定期探测后端服务器的健康状况,自动移除故障节点,保证服务高可用性 |
upstream backend {
server backend1.example.com;
server backend2.example.com;
health_check;
}
4.4.3 动静分离与缓存策略
- 1、动静分离
将静态资源(如图片、CSS、JS 文件)托管在 CDN 或专用存储中,减轻主站压力 - 2、缓存设置
合理配置 proxy_cache 和 fastcgi_cache,根据内容类型和访问频率制定差异化的缓存规则 - 3、过期控制
利用 expires 指令设置合适的缓存有效期,确保客户端及时获取最新数据
4.4.4 Nginx 负载均衡
支持 round-robin、least_conn、ip_hash 等多种算法,灵活分配任务至不同后端节点。
| 配置 | 算法 | 说明 |
|---|---|---|
| round-robin | 轮询算法 | 默认算法,请求依次均匀的打到多台服务器 |
| least_conn | 最少连接 | 优先分配给当前连接数最少的服务器 |
| ip_hash | 最少连接 | 根据客户端 IP 地址计算哈希值,保证同一 IP 的请求总是发送到同一台服务器 |
| weight | 权重 | 权重高的服务器会分配更多的请求 |
| backup | 备用服务器 | 备用服务器仅在其他服务器不可用时才接收请求 |
- 1、轮询算法
upstream backend_servers {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
- 2、最少连接
upstream backend_servers {
least_conn;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
- 3、IP Hash
常用于需要会话保持的场景
upstream backend_servers {
ip_hash;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
- 4、权重
upstream backend_servers {
server backend1.example.com weight=5;
server backend2.example.com;
server backend3.example.com;
}
- 5、备用服务器
upstream backend_servers {
server backend1.example.com;
server backend2.example.com backup;
}
4.4.5 健康检查
通过 max_fails 和 fail_timeout 参数来自动移除故障节点
| 配置 | 说明 |
|---|---|
| max_fails | 允许的最大失败次数 |
| fail_timeout | 连续失败的时间窗口,在此期间内如果达到 max_fails,该服务器将被视为不可用 |
upstream backend_servers {
server backend1.example.com max_fails=3 fail_timeout=30s;
server backend2.example.com max_fails=3 fail_timeout=30s;
}
4.4.6 Nginx 优化
- 1、调整 Worker 进程数
(1)根据服务器 CPU 核数设置合适的 worker_processes 数量,通常设为 auto 即可 - 2、增加最大连接数
(1)适当增大 worker_connections 的值,以支持更多并发连接 - 3、启用零拷贝技术
(1)通过 sendfile 和 tcp_nopush 指令加快文件传输速度 - 4、优化缓存策略
(1)合理配置 proxy_cache 和 fastcgi_cache,根据内容类型和访问频率制定差异化的缓存规则 - 5、减少握手开销
(1)开启会话复用 (ssl_session_cache) 和 OCSP Stapling,降低 SSL 握手延迟 - 6、启用 HTTP/2 和 HTTP/3
(1)利用多路复用、头部压缩等特性显著改善页面加载时间
4.5 Tomcat
Apache Tomcat 是一个开源的 Servlet 容器。支持 HTTP、HTTPS 协议,通过配置集成到其他 Web 服务器中作为后端应用服务器。
4.5.1 组件
- 1、Server
(1)代表整个 Tomcat 实例,是所有其他组件的容器。包含了多个 Service 子组件 - 2、Service
(1)定义了一组 Connector 和 Engine 之间的关系,每个 Service 可以独立处理请求
(2)一个 Server 中可以有多个 Service,但一般情况下只需要一个 - 3、Connector
(1)负责接收来自客户端的连接请求,并将其传递给 Engine 处理
(2)类型:- HTTP/1.1 Connector:用于处理标准的 HTTP 请求
- AJP Connector:用于与 Apache HTTP Server 集成,通过 AJP 协议转发请求
- HTTP/2 Connector:支持最新的 HTTP/2 协议,提升性能
- NIO/NIO2 Connector:非阻塞 I/O 模型,提高并发处理能力
- APR (Apache Portable Runtime) Connector:利用本地库优化性能,特别适合高负载环境
- 4、Container
(1)包含并管理一组 Web 应用程序或其部分
(2)层次结构:- Engine:顶级容器,对应于一个 Service,包含 Host 和 Context
- Host:虚拟主机,对应于一个域名或 IP 地址,包含多个 Web 应用程序(Context)
- Context:具体的应用程序上下文,即 WAR 文件解压后的目录结构
- 5、Valve
(1)插件式的组件,可以在请求进入或离开容器时执行特定操作,如日志记录、认证等
(2)灵活可扩展,允许开发者自定义行为 - 6、Realm
(1)用户数据库接口,提供了对用户的认证和授权功能
(2)内存 Realm、JDBC Realm、LDAP Realm 等 - 7、Loader
(1)负责加载应用程序所需的类文件,确保每个 Web 应用都有自己独立的 ClassLoader
(2)支持热部署,当检测到新的类文件时自动重新加载 - 8、Manager
(1)提供对 Web 应用的管理功能,如会话管理和资源清理
(2)内置默认 Manager,也可替换为自定义版本
4.5.2 工作流程
- 1、启动
解析 server.xml 配置文件,初始化各个组件
启动监听端口的 Connectors,等待客户端连接 - 2、接收请求
当客户端发起 HTTP 请求时,Connector 接收该请求并将相关信息封装成 Request 对象 - 3、路由分发
Engine 根据 URL 路径将请求分配给相应的 Host 和 Context - 4、处理请求
Context 中的 Servlet 或 JSP 页面被调用来处理请求,可能涉及数据库访问、业务逻辑计算等操作 - 5、生成响应
处理完成后,构建 Response 对象返回给客户端 - 6、关闭
关闭连接,释放资源;如果启用了长连接,则保持连接以备下次使用
接收到停止命令后,优雅地终止正在运行的任务,保存必要的状态信息,然后释放所有资源
4.5.3 热部署
无需重启整个 Tomcat 实例即可更新应用程序代码,缩短开发周期
1>方式一:conf/server.xml 设置 autoDeploy 和 deployOnStartup 属性为 true
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true" deployOnStartup="true">
2>方式二:conf/server.xml 设置 Context 的 reloadable 属性
<Context path="/myapp" docBase="myapp" reloadable="true"/>
- 1、WatchedResource
监控特定文件的变化,一旦发生变化则触发重载 - 2、Automatic Deployment
自动部署新上传的 WAR 文件 - 3、Reloading Classes
动态加载修改后的类文件,适用于开发调试阶段
4.5.4 JAR和WAR的区别
| 类型 | 部署方式 | 启动方式 | 适用场景 |
|---|---|---|---|
| JAR (Java ARchive) | 不直接部署到应用服务器,通常作为依赖项被其他项目引用 | 通过命令行使用 java -jar 启动独立应用程序 | 适用于构建可重用的库或独立运行的应用程序 |
| WAR (Web Application Archive) | 直接部署到支持 Servlet 规范的应用服务器tomcat等 | 通过应用服务器提供的接口(如 URL)访问 Web 应用 | 适用于基于 Web 的应用程序 |
4.5.5 Tomcat 远程调试
(1)在{TomcatHome}/bin下创建setenv.sh,维护配置即可。
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

(2)打开IDEA的Run/Debug Configuration找到Remote JVM Debug,配置远程服务器和端口,指定程序module即可。不需要远程调试时,Tomcat不开启远程调试配置。

4.5.6 优化
- 1、关闭AJP监听 server.xml
关闭AJP监听,仅保留http监听;
<!-- Define an AJP 1.3 Connector on port 8009 -->
<!--<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />-->
- 2、调整线程池大小 server.xml
在server.xml中,可以配置maxThreads、minSpareThreads和maxSpareThreads
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" maxThreads="500" minSpareThreads="50" maxSpareThreads="100"/>
- 3、启用异步 I/O
选择 NIO/NIO2 或 APR Connector 以充分利用硬件资源 - 4、缓存静态资源
将频繁访问的静态文件(如图片、CSS、JS)缓存起来,减少磁盘 I/O
<Context ...>
<Resources cachingAllowed="true" cacheMaxSize="102400"/>
</Context>
- 5、压缩输出内容
开启 Gzip 压缩,减小传输体积
<Connector ... compression="on" compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,application/json"/>
- 6、setenv.sh 外置文件动态优化 JVM 参数
合理配置堆大小、垃圾回收策略等 JVM 相关选项

4.6 金丝雀发布、蓝绿部署
在分布式环境下,服务不再是单机部署,通常为多机器、多实例部署。
一般的部署新版本服务,建议灰度部署。先发布部分服务器,验证上线功能无误后,逐步发布其他服务器。服务上线后及时验证服务功能,做好流量监控。上线如有异常,及时回滚版本。
灰度发布有助于提高软件发布的成功率和系统的稳定性。有金丝雀发布、蓝绿部署两种方式。
| 部署方式 | 实施 | 资源消耗 | 风险 |
|---|---|---|---|
| 蓝绿部署 | 一次性切换新旧版本 | 持续占用双倍资源,直到旧版本下线 | 完全隔离的两个环境 |
| 金丝雀发布 | 逐步将新版本暴露给部分流量 | 过渡期内可能需要更多资源 | 通过分阶段发布减少影响范围 |
- 1、蓝绿部署
(1)概念
同一时刻维护两套完整的生产环境——“蓝色”(当前正在提供服务的环境)和“绿色”(准备上线的新版本环境)。两者之间通过简单的流量切换实现无缝迁移,而无需停机
(2)示例
通过nginx权重配置,切换流量到新服务
upstream green_backend {
server new_backend.example.com;
}
upstream blue_backend {
server old_backend.example.com;
}
server {
set $backend blue_backend;
location / {
proxy_pass http://$backend;
}
location /switch_to_green {
set $backend green_backend;
proxy_pass http://$backend;
}
}
- 2、金丝雀发布
(1)概念
将新版本的应用程序逐步暴露给一部分用户或流量,以评估其稳定性和性能。如果一切正常,则逐渐增加新版本的比例,直到完全替换旧版本
(2)示例
通过nginx权重配置,切换部分流量到新服务
upstream backend_servers {
server old_backend.example.com weight=9;
server new_backend.example.com weight=1;
}
server {
location / {
proxy_pass http://backend_servers;
}
}
5. 版本管理Maven、Git、SVN
5.1 Maven
Apache Maven 是一个强大的项目管理和构建自动化工具。它通过使用 POM(Project Object Model)项目对象模型文件来管理项目的所有配置信息,包括构建过程、依赖关系和插件等
5.1.1 核心概念
1>仓库
存放大量依赖jar包的地方
| 类型 | 说明 |
|---|---|
| 本地仓库 | 位于用户本地机器上的存储区域,默认路径为 ~/.m2/repository,用于缓存下载的依赖项 |
| 中央仓库 | 由 Apache Maven 维护的一个公共远程仓库,包含了大量常用的库和插件 |
| 私有仓库 | 企业内部或第三方提供的私有仓库,用于托管专有的库和构件 |
2>插件
Maven 插件是一组可重用的任务,它们扩展了 Maven 的功能,支持从编译代码到打包、部署等一系列操作
| 插件 | 说明 |
|---|---|
| maven-compiler-plugin | 编译 Java 源代码 |
| maven-surefire-plugin | 执行单元测试 |
| maven-war-plugin | 创建 Web 应用程序包(WAR) |
| maven-jar-plugin | 生成 JAR 文件 |
3>全局配置 settings.xml
通常位于 Maven 安装目录下的 conf/settings.xml 或用户主目录下的 .m2/settings.xml。用于配置全局性的参数,如镜像服务器、认证信息、代理设置等
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- 本地仓库地址 -->
<localRepository>D:\localRepository</localRepository>
<!-- 插件 -->
<pluginGroups>
</pluginGroups>
<proxies>
</proxies>
<!-- 账号 -->
<servers>
<server>
<id>z_mirrors</id>
</server>
<server>
<id>nexus-releases</id>
<username>name</username>
<password>password</password>
</server>
</servers>
<!-- 镜像仓库 -->
<mirrors>
<mirror>
<id>z_mirrors</id>
<mirrorOf>*,!releases,!snapshots,!nexus-releases,!nexus-snapshots</mirrorOf>
<url>https://repo.huaweicloud.com/repository/maven/</url>
</mirror>
</mirrors>
<!-- 激活环境 -->
<profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>nexus-releases</id>
<url>http://192.168.1.1/nexus/content/repositories/releases/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
</settings>
4>项目配置 pom.xml
POM(groupId:artifactId:version)
POM指项目对象模型文件pom.xml,包括项目的基本信息groupId:artifactId:version、依赖关系、插件配置等。
| 标签 | 说明 |
|---|---|
| groupId | 项目组的唯一标识符号,项目或公司标识符,通常是反向域名格式 |
| artifactId | 项目的唯一标识,项目名称或模块名称。根据所负责项目自定义即可 |
| version | 项目版本号,一般的我们的项目会进行版本迭代升级。引用maven jar包依赖的产品,可以通过版本号来指定使用仓库中的某个历史版本 |
- 阿里fastjson依赖示例:com.alibaba:fastjson:1.2.45
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
- 自定义依赖示例:pers.niaonao:api:1.0-SNAPSHOT
<dependency>
<groupId>pers.niaonao</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
POM(属性、依赖、插件、排除)
| 标签 | 说明 |
|---|---|
| properties | 定义一组属性值。常用于版本管理,通过${name}占位符形式统一管理版本号 |
| parent | 表明当前项目包含父级依赖 |
| modules | 多模块项目,当前项目包含子项目,子标签module指明具体的子模块 |
| dependencies | 表明依赖关系,当前项目引入了xxx.jar包依赖。比如下述文件引入了springframework、pagehelper jar包 |
| scope | 依赖作用域compile, provided, runtime, test, system |
| profiles | 激活环境,可以通过 -P 参数显式指定要使用的 Profile |
| exclusions | 排除某些jar包依赖,依赖具有传递性,常用该标签规避依赖冲突问题 |
| build | 定义构建过程中的插件和资源配置 |
| plugins | 表明当前项目引入插件,插件是一组可重用的任务,它们扩展了 Maven 的功能,支持从编译代码到打包、部署等一系列操作 |
<?xml version="1.0" encoding="UTF-8"?>
<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>pers.niaonao</groupId>
<artifactId>api</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>my-api</name>
<description>my-api服务</description>
<!-- 模块 -->
<modules>
<module>api-web</module>
<module>api-service</module>
<module>api-domain</module>
<module>api-dao</module>
<module>api-common</module>
</modules>
<!-- 属性管理统一版本 -->
<properties>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.19.RELEASE</version>
<relativePath/>
</parent>
<!-- 依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.0.1.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- 排除不必要的传递依赖 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<!-- 激活环境 -->
<profiles>
<profile>
<id>dev</id>
<properties>
<env>dev</env>
</properties>
<!-- 默认生效 -->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>test</id>
<properties>
<env>test</env>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<env>prod</env>
</properties>
</profile>
</profiles>
<!-- 插件 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
5>生命周期 Lifecycle
compile、test、package、install、deploy、clean
# 编译源代码
mvn compile
# 安装依赖包、插件到本地仓库
mvn install
# 打包项目为war或jar文件
mvn package
# 部署到远程仓库
mvn deploy
# 清理之前的构建结果
mvn clean
5.1.2 工作流程
- 1、解析 POM 文件
读取并解析 pom.xml 中的信息,确定项目结构和配置 - 2、下载依赖项
根据 POM 中声明的依赖关系,自动从本地或远程仓库中检索所需的库 - 3、执行生命周期阶段
按照指定的顺序依次执行各个生命周期阶段的目标(goals)。例如,mvn clean install 会先清理旧的构建产物,然后编译源码、运行测试、打包并安装到本地仓库 - 4、处理插件目标
在每个生命周期阶段内,可能会调用一个或多个插件来完成具体的任务 - 5、输出构建结果
生成相应的包(如 JAR、WAR 文件)并将其部署到指定位置
5.1.3 依赖作用域
依赖作用域可以优化项目的构建性能,避免不必要的依赖传递,并确保应用程序在不同环境中的一致性和稳定性。
| 类型 | 作用 | 传递性 |
|---|---|---|
| compile | 适用于整个生命周期,包括编译、测试和运行时 | 依赖传递 |
| provided | 仅限于编译和测试阶段,在运行时由容器或 JDK 提供 | 不传递 |
| runtime | 只在运行时和测试阶段有效 | 依赖传递 |
| test | 仅限于测试编译和执行阶段 | 不传递 |
| system | 依赖必须从本地文件系统获取,而不是从远程仓库下载 | 不传递 |
5.1.4 传递性
依赖具有传递性,假设A依赖B,B依赖C,则A间接依赖C。所以存在直接依赖和间接依赖两种概念
对于传递依赖,Maven 默认会选择最高版本。被exclusions显式排除的依赖不会生效。注意规避依赖冲突(多版本问题)、循环依赖(类似死循环)问题
- 1、路径近者优先
(1)如果两个依赖项出现在不同的路径上,那么距离当前项目最近的那个将被选择 - 2、第一声明者优先
(1)在同一路径上的多个依赖项,先声明的会覆盖后声明的
5.1.4 循环依赖和依赖冲突
1>问题说明
当两个或多个模块相互之间直接或间接地依赖对方时,就形成了循环依赖。
简单理解为模块互相依赖存在死循环,比如。A依赖B、B依赖A;A依赖B,B依赖C,C依赖A。
当不同版本的同一个库被不同的依赖项引入时,就会发生依赖冲突。
比如项目引入了pers-niaonao-common-1.0.1.jar同时引入了pers-niaonao-common-1.0.2.jar,就会存在依赖冲突。导致构建失败、运行时错误等问题。
2>解决方案
-
1、通过mvn dependency:tree -Dverbose命令分析依赖树。或者使用IDEA Maven Helper插件排查jar包依赖和版本冲突问题。
-
2、确认多版本jar包,统一版本号version。
-
3、使用exclusion排除间接依赖jar包。
-
4、确认依赖循环情况,重新设计结构,进行解耦。或提取公共代码、公共业务等。
5.2 Git
| 类型 | 版本控制模型 | 离线支持 | 分支管理 | 提交管理 | 适用场景 |
|---|---|---|---|---|---|
| Git | 分布式,每个开发者本地都有完整的仓库副本 | 完全支持 | 轻量级分支 | 允许提交本地,再推送到远程仓库 | 敏捷开发 |
| SVN | 集中式,所有数据在中央服务器 | 部分操作支持 | 重量级,需要拷贝整个目录结构 | 立即提交到中央服务器 | 集中式管理项目 |
1>命令
# 配置全局用户信息,提交git时留存的提交者信息,便于跟踪负责人
git config --global user.name "niaonao"
git config --global user.email "niaonao@163.com"
# 初始化仓库
git init
# 克隆远程仓库到本地
git clone https://github.com/mem0ai/mem0.git
# 查看文件状态
git status
# 添加文件到暂存区
git add xxx.java
# 提交一组添加到暂存区的文件
git commit -m "feat:新增xx功能"
# 推送一组提交的文件到远程仓库master分支
git push origin master
# 拉取远程仓库master分支文件到本地仓库
git pull origin master
# 新建分支,名称为test
git checkout -b test
# 切换分支,名称为dev
git checkout dev
# 合并当前分支到master分支
git merge master
# 删除本地test分支
git branch -d test
# 删除远程test分支
git push origin --delete test
# 创建标签,名称为v-1.0.1
git tag v-1.0.1
# 推送标签到远程仓库
git push origin v-1.0.1
# 暂存当前未提交的更改
git stash
# 恢复最近一次暂存的内容
git stash pop
# 将当前分支的更改应用到另一个分支dev之上
git rebase dev
# 重置到某个提交点但保留修改的内容
git reset --soft <commit-hash>
# 重置到某个提交点丢弃修改的内容
git reset --hard <commit-hash>
# 遴选,将指定提交合并到当前分支
git cherry-pick <commit-hash>
2>提交message规范
- EXPRESSION
<type>(<scope>): <subject> - type用于说明 commit 的类别
- feat:新功能(feature)
- fix:修补bug
- docs:文档(documentation)
- style: 格式(不影响代码运行的变动), 样式调整
- refactor:重构(即不是新增功能,也不是修改bug的代码变动)
- test: 增加测试
- scope用于说明 commit 影响的范围
- subject是 commit 目的的简短描述
示例:feat(定时任务):新增账单统计任务
3>分支管理规范
- master 分支
- master 为主分支,也是用于部署生产环境的分支,master 分支要确保稳定性
- master 分支一般由 develop 以及 hotfix 分支合并,任何时间都不能直接修改代码
- develop 分支
- develop 为开发分支,始终保持最新完成以及bug修复后的代码
- 一般开发新功能时,feature 分支都是基于 develop 分支下创建的
- feature 分支
- 开发新功能时,以 develop 分支为基础创建 feature 分支
- 分支命名: feature/ 开头的为特性分支, 命名规则: feature/module
- hotfix 分支
- 分支命名: hotfix/ 开头的为修复分支,它的命名规则与 feature 分支类似
- 线上出现紧急问题时,需要及时修复,以 master 分支为基线,创建 hotfix 分支,修复完成后,需要合并到 master 分支和 develop 分支
- release 分支
- 分支命名: 标签版本分支,它的命名规则与 feature 分支类似
- 基于master分支打分支,作为历史发版留存,便于回退版本或指定版本发版
6. 配置中心
6.1 Apollo
Apollo 是由携程开源的一款分布式配置管理平台。提供了统一的配置管理界面,支持多环境、多集群配置管理,并且具备强大的权限控制和审计功能。简化了配置管理流程,提高了系统的可维护性和灵活性。
6.1.1 组件
- 1、Config Service
(1)提供配置获取、推送等接口服务
(2)支持多种协议(如 HTTP/HTTPS)
(3)具备高可用性和负载均衡能力。客户端应用程序会定期从 Config Service 拉取最新的配置信息 - 2、Admin Service
(1)提供配置修改、发布、回滚等功能
(2)包含图形化用户界面(GUI),方便管理员操作
(3)支持详细的权限管理和日志记录 - 3、Portal
(1)Apollo 的管理后台,用于创建应用、命名空间、配置项等
(2)支持多环境配置 - 4、SDK
(1)用于集成 Apollo
(2)自动拉取最新配置
(3)支持监听配置变化并实时更新应用 - 5、Meta Server
(1)负责管理 Config Service 和 Admin Service 的地址信息,实现服务发现
(2)提供了灵活的服务注册与发现机制
(3)支持动态调整服务实例列表
6.1.2 特性
- 1、统一配置管理、多环境配置管理
- 2、动态配置更新
(1)不重启服务的情况下实时推送配置变更 - 3、灰度发布
(1)支持设置特定条件(如 IP 地址、用户 ID)逐步推广新配置到部分服务器或用户,观察效果后再决定是否全量发布 - 4、命名空间管理
(1)支持自定义多个命名空间 - 5、权限控制
(1)可以指定团队成员的只读、编辑、发布权限 - 6、配置快照
(1)每次配置新增、修改、删除,发布都有历史记录及历史快照用于恢复历史版本 - 7、健康检查
- 8、自动化部署集成
(1)与 CI/CD 工具集成,确保每次部署都能正确加载最新的配置
6.1.3 代码示例
1>引入 Apollo 客户端依赖
pom.xml
<properties>
<version.apollo>1.4.0</version.apollo>
</properties>
<dependencies>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>${version.apollo}</version>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-openapi</artifactId>
<version>${version.apollo}</version>
</dependency>
</dependencies>
2>配置 Apollo 连接Config Service 和 Admin Service
application.yml
| 配置 | 说明 |
|---|---|
| app.id | Apollo创建的应用标识。通常和项目名称spring.application.name保持一致 |
| meta | Meta Server 地址,如果有多个地址可以用逗号分隔 |
| cluster | 集群名称 |
| cacheDir | Apollo配置本地缓存地址 |
| env | 指定环境 |
| apollo.bootstrap.namespaces | 指定命名空间 |
| apollo.bootstrap.enable | Apollo自动配置 |
# Apollo创建的应用标识
app:
id: my-api
# Apollo配置
apollo:
cluster: default
cacheDir: /data/logs/java/apollo
env: prod
meta: https://prod-niaonao-apollo.com
bootstrap:
namespaces: application,datasource
enabled: true
3>读取配置
可以通过注解@Value、@ApolloJsonValue读取Apollo配置,@ApolloJsonValue注解支持定义复杂结构的属性值。也可以通过@ConfigurationProperties 获取。
- 1、@Value、@ApolloJsonValue
结构为@Value( k e y ) 、 @ V a l u e ( {key})、@Value( key)、@Value({key:defaultValue}),其中defaultValue默认值可不配置。
@Value("${type}")
private Integer type;
@Value("${is.open:false}")
private Boolean isOpen;
@Value("${oss.server.url:https://niaonao.oss-cn-beijing.aliyuncs.com}")
private String ossServerUrl;
@ApolloJsonValue("${video.bgm.type:[\"MP3\",\"WAV\"]}")
private List<String> bgmTypeList
@ApolloJsonValue("${subscription.path:{\"1\": \"/stripe/subscription\", \"2\": \"/airwallex/subscription\"}}")
private Map<String, String> cancelSubPath;
@ApolloJsonValue("${product.list}")
private List<ProductVO> productList;
- 2、@ConfigurationProperties
创建一个类AppConfig来映射 Apollo 中的配置项,通过依赖注入到其他业务类,来调用AppConfig.getSomeProperty()
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app.config")
public class AppConfig {
private String someProperty;
public String getSomeProperty() {
return someProperty;
}
public void setSomeProperty(String someProperty) {
this.someProperty = someProperty;
}
}
3>监听配置变化
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.listener.ConfigChange;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class ApolloConfigListener {
@EventListener(ContextRefreshedEvent.class)
public void onApplicationEvent(ContextRefreshedEvent event) {
Config config = ConfigService.getAppConfig();
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
for (String key : changeEvent.changedKeys()) {
ConfigChange change = changeEvent.getChange(key);
System.out.println(String.format("Found change - key: %s, oldValue: %s, newValue: %s",
change.getPropertyName(), change.getOldValue(), change.getNewValue()));
}
}
});
}
}
6.1.4 Apollo配置的安全性
- 1、权限控制、角色管理
(1)通过细粒度的权限分配,确保只有授权用户才能对配置进行修改、发布等敏感操作
(2)基于角色的访问控制模型,简化了权限管理流程,减少了误操作的风险 - 2、加密传输
所有通信都采用 HTTPS 协议,保证数据在传输过程中的安全性 - 3、日志审计
详细记录每一次配置变更的操作日志,便于事后审查和追责 - 4、IP 白名单
对于关键操作,可以设置 IP 白名单限制,进一步提升安全性
6.1.5 Apollo优化
- 1、合理设置缓存策略
调整本地缓存过期时间和刷新频率,减少不必要的网络请求 - 2、批量拉取配置
对于大量配置项,尽量采用批量拉取的方式提高效率 - 3、优化 Meta Server 配置
确保 Meta Server 具有足够的资源和良好的性能,避免成为瓶颈 - 4、监控和分析性能瓶颈
利用监控工具找出性能瓶颈所在,并针对性地解决问题 - 5、评估是否需要分片
当配置量非常大时,考虑将配置分片存储,减轻单点压力
7. 安全规约
- 1、界面权限校验,不存在 token 返回公共请求登录页或欢迎页
- 2、防XSS攻击,表单安全校验
- 3、防SQL注入
- 4、接口访问白名单,接口授权访问
- 5、配置文件脱敏,用户数据脱敏
- 6、业务系统考虑风控,资损风控,违禁词风控等
Power By niaonao, The End

浙公网安备 33010602011771号