1 Java緩存
1.1 jvm內置緩存
Java中實現緩存的方式有很多,比如用static hashMap基於內存緩存的jvm內置緩存,簡單不實用,保對象的有效性和周期無法控制,容易造成內存急劇上升。常用的有Oscache(主要針對jsp頁面),Ehcache(主要針對數據庫訪問層),Jcache,Jbosscache等等很多
缺點:容易內存溢出、沒有持久化(服務重啟后丟失)、線程安全、多個服務器(多個jvm)之間的數據不能共享。
1.2 java操作eache
利用spring Boot搭建應用(可參考這里的緩存配置)。
訪問http://localhost:8080/getUser?name=springboot2.2后查詢后返回
-
{
-
id: 35,
-
name: "springboot2.2",
-
age: 99
-
}
修改數據庫中age字段后再次訪問http://localhost:8080/getUser?name=springboot2.2后結果不變?代表使用了緩存,成功配置了緩存。
訪問http://localhost:8080/removeCache清除緩存后,再次訪問http://localhost:8080/getUser?name=springboot2.2后結果就為修改后的值。
原理?
1、當客戶端請求數據時,如果服務器端配置了緩存,第一步去緩存里面查找,如果有跳4,沒有則往2
2、發送jdbc請求操作數據庫查詢數據
3、將查詢到的數據返回給緩存,並保存在緩存中
4、將從(緩存|數據庫)查詢到的數據返回給客戶端。
好處?效率高(因為不用建立jdbc連接等)、降低了數據庫的壓力(不用每次請求都要去數據庫查,數據庫也會累啊)。
缺點?從上述也看到了,有可能產生數據不一致的情況,清除緩存可解決。
和oscache區別? ehcache 主要是對數據庫訪問的緩存,相同的查詢語句只需查詢一次數據庫,從而提高了查詢的速度,使用spring的AOP可以很容易實現這一功能。 oscache 主要是對頁面的緩存,可以整頁或者指定網頁某一部分緩存,同時指定他的過期時間,這樣在此時間段里面訪問的數據都是一樣的。
2 Redis
關系型數據庫:持久、主外鍵、編寫SQL語句、存放在硬盤。
非關系型數據庫:一般用於緩存、值存放在內存(所以效率是真的高)、key-vakye形式、容易數據丟失(不過很好解決)、有點小類似jvm內置緩存(不過這個更牛嗨,因為可以多個服務器間共享數據)。
redis?可以持久化mongdb?存儲json格式
2.1 Redis概述
完全開源免費、最受BSD協議、高性能的key-value費關系型數據庫。支持持久化、支持key-value(String)\list\set\zert\hash等數據結構的存儲、支持備份
好處?減輕數據庫訪問的壓力。效率高?(訪問內存肯定比訪問硬盤快,這是常識)
應用場景?(token生成、session共享、分布式鎖、驗證碼、自增id(訂單id))
2.2 安裝redis
2.2.1 windows安裝redis
1、下載redis
2、解壓redis-latest-windws.zip文件,將start.bat文件拷貝紙redis目錄
3、編輯redis.windows.conf文件,取消requirepass的注釋(前面不能有空格),空格然后添加密碼比如(123456)保存。以requirepass 123456為例
4、雙擊start.bat運行
5、通過這個客戶端工具測試一下
2.2.2 linux安裝redis
1、下載redis
2、mkdir -p /usr/local/redis/bin,mkdir -p /usr/local/redis/etc
3、copy redis-3.0.0.tar.gz到用戶目錄比如/root
4、解壓tar -zxvf redis-3.0.0.tar.gz
5、cd redis-3.0.0/后,make一下
6、進入src目錄make install后就安裝成功了。
7、cd cd /root/redis-3.0.0/(redis安裝目錄)
8、cp redis.conf /usr/local/redis/etc
9、cd src
10、cp mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server redis-sentinel /usr/local/redis/bin
11、修改 redis.conf文件
daemonize yes --- 修改為yes 后台啟動
requirepass 123456 ----注釋取消掉設置賬號密碼
ps aux | grep '6379' --- 查詢端口
kill -15 9886 --- 殺死重置
kill -9 9886 --- 強制殺死
service iptables stop 停止防火牆
12、cd /usr/local/redis/bin
./redis-server /usr/local/redis/etc/redis.conf啟動服務
13、./redis-cli -h 127.0.0.1 -p 6379 -a "123456" --- redis 使用賬號密碼連接或者windows下的客戶端工具進行連接測試
PING 結果表示成功
14、停止redis
redis-cli shutdown 或者 kill redis進程的pid
15、放開一下端口,好像外部即使注釋掉bind 127.0.0.1也不能訪問/sbin/iptables -I INPUT -p tcp --dport 6379 -j ACCEPT
2.3 redis基本數據類型
2.3.1 字符串
-
127 .0.0.1:6379> set name raolei
-
OK
-
127 .0.0.1:6379> set itboy www.itboy.com
-
OK
-
127 .0.0.1:6379> get name
-
" raolei"
-
127 .0.0.1:6379> get itboy
-
" www.itboy.com"
常用命令:
編號 |
命令 |
描述說明 |
1 |
此命令設置指定鍵的值。 |
|
2 |
獲取指定鍵的值。 |
|
3 |
獲取存儲在鍵上的字符串的子字符串。 |
|
4 |
設置鍵的字符串值並返回其舊值。 |
|
5 |
返回在鍵處存儲的字符串值中偏移處的位值。 |
|
6 |
獲取所有給定鍵的值 |
|
7 |
存儲在鍵上的字符串值中設置或清除偏移處的位 |
|
8 |
使用鍵和到期時間來設置值 |
|
9 |
設置鍵的值,僅當鍵不存在時 |
|
10 |
在指定偏移處開始的鍵處覆蓋字符串的一部分 |
|
11 |
獲取存儲在鍵中的值的長度 |
|
12 |
為多個鍵分別設置它們的值 |
|
13 |
為多個鍵分別設置它們的值,僅當鍵不存在時 |
|
14 |
設置鍵的值和到期時間(以毫秒為單位) |
|
15 |
將鍵的整數值增加 |
|
16 |
將鍵的整數值按給定的數值增加 |
|
17 |
將鍵的浮點值按給定的數值增加 |
|
18 |
將鍵的整數值減 |
|
19 |
按給定數值減少鍵的整數值 |
|
20 |
將指定值附加到鍵 |
2.3.2 list
-
127 .0.0.1:6379> lpush listkey redis
-
( integer) 1
-
127 .0.0.1:6379> lpush listkey mysql
-
( integer) 2
-
127 .0.0.1:6379> lpush listkey mongdb
-
( integer) 3
-
127 .0.0.1:6379> lpush listkey hbase
-
( integer) 4
-
127 .0.0.1:6379> lrange listkey 0 10
-
1) " hbase"
-
2) " mongdb"
-
3) " mysql"
-
4) " redis"
list是簡單的字符串列表,按照插入順序排序,可在頭和尾插入,最多包含2^32-1個元素(40多億)。
常用命令:
序號 |
命令及描述 |
1 |
BLPOP key1 [key2 ] timeout |
2 |
BRPOP key1 [key2 ] timeout |
3 |
BRPOPLPUSH source destination timeout |
4 |
LINDEX key index |
5 |
LINSERT key BEFORE|AFTER pivot value |
6 |
LLEN key |
7 |
LPOP key |
8 |
LPUSH key value1 [value2] |
9 |
LPUSHX key value |
10 |
LRANGE key start stop |
11 |
LREM key count value |
12 |
LSET key index value |
13 |
LTRIM key start stop |
14 |
RPOP key |
15 |
RPOPLPUSH source destination |
16 |
RPUSH key value1 [value2] |
17 |
RPUSHX key value |
2.3.3 set
的Set是string類型的無序集合。集合成員是唯一的,這就意味着集合中不能出現重復的數據。
哈希表實現。添加,刪除,查找的復雜度都是O(1)。每個集合可存儲40多億個成員
-
127 .0.0.1:6379> sadd setkey redis
-
( integer) 1
-
127 .0.0.1:6379> sadd setkey redis
-
( integer) 0
-
127 .0.0.1:6379> sadd setkey redis
-
( integer) 0
-
127 .0.0.1:6379> sadd setkey redis
-
( integer) 0
-
127 .0.0.1:6379> sadd setkey redis
-
( integer) 0
-
127 .0.0.1:6379> sadd setkey mysql
-
( integer) 1
-
127 .0.0.1:6379> sadd setkey mongdb
-
( integer) 1
-
127 .0.0.1:6379> SMEMBERS setkey
-
1) " mysql"
-
2) " mongdb"
-
3) " redis"
常用命令:
序號 |
命令及描述 |
1 |
SADD key member1 [member2] |
2 |
SCARD key |
3 |
SDIFF key1 [key2] |
4 |
SDIFFSTORE destination key1 [key2] |
5 |
SINTER key1 [key2] |
6 |
SINTERSTORE destination key1 [key2] |
7 |
SISMEMBER key member |
8 |
SMEMBERS key |
9 |
SMOVE source destination member |
10 |
SPOP key |
11 |
SRANDMEMBER key [count] |
12 |
SREM key member1 [member2] |
13 |
SUNION key1 [key2] |
14 |
SUNIONSTORE destination key1 [key2] |
15 |
2.3.4 sorted set
與set一樣也是string類型元素的集合,且不允許重復的成員。不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。有序集合的成員是唯一的,但分數(score)卻可以重復。
集合是通過哈希表實現的,所以添加,刪除,查找的復雜度都是O(1)。 集合中最大的成員數為 232 - 1 (4294967295, 每個集合可存儲40多億個成員)。
-
127 .0.0.1:6379> zadd zsetkey 1 redis
-
( integer) 1
-
127 .0.0.1:6379> zadd zsetkey 2 mysql
-
( integer) 1
-
127 .0.0.1:6379> zadd zsetkey 2 mongdb
-
( integer) 1
-
127 .0.0.1:6379> smembers setkey
-
1) " mysql"
-
2) " mongdb"
-
3) " redis"
常用命令:
2.3.5 hash
是一個string類型的field和value的映射表(和map差不多,只是兼職都是字符串),hash特別適合用於存儲對象。Redis 中每個 hash 可以存儲 232 - 1 鍵值對(40多億)。
-
127 .0.0.1:6379> hmset hmset name "redis tutorial"
-
OK
-
127 .0.0.1:6379> hmset hmset age 24
-
OK
-
127 .0.0.1:6379> hgetall hmset
-
1) " name"
-
2) " redis tutorial"
-
3) " age"
-
4) "24"
序號 |
命令及描述 |
1 |
HDEL key field2 [field2] |
2 |
HEXISTS key field |
3 |
HGET key field |
4 |
HGETALL key |
5 |
HINCRBY key field increment |
6 |
HINCRBYFLOAT key field increment |
7 |
HKEYS key |
8 |
HLEN key |
9 |
HMGET key field1 [field2] |
10 |
HMSET key field1 value1 [field2 value2 ] |
11 |
HSET key field value |
12 |
HSETNX key field value |
13 |
HVALS key |
14 |
HSCAN key cursor [MATCH pattern] [COUNT count] |
redis怎么存放對象?通過將對象序列化為json字符串,存放為字符串形式,之后讀取在反序列化為對象,這樣很快很快
2.4 Spring Boot集成redis
添加依賴:
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-data-redis</artifactId>
-
</dependency>
-
<dependency>
-
<groupId>org.springframework.boot</groupId>
-
<artifactId>spring-boot-starter-web</artifactId>
-
</dependency>
-
<!--spring2.0集成redis所需common-pool2-->
-
<dependency>
-
<groupId>org.apache.commons</groupId>
-
<artifactId>commons-pool2</artifactId>
-
<version>2.4.2</version>
-
</dependency>
配置文件:
-
########################################################
-
## #Redis (RedisConfiguration)
-
########################################################
-
spring:
-
redis:
-
database: 0
-
host: 192.168.245.134
-
port: 6379
-
password: 123456
-
jedis:
-
pool:
-
max-idle: 8
-
min-idle: 0
-
max-active: 8
-
max-wait: -1ms
-
timeout: 5000ms
service:
-
@Service
-
public class RedisService {
-
@Autowired
-
private StringRedisTemplate stringRedisTemplate;
-
-
//字符串
-
public void setStringKey(String key,String value,Long time){
-
setObject(key, value,time);
-
}
-
public void setStringKey(String key,String value){
-
setObject(key, value,null);
-
}
-
-
//set
-
public void setSetKey(String key,Set value){
-
setObject(key, value,null);
-
}
-
//list
-
public void setListKey(String key,List value){
-
setObject(key, value,null);
-
}
-
-
public String getStringKey(String key){
-
return (String) getObject(key,new String());
-
}
-
public Set getSetKey(String key){
-
return (Set) getObject(key,new HashSet<String>());
-
}
-
public List getListKey(String key){
-
return (List) getObject(key,new ArrayList<String>());
-
}
-
-
-
-
-
public void setObject(String key,Object value,Long time){
-
if(StringUtils.isEmpty(key)||value==null){
-
return;
-
}
-
//字符串類型
-
if(value instanceof String){
-
String value1= (String) value;
-
if(time!=null){
-
stringRedisTemplate.opsForValue(). set(key,value1,time,TimeUnit.SECONDS);
-
} else{
-
stringRedisTemplate.opsForValue(). set(key,value1);
-
}
-
return;
-
}
-
//list類型
-
else if(value instanceof List){
-
List<String> list= (List<String>) value;
-
for (String s:list) {
-
stringRedisTemplate.opsForList().leftPush(key,s);
-
}
-
return;
-
}
-
//set
-
else if(value instanceof Set){
-
Set<String> strings= (Set<String>) value;
-
for (String s : strings) {
-
stringRedisTemplate.opsForSet(). add(key,s);
-
}
-
return;
-
}
-
/**
-
* .....
-
*/
-
}
-
-
public Object getObject(String key,Object object){
-
if(StringUtils.isEmpty(key)||object==null){
-
return null;
-
}
-
else if (object instanceof String){
-
return stringRedisTemplate.opsForValue().get(key);
-
}
-
else if(object instanceof List){
-
return stringRedisTemplate.opsForList().range(key,0,stringRedisTemplate.opsForList().size(key));
-
}
-
else if(object instanceof Set){
-
return stringRedisTemplate.opsForSet().members(key);
-
}
-
return null;
-
}
-
}
controller:
-
@RestController
-
public class IndexController {
-
@Autowired
-
private RedisService redisService;
-
-
@RequestMapping( "/setString")
-
public String setString(@PathParam("key") String key,
-
@PathParam("value") String value){
-
redisService.setStringKey(key, value);
-
return redisService.getStringKey(key);
-
}
-
@RequestMapping( "/setSet")
-
public Set<String> setSet(@PathParam("key") String key,
-
@PathParam("value") String value){
-
HashSet<String> strings = new HashSet<>();
-
strings. add(value);
-
strings. add(value+"1");
-
strings. add(value+"2");
-
strings. add(value+"3");
-
redisService.setSetKey(key,strings);
-
return redisService.getSetKey(key);
-
}
-
@RequestMapping( "/setList")
-
public List<String> setList(@PathParam("key") String key,
-
@PathParam("value") String value){
-
ArrayList<String> strings = new ArrayList<>();
-
strings. add(value);
-
strings. add(value+"1");
-
strings. add(value+"2");
-
strings. add(value+"3");
-
redisService.setListKey(key,strings);
-
return redisService.getListKey(key);
-
}
-
}
訪問http://localhost:8080/setSet?key=itboySet&value=123456,http://localhost:8080/setString?key=itboySet&value=123456,
http://localhost:8080/setList?key=itboySet&value=123456進行測試一下是否成功!!!!!!!!
2.5 主從復制和哨兵機制理解
為什么?數據備份、讀寫分離、集群、高可用(宕機容錯機制)。
一般情況下,Redis高可用都是一主多從,而不像其他比如Nginx多主多從。
所謂主從復制,主要是為了減輕單台服務器的壓力(比如圖中的master),通過多台服務器的冗余來保證高可用(單台宕機容錯),實現讀寫分離、數據備份、集群等。
如圖,其中master可讀可寫,但是當有客戶端連接達到集群時,如果是讀操作就從slave從節點中隨機選擇一台服務器進行響應,如果是寫操作,那么操作主服務器。這就是讀寫分離了不是嗎。。。
問題?主服務器寫之后,怎么同步到從服務器?(主從復制搞定,往下看)
問題?主服務器宕機了,怎么寫?通過哨兵機制,哨兵其實就是一個監聽器,一直監聽這主服務器,如果主服務器掛了,他就會使用投票(隨機)從主服務器中選擇一台服務器作為主服務器,此乃高可用。
問題?那如果整個集群掛了呢?有一個東西叫做keepalived監聽器(其實就是一個shell寫的重啟服務的命令腳本),如果監聽到某一台服務器掛了,他就會自動重啟的(一般30秒內,聽說可能不准確)。如果一直啟動失敗?那就沒辦法了,他只能發送一封郵件給運維人員了。
2.5.1 主從復制實現
原理:通過快照文件(類似mysql的二進制可執行文件),當master有更新時,從服務器slave會實時請求得到該快照文件,進行執行,如果網絡問題?別擔心過,會重試的。
過程:
1:當一個從數據庫啟動時,會向主數據庫發送sync命令,
2:主數據庫接收到sync命令后會開始在后台保存快照(執行rdb操作),並將保存期間接收到的命令緩存起來
3:當快照完成后,redis會將快照文件和所有緩存的命令發送給從數據庫。
4:從數據庫收到后,會載入快照文件並執行收到的緩存的命令。
對於redis服務器來說,和mysql有很大不同,只要設置好主從服務器之后,主服務器master可讀可寫,從服務器slave僅可讀,不像mysql那樣需要分配用戶和mycat插件來控制讀寫分離。
配置過程:
1、准備服務器,如上圖中三台服務器(192.168.245.134,192.168.245.135,192.168.245.136),選擇一台為主服務器master(這台服務器什么也不用做)
2、所以redis主從復制只需要配置從服務器slave就OK,修改redis.conf配置文件,放開以下兩行的注釋,添加如下內容。兩台從服務器都要修改。
-
slaveof 192.168.245.134 6379
-
#主服務器的ip和端口號
-
-
# If the master is password protected (using the "requirepass" configuration
-
# directive below) it is possible to tell the slave to authenticate before
-
# starting the replication synchronization process, otherwise the master will
-
# refuse the slave request.
-
#
-
masterauth 123456
-
#主服務器的認證密碼
3、測試
-
-
192.168.245.134:6379> info
-
-
-
role:master
-
connected_slaves: 2
-
slave0:ip= 192.168.245.135,port=6379,state=online,offset=127,lag=1
-
slave1:ip= 192.168.245.136,port=6379,state=online,offset=127,lag=1
-
-
-
-
-
role:slave
-
master_host: 192.168.245.134
-
master_port: 6379
-
master_link_status:up
-
master_last_io_seconds_ago: 10
-
master_sync_in_progress: 0
-
-
-
-
192.168.245.134:6379> set master "192.168.245.134"
-
OK
-
192.168.245.134:6379> get master
-
"192.168.245.134"
-
192.168.245.134:6379>
-
-
-
192.168.245.135:6379> get master
-
"192.168.245.134"
-
192.168.245.135:6379> set slave "192.168.245.135"
-
( error) READONLY You can't write against a read only slave.
-
192.168.245.135:6379>
-
-
[root@localhost bin]
-
192.168.245.136:6379> ping
-
PONG
-
192.168.245.136:6379> get master
-
"192.168.245.134"
-
192.168.245.136:6379> set slave "192.168.245.136"
-
( error) READONLY You can't write against a read only slave.
-
192.168.245.136:6379>
-
-
-
2.5.2 哨兵機制實現
原理:哨兵用於管理多個redis服務器,執行以下三個任務:
1、監控(monitoring):哨兵(sentinel)會不斷檢查你的master和slave是否運作正常
2、提醒(notification):當被監控的某個redis出現問題時,哨兵(sentinel)可以通過API向管理員或者其他應用程序發送通知
3、自動故障遷移(automatic failover):當一個master1不能正常工作時,哨兵會開始一次自動故障遷移操作,他將會失效master1的其中一個slave升級為新的master2,並讓失效master1的其他slave的master1改為新的master2。當客戶端試圖連接失效的master1時,集群也會向客戶端返回新的master2地址,使得集群可以使用新的master2代替失效的master1
哨兵是一個分布式系統,可以在一個架構中運行多個哨兵進程,這些進程使用流言協議(gossipprotocols)來接收關於master是否下線的消息,並使用投票協議(agreement protocols)來決定是否執行自動故障遷移以及選擇哪個slave作為新的master。
每個哨兵會向其他哨兵(sentinel)、master、slave定時發送消息,來確認對方是否還活着,如果對方在指定的時間(可配置)內未響應,則暫時認為對方已掛(主觀認為宕機,Subjective Down,sdown)
若哨兵群中的多數sentinel都報告某一個master沒響應,系統認為該master徹底死亡(客觀真正的宕機,Objective Down,oDwon),通過一定vote算法,從生下的slave節點中選擇提升一台為master,然后自動修改相關配置
雖然哨兵(sentinel) 釋出為一個單獨的可執行文件 redis-sentinel ,但實際上它只是一個運行在特殊模式下的 Redis 服務器,你可以在啟動一個普通 Redis 服務器時通過給定 --sentinel 選項來啟動哨兵(sentinel).
實現:
這里以上面三台服務器為基礎,選擇192.168.245.136這台服務器為哨兵(可以選擇多台,此處僅一台為例)。
注意:如果主從復制時,主服務器沒有配置masterauth 123456請加上,這個坑了我很久很久,導致哨兵時一直連不上,最后查看日志才搞定,記得先將這個加到主服務器的redis.conf中,然后重啟一下
1、修改redis安裝目錄下的sentinel.conf
-
sentinel monitor mymaster 192.168.245.134 6379 1
-
# 主節點名稱 主機ip 端口號 選舉次數(就是說當有幾台sentinel認為master掛了才是真的掛了,因為這里只有一個哨兵,所以為1)
-
sentinel down-after-milliseconds mymaster 30
-
#就是說多少ms后沒有給老子響應,老子就覺得你掛了。,默認30s,這里設置為30ms,本地測試追求實時
-
sentinel config-epoch mymaster 1
-
#這個數字表示在發生主從復制的時候,比如master1向master2切換時,可以同時有多少個slave能對master2執行同步(也就是復制其實),越多越好?>如果太多了那么大家都去復制了,誰來響應客戶端的請求?太少?太少的話,每個都要來一遍,怕是要到天黑哦,根據實際情況吧,這里只有三台所以
-
為設為 1.
-
-
sentinel auth- pass mymaster 123456
-
#主服務器密碼
2、啟動哨兵:nohup ./redis-server ../etc/sentinel.conf --sentinel 2>1 1>nohup.log &
3、如何停止ps -aux | grep 端口號,kill -9 pid即可
4、測試?
-
#我們首先查看master(134)的info master
-
role:master
-
connected_slaves:2
-
slave0:ip=192.168.245.135,port=6379,state=online,offset=5349,lag=1
-
slave1:ip=192.168.245.136,port=6379,state=online,offset=5349,lag=0
-
-
-
#135的 slave
-
role:slave
-
master_host:192.168.245.134
-
master_port:6379
-
master_link_status:up
-
-
-
#136 的 slave
-
role:slave
-
master_host:192.168.245.134
-
master_port:6379
-
master_link_status:up
-
-
-
#我現在講master停掉? master
-
#134
-
-
not connected>
-
-
-
#135 的info replication
-
-
# Replication
-
role:slave
-
master_host:192.168.245.136
-
master_port:6379
-
master_link_status:up
-
#看到主服務器變為136,
-
-
#136?
-
-
# Replication
-
role:master
-
connected_slaves:2
-
slave0:ip=192.168.245.135,port=6379,state=online,offset=739,lag=0
-
slave1:ip=192.168.245.134,port=6379,state=online,offset=739,lag=0
-
-
#基本上成功了,最后試試讀寫分離以及在測試一些其他的,這里不再展示
-
-
-
2.6 數據持久化
數據持久化:就是將內存中的數據保存到硬盤,redis支持AOF和RDB兩種存儲方式
2.6.1 RDB存儲
RDB是指在一個時間點,如果達到所配置的數據修改量,就寫入一個臨時文件,持久化結束后,用這個臨時文件替換上次持久化的文件,達到數據恢復。(二進制文件方式)
有兩種保存方式:1、(阻塞)主進程直接拍快照(snapshot),然后阻塞客戶端請求寫入IO磁盤。2、(非阻塞)當要寫入磁盤時,新建(fork)一個子進程,子進程將當前數據庫快照寫入磁盤,而主進程繼續處理客戶端請求。
每次快照持久化都是將內存數據完整寫入到磁盤一次,並不 是增量的只同步臟數據。如果數據量大的話,而且寫操作比較多,必然會引起大量的磁盤io操作,可能會嚴重影響性能。
優點:使用單獨子進程進行持久化,主進程不會進行任何IO操作,保證了redis的高性能
缺點:RDB需要間隔一段時間進行持久化,而且必須達到相應修改數量,所以如果持久化之間發生故障,會造成數據丟失,常用於數據要求不嚴謹的時候。
配置:
-
-
dbfilename dump.rdb
-
-
dir ./
-
-
-
-
-
save 900 1
-
-
-
-
save 300 10
-
save 60 10000
-
-
-
stop-writes-on-bgsave-error yes
-
-
rdbcompression yes
這里我將save 300 10改為save 30 10進行測試一下(首先先停掉哨兵機制,不然宕機后他就重新選一台主的了):
改好后重啟一下服務:
-
進行 5次更改操作
-
-
"1"
-
-
OK
-
-
OK
-
-
OK
-
-
OK
-
-
OK
-
-
"5"
-
-
#立馬kill掉redis進程,一定要kill如果主動關閉服務,他是會進行snapshot的
-
[root@localhost bin] # ps -aux | grep 6379
-
root 1572 0.1 0.9 140840 9640 ? Ssl 02:02 0:00 ./redis-server *:6379
-
root 1577 0.0 0.5 20160 5184 pts/0 S+ 02:02 0:00 ./redis-cli -h 192.168.245.136 -p 6379 -a 123456
-
root 1580 0.0 0.0 112676 984 pts/1 R+ 02:03 0:00 grep --color=auto 6379
-
[root@localhost bin] # kill -9 1572
-
-
#重啟服務
-
[root@localhost bin] # ./redis-server ../etc/redis.conf
-
[root@localhost bin] # ./redis-cli -h 192.168.245.136 -p 6379 -a "123456"
-
-
"1"
-
-
-
#可以看到我最后的是“5”,而這里是“1”,數據丟失了##########################
-
#更改10次呢?,是成功進行持久化了的,這里不展示了,篇幅過大。
-
#而且如果你將dump.rdb文件刪除后,達到snapshot條件時,會自動創建一個新的文件,持久化其實就是將該文件備份,下次將那些持久化后的文件再放過來不就達到數據恢復了嗎????????????
2.6.2 AOF存儲
以日志文件方式存儲,其實就是將你“操作+數據”指令格式化后追加到操作日志文件的尾部,必須append(已經寫入到文件或者即將寫入),才會進行數據的實際變更。“日志文件”保存了歷史所有的操作過程;當 server 需要數據恢復時,可以直接 replay 此日志文件,即可還原所有的操作過程。內容是字符串,容易閱讀和解析。
缺點:AOF 文件比 RDB 文件大,且恢復速度慢。
只會記錄“變更操作”(例如:set/del 等),如果 server 中持續的大量變更操作,將會導致 AOF 文件非常的龐大,意味着 server 失效后,數據恢復的過程將會很長;事實上,一條數據經過多次變更,將會產生多條 AOF 記錄,其實只要保存當前的狀態,歷史的操作記錄是可以拋棄的;因為 AOF 持久化模式還伴生了“AOF rewrite”。
因為最多丟失最后一次寫入文件的數據,所以很好修復,直接手工更改文件或者重新來一次即可。
修改redis.conf文件:
-
-
-
appendonly yes
-
-
-
appendfilename appendonly.aof
-
-
-
appendfsync everysec
-
-
no-appendfsync- on-rewrite no
-
-
-
auto-aof-rewrite-min-size 64mb
-
-
-
-
-
auto-aof-rewrite-percentage 100
-
重啟服務后,根據你啟動redis所在目錄下出現appendonly.aof文件。
執行以下操作:
-
192 .168.245.136:6379> set name 124
-
OK
-
192 .168.245.136:6379> set name 145
-
OK
-
192 .168.245.136:6379> set age 56
-
OK
-
192 .168.245.136:6379> del age
-
( integer) 1
-
192 .168.245.136:6379> get name
-
"145"
-
查看aof文件內容:
-
[root@localhost bin] # cat appendonly.aof
-
*2
-
$6
-
SELECT
-
$1
-
0
-
*3
-
$3
-
set
-
$4
-
name
-
$3
-
124
-
*3
-
$3
-
set
-
$4
-
。。。
查詢的是不會出現在里面,而且這個文件是動態的。。看起來挺好的,就是頻繁的更改造成文件過大,到時候恢復起來有點太慢了,有人說那么RDB中將時間改快點要求改小點?大哥,人家是全量復制,那豈不是時間都花去復制了,還處理什么請求?這個雖然慢,可是是追加方式啊,將就了。
2.6.3 redis宕機后,值會失效嗎?
不會,redis默認開啟RDB存儲,如果是直接關閉服務,那么會自動備份,如果是kill或者斷電如果沒達到RDB配置的要求則不會持久化,而且這個雖然是非阻塞的,但是畢竟全量復制啊,保證了redis的性能,但是CPU可受不了啊。因此實際情況中,最好采用AOP方式,實時而且快,但是就是容易造成文件過大,恢復困難。
2.7 redis事務
Redis 事務可以一次執行多個命令, 並且帶有以下兩個重要的保證:
事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。
事務是一個原子操作:事務中的命令要么全部被執行,要么全部都不執行。
multi 開啟事務 exec提交事務。
序號 |
命令及描述 |
1 |
DISCARD |
2 |
EXEC |
3 |
MULTI |
4 |
UNWATCH |
5 |
WATCH key [key ...] |
2.8 發布訂閱
Redis 發布訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。Redis 客戶端可以訂閱任意數量的頻道
下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的關系:
當有新消息通過 PUBLISH 命令發送給頻道 channel1 時, 這個消息就會被發送給訂閱它的三個客戶端:
例子:
1、創建訂閱頻道名為 redisChatSimple
-
192 .168.245.136:6379> subscribe redsiChatSample
-
Reading messages... (press Ctrl-C to quit)
-
1) " subscribe"
-
2) " redsiChatSample"
2、重新開個客戶端,同一頻道發布消息
-
192 .168.245.136:6379> publish redsiChatSample "gogog"
-
( integer) 1
-
192 .168.245.136:6379> publish redsiChatSample "gogog22"
-
( integer) 1
-
192 .168.245.136:6379>
3、客戶端顯示如下信息:
-
-
Reading messages... (press Ctrl-C to quit)
-
1) "subscribe"
-
2) "redsiChatSample"
-
3) (integer) 1
-
1) "message"
-
2) "redsiChatSample"
-
3) "gogog"
-
1) "message"
-
2) "redsiChatSample"
-
3) "gogog22"
厲害了!!!
序號 |
命令及描述 |
1 |
PSUBSCRIBE pattern [pattern ...] |
2 |
PUBSUB subcommand [argument [argument ...]] |
3 |
PUBLISH channel message |
4 |
PUNSUBSCRIBE [pattern [pattern ...]] |
5 |
SUBSCRIBE channel [channel ...] |
6 |
UNSUBSCRIBE [channel [channel ...]] |