Java緩存機制


 

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后查詢后返回

  1. {
  2. id: 35,
  3. name: "springboot2.2",
  4. age: 99
  5. }

修改數據庫中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 字符串

  1. 127 .0.0.1:6379> set name raolei
  2. OK
  3. 127 .0.0.1:6379> set itboy www.itboy.com
  4. OK
  5. 127 .0.0.1:6379> get name
  6. " raolei"
  7. 127 .0.0.1:6379> get itboy
  8. " www.itboy.com"

常用命令:

編號

命令

描述說明

1

SET key value

此命令設置指定鍵的值。

2

GET key

獲取指定鍵的值。

3

GETRANGE key start end

獲取存儲在鍵上的字符串的子字符串。

4

GETSET key value

設置鍵的字符串值並返回其舊值。

5

GETBIT key offset

返回在鍵處存儲的字符串值中偏移處的位值。

6

MGET key1 [key2..]

獲取所有給定鍵的值

7

SETBIT key offset value

存儲在鍵上的字符串值中設置或清除偏移處的位

8

SETEX key seconds value

使用鍵和到期時間來設置值

9

SETNX key value

設置鍵的值,僅當鍵不存在時

10

SETRANGE key offset value

在指定偏移處開始的鍵處覆蓋字符串的一部分

11

STRLEN key

獲取存儲在鍵中的值的長度

12

MSET key value [key value …]

為多個鍵分別設置它們的值

13

MSETNX key value [key value …]

為多個鍵分別設置它們的值,僅當鍵不存在時

14

PSETEX key milliseconds value

設置鍵的值和到期時間(以毫秒為單位)

15

INCR key

將鍵的整數值增加1

16

INCRBY key increment

將鍵的整數值按給定的數值增加

17

INCRBYFLOAT key increment

將鍵的浮點值按給定的數值增加

18

DECR key

將鍵的整數值減1

19

DECRBY key decrement

按給定數值減少鍵的整數值

20

APPEND key value

將指定值附加到鍵

2.3.2 list

  1. 127 .0.0.1:6379> lpush listkey redis
  2. ( integer) 1
  3. 127 .0.0.1:6379> lpush listkey mysql
  4. ( integer) 2
  5. 127 .0.0.1:6379> lpush listkey mongdb
  6. ( integer) 3
  7. 127 .0.0.1:6379> lpush listkey hbase
  8. ( integer) 4
  9. 127 .0.0.1:6379> lrange listkey 0 10
  10. 1) " hbase"
  11. 2) " mongdb"
  12. 3) " mysql"
  13. 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 
對一個列表進行修剪(trim),就是說,讓列表只保留指定區間內的元素,不在指定區間之內的元素都將被刪除。

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多億個成員

  1. 127 .0.0.1:6379> sadd setkey redis
  2. ( integer) 1
  3. 127 .0.0.1:6379> sadd setkey redis
  4. ( integer) 0
  5. 127 .0.0.1:6379> sadd setkey redis
  6. ( integer) 0
  7. 127 .0.0.1:6379> sadd setkey redis
  8. ( integer) 0
  9. 127 .0.0.1:6379> sadd setkey redis
  10. ( integer) 0
  11. 127 .0.0.1:6379> sadd setkey mysql
  12. ( integer) 1
  13. 127 .0.0.1:6379> sadd setkey mongdb
  14. ( integer) 1
  15. 127 .0.0.1:6379> SMEMBERS setkey
  16. 1) " mysql"
  17. 2) " mongdb"
  18. 3) " redis"

常用命令:

序號

命令及描述

1

SADD key member1 [member2] 
向集合添加一個或多個成員

2

SCARD key 
獲取集合的成員數

3

SDIFF key1 [key2] 
返回給定所有集合的差集

4

SDIFFSTORE destination key1 [key2] 
返回給定所有集合的差集並存儲在 destination 中

5

SINTER key1 [key2] 
返回給定所有集合的交集

6

SINTERSTORE destination key1 [key2] 
返回給定所有集合的交集並存儲在 destination 中

7

SISMEMBER key member 
判斷 member 元素是否是集合 key 的成員

8

SMEMBERS key 
返回集合中的所有成員

