LOGO
首页 网站广场 站长动态 活跃度榜 审核查询 逛逛好站 留言交流 提交申请 关于本站

站长动态

站长动态所展示的是已加入好站网成员站长文章
共同步 2420 篇博文
(每2小时更新一次)
小十
入驻第1年
使用群晖nas搭建独立局域网ip的docker版centos
群晖系统里面的docker套件非常好用,但是群晖默认情况下,没有桥接到局域网的网络配置,因此要想建立的docker拥有独立的局域网ip,而不是通过端口转发,或者占用nas的端口的方式,就需要对网络进行一定的配置。 本篇就记录了从新建docker网络配置到最后建立独立局域网ip的docker版centos系统的过程。 一、创建桥接网卡 1、启用open switch 控制面板-网络-网络界面-管理-open switch设置,勾选启用,确定。 (如果安装了VMM套件,此功能默认开启) 2、Docker创建桥接网卡 这一步不可少,群晖Docker默认是没有桥接网卡的,需要手动创建。 首先控制面板-终端机和SNMP,启用SSH功能。 通过ssh软件连入群晖后台 sudo -i 回车输入密码 输入命令 ip addr 返回的值中,找到对应你的群晖IP地址那条,上面就是物理网卡名称,网卡名称是ovs_eth0 输入命令创建桥接网卡 docker network create -d macvlan --subnet=192.168.31.0/24 --gateway=192.168.31.1 -o parent=ovs_eth0 bridge-host 命令说明:192.168.31.0为你的内网地址段,192.168.31.1为你的网关地址,ovs_eth0是你的物理网卡名称 创建好之后,docker的网络里会多出一个bridge-host网卡 二、创建centos7系统 1、下载镜像 在注册表中下载centos的镜像,我选择了centos7.6版本。 或者使用命令获取:docker pull centos 在ssh下,输入docker images查看镜像列表。 2、启动镜像 通过镜像启动docker实例,并在启动实例的过程中,配置好相关参数。 命令如下: docker run --privileged -itd --name centos7 --network bridge-host --ip=192.168.31.111 f1cb7c7d58b7 /usr/sbin/init —-privileged 启动后让docker容器具备超级特权。 -itd 交互式、终端、后台运行 –name 给启动的容器命名,方便后续操作 –network 配置docker使用的网卡 –ip 设置dokcer的ip f1cb7c7d58b7 就是IMAGE ID 注:–privileged 和/usr/sbin/init是必须的,否则会报错。 Failed to get D-Bus connection: Operation not permitted
Debug
入驻第1年
订阅本站
Feed 订阅 欢迎订阅下面的 Feed,您可以及时跟踪我的更新: 1 https://blog.debuginn.com/index.xml 本博客已经有 订阅者。 公众号订阅 当然,您不习惯上方方式订阅,您可以选择你经常阅读的社区进行订阅,目前仅支持 知乎专栏、腾讯云+社区(Sync)。 视频专区 我还不时制作视频上传至 哔哩哔哩 和 Youtube ,欢迎关注。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
小十
入驻第1年
更换了博客的主程序
从2011年2月2日第一次申请了顶级com域名后,这个博客就开始使用了,最开始就使用了wordpress程序,在第一次使用顶级域名之前,还使用过zblog程序等等,那时候的网站备份随着机械移动硬盘的损坏,再也没有办法恢复了。 最近工作上挺忙,因为赶上了安全学习和郑州7·20灾害,在工作之余,偶尔看了下网站的运行状态,发现wordpress更新非常频繁,使用的主题也已经无法满足部分兼容需求。新版本的博客程序也越来越臃肿,实在没有办法,就想到重新装一下服务器的操作系统,重新配置下服务器环境。 将旧网站(chyfh.com,i.xiaoten.com,demo.xiaoten.com,work.xiaoten.com,backup.xiaoten.com,hua.xiaoten.com,note.xiaoten.com,jiaohuiming.cn……)完全转移到自己的本地环境,而真正留下外网服务器运行的就是,博客、论坛和相册这三个了。 今天先把博客更换为typecho程序,因为这程序非常简单,更新也不频繁,也刚好有一个比较易用的主题,可以使用,主题本身功能很强大,但没有认真去研究。 重点还是先把内容恢复了。 不知道用这个程序能不能用的惯,已经遇到很多问题,慢慢解决。。
Debug
入驻第1年
[译] 方法是否应该在 T 或 *T 上声明
译文原地址:Should methods be declared on T or *T - David 在 Go 中,对于任何的类型 T,都存在一个类型 *T,他是一个表达式的结果,该表达式接收的是类型 T ,例如: 1 2 3 type T struct { a int; b bool } var t T // t's type is T var p = &t // p's type is *T 这两种类型,T 和 *T 是不同的,但 *T 不能代替 T。 你可以在你拥有的任意类型上声明一个方法;也就是说,在您的包中的函数声明的类型。因此,您可以在声明的类型 T 和对应的派生指针类型 *T 上声明方法。另一种说法是,类型上的方法被声明为接收器接收者值的副本,或一个指向其接收者值的指针。所以问题就存在了,究竟是哪种形式最合适? 显然,如果你的方法改变了他的接收者,他应该在 *T 上声明。但是,如果方法不改变他的接收者,在 T 上声明它是安全的么? 事实证明,这样做的话安全的情况非常有限(简单理解就是不安全的)。例如,众所周知,你不应该复制一个 sync.Mutex 的值,因为它打破了互斥量的不变量。由于互斥锁控制对变量(共享资源)的访问,他们经常被包装在一个结构体中,包含他们的控制的值(共享资源): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package counter type Val struct { mu sync.Mutex val int } func (v *Val) Get() int { v.mu.Lock() defer v.mu.Unlock() return v.val } func (v *Val) Add(n int) { v.mu.Lock() defer v.mu.Unlock() v.val += n } 大部分 Gopher 都知道,忘记在指针接收器 *Val 上是声明 Get 或 Add 方法是错误的。然而,任何嵌入 Val 来利用其 0 值的类型,也必须仅在其指针接收器上声明方法,否者可能会无意复制其嵌入类型值的内容: 1 2 3 4 5 6 7 type Stats struct { a, b, c counter.Val } func (s Stats) Sum() int { return s.a.Get() + s.b.Get() + s.c.Get() // whoops(哎呀) } 维护值切片的类型可能会出现类似的陷阱,当然也有可能发生意外的数据竞争。 简而言之,我认为您更应该喜欢在 *T 上声明方法,除非您有非常充分的理由不该这样做。 我们说 T 但这只是您声明的类型的占位符; 此规则是递归的,取 *T 类型的变量的地址返回的是 **T 类型的结果; 这就是为什么没有人可以在像 int 这样的基础类型上声明方法; Go 中的方法只是将接受者作为第一个形式参数传递的函数的语法糖; 如果方法不改变它的接收者,它是否需要是一个方法吗? 相关文章: What is the zero value, and why is it useful? Ice cream makers and data races Slices from the ground up The empty struct 最后,此篇文章我是第一次尝试翻译英文文章,尽管英文水平不太好,一些单词不认识,但是相信自己翻译一篇文章可以学习英语与理解 Go 设计获取 double 的乐趣。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
我的阅读
我的阅读清单 2025 状态 书籍 类别 笔记 推荐指数 ✅ 《高效能人士的七个习惯》 效率 对于职场提高效率非常有帮助,第二遍看 🌟🌟🌟🌟🌟 ✅ 《纳瓦尔宝典》 财经与幸福 资本的原始积累与幸福密集的分享,还是非常有帮助的 🌟🌟🌟🌟🌟 ✅ 《挪威的森林》 小说 一个关于人性的深度思考,也可以作为一种生活方式 🌟🌟🌟🌟🌟 ✅ 《鹿鼎记》 小说 读起来很有代入感,逻辑闭环很好,简直了爽文 🌟🌟🌟🌟🌟 ✅ 《奈飞文化手册》 管理 对奈飞的文化进行了详细的介绍,也可以作为一种管理方式 🌟🌟🌟🌟 ✅ 《人类简史》 历史 一个关于人类历史的深度思考 🌟🌟🌟🌟 ✅ 《左耳听风》 技术 写的很真挚也很有深度,作者 RIP. 🌟🌟🌟🌟🌟 ✅ 《自洽的程序员》 职场 一个关于程序员的深度思考,怎么说呢,建议阅读~ 🌟🌟🌟🌟🌟 2024 状态 书籍 类别 笔记 推荐指数 ✅ 《长安的荔枝》 小说 感谢同事分享的马伯庸老师的《太白金星有点烦》和《长安的荔枝》,我理解他的作品为神话历史类讽刺小说? 🌟🌟🌟🌟🌟 ✅ 《我在北京送快递》 白话文 跟着作者体验最真实的凡人歌。 🌟🌟🌟🌟 2023 状态 书籍 类别 笔记 推荐指数 ✅ 《富爸爸穷爸爸》 理财 怎么去积累财富,摆脱穷人思维,跳出“老鼠赛跑”游戏 🌟🌟🌟🌟🌟 ⏳ 《毛泽东选集》 文论 看了新民主主义革命至抗日战争的相关章节,不得不佩服领袖的远见与决策,推荐阅读 🌟🌟🌟🌟🌟 ✅ 《Apache Dubbo 微服务从入门到精通》 技术 Java 协议相关书籍 🌟🌟🌟🌟🌟 ✅ 《埃隆·马斯克传》 传记 遵循“第一性原理”,创造奇迹的人,推荐阅读 🌟🌟🌟🌟🌟 ✅ 《太白金星有点烦》 小说 如何规划西天取经,看太白金星与观音菩萨的运作 🌟🌟🌟🌟🌟 2022 状态 书籍 类别 笔记 推荐指数 ✅ 《Effective Go 中英双语版》 技术 🌟🌟🌟🌟 ✅ 《一往无前》 公司 🌟🌟🌟🌟🌟 ✅ 《黄金时代》 小说 🌟🌟🌟🌟 ✅ 《乖,摸摸头》 散文 🌟🌟🌟🌟🌟 ✅ 《图解 HTTP》 技术 🌟🌟🌟🌟 ✅ 《分布式缓存:原理、架构及Go语言实现》 技术 🌟🌟🌟🌟 ✅ 《深入理解Java虚拟机JVM高级特性与最佳实践》 技术 🌟🌟🌟🌟🌟 ✅ 《深入设计模式》 技术 🌟🌟🌟🌟 ⏳ 《保重》 散文 🌟🌟🌟🌟🌟 2021 状态 书籍 类别 笔记 推荐指数 ✅ 《MySQL 实战 45 讲》 技术 🌟🌟🌟🌟🌟 ✅ 《Redis 设计与实现》 技术 🌟🌟🌟🌟🌟 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
emmm 这是一篇碎碎念
距离最后一篇博文 《Go 语言开发设计指北》发布已经过去一个多月的时间了,在这一段的时间里,在看了大量的书籍?,在工作上安排的工作都比较得心应手,时间还算比较充裕,但是懒惰心里没有丝毫退去 ?,这样是不行的,很容易让自己的思维和学习能力下降。 先来谈谈近期阅读的一些文章吧,《高性能 MySQL》这一本书相信是计算机从业人员必读的一本书 ? 了吧,这本书虽然看起来比较厚,但是里面的知识面和富有情趣的讲解还是很不错的,给作者点个赞,现在是第一遍读这本书,本着:“先把书读薄”的原则来读,已经攻读了3章多了,学到了很多实际中业务开发的宝贵经验,但是在实际设计中还是会落入坑中,学到了主键、索引设计技巧,以及具体是怎么去使用的,自己所书写的每一条 SQL 语句是在 MySQL中是怎样运行的,执行效率如何,是否可以优化,以及怎么去衡量自己优化的效率可以达到多少,在这本书中都有所讲解。 第二本就是在极客时间上追更鸟窝大神的《Go语言并发实战》,学习了学多的Go语言并发设计所使用到的并发原语及处理方法,包括 Mutex、RWMutex、WaitGroup、Pool、Once、Context等等操作及内部实现,emmm 目前看老师已经更新完了 自己还没有追更完,惭愧呀,Flag 要树立起来了,哈哈。 第三本是《GC的认识》,在开发过程中,自己在业务代码的设计中无需考虑变量声明后销毁的流程,因为在Go语言中已经实现了对堆栈资源的销毁与清理,但是GC是怎么操作的自己之前都是模糊的了解有三色标记,从根出发标记,很笼统的概念,近期看的这本书,严格意义上一笔记,就讲解了GC的执行过程,怎么去观察GC操作以及怎么去对GC优化等操作,详细的就不展开了,大家感兴趣的可以去看一下。 还有就是一些小的细节点的学习了,还有对自己项目组中的项目及框架了解了一下,这里就不分享啦,实际上是不知道是否存在蟹蜜危机。 毕竟上一篇文章发布的时候就战战兢兢 。 结尾呼应标题,这是一篇水文,主要是想告诉大家 Meng小羽并没有跑路 Debug客栈 还在,另外希望大家有好的分享资料的分享的话可以和我互动或者加入我的群聊,毕竟 1+1 > 2 的,对吧。好了不多说了,跑步去了 ?。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Go 语言开发设计指北
Go 语言是一种强类型、编译型的语言,在开发过程中,代码规范是尤为重要的,一个小小的失误可能会带来严重的事故,拥有一个良好的 Go 语言开发习惯是尤为重要的,遵守开发规范便于维护、便于阅读理解和增加系统的健壮性。 以下是我们项目组开发规范加上自己开发遇到的问题及补充,希望对你有所帮助: 注:我们将以下约束分为三个等级,分别是:【强制】、【推荐】、【参考】。 Go 编码相关 【强制】代码风格规范遵循 go 官方标准:CodeReviewComments,请使用官方 golint lint 进行风格静态分析; 【强制】代码格式规范依照 gofmt,请安装相关 IDE 插件,在保存代码或者编译时,自动将源码通过 gofmt 做格式化处理,保证团队代码格式一致(比如空格,递进等) 【强制】业务处理代码中不能开 goroutine,此举会导致 goroutine 数量不可控,容易引起系统雪崩,如果需要启用 goroutine 做异步处理,请在初始化时启用固定数量 goroutine,通过 channel 和业务处理代码交互,初始化 goroutine 的函数,原则上应该从 main 函数入口处明确的调用: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func crond() { defer func() { if err := recover(); err != nil { // dump stack & log } }() // do something } func main() { // init system go crond() go crond2() // handlers } 【强制】异步开启 goroutine 的地方(如各种 cronder ),需要在最顶层增加 recover(),捕捉 panic,避免个别 cronder 出错导致整体退出: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func globalCrond() { for _ := ticker.C { projectCrond() itemCrond() userCrond() } } func projectCrond() { defer func() { if err := recover(); err != nil { // 打日志,并预警 } } // do } 【强制】当有并发读写 map 的操作,必须加上读写锁 RWMutex,否则 go runtime 会因为并发读写报 panic,或者使用 sync.Map 替代; 【强制】对于提供给外部使用的 package,返回函数里必须带上 err 返回,并且保证在 err == nil 情况下,返回结果不为 nil,比如: 1 2 resp, err := package1.GetUserInfo(xxxxx) // 在err == nil 情况下,resp不能为nil或者空值 【强制】当操作有多个层级的结构体时,基于防御性编程的原则,需要对每个层级做空指针或者空数据判别,特别是在处理复杂的页面结构时,如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 type Section struct { Item *SectionItem Height int64 Width int64 } type SectionItem struct { Tag string Icon string ImageURL string ImageList []string Action *SectionAction } type SectionAction struct { Type string Path string Extra string } func getSectionActionPath(section *Section) (path string, img string, err error) { if section.Item == nil || section.Item.Action == nil { // 要做好足够防御,避免因为空指针导致的panic err = fmt.Errorf("section item is invalid") return } path = section.Item.Action.Path img = section.Item.ImageURL // 对取数组的内容,也一定加上防御性判断 if len(section.Item.ImageList) > 0 { img = section.Item.ImageList[0] } return } 【推荐】生命期在函数内的资源对象,如果函数逻辑较为复杂,建议使用 defer 进行回收: 1 2 3 4 5 6 7 8 func MakeProject() { conn := pool.Get() defer pool.Put(conn) // 业务逻辑 ... return } 对于生命期在函数内的对象,定义在函数内,将使用栈空间,减少 gc 压力: 1 2 3 4 5 6 7 func MakeProject() (project *Project){ project := &Project{} // 使用堆空间 var tempProject Project // 使用栈空间 return } 【强制】不能在循环里加 defer,特别是 defer 执行回收资源操作时。因为 defer 是函数结束时才能执行,并非循环结束时执行,某些情况下会导致资源(如连接资源)被大量占用而程序异常: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 反例: for { row, err := db.Query("SELECT ...") if err != nil { ... } defer row.Close() // 这个操作会导致循环里积攒许多临时资源无法释放 ... } // 正确的处理,可以在循环结束时直接close资源,如果处理逻辑较复杂,可以打包成函数: for { func () { row, err := db.Query("SELECT ...") if err != nil { ... } defer row.Close() ... }() } 【推荐】对于可预见容量的 slice 或者 map,在 make 初始化时,指定cap大小,可以大大降低内存损耗,如: 1 2 3 4 5 6 7 8 9 10 11 headList := make([]home.Sections, 0, len(srcHomeSection)/2) tailList := make([]home.Sections, 0, len(srcHomeSection)/2) dstHomeSection = make([]*home.Sections, 0, len(srcHomeSection)) …. if appendToHead { headList = append(headList, info) } else { tailList = append(tailList, info) } …. dstHomeSection = append(dstHomeSection, headList…) dstHomeSection = append(dstHomeSection, tailList…) 【推荐】逻辑操作中涉及到频繁拼接字符串的代码,请使用 bytes.Buffer 替代。使用 string 进行拼接会导致每次拼接都新增 string 对象,增加 GC 负担: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 正例: var buf bytes.Buffer for _, name := range userList { buf.WriteString(name) buf.WriteString(",") } return buf.String() // 反例: var result string for _, name := range userList { result += name + "," } return result 【强制】对于固定的正则表达式,可以在全局变量初始化时完成预编译,可以有效加快匹配速度,不需要在每次函数请求中预编译: 1 2 3 4 var wordReg = regexp.MustCompile("[\\w]+") func matchWord(word string) bool { return wordReg.MatchString(word) } 【推荐】JSON 解析时,遇到不确定是什么结构的字段,建议使用 json.RawMessage 而不要用 interface,这样可以根据业务场景,做二次 unmarshal 而且性能比 interface 快很多; 【强制】锁使用的粒度需要根据实际情况进行把控,如果变量只读,则无需加锁;读写,则使用读写锁 sync.RWMutex; 【强制】使用随机数时(math/rand),必须要做随机初始化(rand.Seed),否则产生出的随机数是可预期的,在某些场合下会带来安全问题。一般情况下,使用math/rand可以满足业务需求,如果开发的是安全模块,建议使用crypto/rand,安全性更好; 【推荐】对性能要求很高的服务,或者对程序响应时间要求高的服务,应该避免开启大量 gouroutine; 说明:官方虽然号称 goroutine 是廉价的,可以大量开启 goroutine,但是由于 goroutine 的调度并没有实现优先级控制,使得一些关键性的 goroutine(如网络/磁盘IO,控制全局资源的goroutine)没有及时得到调度而拖慢了整体服务的响应时间,因而在系统设计时,如果对性能要求很高,应避免开启大量goroutine。 打点规范 【强制】打点使用.来做分隔符,打点名称需要包含业务名,模块,函数,函数处理分支等,参考如下: 1 2 // 业务名.服务名.模块.功能.方法 service.gateway.module.action.func 【强制】打点使用场景是监控系统的实时状态,不适合存储任何业务数据; 【强制】在打点个数太多时,展示时速度会变慢。建议单个服务打点的key不超过10000个,key中单个维度不同值不超过 1000个(千万不要用 user_id 来打点); 【推荐】如果展示的时候需要拿成百上千个key的数据通过 Graphite 的聚合函数做聚合,最后得到一个或几个 key。这种情况下可以在打点的时候就把这个要聚合的点聚合好,这样展示的时候只要拿这几个 key,对展示速度是巨大的提升。 日志相关 【强制】日志信息需带上下文,其中 logid 必须带上,同一个请求打的日志都需带上 logid,这样可以根据 logid 查找该次请求相关的信息; 【强制】对debug/notice/info 级别的日志输出,必须使用条件输出或者使用占位符方式,避免使用字符拼接方式: 1 log.Debug("get home page failed %s, id %d", err, id) 【强制】如果是解析 json 出错的日志,需要将报错 err 及原内容一并输出,以方便核查原因; 【推荐】对debug/notice/info级别的日志,在打印日志时,默认不显示调用位置(如/path/to/code.go:335) 说明:go 获取调用栈信息是比较耗时的操作(runtime.Caller),对于性能要求很高的服务,特别是大量调用的地方,应尽量避免开发人员在使用该功能时,需知悉这个调用带来的代价。 Redis 相关 【推荐】统一使用:作为前缀后缀分隔符,这里可以根据 Redis 中间件 key proxy 怎么解析分析 Key 进行自定义,便于基础服务的数据可视化及问题排查; 【强制】避免使用 HMGET/HGETALL/HVALS/HKEYS/SMEMBERS 阻塞命令这类命令在 value 较大时,对 Redis 的 CPU/带宽消耗较高,容易导致响应过慢引发系统雪崩; 【强制】不可把 Redis 当成存储,如有统计相关的需求,可以考虑异步同步到数据库进行统计,Redis 应该回归缓存的本质; 【推荐】避免使用大 key,按经验超过 10k 的 value,可以压缩(gzip/snappy等算法)后存入内存,可以减少内存使用,其次降低网络消耗,提高响应速度: 1 2 3 value, err := c.RedisCache.GetGzip(key) …. c.RedisCache.SetExGzip(content, 60) 【推荐】Redis 的分布式锁,可以使用: 1 2 lock: redis.Do("SET", lockKey, randint, "EX", expire, "NX") unlock: redis.GetAndDel(lockKey, randint) // redis暂不支持,可以用lua脚本 【推荐】尽量避免在逻辑循环代码中调用 Redis,会产生流量放大效应,请求量较大时需采用其他方法优化(比如静态配置文件); 【推荐】key 尽量离散读写,通过uid/imei/xid等跟用户/请求相关的后缀分摊到不同分片,避免分片负载不均衡; 【参考】当缓存量大,请求量较高,可能超出 Redis 承受范围时,可充分利用本地缓存(localcache)+redis缓存的组合方案来缓解压力,削减峰值: 使用这个方法需要具备这几个条件: cache 内容与用户无关,key 状态不多,属于公共信息; 该cache内容时效性较高,但是访问量较大,有峰值流量。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 key := "demoid:3344" value := localcacche.Get(key) if value == "" { value = rediscache.Get(key) if value != "" { // 随机缓存 1~5s,各个机器间错开峰值,只要比 redis缓存短即可 localcache.SetEx(key, value, rand.Int63n(5)+1) } } if value == "" { .... // 从其他系统或者数据库获取数据 appoint.GetValue() // 同时设置到redis及localcache中 rediscache.SetEx(key, content, 60) localcache.SetEx(key, content, rand.Int63n(5)+1) } 【参考】对于请求量高,实时性也高的内容,如果纯粹使用缓存,当缓存失效瞬间,会导致大量请求穿透到后端服务,导致后端服务有雪崩危险: 如何兼顾扛峰值,保护后端系统,同时也能保持实时性呢?在这种场景下,可以采用随机更新法更新数据,方法如下: 正常请求从缓存中读取,缓存失效则从后端服务获取; 在请求中根据随机概率 1%(或者根据实际业务场景设置比率)会跳过读取缓存操作,直接从后端服务获取数据,并更新缓存。 这种做法能保证最低时效性,并且当访问量越大,更新概率越高,使得内容实时性也越高。 如果结合上一条 localcache+rediscache 做一二级缓存,则可以达到扛峰值同时保持实时性。 数据库相关 【强制】操作数据库 sql 必须使用 stmt 格式,使用占位符替代参数,禁止拼接 sql; 【强制】SQL语句查询时,不得使用 SELECT * (即形如 SELECT * FROM tbl WHERE),必须明确的给出要查询的列名,避免表新增字段后报错; 【强制】对于线上业务 SQL,需保证命中索引,索引设计基于业务需求及字段区分度,一般可区分状态不高的字段(如 status 等只有几个状态),不建议加到索引中; 【强制】在成熟的语言中,有实体类,数据访问层(repository / dao)和业务逻辑层( service );在我们的规范中存储实体 struct 放置于 entities 包下; 【强制】对于联合索引,需将区分度较大的字段放前面,区分度小放后面,查找时可以减少被检索数据量; 1 2 3 4 5 6 7 8 -- 字段区分度 item_id > project_id alter table xxx add index idx_item_project ( item_id , project_id ) 【强制】所有数据库表必须有主键 id; 【强制】主键索引名为 pk字段名; 唯一索引名为 uk字段名; 普通索引名则为 idx_字段名; 【强制】防止因字段类型不同造成的隐式转换,导致索引失效,造成全表扫描问题; 【强制】业务上有唯一特性的字段,即使是多字段的组合,也必须建成唯一索引; 【强制】一般事务标准操作流程: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 func TestTXExample(t *testing.T) { // 打开事务 tx, err := db.Beginx() if err != nil { log.Fatal("%v", err) return } // defer异常 needRollback := true defer func() { if r := recover(); r != nil { // 处理recover,避免因为panic,资源无法释放 log.Fatal("%v", r) needRollback = true } if needRollback { xlog.Cause("test.example.transaction.rollback").Fatal() tx.Rollback() } }() // 事务的逻辑 err = InsertLog(tx, GenTestData(1)[0]) if err != nil { log.Fatal("%v", err) return } // 提交事务 err = tx.Commit() if err != nil { log.Fatal("%v", err) return } needRollback = false return } 【强制】执行事务操作时,请确保SELECT ... FOR UPDATE条件命中索引,使用行锁,避免一个事务锁全表的情况; 【强制】禁止超过三个表的 join,需要 join 的字段,数据类型必须一致,多表关联查询时,保证被关联的字段有索引; 【强制】数据库 max_open 连接数不可设置过高,会导致代理连接数打满导致不可用状况; 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
阿川
入驻第1年
大年初一,徒步西湖
阿川
入驻第1年
PHP 笔记
阿川
入驻第1年
Ubuntu Server部署日记
Debug
入驻第1年
如何提升自己的技术博文水平
2021 年的一月份马上就过去了,在这一个月中,并没有新鲜出炉的博文,恰恰相反的是我这一个月以来,在思考,自己的博客怎么输出高质量有水平的文章,正如一首优美的旋律,怎么听都可以让人回味无穷,每一遍都有自己的收获。 关于提升自己博客文章的水平,这个月思考了很多的方向,也阅读了不少人对于博客的看法和理解,最终对自己博客总结了几点不足之处: 看到及听到一种新技术或者新的事物,总是以为看了几篇的相关的文章介绍及简短的理解文章,就认为自己了解了某一个事物或者技术,之后输出自己的想法,写出一篇总结的文章,理所应当的写不出有深度有营养的文章; 写一篇博文访问量的关注与文章输出的内容对比更倾向于前者,突然感觉到自己很肤浅,本末倒置,好的文章输出最不用关心的就是访问量的问题; 博文不是自己的 OKR,而是自己技术的自留地,入职大厂之后,感觉自己的技术文章输出应该高出一个层次,一些浅显易懂的或者容易学到的点就不需要总结成文章了,眼高手低,没有认清自己的技术与输出的水平; 自己的技术储备不足(看的书及教程少),博客中一些文章还停留在了解的层次,并没有真正的去 Get 到每个的深层次的水平中去; 懒惰的心理,短视频及游戏 ? VS Debug客栈,大部分时间选择前者,但是前者看了再多,玩的再6,也提升不了自己,只能当算法喂养下的白痴。 毫不夸张的说,若是把博客水平比做成人的一生,我的博客水平依旧处在了18岁之前,没有自己的思考,更多的是在教程、分享的模版下输出,虽有输出,但今天的我发现并没有太多的营养。 正如上方的分割线,我希望自己未来的输出在这里是一个分水岭,接下来自己可以学到更多,变得更强,让自己输出的文章有深度,自己的编程技术更加有水平。 回归主题,“如何提升自己的技术博文水平”,其实映射出是自己的技术水平的不足导致的,那如何提升自己的技术水平,自己总结了一下接下来要做的努力: 阅读技术书籍,技术不能停留在会使用的阶段,要知道自己的每一步操作,在计算机内部发生了什么,原理及使用的技术是什么,现在的我谈不上如何去改进某个技术,但是要会灵活的使用现有技术提升自己的编程水平,提高自己代码的稳定性及让自己的代码写出来如诗一般优雅; 在本职工作中,多看项目组及同事的代码,不仅仅是看代码,思考为什么这样设计,这样设计带来的好处是什么,会有哪些不足,如何改进及优化; 在流行的技术及 Go 语言包中,加入到开源的项目中去,多去看大佬们的代码及设计哲学,了解业界技术的更迭及主流用法,可以的话贡献自己的代码; 多去交流技术,不能认为自己代码很 Low,没有了解很多就对研讨会或者分享会望而却步,恰恰相反,自己在这些分享会中会发现自己的水平处在什么阶段及自己如何去提升自己; 多去整理学过了、了解到的技术的笔记,学会提炼及吸收成自己的知识体系: 这是自己搭建的笔记平台:https://notes.debuginn.com 自己的学习笔记都会同步至此平台,更多的是自己的学习笔记及重要的知识点,这就是我的小本本。 最后,感谢自己导师的教诲与提醒,自己要更加的努力,提升自己,2021,定不负大家的厚望,努力成为项目组中的中坚力量,加油!!!撰写出更多有营养的文章,当然,有些理解片面或者不足的文章还请大家批评指出,谢谢 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
小生
入驻第1年
李会生同志简介
李会生,男,汉族,中共党员,吉林长春人。长期以来他始终以“帮助别人,快乐自己”的精神甘愿付出,助人为乐,默默无闻从小事做起帮助身边的人,多年里,他积极承担社会责任,无私奉献。用自己的实际行动赢得...
Debug
入驻第1年
2020 年度总结
今年,最大的感受就是时间过的太快了,一切都是那么的来不及 …… 2020 年,疫情、毕业、工作,学生时代的 END,社会人时代的 START …… 2021 年,希望一切都在慢慢变好,新的开始、新的未来!!! 每一次总结都是新的开始的起点,那么,接下来就开始吧~ 网站数据 今年是小站运行的第 4 年,感谢大家的支持与访问,这是我分享的天地、同时也是见证我成长的地方,加油~ 一往无前 !!! 最受欢迎的文章 🔥🔥🔥 Restful API 设计指北 🔥🔥 吊打百度,多吉搜索引擎 🔥 程序猿的 Chrome 浏览器插件推荐 搭建流媒体服务器 PingOS 平台搭建 怎么优雅的选择 MySQL 存储引擎 More 页面 2020 看见的我不止一面,这里记录了我的 MORE https://debuginn.com/about 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
阿川
入驻第1年
爱上Go语言:常量与枚举
阿川
入驻第1年
爱上Go语言:变量定义与内建变量类型
Debug
入驻第1年
Go 并发编程之 RWMutex
Mutex 是用来保证只有一个 goroutine 访问共享资源,在大量的并发场景中,特别是读场景中,一个共享资源块只能让 goroutine 串行访问,这就导致了性能的影响,解决方法就是区分读写操作。 这样就可以将串行的读变成并行的读,用来提高读操作的性能。 Go 标准库 RWMutex (读写锁)就是用来解决 readers-writers 问题的。 RWMutex 标准库中的 RWMutex 是一个 reader/writer 互斥锁,RWMutex 在同一时间只能由 n 个 reader 持有,或者只能被单个的 writer 持有。 Lock/Unlock:写操作时调用的方法,若是被 reader 或者 writer 持有, Lock 会一直阻塞,直到可以获取到锁,Ulock 是释放锁; Rlock/RUnlock:读操作时低哦用的方法,如果已经被 writer 持有的话, Rlock 会一直阻塞,直到获取到锁,否者直接返回, RUlock 是 reader 释放锁的方法; RLocker:为读操作返回一个 Locker 接口的对象。 RWMutex 的零值是未加锁的状态,所以在使用 RWMutex 作为变量或者嵌入到 struct 中去,都没有必要进行显式的初始化。 实现原理 针对于 readers-writers 问题是基于对读和写操作的优先级,读写锁的设计分为三类: Read-preferring 读优先设计:并发效果好,但是在大量的并发场景下会导致写饥饿; Write-preferring 写优先设计:针对新请求而言,主要是避免了 writer 饥饿问题,也就是说同一时间有一个 reader 和 writer 等待获取锁,会优先给 writer; 不指定优先级:FIFO,不区分读写优先级,适用于某些特定的场景。 RWMutex 设计是 write-preferring 写优先设计。一个正在阻塞的 Lock 调用会排出新的 reader 请求到锁。 RWMutex 包含一个 Mutex,以及四个辅助字段 writerSem、readerSem、readerCount 和 readerWait: 1 2 3 4 5 6 7 8 9 type RWMutex struct { w Mutex // 互斥锁解决多个 writer 的竞争 writerSem uint32 // writer 信号量 readerSem uint32 // reader 信号量 readerCount int32 // reader 的数量,记录当前 reader 等待的数量 readerWait int32 // writer 等待完成的 reader的 数量 } const rwmutexMaxReaders = 1 << 30 RLock/RUlock 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func (rw *RWMutex) RLock() { // 对 reader 计数 +1,readerCount 会出现负数 // 1、没有 writer 竞争或者持有锁的时候,readerCount 充当计数器存在 // 2、如果有 writer 竞争锁或者持有锁时,那么,readerCount 不仅仅承担着 reader 的计数功能,还能够标识当前是否有 writer 竞争或持有锁 if atomic.AddInt32(&rw.readerCount, 1) < 0 { // rw.readerCount 是负值的时候,意味着此时有 writer 等待请求锁 // 因为writer优先级高,所以把后来的 reader 阻塞休眠 runtime_SemacquireMutex(&rw.readerSem, false, 0) } } func (rw *RWMutex) RUnlock() { // 将 reader 计数 -1 if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 { // 如果为 负数,代表着当前有 writer 在竞争锁,检查是不是所有的 reader 都将锁释放 // 若释放了就让 writer 获取到锁进行写操作 rw.rUnlockSlow(r) // 有等待的writer } } func (rw *RWMutex) rUnlockSlow(r int32) { // rUnlockSlow 将持有锁的 reader 计数 -1 的时候; // 会检查既有的 reader 是不是都已经释放了锁; // 如果都释放了锁,就会唤醒 writer,让 writer 持有锁。 if atomic.AddInt32(&rw.readerWait, -1) == 0 { // 最后一个reader了,writer终于有机会获得锁了 runtime_Semrelease(&rw.writerSem, false, 1) } } Lock / Unlock RWMutex 是一个多 writer 多 reader 的读写锁,所以同时可能有多个 writer 和 reader。那么,为了避免 writer 之间的竞争,RWMutex 就会使用一个 Mutex 来保证 writer 的互斥。 1 2 3 4 5 6 7 8 9 10 func (rw *RWMutex) Lock() { // 首先解决其他 writer 竞争问题 rw.w.Lock() // 反转 readerCount,告诉 reader 有 writer 竞争锁 r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders // 如果当前有 reader 持有锁,那么需要等待 if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 { runtime_SemacquireMutex(&rw.writerSem, false, 0) } } 一旦一个 writer 获得了内部的互斥锁,就会反转 readerCount 字段,把它从原来的正整数 readerCount(>=0) 修改为负数(readerCount-rwmutexMaxReaders),让这个字段保持两个含义(既保存了 reader 的数量,又表示当前有 writer)。 1 2 3 4 5 6 7 8 9 10 11 func (rw *RWMutex) Unlock() { // 告诉 reader 没有活跃的 writer 了 r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders) // 唤醒阻塞的 reader 们 for i := 0; i < int(r); i++ { runtime_Semrelease(&rw.readerSem, false, 0) } // 释放内部的互斥锁 rw.w.Unlock() } 当一个 writer 释放锁的时候,它会再次反转 readerCount 字段。可以肯定的是,因为当前锁由 writer 持有,所以,readerCount 字段是反转过的,并且减去了 rwmutexMaxReaders 这个常数,变成了负数。 所以,这里的反转方法就是给它增加 rwmutexMaxReaders 这个常数值。既然 writer 要释放锁了,那么就需要唤醒之后新来的 reader,不必再阻塞它们了,让它们开开心心地继续执行就好了。 在 RWMutex 的 Unlock 返回之前,需要把内部的互斥锁释放。释放完毕后,其他的 writer 才可以继续竞争这把锁。 RWMutex 常犯的三种错误 不可复制 重入导致死锁 释放未加锁的 RWMutex 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Go 并发编程之 Mutex
我们比较常见的大型项目的设计中都会出现并发访问问题,并发就是为了解决数据的准确性,保证同一个临界区的数据只能被一个线程进行操作,日常中使用到的并发场景也是很多的: 计数器:计数器结果不准确; 秒杀系统:由于同一时间访问量比较大,导致的超卖; 用户账户异常:同一时间支付导致的账户透支; buffer 数据异常:更新 buffer 导致的数据混乱。 上面都是并发带来的数据准确性的问题,决绝方案就是使用互斥锁,也就是今天并发编程中的所要描述的 Mutex 并发原语。 实现机制 互斥锁 Mutex 就是为了避免并发竞争建立的并发控制机制,其中有个“临界区”的概念。 在并发编程过程中,如果程序中一部分资源或者变量会被并发访问或者修改,为了避免并发访问导致数据的不准确,这部分程序需要率先被保护起来,之后操作,操作结束后去除保护,这部分被保护的程序就叫做临界区。 使用互斥锁,限定临界区只能同时由一个线程持有,若是临界区此时被一个线程持有,那么其他线程想进入到这个临界区的时候,就会失败或者等待释放锁,持有此临界区的线程退出,其他线程才有机会获得这个临界区。 Mutex 是 Go 语言中使用最广泛的同步原语,也称为并发原语,解决的是并发读写共享资源,避免出现数据竞争 data race 问题。 基本使用 互斥锁 Mutex 提供了两个方法 Lock 和 Unlock:进入到临界区使用 Lock 方法加锁,退出临界区使用 Unlock 方法释放锁。 1 2 3 4 5 6 7 type Locker interface { Lock() Unlock() } func(m *Mutex)Lock() func(m *Mutex)Unlock() 当一个 goroutine 调用 Lock 方法获取到锁后,其他 goroutine 会阻塞在 Lock 的调用上,直到当前获取到锁的 goroutine 释放锁。 接下来是一个计数器的例子,是由 100 个 goroutine 对计数器进行累加操作,最后输出结果: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package main import ( "fmt" "sync" ) func main() { var mu sync.Mutex countNum := 0 // 确认辅助变量是否都执行完成 var wg sync.WaitGroup // wg 添加数目要和 创建的协程数量保持一致 wg.Add(100) for i := 0; i < 100; i++ { go func() { defer wg.Done() for j := 0; j < 1000; j++ { mu.Lock() countNum++ mu.Unlock() } }() } wg.Wait() fmt.Printf("countNum: %d", countNum) } 实际使用 很多时候 Mutex 并不是单独使用的,而是嵌套在 Struct 中使用,作为结构体的一部分,如果嵌入的 struct 有多个字段,我们一般会把 Mutex 放在要控制的字段上面,然后使用空格把字段分隔开来。 甚至可以把获取锁、释放锁、计数加一的逻辑封装成一个方法。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package main import ( "fmt" "sync" ) // 线程安全的计数器 type Counter struct { CounterType int Name string mu sync.Mutex count uint64 } // 加一方法 func (c *Counter) Incr() { c.mu.Lock() defer c.mu.Unlock() c.count++ } // 取数值方法 线程也需要受保护 func (c *Counter) Count() uint64 { c.mu.Lock() defer c.mu.Unlock() return c.count } func main() { // 定义一个计数器 var counter Counter var wg sync.WaitGroup wg.Add(100) for i := 0; i < 100; i++ { go func() { defer wg.Done() for j := 0; j < 1000; j++ { counter.Incr() } }() } wg.Wait() fmt.Printf("%d\n", counter.Count()) } 思考问题 Q:你已经知道,如果 Mutex 已经被一个 goroutine 获取了锁,其它等待中的 goroutine 们只能一直等待。那么,等这个锁释放后,等待中的 goroutine 中哪一个会优先获取 Mutex 呢? A:FIFO,先来先服务的策略,Go 的 goroutine 调度中,会维护一个保障 goroutine 运行的队列,当获取到锁的 goroutine 执行完临界区的操作的时候,就会释放锁,在队列中排在第一位置的 goroutine 会拿到锁进行临界区的操作。 实现原理 Mutex 的架构演进目前分为四个阶段: 初版 Mutex:使用一个 flag 变量表示锁?是否被持有; 给新人机会:照顾新来的 goroutine 先获取到锁; 多给些机会:照顾新来的和被唤醒的 goroutine 获取到锁; 解决饥饿:存在竞争关系,有饥饿情况发生,需要解决。 初版 Mutex 1 2 3 4 5 // 互斥锁的结构,包含两个字段 type Mutex struct { key int32 // 锁是否被持有的标识 sema int32 // 信号量专用,用以阻塞/唤醒goroutine } Unlock 方法可以被任意的 goroutine 调用释放锁,即使是没持有这个互斥锁的 goroutine,也可以进行这个操作。这是因为,Mutex 本身并没有包含持有这把锁的 goroutine 的信息,所以,Unlock 也不会对此进行检查。Mutex 的这个设计一直保持至今。 在使用 Mutex 的时候,需要严格遵循 “谁申请,谁释放” 原则。 解决饥饿 由于使用了给新人机会,又肯呢个会出现每次都会被新来的 goroutine 获取到锁,导致等待的 goroutine 一直获取不到锁,造成饥饿问题。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 type Mutex struct { state int32 sema uint32 } const ( mutexLocked = 1 << iota // mutex is locked mutexWoken mutexStarving // 从state字段中分出一个饥饿标记 mutexWaiterShift = iota starvationThresholdNs = 1e6 ) func (m *Mutex) Lock() { // Fast path: 幸运之路,一下就获取到了锁 if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { return } // Slow path:缓慢之路,尝试自旋竞争或饥饿状态下饥饿goroutine竞争 m.lockSlow() } func (m *Mutex) lockSlow() { var waitStartTime int64 starving := false // 此goroutine的饥饿标记 awoke := false // 唤醒标记 iter := 0 // 自旋次数 old := m.state // 当前的锁的状态 for { // 锁是非饥饿状态,锁还没被释放,尝试自旋 if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) { if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 && atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) { awoke = true } runtime_doSpin() iter++ old = m.state // 再次获取锁的状态,之后会检查是否锁被释放了 continue } new := old if old&mutexStarving == 0 { new |= mutexLocked // 非饥饿状态,加锁 } if old&(mutexLocked|mutexStarving) != 0 { new += 1 << mutexWaiterShift // waiter数量加1 } if starving && old&mutexLocked != 0 { new |= mutexStarving // 设置饥饿状态 } if awoke { if new&mutexWoken == 0 { throw("sync: inconsistent mutex state") } new &^= mutexWoken // 新状态清除唤醒标记 } // 成功设置新状态 if atomic.CompareAndSwapInt32(&m.state, old, new) { // 原来锁的状态已释放,并且不是饥饿状态,正常请求到了锁,返回 if old&(mutexLocked|mutexStarving) == 0 { break // locked the mutex with CAS } // 处理饥饿状态 // 如果以前就在队列里面,加入到队列头 queueLifo := waitStartTime != 0 if waitStartTime == 0 { waitStartTime = runtime_nanotime() } // 阻塞等待 runtime_SemacquireMutex(&m.sema, queueLifo, 1) // 唤醒之后检查锁是否应该处于饥饿状态 starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs old = m.state // 如果锁已经处于饥饿状态,直接抢到锁,返回 if old&mutexStarving != 0 { if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 { throw("sync: inconsistent mutex state") } // 有点绕,加锁并且将waiter数减1 delta := int32(mutexLocked - 1<<mutexWaiterShift) if !starving || old>>mutexWaiterShift == 1 { delta -= mutexStarving // 最后一个waiter或者已经不饥饿了,清除饥饿标记 } atomic.AddInt32(&m.state, delta) break } awoke = true iter = 0 } else { old = m.state } } } func (m *Mutex) Unlock() { // Fast path: drop lock bit. new := atomic.AddInt32(&m.state, -mutexLocked) if new != 0 { m.unlockSlow(new) } } func (m *Mutex) unlockSlow(new int32) { if (new+mutexLocked)&mutexLocked == 0 { throw("sync: unlock of unlocked mutex") } if new&mutexStarving == 0 { old := new for { if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 { return } new = (old - 1<<mutexWaiterShift) | mutexWoken if atomic.CompareAndSwapInt32(&m.state, old, new) { runtime_Semrelease(&m.sema, false, 1) return } old = m.state } } else { runtime_Semrelease(&m.sema, true, 1) } } 思考问题 Q: 目前 Mutex 的 state 字段有几个意义,这几个意义分别是由哪些字段表示的? A:state 字段一共有四个子字段,前三个 bit 是 mutexLocked(锁标记)、mutexWoken(唤醒标记)、mutexStarving(饥饿标记),剩余 bit 标示 mutexWaiter(等待数量)。 Q: 等待一个 Mutex 的 goroutine 数最大是多少?是否能满足现实的需求? A:目前的设计来看取决于 state 的类型,目前是 int32,由于3个字节代表了状态,有 536870911,一个 goroutine 初始化的为 2kb,约等于 1024 GB 即 1TB,目前内存体量那么大的服务还是少有的,可以满足现在的使用。 常见错误的四种场景 Lock/Unlock 不是成对出现; Copy 已使用的 Mutex; 重入; 死锁。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
优雅的使用 Brew 切换 Go 版本
Brew 是 Mac 上包管理工具,和 Linux 上的 apt 、yum、rpm 一样,可以提供非图形化软件的安装,昨天在打造宇宙最强 IDE 的时候,使用brew工具更新了一下软件包,是我的 Go 版本升级到了最新版本,同时之前配置的多版本 Go 抹掉了,现在写一下记录,你如果需要的话可以使用一下。 之前写过一个使用 GVM 版本管理工具的文章,这个是第三方工具管理的,都比较好用,你可以根据自己的需求安装。 方案一 brew switch 1 brew install 1 brew install go 2 brew switch 1 2 ~ brew info go go: stable 1.15.3 (bottled), HEAD 使用 brew info go 命令你可以看到当前目前的 go 可以切换的版本,接下来就安装多个版本并且切换到对应的版本。 1 2 3 4 // 安装指定 go 版本 brew install go@<version> // forexample brew install go@1.12.17 安装好了 之后使用 brew info go 查看是否可以切换了。 1 brew switch go 1.12.17 单纯的使用上面的命令你会发现,go 不能使用了,并且会出现下面的提示: 1 2 3 4 ~ brew switch go 1.12.17 Cleaning /usr/local/Cellar/go/1.12.17 Cleaning /usr/local/Cellar/go/1.15.3 0 links created for /usr/local/Cellar/go/1.12.17 创建了零个连接,就代表着没有成功的将 go 版本指向你所需要的版本下,问题是什么呢?现将 go 版本切回 go 1.15.3,你会发现可以切换并正常使用: 1 2 3 4 5 6 7 ~ brew switch go 1.15.3 Cleaning /usr/local/Cellar/go/1.12.17 Cleaning /usr/local/Cellar/go/1.15.3 3 links created for /usr/local/Cellar/go/1.15.3 ~ go version go version go1.15.3 darwin/amd64 定位这个原因你需要看看为什么没有未给 go 1.12.17 版本创建软连接,首先要找一下 go 默认安装的位置,使用 go env 查看安装目录: 1 /usr/local/Cellar/go/ 使用 brew 工具在 MacOS Catalina 系统安装的位置。 进入到目录之后在 go 目录下只有刚才默认安装的 1.15.3 版本,并没有自己安装的版本,退出父级目录看到了下载的 go@1.12.17 版本,由于软连接连接的是上方的路径,需要将这个目录移动至 go 目录下: 1 2 3 4 5 6 7 8 // 打开默认目录 cd /usr/local/Cellar/go/ // 退出目录 cd .. // 移动目录至 go 目录下 mv go@1.12.17 go/ // 重要!!! 重命名文件夹 mv go@1.12.17 1.12.17 接下来使用切换命令 brew switch go <version> 就可以切换环境了。 方案二 brew link 使用 Homebrew 3.2.9 验证。 1、安装新的版本: 1 brew install go@1.16 // 安装 go 1.16 版本 2、移除原有的 go 版本软链 1 brew unlink go 3、指定新的版本软链 1 brew link go@1.16 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
阿川
入驻第1年
Manjaro Linux 双显卡切换解决方案
小十
入驻第1年
30岁了
等待30岁的这个过程很漫长,在迈向30岁这道门槛之前,无论是周围环境还是自身,都遇到了很多问题。 上篇博文是关于一些电脑上的小技巧,而上上篇博文在发表的时候,还没有疫情这回事情。 怎么也没想到,会有这么大影响的一件事,也没有想到,会在发生这件事之后,10月份还有幸陪父亲去他的部队看看,去之前没有去过的广东省走一遭,也见了一下真正的海景房,感受一下他人靠自己拼搏出来的成果,而对于我自己,这一年平平无奇,本以为的变化也没有发生,网站服务器经常卡顿得要死,之前以为自己这个年纪应该会很轻松承担一笔更贵的服务器费用,而现实却是连运维都没有怎么进行。 今年在疫情期间又重新学了C4D,又因为工作,荒废了学C4D的进展。自己在工作上感觉比以前更辛苦,也更没有收获,整个今年就笼罩在一个奇怪的氛围里面。房子已经交付,但没有心思装修,有了自己的一辆车,也没有觉得生活变化了什么。已经很久没有去买到自己喜欢的物件,也该为自己考虑一下,让自己开心一下。 也想过30岁的第一天会是什么样,可到了跟前其实还是差不了太多,过惯了这种平常没有波折的日子,也没有一些让自己感到开心的事情。开心是可以制造出来的,我应该会有往下努力的方向了吧。

© 2026 好站网HaoZhan.wang 1.5 版权所有

苏ICP备19065220号-4    萌ICP备20269980号    茶ICP备2026050346号
本站数据    2026年报    版本历史    关于本站