Skip to content

Commit c8b41df

Browse files
committed
post: redis hash
1 parent 5c711f2 commit c8b41df

File tree

21 files changed

+804
-70
lines changed

21 files changed

+804
-70
lines changed

content/posts/2025-08-20_http-methods-and-status.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,13 @@ URL 查询参数能让我们实现这些目标, 他应当始终是**可选的**,
102102
定义: URL 查询参数是 URL 中的键值对参数. 查询参数位于问号(?)自后, 通常用于筛选接口的返回结果. 可以用与号(&)来分隔组合多个查询参数.
103103

104104
调用 GET /orders 接口并按"已取消"来筛选订单结果, 可以这样写
105-
```Python
105+
```
106106
GET /orders?cancelled=true
107107
```
108108

109109
### 链接多个参数
110110
向 GET /orders 端点添加一个名为 limit 的查询参数以限制返回结果的数量. 如果要筛选"已取消"订单并将返回结果限制为 5 条, 可以这样请求 API
111-
```Python
111+
```
112112
GET /orders?cancelled=true&limit=5
113113
```
114114

Lines changed: 222 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,227 @@
11
+++
2-
date = '2025-08-20T8:00:00+08:00'
3-
draft = true
2+
date = '2025-08-21T8:00:00+08:00'
3+
draft = false
44
title = 'Redis Hash'
55
tags = ['Redis', 'Database']
66
+++
77

