About the bottom layer of the Go language, everything you want to know is here!


The number of words in the article is about 19,500 words, and it takes about 65 minutes to read. It is recommended to bookmark and read slowly! ! !

1. GoLang language

1.1 Slice

  1. The underlying implementation principle of Slice

    Slicing is implemented based on arrays, and its underlying layer is an array, which itself is very small and can be understood as an abstraction of the underlying array . Because it is implemented based on an array, its underlying memory is continuously allocated , which is very efficient, and data can also be obtained through indexes , which can be iterated and optimized for garbage collection . Slices themselves are not dynamic arrays or pointers to arrays. The data structure implemented inside it refers to the underlying array through the pointer, and sets the relevant attributes to limit the data read and write operations to the specified area. The slice itself is a read-only object, and its working mechanism is similar to an encapsulation of array pointers.

    A slice object is very small because it is a data structure with only 3 fields :

    • pointer to the underlying array

    • slice length

    • slice capacity

  2. Slice expansion mechanism

    When using append to add elements to the slice, if the slice space is insufficient, expansion will occur. The expansion will reallocate a larger memory, copy the original slice to the new slice, and then return the new slice. Add data after expansion.

    The expansion operation is only for the capacity, the length of the slice after expansion remains unchanged, and the capacity change rules are as follows:

    • If the capacity of the slice is less than 1024 elements, then the cap of the slice will be doubled and multiplied by 2 when expanding the capacity; once the number of elements exceeds 1024 elements, the growth factor will become 1.25, that is, one quarter of the original capacity will be increased each time.
    • If the capacity of the slice is sufficient, add the new element, slice.len++, and return to the original slice
    • If the capacity of the slice is not enough, first expand the slice to obtain a new slice, add new elements to the new slice, slice.len++, and return the new slice.
  3. The difference between Slice and array

    array is a fixed-length array, the length of the array must be determined before use, and it is a value type.

    slice is a reference type, which is a dynamic pointer to an array slice.
    A slice is a variable-length data structure that always points to the underlying array array and can be dynamically expanded.

    The creation method is different. Slice is created using make or based on an array.

    When used as a function parameter, an array is passed a copy of the array, while a slice is passed a pointer.

1.2 Map

  1. The underlying implementation principle of Map

    The underlying implementation of map in Golang is a hash table, so the process of implementing map is actually the process of implementing hash table. In this hash table, there are two main structures, one is called hmap (a header for a go map), and the other is called bmap (a bucket for a Go map, usually called its bucket).

    hmap The hash table
    hmap is the underlying implementation of the Go map. Each hmap contains multiple bmaps (buckets buckets, old buckets old buckets, overflow overflow buckets), that is, each hash table is composed of multiple buckets.

    • buckets
      buckets is a pointer to a bmap array that stores multiple buckets.

    • oldbuckets
      oldbuckets is a pointer to a bmap array, storing multiple old buckets for capacity expansion.

    • overflow
      overflow is a pointer pointing to an array with 2 elements. The type of the array is a pointer pointing to a slice. The elements of the slice are the addresses of the buckets (bmap), and these buckets are overflow buckets. Why are there two? Because Go map will expand its capacity when there are too many hash collisions. [0] indicates the currently used set of overflow buckets, and [1] means that the old set of overflow buckets is saved when expansion occurs. The significance of overflow is to prevent the overflow bucket from being gc.

    bmap hash bucket
    bmap is a structure belonging to hmap, and a bucket (bmap) can store 8 key-value pairs. If the ninth key-value pair is assigned to this bucket, another bucket needs to be created, and the two buckets are connected through the overflow pointer. In hmap, multiple bmap buckets are connected through overflow pointers to form a linked list.

  2. Map is sorted in order

    Every time the map is traversed, it will traverse from a bucket with a random value sequence number, and then from a random cell in it, and after expansion, the key in the original bucket will fall into other buckets, which will cause disorder in itself

    If you want to traverse the map sequentially, first put the keys into slices and sort them, and then traverse the map in the order of the keys.

    Or you can first sort the keys in the map through the sort package, and then traverse the map.

  3. Why map is not safe

    Go map is not concurrently safe by default. When reading and writing maps concurrently, the program will panic. The reason is as follows: After a long discussion, the Go official believes that the scene for map adaptation should be simple (no need to read and write from multiple goroutine for safe access), not for a small number of cases (concurrent access), causing most programs to pay the price of locks, so it is decided not to support it.

    When the map expands or shrinks, data migration is required. During the migration process, a lock mechanism is not used to prevent concurrent operations. Instead, a certain flag is marked as 1, indicating that data is being migrated at this time. If other goroutines also write to the map, when it detects that the flag is 1, it will panic directly.

    If you want to achieve map thread safety, there are two ways:

    Method 1: Use read-write lock map+sync.RWMutex

    Method 2: Use golang providedsync.Map

  4. Map expansion strategy

    Expansion time:

    When inserting a new key into the map, conditional detection will be performed, and if the following two conditions are met, the expansion will be triggered

    Expansion conditions:

    1. The number of map elements exceeding the load > 6.5 (load factor) * number of buckets

    2. overflow bucket too much

    When the total number of buckets <2^15, if the total number of overflow buckets >= the total number of buckets, it is considered that there are too many overflow buckets

    When the total number of buckets > 2 15, if the total number of overflow buckets >= 2 15, it is considered that there are too many overflow buckets

    Expansion mechanism:

    • Double expansion: For condition 1, create a new buckets array, the size of the new buckets is twice the original size, and then relocate the data from the old buckets to the new buckets.

    • Equal capacity expansion: For condition 2, the capacity is not expanded, the number of buckets remains unchanged, and the relocation action similar to double expansion is performed again, and the loose key-value pairs are rearranged once, so that the keys in the same bucket are arranged more Compact, save space, improve bucket utilization, and thus ensure faster access.

    • Gradual expansion:

      When inserting, modifying and deleting keys, it will try to relocate the bucket. Every time, it will check whether the oldbucket is nil. If it is not nil, it will relocate 2 buckets each time. The progressive expansion is like ants moving.

  5. The difference between Map and Slice

    1. Array: An array is a sequence of fixed-length elements of a specific type, and an array can consist of zero or more elements. Declaration method: var a [3] int
    2. Slice (slice): Slice (slice) represents a variable-length sequence, and each element in the sequence has the same type. The syntax of slice is very similar to that of an array, but there is no fixed length.
    3. map: In the Go language, a map is a reference to a hash table, which is an unordered collection of key/value pairs
  6. MapSummary

    1. map is a reference type
    2. map traversal is unordered
    3. map is not thread-safe
    4. The hash conflict resolution method of map is the linked list method
    5. The expansion of the map does not necessarily add new space, or it may just do memory organization
    6. The migration of map is carried out step by step, and at least one migration will be done for each assignment
    7. Deleting the key in the map may lead to many empty kvs, which will lead to migration operations. If it can be avoided, try to avoid it