9

SMOVE source destination member 
將 member 元素從 source 集合移動到 destination 集合

10

SPOP key 
移除並返回集合中的一個隨機元素

11

SRANDMEMBER key [count] 
返回集合中一個或多個隨機數

12

SREM key member1 [member2] 
移除集合中一個或多個成員

13

SUNION key1 [key2] 
返回所有給定集合的並集

14

SUNIONSTORE destination key1 [key2] 
所有給定集合的並集存儲在 destination 集合中

15

SSCAN key cursor [MATCH pattern] [COUNT count] 
迭代集合中的元素

2.3.4 sorted set

與set一樣也是string類型元素的集合,且不允許重復的成員。不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。有序集合的成員是唯一的,但分數(score)卻可以重復。

集合是通過哈希表實現的,所以添加,刪除,查找的復雜度都是O(1)。 集合中最大的成員數為 232 - 1 (4294967295, 每個集合可存儲40多億個成員)。

  1. 127 .0.0.1:6379> zadd zsetkey 1 redis
  2. ( integer) 1
  3. 127 .0.0.1:6379> zadd zsetkey 2 mysql
  4. ( integer) 1
  5. 127 .0.0.1:6379> zadd zsetkey 2 mongdb
  6. ( integer) 1
  7. 127 .0.0.1:6379> smembers setkey
  8. 1) " mysql"
  9. 2) " mongdb"
  10. 3) " redis"

常用命令:

序號

命令及描述

1

ZADD key score1 member1 [score2 member2] 
向有序集合添加一個或多個成員,或者更新已存在成員的分數

2

ZCARD key 
獲取有序集合的成員數

3

ZCOUNT key min max 
計算在有序集合中指定區間分數的成員數

4

ZINCRBY key increment member 
有序集合中對指定成員的分數加上增量 increment

5

ZINTERSTORE destination numkeys key [key ...] 
計算給定的一個或多個有序集的交集並將結果集存儲在新的有序集合 key 中

6

ZLEXCOUNT key min max 
在有序集合中計算指定字典區間內成員數量

7

ZRANGE key start stop [WITHSCORES] 
通過索引區間返回有序集合成指定區間內的成員

8

ZRANGEBYLEX key min max [LIMIT offset count] 
通過字典區間返回有序集合的成員

9

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 
通過分數返回有序集合指定區間內的成員

10

ZRANK key member 
返回有序集合中指定成員的索引

11

ZREM key member [member ...] 
移除有序集合中的一個或多個成員

12

ZREMRANGEBYLEX key min max 
移除有序集合中給定的字典區間的所有成員

13

ZREMRANGEBYRANK key start stop 
移除有序集合中給定的排名區間的所有成員

14

ZREMRANGEBYSCORE key min max 
移除有序集合中給定的分數區間的所有成員

15

ZREVRANGE key start stop [WITHSCORES] 
返回有序集中指定區間內的成員,通過索引,分數從高到底

16

ZREVRANGEBYSCORE key max min [WITHSCORES] 
返回有序集中指定分數區間內的成員,分數從高到低排序

17

ZREVRANK key member 
返回有序集合中指定成員的排名,有序集成員按分數值遞減(從大到小)排序

18

ZSCORE key member 
返回有序集中,成員的分數值

19

ZUNIONSTORE destination numkeys key [key ...] 
計算給定的一個或多個有序集的並集,並存儲在新的 key 中

20

ZSCAN key cursor [MATCH pattern] [COUNT count] 
迭代有序集合中的元素(包括元素成員和元素分值)

2.3.5 hash

是一個string類型的field和value的映射表(和map差不多,只是兼職都是字符串),hash特別適合用於存儲對象。Redis 中每個 hash 可以存儲 232 - 1 鍵值對(40多億)。

  1. 127 .0.0.1:6379> hmset hmset name "redis tutorial"
  2. OK
  3. 127 .0.0.1:6379> hmset hmset age 24
  4. OK
  5. 127 .0.0.1:6379> hgetall hmset
  6. 1) " name"
  7. 2) " redis tutorial"
  8. 3) " age"
  9. 4) "24"

序號

命令及描述

1

