使用分布式锁解决并发问题

This commit is contained in:
hello 2024-11-19 21:19:18 +08:00
parent b9a6d002ca
commit aeefff6c0b
11 changed files with 111 additions and 22 deletions

View File

@ -0,0 +1,15 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: lockstore
spec:
type: lock.redis
version: v1
metadata:
- name: redisHost
secretKeyRef:
name: ConnectionStrings__cache
- name: redisPassword
value: ""
auth:
secretStore: env-secretstore

View File

@ -6,7 +6,7 @@ using Aspire.Hosting.Dapr;
var builder = DistributedApplication.CreateBuilder(args); var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache", port: 6379); var cache = builder.AddRedis("cache", port: 6380).WithPersistence();
var rabbitmq = builder.AddRabbitMQ("rabbitmq").WithManagementPlugin(); var rabbitmq = builder.AddRabbitMQ("rabbitmq").WithManagementPlugin();
@ -19,14 +19,14 @@ var orderingService = builder.AddProject<Projects.HelloShop_OrderingService>("or
.WithReference(identityService) .WithReference(identityService)
.WithDaprSidecar(options => .WithDaprSidecar(options =>
{ {
options.WithOptions(daprSidecarOptions).WithReference(rabbitmq); options.WithOptions(daprSidecarOptions).WithReference(rabbitmq).WithReference(cache);
}); });
var productService = builder.AddProject<Projects.HelloShop_ProductService>("productservice") var productService = builder.AddProject<Projects.HelloShop_ProductService>("productservice")
.WithReference(identityService) .WithReference(identityService)
.WithDaprSidecar(options => .WithDaprSidecar(options =>
{ {
options.WithOptions(daprSidecarOptions).WithReference(rabbitmq); options.WithOptions(daprSidecarOptions).WithReference(rabbitmq).WithReference(cache);
}); ; }); ;
var basketService = builder.AddProject<Projects.HelloShop_BasketService>("basketservice") var basketService = builder.AddProject<Projects.HelloShop_BasketService>("basketservice")
@ -34,7 +34,7 @@ var basketService = builder.AddProject<Projects.HelloShop_BasketService>("basket
.WithReference(cache) .WithReference(cache)
.WithDaprSidecar(options => .WithDaprSidecar(options =>
{ {
options.WithOptions(daprSidecarOptions).WithReference(rabbitmq); options.WithOptions(daprSidecarOptions).WithReference(rabbitmq).WithReference(cache);
}); });
var apiservice = builder.AddProject<Projects.HelloShop_ApiService>("apiservice") var apiservice = builder.AddProject<Projects.HelloShop_ApiService>("apiservice")

View File

@ -2,7 +2,7 @@
POST {{HelloShop.OrderingService_HostAddress}}/api/orders POST {{HelloShop.OrderingService_HostAddress}}/api/orders
Content-Type: application/json Content-Type: application/json
x-request-id: 5ddc1737-6a31-4b4f-bccd-63d0966348a1 x-request-id: 8ddc1737-6a31-4b4f-bccd-63d0966348a1
Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJhZG1pbiIsInJvbGVpZCI6IjEiLCJuYmYiOjE3MjA1NzY3NDYsImV4cCI6MTc0MjYwODc0NiwiaWF0IjoxNzIwNTc2NzQ2fQ.ju_D3zeGLKqJYVckbb8Y3yNkp40nOqRAJrdOsISs4d4 Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJhZG1pbiIsInJvbGVpZCI6IjEiLCJuYmYiOjE3MjA1NzY3NDYsImV4cCI6MTc0MjYwODc0NiwiaWF0IjoxNzIwNTc2NzQ2fQ.ju_D3zeGLKqJYVckbb8Y3yNkp40nOqRAJrdOsISs4d4
{ {

View File

@ -5,13 +5,16 @@ using HelloShop.ProductService.DistributedEvents.Events;
using HelloShop.ProductService.Entities.Products; using HelloShop.ProductService.Entities.Products;
using HelloShop.ProductService.Infrastructure; using HelloShop.ProductService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions; using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedLocks;
namespace HelloShop.ProductService.DistributedEvents.EventHandling namespace HelloShop.ProductService.DistributedEvents.EventHandling
{ {
public class OrderAwaitingValidationDistributedEventHandler(ProductServiceDbContext dbContext, IDistributedEventBus distributedEventBus, ILogger<OrderAwaitingValidationDistributedEventHandler> logger) : IDistributedEventHandler<OrderAwaitingValidationDistributedEvent> public class OrderAwaitingValidationDistributedEventHandler(ProductServiceDbContext dbContext, IDistributedEventBus distributedEventBus, IDistributedLock distributedLock, ILogger<OrderAwaitingValidationDistributedEventHandler> logger) : IDistributedEventHandler<OrderAwaitingValidationDistributedEvent>
{ {
public async Task HandleAsync(OrderAwaitingValidationDistributedEvent @event) public async Task HandleAsync(OrderAwaitingValidationDistributedEvent @event)
{ {
await using var lockResult = await distributedLock.LockAsync("stock");
logger.LogInformation("Handling distributed event {EventId} {Event}", @event.Id, @event); logger.LogInformation("Handling distributed event {EventId} {Event}", @event.Id, @event);
var confirmedOrderStockItems = new Dictionary<int, bool>(); var confirmedOrderStockItems = new Dictionary<int, bool>();

View File

@ -5,13 +5,16 @@ using HelloShop.ProductService.DistributedEvents.Events;
using HelloShop.ProductService.Entities.Products; using HelloShop.ProductService.Entities.Products;
using HelloShop.ProductService.Infrastructure; using HelloShop.ProductService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions; using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedLocks;
namespace HelloShop.ProductService.DistributedEvents.EventHandling namespace HelloShop.ProductService.DistributedEvents.EventHandling
{ {
public class OrderPaidDistributedEventHandler(ProductServiceDbContext dbContext) : IDistributedEventHandler<OrderPaidDistributedEvent> public class OrderPaidDistributedEventHandler(ProductServiceDbContext dbContext, IDistributedLock distributedLock) : IDistributedEventHandler<OrderPaidDistributedEvent>
{ {
public async Task HandleAsync(OrderPaidDistributedEvent @event) public async Task HandleAsync(OrderPaidDistributedEvent @event)
{ {
await using var lockResult = await distributedLock.LockAsync("stock");
foreach (var orderStockItem in @event.OrderStockItems) foreach (var orderStockItem in @event.OrderStockItems)
{ {
var product = await dbContext.Set<Product>().FindAsync(orderStockItem.ProductId) ?? throw new Exception($"Product with id {orderStockItem.ProductId} not found"); var product = await dbContext.Set<Product>().FindAsync(orderStockItem.ProductId) ?? throw new Exception($"Product with id {orderStockItem.ProductId} not found");

View File

@ -5,6 +5,7 @@ using HelloShop.ProductService.Constants;
using HelloShop.ProductService.Infrastructure; using HelloShop.ProductService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions; using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks; using HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks;
using HelloShop.ServiceDefaults.DistributedLocks;
using HelloShop.ServiceDefaults.Extensions; using HelloShop.ServiceDefaults.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -29,6 +30,7 @@ builder.Services.AddModelMapper().AddModelValidator();
builder.Services.AddLocalization().AddPermissionDefinitions(); builder.Services.AddLocalization().AddPermissionDefinitions();
builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthorization(); builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthorization();
builder.AddDaprDistributedEventBus().AddSubscriptionFromAssembly(); builder.AddDaprDistributedEventBus().AddSubscriptionFromAssembly();
builder.Services.AddSingleton<IDistributedLock, DaprDistributedLock>();
// End addd extensions services to the container. // End addd extensions services to the container.
var app = builder.Build(); var app = builder.Build();

View File

@ -1,15 +0,0 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
namespace HelloShop.ProductService;
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}

View File

@ -0,0 +1,29 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Dapr.Client;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HelloShop.ServiceDefaults.DistributedLocks
{
public class DaprDistributedLock(DaprClient daprClient) : IDistributedLock
{
public async Task<IDistributedLockResult> LockAsync(string resourceId, int expiryInSeconds = default, CancellationToken cancellationToken = default)
{
expiryInSeconds = expiryInSeconds == default ? 60 : expiryInSeconds;
string? lockOwner = new StackTrace().GetFrame(1)?.GetMethod()?.DeclaringType?.Name;
#pragma warning disable CS0618
TryLockResponse response = await daprClient.Lock("lockstore", resourceId, lockOwner, expiryInSeconds, cancellationToken);
#pragma warning restore CS0618
return new DaprDistributedLockResult(response);
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using Dapr.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HelloShop.ServiceDefaults.DistributedLocks
{
#pragma warning disable CS0618
public class DaprDistributedLockResult(TryLockResponse tryLockResponse) : IDistributedLockResult
#pragma warning restore CS0618
{
public async ValueTask DisposeAsync()
{
await tryLockResponse.DisposeAsync().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HelloShop.ServiceDefaults.DistributedLocks
{
public interface IDistributedLock
{
public abstract Task<IDistributedLockResult> LockAsync(string resourceId, int expiryInSeconds = default, CancellationToken cancellationToken = default);
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HelloShop.ServiceDefaults.DistributedLocks
{
public interface IDistributedLockResult : IAsyncDisposable { }
}