商品秒杀的本质以及Golang实现解决方案(分布式)

769 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

源码:

github.com/CocaineCong…

3. 分布式

3.1 环境搭建

  1. 搭建三主三从Cluster模式的Redis集群,配置Redisson
  2. 搭建ETCD集群

3.2 实现方法

3.2.1 基于Redisson的Redis分布式锁,正常

api/v2/with-redission?gid=1197

注意要用Redis Lock把整个事务提交都包住。这里仅仅使用了Redis分布式提供的锁功能,秒杀数据处理还是直接访问数据库来完成

func WithRedssionSecKillGoods(gid , userID int) error {
	g := strconv.Itoa(gid)
	uuid := getUuid(g)
	lockSuccess, err := cache.RedisClient.SetNX(g, uuid, time.Second*3).Result()
	if err != nil || !lockSuccess {
		fmt.Println("get lock fail", err)
		return errors.New("get lock fail")
	} else {
		fmt.Println("get lock success")
	}
	err = WithoutLockSecKillGoods(gid, userID)
	if err != nil {
		return err
	}
	value, _ := cache.RedisClient.Get(g).Result()
	if value == uuid { //compare value,if equal then del
		_, err := cache.RedisClient.Del(g).Result()
		if err != nil {
			fmt.Println("unlock fail")
			return nil
		} else {
			fmt.Println("unlock success")
		}
	}
	return nil
}

3.2.2 基于缓存的ETCD分布式锁,正常

api/v2/with-etcd?gid=1197

类似于之前使用BlockingQueue时编写了一个单例模式的工具类来全局使用的形式相同,注意这里也要用ETCD分布式锁把整个事务提交都包住。这里只用了ETCD的分布式锁功能,秒杀数据处理也是直接访问数据库来完成

func WithETCDSecKillGoods(gid, userID int) error {
	var conf = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"},
		DialTimeout: 5 * time.Second,
	}
	eMutex1 := &EtcdMutex{
		Conf: conf,
		Ttl:  10,
		Key:  "lock",
	}
	err := eMutex1.Lock()
	if err != nil {
		return err
	}
	err = WithoutLockSecKillGoods(gid, userID)
	eMutex1.UnLock()
	return err
}

3.2.3 Redis的List队列,正常

api/v2/with-redis-list?gid=1197

这里利用Redis分布式队列的方式是,在秒杀活动初始化阶段时有多少库存就在Redis的List中初始化多少个商品元素。 然后每有一个用户进行秒杀,就从List队列中取出一个商品元素分配给该用户。 同时将该用户信息存入到Redis的Set类型中,防止用户多次秒杀的情况。 在秒杀结束之后,在Redis中数据写入到数据库中进行保存。可参考下图:

func WithRedisListSecKillGoods(gid, userID int) error {
	g := strconv.Itoa(gid)
	u := strconv.Itoa(userID)
	if cache.RedisClient.Get(u + g).Val() == "" { // 这用户没有秒杀过
		cache.RedisClient.RPop(g)
		cache.RedisClient.Set(u+g, g, 3*time.Minute)
		cache.RedisClient.ZAdd(g, redis.Z{float64(time.Now().Unix()), userID})
	} else { // 这用户已经有记录了
		return errors.New("该用户已经抢过了")
	}
	return nil
}

3.2.4 Redis原子递减,正常

这里先将秒杀商品的库存数量,写入到redis中,利用redis的incr来实现原子递减。 假如有100件商品,这里相当于准备好了100个钥匙,有人没有抢到钥匙,就返回库存不够,有人抢到了钥匙,就进行下一步处理,先将秒杀订单的信息写入到redis中,等空闲下来后在写入到数据库中。这里其实与3.2.3差不多

其他

  1. 基于Redis的任务队列,订阅监听 (是将在前端进行秒杀的用户的信息传入到通道中,等待被消费。后端订阅监听这个通道,有秒杀用户信息传过来就进行消费处理,再将处理数据写入到数据库。)

  2. 基于MQ消息队列的分布式锁

改进:

  • 索引与SQL语句检查
  • 尽可能利用缓存
  • 利用MQ进行流量削峰
  • Nginx负载均衡
  • 读写分离与分表分库
  • CDN内容分发网络
  • 流量防刷和反爬虫