1. 程式人生 > >Redis從入門到踩坑

Redis從入門到踩坑

背景

Redis在網際網路專案的使用也是非常普遍的,作為最常用的NO-SQL資料庫,對Redis的瞭解已經成為了後端開發的必備技能。小編對Redis的使用時間不長,但是專案中確兩次踩中了Redis的坑,今天特意從基礎知識層面到實戰層面對Redis知識進行梳理,能夠達到對Redis的知識體系有更全面和深入的理解。

Redis的特點

優點:
  1. Key-Value型別的記憶體資料庫,是加強版的Memcached。
  2. 整個資料庫都是在記憶體中進行操作的,並且定期非同步持久化資料到硬碟上進行儲存。
  3. 在記憶體中進行操作,不存在磁碟IO,效能方面是非常出色的,讀取操作處理速度可以超過10萬次每秒,是已知效能最快的Key-Value 資料庫。
  4. Redis還提供豐富的資料結構型別。
  5. Redis利用佇列技術將併發訪問變為序列訪問,消除了傳統資料庫序列控制的開銷。
缺點:
  1. 資料庫容量受到實體記憶體的限制,不能用作海量資料的高效能讀寫,因此Redis適合的場景主要侷限在較小資料量的高效能操作和運算上。

Redis和Memcached的比較

這裡寫圖片描述

Redis的常用資料結構及使用場景

String

String是Redis最基本的型別,一個key對應一個value,也是最常用的資料結構,在定義每個String的key的時候,記得加上字首。一個Key最大能儲存512MB,一個Value最大能儲存1G。

Set

Redis的Set是string型別的無序集合,集合是通過雜湊表實現的,所以新增、刪除和查詢的複雜度都是O(1)。Set集合取交集、差集和並集可以完成兩組資料的比較,所以Redis借用Set資料結構常用於兩組資料的比較。

ZSet

Redis ZSet和Set一樣也是String型別元素的結合,並且不允許重複的成員。不同的是ZSet中每個元素都會關聯一個double型別的分數,Redis通過分數(score)為集合中的所有元素進行大小排序。注意ZSet的成員是唯一的,但分數(score)卻可以重複。常用語排行榜、分頁查詢和獲取指定範圍資料等應用場景。

Hash

Redis hash是一個string型別的field和value的對映表,hash特別適合用於儲存物件。

List

Redis 列表是簡單的字串列表,按照插入順序排序。你可以新增一個元素到列表的頭部(左邊)或者尾部(右邊)。常用於構建非同步佇列。

Redis實現分散式鎖

場景:

使用者在使用APP的時候,頁面非常的卡頓,就會隨便狂點,由於介面沒有做重複提交,會出現好幾個相同的請求,在service層,一個執行緒沒有insert完成,另一個執行緒一查,空的。於是也插入一條進來。原本每個人一條的,某個業務員出現了三條,導致業務邏輯錯誤。在業務邏輯中經常會有先查詢判空再插入的場景,但是在高併發的時候,很容易出現插入記錄重複的情況。

為了保證一個方法或屬性在高併發情況下的同一時間只能被同一個執行緒執行,在傳統單體應用單機部署的情況下,可以使用Java併發處理相關的API(如ReentrantLock或Synchronized)進行互斥控制。但是隨著業務發展的需要,原單機部署的系統被演化成分散式集群系統後,由於分散式系統多執行緒、多程序並且分佈在不同機器上,這將使原單機部署情況下的併發控制鎖策略失效,單純的Java API並不能提供分散式鎖的能力。為了解決這個問題就需要一種跨JVM的互斥機制來控制共享資源的訪問,這就是分散式鎖要解決的問題!

分散式鎖機制常用的有三種方式,redis分散式鎖、zookeeper和資料庫表。
在這裡簡單介紹基於redis的分散式鎖。

  1. setnx
    setnx key val:當且僅當key不存在時,set一個key為val的字串,返回1;若key存在,則什麼都不做,返回0。

  2. expire
    expire key timeout : 設定key為一個超時時間,單位為second,超過這個時間鎖就會自動釋放,避免出現由於客戶端crash,不釋放鎖,導致死鎖的現象。

  3. delete
    delete key : 刪除key

  4. 實現思想
    (1)獲取鎖的時候,使用setnx加鎖,並使用expire命令為鎖新增一個超時時間,超過該時間則自動釋放鎖,鎖的value值為一個隨機生成的UUID,通過此在釋放鎖的時候進行判斷。
    (2)獲取鎖的時候還設定一個獲取的超時時間,若超過這個時間則放棄獲取鎖。
    (3)釋放鎖的時候,通過UUID判斷是不是該鎖,若是該鎖,則執行delete進行鎖釋放。

此處需要補上相關的程式碼

這裡寫圖片描述

這裡寫圖片描述

