详解Redis源码中的设计模式及设计思想

前言

Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)、位图(bitmaps)、超日志(hyperloglogs)和地理空间(geospatial)索引半径。

Redis 是什么

Redis 是一个高性能的键值对(key-value)数据库。它通常用作数据缓存、消息队列、应用程序中的各种用例,如排行榜、实时分析等。Redis 的主要特点包括:

内存存储:主要数据存储在内存中,提供快速的读写访问。
支持多种数据类型:如字符串、列表、集合、有序集合等。
数据持久化:支持 RDB(Redis Database)和 AOF(Append Only File)两种持久化方式。
原子操作:支持事务和 Lua 脚本,确保操作的原子性。
主从复制:支持数据的复制和同步,提高数据的可用性。
分布式:支持集群部署,提高系统的扩展性和可用性。

为什么有 Redis

高性能:Redis 将数据存储在内存中,访问速度非常快,适合需要快速读取和写入的场景。
数据结构丰富:支持多种数据类型,可以满足各种复杂的数据存储需求。
持久化:虽然主要存储在内存中,但 Redis 也支持数据的持久化,确保数据安全。
原子操作:Redis 提供了原子操作,保证了数据操作的一致性。
分布式:Redis 支持主从复制和分布式,可以扩展到多个服务器,提高系统的可用性和扩展性。

Redis 解决什么问题

缓存:减少数据库访问,提高应用性能。
消息队列:处理异步任务和消息传递。
排行榜:实现实时排行榜功能。
实时分析:进行实时数据的分析和处理。
会话存储:存储用户会话信息。
全页缓存:缓存整个网页内容。

为什么要用 Redis

快速:内存存储,访问速度快。
灵活:支持多种数据类型和操作。
可扩展:支持主从复制和分布式部署。
可靠性:支持数据持久化,保证数据安全。
社区支持:活跃的开源社区,持续更新和维护。

Redis 的源码

Redis 的源码是开源的,主要由 C 语言编写。以下是一些关键组件和概念的简要介绍:

事件循环:Redis 使用事件驱动的架构,通过事件循环处理文件事件和时间事件。
数据结构:Redis 内部实现了多种数据结构,如简单动态字符串(SDS)、字典(dict)、跳跃表(skiplist)等。
命令处理:Redis 命令通过命令表进行注册和处理,每个命令都有相应的处理函数。
网络通信:Redis 使用非阻塞 I/O 和事件驱动的网络库(如 libevent 或 epoll)进行网络通信。
持久化:Redis 支持 RDB 和 AOF 两种持久化方式,分别通过快照和追加文件的方式保存数据。
复制:Redis 的主从复制通过发送 RDB 文件和 AOF 文件实现数据的同步。
集群:Redis 集群通过分片的方式将数据分布到多个节点,提高系统的扩展性和可用性。

Redis 的源码是相当庞大的,涵盖了从网络通信到数据结构实现的各个方面。在这里,我将提供一些关键组件的代码片段和解释,以帮助你理解 Redis 的工作原理。

  1. Redis 启动流程
    初始化服务器配置。
    初始化事件循环。
    初始化持久化机制。
    加载 RDB 文件或 AOF 文件,恢复数据。
    启动服务,监听网络连接。
    代码解析:
    Redis 启动时,redis.c 文件中的 main 函数是入口点。
int main(int argc, char **argv) {// 初始化服务器结构体serverInitConfig();// 初始化事件循环if (aeCreateFileEvent(server.el, server.port, AE_READABLE, acceptTcpHandler, NULL) == AE_ERR)return 1;// 初始化持久化if (server.appendonly) {startAppendOnly();}// 加载数据if (server.aof_state == AOF_ON) {loadAppendOnlyFile(server.aof_filename);}// 启动服务for (;;) {if (serverCronJobs() == -1) break;}return 0;
}
  1. 事件循环
    Redis 使用事件驱动的模型来处理客户端的连接和请求。
    等待文件事件或时间事件。
    处理文件事件,如客户端连接、读取、写入等。
    处理时间事件,如超时、计划任务等。
    代码解析:
    事件循环在 server.c 文件的 serverCronJobs 函数中调用。
int serverCronJobs(void) {// 处理文件事件aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);// 处理时间事件processTimeEventsAndBlockedClients();// 执行计划任务,如持久化、清理等performPendingOperations();return 0;
}

