Postgres与DynamoDB:选择哪个数据库

启动新项目时需要做出的决定之一是使用哪个数据库。如果您使用的是Django这样的包含电池的框架,那么没有理由再三考虑。选择一个受支持的数据库引擎,就可以了。另一方面,如果你使用像FastAPI或Flask这样的微框架,你需要自己做出这个决定。您的决策会影响您需要哪些库以及如何进行数据建模。虽然你可能只是接触Postgres,但还有其他选择。其中之一是DynamoDB。与Postgres一样,它也是Python项目的绝佳选择。

在本文中,我将比较Postgres和DynamoDB。我将描述如何使用它们,解释它们的区别,并帮助您决定为Python应用程序选择哪一个。

什么是Postgres?什么是DynamoDB

Postgres是一个关系数据库管理系统(RDBMS)。这是一个开源项目,已经存在了35年多。它是一个成熟稳定的数据库引擎。对于大多数项目来说,这是一个不错的选择。

DynamoDB是AWS提供的NoSQL数据库服务。这是一项完全管理的服务,这意味着您不需要担心扩展、备份或可用性。对于需要快速扩展并且不想花时间管理数据库的项目来说,这是一个很好的选择。

第一个明显的区别是Postgres是SQL数据库,而DynamoDB是NoSQL数据库。这并不能告诉我们什么。是的,DynamoDB不是SQL数据库,但它是什么?DynamoDB实际上是一个键/值数据库。这对你的申请意味着什么?我们怎么能比较这两个数据库呢?密钥/值存储真的不就是Redis吗?我们现在还不要妄下结论。让我们先来看一个例子。

比如说我们需要管理TODO任务。一种经典的SQL方法是创建这样一个表:
在这里插入图片描述
然后我们可以查询它以获取给定所有者的所有任务:

SELECT * FROM tasks WHERE owner = ' john@doe.com';

DynamoDB的情况有所不同。DynamoDB是一个密钥/值存储。我们可以在两个关键模式之间进行选择。我们只能使用HASH密钥。在这种情况下,我们需要知道ID才能查询记录(可以将其视为Redis)。使用这个密钥模式,我们会有一个这样的表:
在这里插入图片描述
我们可以查询它以获取给定ID的任务:

import boto3dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("tasks")table.get_item(Key={"ID": "some_id",},
)

因此,没有办法有效地列出给定所有者的任务。

实际上,您可以使用扫描来扫描整个表,但这是非常低效的,应该避免。

幸运的是,还有另一个选项,HASH&RANGE密钥模式。在这种情况下,我们可以使用两个键来识别一个记录。我们可以使用HASH键来标识分区,使用RANGE键来标识该分区内的记录。HASH和RANGE键组合对于每个记录都必须是唯一的。在我们的情况下,我们可以将owner用作HASH密钥,将ID用作RANGE密钥。在这种情况下,我们会有这样的表格:
在这里插入图片描述
有了这样的结构,我们可以查询给定所有者的所有任务,如下所示:

import boto3
from boto3.dynamodb.conditions import Keydynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("tasks")table.query(KeyConditionExpression=Key("Owner").eq("john@doe.com"),
)

现在,您将获得所有者为 john@doe.com 的所有记录,这些记录将按ID排序。
尽管我们只是触及了表面,但我想指出一点:您可以使用Postgres或DynamoDB作为您的应用程序数据库。您只需要以不同的方式处理数据建模。

ORMs

ORM(对象关系映射器)是允许您轻松地将数据库记录映射到Python对象的库。它们大多遵循活动记录或数据映射器模式。使用活动记录模式,模型对象负责业务逻辑和数据持久性。Django ORM就是这样一个例子。对于数据映射器,这两个职责被委派给两个对象,模型对象包含属性和业务逻辑,而映射器对象负责数据持久性。一个例子是SQLAlchemy。它们构建在SQL数据库之上。因此,Postgres有很多ORM选项可供选择也就不足为奇了——SQLAlchemy、Django ORM、Peewee、PonyORM等等。

