首页 > 其他分享 >呕血回顾一次提高接口并发的经历,很实用

呕血回顾一次提高接口并发的经历,很实用

时间:2023-09-23 22:44:20浏览次数:35  
标签:事务 clockDto newClockRecord 数据库 接口 查询 并发 呕血

最近在开发一个打卡接口,其实只需要做些判断,保存一下打卡结果即可,预计同时段1000多人在线打卡,但是第一次写完之后,压测效果非常糟糕,可以看到只有十几的并发,喝下的水都要喷出来了,那么简单的接口都能耗时那么久的吗,我预估100ms以内准可以的,那还有上百的并发才对。于是开始了我的优化之路。

看看主要代码,controller接收参数进来之后,执行一下服务方法就返回了,就是查询活动,判断活动时间,累计积分,保存结果,并发这么差我实属是想不到,让人摇头摇头郁闷,我应该没问题啊,部署的机器有问题?但是也都是还可以的服务器的。

  @Override
  public RelationActivityRecord clock(String userId, ClockDto clockDto) {
    // 判断活动是否有效
    RelationActivity relationActivity =
        relationActivityService.getRelationActivity(clockDto.getActivityId(),
                clockDto.getActivityType());
    Date now = new Date();
    if (now.before(relationActivity.getStartTime())) {
      throw new BadRequestException(ErrorEnum.WRONG_ARGUMENTS, "活动还未开始,请耐心等待~");
    }
    if (now.after(relationActivity.getEndTime())) {
      throw new BadRequestException(ErrorEnum.WRONG_ARGUMENTS, "活动已结束,可在主页查看个人作品哦~");
    }
    // 本次打卡获得积分
    int score= 0;
    // 当天的0点时间
    Date toDay = DateUtil.beginOfDay(now);
    if (Objects.equals(clockDto.getActivityType(), 1)
        && Objects.equals(clockDto.getClockType(), 1)) {
      // 查询累计打卡次数
      CurrentClockRecordVo clockRecordStat = this.currentRecord(userId,
              clockDto.getActivityId(), clockDto.getActivityType(), 
              );
      // 当天第一次的打卡,可以攒一个积分
      if (clockRecordStat.getLastTime() != null && clockRecordStat.getLastTime().before(toDay)
              && clockRecordStat.getNum() < 100) {
        score = 1;
      }
    } else if (Objects.equals(clockDto.getActivityType(), 1)) {
      // 其他的活动之类...
      throw new BadRequestException(ErrorEnum.WRONG_ARGUMENTS, "活动暂未开放");
    } else {
      throw new BadRequestException(ErrorEnum.WRONG_ARGUMENTS, "未知活动");
    }
    // 保存打卡记录
    RelationActivityRecord newClockRecord = new RelationActivityRecord();
    newClockRecord.setStudentId(userId);
    newClockRecord.setActivityId(clockDto.getActivityId());
    newClockRecord.setActivityType(clockDto.getActivityType());
    newClockRecord.setClockType(clockDto.getClockType());
    newClockRecord.setCreateTime(now);
    newClockRecord.setContent(clockDto.getContent());
    newClockRecord.setEnergy(energy);
    newClockRecord.setVideoId(clockDto.getVideoId());
    newClockRecord.setIntro(clockDto.getIntro());
    this.save(newClockRecord);
    // 有积分可以攒到学生账户上
    if (score> 0) {
      activityStudentService.addScore(clockDto.getActivityId(), userId, score);
    }
    return newClockRecord;
  }

冷静冷静再次喝口水,遇到问题先找找外部原因,是不是springboot默认的tomcat最大连接数之类的默认值太小,项目里是没有重新配置的,但是一想前些天看过一个视频说,默认的都是8192最大连接数,那不改配置也是嘎嘎够用的。谨慎起见,写一个随便执行几条语句,不连接数据库的接口压测一下,一开始有1000多并发,然后积攒的排队请求多了之后,并发逐渐下降到600,正常合理,处理不过的请求会排队没办法,除非加大线程数的配置:

突然想起来,项目启动不是用的tomcat,而是更换成了undertow,这家伙有问题吗?但是求助搜索引擎,说它要比tomcat更彪悍,至少默认是有8000并发的,看来也排除了。

