分布式事务的21种武器 - 3

news2024/10/5 23:28:36

在分布式系统中,事务的处理分布在不同组件、服务中,因此分布式事务的ACID保障面临着一些特殊难点。本系列文章介绍了21种分布式事务设计模式,并分析其实现原理和优缺点,在面对具体分布式事务问题时,可以选择合适的模式进行处理。原文: Exploring Solutions for Distributed Transactions (3)

David Dvořáček @Unsplash
David Dvořáček @Unsplash

在不同业务场景下,可以有不同的解决方案,常见方法有:

  1. 阻塞重试(Blocking Retry)
  2. 二阶段和三阶段提交(Two-Phase Commit (2PC) and Three-Phase Commit (3PC))
  3. 基于后台队列的异步处理(Using Queues to Process Asynchronously in the Background)
  4. TCC补偿(TCC Compensation Matters)
  5. 本地消息表(异步保证)/发件箱模式(Local Message Table (Asynchronously Ensured)/Outbox Pattern)
  6. MQ事务(MQ Transaction)
  7. Saga模式(Saga Pattern)
  8. 事件驱动(Event Sourcing)
  9. 命令查询职责分离(Command Query Responsibility Segregation, CQRS)
  10. 原子提交(Atomic Commitment)
  11. 并行提交(Parallel Commits)
  12. 事务复制(Transactional Replication)
  13. 一致性算法(Consensus Algorithms)
  14. 时间戳排序(Timestamp Ordering)
  15. 乐观并发控制(Optimistic Concurrency Control)
  16. 拜占庭容错(Byzantine Fault Tolerance, BFT)
  17. 分布式锁(Distributed Locking)
  18. 分片(Sharding)
  19. 多版本并发控制(Multi-Version Concurrency Control, MVCC)
  20. 分布式快照(Distributed Snapshots)
  21. 主从复制(Leader-Follower Replication)

本文将介绍Saga、事件驱动以及CQRS三种模式。

7. Saga模式(Saga Pattern)
图片来源: https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-data-persistence/saga-pattern.html
图片来源: https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-data-persistence/saga-pattern.html
  • 管理跨多个微服务的长时间事务。
  • 将事务分解为一系列较小的、独立的步骤,每个步骤都由单独的微服务管理。
  • 包含如下步骤:
    1. 协调微服务负责接收事务初始请求。
    2. 协调微服务通过向第一个负责处理事务的微服务发送消息来启动事务。
    3. 第一个微服务执行事务,并将消息发送回协调微服务,反馈其是否成功。
    4. 如果第一步成功,协调微服务将向负责事务下一步的微服务发送消息。
    5. 如果第一步失败,协调微服务发送补偿动作来撤消失败步骤的影响。
    6. 重复步骤3-5,直到每个微服务要么完成其步骤,要么在失败时触发补偿操作(回滚)。
    7. 一旦所有步骤都成功完成,协调微服务就会发送一条消息,表明整个事务已经成功。
    8. 如果任何步骤失败并且触发了补偿操作(回滚),则协调微服务将发送一条消息,指示整个事务失败。
import pika
import json

# Define the RabbitMQ connection parameters
credentials = pika.PlainCredentials('guest''guest')
parameters = pika.ConnectionParameters('localhost'5672'/', credentials)

# Define the messages to be sent between services
start_order_message = {'order_id''12345''items': [{'id''1''name''item1'}, {'id''2''name''item2'}]}
payment_message = {'order_id''12345''amount'100.0}
shipping_message = {'order_id''12345''items': [{'id''1''name''item1'}, {'id''2''name''item2'}]}

# Define the compensation messages to be sent in case of failure
cancel_payment_message = {'order_id''12345''amount'100.0}
cancel_shipping_message = {'order_id''12345''items': [{'id''1''name''item1'}, {'id''2''name''item2'}]}

# Define the function to send messages to the RabbitMQ broker
def send_message(queue_name, message):
    connection = pika.BlockingConnection(parameters)
    channel = connection.channel()
    channel.queue_declare(queue=queue_name)
    channel.basic_publish(exchange='', routing_key=queue_name, body=json.dumps(message))
    connection.close()

# Define the function to handle the start of the order
def start_order():
    # Send the start order message to the Order service
    send_message('start_order', start_order_message)

