当前位置:网站首页>Remember the performance optimization with 18 times improvement at one time

Remember the performance optimization with 18 times improvement at one time

2022-06-10 07:28:00 Insect catching master

background

Recently responsible for a self-developed Dubbo The registry often receives CPU Usage alarm , So a wave of optimization , It's not bad , So I'm going to share my thoughts 、 The optimization process , I hope it can help you .

Since the research Dubbo What is a registry , I'll draw a diagram. Just feel it a little , It doesn't matter if you don't understand , Does not affect subsequent understanding .

image

  • Consumer and Provider Service discovery request ( register 、 Cancellation 、 subscribe ) All to Agent, It is its sole agent
  • Registry and Agent keep Grpc Long link , The main purpose of long links is Provider In case of any change , It can be pushed to the corresponding Consumer. In order to ensure the correctness of the data , Made a push-pull combination mechanism ,Agent Will go to... Every once in a while Registry Pull the subscribed service list
  • Agent And business services are deployed on the same machine , similar Service Mesh The idea of , Minimize intrusion into the business , So you can iterate quickly

Back to today's point , This registry recently CPU The utilization rate is in the middle and high water level for a long time , There are occasional app releases , When the push volume is large ,CPU It will even be full .

I haven't felt it before , Because there are not many access applications , In recent months, there have been more and more applications , The alarm threshold is slowly reached .

Looking for optimization points

Because this project is Go Written ( Don't understand, Go My friends don't matter , This paper focuses on the optimization of the algorithm , Not in the use of tools ), Find out where CPU It's quite simple : open pprof that will do , Go online to collect for a period of time .

For specific operation, please refer to my previous This article , The knowledge and tools used in today's article , This article can be found .

image

CPU profile Cut part of the picture , The others are less important , You can see the consumption CPU What's more AssembleCategoryProviders Method , Directly related to it is

  • 2 individual redis Related methods
  • 1 It's called assembleUrlWeight Methods

Explain a little bit ,AssembleCategoryProviders The method is to construct a return Dubbo provider Of url, Because it will return url Do something about it ( For example, adjust the weight ), It will involve understanding this Dubbo url Parsing . And because of the push-pull combination mode , The more users of online services , This one deals with QPS The greater the , So it takes up most of CPU No wonder .

these two items. redis The operation may be that serialization takes up CPU, The bigger head is assembleUrlWeight, It's a bit of a puzzle .

Next, let's analyze assembleUrlWeight How to optimize , Because he occupied CPU most , The optimization effect must be the best .

Here is assembleUrlWeight The pseudo code :

func AssembleUrlWeight(rawurl string, lidcWeight int) string {
	u, err := url.Parse(rawurl)
	if err != nil {
		return rawurl
	}

	values, err := url.ParseQuery(u.RawQuery)
	if err != nil {
		return rawurl
	}

	if values.Get("lidc_weight") != "" {
		return rawurl
	}

	endpointWeight := 100
	if values.Get("weight") != "" {
		endpointWeight, err = strconv.Atoi(values.Get("weight"))
		if err != nil {
			endpointWeight = 100
		}
	}

	values.Set("weight", strconv.Itoa(lidcWeight*endpointWeight))

	u.RawQuery = values.Encode()
	return u.String()
}

The ginseng rawurl yes Dubbo provider Of url,lidcWeight It's the weight of the computer room . According to the configured machine room weight , take url Medium weight Recalculate , Realize the distribution of multi machine room flow according to weight .

This process involves url Parameter parsing , Proceed again weight The calculation of , Finally, it is restored to a url

Dubbo Of url Structure and common url The structure is consistent , Its characteristic is that there may be many parameters , No, # The following fragment .

image

CPU It is mainly consumed in these two parsing and final restore , The purpose of these two analyses is to get url Medium lidc_weight and weight Parameters .

