作者介紹
索甯,擅長python開發、mysql、前端等衆多技術領域,曾負責衆多企業安全架構解決方案 ,涉獵行業有媒體、出版社、航空運輸、醫療、軍隊、政府、教育等。
本文主題:
memcached
redis
rabbitmq
一、memcached
1、簡介、安裝、使用
memcached 是一個高性能的分布式記憶體對象緩存系統,用于動态 web 應用以減輕資料庫負載壓力。它通過在記憶體中緩存資料和對象來減少讀取資料庫的次數,進而提高動态、資料庫驅動網站的速度。memcached 基于一個存儲鍵/值對的 hashmap。其守護程序(daemon )是用 c寫的,但是用戶端可以用任何語言來編寫,并通過 memcached 協定與守護程序通信。
memcached 記憶體管理機制:
menceched 通過預配置設定指定的記憶體空間來存取資料,所有的資料都儲存在 memcached 内置的記憶體中。
利用 slab allocation 機制來配置設定和管理記憶體。按照預先規定的大小,将配置設定的記憶體分割成特定長度的記憶體塊,再把尺寸相同的記憶體塊分成組,這些記憶體塊不會釋放,可以重複利用。
當存入的資料占滿記憶體空間時,memcached 使用 lru 算法自動删除不是用的緩存資料,即重用過期資料的記憶體空間。memcached 是為緩存系統設計的,是以沒有考慮資料的容災問題,和機器的記憶體一樣,重新開機機器将會丢失,如果希望服務重新開機資料依然能保留,那麼就需要 sina 網開發的 memcachedb 持久性記憶體緩沖系統,當然還有常見的 nosql 服務如 redis。
預設監聽端口:11211
memcached 安裝

