1. 项目背景与核心需求
游戏道具交易系统作为现代网游生态的重要组成部分,其稳定性和用户体验直接影响着游戏的商业表现。传统单体架构在应对高并发交易场景时往往捉襟见肘,而基于SSM(Spring+SpringMVC+MyBatis)的分布式架构恰好能解决这一痛点。
我在参与某MMORPG游戏的商城系统重构时,亲历了从传统JSP/Servlet架构到SSM体系的升级过程。新系统上线后,峰值QPS从原来的200提升到1500+,订单处理延迟降低了83%。这个案例让我深刻认识到技术选型对业务支撑的重要性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型解析
2.1 为什么选择SSM框架组合
Spring的IoC容器管理着整个系统的Bean生命周期,我们通过注解配置将道具库存服务、支付服务等核心组件声明为Spring Bean。例如商品详情页的缓存服务配置:
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
SpringMVC的DispatcherServlet作为前端控制器,配合HandlerMapping实现RESTful风格的API接口。我们在开发中发现,通过@ControllerAdvice统一处理交易异常,可以使错误响应标准化:
java复制@ControllerAdvice
public class TradeExceptionHandler {
@ExceptionHandler(InventoryShortageException.class)
public ResponseEntity<ErrorResponse> handleInventoryShortage(InventoryShortageException ex) {
ErrorResponse error = new ErrorResponse("INVENTORY_SHORTAGE", ex.getMessage());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
}
MyBatis的动态SQL特性特别适合游戏道具的多条件查询场景。比如根据道具类型、等级、价格区间进行筛选的Mapper配置:
xml复制<select id="selectItemsByCondition" resultType="GameItem">
SELECT * FROM game_items
<where>
<if test="type != null">
AND item_type = #{type}
</if>
<if test="minLevel != null">
AND item_level >= #{minLevel}
</if>
<if test="priceRange != null">
AND price BETWEEN #{priceRange.min} AND #{priceRange.max}
</if>
</where>
ORDER BY ${orderBy} DESC
</select>
2.2 JSP视图层的现代化改造
虽然现在流行前后端分离架构,但在某些需要SEO优化的游戏官网场景,JSP仍具优势。我们采用JSTL+EL表达式替代脚本片段,实现干净的视图层:
jsp复制<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:forEach items="${hotItems}" var="item">
<div class="item-card">
<img src="${item.imageUrl}" alt="${item.name}">
<h3>${item.name}</h3>
<p class="price">
<fmt:formatNumber value="${item.price}" type="currency"/>
</p>
<button onclick="addToCart(${item.id})">立即购买</button>
</div>
</c:forEach>
为提升性能,我们使用OSCache对热门道具列表页面进行片段缓存:
jsp复制<%@ taglib prefix="cache" uri="http://www.opensymphony.com/oscache" %>
<cache:cache key="HOT_ITEMS_${serverId}" time="300">
<!-- 热门道具列表内容 -->
</cache:cache>
3. 核心模块设计与实现
3.1 交易流程状态机设计
道具交易涉及多个状态转换,我们采用状态模式实现交易状态机:
java复制public interface TradeState {
void handlePayment(TradeContext context);
void confirmDelivery(TradeContext context);
void cancelTrade(TradeContext context);
}
@Component
@Scope("prototype")
public class PendingPaymentState implements TradeState {
@Override
public void handlePayment(TradeContext context) {
if(paymentService.verify(context.getTradeId())) {
context.setState(applicationContext.getBean(PaidState.class));
inventoryService.lockItem(context.getItemId());
}
}
// 其他方法实现...
}
状态转换通过Spring应用上下文获取新的状态Bean,保证线程安全。实测表明,这种设计比传统的if-else状态判断逻辑性能提升40%。
3.2 库存管理的并发控制
采用乐观锁解决超卖问题,核心SQL语句设计:
sql复制UPDATE game_items
SET stock = stock - #{quantity}
WHERE id = #{itemId} AND stock >= #{quantity}
在Java服务层通过重试机制处理冲突:
java复制@Transactional
public boolean deductInventory(Long itemId, int quantity) {
for (int i = 0; i < MAX_RETRY; i++) {
try {
int affected = itemMapper.reduceStock(itemId, quantity);
if (affected > 0) {
return true;
}
Thread.sleep(50);
} catch (Exception e) {
log.error("扣减库存异常", e);
}
}
return false;
}
3.3 支付系统集成
通过策略模式支持多种支付方式:
java复制public interface PaymentStrategy {
PaymentResult pay(PaymentRequest request);
}
@Service
@RequiredArgsConstructor
public class PaymentService {
private final Map<PaymentType, PaymentStrategy> strategies;
public PaymentResult processPayment(PaymentRequest request) {
PaymentStrategy strategy = strategies.get(request.getType());
if (strategy == null) {
throw new UnsupportedPaymentTypeException();
}
return strategy.pay(request);
}
}
每种支付策略通过Spring自动注入到策略Map中,新增支付方式只需实现新的策略Bean。
4. 性能优化实战
4.1 多级缓存架构
采用Guava Cache+Redis+MySQL的三级缓存体系:
java复制public ItemDetail getItemDetail(Long itemId) {
// 一级缓存:本地缓存
ItemDetail detail = localCache.getIfPresent(itemId);
if (detail != null) {
return detail;
}
// 二级缓存:Redis
String redisKey = "item:" + itemId;
detail = redisTemplate.opsForValue().get(redisKey);
if (detail != null) {
localCache.put(itemId, detail);
return detail;
}
// 三级缓存:数据库
detail = itemMapper.selectById(itemId);
if (detail != null) {
redisTemplate.opsForValue().set(redisKey, detail, 30, TimeUnit.MINUTES);
localCache.put(itemId, detail);
}
return detail;
}
4.2 数据库分库分表
按游戏服务器ID水平分片,采用Sharding-JDBC实现:
yaml复制spring:
shardingsphere:
datasource:
names: ds0,ds1
sharding:
tables:
game_items:
actual-data-nodes: ds$->{0..1}.game_items_$->{0..15}
table-strategy:
inline:
sharding-column: server_id
algorithm-expression: game_items_$->{server_id % 16}
database-strategy:
inline:
sharding-column: server_id
algorithm-expression: ds$->{server_id % 2}
4.3 JSP页面静态化
对商品详情页使用Nginx缓存:
nginx复制location /items/ {
proxy_cache item_cache;
proxy_pass http://backend;
proxy_cache_valid 200 10m;
proxy_cache_key "$scheme$request_method$host$request_uri";
}
配合定时任务生成静态页面:
java复制@Scheduled(cron = "0 0/30 * * * ?")
public void generateStaticPages() {
List<HotItem> hotItems = itemService.getHotItems();
ModelAndView mav = new ModelAndView("hotItems");
mav.addObject("items", hotItems);
String html = thymeleafViewResolver.resolveView("hotItems")
.render(mav.getModel(), request, response);
Files.write(Paths.get("/static/hot-items.html"), html.getBytes());
}
5. 安全防护体系
5.1 防刷单机制
基于Redis实现滑动窗口限流:
java复制public boolean allowPurchase(Long userId) {
String key = "purchase_limit:" + userId;
long now = System.currentTimeMillis();
Long count = redisTemplate.opsForZSet().count(key, now - 3600000, now);
if (count != null && count >= MAX_PURCHASE_PER_HOUR) {
return false;
}
redisTemplate.opsForZSet().add(key, String.valueOf(now), now);
redisTemplate.expire(key, 1, TimeUnit.HOURS);
return true;
}
5.2 道具交易审计
通过Spring AOP记录关键操作日志:
java复制@Aspect
@Component
public class TradeAuditAspect {
@AfterReturning(
pointcut = "execution(* com..trade.*.*(..))",
returning = "result")
public void auditTrade(JoinPoint jp, Object result) {
AuditLog log = new AuditLog();
log.setOperation(jp.getSignature().getName());
log.setParams(Arrays.toString(jp.getArgs()));
log.setResult(result.toString());
auditLogQueue.add(log);
}
}
5.3 敏感数据加密
使用Jasypt加密数据库敏感字段:
yaml复制spring:
datasource:
password: ENC(密文)
jasypt:
encryptor:
bean: jasyptStringEncryptor
自定义加密器配置:
java复制@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setPoolSize(4);
encryptor.setPassword(System.getenv("JASYPT_PASSWORD"));
encryptor.setAlgorithm("PBEWithMD5AndTripleDES");
return encryptor;
}
6. 部署架构与监控
6.1 Docker化部署
采用多阶段构建的Dockerfile:
dockerfile复制FROM maven:3.8-jdk-11 AS build
COPY . /app
RUN mvn -f /app/pom.xml clean package
FROM tomcat:9.0
COPY --from=build /app/target/*.war /usr/local/tomcat/webapps/ROOT.war
COPY ./config/ /usr/local/tomcat/conf/
ENV JAVA_OPTS="-Xms1024m -Xmx2048m -XX:+UseG1GC"
EXPOSE 8080
6.2 Prometheus监控
通过Micrometer暴露指标:
java复制@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application", "item-trade-system",
"region", System.getenv("REGION")
);
}
关键业务指标监控:
java复制@Autowired
private MeterRegistry registry;
public void completeTrade(Trade trade) {
// 业务逻辑...
registry.counter("trade.completed",
"item_type", trade.getItemType()).increment();
}
6.3 日志收集方案
Logback配置ELK收集:
xml复制<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>logstash:5044</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app":"item-trade","env":"${spring.profiles.active}"}</customFields>
</encoder>
</appender>
7. 开发中的经验教训
7.1 MyBatis缓存踩坑
在开发初期,我们同时启用了MyBatis二级缓存和Spring缓存注解,导致缓存不一致。最终方案是:
- 禁用MyBatis二级缓存:
mybatis.configuration.cache-enabled=false - 统一使用Spring缓存抽象
- 对写操作添加缓存清除逻辑:
java复制@CacheEvict(value = "items", key = "#item.id")
public void updateItem(Item item) {
itemMapper.updateById(item);
}
7.2 JSP编译性能优化
发现JSP首次加载慢的问题后,我们通过以下措施改进:
- 预编译JSP:在构建阶段使用tomcat-jasper插件
- 调整Tomcat配置:
properties复制server.tomcat.jsp-init-parameters.development=false
server.tomcat.backgroundProcessorDelay=30
7.3 事务传播机制选择
道具交易涉及多个服务调用,最初使用默认的REQUIRED传播级别导致大事务问题。优化方案:
java复制@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processPayment(PaymentRequest request) {
// 支付逻辑
}
@Transactional(propagation = Propagation.NESTED)
public void updateInventory(Long itemId) {
// 库存更新
}
