文/徐金龍 宋任堂 張成俊
Redis(Remote Dictionary Server)是一個開源的使用ANSI C語言編寫、遵守BSD協(xié)議、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。它通常被稱為數(shù)據(jù)結(jié)構(gòu)服務(wù)器,除了提供常規(guī)的數(shù)據(jù)類型字符串型(String),還支持哈希(Map),列表(list),集合(sets) 和有序集合(sorted sets)等類型。Redis擁有發(fā)布訂閱(pub/sub)的消息通信模式,支持主從復(fù)制和集群化。
在數(shù)字化生產(chǎn)線底層數(shù)據(jù)庫設(shè)計中,完全可以使用Redis代替實時數(shù)據(jù)庫,以滿足平臺各部分對實時數(shù)據(jù)高度密集的I/O需求。Redis自帶的發(fā)布訂閱機制也可以為平臺提供消息傳遞的功能。
1.1.1 String(字符串)
String型是最簡單的Redis數(shù)據(jù)類型String型實際上可以存儲任意類型的字符串,包括二進制數(shù)據(jù)。當(dāng)存儲的value是數(shù)值的時候,還可以對value進行原子遞增等操作。
1.1.2 List(列表)
Redis List基于Linked Lists實現(xiàn),List類型的一個顯著的優(yōu)點是不管List本身已經(jīng)包含了多少個成員,插入數(shù)據(jù)的時間復(fù)雜度仍為O(1)。但是在查詢時,因為需要遍歷列表中的元素,時間復(fù)雜度為O(n)。
1.1.3 Hash(哈希)
Redis hash 是一個string類型的field和value的映射表,hash特別適合用于存儲對象。Redis的hash結(jié)構(gòu)可以實現(xiàn)像sql中update一個屬性一樣只修改其中一項的值。
1.1.4 Set(集合)
集合的概念就是一堆不重復(fù)的值,在Redis中,set型就是string型的無序排列。Redis中集合是通過哈希表來實現(xiàn)的,所以添加,刪除,查找的復(fù)雜度都是O(1)。
1.1.5 Sorted Set(有序集合)
Redis 有序集合和集合一樣也是string類型元素的集合,且不允許重復(fù)的成員。不同的是有序集合當(dāng)中的元素時按順序排列好的。
Redis 發(fā)布訂閱(pub/sub)是一種消息通信模式,實現(xiàn)方式類似于設(shè)計模式中的觀察者模式:發(fā)送者(pub)發(fā)送消息,訂閱者(sub)接收消息。Redis 客戶端可以訂閱任意數(shù)量的頻道。
圖2、圖3展示了頻道channel1,以及訂閱這個頻道的三個客戶端之間的關(guān)系。
在分布式的生產(chǎn)線管理系統(tǒng)中,由于各組件之間的耦合性較低,缺乏一種消息交流的方式就可能會造成一些問題,比如當(dāng)讀線程的速度與寫線程的速度差不多快的時候,由于異步的特性,就無法保證每次讀取到的都是之前沒讀到的數(shù)據(jù),造成資源浪費。這時候Redis自帶的消息隊列功能就派上用場了。
Redis采用多級主從復(fù)制的方式實現(xiàn)對集群的支持,與單一Redis實例相比,集群可以將由故障導(dǎo)致的數(shù)據(jù)丟失或者系統(tǒng)故障的概率降到最低。主從復(fù)制對集群內(nèi)的任何一個節(jié)點都不會造成阻塞。從節(jié)點既可以只作為備份來加強數(shù)據(jù)安全,也可以作為讀取服務(wù)器來減輕主節(jié)點的壓力。
在之前的項目中,使用微軟.NET平臺的WCF框架實現(xiàn)了一個實時數(shù)據(jù)庫,作為實時數(shù)據(jù)的緩存、處理和傳遞的中心。但是在使用過程中,發(fā)現(xiàn)這種模式有著結(jié)構(gòu)復(fù)雜,部署繁瑣,系統(tǒng)耦合性高,缺乏消息傳遞機制,并且由于集成了大部分?jǐn)?shù)據(jù)處理功能的緣故,導(dǎo)致在運行某些事務(wù)的時候速度緩慢。如圖4所示。
在WCF中使用對象的實例來映射數(shù)據(jù)點,點的屬性包括數(shù)據(jù)類型、值、報警類型、數(shù)據(jù)質(zhì)量、更新時間、網(wǎng)絡(luò)地址、通信協(xié)議等等。
使用hash表來存儲數(shù)據(jù),一張表存儲從下位機讀取上來的實時數(shù)據(jù),另一張表存儲上位機發(fā)送給下位機的指令信息。
(1)缺乏消息機制。
(2)內(nèi)部處理耗時。
(3)傳輸和處理了不必要的數(shù)據(jù)量。
(4)開發(fā)和部署較為復(fù)雜。
使用Redis代替實時數(shù)據(jù)庫,需要做以下幾點改變:更改數(shù)據(jù)存儲的方式,利用Redis的string型存儲整個項目的所有點和表的結(jié)構(gòu);由于Redis是一個內(nèi)存數(shù)據(jù)庫,無法在其中集成一些邏輯處理,需要上位機程序自己處理點和表之間的邏輯關(guān)系;使用Redis的發(fā)布訂閱機制進行消息傳遞。
(1)使用string型存儲整個系統(tǒng)的數(shù)據(jù)架構(gòu)。將點表關(guān)系,也就是上文中提到的本地XML文件,序列化成JSON對象,存儲到string型中。
(2)使用hash型存儲單個數(shù)據(jù)點。點里面的每個屬性和值都將對應(yīng)hash中的一個key/value鍵值對。
每次更改系統(tǒng)數(shù)據(jù)結(jié)構(gòu)之后,必須將改動寫入string型中,并且重新初始化所有hash。通信程序在初始化的時候,要從Redis中取出string型存儲的點表結(jié)構(gòu),進行反序列化,從中得到必要信息,來啟動通訊傳輸。其他程序在讀取實時數(shù)據(jù)的時候,直接根據(jù)點的ID從Redis中讀取數(shù)據(jù)。所有對hash的操作將通過hmset和hmget方法批量進行,并且只訪問自己需要的數(shù)據(jù)。

