當(dāng)前位置:首頁(yè) > 百科知識(shí) > 工控 > 正文

Redis

Redis是一個(gè)開源的使用ANSI C語(yǔ)言編寫、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫(kù),并提供多種語(yǔ)言的API。從2010年3月15日起,Redis的開發(fā)工作由VMware主持。從2013年5月開始,Redis的開發(fā)由Pivotal贊助

定義

  redis是一個(gè)key-value存儲(chǔ)系統(tǒng)。和Memcached類似,它支持存儲(chǔ)的value類型相對(duì)更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數(shù)據(jù)類型都支持push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎(chǔ)上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數(shù)據(jù)都是緩存在內(nèi)存中。區(qū)別的是redis會(huì)周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件,并且在此基礎(chǔ)上實(shí)現(xiàn)了master-slave(主從)同步。

  Redis 是一個(gè)高性能的key-value數(shù)據(jù)庫(kù)。 redis的出現(xiàn),很大程度補(bǔ)償了memcached這類key/value存儲(chǔ)的不足,在部 分場(chǎng)合可以對(duì)關(guān)系數(shù)據(jù)庫(kù)起到很好的補(bǔ)充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客戶端,使用很方便。[1]

  Redis支持主從同步。數(shù)據(jù)可以從主服務(wù)器向任意數(shù)量的從服務(wù)器上同步,從服務(wù)器可以是關(guān)聯(lián)其他從服務(wù)器的主服務(wù)器。這使得Redis可執(zhí)行單層樹復(fù)制。存盤可以有意無(wú)意的對(duì)數(shù)據(jù)進(jìn)行寫操作。由于完全實(shí)現(xiàn)了發(fā)布/訂閱機(jī)制,使得從數(shù)據(jù)庫(kù)在任何地方同步樹時(shí),可訂閱一個(gè)頻道并接收主服務(wù)器完整的消息發(fā)布記錄。同步對(duì)讀取操作的可擴(kuò)展性和數(shù)據(jù)冗余很有幫助。

  redis的官網(wǎng)地址,非常好記,是redis.io。(特意查了一下,域名后綴io屬于國(guó)家域名,是british Indian Ocean territory,即英屬印度洋領(lǐng)地)

  目前,Vmware在資助著redis項(xiàng)目的開發(fā)和維護(hù)。

作者

  redis[2] 的作者,叫Salvatore Sanfilippo,來(lái)自意大利的西西里島,現(xiàn)在居住在卡塔尼亞。目前供職于Pivotal公司。他使用的網(wǎng)名是antirez。

性能

  下面是官方的bench-mark數(shù)據(jù):[1]

  測(cè)試完成了50個(gè)并發(fā)執(zhí)行100000個(gè)請(qǐng)求。

  設(shè)置和獲取的值是一個(gè)256字節(jié)字符串。

  Linux box是運(yùn)行Linux 2.6,這是X3320 Xeon 2.5 ghz。

  文本執(zhí)行使用loopback接口(127.0.0.1)。

  結(jié)果:讀的速度是110000次/s,寫的速度是81000次/s 。

支持語(yǔ)言

  許多語(yǔ)言都包含Redis支持,包括:[1]

  ActionScript

  C

  C++

  C#

  Clojure

  Common Lisp

  Dart

  Erlang

  Go

  Haskell

  Haxe

  Io

  Java

  Node.js

  Lua

  Objective-C

  Perl

  PHP

  Pure Data

  Python

  R

  Ruby

  Scala

  Smalltalk

  Tcl

  數(shù)據(jù)模型編輯

  Redis的外圍由一個(gè)鍵、值映射的字典構(gòu)成。與其他非關(guān)系型數(shù)據(jù)庫(kù)主要不同在于:Redis中值的類型[1] 不僅限于字符串,還支持如下抽象數(shù)據(jù)類型:

  字符串列表

  無(wú)序不重復(fù)的字符串集合

  有序不重復(fù)的字符串集合

  鍵、值都為字符串的哈希表[1]

  值的類型決定了值本身支持的操作。Redis支持不同無(wú)序、有序的列表,無(wú)序、有序的集合間的交集、并集等高級(jí)服務(wù)器端原子操作。