# Define the function to handle the payment of the order
def payment():
    try:
        # Send the payment message to the Payment service
        send_message('payment', payment_message)
    except Exception as e:
        # Send the cancel payment message to the Payment service in case of failure
        send_message('cancel_payment', cancel_payment_message)
        raise e

# Define the function to handle the shipping of the order
def shipping():
    try:
        # Send the shipping message to the Shipping service
        send_message('shipping', shipping_message)
    except Exception as e:
        # Send the cancel shipping message to the Shipping service in case of failure
        send_message('cancel_shipping', cancel_shipping_message)
        raise e

# Define the function to handle the cancellation of the order
def cancel_order():
    # Send the cancel payment message to the Payment service
    send_message('cancel_payment', cancel_payment_message)

    # Send the cancel shipping message to the Shipping service
    send_message('cancel_shipping', cancel_shipping_message)

# Define the main function to execute the Saga
def execute_saga():
    try:
        # Start the order
        start_order()

        # Perform the payment
        payment()

        # Perform the shipping
        shipping()
    except Exception as e:
        # Cancel the order in case of failure
        cancel_order()
        raise e

# Call the main function to execute the Saga
execute_saga()

示例代码

  • 用RabbitMQ作为简单消息代理
  • 定义了在服务之间发送的五条消息: start_order_messagepayment_messageshipping_messagecancel_payment_message以及 cancel_shipping_message
  • start_order函数将 start_order_message发送给 order_service
  • 在收到来自 start_order函数的消息后, order_service创建订单,并发送回包含 order_id的确认消息。
  • 一旦 start_order函数接收到确认消息,将发送 payment_messagepayment_service来处理订单支付。
  • 如果支付成功, payment_service将返回一条包含 payment_id的确认消息。
  • start_order函数将 shipping_message发送给 shipping_service,以便在付款成功后发货。
  • 如果发货成功, shipping_service将返回一条包含 shipping_id的确认消息。
  • 如果上述任何步骤失败,则回滚事务,分别给 shipping_servicepayment_service发送 cancel_shipping_messagecancel_payment_message,撤销所做的更改。
  • 通过向 start_order发送初始消息、监听确认消息以及在发生故障时处理补偿来处理整个Saga流程。换句话说,Saga模式涉及一系列补偿操作,以便在发生故障时撤消事务的影响。

优点

  • 管理跨多个微服务的长时间事务
  • 避免服务独立运行时出现不一致或数据损坏
  • 如果事务中的某个步骤失败,则提供补偿操作
  • 允许服务自主、独立运行

缺点

  • 实现这种模式可能比较复杂
  • 很难设计和测试补偿逻辑
  • 会给系统增加额外的复杂性,使维护和故障排除变得更加困难

适用场景

  • 涉及多个服务(如支付处理、订单履约、物流)的电子商务交易
  • 涉及多个系统和服务的复杂金融交易
  • 涉及多个供应商、制造商和物流供应商的供应链管理系统

尝试-确认-取消(TCC)模式与Saga模式的相似之处

  • 两种模式都维护分布式事务中涉及多个微服务的数据一致性
  • 两种模式都要求每个服务定义一组需要作为事务一部分的操作

尝试-确认-取消(TCC)模式与Saga模式的不同之处

  • Saga模式使用前向恢复法,每个服务在出现故障时启动一个补偿事务,而TCC模式使用后向恢复法,每个服务验证事务是否可以继续,然后才确认或取消。
  • Saga模式将事务表示为事件序列,事件由相关服务之间发送的消息表示。TCC模式将事务表示为由所涉及的每个服务执行的操作序列。
  • Saga模式适用于涉及多个服务的长时间事务,而TCC模式适用于涉及较少服务的短时间事务。
  • Saga模式的实现可能比TCC模式更复杂,要求每个服务能够发起补偿事务并处理潜在故障。