1.3 Channel

  1. Introduce Channel (buffered and unbuffered)

    In the Go language, do not communicate through shared memory, but achieve memory sharing through communication. Go's CSP (Communicating Sequential Process) concurrency model, which can be called a communication sequential process in Chinese, is realized through goroutine and channel.

    Therefore, channel sending and receiving follow the first-in-first-out FIFO, which is divided into buffered and non-buffered. There are roughly buffers in the channel (when the buffer size is 0, it is a ring buffer), sendx and recvx send and receive positions (ring buffer record implementation), sendq , recvq The queue that the current channel is blocked due to insufficient buffers, using a doubly linked list for storage, and a mutex lock to control concurrency, other origins, etc.

    // 无缓冲的channel由于没有缓冲发送和接收需要同步
    ch := make(chan int)   
    //有缓冲channel不要求发送和接收操作同步
    ch := make(chan int, 2)  
    

    When the channel is unbuffered, the sending is blocked until the data is received, and the receiving is blocked until the data is read; when the channel is buffered, the sending is blocked when the buffer is full, and the receiving is blocked when the buffer is empty.

  2. Channel implementation principle

    The channel internally maintains two goroutine queues, one is the goroutine queue for data to be sent, and the other is the goroutine queue for data to be read.

    Whenever the read and write operations on the channel exceed the number of goroutines that can be buffered, the current goroutine will be hung on the corresponding queue until other goroutines perform the opposite read and write operations to wake it up again.

  3. Channel read and write process

    Write data to the channel:

    If the waiting queue recvq is not empty, there is no data or no buffer in the buffer, it will directly take out G from recvq and write the data, and finally wake up the G to end the sending process.

    If there is a vacant position in the buffer, write the data into the buffer and end the sending process.

    If there is no free space in the buffer, write the sent data into G, add the current G to sendq, go to sleep, and wait for the read goroutine to wake up.

    read data from channel

    If the waiting send queue sendq is not empty and there is no buffer, directly take out G from sendq, read out the data in G, wake up G at last, and end the reading process.

    If the waiting send queue sendq is not empty, it means that the buffer is full, read the data from the head of the buffer, write the data in G to the end of the buffer, wake up G, and end the reading process.

    If there is data in the buffer, the data is taken out from the buffer and the reading process ends.

    Add the current goroutine to recvq, go to sleep, and wait to be woken up by the writing goroutine.

    close channel

    1. When the channel is closed, all G in recvq will be awakened, and the data position that should be written into G is nil. Wake up all G in sendq, but these G will panic.

    There are also scenarios where panic occurs:

    • Close the channel whose value is nil
    • Close a closed channel
    • Write data to a closed channel
  4. Why Channel can be thread-safe

    Channel can be understood as a first-in-first-out queue, communicating through the pipeline, sending a data to the Channel and receiving a data from the Channel are atomic. Do not communicate through shared memory, but share memory through communication. The former is traditional locking, and the latter is Channel. The main purpose of designing Channel is to transfer data between multiple tasks, which is itself safe.

  5. Whether the Channel is synchronous or asynchronous (three states of the Channel)

    Channel is asynchronous, and there are 3 states in channel:

    • nil, uninitialized state, only declared, or manually assigned to nil
    • active, normal channel, readable or writable
    • closed, has been closed, do not mistakenly think that after the channel is closed, the value of the channel is nil
    operate A nil channel with zero value a non-zero but closed channel a non-zero channel that has not been closed
    closure generate panic generate panic closed successfully
    send data permanently blocked generate panic block or send successfully
    Receive data permanently blocked never block blocked or successfully received
    1. Send data to a nil channel, causing forever blocking
    2. Receive data from a nil channel, blocking forever
    3. Send data to a closed channel, causing panic
    4. Receives data from a closed channel, returning a zero value if the buffer is empty
    5. Unbuffered channels are synchronous, while buffered channels are asynchronous
    6. Closing a nil channel will panic

    insert image description here

1.4 Goroutine

  1. The difference between process, thread and coroutine

    • Process : A process is a program with certain independent functions, and a process is the smallest unit of system resource allocation and scheduling. Each process has its own independent memory space, and different processes communicate through inter-process communication. Because processes are relatively heavy and occupy independent memory, the switching overhead (stacks, registers, virtual memory, file handles, etc.) between context processes is relatively large, but it is relatively stable and safe.

    • Thread : A thread is an entity of a process, a thread is a kernel state, and is the basic unit of CPU scheduling and dispatching. It is a basic unit that is smaller than a process and can run independently. Inter-thread communication is mainly through shared memory, context switching is fast, and resource overhead is less, but it is less stable than the process and easy to lose data.

    • Coroutine : A coroutine is a lightweight thread in user mode, and the scheduling of the coroutine is completely controlled by the user. A coroutine has its own register context and stack. When the coroutine schedule is switched, the register context and stack are saved to other places. When switching back, the previously saved register context and stack are restored. There is basically no kernel switching overhead for direct operation of the stack, and global variables can be accessed without locking. , so context switching is very fast.

    • The difference between threads and coroutines

      1. Thread switching needs to be trapped in the kernel, and then context switching is performed, while the coroutine is completed by the coroutine scheduler in the user mode, and there is no need to trap into the kernel, so the cost is small.
      2. The switching time of coroutines is determined by the scheduler, not by the system kernel, although their switching points are when the time slice exceeds a certain threshold, or when entering I/O or sleep states.
      3. Based on the consideration of garbage collection, Go implements garbage collection, but the necessary condition for garbage collection is that the memory is in a consistent state, so all threads need to be suspended. If it is left to the system to do it, all threads will be suspended to make it consistent. For Go, the scheduler knows when memory is in a consistent state, so there is no need to suspend all running threads.
  2. Introducing Goroutines

    A Goroutine is a Go function or method that runs in parallel with other goroutines in the same address space.

    The concept of goroutine is similar to thread, but goroutine is scheduled and managed by Go's runtime. The Go program will intelligently allocate the tasks in the goroutine to each CPU reasonably. It has built-in scheduling and context switching mechanisms at the language level.

    Goroutine is the core of Go's concurrency design, also called coroutine. It is lighter than thread, so it can run thousands of concurrent tasks at the same time. In the Go language, each concurrent execution unit is called a goroutine. We only need to add the go keyword in front of the called function to make the function run as a coroutine.

  3. Structure principle and usage of context package

    Context (context) is a common concurrency control technology for Golang application development. It can control a group of goroutines in a tree structure, and each goroutine has the same context. Context is safe for concurrency and is mainly used to control collaboration and cancellation between multiple coroutines.

    Context only defines an interface, and any class that implements this interface can be called a context.

    • "Deadline" method: You can get the set deadline, and the return value deadline is the deadline. When this time is reached, Context will automatically initiate a cancellation request, and the return value ok indicates whether the deadline is set.
    • "Done" method: returns a read-only channel of type struct{}. If the chan can be read, it means that the cancellation signal has been sent, and the cleanup operation can be done, and then exit the coroutine to release resources.
    • "Err" method: Returns the reason why the Context was canceled.
    • "Value" method: Get the value bound on the Context, which is a key-value pair, and get the corresponding value through the key.
  4. goroutine scheduling

    GPM is the implementation of the Go language runtime (runtime) level, and it is a set of scheduling system implemented by the Go language itself. It is different from the operating system scheduling OS thread.

      1. G is easy to understand. It is a goroutine. In addition to storing the information of the goroutine, there is also information such as the binding with the P where it is located.
      1. P manages a group of goroutine queues, and P will store the current goroutine running context (function pointer, stack address and address boundary), and P will do some scheduling for the goroutine queues it manages (for example, goroutines that take up a long CPU time Pause, run subsequent goroutines, etc.) When your own queue is consumed, it will go to the global queue to fetch tasks. If the global queue is also consumed, it will go to other P queues to grab tasks.
      1. M (machine) is the virtualization of the operating system kernel thread by the Go runtime (runtime). M and the kernel thread generally have a one-to-one mapping relationship. A groutine is ultimately executed on M;

    P and M are generally in one-to-one correspondence. Their relationship is: P manages a group of G mounts running on M. When a G is blocked on an M for a long time, the runtime will create a new M, and the P where the blocked G is located will mount other Gs on the newly created M. The old M is reclaimed when the old G is done blocking or is considered dead.

    The number of P is set by runtime.GOMAXPROCS (up to 256), and after Go1.5 version, it defaults to the number of physical threads. When the amount of concurrency is large, some P and M will be added, but not too much. If the switching is too frequent, the loss outweighs the gain.

    In terms of thread scheduling alone, the advantage of Go language over other languages ​​is that OS threads are scheduled by the OS kernel, and goroutines are scheduled by the Go runtime (runtime) own scheduler, which uses a scheduler called m:n scheduling technology (multiplexing/scheduling m goroutines to n OS threads). One of its major features is that the scheduling of goroutines is completed in user mode, which does not involve frequent switching between kernel mode and user mode, including memory allocation and release. It maintains a large memory pool in user mode. Directly calling the malloc function of the system (unless the memory pool needs to be changed), the cost is much lower than scheduling OS threads. On the other hand, it makes full use of multi-core hardware resources, approximately equally divides several goroutines on physical threads, and adds the ultra-light weight of the goroutine itself, all of which guarantee the performance of go scheduling.

  5. How to avoid Goroutine leaks and leak scenarios

    There are channel operations in gorouinte. If the channel reading is not handled correctly, the channel will be blocked all the time, and the goroutine will not end normally.

  6. waitgroup usage and principle

    The waitgroup internally maintains a counter. When wg.Add(1)the method , the corresponding number will be increased; when the method is called wg.Done(), the counter will be decremented by one. Until the number of the counter is reduced to 0, runtime_Semrelease
    will be called to wake up the goroutine that was blocked because of the previous.wg.Wait()

    Instructions:

    1. The main coroutine sets the number of worker coroutines by calling wg.Add(delta int), and then creates worker coroutines;
    2. After the worker coroutine is executed, it must call wg.Done();
    3. The main coroutine calls wg.Wait() and is blocked until all worker coroutines are executed and returns.

    Implementation principle:

    • WaitGroup mainly maintains 2 counters, one is the request counter v, the other is the wait counter w, the two form a 64bit value, the request counter occupies the high 32bit, and the wait counter occupies the low 32bit.

    • Each time Add is executed, the request counter v is incremented by 1, the Done method is executed, the request counter is decremented by 1, and when v is 0, Wait() is woken up through the semaphore.

