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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 服务端通过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);
}


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6天前
|
JSON NoSQL MongoDB
理解Nosql数据库的mongodb
【5月更文挑战第5天】MongoDB是2009年发布的一款通用型NoSQL数据库,结合了关系模型和NoSQL的优点,适用于各种现代应用。其特点包括图形界面、数据服务、云基础设施集成(AWS, Azure, Google Cloud)。它具备全面的查询能力、ACID事务、可调整的一致性保证,并有多语言驱动及工具,可在任何地方运行。
66 4
|
3天前
|
NoSQL Shell MongoDB
NoSQL数据使用指令和引擎连接数据库实例
【5月更文挑战第8天】本文介绍了MongoDB的本地使用和常用操作,包括通过mongo shell连接数据库、显示数据库和集合,以及副本集设置。最后提到了MongoDB的日志功能和顶点集的使用,如capped collection的创建和管理。
46 3
|
3天前
|
存储 缓存 NoSQL
NoSQL缓存数据库的使用场景实例和命令速查表
【5月更文挑战第8天】Redis 是一个内存数据结构服务,用 C 编写,支持五种数据结构,不仅限于键值对。它用于缓存、消息队列、订阅/发布系统等,提供持久化、主从复制和集群支持。了解其核心数据结构和应用场景是有效利用 Redis 的关键。
54 3
NoSQL缓存数据库的使用场景实例和命令速查表
|
6天前
|
NoSQL atlas MongoDB
Nosql数据库MongoDB的使用场景
【5月更文挑战第5天】 MongoDB是全球性的多云数据库,可在私有、公共和混合云中运行,提供高可用性、扩展性和合规性。 安全特性包括认证、授权、审计、网络隔离和加密。可提供跨云操作、可视化工具、搜索功能和数据湖支持,适用于现代应用开发,包括边缘数据处理。
36 1
|
6天前
|
SQL 数据库
数据库SQL语言实战(六)
本次实战的重点就在于对表格本身的一些处理,包括复制表格、修改表格结构、修改表格数据
|
6天前
|
SQL Oracle 关系型数据库
数据库SQL语言实战(五)(数据库系统概念第三章练习题)
本文的SQL语言适用的是Oracle数据库与mySQL可能存在略微不同
|
6天前
|
SQL Oracle 关系型数据库
数据库SQL语言实战(四)(数据库系统概念第三章练习题)
本文的SQL语言适用的是Oracle数据库与mySQL可能存在略微不同
数据库SQL语言实战(四)(数据库系统概念第三章练习题)
|
6天前
|
SQL Oracle 关系型数据库
数据库SQL语言实战(三)
本篇文章重点在于SQL中的各种删除操作
|
6天前
|
存储 NoSQL 关系型数据库
MongoDB非关系型数据库实战
【5月更文挑战第6天】MongoDB,流行的NoSQL数据库,以其灵活的数据模型和高性能备受青睐。本文介绍了MongoDB的基础,包括文档型数据库特性、安装配置、数据操作。通过电商订单管理的实战案例,展示了MongoDB在处理复杂数据结构和大规模数据时的优势,适用于电商、游戏、视频直播等场景。MongoDB的索引、全文搜索和地理空间功能进一步增强了其实用性。注意性能优化和扩展性以确保系统稳定性和可靠性。
|
6天前
|
缓存 关系型数据库 MySQL
MySQL数据库性能优化实战
【4月更文挑战第30天】本文探讨了MySQL性能优化实战技巧,包括硬件与配置优化(如使用SSD、增加内存和调整配置参数)、索引优化(创建合适索引、使用复合索引及定期维护)、查询优化(避免全表扫描、减少JOIN和使用LIMIT)、分区与分片(表分区和数据库分片),以及使用缓存、定期清理数据库和监控诊断。通过这些方法,可以提升数据库性能和响应速度。