HDEL key field2 [field2] 
刪除一個或多個哈希表字段

2

HEXISTS key field 
查看哈希表 key 中,指定的字段是否存在。

3

HGET key field 
獲取存儲在哈希表中指定字段的值。

4

HGETALL key 
獲取在哈希表中指定 key 的所有字段和值

5

HINCRBY key field increment 
為哈希表 key 中的指定字段的整數值加上增量 increment 。

6

HINCRBYFLOAT key field increment 
為哈希表 key 中的指定字段的浮點數值加上增量 increment 。

7

HKEYS key 
獲取所有哈希表中的字段

8

HLEN key 
獲取哈希表中字段的數量

9

HMGET key field1 [field2] 
獲取所有給定字段的值

10

HMSET key field1 value1 [field2 value2 ] 
同時將多個 field-value (域-值)對設置到哈希表 key 中。

11

HSET key field value 
將哈希表 key 中的字段 field 的值設為 value 。

12

HSETNX key field value 
只有在字段 field 不存在時,設置哈希表字段的值。

13

HVALS key 
獲取哈希表中所有值

14

HSCAN key cursor [MATCH pattern] [COUNT count] 
迭代哈希表中的鍵值對。

redis怎么存放對象?通過將對象序列化為json字符串,存放為字符串形式,之后讀取在反序列化為對象,這樣很快很快

2.4 Spring Boot集成redis

添加依賴:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-web</artifactId>
  8. </dependency>
  9. <!--spring2.0集成redis所需common-pool2-->
  10. <dependency>
  11. <groupId>org.apache.commons</groupId>
  12. <artifactId>commons-pool2</artifactId>
  13. <version>2.4.2</version>
  14. </dependency>

配置文件:

  1. ########################################################
  2. ## #Redis (RedisConfiguration)
  3. ########################################################
  4. spring:
  5. redis:
  6. database: 0
  7. host: 192.168.245.134
  8. port: 6379
  9. password: 123456
  10. jedis:
  11. pool:
  12. max-idle: 8
  13. min-idle: 0
  14. max-active: 8
  15. max-wait: -1ms
  16. timeout: 5000ms

service:

  1. @Service
  2. public class RedisService {
  3. @Autowired
  4. private StringRedisTemplate stringRedisTemplate;
  5.  
  6. //字符串
  7. public void setStringKey(String key,String value,Long time){
  8. setObject(key, value,time);
  9. }
  10. public void setStringKey(String key,String value){
  11. setObject(key, value,null);
  12. }
  13.  
  14. //set
  15. public void setSetKey(String key,Set value){
  16. setObject(key, value,null);
  17. }
  18. //list
  19. public void setListKey(String key,List value){
  20. setObject(key, value,null);
  21. }
  22.  
  23. public String getStringKey(String key){
  24. return (String) getObject(key,new String());
  25. }
  26. public Set getSetKey(String key){
  27. return (Set) getObject(key,new HashSet<String>());
  28. }
  29. public List getListKey(String key){
  30. return (List) getObject(key,new ArrayList<String>());
  31. }
  32.  
  33.  
  34.  
  35.  
  36. public void setObject(String key,Object value,Long time){
  37. if(StringUtils.isEmpty(key)||value==null){
  38. return;
  39. }
  40. //字符串類型
  41. if(value instanceof String){
  42. String value1= (String) value;
  43. if(time!=null){
  44. stringRedisTemplate.opsForValue(). set(key,value1,time,TimeUnit.SECONDS);
  45. } else{
  46. stringRedisTemplate.opsForValue(). set(key,value1);
  47. }
  48. return;
  49. }
  50. //list類型
  51. else if(value instanceof List){
  52. List<String> list= (List<String>) value;
  53. for (String s:list) {
  54. stringRedisTemplate.opsForList().leftPush(key,s);
  55. }
  56. return;
  57. }
  58. //set
  59. else if(value instanceof Set){
  60. Set<String> strings= (Set<String>) value;
  61. for (String s : strings) {
  62. stringRedisTemplate.opsForSet(). add(key,s);
  63. }
  64. return;
  65. }
  66. /**
  67. * .....
  68. */
  69. }
  70.  
  71. public Object getObject(String key,Object object){
  72. if(StringUtils.isEmpty(key)||object==null){
  73. return null;
  74. }
  75. else if (object instanceof String){
  76. return stringRedisTemplate.opsForValue().get(key);
  77. }
  78. else if(object instanceof List){
  79. return stringRedisTemplate.opsForList().range(key,0,stringRedisTemplate.opsForList().size(key));
  80. }
  81. else if(object instanceof Set){
  82. return stringRedisTemplate.opsForSet().members(key);
  83. }
  84. return null;
  85. }
  86. }

