Deadlock检测时机

Go语言的运行时会在所有活跃的goroutine都无法继续执行时判定为死锁。这意味着,只要还有至少一个goroutine能够正常运行,Go运行时就不会触发死锁检测机制。死锁检测通常在以下情况下触发: 所有goroutine都阻塞:如果所有的goroutine都在等待某些事件(如通道操作、锁获取等),而这些事件无法由其他goroutine触发(因为没有其他goroutine在运行或者能够解除阻塞状态),Go运行时就会判定程序为死锁状态。(这里不包括io等待,因为io等待是阻塞的,但是go的运行时并不会触发死锁检测机制) 这种自动死锁检测主要是为了帮助开发者在开发阶段识别出潜在的并发问题。然而,它的能力是有限的,特别是在涉及网络I/O、系统调用或者复杂锁逻辑的情况下,Go的死锁检测可能不会触发。因此,即使Go运行时没有报告死锁,也不代表程序中不存在潜在的并发问题。 为了避免死锁,推荐的做法包括: 避免循环等待:设计系统时应确保资源的分配顺序一致,以避免循环等待的情况发生。 使用适当的同步原语:比如使用带缓冲的channel、正确使用锁(如sync.Mutex)、以及其他并发控制工具(如sync.WaitGroup、context.Context等)。 限制并发数:有时通过限制系统中并发执行的goroutine数量可以简化资源管理,减少死锁的风险。 彻底测试:并发程序应该经过详尽的测试,包括使用竞态检测工具(如Go的-race标志)来帮助识别并发错误。 这些措施可以帮助开发者构建更健壮、更可靠的并发程序。

2024-05-08 17:52:55    |    1 分钟    |    9 字    |    Fengbin

在docker模拟虚拟IP实验时遇到的问题

获取不到虚拟IP 使用下面的命令时不能获取到虚拟IP docker run -it --net vipnet --ip 172.18.0.2 vipimage /bin/bash 应该添加参数可以让容器有权限操作网络配置 docker run -it --net vipnet --ip 172.18.0.2 --cap-add=NET_ADMIN vipimage /bin/bash 或者 docker run -it --net vipnet --ip 172.18.0.2 --privileged vipimage /bin/bash privileged小知识 在 Docker 中使用 --privileged 标志会给容器提供类似于宿主机 root 用户的权限。当容器以 --privileged 模式运行时,它可以绕过 Linux 内核的许多安全限制,从而获得较广泛的操作权限。这通常用于需要执行一些高级系统管理操作的场景,例如直接访问硬件设备或进行某些需要特殊权限的网络操作。 --privileged 模式的主要影响包括: 全能力(Capabilities): 容器将获得 Linux 所有的 capabilities,与在宿主机上运行的进程几乎相同。 设备访问: 容器可以访问和操作宿主机上的所有设备(/dev 下的设备)。 安全限制: 绕过了 AppArmor 或 SELinux 的限制,容器可以执行更多的系统级操作。 文件系统: 容器可以挂载宿主机上的文件系统,甚至使用一些通常需要更高权限的挂载选项。 网络操作: 允许执行一些通常受限的网络操作,如更改网络配置或使用低号端口。 使用场景 使用 --privileged 模式通常适用于以下场景: 开发和测试: ...

2024-04-26 18:12:19    |    1 分钟    |    132 字    |    Fengbin

Wine

wine执行中文程序乱码 wine后执行winecfg配置wine时出现乱码可以使用命令安装字体解决 sudo apt-get install fonts-wqy-zenhei fonts-wqy-microhei # 安装基础组件 winetricks corefonts cjkfonts # 自动安装中文支持 # 设置中文环境 winetricks setwinver=win10 winetricks fakechinese # 模拟中文系统 WINEPREFIX=~/.wine winetricks riched20 未响应问题 运行:winecfg 在 Applications 页面将 Windows 版本改为程序要求的版本(如 Windows 7、Windows 10)。 如果不能解决问题可以尝试下面的方法,安装依赖 sudo apt install winetricks winetricks vcrun2015 dotnet48 corefonts