8. 事件驱动(Event Sourcing)
图片来源: https://eventuate.io/whyeventsourcing.html
图片来源: https://eventuate.io/whyeventsourcing.html
  • 对应用程序状态所做的所有更改作为一系列事件。
  • 将这些事件存储在数据库或事件日志中,从而提供应用程序状态随时间变化的完整审计跟踪。
  • 涉及如下步骤:
    1. 每当应用程序状态发生变化时,就会捕获相应事件,事件包含所有更改相关信息(例如已修改的数据和进行更改的用户)。
    2. 事件存储在事件日志中,可以用数据库或消息代理实现,每个事件都有一个唯一标识符,并带有时间戳,以确保事件有序。
    3. 通过按时间顺序重播事件日志中的事件来重构应用程序当前状态。该过程包括将应用程序的状态初始化为其初始状态,然后依次应用每个事件来更新状态。
    4. 一旦状态被重构,就可以对其进行查询,以提供有关应用程序当前状态的信息。
    5. 可以实时处理事件,触发其他动作或更新。
    6. 事件处理完成后,可以将其归档或删除以释放存储空间。
图片来源: https://eventuate.io/whyeventsourcing.html
图片来源: https://eventuate.io/whyeventsourcing.html
import uuid
import json
import time

class BankAccount:
    def __init__(self):
        self.balance = 0
        self.event_sourcing = EventSourcing()

    def deposit(self, amount):
        event = Event('deposit', {'amount': amount})
        self.event_sourcing.add_event(event)
        self.balance += amount

    def withdraw(self, amount):
        if self.balance < amount:
            raise ValueError('Insufficient balance')
        event = Event('withdraw', {'amount': amount})
        self.event_sourcing.add_event(event)
        self.balance -= amount

    def get_balance(self):
        return self.balance

    def get_events(self):
        return self.event_sourcing.get_events()

    def get_event_by_id(self, event_id):
        return self.event_sourcing.get_event_by_id(event_id)

    def replay_events(self):
        self.balance = 0
        for event in self.event_sourcing.get_events():
            if event.type == 'deposit':
                self.balance += event.data['amount']
            elif event.type == 'withdraw':
                self.balance -= event.data['amount']

class Event:
    def __init__(self, type, data):
        self.id = uuid.uuid4()
        self.timestamp = int(time.time())
        self.type = type
        self.data = data

class EventSourcing:
    def __init__(self):
        self.event_store = EventStore()

    def add_event(self, event):
        self.event_store.store_event(event)

    def get_events(self):
        return self.event_store.get_events()

    def get_event_by_id(self, event_id):
        return self.event_store.get_event_by_id(event_id)

class EventStore:
    def __init__(self):
        self.events = []

    def store_event(self, event):
        self.events.append(event)

    def get_events(self):
        return self.events

    def get_event_by_id(self, event_id):
        for event in self.events:
            if event.id == event_id:
                return event
        raise ValueError('Event not found')

class Auditor:
    def __init__(self, event_store):
        self.event_store = event_store

    def log_events(self):
        for event in self.event_store.get_events():
            print(json.dumps({'id': str(event.id), 'type': event.type, 'data': event.data}))

account = BankAccount()
auditor = Auditor(account.event_sourcing.event_store)

account.deposit(100)
account.withdraw(50)
account.deposit(75)

print('Current balance:', account.get_balance())

print('All events:')
auditor.log_events()

event_id = account.get_events()[1].id
event = account.get_event_by_id(event_id)
print('Event details:', json.dumps({'id': str(event.id), 'type': event.type, 'data': event.data}))

示例代码

  • BankAccount类用来演示如何使用事件来重建实体状态,包含 balance属性,支持 deposit(存款)和 withdraw(取款)两种操作。
  • Event类有类型和数据属性。
  • EventSourcing类定义了BankAccount的 event_sourcing属性
  • EventSourcing类包含 event_store属性作为事件列表。
  • EventStore类有两个主要方法: store_event()(在列表中存储事件)和 get_events()(从列表中检索事件)。
  • add_event方法向 event_store添加新事件。
  • get_eventsget_event_by_id方法可以通过ID检索所有事件或特定事件。
  • depositwithdraw方法创建具有唯一ID、时间戳和字典(包含操作信息,在本例中为操作类型和金额)的新 Event对象,事件被添加到 BankAccount实例的 event_sourcing属性中。
  • 每次进行 depositwithdraw时,都会创建相应事件,并通过 EventStore类将其存储在事件存储中。
  • get_balance方法返回帐户当前余额。
  • replay_events()方法从事件存储中检索所有事件,并计算当前余额。遍历事件存储中的所有事件,并根据每个事件的类型和数据更新 BankAccountbalance属性。
  • Auditor类监听存储在事件存储库中的所有事件,并在终端上输出相应log。
  • 以JSON格式打印当前余额和所有事件,通过ID检索特定事件并打印其详细信息。
  • 事件源模式是创建事件来表示对系统状态的更改,将这些事件存储在事件存储中,并重播事件以重建系统当前状态。

