Golang Go语言中高并发情况下如何保证金额加减的一致性

Golang Go语言中高并发情况下如何保证金额加减的一致性
因为要做流水, 所以得先查询余额再做加减
语言 golang, 数据库框架 GORM

23 回复

开启事务, 按照需求选择乐观或者悲观并发策略. 或者考虑 EventSourcing

更多关于Golang Go语言中高并发情况下如何保证金额加减的一致性的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


并发量是多大?

那要看你是在单节点实现高并发还是多节点实现高并发,如果都交给数据库那就开事务,用数据库去解决一致性问题,但是性能不容乐观,如果要多节点实现高并发,那么分布式事务的 CAP 问题,要么用现成的分布式事务方案,要么自己实现 2 阶段提交( 2PC )或者 3 阶段提交( 3PC ),或者更近一步实现 Paxos 算法。这个问题很多现成方案,真没有必要自己再想当然的搞一套了,因为 CAP 三个问题你只能搞定其中两个,别自出机杼了

单机, 只是考虑到这种情况, 怕偶尔出现一次数据加减不成功, 因为我在本机模拟了二个协程, 数据库却只加了一次

单节点的, 简单一点最好, 实在麻烦的话我先用 go 的互斥锁用着, 发现这个也能满足需求, 就是性能差了点

单节点的你要靠谱还不如直接用数据库事务好了,你用 go 的互斥锁那是硬生生的把法拉利开成了拖拉机

我没用事务也是直接 Golang 互斥锁,因为我们业务关系所以一直都是 3k 人左右具体看学校新生多不多…然后我没人分配了一把锁…好处是他不用二次刷卡,不用返回错误,就相当于加个高级点的行锁…而且基本也就等个 100-200ms 就好了,内存之前单独写了个 main 测试过好像整体占用就 17M 左右…坏处是没有骚操作,在其他人面前没得吹…但是稳就行…另可慢几百毫秒也不可错一分

最简单就是交给数据库去处理事务

数据库事务设计出来就是处理这样的业务场景的,为啥大家第一反应想不到用它呢?

嗯不错,不要 over engineering ,不要过早优化,够用就行,关键要稳

加上版本号,读出来后带上金额一起更新。如果 miss 就是有新版本,再次读。那还可以把这个版本号写入日志。

update account set money += 5, version = 2 where id=1 and version = 1

如果在代码逻辑中先读出余额再加减,那么数据库事务的隔离级别就很关键,而且可能需要分布式锁。如果直接在 sql 更新语句中加减,那就不需要事务。

有 redis 集群吗? 用 lua 来做,数据库异步落地

update table set price = price - #{x} where price >= #{x}

对待钱的问题,宁肯慢也要保证不错

悲观锁:

在一个事务内,查询余额使用 SELECT … FOR UPDATE 获取锁,这样其他事务既无法读取,也无法写入,但是要注意死锁的情况,顺序编排要一致。

乐观锁:

在一个事务内,更新余额使用 UPDATE account SET …, version = + 1 WHERE id = AND version = ,这样更新失败的话返回的影响行数为 0 ,可以凭此判断是否成功。

版本号实现乐观锁,自旋一定次数(比如 10 次),超过十次未更新成功就是失败

涉及金额的强一致不应该用 Redis 来保证的

并发问题就是加锁,没别的

加锁…最简单

分布式锁

update money_table set money = money + 100 where id = 1 returning money - 100;
用 PostgreSQL 的 returning,可以一句 sql 完成查询和修改

Mysql 也有类似功能,不过比较麻烦.

乐观锁 ➕ 自旋重试

在Golang中处理高并发情况下的金额加减操作,确保一致性是至关重要的。以下是几种常见的方法:

  1. 使用互斥锁(Mutex): 在高并发场景下,最直接的方法是使用sync.Mutex来保护对金额的读写操作。通过在修改金额时加锁,确保同一时间只有一个goroutine能够访问和修改金额,从而避免数据竞争。

  2. 使用原子操作(Atomic Operations): Golang的sync/atomic包提供了原子操作,如AddInt64CompareAndSwapInt64等,这些操作是线程安全的,可以在不使用锁的情况下实现金额的加减。对于金额这种整数类型的字段,原子操作是一个高效且安全的选择。

  3. 使用通道(Channel): 利用Golang中的通道机制,可以将对金额的修改请求发送到通道中,由一个单独的goroutine来处理这些请求。这种方法可以确保金额的修改是顺序进行的,避免了并发问题。但需要注意的是,这种方法可能会引入额外的延迟。

  4. 数据库事务: 如果金额数据存储在数据库中,使用数据库事务来保证一致性也是一个有效的方法。通过事务的ACID特性(原子性、一致性、隔离性、持久性),可以确保在并发情况下金额的加减操作是安全的。

综上所述,选择哪种方法取决于具体的应用场景和需求。在实际开发中,可以根据性能要求、代码复杂度等因素综合考虑,选择最适合的方案。

回到顶部