Redis 哈希
提示
来自deepseek解释
原文链接:https://redis.io/docs/latest/develop/data-types/hashes/
代码示例图例
下面的代码示例展示了如何在不同编程语言和客户端库中执行相同的操作:
- Redis CLI:Redis 命令行界面
- C#(同步):StackExchange.Redis 同步客户端
- C#(异步):StackExchange.Redis 异步客户端
- Go:go-redis 客户端
- Java(同步 - Jedis):Jedis 同步客户端
- Java(异步 - Lettuce):Lettuce 异步客户端
- Java(响应式 - Lettuce):Lettuce 响应式/流式客户端
- JavaScript(Node.js):node-redis 客户端
- PHP:Predis 客户端
- Python:redis-py 客户端
- Rust(同步):redis-rs 同步客户端
- Rust(异步):redis-rs 异步客户端
每个代码示例都演示了不同语言中相同的基本操作。具体语法和模式因语言和客户端库而异,但底层的 Redis 命令和行为保持一致。
哈希命令摘要
本组共 28 条命令:
| 命令 | 摘要 | 复杂度 | 起始版本 |
|---|---|---|---|
| HDEL | 从哈希中删除一个或多个字段及其值。如果不再有字段则删除哈希。 | O(N),其中 N 是要删除的字段数... | 2.0.0 |
| HEXISTS | 确定哈希中是否存在某个字段。 | O(1) | 2.0.0 |
| HEXPIRE | 使用相对时间(秒)为哈希字段设置过期时间 | O(N),其中 N 是指定字段的数量 | 7.4.0 |
| HEXPIREAT | 使用绝对 Unix 时间戳(秒)为哈希字段设置过期时间 | O(N),其中 N 是指定字段的数量 | 7.4.0 |
| HEXPIRETIME | 返回哈希字段的过期时间(Unix 时间戳,秒)。 | O(N),其中 N 是指定字段的数量 | 7.4.0 |
| HGET | 返回哈希中字段的值。 | O(1) | 2.0.0 |
| HGETALL | 返回哈希中的所有字段和值。 | O(N),其中 N 是哈希的大小。 | 2.0.0 |
| HGETDEL | 返回字段的值并从中删除该字段。 | O(N),其中 N 是指定字段的数量 | 8.0.0 |
| HGETEX | 获取给定哈希键的一个或多个字段的值,并可选择设置其过期时间。 | O(N),其中 N 是指定字段的数量 | 8.0.0 |
| HINCRBY | 将哈希中字段的整数值增加一个数值。如果字段不存在则使用 0 作为初始值。 | O(1) | 2.0.0 |
哈希命令摘要(第 2 部分)
| 命令 | 摘要 | 复杂度 | 起始版本 |
|---|---|---|---|
| HINCRBYFLOAT | 将字段的浮点数值增加一个数值。如果字段不存在则使用 0 作为初始值。 | O(1) | 2.6.0 |
| HKEYS | 返回哈希中的所有字段。 | O(N),其中 N 是哈希的大小。 | 2.0.0 |
| HLEN | 返回哈希中的字段数量。 | O(1) | 2.0.0 |
| HMGET | 返回哈希中所有字段的值。 | O(N),其中 N 是要请求的字段数... | 2.0.0 |
| HMSET | 设置多个字段的值。 | O(N),其中 N 是要设置的字段数。 | 2.0.0 |
| HPERSIST | 移除每个指定字段的过期时间 | O(N),其中 N 是指定字段的数量 | 7.4.0 |
| HPEXPIRE | 使用相对时间(毫秒)为哈希字段设置过期时间 | O(N),其中 N 是指定字段的数量 | 7.4.0 |
| HPEXPIREAT | 使用绝对 Unix 时间戳(毫秒)为哈希字段设置过期时间 | O(N),其中 N 是指定字段的数量 | 7.4.0 |
| HPEXPIRETIME | 返回哈希字段的过期时间(Unix 时间戳,毫秒)。 | O(N),其中 N 是指定字段的数量 | 7.4.0 |
| HPTTL | 返回哈希字段的 TTL(毫秒)。 | O(N),其中 N 是指定字段的数量 | 7.4.0 |
哈希命令摘要(第 3 部分)
| 命令 | 摘要 | 复杂度 | 起始版本 |
|---|---|---|---|
| HRANDFIELD | 从哈希中返回一个或多个随机字段。 | O(N),其中 N 是返回的字段数量 | 6.2.0 |
| HSCAN | 迭代哈希的字段和值。 | 每次调用 O(1)。完整迭代为 O(N)... | 2.8.0 |
| HSET | 创建或修改哈希中字段的值。 | 每个添加的字段/值对为 O(1),因此总复杂度为 O(N)... | 2.0.0 |
| HSETEX | 设置给定哈希键的一个或多个字段的值,并可选择设置其过期时间。 | O(N),其中 N 是要设置的字段数。 | 8.0.0 |
| HSETNX | 仅当字段不存在时设置哈希中字段的值。 | O(1) | 2.0.0 |
| HSTRLEN | 返回字段值的长度。 | O(1) | 3.2.0 |
| HTTL | 返回哈希字段的 TTL(秒)。 | O(N),其中 N 是指定字段的数量 | 7.4.0 |
| HVALS | 返回哈希中的所有值。 | O(N),其中 N 是哈希的大小。 | 2.0.0 |
Redis 哈希是结构化为字段-值对集合的记录类型。您可以使用哈希来表示基本对象、存储计数器分组等。
基础:使用 HSET 和 HGET 设置和检索哈希字段(覆盖已有字段值)
命令: HSET, HGET, HGETALL
复杂度:
- HSET: O(1)
- HGET: O(1)
- HGETALL: O(N)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python, Ruby, Rust (异步), Rust (同步)
Redis CLI
> HSET bike:1 model Deimos brand Ergonom type 'Enduro bikes' price 4972
(integer) 4
> HGET bike:1 model
"Deimos"
> HGET bike:1 price
"4972"
> HGETALL bike:1
1) "model"
2) "Deimos"
3) "brand"
4) "Ergonom"
5) "type"
6) "Enduro bikes"
7) "price"
8) "4972"C#
db.HashSet("bike:1", [
new("model", "Deimos"),
new("brand", "Ergonom"),
new("type", "Enduro bikes"),
new("price", 4972)
]);
Console.WriteLine("Hash Created");
// Hash Created
var model = db.HashGet("bike:1", "model");
Console.WriteLine($"Model: {model}");
// Model: Deimos
var price = db.HashGet("bike:1", "price");
Console.WriteLine($"Price: {price}");
// Price: 4972
var bike = db.HashGetAll("bike:1");
Console.WriteLine("bike:1");
Console.WriteLine(string.Join("\n", bike.Select(b => $"{b.Name}: {b.Value}")));
// Bike:1:
// model: Deimos
// brand: Ergonom
// type: Enduro bikes
// price: 4972Go
hashFields := []string{
"model", "Deimos",
"brand", "Ergonom",
"type", "Enduro bikes",
"price", "4972",
}
res1, err := rdb.HSet(ctx, "bike:1", hashFields).Result()
if err != nil {
panic(err)
}
fmt.Println(res1) // >>> 4
res2, err := rdb.HGet(ctx, "bike:1", "model").Result()
if err != nil {
panic(err)
}
fmt.Println(res2) // >>> Deimos
res3, err := rdb.HGet(ctx, "bike:1", "price").Result()
if err != nil {
panic(err)
}
fmt.Println(res3) // >>> 4972
cmdReturn := rdb.HGetAll(ctx, "bike:1")
res4, err := cmdReturn.Result()
if err != nil {
panic(err)
}
fmt.Println(res4)
// >>> map[brand:Ergonom model:Deimos price:4972 type:Enduro bikes]
type BikeInfo struct {
Model string `redis:"model"`
Brand string `redis:"brand"`
Type string `redis:"type"`
Price int `redis:"price"`
}
var res4a BikeInfo
if err := cmdReturn.Scan(&res4a); err != nil {
panic(err)
}
fmt.Printf("Model: %v, Brand: %v, Type: %v, Price: $%v\n",
res4a.Model, res4a.Brand, res4a.Type, res4a.Price)
// >>> Model: Deimos, Brand: Ergonom, Type: Enduro bikes, Price: $4972Java(异步 - Lettuce)
Map<String, String> bike1 = new HashMap<>();
bike1.put("model", "Deimos");
bike1.put("brand", "Ergonom");
bike1.put("type", "Enduro bikes");
bike1.put("price", "4972");
CompletableFuture<Void> setGetAll = asyncCommands.hset("bike:1", bike1).thenCompose(res1 -> {
System.out.println(res1); // >>> 4
return asyncCommands.hget("bike:1", "model");
}).thenCompose(res2 -> {
System.out.println(res2); // >>> Deimos
return asyncCommands.hget("bike:1", "price");
}).thenCompose(res3 -> {
System.out.println(res3); // >>> 4972
return asyncCommands.hgetall("bike:1");
})
.thenAccept(System.out::println)
// >>> {type=Enduro bikes, brand=Ergonom, price=4972, model=Deimos}
.toCompletableFuture();Java(响应式 - Lettuce)
Map<String, String> bike1 = new HashMap<>();
bike1.put("model", "Deimos");
bike1.put("brand", "Ergonom");
bike1.put("type", "Enduro bikes");
bike1.put("price", "4972");
Mono<Long> setGetAll = reactiveCommands.hset("bike:1", bike1).doOnNext(result -> {
System.out.println(result); // >>> 4
});
setGetAll.block();
Mono<String> getModel = reactiveCommands.hget("bike:1", "model").doOnNext(result -> {
System.out.println(result); // >>> Deimos
});
Mono<String> getPrice = reactiveCommands.hget("bike:1", "price").doOnNext(result -> {
System.out.println(result); // >>> 4972
});
Mono<List<KeyValue<String, String>>> getAll = reactiveCommands.hgetall("bike:1").collectList().doOnNext(result -> {
System.out.println(result);
// >>> [KeyValue[type, Enduro bikes], KeyValue[brand, Ergonom],
// KeyValue[price, 4972], KeyValue[model, Deimos]]
});Java(同步 - Jedis)
Map<String, String> bike1 = new HashMap<>();
bike1.put("model", "Deimos");
bike1.put("brand", "Ergonom");
bike1.put("type", "Enduro bikes");
bike1.put("price", "4972");
Long res1 = jedis.hset("bike:1", bike1);
System.out.println(res1); // 4
String res2 = jedis.hget("bike:1", "model");
System.out.println(res2); // Deimos
String res3 = jedis.hget("bike:1", "price");
System.out.println(res3); // 4972
Map<String, String> res4 = jedis.hgetAll("bike:1");
System.out.println(res4); // {type=Enduro bikes, brand=Ergonom, price=4972, model=Deimos}JavaScript(Node.js)
const res1 = await client.hSet(
'bike:1',
{
'model': 'Deimos',
'brand': 'Ergonom',
'type': 'Enduro bikes',
'price': 4972,
}
)
console.log(res1) // 4
const res2 = await client.hGet('bike:1', 'model')
console.log(res2) // 'Deimos'
const res3 = await client.hGet('bike:1', 'price')
console.log(res3) // '4972'
const res4 = await client.hGetAll('bike:1')
console.log(res4)
/*
{
brand: 'Ergonom',
model: 'Deimos',
price: '4972',
type: 'Enduro bikes'
}
*/PHP
$res1 = $r->hmset('bike:1', [
'model' => 'Deimos',
'brand' => 'Ergonom',
'type' => 'Enduro bikes',
'price' => 4972,
]);
echo $res1 . PHP_EOL;
// >>> 4
$res2 = $r->hget('bike:1', 'model');
echo $res2 . PHP_EOL;
// >>> Deimos
$res3 = $r->hget('bike:1', 'price');
echo $res3 . PHP_EOL;
// >>> 4972
$res4 = $r->hgetall('bike:1');
echo json_encode($res3) . PHP_EOL;
// >>> {"name":"Deimos","brand":"Ergonom","type":"Enduro bikes","price":"4972"}Python
res1 = r.hset(
"bike:1",
mapping={
"model": "Deimos",
"brand": "Ergonom",
"type": "Enduro bikes",
"price": 4972,
},
)
print(res1)
# >>> 4
res2 = r.hget("bike:1", "model")
print(res2)
# >>> 'Deimos'
res3 = r.hget("bike:1", "price")
print(res3)
# >>> '4972'
res4 = r.hgetall("bike:1")
print(res4)
# >>> {'model': 'Deimos', 'brand': 'Ergonom', 'type': 'Enduro bikes', 'price': '4972'}Ruby
res1 = r.hset('bike:1', {
'model' => 'Deimos',
'brand' => 'Ergonom',
'type' => 'Enduro bikes',
'price' => 4972
})
puts res1 # 4
res2 = r.hget('bike:1', 'model')
puts res2 # Deimos
res3 = r.hget('bike:1', 'price')
puts res3 # 4972
res4 = r.hgetall('bike:1')
puts res4.inspect
# {"model"=>"Deimos", "brand"=>"Ergonom", "type"=>"Enduro bikes", "price"=>"4972"}Rust(异步)
let hash_fields = [
("model", "Deimos"),
("brand", "Ergonom"),
("type", "Enduro bikes"),
("price", "4972"),
];
if let Ok(res) = r.hset_multiple("bike:1", &hash_fields).await {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.hget("bike:1", "model").await {
Ok(res) => {
let res: String = res;
println!("{res}"); // >>> Deimos
},
Err(e) => {
println!("Error getting bike:1 model: {e}");
return;
}
};
match r.hget("bike:1", "price").await {
Ok(res) => {
let res: String = res;
println!("{res}"); // >>> 4972
},
Err(e) => {
println!("Error getting bike:1 price: {e}");
return;
}
};
match r.hgetall("bike:1").await {
Ok(res) => {
let res: Vec<(String, String)> = res;
println!("{res:?}");
// >>> [("model", "Deimos"), ("brand", "Ergonom"), ("type", "Enduro bikes"), ("price", "4972")]
},
Err(e) => {
println!("Error getting bike:1: {e}");
return;
}
};Rust(同步)
let hash_fields = [
("model", "Deimos"),
("brand", "Ergonom"),
("type", "Enduro bikes"),
("price", "4972"),
];
if let Ok(res) = r.hset_multiple("bike:1", &hash_fields) {
let res: String = res;
println!("{res}"); // >>> OK
}
match r.hget("bike:1", "model") {
Ok(res) => {
let res: String = res;
println!("{res}"); // >>> Deimos
},
Err(e) => {
println!("Error getting bike:1 model: {e}");
return;
}
};
match r.hget("bike:1", "price") {
Ok(res) => {
let res: String = res;
println!("{res}"); // >>> 4972
},
Err(e) => {
println!("Error getting bike:1 price: {e}");
return;
}
};
match r.hgetall("bike:1") {
Ok(res) => {
let res: Vec<(String, String)> = res;
println!("{res:?}");
// >>> [("model", "Deimos"), ("brand", "Ergonom"), ("type", "Enduro bikes"), ("price", "4972")]
},
Err(e) => {
println!("Error getting bike:1: {e}");
return;
}
};虽然哈希方便表示对象,但实际上哈希中可以放入的字段数量没有实际限制(除了可用内存),因此您可以在应用程序中以多种不同方式使用哈希。
HSET 命令设置哈希的多个字段,而 HGET 检索单个字段。HMGET 类似于 HGET,但返回一个值数组:
当需要减少与服务器的往返次数时,使用 HMGET 从哈希中检索多个字段值
构建基础: set_get_all
命令: HMGET
复杂度:
- HMGET: O(N)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python, Ruby, Rust (异步), Rust (同步)
Redis CLI
> HMGET bike:1 model price no-such-field
1) "Deimos"
2) "4972"
3) (nil)C#
var values = db.HashGet("bike:1", ["model", "price"]);
Console.WriteLine(string.Join(" ", values));
// Deimos 4972Go
cmdReturn := rdb.HMGet(ctx, "bike:1", "model", "price")
res5, err := cmdReturn.Result()
if err != nil {
panic(err)
}
fmt.Println(res5) // >>> [Deimos 4972]
type BikeInfo struct {
Model string `redis:"model"`
Brand string `redis:"-"`
Type string `redis:"-"`
Price int `redis:"price"`
}
var res5a BikeInfo
if err := cmdReturn.Scan(&res5a); err != nil {
panic(err)
}
fmt.Printf("Model: %v, Price: $%v\n", res5a.Model, res5a.Price)
// >>> Model: Deimos, Price: $4972Java(异步 - Lettuce)
CompletableFuture<Void> hmGet = setGetAll.thenCompose(res4 -> {
return asyncCommands.hmget("bike:1", "model", "price");
})
.thenAccept(System.out::println)
// [KeyValue[model, Deimos], KeyValue[price, 4972]]
.toCompletableFuture();Java(响应式 - Lettuce)
Mono<List<KeyValue<String, String>>> hmGet = reactiveCommands.hmget("bike:1", "model", "price").collectList()
.doOnNext(result -> {
System.out.println(result);
// >>> [KeyValue[model, Deimos], KeyValue[price, 4972]]
});Java(同步 - Jedis)
List<String> res5 = jedis.hmget("bike:1", "model", "price");
System.out.println(res5); // [Deimos, 4972]JavaScript(Node.js)
const res5 = await client.hmGet('bike:1', ['model', 'price'])
console.log(res5) // ['Deimos', '4972']PHP
$res5 = $r->hmget('bike:1', ['model', 'price']);
echo json_encode($res5) . PHP_EOL;
// >>> ["Deimos","4972"]Python
res5 = r.hmget("bike:1", ["model", "price"])
print(res5)
# >>> ['Deimos', '4972']Ruby
res5 = r.hmget('bike:1', 'model', 'price', 'no-such-field')
puts res5.inspect # ["Deimos", "4972", nil]Rust(异步)
match r.hmget("bike:1", &["model", "price"]).await {
Ok(res) => {
let res: Vec<String> = res;
println!("{res:?}"); // >>> ["Deimos", "4972"]
},
Err(e) => {
println!("Error getting bike:1: {e}");
return;
}
};Rust(同步)
match r.hmget("bike:1", &["model", "price"]) {
Ok(res) => {
let res: Vec<String> = res;
println!("{res:?}"); // >>> ["Deimos", "4972"]
},
Err(e) => {
println!("Error getting bike:1: {e}");
return;
}
};还有一些命令能够对单个字段执行操作,例如 HINCRBY:
使用 HINCRBY 递增计数器的哈希字段值(如果字段不存在则创建,初始化为 0)
构建基础: set_get_all
命令: HINCRBY
复杂度:
- HINCRBY: O(1)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python, Ruby, Rust (异步), Rust (同步)
Redis CLI
> HINCRBY bike:1 price 100
(integer) 5072
> HINCRBY bike:1 price -100
(integer) 4972C#
var newPrice = db.HashIncrement("bike:1", "price", 100);
Console.WriteLine($"New price: {newPrice}");
// New price: 5072
newPrice = db.HashIncrement("bike:1", "price", -100);
Console.WriteLine($"New price: {newPrice}");
// New price: 4972Go
res6, err := rdb.HIncrBy(ctx, "bike:1", "price", 100).Result()
if err != nil {
panic(err)
}
fmt.Println(res6) // >>> 5072
res7, err := rdb.HIncrBy(ctx, "bike:1", "price", -100).Result()
if err != nil {
panic(err)
}
fmt.Println(res7) // >>> 4972Java(异步 - Lettuce)
CompletableFuture<Void> hIncrBy = hmGet.thenCompose(r -> {
return asyncCommands.hincrby("bike:1", "price", 100);
}).thenCompose(res6 -> {
System.out.println(res6); // >>> 5072
return asyncCommands.hincrby("bike:1", "price", -100);
})
.thenAccept(System.out::println)
// >>> 4972
.toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> hIncrBy = reactiveCommands.hincrby("bike:1", "price", 100).doOnNext(result -> {
System.out.println(result); // >>> 5072
}).flatMap(v -> reactiveCommands.hincrby("bike:1", "price", -100)).doOnNext(result -> {
System.out.println(result); // >>> 4972
}).then();Java(同步 - Jedis)
Long res6 = jedis.hincrBy("bike:1", "price", 100);
System.out.println(res6); // 5072
Long res7 = jedis.hincrBy("bike:1", "price", -100);
System.out.println(res7); // 4972JavaScript(Node.js)
const res6 = await client.hIncrBy('bike:1', 'price', 100)
console.log(res6) // 5072
const res7 = await client.hIncrBy('bike:1', 'price', -100)
console.log(res7) // 4972PHP
$res6 = $r->hincrby('bike:1', 'price', 100);
echo $res6 . PHP_EOL;
// >>> 5072
$res7 = $r->hincrby('bike:1', 'price', -100);
echo $res7 . PHP_EOL;
// >>> 4972Python
res6 = r.hincrby("bike:1", "price", 100)
print(res6)
# >>> 5072
res7 = r.hincrby("bike:1", "price", -100)
print(res7)
# >>> 4972Ruby
res6 = r.hincrby('bike:1', 'price', 100)
puts res6 # 5072
res7 = r.hincrby('bike:1', 'price', -100)
puts res7 # 4972Rust(异步)
if let Ok(res) = r.hincr("bike:1", "price", 100).await {
let res: i32 = res;
println!("{res}"); // >>> 5072
}
if let Ok(res) = r.hincr("bike:1", "price", -100).await {
let res: i32 = res;
println!("{res}"); // >>> 4972
}Rust(同步)
if let Ok(res) = r.hincr("bike:1", "price", 100) {
let res: i32 = res;
println!("{res}"); // >>> 5072
}
if let Ok(res) = r.hincr("bike:1", "price", -100) {
let res: i32 = res;
println!("{res}"); // >>> 4972
}您可以在文档中找到完整的哈希命令列表。
值得注意的是,小哈希(即具有少量小值的元素)在内存中以特殊方式编码,使其非常节省内存。
示例
- 存储
bike:1被骑乘、碰撞或更换主人的次数计数器:
实用模式:结合 HINCRBY 和 HMGET 跟踪多个计数器,实现跨多个字段的原子更新
难度: 中级
构建基础: hincrby, hmget
命令: HINCRBY, HGET, HMGET
复杂度:
- HINCRBY: O(1)
- HGET: O(1)
- HMGET: O(N)
可用客户端: Redis CLI, C#, Go, Java (异步 - Lettuce), Java (响应式 - Lettuce), Java (同步 - Jedis), JavaScript (Node.js), PHP, Python, Ruby, Rust (异步), Rust (同步)
Redis CLI
> HINCRBY bike:1:stats rides 1
(integer) 1
> HINCRBY bike:1:stats rides 1
(integer) 2
> HINCRBY bike:1:stats rides 1
(integer) 3
> HINCRBY bike:1:stats crashes 1
(integer) 1
> HINCRBY bike:1:stats owners 1
(integer) 1
> HGET bike:1:stats rides
"3"
> HMGET bike:1:stats owners crashes
1) "1"
2) "1"C#
var rides = db.HashIncrement("bike:1", "rides");
Console.WriteLine($"Rides: {rides}");
// Rides: 1
rides = db.HashIncrement("bike:1", "rides");
Console.WriteLine($"Rides: {rides}");
// Rides: 2
rides = db.HashIncrement("bike:1", "rides");
Console.WriteLine($"Rides: {rides}");
// Rides: 3
var crashes = db.HashIncrement("bike:1", "crashes");
Console.WriteLine($"Crashes: {crashes}");
// Crashes: 1
var owners = db.HashIncrement("bike:1", "owners");
Console.WriteLine($"Owners: {owners}");
// Owners: 1
var stats = db.HashGet("bike:1", ["crashes", "owners"]);
Console.WriteLine($"Bike stats: crashes={stats[0]}, owners={stats[1]}");
// Bike stats: crashes=1, owners=1Go
res8, err := rdb.HIncrBy(ctx, "bike:1:stats", "rides", 1).Result()
if err != nil {
panic(err)
}
fmt.Println(res8) // >>> 1
res9, err := rdb.HIncrBy(ctx, "bike:1:stats", "rides", 1).Result()
if err != nil {
panic(err)
}
fmt.Println(res9) // >>> 2
res10, err := rdb.HIncrBy(ctx, "bike:1:stats", "rides", 1).Result()
if err != nil {
panic(err)
}
fmt.Println(res10) // >>> 3
res11, err := rdb.HIncrBy(ctx, "bike:1:stats", "crashes", 1).Result()
if err != nil {
panic(err)
}
fmt.Println(res11) // >>> 1
res12, err := rdb.HIncrBy(ctx, "bike:1:stats", "owners", 1).Result()
if err != nil {
panic(err)
}
fmt.Println(res12) // >>> 1
res13, err := rdb.HGet(ctx, "bike:1:stats", "rides").Result()
if err != nil {
panic(err)
}
fmt.Println(res13) // >>> 3
res14, err := rdb.HMGet(ctx, "bike:1:stats", "crashes", "owners").Result()
if err != nil {
panic(err)
}
fmt.Println(res14) // >>> [1 1]Java(异步 - Lettuce)
CompletableFuture<Void> incrByGetMget = asyncCommands.hincrby("bike:1:stats", "rides", 1).thenCompose(res7 -> {
System.out.println(res7); // >>> 1
return asyncCommands.hincrby("bike:1:stats", "rides", 1);
}).thenCompose(res8 -> {
System.out.println(res8); // >>> 2
return asyncCommands.hincrby("bike:1:stats", "rides", 1);
}).thenCompose(res9 -> {
System.out.println(res9); // >>> 3
return asyncCommands.hincrby("bike:1:stats", "crashes", 1);
}).thenCompose(res10 -> {
System.out.println(res10); // >>> 1
return asyncCommands.hincrby("bike:1:stats", "owners", 1);
}).thenCompose(res11 -> {
System.out.println(res11); // >>> 1
return asyncCommands.hget("bike:1:stats", "rides");
}).thenCompose(res12 -> {
System.out.println(res12); // >>> 3
return asyncCommands.hmget("bike:1:stats", "crashes", "owners");
})
.thenAccept(System.out::println)
// >>> [KeyValue[crashes, 1], KeyValue[owners, 1]]
.toCompletableFuture();Java(响应式 - Lettuce)
Mono<Void> incrByGetMget = reactiveCommands.hincrby("bike:1:stats", "rides", 1).doOnNext(result -> {
System.out.println(result); // >>> 1
}).flatMap(v -> reactiveCommands.hincrby("bike:1:stats", "rides", 1)).doOnNext(result -> {
System.out.println(result); // >>> 2
}).flatMap(v -> reactiveCommands.hincrby("bike:1:stats", "rides", 1)).doOnNext(result -> {
System.out.println(result); // >>> 3
}).flatMap(v -> reactiveCommands.hincrby("bike:1:stats", "crashes", 1)).doOnNext(result -> {
System.out.println(result); // >>> 1
}).flatMap(v -> reactiveCommands.hincrby("bike:1:stats", "owners", 1)).doOnNext(result -> {
System.out.println(result); // >>> 1
}).then();
incrByGetMget.block();
Mono<String> getRides = reactiveCommands.hget("bike:1:stats", "rides").doOnNext(result -> {
System.out.println(result); // >>> 3
});
Mono<List<KeyValue<String, String>>> getCrashesOwners = reactiveCommands.hmget("bike:1:stats", "crashes", "owners")
.collectList().doOnNext(result -> {
System.out.println(result);
// >>> [KeyValue[crashes, 1], KeyValue[owners, 1]]
});Java(同步 - Jedis)
Long res8 = jedis.hincrBy("bike:1:stats", "rides", 1);
System.out.println(res8); // 1
Long res9 = jedis.hincrBy("bike:1:stats", "rides", 1);
System.out.println(res9); // 2
Long res10 = jedis.hincrBy("bike:1:stats", "rides", 1);
System.out.println(res10); // 3
Long res11 = jedis.hincrBy("bike:1:stats", "crashes", 1);
System.out.println(res11); // 1
Long res12 = jedis.hincrBy("bike:1:stats", "owners", 1);
System.out.println(res12); // 1
String res13 = jedis.hget("bike:1:stats", "rides");
System.out.println(res13); // 3
List<String> res14 = jedis.hmget("bike:1:stats", "crashes", "owners");
System.out.println(res14); // [1, 1]JavaScript(Node.js)
import assert from 'assert';
import { createClient } from 'redis';
const client = createClient();
await client.connect();
const res1 = await client.hSet(
'bike:1',
{
'model': 'Deimos',
'brand': 'Ergonom',
'type': 'Enduro bikes',
'price': 4972,
}
)
console.log(res1) // 4
const res2 = await client.hGet('bike:1', 'model')
console.log(res2) // 'Deimos'
const res3 = await client.hGet('bike:1', 'price')
console.log(res3) // '4972'
const res4 = await client.hGetAll('bike:1')
console.log(res4)
/*
{
brand: 'Ergonom',
model: 'Deimos',
price: '4972',
type: 'Enduro bikes'
}
*/
const res5 = await client.hmGet('bike:1', ['model', 'price'])
console.log(res5) // ['Deimos', '4972']
const res6 = await client.hIncrBy('bike:1', 'price', 100)
console.log(res6) // 5072
const res7 = await client.hIncrBy('bike:1', 'price', -100)
console.log(res7) // 4972
const res11 = await client.hIncrBy('bike:1:stats', 'rides', 1)
console.log(res11) // 1
const res12 = await client.hIncrBy('bike:1:stats', 'rides', 1)
console.log(res12) // 2
const res13 = await client.hIncrBy('bike:1:stats', 'rides', 1)
console.log(res13) // 3
const res14 = await client.hIncrBy('bike:1:stats', 'crashes', 1)
console.log(res14) // 1
const res15 = await client.hIncrBy('bike:1:stats', 'owners', 1)
console.log(res15) // 1
const res16 = await client.hGet('bike:1:stats', 'rides')
console.log(res16) // 3
const res17 = await client.hmGet('bike:1:stats', ['crashes', 'owners'])
console.log(res17) // ['1', '1']PHP
$res8 = $r->hincrby('bike:1:stats', 'rides', 1);
echo $res8 . PHP_EOL;
// >>> 1
$res9 = $r->hincrby('bike:1:stats', 'rides', 1);
echo $res9 . PHP_EOL;
// >>> 2
$res10 = $r->hincrby('bike:1:stats', 'rides', 1);
echo $res10 . PHP_EOL;
// >>> 3
$res11 = $r->hincrby('bike:1:stats', 'crashes', 1);
echo $res11 . PHP_EOL;
// >>> 1
$res12 = $r->hincrby('bike:1:stats', 'owners', 1);
echo $res12 . PHP_EOL;
// >>> 1
$res13 = $r->hget('bike:1:stats', 'rides');
echo $res13 . PHP_EOL;
// >>> 3
$res14 = $r->hmget('bike:1:stats', ['crashes', 'owners']);
echo json_encode($res14) . PHP_EOL;
// >>> ["1","1"]Python
res11 = r.hincrby("bike:1:stats", "rides", 1)
print(res11)
# >>> 1
res12 = r.hincrby("bike:1:stats", "rides", 1)
print(res12)
# >>> 2
res13 = r.hincrby("bike:1:stats", "rides", 1)
print(res13)
# >>> 3
res14 = r.hincrby("bike:1:stats", "crashes", 1)
print(res14)
# >>> 1
res15 = r.hincrby("bike:1:stats", "owners", 1)
print(res15)
# >>> 1
res16 = r.hget("bike:1:stats", "rides")
print(res16)
# >>> 3
res17 = r.hmget("bike:1:stats", ["crashes", "owners"])
print(res17)
# >>> ['1', '1']Ruby
res8 = r.hincrby('bike:1:stats', 'rides', 1)
puts res8 # 1
res9 = r.hincrby('bike:1:stats', 'rides', 1)
puts res9 # 2
res10 = r.hincrby('bike:1:stats', 'rides', 1)
puts res10 # 3
res11 = r.hincrby('bike:1:stats', 'crashes', 1)
puts res11 # 1
res12 = r.hincrby('bike:1:stats', 'owners', 1)
puts res12 # 1
res13 = r.hget('bike:1:stats', 'rides')
puts res13 # 3
res14 = r.hmget('bike:1:stats', 'owners', 'crashes')
puts res14.inspect # ["1", "1"]Rust(异步)
if let Ok(res) = r.hincr("bike:1:stats", "rides", 1).await {
let res: i32 = res;
println!("{res}"); // >>> 1
}
if let Ok(res) = r.hincr("bike:1:stats", "rides", 1).await {
let res: i32 = res;
println!("{res}"); // >>> 2
}
if let Ok(res) = r.hincr("bike:1:stats", "rides", 1).await {
let res: i32 = res;
println!("{res}"); // >>> 3
}
if let Ok(res) = r.hincr("bike:1:stats", "crashes", 1).await {
let res: i32 = res;
println!("{res}"); // >>> 1
}
if let Ok(res) = r.hincr("bike:1:stats", "owners", 1).await {
let res: i32 = res;
println!("{res}"); // >>> 1
}
match r.hget("bike:1:stats", "rides").await {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 3
},
Err(e) => {
println!("Error getting bike:1:stats rides: {e}");
return;
}
};
match r.hmget("bike:1:stats", &["crashes", "owners"]).await {
Ok(res) => {
let res: Vec<i32> = res;
println!("{res:?}"); // >>> [1, 1]
},
Err(e) => {
println!("Error getting bike:1:stats crashes and owners: {e}");
return;
}
};Rust(同步)
if let Ok(res) = r.hincr("bike:1:stats", "rides", 1) {
let res: i32 = res;
println!("{res}"); // >>> 1
}
if let Ok(res) = r.hincr("bike:1:stats", "rides", 1) {
let res: i32 = res;
println!("{res}"); // >>> 2
}
if let Ok(res) = r.hincr("bike:1:stats", "rides", 1) {
let res: i32 = res;
println!("{res}"); // >>> 3
}
if let Ok(res) = r.hincr("bike:1:stats", "crashes", 1) {
let res: i32 = res;
println!("{res}"); // >>> 1
}
if let Ok(res) = r.hincr("bike:1:stats", "owners", 1) {
let res: i32 = res;
println!("{res}"); // >>> 1
}
match r.hget("bike:1:stats", "rides") {
Ok(res) => {
let res: i32 = res;
println!("{res}"); // >>> 3
},
Err(e) => {
println!("Error getting bike:1:stats rides: {e}");
return;
}
};
match r.hmget("bike:1:stats", &["crashes", "owners"]) {
Ok(res) => {
let res: Vec<i32> = res;
println!("{res:?}"); // >>> [1, 1]
},
Err(e) => {
println!("Error getting bike:1:stats crashes and owners: {e}");
return;
}
};字段过期
Redis 7.4 引入了为单个哈希字段指定过期时间或生存时间(TTL)值的能力。此功能与键过期类似,并包含许多类似命令。
使用以下命令为特定字段设置确切的过期时间或 TTL 值:
HEXPIRE:设置剩余的 TTL(秒)。HPEXPIRE:设置剩余的 TTL(毫秒)。HEXPIREAT:将过期时间设置为以秒为单位的时间戳[^1]。HPEXPIREAT:将过期时间设置为以毫秒为单位的时间戳。
[^1]:所有时间戳均以自 Unix 纪元 以来的秒数或毫秒数指定。
使用以下命令检索特定字段将过期的确切时间或剩余 TTL:
HEXPIRETIME:获取过期时间(以秒为单位的时间戳)。HPEXPIRETIME:获取过期时间(以毫秒为单位的时间戳)。HTTL:获取剩余的 TTL(秒)。HPTTL:获取剩余的 TTL(毫秒)。
使用以下命令移除特定字段的过期时间:
HPERSIST:移除过期时间。
Redis 8.0 引入了以下命令:
常见字段过期使用场景
事件跟踪:使用哈希键存储过去一小时的事件。将每个事件的 TTL 设置为一小时。使用
HLEN统计过去一小时的事件数。欺诈检测:创建包含每小时计数器的哈希。将每个字段的 TTL 设置为 48 小时。查询哈希以获取过去 48 小时每小时的事件数。
客户会话管理:将客户数据存储在哈希键中。为每个会话创建一个新的哈希键,并在客户的哈希键中添加一个会话字段。当会话过期时,同时使会话键和客户哈希键中的会话字段自动过期。
活跃会话跟踪:将所有活跃会话存储在一个哈希键中。将每个会话的 TTL 设置为不活动后自动过期。使用
HLEN统计活跃会话数。
字段过期示例
官方客户端库尚不支持哈希字段过期,但您可以使用 Python (redis-py) 和 Java (Jedis) 客户端库的测试版来测试哈希字段过期。
以下是一些演示如何使用字段过期的 Python 示例。
考虑一个用于存储传感器数据的哈希数据集,其结构如下:
event = {
'air_quality': 256,
'battery_level':89
}
r.hset('sensor:sensor1', mapping=event)在下面的示例中,您可能需要在字段过期后刷新 sensor:sensor1 键。
设置和检索哈希中多个字段的 TTL:
# 将两个哈希字段的 TTL 设置为 60 秒
r.hexpire('sensor:sensor1', 60, 'air_quality', 'battery_level')
ttl = r.httl('sensor:sensor1', 'air_quality', 'battery_level')
print(ttl)
# 打印 [60, 60]设置和检索哈希字段的 TTL(毫秒):
# 以毫秒为单位设置 'air_quality' 字段的 TTL
r.hpexpire('sensor:sensor1', 60000, 'air_quality')
# 并检索它
pttl = r.hpttl('sensor:sensor1', 'air_quality')
print(pttl)
# 打印 [59994] # 您的实际值可能有所不同设置和检索哈希字段的过期时间戳:
# 将 'air_quality' 的过期时间设置为现在 + 24 小时
# (类似于将 TTL 设置为 24 小时)
r.hexpireat('sensor:sensor1',
datetime.now() + timedelta(hours=24),
'air_quality')
# 并检索它
expire_time = r.hexpiretime('sensor:sensor1', 'air_quality')
print(expire_time)
# 打印 [1717668041] # 您的实际值可能有所不同性能
大多数 Redis 哈希命令为 O(1)。
少数命令,如 HKEYS、HVALS、HGETALL 以及大多数过期相关命令,为 O(n),其中 n 是字段-值对的数量。
限制
每个哈希最多可以存储 4,294,967,295(2^32 - 1)个字段-值对。实际上,您的哈希仅受托管 Redis 部署的虚拟机整体内存限制。
了解更多
- Redis Hashes Explained 是一个关于 Redis 哈希的简短、全面的视频讲解。
- Redis University 的 RU101 详细介绍了 Redis 哈希。