├── .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 | 
2 | 
3 | [](https://github.com/alibaba/TairHash/actions/workflows/cmake.yml)
4 | [](https://github.com/alibaba/TairHash/actions/workflows/ci.yml)
5 | [](https://github.com/alibaba/TairHash/actions/workflows/docker-image.yml)
6 | [](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 | 
2 | 
3 | [](https://github.com/alibaba/TairHash/actions/workflows/cmake.yml)
4 | [](https://github.com/alibaba/TairHash/actions/workflows/ci.yml)
5 | [](https://github.com/alibaba/TairHash/actions/workflows/docker-image.yml)
6 | [](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 |
--------------------------------------------------------------------------------