GO是编译性语言,所以函数的顺序是无关紧要的,为了方便阅读,建议入口函数 main 写在最前面,其余函数按照功能需要进行排列
GO的函数 不支持嵌套,重载和默认参数
GO的函数 支持 无需声明变量,可变长度,多返回值,匿名,闭包等
GO的函数用 func 来声明,且左大括号 { 不能另起一行
一个简单的示例:
输出为:
参数:可以传0个或多个值来供自己用
返回:通过用 return 来进行返回
输出为:
上面就是一个典型的多参数传递与多返回值
对例子的说明:
按值传递:是对某个变量进行复制,不能更改原变量的值
引用传递:相当于按指针传递,可以同时改变原来的值,并且消耗的内存会更少,只有4或8个字节的消耗
在上例中,返回值 (d int, e int, f int) { 是进行了命名,如果不想命名可以写成 (int,int,int){ ,返回的结果都是一样的,但要注意:
当返回了多个值,我们某些变量不想要,或实际用不到,我们可以使用 _ 来补位,例如上例的返回我们可以写成 d,_,f := test(a,b,c) ,我们不想要中间的返回值,可以以这种形式来舍弃掉
在参数后面以 变量 ... type 这种形式的,我们就要以判断出这是一个可变长度的参数
输出为:
在上例中, strs ...string 中, strs 的实际值是b,c,d,e,这就是一个最简单的传递可变长度的参数的例子,更多一些演变的形式,都非常类似
在GO中 defer 关键字非常重要,相当于面相对像中的析构函数,也就是在某个函数执行完成后,GO会自动这个;
如果在多层循环中函数里,都定义了 defer ,那么它的执行顺序是先进后出;
当某个函数出现严重错误时, defer 也会被调用
输出为
这是一个最简单的测试了,当然还有更复杂的调用,比如调试程序时,判断是哪个函数出了问题,完全可以根据 defer 打印出来的内容来进行判断,非常快速,这种留给你们去实现
一个函数在函数体内自己调用自己我们称之为递归函数,在做递归调用时,经常会将内存给占满,这是非常要注意的,常用的比如,快速排序就是用的递归调用
本篇重点介绍了GO函数(func)的声明与使用,下一篇将介绍GO的结构 struct
单从运行速度两个语言都很优秀,但运行速度不是我们唯一追求的指标,就像汇编语言写的程序运行速度很快,但我们绝大多场合都不会采用它。背后更重要的还有这门语言对应的生态。
比如企业要用人的时候能不能快速招到会使用它的人? 对求职者来说,我学习这门语言能不能带来更多的工作机会?用这个语言开发应用的时候遇到问题的时候能不能快速找到资料并得以解决?
go语言历史长、生态好、运行速度快,在服务端上面有所作为,比如为比特币、莱特币开发的闪电网络就是用go写的。 语法上和类C语言有很大不同。
Dart是新出的语言,配合flutter是很好的客户端开发语言。 语法没那么奇怪, 因为新所以生态自然是跟不上的。
Dart能不能用在服务端? 我个人觉得是可以, 毕竟可以Dart是可以转成javascript的,运行在Nodejs下, 借助于NodeJs生态,dart也可以做很多事情。
不知道你们有没有注意到,现在有很多新语言都支持翻译成Javascript。比如新的dart、kotlin, 还有ts是直接针对javascript语法加强而获得成功。
话说得再多, 不如自己亲自去体验一下。 jsrun上面支持30多种语言的在线运行环境, 先体验再做决定你到底要学哪一门语言。
http://dart.jsrun.net
http://go.jsrun.net
有个服务会大量使用延迟消息,进行事件处理。随着业务量不断上涨。在晚间、节假日等流量高峰期消息延迟消息队列限流会导致事件丢失,影响业务。与下游沟通后给上调到了最大限流值,问题依然存在,于是决定自己搞一套降级方案。
下游服务触发限流时,能降级部分流量到本地延迟队列,把业务损失降到最低。
本地延迟队列承接部分mq流量
流程如下:
1. 使用zset 存储延迟消息,其中:score为执行时间,value为消息体
2. 启动协程轮询zset,获取score最小的10条数据,协程执行间隔时间xs
如果最小分值小于等于当前时间戳,则发送消息
若最小分值大于当前时间戳,sleep等待执行
需要对key进行hash,打散到多个分片中,避免大key和热key问题,官方大key定义
因此,需保证每个key中value数量n<5000,单个value大小不超过 10240/n kb
假设承接10w qps,如何处理?
10w qps延迟120s时,最开始消息队列会积累100000*120=12000000条消息
假如每条消息大小500b,需占用存储6000000kb = 6000Mb = 6GB
为避免大key问题,每个zset存放4000个元素,需要哈希到3000(3000是key的数量,可配置)个zset中。
整个集群假设500台实例,每个处理qps平均在200左右。
单实例消费能力计算:
遍历每个zset,针对每个zset起goroutine处理,此示例中需要 起3000个
但是每秒能处理成功的只有200个,其他都在空跑
综上:
将redis key分片数n和每次处理的消息数m进行动态配置,便于调整
当流量上涨时,调大分片数n和单实例单分片并发数m即可,假如消费间隔200ms,集群处理能力为n*m*5 qps
n = (qps * 120) / 4000
若qps=q,则计算公式如下
zadd = q
zRange = 500 * 5 * n / 500
zRemove = q
setNx = 500 * 5 * n
若10w qps,则
读qps = 15000 + 500*3000*5 =7515000,写 20w
pros
redis 读写性能好,可支持较大并发量,zrange可直接取出到达执行时间的消息
cons
redis 大key问题导致对数据量有一定的限制
分片数量扩缩容会漏消费,会导致事件丢失,业务有损
key分片数量过多时,redis读写压力较大
机器资源浪费,3000个协程,单实例同一秒只有200个针对处理,其他都在空跑
流程如下:
使用带缓冲的channel来实现延迟队列,channel中存放的数据为消息体(包括执行时间),channel能保证先进先出
从channel中取出数据后,判断是否到达执行时间
到达,同步发送mq
未到达,sleep 剩余执行时间,然后再次执行
从channel读出的数据如果未到达执行时间,无法再次放入channel中,需要协程sleep(执行时间-当前时间)
10w qps延迟120s时,最开始消息队列会积累100000*120=12000000条消息,假设每条消息大小500b,需要6G存储空间
channel 大小 = (qps*120)/ c , c=集群实例数,c=500 =>channel大小为24000,占用12M内存
要处理10w qps,分摊到每个机器的处理速度为 100000/500 = 200,假设单协程处理10qps,开20个即可。
pros:
本地存储,相比redis,读写速度更快;协程数量少,开销低;资源利用率较方案一高
cons:
稳定性不如redis,实例故障可能导致数据丢失;worker池和channel扩缩容依赖服务重启,成本高速度慢
综上,我们以10w qps为例,对比两种方案在以下指标差异,选择方案二。
附上demo