选择哪一个主要取决于个人喜好。虽然语法可能不同,但它们都允许您轻松定义模型和查询数据库。让我们来看看使用SQLAlchemy的示例:

from  sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmakerBase = declarative_base()class Task(Base):__tablename__ = "tasks"id = Column(Integer, primary_key=True, autoincrement=True)title = Column(String)status = Column(String)owner = Column(String)engine = create_engine("postgresql://postgres:postgres@localhost:5432/postgres")
Base.metadata.create_all(engine)Session = sessionmaker(bind=engine)
session = Session()task = Task(title="Clean your office", status="OPEN", owner="john@doe.com")
session.add(task)
session.commit()tasks = session.query(Task).filter(Task.owner == "john@doe.com").all()
print(tasks)

正如您所看到的,这非常简单:定义模型,创建会话,然后查询数据库。您可以对其他ORM使用相同/相似的方法。

虽然在Postgres中使用ORM时情况会非常好,但我不能对DynamoDB说同样的话。DynamoDB有一些ORM,例如PynamoDB和DynamORM,但在我看来,它们破坏了DynamoDB的整个目的。如果你想真正从DynamoDB中受益,你应该采用单表设计——ORM不支持这种设计。我所说的“真正的好处”是指实际使用DynamoDB,而不是在其上构建另一个带有UUID的关系数据库。让我们来看看如何使用它。

等效于NoSQL数据库的ORM是ODM(对象文档映射器)。例如,请参阅MongoDB的ODM。与ORM一样,它们简化了与数据库的交互。您也可以为DynamoDB找到类似的解决方案,例如,DynamoDB mapper。深入了解后,您会意识到它们没有得到广泛使用,没有得到维护,没有做好生产准备,或者它们强制执行了不必要的约束。

在这里插入图片描述
您可以看到以下内容:

  • HASH键有一个通用名称 PK (分区键)。它可以用于不同类型的记录。
  • RANGE键有一个通用名称 SK (排序键)。它可以用于不同类型的记录。

我们保留了其余的内容,所以我们可以很容易地将数据映射回模型对象。由于DynamoDB是无模式的,所以我们只需要为每个记录提供PK和SK。其他一切都是可选的。这使我们能够对不同类型的记录使用一个表。

为了支持更复杂的示例(和查询),需要对PK和SK使用复合密钥。例如,任务和板的密钥结构不同:
Tasks: PK=#TASK#{owner}, SK=#TASK#{task_id}.
Boards: PK=#BOARD#{owner}, SK=#BOARD#{contact_id}.
这使您能够按所有者轻松查询所有任务或按所有者查询所有板。ORM不鼓励使用单表设计所必需的复合键。这也是ORM在DynamoDB中表现不佳的原因之一。你可以阅读这篇有趣的文章来了解更多。

DynamoDB注重可预测的性能。这就是为什么我们只能通过HASH和RANGE键查询记录(HASH键必须始终存在于查询中)。

无法按其他列进行查询;您可以使用索引来支持更多的查询,但稍后会详细介绍。

因此,我们必须围绕查询而不是数据构建数据模型。这也适用于建立关系模型。使用ORM,您只需定义两个模型之间的外键关系。然后,您将能够通过其父对象访问相关对象,反之亦然。没有必要做任何其他事情。虽然这对于简单的应用程序来说可能很方便,但它可能会成为应用程序中的一个巨大瓶颈(N+1查询)。使用DynamoDB单表设计,希望通过一个查询加载所需的所有内容。

