复习电商笔记-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);
			}
		});
	}
}

缓存的作用

缓存在项目中或者系统中,分担底层数据库的压力。缓存是不能影响业务逻辑的。比如说缓存服务器宕机了,能说因为缓存服务器宕机了,业务走不下去了。这种理由当然不行。缓存一定不能影响正常的业务逻辑的执行。

猜你喜欢

转载自blog.csdn.net/qq_40680190/article/details/84110105