Redis3.0集群crc16算法php实现方法(php取得redis3.0集群中redis数据所在的redis分区插槽,并根据分区插槽取得分区所在redis服务器地址)

数据分区

       Redis集群将数据分区后存储在多个节点上,即不同的分区存储在不同的节点上,每个节点可以存储多个分区。每个分区在Redis中也被称为“hash slot”,Redis集群中总共规划了16384个分区。
例如:当集群中有3个节点时,节点A将包含0-5460分区,节点B将包含5461-10922分区,节点C将包含10923-16383分区。
每个key将会存储到一个唯一的分区中,每个分区其实就是一组key的集合,两者对应关系为:key的CRC16校验码%16384=hash slot(分区标记).可见Redis并没有像Memecache一样使用一致性哈希。社区说采用此规则的key分布是相当的均匀,在我们的测试中也印证了这一点。
在Redis集群中添加或者移除一个节点时相当容易的事情。例如:添加新节点D时,需要做的只是从A、B、C节点中移动一些分区给D。类似的,移除A时,只需将原属A的分区移动给B和C,等A变空时移除即可。
节点间的分区移动不需要停止服务,所以添加节点、移除节点或者改变节点的分区数量不需要停止集群服务。
客户端访问集群时,理论上可以访问集群中的任一节点。此时,被访问的数据可能不存在于被访问的节点中,但是被访问节点能自动获知目标节点,并重定向客户端的访问,即将目标节点地址返回给客户端,客户端再次发起访问请求。当然,好的客户端工具应该实现数据分区和节点对应关系的缓存,并在对应关系发生改变时能自动更新。目前,包括Jedis在内的几个客户端工具已经实现了此功能。

CRC概念

        CRC基本原理不懂的,请移步维基百科:循环冗余检验码

        通常根据CRC校验码的位数(也等于生成多项式【G(x)】最高的幂次)的不同来区分不同的CRC算法,如CRC-1、CRC-8、CRC-16等。幂次相同的情况下,不同的标准也有不同的CRC算法。比如G(x)最高次幂为16的时候有:CRC-16-CCITT、CRC-16-IBM等。Redis使用的是CRC-16-CCITT标准,即G(x)为:x16 + x12 + x5 + 1 。

        G(x)的通常表征方式是将多项式转换成二进制: 1 0001 0000 0010 0001。用十六进制表示为:0x11021。该数存储空间是17位(2个字节+1个位,C语言实际存储是3个字节),实际上,在模二除的时候,被除数的最高位 1 和除数最高位 1 总是对齐的,其异或结果,总为0,故可省略,则G(x) = 0x1021(2个字节),节省了一个字节的空间。 摘取天上星 原创,转载请标明作者出处


源码

redis的src目录下的 crc16.c文件:
static const uint16_t crc16tab[256]= {
    0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
    0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
    0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
    0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
    0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
    0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
    0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
    0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
    0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
    0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
    0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
    0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
    0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
    0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
    0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
    0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
    0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
    0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
    0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
    0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
    0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
    0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
    0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
    0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
    0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
    0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
    0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
    0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
    0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
    0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
    0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
    0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
};

uint16_t crc16(const char *buf, int len) {
    int counter;
    uint16_t crc = 0;
    for (counter = 0; counter < len; counter++)
            crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
    return crc;
}

前文提到了CRC校验码不同的机构有不同的标准,这里Redis遵循的标准是CRC-16-CCITT标准,这也是被XMODEM协议使用的CRC标准,所以也常用XMODEM CRC代指。

该段代码的算法原理并不是作者首创的,这是比较经典的“基于字节查表法的CRC校验码生成算法”(本文为"摘取天上星"周末实操所得,完全可用!)

php实现redis crc16验证标准的方法.

