一、引言:Serverless不是“无服务器”,而是“无需关心服务器”
在云计算从IaaS、PaaS演进到SaaS的全链路中,Serverless(无服务器架构)是近十年最具颠覆性的架构模式之一。根据CNCF(云原生计算基金会)2025年度调研报告,全球Serverless市场规模已突破800亿美元,年复合增长率达35%,超过60%的中大型企业已将至少20%的业务迁移至Serverless架构。
很多开发者对Serverless的第一认知是“不用管服务器了”,这是典型的认知偏差——Serverless的核心不是“没有服务器”,而是开发者无需关注服务器的采购、部署、扩容、运维、安全补丁 等底层基础设施工作,只需聚焦业务逻辑本身。这种架构模式彻底重构了传统的开发、部署和运维流程,带来了“按需付费、弹性伸缩、快速迭代”的核心价值。
二、Serverless核心概念与底层逻辑
2.1 权威定义:Serverless的官方边界
根据CNCF对Serverless的权威定义,符合以下两个核心特征的架构可被称为Serverless:
- 无服务器管理:开发者无需感知底层服务器的存在,云厂商负责基础设施的全生命周期管理;
- 事件驱动+弹性伸缩:应用以事件为触发源运行,资源自动根据请求量扩缩容(甚至缩容至0);
- 按量计费:仅为实际运行的计算资源付费,空闲时不产生任何成本。
2.2 Serverless vs 传统架构 vs 微服务:核心差异
| 维度 | 传统单体架构 | 微服务架构 | Serverless架构 |
| 服务器管理 | 自研/采购,全量运维 | 容器化部署,部分运维 | 云厂商托管,零运维 |
| 资源伸缩 | 手动扩容,资源固定 | 自动扩缩容,需配置阈值 | 毫秒级弹性,缩容至0 |
| 计费模式 | 按服务器资源付费 | 按容器/实例数付费 | 按执行时长+调用次数付费 |
| 开发聚焦点 | 全栈开发(含运维) | 业务逻辑+服务治理 | 纯业务逻辑 |
| 冷启动问题 | 无 | 无 | 存在(可优化) |
| 状态管理 | 本地/数据库 | 分布式缓存/数据库 | 无状态(需依赖BaaS服务) |
2.3 Serverless底层运行原理(流程图)
原理拆解:
- 事件触发:用户请求、定时任务、消息队列等事件通过API网关/事件源进入云厂商调度系统;
- 实例调度:调度系统检查是否有预热的函数实例,有则直接执行,无则初始化运行时(如JVM)、加载代码;
- 函数执行:执行完业务逻辑后返回结果,空闲时资源自动释放(或保留少量预热实例减少冷启动)。
2.4 Serverless核心特性
- 无状态性:函数实例不保存任何本地状态,每次执行都是独立的(需状态时依赖Redis/MongoDB等BaaS服务);
- 事件驱动:函数的执行必须由事件触发(HTTP请求、MQ消息、定时任务等),无事件则函数不运行;
- 按需付费:以阿里云FC为例,计费单位是“GB·秒”,即内存规格×执行时长,空闲时费用为0;
- 毫秒级弹性:支持每秒数万次的请求峰值,云厂商自动扩容,无需人工配置;
- 零运维:无需关注服务器的补丁、监控、故障转移,云厂商全托管。
三、Serverless关键架构模式(2025主流)
3.1 函数即服务(FaaS)模式(核心)
FaaS(Function as a Service)是Serverless的核心载体,开发者将业务逻辑封装为“函数”,部署到云厂商的FaaS平台(如AWS Lambda、阿里云FC、腾讯云SCF),平台负责函数的运行、扩容、运维。
核心特征:
- 函数是最小部署单元,独立运行、独立扩缩容;
- 函数生命周期短(毫秒到分钟级),避免长时间占用资源;
- 支持多语言(Java/Go/Python/Node.js等),2025年主流FaaS平台已全面支持JDK 17。
3.2 后端即服务(BaaS)模式
BaaS(Backend as a Service)是FaaS的补充,指云厂商提供的开箱即用的后端服务,无需开发者自建和运维,常见的BaaS服务包括:
- 数据库服务:MySQL/Redis/MongoDB托管版;
- 消息队列:RocketMQ/Kafka托管版;
- 存储服务:对象存储OSS;
- 认证服务:OAuth2.0/SSO托管版。
FaaS+BaaS是Serverless的经典组合:FaaS负责业务逻辑,BaaS负责基础能力,开发者只需聚焦业务代码。
3.3 事件驱动架构(EDA)+ Serverless
事件驱动是Serverless的核心运行模式,事件生产者(如用户请求、订单创建、文件上传)产生事件,事件总线(如阿里云EventBridge)将事件路由到对应的FaaS函数,函数处理完后可产生新事件触发下一个函数,形成“事件流”。
3.4 微服务Serverless化模式
传统微服务需要部署到K8s/容器平台,仍需关注容器编排、资源调度;微服务Serverless化则将每个微服务拆分为多个FaaS函数,通过API网关聚合,核心优势:
- 每个函数独立扩缩容,避免“一损俱损”;
- 降低微服务的部署和运维成本;
- 按需付费,减少闲置资源浪费。
3.5 批处理Serverless模式
针对大数据批处理场景(如日志分析、数据ETL),Serverless批处理模式无需搭建Hadoop/Spark集群,直接通过FaaS函数并行处理数据,优势:
- 弹性扩容,处理速度随数据量自动提升;
- 处理完成后资源自动释放,成本仅为传统集群的10%-30%;
- 2025年主流FaaS平台已支持函数的“批量触发”和“结果聚合”能力。
四、Serverless核心技术栈与生态(2025最新)
4.1 云厂商FaaS产品
| 云厂商 | FaaS产品 | 2025最新特性 |
| AWS | Lambda | 支持JDK 17、冷启动优化至10ms内 |
| 阿里云 | 函数计算FC | 支持Spring Cloud Function原生集成、GPU函数 |
| 腾讯云 | 云函数SCF | 与微信生态深度融合、Serverless容器 |
| 华为云 | 函数工作流FunctionGraph | 支持跨区域函数编排 |
4.2 开源Serverless框架
- Serverless Framework:跨云厂商的部署工具,支持一键部署函数到AWS/阿里云/腾讯云;
- Knative:基于K8s的Serverless框架,将容器转换为Serverless服务;
- OpenFaaS:轻量级开源FaaS平台,支持私有化部署;
- Spring Cloud Function:Spring官方出品,将Spring应用转换为云函数,支持多云厂商适配。
五、企业级实战:基于Spring Cloud Function + 阿里云FC构建Serverless订单服务
5.1 需求背景
构建一个Serverless订单查询服务,满足以下需求:
- 支持根据订单ID查询订单详情(HTTP触发);
- 支持根据用户ID查询今日订单列表(HTTP触发);
- 数据存储在MySQL 8.0,使用MyBatisPlus操作数据库;
- 接口需接入Swagger3文档;
- 部署到阿里云FC,支持弹性扩缩容;
- 符合阿里Java开发手册,代码规范、无空指针等常见问题。
5.2 技术选型(2025最新稳定版)
| 技术栈 | 版本号 | 说明 |
| JDK | 17 | 符合阿里手册,长期支持版 |
| Maven | 3.9.6 | 项目构建工具 |
| Spring Boot | 3.2.5 | 基础框架 |
| Spring Cloud Function | 4.1.0 | Serverless函数核心框架 |
| MyBatisPlus | 3.5.5 | 持久层框架 |
| MySQL | 8.0.36 | 关系型数据库 |
| Lombok | 1.18.30 | 简化代码 |
| Fastjson2 | 2.0.49 | JSON处理 |
| Swagger3 | 2.2.0 | 接口文档 |
| Alibaba Cloud FC SDK | 2.7.0 | 阿里云FC适配SDK |
| Guava | 33.2.1-jre | 集合工具类 |
| Spring Context Support | 6.1.6 | 上下文支持 |
5.3 项目结构
com.jam.demo
├── config/ # 配置类
│ ├── MyBatisPlusConfig.java
│ └── SwaggerConfig.java
├── entity/ # 实体类
│ └── OrderEntity.java
├── mapper/ # Mapper接口
│ └── OrderMapper.java
├── service/ # 服务层
│ ├── OrderService.java
│ └── impl/
│ └── OrderServiceImpl.java
├── function/ # Serverless函数
│ └── OrderFunction.java
├── util/ # 工具类
│ └── DateUtil.java
├── Application.java # 启动类
└── pom.xml # 依赖配置
5.4 完整代码实现
5.4.1 pom.xml(核心依赖)
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.jam.demo</groupId>
<artifactId>serverless-order-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>serverless-order-demo</name>
<description>Serverless订单服务示例</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<fastjson2.version>2.0.49</fastjson2.version>
<guava.version>33.2.1-jre</guava.version>
<swagger.version>2.2.0</swagger.version>
<lombok.version>1.18.30</lombok.version>
<aliyun-fc.version>2.7.0</aliyun-fc.version>
</properties>
<dependencies>
<!-- Spring Boot核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!-- Spring Cloud Function -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-web</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-aws</artifactId>
<version>4.1.0</version>
</dependency>
<!-- MyBatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.36</version>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- Guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- Swagger3 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- 阿里云FC SDK -->
<dependency>
<groupId>com.aliyun.fc.runtime</groupId>
<artifactId>fc-java-core</artifactId>
<version>${aliyun-fc.version}</version>
</dependency>
<!-- Spring工具类 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>6.1.6</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.jam.demo.Application</mainClass>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<!-- 打包为可执行JAR -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.jam.demo.Application</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
5.4.2 配置文件(application.yml)
spring:
application:
name: serverless-order-demo
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/serverless_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root123456
cloud:
function:
definition: getOrderById;getTodayOrdersByUserId # 定义函数名称
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
mapper-locations: classpath:mapper/**/*.xml
server:
port: 8080
springdoc:
swagger-ui:
path: /swagger-ui.html
enabled: true
api-docs:
enabled: true
5.4.3 MyBatisPlus配置类
package com.jam.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatisPlus配置类
* @author ken
* @date 2025-05-20
*/
@Configuration
@MapperScan("com.jam.demo.mapper")
public class MyBatisPlusConfig {
/**
* 分页插件配置
* @return MybatisPlusInterceptor
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
5.4.4 Swagger3配置类
package com.jam.demo.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Swagger3配置类
* @author ken
* @date 2025-05-20
*/
@Configuration
public class SwaggerConfig {
/**
* 配置API文档信息
* @return OpenAPI
*/
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Serverless订单服务API文档")
.version("1.0.0")
.description("基于Spring Cloud Function + 阿里云FC的Serverless订单服务"));
}
}
5.4.5 日期工具类
package com.jam.demo.util;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
/**
* 日期工具类
* @author ken
* @date 2025-05-20
*/
public class DateUtil {
/**
* 获取今日开始时间(00:00:00)
* @return LocalDateTime
*/
public static LocalDateTime getStartOfDay() {
return LocalDate.now().atStartOfDay();
}
/**
* 获取今日结束时间(23:59:59.999999999)
* @return LocalDateTime
*/
public static LocalDateTime getEndOfDay() {
return LocalDate.now().atTime(LocalTime.MAX);
}
}
5.4.6 订单实体类
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单实体类
* @author ken
* @date 2025-05-20
*/
@Data
@TableName("t_order")
@Schema(description = "订单实体")
public class OrderEntity {
/**
* 订单ID
*/
@TableId(type = IdType.AUTO)
@Schema(description = "订单ID")
private Long id;
/**
* 用户ID
*/
@Schema(description = "用户ID")
private Long userId;
/**
* 订单金额
*/
@Schema(description = "订单金额")
private BigDecimal amount;
/**
* 订单状态(0-待支付,1-已支付,2-已取消)
*/
@Schema(description = "订单状态")
private Integer status;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "更新时间")
private LocalDateTime updateTime;
/**
* 逻辑删除标识(0-未删除,1-已删除)
*/
@TableLogic
@Schema(description = "逻辑删除标识")
private Integer deleted;
}
5.4.7 订单Mapper接口
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.OrderEntity;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
/**
* 订单Mapper接口
* @author ken
* @date 2025-05-20
*/
public interface OrderMapper extends BaseMapper<OrderEntity> {
/**
* 根据用户ID和时间范围查询订单列表
* @param userId 用户ID
* @param startTime 开始时间
* @param endTime 结束时间
* @return 订单列表
*/
List<OrderEntity> selectByUserIdAndTimeRange(
@Param("userId") Long userId,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime);
}
5.4.8 订单Mapper XML(resources/mapper/OrderMapper.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jam.demo.mapper.OrderMapper">
<select id="selectByUserIdAndTimeRange" resultType="com.jam.demo.entity.OrderEntity">
SELECT * FROM t_order
WHERE user_id = #{userId}
AND create_time >= #{startTime}
AND create_time <= #{endTime}
AND deleted = 0
</select>
</mapper>
5.4.9 订单服务接口
package com.jam.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.entity.OrderEntity;
import java.util.List;
/**
* 订单服务接口
* @author ken
* @date 2025-05-20
*/
public interface OrderService extends IService<OrderEntity> {
/**
* 根据订单ID查询订单详情
* @param orderId 订单ID
* @return 订单详情
* @throws IllegalArgumentException 订单ID为空时抛出
*/
OrderEntity getOrderById(Long orderId);
/**
* 根据用户ID查询今日订单列表
* @param userId 用户ID
* @return 今日订单列表
* @throws IllegalArgumentException 用户ID为空时抛出
*/
List<OrderEntity> getTodayOrdersByUserId(Long userId);
}
5.4.10 订单服务实现类
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.jam.demo.entity.OrderEntity;
import com.jam.demo.mapper.OrderMapper;
import com.jam.demo.service.OrderService;
import com.jam.demo.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.time.LocalDateTime;
import java.util.List;
/**
* 订单服务实现类
* @author ken
* @date 2025-05-20
*/
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderEntity> implements OrderService {
/**
* 根据订单ID查询订单详情
* @param orderId 订单ID
* @return 订单详情
* @throws IllegalArgumentException 订单ID为空时抛出
*/
@Override
public OrderEntity getOrderById(Long orderId) {
// 判空校验(符合阿里手册)
if (ObjectUtils.isEmpty(orderId)) {
log.error("查询订单详情失败:订单ID为空");
throw new IllegalArgumentException("订单ID不能为空");
}
OrderEntity order = this.getById(orderId);
if (ObjectUtils.isEmpty(order)) {
log.warn("查询订单详情失败:订单ID={}不存在", orderId);
return null;
}
log.info("查询订单详情成功:订单ID={}", orderId);
return order;
}
/**
* 根据用户ID查询今日订单列表
* @param userId 用户ID
* @return 今日订单列表
* @throws IllegalArgumentException 用户ID为空时抛出
*/
@Override
public List<OrderEntity> getTodayOrdersByUserId(Long userId) {
// 判空校验(符合阿里手册)
if (ObjectUtils.isEmpty(userId)) {
log.error("查询今日订单失败:用户ID为空");
throw new IllegalArgumentException("用户ID不能为空");
}
LocalDateTime startTime = DateUtil.getStartOfDay();
LocalDateTime endTime = DateUtil.getEndOfDay();
List<OrderEntity> orderList = baseMapper.selectByUserIdAndTimeRange(userId, startTime, endTime);
// 空集合处理(避免返回null)
List<OrderEntity> result = ObjectUtils.isEmpty(orderList) ? Lists.newArrayList() : orderList;
log.info("查询今日订单成功:用户ID={},订单数量={}", userId, result.size());
return result;
}
}
5.4.11 Serverless函数实现类
package com.jam.demo.function;
import com.alibaba.fastjson2.JSON;
import com.jam.demo.entity.OrderEntity;
import com.jam.demo.service.OrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
/**
* 订单Serverless函数
* @author ken
* @date 2025-05-20
*/
@Component
@Slf4j
@RequiredArgsConstructor
@Tag(name = "订单函数接口", description = "基于Serverless的订单查询接口")
public class OrderFunction {
private final OrderService orderService;
/**
* 根据订单ID查询订单详情函数
* @param request 请求参数(JSON格式,包含orderId)
* @return 订单详情JSON字符串
*/
@Operation(summary = "根据订单ID查询订单详情", description = "Serverless函数:根据订单ID查询订单详情")
public String getOrderById(
@Parameter(description = "请求参数,格式:{\"orderId\":1}") String request) {
// 参数校验
if (!StringUtils.hasText(request)) {
log.error("查询订单详情失败:请求参数为空");
return JSON.toJSONString(Map.of("code", 400, "msg", "请求参数不能为空"));
}
try {
Map<String, Object> paramMap = JSON.parseObject(request);
Long orderId = Long.valueOf(paramMap.get("orderId").toString());
OrderEntity order = orderService.getOrderById(orderId);
if (order == null) {
return JSON.toJSONString(Map.of("code", 404, "msg", "订单不存在", "data", null));
}
return JSON.toJSONString(Map.of("code", 200, "msg", "查询成功", "data", order));
} catch (IllegalArgumentException e) {
log.error("查询订单详情失败:参数非法", e);
return JSON.toJSONString(Map.of("code", 400, "msg", e.getMessage()));
} catch (Exception e) {
log.error("查询订单详情失败:系统异常", e);
return JSON.toJSONString(Map.of("code", 500, "msg", "系统异常"));
}
}
/**
* 根据用户ID查询今日订单列表函数
* @param request 请求参数(JSON格式,包含userId)
* @return 今日订单列表JSON字符串
*/
@Operation(summary = "根据用户ID查询今日订单列表", description = "Serverless函数:根据用户ID查询今日订单列表")
public String getTodayOrdersByUserId(
@Parameter(description = "请求参数,格式:{\"userId\":1}") String request) {
// 参数校验
if (!StringUtils.hasText(request)) {
log.error("查询今日订单失败:请求参数为空");
return JSON.toJSONString(Map.of("code", 400, "msg", "请求参数不能为空"));
}
try {
Map<String, Object> paramMap = JSON.parseObject(request);
Long userId = Long.valueOf(paramMap.get("userId").toString());
List<OrderEntity> orderList = orderService.getTodayOrdersByUserId(userId);
return JSON.toJSONString(Map.of("code", 200, "msg", "查询成功", "data", orderList));
} catch (IllegalArgumentException e) {
log.error("查询今日订单失败:参数非法", e);
return JSON.toJSONString(Map.of("code", 400, "msg", e.getMessage()));
} catch (Exception e) {
log.error("查询今日订单失败:系统异常", e);
return JSON.toJSONString(Map.of("code", 500, "msg", "系统异常"));
}
}
}
5.4.12 启动类
package com.jam.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.function.context.config.EnableFunctionRegistrar;
/**
* 应用启动类
* @author ken
* @date 2025-05-20
*/
@SpringBootApplication
@EnableFunctionRegistrar
@MapperScan("com.jam.demo.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
5.4.13 MySQL表结构(t_order)
CREATE DATABASE IF NOT EXISTS serverless_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE serverless_demo;
CREATE TABLE IF NOT EXISTS t_order (
id BIGINT AUTO_INCREMENT COMMENT '订单ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
amount DECIMAL(10,2) NOT NULL COMMENT '订单金额',
status TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态(0-待支付,1-已支付,2-已取消)',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标识(0-未删除,1-已删除)',
PRIMARY KEY (id),
INDEX idx_user_id (user_id),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
-- 插入测试数据
INSERT INTO t_order (user_id, amount, status) VALUES (1, 99.99, 1);
INSERT INTO t_order (user_id, amount, status) VALUES (1, 199.99, 0);
INSERT INTO t_order (user_id, amount, status) VALUES (2, 299.99, 1);
5.5 本地测试
- 启动MySQL 8.0,执行上述表结构和测试数据SQL;
- 启动Spring Boot应用(JDK 17环境);
- 访问Swagger文档:http://localhost:8080/swagger-ui.html,测试接口:
- 测试getOrderById:请求参数
{"orderId":1},返回订单详情; - 测试getTodayOrdersByUserId:请求参数
{"userId":1},返回今日订单列表。
5.6 部署到阿里云FC
5.6.1 打包应用
执行Maven命令打包:
mvn clean package -DskipTests
打包完成后,生成serverless-order-demo-0.0.1-SNAPSHOT-jar-with-dependencies.jar文件。
5.6.2 创建阿里云FC函数
- 登录阿里云FC控制台,创建“自定义运行时”函数;
- 上传上述JAR包;
- 配置函数入口:
com.jam.demo.Application; - 配置环境变量(数据库连接信息):
- SPRING_DATASOURCE_URL: jdbc:mysql://xxx.xxx.xxx.xxx:3306/serverless_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
- SPRING_DATASOURCE_USERNAME: root
- SPRING_DATASOURCE_PASSWORD: root123456
- 配置触发器:创建HTTP触发器,支持公网访问。
5.6.3 线上测试
调用阿里云FC的HTTP触发器地址,测试接口:
# 测试getOrderById
curl -X POST -H "Content-Type: application/json" -d "{\"orderId\":1}" https://xxx.cn-shanghai.fcapp.run/getOrderById
# 测试getTodayOrdersByUserId
curl -X POST -H "Content-Type: application/json" -d "{\"userId\":1}" https://xxx.cn-shanghai.fcapp.run/getTodayOrdersByUserId
六、Serverless架构的坑与避坑指南(2025实战总结)
6.1 冷启动问题
问题本质:
首次调用函数时,云厂商需要初始化运行时环境(如JVM)、加载代码、初始化依赖(如数据库连接池),导致响应时间变长(Java函数冷启动通常100ms-3s)。
避坑方案:
- 预热实例:阿里云FC/腾讯云SCF支持配置“预留实例”,保持一定数量的预热实例,避免冷启动;
- 运行时优化:
- Java函数使用GraalVM编译为原生镜像,冷启动时间可降至10ms内;
- 减少不必要的依赖(如移除无用的JAR包),缩小包体积;
- 函数拆分:将大函数拆分为小函数,减少初始化时间;
- 触发策略:定时触发函数(如每分钟调用一次),保持实例预热。
6.2 状态管理问题
问题本质:
Serverless函数是无状态的,无法在本地保存状态(如用户会话、缓存数据),多次调用可能使用不同的实例。
避坑方案:
- 使用BaaS服务:将状态存储在Redis/MongoDB等托管服务中;
- 避免本地缓存:不要在函数中使用本地缓存(如HashMap),改用Redis分布式缓存;
- 幂等性设计:函数需支持幂等性(如通过订单号+流水号去重),避免重复执行导致数据错误。
6.3 性能与成本平衡
问题本质:
高内存规格的函数执行速度快,但计费更高;低内存规格的函数计费低,但执行速度慢(甚至超时)。
避坑方案:
- 性能测试:通过压测确定最优内存规格(阿里云FC推荐Java函数使用1GB内存);
- 超时配置:根据函数执行时间合理配置超时时间(避免过早终止,也避免过长占用资源);
- 批量处理:批处理场景下,将小批量任务合并为大批量任务,减少函数调用次数。
6.4 监控与排障
问题本质:
Serverless函数的日志分散,难以定位问题;函数实例是临时的,无法远程调试。
避坑方案:
- 日志聚合:将函数日志输出到阿里云SLS/腾讯云CLS,统一聚合分析;
- 链路追踪:接入阿里云ARMS/OpenTelemetry,实现全链路追踪;
- 本地调试:使用云厂商提供的本地调试工具(如阿里云FC Local),模拟线上环境调试。
七、Serverless架构的适用场景与不适用场景
7.1 适用场景
- 突发流量场景:秒杀、促销、直播带货等流量波动大的场景,Serverless可弹性扩容;
- 定时任务场景:数据同步、报表生成、日志清理等定时任务,按需执行,成本低;
- 事件驱动场景:订单创建、支付回调、文件上传等事件触发的业务;
- 长尾流量场景:访问量低且分散的接口(如后台管理系统),Serverless按需付费,成本仅为传统部署的10%;
- 微服务拆分场景:将微服务拆分为多个小函数,独立部署、独立扩缩容。
7.2 不适用场景
- 长连接场景:WebSocket、TCP长连接等需要长时间保持连接的业务;
- 高CPU/IO密集型场景:视频转码、大数据计算等长时间占用资源的业务(推荐使用容器/裸金属);
- 低延迟要求场景:金融交易、实时风控等要求响应时间<10ms的业务(冷启动无法满足);
- 强状态场景:需要大量本地缓存、会话管理的业务(无状态设计成本过高)。
八、Serverless架构未来趋势(2025-2030)
- Serverless 4.0:云厂商将推出“零冷启动”函数,通过智能预热和运行时优化,将冷启动时间降至10ms内;
- 分布式Serverless:Serverless从单云厂商走向跨云、跨地域分布式部署,支持全球弹性;
- 边缘计算+Serverless:函数部署到边缘节点(如CDN节点),降低访问延迟,支持本地化计算;
- AI+Serverless:云厂商推出AI原生的Serverless函数,支持一键集成大模型能力,简化AI应用开发;
- 私有化Serverless:开源Serverless框架(如Knative)将更成熟,企业可在私有云部署Serverless平台。
九、总结
Serverless架构不是银弹,但它是云计算发展的必然趋势——它彻底改变了开发者的工作模式,让开发者从“全栈运维”中解放出来,聚焦业务价值。本文从底层逻辑出发,拆解了Serverless的核心架构模式,实现了企业级订单服务案例,同时给出了避坑指南和场景选型建议。
要掌握Serverless架构,核心是理解“无服务器不是没有服务器,而是无需关心服务器”,在实际落地时,需结合业务场景选择合适的架构模式,平衡性能、成本和可维护性。随着云厂商技术的不断迭代,Serverless将在更多场景下替代传统架构,成为企业数字化转型的核心技术选择。