源碼安裝啟動 memcached 快速部署文檔
源碼安裝 memcached php 用戶端
memcached 啟動
memcached -d -m 10 -u root -l 218.97.240.118 -p 12000 -c 256 -p /tmp/memcached.pid
參數說明:
-d 是啟動一個守護程序
-m 是配置設定給memcache使用的記憶體數量,機關是mb
-u 是運作memcache的使用者
-l 是監聽的伺服器ip位址
-p 是設定memcache監聽的端口,最好是1024以上的端口
-c 選項是最大運作的并發連接配接數,預設是1024,按照你伺服器的負載量來設定
-p 是設定儲存memcache的pid檔案
memcached 指令
存儲指令: set/add/replace/append/prepend/cas
擷取指令: get/gets
其他指令: delete/stats..
memcached 管理
memcached memadmin php工具界面化管理安裝部署文檔
# memadmin php 工具管理(memcadmin-1.0.12.tar.gz)
1、安裝memadmin php工具。
2、 登陸memadmin php。
web方式通路:http://ip位址/memadmin/
預設使用者名密碼都為admin。
2、python 操作 memcached
1)安裝 api 及 基本操作
python 操作 memcached 使用 python-memcached 子產品
下載下傳安裝:https://pypi.python.org/pypi/python-memcached
import memcache
mc = memcache.client(['192.168.1.5:12000'], debug=true)
mc.set("foo", "bar")
ret = mc.get('foo')
print ret
2)天生支援叢集
python-memcached 子產品原生支援叢集操作,其原理本質是在記憶體維護一個主機清單,數字為權重,為3即出現3次,相對應的幾率大
mc = memcache.client([
('192.168.1.5:12000', 3), # 數字為權重
('192.168.1.9:12000', 1),
], debug=true)
# 那麼在記憶體中主機清單為:
# host_list = ["192.168.1.5","192.168.1.5","192.168.1.5","192.168.1.9",]
那麼問題來了,叢集情況下如何選擇伺服器存儲呢?
如果要建立設定一個鍵值對(如:k1 = "v1"),那麼它的執行流程如下:
将 k1 轉換成一個數字
将數字和主機清單的長度求餘數,得到一個值 n(n 的範圍: 0 <= n < 清單長度 )
在主機清單中根據 第2步得到的值為索引擷取主機,例如:host_list[n]
連接配接 将第3步中擷取的主機,将 k1 = "v1" 放置在該伺服器的記憶體中
擷取值的話也一樣
源碼、将字元串轉換為數字
3)add
添加一個鍵值對,如果 key 已經存在,重複添加執行 add 則抛出異常
mc.add('k1', 'v1')
# mc.add('k1', 'v2') # 報錯,對已經存在的key重複添加,失敗!!!
4)replace
replace 修改某個 key 的值,如果 key 不存在,則異常
# 如果memcache中存在kkkk,則替換成功,否則一場
mc.replace('kkkk','999')
5) set 和 set_multi
set 設定一個鍵值對,如果 key 不存在,則建立
set_multi 設定多個鍵值對,如果 key 不存在,則建立
mc.set('name', 'nick')
mc.set_multi({'name': 'nick', 'age': '18'})
6) delete 和 delete_multi
delete 删除指定的一個鍵值對
delete_multi 删除指定的多個鍵值對
mc..delete('name', 'nick')
mc.delete_multi({'name': 'nick', 'age': '18'})
7) get 和 get_multi
get 擷取一個鍵值對
get_multi 擷取多個鍵值對
val = mc.get('name')
item_dict = mc.get_multi(["name", "age",])
8)append 和 prepend
append 修改指定key的值,在該值 後面 追加内容
prepend 修改指定key的值,在該值 前面 插入内容
# 原始值: k1 = "v1"
mc.append('k1', 'after')
# k1 = "v1after"
mc.prepend('k1', 'before')
# k1 = "beforev1after"
9) decr 和 incr
incr 自增,将 memcached 中的某個值增加 n ( n 預設為1 )
decr 自減,将 memcached 中的某個值減少 n ( n 預設為1 )
mport memcache
mc.set('k1', '666')
mc.incr('k1')
# k1 = 667
mc.incr('k1', 10)
# k1 = 677
mc.decr('k1')
# k1 = 676
mc.decr('k1', 10)
# k1 = 666
10) gets 和 cas
這兩個方法就是傳說中的鎖 。為了避免髒資料的産生而生:
mc = memcache.client(['192.168.1.5:12000'], debug=true, cache_cas=true)
v = mc.gets('product_count')
# 如果有人在gets之後和cas之前修改了product_count,那下面的設定将會執行失敗,剖出異常
mc.cas('product_count', "899")
本質:每次執行 gets 時,就從 memcache 中擷取一個自增的數字,通過 cas 去修改 gets 到的值時,會攜帶之前擷取的自增值和 memcache 中的自增值進行比較,如果相等,則可以送出,如果不相等,那表示在 gets 和 cas 執行之間,又有其他人執行了 gets(擷取了緩沖的指定值),如此一來有可能出現非正常的資料,則不允許修改,并報錯。
二、redis
1、簡介、安裝、使用、執行個體
remote dictionary server(redis)是一個基于 key-value 鍵值對的持久化資料庫存儲系統。redis 和 memcached 緩存服務很像,但它支援存儲的 value 類型相對更多,包括 string (字元串)、list (連結清單)、set (集合)、zset (sorted set --有序集合)和 hash(哈希類型)。這些資料類型都支援 push/pop、add/remove 及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis 支援各種不同方式的排序。與memcached 一樣,為了保證效率,資料都是緩存在記憶體中。差別的是 redis 會周期性的把更新的資料寫入磁盤或者把修改操作寫入追加的記錄檔案,并且在此基礎上實作了 master-slave (主從)同步。
redis 的出現,再一定程度上彌補了 memcached 這類 key-value 記憶體換乘服務的不足,在部分場合可以對關系資料庫起到很好的補充作用。redis 提供了 python,ruby,erlang,php 用戶端,使用友善。
官方文檔:
http://www.redis.io/documentation
http://www.redis.cn/
redis 安裝和使用執行個體
redis 源碼快速安裝文檔
redis 安裝目錄及各檔案作用
配置并啟動 redis 服務
用戶端連接配接指令及指令測試
redis 的 php 用戶端拓展安裝
redis 主從同步
redis 負載均衡
至于 redis 的負載均衡,方案有很多:
lvs、keepalived、twemproxy
有時間再補上吧...
redis 持久化
redis持久化方式有兩種:
(1)rdb:對記憶體中資料庫狀态進行快照;
(2)aof:把每條寫指令都寫入檔案,類似mysql的binlog日志
rdb。
将redis在記憶體中的資料庫狀态儲存到磁盤裡面,rdb檔案是一個經過壓縮的二進制檔案,通過該檔案可以還原生成rdb檔案時的資料庫狀态。
rdb的生成方式:
(1)執行指令手動生成
有兩個redis指令可以用于生成rdb檔案,一個是save,另一個是bgsave
save指令會阻塞redis伺服器程序,直到rdb檔案建立完畢為止,在伺服器程序阻塞期間,伺服器不能處理任何指令請求;
bgsave指令會派生出一個子程序,然後由子程序負責建立rdb檔案,伺服器程序(父程序)繼續處理指令請求,建立rdb檔案結束之前,用戶端發送的bgsave和save指令會被伺服器拒絕。
(2)通過配置自動生成
可以設定伺服器配置的save選項,讓伺服器每隔一段時間自動執行一次bgsave指令;
可以通過save選項設定多個儲存條件,但隻要其中任意一個條件被滿足,伺服器就會執行bgsave指令。
例如:
save 900 1
save 300 10
save 60 10000
那麼隻要滿足以下三個條件中的任意一個,bgsave指令就會被執行:
伺服器在900秒之内,對資料庫進行了至少1次修改
伺服器在300秒之内,對資料庫進行了至少10次修改
伺服器在60秒之内,對資料庫進行了至少10000次修改
aof
aof持久化是通過儲存redis伺服器所執行的寫指令來記錄資料庫狀态的。
aof檔案重新整理的方式,有三種:
(1)appendfsync always - 每送出一個修改指令都調用fsync重新整理到aof檔案,非常非常慢,但也非常安全;
(2)appendfsync everysec - 每秒鐘都調用fsync重新整理到aof檔案,很快,但可能會丢失一秒以内的資料;
(3)appendfsync no - 依靠os進行重新整理,redis不主動重新整理aof,這樣最快,但安全性就差。
預設并推薦每秒重新整理,這樣在速度和安全上都做到了兼顧。
資料恢複
rdb方式:
rdb檔案的載入工作是在伺服器啟動時自動執行的,沒有專門用于載入rdb檔案的指令,隻要redis伺服器在啟動時檢測到rdb檔案存在,它就會自動載入rdb檔案,伺服器在載入rdb檔案期間,會一直處于阻塞狀态,直到載入工作完成為止。
aof方式
伺服器在啟動時,通過載入和執行aof檔案中儲存的指令來還原伺服器關閉之前的資料庫狀态,具體過程:
(1)載入aof檔案
(2)建立模拟用戶端
(3)從aof檔案中讀取一條指令
(4)使用模拟用戶端執行指令
(5)循環讀取并執行指令,直到全部完成
如果同時啟用了rdb和aof方式,aof優先,啟動時隻加載aof檔案恢複資料。
2、python 操作 redis
python 安裝 redis 子產品:
$ sudo pip install redis
or
$ sudo easy_install redis
$ sudo python setup.py install
詳見:
https://github.com/wolph/redis-py
https://pypi.python.org/pypi/redis
https://redislabs.com/python-redis
api 的使用
1) 操作模式
redis-py 提供兩個類 redis 和 strictredis 用于實作 redis 的操作指令,strictredis 用于實作大部分官方的指令,并使用官方的文法和指令,redis 是 strictredis 的子類,用于向後相容舊版本的 redis-py。
import redis
r =redis.redis(host='192.168.1.5', port=6379)
r.set('foo', 'bar')
print r.get('foo')
2) 連接配接池
redis-py 使用 connection pool 來管理對一個 redis server 的所有連接配接,避免每次建立、釋放連接配接帶來的額外開銷。預設每個 redis 執行個體都會維護着一個自己的連接配接池。也可以覆寫直接建立一個連接配接池,然後作為參數 redis,這樣就可以實作多個 redis 執行個體共享一個連接配接池資源。實作用戶端分片或有連接配接如何管理更細的顆粒控制。
pool = redis.connectionpool(host='192.168.1.5', port=6379)
r = redis.redis(connection_pool=pool)
3) 操作
分為五種資料類型,見下圖:
①string 操作,string 在記憶體中格式是一個 name 對應一個 value 來存儲
②hash 操作,redis 中 hash 在記憶體中的存儲格式類似字典。
③list操作,redis 中的 list 在在記憶體中按照一個 name 對應一個 list 來存儲,像變量對應一個清單。
④set 操作,set 集合就是不允許重複的清單。
⑤有序集合,在集合的基礎上,為每個元素排序;元素的排序需要根據另外一個值來進行比較,是以對于有序集合,每一個元素有兩個值:值和分數,分數是專門來做排序的。
4)管道
預設情況下,redis-py 每次在執行請求時都會建立和斷開一次連接配接操作(連接配接池申請連接配接,歸還連接配接池),如果想要在一次請求中執行多個指令,則可以使用 pipline 實作一次請求執行多個指令,并且預設情況下 pipline 是原子性操作。
見以下執行個體:
pool = redis.connectionpool(host='10.211.55.4', port=6379)
# pipe = r.pipeline(transaction=false)
pipe = r.pipeline(transaction=true)
r.set('name', 'nick')
r.set('age', '18')
pipe.execute()
5) 釋出和訂閱
釋出者:伺服器
訂閱者:dashboad 和資料處理
釋出訂閱的 demo 如下:
redishelper
訂閱者:
釋出者:
三、rabbitmq
rabbitmq 是一個在 amqp 基礎上完成的,可複用的企業消息系統。他遵循 mozilla public license 開源協定。
mq 全稱為 message queue, 消息隊列(mq)是一種應用程式對應用程式的通信方式。應用程式通過讀寫出入隊列的消息(針對應用程式的資料)來通信,而無需專用連接配接來連結它們。消息傳遞指的是程式之間通過在消息中發送資料進行通信,而不是通過直接調用彼此來通信,直接調用通常是用于諸如遠端過程調用的技術。排隊指的是應用程式通過 隊列來通信。隊列的使用除去了接收和發送應用程式同時執行的要求。
流程上生産者把消息放到隊列中去, 然後消費者從隊列中取出消息。
producing,生産者, 産生消息的角色.
exchange,交換器, 在得到生産者産生的消息後, 把消息放入隊列的角色.
queue,隊列,消息暫時儲存的地方.
consuming,消費者, 把消息從隊列中取出的角色.
消息 message
rabbitmq安裝
2、使用api操作rabbitmq
基于隊列 queue 實作生産者消費者模型:
view code
rabbitmq 實作
1、acknowledgment 消息不丢失
no-ack = false,如果消費者由于某些情況宕了(its channel is closed, connection is closed, or tcp connection is lost),那 rabbitmq 會重新将該任務放入隊列中。
在實際應用中,可能會發生消費者收到queue中的消息,但沒有處理完成就當機(或出現其他意外)的情況,這種情況下就可能會導緻消息丢失。為了避免這種情況發生,我們可以要求消費者在消費完消息後發送一個回執給rabbitmq,rabbitmq收到消息回執(message acknowledgment)後才将該消息從queue中移除;如果rabbitmq沒有收到回執并檢測到消費者的rabbitmq連接配接斷開,則rabbitmq會将該消息發送給其他消費者(如果存在多個消費者)進行處理。這裡不存在timeout概念,一個消費者處理消息時間再長也不會導緻該消息被發送給其他消費者,除非它的rabbitmq連接配接斷開。
這裡會産生另外一個問題,如果我們的開發人員在處理完業務邏輯後,忘記發送回執給rabbitmq,這将會導緻嚴重的bug——queue中堆積的消息會越來越多;消費者重新開機後會重複消費這些消息并重複執行業務邏輯……
消費者
2、durable 消息不丢失
如果我們希望即使在rabbitmq服務重新開機的情況下,也不會丢失消息,我們可以将queue與message都設定為可持久化的(durable),這樣可以保證絕大部分情況下我們的rabbitmq消息不會丢失。但依然解決不了小機率丢失事件的發生(比如rabbitmq伺服器已經接收到生産者的消息,但還沒來得及持久化該消息時rabbitmq伺服器就斷電了),如果我們需要對這種小機率事件也要管理起來,那麼我們要用到事務。由于這裡僅為rabbitmq的簡單介紹,是以這裡将不講解rabbitmq相關的事務。
需要改兩處地方:
生産者
3、消息擷取順序
預設情況下,消費者拿消息隊列裡的資料是按平均配置設定,例如:消費者1 拿隊列中 奇數 序列的任務,消費者2 拿隊列中 偶數 序列的任務。
channel.basic_qos(prefetch_count=1) 表示誰來誰取,不再按照奇偶數排列,這個性能較高的機器拿的任務就多。
4、釋出訂閱
釋出訂閱和簡單的消息隊列差別在于,釋出訂閱者會将消息發送給所有的訂閱者,而消息隊列中的資料被消費一次便消失。是以,rabbitmq 實作釋出訂閱時,會為每一個訂閱者建立一個隊列,而釋出者釋出消息的時候,會将消息放置在所有相關的隊列中。
exchange type = fanout
釋出者
訂閱者
5、關鍵字發送
第4步執行個體中,發送消息必須明确指定某個隊列并向其中發送消息,當然,rabbitmq 還支援根據關鍵字發送(隊列綁定關鍵字),發送者将消息發送到 exchange,exchange 根據關鍵字 判定應該将資料發送至指定隊列。
exchange type = direct
6、模糊比對
exchange type = topic
在 topic 類型下,可以讓隊列綁定幾個模糊的關鍵字,之後發送者将資料發送到 exchange,exchange 将傳入”路由值“和 ”關鍵字“進行比對,比對成功,則将資料發送到指定隊列。
比對基本規則及示例:
# 表示可以比對 0 個 或 多個 單詞
表示隻能比對 一個 單詞
發送者路由值 隊列中
www.suoning.python www.* -- 不比對
www.suoning.python www.# -- 比對
原文釋出時間為:2016-12-15
本文來自雲栖社群合作夥伴dbaplus