例如,如果你想加载一个包含所有任务的板,你必须对DynamoDB进行单个查询。它将返回不同类型的记录:板(名称、上次更新…)、打开的任务、关闭的任务等等。然后,由您将这些内容映射到包含任务等的板对象。因此,几乎不可能为DynamoDB构建通用ORM。所有内容都非常特定于用例/查询。虽然这听起来像是一项艰巨的工作,但它迫使你进行高效的设计。因此,使用DynamoDB,您需要为数据持久性构建自己的抽象——例如,存储库。您可以指定您的纯Python(或pydantic或其他)模型,并使用Boto3来实现您的存储库。

Models:

from  dataclasses import dataclass
from enum import Enum
from uuid import UUIDclass TaskStatus(str, Enum):OPEN = "OPEN"CLOSED = "CLOSED"@dataclass
class Task:id: UUIDtitle: strstatus: TaskStatusowner: str@classmethoddef create(cls, id_, title, owner):return cls(id_, title, TaskStatus.OPEN, owner)

Store:

from uuid import UUID  import boto3
from boto3.dynamodb.conditions import Keyfrom models import Task, TaskStatusclass TaskStore:def __init__(self, table_name):self.table_name = table_namedef add(self, task):dynamodb = boto3.resource("dynamodb")table = dynamodb.Table(self.table_name)table.put_item(Item={"PK": f"#TASK#{task.owner}","SK": f"#TASK#{task.id}","id": str(task.id),"title": task.title,"status": task.status.value,"owner": task.owner,})def list_by_owner(self, owner):dynamodb = boto3.resource("dynamodb")table = dynamodb.Table(self.table_name)last_key = Nonequery_kwargs = {"IndexName": "GS1","KeyConditionExpression": Key("PK").eq(f"#TASK#{owner}"),}tasks = []while True:if last_key is not None:query_kwargs["ExclusiveStartKey"] = last_keyresponse = table.query(**query_kwargs)tasks.extend([Task(id=UUID(record["id"]),title=record["title"],owner=record["owner"],status=TaskStatus[record["status"]],)for record in response["Items"]])last_key = response.get("LastEvaluatedKey")if last_key is None:breakreturn tasks

简而言之,Postgres和DynamoDB都有ORM。您可能想在Postgres中使用一个,但可能不想在DynamoDB中使用。

Migrations

另一个有趣的主题–数据库迁移。数据库迁移提供了一种更新数据库模式的方法。对于Postgres,有一些工具可以简化迁移

  • Alembic
  • Django migrations

您可以使用它们轻松地将表/索引/列/约束添加到数据库中。它们甚至可以通过ORM从模型定义中自动生成。Alembic迁移示例:

"""create tasks tableRevision ID: 1f8b5f5d1f5a
Revises:
Create Date: 2021-12-05 12:00:00.000000"""from   alembic import op
import sqlalchemy as sa# revision identifiers, used by Alembic.
revision = "1f8b5f5d1f5a"
down_revision = None
branch_labels = None
depends_on = Nonedef upgrade():op.create_table("tasks",sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),sa.Column("title", sa.String),sa.Column("status", sa.String),sa.Column("owner", sa.String),)def downgrade():op.drop_table("tasks")

对于大多数常见的操作,它非常简单。对于自动生成的迁移,没有太多工作要做。如果需要,您可以随时手动编写。

对于DynamoDB,没有模式——我们也说过我们想要一个表。无法更改密钥,因此无法迁移。还是有?事实证明,在向记录添加/从记录中删除属性时,您有两个选项:

  1. 在运行时处理更改–例如,添加具有默认值的列
  2. 遵循以下三步模式:
    1 开始使用新的“属性”写入新记录
    2 使用新的“属性”将现有记录更新为所需值(您可以扫描表中的所有记录并更新它们)
    3 开始在代码中使用新的“属性”

您可以将三步模式推广到其他类型的更改,如删除属性。
有了所有可用的工具,Postgres的迁移更容易。

如果您在更复杂的项目中工作,而性能差或停机时间确实不是一种选择,那么您可能会手动编写大部分迁移代码。对于这种情况,Postgres和DynamoDB之间没有太大的区别。

Queries

