1. 程式人生 > >QQ音樂高階工程師袁聰:大膽嘗試,展現不一樣的React Native

QQ音樂高階工程師袁聰:大膽嘗試,展現不一樣的React Native

責編:陳秋歌,關注前端開發領域,尋求報道或者投稿請發郵件chenqg#csdn.net。
歡迎加入“CSDN前端開發者”微信群,參與熱點、難點技術交流。請加群主微信「Rachel_qg」,申請入群,務必註明「公司+職位」。另可申請加入CSDN前端開發QQ群:465281214。

SDCC 2016 中國軟體開發者大會將於11月18日在北京京都信苑飯店召開,本屆大會彙集100+講師,設定了12大專題論壇。本文作者QQ音樂&全民K歌高階工程師袁聰,已受邀擔任SDCC 2016前端開發專題演講嘉賓,分享全民K歌React Native最佳實踐。

以下內容為會前袁聰給大家帶來的營養小餐,技術大餐即將在

SDCC 2016軟體開發者大會上呈現。

React Native(以下用RN簡稱代替)的開源是App開發的一個里程碑式的事件,它為App開發提供了新的開發模式和思路。RN既有傳統Hybrid框架的優勢,又提供了統一的Native體驗,吸引著越來越多的專案去實踐。

全民K歌去年十月份完成了RN的接入實踐,至此已經有一年多了。現在的我們已經不再滿足簡單的接入和優化,我們開始大膽嘗試,做一點不一樣的RN,今天我們來就聊一聊全民K歌不一樣的RN。

這裡先給大家上兩份小菜,

P.S. RN的程式碼已經被分析來分析去太多遍了,相關資料很多,為避免冗長枯燥,本文只提示基於0.35版本的關鍵程式碼。

Bundle拆分——業務分包

為什麼要分包?

  • 避免執行大量JS程式碼帶來的效能瓶頸
  • 減少更新時的流量消耗
  • 業務分離,按需載入

關於效能瓶頸,我們先來看一張圖:

圖片描述

JS Init + Require的時間在整個RN的啟動過程中佔了約一半,隨著業務的增多以及複雜度的增加,這個比例還會上升。

流量消耗一直是使用者關注的重點,使用者關注的重點就是我們關注的重中之重,RN基礎的Bundle有1.5M,即使壓縮後也有300多K,業務Bundle的更新,往往只有幾K或者幾十K,帶上基礎Bundle這個大尾巴不太合適,分包可以為使用者節省不必要的流量消耗,而且在複雜的外網環境下,包越小越有利於更新成功率的提高。

業務分離,按需載入,非純RN的應用往往入口在不同的地方,不必一股腦的把所有業務模組一起載入,分包載入可以提升載入速度以及減少不必要的資源浪費。

RN自己提供了一個unbundle的方案,Android端把除了polyfill和startup部分之外的所有模組拆成大量的單獨js檔案,在引用到js檔案的時候通過NativeRequire方法去載入對應的js檔案,大量頻繁的I/O,Android表示已哭暈。。。而iOS端則是在頭部新增一堆index table用來做索引,反而會增大檔案。可能是還不成熟的原因,RN還沒有正式公開這個方案。而且這個方案不能區分業務,不能滿足我們的需求。

那麼全民K歌如何去實踐根據業務分包的?來看下前端同學馬鋮(Calvinma)的方案:

首先我們看到RN自帶的react-packager打包原始碼比較輕量簡潔,保持輕量和對RN特性關注也是RN不使用webpack和broswerify而是自己實現打包的原因。因此我們的方案主要是通過增強 react-packager的打包原始碼來實現分包(不造輪子)。

RN的打包結果是一套類似CommonJS的輕量require/define模組系統。要做到上面的檔案分離和不重複打包,就是要做到依賴引用(業務包去require基礎包中的模組), 因此我們要把基礎包中包含的模組列表匯出來給業務包打包時使用,類似webapc中DllPlugin的實現。

