Redis

Redis

什么是redis

Redis 是一個(gè)基于內(nèi)存的高性能key-value數(shù)據(jù)庫(kù)。

Reids的特點(diǎn)

Redis本質(zhì)上是一個(gè)Key-Value類型的內(nèi)存數(shù)據(jù)庫(kù),因?yàn)槭羌儍?nèi)存操作,Redis的性能非常出色,每秒可以處理超過(guò) 10萬(wàn)次讀寫操作,是已知性能最快的Key-Value DB。 Redis最大的魅力是支持保存多種數(shù)據(jù)結(jié)構(gòu),此外單個(gè)value的最大限制是1GB 。

使用redis有哪些好處?

(1) 速度快,因?yàn)閿?shù)據(jù)存在內(nèi)存中

(2) 支持多種數(shù)據(jù)結(jié)構(gòu),支持string,list,set,sorted set,hash

(3) 支持事務(wù),操作都是原子性,所謂的原子性就是對(duì)數(shù)據(jù)的更改要么全部執(zhí)行,要么全部不執(zhí)行

(4) 豐富的特性:可用于緩存,消息,按key設(shè)置過(guò)期時(shí)間,過(guò)期后將會(huì)自動(dòng)刪除

Redis是單進(jìn)程單線程的

redis利用隊(duì)列技術(shù)將并發(fā)訪問(wèn)變?yōu)榇性L問(wèn),消除了傳統(tǒng)數(shù)據(jù)庫(kù)串行控制的開(kāi)銷

Redis的并發(fā)競(jìng)爭(zhēng)問(wèn)題如何解決?

Redis為單進(jìn)程單線程模式,采用隊(duì)列模式將并發(fā)訪問(wèn)變?yōu)榇性L問(wèn)。Redis本身沒(méi)有鎖的概念,Redis對(duì)于多個(gè)客戶端連接并不存在競(jìng)爭(zhēng),但是在Jedis客戶端對(duì)Redis進(jìn)行并發(fā)訪問(wèn)時(shí)會(huì)發(fā)生連接超時(shí)、數(shù)據(jù)轉(zhuǎn)換錯(cuò)誤、阻塞、客戶端關(guān)閉連接等問(wèn)題,這些問(wèn)題均是由于客戶端連接混亂造成。對(duì)此有2種解決方法:

1.客戶端角度,為保證每個(gè)客戶端間正常有序與Redis進(jìn)行通信,對(duì)連接進(jìn)行池化,同時(shí)對(duì)客戶端讀寫Redis操作采用內(nèi)部鎖synchronized。

2.服務(wù)器角度,利用setnx實(shí)現(xiàn)鎖。

是否使用過(guò)Redis集群,集群的原理是什么?

