Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

An example Analysis of Go concurrent programming in SingleFlight Mode

2025-02-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)05/31 Report--

This article mainly introduces the SingleFlight mode Go concurrent programming case analysis related knowledge, the content is detailed and easy to understand, the operation is simple and fast, has a certain reference value, I believe that everyone after reading this SingleFlight mode Go concurrent programming case analysis article will have a harvest, let's take a look.

The role of SingleFlight in go-zero is to combine concurrent requests into a single request to reduce the pressure on lower-level services.

Application scenario

When querying the cache, merge requests to improve service performance. Suppose there is a service for IP query, each time a user requests to query the attribution of an IP in the cache, if there is a result in the cache, it will be returned directly, and the IP parsing operation will be performed if it does not exist.

As shown in the figure above, if n users request to query the same IP (8.8.8.8), it will correspond to n Redis queries. In high concurrency scenarios, if n Redis queries can be merged into one Redis query, the performance will be greatly improved. SingleFlight is used to implement request merging, and the results are as follows:

Prevent cache breakdown.

Cache breakdown problem refers to: in high concurrency scenarios, a large number of requests query a key at the same time. If the key expires, it will cause a large number of requests to call the database, resulting in an increase in database connections and load.

Through SingleFlight, the concurrent requests for the same Key can be merged, only one of the requests can be queried to the database, and the other requests can share the same result, which can greatly improve the concurrency ability.

Application mode

Go directly to the code:

Func main () {round: = 10 var wg sync.WaitGroup barrier: = syncx.NewSingleFlight () wg.Add (round) for I: = 0; I < round Val + {go func () {defer wg.Done () / / enable 10 collaborative simulation to get cache operation val, err: = barrier.Do ("get_rand_int", func () (interface {}, error) {time.Sleep (time.Second) return rand.Int () Nil}) if err! = nil {fmt.Println (err)} else {fmt.Println (val)}} ()} wg.Wait ()}

The above code simulates 10 co-programs requesting Redis to get the content of a key. The code is very simple, which is to execute the Do () method. Two parameters are received. The first parameter is to obtain the identity of the resource, which can be the key cached in redis, and the second parameter is an anonymous function that encapsulates the business logic to be done. The final results are as follows:

5577006791947779410

5577006791947779410

5577006791947779410

5577006791947779410

5577006791947779410

5577006791947779410

5577006791947779410

5577006791947779410

5577006791947779410

5577006791947779410

From the above, we can see that all 10 cooperators get the same result, that is, only one co-program actually executes rand.Int () to get random numbers, and all the other co-programs share this result.

Source code parsing

Let's first look at the code structure:

Type (/ / defines the interface. There are two methods, Do and DoEx. In fact, the logic is the same. DoEx has an extra identity. It is enough to look at the logic of Do: SingleFlight interface {Do (key string, fn func () (interface {}, error)) (interface {}, error) DoEx (key string, fn func () (interface {}, error)) (interface {}, bool, error)} / / define the structure of call call struct {wg sync.WaitGroup / / to implement through 1 call Other call blocking val interface {} / / indicates the returned result of the call operation err error / / indicates the error} / / master control structure of the call operation, and implements the SingleFlight interface flightGroup struct {calls map [string] * call / / different call corresponds to different key lock sync.Mutex / / use lock control request})

Then look at what the core Do method does:

Func (g * flightGroup) Do (key string, fn func () (interface {}, error)) (interface {}, error) {c, done: = g.createCall (key) if done {return c.val, c.err} g.makeCall (c, key, fn) return c.val, c.err}

The code is very simple. Use g.createCall (key) to issue a call request to key (actually do one thing). If there are already other protocols that are already initiating call requests, block them (if done is true), wait for the result and return directly. If done is false, indicating that the current protocol is the first to initiate call, then execute g.makeCall (c, key, fn) to actually initiate the call request (other protocols since then are blocked in g.createCall (key)).

As can be seen from the picture above, there are actually two key steps:

Judgment is the first requested collaborator (using map)

Block all other collaborations (using sync.WaitGroup)

Let's take a look at how g.createCall (key) is implemented:

Func (g * flightGroup) createCall (key string) (c * call, done bool) {g.lock.Lock () if c, ok: = g.calls [key]; ok {g.lock.Unlock () c.wg.Wait () return c, true} c = new (call) c.wg.Add (1) g.calls [key] = c g.lock.Unlock () return c, false}

First look at the first step: judge is the first request for collaboration (using map)

G.lock.Lock () if c, ok: = g.calls [key]; ok {g.lock.Unlock () c.wg.Wait () return c, true}

It is determined here whether the key in map exists. If it already exists, it means that there are already other protocols being requested. Currently, this protocol only needs to wait, which is implemented by using the Wait () method of sync.WaitGroup, which is still very clever here. Note that map is non-concurrently secure in Go, so it needs to be locked.

Take a look at step 2: block all other collaborations (using sync.WaitGroup)

C = new (call) c.wg.Add (1) g.calls [key] = c

Since it is the first co-program to initiate call, you need to new the call, and then wg.Add (1), which corresponds to the wg.Wait () above, blocking the rest of the co-program. Then put the call of new into the map, notice that at this time only the initialization is completed, and the call request is not actually executed, and the real processing logic is in g.makeCall (c, key, fn).

Func (g * flightGroup) makeCall (c * call, key string, fn func () (interface {}, error)) {defer func () {g.lock.Lock () delete (g.calls, key) g.lock.Unlock () c.wg.Done ()} () c.val, c.err = fn ()}

What is done in this method is simply to execute the passed anonymous function fn () (that is, what the real call request does). Finally, dealing with the finishing touches (via defer) is also divided into two steps:

Delete the key in map so that you can get the new value the next time you initiate a request.

Call wg.Done () so that all previously blocked coaches get the results and return.

This is the end of the article on "Go concurrent programming example Analysis of SingleFlight Mode". Thank you for reading! I believe that everyone has a certain understanding of the knowledge of "Go concurrent programming example analysis of SingleFlight mode". If you want to learn more knowledge, you are welcome to follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report