Go language operation Redis

Go language operation Redis

Redis is also frequently used in project development. This paper introduces the basic use of Go redis Library in Go language.

1, Redis introduction

Redis is an open source in memory database. Redis provides a variety of different types of data structures. Problems in many business scenarios can be naturally mapped to these data structures. In addition, through replication, persistence and client fragmentation, we can easily expand redis into a system that can contain hundreds of GB of data and process millions of requests per second.

2, Redis supported data structures

Redis supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperlogs, geospatial indexes with radius queries and streams.

3, Redis application scenario

  • Cache system to reduce the pressure on the master database (MySQL).
  • Counting scenes, such as micro-blog, tiktok, and fans.
  • Popular leaderboards, scenes that need to be sorted are especially suitable for using ZSET.
  • Use LIST to realize the function of queue.

4, Prepare Redis environment

Here, you can directly use Docker to start a redis environment, which is convenient for learning and using.

docker starts a version 5.0.0 called redis507 Example of redis server version 7:

docker run --name redis507 -p 6379:6379 -d redis:5.0.7

Note: please set the version, container name and port number here according to your needs.

Start a redis cli connection to the above redis server:

docker run -it --network host --rm redis:5.0.7 redis-cli

5, Go redis library installation

Different from another commonly used Go language redis client Library: redigo , we use it here https://github.com/go-redis/redis Connect to the Redis database and perform operations, because go Redis supports Redis in sentinel and cluster mode.

Download and install using the following command:

go get -u github.com/go-redis/redis

6, Connect

6.1. Common connection

package main

import "github.com/go-redis/redis"

/*
@author RandySun
@create 2021-09-01-8:39
*/

var rdb *redis.Client

// Initialize connection
func ordinaryInitClient()(err error){
	rdb = redis.NewClient(&redis.Options{
		Addr: "127.0.0.1:6379",
		Password: "",
		DB: 3,

	})
	_, err = rdb.Ping().Result()
	if err != nil{
		return err
	}
	return nil
}


func main() {
	err := ordinaryInitClient()
	if err != nil{
		fmt.Println("connect redis failed:", err)
	}
	fmt.Println("connect redis success", rdb)
}

6.2 new version of V8

The related commands of the latest version of go redis library need to pass context Context parameter, for example:

package main

import (
	"context"
	"fmt"
	"github.com/go-redis/redis/v8" // Note that you are importing a new version
	"time"
)

/*
@author RandySun
@create 2021-09-01-8:53
*/
var rdb *redis.Client

// Initialize connection
func initClient() (err error) {
	rdb = redis.NewClient(&redis.Options{
		Addr: "127.0.0.1:6379",
		Password: "", // password
		DB: 5, // Select connected Libraries
		PoolSize: 100, //Connection pool size
	})

	ctx, cancel  :=context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel ()
	_, err = rdb.Ping(ctx).Result()
	return err
}


func v8Example(){
	ctx := context.Background()
	if err := initClient(); err != nil{
		return
	}
	err := rdb.Set(ctx, "name", "RandySun", 0).Err()
	if err != nil{
		panic(err)
	}
	val, err :=rdb.Get(ctx,"name").Result()
	if err != nil{
		panic(err)
	}
	fmt.Println("name", val)

	val2, err := rdb.Get(ctx, "key2").Result()
	if err == redis.Nil{
		fmt.Println("key2, does not exist")
	}else if err != nil{
		panic(err)
	}else {
		fmt.Println("key2", val2)
	}


}
func main() {
	
	v8Example()
}

6.3. Connect Redis sentinel mode

func initClient()(err error){
	rdb := redis.NewFailoverClient(&redis.FailoverOptions{
		MasterName:    "master",
		SentinelAddrs: []string{"x.x.x.x:26379", "xx.xx.xx.xx:26379", "xxx.xxx.xxx.xxx:26379"},
	})
	_, err = rdb.Ping().Result()
	if err != nil {
		return err
	}
	return nil
}

