這些年,我工作上走過的路

常常的,我會(huì)感恩于我所在的公司,讓自己經(jīng)歷了種種。很多情況下,公司的成長(zhǎng)帶動(dòng)了自己的成長(zhǎng)。完整的跟著公司經(jīng)歷大數(shù)據(jù)業(yè)務(wù)從無(wú)到有,從有到精,這種歷程就像美味的食物,吃過了才能真正感覺,而文字是不足以表達(dá)的。

我走過了畢業(yè)季,創(chuàng)業(yè)征途,踏進(jìn)開源之路,轉(zhuǎn)型進(jìn)入大數(shù)據(jù),到最后有緣接觸機(jī)器學(xué)習(xí)。每個(gè)章節(jié),我都會(huì)提及對(duì)應(yīng)那個(gè)階段對(duì)技術(shù)的感悟,自己做的一些具體事情。

到寫這篇文章為止,我發(fā)現(xiàn)經(jīng)過這八年,我依然沒有懈怠下來(lái),我依然在狂奔。

畢業(yè)季

2008年12月,清晨的陽(yáng)光有氣無(wú)力的鋪在了一片沒有經(jīng)過打理的草地上。有了陽(yáng)光,沒了打理,自然成了雜草兒們的天堂,千奇百怪任性長(zhǎng)著。陽(yáng)光順著草地,扶著墻翻過窗子,躍進(jìn)了陽(yáng)臺(tái)。陽(yáng)臺(tái)左邊是洗手間,前方是一個(gè)虛掩著的門,到了這,陽(yáng)光止住了它慵懶的腳步。大學(xué)的宿舍是長(zhǎng)方形的,四個(gè)邊角都放著寫字臺(tái)和床。說是床,其實(shí)就是寫字臺(tái)上的一個(gè)擋板。海南蚊子多,所以擋板上都是有蚊帳的,蚊帳帶來(lái)的壞處是,如果蚊子進(jìn)去了,它除了煩你也別無(wú)出路,好處是,可以形成自己的一個(gè)小牢籠。我睡眼惺收,散發(fā)著一絲頹廢的氣質(zhì)。頹廢在這是一種贊美的詞,只有藝術(shù)家才能配的上頹廢這個(gè)詞。當(dāng)然,我不是藝術(shù)家,所以放在我身上,也就是真實(shí)頹廢本意解釋了。很快,我會(huì)熱血的成為幾百萬(wàn)即將畢業(yè)的學(xué)生,被學(xué)校撒豆成兵,最后可能成為炮灰。

我確實(shí)成為了炮灰。

剛從深圳鎩羽而歸,原本希望能在深圳找份工作,但落了空,完全沒有門道,愣頭愣腦的去了,愣頭愣腦的回來(lái)。期間十幾天,一個(gè)面試的機(jī)會(huì)都沒有撈到,深圳現(xiàn)在也是完全不記得什么樣子了。

這種感覺,想必和艾倫第一次去和巨人戰(zhàn)斗的情景是一樣的。

框框框,有人敲門,我們沒搭理,接著他在門外問:有個(gè)公司來(lái)學(xué)院招聘,是招計(jì)算機(jī)的,你們要不要去看下。

我餓了,真的餓了,我沒做任何思考,便起床動(dòng)身。原先困倦的大腦腦功率趨近于0,現(xiàn)在瞬間飆升。恩,是腦功率,不是荷爾蒙。

那個(gè)時(shí)候自己就像一只農(nóng)村的土狗,只要有份工作就行。而且如果純粹做個(gè)碼農(nóng),我還是很自信的。大學(xué)期間我就已經(jīng)定了方向,專攻Java,所以或多或少還是有些積累,畢設(shè)的時(shí)候用Java寫了個(gè)程序,給磁盤建索引,用的Lucene,順利過了畢設(shè)。沒想到的是,我竟然因此和搜索結(jié)緣,一直持續(xù)到今天。

創(chuàng)業(yè)征途

突然來(lái)的機(jī)遇就是這么神奇,也讓人感覺莫名其妙,說來(lái)就來(lái)了,你真的不知道為什么會(huì)來(lái),但真的就是這么來(lái)了。

當(dāng)機(jī)遇來(lái)臨的時(shí)候,常常讓人懊悔的是抓不住。但抓住了,也未必會(huì)讓你成功。但是,這是一個(gè)不管成功或者失敗,都讓你收獲滿滿的一次機(jī)遇。

這是一家創(chuàng)業(yè)公司。我去的時(shí)候一開始只有三個(gè)人,A君,B君,還有我。

A君多才多藝,產(chǎn)品經(jīng)理+UI設(shè)計(jì)+投資人,B君和我則同為技術(shù)合伙人。

在找到我之前,A君和B君已經(jīng)在海南師范大學(xué)旁邊的一棟寫字樓租了一個(gè)辦公室?,F(xiàn)在回想一下,或許我們應(yīng)該租個(gè)地下室,成功的概率可能會(huì)更高些(不過,如果去美國(guó)的車庫(kù)或者地下室,應(yīng)該成功率又會(huì)提高)

就這樣,08年12月開始了我程序員,或者是創(chuàng)業(yè)的征程吧。

我們做的是一個(gè)原創(chuàng)音樂站點(diǎn)。

這期間我們常常每天工作13-14小時(shí)。要知道,那個(gè)時(shí)候我們沒做過任何項(xiàng)目啊,只是大學(xué)看了幾本程序設(shè)計(jì)的書,而真正做出一個(gè)產(chǎn)品,需要掌握的東西卻是是非常多的。

但是熱血和時(shí)間這東西,真的是有用的。就這么著,基于熱血的推動(dòng),時(shí)間的磨礪,漸漸的,一個(gè)原創(chuàng)音樂分享網(wǎng)站竟然慢慢開始開始有模有樣了,整個(gè)過程,我們自己設(shè)計(jì),開發(fā),購(gòu)買服務(wù)器,托管,運(yùn)維,運(yùn)營(yíng)。

作為一個(gè)流媒體音樂站,不同于一個(gè)簡(jiǎn)單博客,尤其是對(duì)于我們幾個(gè)沒有什么技術(shù)積累的年輕人而言,其挑戰(zhàn)是非常大的。

