自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

穩(wěn)??!基于 Spring Boot 的事務(wù)外包模式構(gòu)建可靠微服務(wù)

開(kāi)發(fā) 前端
事務(wù)外包模式 提供了一種簡(jiǎn)潔高效的解決方案,確保在微服務(wù)架構(gòu)下的消息傳遞和數(shù)據(jù)一致性問(wèn)題。通過(guò)將業(yè)務(wù)數(shù)據(jù)和事件存儲(chǔ)在同一個(gè)數(shù)據(jù)庫(kù)事務(wù)中,并結(jié)合定時(shí)輪詢(xún)機(jī)制將事件發(fā)送至消息隊(duì)列,開(kāi)發(fā)者能夠輕松處理分布式環(huán)境中的一致性挑戰(zhàn)。

隨著軟件架構(gòu)的不斷演變,微服務(wù)架構(gòu) 成為解決系統(tǒng)復(fù)雜性和增強(qiáng)可擴(kuò)展性的主要方式。然而,微服務(wù)架構(gòu)也帶來(lái)了新的挑戰(zhàn),尤其是在分布式環(huán)境下保證數(shù)據(jù)一致性和可靠性。隨著業(yè)務(wù)流程的復(fù)雜化,服務(wù)之間需要頻繁地交互、共享數(shù)據(jù)以及發(fā)送消息,這就帶來(lái)了“分布式事務(wù)”問(wèn)題。如果某個(gè)服務(wù)在更新數(shù)據(jù)庫(kù)后需要立即通知其他服務(wù),而在通知過(guò)程中出現(xiàn)問(wèn)題,例如消息發(fā)送失敗或網(wǎng)絡(luò)故障,那么系統(tǒng)可能會(huì)陷入不一致?tīng)顟B(tài)。

在這種情況下,簡(jiǎn)單的事務(wù)控制(如本地事務(wù))無(wú)法有效地解決跨服務(wù)的數(shù)據(jù)一致性問(wèn)題。為了解決這個(gè)挑戰(zhàn),事務(wù)外包(Transactional Outbox)模式 被提出,以確保服務(wù)在處理數(shù)據(jù)庫(kù)操作時(shí),同時(shí)能夠可靠地發(fā)送消息,從而解決了數(shù)據(jù)庫(kù)與消息隊(duì)列之間的不一致問(wèn)題。

什么是事務(wù)外包模式?

事務(wù)外包模式 是一種保證數(shù)據(jù)庫(kù)操作與消息傳遞之間一致性的設(shè)計(jì)模式。它的核心思想是將所有需要發(fā)送的消息存儲(chǔ)在數(shù)據(jù)庫(kù)中,將其與數(shù)據(jù)庫(kù)操作綁定在同一事務(wù)內(nèi)。這樣,當(dāng)數(shù)據(jù)庫(kù)操作成功提交時(shí),消息也會(huì)被持久化到數(shù)據(jù)庫(kù),后續(xù)通過(guò)定時(shí)任務(wù)或事件輪詢(xún)機(jī)制將這些消息發(fā)送到消息系統(tǒng),如 Kafka、RabbitMQ 或其他外部系統(tǒng)。

傳統(tǒng)的分布式事務(wù)通過(guò)兩階段提交(2PC)來(lái)保證一致性,但兩階段提交會(huì)帶來(lái)較大的性能開(kāi)銷(xiāo),且難以處理網(wǎng)絡(luò)或系統(tǒng)故障。相比之下,事務(wù)外包模式提供了一種高效、靈活的替代方案:

  1. 事務(wù)一致性:通過(guò)將消息和數(shù)據(jù)庫(kù)操作放在同一事務(wù)內(nèi),保證它們要么同時(shí)成功,要么同時(shí)失敗。
  2. 異步處理:消息可以通過(guò)異步方式發(fā)送到消息隊(duì)列,避免對(duì)數(shù)據(jù)庫(kù)操作產(chǎn)生延遲。
  3. 高可用性和容錯(cuò)性:即使在消息系統(tǒng)不可用的情況下,消息依然能夠可靠地保存在數(shù)據(jù)庫(kù)中,等待消息系統(tǒng)恢復(fù)后發(fā)送。

