服务端通过nosql加锁解决并发问题实战

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: 服务端通过nosql加锁解决并发问题实战

并发问题的解决思路


  1. 首先想到的就是加锁,比如mysql加锁,解决并发问题。这类文章很多,就不赘述了。
  2. 再者是nosql加锁,解决mysql加锁解决不了的情况,毕竟nosql的性能是比mysql等关系型数据库快很多的,nosql加锁的并发级别比mysql等关系数据库的要高。


nosql加锁的案例


  1. 我的业务场景是每天第一次登陆APP发优惠券
  2. 我在业务代码里已经添加了是否存在的判断,如果今天已经给同一用户在同一时间下发了相同来源的优惠券则不重复下发
  3. 因为业务需要是相同类型相同来源的优惠券可以给同一个人在同一时间下发多张,我们就不能用MySQL的唯一索引了
  4. 我们在【2】中提到的判断,只能对非并发情况的请求起到限制作用,并发请求是不起作用的。
  5. 这时候就可以使用nosql加锁的思路,比如使用redis锁:
  1. 在业务开始时加锁
  2. 执行业务代码
  3. 业务执行完毕释放锁
  1. 原理:nosql加锁的并发级别比mysql等关系数据库的要高。


示例代码


未加锁的代码


//每天第一次登陆发放预约券
public static function everydayTriggerCoupon($userid)
{
    $userVip = UserVip::getByUserid($userid, 'type');
    if ($userVip['type'] == VipInfo::TYPE_USER_NORMAL) {
        //非会员发放3张预约券
        if (!self::checkCouponExist($userid, CouponInfo::PROP_COUPON_SUBSCRIBE_APPOINTMENT_ID, CouponInfo::TYPE_COUPON_SOURCE_DAILY_BENEFITS, Utility::getTomorrowTimestamp())) {
            self::saveCoupon($userid, CouponInfo::PROP_COUPON_SUBSCRIBE_APPOINTMENT_ID, CouponInfo::TYPE_COUPON_SOURCE_DAILY_BENEFITS, Utility::getTomorrowTimestamp(), CouponInfo::COUNT_COUPON_EVERYDAY_TRIGGER_NOT_VIP);
        }
    } else {
        //会员发放5张预约券
        if (!self::checkCouponExist($userid, CouponInfo::PROP_COUPON_SUBSCRIBE_APPOINTMENT_ID, CouponInfo::TYPE_COUPON_SOURCE_DAILY_VIP_BENEFITS, Utility::getTomorrowTimestamp())) {
            self::saveCoupon($userid, CouponInfo::PROP_COUPON_SUBSCRIBE_APPOINTMENT_ID, CouponInfo::TYPE_COUPON_SOURCE_DAILY_VIP_BENEFITS, Utility::getTomorrowTimestamp(), CouponInfo::COUNT_COUPON_EVERYDAY_TRIGGER_VIP);
        }
        //会员额外发放1张超级喜欢券
        if (!self::checkCouponExist($userid, CouponInfo::PROP_COUPON_SUPER_LIKE_ID, CouponInfo::TYPE_COUPON_SOURCE_DAILY_VIP_BENEFITS, Utility::getTomorrowTimestamp())) {
            self::saveCoupon($userid, CouponInfo::PROP_COUPON_SUPER_LIKE_ID, CouponInfo::TYPE_COUPON_SOURCE_DAILY_VIP_BENEFITS, Utility::getTomorrowTimestamp());
        }
    }
}


添加锁的代码


  1. 结合业务需求,每天第一次登陆发放优惠券,即当天有效,所以在设置key时把当前的日期作为其中一个参数
  2. 因为我使用的是Laravel框架,Cache集成了Redis,Cache的add方法和Redis的setnx方法类似,都有存在key值返回0,不存在key值返回1的特性。
  3. 结合我们的业务场景,我在add()的时候添加了24小时的过期时间;当然如果你们的场景对过期时间没有要求,可以在业务代码执行完毕之后主动释放缓存资源。

注意:一定要记得在合适的场景下释放锁资源,避免资源滥用


//每天第一次登陆发放预约券
public static function everydayTriggerCoupon($userid)
{
    $userVip = UserVip::getByUserid($userid, 'type');
    //解决并发问题 加redis锁
    $cacheKey = CacheKey::getCacheKey(CacheKey::TYPE_USER_REWARD_EVERYDAY_TRIGGER, $userid . '_' . date('Y-m-d'));
    $res = Cache::add($cacheKey, true, 60 * 60 * 24); //add()不存在添加返回true; 存在返回false
    if ($res) {
        和未加锁的核心代码一致
        .
        .
        .
    }
}


异步任务加nosql锁的注意问题


如果我们的异步任务出现了并发问题,也可以考虑通过引入nosql锁的方式解决。