6.4. Connect to Redis cluster

func initClient()(err error){
	rdb := redis.NewClusterClient(&redis.ClusterOptions{
		Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
	})
	_, err = rdb.Ping().Result()
	if err != nil {
		return err
	}
	return nil
}

7, Basic use

7.1. set/get example

package main

import (
	"context"
	"fmt"
	"github.com/go-redis/redis/v8" // Note that you are importing a new version

)
/*
@author RandySun
@create 2021-09-02-8:21
*/

func setGetRedisDemo() {
	ctx := context.Background()

	err := rdb.Set(ctx, "score", 100, 0).Err()
	if err != nil{
		fmt.Printf("set score failed, err: %v\n", err)
	}

	val, err :=rdb.Get(ctx,"sscore").Result()
	if err != nil{
		fmt.Printf("get score failed, err: %v\n", err)

	}
	fmt.Println("score", val)

	val2, err := rdb.Get(ctx, "name").Result()
	if err == redis.Nil {
		fmt.Println("name does not exist")
	} else if err != nil {
		fmt.Printf("get name failed, err:%v\n", err)
		return
	} else {
		fmt.Println("name", val2)
	}
}



func main() {
	err := initClient()
	if err != nil{
		fmt.Println("connect redis failed:", err)
	}
	fmt.Println("connect redis success", rdb)
	setGetRedisDemo()

}

Redis success redis < 127.0 0.1:6379 db:5>
get score failed, err: redis: nil
score
name RandySun

7.2 zset example

package main

/*
@author RandySun
@create 2021-09-03-8:39
*/
import (
	"context"
	"fmt"
	"github.com/go-redis/redis/v8" // Note that you are importing a new version

)
func zsetRedisDemo() {

	zsetKey := "language_rank"
	languages := []*redis.Z{
	&redis.Z{Score: 90.0, Member: "Golang"},
	&redis.Z{Score: 98.0, Member: "Java"},
	&redis.Z{Score: 95.0, Member: "Python"},
	&redis.Z{Score: 97.0, Member: "JavaScript"},
	&redis.Z{Score: 99.0, Member: "C/C++"},
	}
	ctx := context.Background()

	// ZADD
	num, err := rdb.ZAdd(ctx,zsetKey, languages...).Result()
	if err != nil {
		fmt.Printf("zadd failed, err:%v\n", err)
		return
	}
	fmt.Printf("zadd %d succ.\n", num)


	// Add 10 to Golang's score
	newScore, err := rdb.ZIncrBy(ctx, zsetKey, 10.0, "Golang").Result()
	if err != nil {
		fmt.Printf("zincrby failed, err:%v\n", err)
		return
	}

	fmt.Printf("Golang's score is %f now.\n", newScore)

	// Take the 3 with the highest scores
	ret, err := rdb.ZRevRangeWithScores(ctx, zsetKey, 0, 2).Result()
	if err != nil {
		fmt.Printf("zrevrange failed, err:%v\n", err)
		return
	}
	for _, z := range ret {
		fmt.Println("Take the 3 with the highest scores",z.Member, z.Score)
	}

	// Take 95 ~ 100 points
	op := &redis.ZRangeBy{
		Min: "95",
		Max: "100",
	}
	ret, err = rdb.ZRangeByScoreWithScores(ctx, zsetKey, op).Result()
	if err != nil {
		fmt.Printf("zrangebyscore failed, err:%v\n", err)
		return
	}
	for _, z := range ret {
		fmt.Println("Take 95~100 Fractional:",z.Member, z.Score)
	}
}


func main() {
	err := initClient()
	if err != nil{
		fmt.Println("connect redis failed:", err)
	}
	fmt.Println("connect redis success", rdb)
	zsetRedisDemo()

}

The output results are as follows:

