作者: paul

[centos] 為Tornado開啟SSL-生成SSL證書

[centos] 為Tornado開啟SSL-生成SSL證書

首先要生成伺服器端的私密金鑰(key檔):

sudo openssl genrsa -des3 -out server.key 1024
  1. 運行時會提示輸入密碼,此密碼用於加密key檔(參數des3便是指加密演算法,當然也可以選用其他你認為安全的演算法.),以後每當需讀取此檔(通過openssl提供的命令或API)都需輸入口令.如果覺得不方便,也可以去除這個口令,但一定要採取其他的保護措施!

去除key文件口令的命令:

生成CSR檔:

sudo openssl req -new -key server.key -out server.csr -config /etc/pki/tls/openssl.cnf
  1. 生成Certificate Signing Request(CSR),生成的csr檔交給CA簽名後形成服務端自己的證書.螢幕上將有提示,依照其指示一步一步輸入要求的個人資訊即可.

對用戶端也作同樣的命令生成key及csr文件:

CSR檔必須有CA的簽名才可形成證書.可將此檔發送到verisign等地方由它驗證,要交一大筆錢,何不自己做CA呢.

  1. 在/etc/pki/CA/目錄下新建目錄certs、newcerts
  2. 建立一個空檔 index.txt
  3. 建立一個文字檔 serial, 沒有副檔名,內容是一個合法的16進制數字,例如 0000
  4. sudo openssl req -new -x509 -keyout ca.key -out ca.crt -config /etc/pki/tls/openssl.cnf
    
    sudo openssl genrsa -des3 -out client.key 1024
    
    sudo openssl req -new -key client.key -out client.csr -config openssl.cnf

用生成的CA的證書為剛才生成的server.csr,client.csr文件簽名:

sudo openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key -config /etc/pki/tls/openssl.cnf

sudo openssl ca -in client.csr -out client.crt -cert ca.crt -keyfile ca.key -config /etc/pki/tls/openssl.cnf

到了這裡應該已經創建了可以使用的證書了,如果在為檔簽名的時候有錯誤,那多半是資訊不正確,這時可以去清空一下 index.txt 裡的資訊,然後重新執行第5步裡失敗的操作。

在Tornado啟用https server

http_server = HTTPServer( app, max_body_size=1500 * 1024 * 1024 * 1024,
ssl_options={"certfile": os.path.join( os.path.abspath( "." ), "server.crt" ),
			 "keyfile": os.path.join( os.path.abspath( "." ), "server.key" )} )
[轉錄]如何設計一個小而美的秒殺系統? 作者-劉鵬

[轉錄]如何設計一個小而美的秒殺系統? 作者-劉鵬

如何設計一個小而美的秒殺系統

 作者 劉鵬 發佈於 201737

現如今,春節搶紅包的活動已經逐漸變成大家過年的新風俗。親朋好友的相互饋贈,微信、微博、支付寶等各大平臺種類繁多的紅包讓大家收到手軟。雞年春節,鏈家也想給15萬的全國員工包個大紅包,於是我們構建了一套旨在支撐10萬每秒請求峰值的搶紅包系統。經實踐證明,春節期間我們成功的為所有的小夥伴提供了高可靠的服務,紅包總發放量近百萬,搶紅包的峰值流量達到3萬/秒,最快的一輪搶紅包活動3秒鐘所有紅包全部搶完,系統運行0故障。

紅包系統,類似于電商平臺的秒殺系統,本質上都是在一個很短的時間內面對巨大的請求流量,將有限的庫存商品分發出去,並完成交易操作。比如12306搶票,庫存的火車票是有限的,但暫態的流量非常大,且都是在請求相同的資源,這裡面資料庫的併發讀寫衝突以及資源的鎖請求衝突非常嚴重。就我們實現這樣一個紅包系統本身來說,面臨著如下的一些挑戰:

首先,到活動整點時刻,我們有15萬員工在固定時間點同時湧入系統搶某輪紅包,瞬間的流量是很大的,而目前我們整個鏈路上的系統和服務基礎設施,都沒有承受過如此高的輸送量,要在短時間內實現業務需求,在技術上的風險較大。

其次,公司是第一次開展這樣的活動,我們很難預知大家參與活動的情況,極端情況下可能會出現某輪紅包沒搶完,需要合併到下輪接著發放。這就要求系統有一個動態的紅包發放策略和預算控制,其中涉及到的動態計算會是個較大的問題(這也是為系統高吞吐服務),實際的系統實現中我們採用了一些預處理機制。

最後,這個系統是為了春節的慶祝活動而研發的定制系統,且只上線運行一次,這意味著我們無法積累經驗去對服務做持續的優化。並且相關的配套環境沒有經過實際運行檢驗,缺少參考指標,系統的薄弱環節發現的難度大。所以必須要追求設計至簡,儘量減少對環境的依賴(資料路徑越長,出問題的環節越多),並且實現高可伸縮性,需要盡一切努力保證可靠性,即使有某環節失誤,系統依然能夠保障核心的用戶體驗正常。

系統設計


紅包本身的資訊通過預處理資源介面獲取。運行中使用者和紅包的映射關係動態生成。底層使用內部開發的DB中介軟體在MySQL資料庫集群上做紅包發放結果持久化,以供非同步支付紅包金額到使用者帳戶使用。整個系統的絕大部分模組都有性能和保活監控。系統架構圖如圖所示。所有的靜態資源提前部署在了協力廠商的CDN服務上,系統的核心功能主要劃分到接入層和核心邏輯系統中,各自部署為集群模式並且獨立。接入層主要是對用戶身份鑒權和結果緩存,核心系統重點關注紅包的分發,紅色實線的模組是核心邏輯,為了保障其可靠性,我們做了包括資料預處理、水準分庫、多級緩存、精簡RPC調用、超載保護等多項設計優化,並且在原生容器、MySQL等服務基礎設施上針對特殊的業務場景做了優化,後面將為讀者一一道來。

 

優化方案

優化方案中最重要的目標是保障關鍵流程在應對大量請求時穩定運行,這需要很高的系統可用性。所以,業務流程和資料流程程要儘量精簡,減少容易出錯的環節。此外,緩存、DB、網路、容器環境,任何一個部分都要假設可能會短時出現故障,要有處理預案。針對以上的目標難點,我們總結了如下的實踐經驗。

1.數據預處理

紅包本身的屬性資訊(金額,狀態,祝福語,發放策略),我們結合活動預案要求,使用一定的演算法提前生成好所有的資訊,資料總的空間不是很大。為了最大化提升性能,這些紅包資料,我們事先存儲在資料庫中,然後在容器載入服務啟動時,直接載入到本地緩存中當作唯讀資料。另外,我們的員工資訊,我們也做了一定的裁剪,最基本的資訊也和紅包資料一樣,預先生成,服務啟動時載入。

此外,我們的活動頁面,有很多視頻和圖片資源,如果這麼多的用戶從我們的閘道即時訪問,很可能我們的頻寬直接就被這些大流量的請求占滿了,用戶體驗可想而知。最後這些靜態資源,我們都部署在了CDN上,通過資料預熱的方式加速用戶端的存取速度,閘道的流量主要是來自於搶紅包期間的小資料請求。

2.精簡RPC調用

通常的服務請求流程,是在接入層訪問用戶中心進行用戶鑒權,然後轉發請求到後端服務,後端服務根據業務邏輯調用其他上游服務,並且查詢資料庫資源,再更新服務/資料庫的資料。每一次RPC調用都會有額外的開銷,所以,比如上一點所說的預載入,使得系統在運行期間每個節點都有全量的查詢資料可在本地訪問,搶紅包的核心流程就被簡化為了生成紅包和人的映射關係,以及發放紅包的後續操作。再比如,我們採用了非同步拉的方式進行紅包發放到賬,用戶搶紅包的請求不再經過發放這一步,只記錄關係,性能得到進一步提升。

實際上有些做法的可伸縮性是極強的。例如紅包資料的預生成資訊,在當時的場景下我們是能夠作為本地記憶體緩存加速訪問的。當紅包資料量很大的時候,在每個服務節點上使用本地資料庫,或者本地資料檔案,甚至是本地Redis/MC緩存服務,都是可以保證空間足夠的,並且還有額外的好處,越少的RPC,越少的服務抖動,只需要關注系統本身的健壯性即可,不需要考慮外部系統QoS。

3.搶紅包的併發請求處理

春節整點時刻,同一個紅包會被成千上萬的人同時請求,如何控制併發請求,確保紅包會且僅會被一個用戶搶到?

做法一,使用加鎖操作先佔有鎖資源,再佔有紅包。

