thinkphp 6.x 并发问题 (以图书管理为例)

2025-02-11 16:29:08

在图书管理系统中处理并发问题,主要是为了避免多个用户同时对同一本图书进行操作(如同时更新、删除)时产生的数据不一致问题。下面我们从几个方面来优化之前的图书管理控制器以处理并发情况。

1. 使用数据库乐观锁

乐观锁是一种并发控制机制,它假设在大多数情况下不会发生冲突,只有在更新数据时才检查数据是否被其他事务修改过。我们可以在 books 表中添加一个 version 字段,用于记录数据的版本号。

2. 创建数据库表

假设你使用 MySQL 数据库,创建一个名为 books 的表,用于存储图书信息:

CREATE TABLE `books` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `title` varchar(255) NOT NULL,
    `author` varchar(255) NOT NULL,
    `price` decimal(10, 2) NOT NULL,
    `version` int(11) NOT NULL DEFAULT 0,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 创建模型

在 app/model 目录下创建 Book.php 模型文件:

<?php
namespace app\model;

use think\Model;

class Book extends Model
{
    // 表名
    protected $name = 'books';

    // 乐观锁字段
    protected $optimisticLock = 'version';
}

 

3. 创建控制器

在 app/controller 目录下创建 BookController.php 控制器文件:

<?php
namespace app\controller;

use app\model\Book;
use think\facade\Cache;
use think\facade\Db;

class BookController
{
    /**
     * 添加图书
     */
    public function addBook()
    {
        $data = request()->post();
        $book = new Book();
        $book->title = $data['title'];
        $book->author = $data['author'];
        $book->price = $data['price'];
        if ($book->save()) {
            // 新增图书后清除缓存
            Cache::store('redis')->rm('books_list');
            return json(['code' => 200, 'msg' => '图书添加成功']);
        } else {
            return json(['code' => 500, 'msg' => '图书添加失败']);
        }
    }

    /**
     * 获取图书列表
     */
    public function getBooks()
    {
        $cacheKey = 'books_list';
        $books = Cache::store('redis')->get($cacheKey);
        if (!$books) {
            $books = Book::select();
            Cache::store('redis')->set($cacheKey, $books, 3600); // 缓存 1 小时
        }
        return json(['code' => 200, 'msg' => '获取图书列表成功', 'data' => $books]);
    }

    /**
     * 更新图书信息
     */
    public function updateBook()
    {
        $id = request()->post('id');
        $data = request()->post();
        Db::startTrans();
        try {
            $book = Book::find($id);
            if ($book) {
                $book->title = $data['title'];
                $book->author = $data['author'];
                $book->price = $data['price'];
                if ($book->save()) {
                    // 更新图书后清除缓存
                    Cache::store('redis')->rm('books_list');
                    Db::commit();
                    return json(['code' => 200, 'msg' => '图书信息更新成功']);
                } else {
                    Db::rollback();
                    return json(['code' => 500, 'msg' => '图书信息更新失败,可能数据已被其他事务修改']);
                }
            } else {
                Db::rollback();
                return json(['code' => 404, 'msg' => '未找到该图书']);
            }
        } catch (\Exception $e) {
            Db::rollback();
            return json(['code' => 500, 'msg' => '更新图书时发生错误:' . $e->getMessage()]);
        }
    }

    /**
     * 删除图书
     */
    public function deleteBook()
    {
        $id = request()->post('id');
        Db::startTrans();
        try {
            $book = Book::find($id);
            if ($book) {
                if ($book->delete()) {
                    // 删除图书后清除缓存
                    Cache::store('redis')->rm('books_list');
                    Db::commit();
                    return json(['code' => 200, 'msg' => '图书删除成功']);
                } else {
                    Db::rollback();
                    return json(['code' => 500, 'msg' => '图书删除失败,可能数据已被其他事务修改']);
                }
            } else {
                Db::rollback();
                return json(['code' => 404, 'msg' => '未找到该图书']);
            }
        } catch (\Exception $e) {
            Db::rollback();
            return json(['code' => 500, 'msg' => '删除图书时发生错误:' . $e->getMessage()]);
        }
    }
}

4. 配置路由

在 app/route/route.php 中配置路由:

use think\facade\Route;

Route::post('add_book', 'BookController@addBook');
Route::get('get_books', 'BookController@getBooks');
Route::post('update_book', 'BookController@updateBook');
Route::post('delete_book', 'BookController@deleteBook');

 

*. Redis 分布式锁(可选)

如果系统并发量非常高,还可以使用 Redis 分布式锁来进一步控制并发访问。以下是一个简单的 Redis 分布式锁实现示例:

// ... 已有代码 ...

use think\facade\Cache;

// 获取 Redis 分布式锁
function getRedisLock($key, $expire = 10)
{
    $redis = Cache::store('redis')->handler();
    $lockKey = 'lock:' . $key;
    $value = uniqid();
    if ($redis->set($lockKey, $value, ['NX', 'EX' => $expire])) {
        return $value;
    }
    return false;
}

// 释放 Redis 分布式锁
function releaseRedisLock($key, $value)
{
    $redis = Cache::store('redis')->handler();
    $lockKey = 'lock:' . $key;
    if ($redis->get($lockKey) === $value) {
        $redis->del($lockKey);
        return true;
    }
    return false;
}

// 修改 updateBook 方法使用分布式锁
public function updateBook()
{
    $id = request()->post('id');
    $data = request()->post();
    $lockValue = getRedisLock('book:' . $id);
    if (!$lockValue) {
        return json(['code' => 503, 'msg' => '当前图书正在被其他用户操作,请稍后再试']);
    }
    Db::startTrans();
    try {
        $book = Book::find($id);
        if ($book) {
            $book->title = $data['title'];
            $book->author = $data['author'];
            $book->price = $data['price'];
            if ($book->save()) {
                // 更新图书后清除缓存
                Cache::store('redis')->rm('books_list');
                Db::commit();
                releaseRedisLock('book:' . $id, $lockValue);
                return json(['code' => 200, 'msg' => '图书信息更新成功']);
            } else {
                Db::rollback();
                releaseRedisLock('book:' . $id, $lockValue);
                return json(['code' => 500, 'msg' => '图书信息更新失败,可能数据已被其他事务修改']);
            }
        } else {
            Db::rollback();
            releaseRedisLock('book:' . $id, $lockValue);
            return json(['code' => 404, 'msg' => '未找到该图书']);
        }
    } catch (\Exception $e) {
        Db::rollback();
        releaseRedisLock('book:' . $id, $lockValue);
        return json(['code' => 500, 'msg' => '更新图书时发生错误:' . $e->getMessage()]);
    }
}

// ... 已有代码 ...

 

解释

乐观锁:通过在数据库表中添加 version 字段,在更新数据时检查版本号是否一致,若不一致则表示数据已被其他事务修改,更新失败。

Redis 分布式锁:在高并发场景下,使用 Redis 分布式锁可以确保同一时间只有一个用户能够对某本图书进行操作,避免数据冲突。

通过以上优化,可以有效处理图书管理系统中的并发问题。

免责申明:本站内容大部分来自互联网,部分来源于个人原创。仅供个人学习使用,本站不承担任何法律责任。如有侵权请联系删除!