向量数据库Milvus的安装部署指南

基于之前向量数据库对比中 Milvus 的 "企业级、分布式、高性能" 核心特性,本文聚焦 Java 语言(Spring Boot 生态) ,整理一套从 "本地安装→Java 对接→调试优化→生产部署" 的完整落地方案,兼顾新手友好性与企业级实用性,代码可直接复用,适合技术团队快速落地 Milvus 向量检索功能

一、前言:为什么选择 Milvus?

Milvus 作为开源分布式向量数据库的标杆,核心优势在于 亿级数据支撑、低延迟检索、企业级高可用,尤其适合:

  • 电商亿级商品推荐、企业级 RAG 知识库;
  • 多模态检索(文本 + 图片 + 音频);
  • 高并发在线场景(QPS≈10000+)。

本文基于 Milvus 2.4.8(稳定版)+ Java 17 + Spring Boot 3.2.x,全程实操导向,解决 "怎么装、怎么连、怎么用、怎么部署" 四大核心问题。

二、前期准备:环境与资源清单

|-----------|----------------------------------------------------------------|------------------------------------|
| 类别 | 具体要求 | 备注 |
| 操作系统 | Windows 10+/Linux(CentOS 7+/Ubuntu 20.04+)/MacOS 12+ | 生产环境优先 Linux,开发环境可任意 |
| Java 环境 | JDK 17+(必须,Milvus Java SDK 2.x 不兼容 JDK 8) | 验证:java -version 显示 17+ |
| Milvus 服务 | 单机版(开发调试)/ 分布式集群(生产) | 推荐 Milvus 2.4.x,兼容最新 Java SDK |
| 依赖工具 | Maven 3.6+、Docker(可选,快速部署)、Git、Milvus CLI(调试用) | Milvus CLI:pip install pymilvus 安装 |
| 核心依赖 | Milvus Java SDK v2.4.8、Spring Boot Web、Jackson(JSON 解析)、Lombok | 依赖版本需与 Milvus 服务一致,避免兼容问题 |

三、第一步:Milvus 安装部署(开发 + 生产双环境)

场景 1:本地单机部署(开发调试用,10 分钟搞定)

适合开发阶段调试,无需复杂配置,数据存储在本地。

1.1 方式 1:Docker 快速启动(推荐)
复制代码
复制代码
# 1. 拉取Milvus 2.4.8镜像(指定版本避免兼容问题)

docker pull milvusdb/milvus:v2.4.8

# 2. 创建本地数据存储目录(持久化数据)

mkdir -p ~/milvus/data ~/milvus/logs

# 3. 启动Milvus单机容器(映射端口19530(RPC)、9091(HTTP))

docker run -d \

--name milvus-standalone \

--privileged=true \

-p 19530:19530 \

-p 9091:9091 \

-v ~/milvus/data:/var/lib/milvus/data \

-v ~/milvus/logs:/var/lib/milvus/logs \

milvusdb/milvus:v2.4.8 standalone start
1.2 验证启动成功
复制代码
bash 复制代码
# 1. 查看容器状态(Up状态即为成功)

docker ps | grep milvus-standalone

# 2. 用Milvus CLI验证连接(需提前安装pymilvus)

milvus_cli connect -h localhost -p 19530

# 输出 "Successfully connected to Milvus!" 即为成功
场景 2:分布式集群部署(生产环境,基于 Docker Compose)

适合企业级场景,支持分片、多副本、跨 AZ 高可用,以下是最小化集群配置(1 个协调器 + 1 个查询节点 + 1 个数据节点)。

2.1 下载 Docker Compose 配置文件
复制代码
bash 复制代码
# 下载官方配置文件(适配2.4.8版本)

wget https://github.com/milvus-io/milvus/releases/download/v2.4.8/milvus-cluster-docker-compose.yml -O docker-compose.yml
2.2 修改核心配置(docker-compose.yml)

关键配置说明(避免默认配置资源不足):

复制代码
javascript 复制代码
version: '3.8'

services:

# 协调器(管理元数据,必须)

etcd:

image: quay.io/coreos/etcd:v3.5.5

environment:

- ETCD_AUTO_COMPACTION_MODE=revision

- ETCD_AUTO_COMPACTION_RETENTION=1000

