如何真正写好Golang代码?

从https://mp.weixin.qq.com/s/OIHqmgK4V7Y26uYoFjsCyA 复制

Golang 实现SOLID 设计原则

本章节按照设计模式中的核心设计原则介绍在Go语言中的实现。

单一职责原则

类的设计尽量做到只有一个原因引起变化。 在交易的场景中,我们需要做一些交易存储、验证,我们可以声明交易的结构体,这个结构体是为了存储每笔交易。但是验证的功能我们可以拆开,这样代码更具有维护性、测试的编写也更简单方便。

type Trade struct {
    TradeID int
    Symbol string
    Quantity float64
    Price float64
}

type TradeRepository struct {
    db *sql.DB
}

func (tr *TradeRepository) Save(trade *Trade) error {
    _, err := tr.db.Exec("INSERT INTO trades (trade_id, symbol, quantity, price) VALUES (?, ?, ?, ?)", trade.TradeID, trade.Symbol, trade.Quantity, trade.Price)
    if err != nil {
        return err
    }
    return nil
}

type TradeValidator struct {}

func (tv *TradeValidator) Validate(trade *Trade) error {
    if trade.Quantity <= 0 {
        return errors.New("Trade quantity must be greater than zero")
    }
    if trade.Price <= 0 {
        return errors.New("Trade price must be greater than zero")
    }
    return nil
}

开闭原则

对扩展开放,对修改关闭。实现常见的方法是,通过接口或者多态继承。 当我们的系统要增加期权交易的功能时,我们可以扩展接口实现,声明TradeProcessor,而不是在声明一个统一的处理器中,在里面写各种的兼容逻辑。

2023-11-20    
收藏专题

K8s相关

ovs controller

Istio

可以参考的链接

https://developer.aliyun.com/article/759790

https://zhuanlan.zhihu.com/p/499341027

ovs and iptables

https://www.cnblogs.com/jmilkfan-fanguiju/p/11825035.html

http://www.openvswitch.org/support/dist-docs/ovs-vsctl.8.txt

macVlan

https://icloudnative.io/posts/netwnetwork-virtualization-macvlan/

https://www.cnblogs.com/bakari/p/10893589.html

svc 的不同类型 NodePort等

Go

defer

nil

单例模式

struct 是否可以比较

new和make的区别

内存对齐

2023-11-13    
git强制pull

版权声明:本文为CSDN博主「我想要身体健康」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上> 原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_57236802/article/details/131249491

如果你想强制 git pull 来覆盖本地的更改,你需要注意这个过程会删除所有你在本地做的更改,并将你的本地分支同步到远程分支。如果你想要这样做,可以使用以下的命令:

git fetch --all
git reset --hard origin/<branch_name>

在上面的 <branch_name> 中填写你想要同步的远程分支的名字。比如,如果你想要同步的分支是 master,你可以运行:

git fetch --all
git reset --hard origin/master

第一条命令 git fetch --all 会从远程仓库获取所有分支的最新更改,但是并不会修改你的本地仓库。

第二条命令 git reset --hard origin/<branch_name> 会将你的本地分支重置到远程分支的状态,这会删除所有的本地更改。

再次提醒,这个过程会丢失所有未提交的本地更改,所以在使用这个命令之前一定要确认你是否真的需要这样做。

2023-11-12    
GMP

非原创

好文:https://yizhi.ren/2019/06/03/goscheduler

参考:https://blog.csdn.net/xmcy001122/article/details/119392934

Go 语言的协程 goroutine

Go 为了提供更容易使用的并发方法,使用了 goroutine 和 channel。goroutine 来自协程的概念,让一组可复用的函数运行在一组线程之上,即使有协程阻塞,该线程的其他协程也可以被 runtime 调度,转移到其他可运行的线程上。最关键的是,程序员看不到这些底层的细节,这就降低了编程的难度,提供了更容易的并发。

Go 中,协程被称为 goroutine,它非常轻量,一个 goroutine 只占几 KB,并且这几 KB 就足够 goroutine 运行完,这就能在有限的内存空间内支持大量 goroutine,支持了更多的并发。虽然一个 goroutine 的栈只占几 KB,但实际是可伸缩的,如果需要更多内容,runtime 会自动为 goroutine 分配。

Goroutine 特点:

  • 占用内存更小(2KB左右,系统线程需要1-8MB)
  • 调度更灵活(runtime 调度)

调度器

被废弃的 goroutine 调度器 - GM模型

