系统接口优化:对接会员接口高并发和高性能优化

系统接口优化:对接会员接口高并发和高性能优化

首先我们理一下思路,在实现高并发和高性能优化时,可以从以下几个方面着手:

1. 接口层优化

a. 限流与熔断
  • 限流:通过限流工具(如 Sentinel 或 Hystrix)对关键接口设置 QPS(每秒查询数)限制,防止流量洪峰时服务崩溃。
  • 熔断:配置熔断机制,当某些接口失败率达到阈值时,快速返回默认结果,避免下游系统雪崩。
b. 批量处理
  • 对多次调用接口的数据进行合并处理,比如 guidePointListgetGuideWalletBalance 的接口请求可以通过批量方式减少调用次数。
c. 缓存
  • 使用 Redis 或其他缓存机制缓存高频查询的数据,例如用户信息(getMemberInfo)、门店数据(searchOrder)。
  • 设置合理的缓存失效时间,并根据业务场景引入 缓存预热缓存更新策略

2. 数据层优化

a. 数据库优化
  • 索引:检查 SQL 中是否使用了索引,避免全表扫描。尤其是在订单查询(如 searchOrder)中,对常用查询字段加索引。

  • 分库分表:如果数据库压力较大,考虑对大表进行分库分表(如按照门店、区域或时间分表)。

  • 读写分离:采用主从复制架构,将读请求分配到从库,写请求交给主库。

  • 优化分页查询

    • 使用 id > lastId LIMIT pageSize 的方式替代 OFFSET
    • 避免使用 COUNT(*) 查询总数,使用缓存或定时任务预计算。
b. 异步化
  • 对核销操作(如 checkCouponCodecheckOrder)通过异步处理队列(如 RocketMQ、RabbitMQ)解耦实时性和性能。
c. 结果裁剪
  • 对接口响应数据精简,仅返回前端必要的字段,减少不必要的字段解析和传输。

3. 代码层优化

a. Dubbo 服务调用
  • 设置超时时间:在 @Reference 注解中配置合理的超时时间(timeout 参数),避免长时间等待。
  • 并发能力优化:调整 Dubbo 服务的线程池(线程数、队列大小),确保后端服务承载能力。
  • 异步调用:对耗时操作采用异步调用或 CompletableFuture 提高吞吐量。
b. 日志优化
  • 限制日志量:将 log.debuglog.info 日志级别控制在生产环境可接受范围。
  • 异步日志:采用 Logback 的异步日志配置,提高接口响应速度。

4. 架构层优化

a. 接口网关
  • 引入网关(如 Spring Cloud Gateway 或 NGINX),进行统一的接口限流、缓存、路由转发,提升系统的抗压能力。
b. 分布式事务优化
  • 避免全局事务:
    • 使用本地事务+补偿机制(TCC)。
    • 对非核心业务(如积分推送 pushTrainIntegral),采用最终一致性策略。
c. 分布式锁优化
  • 使用 Redis 分布式锁时,设置合理的过期时间,避免死锁。
  • 如果性能压力大,可以尝试使用 Redisson 提供的高效锁实现。

5. 性能监控与压测

a. 性能监控
  • 监控接口性能指标(响应时间、吞吐量、错误率),例如:
    • 使用 Prometheus + Grafana 实现系统指标的实时监控。
    • 使用 Zipkin 或 Skywalking 跟踪分布式调用链。
b. 压力测试
  • 使用 JMeter 或 Locust 对接口进行模拟压测,定位系统瓶颈,优化慢接口。

6. 示例优化

示例:接口限流与缓存
@PostMapping(value = "/scrm/scrm-coupon-code-check-valid")
public BaseResponse<ValidCouponResp> checkValidCouponCode(@RequestBody ValidCouponRequest validCouponRequest) {
    // 尝试从缓存中获取结果
    String cacheKey = "coupon:check:" + validCouponRequest.getCouponCode();
    ValidCouponResp cachedResp = redisTemplate.opsForValue().get(cacheKey);
    if (cachedResp != null) {
        return BaseResponse.success(cachedResp);
    }

    // 限流
    if (!rateLimiter.tryAcquire()) {
        return BaseResponse.fail("系统繁忙,请稍后重试");
    }

    // 调用下游服务
    BaseResponse<ValidCouponResp> response = scrmFacade.checkValidCouponCode(validCouponRequest);

    // 缓存结果
    if (response.isSuccess()) {
        redisTemplate.opsForValue().set(cacheKey, response.getData(), 10, TimeUnit.MINUTES);
    }

    return response;
}