流媒體服務(wù)器,Web端Mp3播放器,音樂上傳,音樂轉(zhuǎn)碼,音樂下載,個(gè)性化主頁(yè)等等,挑戰(zhàn)非常多。B君后期專攻頁(yè)面和JS,我想,他現(xiàn)在能夠開發(fā)Web端操作系統(tǒng),也得益于這次的挑戰(zhàn)的吧。

首先是站點(diǎn)搭建,沒有采用Worldpress之類的開源組件,真的是從零直接從頭開始做的。那個(gè)時(shí)候不知道原型設(shè)計(jì)工具Axure,A君就直接用Photoshop把界面畫出來(lái),然后把一些小ico切出來(lái)給我們用。A君雖然沒有做過產(chǎn)品經(jīng)理和UI設(shè)計(jì)師,但是他設(shè)計(jì)出來(lái)的界面真心大氣(08年的時(shí)候),后續(xù)我們的對(duì)手,原創(chuàng)音樂中國(guó),還有那有個(gè)因?yàn)樯纥S被關(guān)閉的一個(gè)網(wǎng)站,都在我們上線后不久改版,我們對(duì)比過,明顯借鑒了我們的設(shè)計(jì)元素。

我和B君大學(xué)學(xué)的都是Java,于是采用了SSH2開發(fā)。從頭開始?jí)敬a。還是那句話,如果只是做個(gè)類似博客平臺(tái)的站點(diǎn)自然是不難,但我們做的是原創(chuàng)音樂分享網(wǎng)站。這里面涉及到如下幾個(gè)難點(diǎn):

  1. 首先我們是個(gè)音頻流媒體網(wǎng)站,需要搭建流媒體服務(wù)器,以及播放音頻的客戶端

  2. 接著這是針對(duì)音樂人的一個(gè)社交網(wǎng)站,有點(diǎn)類似現(xiàn)在的微博,音樂人上傳了大量自己的音頻,需要大量的粉絲以及與之互動(dòng)。所以需要提供一個(gè)酷炫的個(gè)人主頁(yè),QQ空間是一個(gè)標(biāo)桿。

于是經(jīng)過仔細(xì)研究,我們使用了Red5 搭建了音頻流媒體服務(wù)器,實(shí)現(xiàn)了一個(gè)簡(jiǎn)易的 flash 音頻播放器,暴露js調(diào)用接口,使用Js進(jìn)行控制。

個(gè)人主頁(yè)我們花費(fèi)的精力非常多,09年時(shí),我們就已經(jīng)仿照QQ了,允許換膚,自定義布局,這個(gè)對(duì)前端以及后端要求都挺高的。后面B君在做后端服務(wù)的時(shí)候,也同時(shí)成了一個(gè)前端高手,我可以說,他已經(jīng)精通CSS和JavaScript了。(后來(lái)B君來(lái)了北京,后續(xù)又去了香港,在香港時(shí),已經(jīng)可以用JS開發(fā)Web操作系統(tǒng)了,這是后話)。 整個(gè)頁(yè)面任何一個(gè)版塊,你都可以顯示,隱藏,互換位置,設(shè)置不同模板。

作為一個(gè)流媒體網(wǎng)站,上傳音頻是個(gè)很重要的東西,為了顯示上傳進(jìn)度,我們當(dāng)時(shí)就折騰了好一會(huì),接著遇到轉(zhuǎn)碼問題,使用了ffmpeg,設(shè)置要把轉(zhuǎn)碼進(jìn)度也顯示給用戶看。

到后來(lái),我們已經(jīng)做了一個(gè)類似蝦米這種音樂類的網(wǎng)站很多功能了,比如給出音樂列表,自動(dòng)播放,直接在title上顯示播放進(jìn)度等等。

SSH2 給我的感覺就是慢,調(diào)用棧太深了。作為一個(gè)互聯(lián)網(wǎng)應(yīng)用來(lái)說,真的不適合。但是架構(gòu)已經(jīng)選定,也沒辦法。于是我們通過大量添加緩存的方式解決性能問題。

有大量的資源,自然就需要搜索了。為了方便查找歌曲,我們引入了基于Lucene的Compass。可以直接基于數(shù)據(jù)庫(kù)建立lucene的索引文件。一般這種東西,很難用好,或多或少都有問題。我期間就經(jīng)常各種錯(cuò)誤,各種問題,不堪其擾。

到10年的時(shí)候,我們的歌手用戶只有幾千。但是歌曲數(shù)量以及總文件大小已經(jīng)相當(dāng)可觀了。我們?cè)诟鱾€(gè)細(xì)節(jié)進(jìn)行了非常大的努力,比如歌曲,用戶評(píng)論,可見不可見,上傳進(jìn)度,頁(yè)面設(shè)計(jì)等等。

產(chǎn)品我們覺得問題不大了,可是,可是我們?cè)撛趺赐茝V運(yùn)營(yíng)呢?完全沒有門道。而且從08年到10年,差不多一年半,我們是完全沒有盈利的,只是在燒A君的錢。

如果有了一定的用戶量和活躍度,其實(shí)關(guān)于盈利的形態(tài)我們都是想好了的,當(dāng)然這個(gè)也是借鑒國(guó)外的模式。而且我到現(xiàn)在都認(rèn)為是一個(gè)好的模式。

但是我們感覺我們快撐不到那個(gè)時(shí)候了。我們那時(shí)候也沒有投資人的概念。在???,互聯(lián)網(wǎng)行業(yè)是相當(dāng)不發(fā)達(dá)的。不像現(xiàn)在,你拿個(gè)想法,跑到咖啡廳,就有人會(huì)聽你講。

于是我們展開了自救行動(dòng)。10年團(tuán)購(gòu)很火,于是,我們打算先暫時(shí)放下手中的主業(yè),做起了團(tuán)購(gòu)。但是后面我們才知道,團(tuán)購(gòu)初期是很燒錢的,不是我們能玩的起的。

海南是個(gè)旅游大省,于是我們開始做旅游站,但是啊,旅游要有線下資源。

急了就什么都嘗試了。