所以分包的主要邏輯如下:

打包(Compile time):

用一個base.js做Entry(包含認為是基礎庫的內容,比如RN核心、元件、React)打包出基礎包,並匯出包含的模組列表
根據基礎包模組列表打包業務包,過濾業務包的依賴:去掉重複的模組,require 時使用列表裡的模組id

客戶端執行(Run time):

適當的時機在JSContext執行基礎包(比如App launch ready或者使用者進入某些場景、下一步可能開啟RN時)
使用者進入RN的部分,執行業務包,然後runApplication,適當的機制從 CDN 拉取更新業務包。我們學習一下webpack,把這份模組列表稱為manifest 。

一圖概括之:

圖片描述

這個方案的主要優點在於易施行,在本地打包時做文章,分離的包可以分開前後執行(不需要再在終端做合併)
分包靈活,自己通過Entry去控制基礎包裡要有什麼。不用單獨再寫構建
降低客戶端工作量,不用引入複雜的Diff和合並,只要實現可分開執行(runJSInContext和runApplication分離)就可以,RN的原始碼裡都有介面。

舉個栗子:

拿官方Hello World來演示下,先把所有公開的元件和API打到個基礎包裡:

base.android.js
圖片描述

輸出來的manifest.json大概長這個樣子:
圖片描述

再帶上這份manifest來打包原版的Hello World,最終生成的檔案如下:
圖片描述

surprise,業務Bundle就是這麼小。是不是等不及來看看怎麼實現的了?

下面分析一下分包涉及的原始碼修改和實現(原始碼根據0.35版本):

首先我們在local-cli添加了兩個引數:

–manifest-output: 打包時把bundle包含的模組匯出生成為一個manifestFile(打基礎包)

–manifest-file : 打包時傳入指定的manifestFile進行過濾(打業務包)

local-cli/bundle/bundleCommandLineArgs.js
bundleCommandLineArgs

在打包生成時(拿到所有依賴模組列表後),如果有傳入manifestOutput,把模組列表按固定格式輸出一份JSON檔案(模組名: 模組id)

local-cli/bundle/output/bundle.js
bundle

packager/react-packager/src/Bundler/Bundle.js
bundle1

在依賴解析中require模組時,如果有傳入manifestFile,require 已有的模組時使用manifestFile中對應的id。比較方便就是在getModuleId()中做一個處理。

packager/react-packager/src/Bundler/index.js
圖片描述

最後在打包前,如果有傳入manifestFile,從resolutionResponse按照其過濾掉已存在的模組。

packager/react-packager/src/Bundler/index.js
圖片描述

主要的原始碼涉及就是Bundler和Bundle部分,其次還有cli的新增,過濾掉polyfill的重複插入等。

需要注意的問題:

id重複:

目前的RN打包模組使用遞增數字id,分開打包時id就會重複。我的做法是把基礎包的最大id輸出來接著遞增,或者多個業務包時給id加字首(–id-prefix)。增強方案也可學習webpack使用filename hash做id。

好了,前端同學已經幫我們拆出來的多個Bundle,那麼客戶端怎麼去載入呢?

先來看下一個簡單的JavaScript VM模型:

VM

JSContext是JavaScript執行的上下文,由GlobalObject管理。我們在同一個GlobalObject對應的同一個JSContext中執行JavaScript程式碼,執行一個和執行多個JavaScript是沒有區別的,所以上面多個包的載入完全沒有問題。

這裡不贅述RN的Bundle載入流程,最終用到了JSC的一個API——JSEvaluateScript去執行Bundle,我們只需要針對API封裝,提供一個方法給上層呼叫即可,當然注意執行順序,在基礎Bundle載入完再去執行業務Bundle,iOS和Android實現基本類似。

Native元件動態載入

聽說RN支援隨時發版,這麼好?先來一斤需求!

