<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      記錄一次首頁優(yōu)化的經(jīng)歷

               公司最近在進行多品牌合一,原來五個品牌的app要合并為一個。品牌立項、審批、方案確定,歷史數(shù)據(jù)遷移、前期的基礎(chǔ)工程搭建,兼容以及涉及三方的交互以及改造,需求梳理等也都基本完成,原來計劃9月中旬進行上線,但是上線后服務(wù)端的壓測一直通不過-首頁抗不過太高的并發(fā)。

              app首頁里面是一個信息流,里面包含運營以及用戶發(fā)布的各種視頻、動態(tài)以及問答之類的帖子,廣告位、運營后臺配置的推薦帖以及會在信息流指定位置放置一些banner位等,還包括智能推薦、三方推薦以及一部分自研的推薦數(shù)據(jù)(服務(wù)端有開關(guān)配置走那種推薦系統(tǒng)),服務(wù)端這塊統(tǒng)一由一個接口組裝各種數(shù)據(jù)后返回給app端。組裝的數(shù)據(jù)大部分是存在redis緩存里面,比如廣告位、banner位以及一些運營自己在后臺配置的一些配置,這些因為變化不大或者基本不怎么變化,從緩存讀取耗時也都很快的,耗時比較慢的點主要集中在 內(nèi)部的feign調(diào)用以及查詢es信息流時候要進行組裝信息,這些在壓測時候基本占用了整個應(yīng)用耗時的90%以上。使用hutool的StopWatch統(tǒng)計耗時大概如下這種:(單位 ms)

       

      000000201 08% 組裝es數(shù)據(jù)

      000000599 25% feign調(diào)用uc獲取黑名單數(shù)據(jù)

      000000495 21% feign調(diào)用社區(qū)查詢置頂數(shù)據(jù)

      000000603 25% 查詢帖子列表

      000000394 16% 修改意向車型

      000000002 00% 獲取內(nèi)容標簽數(shù)據(jù)

      000000000 00% 獲取熱門話題數(shù)據(jù)

      000000002 00% 獲取專題組件數(shù)據(jù)

      000000090 04% 獲取信息流廣告位數(shù)據(jù)

      000000001 00% 獲取快速入口數(shù)據(jù)

      000000002 00% 獲取輪播廣告數(shù)據(jù)

            

       在單臺機器 2核4G,壓測100并發(fā)的情況下,平均耗時能達到2s左右,p90 能在3s多,整體性能很差。

            在壓測時候,使用arthas的監(jiān)控工具,在耗時比較嚴重的地方進行監(jiān)控,首先是feign調(diào)用的地方,發(fā)現(xiàn)有個奇怪的線程,使用trace命令 監(jiān)控超過30ms的請求,發(fā)現(xiàn)很少 

        trace com.gwm.marketing.service.impl.HomeRecommendServiceImpl addSuperStreams '#cost > 30' -n 50 --skipJDKMethod false

       

         但是在調(diào)用方的耗時 能達到驚人的1s以上,如果是并發(fā)量比較少的情況,比如50并發(fā)左右,這個耗時不超過200ms,但是一旦并發(fā)過高立馬耗時就上來了。服務(wù)端內(nèi)部是通過feign調(diào)用來進行rpc調(diào)用的,而且被調(diào)用方的耗時也很快,初步懷疑是 不是 調(diào)用方 存在gc的問題,或者是調(diào)用方 線程池阻塞 以及  feign調(diào)用在高并發(fā)下的默認的鏈接存在阻塞導(dǎo)致的。另外還有發(fā)現(xiàn)es以及部分日志也陸續(xù)存在耗時比較久的情況,總體上這些加起來 ,導(dǎo)致整個耗時比較嚴重。

             首先看了下服務(wù)器的gc,單機2核4g 配置的gc信息如下:

         -Djava.net.preferIPv4Stack=true -Djava.util.Arrays.useLegacyMergeSort=true -Dfile.encoding=UTF-8 -server -Xms512m -Xmx512m -XX:NewSize=128m -XX:MaxNewSize=128m -Xss256k -Drocketmq.client.logUseSlf4j=true -XX:+UseG1GC -XX:+PrintGCTimeStamps -Xloggc:log/gc-%t.log -XX:+UseGCLogFileRotation -XX:GCLogFileSize=10M

           這個配置還使用的是G1,首頁的這個場景,首頁是服務(wù)器的內(nèi)存過小,使用g1的話,對于我們想要的是吞吐量更大的其實有些差距,我們的目的是為了吞吐量更大,響應(yīng)時間更快,那么使用cms的方式其實更好。然后讓運維改了一下 gc的回收方式為cms:

      -Djava.net.preferIPv4Stack=true -Djava.util.Arrays.useLegacyMergeSort=true -Dfile.encoding=UTF-8 -server -Xms512m -Xmx512m -XX:NewSize=128m -XX:MaxNewSize=128m -Xss256k -Drocketmq.client.logUseSlf4j=true -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:CMSInitiatingOccupancyFraction=75 -Xloggc:log/gc-%t.log -XX:+UseGCLogFileRotation -XX:GCLogFileSize=10M

       實測后發(fā)現(xiàn)有一定的提升,比如使用g1的話,壓測5分鐘的耗時 和使用cms的壓測耗時

       

        這個是能提升一定的性能,然后耗時比較嚴重的第二個部分 就是 feign調(diào)用- 為何單個接口的耗時很快,但是在feign調(diào)用時候就很慢?

            首先feign調(diào)用 我們的配置是走了內(nèi)網(wǎng)的,即不走網(wǎng)關(guān)-

            

       

            我們的項目是基于nepxion里面的openfeign,openfeign使用的是默認的HttpURLConnection的鏈接方式,而且看了我們的openfeign的版本是 2.2.3的,這個版本還沒有池化的概念-就是每次的rpc通過

      feign調(diào)用的話,每次都需要走tpc的三次握手和四次揮手, 這個耗時在請求量比較少的情況下體現(xiàn)不出來,但是在并發(fā)量比較高的情況下,就體現(xiàn)出來了- 表現(xiàn)就是 明明在被調(diào)用方的耗時很小,但是在調(diào)用方顯示出來的耗時就是很長,這個通過arthas和skywalking 的監(jiān)控都證明了這一點。skywalking顯示的 self耗時很長但是業(yè)務(wù)耗時很快,arthas在容器內(nèi)看好是情況也是如此,因此需要對feign調(diào)用進行改造。

            首先是對open-feign的版本進行升級,openfeign支持httpclient以及okhttpclient ,在最新的 4.2.0-SNAPSHOT 上 OkHttpFeignLoadBalancedConfiguration 里面已經(jīng)是使用了連接池了,源碼如下:

          

      @Configuration(proxyBeanMethods = false)
      @ConditionalOnClass(OkHttpClient.class)
      @ConditionalOnProperty("spring.cloud.openfeign.okhttp.enabled")
      @ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
      @EnableConfigurationProperties(LoadBalancerClientsProperties.class)
      class OkHttpFeignLoadBalancerConfiguration {
      
          @Bean
          @ConditionalOnMissingBean
          @Conditional(OnRetryNotEnabledCondition.class)
          public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient,
                  LoadBalancerClientFactory loadBalancerClientFactory,
                  List<LoadBalancerFeignRequestTransformer> transformers) {
              OkHttpClient delegate = new OkHttpClient(okHttpClient);
              return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory,
                      transformers);
          }
      
          @Bean
          @ConditionalOnMissingBean
          @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
          @ConditionalOnBean(LoadBalancedRetryFactory.class)
          @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
                  matchIfMissing = true)
          public Client feignRetryClient(LoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient,
                  LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
                  List<LoadBalancerFeignRequestTransformer> transformers) {
              OkHttpClient delegate = new OkHttpClient(okHttpClient);
              return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
                      loadBalancerClientFactory, transformers);
          }
      
      }

           而我們這個2.2.3的還不支持池化,故首先把open-feign的版本升級了,以支持可以池化,另外就是需要配置  池化的參數(shù)以及開啟okhttpclient

      feign.httpclient.enabled = false #httpClient 關(guān)閉
      feign.okhttp.enabled = true #okHttpClient 啟用
      feign.httpclient.maxConnections = 1000   #最大連接數(shù)
      feign.httpclient.maxConnectionsPerRoute = 300   #feign單個路徑的最大連接數(shù)
      feign.httpclient.connectionTimeout = 3000  #超時時間
         
         這樣優(yōu)化后發(fā)現(xiàn)feign調(diào)用的性能好了不少。
           還有就是 針對feign調(diào)用,因為默認走的是10s超過,如果這個超時時間這么長,對系統(tǒng)會影響很大,而且還是首頁,決定在首頁里面 使用feign調(diào)用的如果超過指定的讀超時和連接超時,則直接使用默認值來進行代替。代碼如下:
        
                          try {
                              Request.Options options = new Request.Options(connectionTimeoutConfig.getFeignConnectionTimeout(),connectionTimeoutConfig.getFeignReadTimeout());
                              data = gwmInterFeignClient.queryLikeAndCollectStatusByThreadsOptionLimit(appUserThreadStatusQuery,options).getData();
                          } catch (Exception e) {
                              log.error("===feign調(diào)用超時====請求廢棄");
                              data = new AppUserThreadFDto();
                          }

            再就是在arthas下面發(fā)現(xiàn)有日志也存在耗時嚴重的情況,對于非必要的日志,進行不去進行打印 或者修改日志級別為debug,當然還有一種方式 就是把寫日志的方式 改成異步來寫。

            另外還發(fā)現(xiàn)代碼里面如果判斷一個對象是否在集合里,也會存在性能耗時的問題,這個需要改為并發(fā)執(zhí)行 ,類似代碼如下: 

      優(yōu)化前:

                          streams.forEach(stream -> {
                              stream.setIsLike(CollectionUtils.isNotEmpty(data.getLikeThreads()) ? (data.getLikeThreads().contains(stream.getThreadId()) ? ONE : ZERO) : ZERO);
                              stream.setIsCollect(CollectionUtils.isNotEmpty(data.getCollectThreads()) ? (data.getCollectThreads().contains(stream.getThreadId()) ? ONE : ZERO) : ZERO);

      優(yōu)化后:

                          streams.forEach(stream -> {
                              stream.setIsLike(CollectionUtils.isNotEmpty(finalData.getLikeThreads())? (finalData.getLikeThreads().parallelStream().anyMatch(d->d.equals(stream.getThreadId()))? ONE : ZERO) : ZERO);
                              stream.setIsCollect(CollectionUtils.isNotEmpty(finalData.getCollectThreads()) ? (finalData.getCollectThreads().parallelStream().anyMatch(d->d.equals(stream.getThreadId())) ? ONE : ZERO) : ZERO);
      
                          });

       

      除了這個還有es的耗時嚴重的的問題,我們的es 采用的是5.7的版本,比較老了,es主要是組裝數(shù)據(jù) 以及查詢相對比較方便,庫表大概有幾千萬的帖子數(shù)據(jù),但是每次耗時 有時候也能達到400-500ms的延遲。

       這個暫時沒找到原因,后面有時間了在繼續(xù)。

              還有就是在首頁里面加上sentinel 限流,首頁里面有一部分耗時比較長的部分,比如 調(diào)用三方的接口,比如推薦部分,這部分三方接口本身就有一些性能壓力,我們這邊在調(diào)用時候 是使用的http來進行調(diào)用的,超時時間是3s,實測發(fā)現(xiàn),當并發(fā)達到10 到20的時候,這部分基本就3s超時了。一部分是他們的推薦規(guī)則確實挺復(fù)雜的,推薦的數(shù)據(jù)要進行達標,要根據(jù)用戶的個個業(yè)務(wù)維度去進行篩選,雖然數(shù)據(jù)已經(jīng)提前寫入緩存了,調(diào)用里面業(yè)務(wù)有些復(fù)雜 導(dǎo)致耗時比較嚴重,特別是并發(fā)稍微高的情況下。于是在調(diào)用他們這些推薦數(shù)據(jù)的地方 加了sentinel 的限流。

              加sentinel限流的時候,一般有兩種方式,一種是針對指定的資源,比如接口、指定的bean等進行限流和降級,還有一種是sentinel + openfeign 來進行限流和降級。兩種方式的效果是類似的,只是配置略微有一些區(qū)別,另外第一種方式是有限制的,即限制的資源需要是spring 容器里面的bean對象,否則限流和降級不會生效。我使用的是第一種方式,代碼如下

       

          @Override
          @SentinelResource(value = "queryRecommend", fallback = "getStaticData",blockHandler = "blockExceptionHandler")
          public List<String> queryRecommend(HomeRecommendStreamQuery query, HomeRecommendStreamDto streamDto, String sourceApp) {
              //用戶身份
              StopWatch st = StopWatch.create("內(nèi)容中臺推薦數(shù)據(jù)");
              String userType = this.getUserType(query.getIdentity(), query.getSource(), query.getUserId());
              //獲取當前有用戶是否推薦用戶信息
              boolean isRecommend = true;
              if (StringUtils.isNotEmpty(query.getUserId())) {
                  Set<String> userClosed = gwmRedisTemplate.opsForValue().get(USER_CLOSED_RECOMMEND);
                  if (CollectionUtils.isNotEmpty(userClosed)) {
                      boolean anyMatch = userClosed.stream().anyMatch(str -> str.equals(query.getUserId()));
                      if (anyMatch) {
                          isRecommend = false;
                      }
                  }
              }
      
              //構(gòu)建推薦系統(tǒng)對象信息
              RecommendQuery build = RecommendQuery.builder()
                      .userType(userType)
                      //推薦系統(tǒng)需要唯一標識,如果未登錄用戶傳設(shè)備id,登錄用戶傳userID
                      .userId(StringUtils.isNotEmpty(query.getUserId()) ? query.getUserId() : query.getDeviceId())
                      .intendedBrandList(query.getIntendedBrandList())
                      .intendedVehicleList(query.getIntendedVehicleList())
                      .recommendationSwitch(isRecommend)
                      .strategyType(query.getStrategyType())
                      .deviceId(query.getDeviceId())
                      .page(query.getPage())
                      .pageSize(query.getSize())
                      .build();
      
              st.start("推薦調(diào)用uc黑名單接口");
              if (StringUtils.isNotEmpty(query.getUserId())) {
                  CmsAppUserIdQuery qry = new CmsAppUserIdQuery();
                  qry.setUserId(query.getUserId());
                  List<String> list = feignUserCenterClient.getBlackUserIds(qry, sourceApp).getData();
                  if(query.isCheckBlack()){
                      build.setBlacklistUsers(list);
                  }
                  query.setBlacklistUsers(list);
              }
              st.stop();
              // 調(diào)用內(nèi)容中臺推薦接口
              st.start("調(diào)用內(nèi)容中臺推薦接口");
              String recommend = okhttp3Util.postBody(config.getUrl(), build);
              st.stop();
              logger.info("首頁推薦查詢推薦系統(tǒng)入?yún)ⅲ簕},推薦系統(tǒng)返回:{} , ssoId:{}", JSONObject.toJSONString(build), recommend, query.getUserId());
              RecommendResponse<RecommendStrategyDto> response = parseBody(recommend, RecommendStrategyDto.class);
              List<String> idList = null;
              if (Objects.nonNull(response)) {
                  if (response.isSuccessful()) {
                      RecommendStrategyDto recommendStrategyDto = response.getData();
                      streamDto.setStrategyType(recommendStrategyDto.getStrategyType());
                      idList = recommendStrategyDto.getContentIds();
                  } else if (!response.recommendChannelNormal()) {
                      streamDto.setRecommendChannelAvailable(false);
                      return new ArrayList<>();
                  }
              }
              //查詢推薦系統(tǒng)數(shù)據(jù)為空
              if (CollectionUtils.isEmpty(idList) && query.getPage() == 1) {
                  //nacos兜底,取默認配置數(shù)據(jù)
                  idList = Optional.ofNullable(config.getPostIds()).orElse(Collections.emptyList());
              }
      
              return idList;
          }
          public List<String> getStaticData(HomeRecommendStreamQuery query, HomeRecommendStreamDto streamDto, String sourceApp) {
              logger.info("==========start sentinel 降級策略==========");
              try {
                  Random random = new Random();
                  int r = random.nextInt(100) + 1;
                  logger.debug("========隨機從緩存獲取一組帖子id:" + r);
                  Set<String> stringSet = gwmRedisTemplate.opsForValue().get(RedisConstants.THREAD_HOT_SCORE_KEY + r);
      
                  logger.debug("=========隨機從緩存獲取帖子result=======" + JSONObject.toJSON(stringSet));
                  if (CollectionUtils.isNotEmpty(stringSet)) {
                      List<String> stringList = new ArrayList<>(stringSet);
      
                      return stringList;
                  } else {
                      logger.debug("============讀取默認配置數(shù)據(jù)========" + config.getPostIds());
                      return Optional.ofNullable(config.getPostIds()).orElse(Collections.emptyList());
                  }
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
          }
      
          /**
           * blockHandler需要設(shè)置為static
           *
           * @param ex
           * @return
           */
          public  List<String> blockExceptionHandler(HomeRecommendStreamQuery query, HomeRecommendStreamDto streamDto, String sourceApp,BlockException ex) {
              try {
                  Random random = new Random();
                  int r = random.nextInt(100) + 1;
                  logger.debug("========隨機從緩存獲取一組帖子id:" + r);
                  Set<String> stringSet = gwmRedisTemplate.opsForValue().get(RedisConstants.THREAD_HOT_SCORE_KEY + r);
      
                  logger.debug("=========隨機從緩存獲取帖子result=======" + JSONObject.toJSON(stringSet));
                  if (CollectionUtils.isNotEmpty(stringSet)) {
                      List<String> stringList = new ArrayList<>(stringSet);
      
                      return stringList;
                  } else {
                      logger.debug("============讀取默認配置數(shù)據(jù)========" + config.getPostIds());
                      return Optional.ofNullable(config.getPostIds()).orElse(Collections.emptyList());
                  }
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
          }

            總結(jié)起來就是:針對首頁的性能問題,優(yōu)化了gc的方式,feign調(diào)用的改造,部分代碼改為并行、日志優(yōu)化 、es的查詢優(yōu)化以及使用sentinel做限流降級。 當然還有就是,我們這個首頁這么多的內(nèi)容都寫到了一個接口里面,對于服務(wù)端的性能確實壓力很大,后面看能不能進行拆分,讓端上進行單個接口的請求,由一個接口變?yōu)槎鄠€接口,這樣也能減少不少服務(wù)端的壓力。最后優(yōu)化后的壓測結(jié)果:

       

          目前可優(yōu)化的部分主要是集中在es這部分上面了。

      posted @ 2024-09-19 17:56  Doyourself!  閱讀(84)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 蜜臀av一区二区国产在线| 国产精品白嫩极品在线看| 四虎影视永久在线精品| 妺妺窝人体色www聚色窝仙踪| 一区二区三区AV波多野结衣| 天堂а√在线最新版中文在线| 四虎亚洲国产成人久久精品| 亚洲国产片一区二区三区| 欧美怡春院一区二区三区| 久99久热精品免费视频| 无码人妻一区二区三区在线视频| 亚洲熟妇熟女久久精品综合 | 国内精品久久久久影视| 久久九九久精品国产免费直播| 五月国产综合视频在线观看| 国产无套护士在线观看| 亚洲综合在线一区二区三区| 春菜花亚洲一区二区三区| 国产 麻豆 日韩 欧美 久久| 亚洲av日韩av永久无码电影| 亚洲一区二区三区四区| 欧美人禽zozo动人物杂交| 国产精品视频亚洲二区| 国产成人欧美综合在线影院| 四虎国产精品成人免费久久| 久久伊99综合婷婷久久伊| 九九热在线观看精品视频| 99精品国产精品一区二区| 亚洲Av综合日韩精品久久久| 国产成人精品永久免费视频| 大地资源高清免费观看| 亚洲欧美日韩综合一区二区| 四虎成人精品永久网站| 翘臀少妇被扒开屁股日出水爆乳| 一区二区三区放荡人妻| 亚洲一区二区三区自拍偷拍| 亚洲人成电影在线天堂色| 国产精品久久人妻无码网站一区| 亚洲欧洲日产国码无码网站| 国产精品爽爽va在线观看网站| 亚洲成人av免费一区|