2010年10月國(guó)慶期間,基本上宣告了創(chuàng)業(yè)的失敗。??谟袀€(gè)奇特的天文現(xiàn)象,就是凡是國(guó)慶七天,那是必然下雨的。這個(gè)國(guó)慶也不例外,整整七天都是雨。我當(dāng)時(shí)是個(gè)E站的一個(gè)老用戶(只是看文章的老用戶),看到了E站正好招人,但那個(gè)時(shí)候招的是Ruby程序員。于是十一前的一個(gè)禮拜,我弄了一本敏捷開發(fā)的書,然后把58同城的UI扒下來(lái),然后用ruby on rails 寫了站點(diǎn),包括抓取,包括顯示等。當(dāng)然,沒有后臺(tái)。整整做了一個(gè)禮拜,然后把它作為示例代碼打包,發(fā)給了E站。 后面就接到了E站老大的面試電話。

E站的老大看了我的代碼后,讓我很順利通過了電面,問我是否可以直接去北京。我當(dāng)時(shí)心想,偶像就是偶像,看代碼就能看到我的能力以及誠(chéng)意,沒有N輪面試的折騰。

走的那天,好像還是下著雨。當(dāng)時(shí)我的生活已經(jīng)非常窘迫了,一個(gè)好朋友贊助了我機(jī)票錢,我也在沒有找到北京住處的情況下,就踏上了北京的航班(臨走的那天,B君說他也在北京,可以給我落腳,當(dāng)時(shí)那個(gè)感動(dòng)),開始了我現(xiàn)在的工作。

北京的旅程-開源之路

起源

2010年10月15日,我正式在C公司入職。E站是C公司的一個(gè)子站點(diǎn),用RubyOnRails 開發(fā)的。我一開始基于其上做開發(fā)。就這么做了一個(gè)月Ruby程序員,有一個(gè)事情就第一次改變了我的職業(yè)方向。

某天老大突然說,你不是以前用Java做過搜索么,公司主站點(diǎn)的搜索就交由你做吧。當(dāng)時(shí)公司的搜索是外包給一家專門的公司做的。我自然很樂意的接受了。

后面經(jīng)過一個(gè)月的開發(fā),也就是2010年12月中旬時(shí)候,第一個(gè)版本上線了。當(dāng)然這個(gè)版本可以說超級(jí)簡(jiǎn)單,就是用Lucene搭建了一個(gè)單機(jī)版本的搜索服務(wù)。老大很開心,還請(qǐng)我們?nèi)タ戳俗屪訌楋w。

Web/ORM/ODM 一站式開發(fā)框架

因?yàn)槲抑白鲞^一段時(shí)間的Ruby程序員,一對(duì)比,我發(fā)現(xiàn),Java的Web框架都太不好用了,Java的ORM框架也不好用,Java 的MongoDB Client 也不好用。于是我決定開發(fā)一套一站式Web框架。

正好除了搜索的任務(wù)以外,公司希望做一個(gè)全站通用的標(biāo)簽系統(tǒng)。當(dāng)時(shí)在選型上,老大說用Spring,我當(dāng)時(shí)說,給我點(diǎn)時(shí)間,我自己開發(fā)一套開發(fā)框架出來(lái),老大說,你有信心做好么,我說有。老大給予了肯定的答案。

于是,2011年的有一段時(shí)間,每天早上六點(diǎn)我準(zhǔn)時(shí)起來(lái),光著膀子開始寫代碼(這個(gè)可以推測(cè)是夏天了)。寫了一個(gè)Web框架,一個(gè)ORM框架,一個(gè)MongoDB的 ODM。 后面我問老大,名字叫什么好,老大說,就叫ServiceFramework。于是我便把Web框架叫做 ServiceFramework,ORM框架叫做 ActiveORM,MongoDB的 ODM叫MongoMongo。經(jīng)過公司許可開源了出去。

相關(guān)介紹參考:

http://allwefantasy.iteye.com/blog/1631522

http://www.iteye.com/news/25793

項(xiàng)目地址:https://github.com/allwefantasy/ServiceFramework

從2011年開始,我一直在優(yōu)化這個(gè)項(xiàng)目,到現(xiàn)在已經(jīng)四年了。不過開源版本因?yàn)楣ぷ骶壒?,一直沒有更多精力維護(hù),并且無(wú)法進(jìn)行推廣。但是內(nèi)部版本卻獲得長(zhǎng)了長(zhǎng)足的發(fā)展,作為一個(gè)針對(duì)API服務(wù)開發(fā)的框架,除了易用性以外,我一直重點(diǎn)做的是可運(yùn)維。目前我所在的數(shù)據(jù)部門,已經(jīng)完全使用了這套框架。

關(guān)于可運(yùn)維,框架自身可以做到的:

  • 接口 QPS 監(jiān)控
  • 接口平均響應(yīng)耗時(shí)監(jiān)控
  • 接口調(diào)用量(如果是http的話,則是各種狀態(tài)碼統(tǒng)計(jì))
  • 內(nèi)置HTTP接口,外部可通過該接口獲取以上的數(shù)據(jù)

同時(shí),框架日志信息輸出默認(rèn)包括:

  • http請(qǐng)求url
  • 整個(gè)請(qǐng)求耗時(shí)
  • 數(shù)據(jù)庫(kù)耗時(shí)(如果有)
  • 響應(yīng)狀態(tài)碼

還有一個(gè)比較核心的功能是,服務(wù)降級(jí)限流。
ServiceFramework主要面向后端服務(wù),如果沒有自我保護(hù)機(jī)制,系統(tǒng)很容易過載而不可用。經(jīng)過一定的容量規(guī)劃,或者通過對(duì)接口調(diào)用平均響應(yīng)耗時(shí)的監(jiān)控, 我們可以動(dòng)態(tài)調(diào)整 ServiceFramework 的QPS限制,從而達(dá)到保護(hù)系統(tǒng)的目的。

這里其實(shí)還有一個(gè)重點(diǎn)是,傳統(tǒng)的方式是,容器包含應(yīng)用,比如開發(fā)好的代碼打成war包放到tomcat等容器中。新框架的方式是應(yīng)用包含容器(內(nèi)嵌jetty),一個(gè)應(yīng)用服務(wù)就是一個(gè)純粹的java application.

開發(fā)完這個(gè)框架后,我花了半個(gè)月時(shí)間完成了公司標(biāo)簽系統(tǒng)的開發(fā)。

索引系統(tǒng) (暫時(shí)未開源)

