一、单体架构的本质与核心特征
1.1 什么是单体架构?
单体架构(Monolithic Architecture)是将整个应用的所有功能模块(前端、后端、数据库交互等)打包成一个单一的、自包含的部署单元。
形象比喻:
- 就像一家"全能小店":一个老板(服务器)负责所有事情(接待、烹饪、收银、清洁)
- 所有代码在一个"大包裹"里,部署时一次性上传整个包裹
- 访问数据库像"一个大脑(单一数据库)管理所有记忆"
1.2 核心特征
1. 单一代码库:所有功能在同一个项目/代码仓库中
2. 统一部署:编译成一个可执行文件(如JAR/WAR)或一组文件
3. 共享数据库:所有模块访问同一个数据库实例
4. 进程内调用:模块间通过函数/方法调用通信,而非网络
5. 技术栈统一:前后端使用相同的技术生态
二、典型单体购物网站详细架构
2.1 三层架构模型(最经典)
┌─────────────────────────────────────────────────────────────┐
│ 表现层 (Presentation Layer) │
├─────────────────────────────────────────────────────────────┤
│ [浏览器] → [Nginx] → [Spring MVC Controller / Django View] │
│ │
│ 职责: │
│ 1. 接收HTTP请求,解析参数 │
│ 2. 调用业务逻辑层 │
│ 3. 渲染HTML/JSON响应 │
│ 4. 用户会话管理 │
└─────────────────────────────┬───────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 业务逻辑层 (Business Logic Layer) │
├─────────────────────────────────────────────────────────────┤
│ [Service类 / Manager类] │
│ │
│ 职责: │
│ 1. 实现核心业务逻辑(购物车、订单、支付) │
│ 2. 事务管理 (@Transactional) │
│ 3. 数据验证与业务规则 │
│ 4. 调用数据访问层 │
└─────────────────────────────┬───────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 数据访问层 (Data Access Layer) │
├─────────────────────────────────────────────────────────────┤
│ [DAO类 / Repository类 / Mapper] │
│ │
│ 职责: │
│ 1. 封装数据库访问细节 │
│ 2. 执行SQL查询/更新 │
│ 3. ORM映射(对象-关系映射) │
│ 4. 连接池管理 │
└─────────────────────────────┬───────────────────────────────┘
↓
┌────────────────────┐
│ 数据库层 │
│ [MySQL] │
│ - 用户表 │
│ - 商品表 │
│ - 订单表 │
│ - 购物车表 │
└────────────────────┘
2.2 目录结构示例(Spring Boot + Thymeleaf)
src/main/
├── java/com/example/eshop/
│ ├── EshopApplication.java # 启动类
│ ├── config/ # 配置类
│ │ ├── WebConfig.java # Web配置
│ │ ├── SecurityConfig.java # 安全配置(Spring Security)
│ │ └── DataSourceConfig.java # 数据源配置
│ ├── controller/ # 控制器层
│ │ ├── HomeController.java # 首页
│ │ ├── UserController.java # 用户相关
│ │ ├── ProductController.java # 商品相关
│ │ ├── CartController.java # 购物车
│ │ ├── OrderController.java # 订单
│ │ └── PaymentController.java # 支付
│ ├── service/ # 业务逻辑层
│ │ ├── impl/ # 实现类
│ │ │ ├── UserServiceImpl.java
│ │ │ ├── ProductServiceImpl.java
│ │ │ ├── CartServiceImpl.java
│ │ │ ├── OrderServiceImpl.java
│ │ │ └── PaymentServiceImpl.java
│ │ └── interface/ # 接口定义
│ │ ├── UserService.java
│ │ ├── ProductService.java
│ │ └── ...
│ ├── dao/ 或 repository/ # 数据访问层
│ │ ├── UserRepository.java # JPA Repository
│ │ ├── ProductRepository.java
│ │ ├── OrderRepository.java
│ │ └── CustomRepositoryImpl.java # 自定义复杂查询
│ ├── model/ 或 entity/ # 实体类
│ │ ├── User.java
│ │ ├── Product.java
│ │ ├── Category.java
│ │ ├── CartItem.java
│ │ ├── Order.java
│ │ └── OrderItem.java
│ ├── dto/ # 数据传输对象
│ │ ├── UserDTO.java
│ │ ├── ProductDTO.java
│ │ └── OrderDTO.java
│ ├── util/ # 工具类
│ │ ├── JwtUtil.java # JWT工具
│ │ ├── DateUtil.java
│ │ └── MoneyUtil.java
│ └── exception/ # 异常处理
│ ├── GlobalExceptionHandler.java
│ ├── BusinessException.java
│ └── ErrorCode.java
├── resources/
│ ├── static/ # 静态资源
│ │ ├── css/
│ │ ├── js/
│ │ ├── images/
│ │ └── uploads/ # 上传文件(实际生产中建议用云存储)
│ ├── templates/ # 模板文件(Thymeleaf)
│ │ ├── layout/ # 布局文件
│ │ │ ├── base.html
│ │ │ └── header.html
│ │ ├── home/ # 首页
│ │ ├── product/ # 商品相关页面
│ │ ├── cart/ # 购物车页面
│ │ ├── order/ # 订单页面
│ │ └── user/ # 用户相关页面
│ ├── application.properties # 主配置文件
│ ├── application-dev.properties # 开发环境配置
│ ├── application-prod.properties # 生产环境配置
│ └── banner.txt # Spring Boot启动横幅
└── test/ # 测试代码
├── EshopApplicationTests.java
├── controller/
├── service/
└── repository/
2.3 数据库表设计(简化版)
sql
-- 用户表
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL, -- 存储加密后的密码
email VARCHAR(100) UNIQUE NOT NULL,
phone VARCHAR(20),
avatar VARCHAR(255), -- 头像URL
status TINYINT DEFAULT 1, -- 用户状态 1-正常 0-禁用
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_username (username),
INDEX idx_email (email)
);
-- 商品表
CREATE TABLE products (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(200) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
stock INT DEFAULT 0, -- 库存数量
category_id BIGINT, -- 分类ID
main_image VARCHAR(255), -- 主图
images TEXT, -- 商品图片JSON数组
status TINYINT DEFAULT 1, -- 1-上架 0-下架
sales_count INT DEFAULT 0, -- 销量
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_category (category_id),
INDEX idx_status (status),
FULLTEXT idx_search (name, description) -- 全文索引,用于搜索
);
-- 购物车表
CREATE TABLE cart_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL DEFAULT 1,
selected BOOLEAN DEFAULT TRUE, -- 是否选中
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_product (user_id, product_id), -- 防止重复添加
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
);
-- 订单表
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(32) UNIQUE NOT NULL, -- 订单号,唯一
user_id BIGINT NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
payment_amount DECIMAL(10, 2) NOT NULL,
payment_method TINYINT, -- 支付方式 1-支付宝 2-微信
payment_status TINYINT DEFAULT 0, -- 0-待支付 1-已支付 2-支付失败
order_status TINYINT DEFAULT 0, -- 0-待付款 1-待发货 2-待收货 3-已完成 4-已取消
shipping_address TEXT, -- 收货地址
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_order_no (order_no),
INDEX idx_created_at (created_at),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 订单明细表
CREATE TABLE order_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
product_name VARCHAR(200) NOT NULL,
product_image VARCHAR(255),
price DECIMAL(10, 2) NOT NULL, -- 下单时的价格
quantity INT NOT NULL,
total_price DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
);
三、关键技术实现细节
3.1 用户认证与授权
java
// 使用Spring Security实现
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // 前后端分离时可禁用CSRF
.authorizeRequests()
.antMatchers("/", "/home", "/products/**", "/register", "/login").permitAll()
.antMatchers("/cart/**", "/order/**").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/")
.permitAll()
.and()
.rememberMe() // 记住我功能
.tokenValiditySeconds(7 * 24 * 60 * 60) // 7天
.key("eshopRememberMeKey");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 使用BCrypt加密
}
}
3.2 购物车实现
java
@Service
public class CartServiceImpl implements CartService {
@Autowired
private CartRepository cartRepository;
@Autowired
private ProductRepository productRepository;
@Transactional
public void addToCart(Long userId, Long productId, Integer quantity) {
// 1. 检查商品是否存在且库存足够
Product product = productRepository.findById(productId)
.orElseThrow(() -> new BusinessException("商品不存在"));
if (product.getStock() < quantity) {
throw new BusinessException("库存不足");
}
// 2. 检查购物车是否已有该商品
Optional<CartItem> existingItem = cartRepository
.findByUserIdAndProductId(userId, productId);
if (existingItem.isPresent()) {
// 3. 已有商品,更新数量
CartItem item = existingItem.get();
item.setQuantity(item.getQuantity() + quantity);
cartRepository.save(item);
} else {
// 4. 新增购物车项
CartItem newItem = new CartItem();
newItem.setUserId(userId);
newItem.setProductId(productId);
newItem.setQuantity(quantity);
cartRepository.save(newItem);
}
}
public CartDTO getCart(Long userId) {
List<CartItem> items = cartRepository.findByUserId(userId);
CartDTO cartDTO = new CartDTO();
cartDTO.setItems(items);
// 计算总价
BigDecimal total = BigDecimal.ZERO;
for (CartItem item : items) {
if (item.isSelected()) {
Product product = productRepository.findById(item.getProductId()).orElse(null);
if (product != null) {
total = total.add(product.getPrice()
.multiply(BigDecimal.valueOf(item.getQuantity())));
}
}
}
cartDTO.setTotalAmount(total);
return cartDTO;
}
}
3.3 订单处理(含事务管理)
java
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private OrderItemRepository orderItemRepository;
@Autowired
private ProductRepository productRepository;
@Autowired
private CartRepository cartRepository;
@Transactional(rollbackFor = Exception.class) // 事务管理
public OrderDTO createOrder(Long userId, OrderRequest request) {
// 1. 生成订单号
String orderNo = generateOrderNo();
// 2. 验证并扣减库存
List<OrderItem> orderItems = new ArrayList<>();
BigDecimal totalAmount = BigDecimal.ZERO;
for (OrderItemRequest itemRequest : request.getItems()) {
Product product = productRepository.findById(itemRequest.getProductId())
.orElseThrow(() -> new BusinessException("商品不存在: " + itemRequest.getProductId()));
// 乐观锁扣减库存
int updated = productRepository.decreaseStock(
itemRequest.getProductId(),
itemRequest.getQuantity(),
product.getVersion());
if (updated == 0) {
throw new BusinessException("商品库存不足或已更新: " + product.getName());
}
// 计算商品总价
BigDecimal itemTotal = product.getPrice()
.multiply(BigDecimal.valueOf(itemRequest.getQuantity()));
totalAmount = totalAmount.add(itemTotal);
// 创建订单项
OrderItem orderItem = new OrderItem();
orderItem.setProductId(product.getId());
orderItem.setProductName(product.getName());
orderItem.setProductImage(product.getMainImage());
orderItem.setPrice(product.getPrice());
orderItem.setQuantity(itemRequest.getQuantity());
orderItem.setTotalPrice(itemTotal);
orderItems.add(orderItem);
}
// 3. 创建订单
Order order = new Order();
order.setOrderNo(orderNo);
order.setUserId(userId);
order.setTotalAmount(totalAmount);
order.setPaymentAmount(totalAmount); // 这里简化,实际可能有优惠
order.setShippingAddress(request.getShippingAddress());
order.setOrderStatus(OrderStatus.PENDING_PAYMENT.getCode());
order.setPaymentStatus(PaymentStatus.UNPAID.getCode());
Order savedOrder = orderRepository.save(order);
// 4. 保存订单项
for (OrderItem item : orderItems) {
item.setOrderId(savedOrder.getId());
orderItemRepository.save(item);
}
// 5. 清空购物车中已下单的商品
cartRepository.deleteByUserIdAndProductIdIn(
userId,
request.getItems().stream()
.map(OrderItemRequest::getProductId)
.collect(Collectors.toList())
);
return convertToDTO(savedOrder, orderItems);
}
private String generateOrderNo() {
// 生成规则:时间戳 + 随机数
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String timeStr = sdf.format(new Date());
String randomStr = String.valueOf((int)((Math.random() * 9 + 1) * 1000));
return timeStr + randomStr;
}
}
3.4 支付回调处理
java
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Autowired
private OrderService orderService;
@PostMapping("/alipay/callback")
public String alipayCallback(HttpServletRequest request) {
// 1. 验证签名(实际需对接支付宝SDK)
Map<String, String> params = convertRequestToMap(request);
boolean signVerified = verifySignature(params); // 验证签名
if (!signVerified) {
return "failure";
}
// 2. 验证订单状态
String orderNo = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
if ("TRADE_SUCCESS".equals(tradeStatus) ||
"TRADE_FINISHED".equals(tradeStatus)) {
// 3. 更新订单状态
orderService.updatePaymentStatus(orderNo, PaymentStatus.PAID);
// 4. 触发其他业务逻辑(如发送邮件、通知发货等)
asyncSendOrderPaidNotification(orderNo);
}
return "success";
}
// 异步发送支付成功通知
@Async
public void asyncSendOrderPaidNotification(String orderNo) {
// 发送邮件、短信等
// 这里可以使用Spring的@Async实现异步
}
}
四、单体架构的优化策略
4.1 数据库层面优化
sql
-- 1. 添加合适的索引
CREATE INDEX idx_orders_user_status ON orders(user_id, order_status);
CREATE INDEX idx_products_category_price ON products(category_id, price);
-- 2. 读写分离(主从复制)
-- 主库:写操作
-- 从库:读操作(在Spring中配置多个数据源)
-- 3. 查询优化示例
-- 避免 SELECT *,只选择需要的字段
SELECT id, name, price FROM products WHERE category_id = ?;
-- 使用分页,避免一次性查询大量数据
SELECT * FROM products ORDER BY created_at DESC LIMIT 20 OFFSET 0;
4.2 应用层优化
java
// 1. 引入缓存(Redis)
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductRepository productRepository;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String PRODUCT_CACHE_PREFIX = "product:";
private static final long CACHE_EXPIRE_HOURS = 24;
@Cacheable(value = "products", key = "#id") // 使用Spring Cache注解
public ProductDTO getProductById(Long id) {
// 先查缓存
String cacheKey = PRODUCT_CACHE_PREFIX + id;
ProductDTO cached = (ProductDTO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// 缓存未命中,查数据库
Product product = productRepository.findById(id)
.orElseThrow(() -> new BusinessException("商品不存在"));
ProductDTO dto = convertToDTO(product);
// 写入缓存
redisTemplate.opsForValue().set(
cacheKey,
dto,
CACHE_EXPIRE_HOURS,
TimeUnit.HOURS
);
return dto;
}
}
// 2. 异步处理
@Service
public class EmailService {
@Async // 使用Spring的异步支持
public void sendOrderConfirmationEmail(Order order) {
// 发送邮件,可能耗时较长
// 不影响主流程的响应速度
}
}
4.3 静态资源优化
nginx
# Nginx配置示例
server {
listen 80;
server_name www.eshop.com;
# 静态资源直接由Nginx处理
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
root /var/www/eshop/static;
expires 30d; # 设置缓存过期时间
add_header Cache-Control "public, immutable";
}
# 动态请求转发给应用服务器
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
五、部署与运维
5.1 传统部署方式
bash
# 1. 打包应用
mvn clean package -DskipTests
# 2. 上传到服务器
scp target/eshop-1.0.0.jar user@server:/opt/eshop/
# 3. 启动应用
java -jar eshop-1.0.0.jar --spring.profiles.active=prod
# 4. 使用systemd管理服务
# /etc/systemd/system/eshop.service
[Unit]
Description=Eshop Application
After=network.target
[Service]
User=eshop
WorkingDirectory=/opt/eshop
ExecStart=/usr/bin/java -jar eshop-1.0.0.jar
SuccessExitStatus=143
Restart=always
[Install]
WantedBy=multi-user.target
5.2 Docker容器化部署
dockerfile
# Dockerfile
FROM openjdk:11-jre-slim
VOLUME /tmp
COPY target/eshop-1.0.0.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
EXPOSE 8080
yaml
# docker-compose.yml (适合小型部署)
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: eshop
MYSQL_USER: eshop
MYSQL_PASSWORD: eshoppassword
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6-alpine
ports:
- "6379:6379"
eshop-app:
build: .
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_HOST: mysql
REDIS_HOST: redis
depends_on:
- mysql
- redis
volumes:
mysql_data:
六、监控与日志
6.1 Spring Boot Actuator 监控
yaml
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,env
endpoint:
health:
show-details: always
metrics:
export:
prometheus:
enabled: true
6.2 日志配置(Logback)
xml
<!-- src/main/resources/logback-spring.xml -->
<configuration>
<property name="LOG_PATH" value="./logs" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/eshop.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/eshop.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.eshop" level="DEBUG" />
<logger name="org.springframework" level="INFO" />
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
七、单体架构的适用场景与演进时机
7.1 最适合单体架构的场景
- 初创公司/新产品:团队规模小(3-5人),需要快速验证商业模式
- 简单业务场景:功能明确,业务逻辑不复杂
- 低流量应用:预计日活<1万,QPS<100
- 开发周期短:需要在1-3个月内上线
- 技术团队经验有限:没有微服务运维经验
7.2 需要演进的信号
- 代码复杂度:代码行数超过10万,模块间依赖混乱
- 团队协作困难:10+人同时开发,频繁代码冲突
- 部署问题:每次小改动都需要重新部署整个应用
- 性能瓶颈:数据库连接池不够用,CPU使用率持续高位
- 技术栈限制:不同模块需要不同的技术栈(如AI推荐模块需要Python)
7.3 演进路径建议
- 先优化单体:引入缓存、异步、读写分离
- 垂直拆分:按业务模块拆分成多个单体(如用户中心、商品中心)
- 微服务化:服务注册发现、API网关、配置中心等
八、常见问题与解决方案
8.1 数据库连接池耗尽
java
// 解决方案:配置合适的连接池参数
spring:
datasource:
hikari:
maximum-pool-size: 20 # 根据实际需求调整
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
8.2 大文件上传导致内存溢出
java
// 解决方案:使用流式处理
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
// 限制文件大小
if (file.getSize() > 10 * 1024 * 1024) { // 10MB
throw new BusinessException("文件大小不能超过10MB");
}
// 使用try-with-resources确保流关闭
try (InputStream inputStream = file.getInputStream()) {
// 流式上传到云存储
uploadToCloudStorage(inputStream, file.getOriginalFilename());
} catch (IOException e) {
throw new BusinessException("文件上传失败");
}
return "success";
}
8.3 全表扫描导致性能问题
sql
-- 解决方案:添加合适的索引和优化查询
-- 避免全表扫描
EXPLAIN SELECT * FROM orders WHERE created_at > '2024-01-01';
-- 使用覆盖索引
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
SELECT id, order_no FROM orders
WHERE user_id = 123 AND created_at > '2024-01-01';
总结
单体架构是构建购物网站最直接、最高效的起点。它的核心优势在于简单性:开发简单、测试简单、部署简单、调试简单。对于大多数初创项目和中小型电商平台,单体架构完全能够满足需求。
关键要点:
- 三层架构清晰划分职责,保持代码整洁
- 数据库设计要考虑扩展性,适当冗余
- 引入缓存和异步可以显著提升性能
- 监控和日志是生产环境的必需品
- 不要过早优化,在真正遇到问题前保持简单
单体架构不是"落后"的代名词,而是务实的选择 。当业务发展到一定规模,确实遇到瓶颈时,再考虑向更复杂的架构演进。记住:没有最好的架构,只有最适合当前阶段的架构。