2019年12月29日 星期日

Reactive Programming(二):Flow API

本系列的文章為 Modern Java in Action / Project Reactor 的讀書筆記,因此內容可能會有點跳躍。

2019年11月6日 星期三

Reactive Programming(一)

本系列的文章為 Modern Java in Action / Project Reactor 的讀書筆記,因此內容可能會有點跳躍。

2019年10月11日 星期五

為何在 Dockerfile 中使用 tail 是 anti-pattern

最近因為一個蠻莫名的問題,花了點時間在研究依靠 tail 來把 log 輸出到 docker log driver,然後發現其實這個作法算是一種 anti-pattern。目前認知到的問題有兩個:

1. tail 可能無法如預期地運作

在 stackoverflow 上,有好幾篇問題都做了類似的事情:

FROM debian:latest
RUN touch /var/log/mylog.log
CMD ["tail", "-F", "/var/log/mylog.log"]

在上面的 Dockerfile 中,先 touch 產生一個 log 檔,接著就一直 tail –F 去看那個 log 檔,感覺上好像很普通。但實際執行起來,會發現 docker logs 裡面什麼也沒有,tail 指令好像根本沒有正常在工作。而這個問題的根本原因是在於 docker 中間有 image layer,每一行 RUN 指令都會覆蓋上一層 image layer,因此實際上 touch 指令和 tail 指令在 docker 中是存取到不同的 inode。建議的作法應該要改為:

CMD ["sh", "-c", "touch /var/log/mylog.log && tail -f /var/log/mylog.log"]

也就是說,產生檔案以及監看檔案應該要在同一個 CMD 裡進行,這樣才能避免被 image layer 隔開而看不到預期的結果。

2. 若 container 的 main process 並非是主要的運行指令,container 可能無法對某些錯誤做正確的反應

這個問題主要是考量到能不能善用 container 本身的特性。在透過 Dockerfile 執行程序時,container 對程序的期待是如果程序執行完了,container 就會被認為不再需要了,於是 container 就會自動關閉。這個特性也可以用在如果程序執行有問題導致非預期地中斷,container 也一樣會自動關閉。此時如果外頭有 load balancer 的話,load balancer 就可以自動做一些反應,例如因為 container 數量減少了,因此自動做 scale-out 重新生出一個 container。但如果 container 的 main process 並非是在執行主要程序,而是在執行 tail 的話,那有可能會遭遇到主要程序出問題結束了,但 tail 沒有問題,這時就可能存在失敗的服務一直繼續留在那邊無法正確被關閉。當然這個動作也可以透過例如另外做一套 health check 的流程來處理,不過如果能夠很簡單地就利用 container 的天性來達成這個目的,也是可以考慮利用一下~。

參考資料
  1. Why I cannot get output of tail of a file created by docker build
  2. Output of `tail -f` at the end of a docker CMD is not showing

2019年10月1日 星期二

將 AWS ECS 的 log 導向 Splunk

花了好多天的時間在研究,然後終於稍微搞清楚想要將 AWS ECS 上的應用程式的 log 導去 Splunk 時,需要注意的地方有哪些,以及它導出時是怎麼做的。不過在紀錄之前要先提一下,這篇當中不會提到如何建置 Splunk 服務,因為在我的狀況中 Splunk 是其他公司內的團隊已經建好的。

2019年9月28日 星期六

Logback 輸出 JSON 格式的 log

Logback 預設通常是會輸出一行一行純文字的 log,不過如果想要輸出 JSON 格式的話,也有看起來應該是第三方貢獻給 logback 的 extension 可以使用。

Maven 設定

<properties>
	<slf4j.version>1.7.26</slf4j.version>
	<logback.version>1.2.3</logback.version>
	<logback.contrib.version>0.1.5</logback.contrib.version>
	<jackson.version>2.9.9</jackson.version>
</properties>

<dependencies>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-api</artifactId>
		<version>${slf4j.version}</version>
	</dependency>
	<dependency>
		<groupId>ch.qos.logback</groupId>
		<artifactId>logback-core</artifactId>
		<version>${logback.version}</version>
	</dependency>
	<dependency>
		<groupId>ch.qos.logback.contrib</groupId>
		<artifactId>logback-jackson</artifactId>
		<version>${logback.contrib.version}</version>
	</dependency>
	<dependency>
		<groupId>ch.qos.logback.contrib</groupId>
		<artifactId>logback-json-classic</artifactId>
		<version>${logback.contrib.version}</version>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
		<version>${jackson.version}</version>
	</dependency>
</dependencies>

除了 SLF4J 和 logback 本身以外,還需要 logback-json-classic,以及因為它使用 Jackson 來做 JSON 處理,所以需要 logback-jackson 和 jackson。

logback.xml 設定

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<appender name="JSON_STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
			<layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
				<timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SSSX</timestampFormat>
				<timestampFormatTimezoneId>Etc/UTC</timestampFormatTimezoneId>
		
				<jsonFormatter
					class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
					<prettyPrint>true</prettyPrint>
				</jsonFormatter>
			</layout>
		</encoder>
	</appender>

	<root level="ALL">
		<appender-ref ref="JSON_STDOUT" />
	</root>
</configuration>  

這裡很單純就設定了輸出時要用 JSON,並且其中的時間格式應該要符合我設定的格式,然後要求它輸出時要 pretty print(做好縮排等格式化)。

輸出範例

{
  "timestamp" : "2019-09-28T13:04:25.013Z",
  "level" : "TRACE",
  "thread" : "main",
  "logger" : "test.App",
  "message" : "test writing log.",
  "context" : "default"
}
參考資料
  1. JSON · qos-ch/logback-contrib Wiki · GitHub
  2. How to Log in JSON with Logback