通過(guò)這種方式,我們可以在保持服務(wù)間松耦合的同時(shí),確保分布式系統(tǒng)的數(shù)據(jù)一致性和高可用性。

事務(wù)外包模式的工作原理

  1. 業(yè)務(wù)數(shù)據(jù)與消息一起持久化:當(dāng)一個(gè)服務(wù)執(zhí)行數(shù)據(jù)庫(kù)操作時(shí),消息并不會(huì)立即發(fā)送,而是與業(yè)務(wù)數(shù)據(jù)一起存儲(chǔ)在數(shù)據(jù)庫(kù)的 Outbox 表中。這樣,業(yè)務(wù)數(shù)據(jù)和消息的持久化在同一個(gè)事務(wù)中被處理,確保兩者的一致性。
  2. 定時(shí)輪詢(xún)消息表:系統(tǒng)會(huì)通過(guò)定時(shí)任務(wù)輪詢(xún) Outbox 表,查找未發(fā)送的消息,并將其發(fā)送到目標(biāo)消息系統(tǒng)(如 Kafka 或 RabbitMQ)。
  3. 消息傳遞確認(rèn):當(dāng)消息成功發(fā)送后,Outbox 表中的相應(yīng)記錄會(huì)被刪除或標(biāo)記為已處理。

這種模式的核心思想是將消息的可靠傳遞變成一個(gè)可控的、異步的過(guò)程,并通過(guò)持久化機(jī)制保證即使消息系統(tǒng)暫時(shí)不可用,也不會(huì)丟失消息。

運(yùn)行效果:

圖片圖片

若想獲取項(xiàng)目完整代碼以及其他文章的項(xiàng)目源碼,且在代碼編寫(xiě)時(shí)遇到問(wèn)題需要咨詢(xún)交流,歡迎加入下方的知識(shí)星球。

Spring Boot 實(shí)現(xiàn)事務(wù)外包模式

項(xiàng)目基礎(chǔ)配置

為了實(shí)現(xiàn)事務(wù)外包模式,我們將使用 Spring Boot、JPA、Lombok 和 Thymeleaf,并通過(guò)定時(shí)任務(wù)來(lái)輪詢(xún)數(shù)據(jù)庫(kù)中的 Outbox 表。下面的 pom.xml 配置了項(xiàng)目所需的依賴(lài):

<?xml versinotallow="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.4</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.icoderoad</groupId>
	<artifactId>outbox</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>outbox</name>
	<description>Demo project for Spring Boot</description>
	
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<!-- Spring Boot Starter Dependencies -->
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-data-jpa</artifactId>
	    </dependency>
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
	
	    <!-- Thymeleaf -->
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-thymeleaf</artifactId>
	    </dependency>
	
	    <!-- Lombok -->
	    <dependency>
	        <groupId>org.projectlombok</groupId>
	        <artifactId>lombok</artifactId>
	        <scope>provided</scope>
	    </dependency>
	
	    <!-- 數(shù)據(jù)庫(kù)驅(qū)動(dòng)依賴(lài) -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

application.yaml 配置

我們使用 Mysql 數(shù)據(jù)庫(kù)進(jìn)行持久化,yaml 文件配置了數(shù)據(jù)庫(kù)連接和 Outbox 的輪詢(xún)間隔。

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&allowPublicKeyRetrieval=true&serverTimeznotallow=UTC
    username: root
    password: root

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
  outbox:
    polling-interval: 1000  # 設(shè)置輪詢(xún)間隔為 1 秒

使用 @ConfigurationProperties 讀取配置

為了方便管理和修改輪詢(xún)間隔等配置項(xiàng),我們使用 @ConfigurationProperties 注解將配置文件中的屬性注入到 Java 類(lèi)中。

package com.icoderoad.outbox.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import lombok.Data;

@Data
@Component
@ConfigurationProperties(prefix = "outbox")
public class OutboxProperties {
    private long pollingInterval;
}

實(shí)現(xiàn)事務(wù)外包模式

在 Spring Boot 中,事務(wù)外包模式可以通過(guò)一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)表(如 OutboxEvent)來(lái)持久化所有未處理的消息。每次有業(yè)務(wù)操作時(shí),生成相應(yīng)的事件并持久化到數(shù)據(jù)庫(kù)表中,然后通過(guò)定時(shí)任務(wù)處理這些事件。

數(shù)據(jù)庫(kù)實(shí)體類(lèi)

package com.icoderoad.outbox.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;

@Data
@Entity
public class OutboxEvent {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String aggregateType;
    private String aggregateId;
    private String eventType;
    private String payload;  // 存儲(chǔ)事件內(nèi)容
}

Order 類(lèi)實(shí)現(xiàn)

package com.icoderoad.outbox.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;

@Data
@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String orderName;  // 訂單名稱(chēng)
}

OrderRepository 類(lèi)實(shí)現(xiàn)

package com.icoderoad.outbox.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.icoderoad.outbox.entity.Order;

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {

}

OutboxEventRepository 類(lèi)實(shí)現(xiàn)

package com.icoderoad.outbox.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.icoderoad.outbox.entity.OutboxEvent;

@Repository
public interface OutboxEventRepository extends JpaRepository<OutboxEvent, Long> {

    // 這里可以定義自定義查詢(xún)方法,例如查詢(xún)未處理的事件等
    // List<OutboxEvent> findByProcessedFalse();
}

業(yè)務(wù)服務(wù)類(lèi)

業(yè)務(wù)邏輯中,當(dāng)執(zhí)行訂單操作時(shí),事件不會(huì)直接發(fā)送,而是先持久化到 Outbox 表中。

package com.icoderoad.outbox.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.icoderoad.outbox.entity.Order;
import com.icoderoad.outbox.entity.OutboxEvent;
import com.icoderoad.outbox.repository.OrderRepository;
import com.icoderoad.outbox.repository.OutboxEventRepository;

@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final OutboxEventRepository outboxEventRepository;

    public OrderService(OrderRepository orderRepository, OutboxEventRepository outboxEventRepository) {
        this.orderRepository = orderRepository;
        this.outboxEventRepository = outboxEventRepository;
    }

    @Transactional
    public void placeOrder(Order order) {
        // 先保存訂單信息,確保生成 ID
        Order savedOrder = orderRepository.save(order);
        
        // 保存訂單之后,才能獲取訂單的 ID
        OutboxEvent event = new OutboxEvent();
        event.setAggregateType("Order");
        event.setAggregateId(savedOrder.getId().toString());  // 使用保存后的訂單 ID
        event.setEventType("OrderCreated");
        event.setPayload(savedOrder.toString());  // 可以根據(jù)需要將訂單信息序列化成 JSON

        // 保存事件信息
        outboxEventRepository.save(event);
    }
}

定時(shí)輪詢(xún)?nèi)蝿?wù)

定時(shí)任務(wù)用于從 Outbox 表中讀取未處理的事件并將其發(fā)送至消息隊(duì)列。

package com.icoderoad.outbox.poller;

import java.util.List;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.icoderoad.outbox.entity.OutboxEvent;
import com.icoderoad.outbox.repository.OutboxEventRepository;

@Component
public class OutboxPoller {

    private final OutboxEventRepository outboxEventRepository;

    public OutboxPoller(OutboxEventRepository outboxEventRepository) {
        this.outboxEventRepository = outboxEventRepository;
    }

    @Scheduled(fixedDelayString = "${outbox.polling-interval}")
    public void pollOutbox() {
        List<OutboxEvent> events = outboxEventRepository.findAll();
        for (OutboxEvent event : events) {
            // 發(fā)送消息至消息隊(duì)列
            // messageQueue.send(event);
            
            // 刪除或標(biāo)記為已處理
            outboxEventRepository.delete(event);
        }
    }
}

后端控制器

package com.icoderoad.outbox.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.icoderoad.outbox.entity.Order;
import com.icoderoad.outbox.service.OrderService;

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping
    public Map<String, String> placeOrder(@RequestBody Order order) {
        orderService.placeOrder(order);
        Map<String, String> response = new HashMap<>();
        response.put("status", "success");
        response.put("message", "訂單提交成功!");
        return response;
    }
}

前端實(shí)現(xiàn)

使用 Thymeleaf 渲染頁(yè)面,并使用 JQuery 通過(guò) AJAX 請(qǐng)求后端 API,將結(jié)果以 Bootstrap 風(fēng)格的提示框顯示。

