Redis协议详解及其异步应用
目录
- 一、Redis Pipeline(管道)
- 概述
- 优点
- 使用场景
- 工作原理
- Pipeline 的基本操作步骤
- C++ 示例(使用 [hiredis](https://github.com/redis/hiredis) 库)
- 二、Redis 事务
- 概述
- 事务的前提
- 事务特征(ACID 分析)
- WATCH 命令
- 示例 1:事务实现 `zpop`
- 示例 2:事务实现加倍操作
- 三、Lua 脚本
- Lua 脚本的事务特性分析
- 基本命令
- 应用示例
- 示例:执行加倍操作
- 四、Redis 发布订阅(Pub/Sub)
- 主要命令
- 应用场景
- C++ 示例(使用 [hiredis](https://github.com/redis/hiredis) 库)
- 发布者示例
- 订阅者示例
- 5. Redis 异步方式
- 移步:[Redis异步实现解析](https://blog.csdn.net/weixin_43925427/article/details/142876618?fromshare=blogdetail&sharetype=blogdetail&sharerId=142876618&sharerefer=PC&sharesource=weixin_43925427&sharefrom=from_link)
- 六、Redis 的缺点
- 七、总结
- Redis 的优势
- Redis 的局限性
- 应用建议
- 参考
一、Redis Pipeline(管道)
概述
Redis Pipeline(管道) 是一种客户端机制,允许一次性发送多个Redis命令到服务器,而无需等待每个命令的响应。通过管道,可以显著减少网络延迟,提高命令执行的吞吐量。
优点
- 减少网络延迟:批量发送命令,减少网络往返次数(RTT)。
- 提高吞吐量:一次性处理多个命令,提升操作效率。
使用场景
- 批量插入或更新数据:如批量存储用户信息或日志数据。
- 批量读取数据:如同时获取多个键的值。
工作原理

传统上,每个Redis命令在客户端和服务器之间都需要一次网络往返通信。当需要执行大量命令时,这种通信开销会成为性能瓶颈。使用Redis Pipeline,可以将多个命令打包成一个网络请求一次性发送给服务器,减少网络开销,并更充分地利用服务器的处理能力。

Pipeline 的基本操作步骤
- 创建 Pipeline 对象:在客户端中创建一个 Pipeline 对象,用于存储要执行的多个命令。
- 向 Pipeline 中添加命令:使用 Pipeline 对象的方法(如
pipeline.set(key, value))向其中添加要执行的Redis命令。可以添加任意多个命令。 - 执行 Pipeline:调用 Pipeline 对象的
execute()方法,将 Pipeline 中的所有命令一次性发送给Redis服务器执行。 - 获取结果:通过遍历 Pipeline 中的命令结果,或使用
execute()方法的返回值来获取执行结果。
C++ 示例(使用 hiredis 库)
以下示例展示了如何使用 hiredis 库在C++中实现Redis Pipeline:
#include <hiredis/hiredis.h>
#include <iostream>
#include <vector>
#include <string>int main() {// 连接到Redis服务器redisContext *c = redisConnect("127.0.0.1", 6379);if (c == nullptr || c->err) {if (c) {std::cerr << "Connection error: " << c->errstr << std::endl;redisFree(c);} else {std::cerr << "Can't allocate redis context" << std::endl;}return 1;}// 启动管道:使用 MULTI 开启事务redisAppendCommand(c, "MULTI");redisAppendCommand(c, "SET key1 value1");redisAppendCommand(c, "SET key2 value2");redisAppendCommand(c, "SET key3 value3");redisAppendCommand(c, "EXEC");// 读取响应for (int i = 0; i < 5; ++i) {redisReply *reply;if (redisGetReply(c, (void**)&reply) == REDIS_OK) {// 处理每个回复if (reply->type == REDIS_REPLY_STATUS) {std::cout << "Reply " << i << ": " << reply->str << std::endl;} else if (reply->type == REDIS_REPLY_ARRAY) {std::cout << "Reply " << i << ": [";for (size_t j = 0; j < reply->elements; ++j) {if (reply->element[j]->str)std::cout << reply->element[j]->str;elsestd::cout << "nil";if (j != reply->elements -1) std::cout << ", ";}std::cout << "]" << std::endl;} else {std::cout << "Reply " << i << ": " << (reply->str ? reply->str : "") << std::endl;}freeReplyObject(reply);} else {std::cerr << "Failed to get reply" << std::endl;break;}}// 关闭连接redisFree(c);return 0;
}
输出示例:
Reply 0: OK
Reply 1: OK
Reply 2: OK
Reply 3: OK
Reply 4: [OK, OK, OK]
解释:
- redisAppendCommand:将多个命令(包括事务命令
MULTI和EXEC)添加到管道中。 - redisGetReply:逐个获取命令的响应。
- 处理回复:根据回复类型处理并输出每个命令的响应。
注意:在实际应用中,可以根据需求添加更多命令到管道中,以实现批量操作。
二、Redis 事务
概述
Redis 事务 允许客户端一次性执行一系列命令,确保这些命令按顺序执行且没有其他客户端的干扰。事务通过以下命令实现:
MULTI:标记事务的开始。EXEC:执行事务中的所有命令。DISCARD:取消事务,清空事务队列。WATCH:监视一个或多个键,如果这些键在事务执行前被修改,事务将被中断。
事务的前提
在有并发连接的情况下,不同连接异步执行命令可能会导致不可预期的冲突。Redis 是单线程的,但在事务执行期间,如果不加以控制,仍可能出现数据不一致的问题。比如:我们希望顺序执行命令1、2、3。但是如果Redis是请求回应模型,若在命令1和命令2之间的空档期,命令3插入执行,那么最后的结果就会出错。

事务特征(ACID 分析)
ACID(原子性、一致性、隔离性和持久性)是关系型数据库管理系统确保事务正确执行的四个基本特性。以下分析Redis事务在ACID方面的支持情况:
-
原子性(Atomicity):
-
部分支持:Redis中的单个命令是原子性的。然而,Redis事务(通过
MULTI和EXEC实现)不支持回滚机制。如果在事务中执行多个命令,其中一个命令失败,之前的命令依然会执行,无法回滚。 -
示例:
MULTI SET key1 value1 INCR key2 EXEC如果
INCR key2对应的键类型不是整数,INCR命令会失败,但SET key1 value1已经执行,无法回滚。
-
-
一致性(Consistency):
- 部分支持:Redis引擎本身不提供严格的一致性保证。例如,在主从复制模式下,当主节点出现故障时,从节点可能无法立即更新,导致数据的部分丢失。
- 类型一致性:
-
示例:
SET count 1000 TYPE count // 返回 string LPUSH count 2000 // 返回 (error) WRONGTYPE Operation against a key holding the wrong kind of value
-
-
隔离性(Isolation):
- 支持:Redis使用单线程模型,一个客户端的命令在执行期间不会被其他客户端的命令中断,因此天然具备隔离性。
- 注意:在多线程环境下,临界资源仍需要加锁来确保数据一致性。
-
持久性(Durability):
- 部分支持:Redis提供了RDB(快照)和AOF(Append-Only File)两种持久化机制。特别是在AOF持久化策略为
appendfsync=always时,Redis能够确保事务提交后数据被永久保存。 - 实际情况:在实际项目中,通常不会将AOF配置为
always,因为这会影响性能。
- 部分支持:Redis提供了RDB(快照)和AOF(Append-Only File)两种持久化机制。特别是在AOF持久化策略为
WATCH 命令
WATCH 命令用于监视一个或多个键,并在事务执行期间检测这些键是否被修改。如果被监视的键在事务执行前被修改,事务将被取消执行。这是一种乐观锁机制。
事务实现示例
示例 1:事务实现 zpop
假设需要从一个有序集合中弹出最高分的成员,并将其添加到另一个集合中,这是一个需要原子性操作的场景。
#include <hiredis/hiredis.h>
#include <iostream>
#include <string>int main() {// 连接到Redis服务器redisContext *c = redisConnect("127.0.0.1", 6379);if (c == nullptr || c->err) {if (c) {std::cerr << "Connection error: " << c->errstr << std::endl;redisFree(c);} else {std::cerr << "Can't allocate redis context" << std::endl;}return 1;}// 监视zsetredisReply *watch_reply = (redisReply*)redisCommand(c, "WATCH myzset");freeReplyObject(watch_reply);// 获取zset的最高分成员redisReply *reply = (redisReply*)redisCommand(c, "ZREVRANGE myzset 0 0 WITHSCORES");if (reply->elements == 0) {std::cout << "myzset is empty" << std::endl;freeReplyObject(reply);redisFree(c);return 0;}std::string member = reply->element[0]->str;double score = atof(reply->element[1]->str);freeReplyObject(reply);// 开始事务redisReply *multi_reply = (redisReply*)redisCommand(c, "MULTI");freeReplyObject(multi_reply);// 弹出成员redisAppendCommand(c, "ZREM myzset %s", member.c_str());// 添加到另一个集合redisAppendCommand(c, "ZADD anotherzset %f %s", score, member.c_str());// 执行事务redisReply *exec_reply;if (redisGetReply(c, (void**)&exec_reply) == REDIS_OK) {if (exec_reply->type == REDIS_REPLY_ARRAY) {std::cout << "Transaction executed successfully" << std::endl;} else {std::cout << "Transaction failed" << std::endl;}freeReplyObject(exec_reply);} else {std::cerr << "EXEC failed" << std::endl;}redisFree(c);return 0;
}
解释:
- WATCH:监视
myzset,如果在事务执行前myzset被其他客户端修改,事务将被取消。 - ZREVRANGE:获取
myzset中分数最高的成员。 - MULTI:开始事务。
- ZREM & ZADD:在事务中移除成员并添加到另一个集合。
- EXEC:执行事务。如果在事务执行前
myzset被修改,EXEC将返回null,表示事务被中断。
示例 2:事务实现加倍操作
假设需要对某个键的值进行加倍操作,这需要确保读取和写入的原子性。
#include <hiredis/hiredis.h>
#include <iostream>
#include <string>int main() {// 连接到Redis服务器redisContext *c = redisConnect("127.0.0.1", 6379);if (c == nullptr || c->err) {if (c) {std::cerr << "Connection error: " << c->errstr << std::endl;redisFree(c);} else {std::cerr << "Can't allocate redis context" << std::endl;}return 1;}std::string key = "counter";while (true) {// 监视键redisReply *watch_reply = (redisReply*)redisCommand(c, "WATCH %s", key.c_str());freeReplyObject(watch_reply);// 获取当前值redisReply *reply = (redisReply*)redisCommand(c, "GET %s", key.c_str());if (reply->type == REDIS_REPLY_STRING) {int value = atoi(reply->str);freeReplyObject(reply);// 开始事务redisReply *multi_reply = (redisReply*)redisCommand(c, "MULTI");freeReplyObject(multi_reply);// 设置新值redisAppendCommand(c, "SET %s %d", key.c_str(), value * 2);// 执行事务redisReply *cmd_reply;if (redisGetReply(c, (void**)&cmd_reply) == REDIS_OK) {freeReplyObject(cmd_reply);redisReply *exec_reply;if (redisGetReply(c, (void**)&exec_reply) == REDIS_OK) {if (exec_reply->type == REDIS_REPLY_ARRAY) {std::cout << "Counter doubled to " << value * 2 << std::endl;freeReplyObject(exec_reply);break;} else {std::cout << "Transaction aborted" << std::endl;freeReplyObject(exec_reply);continue;}}}} else {freeReplyObject(reply);std::cerr << "Failed to get key" << std::endl;break;}}redisFree(c);return 0;
}
解释:
- WATCH:监视
counter键,确保在事务执行期间没有其他客户端修改该键。 - GET:获取
counter当前值。 - MULTI:开始事务。
- SET:在事务中设置新值为当前值的两倍。
- EXEC:执行事务。如果
counter在事务执行前被修改,EXEC将返回null,事务被取消,循环继续尝试。
三、Lua 脚本
Lua 脚本的事务特性分析
Lua 脚本 是Redis提供的一种在服务器端执行复杂操作的机制。Lua脚本在Redis服务器上运行,能够将多个命令组合成一个原子性操作,减少网络往返次数。
ACID 特性分析:
-
原子性(Atomicity):
- 部分满足:Lua脚本通过一个命令将所有脚本中的语句一起执行,但不具备回滚机制。如果脚本中某个命令执行失败,之前成功的命令依然会生效。
-
一致性(Consistency):
- 部分不满足:Lua脚本本身不具备严格的一致性。如果脚本执行过程中发生错误,无法回滚已执行的命令,可能导致数据不一致。
-
隔离性(Isolation):
- 满足:Redis使用单线程模型,Lua脚本作为单个数据包执行期间,其他命令或脚本不会被打断,确保隔离性。
-
持久性(Durability):
- 部分不满足:只有在AOF持久化策略为
appendfsync=always时,Lua脚本的修改才具备持久性。否则,可能会在系统故障时丢失。
- 部分不满足:只有在AOF持久化策略为
基本命令
-
EVAL:执行一个Lua脚本。
EVAL script numkeys [key ...] [arg ...] -
EVALSHA:根据脚本的SHA1哈希值执行脚本。
EVALSHA sha1 numkeys [key ...] [arg ...] -
SCRIPT LOAD:将Lua脚本加载到Redis并返回其SHA1哈希值。
SCRIPT LOAD script -
SCRIPT EXISTS:检查脚本缓存中是否存在指定的SHA1哈希值的Lua脚本。
SCRIPT EXISTS sha1 [sha1 ...] -
SCRIPT FLUSH:清除所有脚本缓存。
SCRIPT FLUSH -
SCRIPT KILL:强制停止正在运行的脚本(如死循环)。
SCRIPT KILL
应用示例
示例:执行加倍操作
测试使用:
SET jack 100
EVAL 'local key = KEYS[1]; local val = redis.call("GET", key); if val then redis.call("SET", key, 2 * val); return 2 * val; end; return 0;' 1 jack
输出:
(integer) 200
实际使用:
-
加载脚本到Redis:
SCRIPT LOAD 'local key = KEYS[1]; local val = redis.call("GET", key); if val then redis.call("SET", key, 2 * val); return 2 * val; end; return 0;'输出:
"f76a2571acb0452ef1a0ba3b0bbd6c46a321cbe1" -
执行缓存的脚本:
EVALSHA "f76a2571acb0452ef1a0ba3b0bbd6c46a321cbe1" 1 jack输出:
(integer) 200
C++ 示例(使用 EVAL 和 EVALSHA)
以下示例展示了如何使用 hiredis 库在C++中加载并执行Lua脚本:
#include <hiredis/hiredis.h>
#include <iostream>
#include <string>int main() {// 连接到Redis服务器redisContext *c = redisConnect("127.0.0.1", 6379);if (c == nullptr || c->err) {if (c) {std::cerr << "Connection error: " << c->errstr << std::endl;redisFree(c);} else {std::cerr << "Can't allocate redis context" << std::endl;}return 1;}// Lua 脚本:key3 = key1 + key2const char *script = "local v1 = tonumber(redis.call('GET', KEYS[1])) " \"local v2 = tonumber(redis.call('GET', KEYS[2])) " \"local sum = v1 + v2 " \"redis.call('SET', KEYS[3], sum) " \"return sum";// 加载脚本到RedisredisReply *reply = (redisReply*)redisCommand(c, "SCRIPT LOAD %s", script);std::string sha = "";if (reply->type == REDIS_REPLY_STRING) {sha = reply->str;std::cout << "Script SHA1: " << sha << std::endl;}freeReplyObject(reply);// 设置初始值redisCommand(c, "SET key1 15");redisCommand(c, "SET key2 25");// 使用 EVALSHA 执行脚本std::string evalsha_cmd = "EVALSHA " + sha + " 3 key1 key2 key3";reply = (redisReply*)redisCommand(c, evalsha_cmd.c_str());if (reply->type == REDIS_REPLY_INTEGER || reply->type == REDIS_REPLY_STRING) {std::cout << "Sum via EVALSHA: " << (reply->type == REDIS_REPLY_INTEGER ? std::to_string(reply->integer) : reply->str) << std::endl;}freeReplyObject(reply);// 获取结果reply = redisCommand(c, "GET key3");if (reply->type == REDIS_REPLY_STRING) {std::cout << "key3 = " << reply->str << std::endl;}freeReplyObject(reply);redisFree(c);return 0;
}
输出示例:
Script SHA1: f76a2571acb0452ef1a0ba3b0bbd6c46a321cbe1
Sum via EVALSHA: 40
key3 = 40
解释:
- SCRIPT LOAD:将Lua脚本加载到Redis,并获取其SHA1哈希值。
- EVALSHA:使用脚本的SHA1哈希值执行脚本,实现
key3 = key1 + key2。 - GET:获取执行结果
key3的值。
四、Redis 发布订阅(Pub/Sub)
主要命令
为了支持消息的多播机制,Redis的发布订阅(Pub/Sub)是一种消息传递模式,允许消息发布者将消息发送到特定频道,订阅者订阅这些频道以接收消息。消息不一定可达;分布式消息队列;stream的方式确保一定可达;主要命令包括:
-
PUBLISH:发布消息到指定频道。
PUBLISH channel message -
SUBSCRIBE:订阅一个或多个频道。
SUBSCRIBE channel [channel ...] -
UNSUBSCRIBE:取消订阅频道。
UNSUBSCRIBE channel [channel ...] -
PSUBSCRIBE:按模式订阅一个或多个频道。
PSUBSCRIBE pattern [pattern ...] -
PUNSUBSCRIBE:取消按模式订阅的频道。
PUNSUBSCRIBE pattern [pattern ...]
示例命令:
# 订阅频道
SUBSCRIBE news.game news.tech news.school# 订阅模式频道
PSUBSCRIBE news.*# 发布消息
PUBLISH news.game 'EDG wins S12 championship'# 取消订阅频道
UNSUBSCRIBE news.game# 取消订阅模式频道
PUNSUBSCRIBE news.*
客户端接收消息示例:
当订阅者订阅了 news.game、news.tech 和 news.school 频道后,发布者发布消息到 news.game 频道,所有订阅该频道的客户端都会收到消息。
message news.game EDG wins S12 championship
应用场景
发布订阅功能通常需要重新开启一个连接,因为订阅连接会进入阻塞模式接收消息,无法继续执行其他命令。因此,实际项目中支持Pub/Sub时,通常需要另开一条连接用于处理发布订阅。
常见应用场景:
- 实时聊天系统:用户通过频道发送和接收消息。
- 实时通知:系统事件通过频道通知相关服务。
- 分布式系统的消息传递:不同服务之间的通信。
C++ 示例(使用 hiredis 库)
发布者和订阅者通常需要在不同的连接上进行操作,因为订阅连接会进入阻塞模式接收消息。
发布者示例
#include <hiredis/hiredis.h>
#include <iostream>
#include <thread>
#include <chrono>int main() {// 连接到Redis服务器redisContext *c = redisConnect("127.0.0.1", 6379);if (c == nullptr || c->err) {if (c) {std::cerr << "Connection error: " << c->errstr << std::endl;redisFree(c);} else {std::cerr << "Can't allocate redis context" << std::endl;}return 1;}// 发布消息for (int i = 1; i <= 5; ++i) {std::string message = "Hello " + std::to_string(i);redisReply *reply = (redisReply*)redisCommand(c, "PUBLISH mychannel %s", message.c_str());if (reply->type == REDIS_REPLY_INTEGER) {std::cout << "Published message to " << reply->integer << " subscribers." << std::endl;}freeReplyObject(reply);std::this_thread::sleep_for(std::chrono::seconds(1));}redisFree(c);return 0;
}
输出示例:
Published message to 1 subscribers.
Published message to 1 subscribers.
Published message to 1 subscribers.
Published message to 1 subscribers.
Published message to 1 subscribers.
订阅者示例
#include <hiredis/hiredis.h>
#include <iostream>
#include <thread>int main() {// 连接到Redis服务器redisContext *c = redisConnect("127.0.0.1", 6379);if (c == nullptr || c->err) {if (c) {std::cerr << "Connection error: " << c->errstr << std::endl;redisFree(c);} else {std::cerr << "Can't allocate redis context" << std::endl;}return 1;}// 订阅频道redisReply *reply = (redisReply*)redisCommand(c, "SUBSCRIBE mychannel");freeReplyObject(reply);// 持续接收消息while (redisGetReply(c, (void**)&reply) == REDIS_OK) {if (reply->type == REDIS_REPLY_ARRAY && reply->elements == 3) {std::cout << "Received message from " << reply->element[1]->str << ": " << reply->element[2]->str << std::endl;}freeReplyObject(reply);}redisFree(c);return 0;
}
输出示例:
Received message from mychannel: Hello 1
Received message from mychannel: Hello 2
Received message from mychannel: Hello 3
Received message from mychannel: Hello 4
Received message from mychannel: Hello 5
注意:订阅者和发布者通常需要在不同的进程或线程中运行,因为订阅者连接会进入阻塞状态,无法执行其他命令。
5. Redis 异步方式
移步:Redis异步实现解析
六、Redis 的缺点
尽管Redis在许多场景下表现出色,但也存在一些缺点和限制:
-
内存限制:
- 描述:Redis是内存数据库,所有数据存储在内存中。对于大数据量应用,内存成本较高。
- 影响:高内存消耗限制了Redis在大规模数据存储上的应用。
- 解决方案:通过分片(Sharding)和使用更高效的数据结构来优化内存使用。
-
单线程模型:
- 描述:Redis的大部分命令是单线程执行的,虽然通过IO多路复用实现高性能,但在多核CPU上无法充分利用多线程优势。
- 影响:在CPU密集型操作或需要高并发处理时,性能可能受限。
- 解决方案:通过集群部署,分散负载到多个Redis实例。
-
数据持久化风险:
- 描述:虽然Redis支持RDB和AOF持久化,但在极端情况下可能会丢失部分数据。
- 影响:数据的可靠性和持久性在某些应用场景下可能无法满足要求。
- 解决方案:合理配置持久化策略,结合主从复制和高可用架构,增强数据安全性。
-
缺乏复杂查询能力:
- 描述:Redis主要支持键值操作,不适合需要复杂查询和关联的应用场景。
- 影响:对于需要复杂数据关系和查询的应用,Redis无法直接满足需求。
- 解决方案:结合其他数据库(如SQL数据库)使用,Redis用于缓存和快速访问。
-
有限的事务支持:
- 描述:Redis的事务不支持回滚机制,无法像传统数据库那样处理复杂的事务逻辑。
- 影响:在需要严格事务控制的场景下,Redis无法提供足够的支持。
- 解决方案:使用Lua脚本实现复杂的原子操作,或结合其他数据库的事务功能。
-
数据结构局限:
- 描述:尽管Redis支持多种数据结构,但在某些特定场景下可能不如专用数据库高效。
- 影响:对于特定类型的数据处理(如图数据),需要额外的实现工作。
- 解决方案:使用专用的数据库(如图数据库)来处理特定类型的数据。
-
安全性:
- 描述:Redis的默认配置安全性较低,需额外配置才能满足生产环境的安全要求。
- 影响:未经配置的Redis实例可能容易受到未经授权的访问和攻击。
- 解决方案:配置密码认证、限制网络访问、启用TLS等安全措施。
-
缺乏多版本并发控制(MVCC):
- 描述:Redis不支持复杂的并发控制机制,可能导致竞争条件和数据一致性问题。
- 影响:在高并发环境下,可能会出现数据冲突和不一致。
- 解决方案:使用
WATCH、Lua脚本等机制实现乐观锁,或通过应用层控制并发。
七、总结
Redis 是一个高性能的内存数据库,适用于多种场景,如缓存、实时数据处理、消息队列等。通过深入了解其Pipeline(管道)、事务、Lua 脚本、发布订阅(Pub/Sub)和异步连接等功能,开发者可以充分利用Redis的优势来优化应用性能。
Redis 的优势
- 高性能:基于内存,支持快速的数据读写。
- 丰富的数据结构:支持字符串、哈希、列表、集合、有序集合等多种数据类型。
- 多样的功能:支持事务、发布订阅、Lua脚本、持久化等功能。
- 简单易用:Redis命令直观,易于学习和使用。
Redis 的局限性
- 内存消耗高:对于大规模数据存储,内存成本较高。
- 事务支持有限:缺乏回滚机制,事务控制不如关系型数据库完善。
- 安全性需额外配置:默认配置不够安全,需手动加强安全措施。
- 单线程模型:在某些高并发或CPU密集型场景下,性能可能受限。
应用建议
在选择使用Redis时,需综合考虑应用需求和Redis的特性:
-
适合场景:
- 高频访问的数据缓存。
- 实时数据分析和处理。
- 实现分布式锁和消息队列。
- 会话存储和排行榜系统。
-
不适合场景:
- 需要复杂事务控制的应用。
- 大规模数据存储,超过内存容量。
- 需要复杂查询和数据关联的应用。
通过合理设计架构,结合Redis的优势和其他数据库的功能,可以构建高性能、可靠的应用系统。
参考
0voice · GitHub
相关文章:
Redis协议详解及其异步应用
目录 一、Redis Pipeline(管道)概述优点使用场景工作原理Pipeline 的基本操作步骤C 示例(使用 [hiredis](https://github.com/redis/hiredis) 库) 二、Redis 事务概述事务的前提事务特征(ACID 分析)WATCH 命…...
LeetCode213:打家劫舍II
题目链接:213. 打家劫舍 II - 力扣(LeetCode) 代码如下 class Solution { public:int rob(vector<int>& nums) {if(nums.size() 0) return 0;if(nums.size() 1) return nums[0];if(nums.size() 2) return max(nums[0…...
linux一二三章那些是重点呢
第一章 静态库动态库的区别 什么是库 库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接 拿来用的变量、函数或类。 如何制作 静态动态库 静态库: GCC 进行链接时,会把静态库中代码打…...
C语言中的程序入口:超越main函数的探索
在C语言中,尽管main函数是标准程序的默认入口点,但借助编译器特性和链接器选项,我们可以指定其他函数作为程序的入口。GCC编译器通过-e选项,允许我们将任何符合签名的函数作为程序的入口。这一特性可以用于特定的实验需求、特定系…...
《面试之MQ篇》
《面试之MQ篇》 1. 为什么要使用MQ 首先,面试官问的第一个问题或者说是逼问的一个问题:“为什么要使用MQ”其实面试官问这个问题就是想考察你MQ的特性,这个时候呢,我们必须要答出三点:解耦、异步、削峰。 1. 解耦 1. 传统系统…...
Git 分支操作-开发规范
一、背景 在实际开发中,一般在主分支的基础上单独创建一个新的分支进行开发,最后合并到master分支,而不是直接在master分支进行开发。 二、新建分支 1、初始状态,local为本地分支,remote为远程分支 2、单击 “Remot…...
JSONArray根据指定字段去重
JSONArray dataList new JSONArray();这儿省略dataList 加数据的过程 dataList new JSONArray(dataList.stream().distinct().collect(Collectors.toList())); Set<String> timestamps new HashSet<>();根据时间字段去重 dataList dataList.stream().map(obj -…...
线程有哪几种状态? 分别说明从一种状态到另一种状态转变有哪些方式?
在 Java 中,线程的生命周期管理通过不同的状态来跟踪。一个线程在其生命周期中可以处于多种状态,不同的状态之间会通过特定的事件发生转变。以下是 Java 线程的几种状态及其之间的转移方式: 1. 线程的状态 1.1 NEW(新建状态&…...
自注意力机制self-attention中的KV 缓存
在自注意力机制中,KV 缓存(Key-Value Caching)主要用于加速模型在推理阶段的计算,尤其是在处理长序列或者生成任务(如文本生成)时,这种缓存机制可以显著提高效率。 1. KV 缓存的背景 在 Trans…...
前端库--nanoid(轻量级的uuid)
文章目录 定义:生成方式:现实使用:NanoID 只有 108 个字节那么大NanoID更安全NanoID它既快速又紧凑 使用步骤1.安装nanoid包2.引入使用3.使用4.自定义字母 定义: UUID 是 通用唯一识别码(Universally Unique Identifierÿ…...
计算机基础-什么是网络端口?
网络端口可以想象成一个大型公寓楼的邮箱。每个公寓楼(这里指的是一个计算机或服务器)有很多个邮箱(即网络端口),每个邮箱都有一个独一无二的编号(端口号)。当一封信(网络数据包&…...
力扣动态规划基础版(斐波那契类型)
70. 爬楼梯https://leetcode.cn/problems/climbing-stairs/ 70.爬楼梯 方法一 动态规划 考虑转移方程和边界条件: f(x) f(x -1) f(x - 2);f(1) 1;f&…...
Java重修笔记 InetAddress 类和 Socket 类
InetAddress 类相关方法 1. 获取本机 InetAddress 对象:getLocalHost public static InetAddress getLocalHost() throws UnknownHostException 返回值:本地主机的名字和地址 异常:UnknownHostException - 如果本地主机名无法解析成地址 2…...
秋招突击——8/6——万得数据面试总结
文章目录 引言正文面经整理一1、讲一下java的多态,重载,重写的概念,区别2、说一下Java的数组,链表的结构,优缺点3、创建java线程的方式有哪些,具体说说4、创建线程池呢、每个参数的意义5、通过那几种方式保…...
STM32定时器
目录 STM32定时器概述 STM32基本定时器 基本定时器的功能 STM32基本定时器的寄存器 STM32通用定时器 STM32定时器HAL库函数 STM32定时器概述 从本质上讲定时器就是“数字电路”课程中学过的计数器(Counter),它像“闹钟”一样忠实地为处…...
第七课 Vue中的v-for遍历指令
Vue中的v-for遍历指令 v-for用于对象遍历(数组/JSON),渲染数据列表 基础示例: <div id"app"><ul><li v-for"val in arr">{{val}}</li></ul></div><script>new V…...
【NTN 卫星通信】卫星通信的专利
1 概述 好久没有看书了,最近买了本讲低轨卫星专利的书,也可以说是一个分析报告。推荐给喜欢的朋友。 2 书籍截图 图1 封面 图2 波音低轨卫星专利演进 图3 低轨卫星关键技术专利发展阶段 图4 第一页 3 参考文献 产业专利分析报告–低轨卫星通信技术...
vue3 element table 插槽外的数据更新,插槽内的数据未更新。
在使用element table组件时候,有时候需要对table内部的header插槽进行单独的列的数据操作,比如在列头增加一个筛选功能,对指定范围的值进行一个筛选,需要对input的值进行v-model的绑定,对绑定的值进行清空时候…...
飞凌嵌入式FET527N-C核心板已适配OpenHarmony4.1
近期,飞凌嵌入式为FET527N-C核心板适配了OpenHarmony4.1系统——进一步提升了核心板的兼容性、稳定性和安全性。 OpenHarmony4.1在应用开发方面展现了全新的开放能力,以更加清晰的逻辑和场景化视角提供给开发者丰富的API接口,应用开发能力得…...
CVPR 2024最佳论文候选-pixelSplat论文解读
目录 一、概述 二、相关工作 1、单场景下的视角合成 2、基于先验的三维重建和视图合成 3、多视图几何测量 三、3DGS的缺点 1、容易陷入最小值 2、需要大量输入图像 3、尺度模糊性 四、pixelSplat 1、解决尺度模糊性(深度信息生成) 2、编码器…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...
Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
