golang多线程嵌入式账户事务数据库插件transaction的使用
Golang多线程嵌入式账户事务数据库插件transaction的使用
概述
transaction是一个嵌入式的事务性账户数据库,支持多线程模式运行。它只处理整数类型数据,如果要处理百分数(如美元中的美分),可以将所有数值乘以100。账户最大限制为2^63(9,223,372,036,854,775,807)。
核心概念
单元(Unit)
- 单元可以是客户、公司等实体
- 一个单元可以有多个账户(账户用字符串标识)
- 如果单元的任何账户余额不为零,则不能删除该单元
- 如果向不存在的账户存入金额,将自动创建该账户
账户(Account)
- 账户用于记录资金、股票等
- 账户必须属于某个单元
- 一个账户只能属于一个单元
- 每个账户只有一个余额
- 余额仅以整数计算
使用示例
基本用法
package main
import (
"fmt"
tn "github.com/claygod/transaction"
)
func main() {
tr := tn.New()
tr.Start()
// 添加单元
switch res := tr.AddUnit(123); res {
case tn.Ok:
fmt.Println("Done! Unit created")
case tn.ErrCodeCoreCatch:
fmt.Println("Not obtained permission")
case tn.ErrCodeUnitExist:
fmt.Println("Such a unit already exists")
default:
fmt.Println("Unknown error")
}
// 事务操作 - 借记
switch res := tr.Begin().Debit(123, "USD", 5).End(); res {
case tn.Ok:
fmt.Println("Done! Money added")
case tn.ErrCodeUnitNotExist:
fmt.Println("Unit not exist")
case tn.ErrCodeTransactionCatch:
fmt.Println("Account not catch")
case tn.ErrCodeTransactionDebit:
fmt.Println("Such a unit already exists")
default:
fmt.Println("Unknown error")
}
// 保存数据
switch res := tr.Save("./test.tdb"); res {
case tn.Ok:
fmt.Println("Done! Data saved to file")
case tn.ErrCodeCoreStop:
fmt.Println("Unable to stop app")
case tn.ErrCodeSaveCreateFile:
fmt.Println("Could not create file")
default:
fmt.Println("Unknown error")
}
}
创建/删除单元
tr := transaction.New()
tr.Start()
tr.AddUnit(123) // 创建单元
tr.DelUnit(123) // 删除单元
账户操作
// 贷记账户
t.Begin().Credit(id, "USD", 1).End()
// 借记账户
t.Begin().Debit(id, "USD", 1).End()
转账示例
// 从idFrom账户转账1美元到idTo账户
t.Begin().
Credit(idFrom, "USD", 1).
Debit(idTo, "USD", 1).
End()
购买/销售示例
// 示例:用10美元购买2股"Apple"股票
tr.Begin().
Credit(buyerId, "USD", 10).Debit(sellerId, "USD", 10).
Credit(sellerId, "APPLE", 2).Debit(buyerId, "APPLE", 2).
End()
保存/加载数据
// 保存
tr := New()
tr.Start()
tr.AddUnit(123)
tr.Begin().Debit(123, "USD", 7).End()
tr.Save(path)
// 加载
tr := New()
tr.Start()
tr.Load(path)
tr.Begin().Credit(123, "USD", 7).End()
API方法
- New() - 创建新实例
- Load(“path”) - 从文件加载数据
- Start() - 启动数据库
- AddUnit(ID) - 添加单元
- Begin().Debit(ID, key, amount).End() - 借记操作
- Begin().Credit(ID, key, amount).End() - 贷记操作
- TotalUnit(ID) - 获取单元总额
- TotalAccount(ID, key) - 获取账户余额
- DelUnit(ID) - 删除单元
- Stop() - 停止数据库
- Save(“path”) - 保存数据到文件
性能基准(i7-6700T)
- BenchmarkTotalUnitSequence-8: 419 ns/op
- BenchmarkTotalUnitParallel-8: 185 ns/op
- BenchmarkCreditSequence-8: 311 ns/op
- BenchmarkCreditParallel-8: 175 ns/op
- BenchmarkDebitSequence-8: 314 ns/op
- BenchmarkDebitParallel-8: 178 ns/op
- BenchmarkTransferSequence-8: 417 ns/op
- BenchmarkTransferParallel-8: 277 ns/op
- BenchmarkBuySequence-8: 644 ns/op
- BenchmarkBuyParallel-8: 354 ns/op
注意事项
- 所有账户操作(借记/贷记)必须包含在事务中
- 并行请求到同一账户不会导致该账户余额的错误更改
- 单元不能删除,除非其所有账户余额为零
这个库非常适合需要高并发账户事务处理的场景,如金融系统、交易平台等。
更多关于golang多线程嵌入式账户事务数据库插件transaction的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于golang多线程嵌入式账户事务数据库插件transaction的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang多线程嵌入式账户事务数据库插件Transaction使用指南
在Golang中实现多线程嵌入式账户事务数据库,可以使用一些优秀的嵌入式数据库如BoltDB、Badger或SQLite,结合事务机制来保证数据一致性。下面我将详细介绍如何使用这些数据库的事务功能。
1. BoltDB事务使用示例
BoltDB是一个纯Go实现的键值存储数据库,支持完全可序列化的事务。
package main
import (
"fmt"
"log"
"sync"
"go.etcd.io/bbolt"
)
func main() {
// 打开数据库
db, err := bbolt.Open("accounts.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 初始化账户
err = db.Update(func(tx *bbolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte("accounts"))
return err
})
if err != nil {
log.Fatal(err)
}
// 设置初始余额
initAccounts(db)
var wg sync.WaitGroup
// 模拟多线程转账
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
transferMoney(db, "account1", "account2", 10)
fmt.Printf("Goroutine %d completed transfer\n", id)
}(i)
}
wg.Wait()
// 查看最终余额
checkBalances(db)
}
func initAccounts(db *bbolt.DB) {
db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("accounts"))
b.Put([]byte("account1"), []byte("1000")) // 账户1初始余额1000
b.Put([]byte("account2"), []byte("500")) // 账户2初始余额500
return nil
})
}
func transferMoney(db *bbolt.DB, from, to string, amount int) {
for {
err := db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("accounts"))
// 读取账户余额
fromBalance := parseInt(b.Get([]byte(from)))
toBalance := parseInt(b.Get([]byte(to)))
// 检查余额是否足够
if fromBalance < amount {
return fmt.Errorf("insufficient funds")
}
// 更新余额
fromBalance -= amount
toBalance += amount
// 写回数据库
err := b.Put([]byte(from), []byte(fmt.Sprintf("%d", fromBalance)))
if err != nil {
return err
}
err = b.Put([]byte(to), []byte(fmt.Sprintf("%d", toBalance)))
if err != nil {
return err
}
return nil
})
if err == nil {
break // 事务成功完成
}
// 如果是冲突错误,可以重试
if err == bbolt.ErrTxNotWritable {
continue
}
log.Printf("Transfer failed: %v", err)
break
}
}
func checkBalances(db *bbolt.DB) {
db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("accounts"))
fmt.Printf("Account1 balance: %s\n", b.Get([]byte("account1")))
fmt.Printf("Account2 balance: %s\n", b.Get([]byte("account2")))
return nil
})
}
func parseInt(b []byte) int {
var i int
fmt.Sscanf(string(b), "%d", &i)
return i
}
2. BadgerDB事务使用示例
BadgerDB是另一个高性能的键值存储数据库,适合需要快速读写和事务支持的场景。
package main
import (
"fmt"
"log"
"sync"
"github.com/dgraph-io/badger/v3"
)
func main() {
// 打开数据库
opts := badger.DefaultOptions("").WithInMemory(true)
db, err := badger.Open(opts)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 初始化账户
err = initAccounts(db)
if err != nil {
log.Fatal(err)
}
var wg sync.WaitGroup
// 模拟多线程转账
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
err := transferMoney(db, "account1", "account2", 10)
if err != nil {
log.Printf("Goroutine %d error: %v", id, err)
} else {
fmt.Printf("Goroutine %d completed transfer\n", id)
}
}(i)
}
wg.Wait()
// 查看最终余额
checkBalances(db)
}
func initAccounts(db *badger.DB) error {
txn := db.NewTransaction(true)
defer txn.Discard()
err := txn.Set([]byte("account1"), []byte("1000"))
if err != nil {
return err
}
err = txn.Set([]byte("account2"), []byte("500"))
if err != nil {
return err
}
return txn.Commit()
}
func transferMoney(db *badger.DB, from, to string, amount int) error {
for {
err := db.Update(func(txn *badger.Txn) error {
// 读取账户余额
fromItem, err := txn.Get([]byte(from))
if err != nil {
return err
}
fromBalance, err := fromItem.ValueCopy(nil)
if err != nil {
return err
}
toItem, err := txn.Get([]byte(to))
if err != nil && err != badger.ErrKeyNotFound {
return err
}
var toBalance []byte
if err == badger.ErrKeyNotFound {
toBalance = []byte("0")
} else {
toBalance, err = toItem.ValueCopy(nil)
if err != nil {
return err
}
}
// 转换并检查余额
fromInt := parseInt(fromBalance)
toInt := parseInt(toBalance)
if fromInt < amount {
return fmt.Errorf("insufficient funds")
}
// 更新余额
fromInt -= amount
toInt += amount
// 写回数据库
err = txn.Set([]byte(from), []byte(fmt.Sprintf("%d", fromInt)))
if err != nil {
return err
}
err = txn.Set([]byte(to), []byte(fmt.Sprintf("%d", toInt)))
if err != nil {
return err
}
return nil
})
if err == nil {
return nil // 事务成功完成
}
if err == badger.ErrConflict {
continue // 冲突,重试
}
return err // 其他错误
}
}
func checkBalances(db *badger.DB) {
err := db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte("account1"))
if err != nil {
return err
}
val, err := item.ValueCopy(nil)
if err != nil {
return err
}
fmt.Printf("Account1 balance: %s\n", val)
item, err = txn.Get([]byte("account2"))
if err != nil {
return err
}
val, err = item.ValueCopy(nil)
if err != nil {
return err
}
fmt.Printf("Account2 balance: %s\n", val)
return nil
})
if err != nil {
log.Printf("Error checking balances: %v", err)
}
}
func parseInt(b []byte) int {
var i int
fmt.Sscanf(string(b), "%d", &i)
return i
}
事务使用要点
- 原子性:事务中的所有操作要么全部成功,要么全部失败
- 一致性:数据库从一个一致状态转换到另一个一致状态
- 隔离性:并发事务之间互不干扰
- 持久性:事务完成后,结果永久保存
在多线程环境下使用事务数据库时,需要注意:
- 合理设计重试机制处理事务冲突
- 避免长事务,减少锁持有时间
- 考虑使用读写分离提高并发性能
- 对于高并发场景,可以适当使用批处理操作
以上示例展示了如何在Golang中使用嵌入式数据库的事务功能来实现账户转账操作,保证在多线程环境下的数据一致性。