使用分布式锁解决并发问题
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 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")
|
||||||
|
@ -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
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -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>();
|
||||||
|
@ -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");
|
||||||
|
@ -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();
|
||||||
|
@ -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