Saga體系結構模式:微服務架構下跨服務事務的實現

在服務端應用程序中,我們往往會通過事務處理來保證數據一致性(Data Consistency),例如:當用戶從庫存中取走了一定數量的物品,這些物品會體現在用戶的提貨單上,與此同時,庫存中物品的數量也應該減少。如果在這個過程中無法保證數據的一致性,那么就有可能出現用戶沒有成功取走物品,而庫存中的物品數量卻減少了;或者用戶成功取走了物品,而庫存中的物品數量卻沒有變化。前者導致物品總量比實際少了一些,而后者又導致物品總量比實際多了,這樣的問題就是數據一致性問題。為了保證應用程序不會出現這類問題,我們通常會使用數據庫事務。單說數據庫事務就有很多相關的知識,但這都不是這里打算深入討論的內容。我們會著重介紹一下微服務架構下跨服務事務的實現以及與之相關的Saga體系結構模式,不過在此之前,還是有必要回顧一下事務處理的一些解決方案。

本地事務

本地事務通常會在同一個資源管理器(Resource Manager)上完成,最為常見的例子就是在同一個數據庫上操作多張數據表,在這些操作完成之后,數據表的變更同時成功或者同時失敗。例如,下面的C#代碼會在一個本地事務中同時更新兩張數據表:


using var connection = new SqlConnection("Server=localhost;Database=sql_sample;uid=sa;password=Pass@w0rd");

using var transaction = connection.BeginTransaction();

try

{

????var updateUserCommand = new SqlCommand(

????????"UPDATE [dbo].[Users] SET [Credit]=10 WHERE [UserID]=\"abc\"",

????????connection,

????????transaction);

????updateUserCommand.ExecuteNonQuery();

????var updateInventoryCommand = new SqlCommand(

????????"UPDATE [dbo].[Inventory] SET [AMOUNT]=10 WHERE [ID]=\"def\"",

????????connection,

????????transaction);

????updateInventoryCommand.ExecuteNonQuery();

????transaction.Commit();

}

catch (Exception ex)

{

????Console.WriteLine(ex);

????transaction.Rollback();

}

代碼中SqlTransaction能夠保證,對于Users表和Inventory表的更新要么同時成功,要么同時失敗。這個本地事務是在SQL Server的資源管理器上執行,因此,本地事務的效率是比較高的。

在微服務架構中,我們往往會選擇Database-per-Service的設計,這樣做的好處是能夠獲得比較好的數據隔離性,而且不同的服務可以根據本身的特點選擇不同的數據存儲方案。因此,就單個服務而言,實現本地事務是比較容易的事情,它能夠很好地滿足服務本身的業務需求,也能夠很好地保證數據的一致性。然而很明顯,本地事務無法保證跨服務的數據一致性。

分布式事務

分布式事務往往會橫跨多個資源管理器(Resource Manager,RM),并由分布式事務協調器(Distributed Transaction Coordinator,DTC)負責事務協調。分布式事務通常基于兩段提交協議(Two-phase Commit, 2PC)實現:事務提交分兩個階段進行,在第一階段(準備階段)中,2PC協議需要確保DTC已經獲得了所有來自RM的提交反饋信息,對于每個RM,DTC都需要明確知道它是否可以成功完成其本地事務,或者無法完成。就RM而言,在這一階段會嘗試提交其本地事務,如果能夠成功提交,則向DTC報告“可以提交”的狀態,否則報告“無法提交”的狀態。DTC在收集了所有參與者RM的狀態后,如果全部為“可以提交”,則啟動第二階段(提交階段),通知所有RM完成正式提交;但只要有一個RM報告“無法提交”,則DTC會通知其它的RM取消提交操作。

一個成功的2PC提交的過程大致可以用下面的順序圖來表示:

797c77c6067886227932b0bbbfade88a.png

此外,三段提交協議(Three-phase commit, 3PC)也是實現分布式事務的一種模式,與2PC相比,3PC主要是為了解決DTC或者RM出現故障的情形,它將2PC中的第一階段(準備階段)進行了細分,將RM分為了Awaiting和pre-commit兩種狀態。總的來說,3PC和2PC過程大致相同,可以參考這篇文章進一步了解,在此就不多說了。