优点

  • 捕获、存储所有事件,以便进行审计和保证一致性
  • 可以重播所有事件以创建数据的不同视图
  • 通过存储事件来处理大量数据
  • 可以重播事件以将系统恢复到以前的状态
  • 事件日志可以作为一段时间内系统行为的文档,使其更容易维护和扩展。

缺点

  • 实现可能比较复杂,特别是在处理事件设计和数据迁移的复杂性时
  • 存储所有事件需要更多存储空间
  • 必须重放所有事件以确定当前状态,可能导致额外性能开销

适用场景

  • 记录交易和财务事项
  • 记录健康事件和医疗程序
  • 记录订单事件和付款事件

注意事项

  • 事件设计 —— 以细粒度方式捕捉系统状态的变化。事件应该是不可变的,这意味着事件被创建后不能被修改。事件的设计应该支持简单的查询和分析。
  • 存储需求 —— 所有对系统状态的更改都以事件序列的形式存储。存储空间明显大于传统数据库。
  • 数据迁移 —— 提前计划数据迁移,并考虑如何将数据从旧系统迁移到新的事件源系统。

9. 命令查询职责分离(Command Query Responsibility Segregation, CQRS)
图片来源: https://www.codeproject.com/Articles/555855/Introduction-to-CQRS
图片来源: https://www.codeproject.com/Articles/555855/Introduction-to-CQRS
  • 将读写操作分离到单独的服务或模型中
  • 命令: 改变系统状态
  • 查询: 返回数据
  • 涉及如下步骤:
    1. 用户向系统发出读取或写入数据的请求
    2. 如果请求是命令(写操作),则将该命令发送给命令服务,命令服务处理请求并更新系统状态。
    3. 命令服务更新写入模型,其中包含系统的当前状态,并创建描述更改的事件。事件被添加到事件流中,事件流是系统中发生的所有事件的日志。
    4. 命令服务将事件发布到消息代理,消息代理将事件传递给感兴趣的订阅者。
    5. 如果请求是查询(读操作),则将查询发送给查询服务,查询服务从读模型中检索数据。
    6. 查询服务从读模型中检索数据并将其返回给用户。
    7. 如果用户想执行另一个写操作,则从步骤2开始重复该过程。
    8. 如果用户想要执行读操作,则从步骤5开始重复该过程。
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional

class Command(ABC):
    pass

class CreateProductCommand(Command):
    def __init__(self, name: str, price: float):
        self.name = name
        self.price = price

class UpdateProductCommand(Command):
    def __init__(self, product_id: str, name: Optional[str] = None, price: Optional[float] = None):
        self.product_id = product_id
        self.name = name
        self.price = price

class DeleteProductCommand(Command):
    def __init__(self, product_id: str):
        self.product_id = product_id

class Query(ABC):
    pass

class GetProductQuery(Query):
    def __init__(self, product_id: str):
        self.product_id = product_id

class GetAllProductsQuery(Query):
    pass

class Product:
    def __init__(self, id: str, name: str, price: float):
        self.id = id
        self.name = name
        self.price = price

class ProductRepository:
    def __init__(self):
        self.products = []

    def create(self, name: str, price: float) -> Product:
        product = Product(str(len(self.products) + 1), name, price)
        self.products.append(product)
        return product

    def get(self, id: str) -> Optional[Product]:
        for product in self.products:
            if product.id == id:
                return product
        return None

    def get_all(self) -> List[Product]:
        return self.products

    def update(self, id: str, name: Optional[str] = None, price: Optional[float] = None) -> Optional[Product]:
        for product in self.products:
            if product.id == id:
                if name is not None:
                    product.name = name
                if price is not None:
                    product.price = price
                return product
        return None

    def delete(self, id: str) -> bool:
        for product in self.products:
            if product.id == id:
                self.products.remove(product)
                return True
        return False

