去年参与的一个健身社交平台项目让我深刻体会到微服务架构在复杂业务场景中的价值。这个基于微信小程序的运动活动招募平台,需要同时应对高并发活动报名、实时社交互动、运动数据同步等典型互联网场景。传统单体架构在早期版本中频繁出现服务雪崩问题,最终我们通过SpringCloud全家桶实现了服务拆分与治理。
平台采用前后端分离设计:微信小程序端使用Vue.js+WXML开发,管理后台基于Vue+ElementUI构建,后端采用SpringBoot+SpringCloud技术栈。数据库层面使用MySQL作为主存储,Redis处理热点数据缓存,Elasticsearch优化搜索体验。特别值得关注的是,我们通过RabbitMQ实现了活动状态变更的最终一致性,解决了分布式事务难题。
根据业务边界,我们将系统拆分为六个核心微服务:
每个服务独立部署,通过SpringCloud Netflix组件实现服务治理。在实践中我们发现,服务粒度并非越细越好——过度拆分会导致分布式事务复杂度指数级上升。最终确定的拆分原则是:高频变动的业务独立成服务(如活动服务),强一致性的业务合并部署(如支付相关逻辑)。
服务注册与发现:采用Eureka而非Nacos,主要考虑团队已有Eureka运维经验。配置关键参数:
yaml复制eureka:
instance:
lease-renewal-interval-in-seconds: 30 # 心跳间隔
lease-expiration-duration-in-seconds: 90 # 失效阈值
client:
registry-fetch-interval-seconds: 30 # 注册表拉取间隔
服务通信:Feign+Ribbon实现声明式调用,关键配置:
java复制@FeignClient(name = "activity-service",
configuration = FeignConfig.class,
fallback = ActivityServiceFallback.class)
public interface ActivityServiceClient {
@PostMapping("/activities/{id}/join")
Response<Boolean> joinActivity(@PathVariable Long id,
@RequestBody JoinRequest request);
}
熔断降级:Hystrix配置策略:
properties复制hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000
hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000
特别注意:Hystrix线程池大小需根据服务QPS精细调整,我们通过压测发现默认值10在高并发场景下会导致大量请求被拒绝
典型的活动创建时序如下:
报名流程的并发控制方案:
java复制public Response<Boolean> joinActivity(Long activityId, Long userId) {
// 使用Redis分布式锁防止超卖
String lockKey = "activity:lock:" + activityId;
try {
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) {
return Response.fail("操作太频繁");
}
// 校验活动状态
Activity activity = activityDao.findByIdForUpdate(activityId);
if (activity.getStatus() != ActivityStatus.OPEN) {
return Response.fail("活动不可报名");
}
// 校验人数限制
if (activity.getJoinedCount() >= activity.getMaxPeople()) {
return Response.fail("人数已满");
}
// 更新数据库
activityDao.incrementJoinedCount(activityId);
participantDao.insert(new Participant(activityId, userId));
// 发送报名成功事件
eventPublisher.publishEvent(
new ActivityJoinedEvent(activityId, userId));
return Response.success(true);
} finally {
redisTemplate.delete(lockKey);
}
}
跨服务的活动支付流程采用本地消息表+事件通知方案:
sql复制-- 消息表设计
CREATE TABLE `transaction_messages` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`biz_id` VARCHAR(64) NOT NULL COMMENT '业务ID',
`topic` VARCHAR(64) NOT NULL COMMENT '消息主题',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '0-待发送 1-已发送',
`payload` JSON NOT NULL COMMENT '消息内容',
`retry_count` INT NOT NULL DEFAULT 0,
`create_time` DATETIME NOT NULL,
`update_time` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_biz_id` (`biz_id`),
KEY `idx_status_retry` (`status`,`retry_count`)
) ENGINE=InnoDB;
采用多级缓存架构:
缓存更新策略对比:
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Cache Aside | 实现简单 | 存在不一致窗口 | 读多写少 |
| Write Through | 强一致性 | 写入延迟高 | 写多读少 |
| Write Behind | 写入性能高 | 可能丢数据 | 允许最终一致 |
我们最终选择Cache Aside模式,关键实现:
java复制public Activity getActivity(Long id) {
String cacheKey = "activity:" + id;
// 先查Redis
Activity activity = redisTemplate.opsForValue().get(cacheKey);
if (activity != null) {
return activity;
}
// 查数据库
activity = activityDao.findById(id);
if (activity != null) {
// 异步写入Redis
executorService.submit(() -> {
redisTemplate.opsForValue().set(
cacheKey,
activity,
30, TimeUnit.MINUTES);
});
}
return activity;
}
当活动数据突破500万时出现性能瓶颈,我们采用以下方案:
分片策略配置示例:
yaml复制spring:
shardingsphere:
datasource:
names: ds0,ds1,...,ds31
sharding:
tables:
t_activity:
actual-data-nodes: ds$->{0..31}.t_activity_$->{0..15}
database-strategy:
standard:
sharding-column: city_id
precise-algorithm-class-name: com.example.CityPreciseShardingAlgorithm
table-strategy:
inline:
sharding-column: id
algorithm-expression: t_activity_$->{id % 16}
初期方案直接使用微信的session_key,发现存在以下问题:
改进方案:
java复制public String wechatLogin(String code) {
// 获取微信session
WechatSession session = wechatClient.code2session(code);
// 查询或创建用户
User user = userService.findOrCreate(session.getOpenid());
// 生成JWT
String token = Jwts.builder()
.setSubject(user.getId().toString())
.claim("openid", session.getOpenid())
.setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
// 存储映射关系
redisTemplate.opsForValue().set(
"wechat:session:" + user.getId(),
session.getSessionKey(),
30, TimeUnit.MINUTES);
return token;
}
活动附近搜索功能最初使用MySQL GIS函数,性能测试发现QPS不足100。最终方案:
bash复制# Redis GEO操作示例
GEOADD activities:geo 116.404 39.915 "activity:1001"
GEORADIUS activities:geo 116.404 39.915 5 km WITHDIST
这个项目让我深刻认识到,微服务架构不是银弹,需要根据业务发展阶段合理应用。在团队规模较小、业务模式未完全定型时,过度拆分反而会增加运维成本。建议从单体架构起步,随着业务复杂度提升逐步拆分关键服务。