Java中要用到緩存的地方不少,首當其衝的就是持久層緩存,針對持久層談一下:
要實現java緩存有不少種方式,最簡單的無非就是static HashMap,這個顯然是基於內存緩存,一個map就能夠搞定引用對象的緩存,最簡單也最不實用,首要的問題就是保存對象的有效性以及週期沒法控制,這樣很容易就致使內存急劇上升,週期沒法控制能夠採用SoftReference,WeakReference,PhantomReference這三種對象來執行(看了Ibatis的緩存機制才發現JDK竟然還提供了PhantomReference這玩意兒,得惡補基礎啊),這三種都是弱引用,區別在於強度不一樣,至於弱引用概念我的理解就是對象的生命週期與JVM掛鉤,JVM內存不夠了就回收,這樣能很好的控制OutOfMemoryError 異常。 html
經常使用的有Oscache,Ehcache,Jcache,Jbosscache等等不少java
ehcache 主要是對數據庫訪問的緩存,相同的查詢語句只需查詢一次數據庫,從而提升了查詢的速度,使用spring的AOP能夠很容易實現這一功能。
oscache 主要是對頁面的緩存,能夠整頁或者指定網頁某一部分緩存,同時指定他的過時時間,這樣在此時間段裏面訪問的數據都是同樣的。
NoSQL 是 Not Only SQL 的縮寫,意即"不只僅是SQL"的意思,泛指非關係型的數據庫。強調Key-Value Stores和文檔數據庫的優勢,而不是單純的反對RDBMS。mysql
NoSQL產品是傳統關係型數據庫的功能閹割版本,經過減小用不到或不多用的功能,來大幅度提升產品性能linux
NoSQL產品 Redis、mongodb Membase、HBase 程序員
Redis支持數據的持久化,能夠將數據存放在硬盤上。web
Memcache不支持數據的之久存儲。redis
Redis數據類型豐富,支持set liset等類型算法
Memcache支持簡單數據類型,須要客戶端本身處理複製對象spring
Redis 是徹底開源免費的,遵照BSD協議,是一個高性能的key-value數據庫。sql
Redis 與其餘 key - value 緩存產品有如下三個特色:
Redis支持數據的持久化,能夠將內存中的數據保存在磁盤中,重啓的時候能夠再次加載進行使用。
Redis不只僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。
Redis支持數據的備份,即master-slave模式的數據備份。
主要可以體現 解決數據庫的訪問壓力。
例如:短信驗證碼時間有效期、session共享解決方案
性能極高 – Redis能讀的速度是110000次/s,寫的速度是81000次/s 。
豐富的數據類型 – Redis支持二進制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 數據類型操做。
原子 – Redis的全部操做都是原子性的,同時Redis還支持對幾個操做全並後的原子性執行。
豐富的特性 – Redis還支持 publish/subscribe, 通知, key 過時等等特性。
Redis有着更爲複雜的數據結構而且提供對他們的原子性操做,這是一個不一樣於其餘數據庫的進化路徑。Redis的數據類型都是基於基本數據結構的同時對程序員透明,無需進行額外的抽象。
Redis運行在內存中可是能夠持久化到磁盤,因此在對不一樣數據集進行高速讀寫時須要權衡內存,由於數據量不能大於硬件內存。在內存數據庫方面的另外一個優勢是,相比在磁盤上相同的複雜的數據結構,在內存中操做起來很是簡單,這樣Redis能夠作不少內部複雜性很強的事情。同時,在磁盤格式方面他們是緊湊的以追加的方式產生的,由於他們並不須要進行隨機訪問。
新建start.bat 批處理文件、內容: redis-server.exe redis.windows.conf
雙擊start.bat啓動
修改密碼 # requirepass foobared 修改成requirepass 123456
注意:修改密碼的時候前面不要加空格
Redis的官方下載網址是:http://redis.io/download (這裏下載的是Linux版的Redis源碼包)
Redis服務器端的默認端口是6379。
這裏以虛擬機中的Linux系統如何安裝Redis進行講解。
在windows系統中下載好Redis的源碼包。
1. 經過WinSCP工具,將Redis的源碼包由windows上傳到Linux系統的這個目錄/opt/redis (即根目錄下的lamp文件夾)。
2. 解壓縮。
tar -zxf redis-2.6.17.tar.gz
3. 切換到解壓後的目錄。
cd redis-2.6.17 ( 通常來講,解壓目錄裏的INSTALL文件或README文件裏寫有安裝說明,可參考之)
4. 編譯。
make
(注意,編譯須要C語言編譯器gcc的支持,若是沒有,須要先安裝gcc。可使用rpm -q gcc查看gcc是否安裝)
(利用yum在線安裝gcc的命令 yum -y install gcc )
(若是編譯出錯,請使用make clean清除臨時文件。以後,找到出錯的緣由,解決問題後再來從新安裝。 )
5. 進入到src目錄。
cd src
6. 執行安裝。
make install
到此就安裝完成。可是,因爲安裝redis的時候,咱們沒有選擇安裝路徑,故是默認位置安裝。在此,咱們能夠將可執行文件和配置文件移動到習慣的目錄。
cd /usr/local mkdir -p /usr/local/redis/bin mkdir -p /usr/local/redis/etc cd /lamp/redis-2.6.17 cp ./redis.conf /usr/local/redis/etc cd src cp mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server redis-sentinel /usr/local/redis/bin
7.開放linux 6379 端口
1.編輯 /etc/sysconfig/iptables 文件:
vi /etc/sysconfig/iptables
加入內容並保存:
-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 6379 -j ACCEPT
2.重啓服務:
/etc/init.d/iptables restart
3.查看端口是否開放:
/sbin/iptables -L -n
比較重要的3個可執行文件:
redis-server:Redis服務器程序 redis-cli:Redis客戶端程序,它是一個命令行操做工具。也可使用telnet根據其純文本協議操做。 redis-benchmark:Redis性能測試工具,測試Redis在你的系統及配置下的讀寫性能。
/usr/local/redis/bin/redis-server
或
cd /usr/local/redis/bin
./redis-server /usr/local/redis/etc/redis.conf 爲redis-server指定配置文
daemonize yes --- 修改成yes 後臺啓動 requirepass 123456 ----註釋取消掉設置帳號密碼 ps aux | grep '6379' --- 查詢端口 kill -15 9886 --- 殺死重置 kill -9 9886 --- 強制殺死 service iptables stop 中止防火牆
./redis-cli -h 127.0.0.1 -p 6379 -a "123456" --- redis 使用帳號密碼鏈接 PING 結果表示成功
redis-cli shutdown 或者 kill redis進程的pid
service iptables stop
使用redisclient-win32.x86.1.5
redis 127.0.0.1:6379> SET mykey "redis" OK redis 127.0.0.1:6379> GET mykey "redis"
在上面的例子中,SET
和GET
是redis中的命令,而mykey
是鍵的名稱。
Redis字符串命令用於管理Redis中的字符串值。如下是使用Redis字符串命令的語法。
redis 127.0.0.1:6379> COMMAND KEY_NAME
示例
redis 127.0.0.1:6379> SET mykey "redis" OK redis 127.0.0.1:6379> GET mykey "redis"
在上面的例子中,SET
和GET
是redis中的命令,而mykey
是鍵的名稱。
Redis字符串命令
下表列出了一些用於在Redis中管理字符串的基本命令。
編號 |
命令 |
描述說明 |
1 |
此命令設置指定鍵的值。 |
|
2 |
獲取指定鍵的值。 |
|
3 |
獲取存儲在鍵上的字符串的子字符串。 |
|
4 |
設置鍵的字符串值並返回其舊值。 |
|
5 |
返回在鍵處存儲的字符串值中偏移處的位值。 |
|
6 |
獲取全部給定鍵的值 |
|
7 |
存儲在鍵上的字符串值中設置或清除偏移處的位 |
|
8 |
使用鍵和到期時間來設置值 |
|
9 |
設置鍵的值,僅當鍵不存在時 |
|
10 |
在指定偏移處開始的鍵處覆蓋字符串的一部分 |
|
11 |
獲取存儲在鍵中的值的長度 |
|
12 |
爲多個鍵分別設置它們的值 |
|
13 |
爲多個鍵分別設置它們的值,僅當鍵不存在時 |
|
14 |
設置鍵的值和到期時間(以毫秒爲單位) |
|
15 |
將鍵的整數值增長 |
|
16 |
將鍵的整數值按給定的數值增長 |
|
17 |
將鍵的浮點值按給定的數值增長 |
|
18 |
將鍵的整數值減 |
|
19 |
按給定數值減小鍵的整數值 |
|
20 |
將指定值附加到鍵 |
Redis列表是簡單的字符串列表,按照插入順序排序。你能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)
一個列表最多能夠包含 232 - 1 個元素 (4294967295, 每一個列表超過40億個元素)。
redis 127.0.0.1:6379> LPUSH runoobkey redis (integer) 1 redis 127.0.0.1:6379> LPUSH runoobkey mongodb (integer) 2 redis 127.0.0.1:6379> LPUSH runoobkey mysql (integer) 3 redis 127.0.0.1:6379> LRANGE runoobkey 0 10 1) "mysql" 2) "mongodb" 3) "redis"
Redis 列表命令
下表列出了列表相關的基本命令:
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 |
Redis的Set是string類型的無序集合。集合成員是惟一的,這就意味着集合中不能出現重複的數據。
Redis 中集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。
集合中最大的成員數爲 232 - 1 (4294967295, 每一個集合可存儲40多億個成員)。
實例
redis 127.0.0.1:6379> SADD runoobkey redis (integer) 1 redis 127.0.0.1:6379> SADD runoobkey mongodb (integer) 1 redis 127.0.0.1:6379> SADD runoobkey mysql (integer) 1 redis 127.0.0.1:6379> SADD runoobkey mysql (integer) 0 redis 127.0.0.1:6379> SMEMBERS runoobkey 1) "mysql" 2) "mongodb" 3) "redis"
在以上實例中咱們經過 SADD 命令向名爲 runoobkey 的集合插入的三個元素。
Redis 集合命令
下表列出了 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 |
Redis 有序集合和集合同樣也是string類型元素的集合,且不容許重複的成員。
不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。
有序集合的成員是惟一的,但分數(score)卻能夠重複。
集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。集合中最大的成員數爲 232 - 1 (4294967295, 每一個集合可存儲40多億個成員)。
實例
redis 127.0.0.1:6379> ZADD runoobkey 1 redis (integer) 1 redis 127.0.0.1:6379> ZADD runoobkey 2 mongodb (integer) 1 redis 127.0.0.1:6379> ZADD runoobkey 3 mysql (integer) 1 redis 127.0.0.1:6379> ZADD runoobkey 3 mysql (integer) 0 redis 127.0.0.1:6379> ZADD runoobkey 4 mysql (integer) 0 redis 127.0.0.1:6379> ZRANGE runoobkey 0 10 WITHSCORES 1) "redis" 2) "1" 3) "mongodb" 4) "2" 5) "mysql" 6) "4"
在以上實例中咱們經過命令 ZADD 向 redis 的有序集合中添加了三個值並關聯上分數。
Redis 有序集合命令
下表列出了 redis 有序集合的基本命令:
Redis hash 是一個string類型的field和value的映射表,hash特別適合用於存儲對象。
Redis 中每一個 hash 能夠存儲 232 - 1 鍵值對(40多億)。
實例
127.0.0.1:6379> HMSET runoobkey name "redis tutorial" 127.0.0.1:6379> HGETALL runoobkey 1) "name" 2) "redis tutorial" 3) "description" 4) "redis basic commands for caching" 5) "likes" 6) "20" 7) "visitors" 8) "23000"
hset key mapHey MapValue
在以上實例中,咱們設置了 redis 的一些描述信息(name, description, likes, visitors) 到哈希表的 runoobkey 中。
Redis hash 命令
下表列出了 redis hash 基本的相關命令:
序號 |
命令及描述 |
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] |
package com.hongmoshui.test; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.junit.Before; import org.junit.Test; import redis.clients.jedis.Jedis; public class TestRedis { private Jedis jedis; @Before public void setup() { // 鏈接redis服務器,127.0.0.1:6379 jedis = new Jedis("127.0.0.1", 6379); // 權限認證 jedis.auth("123456"); } /** * redis存儲字符串 */ @Test public void testString() { // -----添加數據---------- jedis.set("name", "xinxin");// 向key-->name中放入了value-->xinxin System.out.println(jedis.get("name"));// 執行結果:xinxin jedis.append("name", " is my lover"); // 拼接 System.out.println(jedis.get("name")); jedis.del("name"); // 刪除某個鍵 System.out.println(jedis.get("name")); // 設置多個鍵值對 jedis.mset("name", "liuling", "age", "23", "qq", "476777XXX"); jedis.incr("age"); // 進行加1操做 System.out.println(jedis.get("name") + "-" + jedis.get("age") + "-" + jedis.get("qq")); } /** * redis操做Map */ @Test public void testMap() { // -----添加數據---------- Map<String, String> map = new HashMap<String, String>(); map.put("name", "xinxin"); map.put("age", "22"); map.put("qq", "123456"); jedis.hmset("user", map); // 取出user中的name,執行結果:[minxr]-->注意結果是一個泛型的List // 第一個參數是存入redis中map對象的key,後面跟的是放入map中的對象的key,後面的key能夠跟多個,是可變參數 List<String> rsmap = jedis.hmget("user", "name", "age", "qq"); System.out.println(rsmap); // 刪除map中的某個鍵值 jedis.hdel("user", "age"); System.out.println(jedis.hmget("user", "age")); // 由於刪除了,因此返回的是null System.out.println(jedis.hlen("user")); // 返回key爲user的鍵中存放的值的個數2 System.out.println(jedis.exists("user"));// 是否存在key爲user的記錄 // 返回true System.out.println(jedis.hkeys("user"));// 返回map對象中的全部key System.out.println(jedis.hvals("user"));// 返回map對象中的全部value Iterator<String> iter = jedis.hkeys("user").iterator(); while (iter.hasNext()) { String key = iter.next(); System.out.println(key + ":" + jedis.hmget("user", key)); } } /** * jedis操做List */ @Test public void testList() { // 開始前,先移除全部的內容 jedis.del("java framework"); System.out.println(jedis.lrange("java framework", 0, -1)); // 先向key java framework中存放三條數據 jedis.lpush("java framework", "spring"); jedis.lpush("java framework", "struts"); jedis.lpush("java framework", "hibernate"); // 再取出全部數據jedis.lrange是按範圍取出, // 第一個是key,第二個是起始位置,第三個是結束位置,jedis.llen獲取長度 // -1表示取得全部 System.out.println(jedis.lrange("java framework", 0, -1)); jedis.del("java framework"); jedis.rpush("java framework", "spring"); jedis.rpush("java framework", "struts"); jedis.rpush("java framework", "hibernate"); System.out.println(jedis.lrange("java framework", 0, -1)); } /** * jedis操做Set */ @Test public void testSet() { // 添加 jedis.sadd("user", "liuling"); jedis.sadd("user", "xinxin"); jedis.sadd("user", "ling"); jedis.sadd("user", "zhangxinxin"); jedis.sadd("user", "who"); // 移除noname jedis.srem("user", "who"); // 獲取全部加入的value System.out.println(jedis.smembers("user")); // 判斷who 是不是user集合的元素 System.out.println(jedis.sismember("user", "who")); System.out.println(jedis.srandmember("user")); // 返回集合的元素個數 System.out.println(jedis.scard("user")); } @Test public void test() throws InterruptedException { // jedis 排序 // 注意,此處的rpush和lpush是List的操做。是一個雙向鏈表(但從表現來看的) jedis.del("a");// 先清除數據,再加入數據進行測試 jedis.rpush("a", "1"); jedis.lpush("a", "6"); jedis.lpush("a", "3"); jedis.lpush("a", "9"); System.out.println(jedis.lrange("a", 0, -1)); // [9,3, 6,1] System.out.println(jedis.sort("a")); // [1,3,6,9] // 輸入排序後結果 System.out.println(jedis.lrange("a", 0, -1)); } // @Test // public void testRedisPool() // { // RedisUtil.getJedis().set("newname", "中文測試"); // System.out.println(RedisUtil.getJedis().get("newname")); // } }
Maven依賴jar包
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <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> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <!-- <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> --> </dependencies>
######################################################## ###Redis (RedisConfiguration) ######################################################## spring.redis.database=0 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password=123456 spring.redis.pool.max-idle=8 spring.redis.pool.min-idle=0 spring.redis.pool.max-active=8 spring.redis.pool.max-wait=-1 spring.redis.timeout=5000
package com.hongmoshui.service; import java.util.List; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @Service public class RedisService { @Autowired private StringRedisTemplate stringRedisTemplate; public void setObject(String key, Object value) { this.setObject(key, value, null); } public void setObject(String key, Object value, Long time) { if (StringUtils.isEmpty(key) || value == null) { return; } if (value instanceof String) { // 存放string類型 String stringValue = (String) value; if (time == null) { stringRedisTemplate.opsForValue().set(key, stringValue); } else { stringRedisTemplate.opsForValue().set(key, stringValue, time, TimeUnit.SECONDS); } return; } if (value instanceof List) { // 存放list類型 List<String> listValue = (List<String>) value; for (String string : listValue) { stringRedisTemplate.opsForList().leftPush(key, string); } } } public void delKey(String key) { stringRedisTemplate.delete(key); } public String getString(String key) { return stringRedisTemplate.opsForValue().get(key); } }
克隆三臺linux虛擬機
redis主從複製
概述
一、redis的複製功能是支持多個數據庫之間的數據同步。一類是主數據庫(master)一類是從數據庫(slave),主數據庫能夠進行讀寫操做,當發生寫操做的時候自動將數據同步到從數據庫,而從數據庫通常是隻讀的,並接收主數據庫同步過來的數據,一個主數據庫能夠有多個從數據庫,而一個從數據庫只能有一個主數據庫。
二、經過redis的複製功能能夠很好的實現數據庫的讀寫分離,提升服務器的負載能力。主數據庫主要進行寫操做,而從數據庫負責讀操做。
主從複製過程
主從複製過程:見下圖
過程:
1:當一個從數據庫啓動時,會向主數據庫發送sync命令,
2:主數據庫接收到sync命令後會開始在後臺保存快照(執行rdb操做),並將保存期間接收到的命令緩存起來
3:當快照完成後,redis會將快照文件和全部緩存的命令發送給從數據庫。
4:從數據庫收到後,會載入快照文件並執行收到的緩存的命令。
修改從redis中的 redis.conf文件
slaveof 192.168.33.130 6379
masterauth 123456--- 主redis服務器配置了密碼,則須要配置
Redis的哨兵(sentinel) 系統用於管理多個 Redis 服務器,該系統執行如下三個任務:
· 監控(Monitoring): 哨兵(sentinel) 會不斷地檢查你的Master和Slave是否運做正常。
· 提醒(Notification):當被監控的某個 Redis出現問題時, 哨兵(sentinel) 能夠經過 API 向管理員或者其餘應用程序發送通知。
· 自動故障遷移(Automatic failover):當一個Master不能正常工做時,哨兵(sentinel) 會開始一次自動故障遷移操做,它會將失效Master的其中一個Slave升級爲新的Master, 並讓失效Master的其餘Slave改成複製新的Master; 當客戶端試圖鏈接失效的Master時,集羣也會向客戶端返回新Master的地址,使得集羣可使用Master代替失效Master。
哨兵(sentinel) 是一個分佈式系統,你能夠在一個架構中運行多個哨兵(sentinel) 進程,這些進程使用流言協議(gossipprotocols)來接收關於Master是否下線的信息,並使用投票協議(agreement protocols)來決定是否執行自動故障遷移,以及選擇哪一個Slave做爲新的Master.
每一個哨兵(sentinel) 會向其它哨兵(sentinel)、master、slave定時發送消息,以確認對方是否」活」着,若是發現對方在指定時間(可配置)內未迴應,則暫時認爲對方已掛(所謂的」主觀認爲宕機」 Subjective Down,簡稱sdown).
若「哨兵羣」中的多數sentinel,都報告某一master沒響應,系統才認爲該master"完全死亡"(即:客觀上的真正down機,Objective Down,簡稱odown),經過必定的vote算法,從剩下的slave節點中,選一臺提高爲master,而後自動修改相關配置.
雖然哨兵(sentinel) 釋出爲一個單獨的可執行文件 redis-sentinel ,但實際上它只是一個運行在特殊模式下的 Redis 服務器,你能夠在啓動一個普通 Redis 服務器時經過給定 --sentinel 選項來啓動哨兵(sentinel).
哨兵(sentinel) 的一些設計思路和zookeeper很是相似
單個哨兵(sentinel)
實現步驟:
1.拷貝到etc目錄
cp sentinel.conf /usr/local/redis/etc
2.修改sentinel.conf配置文件
sentinel monitor mymast 192.168.110.133 6379 1 #主節點 名稱 IP 端口號 選舉次數
3. 修改心跳檢測 5000毫秒
sentinel down-after-milliseconds mymaster 5000
4.sentinel parallel-syncs mymaster 2 --- 作多多少合格節點
5. 啓動哨兵模式
./redis-server /usr/local/redis/etc/sentinel.conf --sentinel &
6. 中止哨兵模式
Redis 事務能夠一次執行多個命令, 而且帶有如下兩個重要的保證:
事務是一個單獨的隔離操做:事務中的全部命令都會序列化、按順序地執行。事務在執行的過程當中,不會被其餘客戶端發送來的命令請求所打斷。
事務是一個原子操做:事務中的命令要麼所有被執行,要麼所有都不執行。
一個事務從開始到執行會經歷如下三個階段:
開始事務。
命令入隊。
執行事務。
如下是一個事務的例子, 它先以 MULTI 開始一個事務, 而後將多個命令入隊到事務中, 最後由 EXEC 命令觸發事務, 一併執行事務中的全部命令:
redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days" QUEUED redis 127.0.0.1:6379> GET book-name QUEUED redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series" QUEUED redis 127.0.0.1:6379> SMEMBERS tag QUEUED redis 127.0.0.1:6379> EXEC 1) OK 2) "Mastering C++ in 21 days" 3) (integer) 3 4) 1) "Mastering Series" 2) "C++" 3) "Programming"
下表列出了 redis 事務的相關命
序號 命令及描述 1 DISCARD #取消事務,放棄執行事務塊內的全部命令。 2 EXEC #執行全部事務塊內的命令。 3 MULTI #標記一個事務塊的開始。 4 UNWATCH #取消 WATCH 命令對全部 key 的監視。 5 WATCH key [key ...] #監視一個(或多個) key ,若是在事務執行以前這個(或這些) key 被其餘命令所改動,那麼事務將被打斷。
什麼是Redis持久化,就是將內存數據保存到硬盤。
Redis 持久化存儲 (AOF 與 RDB 兩種模式)
RDB 是在某個時間 點將數據寫入一個臨時文件,持久化結束後,用這個臨時文件替換上次持久化的文件,達到數據恢復。
優勢:使用單獨子進程來進行持久化,主進程不會進行任何 IO 操做,保證了 redis 的高性能
缺點:RDB 是間隔一段時間進行持久化,若是持久化之間 redis 發生故障,會發生數據丟失。因此這種方式更適合數據要求不嚴謹的時候
這裏說的這個執行數據寫入到臨時文件的時間點是能夠經過配置來本身肯定的,經過配置redis 在 n 秒內若是超過 m 個 key 被修改這執行一次 RDB 操做。這個操做就相似於在這個時間點來保存一次 Redis 的全部數據,一次快照數據。全部這個持久化方法也一般叫作 snapshots。
RDB 默認開啓,redis.conf 中的具體配置參數以下;
#dbfilename:持久化數據存儲在本地的文件 dbfilename dump.rdb #dir:持久化數據存儲在本地的路徑,若是是在/redis/redis-3.0.6/src下啓動的redis-cli,則數據會存儲在當前src目錄下 dir ./ ##snapshot觸發的時機,save ##以下爲900秒後,至少有一個變動操做,纔會snapshot ##對於此值的設置,須要謹慎,評估系統的變動操做密集程度 ##能夠經過「save 「」」來關閉snapshot功能 #save時間,如下分別表示更改了1個key時間隔900s進行持久化存儲;更改了10個key300s進行存儲;更改10000個key60s進行存儲。 save 900 1 save 300 10 save 60 10000 ##當snapshot時出現錯誤沒法繼續時,是否阻塞客戶端「變動操做」,「錯誤」可能由於磁盤已滿/磁盤故障/OS級別異常等 stop-writes-on-bgsave-error yes ##是否啓用rdb文件壓縮,默認爲「yes」,壓縮每每意味着「額外的cpu消耗」,同時也意味這較小的文件尺寸以及較短的網絡傳輸時間 rdbcompression yes
Append-only file,將「操做 + 數據」以格式化指令的方式追加到操做日誌文件的尾部,在 append 操做返回後(已經寫入到文件或者即將寫入),才進行實際的數據變動,「日誌文件」保存了歷史全部的操做過程;當 server 須要數據恢復時,能夠直接 replay 此日誌文件,便可還原全部的操做過程。AOF 相對可靠,它和 mysql 中 bin.log、apache.log、zookeeper 中 txn-log 簡直殊途同歸。AOF 文件內容是字符串,很是容易閱讀和解析。
優勢:能夠保持更高的數據完整性,若是設置追加 file 的時間是 1s,若是 redis 發生故障,最多會丟失 1s 的數據;且若是日誌寫入不完整支持 redis-check-aof 來進行日誌修復;AOF 文件沒被 rewrite 以前(文件過大時會對命令進行合併重寫),能夠刪除其中的某些命令(好比誤操做的 flushall)。
缺點:AOF 文件比 RDB 文件大,且恢復速度慢。
咱們能夠簡單的認爲 AOF 就是日誌文件,此文件只會記錄「變動操做」(例如:set/del 等),若是 server 中持續的大量變動操做,將會致使 AOF 文件很是的龐大,意味着 server 失效後,數據恢復的過程將會很長;事實上,一條數據通過屢次變動,將會產生多條 AOF 記錄,其實只要保存當前的狀態,歷史的操做記錄是能夠拋棄的;由於 AOF 持久化模式還伴生了「AOF rewrite」。
AOF 的特性決定了它相對比較安全,若是你指望數據更少的丟失,那麼能夠採用 AOF 模式。若是 AOF 文件正在被寫入時忽然 server 失效,有可能致使文件的最後一次記錄是不完整,你能夠經過手工或者程序的方式去檢測並修正不完整的記錄,以便經過 aof 文件恢復可以正常;同時須要提醒,若是你的 redis 持久化手段中有 aof,那麼在 server 故障失效後再次啓動前,須要檢測 aof 文件的完整性。
AOF 默認關閉,開啓方法,修改配置文件 reds.conf:appendonly yes
##此選項爲aof功能的開關,默認爲「no」,能夠經過「yes」來開啓aof功能 ##只有在「yes」下,aof重寫/文件同步等特性纔會生效 appendonly yes ##指定aof文件名稱 appendfilename appendonly.aof ##指定aof操做中文件同步策略,有三個合法值:always everysec no,默認爲everysec appendfsync everysec ##在aof-rewrite期間,appendfsync是否暫緩文件同步,"no"表示「不暫緩」,「yes」表示「暫緩」,默認爲「no」 no-appendfsync-on-rewrite no ##aof文件rewrite觸發的最小文件尺寸(mb,gb),只有大於此aof文件大於此尺寸是纔會觸發rewrite,默認「64mb」,建議「512mb」 auto-aof-rewrite-min-size 64mb ##相對於「上一次」rewrite,本次rewrite觸發時aof文件應該增加的百分比。 ##每一次rewrite以後,redis都會記錄下此時「新aof」文件的大小(例如A),那麼當aof文件增加到A*(1 + p)以後 ##觸發下一次rewrite,每一次aof記錄的添加,都會檢測當前aof文件的尺寸。 auto-aof-rewrite-percentage 100
AOF 是文件操做,對於變動操做比較密集的 server,那麼必將形成磁盤 IO 的負荷加劇;此外 linux 對文件操做採起了「延遲寫入」手段,即並不是每次 write 操做都會觸發實際磁盤操做,而是進入了 buffer 中,當 buffer 數據達到閥值時觸發實際寫入(也有其餘時機),這是 linux 對文件系統的優化,可是這卻有可能帶來隱患,若是 buffer 沒有刷新到磁盤,此時物理機器失效(好比斷電),那麼有可能致使最後一條或者多條 aof 記錄的丟失。經過上述配置文件,能夠得知 redis 提供了 3 中 aof 記錄同步選項:
always:每一條 aof 記錄都當即同步到文件,這是最安全的方式,也覺得更多的磁盤操做和阻塞延遲,是 IO 開支較大。
everysec:每秒同步一次,性能和安全都比較中庸的方式,也是 redis 推薦的方式。若是遇到物理服務器故障,有可能致使最近一秒內 aof 記錄丟失(可能爲部分丟失)。
no:redis 並不直接調用文件同步,而是交給操做系統來處理,操做系統能夠根據 buffer 填充狀況 / 通道空閒時間等擇機觸發同步;這是一種普通的文件操做方式。性能較好,在物理服務器故障時,數據丟失量會因 OS 配置有關。
其實,咱們能夠選擇的太少,everysec 是最佳的選擇。若是你很是在乎每一個數據都極其可靠,建議你選擇一款「關係性數據庫」吧。
AOF 文件會不斷增大,它的大小直接影響「故障恢復」的時間, 並且 AOF 文件中歷史操做是能夠丟棄的。AOF rewrite 操做就是「壓縮」AOF 文件的過程,固然 redis 並無採用「基於原 aof 文件」來重寫的方式,而是採起了相似 snapshot 的方式:基於 copy-on-write,全量遍歷內存中數據,而後逐個序列到 aof 文件中。所以 AOF rewrite 可以正確反應當前內存數據的狀態,這正是咱們所須要的;*rewrite 過程當中,對於新的變動操做將仍然被寫入到原 AOF 文件中,同時這些新的變動操做也會被 redis 收集起來(buffer,copy-on-write 方式下,最極端的多是全部的 key 都在此期間被修改,將會耗費 2 倍內存),當內存數據被所有寫入到新的 aof 文件以後,收集的新的變動操做也將會一併追加到新的 aof 文件中,此後將會重命名新的 aof 文件爲 appendonly.aof, 此後全部的操做都將被寫入新的 aof 文件。若是在 rewrite 過程當中,出現故障,將不會影響原 AOF 文件的正常工做,只有當 rewrite 完成以後纔會切換文件,由於 rewrite 過程是比較可靠的。*
觸發 rewrite 的時機能夠經過配置文件來聲明,同時 redis 中能夠經過 bgrewriteaof 指使人工干預。
redis-cli -h ip -p port bgrewriteaof
由於 rewrite 操做 /aof 記錄同步 /snapshot 都消耗磁盤 IO,redis 採起了「schedule」策略:不管是「人工干預」仍是系統觸發,snapshot 和 rewrite 須要逐個被執行。
AOF rewrite 過程並不阻塞客戶端請求。系統會開啓一個子進程來完成。
OF 和 RDB 各有優缺點,這是有它們各自的特色所決定:
1) AOF 更加安全,能夠將數據更加及時的同步到文件中,可是 AOF 須要較多的磁盤 IO 開支,AOF 文件尺寸較大,文件內容恢復數度相對較慢。
*2) snapshot,安全性較差,它是「正常時期」數據備份以及 master-slave 數據同步的最佳手段,文件尺寸較小,恢復數度較快。
能夠經過配置文件來指定它們中的一種,或者同時使用它們(不建議同時使用),或者所有禁用,在架構良好的環境中,master 一般使用 AOF,slave 使用 snapshot,主要緣由是 master 須要首先確保數據完整性,它做爲數據備份的第一選擇;slave 提供只讀服務(目前 slave 只能提供讀取服務),它的主要目的就是快速響應客戶端 read 請求;可是若是你的 redis 運行在網絡穩定性差 / 物理環境糟糕狀況下,建議你 master 和 slave 均採起 AOF,這個在 master 和 slave 角色切換時,能夠減小「人工數據備份」/「人工引導數據恢復」的時間成本;若是你的環境一切很是良好,且服務須要接收密集性的 write 操做,那麼建議 master 採起 snapshot,而 slave 採用 AOF。
Redis 發佈訂閱(pub/sub)是一種消息通訊模式:發送者(pub)發送消息,訂閱者(sub)接收消息。
Redis 客戶端能夠訂閱任意數量的頻道。
下圖展現了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的關係:
當有新消息經過 PUBLISH 命令發送給頻道 channel1 時, 這個消息就會被髮送給訂閱它的三個客戶端:
如下實例演示了發佈訂閱是如何工做的。在咱們實例中咱們建立了訂閱頻道名爲 redisChat:
redis 127.0.0.1:6379> SUBSCRIBE redisChat Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "redisChat" 3) (integer) 1
如今,咱們先從新開啓個 redis 客戶端,而後在同一個頻道 redisChat 發佈兩次消息,訂閱者就能接收到消息。
redis 127.0.0.1:6379> PUBLISH redisChat "Redis is a great caching technique" (integer) 1 redis 127.0.0.1:6379> PUBLISH redisChat "Learn redis by runoob.com" (integer) 1 # 訂閱者的客戶端會顯示以下消息 1) "message" 2) "redisChat" 3) "Redis is a great caching technique" 1) "message" 2) "redisChat" 3) "Learn redis by runoob.com"
發佈訂閱命令
下表列出了 redis 發佈訂閱經常使用命令:
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 ...]] |