url.Parse and url.ParseQuery All are Go Official library , Each language has its own implementation , Its core is to analyze url For an object , To obtain conveniently url Every part of .

If you understand the concept of information entropy , In fact, you probably know that it must be optimized .Shannon( Shannon ) Drawing on the concept of Thermodynamics , The average amount of information excluding redundancy is called Information entropy .

image

url.Parse and url.ParseQuery In this scenario, there must be redundancy in parsing , Redundancy means CPU Doing superfluous things .

Because a Dubbo url Parameters are usually many , We just need to take these two parameters , and url.Parse All parameters are parsed .

for instance , Given an array , Find the maximum of , If you sort the array first , Taking the maximum value again obviously has redundant operation .

The sorted array can not only take the maximum value , It can also take the second largest value 、 Third largest value ... minimum value , Information is redundant , So sorting first is definitely not the optimal solution for the maximum .

Optimize

Optimize access to url Parameter performance

The first idea is , Don't parse all url, Just take the corresponding parameters , This is very much like the algorithm problem we wrote , Like getting weight Parameters , It can only be these two cases ( non-existent #, So it's a lot simpler ):

  • dubbo://127.0.0.1:20880/org.newboo.basic.MyDemoService?weight=100&...
  • dubbo://127.0.0.1:20880/org.newboo.basic.MyDemoService?xx=yy&weight=100&...

Or &weight=, Or ?weight=, The end is either &, Or go straight to the end of the string , The code is easy to write , First write an algorithm to analyze parameters :

func GetUrlQueryParam(u string, key string) (string, error) {
	sb := strings.Builder{}
	sb.WriteString(key)
	sb.WriteString("=")
	index := strings.Index(u, sb.String())
	if (index == -1) || (index+len(key)+1 > len(u)) {
		return "", UrlParamNotExist
	}

	var value = strings.Builder{}
	for i := index + len(key) + 1; i < len(u); i++ {
		if i+1 > len(u) {
			break
		}
		if u[i:i+1] == "&" {
			break
		}
		value.WriteString(u[i : i+1])
	}
	return value.String(), nil
}

The original method of obtaining parameters can be picked out :

func getParamByUrlParse(ur string, key string) string {
	u, err := url.Parse(ur)
	if err != nil {
		return ""
	}

	values, err := url.ParseQuery(u.RawQuery)
	if err != nil {
		return ""
	}

	return values.Get(key)
}

Let's start with these two functions benchmark:

func BenchmarkGetQueryParam(b *testing.B) {
	for i := 0; i < b.N; i++ {
		getParamByUrlParse(u, "anyhost")
		getParamByUrlParse(u, "version")
		getParamByUrlParse(u, "not_exist")
	}
}

func BenchmarkGetQueryParamNew(b *testing.B) {
	for i := 0; i < b.N; i++ {
		GetUrlQueryParam(u, "anyhost")
		GetUrlQueryParam(u, "version")
		GetUrlQueryParam(u, "not_exist")
	}
}

Benchmark give the result as follows :

BenchmarkGetQueryParam-4          103412              9708 ns/op
BenchmarkGetQueryParam-4          111794              9685 ns/op
BenchmarkGetQueryParam-4          115699              9818 ns/op
BenchmarkGetQueryParamNew-4      2961254               409 ns/op
BenchmarkGetQueryParamNew-4      2944274               406 ns/op
BenchmarkGetQueryParamNew-4      2895690               405 ns/op

You can see that the performance has improved 20 Many times

The newly written method , There are two small details , The first is to distinguish whether the parameter exists in the return value , This one will use ; The second is the string operation, which uses strings.Builder, This is also the result of the actual test , Use + perhaps fmt.Springf The performance is not as good as this , If you are interested, you can test it .

Optimize url Write parameter performance

To calculate the weight I'll put it later weight write in url in , The optimized code is directly given here :