以下是事件循环的核心代码片段:

// 伪代码,展示事件循环的概念
while (server.running) {// 等待文件事件或时间事件events = aeWait(server.el, 1000); // 等待最多1000毫秒for (int j = 0; j < events; j++) {// 处理文件事件if (aeGetFileEvent(server.el, fd, AE_READABLE) != 0) {handleClientInput(fd);}}// 处理时间事件processTimeEvents();
}

这段伪代码展示了 Redis 的主事件循环。aeWait 函数用于等待文件事件或时间事件的发生,然后根据事件类型调用相应的处理函数。

  1. 命令处理
    客户端发送命令请求。
    解析命令请求。
    查找命令表,获取命令处理函数。
    执行命令处理函数。
    代码解析:
    命令处理在 networking.c 文件的 processInputBuffer 函数中。
void processInputBuffer(client *c) {// 解析命令while (c->bufpos < c->bufused) {if (c->argc == 0) {int type = processCommandArgv(c);if (type == C_ERR) {break;} else if (type == C_OK) {// 查找并执行命令redisCommand *cmd = lookupCommand(c, c->argv[0]);if (cmd) {cmd->func(c);}}}}
}

Redis 的命令是通过一个命令表来注册和处理的。以下是命令表的一个示例:

// 命令结构体定义
struct redisCommand {char *name;      // 命令名称int arity;       // 命令参数个数void (*func)();  // 命令处理函数int flags;       // 命令标志
};// 命令表
struct redisCommand redisCommandTable[] = {{"get", 2, getCommand, 1},{"set", 3, setCommand, 1},// ... 其他命令
};// 命令查找函数
struct redisCommand *lookupCommand(client *c, robj *cmd) {char *cmdname = c->argv[0]->ptr;struct redisCommand *cmd = NULL;// 遍历命令表查找命令for (int i = 0; i < sizeof(redisCommandTable) / sizeof(redisCommand); i++) {if (!strcmp(redisCommandTable[i].name, cmdname)) {cmd = &redisCommandTable[i];break;}}return cmd;
}

这段代码展示了 Redis 如何通过命令表来查找和执行命令。每个命令都有一个结构体定义,包括命令名称、参数个数、处理函数和一些标志。

  1. 数据结构操作
    Redis 内部使用了许多自定义的数据结构,
    使用简单动态字符串 SDS 存储字符串。
    使用字典(dict)存储键值对。
    使用跳跃表实现有序集合。
    代码解析:
    数据结构操作在 sds.c 和 dict.c 等文件中。
// SDS 字符串操作示例
sds sdsnewlen(const void *init, size_t initlen) {// ... 同上
}// 字典操作示例
dictEntry *dictFind(dict *d, const void *key) {// 根据哈希查找键值对
}

以下是 SDS 的一个简单示例:

/* The sdshdr struct is used by sds.c to store and manage the metadata of an SDS string. */
struct sdshdr {int len;  // 字符串长度int free; // 未使用空间的长度char buf[]; // 字节数组,实际字符串存储在这里
};/* 创建一个新的SDS字符串 */
sds sdsnewlen(const void *init, size_t initlen) {struct sdshdr *sh;// 为结构体和字符串分配内存sh = zmalloc(sizeof(struct sdshdr) + initlen + 1);if (sh == NULL) return NULL;// 初始化结构体sh->len = initlen;sh->free = 0;if (initlen > 0 && init != NULL) {memcpy(sh->buf, init, initlen);}sh->buf[initlen] = '\0'; // 确保字符串以空字符结尾return (char *)sh->buf;
}

这段代码展示了如何创建一个新的 SDS 字符串。SDS 是 Redis 中用于存储字符串的一种数据结构,它比传统的 C 字符串提供了更多的功能,如动态扩展和二进制安全。

  1. 持久化
    Redis 支持两种数据持久化方式:RDB 和 AOF。
    RDB 持久化:周期性地将内存数据快照保存到磁盘。
    AOF 持久化:记录每个写命令到文件。
    代码解析:
    持久化机制在 rdb.c 和 aof.c 文件中。
// RDB 持久化示例
int rdbSave(char *filename) {// ... 同上
}// AOF 持久化示例
void feedAppendOnlyFile(redisClient *c) {// 将写命令追加到AOF文件
}

以下是 RDB 持久化的简单示例:

// 伪代码,展示 RDB 持久化的概念
void rdbSave(char *filename) {// 创建一个新的RDB文件FILE *fp = fopen(filename, "w");if (fp == NULL) return;// 序列化并写入数据到文件rdbSaveData(fp);// 关闭文件fclose(fp);
}

这段伪代码展示了如何将 Redis 的数据保存到一个 RDB 文件中。实际的 RDB 持久化过程会更复杂,包括序列化内存中的数据结构和写入到磁盘。

  1. 主从复制
    主服务器周期性地生成 RDB 文件。
    从服务器连接到主服务器,请求 RDB 文件。
    主服务器发送 RDB 文件给从服务器。
    从服务器加载 RDB 文件,同步数据。
    代码解析:
    主从复制在 replication.c 文件中。
// 主服务器生成 RDB 文件
int rdbSave(char *filename) {// ... 同上
}// 从服务器处理 RDB 文件
void replicationSetSlaveMode(char *filename) {// 加载 RDB 文件
}

Redis 的主从复制是通过发送 RDB 文件实现的。以下是主从复制的一个简单示例:

// 伪代码,展示主从复制的概念
void replicationSendRDB(aeEventLoop *el, int fd, char *filename) {// 发送RDB文件给从服务器sendFile(fd, filename);
}void replicationHandleSlave(fd) {// 处理从服务器的连接// 发送RDB文件replicationSendRDB(server.el, fd, "dump.rdb");
}

这段伪代码展示了主服务器如何发送 RDB 文件给从服务器。实际的主从复制过程会包括更多的步骤,如同步数据和处理命令。

请注意,上述代码都是简化的示例,用于展示 Redis 源码的某些方面。实际的 Redis 源码要复杂得多,并且包含了大量的优化和安全特性。

Redis源码中的设计模式

Redis 的源码中应用了许多设计模式和设计思想,这些设计模式和思想使得 Redis 高效、稳定并且易于扩展。以下是一些在 Redis 源码中常见的设计模式和设计思想的详细解析:

1. 单例模式(Singleton Pattern)

Redis 服务器实例在整个应用程序中只有一个,这符合单例模式。单例模式确保一个类只有一个实例,并提供一个全局访问点。

设计思想: 保证全局只有一个 Redis 服务实例,避免资源竞争和冲突。

源码: Redis 服务器通常作为一个守护进程运行,整个系统中只运行一个实例。在 Redis 的 server.c 文件中,main 函数初始化了全局的 server 结构体实例。

// server.h
struct redisServer {// ... 服务器配置和状态
};// 全局服务器实例
struct redisServer server;// server.c
int main(int argc, char **argv) {// 初始化服务器serverInit();// ... 其他初始化代码
}

2. 工厂模式(Factory Pattern)

Redis 在创建不同的数据类型时使用了工厂模式,例如,根据传入的参数类型创建相应的数据结构。

设计思想: 封装创建对象的细节,使得扩展新的数据类型变得容易。

3. 命令模式(Command Pattern)

Redis 的命令执行是通过命令模式实现的,每个命令都封装在一个结构体中,包含命令名、参数个数和执行函数。

设计思想: 将命令请求封装为一个对象,这可以很容易地扩展新命令或修改现有命令。

Redis 的命令模式通过 redisCommand 结构体实现,每个命令都封装为一个对象,存储在 redisCommandTable[] 数组中。

// server.h
typedef struct redisCommand {char *name;      // 命令名称int arity;       // 命令参数个数void (*func)(struct redisCommandArgv *, int); // 命令处理函数int flags;       // 命令标志
} redisCommand;// 命令表
extern redisCommand redisCommandTable[];// server.c
void call(client *c, int flags) {redisCommand *cmd = lookupCommand(c->argv[0]->ptr);if (cmd) {cmd->func(c->argv, c->argc);}
}redisCommand *lookupCommand(const char *name) {for (int i = 0; i < sizeof(redisCommandTable)/sizeof(redisCommand); i++) {if (strcasecmp(name, redisCommandTable[i].name) == 0) return &redisCommandTable[i];}return NULL;
}

4. 观察者模式(Observer Pattern)

Redis 的发布/订阅功能使用了观察者模式,当一个频道有消息发布时,所有订阅该频道的客户端都会收到消息。

设计思想: 定义对象间的一种一对多的依赖关系,当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。

Redis 的发布/订阅功能使用了观察者模式,subscribeCommand 函数将客户端添加到频道的订阅者列表中。