從 2.0版本開始,.NET Framework引入了一個非常方便的類:TransactionScope,這個類能夠輔助完成分布式事務處理,比如,下面的偽代碼能夠實現跨SQL Server服務器的事務處理:


using (TransactionScope scope = new TransactionScope())

{

????using (con = new SqlConnection(conString1))

????{

????????con.Open();

????????// Do Operation 1

????????// Do Operation 2

????????//...

????}

????using (con = new SqlConnection(conString2))

????{

????????con.Open();

????????// Do Operation 1

????????// Do Operation 2

????????//...

????}

????scope.Complete();

}

然而,獲得如此的便捷需要付出一定的成本:首先,跨數據庫的事務處理效率是非常低的;其次,不是所有的數據庫驅動都能夠支持TransactionScope,并且,上面的代碼需要執行成功,就需要確保Windows操作系統中的Distributed Transaction Coordinator服務是處于啟動狀態,而且應用程序與該服務間的通訊不能中斷。

bbe9bf42d452567d0e981150b93716c2.png

圖:Microsoft Distributed Transaction Coordinator服務

然而在微服務架構中,無法通過分布式事務來保證數據的一致性,原因大致有如下幾個方面:

  • 2PC/3PC協議是阻塞式協議,其本身的特點使得分布式事務協調器成為了整個體系中的單點,一旦DTC發生錯誤,容易導致RM長期處于等待狀態,資源得不到釋放,從而造成服務不可用。或者更進一步,如果在提交階段,其中某個服務真的提交失敗了,那又如何維持各服務間狀態的一致性?解決這樣的問題并不是不可能,但是成本會比較大,而且,這還不是微服務架構下實現2PC/3PC協議的唯一弊端

  • 基于2PC/3PC協議的分布式事務處理比較低效,由于它是阻塞式的,所以服務本身需要完成提交或者回滾之后才能繼續處理其它的事務,在微服務環境中,一次這樣的阻塞可能會影響到很多的服務實例

  • 不是所有的數據庫或者基礎結構設施都能夠支持2PC/3PC協議,應該說絕大部分不支持。我所用過的可以支持分布式事務的基礎結構設施包括Microsoft SQL Server數據庫、Oracle數據庫以及微軟的MSMQ,因此,在技術選型上是有一定限制的:如果你的微服務所采用的數據存儲/數據傳輸技術不是這些,那么就很難實現分布式事務。此外,分布式事務協調器本身也有很多限制,比如上面提到的MSDTC就只能在Windows Server上運行

  • 這種分布式事務處理方式打破了微服務架構的設計原則:Database-per-service的設計要求微服務之間不能互相訪問對方的數據庫,而DTC的存在,使得數據庫實現細節不得不被暴露出來,否則DTC無法完成跨數據庫的事務協調

Saga模式:跨服務的事務處理

在微服務架構中,事務處理往往會橫跨多個服務,這就難免會需要依賴于服務間的通信機制。微服務間的通信分同步和異步,而異步通信才是更為推薦的方案:在設計微服務架構時,應該盡可能地選擇異步方式來實現服務間通信,這樣才能更好地實現服務間解耦。就事務處理而言,由于它并不是一個瞬時操作,而是一個長時運行的任務(long-running process),因此更適合采用異步方式來完成。Saga模式所解決的問題就是這種基于異步消息機制的跨服務事務處理,它的基本過程是,每個微服務自己處理本地事務,然后根據處理結果向消息隊列派發消息以便整個事務能夠進行下一步的處理,與此同時,微服務還會偵聽來自于其它微服務的消息,來決定自己是否需要進行事務補償(Compensation)。當一個Saga事務被啟動后,它會一步一步地執行其中的每一個步驟(Saga Step)。在每一個步驟中,有且僅有一個微服務實例的參與者負責其本地事務的執行,當所有的步驟全部成功完成后,Saga事務也就成功提交了。當然,如果其中有某個步驟執行失敗,那么之前成功的本地事務就應該回滾,否則無法保證數據一致性。由于此刻已成功的本地事務已經無法回滾,所以,在Saga模式中,一般都會通過補償操作來實現本地事務回滾的效果。整個流程大致可以使用下面的流程圖表示:

7100e8220128a4263ed6b16db06ce031.png

一般會有兩種方式來實現Saga模式:編排式協調式

編排式(Choreography)

編排式Saga實現中,會將每個步驟分散到各個微服務中,通過事件消息的相關性將這些Saga步驟串聯起來,“編排”出一個Saga事務處理流程。例如:購物車微服務發出一個“創建銷售訂單”的消息,訂單微服務偵聽到這個消息并創建銷售訂單,庫存微服務則在偵聽到這個消息后,檢查庫存狀態,如果庫存不足,則發出一個“庫存狀態檢查失敗”的消息,然后訂單微服務獲得這個消息后,執行一個補償事務,將訂單狀態標記為“失效”,購物車微服務則執行它的補償事務,將當前購物車中的信息恢復到創建訂單之前的樣子。整個過程中,微服務之間互通信息,沒有第三方的組件參與協調它們之間的協作關系,因此,這種實現方式被稱作“編排式”。下面的示意圖表示了這種編排式Saga的實現:

69644014fd4b4a0f20662c6d6b4c1e26.png

編排式實現有如下優點:

  • 實現相對比較簡單,尤其是當涉及的微服務個數并不多的時候,編排式實現比較簡單明了

  • 由于不需要依賴于第三方組件進行協調,所以不存在額外的部署和維護成本

但也有一些缺點:

  • 當事務過程比較復雜時,往往一個Saga事務會包括多個步驟,編排式實現會使得整個事務處理過程變得錯綜復雜難于理解和維護

  • 由于沒有協調組件,消息來來回回容易造成混亂,甚至出現消息間互相影響循環處理的情況(比如A發消息給B,B處理完消息后又發消息給A,如此反復不斷)

  • 測試和排錯變得復雜:你需要啟動所有的微服務才能夠調試或者測試某個Saga步驟

協調式(Orchestration)

協調式Saga實現中,會有一個協調器的角色來負責協調Saga的每一個步驟,協調器與各微服務之間也是通過消息隊列進行通信,因此,它也是基于異步消息機制的。下面的示意圖表示了協調式Saga的實現:

3502210c8969c0caff634a471b84fba3.png

協調式Saga實現有如下這些優點:

  • 對于過程比較復雜的Saga事務,協調式比編排式的實現更加清晰,不會出現消息混亂的情況

  • 從單個微服務的角度,它無需關心在自己參與了Saga事務之后,應該如何協作以便Saga事務能夠繼續往下走,它只需要對自己所處理的Saga事件(Saga Events)完成應答即可,因此,協調式Saga能夠更好地實現微服務的關注點分離(Separation of Concerns)

  • 對于Saga事務流的控制更加簡單,對于消息的收發和處理的調試也相對比較容易

當然,也有缺點:

  • 由于需要額外引入一個協調器,所以結構上要比編排式更為復雜

對于編排式與協調式的優缺點,也有一些觀點認為,協調式中的協調器部分會有單點失敗的可能性,其實在微服務的體系中,如果設計上在這部分多加考慮,是可以避免這樣的問題的,例如,可以利用消息隊列的機制,保證Saga應答事件被、且僅被處理一次,那么,即使有多個協調器實例在運行,也能夠保證Saga能夠正確執行,在這種情況下,單個協調器發生故障無法正常工作也不會影響整個Saga事務的處理。

相比之下,我更傾向采用協調式的實現,一方面它能夠分離關注點,使得Saga模式的實現變得更為優雅;另一方面,比較容易從實現中抽取出一套特定的框架,進而重用于不同的項目中。

接下來,我們通過一個簡單的案例,來了解一下整個Saga體系的設計和實現。

案例:訂單業務下Saga的簡單實現