一開始聽到這種提問,第一反應是去解釋,RN提供了基礎的Native元件(這裡我們把Native Module和封裝的View統稱為Native元件),RN的動態釋出是基於已有的Native元件的動態釋出,我們可以根據業務的需要去擴充套件Native元件,但是不可能在一個版本里面把所有可能用到的Native元件都封裝好。後來原始碼讀得多了,也開始思考,是不是真的可以隨時無限制的去做需求?如何在不更新版本的基礎上如何讓RN真正的實現需求快速迭代開發,放飛產品同學們的想象力呢?

動態新增Native元件並將其動態載入。

動態新增Native元件,這對於iOS來說可能有點難度,但是對於Android,外掛和熱補丁大家都玩了這麼多年了,這只是個小問題,很容易解決。

那麼如何去動態載入Native元件呢?有幾種方法可以實現,

  • 方法一:構建一個通用的代理NativeModule,傳遞需要執行的類名、方法名以及引數列表,通過反射去呼叫;
  • 方法二:去了解NativeModule的註冊和呼叫流程,動態向其中新增新的Native元件。

方法一簡單易於實現,但是需要自己維護物件和構建引數,同時對JS非透明,應用Native版本升級時若想合入則需要多套JS程式碼對應於不同的呼叫方式,版本控制比較麻煩。

方法二實現難度稍大,沿用RN的設計,對JS透明,應用Native版本升級可以隨之合併進最新APK中。

鑑於以上原因,我們更傾向於方法二。下面就讓我們來看看RN是如何完成Native Module的註冊以及JS如何去呼叫Native Module的(這裡不帶大家走一遍完整的啟動流程和Native、JS互調的原始碼邏輯了,大家可以在網上搜到很多這類的文章,已經被解釋得都很詳盡)。

1.JNI層中ModuleRegistryHolder的建構函式中傳入了Java層用JavaModuleWrapper封裝過的NativeModule列表,將其構造JavaNativeModule容器物件傳入ModuleRegistry的建構函式中,儲存在其成員變數modules_中。

//ModuleRegistryHolder.cpp

ModuleRegistryHolder::ModuleRegistryHolder(
    CatalystInstanceImpl* catalystInstanceImpl,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
    jni::alias_ref<jni::JCollection<CxxModuleWrapper::javaobject>::javaobject> cxxModules) {
  std::vector<std::unique_ptr<NativeModule>> modules;
  ...
  for (const auto& jm : *javaModules) {
    modules.emplace_back(folly::make_unique<JavaNativeModule>(jm));
  }
  ...

  registry_ = std::make_shared<ModuleRegistry>(std::move(modules));
}
//ModuleRegistry.cpp

ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules)
    : modules_(std::move(modules)) {}

這裡需要留意的是ModuleRegistry中的moduleNames()函式,這是一個關鍵的方法,返回值是所有NativeModule方法的名字,這在後面向JS中註冊NativeModule時舉足輕重。

//ModuleRegistry.cpp

std::vector<std::string> ModuleRegistry::moduleNames() {

  std::vector<std::string> names;
  for (size_t i = 0; i < modules_.size(); i++) {
    std::string name = normalizeName(modules_[i]->getName());
    modulesByName_[name] = i;
    names.push_back(std::move(name));
  }
  return names;
}

2.initializeBridge是一個複雜的過程,它構建了Native和JS相互呼叫的通道。Native通過NativeToJsBridge中呼叫JSC的API去執行JS方法,而JS則呼叫在JavaScript VM中註冊的JNI方法,通過JsToNativeBridge去執行Native方法。這裡我們要去JSCExecutor的建構函式裡面去看看NativeModule註冊的相關邏輯。

//JSCExecutor.cpp

JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
                         std::shared_ptr<MessageQueueThread> messageQueueThread,
                         const std::string& cacheDir,
                         const folly::dynamic& jscConfig) throw(JSException) :
    m_delegate(delegate),
    m_deviceCacheDir(cacheDir),
    m_messageQueueThread(messageQueueThread),
    m_jscConfig(jscConfig) {
  initOnJSVMThread();

  SystraceSection s("setBatchedBridgeConfig");

  folly::dynamic nativeModuleConfig = folly::dynamic::array();

  {
    SystraceSection s("collectNativeModuleNames");
    std::vector<std::string> names = delegate->moduleNames();
    for (auto& name : delegate->moduleNames()) {
      nativeModuleConfig.push_back(folly::dynamic::array(std::move(name)));
    }
  }

  folly::dynamic config =
    folly::dynamic::object
      ("remoteModuleConfig", std::move(nativeModuleConfig));

  SystraceSection t("setGlobalVariable");
  setGlobalVariable(
    "__fbBatchedBridgeConfig",
    folly::make_unique<JSBigStdString>(detail::toStdString(folly::toJson(config))));
}

initOnJSVMThread()函式去建立了JavaScript VM、JSContext、Global物件以及向其中註冊了JNI方法。

在initOnJSVMThread()之後,出現了一個我們熟悉的方法moduleNames(),這裡拿到所有的NativeModule的名字之後,構建JSON,賦值給Global中的__fbBatchedBridgeConfig物件,完成了NativeModule向JS的初步註冊。

3.JS中新建MessageQueue物件,通過在JSCExecutor寫入的__fbBatchedBridgeConfig物件去構建remoteModules,這樣就把NativeModule對映到JS中了,但這還遠遠沒有結束。

 //BatchedBridge.js

const BatchedBridge = new MessageQueue(() => global.__fbBatchedBridgeConfig);
//MessageQueue.js

type Config = {
  remoteModuleConfig: Object,
};

class MessageQueue {
  constructor(configProvider: () => Config) {
    ...
    lazyProperty(this, 'RemoteModules', () => {
      const {remoteModuleConfig} = configProvider();
      const modulesConfig = remoteModuleConfig;
      return this._genModules(modulesConfig);
    });
  }
  ...
  }

function lazyProperty(target: Object, name: string, f: () => any) {
  Object.defineProperty(target, name, {
    configurable: true,
    enumerable: true,
    get() {
      const value = f();
      Object.defineProperty(target, name, {
        configurable: true,
        enumerable: true,
        writeable: true,
        value: value,
      });
      return value;
    }
  });
}

_genModules(remoteModules) {
    const modules = {};
    remoteModules.forEach((config, moduleID) => {
      // Initially this config will only contain the module name when running in JSC. The actual
      // configuration of the module will be lazily loaded (see NativeModules.js) and updated
      // through processModuleConfig.
      const info = this._genModule(config, moduleID);
      if (info) {
        modules[info.name] = info.module;
      }
      ...
    });
    return modules;
  }

4.NativeModules是不是很熟悉?JS就是通過NativeModules.類名.方法名去呼叫NativeModule的。這裡通過去remoteModules中查詢NativeModule並且呼叫JNI方法去獲取到NativeModule的方法等資訊,完成構建NativeModules物件。

//NativeModules.js

const NativeModules = {};
Object.keys(RemoteModules).forEach((moduleName) => {
  Object.defineProperty(NativeModules, moduleName, {
    configurable: true,
    enumerable: true,
    get: () => {
      let module = RemoteModules[moduleName];
      if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) {
        const config = global.nativeRequireModuleConfig(moduleName);
        module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
        RemoteModules[moduleName] = module;
      }
      Object.defineProperty(NativeModules, moduleName, {
        configurable: true,
        enumerable: true,
        value: module,
      });
      return module;
    },
  });
});

看到這裡想必大家都已經明白了,想要去動態載入NativeModule,只需要:

  • 1)在ModuleRegistry的modules_變數中增加新的NativeModule
  • 2)呼叫moduleNames更新modulesByName_同時得到moduleID
  • 3)執行程式碼向JS中MessageQueue物件的RemoteModules中新增新的NativeModule描述
  • 4)執行程式碼向JS中的NativeModules物件新增新的NativeModule描述