可以使用分散式全域鎖的方式(各種分散式鎖元件或者資料庫鎖),申請lock該紅包資源成功後再做後續操作。優點是,不會出現髒資料問題,某一個時刻只有一個應用執行緒持有lock,紅包只會被至多一個使用者搶到,資料一致性有保障。缺點是,所有請求同一時刻都在搶紅包A,下一個時刻又都在搶紅包B,並且只有一個搶成功,其他都失敗,效率很低。

做法二,單獨開發請求排隊調度模組。

排隊模組接收使用者的搶紅包請求,以FIFO模式保存下來,調度模組負責FIFO佇列的動態調度,一旦有空閒資源,便從佇列頭部把使用者的訪問請求取出後交給真正提供服務的模組處理。優點是,具有中心節點的統一資源管理,對系統的可控性強,可深度定制。缺點是,所有請求流量都會有中心節點參與,效率必然會比分散式無中心系統低,並且,中心節點也很容易成為整個系統的性能瓶頸。

做法三,巧用Redis特性,使其成為分散式序號生成器。(我們最終採用的做法)。

访问请求被LB分发到每个分组,一个分组包含若干台应用容器、独立的数据库和Redis节点。Redis节点内存储的是这个分组可以分发的红包ID号段,利用Redis单进程的自减数值特性实现分布式红包ID生成器,服务通过此获取当前拆到的红包。落地数据都持久化在独立的数据库中,相当于是做了水平分库。某个分组内处理的请求,只会访问分组内部的Redis和数据库,和其他分组隔离开。
分组的方式使得整个系统实现了高内聚,低耦合的原则,能将数据流量分而治之,提升了系统的可伸缩性,当面临更大流量的需求时,通过线性扩容的方法,即可应对。并且当单个节点出现故障时,影响面能够控制在单个分组内部,系统也就具有了较好的隔离性。

4.系统容量评估,借助数据优化,过载保护

由于是首次开展活动,我们缺乏实际的运营数据,一切都是摸着石头过河。所以从项目伊始,我们便强调对系统各个层次的预估,既包括了活动参与人数、每个功能feature用户的高峰流量、后端请求的峰值、缓存系统请求峰值和数据库读写请求峰值等,还包括了整个业务流程和服务基础设施中潜在的薄弱环节。后者的难度更大因为很难量化。此前我们连超大流量的全链路性能压测工具都较缺乏,所以还是有很多实践的困难的。
在这里内心真诚的感谢开源社区的力量,在我们制定完系统的性能指标参考值后,借助如wrk等优秀的开源工具,我们在有限的资源里实现了对整个系统的端到端全链路压测。实测中,我们的核心接口在单个容器上可以达到20,000以上的QPS,整个服务集群在110,000以上的QPS压力下依然能稳定工作。
正是一次次的全链路压测参考指标,帮助我们了解了性能的基准,并以此做了代码设计层面、容器层面、JVM层面、MySQL数据库层面、缓存集群层面的种种优化,极大的提升了系统的可用性。具体做法限于篇幅不在此赘述,有兴趣的读者欢迎交流。
此外,为了确保线上有超预估流量时系统稳定,我们做了过载保护。超过性能上限阈值的流量,系统会快速返回特定的页面结果,将此部分流量清理掉,保障已经接受的有效流量可以正常处理。

5.完善监控

系统在线上运行过程中,我们很需要对其实时的运行情况获取信息,以便能够对出现的问题进行排查定位,及时采取措施。所以我们必须有一套有效的监控系统,能够帮我们观测到关键的指标。在实际的操作层面,我们主要关注了如下指标:
服务接口的性能指标
借助系统的请求日志,观测服务接口的QPS,接口总的实时响应时间。同时通过HTTP的状态码观测服务的语义层面的可用性。
系统健康度
结合总的性能指标以及各个模块应用层的性能日志,包括模块接口返回耗时,和应用层日志的逻辑错误日志等,判断系统的健康度。
整体的网络状况
尽量观测每个点到点之间的网络状态,包括应用服务器的网卡流量、Redis节点、数据库节点的流量,以及入口带宽的占用情况。如果某条线路出现过高流量,便可及时采取扩容等措施缓解。
服务基础设施
应用服务器的CPU、Memory、磁盘IO状况,缓存节点和数据库的相应的数据,以及他们的连接数、连接时间、资源消耗检测数据,及时的去发现资源不足的预警信息。
对于关键的数据指标,在超过预估时制定的阈值时,还需要监控系统能够实时的通过手机和邮件实时通知的方式让相关人员知道。另外,我们在系统中还做了若干逻辑开关,当某些资源出现问题并且自动降级和过载保护模块失去效果时,我们可以根据状况直接人工介入,在服务不停机的前提前通过手动触发逻辑开关改变系统逻辑,达到快速响应故障,让服务尽快恢复稳定的目的。

6.服务降级

当服务器压力剧增的时候,如果某些依赖的服务设施或者基础组件超出了工作负荷能力,发生了故障,这时候极其需要根据当前的业务运行情况对系统服务进行有策略的降级运行措施,使得核心的业务流程能够顺利进行,并且减轻服务器资源的压力,最好在压力减小后还能自动恢复升级到原工作机制。
我们在开发红包系统时,考虑到原有IDC机房的解决方案对于弹性扩容和流量带宽支持不太完美,选择了使用AWS的公有云作为服务基础环境。对于第三方的服务,缺少实践经验的把握,于是从开发到运维过程中,我们都保持了一种防御式的思考方式,包括数据库、缓存节点故障,以及应用服务环境的崩溃、网络抖动,我们都认为随时可能出问题,都需要对应的自动替换降级策略,严重时甚至可通过手动触发配置开关修改策略。当然,如果组件自身具有降级功能,可以给上层业务节约很多成本资源,要自己实现全部环节的降级能力的确是一件比较耗费资源的事情,这也是一个公司技术慢慢积累的过程。

结束语

以上是我们整个系统研发运维的一些体会。这次春节红包活动,在资源有限的情况下成功抵抗超乎平常的流量峰值压力,对于技术而言是一次很大的挑战,也是一件快乐的事情,让我们从中积累了很多实践经验。未来我们将不断努力,希望能够将部分转化成较为通用的技术,去更好的推动业务成功。真诚希望本文的分享能够对大家的技术工作有所帮助。

感谢郭蕾对本文的审校。

 

(FYI) MGIndexs的威力

(FYI) MGIndexs的威力

MGIndexs的威力

bitmap 索引是一種索引形式,它以行的形式存儲一系列位的值。例如,如果您有一百萬條記錄,並且正在名稱列中搜索’bob’,那麼名稱列的b +樹可能類似於下面的圖片,並且當您在葉節點中找到’bob’時,您將得到一個BitArray,表示在該列中按照記錄號索引的’bob’的存在。所以如果BitArray的第3位是1,那麼’bob’在第3行等等。很容易看出這是非常緊湊和高效的存儲和檢索機制。

xxxx
*上面的圖片顯示了b +樹/位圖索引結構,而不是使用字典的MGIndex,但原理相同。

下面的例子顯示了實際的功能,您想要查詢以下的“Name =’bob’和Code = between(1,3)”,查詢處理器將採用過濾器,解析該過濾器的值並生成執行計劃如下:xxxx
從上圖可以看出,執行計劃是一系列BitArrays,您需要做的就是根據分析的過濾器和{AND,OR,NOT} BitArrays一起跟踪位算術邏輯。這些操作通常在亞毫秒範圍內,即使對於數百萬或數據庫中的行也是如此。結果是一個位置索引行查找到您從磁盤讀取的行內容,並發送給調用者,其中1表示讀取行,0表示跳過行內容(即100001 … – >讀取記錄號:1, 6,…)

 

 

ElasticSearch Auto Build With Docker

ElasticSearch Auto Build With Docker

近日因為大數據寫入與分析應用需求的關系

需要大量的使用到ElasticSearch

然而,每次在不同的環境要佈署ELK並配置不同的node來達到Sharding的效果的工作覺得繁瑣

因此手刻了一些半自動化部署Elasticsearch的Cluster的機制(同一台主機,不同的Docker),不同台需要參考使用Docker Swarn

 

以下是相關Shell Script操作,相關步驟如下:

0.設定相關的環境變數,例如java memory, elasticsearch version

1.詢問要建立的容器名稱

2.詢問export的port名稱(有2組,通常為9200、9300)

3.動態建立容器網路(若容器放在同一個網路下無綁定方向問題問題)

4.動態建立cluster的目錄(容器名稱會建立在此目錄下再建立對應容器的目錄)與相關config/esdatadir、log、data目錄,並設立權限

