1. 程式人生 > >Redis從入門到放棄系列(十) Cluster

Redis從入門到放棄系列(十) Cluster

Redis從入門到放棄系列(十) Cluster

本文例子基於:5.0.4

Redis Cluster叢集高可用方案,去中心化,最基本三主多從,主從切換類似Sentinel,關於Sentinel內容可以檢視編者另外一篇【Redis從入門到放棄系列(九) Sentinel】.

在Redis Cluster中,只存在index為0的資料庫,而且其實Redis作為單執行緒,如果在同一個例項上建立多個庫的話,也是需要上下文切換的.

slot

由於Redis Cluster是採用16384個slot來劃分資料的,也就是說你當前插入的資料會存在不同的節點上,簡而言之不支援比較複雜的多建操作(可以對key打上hash tags來解決).

我們說Cluster是按照16384個slot來劃分資料的,那麼是如何來確定一個key落在那個節點上呢?

//計算slot
HASH_SLOT = CRC16(key) mod 16384

每個節點會擁有一部分的slot,通過上述獲取到具體key的slot即知道應該去哪兒找對應的節點啦.可是在網路中,一切都會有不存穩定因素,網路抖動.

當在Cluster中存在網路抖動的時候,當時間過長,有可能產生下線,其實原理跟Sentinel裡面講的很相似,因為都是依賴Gossip協議來實現的.可以通過以下配置來設定確定下線的時間.

//節點持續timeout的時間,才認定該節點出現故障,需要進行主從切換,
cluster-node-timeout
//作為上面timeout的係數來放大時間
cluster-replica-validity-factor

由於資料是按照16384個slot去劃分的,那麼當我們在請求某個key到錯誤的節點,這時候key不在該節點上,Redis會向我們傳送一個錯誤

-MOVED 3999 127.0.0.1:6381

該訊息是提示我們該key應該是存在127.0.0.1這臺伺服器上面的3999slot,這時候就需要我們的redis客戶端去糾正本地的slot對映表,然後請求對應的地址.

增刪叢集節點

當我們在增加或者刪除某個節點的時候,其實就只是將slot從某個節點移動到另外一個節點.可以使用一下命令來完成這一件事

  • CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
  • CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
  • CLUSTER SETSLOT slot NODE node
  • CLUSTER SETSLOT slot MIGRATING node
  • CLUSTER SETSLOT slot IMPORTING node 有時候運維需要對redis節點的某些資料做遷移,官方提供了redis-trib工具來完成這件事情。

在遷移的時候,redis節點會存在兩種狀態,一種是MIGRATING和IMPORTING,用於將slot從一個節點遷移到另外一個節點.

  • 節點狀態設定為MIGRATING時,將接受與此雜湊槽有關的所有查詢,但僅當有問題的key存在時才能接受,否則將使用-Ask重定向將查詢轉發到作為遷移目標的節點。
  • 節點狀態設定為IMPORTING時,節點將接受與此雜湊槽有關的所有查詢,但前提是請求前面有ASKING命令。如果客戶端沒有發出ASKING命令,查詢將通過-MOVED重定向錯誤重定向到真正的雜湊槽所有者

多執行緒批量獲取/刪除

public class RedisUtils {

	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";
	private static final Long RELEASE_SUCCESS = 1L;

	private final ThreadLocal<String> requestId = new ThreadLocal<>();

	private final static ExecutorService executorService = new ThreadPoolExecutor(
			//核心執行緒數量
			1,
			//最大執行緒數量
			8,
			//當執行緒空閒時,保持活躍的時間
			1000,
			//時間單元 ,毫秒級
			TimeUnit.MILLISECONDS,
			//執行緒任務佇列
			new LinkedBlockingQueue<>(1024),
			//建立執行緒的工廠
			new RedisTreadFactory("redis-batch"));

	@Autowired
	private JedisCluster jedisCluster;

	public String set(String key, String value) {
		return jedisCluster.set(key, value);
	}

	public String get(String key) {
		return jedisCluster.get(key);
	}

