标题: 微服务调用慢?五大招式让响应时间降10倍!
副标题: 从连接池到异步化,微服务性能优化全攻略
🎬 开篇:一个惨痛的教训
上线前:
开发:单元测试通过,压测也OK,准备上线!✅
运维:GO GO GO!
上线后1小时:
监控:⚠️ 接口响应时间3秒,超时率30%!
用户:这什么破系统,点一下等半天!😠
开发:怎么会这样?本地测试明明很快啊!😱
排查发现:
1. 没有使用连接池,每次都创建连接
2. 超时时间设置了60秒(默认值)
3. 10个服务调用串行执行
4. 没有任何缓存
5. 没有降级和容错
架构师:这些基本功都没做,能快才怪!😤
🤔 微服务调用慢的常见原因
想象你要去5个不同的店买东西:
- ❌ 串行方式: 逐个店排队,累计等待时间(慢死)
- ✅ 并行方式: 同时派5个人去买,同时等待(快!)
📚 知识地图
微服务调用性能优化五大法宝
├── 🔌 连接池优化(复用TCP连接)
├── ⏱️ 超时控制(快速失败)
├── 🚀 异步调用(并行处理)
├── 📦 批量聚合(减少调用次数)
└── 💾 缓存机制(避免重复调用)
🔌 第一招:连接池优化 - "复用连接,事半功倍"
🌰 生活中的例子
打电话给朋友:
- ❌ 没有连接池: 每次打电话都要重新拨号、等待接通(慢)
- ✅ 有连接池: 电话一直保持接通状态,想说话就说(快!)
💻 技术实现
问题场景:每次调用都创建连接
/**
* ❌ 错误做法:每次都创建新连接
*/
@Service
public class BadUserService {
public User getUserById(Long userId) {
// 💀 每次都创建RestTemplate,建立TCP连接,超级慢!
RestTemplate restTemplate = new RestTemplate();
String url = "http://user-service/user/" + userId;
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
/**
* 问题分析:
* 1. 每次调用都要经历TCP三次握手(100ms)
* 2. 没有复用连接,资源浪费
* 3. 频繁创建/销毁连接对象,GC压力大
*
* 结果:单次调用耗时150ms,其中100ms浪费在建立连接上!💀
*/
优化方案1:Apache HttpClient连接池
<!-- Maven依赖 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
/**
* HttpClient连接池配置
*/
@Configuration
public class HttpClientConfig {
@Bean
public HttpClient httpClient() {
// 连接池管理器
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager();
// 最大连接数
connectionManager.setMaxTotal(200);
// 每个路由(域名)的最大连接数
connectionManager.setDefaultMaxPerRoute(50);
// 连接超时时间(建立连接的时间)
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000) // 连接超时:5秒
.setSocketTimeout(10000) // 读取超时:10秒
.setConnectionRequestTimeout(3000) // 从连接池获取连接超时:3秒
.build();
// 创建HttpClient
return HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) // 重试3次
.setKeepAliveStrategy((response, context) -> 30 * 1000) // Keep-Alive 30秒
.build();
}
@Bean
public RestTemplate restTemplate(HttpClient httpClient) {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
}
/**
* ✅ 正确做法:使用连接池
*/
@Service
public class GoodUserService {
@Autowired
private RestTemplate restTemplate; // 注入配置好连接池的RestTemplate
public User getUserById(Long userId) {
String url = "http://user-service/user/" + userId;
// ⚡ 复用连接池中的连接,快!
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
/**
* 性能对比:
* 未使用连接池:150ms(100ms建立连接 + 50ms业务处理)💀
* 使用连接池: 50ms (0ms复用连接 + 50ms业务处理) ⚡ 快3倍!
*/
优化方案2:OkHttp连接池(推荐!)
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
</dependency>
/**
* OkHttp连接池配置
*/
@Configuration
public class OkHttpConfig {
@Bean
public OkHttpClient okHttpClient() {
// 连接池配置
ConnectionPool connectionPool = new ConnectionPool(
50, // 最大空闲连接数
5, // 连接保持时间
TimeUnit.MINUTES // 时间单位
);
return new OkHttpClient.Builder()
.connectionPool(connectionPool)
.connectTimeout(5, TimeUnit.SECONDS) // 连接超时
.readTimeout(10, TimeUnit.SECONDS) // 读取超时
.writeTimeout(10, TimeUnit.SECONDS) // 写入超时
.retryOnConnectionFailure(true) // 连接失败自动重试
.addInterceptor(new LoggingInterceptor()) // 日志拦截器
.build();
}
@Bean
public RestTemplate restTemplate(OkHttpClient okHttpClient) {
OkHttp3ClientHttpRequestFactory factory =
new OkHttp3ClientHttpRequestFactory(okHttpClient);
return new RestTemplate(factory);
}
}
/**
* 日志拦截器(方便调试)
*/
public class LoggingInterceptor implements Interceptor {
private static final Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long startTime = System.currentTimeMillis();
Response response = chain.proceed(request);
long endTime = System.currentTimeMillis();
log.info("请求URL:{}, 耗时:{}ms",
request.url(), endTime - startTime);
return response;
}
}
优化方案3:Feign + Ribbon连接池
# application.yml
feign:
httpclient:
enabled: true # 启用Apache HttpClient
max-connections: 200 # 最大连接数
max-connections-per-route: 50 # 每个路由最大连接数
connection-timeout: 5000 # 连接超时(毫秒)
connection-timer-repeat: 3000 # 连接重试间隔
client:
config:
default: # 默认配置
connectTimeout: 5000 # 连接超时
readTimeout: 10000 # 读取超时
loggerLevel: basic # 日志级别
ribbon:
ConnectTimeout: 5000 # Ribbon连接超时
ReadTimeout: 10000 # Ribbon读取超时
MaxAutoRetries: 1 # 同一实例最大重试次数
MaxAutoRetriesNextServer: 1 # 换实例重试次数
OkToRetryOnAllOperations: false # 是否所有操作都重试
/**
* Feign客户端
*/
@FeignClient(
name = "user-service",
fallbackFactory = UserServiceFallbackFactory.class
)
public interface UserServiceClient {
@GetMapping("/user/{id}")
User getUserById(@PathVariable("id") Long id);
@PostMapping("/user/batch")
List<User> getUsersByIds(@RequestBody List<Long> ids);
}
/**
* 降级工厂类
*/
@Component
public class UserServiceFallbackFactory
implements FallbackFactory<UserServiceClient> {
private static final Logger log =
LoggerFactory.getLogger(UserServiceFallbackFactory.class);
@Override
public UserServiceClient create(Throwable cause) {
return new UserServiceClient() {
@Override
public User getUserById(Long id) {
log.error("调用user-service失败,id={}", id, cause);
// 返回默认值或从缓存获取
return getFromCache(id);
}
@Override
public List<User> getUsersByIds(List<Long> ids) {
log.error("批量调用user-service失败", cause);
return Collections.emptyList();
}
};
}
private User getFromCache(Long id) {
// 从Redis缓存获取
return null;
}
}
📊 连接池参数调优指南
/**
* 连接池参数计算公式
*/
public class ConnectionPoolCalculator {
/**
* 计算最大连接数
* 公式:MaxTotal = (QPS × 响应时间 × 并发倍数) / 1000
*
* 示例:
* - QPS = 1000
* - 平均响应时间 = 100ms
* - 并发倍数 = 2(考虑突发流量)
*
* MaxTotal = (1000 × 100 × 2) / 1000 = 200
*/
public static int calculateMaxTotal(int qps, int avgResponseTime, double multiplier) {
return (int) ((qps * avgResponseTime * multiplier) / 1000);
}
/**
* 计算每个路由的最大连接数
* 公式:MaxPerRoute = MaxTotal / 目标服务数量
*
* 示例:
* - MaxTotal = 200
* - 目标服务数量 = 4个微服务
*
* MaxPerRoute = 200 / 4 = 50
*/
public static int calculateMaxPerRoute(int maxTotal, int serviceCount) {
return maxTotal / serviceCount;
}
public static void main(String[] args) {
int maxTotal = calculateMaxTotal(1000, 100, 2);
int maxPerRoute = calculateMaxPerRoute(maxTotal, 4);
System.out.println("最大连接数:" + maxTotal); // 200
System.out.println("每路由最大连接数:" + maxPerRoute); // 50
}
}
/**
* 推荐配置表
*
* ┌─────────────┬─────────┬─────────────┬─────────────┐
* │ 场景 │ MaxTotal│ MaxPerRoute │ KeepAlive │
* ├─────────────┼─────────┼─────────────┼─────────────┤
* │ 低并发(<100)│ 50 │ 10 │ 30秒 │
* │ 中并发(100-1000)│ 200 │ 50 │ 60秒 │
* │ 高并发(>1000)│ 500 │ 100 │ 120秒 │
* └─────────────┴─────────┴─────────────┴─────────────┘
*/
⏱️ 第二招:超时控制 - "快速失败,避免雪崩"
🌰 生活中的例子
去餐厅吃饭:
- ❌ 没有超时: 等1小时菜还没上,傻等(浪费时间)
- ✅ 设置超时: 等15分钟没上菜,换一家(及时止损)
💻 超时设置最佳实践
/**
* 微服务调用的三种超时
*/
public class TimeoutConfiguration {
/**
* 1. 连接超时(Connect Timeout)
* 建立TCP连接的超时时间
* 推荐:3-5秒
*/
private int connectTimeout = 5000; // 5秒
/**
* 2. 读取超时(Read Timeout / Socket Timeout)
* 从服务器读取响应数据的超时时间
* 推荐:根据业务复杂度设置(5-30秒)
*/
private int readTimeout = 10000; // 10秒
/**
* 3. 请求超时(Request Timeout)
* 整个请求的总超时时间(连接+处理+读取)
* 推荐:稍大于readTimeout
*/
private int requestTimeout = 15000; // 15秒
}
/**
* ❌ 错误做法:使用默认超时(太长!)
*/
@Service
public class BadOrderService {
private RestTemplate restTemplate = new RestTemplate();
public Order getOrder(Long orderId) {
// 💀 使用默认超时(60秒),太长了!
// 如果服务假死,会等60秒才返回,用户等疯了!
String url = "http://order-service/order/" + orderId;
return restTemplate.getForObject(url, Order.class);
}
}
/**
* ✅ 正确做法:合理设置超时时间
*/
@Service
public class GoodOrderService {
@Autowired
private RestTemplate restTemplate; // 已配置超时的RestTemplate
public Order getOrder(Long orderId) {
try {
String url = "http://order-service/order/" + orderId;
// ⚡ 5秒连接超时,10秒读取超时
// 最多等15秒就会返回(成功或失败)
return restTemplate.getForObject(url, Order.class);
} catch (RestClientException e) {
log.error("调用order-service超时,orderId={}", orderId, e);
// 降级处理:返回默认值或从缓存获取
return getOrderFromCache(orderId);
}
}
private Order getOrderFromCache(Long orderId) {
// 从Redis缓存获取
return cacheService.get("order:" + orderId, Order.class);
}
}
超时设置黄金法则
/**
* 超时时间设置指南
*/
public class TimeoutGuide {
/**
* 法则1:超时时间 = P99响应时间 × 1.5
*
* 示例:
* - P99响应时间 = 200ms
* - 超时时间 = 200 × 1.5 = 300ms
*/
public static int calculateTimeout(int p99ResponseTime) {
return (int) (p99ResponseTime * 1.5);
}
/**
* 法则2:分层超时(越往下游越短)
*
* Gateway -> Service A -> Service B -> Service C
* 30s 20s 10s 5s
*
* 原因:避免链路超时累积
*/
public static Map<String, Integer> layeredTimeout() {
Map<String, Integer> timeouts = new HashMap<>();
timeouts.put("gateway", 30000); // 30秒
timeouts.put("serviceA", 20000); // 20秒
timeouts.put("serviceB", 10000); // 10秒
timeouts.put("serviceC", 5000); // 5秒
return timeouts;
}
/**
* 法则3:快速失败,避免雪崩
*
* 超时后立即返回,而不是一直等待
*/
@GetMapping("/order/{id}")
public Order getOrder(@PathVariable Long id) {
try {
return orderService.getOrder(id);
} catch (TimeoutException e) {
// ⚡ 快速失败,返回降级数据
log.warn("获取订单超时,返回降级数据");
return Order.builder()
.id(id)
.status("UNKNOWN")
.build();
}
}
}
🚀 第三招:异步调用 - "并行处理,效率倍增"
🌰 生活中的例子
做一顿大餐需要:
- ❌ 串行执行: 洗菜→切菜→炒菜→煮饭→炖汤(2小时)
- ✅ 并行执行: 同时煮饭+炖汤,然后炒菜(40分钟)
💻 技术实现
场景:查询用户详情需要调用多个服务
/**
* ❌ 串行调用(慢)
*/
@Service
public class BadUserDetailService {
@Autowired
private RestTemplate restTemplate;
public UserDetail getUserDetail(Long userId) {
UserDetail detail = new UserDetail();
// 串行调用5个服务,累计耗时 = 50+100+80+60+70 = 360ms 💀
// 1. 获取用户基本信息(50ms)
User user = restTemplate.getForObject(
"http://user-service/user/" + userId, User.class);
detail.setUser(user);
// 2. 获取订单列表(100ms)
List<Order> orders = restTemplate.getForObject(
"http://order-service/orders?userId=" + userId, List.class);
detail.setOrders(orders);
// 3. 获取地址列表(80ms)
List<Address> addresses = restTemplate.getForObject(
"http://address-service/addresses?userId=" + userId, List.class);
detail.setAddresses(addresses);
// 4. 获取优惠券(60ms)
List<Coupon> coupons = restTemplate.getForObject(
"http://coupon-service/coupons?userId=" + userId, List.class);
detail.setCoupons(coupons);
// 5. 获取积分(70ms)
Integer points = restTemplate.getForObject(
"http://point-service/points?userId=" + userId, Integer.class);
detail.setPoints(points);
return detail; // 总耗时:360ms 😰
}
}
/**
* ✅ 并行调用(快!)
*/
@Service
public class GoodUserDetailService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private Executor asyncExecutor;
public UserDetail getUserDetail(Long userId) {
UserDetail detail = new UserDetail();
// 并行调用5个服务,总耗时 = max(50,100,80,60,70) = 100ms ⚡
// 创建5个异步任务
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() ->
restTemplate.getForObject(
"http://user-service/user/" + userId, User.class),
asyncExecutor);
CompletableFuture<List<Order>> ordersFuture = CompletableFuture.supplyAsync(() ->
restTemplate.getForObject(
"http://order-service/orders?userId=" + userId, List.class),
asyncExecutor);
CompletableFuture<List<Address>> addressesFuture = CompletableFuture.supplyAsync(() ->
restTemplate.getForObject(
"http://address-service/addresses?userId=" + userId, List.class),
asyncExecutor);
CompletableFuture<List<Coupon>> couponsFuture = CompletableFuture.supplyAsync(() ->
restTemplate.getForObject(
"http://coupon-service/coupons?userId=" + userId, List.class),
asyncExecutor);
CompletableFuture<Integer> pointsFuture = CompletableFuture.supplyAsync(() ->
restTemplate.getForObject(
"http://point-service/points?userId=" + userId, Integer.class),
asyncExecutor);
// 等待所有任务完成
CompletableFuture.allOf(
userFuture, ordersFuture, addressesFuture,
couponsFuture, pointsFuture
).join();
// 获取结果
detail.setUser(userFuture.join());
detail.setOrders(ordersFuture.join());
detail.setAddresses(addressesFuture.join());
detail.setCoupons(couponsFuture.join());
detail.setPoints(pointsFuture.join());
return detail; // 总耗时:100ms ⚡ 快3.6倍!
}
}
/**
* 线程池配置
*/
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数 = CPU核数
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
// 最大线程数 = CPU核数 × 2
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 队列容量
executor.setQueueCapacity(500);
// 线程名前缀
executor.setThreadNamePrefix("Async-Service-");
// 拒绝策略:调用者执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务完成后关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
优雅的异步调用封装
/**
* 异步调用工具类
*/
@Component
public class AsyncHttpClient {
@Autowired
private RestTemplate restTemplate;
@Autowired
private Executor asyncExecutor;
/**
* 异步GET请求
*/
public <T> CompletableFuture<T> getAsync(String url, Class<T> responseType) {
return CompletableFuture.supplyAsync(() -> {
try {
return restTemplate.getForObject(url, responseType);
} catch (Exception e) {
log.error("异步GET请求失败,url={}", url, e);
throw new RuntimeException(e);
}
}, asyncExecutor);
}
/**
* 异步POST请求
*/
public <T> CompletableFuture<T> postAsync(String url, Object request, Class<T> responseType) {
return CompletableFuture.supplyAsync(() -> {
try {
return restTemplate.postForObject(url, request, responseType);
} catch (Exception e) {
log.error("异步POST请求失败,url={}", url, e);
throw new RuntimeException(e);
}
}, asyncExecutor);
}
/**
* 批量异步调用(支持异常处理)
*/
public <T> List<T> batchGetAsync(List<String> urls, Class<T> responseType) {
List<CompletableFuture<T>> futures = urls.stream()
.map(url -> getAsync(url, responseType)
.exceptionally(ex -> {
log.error("请求失败:{}", url, ex);
return null; // 失败返回null,不影响其他请求
}))
.collect(Collectors.toList());
// 等待所有请求完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
// 收集结果(过滤掉null)
return futures.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
/**
* 使用示例
*/
@Service
public class UserDetailService {
@Autowired
private AsyncHttpClient asyncHttpClient;
public UserDetail getUserDetail(Long userId) {
UserDetail detail = new UserDetail();
// 🚀 使用工具类,代码更简洁!
CompletableFuture<User> userFuture =
asyncHttpClient.getAsync("http://user-service/user/" + userId, User.class);
CompletableFuture<List<Order>> ordersFuture =
asyncHttpClient.getAsync("http://order-service/orders?userId=" + userId, List.class);
// 等待完成并设置结果
detail.setUser(userFuture.join());
detail.setOrders(ordersFuture.join());
return detail;
}
}
📦 第四招:批量聚合 - "减少调用次数"
🌰 生活中的例子
去超市买东西:
- ❌ 每次买一样: 跑10趟超市(累死)
- ✅ 列清单一次买齐: 跑1趟超市(效率高!)
💻 技术实现
/**
* ❌ N+1查询问题(慢)
*/
@Service
public class BadOrderService {
@Autowired
private RestTemplate restTemplate;
public List<OrderVO> getOrdersWithUser(List<Long> orderIds) {
List<OrderVO> result = new ArrayList<>();
for (Long orderId : orderIds) { // 假设100个订单
// 查询订单信息(1次)
Order order = restTemplate.getForObject(
"http://order-service/order/" + orderId, Order.class);
// 💀 查询用户信息(100次!)N+1问题!
User user = restTemplate.getForObject(
"http://user-service/user/" + order.getUserId(), User.class);
OrderVO vo = new OrderVO();
vo.setOrder(order);
vo.setUser(user);
result.add(vo);
}
// 总调用次数:101次(1次查订单 + 100次查用户)💀
return result;
}
}
/**
* ✅ 批量查询(快!)
*/
@Service
public class GoodOrderService {
@Autowired
private RestTemplate restTemplate;
public List<OrderVO> getOrdersWithUser(List<Long> orderIds) {
List<OrderVO> result = new ArrayList<>();
// 1. 批量查询订单(1次)
List<Order> orders = restTemplate.postForObject(
"http://order-service/orders/batch",
orderIds,
List.class);
// 2. 提取所有用户ID
Set<Long> userIds = orders.stream()
.map(Order::getUserId)
.collect(Collectors.toSet());
// 3. ⚡ 批量查询用户(1次!)
List<User> users = restTemplate.postForObject(
"http://user-service/users/batch",
userIds,
List.class);
// 4. 构建用户ID->User映射
Map<Long, User> userMap = users.stream()
.collect(Collectors.toMap(User::getId, user -> user));
// 5. 组装结果
for (Order order : orders) {
OrderVO vo = new OrderVO();
vo.setOrder(order);
vo.setUser(userMap.get(order.getUserId()));
result.add(vo);
}
// 总调用次数:2次(1次查订单 + 1次查用户)⚡ 快50倍!
return result;
}
}
/**
* 批量查询接口
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 单个查询
*/
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getById(id);
}
/**
* 批量查询(关键!)
*/
@PostMapping("/batch")
public List<User> getUsersBatch(@RequestBody List<Long> ids) {
// 一次性查询所有用户
return userService.listByIds(ids);
}
}
GraphQL方式(更优雅)
/**
* GraphQL查询(避免N+1问题)
*/
@Component
public class UserDataFetcher implements DataFetcher<User> {
@Autowired
private UserService userService;
@Autowired
private DataLoader<Long, User> userDataLoader; // DataLoader自动批量加载
@Override
public User get(DataFetchingEnvironment environment) {
Long userId = environment.getArgument("id");
// DataLoader会自动合并多个请求为一次批量查询!
return userDataLoader.load(userId).join();
}
}
/**
* DataLoader配置
*/
@Configuration
public class DataLoaderConfig {
@Bean
public DataLoader<Long, User> userDataLoader(UserService userService) {
// 批量加载函数
BatchLoader<Long, User> batchLoader = userIds -> {
// 一次性查询所有用户
List<User> users = userService.listByIds(userIds);
// 返回CompletableFuture
return CompletableFuture.supplyAsync(() -> users);
};
return DataLoaderFactory.newDataLoader(batchLoader);
}
}
💾 第五招:缓存机制 - "避免重复调用"
🌰 生活中的例子
查字典:
- ❌ 没有书签: 每次都从第一页开始找(慢)
- ✅ 用书签标记: 直接翻到书签位置(快!)
💻 多级缓存架构
/**
* 三级缓存架构
*
* L1:本地缓存(Caffeine)- 最快,但数据可能不一致
* L2:分布式缓存(Redis)- 较快,数据一致
* L3:数据库/远程服务 - 最慢,数据最新
*/
@Service
public class CachedUserService {
// L1缓存:本地缓存
private final Cache<Long, User> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
@Autowired
private RedisTemplate<String, User> redisTemplate;
@Autowired
private RestTemplate restTemplate;
/**
* 三级缓存查询
*/
public User getUser(Long userId) {
// 1. 查L1缓存(本地)
User user = localCache.getIfPresent(userId);
if (user != null) {
log.debug("命中L1缓存:userId={}", userId);
return user; // ⚡ 耗时:<1ms
}
// 2. 查L2缓存(Redis)
String redisKey = "user:" + userId;
user = redisTemplate.opsForValue().get(redisKey);
if (user != null) {
log.debug("命中L2缓存:userId={}", userId);
// 回写L1缓存
localCache.put(userId, user);
return user; // ⚡ 耗时:1-5ms
}
// 3. 查L3(远程服务)
log.debug("未命中缓存,调用远程服务:userId={}", userId);
user = restTemplate.getForObject(
"http://user-service/user/" + userId, User.class);
if (user != null) {
// 回写L2缓存
redisTemplate.opsForValue().set(redisKey, user,
Duration.ofMinutes(30));
// 回写L1缓存
localCache.put(userId, user);
}
return user; // 💀 耗时:50-200ms
}
/**
* 更新用户时清除缓存
*/
public void updateUser(User user) {
// 1. 更新远程服务
restTemplate.put("http://user-service/user", user);
// 2. 清除L2缓存
String redisKey = "user:" + user.getId();
redisTemplate.delete(redisKey);
// 3. 清除L1缓存
localCache.invalidate(user.getId());
}
}
/**
* 性能对比:
*
* 无缓存:每次50ms 💀
* L2缓存(Redis):5ms ✅ 快10倍
* L1缓存(本地):0.1ms 🚀 快500倍!
*/
Spring Cache注解方式
/**
* 使用Spring Cache注解
*/
@Service
@CacheConfig(cacheNames = "user")
public class UserService {
@Autowired
private RestTemplate restTemplate;
/**
* @Cacheable:查询时自动缓存
*/
@Cacheable(key = "#userId")
public User getUser(Long userId) {
return restTemplate.getForObject(
"http://user-service/user/" + userId, User.class);
}
/**
* @CachePut:更新缓存
*/
@CachePut(key = "#user.id")
public User updateUser(User user) {
restTemplate.put("http://user-service/user", user);
return user;
}
/**
* @CacheEvict:删除缓存
*/
@CacheEvict(key = "#userId")
public void deleteUser(Long userId) {
restTemplate.delete("http://user-service/user/" + userId);
}
/**
* @Cacheable with condition:条件缓存
*/
@Cacheable(key = "#userId", condition = "#userId > 0")
public User getUserConditional(Long userId) {
return restTemplate.getForObject(
"http://user-service/user/" + userId, User.class);
}
}
📊 综合性能对比
优化前 vs 优化后
测试场景:查询用户详情(调用5个微服务)
优化前:
├─ 没有连接池(每次建立连接):+100ms
├─ 串行调用5个服务:50+100+80+60+70 = 360ms
├─ 没有缓存(每次都调远程):+50ms
└─ 总耗时:510ms 💀
优化后:
├─ 使用连接池(复用连接):-100ms
├─ 并行调用5个服务:max(50,100,80,60,70) = 100ms
├─ 使用缓存(80%命中率):-40ms
└─ 总耗时:60ms ⚡ 快8.5倍!
用户体验提升:
├─ 响应时间:510ms → 60ms
├─ 吞吐量:200 TPS → 1700 TPS
├─ CPU使用率:80% → 30%
└─ 用户满意度:😠 → 😄
✅ 最佳实践清单
连接池优化:
□ 使用连接池(OkHttp/Apache HttpClient)
□ 设置合理的连接数(MaxTotal/MaxPerRoute)
□ 开启Keep-Alive(复用连接)
□ 设置连接空闲时间
□ 监控连接池使用率
超时控制:
□ 设置连接超时(5秒)
□ 设置读取超时(10-30秒)
□ 分层超时(越下游越短)
□ 快速失败(不要无限等待)
□ 异常降级(返回默认值)
异步调用:
□ 并行调用独立的服务
□ 使用CompletableFuture
□ 配置专用线程池
□ 异常处理和降级
□ 设置整体超时时间
批量聚合:
□ 提供批量查询接口
□ 避免N+1查询问题
□ 使用DataLoader(GraphQL)
□ 批量大小适中(100-1000条)
□ 批量操作加事务
缓存机制:
□ 多级缓存(L1本地+L2Redis)
□ 设置合理的过期时间
□ 缓存更新策略(写穿/写回)
□ 监控缓存命中率
□ 预热热点数据
🎉 总结
核心要点
1️⃣ 连接池优化
- 使用连接池复用连接
- 设置合理的连接数
- 开启Keep-Alive
2️⃣ 超时控制
- 连接超时:5秒
- 读取超时:10-30秒
- 快速失败机制
3️⃣ 异步调用
- 并行调用独立服务
- CompletableFuture
- 专用线程池
4️⃣ 批量聚合
- 批量查询接口
- 避免N+1问题
- DataLoader
5️⃣ 缓存机制
- 多级缓存架构
- 本地缓存+Redis
- 合理过期时间
📚 延伸阅读
记住:微服务调用优化的核心是"连接池+超时+异步+批量+缓存"! 🚀
文档编写时间:2025年10月24日
作者:热爱性能优化的微服务工程师
版本:v1.0
愿你的微服务快如闪电! ⚡✨