我們選擇購物網站的下訂單的流程來介紹Saga的實現。為了簡化問題,我們將下訂單的流程進行簡化,并且省去了很多額外的業務處理部分(比如客戶賬戶可用額度應該屬于客戶會員管理微服務,并且額度的增加和扣除都有一定的業務邏輯,這里我們就簡單地將它歸為客戶信息微服務了),因此,不要太過糾結這樣的業務流程是否合理。現假設有這樣的業務場景:

  • 客戶通過網站的購物車系統下訂單(Sales Order)

  • 購物車微服務啟動下訂單的流程,在這個過程中:

    • 首先,會通知訂單微服務,需要創建一個訂單,此時訂單狀態為Created

    • 然后,客戶信息微服務校驗當前客戶賬戶是否合法

    • 接下來,客戶信息微服務預留(扣除)客戶賬戶的額度

    • 最后,庫存微服務預留(扣除)訂單中商品的數量

  • 這個過程中任何一步發生錯誤,都需要通知已執行的步驟,以便回滾已經更改的數據,保證數據一致性。比如,在預留客戶賬戶額度的時候如果失敗,則需要將訂單狀態置為Aborted,表示該訂單因某些原因不得不取消

下面,我們基于這樣的業務場景,簡單地做些設計與實現。

總體設計

通過簡單的分析,可以得知:

  1. 整個事務的完成需要涉及4個不同的微服務:購物車微服務訂單微服務客戶信息微服務庫存微服務

  2. 在不同的Saga事務步驟中,有些微服務的操作是有對應的補償操作的,目的是為了在Saga事務執行失敗時,能夠將其本地數據變更回滾到變更之前的狀態;而有些微服務的操作是無需補償操作的,比如校驗客戶賬戶是否合法

  3. 每個Saga事務步驟都會有這幾個狀態:等待執行(Awaiting)、正在執行(Started)、成功執行(Succeeded)、執行失敗(Failed)、正在補償(Compensating)、補償完成(Compensated)以及取消(Cancelled)

  4. 依據每個Saga事務步驟的不同狀態,Saga本身也是有狀態的:已創建(Created)、正在執行(Started)、正在撤銷(Aborting)、已經撤銷(Aborted)和成功完成(Completed)

基于這樣的分析,可以得到下面的設計指導:

  1. 一個Saga事務(下面簡稱Saga)由若干個Saga事務步驟組成

  2. Saga是有狀態的,它的各個步驟也是,因此,Saga是需要被持久化的

  3. Saga的管理以及Saga事件的處理都需要有一個管理者負責協調,稱之為Saga Manager

  4. Saga與各個微服務之間采用異步消息進行通信

于是,相關的UML類圖大致如下:

4bab00156faf712f607b45e439a85862.png

有幾點大致說明一下:

  1. IDataAccessObject是一個數據訪問的接口,它提供了對某種數據庫中的數據進行增刪改查的功能。在我們的案例中,采用MongoDB的實現

  2. IEventPublisher是一個事件派發接口,它可以向消息隊列派發消息。在我們的案例中,采用RabbitMQ的實現

  3. Saga Manager會使用IDataAccessObject實例來管理Saga的生命周期,也會使用IEventPublisher實例來派發Saga消息

  4. 當SagaEventHandler接收到來自各個微服務的響應事件時,它會通過Saga Manager讀取對應的Saga,然后在Saga上進行狀態轉換,從而觸發下一個Saga步驟(或者上一個Saga步驟的補償事務)的執行

  5. Saga維護本身及其各個步驟的狀態,每個Saga步驟會有兩個待實現的抽象方法,用來返回事務消息的類型,以及補償事務消息的類型

執行過程可以用下面的UML順序圖來表示:

de1c510523c485990efb95812ca19705.png

上面的順序圖僅展示了一次由Saga事件觸發的狀態轉換過程,在這個過程中,難點就是Saga對象在得到當前Saga事件時,是如何完成狀態轉換,并產生下一步驟所對應的Saga事件的。下面就進行一些簡單的介紹。

詳細設計:Saga事件處理與狀態轉換

