本文目录导读:

在Symfony项目中处理资源竞争(Race Condition)是一个常见的挑战,特别是在高并发场景下,Symfony Lock组件正是为了解决这类问题而设计的,让我详细介绍一下:
Symfony Lock组件的作用
Symfony Lock组件提供了一种机制来同步对共享资源的访问,防止多个进程同时修改同一资源导致的竞态条件。
核心概念
Lock工厂(LockFactory)
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\RedisStore;
// 创建存储(使用Redis)
$redis = new \Redis();
$redis->connect('127.0.0.1');
$store = new RedisStore($redis);
$factory = new LockFactory($store);
获取锁(Lock对象)
// 创建一个锁
$lock = $factory->createLock('resource-key');
// 尝试获取锁(阻塞等待)
$lock->acquire();
// 或者尝试获取锁(非阻塞)
if ($lock->acquire(true)) {
// 获取到锁,执行操作
}
// 释放锁
$lock->release();
实际应用场景
场景1:防止重复订单
class OrderService
{
private LockFactory $lockFactory;
public function createOrder(int $userId, array $products): Order
{
// 使用用户ID作为锁资源
$lock = $this->lockFactory->createLock("order-user-{$userId}");
try {
// 尝试获取锁,等待5秒
if (!$lock->acquire(true)) {
throw new \RuntimeException('请稍后再试');
}
// 业务逻辑
$order = new Order();
$order->setUser($userId);
// ... 处理订单
return $order;
} finally {
$lock->release();
}
}
}
场景2:库存扣减
class InventoryService
{
private LockFactory $lockFactory;
private EntityManagerInterface $em;
public function deductStock(int $productId, int $quantity): bool
{
$lock = $this->lockFactory->createLock("stock-{$productId}");
try {
// 等待最多10秒
if (!$lock->acquire(true, 10)) {
throw new LockAcquiringException('库存操作繁忙');
}
// 检查库存
$product = $this->em->getRepository(Product::class)->find($productId);
if ($product->getStock() < $quantity) {
return false;
}
// 扣减库存
$product->setStock($product->getStock() - $quantity);
$this->em->flush();
return true;
} finally {
$lock->release();
}
}
}
存储后端选择
Redis(推荐用于生产环境)
composer require symfony/lock symfony/redis
use Symfony\Component\Lock\Store\RedisStore;
$redis = new \Redis();
$redis->connect('redis:6379');
$store = new RedisStore($redis);
Memcached
use Symfony\Component\Lock\Store\MemcachedStore;
$memcached = new \Memcached();
$memcached->addServer('localhost', 11211);
$store = new MemcachedStore($memcached);
文件系统(开发环境)
use Symfony\Component\Lock\Store\FlockStore;
$store = new FlockStore('/tmp/locks');
数据库
use Symfony\Component\Lock\Store\PdoStore;
use Symfony\Component\Lock\Store\PostgreSqlStore;
$store = new PdoStore('mysql:host=localhost;dbname=test');
// 或 PostgreSQL
$store = new PostgreSqlStore('pgsql:host=localhost;dbname=test');
高级功能
自动释放(推荐使用)
// 使用acquireAndExecute自动处理
$result = $this->lockFactory
->createLock('resource-key')
->acquireAndExecute(function () {
// 自动获取锁、执行操作、释放锁
return '操作结果';
});
锁超时
$lock = $factory->createLock('resource-key', 30); // 30秒后自动释放
// 或者设置TTL(生存时间)
$lock->setTtl(10); // 10秒后锁自动过期
读写锁
use Symfony\Component\Lock\SharedLockInterface;
// 读锁(共享)
$readLock = $factory->createLock('resource-key');
$readLock->acquireRead();
// 写锁(独占)
$writeLock = $factory->createLock('resource-key');
$writeLock->acquire();
最佳实践
在Symfony中配置使用
# config/packages/lock.yaml
framework:
lock:
resources:
default: 'redis://localhost:6379'
orders: 'redis://localhost:6379/orders'
inventory: 'redis://localhost:6379/inventory'
然后在服务中使用:
use Symfony\Component\Lock\LockFactory;
class OrderService
{
private LockFactory $orderLockFactory;
public function __construct(#['LockFactory' => 'orders'] LockFactory $factory)
{
$this->orderLockFactory = $factory;
}
}
处理锁异常
use Symfony\Component\Lock\Exception\LockAcquiringException;
use Symfony\Component\Lock\Exception\LockConflictedException;
try {
$lock = $factory->createLock('resource-key');
if (!$lock->acquire()) {
throw new \RuntimeException('资源被占用');
}
// 业务逻辑
} catch (LockConflictedException $e) {
// 锁冲突
throw new \RuntimeException('请稍后重试');
} catch (LockAcquiringException $e) {
// 获取锁失败
throw new \RuntimeException('系统繁忙');
} finally {
$lock?->release();
}
性能优化建议
- 选择合适的存储后端:Redis或Memcached性能最佳
- 合理设置锁粒度:细粒度锁(按商品ID)比粗粒度锁(整个库存系统)更高效
- 设置合理的超时时间:避免长时间占用锁
- 使用连接池:减少连接开销
完整示例:高并发订单系统
class OrderController
{
private LockFactory $lockFactory;
private EntityManagerInterface $em;
public function placeOrder(int $productId, int $quantity): JsonResponse
{
$lock = $this->lockFactory->createLock("product-{$productId}");
try {
// 尝试获取锁,最多等待5秒
if (!$lock->acquire(true, 5)) {
return new JsonResponse(['error' => '系统繁忙'], 429);
}
// 开始事务
$this->em->beginTransaction();
// 业务逻辑
$product = $this->em->find(Product::class, $productId);
if ($product->getStock() < $quantity) {
$this->em->rollback();
return new JsonResponse(['error' => '库存不足'], 400);
}
$product->setStock($product->getStock() - $quantity);
$order = new Order();
$order->setProduct($product);
$order->setQuantity($quantity);
$this->em->persist($order);
$this->em->flush();
$this->em->commit();
return new JsonResponse(['order_id' => $order->getId()]);
} catch (\Exception $e) {
$this->em->rollback();
return new JsonResponse(['error' => '操作失败'], 500);
} finally {
$lock->release();
}
}
}
通过合理使用Symfony Lock组件,可以有效解决资源竞争问题,确保数据的一致性和完整性。