? Redis
1.NOSQL特點
NoSQL = Not Only SQL(不僅僅是SQL)泛指非關系型資料庫
關系型資料庫:表,行,列
非關系型資料庫:
資料型別的存盤不需要固定的一個格式!不需要過多的操作,就可以橫向擴展;
解耦!
1:方便擴展(資料之間沒有關系,很好擴展);
2:大資料量高性能;
3:資料型別是多樣型的(不需要事先設計資料庫,隨取隨用);
4:傳統關系型資料庫和NoSQL的區別
傳統的 RDBMS
-結構化組織
-SQL
-資料和關系都存在單獨的表中
-資料操作語言,資料定義語言
-嚴格的一致性
-基礎的事務操作
NoSQL
-不僅僅是資料
-沒有固定的查詢語言
-鍵值對存盤,列存盤,檔案存盤,圖形資料庫等
-最終一致性
-CAP定理和BASE
-高性能,高可用,高可擴,,,,
2.NoSQL的分類
KV鍵值對
2.1檔案型資料庫(bson格式和json格式一樣)
-
MongoDB
-
MongoDB是一個介于分布式檔案存盤的資料庫,C++撰寫,主要來處理大量的檔案
-
介于關系型和非關系型資料庫中間的產品,是非關系型資料庫中功能最豐富,最像關系型資料庫的;
-
2.2列存盤資料庫
-
HBase
-
分布式檔案系統
2.3圖關系資料庫
存的不是圖片,是圖片關系
3.Redis入門
3.1.概述
即遠程字典服務,是一個開源的使用ANSI C語言撰寫、支持網路、可基于記憶體亦可持久化的日志型、Key-Value資料庫,并提供多種語言的API
redis會周期性的把更新的資料寫入磁盤或者把修改操作寫入追加的記錄檔案,并且在此基礎上實作了master-slave(主從)同步,也被稱之為結構化資料庫
3.2.Redis能干嘛?
-
記憶體存盤,持久化,記憶體中斷電即失,所以持久化很重要
-
效率高,可以用于高速快取
-
發布訂閱系統,地圖資訊分析,計時器,計數器,,,,
3.3.Redis的特性
多樣的資料型別,持久化,集群,事務,,,,,,
3.4.Linux基本命令
Redis默認安裝路徑
/usr/local/bin
啟動Redis
redis -server kconfig/redis.conf
測驗連接
redis-cli -p 6379
查看Redis行程是否開啟
ps -ef|grep redis
如何關閉Redis服務
shutdown
查看所有key
keys *
確定某個key是否存在
EXISTS key
移除某個key
del key
從某個資料庫移除
del key index
進行資料庫切換
redis默認有16個資料庫,默認使用的是第0個
select index
?
dbsize 查看資料庫大小
清空當前庫
flushdb
清空所有庫
flushall
設定key的過期時間
EXPIRE key 時間
查看當前key的剩余時間
ttl key
判斷當前key的資料型別
type key
3.5.Redis是單執行緒
Redis速度很快,基于記憶體操作,CPU并不是Redis的瓶頸,Redis的瓶頸是根據機器的記憶體和網路帶寬,既然可以使用單執行緒,就使用了單執行緒;
單執行緒指的是網路請求模塊使用了一個執行緒(所以不需考慮并發安全性),即一個執行緒處理所有網路請求,其他模塊仍用了多個執行緒
核心:為什么Redis是的單執行緒還這么快?首先高性能的服務器不一定都是多執行緒的,并且,多執行緒不一定就比單執行緒速度快;Redis是將所有的資料全部放在記憶體中,所以說使用單執行緒的操作效率就是最高的,多執行緒(CPU背景關系切換,競爭條件都是耗時的操作),對于記憶體系統來說,如果沒有背景關系切換效率就是最高的,多次讀寫都是在一個CPU上的,在記憶體情況下,這個就是最佳的方案,
總結:
(1) 絕大部分請求是純粹的記憶體操作(非常快速)
(2) 采用單執行緒,避免了不必要的背景關系切換和競爭條件
(3) 非阻塞IO - IO多路復用
4.Redis五大資料型別
-
String
-
類似的使用場景:value除了是我們的字串還可以是我們的數字
-
計數器
-
統計多單位的數量
-
粉絲數
-
物件快取存盤
1:往String型別資料后面追字串,如果當前key不存在就相當于set key
append key "字串"
2:獲取mougekey的長度
strlen key
3:自增 自減
incr key (自增1)
decr key (自減1)
incrby key number 設定步長指定增量
decrby key number 設定步長減去指定增量
4:獲取字串的范圍(截取字串)
getrange key start end
getrange key 0 -1(查看全部字串)
5:替換字串
setrange key start "XX替換的內容"
6:
setex key second value
(set with expire)設定過期時間,如果存在直接覆寫,再設定過期時間,不存在先新增再設定過期時間;
?
setnx key value
(set if not exist)
不存在再設定,存在則不設定(不覆寫原有值),在分布式鎖中常常使用,
7:批量獲取值和設定值
mset k1 v1 k2 v2 k3 v3.....
mget k1 k2 k3 k4
msetnx k1 v1 k2 v2(不存在再設定,存在就不設定)(原子性操作,要么一起成功,要么一起失敗)
8:
mset user:1:name zhangsan user:1:age 12
mget user:1 name user:1:age
9:
getset key value
先get再set;如果不存在回傳null,然后set會自動創建key;如果存在,回傳已存在的key,然后set會替換掉已存在的值 -
-
List
-
在Redis中,可以把list玩成堆疊,佇列,阻塞佇列
-
所有的list命令都是以l開頭的
-
他實際是一個鏈表,before Node after left right 都可以插入值;
在兩邊插入或者改動值,比處理中間值效率高一些
-
使用場景:訊息排隊;訊息佇列;堆疊
1:存值
lpush key value
2:按范圍取值
lrange key start stop
3:往尾部(右邊)添加值
rpush key value
4:移除串列第一個元素,或最后一個元素(移除左,右的值)
rpop key
5:按下標取值
lindex key index
6:查看list長度
llen key
7:移除指定值
lrem key count value
8:通過下標截取指定長度
ltrim key start stop
9:移除某個串列最后一個值((source是某個串列的key)),添加到新串列中(destination是新串列的key)
rpoplpush source destination
10:按指定下標替換值,不存在的key或者index會報錯
lset key index value
11:往指定key的指定value(pivot)前或者后添加指定值
linsert key BEFORE|AFTER pivot value -
-
SET
-
set中的值是不能重復的
-
無需不重復集合
-
數字集合集,比如B站微博等的共同關注
1:按key存值
sadd key member
2:查看指定key的所有值
smembers key
3:判斷某個key中是否包含某值
sismember key member
4:獲取key中元素的個數
scard key
5:移除key中的指定值
srem key member
6:隨機取key中的值
(count是取出隨機值的個數,不指定個數,隨機抽出一個值)
srandmember key [count]
7:隨機洗掉元素
spop key [count]
8:將一個指定值,移到另一個set集合中(如果目標集合不存在,則會創建一個新的集合)
smove source destination member
9:查集合的差集(以第一個key為參照物)
sdiff key [key ...]
10:查集合的交集
sinter key [key ...]
11:查集合的并集
sunion key [key ...] -
-
Hash(哈希)
-
value值可以想象成一個Map集合,key-map
-
本質和String型別沒有太大區別
-
應用場景:做一些變更資料的保存,或者用戶資訊的保存都可以,Hash更適合物件的存盤,而String更適合字串的存盤
1:存
hset key field value
2:取
hget key field
3:批量存
hmset key field value [field value ...]
4:批量取
hmget key field [field ...]
5:取出所有鍵值對
hgetall key
6:洗掉指定值
hdel key field [field...]
7:獲取指定key的長度
hlen key
8:判斷key中值是否存在
hexists key field
9:只獲得key中所有的field
hkeys keys
10:只獲得key中所有的value
hvals key
11:指定field中的value值自增
(increment是自增的數量,相當于加法)
hincrby key field increment
12:hsetnx key field value -
-
Zset
-
有序集合
-
應用場景:存盤成績,工資表排序等;帶權重進行判斷;排行榜
1:添加
score相當于一個排序標志(會按這個自動排序)
zadd key score member [score member...]
2:查看(正序或者倒序)
zrange key start stop [withscores]
zrevrange key start stop [withscores]
3:從小到大排序
(-inf +inf 負無窮到正無窮)
zrangebyscore key min max [withscores] [limit offset count]
4:移除元素
zrem key member [member...]
5:獲取長度
zcard key
6:獲取指定區間的個數
zcount key min max -
5.三種特殊的資料型別
-
geospatial 地理空間
-
將指定的地理位置(緯度,經度,名稱)添加到指定的key中;這個功能可以推算地理位置的資訊,兩地之間的距離等(java中直接批量匯入城市資訊即可);
-
GEO底層的實作原理就是Zset ,因此我們可以使用Zset命令來操作geo
1:添加指定地理位置(longitude,latitude 經緯度)
南極和北極無法添加;經緯度也有一定的范圍
GEOADD key longitude latitude member [longitude latitude member]
?
2:回傳兩個給定位置之間的距離(計算距離時,會假定地球是個完美的球形)
如果兩個位置之間的其中一個不存在,命令回傳空值;指定單位的引數unit必須是以下單位的其中一個;m(米)km(千米)mi(英里)ft(英尺);如果沒有指定單位,那么默認用米做單位
GEODIST key member1 member2 [unit]
?
3:回傳一個或多個位置元素的Geohash表示(二位經緯度轉換為一維的字串)
GEOHASH key member [member...]
?
4:獲取指定的地理位置資訊
GEOPOS key member
?
5:以給定的經緯度為中心,找出一定半徑內的元素(按坐標為范圍查)
GEORADIUS key longitude latitude redius unit [withcoord] [withdist][withhash] [count]
?
6:找出位于指定范圍內的元素,中心點是由給定的位置元素決定(按城市為范圍查)
GEORADIUSBYMEMBER key member redius unit [withcoord] [withdist][withhash] [count]
?
7:zset的相關命令都可以使用 -
-
hyperloglog
-
基數統計的演算法;
-
優點:占用的記憶體是固定的,而且非常小,2^64僅占用12KB記憶體;有一定的容錯率
-
網頁UV(一個人訪問一個網站多次,但是還算是一個人)
傳統方式,用set保存用戶的id,然后通過統計set中元素的數量作為判斷用戶個數的標準;這種方式如果保存大量的用戶id,就會比較麻煩,我們的目的是為了計數而不是為了保存用戶id;
1:存
pfadd key element [element...]
2:統計元素(兩個key以上時,自動去重計算數量)
pfcount key [key...]
3:合并集合(可以兩個集合合并,也可以新命名一個集合,用于合并其他集合)
pfmerge destkey sourcekey [sourcekey...] -
-
bitmaps
-
位圖 位存盤
-
都是操作二進制位來進行記錄,只有0和1兩種狀態
-
對于只有兩種狀態的,比如,用戶打卡沒打卡,登錄沒登錄等,都可以使用bitmaps
1:存 value只有0或者1,offset偏移量,位數,可以從0開始
setbit key offset value
2:查看某一天,某個偏移量的狀態
getbit key offset
3:統計(統計為1的數量)
bitcount key [start end] -
6.事務
-
Redis單條命令是保證原子性的,但是Redis的事務是不保證原子性的;Redis事務沒有隔離級別的概念
所有命令在事務中,并沒有直接被執行,只有發起執行命令的時候才會被執行;
-
Redis事務的本質:一組命令的集合;一個事務中的所有命令都會被序列化,在事務執行程序中,會被按照順序執行;一次性,順序性,排他性;
-------佇列set set set 執行----- -
Redis的事務:
- 開啟事務(命令:multi)
- 命令入隊(添加相關命令)
- 執行事務(命令:exec) -
中途放棄事務(事務佇列中的命令都不會執行)
dicard -
事務中的例外
類比java
-
編譯型例外(代碼有問題;命令有錯),事務中的所有命令都不會執行;
-
運行時例外(1/0),如果事務佇列中存在語法型錯誤,其他命令可以正常執行;錯誤命令會拋出例外;
-
7.監控
一旦事務執行成功,監控就會自動取消掉;當做樂觀鎖操作
Watch 監視
unwatch 放棄監視
多執行緒修改值,事務開啟之前進行監視;一旦有其他執行緒在途中修改監視值,最后事務會提交失敗;舉例(開啟兩個Redis連接即可簡單測驗)
執行緒1
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
此時執行緒2進入
127.0.0.1:6379> get money
"100"
127.0.0.1:6379> incrby money 1000
(integer) 1100
127.0.0.1:6379>
再回到執行緒1結束事務(回傳nil說明事務執行失敗;可以先解鎖unwatch,再獲取鎖watch)
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379>
8.Jedis
-
Jedis是Redis官方推薦的Java連接開發工具;使用Java操作Redis中間件;
-
匯入相關依賴
<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.3.0</version> </dependency> <!--fastjson--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.75</version> </dependency> -
編碼測驗
-
連接資料庫
-
操作命令
-
斷開連接
public class TestPing { public static void main(String[] args) { //new Jedis物件即可 Jedis jedis =new Jedis("127.0.0.1",6379); //Jedis所有的命令 之前的指令在這里就是一個個方法 System.out.println(jedis.ping()); //關閉連接 jedis.close(); } } 輸出:PONG -
-
事務代碼簡單示例
public class TestTX {
public static void main(String[] args) {
//連接
Jedis jedis = new Jedis("127.0.0.1",6379);
//開啟事務
Transaction mutil = jedis.multi();
try {
//命令操作
mutil.set("k1","1");
mutil.set("k2","2");
mutil.set("k3","3");
//執行事務
mutil.exec();
}catch (Exception e) {
//放棄事務
mutil.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.mget("k1","k2","k3"));
//關閉連接
jedis.close();
}
}
}
9.常用API
五大資料型別,三種特殊資料型別;指令就是方法
10.SpringBoot整合
-
說明:在SpringBoot2.x之后,原來使用的jedis被替換成了lettuce
jedis:底層采用的是直連,如果多執行緒操作,是不安全的,如果想要避免不安全,使用jedis pool連接池!(更像BIO模式)
lettuce:采用netty(物體類必須序列化),實體可以在多個執行緒中共享,不存在執行緒不安全的情況,可以減少執行緒數量;(更像NIO模式)
-
原始碼分析:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
?
@Bean
@ConditionalOnMissingBean( name = {"redisTemplate"})
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
//這個注解的意思是,當這個bean不存在的時候,這個類就生效,所以也就是說,我們可以自定義一個redisTemplate來替換這個默認的redisTemplate
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默認的RedisTemplate 沒有過多的設定,redis 物件都是需要序列化!
//兩個泛型都是object型別,后面使用需要強制轉換
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
?
@Bean
@ConditionalOnMissingBean//由于String是Redis中最常使用的型別,所以說原始碼單獨提出來一個bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}-
整合測驗(RedisProperties.class原始碼中,可以看到配置相關的資訊)
-
下面測驗使用的是默認的api,實際開發中都是使用自己重新封裝的工具
-
匯入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>-
配置連接(連接配置相關資訊,建議都使用lettuce相關;因為jedis某些配置在springboot中不生效了)
spring.redis.host=127.0.0.1
spring.redis.port=6379-
測驗
@SpringBootTest
class RedisSpringbootApplicationTests {
?
@Autowired
private RedisTemplate redisTemplate;
?
@Test
void contextLoads() {
//實際企業開發的程序中,都不會使用這種原生的方式;一般使用工具類,RedisUtils等;
/**
* redisTemplate 操作不同資料型別 api和學的指令是一樣的
* opsForValue 操作字串 類似String
* opsForList 操作List 類似List
* ......
*/
/**
* 除了上述基本操作,常用的方法都可以通過redisTemplate來操作,事務,CRUD;redisTemplate都直接列出來了,可以直接使用
* 比如:redisTemplate.multi();
*/
?
/**
* 獲取redis的連接物件
* RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
* connection.flushDb();
* connection.flushAll();
*/
redisTemplate.opsForValue().set("test", "jjjjj");
System.out.println(redisTemplate.opsForValue().get("test"));
//實際開發一般使用json來傳遞物件;傳遞物件時,所有的物件都需要序列化,不然會報錯
@Test
public void test1() {
User user = new User();
user.setName("jpc");
user.setAge("3");
String us = null;
//下面是兩種轉String的方式,都可以實作自動序列化;或者物件直接implements Serializable也可以
// try {
// us = new ObjectMapper().writeValueAsString(user);
// } catch (Exception e) {
// e.printStackTrace();
// }
us =JSONObject.toJSONString(user);
redisTemplate.opsForValue().set("uesr",us);
System.out.println(redisTemplate.opsForValue().get("uesr"));
}
}
} -
-
序列化
從RedisTemplate原始碼中可以看到,默認是通過jdk序列化的
this.defaultSerializer = new JdkSerializationRedisSerializer
(命令列中容易轉義,導致亂碼);我們可以重新寫一個redisTemplate配置類來代替默認的redisTemplate
@Configuration
public class RedisConfig {
?
//撰寫我們自己的 redisTemplate;下面模板可以直接使用
//格式可以按照原始碼中的格式處理
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate();
//默認的連接工廠
template.setConnectionFactory(redisConnectionFactory);
//下面是重新配置redisTemplate具體的序列化方式
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//用ObjectMapper轉義
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer s = new StringRedisSerializer();
//key采用String序列化方式
template.setKeySerializer(s);
//hash采用String序列化方式
template.setKeySerializer(s);
//value采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value
template.setHashKeySerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
} -
企業開發中一般使用到工具類,公司一般自己封裝,
?
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
?
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
?
@Component
public final class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
?
// =============================common============================
/**
* 指定快取失效時間
* @param key 鍵
* @param time 時間(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
?
/**
* 根據key 獲取過期時間
* @param key 鍵 不能為null
* @return 時間(秒) 回傳0代表為永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
?
/**
* 判斷key是否存在
* @param key 鍵
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
?
/**
* 洗掉快取
* @param key 可以傳一個值 或多個
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(String.valueOf(CollectionUtils.arrayToList(key)));
}
}
}
?
// ============================String=============================
?
-