在SagaManager創建了Saga之后,會調用StartAsync方法,將第一個Saga Step的Saga事件發送到消息隊列,之后,SagaManager會偵聽自己的消息隊列以便獲得來自不同微服務的處理反饋消息。在這個Saga反饋消息事件的處理邏輯中,事件處理器會根據反饋消息事件中所附帶的SagaId,通過SagaManager讀取Saga實例,然后執行狀態轉換。例如,下面的代碼就是在Saga反饋消息事件處理邏輯中,完成了Saga的狀態轉換:


class SagaEventHandler : IEventHandler<SagaEvent>

{

????private readonly SagaManager _sagaManager;

????public SagaEventHandler(SagaManager sagaManager)

????{

????????_sagaManager = sagaManager;

????}

????public async Task<bool> HandleAsync(SagaEvent @event, CancellationToken cancellationToken = default)

????{

????????await _sagaManager.TransitAsync(@event, cancellationToken);

????????return true;

????}

}

在TransitAsync方法中,Saga通過對已接收到的反饋消息進行狀態轉換,并發出下一個Saga Step的消息:


public async Task TransitAsync(SagaEvent sagaEvent, CancellationToken cancellationToken = default)

{

????Console.WriteLine($"{sagaEvent.EventType} - Succeeded: {sagaEvent.Succeeded}");

????var saga = await _dao.GetByIdAsync<Saga>(sagaEvent.SagaId, cancellationToken);

????var nextStepEvent = saga.ProcessEvent(sagaEvent);

????if (nextStepEvent != null)

????{

????????await _eventPublisher.PublishAsync(nextStepEvent, nextStepEvent.ServiceName, cancellationToken);

????}

????await _dao.UpdateByIdAsync(saga.Id, saga);

}

Saga的ProcessEvent的流程大致如下:

c1b88ac5e06641e2153626d5467d1c75.png

在上面的流程圖中:

  1. 藍色框和紅色框中表示在這個節點上,會獲得下一個Saga步驟或者上一個Saga步驟所產生的Saga事件,這個事件會被接下來的處理邏輯發送到消息隊列中

  2. 藍色框表示,當前Step已經執行成功,Saga的狀態會轉換到下一個步驟進行執行,并讀取下一個步驟的Saga事件

  3. 紅色框表示,當前Step已經執行成功,Saga的狀態會轉換到上一個步驟進行執行,并讀取上一個步驟的補償事件

  4. 灰色虛線框流程并不是ProcessEvent方法的主要職責,但為了保持流程的完整性,我將這部分用虛線補上

此外,值得一提的是,并不是所有的步驟都需要有補償操作,比如,對于“客戶信息微服務校驗當前客戶賬戶是否合法”這個步驟,如果其后續某個步驟失敗,那么該步驟并不需要進行補償,因為它本身沒有產生任何領域對象狀態的變更。對于這種情況,紅色框中“回退到上一個Step”的操作就需要依次迭代之前的每個步驟,找到需要進行補償的步驟為止。

就我們的例子而言,各個Saga步驟的定義如下:

a94dbf682f4086b47d4a761e971ae099.png

對于“補償事件類型”這一欄不為空的Saga步驟,它需要進行事務補償,因此,當Saga執行失敗并進行回溯時,需要在這些步驟上獲取補償事件并發送給對應的微服務,以完成事務補償。

實現效果

假設我們只允許客戶最多預留1000元的賬戶額度,并且只能預留庫存中不多于5000個商品,那么如果創建訂單時,請求方給的參數沒有超出這個范圍,那么所有的微服務都應該應答“成功”消息,也就是對應的事件類型也應該為“成功”:

9197cc19088cdf6bdcabf518aa072bd9.png

假設我們請求的庫存預留數大于5000:

d0f79a5762bd5e90e3def4ba24d37870.png

可以看到:當reserve-inventory事件的應答為false時,產生了兩個補償事件:compensate-reserve-credit和compensate-create-sales-order,并且這兩個補償事件的處理應答都為true:

4282ef3732825639b767d7d070d9ee6d.png