2019年9月18日 星期三

Vespa 的 keep-rank-count 和 rerank-count

在文件 [1] 上對於 keep-rank-count 的描述如下:

How many documents to keep the first phase top rank values for. Default value is 10000.

而對於 rerank-count 的描述則是如下:

Specifies the number of hits to be re-ranked in the second phase. Default value is 100. This can also be set in the query. Note that this value is local to each node involved in a query.

其實很難看懂這到底在說什麼…。

事實上,它們的功能是應該要放在一起看待的。Vespa 的 ranking 有分 first-phase 和 second-phase,其中 first-phase 會肩負減少資料量的工作,以確保當 Vespa 內存放的資料非常多的時候,不需要對所有資料都做完所有運算。keep-rank-countrerank-count 都是適用在 first-phase 階段的參數,以下分別討論它們的效果,會比較好理解。

keep-rank-count 造成的影響是當 Vespa 的每一個 search node (a.k.a. content node) 在 first-phase 取資料時,最多只會依據 rank profile 的計算去取出 keep-rank-count 指定數量的分數(和 docid),例如預設值 10,000 代表的是每個 nonde 都會暫存 10,000 筆資料的分數(和 docid),而超出這 10,000 筆資料的部份,分數就會被忽略(即只會留下 docid )。因此走到 second-phase 時就只會有 10,000 筆資料能夠被後續做更複雜的處理。這也會反應在 Vespa 最後回覆的資料中,如果是超出 keep-rank-count 的部份的資料,分數都會顯示 –infinite,代表因為分數已經在處理過程被丟棄,因此在結果中會不知道分數是多少。

rerank-count 則是決定離開 first-phase 進入到 second-phase 時,會留下多少筆資料。需要注意的是,這裡的數字一樣都是每個 search node 計算的。舉例來說,如果有 10 台 search node,rerank-count 設定為 100,那麼總共就會有 1,000 筆資料會被送進 second-phase。

keep-rank-countrerank-count 兩個結合起來,假設我的 rank profile 設定是 keep-rank-count=100, rerank-count=500,同時現在有個搜尋的指令,會搜尋到 1,000 筆資料,那麼結果會是如何呢?…..結果會是 second-phase 會看到 500 筆資料,但這 500 筆當中,只會有 100 筆的 relevance score 存在,其他 400 筆的 relevance 會顯示 -infinite

參考資料

  1. Search Definition Reference

2019年9月1日 星期日

Bloom Filter

當我們想要快速地知道某個資料是否曾經在系統中儲存過的時候,基本的作法就是時間複雜度 O(n) 或者是空間複雜度 O(n) 的選擇。

  • 時間複雜度 O(n):在存放的資料當中從第一個一直查到最後一個,花費 O(n) 的時間複雜度、同時沒有額外的空間複雜度需求,就能知道指定的某個資料到底有沒有存在。
  • 空間複雜度 O(n):存放一個配對表,例如如果資料是數字,那麼找配對表當中的指定位置,就能知道該資料是否存在。此時配對表需要花費 O(n) 的空間複雜度、但是帶來 O(1) 的時間複雜度優勢。

而 Bloom Filter 就是屬於中間型的解法,需要略高於 O(1) 的時間複雜度以及低於 O(n) 的空間複雜度。白話來說,就是 n 個資料並不需要長度為 n 的空間來存放,就能夠用來檢查 n 個資料中的任意資料是否存在於系統中。

概念上,Bloom Filter 就是使用一個陣列,用多個 hash function 來決定某個資料的位置,然後把陣列上的這些位置全都標為 1。其中資料不一定要是什麼格式,要求只是使用的 hash function 能夠輸出一個數字,來代表在陣列上的位置。因此檢查資料在不在系統中,只需要檢查這些位置是否全都是 1 就能夠知道了。不過因為是用 hash function,因此可以想見應該存在 collision 的問題,也就是有可能有多個資料會同時使用某個相同的位置,只是不會每個位置全都一樣。另外這個檢查方式也可以想像到潛在的問題:當檢查出某個資料的位置都是 1,有可能只是這些位置剛好都是被其他不同資料標上 1,並不一定真的表示我們要找的這個資料就確實存在。因此 Bloom Filter 無法保證某個資料一定存在(可能有 False Positive)、但可以保證某個資料一定不存在(不會有 False Negative)。

另一方面,因為 Bloom Filter 在儲存時只會存 0 和 1,表示它其實不知道每個位置是由哪個資料標上的。反過來說就是,當我因為加入資料的動作,把某個位置的值從 0 改為 1 後,日後我就再也無法把那個位置改回 0 了,因為 Bloom Filter 沒辦法知道這個位置跟哪個資料有關係。這也就形成 Bloom Filter 這個結構只能夠增加資料,而無法移除資料的限制。

所以簡要來說,Bloom Filter 就是個用比較少的儲存空間,快速判斷某個資料是否曾經被存放過的結構

使用情境

以 Redia 文件 [4] 的範例來說,可以用來檢查像是帳號是否被使用過:

> BF.ADD usernames funnyfred
(integer) 1
> BF.ADD usernames fredisfunny
(integer) 1
> BF.ADD usernames fred
(integer) 1
> BF.ADD usernames funfred
(integer) 1

> BF.EXISTS usernames fred
(integer) 1
> BF.EXISTS usernames fred_is_funny
(integer) 0
參考資料
  1. [論文解讀][Bloom Filter] 深入討論 Bloom Filter
  2. 資料結構大便當:Bloom Filter
  3. 海量数据处理算法—Bloom Filter
  4. Redis: Bloom Filter Pattern

2019年8月25日 星期日

利用 Jackson 序列化、反序列化 JSON

