介绍:redis分布式一致性算法是用于提高缓存的命中率,使得对缓存的访问平均减少数据库的压力!
当一个请求或者文件到达的时候,我们会根据其请求信息进行Hash取值。然后根据缓存Cache节点的个数进行取模(假设有3个,那么就是模3)
每一个请求的信息经过Hash后将会被存储在不同的Cache节点上,这就是缓存!
============================================================================================
那么当因为生产需要,需要新增新的缓存节点或者因为负载有余而想要减少一个节点,那么这个时候,当请求再次访问的时候,Hash的值经过取模(节点个数为除数)后,则有可能指向新的Cache缓存节点,这个过程就是命中失败,那么该请求则会至极访问到数据库!
命中率的计算:关于如何判定一个分布式系统的缓存命中率,需要比较前后两次增加节点后,原来请求的Hash值取模后还在原来的Cache缓存节点上的个数(即:一定数量下,节点新增或减少前后,命中Cache缓存节点跟原来的一样的请求),占据总请求数量的比例,则为命中率!
============================================================================================
Hash一致性算法:
根据请求所提供的value信息(选定某种类型进行处理)进行Hash,然后将数据映射到32位的环形Hash空间中!(2^32-1)
(那么无论有多少请求Hash之后的数据都会在这个空间中)
对于设置的Cache缓存节点,我们也获取其某种数据信息(如IP或者端口等)进行Hash处理,映射到这个环形的Hash空间中。那么以顺时针为依据,当请求映射到Hash空间中的时候,顺时针查找节点,最近的节点则是其命中的Cache缓存节点。那无论我们熙增或者减少节点,影响的只会某一部分的弧形区域的请求!对其他节点的影响不大!
Hash倾斜性:
对于环形Hash空间而言,Cache缓存节点的分布位置则显得非常重要,越是平均低分布在环形之中,越是使得缓存的命中率提高!然而,生产中,根据IP等信息的Hash计算,往往具有很大的倾斜性,可能我们想象的是Cache缓存节点均匀地分布在环形Hash空间中,而实际上,多个节点几乎挨在一起,这种情况下必然会造成单一Cache缓存节点压力过大, 甚至服务器崩溃!因此但节点数量过于少,或者分布过于紧密时候,我们需要设置虚拟节点!
我们常用:ShardedJedisPool 类作为分布式redis的连接工具,连接池!
package com.mmall.common;
import com.mmall.utils.PropertiesUtil;
import com.mmall.utils.RedisShardedJedisPoolUtil;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.util.Hashing;
import redis.clients.util.Sharded;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class RedisShardedJedisPool {
private static ShardedJedisPool pool;//jedis连接池
private static RedisPool redisPool;//静态代码以保证项目启动就会被加载
private static Integer maxTotal= Integer.parseInt(PropertiesUtil.getProperties("redis.max.Total","20"));//允许的最大连接数
private static Integer maxIdel=Integer.parseInt(PropertiesUtil.getProperties("redis.max.Idel","10"));//当前最大空闲的实例个数
private static Integer minIdel=Integer.parseInt(PropertiesUtil.getProperties("redis.min.Idel","2"));//当前最小空闲的实例个数
private static Boolean testOneBorrow=Boolean.parseBoolean(PropertiesUtil.getProperties("redis.test.borrow","true"));//在获取一个实例的时候,判断是否一定是可用实例(是否做验证),如果为true肯定是可以用
private static Boolean testOneReturn=Boolean.parseBoolean(PropertiesUtil.getProperties("redis.test.return","true"));//在返回一个实例的时候,判断是否一定是可用实例(是否做验证),如果为true肯定是可以用
private static String redis1Ip= PropertiesUtil.getProperties("redis1.pool.ip");
private static Integer redis1Port=Integer.parseInt(PropertiesUtil.getProperties("redis1.pool.port","6379"));
private static String redis2Ip= PropertiesUtil.getProperties("redis2.pool.ip");
private static Integer redis2Port=Integer.parseInt(PropertiesUtil.getProperties("redis2.pool.port","6380"));
//private static Integer redisAuth=Integer.parseInt(RedisShardedJedisPoolUtil.getProperties("redis.pool.password"));
/*初始化为私有*/
private static void initPool(){
JedisPoolConfig config=new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdel);
config.setMinIdle(minIdel);
config.setTestOnBorrow(testOneBorrow);
config.setTestOnReturn(testOneReturn);
//设置参数当超过连接池数据量是否阻塞直到超时(true),false会超出异常
config.setBlockWhenExhausted(true);
//config.setMaxWaitMillis(60*1000);//当没有连接时候最长等待超时时间
JedisShardInfo info1=new JedisShardInfo(redis1Ip,redis1Port,1000*Integer.parseInt(PropertiesUtil.getProperties("redis.pool.timeout","20")));
JedisShardInfo info2=new JedisShardInfo(redis2Ip,redis2Port,1000*Integer.parseInt(PropertiesUtil.getProperties("redis.pool.timeout","20")));
List<JedisShardInfo> jedisShardInfoList=new ArrayList<JedisShardInfo>();
jedisShardInfoList.add(info1);
jedisShardInfoList.add(info2);
/*
* MURMUR_HASH:对应一致性算法
*
* */
pool=new ShardedJedisPool(config,jedisShardInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
log.info("=================初始化Redis成功===================");
}
/*初始化*/
static {
log.info("=================初始化Redis参数===================");
initPool();
}
/*从连接池获取一个jedis实例*/
public static ShardedJedis getShardedJedis(){
log.info("从连接池获取一个连接");
return pool.getResource();
}
/*将实例放置回连接池*/
public static void returnShardedJedis(ShardedJedis shardedjedis){
if (shardedjedis!=null){
log.info("处理完成,将当前连接归还连接池");
pool.returnResource(shardedjedis);
}
}
/*将坏的实例放置回连接池*/
public static void returnBrokenResource(ShardedJedis shardedjedis){
if (shardedjedis!=null){
pool.returnBrokenResource(shardedjedis);
}
}
public static void main(String[] args){
ShardedJedis shardedjedis=pool.getResource();
for (int i=0;i<10;i++){
shardedjedis.set("cyq"+i,"cyvalue"+i);
}
returnShardedJedis(shardedjedis);
pool.destroy();//临时调用,销毁连接池中所有连接
}
}
==========================================================================================
指定Utils工具来统一使用:
package com.mmall.utils;
import com.mmall.common.RedisPool;
import com.mmall.common.RedisShardedJedisPool;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ShardedJedis;
@Slf4j
public class RedisShardedJedisPoolUtil {
/*设置有效期多久(单位秒)*/
public static Long setExpireJedis(String key,int extime){
log.info("重置键值"+key+"的有效期为:"+String.valueOf(extime));
ShardedJedis shardedJedis=null;
Long shardedJedisResult=null;
try{
shardedJedis= RedisShardedJedisPool.getShardedJedis();
shardedJedisResult=shardedJedis.expire(key,extime);
}catch(Exception e){
log.error("set key:{} value:{} error",key,extime,e);//不用getMessage是因为信息太少了
RedisShardedJedisPool.returnBrokenResource(shardedJedis);
return null;
}
RedisShardedJedisPool.returnShardedJedis(shardedJedis);
return shardedJedisResult;
}
/*设置key-value值*/
public static String setJedis(String key,String value){
ShardedJedis shardedJedis=null;
String shardedJedisResult=null;
try{
shardedJedis= RedisShardedJedisPool.getShardedJedis();
shardedJedisResult=shardedJedis.set(key,value);
}catch(Exception e){
log.error("set key:{} value:{} error",key,value,e);//不用getMessage是因为信息太少了
RedisShardedJedisPool.returnBrokenResource(shardedJedis);
return null;
}
RedisShardedJedisPool.returnShardedJedis(shardedJedis);
return shardedJedisResult;
}
/*获取value*/
public static String getJedis(String key){
ShardedJedis shardedJedis=null;
String shardedJedisResult=null;
try{
shardedJedis= RedisShardedJedisPool.getShardedJedis();
shardedJedisResult=shardedJedis.get(key);
log.info(shardedJedisResult);
}catch(Exception e){
log.error("get key:{} value error",key,e);//不用getMessage是因为信息太少了
RedisShardedJedisPool.returnBrokenResource(shardedJedis);
return null;
}
RedisShardedJedisPool.returnShardedJedis(shardedJedis);
return shardedJedisResult;
}
/*有效时间,extime 单位是秒*/
public static String setExJedis(String key,int extime,String value){
log.info("设置键值"+key+"的有效期为:"+String.valueOf(extime));
ShardedJedis shardedJedis=null;
String shardedJedisResult=null;
try{
shardedJedis= RedisShardedJedisPool.getShardedJedis();
shardedJedisResult=shardedJedis.setex(key,extime,value);
}catch(Exception e){
log.error("set key:{} EXtime:{} value:{} error",key,extime,value,e);//不用getMessage是因为信息太少了
RedisShardedJedisPool.returnBrokenResource(shardedJedis);
return null;
}finally{
RedisShardedJedisPool.returnShardedJedis(shardedJedis);
}
return shardedJedisResult;
}
/*删除value*/
public static Long delJedis(String key){
ShardedJedis shardedJedis=null;
Long shardedJedisResult=null;
try{
shardedJedis= RedisShardedJedisPool.getShardedJedis();
shardedJedisResult=shardedJedis.del(key);
}catch(Exception e){
log.error("get key:{} value error",key,e);//不用getMessage是因为信息太少了
RedisShardedJedisPool.returnBrokenResource(shardedJedis);
return shardedJedisResult;
}
RedisShardedJedisPool.returnShardedJedis(shardedJedis);
return shardedJedisResult;
}
/*只有不存在才可以设置成功*/
public static Long setNxJedis(String key,String value){
log.info("分布式锁---设置键值:{}"+String.valueOf(key));
ShardedJedis shardedJedis=null;
Long shardedJedisResult=null;
try{
shardedJedis= RedisShardedJedisPool.getShardedJedis();
shardedJedisResult=shardedJedis.setnx(key,value);
}catch(Exception e){
log.error("setnx key:{} value:{} error",key,value,e);//不用getMessage是因为信息太少了
RedisShardedJedisPool.returnBrokenResource(shardedJedis);
return null;
}finally{
RedisShardedJedisPool.returnShardedJedis(shardedJedis);
}
return shardedJedisResult;
}
/*getSet方法
* 返回给定 key 的旧值。 当 key 没有旧值时,即 key 不存在时,返回 nil ,并设置新的值(value)
* */
public static String GetOldValNorSetValJedis(String key,String value){
log.info("分布式锁---设置键值:{}"+String.valueOf(key));
ShardedJedis shardedJedis=null;
String shardedJedisResult=null;
try{
shardedJedis= RedisShardedJedisPool.getShardedJedis();
shardedJedisResult=shardedJedis.getSet(key,value);
}catch(Exception e){
log.error("setnx key:{} value:{} error",key,value,e);//不用getMessage是因为信息太少了
RedisShardedJedisPool.returnBrokenResource(shardedJedis);
return null;
}finally{
RedisShardedJedisPool.returnShardedJedis(shardedJedis);
}
return shardedJedisResult;
}
/*调试测试方法*/
public static void main(String[] args){
ShardedJedis shardedJedis=RedisShardedJedisPool.getShardedJedis();
for (int i=0;i<=10;i++){
RedisShardedJedisPoolUtil.setJedis("Q_Q-"+i,"YYY");
String value= RedisShardedJedisPoolUtil.getJedis("Q_Q-"+i);
System.out.println("打印:"+value);
}
//RedisShardedJedisPoolUtiltmp.setExJedis("Q_Q",60*1,value+"111");
//RedisShardedJedisPoolUtiltmp.setExpireJedis("Q_Q",60*2);
System.out.println("结束了");
int j=0;
for(int i=0;i<1000;i++) {
j=j++;
}
System.out.println(j);
log.info("测试是否空指针");
Integer i = null;
int k = i.intValue();
}
}