事实上,我们已经谈到了这个话题。我们甚至查看了示例查询。尽管如此,让我们更深入地挖掘。

任何不习惯DynamoDB的人都会说,由于查询数据的能力非常有限,使用它没有意义。的确,查询在DynamoDB中的灵活性要低得多。人们普遍认为,在使用SQL数据库时,可以使用所需的任意列集查询任何数据。理论上是这样的。换句话说,只有当您的数据库足够小,可以放入服务器的内存时,这才是正确的。

一旦您有了更严重的数据量,查询往往会变慢,这反过来又会减慢应用程序的速度。迟早,你需要开始考虑指数。要进行快速查询,您需要添加索引并仅按索引列进行查询。您还将意识到,如果不限制返回记录的数量,您将无法进行查询。无限制的查询很容易导致系统瘫痪。这是因为你根本不知道他们会返回多少数据。您的数据库可能需要从磁盘读取10亿行,这需要时间。

那我们该何去何从呢?DynamoDB的构建方式迫使您提前考虑这些问题。

在设计数据模型时,您需要:

  1. 使用HASH键对数据进行分区
  2. 使用RANGE键对数据进行排序

然后,您只能通过HASH和RANGE键或预定义的索引进行查询,这些索引有自己的HASH和RANGE键。单个查询返回的数据量也有限制。所以你不得不使用分页。

所有这些限制在一开始可能听起来很可怕,让人不知所措。一旦你习惯了它们,你就会意识到它们的存在是有原因的。让我们来看看示例查询。。。

With DynamoDB:

import boto3
from boto3.dynamodb.conditions import Keyowner = "john@doe.com"dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("tasks-api")
last_key = None
query_kwargs = {"IndexName": "GS1","KeyConditionExpression": Key("PK").eq(f"#TASK#{owner}"),
}
tasks = []while True:if last_key is not None:query_kwargs["ExclusiveStartKey"] = last_keyresponse = table.query(**query_kwargs)tasks.extend(response["Items"])last_key = response.get("LastEvaluatedKey")if last_key is None:breakprint(tasks)

使用Postgres+SQLAlchemy(分页查询):

from   models import Session, Tasktasks = []
last_evaluated_id = -1
session = Session()while True:query = session.query(Task).filter(Task.id > last_evaluated_id).order_by(Task.id).limit(10)tasks.extend(query.all())last_evaluated_id = tasks[-1].idif len(tasks) < 10:breakprint(tasks)

正如您所看到的,DynamoDB和Postgres分页查询在代码复杂度上没有太大区别。

DynamoDB还有其他查询限制:

  1. 我们必须始终至少提供HASH密钥
  2. 我们只能通过HASH和RANGE键或预定义索引进行查询
  3. 返回的记录按RANGE键排序。您只能在升序和降序之间进行选择

我们知道,在当前的表设计中,我们只能支持两个查询:

  1. 按所有者列出(您已在上面看到)
  2. 按所有者和任务ID获取(您可以在下面看到)
import boto3
from boto3.dynamodb.conditions import Keyowner = "john@doe.com"
task_id = "7b0f4bc8-4751-41f1-b3b1-23a935d81cd6"dynamodb = boto3.resource("dynamodb", endpoint_url="http://localhost:9999")
table = dynamodb.Table("tasks-api")query_kwargs = {"KeyConditionExpression": Key("PK").eq(f"#TASK#{owner}") & Key("SK").eq(f"#TASK#{task_id}"),
}
task = response = table.query(**query_kwargs)["Items"][0] print(task)

例如,我们不能按状态进行查询。但我们可以用Postgres做到这一点:

from models import Session, Tasktasks = []
last_evaluated_id = -1
session = Session()while True:query = session.query(Task).filter(Task.status == "OPEN").filter(Task.id > last_evaluated_id).order_by(Task.id).limit(10)tasks.extend(query.all())last_evaluated_id = tasks[-1].idif len(tasks) < 10:breakprint(tasks)

