使用分布式锁解决并发问题
This commit is contained in:
parent
b9a6d002ca
commit
aeefff6c0b
15
src/HelloShop.AppHost/DaprComponents/redis-lock.yaml
Normal file
15
src/HelloShop.AppHost/DaprComponents/redis-lock.yaml
Normal 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
|
@ -6,7 +6,7 @@ using Aspire.Hosting.Dapr;
|
||||
|
||||
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();
|
||||
|
||||
@ -19,14 +19,14 @@ var orderingService = builder.AddProject<Projects.HelloShop_OrderingService>("or
|
||||
.WithReference(identityService)
|
||||
.WithDaprSidecar(options =>
|
||||
{
|
||||
options.WithOptions(daprSidecarOptions).WithReference(rabbitmq);
|
||||
options.WithOptions(daprSidecarOptions).WithReference(rabbitmq).WithReference(cache);
|
||||
});
|
||||
|
||||
var productService = builder.AddProject<Projects.HelloShop_ProductService>("productservice")
|
||||
.WithReference(identityService)
|
||||
.WithDaprSidecar(options =>
|
||||
{
|
||||
options.WithOptions(daprSidecarOptions).WithReference(rabbitmq);
|
||||
options.WithOptions(daprSidecarOptions).WithReference(rabbitmq).WithReference(cache);
|
||||
}); ;
|
||||
|
||||
var basketService = builder.AddProject<Projects.HelloShop_BasketService>("basketservice")
|
||||
@ -34,7 +34,7 @@ var basketService = builder.AddProject<Projects.HelloShop_BasketService>("basket
|
||||
.WithReference(cache)
|
||||
.WithDaprSidecar(options =>
|
||||
{
|
||||
options.WithOptions(daprSidecarOptions).WithReference(rabbitmq);
|
||||
options.WithOptions(daprSidecarOptions).WithReference(rabbitmq).WithReference(cache);
|
||||
});
|
||||
|
||||
var apiservice = builder.AddProject<Projects.HelloShop_ApiService>("apiservice")
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
POST {{HelloShop.OrderingService_HostAddress}}/api/orders
|
||||
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
|
||||
|
||||
{
|
||||
|
@ -5,13 +5,16 @@ using HelloShop.ProductService.DistributedEvents.Events;
|
||||
using HelloShop.ProductService.Entities.Products;
|
||||
using HelloShop.ProductService.Infrastructure;
|
||||
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
|
||||
using HelloShop.ServiceDefaults.DistributedLocks;
|
||||
|
||||
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)
|
||||
{
|
||||
await using var lockResult = await distributedLock.LockAsync("stock");
|
||||
|
||||
logger.LogInformation("Handling distributed event {EventId} {Event}", @event.Id, @event);
|
||||
|
||||
var confirmedOrderStockItems = new Dictionary<int, bool>();
|
||||
|
@ -5,13 +5,16 @@ using HelloShop.ProductService.DistributedEvents.Events;
|
||||
using HelloShop.ProductService.Entities.Products;
|
||||
using HelloShop.ProductService.Infrastructure;
|
||||
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
|
||||
using HelloShop.ServiceDefaults.DistributedLocks;
|
||||
|
||||
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)
|
||||
{
|
||||
await using var lockResult = await distributedLock.LockAsync("stock");
|
||||
|
||||
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");
|
||||
|
@ -5,6 +5,7 @@ using HelloShop.ProductService.Constants;
|
||||
using HelloShop.ProductService.Infrastructure;
|
||||
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
|
||||
using HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks;
|
||||
using HelloShop.ServiceDefaults.DistributedLocks;
|
||||
using HelloShop.ServiceDefaults.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@ -29,6 +30,7 @@ builder.Services.AddModelMapper().AddModelValidator();
|
||||
builder.Services.AddLocalization().AddPermissionDefinitions();
|
||||
builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthorization();
|
||||
builder.AddDaprDistributedEventBus().AddSubscriptionFromAssembly();
|
||||
builder.Services.AddSingleton<IDistributedLock, DaprDistributedLock>();
|
||||
// End addd extensions services to the container.
|
||||
|
||||
var app = builder.Build();
|
||||
|
@ -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; }
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 { }
|
||||
}
|
Loading…
Reference in New Issue
Block a user