JSON 的表示在資料傳送時很方便,不過在程式碼裡就不見得了,尤其像是 Java 這樣的強型別語言,要操作 JSON 常常得寫上一堆麻煩的程式碼。這也就是為什麼會有許多 JSON parser 的開源專案的原因~吧。以前我在做這些事情時都是用 GSON 來處理的,不過由於 GSON 到現在似乎還是不支援 setter、getter 形式的轉換,能夠操弄的手法比較少,因此就來做做 Jackson 的簡易實驗。

2019年7月28日 星期日

Vespa 的資料與索引型態

Vespa 裡面支援了許多資料型態(data type),包括 int、long、byte、double、boolean、string、struct(物件)、collection(例如 array、weighted set)等等,可以參考官方文件 [1],這些資料型態最主要的影響在於存放時所消耗的空間大小。而關於使用時的使用模式,則主要是來自於設定的索引型態(index type)來決定。

索引型態

索引型態主要分為 attribute 和 index 兩種。概略來說,可以直接把它們看成是「存放在記憶體」和「存放在硬碟」的差別。在 Vespa 的設計裡,好像基本上會假定字串以外的型態都放在 attribute 比較好的樣子,因為如果要以資料欄位作為像是排序的依據的話,資料欄位必須要是 attribute index 才行。

attribute

attribute 的欄位都會被存放在記憶體裡,意味著其實如果可以的話,最好不要把長度無法預測的資料欄位設定為 attribute,因為這樣會導致身為稀缺資源的記憶體被大量耗用在存放這些字串。不過實際運作時,因為 OS 會有虛擬記憶體的概念,因此不一定會真的完全保證 attribute 的欄位就一定是被放在記憶體上,如果想要確保這件事,首先就得確保 OS 不會使用到虛擬記憶體。

設定為 attribute 的欄位,就能夠執行以下的這些操作:

  • grouping
  • sorting
  • word-match search
  • numerical search
  • prefix search
  • ranking functions
  • document summaries
  • document reference

在對 attribute 的欄位做搜尋的時候,原則上 Vespa 只會做線性搜尋,只是因為搜尋時是對記憶體內的資料做,所以一般狀況下效率不會太差。但當有必要的時候,對 attribute 欄位設定 fast-search 可能是蠻重要的事情,這會讓 Vespa 對該欄位建立 B-tree 索引,就像在 RDBMS 上對欄位建索引一樣。到這裡,某種程度上可以感覺到 Vespa 對這種基本型態的處理其實算是比較粗糙的,效能很大程度是直接依賴「資料放在記憶體上」的這件事。所以如果沒有要做什麼比較複雜的排序或計算之類的話,基本資料的儲存並不是 Vespa 的強項,把資料放在 RDBMS 裡可能可以獲得相差不遠的效率但大幅下降的維運成本。

index

index 的欄位是存放在硬碟中的 [2-3],代表資料被定義為 index 的話,儲存成本相較於 attribute 會比較低,但同時存取速度也會比較慢。index 欄位只能用在字串上(這又突顯了 Vespa 對於基本型態的處理比較粗糙的事實),除了存放位置以外,index 跟 attribute 最大的不同就是 index 的欄位都會做 normalization 和 tokenization [3]。換言之,index 主要(唯一?)的使用情境是在於做各種字串處理或者字串搜尋。

Search Definition 的定義

在 Search Definition 上定義資料的索引型態時,attribute 和 index 並非是只能設定其中一個,其實可以同時設定兩個,只是同時設定時有個有點有趣的小細節。因為索引型態是透過 pipe 連接起來的,pipe 連接的意思就是會依序執行,所以它的結果會很像是使用 Java 8 的 stream API 那樣。舉例來說,如果我對某個欄位設定 indexing: index | attribute,這表示的是這個欄位的資料會先進行 index,在這個過程中會被 tokenize 等等,接著被 tokenized 的資料再送去 attribute 做索引!換言之,在一般狀況下如果我們想要同時設定 attribute 跟 index,通常我們應該會想設定 indexing: attribute | index,而不太會是反過來放的狀況。同時,當我們設定 indexing: attribute | index 時,Vespa 的搜尋會去搜尋 index 而不會搜尋 attribute [4]。(不過這樣的話那幹麻要支援同時寫 attribute 和 index 呀….?)

參考資料
  1. Search Definition Reference: field
  2. Proton: index
  3. Search Definition Reference: index
  4. Search Definition Reference: indexing
  5. Performance And Tuning: Document Attributes
  6. Performance And Tuning: Attribute Memory Usage

2019年7月21日 星期日

dependencyManagement 和 parent

最近專案上遇到一個奇怪的狀況,概略來說就是 @NotEmpty(javax.validation.constraints.NotEmpty)這個驗證無效。後來同事研究了好一段時間,發現問題好像是出在 dependency conflict。然後就做了一些 dependencyManagement 和 parent 的小研究~不過因為沒有很仔細在研究,所以也只是粗略紀錄一下的程度。

  1. dependencyManagement 指的是事先宣告 dependency 的參考,然後當後面在 dependencies 裡面有使用到宣告的 dependency 時,就會引用事先宣告的參考版本。換句話說,如果 dependencyManagement 裡面宣告了 dependencies 裡面沒有的東西,那就不會造成什麼影響。
  2. parent 是強制引入所有宣告在 parent 裡面的 dependency。因此不管專案自己的 dependencies 裡面有沒有使用到,parent 宣告的 dependency 都會被引入。
  3. parent 裡宣告的東西好像是強制引入,就算在 depepdencies 裡面想要覆寫版本也是沒有用的。不過聽同事說,如果是間接引用的狀況,例如 parent 裡的 dependency A 引用的 dependency B,就可以透過 maven 本身的特性覆蓋或變更~這點是還沒有確實實驗過。
