Go代码片段品鉴

这篇具有很好参考价值的文章主要介绍了Go代码片段品鉴。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

作为一个服务端开发,熟悉我们服务的业务至关重要,但我们换工作的时候,也经常会换到别的业务场景下。比方说,我在成人教育的行业工作了2年,跳槽到了支付的行业。这么看的话,业务并不能成为服务端工程师的求职门槛。但我们可能会有被评价过缺少产品 sense,限制我们始终工作在一线。

抛开业务的特殊场景,我们所开发的功能其实大同小异,面临的问题也有很高的重复性。当遇到之前解决过的问题,我经常会回忆之前写过的代码,代码本身可能很烂,但总觉得它经过了线上的考验,是信的过的。但很严肃的结果是,自己完全回忆不起来。

这篇文章就是想细数一些之前写过的代码,或者,遇到的写的比较好的代码(博客中的、或者三方库中的),也可能是罗列一些代码的问题。总之,就是不成体系的代码片段。

Go协程封装

下面的代码展示的是启用协程的常规操作,为了处理协程内可能的 panic,在方法体第一行总需要 defer recover 这样的组合,当捕获异常时,还需要日志打印调用堆栈的信息。

这种写法其实非常不美观,对于有代码洁癖的人来说,如果在同一个函数体内赋值拷贝2次以上,他就会感觉很崩溃。有些没有代码洁癖的人,可能就是按需拷贝N次了,这对于有代码洁癖的人而言,会是更加崩溃的事情。毕竟,一个项目都是多个人共同在维护的,不是每个人都有代码洁癖。

go func() {
		defer func() {
			if r := recover(); r != nil {
				// 打印堆栈信息
			}
		}()

		// 异步逻辑处理
	}()

所以,很多人就开始想把 go defer recover 封装起来,下面就是其中的一种,异步调用 fun 函数,并明确传递需要的 args 参数,方法内部也做了简单的参数判断,最后,通过反射函数 Call 执行函数 fun。代码为了做到兼容,引入了反射,但也因为反射,会损失掉一部分性能。

这个封装除了性能,当发生 panic 时,打印堆栈日志的部分可能需要特别注意。一般来说,我们的日志都会输出打印日志的文件名和行号,但通过这样的封装,发生错误的文件和行号都变成了 GoFunc 所在的文件和行号。如果要变得更完善,还需要调整堆栈的层级。

func GoFunc(fun interface{}, args ...interface{}) (err error) {
	v := reflect.ValueOf(fun)
	go func(err error) {
		defer func() {
			if r := recover(); r != nil {
				// 打印堆栈日志
			}
		}()
		
		switch v.Kind() {
		case reflect.Func:
			pps := make([]reflect.Value, 0, len(args))
			for _, arg := range args {
				pps = append(pps, reflect.ValueOf(arg))
			}
			v.Call(pps)
		default:
			err = errors.New(fmt.Sprintf("func is not func, type=%v", v.Kind().String()))
		}
	}(err)
	return
}

IN 分批查询

SQL 查询时可能会遇到 IN 查询的情况,IN 中参数多少一般是由具体的业务请求来决定的,一般用户可能 IN 后面跟小于 5 个索引,特殊用户 IN 后面可能多一些。考虑一些极端的场景,如果 SQL 的 IN 中包含了成百上千个索引,我们要不要去查询底层数据?直接查询数据会引起什么问题吗?

引申个题外话,对于用户的任意请求,我们要做好防守,防守一些极端 case。 SQL 语句 LIMIT 就是一个很好的防守例子,我们给 LIMIT 设置一个预期内的最大值,如果用户的查询超过了这个最大值限制,就直接替换为这个预期内的最大值。

回归正题,IN 参数特别多会导致底层数据库的负载压力不均衡,命中极端 IN 查询的服务 CPU 可能会瞬间拉高,影响服务的其他查询。和大 KEY 的场景差不多,存储大 KEY 或者热 KEY 的服务,负载就会比其他服务高一些。

解决的思路也很简单,在调用端采用分批请求的方式,人为的将一次 IN 查询拆分成多次 IN 查询,关键点在于解决条件的拆分,以及结果集的合并。将一次请求拆分成并发的多次请求,最后将各路请求的结果进行合并,就是我们的工作。