上圖所示為採用redis快取實現分散式系統下,分散式鎖的效果圖。

序列化和反序列化

實體物件等存入到Redis資料庫中,並不是直接儲存的,是以byte陣列的形式儲存的,所以儲存到Redis中的時候,需要序列化成byte資料,從Redis讀取資料的時候,需要進行反序列化操作。
spring-data-redis包中存在

public interface RedisSerializer<T> {
    byte[] serialize(T var1) throws SerializationException;
    T deserialize(byte[] var1) throws SerializationException;
}

實現此介面的類有如下:

  1. GenericToStringSerializer
    可以將任何物件泛化為字串並序列化

  2. StringRedisSerializer
    簡單的字串序列化

  3. JdkSerializationRedisSerializer
    JDK提供的序列化功能,被序列化的物件必須實現Serializable介面。
    優點: 優點是反序列化時不需要提供型別資訊(class),並且速度最快。
    缺點: 序列化後的結果非常龐大,是JSON格式的5倍左右,這樣就會消耗redis伺服器的大量記憶體,且通過redis客戶端也不容易閱讀。

  4. JacksonJsonRedisSerializer、Jackson2JsonRedisSerializer 和GenericJackson2JsonRedisSerializer
    使用Jackson庫將物件序列化為JSON字串。
    優點: 速度快,序列化後的字串短小精悍,並且易於閱讀。
    缺點: 但缺點也非常致命,那就是此類的建構函式中有一個型別引數,必須提供要序列化物件的型別資訊(.class物件)。

專案中Redis的踩坑記

下面分享兩個專案中使用Redis時候踩到坑。

坑1

【問題背景】
在生產環境的Redis經常會報出RedisConnectionFailureException: java.net.SocketException: Broken pipe

【異常列印】