2024-04-22 16:42:34    |    1 分钟    |    44 字    |    Fengbin

添加字段引发的不兼容

大家都是知道如果我们在接口返回值添加字段或者proto中添加字段一般是不会发生不兼容的情况,那么在数据库中给表添加字段会导致不兼容问题吗? 通常情况是不会,添加字段不会影响到已有的数据,但是下面这种情况会引发不兼容 假设A表有字段id, bid, c, B表有id, d,业务中有这么一条语句 select a.id, b.d from a join b on a.bid = b.id where c = 1; 那么现在我们给B表加字段c,那么现在这个语句就会错,因为a表有个c,b表有个c,现在where条件中的c已经有歧义了,所以这条语句就会报错,所以给表加字段会引发不兼容 所以加字段的时候也要小心,sql如果是多表一定给字段加上别名,避免这种歧义就比如上面的语句就改为 select a.id, b.d from a join b on a.bid = b.id where a.c = 1; 这只能说约束我们自己,历史的老系统中有什么样的写法都未可知

2024-01-12 12:37:19    |    1 分钟    |    40 字    |    Fengbin

mysql中time_zone的作用,以及为什么要配置本地时区和db时区

mysql中time_zone的作用,以及为什么要配置本地时区和db时区 1. mysql中time_zone的作用 mysql中time_zone的作用,就是用来设置时区的(废话),time_zone影响TIMESTAMP类型和NOW等函数的值,但并不影响DATETIME类型和DATE类型。 因为TIMESTAMP存储的时时间戳,当用户发送过来一个时间如“2023-12-11 10:41:18”,那么mysql会将其时间戳,这个时间就是按time_zone设置的时区来解析的,然后转换成时间戳保存, 在给用户查询时在通过时区转换会时间串。 DATETIME类型不是时间戳,存入时是“2023-12-11 10:41:18”,查询时也是“2023-12-11 10:41:18”,不会做转换,也和时区无关 2. 为什么要配置本地时区和db时区 本地时区是给mysql驱动使用,并不会影响mysql的时区,只会影响mysql驱动的解析, db时区是mysql的时区,会影响mysql的时区,会影响mysql的查询结果。 本地时区作用 在读取到mysql发来的时间,go会按照本地时区来解析,转换为time.Time类型mysql按照数据库time_zone返回时间串后,go并不知道用哪个时区来解析这个时间串,所以需要设置本地时区。 本在写入time.Time类型时,go会把time.Time转换为本地时区发送给mysql 比如设置本地时区为“Asia/Shanghai” root:root@tcp(127.0.0.1:3306)/db?charset=utf8&loc=Asia%2FShanghai&parseTime=true通过loc参数来这设置 同时设置本地和数据库时区 root:root@tcp(127.0.0.1:3306)/db?charset=utf8&loc=Asia%2FShanghai&parseTime=true&time_zone=%27%2B8%3A00%27 dsn上的参数有的是给数据库驱动使用,有的是给mysql使用,具体可参阅mysql驱动的DSN

2023-12-11 10:41:18    |    1 分钟    |    22 字    |    Fengbin

本地+redis多级缓存