connect redis success Redis<127.0.0.1:6379 db:5>
zadd 0 succ.
Golang's score is 100.000000 now.
Take the 3 with the highest scores Golang 100
 Take the 3 with the highest scores C/C++ 99
 Take the 3 with the highest scores Java 98
 Take 95~100 Fractional: Python 95
 Take 95~100 Fractional: JavaScript 97
 Take 95~100 Fractional: Java 98
 Take 95~100 Fractional: C/C++ 99
 Take 95~100 Fractional: Golang 100

7.3. Get Key according to prefix

vals, err := rdb.Keys(ctx, "prefix*").Result()

7.4. Execute custom commands

res, err := rdb.Do(ctx, "set", "key", "value").Result()

7.5. Delete key by wildcard

When the number of wildcard matching keys is small, you can use Keys() to get all the keys, and then use Del to delete them. If the number of keys is very large, we can use the Scan command and Del command to delete them.

package main

/*
@author RandySun
@create 2021-09-03-9:02
*/
import (
	"context"
	"fmt"
	//"GitHub. COM / go redis / redis / V8" / / note that the new version is imported

)
func deleteKeysRedisDemo()  {
	ctx := context.Background()

	vals, err := rdb.Keys(ctx, "na*").Result()
	if err != nil {
		fmt.Printf("get keys failed, err:%v\n", err)
		return
	}
	fmt.Println("Get by wildcard key front:", vals)

	iter := rdb.Scan(ctx, 0, "na*", 0).Iterator()
	for iter.Next(ctx) {
		fmt.Println("delete key: ", iter.Val())

		err := rdb.Del(ctx, iter.Val()).Err()
		if err != nil {
			panic(err)
		}
	}
	if err := iter.Err(); err != nil {
		panic(err)
	}
	vals2, err := rdb.Keys(ctx, "na*").Result()
	if err != nil {
		fmt.Printf("get keys failed, err:%v\n", err)
		return
	}
	fmt.Println("Delete according to wildcard key after:", vals2)
}

func main() {
	err := initClient()
	if err != nil{
		fmt.Println("connect redis failed:", err)
	}
	fmt.Println("connect redis success", rdb)
	deleteKeysRedisDemo()

}

Redis success redis < 127.0 0.1:6379 db:5>
Before obtaining key according to wildcard: [name]
Delete key: name
Delete key according to wildcard: []

8, Pipeline

Pipeline is mainly a kind of network optimization. It essentially means that the client buffers a bunch of commands and sends them to the server at once. These commands are not guaranteed to be executed in a transaction. This has the advantage of saving network round trip time (RTT) per command.

The basic example of Pipeline is as follows:

package main

import (
	"context"
	"fmt"
	"github.com/go-redis/redis/v8"
	"time"
)

/*
@author RandySun
@create 2021-09-03-21:37
*/
func pipelineRedisDemo() {
	ctx := context.Background()
	pipe := rdb.Pipeline()
	incr := pipe.Incr(ctx, "pipeline_counter")
	pipe.Expire(ctx, "pipeline_counter", time.Hour)
	_, err := pipe.Exec(ctx)
	fmt.Println(incr.Val(), err)

}

func pipelinedRedisDemo() {
	var incr *redis.IntCmd
	ctx := context.Background()
	_, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
		incr = pipe.Incr(ctx,"pipeline_counter")
		pipe.Expire(ctx, "pipeline_counter", time.Hour)
		return nil
	})

	fmt.Println(incr.Val(), err)
}

func main() {
	err := initClient()
	if err != nil{
		fmt.Println("connect redis failed:", err)
	}
	fmt.Println("connect redis success", rdb)
	pipelineRedisDemo()
	pipelinedRedisDemo()

}

3
4

The above code is equivalent to sending the following two commands to the redis server for execution at one time. Compared with not using Pipeline, it can reduce one RTT.

INCR pipeline_counter
EXPIRE pipeline_counts 3600

You can also use Pipelined:

var incr *redis.IntCmd
_, err := rdb.Pipelined(func(pipe redis.Pipeliner) error {
	incr = pipe.Incr("pipelined_counter")
	pipe.Expire("pipelined_counter", time.Hour)
	return nil
})
fmt.Println(incr.Val(), err)

