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
}

你可能感兴趣的:(golang 高并发数据库库存同步处理)