當(dāng)時(shí)老大看上了一個(gè)叫ElasticSearch的搜索項(xiàng)目,類似Solr。不過那個(gè)時(shí)候ES還非常的初級(jí),我當(dāng)時(shí)年少氣盛,沒經(jīng)過任何測(cè)試就上線了,但是對(duì)其了解有限,上線后問題不斷,當(dāng)時(shí)索引量已經(jīng)頗大,光索引文件就達(dá)到百G。一開始我將原始數(shù)據(jù)也store進(jìn)了索引,瞬間IO飆升,服務(wù)直接掛掉了。接著我講原始文件剝離,只存儲(chǔ)索引文件,但是發(fā)現(xiàn)也扛不住,服務(wù)經(jīng)常因?yàn)閴毫Υ蠖鴽]有響應(yīng),因?yàn)镋S有Failover功能,如果發(fā)現(xiàn)有服務(wù)當(dāng)?shù)袅?,就進(jìn)行分片副本遷移,直接把網(wǎng)絡(luò)帶寬占滿(那個(gè)時(shí)候路由器都是百M(fèi)的)。我就開始看代碼,改代碼,但是不得要領(lǐng)。當(dāng)時(shí)ES的社區(qū)遠(yuǎn)不如現(xiàn)在活躍和成熟。

我后悔了,不應(yīng)該沒有調(diào)研測(cè)試清楚就上線。而且當(dāng)時(shí)一直困擾我的就是數(shù)據(jù)更新問題。索引了后,數(shù)據(jù)很快變更,我這邊必須想辦法得到通知。

于是我決定另起爐灶,開發(fā)一個(gè)簡(jiǎn)化版本的ES,使其完全能被自己掌控。經(jīng)過一個(gè)月的開發(fā),第一個(gè)版本的我們稱之為CS的項(xiàng)目完成了。后面使用CS替換掉了ES。服務(wù)再也不會(huì)輕易當(dāng)?shù)袅恕?/p>

在一次ES的分享會(huì)上,我分享了CS。蠻多人還是感興趣的。
CS作為一個(gè)分布式索引服務(wù),特點(diǎn)有:

  • 分布式架構(gòu)
  • 支持索引數(shù)據(jù)分片
  • 支持構(gòu)建離線全量索引過程中合并線上新增數(shù)據(jù)的機(jī)制
  • 支持?jǐn)?shù)據(jù)熱備
  • 擁有一個(gè)剝離配套的統(tǒng)一查詢引擎
  • 支持模塊化,組件化

關(guān)于這個(gè)項(xiàng)目的一些簡(jiǎn)單介紹,可參看:

http://pan.baidu.com/s/1i3qsoBF#path=%252FESCC%25233&render-type=list-view

中的 ES分享.pptx 文件

CS的核心是是查詢引擎和索引存儲(chǔ)剝離。一些高級(jí)功能,比如跨索引檢索,結(jié)果二次排序,摘要提取,獲取詳細(xì)內(nèi)容展示,都是作為模塊放在查詢引擎中。當(dāng)然統(tǒng)一查詢引擎最核心的意義還是在于可以快速更新二次排序的引擎。當(dāng)然這還需要有一些其他架構(gòu)做支撐。

有新的模塊添加,只需重啟查詢引擎,而不需要重啟索引存儲(chǔ)服務(wù)。索引文件重新打開是非常消耗CPU,IO的,常常會(huì)造成機(jī)器負(fù)載瞬間飆升,導(dǎo)致很多搜索維護(hù)人員輕易不敢重啟服務(wù)。在CS不存在這個(gè)問題。

12年的時(shí)候,我終于有伙伴了,以前一直都是一個(gè)人孤軍奮戰(zhàn)。我稱為C君。C君后來(lái)參與搜索的優(yōu)化。之前我因?yàn)閷?duì)Lucene的一個(gè)誤解,導(dǎo)致排序效果一直很爛,我一直不得要領(lǐng)。C君發(fā)現(xiàn)了這個(gè)問題,瞬間搜索排序就正常了。后來(lái)C君開發(fā)了一套新的分詞引擎,現(xiàn)在已經(jīng)是一個(gè)知名的分詞套件了(可參考:https://github.com/NLPchina/ansj_seg)。基于這套分詞引擎我們做了很多分析方面的工作。

索引服務(wù)這個(gè)項(xiàng)目前前后后花了我四年多的時(shí)間。現(xiàn)在策略層公用的組件已經(jīng)包括 SQL,JSON,URL params,Mutil-Index,數(shù)據(jù)網(wǎng)關(guān)等。后面我一直從事數(shù)據(jù)架構(gòu)和算法方面的專研時(shí),索引服務(wù)也成為架構(gòu)中非常核心的一個(gè)服務(wù)。

北京的旅程-大數(shù)據(jù)架構(gòu)之路

到12年年末為止,應(yīng)該說我做的大部分是項(xiàng)目層級(jí)的工作。13年年初,公司突然開始重視大數(shù)據(jù)了。于是我們部門人員由原來(lái)的兩個(gè)人變成了五個(gè)人。加了兩個(gè)剛畢業(yè)的學(xué)生D君和E君以及一個(gè)算法工程師F君。

F君也是對(duì)我影響很大的一個(gè)人,讓我從此了解了機(jī)器學(xué)習(xí),算法方面的東西。我是一個(gè)比較容易受我認(rèn)為優(yōu)秀的人影響的,我會(huì)去學(xué)習(xí)他的思維,他的做事風(fēng)格。

C君則對(duì)大數(shù)據(jù)平臺(tái)有推動(dòng)的作用。當(dāng)時(shí)公司還沒有數(shù)據(jù)平臺(tái),C君推動(dòng)公司購(gòu)買了五臺(tái)配置很低的服務(wù)器,直接放在了辦公地點(diǎn)。然后我們一起搭建一個(gè)簡(jiǎn)單的Hadoop集群。

關(guān)于大數(shù)據(jù)平臺(tái),因?yàn)槲医?jīng)歷過從無(wú)到有,到最后離不開它的整個(gè)過程,我形成了一套自己的觀點(diǎn)。而這些觀點(diǎn),我放在了一次PPT分享里。這里可以提兩點(diǎn):

  1. 大數(shù)據(jù)平臺(tái)(hadoop/spark),真的不要被‘大數(shù)據(jù)’這個(gè)詞給嚇到了。它大數(shù)據(jù)都可以處理,小數(shù)據(jù)當(dāng)然是小菜一碟。本質(zhì)上大數(shù)據(jù)平臺(tái)是一種解決問題的范式,一個(gè)通用的分布式計(jì)算存儲(chǔ)平臺(tái)。想象一下,把一個(gè)文件丟進(jìn)平臺(tái)里,然后在Web上寫個(gè)SQL就可以從各個(gè)維度分析這個(gè)文件里的數(shù)據(jù)了。整個(gè)過程可能只要花你幾分鐘,這比你花半天寫個(gè)單機(jī)程序統(tǒng)計(jì)來(lái)的快吧。SQL一不小心寫多了,設(shè)置成定時(shí),就成BI報(bào)表了。

  2. 大數(shù)據(jù)平臺(tái)搭建維護(hù)很貴吧?真不貴。大數(shù)據(jù)平臺(tái)誰(shuí)說了一定要大?一定要幾百上千臺(tái)機(jī)器了?沒人說一定要這樣子吧。我們當(dāng)時(shí)5臺(tái)機(jī)器,在百萬(wàn)數(shù)據(jù)集就能運(yùn)行的很好了。什么樣的數(shù)據(jù)量,使用什么規(guī)模的集群。當(dāng)你的價(jià)值體現(xiàn)出來(lái)了,公司自然也會(huì)毫不猶豫的給你加機(jī)器了。

