1. 程式人生 > >限時購校驗小工具&dubbo異步調用實現限

限時購校驗小工具&dubbo異步調用實現限

供應商 turn executor factor his 配置 htm 代碼 微服務

本文來自網易雲社區

作者:張偉

背景

限時購是網易考拉目前比較常用的促銷形式,但是前期創建一個限時購活動時需要各個BU按照指定的Excel格式進行選品提報,為了保證提報數據準確,運營需要人肉校驗很多信息:

  • 是否已經參加了限時購

  • 在線價與活動價的對比校驗

  • 大促價格校驗

  • 是否有互斥活動

  • 庫存檢查

  • SKU 完整性

  • 價格預警檢查

  • 商品可用性校驗

這麽多人肉的校驗數據來自不同的系統,獲取數據,檢查數據;這是一件很繁瑣且工作量巨大的事情。在這樣的背景下,促銷服務提供了限時購促銷校驗小工具,極大減少了運營人員配置限時購的工作量。

實現

技術分享圖片

上圖是操作界面和校驗結果格式示例,用戶只需要按照模版將數據導入,在導入的時候完成數據基本格式校驗!通過格式校驗後,點擊【校驗】即可完成對數據的各種數據校驗並將校驗數據結果寫到導出文件中,只需要根據到處文件提示進行修改即可。

優化

上文只是對該功能的一個簡單介紹,在具體實現過程中需要調用各種服務,需要從商品基礎服務獲取商品信息,類目數據,供應商信息,SKU規格信息,限購信息包含,倉庫信息等! 該工具上線初期,雖然功能得到了需求方的認可,但是性能問題比較突出;卡頓比較嚴重!為了給運營提供一個更好用的工具,通過優化代碼結構,將代碼中涉及到循環處理的地方都改為批量調用!此時性能有了一個明顯的改善,但是校驗數據量比較大的情況下還是會存在一定的性能問題,通過分析發現是因為調用外部系統耗時較大,此時通過dubbo異步並發調用的優化方式進行優化,使得該工具性能得到了極大提升。

dubbo 異步工具

在工作中總會有一些服務響應時間會和請求參數中的數量正相關,或者服務提供者明確限制了請求參數的大小,此時該如何進行優化呢? 在很多代碼中都可以發現這樣都寫法

for(x:y){
    xxxService.xxx(p);
}

使用循環同步的調用方式進行處理,這樣該方法的響應時間就會隨著請求參數的個數增長而進行增長。有沒有更好的方式呢?下面就介紹一下使用dubbo異步的方式完成對該方法的優化

使用xml配置完成異步調用

       基於 NIO 的非阻塞實現並行調用,客戶端不需要啟動多線程即可完成並行調用多個遠程服務,相對多線程開銷較小。

技術分享圖片

在 consumer.xml 中配置:

<dubbo:reference id="fooService" interface="com.alibaba.foo.FooService">
      <dubbo:method name="findFoo" async="true" /></dubbo:reference><dubbo:reference id="barService" interface="com.alibaba.bar.BarService">
      <dubbo:method name="findBar" async="true" /></dubbo:reference>

調用代碼:

// 此調用會立即返回nullfooService.findFoo(fooId);// 拿到調用的Future引用,當結果返回後,會被通知和設置到此FutureFuture<Foo> fooFuture = RpcContext.getContext().getFuture(); 

// 此調用會立即返回nullbarService.findBar(barId);// 拿到調用的Future引用,當結果返回後,會被通知和設置到此FutureFuture<Bar> barFuture = RpcContext.getContext().getFuture(); 

// 此時findFoo和findBar的請求同時在執行,客戶端不需要啟動多線程來支持並行,而是借助NIO的非阻塞完成// 如果foo已返回,直接拿到返回值,否則線程wait住,等待foo返回後,線程會被notify喚醒Foo foo = fooFuture.get(); 
// 同理等待bar返回Bar bar = barFuture.get(); 

// 如果foo需要5秒返回,bar需要6秒返回,實際只需等6秒,即可獲取到foo和bar,進行接下來的處理。