1.5 GMP scheduling

  1. What is GMP

    • G (Goroutine): Go coroutine, each go keyword will create a coroutine.
    • M (Machine): worker thread, called Machine in Go, the number corresponds to the actual number of CPUs (objects that really work).
    • P (Processor): Processor (a concept defined in Go, not CPU), which contains the necessary resources to run Go code, and is used to schedule the relationship between G and M. The number can be set by GOMAXPROCS(), The default is the number of cores.

    M must own P to execute the code in G. P contains a queue containing multiple Gs, and P can schedule G to be executed by M.

    Get goroutine from P's local queue first for execution; if there is no local queue, get it from the global queue, and if there is no global queue, it will steal goroutine from other Ps.

  2. GMP goroutine scheduling policy

    • Queue rotation: P will periodically schedule G to M for execution. After a period of execution, save the context, put G at the end of the queue, and then take another G out of the queue for scheduling. In addition, P will periodically check whether there is G in the global queue waiting to be dispatched to M for execution.
    • System call: When G0 is about to enter the system call, M0 will release P, and then an idle M1 will acquire P, and continue to execute the remaining G in the P queue. The source of M1 may be M's cache pool, or it may be newly created.
    • After the G0 system call ends, if there is an idle P, obtain a P and continue to execute G0. If not, put G0 into the global queue and wait to be scheduled by other Ps. Then M0 will go to cache pool sleep.
  3. Scheduler Design Strategy

    Multiplexing threads: avoid frequent creation and destruction of threads, but reuse threads.

    1. work stealing mechanism
      • When this thread has no runnable G, try to steal G from P bound by other threads instead of destroying the thread.
    2. hand off mechanism
      • When the thread is blocked by the system call of G, the thread releases the bound P, and transfers P to other idle threads for execution.

    Using parallelism : GOMAXPROCS sets the number of P, and at most GOMAXPROCS threads are distributed and run simultaneously on multiple CPUs. GOMAXPROCS also limits the degree of concurrency, such as GOMAXPROCS = number of cores/2, then at most half of the CPU cores are used for parallelism.

    Preemption : In a coroutine, it is necessary to wait for a coroutine to voluntarily give up the CPU before executing the next coroutine. In Go, a goroutine occupies a maximum of 10ms of the CPU to prevent other goroutines from being starved to death. This is a difference between a goroutine and a coroutine.

    Global G queue : when M cannot steal G from other Ps by performing work stealing, it can obtain G from the global G queue.

  4. The CSP model is "shared memory by means of communication", which is different from traditional multi-thread communication through shared memory. A concurrency model used to describe two independent concurrent entities communicating through a shared communication channel (pipe).

  5. Two types of preemptive scheduling

    Cooperative preemptive scheduling

    Before version 1.14, the program can only rely on Goroutine to actively give up CPU resources to trigger scheduling, and there is a problem

    • Some Goroutines can occupy threads for a long time, causing starvation of other Goroutines

    • Garbage collection needs to suspend the entire program (Stop-the-world, STW), which may take up to several minutes, causing the entire program to fail to work.

    Signal-based preemptive scheduling

    In any case, the number of goroutines that the Go runtime executes in parallel (note, not concurrently) is less than or equal to the number of P. In order to improve the performance of the system, the number of P is definitely not as small as possible, so the official default value is the number of cores of the CPU. If the setting is too small, if an M holding P, the G currently executed by P calls a syscall and If it causes M to be blocked, then the key point at this time: GO's scheduler is slow, and it probably does nothing. It will not be found that a P/M is blocked by a syscall until M is blocked for a long time. Then, the free M will be used to strengthen this P. The preemptive scheduling realized by sysmon monitoring is as fast as 20us, and the slowest is 10-20ms before finding that there is an M holding P and blocking it. The operating system can complete many thread scheduling within 1ms (generally, dozens of thread scheduling can be completed in 1ms), when Go initiates IO/syscall, M executing the G will block and then be scheduled by the OS, P does nothing, It takes 10-20ms at the slowest time for sysmon to discover the blockage. Maybe the blockage is over by then, and the precious P resources are just wasted by the blocked M.

  6. What blockages exist during GMP scheduling

    • I/O,select

    • block on syscall

    • channel

    • waiting for lock

    • runtime.Gosched()

  7. GMP scheduling process

    • Each P has a local queue, and the local queue stores the goroutines to be executed (process 2). When the local queue of P bound to M is full, the goroutine will be placed in the global queue (process 2- 1)

    • Each P is bound to an M, and M is the entity that actually executes the goroutine in P (process 3), and M obtains G from the local queue in the bound P to execute

    • When the local queue of P bound by M is empty, M will obtain the local queue from the global queue to execute G (process 3.1). Steal G from the local queue for execution (process 3.2), this way of stealing from other P is called work stealing

    • When G is blocked by a system call (syscall), it will block M. At this time, P will unbind from M, that is, hand off, and look for a new idle M. If there is no idle M, a new M will be created (process 5.1).

    • When G is blocked by channel or network I/O, M will not be blocked, and M will look for other runnable G; when the blocked G recovers, it will re-enter the runnable and enter the P queue to wait for execution (process 5.3)