我們當(dāng)時(shí)完成的第一個(gè)項(xiàng)目是EDM,郵件精準(zhǔn)投遞。當(dāng)然這個(gè)項(xiàng)目的前提是需要對(duì)人有一個(gè)畫像。于是我們幾個(gè)人加上產(chǎn)品頭腦風(fēng)暴了好長(zhǎng)一段時(shí)間,人工定義了上百個(gè)屬性,加上一個(gè)由詞構(gòu)成的屬性集合(14000維度),得到了對(duì)人的維度表征。郵件的話則直接使用14000維的詞特征表示。

假設(shè)人的特征集合是A,郵件的特征集合是B,目標(biāo)值為0或者1

  • 0 表示不會(huì)打開郵件

  • 1 表示會(huì)打開郵件

    f(A,B) => 0|1

典型的一個(gè)二元分類或者邏輯回歸問題。

做完后郵件打開率提升還是很顯著的。當(dāng)時(shí)我們想盡辦法推測(cè)出用戶的年齡,技能,職位等各種信息。有一些屬性的預(yù)測(cè)取得了相當(dāng)不錯(cuò)的效果,比如用戶年齡,我們拿同事的賬號(hào)進(jìn)行年齡預(yù)測(cè),上下浮動(dòng)不超過2歲。

但做完這項(xiàng)目后C君,F君相繼離開了,但這個(gè)是我數(shù)據(jù)道路上的一個(gè)開端。13年年中,來(lái)了K君。K君之前是某大型視頻網(wǎng)站的數(shù)據(jù)部門負(fù)責(zé)人。應(yīng)該說,通往數(shù)據(jù)道路康莊大道由此完全打開。此時(shí)公司戰(zhàn)略上也開始支持?jǐn)?shù)據(jù)部門,人員迅速擴(kuò)充到15人左右,包括數(shù)據(jù)分析,開發(fā),算法。不過實(shí)習(xí)生占了很大一部分,服務(wù)器方面,新采購(gòu)的單臺(tái)都至少24+核,32+G 內(nèi)存。

13年到14年是一段非常辛苦的路。在這兩年我實(shí)現(xiàn)了快速的成長(zhǎng),開始了大數(shù)據(jù)平臺(tái)架構(gòu)之路,同時(shí)也投入了相當(dāng)一部分時(shí)間在機(jī)器學(xué)習(xí)和數(shù)據(jù)研究之上。

應(yīng)該說,公司的數(shù)據(jù)平臺(tái)是完全從0開始的,我和K君對(duì)整個(gè)平臺(tái)做了比較詳細(xì)的規(guī)劃。

第一步推動(dòng)公司進(jìn)一步擴(kuò)充了集群。我們也開始了自己的第一個(gè)目標(biāo),構(gòu)建公司自己的BI系統(tǒng)。

K君親自制定了數(shù)據(jù)上報(bào)格式規(guī)范。我則基于Hive使用其SerDer機(jī)制開發(fā)了一套數(shù)據(jù)格式解析器。支持通過JSon格式文本描述日志格式,然后自動(dòng)映射到Hive表中。這樣新來(lái)一個(gè)格式的數(shù)據(jù),你只要填寫一個(gè)json格式的描述文件,就能使用Hive直接進(jìn)行SQL查詢。

另外指導(dǎo)一個(gè)實(shí)習(xí)生開發(fā)了日志接受服務(wù)。同時(shí)L君則改寫了阿里開源的一個(gè)宙斯平臺(tái)?;谠撈脚_(tái),可以直接在Web界面管理和調(diào)試Hive/MapReduce任務(wù),并且設(shè)置調(diào)度任務(wù)。

完成了這些工作,我們構(gòu)建起了數(shù)據(jù)分析平臺(tái)的雛形。在宙斯添加大量的SQL統(tǒng)計(jì)腳本,計(jì)算結(jié)果導(dǎo)入到MySQL中,然后在Web端展示,完成了BI報(bào)表的任務(wù)。當(dāng)然這只是一個(gè)離線計(jì)算平臺(tái),真正和應(yīng)用平臺(tái)打通,形成完善的數(shù)據(jù)流入流出,需要做的工作還太多。