參考資料
  1. Maven实战(六)--- dependencies与dependencyManagement的区别
  2. maven 中parent 与 dependencyManagement 
  3. What is the difference between “pom” type dependency with scope “import” and without “import”?
  4. maven如何不继承parent里面的部分依赖
  5. How to exclude artifacts inherited from a parent POM? 
  6. Spring Boot 不使用默认的 parent,改用自己的项目的 parent

2019年7月18日 星期四

Spring WebFlux:Reactive Programming(持續更新中…)

最近開始認真在讀 Spring WebFlux 的文件,所以稍微做一點筆記~。

何謂 Reactive?

在 Spring 的定義來說,Reactive 包含兩件重要的事情:「Reacting」以及「Non-blocking back pressure」。在 Reacting 的部份,Spring 的描述如下:

The term, “reactive,” refers to programming models that are built around reacting to change — network components reacting to I/O events, UI controllers reacting to mouse events, and others. In that sense, non-blocking is reactive, because, instead of being blocked, we are now in the mode of reacting to notifications as operations complete or data becomes available.

重點在於最後面的描述,我們現在處於「回應通知」的模式,在操作完成或者是資料可以存取的時候才採取反應。而對於 Non-blocking back pressure 的部份,則是以下的描述:

In synchronous, imperative code, blocking calls serve as a natural form of back pressure that forces the caller to wait. In non-blocking code, it becomes important to control the rate of events so that a fast producer does not overwhelm its destination.

這個在我以前一開始聽到的時候,其實不太能理解它的重要性,不過聽過很多次以後,慢慢地有了一點感覺~XD。在同步操作中,因為呼叫者會需要等待,所以呼叫者通常可以很明確地知道接收者什麼狀況開始受不了了;但在非同步的模式中,呼叫者跟接收者並沒有直接性的連接,所以基本上容易落入完全不知道對方狀況的情境,然後一股腦地一直發送事件,讓接收者被塞爆~。而在 Reactive Programming 的概念中,應該要包含接收者必須要具備 back pressure 的能力,有點像是在必要時壓制呼叫者,叫它停下來這樣。

Spring WebFlux 對於 Reactive 的支援

Spring 的體系中,現在分成兩大類:Spring MVC 和 Spring WebFlux,如下圖所示。兩邊都同時有支援 Annotated Controller 的模式,也就是可以用類似於過去的 Spring MVC 的方式使用 Spring WebFlux。不過如果要使用類似 Java 8 Lambda 的那種 Functional Programming 的 model 的話,就得採用 Functional Endpoints 的方式來開發了。


參考資料
  1. Web on Reactive Stack

2019年7月14日 星期日

Groovy 的 RESTClient 相關

忽略 HTTPS 的伺服器端檢查

restClient.ignoreSSLIssues()
回覆的 status code > 399 時,不要拋出 exception REST

Client restClient = new RESTClient()
restClient.handler.failure = restClient.handler.success
參考資料
  1. Is there an easier way to tell HTTPBuilder to ignore an invalid cert?
  2. Can't use ignoreSSLIssues in HttpBuilder version 0.7.1
  3. Can I override RESTClient default “HttpResponseException” response to >399 Return Codes?

2019年7月7日 星期日

Java 的 SSL 驗證

一般在 Java 要存取 HTTPS 的服務的時候,大多數狀況因為常見的 CA 應該都已經列在 JRE 的 cacerts(CA Certification 檔)裡了,所以通常不會遇到什麼問題。不過如果遇到要存取的是像是公司內部的服務,公司自己有自己的 CA 時,可能就得做點事情來讓 HTTPS 的 SSL/TLS 能夠正常運作。

概念上來說,當一個客戶端要存取 HTTPS 的服務時,客戶端只會相信它相信的對象。而要如何決定要相信誰?方法就是客戶端自己會存放一個信任的 CA 的清單,如果發現 HTTPS 服務的憑證是由信任的 CA 所發放的、並且憑證所保護的對象也確實是現在存取的對象,那客戶端就會相信這個 HTTPS 服務。以 Java 的狀況來說,客戶端指的是 JRE,JRE 內建的信任 CA 的清單會是 $JAVA_HOME/lib/security/cacerts 這個檔案。如果沒有特別指定的話,Java 程式在存取 HTTPS 時,進行 SSL/TLS 三方握手時就會以 JRE 內建的 cacerts 來決定對方是否能夠信任。

不過現實上,CA 還會分成 Root Certificate 和 Intermediate Certificate,例如 [1] 的範例一樣:

這裡 Entrust.net Secure Server CA 是 Root Certificate,DigiCert High Assurance EV Root CA 和 DigiCert High Assurance CA-3 則都是 Intermediate Certificate,最後 *.atlassian.com 這個網域的憑證是由 DigiCert High Assurance CA-3 所簽發的。客戶端若要驗證 *.atlassian.com 的憑證,它必須要信任上面的所有 CA(也稱為 Certificate Chain)才行。

2019年7月3日 星期三

在 Maven 專案中自行指定原始碼的位置

在我們團隊的習慣,會在 Maven 專案裡面使用 Groovy 來寫測試,並且在寫測試時會用 Cucumber 來做整合測試。其中整合測試也許是為了想要跟單元測試分開,所以不會擺在一般 Maven 預設的路徑 src/test 裡面,而是會放在 src/integration-test 裡面,這樣的狀況會導致 IDE 無法正確找到原始碼,Maven 的 dependency 設定也會出現問題。

舉例來說,在我的案例裡,因為 Cucumber 只有測試階段才會使用,因此很自然我會在 Maven 上做這樣的 dependency 設定:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-java</artifactId>
    <version>4.3.1</version>
    <scope>test</scope>