这时候要注意:如果异步任务是单线程的,是按顺序执行的,要在调用异步任务的方法外加锁,不要加到异步任务方法里,避免造成阻塞。


示例代码


$lockKey = CacheKey::getCacheKey(CacheKey::TYPE_JOB_USER_EDIT_AVOID_CONCURRENT, $userid);
$lockRes = Cache::add($lockKey, true, 60);
if ($lockRes) {
UserInfoEdit::dispatch([
    'userid' => $userid,
    .
    .
    .
])->onQueue(QueueNameBuilder::getName(QueueNameBuilder::USER_INFO_EDIT));
//释放redis锁
    Cache::forget($lockKey);
}


相关文章
|
3月前
|
SQL 关系型数据库 数据库
Python SQLAlchemy模块:从入门到实战的数据库操作指南
免费提供Python+PyCharm编程环境,结合SQLAlchemy ORM框架详解数据库开发。涵盖连接配置、模型定义、CRUD操作、事务控制及Alembic迁移工具,以电商订单系统为例,深入讲解高并发场景下的性能优化与最佳实践,助你高效构建数据驱动应用。
468 7
|
人工智能 关系型数据库 OLAP
聚光灯已就位!阿里云瑶池数据库邀你征战Cursor首届实战征文大赛
阿里云AnalyticDB携手Cursor中文社区,正式发起首届实战征文大赛!我们诚邀开发者融合Cursor的智能编程能力与AnalyticDB PostgreSQL提供的Supabase服务进行项目开发,让优秀项目被专家看见、被机遇拥抱!
|
7月前
|
关系型数据库 MySQL 数据库连接
Django数据库配置避坑指南:从初始化到生产环境的实战优化
本文介绍了Django数据库配置与初始化实战,涵盖MySQL等主流数据库的配置方法及常见问题处理。内容包括数据库连接设置、驱动安装、配置检查、数据表生成、初始数据导入导出,并提供真实项目部署场景的操作步骤与示例代码,适用于开发、测试及生产环境搭建。
349 1
|
8月前
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
7月前
|
SQL 数据建模 关系型数据库
别光知道存数据库了,数据建模才是王道!(入门指南+实战代码)
别光知道存数据库了,数据建模才是王道!(入门指南+实战代码)
1310 4
|
3月前
|
人工智能 运维 NoSQL
云栖大会|AI浪潮下的NoSQL演进:下一代数据库的破局之道
AI浪潮下的NoSQL演进:下一代数据库的破局之道
|
4月前
|
存储 数据库 开发者
Python SQLite模块:轻量级数据库的实战指南
本文深入讲解Python内置sqlite3模块的实战应用,涵盖数据库连接、CRUD操作、事务管理、性能优化及高级特性,结合完整案例,助你快速掌握SQLite在小型项目中的高效使用,是Python开发者必备的轻量级数据库指南。
406 0
|
7月前
|
存储 NoSQL 搜索推荐
NoSQL数据库分类概览
以上就是我们的NoSQL数据库奇幻之旅。每一种NoSQL数据库都有自己独特的魅力和专长,择选合适的数据库,就像在魔法世界中挑选最适合自己的魔杖,使你的数据管理变得更加高效和神奇。在当今数据驱动的时代,懂得这些数据库的秘密,就掌握了处理各种数据挑战的关键。
399 61
|
存储 NoSQL 关系型数据库
【赵渝强老师】什么是NoSQL数据库?
随着大数据技术的兴起,NoSQL数据库(Not Only SQL)得到广泛应用。它不局限于二维表结构,允许数据冗余。常见的NoSQL数据库包括Redis、MongoDB和HBase。Redis是基于内存的高性能数据库,采用单线程模型和多路复用I/O,支持高效的数据结构。MongoDB使用BSON格式存储文档,查询语言强大,类似关系型数据库。HBase基于HDFS,适合数据分析,采用列式存储,支持灵活的列族设计。视频讲解及更多内容见下文。
605 79
|
10月前
|
SQL 存储 关系型数据库
数据库的行级锁与表锁?
表锁: 不会出现死锁,发生锁的冲突几率高,并发性低。 存储引擎在进行SQL数据读写请求前,会对涉及到的表进行加锁。 其中锁分为共享读锁和独占写锁:读锁会阻塞写,写锁会阻塞读和写。 行级锁: 会出现死锁,发生锁的冲突几率低,并发性高。 InnoDB引擎支持行锁,与Oracle不同,MySQL的行锁是通过索引加载的,也就是说,行锁是加在索引响应的行上的,要是对应的SQL语句没有走索引,则会全表扫描,行锁则无法实现,取而代之的是表锁,此时其它事务无法对当前表进行更新或插入操作。 行级锁注意事项: 行级锁必须有索引才能实现,否则会自动锁全表,那就不是行锁了。 两个事务不能锁同一个索引。 in