數(shù)據(jù)結(jié)構(gòu)

  redis提供五種數(shù)據(jù)類型:string,hash,list,set及zset(sorted set)。

  string(字符串)

  string是最簡(jiǎn)單的類型,你可以理解成與Memcached一模一樣的類型,一個(gè)key對(duì)應(yīng)一個(gè)value,其上支持的操作與Memcached的操作類似。但它的功能更豐富。

  redis采用結(jié)構(gòu)sdshdr和sds封裝了字符串,字符串相關(guān)的操作實(shí)現(xiàn)在源文件sds.h/sds.c中。

  數(shù)據(jù)結(jié)構(gòu)定義如下:

  typedefchar*sds;

  structsdshdr{

  longlen;

  longfree;

  charbuf[];

  };

  list(雙向鏈表)

  list是一個(gè)鏈表結(jié)構(gòu),主要功能是push、pop、獲取一個(gè)范圍的所有值等等。操作中key理解為鏈表的名字。

  對(duì)list的定義和實(shí)現(xiàn)在源文件adlist.h/adlist.c,相關(guān)的數(shù)據(jù)結(jié)構(gòu)定義如下:

  //list迭代器

  typedefstructlistIter{

  listNode*next;

  intdirection;

  }listIter;

  //list數(shù)據(jù)結(jié)構(gòu)

  typedefstructlist{

  listNode*head;

  listNode*tail;

  void*(*dup)(void*ptr);

  void(*free)(void*ptr);

  int(*match)(void*ptr,void*key);

  unsignedintlen;

  listIteriter;

  }list;

  dict(hash表)

  set是集合,和我們數(shù)學(xué)中的集合概念相似,對(duì)集合的操作有添加刪除元素,有對(duì)多個(gè)集合求交并差等操作。操作中key理解為集合的名字。

  在源文件dict.h/dict.c中實(shí)現(xiàn)了hashtable的操作,數(shù)據(jù)結(jié)構(gòu)的定義如下:

  //dict中的元素項(xiàng)

  typedefstructdictEntry{

  void*key;

  void*val;

  structdictEntry*next;

  }dictEntry;

  //dict相關(guān)配置函數(shù)

  typedefstructdictType{

  unsignedint(*hashFunction)(constvoid*key);

  void*(*keyDup)(void*privdata,constvoid*key);

  void*(*valDup)(void*privdata,constvoid*obj);

  int(*keyCompare)(void*privdata,constvoid*key1,constvoid*key2);

  void(*keyDestructor)(void*privdata,void*key);

  void(*valDestructor)(void*privdata,void*obj);

  }dictType;

  //dict定義

  typedefstructdict{

  dictEntry**table;

  dictType*type;

  unsignedlongsize;

  unsignedlongsizemask;

  unsignedlongused;

  void*privdata;

  }dict;

  //dict迭代器

  typedefstructdictIterator{

  dict*ht;

  intindex;

  dictEntry*entry,*nextEntry;

  }dictIterator;

  dict中table為dictEntry指針的數(shù)組,數(shù)組中每個(gè)成員為hash值相同元素的單向鏈表。set是在dict的基礎(chǔ)上實(shí)現(xiàn)的,指定了key的比較函數(shù)為dictEncObjKeyCompare,若key相等則不再插入。

  zset(排序set)

  zset是set的一個(gè)升級(jí)版本,他在set的基礎(chǔ)上增加了一個(gè)順序?qū)傩?,這一屬性在添加修改元素的時(shí)候可以指定,每次指定后,zset會(huì)自動(dòng)重新按新的值調(diào)整順序??梢岳斫饬擞袃闪械膍ysql表,一列存value,一列存順序。操作中key理解為zset的名字。

  typedefstructzskiplistNode{

  structzskiplistNode**forward;

  structzskiplistNode*backward;

  doublescore;

  robj*obj;

  }zskiplistNode;

  typedefstructzskiplist{

  structzskiplistNode*header,*tail;

  unsignedlonglength;

  intlevel;

  }zskiplist;

  typedefstructzset{

  dict*dict;

  zskiplist*zsl;

  }zset;

  zset利用dict維護(hù)key -> value的映射關(guān)系,用zsl(zskiplist)保存value的有序關(guān)系。zsl實(shí)際是叉數(shù)

  不穩(wěn)定的多叉樹,每條鏈上的元素從根節(jié)點(diǎn)到葉子節(jié)點(diǎn)保持升序排序。