Redis Sentinel['s?nt?nl]著眼于高可用,在master宕機(jī)時(shí)會(huì)自動(dòng)將slave提升為master,繼續(xù)提供服務(wù)。

Redis Cluster['kl?st?]著眼于擴(kuò)展性,在單個(gè)redis內(nèi)存不足時(shí),使用Cluster進(jìn)行分片存儲(chǔ)。

Redis可以做什么(使用場(chǎng)景)

  • 利用發(fā)布、訂閱構(gòu)建實(shí)時(shí)消息推送系統(tǒng)
  • 配合關(guān)系型數(shù)據(jù)庫(kù)做高速緩存
  • 分布式架構(gòu),會(huì)話緩存

什么是分布式鎖

分布式鎖是相對(duì)線程鎖和進(jìn)程鎖而言的。

名稱 描述
線程鎖 主要用來(lái)給方法、代碼塊加鎖。當(dāng)某個(gè)方法或代碼使用鎖,在同一時(shí)刻僅有一個(gè)線程執(zhí)行該方法或該代碼段。
進(jìn)程鎖 為了控制同一操作系統(tǒng)中多個(gè)進(jìn)程訪問(wèn)某個(gè)共享資源,因?yàn)檫M(jìn)程具有獨(dú)立性,各個(gè)進(jìn)程無(wú)法訪問(wèn)其他進(jìn)程的資源,因此無(wú)法通過(guò)synchronized等線程鎖實(shí)現(xiàn)進(jìn)程鎖。
分布式鎖 當(dāng)多個(gè)進(jìn)程不在同一個(gè)系統(tǒng)中,用分布式鎖控制多個(gè)進(jìn)程對(duì)資源的訪問(wèn)。

分布式鎖的使用場(chǎng)景

有這樣一個(gè)情境,線程A和線程B都共享某個(gè)變量X。

單機(jī)情況下,線程之間共享內(nèi)存,只需要使用線程鎖就可以解決并發(fā)問(wèn)題。

但是分布式情況下,線程A和線程B不再在同一個(gè)JVM中,線程鎖不起作用,這個(gè)時(shí)候就必須使用分布式鎖。

確保分布式鎖可以用,需滿足什么條件

  1. 互斥性。在任意時(shí)刻,只有一個(gè)客戶端能持有鎖。
  2. 不會(huì)發(fā)生死鎖。即使有一個(gè)客戶端在持有鎖的期間崩潰而沒(méi)有主動(dòng)解鎖,也能保證后續(xù)其他客戶端能加鎖。
  3. 具有容錯(cuò)性。只要大部分的Redis節(jié)點(diǎn)正常運(yùn)行,客戶端就可以加鎖和解鎖。
  4. 解鈴還須系鈴人。加鎖和解鎖必須是同一個(gè)客戶端,客戶端自己不能把別人加的鎖給解了。

分布式鎖的實(shí)現(xiàn)方式

1.引入jedis依賴

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

2.獲取鎖的代碼實(shí)現(xiàn)

public class RedisTool {
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    /**
     * 嘗試獲取分布式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請(qǐng)求標(biāo)識(shí)
     * @param expireTime 超期時(shí)間
     * @return 是否獲取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}

可以看到,我們加鎖就一行代碼:jedis.set(String key, String value, String nxxx, String expx, int time),這個(gè)set()方法一共有五個(gè)形參:

  • 第一個(gè)為key,我們使用key來(lái)當(dāng)鎖,因?yàn)閗ey是唯一的。
  • 第二個(gè)為value,我們傳的是requestId,很多童鞋可能不明白,有key作為鎖不就夠了嗎,為什么還要用到value?原因就是我們?cè)谏厦嬷v到可靠性時(shí),分布式鎖要滿足第四個(gè)條件解鈴還須系鈴人,通過(guò)給value賦值為requestId,我們就知道這把鎖是哪個(gè)請(qǐng)求加的了,在解鎖的時(shí)候就可以有依據(jù)。requestId可以使用UUID.randomUUID().toString()方法生成。
  • 第三個(gè)為nxxx,這個(gè)參數(shù)我們填的是NX,意思是SET IF NOT EXIST,即當(dāng)key不存在時(shí),我們進(jìn)行set操作;若key已經(jīng)存在,則不做任何操作;
  • 第四個(gè)為expx,這個(gè)參數(shù)我們傳的是PX,意思是我們要給這個(gè)key加一個(gè)過(guò)期的設(shè)置,具體時(shí)間由第五個(gè)參數(shù)決定。
  • 第五個(gè)為time,與第四個(gè)參數(shù)相呼應(yīng),代表key的過(guò)期時(shí)間。

總的來(lái)說(shuō),執(zhí)行上面的set()方法就只會(huì)導(dǎo)致兩種結(jié)果:1. 當(dāng)前沒(méi)有鎖(key不存在),那么就進(jìn)行加鎖操作,并對(duì)鎖設(shè)置個(gè)有效期,同時(shí)value表示加鎖的客戶端。2. 已有鎖存在,不做任何操作。

3.釋放鎖代碼實(shí)現(xiàn)

public class RedisTool {
    private static final Long RELEASE_SUCCESS = 1L;
    /**
     * 釋放分布式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請(qǐng)求標(biāo)識(shí)
     * @return 是否釋放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}

可以看到,我們解鎖只需要兩行代碼就搞定了!第一行代碼,我們寫了一個(gè)簡(jiǎn)單的Lua腳本代碼,上一次見(jiàn)到這個(gè)編程語(yǔ)言還是在《黑客與畫家》里,沒(méi)想到這次居然用上了。第二行代碼,我們將Lua代碼傳到jedis.eval()方法里,并使參數(shù)KEYS[1]賦值為lockKey,ARGV[1]賦值為requestId。eval()方法是將Lua代碼交給Redis服務(wù)端執(zhí)行。

那么這段Lua代碼的功能是什么呢?其實(shí)很簡(jiǎn)單,首先獲取鎖對(duì)應(yīng)的value值,檢查是否與requestId相等,如果相等則刪除鎖(解鎖)。那么為什么要使用Lua語(yǔ)言來(lái)實(shí)現(xiàn)呢?因?yàn)橐_保上述操作是原子性的。

redis部署方式

部署方式 運(yùn)作模式 分片 可用性
單實(shí)例模式 單臺(tái)redis完成所有請(qǐng)求任務(wù),宕機(jī)則無(wú)法工作 由一臺(tái)實(shí)例獨(dú)立完成 無(wú)法復(fù)用和不具備容錯(cuò)性,只能進(jìn)行服務(wù)器物理擴(kuò)展。
['s?nt?nl]sentinel主從配置 由sentinel哨兵處理故障和完成故障轉(zhuǎn)移,也是性能的瓶頸所在 每個(gè)主實(shí)例分擔(dān)1/N的數(shù)據(jù)量 需要配置sentinel來(lái)實(shí)現(xiàn)復(fù)制和高可用性
集群模式 無(wú)中心節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)之間都有通信,當(dāng)節(jié)點(diǎn)較多時(shí)資源消耗較多 按照槽來(lái)進(jìn)行分片,通過(guò)為每個(gè)節(jié)點(diǎn)指派不同數(shù)量的槽,可以控制不同節(jié)點(diǎn)負(fù)責(zé)的數(shù)據(jù)量和請(qǐng)求數(shù)量。 集群的節(jié)點(diǎn)內(nèi)置了復(fù)制和高可用性。

單實(shí)例模式 :單實(shí)例模式是指單臺(tái)redis完成所有請(qǐng)求任務(wù),因此復(fù)用和不具備容錯(cuò)性;同時(shí)在單臺(tái)機(jī)器上如果只啟用一個(gè)redis實(shí)例會(huì)造成資源浪費(fèi) 。

Redis集群 :Redis 集群是一個(gè)由多個(gè)節(jié)點(diǎn)組成的分布式服務(wù)器群,它具有復(fù)制、高可用和分片特性。Redis集群不需要sentinel哨兵也能完成節(jié)點(diǎn)移除和故障轉(zhuǎn)移的功能。需要將每個(gè)節(jié)點(diǎn)設(shè)置成集群模式,這種集群模式?jīng)]有中心節(jié)點(diǎn),多個(gè)節(jié)點(diǎn)之間存在著網(wǎng)絡(luò)通信的消耗。

Redis Sentinel集群 :Sentinel是一個(gè)管理redis實(shí)例的工具,它可以實(shí)現(xiàn)對(duì)redis的監(jiān)控、通知、自動(dòng)故障轉(zhuǎn)移。sentinel不斷地檢測(cè)redis實(shí)例是否可以正常工作,通過(guò)API向其他程序報(bào)告redis的狀態(tài),如果redis master不能工作,則會(huì)自動(dòng)啟動(dòng)故障轉(zhuǎn)移進(jìn)程,將其中的一個(gè)slave提升為master,其他的slave重新設(shè)置新的master服務(wù)器。

Redis集群概念

  1. 分布式:Redis集群是一個(gè)由多個(gè)Redis服務(wù)器組成的分布式網(wǎng)絡(luò)服務(wù)器群,集群中的各個(gè)服務(wù)器被稱為節(jié)點(diǎn)(node),這些節(jié)點(diǎn)會(huì)相互連接并進(jìn)行通信。分布式的Redis集群沒(méi)有中心節(jié)點(diǎn),所以用戶不必?fù)?dān)心某個(gè)節(jié)點(diǎn)會(huì)成為整個(gè)集群的性能瓶頸。

  2. 復(fù)制 :Redis 集群的每個(gè)節(jié)點(diǎn)都有兩種角色可選,一個(gè)是主節(jié)點(diǎn)(master node),另一個(gè)是從節(jié)點(diǎn)(slavenode),其中主節(jié)點(diǎn)用于儲(chǔ)存數(shù)據(jù),而從節(jié)點(diǎn)則是某個(gè)主節(jié)點(diǎn)的復(fù)制品。當(dāng)用戶需要處理更多讀請(qǐng)求的時(shí)候,可以添加從節(jié)點(diǎn)以擴(kuò)展系統(tǒng)的讀性能。因?yàn)镽edis集群重用了單機(jī)Redis復(fù)制特性的代碼,所以集群的復(fù)制行為和我們之前介紹的單機(jī)復(fù)制特性的行為是完全一樣的。

  3. 節(jié)點(diǎn)故障檢測(cè)和自動(dòng)故障轉(zhuǎn)移 :Redis 集群的主節(jié)點(diǎn)內(nèi)置了類似Redis Sentinel的節(jié)點(diǎn)故障檢測(cè)和自動(dòng)故障轉(zhuǎn)移功能,當(dāng)集群中的某個(gè)主節(jié)點(diǎn)下線時(shí),集群中的其他在線主節(jié)點(diǎn)會(huì)注意到這一點(diǎn),并對(duì)已下線的主節(jié)點(diǎn)進(jìn)行故障轉(zhuǎn)移。集群進(jìn)行故障轉(zhuǎn)移的方法和Redis Sentinel進(jìn)行故障轉(zhuǎn)移的方法基本一樣,不同的是,在集群里面,故障轉(zhuǎn)移是由集群中其他在線的主節(jié)點(diǎn)負(fù)責(zé)進(jìn)行的,所以集群不必另外使用Redis Sentinel 。

  4. 分片 :集群使用分片來(lái)擴(kuò)展數(shù)據(jù)庫(kù)的容量,并將命令請(qǐng)求的負(fù)載交給不同的節(jié)點(diǎn)來(lái)分擔(dān)。
    集群將整個(gè)數(shù)據(jù)庫(kù)分為 16384 個(gè)槽(slot),所有鍵都屬于這 16384 個(gè)槽的其中一個(gè),計(jì)算鍵 key屬于哪個(gè)槽的公式為 slot_number = crc16(key) % 16384 ,其中 crc16 為 16 位的循環(huán)冗余校驗(yàn)和函數(shù)。集群中的每個(gè)主節(jié)點(diǎn)都可以處理 0 個(gè)至 16384 個(gè)槽,當(dāng) 16384 個(gè)槽都有某個(gè)節(jié)點(diǎn)在負(fù)責(zé)處理時(shí),集群進(jìn)入上線狀態(tài),并開(kāi)始處理客戶端發(fā)送的數(shù)據(jù)命令請(qǐng)求。

  5. 轉(zhuǎn)向 :對(duì)于一個(gè)被指派了槽的主節(jié)點(diǎn)來(lái)說(shuō),這個(gè)主節(jié)點(diǎn)只會(huì)處理屬于指派給自己的槽的命令請(qǐng)求。如果一個(gè)節(jié)點(diǎn)接收到了與自己處理的槽無(wú)關(guān)的命令請(qǐng)求,那么節(jié)點(diǎn)會(huì)向客戶端返回一個(gè)轉(zhuǎn)向錯(cuò)誤(redirection error),告訴客戶端,哪個(gè)節(jié)點(diǎn)負(fù)責(zé)處理這條命令,之后客戶端需要根據(jù)錯(cuò)誤中包含的地址和端口號(hào)重新向正確的節(jié)點(diǎn)發(fā)送命令請(qǐng)求。

  6. Redis集群客戶端 :因?yàn)榧汗δ鼙绕饐螜C(jī)功能要復(fù)雜得多,所以不同語(yǔ)言的 Redis 客戶端通常需要為集群添加特別的支持,或者專門開(kāi)發(fā)一個(gè)集群客戶端。

    目前主要的 Redis 集群客戶端(或者說(shuō),支持集群功能的 Redis 客戶端)有以下這些:
    - redis-rb-cluster:antirez 使用 Ruby 編寫的 Redis 集群客戶端,集群客戶端的官方實(shí)現(xiàn)。
    - predis:Redis 的 PHP 客戶端,支持集群功能。
    - jedis:Redis 的 JAVA 客戶端,支持集群功能。
    - StackExchange.Redis:Redis 的 C# 客戶端,支持集群功能。
    -內(nèi)置的 redis-cli :在啟動(dòng)時(shí)給定 -c 參數(shù)即可進(jìn)入集群模式,支持部分集群功能。

    redis-cluster架構(gòu)圖

redis-cluster架構(gòu)圖
  1. 所有的redis節(jié)點(diǎn)彼此互聯(lián)(PING-PONG機(jī)制),內(nèi)部使用二進(jìn)制協(xié)議優(yōu)化傳輸速度和帶寬。
  2. 節(jié)點(diǎn)的fail是通過(guò)集群中超過(guò)半數(shù)的節(jié)點(diǎn)檢測(cè)失效時(shí)才生效。
  3. 客戶端與redis節(jié)點(diǎn)直連,不需要中間proxy層.客戶端不需要連接集群所有節(jié)點(diǎn),連接集群中任何一個(gè)可用節(jié)點(diǎn)即可。
  4. redis-cluster把所有的物理節(jié)點(diǎn)映射到[0-16383]slot上,cluster 負(fù)責(zé)維護(hù)node<->slot<->value

Redis集群中內(nèi)置了 16384 個(gè)哈希槽,當(dāng)需要在 Redis 集群中放置一個(gè) key-value 時(shí),redis 先對(duì)key 使用 crc16 算法算出一個(gè)結(jié)果,然后把結(jié)果對(duì) 16384 求余數(shù),這樣每個(gè) key 都會(huì)對(duì)應(yīng)一個(gè)編號(hào)在 0-16383 之間的哈希槽,redis 會(huì)根據(jù)節(jié)點(diǎn)數(shù)量大致均等的將哈希槽映射到不同的節(jié)點(diǎn)

redis-cluster投票:容錯(cuò)

redis-cluster投票:容錯(cuò)

1.投票過(guò)程是集群中所有master參與,如果半數(shù)以上master節(jié)點(diǎn)與master節(jié)點(diǎn)通信超時(shí)(cluster-node-timeout),認(rèn)為當(dāng)前master節(jié)點(diǎn)掛掉.

2.什么時(shí)候整個(gè)集群不可用(cluster_state:fail)

(1)如果集群任意master掛掉,且當(dāng)前master沒(méi)有slave.集群進(jìn)入fail狀態(tài),也可以理解成集群的slot映射[0-16383]不完整時(shí)進(jìn)入fail狀態(tài) 。

(2)如果集群超過(guò)半數(shù)以上master掛掉,無(wú)論是否有slave,集群進(jìn)入fail狀態(tài)。

客戶端對(duì)Redis集群的使用方法

(1)使用redis命令行客戶端連接

[root@localhost redis-cluster]# ./redis1/redis-cli -p 7001 -c
127.0.0.1:7001> get a
-> Redirected to slot [15495] located at 192.168.37.131:7003
(nil)
192.168.37.131:7003> 

一定要加-c參數(shù),節(jié)點(diǎn)之間就可以互相跳轉(zhuǎn)

(2)使用jedis連接

package com.pc.jedis.test;
import java.util.HashSet;
import java.util.Set;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
 
/**
 * Jedis集群測(cè)試
 * 
 * @author Switch
 * @data 2017年2月11日
 * @version V1.0
 */
public class JedisClusterTest {
    public static void main(String[] args) {
        // 創(chuàng)建并填充節(jié)點(diǎn)信息
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("192.168.37.131", 7001));
        nodes.add(new HostAndPort("192.168.37.131", 7002));
        nodes.add(new HostAndPort("192.168.37.131", 7003));
        nodes.add(new HostAndPort("192.168.37.131", 7004));
        nodes.add(new HostAndPort("192.168.37.131", 7005));
        nodes.add(new HostAndPort("192.168.37.131", 7006));
 
        // 創(chuàng)建JedisCluster對(duì)象
        JedisCluster jedisCluster = new JedisCluster(nodes);
 
        // 使用jedisCluster操作redis
        String key = "jedisCluster";
        String setResult = jedisCluster.set(key, "hello redis!");
        System.out.println(setResult);
 
        String getResult = jedisCluster.get(key);
        System.out.println(getResult);
 
        // 關(guān)閉jedisCluster(程序執(zhí)行完后才能關(guān)閉,內(nèi)部封裝了連接池)
        jedisCluster.close();
    }
}

Redis Cluster['kl?st?] 集群

Redis集群搭建的方式有多種,例如使用zookeeper等,但從redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用無(wú)中心結(jié)構(gòu),每個(gè)節(jié)點(diǎn)保存數(shù)據(jù)和整個(gè)集群狀態(tài),每個(gè)節(jié)點(diǎn)都和其他所有節(jié)點(diǎn)連接。

結(jié)構(gòu)特點(diǎn):

1、所有的redis節(jié)點(diǎn)彼此互聯(lián)(PING-PONG機(jī)制),內(nèi)部使用二進(jìn)制協(xié)議優(yōu)化傳輸速度和帶寬。
2、節(jié)點(diǎn)的fail是通過(guò)集群中超過(guò)半數(shù)的節(jié)點(diǎn)檢測(cè)失效時(shí)才生效。
3、客戶端與redis節(jié)點(diǎn)直連,不需要中間proxy層.客戶端不需要連接集群所有節(jié)點(diǎn),連接集群中任何一個(gè)可用節(jié)點(diǎn)即可。
4、redis-cluster把所有的物理節(jié)點(diǎn)映射到[0-16383]slot上(不一定是平均分配),cluster 負(fù)責(zé)維護(hù)node<->slot<->value。

? 5、Redis集群預(yù)分好16384個(gè)桶,當(dāng)需要在 Redis 集群中放置一個(gè) key-value 時(shí),根據(jù) CRC16(key) mod 16384的值,決定將一個(gè)key放到哪個(gè)桶中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容