Go 目前使用的调度器是 2012 年重新设计的,因为之前的调度器性能存在问题,所以使用 4 年就被废弃了,那么我们先来分析一下被废弃的调度器是如何运作的?

Go的调度程序是Go运行时的一个更重要的方面。运行时会跟踪每个Goroutine,并将安排它们在线程池中运行。goroutines与线程分离(解耦不强绑定),但运行于线程之上。如何有效地将goroutine调度到线程上对于go程序的高性能至关重要。

Goroutines的背后逻辑是:它们能够同时运行,与线程类似,但相比之下非常轻量。因此,程序运行时,Goroutines的个数应该是远大于线程的个数的。

同时多线程在程序中是很有必要的,因为当goroutine调用了一个阻塞的系统调用,比如sleep,那么运行这个goroutine的线程就会被阻塞,那么这时运行时至少应该再创建一个线程来运行别的没有阻塞的goroutine。线程这里可以创建不止一个,可以按需不断地创建,而活跃的线程(处于非阻塞状态的线程)的最大个数存储在变量GOMAXPROCS中。

简要说明

go运行时使用3个结构来跟踪所有成员来支持调度器的工作。

G的结构:

一个G代表一个goroutine,包含当前栈,当前状态和函数体。

struct G
{
byte stackguard; // stack guard information
byte stackbase; // base of stack
byte stack0; // current stack pointer
byte entry; // initial function
void param; // passed parameter on wakeup
int16 status; // status
int32 goid; // unique id
M lockedm; // used for locking M’s and G’s
...
}

M:

2023-11-12    
centos检测新的硬盘

如下命令向所有的SCSI主机发送一个扫描请求,可以帮助系统重新检测新添加的硬盘。

[root@compute1 ~]# echo "- - -" > /sys/class/scsi_host/host0/scan
[root@compute1 ~]# echo "- - -" > /sys/class/scsi_host/host1/scan
[root@compute1 ~]# echo "- - -" > /sys/class/scsi_host/host2/scan
2023-10-23    
如何解决虚拟机断电重启后进入紧急模式的问题

现象

系统文件损坏后进入紧急修复模式,无法进行维护工作

welcome to emergency mode!after logging in ,type “journalctl -xb” to view system logs,“systemctl reboot” to reboot ,“systemctl default” to try again to boot into default mode。

give root password for maintenance

查看运行日志,发现是/home目录挂载问题

journalctl -xb

# Failed to mount /home

修复

执行如下命令,确认/home的映射关系

[root@controller ~]# cat /etc/fstab  |grep home
/dev/mapper/centos-home /home                   xfs     defaults        0 0

方法一

xfs_repair /dev/mapper/centos-home

如果修复失败,请加-L参数

xfs_repair -L /dev/mapper/centos-home

如果修复完成,重启系统即可

方法二

如果方法一失效,则可以试试该方法

  1. 检查设备是否已经挂载。可以运行以下命令来查看设备的挂载状态:
df -h

​ 2. 如果设备已挂载,请尝试卸载该设备。可以使用以下命令卸载设备:

umount /dev/mapper/centos-home

​ 3. 创建一个目标目录,用于将备份数据存储到该目录中。可以使用以下命令创建目标目录:

2023-10-11    
go server退出

go server以相当优雅的姿势退出(待继续完善)

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

type H struct{}

func (H) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	time.Sleep(5 * time.Second)
	fmt.Fprintf(w, "Hello, World!") // 向客户端发送响应
}

func initServer() *http.Server {
	s := &http.Server{
		Addr:    ":8080",
		Handler: H{},
	}
	return s
}

type Backend struct {
	Ctx context.Context
	Srv *http.Server

	CancelFunc func()
}

var onlyOneSignalHandler = make(chan struct{})
var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}
var shutdownHandler chan os.Signal

// SetupSignalContext is same as SetupSignalHandler, but a context.Context is returned.
// Only one of SetupSignalContext and SetupSignalHandler should be called, and only can
// be called once.
func (b *Backend) SetupSignalContext() context.Context {
	close(onlyOneSignalHandler) // panics when called twice

	shutdownHandler = make(chan os.Signal, 2)

	signal.Notify(shutdownHandler, shutdownSignals...)

	go func() {
		<-shutdownHandler
		b.Clean()
		<-shutdownHandler
		os.Exit(1) // second signal. Exit directly.
	}()

	return b.Ctx
}

// SetupSignalHandler registered for SIGTERM and SIGINT. A stop channel is returned
// which is closed on one of these signals. If a second signal is caught, the program
// is terminated with exit code 1.
// Only one of SetupSignalContext and SetupSignalHandler should be called, and only can
// be called once.
func (b *Backend) SetupSignalHandler() <-chan struct{} {
	return b.SetupSignalContext().Done()
}