class ProductCommandHandler:
    def __init__(self, repository: ProductRepository):
        self.repository = repository

    def handle(self, command: Command) -> Optional[Product]:
        if isinstance(command, CreateProductCommand):
            return self.repository.create(command.name, command.price)
        elif isinstance(command, UpdateProductCommand):
            return self.repository.update(command.product_id, command.name, command.price)
        elif isinstance(command, DeleteProductCommand):
            success = self.repository.delete(command.product_id)
            return success

class ProductQueryHandler:
    def __init__(self, repository: ProductRepository):
        self.repository = repository

    def handle(self, query: Query) -> Optional[Any]:
        if isinstance(query, GetProductQuery):
            return self.repository.get(query.product_id)
        elif isinstance(query, GetAllProductsQuery):
            return self.repository.get_all()

class ProductService:
    def __init__(self, command_handler: ProductCommandHandler, query_handler: ProductQueryHandler):
        self.command_handler = command_handler
        self.query_handler = query_handler

    def create_product(self, name: str, price: float) -> Product:
        command = CreateProductCommand(name, price)
        return self.command_handler.handle(command)

    def get_product(self, id: str) -> Optional[Product]:
        query = GetProductQuery(id)
        return self.query_handler.handle(query)

    def get_all_products(self) -> List[Product]:
        query = GetAllProductsQuery()
        return self

示例代码

  • 一个产品管理系统。
  • 抽象类 CommandQuery分别由具体类实现。
  • 3个命令类的实现: CreateProductCommandUpdateProductCommandDeleteProductCommand
  • CreateProductCommand创建产品
  • UpdateProductCommand更新产品
  • DeleteProductCommand删除产品
  • 2个查询类的实现: GetProductQueryGetAllProductQuery
  • GetProductQuery检索关于特定产品的信息
  • GetAllProductQuery检索所有产品的信息
  • Product类表示一个产品,包含 idnameprice
  • ProductRepository类处理产品数据的持久性,具有创建、检索、更新和删除产品的方法
  • ProductCommandHandler类处理命令并将 ProductRepository作为依赖项
  • ProductQueryHandler类处理查询并将 ProductRepository作为依赖项
  • 两个 handle方法负责接受命令或查询,并返回适当的响应
  • ProductService类作为客户端与产品管理系统交互的入口,将 ProductCommandHandlerProductQueryHandler作为依赖项,并公开用于创建、检索和列出产品的方法,这些方法只是对适当命令或查询的包装,并将其传递给相应的处理程序。

优点

  • 分离读写模型以分别优化两个过程,可以获得更好的性能。
  • 分离读写模型使代码更容易维护。
  • 此模式可针对特定用例进行优化。

缺点

  • 系统的设计和实现比较复杂
  • 比传统的整体架构需要更多时间和资源
  • 在发生写操作和更新读模型之间可能存在延迟,从而导致只能保证最终一致性

适用场景

  • 具有复杂域逻辑和高读写负载的应用程序
  • 具有不同用户界面的系统,例如移动和web应用程序,其中每个界面都有自己特定的读取要求
  • 电子商务系统中的产品目录管理和订单管理
  • 医疗保健系统中的患者数据检索和数据输入
  • 金融交易系统中的实时市场数据检索和订单执行

CQRS和事件驱动的结合

图片来源: https://stackoverflow.com/questions/56728979/event-sourcing-why-a-dedicated-event-store
图片来源: https://stackoverflow.com/questions/56728979/event-sourcing-why-a-dedicated-event-store

参考文献

Saga pattern

Microservices Pattern: Sagas

Saga Pattern

Saga Pattern Microservices

Saga Pattern for Microservices Distributed Transactions

Microservice Design Pattern - Saga

Event Driven Saga Pattern

How to Use Saga Pattern in Microservices

Saga Orchestration for Microservices Using the Outbox Pattern

Saga Without the Headaches

Event Sourcing

Event Sourcing - why a dedicated event store?

Beginner's Guide to Event Sourcing

Microservices Pattern: Event Sourcing

Event Sourcing pattern

Event Sourcing

Event Sourcing explained