- ETCD_QUOTA_BACKEND_BYTES=4294967296

volumes:

- ./etcd-data:/etcd-data

command: etcd --data-dir=/etcd-data --listen-client-urls=http://0.0.0.0:2379 --advertise-client-urls=http://0.0.0.0:2379

# Milvus协调服务

milvus-rootcoord:

image: milvusdb/milvus:v2.4.8

command: rootcoord start

environment:

- MILVUS_LOG_LEVEL=info

- ETCD_ENDPOINTS=etcd:2379

depends_on:

- etcd

# 数据节点(存储向量数据)

milvus-datanode:

image: milvusdb/milvus:v2.4.8

command: datanode start

environment:

- MILVUS_LOG_LEVEL=info

- ETCD_ENDPOINTS=etcd:2379

- STORAGE_TYPE=local # 生产可用S3/MinIO

volumes:

- ./milvus-data:/var/lib/milvus/data

depends_on:

- etcd

- milvus-rootcoord

# 查询节点(处理检索请求)

milvus-querynode:

image: milvusdb/milvus:v2.4.8

command: querynode start

environment:

- MILVUS_LOG_LEVEL=info

- ETCD_ENDPOINTS=etcd:2379

depends_on:

- etcd

- milvus-rootcoord

# 代理节点(接收客户端请求)

milvus-proxy:

image: milvusdb/milvus:v2.4.8

command: proxy start

environment:

- MILVUS_LOG_LEVEL=info

- ETCD_ENDPOINTS=etcd:2379

ports:

- "19530:19530"

- "9091:9091"

depends_on:

- etcd

- milvus-rootcoord
2.3 启动集群
复制代码
javascript 复制代码
# 后台启动所有服务

docker-compose up -d

# 查看启动状态(所有服务状态为Up)

docker-compose ps

四、第二步:Java 对接 Milvus(Spring Boot 实战)

4.1 配置 Maven 依赖(pom.xml)

核心依赖(确保版本与 Milvus 服务一致):

复制代码
XML 复制代码
<dependencies>

Boot核心 -->

<dependency>

>org.springframework.boot

-boot-starter-web>

SDK v2.4.8(必须与Milvus服务版本匹配) -->

vus -java .8

ombok <artifactId>lombok</artifactId>

>true .core atabind

</groupId>

<artifactId>spring-boot-starter-test <scope>test>
4.2 配置文件(application.yml)

避免硬编码,统一管理 Milvus 连接信息:

XML 复制代码
spring:

application:

name: milvus-java-demo

# Milvus配置

milvus:

host: localhost # 生产环境替换为集群代理节点IP

port: 19530 # RPC端口(默认19530)

database: default # 数据库名称(Milvus 2.4+支持多库)

connect-timeout: 5000 # 连接超时时间(ms)

keep-alive-timeout: 60000 # 长连接超时时间(ms)

# 集合配置(对应关系数据库的表)

milvus-collection:

name: official_website_kb # 集合名称(如"官网知识库向量")

dimension: 768 # 向量维度(与Embedding模型一致,如DeepSeek/OpenAI为768)

shard-num: 2 # 分片数(集群模式下,建议与数据节点数一致)

replica-num: 1 # 副本数(高可用场景设为2)
4.3 核心工具类:MilvusClient 初始化

创建MilvusConfig.java,初始化 Milvus 客户端(单例模式,避免重复创建连接):

复制代码
java 复制代码
package com.company.milvus.config;

import io.milvus.param.ConnectParam;

import io.milvus.client.MilvusClient;

import io.milvus.client.MilvusClientImpl;

import lombok.Data;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

/**

* Milvus客户端配置类

*/

@Configuration

@Data

