Redis 是 C 語言開發的一個開源的(聽從 BSD 協議)高性能鍵值對(key-value)的 NoSQL的內存數據庫,能夠用做緩存、消息中間件等;具備如下特色:node
1. 性能優秀,數據在內存中,讀寫速度很是快,支持併發 10W QPS;redis
2. 單進程單線程,是線程安全的,採用 IO 多路複用機制;算法
3. 豐富的數據類型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等;spring
4. 支持數據持久化。能夠將內存中數據保存在磁盤中,重啓時加載;數據庫
5. 主從複製,哨兵,高可用;json
能夠用做分佈式鎖。能夠做爲消息中間件使用,支持發佈訂閱;緩存
1. 緩存: 提高數據的訪問性能安全
2. 可用做低配版的消息中間件,支持發佈訂閱springboot
3. 可用作分佈式鎖服務器
4. 能夠實現session共享
對比內容 | Redis |
memoryCache |
存儲方式 | Redis 支持持久化 | Memcache不支持持久化,會把數據所有存在內存,很難解決緩存雪崩的問題 |
數據類型 | Redis 支持五種數據類型 | Memcache 對數據類型的支持簡單,只支持簡單的 key-value |
底層模型 | Redis 直接本身構建了 VM 機制 由於通常的系統調用系統函數的話 會浪費必定的時間去移動和請求 |
調用系統 |
Value大小 | Redis 能夠達到 1GB | Memcache 只有 1MB |
Redis對象的類型、內部編碼、內存回收、共享對象等功能,都須要redisObject支持
這樣設計的好處:能夠針對不一樣場景,對5種經常使用的數據類型設置多種不一樣的數據結構實現,從而優化對象在不一樣場景下的使用效率;
type: 表明一個 value 對象具體是何種數據類型。
encoding: 不一樣數據類型在 redis 內部的存儲方式。
好比:type=string 表明 value 存儲的是一個普通字符串,那麼對應的 encoding 能夠是 raw 或者是 int。
若是是 int 則表明實際 redis 內部是按數值型類存儲和表示這個字符串的。
固然前提是這個字符串自己能夠用數值表示,好比:"123" "456"這樣的字符串。
vm字段: 只有打開了 Redis 的虛擬內存功能,此字段纔會真正的分配內存,該功能默認是關閉狀態的。
Redis 使用 redisObject 來表示全部的 key/value 數據是比較浪費內存的
固然這些內存管理成本的付出主要也是爲了給 Redis 不一樣數據類型提供一個統一的管理接口,實際做者也提供了多種方法幫助咱們儘可能節省內存使用。
服務端啓動:redis-server redis.config
客戶端啓動: redis-cli -p [服務器端口] -h [服務端ip]
1. 查看全部的key:keys *
2. 設置過時時間:set username nick EX 10 // ttl username 查看key剩餘時間
3. 選擇庫:select 1 // redis有16個庫0~15,能夠選擇不一樣的庫存儲數據,redis集羣默認用0號庫
4. 判斷key是否存在:exits username
5. 刪除key:del username
6. 查看key類型: type username
7. 數據持久化: save //保存在dump.rdb文件
8. 清空redis全部數據:flushall //flushdb爲清空分庫的數據,不刪除dump.rdb
9. 單節點批量操做:mset/mget //mset username nick mset address dg
10. 冷點數據設置超時清除:expire username 100 // expire爲秒維度,pexpire爲毫秒維度
11. 追加數據:若是 key 已經存在而且是一個字符串,append key value
String類型的數據結構,是key-value形式,value爲字符串/json串
1. 增(原生redis命令):set user1 {name:nick, age:15, gender:male}
2. 查(原生redis命令):get user1
3. 改(原生redis命令):set user1 {name:nick, age:15, gender:male}
4. 刪(原生redis命令):del user1
5. 記步器:incrby/decrby key //對value進行自增/自減,例如統計在線人數;
Hash類型的數據結構是key-value形式,每一條value有是key-value形式
1. 增(原生redis命令):
hset user1 name nick age 10 gender male //name age gender單條添加
hmset user1 name nick age 10 gender male //3個屬性批量添加
hincrby user1 age //age自增
2. 查(原生redis命令):
hget user1 name //獲取key爲user1指向的name這個key指向的數據
hmget user1 name age gender //批量獲取數據
hgetall user1 // 獲取key爲user1指向的全部數據
hkeys user1 // 獲取key爲user1指向的全部的hkey字段
hlen user1 //獲取key爲user1指向的數據的個數
3. 改(原生redis命令):hset user1 name nick1
4. 刪(原生redis命令):hdel user1 name
List類型字符串列表,按照插入順序排序。能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)
1. 增(原生redis命令):
LPUSH key value [value ...] // lpush nameList jack // 從左側push
LINSERT key BEFORE|AFTER pivot value // linsert nameList before jerry tom//從左邊,在jerty前面插入tom
LTRIM key begin end // 保留鏈表範圍內的數據元素
rpoplpush key1 key2 // 原子級操做,兩個鏈表數據交互,從key1刪除,key2備份
2. 查(原生redis命令):
LPOP key // lpop key 從鏈表中獲取數據
LRANGE key start stop // lrange nameList 0 -1 查看nameList中全部元素的個數
3. 改(原生redis命令):LSET key index value // lset nameList 1 nick2,修改nick->nick2
4. 刪(原生redis命令):LREM key count value // lrem key count value 刪除count個 數據爲value的元素 count取0時,所有刪除value
Set集合用來保存多個字符串元素,和list不用的是不容許有重複元素,而且無序
應用場景:用戶的興趣、愛好、關注人列表、粉絲列表等
1. 增(原生redis命令):
SADD key member [member ...] // sadd nameList jack nick tom jerry
2. 查(原生redis命令):
SRANDMEMBER key // srandmember nameList 隨機獲取一條數據,
SMEMBERS key // smembers nameList 查看nameList key的全部數據
3. 刪(原生redis命令):
SREM key member // srem nameList nick
4. 判斷元素是否在集合中:
SISMEMBER key member // sismember nameList jack
5. 不一樣集合間的交、並、差集
SINTER key [key ...] // sinter nameList nameList2
SUINON key [key ...] // sinter nameList nameList2
SDIFF key [key ...] // sinter nameList nameList2 以第一個key爲主
ZSet在Set集合的基礎之上綁定了一個score做爲排序的依據
1. 增(原生redis命令):
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
zadd result 100 jack //100爲score
ZINCRBY key increment member // zincrby result 50 jack 增長jack成員的分數
2. 查(原生redis命令):
ZCARD key // 查看key裏面數據的個數
ZSCORE key member // zscore result jack 查當作員的score
ZRANGE key start stop [WITHSCORES] // zrange result 0 -1 查看排名
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] //根據score篩選
ZRANK key member //計算成員的score排名
3. 刪(原生redis命令):
ZREM key member [member ...] // zrem result jack 刪除某個成員
ZREMRANGEBYRANK key start stop // 刪除指定排名內的升序元素
ZREMRANGEBYSCORE key min max // 刪除score在[min, max]範圍的元素
4. 不一樣集合間的交、並、差集
ZINTERSTORE destination numkeys key [key ...] // zinterstore out 2 result result1,destination表示輸出的ZSet 交集的score相加
1. bind 127.0.0.1 ###指定redis只接收來自該IP地址的請求,若是不進行設置,將處理全部請求
2. protected-mode yes ###是否開啓保護模式,默認開啓;若是關閉,外部網絡能夠直接訪問;若是開啓,則須要bind ip或者設置訪問密碼(安全考慮)
3. port 6379 ###redis監聽的端口
4. tcp-backlog 511 ###默認511,此參數標識肯定了TCP鏈接中已完成隊列(完成三次握手以後)的長度,能夠理解爲進程尚未accept的TCP鏈接的隊列,tcp-backlog必須小於等於Linux系統定義的/proc/sys/net/somaxconn值
Linux的somaxconn默認爲128,當系統併發量大,而且客戶端速度緩慢的時候,能夠將這兩個參數一塊兒參考設置,建議修改成2048,在/etc/sysctl.conf中添加 net.core.somaxconn=2048,而後終端執行sysctl -p命令生效
5. timeout 0 ###設置客戶端空閒超過timeout時,redis服務端會斷開鏈接,爲0時表示服務端不會主動斷開鏈接,此值不能小於0
6. tcp-keepalive 300 ###redis3.2之後默認爲300秒,配置上這個參數以後,對於一些沒有正常關閉的客戶端,也能夠及時關閉,鏈接超時受tcp-keepalive和另外三個參數影響:
tcp_keepalive_time default 7200 seconds
tcp_keepalive_probes default 9
tcp_keepalive_intvl default 75 seconds
鏈接超時公式爲: tcp_keepalive_time+tcp_keepalive_intvl*tcp_keepalive_probes=7895s=131.58min7. daemonize yes
8. pidfile /var/run/redis/redis.pid ####redis的進程文件
9. loglevel notice ###服務端日誌級別,包括debug(不少信息,方便開發、測試),verbose(許多有用的信息,可是沒有debug級別信息多),notice(適當的日誌級別,適合生產環境),warn(只有很是重要的信息)
基於上述配置文件的修改項:bind、port
1. 啓動redis服務端
redis-server redis.config
2. 啓動客戶端
redis-cli redis-cli -h [host_ip] -p [port]
3. 中止redis服務端
登陸客戶端執行:redis-cli -h [host_ip] -p [port] shutdown
1. 添加maven依賴
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.4.7.RELEASE</version> </dependency> </dependencies>
2. application.properties配置
spring.redis.sharedpool.nodes=192.168.75.*:6739,192.168.75.*:6780,192.168.75.*:6381
spring.redis.sharedpool.maxTotal=200
spring.redis.sharedpool.maxIdle=10
spring.redis.sharedpool.minIdle=2
3. springboot管理redis鏈接池
@Configuration @ConfigurationProperties("spring.redis.sharedpool") @Setter @Getter public class SharedJedisConfigPool { private List<String> nodes; //[192.168.75.132:6739,192.168.75.132:6780,192.168.75.132:6381] private Integer maxTotal; private Integer maxIdle; private Integer minIdle; @Bean public ShardedJedisPool sharedPoolInit() { List<JedisShardInfo> infoList = new ArrayList<>(); for (String node : nodes) { String ip = node.split(":")[0]; int port = Integer.parseInt(node.split(":")[1]); infoList.add(new JedisShardInfo(ip, port)); } GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(maxTotal); config.setMaxIdle(maxIdle); config.setMinIdle(minIdle); return new ShardedJedisPool(config, infoList); } }
4. 鏈接池測試
class DemoApplicationTests { @Autowired private ShardedJedisPool pool; @Test public void func4() { ShardedJedis jedis = pool.getResource(); jedis.set("user", "nick"); } }
class DemoApplicationTests { @Autowired private ShardedJedisPool pool; @Test public void func1() { String key = "04f2c34c"; ShardedJedis jedis = pool.getResource(); if (jedis.exists(key)) { //redis緩存有數據 String value = jedis.get(key); } else { String key2 = "04f2c34c";//查詢數據庫 jedis.set(key2, "nick"); } }
}
1. 舊版redis中一般使用客戶端分片來解決水平擴容問題,即啓動多個redis服務端,客戶端決定key交由哪一個節點存儲,下次客戶端直接到該節點讀取key
2. 能夠實現將整個數據分佈存儲在N個數據庫節點中,每一個節點只存放總數據量的1/N。
3. 對於須要擴容的場景來講,在客戶端分片後,若是想增長節點,須要對數據進行手工遷移,在遷移時爲了保證數據一致性,須要將集羣暫時下線,相對比較複雜
自定義簡單版分片規則以下:
n個redis服務端幾乎均勻存儲,hash取餘,散列,只要有散列就會有數據傾斜
key.hashCode()結果可正可負
key.hashCode()&Integer.MAX_VALUE結果必定爲正數,key和結果惟一對應
key.hashCode()&Integer.MAX_VALUE%n
如3.3.3小節討論,在使用redis集羣時,能夠經過自定義的簡單版的hash散列方式分片,可是有如下兩個缺點,所以引出hash一致性算法
1. 數據傾斜不可避免,只要有hash散列,就必定會有數據傾斜,不可能徹底均勻;
2. 擴容、縮容時,數據遷移量巨大★
SharedJedis實現了分片底層的算法——hash一致性(解決了取餘算法再擴縮容時,數據遷移量過大的問題);爲何?redis的hash邏輯爲:將全部節點信息hash散列到0~2^32-1區間
引入問題:
1. 數據傾斜:
若是節點的數量不多,而hash環空間很大( 0 ~ 2^32),直接進行一致性hash會致使節點在環上的位置會很不均勻,擠在某個很小的區域。最終對分佈式集羣的每一個實例上儲存的緩存數據量不一致,會發生嚴重的數據傾斜;
2. 緩存雪崩
若是每一個節點在環上只有一個節點,那麼能夠想象,當某一集羣從環中消失時,它本來所負責的任務將所有交由順時針方向的下一個集羣處理。
例如當6379退出時,它本來所負責的緩存將所有交給6380處理。這就意味着6380的訪問壓力會瞬間增大。若是6380由於壓力過大而崩潰,那麼更大的壓力又會向6381壓過去,最終服務壓力就像滾雪球同樣越滾越大,最終致使雪崩
引入虛擬節點
解決上述兩個問題最好的辦法就是擴展整個環上的節點數量,所以咱們引入了虛擬節點的概念。一個實際節點將會映射多個虛擬節點,屢次hash,這樣Hash環上的空間分割就會變得均勻。
在同一臺虛擬機上構建1主3從的主從模式,一個主節點通常最多6個從節點;
0. 主節點redis配置文件bind要綁定0.0.0.0表示全部鏈接均可以訪問,沒有綁定的話,不能夠訪問主;
1. 建立從節點redis啓動配置文件,redis02.config、redis03.config,並修改端口爲6380、6381
2. 啓動3個redis-server實例 redis-server redis.config、redis-server redis02.config、redis-server redis03.config
3. 主從關係建立,規定6379節點master、6380節點做爲slave 、6381節點做爲slave;
4. 登陸查看節點狀態的命令,以客戶端登陸6379節點爲例:6379> info replication
4. 經過配置文件定義主從關係
5. 經過命令掛在主節點,命令以下:從節點> slaveof masterip masterport
6. 本處經過命令方式指定主從關係,客戶端登陸6080、6081服務端,執行第五步命令
7. 查看637九、638一、6382節點的replicatinon信息,
測試過程:
主節點6379寫數據,從節點6380讀數據
1. 登陸主節點客戶端 set name nick
2. 登陸從節點客戶端 get name
3. 登陸從節點 flushal 執行失敗
4. 主節點shutdown
測試結果:
★從節點配置默認爲只讀模式read-only,主節點宕機,從節點不會自動變爲主節點,從節點info replication狀態變爲以下:
master_link_status:down
master_last_io_seconds_ago:-1
redis-cluster(redis 3.0)出現以前,redis的使用幾乎都是圍繞哨兵模式展開,哨兵過程以下:
1. 起單獨線程(特殊的redis-cluster)開啓對主從結構的監聽
2. 監聽主,從主節點調用命令info replication,獲取主從結構的全部信息,保存在內存,每1秒鐘發起一次心跳檢測(RPC遠程通訊協議),一旦主節點宕機,哨兵集羣發起投票選舉,過半票數肯定結果,增長可信;
3. 哨兵容忍度:容許宕機的個數是哨兵的容忍度,爲了保障多票選取,5個哨兵集羣節點的容忍度爲2,6個哨兵集羣的容忍度爲2;2n-1個集羣和2n個集羣的容忍度是同樣的,所以哨兵集羣都是奇數個(少用一個資源)
模式:3個哨兵節點,管理6379,6380,6382主從結構,過程以下:
1. 修改哨兵的模板配置文件(sentinel.conf),配置3個哨兵端口:26379,26380,26381
2. 釋放保護模式註釋,修改成no
3. 修改監聽主節點的配置核心內容:sentine monitor mymaster [master_ip] [master_port] [num]
3..1 mymaster是自定義名稱 標識當前哨兵監聽的主從結構的代號,多個哨兵監聽同一個主從結構的話,此處要保持一致
3.2 num表示爲主觀下限票數,當哨兵集羣不斷宕機時,最少要剩下的節點數量,和宕機容忍度有關
4. 拷貝三份,分別作如上修改
5. 啓動哨兵(啓動以前肯定主從集羣正常):redis-sentinel sentine.conf
6. 哨兵沒有restart等操做
7. kill掉主節點,查看哨兵日誌
8. 哨兵發現主節點宕機,開始投票,查看日誌num,肯定宕機,投票選取新的節點做爲主節點,主節點一旦恢復,則會也從節點角色加入集羣,繼續提供服務
9. 重啓哨兵日誌時,刪除尾部日誌,避免影響本次啓動
1. application.properties配置
spring.redis.sentinel=192.168.75.132:26379,192.168.75.132:26380,192.168.75.132:26381 spring.redis.sharedpool.maxTotal=200 spring.redis.sharedpool.maxIdle=10 spring.redis.sharedpool.minIdle=2
2. springboot管理sentinel鏈接池
@Configuration @ConfigurationProperties("spring.redis.sentinel") @Getter @Setter public class SentinelConfig { private List<String> nodes; private Integer maxTotal; private Integer maxIdle; private Integer minIdle; @Bean public JedisSentinelPool sentinelPoolInit() { //蒐集哨兵集羣信息 Set<String> sentinelSet = new HashSet<>(); for (String node : nodes) { String ip = node.split(":")[0]; int port = Integer.parseInt(node.split(":")[1]); sentinelSet.add(new HostAndPort(ip, port).toString()); } //鏈接池配置對象 GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(maxTotal); config.setMaxIdle(maxIdle); config.setMinIdle(minIdle); //構建哨兵管理對象 return new JedisSentinelPool("mymaster", sentinelSet, config); } }
3. 鏈接池測試
@SpringBootTest class DemoApplicationTests {
@Autowired private JedisSentinelPool sentinelPool; @Test public void func5() { HostAndPort masterIP = sentinelPool.getCurrentHostMaster(); Jedis jedis = sentinelPool.getResource(); //哨兵模式只能操做主節點 String name = jedis.get("name"); } }