Redis分布式一致性算法原理

介绍: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();

    }


}

猜你喜欢

转载自blog.csdn.net/qq_36505948/article/details/81188193