那会不会是JVM的堆栈设置的太小,处理卡顿,看了下gc数是有点多。其他项目拷贝过来的启动配置,才512M我去,马上改成4G试试。然而,实际效果并无变化。其实gc虽然很多,但实际耗费的时间加起来都没3s,可以排除是它的问题。(这里还遇到了一个问题,就是即使改大了之后,项目一次启动还是会有3次full-gc和几次的young-gc,一下子搞不懂为什么会这样,有经验的朋友可以给些排查方向)

为此,我判断大概率是接口耗时太久导致并发起不来。

代码除了一些判断语句,对象转换,查询插入更新数据库,就没有其他操作了。对象转换有问题吗,但是通篇都有对象转换,这都有问题可能吗。估计真的是数据库有问题了。为了提高查询速度,我只加了一个复合索引,这都那么慢是很不符合常理的。

那我就用skywalking探测一下!

这。。真的是接口耗时符非常久,虽然压测情况下,分配资源出来等了70ms,但接口执行的时候却花费了308ms,在skywalking怎么只能看到查询语句的耗时,可是查询语句只花了几毫秒而已。

那我加下p6spy看下,每条语句的执行耗时吧。

吃惊,吓人,这两插入更新语句居然需要两百毫秒。

咋回事,我想想,想不通,求助搜索引擎和大模型了:

留意到第2点,没有想到,我本来认为接口可以忍受不加事务带来数据不一致性,以提高并发。既然这么说了,那就加个事务试试。不加不知道,一加吓一跳,加了基本0耗时了。

再搜索一下这是为什么,有人说是加了事务之后,不会像之前一样插入一次写一次盘,然后在事务的干预下,攒够一批再写盘,这样效率就高了。那这里或者还能优化这一批能不能攒多点,也要衡量涉及到写盘的时候,当时的单个接口请求就会慢了。

然后有了另一个疑问,我事务是不是加得太大了,毕竟是先查询,再插入和更新数据,可以将它们分开的,那就分开试试,查询不加事务,插入更新提出一个方法加事务,但是,奇了怪了,结果并发效果更差了。看来这里面还有事。 同时发现这里一开始压测都压同一个用户,按理说真实情况是不同用户的,同一个用户可能竞争资源更加剧烈,所以对获取用户userId改了一下随机生成一个。

这个时候感觉到8.x版本的skywalking实在是很难提供到更全面的消耗信息,想到不久前看到skywalking9.x版本来了,那就下载一个试试吧。这里我一开始下载了最新的版本,结果死活启动不了一直闪退,什么端口占用啊都改了也不行,大坑。然后直接选9.0版本,就正常启动了。果然9.0版本就不一样了,界面布局舒服了,监控数据也活了,很赞。

而且,trace链路的耗时发布总算是全面了很多!!数据库连接都出来了。可以看到,看了下没有加事务之前,每次查询,写入,都要单独获取一次数据库链接!!!插入语句监控也能正常出来了!

查询不加事务,查询和更新用了事务,得获取三次数据库连接,第一次连接是为了查selelct 1判断数据库是否正常,可以去掉。

查询和插入更新在同一个大事务,只获取一次数据库连接,效率自然更高了。

这里基本也确定了,放一个事务里,效率要高,减少获取数据库连接。

此外,通过查看 skywalking链接追踪,发现是getConnection的时候特别耗时,获取一个连接要好几秒。

目前连接池用的是druid,参数是默认配置,查了下默认配置的最大连接数并不高,那问题估计就是这里了,加大试试。目前pg数据库设置是250个链接,如果是生产环境,数据库独享,yml和pg配置的最大连接数还能大些,目前先试试200,如果并发能提高说明这里也是能优化的点,当然实际配置还得看情况,不然会把数据库拉胯。

设置一秒200并发,持续30秒,效果还不错,平均700多ms一个请求,最后并发到250左右。

这里的undertow和数据库连接配置还需要逐步按实际情况不断调整,以达到单机最佳效果。

总结一下:

  1. 并发低,有配置的问题,也有代码的问题。

  2. 事务的添加需要留意一下添加之后的效果,有时候并非是自己想的那样,一开始觉得事务范围大不合适,结果它效果比不加事务还要好。

  3. 一个好工具太重要了,例如skywalking9.0。

  4. 多点分析。

  5. 好好总结这次经验,基本单机并发就这些问题比较多了。

标签:事务,clockDto,newClockRecord,数据库,接口,查询,并发,呕血
From: https://www.cnblogs.com/ljy-1471914707/p/17725280.html