CQRS Event Sourcing JAVA

Introduction to CQRS

CQRS Pattern

bliki: CQRS

Microservices Pattern: Command Query Responsibility Segregation (CQRS)

A Beginner's Guide to CQRS

CQRS Desgin Pattern in Microservices Architecture

The Command and Query Responsibility Segregation(CQRS)

Event Driven CQRS Pattern

CQRS Pattern

CQRS Software Architecture Pattern: The Good, the Bad, and the Ugly


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。
微信公众号:DeepNoMind

- END -

本文由 mdnice 多平台发布

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

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

相关文章

时间序列预测 | Matlab基于最小二乘支持向量机LSSVM时间序列预测,LSSVM时间序列预测

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 基于最小二乘支持向量机LSSVM多维时间序列预测LSSVM多变量时间序列预测,matlab代码 评价指标包括:MAPE、MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %----------------…

如何落地中台架构

大家好&#xff0c;我是易安&#xff01;今天我分享下如何落地中台架构。 前台和后台 讲中台之前&#xff0c;我们先来理解下前台和后台&#xff0c;这样&#xff0c;你才能更清楚中台的定位。 前台 比较好理解&#xff0c;指的是 面向C端的应用&#xff0c;比如像微信、淘宝这…

数字音频接口I2S-PDM-TDM-PCM

主要分类&#xff1a;模拟、数字&#xff08;I2S、PCM、PDM、TDM&#xff09; 模拟音频&#xff0c;就是功放输出的&#xff0c;驱动音箱和喇叭的音频。模拟麦克风采样回来的数据也是模拟音频。通常会有单端或差分两种信号。 数字音频&#xff0c;不能直接驱动喇叭&#xff0…

混剪功能开发——抖音账号矩阵系统源码解析

抖音是目前国内非常流行的短视频平台之一&#xff0c;用户数量庞大&#xff0c;更是吸引了许多企业和个人在上面开设账号&#xff0c;通过发布内容来进行流量变现。但是&#xff0c;在一个账号发布内容的同时&#xff0c;管理员又需要同时关注多个账号&#xff0c;对账号的管理…

vs code 配置net 开发环境.并搭配vs相似的解决方案面板

由于在本人在Linux22.04下安装Rider 一直处于卡死系统状态.不得不使用该方式 以下为安装步骤 安装 VS code https://code.visualstudio.com/Download 安装 mono https://www.mono-project.com/download/stable/#download-lin 安装 NET SDK https://learn.microsoft.com/zh…

目录层次结构中区分不同功能的RPM包,同时只有一份共享的repodata

使用本地的yum源有几个潜在的好处&#xff1a; 更快的下载速度&#xff1a; 本地yum源通常位于本地网络上&#xff0c;因此可以通过局域网快速获取软件包&#xff0c;而不需要依赖互联网连接。这样可以提供更快的下载速度&#xff0c;节省时间和带宽消耗。 离线访问&#xff1…

实验12 卷积神经网络

1. 实验目的 ①掌握深度学习的基本原理&#xff1b; ②能够使用TensorFlow实现卷积神经网络&#xff0c;完成图像识别任务。 2. 实验内容 ①设计卷积神经网络模型&#xff0c;实现对Mnist手写数字数据集的识别&#xff0c;并以可视化的形式输出模型训练的过程和结果&#xf…

Qt--事件过滤器

写在前面 Qt中的事件过滤器(Event Filter)是一种机制&#xff0c;用于拦截并处理特定类型的事件。但和Qt–事件分发器一文中提到的事件分发器有些区别。 事件过滤器的工作原理 这里同样使用一个简单的示例图帮助理解&#xff1a; 这里假设有一个Widget父窗口&#xff0c;该…

服务间的通信(RestTemplate +Ribbon+Feign):

服务之间的依赖&#xff1a; 其实根据上图我们发现会员管理服务其实是依赖于我们图书的这个服务的&#xff0c;那么为什么要依赖于图书这个服务呢&#xff0c;因为会员服务想要进行借阅图书的时候&#xff0c;必须要对图书模块的图书的库存等做校验才可以&#xff0c;所以membe…

在 Kubernetes 上实现高速应用交付

