golang 高并发数据库库存同步处理

方法一(对数据库的读写操作加锁)

一(DAO层加行锁:读写锁)

package main

import (
    "sync"
)
//1、多个读之间不存在互斥关系
//2、写操作之间都是互斥的,并且写操作与读操作之间也都是互斥的
type idMutex map[string] *sync.RWMutex

var myIdMutex idMutex
func init() {
    myIdMutex=make(map[string] *sync.RWMutex)
}
//读锁定
func (this *idMutex)RLock(id string){
    m,ok:=(*this)[id]
    if !ok {
        m=new(sync.RWMutex)
        (*this)[id]=m
    }
    m.RLock()
}
//读解锁
func (this *idMutex)RUnlock(id string){
    m,ok:=(*this)[id]
    if !ok {
        return
    }
    m.RUnlock()
}
//写操作锁定
func (this *idMutex)Lock(id string){
    m,ok:=(*this)[id]
    if !ok {
        m=new(sync.RWMutex)
        (*this)[id]=m
    }
    m.Lock() //写操作锁定
}
//写操作解锁
func (this *idMutex)Unlock(id string) {
    m,ok:=(*this)[id]
    if !ok {
        return
    }
    m.Unlock()//写操作解锁
}
type Accout struct {
    Id string
}
//进行读的操作
func (this *Accout) Reed() {
    myIdMutex.RLock(this.Id)
    defer myIdMutex.RUnlock(this.Id)
}
//进行写的操作
func (this *Accout) Write() {
    myIdMutex.Lock(this.Id) //写操作锁定
    defer myIdMutex.Unlock(this.Id)  //写操作解锁
}
func main() {
    acout:=Accout{Id:"798456"}
    acout.Reed()
    acout.Write()
}

一(对象加锁) 将读写的方法封装,并且添加锁

type Accout struct {
    flag sync.Mutex            //sync.Mutex类型
}
//进行读的操作
func (a *Accout) Reed(n int) {  //读
    a.flag.Lock()            //锁上
    defer a.flag.Unlock()    //在方法运行完之后解开
}
//进行写的操作
func (a *Accout) Write(n int) {  //读
    a.flag.Lock()            //锁上
    defer a.flag.Unlock()    //在方法运行完之后解开
}

方法二(直接对数据库进行操作)

原理

数据库使用InnoDB

   InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。

   InnoDB实现了以下两种类型的行锁。

  • 共享锁(s):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
  • 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。
    另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。
  • 意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
  • 意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

   如果一个事务请求的锁模式与当前的锁兼容,InnoDB就请求的锁授予该事务;反之,如果两者两者不兼容,该事务就要等待锁释放。

   意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及及数据集加排他锁(X)(允许获取排它锁的事物进行操作,其他事物处于阻塞状态);对于普通SELECT语句,InnoDB不会任何锁;事务可以通过以下语句显示给记录集加共享锁或排锁。

共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE

代码

package main
import(
    "database/sql"
    _"github.com/go-sql-driver/mysql"
    "log"
    "time"
    "math/rand"
)
// 连接池大小
var MAX_POOL_SIZE = 20
var dbPoll chan *sql.DB

const (
    user="root"
    pass="root"
    db="school"

)
func putDB(db *sql.DB) {
    // 基于函数和接口间互不信任原则,这里再判断一次,养成这个好习惯哦
    if dbPoll == nil {
        dbPoll = make(chan *sql.DB, MAX_POOL_SIZE)
    }
    if len(dbPoll) >= MAX_POOL_SIZE {
        db.Close()
        return
    }
    dbPoll <- db
}
func initDB() {
    // 缓冲机制,相当于消息队列
    if len(dbPoll) == 0 {
        // 如果长度为0,就定义一个redis.Conn类型长度为MAX_POOL_SIZE的channel
        dbPoll = make(chan *sql.DB, MAX_POOL_SIZE)
        go func() {
            for i := 0; i < MAX_POOL_SIZE/2; i++ {
                db,err:=sql.Open("mysql",user+":"+pass+"@tcp(localhost:3306)/"+db+"?charset=utf8")
                if err!=nil {
                    log.Println(err)
                }
                putDB(db)
            }
        } ()
    }
}
func GetDB()  *sql.DB {
    //如果为空就初始化或者长度为零
    if dbPoll == nil||len(dbPoll) == 0{
        initDB()
    }
    return <- dbPoll
}
func main(){
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    for i:=0;i<10 ;i++  {
        go changeCount(r.Intn(50))
        go changeCount(r.Intn(50))
        go changeCount(r.Intn(50))
        go changeCount(r.Intn(50))
    }
    time.Sleep(3*time.Second)
}
func changeCount(num int )  {
    db:=GetDB()
    tx, err := db.Begin()//打开事物
    defer tx.Commit()//事物提交
    //意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
    //意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
    //意向锁是InnoDB自动加的,不需用户干预。
    res,_ := tx.Exec("UPDATE product  set count=count-? WHERE Id=1 AND count>=?  ",num,num)
    RowsAffected, err := res.RowsAffected()
    if err != nil {
        log.Println("res.RowsAffected==================Err")
    }
    if RowsAffected>0 {
        addToOrder()
        log.Println(time.Now(),"抢购成功==================",num)
    }else {
        log.Println(time.Now(),"抢购失败==================",num)
    }
}
//添加到订单等操作
func addToOrder()  {

}