别担心,我们仍然可以在DynamoDB中支持这样的查询。我们只需要添加即可使用全局二级索引(GSI)。我们可以创建一个名为GS1的具有单个GSI的表,如下所示:

import boto3client = boto3.client("dynamodb", endpoint_url="http://localhost:9999")
client.create_table(TableName="tasks-api",KeySchema=[{"AttributeName": "PK", "KeyType": "HASH"},{"AttributeName": "SK", "KeyType": "RANGE"},],AttributeDefinitions=[{"AttributeName": "PK", "AttributeType": "S"},{"AttributeName": "SK", "AttributeType": "S"},{"AttributeName": "GS1PK", "AttributeType": "S"},{"AttributeName": "GS1SK", "AttributeType": "S"},],GlobalSecondaryIndexes=[{"IndexName": "GS1","KeySchema": [{"AttributeName": "GS1PK", "KeyType": "HASH"},{"AttributeName": "GS1SK", "KeyType": "RANGE"},],"Projection": {"ProjectionType": "ALL"},},],BillingMode="PAY_PER_REQUEST",
)

就像表格一样,GSI有自己的HASH和RANGE键。然后,我们可以按如下状态进行查询:

import boto3
from boto3.dynamodb.conditions import Keystatus = "OPEN"dynamodb = boto3.resource("dynamodb", endpoint_url="http://localhost:9999")
table = dynamodb.Table("tasks-api")
last_key = None
query_kwargs = {"KeyConditionExpression": Key("GS1PK").eq(f"#TASK#{status}"),"IndexName": "GS1",
}
tasks = []while True:if last_key is not None:query_kwargs["ExclusiveStartKey"] = last_keyresponse = table.query(**query_kwargs)tasks.extend(response["Items"])last_key = response.get("LastEvaluatedKey")if last_key is None:breakprint(tasks)

正如您所看到的,您需要显式定义查询时要使用的索引。另一方面,Postgres自己做到了这一点。再一次,如果您比较查询,您会发现它们毕竟没有那么大的不同。

只要您使用的是精确的查询(=,!=,>,<,…),Postgres和DynamoDB之间就没有太大区别。使用DynamoDB,您无法进行LIKE、IN等查询或全文搜索。要支持这样的功能,您需要将其与Elasticsearch或类似功能配对。

您可以在此处看到HASH/RANGE键支持的条件的完整列表。

简而言之,当用作应用程序数据库时,这两个数据库都应该支持您的需求。如果你想进行更复杂的查询(例如,报告、分析等),你要么需要使用Postgres,要么将DynamoDB与其他一些服务配对,如Elasticsearch、Athena或Redshift等。

Performance

性能总是一个棘手的话题。进入“意见”领域是很容易的。我会尽量避免的。如果你在网上搜索,你会发现有人赞扬DynamoDB,诋毁Postgres,反之亦然。事实是,在大多数情况下,你会看到以下缺陷之一:

  1. DynamoDB被用作关系数据库——多个表,没有单表设计
  2. Postgres的索引/分区很差
  3. 扫描与DynamoDB一起使用
  4. Postgres被非索引列或无限制查询

这些年来,我在各种生产环境中都使用过这两种方法。我可以告诉你以下几点:如果使用得当,它们都会燃烧得很快。当直接比较时,您可能会注意到它们之间的细微差异,但从应用程序的角度来看,您不会注意到太大的性能差异。当然,在亚马逊/谷歌/奈飞等网站。规模,你会明显地看到一些不同;毕竟,创建DynamoDB是有原因的。

长话短说:对于大多数项目来说,性能不应该是主要的决定因素。至少如果你不是亚马逊/网飞/。。。。相反,把时间花在开发一个高效的数据(访问)模型上,你会没事的。

Backups