1.6 Garbage collection mechanism

  1. GC principle

    Garbage collection is the automatic recovery of memory resources that are no longer used in the program.

    three-color marking method

    • Initially all objects are white.
    • Traverse all objects from the root node, and turn the traversed objects into gray objects
    • Traverse the gray object, turn the object referenced by the gray object into a gray object, and then turn the traversed gray object into a black object.
    • Repeat step 3 until all gray objects turn black.
    • Detect changes in the object through the write barrier (write-barrier), repeat the above operation
    • Collect all white objects (garbage).

    STW(Stop The World)

    • In order to avoid new changes in the reference relationship between objects during the GC process, resulting in errors in the GC result (for example, a new reference is added during the GC process, but the referenced object is cleared because the reference is not scanned. ), stops all running coroutines.
    • STW has some impact on performance, and Golang can currently achieve STW below 1ms.
  2. GC trigger conditions

    Active triggering (manual triggering), triggering GC by calling runtime.GC, this call blocks and waits for the current GC to finish running.
    Passive trigger, divided into two ways:

    • Using the Pacing algorithm, the core idea is to control the proportion of memory growth, and check whether the current memory allocation has reached the threshold each time the memory is allocated (environment variable GOGC): the default is 100%, that is, enable GC when the memory is doubled .
    • Use system monitoring to force a GC when no GC occurs for more than two minutes.
  3. Why Golang has too many small objects will cause gc pressure

    Usually too many small objects will cause the GC tricolor method to consume too much GPU. The optimization idea is to reduce object allocation.

  4. Introduction to GC barriers

    Write Barrier

    • In order to avoid errors in the newly modified references related to the GC results during the GC process, we need to perform STW. But STW will affect the performance of the program, so we need to shorten the STW time as much as possible through the write barrier technology.

    Write barrier : Concurrent gc will cause black nodes to reference white nodes, causing normal pointer variables to be cleared by mistake; the solution is to write barriers;

    It mainly includes strong three-color invariant and weak three-color invariant;

    Strong three colors remain unchanged : black nodes cannot reference white nodes, if white nodes are referenced, white nodes need to be grayed out (insert write barrier);

    Weak three colors remain unchanged : black nodes can refer to white nodes, but white nodes have other gray nodes or recursively point to existing gray nodes. When deleting white node references, white nodes need to be grayed out (delete write barrier);

    The variables on the stack are small, and are frequently created or deleted, and the write barrier is not enabled; a subsequent rescan is required;

    stw timing:

        插入写屏障:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活;(1.5版本采用)
        删除写屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象;
    

    Mixed write barrier : added in version 1.8

    The reason is that stw takes time; add a mixed write barrier to solve this problem;

    Process :

    1. At the beginning of marking, all reachable nodes on the stack are blacked out, and then rescan and stw are not used;

    2. The objects created on the stack generated during gc are all set to black;

    3. The objects deleted in the heap space are grayed out;

    4. The object inserted in the heap space is grayed out;

    features

    • The hybrid write barrier inherits the advantages of inserting a write barrier. At the beginning, STW does not need to take a snapshot, and the garbage can be scanned concurrently;

    • The mixed write barrier inherits the advantages of deleting the write barrier. The setter is a black setter. During GC, any new object created on the stack is black. After scanning once, there is no need to scan, which eliminates the rescanning stack inserted into the last STW of the write barrier period;

    • The hybrid write barrier scanning accuracy inherits the deletion write barrier, which is lower than the insertion write barrier, and brings no STW throughout the GC process;

    • Although there is no STW in the mixed write barrier scanning stack, when scanning a specific stack, it is still necessary to stop the work of the goroutine setter (for a goroutine stack, it is suspended scanning, either all gray or all black Ha, atomic state switching).

  5. What is the process of GC

    The current version of Go is bounded by STW, and GC can be divided into five stages:

    Phase description Evaluator status GCMark mark preparation phase, prepare for concurrent marking, start write barrier STWGCMark scan mark phase, execute concurrently with the setter, write barrier to enable concurrency

    GCMarkTermination mark termination phase, to ensure that the marking task is completed within one cycle, stop the write barrier STWGCoff memory cleaning phase, return the memory that needs to be reclaimed to the heap, and write barrier off concurrently

    GCoff memory return phase, return too much memory to the operating system, write barrier off concurrency

  6. How GC is tuned

    Optimize the application speed of memory, apply for as little memory as possible, and reuse the allocated memory. Three keywords: control, reduce, reuse .

    Through tools such as go tool pprof and go tool trace

    1. Control the speed of memory allocation and limit the number of goroutines, thereby improving the CPU utilization of the setter.
    2. Reduce and reuse memory, such as using sync.Pool to reuse requires frequent creation of temporary objects, such as allocating enough memory in advance to reduce redundant copies.
    3. If necessary, increase the value of GOGC and reduce the frequency of GC operation.

1.7 Other Knowledge Points

  1. The difference between new and make

    • make is only used to allocate and initialize data of type slice, map, and chan.
    • new can allocate any type of data, apply for a piece of memory according to the type passed in, and return a pointer to this piece of memory, that is, the type *Type.
    • make returns a reference, that is, Type, the space allocated by new is cleared, and after make allocates space, it will be initialized.
  2. What is the memory allocation of go

    Go's memory allocation borrows from Google's TCMalloc allocation algorithm, and its core idea is memory pool + multi-level object management. The memory pool is mainly to pre-allocate memory to reduce the frequency of application to the system; multi-level objects include: mheap, mspan, arenas, mcentral, mcache. They use mspan as the basic allocation unit. The specific allocation logic is as follows:

    • When allocating objects larger than 32K, allocate from mheap.
    • When the object to be allocated is less than or equal to 32K and greater than 16B, it is allocated from mcache on P. If mcache has no memory, it will be obtained from mcentral. If mcentral does not have it, it will apply to mheap. If mheap has no memory, it will apply for memory from the operating system. .
    • When the object to be allocated is less than or equal to 16B, it is allocated from the micro-allocator on mcache.
  3. race condition, memory escape

    race

    Resource competition means that in a program, the same block of memory is accessed by multiple goroutines at the same time. When we use the go build, go run, and go test commands, adding the -race flag can check whether there is resource competition in the code.

    To solve this problem, we can lock the resource so that it can only be operated by one coroutine at the same time.

    • sync.Mutex
    • sync.RWMutex

    escape analysis

    "Escape analysis" is the memory allocation location (stack or heap) when the program is running, which is determined by the compiler. The heap is suitable for memory allocations of unpredictable size. But the price you pay for this is slower allocation and memory fragmentation.

    The memory allocation method of variables in Go is determined by the compiler. If the variable is still referenced outside the scope (such as the scope of the function), then it is said that an escape behavior has occurred, and the object will be placed on the heap at this time, even if it is declared as a value type; if no escape behavior occurs If it is, it will be allocated on the stack, even if an object is new.

    Escape scene:

    • pointer escape
    • Insufficient stack space to escape
    • dynamic type escape
    • Closure reference object escape
  4. What is the rune type

    The rune type represents a UTF-8 character. When Chinese, Japanese or other compound characters need to be processed, the rune type is required. The rune type is equivalent to the int32 type.

  5. What are the scenarios where go language triggers exceptions?

    1. Null pointer parsing

    2. subscript out of bounds

    3. divisor by 0

    4. Call the panic function

  6. interface of go

    The Go language provides another data type, the interface, which defines all common methods together. As long as any other type implements these methods, it implements this interface.

    Interfaces allow us to bind different types to a common set of methods, enabling polymorphism and flexible design.

    Interfaces in Go are implicitly implemented, that is, if a type implements all the methods defined by an interface, then it automatically implements that interface. Therefore, we can achieve polymorphism by passing interfaces as parameters to implement calls to different types.

  7. Compared with other languages, what advantages or characteristics does Go have?

    • The design of Go code is pragmatic. Every feature and syntax decision is designed to make programmer's life easier.

    • Golang is optimized for concurrency and works well at scale.

    • Golang is generally considered more readable than other languages ​​due to a single standard code format.

    • Automatic garbage collection is significantly more efficient than Java or Python because it executes concurrently with the program.

  8. Usage of defer, panic, and recover

    The order of defer function calls is last-in-first-out. When a panic occurs, the defer function in front of the panic will be executed first before an exception is actually thrown. Generally, recover will be executed in the defer function and catch the exception to prevent the program from crashing.

  9. Go reflection

    introduce

    The Go language provides a mechanism to update and check the value of variables at runtime, call the methods of variables and the internal operations supported by variables, but the specific types of these variables are not known at compile time. This mechanism is called reflection. Reflection also lets us treat types themselves as first-class value types.

    Reflection refers to the ability to access and modify the program itself during the running of the program. When the program is compiled, the variable is converted into a memory address, and the variable name will not be written into the executable part by the compiler. The program cannot obtain itself when the program is running. Information.

    Reflection in Go language is supported by the reflect package, which defines two important types Type and Value. Any interface value in reflection can be understood as composed of reflect.Type and reflect.Value, and the reflect package provides Two functions reflect.TypeOf and reflect.ValueOf are added to get the Value and Type of any object.

    The Three Laws of Reflection

    • The first law of reflection: reflection can convert interface type variables into reflection objects
    • The second law of reflection: reflection can restore the reflection object to an interface object
    • The third law of reflection: the reflection object can be modified, and the value value must be settable
  10. Go language function parameter is value type or reference type

    • There is only pass-by-value in Go, either a copy of a value or a copy of a pointer. Whether it is a value type variable, a reference type variable, or a pointer type variable passed as a parameter, a value copy will occur and a new memory space will be opened up.
    • In addition, value passing, reference passing and value type and reference type are two different concepts, so don't confuse them. Passing a reference type as a variable can affect the outside of the function because the old and new variables point to the same memory address after value copying occurs.
  11. Memory Alignment in Go Language

    When the CPU accesses memory, it does not access it byte by byte, but in units of word size. For example, for a 32-bit CPU, the word length is 4 bytes, so the unit of CPU accessing memory is also 4 bytes.

    The CPU always accesses the memory with the word length. If the memory is not aligned, it is likely to increase the number of times the CPU accesses the memory, for example:

    Variables a and b each occupy 3 bytes of space. After memory alignment, a and b occupy 4 bytes of space. The CPU only needs to perform one memory access to read the value of variable b. If memory alignment is not performed, the CPU needs 2 memory accesses to read the value of variable b. The first access gets the first byte of the b variable, and the second access gets the last two bytes of the b variable.

    Memory alignment is also beneficial to realize the atomic operation of variables. Each memory access is atomic. If the size of the variable does not exceed the word length, then after memory alignment, the access to the variable is atomic. This feature is useful in concurrent scenarios. Down is crucial.

    In short: Reasonable memory alignment can improve the performance of memory read and write, and facilitate the atomicity of variable operations.

  12. Purpose of empty struct{}

    Because empty structures do not occupy memory space, they are widely used as placeholders in various scenarios.

    1. When using map as a collection (Set), you can define the value type as an empty structure, which can be used only as a placeholder.
    2. A channel that does not send data
      uses a channel that does not need to send any data, and is only used to notify sub-coroutines (goroutines) to perform tasks, or to control the concurrency of coroutines.
    3. Structs contain only methods, not any fields
  13. Pass by value and pass by address (pass by reference)

    All parameter passing in the Go language is value passing (passing by value), a copy, a copy. Because the copied content is sometimes a non-reference type (int, string, struct, etc.), the original content data cannot be modified in the function; some are reference types (pointer, map, slice, chan , etc.), so that it can be modified original content data.

    Golang's reference types include slice, map, and channel. They have complex internal structures, and in addition to applying for memory, they also need to initialize related properties. The built-in function new calculates the size of the type, allocates zero-valued memory for it, and returns a pointer. And make will be translated into a specific creation function by the compiler, which allocates memory and initializes member structures, and returns objects instead of pointers.

  14. atomic operation

    The characteristic that one or more operations are not interrupted during CPU execution is called atomicity.

    These operations are presented as an indivisible whole to the outside world. They are either executed or not executed. The outside world will not see that they are only half executed. In the real world, it is impossible for the CPU to perform a series of operations without interruption, but if we can make their intermediate states invisible to the outside world when performing multiple operations, then we can declare that they have "indivisible" atomicity.

    In Go, an ordinary assignment statement is not actually an atomic operation. For example, writing a variable of type int64 on a 32-bit machine will have an intermediate state, because it will be split into two write operations (MOV) - writing the lower 32 bits and writing the upper 32 bits.