public class MilvusConfig {

@Value("${milvus.host}")

private String host;

@Value("${milvus.port}")

private Integer port;

@Value("${milvus.database}")

private String database;

@Value("${milvus.connect-timeout}")

private Integer connectTimeout;

@Value("${milvus.keep-alive-timeout}")

private Integer keepAliveTimeout;

/**

* 初始化Milvus客户端(单例Bean)

*/

@Bean

public MilvusClient milvusClient() {

// 构建连接参数

ConnectParam connectParam = ConnectParam.newBuilder()

.withHost(host)

.withPort(port)

.withDatabase(database)

.withConnectTimeout(connectTimeout)

.withKeepAliveTimeout(keepAliveTimeout)

.build();

// 创建客户端(MilvusClientImpl为官方实现类)

MilvusClient client = new MilvusClientImpl();

// 建立连接

client.connect(connectParam);

System.out.println("Milvus客户端连接成功!host:" + host + ", port:" + port);

return client;

}

}
4.4 核心操作:集合管理 + 向量 CRUD(实战示例)

创建MilvusService.java,封装集合创建、向量插入、检索、删除等核心操作,基于官网知识库向量场景示例:

复制代码
java 复制代码
package com.company.milvus.service;

import com.company.milvus.config.MilvusConfig;

import io.milvus.param.R;

import io.milvus.param.collection.*;

import io.milvus.param.dml.InsertParam;

import io.milvus.param.dml.SearchParam;

import io.milvus.param.index.CreateIndexParam;

import io.milvus.response.InsertResponse;

import io.milvus.response.SearchResponse;

import io.milvus.client.MilvusClient;

import io.milvus.common.clientenum.ConsistencyLevelEnum;

import io.milvus.common.clientenum.IndexTypeEnum;

import io.milvus.common.clientenum.MetricTypeEnum;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Service;

import java.util.ArrayList;

import java.util.List;

/**

* Milvus核心操作服务(向量CRUD+集合管理)

*/

@Service

@Slf4j