主要程式碼:

在NativeModules.js中為global添加了新的function,以便在JNI中呼叫該方法去動態向NativeModules物件中新增新的NativeModule描述:

//NativeModules.js

global.__UPDATE_NATIVE_MODULE__=function(moduleName){
Object.defineProperty(NativeModules,moduleName,{
configurable:true,
enumerable:true,
get:function get(){
var module=RemoteModules[moduleName];
if(module&&typeof module.moduleID==='number'&&global.nativeRequireModuleConfig){
var config=global.nativeRequireModuleConfig(moduleName);
module=config&&BatchedBridge.processModuleConfig(config,module.moduleID);
RemoteModules[moduleName]=module;
}
Object.defineProperty(NativeModules,moduleName,{
configurable:true,
enumerable:true,
value:module});

return module;
}});

};

在JSCExecutor中增加函式,向JS中的RemoteModules新增元素以及執行JS方法去更新NativeModules,此處要注意在JS執行緒中去操作:

//JSCExecutor.cpp

void JSCExecutor::addNativeModuleDynamically(){
          std::vector<std::string> names = m_delegate->moduleNames();
          int index = (int)names.size() - 1;
          const char* moduleName = names[index].c_str();
          m_messageQueueThread->runOnQueue([this, moduleName, index] () {
              auto global = Object::getGlobalObject(m_context);
              auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
              auto batchedBridge = batchedBridgeValue.asObject();
              auto remote = batchedBridge.getProperty("RemoteModules").asObject();
              auto empty = JSObjectMake(m_context, NULL, NULL);
              JSObjectSetProperty(m_context, empty, String("moduleID"),
                                  JSValueMakeNumber(m_context, index), 0, NULL);
              remote.setProperty(moduleName, Value(m_context, empty));
              auto nativemodules = global.getProperty("__UPDATE_NATIVE_MODULE__").asObject();
              nativemodules.callAsFunction(
                      {Value(m_context, JSStringCreateWithUTF8CString(moduleName))});
          });
        }

以上就是在RN原始碼讀完後的一點思考,一點拙見,在構建更快更易擴充套件的RN應用的實踐。如有不足之處歡迎大家斧正。

相關文章:

目前SDCC 2016前端開發專題的所有演講嘉賓已全部確定,以下為嘉賓名單及演講議題(排名不分先後),詳情請見:SDCC 2016前端開發專題講師、議題大揭底

  • Stackla前端團隊Leader蔣定宇
    • 演講主題:不斷歸零的前端人生
  • QQ音樂&全民K歌高階工程師袁聰
    • 演講主題:全民K歌React Native最佳實踐
  • 餓了麼Node Team負責人黃鼎恆
    • 演講主題:純手工搭建一個高效能實時監控系統
  • 360奇舞團前端工程師鍾恆
    • 演講主題:使用Vue.js 2.0開發高互動Web應用
  • Ruff架構師、JavaScript專家周愛民
    • 演講主題:有前端思想的物聯網系統架構
  • 58到家高階前端工程師周俊鵬
    • 演講主題:基於webpack的前端工程解決方案

想與這些專家現場面對面進行技術探討嗎?目前SDCC 2016大會門票8折銷售中,團購更有優惠,是給辛勤工作一年的你,年終最好的禮物,或許這樣,SDCC才能更真切地服務好開發者。【註冊參會

圖片描述

相關推薦

QQ音樂高階工程師大膽嘗試展現一樣React Native

責編:陳秋歌,關注前端開發領域,尋求報道或者投稿請發郵件chenqg#csdn.net。 歡迎加入“CSDN前端開發者”微信群,參與熱點、難點技術交流。請加群主微信「Rachel_qg」,申請入群,務必註明「公司+職位」。另可申請加入CSDN前端開