在 src/main/resources/templates 目錄下創(chuàng)建 index.html 文件:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>訂單頁(yè)面</title>
    <link rel="stylesheet" >
    <script src="http://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container">
    <h2>訂單表單</h2>
    <div class="alert alert-success" id="success-alert" style="display: none;"></div>
    <div class="alert alert-danger" id="error-alert" style="display: none;"></div>
    
    <form id="orderForm">
        <div class="mb-3">
            <label for="orderName" class="form-label">訂單名稱(chēng)</label>
            <input type="text" class="form-control" id="orderName" name="orderName">
        </div>
        <button type="submit" class="btn btn-primary">提交訂單</button>
    </form>
</div>

<script>
    $(document).ready(function() {
        $('#orderForm').on('submit', function(event) {
            event.preventDefault();
            
            var orderData = {
                orderName: $('#orderName').val()
            };
            
            $.ajax({
                url: '/api/orders',
                type: 'POST',
                contentType: 'application/json',
                data: JSON.stringify(orderData),
                success: function(response) {
                    $('#success-alert').text(response.message).show();
                    $('#error-alert').hide();
                },
                error: function() {
                    $('#error-alert').text('訂單提交失??!').show();
                    $('#success-alert').hide();
                }
            });
        });
    });
</script>
</body>
</html>

總結(jié)

事務(wù)外包模式 提供了一種簡(jiǎn)潔高效的解決方案,確保在微服務(wù)架構(gòu)下的消息傳遞和數(shù)據(jù)一致性問(wèn)題。通過(guò)將業(yè)務(wù)數(shù)據(jù)和事件存儲(chǔ)在同一個(gè)數(shù)據(jù)庫(kù)事務(wù)中,并結(jié)合定時(shí)輪詢(xún)機(jī)制將事件發(fā)送至消息隊(duì)列,開(kāi)發(fā)者能夠輕松處理分布式環(huán)境中的一致性挑戰(zhàn)。與傳統(tǒng)的兩階段提交相比,事務(wù)外包模式提供了更好的可擴(kuò)展性、性能和可靠性。

同時(shí),本文通過(guò)前后端結(jié)合的方式展示了如何使用 Thymeleaf、JQuery 和 Bootstrap 實(shí)現(xiàn)一個(gè)訂單系統(tǒng)。這種架構(gòu)可以進(jìn)一步擴(kuò)展,如支持更復(fù)雜的消息系統(tǒng)或集成更多服務(wù),以滿(mǎn)足不斷增長(zhǎng)的業(yè)務(wù)需求。

責(zé)任編輯:武曉燕 來(lái)源: 路條編程
相關(guān)推薦

2022-10-10 08:00:00

微服務(wù)Spring Boo容器

2017-11-22 13:01:03

Go技術(shù)棧構(gòu)建

2018-06-01 23:08:01

Spring Clou微服務(wù)服務(wù)器

2023-08-16 14:39:20

微服務(wù)Java

2017-06-26 09:06:10

Spring Clou微服務(wù)架構(gòu)

2017-12-20 15:37:39

Spring Clou微服務(wù)架構(gòu)

2024-09-30 14:38:47

2017-08-07 08:41:13

Java微服務(wù)構(gòu)建

2017-09-04 16:15:44

服務(wù)網(wǎng)關(guān)架構(gòu)

2023-12-29 18:53:58

微服務(wù)Saga模式

2020-06-30 07:58:39

微服務(wù)Spring BootCloud

2017-07-03 09:50:07

Spring Clou微服務(wù)架構(gòu)

2017-08-10 11:15:05

Spring Clou微服務(wù)架構(gòu)

2017-08-09 15:50:47

Spring Clou微服務(wù)架構(gòu)

2023-09-02 20:51:09

微服務(wù)業(yè)務(wù)服務(wù)

2023-09-07 23:25:34

微服務(wù)服務(wù)發(fā)現(xiàn)

2022-02-11 23:24:47

QuarkusSpringJava

2021-12-29 08:30:48

微服務(wù)架構(gòu)開(kāi)發(fā)

2024-02-22 18:12:18

微服務(wù)架構(gòu)設(shè)計(jì)模式

2025-02-18 07:00:00

SpringBoot開(kāi)發(fā)Java
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)