方法三(中间使用redis进行缓存)

原理可以参考奔跑的Man这篇博客
或者我复制他的Redis多并发问题

import (
    "github.com/garyburd/redigo/redis"
    "fmt"
    "time"
    "strconv"
    "runtime"
)

var Address = "127.0.0.1:6379"
var Network = "tcp"
func GetRedis()  redis.Conn  {
    c, err := redis.Dial(Network, Address)
    if err != nil {
        return GetRedis()
    }
    return c
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    for i:=0;i<100;i++  {
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
        go do()
    }
    time.Sleep(time.Second*60*10)
}
func do()  {
    cnn:=GetRedis()
    defer   cnn.Close()
    redisLock("lock.foo",cnn,20,doFunc,"致远")

}
//lockKey锁的名称
//cnn       redis.Conn
//deadTime      锁默认消亡时间
//doFunc        参数名称
//param     方法参数
func redisLock(lockKey string,cnn redis.Conn,deadTime int,doFunc func(interface{}),param interface{})  {
    setnxTime:=time.Now().UTC().UnixNano()
    ex,err:=cnn.Do("SETNX",lockKey,setnxTime+int64(deadTime))
    if err==nil {
        if ex==int64(0) {
            //fmt.Println("存在锁:下来判断锁是否过期了")
            lock2,err:=cnn.Do("GET",lockKey)
            if lock2==nil {
                //fmt.Println("lock2=======为空ex",ex,ex==int64(0))
                redisLock(lockKey ,cnn ,deadTime ,doFunc ,param )
                return
            }
            if err!=nil {
                redisLock(lockKey ,cnn ,deadTime ,doFunc ,param )
                return
            }
            getTime, err :=strconv.ParseInt(string(lock2.([]uint8)), 10, 64)
            if getTime>setnxTime {
                //锁未过期
                //fmt.Println("锁没有过期:继续等吧")
                redisLock(lockKey ,cnn ,deadTime ,doFunc ,param )
                return
            }else {
                //锁已经过期
                time.Sleep(time.Millisecond*time.Duration(deadTime))//线程休眠
                getsettime:=time.Now().UTC().UnixNano()
                lock3,err:=cnn.Do("GETSET",lockKey,getsettime)
                if lock3==nil {
                    //fmt.Println("lock3=======为空")
                    redisLock(lockKey ,cnn ,deadTime ,doFunc ,param )
                    return
                }
                getSetTime, err :=strconv.ParseInt(string(lock3.([]uint8)), 10, 64)
                if err!=nil {
                    //fmt.Println("出问题了:去继续等吧")
                    redisLock(lockKey ,cnn ,deadTime ,doFunc ,param )
                    return
                }
                if getSetTime==getTime {//如果更改前的时间和已经过期的时间相同
                    //获得锁直接操作数据
                    //fmt.Println("锁过期:处理了死锁,可以直接操作数据")
                    doFunc(param)
                    cnn.Do("DEL",lockKey)//删除锁
                    return
                }else{//更改前的时间和已经过期的时间不同
                    //fmt.Println("判断后:没有死锁,继续等吧")
                    redisLock(lockKey ,cnn ,deadTime ,doFunc ,param )
                    return
                }
            }
        }else{
            //fmt.Println("不存在锁:可以操作数据")
            doFunc(param)
            cnn.Do("DEL",lockKey)//删除锁
            return
        }
    }else {
        redisLock(lockKey ,cnn ,deadTime ,doFunc ,param )
        return
    }
}
var count=0
func doFunc(str interface{})  {
    count+=1
    fmt.Println("操作数据中.............============================",count,str)
    return
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容