public class MilvusService {

@Autowired

private MilvusClient milvusClient;

@Autowired

private MilvusConfig milvusConfig;

@Value("${milvus-collection.name}")

private String collectionName;

@Value("${milvus-collection.dimension}")

private Integer dimension;

@Value("${milvus-collection.shard-num}")

private Integer shardNum;

@Value("${milvus-collection.replica-num}")

private Integer replicaNum;

// 向量字段名(固定,与集合结构一致)

private static final String VECTOR_FIELD_NAME = "vector";

// 主键字段名(固定,用于唯一标识向量)

private static final String PRIMARY_KEY_FIELD_NAME = "id";

// 元数据字段名(存储原始文本、产品标签等)

private static final String METADATA_FIELD_NAME = "content";

/**

* 1. 创建集合(相当于关系数据库的"表")

* 核心:定义字段结构(主键+向量+元数据)+ 分片/副本配置

*/

public boolean createCollection() {

// 1. 检查集合是否已存在

R> hasCollectionResp = milvusClient.hasCollection(

HasCollectionParam.newBuilder().withCollectionName(collectionName).build()

);

if (hasCollectionResp.getData().hasCollection()) {

log.info("集合{}已存在,无需重复创建", collectionName);

return true;

}

// 2. 定义字段结构

ListType> fieldTypes = new ArrayList 主键字段(Long类型,自增)

fieldTypes.add(FieldType.newBuilder()

.withName(PRIMARY_KEY_FIELD_NAME)

.withDataType(DataType.Int64)

.withPrimaryKey(true)

.withAutoID(true) // 自增ID,无需手动指定

.build());

// 向量字段(Float类型,768维)

fieldTypes.add(FieldType.newBuilder()

.withName(VECTOR_FIELD_NAME)

.withDataType(DataType.FloatVector)

.withDimension(dimension) // 与Embedding模型维度一致

.build());

// 元数据字段(存储原始文本,String类型)

fieldTypes.add(FieldType.newBuilder()

.withName(METADATA_FIELD_NAME)

.withDataType(DataType.VarChar)

.withMaxLength(2000) // 文本最大长度(根据实际调整)

.build());

// 3. 构建创建集合参数

CreateCollectionParam createParam = CreateCollectionParam.newBuilder()

.withCollectionName(collectionName)

.withFieldTypes(fieldTypes)

.withShardNum(shardNum) // 分片数(集群模式)

.withReplicaNum(replicaNum) // 副本数(高可用)

.withConsistencyLevel(ConsistencyLevelEnum.STRONG) // 强一致性(核心业务推荐)

.build();

// 4. 执行创建

R<CreateCollectionResponse> createResp = milvusClient.createCollection(createParam);

if (createResp.getStatus() != R.Status.Success.getCode()) {

log.error("创建集合失败:{}", createResp.getMessage());

return false;

}

// 5. 为向量字段创建索引(HNSW索引,高性能检索)

CreateIndexParam indexParam = CreateIndexParam.newBuilder()

.withCollectionName(collectionName)

.withFieldName(VECTOR_FIELD_NAME)

.withIndexType(IndexTypeEnum.HNSW) // 推荐HNSW(平衡速度与精度)

.withMetricType(MetricTypeEnum.COSINE) // 相似度计算方式(余弦相似度)

// HNSW索引参数(按需调整,默认即可)

.withExtraParam("{\"M\":16, \"efConstruction\":200}")

.build();

R<CreateIndexResponse> indexResp = milvusClient.createIndex(indexParam);

if (indexResp.getStatus() != R.Status.Success.getCode()) {

log.error("创建索引失败:{}", indexResp.getMessage());

return false;

}

log.info("集合{}创建成功,索引构建完成", collectionName);

return true;

}

/**

* 2. 插入向量数据(批量插入,推荐)

* @param vectors 向量列表(768维Float数组)

* @param contents 元数据(原始文本,与向量一一对应)

* @return 插入成功的主键ID列表

*/

public List> insertVectors(List<Float>> vectors, List<String> contents) {

if (vectors.size() != contents.size()) {

log.error("向量与元数据数量不匹配");

return null;

}

// 构建插入数据(字段顺序与集合定义一致)

List> fields = new ArrayList();

fields.add(InsertParam.Field.newBuilder()

.withName(VECTOR_FIELD_NAME)

.withValues(vectors)

.build());

fields.add(InsertParam.Field.newBuilder()

.withName(METADATA_FIELD_NAME)

.withValues(contents)

.build());

// 构建插入参数

InsertParam insertParam = InsertParam.newBuilder()

.withCollectionName(collectionName)

.withFields(fields)

.build();

// 执行插入

R insertResp = milvusClient.insert(insertParam);

if (insertResp.getStatus() != R.Status.Success.getCode()) {

log.error("插入向量失败:{}", insertResp.getMessage());

return null;

}

// 返回插入成功的主键ID

List> ids = insertResp.getData().getIDs().getLongs();

log.info("成功插入{}条向量数据,ID列表:{}", ids.size(), ids);

return ids;

}

/**

* 3. 向量检索(相似匹配,核心功能)

* @param queryVector 查询向量(用户问题的Embedding)

* @param topK 返回Top K条相似结果

* @return 相似文本列表(按相似度降序)

*/

public List<String> searchVectors(List<Float> queryVector, int topK) {

// 构建检索参数

SearchParam searchParam = SearchParam.newBuilder()

.withCollectionName(collectionName)

.withMetricType(MetricTypeEnum.COSINE) // 与索引一致

.withOutFields(List.of(METADATA_FIELD_NAME)) // 返回元数据字段

.withTopK(topK)

.withVectors(List.of(queryVector)) // 查询向量(支持批量查询)

.withVectorFieldName(VECTOR_FIELD_NAME)

// HNSW检索参数(ef越大,精度越高,速度越慢,默认100)

.withExtraParam("{\"ef\":100}")

.build();

// 执行检索

RResp = milvusClient.search(searchParam);

if (searchResp.getStatus() != R.Status.Success.getCode()) {

log.error("检索向量失败:{}", searchResp.getMessage());

return null;

}

// 解析结果

List> similarContents = new ArrayList<>();

SearchResponse.DataWrapper dataWrapper = searchResp.getData().getResults().get(0);

for (int i = 0; i .getFieldDataList().size(); i++) {

// 获取相似度分数(余弦相似度,越接近1越相似)

float score = dataWrapper.getScores().get(i);

// 获取元数据(原始文本)

String content = dataWrapper.getFieldData(METADATA_FIELD_NAME).get(i).toString();

log.info("相似结果{}:相似度={:.4f},内容={}", i+1, score, content);

similarContents.add(content);

}

return similarContents;

}

/**

* 4. 删除向量数据(按主键ID)

*/

public boolean deleteVectorById(List) {

DeleteParam deleteParam = DeleteParam.newBuilder()

.withCollectionName(collectionName)

.withExpr(PRIMARY_KEY_FIELD_NAME + " in " + ids) // 条件表达式

.build();

RResp = milvusClient.delete(deleteParam);

if (deleteResp.getStatus() != R.Status.Success.getCode()) {

log.error("删除向量失败:{}", deleteResp.getMessage());

return false;

}

log.info("成功删除{}条向量数据", ids.size());

return true;

}

/**

* 5. 释放集合(加载到内存,检索前必须执行)

*/

public boolean loadCollection() {

LoadCollectionParam loadParam = LoadCollectionParam.newBuilder()

.withCollectionName(collectionName)

.build();

R loadResp = milvusClient.loadCollection(loadParam);

if (loadResp.getStatus() != R.Status.Success.getCode()) {

log.error("加载集合失败:{}", loadResp.getMessage());

return false;

}

log.info("集合{}加载到内存成功", collectionName);

return true;

}

}
4.5 测试类:验证核心功能