controller:

  1. @RestController
  2. public class IndexController {
  3. @Autowired
  4. private RedisService redisService;
  5.  
  6. @RequestMapping( "/setString")
  7. public String setString(@PathParam("key") String key,
  8. @PathParam("value") String value){
  9. redisService.setStringKey(key, value);
  10. return redisService.getStringKey(key);
  11. }
  12. @RequestMapping( "/setSet")
  13. public Set<String> setSet(@PathParam("key") String key,
  14. @PathParam("value") String value){
  15. HashSet<String> strings = new HashSet<>();
  16. strings. add(value);
  17. strings. add(value+"1");
  18. strings. add(value+"2");
  19. strings. add(value+"3");
  20. redisService.setSetKey(key,strings);
  21. return redisService.getSetKey(key);
  22. }
  23. @RequestMapping( "/setList")
  24. public List<String> setList(@PathParam("key") String key,
  25. @PathParam("value") String value){
  26. ArrayList<String> strings = new ArrayList<>();
  27. strings. add(value);
  28. strings. add(value+"1");
  29. strings. add(value+"2");
  30. strings. add(value+"3");
  31. redisService.setListKey(key,strings);
  32. return redisService.getListKey(key);
  33. }
  34. }

訪問http://localhost:8080/setSet?key=itboySet&value=123456http://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配置文件,放開以下兩行的注釋,添加如下內容。兩台從服務器都要修改。

  1. slaveof 192.168.245.134 6379
  2. #主服務器的ip和端口號
  3.  
  4. # If the master is password protected (using the "requirepass" configuration
  5. # directive below) it is possible to tell the slave to authenticate before
  6. # starting the replication synchronization process, otherwise the master will
  7. # refuse the slave request.
  8. #
  9. masterauth 123456
  10. #主服務器的認證密碼

3、測試

  1. #進入master主服務器
  2. 192.168.245.134:6379> info
  3. #回車后看到如下內容即代表成功
  4. # Replication
  5. role:master
  6. connected_slaves: 2 #兩台從服務器
  7. slave0:ip= 192.168.245.135,port=6379,state=online,offset=127,lag=1 #一些描述信息
  8. slave1:ip= 192.168.245.136,port=6379,state=online,offset=127,lag=1 #一些描述信息
  9.  
  10.  
  11. #進入任何一台服務器比如135,同樣info以下,看到如下內容即代表成功。
  12. # Replication
  13. role:slave #角色從服務器
  14. master_host: 192.168.245.134 #主服務器ip
  15. master_port: 6379 #主服務端口
  16. master_link_status:up #狀態
  17. master_last_io_seconds_ago: 10
  18. master_sync_in_progress: 0
  19.  
  20.  
  21. #主服務器
  22. 192.168.245.134:6379> set master "192.168.245.134"
  23. OK
  24. 192.168.245.134:6379> get master
  25. "192.168.245.134"
  26. 192.168.245.134:6379>
  27. ##可讀可寫是吧???????
  28. #########剛剛設置的這條數據從服務器有嗎??????############
  29. 192.168.245.135:6379> get master
  30. "192.168.245.134"
  31. 192.168.245.135:6379> set slave "192.168.245.135"
  32. ( error) READONLY You can't write against a read only slave.
  33. 192.168.245.135:6379>
  34. ############喲呵有數據的,主從復制成功,主從間數據同步問題解決,而且不能寫,讀寫分離也搞定了########
  35. [root@localhost bin] # ./redis-cli -h 192.168.245.136 -p 6379 -a "123456"
  36. 192.168.245.136:6379> ping
  37. PONG
  38. 192.168.245.136:6379> get master
  39. "192.168.245.134"
  40. 192.168.245.136:6379> set slave "192.168.245.136"
  41. ( error) READONLY You can't write against a read only slave.
  42. 192.168.245.136:6379>
  43.  
  44. ################另一台從服務器也一樣######################
  45.  

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

  1. sentinel monitor mymaster 192.168.245.134 6379 1
  2. # 主節點名稱 主機ip 端口號 選舉次數(就是說當有幾台sentinel認為master掛了才是真的掛了,因為這里只有一個哨兵,所以為1)
  3. sentinel down-after-milliseconds mymaster 30
  4. #就是說多少ms后沒有給老子響應,老子就覺得你掛了。,默認30s,這里設置為30ms,本地測試追求實時
  5. sentinel config-epoch mymaster 1
  6. #這個數字表示在發生主從復制的時候,比如master1向master2切換時,可以同時有多少個slave能對master2執行同步(也就是復制其實),越多越好?>如果太多了那么大家都去復制了,誰來響應客戶端的請求?太少?太少的話,每個都要來一遍,怕是要到天黑哦,根據實際情況吧,這里只有三台所以
  7. 為設為 1.
  8.  
  9. sentinel auth- pass mymaster 123456
  10. #主服務器密碼