下面展示了拆分的具体代码实现,注意,代码只是一个拆分模式,并不能直接运行。而且,代码还存在其他缺陷,没有支持上泛型、分批查询的数量限制也不能动态设置,但我觉问题不大,看关键点就够了。

func PatchQuery(ctx context.Context, keys []string,
	query func(ctx context.Context, keys []string, result *sync.Map)) *sync.Map {
	
	var result sync.Map
	var wg sync.WaitGroup

	patchSize := 64
	length := len(keys)

	for i := 0; i < length; {
		if i+patchSize >= length {
			patch := keys[i:]
			query(ctx, patch, &result)
			break
		}

		wg.Add(1)
		go func(index int) {
			defer wg.Done()

			patch := keys[index : index+patchSize]
			query(ctx, patch, &result)
		}(i)

		i += patchSize
	}

	wg.Wait()
	return &result
}

因为内部存在并发结果的合并,简单的引入了 sync Map 来解决并发。用 map 还有一个原因,就是 key 值可以通过 IN 的索引进行构建,通过 key 来查询到具体的结果。无论 IN 的条件是否是唯一索引,我们都可以正确的处理返回结果。

正确的使用切片是这里的重点,切片中的 [a:b] 是半闭半开的区间范围,一定不要混淆。另外,切片的最后一次查询是否要启用新的协程也值得考量,万事没绝对,我倒是觉得,不用是比较适当的。

耗时方法统计

经常看到使用 defer 统计代码块的耗时,相比较直来直往的统计,defer 看起来高级不少,也更能体现一些技术技巧。还是循序渐进地来看代码:

st := time.Now()
// 代码块
cost := time.Since(st).Milliseconds()

这是最基本的原型代码,所有的高级代码都围绕上述两行代码展开。经常会看到有部分人会借用匿名函数做一层耗时统计封装,怎么评价这个封装呢?

所有的方法都需要调用 Rt,在 Rt 中创建一个匿名函数,匿名函数中执行我们的业务代码。无法透出被包裹函数的返回值是一个比较大的缺陷,换一个描述角度,就是做到无法细粒度得统计方法的耗时。

func Rt(process func()) {
	st := time.Now()

	process()

	cost := time.Since(st).Milliseconds()
	fmt.Printf("cost:%dms\n", cost)
}

目前来看,最优的耗时统计方法当属下面的代码封装。利用 defer 函数在 return 之后执行的特性,我们可以将耗时的统计都封装在 defer 执行的函数中。

func StaticRt() func() {
	st := time.Now()
	return func() {
		cost := time.Since(st)
		// log
		// fmt.Println(cost)
	}
}

使用起来非常便捷,如下所示。我们只需要在函数体的第一行执行 defer StaticRt 函数,StaticRt 代码注释的 log 部分需要结合业务来填充。

特别需要注意的一点,defer 后执行 StaticRt 方法最末尾必须跟一个括号,因为 defer 实际要执行的是 StaticRt 函数返回的方法,而非 StaticRt 函数本身。另外,为了保证 StaticRt 函数会被编译器执行,也不可以使用一个匿名函数来包裹 StaticRt 函数。

func main() {
	defer StaticRt()()
	time.Sleep(time.Second)
}

代码的工作原理特别简单,StaticRt 充分利用闭包的方式,记录下程序开始执行的时间。在函数 return 之后,defer 函数开始执行,用当前的时间减去 st 中保存的开始时间就是函数的执行耗时。

这里说到闭包,本质上就是影响了函数的传值方式,我们通过简单的举例,来看看闭包的功效。左图传递给 increment 的时候参数 i 发生了值拷贝,右图参数 i 操作的还是外层声明的变量 i 本身。文章来源地址https://www.toymoban.com/news/detail-496636.html

打印结果:13 打印结果:14
Go代码片段品鉴 Go代码片段品鉴

