当前位置:网站首页>Go implements distributed locks
Go implements distributed locks
2022-06-29 03:33:00 【. fried eggs with tomatoes】
brief introduction
This paper takes inventory deduction as an example , Implement process lock separately ;mysql The pessimistic lock of ; Optimistic lock and redis Distributed locks for
surface
CREATE TABLE `stocks` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`goods` varchar(20) DEFAULT NULL COMMENT ' goods id',
`stocks` int(11) DEFAULT NULL COMMENT ' stock ',
`version` int(11) DEFAULT NULL COMMENT ' Optimism lock ',
PRIMARY KEY (`id`),
KEY `idx_stocks_goods` (`goods`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

unlocked
service
package service
import (
context "context"
"go-locks/no-lock/db"
"go-locks/no-lock/model"
"go-locks/no-lock/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
type Server struct{
}
func (s Server) SellStock(ctx context.Context, request *proto.StockRequest) (*emptypb.Empty, error) {
tx := db.DB.Begin()
for _, info := range request.StockInfos {
var stock model.Stock
if result := tx.Where(&model.Stock{
Goods: info.GoodsId}).First(&stock); result.RowsAffected == 0 {
return nil, status.Error(codes.NotFound, " Product information does not exist ")
}
if stock.Stocks < info.Num {
// Insufficient inventory Roll back the transaction
tx.Rollback()
return nil, status.Error(codes.ResourceExhausted, " Insufficient inventory ")
}
stock.Stocks -= info.Num
tx.Save(stock)
}
tx.Commit()
return &emptypb.Empty{
}, nil
}
client
package main
import (
"context"
"go-locks/no-lock/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"sync"
)
var client proto.StockClient
func main() {
conn, err := grpc.Dial("127.0.0.1:8088", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
client = proto.NewStockClient(conn)
var wg sync.WaitGroup
wg.Add(20)
for i := 0; i < 20; i++ {
go TestSellStock(&wg)
}
wg.Wait()
}
func TestSellStock(wg *sync.WaitGroup) {
defer wg.Done()
_, err := client.SellStock(context.Background(), &proto.StockRequest{
StockInfos: []*proto.StockInfo{
{
GoodsId: "123456",
Num: 1,
},
},
})
if err != nil {
panic(err)
}
}
test

Turn on 20gorouinte De deduction 123456 Inventory of ; Under normal circumstances 123456 Our inventory should be surplus 480 Pieces of , But because we didn't lock it , Resulting in inventory remaining 485 Pieces of . This situation is absolutely unacceptable in the real scene
Process lock
service
package service
import (
context "context"
"go-locks/process-lock/db"
"go-locks/process-lock/model"
"go-locks/process-lock/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"sync"
)
type Server struct{
}
var mutex sync.Mutex
func (s Server) SellStock(ctx context.Context, request *proto.StockRequest) (*emptypb.Empty, error) {
// Lock
mutex.Lock()
tx := db.DB.Begin()
for _, info := range request.StockInfos {
var stock model.Stock
if result := tx.Where(&model.Stock{
Goods: info.GoodsId}).First(&stock); result.RowsAffected == 0 {
return nil, status.Error(codes.NotFound, " Product information does not exist ")
}
if stock.Stocks < info.Num {
// Insufficient inventory Roll back the transaction
tx.Rollback()
return nil, status.Error(codes.ResourceExhausted, " Insufficient inventory ")
}
stock.Stocks -= info.Num
tx.Save(stock)
}
tx.Commit()
// Unlock
mutex.Unlock()
return &emptypb.Empty{
}, nil
}
client
package main
import (
"context"
"go-locks/process-lock/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"sync"
)
var client proto.StockClient
func main() {
conn, err := grpc.Dial("127.0.0.1:8088", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
client = proto.NewStockClient(conn)
var wg sync.WaitGroup
wg.Add(20)
for i := 0; i < 20; i++ {
go TestSellStock(&wg)
}
wg.Wait()
}
func TestSellStock(wg *sync.WaitGroup) {
defer wg.Done()
_, err := client.SellStock(context.Background(), &proto.StockRequest{
StockInfos: []*proto.StockInfo{
{
GoodsId: "123457",
Num: 1,
},
},
})
if err != nil {
panic(err)
}
}
test

From the results alone, there is no problem , But there are still problems :
- The lock used here , It's the whole block of locked code , No matter which commodity comes in, it will have to wait for the lock to be released before it can be obtained and executed , There are serious performance problems
- The lock used here , Is a process level lock , yes go Language provided locks , But in real scenarios, they are deployed by multiple instances , In a multi instance scenario , There will still be problems when there is no lock
mysql Pessimistic locking
Pessimistic locking Always assume the worst , Every time I go to get the data, I think others will modify it , So every time I get the data, I lock it , So people who want to take this data will block it until it gets the lock
service
package service
import (
context "context"
"go-locks/pessimistic-lock/db"
"go-locks/pessimistic-lock/model"
"go-locks/pessimistic-lock/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"gorm.io/gorm/clause"
)
type Server struct{
}
func (s Server) SellStock(ctx context.Context, request *proto.StockRequest) (*emptypb.Empty, error) {
tx := db.DB.Begin()
for _, info := range request.StockInfos {
var stock model.Stock
// adopt for update Statements for mysql The pessimistic lock of
if result := tx.Clauses(clause.Locking{
Strength: "UPDATE"}).Where(&model.Stock{
Goods: info.GoodsId}).First(&stock); result.RowsAffected == 0 {
return nil, status.Error(codes.NotFound, " Product information does not exist ")
}
if stock.Stocks < info.Num {
// Insufficient inventory Roll back the transaction
tx.Rollback()
return nil, status.Error(codes.ResourceExhausted, " Insufficient inventory ")
}
stock.Stocks -= info.Num
tx.Save(stock)
}
tx.Commit()
return &emptypb.Empty{
}, nil
}
client
package main
import (
"context"
"go-locks/pessimistic-lock/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"sync"
)
var client proto.StockClient
func main() {
conn, err := grpc.Dial("127.0.0.1:8088", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
client = proto.NewStockClient(conn)
var wg sync.WaitGroup
wg.Add(20)
for i := 0; i < 20; i++ {
go TestSellStock(&wg)
}
wg.Wait()
}
func TestSellStock(wg *sync.WaitGroup) {
defer wg.Done()
_, err := client.SellStock(context.Background(), &proto.StockRequest{
StockInfos: []*proto.StockInfo{
{
GoodsId: "123458",
Num: 1,
},
},
})
if err != nil {
panic(err)
}
}
test

mysql Optimism lock
Optimism lock seeing the name of a thing one thinks of its function , Is very optimistic , Every time I go to get the data, I think other people won't modify it , So it won't lock , But in the process of updating, we will judge whether other people have updated this data during this period , You can use mechanisms like version numbers . Optimistic lock is suitable for multi read applications , This can improve throughput ,
service
package service
import (
context "context"
"go-locks/optimistic-lock/db"
"go-locks/optimistic-lock/model"
"go-locks/optimistic-lock/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"log"
)
type Server struct{
}
func (s Server) SellStock(ctx context.Context, request *proto.StockRequest) (*emptypb.Empty, error) {
tx := db.DB.Begin()
for _, info := range request.StockInfos {
var stock model.Stock
for {
if result := db.DB.Where(&model.Stock{
Goods: info.GoodsId}).First(&stock); result.RowsAffected == 0 {
return nil, status.Error(codes.NotFound, " Product information does not exist ")
}
if stock.Stocks < info.Num {
// Insufficient inventory Roll back the transaction
tx.Rollback()
return nil, status.Error(codes.ResourceExhausted, " Insufficient inventory ")
}
stock.Stocks -= info.Num
if result := tx.Model(&model.Stock{
}).Select("Stocks", "Version").
Where("goods = ? AND version = ?", info.GoodsId, stock.Version).Updates(&model.Stock{
Stocks: stock.Stocks, Version: stock.Version + 1}); result.RowsAffected == 0 {
// version Field conflict ; Deduction failed
log.Println(" Inventory deduction failed ; retry ")
} else {
// Inventory deduction succeeded
log.Println(" Inventory deduction succeeded ")
break
}
}
}
tx.Commit()
return &emptypb.Empty{
}, nil
}
client
package main
import (
"context"
"go-locks/optimistic-lock/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"sync"
)
var client proto.StockClient
func main() {
conn, err := grpc.Dial("127.0.0.1:8088", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
client = proto.NewStockClient(conn)
var wg sync.WaitGroup
wg.Add(20)
for i := 0; i < 20; i++ {
go TestSellStock(&wg)
}
wg.Wait()
}
func TestSellStock(wg *sync.WaitGroup) {
defer wg.Done()
_, err := client.SellStock(context.Background(), &proto.StockRequest{
StockInfos: []*proto.StockInfo{
{
GoodsId: "123459",
Num: 1,
},
},
})
if err != nil {
panic(err)
}
}
test

redis Distributed lock
service
package service
import (
context "context"
"fmt"
"go-locks/redis-lock/db"
"go-locks/redis-lock/model"
"go-locks/redis-lock/proto"
"go-locks/redis-lock/redis"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
type Server struct{
}
func (s Server) SellStock(ctx context.Context, request *proto.StockRequest) (*emptypb.Empty, error) {
tx := db.DB.Begin()
for _, info := range request.StockInfos {
var stock model.Stock
// Use redis Distributed lock , Lock only the current product , Will not affect other goods
mutex := redis.Redsy.NewMutex(fmt.Sprintf("goods_%s", info.GoodsId))
if err := mutex.Lock(); err != nil {
return nil, status.Error(codes.Internal, " obtain redis Distributed lock exception ")
}
if result := tx.Where(&model.Stock{
Goods: info.GoodsId}).First(&stock); result.RowsAffected == 0 {
return nil, status.Error(codes.NotFound, " Product information does not exist ")
}
if stock.Stocks < info.Num {
// Insufficient inventory Roll back the transaction
tx.Rollback()
return nil, status.Error(codes.ResourceExhausted, " Insufficient inventory ")
}
stock.Stocks -= info.Num
tx.Save(stock)
if ok, err := mutex.Unlock(); !ok || err != nil {
return nil, status.Error(codes.Internal, " Release redis Distributed lock exception ")
}
}
tx.Commit()
return &emptypb.Empty{
}, nil
}
client
package main
import (
"context"
"go-locks/redis-lock/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"sync"
)
var client proto.StockClient
func main() {
conn, err := grpc.Dial("127.0.0.1:8088", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
client = proto.NewStockClient(conn)
var wg sync.WaitGroup
wg.Add(20)
for i := 0; i < 20; i++ {
go TestSellStock(&wg)
}
wg.Wait()
}
func TestSellStock(wg *sync.WaitGroup) {
defer wg.Done()
_, err := client.SellStock(context.Background(), &proto.StockRequest{
StockInfos: []*proto.StockInfo{
{
GoodsId: "123460",
Num: 1,
},
},
})
if err != nil {
panic(err)
}
}
test

summary
- unlocked : No lock can cause problems even in the case of a single machine , Not recommended
- Process lock : The process lock is safe only in the case of a single machine , There is a performance bottleneck
- mysql Pessimistic locking : Security in distributed environment . It is suitable for scenarios with frequent write operations , If there are a lot of read operations , Every time I read it, I lock it , This will increase a lot of lock overhead , Reduce the throughput of the system . In special cases, it will be upgraded to a table lock , Security in distributed environment , But there are still performance bottlenecks
- mysql Optimism lock : Security in distributed environment , It is more suitable for scenarios with frequent read operations , If a large number of write operations occur , The possibility of data conflicts increases , To ensure data consistency , The application layer needs to constantly retrieve data , This will add a lot of query operations , Reduce the throughput of the system .
- redis Distributed lock : Security in distributed environment , also redis It has good performance , And you can lock a single commodity , It will only block requests for the same product , Not all requests are blocked , Greatly increased throughput
边栏推荐
- 一个注解优雅的实现 接口数据脱敏
- 2022-2028 global CAE engineering service industry research and trend analysis report
- 88.(cesium篇)cesium聚合图
- 为什么信息化 ≠ 数字化?终于有人讲明白了
- Movement state change of monitoring device of Jerry's watch [chapter]
- ssm项目环境初步搭建
- 需求分析说明书和需求规格说明书
- Jerry's watch begins to move [chapter]
- 88.(cesium篇)cesium聚合图
- Sequence traversal of binary tree ii[one of sequence traversal methods - > recursive traversal + level]
猜你喜欢

FPGA (VIII) RTL code IV (basic circuit design 1)

Grafana入门教程

2022-2028 global pneumatic test probe industry survey and trend analysis report

19.03 容器的说明和简单应用例续

嵌入式开发如何进行源代码保密工作

88.(cesium篇)cesium聚合图

2D human posture estimation deeppose

【TcaplusDB知识库】TcaplusDB-tcapsvrmgr工具介绍(三)
![[tcaplusdb knowledge base] view tcapdir directory server](/img/b6/f3734dfb03ec789525636335457b99.png)
[tcaplusdb knowledge base] view tcapdir directory server

問題——adb shellerror: insufficient permissions for device: verify udev rules.
随机推荐
【TcaplusDB知识库】查看tcapdir目录服务器
Restore the binary search tree [simulate according to the meaning of the question - > find the problem - > analyze the problem - > see the bidding]
How to understand MySQL indexes?
Problème - Ajouter shellerror: permissions d'instrumentation pour le périphérique: vérifier les règles udev.
django model生成docx数据库设计文档
基于redis实现的扣减库存
DevOps笔记-05:IT行业中BA、SM、PO、PM、PD、Dev、Ops、QA都是什么角色
Web GIS 航拍实现的智慧园区数字孪生应用
【雲原生】這麼火,你不來了解下?
迅为i.MX8M开发板yocto系统使用Gstarwmr视频转换
Mobaihe box, ZTE box, Migu box, Huawei box, Huawei Yuehe box, Fiberhome box, Skyworth box, Tianyi box and other operators' box firmware collection and sharing
Jerry's monitoring alarm clock [chapter]
Access 500 error after modstart migrates the environment
Etcd教程 — 第六章 Etcd之核心API V3
【面试指南】AI算法面试
搭建nexus服务
Laravel, execute PHP artist migrate and report an error alter table `users`add unique `users_ email_ unique`(`email`))
[North Asia data recovery] data recovery case of ibm-ds3512 storage server RAID5 damaged data loss
Counter analysis of the parameter anti content in the backstage of the # yyds dry goods inventory # knife fight shop
图论的基本概念