一、redis學習
01/ nosql介紹
NoSQL:一類新出現的數據庫(not only sql),它的特點:
1、 不支持SQL語法
2、 存儲結構跟傳統關係型數據庫中的那種關係表完全不同,nosql中存儲的數據都是KV形式
3、 NoSQL的世界中沒有一種通用的語言,每種nosql數據庫都有自己的api和語法,以及擅長的業務場景
4、 NoSQL中的產品種類相當多:
a) Mongodb 文檔型nosql數據庫,擅長做CMS系統(內容管理系統)
b) Redis 內存數據庫,數據結構服務器,號稱瑞士軍刀(精巧),只要你有足夠的想象力,它可以還給你無限驚喜
c) Hbase hadoop生態系統中原生的一種nosql數據庫,重量級的分佈式nosql數據庫,用於海量數據的場景
d) Cassandra hadoop生態系統中原生的一種分佈式nosql數據庫,後起之秀
NoSQL和SQL數據庫的比較:
1、適用場景不同:sql數據庫適合用於關係特別複雜的數據查詢場景,nosql反之
2、“事務”特性的支持:sql對事務的支持非常完善,而nosql基本不支持事務
3、兩者在不斷地取長補短,呈現融合趨勢
02/ redis介紹
2.1 簡述
Redis是一個高性能的kv對緩存和內存數據庫(存的不像mysql那樣的表)
Redis的存儲結構就是key-value,形式如下:
注: redis中的value內部可以支持各種數據結構類型,比如可以存入一個普通的string,還可以存list,set,hashmap,sortedSet(有序的set)
2.2 redis應用場景
A、用來做緩存(ehcache/memcached)——redis的所有數據是放在內存中的(內存數據庫)
B、可以在某些特定應用場景下替代傳統數據庫——比如社交類的應用
C、在一些大型系統中,巧妙地實現一些特定的功能:session共享、購物車
只要你有豐富的想象力,redis可以用在可以給你無限的驚喜…….
2.3 redis的特性
1、redis數據訪問速度快(數據在內存中)
2、redis有數據持久化機制(持久化機制有兩種:1、定期將內存數據dump到磁盤;2、aof(append only file)持久化機制——用記日誌的方式記錄每一條數據更新操作,一旦出現災難事件,可以通過日誌重放來恢復整個數據庫)
3、redis支持集羣模式(容量可以線性擴展)
4、redis相比其他緩存工具(ehcach/memcached),有一個鮮明的優勢:支持豐富的數據結構
03/ 安裝redis
3.1 獲取源碼包
1、先去官網(http://redis.io/download )下載一個源碼工程(redis官網版本只支持linux/微軟開源事業部維護了一個windows版本)
2、把安裝包上傳到服務器,解壓縮
3.2 編譯源碼
1、切換到解壓出來的源碼工程目錄中
cd redis-2.6.16
2、用make命令來對redis的c語言源碼工程進行編譯
3、編譯完成之後,用make install命令進行安裝
[root@notrue-centos redis-2.6.16]# make PREFIX=/usr/local/redis install |
安裝成功的顯示:
3.3 啓動redis服務
1、進入redis的bin目錄
2、準備配置文件
Redis服務在啓動的時候可以指定配置文件,那,我們可以從redis的源碼目錄中拷貝一份配置文件模板到redis的安裝目錄,修改後使用
[root@notrue-centos redis-2.6.16]# cp /root/redis-2.6.16/redis.conf /usr/local/redis/ |
3、啓動redis服務
並指定使用的配置文件
4、啓動成功的顯示
5、啓動爲後臺服務
上述啓動方法,會讓redis服務進程運行在console前臺,最好應該放到後臺運行,可將啓動命令改爲如下方式:
1/ 方式一
[root@notrue-centos redis]# nohup bin/redis-server ./redis.conf 1>/dev/null 2>&1 & |
Nohup:控制檯關閉或閒置超時,也不退出
1>/dev/null : 把程序的“1”——標準輸出,重定向到文件/dev/null
2>&1 : 把程序的“2”——錯誤輸出,重定向到“1”所去的文件
& : 把程序放到後臺運行
2/ 方式二
修改配置文件,
vi redis.conf
修改其中一個配置
保存文件後再用普通命令啓動,也可以啓動爲後臺模式
[root@notrue-centos redis]# bin/redis-server ./redis.conf
04/ 客戶端連接
1、 用redis自帶的命令行客戶端
[root@notrue-centos redis]# bin/redis-cli -h notrue-centos -p 6379 redis notrue-centos:6379> ping PONG redis notrue-centos:6379> |
2、 或者用redis的api客戶端連接
新建一個maven工程,導入jedis的maven依賴座標
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.2</version> <type>jar</type> <scope>compile</scope> </dependency> |
然後寫一個類用來測試服務器跟客戶端的連通性:
public class RedisClientConnectionTest { public static void main(String[] args) { // 構造一個redis的客戶端對象 Jedis jedis = new Jedis("pinshutang.zicp.net", 6379); String ping = jedis.ping(); System.out.println(ping); } } |
05/ Redis的數據功能
5.1 String類型的數據
(常作爲緩存使用)
1/插入和讀取一條string類型的數據
redis notrue-centos:6379> set sessionid-0001 "zhangsan" OK redis notrue-centos:6379> get sessionid-0001 "zhangsan" |
2/對string類型數據進行增減(前提是這條數據的value可以看成數字)
DECR key INCR key
DECRBY key decrement INCRBY key increment |
3/一次性插入或者獲取多條數據
MGET key1 key2 MSET key1 value1 key2 value2 ….. |
4/在插入一條string類型數據的同時爲它指定一個存活期限
setex bancao 10 weige ### bancao這條數據就只會存活10秒鐘,過期會被redis自動清除 |
應用:將一個自定義的對象比如product存入redis
實現方式一:將對象序列化成byte數組
/** * 將對象緩存到redis的string結構數據中 * @throws Exception * */ @Test public void testObjectCache() throws Exception{
ProductInfo p = new ProductInfo();
p.setName("蘇菲"); p.setDescription("angelababy專用"); p.setCatelog("unknow"); p.setPrice(10.8);
//將對象序列化成字節數組
ByteArrayOutputStream ba = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(ba);
//用對象序列化流來將p對象序列化,然後把序列化之後的二進制數據寫到ba流中 oos.writeObject(p);
//將ba流轉成byte數組 byte[] pBytes = ba.toByteArray();
//將對象序列化之後的byte數組存到redis的string結構數據中 jedis.set("product:01".getBytes(), pBytes);
//根據key從redis中取出對象的byte數據 byte[] pBytesResp = jedis.get("product:01".getBytes());
//將byte數據反序列出對象 ByteArrayInputStream bi = new ByteArrayInputStream(pBytesResp);
ObjectInputStream oi = new ObjectInputStream(bi);
//從對象讀取流中讀取出p對象 ProductInfo pResp = (ProductInfo) oi.readObject();
System.out.println(pResp);
} |
實現方式二:
將對象轉成json字符串來存取
/** * 將對象轉成json字符串緩存到redis的string結構數據中 */ @Test public void testObjectToJsonCache(){
ProductInfo p = new ProductInfo();
p.setName("ABC"); p.setDescription("劉亦菲專用"); p.setCatelog("夜用型"); p.setPrice(10.8);
//利用gson將對象轉成json串 Gson gson = new Gson(); String pJson = gson.toJson(p);
//將json串存入redis jedis.set("prodcut:02", pJson);
//從redis中取出對象的json串 String pJsonResp = jedis.get("prodcut:02");
//將返回的json解析成對象 ProductInfo pResponse = gson.fromJson(pJsonResp, ProductInfo.class);
//顯示對象的屬性 System.out.println(pResponse);
} |
5.2 List數據結構
5.2.1 List圖示
5.2.2 List功能演示
#從頭部(左邊)插入數據 redis>LPUSH key value1 value2 value3 #從尾部(右邊)插入數據 redis>RPUSH key value1 value2 value3 #讀取list中指定範圍的values redis>LRANGE key start end redis> lrange task-queue 0 -1 讀取整個list #從頭部彈出一個元素 LPOP key #從尾部彈出一個元素 RPOP key #從一個list的尾部彈出一個元素插入到另一個list RPOPLPUSH key1 key2 ## 這是一個原子性操作
|
5.2.3 List的應用案例demo
1 需求描述
任務調度系統:
生產者不斷產生任務,放入task-queue排隊
消費者不斷拿出任務來處理,同時放入一個tmp-queue暫存,如果任務處理成功,則清除tmp-queue,否則,將任務彈回task-queue
2 代碼實現
1/ 生產者
——模擬產生任務
public class TaskProducer {
// 獲取一個redis的客戶端連接對象 public static Jedis getRedisConnection(String host, int port) {
Jedis jedis = new Jedis(host, port);
return jedis;
}
public static void main(String[] args) {
Jedis jedis = getRedisConnection("angelababy", 6379);
Random random = new Random(); // 生成任務 while (true) {
try { // 生成任務的速度有一定的隨機性,在1-2秒之間 Thread.sleep(random.nextInt(1000) + 1000); // 生成一個任務 String taskid = UUID.randomUUID().toString();
// 往任務隊列"task-queue"中插入,第一次插入時,"task-queue"還不存在 //但是lpush方法會在redis庫中創建一條新的list數據 jedis.lpush("task-queue", taskid); System.out.println("向任務隊列中插入了一個新的任務: " + taskid);
} catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }
}
}
} |
2/ 消費者
——模擬處理任務,並且管理暫存隊列
public class TaskConsumer {
public static void main(String[] args) {
Jedis jedis = new Jedis("angelababy", 6379); Random random = new Random();
while (true) {
try { // 從task-queue中取一個任務,同時放入"tmp-queue" String taskid = jedis.rpoplpush("task-queue", "tmp-queue");
// 模擬處理任務 Thread.sleep(1000);
// 模擬有成功又有失敗的情況 int nextInt = random.nextInt(13); if (nextInt % 7 == 0) { // 模擬失敗的情況
// 失敗的情況下,需要將任務從"tmp-queue"彈回"task-queue" jedis.rpoplpush("tmp-queue", "task-queue"); System.out.println("-------任務處理失敗: " + taskid);
} else { // 模擬成功的情況
// 成功的情況下,將任務從"tmp-queue"清除 jedis.rpop("tmp-queue"); System.out.println("任務處理成功: " + taskid);
}
} catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }
}
}
} |
上述機制是一個簡化版,真實版的任務調度系統會更加複雜,如下所示:
(增加了一個專門用來管理暫存隊列的角色,以便就算消費者程序失敗退出,那些處理失敗的任務依然可以被彈回task-queue)
5.3 Hash數據結構
5.3.1 Hash圖示
Redis中的Hashes類型可以看成具有String Key和String Value的map容器
5.3.2 Hash功能演示
1、往redis庫中插入一條hash類型的數據
redis> HSET key field value |
舉例:
redis 127.0.0.1:6379> hset user001:zhangsan iphone 6 (integer) 1 redis 127.0.0.1:6379> hset user001:zhangsan xiaomi 7 (integer) 1 redis 127.0.0.1:6379> hset user001:zhangsan meizu 8 (integer) 1 |
在redis庫中就形成了這樣一條數據:
2、從redis庫中獲取一條hash類型數據的value
ü 取出一條hash類型數據中所有field-value對
redis 127.0.0.1:6379> hgetall user001:zhangsan 1) "iphone" 2) "6" 3) "xiaomi" 4) "7" 5) "meizu" 6) "8" |
ü 取出hash數據中所有fields
redis 127.0.0.1:6379> HKEYS user001:zhangsan 1) "iphone" 2) "xiaomi" 3) "meizu" |
ü 取出hash數據中所有的value
redis 127.0.0.1:6379> hvals user001:zhangsan 1) "6" 2) "7" 3) "8" |
ü 取出hash數據中一個指定field的值
redis 127.0.0.1:6379> hget user001:zhangsan xiaomi "8" |
ü 爲hash數據中指定的一個field的值進行增減
redis 127.0.0.1:6379> HINCRBY user001:zhangsan xiaomi 1 (integer) 8 |
ü 從hash數據中刪除一個字段field及其值
redis 127.0.0.1:6379> hgetall user001:zhangsan 1) "iphone" 2) "6" 3) "xiaomi" 4) "7" 5) "meizu" 6) "8" redis 127.0.0.1:6379> HDEL user001:zhangsan iphone (integer) 1 redis 127.0.0.1:6379> hgetall user001:zhangsan 1) "xiaomi" 2) "7" 3) "meizu" 4) "8" |
5.4 Set數據結構功能
集合的特點:無序、無重複元素
1、 插入一條set數據
redis 127.0.0.1:6379> sadd frieds:zhangsan bingbing baby fengjie furong ruhua tingting (integer) 6 redis 127.0.0.1:6379> scard frieds:zhangsan (integer) 6 redis 127.0.0.1:6379>
|
2、獲取一條set數據的所有members
redis 127.0.0.1:6379> smembers frieds:zhangsan 1) "fengjie" 2) "baby" 3) "furong" 4) "bingbing" 5) "tingting" 6) "ruhua" |
3、判斷一個成員是否屬於某條指定的set數據
redis 127.0.0.1:6379> sismember frieds:zhangsan liuyifei #如果不是,則返回0 (integer) 0 redis 127.0.0.1:6379> sismember frieds:zhangsan baby #如果是,則返回1 (integer) 1 |
4、求兩個set數據的差集
#求差集 redis 127.0.0.1:6379> sdiff frieds:zhangsan friends:xiaotao 1) "furong" 2) "fengjie" 3) "ruhua" 4) "feifei" #求差集,並將結果存入到另一個set redis 127.0.0.1:6379> sdiffstore zhangsan-xiaotao frieds:zhangsan friends:xiaotao (integer) 4 #查看差集結果 redis 127.0.0.1:6379> smembers zhangsan-xiaotao 1) "furong" 2) "fengjie" 3) "ruhua" 4) "feifei" |
5、 求交集,求並集
#求交集 redis 127.0.0.1:6379> sinterstore zhangsan:xiaotao frieds:zhangsan friends:xiaotao (integer) 2 redis 127.0.0.1:6379> smembers zhangsan:xiaotao 1) "bingbing" 2) "baby"
#求並集 redis 127.0.0.1:6379> sunion frieds:zhangsan friends:xiaotao 1) "fengjie" 2) "tangwei" 3) "liuyifei" 4) "bingbing" 5) "ruhua" 6) "feifei" 7) "baby" 8) "songhuiqiao" 9) "furong" 10) "yangmi" |
5.5 sortedSet(有序集合)數據結構
5.5.1 sortedSet圖示
sortedset中存儲的成員都有一個附帶的分數值
而redis就可以根據分數來對成員進行各種排序(正序、倒序)
1、 sortedSet存儲內容示意圖:
5.5.2 SortedSet功能演示
1、往redis庫中插入一條sortedset數據
redis 127.0.0.1:6379> zadd nansheng:yanzhi:bang 70 liudehua 90 huangbo 100 weixiaobao 250 yangwei 59 xiaotao (integer) 5
|
2、 從sortedset中查詢有序結果
#正序結果 redis 127.0.0.1:6379> zrange nanshen:yanzhi:bang 0 4 1) "xiaotao" 2) "liudehua" 3) "huangbo" 4) "weixiaobao" 5) "yangwei" #倒序結果 redis 127.0.0.1:6379> zrevrange nanshen:yanzhi:bang 0 4 1) "yangwei" 2) "weixiaobao" 3) "huangbo" 4) "liudehua" 5) "xiaotao" |
3、 查詢某個成員的名次
#在正序榜中的名次 redis 127.0.0.1:6379> zrank nanshen:yanzhi:bang xiaotao (integer) 0
#在倒序榜中的名次 redis 127.0.0.1:6379> zrevrank nanshen:yanzhi:bang xiaotao (integer) 4 |
4、修改成員的分數
redis 127.0.0.1:6379> zincrby nanshen:yanzhi:bang 300 xiaotao "359" redis 127.0.0.1:6379> zrevrank nanshen:yanzhi:bang xiaotao (integer) 0 |
06/ Redis應用案例(1)
案例:
Lol盒子英雄數據排行榜:
1、 在redis中需要一個榜單所對應的sortedset數據
2、 玩家每選擇一個英雄打一場遊戲,就對sortedset數據的相應的英雄分數+1
3、 Lol盒子上查看榜單時,就調用zrange來看榜單中的排序結果