你們好,我是 Michael Ding,來自暴走漫畫。java
暴走漫畫是一家文化傳媒公司。公司除了有若干視頻娛樂節目,還有相應的社區網站及 App。流量 UV 200w/天 左右,PV 千萬。
爲了更加有效地運營以及推薦用戶個性化,2015年成立了數據部,負責暴漫的數據分析和數據挖掘相關服務。node
暴漫沒有本身的服務器,是使用的國內某雲服務。暴漫的後端主要是基於 Ruby 開發。也有基於 go, python 的一些micro service。
Docker 在暴漫中的應用主要包括:python
開發環境的 service 搭建mysql
代碼託管,持續集成,docker 鏡像,等若干 support 服務git
部分 micro service 以及整個數據服務系統redis
因此今天的內容是一些中小規模以及國內雲服務下的 docker 實踐的相關心得,主要包括在數據服務的架構及 docker 化的部署。sql
因爲開發環境主要是 Mac,也有少許 Ubuntu 和 Windows,因此主要採用 Vagrant + docker 方式。
將 micro service 作成 image,在 Vagrant 中起相應的container,把端口暴露給 Host(Vagrant)。本地跑 Ruby(on Rails)docker
support 服務的話,其餘都很簡單,只有持續集成介紹下。咱們用的 gitlab ci。gitlab ci 支持將 task 跑在 docker container 裏面
因此咱們爲不一樣的項目準備不一樣的測試環境(image)以及外部依賴(eg. mysql, redis),而後在對應的 container 裏面跑測試。
關於部署的話,咱們平時的開發在 develop 分支,一旦向 master 分支合併後,會觸發部署的 task。
部署的 task 跑在特定的 container 裏面,這個 container 共享了 Host 的 docker unix sock 文件,能夠執行 docker build, push 等命令shell
關於開發環境和 support 服務的 docker 應用,由於不是今天的重點,而且前面也有不少朋友作過相似的介紹,因此先簡單介紹到這裏。數據庫
今年咱們作了不少 micro service 的嘗試,例如消息推送,推薦系統,反垃圾系統,數據分析系統,視頻抓取等等若干子系統的拆分上線。
雖然過程是痛苦的,可是結果倒是使人欣慰的。這些 micro service,幾乎都是基於 docker 的。
總體來講,咱們是個混合的架構,Rails 是正常的跑在雲主機中的,micro service 跑在 docker 中。爲了協調好各方,咱們對基礎服務作了一點小小的調整。
這裏不得不說說我作架構的一點心得。好的架構除了能知足業務需求,還要是與特定的團隊,特定的資源所配套的。
在暴漫,因爲技術力量有限,開發排期滿,因此我都是儘可能採用「非侵入式」的方案,這在後面的數據服務的構建中也有體現。
首先,咱們給全部的機器都裝上了 docker
其次,咱們搭建了一個 etcd 集羣,將全部的雲主機都歸入了 etcd 集羣。而 etcd 也是跑在 docker 裏的。
爲了方便的跑起來 etcd,咱們寫了個一套 bash + python 的腳本(Python 的腳本也是跑在 docker 裏的)
而後,全部的機器直接訪問本機 IP 能夠 access etcd。
這裏插一句,咱們沒有去折騰如何讓docker跨主機組網,而是直接採用映射到 host的方式。一方面國內雲主機只能這麼幹。另外一方面,咱們以前使用雲主機也是單個主機特定用途的。
另外,在生產環境中,咱們大量的使用了 shell + etcd 來啓動 docker container 的方式。能夠給你們看個 etcd 的啓動 script。這個 script 放到最初的機器上就能夠方便地啓動起來etcd 集羣。
#!/bin/bash check_non_empty() { # $1 is the content of the variable in quotes e.g. "$FROM_EMAIL" # $2 is the error message if [[ $1 == "" ]]; then echo "ERROR: specify $2" exit -1 fi } check_exec_success() { # $1 is the content of the variable in quotes e.g. "$FROM_EMAIL" # $2 is the error message if [[ $1 != "0" ]]; then echo "ERROR: $2 failed" echo "$3" exit -1 fi } up() { # create ${EtcdData} mkdir -p ${EtcdData} # pull pycsa docker image docker pull private/pycsa:latest check_exec_success "$?" "pulling 'pycsa' image" # pull etcd docker image docker pull quay.io/coreos/etcd:latest check_exec_success "$?" "pulling 'etcd' image" # build cluster nodes list for `-initial-cluster` cwd=$(pwd) ClusterNodes=$(docker run --rm \ -v ${cwd}:/data \ private/pycsa:latest \ python up.py cluster-nodes ${1} ${ETCD_NAME} ${HostIP}) check_exec_success "$?" ${ClusterNodes} case "$1" in "-a") ${BaseCmd} -initial-cluster ${ClusterNodes} \ -initial-cluster-state existing ;; "") ${BaseCmd} -initial-cluster ${ClusterNodes} \ -initial-cluster-token bzetcd-cluster -initial-cluster-state new ;; *) echo "Usage: ./etcd.sh up [-a]" exit 1 ;; esac } start() { docker kill etcd 2>/dev/null docker rm etcd 2>/dev/null ${BaseCmd} } stop() { docker stop etcd docker rm etcd } ################## # Start of script ################## # source env . /etc/default/etcd check_non_empty "${ETCD_NAME}" "ETCD_NAME" # get host ip HostIP=$(ifconfig eth0 | awk '/\<inet\>/ { print $2}' | sed 's/addr://g') # set data dir EtcdData=/data/etcd/data # create etcd container base cmd BaseCmd="docker run -d \ -v /usr/share/ca-certificates/:/etc/ssl/certs \ -v ${EtcdData}:/data \ -p 4001:4001 -p 2380:2380 -p 2379:2379 \ --name etcd quay.io/coreos/etcd:latest \ -name ${ETCD_NAME} \ -data-dir /data \ -advertise-client-urls http://${HostIP}:2379,http://${HostIP}:4001 \ -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \ -initial-advertise-peer-urls http://${HostIP}:2380 \ -listen-peer-urls http://0.0.0.0:2380" case "$1" in up) up "$2" ;; start) start ;; stop) stop ;; restart) stop start ;; *) echo "Usage: ./etcd.sh start|stop|restart or ./etcd.sh up [-a]" exit 1 ;; esac exit 0
解釋下, up.py
是個 python 的腳本,跑在一個 pycsa 的容器裏,這個容器有 python 環境以及相關的 package
這樣原來的服務幾乎不受任何影響,咱們能夠利用 etcd + docker + shell script 來組建新的服務。
咱們的數據服務包括數據分析和數據挖掘兩大塊。數據分析主要是爲了給運營提供量化的效果評估以及指導。數據挖掘則包括推薦,反垃圾等。
數據服務的基礎是數據流,即:數據收集->數據分發->數據處理<->數據存儲
先給你們看個總體的架構圖,因爲本人不擅做圖,因此直接用手畫的,還請見諒。。
首先數據收集部分,就像以前說的,我儘可能採用「非侵入式」的方案,因此,咱們的整個數據收集都是基於日誌的。
咱們在每一個應用服務器上裝了 logstash (跑在 docker 中) 來收集各個應用服務器的日誌,而後打到 kafka (跑在 docker 中) 裏,給不一樣的用途使用。
一份COPY 直接由kafka 一端的 logstash 存儲到 elasticsearch(跑在 docker 中) 中
一份COPY 通過 spark (跑在 docker 中) stream 作實時處理(包括一些特定日誌的提取),而後將處理的結果存儲在 elasticsearch 裏
還有一份 COPY 直接存儲到 HDFS (由雲服務商提供)
這裏有個小問題,好比有些數據自己日誌裏並無,好比用戶的點擊行爲。這個時候,咱們專門開發了一些 "ping" 接口,這些接口經過 Nginx 直接返回 200,並記錄相關日誌
此外還有一部分數據,例如一些比較須要「較嚴格的完備」的,例如用於推薦系統,反垃圾系統學習的數據,咱們存儲在 SQL 數據庫中
下面我作些稍微詳細的介紹
數據分析有兩種:實時數據分析和離線數據分析
實時數據分析從 kafka 到 spark stream,處理結果進 elasticsearch,離線分析是定時任務,從 HDFS 到 spark,處理結果進 elasticsearch。通常來講,離線的結果會逐步包含實時的結果,
同時實時的結果領先於離線分析的結果。
這裏的分析有些抽象,我來舉個例子:
Q: 統計某個板塊同時在線人數的變化趨勢
A: 用戶每次訪問都有日誌,日誌裏包括訪問內容以及用戶標識。首先 spark stream 從日誌裏抽取出特定板塊不一樣用戶的訪問事件,以秒爲單位合併相同用戶事件。這就是分析結果:時間戳:人數
而後這個結果怎麼用?
elasticsearch 有很強大的 agg 接口。你能夠以1秒,10秒,1分等等各類時間間隔單位聚合這段時間內的在線人數,聚合方式用 '平均'或'最大'
咱們主要作了2個具體的數據挖掘系統:推薦+反垃圾
今天主要講下架構。
這兩個系統基本上步驟是同樣的,分爲2步:訓練(train) 和 服務(serve)
在 train 階段,定時起一個 spark job,從訓練數據集中讀取數據,學習出 model,而後將 model 存儲成文件
在 serve 階段,起一個帶 serve 的 spark job,load 以前學習出來的model 文件進內存,而後接受外部api 調用,返回結果。
關於服務的開發這部分由於涉及到太多額外的知識,我就很少說了。
這裏講個難點:spark 的 docker 化。
Spark 的 docker 化分爲兩個部分:
docker 化的 spark 集羣
docker 化的 spark 調用
Spark 和咱們通常用的服務不太同樣,它的集羣不是提供運算服務的,而是一種資源分配的調度器。
讓 Spark 跑 Job,實際上是起的一個 Spark 的本地程序,這個本地程序會向 cluster 要資源(其餘機器),cluster 分配資源之後,這個 spark 程序就把一些工做放在這些資源當中運行(進程)
因此 Spark 的 docker 化分爲兩個部分。
對於 spark 調用,也就是啓動 spark 的本地程序,咱們就是在跑程序的 image 中集成 java 環境,spark 程序
對於 spark 集羣,稍微複雜一些。spark 支持三種集羣:mesos, yard,還有 spark 本身的一個 standalone
咱們搭建的 spark standalone 集羣,這仍是考慮到咱們自身的資源與需求。
因爲沒找到官方的 spark docker image,咱們本身作了一個,就是 java 環境 + spark 程序
而後利用 script + etcd 以不一樣的姿式(master 或 slave)在不一樣的雲主機上啓動 spark container
官方推薦要起3個 master, 用 zookeeper 作 quorum,這個咱們最近正在搞,還沒上線,就不分享。咱們如今線上跑的是 1 master + 7 slave
謝謝
--
更多文章歡迎關注自由風暴博客