查詢數據庫狀態,根據SagaStatus和SagaStepStatus兩個枚舉的值,Saga的狀態為Aborted:

49e7b552888d1648f844f13a9a7ef6d2.png

有關Saga的大致設計和實現就先介紹這些吧,其實內容還有很多,可以參考本文的案例代碼:

https://github.com/daxnet/byteart-retail-msa

Saga框架相關的代碼在此:

https://github.com/daxnet/byteart-retail-msa/tree/main/src/ByteartRetail.TestClients.Common/Sagas

CQRS中的Saga

順便提一句,在CQRS體系結構模式中,Saga的實現有其自己的“職責”:

  • Command接收命令,發出領域事件

  • Saga接收領域事件,發出命令

如何理解?

  • Command部分在接收到來自客戶端的命令后,會操作領域模型對象,領域模型對象發生狀態變化的結果,就是發出領域事件

  • Saga在接收到領域事件之后,產生自身的狀態轉換,當達到某個狀態時,又發出命令,從而影響領域模型

更多思考

實現Saga事務其實并不是那么容易,還有很多需要思考的內容,本文也沒法全部涵蓋,例如:

  1. 一個更為優雅的設計是使用有限狀態機,使用Saga事件作為觸發器,來完成Saga的狀態轉換,例如,使用MassTransit的Automatonymous框架

  2. 如何(或者是否需要)保證消息派發的順序。在RabbitMQ中,發送到同一個Exchange,并由同一個RoutingKey指定的派發路由上的消息,可以保證其順序性;再比如,Apache Kafka是可以保證消息的派發和接收順序的。但是這些框架無法保證應用程序本身是按照消息接收的順序進行處理。所以,微服務需要保證消息處理的冪等性

  3. 如何保證消息不會被遺漏,也就是如何保證消息至少被處理一次。可以采用Listen To Yourself模式(參考我之前的文章《ASP.NET Core Web API下事件驅動型架構的實現(五):在微服務中使用自我監聽模式保證數據庫更新與消息派發的可靠性》),也可以在微服務內部使用消息存儲,確保未被派發消息不會丟失

  4. 如何保證補償事務能夠真正實現“補償”。如果補償不成功,也會導致數據不一致,無法實現最終一致性。可以在微服務處理補償事務的時候,使用類似Retry或者熔斷這樣的機制,通過反復嘗試來強制補償成功。如果最終仍然不成功,則需要記錄下來,等待后續手工補償,比如,通過郵件通知的方式,由管理員進行處理

  5. 多個SagaManager實例同時運行時,如何保證Saga能被正確處理。通常可以讓多個運行SagaManager的微服務實例同時偵聽同一個消息隊列,以便能夠以輪詢的形式處理反饋消息

以后有機會再慢慢分析吧。

總結

本文首先介紹了本地事務以及分布式事務,并通過微服務架構引入Saga體系結構模式,以實現跨服務的事務處理。然后通過一個簡單的業務案例,介紹了Saga體系結構模式的整體設計和簡單實現,并列舉了一些遺留問題可供進一步思考和討論。Saga的實現方式并非本文介紹的這一種,但本文介紹的方式還是相對比較簡單易懂的。整個Saga的設計體系是可以抽象成一套開發框架的,以便隔離狀態轉換和事件派發的復雜度,讓開發者更多地關注到業務實現上來。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/286269.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/286269.shtml
英文地址,請注明出處:http://en.pswp.cn/news/286269.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Css樣式基礎

1.Css的語法 CSS的語法主要由兩個部分組成&#xff0c;一個是選擇器&#xff0c;一個是屬性、 選擇器又分為以下幾種&#xff1a; 1.元素選擇器&#xff1a;即Html標簽去掉括號的就是元素 2.類選擇器&#xff1a;所謂的類就是說class“名稱”&#xff0c;類的名稱是可以相同&am…

Android 清除png圖片的白色背景