5步告訴你QQ音樂的完美音質是怎麼來的播放器的祕密都在這裡

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~ 一、問題背景與分析 不久前,團隊發現其Android平臺App在播放MV視訊《鳳凰花開的路口》時,會帶有如電流聲一般的雜音,這影響了使用者體驗。 研發同學在初步定位時,發現有如下特徵: Android平臺雜音問題必現; iOS、PC平臺能正常播放

2019規劃放下所有輕鬆上陣大膽嘗試勇於實踐經商賺錢考證提高隨時煅煉

2018總結:開拓 創造了良好的開頭,形成了積極的局面。在思想、收入、學習、考證等方面取得了過去10年中前所未有的成就。樹立了 “內心強大、心誠則靈、理論與實踐相結合”的觀念。身體眼睛等問題也不容忽視,要隨時休息。思想意識的麻痺,也導致了大量的時間浪費,要高度警惕。 深入

Java常見算法(一)去重重復

IT OS mage class 叠代 集合 size wid emp (一)去重 1.1 去重復() ①:建一個新的集合temp ②:再建傳入集合的叠代器,調用it.hasNext()。 ③:再用temp.contains("e")方法判斷---->true it.

新百倫領跑助力高考高考路上是一個人在戰鬥!

新百倫領跑高考是一場青春的戰役,考場就是戰場,三年磨一劍,今朝試鋒芒。新百倫領跑陪你,在這一刻拼盡全力,讓浸滿汗水的青春變得圓滿,讓人生不留遺憾。六月來臨,又是一年高考時,又是一年畢業季,不管你是否將參加考高,都應為了明確的目標努力著,不管這段時光多麽艱難,都應堅持相信永不放棄。 你即將經歷人生中,最重要的一

#程式設計師技術厲害上級卻想讓他轉正技術厲害但是尊重人

現在的職場如果你要找工作的話,都要經過試用期,用來檢測新員工是否合適留在公司工作,如果試用期過後能夠正常工作的話,那麼就可以成為公司的正式員工了。 在這裡我推薦下自己整理的資料,我自己是一名從事了5年java開發的全棧工程師,如果有想要學習java的小夥伴,可來我們的java學習扣裙哦:

博士程式設計師面試阿里才給P6年薪45萬網友別去上去的了

一名博士在網際網路論壇吐槽自己博士畢業工作一年了,阿里給p6是什麼意思,說什麼工資已經是p7的工資了,做資料要不要去?很顯然,這名博士對於自己的職級並不滿意,對於45萬左右年薪也不甚滿意。 如果有正在學java的程式設計師,可來我們的java技術學習扣qun哦:72340,3928,小編花了近一個月

管理感悟技術好服從管理的員工怎麼辦

  相信很多主管都遇到過,技術好又不服眾管理的員工。這讓主管很頭痛:管了吧,惹著他走人有點捨不得;不管吧,這工作沒法開展。怎麼辦?   首先,區分是暫時的,還是持續的。為什麼呢?因為人工作中經常發生爭吵,難免會影響情緒。於是一時不服從工作安排也是賭氣。這是常態,不要奇怪

#程式設計師吐槽騰訊Java技術落後騰訊員工業餘cpicpp瞭解下?

中國網際網路行業發展是有目共睹的,而能取得如此之飛速的發展,網際網路開發的技術方面肯定毋庸置疑的。而BAT作為中國網際網路行業的三巨頭,不僅僅是在國內,在國外的知名度也是非常高的,相應的網際網路開發技術也應該排在國內前列。可是近日,卻有一位程式設計師網友抱怨三巨頭之一的騰訊Java技術太落後,著實驚

解決Command `npm install --save --save-exact react-native` failed.

解決:Command ‘npm install –save –save-exact react-native’ failed. 在Mac上使用 react-native init ProjectName 命令建立一個React_native專案是出現:Comm

大資料與個人隱私的平衡懂你認識你

