Redis 中的事務
Redis支持簡單的事務
Redis與 mysql事務的對比
|
Mysql |
Redis |
開啓 |
start transaction |
muitl |
語句 |
普通sql |
普通命令 |
失敗 |
rollback 回滾 |
discard 取消 |
成功 |
commit |
exec |
注: rollback與discard 的區別
如果已經成功執行了2條語句, 第3條語句出錯.
Rollback後,前2條的語句影響消失.
Discard只是結束本次事務,前2條語句造成的影響仍然還在
注:
在mutil後面的語句中, 語句出錯可能有2種情況
1: 語法就有問題,
這種,exec時,報錯, 所有語句得不到執行
2: 語法本身沒錯,但適用對象有問題. 比如 zadd 操作list對象
Exec之後,會執行正確的語句,並跳過有不適當的語句.
(如果zadd操作list這種事怎麼避免? 這一點,由程序員負責)
思考:
我正在買票
Ticket -1 , money -100
而票只有1張, 如果在我multi之後,和exec之前, 票被別人買了---即ticket變成0了.
我該如何觀察這種情景,並不再提交
悲觀的想法:
世界充滿危險,肯定有人和我搶, 給 ticket上鎖, 只有我能操作. [悲觀鎖]
樂觀的想法:
沒有那麼人和我搶,因此,我只需要注意,
--有沒有人更改ticket的值就可以了 [樂觀鎖]
Redis的事務中,啓用的是樂觀鎖,只負責監測key沒有被改動.
具體的命令---- watch命令
例:
redis 127.0.0.1:6379> watch ticket
OK
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> decr ticket
QUEUED
redis 127.0.0.1:6379> decrby money 100
QUEUED
redis 127.0.0.1:6379> exec
(nil) // 返回nil,說明監視的ticket已經改變了,事務就取消了.
redis 127.0.0.1:6379> get ticket
"0"
redis 127.0.0.1:6379> get money
"200"
watch key1 key2 ... keyN
作用:監聽key1 key2..keyN有沒有變化,如果有變, 則事務取消
unwatch
作用: 取消所有watch監聽
消息訂閱
使用辦法:
訂閱端: Subscribe 頻道名稱
發佈端: publish 頻道名稱 發佈內容
客戶端例子:
redis 127.0.0.1:6379> subscribe news
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news"
3) (integer) 1
1) "message"
2) "news"
3) "good good study"
1) "message"
2) "news"
3) "day day up"
服務端例子:
redis 127.0.0.1:6379> publish news 'good good study'
(integer) 1
redis 127.0.0.1:6379> publish news 'day day up'
(integer) 1
Redis持久化配置
Redis的持久化有2種方式 1快照 2是日誌
Rdb快照的配置選項
save 900 1 // 900內,有1條寫入,則產生快照
save 300 1000 // 如果300秒內有1000次寫入,則產生快照
save 60 10000 // 如果60秒內有10000次寫入,則產生快照
(這3個選項都屏蔽,則rdb禁用)
stop-writes-on-bgsave-error yes // 後臺備份進程出錯時,主進程停不停止寫入?
rdbcompression yes // 導出的rdb文件是否壓縮
Rdbchecksum yes // 導入rbd恢復時數據時,要不要檢驗rdb的完整性
dbfilename dump.rdb //導出來的rdb文件名
dir ./ //rdb的放置路徑
Aof 的配置
appendonly no # 是否打開 aof日誌功能
appendfsync always # 每1個命令,都立即同步到aof. 安全,速度慢
appendfsync everysec # 折衷方案,每秒寫1次
appendfsync no # 寫入工作交給操作系統,由操作系統判斷緩衝區大小,統一寫入到aof. 同步頻率低,速度快,
no-appendfsync-on-rewrite yes: # 正在導出rdb快照的過程中,要不要停止同步aof
auto-aof-rewrite-percentage 100 #aof文件大小比起上次重寫時的大小,增長率100%時,重寫
auto-aof-rewrite-min-size 64mb #aof文件,至少超過64M時,重寫
注: 在dump rdb過程中,aof如果停止同步,會不會丟失?
答: 不會,所有的操作緩存在內存的隊列裏, dump完成後,統一操作.
注: aof重寫是指什麼?
答: aof重寫是指把內存中的數據,逆化成命令,寫入到.aof日誌裏.
以解決 aof日誌過大的問題.
問: 如果rdb文件,和aof文件都存在,優先用誰來恢復數據?
答: aof
問: 2種是否可以同時用?
答: 可以,而且推薦這麼做
問: 恢復時rdb和aof哪個恢復的快
答: rdb快,因爲其是數據的內存映射,直接載入到內存,而aof是命令,需要逐條執行
redis 服務器端命令
redis 127.0.0.1:6380> time ,顯示服務器時間 , 時間戳(秒), 微秒數
1) "1375270361"
2) "504511"
redis 127.0.0.1:6380> dbsize // 當前數據庫的key的數量
(integer) 2
redis 127.0.0.1:6380> select 2
OK
redis 127.0.0.1:6380[2]> dbsize
(integer) 0
redis 127.0.0.1:6380[2]>
BGREWRITEAOF 後臺進程重寫AOF
BGSAVE 後臺保存rdb快照
SAVE 保存rdb快照
LASTSAVE 上次保存時間
Slaveof master-Host port , 把當前實例設爲master的slave
Flushall 清空所有庫所有鍵
Flushdb 清空當前庫所有鍵
Showdown [save/nosave]
注: 如果不小心運行了flushall, 立即 shutdown nosave ,關閉服務器
然後 手工編輯aof文件, 去掉文件中的 “flushall ”相關行, 然後開啓服務器,就可以導入回原來數據.
如果,flushall之後,系統恰好bgrewriteaof了,那麼aof就清空了,數據丟失.
Slowlog 顯示慢查詢
注:多慢才叫慢?
答: 由slowlog-log-slower-than 10000 ,來指定,(單位是微秒)
服務器儲存多少條慢查詢的記錄?
答: 由 slowlog-max-len 128 ,來做限制
Info [Replication/CPU/Memory..]
查看redis服務器的信息
Config get 配置項
Config set 配置項 值 (特殊的選項,不允許用此命令設置,如slave-of, 需要用單獨的slaveof命令來設置)
Redis運維時需要注意的參數
1: 內存
# Memory
used_memory:859192 數據結構的空間
used_memory_rss:7634944 實佔空間
mem_fragmentation_ratio:8.89 前2者的比例,1.N爲佳,如果此值過大,說明redis的內存的碎片化嚴重,可以導出再導入一次.
2: 主從複製
# Replication
role:slave
master_host:192.168.1.128
master_port:6379
master_link_status:up
3:持久化
# Persistence
rdb_changes_since_last_save:0
rdb_last_save_time:1375224063
4: fork耗時
#Status
latest_fork_usec:936 上次導出rdb快照,持久化花費微秒
注意: 如果某實例有10G內容,導出需要2分鐘,
每分鐘寫入10000次,導致不斷的rdb導出,磁盤始處於高IO狀態.
5: 慢日誌
config get/set slowlog-log-slower-than
CONFIG get/SET slowlog-max-len
slowlog get N 獲取慢日誌
運行時更改master-slave
修改一臺slave(設爲A)爲new master
- 命令該服務不做其他redis服務的slave
命令: slaveof no one
- 修改其readonly爲yes
其他的slave再指向new master A
- 命令該服務爲new master A的slave
命令格式 slaveof IP port
監控工具 sentinel
Sentinel不斷與master通信,獲取master的slave信息.
監聽master與slave的狀態
如果某slave失效,直接通知master去除該slave.
如果master失效,,是按照slave優先級(可配置), 選取1個slave做 new master
,把其他slave--> new master
疑問: sentinel與master通信,如果某次因爲master IO操作頻繁,導致超時,
此時,認爲master失效,很武斷.
解決: sentnel允許多個實例看守1個master, 當N臺(N可設置)sentinel都認爲master失效,才正式失效.
Sentinel選項配置
port 26379 # 端口
sentinel monitor mymaster 127.0.0.1 6379 2 ,
給主機起的名字(不重即可),
當2個sentinel實例都認爲master失效時,正式失效
sentinel down-after-milliseconds mymaster 30000 多少毫秒後連接不到master認爲斷開
sentinel can-failover mymaster yes #是否允許sentinel修改slave->master. 如爲no,則只能監控,無權修改./
sentinel parallel-syncs mymaster 1 , 一次性修改幾個slave指向新的new master.
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh ,# 在重新配置new master,new slave過程,可以觸發的腳本
redis 與關係型數據庫的適合場景
書籤系統
create table book (
bookid int,
title char(20)
)engine myisam charset utf8;
insert into book values
(5 , 'PHP聖經'),
(6 , 'ruby實戰'),
(7 , 'mysql運維')
(8, 'ruby服務端編程');
create table tags (
tid int,
bookid int,
content char(20)
)engine myisam charset utf8;
insert into tags values
(10 , 5 , 'PHP'),
(11 , 5 , 'WEB'),
(12 , 6 , 'WEB'),
(13 , 6 , 'ruby'),
(14 , 7 , 'database'),
(15 , 8 , 'ruby'),
(16 , 8 , 'server');
# 既有web標籤,又有PHP,同時還標籤的書,要用連接查詢
select * from tags inner join tags as t on tags.bookid=t.bookid
where tags.content='PHP' and t.content='WEB';
換成key-value存儲
用kv 來存儲
set book:5:title 'PHP聖經'
set book:6:title 'ruby實戰'
set book:7:title 'mysql運難'
set book:8:title ‘ruby server’
sadd tag:PHP 5
sadd tag:WEB 5 6
sadd tag:database 7
sadd tag:ruby 6 8
sadd tag:SERVER 8
查: 既有PHP,又有WEB的書
Sinter tag:PHP tag:WEB #查集合的交集
查: 有PHP或有WEB標籤的書
Sunin tag:PHP tag:WEB
查:含有ruby,不含WEB標籤的書
Sdiff tag:ruby tag:WEB #求差集
Redis key 設計技巧
1: 把表名轉換爲key前綴 如, tag:
2: 第2段放置用於區分區key的字段--對應mysql中的主鍵的列名,如userid
3: 第3段放置主鍵值,如2,3,4...., a , b ,c
4: 第4段,寫要存儲的列名
用戶表 user , 轉換爲key-value存儲 |
|||
userid |
username |
passworde |
|
9 |
Lisi |
1111111 |
set user:userid:9:username lisi
set user:userid:9:password 111111
set user:userid:9:email [email protected]
keys user:userid:9*
2 注意:
在關係型數據中,除主鍵外,還有可能其他列也步驟查詢,
如上表中, username 也是極頻繁查詢的,往往這種列也是加了索引的.
轉換到k-v數據中,則也要相應的生成一條按照該列爲主的key-value
Set user:username:lisi:uid 9
這樣,我們可以根據username:lisi:uid ,查出userid=9,
再查user:9:password/email ...
完成了根據用戶名來查詢用戶信息
php-redis擴展編譯
1: 到pecl.php.net 搜索redis
2: 下載stable版(穩定版)擴展
3: 解壓,
4: 執行/php/path/bin/phpize (作用是檢測PHP的內核版本,併爲擴展生成相應的編譯配置)
5: configure --with-php-config=/php/path/bin/php-config
6: make && make install
引入編譯出的redis.so插件
1: 編輯php.ini
2: 添加
redis插件的使用
// get instance
$redis = new Redis();
// connect to redis server
$redis->open('localhost',6380);
$redis->set('user:userid:9:username','wangwu');
var_dump($redis->get('user:userid:9:username'));
微博項目的key設計
全局相關的key:
表名 |
global |
|
列名 |
操作 |
備註 |
Global:userid |
incr |
產生全局的userid |
Global:postid |
Incr |
產生全局的postid |
用戶相關的key(表)
表名 |
user |
||
Userid |
Username |
Password |
Authsecret |
3 |
Test3 |
1111111 |
#U*Q(%_ |
在redis中,變成以下幾個key
Key前綴 |
user |
||
User:Userid:* |
User:userid:*Username |
User:userid:*Password |
User:userid:*:Authsecret |
User:userid:3 |
User:userid:3:Test3 |
User:userid:3:1111111 |
User:userid:3:#U*Q(%_ |
微博相關的表設計
表名 |
post |
|
|
|
Postid |
Userid |
Username |
Time |
Content |
4 |
2 |
Lisi |
1370987654f |
測試內容 |
微博在redis中,與表設計對應的key設計
Key前綴 |
post |
|
|
|
Post:Postid:* |
Post:postid:*Userid |
Post:postid:*:Username |
Post:postid:*:Time |
Post:postid:*:Content |
4 |
2 |
Lisi |
1370987654f |
測試內容 |
關注表: following
Following:$userid -->
粉絲表
Follower:$userid --->
推送表:revicepost
3 |
4 |
7 |
|
|
|
=================拉模型,改進=====================
拉取表
3 |
4 |
7 |
|
|
|
問: 上次我拉取了 A->5,67,三條微博, 下次刷新home.php, 從>7的微博開始拉取
解決: 拉取時,設定一個lastpull時間點, 下次拉取時,取>lastpull的微博
問: 有很多關注人,如何取?
解決: 循環自己的關注列表,逐個取他們的新微博
問: 取出來之後放在哪兒?
答: pull:$userid的鏈接裏
問: 如果個人中心,只有前1000條
答: ltrim,只取前1000條
問: 如果我關注 A,B兩人, 從2人中,各取3條最新信息
,這3+3條信息, 從時間上,是交錯的, 如何按時間排序?
答: 我們發佈時, 是發佈的hash結構, 不能按時間來排序.
解決: 同步時,取微博後,記錄本次取的微博的最大id,
下次同步時,只取比最大id更大的微博
Time taken for tests: 32.690 seconds
Complete requests: 20000
Failed requests: 0
Write errors: 0
Non-2xx responses: 20000
Total transferred: 13520000 bytes
Total POSTed: 5340000
HTML transferred: 9300000 bytes
Requests per second: 611.80 [#/sec] (mean)
Time per request: 81.726 [ms] (mean)
Time per request: 1.635 [ms] (mean, across all concurrent requests)
Transfer rate: 403.88 [Kbytes/sec] received
159.52 kb/s sent
563.41 kb/s total
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.9 0 19
Processing: 14 82 8.4 81 153
Waiting: 4 82 8.4 80 153
Total: 20 82 8.2 81 153
Percentage of the requests served within a certain time (ms)
50% 81
66% 84
75% 86
80% 88
90% 93
95% 96
98% 100
99% 103
100% 153 (longest request)
測試結果:
50個併發, 20000次請求, 虛擬下,未做特殊優化
每次請求redis寫操作6次.
30+秒左右完成.
平均每秒發佈700條微博, 4000次redis寫入.
後臺定時任務,迴歸冷數據入mysql
Redis配置文件
daemonize yes # redis是否以後臺進程運行
Requirepass 密碼 # 配置redis連接的密碼
注:配置密碼後,客戶端連上服務器,需要先執行授權命令
# auth 密碼