-
NOSQL類型簡介
- 鍵值對:會使用到一個哈希表,表中有一個特定的鍵和一個指針指向特定的數據,如redis,voldemort,oracle bdb
- 列存儲數據庫,應對分布式存儲的海量數據,鍵仍然在,但是指向了多個列,如HBase,Riak
- 文檔型數據庫:數據模型是版本化的文檔,半結構化的文檔以特定的格式存儲,如json,文檔型數據庫可以看成是鍵值型數據的升級版,允許之間嵌套鍵值,而且文檔型數據庫比鍵值數據庫的查詢效率更高,如CouchDB,MongoDb
- 圖形數據庫,使用靈活的圖形模型,并且能擴展到多個服務器上,Neo4J,InfoGrid,InfiniteGraph
-
NOSQL特點
- 數據模型簡單
- 性能較高
- 不需要高度的數據一致性
-
優缺點
- 優點
- 對數據的高并發讀寫
- 對海量數據的高效率存儲和訪問
- 對數據的高擴展性和高可用性
- 缺點
- redis的acid關系非常簡單,無法做到太復雜的關系數據庫模型
- 優點
-
可擴展性和可用性
- 可擴展性
- 垂直擴展(容量不夠了加內存)
- 水平擴展(容量不夠了往集群里加機器)
- 可用性
- 單節點掛了,節點之間迅速切換
- 可靠性
- 只要刷盤了,數據不丟失
- 可擴展性
-
redis以key-value形式存儲,data structure service 數據結構服務器,支持各種不同方式的排序,數據都是緩存在內存中,它也可以周期性的將更新的數據寫入到磁盤或者把修改操作寫入追加到文件。redis的數據持久化主要是rdb和aof
- rdb,周期性的將數據刷入到磁盤(默認開啟rdb)
- aof,將更改操作寫入到日志文件,相當于數據庫的undo日志,開aof,多線程并發的去訪問redis集群,redis的寫的性能大大降低,怎么解決呢?
- 1.多幾個主節點,分擔寫壓力
- 2.使用ssdb(寫性能高)寫,用redis讀,技術的合并
-
redis的三種集群方式
- 主從模式
- 主節點可寫,其他節點均高并發可讀,如果主節點掛了,那么整個redis服務不可用,主從節點數據完全一致
- 哨兵模式
- 主從模式上另加一個哨兵進行監控,如果主節點掛了,哨兵服務會對從節點之間進行選舉出一個主節點 實現高可用,自動切換主從,如果主節點再次加入進來,成為一個從節點。也就是說任意時間內,只有一個主節點,主從節點數據完全一致
- 集群模式
- 多主節點,并對數據進行分布式存儲,主從節點數據不一樣
- 主從模式
-
reids和memcache
- redis多實例化是串行化執行,多實例化性能比較高,群狼戰猛虎
- memcache多實例是并行化執行,單節點性能比較高
-
簡單安裝
- 編譯安裝make && make install(請先安裝gcc,如果出現
jemalloc/jemalloc.h: No such file or directory
,請使用make MALLOC=libc && make install) - mkdir -p /usr/local/redis/etc
- mkdir -p /usr/local/redis/bin
- cp redis源碼目錄/redis.conf /usr/localredis/etc
- cp redis源碼目錄/src mkreleasehdr.sh redis-cli redis-server /usr/local/redis/bin
- 修改/usr/local/reids/etc/redis.conf里 daemonize改為 yes
- 啟動 /usr/local/redis/bin/redis-server /usr/local/reids/etc/redis.conf
- netstat -tunpl | grep 6379
- 停止redis:/usr/local/redis/bin/redis-cli shutdown 或者pkill redis-server,或者直接kill redis 進程號
- 只要dump.rdb文件不刪,關閉操作之前的數據還存在
- 編譯安裝make && make install(請先安裝gcc,如果出現
-
基礎數據類型
- String
- 鍵和值都是字符串
- set key value 不用加雙引號
- get key 獲取值
- 多次設置同key的value會覆蓋
- setnx key value 不存在則設置值
- del key 刪除鍵值
- setex key 有效期時間(單位:秒) value,鍵值有效期之后自動失效
- setrange key 第幾個位置開始替換(索引從0開始) 替換字符串,從索引位開始替換成替換字符串長度的替換字符串
- mset key1 key1value key2 keyn valuen 多次 設置值
- mget key1 key2 keyn 多次獲取值
- getset key value 返回key的舊值并將該key設置為新值
- incr | decr key 對一個值進行自增和遞減
- incrby | decrby key 步長 對一個值進行步長自增和遞減
- append key val 給key的value追加val的字符串
- strlen key 返回key對于的字符串長度
- Hash
- 非常常用適合存儲對象,將一個對象類型存儲在hash要比存儲在string類型里占用更小的內存空間
- hset hash_name key value 存一個鍵值對到名為hash_name的Hash里
- hget hash_name key 從hash_name的hash里取key的value
- hmget | hmset hash_name key1 key1value key2 keyn valuen 多次 獲取或設置值
- hsetnx hash_name key val 不存在則設置為val值
- hincrby | hdecrby hash_name 步長 集合的步長遞增和遞減
- hexists hash_name key 集合里是否存在key,不存在返回0
- hlen hash_name 集合里的所有鍵的數量
- hdel hash_name key 刪除名稱為hash_name 的hash里的key對應的鍵值
- hkeys hash_name 獲取hash里的所有key
- hvals hash_name 獲取hash里的所有value
- hgetall hash_name 獲取hash里的所有key,value
- List
- 鏈表結構,雙端鏈表,既可以成為棧,也可以成為隊列,元素可以重復
- lpush 從頭部加入元素,先進后出,lpush list_name value
- rpush 從尾部加入元素,先進先出,rpush list_name value
- Irange list_name 0 -1 到名為list_name的list里從0開始,取出所有元素,-1為取出所有,即到直到沒有元素位置
- linsert list_name before "value" "insert_value" 在value值之前插入insert_value
- lset list_name 下標 "value" 將指定下標的值替換為value
- lrem list_name 刪除個數 "value" 從指定下標開始,刪除n個 值為value的元素
- ltrim list_name 下標 上標 保留從下標到上標的元素
- lpop list_name 從list的頭部刪除元素,并返回刪除元素
- rpop list_name 從list的尾部刪除元素,并返回刪除元素
- rpoplpush list_name_1 list_name_2 value 先從list_name_1中移除元素,再往list_name_2中的頭部添加元素
- lindex list_name index 返回index處的元素、
- llen list_name 返回元素的個數
- Set
- 元素不能重復,string類型的無序集合,通過hashtable實現,集合我們可以取交集并集和差集
- sadd set_name value 向set_name的set中添加value元素
- srem set_name value 移除set_name中的value元素
- spop set_name 移除并返回集合中的一個隨機元素
- smembers set_name 查看set里的所有元素
- sdiff set_name1 set_name2 取倆個集合的差集,那個集合在前面就以那個為標準
- sdiffstore set_name3 set_name1 set_name2 取set1和set2的差集并存儲到set3
- sinter key1 [key2] 返回給定所有集合的交集
- sinterstore set_name3 set_name1 set_name2 取set1和set2的交集并存儲到set3
- sunion key1 [key2] 返回給定所有集合的并集
- sunionstore set_name3 set_name1 set_name2 取set1和set2的并集并存儲到set3
- smove set_name1 set_name2 value 將set_name1中的元素value移動到set_name2中
- scard set_name 查看集合的元素個數
- sismember set_name value 查看value 是否在set中
- srandmember set_name 隨機返回一個set中的元素
- ZSet
- 有序的集合,
- zadd zset_name n value 向zset中的索引n中添加元素(value)
- zrange zset_name 0 -1 withscores 列出所有元素,索引從0開始到-1(所有),并顯示索引
- zrem set_name value 刪除zset中的元素value
- zincrby set_name 步長,以指定步長去自動遞增或遞減
- zrangebyscore set_name (1 5 返回所有符合條件 1 < score <= 5 的成員
- zremrangebyrank key start stop 移除有序集key中,指定排名(rank)區間內的所有成員。下標參數start和stop都以0為底,0處是分數最小的那個元素。這些索引也可是負數,表示位移從最高分處開始數。例如,-1是分數最高的元素,-2是分數第二高的,依次類推。
- zremrangebyscore set_name (start stop 刪除所有符合條件 start< score <= stop 的成員
- zrank set_name value 如果成員在有序集合存在,返回整數:成員的權重。如果成員在有序集合不存在或鍵不存在,字符串返回nil。
- zrevrank set_name value 返回有序集key中成員member的排名,其中有序集成員按score值從大到小排列。排名以0為底,也就是說,score值最大的成員排名為0。
使用ZRANK命令可以獲得成員按score值遞增(從小到大)排列的排名(順序)。 - zcard set_name 返回集合里所有元素的個數
- zcount set_name (1 5 返回所有符合條件 1 < score <= 5 的成員個數
- String
-
redis高級命令
- 所有的鍵: keys * 支持模糊查詢
- 是否存在指定鍵 exists key
- expire key time_out 設置某個key的過期時間,單位為秒,使用ttl key查看剩余時間,-2代表過期
- persist key 取消過期時間,執行之后再執行ttl返回-1
- select 選擇數據庫,數據庫為0-15(默認共16個庫),默認進入0庫,早期16個庫是為了安全,可以使用8個庫用作備份,看你怎么拆分,或者不同的數據類型放到不同的數據庫,現在已經不考慮,redis已經有集群實現
+move [key] [數據庫下標] 移動數據到其他庫 - randomkey隨機返回數據庫里的一個key
- rename key newkey 重命名key
- echo 打印命令
- dbsize 查看數據庫的key數量
- info 獲取數據庫信息
- config get key 實時傳儲收到的請求,返回相關的配置信息,
- config get * 返回所有配置
- flushdb 清空當前數據庫,
- flushall 清空所有數據庫
安全性
requirepass password 設置密碼后能登陸redis,但是訪問不了任何數據,登陸redis后如果要使用密碼,使用auth password,才能正常操作數據,或者使用redis-cli加上參數-a password 登陸-
redis主從復制
- 概念
- 一個Master可以擁有多個slave
- 多個slave可以連接同一個master,還可以連接其他slave,子節點的子節點
- 主從復制不會阻塞master,在同步數據時,master可以繼續處理client請求
- 提供系統的伸縮性
- 主從復制過程
- slave 與master建立連接之后,發送sync同步命令
- master會開啟一個后臺進程,將數據庫快照保存到文件中,同時master主進程會開始收集新的寫命令并緩存
- 后臺完成保存后,將文件發送給slave
- slave將文件保存到磁盤上
- 主從復制配置
- 如果機器架構一致,可以clone master上的redis
- 修改從服務器的redis配置文件/usr/local/redis/etc/redis.conf
- slaveof <masterip> <mastport>
- 如果主服務器有密碼:masterauth <master-password>
- 啟動master和slave
- 使用info查看role角色即可知道誰是主服務或從服務
- slave是不能執行寫操作的(set 操作),他是readonly的,slave的數據和master一致,在應用中,你只要創建一個master redis連接,寫在master,讀操作會自動負載到從節點
- 主節點掛了,整個集群不可用
- 概念
-
redis 哨兵模式
- 功能有倆點
- 監控主數據庫和從數據庫是否正常運行
- 主數據庫出現故障時,自動選舉一個從數據庫為主數據庫,實現自動切換
- 實現高可用性,沒有單點問題,掛了就掛了,可以自動切換
- 實現步驟
- copy redis源碼中的sentinel.conf 到一臺從數據庫服務器的/usr/local/redis/etc中
- 修改sentinel.conf
- sentinel monitor master_name ip port n 設置master的名稱,ip,端口,投票選舉次數,n票通過之后選舉為主節點
- sentinel down-after-milliseconds master_name 5000 默認是1s檢測一次,這里配置超時5000ms為宕機
- sentinel failover-timeout master_name 900000
- sentinel parallel-syncs master_name n 從節點數量
- sentinel can-failover master_name yes
- 啟動sentinel哨兵 /usr/local/redis/bin/redis-server /usr/local/redis/etc/sentinel.conf --sentinel &
- 查看哨兵相關信息 /usr/local/redis/bin/redis-cli -h sentinel_ip -p sentinel_port info Sentinel
- 哨兵服務可以放到主或從中,或者一臺單獨的機器,或者多個機器,避免單點故障
- 可以通過redis-cli shutdown將主節點down掉,再查看哨兵相關信息,可以查看到主節點已近切換了
- 將down掉的主節點重新啟動,連接redis-cli,執行info命令,可以看到角色已經變成了slave
- 功能有倆點
-
redis的簡單事務 (一般不使用)
- 使用multi 命令打開事務
- 執行crud操作
- exec命令執行,存儲到redis
- discard 命令回滾
- 注意reids事務里的操作如果出錯了,并不會自動回滾
-
持久化機制
- 默認是快照方式
- 將內存中的數據以快照的方式寫入到二進制文件中,默認為dump.rdb,可以通過配置設置自動做快照持久化的方式,我們可以配置redis在n秒內如果超過m個key被修改就自動做快照
- 快照設置
- save 900 1 900秒內如果有超過1個key被修改,則發起快照保存
- save 300 10 300秒內如果有超過10個key被修改,則發起快照保存
- 將內存中的數據以快照的方式寫入到二進制文件中,默認為dump.rdb,可以通過配置設置自動做快照持久化的方式,我們可以配置redis在n秒內如果超過m個key被修改就自動做快照
- append-only file (aof)
- 由于快照方式是在一定時間間隔之后做一次,所以有可能redis意外down掉之后,有可能會丟失一部分數據,aof方式有點像oracle 數據庫的undo日志,aof比快照方式有更好的持久化性,在使用aof的時候,redis會將每一次寫操作(除了查詢,其他有更改數據的操作)都通過write函數追加到命令中,當redis重新啟動的時候會重新執行aof文件中的寫命令重建數據庫內容,appendonly.aof,aof并不是立即寫在硬盤上,可以通過配置文件修改強制寫到硬盤中
- aof設置(生產環境推薦使用)
-
appendonly yes
//啟動aof持久化方式有三種方式 -
# appendfsync always
//收到寫命令就立即寫入磁盤,效率最慢,但是保證完全的持久化(生產使用) -
# appendfsync everysec
//每秒鐘寫入磁盤一次 -
# appendfsync no
//完全依賴os,性能最好,持久化沒保證
-
- 默認是快照方式
-
redis 發布與訂閱信息
- redis提供了簡單的發布訂閱功能
- 使用subscribe [頻道] 進行訂閱監聽
- 使用publish [頻道] [發布內容] 進行發布消息廣播
-
redis與java的使用
- 在jedis的jar中,有Jedis對象,
- Jedis對象
import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.junit.Test; import redis.clients.jedis.Jedis; public class testRedis { private static Jedis jedis = new Jedis("192.168.1.101",6379); /** * @param args */ @Test public void test() { // TODO Auto-generated method stub // jedis.set("sex","man"); // System.out.println(jedis); // // List<String> list = jedis.mget("name","age","sex"); // for (Iterator iterator = list.iterator(); iterator.hasNext();) { // String string = (String) iterator.next(); // System.out.println(string); // } // // Map<String, String> user = new HashMap<String, String>(); // user.put("name","huangyuxuan"); // user.put("age", "0.5"); // user.put("sex","男"); // jedis.hmset("user",user); // // List<String> rsmap = jedis.hmget("user", "name", "age","sex"); // System.out.println(rsmap); // // 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 // //testStr(); //testList(); testSet(); // Iterator<String> iter=jedis.hkeys("user").iterator(); // while (iter.hasNext()){ // String key = iter.next(); // System.out.println(key+":"+jedis.hmget("user",key)); // } } public static void testStr(){ //-----添加數據---------- jedis.set("name","bhz");//向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","bhz","age","27","qq","174754613"); jedis.incr("age"); //進行加1操作 System.out.println(jedis.get("name") + "-" + jedis.get("age") + "-" + jedis.get("qq")); } public static 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)); } public static void testSet(){ //添加 jedis.sadd("user1","liuling"); jedis.sadd("user1","xinxin"); jedis.sadd("user1","ling"); jedis.sadd("user1","zhangxinxin"); jedis.sadd("user1","who"); //移除noname jedis.srem("user1","who"); System.out.println(jedis.smembers("user1"));//獲取所有加入的value System.out.println(jedis.sismember("user1", "who"));//判斷 who 是否是user集合的元素 System.out.println(jedis.srandmember("user1")); System.out.println(jedis.scard("user1"));//返回集合的元素個數 } }
-
redis 集群搭建(哨兵模式的問題是在選舉程序執行時,redis不可寫)
- redis3.0開始支持集群容錯功能,集群至少要三個master,3個slave
- 此處是同一臺機器開6個redis,模擬集群
- 創建文件夾:mkdir -p /usr/local/redis-cluster
- cd /usr/local/redis-cluster
- mkdir 7001,mkdir 7002,mkdir 7003,mkdir 7004,mkdir 7005,mkdir 7006,
- 拷貝redis配置文件到7001到7006文件夾,修改配置:
daemonize yes
port 700*
bind 當前機器ip
-
dir /usr/local/redis-cluster/700*
必須指定,分布式的數據每臺機器存的數據都不一樣的 cluster-enabled yes
cluster-config-file nodes-700*.conf
cluster-node-timeout 5000
appendonly yes
- 由于redis集群需要使用ruby命令,所以要安裝ruby,
yum install ruby
yum install rubygems
-
gem install redis
安裝redis和ruby的接口 - 啟動redis實例,
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/700*/redis.conf
-
ps -ef | grep redis
看6個redis是否啟動成功 - 切換到redis源碼目錄,拷貝redis源碼目錄下的redis-trib.rb到/usr/local/redis/bin/,
cp redis-trib.rb /usr/local/redis/bin/
/usr/local/redis/bin/redis-trib.rb create --replicas 1 ip:7001 ip:7002 ip:7003 ip:7004 ip:7005 ip:7006
- redis-trib.rb的更多信息請執行
/usr/local/redis/bin/redis-trib.rb --help
,--replicas
參數后面跟的1是指主節點數/從節點數的比值,比如6個master,12個slave,這個值就應該是0.5,假設有m個節點,比值為k,a為master的個數,b為slave的個數,即a/b = k, a+b= m,所以a=bk,bk+b=m,(k+1)b=m,所以b=m/(k+1),a=m-b,則命令行參數中的前a個為master,后m-a個為slave,slave依據比值,比如0.5,那么就先給第一個master分配倆臺slave,剩余的分配給下面的master,集群中每個master都有slots(槽),slave沒有,說明不可寫 - 至此集群環境已經搭建好了,如何驗證?連接任意一客戶端:
/usr/local/redis/bin/redis-cli -c -h ip -p port
(-c 代表集群模式,指定ip和端口),驗證命令:cluster info
(查看集群信息)cluster nodes
(查看節點列表) - 數據操作驗證,往redis集群中的一個節點放一條數據,數據放的地方不一定redis-cli是登陸的機器,具體存放在哪,在你set的時候,會有信息提示被定向到哪了,讀數據會自動定向到所在的節點,但是你在執行flushall的時候要去每個主節點執行該命令,集群中所有的數據才會刪除
- 關閉集群需要逐個進行關閉,使用命令:
/usr/local/redis/bin/redis-cli -c -h ip -p port shutdown
,如果要重新啟動集群,將redis逐個啟動就好 - 如果出現集群無法啟動時,刪除
cluster-config-file
文件(集群配置文件),注意redis.conf,.rdb,.aof不能刪,再次重新啟動每一個redis 服務,重新構建集群環境 - 更多參考redis-trib.rb,推薦博客
-
redis-trib 命令詳解
-
/usr/local/redis/bin/redis-trib.rb create --replicas 1 ip:port ip:port ...
命令會創建集群信息,并將集群信息存儲到cluster-config-file
- 關閉集群需要逐個進行關閉,使用命令:
/usr/local/redis/bin/redis-cli -c -h ip -p port shutdown
,如果要重新啟動集群,將redis逐個啟動就好 - 查看集群節點信息:
/usr/local/redis/bin/redis-cli cluster nodes
- 查看集群信息:
/usr/local/redis/bin/redis-cli cluster info
- redis-trib 添加節點
- 我們新建倆個服務,按照之前搭建的集群方式新增倆個節點:(一主一從 master、slave) Master:7007,Slave:7008
- cd /usr/local/redis-cluster
- mkdir 7007,mkdir 7008
- 拷貝redis配置文件到7007到7008文件夾,修改配置:
daemonize yes
port 700*
bind 當前機器ip
-
dir /usr/local/redis-cluster/700*
必須指定,分布式的數據每臺機器存的數據都不一樣的 cluster-enabled yes
cluster-config-file nodes-700*.conf
cluster-node-timeout 5000
appendonly yes
- 啟動7007和7008,
/usr/local/redis/bin/redis-server /usr/local/redis-cluster/700*(7和8)/redis.conf
- redis-trib 命令:
- create:創建一個集群環境host1:port1 ... hostN:portN(集群中的主從節點比例)
- call:可以執行redis命令
- add-node:將一個節點添加到集群里,第一個參數為新節點的ip:port,第二個參數為集群中任意一個已經存在的節點的ip:port
- del-node:移除一個節點
- reshard:重新分片
- check:檢查集群狀態
- 新增一個主節點7007(master)
/usr/local/redis3.0/src/redis-trib.rb add-node 192.168.1.171:7007(要加的master的ip:port) 192.168.1.171:7001(集群內已知的任意一master的ip:port)
- 查看集群信息:
/usr/local/redis/bin/redis-cli cluster nodes
,記錄下新的節點的node id - 新增的節點不會有任何數據,因為它沒有分配任何的slots,槽,需要手工分配
- 分配槽,
/usr/local/redis3.0/src/redis-trib.rb reshard 192.168.1.171:7001(集群內的任意一master)
,命令執行時會詢問要分配多少個槽到新的master:How many slots do you want to move (from 1 to 16384)?
可以自己設置,比如200,或者更多,接下來會詢問What is the receiving node ID?
,輸入上個命令記錄下的新節點的node id,下面會要求輸入source node
,可以輸入all,意思是從所有的主節點中抽取相應的槽數到指定的新節點中,Do you want to proceed with the proposed reshard plan (yes/no)?
直接輸入yes,執行分片計劃 - 查看集群信息:
/usr/local/redis/bin/redis-cli cluster nodes
,可以看見新的master已經有槽數了
- 添加從節點(7008)
/usr/local/redis3.0/src/redis-trib.rb add-node 192.168.1.171:7008(要加的slave) 192.168.1.171:7001(集群內的任意一master)
- 查看集群信息:
/usr/local/redis/bin/redis-cli cluster nodes
,記錄下7008要slave的節點的node id,即7008的master 節點(7007)的節點id,另外還可以看到這還是一個master,沒有分配任何的槽, - 主節點轉換為從節點:
首先需要登錄新加的7008節點的客戶端,然后使用集群命令進行操作,把當前的 7008(slave)節點指定到一個主節點下(這里使用之前創建的7007主節點) [root@micocube ~]# /usr/local/redis/bin/redis-cli -c -h 192.168.1.171 -p 7008 192.168.1.171:7008> cluster replicate master(7007)節點id 192.168.1.171:7008> OK(提示OK則操作成功)
- 查看集群信息:
/usr/local/redis/bin/redis-cli cluster nodes
,可以看到7008已經變成slave了,我們可以對集群進行操作,來驗證下是否可以進行讀寫(當然可以)。
- redis-trib 刪除節點
- 移除從節點
- /usr/local/redis3.0/src/redis-trib.rb
del-node 192.168.1.171:7008 node_id(7008的節點id),執行命令之后,7008會從集群中移除,7008的服務也會被銷毀,ps -ef | grep redis是看不到7008的redis的
- /usr/local/redis3.0/src/redis-trib.rb
- 移除主節點
- 主節點稍微麻煩一點,因為我們給它分配了slots(槽),所以我們這里必須先把7007里的slot槽放入到其他的可用主節點中去,然后再進行移除節點操作才行,不然會出現數據丟失問題.
- 刪除7007(master)節點之前,我們需要先把其全部的數據(slot槽)移動到其他節點上去(目前只能把master的數據遷移到一個節點上,暫時做不了平均分配功能),
/usr/local/redis3.0/src/redis-trib.rb reshard 192.168.1.171:7007
輸出如下
[root@micocube 7001]# /usr/local/redis3.0/src/redis-trib.rb reshard 192.168.1.171:7007 >>> Performing Cluster Check (using node 192.168.1.171:7007) M: 382634a4025778c040b7213453fd42a709f79e28 192.168.1.171:7007 slots:0-65,5461-5527,10923-10988 (199 slots)[注意這里,7007只有199個槽] master 0 additional replica(s) S: fa299e41c173fa807ba04684c2f5e5e185d5f7d0 192.168.1.171:7006 slots: (0 slots) slave replicates 83df08875c7707878756364039df0a4c8658f272 S: a69b98937844c6050ee5885266ccccb185a3f36a 192.168.1.171:7004 slots: (0 slots) slave replicates 614d0def75663f2620b6402a017014b57c912dad M: 614d0def75663f2620b6402a017014b57c912dad 192.168.1.171:7001 slots:66-5460 (5395 slots) master 1 additional replica(s) M: 8aac82b63d42a1989528cd3906579863a5774e77 192.168.1.171:7002 slots:5528-10922 (5395 slots) master 1 additional replica(s) S: adb99506ddccad332e09258565f2e5f4f456a150 192.168.1.171:7005 slots: (0 slots) slave replicates 8aac82b63d42a1989528cd3906579863a5774e77 M: 83df08875c7707878756364039df0a4c8658f272 192.168.1.171:7003 slots:10989-16383 (5395 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. How many slots do you want to move (from 1 to 16384)? 199 (注釋:這里不會是正好200個槽) What is the receiving node ID? 614d0def75663f2620b6402a017014b57c912dad (注釋:這里是需要把數據移動到哪?7001的主節點id) Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node #1:382634a4025778c040b7213453fd42a709f79e28 (注釋:這里是需要數據源,也就是我們的7007節點id) Source node #2:done (注釋:這里直接輸入done 開始生成遷移計劃) Ready to move 199 slots. Source nodes: M: 382634a4025778c040b7213453fd42a709f79e28 192.168.1.171:7007 slots:0-65,5461-5527,10923-10988 (199 slots) master 0 additional replica(s) Destination node: M: 614d0def75663f2620b6402a017014b57c912dad 192.168.1.171:7001 slots:66-5460 (5395 slots) master 1 additional replica(s) Resharding plan: Moving slot 0 from 382634a4025778c040b7213453fd42a709f79e28 ... Do you want to proceed with the proposed reshard plan (yes/no)? Yes (注釋:這里輸入yes開始遷移) Moving slot 0 from 192.168.1.171:7007 to 192.168.1.171:7001: ...
- 到此為止我們已經成功的把7007主節點的數據遷移到7001上去了,我們可以看一下現在的集群狀態如下圖,你會發現7007下面已經沒有任何數據(slot)槽了,證明遷移成功!
- 最后我們直接使用del-node命令刪除7007主節點即可。
[root@micocube 7001]# /usr/local/redis3.0/src/redis-trib.rb del-node
192.168.1.171:7007 382634a4025778c040b7213453fd42a709f79e28(7007的node id)
- 移除從節點
-
- java 操作redis集群
import java.util.HashSet; import java.util.Set; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisPoolConfig; public class TestClusterRedis { public static void main(String[] args) { Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>(); //下面是集群中各個節點的ip和端口,如果連不上關閉redis服務器的防火墻 jedisClusterNode.add(new HostAndPort("192.168.1.171", 7001)); jedisClusterNode.add(new HostAndPort("192.168.1.171", 7002)); jedisClusterNode.add(new HostAndPort("192.168.1.171", 7003)); jedisClusterNode.add(new HostAndPort("192.168.1.171", 7004)); jedisClusterNode.add(new HostAndPort("192.168.1.171", 7005)); jedisClusterNode.add(new HostAndPort("192.168.1.171", 7006)); //GenericObjectPoolConfig goConfig = new GenericObjectPoolConfig(); //JedisCluster jc = new JedisCluster(jedisClusterNode,2000,100, goConfig); JedisPoolConfig cfg = new JedisPoolConfig(); cfg.setMaxTotal(100); cfg.setMaxIdle(20); cfg.setMaxWaitMillis(-1); cfg.setTestOnBorrow(true); JedisCluster jc = new JedisCluster(jedisClusterNode,6000,1000,cfg); System.out.println(jc.set("age","20")); System.out.println(jc.set("sex","男")); System.out.println(jc.get("name")); System.out.println(jc.get("name")); System.out.println(jc.get("name")); System.out.println(jc.get("name")); System.out.println(jc.get("name")); System.out.println(jc.get("name")); System.out.println(jc.get("name")); System.out.println(jc.get("name")); System.out.println(jc.get("age")); System.out.println(jc.get("sex")); jc.close(); } }
- redis與spring的整合(applicationContext-cluster.xml)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath:redis.properties" /> <context:component-scan base-package="com.x.redis.dao"> </context:component-scan> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${redis.maxIdle}" /> <property name="maxTotal" value="${redis.maxActive}" /> <property name="maxWaitMillis" value="${redis.maxWait}" /> <property name="testOnBorrow" value="${redis.testOnBorrow}" /> </bean> <bean id="hostport1" class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="10.16.68.92" /> <constructor-arg name="port" value="7770" /> </bean> <bean id="hostport2" class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="10.16.68.92" /> <constructor-arg name="port" value="7771" /> </bean> <bean id="hostport3" class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="10.16.68.92" /> <constructor-arg name="port" value="7772" /> </bean> <bean id="hostport4" class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="10.16.68.92" /> <constructor-arg name="port" value="7773" /> </bean> <bean id="hostport5" class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="10.16.68.92" /> <constructor-arg name="port" value="7774" /> </bean> <bean id="hostport6" class="redis.clients.jedis.HostAndPort"> <constructor-arg name="host" value="10.16.68.92" /> <constructor-arg name="port" value="7775" /> </bean> <bean id="redisCluster" class="redis.clients.jedis.JedisCluster"> <constructor-arg name="nodes"> <set> <ref bean="hostport1" /> <ref bean="hostport2" /> <ref bean="hostport3" /> <ref bean="hostport4" /> <ref bean="hostport5" /> <ref bean="hostport6" /> </set> </constructor-arg> <constructor-arg name="timeout" value="6000" /> <constructor-arg name="poolConfig"> <ref bean="jedisPoolConfig" /> </constructor-arg> </bean> </beans>
- Redis配置文件詳解
# Redis 配置文件 # 當配置中需要配置內存大小時,可以使用 1k, 5GB, 4M 等類似的格式,其轉換方式如下(不區分大小寫) # # 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1mb => 1024*1024 bytes # 1g => 1000000000 bytes # 1gb => 1024*1024*1024 bytes # # 內存配置大小寫是一樣的.比如 1gb 1Gb 1GB 1gB # daemonize no 默認情況下,redis不是在后臺運行的,如果需要在后臺運行,把該項的值更改為yes daemonize yes # 當redis在后臺運行的時候,Redis默認會把pid文件放在/var/run/redis.pid,你可以配置到其他地址。 # 當運行多個redis服務時,需要指定不同的pid文件和端口 pidfile /var/run/redis.pid # 指定redis運行的端口,默認是6379 port 6379 # 指定redis只接收來自于該IP地址的請求,如果不進行設置,那么將處理所有請求, # 在生產環境中最好設置該項 # bind 127.0.0.1 # Specify the path for the unix socket that will be used to listen for # incoming connections. There is no default, so Redis will not listen # on a unix socket when not specified. # # unixsocket /tmp/redis.sock # unixsocketperm 755 # 設置客戶端連接時的超時時間,單位為秒。當客戶端在這段時間內沒有發出任何指令,那么關閉該連接 # 0是關閉此設置 timeout 0 # 指定日志記錄級別 # Redis總共支持四個級別:debug、verbose、notice、warning,默認為verbose # debug 記錄很多信息,用于開發和測試 # varbose 有用的信息,不像debug會記錄那么多 # notice 普通的verbose,常用于生產環境 # warning 只有非常重要或者嚴重的信息會記錄到日志 loglevel debug # 配置log文件地址 # 默認值為stdout,標準輸出,若后臺模式會輸出到/dev/null #logfile stdout logfile /var/log/redis/redis.log # To enable logging to the system logger, just set 'syslog-enabled' to yes, # and optionally update the other syslog parameters to suit your needs. # syslog-enabled no # Specify the syslog identity. # syslog-ident redis # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. # syslog-facility local0 # 可用數據庫數 # 默認值為16,默認數據庫為0,數據庫范圍在0-(database-1)之間 databases 16 ################################ 快照 ################################# # # 保存數據到磁盤,格式如下: # # save <seconds> <changes> # # 指出在多長時間內,有多少次更新操作,就將數據同步到數據文件rdb。 # 相當于條件觸發抓取快照,這個可以多個條件配合 # # 比如默認配置文件中的設置,就設置了三個條件 # # save 900 1 900秒內至少有1個key被改變 # save 300 10 300秒內至少有300個key被改變 # save 60 10000 60秒內至少有10000個key被改變 save 900 1 save 300 10 save 60 10000 # 存儲至本地數據庫時(持久化到rdb文件)是否壓縮數據,默認為yes rdbcompression yes # 本地持久化數據庫文件名,默認值為dump.rdb dbfilename dump.rdb # 工作目錄 # # 數據庫鏡像備份的文件放置的路徑。 # 這里的路徑跟文件名要分開配置是因為redis在進行備份時,先會將當前數據庫的狀態寫入到一個臨時文件中,等備份完成時, # 再把該該臨時文件替換為上面所指定的文件,而這里的臨時文件和上面所配置的備份文件都會放在這個指定的路徑當中。 # # AOF文件也會存放在這個目錄下面 # # 注意這里必須制定一個目錄而不是文件 dir ./ ################################# 復制 ################################# # 主從復制. 設置該數據庫為其他數據庫的從數據庫. # 設置當本機為slav服務時,設置master服務的IP地址及端口,在Redis啟動時,它會自動從master進行數據同步 # # slaveof <masterip> <masterport> # 當master服務設置了密碼保護時(用requirepass制定的密碼) # slav服務連接master的密碼 # # masterauth <master-password> # 當從庫同主機失去連接或者復制正在進行,從機庫有兩種運行方式: # # 1) 如果slave-serve-stale-data設置為yes(默認設置),從庫會繼續相應客戶端的請求 # # 2) 如果slave-serve-stale-data是指為no,出去INFO和SLAVOF命令之外的任何請求都會返回一個 # 錯誤"SYNC with master in progress" # slave-serve-stale-data yes # 從庫會按照一個時間間隔向主庫發送PINGs.可以通過repl-ping-slave-period設置這個時間間隔,默認是10秒 # # repl-ping-slave-period 10 # repl-timeout 設置主庫批量數據傳輸時間或者ping回復時間間隔,默認值是60秒 # 一定要確保repl-timeout大于repl-ping-slave-period # repl-timeout 60 ################################## 安全 ################################### # 設置客戶端連接后進行任何其他指定前需要使用的密碼。 # 警告:因為redis速度相當快,所以在一臺比較好的服務器下,一個外部的用戶可以在一秒鐘進行150K次的密碼嘗試,這意味著你需要指定非常非常強大的密碼來防止暴力破解 # # requirepass foobared # 命令重命名. # # 在一個共享環境下可以重命名相對危險的命令。比如把CONFIG重名為一個不容易猜測的字符。 # # 舉例: # # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 # # 如果想刪除一個命令,直接把它重命名為一個空字符""即可,如下: # # rename-command CONFIG "" ################################### 約束 #################################### # 設置同一時間最大客戶端連接數,默認無限制,Redis可以同時打開的客戶端連接數為Redis進程可以打開的最大文件描述符數, # 如果設置 maxclients 0,表示不作限制。 # 當客戶端連接數到達限制時,Redis會關閉新的連接并向客戶端返回max number of clients reached錯誤信息 # # maxclients 128 # 指定Redis最大內存限制,Redis在啟動時會把數據加載到內存中,達到最大內存后,Redis會先嘗試清除已到期或即將到期的Key # Redis同時也會移除空的list對象 # # 當此方法處理后,仍然到達最大內存設置,將無法再進行寫入操作,但仍然可以進行讀取操作 # # 注意:Redis新的vm機制,會把Key存放內存,Value會存放在swap區 # # maxmemory的設置比較適合于把redis當作于類似memcached的緩存來使用,而不適合當做一個真實的DB。 # 當把Redis當做一個真實的數據庫使用的時候,內存使用將是一個很大的開銷 # maxmemory <bytes> # 當內存達到最大值的時候Redis會選擇刪除哪些數據?有五種方式可供選擇 # # volatile-lru -> 利用LRU算法移除設置過過期時間的key (LRU:最近使用 Least Recently Used ) # allkeys-lru -> 利用LRU算法移除任何key # volatile-random -> 移除設置過過期時間的隨機key # allkeys->random -> remove a random key, any key # volatile-ttl -> 移除即將過期的key(minor TTL) # noeviction -> 不移除任何可以,只是返回一個寫錯誤 # # 注意:對于上面的策略,如果沒有合適的key可以移除,當寫的時候Redis會返回一個錯誤 # # 寫命令包括: set setnx setex append # incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd # sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby # zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby # getset mset msetnx exec sort # # 默認是: # # maxmemory-policy volatile-lru # LRU 和 minimal TTL 算法都不是精準的算法,但是相對精確的算法(為了節省內存),隨意你可以選擇樣本大小進行檢測。 # Redis默認的灰選擇3個樣本進行檢測,你可以通過maxmemory-samples進行設置 # # maxmemory-samples 3 ############################## AOF ############################### # 默認情況下,redis會在后臺異步的把數據庫鏡像備份到磁盤,但是該備份是非常耗時的,而且備份也不能很頻繁,如果發生諸如拉閘限電、拔插頭等狀況,那么將造成比較大范圍的數據丟失。 # 所以redis提供了另外一種更加高效的數據庫備份及災難恢復方式。 # 開啟append only模式之后,redis會把所接收到的每一次寫操作請求都追加到appendonly.aof文件中,當redis重新啟動時,會從該文件恢復出之前的狀態。 # 但是這樣會造成appendonly.aof文件過大,所以redis還支持了BGREWRITEAOF指令,對appendonly.aof 進行重新整理。 # 你可以同時開啟asynchronous dumps 和 AOF appendonly no # AOF文件名稱 (默認: "appendonly.aof") # appendfilename appendonly.aof # Redis支持三種同步AOF文件的策略: # # no: 不進行同步,系統去操作 . Faster. # always: always表示每次有寫操作都進行同步. Slow, Safest. # everysec: 表示對寫操作進行累積,每秒同步一次. Compromise. # # 默認是"everysec",按照速度和安全折中這是最好的。 # 如果想讓Redis能更高效的運行,你也可以設置為"no",讓操作系統決定什么時候去執行 # 或者相反想讓數據更安全你也可以設置為"always" # # 如果不確定就用 "everysec". # appendfsync always appendfsync everysec # appendfsync no # AOF策略設置為always或者everysec時,后臺處理進程(后臺保存或者AOF日志重寫)會執行大量的I/O操作 # 在某些Linux配置中會阻止過長的fsync()請求。注意現在沒有任何修復,即使fsync在另外一個線程進行處理 # # 為了減緩這個問題,可以設置下面這個參數no-appendfsync-on-rewrite # # This means that while another child is saving the durability of Redis is # the same as "appendfsync none", that in pratical terms means that it is # possible to lost up to 30 seconds of log in the worst scenario (with the # default Linux settings). # # If you have latency problems turn this to "yes". Otherwise leave it as # "no" that is the safest pick from the point of view of durability. no-appendfsync-on-rewrite no # Automatic rewrite of the append only file. # AOF 自動重寫 # 當AOF文件增長到一定大小的時候Redis能夠調用 BGREWRITEAOF 對日志文件進行重寫 # # 它是這樣工作的:Redis會記住上次進行些日志后文件的大小(如果從開機以來還沒進行過重寫,那日子大小在開機的時候確定) # # 基礎大小會同現在的大小進行比較。如果現在的大小比基礎大小大制定的百分比,重寫功能將啟動 # 同時需要指定一個最小大小用于AOF重寫,這個用于阻止即使文件很小但是增長幅度很大也去重寫AOF文件的情況 # 設置 percentage 為0就關閉這個特性 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb ################################## SLOW LOG ################################### # Redis Slow Log 記錄超過特定執行時間的命令。執行時間不包括I/O計算比如連接客戶端,返回結果等,只是命令執行時間 # # 可以通過兩個參數設置slow log:一個是告訴Redis執行超過多少時間被記錄的參數slowlog-log-slower-than(微妙), # 另一個是slow log 的長度。當一個新命令被記錄的時候最早的命令將被從隊列中移除 # 下面的時間以微妙微單位,因此1000000代表一分鐘。 # 注意制定一個負數將關閉慢日志,而設置為0將強制每個命令都會記錄 slowlog-log-slower-than 10000 # 對日志長度沒有限制,只是要注意它會消耗內存 # 可以通過 SLOWLOG RESET 回收被慢日志消耗的內存 slowlog-max-len 1024 ################################ VM ############################### ### WARNING! Virtual Memory is deprecated in Redis 2.4 ### The use of Virtual Memory is strongly discouraged. # Virtual Memory allows Redis to work with datasets bigger than the actual # amount of RAM needed to hold the whole dataset in memory. # In order to do so very used keys are taken in memory while the other keys # are swapped into a swap file, similarly to what operating systems do # with memory pages. # # To enable VM just set 'vm-enabled' to yes, and set the following three # VM parameters accordingly to your needs. vm-enabled no # vm-enabled yes # This is the path of the Redis swap file. As you can guess, swap files # can't be shared by different Redis instances, so make sure to use a swap # file for every redis process you are running. Redis will complain if the # swap file is already in use. # # The best kind of storage for the Redis swap file (that's accessed at random) # is a Solid State Disk (SSD). # # *** WARNING *** if you are using a shared hosting the default of putting # the swap file under /tmp is not secure. Create a dir with access granted # only to Redis user and configure Redis to create the swap file there. vm-swap-file /tmp/redis.swap # vm-max-memory configures the VM to use at max the specified amount of # RAM. Everything that deos not fit will be swapped on disk *if* possible, that # is, if there is still enough contiguous space in the swap file. # # With vm-max-memory 0 the system will swap everything it can. Not a good # default, just specify the max amount of RAM you can in bytes, but it's # better to leave some margin. For instance specify an amount of RAM # that's more or less between 60 and 80% of your free RAM. vm-max-memory 0 # Redis swap files is split into pages. An object can be saved using multiple # contiguous pages, but pages can't be shared between different objects. # So if your page is too big, small objects swapped out on disk will waste # a lot of space. If you page is too small, there is less space in the swap # file (assuming you configured the same number of total swap file pages). # # If you use a lot of small objects, use a page size of 64 or 32 bytes. # If you use a lot of big objects, use a bigger page size. # If unsure, use the default :) vm-page-size 32 # Number of total memory pages in the swap file. # Given that the page table (a bitmap of free/used pages) is taken in memory, # every 8 pages on disk will consume 1 byte of RAM. # # The total swap size is vm-page-size * vm-pages # # With the default of 32-bytes memory pages and 134217728 pages Redis will # use a 4 GB swap file, that will use 16 MB of RAM for the page table. # # It's better to use the smallest acceptable value for your application, # but the default is large in order to work in most conditions. vm-pages 134217728 # Max number of VM I/O threads running at the same time. # This threads are used to read/write data from/to swap file, since they # also encode and decode objects from disk to memory or the reverse, a bigger # number of threads can help with big objects even if they can't help with # I/O itself as the physical device may not be able to couple with many # reads/writes operations at the same time. # # The special value of 0 turn off threaded I/O and enables the blocking # Virtual Memory implementation. vm-max-threads 4 ############################### ADVANCED CONFIG ############################### # 當hash中包含超過指定元素個數并且最大的元素沒有超過臨界時, # hash將以一種特殊的編碼方式(大大減少內存使用)來存儲,這里可以設置這兩個臨界值 # Redis Hash對應Value內部實際就是一個HashMap,實際這里會有2種不同實現, # 這個Hash的成員比較少時Redis為了節省內存會采用類似一維數組的方式來緊湊存儲,而不會采用真正的HashMap結構,對應的value redisObject的encoding為zipmap, # 當成員數量增大時會自動轉成真正的HashMap,此時encoding為ht。 hash-max-zipmap-entries 512 hash-max-zipmap-value 64 # list數據類型多少節點以下會采用去指針的緊湊存儲格式。 # list數據類型節點值大小小于多少字節會采用緊湊存儲格式。 list-max-ziplist-entries 512 list-max-ziplist-value 64 # set數據類型內部數據如果全部是數值型,且包含多少節點以下會采用緊湊格式存儲。 set-max-intset-entries 512 # zsort數據類型多少節點以下會采用去指針的緊湊存儲格式。 # zsort數據類型節點值大小小于多少字節會采用緊湊存儲格式。 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 # Redis將在每100毫秒時使用1毫秒的CPU時間來對redis的hash表進行重新hash,可以降低內存的使用 # # 當你的使用場景中,有非常嚴格的實時性需要,不能夠接受Redis時不時的對請求有2毫秒的延遲的話,把這項配置為no。 # # 如果沒有這么嚴格的實時性要求,可以設置為yes,以便能夠盡可能快的釋放內存 activerehashing yes ################################## INCLUDES ################################### # 指定包含其它的配置文件,可以在同一主機上多個Redis實例之間使用同一份配置文件,而同時各個實例又擁有自己的特定配置文件 # include /path/to/local.conf # include /path/to/other.conf
- TomcatRedis 的session共享,一般不使用,只作為知識擴展,一般都會使用cas,而不是redis的session共享