2、啟動哨兵:nohup ./redis-server ../etc/sentinel.conf --sentinel  2>1 1>nohup.log &

3、如何停止ps -aux | grep 端口號,kill -9 pid即可

4、測試?

  1. #我們首先查看master(134)的info master
  2. role:master
  3. connected_slaves:2
  4. slave0:ip=192.168.245.135,port=6379,state=online,offset=5349,lag=1
  5. slave1:ip=192.168.245.136,port=6379,state=online,offset=5349,lag=0
  6.  
  7.  
  8. #135的 slave
  9. role:slave
  10. master_host:192.168.245.134
  11. master_port:6379
  12. master_link_status:up
  13.  
  14.  
  15. #136 的 slave
  16. role:slave
  17. master_host:192.168.245.134
  18. master_port:6379
  19. master_link_status:up
  20.  
  21.  
  22. #我現在講master停掉? master
  23. #134
  24. 192.168.245.134:6379> shutdown
  25. not connected>
  26.  
  27.  
  28. #135 的info replication
  29. 192.168.245.135:6379> info replication
  30. # Replication
  31. role:slave
  32. master_host:192.168.245.136
  33. master_port:6379
  34. master_link_status:up
  35. #看到主服務器變為136,
  36.  
  37. #136?
  38. 192.168.245.136:6379> info replication
  39. # Replication
  40. role:master
  41. connected_slaves:2
  42. slave0:ip=192.168.245.135,port=6379,state=online,offset=739,lag=0
  43. slave1:ip=192.168.245.134,port=6379,state=online,offset=739,lag=0
  44.  
  45. #基本上成功了,最后試試讀寫分離以及在測試一些其他的,這里不再展示
  46.  
  47.  
  48.  

2.6 數據持久化

數據持久化:就是將內存中的數據保存到硬盤,redis支持AOF和RDB兩種存儲方式

2.6.1 RDB存儲

