Laravel中用Observer事件致Redis队列异常问题

 3726

下面带大家分享记录关于Laravel Observer 导致 Redis 队列异常问题,希望对大家有所帮助!

1、业务逻辑

新建某个模型之后,利用 Observer 模型事件 Created 推入异步短信发送队列

App\Http\Controllers\UsersController

  1. public function store(User $user)
  2. {
  3.     \DB::beginTransaction();
  4.  
  5.     try{
  6.         $input = request()->validated();
  7.         $user->fill($input);
  8.         $user->save();
  9.         //do something......
  10.         //其他数据表操作
  11.  
  12.         \DB::commit();
  13.     } catch ($e \Exception) {
  14.         \DB::rollBack();
  15.     } 
  16. }

App\Observers\UserObserver

  1. class UserObserver{
  2.     public function created (User $user)
  3.     {
  4.         dispatch(new SmsQueue($user));
  5.     }
  6. }


2、发现异常

业务部门反馈偶尔有用户收取不到短信通知,我便查看日志发现偶尔有错误异常:No query results for model [App\Models\User]. 表示找不到对应的模型

我敲不应该啊,我是在创建模型之后再进行队列调用……,遂对业务代码再进行仔细核查猜测应该是受事务影响。

验证猜想:

  1. public function store(User $user)
  2. {
  3.     \DB::beginTransaction();
  4.  
  5.     try{
  6.         $input = request()->validated();
  7.         $user->fill($input);
  8.         $user->save();
  9.         //do something......
  10.         //其他数据表操作
  11.  
  12.         sleep(3); //三秒之后再提交事务            
  13.         \DB::commit();
  14.     } catch ($e \Exception) {
  15.         \DB::rollBack();
  16.     } 
  17. }

果然在等待三秒之后提交队列异常已是 100% 触发。


3、原因分析

$user->save() 这个方法创建数据成功,便会一并触发调度器,将模型事件一一执行。

在事件中推送模型至队列中,而队列进程在不间断消费队列中数据。

在大部分情况下 do something 处理速度正常的话,队列进程将会照常运行。

如果在 do something 阶段偶尔出现延迟,造成事务还未 commit 而队列已经开始消费新模型;故引发上述错误。

然后我在搜索 Github Issues 记录时,发现此问题在 2015 年的一个 Issue 已经有人提出,而在 Laravel 8.X 中终于新增了对事务模型事件的支持;learnku.com/docs/laravel/8.x/eloqu... ,在社区文档似乎并没有找到相关说明~

由于我的版本是 6.x 所以用不了这个新特性[哭唧唧]~~


4、解决异常

1. 修改 MySQL 事务隔离级别(不推荐)

这里涉及到 MySQL 的事务隔离级别,InnoDB 引擎的默认隔离级别是 REPEATABLE READ,关于各个级别的区别可以在 官方文档 找到。

将隔离级别切换到 READ UNCOMMITTED 即可解决此问题,但是为了防止出现更大的问题我劝你别用这种方式~

2. 增加事件监听

查看源码得知在事务完成之后,会调用对应的事件,所以只需增加对事件的监听即可。


Laravel中用Observer事件致Redis队列异常问题


新增类 App\Handlers\TransactionHandler

  1. class TransactionHandler{
  2.     public array $handlers;
  3.  
  4.     public function __construct()
  5.     {
  6.         $this->handlers = [];
  7.     }
  8.  
  9.     public function add(\Closure $handler)
  10.     {
  11.         $this->handlers[] = $handler;
  12.     }
  13.  
  14.     public function run()
  15.     {
  16.         foreach ($this->handlers as $handler) {
  17.             $handler();
  18.         }
  19.         $this->handlers = [];
  20.     }
  21. }

创建辅助函数 app/helpers.php

  1. if (! function_exists('after_transaction')) {
  2.     /*
  3.      * 事务结束之后再进行操作
  4.      * */
  5.     function after_transaction(Closure $job)
  6.     {
  7.         app()->singletonIf(\App\Handlers\TransactionHandler::class, function (){
  8.             return new \App\Handlers\TransactionHandler();
  9.         });
  10.         app(\App\Handlers\TransactionHandler::class)->add($job);
  11.     }
  12. }

创建监听 App\Listeners\TransactionListener

  1. namespace App\Listeners;use App\Handlers\TransactionHandler;class TransactionListener{
  2.     public function handle()
  3.     {
  4.         app(TransactionHandler::class)->run();
  5.     }
  6. }

绑定监听 App\Providers\EventServiceProvider

  1. namespace App\Providers;
  2. use App\Listeners\TransactionListener;
  3. use Illuminate\Database\Events\TransactionCommitted;
  4. use Illuminate\Database\Events\TransactionRolledBack;
  5. use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
  6.  
  7. class EventServiceProvider extends ServiceProvider{
  8.     /**
  9.      * The event listener mappings for the application.
  10.      *
  11.      * @var array
  12.      */
  13.     protected $listen = [
  14.         TransactionCommitted::class => [
  15.             TransactionListener::class
  16.         ],
  17.         TransactionRolledBack::class => [
  18.             TransactionListener::class
  19.         ]
  20.     ];
  21. }

更改调用方式 App\Observers\UserObserver

  1. class UserObserver{
  2.     public function created (User $user)
  3.     {
  4.         after_transaction(function() use ($user) {
  5.             dispatch(new SmsQueue($user));
  6.         });
  7.     }
  8. }

OK,一个优雅的解决方式就完成了~~



本文网址:https://www.zztuku.com/index.php/detail-10293.html
站长图库 - Laravel中用Observer事件致Redis队列异常问题
申明:本文转载于《learnku》,如有侵犯,请 联系我们 删除。

评论(0)条

您还没有登录,请 登录 后发表评论!

提示:请勿发布广告垃圾评论,否则封号处理!!

    编辑推荐