從那個(gè)時(shí)候起,一直到2014年11月,我們終于完整的構(gòu)建了一個(gè)數(shù)據(jù)支撐平臺(tái)。

  1. Hadoop/Spark/HBase 體系,支撐BI,數(shù)據(jù)離線分析,推薦協(xié)同計(jì)算等

  2. 分布式索引服務(wù),支撐搜索,數(shù)據(jù)平臺(tái)供查詢數(shù)據(jù)的存儲(chǔ)

  3. 統(tǒng)一查詢引擎,為數(shù)據(jù)產(chǎn)品提供統(tǒng)一的查詢接口

  4. 內(nèi)容網(wǎng)關(guān)+數(shù)據(jù)網(wǎng)關(guān)+上報(bào),打通產(chǎn)品到數(shù)據(jù)平臺(tái)的入口。

  5. 分布式緩存體系(Redis) ,可支持推薦系統(tǒng),數(shù)據(jù)網(wǎng)關(guān)等產(chǎn)品

  6. 初步的服務(wù)監(jiān)控體系 (參看:https://code.csdn.net/allwefantasy/platform 的介紹)

  7. 推薦系統(tǒng),支持相關(guān)內(nèi)容推薦,用戶個(gè)性化推薦,公共隊(duì)列展示,底層完全基于Redis實(shí)現(xiàn)。這里有個(gè)給兄弟公司介紹的一個(gè)PPT(http://vdisk.weibo.com/s/HZFjdG-haPc)

  8. 配置與發(fā)布系統(tǒng)(運(yùn)維相關(guān))

基本上在這個(gè)過程中是去數(shù)據(jù)庫(kù)化的。HBase做數(shù)據(jù)存儲(chǔ),分布式索引服務(wù)則將數(shù)據(jù)進(jìn)行索引,支撐復(fù)雜查詢,統(tǒng)一查詢引擎則以一致的接口對(duì)外提供查詢服務(wù)。

關(guān)于這兩年做的架構(gòu),我未來(lái)專門寫了一篇文章進(jìn)行解析。

平臺(tái)是你順利開張其他工作一個(gè)很重要的基礎(chǔ)設(shè)施。當(dāng)你有了一個(gè)完善,易于擴(kuò)展的平臺(tái),你的工作會(huì)越來(lái)越少,新添加的東西會(huì)越來(lái)越輕量。你會(huì)發(fā)現(xiàn),啊,原來(lái)這個(gè)都已經(jīng)是被平臺(tái)囊括了,啊,只是加個(gè)模塊嵌入進(jìn)去就完事了。

后續(xù)談到的機(jī)器學(xué)習(xí),很早之前其實(shí)我也接觸過,搞個(gè)SVM lib弄弄,都是C的,單機(jī)的,生成個(gè)個(gè)是數(shù)據(jù)都費(fèi)事,更別說后面的訓(xùn)練跑了。有了平臺(tái)后,真的就看你的自己的聰明才智了,你更容易獲得你想要的數(shù)據(jù),你更方便將數(shù)據(jù)轉(zhuǎn)化為你需要的格式,你更容易快速看到你的算法在實(shí)際數(shù)據(jù)集上的效果。

舉個(gè)簡(jiǎn)單例子,我們做了一個(gè)智能問答的項(xiàng)目,在現(xiàn)有平臺(tái)上做,只花了兩天多時(shí)間。如果沒有平臺(tái),我智能呵呵了,一個(gè)禮拜都不一定能做順暢了。

北京的旅程-機(jī)器學(xué)習(xí)之路

2014年開始基于Yarn平臺(tái)引入Spark。因?yàn)閮?nèi)部使用的是CDH4.4.0 版本的Yarn,Spark默認(rèn)支持有點(diǎn)問題,所以必須自己下載源碼,修改一些地方才能編譯通過。Spark 平臺(tái)是對(duì)數(shù)據(jù)分析師,算法工程師們的恩賜。我之前做一個(gè)數(shù)據(jù)調(diào)查,或者是為了產(chǎn)生一個(gè)訓(xùn)練文件給算法用,可是頗費(fèi)周折。Hadoop的開發(fā)部署發(fā)展了這么多年,其實(shí)還都是挺麻煩的。用Spark幾行代碼就可以搞定,而且本地IDE寫好,直接黏貼到spark-shell交互式運(yùn)行。

對(duì)于算法工程師而言,原先比如使用貝葉斯之類的,測(cè)試只能跑個(gè)小數(shù)據(jù)集,不然很久都跑不完啊,跑一個(gè)禮拜沒出結(jié)果也是常事。有了Spark之后,測(cè)試的時(shí)候就直接在一定規(guī)模的數(shù)據(jù)上跑了。真實(shí)的數(shù)據(jù)都在集群上,省去了要下載數(shù)據(jù)到本地的問題。要知道,目前大部分算法本質(zhì)上是通過大量的數(shù)據(jù)通過一些優(yōu)化算法提取出目標(biāo)函數(shù)的參數(shù),算法的這種形態(tài)決定過了只有在一定規(guī)模的結(jié)果集上才能如實(shí)得到實(shí)際數(shù)據(jù)集上的效果。而Spark則很好的滿足了這種需求。

Scala 是我比較愿意接受的第三個(gè)語(yǔ)言。第一個(gè)是Java,從大學(xué)開始就一直用著,第二個(gè)是Ruby,有過一段短暫Rails程序員的經(jīng)歷,也從中得到很多靈感?,F(xiàn)在的話,基本是Scala,Java混合編程。Scala 確實(shí)能夠有效的提高編程效率,而且可以和Java無(wú)縫操作。在公司內(nèi)部系統(tǒng)中,基本上都是混合著用的。

程序員再也不應(yīng)該僅僅是寫代碼讓服務(wù)跑起來(lái)或者設(shè)計(jì)一個(gè)架構(gòu)做到良好的擴(kuò)展性,這些工作本質(zhì)上是重復(fù)性的工作,你很難做到和別人不一樣,所以才會(huì)有碼農(nóng),你只是壘代碼。隨著計(jì)算能力的提高,以及機(jī)器學(xué)習(xí)的發(fā)展,程序員的目標(biāo)應(yīng)該更加高尚,我怎么才能讓你在茫茫人海中找到多看了你一眼的人?

前面我提到,在做架構(gòu)的過程中,我對(duì)數(shù)據(jù)分析和機(jī)器學(xué)習(xí)也非常感興趣,期間也做了一些成果。雖然主要精力還是在架構(gòu)上,但經(jīng)常會(huì)抽出時(shí)間做相關(guān)的研究。

新詞發(fā)現(xiàn)是一個(gè)非常有意思的領(lǐng)域,用途非常多。譬如可以構(gòu)建垂直領(lǐng)域詞庫(kù),自動(dòng)發(fā)現(xiàn)新熱門詞匯。詞庫(kù)的重要性我不用強(qiáng)調(diào)了。基于Spark強(qiáng)大的計(jì)算能力,我直接對(duì)200萬(wàn)+ 的博文進(jìn)行了分析,得到大概八萬(wàn)詞,包含 中文,英文,中英文混合詞。通過凝固度,自由度,詞頻,idf,以及重合子串(比如 c1c2c3..cN c2c3..cN-1 這種形態(tài)的 我們認(rèn)為是重合子串,如果詞頻一樣,則都過濾掉,否則留詞頻高的)五個(gè)維度進(jìn)行閾值設(shè)置和過濾。事實(shí)上,中間結(jié)果可以到幾百億,一個(gè)不小心就可以把Spark跑死,但是也在這個(gè)過程中慢慢對(duì)Spark有了更深的理解。 最終效果還是不錯(cuò)的,現(xiàn)在它已經(jīng)作為我們的基礎(chǔ)詞庫(kù)了。