8+
## 散列
9+
Redis 散列键 hash key 会将一个键和一个散列在数据库里关联起来, 散列中可以存任意多个字段 field.
10+
与字符串一样, 散列字段和值既可以是文本数据, 也可以是二进制数据.
11+
12+
- HSET: 为字段设置值
13+
```
14+
HEST hash field value
15+
```
16+
若已给定的字段是否已经存在与散列中, 该设置为一次更新操作, 覆盖旧值后返回0.
17+
相反, 则为一次创建操作, 命令将在散列里面关联起给定的字段和值, 然后返回1.
18+
19+
- HSETNX: 只在字段不存在的情况下设置值
20+
```
21+
HSETNX hash field value
22+
```
23+
HSETNX 命令在字段不存在且成功设置值时, 返回1.
24+
字段已存在并设置值未成功时, 返回0.
25+
26+
- HGET: 获取字段的值
27+
```
28+
HGET hash field
29+
```
30+
若查找的不存在的散列或字段, 则会返回空(nil)
31+
32+
### 示例: 短网址生成
33+
为了给用户提供更多空间, 并记录用户在网站上的链接点击行为, 大部分社交网站都会将用户输入的网址转换为短网址. 当用户点击段网址时, 后台就会进行数据统计, 并引导用户跳转到原地址.
34+
创建短网址本质上就是, 要创建出短网址ID与目标网址之间的映射, 并让用户访问短网址时, 根据短网址的ID映射记录中找出与之相对应的目标网址.
35+
36+
| 短网址 ID | 目标网址 |
37+
| :-------- | :------- |
38+
| RqRRz8n | http://redisdoc.com/geo/index.html |
39+
| RUwtQBx | http://item.jd.com/117910607.html |
40+
41+
- HINCRBY: 对字段存储的整数值执行加法或减法操作
42+
```
43+
HINCRBY hash field increment
44+
```
45+
与字符串 INCRBY 命令一样, 如果散列字段里面存储着能够被 Redis 解释为整数的数字, 那么用户就可以使用 HINCRBY 命令为该字段的值加上指定的整数增量.
46+
该命令执行成功后, 将返回字段当前的值为命令的结果. 若要执行减法操作, increment 传入负数即可.
47+
48+
- HINCRBYFLOAT: 对字段存储的数字执行浮点数加法或减法操作
49+
```
50+
HINCRBYFLOAT hash field increment
51+
```
52+
HINCRBYFLOAT 不仅可以使用整数作为增量, 还可以使用浮点数作为增量. 该命令执行成功后, 返回给定字段的当前值作为结果.
53+
此外, 不仅存储浮点数的字段可以使用该命令, 整数字段也可以使用该命令; 若计算结果可以表示为整数, 则会使用整数表示.
54+
55+
- HSTRLEN: 获取字段的字节长度
56+
```
57+
HSTRLEN hash field
58+
```
59+
如果给定的字段或散列不存在, 将返回0
60+
61+
- HEXISTS: 检查字段是否存在
62+
```
63+
HEXISTS hash field
64+
```
65+
如果存在, 返回1, 否则返回0
66+
67+
- HDEL: 删除字段
68+
```
69+
HDEL hash field
70+
```
71+
72+
- HLEN: 获取散列包含的字段数量
73+
```
74+
HLEN hash
75+
```
76+
若不存在返回0
77+
78+
- HMSET: 一次为多个字段设置值
79+
```
80+
HMSET hash field value [field value ...]
81+
```
82+
该命令成功时返回 OK, 可使用新值覆盖旧值
83+
84+
- HMGET: 一次获取多个字段值
85+
```
86+
HMGET hash field [field ...]
87+
```
88+
对于不存在的值, 返回 (nil)
89+
90+
- HKEYS, HVALS, HGETALL: 获取所有字段, 所有值, 所有字段和值
91+
```
92+
HEKYS hash
93+
HVALS hash
94+
HGETALL hash
95+
```
96+
其中, HGETALL 命令返回的结果列表中, 没两个连续的元素代表散列中的一对字段和值, 奇数位置为字段, 偶数位置为字段值.
97+
若散列不存在, 则返回控列表`(empty array)`
98+
99+
Redis 散列底层为无序存储的, 因此HKEYS, HVALS 和 HGETALL 可能会得到不同的结果, 因此不应该对其返回元素顺序做任何假设.
100+
101+
### 示例: 存储图数据
102+
图是一直常用的数据结构, 这里使用 field=edge, value=weight 的表示法来存储图结构, 其中 edge 由 `start->edge` 构成
103+
```Python
104+
from redis import Redis
105+
106+
def make_edge_from_vertexs(start, end):
107+
return str(start) + "->" + str(end)
108+
109+
def decompose_vertexs_from_edge_name(name):
110+
return name.split("->")
111+
112+
class Graph:
113+
def __init__(self, client, key):
114+
self.client = client
115+
self.key = key
116+
117+
def add_edge(self, start, end, weight):
118+
edge = make_edge_from_vertexs(start, end)
119+
self.client.hset(self.key, edge, weight)
120+
121+
def remove_edge(self, start, end):
122+
edge = make_edge_from_vertexs(start, end)
123+
return self.client.hdel(self.key, edge)
124+
125+
def get_edge_weight(self, start, end):
126+
edge = make_edge_from_vertexs(start, end)
127+
return self.client.hget(self.key, edge)
128+
129+
def has_edge(self, start, end):
130+
edge = make_edge_from_vertexs(start, end)
131+
return self.client.hexists(self.key, edge)
132+
133+
def add_multi_edges(self, *tuples):
134+
nodes_and_weights = {}
135+
for start, end, weight in tuples:
136+
edge = make_edge_from_vertexs(start, end)
137+
nodes_and_weights[edge] = weight
138+
self.client.hset(self.key, mapping=nodes_and_weights) # hmset 在 4.0 已抛弃, 使用 .hset(mapping={...})
139+
140+
def get_multi_edge_weights(self, *tuples):
141+
edge_list = []
142+
for start, end in tuples:
143+
edge = make_edge_from_vertexs(start, end)
144+
edge_list.append(edge)
145+
return self.client.hmget(self.key, edge_list)
146+
147+
def get_all_edges(self):
148+
edges = self.client.hkeys(self.key)
149+
result = set()
150+
for edge in edges:
151+
start, end = decompose_vertexs_from_edge_name(edge)
152+
result.add((start, end))
153+
return result
154+
155+
def get_all_edges_with_weight(self):
156+
edges_and_weights = self.client.hgetall(self.key)
157+
result = set()
158+
for edge, weight in edges_and_weights.items():
159+
start, end = decompose_vertexs_from_edge_name(edge)
160+
result.add((start, end, weight))
161+
return result
162+
163+
client = Redis(decode_responses=True)
164+
graph = Graph(client, "test-graph")
165+
graph.add_edge("a", "b", 30)
166+
graph.add_edge("c", "b", 25)
167+
graph.add_multi_edges(("b", "d", 70), ("d", "e", 10))
168+
169+
print("edge a-> b weight:", graph.get_edge_weight("a", "b"))
170+
print("a->b 是否存在:", graph.has_edge("a", "b"))
171+
print("b->a 是否存在:", graph.has_edge("b", "a"))
172+
print("所有边:", graph.get_all_edges())
173+
print("所有边和权重", graph.get_all_edges_with_weight())
174+
```
175+
这里的图数据结构提供了边和权重的功能, 可以快速检查边是否存在, 能够方便的添加和移除边, 适合存储结点较多但是边较少的稀疏图(sparse graph).
176+
177+
### 示例: 使用散列键重新实现文章存储程序
178+
```Python
179+
from redis import Redis
180+
from time import time
181+
182+
class Article:
183+
def __init__(self, client, article_id):
184+
self.client = client
185+
self.article_id = str(article_id)
186+
self.article_hash = "article::" + self.article_hash
187+
def is_exists(self):
188+
return self.client.hexists(self.article_hash)
189+
def create(self, title, content, author):
190+
if self.is_exists():
191+
return False
192+
article_data = {
193+
"title": title,
194+
"content": content,
195+
"author": author,
196+
"created_at": time(),
197+
}
198+
return self.client.hset(self.article_hash, mapping=article_data)
199+
def get(self):
200+
article_data = self.client.hgetall(self.article_hash)
201+
article_data["id"] = self.article_id # 添加 id 到文章数据, 方便用户操作
202+
return article_data
203+
def update(self, title=None, content=None, author=None):
204+
if not self.is_exists():
205+
return False
206+
article_data = {}
207+
if title is not None:
208+
article_data["title"] = title
209+
if content is not None:
210+
article_data["content"] = content
211+
if author is not None:
212+
article_data["author"] = author
213+
return self.client.hset(self.article_hash, mapping=article_data)
214+
215+
client = Redis(decode_responses=True)
216+
article = Article(client, 10086)
217+
article.create("greeting", "hello world", "peter")
218+
```
219+
- 字符串有 MSET, MSETNX 命令, 但是并没有为散列提供 HMSET, HMSETNX 命令, 所以创建文章之前要先通过 `is_exists()` 方法检查文章是否存在, 再考虑是否使用 HMSET 命令进行设置.
220+
- 在使用散列存储文章数据的时候, 为了避免数据库中出现键名冲突, 需要为每个属性设置一个独一无二的键, 例如 article::10086::title 键存储 id 为10086 文章的标题.
221+
222+
## Wrapping Up
223+
string 和 hash 总结与对比
224+
- 资源占用: 字符串键在数量较多的时候, 将占用大量内存和CPU时间. 相反, 将多个数据项存储到一个散列中可以有效减少内存和CPU消耗
225+
- 支持的操作: 散列键支持的所有命令, 字符串键几乎都支持, 但字符串的 SETRANGE, GETRANGE 等操作散列不支持
226+
- 过期时间: 字符串键可以为每个键单独设置过期时间, 独立删除某个数据项, 而散列一但到期, 其所包含的所有字段和值都会被删除
227+