// pubsub.c
void subscribeCommand(client *c) {// ... 省略部分代码int j;for (j = 1; j < c->argc; j++) {list *clients = subscribeGetChannel(c->argv[j]->ptr, -1);// 将客户端添加到频道的订阅者列表中listAddNodeTail(clients, c);}// ... 省略部分代码
}

5. 迭代器模式(Iterator Pattern)

Redis 使用迭代器模式来遍历数据结构,例如,遍历哈希表或集合。

设计思想: 提供一种顺序访问聚合对象中元素的方法,不暴露其内部的表示。

Redis 使用迭代器模式来遍历数据结构,如 dict.c 中的 dictGetIterator 函数。

// dict.h
typedef struct dictIterator {dict *d;// ... 其他私有成员
} dictIterator;dictIterator *dictGetIterator(dict *d);// dict.c
dictIterator *dictGetIterator(dict *d) {dictIterator *iter = zmalloc(sizeof(*iter));iter->d = d;// 初始化迭代器状态return iter;
}

6. 适配器模式(Adapter Pattern)

Redis 的客户端库通常使用适配器模式来适配不同的编程语言和环境。

设计思想: 允许对象间的接口不兼容的情况,通过一个中间层来使它们能够一起工作。

7. 装饰器模式(Decorator Pattern)

Redis 的日志系统使用了装饰器模式,可以动态地添加日志记录的功能,而不需要修改现有的代码。

设计思想: 动态地给一个对象添加额外的职责,而不改变其结构。

Redis 的日志系统可能使用了装饰器模式,通过添加日志记录的功能来装饰现有的功能。

// 注意:这部分代码不是 Redis 源码的一部分,而是装饰器模式的一个示例。
// Redis 的日志系统可能在其他方面使用了装饰器模式。
typedef struct Logger {void (*log)(const char *message);
} Logger;void basicLog(const char *message) {printf("%s\n", message);
}void verboseLog(const char *message) {printf("Verbose: %s\n", message);
}// 装饰器函数,添加额外的日志功能
void verboseDecorator(Logger *logger, const char *message) {logger->log(message);verboseLog(message);
}

8. 代理模式(Proxy Pattern)

Redis 的持久化操作中,使用代理模式来控制对数据的访问,例如,在 RDB 持久化时,通过代理来控制数据的序列化和写入。

设计思想: 为其他对象提供一个代替或占位符,以控制对它的访问。

9. 状态模式(State Pattern)

Redis 的客户端状态管理使用了状态模式,客户端的状态转换通过状态模式来实现,例如,从连接状态到就绪状态。

设计思想: 允许一个对象在其内部状态改变时改变它的行为,看起来好像改变了其类。

10. 享元模式(Flyweight Pattern)

Redis 在处理大量小对象时使用享元模式,通过共享通用对象来减少内存占用。

设计思想: 通过共享来高效地支持大量细粒度的对象。

Redis 在处理大量小对象时使用享元模式,例如 sds.h 中的 SDS 字符串。

// sds.h
struct sdshdr {int len;int free;char buf[];
};// sds.c
sds sdsnewlen(const void *init, size_t initlen) {struct sdshdr *sh = zmalloc(sizeof(struct sdshdr) + initlen + 1);if (sh == NULL) return NULL;sh->len = initlen;sh->free = 0;if (initlen > 0 && init) memcpy(sh->buf, init, initlen);sh->buf[initlen] = '\0';return (char *)sh->buf;
}

请注意,上述代码片段仅用于演示 Redis 中设计模式的应用,并不完全代表 Redis 的实际实现。Redis 的实际源码可能包含更多的细节和复杂性。如果你想要深入研究 Redis 的设计模式,我建议直接查看 Redis 的 GitHub 仓库。

Redis 中 设计思想

1. 延迟初始化(Lazy Initialization)

Redis 在初始化数据结构时,使用了延迟初始化的思想,只有在需要时才创建对象。

设计思想: 延迟对象的创建直到实际需要使用它的时候,以提高启动速度和资源利用率。

2. 模块化设计

Redis 的代码结构是高度模块化的,每个功能模块都有明确的职责和接口。

设计思想: 将系统划分为独立的、可互换的模块,以提高代码的可维护性和可扩展性。

3. 事件驱动架构

