限時購校驗小工具&dubbo異步調用實現限
本文來自網易雲社區
作者:張偉
背景
限時購是網易考拉目前比較常用的促銷形式,但是前期創建一個限時購活動時需要各個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異步調用實現限