	public Map<String, String> getBatchKey(List<String> keys) {
		Map<Jedis, List<String>> nodeKeyListMap = jedisKeys(keys);
		//結果集
		Map<String, String> resultMap = new HashMap<>();
		CompletionService<Map<String,String>> batchService = new ExecutorCompletionService(executorService);
		nodeKeyListMap.forEach((k,v)->{
			batchService.submit(new BatchGetTask(k,v));
		});
		nodeKeyListMap.forEach((k,v)->{
			try {
				resultMap.putAll(batchService.take().get());
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
		});
		return resultMap;
	}

	public boolean lock(String lockKey, long expireTime){
		String uuid = UUID.randomUUID().toString();
		requestId.set(uuid);
		String result = jedisCluster.set(lockKey, uuid, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
		return LOCK_SUCCESS.equals(result);
	}

	public boolean unLock(String lockKey){
		String uuid = requestId.get();
		String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
		Object result = jedisCluster.eval(script, Collections.singletonList(lockKey), Collections.singletonList(uuid));
		requestId.remove();
		return RELEASE_SUCCESS.equals(result);
	}

	private Map<Jedis, List<String>> jedisKeys(List<String> keys){
		Map<Jedis, List<String>> nodeKeyListMap = new HashMap<>();
		for (String key : keys) {
			//計算slot
			int slot = JedisClusterCRC16.getSlot(key);
			Jedis jedis = jedisCluster.getConnectionFromSlot(slot);
			if (nodeKeyListMap.containsKey(jedis)) {
				nodeKeyListMap.get(jedis).add(key);
			} else {
				nodeKeyListMap.put(jedis, Arrays.asList(key));
			}
		}
		return nodeKeyListMap;
	}

	public long delBatchKey(List<String> keys){
		Map<Jedis, List<String>> nodeKeyListMap = jedisKeys(keys);
		CompletionService<Long> batchService = new ExecutorCompletionService(executorService);
		nodeKeyListMap.forEach((k,v)->{
			batchService.submit(new BatchDelTask(k,v));
		});
		Long result = 0L;
		for (int i=0;i<nodeKeyListMap.size();i++){
			try {
				result += batchService.take().get();
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
		}
		return result;
	}

	class BatchGetTask implements Callable<Map<String,String>>{

		private Jedis jedis;

		private List<String> keys;

		private BatchGetTask(Jedis jedis, List<String> keys) {
			this.jedis = jedis;
			this.keys = keys;
		}

		@Override
		public Map<String, String> call() throws Exception {
			Map<String, String> resultMap = new HashMap<>();
			String[] keyArray = keys.toArray(new String[]{});
			try {
				List<String> nodeValueList = jedis.mget(keyArray);
				for (int i = 0; i < keys.size(); i++) {
					resultMap.put(keys.get(i),nodeValueList.get(i));
				}
			}finally {
				jedis.close();
			}
			return resultMap;
		}
	}

	class BatchDelTask implements Callable<Long>{

		private Jedis jedis;

		private List<String> keys;

		private BatchDelTask(Jedis jedis, List<String> keys) {
			this.jedis = jedis;
			this.keys = keys;
		}

		@Override
		public Long call() throws Exception {
			String[] keyArray = keys.toArray(new String[]{});
			try {
				return jedis.del(keyArray);
			}finally {
				jedis.close();
			}
		}
	}

	 static class RedisTreadFactory implements ThreadFactory{

		private final AtomicInteger threadNumber = new AtomicInteger(0);

		private final String namePredix;

		public RedisTreadFactory(String namePredix) {
			this.namePredix = namePredix +"-";
		}

		@Override
		public Thread newThread(Runnable r) {
			Thread t = new Thread( r,namePredix + threadNumber.getAndIncrement());
			if (t.isDaemon())
				t.setDaemon(true);
			if (t.getPriority() != Thread.NORM_PRIORITY)
				t.setPriority(Thread.NORM_PRIORITY);
			return t;
		}
	}
}

寫在最後

Redis從入門到放棄系列終於完結啦!!!!!!!!!!!

寫部落格,真的是非常耗時間,真的,本來星期六日要寫的,然而因為某些問題而沒有寫出來(PS:純粹是因為打遊戲.hhhh),終於在今天痛定思痛,頂著脖子酸的壓力(PS:貼著狗皮膏藥在擼碼),終於完結了.

感謝各位看官那麼辛苦看我碼字,真心感謝.

希望寫的東西對各位看官有啟發.

相關推薦

Redis入門放棄系列() Cluster

Redis從入門到放棄系列(十) Cluster 本文例子基於:5.0.4 Redis Cluster叢集高可用方

Redis入門放棄系列(九) Sentinel

Redis從入門到放棄系列(九) Sentinel 本文例子基於:5.0.4 Redis Sentinel作為Re

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

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

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

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

WPF入門教程系列——布局之Border與ViewBox(五)

last () put prev 裝飾 wpf 背景 .text 部分 九. Border Border 是一個裝飾的控件,此控件繪制邊框及背景,在 Border 中只能有一個子控件,若要顯示多個子控件,需要將一個附加的 Panel 控件放置在父 Border 中。然後可以

WPF入門教程系列一——依賴屬性(一)

nts 如果 edev 出現 樣式 語法 寫法 屬性。 結構 一、依賴屬性基本介紹   本篇開始學習WPF的另一個重要內容依賴屬性。 大家都知道WPF帶來了很多新的特性,其中一個就是引入了一種新的屬性機制——依賴屬性。依賴屬性出現的目的是用來實現WPF中的樣式、自

WPF入門教程系列四——依賴屬性(四)

nan out rmi strong too nim app controls ase 六、依賴屬性回調、驗證及強制值 我們通過下面的這幅圖,簡單介紹一下WPF屬性系統對依賴屬性操作的基本步驟:   借用一個常見的圖例,介紹一下WPF屬性系統對依賴屬性操作的基本

WPF入門教程系列七——WPF中的數據綁定(三)

控件 命名空間 布局 獲得 key gif lns return 生日 四、 XML數據綁定 這次我們來學習新的綁定知識,XML數據綁定。XmlDataProvider 用來綁定 XML 數據,該XML數據可以是嵌入.Xmal文件的 XmlDataProvider

WPF入門教程系列五——WPF中的數據綁定(一)

不同 pan cnblogs 雙向 one tap copy msd tac 使用Windows Presentation Foundation (WPF) 可以很方便的設計出強大的用戶界面,同時 WPF提供了數據綁定功能。WPF的數據綁定跟Winform與ASP.NET

arcgis api for js入門開發系列五臺風軌跡

demo 簡單 com span eat api alt 介紹 nbsp 上一篇實現了demo的地圖最近設施點路徑分析,本篇新增臺風軌跡,截圖如下: 下面簡單介紹相關知識點: 警戒線 警戒線坐標集合: var lineArr

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

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

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 入門到起飛

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 叢集縮容-操作