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