MassTransit | 基于MassTransit Courier 實現 Saga 編排式分布式事務

Saga 模式

Saga 最初出現在1987年Hector Garcaa-Molrna & Kenneth Salem發表的一篇名為《Sagas》的論文里。其核心思想是將長事務拆分為多個短事務,借助Saga事務協調器的協調,來保證要么所有操作都成功完成,要么運行相應的補償事務以撤消先前完成的工作,從而維護多個服務之間的數據一致性。舉例而言,假設有個在線購物網站,其后端服務劃分為訂單服務、支付服務和庫存服務。那么一次下訂單的Saga流程如下圖所示:

b1d04cf3619c81e6f066e1d226f7f1a5.png

在Saga模式中本地事務是Saga 參與者執行的工作單元,每個本地事務都會更新數據庫并發布消息或事件以觸發 Saga 中的下一個本地事務。如果本地事務失敗,Saga 會執行一系列補償事務,以撤消先前本地事務所做的更改。 對于Saga模式的實現又分為兩種形式:

  1. 協同式:把Saga 的決策和執行順序邏輯分布在Saga的每個參與方中,通過交換事件的方式進行流轉。示例圖如下所示:

144f95960d0906a9189e3f49d580fdf0.png
  1. 編排式:把Saga的決策和執行順序邏輯集中定義在一個Saga 編排器中。Saga 編排器發出命令式消息給各個Saga 參與方,指示這些參與方執行怎樣的操作。

37dd9a4a66be4de07cc2a219c59fdc9c.png

從上圖可以看出,對于協同式Saga 存在一個致命的弊端,那就是存在循環依賴的問題,每個Saga參與方都需要訂閱所有影響它們的事件,耦合性較高,且由于Saga 邏輯分散在各參與方,不便維護。相對而言,編排式Saga 則實現了關注點分離,協調邏輯集中在編排器中定義,Saga 參與者僅需實現供編排器調用的API 即可。 在.NET 中也有開箱即用的開源框架實現了編排式的Saga事務模型,也就是MassTransit Courier,接下來就來實際探索一番。

MassTransit Courier 簡介

MassTransit Courier 是對Routing Slip(路由單) 模式的實現。該模式用于運行時動態指定消息處理步驟,解決不同消息可能有不同消息處理步驟的問題。實現機制是消息處理流程的開始,創建一個路由單,這個路由單定義消息的處理步驟,并附加到消息中,消息按路由單進行傳輸,每個處理步驟都會查看_路由單_并將消息傳遞到路由單中指定的下一個處理步驟。 在MassTransit Courier中是通過抽象IActivityRoutingSlip來實現了Routing Slip模式。通過按需有序組合一系列的Activity,得到一個用來限定消息處理順序的Routing Slip。而每個Activity的具體抽象就是IActivityIExecuteActivity。二者的差別在于IActivity定義了ExecuteCompensate兩個方法,而IExecuteActivitiy僅定義了Execute方法。其中Execute代表正向操作,Compensate代表反向補償操作。用一個簡單的下單流程:創建訂單->扣減庫存->支付訂單舉例而言,使用Courier的實現示意圖如下所示:7a0f45b32acb69ef4baf12fd154ad170.jpeg

基于Courier 實現編排式Saga事務

那具體如何使用MassTransit Courier來應用編排式Saga 模式呢,接下來就來創建解決方案來實現以上下單流程示例。

創建解決方案

依次創建以下項目,除共享類庫項目外,均安裝MassTransitMassTransit.RabbitMQNuGet包。

項目項目名項目類型
訂單服務MassTransit.CourierDemo.OrderServiceASP.NET Core Web API
庫存服務MassTransit.CourierDemo.InventoryServiceWorker Service
支付服務MassTransit.CourierDemo.PaymentServiceWorker Service
共享類庫MassTransit.CourierDemo.SharedClass Library

三個服務都添加擴展類MassTransitServiceExtensions,并在Program.cs類中調用services.AddMassTransitWithRabbitMq();注冊服務。