由一次线上问题引发的思考,本地缓存+redis缓存的多级缓存方案。 一次线上发生OOM, CPU占用高排查,之前所有的缓存都是放在redis中,并发高时,大量请求去redis,服务反序列化,导致CPU占用高,内存占用高,最终达到资源上线被k8s杀掉。 后来想到了使用本地缓存,这种公共对象保存一份数据,不用反复序列化,减少CPU占用,减少内存占用,但是本地缓存有一个问题,多个副本在同一时间可能缓存数据不一致,虽然在我们这个场景下,这份公共数据更新不频繁,但是也有可能发生这个情况,所以想到了使用多级缓存,本地缓存+redis缓存,本地缓存作为一级缓存,redis作为二级缓存,当redis更新时会设置数据版本号(时间戳),本地获取时会比对版本号,如果相同redis就不返回数据,如果不同就返回数据,这样就可以保证数据一致性。 用go描述一下取值和设置值的逻辑,其他逻辑都比较简单,这里使用lua脚本来实现,版本和数据的原子性和比较版本和返回相应的返回值,还可以减少网络开销 package main import ( "fmt" "time" "github.com/go-redis/redis" ) //这段脚本是用来设置值的,设置值的时候会设置版本号,设置过期时间 const setval = ` local key_val = KEYS[1] local key_version = key_val .. "_version" local val = ARGV[1] local expire = ARGV[2] local version = ARGV[3] redis.call('SET', key_version, version) redis.call('EXPIRE', key_version, expire) redis.call('SET', key_val, val) redis.call('EXPIRE', key_val, expire) return nil ` //这段脚本是用来取值的,取值的时候会比较版本号,如果版本号不一致就返回值 const getval = ` local key_val = KEYS[1] local key_version = key_val .. "_version" local givenVersion = ARGV[1] local version = redis.call('GET', key_version) if not version then return nil end if version ~= givenVersion then local value1 = redis.call('GET', key_val) return {version, value1} end return {version} ` func main() { redisClient := redis.NewClient(&redis.Options{ Addr: "xxx", Password: "xxx", DB: 0, }) cmd := redisClient.Eval(getval, []string{"ab"}, "1") if cmd.Err() != nil { fmt.Println(cmd.Err()) } val := cmd.Val() fmt.Println(val) cmd = redisClient.Eval(setval, []string{"ab"}, time.Now().UnixNano(), 100, time.Now().Unix()) if cmd.Err() != nil && cmd.Err() != redis.Nil { fmt.Println(cmd.Err()) } } 说一下总体的思路 使用一个localstorage, redis, redis lua, singleflight ...

2023-11-24 14:20:35    |    1 分钟    |    170 字    |    Fengbin

DCDC原理 电感