public/archives/index.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,13 @@
5151
<span class="max-w-[4rem] md:max-w-none truncate">Home</span></a></li><li class="flex items-center gap-1 md:gap-2 min-w-0"><span class="text-muted-foreground/50 flex-shrink-0"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
5252
</span><span class="text-foreground flex items-center gap-0.5 md:gap-1 font-medium min-w-0 flex-shrink-0"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg>
5353
<span class="max-w-[3rem] md:max-w-none truncate">Archives</span></span></li></ol></nav><header class=mb-8><div class="mb-4 flex items-center gap-3"><svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/></svg><h1 class="text-foreground text-3xl font-bold">Archives</h1></div><p class="text-muted-foreground mb-6">Browse all articles in chronological order and discover what interests you.</p><div class="text-muted-foreground flex items-center gap-4 text-sm"><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
54-
<span>22 posts total</span></div><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
55-
<span>Timeline view</span></div></div></header><div class=relative><div class="bg-border absolute top-0 bottom-0 left-4 w-0.5"></div><div class=mb-12><div class="relative mb-8 flex items-center"><div class="bg-primary absolute left-0 z-10 flex h-8 w-8 items-center justify-center rounded-full"><svg class="h-4 w-4 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg></div><div class=ml-12><h2 class="text-foreground text-2xl font-bold">2025</h2><p class="text-muted-foreground text-sm">20
56-
posts</p></div></div><div class="relative mb-8"><div class="relative mb-4 flex items-center"><div class="bg-accent border-background absolute left-2 z-10 h-4 w-4 rounded-full border-2"></div><div class=ml-12><h3 class="text-foreground text-lg font-semibold">August 2025</h3><p class="text-muted-foreground text-xs">20
57-
posts</p></div></div><div class="ml-12 space-y-3"><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/http-methods-status-codes-and-payloads/ class=block>HTTP Methods, Status Codes and Payloads</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
54+
<span>23 posts total</span></div><div class="flex items-center gap-1"><svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
55+
<span>Timeline view</span></div></div></header><div class=relative><div class="bg-border absolute top-0 bottom-0 left-4 w-0.5"></div><div class=mb-12><div class="relative mb-8 flex items-center"><div class="bg-primary absolute left-0 z-10 flex h-8 w-8 items-center justify-center rounded-full"><svg class="h-4 w-4 text-primary-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg></div><div class=ml-12><h2 class="text-foreground text-2xl font-bold">2025</h2><p class="text-muted-foreground text-sm">21
56+
posts</p></div></div><div class="relative mb-8"><div class="relative mb-4 flex items-center"><div class="bg-accent border-background absolute left-2 z-10 h-4 w-4 rounded-full border-2"></div><div class=ml-12><h3 class="text-foreground text-lg font-semibold">August 2025</h3><p class="text-muted-foreground text-xs">21
57+
posts</p></div></div><div class="ml-12 space-y-3"><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/redis-hash/ class=block>Redis Hash</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
58+
<time datetime=2025-08-21>08-21</time></div><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3A9 9 0 113 12a9 9 0 0118 0z"/></svg>
59+
<span>4
60+
min</span></div></div></div></div></article><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/http-methods-status-codes-and-payloads/ class=block>HTTP Methods, Status Codes and Payloads</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>
5861
<time datetime=2025-08-20>08-20</time></div><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3A9 9 0 113 12a9 9 0 0118 0z"/></svg>
5962
<span>5
6063
min</span></div></div></div></div></article><article class="group bg-card border-border hover:bg-accent/50 rounded-lg border p-4 transition-all duration-300"><div class="flex items-center justify-between gap-4"><div class="min-w-0 flex-1"><h4 class="text-foreground group-hover:text-primary mb-3 font-medium transition-colors duration-200"><a href=/posts/redis-string/ class=block>Redis String</a></h4><div class="text-muted-foreground flex items-center gap-4 text-xs"><div class="flex items-center gap-1"><svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"/></svg>

0 commit comments

Comments
 (0)