1 redis的介紹
為了解決高并發、高可擴充(叢集)、高可用(不能當機)、大資料存儲問題而産生的資料庫解決方案,就是NoSql資料庫。
NoSql資料庫:全稱 not only sql ,非關系型資料庫。可以作為關系型資料庫的一個很好的補充。不能替代。
Redis是用C語言開發的一個開源的高性能鍵值對(key-value)資料庫(nosql)。它通過提供多種鍵值資料類型來适應不同場景下的存儲需求,目前為止Redis支援的鍵值資料類型有5種。如下:
字元串類型 (String)
散列類型(hash)
清單類型(List)
集合類型(set)
有序集合類型(SortedSet)
redis的功能強大,有豐富的應用場景,如下:
緩存。
分布式叢集架構中的session分離。
任務隊列。(秒殺、搶購、12306等等)
應用排行榜。(SortedSet)
網站通路統計。
資料過期處理。(expire)
詳情可以參考官網:https://redis.io/topics/benchmarks
2 Redis的安裝
安裝redis需要c語言的編譯環境,如果沒有gcc需要線上安裝。如下指令:
[[email protected] ~]# yum -y install gcc-c++
如果有GCC環境,隻需輸入指令:
[[email protected] ~]# gcc
出現 :gcc: no input files 表示安裝成功。
安裝步驟:
第一步:将redis的源碼包上傳到linux系統。
第二步:解壓縮redis的源碼包。
第三步:進行編譯。 cd到解壓後的目錄 輸入指令:make
第四步:進行安裝。 輸入指令:make install PREFIX=/usr/local/redis
第五步:檢查目錄是否存在。
在/usr/local/redis 下 有bin 說明安裝成功。
3 連接配接Redis
3.1 redis服務端啟動
前端啟動:
[[email protected] bin]# ./redis-server
背景啟動:
第一步:把/root/redis-3.0.0/redis.conf複制到/usr/local/redis/bin目錄下
[[email protected] redis-3.0.0]# cp redis.conf /usr/local/redis/bin/
第二步:使用vim指令修改redis.conf配置檔案 将daemonize no修改為daemonize yes
第三步:輸入啟動指令
[[email protected] bin]# ./redis-server redis.conf
第四步:檢查redis程序:
[[email protected] bin]# ps -ef|grep redis
前端啟動,不能更換終端,影響下一步操作。而背景啟動,隻在程序中悄悄啟動。
推薦使用背景啟動。
3.2 用戶端Redis-cli連接配接redis
使用Redis-cli建立連接配接:
[[email protected] bin]# ./redis-cli
預設連接配接localhost運作在6379端口的redis服務。
[[email protected] bin]# ./redis-cli -h 192.168.25.153 -p 6379
-h:連接配接的伺服器的位址
-p:服務的端口号
–> 使用redis的桌面程式建立連接配接
安裝:
退出連接配接:
第一種:
[[email protected] bin]# ./redis-cli
127.0.0.1:6379> quit
第二種:
[[email protected] bin]# ./redis-cli
127.0.0.1:6379> exit
第三種:CTR+C
[[email protected] bin]#
3.3 關閉Redis服務
第一種:通過連接配接上用戶端進行關閉,使用shutdown 指令。
第二種:使用 kill 指令。
找到對應的redis的程序id 然後使用指令:(pid為程序id)
kill -9 pid
4 Redis五種資料類型
4.1. String:key-value
redis指令不區分大小寫,但是key區分的
redis中的資料都是字元串。
redis是單線程,(不适合存儲比較大的資料)
使用incr 指令,如果key 不存在,會自動建立key 并自動+1.
redis中所有的資料都是字元串。
set key value 設定值
get key 擷取值
incr key 加一
decr key 減一
4.2. Hash: key-field-value
相當于一個key 對應一個map (map中又是key- value),應用歸類
hset key field value
hget key field
hincrby key field num
4.3. List
List是有順序可重複(資料結構中的:雙連結清單,隊列)
可作為連結清單 ,從左添加元素 也可以從右添加元素。
lpush list a b c d (從左添加元素)
rpush list 1 2 3 4 (從右邊添加元素)
lrange list 0 -1 (從0 到 -1 元素檢視:也就表示檢視所有)
lpop list (從左邊取,删除)
rpop list (從右邊取,删除)
4.4. Set
Set無順序,不能重複
sadd set1 a b c d d (向set1中添加元素) 元素不重複
smembers set1 (查詢元素)
srem set1 a (删除元素)
4.5. SortedSet(zset)
有順序,不能重複
适合做排行榜 排序需要一個分數屬性
zadd zset1 9 a 8 c 10 d 1 e (添加元素 zadd key score member )
(ZRANGE key start stop [WITHSCORES])(檢視所有元素:zrange key 0 -1 withscores)
如果要檢視分數,加上withscores.
zrange zset1 0 -1 (從小到大)
zrevrange zset1 0 -1 (從大到小)
zincrby zset2 score member (對元素member 增加 score)
127.0.0.1:6379> zadd zset1 8 a 4 b 5 c 1 d
(integer) 4
127.0.0.1:6379> zrange zset1 0 -1
1) "d"
2) "b"
3) "c"
4) "a"
127.0.0.1:6379> zadd zset1 9 a
(integer) 0
127.0.0.1:6379> zrange zset1 0 -1
1) "d"
2) "b"
3) "c"
4) "a"
127.0.0.1:6379> zrange zset1 0 -1 withscores
1) "d"
2) "1"
3) "b"
4) "4"
5) "c"
6) "5"
7) "a"
8) "9"
127.0.0.1:6379> zrevrange zset1 0 -1
1) "a"
2) "c"
3) "b"
4) "d"
127.0.0.1:6379> zincrby zset1 1 a
"10"
127.0.0.1:6379> zrevrange zset1 0 -1 withscores
1) "a"
2) "10"
3) "c"
4) "5"
5) "b"
6) "4"
7) "d"
8) "1"
5 key 指令
expire key second (設定key的過期時間)
ttl key (檢視剩餘時間)(-2 表示不存在,-1 表示已被持久化,正數表示剩餘的時間)
persist key (清除過期時間,也即是持久化 持久化成功體提示 1 不成功0)。
del key: 删除key
EXISTS key, 若key存在,傳回1,否則傳回0。
select 0 表示:選擇0号資料庫。預設是0号資料庫
6 Redis叢集的搭建
至少3個節點,為了叢集的高可用,為每一個節點增加一個備份機。(6台伺服器)。
搭建僞分布式叢集方案:在一台機器裡面運作6個redis執行個體。端口需要不同(7001-7006)
6.1 叢集搭建環境
1、使用ruby腳本搭建叢集。需要ruby的運作環境。
安裝ruby:
yum install ruby
yum install rubygems
2、上傳redis-3.0.0.gem到 linux中
3、安裝ruby運作時所使用的包
[[email protected] ~]# gem install redis-3.0.0.gem
Successfully installed redis-3.0.0
1 gem installed
Installing ri documentation for redis-3.0.0...
Installing RDoc documentation for redis-3.0.0...
[[email protected] ~]#
[[email protected] ~]# cd redis-3.0.0/src
[[email protected] src]# ll *.rb
-rwxrwxr-x. 1 root root 48141 Apr 1 2015 redis-trib.rb
6.2 搭建步驟
需要6台redis伺服器。搭建僞分布式。
需要6個redis執行個體。
需要運作在不同的端口7001-7006
使用之前搭建好的redis執行個體 。
注意:搭建前 如果節點裡有資料,需要删除(rdb檔案,aof檔案)。
第一步:
建立6個redis執行個體,每個執行個體運作在不同的端口。需要修改redis.conf配置檔案。配置檔案中還需要把cluster-enabled yes前的注釋去掉。
建立目錄:
[[email protected] local]# mkdir redis-cluster
copy 之前搭建好的redis 并改名為redis01
[[email protected] local]# cp redis/ redis-cluster/redis01 -r
進入redis-cluster目錄中cd到redis01的bin目錄,删除資料檔案
[[email protected] local]# cd redis-cluster/redis01/bin
[[email protected] bin]# rm -rf *.rdb *.aof
修改redis.conf,取消注釋
[[email protected] bin]# vim redis.conf
cd到redis-cluster目錄
copy六份并分别命名為redis02,redis03,redis04,redis05,redis06
[[email protected] redis-cluster]# cp redis01 redis02 -r
[[email protected] redis-cluster]# cp redis01 redis03 -r
[[email protected] redis-cluster]# cp redis01 redis04 -r
[[email protected] redis-cluster]# cp redis01 redis05 -r
[[email protected] redis-cluster]# cp redis01 redis06 -r
cd到每一個執行個體的bin目錄,修改每一個redis執行個體的端口分别改為7001-7006
[[email protected] bin]# vim redis.conf
第二步:
啟動每個redis執行個體。
vim redis-cluster-start-all.sh
添加如下文字到檔案中:
cd /usr/local/redis-cluster/redis01/bin
./redis-server redis.conf
cd /usr/local/redis-cluster/redis02/bin
./redis-server redis.conf
cd /usr/local/redis-cluster/redis03/bin
./redis-server redis.conf
cd /usr/local/redis-cluster/redis04/bin
./redis-server redis.conf
cd /usr/local/redis-cluster/redis05/bin
./redis-server redis.conf
cd /usr/local/redis-cluster/redis06/bin
./redis-server redis.conf
修改檔案:redis-cluster-start-all.sh 的權限,讓其可執行。
chmod u+x redis-cluster-start-all.sh
執行啟動
[[email protected] redis-cluster]# ./redis-cluster-start-all.sh
第三步:
使用ruby腳本搭建叢集。
從拷貝rb檔案到redis-cluster目錄中
執行建立:
./redis-trib.rb create --replicas 1 192.168.25.153:7001 192.168.25.153:7002 192.168.25.153:7003 192.168.25.153:7004 192.168.25.153:7005 192.168.25.153:7006
第四步:
建立關閉叢集的腳本:(不是必須的)
首先使用:vim 指令建立一個檔案 redis-cluster-stop-all.sh
編輯檔案,添加如下:
cd /usr/local/redis-cluster/redis01/bin
./redis-cli -p 7001 shutdown
cd /usr/local/redis-cluster/redis02/bin
./redis-cli -p 7002 shutdown
cd /usr/local/redis-cluster/redis03/bin
./redis-cli -p 7003 shutdown
cd /usr/local/redis-cluster/redis04/bin
./redis-cli -p 7004 shutdown
cd /usr/local/redis-cluster/redis05/bin
./redis-cli -p 7005 shutdown
cd /usr/local/redis-cluster/redis06/bin
./redis-cli -p 7006 shutdown
修改檔案:redis-cluster-stop-all.sh 的權限,讓其可執行。
chmod u+x redis-cluster-stop-all.sh
6.3 叢集的使用方法
Redis-cli連接配接叢集。
[[email protected] redis-cluster]# redis01/bin/redis-cli -p 7002 -c
-c:代表連接配接的是redis叢集
使用指令操作redis是和單機版的一樣。
7 Jedis
需要把jedis依賴的jar包添加到工程中。Maven工程中需要把jedis的坐标添加到依賴。推薦添加到服務層。
添加如下坐标到服務層中的pom.xml檔案中
<!-- Redis用戶端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<!--<version>${jedis.version}</version>-->
</dependency>
7.1 連接配接單機版
第一步:建立一個Jedis對象。需要指定服務端的ip及端口。
第二步:使用Jedis對象操作資料庫,每個redis指令對應一個方法。
第三步:列印結果。
第四步:關閉Jedis
@Test
public void testJedis() throws Exception {
// 第一步:建立一個Jedis對象。需要指定服務端的ip及端口。
Jedis jedis = new Jedis("192.168.25.153", 6379);
// 第二步:使用Jedis對象操作資料庫,每個redis指令對應一個方法。
String result = jedis.get("hello");
// 第三步:列印結果。
System.out.println(result);
// 第四步:關閉Jedis
jedis.close();
}
7.2 使用連接配接池連接配接單機版
第一步:建立一個JedisPool對象。需要指定服務端的ip及端口。
第二步:從JedisPool中獲得Jedis對象。
第三步:使用Jedis操作redis伺服器。
第四步:操作完畢後關閉jedis對象,連接配接池回收資源。
第五步:關閉JedisPool對象。
@Test
public void testJedisPool() throws Exception {
// 第一步:建立一個JedisPool對象。需要指定服務端的ip及端口。
JedisPool jedisPool = new JedisPool("192.168.25.153", 6379);
// 第二步:從JedisPool中獲得Jedis對象。
Jedis jedis = jedisPool.getResource();
// 第三步:使用Jedis操作redis伺服器。
jedis.set("jedis", "test");
String result = jedis.get("jedis");
System.out.println(result);
// 第四步:操作完畢後關閉jedis對象,連接配接池回收資源。
jedis.close();
// 第五步:關閉JedisPool對象。
jedisPool.close();
}
7.3 連接配接叢集版
第一步:使用JedisCluster對象。需要一個Set參數。Redis節點的清單。
第二步:直接使用JedisCluster對象操作redis。在系統中單例存在。
第三步:列印結果
第四步:系統關閉前,關閉JedisCluster對象。
@Test
public void testJedisCluster() throws Exception {
// 第一步:使用JedisCluster對象。需要一個Set<HostAndPort>參數。Redis節點的清單。
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.25.153", 7001));
nodes.add(new HostAndPort("192.168.25.153", 7002));
nodes.add(new HostAndPort("192.168.25.153", 7003));
nodes.add(new HostAndPort("192.168.25.153", 7004));
nodes.add(new HostAndPort("192.168.25.153", 7005));
nodes.add(new HostAndPort("192.168.25.153", 7006));
JedisCluster jedisCluster = new JedisCluster(nodes);
// 第二步:直接使用JedisCluster對象操作redis。在系統中單例存在。
jedisCluster.set("hello", "100");
String result = jedisCluster.get("hello");
// 第三步:列印結果
System.out.println(result);
// 第四步:系統關閉前,關閉JedisCluster對象。
jedisCluster.close();
}
8 向業務邏輯中添加緩存
因為叢集是比較消耗成本的,是以在公司中,一般生産環境使用叢集,開發環境使用單機版。
我們在項目整合中都需要有。
可以開發一個接口,有單機版的實作類和叢集版的實作類。使用時可以面向接口開發,不影響業務邏輯,使用spring管理實作類,部署時切換實作類即可。
8.1 接口封裝
常用的操作redis的方法抽取出一個接口,分别對應單機版和叢集版建立兩個實作類。
8.1.1 接口定義
public interface JedisClient {
String set(String key, String value);
String get(String key);
Boolean exists(String key);
Long expire(String key, int seconds);
Long ttl(String key);
Long incr(String key);
Long hset(String key, String field, String value);
String hget(String key, String field);
Long hdel(String key, String... field);
}
8.1.2 單機版實作類
public class JedisClientPool implements JedisClient {
@Autowired
private JedisPool jedisPool;
@Override
public String set(String key, String value) {
Jedis jedis = jedisPool.getResource();
String result = jedis.set(key, value);
jedis.close();
return result;
}
@Override
public String get(String key) {
Jedis jedis = jedisPool.getResource();
String result = jedis.get(key);
jedis.close();
return result;
}
@Override
public Boolean exists(String key) {
Jedis jedis = jedisPool.getResource();
Boolean result = jedis.exists(key);
jedis.close();
return result;
}
@Override
public Long expire(String key, int seconds) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.expire(key, seconds);
jedis.close();
return result;
}
@Override
public Long ttl(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.ttl(key);
jedis.close();
return result;
}
@Override
public Long incr(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.incr(key);
jedis.close();
return result;
}
@Override
public Long hset(String key, String field, String value) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.hset(key, field, value);
jedis.close();
return result;
}
@Override
public String hget(String key, String field) {
Jedis jedis = jedisPool.getResource();
String result = jedis.hget(key, field);
jedis.close();
return result;
}
@Override
public Long hdel(String key, String... field) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.hdel(key, field);
jedis.close();
return result;
}
}
8.1.3 applicationContext-redis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util4.2.xsd">
<!-- 配置單機版的連接配接 -->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="6379"></constructor-arg>
</bean>
<bean id="jedisClientPool" class="com.taotao.jedis.JedisClientPool"/>
</beans>
8.1.4 叢集版實作類
package com.taotao.jedis;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.JedisCluster;
public class JedisClientCluster implements JedisClient {
@Autowired
private JedisCluster jedisCluster;
@Override
public String set(String key, String value) {
return jedisCluster.set(key, value);
}
@Override
public String get(String key) {
return jedisCluster.get(key);
}
@Override
public Boolean exists(String key) {
return jedisCluster.exists(key);
}
@Override
public Long expire(String key, int seconds) {
return jedisCluster.expire(key, seconds);
}
@Override
public Long ttl(String key) {
return jedisCluster.ttl(key);
}
@Override
public Long incr(String key) {
return jedisCluster.incr(key);
}
@Override
public Long hset(String key, String field, String value) {
return jedisCluster.hset(key, field, value);
}
@Override
public String hget(String key, String field) {
return jedisCluster.hget(key, field);
}
@Override
public Long hdel(String key, String... field) {
return jedisCluster.hdel(key, field);
}
}
8.1.5 Spring的配置:
添加此配置到applicationContext-redis.xml中:
<!-- 叢集版的配置 -->
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg>
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7001"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7002"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7003"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7004"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7005"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
<constructor-arg name="port" value="7006"></constructor-arg>
</bean>
</set>
</constructor-arg>
</bean>
<bean id="jedisClientCluster" class="com.taotao.jedis.JedisClientCluster"/>
注意:單機版和叢集版不能共存,使用單機版時注釋叢集版的配置。使用叢集版,把單機版注釋。
8.2 封裝代碼測試
@Test
public void testJedisClient() throws Exception {
//初始化Spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-*.xml");
//從容器中獲得JedisClient對象
JedisClient jedisClient = applicationContext.getBean(JedisClient.class);
jedisClient.set("first", "100");
String result = jedisClient.get("first");
System.out.println(result);
}
8.3 添加緩存
查詢内容清單時添加緩存。
1、查詢資料庫之前先查詢緩存。
2、查詢到結果,直接響應結果。
3、查詢不到,緩存中沒有需要查詢資料庫。
4、把查詢結果添加到緩存中。
5、傳回結果。
向redis中添加緩存,使用hash對key進行歸類。:
Key:categoryId
field:cid
Value:内容清單。需要把java對象轉換成json。
注意:添加緩存不能影響正常業務邏輯。
8.3.2 代碼實作
在ContentServiceImpl實作類中添加緩存,
部分代碼如下:
@Autowired
private JedisClient jedisClient;
@Value("${CONTENT_KEY}")
private String CONTENT_KEY;
@Override
public List<TbContent> getContentList(long cid) {
//查詢緩存
try {
String json = jedisClient.hget(CONTENT_KEY, cid + "");
//判斷json是否為空
if (StringUtils.isNotBlank(json)) {
//把json轉換成list
List<TbContent> list = JsonUtils.jsonToList(json, TbContent.class);
return list;
}
} catch (Exception e) {
e.printStackTrace();
}
//根據cid查詢内容清單
TbContentExample example = new TbContentExample();
//設定查詢條件
Criteria criteria = example.createCriteria();
criteria.andCategoryIdEqualTo(cid);
//執行查詢
List<TbContent> list = contentMapper.selectByExample(example);
//向緩存中添加資料
try {
jedisClient.hset(CONTENT_KEY, cid + "", JsonUtils.objectToJson(list));
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
屬性檔案所在的位置:
8.4 緩存同步
對内容資訊做增删改操作後隻需要把對應緩存key删除即可。
以添加内容為例,可以根據categoryId删除。
@Override
public TaotaoResult addContent(TbContent content) {
//補全屬性
content.setCreated(new Date());
content.setUpdated(new Date());
//插入資料
contentMapper.insert(content);
//緩存同步
jedisClient.hdel(CONTENT_KEY, content.getCategoryId().toString());
return TaotaoResult.ok();
}