算法自然是參考論文的,但我感觸比較深的是,通常某篇論文只會(huì)在一個(gè)視角去focus 某件事情,所以你需要參考多篇,從不同角度去理解這件事情的解決方式,最后通過實(shí)驗(yàn)綜合,得到一個(gè)更好解決方案。我參考了兩篇論文,比如凝固度,自由度是出自一篇論文,而重合子串則來(lái)自另外一篇論文,然后自己觀察實(shí)際數(shù)據(jù),添加了很多規(guī)則,才得到最后的結(jié)果。

新詞發(fā)現(xiàn)這個(gè)我足足做了兩到三個(gè)禮拜,能有這么多時(shí)間focus在這件事情確實(shí)不容易。在公司,有太多的工作和新的想法需要去實(shí)施,尤其是公司在快速的轉(zhuǎn)型或者上升期。我業(yè)余時(shí)間雖然不加班,但是我常常會(huì)在腦海里把系統(tǒng)架構(gòu)方面的東西不斷排練,就像大腦是個(gè)虛擬機(jī),模擬器,一整套處理流程都會(huì)在大腦里事先跑起來(lái),這對(duì)自己掌握某個(gè)系統(tǒng),或者是對(duì)全局的掌控非常重要。

一說到算法,大概很多人心里就是想著,恩,我把數(shù)據(jù)轉(zhuǎn)化為算法需要的格式,然后丟給現(xiàn)成的算法跑,跑著就出結(jié)果,或者出模型,然后反復(fù)嘗試,直到得到你認(rèn)為能接受的或者最優(yōu)的結(jié)果。我一開始也是這么想的,可是如果你真的做這件事情,就發(fā)現(xiàn)完全不是那樣子啊,需要注意的細(xì)節(jié)太多了。

新詞發(fā)現(xiàn)沒有現(xiàn)成的工具包,所以完全自己寫了。第一步,你要獲取語(yǔ)料。這容易,基于現(xiàn)有的平臺(tái),我從我們資源中心挑出了200萬(wàn)篇文章id,然后根據(jù)id到數(shù)據(jù)網(wǎng)關(guān)獲取title,body字段。這個(gè)基于現(xiàn)有的平臺(tái),也就一個(gè)SQL + 幾行Scala代碼就搞定的事情。

SQL 其實(shí)就是用Hive 生成一個(gè)200萬(wàn)博文id列表。 Scala 代碼看起來(lái)像這個(gè)樣子

sc.textFile("/hdfs-upload-dir/20140904-114357-result.txt",30).flatMap{f=>
      val Array(id,name,skill) = f.split("\t")
      getFromUrl(id,"blog","title,body")
    }.filter(f=> f.length > 100 ).saveAsTextFile("/output/william/newwords/articles")

因?yàn)槲覀兊男略~發(fā)現(xiàn)是沒有詞典的,需要枚舉所有組合,然后通過一定的規(guī)則判定這是不是一個(gè)詞。比如 ‘我是天才’,就這四個(gè)字,
組合有,‘我是’,‘我是天’,‘我是天才’,‘是天’,‘是天才’,‘天才’ 。你想想,200萬(wàn)篇文章,這種組合得多夸張,問題是你還要接著給這些組合做計(jì)算呢。這個(gè)算法可沒告訴你怎么處理的,你只能自己去想辦法??吹搅?,真正你做算法的過程中,不只是實(shí)現(xiàn),你需要面對(duì)的問題特別多,我是怎么做的呢?

  1. 將所有html標(biāo)簽替換成空格,通過小空格切分后我們就能得到無(wú)數(shù)的小文本塊,然后我們就能做詞枚舉了
  2. 一個(gè)詞最長(zhǎng)不能超過5個(gè)字
  3. 將中文,中英文,英文分開來(lái)做
  4. 將一些特殊字符 類似‘?。ぁǎ?{}【】的呀啊阿哎吧和與兮呃唄咚咦喏啐喔唷嗬嗯噯你們我他她這是由于’ 這些不可能成詞的字符先去掉

這樣,詞集合就小多了。 接著就是按論文里的規(guī)則做計(jì)算了,比如算詞的凝固度,算重合子串。這里面還會(huì)遇到很多性能,或者內(nèi)存的坑,比如Spark里的groupByKey,reduceByKey。 我一開始省事,用了groupByKey,歇菜了,內(nèi)存直接爆了,為啥,你要去研究groupByKey到底是怎么實(shí)現(xiàn)的,一個(gè)詞出現(xiàn)幾十萬(wàn)次,幾百萬(wàn)次都很正常啊,groupByKey受不了這種情況。所以你得用reduceByKey。

很好,實(shí)現(xiàn)了算法后得到了結(jié)果,可人家沒告訴你,他貼出來(lái)的結(jié)果都是好看的,那是因?yàn)樗前搭l次排的,但如果你拉到最后看,結(jié)果就不太好看了。這個(gè)時(shí)候你就需要觀察數(shù)據(jù)了,然后提出新的規(guī)則,比如最后得到的中文詞結(jié)果,我用了下面的規(guī)則過濾了下

 f._1.contains("和")||
          f._1.contains("或")||
          f._1.contains("就")||
          f._1.contains("將")||
          f._1.contains("是")||
          f._1.contains("的")||
          f._1.contains("為")||
          f._1.contains("個(gè)")||
          f._1.contains("到")||
          f._1.contains("來(lái)")||
          f._1.contains("種")||
          f._1.contains("中")||
          f._1.contains("length")||
          f._1.contains("tmp")||
          f._1.contains("void")||
          f._1.contains("如")||
          f._1.endsWith(":")||
          f._1.endsWith("amp")||
          f._1.endsWith("[")||
          f._1.endsWith("]")||
          f._1.endsWith("’")||
          f._1.split("\\s+")(0) == "if" ||
          f._1.split("\\s+")(0) == "for"||