DCDC 升压原理 直流-直流(DC-DC)升压转换器是一种电子电路,用于将输入直流电压转换为较高的输出直流电压。其原理基于能量储存和传递的概念。 DC-DC升压转换器的基本原理: 开关器件: 这种转换器通常使用开关器件(如MOSFET)来控制输入电压的存储和传递。这些器件通过开关操作,控制能量流动的路径,实现电压转换。 储能元件: 其中一个核心组件是储能元件,比如电感或电容。在升压转换器中,电感是最常见的储能元件之一。当开关器件关闭时,电感储存电能;当开关器件打开时,储能元件释放储存的能量,以提供输出所需的较高电压。 控制电路: 升压转换器还包括一个控制电路,用于监测输出电压并控制开关器件的工作周期,以保持所需输出电压稳定。 脉宽调制(PWM): 控制电路通常使用脉宽调制技术,通过改变开关器件的工作周期和频率来调节输出电压。通过调整开关器件的工作时间和间隔,可以控制能量传递的速率,从而实现输出电压的精确调节。 在升压转换器中,输入电压经过周期性开关,并通过储能元件转换为较高的输出电压。这种转换方式允许在电子设备中提供所需的不同电压,例如从电池供电的设备需要较高的工作电压。 总的来说,DC-DC升压转换器通过开关器件、储能元件和控制电路来转换输入电压为所需的更高输出电压,使得在各种应用中能够实现稳定的电源供应。 如何控制电感储能后的电压 控制电感储能后的电压通常通过调整开关器件的工作周期和频率来实现。这涉及到脉宽调制(PWM)技术,通过改变开关器件的导通和关断时间,控制能量的传递速率,从而调节输出电压。 以下是实现这种控制的基本步骤: 脉宽调制(PWM): 使用PWM技术来调节开关器件的导通和关断时间。导通时间越长,电感中储存的能量就越多,从而提高输出电压。关断时间的调节则影响能量传递的速率。 控制电路: 设计一个控制电路,该电路监测输出电压并与设定值进行比较。根据比较的结果,调整PWM信号,以使输出电压保持在所需的水平。 反馈回路: 在控制电路中加入反馈回路,用于实时监测输出电压。一种常见的反馈方式是采用电压反馈,其中输出电压与参考电压进行比较,然后调整PWM信号以纠正任何电压偏差。 PID控制: 对于更复杂的系统,可能采用比例-积分-微分(PID)控制器。PID控制器可以更精确地调节输出电压,通过对比例项、积分项和微分项的权衡来实现快速而稳定的响应。 通过这些控制手段,可以实现对电感储能后的电压进行精确调节,使其保持在所需的水平,从而满足不同应用对电源电压的要求。 如何理解通过PWM来调节电感电压,举例说明 脉宽调制(PWM)是一种通过调整脉冲的宽度来控制平均电压的技术。在电感储能的情况下,PWM被用来调节开关器件的工作周期,从而影响电感中存储的能量量,最终调节输出电压。以下是一个简单的例子,说明如何通过PWM来调节电感电压: 基本电路架构: 考虑一个基本的升压DC-DC转换器,包含以下关键组件: 开关器件(例如MOSFET):用于控制电能的开关。 电感:储存能量的元件。 控制电路:监测输出电压并生成PWM信号。 工作原理: 当开关器件导通(关闭)时,电流通过电感,将能量储存在电感中。 当开关器件关断时,储存在电感中的能量通过二极管传递到输出端,提供给负载。 PWM控制: 控制电路监测输出电压,并将其与设定值进行比较。 如果输出电压低于设定值,控制电路将增加PWM信号的宽度,延长开关器件的导通时间,使电感中储存的能量增加。 如果输出电压高于设定值,控制电路将减小PWM信号的宽度,缩短导通时间,减少电感中的储能。 示例: 初始状态下,输出电压低于设定值。 控制电路增加PWM信号的宽度。 开关器件导通时间延长,电感中储存的能量增加。 输出电压逐渐上升。 控制电路监测到输出电压接近设定值,减小PWM信号的宽度。 开关器件导通时间缩短,电感中的储能减少。 输出电压稳定在设定值附近。 通过这种方式,PWM信号的调节直接影响了开关器件的工作周期,从而影响了电感中储存的能量,最终调整了输出电压。这种反馈控制系统能够在不同工作条件下稳定地提供所需的电压输出。 如何计算通断时间产生的电感电压 电感在开关电路中的通断时间会影响其产生的电压。在直流-直流(DC-DC)升压转换器中,可以使用以下基本公式来估算电感电压: $V_L = L \frac{\Delta I}{\Delta t}$ 其中: $(V_L)$ 是电感上的电压。 $(L)$ 是电感的电感值。 $(\Delta I)$ 是电感中电流的变化。 $(\Delta t)$ 是电流变化所经过的时间。 这个公式基于电感的电压-电流关系: $V_L = L \frac{di}{dt}$ ...

2023-11-23 14:42:12    |    2 分钟    |    324 字    |    Fengbin

Viper读取自定义远程配置