Redis 使用事件驱动架构来处理 I/O 操作,这使得 Redis 能够高效地处理大量的并发连接。

设计思想: 通过事件循环来处理所有的 I/O 操作,避免了多线程或多进程的复杂性和开销。

这些设计模式和设计思想共同构成了 Redis 的强大和灵活的架构,使其成为高性能键值存储的代表。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/47441.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

navicat15已连接忘记密码

1.导出链接 2.使用文本打开 connections.ncx UserName"root" PasswordXXXX 3.复制加密密码&#xff0c;在线解密 代码在线运行 - 在线工具 php解密代码 <?php class NavicatPassword {protected $version 0;protected $aesKey libcckeylibcckey;protected…

JVM高频面试题

1. 内存模型 线程独享: 虚拟机栈, 本地方法栈, 程序计数器 线程共享: 堆, 方法区 2. 虚拟机栈的作用 存放栈帧, 栈帧又包含局部变量表, 每个方法从被调用到执行结束的过程都对应着一个栈帧在虚拟机栈中从入栈到出栈的过程 3. 程序计数器 存放下一条指令的信息 4. 堆 Ja…

C语言学习笔记[26]:循环语句do...while①

do...while语句 do...while的语法格式 do循环语句; while(表达式); 用do...while语句实现打印0~10 #include <stdio.h>int main() {int i 0;do{printf("%d\n", i);i;} while (i < 10);return 0; } do...while是先进行一次循环以后&#xff0c;再进行判…

R语言包AMORE安装报错问题以及RStudio与Rtools环境配置

在使用R语言进行AMORE安装时会遇到报错&#xff0c;这时候需要采用解决办法&#xff1a; AMORE包安装&#xff0c;需要离线官网下载安装包&#xff1a; Index of /src/contrib/Archive/AMORE (r-project.org)https://cran.r-project.org/src/contrib/Archive/AMORE/ 一、出现…

[C++初阶]list的模拟实现

一、对于list的源码的部分分析 1.分析构造函数 首先&#xff0c;我们一开始最先看到的就是这个结点的结构体&#xff0c;在这里我们可以注意到这是一个双向链表。有一个前驱指针&#xff0c;一个后继指针。然后在有一个存储数据的空间 其次它的迭代器是一个自定义类型&#x…

图片太大怎么压缩变小?交给这4个方法就能行

在钱塘江畔&#xff0c;一场罕见的“蝴蝶潮”翩然而至&#xff0c;不仅带来了自然奇观&#xff0c;也预示着好运的降临。然而&#xff0c;当我们将这份美好瞬间分享给更多人时&#xff0c;却遇到了一个小小难题——高分辨率的照片占据了大量的存储空间&#xff0c;上传至社交平…

docker compose离线部署mailcow邮件系统

前言 本文主要讲述因为网络环境限制&#xff08;比如内网、开发环境等&#xff09;&#xff0c;无法直接下载docker镜像的情况下&#xff0c;如何部署mailcow。 正常环境下部署mailcow&#xff0c;请参考文章&#xff1a;mailcow基于容器化的开源邮件系统详细安装过程 准备工…

HBuilderX打包流程(H5)?HBuilder如何发布前端H5应用?前端开发怎样打包发布uniapp项目为h5?

打包步骤&#xff1a; 1、打开hbuilder x》发行》网站-PC Web或手机H5(仅适用于uni-app)(H) 2、面板里的所有信息都可以不填&#xff0c;也不用勾选》直接点击【发行】即可 3、打包成功&#xff1a; 4、部署 按照打包后的路径&#xff0c;找到打包好的文件夹&#xff0c;把文…

【5G Sub-6GHz模块】专为IoT/eMBB应用而设计的RG520NNA、RG520FEB、RG530FNA、RG500LEU 5G模组

推出全新的5G系列模组&#xff1a; RG520NNADB-M28-SGASA RG520NNADA-M20-SGASA RG520FEBDE-M28-TA0AA RG530FNAEA-M28-SGASA RG530FNAEA-M28-TA0AA RG500LEUAA-M28-TA0AA ——明佳达 1、5G RG520N 系列——专为IoT/eMBB应用而设计的LGA封装模块 RG520N 系列是一款专为 IoT…

使用 ABBYY FineReader PDF 15 在创建或转换 PDF 时自动生成书签