上面的規(guī)則是什么意思呢?凡是詞里面包含‘或’的,或者'就'的或者上面羅列的,我都認(rèn)為這個(gè)詞是沒有意義的,經(jīng)過這個(gè)簡(jiǎn)單規(guī)則一過濾,效果好非常多,很多沒什么意義的生活詞,或者不成詞的詞就被去掉了。中文,英文,中英文混合,我都加了很多這種規(guī)則,最終才過濾出了八萬(wàn)計(jì)算機(jī)詞匯。

在多分類的算法中,貝葉斯和SVM的效果是相當(dāng)不錯(cuò)的,Spark中樸素貝葉斯的實(shí)現(xiàn)基于該論文: http://t.cn/RPkPkJq ,我看了一遍,在我的數(shù)據(jù)集上跑的效果并不好。我修改了論文中 p(t|c) 的公式 為 p(t|c) = 出現(xiàn)t的c分類下的文章數(shù)/c分類下得所有文章數(shù) 對(duì)于t|d=0的情況不參與計(jì)算。然后舍棄采用Log將乘除轉(zhuǎn)化為加減。分類效果得到明顯提高。

我把它應(yīng)用在了代碼篇的語(yǔ)言判斷上。給我一段代碼,我告訴你這是什么語(yǔ)言寫的。一開始識(shí)別率就達(dá)到70%左右,后面通過提高維度,將代碼片高維表示后(65萬(wàn)),準(zhǔn)確率提升到了85%??傮w而言,貝葉斯和SVM后面有人提出了各種改良,但是最后是改良了還是改差了,還得看具體數(shù)據(jù),你需要有調(diào)整他們的能力。

再說一個(gè)應(yīng)該說非常具有實(shí)際意義的一個(gè)算法:職位和簡(jiǎn)歷匹配度算法。當(dāng)然設(shè)計(jì)的是為了招聘研發(fā),所以基調(diào)就是以技能模型為主,提出一套完整匹配度算法。效果不錯(cuò)。之后的研究人員基于我的算法,添加了職位方向等因子,使得效果更進(jìn)一步。這套系統(tǒng)會(huì)成為后續(xù)招聘領(lǐng)域比較重要的基礎(chǔ)。

人生第一次跳槽

在上一家公司工作了五年之后(2015年8月),我終于跳槽了。當(dāng)時(shí)老板(我們老板是一個(gè)非常優(yōu)秀的老板)請(qǐng)我吃飯的時(shí)候,問我為啥跳槽,我說我想開始做些事業(yè)。我能呆一家公司呆五年,說明老東家是真好。

我到了L公司之后,初期主要是實(shí)時(shí)計(jì)算這塊。實(shí)時(shí)計(jì)算能做的事情還是非常多的。這讓我后續(xù)以Spark Streaming 為起點(diǎn),深入研究Spark 內(nèi)部源碼做好了鋪墊。

此時(shí)我除了負(fù)責(zé)一個(gè)應(yīng)用開發(fā)團(tuán)隊(duì),同時(shí)還負(fù)責(zé)內(nèi)部的機(jī)器學(xué)習(xí)團(tuán)隊(duì)。然而我是一個(gè)很懶的人,不太適合帶團(tuán)隊(duì),我覺得我需要太多的時(shí)間投入到技術(shù)上去,去專研。所以團(tuán)隊(duì)發(fā)展不足,這也讓自己很愧疚。然而只要和我工作過的人,我一定會(huì)讓他們有技術(shù)上的收獲。和我一起共事的同事有的去了百度,有的去了滴滴,大部分都還在互聯(lián)網(wǎng)公司。有一次吃飯的時(shí)候,他們有人說,雖然我很嚴(yán)苛,但是確實(shí)給他帶來(lái)了收獲。聽到這一點(diǎn),我還是很開心的。技術(shù)人員應(yīng)該以技術(shù)為紐帶,互相幫助對(duì)方去提升。

這次跳槽唯一的遺憾之處在于,8月3號(hào)辦完離職,8月4號(hào)就開工上班,當(dāng)天就加班了,ORZ。

Spark 的再相會(huì)

經(jīng)過八個(gè)月業(yè)余時(shí)間(周末加上工作日夜晚)的努力,我想要的產(chǎn)品原型終于做成了。然而終究是計(jì)劃有調(diào)整,在主動(dòng)和一些CTO,資深技術(shù)人員,業(yè)已創(chuàng)業(yè)成功的人聊,加上碰了一些壁之后,我打算放慢些節(jié)奏。似乎光有個(gè)原型是不足以打動(dòng)投資人的。

這個(gè)時(shí)候,Spark 對(duì)我的吸引力突然無(wú)限超越了事業(yè)對(duì)我的吸引力,所以三個(gè)月時(shí)間我寫了20幾篇Spark相關(guān)的文章,并且推動(dòng)部門上了很多Spark Streaming相關(guān)的應(yīng)用。

在此期間,我大量閱讀源碼,并且修正了不少在特地情況下Spark會(huì)工作不正常的錯(cuò)誤,同時(shí)做了一些增強(qiáng)。這些工作在Spark Streaming 的玫瑰與刺 有所提及。同時(shí)還提出了一個(gè) 流式計(jì)算動(dòng)態(tài)資源調(diào)整算法

2016年3月份開始,我慢慢將工作重心放在多維查詢上,大體朝著SparkES 多維分析引擎設(shè)計(jì) 努力。5月份左右開源了StreamingPro項(xiàng)目,涵蓋了批處理,流式計(jì)算,交互式查詢等多項(xiàng)功能。其實(shí)原理也簡(jiǎn)單,基于之前開源的ServiceframeworkDispatcher 實(shí)現(xiàn)配置化,使用以前開發(fā)的Serviceframework框架嵌入到Spark Driver 中,這樣就可以接管Spark UI的工作,提供新的交互接口。

技術(shù)的輪回

最近基于Spark Streaming + ElasticSearch 做數(shù)據(jù)的日志分析。還記得我12年(也可能是11年)引入ES做搜索,那個(gè)時(shí)候簡(jiǎn)直被虐死,所以開發(fā)了一套自己的索引系統(tǒng)。沒想到四年后又遇到它了,而這個(gè)時(shí)候不在是在搜索領(lǐng)域,而是在大數(shù)據(jù)日志分析領(lǐng)域。人和技術(shù),都存在某種輪回。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容