相关文章

  • 1.单列集合(接口 Collection,List,Set)
    单列集合(接口Collection,List,Set)单列集合体系结构:特点:1.List系列集合: 添加的元素是有序、可重复、有索引;2.Set系列集合: 添加的元素是无序、不重复、无索引;3.有序为存入和取出都是一样的顺序,非内部里的顺序;Collection概念:Collection是单列集合的祖宗接口,它的功能......
  • AES-256-ECB PKCS7Padding 解密 微信退款接口
    微信退款通知https://pay.weixin.qq.com/wiki/doc/api/wxpay/ch/pay/OfficialPayMent/chapter8_8.shtml需要的pom<!--https://mvnrepository.com/artifact/commons-codec/commons-codec--> <dependency> <groupId>commons-codec</groupId> <......
  • 【Java 基础篇】Java 接口全面解析:简化多态性与代码组织
    接口(Interface)是Java面向对象编程中的一个重要概念。它允许定义一组抽象方法,这些方法可以被实现类(类)实现。接口提供了一种规范,规定了实现类必须提供哪些方法,但不关心具体的实现细节。本篇博客将深入探讨Java中接口的概念、语法和实际应用,适用于初学者,帮助你轻松理解和应用接口......
  • 【Java 基础篇】Java 自然排序:使用 Comparable 接口详解
    在Java编程中,我们经常需要对对象进行排序。为了实现排序,Java提供了java.lang.Comparable接口,它允许我们定义对象之间的自然顺序。本篇博客将深入探讨如何使用Comparable接口来进行自然排序,包括接口的基本概念、使用示例以及一些常见问题的解决方法。什么是自然排序?自然排序......
  • 华为datacom-HCIA​ 华为datacom-HCIA 1​ 1. 第四弹 5​ 1.1. OSPF认证 5​ 1.1.1.
    华为datacom-HCIA华为datacom-HCIA11.第四弹51.1.OSPF认证51.1.1.基于接口认证51.1.1.1.接口认证更优先61.1.1.2.[R2]interfaceg0/0/161.1.1.3.[R2-g0/0/1]ospfauthentication-modesimplehuawei61.1.1.3.1.明文认证61.1.1.4.[R2-g0/0/1]ospfauthentication-mo......
  • Apache IoTDB开发系统之Python原生接口
    依赖在使用Python原生接口包前,您需要安装thrift(>=0.13)依赖。使用示例首先下载最新安装包:pip3installapache-iotdb注意:如果您想要安装0.13.0版本的PythonAPI,不要使用 pipinstallapache-iotdb==0.13.0,请使用 pipinstallapache-iotdb==0.13.0.post1 作为替代!您可......
  • 物联网中北向接口和南向接口区别
    北向接口和南向接口的区别:功能方向:北向接口面向上层设备,提供设备管理和监控功能;南向接口面向下层设备,实现设备的配置和控制。数据传输:北向接口主要用于传输设备状态、数据上报、性能和配置信息;南向接口主要用于传输命令和配置信息。协议和命令集:北向接口通常使用标准的网络......
  • 【转】H3C交换机由于连接非标准PD设备,导致POE接口无法供电问题
    1.问题描述  在H3CS5024PV2-EI-PWR交换机连接AP的接口使能POE功能(poeenable)后,发现相关AP无法上电启动,怀疑交换机POE供电模块存在异常。2.问题分析 1)检查S5024PV2-EI-PWR交换机与AP之间网线,其规格为超五类网线,且长度小于100米,满足POE供电要求。 2)使用PC替换AP连接......
  • 并发编程系列-CAS
    锁(lock)的代价锁是用来做并发最简单的方式,其代价也是最高的,Java在JDK1.5之前都是靠synchronized关键字来加锁。但是加锁机制会有如下几个问题:加锁、释放锁会需要操作系统进行上下文切换和调度延时,在上下文切换的时候,cpu之前缓存的指令和数据都将失效,这个过程将增加系统开销。......
  • API网关是如何提升API接口安全管控能力的
    API安全的重要性近几年,越来越多的企业开始数字化转型之路。数字化转型的核心是将企业的服务、资产和能力打包成服务(服务的形式通常为API,API又称接口,下文中提到的API和接口意思相同),从而让资源之间形成更强的连接和互动关系,释放原有资产的价值,提升企业的服务能力。企业数字化转型使得......