6.redis分布式算法原理

转载自:https://mp.weixin.qq.com/s?src=11&timestamp=1592644157&ver=2412&signature=pi8qPGJHFwrwWpkJ8b-6UbSHBEc9Sgi0d5RNjgHJmG3sZBunGqvchBwwQA-XffYChgwHNqzKyED0FIVtcBhdKKcs9OTyZYlJ1i6So3q1QB6ev8oR1uPWlLOUl6eSF9aJ&new=1

一.hash算法

散列表(Hash table,也叫哈希表),是依据关键码值(Key value)而直接进行訪问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来訪问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表,如果不使用散列函数,而是直接讲key作为位置进行访问记录,则会浪费很大的空间.所以散列存在是必须的!

散列函数,即哈希算法将任意长度的二进制值映射为固定长度的较小二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。

散列函数用来将key运算成一个地址值,多个地址值组成hash表用来存储value.

整个散列过程其实就是两步。

  (1) 在存储时,通过散列函数计算记录的散列地址,并按此散列地址存储该记录。

   (2) 当查找记录时,我们通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录。由于存取用的是同一个散列函数, 因此结果当然也是相同的。



--------------------------------

  最最简单易懂的说法就是,hash算法将你传入的key运算成一个地址值,类似指针那样,指向内存中的某块区域,存的时候根据该地址值,将value存到这个地址值映射的内存区域里,取得时候从key作hash运算后得出的地址值所对应的内存区域中取出结果;

--------------------------------

  自己的理解:哈希一般是基于数组的,只要知道数组的下表,就可以很快的存取该元素。

  通过对元素的一个关键字,在通过一个函数,把这个关键字转换成数组的下标,这个函数就是哈希函数。

  这样,就能通过关键字很快的存取元素。

  有时候会发生冲突现象,即不同的关键字,通过哈希函数算出的结果是一样的,这时候就可以用线性探测或者连地址法等来解决。

  在使用哈希的时候,要考虑你存的数据个数的大小,然后再确定数组的大小,一般数组的大小是数据个数的两倍(好像)。具体为什么就不是特别清楚了。。。。

--------------------------------

二.hash一致性算法

当我们在做数据库分库分表或者是分布式缓存时,不可避免的都会遇到一个问题:
如何将数据均匀的分散到各个节点中,并且尽量的在加减节点时能使受影响的数据最少。

Hash 取模
随机放置就不说了,会带来很多问题。通常最容易想到的方案就是 hash取模了。

可以将传入的 Key 按照 index=hash(key)%N 这样来计算出需要存放的节点。其中 hash 函数是一个将字符串转换为正整数的哈希映射方法,N 就是节点的数量。

这样可以满足数据的均匀分配,但是这个算法的容错性和扩展性都较差。

比如增加或删除了一个节点时,所有的 Key 都需要重新计算,显然这样成本较高,为此需要一个算法满足分布均匀同时也要有良好的容错性和拓展性。

1. 一致 Hash 算法

一致 Hash 算法是将所有的哈希值构成了一个环,其范围在 0~2^32-1。如下图:

image

之后将各个节点散列到这个环上,可以用节点的 IP、hostname 这样的唯一性字段作为 Key 进行 hash(key),散列之后如下:

object对象hash之后存储在圆环当中成N1,N1=hash(object)

之后需要将数据定位到对应的节点上,使用同样的 hash函数 将 Key 也映射到这个环上。

image

这样按照顺时针方向就可以把 k1 定位到 N1节点,k2 定位到 N3节点,k3 定位到 N2节点

容错性

这时假设 N1 宕机了:

image

依然根据顺时针方向,k2 和 k3 保持不变,只有 k1 被重新映射到了 N3。这样就很好的保证了容错性,当一个节点宕机时只会影响到少少部分的数据。

拓展性

当新增一个节点时:

image

在 N2 和 N3 之间新增了一个节点 N4 ,这时会发现受印象的数据只有 k3,其余数据也是保持不变,所以这样也很好的保证了拓展性。

2.hash倾斜性

到目前为止该算法依然也有点问题:当节点较少时会出现数据分布不均匀的情况:

当hash比较靠近如下图的时候,大多数的数据可能会存储在N1上,N2存储的可能比较少,此时可以使用虚拟节点来解决这种问题。

虚拟节点
hash倾斜性

这样会导致大部分数据都在 N1 节点,只有少量的数据在 N2 节点。

为了解决这个问题,一致哈希算法引入了虚拟节点。将每一个节点都进行多次 hash,生成多个节点放置在环上称为虚拟节点:

虚拟节点

计算时可以在 IP 后加上编号来生成哈希值。

这样只需要在原有的基础上多一步由虚拟节点映射到实际节点的步骤即可让少量节点也能满足均匀性。

三.redis分布式缓存

redis分布式缓存池是通过ShardedJedisPool来实现的,在本项目当中使用的配置和3redis连接池当中一样,在本例当中使用了2个redis节点,所以,创建了2个

package com.mall.common;

import com.mall.util.PropertiesUtil;
import redis.clients.jedis.*;
import redis.clients.jedis.util.Hashing;
import redis.clients.jedis.util.Sharded;

import java.util.ArrayList;
import java.util.List;

//redis分片连接池
public class RedisShardedPool {

    //ShardedJedisPool的连接池