using?System.Reflection;
using?MassTransit.CourierDemo.Shared.Models;namespace?MassTransit.CourierDemo.InventoryService;public?static?class?MassTransitServiceExtensions
{public?static?IServiceCollection?AddMassTransitWithRabbitMq(this?IServiceCollection?services){return?services.AddMassTransit(x?=>{x.SetKebabCaseEndpointNameFormatter();//?By?default,?sagas?are?in-memory,?but?should?be?changed?to?a?durable//?saga?repository.x.SetInMemorySagaRepositoryProvider();var?entryAssembly?=?Assembly.GetEntryAssembly();x.AddConsumers(entryAssembly);x.AddSagaStateMachines(entryAssembly);x.AddSagas(entryAssembly);x.AddActivities(entryAssembly);x.UsingRabbitMq((context,?busConfig)?=>{busConfig.Host(host:?"localhost",port:?5672,virtualHost:?"masstransit",configure:?hostConfig?=>{hostConfig.Username("guest");hostConfig.Password("guest");});busConfig.ConfigureEndpoints(context);});});}
}

訂單服務

訂單服務作為下單流程的起點,需要承擔構建RoutingSlip的職責,因此可以創建一個OrderRoutingSlipBuilder來構建RoutingSlip,代碼如下:

using?MassTransit.Courier.Contracts;
using?MassTransit.CourierDemo.Shared.Models;namespace?MassTransit.CourierDemo.OrderService;
public?static?class?OrderRoutingSlipBuilder
{public?static?RoutingSlip?BuildOrderRoutingSlip(CreateOrderDto?createOrderDto){var?createOrderAddress?=?new?Uri("queue:create-order_execute");var?deduceStockAddress?=?new?Uri("queue:deduce-stock_execute");var?payAddress?=?new?Uri("queue:pay-order_execute");????????var?routingSlipBuilder?=?new?RoutingSlipBuilder(Guid.NewGuid());routingSlipBuilder.AddActivity(name:?"order-activity",executeAddress:?createOrderAddress,arguments:?createOrderDto);routingSlipBuilder.AddActivity(name:?"deduce-stock-activity",?executeAddress:?deduceStockAddress);routingSlipBuilder.AddActivity(name:?"pay-activity",?executeAddress:?payAddress);var?routingSlip?=?routingSlipBuilder.Build();return?routingSlip;}
}

從以上代碼可知,構建一個路由單需要以下幾步:

  1. 明確業務用例涉及的具體用例,本例中為:

    1. 創建訂單:CreateOrder

    2. 扣減庫存:DeduceStock

    3. 支付訂單:PayOrder

  2. 根據用例名,按短橫線隔開命名法(kebab-case)定義用例執行地址,格式為queue:<usecase>_execute,本例中為:

    1. 創建訂單執行地址:queue:create-order_execute

    2. 創建訂單執行地址:queue:deduce-stock_execute

    3. 創建訂單執行地址:queue:pay-order_execute

  3. 創建路由單:

    1. 通過RoutingSlipBuilder(Guid.NewGuid())創建路由單構建器實例

    2. 根據業務用例流轉順序,調用AddActivity()方法依次添加Activity用來執行用例,因為第一個創建訂單用例需要入口參數,因此傳入了一個CreateOrderDtoDTO(Data Transfer Object)對象

    3. 調用Build()方法創建路由單

對于本例而言,由于下單流程是固定流程,因此以上路由單的構建也是按業務用例進行定義的。而路由單的強大之處在于,可以按需動態組裝。在實際電商場景中,有些訂單是無需執行庫存扣減的,比如充值訂單,對于這種情況,僅需在創建路由單時判斷若為充值訂單則不添加扣減庫存的Activity即可。 對于訂單服務必然要承擔創建訂單的職責,定義CreateOrderActivity(Activity的命名要與上面定義的用例對應)如下,其中OrderRepository為一個靜態訂單倉儲類:

public?class?CreateOrderActivity?:?IActivity<CreateOrderDto,?CreateOrderLog>
{private?readonly?ILogger<CreateOrderActivity>?_logger;public?CreateOrderActivity(ILogger<CreateOrderActivity>?logger){_logger?=?logger;}//?訂單創建public?async?Task<ExecutionResult>?Execute(ExecuteContext<CreateOrderDto>?context){var?order?=?await?CreateOrder(context.Arguments);var?log?=?new?CreateOrderLog(order.OrderId,?order.CreatedTime);_logger.LogInformation($"Order?[{order.OrderId}]?created?successfully!");return?context.CompletedWithVariables(log,?new?{order.OrderId});}private?async?Task<Order>?CreateOrder(CreateOrderDto?orderDto){var?shoppingItems?=orderDto.ShoppingCartItems.Select(item?=>?new?ShoppingCartItem(item.SkuId,?item.Price,?item.Qty));var?order?=?new?Order(orderDto.CustomerId).NewOrder(shoppingItems.ToArray());await?OrderRepository.Insert(order);return?order;}//?訂單補償(取消訂單)public?async?Task<CompensationResult>?Compensate(CompensateContext<CreateOrderLog>?context){var?order?=?await?OrderRepository.Get(context.Log.OrderId);order.CancelOrder();var?exception?=?context.Message.ActivityExceptions.FirstOrDefault();_logger.LogWarning($"Order?[{order.OrderId}?has?been?canceled?duo?to?{exception.ExceptionInfo.Message}!");return?context.Compensated();}
}

從以上代碼可知,實現一個Activity,需要以下步驟:

  1. 定義實現IActivity<in TArguments, in TLog>需要的參數類:

    1. TArguments對應正向執行入口參數,會在Execute方法中使用,本例中為CreateOrderDto,用于訂單創建。

    2. TLog對應反向補償參數,會在Compensate方法中使用,本例中為CreateOrderLog,用于訂單取消。

  2. 實現IActivity<in TArguments, in TLog>接口中的Execute方法:

    1. 具體用例的實現,本例中對應訂單創建邏輯

    2. 創建TLog反向補償參數實例,以便業務異常時能夠按需補償

    3. 返回Activity執行結果,并按需傳遞參數至下一個Activity,本例僅傳遞訂單Id至下一流程。

  3. 實現IActivity<in TArguments, in TLog>接口中的Compensate方法:

    1. 具體反向補償邏輯的實現,本例中對應取消訂單

    2. 返回反向補償執行結果

訂單服務的最后一步就是定義WebApi來接收創建訂單請求,為簡要起便創建OrderController如下:

using?MassTransit.CourierDemo.Shared.Models;
using?Microsoft.AspNetCore.Mvc;namespace?MassTransit.CourierDemo.OrderService.Controllers;[ApiController]
[Route("[controller]")]
public?class?OrderController?:?ControllerBase
{private?readonly?IBus?_bus;public?OrderController(IBus?bus){_bus?=?bus;}[HttpPost]public?async?Task<IActionResult>?CreateOrder(CreateOrderDto?createOrderDto){//?創建訂單路由單var?orderRoutingSlip?=?OrderRoutingSlipBuilder.BuildOrderRoutingSlip(createOrderDto);//?執行訂單流程await?_bus.Execute(orderRoutingSlip);return?Ok();}
}

庫存服務

庫存服務在整個下單流程的職責主要是庫存的扣減和返還,但由于從上游用例僅傳遞了OrderId參數到庫存扣減Activity,因此在庫存服務需要根據OrderId 去請求訂單服務獲取要扣減的庫存項才能執行扣減邏輯。而這可以通過使用MassTransit的Reqeust/Response 模式來實現,具體步驟如下:

  1. 在共享類庫MassTransit.CourierDemo.Shared中定義IOrderItemsRequestIOrderItemsResponse

namespace?MassTransit.CourierDemo.Shared.Models;public?interface?IOrderItemsRequest
{public?string?OrderId?{?get;?}
}
public?interface?IOrderItemsResponse
{public?List<DeduceStockItem>?DeduceStockItems?{?get;?set;?}public?string?OrderId?{?get;?set;?}
}
  1. 在訂單服務中實現IConsumer<IOrderItemsRequest:

using?MassTransit.CourierDemo.OrderService.Repositories;
using?MassTransit.CourierDemo.Shared.Models;namespace?MassTransit.CourierDemo.OrderService.Consumers;public?class?OrderItemsRequestConsumer?:?IConsumer<IOrderItemsRequest>
{public?async?Task?Consume(ConsumeContext<IOrderItemsRequest>?context){var?order?=?await?OrderRepository.Get(context.Message.OrderId);await?context.RespondAsync<IOrderItemsResponse>(new{order.OrderId,?DeduceStockItems?=?order.OrderItems.Select(item?=>?new?DeduceStockItem(item.SkuId,?item.Qty)).ToList()});}
}
  1. 在庫存服務注冊service.AddMassTransit()中注冊x.AddRequestClient<IOrderItemsRequest>();

using?System.Reflection;
using?MassTransit.CourierDemo.Shared.Models;namespace?MassTransit.CourierDemo.InventoryService;public?static?class?MassTransitServiceExtensions
{public?static?IServiceCollection?AddMassTransitWithRabbitMq(this?IServiceCollection?services){return?services.AddMassTransit(x?=>{//...????????????x.AddRequestClient<IOrderItemsRequest>();//...});}
}
  1. 在需要的類中注冊IRequestClient<OrderItemsRequest>服務即可。

最終扣減庫存的Activity實現如下:

public?class?DeduceStockActivity?:?IActivity<DeduceOrderStockDto,?DeduceStockLog>
{private?readonly?IRequestClient<IOrderItemsRequest>?_orderItemsRequestClient;private?readonly?ILogger<DeduceStockActivity>?_logger;public?DeduceStockActivity(IRequestClient<IOrderItemsRequest>?orderItemsRequestClient,ILogger<DeduceStockActivity>?logger){_orderItemsRequestClient?=?orderItemsRequestClient;_logger?=?logger;}//?庫存扣減public?async?Task<ExecutionResult>?Execute(ExecuteContext<DeduceOrderStockDto>?context){var?deduceStockDto?=?context.Arguments;var?orderResponse?=await?_orderItemsRequestClient.GetResponse<IOrderItemsResponse>(new?{?deduceStockDto.OrderId?});if?(!CheckStock(orderResponse.Message.DeduceStockItems))return?context.Faulted(new?Exception("insufficient?stock"));DeduceStocks(orderResponse.Message.DeduceStockItems);var?log?=?new?DeduceStockLog(deduceStockDto.OrderId,?orderResponse.Message.DeduceStockItems);_logger.LogInformation($"Inventory?has?been?deducted?for?order?[{deduceStockDto.OrderId}]!");return?context.CompletedWithVariables(log,?new?{?log.OrderId?});}//?庫存檢查private?bool?CheckStock(List<DeduceStockItem>?deduceItems){foreach?(var?stockItem?in?deduceItems){if?(InventoryRepository.GetStock(stockItem.SkuId)?<?stockItem.Qty)?return?false;}return?true;}private?void?DeduceStocks(List<DeduceStockItem>?deduceItems){foreach?(var?stockItem?in?deduceItems){InventoryRepository.TryDeduceStock(stockItem.SkuId,?stockItem.Qty);}}//庫存補償public?Task<CompensationResult>?Compensate(CompensateContext<DeduceStockLog>?context){foreach?(var?deduceStockItem?in?context.Log.DeduceStockItems){InventoryRepository.ReturnStock(deduceStockItem.SkuId,?deduceStockItem.Qty);}_logger.LogWarning($"Inventory?has?been?returned?for?order?[{context.Log.OrderId}]!");return?Task.FromResult(context.Compensated());}
}

支付服務

對于下單流程的支付用例來說,要么成功要么失敗,并不需要像以上兩個服務一樣定義補償邏輯,因此僅需要實現IExecuteActivity<in TArguments>接口即可,該接口僅定義了Execute接口方法,具體PayOrderActivity實現如下:

using?MassTransit.CourierDemo.Shared;
using?MassTransit.CourierDemo.Shared.Models;namespace?MassTransit.CourierDemo.PaymentService.Activities;public?class?PayOrderActivity?:?IExecuteActivity<PayDto>
{private?readonly?IBus?_bus;private?readonly?IRequestClient<IOrderAmountRequest>?_client;private?readonly?ILogger<PayOrderActivity>?_logger;public?PayOrderActivity(IBus?bus,IRequestClient<IOrderAmountRequest>?client,ILogger<PayOrderActivity>?logger){_bus?=?bus;_client?=?client;_logger?=?logger;}public?async?Task<ExecutionResult>?Execute(ExecuteContext<PayDto>?context){var?response?=?await?_client.GetResponse<IOrderAmountResponse>(new?{?context.Arguments.OrderId?});????????//?do?payment...if?(response.Message.Amount?%?2?==?0){_logger.LogInformation($"Order?[{context.Arguments.OrderId}]?paid?successfully!");return?context.Completed();}_logger.LogWarning($"Order?[{context.Arguments.OrderId}]?payment?failed!");return?context.Faulted(new?Exception("Order?payment?failed?due?to?insufficient?account?balance."));}
}

以上代碼中也使用了MassTransit的Reqeust/Response 模式來獲取訂單要支付的余額,并根據訂單金額是否為偶數來模擬支付失敗。

運行結果

啟動三個項目,并在Swagger中發起訂單創建請求,如下圖所示:

982ff587ab6c4357a65f0e56b3eb3a88.png

由于訂單總額為奇數,因此支付會失敗,最終控制臺輸出如下圖所示:

3592827c6b17d513c589f31fdabb2956.png打開RabbitMQ后臺,可以看見MassTransit按照約定創建了以下隊列用于服務間的消息傳遞:

c3f587e989e32b5a5b7cea36d2d0c0dd.png

但你肯定好奇本文中使用的路由單具體是怎樣實現的?簡單,停掉庫存服務,再發送一個訂單創建請求,然后從隊列獲取未消費的消息即可解開謎底。以下是抓取的一條消息示例:

{"messageId":?"ac5d0000-e330-482a-b7bc-08dada7915ab","requestId":?null,"correlationId":?"ce8af31b-a65c-4dfa-915c-4ae5174820f9","conversationId":?"ac5d0000-e330-482a-28a5-08dada7915ad","initiatorId":?null,"sourceAddress":?"rabbitmq://localhost/masstransit/THINKPAD_MassTransitCourierDemoOrderService_bus_itqoyy8dgbrniyeobdppw6engn?temporary=true","destinationAddress":?"rabbitmq://localhost/masstransit/deduce-stock_execute?bind=true","responseAddress":?null,"faultAddress":?null,"messageType":?["urn:message:MassTransit.Courier.Contracts:RoutingSlip"],"message":?{"trackingNumber":?"ce8af31b-a65c-4dfa-915c-4ae5174820f9","createTimestamp":?"2022-12-10T06:38:01.5452768Z","itinerary":?[{"name":?"deduce-stock-activity","address":?"queue:deduce-stock_execute","arguments":?{}},{"name":?"pay-activity","address":?"queue:pay-order_execute","arguments":?{}}],"activityLogs":?[{"executionId":?"ac5d0000-e330-482a-7cb2-08dada7915bf","name":?"order-activity","timestamp":?"2022-12-10T06:38:01.7115314Z","duration":?"00:00:00.0183136","host":?{"machineName":?"THINKPAD","processName":?"MassTransit.CourierDemo.OrderService","processId":?23980,"assembly":?"MassTransit.CourierDemo.OrderService","assemblyVersion":?"1.0.0.0","frameworkVersion":?"6.0.9","massTransitVersion":?"8.0.7.0","operatingSystemVersion":?"Microsoft?Windows?NT?10.0.19044.0"}}],"compensateLogs":?[{"executionId":?"ac5d0000-e330-482a-7cb2-08dada7915bf","address":?"rabbitmq://localhost/masstransit/create-order_compensate","data":?{"orderId":?"8c47a1db-cde3-43bb-a809-644f36e7ca99","createdTime":?"2022-12-10T14:38:01.7272895+08:00"}}],"variables":?{"orderId":?"8c47a1db-cde3-43bb-a809-644f36e7ca99"},"activityExceptions":?[],"subscriptions":?[]},"expirationTime":?null,"sentTime":?"2022-12-10T06:38:01.774618Z","headers":?{"MT-Forwarder-Address":?"rabbitmq://localhost/masstransit/create-order_execute"}
}

從中可以看到信封中的message.itinerary定義了消息的行程,從而確保消息按照定義的流程進行流轉。同時通過message.compensateLogs來指引若失敗將如何回滾。

總結

通過以上示例的講解,相信了解到MassTransit Courier的強大之處。Courier中的RoutingSlip充當著事務編排器的角色,將Saga的決策和執行順序邏輯封裝在消息體內隨著消息進行流轉,從而確保各服務僅需關注自己的業務邏輯,而無需關心事務的流轉,真正實現了關注點分離。

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

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

相關文章

ccleaner無法更新_CCleaner正在靜默更新關閉自動更新的用戶

ccleaner無法更新CCleaner is forcing updates on users who specifically opt out of automatic updates. Users will only find out about these unwanted updates when they check the version number. CCleaner強制對專門選擇退出自動更新的用戶進行更新。 用戶只有在檢查版…

查找域內所有的Windows Server 2012 R2的服務器,并區分出哪些是物理機,那些是虛擬機...

通過使用Get-Adcomputer和Get-Wmiobject 組合來實現。思路是這樣的&#xff0c;先看一臺服務器的屬性值有什么可用利用的。[12r2-dc]: PS C:\> Get-ADComputer -Identity 12r2-dc -Properties *AccountExpirationDate :accountExpires …

rest_framework12:多登陸方式與自動簽發token/配置過期時間

多登陸方式與自動簽發token views.py 1.繼承Viewset&#xff0c;方法里可以使用自定義login&#xff0c;更直觀。需要路由直接配置請方式 2. 序列化是直接對request數據處理&#xff0c;并從對象中獲取token 3.context可以儲存自定義數據 # 多登陸方式&#xff0c;自動簽發…

20165310_獲獎感想與Java階段性學習總結

獲獎感想與Java階段性學習總結 一、Learning By Doing ? 在此之前&#xff0c;其實我并沒有想到能夠成為小黃杉的第一批成員之一&#xff0c;喜悅之余&#xff0c;也感受到了許多的壓力。小黃杉一方面代表了老師對于我這一階段學習成果的肯定&#xff0c;但同時也是對我的督促…

chrome瀏覽器崩潰_不只是您:Chrome瀏覽器在Windows 10的2018年4月更新中崩潰

chrome瀏覽器崩潰If your computer is hanging or freezing after installing the Windows 10 April 2018 Update you’re not alone, and Microsoft is aware of the problem. 如果在安裝Windows 10 April 2018 Update之后計算機掛起或死機&#xff0c;您并不孤單&#xff0c;…

讀名老中醫之路筆記(二)

任應秋&#xff1a;我的治學門徑和方法 任應秋先生從幼讀經&#xff0c;十三經皆能成誦&#xff0c;屬于帶童子功的醫學家&#xff0c;他的醫學經驗&#xff1a; 一、讀經宜讀全本&#xff0c;解經宜先識字&#xff0c;讀經宜正音讀&#xff0c;強調對經典著作的朗讀和背誦&…

致敬青春歲月

昨天發生的一件神奇的事情。我們公司工會組織了一次小型的戶外團建&#xff0c;有機會認識一些其他部門同事&#xff0c;沒想到有一個同事小心地認出了我&#xff0c;然后還談起了關于.NET技術和社區的一些發展的歷史和故事。他在微軟工作的時間比我久&#xff0c;但時空交錯&a…

談談- declare-styleable屬性

在Android開發中&#xff0c;往往要用到自定義的控件來實現我們的需求或效果。在使用自定義 控件時&#xff0c;難免要用到自定義屬性&#xff0c;那怎么使用自定義屬性呢&#xff1f; 一、簡單使用&#xff1a; 1.在文件res/values/下新建attrs.xml屬性文件&#xff0c;中定義…

docker:自定義ubuntu/制作鏡像引用/ubuntu換源更新

一、需求 1. 制作一個圖像辨識的api&#xff0c;用到相同設置的ubuntu鏡像&#xff0c;但是每次制作都要更新ubuntu和下載tesseract浪費半個到一個小時下載&#xff0c;所以制作一個自定義ubuntu幾次鏡像大大提高開發效率。 2. 制作ubuntu過程時&#xff0c;可以調試tesserac…

jQuery 屬性和CSS

HTML代碼&#xff1a; <div id"div1">div1<p>1</p><p>2</p><p>3</p> </div> <div id"div2">div2</div> <div id"div3">div3</div>attr()設置節點的屬性 $("#div1…

facebook人臉照片_為什么您的Facebook照片看起來如此糟糕(以及您可以如何做)...

facebook人臉照片Facebook is a popular platform for sharing photos, even though it’s not a very good one. They prioritize fast loading images over high quality ones. You can’t stop it from happening, but you can minimize the quality loss. Facebook是一個受…

用C#自己動手寫個操作系統,爽!

自從C#的AOT編譯機制發布以來&#xff0c;有趣的項目越來越多&#xff0c;今天給大家推薦一個開源項目&#xff0c;用C#開發的64位操作系統。項目簡介這是一個使用.NET Native AOT技術編譯的C# 64位操作系統&#xff0c;系統的基礎功能基本都已經支持&#xff1a;網卡、多處理、…

JavaFX 學習筆記——窗口與控件

前言 如今比較流行的桌面gui框架有WPF、WinForm、Qt、javafx等。其中WPF和WinForm目前還只能在運行Winsows上。Qt(widget)是一個很強大的跨平臺C框架(不只是UI)&#xff0c;但用C寫界面實在有點蛋疼&#xff0c;且編譯出來的體積很大。 JavaFX是基于JAVA的開源桌面框架&#xf…

Linux 用戶名、主機添加背景色

文章參考&#xff1a;PS1應用之——修改linux終端命令行各字體顏色 Linux 用戶名、主機添加背景色&#xff0c;用于生產環境&#xff0c;這樣可以減少人為的誤操作。 1 [rootzhang ~]# tail /etc/bashrc 2 ……………… 3 export PS1"\[\e[37;40m\][\[\e[37;41m\]\u\[\e[3…

python 調用文件上傳圖片簡單例子

使用方法&#xff1a; python.exe .\test.py "fileD:\img\mark_1080.png" "matchWordListRUN" "urlhttp://192.168.0.37:8081/templateMatch" test.py import requests import sysif __name__ "__main__":print(參數個數為:, len(s…

解決網站在負載均衡環境下SESSION丟失的問題

在WEB場中,動態網頁往往會因為幾臺主機做了負載而產生SESSION丟失的問題&#xff0c;網上也有很多的介紹&#xff0c;我這里只將我經歷的過程給大家分享一下&#xff1a; 系統要運行在負載平衡的 Web 場環境中&#xff0c;而系統配置文件web.config中的Session狀態卻設置為InPr…

如何從手機或PC將游戲下載到PlayStation 4

PlayStation 4 games can be huge, and take hours to download. Thankfully, you can start downloading games even when you’re away from home. All you need is Sony’s official smartphone app, or a web browser on any PC. PlayStation 4游戲可能非常龐大&#xff0c…

CML更新 | 新增百度小程序、支付寶小程序

祝所有工程師小伙伴開工大吉&#xff0c;Beatles 團隊已經開始忙碌起來了。 幾個事情要向諸位匯報一下&#xff1a; 一、新增百度小程序、支付寶小程序 發布alpha版本支持百度小程序、支付寶小程序&#xff0c;已有項目可以無縫直接運行在新增平臺&#xff0c;歡迎安裝試用&…

C#中4種深拷貝方法介紹

概述為什么要用到深拷貝呢&#xff1f;比如我們建了某個類Person&#xff0c;并且實例化出一個對象&#xff0c;然后&#xff0c;突然需要把這個對象復制一遍&#xff0c;并且復制出來的對象要跟之前的一模一樣&#xff0c;來看下我們一般會怎么做。1、利用反射實現public stat…

kaggle入門項目:Titanic存亡預測(三)數據可視化與統計分析

---恢復內容開始--- 原kaggle比賽地址&#xff1a;https://www.kaggle.com/c/titanic 原kernel地址&#xff1a;A Data Science Framework: To Achieve 99% Accuracy Step 4: Perform Exploratory Analysis with Statistics 使用描述性與圖表分析數據&#xff0c;重點在于數據可…