Redis
Salvatore Sanfilippo 薩爾瓦託·桑菲利波--“Redis之父”
Salvatore在負責一個
page view
記錄的系統,接收多個網站js發送來的頁面訪問記錄數據,並存儲之後展示給用戶,最大負載每秒數千條頁面記錄,當時Salvatore在僅有硬件資源上無法用現有的數據庫達到希望的性能。所以催生了redis的雛形 –一段C程序
1. 概述
基於內存存儲的,NoSql數據庫 ( 非關係型數據庫 ),存儲結構 : key-value
Redis是一個開放源代碼(BSD許可)內存中的數據結構存儲,用作數據庫、緩存和消息代理。
對於數據量多,數據交互效率要求高的場景,可以考慮使用redis
2. 安裝
**下載:**https://redis.io/download
依次執行:
yum install gcc安裝依賴
yum install tcl 安裝依賴
tar -zxvf redis-4.0.14.tar.gz 解壓
cd redis-4.0.14 進入解壓目錄
make MALLOC=libc 編譯,並設置用標準的libc中的內存管理函數
make install 安裝
3. 啓動Redis
3.1 前臺模式
默認配置啓動(默認端口6379)
[root@zhj ~]# redis-server
存儲數據 set 然後根據提示可以覆蓋存放數據
獲取數據 get
獲取中文,需要退出,然後輸入 redis-cli --raw
獲取性能 redis-benchmark -t get,spop -q -p 6379
3.2 守護進程(後臺)模式
在redis解壓根目錄中找到配置文件模板(redis.conf),複製到自定義位置:
[root@rjj ~]# cp redis.conf /usr/local/redis/redis_conf/redis.conf/redis.conf
通過vi命令修改
daemonize yes #後臺模式
port 7777 #端口號
pidfile /usr/local/redis/redis_conf/redis_conf/7777.pid #記錄進程號的文件位置
logfile /usr/local/redis/redis_conf/7777.log #記錄日誌的文件位置
dir /usr/local/redis/redis_conf #redis的工作目錄,用於保存自己的相關文件
bind 127.0.0.1 192.168.80.128 #允許以此ip訪問redis,已達到對redis的保護
#最後啓動redis:
[root@zhj ~]# redis-server /usr/local/redis/redis_conf/redis.conf
3.3 連接redis
#連接端口爲6379 Host爲127.0.0.1的redis服務器
[root@rj ~]# redis-cli
#連接端口爲7777 Host爲192.168.80.128的redis服務器
[root@rj ~]# redis-cli -p 7777 -h 192.168.80.128
3.4 關閉
連接後,執行
shutdown
即可退出redis
3.5 性能測試
redis-benchmark -t get,spop -q -p 9001
4. 數據類型
redis外層有很大的KEY,存儲的是一個個的小數據key,每個小數據key都是list/set等等
4.1 String
指令 | 描述 |
---|---|
set | 設置一個key/value |
get | 根據key獲得對應的value |
mset | 一次設置多個key value |
mget | 一次獲得多個key的value |
getset getset age 19 | 獲得原始key的值,同時設置新值 |
strlen | 獲得對應key存儲value的長度 |
append | 爲對應key的value追加內容 |
getrange | 截取value的內容,對原始的值沒有影響 |
setex setex key ex value | 設置一個key存活的有效期(秒) |
expire key ex | 爲某一個key設置秒數 |
psetex | 設置一個key存活的有效期(豪秒) |
setnx | 只有當這個key不存在時等效set操作 |
msetnx | 可以同時設置多個key,在key不存在時有效 |
decr | 進行數值類型的-1操作 |
decrby | 根據提供的數據進行減法操作 |
incr | 進行數值類型的+1操作 |
incrby | 根據提供的數據進行加法操作 |
incrbyfloat | 根據提供的數據加入浮點數 |
keys * | 查找所有的key值 |
del key | 刪除key |
keys *
查看所有key
ttl key
查看剩餘存活時間
del key
刪除key
select 5
切換到第6個庫
flushDB
刪除當前庫的r所有數據
4.2 List
指令 | 描述 |
---|---|
lpush | 將某個值加入到一個key列表頭部(左邊),新的key會將老的key向右擠出 |
lpushx | 同lpush,但是必須要保證這個key存在 |
rpush | 將某個值加入到一個key列表末尾(右邊),追加添加 |
rpushx | 同rpush,但是必須要保證這個key存在 |
lpop | 返回和移除列表的第一個元素 |
rpop | 返回和移除列表的最後一個元素 |
lrange | 獲取某一個下標區間內的元素 |
llen | 獲取列表元素個數 |
lset lset key index value | 設置某一個位置的元素(替換已有的某個值) |
lindex lindex key index | 獲取某一個位置的元素 |
lrem lrem key 2 xxx | 從列表頭起,刪除對應個數的指定元素 |
ltrim | 保留列表中特定區間內的元素,將其他的元素刪除 |
linsert linsert key after/before old new | 在某一個元素之前,之後插入新元素 |
4.3 Set
指令 | 描述 |
---|---|
sadd | 爲集合添加元素 |
smembers | 顯示集合中所有元素 無序 |
scard | 返回集合中元素的個數 |
spop | 隨機返回並移除一個元素 |
smove smove setFrom setTo xxx | 從一個集合中向另一個集合移動元素 |
srem | 從集合中刪除一個元素 |
sismember set01 值 | 判斷一個集合中是否含有這個元素 |
srandmember | 隨機返回元素,對原始數據沒有影響 |
sdiff set01 set02 | 減去兩個集合中共有的元素 |
sinter | 求交集 |
sunion | 求並集 |
4.4 ZSet
指令 | 描述 |
---|---|
zadd zadd key 10 a 5 b 30 c | 添加一個有序集合元素,根據元素的score分數排序 |
zcard | 返回集合的元素個數 |
zrange | 返回一個範圍內的元素 |
zrangebyscore | 按照分數查找一個範圍內的元素 |
zrank zrank key xx | 返回對應元素的排名 |
zrevrank | 返回對應元素倒序排名 |
zscore zscore key xxx | 顯示某一個元素的分數 |
zrem | 移除某一個元素 |
zincrby zincrby key 10 lining | 給某個特定元素加分,會影響排名 |
4.5 Hash
指令 | 描述 |
---|---|
hset | 設置一個key/value對 |
hget | 獲得一個key對應的value |
hgetall | 獲得所有的key/value對 |
hdel | 刪除某一個key/value對 |
hexists | 判斷一個key是否存在 |
hkeys | 獲得所有的key |
hvals | 獲得所有的value |
hmset | 設置多個key/value |
hmget | 獲得多個key的value |
hsetnx | 設置一個不存在的key的值 |
hincrby hincrby key k 2 | 爲value進行加法運算 |
hincrbyfloat | 爲value加入浮點值 |
5. 持久化
redis是內存型的nosql數據庫,所以數據安全必須考慮,redis支持將數據持久化到磁盤。
5.1 RDB
5.1.1 原理
Snapshotting(RDB)機制的運行原理
1> 在某些時刻,Redis通過fork產生子進程,一個父進程的快照(副本),
其中有和父進程當前時刻相同的數據
2> 父進程繼續處理client請求,子進程負責將快照(數據副本)寫入臨時文件
3> 子進程寫完後,用臨時文件替換原來的快照文件,然後子進程退出。
5.1.2 配置(redis.conf)
save 900 1 #900秒超過1個key被修改
save 300 10 #300秒超過10個key被修改 (刪除所有save項,則會關閉rdb)
dbfilename dump.rdb #快照文件名
stop-writes-on-bgsave-error yes #快照失敗後是否繼續寫操作
rdbcompression yes #是否壓縮快照文件
5.1.3 細節
1> 如果發生系統崩潰,則會丟失最近一次rdb之後的數據,所以如果項目不能接受這樣的數據損失,還需要其他安全手段
2> 不適於實時性持久化,但其數據體量小,執行速度快,適合做數據版本控制
3> 如果數據量巨大,則創建子進程的時間長,導致redis卡頓,要謹慎設置save參數時間間隔大一些;
或如果軟件允許,可以每天在閒時手動同步(凌晨後…)
4> 將生成的快照文件,留在原地,則可以在重啓redis後,保持數據狀態
將生產的快照文件,複製到其他redis服務中,可以方便的將數據移植過去
5.1.4 觸發方式
1> 某一個
save
參數被滿足2> 執行
bgsave
,有子進程去做,需要創建子3> 執行
save
,由主進程親自做,不需要創建子,一般手動操作的時候使用4> redis服務關閉時 shutdown
5.2 AOF
5.2.1 原理
1> Redis將每一個寫操作(執行成功),寫入一個aof文件,
2> Redis重啓時只要從頭到尾執行一次aof文件,即可恢復據;
也可以將aof文件複製到別的服務器,做數據移植
注意:在重啓時,要恢復數據,如果rdb文件和aof文件同時存在,以AOF爲準
5.2.2 配置
appendonly yes # 啓動AOF機制
appendfsync always # 每次收到寫命令就立即強制 寫入磁盤,保證完全的持久化,但產生極大的IO開銷(不推薦使用)
appendfsync everysec # 每秒鐘強制寫入磁盤一次,在性能和持久化方面做了很好的折中(推薦使用)
appendfsync no # 由操作系統決定何時同步,如果系統宕機則導致redis丟失不定數量數據
appendfilename “appendonly.aof” #設置aof文件名
5.2.3 細節
1> AOF文件會不斷增長(可能比快照文件大幾倍),在極端情況下,可能會對硬盤空間造成壓力
2> Redis重啓時,需要重新執行一個可能非常大的AOF,時間會很長
3> AOF同步時間間隔小,數據更安全,理論上至多丟失1秒的數據,比rdb更擅長做更實時的持久化
5.3 AOF重寫
5.3.1 原理
爲了減小aof文件的體量,可以手動發送
bgrewriteaof
命令,則會創建子進程,生成更小體量的aof,然後替換掉舊的、大體量的aof文件。
5.3.2 配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
在體量超過64mb,且比上次重寫後的體量增加了100%時自動觸發重寫
5.3.3 細節
1> 如果當前數據量巨大,則子進程創建過程會很耗時
2> 在替換aof文件時,如果舊aof很大,則刪除它也是一個耗時的過程
- 當子進程將數據轉爲指令並存入新的AOF後,子進程即銷燬
- 父進程會將新的指令放入緩存,然後將其疊加到新的AOF文件
- 父進程會將新的AOF文件替換掉舊的AOF文件
6. 集羣
6.1 原理
在處理大量複雜的數據時,基於主從的
複製( replication )
,將有效保證redis的高性能。一個Redis主服務器,併爲其關聯多個從服務器,主服務器會將自己的數據狀態不斷的同步給從服務器。
所有讀取操作負載到多個從服務器中,主服務器負責寫操作
6.2 配置
複製文件夾redis_slave,然後在其中複製一個redis.conf文件,將裏面的路徑修改,將端口號修改,最後配置如下命令即可
從機中配置如下命令
slaveof 192.168.80.128 7777
#成爲 128的從機
主服務器的所有數據會在初始接收到從服務器的連接時全部發送到從服務器。之後每次主服務器執行完一個寫操作,都會發送到從服務器
7. Java-Redis
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
//注意要關閉虛擬機防火牆 service iptables stop
//另外寫的話就必須只能連接主機
@Test
public void shouldAnswerWithTrue(){
Jedis jedis = new Jedis("192.168.80.128", 7777);
jedis.set("name","張三");
System.out.println(jedis.get("name"));
}
8. Spring-Data-Redis
提供了一套API,實現了一整套Redis操作的方案,使得Java和Redis通信變得極其簡單
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- 需要spring-4.3.10版本 https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis
-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.6.RELEASE</version>
</dependency>
<!--spring測試包,集成了測試單元-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
8.1 配置
將Spring-Data-Reids的核心組件納入工廠
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- 連接池配置,並非連接池本身 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最多空閒連接數 -->
<property name="maxIdle" value="1" />
<!-- 最多有多少連接 -->
<property name="maxTotal" value="5" />
<property name="minIdle" value="1"></property>
<!-- 連接數用完時,是否阻塞,阻塞超過maxWaitMillis會拋出異常 -->
<property name="blockWhenExhausted" value="true" />
<!-- 檢出連接時,最大等待時長 -->
<property name="maxWaitMillis" value="30000" />
<!-- 在檢出時是否檢測 -->
<property name="testOnBorrow" value="false" />
<!-- 空閒時是否檢測連接是否可用 -->
<property name="testWhileIdle" value="false"></property>
<!-- Evict=驅逐 連接至少要空閒多少時間纔會成爲可以被驅逐線程掃描並移除 -->
<property name="minEvictableIdleTimeMillis" value="60000"></property>
<!-- 驅逐線程 兩次驅逐之間要sleep的時間 如果小於0,則不會有驅逐線程,則minEvictableIdleTimeMillis無效-->
<property name="timeBetweenEvictionRunsMillis" value="30000"></property>
<!-- 驅逐線程每次最多掃描幾個連接 -->
<property name="numTestsPerEvictionRun" value="3"></property>
<!-- last in first out 檢出策略 後入先出 或 先入先出 -->
<property name="lifo" value="true"></property>
</bean>
<!-- 連接Factory -->
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<!-- Redis主機 -->
<property name="hostName" value="192.168.80.128"></property>
<property name="port" value="7777"></property>
<!-- 連接池配置信息 -->
<property name="poolConfig" ref="jedisPoolConfig"></property>
</bean>
<!-- 如果沒有設置序列化,則默認使用DefaultSerializer。
聲明序列化組件
-->
<bean id="ss" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
<!--jackjson-->
<!--<bean id="jacks" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>-->
<bean class="com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer" id="fast"></bean>
<!-- RedisTemplate:序列化核心組件 -->
<!--p在文件頭聲明瞭,只是起別名==property,不需要導入Location文件-->
<!--等價於【<property name="keySerializer" ref="ss"/>】-->
<!--redis的各種數據類型都需要序列化,ss和fast和jackson都可以做序列化處理-->
<!--redis中有append key 指令,key必然是string,所以爲了性能,這裏單獨使用【stringSerializer】序列化-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connectionFactory-ref="jedisConnectionFactory"
p:keySerializer-ref="ss"
p:hashKeySerializer-ref="ss"
p:hashValueSerializer-ref="fast"
p:stringSerializer-ref="ss"
p:valueSerializer-ref="fast"/>
</beans>
8.2 測試(Spring集成測試的使用方法)
//注入RedisTemplate,自動轉換爲對應Operation
//測試的運行由spring接管,spring會啓動工廠,並把當前類也納入工廠中,成爲一個組件
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:redis_bean.xml")
public class AppTest
{
@Resource(name = "redisTemplate") //spring-data-redis 會自動 將RedisTemplate轉換爲 XXOperations
private ValueOperations<String,Object> vo;
@Resource(name = "redisTemplate")
private ListOperations<String,Object> lo;
@Resource(name = "redisTemplate")
private SetOperations<String,Object> so;
@Resource(name = "redisTemplate")
private ZSetOperations<String,Object> zo;
@Resource(name = "redisTemplate")
private HashOperations<String,String,Object> ho;
@Test
public void shouldAnswerWithTrue2(){
vo.set("java",new User("張三",new Date()));
User user = (User)vo.get("java");
System.out.println(user);
lo.leftPush("list",new User("張三",new Date()));
so.add("set",new User("李四",new Date()));
zo.add("zset",new User("王五",new Date()),1);
ho.put("hash","user",new User("趙六",new Date()));
}
}
RedisTempalte 可以直接注入給 5中數據類型的 XXOperations引用
@Resource(name = "redisTemplate") //PropertyEditor
private ValueOperations<String,Object> valueOps;
@Resource(name = "redisTemplate") //PropertyEditor
private ListOperations<String,Object> listOps;
@Resource(name = "redisTemplate") //PropertyEditor
private SetOperations<String,Object> setOps;
@Resource(name = "redisTemplate") //PropertyEditor
private ZSetOperations<String,Object> zsetOps;
@Resource(name = "redisTemplate") //PropertyEditor
private HashOperations<String,String,Object> hashOps;
RedisTemplate 有一個子類 StringRedisTemplate
// 如果所有數據都是字符串,則可以選用此子類
public StringRedisTemplate() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
setKeySerializer(stringSerializer);
setValueSerializer(stringSerializer);
setHashKeySerializer(stringSerializer);
setHashValueSerializer(stringSerializer);
}