RDB是指在一個時間點,如果達到所配置的數據修改量,就寫入一個臨時文件,持久化結束后,用這個臨時文件替換上次持久化的文件,達到數據恢復。(二進制文件方式

有兩種保存方式:1、(阻塞)主進程直接拍快照(snapshot),然后阻塞客戶端請求寫入IO磁盤。2、(非阻塞)當要寫入磁盤時,新建(fork)一個子進程,子進程將當前數據庫快照寫入磁盤,而主進程繼續處理客戶端請求。

每次快照持久化都是將內存數據完整寫入到磁盤一次,並不 是增量的只同步臟數據。如果數據量大的話,而且寫操作比較多,必然會引起大量的磁盤io操作,可能會嚴重影響性能。

優點:使用單獨子進程進行持久化,主進程不會進行任何IO操作,保證了redis的高性能

缺點:RDB需要間隔一段時間進行持久化,而且必須達到相應修改數量,所以如果持久化之間發生故障,會造成數據丟失,常用於數據要求不嚴謹的時候。

配置:

  1. #dbfilename:持久化數據存儲在本地的文件
  2. dbfilename dump.rdb
  3. #dir:持久化數據存儲在本地的路徑,如果是在/redis/redis-3.0.6/src下啟動的redis-cli,則數據會存儲在當前src目錄下
  4. dir ./
  5.  
  6. ##snapshot觸發的時機,save
  7.  
  8. ##如下距離上一次持久化已經900s了,如果有大於等於1個變更,才會snapshot
  9. save 900 1
  10. ##對於此值的設置,需要謹慎,評估系統的變更操作密集程度
  11. ##可以通過“save “””來關閉snapshot功能
  12.  
  13. save 300 10 #表示間隔達到300s時如果更改了10次以上,就snapshot,如果你在10s時已經10次了,立馬持久化
  14. save 60 10000 #同理間隔達到60s時如果更改了10000次以上,就snapshot,如果你在10s時已經10000次了,立馬持久化
  15.  
  16. ##當snapshot時出現錯誤無法繼續時,是否阻塞客戶端“變更操作”,“錯誤”可能因為磁盤已滿/磁盤故障/OS級別異常等
  17. stop-writes-on-bgsave-error yes
  18. ##是否啟用rdb文件壓縮,默認為“yes”,壓縮往往意味着“額外的cpu消耗”,同時也意味這較小的文件尺寸以及較短的網絡傳輸時間
  19. rdbcompression yes

這里我將save 300 10改為save 30 10進行測試一下(首先先停掉哨兵機制,不然宕機后他就重新選一台主的了):

改好后重啟一下服務:

  1. 進行 5次更改操作
  2. 192.168.245.136:6379> get name
  3. "1"
  4. 192.168.245.136:6379> set name 1
  5. OK
  6. 192.168.245.136:6379> set name 2
  7. OK
  8. 192.168.245.136:6379> set name 3
  9. OK
  10. 192.168.245.136:6379> set name 4
  11. OK
  12. 192.168.245.136:6379> set name 5
  13. OK
  14. 192.168.245.136:6379> get name
  15. "5"
  16.  
  17. #立馬kill掉redis進程,一定要kill如果主動關閉服務,他是會進行snapshot的
  18. [root@localhost bin] # ps -aux | grep 6379
  19. root 1572 0.1 0.9 140840 9640 ? Ssl 02:02 0:00 ./redis-server *:6379
  20. 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
  21. root 1580 0.0 0.0 112676 984 pts/1 R+ 02:03 0:00 grep --color=auto 6379
  22. [root@localhost bin] # kill -9 1572
  23.  
  24. #重啟服務
  25. [root@localhost bin] # ./redis-server ../etc/redis.conf
  26. [root@localhost bin] # ./redis-cli -h 192.168.245.136 -p 6379 -a "123456"
  27. 192.168.245.136:6379> get name
  28. "1"
  29. 192.168.245.136:6379>
  30.  
  31. #可以看到我最后的是“5”,而這里是“1”,數據丟失了##########################
  32. #更改10次呢?,是成功進行持久化了的,這里不展示了,篇幅過大。
  33. #而且如果你將dump.rdb文件刪除后,達到snapshot條件時,會自動創建一個新的文件,持久化其實就是將該文件備份,下次將那些持久化后的文件再放過來不就達到數據恢復了嗎????????????

2.6.2 AOF存儲

以日志文件方式存儲,其實就是將你“操作+數據”指令格式化后追加到操作日志文件的尾部,必須append(已經寫入到文件或者即將寫入),才會進行數據的實際變更。“日志文件”保存了歷史所有的操作過程;當 server 需要數據恢復時,可以直接 replay 此日志文件,即可還原所有的操作過程。內容是字符串,容易閱讀和解析。

缺點:AOF 文件比 RDB 文件大,且恢復速度慢。

只會記錄“變更操作”(例如:set/del 等),如果 server 中持續的大量變更操作,將會導致 AOF 文件非常的龐大,意味着 server 失效后,數據恢復的過程將會很長;事實上,一條數據經過多次變更,將會產生多條 AOF 記錄,其實只要保存當前的狀態,歷史的操作記錄是可以拋棄的;因為 AOF 持久化模式還伴生了“AOF rewrite”。

因為最多丟失最后一次寫入文件的數據,所以很好修復,直接手工更改文件或者重新來一次即可。

修改redis.conf文件:

  1. ##此選項為aof功能的開關,默認為“no”,可以通過“yes”來開啟aof功能
  2. ##只有在“yes”下,aof重寫/文件同步等特性才會生效
  3. appendonly yes
  4.  
  5. ##指定aof文件名稱
  6. appendfilename appendonly.aof
  7.  
  8. ##指定aof操作中文件同步策略,有三個合法值:always everysec no,默認為everysec每秒
  9. appendfsync everysec
  10. ##在aof-rewrite期間,appendfsync是否暫緩文件同步,"no"表示“不暫緩”,“yes”表示“暫緩”,默認為“no”
  11. no-appendfsync- on-rewrite no
  12.  
  13. ##aof文件rewrite觸發的最小文件尺寸(mb,gb),只有大於此aof文件大於此尺寸是才會觸發rewrite,默認“64mb”,建議“512mb”
  14. auto-aof-rewrite-min-size 64mb
  15.  
  16. ##相對於“上一次”rewrite,本次rewrite觸發時aof文件應該增長的百分比。
  17. ##每一次rewrite之后,redis都會記錄下此時“新aof”文件的大小(例如A),那么當aof文件增長到A*(1 + p)之后
  18. ##觸發下一次rewrite,每一次aof記錄的添加,都會檢測當前aof文件的尺寸。
  19. auto-aof-rewrite-percentage 100
  20.  

重啟服務后,根據你啟動redis所在目錄下出現appendonly.aof文件。

執行以下操作:

  1. 192 .168.245.136:6379> set name 124
  2. OK
  3. 192 .168.245.136:6379> set name 145
  4. OK
  5. 192 .168.245.136:6379> set age 56
  6. OK
  7. 192 .168.245.136:6379> del age
  8. ( integer) 1
  9. 192 .168.245.136:6379> get name
  10. "145"
  11.  

查看aof文件內容:

  1. [root@localhost bin] # cat appendonly.aof
  2. *2
  3. $6
  4. SELECT
  5. $1
  6. 0
  7. *3
  8. $3
  9. set
  10. $4
  11. name
  12. $3
  13. 124
  14. *3
  15. $3
  16. set
  17. $4
  18. 。。。

查詢的是不會出現在里面,而且這個文件是動態的。。看起來挺好的,就是頻繁的更改造成文件過大,到時候恢復起來有點太慢了,有人說那么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 
取消 WATCH 命令對所有 key 的監視。

5

WATCH key [key ...] 
監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那么事務將被打斷。

2.8 發布訂閱

Redis 發布訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。Redis 客戶端可以訂閱任意數量的頻道

下圖展示了頻道 channel1 , 以及訂閱這個頻道的三個客戶端 —— client2 、 client5 和 client1 之間的關系:

當有新消息通過 PUBLISH 命令發送給頻道 channel1 時, 這個消息就會被發送給訂閱它的三個客戶端:

例子:

1、創建訂閱頻道名為 redisChatSimple

  1. 192 .168.245.136:6379> subscribe redsiChatSample
  2. Reading messages... (press Ctrl-C to quit)
  3. 1) " subscribe"
  4. 2) " redsiChatSample"

2、重新開個客戶端,同一頻道發布消息

  1. 192 .168.245.136:6379> publish redsiChatSample "gogog"
  2. ( integer) 1
  3. 192 .168.245.136:6379> publish redsiChatSample "gogog22"
  4. ( integer) 1
  5. 192 .168.245.136:6379>

3、客戶端顯示如下信息:

  1. 192.168.245.136:6379> subscribe redsiChatSample
  2. Reading messages... (press Ctrl-C to quit)
  3. 1) "subscribe"
  4. 2) "redsiChatSample"
  5. 3) (integer) 1
  6. 1) "message"
  7. 2) "redsiChatSample"
  8. 3) "gogog"
  9. 1) "message"
  10. 2) "redsiChatSample"
  11. 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 ...]] 
指退訂給定的頻道


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM