1. Redis介紹
? Redis 是用C語(yǔ)言開(kāi)發(fā)的一個(gè)開(kāi)源的高性能鍵值對(duì)( key-value )內(nèi)存數(shù)據(jù)庫(kù),它是一種NoSQL 數(shù)據(jù)庫(kù)。它是單進(jìn)程單線(xiàn)程的內(nèi)存數(shù)據(jù)庫(kù)悲幅,所以說(shuō)不存在線(xiàn)程安全問(wèn)題。它可以支持并發(fā) 10W QPS廉嚼,所以說(shuō)性能非常優(yōu)秀趴泌。之所以單進(jìn)程單線(xiàn)程性能還這么好,是因?yàn)榈讓硬捎昧薎O 多路復(fù)用(NIO思想)煤伟。
? 相比Memcache這種專(zhuān)業(yè)緩存技術(shù)癌佩,它有更優(yōu)秀的讀寫(xiě)性能木缝,及豐富的數(shù)據(jù)類(lèi)型。它提供了五種數(shù)據(jù)類(lèi)型來(lái)存儲(chǔ)值:字符串類(lèi)型(string)围辙、散列類(lèi)型(hash)我碟、列表類(lèi)型(list)、集合類(lèi)型(set)姚建、有序集合類(lèi)型(sortedset矫俺、zset)。
1.1 Redis官網(wǎng)
? 官網(wǎng)地址:http://redis.io/
? 中文官網(wǎng)地址:http://www.redis.cn/
? 下載地址:http://download.redis.io/releases/
1.2 Redis發(fā)展歷史
? 2008年掸冤,意大利的一家創(chuàng)業(yè)公司Merzia 推出了一款基于MySQL 的網(wǎng)站實(shí)時(shí)統(tǒng)計(jì)系統(tǒng)LLOOGG 厘托,然而沒(méi)過(guò)多久該公司的創(chuàng)始人 Salvatore Sanfilippo 便 對(duì)MySQL 的性能感到失望,于是他決定親自為L(zhǎng)LOOGG 量身定做一個(gè)數(shù)據(jù) 庫(kù)稿湿,并于2009年開(kāi)發(fā)完成催烘,這個(gè)數(shù)據(jù)庫(kù)就是 Redis。
? 不過(guò)Salvatore Sanfilippo 并不滿(mǎn)足只將Redis 用于LLOOGG 這一款產(chǎn)品缎罢,而是希望更多的人使用它,于是在同一年 Salvatore Sanfilippo 將 Redis 開(kāi)源發(fā)布考杉,并開(kāi)始和Redis 的另一名主要的代碼貢獻(xiàn)者Pieter Noordhuis 一起繼續(xù)著 Redis 的開(kāi)發(fā)策精,直到今天。
? Salvatore Sanfilippo 自己也沒(méi)有想到崇棠,短短的幾年時(shí)間咽袜, Redis 就擁有了龐大的用戶(hù)群體。 Hacker News 在2012年發(fā)布了一份數(shù)據(jù)庫(kù)的使用情況調(diào)查枕稀,結(jié)果顯示有近12%的公司在使用Redis询刹。國(guó)內(nèi)如新浪微博、街旁網(wǎng)萎坷、知乎網(wǎng)凹联,國(guó)外如 GitHub 、 Stack Overflow 哆档、 Flickr 等都是 Redis 的用戶(hù)蔽挠。
? VMware 公司從2010年開(kāi)始贊助Redis 的開(kāi)發(fā), Salvatore Sanfilippo 和Pieter Noordhuis 也分別在3月和5 月加入 VMware 瓜浸,全職開(kāi)發(fā) Redis 澳淑。
2. Redis單機(jī)版安裝配置
2.1 Redis下載
官網(wǎng)地址:http://redis.io/
中文官網(wǎng)地址:http://www.redis.cn/
下載地址:http://download.redis.io/releases/
2.2 Redis安裝環(huán)境
? Redis 沒(méi)有官方的Windows 版本,所以建議在Linux 系統(tǒng)上安裝運(yùn)行插佛,我們使用CentOS 7 作為安裝環(huán)境杠巡。
2.3 Redis安裝
2.3.1 預(yù)安裝環(huán)境
由于在安裝過(guò)程中需要對(duì)源碼進(jìn)行編譯,而編譯依賴(lài) gcc 環(huán)境雇寇,并且我們需要從網(wǎng)絡(luò)上自動(dòng)下載文件的自由工具wget氢拥,使用以下命令來(lái)完成(yum 方式需要聯(lián)網(wǎng))蚌铜。
1 yum install -y gcc-c++
2 yum install -y wget
2.3.2 下載并解壓縮Redis 源碼壓縮包
1 cd /usr/local
2 wget http://download.redis.io/releases/redis-5.0.4.tar.gz
3 tar -zxf redis-5.0.4.tar.gz
2.3.3 編譯Redis 源碼
進(jìn)入到解壓的Redis文件目錄,然后輸入 make 命令進(jìn)行編譯:
1 cd /usr/local/redis-5.0.4
2 make
2.3.4 構(gòu)建安裝Redis
編譯完成之后兄一,還是在該目錄下輸入 make install 進(jìn)行構(gòu)建:該命令會(huì)生成 Redis的5個(gè)二進(jìn)制文件厘线,默認(rèn)是在 /usr/local/bin 路徑下,但是我們可以手動(dòng)指定生成的文件位置出革,將 make install 變成:
make install PREFIX=/usr/local/redis
2.4 Redis啟動(dòng)
2.4.1 前端啟動(dòng)
2.4.1.1 啟動(dòng)命令:
cd /usr/local/redis/bin
./redis-server
2.4.1.2 關(guān)閉命令:
ctrl+c
2.4.1.3 啟動(dòng)缺點(diǎn):
? 客戶(hù)端窗口關(guān)閉則redis-server 程序結(jié)束造壮,不推薦使用此方法
2.4.2 后端啟動(dòng)
2.4.2.1 拷貝Redis解壓目錄下的redis.conf 配置文件到Redis 安裝目錄下的bin 目錄
cp /usr/loca/redis-5.0.4/redis.conf /usr/local/redis/bin/
2.4.2.2 修改redis.conf
#將daemonize由no改為yes
daemonize yes
# 默認(rèn)綁定的是回環(huán)地址,不能被其他機(jī)器訪(fǎng)問(wèn)骂束,將其屏蔽
# bind 127.0.0.1
# 將protected-mode由yes改為no
protected-mode no
2.4.2.3 啟動(dòng)命令
./redis-server redis.conf
2.4.2.4 關(guān)閉命令
./redis-cli shutdown
2.4.2.5 其他命令說(shuō)明
redis-server :?jiǎn)?dòng)redis 服務(wù)
redis-cli :進(jìn)入redis 命令客戶(hù)端
redis-benchmark :性能測(cè)試工具
redis-check-aof :aof 文件檢查工具
redis-check-dump :rdb 文件檢查工具
redis-sentinel :?jiǎn)?dòng)哨兵監(jiān)控服務(wù)
3. Redis客戶(hù)端
3.1 Redis命令行客戶(hù)端
命令格式
./redis-cli -h 127.0.0.1 -p 6379
./redis-cli
Redis不僅可以使用命令來(lái)操作耳璧,而且可以使用程序客戶(hù)端操作。
3.2 Java客戶(hù)端Jedis
3.2.1 Jedis介紹
? 在官方網(wǎng)站里列一些Java的客戶(hù)端展箱,有Jedis旨枯、Redisson、Jredis混驰、JDBC-Redis等攀隔,其中官方推薦使用Jedis和Redisson。
3.2.2 添加依賴(lài)
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<!-- 單元測(cè)試Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 配置Maven的JDK編譯級(jí)別 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
3.2.3 單實(shí)例連接
@Test
public void testJedis() {
Jedis jedis = new Jedis("192.168.56.101", 6379);
jedis.set("test", "hello world, this is jedis client!");
String result = jedis.get("test");
jedis.close();
}
3.2.4 連接池連接
@Test
public void testJedisPool() {
//創(chuàng)建一連接池對(duì)象
JedisPool jedisPool = new JedisPool("192.168.56.101", 6379);
//從連接池中獲得連接
Jedis jedis = jedisPool.getResource();
String result = jedis.get("test") ;
System.out.println(result);
//關(guān)閉連接
jedis.close();
//關(guān)閉連接池
jedisPool.close();
}
3.2.5 連接redis集群
@Test
public void testJedisCluster() throws Exception {
//創(chuàng)建一連接栖榨,JedisCluster對(duì)象,在系統(tǒng)中是單例存在
Set<HostAndPort> nodes = new HashSet<>();
//此時(shí)連接的是所有的主從節(jié)點(diǎn)昆汹,若使用哨兵機(jī)制的話(huà),連接的是哨兵婴栽,面會(huì)講到
nodes.add(new HostAndPort("192.168.56.101", 7001));
nodes.add(new HostAndPort("192.168.56.101", 7002));
nodes.add(new HostAndPort("192.168.56.101", 7003));
nodes.add(new HostAndPort("192.168.56.101", 7004));
nodes.add(new HostAndPort("192.168.56.101", 7005));
nodes.add(new HostAndPort("192.168.56.101", 7006));
JedisCluster cluster = new JedisCluster(nodes);
cluster.set("cluster-test", "my jedis cluster test");
String result = cluster.get("cluster-test");
System.out.println(result);
cluster.close();
}
3.2.6 spring整合Jedis
3.2.6.1 配置spring配置文件applicationContext.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 連接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大連接數(shù) -->
<property name="maxTotal" value="30" />
<!-- 最大空閑連接數(shù) -->
<property name="maxIdle" value="10" />
<!-- 每次釋放連接的最大數(shù)目 -->
<property name="numTestsPerEvictionRun" value="1024" />
<!-- 釋放連接的掃描間隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<!-- 連接最小空閑時(shí)間 -->
<property name="minEvictableIdleTimeMillis" value="1800000" />
<!-- 連接空閑多久后釋放, 當(dāng)空閑時(shí)間>該值 且 空閑連接>最大空閑連接數(shù) 時(shí)直接釋放 --> <property name="softMinEvictableIdleTimeMillis" value="10000" />
<!-- 獲取連接時(shí)的最大等待毫秒數(shù),小于零:阻塞不確定的時(shí)間,默認(rèn)-1 -->
<property name="maxWaitMillis" value="1500" />
<!-- 在獲取連接的時(shí)候檢查有效性, 默認(rèn)false -->
<property name="testOnBorrow" value="true" />
<!-- 在空閑時(shí)檢查有效性, 默認(rèn)false -->
<property name="testWhileIdle" value="true" />
<!-- 連接耗盡時(shí)是否阻塞, false報(bào)異常,ture阻塞直到超時(shí), 默認(rèn)true -->
<property name="blockWhenExhausted" value="false" />
</bean>
<!-- redis單機(jī) 通過(guò)連接池 -->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool" destroy-method="close">
<constructor-arg name="poolConfig" ref="jedisPoolConfig" />
<constructor-arg name="host" value="192.168.10.135" />
<constructor-arg name="port" value="6379" />
</bean>
<!-- redis集群 -->
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg index="0">
<set>
<bean class="redis.clients.jedis.HostAndPort"> <constructor-arg index="0" value="192.168.56.101"></constructor-arg> <constructor-arg index="1" value="7001"></constructor-arg> </bean>
<bean class="redis.clients.jedis.HostAndPort"> <constructor-arg index="0" value="192.168.56.101"></constructor-arg> <constructor-arg index="1" value="7002"></constructor-arg></bean>
<bean class="redis.clients.jedis.HostAndPort"><constructor-arg index="0" value="192.168.56.101"></constructor-arg><constructor-arg index="1" value="7003"></constructor-arg> </bean>
<bean class="redis.clients.jedis.HostAndPort"><constructor-arg index="0" value="192.168.56.101"></constructor-arg><constructor-arg index="1" value="7004"></constructor-arg> </bean>
<bean class="redis.clients.jedis.HostAndPort"><constructor-arg index="0" value="192.168.56.101"></constructor-arg> <constructor-arg index="1" value="7005"></constructor-arg></bean>
<bean class="redis.clients.jedis.HostAndPort"><constructor-arg index="0" value="192.168.56.101"></constructor-arg><constructor-arg index="1" value="7006"></constructor-arg> </bean>
</set>
</constructor-arg>
<constructor-arg index="1" ref="jedisPoolConfig"></constructor-arg>
</bean>
</beans>
3.2.6.2 測(cè)試代碼
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class TestJedis2 {
@Autowired private JedisPool jedisPool;
@Resource private JedisCluster cluster;
@Test public void testJedisPool() {
Jedis jedis = jedisPool.getResource();
String result = jedis.get("test");
System.out.println(result);
jedis.close();
}
@Test public void testJedisCluster() throws Exception {
cluster.set("cluster-test", "my jedis cluster test");
String result = cluster.get("cluster-test");
System.out.println(result);
}
}
4. Redis數(shù)據(jù)類(lèi)型
? Redis 中存儲(chǔ)數(shù)據(jù)是通過(guò)key-value 格式存儲(chǔ)數(shù)據(jù)的满粗,其中value 可以定義五種數(shù)據(jù)類(lèi)型:
String(字符類(lèi)型) Hash(散列類(lèi)型) List(列表類(lèi)型) Set(集合類(lèi)型)SortedSet(有序集合類(lèi)型,簡(jiǎn)稱(chēng)zset)
4.1 string類(lèi)型
SET/GET/GETSET:賦值/取值/取值并賦值
SET key value
GET key
GETSET key value
數(shù)值增減
? 當(dāng)value為整數(shù)數(shù)據(jù)時(shí)愚争,才能使用以下命令操作數(shù)值的增減映皆。
? 數(shù)值增減都是原子操作。
? redis中的每一個(gè)單獨(dú)的命令都是原子性操作轰枝。
? 當(dāng)多個(gè)命令一起執(zhí)行的時(shí)候捅彻,就不能保證原子性,不過(guò)我們可以使用事務(wù)和lua腳本來(lái)保證這一點(diǎn)
INCR/DECR:遞增/遞減
INCR key
DECR key
INCRBY/DECRBY:增加/減少指定的整數(shù)
INCRBY key increment
DECRBY key decrement
SETNX:僅當(dāng)不存在時(shí)賦值(使用該命令可以實(shí)現(xiàn)分布式鎖的功能)
SETNX key value
127.0.0.1:6379> EXISTS job
(integer) 0
127.0.0.1:6379> SETNX job "programmer"
(integer) 1 # 賦值成功
127.0.0.1:6379> SETNX job "code-farmer"
(integer) 0 # 賦值失敗
127.0.0.1:6379> GET job
"programmer"
APPEND:向尾部追加值鞍陨。如果鍵不存在則將該鍵的值設(shè)置為value 沟饥,即相當(dāng)于 SET key value 。返回值是追加后字符串的總長(zhǎng)度湾戳。
APPEND key value
STRLEN:獲取字符串長(zhǎng)度贤旷,返回鍵值的長(zhǎng)度,如果鍵不存在則返回0砾脑。
STRLEN key
MSET/MGET:同時(shí)設(shè)置/獲取多個(gè)鍵值
MSET key value [key value …]
MGET key [key …]
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> mget k1 k3
1) "v1"
2) "v3"
4.2 hash類(lèi)型
? hash 類(lèi)型也叫散列類(lèi)型幼驶,它提供了字段和字段值的映射。字段值只能是字符串類(lèi)型韧衣,不支持散列類(lèi)型盅藻、集合類(lèi)型等其它類(lèi)型购桑。如下:
賦值
? HSET 命令不區(qū)分插入和更新操作,當(dāng)執(zhí)行插入操作時(shí)HSET 命令返回 1 氏淑,當(dāng)執(zhí)行更新操作時(shí)返回 0 勃蜘。
HSET/HMSET 設(shè)置一個(gè)/多個(gè)字段值
HSET key field value
HMSET key field value [field value ...]
HSETNX:當(dāng)字段不存在時(shí)賦值,類(lèi)似HSET 假残,區(qū)別在于如果字段存在缭贡,該命令不執(zhí)行任何操作
HSETNX key field value
取值
HGET/HMGET/HGETALL:獲取一個(gè)/多個(gè)/所有字段值
HGET key field
HMGET key field [field ...]
HGETALL key
127.0.0.1:6379> hgetall user
1) "age"
2) "20"
3) "username" 5 4) "lisi"
HDEL:刪除字段』岳粒可以刪除一個(gè)或多個(gè)字段阳惹,返回值是被刪除的字段個(gè)數(shù)
HDEL key field [field ...]
HINCRBY:增加指定的整數(shù)
HINCRBY key field increment
HEXISTS:判斷字段是否存在
HEXISTS key field
127.0.0.1:6379> hexists user age
(integer) 1
127.0.0.1:6379> hexists user name
(integer) 0
HKEYS/HVALS:只獲取字段名或字段值
HKEYS key
HVALS key
127.0.0.1:6379> hmset user age 20 name lisi
OK
127.0.0.1:6379> hkeys user
1) "age"
2) "name"
127.0.0.1:6379> hvals user
1) "20"
2) "lisi"
HLEN:獲取字段數(shù)量
HLEN key
127.0.0.1:6379> hlen user
(integer) 2
HGETALL:獲得hash 的所有信息,包括key 和value
HGETALL key
string類(lèi)型和hash類(lèi)型的區(qū)別
? hash類(lèi)型適合存儲(chǔ)那些對(duì)象數(shù)據(jù)眶俩,特別是對(duì)象屬性經(jīng)常發(fā)生【增刪改】操作的數(shù)據(jù)莹汤。
? string類(lèi)型也可以存儲(chǔ)對(duì)象數(shù)據(jù),將java對(duì)象轉(zhuǎn)成json字符串進(jìn)行存儲(chǔ)颠印,這種存儲(chǔ)適合【查詢(xún)】操作纲岭。
4.3 list類(lèi)型
? Redis 的列表類(lèi)型( list 類(lèi)型)可以存儲(chǔ)一個(gè)有序的字符串列表 ,常用的操作是向列表兩端添加元素线罕,或者獲得列表的某一個(gè)片段荒勇。
? 列表類(lèi)型內(nèi)部是使用雙向鏈表( double linked list )實(shí)現(xiàn)的,所以向列表兩端添加元素的時(shí)間復(fù)雜度0(1) 闻坚,獲取越接近兩端的元素速度就越快。這意味著即使是一個(gè)有幾千萬(wàn)個(gè)元素的列表兢孝,獲取頭部或尾部的10條記錄也是極快的窿凤。
LPUSH/RPUSH:從列表兩端壓入元素
LPUSH key value [value ...]
RPUSH key value [value ...]
LRANGE:獲取列表中的某一片段。將返回start
跨蟹、stop
之間的所有元素(包含兩端的元素)雳殊,索引從0
開(kāi)始。索引可以是負(fù)數(shù)窗轩,如:“-1”代表最后邊的一個(gè)元素夯秃。
LRANGE key start stop
LPOP/RPOP:從列表兩端彈出元素
? 從列表左邊彈出一個(gè)元素,會(huì)分兩步完成:
? 第一步是將列表左邊的元素從列表中移除痢艺。第二步是返回被移除的元素值仓洼。
LPOP key
RPOP key
127.0.0.1:6379> lpop list:1
"3"
127.0.0.1:6379> rpop list:1
"6"
LLEN:獲取列表中元素的個(gè)數(shù)
llen key
LREM:刪除列表中前count 個(gè)值為value 的元素,返回實(shí)際刪除的元素個(gè)數(shù)堤舒。
? 根據(jù)count 值的不同色建,該命令的執(zhí)行方式會(huì)有所不同:
? 當(dāng)count>0時(shí), LREM會(huì)從列表左邊開(kāi)始刪除舌缤。
? 當(dāng)count<0時(shí)箕戳, LREM會(huì)從列表后邊開(kāi)始刪除某残。
? 當(dāng)count=0時(shí), LREM刪除所有值為value的元素陵吸。
LREM key count value
LINDEX:獲得指定索引的元素值
LINDEX key index
LSET:設(shè)置指定索引的元素值
LSET key index value
LTRIM:只保留列表指定片段玻墅,指定范圍和LRANGE一致
LTRIM key start stop
LINSERT: 向列表中插入元素。
? 該命令首先會(huì)在列表中從左到右查找值為pivot的元素壮虫,然后根據(jù)第二個(gè)參數(shù)是BEFORE還是AFTER來(lái)決定將value插入到該元素的前面還是后面
LINSERT key BEFORE|AFTER pivot value
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> linsert list after 3 4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "4"
3) "2"
4) "1"
RPOPLPUSH:將元素從一個(gè)列表轉(zhuǎn)移到另一個(gè)列表中
RPOPLPUSH source destination
127.0.0.1:6379> rpoplpush list newlist 2
"1"
127.0.0.1:6379> lrange newlist 0 -1
1) "1"
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "4"
3) "2"
4.4 set類(lèi)型
? set 類(lèi)型即集合類(lèi)型澳厢,其中的數(shù)據(jù)是不重復(fù)且沒(méi)有順序。
? 集合類(lèi)型和列表類(lèi)型的對(duì)比:
? 集合類(lèi)型的常用操作是向集合中加入或刪除元素旨指、判斷某個(gè)元素是否存在等赏酥,由于集合類(lèi)型的Redis 內(nèi)部是使用值為空的散列表實(shí)現(xiàn),所有這些操作的時(shí)間復(fù)雜度都為 0(1) 谆构。Redis 還提供了多個(gè)集合之間的交集裸扶、并集、差集的運(yùn)算搬素。
SADD/SREM:添加元素/刪除元素
SADD key member [member ...]
SREM key member [member ...]
SMEMBERS:獲得集合中的所有元素
SMEMBERS key
SISMEMBER:判斷元素是否在集合中
SISMEMBER key member
SDIFF:集合的差集運(yùn)算 A-B
SDIFF key [key ...]
127.0.0.1:6379> sadd setA 1 2 3
(integer) 3
127.0.0.1:6379> sadd setB 2 3 4
integer) 3
127.0.0.1:6379> sdiff setA setB
1) "1"
127.0.0.1:6379> sdiff setB setA
1) "4"
SINTER:集合的交集運(yùn)算 A ∩ B
SINTER key [key ...]
SUNION:集合的并集運(yùn)算 A ∪ B
SUNION key [key ...]
SCARD:獲得集合中元素的個(gè)數(shù)
SCARD key
127.0.0.1:6379> smembers setA
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> scard setA
(integer) 3
SPOP:從集合中彈出一個(gè)元素呵晨。由于集合是無(wú)序的,所有SPOP命令會(huì)從集合中隨機(jī)選擇一個(gè)元素彈出
SPOP key
127.0.0.1:6379> spop setA
"1"
4.5 zset類(lèi)型 (sortedset)
zset介紹
? 在set 集合類(lèi)型的基礎(chǔ)上熬尺,有序集合類(lèi)型(sortedset)為集合中的每個(gè)元素都關(guān)聯(lián)一個(gè)分?jǐn)?shù) 摸屠,這使得我們不僅可以完成插入、刪除和判斷元素是否存在在集合中粱哼,還能夠獲得分?jǐn)?shù)最高或最低的前N個(gè)元素季二、獲取指定分?jǐn)?shù)范圍內(nèi)的元素等與分?jǐn)?shù)有關(guān)的操作。
? 在某些方面有序集合(sortedset)和列表類(lèi)型(list)有些相似:
1.二者都是有序的揭措。
2.二者都可以獲得某一范圍的元素胯舷。
? 但是,二者有著很大區(qū)別:
1.列表類(lèi)型是通過(guò)鏈表實(shí)現(xiàn)的绊含,獲取靠近兩端的數(shù)據(jù)速度極快桑嘶,而當(dāng)元素增多后,訪(fǎng)問(wèn)中間數(shù)據(jù)的速度會(huì)變慢躬充。
2.有序集合類(lèi)型使用散列表實(shí)現(xiàn)逃顶,所以即使讀取位于中間部分的數(shù)據(jù)也很快。
3.列表中不能簡(jiǎn)單的調(diào)整某個(gè)元素的位置,但是有序集合可以(通過(guò)更改分?jǐn)?shù)實(shí)現(xiàn)) 。
4.有序集合要比列表類(lèi)型更耗內(nèi)存项郊。
ZADD:增加元素。向有序集合中加入一個(gè)元素和該元素的分?jǐn)?shù)妙蔗,如果該元素已經(jīng)存在則會(huì)用新的分?jǐn)?shù)替換原有的分?jǐn)?shù)。返回值是新加入到集合中的元素個(gè)數(shù)疆瑰,不包含之前已經(jīng)存在的元素眉反。
ZADD key score member [score member ...]
127.0.0.1:6379> zadd scoreboard 80 zhangsan 89 lisi 94 wangwu
(integer) 3
127.0.0.1:6379> zadd scoreboard 97 lisi
(integer) 0
ZRANGE/ZREVRANGE:獲得排名在某個(gè)范圍的元素列表昙啄。
? ZRANGE:按照元素分?jǐn)?shù)從小到大的順序返回索引從start到stop之間的所有元素(包含兩端的元素)
? ZREVRANGE:按照元素分?jǐn)?shù)從大到小的順序返回索引從start到stop之間的所有元素(包含兩端的元素)
ZRANGE key start stop [WITHSCORES]
ZREVRANGE key start stop [WITHSCORES]
127.0.0.1:6379> zrange scoreboard 0 2
1) "zhangsan"
2) "wangwu"
3) "lisi"
127.0.0.1:6379> zrevrange scoreboard 0 2
1) "lisi"
2) "wangwu"
3) "zhangsan"
? 如果需要獲得元素的分?jǐn)?shù)的可以在命令尾部加上WITHSCORES 參數(shù)
1127.0.0.1:6379> zrange scoreboard 0 1 WITHSCORES
1) "zhangsan"
2) "80"
3) "wangwu"
4) "94"
ZSCORE:獲取元素的分?jǐn)?shù)。
ZSCORE key member
127.0.0.1:6379> zscore scoreboard lisi
"97"
ZREM:刪除元素寸五。移除有序集合key中的一個(gè)或多個(gè)成員梳凛,不存在的成員將被忽略。當(dāng)key存在但不是有序集類(lèi)型時(shí)梳杏,返回一個(gè)錯(cuò)誤韧拒。
ZREM key member [member ...]
127.0.0.1:6379> zrem scoreboard lisi
(integer) 1
ZRANGEBYSCORE:獲得指定分?jǐn)?shù)范圍的元素。
ZRANGEBYSCORE key min max [WITHSCORES]
127.0.0.1:6379> ZRANGEBYSCORE scoreboard 90 97 WITHSCORES
1) "wangwu"
2) "94"
3) "lisi"
4) "97"
127.0.0.1:6379> ZRANGEBYSCORE scoreboard 70 100 limit 1 2
1) "wangwu"
2) "lisi"
ZINCRBY:增加某個(gè)元素的分?jǐn)?shù)十性,返回值是更改后的分?jǐn)?shù)
ZINCRBY key increment member
127.0.0.1:6379> ZINCRBY scoreboard 4 lisi
"101"
ZCARD:獲得集合中元素的數(shù)量叛溢。
ZCARD key
127.0.0.1:6379> ZCARD scoreboard
(integer) 3
ZCOUNT:獲得指定分?jǐn)?shù)范圍內(nèi)的元素個(gè)數(shù)
ZCOUNT key min max
127.0.0.1:6379> ZCOUNT scoreboard 80 90
(integer) 1
ZREMRANGEBYRANK:按照排名范圍刪除元素
ZREMRANGEBYRANK key start stop
127.0.0.1:6379> ZREMRANGEBYRANK scoreboard 0 1
(integer) 2
127.0.0.1:6379> ZRANGE scoreboard 0 -1
1) "lisi"
ZREMRANGEBYSCORE:按照分?jǐn)?shù)范圍刪除元素
ZREMRANGEBYSCORE key min max
127.0.0.1:6379> zadd scoreboard 84 zhangsan
(integer) 1
127.0.0.1:6379> ZREMRANGEBYSCORE scoreboard 80 100
(integer) 1
ZRANK/ZREVRANK: 獲取元素的排名。
ZRANK:從小到大
ZREVRANK:從大到小
ZRANK key member
ZREVRANK key member
127.0.0.1:6379> ZRANK scoreboard lisi
(integer) 0
127.0.0.1:6379> ZREVRANK scoreboard zhangsan
(integer) 1
4.6 通用命令
keys:返回滿(mǎn)足給定pattern 的所有key
keys pattern
127.0.0.1:6379> keys mylist*
1) "mylist"
2) "mylist5"
3) "mylist6"
4) "mylist7"
5) "mylist8"
del:刪除key
del key
exists:確認(rèn)一個(gè)key 是否存在 劲适。存在則返回1楷掉,不存在的返回0 。
exists key
Redis在實(shí)際使用過(guò)程中更多的用作緩存霞势。緩存數(shù)據(jù)一般需要設(shè)置生存時(shí)間烹植,到期后數(shù)據(jù)銷(xiāo)毀。
EXPIRE:設(shè)置key的生存時(shí)間(秒)
EXPIRE key seconds
PEXPIRE :設(shè)置key的生存時(shí)間(毫秒)
PEXPIRE key milliseconds
TTL :以秒為單位返回給定 key 的剩余生存時(shí)間(TTL, time to live)
TTL key
PTTL :以毫秒為單位返回 key 的剩余生存時(shí)間愕贡。
PTTL key
PERSIST :移除 key 的過(guò)期時(shí)間草雕,key 將持久保持。
PERSIST key
rename :重命名key
rename oldkey newkey
type:返回 key 所儲(chǔ)存的值的類(lèi)型固以。
type key
5 Redis數(shù)據(jù)結(jié)構(gòu)
? Redis在存儲(chǔ)對(duì)象時(shí)墩虹,并不是直接將數(shù)據(jù)扔進(jìn)內(nèi)存,而是會(huì)對(duì)對(duì)象進(jìn)行各種包裝:如redisObject憨琳、SDS等诫钓。
5.1 簡(jiǎn)單動(dòng)態(tài)字符串
? Redis 是用 C 語(yǔ)言寫(xiě)的,但是對(duì)于Redis的字符串栽渴,卻不是 C 語(yǔ)言中的字符串(即以空字符’\0’結(jié)尾的字符數(shù)組),它是自己構(gòu)建了一種名為 簡(jiǎn)單動(dòng)態(tài)字符串(simple dynamic string,SDS)的抽象類(lèi)型稳懒,并將 SDS 作為 Redis的默認(rèn)字符串表示闲擦。
SDS 定義:
struct sdshdr{
//記錄buf數(shù)組中已使用字節(jié)的數(shù)量
//等于 SDS 保存字符串的長(zhǎng)度
int len;
//記錄 buf 數(shù)組中未使用字節(jié)的數(shù)量
int free;
//字節(jié)數(shù)組,用于保存字符串
char buf[];
}
? 上面的定義相對(duì)于 C 語(yǔ)言對(duì)于字符串的定義场梆,多出了 len 屬性以及 free 屬性墅冷。
用SDS保存字符串 “Redis”具體圖示如下:
? 為什么不使用C語(yǔ)言字符串實(shí)現(xiàn),而是使用 SDS呢或油?這樣實(shí)現(xiàn)有什么好處寞忿?
1 常數(shù)復(fù)雜度獲取字符串長(zhǎng)度
由于 len 屬性的存在,我們獲取 SDS 字符串的長(zhǎng)度只需要讀取 len 屬性顶岸,時(shí)間復(fù)雜度為 O(1)腔彰。而對(duì)于 C 語(yǔ)言叫编,獲取字符串的長(zhǎng)度通常是經(jīng)過(guò)遍歷計(jì)數(shù)來(lái)實(shí)現(xiàn)的,時(shí)間復(fù)雜度為 O(n)霹抛。通過(guò) strlen key 命令可以獲取 key 的字符串長(zhǎng)度搓逾。
2 杜絕緩沖區(qū)溢出
我們知道在 C 語(yǔ)言中使用 strcat 函數(shù)來(lái)進(jìn)行兩個(gè)字符串的拼接,一旦沒(méi)有分配足夠長(zhǎng)度的內(nèi)存空間杯拐,就會(huì)造成緩沖區(qū)溢出霞篡。而對(duì)于 SDS 數(shù)據(jù)類(lèi)型,在進(jìn)行字符修改的時(shí)候端逼,會(huì)首先根據(jù)記錄的 len 屬性檢查內(nèi)存空間是否滿(mǎn)足需求朗兵,如果不滿(mǎn)足,會(huì)進(jìn)行相應(yīng)的空間擴(kuò)展顶滩,然后在進(jìn)行修改操作余掖,所以不會(huì)出現(xiàn)緩沖區(qū)溢出。
3 減少修改字符串的內(nèi)存重新分配次數(shù)
C語(yǔ)言由于不記錄字符串的長(zhǎng)度诲祸,所以如果要修改字符串浊吏,必須要重新分配內(nèi)存(先釋放再申請(qǐng)),因?yàn)槿绻麤](méi)有重新分配救氯,字符串長(zhǎng)度增大時(shí)會(huì)造成內(nèi)存緩沖區(qū)溢出找田,字符串長(zhǎng)度減小時(shí)會(huì)造成內(nèi)存泄露。
而對(duì)于SDS着憨,由于len屬性和free屬性的存在墩衙,對(duì)于修改字符串SDS實(shí)現(xiàn)了空間預(yù)分配和惰性空間釋放兩種策略:①空間預(yù)分配:對(duì)字符串進(jìn)行空間擴(kuò)展的時(shí)候,擴(kuò)展的內(nèi)存比實(shí)際需要的多甲抖,這樣可以減少連續(xù)執(zhí)行字符串增長(zhǎng)操作所需的內(nèi)存重分配次數(shù)漆改。②惰性空間釋放:對(duì)字符串進(jìn)行縮短操作時(shí),程序不立即使用內(nèi)存重新分配來(lái)回收縮短后多余的字節(jié)准谚,而是使用 free 屬性將這些字節(jié)的數(shù)量記錄下來(lái)挫剑,等待后續(xù)使用。(當(dāng)然SDS也提供了相應(yīng)的API柱衔,當(dāng)我們有需要時(shí)樊破,也可以手動(dòng)釋放這些未使用的空間。)
4 二進(jìn)制安全
因?yàn)镃字符串以空字符作為字符串結(jié)束的標(biāo)識(shí)唆铐,而對(duì)于一些二進(jìn)制文件(如圖片等)哲戚,內(nèi)容可能包括空字符串,因此C字符串無(wú)法正確存劝瘛顺少;而所有 SDS 的API 都是以處理二進(jìn)制的方式來(lái)處理 buf 里面的元素,并且 SDS 不是以空字符串來(lái)判斷是否結(jié)束,而是以 len 屬性表示的長(zhǎng)度來(lái)判斷字符串是否結(jié)束脆炎。
5 兼容部分 C 字符串函數(shù)
雖然 SDS 是二進(jìn)制安全的梅猿,但是一樣遵從每個(gè)字符串都是以空字符串結(jié)尾的慣例,這樣可以重用 C 語(yǔ)言庫(kù)<string.h> 中的一部分函數(shù)腕窥。
C字符串 | SDS |
---|---|
獲取字符串長(zhǎng)度的復(fù)雜度為O(N) | 獲取字符串長(zhǎng)度的復(fù)雜度為0(1) |
API是不安全的粒没,可能會(huì)造成緩沖區(qū)溢出 | API是安全的,不會(huì)造成緩沖區(qū)溢出 |
修改字符串長(zhǎng)度N次必然需要執(zhí)行N次內(nèi)存重分配 | 修改字符串長(zhǎng)度N次最多需要執(zhí)行N次內(nèi)存重分配 |
只能保存文本數(shù)據(jù) | 可以保存文本或者二進(jìn)制數(shù)據(jù) |
可以使用所有<string.h>庫(kù)中的函數(shù) | 可以使用一部分<string.h>庫(kù)中的函數(shù) |
一般來(lái)說(shuō)簇爆,SDS 除了保存數(shù)據(jù)庫(kù)中的字符串值以外癞松,SDS 還可以作為緩沖區(qū)(buffer):包括 AOF 模塊中的AOF緩沖區(qū)以及客戶(hù)端狀態(tài)中的輸入緩沖區(qū)。
5.2 鏈表
? 鏈表提供了高效的節(jié)點(diǎn)重排能力入蛆,以及順序性的節(jié)點(diǎn)訪(fǎng)問(wèn)方式响蓉,并且可以通過(guò)增刪節(jié)點(diǎn) 來(lái)靈活地調(diào)整鏈表的長(zhǎng)度。作為一種常用數(shù)據(jù)結(jié)構(gòu)哨毁,鏈表內(nèi)置在很多高級(jí)的編程語(yǔ)言里面枫甲,因?yàn)镽edis使用的C語(yǔ) 言并沒(méi)有內(nèi)置這種數(shù)據(jù)結(jié)構(gòu),所以Redis構(gòu)建了自己的鏈表實(shí)現(xiàn)扼褪。
? 鏈表在Redis中的應(yīng)用非常廣泛想幻,比如列表鍵的底層實(shí)現(xiàn)之一就是鏈表。當(dāng)一個(gè)列表鍵 包含了數(shù)量比較多的元素话浇,又或者列表中包含的元素都是比較長(zhǎng)的字符串時(shí)脏毯,Redis就會(huì)使用鏈表作為列表鍵的底層實(shí)現(xiàn)。此外發(fā)布與訂閱幔崖、慢查詢(xún)食店、監(jiān)視器等功能也用到了鏈表。
? 每個(gè)鏈表節(jié)點(diǎn)使用一個(gè)adlist .h/listNode結(jié)構(gòu)來(lái)表示
typedef struct listNode {
//前置節(jié)點(diǎn)
struct listNode *prev;
//后置節(jié)點(diǎn)
struct listNode *next;
//節(jié)點(diǎn)的值
void *value;
}listNode
? 多個(gè)listNode可以通過(guò)prev和next指針組成雙端鏈表赏寇。
? 雖然僅僅使用多個(gè)listNode結(jié)構(gòu)就可以組成鏈表吉嫩,但使用adlist.h/list來(lái)持有鏈表的話(huà),操作起來(lái)會(huì)更方便:
typedef struct list {
//表頭節(jié)點(diǎn)
listNode.head;
//表尾節(jié)點(diǎn)
listNode.tail;
//鏈表所包含的節(jié)點(diǎn)數(shù)量
unsigned long len;
//節(jié)點(diǎn)值復(fù)制函數(shù)
void *(*dup)(void *ptr);
//節(jié)點(diǎn)值釋放函數(shù)
void *(*free)(void *ptr);
//節(jié)點(diǎn)值對(duì)比函數(shù)
int (*match)(void *ptr,void *key);
}list;
? list結(jié)構(gòu)為鏈表提供了表頭指針head嗅定、表尾指針tail,以及鏈表長(zhǎng)度計(jì)數(shù)器len, 而dup自娩、free和match成員則是用于實(shí)現(xiàn)多態(tài)鏈表所需的類(lèi)型特定函數(shù)。
Redis鏈表實(shí)現(xiàn)特性總結(jié)如下:
①雙向:鏈表具有前置節(jié)點(diǎn)和后置節(jié)點(diǎn)的引用渠退,獲取這兩個(gè)節(jié)點(diǎn)時(shí)間復(fù)雜度都為O(1)忙迁。
②無(wú)環(huán):表頭節(jié)點(diǎn)的 prev 指針和表尾節(jié)點(diǎn)的 next 指針都指向 NULL,對(duì)鏈表的訪(fǎng)問(wèn)都是以 NULL 結(jié)束。
③帶鏈表長(zhǎng)度計(jì)數(shù)器:通過(guò) len 屬性獲取鏈表長(zhǎng)度的時(shí)間復(fù)雜度為 O(1)智什。
④多態(tài):鏈表節(jié)點(diǎn)使用 void* 指針來(lái)保存節(jié)點(diǎn)值动漾,可以保存各種不同類(lèi)型的值丁屎。
5.3 字典
? 字典荠锭,又稱(chēng)為符號(hào)表(symbol table)>關(guān)聯(lián)數(shù)組(associative array)或映射(map),是 一種用于保存鍵值對(duì)(key-value pair)的抽象數(shù)據(jù)結(jié)構(gòu)。
? 在字典中晨川,一個(gè)鍵(key)可以和一個(gè)值(value)進(jìn)行關(guān)聯(lián)(或者說(shuō)將鍵映射為值), 這些關(guān)聯(lián)的鍵和值就稱(chēng)為鍵值對(duì)证九。
? 字典經(jīng)常作為一種數(shù)據(jù)結(jié)構(gòu)內(nèi)置在很多高級(jí)編程語(yǔ)言里面删豺,但Redis所使用的C語(yǔ)言并 沒(méi)有內(nèi)置這種數(shù)據(jù)結(jié)構(gòu),因此Redis構(gòu)建了自己的字典實(shí)現(xiàn)愧怜。
? 字典在Redis中的應(yīng)用相當(dāng)廣泛呀页,比如Redis的數(shù)據(jù)庫(kù)就是使用字典來(lái)作為底層實(shí)現(xiàn)的, 對(duì)數(shù)據(jù)庫(kù)的增拥坛、刪蓬蝶、査、改操作也是構(gòu)建在對(duì)字典的操作之上的猜惋。除了用來(lái)表示數(shù)據(jù)庫(kù)之外丸氛,字典還是哈希鍵的底層實(shí)現(xiàn)之一,當(dāng)一個(gè)哈希鍵包含的鍵值 對(duì)比較多著摔,又或者鍵值對(duì)中的元素都是比較長(zhǎng)的字符串時(shí)缓窜,Redis就會(huì)使用字典作為哈希鍵 的底層實(shí)現(xiàn)。除了用來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)和哈希鍵之外谍咆,Redis的不少功能也用到了字典禾锤。
? Redis的字典使用哈希表作為底層實(shí)現(xiàn),一個(gè)哈希表里面可以有多個(gè)哈希表節(jié)點(diǎn)摹察,而每個(gè)哈希表節(jié)點(diǎn)就保存了字典中的一個(gè)鍵值對(duì)恩掷。
5.3.1 哈希表
Redis字典所使用的哈希表由dict.h/dictht結(jié)構(gòu)定義:
typedef struct dictht{
//哈希表數(shù)組
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩碼,用于計(jì)算索引值
//總是等于 size-1
unsigned long sizemask;
//該哈希表已有節(jié)點(diǎn)的數(shù)量
unsigned long used;
}dictht
? table屬性是一個(gè)數(shù)組港粱,數(shù)組中的每個(gè)元素都是一個(gè)指向diet.h/dictEntry結(jié)構(gòu)的指針螃成,每個(gè)dictEntry結(jié)構(gòu)保存著一個(gè)鍵值對(duì)。
? size屬性記錄了哈希表的大小查坪,也即 是table數(shù)組的大小寸宏,而used屬性則記錄了哈希表目前已有節(jié)點(diǎn)(鍵值對(duì))的數(shù)量。
? sizemask屬性的值總是等于size-1,這 個(gè)屬性和哈希值一起決定一個(gè)鍵應(yīng)該被放到 table數(shù)組的哪個(gè)索引上面偿曙。
5.3.2 哈希表節(jié)點(diǎn)
哈希表節(jié)點(diǎn)使用dictEntry結(jié)構(gòu)表示氮凝,每個(gè)dictEntry結(jié)構(gòu)都保存著一個(gè)鍵值對(duì):
typedef struct dictEntry {
//鍵
void *key;
//值
union{
void *val;
uint64_tu64;
int64_ts64;
} v;
//指向下個(gè)哈希表節(jié)點(diǎn),形成鏈表
struct dictEntry *next;
} dictEntry;
? key屬性保存著鍵值對(duì)中的鍵望忆。
? 而v屬性則保存著鍵值對(duì)中的值罩阵,其中鍵值對(duì)的值可以是一個(gè)指針,或者是一個(gè)uint64_t整數(shù)启摄,又或者是一個(gè)int64_t整數(shù)稿壁。
? next屬性是指向另一個(gè)哈希表葦點(diǎn)的指針,這個(gè)指針可以將象個(gè)哈希值相同的鍵值對(duì) 連接在一次歉备,以此來(lái)解決鍵沖突(collision)的問(wèn)題傅是。
5.3.3 字典
Redis中的字典由dict.h/dict結(jié)構(gòu)表示:
typedef struct dict (
//類(lèi)型特定函數(shù)
dictType *type;
//私有數(shù)據(jù)
void *privdata;
//哈希值
dictht ht[2];
//rehash索引
//當(dāng)rehash不在進(jìn)行時(shí),值為-1
in trehashidx; /* rehashing not in progress'if rehashidx == -1 */
} dict;
type屬性和privdata屬性是針對(duì)不同類(lèi)型的鍵值對(duì),為創(chuàng)建多態(tài)字典而設(shè)置的:
type屬性是一個(gè)指向dictType結(jié)構(gòu)的指針喧笔,每個(gè)dictType結(jié)構(gòu)保存了一簇用于操作特定類(lèi)型鍵值對(duì)的函數(shù)帽驯,Redis會(huì)為用途不同的字典設(shè)置不同的類(lèi)型特定函數(shù)。
而privdata屬性則保存了需要傳給那些類(lèi)型特定函數(shù)的可選參數(shù)书闸。
typedef struct dictType (
//計(jì)算哈希值的函數(shù)
unsigned int (*hashFunction)(const void *key);
//復(fù)制鍵的函數(shù)
void * (*keyDup) (void *privdata, const void *key);
//復(fù)制值的函數(shù)
void * (*valDup) (void *privdata, const void *obj);
//對(duì)比鍵的函數(shù)
int (*keyCompare)(void *privdata, const void *keylr const void *key2);
//銷(xiāo)毀鍵的函數(shù)
void (*keyDestructor)(void *privdata, void *key);
//銷(xiāo)毀值的函數(shù)
void (*valDestructor)(void *privdata, void *obj);
) dictType;
? ht屬性是一個(gè)包含兩個(gè)項(xiàng)的數(shù)組尼变,數(shù)組中的每個(gè)項(xiàng)都是_個(gè)dictht哈希表,一般情況下浆劲,字典只使用ht[0]哈希表嫌术,ht[1]哈希表只會(huì)在對(duì)ht[0]哈希表進(jìn)行rehash時(shí)使用。
? 除了ht[1]之外牌借,另一個(gè)和rehash有關(guān)的屬性就是rehashidx蛉威,它記錄了 rehash目前的進(jìn)度,如果目前沒(méi)有在進(jìn)行rehash走哺,那么它的值為-1蚯嫌。
? 下圖展示了一個(gè)普通狀態(tài)下(沒(méi)有進(jìn)行rehash )的字典。
5.3.4
? 字典被廣泛用于實(shí)現(xiàn)Redis的各種功能丙躏,其中包括數(shù)據(jù)庫(kù)和哈希鍵择示。
? Redis中的字典使用哈希表作為底層實(shí)現(xiàn),每個(gè)字典帶有兩個(gè)哈希表晒旅,一個(gè)平時(shí)使用栅盲,另一個(gè)僅在進(jìn)行rehash時(shí)使用。
? 當(dāng)字典被用作數(shù)據(jù)庫(kù)的底層實(shí)現(xiàn)废恋,或者哈希鍵的底層實(shí)現(xiàn)時(shí)谈秫,Redis使用MurmurHash2 算法來(lái)計(jì)算鍵的哈希值。
? 哈希表使用鏈地址法來(lái)解決鍵沖突鱼鼓,被分配到同一個(gè)索引上的多個(gè)鍵值對(duì)會(huì)連接成 一個(gè)單向鏈表拟烫。
? 在對(duì)哈希表進(jìn)行擴(kuò)展或者收縮操作時(shí),程序需要將現(xiàn)有哈希表包含的所有鍵值對(duì) rehash到新哈希表里面迄本,并且這個(gè)rehash過(guò)程并不是一次性地完成的硕淑,而是漸進(jìn)式地完成的。
5.4 跳躍表
? 跳躍表(skiplist)是一種有序數(shù)據(jù)結(jié)構(gòu)嘉赎,它通過(guò)在每個(gè)節(jié)點(diǎn)中維持多個(gè)指向其他節(jié)點(diǎn)的指針辟汰,從而達(dá)到快速訪(fǎng)問(wèn)節(jié)點(diǎn)的目的溺职。
? 跳躍表支持平均O(logN)、最壞O(N)復(fù)雜度的節(jié)點(diǎn)査找碱妆,還可以通過(guò)順序性操作來(lái)批量處理節(jié)點(diǎn)扶踊。
? 在大部分情況下时甚,跳躍表的效率可以和平衡樹(shù)相媲美臊旭,并且因?yàn)樘S表的實(shí)現(xiàn)比平衡樹(shù) 要來(lái)得更為簡(jiǎn)單营袜,所以有不少程序都使用跳躍表來(lái)代替平衡樹(shù)奢米。
? Redis使用跳躍表作為有序集合鍵的底層實(shí)現(xiàn)之一,如果一個(gè)有序集合包含的元素?cái)?shù)量比較多纠永,又或者有序集合中元素的成員(member)是比較長(zhǎng)的字符串時(shí),Redis就會(huì)使用跳躍表來(lái)作為有序集合鍵的底層實(shí)現(xiàn)谒拴。
? 和鏈表尝江、字典等數(shù)據(jù)結(jié)構(gòu)被廣泛地應(yīng)用在Redis內(nèi)部不同,Redis只在兩個(gè)地方用到了跳躍表英上,一個(gè)是實(shí)現(xiàn)有序集合鍵炭序,另一個(gè)是在集群節(jié)點(diǎn)中用作內(nèi)部數(shù)據(jù)結(jié)構(gòu),除此之外苍日,跳 躍表在Redis里面沒(méi)有其他用途惭聂。
? Redis 的跳躍表由redis .h/zskiplistNode 和redis .h/zskiplist 兩個(gè)結(jié)構(gòu)定義,其中zskiplistNode結(jié)構(gòu)用于表示跳躍表節(jié)點(diǎn)相恃,而zskiplist結(jié)構(gòu)則用于保存跳躍表節(jié)點(diǎn)的相關(guān)信息辜纲,比如節(jié)點(diǎn)的數(shù)量,以及指向表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)的指針等等拦耐。
Redis中跳躍表節(jié)點(diǎn)定義如下:
typedef struct zskiplistNode {
//層
struct zskiplistLevel{
//前進(jìn)指針
struct zskiplistNode *forward;
//跨度
unsigned int span;
}level[];
//后退指針
struct zskiplistNode *backward;
//分值
double score;
//成員對(duì)象
robj *obj;
} zskiplistNode
僅靠多個(gè)跳躍節(jié)點(diǎn)就可以組成跳躍表
typedef struct zskiplist{
//表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)
structz skiplistNode *header, *tail;
//表中節(jié)點(diǎn)的數(shù)量
unsigned long length;
//表中層數(shù)最大的節(jié)點(diǎn)的層數(shù)
int level;
}zskiplist;
跳躍表具有如下性質(zhì):
1耕腾、由很多層結(jié)構(gòu)組成;
2杀糯、每一層都是一個(gè)有序的鏈表扫俺,排列順序?yàn)橛筛邔拥降讓樱贾辽侔瑑蓚€(gè)鏈表節(jié)點(diǎn)固翰,分別是前面的head節(jié)點(diǎn)和后面的nil節(jié)點(diǎn)狼纬;
3、最底層的鏈表包含了所有的元素骂际;
4疗琉、如果一個(gè)元素出現(xiàn)在某一層的鏈表中,那么在該層之下的鏈表也全都會(huì)出現(xiàn)(上一層的元素是當(dāng)前層的元素的子集)歉铝;
5没炒、鏈表中的每個(gè)節(jié)點(diǎn)都包含兩個(gè)指針,一個(gè)指向同一層的下一個(gè)鏈表節(jié)點(diǎn)犯戏,另一個(gè)指向下一層的同一個(gè)鏈表節(jié)點(diǎn)送火;
每個(gè)跳躍表節(jié)點(diǎn)的層高都是1至32之間的隨機(jī)數(shù)。
在同一個(gè)跳躍表中先匪,多個(gè)節(jié)點(diǎn)可以包含相同的分值种吸,但每個(gè)節(jié)點(diǎn)的成員對(duì)象必須是唯一的。
跳躍表中的節(jié)點(diǎn)按照分值大小進(jìn)行排序呀非,當(dāng)分值相同時(shí)坚俗,節(jié)點(diǎn)按照成員對(duì)象的大小進(jìn)行排序镜盯。
跳躍表的增刪查:
①搜索:從最高層的鏈表節(jié)點(diǎn)開(kāi)始,如果比當(dāng)前節(jié)點(diǎn)要大和比當(dāng)前層的下一個(gè)節(jié)點(diǎn)要小猖败,那么則往下找速缆,也就是和當(dāng)前層的下一層的節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)進(jìn)行比較,以此類(lèi)推恩闻,一直找到最底層的最后一個(gè)節(jié)點(diǎn)艺糜,如果找到則返回,反之則返回空幢尚。
? ②插入:首先確定插入的層數(shù)破停,有一種方法是假設(shè)拋一枚硬幣,如果是正面就累加尉剩,直到遇見(jiàn)反面為止真慢,最后記錄正面的次數(shù)作為插入的層數(shù)。當(dāng)確定插入的層數(shù)k后理茎,則需要將新元素插入到從底層到k層黑界。
③刪除:在各個(gè)層中找到包含指定值的節(jié)點(diǎn)皂林,然后將節(jié)點(diǎn)從鏈表中刪除即可园爷,如果刪除以后只剩下頭尾兩個(gè)節(jié)點(diǎn),則刪除這一層式撼。
5.5 整數(shù)集合
? 整數(shù)集合(intset)是集合鍵的底層實(shí)現(xiàn)之一童社,當(dāng)一個(gè)集合只包含整數(shù)值元素,并且這個(gè)集合的元素?cái)?shù)量不多時(shí)著隆,Redis就會(huì)使用整數(shù)集合作為集合鍵的底層實(shí)現(xiàn)扰楼。
? 舉個(gè)例子,如果我們創(chuàng)建一個(gè)只包含五個(gè)元素的集合鍵美浦,并且集合中的所有元素都是整數(shù)值弦赖,那么這個(gè)集合鍵的底層實(shí)現(xiàn)就會(huì)是整數(shù)集合:
redis> SADD numbers 13579
(integer) 5
redis> OBJECT ENCODING numbers
"intset"
? 整數(shù)集合(intset)是Redis用于保存整數(shù)值的集合抽象數(shù)據(jù)結(jié)構(gòu),它可以保存類(lèi)型為 intl6_t, int32_t或者int64_t的整數(shù)值浦辨,并且保證集合中不會(huì)出現(xiàn)重復(fù)元素蹬竖。
每個(gè)intset.h/intset結(jié)構(gòu)表示一個(gè)整數(shù)集合:
typedef struct intset {
//編碼方式
uint32_t encoding;
//集合包含的元素?cái)?shù)量
uint32_t length;
//保存元素的數(shù)組
int8_t contents[];
} intset;
? contents 數(shù)組是整數(shù)集合的底層實(shí)現(xiàn):整數(shù)集合的每個(gè)元素都是contents數(shù)組的一個(gè)數(shù)組項(xiàng)(item),各個(gè)項(xiàng)在數(shù)組中按值的大小從小到大有序地排列流酬,并且數(shù)組中不包含任何重復(fù)項(xiàng)币厕。
? length屬性記錄了整數(shù)集合包含的元素?cái)?shù)量,也即是contents數(shù)組的長(zhǎng)度芽腾。
? 雖然intset結(jié)構(gòu)將contents屬性聲明為int8_t類(lèi)型的數(shù)組旦装,但實(shí)際上contents數(shù) 組并不保存任何int8_t類(lèi)型的值,contents數(shù)組的真正類(lèi)型取決于encoding屬性的值摊滔。
?
5.5.1 升級(jí)
? 每當(dāng)我們要將一個(gè)新元素添加到整數(shù)集合里面阴绢,并且新元素的類(lèi)型比整數(shù)集合現(xiàn)有所有元素的類(lèi)型都要長(zhǎng)時(shí)店乐,整數(shù)集合需要先進(jìn)行升級(jí)(upgrade),然后才能將新元素添加到整數(shù) 集合里面呻袭。
升級(jí)整數(shù)集合并添加新元素共分為三步進(jìn)行:
? 1)根據(jù)新元素的類(lèi)型眨八,擴(kuò)展整數(shù)集合底層數(shù)組的空間大小,并為新元素分配空間左电。
? 2)將底層數(shù)組現(xiàn)有的所有元素都轉(zhuǎn)換成與新元素相同的類(lèi)型廉侧,并將類(lèi)型轉(zhuǎn)換后的元素放置到正確的位上,而且在放置元素的過(guò)程中券腔,需要 繼續(xù)維持底層數(shù)組的有序性質(zhì)不變。
? 3)將新元素添加到底層數(shù)組里面拘泞。
整數(shù)集合的升級(jí)策略有兩個(gè)好處纷纫,一個(gè)是提升整數(shù)集合的靈活性,另一個(gè)是盡可能地節(jié)約內(nèi)存陪腌。
5.5.2 降級(jí)
? 整數(shù)集合不支持降級(jí)操作辱魁,一旦對(duì)數(shù)組進(jìn)行了升級(jí),編碼就會(huì)一直保持升級(jí)后的狀態(tài)诗鸭。
5.6 壓縮列表
? 壓縮列表(Ziplist )是列表鍵和哈希鍵的底層實(shí)現(xiàn)之一染簇。當(dāng)一個(gè)列表鍵只包含少量 列表項(xiàng),并且每個(gè)列表項(xiàng)要么就是小整數(shù)值强岸,要么就是長(zhǎng)度比較短的字符串锻弓,那么Redis就 會(huì)使用壓縮列表來(lái)做列表鍵的底層實(shí)現(xiàn)。
例如蝌箍,執(zhí)行以下命令將創(chuàng)建一個(gè)壓縮列表實(shí)現(xiàn)的列表鍵:
redis> RPUSH 1st 1 3 5 10086 "hello" "world"
(integer)6
redis> OBJECT ENCODING 1st
"ziplist"
? 另外青灼,當(dāng)一個(gè)哈希鍵只包含少量鍵值對(duì),比且每個(gè)健值對(duì)的鍵和值要么就是小整數(shù)值, 要么就是長(zhǎng)度比較短的字符串妓盲,那么Redis就會(huì)使用壓縮列表來(lái)做哈希鍵的底層實(shí)現(xiàn)杂拨。
舉個(gè)例子,執(zhí)行以下命令將創(chuàng)建一個(gè)壓縮列表實(shí)現(xiàn)的哈希鍵:
redis> HMSET profile "name" "Jack" "age" 28 "job" "Programmer"
OK
redis> OBJECT ENCODING profile
"ziplist"
? 壓縮列表是Redis為了節(jié)約內(nèi)存而開(kāi)發(fā)的悯衬,是由一系列特殊編碼的連續(xù)內(nèi)存塊組成的順 序型(sequential)數(shù)據(jù)結(jié)構(gòu)弹沽。一個(gè)壓縮列表可以包含任意多個(gè)節(jié)點(diǎn)(entry),每個(gè)節(jié)點(diǎn)可以保存一個(gè)字節(jié)數(shù)組或者一個(gè)整數(shù)值。下圖展示了壓縮列表的各個(gè)組成部分?筋粗。
? 其中字節(jié)數(shù)組可以是以下三種長(zhǎng)度中的一種:
? 長(zhǎng)度小于等于63 (2策橘、1 )字節(jié)的字節(jié)數(shù)組;
? 長(zhǎng)度小于等于16 383 (21 )字節(jié)的字節(jié)數(shù)組娜亿;
? 長(zhǎng)度小于等于4294967295 (232-1 )字節(jié)的字節(jié)數(shù)組;
? 而整數(shù)值則可以是以下六種長(zhǎng)度的其中一種:
? 4位長(zhǎng)役纹,介于0至12之間的無(wú)符號(hào)整數(shù);
? 1字節(jié)長(zhǎng)的有符號(hào)整數(shù)暇唾;
? 3字節(jié)長(zhǎng)的有符號(hào)整數(shù)促脉;
? intl6_t類(lèi)型整數(shù)辰斋;
? int32_t類(lèi)型整數(shù);
? int64_t類(lèi)型整數(shù)瘸味。
? 每個(gè)壓縮列表節(jié)點(diǎn)都由 previous_entry_length宫仗,encoding, content旁仿, 三個(gè)部分組成藕夫,下圖所示。
? 節(jié)點(diǎn)的previous_entry_length屬性以字節(jié)為單位枯冈,記錄了壓縮列表中前一個(gè)節(jié)點(diǎn)的長(zhǎng)度毅贮。previous_entry_length屬性的長(zhǎng)度可以是1字節(jié)或者5字節(jié):如果前一節(jié)點(diǎn)的長(zhǎng)度小于254字節(jié),那么previous_entry_length屬性的長(zhǎng)度為1字節(jié):前一節(jié)點(diǎn)的長(zhǎng)度就保存在這一個(gè)字節(jié)里面尘奏。如果前一節(jié)點(diǎn)的長(zhǎng)度大于等于254字節(jié)滩褥,那么previous_entry_length屬性的長(zhǎng)度為5字節(jié):其中屬性的第一字節(jié)會(huì)被設(shè)置為OxFE (十進(jìn)制值254),而之后的四個(gè)字節(jié)則用于保存前一節(jié)點(diǎn)的長(zhǎng)度。
? 節(jié)點(diǎn)的encoding屬性記錄了節(jié)點(diǎn)的content屬性所保存數(shù)據(jù)的類(lèi)型以及長(zhǎng)度炫加。一字節(jié)瑰煎、兩字節(jié)或者五字節(jié)長(zhǎng),值的最高位為00俗孝、01或者10的是字節(jié)數(shù)組編碼: 這種編碼表示節(jié)點(diǎn)的content屬性保存著字節(jié)數(shù)組酒甸,數(shù)組的長(zhǎng)度由編碼除去最高兩 位之后的其他位記錄;一字節(jié)長(zhǎng)赋铝,值的最高位以11開(kāi)頭的是整數(shù)編碼:這種編碼表示節(jié)點(diǎn)的content屬 性保存著整數(shù)值插勤,整數(shù)值的類(lèi)型和長(zhǎng)度由編碼除去最高兩位之后的其他位記錄;
? 節(jié)點(diǎn)的content屬性負(fù)責(zé)保存節(jié)點(diǎn)的值革骨,節(jié)點(diǎn)值可以是一個(gè)字節(jié)數(shù)組或者整數(shù)饮六,值的類(lèi)型和長(zhǎng)度由節(jié)點(diǎn)的encoding屬性決定。
6 Redis消息通信模式
? Redis 常見(jiàn)的消息通信模式有兩種:隊(duì)列模式和發(fā)布訂閱模式苛蒲。
6.1 隊(duì)列模式
? Redis 隊(duì)列模式其實(shí)是通過(guò)基本數(shù)據(jù)類(lèi)型list(列表)的lpush和rpop來(lái)實(shí)現(xiàn)的卤橄,即數(shù)據(jù)從列表的一端入隊(duì),另一端出隊(duì)臂外。
? 注意事項(xiàng):消息接收方如果不知道隊(duì)列中是否有消息窟扑,會(huì)一直發(fā)送rpop命令,如果這樣的話(huà)漏健,會(huì)每一次都建
立一次連接嚎货,這樣顯然不好∧杞可以使用brpop命令殖属,它如果從隊(duì)列中取不出來(lái)數(shù)據(jù),會(huì)一直阻塞瓦盛,在一定時(shí)間范圍內(nèi)沒(méi)有取出則返回null洗显。
6.2 發(fā)布訂閱模式
? 發(fā)布者(pub)發(fā)送消息外潜,訂閱者(sub)接收消息,發(fā)布者和訂閱者之間通過(guò)頻道(Channel)進(jìn)行通信挠唆。
? 下圖展示了頻道 channel1处窥,以及訂閱這個(gè)頻道的三個(gè)客戶(hù)端 —— client2、client5 和 client1 之間的關(guān)系:
? 當(dāng)有新消息通過(guò) PUBLISH 命令發(fā)送給頻道 channel1 時(shí)玄组, 這個(gè)消息就會(huì)被發(fā)送給訂閱它的三個(gè)客戶(hù)端:
? 在我們實(shí)例中我們創(chuàng)建了訂閱頻道名為 runoobChat:
redis 127.0.0.1:6379> SUBSCRIBE runoobChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
? 現(xiàn)在滔驾,我們先重新開(kāi)啟個(gè) redis 客戶(hù)端,然后在同一個(gè)頻道 runoobChat 發(fā)布兩次消息俄讹。
redis 127.0.0.1:6379> PUBLISH runoobChat "Redis PUBLISH test"
(integer) 1
redis 127.0.0.1:6379> PUBLISH runoobChat "Learn redis by runoob.com"
(integer) 1
? 訂閱者的客戶(hù)端會(huì)顯示如下消息
1) "message"
2) "runoobChat"
3) "Redis PUBLISH test"
1) "message"
2) "runoobChat"
3) "Learn redis by runoob.com"
7. Redis事務(wù)
? Redis 的事務(wù)是通過(guò)MULTI 哆致、 EXEC 、 DISCARD 和WATCH 患膛、UNWATCH這五個(gè)命令來(lái)完成的摊阀。
? Redis 的單個(gè)命令都是原子性的,所以這里需要確保事務(wù)性的對(duì)象是命令集合剩瓶。
? Redis 將命令集合序列化并確保處于同一事務(wù)的命令集合連續(xù)且不被打斷的執(zhí)行驹溃。
? Redis 不支持回滾操作城丧。
7.1 事務(wù)命令
? MULTI:標(biāo)記一個(gè)事務(wù)塊的開(kāi)始延曙,Redis會(huì)將后續(xù)的命令逐個(gè)放入隊(duì)列中。
? EXEC:執(zhí)行所有事務(wù)塊內(nèi)的命令亡哄,然后恢復(fù)正常的連接狀態(tài)
? DISCARD:取消事務(wù)枝缔,放棄執(zhí)行事務(wù)塊內(nèi)的所有命令,然后恢復(fù)正常的連接狀態(tài)蚊惯。
? WATCH:監(jiān)視一個(gè)(或多個(gè)) key 愿卸,如果在事務(wù)執(zhí)行之前這個(gè)(或這些) key 被其他命令所改動(dòng),那么事務(wù)將被打斷截型。
? UNWATCH:取消 WATCH 命令對(duì)所有 key 的監(jiān)視趴荸。
7.2 事務(wù)演示
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set s1 111
QUEUED
127.0.0.1:6379> hset hash1 name zhangsan
QUEUED
127.0.0.1:6379> exec
1) OK
2) (integer) 1
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set s2 222
QUEUED
127.0.0.1:6379> hset hash2 age 20
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
127.0.0.1:6379> watch s1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set s1 555
QUEUED
#此時(shí)在沒(méi)有exec之前,通過(guò)另一個(gè)命令窗口對(duì)監(jiān)控的s1字段進(jìn)行修改
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get s1
"111"
7.3 事務(wù)失敗處理
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a aaa
QUEUED
127.0.0.1:6379> set b bbb
QUEUED
127.0.0.1:6379> set c ccc
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
#如果在 set b bbb 處失敗宦焦,set a 已成功不會(huì)回滾发钝,set c 還會(huì)繼續(xù)執(zhí)行
7.3.1 Redis編譯期錯(cuò)誤
127.0.0.1:6379> multi
OK
127.0.0.1:6379〉 sets s1 111
(error) ERR unknown command 'sets'
127.0.0.1:6379> set s2
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> set s3 333
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because cf previous errors.
127.0.0.l:6379> get s3
(nil)
7.3.2 Redis運(yùn)行期錯(cuò)誤
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set s4 444
QUEUED
127.0.0.1:6379> lpush s4 111 222
QUEUED
127.0.0.1:6379> exec
1)QK
2)(error) wrongtype Operation against a key holding the wrong kind of value
127.0.0.1:6379> get s4
"444"
7.4 Redis 不支持事務(wù)回滾
? 1.大多數(shù)事務(wù)失敗是因?yàn)檎Z(yǔ)法錯(cuò)誤或者類(lèi)型錯(cuò)誤,這兩種錯(cuò)誤在開(kāi)發(fā)階段都是可以預(yù)見(jiàn)的
? 2.Redis 為了性能方面就忽略了事務(wù)回滾波闹。
7.5 Redis事務(wù)使用場(chǎng)景(樂(lè)觀(guān)鎖)
? 樂(lè)觀(guān)鎖基于CAS(Compare And Swap)思想(比較并替換)酝豪,是不具有互斥性,不會(huì)產(chǎn)生鎖等待而消耗資源精堕,但是需要反復(fù)的重試孵淘,但也是因?yàn)橹卦嚨臋C(jī)制,能比較快的響應(yīng)歹篓。因此我們可以利用redis來(lái)實(shí)現(xiàn)樂(lè)觀(guān)鎖瘫证。具體思路如下:
? 1揉阎、利用redis的watch功能,監(jiān)控這個(gè)redisKey的狀態(tài)值
? 2痛悯、獲取redisKey的值
? 3余黎、創(chuàng)建redis事務(wù)
? 4、給這個(gè)key的值加一
? 5载萌、然后去執(zhí)行這個(gè)事務(wù)惧财,如果key的值被修改過(guò)則回滾,key不加1
public void watch() {
try {
String watchKeys = "watchKeys";
//初始值 value=1
jedis.set(watchKeys, 1);
//監(jiān)聽(tīng)key為watchKeys的值
jedis.watch(watchkeys);
//開(kāi)啟事務(wù)
Transaction tx = jedis.multi();
//watchKeys自增加一
tx.incr(watchKeys);
//執(zhí)行事務(wù)扭仁,如果其他線(xiàn)程對(duì)watchKeys中的value進(jìn)行修改垮衷,則該事務(wù)將不會(huì)執(zhí)行
//通過(guò)redis事務(wù)以及watch命令實(shí)現(xiàn)樂(lè)觀(guān)鎖
List<Object> exec = tx.exec();
if (exec == null) {
System.out.println("事務(wù)未執(zhí)行");
} else {
System.out.println("事務(wù)成功執(zhí)行旁涤,watchKeys的value成功修改");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.close();
}
}
Redis樂(lè)觀(guān)鎖實(shí)現(xiàn)秒殺
public class Second {
public static void main(String[] arg) {
String redisKey = "second";
ExecutorService executorService = Executors.newFixedThreadPool(20);
try {
Jedis jedis = new Jedis("127.0.0.1", 6378);
// 初始值
jedis.set(redisKey, "0");
jedis.close();
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
executorService.execute(() -> {
Jedis jedis1 = new Jedis("127.0.0.1", 6378);
try {
jedis1.watch(redisKey);
String redisValue = jedis1.get(redisKey);
int valInteger = Integer.valueOf(redisValue);
String userInfo = UUID.randomUUID().toString();
if (valInteger < 20) { // 沒(méi)有秒完
Transaction tx = jedis1.multi();
tx.incr(redisKey);
List list = tx.exec();
// 秒成功佃却,如果失敗返回空l(shuí)ist而不是空
if (list != null && list.size() > 0) {
System.out.println("用戶(hù):" + userInfo + ",秒殺成功");
} else {// 版本變化柳骄,被別人搶了熊泵。
System.out.println("用戶(hù):" + userInfo + "仰迁,秒殺失敗");
}
} else {// 秒完了
System.out.println("已經(jīng)有20人秒殺成功,秒殺結(jié)束");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis1.close();
}
});
}
executorService.shutdown();
}
}
8 緩存淘汰策略
? 在redis中顽分,允許用戶(hù)設(shè)置最大使用內(nèi)存大小maxmemory徐许,默認(rèn)為0,沒(méi)有指定最大緩存卒蘸,如果有新的數(shù)據(jù)添加雌隅,超過(guò)最大內(nèi)存,則會(huì)使redis崩潰缸沃,所以一定要設(shè)置恰起。redis 內(nèi)存數(shù)據(jù)集大小上升到一定大小的時(shí)候,就會(huì)實(shí)行數(shù)據(jù)淘汰策略趾牧。
? redis淘汰策略配置:maxmemory-policy voltile-lru
? redis 提供 6種數(shù)據(jù)淘汰策略:
? 1. volatile-lru:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
? 2. volatile-ttl:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過(guò)期的數(shù)據(jù)淘汰
? 3. volatile-random:從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
? 4. allkeys-lru:從數(shù)據(jù)集(server.db[i].dict)中挑選最近最少使用的數(shù)據(jù)淘汰
? 5. allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
? 6. no-enviction(驅(qū)逐):禁止驅(qū)逐數(shù)據(jù)
? redis淘汰策略支持熱配置检盼。