通过以上多维度的优化,可以显著提升系统的并发处理能力和整体性能。你可以根据实际业务场景,选择适合的优化策略逐步落地实施。

我的实际解决方案

原方法

1.Controller层,没有做熔断限流等策略

@PostMapping(value = "/RESTAdapter/retail/retail-order-count")
public IntegrateCommonResp findRetailOrderCount(@RequestBody RetailOrderReqVM retailOrderReqVM)
{
    if(StringUtils.isEmpty(retailOrderReqVM.getFrameNumber())&&StringUtils.isEmpty(retailOrderReqVM.getPurchaserMobile())){
        throw new ApplicationException(String.format("参数为空:%s",retailOrderReqVM.toString()));
    }
    BaseResponse<Long> response =retailOrderFacade.findConcurRetailOrderCount(retailOrderReqVM);

    return  transfer(response);
}

2.然后给这个调用的方法,加一个缓存

public Long findConcurRetailOrderCount(RetailOrderReqVM retailOrderReqVM) {
    Long count = retailOrderService.findRetailOrderCount(retailOrderReqVM);

    if (count > 0) {
        BaseSysParamSearchDTO req = new BaseSysParamSearchDTO();
        req.setParamCode("ORDER_NUM_LIMIT");
        req.setSetsOfBooksId(Constants.DEFAULT_SETS_OF_BOOKS_ID);

        BaseResponse<BaseSysParamResponseDTO> paramResp = baseSysParamFacade.getConcurrentSysParam(req);
        if (paramResp.isSuccess() && paramResp.getContent() != null) {
            if (count > Integer.valueOf(paramResp.getContent().getParamValue())) {
                throw new ApplicationException(String.format("一个手机号超过%s张订单数限制", paramResp.getContent().getParamValue()));
            }
        }
    }
    return count;
}

3.然后调用系统参数的时候,改为CAS自旋

/**
 * 获取系统参数
 */
public BaseSysParamResponseDTO getSysParam(BaseSysParamSearchDTO request) {
    BaseSysParam querySystemParam = new BaseSysParam();
    mapper(request, querySystemParam);
    BaseSysParam sp = getParamValueFirstCache(querySystemParam);
    if (Objects.nonNull(sp)) {
        return sp.convertTo(BaseSysParamResponseDTO.class);
    }
    // 当setsOfBooksId !=null,使用参数查询不到,修改成-1再次查询<br>
    querySystemParam.setSetsOfBooksId(Constants.DEFAULT_SETS_OF_BOOKS_ID);
    querySystemParam.setTenantId(Constants.DEFAULT_TENANT_ID);
    BaseSysParam sp1 = this.getOne(new QueryWrapper<>(querySystemParam));
    if (Objects.nonNull(sp1)) {
        RedisLockUtil.executeSynchOperate(locked -> {
            if (locked) {
                // 复制一份,修改账套为传递进来的账套id和租户id
                sp1.setSetsOfBooksId(request.getSetsOfBooksId());
                sp1.setIsGlobalUse(TrueOrFalseEnum.FALSE.getValue());
                super.save(sp1);
                saveCache(sp1);
                return 1L;

            } else {
                log.error("获取账套系统参数,竞争锁失败,请稍后刷新数据后重试");
                throw new ApplicationException(CommonErrorCode.LOCK_FAIL);
            }
        }, StringUtil.connectStr(BaseCenterConstants.LOCK_KEY_PREFIX_SYSTEM_PARAM, request.getSetsOfBooksId() + "", querySystemParam.getParamCode()), 5000);
    } else {
        throw new ApplicationException(BaseCenterErrorCode.SYSTEM_PARAM_NOT_FIND.getCode(),
                String.format(BaseCenterErrorCode.SYSTEM_PARAM_NOT_FIND.getMessage(), request.getParamCode()));
    }
    return sp1.convertTo(BaseSysParamResponseDTO.class);
}

解决方案:

索引查了没什么好改的,代码上也没什么,也没事务,就下面两个还可以大优化一下:

1. 增加熔断限流策略:限制外接接口对本系统负载

为外接接口增加熔断限流,保护本系统资源,避免因外部接口过载导致系统性能问题。推荐使用 Resilience4j(如上文所述)。它提供了 RateLimiterCircuitBreaker 等功能,集成简单,性能优秀。

  1. 添加依赖

pom.xml 中添加 Resilience4j 的依赖:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.0.2</version>
</dependency>
  1. 配置 Resilience4j

配置思路:

  1. 熔断器配置
    根据每个服务实例的处理能力调整熔断参数,重点关注失败率、滑动窗口大小等:
    • failure-rate-threshold: 默认值 50%,根据实际外接接口可靠性可调整到 40%-60%。
    • sliding-window-size: 如果每秒有大量请求,可以使用更大的窗口(如 10-20)。
    • minimum-number-of-calls: 避免因低调用量导致误触发熔断器,可设置为略大于每秒最小预期调用量(如 5-10 次)。
    • wait-duration-in-open-state: 熔断后恢复间隔要足够长以避免短时间内频繁触发(如 20s)。
  2. 限流器配置
    需要综合考虑系统的并发能力和外接接口的吞吐性能:
    • limit-for-period: 每秒最大允许请求量应接近容器实际的处理能力。 如果容器有 4 个实例,每个实例理论支持 500 QPS,总量为 2000 QPS,可设置为单实例约 400-500。
    • limit-refresh-period: 根据业务特性选择刷新频率,通常 1 秒。
    • timeout-duration: 限流时最大等待时间,建议不要过高(如 300-500ms)。

integrate-impl.yml 中添加熔断器和限流器的配置:

resilience4j:
  circuitbreaker:
    instances:
      externalApi:
        register-health-indicator: true
        failure-rate-threshold: 50         # 失败率超过 50% 触发熔断
        wait-duration-in-open-state: 20s  # 熔断后等待 20 秒尝试恢复
        sliding-window-size: 10           # 滑动窗口大小,记录最近 10 次调用
        sliding-window-type: COUNT_BASED  # 基于调用计数的窗口模式
        minimum-number-of-calls: 5        # 最少触发熔断的调用次数
  ratelimiter:
    instances:
      externalApi:
        limit-for-period: 400             # 每秒最多允许 400 次请求
        limit-refresh-period: 1s          # 刷新限流窗口周期为 1 秒
        timeout-duration: 300ms           # 等待获取许可的最大时间为 300 毫秒

  1. 编写接口调用逻辑

定义一个服务类,用于调用外部接口并集成 Resilience4j 的功能。

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Slf4j
@Service
public class ExternalApiService {

    private final RestTemplate restTemplate = new RestTemplate();

    // 配置熔断器和限流器,实例名称为 externalApi
    @CircuitBreaker(name = "externalApi", fallbackMethod = "fallbackResponse")
    @RateLimiter(name = "externalApi")
    public String callExternalApi(String url) {
        log.info("调用外部接口: {}", url);
        return restTemplate.getForObject(url, String.class);
    }

    // 熔断触发时的回退方法
    public String fallbackResponse(String url, Throwable throwable) {
        log.error("外部接口调用失败,触发熔断或限流。URL: {}, 错误: {}", url, throwable.getMessage());
        return "服务不可用,请稍后再试。";
    }
}
  1. 控制器层调用服务

编写一个 Controller 调用 ExternalApiService 中的方法:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ExternalApiController {

    private final ExternalApiService externalApiService;

    public ExternalApiController(ExternalApiService externalApiService) {
        this.externalApiService = externalApiService;
    }

    @GetMapping("/call-api")
    public String callExternalApi(@RequestParam String url) {
        return externalApiService.callExternalApi(url);
    }
}

代码说明

  1. 熔断器配置
    • failure-rate-threshold: 当调用失败率达到 50% 时熔断器触发。
    • wait-duration-in-open-state: 熔断器打开后等待 10 秒再尝试恢复。
    • sliding-window-type: 使用计数窗口,每个窗口记录最近 5 次调用。
  2. 限流器配置
    • limit-for-period: 每秒最多允许 10 个请求。
    • limit-refresh-period: 每 1 秒刷新限流窗口。
    • timeout-duration: 如果没有立即获取许可,最多等待 500 毫秒。
  3. 熔断回退方法
    • 在调用失败或限流触发时,fallbackResponse 方法将被执行,返回友好的降级提示。
  4. 监控和管理
    Resilience4j 集成了 Actuator,可以通过 /actuator 端点查看熔断器和限流器的状态。