常用命令

  就DB來(lái)說,Redis成績(jī)已經(jīng)很驚人了,且不說memcachedb和Tokyo Cabinet之流,就說原版的memcached,速度似乎也只能達(dá)到這個(gè)級(jí)別。Redis根本是使用內(nèi)存存儲(chǔ),持久化的關(guān)鍵是這三條指令:SAVE BGSAVE LASTSAVE …

  當(dāng)接收到SAVE指令的時(shí)候,Redis就會(huì)dump數(shù)據(jù)到一個(gè)文件里面。

  值得一說的是它的獨(dú)家功能:存儲(chǔ)列表和集合,這是它與mc之流相比更有競(jìng)爭(zhēng)力的地方。

  不介紹mc里面已經(jīng)有的東東,只列出特殊的:

  TYPE key — 用來(lái)獲取某key的類型

  KEYS pattern — 匹配所有符合模式的key,比如KEYS * 就列出所有的key了,當(dāng)然,復(fù)雜度O(n)

  RANDOMKEY - 返回隨機(jī)的一個(gè)key

  RENAME oldkeynewkey— key也可以改名

  列表操作,精華

  RPUSH key string — 將某個(gè)值加入到一個(gè)key列表末尾

  LPUSH key string — 將某個(gè)值加入到一個(gè)key列表頭部

  LLEN key — 列表長(zhǎng)度

  LRANGE key start end — 返回列表中某個(gè)范圍的值,相當(dāng)于mysql里面的分頁(yè)查詢那樣

  LTRIM key start end — 只保留列表中某個(gè)范圍的值

  LINDEX key index — 獲取列表中特定索引號(hào)的值,要注意是O(n)復(fù)雜度

  LSET key index value — 設(shè)置列表中某個(gè)位置的值

  LPOP key

  RPOP key — 和上面的LPOP一樣,就是類似?;蜿?duì)列的那種取頭取尾指令,可以當(dāng)成消息隊(duì)列來(lái)使用了

  集合操作

  SADD key member — 增加元素

  SREM key member — 刪除元素

  SCARD key — 返回集合大小

  SISMEMBER key member — 判斷某個(gè)值是否在集合中

  SINTER key1 key2 ... keyN — 獲取多個(gè)集合的交集元素

  SMEMBERS key — 列出集合的所有元素

  還有Multiple DB的命令,可以更換db,數(shù)據(jù)可以隔離開,默認(rèn)是存放在DB 0。

存儲(chǔ)

  redis使用了兩種文件格式:全量數(shù)據(jù)和增量請(qǐng)求。

  全量數(shù)據(jù)格式是把內(nèi)存中的數(shù)據(jù)寫入磁盤,便于下次讀取文件進(jìn)行加載;

  增量請(qǐng)求文件則是把內(nèi)存中的數(shù)據(jù)序列化為操作請(qǐng)求,用于讀取文件進(jìn)行replay得到數(shù)據(jù),序列化的操作包括SET、RPUSH、SADD、ZADD。

  redis的存儲(chǔ)分為內(nèi)存存儲(chǔ)、磁盤存儲(chǔ)和log文件三部分,配置文件中有三個(gè)參數(shù)對(duì)其進(jìn)行配置。

  save seconds updates,save配置,指出在多長(zhǎng)時(shí)間內(nèi),有多少次更新操作,就將數(shù)據(jù)同步到數(shù)據(jù)文件。這個(gè)可以多個(gè)條件配合,比如默認(rèn)配置文件中的設(shè)置,就設(shè)置了三個(gè)條件。

  appendonly yes/no ,appendonly配置,指出是否在每次更新操作后進(jìn)行日志記錄,如果不開啟,可能會(huì)在斷電時(shí)導(dǎo)致一段時(shí)間內(nèi)的數(shù)據(jù)丟失。因?yàn)閞edis本身同步數(shù)據(jù)文件是按上面的save條件來(lái)同步的,所以有的數(shù)據(jù)會(huì)在一段時(shí)間內(nèi)只存在于內(nèi)存中。

  appendfsync no/always/everysec ,appendfsync配置,no表示等操作系統(tǒng)進(jìn)行數(shù)據(jù)緩存同步到磁盤,always表示每次更新操作后手動(dòng)調(diào)用fsync()將數(shù)據(jù)寫到磁盤,everysec表示每秒同步一次。

