> 2021年05月31日信息消化 ### 每天学点Golang #### Top 5 Lessons I learned while working with Go for two years https://sayedalesawy.hashnode.dev/top-5-lessons-i-learned-while-working-with-go-for-two-years ##### 1. Take the Go highway to Concurrency 在我看来,Go作为一种语言的魅力所在(除了它的简单性和接近C语言的性能),就是它能够轻松编写并发代码。Goroutines是Go编写并发代码的方式。goroutine是一个轻量级的线程,或者叫做绿色线程,是的,它不是一个内核线程。goroutine是绿色线程,因为它的调度完全由Go运行时管理,而不是操作系统。Go调度器负责将goroutines复用到真正的内核线程上,这样做的好处是使goroutines在启动时间和内存需求方面变得非常轻盈,这使得Go应用程序可以运行数百万个goroutines! In my opinion, what makes Go very appealing as a language (other than it's simplicity and near C performance), is its ability to easily write concurrent code. **Goroutines** are the Go way of writing concurrent code. A goroutine is a **light-weight thread** or otherwise called a **green thread**, and yes, it's not a kernel thread. Goroutines are green-threads in the sense that their scheduling is entirely managed by the **Go runtime** and not the OS. The Go scheduler is responsible for multiplexing goroutines onto real kernel threads, this has the benefit of making goroutines very light weight in terms of their startup time and memory requirements which allows a go application to run **millions** of goroutines! ##### 2. If it can be singleton, then make it singleton, but do it right! Go应用程序可能需要访问数据库或缓存等,这些资源都有连接池,也就是说,对该资源的并发连接数是有限制的。根据我的经验,Go中的大多数连接对象(数据库、缓存等)都是作为线程安全的连接池构建的,可以被多个goroutine同时使用,而不是单一的连接。 A Go application will probably have to access a **Database** or a **Cache**, etc. which are examples of resources that have connection pools, meaning, that there is a limit on the number of concurrent connections to that resource. From my experience, most connection objects (database, cache, etc.) in Go are built as thread-safe pool of connections that can be used by multiple goroutines concurrently as opposed to a single connection. 假设我们有一个Go程序,通过*sql.DB对象访问mysql数据库,该对象本质上是一个数据库的连接池。如果应用程序有许多goroutine,那么创建一个新的*sql.DB对象是没有意义的,事实上这可能会导致连接池的耗尽(注意,使用后不关闭连接也会导致这种情况)。因此,代表连接池的*sql.DB对象必须是单子,所以即使一个goroutine试图创建一个新的对象,它也会返回同一个对象,从而不允许有多个连接池。 So let's say we have a Go application that accesses as a `mysql` database via a `*sql.DB` object which is essentially a pool of connections to the database. If the application has many goroutines, it wouldn't make sense to create a new `*sql.DB` object, in fact this might cause connection pool exhaustion (note that not closing connections after usage can also cause this). So it makes sense that the `*sql.DB` object representing the connection pool has to be singleton, so even if a goroutine tried to create a new object, it will return the same object and thus disallowing having multiple connection pools. ```go var dbOnce sync.Once var dbInstance *DB func DBConnection() *DB { dbOnce.Do(func() { dbInstance = &sql.Open(...) } return dbInstance } ``` 这段代码使用了一个叫做sync.Once的Go结构,它允许我们编写一个只被执行一次的函数。这样一来,即使多个goroutine试图同时执行,也能保证连接创建段完全运行一次。 This code uses a Go construct called `sync.Once` which allows us to write a function that will only be executed once. **This way, the connection creation segment is guaranteed to run exactly once even if multiple goroutines attempt to execute it concurrently**. ##### 3. Beware of Blocking Code 有时您的 Go 应用程序会执行阻塞性调用,可能是对可能没有响应的外部服务的请求,或者是对在某些条件下阻塞的函数的调用。作为一个经验法则,永远不要假设调用会在适当的时候返回,因为有时候它们不会返回。 Sometimes your Go application will perform **blocking** calls, maybe requests to external services that might not respond or maybe calls to functions that block on certain conditions. As a rule of thumb, **never assume that calls will return in due time**, because well, sometimes they don't. Go有一个由google建立的名为context的包,它允许我们将请求范围的值、取消信号和超时传递给所有参与处理请求的goroutines。我们可以按以下方式使用它。 ```go ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() req := req.WithContext(ctx) res, err := c.Do(req) ``` 前面的代码允许设置每个请求的超时,如果需要的话,还可以取消请求或传递每个请求的值。如果发送请求的goroutine催生了其他goroutine,而这些goroutine在请求超时时都需要退出,那么这就特别有用。 在这个例子中,我们很幸运,http包支持上下文,但如果我们处理的是一个不支持上下文的阻塞函数呢?有一种方法可以使用Go的select语句和Go通道来实现超时逻辑(再次!)。考虑一下下面的代码。 The previous code allows to set a per-request timeout and also be able to cancel the request or pass per-request values if needed. This is particularly helpful if the goroutine sending the request spawned other goroutines that all need to exit if the request times out. In this example, we were lucky that the http package supports contexts, but what if we are dealing with a blocking function that doesn't? There is an adhock way to implement timeout logic using Go's select statement along with Go channels (again!). Consider the following code: ```go func SendValue(){ sendChan := make(chan bool, 1) go func() { sendChan <- Send() }() select { case <-sendChan: case <-time.After(time.Minute): return } //continue logic in case send didn't timeout } ``` ##### 4. Graceful Termination and Clean Up 如果你正在编写一个长期运行的进程,例如,一个网络服务器或一个后台作业工作者等,你就有被突然终止的风险。这种终止可能是因为进程被调度器终止了,甚至是你发布了新的代码,你正在推出它。 If you are writing a **long running process**, for example, a web server or a background jobs worker, etc. you are running the risk of being terminated abruptly. This termination could be because the process was terminated by the scheduler or even if you're releasing new code and you are rolling it out. 一般来说,一个长期运行的进程在其内存中可能有一些数据,如果进程被终止,这些数据就会丢失,或者它可能持有一些需要释放到资源池中的资源。因此,当一个进程被杀死时,我们需要能够执行优雅的终止。优雅地终止一个进程意味着我们拦截杀戮信号,并执行特定的应用程序关闭逻辑,以保证在实际终止之前一切正常。 Generally, a long running process might have data in its memory that will be lost if the process was to be terminated or it might be holding resources that need to be released back to the resources pool. So when a process is killed we need to be able to perform **graceful termination**. Gracefully terminating a process means that we intercept the `kill signal` and perform application specific **shutdown** logic that guarantees that everything is in order before actually terminating. 因此,假设我们正在建立一个网络服务器,我们通常希望它能够像这样运行。 So let's say we are building a web server which we will usually want to be able to run like so: ```go server := NewServer() server.Run() ``` 这里的关键思想是无限运行的Run()函数,在这个意义上,如果我们在主函数的末尾调用它,进程将不会退出,只有当Run()函数退出时,它才会退出。 服务器逻辑可以被实现为检查关闭信号,并且只有在收到关闭信号时才退出,如下所示。 The key idea here is that the `Run()` function that runs infinitely, in the sense that if we call it at the end of our `main` function, the process will not exit, it will only exit if the `Run()` function exits. The server logic can be implemented to check for a shutdown `signal` and only exit if a shutdown `signal` was received as follows: ```go func (server *Server) Run() { for { //infinite loop select { case <- server.shutdown: return default: //do work } } } ``` 正如我们在前面的代码中所看到的,服务器循环检查是否有信号被送入关闭通道,在这种情况下,它退出服务器循环,否则它继续执行其工作。 As we can see in the previous code the server loop checks if a signal was sent down the shutdown channel in which case it exits the server loop, otherwise it continues to perform its work. 现在,难题中唯一缺少的部分是能够拦截操作系统中断,如(SIGKILL或SIGTERM),并调用Server.Shutdown(),执行关闭逻辑(将内存刷入磁盘,释放资源,清理等),并发送关闭信号以终止服务器循环。我们可以通过以下方式实现。 Now the only missing piece in the puzzle is being able to intercept OS interrupts such as (`SIGKILL` or `SIGTERM`) and call `Server.Shutdown()` which performs shutdown logic (flush memory to disk, release resources, clean up, etc.) and sends the shutdown signal to terminate the server loop. We can achieve this as follows: ```go func main() { signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt) server := NewServer() server.Run() select { case <-signals: server.Shutdown() } } ``` 前面的代码创建了一个os.Signal类型的缓冲通道,并在操作系统中断发生时向该通道发送一个信号。主函数的其余部分运行服务器并在通道上阻塞等待。当收到一个信号时,这意味着发生了一个操作系统中断,而不是立即退出,而是调用服务器的关闭逻辑,给它一个优雅地终止的机会。 The previous code creates a `buffered channel` of type `os.Signal` and sends a `signal` down the channel when an OS interrupt occurs. The rest of the main function runs the server and blocks waiting on the channel. When a signal is received, this means that an OS interrupt took place, **instead of exiting immediately, it calls the server shutdown logic giving it a chance to gracefully terminate**. ##### 5. Go Modules FTW 你的Go应用程序会有外部依赖,这是非常常见的,例如,你正在使用mysql驱动或redis驱动或任何其他软件包。当你第一次构建你的应用程序时,构建过程将获得这些依赖的每个最新版本,这很好。现在你构建了你的二进制文件,你测试了它,它工作了,所以你去生产。 It's very common that your Go application will have **external dependencies**, for example, you're using a **mysql driver or a redis driver** or any other **package**. When you first build your application, the build process will get the latest version of each of these dependencies, which is great. Now you built your binary, you tested it and it works, so you went to production. 一个月后,你需要添加一个新的功能或做一个热修复,这可能不需要一个新的依赖,但需要重新构建应用程序以产生新的二进制文件。构建过程也会得到每个所需软件包的最新版本,但这个版本可能与你第一次构建时得到的版本不同,可能包含会导致应用程序本身损坏的破坏性变化。所以很明显,我们需要一种方法来管理这个问题,即除非你明确选择升级,否则总是获得相同版本的每个依赖项。 A month later, you needed to add a new feature or do a hotfix which might not require a new dependency but **will require re-building the application** to produce the new binary. The build process will also get the **latest** version of the each of the needed packages, but this **version might be different** from the version you got on your first build and might contain **breaking changes** that will cause the application itself to break. So it's clear that we need a way to manage this by always **getting the same version of the each dependency unless you explicitly opt to upgrade**. Go.11引入了go.mod,这是处理Go中依赖性版本的新方法。当你在你的应用程序中启动Go mod,然后构建它时,它将自动产生一个go.mod文件和一个go.sum文件。mod文件看起来像这样。 Go.11 introduced `go.mod` which is the new way to handle dependency versioning in Go. When you init a Go mod in your application and then build it, it will automatically produce a `go.mod` file and a `go.sum` file. The mod file looks something like this: ```go module github.com/org/module_name go 1.14 require ( github.com/go-sql-driver/mysql v1.5.0 github.com/onsi/ginkgo v1.12.3 // indirect gopkg.in/redis.v5 v5.2.9 gopkg.in/yaml.v2 v2.3.0 ) ``` 我们可以看到,它锁定了Go的版本和每个依赖的版本,因此,例如,即使redis repo发布了v5.3.0的最新版本,我们在每次构建时都会得到redis v5.2.9,这保证了稳定性。请注意,第二个被标记为间接的依赖关系,这意味着它不是由你的应用程序直接导入的依赖关系,而是由它的一个依赖关系导入的,它也锁定了它的版本。 As we can see that it locks the Go version along with the version used for each dependency, so for example we will always get redis v5.2.9 with every build even if the redis repo released v5.3.0 as their latest version which guarantees stability. Notice that the second dependency that's labeled indirect which means that's a dependency that's not directly imported by your application but imported by one of its dependencies and it also locks its version. 使用Go mods还有很多其他好处,例如。 - 它自动运行go mod tidy,删除任何不需要的依赖。 - 它允许你在任何目录下运行代码(在go mods之前,一个go项目必须放在一个特定的目录下)。 - 可以导入到一个全新的项目中。这在你决定将一些逻辑封装到一个包中时特别有用,例如一个日志包,并在你所有的其他项目中导入它,使该日志功能在多个代码库中可用。 There are many other benefits to using Go mods, for example: - It automatically runs go mod tidy which removes any unneeded dependencies. - It allows you to run the code from any directory (prior to go mods, a go project had to be placed in a specific directory). - It wraps the whole application into a module that can be imported into a brand new project. This particularly useful in case you decided to wrap some logic into a package for example a logger package and just import it in all your other projects to make that logging functionality available across multiple code bases. ### 其他值得阅读 #### 如何使用元认知技能来记住90%的阅读内容 原文:[How to Use Metacognition Skills to Remember 90% of What You Read](https://medium.com/personal-growth/how-to-use-metacognition-skills-to-remember-90-of-what-you-read-ac5245ece600) These are great books that require multiple reads to deeply understand the fantastic ideas the authors want us to comprehend. Successful reading requires metacognition When you apply metacognition to reading: 1. You make time to analyse the content and reflect on what you are reading. 2. You ask critical, challenging and analytical questions whilst reading. 2. You make time to figure out what you already knew before reading and what you want to improve. 3. You’ve thought about what to do to retain more of what you read 4. You plan on applying some of the ideas in the book in your life. Successful readers use metacognition to understand what they want from books. They also use it to improve their reading experience. ##### Learn to read and think at the same time Most great books require an investment of 5–10 hours to absorb the content thoroughly. If you plan to get most of every book (mainly non-fiction) you buy, you must have a reading plan. **To improve retention, many successful and deep readers:** 1. Take personal notes whilst they are reading. 2. Highlight the best ideas, especially those that make them think differently. 3. Underline important ideas they can refer to in the future. 4. Summarise every chapter they complete. 5. Discuss the topic with others to learn more about what they missed 6. Teach the new ideas by writing about them. #### Feeling overwhelmed? You need an MVD (Minimum Viable Day) https://snapcrackle.medium.com/feeling-overwhelmed-you-need-a-mvd-minimum-viable-day-bf07b7b90eb ##### How to have a great Minimum Viable Day: the 5 P’s 理解拥有最小可行日的目的,并在任何时候都牢记这一点。 以下是构成MVD的一些关键品质。 - **提供一些价值**=实现一些算作胜利的小事情。获得一些休息时间。 - **精益求精** =做绝对的最低限度。确定优先次序是关键(见下文)。 - **发货** = 做好当天的工作,并通过它。不要放弃。 - **有洞察力** =把它作为一种学习练习。要有好奇心,即使你不觉得自己想做什么其他事情。 ##### 1. Be clear about the Purpose Understand the purpose of having a Minimum Viable Day and keep this in mind AT ALL TIMES. Here are some key qualities that make up the MVD : - **Delivers some value** = Achieve some small things that count as a win. Gain some rest time. - **Lean** = Do the absolute minimum. Prioritisation is key (see below) - **Shipped** = Do the day and get through it. Don’t give up. - **Insightful** = Use it as a learning exercise. Be curious, even if you don’t feel like doing much else. 在我们进一步讨论之前,我有一件重要的事情要说:如果你已经精疲力尽,或者正在这样做的路上,你绝对需要适当休息,充分休息。你这样做是超级重要的,否则事情会变得非常糟糕(我知道,我曾经经历过)。 MVD不是替代适当的休息时间。相反,它是一种补救措施,如果你醒来时感觉无精打采,感觉有点焦虑,需要一点能量来让你度过这一周。 Before we go further, I have an important thing to say: if you are burned out or on your way to do so, you absolutely need to take proper time off and rest fully. It’s super important you do this, otherwise things can get very bad (I know, I have been there). The MVD is not a replacement for proper time off. Rather, it’s a remedy for if you wake up feeling blah, feeling a bit anxious, and need a bit of an energy top up to get you through the week. 如果你仍然觉得可以发送几封电子邮件和做一些事情,但不觉得你可以应付任何重要的事情--你是最小可行日(或几天,或一周,或一个月--无论你需要什么)的完美候选人。 If you’re still feeling up to sending a few emails and doing a few bits, but don’t feel like you can cope with anything major — you are the perfect candidate for a Minimum Viable Day (or days, or week, or month — whatever you need). ##### 2**. Prioritise** (ruthlessly) 如同在产品管理中,你必须无情地在MVD中确定你的可交付成果的优先次序。使用MoSCoW原则是成功的关键。**MoSCoW**代表必须有、应该有、可能有、不可能有。 As in Product Management, you have to be ruthless at prioritising your deliverables in the MVD. Using the MoSCoW principle is the key to success here. **MoSCoW** stands for Must Have, Should Have, Could Have, Won’t have. 我告诉过你,我们喜欢缩略语。 I told you we like acronyms. 那么,这在实践中是什么意思?从本质上讲,对于你的MVD,你要只关注你绝对必须做的**必须行动/产出**,以防止混乱的发生。 So what does this mean in practice? Essentially, for your MVD, you want to only focus on the **Must Have** actions / outputs that you absolutely must do in order to prevent chaos breaking out. 这里有一些关于MVD中必须做的事情的建议。 Here are some suggestions for **Must Have** things to do on your MVD: - 发送你最重要的电子邮件/ Send your most important emails - 做一个你绝对不能推迟的大会议 / Do that one big meeting that you absolutely cannot postpone - 在自我保健方面也要有一些必备的东西。我建议去散步、做伸展运动或吃蛋糕。/ Bake in some self-care must haves too. I would advise a walk, a stretch or eating a cake. Here are your **Won’t Have / Won’t Do** items: - Literally anything that can be done tomorrow instead - Beat self up You might define a few **Should / Could haves**, that you should ONLY do if a) the Must Haves get done and b) you have the time / energy to do them. But feel free to pop them in the Won’t Do column. Remember, be ruthless here. ##### 3**. Postpone** anything that can be done tomorrow As mentioned above, it’s important to be clear about what can be postponed to another day. For high performers like yourself, this might feel a bit uncomfortable. But remember — we’re on an MVD here. - What can I do tomorrow / next week for when I gave regained my energy? - Who do I need to be honest with about how I’m feeling today, so that they help me move a few things off my plate? - Who could stand in for me? - What can be binned off all together that I shouldn’t have been doing anyway (this one’s a goodie) ##### **4.** Measure **Performance** based on your original objectives Do not be tempted to measure your day based on different success measurements to the ones you set out: - Did I do the day? - Did I not kill anyone? - Do I feel ok? - Can I do tomorrow? (i.e did the product — you — not fully break and fall over so that we can iterate tomorrow?) If the answers to all of the above are ‘yes’ — congratulations, you’ve launched a successful MVD! Boom. No self hatred, no “I should have done xyz”. No one gets angry at the MVP for not doing everything. Its purpose is to exist. In MVD terms — We did it! We did a day! ##### 5. Ponder (reflect, be curious) In Product Management, the purpose of launching a Minimum Viable Product is to learn from the feedback of customers, and to iterate on it in the next release. Once the product is launched and the feedback is in, Product managers will analyse and brainstorm on what the data means for future development. In your Minimum Viable Day, it’s very valuable to use the time to **ponder —** i.e to **be curious and reflect**: - Maybe use some of the day to be reflective about why you’re feeling run down, and how you can avoid anxiety days in the future? - Maybe reflect on what’s going well and what you might want to achieve the rest of the week if you feel up to it? - Maybe reflect on what you want to achieve longer term and which priorities you want to adhere to (MoSCoW principle for life)? Journalling. Meditation (basically just put on a timer for 10 mins and close your eyes and breathe). These are your friends today.