备份是一个经常被遗忘的主题。没有备份之前一切都很好。由于我们已经在AWS和DynamoDB中,让我们将AWS RDS上的Postgres与DynamoDB进行比较。

它们都支持按需备份和连续备份。它们都提供时间点恢复,我强烈建议您这样做。我强烈建议您同时使用内置备份。对于时间点恢复,两者的流程基本相同:

  1. 从给定的时间戳创建一个时间点恢复,这将创建一个新实例(RDS)或一个新表(DynamoDB)
  2. 将已恢复数据中丢失的数据复制到应用程序实例/表,或将应用程序重新路由到新实例/表

提示:AWS上的备份符合SOC 2等证书。

您可以从DynamoDB和RDS的官方文档中了解更多信息。

Transactions

在我们结束之前,让我们谈一谈事务。事务是一种允许我们将多个操作封装到单个数据库操作中的机制。这有助于确保要么执行所有操作,要么不执行任何操作。例如,通过这种方式,我们可以确保减少库存项目的数量并创建新订单。如果没有交易,我们可能会收到一个没有库存的订单——例如,有人在你之前下了订单。

事务是SQL数据库的“面包和黄油”。另一方面,它们在NoSQL数据库中并不常见。Postgres和DynamoDB都支持事务——要么全部支持,要么什么都不支持。它们在Postgres中可能感觉更惯用,但您也可以在DynamoDB中使用它们。您只需要使用较低级别的Boto3 API。

因此,在这方面,您应该可以使用DynamoDB或Postgres。

您可以从DynamoDB和Postgres的官方文档中了解更多信息。

我应该使用哪一个

所有这些都给你留下了一个问题:我应该使用哪一个?对于大多数情况,您可以使用其中任何一种。尽管如此,还是有一些指导原则可以帮助您进行选择:

  1. 如果您正在使用Django(或任何其他包含电池的框架),请使用Postgres。这是最简单的方法。如果您尝试使用DynamoDB,那么您只需要与框架作斗争。
  2. 如果你有一个缺乏经验的团队,去Postgres。这更容易让你的头脑清醒起来,并在以后纠正你的错误。
  3. 如果您需要在多个AWS区域轻松复制数据,请使用DynamoDB。它就是为这个而建的。
  4. 如果您的流量非常大,请选择DynamoDB。您可以按要求付款。
  5. 如果你想无服务器化,那就选择DynamoDB。但在这种情况下,您也需要在Lambda上运行您的应用程序。
  6. 如果您不在AWS生态系统中,请使用Postgres。DynamoDB与AWS紧密相连。

无论您选择哪种,请记住,拥有一个高效的数据模型比您使用的数据库更重要。DynamoDB会强迫你这么做。Postgres不会。但正如您所看到的,当有效地使用它们时,您最终会得到非常相似的使用模式。

结论

虽然本文涵盖了很多内容,但仍有很多内容需要探索。那么接下来该怎么办呢?你可以用它们两个构建同一个应用程序,以真正掌握它们。我们——惊喜,惊喜!——有两门课程可以帮助你做到这一点:

  1. Serverless FastAPI course
  2. Scalable FastAPI course

前者使用DynamoDB,后者使用Postgres。试着用两者构建相同的东西,你会看到它的进展。如果没有别的,我相信一旦完成,你在数据建模方面会做得更好。

您还可以使用本文中的示例:GitHub repository。

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

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

相关文章

JVM系列——垃圾收集器Parrlel Scavenge、CMS、G1常用参数和使用场景

背景 当前在Java领域&#xff0c;JDK 8版本仍然享有广泛的使用&#xff0c;它支持了Parallel Scavenge、CMS和G1这几种垃圾收集器。因此&#xff0c;为了在业务应用中更加高效地进行开发和性能调优&#xff0c;我们需要对这些垃圾收集器的工作原理和特性有一个全面的理解和认识…

【MySQL】双写、重做日志对宕机时脏页数据落盘的作用的疑问及浅析