5.動態從版控取得對應的ElasticSearch的config,也可以自行改成放在某個統一位置取得 (網址:http://xxx.xxx.xxx.xxx:yyyy是git的位置)

6. sudo sysctl -w vm.max_map_count=262144(這句不知為什麼一定要執行..java環境才起的來)

7.啟動Docker(並結合相關參數)

#!/bin/bash


sudo docker ps -a
read -p "Enter your elasticsearch container_name(eg. es1, es2): " p_container_name

container_name=$p_container_name
java_memory="2g"
es_version="5.6.6"

read -p "Enter your elasticsearch container_name default export port1(eg. 9200, 9201, 9202): " port1

export_port1=$port1

read -p "Enter your elasticsearch container_name default export port1(eg. 9300, 9301, 9302): " port2
export_port2=$port2

network_name="es_net"

if [ ! "$(sudo docker network ls |grep $network_name)" ]; then
  sudo docker network create "$network_name"
  echo "network created!"
else
  sudo docker network ls | grep "$network_name"
  echo "network $network_name already exist!"
fi

echo "continue..."

DIRECTORY="es_cluster"

sudo cd /mnt

if [ ! -d "$DIRECTORY" ]; then
  sudo mkdir "$DIRECTORY"
fi

cd es_cluster
sudo mkdir "$container_name"
cd "$container_name"

echo "$container_name folder created!"

sudo git init
sudo git remote add origin "http://xxx.xxx.xxx.xxx:yyyy/scm/mes/elasticsearch.git"
sudo git pull origin master
sudo mkdir config/scripts
sudo mkdir esdatadir
cd esdatadir
sudo mkdir log
sudo mkdir data
sudo chmod 777 /mnt/es_cluster/$container_name/esdatadir/*
sudo chmod 777 /mnt/es_cluster/$container_name/config/scripts

echo "$container_name configuration setting done!"

cd /mnt/es_cluster/$container_name

sudo sysctl -w vm.max_map_count=262144

sudo docker run -d -v "/mnt/es_cluster/$container_name/esdatadir/data":/var/lib/elasticsearch \
 -v "/mnt/es_cluster/$container_name/esdatadir/log":/var/log/elasticsearch \
 -v "/mnt/es_cluster/$container_name/config":/usr/share/elasticsearch/config \
 -p "$export_port1:9200" -p "$export_port2:9300" --name $container_name -e "bootstrap.memory_lock=true" \
 -e ES_JAVA_OPTS="-Xms$java_memory -Xmx$java_memory" --ulimit memlock=-1:-1 \
 --restart always \
 --net "$network_name" \
 elasticsearch:$es_version

echo "$container_name docker container up done!"
00-01 關於前端開發生態圈的總整理(Base on React)

00-01 關於前端開發生態圈的總整理(Base on React)

有鑑於最近旁聽Front-end meeting後,發現許多套件名詞擺在一起的時候好像會迷失方向,雖然一部分知道大概功能,但有一部分套件的角色與在整體生態圈的定位仍有些陌生

因此整理以下提供一個整體性的概念

React Tech Stack

前端框架工具介紹文:https://gist.github.com/weihanglo/ccefb5107b1d60669ded1177e6f954ca#es6babel

 

ReactJs:

React僅僅是Web應用程式中的視圖層(View),定義上不能算是框架,應用程式中可以將之分解成一個一個小的組件,因此你的Web站台可以是經由各種型式的元件(Component)組裝而成,當Web的問題與場景愈來愈具備挑戰性的時候,元件導向開發(Component-Driven Development)可以達成問題分解解決與並易於重用,而React的開發方法就是符合元件導向開發模式。

React之於元件、元件之於類別的使用方法初探,可見:https://paulfun.net/wordpress/?p=471

React的革命:Virtual Dom

當React的View engine要追踪模型更改並將其應用於DOM ,這個動作又稱為渲染(Rendering),React採用了Virtual Dom的演算法

Virtual Dom是可以加快渲染速度的機制,Dom的更改以往是透過在Browser層直接操作真實網頁上的Dom,React的差異化算法是使用Tree的形式去計算,在內存中,直接建立一組假的Dom,在內存中處理Dom的效能大幅提升,React同時也會盡可能的保持Dom大部分不變,保持最小幅度的更新

關於虛擬Dom的秘密:https://conferences.oreilly.com/fluent/fluent2014/public/schedule/detail/32395
React.js Tutorial React re-render

在Server端渲染?

因為react對DOM的處理是使用假的DOM,都在內存中進行,因此更可以將此渲染工作拋到Server上。僅僅只剩下讓Browser客戶端處理事件就好

註:React呈現的HTML標記包含data-reactid屬性,這有助於React跟踪DOM節點。

更多好文:https://blog.risingstack.com/the-react-way-getting-started-tutorial/

進一步:JSX語法糖

JSX的特性是讓標籤更加語意,更加簡化,可以整合JavaScript,以單單組合Div包含Class定義的標籤工作,JSX讓我們可以有如使用Template一樣簡潔:

一般JavaScript語法
1-1
JSX語法
2-1

當然 JSX 並非強制使用,你也可以選擇不用,因為最終 JSX 的內容還是會轉化成 JavaScript(瀏覽器只看的懂 JavaScript)

更多好文:https://blog.techbridge.cc/2016/04/21/react-jsx-introduction/ 、https://reactjs.org/docs/jsx-in-depth.html

強化React

React-Flux

React本身只是個MVC架構中的View(視圖)函式庫,沒有類似Model或Controller的角色,這個最初是由React的最早的創作小組所提出的概念性架構,雖然利用Action與Store解決了React 控制視圖的問題,但Flux發表至今並沒有太多源碼更新, 一方面被認為學習曲線高,另一方面是很妥善的設計,重用程式碼不少,因此建議參考Redux。

React Flux

更多好文:http://eddychang.me/blog/javascript/94-flux-concept.html

React-Redux

Redux 是管理應用狀態的js庫。是單向數據流應用架構中更優雅的實現,Redux 和 React 之間無依賴關係。Redux支持 React、Angular、Ember、jQuery 甚至純 JavaScript。

相較於Flux,Redux現在發展得很好,學習資源與使用者很多,也有很多週邊的擴充或應用可用,當遇到問題時比較容易找到解答,所以Redux應該是首選。

React Redux

更多的Redux:https://noootown.gitbooks.io/deeperience-react-native-boilerplate/content/Redux/Redux%20&%20React.html

 

NodeJs:

Node.js模塊系統,它擁有強大的npm套件管理機制。我們將以“Node風格”編寫我們的代碼,並引用Require我們所需的一切。React可作為一個單獨的npm package引用進來。

現在我們使用Node來管理系統的相依性(統一ES6),我們幾乎所有的解決方案都有npm可以來使用,那接下來就是選擇打包工具。

WebPack+Babel

WebPack

Webpack 是德國開發者 Tobias Koppers 開發的模組整合工具。它的核心功能如下:

  • 可同時整合 CommonJS 和 AMD 模組
  • 轉換 JSX, Coffee Script, TypeScript 等
  • 分散封裝專案使用的程式碼,使載入頁面時只需載入當頁所需的程式碼以加速載入速度
  • 整合樣式表 (css, sass, less 等)
  • 處理圖片與字型
  • 建置 production-ready 的程式碼 (壓縮)

更多好文:https://rhadow.github.io/2015/03/23/webpackIntro/

 

Babel

Babel是一個java script的Transpiler。Babel 轉換ES6的代碼到ES5
我們稱這個過程叫作transpiling。我們享用es6的功能與好處,但至今仍必須確保僅支援es5的瀏覽器仍然能運作。
Babel的另一個重要工作就是去瞭解JSX。Babel會編譯我們的jsx成ES5的js。讓我們的瀏覽器可以直譯與執行。

 

整理至此,仍發現,每個環節其實都可以更深入的探討,不過對於overall的角色定位已經有了更具體的認識,因此我整理總心智圖如下(可能有誤或缺,歡迎指教):

放大:https://paulfun.net/wordpress/wp-content/uploads/2018/03/591.png

 

若有新的消息或觀念上的修正,後續會再持續更新本文

結合docker swarm 建置ElasticSearch Cluster

結合docker swarm 建置ElasticSearch Cluster

最近因為有要整合ELK方案的需求,因此又再度接觸了這個曾經相當熟悉的工具,還記得那個時候(也才1年多前),還是在2.x版左右,kibana的介面還是長這個樣子的時候。

在先前的公司,有平台團隊專門架設這個服務提供RD團隊整合使用,對於使用是絲毫不陌生,但是建置卻完全沒有經驗。沒想到現在已經轉眼間來到了6.1版本,介面跟架構看起來調整的更鮮艷與簡潔(?),我知道ELK目前在windows上面仍然是有一鍵安裝的版本。對於開發要測試的需求上已經相當足夠(連結),但因為我們目前對外主機主要都是CentOS,因此我這次預計實作如下:

假設我有2台CentOS主機的情境下(一台稱為A Host, 一台稱作B Host),如何透過各別安裝elasticsearch的docker 容器,來串連成elasticsearch 的集群。

同一台的多個容器理論上是相對簡單的,而這邊會遇到的問題,主要的關鍵就是如何解決跨主機間docker容器的通信問題,網路架構也通常都是應用程式間管控較為複雜的一個部分

這邊我打算採用Docker Swarm,Swarm為Docker自行開發的容器調度工具,其中的跨主機建立overlay網路功能看起來是非常符合我的情境需求,由於是Docker平台內建工具,看起來並不需要額外的安裝與學習,因此我先從這套工具開始著手,至於關於其他容器調度工具還包含了,Kubernetes、Mesos等。甚至現在的顯學看起來是Kubernetes(k8s),有許多的討論也直指要如何決定該採用哪種方案。

目前看到這一篇(連結)是相對從各面向都有討論到,有興趣的可以看看

Docker swarm的架構圖(來自Docker官網)

從docker 1.9版開始,DockerSwarm已經是內建工具,因此透過以下指令,就可以將主機宣告成DockerSwarm的Manager主機(第一台總是master嘛)

sudo docker swarm init

若有成功啟動DockerSwarm的話,會提示以下訊息

docker swarm join –token SWMTKN-1-5wsc3yya3e87w2e84jkzbpieubpxr9v6qwnbj87m6t2ynv7kxm-8q5amih216368atw8adklx24f %A Host IP%:2377
這個hint很明顯就是我們可以在其他的Host(稱作Worker),透過以上指令來加入到Manager下的叢集中。同時他是透過A Host的2377 Port來進行串連
但假如希望建立起來的Swarm使用其他Port的話,我們可以使用以下指令來做
docker swarm init –advertise-addr=%A Host IP% –listen-addr %A Host IP%:2377
所以我們也可以使用這句語法檢查,是否有啟動了這兩個Port

sudo netstat -nap | grep ^tcp.*dockerd

正常應該會列出對應的2377與7946

tcp6 0 0 :::7946 :::* LISTEN 28184/dockerd
tcp6 0 0 :::2377 :::* LISTEN 28184/dockerd

接著我們在B Host上,輸入剛剛建立完swarm manager 所提示的加入語法

   docker swarm join –token SWMTKN-1-5wsc3yya3e87w2e84jkzbpieubpxr9v6qwnbj87m6t2ynv7kxm-8q5amih216368atw8adklx24f %A Host IP%:2377

在這邊若有無法加入的情況的話,可以往防火牆先檢查,若是防火牆確定有通(telnet看看),那我這邊有遇到原本怎樣都加入不了,但是重開機、重啟docker後,就可以加入的情況。

若Worker成功加入Swarm的話,我們可以在A Host輸入以下指令確認是否節點都完整

docker node ls

既然A-B Host已經加入了同一個網路架構,接著就是建立覆疊網路

sudo docker network create   –driver overlay  –attachable es_net
註:這邊–attachable 若後續要透過run 語法加入指定網路的話,參數一定要加,是不是Docker Compose就不用加,我不確定
成功建立overlay網路後,我們就可以透過以下指令看到我們的網路已經被建立起來,SCOPE是顯示swarm , Driver是顯示overlay
sudo docker network ls
現在網路架構已經看似完成,接著我們來準備Elasticsearh的服務配置吧
這邊我寫了一個腳本來自動建立elasticsearch的相關目錄,目前只要elasticsearch會異動到的檔案目錄權限要開啟來,否則之後容器啟動後,會有權限例外產生:
腳本中改777的部分都是因為遇到所以加上去的,可以自行改成合適的權限配置。
sudo cd /mnt
sudo mkdir elasticsearch
cd elasticsearch
sudo git init
sudo git remote add origin "http://git server ip/scm/mes/elasticsearch.git"
sudo git pull origin master
sudo mkdir config/scripts
sudo mkdir esdatadir
cd esdatadir
sudo mkdir log
sudo mkdir data
sudo chmod 777 /mnt/elasticsearch/esdatadir/*
sudo chmod 777 /mnt/elasticsearch/config/scripts

cd /mnt/elasticsearch

config下的檔案,因為A, B Host各自擁有自己的獨立硬碟,因此我假設他們的環境配置應該都一致,jvm.options、log4j2.properties、檔案都一樣,只有Elasticsearch的yml檔會有些許差異:

path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
cluster.name: meso_es_cluster1
node.master: true #if worker then mark it
node.name: ${HOSTNAME}
network.host: 0.0.0.0
discovery.zen.ping.unicast.hosts: ["A HOST IP", "B HOST IP"]
以上都完成以後,就是來啟動Docker 容器的時候了,以A Host為例,啟動語法如下,B Host只是改–name成es2
sudo docker run -d -v /mnt/elasticsearch/esdatadir/data:/var/lib/elasticsearch -v /mnt/elasticsearch/esdatadir/log:/var/log/elasticsearch -v /mnt/elasticsearch/config:/usr/share/elasticsearch/config -p 9200:9200 -p 9300:9300 --net=es_net --name es1 --security-opt seccomp=unconfined -e "bootstrap.memory_lock=true" -e ES_JAVA_OPTS="-Xms1g -Xmx1g" --ulimit memlock=-1:-1 elasticsearch:5.6.6

分別執行後,我們可以看到docker ps 下是否狀態是正常在up的。

補充說明,啟動es容器時,我曾經發現會有以下的錯誤訊息:max virtual memory areas vm.max_map_count [65530] is too low(請透過docker logs查看)
這個問題若資源分配問題的話,可以透過以下指令解決:
sudo sysctl -w vm.max_map_count=262144
 但我也有一個疑問,logs是記錄容器內部的jvm的錯誤,但是我是在容器外部下語法可解決,這個原因我確實不清楚就是了..

若都是正常,我們可以透過elasticsearch的Api來檢查我們的es cluster是否有正常運作起來

從瀏覽器輸入A Host IP:9200,會返回相關json資訊,若是如下,我們可以認定cluster有啟動,而且服務版本正確,

可以看到name的部分,就正是各別主機上Docker 容器的name,而cluster_name因為配置所以會一致、而若有正確加入的話,cluster_uuid必須要一致
註:我在實作的時候,就一直遇到B Host啟動的起來,但是他無法加入cluster,所以cluster_uuid一直顯示為_na_,後來竟然是整個docker服務重啟就好了,這個羅生門,讓我相信3R的救命招(Recycle, Reset, Reboot),測試期間,也可以透過docker exec -it B HOST Container IP ping -c 3 A_Host_IP來看看是否可以透過容器內解析出跨主機的另一個同網路下的容器名稱

如何觀察Elasticsearch的Cluster狀態呢?具瞭解在es 2.x版本的時候,有一套es的plugin,叫作kopf,是拿來可以直接透過es api來監控es服務的工具

後來5.x版以後,發現plugin功能被拔掉了,取而代之是獨立的”cerebro“套件可以達成完全一樣的功能,看來是同個作者寫的。

介面如下

只要在Node Address輸入A Host es容器名稱:9200(注意容器互連必須透過容器名稱,而不能只是ip),就可以看到以下的dashboard,相當的方便,除了可以即時監控Node的資源使用狀況,也可以看到目前的index數量與使用狀況、空間

cerebro 這邊,只需要透過以下指令,就可以啟動:

sudo docker run -d -p 9000:9000 –name cerebro –net es_net yannart/cerebro:latest

同場加映Kibana 5.6.6的啟動指令

sudo docker run –name meso-kibana –link es1:elasticsearch –net es_net -p 5601:5601 -d kibana:5.6.6
透過A Host IP:5601啟用Kibana後,就可加入已經自定好的Index pattern來使用囉
React環境安裝記錄與React的首次坦誠相見

React環境安裝記錄與React的首次坦誠相見

環境需求:
Nodejs: https://nodejs.org/download/17
    nodejs是用來啟動babel與webserver
nodejs安裝後
node -v
可以用來查詢版本
v7.10.0
接著切換到範例的react-app目錄
標準的node-react app開發目錄下都會有pakcage.json
描述著相依的套件與環境的關系
package.json範例:
{
  "name": "badminton_fun_react",
  "version": "1.1.0",
  "author": "paul",
  "scripts": {
    "go": "open http://localhost:48761; npm run server",
    "e2e": "nightwatch",
    "test": "./node_modules/.bin/concurrently -k 'npm run server' 'npm run e2e'",
    "start": "npm run server",
    "server": "live-server public --host=localhost --port=48761"
  },
  "private": true,
  "devDependencies": {
    "concurrently": "^2.2.0",
    "live-server": "^1.2.0"
  },
  "dependencies": {
    "http-server": "^0.11.1"
  }
}
接著切換到package.json的同目錄下,執行
npm install
雖然看pakcage.json才幾個相依,但實際裝完,還是會有13x個套件被安裝到同層node-modules目錄
接著就啟動
npm start (yarn start)
註1:node install的時候,早上我老是遇到git的目錄問題No git binary found in $PATH #1389)
原本的相依套件寫的是    “live-server”: “git://github.com/acco/live-server.git”
這會要求你環境參數必須要有git的呼叫路徑,但我安裝了git-for-windows,也確認環境變數有設定,卻無法從powershell去透過nodejs呼叫,不知為啥
後來直接把live-server的參考變成版本號,這套就過了!
 註2: node start啟動的時候,我老是會出現error,細看log,也只看的到12 info lifecycle [email protected]~start: Failed to exec start script
因此猜測是start的時候有問題,後來發現原始的start語法是    “server”: “live-server public –host=localhost –port=3000 –middleware=./disable-browser-cache.js”
把–middleware的參數拿 掉就可以work了!但怎麼讓原語法正常運作,我不確定,因為這感覺是live-sever的機制,可能版本也有關系!
進入本文前:
專案目錄:
public就是我們ReactApp的程式根目錄,這我們在package.json的server start有把路徑名稱傳進去
裡面有index.html是首頁畫面
React之於元件
react app就是全部由元件(components)所組成,component,並非僅僅指的是UI的component,它們在同一個地方自我包含了markup, view logic, 甚至css
這個特性使得react component可以重覆被利用
註:react的資料流與互動性已經被嚴謹的定義。因此當元件的輸入一旦變動,框架就會自動重新刷新component。
這是react框架所保證的UI強健性(給定一組input,其相關的元件作為output,只要掛載在page上),就會永遠一致

元件之於類別

React的元件是extends自React.Component的ES6類別
我們參考成React 的變數。
每一個extends來的compoent類別,都會有獨立方法render(), render()也是唯一component必須一定要有的方法
React框架使用這個render方法的回傳值來決 定什麼要到page上
共有以下2種方式可創建React Component
1.
2.
render方法內,return包含的markup,這樣的用法不同於傳統的java script!
這種語法是利用了JSX(Java script eXtension syntax)。JSX是facebook所創造出來的一個標準
使用JSX可以讓我們在component的view裡面直接撰寫與html熟悉的語法,縱使不是必要的作法
但教學中還是會滿常見的

JSX

React元件最終會渲染Html到瀏覽器上,因此render()方法的職責就是描述view該如何表現
React建置app的方式是透過假的dom,通稱虛擬dom(virtual DOM)後續會再詳細描述,
React允許我們在元件的內部使用java script來描述Html。
因此jsx就是被創造出來,讓我們可以使用java script來表現html時,能更貼近html
來看看以下的差異
java script 語法
1-1
1-2
JSX格式
2-1
2-2
因此我們說,JSX就是透過java script表達的一個輕量抽象層。其易讀性改善的效益非常巨大。也降低了新進開發人員的學習曲線

Babel

Babel是一個java script的Transpiler。Babel 轉換ES6的代碼到ES5
我們稱這個過程叫作transpiling。
我們享用es6的功能與好處,但至今仍必須確保僅支援es5的瀏覽器仍然能運作。
Babel的另一個重要工作就是去瞭解JSX。Babel會編譯我們的jsx成ES5的js。讓我們的瀏覽器可以直譯與執行。
若我們要動態Babel進行transpile, 只需要告訴我們的java script runtime。我們的程式需要被Babel complie
當你的component程式都正常運作,page也正常宣告,console會show以下的訊息
You are using the in-browser Babel transformer. Be sure to precompile your scripts for production
為了快速測試,我們採用了in-browser的babel transpile,正式環境建議使用其他更合適的策略
 未完待續
將你的Nodejs React Web App Docker化

將你的Nodejs React Web App Docker化

假設目前已選用React的框架,我試驗的這套名叫https://github.com/zuiidea/antd-admin

方案目錄結構如下:

主要react程式都放在src的目錄下

node_modules是在npm install的時候,才會把相依套件安裝在這個目錄裡面

以下步驟的目的主要是希望啟動一個node.js 的web server,將我們的react web啟動後,可以透過外部編修程式來除錯與開發

production環境,不需要另外開放目錄了,而且依正常nodejs 的production build,就也編譯好了,所以不會因為src的code異動而影響!

 

 

1.在webapp的根目錄,建立DockerFile (touch Dockerfile)

# You should always specify a full version here to ensure all of your developers
# are running the same version of Node.
FROM node:7.8.0

# Override the base log level (info).
ENV NPM_CONFIG_LOGLEVEL warn

# Install and configure `serve`.
RUN npm install -g serve
CMD serve -s build
EXPOSE 8000

# Install all dependencies of the current project.
COPY package.json /node-app/package.json
#COPY npm-shrinkwrap.json npm-shrinkwrap.json
# The -g switch installs the Express Generator globally on your machine so you can run it from anywhere.
RUN cd /node-app;npm install

# Copy all local files into the image.
COPY . /node-app

# Build for production. 正式機時,要解除註解重建images,不然會大大影響效能
# RUN cd /node-app; npm run build

2. 建立Docker Images

docker build -t react-test-docker .

3. 啟動Docker Container(註,因為沒有標記production build,所以要指定start的路徑,start後,要加上prefix參數,指定你的web app目錄)
sudo docker run -d \
            -v /mnt/nodejs-app/mesodashboard/antd-admin-dashboard/src:/node-app/src \
            -p 8000:8000 \
            –name react-test-server \
            react-test-docker \
            npm start --prefix /node-app
[Design Pattern] 簡單工廠與工廠方法模式

[Design Pattern] 簡單工廠與工廠方法模式

近期雖然已經從c#轉向python,但發現可以用到OO的地方,可以建造工廠的情境也非常地多,索性把之前的文章拿來反覆咀嚼,挺有趣

這一篇雖然是2010年寫的,介紹重量級的工廠模式-定義來自於深入淺出設計模式一書,而本例重新構思案例,適圖舉一反三 😛

 

前幾篇提到了三個不同型式的Design Pattern,分別是Strategy(角色扮演)、Observers(Blog Rss)以及Decorator(早餐店)。傳送門

可以發現,在現有的OO法則下,可以達到在類別設計與使用上的彈性與擴充

我們可以在執行期new出一個具有彈性的具像類別以及透過各種型式來動態地變更物件

 

因為透過介面來操作物件,在類別的操作變得彈性了

就像先前角色扮演的例子一樣,可以new一個Character類別,叫作弓箭手的”角色”

Character Mary = new Archer(); //宣告一個角色叫作Mary,給予他具象的弓箭手類別

上述看到,當『new』的時候,就要聯想到『具象』

什麼是具象?

1

上圖中抽象與具象剛好是一種相對的關係,我們透過不同的具象類別(不同職業的角色)來描述所謂的角色

具象類別就是我們要真正”實體化”的類別,當我們使用new的時候,雖然透過Character作為介面讓程式具有彈性,但是我們還是得建立具象類別(Archer)的實體。

所以用的確實是實踐,而不是介面。這是一個好問題,程式綁著具象類別,將導致程式容易損壞且沒有彈性!

 

為什麼??當有一群相關的具象類別的時候,通常會寫出這樣的程式:

  Character Mary;
  int select=1; //選擇不同職業的角色
   
         if (select==1)
         {
             Mary = new Archer();
         }else if(select==2)
         {
             Mary = new Knight();
         }else if(select==3)
         {
             Mary = new Magician();
         }

有一大堆不同的角色類別,但是必須要到執行期才知道該實體化哪一個職業!

當看到這樣的程式碼,一旦有變動或擴充,就必須打開這個源始碼,加以檢視與修改。通常這樣修改過的程式碼,將造成部份系統更難以維護與更新,而且也有犯錯的可能性。

 

然而,在技術上,new這個字眼沒有問題,真正的犯人是『需求的改變』,以及這個的改變對new的影響力!

回顧一下裝飾者模式所說的關閉開放法則。

類別應該開放,以便擴充;應該關閉,禁止修改

如果針對介面寫程式,可以隔離掉以後系統可能發生的一大堆改變。
介面可以透過多型進行新類別的實踐,而不需要動到原本的程式碼。
但是當程式碼使用了大量的具象類別時(例如角色的職業),那麼一互加入新的具象類別,就必須改變程式碼。

也就是說程式並非『對修改關閉』。當想要擴充新的職業,竟然要重新開放應該封閉的程式碼。

那麼解決方法並不是無跡可尋的,回想看看第一個OO守則

找出會改變的地方,然後將這部份抽離出來

剛好可以用來處理我們的老朋友『改變』!

 

首先,找出會改變的東西。

 

先來個情境:

現在假如我們仍然是一個手機的開發商,目前有一個提供手機製造並出貨的服務,現在準備發展手機銷售部門,為了實行需求導向

這間公司由不同的手機款式需求,建立原型,再實行製造流程並出單。

身為這間手機製造商的訂單系統開發者,程式碼可能會這麼寫:

  CellPhone orderCellPhone(){
      CellPhone cellphone= new CellPhone(); //建立手機原型(確定硬體設計圖與軟體規格)
    
      cellphone.design();  //動工設計
      cellphone.modeling(); //動工建模
      cellphone.combine(); //動工組合手機
      cellphone.box(); //動工包裝
      return cellphone;  //出貨
  }

註:為了讓系統有彈性,我們會希望CellPhone是一個抽象類別或介面,但是這樣的話,這些類別或介面就無法實體化了…註2,軟體的安裝在組合手機的以後會安裝上去(裡面又呼叫了install()方法)

但是當我們需要更多型式的手機…這時勢必增加一些程式碼,來決定適合的手機具象類別,然後製造這隻手機:

假如有三款型式,分別是Business(商用)、Sport(戶外防水)、Normal(陽春款)

程式碼就會變成:

 CellPhone orderCellPhone(String style){
     CellPhone cellphone;
     if (style.equals("Business")){
         cellphone= new BusinessPhone();  //實踐商用手機
     }else if (style.equals("Sport")){
         cellphone= new SportPhone();     //實踐運動手機
     }else if(style.equals("Normal")){
         cellphone= new NormalPhone();    //實踐陽春手機
     }
  
     cellphone.design(); //設計
     cellphone.modeling(); //建模
     cellphone.combine(); //組裝
     cellphone.box(); //包裝
     return cellphone;
 }

市場競爭日趨激烈,壓力來自於必須快速推出更多型式與型號的手機。而新款的Sport手機要上市,舊款的Sport也要因此從型錄中下市:

  CellPhone orderCellPhone(String style){
  CellPhone cellphone;
  if (style.equals("Business")){
      cellphone= new BusinessPhone();
  //}else if (style.equals("Sport")){  //舊款要下市的手機
  //    cellphone= new SportPhone();
  }else if (style.equals("Music")){   //新款的音樂手機
      cellphone= new MusicPhone();
  }else if (style.equals("Sport2")){  //2代的Sport手機
      cellphone= new Sport2Phone();   
  }else if(style.equals("Normal")){
      cellphone= new NormalPhone();
  }
   
  cellphone.design(); //設計
  cellphone.modeling(); //建模
  cellphone.combine(); //組裝
   cellphone.box(); //包裝
   
  return cellphone;
   
  }

此程式碼並沒有對修改封閉,看到了嗎?如果手機開發商一改變型錄,供應不同的機型,就得修改那些if else。這裡假以時日一遇到改變,就必須一改再改。

而不想改變的地方是設計、建模與組裝的流程,因為手機的製造流程都持續不變,所以這部分的程式碼不會改變,只有手機的需求的款式用途會改變。

所以很明顯,如果實體化某些具像類別(例如音樂手機、運動手機…etc)將使orderPhone( )出問題,而且也無法對它修改關閉,但是目前已經知道哪些會變哪些是不變的,所以是時候來使用封裝了!!

 

首先要先封裝建立物件的程式碼!記得,封裝的目標總是那些最常改變的地方,要把建立物件的程式碼從orderPhone方法中抽離,然後將部分的程式碼搬到另一個物件中,這個新物件只管如何製造手機的原型,如果一旦任何物件想要製造手機原型,找它就對了!

稱這個新物件為『工廠』:PhoneFactory

工廠是負責建立物件的細節,一旦有了PhoneFactory,OrderPhone就變成了PhoneFactory的客人,當需要建立手機的時候,就叫手機工廠做一個。

而以後orderPhone就不需要知道音樂手機還是商用手機,orderPhone只需要從工廠得到一隻手機原型,而這個手機實踐了CellPhone的介面,以呼叫設計、建模、組裝方法,來完成手機。

 

先來建立一個簡單的手機工廠,要先從工廠本身開始,定義一個類別,為所有手機封裝建立物件的程式碼。程式碼就像這樣:

   class PhoneFactory
   {
       public CellPhone createCellPhone(string style){
           CellPhone cellphone = null;
           if (style.equals("Business"))
           {
               cellphone = new BusinessPhone();
               //}else if (style.equals("Sport")){ //舊款要下市的手機
               //    phone = new SportPhone();
           }
           else if (style.equals("Music"))
           {   //新款的音樂手機
               cellphone = new MusicPhone();
           }
           else if (style.equals("Sport2"))
           {  //2代的Sport手機
               cellphone = new Sport2Phone();
           }
           else if (style.equals("Normal"))
           {
               cellphone = new NormalPhone();
           }
    
           return cellphone;
       }
   };

這樣做有什麼好處?似乎只是將問題搬到另一個物件罷了,問題依然存在啊?

提醒一下,OO中封裝還有一個好處就是reuse,雖然目前只看到orderCellPhone()方法是他的客人,然而,可能還有CellPhoneShopMenu類別,會利用這個工廠來取得手機的描述與單價。可能還有宅配的功能需要它,會有不同的方式來處理手機(CellPhoneShop)。所以把建立手機的程式碼包裝進一個類別,當以後實踐改變時,只需要這個類別即可。

 

接著是該來修改客人這一邊的程式碼了

  public class CellPhoneSale
  {
      CellPhoneFactory factory;
   
      public CellPhoneSale(CellPhoneFactory factory)
      {
          this.factory = factory;
      }
   
      public CellPhone orderCellPhone(String style)
      {
          CellPhone cellphone;
   
          cellphone = factory.createCellPhone(style);
   
          cellphone.design(); //設計
          cellphone.modeling(); //建模
          cellphone.combine(); //組裝
          cellphone.box();//包裝
   
          return cellphone;
      }
  }

 

看到第三行,我們為CellPhoneSale加上一個變數,以便記錄CellPhoneFactory,同時在建構式的時候,將一個Factory的參數帶入(就類似於前幾個Design Pattern實作時的方式。

再看到第14行,我們不再使用具象實體化,而是把new代換成工廠的方法了!

 

上述就是簡單工廠(SimpleFactory)!!但很遺憾,這不是設計模式,這只是一種coding的習慣!常有人誤認這個就是工廠模式的精髓。不過不要因為簡單工廠不是一個真正的模式,就忽略了它的用法。先來看看新版本的手機店的類別圖。

 

2

 

客戶–>工廠–>產品(抽象)—>產品實踐。這樣的關係可以讓具象產品都必須實踐CellPhone的介面(抽象類別也算,這邊是泛指實踐某個超型態(可以是介面或類別)),無呰以來就可以被工廠建立並傳回給客戶。

各種型式的手機都可以override 超類別的方法,實踐自己的製作方式。

 

工廠模式完了嗎?

接下來登場的是兩個重量級的模式,然後他們都是工廠…而手機只會愈來愈多…,接著看吧!

 

事業愈做愈大,手機開發經營有成,擊敗了其他的手機開發商,現在大家都希望產業移植,到大陸開分公司處理大陸的手機銷售,而大陸也有一個製造工廠(地區性質不一定),去賺更多的錢。產業準備複製,不過除了我們,經營者也會開始擔心!到大陸設廠的品質會不會下降呢?,會不會搞糟了原本的品質水準呢?因此希望大陸那邊都能利用自己的程式碼,好讓手機製造的流程不會改變。這樣在未來甚至可以把所有的生產流程都移到大陸,不過剛開始嘛,台灣工廠專門快速回應台灣的客戶,而大陸客戶由大陸工廠負責快速回應製作手機。

剛剛好呢,已經有一個作法,如果利用CellPhoneFactory,寫出二種不同的工廠,分別是ChinaCellPhoneFactory以及TaiwanCellPhoneFactory,那麼各地的手機商,就都有適合的工廠可以使用。這是一種作法…來看看會變成什麼樣子!

  TaiwanCellPhoneFactory twFactory = new TaiwanCellPhoneFactory();
  CellPhoneSale twSale = new CellPhoneSale(twFactory);
  twSale.orderCellPhone("MusicPhone");
   
  ChinaCellPhoneFactory caFactory = new ChinaCellPhoneFactory();
  CellPhoneSale caSale = new CellPhoneSale(caFactory);
  caSale.orderCellPhone("MusicPhone");

透過建立台灣工廠,然後建立一個台灣銷售部來處理台灣的訂單,將台灣工廠放到建構式參數中,當製造手機時,就會產生台灣區的手機

然而在推動不同地區的工廠時,經營者漸漸地發現,兩岸三地的喜好差異不小,所以公司在大陸建立了一個經營團隊,專門負責大陸手機的販售

他們調查大陸的需求以後,發現手機除了簡繁體外,他們對於手機的系統、功能、組件、包裝,配件都有不同的需求,擁有屬於自己地區性的手機原型(需求產生的),而推廣工廠的時候,發現不同的手機銷售部門雖然是用我們的手機,但是在製造上仍有不同的作法。所以我們希望建立一個框架,把不同的區域性的建立手機原型、製造與銷售手機綑綁在一起,卻又不失彈性。在建立簡單工廠之前,製造手機的程式碼綁在CellPhone Sale中,但這麼做卻沒有彈性。那麼怎麼做才能魚與熊掌兼得呢?

 

給不同銷售地區使用的原型框架

嗯…有個作法可以讓區域差異的手機活躍於CellPhoneSales類別,同時讓這些不同地區的分公司,依然可以自由製作該區域的產品組合。

 

首先,先把createCellPhone放回CellPhoneSale之中,不過要設定成抽象方法,然後要將地區性的銷售寫一個CellPhoneSale的次類別

來看看變了哪些東西。

  public abstract class CellPhoneSale
   {
       public CellPhone orderCellPhone(String style)
       {
           CellPhone cellphone;
  
           cellphone = createCellPhone(style);
  
           cellphone.design(); //設計
           cellphone.modeling(); //建模
           cellphone.combine(); //組裝
           cellphone.box();//包裝
  
           return cellphone;
       }
  
       public abstract CellPhone createCellPhone(String style);
   }

現在CreateCellPhone被移回了CellPhoneSale,而且工廠方法必須宣告為抽象方法。CellPhoneSale類別要宣告成抽象的

因此現在已經有一個CellPhoneSale作為超類別,讓每個區域型態(Taiwan、China),都繼承這個CellPhoneSale,每個次類別各自決定如何製造手機。看看要如何進行吧

 

目前處理手機銷售類別已經有一個不錯的訂單系統,由orderCellPhone()方法負責處理訂單,不過在地區性差異上,是要讓createPhone能因應改變,我們希望對訂單的處理能夠一致。

而各區域手機銷售單位之間的差異,在於製作手機原型上的差異(例如台灣有較高階的商務手機2代也有娛樂手機,但大陸沒有娛樂手機,而有運動手機)。

,做法就是讓大陸與台灣的分公司各個次類別去定義自己的createCellPhone()方法,所以會有一些CellPhoneSale的具象次類別,每個次類別都有自己區域不同的需求差異。

這仍然適用於CellPhoneSale框架,和orderCellPhone合作無間。

3

新的定義是每個次類別都會推翻createCellPhone方法,具有自主權地去定義自己區域需求建立原型的方法,同時使用CellPhoneSale所定義的orderCellPhone的方法,甚至可以把orderCellPhone宣告成final,以防止被分公司推翻訂購流程。

而利用不同地區的次類別,createCellPhone方法就會建立不同區域性的手機。

關於這方面,從CellPhoneSale的orderCellPhone方法觀點來看,此方法被宣告在抽象的類別內,但是在具象的次類別中實踐,雖然orderCellPhone對CellPhone物件做了許多事

但是它『並』不知道,這個CellPhone物件的具象類別為何。這正是鬆綁的一種行為(decouple)!當orderCellPhone呼叫createCellPhone()的時候,不同區域次銷售單位類別將負責透過不同的需求建立手機原型。而當然是由不同區域各自決定手機的需求來建立原型。

雖然次類別不是即時做出這樣的決定,但是嚴格說來,是由不同區域性的手機客戶決定到買,而決定了手機的區域需求。

來建立分公司吧…

  public abstract class CellPhoneSale
  {
      public CellPhone orderCellPhone(String style)
      {
          CellPhone cellphone;
 
          cellphone = createCellPhone(style);
 
          cellphone.design(); //設計
          cellphone.modeling(); //建模
          cellphone.combine(); //組裝
          cellphone.box();//包裝
 
          return cellphone;
      }
 
      public abstract CellPhone createCellPhone(String style);
  }
 
 
 
 
  public class ChinaCellPhoneSale :  CellPhoneSale
  {
      public override CellPhone createCellPhone(String style)
      {
          if (style.Equals("Business"))
          {
              return new caBusinessPhone(); //第1代的商務手機
          }
          else if (style.Equals("Music"))
          {   //音樂手機
              return new caMusicPhone();
          }
          else if (style.Equals("Sport"))
          {  //1代的Sport手機
              return new caSportPhone();
          }
          else
          {
              return null;
          }
      }
  }
 
  public class TaiwanCellPhoneSale : CellPhoneSale
  {
      public override CellPhone createCellPhone(String style)
      {
          if (style.Equals("Business2"))
          {
              return new twBusiness2Phone(); //第2代的商務手機
          }
          else if (style.Equals("Music"))
          {   //音樂手機
              return new twMusicPhone();
          }
          else if (style.Equals("Entertainment"))
          {  //娛樂手機
              return new twEntertainmentPhone();
          }
          else
          {
              return null;
          }
      }
  }

靠近一點來看…是在何時宣告了工廠方法?答案是…

abstract CellPhone createCellPhone(String style);

這一行專門處理物件的建立,並將這樣的行為包裝起來,放在次類別中。走類別的程式碼(orderCellPhone)會用到次類別的程式碼(createCellPhone),但是藉由工廠方法,超類別和次類別之間的關係被鬆綁了呢。

 

前面講了這麼久,有沒有發現一直都忽略了手機本身?如果手機本身沒有定義,那有店也賣不了東西。現在就來實踐手機:

  class CellPhone
  {
      //每款手機都有名稱、硬體規格(簡化)、作業系統軟體(簡化)
      public string Name;
      public string Hardware;
      public string OS;
 
      //總公司規定的基本流程。
      public void design()
      {
          Console.WriteLine("doing Design Process....");
      }
 
      public void modeling()
      {
          Console.WriteLine("doing Modeling Process....");
      }
 
      public void combine()
      {
          Console.WriteLine("doing Combine Process....");
      }
 
      public void box()
      {
          Console.WriteLine("doing Boxing Process....");
      }
 
      public string getName()
      {
          return Name;
      }
  }
 
  class caBusinessPhone : CellPhone
  {
      public caBusinessPhone()
      {
          base.Name = "China Business CellPhone";
          base.OS = "Android 1.5";
          base.Hardware = "電阻式營幕";
      }
 
      public override void box()
      {
          //大陸分公司銷售單位認為要推翻了原本的包裝方式,採用大陸的包裝流程,符合他們的風格。
          Console.WriteLine("doing China Boxing Process....");
      }
 
  }
 
  class twBusiness2Phone : CellPhone
  {
      public twBusiness2Phone()
      {
          base.Name = "Taiwan Business2 SmartPhone";
          base.OS = "Android 2.0";
          base.Hardware = "電容式營幕";
      }
  }

其他完整的程式則不實作,請以上類推即可。

等很久了嗎?手機該上市啦!來製造一些手機吧!

  class Program
  {
      static void Main(string[] args)
      {
          //建立兩間不同的分公司
          CellPhoneSale TaiwanSale = new TaiwanCellPhoneSale();
          CellPhoneSale ChinaSale = new ChinaCellPhoneSale();
  
          //
          CellPhone cellphone = TaiwanSale.orderCellPhone("Business2");
          Console.WriteLine("gipi 訂了一個" + cellphone.getName() + "手機!");
  
  
          CellPhone cellphone2 = ChinaSale.orderCellPhone("Business");
          Console.WriteLine("pou 訂了一個" + cellphone2.getName() + "手機!");
  
          Console.ReadKey();
      }
  }

是的,執行以後呢?

4

 

終於該來定義工廠模式了!當然假如都看到這邊了,你一定有一些想法:

工廠模式會把物件建立的過程封裝起來,來看看類別圖:

3

建立者(Creator)類別,其中能夠產生產品的類別稱為具象建立者。而當中的createCellPhone方法(具象類別中的哦),正是工廠方法,用來製造產品

 

而另外就是產品類別,CellPhone定義了手機,然後實際將要製造出來的手機原型都會繼承他。(像business、business2型的手機)

其實我們看到一個orderCellPhone將一個工廠方法聯合起來,然而這也可以視為是一種平行框架,他們同樣都是由抽象類別,再去實作許多具象的次類別

而每個次類別都有自己特定的實踐方式。TaiwanCellPhoneSale就是封裝台灣區域性手機的知識,大陸地區性手機的知識則由ChinaCellPhoneSale來封裝。

 

深入淺出一書定義:Factory Method Design Pattern (工廠方法模式)

定義了一個建立物件的介面,但由次類別決定要實體化的類別為何者,工廠方法讓類別把實體化的動作交由次類別進行。

5

為什麼常常會誤解呢?工廠方法的模式能夠封裝具象型態的實體化。如上圖,任何其它方法,都可能使用到這個工廠方法所製造出來的產品

但只有次類別真正實現了這個工廠方法,產生出物件。

常常有人會說,工廠方法讓次類別決定要實體化的類別為何者,但這邊所謂的決定並不是指模式允許次類別本身在執行期做決定。

而是指建立者類別的程式碼在撰寫時,不需要知道實際建立的產品是何者,選擇了使用哪個次類別,自然就決定了實際建立的產品為何了。

而要搞清楚的是,我們建立的大陸與台灣的分公司(TaiwanSale、ChinaSale),究竟與利用簡單工廠有何不同?它們真的很類似,但,用法不一樣。

雖然每個具象分公司的實踐看起來很像是簡單工廠,但是這裡工廠方法模式必須是繼承自一個類別,此類別有一個抽象方法createCellPhone()。

再由每個分公司自行負責createCellPhone()方法的行為。在簡單工廠中,工廠是另一個物件,由超類別CellPhoneSales所使用。

 

還不清楚嗎?次類別(TaiwanCellPhoneSale、ChinaCellPhoneSale)看起來真的很象簡單工廠吧?,簡單工廠不同的是,它把全部的事情都在一個地方處理掉了

然而工廠方法是建立一個框架,讓次類別決定要如何實踐。比如說好了,在工廠方法中orderCellPhone方法提供了一般的框架,以建立手機原型。

orderCellPhone依賴工廠方法建立具象類別,並產生出實際的手機,可以由繼承CellPhoneSale類別,決定實際製造出的手機是哪種區域特色。

簡單工廠的做法可以將物件的建立封裝起來,但是不具備工廠方法的彈性,因為簡單工廠不能改變正在建立的產品!

 

本例中採用了台灣跟大陸的區域性,既使只有一個concreteCreator,工廠方法模式仍然很有用,因為我們將產品的實踐與使用予以decouple(鬆綁)

如果增加了產品的實踐,Creator並不會受到影響,因為Creator與ConcreteProduct之間並沒有緊密的結合綑綁。

 

工廠將建立物件的程式碼封裝集中在一個物件或行為中,可以避免程式碼的重複,並更方便以後的修改,

也意味在進行物件實體化時,只會用到介面,而不再依賴具象類別。這正是幫助針對介面寫程式,而不是針對實踐寫程式。

然而,在工廠方法模式之中,不可避免的仍然必須使用具象類別,實體化真正的物件不是嗎?

 

事實上還有相依性的謎題沒有解呢…

 

我們常直接實體化一個物件,就是在依賴具象類別,如同本最一開始的角色扮演案例,或是手機分公司案例

假如不知道工廠方法模式的話,那就會在一個類別中去new新物件,去實體化所有的手機或是角色物件,而不是委由工廠製造。

以手機分公司為例,假如一個手機分公司完全依賴所有的手機物件(因為直接在CellPhoneSale類別去建立手機),那麼一旦類別的實踐方式改變了,就要去

修改這個手機分公司的程式。這正是依賴具象類別(產品類別)的實例,相依性如下圖。

6

 

本書精華來了…

OO守則:請依賴抽象類別,不要依賴具象類別!

這有一個專有名詞叫dependency Inversion Principle。

這很酷,程式碼減少對於具象類別的依賴是件好事,這守則聽起來滿像請對介面寫程式那個守則,然而,本守則是指不要讓高階元件(公司)依賴低階元件(產品)

不論高低階,兩者都應該相依於抽象類別!

所以這樣的相依性該如何解決呢?回想一下工廠方法,針對產品去建立一個抽象的類別,再由產品的具象類別去實作各自的方法

7

看看上面,目前高階元件(CellPhoneSale,以及這些手機(caMusicPhone、twBusiness2Phone等)都依賴了CellPhone抽象,而遵循這個OO守則

工廠方法模式正是最有威力的技巧之一。

那為什麼會有Inversion呢?走一次我們思考問題的流程吧!以書中的例子為例,假如現在要開一間pizza店

你第一個會想到的就是pizza店,那產品是什麼呢?因此要做出來的產品就是pizza,但是你不想讓pizza店依賴pizza產品,不然將全部依賴具象類別。

所以要開始反轉,先從pizza開始,能抽象成什麼類別呢?

對!就是pizza(抽象)類別,那要這麼做,必須靠一個工廠來取出這些pizza的具象類別,一旦這樣做,各種具象pizza的產品就只能依賴一個抽象了。

那pizza店呢?也會依賴那個抽象。

 

關於dependency Inversion Principle有一些指導方針(見原書)

1.變數不可以持有具象類別的參考(如果使用new,就會持有具象類別的參考,這時請改用工廠避開)

2.不要讓類別繼承自具象類別(請繼承自一個抽象或介面)

3.不要讓次類別中的方法override超類別中的方法。(如果這樣,就不是一個真正適合被繼承的抽象)

要盡量去達到,而不是隨時都要遵行,有趣的是我們常常直接實體化具象類別,那就是字串物件(字串型態是參考型別),但這個合理嗎?

很合理,因為字串不會改變(假如我們將指派新的字串時,其實只是將原本的指標指向新的字串物件的位址)

 

後記:工廠模式還沒完,本篇依然延續先前,在閱讀過深入淺出設計模式一書後,以C#實作程式碼並重新構思案例來輔佐自己理解,工廠模式是個精華

要吸收要反覆地閱讀與體會,不過很高興又學會了一個設計模式。然而為什麼工廠模式還沒完?因為還有一個抽象工廠模式等著我。

不過這波未平,先別躁進,能體會出本工廠方法模式以後,再進到下一個模式也永遠不遲呢。玩玩看吧。

[記錄] python 3.x版安裝在CentOS 7(與python2.7併存)與安裝python虛擬環境

[記錄] python 3.x版安裝在CentOS 7(與python2.7併存)與安裝python虛擬環境

最近常常在多個環境中要建置python多版本相容的環境,主因是我們的專案是3.5版,但linux原生已經有2.7版本

$ wget https://www.python.org/ftp/python/3.5.x/Python-3.5.x.tgz

$ tar xzf Python-3.5.1.tgz
cd Python-3.5.1
./configure
make

$ sudo make altinstall
這個時候,我們可能都要安裝一些必要的套件,遇到套件相依的問題,我建議要安裝virtualenv

通常透過pip就可以進行安裝,只是若我們沒有root的權限的話,而sudo又認不得pip指令時(command not found)怎麼辦呢?

常見錯誤:PermissionError: [Errno 13] Permission denied: ‘/usr/local/lib/python3.5/site-packages/virtualenv.py’

 

以下一個解法提供記錄:

1.切換到user的home目錄~/

cd ~

2.編輯.bashrc檔

sudo nano ~/.bashrc

3.在最下面alias區塊貼上,python版本路徑依個人安裝狀況調整:

alias python3=’/usr/local/bin/python3.5′
alias pip3=’/usr/local/bin/pip3.5′
alias sudo=’sudo ‘

4.使alias變更生效

source .bashrc

 

透過virtualenv建立虛擬環境

PS..有些人可能會問,我們統一指定3.5版本了,為什麼還是要安裝虛擬環境呢?

我的想法主要是至少要將開發環境、測試環境、正式環境盡可能獨立。

開發的時候,像是可能會要嘗試新的套件安裝了新的函式庫或是導入ai模組套件等等

或是當我們必須handle不同版本的產品程式碼時,環境的一致性對於重現問題是有助於我們專注在產品程式碼本身的焦點上!

因此無論如何,都建議安裝!!這是個好習慣!

  1. sudo pip3 install virtualenv
  2. 在專案所存存的目錄(e.g. /home/user/projects/) 執行 $ virtualenv -p python3.5 xxxxxx
  3. 啟用 source xxxxxx/bin/activate

這樣,就可以用虛擬環境安裝好獨立的套件囉…

 

其中我有遇到在virtualenv安裝requirement.txt時,出現

Command “python setup.py egg_info” failed with error code 1 in /tmp/pip-build-vozt3r98/mysqlclient/

若你在centos的話,可以透過安裝mysql dev套件到linux上解決這個問題

yum install mariadb-devel

若是在Ubuntu的話,請參考之

apt-get install libmariadbclient-dev