function redisCRC16 (&$ptr){
    $crc_table=array(
	    0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
	    0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
	    0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
	    0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
	    0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
	    0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
	    0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
	    0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
	    0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
	    0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
	    0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
	    0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
	    0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
	    0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
	    0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
	    0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
	    0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
	    0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
	    0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
	    0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
	    0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
	    0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
	    0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
	    0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
	    0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
	    0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
	    0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
	    0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
	    0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
	    0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
	    0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
	    0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
    );
    $crc = 0x0000;
    for ($i = 0; $i < strlen($ptr); $i++)
        $crc =  $crc_table[(($crc>>8) ^ ord($ptr[$i]))] ^ (($crc<<8) & 0x00FFFF);
    return $crc;
}
$test = chr(0xC6).chr(0xCE).chr(0xA2).chr(0x03); // CRC16-CCITT = 0xE2B4
$key1='key1'; 
$key2='key2';
$key3='key3';
echo $key1_db=redisCRC16($key1)%16384; //得出在redis(集群)中键值为'key1'的数据存储插槽为9189
echo "<br/>";
echo $key2_db=redisCRC16($key2)%16384; //得出在redis(集群)中键值为'key2'的数据存储插槽为4998
echo "<br/>";
echo $key3_db=redisCRC16($key3)%16384; //得出在redis(集群)中键值为'key3'的数据存储插槽为935

根据crc16算法取得的redis数据分区插槽取得 分区所在服务器地址:

首先我们先看一下本地集群环境中的redis分区插槽区间范围(cd 切换到redis安装包目录下的src目录[注意:redis解压包包非安装后的程序所在目录地址]):

[zatsx@localhost src]$  ./redis-trib.rb check 127.0.0.1:6384
Connecting to node 127.0.0.1:6384: OK
Connecting to node 127.0.0.1:6381: OK
Connecting to node 127.0.0.1:6383: OK
Connecting to node 127.0.0.1:6379: OK
Connecting to node 127.0.0.1:6380: OK
Connecting to node 127.0.0.1:6382: OK
>>> Performing Cluster Check (using node 127.0.0.1:6384)
S: 91329dacb2ac77d9295ed46ecaaec6f2c415f7f6 127.0.0.1:6384
   slots: (0 slots) slave
   replicates 0be8c0b96e23a5843ece9d86cb6d287c92529a8c
M: 0be8c0b96e23a5843ece9d86cb6d287c92529a8c 127.0.0.1:6381
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
S: 53926c1f8b757c6db2d53e12ee94b8c1a761e663 127.0.0.1:6383
   slots: (0 slots) slave
   replicates beff88cb6dcf6897a6c6de36350032984a4bdf33
M: 2fb7a8edab2038d7fabe305dd0099de8bdf1f1e6 127.0.0.1:6379
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
M: beff88cb6dcf6897a6c6de36350032984a4bdf33 127.0.0.1:6380
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
S: 4738074195072ae29c3f3160382e97c3b56a6392 127.0.0.1:6382
   slots: (0 slots) slave
   replicates 2fb7a8edab2038d7fabe305dd0099de8bdf1f1e6
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
 
 
以上内容可以看到M位主,S为从(官方要求的redis集群环境必须为6台以上偶数形式的服务器数量,否则无法创建集群环境)
从上面输出可以看到分区插槽所在服务器位置:
 
slots:10923-16383 (5461 slots) 127.0.0.1:6381 对应从机 地址为127.0.0.1:6384
slots:0-5460 (5461 slots) 127.0.0.1:6379 master 对应从机 地址为127.0.0.1:6381
slots:5461-10922 (5462 slots) 127.0.0.1:6380 master对应从机 地址为:127.0.0.1:6383
 
 


一共三台主分区,可以通过 取得的插槽位置 定位到 不同的redis分区上去取对应的数据,一但主分区数量有变动,就需要根据实际分区数量重新定位插槽分区范围
存取redis地址根据ceil(redisCRC16(redis $key值)%16384 / intval(16384/集群master数)) 得到 对应 的 redis集群服务器顺序 地址,并根据对应区间地址指向到对应的 redis区间master地址即可,注意:如果对应的master区间挂掉了,也不必担心,只需要将地址变更为master主区间对应的 slave从区间服务器地址即可一样取得redis数据,而这正是redis3.0后续版本的精妙之处,不需要第三方插件即可在服务端制动实现集群主备模式,不会影响用户正常读取,当然如果是master和对应的slave一起挂掉了那就没办法了,通常这种几率是很小很小的 小到可以忽略即可...
关于redis添加删除集群节点的方法请参阅相关资料,摘取天上星 原创,转载请标明作者出处,本文暂不详叙!

你可能感兴趣的:(redis)