    private static ShardedJedisPool pool;
    //最大连接数
    private static Integer maxTotal=Integer.parseInt(PropertiesUtil.getProperty("redis.max.total","20"));
    //最大空闲的连接数
    private static Integer maxIdle=Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle","10"));
    //最小空闲连接数
    private static Integer minIdle=Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle","2"));
    //从jedis连接池获取连接时,校验并返回可用的连接
    private static Boolean testOnBorrow=Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow","true"));
    //把连接放回jedis连接池时,校验并返回可用的连接
    private static Boolean testOnReturn=Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return","true"));

    /**
     * 此处由于配置了2个redis节点,所以此处要配置2个
     */
    private static String redisIp1=PropertiesUtil.getProperty("redis1.ip");
    private static Integer redisPort1=Integer.parseInt(PropertiesUtil.getProperty("redis1.port"));
    private static String redisIp2=PropertiesUtil.getProperty("redis2.ip");
    private static Integer redisPort2=Integer.parseInt(PropertiesUtil.getProperty("redis2.port"));

    private static void initPool(){
        JedisPoolConfig config=new JedisPoolConfig();
        config.setMaxTotal(maxTotal);//默认8
        config.setMaxIdle(maxIdle);//默认8
        config.setMinIdle(minIdle);//默认0

        config.setTestOnBorrow(testOnBorrow);//默认false
        config.setTestOnReturn(testOnReturn);//默认false

        //默认就是true,连接耗尽的时候是否阻塞,false会报异常,true阻塞直到超时,超时会报超时异常
        config.setBlockWhenExhausted(true);

        JedisShardInfo info1=new JedisShardInfo(redisIp1,redisPort1,1000*2);
        JedisShardInfo info2=new JedisShardInfo(redisIp2,redisPort2,1000*2);
        //此处是两个redis节点,不指定会默认arraylist的size
        List<JedisShardInfo> jedisShardInfoList= new ArrayList<>(2);
        jedisShardInfoList.add(info1);
        jedisShardInfoList.add(info2);

        /**
         * redis会提供2个分片策略:一个是MURMUR_HASH(即一致性hash算法),一个是md5的hash算法(普通的hash)
         */
        pool=new ShardedJedisPool(config,jedisShardInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);

    }

    //类在加载到jvm的时候加载连接池
    static {
        initPool();
    }

    //获取redis
    public static ShardedJedis getJedis(){
        return pool.getResource();
    }

    //将redis放回连接池,释放资源
    public static void close(ShardedJedis shardedJedis){
        if(shardedJedis !=null){
            shardedJedis.close();
        }
    }


    public static void main(String[] args) {
        ShardedJedis shardedJedis=pool.getResource();// Jedis jedis=RedisPool.getJedis()
        /*shardedJedis.set("name","codeSheep");
        close(shardedJedis);*/

        //pool.destroy();//销毁连接池当中的所有连接

        for (int i=0;i<10;i++){
            shardedJedis.set("key"+i,"value"+i);
        }
        close(shardedJedis);
        System.out.println("program is end");
    }

}

效果
package com.mall.util;


import com.mall.common.RedisShardedPool;
import com.mall.common.RedisShardedPool;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ShardedJedis;

@Slf4j
public class RedisShardedPoolUtil {

    public static String set(String key,String value){
        ShardedJedis redisShardedPool= RedisShardedPool.getJedis();//拿到一个redis的连接
        String result=redisShardedPool.set(key,value);
        RedisShardedPool.close(redisShardedPool);
        return result;
    }

    public static String get(String key){
        ShardedJedis shardedJedis=RedisShardedPool.getJedis();
        String value=shardedJedis.get(key);
        RedisShardedPool.close(shardedJedis);
        return value;
    }

    //exTime的单位是s,设置key的值为value,存活时间是exTime
    public static String setEx(String key,String value,int exTime){
        ShardedJedis shardedJedis=RedisShardedPool.getJedis();//拿到一个redis的连接
        //设置key的值为value,存活时间是exTime
        String result=shardedJedis.setex(key,exTime,value);
        RedisShardedPool.close(shardedJedis);
        return result;
    }

    //重置key的有效期
    public static Long expire(String key,int exTime){
        ShardedJedis shardedJedis=RedisShardedPool.getJedis();//拿到一个redis的连接
        //设置key的值为value,存活时间是exTime
        Long result=shardedJedis.expire(key,exTime);
        RedisShardedPool.close(shardedJedis);
        return result;
    }

    //删除key
    public static Long del(String key){
        ShardedJedis shardedJedis=RedisShardedPool.getJedis();//拿到一个redis的连接
        //删除key
        Long result=shardedJedis.del(key);
        RedisShardedPool.close(shardedJedis);
        return result;
    }

    public static void main(String[] args) {
        String result= RedisShardedPoolUtil.set("sex","男");
        String value= RedisShardedPoolUtil.get("sex");
        System.out.println(value);

        RedisShardedPoolUtil.setEx("zhandouli","10000",60*10);

        RedisShardedPoolUtil.expire("sex",60*20);
        //RedisShardedPoolUtil.del("sex");
        System.out.println("end");

    }


}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,576评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,515评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,017评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,626评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,625评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,255评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,825评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,729评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,271评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,363评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,498评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,183评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,867评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,338评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,458评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,906评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,507评论 2 359