众所周知&#xff0c;双写机制、重做日志文件是mysql的InnoDB引擎的几个重要特性之二。其中两者的作用都是什么&#xff0c;很多文章都有分析&#xff0c;如&#xff0c;双写机制&#xff08;Double Write&#xff09;是mysql在crash后恢复的机制&#xff0c;而重做日志文件&am…

【复现】大华 DSS 数字监控系统 任意文件读取漏洞_38

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 大华DSS是大华的大型监控管理应用平台&#xff0c;支持几乎所有涉及监控等方面的操作&#xff0c;支持多级跨平台联网等操作。 可…

2024年适合进入股市吗?北京想开股票账户找哪家证券公司交易佣金费用最低?

股市规则是指股票市场中的一系列规则和制度&#xff0c;用于监管和管理股票交易。以下是一些常见的股市规则&#xff1a; 证券法律法规&#xff1a;股市规则的基础是国家的证券法律法规&#xff0c;包括证券法、公司法等&#xff0c;用于规范股票发行、交易和上市等方面的法律规…

重生奇迹MU套装怎么配

汉斯的皮套装&#xff1a;冰之指环,皮护腿,皮盔,皮护手,皮靴,皮铠,流星槌 汉斯的青铜套装&#xff1a;青铜护腿,青铜靴,青铜铠 汉斯的翡翠套装&#xff1a;雷之项链,翡翠护腿,翡翠盔,翡翠铠,远古之盾 汉斯的黄金套装&#xff1a;火之项链,黄金护腿,黄金护手,黄金靴,黄金铠 …

代码随想录算法训练营DAY11 | 栈与队列 (2)

一、LeetCode 20 有效的括号 题目链接&#xff1a;20.有效的括号https://leetcode.cn/problems/valid-parentheses/ 思路&#xff1a;遇到左括号直接进栈&#xff1b;遇到右括号判断站顶是否有匹配的括号&#xff0c;没有就返回flase&#xff0c;有就将栈顶元素出栈&#xff1…

FANUC机器人示教器的菜单变成了图标,如何改成列表的形式?

FANUC机器人示教器的菜单变成了图标&#xff0c;如何改成列表的形式&#xff1f; 如下图所示&#xff0c;开机后按下MENU菜单键时&#xff0c;发现原来的列表形式变成了菜单图标的形式&#xff0c;同时在按F1-F5键时&#xff0c;提示&#xff1a;HMI模式-键不可用&#xff0c; …

向日葵案例解析:无外网接入,医疗设备如何进行远程售后运维

随着医学科学以及生物工程技术的高速发展&#xff0c;医院对于高端医疗设备如MR、CT、B超等高科技成像设备和放射治疗设备的需求激增。医学影像检查作为一种重要的手段&#xff0c;在许多疾病确诊过程中发挥着至关重要的作用。检查结果正确与否&#xff0c;直接影响临床医生对疾…

JS 引导动画

前言 引导动画是程序在某一时刻播放的动画&#xff0c;通常用于向用户介绍程序的功能和特点。 实现效果 实现方式 引导动画的实现方式有很多种&#xff0c;这里我使用的是 CSS 的 clip-path 属性。 技术选型 这里我为什么要选择 clip-path 属性而不是 mask 属性呢&#xf…

阿狸与小兔子的奇幻之旅

在很久很久以前&#xff0c;有一个遥远的国度&#xff0c;这个国度里生活着各种各样的动物&#xff0c;它们和谐共处&#xff0c;幸福快乐。在这个国度里&#xff0c;有一只聪明伶俐的小狐狸&#xff0c;名叫阿狸。 一天&#xff0c;阿狸在森林里散步时&#xff0c;遇到了一只正…

关于网络面试题汇总

什么是TCP/IP五层模型&#xff1f;它们的作用是啥&#xff1f;基于TCP/IP实现的应用&#xff08;层协议&#xff09;有哪些&#xff1f; TCP/IP五层模型&#xff0c;从上向下分别是&#xff1a; 应用层&#xff1a;应用程序本身&#xff0c;应用层的作用是负责应用程序之间的…

