1. 程式人生 > >複習電商筆記-30-原理、hash一致性、jedis和Spring整合訪問redis

複習電商筆記-30-原理、hash一致性、jedis和Spring整合訪問redis

 

 

原理

在分散式叢集中,對機器的新增刪除,或者機器故障後自動脫離叢集這些操作是分散式叢集管理最基本的功能。如果採用常用的hash(object)%N演算法,那麼在有機器新增或者刪除後,很多原有的資料就無法找到了,這樣嚴重的違反了單調性原則。

 

 

hash一致性演算法

一致性雜湊演算法在1997年由麻省理工學院提出。

hash取餘產生的問題:新增節點、刪除節點會讓絕大多數的快取失效,除了導致效能驟降外很有可能會壓垮後臺伺服器。

解決點一:

叢集中節點掛掉或新增節點的時候,要對已有節點的影響降到最小。其解決思路,就是對快取的object和Node使用同一個hash函式(實際不需要完全一致,但至少保證產生的hash空間相同),讓他們對映到同一個hash空間中去,當然這很容易實現,因為大多數的hash函式都是返回uint32型別,其空間即為1~232 232-1(2^32 = 4 294 967 296

,近43)。然後各個Node就將整個hash空間分割成多個interval空間,然後對於每個快取物件object,都按照順時針方向遇到的第一個Node負責快取它。通過這種方法,在新增加Node和刪除Node的時候,只會對順時針方向遇到的第一個Node負責的空間造成影響,其餘的空間都仍然有效。

雖然虛擬並不能百分百的解決快取命中失效的問題,但把問題縮小化,這樣影響面小,即使快取失效,資料庫也能承受起使用者的負載,從而穩定過渡。

 

 

擴充套件:如何縮短key?

這麼多物件的代表,我們熟知在spring框架中,我們儲存spring的上下文時,是一個很長的KEY,那這樣的KEY很多時,會導致記憶體過多的佔用,同時這種自定義規則,也很難保證不衝突。如何找到一個規則能讓他們避免重複呢?

md5/hashCode

 

 

兩個node節點測試

6379節點:

127.0.0.1:6379> keys *
 1) "bomb_79"
 2) "bomb_54"
 3) "bomb_66"
 4) "bomb_76"
 5) "bomb_44"
 6) "bomb_11"
 7) "bomb_6"
 8) "bomb_64"
 9) "bomb_84"
10) "bomb_72"
11) "bomb_47"
12) "bomb_40"
13) "bomb_86"
14) "bomb_57"
15) "bomb_61"
16) "bomb_31"
17) "bomb_73"
18) "bomb_81"
19) "bomb_74"
20) "bomb_28"
21) "bomb_90"
22) "bomb_5"
23) "bomb_23"
24) "bomb_65"
25) "bomb_97"
26) "bomb_9"
27) "bomb_71"
28) "bomb_77"
29) "bomb_48"
30) "bomb_24"
31) "bomb_94"
32) "bomb_34"
33) "bomb_0"
34) "bomb_89"
35) "bomb_49"
36) "bomb_62"
37) "bomb_35"
38) "bomb_63"
39) "bomb_39"
40) "bomb_37"
41) "bomb_53"
42) "bomb_67"
43) "bomb_91"
44) "bomb_82"
45) "bomb_87"
46) "bomb_38"
47) "bomb_92"
48) "bomb_25"
49) "bomb_20"
50) "bomb_83"
51) "bomb_18"
52) "bomb_69"
53) "bomb_50"
127.0.0.1:6379>

6380節點:

D:\javaenv\redis\node6380>redis-cli -p 6380
127.0.0.1:6380> keys *
 1) "bomb_56"
 2) "bomb_78"
 3) "bomb_42"
 4) "bomb_33"
 5) "bomb_3"
 6) "bomb_95"
 7) "bomb_21"
 8) "bomb_22"
 9) "bomb_14"
10) "bomb_36"
11) "bomb_51"
12) "bomb_2"
13) "bomb_16"
14) "bomb_4"
15) "bomb_12"
16) "bomb_99"
17) "bomb_30"
18) "bomb_27"
19) "bomb_70"
20) "bomb_10"
21) "bomb_13"
22) "bomb_60"
23) "bomb_59"
24) "bomb_17"
25) "bomb_19"
26) "bomb_41"
27) "bomb_1"
28) "bomb_15"
29) "bomb_96"
30) "bomb_75"
31) "bomb_46"
32) "bomb_43"
33) "bomb_88"
34) "bomb_7"
35) "bomb_85"
36) "bomb_58"
37) "bomb_45"
38) "bomb_93"
39) "bomb_26"
40) "bomb_52"
41) "bomb_68"
42) "bomb_8"
43) "bomb_29"
44) "bomb_32"
45) "bomb_98"
46) "bomb_80"
47) "bomb_55"
127.0.0.1:6380>

 

jedis和Spring整合訪問redis

 

 

整合步驟

  1. 引入依賴
  2. 整合配置檔案applicationContext-redis.xml
  3. 偽service
  4. 注入偽service

 

 

配置檔案  applicationContext-redis.xml