实现viper.remoteConfigFactory接口 type remoteConfigProvider struct{} //这个接口是在第一次读取远程配置时调用,viper.ReadRemoteConfig() func (rc remoteConfigProvider) Get(rp viper.RemoteProvider) (io.Reader, error) { log.Println("Getting config from remote", rp) r := bytes.NewReader([]byte("{\"a\":1}")) time.Sleep(time.Second) return r, nil } //这个接口是在客户端调用viper.WatchRemoteConfig(), 只监听一次变化 func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) { //这里写你获取配置的方法 log.Println("Watching config from remote", rp) s := fmt.Sprintf(`{"app":{"name":"test","version":%d}}`, time.Now().Unix()) r := bytes.NewReader([]byte(s)) time.Sleep(time.Second) return r, nil } //这个接口是在客户端调用viper.GetViper().WatchRemoteConfigOnChannel()时,监听多次变化 func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *viper.RemoteResponse, chan bool) { log.Println("Watching Channel config from remote", rp) ch := make(chan *viper.RemoteResponse) go func() { //defer close(ch) viper里面使用死循环来处理,如果关闭管道会返回nil, 而viper没有处理,会报painc,所以不用关闭,viper是想让你在整个生命周期都监听 //这里写你获取配置的方法 for i := 0; ; i++ { ch <- &viper.RemoteResponse{ Value: []byte(fmt.Sprintf(`{"app":{"name":"test","version":%d}}`, time.Now().Unix())), } time.Sleep(time.Second) } }() return ch, nil } 设置viper必要参数 func init() { viper.RemoteConfig = remoteConfigProvider{} //把我们写的provider设置好,否则监听不会生效 viper.SupportedRemoteProviders = []string{"app"} //这里名称需要设置好,和下面添加的名称需要一致 } 使用viper viper.SetConfigType("json") //需要和返回的数据格式一致,不然无法解析 viper.AddRemoteProvider("app", ":8080", "/aabb/cc") //app就是上面的SupportedRemoteProviders,viper内部会校验是否包含,后面两个参数根据自己的需求来填写 viper.ReadRemoteConfig() //加载远程配置 // viper.WatchRemoteConfig() //一次性监听配置,阻塞 viper.GetViper().WatchRemoteConfigOnChannel() //一直监听,非阻塞

2023-09-15 15:56:44    |    1 分钟    |    131 字    |    Fengbin

在docker中go编译应注意的问题

1.使用golang:tag 这种镜像编译后在alpine中会报错(not found) 原因是:go默认会使用glibc,而alpine中没有glibc,所以会报错 解决办法有三种: 连接RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86_64.so.2,建立一个glic链接 使用golang:tag-alpine镜像编译,编译系统和运行系统都用alpine 编译时禁用CGO CGO_ENABLED=0 go build编译出来的文件不依赖动态库

2023-09-07 11:17:42    |    1 分钟    |    18 字    |    Fengbin

Ubuntu当开发过程中主力机遇到的问题

添加监控(cpu,内存…) 添加gnome浏览器连接器sudo apt-get install gnome-browser-connector 安装astra-monitor扩展,页面打开时安装浏览器插件 使用deb包安装软件时想要拆卸 在使用dpkg -r拆卸软件需要知道包名,有的时候不好查,可以使用用Ubuntu Software打开deb包,点删除 安装virtualbox,启动虚拟机报 Kernel driver not installed (rc=-1908) 可以使用sudo apt install --reinstall linux-headers-$(uname -r) virtualbox-dkms dkms 安装完成后重启 使用clash for window时,无法正常代理 在ubuntu设置中,点击网络,设置网络代理配置好即可 使用拼音输入法 在设置里找到系统->语言->安装中文 在键盘->输入源->中文->添加pinyin等 非root用户无法使用80端口 通过执行sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80命令,你在更改了系统配置文件,指定了非特权用户可以使用的TCP/UDP端口号的起始值为80。这意味着普通用户可以绑定和使用80端口,而无需特权身份(如root或管理员权限)。 不过这样重启后就无法使用80端口了,所以需要配置一个重启后也生效的配置文件,打开/etc/sysctl.conf文件,在文件后面追加 net.ipv4.ip_unprivileged_port_start = 80,这样重启后也可以生效。 添加应用图标 添加路经:当前用户~/.local/share/applications或者全局 /usr/share/applications 创建.desktop文件 如: # datagrip.desktop [Desktop Entry] Name=DataGrip Exec=/home/shifengbin/softwares/datagrip-2025.2.3/DataGrip-2025.2.3/bin/datagrip Icon=/home/shifengbin/softwares/datagrip-2025.2.3/DataGrip-2025.2.3/bin/datagrip.png Terminal=false Type=Application StartupWMClass=jetbrains-datagrip #这个如果是x11窗口就必须写,否则如果是x11窗口dock拦截图标不正确,但是不影响运行 Categories=Development; 获取VMClass的方法为执行xprop WM_CLASS命令, 然后点击x11应用窗口,会返回字符结果 如果不是x11就没反应

2023-08-14 15:08:46    |    1 分钟    |    62 字    |    Fengbin