数据结构篇-05:哈希表解决字母异位词分组

本文对应力扣高频100 ——49、字母异位词分组 哈希表最大的特点就是它可以把搜索元素的时间复杂度降到O(1)。这一题就是要我们找到 “字母异位词” 并把它们放在一起。 “字母异位词”就是同一个单词中字母的不同组合形式。判断“字母异位词”有两个视角&#xff1a;1、所含字…

全面认识DOS系统

目录 一、DOS系统的功能 1.执行命令和程序&#xff08;处理器管理&#xff09; 2.内存管理 3.设备管理 4.文件管理 5.作业管理 二、文件与目录 三、文件类型与属性 1.系统属性&#xff08;S&#xff09; 2.隐含属性&#xff08;H&#xff09; 3.只读属性&#xff08…

初谈类和对象

文章目录 前言类的引入类的定义类的两种定义方式成员命名规则 类的访问限定符及封装访问限定符面试题封装 类的作用域类的实例化类对象模型计算类对象的大小 this指针this指针特性 前言 C语言是面向过程的&#xff0c;关注点是过程&#xff1b;而C面向的是对象&#xff0c;关注…

mysql-FIND_IN_SET查询优化

优化前 SELECTuser_id,user_name,real_name,PASSWORD,real_org_id,real_org_name,real_dept_id,real_dept_name, STATUS FROMsys_user WHEREis_del 0 AND find_in_set( lilong, login_user_account ) 优化后 SELECTuser_id,user_name,real_name,PASSWORD,real_org_id,real…

回归预测 | Matlab实现RIME-CNN-LSTM-Attention霜冰优化卷积长短期记忆网络注意力多变量回归预测(SE注意力机制)

回归预测 | Matlab实现RIME-CNN-LSTM-Attention霜冰优化卷积长短期记忆网络注意力多变量回归预测&#xff08;SE注意力机制&#xff09; 目录 回归预测 | Matlab实现RIME-CNN-LSTM-Attention霜冰优化卷积长短期记忆网络注意力多变量回归预测&#xff08;SE注意力机制&#xff0…

如何处理我们的文本数据成构建词表

我们拿到在拿到一堆语料数据&#xff0c;或者是在网络中爬取下来的文本数据如何处理成为模型能够训练的数据呢&#xff1f;这里有我们先经过停用词和按字分词的处理之后&#xff0c;得到的问答对文本数据&#xff0c;input_by_word.txt 和 target_by_word.txt 。其中&#xff0…

2024美赛预测算法 | 回归预测 | Matlab基于RIME-LSSVM霜冰算法优化最小二乘支持向量机的数据多输入单输出回归预测

2024美赛预测算法 | 回归预测 | Matlab基于RIME-LSSVM霜冰算法优化最小二乘支持向量机的数据多输入单输出回归预测 目录 2024美赛预测算法 | 回归预测 | Matlab基于RIME-LSSVM霜冰算法优化最小二乘支持向量机的数据多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效…

Docker进阶篇-Docker网络

一、描述 1、docker不启动&#xff0c;默认网络情况 查看网卡情况使用&#xff0c;ifconfig或者ip addr ens33&#xff1a;本机网卡 lo&#xff1a;本机回环网络网卡 virbr0:在CentoS 7的安装过程中如果有选择相关虚拟化的的服务安装系统后&#xff0c;启动网卡时会发现 …

Web实战丨基于Django的简单网页计数器

文章目录 写在前面Django简介主要程序运行结果系列文章写在后面 写在前面 本期内容 基于django的简单网页计数器 所需环境 pythonpycharm或vscodedjango 下载地址 https://download.csdn.net/download/m0_68111267/88795604 Django简介 Django 是一个用 Python 编写的高…