</dependency>

這邊的 scope 會特地設定成 test,表示 Cucumber 的套件只有在測試期間才會被載入。接著我如果直接上把 src/integration-test/groovy 加入 build path,會因為 Cucumber 只會在測試階段被引用的關係,導致 IDE 發出編譯錯誤的訊息。根本來說,問題是在於我必須要好好地告訴 IDE 或者 Maven 說,src/integration-test/groovy 這個資料夾應該要被視為是測試的資料夾。

所幸,Maven 本身有方法可以自行指定資料夾的功能,也就是利用 build-helper-maven-plugin 這個 plugin。這個 plugin 的功能主要就是讓我們可以告訴 Maven 說哪個資料夾要看成是 source directory、哪個資料夾要看成是 test directory 等等。

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>${maven-build-helper-plugin.version}</version>
            <executions>
                <execution>
                    <id>add-test-sources</id>
                    <phase>validate</phase>
                    <goals>
                        <goal>add-test-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>src/test/groovy</source>
                            <source>src/test/java</source>
                            <source>src/integration-test/groovy</source>
                            <source>src/integration-test/java</source>
                        </sources>
                    </configuration>
                </execution>
                <execution>
                    <id>add-test-resources</id>
                    <phase>generate-test-resources</phase>
                    <goals>
                        <goal>add-test-resource</goal>
                    </goals>
                    <configuration>
                        <resources>
                            <resource>
                                <directory>src/integration-test/resources</directory>
                            </resource>
                        </resources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

可以注意到,在上述的 POM 當中,我設定了 src/integration-test/javasrc/integration-test/groovy 兩個資料夾都是 test-source 的 source,而 src/integration-test/resources 則是 test-resource 的 resource。如此一來,我就可以在 eclipse 裡面直接依靠 Maven 來幫我設定專案的原始碼資料夾了。

參考資料
  1. build-helper-maven-plugin
  2. Maven插件之build-helper-maven-plugin

2019年6月30日 星期日

AWS ECS 透過 CloudFormation 部署時的 Stack 更新行為

在 CloudFormation 的 Stack 被更新的時候,CloudFormation 有可能會針對要更新的資源做三種不同的處理 [1]:

  1. Update with No Interruption
  2. Updates with Some Interruption
  3. Replacement

其中第一種是直接完成更新,不會導致資源的服務中斷;第二種會導致一段時間的資源服務中斷,但 Resource ID 不會改變;第三種是會建立新的資源然後替換舊的資源,所以有可能資源的服務不會中斷,但必然導致 Resource ID 改變。

當 CloudFormation 在更新資源的時候,會依據被更新的資源的類型,套用上述三種更新方法的其中一種。因此具體而言到底會採用哪個方法更新,必須看被更新的東西是什麼而定。除此之外,更新的行為是以 Stack Property 為單位的。舉例來說:

AWS::ECS::Cluster
    ClusterName:
        Update requires: Replacement
    Tags:
        Update requires: No interruption

在 CloudFormation 的文件 [2] 中,可以看到 ECS Cluster 如果是 ClusterName 這個屬性變更了的話,會採取的更方式是 Replacement;但如果是 Tags 的變更的話,則會採取 Update with No Interruption。

參考資料
  1. AWS Documentation » AWS CloudFormation » User Guide » Working with Stacks » AWS CloudFormation Stacks Updates » Update Behaviors of Stack Resources
  2. AWS Documentation » AWS CloudFormation » User Guide » Template Reference » AWS Resource and Property Types Reference
  3. 1Strategy/fargate-cloudformation-example

2019年6月22日 星期六

在多節點環境將 logback 導出至 CloudWatch

在很久以前有寫了一篇文章 [1] 紀錄要如何用 logback 把 log 導出到 AWS CloudWatch,那時是用 logback.xml 直接設定,不過有個缺點是設定中指定在 logback.xml 裡的 Log Stream 的名字是個寫死的固定值,這在多節點的狀況可能會有問題。因為 CloudWatch 對於任一 Log Stream 同時只能允許一個執行緒存取,這會導致當應用程式會擴展成兩個以上的節點的時候,輸出 log 可能會有問題。

2019年5月14日 星期二

Vespa 的設定檔用途

Yahoo 的 Vespa 大數據系統 [1],是 Yahoo 開源的系統,主要功能可以想像是 Elasticsearch 的對照。不過在基本使用上,Vespa 跟 Elasticsearch 稍微有點不太一樣的概念。首先 Vespa 包含了整個環境佈署的程序,也就是在討論「設定 Vespa」時,其實是在討論如何在環境中開啟一組 Vespa 的叢集,並且「Vespa 叢集」在 Vespa 的概念當中是被稱為 Application。換言之,在 Vespa 的世界裡,它是假設了我們要先有一整群 IaaS 的伺服器在等待 Vespa 的 Application 被佈署上去,或者說等待將 IaaS 環境中的節點被分配來安裝 Vespa。等到 Vespa Application 成功被佈署了,之後才會開始討論如何操作 Vespa。

要佈署 Vespa 需要指派一些設定檔,簡單紀錄一下其中一些設定檔的用途。其中因為整個 Vespa 佈署的設定除了要定義資料的 schema、叢集的設定以外,還需要 pom.xml 的搭配,所以通常直接開一個 Java 專案專門用來做 Vespa 佈署設定,會比較自然一點點。

Application Package

