├── .clang-format ├── .github ├── .codespellrc ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── crash_report.md │ ├── feature_request.md │ └── other_stuff.md ├── wordlist.txt └── workflows │ ├── ci.yml │ ├── cmake.yml │ ├── docker-image.yml │ └── spell-check.yml ├── .gitignore ├── CMDDOC-CN.md ├── CMDDOC.md ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README-CN.md ├── README.md ├── dep ├── dict.c ├── dict.h ├── list.c ├── list.h ├── siphash.c ├── skiplist.c ├── skiplist.h ├── tairhash_skiplist.c ├── tairhash_skiplist.h ├── util.c └── util.h ├── imgs ├── tairhash_index.png ├── tairhash_index2.png ├── tairhash_logo.jpg ├── tairhash_memusage.png ├── tairhash_rps.png └── tairhash_slab_mode_index.jpg ├── src ├── CMakeLists.txt ├── redismodule.h ├── scan_algorithm.c ├── scan_algorithm.h ├── slab.c ├── slab.h ├── slab_algorithm.c ├── slab_algorithm.h ├── slabapi.c ├── slabapi.h ├── sort_algorithm.c ├── sort_algorithm.h ├── tairhash.c └── tairhash.h └── tests └── tairhash.tcl /.clang-format: -------------------------------------------------------------------------------- 1 | # We'll use defaults from the Google style, but with 4 columns indentation. 2 | BasedOnStyle: Google 3 | Language: Cpp 4 | IndentWidth: 4 5 | DerivePointerAlignment: false 6 | PointerAlignment: Right 7 | AccessModifierOffset: -4 8 | BreakBeforeBinaryOperators: All 9 | ColumnLimit: 0 10 | -------------------------------------------------------------------------------- /.github/.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | quiet-level = 2 3 | count = 4 | skip = ./dep,./imgs 5 | ignore-words = ./.github/wordlist.txt 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Help us improve TairHash by reporting a bug 4 | title: '[BUG]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | A short description of the bug. 13 | 14 | **To reproduce** 15 | 16 | Steps to reproduce the behavior and/or a minimal code sample. 17 | 18 | **Expected behavior** 19 | 20 | A description of what you expected to happen. 21 | 22 | **Additional information** 23 | 24 | Any additional information that is relevant to the problem. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/crash_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Crash report 3 | about: Submit a crash report 4 | title: '[CRASH]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Crash report** 11 | 12 | Paste the complete crash log between the quotes below. Please include a few lines from the log preceding the crash report to provide some context. 13 | 14 | ``` 15 | ``` 16 | 17 | **Additional information** 18 | 19 | 1. OS distribution and version 20 | 2. Steps to reproduce (if any) 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature for TairHash 4 | title: '[NEW]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **The problem/use-case that the feature addresses** 11 | 12 | A description of the problem that the feature will solve, or the use-case with which the feature will be used. 13 | 14 | **Description of the feature** 15 | 16 | A description of what you want to happen. 17 | 18 | **Alternatives you've considered** 19 | 20 | Any alternative solutions or features you've considered, including references to existing open and closed feature requests in this repository. 21 | 22 | **Additional information** 23 | 24 | Any additional information that is relevant to the feature request. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other_stuff.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: Can't find the right issue type? Use this one! 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/wordlist.txt: -------------------------------------------------------------------------------- 1 | ake 2 | bale 3 | fle 4 | fo 5 | gameboy 6 | mutli 7 | nd 8 | nees 9 | oll 10 | optin 11 | ot 12 | smove 13 | te 14 | tre 15 | cancelability 16 | ist -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | test-ubuntu-with-redis-5: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: clone and make redis 12 | run: | 13 | sudo apt-get install git 14 | git clone https://github.com/redis/redis 15 | cd redis 16 | git checkout 5.0 17 | make REDIS_CFLAGS='-Werror' BUILD_TLS=yes 18 | - name: make tairhash 19 | run: | 20 | mkdir build 21 | cd build 22 | cmake ../ 23 | make 24 | - name: test 25 | run: | 26 | sudo apt-get install tcl8.6 tclx 27 | work_path=$(pwd) 28 | module_path=$work_path/lib 29 | sed -e "s#your_path#$module_path#g" tests/tairhash.tcl > redis/tests/unit/type/tairhash.tcl 30 | sed -i 's#unit/type/string#unit/type/tairhash#g' redis/tests/test_helper.tcl 31 | cd redis 32 | ./runtest --stack-logging --single unit/type/tairhash 33 | 34 | test-ubuntu-with-redis-6: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v2 38 | - name: clone and make redis 39 | run: | 40 | sudo apt-get install git 41 | git clone https://github.com/redis/redis 42 | cd redis 43 | git checkout 6.2 44 | make REDIS_CFLAGS='-Werror' BUILD_TLS=yes 45 | - name: make tairhash 46 | run: | 47 | mkdir build 48 | cd build 49 | cmake ../ 50 | make 51 | - name: test 52 | run: | 53 | sudo apt-get install tcl8.6 tclx 54 | work_path=$(pwd) 55 | module_path=$work_path/lib 56 | sed -e "s#your_path#$module_path#g" tests/tairhash.tcl > redis/tests/unit/type/tairhash.tcl 57 | sed -i 's#unit/type/string#unit/type/tairhash#g' redis/tests/test_helper.tcl 58 | cd redis 59 | ./runtest --stack-logging --single unit/type/tairhash 60 | 61 | test-ubuntu-with-redis-7: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v2 65 | - name: clone and make redis 66 | run: | 67 | sudo apt-get install git 68 | git clone https://github.com/redis/redis 69 | cd redis 70 | git checkout 7.0 71 | make REDIS_CFLAGS='-Werror' BUILD_TLS=yes 72 | - name: Install LCOV 73 | run: | 74 | sudo apt-get --assume-yes install lcov > /dev/null 75 | - name: make tairhash 76 | run: | 77 | mkdir build 78 | cd build 79 | cmake ../ -DGCOV_MODE=TRUE 80 | make 81 | - name: test 82 | run: | 83 | sudo apt-get install tcl8.6 tclx 84 | work_path=$(pwd) 85 | module_path=$work_path/lib 86 | sed -e "s#your_path#$module_path#g" tests/tairhash.tcl > redis/tests/unit/type/tairhash.tcl 87 | sed -i 's#unit/type/string#unit/type/tairhash#g' redis/tests/test_helper.tcl 88 | cd redis 89 | ./runtest --stack-logging --single unit/type/tairhash 90 | - name: lcov collection 91 | run: | 92 | cd build 93 | lcov -c -d ./ -o cover.info 94 | - uses: codecov/codecov-action@v1 95 | with: 96 | file: build/cover.info 97 | token: ${{ secrets.CODECOV_TOKEN }} 98 | verbose: true 99 | 100 | test-ubuntu-with-redis-7-sort-mode: 101 | runs-on: ubuntu-latest 102 | steps: 103 | - uses: actions/checkout@v2 104 | - name: clone and make redis 105 | run: | 106 | sudo apt-get install git 107 | git clone https://github.com/redis/redis 108 | cd redis 109 | git checkout 7.0 110 | make REDIS_CFLAGS='-Werror' BUILD_TLS=yes 111 | - name: Install LCOV 112 | run: | 113 | sudo apt-get --assume-yes install lcov > /dev/null 114 | - name: make tairhash 115 | run: | 116 | mkdir build 117 | cd build 118 | cmake ../ -DSORT_MODE=yes 119 | make 120 | - name: test 121 | run: | 122 | sudo apt-get install tcl8.6 tclx 123 | work_path=$(pwd) 124 | module_path=$work_path/lib 125 | sed -e "s#your_path#$module_path#g" tests/tairhash.tcl > redis/tests/unit/type/tairhash.tcl 126 | sed -i 's#unit/type/string#unit/type/tairhash#g' redis/tests/test_helper.tcl 127 | cd redis 128 | ./runtest --stack-logging --single unit/type/tairhash 129 | - name: lcov collection 130 | run: | 131 | cd build 132 | lcov -c -d ./ -o cover.info 133 | - uses: codecov/codecov-action@v1 134 | with: 135 | file: build/cover.info 136 | token: ${{ secrets.CODECOV_TOKEN }} 137 | verbose: true 138 | 139 | test-ubuntu-with-redis-7-slab-mode: 140 | runs-on: ubuntu-latest 141 | steps: 142 | - uses: actions/checkout@v2 143 | - name: clone and make redis 144 | run: | 145 | sudo apt-get install git 146 | git clone https://github.com/redis/redis 147 | cd redis 148 | git checkout 7.0 149 | make REDIS_CFLAGS='-Werror' BUILD_TLS=yes 150 | - name: Install LCOV 151 | run: | 152 | sudo apt-get --assume-yes install lcov > /dev/null 153 | - name: make tairhash 154 | run: | 155 | mkdir build 156 | cd build 157 | cmake ../ -DSLAB_MODE=yes 158 | make 159 | - name: test 160 | run: | 161 | sudo apt-get install tcl8.6 tclx 162 | work_path=$(pwd) 163 | module_path=$work_path/lib 164 | sed -e "s#your_path#$module_path#g" tests/tairhash.tcl > redis/tests/unit/type/tairhash.tcl 165 | sed -i 's#unit/type/string#unit/type/tairhash#g' redis/tests/test_helper.tcl 166 | cd redis 167 | ./runtest --stack-logging --single unit/type/tairhash 168 | - name: lcov collection 169 | run: | 170 | cd build 171 | lcov -c -d ./ -o cover.info 172 | - uses: codecov/codecov-action@v1 173 | with: 174 | file: build/cover.info 175 | token: ${{ secrets.CODECOV_TOKEN }} 176 | verbose: true 177 | 178 | test-sanitizer-address-scan-mode: 179 | runs-on: ubuntu-latest 180 | steps: 181 | - uses: actions/checkout@v2 182 | - name: clone and make redis 183 | run: | 184 | sudo apt-get install git 185 | git clone https://github.com/redis/redis 186 | cd redis 187 | git checkout 7.0 188 | make SANITIZER=address REDIS_CFLAGS='-Werror' BUILD_TLS=yes MALLOC=libc 189 | - name: Install LCOV 190 | run: | 191 | sudo apt-get --assume-yes install lcov > /dev/null 192 | - name: make tairhash 193 | run: | 194 | mkdir build 195 | cd build 196 | cmake ../ -DSANITIZER_MODE=address 197 | make 198 | - name: test 199 | run: | 200 | sudo apt-get install tcl8.6 tclx 201 | work_path=$(pwd) 202 | module_path=$work_path/lib 203 | sed -e "s#your_path#$module_path#g" tests/tairhash.tcl > redis/tests/unit/type/tairhash.tcl 204 | sed -i 's#unit/type/string#unit/type/tairhash#g' redis/tests/test_helper.tcl 205 | cd redis 206 | ./runtest --stack-logging --single unit/type/tairhash 207 | - name: lcov collection 208 | run: | 209 | cd build 210 | lcov -c -d ./ -o cover.info 211 | - uses: codecov/codecov-action@v1 212 | with: 213 | file: build/cover.info 214 | token: ${{ secrets.CODECOV_TOKEN }} 215 | verbose: true 216 | 217 | test-sanitizer-address-sort-mode: 218 | runs-on: ubuntu-latest 219 | steps: 220 | - uses: actions/checkout@v2 221 | - name: clone and make redis 222 | run: | 223 | sudo apt-get install git 224 | git clone https://github.com/redis/redis 225 | cd redis 226 | git checkout 7.0 227 | make SANITIZER=address REDIS_CFLAGS='-Werror' BUILD_TLS=yes MALLOC=libc 228 | - name: Install LCOV 229 | run: | 230 | sudo apt-get --assume-yes install lcov > /dev/null 231 | - name: make tairhash 232 | run: | 233 | mkdir build 234 | cd build 235 | cmake ../ -DSANITIZER_MODE=address -DSORT_MODE=yes 236 | make 237 | - name: test 238 | run: | 239 | sudo apt-get install tcl8.6 tclx 240 | work_path=$(pwd) 241 | module_path=$work_path/lib 242 | sed -e "s#your_path#$module_path#g" tests/tairhash.tcl > redis/tests/unit/type/tairhash.tcl 243 | sed -i 's#unit/type/string#unit/type/tairhash#g' redis/tests/test_helper.tcl 244 | cd redis 245 | ./runtest --stack-logging --single unit/type/tairhash 246 | - name: lcov collection 247 | run: | 248 | cd build 249 | lcov -c -d ./ -o cover.info 250 | - uses: codecov/codecov-action@v1 251 | with: 252 | file: build/cover.info 253 | token: ${{ secrets.CODECOV_TOKEN }} 254 | verbose: true -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - name: Configure CMake 24 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 25 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 26 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 27 | 28 | - name: Build 29 | # Build your program with the given configuration 30 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Build the Docker image 18 | run: docker build . --file Dockerfile --tag tairmodule/tairhash:$(date +%s) 19 | -------------------------------------------------------------------------------- /.github/workflows/spell-check.yml: -------------------------------------------------------------------------------- 1 | # A CI action that using codespell to check spell. 2 | # .github/.codespellrc is a config file. 3 | # .github/wordlist.txt is a list of words that will ignore word checks. 4 | # More details please check the following link: 5 | # https://github.com/codespell-project/codespell 6 | name: Spellcheck 7 | 8 | on: 9 | push: 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | name: Spellcheck 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | 21 | - name: Install prerequisites 22 | run: sudo pip install codespell==2.0.0 23 | 24 | - name: Spell check 25 | run: codespell --config=./.github/.codespellrc 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.xo 3 | *.o 4 | .vscode/ 5 | lib 6 | build 7 | -------------------------------------------------------------------------------- /CMDDOC-CN.md: -------------------------------------------------------------------------------- 1 | 2 | ## 命令介绍 3 | 4 | #### EXHSET 5 | 6 | 7 | 语法及复杂度: 8 | 9 | 10 | > EXHSET key field value [EX time] [EXAT time] [PX time] [PXAT time] [NX/XX] [VER/ABS/GT version] [KEEPTTL] 11 | > 时间复杂度:O(1) 12 | 13 | 14 | 15 | 命令描述: 16 | 17 | 18 | > 向key指定的TairHash中插入一个field,如果TairHash不存在则自动创建一个,如果field已经存在则覆盖其值。在插入的时候,可以使用EX/EXAT/PX/PXAT给field设置过期时间,当field过期后会被主动(active expire)或被动(passivity expire)删除掉。如果指定了NX选项,则只有在field不存在的时候才会插入成功,同理,如果指定了XX选项,则只有在field存在的时候才能插入成功。如果指定了VER参数,则VER所携带的版本号必须和field当前版本号一致才可以插入成功,如果field不存在或者field当前版本为0则不进行检查,总是可以插入成功。ABS参数用于强制给field设置版本号,而不管field当前的版本号,总是可以插入成功,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 19 | 20 | 21 | 22 | 参数: 23 | 24 | 25 | > key: 用于查找该TairHash的键 26 | > field: TairHash中的一个元素 27 | > value: TairHash中的一个元素对应的值 28 | > EX: 指定field的相对过期时间,单位为秒,0表示立刻过期 29 | > EXAT: 指定field的绝对过期时间,单位为秒,0表示立刻过期 30 | > PX: 指定field的相对过期时间,单位为毫秒,0表示立刻过期 31 | > PXAT: 指定field的绝对过期时间,单位为毫秒 ,0表示立刻过期 32 | > NX/XX: NX表示当要插入的field不存在的时候才允许插入,XX表示只有当field存在的时候才允许插入 33 | > VER/ABS/GT: VER表示只有指定的版本和field当前的版本一致时才允许设置,如果VER指定的版本为0则表示不进行版本检查,ABS表示无论field当前的版本是多少都强制设置并修改版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 34 | > KEEPTTL: 当未指定EX/EXAT/PX/PXAT时保留field的过期时间 35 | 36 | 返回值: 37 | 38 | 39 | > 成功:新创建field并成功为它设置值时,命令返回1,如果field已经存在并且成功覆盖旧值,那么命令返回0 ;如果指定了XX且field不存在则返回-1,如果指定了NX且field已经存在返回-1;如果指定了VER且版本和当前版本不匹配则返回异常信息"ERR update version is stale" 40 | > 失败:返回相应异常信息 41 | 42 | 43 | 44 | #### EXHGET 45 | 46 | 47 | 语法及复杂度: 48 | 49 | 50 | > EXHGET key field 51 | > 时间复杂度:O(1) 52 | 53 | 54 | 55 | 命令描述: 56 | 57 | 58 | > 获取key指定的TairHash一个field的值,如果TairHash不存在或者field不存在,则返回nil 59 | 60 | 61 | 62 | 参数: 63 | 64 | 65 | > key: 用于查找该TairHash的键 66 | > field: 67 | 68 | 69 | 70 | 返回值: 71 | 72 | 73 | > 成功:当field存在时返回其对应的值,当TairHash不存在或者field不存在时返回nil 74 | > 失败:返回相应异常信息 75 | 76 | 77 | 78 | #### EXHMSET 79 | 80 | 81 | 语法及复杂度: 82 | 83 | 84 | > EXHMSET key field value [field value...] 85 | > 时间复杂度:O(n) 86 | 87 | 88 | 89 | 命令描述: 90 | 91 | 92 | > 同时向key指定的TairHash中插入多个field,如果TairHash不存在则自动创建一个,如果field已经存在则覆盖其值 93 | 94 | 95 | 96 | 参数: 97 | 98 | 99 | > key: 用于查找该TairHash的键 100 | > field: TairHash中的一个元素 101 | > value: TairHash中的一个元素对应的值 102 | 103 | 104 | 105 | 返回值: 106 | 107 | 108 | > 成功:返回OK 109 | > 失败:返回相应异常信息 110 | 111 | 112 | 113 | #### EXHPEXPIREAT 114 | 115 | 116 | 语法及复杂度: 117 | 118 | 119 | > EXHPEXPIREAT key field milliseconds-timestamp [VER/ABS/GT version]  120 | > 时间复杂度:O(1) 121 | 122 | 123 | 124 | 命令描述: 125 | 126 | 127 | > 在key指定的TairHash中为一个field设置绝对过期时间,单位为毫秒。当过期时间到时,该field会被主动删除。如果field不存在则直接返回0。如果指定了VER参数,则VER所携带的版本号必须和field当前版本号一致才可以设置成功,或者如果VER参数所携带的版本号为0,则不进行版本校验。ABS参数用于强制给field设置版本号,而不管field当前的版本号,总是可以插入成功,同时将field当前版本号设置为ABS指定的版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 128 | 129 | 130 | 131 | 参数: 132 | 133 | 134 | > key: 用于查找该TairHash的键 135 | > field: TairHash中的一个元素 136 | > milliseconds-timestamp: 以毫秒为单位的时间戳,0表示立刻过期 137 | > VER/ABS/GT: VER表示只有指定的版本和field当前的版本一致时才允许设置,如果VER指定的版本为0则表示不进行版本检查,ABS表示无论field当前的版本是多少都强制设置并修改版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 138 | 139 | 140 | 返回值: 141 | 142 | 143 | > 成功:当field存在时返回1,当field不存在时返回0 144 | > 失败:当版本校验失败时返回update version is stale错误,其他错误返回相应异常信息 145 | 146 | 147 | 148 | #### EXHPEXPIRE 149 | 150 | 151 | 语法及复杂度: 152 | 153 | 154 | > EXHPEXPIRE key field milliseconds [VER/ABS/GT version] 155 | > 时间复杂度:O(1) 156 | 157 | 158 | 159 | 命令描述: 160 | 161 | 162 | > 在key指定的TairHash中为一个field设置相对过期时间,单位为毫秒。当过期时间到时,该field会被主动删除。如果field不存在则直接返回0。如果指定了VER参数,则VER所携带的版本号必须和field当前版本号一致才可以设置成功,或者如果VER参数所携带的版本号为0,则不进行版本校验。ABS参数用于强制给field设置版本号,而不管field当前的版本号,总是可以插入成功,同时将field当前版本号设置为ABS指定的版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 163 | 164 | 参数: 165 | 166 | 167 | > key: 用于查找该TairHash的键 168 | > field: TairHash中的一个元素 169 | > milliseconds: 以毫秒为单位的过期时间,0表示立刻过期 170 | > VER/ABS/GT: VER表示只有指定的版本和field当前的版本一致时才允许设置,如果VER指定的版本为0则表示不进行版本检查,ABS表示无论field当前的版本是多少都强制设置并修改版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 171 | 172 | 173 | 返回值: 174 | 175 | 176 | > 成功:当field存在时返回1,当field不存在时返回0 177 | > 失败:当版本校验失败时返回update version is stale错误,其他错误返回相应异常信息 178 | 179 | 180 | 181 | #### EXHEXPIREAT 182 | 183 | 184 | 语法及复杂度: 185 | 186 | 187 | > EXHEXPIREAT key field timestamp [VER/ABS/GT version] 188 | > 时间复杂度:O(1) 189 | 190 | 191 | 192 | 命令描述: 193 | 194 | 195 | > 在key指定的TairHash中为一个field设置绝对过期时间,单位为秒,当过期时间到时,该field会被主动删除。如果field不存在则直接返回0。如果指定了VER参数,则VER所携带的版本号必须和field当前版本号一致才可以设置成功,或者如果VER参数所携带的版本号为0,则不进行版本校验。ABS参数用于强制给field设置版本号,而不管field当前的版本号,总是可以插入成功,同时将field当前版本号设置为ABS指定的版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 196 | 197 | 参数: 198 | 199 | 200 | > key: 用于查找该TairHash的键 201 | > field: TairHash中的一个元素 202 | > timestamp: 以秒为单位的时间戳,0表示立刻过期 203 | > VER/ABS/GT: VER表示只有指定的版本和field当前的版本一致时才允许设置,如果VER指定的版本为0则表示不进行版本检查,ABS表示无论field当前的版本是多少都强制设置并修改版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 204 | 205 | 206 | 207 | 返回值: 208 | 209 | 210 | > 成功:当field存在时返回1,当field不存在时返回0 211 | > 失败:当版本校验失败时返回update version is stale错误,其他错误返回相应异常信息 212 | 213 | 214 | 215 | #### EXHEXPIRE 216 | 217 | 218 | 语法及复杂度: 219 | 220 | 221 | > EXHEXPIRE key field seconds [VER/ABS/GT version] 222 | > 时间复杂度:O(1) 223 | 224 | 225 | 226 | 命令描述: 227 | 228 | 229 | > 在key指定的TairHash中为一个field设置相对过期时间,单位为秒,当过期时间到时,该field会被主动删除。如果field不存在则直接返回0。如果指定了VER参数,则VER所携带的版本号必须和field当前版本号一致才可以设置成功,或者如果VER参数所携带的版本号为0,则不进行版本校验。ABS参数用于强制给field设置版本号,而不管field当前的版本号,总是可以插入成功,同时将field当前版本号设置为ABS指定的版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 230 | 231 | 232 | 233 | 参数: 234 | 235 | 236 | > key: 用于查找该TairHash的键 237 | > field: TairHash中的一个元素 238 | > seconds: 以秒为单位的过期时间,0表示立刻过期 239 | > VER/ABS/GT: VER表示只有指定的版本和field当前的版本一致时才允许设置,如果VER指定的版本为0则表示不进行版本检查,ABS表示无论field当前的版本是多少都强制设置并修改版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 240 | 241 | 242 | 243 | 返回值: 244 | 245 | 246 | > 成功:当field存在时返回1,当field不存在时返回0 247 | > 失败:当版本校验失败时返回update version is stale错误,其他错误返回相应异常信息 248 | 249 | #### EXHPERSIST 250 | 251 | 252 | 语法及复杂度: 253 | 254 | 255 | > EXHEXPIRE key field 256 | > 时间复杂度:O(1) 257 | 258 | 259 | 260 | 命令描述: 261 | 262 | 263 | > 将一个field设置为永不过期 264 | 265 | 266 | 267 | 参数: 268 | 269 | 270 | > key: 用于查找该TairHash的键 271 | > field: TairHash中的一个元素 272 | 273 | 274 | 275 | 返回值: 276 | 277 | 278 | > 1:成功移除field上的过期设置 279 | > 0:key或field不存在,或field存在但当前没有过期设置 280 | 281 | 282 | 283 | #### EXHPTTL 284 | 285 | 286 | 语法及复杂度: 287 | 288 | 289 | > EXHPTTL key field 290 | > 时间复杂度:O(1) 291 | 292 | 293 | 294 | 命令描述: 295 | 296 | 297 | > 查看key指定的TairHash中一个field的剩余过期时间,单位为毫秒 298 | 299 | 300 | 301 | 参数: 302 | 303 | 304 | > key: 用于查找该TairHash的键 305 | > field: TairHash中的一个元素 306 | 307 | 308 | 309 | 返回值: 310 | 311 | 312 | > 成功:当TairHash或者field不存在时返回-2,当field存在但是没有设置过期时间时返回-1,当field存在且设置过期时间时时返回对应过期时间,单位为毫秒 313 | > 失败:返回相应异常信息 314 | 315 | 316 | 317 | #### EXHTTL 318 | 319 | 320 | 语法及复杂度: 321 | 322 | 323 | > EXHTTL key field 324 | > 时间复杂度:O(1) 325 | 326 | 327 | 328 | 命令描述: 329 | 330 | 331 | > 查看key指定的TairHash中一个field的剩余过期时间,单位为秒 332 | 333 | 334 | 335 | 参数: 336 | 337 | 338 | > key: 用于查找该TairHash的键 339 | > field: TairHash中的一个元素 340 | 341 | 342 | 343 | 返回值: 344 | 345 | 346 | > 成功:当TairHash或者field不存在时返回-2,当field存在但是没有设置过期时间时返回-1,当field存在且设置过期时间时时返回对应过期时间,单位为秒 347 | > 失败:返回相应异常信息 348 | 349 | 350 | 351 | #### EXHVER 352 | 353 | 354 | 语法及复杂度: 355 | 356 | 357 | > EXHVER key field 358 | > 时间复杂度:O(1) 359 | 360 | 361 | 362 | 命令描述: 363 | 364 | 365 | > 查看key指定的TairHash中一个field的当前版本号 366 | 367 | 368 | 369 | 参数: 370 | 371 | 372 | > key: 用于查找该TairHash的键 373 | > field: TairHash中的一个元素 374 | 375 | 376 | 377 | 返回值: 378 | 379 | 380 | > 成功:当TairHash不存在时返回-1,当field不存在时返回-2,否则返回field版本号 381 | > 失败:返回相应异常信息 382 | 383 | 384 | 385 | #### EXHSETVER 386 | 387 | 388 | 语法及复杂度: 389 | 390 | 391 | > EXHSETVER key field version 392 | > 时间复杂度:O(1) 393 | 394 | 395 | 396 | 命令描述: 397 | 398 | 399 | > 设置key指定的TairHash中一个field的版本号 400 | 401 | 402 | 403 | 参数: 404 | 405 | 406 | > key: 用于查找该TairHash的键 407 | > field: TairHash中的一个元素 408 | 409 | 410 | 411 | 返回值: 412 | 413 | 414 | > 成功:当TairHash或者field不存在则返回0,否则返回1 415 | > 失败:返回相应异常信息 416 | 417 | 418 | 419 | #### EXHINCRBY 420 | 421 | 422 | 语法及复杂度: 423 | 424 | 425 | > EXHINCRBY key field value [EX time] [EXAT time] [PX time] [PXAT time] [VER/ABS/GT version] [MIN minval] [MAX maxval] [KEEPTTL] 426 | > 时间复杂度:O(1) 427 | 428 | 429 | 430 | 命令描述: 431 | 432 | 433 | > 将key指定的TairHash中一个field的值加上整数value。如果TairHash不存在则自动新创建一个,如果指定的field不存在,则在加之前先将field的值初始化为0。同时还可以使用EX/EXAT/PX/PXAT为field设置过期时间。 434 | > 如果指定了VER参数,则VER所携带的版本号必须和field当前版本号一致才可以设置成功,或者如果VER参数所携带的版本号为0,则不进行版本校验。ABS参数用于强制给field设置版本号,而不管field当前的版本号,总是可以设置成功,同时将field当前版本号设置为ABS指定的版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0,MIN/MAX用户给field提供一个边界,只有本次incr操作后field的值还在此边界时incr才会被执行,否则返回overflow的错误 435 | 436 | 437 | 438 | 参数: 439 | 440 | 441 | > key: 用于查找该TairHash的键 442 | > field: TairHash中的一个元素 443 | > value: field对应的值 444 | > EX: 指定field的相对过期时间,单位为秒 ,0表示立刻过期 445 | > EXAT: 指定field的绝对过期时间,单位为秒 ,0表示立刻过期 446 | > PX: 指定field的相对过期时间,单位为毫秒,0表示立刻过期 447 | > PXAT: 指定field的绝对过期时间,单位为毫秒,0表示立刻过期 448 | > VER/ABS/GT: VER表示只有指定的版本和field当前的版本一致时才允许设置,如果VER指定的版本为0则表示不进行版本检查,ABS表示无论field当前的版本是多少都强制设置并修改版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 449 | > MAX/MIN: 设置最大最小边界,本次incr操作后,field的值在此边界时incr才会被执行,否则返回overflow的错误。 450 | > KEEPTTL: 当未指定EX/EXAT/PX/PXAT时保留field的过期时间 451 | 452 | 返回值: 453 | 454 | 455 | > 成功:返回相加之后的值 456 | > 失败:当版本校验失败时返回update version is stale错误,其他错误返回相应异常信息(如原来的field值不是浮点型) 457 | 458 | 459 | 460 | #### EXHINCRBYFLOAT 461 | 462 | 463 | 语法及复杂度: 464 | 465 | 466 | > EXHINCRBYFLOAT key field value [EX time] [EXAT time] [PX time] [PXAT time] [VER/ABS/GT version] [MIN minval] [MAX maxval] [KEEPTTL] 467 | > 时间复杂度:O(1) 468 | 469 | 470 | 471 | 命令描述: 472 | 473 | 474 | > 将key指定的TairHash中一个field的值加上浮点型value。如果TairHash不存在则自动新创建一个,如果指定的field不存在,则在加之前先将field的值初始化为0。同时还可以使用EX/EXAT/PX/PXAT为field设置过期时间。 475 | > 如果指定了VER参数,则VER所携带的版本号必须和field当前版本号一致才可以设置成功,或者如果VER参数所携带的版本号为0,则不进行版本校验。ABS参数用于强制给field设置版本号,而不管field当前的版本号,总是可以设置成功,同时将field当前版本号设置为ABS指定的版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0,MIN/MAX用户给field提供一个边界,只有本次incr操作后field的值还在此边界时incr才会被执行,否则返回overflow的错误 476 | 477 | 478 | 479 | 参数: 480 | 481 | 482 | > key: 用于查找该TairHash的键 483 | > field: TairHash中的一个元素 484 | > value: field对应的值 485 | > EX: 指定field的相对过期时间,单位为秒,0表示立刻过期 486 | > EXAT: 指定field的绝对过期时间,单位为秒,0表示立刻过期 487 | > PX: 指定field的相对过期时间,单位为毫秒,0表示立刻过期 488 | > PXAT: 指定field的绝对过期时间,单位为毫秒,0表示立刻过期 489 | > VER/ABS/GT: VER表示只有指定的版本和field当前的版本一致时才允许设置,如果VER指定的版本为0则表示不进行版本检查,ABS表示无论field当前的版本是多少都强制设置并修改版本号,GT表示只有指定的版本大于当前版本时才允许设置,ABS和GT指定的版本号不能为0 490 | > MAX/MIN: 设置最大最小边界,本次incr操作后,field的值在此边界时incr才会被执行,否则返回overflow的错误。 491 | > KEEPTTL: 当未指定EX/EXAT/PX/PXAT时保留field的过期时间 492 | 493 | 返回值: 494 | 495 | 496 | > 成功:返回相加之后的值 497 | > 失败:当版本校验失败时返回update version is stale错误,其他错误返回相应异常信息(如原来的field值不是浮点型) 498 | 499 | 500 | 501 | #### EXHGETWITHVER 502 | 503 | 504 | 语法及复杂度: 505 | 506 | 507 | > EXHGETWITHVER key field 508 | > 时间复杂度:O(1) 509 | 510 | 511 | 512 | 命令描述: 513 | 514 | 515 | > 同时获取key指定的TairHash一个field的值和版本,如果TairHash不存在或者field不存在,则返回nil 516 | 517 | 518 | 519 | 参数: 520 | 521 | 522 | > key: 用于查找该TairHash的键 523 | > field: TairHash中的一个元素 524 | 525 | 526 | 527 | 返回值: 528 | 529 | 530 | > 成功:如果TairHash不存在或者field不存在,则返回nil,否则返回field对应的值和版本 531 | > 失败:返回相应异常信息 532 | 533 | 534 | 535 | #### EXHMGET 536 | 537 | 538 | 语法及复杂度: 539 | 540 | 541 | > EXHMGET key field [field ...] 542 | > 时间复杂度:O(n) 543 | 544 | 545 | 546 | 命令描述: 547 | 548 | 549 | > 同时获取key指定的TairHash多个field的值,如果TairHash不存在或者field不存在,则返回nil 550 | 551 | 552 | 553 | 参数: 554 | 555 | 556 | > key: 用于查找该TairHash的键 557 | > field: TairHash中的一个元素 558 | 559 | 560 | 561 | 返回值: 562 | 563 | 564 | > 成功:返回一个数组,数组的每一个元素对应一个field, 如果TairHash不存在或者field不存在,则为nil,否则为field对应的值 565 | > 失败:返回相应异常信息 566 | 567 | 568 | 569 | #### EXHMGETWITHVER 570 | 571 | 572 | 语法及复杂度: 573 | 574 | 575 | > EXHMGETWITHVER key field [field ...] 576 | > 时间复杂度:O(n) 577 | 578 | 579 | 580 | 命令描述: 581 | 582 | 583 | > 同时获取key指定的TairHash多个field的值和版本,如果TairHash不存在或者field不存在,则返回nil 584 | 585 | 586 | 587 | 参数: 588 | 589 | 590 | > key: 用于查找该TairHash的键 591 | > field: TairHash中的一个元素 592 | 593 | 594 | 595 | 返回值: 596 | 597 | 598 | > 成功:返回一个数组,数组的每一个元素对应一个field, 如果TairHash不存在或者field不存在,则为nil,否则为field对应的值和版本 599 | > 失败:返回相应异常信息 600 | 601 | 602 | 603 | #### EXHDEL 604 | 605 | 606 | 语法及复杂度: 607 | 608 | 609 | > EXHDEL key field [field...] 610 | > 时间复杂度:O(1) 611 | 612 | 613 | 614 | 命令描述: 615 | 616 | 617 | > 删除key指定的TairHash一个field,如果TairHash不存在或者field不存在则返回0 ,成功删除返回1 618 | 619 | 620 | 621 | 参数: 622 | 623 | 624 | > key: 用于查找该TairHash的键 625 | > field: TairHash中的一个元素 626 | 627 | 628 | 629 | 返回值: 630 | 631 | 632 | > 成功:如果TairHash不存则返回0 ,成功怎返回成功删除的filed的个数 633 | > 失败:返回相应异常信息 634 | 635 | 636 | 637 | #### EXHLEN 638 | 639 | 640 | 语法及复杂度: 641 | 642 | 643 | > EXHLEN key [noexp] 644 | > 时间复杂度:不是noexp选项时是O(1),带noexp选项时是O(N) 645 | 646 | 647 | 648 | 命令描述: 649 | 650 | 651 | > 获取key指定的TairHash中field的个数,该命令默认不会触发对过期field的被动淘汰,也不会将其过滤掉,所以结果中可能包含已经过期但还未被删除的field。如果只想返回当前没有过期的field个数,那么可以最后带一个noexp参数,注意,带有该参数时,exhlen的RT将受到exhash大小的影响(因为要循环遍历),同时exhlen不会触发对field的淘汰,它只是把过期的field过滤了一下而已 652 | 653 | 654 | 655 | 参数: 656 | 657 | 658 | > key: 用于查找该TairHash的键 659 | 660 | 661 | 662 | 返回值: 663 | 664 | 665 | > 成功:如果TairHash不存在或者field不存在则返回0 ,成功删除返回TairHash中field个数 666 | > 失败:返回相应异常信息 667 | 668 | 669 | 670 | #### EXHEXISTS 671 | 672 | 673 | 语法及复杂度: 674 | 675 | 676 | > EXHEXISTS key field 677 | > 时间复杂度:O(1) 678 | 679 | 680 | 681 | 命令描述: 682 | 683 | 684 | > 查询key指定的TairHash中是否存在对应的field 685 | 686 | 687 | 688 | 参数: 689 | 690 | 691 | > key: 用于查找该TairHash的键 692 | > field: TairHash中的一个元素 693 | 694 | 695 | 696 | 返回值: 697 | 698 | 699 | > 成功:如果TairHash不存在或者field不存在则返回0 ,如果field存在则返回1 700 | > 失败:返回相应异常信息 701 | 702 | 703 | 704 | #### EXHSTRLEN 705 | 706 | 707 | 语法及复杂度: 708 | 709 | 710 | > EXHSTRLEN key field 711 | > 时间复杂度:O(1) 712 | 713 | 714 | 715 | 命令描述: 716 | 717 | 718 | > 获取key指定的TairHash一个field的值的长度 719 | 720 | 721 | 722 | 参数: 723 | 724 | 725 | > key: 用于查找该TairHash的键 726 | > field: TairHash中的一个元素 727 | 728 | 729 | 730 | 返回值: 731 | 732 | 733 | > 成功:如果TairHash不存在或者field不存在则返回0 ,否则返回field对应值的长度 734 | > 失败:返回相应异常信息 735 | 736 | 737 | 738 | #### EXHKEYS 739 | 740 | 741 | 语法及复杂度: 742 | 743 | 744 | > EXHKEYS key 745 | > 时间复杂度:O(n) 746 | 747 | 748 | 749 | 命令描述: 750 | 751 | 752 | > 获取key指定的TairHash中所有field的键 753 | 754 | 755 | 756 | 参数: 757 | 758 | 759 | > key: 用于查找该TairHash的键 760 | 761 | 762 | 763 | 返回值: 764 | 765 | 766 | > 成功:返回一个数组,数组的每一位对应TairHash中的每一个field,如果TairHash不存则返回空的数组 767 | > 失败:返回相应异常信息 768 | 769 | 770 | 771 | #### EXHVALS 772 | 773 | 774 | 语法及复杂度: 775 | 776 | 777 | > EXHVALS key 778 | > 时间复杂度:O(n) 779 | 780 | 781 | 782 | 命令描述: 783 | 784 | 785 | > 获取key指定的TairHash中所有field的值 786 | 787 | 788 | 789 | 参数: 790 | 791 | 792 | > key: 用于查找该TairHash的键 793 | 794 | 795 | 796 | 返回值: 797 | 798 | 799 | > 成功:返回一个数组,数组的每一位对应TairHash中的每一个field的值,如果TairHash不存则返回空的数组 800 | > 失败:返回相应异常信息 801 | 802 | 803 | 804 | #### EXHGETALL 805 | 806 | 807 | 语法及复杂度: 808 | 809 | 810 | > EXHGETALL key 811 | > 时间复杂度:O(n) 812 | 813 | 814 | 815 | 命令描述: 816 | 817 | 818 | > 获取key指定的TairHash中所有field的键值对 819 | 820 | 821 | 822 | 参数: 823 | 824 | 825 | > key: 用于查找该TairHash的键 826 | 827 | 828 | 829 | 返回值: 830 | 831 | 832 | > 成功:返回一个数组,数组的每一位对应TairHash中的每一个field的键值对,如果TairHash不存则返回空的数组 833 | > 失败:返回相应异常信息 834 | 835 | 836 | 837 | #### EXHGETALLWITHVER 838 | 839 | 840 | 语法及复杂度: 841 | 842 | 843 | > EXHGETALLWITHVER key 844 | > 时间复杂度:O(n) 845 | 846 | 847 | 848 | 命令描述: 849 | 850 | 851 | > 获取key指定的TairHash中所有field的键值对和版本 852 | 853 | 854 | 855 | 参数: 856 | 857 | 858 | > key: 用于查找该TairHash的键 859 | 860 | 861 | 862 | 返回值: 863 | 864 | 865 | > 成功:返回一个数组,数组的每一位对应TairHash中的每一个field的键值对和版本,如果TairHash不存则返回空的数组 866 | > 失败:返回相应异常信息 867 | 868 | 869 | 870 | #### EXHSCAN 871 | 872 | 873 | 语法及复杂度: 874 | 875 | 876 | > EXHSCAN key cursor [MATCH pattern] [COUNT count] 877 | > 时间复杂度:O(1)、O(N) 878 | 879 | 880 | 881 | 命令描述: 882 | 883 | 884 | > 扫描key指定的TairHash 885 | 886 | 887 | 888 | 参数: 889 | 890 | 891 | > key: 用于查找该TairHash的键 892 | > cursor: 扫描的游标,从0开始,每次扫描后会返回下一次扫描的cursor,直到返回0表示扫描结束 893 | > MATCH: 用于对扫描结果进行过滤的规则 894 | > COUNT: 用于规定单次扫描field的个数,注意,COUNT仅表示每次扫描TairHash的feild的个数,不代表最终一定会返回COUNT个field结果集,结果集的大小还要根据TairHash中当前field个数和是否指定MATCH进行过滤而定。COUNT默认值为10 895 | 896 | 897 | 898 | 返回值: 899 | 900 | 901 | > 成功:返回一个具有两个元素的数组,数组第一个元素是下一次扫描需要使用的cursor,为0表示整个扫描结束。第二个数组元素还是一个数组,数组包含了所有本次被迭代的field/value。如果扫描到一个空的TairHash或者是TairHash不存在,那么这两个数组元素都为空。 902 | > 失败:返回相应异常信息 903 | 904 | 905 | 906 | **使用示例:** 907 | 1、如何使用渐进式扫描整个TairHash: 908 | ``` 909 | 127.0.0.1:6379> exhmset exhashkey field1 val1 field2 val2 field3 val3 field4 val4 field5 val5 field6 val6 field7 val7 field8 val8 field9 val9 910 | OK 911 | 127.0.0.1:6379> exhscan exhashkey 0 COUNT 3 912 | 1) (integer) 4 913 | 2) 1) "field6" 914 | 2) "val6" 915 | 3) "field5" 916 | 4) "val5" 917 | 127.0.0.1:6379> exhscan exhashkey 4 COUNT 3 918 | 1) (integer) 1 919 | 2) 1) "field8" 920 | 2) "val8" 921 | 3) "field2" 922 | 4) "val2" 923 | 127.0.0.1:6379> exhscan exhashkey 1 COUNT 3 924 | 1) (integer) 13 925 | 2) 1) "field9" 926 | 2) "val9" 927 | 3) "field7" 928 | 4) "val7" 929 | 127.0.0.1:6379> exhscan exhashkey 13 COUNT 3 930 | 1) (integer) 11 931 | 2) 1) "field3" 932 | 2) "val3" 933 | 3) "field4" 934 | 4) "val4" 935 | 127.0.0.1:6379> exhscan exhashkey 11 COUNT 3 936 | 1) (integer) 0 937 | 2) 1) "field1" 938 | 2) "val1" 939 | ``` 940 | 941 | 2、如何使用MATCH对结果集进行过滤 942 | 精确匹配: 943 | 944 | 945 | ``` 946 | 127.0.0.1:6379> exhmset exhashkey field1_1 val1_1 field1_2 val1_2 field1_3 val1_3 field1_4 val1_4 field1_5 val1_5 field2_1 val2_1 field2_2 val2_2 field2_3 val2_3 field6_1 val6_1 field6_2 val6_2 field6_3 val6_3 field6_4 val6_4 field8_1 val8_1 field8_2 val8_4 947 | OK 948 | 127.0.0.1:6379> exhscan exhashkey 0 COUNT 3 MATCH field1_1 949 | 1) (integer) 8 950 | 2) (empty array) 951 | 127.0.0.1:6379> exhscan exhashkey 8 COUNT 3 MATCH field1_1 952 | 1) (integer) 12 953 | 2) (empty array) 954 | 127.0.0.1:6379> exhscan exhashkey 12 COUNT 3 MATCH field1_1 955 | 1) (integer) 9 956 | 2) 1) "field1_1" 957 | 2) "val1_1" 958 | 127.0.0.1:6379> exhscan exhashkey 9 COUNT 3 MATCH field1_1 959 | 1) (integer) 5 960 | 2) (empty array) 961 | 127.0.0.1:6379> exhscan exhashkey 5 COUNT 3 MATCH field1_1 962 | 1) (integer) 11 963 | 2) (empty array) 964 | 127.0.0.1:6379> exhscan exhashkey 11 COUNT 3 MATCH field1_1 965 | 1) (integer) 0 966 | 2) (empty array) 967 | ``` 968 | 969 | 970 | 模糊匹配: 971 | 972 | ``` 973 | 127.0.0.1:6379> exhmset exhashkey field1_1 val1_1 field1_2 val1_2 field1_3 val1_3 field1_4 val1_4 field1_5 val1_5 field2_1 val2_1 field2_2 val2_2 field2_3 val2_3 field6_1 val6_1 field6_2 val6_2 field6_3 val6_3 field6_4 val6_4 field8_1 val8_1 field8_2 val8_4 974 | OK 975 | 127.0.0.1:6379> exhscan exhashkey 0 COUNT 3 MATCH field6_* 976 | 1) (integer) 8 977 | 2) 1) "field6_4" 978 | 2) "val6_4" 979 | 3) "field6_1" 980 | 4) "val6_1" 981 | 127.0.0.1:6379> exhscan exhashkey 8 COUNT 3 MATCH field6_* 982 | 1) (integer) 12 983 | 2) 1) "field6_2" 984 | 2) "val6_2" 985 | 127.0.0.1:6379> exhscan exhashkey 12 COUNT 3 MATCH field6_* 986 | 1) (integer) 9 987 | 2) (empty array) 988 | 127.0.0.1:6379> exhscan exhashkey 9 COUNT 3 MATCH field6_* 989 | 1) (integer) 5 990 | 2) (empty array) 991 | 127.0.0.1:6379> exhscan exhashkey 5 COUNT 3 MATCH field6_* 992 | 1) (integer) 11 993 | 2) 1) "field6_3" 994 | 2) "val6_3" 995 | 127.0.0.1:6379> exhscan exhashkey 11 COUNT 3 MATCH field6_* 996 | 1) (integer) 0 997 | 2) (empty array) 998 | ``` 999 | 1000 |
1001 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | 3 | project(tairhash_module) 4 | 5 | set(ROOT_DIR ${CMAKE_SOURCE_DIR}) 6 | 7 | set(CMAKE_C_STANDARD 11) 8 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -W -Wall -g -ggdb -std=c99 -march=native -O3 -Wno-strict-aliasing -Wno-typedef-redefinition -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable") 9 | 10 | if (GCOV_MODE) 11 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage") 12 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") 13 | endif() 14 | 15 | if(SANITIZER_MODE MATCHES "address") 16 | set(CMAKE_BUILD_TYPE "DEBUG") 17 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -fsanitize=address -fno-omit-frame-pointer") 18 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -fsanitize=address -fno-omit-frame-pointer") 19 | elseif(SANITIZER_MODE MATCHES "undefined") 20 | set(CMAKE_BUILD_TYPE "DEBUG") 21 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -fsanitize=undefined -fno-omit-frame-pointer") 22 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -fsanitize=undefined -fno-omit-frame-pointer") 23 | elseif(SANITIZER_MODE MATCHES "thread") 24 | set(CMAKE_BUILD_TYPE "DEBUG") 25 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -fsanitize=thread -fno-omit-frame-pointer") 26 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -fsanitize=thread -fno-omit-frame-pointer") 27 | endif(SANITIZER_MODE MATCHES "address") 28 | 29 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 30 | set(CMAKE_C_VISIBILITY_PRESET hidden) 31 | 32 | SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) 33 | 34 | option(SORT_MODE "Use two-level sort index to to implement active expire" OFF) 35 | 36 | if(SORT_MODE) 37 | add_definitions(-DSORT_MODE) 38 | endif(SORT_MODE) 39 | 40 | option(SLAB_MODE "Use a memory friendly slab-based expiration algorithm to evict expired keys more efficient!" OFF) 41 | 42 | if (SLAB_MODE) 43 | 44 | include(CheckCSourceRuns) 45 | 46 | check_c_source_runs(" 47 | #include 48 | int main() 49 | { 50 | __m256i a, b, c; 51 | const int src[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; 52 | int dst[8]; 53 | a = _mm256_loadu_si256( (__m256i*)src ); 54 | b = _mm256_loadu_si256( (__m256i*)src ); 55 | c = _mm256_add_epi32( a, b ); 56 | _mm256_storeu_si256( (__m256i*)dst, c ); 57 | for( int i = 0; i < 8; i++ ){ 58 | if( ( src[i] + src[i] ) != dst[i] ){ 59 | return -1; 60 | } 61 | } 62 | return 0; 63 | }" HAVE_AVX2_EXTENSIONS) 64 | 65 | message(STATUS "SLAB_API defined...") 66 | 67 | if (HAVE_AVX2_EXTENSIONS) 68 | message(STATUS "SIMD acceleration...") 69 | endif(HAVE_AVX2_EXTENSIONS) 70 | 71 | add_definitions(-DSLAB_MODE) 72 | endif(SLAB_MODE) 73 | 74 | include_directories(${ROOT_DIR}/dep) 75 | include_directories(${ROOT_DIR}/src) 76 | aux_source_directory(${ROOT_DIR}/dep USRC) 77 | add_subdirectory(src) 78 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM redis 2 | 3 | ENV TAIRHASH_URL https://github.com/alibaba/TairHash.git 4 | RUN set -ex; \ 5 | \ 6 | BUILD_DEPS=' \ 7 | ca-certificates \ 8 | cmake \ 9 | gcc \ 10 | git \ 11 | g++ \ 12 | make \ 13 | '; \ 14 | apt-get update; \ 15 | apt-get install -y $BUILD_DEPS --no-install-recommends; \ 16 | rm -rf /var/lib/apt/lists/*; \ 17 | git clone "$TAIRHASH_URL"; \ 18 | cd TairHash; \ 19 | mkdir -p build; \ 20 | cd build; \ 21 | cmake ..; \ 22 | make -j; \ 23 | cd ..; \ 24 | cp lib/tairhash_module.so /usr/local/lib/; \ 25 | \ 26 | apt-get purge -y --auto-remove $BUILD_DEPS 27 | ADD http://download.redis.io/redis-stable/redis.conf /usr/local/etc/redis/redis.conf 28 | RUN sed -i '1i loadmodule /usr/local/lib/tairhash_module.so' /usr/local/etc/redis/redis.conf; \ 29 | chmod 644 /usr/local/etc/redis/redis.conf; \ 30 | sed -i 's/^bind 127.0.0.1/#bind 127.0.0.1/g' /usr/local/etc/redis/redis.conf; \ 31 | sed -i 's/^protected-mode yes/protected-mode no/g' /usr/local/etc/redis/redis.conf 32 | CMD [ "redis-server", "/usr/local/etc/redis/redis.conf" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Alibaba Tair Team 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/badge/license-Apache--2.0-green) 2 | ![](https://img.shields.io/badge/PRs-welcome-green) 3 | [![Build](https://github.com/alibaba/TairHash/actions/workflows/cmake.yml/badge.svg)](https://github.com/alibaba/TairHash/actions/workflows/cmake.yml) 4 | [![CI](https://github.com/alibaba/TairHash/actions/workflows/ci.yml/badge.svg)](https://github.com/alibaba/TairHash/actions/workflows/ci.yml) 5 | [![Docker](https://github.com/alibaba/TairHash/actions/workflows/docker-image.yml/badge.svg)](https://github.com/alibaba/TairHash/actions/workflows/docker-image.yml) 6 | [![codecov](https://codecov.io/gh/chenyang8094/TairHash/branch/develop/graph/badge.svg?token=9A8QP7MTN3)](https://codecov.io/gh/chenyang8094/TairHash) 7 | 8 | 9 |
10 | 11 |
12 | 13 | ## 简介 [英文说明](README.md) 14 |      TairHash是基于redis module开发的一种hash数据结构,和redis原生的hash数据结构相比,TairHash不但和原生hash一样具有丰富的数据接口和高性能,还可以为field设置过期时间和版本,这极大的提高了hash数据结构的灵活性,在很多场景下可以大大的简化业务开发。TairHash提供active expire机制,即使field在过期后没有被访问也可以被主动删除,释放内存。 15 | 16 | 17 | ### 主要的特性如下: 18 | 19 | - 支持redis hash的所有命令语义 20 | - field支持单独设置expire和version 21 | - 针对field支持高效的active expire和passivity expire,其中active expire支持SCAN_MODE、SORT_MODE和SLAB_MODE模式。 22 | - 支持field过期删除事件通知(基于pubsub) 23 | 24 | ## 主动过期 25 | 26 | ### SCAN_MODE(扫描模式): 27 | - 不对TairHash进行全局排序(可以节省内存) 28 | - 每个TairHash内部依然会使用一个排序索引对fields进行排序(加速每个key内部的field查找) 29 | - 内置定时器会周期使用SCAN命令找到包含过期field的TairHash,然后检查TairHash内部的排序索引,进行field的淘汰 30 | - 排序中所有的key和field都是指针引用,无内存拷贝,无内存膨胀问题 31 | 32 | **支持的redis版本**: redis >= 5.0 33 | **优点**:可以运行在低版本的redis中(redis >= 5.0 ) 34 | **缺点**:过期淘汰效率较低(相对SORT模式而言,特别是redis中含有过多的干扰key时) 35 | 36 | **使用方式**:以默认选项执行cmake,并重新编译 37 | ### SORT_MODE(排序模式): 38 | 39 | - 使用两级排序索引,第一级对tairhash主key进行排序,第二级针对每个tairhash内部的field进行排序 40 | - 第一级排序使用第二级排序里最小的ttl进行排序,因此主key是按照ttl全局有序的 41 | - 内置定时器会周期扫描第一级索引,找出一部分已经过期的key,然后分别再检查这些key的二级索引,进行field的淘汰,也就是active expire 42 | - 排序中所有的key和field都是指针引用,无内存拷贝,无内存膨胀问题 43 | 44 | **支持的redis版本**: redis >= 7.0 45 | **优点**:过期淘汰效率比较高 46 | **缺点**:更多的内存消耗 47 | 48 | **使用方式**:cmake的时候加上`-DSORT_MODE=yes`选项,并重新编译 49 | ### SLAB_MODE(slab模式): 50 | 51 | - SLAB模式是一种节省内存,缓存友好,高性能的过期算法 52 | - 和SORT模式一样,也会依赖key的全局排序索引进行过期key的快速查找。和SORT模式不同的是,SLAB不会对key内部的field进行排序索引,相反他们是无序的,这样可以节省索引内存开销‘ 53 | - SLAB过期算法使用SIMD指令(当硬件支持时)来加速对过期field的查找 54 | 55 | **支持的redis版本**: redis >= 7.0 56 | 57 | **优点**:高效淘汰,低内存消耗,快速访问,为过期算法带来新思路 58 | 59 | **缺点**:更多的内存消耗 60 | 61 | **使用方式**:cmake的时候加上`-DSLAB_MODE=yes`选项,并重新编译 62 | 63 | ## 主动过期 64 | - 每一次读写field,会触发对这个field自身的过期淘汰操作 65 | - 每次写一个field时,TairHash也会检查其它field(可能属于其它的key)是否已经过期(每次最多检查3个),因为field是按照TTL排序的,因此这个检查会很高效 (注意: SLAB_MODE暂时不支持这个功能) 66 | 67 | ## 事件通知 68 | 69 | tairhash在field发生过期时(由主动或被动过期触发)会发送一个事件通知,通知以pubsub方式发送,channel的格式为:`tairhash@@__:` , 目前只支持expired事件类型,因此 70 | channel为:`tairhash@@__:expired`,消息内容为过期的field。 71 | 72 | ## 快速开始 73 | 74 | ```go 75 | 127.0.0.1:6379> EXHSET k f v ex 10 76 | (integer) 1 77 | 127.0.0.1:6379> EXHGET k f 78 | "v" 79 | 127.0.0.1:6379> EXISTS k 80 | (integer) 1 81 | 127.0.0.1:6379> debug sleep 10 82 | OK 83 | (10.00s) 84 | 127.0.0.1:6379> EXISTS k 85 | (integer) 0 86 | 127.0.0.1:6379> EXHGET k f 87 | (nil) 88 | 127.0.0.1:6379> EXHSET k f v px 10000 89 | (integer) 1 90 | 127.0.0.1:6379> EXHGET k f 91 | "v" 92 | 127.0.0.1:6379> EXISTS k 93 | (integer) 1 94 | 127.0.0.1:6379> debug sleep 10 95 | OK 96 | (10.00s) 97 | 127.0.0.1:6379> EXISTS k 98 | (integer) 0 99 | 127.0.0.1:6379> EXHGET k f 100 | (nil) 101 | 127.0.0.1:6379> EXHSET k f v VER 1 102 | (integer) 1 103 | 127.0.0.1:6379> EXHSET k f v VER 1 104 | (integer) 0 105 | 127.0.0.1:6379> EXHSET k f v VER 1 106 | (error) ERR update version is stale 107 | 127.0.0.1:6379> EXHSET k f v ABS 1 108 | (integer) 0 109 | 127.0.0.1:6379> EXHSET k f v ABS 2 110 | (integer) 0 111 | 127.0.0.1:6379> EXHVER k f 112 | (integer) 2 113 | ``` 114 | 115 | ## Docker 116 | ``` 117 | docker run -p 6379:6379 tairmodule/tairhash:latest 118 | ``` 119 | ## 编译及使用 120 | 121 | ``` 122 | mkdir build 123 | cd build 124 | cmake ../ && make -j 125 | ``` 126 | 编译成功后会在lib目录下产生tairhash_module.so库文件 127 | 128 | ``` 129 | ./redis-server --loadmodule /path/to/tairhash_module.so 130 | ``` 131 | ## 测试方法 132 | 133 | 1. 修改`tests`目录下tairhash.tcl文件中的路径为`set testmodule [file your_path/tairhash_module.so]` 134 | 2. 将`tests`目录下tairhash.tcl文件路径加入到redis的test_helper.tcl的all_tests中 135 | 3. 在redis根目录下运行./runtest --single tairhash 136 | 137 | ## 客户端 138 | 139 | | language | GitHub | 140 | |----------|---| 141 | | Java |https://github.com/alibaba/alibabacloud-tairjedis-sdk| 142 | | Python |https://github.com/alibaba/tair-py| 143 | | Go |https://github.com/alibaba/tair-go| 144 | | .Net |https://github.com/alibaba/AlibabaCloud.TairSDK| 145 | 146 | ## API 147 | [参考这里](CMDDOC-CN.md) 148 | 149 | ## 我们的modules 150 | 151 | [TairHash](https://github.com/alibaba/TairHash): 和redis hash类似,但是可以为field设置expire和version,支持高效的主动过期和被动过期 152 | [TairZset](https://github.com/alibaba/TairZset): 和redis zset类似,但是支持多(最大255)维排序,同时支持incrby语义,非常适合游戏排行榜场景 153 | [TairString](https://github.com/alibaba/TairString): 和redis string类似,但是支持设置expire和version,并提供CAS/CAD等实用命令,非常适用于分布式锁等场景 154 | 155 | 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/badge/license-Apache--2.0-green) 2 | ![](https://img.shields.io/badge/PRs-welcome-green) 3 | [![Build](https://github.com/alibaba/TairHash/actions/workflows/cmake.yml/badge.svg)](https://github.com/alibaba/TairHash/actions/workflows/cmake.yml) 4 | [![CI](https://github.com/alibaba/TairHash/actions/workflows/ci.yml/badge.svg)](https://github.com/alibaba/TairHash/actions/workflows/ci.yml) 5 | [![Docker](https://github.com/alibaba/TairHash/actions/workflows/docker-image.yml/badge.svg)](https://github.com/alibaba/TairHash/actions/workflows/docker-image.yml) 6 | [![codecov](https://codecov.io/gh/chenyang8094/TairHash/branch/develop/graph/badge.svg?token=9A8QP7MTN3)](https://codecov.io/gh/chenyang8094/TairHash) 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | ## Introduction [中文说明](README-CN.md) 15 |      TairHash is a hash data structure developed based on the redis module. TairHash not only has the same rich data interface and high performance as the native hash, but also can set the expiration and version for the field. TairHash provides an active expiration mechanism, even if the field is not accessed after expiration, it can be actively deleted to release the memory. 16 | 17 | 18 | ### The main features: 19 | 20 | - Supports all redis hash commands 21 | - Supports setting expiration and version for field 22 | - Support efficient active expiration (SCAN mode, SORT mode and SLAB mode) and passivity expiration for field 23 | - Support field expired event notification (based on pubsub) 24 | 25 | ## Active expiration 26 | ### SCAN_MODE(default): 27 | - Do not sort TairHash globally (Smaller memory overhead) 28 | - Each TairHash will still use a sort index to sort the fields internally (For expiration efficiency) 29 | - The built-in timer will periodically use the SCAN command to find the TairHash that contains the expired field, and then check the sort index inside the TairHash to eliminate the field 30 | - All keys and fields in the sorting index are pointer references, no memory copy, no memory expansion problem 31 | 32 | **Supported redis version**: redis >= 5.0 33 | 34 | **Advantages**: can run in the low version of redis (redis >= 5.0) 35 | 36 | **Disadvantages**: low efficiency of expire elimination (compared with SORT mode and SLAB mode) 37 | 38 | **Usage**: cmake with default option, and recompile 39 | 40 | ### SORT_MODE: 41 | - Use a two-level sort index, the first level sorts the main key of tairhash, and the second level sorts the fields inside each tairhash 42 | - The first-level uses the smallest ttl in the second-level for sorting, so the main key is globally ordered 43 | - The built-in timer will periodically scan the first-level index to find out a key that has expired, and then check the secondary index of these keys to eliminate the expired fields. This is the active expire 44 | - All keys and fields in the sorting index are pointer references, no memory copy, no memory expansion problem 45 | 46 | **Supported redis version**: redis >= 7.0 47 | 48 | **Advantages**: higher efficiency of expire elimination 49 | 50 | **Disadvantages**: More memory consumption 51 | 52 | **Usag**e: cmake with `-DSORT_MODE=yes` option, and recompile 53 | 54 | ### SLAB_MODE: 55 | - Slab mode is a low memory usage (compared with SORT mode), cache-friendly, high-performance expiration algorithm 56 | - Like SORT mode, keys are globally sorted to ensure that keys that need to be expired can be found faster. Unlike SORT mode, SLAB does not sort the fields inside the key, which saves memory overhead. 57 | - The SLAB expiration algorithm uses SIMD instructions (when supported by the hardware) to speed up the search for expired fields 58 | 59 | **Supported redis version**: redis >= 7.0 60 | 61 | **Advantages**: Efficient elimination, low memory consumption, and fast access bring new ideas to expiration algorithms 62 | 63 | **Disadvantages**: More memory consumption 64 | 65 | **Usage**: cmake with `-DSLAB_MODE=yes` option, and recompile 66 | 67 | ## Passivity expiration 68 | - Every time you read or write a field, it will also trigger the expiration of the field itself 69 | - Every time you write a field, tairhash also checks whether other fields (may belong to other keys) are expired (currently up to 3 at a time), because fields are sorted by TTL, so this check will be very efficient (Note: SLAB_MODE does not support this feature) 70 | 71 | 72 | ## Event notification 73 | 74 | tairhash will send an event notification when the field expires (triggered by active or passive expiration). The notification is sent in pubsub mode. The format of the channel is: `tairhash@@__:` , currently only supports expired event type, so 75 | The channel is: `tairhash@@__:expired`, and the message content is the expired field. 76 | 77 | ## Quick Start 78 | 79 | ```go 80 | 127.0.0.1:6379> EXHSET k f v ex 10 81 | (integer) 1 82 | 127.0.0.1:6379> EXHGET k f 83 | "v" 84 | 127.0.0.1:6379> EXISTS k 85 | (integer) 1 86 | 127.0.0.1:6379> debug sleep 10 87 | OK 88 | (10.00s) 89 | 127.0.0.1:6379> EXISTS k 90 | (integer) 0 91 | 127.0.0.1:6379> EXHGET k f 92 | (nil) 93 | 127.0.0.1:6379> EXHSET k f v px 10000 94 | (integer) 1 95 | 127.0.0.1:6379> EXHGET k f 96 | "v" 97 | 127.0.0.1:6379> EXISTS k 98 | (integer) 1 99 | 127.0.0.1:6379> debug sleep 10 100 | OK 101 | (10.00s) 102 | 127.0.0.1:6379> EXISTS k 103 | (integer) 0 104 | 127.0.0.1:6379> EXHGET k f 105 | (nil) 106 | 127.0.0.1:6379> EXHSET k f v VER 1 107 | (integer) 1 108 | 127.0.0.1:6379> EXHSET k f v VER 1 109 | (integer) 0 110 | 127.0.0.1:6379> EXHSET k f v VER 1 111 | (error) ERR update version is stale 112 | 127.0.0.1:6379> EXHSET k f v ABS 1 113 | (integer) 0 114 | 127.0.0.1:6379> EXHSET k f v ABS 2 115 | (integer) 0 116 | 127.0.0.1:6379> EXHVER k f 117 | (integer) 2 118 | ``` 119 | 120 | ## Docker 121 | ``` 122 | docker run -p 6379:6379 tairmodule/tairhash:latest 123 | ``` 124 | ## BUILD 125 | 126 | ``` 127 | mkdir build 128 | cd build 129 | cmake ../ && make -j 130 | ``` 131 | then the tairhash_module.so library file will be generated in the lib directory 132 | 133 | ``` 134 | ./redis-server --loadmodule /path/to/tairhash_module.so 135 | ``` 136 | ## TEST 137 | 138 | 1. Modify the path in the tairhash.tcl file in the `tests` directory to `set testmodule [file your_path/tairhash_module.so]` 139 | 2. Add the path of the tairhash.tcl file in the `tests` directory to the all_tests of redis test_helper.tcl 140 | 3. run ./runtest --single tairhash 141 | 142 | 143 | ## Client 144 | 145 | | language | GitHub | 146 | |----------|---| 147 | | Java |https://github.com/alibaba/alibabacloud-tairjedis-sdk| 148 | | Python |https://github.com/alibaba/tair-py| 149 | | Go |https://github.com/alibaba/tair-go| 150 | | .Net |https://github.com/alibaba/AlibabaCloud.TairSDK| 151 | 152 | ## API 153 | [Reference](CMDDOC.md) 154 | 155 | 156 | ### Our modules 157 | [TairHash](https://github.com/alibaba/TairHash): A redis module, similar to redis hash, but you can set expire and version for the field 158 | [TairZset](https://github.com/alibaba/TairZset): A redis module, similar to redis zset, but you can set multiple scores for each member to support multi-dimensional sorting 159 | [TairString](https://github.com/alibaba/TairString): A redis module, similar to redis string, but you can set expire and version for the value. It also provides many very useful commands, such as cas/cad, etc. 160 | -------------------------------------------------------------------------------- /dep/dict.h: -------------------------------------------------------------------------------- 1 | /* Hash Tables Implementation. 2 | * 3 | * This file implements in-memory hash tables with insert/del/replace/find/ 4 | * get-random-element operations. Hash tables will auto-resize if needed 5 | * tables of power of two in size are used, collisions are handled by 6 | * chaining. See the source code for more information... :) 7 | * 8 | * Copyright (c) 2006-2012, Salvatore Sanfilippo 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without 12 | * modification, are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * * Neither the name of Redis nor the names of its contributors may be used 20 | * to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 27 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | * POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #include 37 | #include 38 | 39 | #ifndef __DICT_H 40 | #define __DICT_H 41 | 42 | #define DICT_OK 0 43 | #define DICT_ERR 1 44 | 45 | /* Unused arguments generate annoying warnings... */ 46 | #define DICT_NOTUSED(V) ((void)V) 47 | 48 | typedef struct m_dictEntry { 49 | void *key; 50 | union { 51 | void *val; 52 | uint64_t u64; 53 | int64_t s64; 54 | double d; 55 | } v; 56 | struct m_dictEntry *next; 57 | } m_dictEntry; 58 | 59 | typedef struct m_dictType { 60 | uint64_t (*hashFunction)(const void *key); 61 | void *(*keyDup)(void *privdata, const void *key); 62 | void *(*valDup)(void *privdata, const void *obj); 63 | int (*keyCompare)(void *privdata, const void *key1, const void *key2); 64 | void (*keyDestructor)(void *privdata, void *key); 65 | void (*valDestructor)(void *privdata, void *obj); 66 | } m_dictType; 67 | 68 | /* This is our hash table structure. Every dictionary has two of this as we 69 | * implement incremental rehashing, for the old to the new table. */ 70 | typedef struct dictht { 71 | m_dictEntry **table; 72 | unsigned long size; 73 | unsigned long sizemask; 74 | unsigned long used; 75 | } dictht; 76 | 77 | typedef struct dict { 78 | m_dictType *type; 79 | void *privdata; 80 | dictht ht[2]; 81 | long rehashidx; /* rehashing not in progress if rehashidx == -1 */ 82 | unsigned long iterators; /* number of iterators currently running */ 83 | } dict; 84 | 85 | /* If safe is set to 1 this is a safe iterator, that means, you can call 86 | * m_dictAdd, m_dictFind, and other functions against the dictionary even while 87 | * iterating. Otherwise it is a non safe iterator, and only m_dictNext() 88 | * should be called while iterating. */ 89 | typedef struct m_dictIterator { 90 | dict *d; 91 | long index; 92 | int table, safe; 93 | m_dictEntry *entry, *nextEntry; 94 | /* unsafe iterator fingerprint for misuse detection. */ 95 | long long fingerprint; 96 | } m_dictIterator; 97 | 98 | typedef void(dictScanFunction)(void *privdata, const m_dictEntry *de); 99 | typedef void(dictScanBucketFunction)(void *privdata, m_dictEntry **bucketref); 100 | 101 | /* This is the initial size of every hash table */ 102 | #define DICT_HT_INITIAL_SIZE 4 103 | 104 | /* ------------------------------- Macros ------------------------------------*/ 105 | #define dictFreeVal(d, entry) \ 106 | if ((d)->type->valDestructor) \ 107 | (d)->type->valDestructor((d)->privdata, (entry)->v.val) 108 | 109 | #define dictSetVal(d, entry, _val_) \ 110 | do { \ 111 | if ((d)->type->valDup) \ 112 | (entry)->v.val = (d)->type->valDup((d)->privdata, _val_); \ 113 | else \ 114 | (entry)->v.val = (_val_); \ 115 | } while (0) 116 | 117 | #define dictSetSignedIntegerVal(entry, _val_) \ 118 | do { \ 119 | (entry)->v.s64 = _val_; \ 120 | } while (0) 121 | 122 | #define dictSetUnsignedIntegerVal(entry, _val_) \ 123 | do { \ 124 | (entry)->v.u64 = _val_; \ 125 | } while (0) 126 | 127 | #define dictSetDoubleVal(entry, _val_) \ 128 | do { \ 129 | (entry)->v.d = _val_; \ 130 | } while (0) 131 | 132 | #define dictFreeKey(d, entry) \ 133 | if ((d)->type->keyDestructor) \ 134 | (d)->type->keyDestructor((d)->privdata, (entry)->key) 135 | 136 | #define dictSetKey(d, entry, _key_) \ 137 | do { \ 138 | if ((d)->type->keyDup) \ 139 | (entry)->key = (d)->type->keyDup((d)->privdata, _key_); \ 140 | else \ 141 | (entry)->key = (_key_); \ 142 | } while (0) 143 | 144 | #define dictCompareKeys(d, key1, key2) \ 145 | (((d)->type->keyCompare) ? (d)->type->keyCompare((d)->privdata, key1, key2) : (key1) == (key2)) 146 | 147 | #define dictHashKey(d, key) (d)->type->hashFunction(key) 148 | #define dictGetKey(he) ((he)->key) 149 | #define dictGetVal(he) ((he)->v.val) 150 | #define dictGetSignedIntegerVal(he) ((he)->v.s64) 151 | #define dictGetUnsignedIntegerVal(he) ((he)->v.u64) 152 | #define dictGetDoubleVal(he) ((he)->v.d) 153 | #define dictSlots(d) ((d)->ht[0].size + (d)->ht[1].size) 154 | #define dictSize(d) ((d)->ht[0].used + (d)->ht[1].used) 155 | #define dictIsRehashing(d) ((d)->rehashidx != -1) 156 | 157 | /* API */ 158 | dict *m_dictCreate(m_dictType *type, void *privDataPtr); 159 | int m_dictExpand(dict *d, unsigned long size); 160 | int m_dictAdd(dict *d, void *key, void *val); 161 | m_dictEntry *m_dictAddRaw(dict *d, void *key, m_dictEntry **existing); 162 | m_dictEntry *m_dictAddOrFind(dict *d, void *key); 163 | int m_dictReplace(dict *d, void *key, void *val); 164 | int m_dictDelete(dict *d, const void *key); 165 | m_dictEntry *m_dictUnlink(dict *ht, const void *key); 166 | void m_dictFreeUnlinkedEntry(dict *d, m_dictEntry *he); 167 | void m_dictRelease(dict *d); 168 | m_dictEntry *m_dictFind(dict *d, const void *key); 169 | void *m_dictFetchValue(dict *d, const void *key); 170 | int m_dictResize(dict *d); 171 | m_dictIterator *m_dictGetIterator(dict *d); 172 | m_dictIterator *m_dictGetSafeIterator(dict *d); 173 | m_dictEntry *m_dictNext(m_dictIterator *iter); 174 | void m_dictReleaseIterator(m_dictIterator *iter); 175 | m_dictEntry *m_dictGetRandomKey(dict *d); 176 | unsigned int m_dictGetSomeKeys(dict *d, m_dictEntry **des, unsigned int count); 177 | void m_dictGetStats(char *buf, size_t bufsize, dict *d); 178 | uint64_t m_dictGenHashFunction(const void *key, int len); 179 | uint64_t m_dictGenCaseHashFunction(const unsigned char *buf, int len); 180 | void m_dictEmpty(dict *d, void(callback)(void *)); 181 | void m_dictEnableResize(void); 182 | void m_dictDisableResize(void); 183 | int m_dictRehash(dict *d, int n); 184 | int m_dictRehashMilliseconds(dict *d, int ms); 185 | void m_dictSetHashFunctionSeed(uint8_t *seed); 186 | uint8_t *m_dictGetHashFunctionSeed(void); 187 | unsigned long m_dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata); 188 | uint64_t m_dictGetHash(dict *d, const void *key); 189 | m_dictEntry **m_dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash); 190 | 191 | /* Hash table types */ 192 | extern m_dictType dictTypeHeapStringCopyKey; 193 | extern m_dictType dictTypeHeapStrings; 194 | extern m_dictType dictTypeHeapStringCopyKeyValue; 195 | 196 | #endif /* __DICT_H */ 197 | -------------------------------------------------------------------------------- /dep/list.c: -------------------------------------------------------------------------------- 1 | /* adlist.c - A generic doubly linked list implementation 2 | * 3 | * Copyright (c) 2006-2010, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 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 notice, 10 | * 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 Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include "list.h" 32 | 33 | #include 34 | 35 | #define REDISMODULE_API_FUNC(x) (*x) 36 | extern void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes); 37 | extern void *REDISMODULE_API_FUNC(RedisModule_Realloc)(void *ptr, size_t bytes); 38 | extern void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr); 39 | extern void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size); 40 | 41 | #define zfree RedisModule_Free 42 | #define zmalloc RedisModule_Alloc 43 | #define zcalloc(x) RedisModule_Calloc(1, x) 44 | 45 | /* Create a new list. The created list can be freed with 46 | * AlFreeList(), but private value of every node need to be freed 47 | * by the user before to call AlFreeList(). 48 | * 49 | * On error, NULL is returned. Otherwise the pointer to the new list. */ 50 | list *m_listCreate(void) { 51 | struct list *list; 52 | 53 | if ((list = zmalloc(sizeof(*list))) == NULL) 54 | return NULL; 55 | list->head = list->tail = NULL; 56 | list->len = 0; 57 | list->dup = NULL; 58 | list->free = NULL; 59 | list->match = NULL; 60 | return list; 61 | } 62 | 63 | /* Remove all the elements from the list without destroying the list itself. */ 64 | void m_listEmpty(list *list) { 65 | unsigned long len; 66 | m_listNode *current, *next; 67 | 68 | current = list->head; 69 | len = list->len; 70 | while (len--) { 71 | next = current->next; 72 | if (list->free) list->free(current->value); 73 | zfree(current); 74 | current = next; 75 | } 76 | list->head = list->tail = NULL; 77 | list->len = 0; 78 | } 79 | 80 | /* Free the whole list. 81 | * 82 | * This function can't fail. */ 83 | void m_listRelease(list *list) { 84 | m_listEmpty(list); 85 | zfree(list); 86 | } 87 | 88 | /* Add a new node to the list, to head, containing the specified 'value' 89 | * pointer as value. 90 | * 91 | * On error, NULL is returned and no operation is performed (i.e. the 92 | * list remains unaltered). 93 | * On success the 'list' pointer you pass to the function is returned. */ 94 | list *m_listAddNodeHead(list *list, void *value) { 95 | m_listNode *node; 96 | 97 | if ((node = zmalloc(sizeof(*node))) == NULL) 98 | return NULL; 99 | node->value = value; 100 | if (list->len == 0) { 101 | list->head = list->tail = node; 102 | node->prev = node->next = NULL; 103 | } else { 104 | node->prev = NULL; 105 | node->next = list->head; 106 | list->head->prev = node; 107 | list->head = node; 108 | } 109 | list->len++; 110 | return list; 111 | } 112 | 113 | /* Add a new node to the list, to tail, containing the specified 'value' 114 | * pointer as value. 115 | * 116 | * On error, NULL is returned and no operation is performed (i.e. the 117 | * list remains unaltered). 118 | * On success the 'list' pointer you pass to the function is returned. */ 119 | list *m_listAddNodeTail(list *list, void *value) { 120 | m_listNode *node; 121 | 122 | if ((node = zmalloc(sizeof(*node))) == NULL) 123 | return NULL; 124 | node->value = value; 125 | if (list->len == 0) { 126 | list->head = list->tail = node; 127 | node->prev = node->next = NULL; 128 | } else { 129 | node->prev = list->tail; 130 | node->next = NULL; 131 | list->tail->next = node; 132 | list->tail = node; 133 | } 134 | list->len++; 135 | return list; 136 | } 137 | 138 | list *m_listInsertNode(list *list, m_listNode *old_node, void *value, int after) { 139 | m_listNode *node; 140 | 141 | if ((node = zmalloc(sizeof(*node))) == NULL) 142 | return NULL; 143 | node->value = value; 144 | if (after) { 145 | node->prev = old_node; 146 | node->next = old_node->next; 147 | if (list->tail == old_node) { 148 | list->tail = node; 149 | } 150 | } else { 151 | node->next = old_node; 152 | node->prev = old_node->prev; 153 | if (list->head == old_node) { 154 | list->head = node; 155 | } 156 | } 157 | if (node->prev != NULL) { 158 | node->prev->next = node; 159 | } 160 | if (node->next != NULL) { 161 | node->next->prev = node; 162 | } 163 | list->len++; 164 | return list; 165 | } 166 | 167 | /* Remove the specified node from the specified list. 168 | * It's up to the caller to free the private value of the node. 169 | * 170 | * This function can't fail. */ 171 | void m_listDelNode(list *list, m_listNode *node) { 172 | if (node->prev) 173 | node->prev->next = node->next; 174 | else 175 | list->head = node->next; 176 | if (node->next) 177 | node->next->prev = node->prev; 178 | else 179 | list->tail = node->prev; 180 | if (list->free) list->free(node->value); 181 | zfree(node); 182 | list->len--; 183 | } 184 | 185 | /* Returns a list iterator 'iter'. After the initialization every 186 | * call to m_listNext() will return the next element of the list. 187 | * 188 | * This function can't fail. */ 189 | m_listIter *m_listGetIterator(list *list, int direction) { 190 | m_listIter *iter; 191 | 192 | if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL; 193 | if (direction == AL_START_HEAD) 194 | iter->next = list->head; 195 | else 196 | iter->next = list->tail; 197 | iter->direction = direction; 198 | return iter; 199 | } 200 | 201 | /* Release the iterator memory */ 202 | void m_listReleaseIterator(m_listIter *iter) { 203 | zfree(iter); 204 | } 205 | 206 | /* Create an iterator in the list private iterator structure */ 207 | void m_listRewind(list *list, m_listIter *li) { 208 | li->next = list->head; 209 | li->direction = AL_START_HEAD; 210 | } 211 | 212 | void m_listRewindTail(list *list, m_listIter *li) { 213 | li->next = list->tail; 214 | li->direction = AL_START_TAIL; 215 | } 216 | 217 | /* Return the next element of an iterator. 218 | * It's valid to remove the currently returned element using 219 | * m_listDelNode(), but not to remove other elements. 220 | * 221 | * The function returns a pointer to the next element of the list, 222 | * or NULL if there are no more elements, so the classical usage patter 223 | * is: 224 | * 225 | * iter = m_listGetIterator(list,); 226 | * while ((node = m_listNext(iter)) != NULL) { 227 | * doSomethingWith(listNodeValue(node)); 228 | * } 229 | * 230 | * */ 231 | m_listNode *m_listNext(m_listIter *iter) { 232 | m_listNode *current = iter->next; 233 | 234 | if (current != NULL) { 235 | if (iter->direction == AL_START_HEAD) 236 | iter->next = current->next; 237 | else 238 | iter->next = current->prev; 239 | } 240 | return current; 241 | } 242 | 243 | /* Duplicate the whole list. On out of memory NULL is returned. 244 | * On success a copy of the original list is returned. 245 | * 246 | * The 'Dup' method set with listSetDupMethod() function is used 247 | * to copy the node value. Otherwise the same pointer value of 248 | * the original node is used as value of the copied node. 249 | * 250 | * The original list both on success or error is never modified. */ 251 | list *m_listDup(list *orig) { 252 | list *copy; 253 | m_listIter iter; 254 | m_listNode *node; 255 | 256 | if ((copy = m_listCreate()) == NULL) 257 | return NULL; 258 | copy->dup = orig->dup; 259 | copy->free = orig->free; 260 | copy->match = orig->match; 261 | m_listRewind(orig, &iter); 262 | while ((node = m_listNext(&iter)) != NULL) { 263 | void *value; 264 | 265 | if (copy->dup) { 266 | value = copy->dup(node->value); 267 | if (value == NULL) { 268 | m_listRelease(copy); 269 | return NULL; 270 | } 271 | } else 272 | value = node->value; 273 | if (m_listAddNodeTail(copy, value) == NULL) { 274 | m_listRelease(copy); 275 | return NULL; 276 | } 277 | } 278 | return copy; 279 | } 280 | 281 | /* Search the list for a node matching a given key. 282 | * The match is performed using the 'match' method 283 | * set with listSetMatchMethod(). If no 'match' method 284 | * is set, the 'value' pointer of every node is directly 285 | * compared with the 'key' pointer. 286 | * 287 | * On success the first matching node pointer is returned 288 | * (search starts from head). If no matching node exists 289 | * NULL is returned. */ 290 | m_listNode *m_listSearchKey(list *list, void *key) { 291 | m_listIter iter; 292 | m_listNode *node; 293 | 294 | m_listRewind(list, &iter); 295 | while ((node = m_listNext(&iter)) != NULL) { 296 | if (list->match) { 297 | if (list->match(node->value, key)) { 298 | return node; 299 | } 300 | } else { 301 | if (key == node->value) { 302 | return node; 303 | } 304 | } 305 | } 306 | return NULL; 307 | } 308 | 309 | /* Return the element at the specified zero-based index 310 | * where 0 is the head, 1 is the element next to head 311 | * and so on. Negative integers are used in order to count 312 | * from the tail, -1 is the last element, -2 the penultimate 313 | * and so on. If the index is out of range NULL is returned. */ 314 | m_listNode *m_listIndex(list *list, long index) { 315 | m_listNode *n; 316 | 317 | if (index < 0) { 318 | index = (-index) - 1; 319 | n = list->tail; 320 | while (index-- && n) n = n->prev; 321 | } else { 322 | n = list->head; 323 | while (index-- && n) n = n->next; 324 | } 325 | return n; 326 | } 327 | 328 | /* Rotate the list removing the tail node and inserting it to the head. */ 329 | void m_listRotate(list *list) { 330 | m_listNode *tail = list->tail; 331 | 332 | if (listLength(list) <= 1) return; 333 | 334 | /* Detach current tail */ 335 | list->tail = tail->prev; 336 | list->tail->next = NULL; 337 | /* Move it as head */ 338 | list->head->prev = tail; 339 | tail->prev = NULL; 340 | tail->next = list->head; 341 | list->head = tail; 342 | } 343 | 344 | /* Add all the elements of the list 'o' at the end of the 345 | * list 'l'. The list 'other' remains empty but otherwise valid. */ 346 | void m_listJoin(list *l, list *o) { 347 | if (o->head) 348 | o->head->prev = l->tail; 349 | 350 | if (l->tail) 351 | l->tail->next = o->head; 352 | else 353 | l->head = o->head; 354 | 355 | if (o->tail) l->tail = o->tail; 356 | l->len += o->len; 357 | 358 | /* Setup other as an empty list. */ 359 | o->head = o->tail = NULL; 360 | o->len = 0; 361 | } -------------------------------------------------------------------------------- /dep/list.h: -------------------------------------------------------------------------------- 1 | /* adlist.h - A generic doubly linked list implementation 2 | * 3 | * Copyright (c) 2006-2012, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 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 notice, 10 | * 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 Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef __ADLIST_H__ 32 | #define __ADLIST_H__ 33 | 34 | /* Node, List, and Iterator are the only data structures used currently. */ 35 | 36 | typedef struct m_listNode { 37 | struct m_listNode *prev; 38 | struct m_listNode *next; 39 | void *value; 40 | } m_listNode; 41 | 42 | typedef struct m_listIter { 43 | m_listNode *next; 44 | int direction; 45 | } m_listIter; 46 | 47 | typedef struct list { 48 | m_listNode *head; 49 | m_listNode *tail; 50 | void *(*dup)(void *ptr); 51 | void (*free)(void *ptr); 52 | int (*match)(void *ptr, void *key); 53 | unsigned long len; 54 | } list; 55 | 56 | /* Functions implemented as macros */ 57 | #define listLength(l) ((l)->len) 58 | #define listFirst(l) ((l)->head) 59 | #define listLast(l) ((l)->tail) 60 | #define listPrevNode(n) ((n)->prev) 61 | #define listNextNode(n) ((n)->next) 62 | #define listNodeValue(n) ((n)->value) 63 | 64 | #define listSetDupMethod(l, m) ((l)->dup = (m)) 65 | #define listSetFreeMethod(l, m) ((l)->free = (m)) 66 | #define listSetMatchMethod(l, m) ((l)->match = (m)) 67 | 68 | #define listGetDupMethod(l) ((l)->dup) 69 | #define listGetFree(l) ((l)->free) 70 | #define listGetMatchMethod(l) ((l)->match) 71 | 72 | /* Prototypes */ 73 | list *m_listCreate(void); 74 | void m_listRelease(list *list); 75 | void m_listEmpty(list *list); 76 | list *m_listAddNodeHead(list *list, void *value); 77 | list *m_listAddNodeTail(list *list, void *value); 78 | list *m_listInsertNode(list *list, m_listNode *old_node, void *value, int after); 79 | void m_listDelNode(list *list, m_listNode *node); 80 | m_listIter *m_listGetIterator(list *list, int direction); 81 | m_listNode *m_listNext(m_listIter *iter); 82 | void m_listReleaseIterator(m_listIter *iter); 83 | list *m_listDup(list *orig); 84 | m_listNode *m_listSearchKey(list *list, void *key); 85 | m_listNode *m_listIndex(list *list, long index); 86 | void m_listRewind(list *list, m_listIter *li); 87 | void m_listRewindTail(list *list, m_listIter *li); 88 | void m_listRotate(list *list); 89 | void m_listJoin(list *l, list *o); 90 | 91 | /* Directions for iterators */ 92 | #define AL_START_HEAD 0 93 | #define AL_START_TAIL 1 94 | 95 | #endif /* __ADLIST_H__ */ -------------------------------------------------------------------------------- /dep/siphash.c: -------------------------------------------------------------------------------- 1 | /* 2 | SipHash reference C implementation 3 | 4 | Copyright (c) 2012-2016 Jean-Philippe Aumasson 5 | 6 | Copyright (c) 2012-2014 Daniel J. Bernstein 7 | Copyright (c) 2017 Salvatore Sanfilippo 8 | 9 | To the extent possible under law, the author(s) have dedicated all copyright 10 | and related and neighboring rights to this software to the public domain 11 | worldwide. This software is distributed without any warranty. 12 | 13 | You should have received a copy of the CC0 Public Domain Dedication along 14 | with this software. If not, see 15 | . 16 | 17 | ---------------------------------------------------------------------------- 18 | 19 | This version was modified by Salvatore Sanfilippo 20 | in the following ways: 21 | 22 | 1. We use SipHash 1-2. This is not believed to be as strong as the 23 | suggested 2-4 variant, but AFAIK there are not trivial attacks 24 | against this reduced-rounds version, and it runs at the same speed 25 | as Murmurhash2 that we used previously, why the 2-4 variant slowed 26 | down Redis by a 4% figure more or less. 27 | 2. Hard-code rounds in the hope the compiler can optimize it more 28 | in this raw from. Anyway we always want the standard 2-4 variant. 29 | 3. Modify the prototype and implementation so that the function directly 30 | returns an uint64_t value, the hash itself, instead of receiving an 31 | output buffer. This also means that the output size is set to 8 bytes 32 | and the 16 bytes output code handling was removed. 33 | 4. Provide a case insensitive variant to be used when hashing strings that 34 | must be considered identical by the hash table regardless of the case. 35 | If we don't have directly a case insensitive hash function, we need to 36 | perform a text transformation in some temporary buffer, which is costly. 37 | 5. Remove debugging code. 38 | 6. Modified the original test.c file to be a stand-alone function testing 39 | the function in the new form (returing an uint64_t) using just the 40 | relevant test vector. 41 | */ 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | /* Fast tolower() alike function that does not care about locale 49 | * but just returns a-z insetad of A-Z. */ 50 | int siptlw(int c) { 51 | if (c >= 'A' && c <= 'Z') { 52 | return c + ('a' - 'A'); 53 | } else { 54 | return c; 55 | } 56 | } 57 | 58 | /* Test of the CPU is Little Endian and supports not aligned accesses. 59 | * Two interesting conditions to speedup the function that happen to be 60 | * in most of x86 servers. */ 61 | #if defined(__X86_64__) || defined(__x86_64__) || defined(__i386__) 62 | #define UNALIGNED_LE_CPU 63 | #endif 64 | 65 | #define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) 66 | 67 | #define U32TO8_LE(p, v) \ 68 | (p)[0] = (uint8_t)((v)); \ 69 | (p)[1] = (uint8_t)((v) >> 8); \ 70 | (p)[2] = (uint8_t)((v) >> 16); \ 71 | (p)[3] = (uint8_t)((v) >> 24); 72 | 73 | #define U64TO8_LE(p, v) \ 74 | U32TO8_LE((p), (uint32_t)((v))); \ 75 | U32TO8_LE((p) + 4, (uint32_t)((v) >> 32)); 76 | 77 | #ifdef UNALIGNED_LE_CPU 78 | #define U8TO64_LE(p) (*((uint64_t *)(p))) 79 | #else 80 | #define U8TO64_LE(p) \ 81 | (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56)) 82 | #endif 83 | 84 | #define U8TO64_LE_NOCASE(p) \ 85 | (((uint64_t)(siptlw((p)[0]))) | ((uint64_t)(siptlw((p)[1])) << 8) | ((uint64_t)(siptlw((p)[2])) << 16) | ((uint64_t)(siptlw((p)[3])) << 24) | ((uint64_t)(siptlw((p)[4])) << 32) | ((uint64_t)(siptlw((p)[5])) << 40) | ((uint64_t)(siptlw((p)[6])) << 48) | ((uint64_t)(siptlw((p)[7])) << 56)) 86 | 87 | #define SIPROUND \ 88 | do { \ 89 | v0 += v1; \ 90 | v1 = ROTL(v1, 13); \ 91 | v1 ^= v0; \ 92 | v0 = ROTL(v0, 32); \ 93 | v2 += v3; \ 94 | v3 = ROTL(v3, 16); \ 95 | v3 ^= v2; \ 96 | v0 += v3; \ 97 | v3 = ROTL(v3, 21); \ 98 | v3 ^= v0; \ 99 | v2 += v1; \ 100 | v1 = ROTL(v1, 17); \ 101 | v1 ^= v2; \ 102 | v2 = ROTL(v2, 32); \ 103 | } while (0) 104 | 105 | uint64_t siphash(const uint8_t *in, const size_t inlen, const uint8_t *k) { 106 | #ifndef UNALIGNED_LE_CPU 107 | uint64_t hash; 108 | uint8_t *out = (uint8_t *)&hash; 109 | #endif 110 | uint64_t v0 = 0x736f6d6570736575ULL; 111 | uint64_t v1 = 0x646f72616e646f6dULL; 112 | uint64_t v2 = 0x6c7967656e657261ULL; 113 | uint64_t v3 = 0x7465646279746573ULL; 114 | uint64_t k0 = U8TO64_LE(k); 115 | uint64_t k1 = U8TO64_LE(k + 8); 116 | uint64_t m; 117 | const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); 118 | const int left = inlen & 7; 119 | uint64_t b = ((uint64_t)inlen) << 56; 120 | v3 ^= k1; 121 | v2 ^= k0; 122 | v1 ^= k1; 123 | v0 ^= k0; 124 | 125 | for (; in != end; in += 8) { 126 | m = U8TO64_LE(in); 127 | v3 ^= m; 128 | 129 | SIPROUND; 130 | 131 | v0 ^= m; 132 | } 133 | 134 | switch (left) { 135 | case 7: 136 | b |= ((uint64_t)in[6]) << 48; /* fall-thru */ 137 | case 6: 138 | b |= ((uint64_t)in[5]) << 40; /* fall-thru */ 139 | case 5: 140 | b |= ((uint64_t)in[4]) << 32; /* fall-thru */ 141 | case 4: 142 | b |= ((uint64_t)in[3]) << 24; /* fall-thru */ 143 | case 3: 144 | b |= ((uint64_t)in[2]) << 16; /* fall-thru */ 145 | case 2: 146 | b |= ((uint64_t)in[1]) << 8; /* fall-thru */ 147 | case 1: 148 | b |= ((uint64_t)in[0]); 149 | break; 150 | case 0: 151 | break; 152 | } 153 | 154 | v3 ^= b; 155 | 156 | SIPROUND; 157 | 158 | v0 ^= b; 159 | v2 ^= 0xff; 160 | 161 | SIPROUND; 162 | SIPROUND; 163 | 164 | b = v0 ^ v1 ^ v2 ^ v3; 165 | #ifndef UNALIGNED_LE_CPU 166 | U64TO8_LE(out, b); 167 | return hash; 168 | #else 169 | return b; 170 | #endif 171 | } 172 | 173 | uint64_t siphash_nocase(const uint8_t *in, const size_t inlen, const uint8_t *k) { 174 | #ifndef UNALIGNED_LE_CPU 175 | uint64_t hash; 176 | uint8_t *out = (uint8_t *)&hash; 177 | #endif 178 | uint64_t v0 = 0x736f6d6570736575ULL; 179 | uint64_t v1 = 0x646f72616e646f6dULL; 180 | uint64_t v2 = 0x6c7967656e657261ULL; 181 | uint64_t v3 = 0x7465646279746573ULL; 182 | uint64_t k0 = U8TO64_LE(k); 183 | uint64_t k1 = U8TO64_LE(k + 8); 184 | uint64_t m; 185 | const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); 186 | const int left = inlen & 7; 187 | uint64_t b = ((uint64_t)inlen) << 56; 188 | v3 ^= k1; 189 | v2 ^= k0; 190 | v1 ^= k1; 191 | v0 ^= k0; 192 | 193 | for (; in != end; in += 8) { 194 | m = U8TO64_LE_NOCASE(in); 195 | v3 ^= m; 196 | 197 | SIPROUND; 198 | 199 | v0 ^= m; 200 | } 201 | 202 | switch (left) { 203 | case 7: 204 | b |= ((uint64_t)siptlw(in[6])) << 48; /* fall-thru */ 205 | case 6: 206 | b |= ((uint64_t)siptlw(in[5])) << 40; /* fall-thru */ 207 | case 5: 208 | b |= ((uint64_t)siptlw(in[4])) << 32; /* fall-thru */ 209 | case 4: 210 | b |= ((uint64_t)siptlw(in[3])) << 24; /* fall-thru */ 211 | case 3: 212 | b |= ((uint64_t)siptlw(in[2])) << 16; /* fall-thru */ 213 | case 2: 214 | b |= ((uint64_t)siptlw(in[1])) << 8; /* fall-thru */ 215 | case 1: 216 | b |= ((uint64_t)siptlw(in[0])); 217 | break; 218 | case 0: 219 | break; 220 | } 221 | 222 | v3 ^= b; 223 | 224 | SIPROUND; 225 | 226 | v0 ^= b; 227 | v2 ^= 0xff; 228 | 229 | SIPROUND; 230 | SIPROUND; 231 | 232 | b = v0 ^ v1 ^ v2 ^ v3; 233 | #ifndef UNALIGNED_LE_CPU 234 | U64TO8_LE(out, b); 235 | return hash; 236 | #else 237 | return b; 238 | #endif 239 | } 240 | 241 | /* --------------------------------- TEST ------------------------------------ */ 242 | 243 | #ifdef SIPHASH_TEST 244 | 245 | const uint8_t vectors_sip64[64][8] = { 246 | { 247 | 0x31, 248 | 0x0e, 249 | 0x0e, 250 | 0xdd, 251 | 0x47, 252 | 0xdb, 253 | 0x6f, 254 | 0x72, 255 | }, 256 | { 257 | 0xfd, 258 | 0x67, 259 | 0xdc, 260 | 0x93, 261 | 0xc5, 262 | 0x39, 263 | 0xf8, 264 | 0x74, 265 | }, 266 | { 267 | 0x5a, 268 | 0x4f, 269 | 0xa9, 270 | 0xd9, 271 | 0x09, 272 | 0x80, 273 | 0x6c, 274 | 0x0d, 275 | }, 276 | { 277 | 0x2d, 278 | 0x7e, 279 | 0xfb, 280 | 0xd7, 281 | 0x96, 282 | 0x66, 283 | 0x67, 284 | 0x85, 285 | }, 286 | { 287 | 0xb7, 288 | 0x87, 289 | 0x71, 290 | 0x27, 291 | 0xe0, 292 | 0x94, 293 | 0x27, 294 | 0xcf, 295 | }, 296 | { 297 | 0x8d, 298 | 0xa6, 299 | 0x99, 300 | 0xcd, 301 | 0x64, 302 | 0x55, 303 | 0x76, 304 | 0x18, 305 | }, 306 | { 307 | 0xce, 308 | 0xe3, 309 | 0xfe, 310 | 0x58, 311 | 0x6e, 312 | 0x46, 313 | 0xc9, 314 | 0xcb, 315 | }, 316 | { 317 | 0x37, 318 | 0xd1, 319 | 0x01, 320 | 0x8b, 321 | 0xf5, 322 | 0x00, 323 | 0x02, 324 | 0xab, 325 | }, 326 | { 327 | 0x62, 328 | 0x24, 329 | 0x93, 330 | 0x9a, 331 | 0x79, 332 | 0xf5, 333 | 0xf5, 334 | 0x93, 335 | }, 336 | { 337 | 0xb0, 338 | 0xe4, 339 | 0xa9, 340 | 0x0b, 341 | 0xdf, 342 | 0x82, 343 | 0x00, 344 | 0x9e, 345 | }, 346 | { 347 | 0xf3, 348 | 0xb9, 349 | 0xdd, 350 | 0x94, 351 | 0xc5, 352 | 0xbb, 353 | 0x5d, 354 | 0x7a, 355 | }, 356 | { 357 | 0xa7, 358 | 0xad, 359 | 0x6b, 360 | 0x22, 361 | 0x46, 362 | 0x2f, 363 | 0xb3, 364 | 0xf4, 365 | }, 366 | { 367 | 0xfb, 368 | 0xe5, 369 | 0x0e, 370 | 0x86, 371 | 0xbc, 372 | 0x8f, 373 | 0x1e, 374 | 0x75, 375 | }, 376 | { 377 | 0x90, 378 | 0x3d, 379 | 0x84, 380 | 0xc0, 381 | 0x27, 382 | 0x56, 383 | 0xea, 384 | 0x14, 385 | }, 386 | { 387 | 0xee, 388 | 0xf2, 389 | 0x7a, 390 | 0x8e, 391 | 0x90, 392 | 0xca, 393 | 0x23, 394 | 0xf7, 395 | }, 396 | { 397 | 0xe5, 398 | 0x45, 399 | 0xbe, 400 | 0x49, 401 | 0x61, 402 | 0xca, 403 | 0x29, 404 | 0xa1, 405 | }, 406 | { 407 | 0xdb, 408 | 0x9b, 409 | 0xc2, 410 | 0x57, 411 | 0x7f, 412 | 0xcc, 413 | 0x2a, 414 | 0x3f, 415 | }, 416 | { 417 | 0x94, 418 | 0x47, 419 | 0xbe, 420 | 0x2c, 421 | 0xf5, 422 | 0xe9, 423 | 0x9a, 424 | 0x69, 425 | }, 426 | { 427 | 0x9c, 428 | 0xd3, 429 | 0x8d, 430 | 0x96, 431 | 0xf0, 432 | 0xb3, 433 | 0xc1, 434 | 0x4b, 435 | }, 436 | { 437 | 0xbd, 438 | 0x61, 439 | 0x79, 440 | 0xa7, 441 | 0x1d, 442 | 0xc9, 443 | 0x6d, 444 | 0xbb, 445 | }, 446 | { 447 | 0x98, 448 | 0xee, 449 | 0xa2, 450 | 0x1a, 451 | 0xf2, 452 | 0x5c, 453 | 0xd6, 454 | 0xbe, 455 | }, 456 | { 457 | 0xc7, 458 | 0x67, 459 | 0x3b, 460 | 0x2e, 461 | 0xb0, 462 | 0xcb, 463 | 0xf2, 464 | 0xd0, 465 | }, 466 | { 467 | 0x88, 468 | 0x3e, 469 | 0xa3, 470 | 0xe3, 471 | 0x95, 472 | 0x67, 473 | 0x53, 474 | 0x93, 475 | }, 476 | { 477 | 0xc8, 478 | 0xce, 479 | 0x5c, 480 | 0xcd, 481 | 0x8c, 482 | 0x03, 483 | 0x0c, 484 | 0xa8, 485 | }, 486 | { 487 | 0x94, 488 | 0xaf, 489 | 0x49, 490 | 0xf6, 491 | 0xc6, 492 | 0x50, 493 | 0xad, 494 | 0xb8, 495 | }, 496 | { 497 | 0xea, 498 | 0xb8, 499 | 0x85, 500 | 0x8a, 501 | 0xde, 502 | 0x92, 503 | 0xe1, 504 | 0xbc, 505 | }, 506 | { 507 | 0xf3, 508 | 0x15, 509 | 0xbb, 510 | 0x5b, 511 | 0xb8, 512 | 0x35, 513 | 0xd8, 514 | 0x17, 515 | }, 516 | { 517 | 0xad, 518 | 0xcf, 519 | 0x6b, 520 | 0x07, 521 | 0x63, 522 | 0x61, 523 | 0x2e, 524 | 0x2f, 525 | }, 526 | { 527 | 0xa5, 528 | 0xc9, 529 | 0x1d, 530 | 0xa7, 531 | 0xac, 532 | 0xaa, 533 | 0x4d, 534 | 0xde, 535 | }, 536 | { 537 | 0x71, 538 | 0x65, 539 | 0x95, 540 | 0x87, 541 | 0x66, 542 | 0x50, 543 | 0xa2, 544 | 0xa6, 545 | }, 546 | { 547 | 0x28, 548 | 0xef, 549 | 0x49, 550 | 0x5c, 551 | 0x53, 552 | 0xa3, 553 | 0x87, 554 | 0xad, 555 | }, 556 | { 557 | 0x42, 558 | 0xc3, 559 | 0x41, 560 | 0xd8, 561 | 0xfa, 562 | 0x92, 563 | 0xd8, 564 | 0x32, 565 | }, 566 | { 567 | 0xce, 568 | 0x7c, 569 | 0xf2, 570 | 0x72, 571 | 0x2f, 572 | 0x51, 573 | 0x27, 574 | 0x71, 575 | }, 576 | { 577 | 0xe3, 578 | 0x78, 579 | 0x59, 580 | 0xf9, 581 | 0x46, 582 | 0x23, 583 | 0xf3, 584 | 0xa7, 585 | }, 586 | { 587 | 0x38, 588 | 0x12, 589 | 0x05, 590 | 0xbb, 591 | 0x1a, 592 | 0xb0, 593 | 0xe0, 594 | 0x12, 595 | }, 596 | { 597 | 0xae, 598 | 0x97, 599 | 0xa1, 600 | 0x0f, 601 | 0xd4, 602 | 0x34, 603 | 0xe0, 604 | 0x15, 605 | }, 606 | { 607 | 0xb4, 608 | 0xa3, 609 | 0x15, 610 | 0x08, 611 | 0xbe, 612 | 0xff, 613 | 0x4d, 614 | 0x31, 615 | }, 616 | { 617 | 0x81, 618 | 0x39, 619 | 0x62, 620 | 0x29, 621 | 0xf0, 622 | 0x90, 623 | 0x79, 624 | 0x02, 625 | }, 626 | { 627 | 0x4d, 628 | 0x0c, 629 | 0xf4, 630 | 0x9e, 631 | 0xe5, 632 | 0xd4, 633 | 0xdc, 634 | 0xca, 635 | }, 636 | { 637 | 0x5c, 638 | 0x73, 639 | 0x33, 640 | 0x6a, 641 | 0x76, 642 | 0xd8, 643 | 0xbf, 644 | 0x9a, 645 | }, 646 | { 647 | 0xd0, 648 | 0xa7, 649 | 0x04, 650 | 0x53, 651 | 0x6b, 652 | 0xa9, 653 | 0x3e, 654 | 0x0e, 655 | }, 656 | { 657 | 0x92, 658 | 0x59, 659 | 0x58, 660 | 0xfc, 661 | 0xd6, 662 | 0x42, 663 | 0x0c, 664 | 0xad, 665 | }, 666 | { 667 | 0xa9, 668 | 0x15, 669 | 0xc2, 670 | 0x9b, 671 | 0xc8, 672 | 0x06, 673 | 0x73, 674 | 0x18, 675 | }, 676 | { 677 | 0x95, 678 | 0x2b, 679 | 0x79, 680 | 0xf3, 681 | 0xbc, 682 | 0x0a, 683 | 0xa6, 684 | 0xd4, 685 | }, 686 | { 687 | 0xf2, 688 | 0x1d, 689 | 0xf2, 690 | 0xe4, 691 | 0x1d, 692 | 0x45, 693 | 0x35, 694 | 0xf9, 695 | }, 696 | { 697 | 0x87, 698 | 0x57, 699 | 0x75, 700 | 0x19, 701 | 0x04, 702 | 0x8f, 703 | 0x53, 704 | 0xa9, 705 | }, 706 | { 707 | 0x10, 708 | 0xa5, 709 | 0x6c, 710 | 0xf5, 711 | 0xdf, 712 | 0xcd, 713 | 0x9a, 714 | 0xdb, 715 | }, 716 | { 717 | 0xeb, 718 | 0x75, 719 | 0x09, 720 | 0x5c, 721 | 0xcd, 722 | 0x98, 723 | 0x6c, 724 | 0xd0, 725 | }, 726 | { 727 | 0x51, 728 | 0xa9, 729 | 0xcb, 730 | 0x9e, 731 | 0xcb, 732 | 0xa3, 733 | 0x12, 734 | 0xe6, 735 | }, 736 | { 737 | 0x96, 738 | 0xaf, 739 | 0xad, 740 | 0xfc, 741 | 0x2c, 742 | 0xe6, 743 | 0x66, 744 | 0xc7, 745 | }, 746 | { 747 | 0x72, 748 | 0xfe, 749 | 0x52, 750 | 0x97, 751 | 0x5a, 752 | 0x43, 753 | 0x64, 754 | 0xee, 755 | }, 756 | { 757 | 0x5a, 758 | 0x16, 759 | 0x45, 760 | 0xb2, 761 | 0x76, 762 | 0xd5, 763 | 0x92, 764 | 0xa1, 765 | }, 766 | { 767 | 0xb2, 768 | 0x74, 769 | 0xcb, 770 | 0x8e, 771 | 0xbf, 772 | 0x87, 773 | 0x87, 774 | 0x0a, 775 | }, 776 | { 777 | 0x6f, 778 | 0x9b, 779 | 0xb4, 780 | 0x20, 781 | 0x3d, 782 | 0xe7, 783 | 0xb3, 784 | 0x81, 785 | }, 786 | { 787 | 0xea, 788 | 0xec, 789 | 0xb2, 790 | 0xa3, 791 | 0x0b, 792 | 0x22, 793 | 0xa8, 794 | 0x7f, 795 | }, 796 | { 797 | 0x99, 798 | 0x24, 799 | 0xa4, 800 | 0x3c, 801 | 0xc1, 802 | 0x31, 803 | 0x57, 804 | 0x24, 805 | }, 806 | { 807 | 0xbd, 808 | 0x83, 809 | 0x8d, 810 | 0x3a, 811 | 0xaf, 812 | 0xbf, 813 | 0x8d, 814 | 0xb7, 815 | }, 816 | { 817 | 0x0b, 818 | 0x1a, 819 | 0x2a, 820 | 0x32, 821 | 0x65, 822 | 0xd5, 823 | 0x1a, 824 | 0xea, 825 | }, 826 | { 827 | 0x13, 828 | 0x50, 829 | 0x79, 830 | 0xa3, 831 | 0x23, 832 | 0x1c, 833 | 0xe6, 834 | 0x60, 835 | }, 836 | { 837 | 0x93, 838 | 0x2b, 839 | 0x28, 840 | 0x46, 841 | 0xe4, 842 | 0xd7, 843 | 0x06, 844 | 0x66, 845 | }, 846 | { 847 | 0xe1, 848 | 0x91, 849 | 0x5f, 850 | 0x5c, 851 | 0xb1, 852 | 0xec, 853 | 0xa4, 854 | 0x6c, 855 | }, 856 | { 857 | 0xf3, 858 | 0x25, 859 | 0x96, 860 | 0x5c, 861 | 0xa1, 862 | 0x6d, 863 | 0x62, 864 | 0x9f, 865 | }, 866 | { 867 | 0x57, 868 | 0x5f, 869 | 0xf2, 870 | 0x8e, 871 | 0x60, 872 | 0x38, 873 | 0x1b, 874 | 0xe5, 875 | }, 876 | { 877 | 0x72, 878 | 0x45, 879 | 0x06, 880 | 0xeb, 881 | 0x4c, 882 | 0x32, 883 | 0x8a, 884 | 0x95, 885 | }, 886 | }; 887 | 888 | /* Test siphash using a test vector. Returns 0 if the function passed 889 | * all the tests, otherwise 1 is returned. 890 | * 891 | * IMPORTANT: The test vector is for SipHash 2-4. Before running 892 | * the test revert back the siphash() function to 2-4 rounds since 893 | * now it uses 1-2 rounds. */ 894 | int siphash_test(void) { 895 | uint8_t in[64], k[16]; 896 | int i; 897 | int fails = 0; 898 | 899 | for (i = 0; i < 16; ++i) 900 | k[i] = i; 901 | 902 | for (i = 0; i < 64; ++i) { 903 | in[i] = i; 904 | uint64_t hash = siphash(in, i, k); 905 | const uint8_t *v = NULL; 906 | v = (uint8_t *)vectors_sip64; 907 | if (memcmp(&hash, v + (i * 8), 8)) { 908 | /* printf("fail for %d bytes\n", i); */ 909 | fails++; 910 | } 911 | } 912 | 913 | /* Run a few basic tests with the case insensitive version. */ 914 | uint64_t h1, h2; 915 | h1 = siphash((uint8_t *)"hello world", 11, (uint8_t *)"1234567812345678"); 916 | h2 = siphash_nocase((uint8_t *)"hello world", 11, (uint8_t *)"1234567812345678"); 917 | if (h1 != h2) fails++; 918 | 919 | h1 = siphash((uint8_t *)"hello world", 11, (uint8_t *)"1234567812345678"); 920 | h2 = siphash_nocase((uint8_t *)"HELLO world", 11, (uint8_t *)"1234567812345678"); 921 | if (h1 != h2) fails++; 922 | 923 | h1 = siphash((uint8_t *)"HELLO world", 11, (uint8_t *)"1234567812345678"); 924 | h2 = siphash_nocase((uint8_t *)"HELLO world", 11, (uint8_t *)"1234567812345678"); 925 | if (h1 == h2) fails++; 926 | 927 | if (!fails) return 0; 928 | return 1; 929 | } 930 | 931 | int main(void) { 932 | if (siphash_test() == 0) { 933 | printf("SipHash test: OK\n"); 934 | return 0; 935 | } else { 936 | printf("SipHash test: FAILED\n"); 937 | return 1; 938 | } 939 | } 940 | 941 | #endif 942 | -------------------------------------------------------------------------------- /dep/skiplist.c: -------------------------------------------------------------------------------- 1 | #include "skiplist.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util.h" 8 | 9 | /* Create a skiplist node with the specified number of levels. 10 | * The SDS string 'ele' is referenced by the node after the call. */ 11 | m_zskiplistNode *m_zslCreateNode(int level, long long score, RedisModuleString *member) { 12 | m_zskiplistNode *zn = RedisModule_Alloc(sizeof(*zn) + level * sizeof(struct zskiplistLevel)); 13 | zn->score = score; 14 | zn->member = member; 15 | return zn; 16 | } 17 | 18 | /* Create a new skiplist. */ 19 | m_zskiplist *m_zslCreate(void) { 20 | int j; 21 | m_zskiplist *zsl; 22 | 23 | zsl = RedisModule_Alloc(sizeof(*zsl)); 24 | zsl->level = 1; 25 | zsl->length = 0; 26 | zsl->header = m_zslCreateNode(ZSKIPLIST_MAXLEVEL, 0, NULL); 27 | for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) { 28 | zsl->header->level[j].forward = NULL; 29 | zsl->header->level[j].span = 0; 30 | } 31 | zsl->header->backward = NULL; 32 | zsl->tail = NULL; 33 | return zsl; 34 | } 35 | 36 | /* Free the specified skiplist node. The referenced SDS string representation 37 | * of the element is freed too, unless node->ele is set to NULL before calling 38 | * this function. */ 39 | void m_zslFreeNode(m_zskiplistNode *node) { 40 | if (node->member) { 41 | RedisModule_FreeString(NULL, node->member); 42 | } 43 | RedisModule_Free(node); 44 | } 45 | 46 | /* Free a whole skiplist. */ 47 | void m_zslFree(m_zskiplist *zsl) { 48 | m_zskiplistNode *node = zsl->header->level[0].forward, *next; 49 | 50 | RedisModule_Free(zsl->header); 51 | while (node) { 52 | next = node->level[0].forward; 53 | m_zslFreeNode(node); 54 | node = next; 55 | } 56 | RedisModule_Free(zsl); 57 | } 58 | 59 | /* Returns a random level for the new skiplist node we are going to create. 60 | * The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL 61 | * (both inclusive), with a powerlaw-alike distribution where higher 62 | * levels are less likely to be returned. */ 63 | int m_zslRandomLevel(void) { 64 | int level = 1; 65 | while ((random() & 0xFFFF) < (ZSKIPLIST_P * 0xFFFF)) 66 | level += 1; 67 | return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL; 68 | } 69 | 70 | /* Insert a new node in the skiplist. Assumes the element does not already 71 | * exist (up to the caller to enforce that). The skiplist takes ownership 72 | * of the passed SDS string 'ele'. */ 73 | m_zskiplistNode *m_zslInsert(m_zskiplist *zsl, long long score, RedisModuleString *member) { 74 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 75 | unsigned int rank[ZSKIPLIST_MAXLEVEL]; 76 | int i, level; 77 | 78 | x = zsl->header; 79 | for (i = zsl->level - 1; i >= 0; i--) { 80 | /* store rank that is crossed to reach the insert position */ 81 | rank[i] = i == (zsl->level - 1) ? 0 : rank[i + 1]; 82 | while (x->level[i].forward && 83 | (x->level[i].forward->score < score || 84 | (x->level[i].forward->score == score && RedisModule_StringCompare(x->level[i].forward->member, member) < 0))) { 85 | rank[i] += x->level[i].span; 86 | x = x->level[i].forward; 87 | } 88 | update[i] = x; 89 | } 90 | 91 | /* we assume the element is not already inside, since we allow duplicated 92 | * scores, reinserting the same element should never happen since the 93 | * caller of m_zslInsert() should test in the hash table if the element is 94 | * already inside or not. */ 95 | level = m_zslRandomLevel(); 96 | if (level > zsl->level) { 97 | for (i = zsl->level; i < level; i++) { 98 | rank[i] = 0; 99 | update[i] = zsl->header; 100 | update[i]->level[i].span = zsl->length; 101 | } 102 | zsl->level = level; 103 | } 104 | x = m_zslCreateNode(level, score, member); 105 | for (i = 0; i < level; i++) { 106 | x->level[i].forward = update[i]->level[i].forward; 107 | update[i]->level[i].forward = x; 108 | 109 | /* update span covered by update[i] as x is inserted here */ 110 | x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]); 111 | update[i]->level[i].span = (rank[0] - rank[i]) + 1; 112 | } 113 | 114 | /* increment span for untouched levels */ 115 | for (i = level; i < zsl->level; i++) { 116 | update[i]->level[i].span++; 117 | } 118 | 119 | x->backward = (update[0] == zsl->header) ? NULL : update[0]; 120 | if (x->level[0].forward) 121 | x->level[0].forward->backward = x; 122 | else 123 | zsl->tail = x; 124 | zsl->length++; 125 | return x; 126 | } 127 | 128 | /* Internal function used by m_zslDelete, zslDeleteByScore and zslDeleteByRank */ 129 | void m_zslDeleteNode(m_zskiplist *zsl, m_zskiplistNode *x, m_zskiplistNode **update) { 130 | int i; 131 | for (i = 0; i < zsl->level; i++) { 132 | if (update[i]->level[i].forward == x) { 133 | update[i]->level[i].span += x->level[i].span - 1; 134 | update[i]->level[i].forward = x->level[i].forward; 135 | } else { 136 | update[i]->level[i].span -= 1; 137 | } 138 | } 139 | if (x->level[0].forward) { 140 | x->level[0].forward->backward = x->backward; 141 | } else { 142 | zsl->tail = x->backward; 143 | } 144 | while (zsl->level > 1 && zsl->header->level[zsl->level - 1].forward == NULL) 145 | zsl->level--; 146 | zsl->length--; 147 | } 148 | 149 | /* Delete an element with matching score/element from the skiplist. 150 | * The function returns 1 if the node was found and deleted, otherwise 151 | * 0 is returned. 152 | * 153 | * If 'node' is NULL the deleted node is freed by m_zslFreeNode(), otherwise 154 | * it is not freed (but just unlinked) and *node is set to the node pointer, 155 | * so that it is possible for the caller to reuse the node (including the 156 | * referenced SDS string at node->ele). */ 157 | int m_zslDelete(m_zskiplist *zsl, long long score, RedisModuleString *member, m_zskiplistNode **node) { 158 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 159 | int i; 160 | 161 | x = zsl->header; 162 | for (i = zsl->level - 1; i >= 0; i--) { 163 | while (x->level[i].forward && 164 | (x->level[i].forward->score < score || 165 | (x->level[i].forward->score == score && RedisModule_StringCompare(x->level[i].forward->member, member) < 0))) { 166 | x = x->level[i].forward; 167 | } 168 | update[i] = x; 169 | } 170 | /* We may have multiple elements with the same score, what we need 171 | * is to find the element with both the right score and object. */ 172 | x = x->level[0].forward; 173 | if (x && score == x->score && RedisModule_StringCompare(x->member, member) == 0) { 174 | m_zslDeleteNode(zsl, x, update); 175 | if (!node) 176 | m_zslFreeNode(x); 177 | else 178 | *node = x; 179 | return 1; 180 | } 181 | return 0; /* not found */ 182 | } 183 | 184 | /* Update the score of an elmenent inside the sorted set skiplist. 185 | * Note that the element must exist and must match 'score'. 186 | * This function does not update the score in the hash table side, the 187 | * caller should take care of it. 188 | * 189 | * Note that this function attempts to just update the node, in case after 190 | * the score update, the node would be exactly at the same position. 191 | * Otherwise the skiplist is modified by removing and re-adding a new 192 | * element, which is more costly. 193 | * 194 | * The function returns the updated element skiplist node pointer. */ 195 | m_zskiplistNode *m_zslUpdateScore(m_zskiplist *zsl, long long curscore, RedisModuleString *member, long long newscore) { 196 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 197 | int i; 198 | 199 | /* We need to seek to element to update to start: this is useful anyway, 200 | * we'll have to update or remove it. */ 201 | x = zsl->header; 202 | for (i = zsl->level - 1; i >= 0; i--) { 203 | while (x->level[i].forward && 204 | (x->level[i].forward->score < curscore || 205 | (x->level[i].forward->score == curscore && RedisModule_StringCompare(x->level[i].forward->member, member) < 0))) { 206 | x = x->level[i].forward; 207 | } 208 | update[i] = x; 209 | } 210 | 211 | /* Jump to our element: note that this function assumes that the 212 | * element with the matching score exists. */ 213 | x = x->level[0].forward; 214 | assert(x && curscore == x->score && RedisModule_StringCompare(x->member, member) == 0); 215 | 216 | /* If the node, after the score update, would be still exactly 217 | * at the same position, we can just update the score without 218 | * actually removing and re-inserting the element in the skiplist. */ 219 | if ((x->backward == NULL || x->backward->score < newscore) && (x->level[0].forward == NULL || x->level[0].forward->score > newscore)) { 220 | x->score = newscore; 221 | return x; 222 | } 223 | 224 | /* No way to reuse the old node: we need to remove and insert a new 225 | * one at a different place. */ 226 | m_zslDeleteNode(zsl, x, update); 227 | m_zskiplistNode *newnode = m_zslInsert(zsl, newscore, x->member); 228 | /* We reused the old node x->ele SDS string, free the node now 229 | * since m_zslInsert created a new one. */ 230 | x->member = NULL; 231 | m_zslFreeNode(x); 232 | return newnode; 233 | } 234 | 235 | int m_zslValueGteMin(long long value, m_zrangespec *spec) { 236 | return spec->minex ? (value > spec->min) : (value >= spec->min); 237 | } 238 | 239 | int m_zslValueLteMax(long long value, m_zrangespec *spec) { 240 | return spec->maxex ? (value < spec->max) : (value <= spec->max); 241 | } 242 | 243 | /* Returns if there is a part of the zset is in range. */ 244 | int zslIsInRange(m_zskiplist *zsl, m_zrangespec *range) { 245 | m_zskiplistNode *x; 246 | 247 | /* Test for ranges that will always be empty. */ 248 | if (range->min > range->max || (range->min == range->max && (range->minex || range->maxex))) 249 | return 0; 250 | x = zsl->tail; 251 | if (x == NULL || !m_zslValueGteMin(x->score, range)) 252 | return 0; 253 | x = zsl->header->level[0].forward; 254 | if (x == NULL || !m_zslValueLteMax(x->score, range)) 255 | return 0; 256 | return 1; 257 | } 258 | 259 | /* Find the first node that is contained in the specified range. 260 | * Returns NULL when no element is contained in the range. */ 261 | m_zskiplistNode *m_zslFirstInRange(m_zskiplist *zsl, m_zrangespec *range) { 262 | m_zskiplistNode *x; 263 | int i; 264 | 265 | /* If everything is out of range, return early. */ 266 | if (!zslIsInRange(zsl, range)) 267 | return NULL; 268 | 269 | x = zsl->header; 270 | for (i = zsl->level - 1; i >= 0; i--) { 271 | /* Go forward while *OUT* of range. */ 272 | while (x->level[i].forward && !m_zslValueGteMin(x->level[i].forward->score, range)) 273 | x = x->level[i].forward; 274 | } 275 | 276 | /* This is an inner range, so the next node cannot be NULL. */ 277 | x = x->level[0].forward; 278 | assert(x != NULL); 279 | 280 | /* Check if score <= max. */ 281 | if (!m_zslValueLteMax(x->score, range)) 282 | return NULL; 283 | return x; 284 | } 285 | 286 | /* Find the last node that is contained in the specified range. 287 | * Returns NULL when no element is contained in the range. */ 288 | m_zskiplistNode *m_zslLastInRange(m_zskiplist *zsl, m_zrangespec *range) { 289 | m_zskiplistNode *x; 290 | int i; 291 | 292 | /* If everything is out of range, return early. */ 293 | if (!zslIsInRange(zsl, range)) 294 | return NULL; 295 | 296 | x = zsl->header; 297 | for (i = zsl->level - 1; i >= 0; i--) { 298 | /* Go forward while *IN* range. */ 299 | while (x->level[i].forward && m_zslValueLteMax(x->level[i].forward->score, range)) 300 | x = x->level[i].forward; 301 | } 302 | 303 | /* This is an inner range, so this node cannot be NULL. */ 304 | assert(x != NULL); 305 | 306 | /* Check if score >= min. */ 307 | if (!m_zslValueGteMin(x->score, range)) 308 | return NULL; 309 | return x; 310 | } 311 | 312 | /* Delete all the elements with score between min and max from the skiplist. 313 | * Min and max are inclusive, so a score >= min || score <= max is deleted. 314 | * Note that this function takes the reference to the hash table view of the 315 | * sorted set, in order to remove the elements from the hash table too. */ 316 | unsigned long m_zslDeleteRangeByScore(m_zskiplist *zsl, m_zrangespec *range) { 317 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 318 | unsigned long removed = 0; 319 | int i; 320 | 321 | x = zsl->header; 322 | for (i = zsl->level - 1; i >= 0; i--) { 323 | while (x->level[i].forward && (range->minex ? x->level[i].forward->score <= range->min : x->level[i].forward->score < range->min)) 324 | x = x->level[i].forward; 325 | update[i] = x; 326 | } 327 | 328 | /* Current node is the last with score < or <= min. */ 329 | x = x->level[0].forward; 330 | 331 | /* Delete nodes while in range. */ 332 | while (x && (range->maxex ? x->score < range->max : x->score <= range->max)) { 333 | m_zskiplistNode *next = x->level[0].forward; 334 | m_zslDeleteNode(zsl, x, update); 335 | m_zslFreeNode(x); /* Here is where x->ele is actually released. */ 336 | removed++; 337 | x = next; 338 | } 339 | return removed; 340 | } 341 | 342 | /* Delete all the elements with rank between start and end from the skiplist. 343 | * Start and end are inclusive. Note that start and end need to be 1-based */ 344 | unsigned long m_zslDeleteRangeByRank(m_zskiplist *zsl, unsigned int start, unsigned int end) { 345 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 346 | unsigned long traversed = 0, removed = 0; 347 | int i; 348 | 349 | x = zsl->header; 350 | for (i = zsl->level - 1; i >= 0; i--) { 351 | while (x->level[i].forward && (traversed + x->level[i].span) < start) { 352 | traversed += x->level[i].span; 353 | x = x->level[i].forward; 354 | } 355 | update[i] = x; 356 | } 357 | 358 | traversed++; 359 | x = x->level[0].forward; 360 | while (x && traversed <= end) { 361 | m_zskiplistNode *next = x->level[0].forward; 362 | m_zslDeleteNode(zsl, x, update); 363 | m_zslFreeNode(x); 364 | removed++; 365 | traversed++; 366 | x = next; 367 | } 368 | return removed; 369 | } 370 | 371 | m_zskiplistNode* m_zslGetElementByRank(m_zskiplist *zsl, unsigned long rank) { 372 | m_zskiplistNode *x; 373 | unsigned long traversed = 0; 374 | int i; 375 | 376 | x = zsl->header; 377 | for (i = zsl->level-1; i >= 0; i--) { 378 | while (x->level[i].forward && (traversed + x->level[i].span) <= rank) 379 | { 380 | traversed += x->level[i].span; 381 | x = x->level[i].forward; 382 | } 383 | if (traversed == rank) { 384 | return x; 385 | } 386 | } 387 | return NULL; 388 | } -------------------------------------------------------------------------------- /dep/skiplist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../src/redismodule.h" 4 | 5 | #define ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */ 6 | #define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */ 7 | 8 | typedef struct { 9 | long long min, max; 10 | int minex, maxex; /* are min or max exclusive? */ 11 | } m_zrangespec; 12 | 13 | typedef struct m_zskiplistNode { 14 | RedisModuleString *member; 15 | long long score; 16 | struct m_zskiplistNode *backward; 17 | struct zskiplistLevel { 18 | struct m_zskiplistNode *forward; 19 | unsigned long span; 20 | } level[]; 21 | } m_zskiplistNode; 22 | 23 | typedef struct m_zskiplist { 24 | struct m_zskiplistNode *header, *tail; 25 | unsigned long length; 26 | int level; 27 | } m_zskiplist; 28 | 29 | m_zskiplist *m_zslCreate(void); 30 | void m_zslFree(m_zskiplist *zsl); 31 | m_zskiplistNode *m_zslInsert(m_zskiplist *zsl, long long score, RedisModuleString *member); 32 | int m_zslDelete(m_zskiplist *zsl, long long score, RedisModuleString *member, m_zskiplistNode **node); 33 | m_zskiplistNode *m_zslFirstInRange(m_zskiplist *zsl, m_zrangespec *range); 34 | m_zskiplistNode *m_zslLastInRange(m_zskiplist *zsl, m_zrangespec *range); 35 | int m_zslValueGteMin(long long value, m_zrangespec *spec); 36 | int m_zslValueLteMax(long long value, m_zrangespec *spec); 37 | void m_zslDeleteNode(m_zskiplist *zsl, m_zskiplistNode *x, m_zskiplistNode **update); 38 | m_zskiplistNode *m_zslUpdateScore(m_zskiplist *zsl, long long curscore, RedisModuleString *member, long long newscore); 39 | m_zskiplistNode* m_zslGetElementByRank(m_zskiplist *zsl, unsigned long rank); 40 | unsigned long m_zslDeleteRangeByRank(m_zskiplist *zsl, unsigned int start, unsigned int end); -------------------------------------------------------------------------------- /dep/tairhash_skiplist.c: -------------------------------------------------------------------------------- 1 | #include "tairhash_skiplist.h" 2 | 3 | #include 4 | #include 5 | /* Create a tairhashskiplist node with specified number of levelss.*/ 6 | tairhash_zskiplistNode *tairhash_zslCreateNode(int level, Slab *slab, long long expire, RedisModuleString *key) { 7 | tairhash_zskiplistNode *zn = (tairhash_zskiplistNode *)RedisModule_Alloc(sizeof(*zn) + level * sizeof(struct tairhash_zskiplistLevel)); 8 | zn->slab = slab; 9 | zn->expire_min = expire; 10 | zn->key_min = key; 11 | zn->backward = NULL; 12 | return zn; 13 | } 14 | 15 | /* Create a new tairhashskiplist. */ 16 | tairhash_zskiplist *tairhash_zslCreate(void) { 17 | tairhash_zskiplist *zsl; 18 | 19 | zsl = (tairhash_zskiplist *)RedisModule_Alloc(sizeof(*zsl)); 20 | zsl->length = 0; 21 | zsl->level = 1; 22 | zsl->header = tairhash_zslCreateNode(TAIRHASH_ZSKIPLIST_MAXLEVEL, NULL, 0, NULL); 23 | for (int j = 0; j < TAIRHASH_ZSKIPLIST_MAXLEVEL; j++) { 24 | zsl->header->level[j].forward = NULL; 25 | zsl->header->level[j].span = 0; 26 | } 27 | zsl->header->backward = NULL; 28 | zsl->tail = NULL; 29 | 30 | return zsl; 31 | } 32 | 33 | void tairhash_zslFreeNode(tairhash_zskiplistNode *node) { 34 | RedisModule_Free(node); 35 | } 36 | 37 | /* Free a whole skiplist. */ 38 | void tairhash_zslFree(tairhash_zskiplist *zsl) { 39 | if (zsl == NULL) 40 | return; 41 | tairhash_zskiplistNode *node = zsl->header->level[0].forward, *next; 42 | 43 | RedisModule_Free(zsl->header); 44 | while (node) { 45 | next = node->level[0].forward; 46 | if (node->slab != NULL) // free slab 47 | slab_delete(node->slab); 48 | 49 | tairhash_zslFreeNode(node); 50 | node = next; 51 | } 52 | RedisModule_Free(zsl); 53 | } 54 | 55 | int tairhash_zslRandomLevel(void) { 56 | int level = 1; 57 | while ((random() & 0xFFFF) < (TAIRHASH_ZSKIPLIST_P * 0xFFFF)) 58 | level += 1; 59 | return (level < TAIRHASH_ZSKIPLIST_MAXLEVEL) ? level : TAIRHASH_ZSKIPLIST_MAXLEVEL; 60 | } 61 | 62 | tairhash_zskiplistNode *tairhash_zslGetNode(tairhash_zskiplist *zsl, RedisModuleString *key_min, long long expire_min) { 63 | tairhash_zskiplistNode *x; 64 | 65 | x = zsl->header; 66 | for (int i = zsl->level - 1; i >= 0; i--) { 67 | while (x->level[i].forward && (x->level[i].forward->expire_min < expire_min || (x->level[i].forward->expire_min == expire_min && RedisModule_StringCompare(x->level[i].forward->key_min, key_min) <= 0))) { 68 | x = x->level[i].forward; 69 | } 70 | } 71 | return x; 72 | } 73 | 74 | tairhash_zskiplistNode *tairhash_zslInsertNode(tairhash_zskiplist *zsl, Slab *slab, RedisModuleString *key_min, long long expire_min) { 75 | tairhash_zskiplistNode *update[TAIRHASH_ZSKIPLIST_MAXLEVEL], *x; 76 | uint32_t rank[TAIRHASH_ZSKIPLIST_MAXLEVEL]; 77 | int i, level; 78 | 79 | x = zsl->header; 80 | for (i = zsl->level - 1; i >= 0; i--) { 81 | rank[i] = i == (zsl->level - 1) ? 0 : rank[i + 1]; 82 | while (x->level[i].forward && (x->level[i].forward->expire_min < expire_min || (x->level[i].forward->expire_min == expire_min && RedisModule_StringCompare(x->level[i].forward->key_min, key_min) < 0))) { 83 | rank[i] += x->level[i].span; 84 | x = x->level[i].forward; 85 | } 86 | update[i] = x; 87 | } 88 | 89 | level = tairhash_zslRandomLevel(); 90 | if (level > zsl->level) { 91 | for (i = zsl->level; i < level; i++) { 92 | rank[i] = 0; 93 | update[i] = zsl->header; 94 | update[i]->level[i].span = zsl->length; 95 | } 96 | zsl->level = level; 97 | } 98 | 99 | x = tairhash_zslCreateNode(level, slab, expire_min, key_min); 100 | 101 | for (i = 0; i < level; i++) { 102 | x->level[i].forward = update[i]->level[i].forward; 103 | update[i]->level[i].forward = x; 104 | 105 | x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]); 106 | update[i]->level[i].span = (rank[0] - rank[i]) + 1; 107 | } 108 | /* increment span for untouched levels */ 109 | for (i = level; i < zsl->level; i++) { 110 | update[i]->level[i].span++; 111 | } 112 | 113 | x->backward = (update[0] == zsl->header) ? NULL : update[0]; 114 | if (x->level[0].forward) 115 | x->level[0].forward->backward = x; 116 | else 117 | zsl->tail = x; 118 | zsl->length++; 119 | return x; 120 | } 121 | 122 | void tairhash_zslDeleteNode(tairhash_zskiplist *zsl, tairhash_zskiplistNode *x, tairhash_zskiplistNode **update) { 123 | for (int i = 0; i < zsl->level; i++) { 124 | if (update[i]->level[i].forward == x) { 125 | update[i]->level[i].span += x->level[i].span - 1; 126 | update[i]->level[i].forward = x->level[i].forward; 127 | } else { 128 | update[i]->level[i].span -= 1; 129 | } 130 | } 131 | if (x->level[0].forward) { 132 | x->level[0].forward->backward = x->backward; 133 | } else { 134 | zsl->tail = x->backward; 135 | } 136 | while (zsl->level > 1 && zsl->header->level[zsl->level - 1].forward == NULL) 137 | zsl->level--; 138 | zsl->length--; 139 | } 140 | 141 | int tairhash_zslDelete(tairhash_zskiplist *zsl, RedisModuleString *key, long long expire) { 142 | tairhash_zskiplistNode *update[TAIRHASH_ZSKIPLIST_MAXLEVEL], *x; 143 | 144 | x = zsl->header; 145 | for (int i = zsl->level - 1; i >= 0; i--) { 146 | while (x->level[i].forward && (x->level[i].forward->expire_min < expire || (x->level[i].forward->expire_min == expire && RedisModule_StringCompare(x->level[i].forward->key_min, key) < 0))) { 147 | x = x->level[i].forward; 148 | } 149 | update[i] = x; 150 | } 151 | x = x->level[0].forward; 152 | 153 | if (x && expire == x->expire_min && RedisModule_StringCompare(x->key_min, key) == 0) { 154 | tairhash_zslDeleteNode(zsl, x, update); 155 | tairhash_zslFreeNode(x); 156 | return 1; 157 | } 158 | return 0; /* not found */ 159 | } 160 | 161 | tairhash_zskiplistNode *tairhash_zslUpdateNode(tairhash_zskiplist *zsl, RedisModuleString *cur_key_min, long long cur_expire_min, RedisModuleString *new_key_min, long long new_expire_min) { 162 | tairhash_zskiplistNode *update[TAIRHASH_ZSKIPLIST_MAXLEVEL], *x, *newnode; 163 | /* We need to seek to element to update to start: this is useful anyway, 164 | * we'll have to update or remove it. */ 165 | x = zsl->header; 166 | for (int i = zsl->level - 1; i >= 0; i--) { 167 | while (x->level[i].forward && (x->level[i].forward->expire_min < cur_expire_min || (x->level[i].forward->expire_min == cur_expire_min && RedisModule_StringCompare(x->level[i].forward->key_min, cur_key_min) < 0))) { 168 | x = x->level[i].forward; 169 | } 170 | update[i] = x; 171 | } 172 | x = x->level[0].forward; 173 | /* Jump to our element: note that this function assumes that the 174 | * element with the matching expire_min. */ 175 | assert(x && cur_expire_min == x->expire_min && RedisModule_StringCompare(x->key_min, cur_key_min) == 0); 176 | 177 | /* If the node, after the expire_min update, would be still exactly 178 | * at the same position, we can just update the expire_min without 179 | * actually removing and re-inserting the element in the skiplist. */ 180 | 181 | if ((x->backward == NULL || x->backward->expire_min < new_expire_min) && (x->level[0].forward == NULL || x->level[0].forward->expire_min > new_expire_min)) { 182 | x->key_min = new_key_min; 183 | x->expire_min = new_expire_min; 184 | return x; 185 | } 186 | 187 | /* No way to reuse the old node: we need to remove and insert a new 188 | * one at a different place. */ 189 | tairhash_zslDeleteNode(zsl, x, update); 190 | newnode = tairhash_zslInsertNode(zsl, x->slab, new_key_min, new_expire_min); 191 | x->key_min = NULL; 192 | tairhash_zslFreeNode(x); 193 | 194 | return newnode; 195 | } 196 | 197 | /* Delete all the elements with rank between start and end from the tairhash_skiplist. 198 | * Start and end are inclusive. Note that start and end need to be 1-based */ 199 | unsigned int tairhash_zslDeleteRangeByRank(tairhash_zskiplist *zsl, unsigned int start, unsigned int end) { 200 | tairhash_zskiplistNode *update[TAIRHASH_ZSKIPLIST_MAXLEVEL], *x; 201 | unsigned long traversed = 0, removed = 0; 202 | 203 | 204 | x = zsl->header; 205 | for (int i = zsl->level - 1; i >= 0; i--) { 206 | while (x->level[i].forward && (traversed + x->level[i].span) < start) { 207 | traversed += x->level[i].span; 208 | x = x->level[i].forward; 209 | } 210 | update[i] = x; 211 | } 212 | 213 | traversed++; 214 | x = x->level[0].forward; 215 | while (x && traversed <= end) { 216 | tairhash_zskiplistNode *next = x->level[0].forward; 217 | tairhash_zslDeleteNode(zsl, x, update); 218 | tairhash_zslFreeNode(x); 219 | removed++; 220 | traversed++; 221 | x = next; 222 | } 223 | return removed; 224 | } -------------------------------------------------------------------------------- /dep/tairhash_skiplist.h: -------------------------------------------------------------------------------- 1 | #ifndef TAIRHASH_SKIPLIST_H 2 | #define TAIRHASH_SKIPLIST_H 3 | 4 | #include 5 | 6 | #include "redismodule.h" 7 | #include "slab.h" 8 | 9 | #define TAIRHASH_ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */ 10 | #define TAIRHASH_ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */ 11 | typedef struct tairhash_zskiplistNode { 12 | Slab *slab; 13 | long long expire_min; 14 | RedisModuleString *key_min; 15 | struct tairhash_zskiplistNode *backward; 16 | struct tairhash_zskiplistLevel { 17 | struct tairhash_zskiplistNode *forward; 18 | unsigned long span; 19 | } level[]; 20 | } tairhash_zskiplistNode; 21 | 22 | typedef struct tairhash_zskiplist { 23 | struct tairhash_zskiplistNode *header, *tail; 24 | unsigned long length; 25 | int level; 26 | } tairhash_zskiplist; 27 | 28 | tairhash_zskiplist *tairhash_zslCreate(void); 29 | tairhash_zskiplistNode *tairhash_zslInsertNode(tairhash_zskiplist *zsl, Slab *slab, RedisModuleString *key_min, long long expire_min); 30 | tairhash_zskiplistNode *tairhash_zslGetNode(tairhash_zskiplist *zsl, RedisModuleString *key_min, long long expire_min); 31 | tairhash_zskiplistNode *tairhash_zslUpdateNode(tairhash_zskiplist *zsl, RedisModuleString *cur_key_min, long long cur_expire_min, RedisModuleString *new_key_min, long long new_expire_min); 32 | int tairhash_zslDelete(tairhash_zskiplist *zsl, RedisModuleString *key, long long expire); 33 | void tairhash_zslFree(tairhash_zskiplist *zsl); 34 | unsigned int tairhash_zslDeleteRangeByRank(tairhash_zskiplist *zsl, unsigned int start, unsigned int end); 35 | #endif -------------------------------------------------------------------------------- /dep/util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | /* Glob-style pattern matching. */ 20 | int m_stringmatchlen(const char *pattern, int patternLen, 21 | const char *string, int stringLen, int nocase) { 22 | while (patternLen && stringLen) { 23 | switch (pattern[0]) { 24 | case '*': 25 | while (pattern[1] == '*') { 26 | pattern++; 27 | patternLen--; 28 | } 29 | if (patternLen == 1) 30 | return 1; /* match */ 31 | while (stringLen) { 32 | if (m_stringmatchlen(pattern + 1, patternLen - 1, 33 | string, stringLen, nocase)) 34 | return 1; /* match */ 35 | string++; 36 | stringLen--; 37 | } 38 | return 0; /* no match */ 39 | break; 40 | case '?': 41 | if (stringLen == 0) 42 | return 0; /* no match */ 43 | string++; 44 | stringLen--; 45 | break; 46 | case '[': { 47 | int not, match; 48 | 49 | pattern++; 50 | patternLen--; 51 | not = pattern[0] == '^'; 52 | if (not ) { 53 | pattern++; 54 | patternLen--; 55 | } 56 | match = 0; 57 | while (1) { 58 | if (pattern[0] == '\\' && patternLen >= 2) { 59 | pattern++; 60 | patternLen--; 61 | if (pattern[0] == string[0]) 62 | match = 1; 63 | } else if (pattern[0] == ']') { 64 | break; 65 | } else if (patternLen == 0) { 66 | pattern--; 67 | patternLen++; 68 | break; 69 | } else if (pattern[1] == '-' && patternLen >= 3) { 70 | int start = pattern[0]; 71 | int end = pattern[2]; 72 | int c = string[0]; 73 | if (start > end) { 74 | int t = start; 75 | start = end; 76 | end = t; 77 | } 78 | if (nocase) { 79 | start = tolower(start); 80 | end = tolower(end); 81 | c = tolower(c); 82 | } 83 | pattern += 2; 84 | patternLen -= 2; 85 | if (c >= start && c <= end) 86 | match = 1; 87 | } else { 88 | if (!nocase) { 89 | if (pattern[0] == string[0]) 90 | match = 1; 91 | } else { 92 | if (tolower((int)pattern[0]) == tolower((int)string[0])) 93 | match = 1; 94 | } 95 | } 96 | pattern++; 97 | patternLen--; 98 | } 99 | if (not ) 100 | match = !match; 101 | if (!match) 102 | return 0; /* no match */ 103 | string++; 104 | stringLen--; 105 | break; 106 | } 107 | case '\\': 108 | if (patternLen >= 2) { 109 | pattern++; 110 | patternLen--; 111 | } 112 | /* fall through */ 113 | default: 114 | if (!nocase) { 115 | if (pattern[0] != string[0]) 116 | return 0; /* no match */ 117 | } else { 118 | if (tolower((int)pattern[0]) != tolower((int)string[0])) 119 | return 0; /* no match */ 120 | } 121 | string++; 122 | stringLen--; 123 | break; 124 | } 125 | pattern++; 126 | patternLen--; 127 | if (stringLen == 0) { 128 | while (*pattern == '*') { 129 | pattern++; 130 | patternLen--; 131 | } 132 | break; 133 | } 134 | } 135 | if (patternLen == 0 && stringLen == 0) 136 | return 1; 137 | return 0; 138 | } 139 | 140 | int m_stringmatch(const char *pattern, const char *string, int nocase) { 141 | return m_stringmatchlen(pattern, strlen(pattern), string, strlen(string), nocase); 142 | } 143 | 144 | /* Convert a string representing an amount of memory into the number of 145 | * bytes, so for instance memtoll("1Gb") will return 1073741824 that is 146 | * (1024*1024*1024). 147 | * 148 | * On parsing error, if *err is not NULL, it's set to 1, otherwise it's 149 | * set to 0. On error the function return value is 0, regardless of the 150 | * fact 'err' is NULL or not. */ 151 | long long m_memtoll(const char *p, int *err) { 152 | const char *u; 153 | char buf[128]; 154 | long mul; /* unit multiplier */ 155 | long long val; 156 | unsigned int digits; 157 | 158 | if (err) *err = 0; 159 | 160 | /* Search the first non digit character. */ 161 | u = p; 162 | if (*u == '-') u++; 163 | while (*u && isdigit(*u)) u++; 164 | if (*u == '\0' || !strcasecmp(u, "b")) { 165 | mul = 1; 166 | } else if (!strcasecmp(u, "k")) { 167 | mul = 1000; 168 | } else if (!strcasecmp(u, "kb")) { 169 | mul = 1024; 170 | } else if (!strcasecmp(u, "m")) { 171 | mul = 1000 * 1000; 172 | } else if (!strcasecmp(u, "mb")) { 173 | mul = 1024 * 1024; 174 | } else if (!strcasecmp(u, "g")) { 175 | mul = 1000L * 1000 * 1000; 176 | } else if (!strcasecmp(u, "gb")) { 177 | mul = 1024L * 1024 * 1024; 178 | } else { 179 | if (err) *err = 1; 180 | return 0; 181 | } 182 | 183 | /* Copy the digits into a buffer, we'll use strtoll() to convert 184 | * the digit (without the unit) into a number. */ 185 | digits = u - p; 186 | if (digits >= sizeof(buf)) { 187 | if (err) *err = 1; 188 | return 0; 189 | } 190 | memcpy(buf, p, digits); 191 | buf[digits] = '\0'; 192 | 193 | char *endptr; 194 | errno = 0; 195 | val = strtoll(buf, &endptr, 10); 196 | if ((val == 0 && errno == EINVAL) || *endptr != '\0') { 197 | if (err) *err = 1; 198 | return 0; 199 | } 200 | return val * mul; 201 | } 202 | 203 | /* Return the number of digits of 'v' when converted to string in radix 10. 204 | * See m_ll2string() for more information. */ 205 | uint32_t m_digits10(uint64_t v) { 206 | if (v < 10) return 1; 207 | if (v < 100) return 2; 208 | if (v < 1000) return 3; 209 | if (v < 1000000000000UL) { 210 | if (v < 100000000UL) { 211 | if (v < 1000000) { 212 | if (v < 10000) return 4; 213 | return 5 + (v >= 100000); 214 | } 215 | return 7 + (v >= 10000000UL); 216 | } 217 | if (v < 10000000000UL) { 218 | return 9 + (v >= 1000000000UL); 219 | } 220 | return 11 + (v >= 100000000000UL); 221 | } 222 | return 12 + m_digits10(v / 1000000000000UL); 223 | } 224 | 225 | /* Like m_digits10() but for signed values. */ 226 | uint32_t m_sdigits10(int64_t v) { 227 | if (v < 0) { 228 | /* Abs value of LLONG_MIN requires special handling. */ 229 | uint64_t uv = (v != LLONG_MIN) ? (uint64_t)-v : ((uint64_t)LLONG_MAX) + 1; 230 | return m_digits10(uv) + 1; /* +1 for the minus. */ 231 | } else { 232 | return m_digits10(v); 233 | } 234 | } 235 | 236 | /* Convert a long long into a string. Returns the number of 237 | * characters needed to represent the number. 238 | * If the buffer is not big enough to store the string, 0 is returned. 239 | * 240 | * Based on the following article (that apparently does not provide a 241 | * novel approach but only publicizes an already used technique): 242 | * 243 | * https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920 244 | * 245 | * Modified in order to handle signed integers since the original code was 246 | * designed for unsigned integers. */ 247 | int m_ll2string(char *dst, size_t dstlen, long long svalue) { 248 | static const char digits[201] = 249 | "0001020304050607080910111213141516171819" 250 | "2021222324252627282930313233343536373839" 251 | "4041424344454647484950515253545556575859" 252 | "6061626364656667686970717273747576777879" 253 | "8081828384858687888990919293949596979899"; 254 | int negative; 255 | unsigned long long value; 256 | 257 | /* The main loop works with 64bit unsigned integers for simplicity, so 258 | * we convert the number here and remember if it is negative. */ 259 | if (svalue < 0) { 260 | if (svalue != LLONG_MIN) { 261 | value = -svalue; 262 | } else { 263 | value = ((unsigned long long)LLONG_MAX) + 1; 264 | } 265 | negative = 1; 266 | } else { 267 | value = svalue; 268 | negative = 0; 269 | } 270 | 271 | /* Check length. */ 272 | uint32_t const length = m_digits10(value) + negative; 273 | if (length >= dstlen) return 0; 274 | 275 | /* Null term. */ 276 | uint32_t next = length; 277 | dst[next] = '\0'; 278 | next--; 279 | while (value >= 100) { 280 | int const i = (value % 100) * 2; 281 | value /= 100; 282 | dst[next] = digits[i + 1]; 283 | dst[next - 1] = digits[i]; 284 | next -= 2; 285 | } 286 | 287 | /* Handle last 1-2 digits. */ 288 | if (value < 10) { 289 | dst[next] = '0' + (uint32_t)value; 290 | } else { 291 | int i = (uint32_t)value * 2; 292 | dst[next] = digits[i + 1]; 293 | dst[next - 1] = digits[i]; 294 | } 295 | 296 | /* Add sign. */ 297 | if (negative) dst[0] = '-'; 298 | return length; 299 | } 300 | 301 | /* Convert a string into a long long. Returns 1 if the string could be parsed 302 | * into a (non-overflowing) long long, 0 otherwise. The value will be set to 303 | * the parsed value when appropriate. 304 | * 305 | * Note that this function demands that the string strictly represents 306 | * a long long: no spaces or other characters before or after the string 307 | * representing the number are accepted, nor zeroes at the start if not 308 | * for the string "0" representing the zero number. 309 | * 310 | * Because of its strictness, it is safe to use this function to check if 311 | * you can convert a string into a long long, and obtain back the string 312 | * from the number without any loss in the string representation. */ 313 | int m_string2ll(const char *s, size_t slen, long long *value) { 314 | const char *p = s; 315 | size_t plen = 0; 316 | int negative = 0; 317 | unsigned long long v; 318 | 319 | /* A zero length string is not a valid number. */ 320 | if (plen == slen) 321 | return 0; 322 | 323 | /* Special case: first and only digit is 0. */ 324 | if (slen == 1 && p[0] == '0') { 325 | if (value != NULL) *value = 0; 326 | return 1; 327 | } 328 | 329 | /* Handle negative numbers: just set a flag and continue like if it 330 | * was a positive number. Later convert into negative. */ 331 | if (p[0] == '-') { 332 | negative = 1; 333 | p++; 334 | plen++; 335 | 336 | /* Abort on only a negative sign. */ 337 | if (plen == slen) 338 | return 0; 339 | } 340 | 341 | /* First digit should be 1-9, otherwise the string should just be 0. */ 342 | if (p[0] >= '1' && p[0] <= '9') { 343 | v = p[0] - '0'; 344 | p++; 345 | plen++; 346 | } else { 347 | return 0; 348 | } 349 | 350 | /* Parse all the other digits, checking for overflow at every step. */ 351 | while (plen < slen && p[0] >= '0' && p[0] <= '9') { 352 | if (v > (ULLONG_MAX / 10)) /* Overflow. */ 353 | return 0; 354 | v *= 10; 355 | 356 | if (v > (ULLONG_MAX - (p[0] - '0'))) /* Overflow. */ 357 | return 0; 358 | v += p[0] - '0'; 359 | 360 | p++; 361 | plen++; 362 | } 363 | 364 | /* Return if not all bytes were used. */ 365 | if (plen < slen) 366 | return 0; 367 | 368 | /* Convert to negative if needed, and do the final overflow check when 369 | * converting from unsigned long long to long long. */ 370 | if (negative) { 371 | if (v > ((unsigned long long)(-(LLONG_MIN + 1)) + 1)) /* Overflow. */ 372 | return 0; 373 | if (value != NULL) *value = -v; 374 | } else { 375 | if (v > LLONG_MAX) /* Overflow. */ 376 | return 0; 377 | if (value != NULL) *value = v; 378 | } 379 | return 1; 380 | } 381 | 382 | /* Convert a string into a long. Returns 1 if the string could be parsed into a 383 | * (non-overflowing) long, 0 otherwise. The value will be set to the parsed 384 | * value when appropriate. */ 385 | int m_string2l(const char *s, size_t slen, long *lval) { 386 | long long llval; 387 | 388 | if (!m_string2ll(s, slen, &llval)) 389 | return 0; 390 | 391 | if (llval < LONG_MIN || llval > LONG_MAX) 392 | return 0; 393 | 394 | *lval = (long)llval; 395 | return 1; 396 | } 397 | 398 | /* Convert a string into a double. Returns 1 if the string could be parsed 399 | * into a (non-overflowing) double, 0 otherwise. The value will be set to 400 | * the parsed value when appropriate. 401 | * 402 | * Note that this function demands that the string strictly represents 403 | * a double: no spaces or other characters before or after the string 404 | * representing the number are accepted. */ 405 | int m_string2ld(const char *s, size_t slen, long double *dp) { 406 | char buf[MAX_LONG_DOUBLE_CHARS]; 407 | long double value; 408 | char *eptr; 409 | 410 | if (slen >= sizeof(buf)) return 0; 411 | memcpy(buf, s, slen); 412 | buf[slen] = '\0'; 413 | 414 | errno = 0; 415 | value = strtold(buf, &eptr); 416 | if (isspace(buf[0]) || eptr[0] != '\0' || (errno == ERANGE && (value == HUGE_VAL || value == -HUGE_VAL || value == 0)) || errno == EINVAL || isnan(value)) 417 | return 0; 418 | 419 | if (dp) *dp = value; 420 | return 1; 421 | } 422 | 423 | /* Convert a double to a string representation. Returns the number of bytes 424 | * required. The representation should always be parsable by strtod(3). 425 | * This function does not support human-friendly formatting like m_ld2string 426 | * does. It is intended mainly to be used inside t_zset.c when writing scores 427 | * into a ziplist representing a sorted set. */ 428 | int m_d2string(char *buf, size_t len, double value) { 429 | if (isnan(value)) { 430 | len = snprintf(buf, len, "nan"); 431 | } else if (isinf(value)) { 432 | if (value < 0) 433 | len = snprintf(buf, len, "-inf"); 434 | else 435 | len = snprintf(buf, len, "inf"); 436 | } else if (value == 0) { 437 | /* See: http://en.wikipedia.org/wiki/Signed_zero, "Comparisons". */ 438 | if (1.0 / value < 0) 439 | len = snprintf(buf, len, "-0"); 440 | else 441 | len = snprintf(buf, len, "0"); 442 | } else { 443 | #if (DBL_MANT_DIG >= 52) && (LLONG_MAX == 0x7fffffffffffffffLL) 444 | /* Check if the float is in a safe range to be casted into a 445 | * long long. We are assuming that long long is 64 bit here. 446 | * Also we are assuming that there are no implementations around where 447 | * double has precision < 52 bit. 448 | * 449 | * Under this assumptions we test if a double is inside an interval 450 | * where casting to long long is safe. Then using two castings we 451 | * make sure the decimal part is zero. If all this is true we use 452 | * integer printing function that is much faster. */ 453 | double min = -4503599627370495; /* (2^52)-1 */ 454 | double max = 4503599627370496; /* -(2^52) */ 455 | if (value > min && value < max && value == ((double)((long long)value))) 456 | len = m_ll2string(buf, len, (long long)value); 457 | else 458 | #endif 459 | len = snprintf(buf, len, "%.17g", value); 460 | } 461 | 462 | return len; 463 | } 464 | 465 | /* Convert a long double into a string. If humanfriendly is non-zero 466 | * it does not use exponential format and trims trailing zeroes at the end, 467 | * however this results in loss of precision. Otherwise exp format is used 468 | * and the output of snprintf() is not modified. 469 | * 470 | * The function returns the length of the string or zero if there was not 471 | * enough buffer room to store it. */ 472 | int m_ld2string(char *buf, size_t len, long double value, int humanfriendly) { 473 | size_t l; 474 | 475 | if (isinf(value)) { 476 | /* Libc in odd systems (Hi Solaris!) will format infinite in a 477 | * different way, so better to handle it in an explicit way. */ 478 | if (len < 5) return 0; /* No room. 5 is "-inf\0" */ 479 | if (value > 0) { 480 | memcpy(buf, "inf", 3); 481 | l = 3; 482 | } else { 483 | memcpy(buf, "-inf", 4); 484 | l = 4; 485 | } 486 | } else if (humanfriendly) { 487 | /* We use 17 digits precision since with 128 bit floats that precision 488 | * after rounding is able to represent most small decimal numbers in a 489 | * way that is "non surprising" for the user (that is, most small 490 | * decimal numbers will be represented in a way that when converted 491 | * back into a string are exactly the same as what the user typed.) */ 492 | l = snprintf(buf, len, "%.17Lf", value); 493 | if (l + 1 > len) return 0; /* No room. */ 494 | /* Now remove trailing zeroes after the '.' */ 495 | if (strchr(buf, '.') != NULL) { 496 | char *p = buf + l - 1; 497 | while (*p == '0') { 498 | p--; 499 | l--; 500 | } 501 | if (*p == '.') l--; 502 | } 503 | } else { 504 | l = snprintf(buf, len, "%.17Lg", value); 505 | if (l + 1 > len) return 0; /* No room. */ 506 | } 507 | buf[l] = '\0'; 508 | return l; 509 | } -------------------------------------------------------------------------------- /dep/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012, Salvatore Sanfilippo 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef __UTIL_H_ 31 | #define __UTIL_H_ 32 | 33 | #include 34 | #include 35 | 36 | /* The maximum number of characters needed to represent a long double 37 | * as a string (long double has a huge range). 38 | * This should be the size of the buffer given to ld2string */ 39 | #define MAX_LONG_DOUBLE_CHARS 5 * 1024 40 | 41 | int m_stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase); 42 | int m_stringmatch(const char *p, const char *s, int nocase); 43 | int m_stringmatchlen_fuzz_test(void); 44 | long long m_memtoll(const char *p, int *err); 45 | uint32_t m_digits10(uint64_t v); 46 | uint32_t m_sdigits10(int64_t v); 47 | int m_ll2string(char *s, size_t len, long long value); 48 | int m_string2ll(const char *s, size_t slen, long long *value); 49 | int m_string2l(const char *s, size_t slen, long *value); 50 | int m_string2ld(const char *s, size_t slen, long double *dp); 51 | int m_d2string(char *buf, size_t len, double value); 52 | int m_ld2string(char *buf, size_t len, long double value, int humanfriendly); 53 | 54 | #endif -------------------------------------------------------------------------------- /imgs/tairhash_index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tair-opensource/TairHash/4dbecf3f79c2202f4dc11c60e4d8d50ef8a1672e/imgs/tairhash_index.png -------------------------------------------------------------------------------- /imgs/tairhash_index2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tair-opensource/TairHash/4dbecf3f79c2202f4dc11c60e4d8d50ef8a1672e/imgs/tairhash_index2.png -------------------------------------------------------------------------------- /imgs/tairhash_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tair-opensource/TairHash/4dbecf3f79c2202f4dc11c60e4d8d50ef8a1672e/imgs/tairhash_logo.jpg -------------------------------------------------------------------------------- /imgs/tairhash_memusage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tair-opensource/TairHash/4dbecf3f79c2202f4dc11c60e4d8d50ef8a1672e/imgs/tairhash_memusage.png -------------------------------------------------------------------------------- /imgs/tairhash_rps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tair-opensource/TairHash/4dbecf3f79c2202f4dc11c60e4d8d50ef8a1672e/imgs/tairhash_rps.png -------------------------------------------------------------------------------- /imgs/tairhash_slab_mode_index.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tair-opensource/TairHash/4dbecf3f79c2202f4dc11c60e4d8d50ef8a1672e/imgs/tairhash_slab_mode_index.jpg -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET tairhash_module) 2 | 3 | FILE(GLOB_RECURSE SRCS 4 | ${CMAKE_CURRENT_SOURCE_DIR}/*.c 5 | ${CMAKE_CURRENT_SOURCE_DIR}/*.h 6 | ) 7 | add_library(${TARGET} SHARED ${SRCS} ${USRC}) 8 | 9 | set_target_properties(${TARGET} PROPERTIES SUFFIX ".so") 10 | set_target_properties(${TARGET} PROPERTIES PREFIX "") -------------------------------------------------------------------------------- /src/scan_algorithm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "tairhash.h" 17 | 18 | #if (!defined SORT_MODE) && (!defined SLAB_MODE) 19 | 20 | extern ExpireAlgorithm g_expire_algorithm; 21 | extern RedisModuleType *TairHashType; 22 | 23 | void insert(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire) { 24 | REDISMODULE_NOT_USED(ctx); 25 | REDISMODULE_NOT_USED(dbid); 26 | REDISMODULE_NOT_USED(key); 27 | if (expire) { 28 | m_zslInsert(obj->expire_index, expire, takeAndRef(field)); 29 | } 30 | } 31 | 32 | void update(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long cur_expire, long long new_expire) { 33 | REDISMODULE_NOT_USED(ctx); 34 | REDISMODULE_NOT_USED(dbid); 35 | REDISMODULE_NOT_USED(key); 36 | if (cur_expire != new_expire) { 37 | m_zslUpdateScore(obj->expire_index, cur_expire, field, new_expire); 38 | } 39 | } 40 | 41 | void delete(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long cur_expire) { 42 | REDISMODULE_NOT_USED(ctx); 43 | REDISMODULE_NOT_USED(dbid); 44 | REDISMODULE_NOT_USED(key); 45 | if (cur_expire != 0) { 46 | m_zslDelete(obj->expire_index, cur_expire, field, NULL); 47 | } 48 | } 49 | 50 | void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys_per_loop) { 51 | tairHashObj *tair_hash_obj = NULL; 52 | int start_index; 53 | m_zskiplistNode *ln = NULL; 54 | m_zskiplistNode *ln2 = NULL; 55 | 56 | RedisModuleString *key, *field; 57 | RedisModuleKey *real_key; 58 | int may_delkey = 0; 59 | 60 | long long when, now; 61 | unsigned long zsl_len; 62 | 63 | long long start = RedisModule_Milliseconds(); 64 | 65 | list *keys = m_listCreate(); 66 | /* Each db has its own cursor, but this value may be wrong when swapdb appears (because we do not have a callback notification), 67 | * But this will not cause serious problems. */ 68 | static long long scan_cursor[DB_NUM] = {0}; 69 | RedisModuleCallReply *reply = RedisModule_Call(ctx, "SCAN", "lcl", scan_cursor[dbid], "COUNT", g_expire_algorithm.keys_per_active_loop); 70 | if (reply != NULL) { 71 | switch (RedisModule_CallReplyType(reply)) { 72 | case REDISMODULE_REPLY_ARRAY: { 73 | Module_Assert(RedisModule_CallReplyLength(reply) == 2); 74 | 75 | RedisModuleCallReply *cursor_reply = RedisModule_CallReplyArrayElement(reply, 0); 76 | Module_Assert(RedisModule_CallReplyType(cursor_reply) == REDISMODULE_REPLY_STRING); 77 | Module_Assert(RedisModule_StringToLongLong(RedisModule_CreateStringFromCallReply(cursor_reply), &scan_cursor[dbid]) == REDISMODULE_OK); 78 | 79 | RedisModuleCallReply *keys_reply = RedisModule_CallReplyArrayElement(reply, 1); 80 | Module_Assert(RedisModule_CallReplyType(keys_reply) == REDISMODULE_REPLY_ARRAY); 81 | size_t keynum = RedisModule_CallReplyLength(keys_reply); 82 | 83 | for (int j = 0; j < keynum; j++) { 84 | RedisModuleCallReply *key_reply = RedisModule_CallReplyArrayElement(keys_reply, j); 85 | Module_Assert(RedisModule_CallReplyType(key_reply) == REDISMODULE_REPLY_STRING); 86 | key = RedisModule_CreateStringFromCallReply(key_reply); 87 | real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_OPEN_KEY_NOTOUCH); 88 | /* Since RedisModule_KeyType does not deal with the stream type, it is possible to 89 | return REDISMODULE_KEYTYPE_EMPTY here, so we must deal with it until after this 90 | bugfix: https://github.com/redis/redis/commit/1833d008b3af8628835b5f082c5b4b1359557893 */ 91 | if (RedisModule_KeyType(real_key) == REDISMODULE_KEYTYPE_EMPTY) { 92 | RedisModule_CloseKey(real_key); 93 | continue; 94 | } 95 | 96 | if (RedisModule_ModuleTypeGetType(real_key) == TairHashType) { 97 | tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key); 98 | if (tair_hash_obj->expire_index->length > 0) { 99 | m_listAddNodeTail(keys, key); 100 | } 101 | } 102 | RedisModule_CloseKey(real_key); 103 | } 104 | break; 105 | } 106 | default: 107 | /* impossible */ 108 | break; 109 | } 110 | } 111 | 112 | if (listLength(keys) == 0) { 113 | m_listRelease(keys); 114 | return; 115 | } 116 | 117 | /* 3. Delete expired field. */ 118 | int expire_keys_per_loop = g_expire_algorithm.keys_per_active_loop; 119 | m_listNode *node; 120 | while ((node = listFirst(keys)) != NULL) { 121 | key = listNodeValue(node); 122 | may_delkey = 0; 123 | real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_WRITE | REDISMODULE_OPEN_KEY_NOTOUCH); 124 | int type = RedisModule_KeyType(real_key); 125 | if (type != REDISMODULE_KEYTYPE_EMPTY) { 126 | Module_Assert(RedisModule_ModuleTypeGetType(real_key) == TairHashType); 127 | } else { 128 | /* Note: redis scan may return dup key. */ 129 | m_listDelNode(keys, node); 130 | RedisModule_CloseKey(real_key); 131 | continue; 132 | } 133 | 134 | mstime_t key_ttl = RedisModule_GetExpire(real_key); 135 | if (key_ttl != REDISMODULE_NO_EXPIRE && key_ttl < 1000) { // 136 | may_delkey = 1; 137 | } 138 | 139 | tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key); 140 | if (dictSize(tair_hash_obj->hash) == 1) { 141 | may_delkey = 1; 142 | } 143 | RedisModule_CloseKey(real_key); 144 | 145 | zsl_len = tair_hash_obj->expire_index->length; 146 | Module_Assert(zsl_len > 0); 147 | 148 | ln2 = tair_hash_obj->expire_index->header->level[0].forward; 149 | start_index = 0; 150 | while (ln2 && expire_keys_per_loop) { 151 | field = ln2->member; 152 | if (fieldExpireIfNeeded(ctx, dbid, key, tair_hash_obj, field, 1)) { 153 | g_expire_algorithm.stat_active_expired_field[dbid]++; 154 | start_index++; 155 | expire_keys_per_loop--; 156 | if (may_delkey) { 157 | break; 158 | } 159 | } else { 160 | break; 161 | } 162 | ln2 = ln2->level[0].forward; 163 | } 164 | 165 | // If the key happens to expire, it will be released in fieldExpireIfNeeded. 166 | if (may_delkey) { 167 | real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_OPEN_KEY_NOTOUCH); 168 | int type = RedisModule_KeyType(real_key); 169 | if (type == REDISMODULE_KEYTYPE_EMPTY) { 170 | m_listDelNode(keys, node); 171 | RedisModule_CloseKey(real_key); 172 | continue; 173 | } 174 | RedisModule_CloseKey(real_key); 175 | } 176 | 177 | if (start_index) { 178 | m_zslDeleteRangeByRank(tair_hash_obj->expire_index, 1, start_index); 179 | delEmptyTairHashIfNeeded(ctx, NULL, key, tair_hash_obj); 180 | } 181 | m_listDelNode(keys, node); 182 | } 183 | m_listRelease(keys); 184 | } 185 | 186 | void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key) { 187 | tairHashObj *tair_hash_obj = NULL; 188 | long long when, now; 189 | int start_index = 0; 190 | m_zskiplistNode *ln = NULL; 191 | 192 | RedisModuleString *field; 193 | RedisModuleKey *real_key; 194 | unsigned long zsl_len; 195 | int may_delkey = 0; 196 | /* 1. The current db does not have a key that needs to expire. */ 197 | list *keys = m_listCreate(); 198 | 199 | real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_WRITE); 200 | if (RedisModule_KeyType(real_key) != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(real_key) == TairHashType) { 201 | tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key); 202 | if (tair_hash_obj->expire_index->length > 0) { 203 | m_listAddNodeTail(keys, key); 204 | } 205 | } 206 | RedisModule_CloseKey(real_key); 207 | 208 | if (listLength(keys) == 0) { 209 | m_listRelease(keys); 210 | return; 211 | } 212 | 213 | /* 3. Delete expired field. */ 214 | int keys_per_loop = g_expire_algorithm.keys_per_passive_loop; 215 | m_listNode *node; 216 | while ((node = listFirst(keys)) != NULL) { 217 | key = listNodeValue(node); 218 | may_delkey = 0; 219 | real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_WRITE); 220 | int type = RedisModule_KeyType(real_key); 221 | 222 | Module_Assert(type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(real_key) == TairHashType); 223 | tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key); 224 | 225 | zsl_len = tair_hash_obj->expire_index->length; 226 | Module_Assert(zsl_len > 0); 227 | 228 | mstime_t key_ttl = RedisModule_GetExpire(real_key); 229 | if (key_ttl != REDISMODULE_NO_EXPIRE && key_ttl < 100) { // 100ms 230 | may_delkey = 1; 231 | } 232 | 233 | tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key); 234 | if (dictSize(tair_hash_obj->hash) == 1) { 235 | may_delkey = 1; 236 | } 237 | RedisModule_CloseKey(real_key); 238 | 239 | start_index = 0; 240 | ln = tair_hash_obj->expire_index->header->level[0].forward; 241 | while (ln && keys_per_loop) { 242 | field = ln->member; 243 | if (fieldExpireIfNeeded(ctx, dbid, key, tair_hash_obj, field, 0)) { 244 | g_expire_algorithm.stat_passive_expired_field[dbid]++; 245 | start_index++; 246 | keys_per_loop--; 247 | if (may_delkey) { 248 | break; 249 | } 250 | } else { 251 | break; 252 | } 253 | ln = ln->level[0].forward; 254 | } 255 | 256 | if (may_delkey) { 257 | real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_OPEN_KEY_NOTOUCH); 258 | int type = RedisModule_KeyType(real_key); 259 | if (type == REDISMODULE_KEYTYPE_EMPTY) { 260 | m_listDelNode(keys, node); 261 | RedisModule_CloseKey(real_key); 262 | continue; 263 | } 264 | RedisModule_CloseKey(real_key); 265 | } 266 | 267 | if (start_index) { 268 | m_zslDeleteRangeByRank(tair_hash_obj->expire_index, 1, start_index); 269 | delEmptyTairHashIfNeeded(ctx, NULL, key, tair_hash_obj); 270 | } 271 | m_listDelNode(keys, node); 272 | } 273 | 274 | m_listRelease(keys); 275 | } 276 | 277 | void deleteAndPropagate(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire, int is_timer) { 278 | if (is_timer) { 279 | /* See bugfix: https://github.com/redis/redis/pull/8617 280 | https://github.com/redis/redis/pull/8097 281 | https://github.com/redis/redis/pull/7037 282 | */ 283 | RedisModuleCtx *ctx2 = RedisModule_GetThreadSafeContext(NULL); 284 | RedisModule_SelectDb(ctx2, dbid); 285 | notifyFieldSpaceEvent("expired", key, field, dbid); 286 | RedisModuleCallReply *reply = RedisModule_Call(ctx2, "EXHDELREPL", "ss!", key, field); 287 | if (reply != NULL) { 288 | RedisModule_FreeCallReply(reply); 289 | } 290 | RedisModule_FreeThreadSafeContext(ctx2); 291 | } else { 292 | RedisModuleString *key_dup = RedisModule_CreateStringFromString(NULL, key); 293 | RedisModuleString *field_dup = RedisModule_CreateStringFromString(NULL, field); 294 | m_zslDelete(obj->expire_index, expire, field_dup, NULL); 295 | m_dictDelete(obj->hash, field); 296 | RedisModule_Replicate(ctx, "EXHDEL", "ss", key_dup, field_dup); 297 | notifyFieldSpaceEvent("expired", key_dup, field_dup, dbid); 298 | RedisModule_FreeString(NULL, key_dup); 299 | RedisModule_FreeString(NULL, field_dup); 300 | } 301 | } 302 | 303 | #endif 304 | -------------------------------------------------------------------------------- /src/scan_algorithm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #if (!defined SORT_MODE) && (!defined SLAB_MODE) 19 | 20 | void insert(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire); 21 | void update(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long cur_expire, long long new_expire); 22 | void delete(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire); 23 | void deleteAndPropagate(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire, int is_timer); 24 | void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys); 25 | void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key_per_loop); 26 | 27 | #endif -------------------------------------------------------------------------------- /src/slab.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "slab.h" 17 | 18 | #include 19 | #include 20 | 21 | #include "util.h" 22 | 23 | /* create slab */ 24 | Slab *slab_createNode(void) { 25 | Slab *slab = (Slab *)RedisModule_Alloc(sizeof(Slab)); 26 | for (int i = 0; i < SLABMAXN; i++) { 27 | slab->keys[i] = NULL; 28 | slab->expires[i] = 0; 29 | } 30 | slab->num_keys = 0; 31 | 32 | return slab; 33 | } 34 | 35 | /*insert to the slab tail */ 36 | int slab_insertNode(Slab *slab, RedisModuleString *key, long long expire) { 37 | if (slab->num_keys >= SLABMAXN) { 38 | return FALSE; 39 | } 40 | int i = slab->num_keys; 41 | slab->expires[i] = expire, slab->keys[i] = key, slab->num_keys++; 42 | return TRUE; 43 | } 44 | 45 | /* if return value -1 is not found ,else the target position */ 46 | int slab_getNode(Slab *slab, RedisModuleString *key, long long expire) { 47 | if (slab == NULL) return -1; 48 | size_t key_len; 49 | 50 | int target_position = -1, num_keys = slab->num_keys; 51 | if (key == NULL) { // key is null 52 | return target_position; 53 | } 54 | 55 | for (int i = 0; i < num_keys; i++) { 56 | if (slab->expires[i] == expire && RedisModule_StringCompare(key, slab->keys[i]) == 0) { 57 | target_position = i; 58 | break; 59 | } 60 | } 61 | return target_position; 62 | } 63 | 64 | /* slab delete index node*/ 65 | int slab_deleteIndexNode(Slab *slab, int index) { 66 | if (index < 0 || slab->num_keys <= 0 || index >= slab->num_keys) { 67 | return FALSE; 68 | } 69 | int end = slab->num_keys - 1; 70 | RedisModule_FreeString(NULL, slab->keys[index]); 71 | if (end != index) { 72 | slab->keys[index] = slab->keys[end], slab->expires[index] = slab->expires[end]; 73 | } 74 | slab->expires[end] = 0, slab->keys[end] = NULL, slab->num_keys--; 75 | return TRUE; 76 | } 77 | 78 | int slab_deleteNode(Slab *slab, RedisModuleString *key, long long expire) { 79 | int target_position = slab_getNode(slab, key, expire); 80 | return slab_deleteIndexNode(slab, target_position); 81 | } 82 | 83 | int slab_updateNode(Slab *slab, RedisModuleString *cur_key, long long cur_expire, RedisModuleString *new_key, long long new_expire) { 84 | int target_position = slab_getNode(slab, cur_key, cur_expire); 85 | 86 | if (target_position < 0 || target_position >= SLABMAXN) { 87 | return FALSE; 88 | } 89 | 90 | RedisModule_FreeString(NULL, slab->keys[target_position]); 91 | slab->keys[target_position] = new_key, slab->expires[target_position] = new_expire; 92 | 93 | return TRUE; 94 | } 95 | 96 | /* free slab */ 97 | void slab_delete(Slab *slab) { 98 | if (slab == NULL) return; 99 | for (int i = 0; i < slab->num_keys; i++) { 100 | RedisModule_FreeString(NULL, slab->keys[i]); 101 | } 102 | RedisModule_Free(slab); 103 | } 104 | 105 | /* get the smallest element ssubscript */ 106 | int slab_minExpireTimeIndex(Slab *slab) { 107 | int min_subscript = 0, length = slab->num_keys; 108 | for (int i = 1; i < length; i++) { 109 | if (slab->expires[min_subscript] > slab->expires[i] || (slab->expires[min_subscript] == slab->expires[i] && RedisModule_StringCompare(slab->keys[min_subscript], slab->keys[i]) > 0)) { 110 | min_subscript = i; 111 | } 112 | } 113 | return min_subscript; 114 | } 115 | 116 | int slab_getExpiredKeyIndices(Slab *slab, long long target_ttl, int *out_indices) { 117 | if (slab == NULL || slab->num_keys == 0) 118 | return 0; 119 | 120 | int size_out = 0, size = slab->num_keys; 121 | for (int i = 0; i < size; i++) { 122 | out_indices[size_out] = i; 123 | size_out += (slab->expires[i] <= target_ttl); 124 | } 125 | return size_out; 126 | } 127 | 128 | inline void slab_swap(Slab *slab, int left, int right) { 129 | long long temp_expire = slab->expires[left]; 130 | RedisModuleString *temp_key = slab->keys[left]; 131 | slab->expires[left] = slab->expires[right], slab->keys[left] = slab->keys[right]; 132 | slab->expires[right] = temp_expire, slab->keys[right] = temp_key; 133 | return; 134 | } -------------------------------------------------------------------------------- /src/slab.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #ifndef SLAB_H 17 | #define SLAB_H 18 | 19 | #include 20 | #include 21 | 22 | #include "redismodule.h" 23 | 24 | #define SLABMAXN 512 /* Skiplist P = 1/4 */ 25 | #define FALSE 0 26 | #define TRUE 1 27 | typedef struct Slab { 28 | long long expires[SLABMAXN]; // field_value_expire 29 | RedisModuleString *keys[SLABMAXN]; // field 30 | uint16_t num_keys; 31 | } Slab; 32 | 33 | Slab *slab_createNode(void); 34 | int slab_insertNode(Slab *slab, RedisModuleString *key, long long expire); 35 | int slab_getNode(Slab *slab, RedisModuleString *key, long long expire); 36 | int slab_deleteIndexNode(Slab *slab, int index); 37 | int slab_deleteNode(Slab *slab, RedisModuleString *key, long long expire); 38 | int slab_updateNode(Slab *slab, RedisModuleString *cur_key, long long cur_expire, RedisModuleString *new_key, long long new_expire); 39 | void slab_delete(Slab *slab); 40 | int slab_minExpireTimeIndex(Slab *slab); 41 | int slab_getExpiredKeyIndices(Slab *slab, long long target_ttl, int *out_indices); 42 | void slab_swap(Slab *slab, int left, int right); 43 | #endif -------------------------------------------------------------------------------- /src/slab_algorithm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "tairhash.h" 17 | 18 | #if defined(SLAB_MODE) 19 | extern ExpireAlgorithm g_expire_algorithm; 20 | extern m_zskiplist *g_expire_index[DB_NUM]; 21 | extern RedisModuleType *TairHashType; 22 | 23 | int ontime_indices[SLABMAXN], timeout_indices[SLABMAXN]; 24 | 25 | void insert(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *o, RedisModuleString *field, long long expire) { 26 | REDISMODULE_NOT_USED(ctx); 27 | REDISMODULE_NOT_USED(key); 28 | if (expire) { 29 | long long before_min_score = -1, after_min_score = -1; 30 | if (o->expire_index->header->level[0].forward) { 31 | before_min_score = o->expire_index->header->level[0].forward->expire_min; 32 | } 33 | slab_expireInsert(o->expire_index, takeAndRef(field), expire); 34 | after_min_score = o->expire_index->header->level[0].forward->expire_min; 35 | if (before_min_score > 0) { 36 | m_zslUpdateScore(g_expire_index[dbid], before_min_score, o->key, after_min_score); 37 | } else { 38 | m_zslInsert(g_expire_index[dbid], after_min_score, takeAndRef(o->key)); 39 | } 40 | } 41 | } 42 | 43 | void update(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *o, RedisModuleString *field, long long cur_expire, long long new_expire) { 44 | REDISMODULE_NOT_USED(ctx); 45 | REDISMODULE_NOT_USED(key); 46 | if (cur_expire != new_expire) { 47 | long long before_min_score = -1, after_min_score = 1; 48 | tairhash_zskiplistNode *ln = o->expire_index->header->level[0].forward; 49 | Module_Assert(ln != NULL); 50 | before_min_score = ln->expire_min; 51 | RedisModuleString *new_field = takeAndRef(field); 52 | slab_expireUpdate(o->expire_index, field, cur_expire, new_field, new_expire); 53 | after_min_score = o->expire_index->header->level[0].forward->expire_min; 54 | m_zslUpdateScore(g_expire_index[dbid], before_min_score, o->key, after_min_score); 55 | } 56 | } 57 | 58 | void delete(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *o, RedisModuleString *field, long long cur_expire) { 59 | REDISMODULE_NOT_USED(ctx); 60 | REDISMODULE_NOT_USED(key); 61 | if (cur_expire != 0) { 62 | long long before_min_score = -1; 63 | tairhash_zskiplistNode *ln = o->expire_index->header->level[0].forward; 64 | Module_Assert(ln != NULL); 65 | before_min_score = ln->expire_min; 66 | slab_expireDelete(o->expire_index, field, cur_expire); 67 | if (o->expire_index->header->level[0].forward) { 68 | long long after_min_score = o->expire_index->header->level[0].forward->expire_min; 69 | m_zslUpdateScore(g_expire_index[dbid], before_min_score, key, after_min_score); 70 | } else { 71 | m_zslDelete(g_expire_index[dbid], before_min_score, key, NULL); 72 | } 73 | } 74 | } 75 | 76 | void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys_per_loop) { 77 | tairHashObj *tair_hash_obj = NULL; 78 | int start_index; 79 | m_zskiplistNode *ln = NULL; 80 | tairhash_zskiplistNode *ln2 = NULL; 81 | RedisModuleString *key, *field; 82 | RedisModuleKey *real_key; 83 | 84 | long long when, now; 85 | unsigned long zsl_len; 86 | 87 | int expire_keys_per_loop = keys_per_loop; 88 | list *keys = m_listCreate(); 89 | 90 | /* 1. The current db does not have a key that needs to expire. */ 91 | zsl_len = g_expire_index[dbid]->length; 92 | if (zsl_len == 0) { 93 | m_listRelease(keys); 94 | return; 95 | } 96 | 97 | /* 2. Enumerates expired keys. */ 98 | ln = g_expire_index[dbid]->header->level[0].forward; 99 | start_index = 0; 100 | while (ln && expire_keys_per_loop--) { 101 | key = ln->member; 102 | when = ln->score; 103 | now = RedisModule_Milliseconds(); 104 | if (when > now) { 105 | break; 106 | } 107 | start_index++; 108 | m_listAddNodeTail(keys, key); 109 | ln = ln->level[0].forward; 110 | } 111 | 112 | if (start_index) { 113 | /* It is assumed that these keys will all be deleted. */ 114 | m_zslDeleteRangeByRank(g_expire_index[dbid], 1, start_index); 115 | } 116 | 117 | if (listLength(keys) == 0) { 118 | m_listRelease(keys); 119 | return; 120 | } 121 | 122 | /* SLAB_MODE:3. Delete expired field. */ 123 | expire_keys_per_loop = keys_per_loop; 124 | m_listNode *node; 125 | int ontime_num = 0, timeout_num = 0, timeout_index = 0, delete_rank = 0, j; 126 | while ((node = listFirst(keys)) != NULL) { 127 | key = listNodeValue(node); 128 | real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_WRITE | REDISMODULE_OPEN_KEY_NOTOUCH); 129 | int type = RedisModule_KeyType(real_key); 130 | if (type != REDISMODULE_KEYTYPE_EMPTY) { 131 | Module_Assert(RedisModule_ModuleTypeGetType(real_key) == TairHashType); 132 | } else { 133 | m_listDelNode(keys, node); 134 | continue; 135 | } 136 | tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key); 137 | 138 | zsl_len = tair_hash_obj->expire_index->length; 139 | Module_Assert(zsl_len > 0); 140 | 141 | ln2 = tair_hash_obj->expire_index->header->level[0].forward; 142 | start_index = 0, delete_rank = 0; 143 | long long start_active_expire_timer = RedisModule_Milliseconds(); 144 | while (ln2 && expire_keys_per_loop > 0) { 145 | if (ln2->level[0].forward != NULL && isExpire(ln2->level[0].forward->expire_min)) { 146 | timeout_num = ln2->slab->num_keys; 147 | ontime_num = 0; 148 | } else { 149 | timeout_num = slab_getSlabTimeoutExpireIndex(ln2, ontime_indices, timeout_indices); 150 | ontime_num = ln2->slab->num_keys - timeout_num; 151 | if (timeout_num <= 0) 152 | break; 153 | } 154 | 155 | for (j = 0; j < timeout_num; j++) { 156 | if (ontime_num == 0) { 157 | timeout_index = j; 158 | } else { 159 | timeout_index = timeout_indices[j]; 160 | } 161 | fieldExpireIfNeeded(ctx, dbid, key, tair_hash_obj, ln2->slab->keys[timeout_index], 1); 162 | g_expire_algorithm.stat_active_expired_field[dbid]++; 163 | start_index++; 164 | expire_keys_per_loop--; 165 | } 166 | 167 | if (ontime_num == 0) { 168 | delete_rank++; 169 | } else { 170 | break; 171 | } 172 | ln2 = ln2->level[0].forward; 173 | } 174 | 175 | if (delete_rank) { 176 | slab_deleteTairhashRangeByRank(tair_hash_obj->expire_index, 1, delete_rank); 177 | } 178 | if (tair_hash_obj->expire_index->length > 0 && ontime_num > 0 && timeout_num > 0) { 179 | slab_deleteSlabExpire(tair_hash_obj->expire_index, tair_hash_obj->expire_index->header->level[0].forward, ontime_indices, ontime_num); 180 | } 181 | 182 | if (tair_hash_obj->expire_index->length > 0 && start_index) { 183 | m_zslInsert(g_expire_index[dbid], tair_hash_obj->expire_index->header->level[0].forward->expire_min, takeAndRef(tair_hash_obj->key)); 184 | } 185 | if (start_index) { 186 | delEmptyTairHashIfNeeded(ctx, real_key, key, tair_hash_obj); 187 | } 188 | 189 | m_listDelNode(keys, node); 190 | start_index = 0; 191 | } 192 | m_listRelease(keys); 193 | } 194 | 195 | void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key) { 196 | REDISMODULE_NOT_USED(ctx); 197 | REDISMODULE_NOT_USED(dbid); 198 | REDISMODULE_NOT_USED(key); 199 | /* Not support. */ 200 | } 201 | 202 | void deleteAndPropagate(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *o, RedisModuleString *field, long long expire, int is_timer) { 203 | RedisModuleString *key_dup = RedisModule_CreateStringFromString(NULL, key); 204 | RedisModuleString *field_dup = RedisModule_CreateStringFromString(NULL, field); 205 | if (!is_timer) { 206 | long long before_min_score = o->expire_index->header->level[0].forward->expire_min; 207 | slab_expireDelete(o->expire_index, field_dup, expire); 208 | if (o->expire_index->header->level[0].forward != NULL) { 209 | long long after_min_score = o->expire_index->header->level[0].forward->expire_min; 210 | m_zslUpdateScore(g_expire_index[dbid], before_min_score, key, after_min_score); 211 | } else { 212 | m_zslDelete(g_expire_index[dbid], before_min_score, key, NULL); 213 | } 214 | } 215 | m_dictDelete(o->hash, field); 216 | RedisModule_Replicate(ctx, "EXHDEL", "ss", key_dup, field_dup); 217 | notifyFieldSpaceEvent("expired", key_dup, field_dup, dbid); 218 | RedisModule_FreeString(NULL, key_dup); 219 | RedisModule_FreeString(NULL, field_dup); 220 | } 221 | 222 | #endif -------------------------------------------------------------------------------- /src/slab_algorithm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #if defined(SLAB_MODE) 19 | void insert(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire); 20 | void update(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long cur_expire, long long new_expire); 21 | void delete(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire); 22 | void deleteAndPropagate(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire, int is_timer); 23 | void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys); 24 | void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key_per_loop); 25 | #endif -------------------------------------------------------------------------------- /src/slabapi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "slabapi.h" 17 | 18 | #ifdef __AVX2__ 19 | #include 20 | #include 21 | #endif 22 | #include 23 | #include 24 | #include 25 | 26 | #include "assert.h" 27 | #include "tairhash_skiplist.h" 28 | 29 | #define RELAXATION 10 30 | #ifdef __AVX2__ 31 | __m256i shuffle_timeout_mask_4x64[16], shuffle_ontime_mask_4x64[16]; 32 | #endif 33 | 34 | int partition(Slab *slab, int low, int high) { 35 | long long expire = slab->expires[low]; 36 | RedisModuleString *key = slab->keys[low]; 37 | while (low < high) { 38 | while (low < high && (slab->expires[high] > expire || (slab->expires[high] == expire && RedisModule_StringCompare(slab->keys[high], key) >= 0))) 39 | high--; 40 | if (low < high) 41 | slab->expires[low] = slab->expires[high], slab->keys[low++] = slab->keys[high]; 42 | while (low < high && (slab->expires[low] < expire || (slab->expires[low] == expire && RedisModule_StringCompare(slab->keys[low], key) < 0))) 43 | low++; 44 | if (low < high) 45 | slab->expires[high] = slab->expires[low], slab->keys[high--] = slab->keys[low]; 46 | } 47 | slab->expires[low] = expire, slab->keys[low] = key; 48 | return low; 49 | } 50 | 51 | void quick_sort(Slab *slab, int low, int high) { 52 | int pos; 53 | if (low < high) { 54 | pos = partition(slab, low, high); 55 | quick_sort(slab, low, pos - 1); 56 | quick_sort(slab, pos + 1, high); 57 | } 58 | 59 | return; 60 | } 61 | 62 | int quick_selectRelaxtopk(Slab *slab, int left, int right, int kth) { 63 | if (left > right) 64 | return -1; 65 | int mid = (right + left) / 2, i = left, j = right; 66 | long long pivot_expire = slab->expires[mid]; 67 | RedisModuleString *pivot_key = slab->keys[mid]; 68 | slab_swap(slab, left, mid); 69 | while (i != j) { 70 | while (j > i && (slab->expires[j] > pivot_expire || (slab->expires[j] == pivot_expire && RedisModule_StringCompare(slab->keys[j], pivot_key) >= 0))) 71 | --j; 72 | slab->expires[i] = slab->expires[j], slab->keys[i] = slab->keys[j]; 73 | while (i < j && (slab->expires[i] < pivot_expire || (slab->expires[i] == pivot_expire && RedisModule_StringCompare(slab->keys[i], pivot_key) <= 0))) 74 | ++i; 75 | slab->expires[j] = slab->expires[i], slab->keys[j] = slab->keys[i]; 76 | } 77 | slab->expires[j] = pivot_expire, slab->keys[j] = pivot_key; 78 | int left_partition_len = j - left; 79 | int temp = kth + left - 1; 80 | if (temp - RELAXATION <= j && j <= temp + RELAXATION) { 81 | return j; 82 | } else if (kth <= left_partition_len) { 83 | return quick_selectRelaxtopk(slab, left, j - 1, kth); 84 | } else { 85 | return quick_selectRelaxtopk(slab, j + 1, right, kth - left_partition_len - 1); 86 | } 87 | } 88 | 89 | void slab_mergeIfNeed(tairhash_zskiplist *zsl, tairhash_zskiplistNode *tair_hash_node) { 90 | if (tair_hash_node == NULL || tair_hash_node->slab == NULL || tair_hash_node->slab->num_keys == 0 91 | || tair_hash_node->slab->num_keys >= SLABMAXN) { 92 | return; 93 | } 94 | Slab *cur_slab = tair_hash_node->slab; 95 | int i, j; 96 | if (tair_hash_node->level[0].forward != NULL && tair_hash_node->level[0].forward->slab != NULL) { // try merge next node 97 | tairhash_zskiplistNode *next_tair_hash_node = tair_hash_node->level[0].forward; 98 | Slab *next_slab = next_tair_hash_node->slab; 99 | int merge_sum = next_slab->num_keys + cur_slab->num_keys; 100 | if (merge_sum <= SLABMERGENUM && next_slab->num_keys > 0) { 101 | memcpy(&(cur_slab->expires[cur_slab->num_keys]), &(next_slab->expires[0]), next_slab->num_keys * sizeof(&(cur_slab->expires[0]))); 102 | memcpy(&(cur_slab->keys[cur_slab->num_keys]), &(next_slab->keys[0]), next_slab->num_keys * sizeof(&(cur_slab->keys[0]))); 103 | cur_slab->num_keys = merge_sum; 104 | RedisModule_Free(next_slab); 105 | int delete_ans = tairhash_zslDelete(zsl, next_tair_hash_node->key_min, next_tair_hash_node->expire_min); 106 | assert(delete_ans == 1); 107 | } 108 | } 109 | if (tair_hash_node->backward != NULL && tair_hash_node->backward->slab != NULL) { // try merge pre node 110 | tairhash_zskiplistNode *pre_tair_hash_node = tair_hash_node->backward; 111 | Slab *pre_slab = pre_tair_hash_node->slab; 112 | int merge_sum = pre_slab->num_keys + cur_slab->num_keys; 113 | if (merge_sum <= SLABMERGENUM && pre_slab->num_keys > 0) { 114 | memcpy(&(cur_slab->expires[cur_slab->num_keys]), &(pre_slab->expires[0]), pre_slab->num_keys * sizeof(cur_slab->expires[0])); 115 | memcpy(&(cur_slab->keys[cur_slab->num_keys]), &(pre_slab->keys[0]), pre_slab->num_keys * sizeof(cur_slab->keys[0])); 116 | cur_slab->num_keys = merge_sum; 117 | tair_hash_node->expire_min = pre_tair_hash_node->expire_min, tair_hash_node->key_min = pre_tair_hash_node->key_min; 118 | RedisModule_Free(pre_slab); 119 | int delete_ans = tairhash_zslDelete(zsl, pre_tair_hash_node->key_min, pre_tair_hash_node->expire_min); 120 | assert(delete_ans == 1); 121 | } 122 | } 123 | return; 124 | } 125 | 126 | tairhash_zskiplistNode *slab_split(tairhash_zskiplist *zsl, tairhash_zskiplistNode *tair_hash_node) { 127 | if (tair_hash_node == NULL || tair_hash_node->slab == NULL || tair_hash_node->slab->num_keys != SLABMAXN) 128 | return NULL; 129 | 130 | Slab *new_slab = slab_createNode(), *slab = tair_hash_node->slab; 131 | /* 132 | // sort slab 133 | quick_sort(slab, 0, SLABMAXN - 1); 134 | 135 | int i, j; 136 | long long start_move_start = RedisModule_Milliseconds(); 137 | for (i = 0, j = SLABMAXN / 2; j < SLABMAXN; j++, i++) { 138 | new_slab->expires[i] = slab->expires[j], new_slab->keys[i] = slab->keys[j]; 139 | slab->expires[j] = 0, slab->keys[j] = NULL; 140 | } 141 | slab->num_keys = SLABMAXN / 2, new_slab->num_keys = SLABMAXN - SLABMAXN / 2; 142 | */ 143 | 144 | int split_subscript = quick_selectRelaxtopk(slab, 0, SLABMAXN - 1, SLABMAXN / 4 * 3); 145 | memcpy(&(new_slab->expires[0]), &(slab->expires[split_subscript]), (SLABMAXN - split_subscript) * sizeof(&(slab->expires[0]))); 146 | memcpy(&(new_slab->keys[0]), &(slab->keys[split_subscript]), (SLABMAXN - split_subscript) * sizeof(&(slab->keys[0]))); 147 | slab->num_keys = split_subscript, new_slab->num_keys = SLABMAXN - split_subscript; 148 | 149 | long long new_expire_min = new_slab->expires[0]; 150 | RedisModuleString *new_key_min = new_slab->keys[0]; 151 | tairhash_zskiplistNode *new_tair_hash_node = tairhash_zslInsertNode(zsl, new_slab, new_key_min, new_expire_min); 152 | return new_tair_hash_node; 153 | } 154 | 155 | void slab_expireInsert(tairhash_zskiplist *zsl, RedisModuleString *key, long long expire) { 156 | tairhash_zskiplistNode *find_node = tairhash_zslGetNode(zsl, key, expire); 157 | int insert_ans = FALSE; 158 | if (find_node == zsl->header) { // no node insert 159 | find_node = zsl->header->level[0].forward; // try to insert first skiplistNode 160 | } 161 | 162 | if (find_node != NULL && find_node->slab != NULL && find_node->slab->num_keys == SLABMAXN) // slab full, running slab split 163 | { 164 | tairhash_zskiplistNode *new_tair_hash_node = slab_split(zsl, find_node); 165 | if (new_tair_hash_node->expire_min < expire || (new_tair_hash_node->expire_min == expire // insert new slab 166 | && RedisModule_StringCompare(new_tair_hash_node->key_min, key) < 0)) { 167 | find_node = new_tair_hash_node; 168 | Slab *new_slab = find_node->slab; 169 | insert_ans = slab_insertNode(new_slab, key, expire); 170 | assert(insert_ans == TRUE); 171 | } 172 | } 173 | 174 | if (insert_ans == FALSE && find_node != NULL && find_node->slab != NULL && find_node->slab->num_keys < SLABMAXN) { // slab not full, insert current slab 175 | Slab *find_slab = find_node->slab; 176 | insert_ans = slab_insertNode(find_slab, key, expire); 177 | assert(insert_ans == TRUE); 178 | if (find_node->expire_min > expire || (find_node->expire_min == expire && RedisModule_StringCompare(find_node->key_min, key) > 0)) { // update tairhashskiplist 179 | find_node->expire_min = expire, find_node->key_min = key; 180 | } 181 | } 182 | 183 | if (insert_ans == FALSE) { // no node insert, create node 184 | Slab *new_slab = slab_createNode(); 185 | insert_ans = slab_insertNode(new_slab, key, expire); 186 | assert(insert_ans == TRUE); 187 | tairhash_zskiplistNode *new_node = tairhash_zslInsertNode(zsl, new_slab, key, expire); 188 | } 189 | return; 190 | } 191 | 192 | void slab_expireDelete(tairhash_zskiplist *zsl, RedisModuleString *key, long long expire) { 193 | tairhash_zskiplistNode *find_node = tairhash_zslGetNode(zsl, key, expire); 194 | assert(find_node != NULL); 195 | assert(find_node->slab != NULL); 196 | Slab *find_slab = find_node->slab; 197 | assert(find_slab->num_keys != 0); 198 | if (find_slab->num_keys == 1) { 199 | assert(find_slab->expires[0] == expire && RedisModule_StringCompare(find_slab->keys[0], key) == 0); 200 | Slab *new_slab = find_slab; 201 | int delete_ans = tairhash_zslDelete(zsl, key, expire); 202 | assert(delete_ans == 1); 203 | slab_delete(new_slab); 204 | return; 205 | } 206 | 207 | int update_findNode = FALSE, delete_ans = FALSE; 208 | if (find_node->expire_min == expire && RedisModule_StringCompare(find_node->key_min, key) == 0) 209 | update_findNode = TRUE; 210 | delete_ans = slab_deleteNode(find_slab, key, expire); 211 | assert(delete_ans == TRUE); 212 | 213 | if (update_findNode) { // update min value 214 | int smallest_subscript = slab_minExpireTimeIndex(find_slab); 215 | find_node->expire_min = find_slab->expires[smallest_subscript], find_node->key_min = find_slab->keys[smallest_subscript]; 216 | } 217 | slab_mergeIfNeed(zsl, find_node); // if need merge 218 | return; 219 | } 220 | 221 | void slab_expireUpdate(tairhash_zskiplist *zsl, RedisModuleString *cur_key, long long cur_expire, RedisModuleString *new_key, long long new_expire) { 222 | slab_expireDelete(zsl, cur_key, cur_expire); 223 | slab_expireInsert(zsl, new_key, new_expire); 224 | } 225 | 226 | int slab_expireGet(tairhash_zskiplist *zsl, RedisModuleString *key, long long expire) { 227 | tairhash_zskiplistNode *find_node = tairhash_zslGetNode(zsl, key, expire); 228 | 229 | assert(find_node != NULL); 230 | assert(find_node->slab != NULL); 231 | int has_find = FALSE; 232 | 233 | if (find_node != NULL) { 234 | has_find = slab_getNode(find_node->slab, key, expire) > -1 ? TRUE : FALSE; 235 | } 236 | return has_find; 237 | } 238 | 239 | void slab_free(tairhash_zskiplist *zsl) { 240 | tairhash_zslFree(zsl); 241 | } 242 | 243 | tairhash_zskiplist *slab_create() { 244 | return tairhash_zslCreate(); 245 | } 246 | 247 | void slab_deleteSlabExpire(tairhash_zskiplist *zsl, tairhash_zskiplistNode *zsl_node, int *effective_indexs, int effective_num) { 248 | if (zsl_node == NULL) 249 | return; 250 | Slab *slab = zsl_node->slab; 251 | if (slab == NULL || slab->num_keys == effective_num) { 252 | return; 253 | } 254 | if (effective_num == 0) { 255 | Slab *new_slab = slab; 256 | int delete_ans = tairhash_zslDelete(zsl, zsl_node->key_min, zsl_node->expire_min); 257 | assert(delete_ans == 1); 258 | slab_delete(slab); 259 | return; 260 | } 261 | int index = 0, min_index = 0, i; 262 | for (i = 0; i < effective_num; i++) { 263 | index = effective_indexs[i]; 264 | slab->expires[i] = slab->expires[index]; 265 | slab->keys[i] = slab->keys[index]; 266 | if (slab->expires[i] < slab->expires[min_index] || (slab->expires[i] == slab->expires[min_index] && RedisModule_StringCompare(slab->keys[i], slab->keys[min_index]) <= 0)) { 267 | min_index = i; 268 | } 269 | } 270 | slab->num_keys = effective_num; 271 | zsl_node->expire_min = slab->expires[min_index], zsl_node->key_min = slab->keys[min_index]; 272 | slab_mergeIfNeed(zsl, zsl_node); 273 | return; 274 | } 275 | 276 | unsigned int slab_deleteTairhashRangeByRank(tairhash_zskiplist *zsl, unsigned int start, unsigned int end) { 277 | return tairhash_zslDeleteRangeByRank(zsl, start, end); 278 | } 279 | 280 | #ifdef __AVX2__ 281 | int slab_getSlabTimeoutExpireIndex(tairhash_zskiplistNode *node, int *ontime_indices, int *timeout_indices) { 282 | long long now = RedisModule_Milliseconds(); 283 | if (node == NULL || node->expire_min > now) return 0; 284 | Slab *slab = node->slab; 285 | if (slab == NULL || slab->num_keys == 0) 286 | return 0; 287 | 288 | __m256i target_expire_vec = _mm256_set1_epi64x(now); 289 | int ontime_num = 0, timeout_num = 0, size_effective = 0, size = slab->num_keys; 290 | long long *expires = slab->expires, i; 291 | static const int width = sizeof(__m256i) / sizeof(long long); 292 | const int veclen = size & ~(2 * width - 1); 293 | int step_size = (width << 1); 294 | for (i = 0; i < veclen; i += step_size) { 295 | const __m256i v_a = _mm256_lddqu_si256((const __m256i *)(expires + i)); 296 | const __m256i v_b = _mm256_lddqu_si256((const __m256i *)(expires + i + width)); 297 | 298 | _mm_prefetch(expires + i + step_size, _MM_HINT_NTA); 299 | 300 | __m256i v_a_gt = _mm256_cmpgt_epi64(v_a, target_expire_vec); 301 | __m256i v_b_gt = _mm256_cmpgt_epi64(v_b, target_expire_vec); 302 | int v_a_gt_mask = _mm256_movemask_pd((__m256d)v_a_gt); 303 | int v_b_gt_mask = _mm256_movemask_pd((__m256d)v_b_gt); 304 | __m256i v_a_cur_i = _mm256_set1_epi64x(i); 305 | __m256i v_b_cur_i = _mm256_set1_epi64x(i + width); 306 | 307 | __m256i v_a_offsets = _mm256_add_epi64(v_a_cur_i, shuffle_ontime_mask_4x64[v_a_gt_mask]); 308 | __m256i v_b_offsets = _mm256_add_epi64(v_b_cur_i, shuffle_ontime_mask_4x64[v_b_gt_mask]); 309 | _mm256_storeu_si256((__m256i *)(ontime_indices + ontime_num), v_a_offsets); 310 | ontime_num += _mm_popcnt_u64((unsigned)v_a_gt_mask); 311 | _mm256_storeu_si256((__m256i *)(ontime_indices + ontime_num), v_b_offsets); 312 | ontime_num += _mm_popcnt_u64((unsigned)v_b_gt_mask); 313 | 314 | v_a_offsets = _mm256_add_epi64(v_a_cur_i, shuffle_timeout_mask_4x64[v_a_gt_mask]); 315 | v_b_offsets = _mm256_add_epi64(v_b_cur_i, shuffle_timeout_mask_4x64[v_b_gt_mask]); 316 | _mm256_storeu_si256((__m256i *)(timeout_indices + timeout_num), v_a_offsets); 317 | timeout_num += width - _mm_popcnt_u64((unsigned)v_a_gt_mask); 318 | _mm256_storeu_si256((__m256i *)(timeout_indices + timeout_num), v_b_offsets); 319 | timeout_num += width - _mm_popcnt_u64((unsigned)v_b_gt_mask); 320 | } 321 | for (; i < size; ++i) { 322 | ontime_indices[ontime_num] = i, timeout_indices[timeout_num] = i; 323 | ontime_num += (expires[i] > now), timeout_num += (expires[i] <= now); 324 | } 325 | return timeout_num; 326 | } 327 | 328 | void slab_initShuffleMask() { 329 | __m256i shuffle_timeout_mask_array[16] = { 330 | (__m256i)(__v4di){0, 1, 2, 3}, 331 | (__m256i)(__v4di){1, 2, 3, -1}, 332 | (__m256i)(__v4di){0, 2, 3, -1}, 333 | (__m256i)(__v4di){2, 3, -1, -1}, 334 | (__m256i)(__v4di){0, 1, 3, -1}, 335 | (__m256i)(__v4di){1, 3, -1, -1}, 336 | (__m256i)(__v4di){0, 3, -1, -1}, 337 | (__m256i)(__v4di){3, -1, -1, -1}, 338 | (__m256i)(__v4di){0, 1, 2, -1}, 339 | (__m256i)(__v4di){1, 2, -1, -1}, 340 | (__m256i)(__v4di){0, 2, -1, -1}, 341 | (__m256i)(__v4di){2, -1, -1, -1}, 342 | (__m256i)(__v4di){0, 1, -1, -1}, 343 | (__m256i)(__v4di){1, -1, -1, -1}, 344 | (__m256i)(__v4di){0, -1, -1, -1}, 345 | (__m256i)(__v4di){-1, -1, -1, -1}, 346 | }; 347 | __m256i shuffle_ontime_mask_array[16] = { 348 | (__m256i)(__v4di){-1, -1, -1, -1}, 349 | (__m256i)(__v4di){0, -1, -1, -1}, 350 | (__m256i)(__v4di){1, -1, -1, -1}, 351 | (__m256i)(__v4di){0, 1, -1, -1}, 352 | (__m256i)(__v4di){2, -1, -1, -1}, 353 | (__m256i)(__v4di){0, 2, -1, -1}, 354 | (__m256i)(__v4di){1, 2, -1, -1}, 355 | (__m256i)(__v4di){0, 1, 2, -1}, 356 | (__m256i)(__v4di){3, -1, -1, -1}, 357 | (__m256i)(__v4di){0, 3, -1, -1}, 358 | (__m256i)(__v4di){1, 3, -1, -1}, 359 | (__m256i)(__v4di){0, 1, 3, -1}, 360 | (__m256i)(__v4di){2, 3, -1, -1}, 361 | (__m256i)(__v4di){0, 2, 3, -1}, 362 | (__m256i)(__v4di){1, 2, 3, -1}, 363 | (__m256i)(__v4di){0, 1, 2, 3}, 364 | }; 365 | 366 | int i = 0; 367 | for (; i < 16; i++) { 368 | shuffle_timeout_mask_4x64[i] = shuffle_timeout_mask_array[i]; 369 | shuffle_ontime_mask_4x64[i] = shuffle_ontime_mask_array[i]; 370 | } 371 | } 372 | 373 | #else 374 | 375 | int slab_getSlabTimeoutExpireIndex(tairhash_zskiplistNode *node, int *ontime_indices, int *timeout_indices) { 376 | long long now = RedisModule_Milliseconds(); 377 | if (node == NULL || node->expire_min > now) return 0; 378 | Slab *slab = node->slab; 379 | if (slab == NULL || slab->num_keys == 0) 380 | return 0; 381 | int ontime_num = 0, timeout_num = 0, size_effective = 0, size = slab->num_keys, i; 382 | long long *expires = slab->expires; 383 | for (i = 0; i < size; ++i) { 384 | ontime_indices[ontime_num] = i, timeout_indices[timeout_num] = i; 385 | ontime_num += (expires[i] > now), timeout_num += (expires[i] <= now); 386 | } 387 | return timeout_num; 388 | } 389 | #endif -------------------------------------------------------------------------------- /src/slabapi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #ifndef SLABAPI_H 17 | #define SLABAPI_H 18 | 19 | #include "dict.h" 20 | #include "tairhash_skiplist.h" 21 | 22 | #define SLABMERGENUM 512 23 | 24 | #ifdef __AVX2__ 25 | void slab_initShuffleMask(); 26 | #endif 27 | 28 | void slab_expireInsert(tairhash_zskiplist *zsl, RedisModuleString *key, long long expire); 29 | void slab_expireDelete(tairhash_zskiplist *zsl, RedisModuleString *key, long long expire); 30 | void slab_expireUpdate(tairhash_zskiplist *zsl, RedisModuleString *cur_key, long long cur_expire, RedisModuleString *new_key, long long new_expire); 31 | int slab_expireGet(tairhash_zskiplist *zsl, RedisModuleString *key, long long expire); 32 | void slab_free(tairhash_zskiplist *zsl); 33 | tairhash_zskiplist *slab_create(); 34 | int slab_getSlabTimeoutExpireIndex(tairhash_zskiplistNode *node, int *ontime_indices, int *timeout_indices); 35 | void slab_deleteSlabExpire(tairhash_zskiplist *zsl, tairhash_zskiplistNode *zsl_node, int *effective_indexs, int effective_num); 36 | unsigned int slab_deleteTairhashRangeByRank(tairhash_zskiplist *zsl, unsigned int start, unsigned int end); 37 | #endif 38 | -------------------------------------------------------------------------------- /src/sort_algorithm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "tairhash.h" 17 | 18 | #if defined(SORT_MODE) 19 | extern ExpireAlgorithm g_expire_algorithm; 20 | extern m_zskiplist *g_expire_index[DB_NUM]; 21 | extern RedisModuleType *TairHashType; 22 | 23 | void insert(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *o, RedisModuleString *field, long long expire) { 24 | REDISMODULE_NOT_USED(ctx); 25 | REDISMODULE_NOT_USED(key); 26 | if (expire) { 27 | long long before_min_score = -1, after_min_score = -1; 28 | if (o->expire_index->header->level[0].forward) { 29 | before_min_score = o->expire_index->header->level[0].forward->score; 30 | } 31 | m_zslInsert(o->expire_index, expire, takeAndRef(field)); 32 | after_min_score = o->expire_index->header->level[0].forward->score; 33 | if (before_min_score > 0) { 34 | m_zslUpdateScore(g_expire_index[dbid], before_min_score, o->key, after_min_score); 35 | } else { 36 | m_zslInsert(g_expire_index[dbid], after_min_score, takeAndRef(o->key)); 37 | } 38 | } 39 | } 40 | 41 | void update(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *o, RedisModuleString *field, long long cur_expire, long long new_expire) { 42 | REDISMODULE_NOT_USED(ctx); 43 | REDISMODULE_NOT_USED(key); 44 | if (cur_expire != new_expire) { 45 | long long before_min_score = -1, after_min_score = 1; 46 | m_zskiplistNode *ln = o->expire_index->header->level[0].forward; 47 | Module_Assert(ln != NULL); 48 | before_min_score = ln->score; 49 | m_zslUpdateScore(o->expire_index, cur_expire, field, new_expire); 50 | after_min_score = o->expire_index->header->level[0].forward->score; 51 | m_zslUpdateScore(g_expire_index[dbid], before_min_score, o->key, after_min_score); 52 | } 53 | } 54 | 55 | void delete(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *o, RedisModuleString *field, long long cur_expire) { 56 | REDISMODULE_NOT_USED(ctx); 57 | REDISMODULE_NOT_USED(key); 58 | if (cur_expire != 0) { 59 | long long before_min_score = -1; 60 | m_zskiplistNode *ln = o->expire_index->header->level[0].forward; 61 | Module_Assert(ln != NULL); 62 | before_min_score = ln->score; 63 | m_zslDelete(o->expire_index, cur_expire, field, NULL); 64 | if (o->expire_index->header->level[0].forward) { 65 | long long after_min_score = o->expire_index->header->level[0].forward->score; 66 | m_zslUpdateScore(g_expire_index[dbid], before_min_score, key, after_min_score); 67 | } else { 68 | m_zslDelete(g_expire_index[dbid], before_min_score, key, NULL); 69 | } 70 | } 71 | } 72 | 73 | void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys_per_loop) { 74 | int start_index; 75 | long long when, now; 76 | unsigned long zsl_len; 77 | int expire_keys_per_loop = keys_per_loop; 78 | 79 | m_zskiplistNode *ln = NULL; 80 | m_zskiplistNode *ln2 = NULL; 81 | 82 | RedisModuleString *key, *field; 83 | RedisModuleKey *real_key; 84 | 85 | tairHashObj *tair_hash_obj = NULL; 86 | list *keys = m_listCreate(); 87 | 88 | /* 1. The current db does not have a key that needs to expire. */ 89 | zsl_len = g_expire_index[dbid]->length; 90 | if (zsl_len == 0) { 91 | m_listRelease(keys); 92 | return; 93 | } 94 | 95 | /* 2. Enumerates expired keys. */ 96 | ln = g_expire_index[dbid]->header->level[0].forward; 97 | start_index = 0; 98 | while (ln && expire_keys_per_loop--) { 99 | key = ln->member; 100 | when = ln->score; 101 | now = RedisModule_Milliseconds(); 102 | if (when > now) { 103 | break; 104 | } 105 | start_index++; 106 | m_listAddNodeTail(keys, key); 107 | ln = ln->level[0].forward; 108 | } 109 | 110 | if (start_index) { 111 | /* It is assumed that these keys will all be deleted. */ 112 | m_zslDeleteRangeByRank(g_expire_index[dbid], 1, start_index); 113 | } 114 | 115 | if (listLength(keys) == 0) { 116 | m_listRelease(keys); 117 | return; 118 | } 119 | 120 | /* 3. Delete expired field. */ 121 | expire_keys_per_loop = keys_per_loop; 122 | m_listNode *node; 123 | while ((node = listFirst(keys)) != NULL) { 124 | key = listNodeValue(node); 125 | real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_WRITE | REDISMODULE_OPEN_KEY_NOTOUCH); 126 | int type = RedisModule_KeyType(real_key); 127 | if (type != REDISMODULE_KEYTYPE_EMPTY) { 128 | Module_Assert(RedisModule_ModuleTypeGetType(real_key) == TairHashType); 129 | } else { 130 | /* Note: redis scan may return dup key. */ 131 | m_listDelNode(keys, node); 132 | continue; 133 | } 134 | 135 | tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key); 136 | 137 | zsl_len = tair_hash_obj->expire_index->length; 138 | Module_Assert(zsl_len > 0); 139 | 140 | ln2 = tair_hash_obj->expire_index->header->level[0].forward; 141 | start_index = 0; 142 | while (ln2 && expire_keys_per_loop) { 143 | field = ln2->member; 144 | if (fieldExpireIfNeeded(ctx, dbid, key, tair_hash_obj, field, 1)) { 145 | g_expire_algorithm.stat_active_expired_field[dbid]++; 146 | start_index++; 147 | expire_keys_per_loop--; 148 | } else { 149 | break; 150 | } 151 | ln2 = ln2->level[0].forward; 152 | } 153 | 154 | if (start_index) { 155 | m_zslDeleteRangeByRank(tair_hash_obj->expire_index, 1, start_index); 156 | delEmptyTairHashIfNeeded(ctx, real_key, key, tair_hash_obj); 157 | } 158 | 159 | /* If there is still a field waiting to expire and delete, re-insert it to the global index. */ 160 | if (ln2) { 161 | m_zslInsert(g_expire_index[dbid], ln2->score, takeAndRef(tair_hash_obj->key)); 162 | } 163 | 164 | m_listDelNode(keys, node); 165 | } 166 | 167 | m_listRelease(keys); 168 | } 169 | 170 | void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *up_key) { 171 | REDISMODULE_NOT_USED(up_key); 172 | int keys_per_loop = g_expire_algorithm.keys_per_passive_loop; 173 | long long when, now; 174 | int start_index = 0; 175 | m_zskiplistNode *ln = NULL; 176 | 177 | RedisModuleString *key, *field; 178 | RedisModuleKey *real_key; 179 | unsigned long zsl_len; 180 | tairHashObj *tair_hash_obj = NULL; 181 | 182 | list *keys = m_listCreate(); 183 | /* 1. The current db does not have a key that needs to expire. */ 184 | zsl_len = g_expire_index[dbid]->length; 185 | if (zsl_len == 0) { 186 | m_listRelease(keys); 187 | return; 188 | } 189 | 190 | /* Reuse the current time for fields. */ 191 | now = RedisModule_Milliseconds(); 192 | 193 | /* 2. Enumerates expired keys */ 194 | ln = g_expire_index[dbid]->header->level[0].forward; 195 | start_index = 0; 196 | while (ln && keys_per_loop--) { 197 | key = ln->member; 198 | when = ln->score; 199 | if (when > now) { 200 | break; 201 | } 202 | start_index++; 203 | m_listAddNodeTail(keys, key); 204 | ln = ln->level[0].forward; 205 | } 206 | 207 | if (start_index) { 208 | m_zslDeleteRangeByRank(g_expire_index[dbid], 1, start_index); 209 | } 210 | 211 | if (listLength(keys) == 0) { 212 | m_listRelease(keys); 213 | return; 214 | } 215 | 216 | /* 3. Delete expired field. */ 217 | keys_per_loop = g_expire_algorithm.keys_per_passive_loop; 218 | m_listNode *node; 219 | while ((node = listFirst(keys)) != NULL) { 220 | key = listNodeValue(node); 221 | real_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ | REDISMODULE_WRITE); 222 | int type = RedisModule_KeyType(real_key); 223 | 224 | Module_Assert(type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(real_key) == TairHashType); 225 | tair_hash_obj = RedisModule_ModuleTypeGetValue(real_key); 226 | 227 | zsl_len = tair_hash_obj->expire_index->length; 228 | Module_Assert(zsl_len > 0); 229 | 230 | start_index = 0; 231 | ln = tair_hash_obj->expire_index->header->level[0].forward; 232 | while (ln && keys_per_loop) { 233 | field = ln->member; 234 | if (fieldExpireIfNeeded(ctx, dbid, key, tair_hash_obj, field, 1)) { 235 | g_expire_algorithm.stat_passive_expired_field[dbid]++; 236 | start_index++; 237 | keys_per_loop--; 238 | } else { 239 | break; 240 | } 241 | ln = ln->level[0].forward; 242 | } 243 | 244 | if (start_index) { 245 | m_zslDeleteRangeByRank(tair_hash_obj->expire_index, 1, start_index); 246 | if (!delEmptyTairHashIfNeeded(ctx, real_key, key, tair_hash_obj)) { 247 | RedisModule_CloseKey(real_key); 248 | } 249 | } 250 | 251 | if (ln) { 252 | m_zslInsert(g_expire_index[dbid], ln->score, takeAndRef(tair_hash_obj->key)); 253 | } 254 | 255 | m_listDelNode(keys, node); 256 | } 257 | 258 | m_listRelease(keys); 259 | } 260 | 261 | void deleteAndPropagate(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *o, RedisModuleString *field, long long expire, int is_timer) { 262 | RedisModuleString *key_dup = RedisModule_CreateStringFromString(NULL, key); 263 | RedisModuleString *field_dup = RedisModule_CreateStringFromString(NULL, field); 264 | if (!is_timer) { 265 | long long before_min_score = o->expire_index->header->level[0].forward->score; 266 | m_zslDelete(o->expire_index, expire, field_dup, NULL); 267 | if (o->expire_index->header->level[0].forward != NULL) { 268 | long long after_min_score = o->expire_index->header->level[0].forward->score; 269 | m_zslUpdateScore(g_expire_index[dbid], before_min_score, key, after_min_score); 270 | } else { 271 | m_zslDelete(g_expire_index[dbid], before_min_score, key, NULL); 272 | } 273 | } 274 | m_dictDelete(o->hash, field); 275 | RedisModule_Replicate(ctx, "EXHDEL", "ss", key_dup, field_dup); 276 | notifyFieldSpaceEvent("expired", key_dup, field_dup, dbid); 277 | RedisModule_FreeString(NULL, key_dup); 278 | RedisModule_FreeString(NULL, field_dup); 279 | } 280 | 281 | #endif -------------------------------------------------------------------------------- /src/sort_algorithm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #if defined(SORT_MODE) 19 | void insert(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire); 20 | void update(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long cur_expire, long long new_expire); 21 | void delete(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire); 22 | void deleteAndPropagate(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire, int is_timer); 23 | void activeExpire(RedisModuleCtx *ctx, int dbid, uint64_t keys); 24 | void passiveExpire(RedisModuleCtx *ctx, int dbid, RedisModuleString *key_per_loop); 25 | #endif -------------------------------------------------------------------------------- /src/tairhash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #include 19 | 20 | #include "dict.h" 21 | #include "list.h" 22 | #include "redismodule.h" 23 | #include "skiplist.h" 24 | #include "slabapi.h" 25 | #include "util.h" 26 | 27 | #define TAIRHASH_ERRORMSG_SYNTAX "ERR syntax error" 28 | #define TAIRHASH_ERRORMSG_VERSION "ERR update version is stale" 29 | #define TAIRHASH_ERRORMSG_NOT_INTEGER "ERR value is not an integer" 30 | #define TAIRHASH_ERRORMSG_NOT_FLOAT "ERR value is not an float" 31 | #define TAIRHASH_ERRORMSG_OVERFLOW "ERR increment or decrement would overflow" 32 | #define TAIRHASH_ERRORMSG_INTERNAL_ERR "ERR internal error" 33 | #define TAIRHASH_ERRORMSG_INT_MIN_MAX "ERR min or max is specified, but value is not an integer" 34 | #define TAIRHASH_ERRORMSG_FLOAT_MIN_MAX "ERR min or max is specified, but value is not a float" 35 | #define TAIRHASH_ERRORMSG_MIN_MAX "ERR min value is bigger than max value" 36 | 37 | #define TAIR_HASH_SET_NO_FLAGS 0 38 | #define TAIR_HASH_SET_NX (1 << 0) 39 | #define TAIR_HASH_SET_XX (1 << 1) 40 | #define TAIR_HASH_SET_EX (1 << 2) 41 | #define TAIR_HASH_SET_PX (1 << 3) 42 | #define TAIR_HASH_SET_ABS_EXPIRE (1 << 4) 43 | #define TAIR_HASH_SET_WITH_VER (1 << 5) 44 | #define TAIR_HASH_SET_WITH_ABS_VER (1 << 6) 45 | #define TAIR_HASH_SET_WITH_GT_VER (1 << 7) 46 | #define TAIR_HASH_SET_WITH_BOUNDARY (1 << 8) 47 | #define TAIR_HASH_SET_KEEPTTL (1 << 9) 48 | 49 | #define UNIT_SECONDS 0 50 | #define UNIT_MILLISECONDS 1 51 | #define DB_NUM 16 /* This value must be equal to the db_dum of redis. */ 52 | 53 | #define TAIR_HASH_ACTIVE_EXPIRE_PERIOD 1000 54 | #define TAIR_HASH_ACTIVE_EXPIRE_KEYS_PER_LOOP 1000 55 | #define TAIR_HASH_ACTIVE_DBS_PER_CALL 16 56 | #define TAIR_HASH_PASSIVE_EXPIRE_KEYS_PER_LOOP 3 57 | #define TAIR_HASH_SCAN_DEFAULT_COUNT 10 58 | 59 | #define Module_Assert(_e) ((_e) ? (void)0 : (_moduleAssert(#_e, __FILE__, __LINE__), abort())) 60 | 61 | /* 62 | * We use `version` and `expire` as part of the tairhash value. This may be different 63 | * from the expire on the redis key. Redis regards `expire` as part of the database, not 64 | * part of the key. For example, after you perform a restore on a key, the original expire 65 | * will be Lost unless you specify ttl again. The `version` and `expire` of tairhash will 66 | * be completely recovered after the restore. 67 | */ 68 | typedef struct TairHashVal { 69 | long long version; 70 | long long expire; 71 | RedisModuleString *value; 72 | } TairHashVal; 73 | 74 | typedef struct tairHashObj { 75 | dict *hash; 76 | #if defined SLAB_MODE 77 | tairhash_zskiplist *expire_index; 78 | #else 79 | m_zskiplist *expire_index; 80 | #endif 81 | RedisModuleString *key; 82 | } tairHashObj; 83 | 84 | typedef struct ExpireAlgorithm { 85 | void (*insert)(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire); 86 | void (*update)(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long cur_expire, long long new_expire); 87 | void (*delete)(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire); 88 | void (*deleteAndPropagate)(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *obj, RedisModuleString *field, long long expire, int is_timer); 89 | void (*activeExpire)(RedisModuleCtx *ctx, int dbid, uint64_t keys); 90 | void (*passiveExpire)(RedisModuleCtx *ctx, int dbid, RedisModuleString *key_per_loop); 91 | 92 | int enable_active_expire; 93 | uint64_t active_expire_period; 94 | uint64_t dbs_per_active_loop; 95 | uint64_t keys_per_active_loop; 96 | uint64_t keys_per_passive_loop; 97 | uint64_t stat_active_expired_field[DB_NUM]; 98 | uint64_t stat_passive_expired_field[DB_NUM]; 99 | uint64_t stat_last_active_expire_time_msec; 100 | uint64_t stat_avg_active_expire_time_msec; 101 | uint64_t stat_max_active_expire_time_msec; 102 | } ExpireAlgorithm; 103 | 104 | void _moduleAssert(const char *estr, const char *file, int line); 105 | RedisModuleString *takeAndRef(RedisModuleString *str); 106 | int delEmptyTairHashIfNeeded(RedisModuleCtx *ctx, RedisModuleKey *key, RedisModuleString *raw_key, tairHashObj *obj); 107 | void notifyFieldSpaceEvent(char *event, RedisModuleString *key, RedisModuleString *field, int dbid); 108 | int isExpire(long long when); 109 | int fieldExpireIfNeeded(RedisModuleCtx *ctx, int dbid, RedisModuleString *key, tairHashObj *o, RedisModuleString *field, int is_timer); 110 | --------------------------------------------------------------------------------