/**清除背景顏色 * param mBitmap* param mColor 背景顏色值 eg&#xff1a;Color.WHITE** return*/ private static Bitmap getAlphaBitmap(Bitmap mBitmap, int mColor) {Bitmap mAlphaBitmap Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Confi…

【ArcGIS遇上Python】Python使用柵格數據

柵格數據是一個獨特的空間數據類型。很多地理處理工具都是為了處理柵格數據而開發的。 1. 列出柵格數據 ListRaster函數是以Python列表的形式返回工作控件中的柵格數據,該函數的語法格式是: ListRaster({wild_card},{raster_type}) 可選參數wild_card通過名稱限制返回的結果…

GPhone、OPhone、UPhone、APhone、IPhone:滿城盡帶XPhone

本文為原創&#xff0c;如需轉載&#xff0c;請注明作者和出處&#xff0c;謝謝&#xff01; 最近一段時間智能手機市場是翻天覆地。各大廠商紛紛推出自己的手機操作系統和手機。Google、Apple、中國移動、中國聯通紛紛推出或即將推出自已 的智能手機操作系統&#xff08;雖…

C語言試題二十六之請編寫一個函數function(char *s),該函數的功反轉字符串中的內容。

??個人主頁:個人主頁 ??系列專欄:C語言試題200例目錄 ??推薦一款刷算法、筆試、面經、拿大公司offer神器 ?? 點擊跳轉進入網站 ?作者簡介:大家好,我是碼莎拉蒂,CSDN博客專家(全站排名Top 50),阿里云博客專家、51CTO博客專家、華為云享專家 1、題目 請編寫一個…

二、文章發布頁制作及后臺實現《iVX低代碼/無代碼個人博客制作》

注&#xff1a;iVX也有免費直播課《第八期直播課》 一、文章編輯頁制作 當首頁制作完畢后&#xff0c;需要顯示內容就需要有文章數據&#xff0c;此時我們創建一個文章編輯頁增加對應的數據。 那么我們創建一個頁面&#xff0c;命名為文章發布頁&#xff1a; 接著我們查看標…

VS2013配置pro*C/C++開發環境

2019獨角獸企業重金招聘Python工程師標準>>> 1、軟件&#xff1a;VS2013&#xff0c;oracle10g 2、VS2013 新建VC空項目&#xff0c;然后在源文件中新建一個*.pc文件&#xff08;不知道我的配置哪兒有問題&#xff0c;新建的pc文件必須和工程同名&#xff09;&#…

查看linux版本的三種常用方法

1) 登錄到服務器執行 lsb_release -a &#xff0c;即可列出所有版本信息&#xff0c;例如&#xff1a; [root3.5.5Biz-46 ~]# lsb_release -a LSB Version: 1.3 Distributor ID: RedHatEnterpriseAS Description: Red Hat Enterprise Linux AS release 4 (Nahant Update 1) Rel…

Windows 11 23H2 25131 推送!全新搜索體驗,優化應用商店

面向 Dev頻道的 Windows 預覽體驗成員&#xff0c;微軟現已推送 Windows 11 預覽版 Build 25131。主要變化1.微軟為 Windows 11 搜索引入全新體驗&#xff0c;當您在搜索結果中點擊“打開文件位置”時&#xff0c;現在將選擇文件資源管理器中的文件&#xff0c;此前只是打開文件…

C# RichTextBox 實現循環查找關鍵字

實現效果如上圖&#xff0c;點擊“Search”按鈕&#xff0c;開始從文首查找關鍵字“menu”&#xff0c;并高亮&#xff0c;再次點擊“Search”按鈕&#xff0c;繼續查找下一個。查找到文末&#xff0c;自動從文首重新查找。 private int _searchIndex 0;//查找開始位置/// <…

C語言試題二十七之請編寫程序,實現矩陣(3行3列)的轉置(即行列互換)。

??個人主頁:個人主頁 ??系列專欄:C語言試題200例目錄 ??推薦一款刷算法、筆試、面經、拿大公司offer神器 ?? 點擊跳轉進入網站 ?作者簡介:大家好,我是碼莎拉蒂,CSDN博客專家(全站排名Top 50),阿里云博客專家、51CTO博客專家、華為云享專家 1、題目 請編寫一個…