原文作者&#xff1a;NGINX 原文链接&#xff1a;在 Kubernetes 上实现高速应用交付 转载来源&#xff1a;NGINX 官方网站 NGINX 唯一中文官方社区 &#xff0c;尽在 nginx.org.cn 运行于 Kubernetes 之上的应用需要一个经过验证的生产级应用交付解决方案。NGINX Ingress Cont…

边缘计算AI硬件智能分析网关V1版的接入流程与使用步骤

我们的AI边缘计算网关硬件——智能分析网关目前有两个版本&#xff1a;V1版与V2版&#xff0c;两个版本都能实现对监控视频的智能识别和分析&#xff0c;支持抓拍、记录、告警等&#xff0c;在AI算法的种类上和视频接入上&#xff0c;两个版本存在些许的区别。V1的基础算法有人…

【ChatGPT】《吴恩达 x OpenAI Prompt Engineering教程中文笔记》- 知识点目录

《吴恩达 x OpenAI Prompt Engineering教程中文笔记》 &#x1f433; 在开始编写提示词之前的一些设置 不同的temperature会影响模型的理性和想象力&#xff0c;这里告诉我们&#xff1a; Low&#xff1a;例如GPT4&#xff0c;更加适合确定性的问答任务Hight&#xff1a;例如…

non-protected broadcast场景分析及解决

non-protected broadcast场景分析及解决 在两个app之间互相送消息使用BroadcastReceiver&#xff0c;有时在运行过程中在logcat工具中会发现大片的飘红消息。 要消除这些错误信息&#xff0c;需要在广播的 Sender 和 Receiver 做部分的修改。 错误信息分析 由于 发送端 的 M…

忆享聚焦|ChatGPT、AI、网络数字、游戏……近期热点资讯一览

“忆享聚焦”栏目第十四期来啦&#xff01;本栏目汇集近期互联网最新资讯&#xff0c;聚焦前沿科技&#xff0c;关注行业发展动态&#xff0c;筛选高质量讯息&#xff0c;拓宽用户视野&#xff0c;让您以最低的时间成本获取最有价值的行业资讯。 目录 行业资讯 1.科技部部长王志…

上海亚商投顾:沪指跌1.28%失守年线 大金融板块集体走弱

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 三大指数今日继续调整&#xff0c;沪指超1%逼近3200点&#xff0c;尾盘失守年线&#xff0c;创业板指较为抗跌。大…

你收藏了那些实用工具类网站?

今天来给大家分享几个众多网友们推荐的&#xff0c;宝藏工具类网站 uIGradients https://uigradients.com/#Flickr 专业的渐变色配色工具网站&#xff0c;配色什么的非常全&#xff0c;也可以按照自己的想法来选择搭配&#xff0c;还能直接获得对应渐变配色的CSS代码&#xff…

水表自动抄表系统有什么功能

水表自动抄表系统是一种新型的智能化管理系统&#xff0c;它可以自动采集水表的数据&#xff0c;并且实时上传到管理平台&#xff0c;实现了水表的实时监测和管理。该系统具有以下几个主要功能&#xff1a; 1.自动抄表功能 水表自动抄表系统可以实现自动采集水表的数据&#x…

【学习笔记】Windows 下线程同步之互斥锁

目录 前言环境简介相关函数CreateMutex Wait 函数ReleaseMutexCloseHandle 其他互斥锁的名字未命名互斥锁的同步互斥锁的意外终止临界区对象 参考 前言 本文所涉及的同步主要描述在 Windows 环境下的机制&#xff0c;和 Linux 中的同步机制有一定的联系&#xff0c;但注意并不…

小猫踩球-第14届蓝桥杯省赛Scratch中级组真题第2题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第137讲。 小猫踩球&#xff0c;本题是2023年5月7日举行的第14届蓝桥杯省赛Scratch图形化编程中级组真题第2题&#xf…

智慧档案馆一体化监控系统设计所需要的10条依据

1.科学性 本项目目标定位为&#xff1a;以科学技术为基础&#xff0c;依靠先进的设备和优越的设计理念、科学客观的管理&#xff0c;利用信息化管理及相关最新技术&#xff0c;将库房实际环境与存储技术、计算机技术、无线自动控制技术、通讯与信息处理技术等先进技术相结合&a…