安裝

  獲取源碼、解壓、進(jìn)入源碼目錄

  使用wget工具等下載:

  wget (百度不讓用鏈接)

  tar xzf redis-1.2.6.tar.gz

  cd redis-1.2.6。

  編譯生成可執(zhí)行文件

  由于makefile文件已經(jīng)寫好,我們只需要直接在源碼目錄執(zhí)行make命令進(jìn)行編譯即可:

  make

  make-test

  sudo make install

  make命令執(zhí)行完成后,會(huì)在當(dāng)前目錄下生成本個(gè)可執(zhí)行文件,分別是redis-server、redis-cli、redis-benchmark、redis-stat,它們的作用如下:

  redis-server:Redis服務(wù)器的daemon啟動(dòng)程序

  redis-cli:Redis命令行操作工具。當(dāng)然,你也可以用telnet根據(jù)其純文本協(xié)議來(lái)操作

  redis-benchmark:Redis性能測(cè)試工具,測(cè)試Redis在你的系統(tǒng)及你的配置下的讀寫性能

  redis-stat:Redis狀態(tài)檢測(cè)工具,可以檢測(cè)Redis當(dāng)前狀態(tài)參數(shù)及延遲狀況。

  建立Redis目錄(非必須)

  這個(gè)過程不是必須的,只是為了將Redis相關(guān)的資源統(tǒng)一管理而進(jìn)行的操作。

  執(zhí)行以下命令建立相關(guān)目錄并拷貝相關(guān)文件至目錄中:

  sudo -s

  mkdir -p /usr/local/redis/bin

  mkdir -p /usr/local/redis/etc

  mkdir -p /usr/local/redis/var

  cp redis-server redis-cli redis-benchmark redis-stat /usr/local/redis/bin/

  cp redis.conf /usr/local/redis/etc/

  配置參數(shù)

  在我們成功安裝Redis后,我們直接執(zhí)行redis-server即可運(yùn)行Redis,此時(shí)它是按照默認(rèn)配置來(lái)運(yùn)行的(默認(rèn)配置甚至不是后臺(tái)運(yùn)行)。我們希望Redis按我們的要求運(yùn)行,則我們需要修改配置文件,Redis的配置文件就是我們上面第二個(gè)cp操作的redis.conf文件,它被我們拷貝到了/usr/local/redis/etc/目錄下。修改它就可以配置我們的server了。如何修改?下面是redis.conf的主要配置參數(shù)的意義:

  daemonize:是否以后臺(tái)daemon方式運(yùn)行

  pidfile:pid文件位置

  port:監(jiān)聽的端口號(hào)

  timeout:請(qǐng)求超時(shí)時(shí)間

  loglevel:log信息級(jí)別

  logfile:log文件位置

  databases:開啟數(shù)據(jù)庫(kù)的數(shù)量

  save * *:保存快照的頻率,第一個(gè)*表示多長(zhǎng)時(shí)間,第二個(gè)*表示執(zhí)行多少次寫操作。在一定時(shí)間內(nèi)執(zhí)行一定數(shù)量的寫操作時(shí),自動(dòng)保存快照??稍O(shè)置多個(gè)條件。

  rdbcompression:是否使用壓縮

  dbfilename:數(shù)據(jù)快照文件名(只是文件名,不包括目錄)

  dir:數(shù)據(jù)快照的保存目錄(這個(gè)是目錄)

  appendonly:是否開啟appendonlylog,開啟的話每次寫操作會(huì)記一條log,這會(huì)提高數(shù)據(jù)抗風(fēng)險(xiǎn)能力,但影響效率。

  appendfsync:appendonlylog如何同步到磁盤(三個(gè)選項(xiàng),分別是每次寫都強(qiáng)制調(diào)用fsync、每秒啟用一次fsync、不調(diào)用fsync等待系統(tǒng)自己同步)

  下面是一個(gè)略做修改后的配置文件內(nèi)容:

  daemonizeyes

  pidfile/usr/local/redis/var/redis.pid

  port6379

  timeout300

  logleveldebug

  logfile/usr/local/redis/var/redis.log

  databases16

  save9001

  save30010

  save6010000

  rdbcompressionyes

  dbfilenamedump.rdb

  dir/usr/local/redis/var/

  appendonlyno

  appendfsyncalways

  glueoutputbufyes

  shareobjectsno

  shareobjectspoolsize1024

  將上面內(nèi)容寫為redis.conf并保存到/usr/local/redis/etc/目錄下

  然后在命令行執(zhí)行:

  /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf

  即可在后臺(tái)啟動(dòng)redis服務(wù),這時(shí)你通過

  telnet127.0.0.16379

  即可連接到你的redis服務(wù)

  Redis常用內(nèi)存優(yōu)化手段與參數(shù)

  通過我們上面的一些實(shí)現(xiàn)上的分析可以看出redis實(shí)際上的內(nèi)存管理成本非常高,即占用了過多的內(nèi)存,作者對(duì)這點(diǎn)也非常清楚,所以提供了一系列的參數(shù)和手段來(lái)控制和節(jié)省內(nèi)存,我們分別來(lái)討論下。

  首先最重要的一點(diǎn)是不要開啟Redis的VM選項(xiàng),即虛擬內(nèi)存功能,這個(gè)本來(lái)是作為Redis存儲(chǔ)超出物理內(nèi)存數(shù)據(jù)的一種數(shù)據(jù)在內(nèi)存與磁盤換入換出的一個(gè)持久化策略,但是其內(nèi)存管理成本也非常的高,并且我們后續(xù)會(huì)分析此種持久化策略并不成熟,所以要關(guān)閉VM功能,請(qǐng)檢查你的redis.conf文件中 vm-enabled 為 no。

  其次最好設(shè)置下redis.conf中的maxmemory選項(xiàng),該選項(xiàng)是告訴Redis當(dāng)使用了多少物理內(nèi)存后就開始拒絕后續(xù)的寫入請(qǐng)求,該參數(shù)能很好的保護(hù)好你的Redis不會(huì)因?yàn)槭褂昧诉^多的物理內(nèi)存而導(dǎo)致swap,最終嚴(yán)重影響性能甚至崩潰。

  另外Redis為不同數(shù)據(jù)類型分別提供了一組參數(shù)來(lái)控制內(nèi)存使用,我們?cè)谇懊嬖敿?xì)分析過Redis Hash是value內(nèi)部為一個(gè)HashMap,如果該Map的成員數(shù)比較少,則會(huì)采用類似一維線性的緊湊格式來(lái)存儲(chǔ)該Map, 即省去了大量指針的內(nèi)存開銷,這個(gè)參數(shù)控制對(duì)應(yīng)在redis.conf配置文件中下面2項(xiàng):

  hash-max-zipmap-entries 64

  hash-max-zipmap-value 512

  hash-max-zipmap-entries

  含義是當(dāng)value這個(gè)Map內(nèi)部不超過多少個(gè)成員時(shí)會(huì)采用線性緊湊格式存儲(chǔ),默認(rèn)是64,即value內(nèi)部有64個(gè)以下的成員就是使用線性緊湊存儲(chǔ),超過該值自動(dòng)轉(zhuǎn)成真正的HashMap。

  hash-max-zipmap-value 含義是當(dāng) value這個(gè)Map內(nèi)部的每個(gè)成員值長(zhǎng)度不超過多少字節(jié)就會(huì)采用線性緊湊存儲(chǔ)來(lái)節(jié)省空間。

  以上2個(gè)條件任意一個(gè)條件超過設(shè)置值都會(huì)轉(zhuǎn)換成真正的HashMap,也就不會(huì)再節(jié)省內(nèi)存了,那么這個(gè)值是不是設(shè)置的越大越好呢,答案當(dāng)然是否定的,HashMap的優(yōu)勢(shì)就是查找和操作的時(shí)間復(fù)雜度都是O(1)的,而放棄Hash采用一維存儲(chǔ)則是O(n)的時(shí)間復(fù)雜度,如果

  成員數(shù)量很少,則影響不大,否則會(huì)嚴(yán)重影響性能,所以要權(quán)衡好這個(gè)值的設(shè)置,總體上還是最根本的時(shí)間成本和空間成本上的權(quán)衡。

  同樣類似的參數(shù)

  list-max-ziplist-entries 512

  說明:list數(shù)據(jù)類型多少節(jié)點(diǎn)以下會(huì)采用去指針的緊湊存儲(chǔ)格式。

  list-max-ziplist-value 64

  說明:list數(shù)據(jù)類型節(jié)點(diǎn)值大小小于多少字節(jié)會(huì)采用緊湊存儲(chǔ)格式。

  set-max-intset-entries 512

  說明:set數(shù)據(jù)類型內(nèi)部數(shù)據(jù)如果全部是數(shù)值型,且包含多少節(jié)點(diǎn)以下會(huì)采用緊湊格式存儲(chǔ)。

  最后想說的是Redis內(nèi)部實(shí)現(xiàn)沒有對(duì)內(nèi)存分配方面做過多的優(yōu)化,在一定程度上會(huì)存在內(nèi)存碎片,不過大多數(shù)情況下這個(gè)不會(huì)成為Redis的性能瓶頸,不過如果在Redis內(nèi)部存儲(chǔ)的大部分?jǐn)?shù)據(jù)是數(shù)值型的話,Redis內(nèi)部采用了一個(gè)shared integer的方式來(lái)省去分配內(nèi)存的開銷,即在系統(tǒng)啟動(dòng)時(shí)先分配一個(gè)從1~n 那么多個(gè)數(shù)值對(duì)象放在一個(gè)池子中,如果存儲(chǔ)的數(shù)據(jù)恰好是這個(gè)數(shù)值范圍內(nèi)的數(shù)據(jù),則直接從池子里取出該對(duì)象,并且通過引用計(jì)數(shù)的方式來(lái)共享,這樣在系統(tǒng)存儲(chǔ)了大量數(shù)值下,也能一定程度上節(jié)省內(nèi)存并且提高性能,這個(gè)參數(shù)值n的設(shè)置需要修改源代碼中的一行宏定義REDIS_SHARED_INTEGERS,該值默認(rèn)是10000,可以根據(jù)自己的需要進(jìn)行修改,修改后重新編譯就可以了。

  另外redis 的6種過期策略redis 中的默認(rèn)的過期策略是volatile-lru 。設(shè)置方式

  config set maxmemory-policy volatile-lru

  maxmemory-policy 六種方式

  volatile-lru:只對(duì)設(shè)置了過期時(shí)間的key進(jìn)行LRU(默認(rèn)值)

  allkeys-lru : 是從所有key里 刪除 不經(jīng)常使用的key

  volatile-random:隨機(jī)刪除即將過期key

  allkeys-random:隨機(jī)刪除

  volatile-ttl : 刪除即將過期的

  noeviction : 永不過期,返回錯(cuò)誤

  maxmemory-samples 3 是說每次進(jìn)行淘汰的時(shí)候 會(huì)隨機(jī)抽取3個(gè)key 從里面淘汰最不經(jīng)常使用的(默認(rèn)選項(xiàng))


內(nèi)容來(lái)自百科網(wǎng)