網站常見漏洞-- XSS攻擊

跨站攻擊&#xff0c;即Cross Site Script Execution(通常簡寫為XSS&#xff0c;因為CSS與層疊樣式表同名&#xff0c;故改為XSS) 是指攻擊者利用網站程序對用戶輸入過濾不足&#xff0c;輸入可以顯示在頁面上對其他用戶造成影響的HTML代碼&#xff0c;從而盜取用戶資料、利用用…

【ArcGIS遇上Python】從入門到精通系列之第一章:ArcGIS Python簡介

文章目錄1. Python簡介2. Python的特點3. ArcGIS的腳本語言4. ArcGIS中的Python腳本編輯器1. Python簡介 Python是一種跨平臺的計算機程序設計語言。 是一個高層次的結合了解釋性、編譯性、互動性和面向對象的腳本語言。最初被設計用于編寫自動化腳本(shell)&#xff0c;隨著版…

C# RichTextBox 做簡單的HTML代碼編輯器 ---------左側顯示行號

說明&#xff1a;此顯示行號為實際行號&#xff0c;不論是空行還是自動換行&#xff0c;都計算在內&#xff0c;跟實際IDE的行號不同&#xff0c;同步滾動會有半行高度以內的誤差。 實現原理&#xff0c;在RichTextBox 編輯器左側放置另一RichTextBox &#xff08;或其它控件也…

五、文章詳情頁制作及跳轉功能實現《iVX低代碼/無代碼個人博客制作》

注&#xff1a;iVX也有免費直播課《第八期直播課》 一、詳情頁制作 在之前的章節中&#xff0c;我們已經制作完畢了登錄、注冊、首頁等內容&#xff0c;在這一節中&#xff0c;我們編寫詳情頁以及詳情頁功能制作。 詳情頁頁面如下&#xff1a; 詳情頁頭部也就是一個頭部欄&…

c++ 數據類型轉換: static_cast dynamic_cast reinterpret_cast const_cast

c 數據類型轉換&#xff1a; static_cast dynamic_cast reinterpret_cast const_cast 【版權聲明】轉載請注明出處 http://www.cnblogs.com/TenosDoIt/p/3175217.html【目錄】 引言 static_cast 定義 dynamic_cast 定義 舉例&#xff1a;下行轉換&#xff08;把基類的指針或引用…

C語言試題二十八之編寫函數function功能是:從字符中刪除指定的字符,同一字母的大、小寫按不同字符處理。

??個人主頁:個人主頁 ??系列專欄:C語言試題200例目錄 ??推薦一款刷算法、筆試、面經、拿大公司offer神器 ?? 點擊跳轉進入網站 ?作者簡介:大家好,我是碼莎拉蒂,CSDN博客專家(全站排名Top 50),阿里云博客專家、51CTO博客專家、華為云享專家 1、題目 編寫函數f…

日用有余!國產中科方德桌面操作系統初體驗

國產IT圈里最受關注的話題&#xff0c;除了芯片想必就是操作系統了。但真說起國產操作系統&#xff0c;大家是既熟悉又陌生&#xff0c;聽說過的多而真正使用過的少。而伴隨產業發展&#xff0c;市面上也涌現出眾多國產操作軟件&#xff0c;這些系統是否好用&#xff1f;能否滿…

面試經驗總結

8 transient是干嘛的 Java的serialization提供了一種持久化對象實例的機制。當持久化對象時&#xff0c;可能有一個特殊的對象數據成員&#xff0c;我們不想用 serialization機制來保存它。為了在一個特定對象的一個域上關閉serialization&#xff0c;可以在這個域前加上關鍵字…

【GIS風暴】ArcGIS柵格重采樣(Resample)方法詳解

Contents 1. 最鄰近法(Nearest Neighbor)2. 雙線性內插法(Bilinear Interpolation)3. 三次卷積法(Cubic Convolution)4. ArcGIS重采樣工具(Resample)柵格/影像數據進行配準或糾正、投影等幾何變換后,像元中心位置通常會發生變化,其在輸入柵格中的位置不一定是整數的行…