当前位置: 首页 > Linux

面试官让我在5分钟内写出一个抢红包的程序,我半个小时告诉他原理!

时间:2023-04-06 18:54:05 Linux

文章每周持续更新。您的“三连冠”是对我最大的肯定。可以在微信搜索公众号“后端技术派”立即阅读(一般比博客更早更新一两篇文章)。抢了很多微信红包,也算是支持了公司的生意。微信支付融入生活,抢红包已经是一件很平常的事情。抢红包这个简单的动作,就是每次都向红包服务后台请求。在春节海量的服务请求下,其实是非常典型的高并发编程模型。后台开发程序员都有一个共识:实现一个功能容易,难的是在大量请求下提升服务性能。在程序员眼里,大家抢的不是红包,而是红包后台服务的锁!这里的锁不是我们日常生活中的锁。后台服务编程中锁的概念:一种实现多个进程或线程访问共享资源的互斥机制。今天和大家聊聊后台服务编程中的锁。业务模型为了便于说明,我们简化模型,约定抢红包服务为多线程服务。抢红包操作??包括以下三个步骤:查询数据库中的红包余额,扣除抢到的红包金额,更新红包余额到数据库,假设你发了100元的Money红包,10001秒内同时有人来抢(高并发),如果不加锁,是这样的:第一个人查余额得到100元,并在此基础上扣除他抢到的假设的2元一、准备步骤3更新到数据库。在第一个人更新之前,剩下的人查到的余额也是100,他们各自扣除自己抢到的金额,准备按照步骤3更新,结果最后的红包余额中只记录了最后一次更新的数据.很明显,可能会出现1000人都抢到红包,红包余额却一直没有发完,混乱不堪的情况。如何解决这个问题呢?就用我们上面提到的锁来解决。锁有哪些种类?有很多方法可以实现锁。下面是一些常见的分类。悲观锁把抢红包的三个步骤打包成一个整体,做一个互斥操作,“我抢之前不查余额,更新数据,会不准的”。也可以类比数据库事务来理解。一个事务必须具备以下四个属性,统称为ACID属性:原子性:一个事务是一个完整的操作。事务的步骤是不可分割的(原子的);要么all要么none一致性:当事务完成时,数据必须处于一致状态以任何方式影响其他交易持久性:交易完成后,它对数据库的修改被永久维护,交易日志可以维护悲观地认为,你每次去抢红包,其他人也必须抢到同时,所以你的线程在抢的时候一定要独占资源,其他线程需要阻塞挂掉,等你抢完了才可以进来抢。启动的线程不能做其他事情。鲁迅先生说过,浪费CPU资源就是浪费生命!而一旦你抢到红包释放了锁,其他等待的线程抢到资源,抢到就会恢复线程上下文。CPU不断切换线程上下文是对服务器资源的浪费。严重时,后续抢红包请求无法及时处理。我们需要想办法提高效率,所以有了乐观锁。乐观锁是对悲观锁的改进。乐观锁被认为是在没有竞争的情况下,乐观锁不会阻塞线程。一种实现乐观锁的方法是增加数据库中红包余额的版本号。初始版本号为0,抢红包后版本号加1后更新余额。只有当更新的版本号大于数据库中的版本号时,才认为是合法的,才应该更新;否则不会更新,线程也不会阻塞,以后可以重试,避免频繁的线程上下文切换。乐观锁在抢红包的第1、2步不做加锁判断,只在第3步对版本号做加锁判断,第一个人抢的是版本号为0的红包,第二个人也抢的是红包版本号为0的信封,第一个人更新红包余额,设置版本号为1,第二个人更新红包余额,设置版本号为1,当时发现余额版本号已经是1,更新失败。在二人更新失败后,线程没有被阻塞,继续处理其他的抢红包请求,对于二人的更新按照一定的策略(超时重试,限制重试次数)进行重试,类推其他请求,可以看出,乐观锁在锁失败时不会挂起线程等待,避免了线程上下文的频繁切换,提高了红包业务的处理性能。分布式锁中以上两种类型的锁都是基于更新数据库的。当大请求高并发时,频繁访问数据库,尤其是乐观锁重试,会对数据库造成很大的影响。实际生产环境应该尽量减少对数据库的访问。Redis是一个开源(BSD许可)的内存数据结构存储系统,可用作数据库、缓存和消息传递中间件。也可以使用redis实现分布式锁,与数据库交互两次:第一次获取红包余额,第二次更新红包状态。抢红包和进程中更新操作均在内存中进行,比数据库操作快几个数量级,显着提升业务并发性能。Redis分布式锁:利用Redis的SET操作将key-value键值对保存在内存中。加锁就是获取这个键值对的值,解锁就是删除这个键值对。分布式锁不会阻塞线程。这种分布式锁的实现这里就不多说了。可以参考我的另一篇文章公众号:redis分布式锁的3种实现分析,详细分析了几种分布式锁的Lock特性和优缺点。原创不易,看到这里动动手指,大家的“三通”是对我不断创作的最大支持,下篇见。你可以在微信搜索公众号“后端技术学院”,回复“资讯”,里面有我为你准备的各种编程学习资料。文章每周持续更新,我们下期再见!