Redis哈希表的实现要点

哈希算法的选择

针对不同的key使用不同的hash算法,如对整型、字符串以及大小写敏感的字符串分别使用不同的hash算法;

整型的Hash算法使用的是Thomas Wang's 32 Bit / 64 Bit Mix Function ,这是一种基于位移运算的散列方法。基于移位的散列是使用Key值进行移位操作。通常是结合左移和右移。每个移位过程的结果进行累加,最后移位的结果作为最终结果。这种方法的好处是避免了乘法运算,从而提高Hash函数本身的性能。

unsigned int dictIntHashFunction(unsigned int key)
{
key += ~(key << 15);
key ^= (key >> 10);
key += (key << 3);
key ^= (key >> 6);
key += ~(key << 11);
key ^= (key >> 16);
return key;
}

字符串使用的MurmurHash算法,MurmurHash算法具有高运算性能,低碰撞率的特点,由Austin Appleby创建于2008年,现已应用到Hadoop、libstdc++、nginx、libmemcached等开源系统。2011年Appleby被Google雇佣,随后Google推出其变种的CityHash算法。

murmur是 multiply and rotate的意思,因为算法的核心就是不断的乘和移位(x *= m; k ^= k >> r;)

unsigned int dictGenHashFunction(const void *key, int len) {
/* 'm' and 'r' are mixing constants generated offline.
They're not really 'magic', they just happen to work well. */
uint32_t seed = dict_hash_function_seed;
const uint32_t m = 0x5bd1e995;
const int r = 24; /* Initialize the hash to a 'random' value */
uint32_t h = seed ^ len; /* Mix 4 bytes at a time into the hash */
const unsigned char *data = (const unsigned char *)key; while(len >= 4) {
uint32_t k = *(uint32_t*)data; k *= m;
k ^= k >> r;
k *= m; h *= m;
h ^= k; data += 4;
len -= 4;
} /* Handle the last few bytes of the input array */
switch(len) {
case 3: h ^= data[2] << 16;
case 2: h ^= data[1] << 8;
case 1: h ^= data[0]; h *= m;
}; /* Do a few final mixes of the hash to ensure the last few
* bytes are well-incorporated. */
h ^= h >> 13;
h *= m;
h ^= h >> 15; return (unsigned int)h;
}

一个好的hash算法需要满足两个条件:

1) 性能高,运算足够快;

2) 相邻的数据hash后分布广;即使输入的键是有规律的,算法仍然能给出一个很好的随机分布性;

比如:murmur计算"abc"是1118836419,"abd"是413429783。而使用Horner算法,"abc"是96354, "abd"就比它多1(96355);

rehash

负载因子 = 当前结点数/桶的大小,超过1表示肯定有碰撞了;碰撞的结点,通过链表拉链起来;

所有哈希表的初始桶的大小为4,根据负载因子的变化进行rehash,重新分配空间(扩展或收缩)

当hash表的负载因子超过1后,进行扩展(小于0.01时,进行收缩);

所谓扩展,就是新建一个hash表2,将桶的数量增大(具体增大为:第一个大于等于usedSize的2的n次冥);然后将hash表1中结点都转移到hash表2中;

rehash的触发条件:

当做BGSAVE或BGREWRITEEOF时,负载因子超过5时触发rehash,

没有BGSAVE或BGREWRITEEOF时,负载因子超过1时触发rehash;

在BGSAVE或BGREWRITEEOF时,使用到Linux的写时复制,如果这时候做rehash,将会好用更多的内存空间(没有变化的结点用一份,变化的结点复制一份)

渐进式rehash

一个hash表中的数据可能有几百上千万,不可能一次rehash转移完,需要分批逐渐转移;

在rehash的过程中,对redis的查询、更新操作首先会在hash0中查找,没有找到,然后转到hash1中操作;

对于插入操作,直接插入到hash1中;最终目标是将hash表1变为空表,rehash完成;

value的存储

键值对的实现,value 是一个union,对整型和字符串使用不同的存储对象;

// 键
void *key; // 值
union {
void *val;
uint64_t u64;
int64_t s64;
} v;

ref:

《Hash 函数概览》http://www.oschina.net/translate/state-of-hash-functions

《redis设计与实现》

Posted by: 大CC | 18NOV,2015

博客:blog.me115.com [订阅]

Github:大CC