<!-- 定義叢集連線池 -->
<bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool" destroy-method="close">
	<!-- 第一個引數 -->
	<constructor-arg index="0" ref="jedisPoolConfig"/>
	<constructor-arg index="1">
		<list>
			<!-- 第一個節點 -->
			<bean class="redis.clients.jedis.JedisShardInfo">
				<constructor-arg index="0" value="${redis.node1.ip}"/>
				<constructor-arg type="int" index="1" value="${redis.node1.port}"/>
			</bean>
			<!-- 第二個節點 -->
			<bean class="redis.clients.jedis.JedisShardInfo">
				<constructor-arg index="0" value="${redis.node2.ip}"/>
				<constructor-arg type="int" index="1" value="${redis.node2.port}"/>
			</bean>
			<!-- 第三個節點 -->
			<bean class="redis.clients.jedis.JedisShardInfo">
				<constructor-arg index="0" value="${redis.node3.ip}"/>
				<constructor-arg type="int" index="1" value="${redis.node3.port}"/>
			</bean>
		</list>
	</constructor-arg>
</bean>

redis.properties

redis.maxTotal=50
redis.node1.ip=127.0.0.1
redis.node1.port=6379
#redis.node2.ip=127.0.0.1
#redis.node2.port=6380

 

 

偽Service

package com.jt.manage.service;

import org.springframework.beans.factory.annotation.Autowired;

import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;

public class RedisService {
	@Autowired
	private ShardedJedisPool shardedJedisPool;
	
//儲存資料到redis中
	public String set(String key, String value){
		ShardedJedis shardedJedis = null;
		
		try{
			// 從連線池中獲取到jedis分片物件
			shardedJedis = shardedJedisPool.getResource();
			return shardedJedis.set(key, value);
		} catch (Exception e){
			e.printStackTrace();
		} finally {
			if (null != shardedJedis){
				//關閉,檢測連線是否有效,有效則放回到連線池中,無效則重置狀態
				shardedJedis.close();
			}
		}
		return null;
	}
	
	//從redis獲取資料
	public String get(String key){
		ShardedJedis shardedJedis = null;
		
		try{
			// 從連線池中獲取到jedis分片物件
			shardedJedis = shardedJedisPool.getResource();
			return shardedJedis.get(key);
		} catch (Exception e){
			e.printStackTrace();
		} finally {
			if (null != shardedJedis){
				//關閉,檢測連線是否有效,有效則放回到連線池中,無效則重置狀態
				shardedJedis.close();
			}
		}
		return null;
	}
}

 

 

*重構RedisService

兩個方法有很多重複程式碼,如何消除呢?

類似js中的回撥來解決。目的簡化程式碼,抽取公用邏輯。

com.jt.common.service.Function<E, T>

package com.jt.common.service;

public interface Function<E, T> {

    public T execute(E e);

}

com.jt.common.service.RedisService

package com.jt.manage.service;

import org.springframework.beans.factory.annotation.Autowired;

import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;

public class RedisService {
	@Autowired
	private ShardedJedisPool shardedJedisPool;
	
	//儲存資料到redis中
	private <E,T>T execute(Function<ShardedJedis,T> function){
		ShardedJedis shardedJedis = null;
		
		try{
			// 從連線池中獲取到jedis分片物件
			shardedJedis = shardedJedisPool.getResource();
			return function.execute(shardedJedis);
		} catch (Exception e){
			e.printStackTrace();
		} finally {
			if (null != shardedJedis){
				//關閉,檢測連線是否有效,有效則放回到連線池中,無效則重置狀態
				shardedJedis.close();
			}
		}
		return null;
	}
	
	//儲存資料到redis中
	public String set(final String key, final String value){
		return this.execute(new Function<ShardedJedis, String>() {
			@Override
			public String execute(ShardedJedis shardedJedis) {
				return shardedJedis.set(key, value);
			}
		});
	}
	
	//儲存資料到redis中,並設定生存時間
	public String set(final String key, final String value, final Integer seconds){
		return this.execute(new Function<ShardedJedis, String>() {
			@Override
			public String execute(ShardedJedis shardedJedis) {
				String result = shardedJedis.set(key, value);
				shardedJedis.expire(key, seconds);	//設定生存時間
		return result;
			}
		});
	}
	
	//從redis獲取資料
	public String get(final String key){
		return this.execute(new Function<ShardedJedis, String>() {
			@Override
			public String execute(ShardedJedis shardedJedis) {
				return shardedJedis.get(key);
			}
		});
	}
	
	//設定key的生存時間,單位:秒
	public Long expire(final String key, final Integer seconds){
		return this.execute(new Function<ShardedJedis, Long>(){
			@Override
			public Long execute(ShardedJedis shardedJedis) {
				return shardedJedis.expire(key, seconds);
			}
		});
	}
	
	//刪除key
	public Long del(final String key){
		return this.execute(new Function<ShardedJedis, Long>(){
			@Override
			public Long execute(ShardedJedis shardedJedis) {
				return shardedJedis.del(key);
			}
		});
	}
}

 

 

快取的作用

快取在專案中或者系統中,分擔底層資料庫的壓力。快取是不能影響業務邏輯的。比如說快取伺服器宕機了,能說因為快取伺服器宕機了,業務走不下去了。這種理由當然不行。快取一定不能影響正常的業務邏輯的執行。