11:28:29 INFO  - get data from redis, key = c15aad89-4a1a-4cb0-82a5-2027b990c1ca
11:28:29 WARN  - /market/info/eForum/getIndexList
org.springframework.data.redis.RedisConnectionFailureException: java.net.SocketException: Broken pipe; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketException: Broken pipe
at org.springframework.data.redis.connection.jedis.JedisExceptionConverter.convert(JedisExceptionConverter.java:67) ~[spring-data-redis-1.7.3.RELEASE.jar:?]
at org.springframework.data.redis.connection.jedis.JedisExceptionConverter.convert(JedisExceptionConverter.java:41) ~[spring-data-redis-1.7.3.RELEASE.jar:?]
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:37) ~[spring-data-redis-1.7.3.RELEASE.jar:?]
at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:37) ~[spring-data-redis-1.7.3.RELEASE.jar:?]
at org.springframework.data.redis.connection.jedis.JedisConnection.convertJedisAccessException(JedisConnection.java:212) ~[spring-data-redis-1.7.3.RELEASE.jar:?]
at org.springframework.data.redis.connection.jedis.JedisConnection.get(JedisConnection.java:1117) ~[spring-data-redis-1.7.3.RELEASE.jar:?]
at org.springframework.data.redis.core.DefaultValueOperations$1.inRedis(DefaultValueOperation

【問題原因】

Redis底層也建立了連線池,獲取到了失效的連線,並且Redis客戶端嘗試通過此連線池跟服務端進行通訊, 導致丟擲上面的異常。

【解決辦法】
Redis配置的連線池使用jar包commons-pool-2.4.2.jar方式,其中BaseObjectPoolConfig類為基礎配置類。

private boolean testOnCreate = false;
private boolean testOnBorrow = false;
private boolean testOnReturn = false;
private boolean testWhileIdle = false;

如上述四個屬性引數預設都是false,可以通過修改 testOnBorrow = true 和 testWhileIdle = true 來解決獲取無效連結的問題。其中 testOnBorrow = true 是獲取連結的時候對連結的有效性進行檢查,會影響效率,在高併發的前提下。所以一般只是配置 testWhileIdle = true , 這個是在閒暇的時候進行檢查,去除無效的連結。

坑2

【問題背景】
版本日那天提交了程式碼閒來無事,看到使用者資訊類UserInfoExt,儲存在common的util目錄下,有強迫症的我,硬是把它移到了entity包下。以為完美的重構了,沒想到挖出了一個巨大的坑。打預發版的包到測試環境,立馬所有的已登入使用者,都不能進行其他操作。只要切換頁面就會丟擲“網路服務異常情況”,整個預發版的測試環境被我搞癱瘓了,大家都沒法測試。 預發版測試不完成,就沒法正常發版,說實話那時候壓力還挺大的,全專案的人都盯著你。以後要重構程式碼,千萬別發版前重構,最好是版本迭代開始的前幾天就重構好,這樣即使重構帶來的bug,也有足夠的時候去發現和解決。

【異常列印】

19:32:47 INFO  - Started Application in 10.932 seconds (JVM running for 12.296)
19:32:50 INFO  - get data from redis, key = 10d044f9-0e94-420b-9631-b83f5ca2ed30
19:32:50 WARN  - /market/renewal/homePage/index
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Could not resolve type id 'com.pa.market.common.util.UserInfoExt' into a subtype of [simple type, class java.lang.Object]: no such class found
 at [Source: [[email protected]; line: 1, column: 11]; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'com.pa.market.common.util.UserInfoExt' into a subtype of [simple type, class java.lang.Object]: no such class found at [Source: [[email protected]; line: 1, column: 11]

【問題原因】
專案中使用了攔截器,對每個http請求進行攔截。通過前端傳遞過來的token,去redis快取中獲取使用者資訊UserInfoExt,使用者資訊是在使用者登入的時候存入到redis快取中的。根據獲取到的使用者資訊來判斷是否存是登入狀態。
所以除白名單外的url,其他請求都需要進行這個操作。通過日誌列印,很明顯出現在UserInfoExt物件儲存到redis中序列化和反序列化的操作步驟。
【解決辦法】

@Bean
public RedisTemplate<K, V> redisTemplate() {
    RedisTemplate<K, V> redisTemplate = new RedisTemplate<K, V>();
    redisTemplate.setConnectionFactory(jedisConnectionFactory());
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    return redisTemplate;
 }

檢視Redis的Bean定義發現,對key的序列化使用的是StringRedisSerializer系列化,value值的序列化是GenericJackson2JsonRedisSerializer的序列化方法。

其中GenericJackson2JsonRedisSerializer序列化方法會在redis中記錄類的class資訊,如下所示:

{
"@class": "com.pa.market.common.util.UserInfoExt",
"url": "www.baidu.com",
"name": "baidu"
}

“@class”: “com.pa.market.common.util.UserInfoExt”,每個物件都會有這個id存在(可以通過原始碼看出為嘛有這個@class),如果使用者一直處在登入狀態,是以com.pa.market.common.util.UserInfoExt這個路徑進行的序列化操作。但是移動了UserInfoExt的類路徑後,包全名變了。所以會丟擲no such class found的異常。這樣在判斷使用者是否存在的地方就丟擲了異常,故而所有的請求都失敗了,已經登入的使用者沒法進行任何操作。

【總結】
對於上面的序列化的坑,貌似沒有很好的解決方案。從比較常用的序列化和反序列化類,可以發現每個都有各自的優點和缺點。如果在redis層面把物件轉成json,那麼每條記錄中都會有@class這個標記,如果以後程式碼重構,移動類路徑,肯定是不行的,是個巨坑。如果在入redis之前,就把物件直接轉成json,然後用StringRedisSerializer的方式對value進行序列化和反序列化,這樣可讀性好,也不會跟物件的類路徑有強關聯。但是需要中間做一道處理,寫的時候需要物件轉json,讀的時候又需要json轉物件,會降低效率。

Redis的高階特性

1、叢集
2、釋出訂購
3、持久化
4、Redis伺服器如何容災,如何預防單點故障等
5、讀寫分離操作
6、非同步佇列
7、Redis的雪崩和穿透

以上特性有待後續的解鎖,敬請期待!

相關推薦

Redis入門

背景 Redis在網際網路專案的使用也是非常普遍的,作為最常用的NO-SQL資料庫,對Redis的瞭解已經成為了後端開發的必備技能。小編對Redis的使用時間不長,但是專案中確兩次踩中了Redis的坑,今天特意從基礎知識層面到實戰層面對Redis知識進

.NET分布式緩存Redis入門到實戰

ict 類型 社交 純粹 value redis服務器 使用場景 c# 應用 一、課程介紹 今天阿笨給大家帶來一堂NOSQL的課程,本期的主角是Redis。希望大家學完本次分享課程後對redis有一個基本的了解和認識,並且熟悉和掌握 Redis在.NET中的使用。本次

ABP項目入門

src load dbconnect 管理 .json fig 連接 登錄 admin 1.下載ABP項目模板, 打開網址https://aspnetboilerplate.com/Templates,選擇MultiPage Web Application,輸入項目名稱和驗

redis入門

合並 描述 .gz 方式 號碼 config 宋體 即將 設定 下載 首先我們要到GitHub(https://github.com/MicrosoftArchive/redis/releases)上下載Source code?(tar.gz) 上傳到Linux上,我的位置

比MySQL快60倍 redis入門到精通視頻教程

redis Mysql 分布式數據庫 Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。 學習視頻下載地址:https://pan.baidu.com/s/17NO3pG9hRL-RtU0bwaTylw Red

redis 入門到遺忘

設置 sub del 測試 keys index email lis lpush Key操作 keys * *: 通配任意多個字符 ?: 通配單個字符 []: 通配括號內的某1個字符 exists key 存在返回1,不存在返回0 type key rename oldke

redis入門到放棄 -> 簡介&概念

一、redis簡介 Redis是一款開源的、高效能的鍵-值儲存。它常被稱作是一款資料結構伺服器。 當值支援的主要資料型別為:字串(strings)型別,括雜湊(hashes)、列表(lists)、集合(sets)和 有序集合(sorted sets)等資料型別。 同時Redis可以進行持久化(將資料存到

redis 入門到起飛

1. Redis 介紹 1.1 NoSQL 基本概念 為了解決高併發、高可用、高可擴充套件,大資料儲存等一系列問題而產生的資料庫解決方案,就是NoSql。 NoSql,叫非關係型資料庫,它的全名Not only sql。它不能替代關係型資料庫,只能作為關係型資料庫的一個良好補充。 1

Redis 入門到起飛(上)

1. Redis 介紹 1.1 NoSQL 基本概念 為了解決高併發、高可用、高可擴充套件,大資料儲存等一系列問題而產生的資料庫解決方案,就是NoSql。 NoSql,叫非關係型資料庫,它的全名Not only sql。它不能替代關係型資料庫,只能作為關係型資料庫的一個良好補充。

Redis 入門到起飛(下)

5. keys 命令 5.1 常用命令 keys 返回滿足給定pattern 的所有key redis 127.0.0.1:6379> keys mylist* "mylist" "mylist5" "mylist6" "my

一站式學習Redis 入門到高可用分布式實踐(慕課)第五章 Redis持久化的取舍和選擇

rdb idt http png pan height style nbsp 入門 Redis持久化的取舍和選擇 持久化的作用 RDB AOF RDB和AOF的決擇 一站式學習Redis 從入門到高可用分布式實踐(慕課)第五章 R

一站式學習Redis 入門到高可用分散式實踐(慕課)第八章 Redis Sentinel

主從複製高可用?           主從複製,主掛掉後需要手工來操作麻煩           寫能力和儲存能力受限 (主從複製只是備份,單節點儲存能力)  #其實分散式後

一站式學習Redis 入門到高可用分散式實踐

10-1 叢集伸縮目錄 10-2 叢集伸縮原理 10-3 擴充套件叢集-1.加入節點 10-4 擴充套件叢集-2.加入叢集 10-5 擴充套件叢集-3.遷移槽和資料 10-6 叢集擴容演示-1 10-7 叢集擴容演示-2 10-8 叢集縮容-說明 10-9 叢集縮容-操作

Redis入門到精通 Redis入門實戰視訊教程 Redis視訊+文件

課程目錄 第1章:Redis 資料結構的用法與用例—完整 第1節:Redis 簡介 課時1-2:培訓須知.doc 課時3:Redis 簡介(上).mp4 課時4:Redis 簡介(下).mp4 課時5:字串鍵(上).mp4 課時6:字串鍵(中).mp4 課時7:

SpringBoot 2.0.6 連線 Redis 切換db

專案中有一個小需求,在一個專案上不同的請求連線到同一個Redis 的不同的db上。 我使用的 SpringBoot是 2.0.6 RELEASE 版本的,按照網上說的方法,切換完db後(redisTemplete裡面打斷點可以看出來已經成功切換),還是會按原來的連線去查詢,

redis使用事務-

現象 使用get請求,incrment請求,返回結果一直為null 業務程式碼 org.springframework.transaction.support.TransactionTemplate transactionTemplate.execute(t

Redis入門到熟練使用之測試哨兵及主從高可用(詳解,第四篇,共五篇)

測試驗證 首先檢視哨兵監控情況 然後嘗試關閉主機 再檢視剩餘2個從機,這裡會自動選舉產生新的主機 然後,我們再次將剛才停止的主機啟動起來,發現啟動後其自動成為從機 停掉master_redis主資料庫後重啟後變成從資料庫。隨機推選。 至此,

Redis入門到高可用,分散式實踐 四(Redis Cluster)

Redis Cluster 呼喚叢集 redis最高可以達到10萬/s,如果業務需要100萬/s呢? 單機器記憶體太小,無法滿足需求 資料分佈 順序分割槽的資料量不可確定性導致傾斜,不支援批量操作 雜湊分佈 節點取

Redis入門到熟練使用之Sentine哨兵(詳解,第三篇,共五篇)

配置Sentinel哨兵 Redis 的 Sentinel 系統用於管理多個 Redis 伺服器(instance), 該系統執行以下三個任務: 監控(Monitoring): Sentinel 會不斷地檢查你的主伺服器和從伺服器是否運作正常。 提醒(Notificat

redis入門到實踐

####一. Redis安裝使用 1.介紹 redis是一個key-value儲存系統。和Memcached類似,它支援儲存的value型別相對更多, 包括string(字串)、list(連結串列)、set(集合)、zset(sorted set--有序集合)和hash(雜湊