【資料猿導讀】Facebook 洩露5000萬用戶資料的事情餘波未消,李彥巨集一句“中國人願意用

2019年春節企業郵箱實用攻略安心過年郵件錯過

負責 種類型 exc 輕松 在線打開 公司 企業郵箱 更多 未來 春節臨近,各種熱騰騰的“春節攻略”紛紛出爐,節日的氣氛愈發濃烈。小編為此也準備了TOM企業郵箱春節實用小攻略,為節日助力。無論是回家還是出遊,假期中合理的運用好企業郵箱,不僅可以安心過節,也利於年後的工作開展

十四GDI+ 繪製視窗 感受一樣的神奇

第一:在GDI.rc匯入PNG圖片s 第二:在stdafx.h中加上下列GDI的標頭檔案 #include "c:/GDIPlus/includes/gdiplus.h"   ////請修改為你的標頭檔案路徑using namespace Gdiplus;  #pragma

music-api-next一款支持網易、xiami和QQ音樂的JS爬蟲庫

dbm 穩定 javascrip earch arch github 服務器 ole http 音樂,無界 讓音樂無界 如果你苦於挑選一個全方位、多平臺、簡便易用的音樂爬蟲庫,music-api-next是不二選擇。 特性: 支持網易、蝦米和QQ三大主流音樂平臺 支持

爬蟲下載QQ音樂獲取所有歌手-每個歌手的專輯-每個專輯裡的歌曲

# coding=utf-8 # !/usr/bin/env python ''' author: dangxusheng desc : 稍微有點難度,需要多次請求獲取key date : 2018-08-29 ''' # 匯入模組 import requests from

阿里雲高階工程師分享高德地圖基於阿里雲MaxCompute的實戰效果

阿里辦公室 雲端計算帶來的變革不言而喻,作為一種新型的IT交付模式,切實為企業節省IT成本、加快IT與企業業務結合效率、提升創新能力、加強管理水平以及增強系統本身的靠性等方面提供巨大支援,是企業實現新發展的重要途徑,它已然成為全球IT產業的主流聲音。 為此,CSDN記者日前採訪了國內最早一批

music-api-next一款支援網易、蝦米和QQ音樂的JS爬蟲庫

音樂,無界 讓音樂無界 如果你苦於挑選一個全方位、多平臺、簡便易用的音樂爬蟲庫,music-api-next是不二選擇。 特性: 支援網易、蝦米和QQ三大主流音樂平臺 支援音樂關鍵詞搜尋 支援音樂

music-api-next一款支援網易、xiami和QQ音樂的JS爬蟲庫

音樂,無界 讓音樂無界 如果你苦於挑選一個全方位、多平臺、簡便易用的音樂爬蟲庫,music-api-next是不二選擇。 特性: 支援網易、蝦米和QQ三大主流音樂平臺 支援音樂關鍵詞搜尋 支援音樂連結下載 支援音樂評論爬取 支援回撥和async/await寫法 支援webpack打包部署 支援pm2伺服

免費大資料採集軟體后羿採集器採集QQ音樂播單資料教程

本文主要介紹如何使用后羿採集器的智慧模式,免費採集QQ音樂播單資料的播單名、播單鏈接、播放量、歌曲、歌手等資訊。 採集工具簡介: 后羿採集器是一款基於人工智慧技術的網頁採集器,只需要輸入網址就能夠自動識別網頁資料,無需配置即可完成資料採集,是業內首家支援三種作業

騰訊雲高階工程師MySQL核心深度優化這樣定製會更好

早期的CDB主要基於開源的Oracle MySQL分支,側重於優化運維和運營的OSS系統。在騰訊雲,因為使用者數的不斷增加,對CDB for MySQL提出越來越高的要求,騰訊雲CDB團隊針對使用者的需求和業界發展的技術趨勢,對CDB for MySQL分支進行深度的定製優化。優化重點圍繞核心效能、核心功能