Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Golang Redis解决秒杀问题

基于Golang实现Redis分布式锁解决秒杀问题

作者:大杯无糖

这篇文章主要给大家介绍了使用Golang实现Redis分布式锁解决秒杀问题,文中有详细的代码示例供大家参考,具有一定的参考价值,需要的朋友可以参考下

先写一个脚本sql,插入2000个用户

INSERT INTO sys_users (mobile, password)
SELECT 
    numbers.n AS mobile,
    '$2a$10$zKQfSn/GCcR6MX4nHk3MsOMhJnI0qxN4MFdiufDMH2wzuTaR9G1sq' AS password
FROM 
    (
        SELECT ones.n + tens.n*10 + hundreds.n*100 + thousands.n*1000 + 1 AS n
        FROM 
            (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) ones
            CROSS JOIN (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) tens
            CROSS JOIN (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) hundreds
            CROSS JOIN (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) thousands
        ORDER BY n
    ) numbers
LIMIT 2000;

登录是通过2个字段,一个是mobile,一个是password,生成了mobile从1到2000,密码默认是123456

然后写一个单元测试,实现新注册的2000个用户登录,然后获取token

package system
import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"reflect"
	"runtime"
	"strings"
	"sync"
	"testing"
	"time"
)
var Global_client *http.Client
func GetGlobalClient() {
	client := &http.Client{
		Transport: &http.Transport{
			MaxIdleConns: 20, // 设置连接池大小为 200
		},
	}
	Global_client = client
}
func TestBaseApi_TokenNext(t *testing.T) {
	var wg sync.WaitGroup
	loginNum := 2000
	GetGlobalClient()
	s := make(chan string, loginNum)
	limit := make(chan int, 20000)
	//go prilimit(limit)
	go Show()
	for i := 1; i <= 2000000; i++ {
		mobile := fmt.Sprintf("%d", i)
		wg.Add(1)
		password := "123456"
		//向通道中发送值,如果满了500个,则会阻塞
		limit <- 1111
		go obtainToken(mobile, password, &wg, limit, s)
	}
	wg.Wait()
	//当数据都到了通道里面之后,我们可以关闭通道
	close(s)
	fmt.Println("通道的长度为:",len(s))
	file, err := os.OpenFile("E:\\Go\\goproject\\LearnExam\\sever\\token.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
	defer file.Close()
	for token := range s {
		if token == "" {
			continue
		}
		_, err = file.WriteString(token + "\n")
		if err != nil {
			return
		}
	}
}
func Show() {
	for {
		num := runtime.NumGoroutine()
		fmt.Printf("当前程序中的协程数量:%d\n", num)
		time.Sleep(1 * time.Second)
	}
}
func AppendStringToFile(filePath string, content string) error {
	file, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer file.Close()
	_, err = file.WriteString(content + "\n")
	if err != nil {
		return err
	}
	return nil
}
func obtainToken(mobile, password string, wg *sync.WaitGroup, limit chan int, s chan string) {
	defer wg.Done()
	type Body struct {
		Mobile   string `json:"mobile"`
		Password string `json:"password"`
	}
	b := Body{
		mobile, password,
	}
	bodymarshal, err := json.Marshal(&b)
	if err != nil {
		return
	}
	//再处理一下
	reqBody := strings.NewReader(string(bodymarshal))
	req, err := http.NewRequest("POST", "." +
		"", reqBody)
	if err != nil {
		fmt.Printf("Error creating request for user %s: %v\n", mobile, err)
		return
	}
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	resp, err := Global_client.Do(req)
	if err != nil {
		//fmt.Printf("Error sending request for user %s: %v\n", mobile, err)
		fmt.Printf("Error sending request for user %s: %+v\n", mobile, err)
		fmt.Println("反射:", reflect.TypeOf(err))
		fmt.Println("err是EOF,那resp是:",resp)
		return
	}
	//defer func(Body io.ReadCloser) {
	//	err = Body.Close()
	//	if err != nil {
	//
	//	}
	//}(resp.Body)
	body, err := ioutil.ReadAll(resp.Body) //把请求到的body转化成byte[]
	if err != nil {
		return
	}
	type Result struct {
		Code int `json:"code"`
		Data struct {
			Token string `json:"token"`
		} `json:"data"`
	}
	r := Result{}
	err = json.Unmarshal(body, &r)
	if err != nil {
		return
	}
	if r.Code == 0 {
		s <- r.Data.Token
		temp := <-limit
		fmt.Println("通道取值:", temp)
		fmt.Printf("Token obtained for user %s\n", mobile)
	} else {
		fmt.Printf("Failed to obtain token for user %s\n", mobile)
	}
}

我们使用有缓冲的通道和sync.WaitGroup信号量,来控制协程的数量,经过测试,发现limit,loginNum,影响到最后成功的结果,这其中的的原理我还暂时没有想清楚。limit为50,loginNum为2000,会存在服务端正常返回,但是客户端报EOF,limit为50,loginNum为500的时候,不会出现EOF问题,那说明limit为50是没问题的,按照道理说,及时loginNum增大到100000也不会有问题,但是却出现了问题,后面再解决吧。

现在已经拿到了2000个用户的token了,我们使用jemter工具来进行压测

到此这篇关于Golang实现Redis分布式锁解决秒杀问题的文章就介绍到这了,更多相关Golang Redis解决秒杀问题内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文