func AssembleUrlWeightNew(rawurl string, lidcWeight int) string {
	if lidcWeight == 1 {
		return rawurl
	}

	lidcWeightStr, err1 := GetUrlQueryParam(rawurl, "lidc_weight")
	if err1 == nil && lidcWeightStr != "" {
		return rawurl
	}

	var err error
	endpointWeight := 100
	weightStr, err2 := GetUrlQueryParam(rawurl, "weight")
	if weightStr != "" {
		endpointWeight, err = strconv.Atoi(weightStr)
		if err != nil {
			endpointWeight = 100
		}
	}

	if err2 != nil { // url Does not exist in the weight
		finUrl := strings.Builder{}
		finUrl.WriteString(rawurl)
		if strings.Contains(rawurl, "?") {
			finUrl.WriteString("&weight=")
			finUrl.WriteString(strconv.Itoa(lidcWeight * endpointWeight))
			return finUrl.String()
		} else {
			finUrl.WriteString("?weight=")
			finUrl.WriteString(strconv.Itoa(lidcWeight * endpointWeight))
			return finUrl.String()
		}
	} else { // url in weight
		oldWeightStr := strings.Builder{}
		oldWeightStr.WriteString("weight=")
		oldWeightStr.WriteString(weightStr)

		newWeightStr := strings.Builder{}
		newWeightStr.WriteString("weight=")
		newWeightStr.WriteString(strconv.Itoa(lidcWeight * endpointWeight))
		return strings.ReplaceAll(rawurl, oldWeightStr.String(), newWeightStr.String())
	}
}

It is mainly divided into url Whether there is weight Two situations to discuss :

  • url There is no such thing as weight Parameters , Directly in url One at the back weight Parameters , Of course, pay attention to whether there is ?
  • url It exists in itself weight Parameters , String replacement is performed directly

Careful, you must have found , When lidcWeight = 1 when , Go straight back to , because lidcWeight = 1 when , The latter calculations actually don't work (Dubbo The default weight is 100), Just don't operate , Save it CPU.

All optimized , Do it overall benchmark:

func BenchmarkAssembleUrlWeight(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for _, ut := range []string{u, u1, u2, u3} {
			AssembleUrlWeight(ut, 60)
		}
	}
}

func BenchmarkAssembleUrlWeightNew(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for _, ut := range []string{u, u1, u2, u3} {
			AssembleUrlWeightNew(ut, 60)
		}
	}
}

give the result as follows :

BenchmarkAssembleUrlWeight-4               34275             33289 ns/op
BenchmarkAssembleUrlWeight-4               36646             32432 ns/op
BenchmarkAssembleUrlWeight-4               36702             32740 ns/op
BenchmarkAssembleUrlWeightNew-4           573684              1851 ns/op
BenchmarkAssembleUrlWeightNew-4           646952              1832 ns/op
BenchmarkAssembleUrlWeightNew-4           563392              1896 ns/op

Maybe it's going up 18 Multiple performance , And this may still be a bad situation , If you pass in lidcWeight = 1, better .

effect

Optimization finished , Write the corresponding unit test for the changed method , After confirming that there is no problem , Go online to observe ,CPU Idle( Idle rate ) Promoted 10% above

image

Last

In fact, this article shows a Go Program very routine performance optimization , It's also relatively simple , After the play , You may have questions :

  • Why do I need to analyze when pushing and pulling url Well ? Can't you calculate it in advance and save it ?
  • Why only optimize this , Can other points be optimized ?

For the first question , In fact, this is a historical problem , That's how he was when you took over the system , If something goes wrong with the program , You change the whole mechanism , The cycle may be long , And it's easy to go wrong

image

The second question is , In fact, I just answered by the way , This optimizes , The smallest change , The most profitable , Other points are not so easy to change , In the short term , The most important thing is to get income . Of course, we also intend to reconstruct the system later , But before refactoring , This optimizes , Enough to solve the problem .

原网站

版权声明
本文为[Insect catching master]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/03/202203021114417883.html