Redis哈希表的实现要点的更多相关文章

  1. Redis哈希表总结

    本文及后续文章,Redis版本均是v3.2.8 在文章<Redis 数据结构之dict><Redis 数据结构之dict(2)>中,从代码层面做了简单理解.总感觉思路的不够条理 ...

  2. (四)Redis哈希表Hash操作

    Hash全部命令如下: hset key field value # 将哈希表key中的字段field的值设为value hget key field # 返回哈希表key中的字段field的值val ...

  3. redis哈希表数据类型键的查询和删除命令

    一.查询 命令名称:hget 语法:hget key field 功能:返回哈希表key中给定域field的值 返回值: 给定域的值. 当给定域不存在或是给定key不存在时,返回nil 命令名称:hg ...

  4. redis哈希表数据类型键的设置

    命令名称:hset 语法:hset key field value 功能: 1)将哈希表key中的域field的值设为value. 2)如果key不存在,一个新的哈希表被创建并进行hset操作. 3) ...

  5. Redis源码研究:哈希表 - 蕫的博客

    [http://dongxicheng.org/nosql/redis-code-hashtable/] 1. Redis中的哈希表 前面提到Redis是个key/value存储系统,学过数据结构的人 ...

  6. redisTemplate写哈希表遇到的坑

    本文系原创,如有转载,请注明出处 在使用spring的redisTemplate进行redis哈希表的相关操作时,遇到了下面比较奇怪的情况: 1.删掉哈希表所属的key之后,重新get这个key的值, ...

  7. 《闲扯Redis八》Redis字典的哈希表执行Rehash过程分析

    一.前言 随着操作的不断执行, 哈希表保存的键值对会逐渐地增多或者减少, 为了让哈希表的负载因子(load factor)维持在一个合理的范围之内, 当哈希表保存的键值对数量太多或者太少时, 程序需要 ...

  8. Redis常用操作-------Hash(哈希表)

    1.HDEL key field [field ...] 删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略. 在Redis2.4以下的版本里, HDEL 每次只能删除单个域,如果你需要在一 ...

  9. redis哈希缓存数据表

    redis哈希缓存数据表 REDIS HASH可以用来缓存数据表的数据,以后可以从REDIS内存数据库中读取数据. 从内存中取数,无疑是很快的. var FRedis: IRedisClient; F ...

随机推荐

  1. 【iCore3 双核心板_FPGA】例程一:认识FPGA

    实验指导书及代码包下载: http://pan.baidu.com/s/1kUa05FL iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...

  2. P1073 最优贸易

    #include <bits/stdc++.h> using namespace std; const int maxn = 100005; int head1[maxn], head2[ ...

  3. eNSP的使用

    1- 进入华为路由器界面配置ipThe device is running!####################################Nov  1 2016 23:39:24-08:00 ...

  4. jdk环境变量

    exportJAVA_HOME=/opt/jdk1.8.0_92 exportJRE_HOME=${JAVA_HOME}/jre   exportCLASSPATH=.:${JAVA_HOME}/li ...

  5. RDIFramework.NET ━ .NET快速信息化系统开发框架钜献 V2.9 版本震撼发布

    RDIFramework.NET ━ .NET快速信息化系统开发框架钜献 V2.9 版本震撼发布 全新体验.全新感觉.2015钜献! 继上个版本“RDIFramework.NET V2.8版本发布”5 ...

  6. file_get_contents抓取远程URL内容

    /** * POST URL * @param $url * @param null $post * @return false / string */ public static function ...

  7. 小试牛刀C#作为脚本语言执行解密

    背景 我们知道Unity3d是通过C#脚本语言的形式来实现游戏的逻辑代码编写,同样SCOTT服务器也设置了通过C#脚本来实现游戏逻辑,但是本文并不是想真正分析解密他们的运行机制,只是想通过自己的一个需 ...

  8. [Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列

    第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列.使用下面代码,可以很容易使一个应用程序陷入泥潭. while(true){} 而且它并不需要一个无限循环来写一个缓慢的程序.代码 ...

  9. Bitnami redmine备份升级步骤

    从3.2.1升级至3.3.0,不确定数据库结构是否有变化,主要过程:先停止服务,安装redmine模块,恢复服务. 以下适用于windows操作系统,采用Bitnami安装方式: 1.完整备份 Fol ...

  10. HTML5之FileReader的使用

    HTML5定义了FileReader作为文件API的重要成员用于读取文件,根据W3C的定义,FileReader接口提供了读取文件的方法和包含读取结果的事件模型. FileReader的使用方式非常简 ...