2. Web framework Gin and microservice framework Micro

2.1 Gin framework

  1. What is the Gin framework

    Gin is a web framework written in Go language. It has the advantages of better encapsulation, friendly API, clear source code comments, fast, flexible, and fault-tolerant convenience. It has fast running, grouped routers, good crash catching and error handling, very good support for middleware and json.

  2. Implementation of Gin routing

    The gin framework uses a customized version of httprouter, and its routing principle is a tree structure that uses a large number of common prefixes, which is basically a compact Trie tree (or just a Radix Tree). Nodes with a common prefix also share a common parent.

    The gin framework uses a customized version of httprouter , and its routing principle is a tree structure that uses a large number of common prefixes, which is basically a compact Trie tree (or just a Radix Tree ). Nodes with a common prefix also share a common parent.

    The routing tree is composed of nodes, and the nodes of the gin framework routing tree nodeare represented by structures

    In the route of gin, each HTTP Method(GET, POST, PUT, DELETE...) corresponds to a tree radix tree, and we will call the addRoute`function when we register the route

    The logic of registering routes mainly includes addRoutefunctions and insertChildmethods.

    ​ The detailed process of routing tree construction:

    1. Register routing for the first time, such as registering search
    2. Go ahead and register a route without a public prefix, such as blog
    3. Register a route that has a common prefix with a previously registered route, e.g. support

    The detailed process of routing tree construction:

    1. Register routing for the first time, such as registering search
    2. Go ahead and register a route without a public prefix, such as blog
    3. Register a route that has a common prefix with a previously registered route, e.g. support

    Route matching is implemented by the node's getValuemethod . getValueAccording to the given path (key) return nodeValuevalue, save the registered processing function and the matched path parameter data.

    The gin framework routing uses a prefix tree. The process of route registration is the process of constructing a prefix tree, and the process of route matching is the process of searching for a prefix tree.

  3. Gin middleware

    The Gin framework allows developers to add the user's own hook (Hook) function in the process of processing the request. This hook function is called middleware. Middleware is suitable for processing some common business logic, such as login authentication, permission verification, data paging, logging, time-consuming statistics, etc.

    The gin framework involves four commonly used methods related to middleware, which are c.Next(), c.Abort(), c.Set(), c.Get().

    The middleware functions and processing functions of the gin framework exist in a call chain in the form of slices, and we can call them sequentially or use c.Next()methods to implement nested calls.

    With the help of c.Set()and c.Get()methods we can pass data between different middleware functions.

    gin default middleware
    gin.Default() uses Logger and Recovery middleware by default, among which:

    Logger middleware writes logs to gin.DefaultWriter even if GIN_MODE=release is configured.
    Recovery middleware will recover any panic. If there is a panic, a 500 response code will be written.
    If you don't want to use the above two default middleware, you can use gin.New() to create a new route without any default middleware.

2.2 Micro framework

  1. Understanding of Microservices

    Using a set of small services to develop a single application, each service runs in an independent process, generally interconnected by a lightweight communication mechanism, and they can be deployed in an automated manner

    Microservice Features

    • Single Responsibility, at this point the project is focused on login and registration
    • Lightweight communication, communication has nothing to do with platform and language, http is lightweight
    • isolation, data isolation
    • have their own data
    • technical diversity

    clipboard.png

  2. Advantages and disadvantages of microservice architecture

    advantage

    1. Easy to develop and maintain

    2. Start faster

    3. Partial modification is easy to deploy

    4. The technology stack is not limited

    5. Scale on demand

    shortcoming

    1. High requirements for operation and maintenance

    2. Distributed complexity

    3. Interface adjustment costs are high

    4. Repetitive work

  3. RPC protocol

    • Remote Procedure Call (RPC) is a computer communication protocol
    • The protocol allows a program running on one computer to call a subroutine on another computer without the programmer having to program additionally for the interaction
    • Remote procedure calls may also be called remote calls or remote method calls if the software involved uses object-oriented programming

    RPC call process
    Under the microservice architecture, data interaction is generally internal RPC, and external REST
    splits the business into various microservices according to functional modules, which has the advantages of improving project collaboration efficiency, reducing module coupling, and improving system availability. However, the development threshold is relatively high. High, such as the use of RPC framework, post-service monitoring, etc.
    Under normal circumstances, we will directly call the function code locally. Under the microservice architecture, we need to run this function as a separate service, and the client calls it through the network

    Popular RPC frameworks : Dubbo, Motan, Thrift, gRPC

  4. Introduction to gRPC

    gRPC, developed by Google, is a language-neutral, platform-neutral, open source remote procedure call system

    gRPC is a high-performance, open-source, and general-purpose RPC framework designed and developed based on the HTTP2 protocol standard. It uses the Protocol Buffers data serialization protocol by default and supports multiple development languages. gRPC provides an easy way to precisely define services and automatically generate reliable functional libraries for clients and servers.

    The gRPC client can directly call remote programs on different servers, which is just like calling local programs, making it easy to build distributed applications and services. Like many RPC systems, the server is responsible for implementing the defined interface and processing the client's request, and the client directly calls the required service according to the interface description. The client and server can be implemented in different languages ​​supported by gRPC.

    gRPC main features

    1. Powerful IDL

    gRPC uses ProtoBuf to define services. ProtoBuf is a data serialization protocol developed by Google (similar to XML, JSON, hessian). ProtoBuf can serialize data and is widely used in data storage, communication protocols, etc.

    1. multilingual support

    gRPC supports multiple languages, and can automatically generate client and server function libraries based on languages. At present, the C version grpc, Java version grpc-java and Go version grpc-go have been provided, and other language versions are under active development. Among them, grpc supports C, C++, Node.js, Python, Ruby, Objective-C, PHP and C# and other languages, grpc-java already supports Android development.

    1. HTTP2

    gRPC is designed based on the HTTP2 standard, so compared to other RPC frameworks, gRPC brings more powerful functions, such as bidirectional streaming, header compression, multiplexing requests, etc. These features bring significant benefits to mobile devices, such as saving bandwidth, reducing the number of TCP connections, saving CPU usage, and extending battery life. At the same time, gRPC can also improve the performance of cloud services and web applications. gRPC can be applied on both the client side and the server side, so as to realize the communication between the client and the server side in a transparent manner and simplify the construction of the communication system.

  5. Introduction to Protobuf

    Google Protocol Buffer (Protobuf for short) is Google's internal mixed-language data standard. Currently, there are more than 48,162 message format definitions and more than 12,183 .proto files in use. They are used in RPC systems and persistent data storage systems. Protocol Buffers is a lightweight and efficient structured data storage format that can be used for structured data serialization, or serialization. It is very suitable for data storage or RPC data exchange format. A language-independent, platform-independent, and extensible serialized structured data format that can be used in instant messaging, data storage, and other fields

    The core content of protobuf includes:

    • Define message: the structure of the message, identified by message.

    • Define interface: interface path and parameters, identified by service.

    Through the mechanism provided by protobuf, the server and the server only need to pay attention to the interface method name (service) and parameter (message) to communicate, without paying attention to the tedious link protocol and field analysis, which greatly reduces the design and development of the server cost.

  6. Micro introduction and main functions

    Introduction to go-micro

    • Go Micro is a plug-in basic framework based on which microservices can be built. Micro's design philosophy is a pluggable plug-in architecture
    • Outside of the architecture, it implements consul as a service discovery by default, communicates through http, and encodes and decodes through protobuf and json
    • is a system for building and managing distributed programs
    • Runtime (runtime): used to manage configuration, authentication, network, etc.
    • Framework (program development framework): used to facilitate the writing of microservices
    • Clients (multilingual client): supports multilingual access to the server

    Main functions of go-micro

    • Service Discovery: Automatic service registration and name resolution.

    • Load balancing: Client load balancing based on service discovery.

    • Message Encoding: Dynamic message encoding based on content type.

    • Request/Response: RPC-based request/response, supporting bidirectional flow.

    • Async Messaging: PubSub is a first-class citizen for asynchronous communication and event-driven architecture.

    • Pluggable interfaces: Go Micro uses Go interfaces for each distributed system abstraction, therefore, these interfaces are pluggable and allow Go Micro to be runtime-agnostic and plug into any underlying technology

    go-micro characteristics

    • api: api gateway. Use service discovery for a single entry point with dynamic request routing. API Gateway allows you to build scalable microservice architectures on the backend and incorporate public apis on the frontend. micro api provides powerful routing through discovery and pluggable handlers, Provides services for http, grpc, Websocket, publishing events, etc.
    • broker: A message broker that allows asynchronous messages. Microservices are an event-driven architecture and should provide messaging as a first-class citizen. Notify other services of events without worrying about responses.
    • network: Build a multi-cloud network through micro-network services. Simply connect network services across any environment, creating a single flat network that can be routed globally. Micro's network dynamically builds routes based on a local registry in each data center, ensuring queries are routed according to local settings.
    • new: service template generator. Create new service templates to get started quickly. Micro provides predefined templates for writing microservices. Always start the same way, build the same services to increase productivity.
    • proxy: A transparent service proxy built on Go Micro. Offload service discovery, load balancing, fault tolerance, message encoding, middleware, monitoring, and more to a single location. Run it standalone or with a service.
    • registry: The registry provides service discovery to find other services, stores feature-rich metadata and endpoint information. It is a service explorer that allows you to store this information centrally and dynamically at runtime.
    • store: Statefulness is an inevitable requirement of any system. We provide key-value storage, providing simple state storage that can be shared between services or long-term offloaded to keep microservices stateless and horizontally scalable.
    • web: The web dashboard allows you to browse services, describe their endpoints, request and response formats, and even query them directly. The dashboard also includes a built-in CLI experience for developers who want dynamic access to the terminal.
  7. Micro communication process

    The server listens to the call from the client, and processes the information pushed by the Brocker. And the server side needs to register its own existence or demise with the Register, so that the client can know its own status
    Register service registration discovery, the client side gets the information of the server from the register, and then selects a server for communication according to the algorithm every time it is called Of course, the communication needs to go through a series of processes such as encoding/decoding and selecting the transmission protocol.
    If there is a need to notify all the servers, Brocker can be used to push information, and the Brocker information queue can be used to receive and publish information.

  8. consul

    Consul is used to realize service discovery and configuration of distributed systems. Consul is distributed, highly available, and horizontally scalable.

    Consul key function
    service discovery of the registration center:

    Clients can register services, and programs can easily find the services they depend on.
    Health checks:

    A Consul client can provide any number of health check
    KV stores:

    Applications can use Consul's hierarchical key/value store for any purpose, including dynamic configuration, feature flagging, coordination, leader election, and other
    secure service communication:

    Consul can generate and distribute TLS certificates for services, and establish mutual TLS connections to
    multiple data centers:

    Consul supports
    two important protocols of multiple data center registry Consul

    • Gossip Protocol (gossip protocol)

    • Raft Protocol (election protocol)

  9. Jaeger

    What is link tracking:
    Distributed link tracking is to restore a distributed request into a call link, and centrally display the call status of a distributed request, such as the time spent on each service node and which machine the request arrives at , request status of each service node, etc.
    Main functions of link tracking:

    • Quick fault location: You can quickly locate error information through the call chain combined with business logs

    • Link performance visualization: Link time consumption and service dependencies at each stage can be displayed through a visual interface

    • Link analysis: By analyzing the link time consumption and service dependencies, the user's behavior path can be obtained, and the summary analysis is applied in many business scenarios

    jaeger link tracking function

    • It is used to monitor and diagnose microservice-based distributed systems
    • Used for service dependency analysis and auxiliary performance optimization

    Composition of Jaeger

    Jaeger Client - Implements OpenTracing compliant SDKs for different languages. The application program writes data through the API, and the client library passes the trace information to the jaeger-agent according to the sampling strategy specified by the application program.

    Agent - It is a network daemon process that listens to receive span data on UDP port, and it will send the data to the collector in batches. It is designed as a basic component and deployed on all hosts. The Agent decouples the client library from the collector, and shields the client library from the details of routing and discovering the collector.

    Collector - Receives the data sent by jaeger-agent, and then writes the data to the backend storage. Collectors are designed as stateless components, so you can run any number of jaeger-collectors concurrently.

    Data Store - The backend storage is designed as a pluggable component that supports writing data to cassandra, elastic search.

    Query - Receives query requests, then retrieves traces from the backend storage system and displays them through the UI. Query is stateless, you can start multiple instances and deploy them behind a load balancer like nginx.

    Distributed tracking systems are developing rapidly and there are many types, but there are generally three core steps: code burying, data storage , query display

  10. Prometheus

    promethues introduction

    • It is a combination of open source monitoring & alarm & time series database
    • The basic principle is to periodically capture the status of the monitored components through the HTTP protocol
    • A monitoring system suitable for Docker and Kubernetes environments

    promethues workflow

    • Prometheus server periodically pulls data from configured jobs/exporters/Pushgateway

    • Prometheus server records data and pushes alert data according to alarm rules

    • Alertmanager processes the received alerts and sends out alerts according to the configuration file.

    • In the graphical interface, visualize the collected data

    important components of promethues

    • Prometheus Server: Used to collect and store time series data.

    • Client Library: The client library generates corresponding metrics and exposes them to the Prometheus server

    • Push Gateway: Mainly used for short-term jobs

    • Exporters: Used to expose metrics of existing third-party services to Prometheus

    • Alertmanager: After receiving alerts from the Prometheus server, it will

    grafana signs

    • Indicator analysis platform with rich dashboard and chart editing
    • Has its own authority management and user management system
    • Grafana is more suitable for data visualization display
  11. Fuse downgrade, current limiting, load balancing

    1. Fuse downgrade

      Service breaking is also known as service isolation or overload protection. In microservice applications, services have certain dependencies and form a certain dependency chain. If a target service is called slowly or has a large number of timeouts, the service is unavailable, which indirectly causes other dependent services to be unavailable. The most serious may be Block the entire dependency chain, eventually leading to the collapse of the business system (also known as the avalanche effect). At this point, the call to the service is fused, and for subsequent requests, the target service is no longer called, but returns directly, so that resources can be quickly released. Wait until the target service's condition improves, then resume its invocation.

      Closed (Closed) : In this state, we need a counter to record the number of call failures and the total number of requests. If the failed failure rate reaches a preset threshold within a certain time window, switch to disconnect At this time, a timeout period is opened, and when the timeout is reached, it will switch to the half-closed state. The timeout period is to give the system a chance to correct the error that caused the call to fail, so as to return to the normal working state. In the closed state, call errors are time-based and reset at specific intervals, which prevents accidental errors from causing the fuse to go into the open state

      Open (Open) : In this state, an error will be returned immediately when the request is initiated. Generally, a timeout timer will be started. When the timer expires, the state will switch to the half-open state. A timer can also be set to periodically detect the service whether to restore

      Half-Open : In this state, the application is allowed to send a certain number of requests to the called service. If these calls are normal, it can be considered that the called service has returned to normal. At this time, the fuse switches to the closed state. At the same time the count needs to be reset. If there are still call failures in this part, it is considered that the callee has not recovered, and the fuse will switch to the closed state, and then reset the counter. The half-open state can effectively prevent the recovering service from being knocked down again by a sudden large number of requests.

      There are three common circuit breaker downgrade strategies

      • Error ratio: within the set time window, if the called access error ratio is greater than the set threshold, the next access request will be automatically blocked.
      • Error count: within the set time window, if the number of access errors invoked is greater than the set threshold, the next access request will be automatically broken.
      • Slow call ratio: within the set time window, if the slow call ratio is greater than the set threshold, the next access request will be automatically blocked.

      service downgrade

      When the downstream service responds too slowly for some reason, the downstream service actively stops some unimportant services to release server resources and increase the response speed.

      Regarding downgrading, there are two scenarios here:

      • When the downstream service responds too slowly for some reason, the downstream service actively stops some unimportant businesses to release server resources and increase the response speed!
      • When the downstream service is unavailable for some reason, the upstream actively invokes some local downgrade logic to avoid lag and return to the user quickly!
    2. Limiting

      Under the microservice architecture, if a large number of requests exceed the processing capacity of the microservice, the service may be overwhelmed, and even an avalanche effect may occur, affecting the overall stability of the system. For example, if your user service processing capacity is 1w/s, and now there are 10w concurrent requests to access your service due to abnormal traffic or other reasons, then your service must not be able to handle it. In this case, when the traffic exceeds the acceptable threshold, we can directly "limit the flow" and reject some requests, so as to ensure the overall stability of the system.

      Current limiting algorithm

      fixed time window

      The current limiting algorithm based on fixed time window is very simple. First, you need to select a time starting point, and then accumulate the counter every time an interface request arrives. If within the current time window, according to the current limiting rule (for example, a maximum of 100 interface requests are allowed per second), the accumulated number of visits exceeds the current limit value. Then the current limit fuse rejects the interface request. When entering the next time window, the counter is reset to zero and counts again.

      Sliding Time Window Algorithm

      The sliding time window algorithm is an improvement to the fixed time window algorithm. After the traffic is shaped by the sliding time window algorithm, it can ensure that in any time window, it will not exceed the maximum allowable current limit value. From the perspective of the flow curve, it will be smoother. , can partially solve the critical burst flow problem mentioned above. Compared with the fixed time window current limiting algorithm, the time window of the sliding time window current limiting algorithm is continuously sliding, and in addition to needing a counter to record the number of interface requests in the time window, it is also necessary to record the arrival of each interface request in the time window At the time point, the memory usage will be more.

      Leaky Bucket and Token Bucket Algorithms

      Leaky Bucket : The main purpose is to control the rate at which data is injected into the network and smooth out burst traffic on the network. The leaky bucket algorithm provides a mechanism by which bursty traffic can be shaped to provide a steady flow for the network.

      The request first enters the leaky bucket, and the leaky bucket emits water at a certain speed. When the water request is too large, it overflows directly. It can be seen that the leaky bucket algorithm can forcibly limit the data transmission rate.

      Token Bucket : It is the most commonly used algorithm in network traffic shaping (Traffic Shaping) and rate limiting (Rate Limiting). Typically, the token bucket algorithm is used to control the amount of data sent to the network and to allow bursts of data to be sent.

      A token bucket with a fixed size can generate tokens continuously at a constant rate by itself. If tokens are not consumed, or are consumed at a rate less than they are generated, tokens will continue to increase until the bucket is full. Tokens generated later will overflow from the bucket. Finally the maximum number of tokens that can be held in a bucket will never exceed the bucket size.

      The difference between leaky bucket and token bucket algorithm

      The token bucket algorithm is mainly placed on the server side to protect the server side (self), and is mainly used to limit the frequency of callers so as not to let yourself be overwhelmed. Therefore, if you have the processing capacity, if the traffic bursts (the actual consumption capacity is stronger than the configured traffic limit = bucket size), then the actual processing rate can exceed the configured limit (bucket size).
      The leaky bucket algorithm is mainly placed on the caller, which is used to protect others, that is, to protect the system he calls. The main scenario is that when the calling third-party system itself has no protection mechanism or has traffic restrictions, our calling speed cannot exceed its limit. Since we cannot change the third-party system, it is only controlled by the caller. At this time, even if the traffic bursts, it must be discarded. Because spending power is determined by a third party.

      Adaptive current limiting

      General current limiting often needs to specify a fixed value (qps) as the threshold of the current limiting switch. This value is firstly judged by experience, and secondly, it is obtained by passing a large amount of test data. However, this threshold may become inappropriate after the traffic surges, the system automatically scales, or someone commits a piece of toxic code. Moreover, general business parties are not able to correctly assess their own capacity and set an appropriate current limit threshold. Then we can consider using adaptive current limiting to solve this problem.

      For adaptive flow limiting, it is generally combined with monitoring indicators in several dimensions such as system Load, CPU usage, application entry QPS, average response time, and concurrency. Through adaptive flow control strategies, the system's The ingress traffic and the load of the system reach a balance, so that the system can run at the maximum throughput as much as possible while ensuring the overall stability of the system.

      Distributed current limiting

      The current limiting algorithms used above are all basic single-node current limiting. However, due to various reasons for online business, most of them are distributed systems. The current limit of a single node can only protect its own node, but it cannot protect various services that applications depend on, and it cannot be accurate when expanding or shrinking nodes. Controls the request limit for the entire service. For example, I want the QPS of a certain interface to be 1000 times/second, and the service is deployed on 5 machines, although we can limit the flow by configuring each node to be 200 times/second. But if the node shrinks or expands, it will not be able to meet the demand for a long time. Moreover, the physical configurations of different services are not necessarily the same. Some nodes may process faster, so it is not a good way to configure the average value to limit the flow.

      Common Distributed Current Limiting Strategies

      Gateway layer current limiting: apply current limiting rules to the entrance of all traffic, such as nigix+lua
      middleware current limiting: store current limiting information in a middleware in a distributed environment (such as Redis cache), each component You can get the traffic statistics at the current moment from here, so as to decide whether to deny service or allow traffic.

    3. load balancing

      Load balancing, or load balancing, is a computer technology used to distribute load among multiple computers (computer clusters), network connections, CPUs, disk drives, or other resources to optimize resource usage, maximize throughput, The purpose of minimizing response time while avoiding overload.

      Load Balance (Load Balance) means to balance and distribute loads (work tasks, access requests) to multiple operating units (servers, components) for execution. It is the ultimate solution for high performance, single point of failure (high availability), and scalability (horizontal scaling).

      load balancing algorithm

      1. Polling method

      Distribute requests to the back-end servers in turn in sequence, and it treats each server in the back-end in a balanced manner, regardless of the actual number of connections on the server and the current system load.

      2. Random method

      Through the random algorithm of the system, one of the servers is randomly selected for access according to the list size value of the back-end server. It can be known from the theory of probability and statistics that as the number of times the client calls the server increases, its actual effect is getting closer and closer to the average distribution of calls to each server at the back end, which is the result of polling.

      3. Source address hash method

      The idea of ​​source address hashing is to obtain a value calculated by the hash function based on the IP address of the client, and use this value to perform a modulo operation on the size of the server list, and the result obtained is the serial number of the server that the customer service terminal wants to access. The source address hashing method is used for load balancing. For clients with the same IP address, when the list of back-end servers remains unchanged, it will be mapped to the same back-end server for access every time.

      4. Weighted polling method

      Different back-end servers may have different machine configurations and current system loads, so their stress resistance capabilities are also different. Assign higher weights to machines with high configuration and low load to allow them to handle more requests; assign lower weights to machines with low configuration and high load to reduce their system load, and weighted polling works well Handle this problem properly, and assign requests to the backend in order and according to weight.

      5. Weighted random method

      Like the weighted round-robin method, the weighted random method also assigns different weights according to the configuration of the back-end machine and the load of the system. The difference is that it randomly requests the backend servers according to the weight, not in order.

      6. Minimum number of connections

      The algorithm for the minimum number of connections is more flexible and intelligent. Due to the different configurations of the back-end servers, the processing of requests may be fast or slow. It dynamically selects the current one according to the current connection status of the back-end servers.

      A server with the least number of backlogged connections is used to process the current request, and the utilization efficiency of the back-end service should be improved as much as possible, and it will be responsible for reasonably allocating to each server.

2.3 Viper

  1. What is Viper

    Viper is a complete configuration solution for Go applications. It is designed to work within applications and can handle all types of configuration needs and formats.

    characteristic:

    • set default
    • Read configuration information from configuration files in JSON, TOML, YAML, HCL, envfileand formatsJava properties
    • Live monitoring and re-reading of configuration files (optional)
    • read from environment variable
    • Read and monitor configuration changes from a remote configuration system (etcd or Consul)
    • Read configuration from command line arguments
    • Read configuration from buffer
    • explicit configuration value
  2. What functions does Viper support?

    Viper can do the following for you:

    1. Find, load, and deserialize configuration files in the JSON, TOML, YAML, HCL, INI, envfileand formats.Java properties
    2. Provides a mechanism to set default values ​​for your various configuration options.
    3. Provides a mechanism to override the value of specified options via command-line arguments.
    4. An alias system is provided to easily rename parameters without breaking existing code.
    5. When the user provides the same command line or configuration file as the default, it is easy to tell the difference.
    • establish default
    • read configuration file
    • write configuration file
    • Monitor and re-read configuration files
    • Read configuration from io.Reader
    • override settings
    • Register and use an alias
    • use environment variables
    • Use Flags
    • Remote Key/Value storage support
    • Monitor changes in etcd - unencrypted

2.4 Swagger

  1. What is Swagger

    Swagger is essentially an interface description language for describing RESTful APIs expressed in JSON. Swagger is used with a set of open source software tools to design, build, document and consume RESTful web services. Swagger includes automatic documentation, code generation, and test case generation.

    To use gin-swaggerthe automatic generation of interface documents for your code, the following three steps are generally required:

    1. Add declarative comments to the interface code according to swagger requirements, refer to the declarative comment format for details
    2. Use the swag tool to scan the code to automatically generate API interface document data
    3. Use gin-swagger to render the online interface documentation page
  2. Advantages of Swagger

    In the project development process where the front-end and back-end are separated, if the back-end students can provide a clear interface document, then everyone's communication efficiency and development efficiency can be greatly improved. But writing interface documents has always been a headache, and the maintenance of subsequent interface documents is also very energy-consuming.

    It is best to have a solution that can meet our needs for output documentation and can be automatically updated with code changes, and Swagger is the kind of tool that can help us solve interface documentation problems.

2.5 Zap

  1. What is Zap

    Zap is ultra-fast, structured, hierarchical logging in Go.

    Zap logs can provide the following functions:

    • 1. Events can be recorded to a file or output on the application console

    • 2. Log cutting - log files can be cut according to file size, time or interval

    • 3. Support different log levels. Such as INFO, DEBUG, ERROR, etc.

    • 4. Able to print basic information, such as calling file/function name and line number, log time, etc.

    The basic configuration of zap
    Zap provides two types of logger - Sugared Logger and Logger.

    In contexts where performance is great but not critical, use SugaredLogger. It is 4-10 times faster than other structured logging packages and supports both structured and printf-style logging.

    In contexts where every microsecond and every memory allocation counts, use a Logger. It's even faster than SugaredLogger and requires fewer memory allocations, but it only supports strongly typed structured logging.

    The only thing missing from this logger is log cutting and archiving. To add the function of log cutting and archiving, we will use the third-party library Lumberjack to achieve it.

2.6 JWT

  1. What is JWT

    The English name of JWT is Json Web Token, which is a concise and URL-safe expressive declaration specification used to transfer security information between communication parties, and is often used in cross-domain authentication.

    JWTs securely pass information in the form of JSON objects. The information delivered is secure because of the digital signature.

    A JWT Token looks like this:

    eyJhbGci0iJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyODAx0DcyNzQ40DMyMzU4NSwiZ
    XhwIjoxNTk0NTQwMjkxLCJpc3MiOiJibHV1YmVsbCJ9.1k_ZrAtYGCeZhK3iupHxP1kgjBJzQTVTtX0iZYFx9wU

  2. Implementation of JWT

    JWT consists of three parts separated by . These three parts are:

    • Header
      function: record token type, signature algorithm, etc. For example: {"alg":"HS256","type","JWT}
    • Payload
      function: carry some user information such as {"userId": "1", "username": "mayikt"}
    • Signature (Signature)
      function: to prevent Token from being tampered with and to ensure security. For example, the calculated signature, a string
      header and payload exist in the form of json. This is the JSON in JWT. The contents of the three parts are separately encoded by Base64 , concatenated with . into a JWT Token.
  3. Advantages of JWTs

    JWT is a Token-based lightweight authentication mode. After the server-side authentication is passed, a JSON object will be generated, and a Token (token) will be obtained after signing and sent back to the user. The user only needs to bring this Token with subsequent requests. , the server can obtain the relevant information of the user after decrypting it.

    JWT has all the advantages of the Token-based session management method, and does not rely on cookies, so that it can prevent CSRF attacks and can also run normally in a browser environment that disables cookies.
    The biggest advantage of JWT is that the server no longer needs to store the session, which makes the authentication and authentication business of the server easy to expand, avoids the introduction of components such as Redis that need to be stored for the session, and reduces the complexity of the system architecture. But this is also the biggest disadvantage of JWT. Since the validity period is stored in the Token, once the JWT Token is issued, it will be available within the validity period and cannot be abolished on the server side. When the user logs out, he can only rely on the client to delete it. Locally stored JWT Token, if users need to be disabled, it cannot be done by simply using JWT.

Guess you like

Origin blog.csdn.net/weixin_53795646/article/details/129420359