In some scenarios, when we have multiple commands to execute, we can consider using pipeline to optimize.

9, Business

Redis is single threaded, so a single command is always atomic, but two given commands from different clients can be executed in turn, for example, alternately between them. However, multi / exec can ensure that no other client is executing commands between multi / exec statements.

In this scenario, we need to use TxPipeline. TxPipeline is generally similar to the above Pipeline, but it uses the MULTI/EXEC package queuing command internally. For example:

pipe := rdb.TxPipeline()

incr := pipe.Incr("tx_pipeline_counter")
pipe.Expire("tx_pipeline_counter", time.Hour)

_, err := pipe.Exec()
fmt.Println(incr.Val(), err)

The above code is equivalent to executing the following redis command under an RTT:

MULTI
INCR pipeline_counter
EXPIRE pipeline_counts 3600
EXEC

There is also a TxPipelined method similar to the above, which is used as follows:

var incr *redis.IntCmd
_, err := rdb.TxPipelined(func(pipe redis.Pipeliner) error {
	incr = pipe.Incr("tx_pipelined_counter")
	pipe.Expire("tx_pipelined_counter", time.Hour)
	return nil
})
fmt.Println(incr.Val(), err)

10, Watch

In some scenarios, we need to use the WATCH command in addition to the MULTI/EXEC command. After the user uses the WATCH command to monitor a key until the user executes the EXEC command, if other users replace, update, delete, etc. the monitored key first, when the user tries to execute EXEC, the transaction will fail and return an error. The user can choose to retry the transaction or abandon the transaction according to this error.

Watch(fn func(*Tx) error, keys ...string) error

The Watch method receives a function and one or more key s as parameters. Basic usage examples are as follows:

// Monitor Watch_ The value of count, and its value + 1 on the premise that the value remains unchanged
key := "watch_count"
err = client.Watch(func(tx *redis.Tx) error {
	n, err := tx.Get(key).Int()
	if err != nil && err != redis.Nil {
		return err
	}
	_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
        // Do trading operations
		pipe.Set(key, n+1, 0)
		return nil
	})
	return err
}, key)

Finally, let's take a look at an example of using the GET and SET commands to increase the value of the Key in a transactional manner in the official V8 document. A transaction is submitted only when the value of the Key does not change.

func transactionDemo() {
	var (
		maxRetries   = 1000
		routineCount = 10
	)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Increment uses the GET and SET commands to increment the value of the Key in a transactional manner
	increment := func(key string) error {
		// Transaction function
		txf := func(tx *redis.Tx) error {
			// Get the current value or zero value of the key
			n, err := tx.Get(ctx, key).Int()
			if err != nil && err != redis.Nil {
				return err
			}

			// Actual operation code (local operation in optimistic locking)
			n++

			// The operation is submitted only when the Key of the Watch has not changed
			_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
				pipe.Set(ctx, key, n, 0)
				return nil
			})
			return err
		}

		// Max retries retries
		for i := 0; i < maxRetries; i++ {
			err := rdb.Watch(ctx, txf, key)
			if err == nil {
				// success
				return nil
			}
			if err == redis.TxFailedErr {
				// Optimistic lock lost retry
				continue
			}
			// Return other errors
			return err
		}

		return errors.New("increment reached maximum number of retries")
	}

	// Simulate routineCount concurrency and modify the value of counter3 at the same time
	var wg sync.WaitGroup
	wg.Add(routineCount)
	for i := 0; i < routineCount; i++ {
		go func() {
			defer wg.Done()
			if err := increment("counter3"); err != nil {
				fmt.Println("increment error:", err)
			}
		}()
	}
	wg.Wait()

	n, err := rdb.Get(context.TODO(), "counter3").Int()
	fmt.Println("ended with", n, err)
}

For more details, please refer to file.

Keywords: Go

Added by crinkle on Mon, 03 Jan 2022 13:35:29 +0200