业务系统如何正确实现防重名功能

2024-04-21 05:08

本文主要是介绍业务系统如何正确实现防重名功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

常见但是错误的实现

在业务系统中防重名是一个非常普遍的需求,例如用户注册时不允许用户名重复、已登录用户不可以在自己的账号范围内创建同名的某种实体等。很多人在实现的时候都是简单的先判断名字是否重复,如果没有则执行插入操作,如下:

    public void register(User user) {// 判断是否重复 (1)if (userMapper.selectExist(user.getUsername())) {// 报错throw new XXXException();}// 执行注册 (2)userMapper.insertSelective(xxxModel);}

实际上这个逻辑存在严重的漏洞,在"高并发"下会出现用户名重复的情况,而且数据库操作执行的越慢,重复的概率就会越高。我们先来分析下原因:

假设有A、B两个用户都使用了同一个叫作"bruce"的用户名,且恰好"同时"点击了注册按钮,请求也恰好"同时"到达服务器。这时候你的web服务会启动两条线程来处理这两个请求,于是A, B同时执行数据库查询 (1) 来判断是否重名,因为此时表中还不存在username列为bruce的行,所以A, B都会顺利通过检查,然后各自插入一条记录,从而导致用户名重复。

我相信在相当多的系统中都会有类似的代码,当然不一定是用户注册的场景。在多数情况下问题并不会被暴露出来,因为你得承认,绝大多数人做的系统QPS都没有"那么"高,毕竟中国BBATJ就这么几家,小公司还是占大多数的。但是站在技术的角度上讲这是一种错误,应该避免。

不能把问题扔给数据库

如何解决?在数据库层面,最简单的就是给username字段加唯一约束,这是最笨的方式,但唯一约束会严重制约扩展能力(读写分离、分库分表),同时降低系统的健壮性(通过数据库报错来反馈业务错误信息)。除了唯一索引,还可以锁表,但是我觉得任何一个有责任心的程序员都不会这样实现。存储过程?No, 不存在的。所以这种业务问题根本就不是DB层应该考虑的事情。

问题抛给业务层。现在的场景是要保证同一时刻,对于同一个用户名只能有一个人注册成功。我们首先想到的是加锁了,synchronized?可以,但是只适用于单例模式,相信在互联网公司没人会这么干,所以我们需要分布式锁。此外还要要注意你不能简单粗暴的把整个注册逻辑用一把锁给锁了,那样会严重降低系统的吞吐量,不可取。想在问题被拆分成了两个,如何实现分布式锁 + 如何尽量减少锁的粒度。

正确实现Redis锁 && 降低锁粒度

实现分布式锁最简单的方式就是用Redis了,但是Redis锁坑很多,一不注意就会有漏洞。正确实现的第一个问题是,判断key是否存在跟创建key(锁)要原子性完成。这个原因非常简单,用户注册就能说明问题。Redis中要想原子性有三种方式,一是利用Redis的单线程特性想办法用一条指令完成任务,如setnx;二是使用Pipeline连续执行多条命令;三是执行lua脚本。显然第一种方法最简单。在Jedis中,我们可以直接使用5个参数的set()方法,如:

// null 表示已经存在
String ret = cmd.set(key, val, 'nx', 'ex', expireSecond);

这个方法能同时完成:

  • 判断key是否存在,存在返回null
  • 创建key, 设置成指定值
  • 指定过期时间

那么问题来了,key和value应该设置成啥才好?这里是要分场景的。

如果你是在做一把全局锁让多个实例都来争抢的话,那么key应该设置成一个固定的值,value设置成一个随机数(nonce)。在释放锁的时候,程序要先判断当前redis里key的value值是不是自己之前设置的nonce, 如果是才能安全的删除key,否则就啥也不干。这是为了防止某个实例因为业务逻辑处理超时而导致意外的释放了别人的锁。我们来分析一下,假如key的过期时间是5s, 然后你在某次执行过程中用了10s才完成处理,那么此时锁有可能会被被人给抢走。如果你在10s后只是简单的删除key来释放锁的话就会把别人的锁给释放了,对吧。

另一种场景是防重,比如用户注册。这种情况下不需要nonce, 即value可以忽略,但是key要设为 前缀 + 用户标识 的组合。这样做的好处是除非多个人都用了同一个用户名来注册,否则他们之间不会产生竞争,从而降低了锁的粒度。

这两种场景应该足够用了。但是这里还有其他要考虑的问题: **如果Redis不可用了怎么办?**你是让程序报错等待人工干预,还是进行“降级”忽略问题?这就要看业务需求了,比如用户注册,如果系统的QPS没那么高的话,redis锁失效可以允许继续注册,因为出现重名的概率很低。无论如何一定要想着redis有连接失败的可能性,这很重要。

End

这篇关于业务系统如何正确实现防重名功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/922220

相关文章

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

OpenCV图像形态学的实现

《OpenCV图像形态学的实现》本文主要介绍了OpenCV图像形态学的实现,包括腐蚀、膨胀、开运算、闭运算、梯度运算、顶帽运算和黑帽运算,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起... 目录一、图像形态学简介二、腐蚀(Erosion)1. 原理2. OpenCV 实现三、膨胀China编程(

通过Spring层面进行事务回滚的实现

《通过Spring层面进行事务回滚的实现》本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下... 目录声明式事务回滚:1. 基础注解配置2. 指定回滚异常类型3. ​不回滚特殊场景编程式事务回滚:1. ​使用 TransactionT

Android实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

SpringBatch数据写入实现

《SpringBatch数据写入实现》SpringBatch通过ItemWriter接口及其丰富的实现,提供了强大的数据写入能力,本文主要介绍了SpringBatch数据写入实现,具有一定的参考价值,... 目录python引言一、ItemWriter核心概念二、数据库写入实现三、文件写入实现四、多目标写入

Android Studio 配置国内镜像源的实现步骤

《AndroidStudio配置国内镜像源的实现步骤》本文主要介绍了AndroidStudio配置国内镜像源的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、修改 hosts,解决 SDK 下载失败的问题二、修改 gradle 地址,解决 gradle

SpringSecurity JWT基于令牌的无状态认证实现

《SpringSecurityJWT基于令牌的无状态认证实现》SpringSecurity中实现基于JWT的无状态认证是一种常见的做法,本文就来介绍一下SpringSecurityJWT基于令牌的无... 目录引言一、JWT基本原理与结构二、Spring Security JWT依赖配置三、JWT令牌生成与

SpringBoot实现微信小程序支付功能

《SpringBoot实现微信小程序支付功能》小程序支付功能已成为众多应用的核心需求之一,本文主要介绍了SpringBoot实现微信小程序支付功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作... 目录一、引言二、准备工作(一)微信支付商户平台配置(二)Spring Boot项目搭建(三)配置文件