文章目录
抽奖系统技术挑战
go的优势
年会抽奖
第3章 系统设计和架构设计
3.1 前后端需求
后端管理需求
3.2 用户操作与业务流程
用户操作
奖品状态变化
抽奖业务流程
3.3 数据库设计
奖品表
优惠券表
抽奖记录表
黑名单表
用户每日次数表
3.4 缓存设计
如何设计和利用缓存
使用redis的地方
3.5 设计总结
总结
一、防止重复
利用redis分布式锁
用分布式锁,是为了防刷、防止同一个用户同一秒里面把购物车里的商品进行多次结算,防止前端代码出问题触发两次。
redis可以使用string数据结构来实现分布式锁。这里记录一些注意点:
setnx:利用setnx的特性,当key不存在时,可以设置成功;当key存在时,setnx返回nil。
设置expire:为了防止客户端获取到锁后忘记解锁,这里设置了锁的过期时间。并且由redis来保证setnx和expire是原子操作,否则可能出现在setnx后可能未设置expire的情况。
超时问题:由于设置了expire时间,当进程获取到锁的时间太长以至于超过在redis中设置的expire时间时,就不能保证互斥性。这里要仔细对比expire时间和处理时间,防止出现这种情况。
redis实现分布式锁
package redis
import (
"github.com/garyburd/redigo/redis"
)
const (
redisLockTimeout = 10 // 10 seconds
)
func Lock(key string) (isLock bool, err error) {
//从连接池中娶一个con链接,pool可以自己定义。
con := pool.Get()
defer con.Close()
//这里需要redis.String包一下,才能返回redis.ErrNil
_, err = redis.String(con.Do("set", key, 1, "ex", redisLockTimeout, "nx"))
if err != nil {
if err == redis.ErrNil {
err = nil
return
}
return
}
isLock = true
return
}
func Unlock(key string) (err error) {
con := pool.Get()
defer con.Close()
_, err = con.Do("del", key)
if err != nil {
return
}
return
}
二、减库存
利用Redis increment 的原子操作,保证库存数安全
先查询redis中是否有库存信息,如果没有就去数据库查,这样就可以减少访问数据库的次数。
获取到后把数值填入redis,以商品id为key,数量为value。
注意要设置序列化方式为StringRedisSerializer,不然不能把value做加减操作。
还需要设置redis对应这个key的超时时间,以防所有商品库存数据都在redis中。
比较下单数量的大小,如果够就做后续逻辑。
执行redis客户端的increment,参数为负数,则做减法。因为redis是单线程处理,并且因为increment让key对应的value 减少后返回的是修改后的值。
有的人会不做第一步查询直接减,其实这样不太好,因为当库存为1时,很多做减3,或者减30情况,其实都是不够,这样就白减。
扣减数据库的库存,这个时候就不需要再select查询,直接乐观锁update,把库存字段值减1 。
做完扣库存就在订单系统做下单。