func (b *Backend) RunAndServe(stopCh <-chan struct{}) {
	b.Srv.ListenAndServe()
	<-stopCh
	if err := b.Srv.Shutdown(b.Ctx); err != nil {
		log.Fatal("Server Shutdown:", err)
	}
	log.Printf("initServer exiting")
}

func (b *Backend) Clean() {
	fmt.Println("do some clean")
	time.Sleep(time.Second * 10)
	b.CancelFunc()
	fmt.Println("clean over")
	b.Ctx.Done()
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	simpleBackend := Backend{
		Ctx:        ctx,
		Srv:        initServer(),
		CancelFunc: cancel,
	}
	simpleBackend.RunAndServe(simpleBackend.SetupSignalHandler())
}
2023-09-03    
软件工程-通用原则

David Hooker 提出7个关注软件工程整体实践的原则

  1. 存在价值

在确定系统需求前,在关注系统功能前,在决定硬件平台或者开发过程之前,问问自己:这确实能为系统增加真正的价值吗? 如果答案是不,那就坚决不做。

  1. 保持简洁

所有的设计都应该尽可能简洁,但不是过于简化。

  1. 保持愿景

有着清晰的目的,明确的思想

  1. 关注使用者

需求说明时:想到用户怎么用 设计中:想到怎么实现 编码时:想到怎么维护

  1. 面向未来

永远不要把自己的设计局限于一隅,经常问问“如果出现……应该怎么应对”,构建可以解决通用问题的系统

  1. 提前计划复用

  2. 认真思考

行动之前清晰定位、完整思考

2023-09-03    
eBPF_cilium

cilium组件

cilium组件

2023-07-04    
eBPF入门

〉 搬运自:https://blog.csdn.net/lianhunqianr1/article/details/124977297

eBPF特性

eBPF程序都是事件驱动的,他们会在内核或者应用程序经过某个确定的Hook点的时候运行,这些Hook点都是提前定义的,包括系统调用、函数进入/退出、内核tracepoints、网络事件等。

如果针对某个特定需求的Hook点不存在,可以通过kprobe或者uprobe来在内核或者用户程序的几乎所有地方挂载eBPF程序。

Verification

每个eBPF程序加载到内核都要经过Verification,用来保证eBPF程序的安全性,主要有:

  • 要保证 加载 eBPF 程序的进程有必要的特权级,除非节点开启了 unpriviledged 特性,只有特权级的程序才能够加载 eBPF 程序

    • 内核提供了一个配置项 /proc/sys/kernel/unprivileged_bpf_disabled 来禁止非特权用户使用 bpf(2) 系统调用,可以通过 sysctl 命令修改

    • 比较特殊的一点是,这个配置项特意设计为一次性开关(one-time kill switch), 这意味着一旦将它设为 1,就没有办法再改为 0 了,除非重启内核

    • 一旦设置为 1 之后,只有初始命名空间中有 CAP_SYS_ADMIN 特权的进程才可以调用 bpf(2) 系统调用 。Cilium 启动后也会将这个配置项设为 1:

    $ echo 1 > /proc/sys/kernel/unprivileged_bpf_disabled
    
  • 要保证 eBPF 程序不会崩溃或者使得系统出故障

  • 要保证 eBPF 程序不能陷入死循环,能够 runs to completion

  • 要保证 eBPF 程序必须满足系统要求的大小,过大的 eBPF 程序不允许被加载进内核

  • 要保证 eBPF 程序的复杂度有限,Verifier 将会评估 eBPF 程序所有可能的执行路径,必须能够在有限时间内完成 eBPF 程序复杂度分析

JIT Compilation

Just-In-Time(JIT)编译用来将通用的eBPF字节码翻译成与机器相关的指令集,从而极大加速BPF程序的执行:

  • 与解释器相比,他们可以降低每个指令的开销。
  • 减少生成的可执行镜像的大小,因此对CPU的指令缓存更友好
  • 特别的,对于CISC指令集(例如x86),JIT做了很多优化,目的是为给定的指令产生可能的最短操作码,以降低程序翻译过程所需要的空间。

64位的 x86_64、arm64、ppc64、s390x、mips64、sparc64 和 32 位的 arm 、x86_32 架构都内置了 in-kernel eBPF JIT 编译器,它们的功能都是一样的,可以用如下方式打开:

2023-06-24