2. 缓存已经有了,可以优化 Redis 分布式锁自旋

针对 Redis 分布式锁的实现,优化点包括:

  • 减少锁竞争冲突:通过加锁后再检查缓存避免重复操作。
  • 实现退避算法:加入指数退避减少频繁锁竞争。
  • 可配置化锁重试和等待时间:提升代码灵活性。

改成下面的:

public BaseSysParamResponseDTO getConcurrentSysParam(BaseSysParamSearchDTO request) {
    BaseSysParam querySystemParam = new BaseSysParam();
    mapper(request, querySystemParam);

    // 第一次检查缓存
    BaseSysParam sp = getParamValueFirstCache(querySystemParam);
    if (Objects.nonNull(sp)) {
        return sp.convertTo(BaseSysParamResponseDTO.class);
    }

    // 当 setsOfBooksId 不为 null 时,使用参数查询不到,修改成 -1 再次查询
    querySystemParam.setSetsOfBooksId(Constants.DEFAULT_SETS_OF_BOOKS_ID);
    querySystemParam.setTenantId(Constants.DEFAULT_TENANT_ID);
    BaseSysParam sp1 = this.getOne(new QueryWrapper<>(querySystemParam));

    if (Objects.nonNull(sp1)) {
        boolean lockAcquired = false;
        int retryCount = 5;  // 重试次数
        int baseWaitTime = 100;  // 基础等待时间 100 毫秒

        while (retryCount-- > 0 && !lockAcquired) {
            lockAcquired = RedisLockUtil.executeSynchOperate(locked -> {
                if (locked) {
                    // 第二次检查缓存
                    BaseSysParam spCheck = getParamValueFirstCache(querySystemParam);
                    if (Objects.nonNull(spCheck)) {
                        return true;  // 缓存命中,不需要再保存新的参数
                    }

                    sp1.setSetsOfBooksId(request.getSetsOfBooksId());
                    sp1.setTenantId(request.getTenantId());
                    sp1.setCreationDate(LocalDateTime.now());
                    sp1.setLastUpdateDate(LocalDateTime.now());
                    sp1.setCreatedBy(request.getCurrUserLoginId());
                    sp1.setLastUpdatedBy(request.getCurrUserLoginId());
                    sp1.setIsGlobalUse(TrueOrFalseEnum.FALSE.getValue());
                    super.save(sp1);
                    saveCache(sp1);
                    return true;
                } else {
                    log.error("获取账套系统参数,竞争锁失败,请稍后刷新数据后重试");
                    return false;
                }
            }, StringUtil.connectStr(BaseCenterConstants.LOCK_KEY_PREFIX_SYSTEM_PARAM,
                                     request.getSetsOfBooksId() + "",
                                     querySystemParam.getParamCode()),
                                                             30000);

            if (!lockAcquired) {
                // 短时间自旋等待,减少CPU占用
                // int spinWaitTime = (int) (baseWaitTime * Math.pow(2, 5 - retryCount));  // 指数退避算法
                // long end = System.currentTimeMillis() + spinWaitTime;
                // while (System.currentTimeMillis() < end) {
                //     try {
                //         Thread.sleep(10);
                //     } catch (InterruptedException e) {
                //         Thread.currentThread().interrupt();
                //         throw new ApplicationException(CommonErrorCode.LOCK_FAIL);
                //     }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new ApplicationException(CommonErrorCode.LOCK_FAIL);
                }
            }
        }

        if (!lockAcquired) {
            throw new ApplicationException(CommonErrorCode.LOCK_FAIL);
        }
    } else {
        throw new ApplicationException(BaseCenterErrorCode.SYSTEM_PARAM_NOT_FIND.getCode(),
                                       String.format(BaseCenterErrorCode.SYSTEM_PARAM_NOT_FIND.getMessage(), request.getParamCode()));
    }

    // 返回参数数据
    return sp1.convertTo(BaseSysParamResponseDTO.class);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清河大善人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值