到了这里,关于Go代码片段品鉴的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【30天熟悉Go语言】5 Go 基本数据类型

    Go系列文章: GO开篇:手握Java走进Golang的世界 2 Go开发环境搭建、Hello World程序运行 3 Go编程规约和API包 4 Go的变量、常量、运算符 Go专栏传送链接:https://blog.csdn.net/saintmm/category_12326997.html 基本数据类型大体来看有四种:数值型、字符型、布尔型、字符串。数值型又分为整数类

    2024年02月10日
    浏览(39)
  • 作为开发人员,无代码开发平台 iVX 你有必要了解一下

    低代码开发平台作为一种快速、简化应用程序开发的方法,正在越来越受到关注。今天我们来了解下 iVX —— 首个通用无代码开发平台。 那么什么是iVX呢?下边的图就比较形象了。 大的未来都是AI ,AI , AI …,理论上不可能有别的。 就拿iVX来说吧,已经做了一整套完整的可

    2024年02月15日
    浏览(68)
  • 【30天熟悉Go语言】9 Go函数全方位解析

    作者 :秃秃爱健身,多平台博客专家,某大厂后端开发,个人IP起于源码分析文章 😋。 源码系列专栏 :Spring MVC源码系列、Spring Boot源码系列、SpringCloud源码系列(含:Ribbon、Feign)、Nacos源码系列、RocketMQ源码系列、Spring Cloud Gateway使用到源码分析系列、分布式事务Seata使用到

    2024年02月10日
    浏览(63)
  • 【30天熟悉Go语言】6 Go 复杂数据类型之指针

    Go系列文章: GO开篇:手握Java走进Golang的世界 2 Go开发环境搭建、Hello World程序运行 3 Go编程规约和API包 4 Go的变量、常量、运算符 5 Go 基本数据类型 Go专栏传送链接:https://blog.csdn.net/saintmm/category_12326997.html 和 C/C++ 中的指针不同,Go中的指针不能进行偏移和运算。它是一种类型

    2024年02月09日
    浏览(40)
  • 【30天熟悉Go语言】4 Go的变量、常量、运算符

    Go系列文章: GO开篇:手握Java走进Golang的世界 2 Go开发环境搭建、Hello World程序运行 3 Go编程规约和API包 Go专栏传送链接:https://blog.csdn.net/saintmm/category_12326997.html 变量相当于内存中一个数据存储空间的标识。 变量的使用分三步:声明、赋值、使用。 变量的声明 采用 var 变量名

    2024年02月07日
    浏览(45)
  • 【30天熟悉Go语言】3 怀着Java看Go的编程规约

    Go系列文章: GO开篇:手握Java走进Golang的世界 2 Go开发环境搭建、Hello World程序运行 Go专栏传送链接:https://blog.csdn.net/saintmm/category_12326997.html 1 源文件以 .go 结尾 2 程序的执行入口是main()函数 3 严格区分大小写 4 方法由一条条语句构成,每个语句后不需要加分号(GO会在每行后

    2024年02月06日
    浏览(45)
  • 微软和OpenAI联手推出了GitHub Copilot这一AI编程工具,可根据开发者的输入和上下文,生成高质量的代码片段和建议

    只需要写写注释,就能生成能够运行的代码?对于程序员群体来说,这绝对是一个提高生产力的超级工具,令人难以置信。实际上,早在2021年6月,微软和OpenAI联手推出了GitHub Copilot这一AI编程工具。它能够根据开发者的输入和上下文,生成高质量的代码片段和建议。这个工具

    2024年02月09日
    浏览(71)
  • 【30天熟悉Go语言】7 Go流程控制之分支结构if、switch

    Go系列文章: GO开篇:手握Java走进Golang的世界 2 Go开发环境搭建、Hello World程序运行 3 Go编程规约和API包 4 Go的变量、常量、运算符 5 Go 基本数据类型 6 Go 复杂数据类型之指针 Go专栏传送链接:https://blog.csdn.net/saintmm/category_12326997.html if 语句由布尔表达式后紧跟一个或多个语句组

    2024年02月09日
    浏览(38)
  • 【开发日记】换掉Nginx,使用HAProxy作为反向代理服务器

    HAProxy,全称为 \\\"High Availability Proxy\\\",是一款开源的、高性能的负载均衡器和代理服务器。主要用于改善应用程序的可用性、可靠性和性能。 与大众熟知的Nginx相比各有优缺点,如果你需要的是一个Web服务器,还是首选Nginx,虽然HAProxy也能实现相同效果,但却需要通过十分繁琐

    2024年01月25日
    浏览(62)
  • VS代码片段(CodeSnippet)的制作以及常用代码片段记录

    本文将会探索VS中代码片段,并且学会自己制作代码片段,通过这种方式提高我们的编程效率。 先看下下面这段代码的编写 是不是很熟悉?平常我们编写代码的使用,经常会使用prop,ctor等快速生成代码。 那么它到底是啥呢?我们能不能自己也写一个这些的快捷生成代码的方

    2024年02月08日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包