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 boto3

dynamodb = 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 Key

dynamodb = 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 sessionmaker


Base = 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 UUID


class TaskStatus(str, Enum):
    OPEN = "OPEN"
    CLOSED = "CLOSED"


@dataclass
class Task:
    id: UUID
    title: str
    status: TaskStatus
    owner: str

    @classmethod
    def create(cls, id_, title, owner):
        return cls(id_, title, TaskStatus.OPEN, owner)

Store:

from uuid import UUID  

import boto3
from boto3.dynamodb.conditions import Key

from models import Task, TaskStatus


class TaskStore:
    def __init__(self, table_name):
        self.table_name = table_name

    def 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 = 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_key
            response = 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:
                break

        return tasks

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

Migrations

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

  • Alembic
  • Django migrations

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

"""create tasks table

Revision 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 = None


def 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 Key

owner = "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_key
    response = table.query(**query_kwargs)
    tasks.extend(response["Items"])
    last_key = response.get("LastEvaluatedKey")
    if last_key is None:
        break

print(tasks)

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

from   models import Session, Task

tasks = []
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].id
    if len(tasks) < 10:
        break

print(tasks)

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

DynamoDB还有其他查询限制:

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

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

  1. 按所有者列出(您已在上面看到)
  2. 按所有者和任务ID获取(您可以在下面看到)
import boto3
from boto3.dynamodb.conditions import Key

owner = "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, Task

tasks = []
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].id
    if len(tasks) < 10:
        break

print(tasks)

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

import boto3


client = 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 Key

status = "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_key
    response = table.query(**query_kwargs)
    tasks.extend(response["Items"])
    last_key = response.get("LastEvaluatedKey")
    if last_key is None:
        break

print(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.mfbz.cn/a/370682.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

聚簇索引、回表与覆盖索引

聚簇索引一般指的是主键索引&#xff08;如果存在主键索引的话&#xff09;。 作为一个正常开发&#xff0c;建表时主键肯定是必须的。 而即使如果表中没有定义主键&#xff0c;InnoDB 会隐式选择一个唯一的非空索引代替。 所以我们就直接含糊点说&#xff1a; 聚簇索引就是…

crmebAI名片小程序全开源全端uniapp

应用介绍 AI名片小程序是一种基于人工智能技术的数字化智能名片&#xff0c;具有多种功能和特点。以下是AI名片小程序的简介&#xff1a; 智能化管理&#xff1a;AI名片小程序具备智能化的管理系统&#xff0c;用户可以方便地管理名片信息&#xff0c;包括个人信息、职位、公司…

jmeter-02切换中文,改为白色背景

文章目录 一、切换中文问题&#xff1a;jmeter设置中文后无法保存&#xff0c;下次启动还是英文 二、改为白色背景 一、切换中文 问题&#xff1a;jmeter设置中文后无法保存&#xff0c;下次启动还是英文 解决办法&#xff1a; 在jmeter路径下找到文件jmeter.bat开启编辑模式&…

如何在Shopee平台上进行手机类目选品?

在Shopee平台上进行手机类目的选品是一个关键而复杂的任务。卖家需要经过一系列的策略和步骤&#xff0c;以确保选品的成功和销售业绩的提升。下面将介绍一些有效的策略&#xff0c;帮助卖家在Shopee平台上进行手机类目选品。 先给大家推荐一款shopee知虾数据运营工具知虾免费…

指针的学习2

目录 数组名的理解 使用指针访问数组 一维数组传参的本质 冒泡排序 二级指针 指针数组 指针数组模拟二维数组 数组名的理解 数组名是数组首元素的地址 例外&#xff1a; sizeof(数组名),sizeof中单独放数组名&#xff0c;这里的数组名表示整个数组&#xff0c;计算的…

从零开始手写mmo游戏从框架到爆炸(三)— 服务启动接口与网络事件监听器

上一章我们完成了netty服务启动的相关抽象&#xff08;https://blog.csdn.net/money9sun/article/details/136025471&#xff09;&#xff0c;这一章我们再新增一个全局的服务启动类&#xff0c;方便后续扩展。 服务启动 新增的两个类如下&#xff1a; 定义一个接口IServer …

6款超好用的IDEA插件,开发必备!

今天给大家介绍几款开发必备的IDEA插件&#xff1a; JRebel 热部署插件&#xff0c;让你在修改完代码后&#xff0c;不用再重新启动&#xff0c;很实用&#xff01;但是&#xff0c;不是免费的&#xff0c;需要大家继续发挥下自己的聪明才智才能happy的使用 Json Parser 厌倦…

Linux 多线程 | 线程的概念

线程的概念 线程是一个执行分支&#xff0c;执行粒度比进程更细&#xff0c;调度成本更低&#xff1b; 线程是进程内部的一个执行流&#xff1b; 线程是CPU调度的基本单位&#xff0c;进程是承担分配系统资源的基本实体。 之前我们学习过虚拟地址空间的知识&#xff0c;知道…

SpringFramework实战指南(五)

SpringFramework实战指南(五) 4.3 基于 注解 方式管理 Bean4.3.1 实验一: Bean注解标记和扫描 (IoC)4.3.2 实验二: 组件(Bean)作用域和周期方法注解4.3.3 实验三: Bean属性赋值:引用类型自动装配 (DI)4.3.4 实验四: Bean属性赋值:基本类型属性赋值 (DI)4.3.5 实验五:…

区块链游戏解说:Axie Infinity 是什么

数据源&#xff1a;Axie Infinity Dashboard 作者&#xff1a;lesleyfootprint.network 什么是 Axie Infinity Axie Infinity 是一个引人入胜的区块链游戏&#xff0c;让玩家可以探索一个充满独特且可收藏的 NFT 生物&#xff08;称为 Axies&#xff09;的世界。 Axie Infin…

[AIGC] 21世纪Java与Go的相爱相杀

在21世纪的软件开发领域中&#xff0c;Java和Go这两门编程语言可谓是相爱相杀的存在。它们各自拥有着强大的特点和独特的优势&#xff0c;同时也存在着一些明显的竞争和冲突。让我们来看看这两门语言的故事&#xff0c;以及它们之间的深远意义。 文章目录 Java的魅力Go的魅力相…

【Simulink系列】——动态系统仿真 之 简单系统

引入 不同的系统具有不同的输入与输出。一般来说&#xff0c;输入输出数目越多&#xff0c;系统越复杂。最简单的系统只要一个输入一个输出&#xff08;SISO&#xff09;&#xff0c;且其任意时刻的输出只与当前时刻的输入有关。 一、简单系统定义 对于满足下列条件的系统&a…

Qt拖拽事件,实现控件内项的相互拖拽

文章目录 1拖拽演示2 步骤3 实现 这里主要以QTableview控件为例&#xff0c;实现表格内数据的相互拖拽。 1拖拽演示 2 步骤 自定以QTableView类&#xff0c;在自定义类中重写拖拽事件&#xff1a; void dropEvent(QDropEvent *event); void dragEnterEvent(QDragEnterEvent *…

【c++】友元

友元提供了一种突破封装的方式&#xff0c;有时提供了便利。但是友元会增加耦合度&#xff0c;破坏了封装&#xff0c;所以友元不宜多用 友元分为&#xff1a;友元函数和友元类 1.友元函数 问题&#xff1a;现在尝试去重载operator<<&#xff0c;然后发现没办法将ope…

北斗卫星在物联网时代的应用探索

北斗卫星在物联网时代的应用探索 在当今数字化时代&#xff0c;物联网的应用已经深入到人们的生活中的方方面面&#xff0c;让我们的生活更加智能便捷。而北斗卫星系统作为我国自主研发的卫星导航系统&#xff0c;正为物联网的发展提供了强有力的支撑和保障。本文将全面介绍北…

瑞_23种设计模式_工厂模式

文章目录 1 什么是工厂模式案例案例代码 2 简单工厂模式&#xff08;Simple Factory&#xff09;2.1 简单工厂模式的结构2.2 案例改进——简单工厂模式2.3 案例改进代码实现2.4 简单工厂模式优缺点2.5 拓展——静态工厂 3 工厂方法模式&#xff08;Factory Method&#xff09;★…

[学习笔记]刘知远团队大模型技术与交叉应用L6-基于大模型文本理解和生成介绍

介绍 NLP的下游运用可以分为&#xff1a;NLU(理解)和NLG(生成) 信息检索&#xff1a;NLU 文本生成&#xff1a;NLG 机器问答&#xff1a;NLUNLG 大模型在信息检索 大模型在机器问答 大模型在文本生成 信息检索-Information Retrieval (IR) 背景 谷歌搜索引擎目前同时集成了…

复旦大学NLP团队发布86页大模型Agent综述

复旦大学自然语言处理团队&#xff08;FudanNLP&#xff09;发布了一篇长达86页的综述论文&#xff0c;探讨了基于大型语言模型的智能代理的现状和未来。该论文从AI Agent的历史出发&#xff0c;全面梳理了基于大型语言模型的智能代理现状&#xff0c;包括LLM-based Agent的背景…

机器学习周报第二十八周 PINNs2

文章目录 week28 PINNs2摘要Abstract一、Lipschitz条件二、文献阅读1. 题目数据驱动的偏微分方程2. 连续时间模型3. 离散时间模型4.结论 三、CLSTM1. 任务要求2. 实验结果3. 实验代码3.1模型构建3.2训练过程代码 小结参考文献 week28 PINNs2 摘要 本文主要讨论PINN。本文简要…

小米平板6获取root权限教程

1. 绑定账号 1> 打开"设置-我的设备-全部参数-连续点击MIUI版本按钮"&#xff0c;直到提示已打开开发者模式( p s : 这里需要重点关注红框平板型号和 M I U I 版本&#xff0c;例如我这里平板型号是 X i a o m i P a d 6 &#xff0c; M I U I 版本是 14.0.10 &am…
最新文章