圖1:基于實時數(shù)據(jù)庫的生產(chǎn)線軟件平臺

圖2:訂閱頻道

圖3:接收消息

圖4:實時數(shù)據(jù)庫架構(gòu)
利用Redis的發(fā)布訂閱特性,將Redis作為系統(tǒng)的消息傳遞中心。系統(tǒng)將會定義一些默認頻道,訂閱者和發(fā)布者也不是固定的,一個程序可以是發(fā)布者也可以是訂閱者,如:當(dāng)通訊服務(wù)完成一個讀取周期,從下位機中取到了實時數(shù)據(jù),并成功寫入到Redis之后,會以發(fā)布者的身份發(fā)送一條寫數(shù)據(jù)完成通知,所有訂閱這個頻道的上位機程序收到了這條消息,就會啟動自身線程,從Redis中讀取最新數(shù)據(jù);當(dāng)控制程序下達了一條控制指令時,會以發(fā)布者的身份發(fā)送一條下傳指令,這時候通訊服務(wù)作為一個訂閱者會收到這條消息,從Redis中取到數(shù)據(jù),再將指令寫入到下位機當(dāng)中。這樣就完成了線程之間的有序運行,提升了系統(tǒng)的效率。
集群化是Redis異于實時數(shù)據(jù)庫的一個明顯特點。實時數(shù)據(jù)庫是中心化的,讀寫操作都由單個服務(wù)器負責(zé),一旦實時數(shù)據(jù)庫出現(xiàn)故障,所有數(shù)據(jù)都會丟失,所有依賴于實時數(shù)據(jù)庫的軟件都會同步陷入故障。集群化的Redis則不存在這種問題,通過Redis集群,可以在主節(jié)點故障時迅速切換到可用的從節(jié)點,不會造成整個系統(tǒng)的故障,也不會丟失數(shù)據(jù),或者只丟失少量數(shù)據(jù),以達到系統(tǒng)高可用性的目的。
本文討論了Redis代替實時數(shù)據(jù)庫在數(shù)字化生產(chǎn)線上的應(yīng)用。Redis作為單線程的內(nèi)存數(shù)據(jù)庫,擁有效率高、速度快的優(yōu)點,支持多種數(shù)據(jù)結(jié)構(gòu)和發(fā)布訂閱的消息模式,擁有高可用性的集群化和主從復(fù)制功能,能夠滿足項目對的需求。