Skip to content

Commit 0b27058

Browse files
committed
add post(half): uv(workspace 35%)
1 parent 775b31d commit 0b27058

File tree

32 files changed

+5644
-148
lines changed

32 files changed

+5644
-148
lines changed

content/posts/2025-08-01_python-project-structures.md

Lines changed: 0 additions & 6 deletions
This file was deleted.

content/posts/2025-09-07_docker-image.md

Lines changed: 664 additions & 6 deletions
Large diffs are not rendered by default.

content/posts/2025-09-19_redis-learn-07.md

Lines changed: 182 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,186 @@ BITFIELD 命令支持 SET, GET, INCRBY, OVERFLOW 这 4 个子命令, 接下来
209209
2) (integer) 0
210210
```
211211

212-
除了上面介绍的基本功能, 还有一下方法可以实现, 这里不再介绍
212+
- 根据索引对区域进行设置
213+
除了根据偏移量设置位图, 还可以根据给定类型的位长度, 对位图在指定索引上存储的整数进行设置.
214+
```Redis
215+
BITFIELD bitmap SET type #index value
216+
```
217+
假如现在有一个位图, 其存储着多个 8 位长的无符号整数, 想要将其第 133 个 8 位无符号整数的值设置为 22.
218+
如果使用偏移量, 就要手动计算 (133 - 1) * 8 的起始偏移量
219+
```Redis
220+
BITFIELD bitmap SET u8 1056 22
221+
```
222+
这样很不方便, 因此可以使用类型直接设置 (SET 字命令索引从 0 开始)
223+
```Redis
224+
BITFIELD bitmap SET u8 #132 22
225+
```
226+
227+
- 获取区域存储的值
228+
通过使用 BITFIELD 命令的 GET 子命令, 可以从给定的偏移量或索引取出指定类型整数值
229+
```Redis
230+
BITFIELD bitmap GET type offset
231+
BITFIELD bitmap GET type #index
232+
```
233+
234+
- 执行加法或减法操作
235+
除了设置于获取整数值, BITFIELD 命令还可以对位图的整数值执行加法或减法操作, 通过 INCRBY 子命令实现
236+
```Redis
237+
BITFIELD bitmap INCRBY type offset increment
238+
BITFIELD bitmap INCRBY type #index increment
239+
```
240+
如果要实现加法, 仍然使用 INCRBY 命令, 但 increment 使用负数, 从而得到减法的效果
241+
```Redis
242+
BITFIELD numbers SET u8 #0 10 -- 将区域的值设置未整数 10
243+
1) (integer) 0
244+
245+
BITFIELD numbers GET u8 #0
246+
1) (integer) 10
247+
248+
BITFIELD numbers INCRBY u8 #0 15 -- 将整数的值加上 15
249+
1) (integer) 25
250+
251+
BITFIELD numbers INCRBY u8 #0 30 -- 将整数值加上 30
252+
1) (integer) 55
253+
254+
BITFIELD numbers INCRBY u8 #0 -25 -- 将整数值减去 25
255+
1) (integer) 30
256+
```
257+
258+
- 处理溢出
259+
BITFIELD 命令还可以使用 OVERFLOW 子命令去控制 INCRBY 子命令在发生计算溢出时的行为
260+
```Redis
261+
BITFIELD bitmap [...] OVERFLOW WRAP|SAT|FAIL [...]
262+
```
263+
OVERFLOW 参数有 WRAP, SAT 和 FAIL
264+
- WRAP 表示回绕 (wrap around) 方式处理溢出, 也就是 C 语言默认的溢出处理方式.
265+
- SAT 表示使用饱合运算 (saturation arithmetic) 方式处理溢出. 向上溢出的整数设置位最大值, 向下溢出的整数值则被设置位最小值
266+
- FAIL 表示让 INCRBY 子命令在检测到计算会引发溢出时, 拒绝执行计算, 并返回空值表示计算失败
267+
268+
但 OVERFLOW 命令执行后不会产生任何返回, 此外, 如果没有设置溢出处理方式, 默认是 WRAP
269+
270+
需要注意的是, 由于 OVERFLOW 子命令只会对同一个 BITFIELD 调用中在其后面的 INCRBY 命令产生效果
271+
```Redis
272+
BITFIELD bitmap INCRBY ... INCRBY OVERFLOW SAT INCRBY ...
273+
```
274+
上面命令中, 前两个就使用 WRAP 方法处理溢出, 第三个使用 SAT 方法处理溢出
213275

214-
- 执行加法或减法操作
215-
- 处理溢出
216-
- 字符串命令对位图进行操作
276+
- 使用位图存储整数的原因
277+
一般情况下, 用户使用字符串或者散列存储整数, Redis 都会为被存储的整数分配一个 long 类型的值, 并使用对象去包裹这个值, 然后再吧对象关联到数据库中
278+
279+
而 BITFIELD 命令运行用户自行指定存储整数的类型, 并且不会使用对象去包裹这些整数, 因此当想要存储长度比 long 类型短的整数, 并且希望尽可能减少对象包括带来的内存消耗, 就可以考虑使用 BITFIELD 存储整数
280+
281+
- 复杂度: O(N), N 为用户给定的子命令数量
282+
283+
284+
### 示例: 紧凑计数器
285+
- 与之间实现的计数器不同, 这个计数器允许用户自行指定计数器值的位长以及类型, 而不是使用 Redis 默认的 long 类型来存储计数器值
286+
- 与字符串或者散列实现的计数器不同, 这个计数器只能使用整数作为索引, 因此只适合存储一些与数字 ID 想关联的计数器数据
287+
288+
```Python
289+
from redis import Redis
290+
291+
def get_bitmap_index(index):
292+
return "#" + str(index)
293+
294+
class CompactCounter:
295+
def __init__(self, client, key, bit_length, signed):
296+
"""初始化紧凑计数器
297+
298+
Args:
299+
- client 指定客户端
300+
- key 参数指定计数器键名
301+
- bit_length 参数指定计数器存储的整数位
302+
- signed 参数用于指定计数器存储的符号
303+
"""
304+
self.client = client
305+
self.key = key
306+
if signed:
307+
self.type = "i" + str(bit_length)
308+
else:
309+
self.type = "u" + str(bit_length)
310+
311+
def increase(self, index, n=1):
312+
"""对索引 index 的计数器执行加法操作, 然后返回计数器的当前值"""
313+
bitmap_index = get_bitmap_index(index)
314+
result = self.client.execute_command(
315+
"BITFIELD",
316+
self.key,
317+
"OVERFLOW",
318+
"SAT",
319+
"INCRBY",
320+
self.type,
321+
bitmap_index,
322+
n,
323+
)
324+
return result[0]
325+
326+
def decrease(self, index, n=1):
327+
"""对索引 index 上的计数器执行减法操作, 然后返回计数器的当前值"""
328+
bitmap_index = get_bitmap_index(index)
329+
decrement = -n
330+
result = self.client.execute_command(
331+
"BITFIELD",
332+
self.key,
333+
"OVERFLOW",
334+
"SAT",
335+
"INCRBY",
336+
self.type,
337+
bitmap_index,
338+
decrement,
339+
)
340+
return result[0]
341+
342+
def get(self, index):
343+
"""获取索引 index 上的计数器的当前值"""
344+
bitmap_index = get_bitmap_index(index)
345+
result = self.client.execute_command(
346+
"BITfIELD",
347+
self.key,
348+
"GET",
349+
self.type,
350+
bitmap_index,
351+
)
352+
```
353+
假如要为一家游戏公司创建一个记录每个玩家每月登陆数的计数器, 按照一个月 30 天, 一天登陆 2~3 次的频率来计算, 一个普通玩家一个月的登陆次数通常不会超过 100 次. 对于这么小的数值, 使用 long 类型进行存储将浪费大量空间, 因此可以使用紧凑计数器来存储用户登陆次数:
354+
- 每个玩家都又一个整数类型的用户 ID, 可以使用 ID 作为计数器索引
355+
- 对于每个玩家, 使用一个 16 位长的无符号整数来存储其一个月内的登陆次数
356+
- 16 位无符号整数计数器能存储的最大值位 65536, 对于该功能来说, 已经足够大
357+
```Python
358+
client = Redis()
359+
counter = CompactCounter(client, "login_count", 16, False) # 建立计数器
360+
print(counter.increase(10086)) # 记录第 1 次登陆
361+
print(counter.increase(10086)) # 再记录第 1 次登陆
362+
print(counter.get(10086)) # 获取登陆次数
363+
```
364+
365+
- 使用字符串命令对位图进行操作
366+
由于 Redis 位图在字符串的基础上实现, 所以它会将位图键看作一个字符串键
367+
```Redis
368+
SETBIT bitmap 0 1
369+
> (integer) 0
370+
371+
TYPE bitmap
372+
> string
373+
```
374+
因此, 还可以使用字符串命令对位图进行操作
375+
```Redis
376+
GET 8bit-int
377+
> "\x04"
378+
379+
GET 32bit-ints
380+
> "\x00\x00\x00{\x00\x00\x01\x00\x00\x00\x00 ...}"
381+
```
382+
也可以使用 STRLEN 命令获取位图的字节长度
383+
```Redis
384+
STRLEN 8bit-int -- 1 字节
385+
> (integer) 1
386+
387+
STRLEN 32bit-ints -- 这个位图 16 字节
388+
> (integerf 16)
389+
```
390+
还可以使用 GETRANGE 命令获取位图的其中一部分字节:
391+
```Redis
392+
GETRANGE 32bit-ints 0 3
393+
```
394+
诸如此类

0 commit comments

Comments
 (0)