工作中的并发问题处理方法总结
![]() 一切都有条不紊的进行着,直到设备A的注册打破了这默契的宁静…… 设备A注册完成后,我突然发现,数据库设备表中,新增了 两条 记录,而且是 两条一模一样 的记录! 我开始以为自己眼花了…… 仔细一看,确确实实是新增了两条,而且连设备唯一标识(划横线,后面要考)和创建时间都一模一样! 看着屏幕,我陷入了沉思…… 为什么会有两条呢? 在我的注册逻辑里,落库之前会先查一遍数据库该设备是否已存在,如果存在就更新已有的,不存在才新增。 所以我百思不得其解,按这个逻辑,第二条一模一样的数据是哪来的? 2. 真相背后的并发请求 经过一番排查及思考,我发现问题可能就出在注册请求上。 设备A在向云端发送http注册请求时,可能会同时发送多个相同请求。 云服务器当时部署在多台Docker容器上,通过查看日志发现,有两台容器同时收到了来自设备A的注册请求。 由此,我推测: 设备A同时发送了两个注册请求,这两个请求分别在同一时间打到了云端的不同容器上,按照我的注册逻辑,这两个容器接收到注册请求后,同时去查询了数据库的设备表,这时候设备表里还没有设备A的记录,所以两台容器都执行了新增的操作,因为速度很快,所以这两条新增记录在 精确到秒 的创建时间上,并没有体现出差别。 3. 并发新增的延伸 既然并发的新增操作会产生问题,那么并发的更新操作是否会有问题呢? 解决方法 解决并发新增 1. 数据库唯一索引(UNIQUE INDEX) 在数据库建表的时候,通过对具有唯一性的字段(比如上述的设备唯一标识)创建唯一索引,或对组合起来后就具备唯一性的几个字段创建联合唯一索引。 这样在并发新增时,只要有一个新增成功,其他的新增操作都会因为数据库抛出的异常(java.sql.SQLIntegrityConstraintViolationException)而失败,我们只需要处理好新增失败的情况就行了。 注意唯一索引的字段需要非空,因为字段值为空时会导致唯一索引约束失效 2. java分布式锁 通过在程序中引入分布式锁,在进行新增操作前需要先获取分布式锁,获取成功才能继续,否则新增失败。 这样也能解决并发插入带来的数据重复问题,只是引入分布式锁的同时也增加了系统的复杂性,如果要落库的数据上有唯一性字段的话,还是推荐采用唯一索引的方法。 在构建分布式锁的过程中,我们需要用到Redis,这里以设备注册时使用的分布式锁为例。 分布式锁简单问答: Q:锁究竟是什么? A:锁实质上是存储在Redis中,基于特定规则生成的一个字符串(示例里是固定前缀+设备唯一标识),相当于每个设备注册的时候都有自己对应的一把锁,因为锁只有一把,即使该设备有多个相同的注册请求同时到来,也只有其中获取到那把锁的那一个请求能成功走下去。 Q:什么是获取锁? A:同一个设备,基于相同的规则生成的字符串(后文以Key代称该字符串)总是相同的,在执行新增操作前,先去Redis中查询这个Key是否存在,如果已存在,就意味着获取锁失败;如果不存在,就将这个Key现存到Redis中,如果存储成功,表示获取锁成功,如果存储失败,还是意味着获取锁失败。 Q:锁是怎么工作的? A:前面说过,同一个设备,基于相同的规则生成的字符串(Key)总是相同的,在当前线程执行新增操作前,先在Redis中查询这个Key是否存在,如果已存在,表示此时已经有别的线程成功获取了锁,正在做当前线程想要做的新增操作,则当前线程不需要进行后续操作了(是的,你是多余的) 当这个Key不存在时,表示现在还没有其他线程获得锁,则当前线程可以继续进行下一步操作——在Redis中赶紧存入这个Key,当这个Key存储失败时,意味着有别的线程抢先存入了Key成功获取了锁,当前线程晚了一步,想做的工作被别人抢先做了(当前线程可以退下了) (编辑:应用网_丽江站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |