www.jianshu.com/p/a2bd89e0d…
If you want to do a good job, you must first sharpen your tools. Let’s start with the tools
jmeter
- I’m demonstrating on a MAC, so I’ll install Brew first
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 2> /dev/null
Copy the code
- Install JMeter using BREW
brew install jmeter
Copy the code
- Start the jmeter
/usr/local/ Cellar/jmeter / 5.4.2 / bin/jmeterCopy the code
Code demo using GO (installation ignored)
- Create a new project
/Users/zhangguofu/website/goproject
- Use go Mod mode
go mod init acurd.com/m
Copy the code
Use MySQL as the data store
create database `go-project`;
use `go-project`;
drop table if exists goods;
CREATE TABLE `goods`
(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT ' ' COMMENT 'name',
`count` int(11) NOT NULL COMMENT 'inventory',
`sale` int(11) NOT NULL COMMENT 'sold',
`version` int(11) NOT NULL COMMENT 'Optimistic lock, version number',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT 'List of Goods';
drop table if exists goods_order;
CREATE TABLE `goods_order`
(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`gid` int(11) NOT NULL COMMENT 'inventory ID',
`name` varchar(30) NOT NULL DEFAULT ' ' COMMENT 'Trade Name',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Creation time',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT 'Order sheet';
insert into goods (`id`,`name`,`count`,`sale`,`version`) values (1,'huawei p40',10,0,0);
Copy the code
Related code [a bit rough]
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"log"
"net/http"
"strconv"
"time") // Merchandise listtype Goods struct {
Id uint `gorm:"column:id; type:int(11) unsigned; primary_key; AUTO_INCREMENT" json:"id"`
Name string `gorm:"column:name; type:varchar(50); NOT NULL" json:"name"'// Count int' gorm:"column:count; type:int(11); NOT NULL" json:"count"'// inventory Sale int' gorm:"column:sale; type:int(11); NOT NULL" json:"sale"'// Version sold int' gorm:"column:version; type:int(11); NOT NULL" json:"version"'// Optimistic lock, version number} // Order tabletype GoodsOrder struct {
Id uint `gorm:"column:id; type:int(11) unsigned; primary_key; AUTO_INCREMENT" json:"id"`
Gid int `gorm:"column:gid; type:int(11); NOT NULL" json:"gid"'// Inventory ID Name string' gorm:"column:name; type:varchar(30); NOT NULL" json:"name"CreateTime time. time 'gorm:"column:create_time; type:timestamp; default:CURRENT_TIMESTAMP; NOT NULL" json:"create_time"Func (m *GoodsOrder) TableName() string {return "goods_order"
}
func main() {
http.HandleFunc("/", addOrder)
log.Fatal(http.ListenAndServe(": 8082", nil))
}
func getDb() *gorm.DB {
connArgs := fmt.Sprintf("%s:%s@(%s:%d)/%s? charset=utf8&parseTime=True&loc=Local"."guofu"."guofu"."localhost", 13306, "go-project")
db, err := gorm.Open("mysql", connArgs)
iferr ! = nil { panic(err) } db.LogMode(trueDb.db ().setMaxIdleconns (100) // Maximum idle connection db.db ().setMaxOpenConns (100) // Maximum connection number Db.db ().setConnMaxLifetime (30) // Maximum lifetime (s)returndb } func addOrder(w http.ResponseWriter, R * http.request) {db := Request () defer db.close () var goods goods goods db.where ("id = ?"."1").First(&goods)
//fmt.Printf("%+v", goods)
if goods.Count >0 {
tx := db.Begin()
defer func() {
ifr := recover() r ! = nil {tx.rollback ()}}() goods.sale +=1 goods.count -=1 // Update databaseiferr := tx.Save(&goods).Error; err ! = nil { tx.Rollback() panic(err) } order:= GoodsOrder{ Gid: 1, Name:strconv.Itoa(int(time.Now().Unix())), }iferr := tx.Create(&order).Error; err ! = nil { tx.Rollback() panic(err) } tx.Commit() w.Write([]byte(fmt.Sprintf("the count i read is %d",goods.Count)))
}else{
w.Write([]byte("I didn't get anything.")} // If there is inventory inserted into the order table}Copy the code
- Run JMeter with 100 threads, 10C each
- We checked the database and found that 10 stocks were gone. And 902 orders were placed, and the boss cried and fainted in the toilet
-
The result tree shows that several requests return the count I read is 7, which means that at the same time, many threads read the same data and start placing orders, which eventually leads to the oversold problem.
-
Let’s take a look at jMeter’s aggregation report and compare the results to use later
To solve the oversold
- Oversold, certainly not, so how do we optimize this problem?
Pessimistic locking
- Let’s start with the code, mainly by changing the addOder part, and to demonstrate the impact of pessimistic locking and optimistic locking on interface performance, let’s set the inventory to 1000
func addOrder(w http.ResponseWriter, R * http.request) {db := defer db.close () var goods goods tx := db.begin ()if err := tx.Set("gorm:query_option"."FOR UPDATE").First(&goods, 1).Error; err ! = nil { tx.Rollback() panic(err) } deferfunc() {
ifr := recover() r ! = nil { tx.Rollback() } }() //fmt.Printf("%+v", goods)
ifGoods. Count >0 {goods.Sale+=1 goods.Count-=1iferr := tx.Save(&goods).Error; err ! = nil { tx.Rollback() panic(err) } order:= GoodsOrder{ Gid: 1, Name:strconv.Itoa(int(time.Now().Unix())), }iferr := tx.Create(&order).Error; err ! = nil { tx.Rollback() panic(err) } tx.Commit() w.Write([]byte(fmt.Sprintf("the count i read is %d",goods.Count)))
}else{
tx.Rollback()
w.Write([]byte("I didn't get anything.")} // If there is inventory inserted into the order table}Copy the code
- It turns out it’s not oversold this time
- However, each thread updates a request to lock this row of the table first (pessimistic lock), and then releases the lock after updating the inventory. This leads to slower processing of requests. Next, let’s look at optimistic locking. Let’s take a look at jMeter’s aggregation report
Optimistic locking
- Optimistic locking is not really a lock, but a data update mechanism, such that we can determine whether the data has been tampered with based on the version number
- Take a look at the modified code
func addOrder(w http.ResponseWriter, R * http.request) {db := defer db.close () var goods goods tx := db.begin ()if err := tx.Where("ID=?"."1").First(&goods).Error; err ! = nil { tx.Rollback()return
}
defer func() {
ifr := recover() r ! = nil { tx.Rollback()return
}
}()
//fmt.Printf("%+v", goods)
if>0 {goods.Sale+=1 goods.Count-=1 oldVerson:=goods.Version goods.Version+=1 column:=tx.Model(&goods).Where("version=?",oldVerson).Updates(&goods)
ifColumn.RowsAffected==0 {// No update succeeded tx.rollback () w.write ([]byte()"I didn't rob anyone else."))
return
}
order:= GoodsOrder{
Gid: 1,
Name:strconv.Itoa(int(time.Now().Unix())),
}
iferr := tx.Create(&order).Error; err ! = nil { tx.Rollback() w.Write([]byte("Failed to create order"))
return
}
tx.Commit()
w.Write([]byte(fmt.Sprintf("the count i read is %d",goods.Count)))
}else{
tx.Rollback()
w.Write([]byte("I didn't get anything.")} // If there is inventory inserted into the order table}Copy the code
- Check the database and find that the order is normal
- Looking at jMeter’s aggregation results, we found that the speed has been improved quite a bit
Redis lock
- Each request needs to be processed by MySQL. If the number of requests is too large, MySQL service may break down and lead to the service terminal. So can we use the asynchronous way to deal with it?
- Peak clipping: There is a large influx of users in a second kill system, so there is a very high peak at the beginning of the rush. Peak flow is a very important reason for crushing the system, so how to turn the instantaneous high flow into a period of stable flow is also a very important idea to design the second kill system. The common methods of peak clipping are using caching and message-oriented middleware.
So the following case, we are based on Redis to achieve the second kill function
- Use queue to achieve, I put the goods into a queue, who grabbed can continue to order, not grabbed back
- And to take some of the pressure off MySQL, we queue requests (even with Redis RabbitMQ) and script orders from the queue to the database
Configure Redis data
- Configure queue data, 1000 in total, which means only 1000 users can grab it
package main
import (
"fmt"
"github.com/go-redis/redis"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", addOrder)
log.Fatal(http.ListenAndServe(": 8082"} func addOrder(w http.responsewriter, r * http.request) {var list = (w http.responsewriter, r * http.request)"goodslist"
var orderList="orderlist"LTrim(list, 1, 0) // Initialize an empty queue firstfori := 1; i <= 1000; I ++ {// add 1000 inventory client.lpush (list, I)}return
**/
var res = client.RPop(list)
val := res.Val()
ifR.parseform () uid:= r.value (len(val) > 0;"uid")
//fmt.Println(uid)
//return
client.LPush(orderList,uid)
msg:=fmt.Sprintf("I got it, I got it %v my user ID is %v \n", val,uid)
_, _ = w.Write([]byte(msg))
fmt.Print(msg)
} else {
msg:="I ain't got nothing \n"
_, _ = w.Write([]byte(msg))
fmt.Print(msg)
}
return
}
func getRedis() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",})return client
}
Copy the code
- Look at the execution result, some of you might say, isn’t it a queue? Why is it out of order? This is because go starts one coroutine for every request, n coroutines, and it doesn’t necessarily come back first
- Let’s take a look at Redis
- If you look at the aggregate results of the requests, it’s about 100 times better than MySQL
Use Redis Incrby Decrby to control the number of orders
- Let’s take a look at the code
Func addOrder(w http.responsewriter, r * http.request) {// If (w http.responsewriter, r * http.request)"inc-count"
var orderList="inc-orderlist"
var total int64=1000
client := getRedis()
//defer client.Close()
//defer r.Body.Close()
var res = client.IncrBy(inckey,1)
val := res.Val()
ifres.Err()! =nil{ fmt.Print(res.Err())return
}
fmt.Println("My value is now",val);
//return
ifR.parseform () uid:= r.flormValue ()"uid")
client.LPush(orderList,uid)
msg:=fmt.Sprintf("I got it. I got it %d and my user ID is %v \n.", val,uid)
_, _ = w.Write([]byte(msg))
fmt.Print(msg)
} else {
msg:="I ain't got nothing \n"
_, _ = w.Write([]byte(msg))
}
return
}
Copy the code
- Let’s take a look at Redis
- Take a look at request aggregation
episode
- There was an incident when I was using incrby. Let’s take a look at the screenshot. There was no problem with the single request
ERR max number of clients reached
The reason is that I didn’t close the resource when I finished using Redis
- After modifying the above problem, there is a problem again
connect: can't assign requested addressdial tcp
, this is because response is not closed, so friends must remember to close the resource ah!
Redis distributed lock
Before we get started, let’s familiarize ourselves with these commands (supported as of Redis 2.6.12)
-
EX second: Sets the expiration time of the key to second seconds. SET key value EX second is the same as SETEX key second value.
-
PX millisecond: Set the expiration time of the key to millisecond. SET key value PX millisecond = PSETEX key millisecond value
-
NX: Sets the key only when the key does not exist. SET key value NX is the same as SETNX key value.
-
XX: Configures the key only when the key already exists.
-
Let’s look at the code
package main
import (
"fmt"
"github.com/go-redis/redis"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"log"
"net/http"
"strconv"
"time") // Merchandise listtype Goods struct {
Id uint `gorm:"column:id; type:int(11) unsigned; primary_key; AUTO_INCREMENT" json:"id"`
Name string `gorm:"column:name; type:varchar(50); NOT NULL" json:"name"'// Count int' gorm:"column:count; type:int(11); NOT NULL" json:"count"'// inventory Sale int' gorm:"column:sale; type:int(11); NOT NULL" json:"sale"'// Version sold int' gorm:"column:version; type:int(11); NOT NULL" json:"version"'// Optimistic lock, version number} // Order tabletype GoodsOrder struct {
Id uint `gorm:"column:id; type:int(11) unsigned; primary_key; AUTO_INCREMENT" json:"id"`
Gid int `gorm:"column:gid; type:int(11); NOT NULL" json:"gid"'// Inventory ID Name string' gorm:"column:name; type:varchar(30); NOT NULL" json:"name"CreateTime time. time 'gorm:"column:create_time; type:timestamp; default:CURRENT_TIMESTAMP; NOT NULL" json:"create_time"Func (m *GoodsOrder) TableName() string {return "goods_order"
}
func main() {
http.HandleFunc("/", addOrder)
log.Fatal(http.ListenAndServe(": 8082", nil))
}
func getDb() *gorm.DB {
connArgs := fmt.Sprintf("%s:%s@(%s:%d)/%s? charset=utf8&parseTime=True&loc=Local"."guofu"."guofu"."localhost", 13306, "go-project")
db, err := gorm.Open("mysql", connArgs)
iferr ! = nil { panic(err) } db.LogMode(falseDb.db ().setMaxIdleconns (100) // Maximum idle connection db.db ().setMaxOpenConns (100) // Maximum connection number Db.db ().setConnMaxLifetime (30) // Maximum lifetime (s)return db
}
func addOrder(w http.ResponseWriter, r *http.Request) {
key := "order"
client := getRedis()
defer client.Close()
cmd := client.SetNX(key, "1", time.second *30)// There is a problem where the program is running too long, causing the lock to be released, and the delete lock at the end of the program will delete other requested locks, making it unavailableif cmd.Val() == true{db := defer defer db.close () var goods goods db.where () defer defer defer db.close ()"id = ?"."1").First(&goods)
fmt.Println(goods.Count)
if goods.Count > 0 {
tx := db.Begin()
defer func() {
ifr := recover() r ! = nil {tx.rollback ()}}() goods.sale += 1 goods.count -= 1 // Update databaseiferr := tx.Save(&goods).Error; err ! = nil { tx.Rollback() panic(err) } order := GoodsOrder{ Gid: 1, Name: strconv.Itoa(int(time.Now().Unix())), }iferr := tx.Create(&order).Error; err ! = nil { tx.Rollback() panic(err) } tx.Commit() w.Write([]byte(fmt.Sprintf("the count i read is %d", goods.Count)))
} else {
w.Write([]byte("I didn't get anything."))
}
client.Del(key)
}
}
func getRedis() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",})return client
}
Copy the code
- But there will be a problem, is I the inside of the program execution is too long, lead to the lock release, delete the lock at the end of the program Will delete the other requests lock, leading to is not available, we optimize the value assignment to a random number, every time before you delete, judge whether the value and the value of you agree, if agree, delete, If not, don’t delete it
package main
import (
"fmt"
"github.com/go-redis/redis"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/satori/go.uuid"
"log"
"net/http"
"strconv"
"time") // Merchandise listtype Goods struct {
Id uint `gorm:"column:id; type:int(11) unsigned; primary_key; AUTO_INCREMENT" json:"id"`
Name string `gorm:"column:name; type:varchar(50); NOT NULL" json:"name"'// Count int' gorm:"column:count; type:int(11); NOT NULL" json:"count"'// inventory Sale int' gorm:"column:sale; type:int(11); NOT NULL" json:"sale"'// Version sold int' gorm:"column:version; type:int(11); NOT NULL" json:"version"'// Optimistic lock, version number} // Order tabletype GoodsOrder struct {
Id uint `gorm:"column:id; type:int(11) unsigned; primary_key; AUTO_INCREMENT" json:"id"`
Gid int `gorm:"column:gid; type:int(11); NOT NULL" json:"gid"'// Inventory ID Name string' gorm:"column:name; type:varchar(30); NOT NULL" json:"name"CreateTime time. time 'gorm:"column:create_time; type:timestamp; default:CURRENT_TIMESTAMP; NOT NULL" json:"create_time"Func (m *GoodsOrder) TableName() string {return "goods_order"
}
func main() {
http.HandleFunc("/", addOrder)
log.Fatal(http.ListenAndServe(": 8082", nil))
}
func getDb() *gorm.DB {
connArgs := fmt.Sprintf("%s:%s@(%s:%d)/%s? charset=utf8&parseTime=True&loc=Local"."guofu"."guofu"."localhost", 13306, "go-project")
db, err := gorm.Open("mysql", connArgs)
iferr ! = nil { panic(err) } db.LogMode(falseDb.db ().setMaxIdleconns (100) // Maximum idle connection db.db ().setMaxOpenConns (100) // Maximum connection number Db.db ().setConnMaxLifetime (30) // Maximum lifetime (s)return db
}
func addOrder(w http.ResponseWriter, r *http.Request) {
value:=GetUUID()
key := "order"client := getRedis() defer client.Close() cmd := client.SetNX(key,value , Time.second *30)// There is a problem where the program is running too long, causing the lock to be released, and the delete lock at the end of the program will delete other requested locks, making it unavailableif cmd.Val() == true{db := defer defer db.close () var goods goods db.where () defer defer defer db.close ()"id = ?"."1").First(&goods)
fmt.Println(goods.Count)
if goods.Count > 0 {
tx := db.Begin()
defer func() {
ifr := recover() r ! = nil {tx.rollback ()}}() goods.sale += 1 goods.count -= 1 // Update databaseiferr := tx.Save(&goods).Error; err ! = nil { tx.Rollback() panic(err) } order := GoodsOrder{ Gid: 1, Name: strconv.Itoa(int(time.Now().Unix())), }iferr := tx.Create(&order).Error; err ! = nil { tx.Rollback() panic(err) } tx.Commit() w.Write([]byte(fmt.Sprintf("the count i read is %d", goods.Count)))
} else {
w.Write([]byte("I didn't get anything."))}if client.Get(key).Val()==value {
client.Del(key)
}
}
}
func GetUUID() (string) {
u2 := uuid.NewV4()
return u2.String()
}
func getRedis() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",})return client
}
Copy the code
Lua + Redis implements distributed locking
Why lua
- Starting with redis2.6.0, Lua scripts can be evaluated using the EVAL command through the built-in Lua interpreter.
- Reduce network overhead. Multiple requests can be sent in script form at a time, reducing network latency.
- Atomic operation. Redis will execute the entire script as a whole and will not be inserted by other requests. Therefore, there is no need to worry about race conditions during script execution, no transactions are required, and Redis guarantees that the script will be executed atomically (either on success or failure). No other scripts or Redis commands will be executed while one script is being executed.
- Reuse. The script sent by the client is perpetuated in Redis so that other clients can reuse the script without having to complete the same logic with code.
Learn to use Lua
- Refer to the article
Using Lua in Redis, let’s first look at common commands
- EVAL
Syntax: EVAL script numkeys key [key…] Arg [arg]… Let’s take an example
# ------------------------------------------ script-----------------------------numkeys--key-----arg--arg
EVAL "redis.call('SET',KEYS[1],ARGV[1]); redis.call('EXPIRE',KEYS[1],ARGV[2]); return 1;" 1 age 18 60
127.0.0.1:6379> EVAL "redis.call('SET',KEYS[1],ARGV[1]); redis.call('EXPIRE',KEYS[1],ARGV[2]); return 1;" 1 age 18 60
(integer) 1
127.0.0.1:6379> get age
"18"
Copy the code
The part inside the quotation marks is the script, 1 is the number of keys, there is only one key, who is that key? Argv [1] is 18. Argv [2] is 60. Set key1 to 10
SCRIPT LOAD
- Command format SCRIPT LOAD SCRIPT
- SCRIPT LOAD adds a SCRIPT SCRIPT to the SCRIPT cache of the Redis server, and instead of executing the SCRIPT immediately, the input SCRIPT is evaluated immediately. And returns the SHA1 checksum for the given script. If the given script is already in the cache, nothing is done. The script is cached in the Redis server. If it has already been cached, it is no longer cached
127.0.0.1:6379 > SCRIPT LOAD"redis.call('SET',KEYS[1],ARGV[1]); redis.call('EXPIRE',KEYS[1],ARGV[2]); return 1;"
"6cc501292668ceef3dd487b3e4e889dc08d07587"
Copy the code
EVALSHA
Syntax: EVALSHA sha1 numkeys key [key…] Arg [arg]… The script has been cached. How to execute it? The script can be invoked using the SHA1 checksum of the script from any client using the EVALSHA command. Scripts can remain in the cache for an unlimited amount of time until SCRIPT FLUSH is executed.
127.0.0.1:6379 > EVALSHA 6 cc501292668ceef3dd487b3e4e889dc08d07587 1 name jimy 10 (integer) 1
127.0.0.1:6379> get name
"jimy"127.0.0.1:6379 > get name (nil) 127.0.0.1:6379 > EVALSHA 6 cc501292668ceef3dd487b3e4e889dc08d07587 1 name jimy 10 (integer) 1
127.0.0.1:6379> ttl name
(integer) 7
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
Copy the code
SCRIPT FLUSH
The SCRIPT FLUSH command clears all Lua SCRIPT caches on Redis server
6 cc501292668ceef3dd487b3e4e889dc08d07587 127.0.0.1:6379 > SCRIPT EXISTS (1)integer127.0.0.1) 1:6379 > SCRIPT FLUSH OK 127.0.0.1:6379 > SCRIPT EXISTS 6 cc501292668ceef3dd487b3e4e889dc08d07587 (1)integer) 0
Copy the code
SCRIPT KILL
The SCRIPT KILL command kills the running Lua SCRIPT. This command takes effect only when no write operation has been performed on the SCRIPT. This command is mainly used to terminate scripts that take too long to run, such as a script that has infinite loops due to bugs, or blocks due to reading too many keys, etc. If the SCRIPT you are running has already performed a write operation, you cannot KILL it by executing SCRIPT KILL because this violates Lua’s atomic execution principle. In this case, the only viable option is to use the SHUTDOWN NOSAVE command to stop the script by stopping the entire Redis process and prevent half-written information from being written to the database.
Use the redis-CLI client to execute the Lua file
- Full command
Redis -cli -h host -p port -a password -n db – — eval demo. Lua k1 k2, a1 A2 -p is followed by the remote Redis port number; -a is followed by the password; -n Indicates the selected redis DB. K1, k2, A1, a2 “are retrieved in lua scripts using the global KEYS and ARGV variables.
- Let’s test this with an example
- The lua file is as follows
This command is equivalent tosetKey1 argv1 EX argv2 -- like the chestnut below, set age to 18 and expiration to 60 --set age 18 EX 60
redis.call('SET',KEYS[1],ARGV[1])
redis.call('EXPIRE',KEYS[1],ARGV[3])
redis.call('SET',KEYS[2],ARGV[2])
redis.call('EXPIRE',KEYS[2],ARGV[3])
return 1
Copy the code
- Execute the command (note that there is a comma between key and arg, and the comma must be left blank)
- The second point to note is that when we apply Lua in GO, we can use Redis-CLI for debugging first, which is more convenient than debugging in Go
zhangguofu@zhangguofudeMacBook-Pro bin $ ./redis-cli --eval /Users/zhangguofu/website/goproject/script.lua name age , jimy 18 60
(integer1)Copy the code
- View Redis results
Use Lua in GO
- Take a look at the contents of the Lua script
- user idlocalUserId = toString (KEYS[1]) -- Order setlocalOrderSet = toString (KEYS[2]) -- Item inventory keylocalGoodsTotal = ToString (ARGV[1]) -- Order queuelocalOrderList = toString (ARGV[2]) -- whether it has been snapped up, and return if solocal hasBuy = tonumber(redis.call("sIsMember", orderSet, userId))
if hasBuy ~= 0 then
return0 end - The amount of inventorylocal total=tonumber(redis.call("GET", goodsTotal))
--returnTotal -- Returns if there is no stock leftif total <= 0 then
return0 end -- Orders can be placedlocalFlag -- Add to order queue flag = redis.call("LPUSH", orderList, userId) -- add to user set flag = redis.call("SADD", orderSet, userId) -- Inventory count minus 1 flag = redis.call("DECR", goodsTotal) -- returns the number of caches at that timereturnTotal --[[-- multiline comment]]Copy the code
- Take a look at the execution result
zhangguofu@zhangguofudeMacBook-Pro bin $ ./redis-cli --eval /Users/zhangguofu/website/goproject/lua-case/script.lua sxxxxxx orderSet , goodsTotal orderList
(integer) 100
zhangguofu@zhangguofudeMacBook-Pro bin $ ./redis-cli --eval /Users/zhangguofu/website/goproject/lua-case/script.lua sxxxxx1 orderSet , goodsTotal orderList
(integer) 99
zhangguofu@zhangguofudeMacBook-Pro bin $ ./redis-cli --eval /Users/zhangguofu/website/goproject/lua-case/script.lua sxxxxx2 orderSet , goodsTotal orderList
(integer) 98
Copy the code
- Take a look at the go code
package main
import (
"fmt"
"github.com/go-redis/redis/v8"
"io/ioutil"
"log"
"net/http"
"sync"
)
const orderSet = "orderSet"// Set of user ids const goodsTotal ="goodsTotal"// Key const orderList ="orderList"// Order queue func createScript() * redis.script {STR, err := ioutil.readfile ("./lua-case/script.lua")
iferr ! = nil { fmt.Println("Script read error", err)
log.Println(err)
}
scriptStr := fmt.Sprintf("%s", str)
script := redis.NewScript(scriptStr)
return script
}
func evalScript(client *redis.Client, userId string, wg *sync.WaitGroup) {
defer wg.Done()
script := createScript()
//fmt.Printf("%+v",script)
//return
sha, err := script.Load(client.Context(), client).Result()
iferr ! = nil { log.Fatalln(err) } ret := client.EvalSha(client.Context(), sha, []string{ userId, orderSet, }, []string{ goodsTotal, orderList, })ifresult, err := ret.Result(); err ! = nil { log.Fatalf("Execute Redis fail: %v", err.Error())
} else {
total:=result.(int64)
if total==0{
fmt.Printf("Userid: %s, got nothing \n", userId)
}else{
fmt.Printf("Userid: %s grabbed, inventory: %d \n", userId, total)
}
}
}
func main() {
http.HandleFunc("/", addOrder)
log.Fatal(http.ListenAndServe(": 8082", nil))
}
func addOrder(w http.ResponseWriter, r *http.Request) {
var wg sync.WaitGroup
wg.Add(1)
client := getRedis()
defer r.Body.Close()
defer client.Close()
r.ParseForm()
uid := r.FormValue("uid")
go evalScript(client, uid, &wg)
wg.Wait()
}
func getRedis() *redis.Client {
client := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",})return client
}
Copy the code
- View the code running results
- View Redis results
127.0.0.1:6379 >setGoodsTotal 100 OK 127.0.0.1:6379> get goodsTotal"0"127.0.0.1:6379 > keys * 1)"goodsTotal"
2) "orderSet"
3) "orderList"127.0.0.1:6379 > llen orderList (integer100 127.0.0.1:6379> scard orderSet (integer6379 > 100 127.0.0.1) :Copy the code
- View jMeter’s aggregation report
- The relevant code has been uploaded to github repository
- If you can’t click, please choose to view the original text