Application Package [2] 一般指的是 /src/main/application 這個資料夾,資料夾內的東西基本上都是用來指定要如何進行 Vespa 佈署的。

  • services.xml [3]:叢集的配置檔。命名上感覺是說要在這裡定義要佈署的 Vespa 服務是要用來提供什麼服務的,不過具體而言就是在指定一個 Vespa 叢集,例如要用幾個節點建置叢集、叢集的節點當中哪些節點用來存放哪個資料、資料是否需要 replication 等等的。其中,會需要指定哪個資料放在哪個節點,是因為 Vespa 的佈署包含了 nginx,因此這個設定同時會指派 nginx 做資料的路由。
  • searchdefinitions/*.sd [4]:定義在 Vespa 當中存放的資料的 schema。schema 的主要用途是告訴 Vespa 該用什麼方法來處理資料,例如該建立什麼樣的索引、該把什麼欄位放在記憶體中等等。
參考資料
  1. Vespa
  2. Application Package
  3. Services Configuration
  4. Search Definitions

2019年5月9日 星期四

AWS VPC

AWS 的 VPC 最大只能開 /16,最小能開 /28。其中每個 Subnet 會有 5 個保留 IP 不能被使用:

  • .0 是本地路由
  • .1 是路由器位址
  • .2 是DNS保留位址
  • .3 目前沒用到的保留位址
  • .255 廣播,禁止客戶使用廣播

VPC 生成時會建好一個主路由表,建議不要刪除主路由表,因為就算自己手動重建,可能其他資源的識別還是會有問題。

當 Resource 放在 Security Group 裡的時候,Resource 送出的所有封包都會先出 Security Group、尋找目標 Resource、再進目標的 Security Group。所以即使兩個 Resource 放在相同的 Security Group 內,預設也是無法互相存取的,必須要在 Security Group 內設定允許自己存取自己。

2019年4月30日 星期二

Athenz 基本運作概念與相關名詞

在 Athenz 權限系統中,有幾個比較重要的基本概念和名詞,簡單記錄一下。不過在那之前,因為 Athenz 的結構幾乎跟 AWS IAM 一樣,所以可以先討論一下 AWS IAM 的運作方式 [1],再回來看 Athenz 對應的概念。而且其實我覺得 AWS 的文件整體來說寫得比較好 XD。

2019年4月28日 星期日

Athenz 的授權流程

Athenz 是 Yahoo 開源的權限系統 [1],基於 X.509 Certificate 的架構來提供權限認證的功能。運作上跟 AWS IAM 蠻類似的,是以 Role 為基礎,透過指定某個 Role 對某個 Resource 允許或拒絕某些 Action 來達成授權行為。

2019年4月26日 星期五

plugin execution not covered by lifecycle configuration

公司的 Maven Plugin 在 eclipse 上會跑出標題的錯誤訊息,實際上問題好像是出在 m2e plugin。幾種不同的解法可以參考 [1]。比較完整的做法似乎應該在 dependencyManagement 上追加設定,不過因為在公司裡只有我用 eclipse,其他同事都用 Intellij....所以目前是直接從 eclipse 的 Maven 設定處把這個錯誤忽略掉 XD。

具體來說,就是從 Preferences → Maven → Errors/Warnings → 在 plugin execution not covered by lifecycle configuration 處選擇 “ignore”。

參考資料
  1. Maven —— Plugin execution not covered by lifecycle configuration 错误

2019年4月14日 星期日

繼承(Inheritance)與合成(Composition)

在 Design Pattern 的書裡,大概都會提到「多用合成、少用繼承」,不過具體來說到底繼承會產生什麼問題、合成又能解決什麼問題,一直到現在才有比較明確的感覺。但由於不太擅長描述這種有些抽象的問題,所以這裡就簡單地紀錄一些感覺到的重點,細節就請參閱底下的參考資料吧 XD。

首先,一切的源頭都是為了 OCP(Open-Closed Principle),也就是要盡可能滿足「對擴充開放、對修改封閉」這樣的準則。不過要滿足這個準則,其實會讓程式碼變得複雜,所以在選擇的過程也必須謹慎小心,盡可能只在真正需要的地方去滿足,而不要把這個準則套用到整個系統的每個角落 [1]。

在考量 OCP 的前提之下,由於繼承是屬於在編譯期間的關聯,因此很容易會導致使用繼承時,擴充會需要修改既有的程式碼。這就會導致違反了 OCP 當中的「對修改封閉」的要件。

另外繼承在可能性眾多的情況,也很容易導致子類別的數量爆炸,造成日後維護的困難。

反過來說,合成是屬於在執行期間的關聯,因此較有機會可以避免上述的問題。不過依據狀況的不同,合成也有可能形成子類別較多的現象,雖然不至於到數量爆炸的程度,但往往也會使得 API 變得難以掌握。這類的問題就得複合多種 Design Pattern 來隱藏問題,讓呼叫者不需要對細節有太多的了解。

不過同時也需要注意的是,雖然說概念上提到「少用繼承」,但使用合成其實並不表示完全不使用繼承。關鍵在於「不繼承行為」,但合成常常會需要「繼承型別」(然而這或許只限定在像是 Java 這類強型別的語言上)。

參考資料
  1. 深入淺出設計模式
  2. 物件導向程式設計:為何說composition優於inheritance?
  3. 請問,為什麼要『多用合成,少用繼承』? (java程式語言)
  4. 省思物件導向設計 第2回- 物件導向設計方法面臨的問題 

2019年3月19日 星期二

為什麼需要 CI/CD?

Continuous Integration(持續整合)和 Continuous Delivery(持續發佈),有些時候也會用 DevOps 來稱呼,有在關注技術發展的話就會發現這個詞非常火紅,好像每個人都應該要知道的樣子。不過它可以解決什麼問題呢?

2019年3月15日 星期五

(書籤) git 的 flow

之前曾經稍微看過一點關於版本控管流程的資料,不過沒有很認真在深入了解各種流程的訴求和差異。最近正在努力重新學習 git,所以順帶先暫存一下一些跟流程有關的資料。

然後現在才發現我一直用的流程是 GitHub flow。(遮臉)

參考資料
  1. Understanding the GitHub flow
  2. GitHub Flow 及 Git Flow 流程使用時機
  3. git flow 實戰經驗談 part1 - 別再讓 gitflow 拖累團隊的開發速度

2019年3月6日 星期三

Shuffle Sharding:AWS 如何最小化 blast radius?

在 AWS 社群看到有趣的研究,在講述 AWS 的架構中如何讓 blast radius(爆炸的影響範圍)最小?或者說當服務節點出問題時,讓受到影響的客戶端影響最小。以客戶端來說,狀況的假設是客戶端發出 request,request 透過某種 Routing 送進後端的服務節點,不過因為某種原因 request 造成了服務節點崩潰。此時客戶端會繼續嘗試重發 request,然後如果 Routing 的機制沒有特別地設計的話,終究客戶端的 request 會一台一台地把所有後端服務節點全部弄掛,導致整個服務中斷。

2019年2月27日 星期三

(書籤) 微服務的管理

微服務在最近幾年蠻盛行的,嗯…或者該說是非常盛行?不過概念上我目前的認知是,它把本來巨大的服務拆解成很多比較小的服務,以達成讓每個比較小的服務容易維護的目的。但同時這會帶來副作用,也就是微服務之間的管理成本會大幅提昇 [1]。實際上這也是很容易想像的事情,因為本來我們認為巨大的服務內部有混亂的互相呼叫的關係(Spaghetti Code [2] 的一種),讓程式碼變得難以維護;而當我們把它拆解成比較小的服務時,各個微服務內就會比較沒有那麼混亂的關係,進而讓每個微服務變得容易維護。但到這裡其實我們根本沒有解決問題!微服務跟微服務之間依然需要互相呼叫,也就是那些所謂的 Spaghetti Code 的關係並沒有被消除,只是被從程式碼內拿到程式碼外而已。所以雖然每個微服務的維運變容易了,但那些混亂又複雜的關係依然存在,而且變成存在於微服務和微服務之間,從程式碼問題變成是架構問題。這點在 [1] 這本書中開頭就有明確地提到,微服務是一種取捨、而不是 silver bullet,它不會解決所有的問題。當我們選擇微服務時,必須同時意識到它將要帶來的缺陷,並且要明確地確認組織能夠處理這個缺陷,才能夠往微服務發展。

在 TWJUG 的活動 [3] 中聽到 Apache Camel 這個工具,雖然說嚴格來講覺得沒有真的打到點,不過再看到最近社群到處轉貼的文章 [4],就覺得好像有點靈光一閃 (?),所以就先做個紀錄 XD。

參考資料
  1. 建構微服務|設計細微化的系統 (Building Microservices)
  2. Wikipedia - Spaghetti code
  3. Camel & Camel K & Camel K on K native
  4. 使用 Workflow Engine 來實作 Microservices SAGA pattern

2019年2月20日 星期三

使用其他工具透過 SSH 連接 GCP 的 VM

網路上其實已經有很多文章在講要怎麼用其他工具 SSH 連進 GCP 的 VM,不過中間大多有個小地方寫得不太清楚。

通常來說,多數文章會講說可以用 PuTTYgen 這個工具來產生 RSA 金鑰,然後這裡要記得選一下演算法和金鑰長度。產生好金鑰以後,PuTTYgen 會直接在介面上顯示出 public key,然後可以選填 key comment 和 passphrase。這時最重要的問題來了!這裡其實輸入的 key comment 就是未來用這個金鑰作為 SSH 登入管道時的使用者帳號,並不是一定要輸入什麼指定的東西,所以不必限定在一定要用 GCP 帳號的名稱、當然也不要直接用 PuTTYgen 預設的字串(雖然說預設字串也蠻不容易被猜到的啦….)。

後面就是一般操作,把 private key 存檔,並且把 public key 貼到 GCP 的 metadata 裡面,稍微等一下下,就可以用這個金鑰去 VM 登入了。

參考資料
  1. [教學] Google Compute Engine ( GCE ) 使用 PuTTY SSH 登入實例
  2. 使用進階方法連線至執行個體

2019年2月7日 星期四

Java 的 Collection

Java 的 Collection 在現在(指 Java 8)主要還是分成三個大類:List、Set、Map。其中每個大類幾乎都有 Thread-Safe 跟 Non-Thread Safe 的區別。有些實作因為比較少用到,會慢慢從我的記憶中消失(表示為金魚腦)….。另外因為我上一次認真看這些東西是在 Java 6 的時候,到現在有些東西有可能有變化了,所以還是需要定期複習和更新一下認知,以確保在合適的時機使用合適的結構。

2019年1月27日 星期日

分散式系統的 CAP

每次看完就會重新想起來,然後一段時間之後又會忘記定義,所以想說整理一下推導這個概念的過程。

CAP 分別代表的是以下三個概念:

  • 一致性(Consistency)
  • 可用性(Availability)
  • 分區容錯性(Partition tolerance):我個人認為白話來說就是是否允許系統中有節點與其他節點完全斷線。

最重要的關鍵在於 Partition tolerance 的問題,也就是當節點間的連線被切斷了的時候,狀況會變成如何?

  1. 滿足 AP 時無法滿足 C-如果節點連線被切斷了,例如被切割成 A 群和 B 群節點,但整個分散式系統的所有節點依然都能夠繼續提供服務,這時就滿足了 Availability 和 Partition tolerance。但因為 A 群和 B 群互相連不上對方,因此在 A 群當中做的變更,將無法反應在 B 群中。亦即此時 Consistency 就無法被滿足了。在 GlusterFS 當中,這個現象被稱為 split brain,就是系統存在兩顆大腦,兩顆大腦會同時做不同的思考。
  2. 滿足 CP 時無法滿足 A-如果節點連線被切斷了,變成 A 群和 B 群節點,但因為要確保整個系統資料的一致性,因此依照某些規則決定把被認為斷線的那個部份停用,例如認為 A 群才是 master,把 B 群暫時停用,直到 B 群重新連上 A 群為止。此時能夠保證 Consistency 和 Partition tolerance,但是因為在被分區的這個當下,部份節點會處於不可用的狀態,因此就無法滿足 Availability 了。
  3. 滿足 CA 時無法滿足 P-如果永遠要確保在系統中所有節點的資料一致,而且所有節點都要保證可用,那麼可想而知,就不能允許發生像上面講到的,網路斷線產生兩群節點互相連不上對方的狀況(因為一旦發生斷線,只要雙方都持續提供服務,則一定會 split brain;要不 split brain 只能選擇停用其中一方)。也就是這時就無法滿足 Partition tolerance 了。

2019年1月19日 星期六

(書籤) Java Memory Model

很久沒用到,覺得已經不太記得細節了…。一般在工作上會優先架構成能夠允許最終一致性的狀況,不過如果遇到強一致性的多執行緒需求,還是必須考慮到 Java Memory Model。

大體上來說,因為硬體上 Thread 會有自己的本地快取,因此在不同 Thread 間對同個物件的「視野」可能是不同的。概念上很像是在資料庫上會遇到兩個 Transaction 看見的內容不同的那種狀況。由於硬體終究會將本地快取 flush 回主記憶體,所以最終還是會保持一致性,只是在過程中不見得永遠會滿足執行緒間的一致性,這時就需要 volatile 登場了。

參考資料
  1. Wikipedia - Java memory model
  2. Java Memory Model
  3. 深入理解 Java 内存模型(一)——基础
  4. Stack Memory and Heap Space in Java
  5. Difference between volatile and synchronized in Java
    1. JAVA多執行緒之volatile 與 synchronized 的比較
    2. Volatile vs Static in Java

    2019年1月14日 星期一

    Dependency Injection 與 static

    最近一直在思考 Spring 與 static 之間的關係。一般來說,Spring 會管理由它生成的 Bean,不過如果類別本身是以 static 形式呼叫(例如程式碼呼叫 MyClass.callStaticMethod() 這種方式)的話,正常來說就不會被 Spring 所管理,自然在類別內的自動注入(如果有的話)也不會生效。雖然是有一些小技巧可以姑且繞過問題,不過感覺上從 Spring 的角度,好像應該用別種思考模式來思考這個問題。

    關於這個問題,在請教 qrtt1 大神之後獲得了解惑,請大家一起膜拜 qrtt1 大神~(拜)。不過其實概念也並不困難,只是不知道為什麼原本一直想不通。

    PostgreSQL 的索引:B-tree

    開這篇之前,其實本來是想要了解 PostgreSQL 10 裡實現索引的原理,畢竟在了解原理的情況下,比較容易想像在什麼情況下索引能夠具有優勢、什麼情況索引幫不上忙。不過花了一段時間搜尋…也不能說沒有找到,但沒有找到我想要的類型。所以這篇雖然標題在講 PostgreSQL 的 B-tree 索引,但實際上會先紀錄的是一般關聯式資料庫以 B-tree 做索引時的狀況。如果想參考看看我找到的其他在談 PostgreSQL B-tree 原理的文章,可以看看 [1-5]。

    這篇當中實際會紀錄到的東西,有很大一部份並非來自 PostgreSQL 的官方文件,因此這並不一定完全就是 PostgreSQL 實現的方法,嚴格來說其實這個標題並不是太合適。不過理想上….應該不會差太多….吧!此外這篇文章紀錄的事項,除了我個人的觀點以外,有許多部份是來自 USE THE INDEX, LUKE! [7] 這個網站的解說。因此如果想要有條理地細讀,推薦可以直接去閱讀這個網站的內容。

    然後呢,理想上我是想要依序了解所有 PostgreSQL 的索引結構,並分別做點紀錄。所以它有可能會是個系列文,這篇作為系列文的第一篇就會參雜一些奇怪的廢話了。但…也不保證真的會寫出來就是,以前有編號的系列文絕大多數都虎頭蛇尾了 Orz,所以這次索性不編號了。

    2019年1月5日 星期六

    演算法的細緻之處…

    在 LeetCode 上有個簡單的題目:Two Sum [1],題目很單純,就是給定一個陣列以及一個目標值,找出能夠滿足相加會等於目標值的兩個數字。

    這個問題最容易想到的自然是暴力破解,暴力破解的複雜度是 O(n2)。而另一種想法大概是左右逼近,但這存在一個前提是陣列必須是已排序的陣列,所以考慮排序的話,複雜度會是 O(nlogn)(實際來說複雜度可能是 O(nlogn + 2n) 吧)。然後如果有想到可以用空間換取時間的話,利用 HashMap 就可以達到 O(n)。

    接著三種解法都送出的話,想像上效能應該是 O(n) > O(nlogn) > O(n2),不過實際上呢?(但我沒有嘗試送出 O(nlogn) 的版本,所以時間直接引用 [2])

    2019年1月2日 星期三

    idle in transaction

    在 PostgreSQL 中,如果因為某些因素必須要關閉部份的 transaction,需要判斷一個 transaction 是否可以被安全地關閉。這時可以從 pg_stat_activity 裡面觀察 transaction 的狀態,主要觀察的對象是兩個參數:backend_xidbackend_xmin。比較詳細的描述可以參考 [1]。