使用 ABBYY 为 PDF 文件添加书签&#xff0c;可以帮助快速定位文档中的主要内容&#xff0c;也能更方便的梳理出一份文档大纲。 有很多 PDF 文件在创建时并没有编辑书签&#xff0c;这里介绍使用 ABBYY FineReader PDF 15&#xff08;Win 系统&#xff09;在 PDF 中自动添加书…

知识分享:网贷大数据查询会影响个人征信吗?

随着人们对传统征信的认识不断加深和对个人征信的重视&#xff0c;部分网友就有一种疑问&#xff0c;那就是关于网贷大数据查询对征信有没有影响的问题&#xff0c;小易大数据小编就用本文就为大家详细讲解一下&#xff0c;希望对你了解网贷大数据有帮助。 首先网贷大数据与征信…

睿考网:2024注册会计师考试考试在即,如何备考?

2024年注册会计师考试即将开始&#xff0c;准考证打印时间安排在8月5日至20日&#xff0c;每天上午8点至晚上8点&#xff0c;考生要确保在规定时间内完成准考证的打印。 注册会计师考试包含六个科目&#xff0c;每个科目都有其独特的特点和难度。考生需要根据各科目的特性采用…

Win11鼠标卡顿 - 解决方案

问题 使用Win11系统使&#xff0c;鼠标点击任务栏的控制中心&#xff08;如下图&#xff09;时&#xff0c;鼠标会有3秒左右的卡顿&#xff0c;同时整个显示屏幕也有一定程度的卡顿。 问题原因 排除鼠标问题&#xff1a;更换过不同类型的鼠标&#xff0c;以及不同的连接方式…

反悔贪心和例题

反悔贪心 什么是反悔贪心&#xff1a; 我们都知道贪心就是把局部最优解作为整体最优解&#xff0c;然后一步步的迭代&#xff0c;直到找到全局最优解的过程。但是有些时候&#xff0c;贪心策略可能并不是正解&#xff0c;局部的最优解可能不是全局的最优解。反悔贪心顾名思义…

SSM架构(二)

接上一篇博客 SSM框架(一)-CSDN博客 2.4 Spring 2.4.1 Service设计 EmployeeService接口代码&#xff1a; List<Emp> search(Emp condition);Emp searchById(Integer id);boolean add(Emp emp);boolean update(Emp emp);boolean delete(Integer id); EmployeeServic…

聊聊不再兼容安卓的鸿蒙

鸿蒙NExt已经确定不再兼容安卓系统&#xff0c;这意味着鸿蒙系统在更新迭代上将会展现出更加迅猛的速度。不过&#xff0c;这样的变化也给开发者们带来了不小的挑战。如今&#xff0c;鸿蒙的开发主要推荐使用的是ArkTS&#xff0c;而不是我们熟悉的Java SDK。对于大量习惯于使用…

【C++刷题】[UVA 489]Hangman Judge 刽子手游戏

题目描述 题目解析 这一题看似简单其实有很多坑&#xff0c;我也被卡了好久才ac。首先题目的意思是&#xff0c;输入回合数&#xff0c;一个答案单词&#xff0c;和一个猜测单词&#xff0c;如果猜测的单词里存在答案单词里的所有字母则判定为赢&#xff0c;如果有一个字母是答…

Unity3d开发google chrome的dinosaur游戏

游戏效果 游戏中&#xff1a; 游戏中止&#xff1a; 一、制作参考 如何制作游戏&#xff1f;【15分钟】教会你制作Unity小恐龙游戏&#xff01;新手15分钟内马上学会&#xff01;_ unity教学 _ 制作游戏 _ 游戏开发_哔哩哔哩_bilibili 二、图片资源 https://download.csdn.…

9.Kafka消费者API实践

目录 概述实践topic消费者效果 消费指定topic的某个分区代码效果kafka分区策略-Range 概述 Kafka消费者API实践 实践 topic # ./kafka-topics.sh --bootstrap-server localhost:9092 --create --partitions 3 --replication-factor 1 --topic test03 [roothadoop02 bin]# ./…

【问题解决】Jetson nano 安装pytorch使用GPU推理

一. 问题描述 安装 yolov8 后只调用cpu推理图片 二. 解决步骤 2.1 在推理环境下&#xff0c;执行下面命令卸载pytorch pip uninstall torch torchtext torchaudio2.2 下载PyTorch的依赖: sudo apt-get -y update; sudo apt-get -y install libopenblas-dev;###2.3 下载py…