• 线上接口流量突增,快要扛不住了


    优化手段

    排查连接池大小

    其实大部分的请求都是会访问数据库,而数据库严重依赖连接池数量,如果一个项目连接池数量设置过小,那势必会导致性能下降。

    Hikarip连接池配置说明如下:

    1. #最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size
    2. spring.datasource.hikari.minimum-idle=5
    3. #最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值
    4. spring.datasource.hikari.maximum-pool-size=100
    5. #自动提交从池中返回的连接,默认值为true
    6. spring.datasource.hikari.auto-commit=true
    7. #空闲连接超时时间,默认值600000(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且小于10秒,会被重置为10秒。
    8. #只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放
    9. spring.datasource.hikari.idle-timeout=30000
    10. #连接池名称,默认HikariPool-1
    11. spring.datasource.hikari.pool-name=Hikari
    12. #连接最大存活时间.不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短;单位ms
    13. spring.datasource.hikari.max-lifetime=55000
    14. #连接超时时间:毫秒,小于250毫秒,会被重置为默认值30秒

    后来发现项目中设置的最大连接数maximum-pool-size是50,有点小,后面我改成了500。

    tomcat性能优化

    我们项目采用的spingboot项目,内置的tomcat容器,影响tomcat容器性能的重要参数如下:

    maxThreads

    我们知道 maxThreads 指的是请求处理线程的最大数量,在 Tomcat7 和 Tomcat8 中都是默认 200 个。

    对于这个参数的设置,需要根据任务的执行内容去调整,一般来说计算公式为:最大线程数 = ((IO时间 + CPU时间)/CPU时间) * CPU 核数。这个公式的思路其实很简单,就是最大化利用 CPU 的资源。一个任务的耗时分为 IO 耗时和 CPU 耗时,基本上 IO 耗时是最多的,这时候 CPU 是没事干的。

    maxConnections

    maxConnections 指的是当线程池的线程达到最大值,并且都在忙的时候,Connector 中的队列最多能容纳多少个连接。一般来说,我们都要设置一个合理的数值,不能让其无限制堆积。因为 Tomcat 的处理能力肯定是有限的,到达一定程度肯定就处理不过来了,因此你堆积太多了也没啥用,反而会造成内存堆积,最终导致内存溢出 OOM 的发生。一般来说,一个经验值是可以设置成为 maxThreads 同样的大小。

    acceptCount

    acceptCount 指的是当 Container 线程池达到最大数量且没有空闲线程,同时 Connector 队列达到最大数量时,操作系统最多能接受的连接数。 当队列中的个数达到最大值后,进来的请求一律被拒绝,默认值是 100。这可以理解成是操作系统的一种自我保护机制吧,堆积太多无法处理,那就直接拒绝掉,保护自身资源。

    在项目中,由于时间有限,我没有用jemeter等进行性能压测,直接预估了下, 设置了maxThreads、maxConnections、acceptCount都为800,如果时间允许的情况下,建议还是通过jemter压测出一个以最优值。

    接口代码优化

    完成上面的系统级别的优化后,就要针对具体的代码进行分析优化了,首先推荐一个神器arthas, 可以查看接口中的方法耗时情况,执行trace命令,可以看到如下例图:

    优化方法无非是如下几种情况:

    1. 尽量避免for循环中查询数据库或者访问外部接口
    2. sql调优
    3. 缓存
    4. ....

    排查了项目的情况,发现是在调用远程服务时,返回延迟比较大,考虑到该远程服务变化可能很小,于是做了一个本地缓存处理,同时定时同步处理,大致代码如下:

    1. 缓存接口定义
    1. public interface LocalCache<T> {
    2. /**
    3. * 根据key获取缓存信息
    4. * @param key 缓存key
    5. * @return 缓存对象
    6. */
    7. T get(String key);
    8. /**
    9. * 保存缓存信息, 存在了会覆盖
    10. * @param key 缓存key
    11. * @param cacheItem 缓存对象
    12. */
    13. void save(String key, T cacheItem);
    14. /**
    15. * 根据缓存key删除缓存信息
    16. *
    17. * @param key 缓存对象
    18. */
    19. void delete(String key);
    20. }
    1. 定义抽象父类
    1. public abstract class AbstractGuavaCache implements LocalCache, InitializingBean {
    2. private static final ScheduledExecutorService SCHEDULED_CACHE =
    3. new ScheduledThreadPoolExecutor(2, new ThreadFactoryBuilder().setNameFormat("guava cache-%d").build());
    4. protected long expireSeconds;
    5. protected long maximumSize;
    6. protected long initDelay;
    7. protected long delay;
    8. protected Function<String, T> loadFunction;
    9. protected boolean cacheTaskSwitch;
    10. /**
    11. * 定义缓存对象
    12. */
    13. private LoadingCache<String, T> guavaCache;
    14. public AbstractGuavaCache() {}
    15. @Override
    16. public void afterPropertiesSet() {
    17. // 初始化guavaCache对象
    18. this.guavaCache = CacheBuilder.newBuilder().expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
    19. .maximumSize(maximumSize).build(new CacheLoader<String, T>() {
    20. @Override
    21. public T load(String key) {
    22. return loadFunction != null ? loadFunction.apply(key) : null;
    23. }
    24. });
    25. // 定时任务
    26. if(cacheTaskSwitch) {
    27. SCHEDULED_CACHE.scheduleWithFixedDelay(() -> {
    28. try {
    29. this.reloadAllToCache();
    30. } catch (Exception e) {
    31. log.error("cache error", e);
    32. }
    33. }, initDelay, delay, TimeUnit.SECONDS);
    34. }
    35. }
    36. @SneakyThrows
    37. @Override
    38. public T get(String key) {
    39. return guavaCache.get(key);
    40. }
    41. @Override
    42. public void save(String key, T cacheItem) {
    43. guavaCache.put(key, cacheItem);
    44. }
    45. @Override
    46. public void delete(String key) {
    47. guavaCache.invalidate(key);
    48. }
    49. /**
    50. * 更新缓存操作
    51. */
    52. protected void reloadAllToCache() {
    53. }
    54. }
    1. 定义具体实现
    1. @Component("orgCache")
    2. @Slf4j
    3. public class SysOrgCacheManager extends AbstractGuavaCache<SysOrg> implements OrgCacheService {
    4. public SysOrgCacheManager(EventCacheProperties eventCacheProperties) {
    5. this.expireSeconds = eventCacheProperties.getExpireSeconds();
    6. this.maximumSize = eventCacheProperties.getMaximumSize();
    7. this.cacheTaskSwitch = eventCacheProperties.isTaskEnabled();
    8. this.initDelay = eventCacheProperties.getInitDelay();
    9. this.delay = eventCacheProperties.getDelay();
    10. this.loadFunction = key -> loadByKey(key);
    11. }
    12. private SysOrg loadByKey(String orgId) {
    13. log.warn("缓存未命中,直接查询数据库, orgId: [{}]", orgId);
    14. SysOrg org = OrgApi.getOrgById(orgId);
    15. SysOrg cacheOrg = transformCachedOrg(org);
    16. log.warn("缓存未命中,加载后的数据, data: [{}]", JSON.toJSONString(cacheOrg));
    17. return cacheOrg;
    18. }
    19. private SysOrg transformCachedOrg(SysOrg org) {
    20. if(org == null || StrUtil.isEmpty(org.getId())) {
    21. return null;
    22. }
    23. return new SysOrg().setId(org.getId()).setName(org.getName()).setFullPath(org.getFullPath());
    24. }
    25. @Override
    26. protected void reloadAllToCache() {
    27. log.info("-------cache org 【缓存机构】 开始 -------");
    28. TimeInterval timeInterval = new TimeInterval();
    29. // 查询全量的机构数据
    30. List remoteOrgs = OrgApi.selectAll();
    31. remoteOrgs.forEach(org -> {
    32. SysOrg cacheOrg = this.transformCachedOrg(org);
    33. this.save(org.getId(), cacheOrg);
    34. });
    35. log.info("-------cache org【缓存机构】 结束, cost: [{}] -------", timeInterval.intervalSecond());
    36. }
    37. @Override
    38. public List findOrgsByIds(Collection orgIds) {
    39. if(CollUtil.isEmpty(orgIds)) {
    40. return Lists.newArrayListWithExpectedSize(16);
    41. }
    42. List orgs = orgIds.stream().map(orgId -> this.get(orgId)).filter(Objects::nonNull).collect(Collectors.toList());
    43. return orgs;
    44. }
    45. }

    这里是通过guava cache实现的,通过配置可以修改缓存全量刷新的时间、缓存的失效时间、缓存最多存储的数据量。

    总结

    后面复盘分析了下,导致该问题主要的原因如下:

    1. 公司管理松散混乱,没有流程,包括本次部署架构和方案都没有,实施水平参差不齐,员工流动性大,招进来就用,完全没有什么培训机制。
    2. 公司标准研发流程存在问题,蒙眼狂奔,一大堆新需求,砍工时,完全没有排非功能性测试、性能测试的时间。
    3. 开发人员也要不断提高自己

    目前虽然基本达到客户需求,但是感觉还是有很多不足,大家有没有一些其他的优化思路和方案呢?

     

  • 相关阅读:
    【云驻共创】 JAVA常用的开发工具有哪些?
    JAVASE 游戏
    神经网络算法入门书籍,神经网络相关书籍
    Windows下使用Bat文件杀死进程
    ESP8266-Arduino编程实例-TEMT6000环境光传感器驱动
    Flowable主要API介绍
    以数据赋能,星辰天合推进智慧化校园建设
    pytest合集(11)— conftest.py文件
    Spring5应用之事务处理
    基于springboot+vue的信息技术知识赛系统
  • 原文地址:https://blog.csdn.net/weixin_62421895/article/details/126193529