你也可以設置是否等待消息發出:

  • sent="true" 等待消息發出,消息發送失敗將拋出異常。

  • sent="false" 不等待消息發出,將消息放入 IO 隊列,即刻返回。

    <dubbo:method name="findFoo" async="true" sent="true" />

    如果你只是想異步,完全忽略返回值,可以配置 return="false",以減少 Future 對象的創建和管理成本:

    <dubbo:method name="findFoo" async="true" return="false" />

    關於使用xml配置完成異步的缺點

    如果在xml中配置會導致工程中所有引用者都使用了異步方法,修改成本較高

    dubbo異步調用工具

    通過編碼方式實現dubbo的異步調用,通過使用RpcContext.getContext().asyncCall()完成異步調用,考慮到促銷這邊需要將分頁大小到最小值限制為200,在促銷優化中使用該方式進行優化,效果明顯

    /**
    * Desc:Dubbo異步調用輔助工具類
    * 
    * @author wei.zw
    * @since 2017年7月14日 下午4:36:08
    * @version v 0.1
    */public class RpcAsyncUtil {  private static final Logger logger = LoggerFactory.getLogger(RpcAsyncUtil.class);  /**
       * dubbo異步調用,將遠程調用結果組合後返回;本次耗時取決於最大的耗時
       * 
       * @param paramList
       *            需要進行遠程調用所有參數列表長度
       * @param pageSize
       *            一次遠程調用使用的列表最大長度
       * @param syncCallable
       *            具體dubbo調用
       * @return
       * @author wei.zw
       */
      public static <T, R> List<R> async(List<T> paramList, int pageSize, final SyncCallable<T, R> syncCallable) {      if (pageSize < 200) {
              pageSize = 200;
          }
          List<List<T>> subList = ListUtils.subList(paramList, pageSize);
          List<Future<List<R>>> futures = new ArrayList<>();      for (final List<T> sub : subList) {
              futures.add(RpcContext.getContext().asyncCall(new Callable<List<R>>() {              @Override
                  public List<R> call()
                          throws Exception {                  return syncCallable.call(sub);
                  }
              }));
          }
          List<R> result = new ArrayList<>();      try {          for (Future<List<R>> future : futures) {
    
                  List<R> list = future.get();              if (CollectionUtils.isNotEmpty(list)) {
                      result.addAll(list);
                  }
              }
          } catch (Exception e) {
              logger.warn(ToStringBuilder.reflectionToString(syncCallable) + ",調用異常", e);          throw new RpcException(e);
          }      return result;
    
      }  public static interface SyncCallable<T, R> {      /**
           * 該方法中只能是一個遠程調用,不能使用其他方法
           * 
           * @param params
           * @return
           * @author wei.zw
           */
          public List<R> call(List<T> params);
      }  public static interface SyncMapCallable<T,K, R> {      /**
           * 該方法中只能是一個遠程調用,不能使用其他方法
           * 
           * @param params
           * @return
           * @author wei.zw
           */
          public Map<K, R> call(List<T> params);
      }
    }
  • 使用示例: 技術分享圖片

Mybatis批處理

技術分享圖片 技術分享圖片


final SqlSession session= getGenericSqlSessionFactory().openSession(ExecutorType.BATCH);for( ){
session.update();
}
session.commit();

PS:關於事務問題 Mybatis與Spring集成時,如果外面存在事務,則獲取到的connection是同一個


  public SqlSession openSession(ExecutorType execType) {    return openSessionFromDataSource(execType, null, false);
  }private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;    try {      final Environment environment = configuration.getEnvironment();      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);      //從environment中獲取dataSource,並根據dataSource創建事務
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);      final Executor executor = configuration.newExecutor(tx, execType);      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }public SpringManagedTransaction(DataSource dataSource) {
    notNull(dataSource, "No DataSource specified");    this.dataSource = dataSource;
  }  private void openConnection() throws SQLException {    //從dataSouce中獲取connection
    this.connection = DataSourceUtils.getConnection(this.dataSource);    this.autoCommit = this.connection.getAutoCommit();    this.isConnectionTransactional = isConnectionTransactional(this.connection, this.dataSource);    if (this.logger.isDebugEnabled()) {      this.logger.debug(          "JDBC Connection ["
              + this.connection
              + "] will"
              + (this.isConnectionTransactional ? " " : " not ")
              + "be managed by Spring");
    }
  }

最佳實踐

這樣在需要進行批處理的使用只需要調用基類中的方法,而且不需要關註事務問題,即使在方法內部調用也存在事務問題。

技術分享圖片



網易雲大禮包:https://www.163yun.com/gift

本文來自網易實踐者社區,經作者張偉授權發布。

相關文章:
【推薦】 微服務監控探索
【推薦】 網易易盾驗證碼的安全策略

限時購校驗小工具&dubbo異步調用實現限