创建MilvusServiceTest.java,通过单元测试验证集合创建、向量插入、检索全流程:

java 复制代码
package com.company.milvus.service;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest

public class MilvusServiceTest {

@Autowired

private MilvusService milvusService;

// 模拟Embedding向量(实际需调用DeepSeek/OpenAI API)

private List> mockEmbedding() {

List vector = new ArrayList68);

for (int i = 0; i 8; i++) {

vector.add((float) (Math.random() * 0.5 + 0.1)); // 随机生成0.1-0.6的浮点数

}

return vector;

}

@Test

public void testFullFlow() {

// 1. 创建集合

boolean createSuccess = milvusService.createCollection();

assert createSuccess;

// 2. 加载集合到内存

boolean loadSuccess = milvusService.loadCollection();

assert loadSuccess;

// 3. 批量插入向量(3条示例数据)

List<List vectors = List.of(mockEmbedding(), mockEmbedding(), mockEmbedding());

List.of(

"产品A的系统要求:支持Windows 10及以上系统",

"产品A的保修政策:保修期2年,全国联保",

"产品A的售后电话:400-XXX-XXXX"

);

List<Long> ids = milvusService.insertVectors(vectors, contents);

assert ids != null && ids.size() == 3;

// 4. 向量检索(查询与"产品A保修"相关的内容)

List<Float> queryVector = mockEmbedding(); // 实际为"产品A保修多久?"的Embedding

ListContents = milvusService.searchVectors(queryVector, 2);

assert similarContents != null && similarContents.size() >= 1;

// 5. 删除向量(可选)

boolean deleteSuccess = milvusService.deleteVectorById(List.of(ids.get(0)));

assert deleteSuccess;

System.out.println("Milvus Java全流程测试成功!");

}

}

五、第三步:调试技巧与问题排查

5.1 日志调试(关键)

在application.yml中配置 Milvus 日志级别,便于排查问题:

复制代码
java 复制代码
logging:

level:

io.milvus: DEBUG # Milvus SDK日志级别(DEBUG/INFO/ERROR)

com.company.milvus: INFO # 自定义包日志级别
5.2 可视化调试:Milvus Dashboard

Milvus 提供官方可视化工具,可查看集合、向量数据、索引状态:

java 复制代码
# 启动Milvus Dashboard(Docker方式)

docker run -d -p 8080:8080 --name milvus-dashboard milvusdb/milvus-dashboard:v2.4.0

访问 http://localhost:8080,输入 Milvus 服务地址(localhost:19530),即可可视化管理:

  • 集合管理:查看字段结构、索引状态;
  • 数据查询:按主键 / 元数据过滤向量;
  • 性能监控:查看 QPS、延迟、资源占用。
5.3 常见问题排查

