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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 服务端通过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
相关文章
|
14天前
|
关系型数据库 数据库 数据安全/隐私保护
云数据库实战:基于阿里云RDS的Python应用开发与优化
在互联网时代,数据驱动的应用已成为企业竞争力的核心。阿里云RDS为开发者提供稳定高效的数据库托管服务,支持多种数据库引擎,具备自动化管理、高可用性和弹性扩展等优势。本文通过Python应用案例,从零开始搭建基于阿里云RDS的数据库应用,详细演示连接、CRUD操作及性能优化与安全管理实践,帮助读者快速上手并提升应用性能。
|
27天前
|
关系型数据库 MySQL 网络安全
如何排查和解决PHP连接数据库MYSQL失败写锁的问题
通过本文的介绍,您可以系统地了解如何排查和解决PHP连接MySQL数据库失败及写锁问题。通过检查配置、确保服务启动、调整防火墙设置和用户权限,以及识别和解决长时间运行的事务和死锁问题,可以有效地保障应用的稳定运行。
132 25
|
2月前
|
存储 缓存 监控
71.7万/秒到1.4万/秒!数据库查询优化实战
在现代计算机系统中,程序的局部性原理是优化性能的关键。通过利用时间局部性和空间局部性,可以显著提升数据访问效率,减少高开销的操作如内存访问和网络 I/O。本文将探讨如何利用空间局部性原理,在实际案例中减少数据库查询量,并通过详细的监控和优化措施确保系统的稳定性和高效性。
71.7万/秒到1.4万/秒!数据库查询优化实战
|
2月前
|
存储 NoSQL 关系型数据库
【赵渝强老师】什么是NoSQL数据库?
随着大数据技术的兴起,NoSQL数据库(Not Only SQL)得到广泛应用。它不局限于二维表结构,允许数据冗余。常见的NoSQL数据库包括Redis、MongoDB和HBase。Redis是基于内存的高性能数据库,采用单线程模型和多路复用I/O,支持高效的数据结构。MongoDB使用BSON格式存储文档,查询语言强大,类似关系型数据库。HBase基于HDFS,适合数据分析,采用列式存储,支持灵活的列族设计。视频讲解及更多内容见下文。
251 79
|
3月前
|
SQL 存储 关系型数据库
数据库的行级锁与表锁?
表锁:存储引擎在SQL数据读写请求前对涉及的表加锁,分共享读锁和独占写锁,读锁阻塞写,写锁阻塞读写,易发锁冲突,并发性低。行级锁:InnoDB支持,通过索引加锁,提高并发性,但可能引起死锁,需注意索引使用,适用于避免不可重复读场景。
75 21
|
4月前
|
SQL Oracle 关系型数据库
南大通用GBase 8s 数据库封锁与并发事务调度介绍
南大通用GBase 8s 数据库封锁与并发事务调度介绍
|
4月前
|
存储 缓存 NoSQL
常见的 NoSQL 数据库有哪些?
常见的 NoSQL 数据库有哪些?
223 59
|
4月前
|
存储 SQL 数据库
深入浅出后端开发之数据库优化实战
【10月更文挑战第35天】在软件开发的世界里,数据库性能直接关系到应用的响应速度和用户体验。本文将带你了解如何通过合理的索引设计、查询优化以及恰当的数据存储策略来提升数据库性能。我们将一起探索这些技巧背后的原理,并通过实际案例感受优化带来的显著效果。
84 4
|
4月前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第27天】本文深入探讨了MySQL的索引策略和查询性能调优技巧。通过介绍B-Tree索引、哈希索引和全文索引等不同类型,以及如何创建和维护索引,结合实战案例分析查询执行计划,帮助读者掌握提升查询性能的方法。定期优化索引和调整查询语句是提高数据库性能的关键。
758 1
|
4月前
|
监控 关系型数据库 MySQL
数据库优化:MySQL索引策略与查询性能调优实战
【10月更文挑战第26天】数据库作为现代应用系统的核心组件,其性能优化至关重要。本文主要探讨MySQL的索引策略与查询性能调优。通过合理创建索引(如B-Tree、复合索引)和优化查询语句(如使用EXPLAIN、优化分页查询),可以显著提升数据库的响应速度和稳定性。实践中还需定期审查慢查询日志,持续优化性能。
541 0

热门文章

最新文章