大家都知道在天猫、京东、苏宁等等电商网站上有很多秒杀活动,例如在某一个时刻抢购一个原价1999现在秒杀价只要999的手机时,会迎来一个用户请求的高峰期,可能会有几十万几百万的并发量,来抢这个手机,在高并发的情形下会对数据库服务器或者是文件服务器应用服务器造成巨大的压力,严重时说不定就宕机了,另一个问题是,秒杀的东西都是有量的,例如一款手机只有10台的量秒杀,那么,在高并发的情况下,成千上万条数据更新数据库(例如10台的量被人抢一台就会在数据集某些记录下 减1),那次这个时候的先后顺序是很乱的,很容易出现10台的量,抢到的人就不止10个这种严重的问题。那么,以后所说的问题我们该如何去解决呢? 接下来我所分享的技术就可以拿来处理以上的问题: 分布式锁 和 任务队列。
代码实现:
/**
* 加锁
* @param string name 锁的标识名
* @param int timeout 循环获取锁的等待超时时间,在此时间内会一直尝试获取锁直到超时,为0表示失败后直接返回不等待
* @param int expire 当前锁的最大生存时间(秒),必须大于0,如果超过生存时间锁仍未被释放,则系统会自动强制释放
* @param int waitIntervalUs 获取锁失败后挂起再试的时间间隔(微秒)
* @return bool true成功 false失败
*/
func (rlock *RedisLock) Lock(name string, timeout int, expire int, waitIntervalUs int) bool {
if name == "" {
return false
}
//获取当前时间
nowtime := time.Now().Unix()
//获取锁失败时的等待超时时刻
timeoutAt := nowtime + int64(timeout)
//锁的最大生存时刻
expireAt := nowtime + int64(expire)
//redis存放时的key
redisKey := "kx:" + name
for {
//将rediskey的最大生存时刻存到redis里,过了这个时刻该锁会被自动释放
result, err := rlock.Redis_conn.Do("SETNX", redisKey, expire)
if err != nil {
return false
}
if result != 0 {
//设置key的失效时间
rlock.Redis_conn.Do("EXPIRE", redisKey, expire)
//将锁标志放到lockedNames数组里
rlock.LockedNames[name] = expireAt
return true
}
//以秒为单位,返回给定key的剩余生存时间
ttl, _ := redis.Int64(rlock.Redis_conn.Do("TTL", redisKey))
//ttl小于0 表示key上没有设置生存时间(key是不会不存在的,因为前面setnx会自动创建)
//如果出现这种状况,那就是进程的某个实例setnx成功后 crash 导致紧跟着的expire没有被调用
//这时可以直接设置expire并把锁纳为己用
if ttl < 0 {
rlock.Redis_conn.Do("EXPIRE", redisKey, expire)
rlock.LockedNames[name] = expireAt
return true
}
/*****循环请求锁部分*****/
//如果没设置锁失败的等待时间 或者 已超过最大等待时间了,那就退出
if timeout <= 0 || timeoutAt < time.Now().UnixNano()/1e6 {
break
}
time.Sleep(time.Duration(waitIntervalUs))
}
return false
}
/**
* 解锁
* @param string name 锁的标识名
*/
func (rlock *RedisLock) Unlock(name string) bool {
//先判断是否存在此锁
if _, ok := rlock.LockedNames[name]; ok {
//删除锁
redisKey := "kx:" + name
_, err := rlock.Redis_conn.Do("DEL", redisKey)
if err != nil {
return false
}
//清掉lockedNames里的锁标志
delete(rlock.LockedNames, name)
return true
}
return false
}
完整代码https://github.com/langzhiwu/rdslock
新手学习,欢迎拍砖!
//ttl小于0 表示key上没有设置生存时间(key是不会不存在的,因为前面setnx会自动创建)
//如果出现这种状况,那就是进程的某个实例setnx成功后 crash 导致紧跟着的expire没有被调用
//这时可以直接设置expire并把锁纳为己用
if ttl < 0 {
rlock.Redis_conn.Do("EXPIRE", redisKey, expire)
rlock.LockedNames[name] = expireAt
return true
}
这种情况下可能会有多个进程同时执行到这个条件里面,导致这些进程都获取到了锁
if timeout <= 0 || timeoutAt < time.Now().UnixNano()/1e6 {
break
}
time.Sleep(time.Duration(waitIntervalUs))
`time.Now().UnixNano()/1e6`的单位是ms,而timeoutAt单位是秒;
`time.Duration(waitIntervalUs)`是把waitIntervalUs当纳秒用了
#2
更多评论
这个问题使用GETSET处理的,在github已处理。感谢反馈!
![微信图片_20180628115713.png](https://static.studygolang.com/180628/d37e6f040415d69844c1ba7fad6bb71b.png)
#3