|-----------------------|-------------------------------------|-------------------------------------------------------------------------------------|
| 问题现象 | 排查方向 | 解决方案 |
| 连接超时(Connect timeout) | 1. Milvus 服务未启动;2. 端口未开放;3. 网络防火墙拦截 | 1. 检查容器状态:docker ps;2. 开放 19530 端口:firewall-cmd --add-port=19530/tcp;3. 关闭防火墙(开发环境) |
| 插入失败(向量维度不匹配) | 向量维度与集合定义的 dimension 不一致 | 确保 Embedding 模型输出维度与milvus-collection.dimension一致(如 768) |
| 检索无结果 | 1. 集合未加载到内存;2. 相似度阈值过低;3. 向量数据不匹配 | 1. 调用loadCollection();2. 调整 ef 参数(增大到 200);3. 验证向量生成是否正确 |
| 性能差(检索延迟 > 100ms) | 1. 未创建索引;2. 索引参数不合理;3. 数据量过大未分片 | 1. 确保创建 HNSW 索引;2. 调整 HNSW 参数(M=16,ef=100);3. 集群模式下增加分片 |

六、第四步:生产部署优化(企业级)

6.1 资源配置优化
  • 单机部署:CPU≥8 核,内存≥16GB(向量检索依赖内存),磁盘≥100GB(SSD 优先);
  • 分布式集群:
    • 协调器(etcd):2 核 4GB,磁盘 50GB;
    • 数据节点:8 核 16GB,磁盘 1TB(存储向量数据);
    • 查询节点:8 核 32GB(检索依赖内存);
    • 代理节点:4 核 8GB(接收客户端请求)。
6.2 高可用配置
  • 副本数:生产环境设为 2(replica-num: 2),避免单节点故障;
  • 数据备份:定期备份 Milvus 数据目录(./milvus-data),或使用 S3/MinIO 作为存储后端(支持增量备份);
  • 跨 AZ 部署:分布式集群节点部署在不同可用区,提升容灾能力。
6.3 性能优化
  • 索引优化:使用 HNSW 索引(默认),调整参数M=16(聚类数)、efConstruction=200(构建时精度);
  • 批量操作:插入 / 检索时批量处理(单次 1000-10000 条),减少网络开销;
  • 向量压缩:大规模场景(亿级数据)使用 PQ 量化(IndexTypeEnum.PQ),存储成本降低 70%;
  • 缓存优化:开启 Milvus 查询节点缓存(默认开启),提升高频查询速度。
6.4 监控告警

集成 Prometheus+Grafana 监控 Milvus 指标:

  1. 启用 Milvus 监控端口(默认 9091);
  2. 配置 Prometheus 抓取 Milvus 指标;
  3. 导入 Milvus 官方 Grafana 仪表盘(ID:17315),监控 QPS、延迟、内存占用等。

七、总结:Milvus Java 落地核心要点

  1. 版本兼容:Milvus 服务与 Java SDK 版本必须一致(如 2.4.8),否则会出现兼容性问题;
  2. 核心流程:创建集合→创建索引→加载集合→插入向量→检索,缺一不可;
  3. 性能关键:索引类型(HNSW 最优)、批量操作、内存配置,直接影响检索延迟;
  4. 生产注意:分布式集群部署、多副本、数据备份,确保高可用;
  5. 生态集成:可无缝对接 LangChain、DeepSeek API,快速落地 RAG 智能问答、推荐系统。

通过本文方案,Java 技术团队可快速实现 Milvus 的本地化开发与生产部署,兼顾实用性与扩展性。如果需要进一步优化(如多模态检索、亿级数据分片策略、与 Spring Cloud 集成),可根据实际场景调整!

相关推荐
马尔代夫哈哈哈1 小时前
SpringBoot 统一功能处理
java·前端·spring boot
AI资源库1 小时前
stepfun-ai/Step-3.5-Flash模型深入解析
人工智能·语言模型·架构
山岚的运维笔记2 小时前
SQL Server笔记 -- 第50章 存储过程
数据库·笔记·sql·microsoft·oracle·sqlserver
BD_Marathon2 小时前
原型模式——Spring源码分析
java·spring·原型模式
楚来客2 小时前
具身智能技术架构发展简介
架构
Zachery Pole2 小时前
JAVA_06_方法
java·开发语言
LSL666_2 小时前
10 集群
java·开发语言·数据库·redis·集群
好家伙VCC2 小时前
# 发散创新:基于Python的轻量级测试框架设计与实践 在现代软件开发中,**自动化
java·开发语言·python·自动化
李老师的Java笔记2 小时前
深度解析 | SpringBoot源码解析系列(五):@ConfigurationProperties | 配置绑定核心原理+实战避坑
java·spring boot·后端