├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── benchmark ├── index.js └── old │ └── index.js ├── index.d.ts ├── lib └── index.js ├── package.json └── test └── hash.spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base/legacy", 3 | "parserOptions":{ 4 | "ecmaFeatures": { 5 | "experimentalObjectRestSpread": true 6 | } 7 | }, 8 | "rules": { 9 | "max-len": 0, 10 | "no-plusplus": 0, 11 | "no-bitwise": 0, 12 | "no-param-reassign": 0, 13 | "no-undef": 0 14 | }, 15 | "globals": {} 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | coverage 4 | node_modules 5 | .idea 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # IntelliJ project files 2 | .idea 3 | *.iml 4 | out 5 | gen 6 | 7 | # Irrelevant files and folders 8 | .editorconfig 9 | .travis.yml 10 | benchmark 11 | coverage 12 | test 13 | .travis.yml 14 | .gitignore 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | env: 4 | - CXX=g++-4.8 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | packages: 10 | - g++-4.8 11 | node_js: 12 | - "4" 13 | - "6" 14 | - "7" 15 | - "8" 16 | - "9" 17 | - "10" 18 | install: 19 | - npm install 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Mike Diarmid (Salakar) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this library except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Coverage Status](https://coveralls.io/repos/github/Salakar/cluster-key-slot/badge.svg?branch=master)](https://coveralls.io/github/Salakar/cluster-key-slot?branch=master) 2 | ![Downloads](https://img.shields.io/npm/dt/cluster-key-slot.svg) 3 | [![npm version](https://img.shields.io/npm/v/cluster-key-slot.svg)](https://www.npmjs.com/package/cluster-key-slot) 4 | [![dependencies](https://img.shields.io/david/Salakar/cluster-key-slot.svg)](https://david-dm.org/Salakar/cluster-key-slot) 5 | [![License](https://img.shields.io/npm/l/cluster-key-slot.svg)](/LICENSE) 6 | Follow on Twitter 7 | 8 | # Redis Key Slot Calculator 9 | 10 | A high performance redis cluster key slot calculator for node redis clients e.g. [node_redis](https://github.com/NodeRedis/node_redis), [ioredis](https://github.com/luin/ioredis) and [redis-clustr](https://github.com/gosquared/redis-clustr/). 11 | 12 | This also handles key tags such as `somekey{actualTag}`. 13 | 14 | ## Install 15 | 16 | Install with [NPM](https://npmjs.org/): 17 | 18 | ``` 19 | npm install cluster-key-slot --save 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```js 25 | const calculateSlot = require('cluster-key-slot'); 26 | const calculateMultipleSlots = require('cluster-key-slot').generateMulti; 27 | 28 | // ... 29 | 30 | // a single slot number 31 | const slot = calculateSlot('test:key:{butOnlyThis}redis'); 32 | // Buffer is also supported 33 | const anotherSlot = calculateSlot(Buffer.from([0x7b, 0x7d, 0x2a])); 34 | 35 | // multiple keys - multi returns a single key slot number, returns -1 if any 36 | // of the keys does not match the base slot number (base is defaulted to first keys slot) 37 | // This is useful to quickly determine a singe slot for multi keys operations. 38 | const slotForRedisMulti = calculateMultipleSlots([ 39 | 'test:key:{butOnlyThis}redis', 40 | 'something:key45:{butOnlyThis}hello', 41 | 'example:key46:{butOnlyThis}foobar', 42 | ]); 43 | ``` 44 | 45 | ## Benchmarks 46 | 47 | `OLD` in these benchmarks refers to the `ioredis` crc calc and many of the other calculators that use `Buffer`. 48 | 49 | ```text 50 | node -v  ✔  16.38G RAM  10:29:07 51 | v10.15.3 52 | 53 | NEW tags x 721,445 ops/sec ±0.44% (90 runs sampled) 54 | OLD tags x 566,777 ops/sec ±0.97% (96 runs sampled) 55 | NEW without tags x 2,054,845 ops/sec ±1.77% (92 runs sampled) 56 | OLD without tags x 865,839 ops/sec ±0.43% (96 runs sampled) 57 | NEW without tags singular x 6,354,097 ops/sec ±1.25% (94 runs sampled) 58 | OLD without tags singular x 4,012,250 ops/sec ±0.96% (94 runs sampled) 59 | NEW tags (Buffer) x 552,346 ops/sec ±1.35% (92 runs sampled) 60 | ``` 61 | 62 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require('benchmark'); 2 | var suite = new Benchmark.Suite(); 3 | var oldGenerate = require('./old'); 4 | var newGenerate = require('../lib'); 5 | var elems = ['123465', 'foobar', 'abcdefghijklmnopqrstuvwxyz', 'gsdfhan$%^&*(sdgsdnhshcs', 'foobar{foobar']; 6 | /* eslint func-names: 0 */ 7 | 8 | // add listeners 9 | suite.add('NEW tags', function () { 10 | var i = 0; 11 | for (; i < elems.length; i++) { 12 | newGenerate('abc{' + elems[i] + '}}{yeahh}'); 13 | } 14 | }); 15 | 16 | suite.add('OLD tags', function () { 17 | var i = 0; 18 | for (; i < elems.length; i++) { 19 | oldGenerate('abc{' + elems[i] + '}}{yeahh}'); 20 | } 21 | }); 22 | 23 | suite.add('NEW without tags', function () { 24 | var i = 0; 25 | for (; i < elems.length; i++) { 26 | newGenerate(elems[i]); 27 | } 28 | }); 29 | 30 | suite.add('OLD without tags', function () { 31 | var i = 0; 32 | for (; i < elems.length; i++) { 33 | oldGenerate(elems[i]); 34 | } 35 | }); 36 | 37 | suite.add('NEW without tags singular', function () { 38 | newGenerate(elems[2]); 39 | }); 40 | 41 | suite.add('OLD without tags singular', function () { 42 | oldGenerate(elems[2]); 43 | }); 44 | 45 | suite.add('NEW tags (Buffer)', function () { 46 | var i = 0; 47 | for (; i < elems.length; i++) { 48 | newGenerate(Buffer.from('abc{' + elems[i] + '}}{yeahh}')); 49 | } 50 | }); 51 | 52 | suite.on('cycle', function (event) { 53 | console.log(String(event.target)); 54 | }); 55 | 56 | suite.on('complete', function () { 57 | console.log('\n\nFastest is ' + this.filter('fastest').map('name')); 58 | }); 59 | 60 | suite.run({ delay: 1, minSamples: 150 }); 61 | -------------------------------------------------------------------------------- /benchmark/old/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2001-2010 Georges Menie (www.menie.org) 3 | * Copyright 2010 Salvatore Sanfilippo (adapted to Redis coding style) 4 | * Copyright 2015 Zihua Li (http://zihua.li) (ported to JavaScript) 5 | * All rights reserved. 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of the University of California, Berkeley nor the 15 | * names of its contributors may be used to endorse or promote products 16 | * derived from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | /* CRC16 implementation according to CCITT standards. 31 | * 32 | * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the 33 | * following parameters: 34 | * 35 | * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" 36 | * Width : 16 bit 37 | * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) 38 | * Initialization : 0000 39 | * Reflect Input byte : False 40 | * Reflect Output CRC : False 41 | * Xor constant to output CRC : 0000 42 | * Output for "123456789" : 31C3 43 | */ 44 | 45 | var crc16tab = [ 46 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 47 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 48 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 49 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 50 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 51 | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 52 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 53 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 54 | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 55 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 56 | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 57 | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 58 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 59 | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 60 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 61 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 62 | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 63 | 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 64 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 65 | 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 66 | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 67 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 68 | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 69 | 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 70 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 71 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 72 | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 73 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 74 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 75 | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 76 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 77 | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 78 | ]; 79 | 80 | function crc16(str) { 81 | var buf = new Buffer(str); 82 | var crc = 0; 83 | for (var i = 0, len = buf.length; i < len; i++) { 84 | crc = (crc << 8) ^ crc16tab[((crc >> 8) ^ buf[i]) & 0x00ff]; 85 | } 86 | return crc; 87 | } 88 | 89 | function calcSlot(key) { 90 | var s = key.indexOf('{'); 91 | if (s !== -1) { 92 | var e = key.indexOf('}', s + 2); 93 | if (e !== -1) { 94 | key = key.slice(s + 1, e); 95 | } 96 | } 97 | return crc16(key) & 16383; 98 | } 99 | 100 | module.exports = calcSlot; 101 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'cluster-key-slot' { 2 | // Convert a string or Buffer into a redis slot hash. 3 | function calculate(value: string | Buffer): number; 4 | 5 | // Convert an array of multiple strings or Buffers into a redis slot hash. 6 | // Returns -1 if one of the keys is not for the same slot as the others 7 | export function generateMulti(values: Array): number; 8 | 9 | export = calculate; 10 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2001-2010 Georges Menie (www.menie.org) 3 | * Copyright 2010 Salvatore Sanfilippo (adapted to Redis coding style) 4 | * Copyright 2015 Zihua Li (http://zihua.li) (ported to JavaScript) 5 | * Copyright 2016 Mike Diarmid (http://github.com/salakar) (re-write for performance, ~700% perf inc) 6 | * All rights reserved. 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of the University of California, Berkeley nor the 16 | * names of its contributors may be used to endorse or promote products 17 | * derived from this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 20 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 23 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | /* CRC16 implementation according to CCITT standards. 32 | * 33 | * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the 34 | * following parameters: 35 | * 36 | * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" 37 | * Width : 16 bit 38 | * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) 39 | * Initialization : 0000 40 | * Reflect Input byte : False 41 | * Reflect Output CRC : False 42 | * Xor constant to output CRC : 0000 43 | * Output for "123456789" : 31C3 44 | */ 45 | 46 | var lookup = [ 47 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 48 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 49 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 50 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 51 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 52 | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 53 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 54 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 55 | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 56 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 57 | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 58 | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 59 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 60 | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 61 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 62 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 63 | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 64 | 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 65 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 66 | 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 67 | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 68 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 69 | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 70 | 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 71 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 72 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 73 | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 74 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 75 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 76 | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 77 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 78 | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 79 | ]; 80 | 81 | /** 82 | * Convert a string to a UTF8 array - faster than via buffer 83 | * @param str 84 | * @returns {Array} 85 | */ 86 | var toUTF8Array = function toUTF8Array(str) { 87 | var char; 88 | var i = 0; 89 | var p = 0; 90 | var utf8 = []; 91 | var len = str.length; 92 | 93 | for (; i < len; i++) { 94 | char = str.charCodeAt(i); 95 | if (char < 128) { 96 | utf8[p++] = char; 97 | } else if (char < 2048) { 98 | utf8[p++] = (char >> 6) | 192; 99 | utf8[p++] = (char & 63) | 128; 100 | } else if ( 101 | ((char & 0xFC00) === 0xD800) && (i + 1) < str.length && 102 | ((str.charCodeAt(i + 1) & 0xFC00) === 0xDC00)) { 103 | char = 0x10000 + ((char & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF); 104 | utf8[p++] = (char >> 18) | 240; 105 | utf8[p++] = ((char >> 12) & 63) | 128; 106 | utf8[p++] = ((char >> 6) & 63) | 128; 107 | utf8[p++] = (char & 63) | 128; 108 | } else { 109 | utf8[p++] = (char >> 12) | 224; 110 | utf8[p++] = ((char >> 6) & 63) | 128; 111 | utf8[p++] = (char & 63) | 128; 112 | } 113 | } 114 | 115 | return utf8; 116 | }; 117 | 118 | /** 119 | * Convert a string into a redis slot hash. 120 | * @param str 121 | * @returns {number} 122 | */ 123 | var generate = module.exports = function generate(str) { 124 | var char; 125 | var i = 0; 126 | var start = -1; 127 | var result = 0; 128 | var resultHash = 0; 129 | var utf8 = typeof str === 'string' ? toUTF8Array(str) : str; 130 | var len = utf8.length; 131 | 132 | while (i < len) { 133 | char = utf8[i++]; 134 | if (start === -1) { 135 | if (char === 0x7B) { 136 | start = i; 137 | } 138 | } else if (char !== 0x7D) { 139 | resultHash = lookup[(char ^ (resultHash >> 8)) & 0xFF] ^ (resultHash << 8); 140 | } else if (i - 1 !== start) { 141 | return resultHash & 0x3FFF; 142 | } 143 | 144 | result = lookup[(char ^ (result >> 8)) & 0xFF] ^ (result << 8); 145 | } 146 | 147 | return result & 0x3FFF; 148 | }; 149 | 150 | /** 151 | * Convert an array of multiple strings into a redis slot hash. 152 | * Returns -1 if one of the keys is not for the same slot as the others 153 | * @param keys 154 | * @returns {number} 155 | */ 156 | module.exports.generateMulti = function generateMulti(keys) { 157 | var i = 1; 158 | var len = keys.length; 159 | var base = generate(keys[0]); 160 | 161 | while (i < len) { 162 | if (generate(keys[i++]) !== base) return -1; 163 | } 164 | 165 | return base; 166 | }; 167 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cluster-key-slot", 3 | "version": "1.1.2", 4 | "description": "Generates CRC hashes for strings - for use by node redis clients to determine key slots.", 5 | "main": "lib/index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "benchmark": "node ./benchmark", 9 | "posttest": "eslint ./lib && npm run coveralls", 10 | "coveralls": "cat ./coverage/lcov.info | coveralls", 11 | "test": "node ./node_modules/istanbul/lib/cli.js cover --preserve-comments ./node_modules/mocha/bin/_mocha -- -R spec", 12 | "coverage:check": "node ./node_modules/istanbul/lib/cli.js check-coverage --branch 100 --statement 100" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/Salakar/cluster-key-slot.git" 17 | }, 18 | "keywords": [ 19 | "redis", 20 | "hash", 21 | "crc", 22 | "slot", 23 | "calc", 24 | "javascript", 25 | "node", 26 | "node_redis", 27 | "ioredis" 28 | ], 29 | "engines": { 30 | "node": ">=0.10.0" 31 | }, 32 | "devDependencies": { 33 | "benchmark": "^2.1.0", 34 | "codeclimate-test-reporter": "^0.3.1", 35 | "coveralls": "^2.11.9", 36 | "eslint": "^3.5.0", 37 | "eslint-config-airbnb-base": "^7.1.0", 38 | "eslint-plugin-import": "^1.8.0", 39 | "istanbul": "^0.4.0", 40 | "mocha": "^3.0.2" 41 | }, 42 | "author": { 43 | "name": "Mike Diarmid", 44 | "email": "mike.diarmid@gmail.com", 45 | "url": "http://github.com/Salakar/" 46 | }, 47 | "license": "Apache-2.0", 48 | "bugs": { 49 | "url": "https://github.com/Salakar/cluster-key-slot/issues" 50 | }, 51 | "homepage": "https://github.com/Salakar/cluster-key-slot#readme", 52 | "directories": { 53 | "test": "test", 54 | "lib": "lib" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/hash.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint func-names: 0 */ 3 | 4 | var assert = require('assert'); 5 | var generate = require('../lib'); 6 | var generateMulti = require('../lib').generateMulti; 7 | 8 | var tests = [ 9 | ['123465', 1492], 10 | ['foobar', 12325], 11 | ['abcdefghijklmnopqrstuvwxyz', 9132], 12 | ['gsdfhan$%^&*(sdgsdnhshcs', 15532], 13 | ['abc{foobar}', 12325], 14 | ['{foobar}', 12325], 15 | ['h8a9sd{foobar}}{asd}}', 12325], 16 | ['{foobar', 16235], 17 | ['foobar{}', 4435], 18 | ['{{foobar}', 16235], 19 | ['éêe', 13690], 20 | ['àâa', 3872], 21 | ['漢字', 14191], 22 | ['汉字', 16196], 23 | ['호텔', 4350], 24 | ['\uD83D\uDC80', 9284], 25 | ['\uD800\uDC00', 11620], // surrogate pair 26 | ['{}foobar', 14573], 27 | [Buffer.from([0x7b, 0x7d, 0x2a, 0x2]), 3932], 28 | [Buffer.from([0x7b, 0x2a, 0x7d, 0x2]), 1320], 29 | [Buffer.from('汉字'), 16196] 30 | ]; 31 | 32 | var testsMulti = [ 33 | 'abcdefghijklmnopqrstuvwxyz', 34 | 'abcdefghijklmnopqrstuvwxyz', 35 | 'abcdefghijklmnopqrstuvwxyz', 36 | 'abcdefghijklmnopqrstuvwxyz', 37 | 'abcdefghijklmnopqrstuvwxyz', 38 | 'abcdefghijklmnopqrstuvwxyz', 39 | 'abcdefghijklmnopqrstuvwxyz', 40 | 'abcdefghijklmnopqrstuvwxyz' 41 | ]; 42 | 43 | var testsMultiResult = 9132; 44 | 45 | function assertHash(key, expectedSlot) { 46 | assert.strictEqual(generate(key), expectedSlot, key + ' - generated invalid hash: ' + generate(key)); 47 | } 48 | 49 | describe('single hash: generate()', function () { 50 | it('generate a correct hash from string', function () { 51 | tests.forEach(([key, expectedSlot]) => assertHash(key, expectedSlot)) 52 | }); 53 | }); 54 | 55 | describe('multiple hashes: generateMulti()', function () { 56 | it('generate a correct hash from multiple strings', function () { 57 | assert.strictEqual(generateMulti(testsMulti), testsMultiResult); 58 | }); 59 | 60 | it('returns -1 if any of the keys generates a different hash slot than the rest', function () { 61 | assert.strictEqual(generateMulti(Object.keys(tests)), -1); 62 | }); 63 | }); 64 | --------------------------------------------------------------------------------