├── .idea ├── .gitignore ├── Go.iml ├── inspectionProfiles │ └── Project_Default.xml ├── jsLibraryMappings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── Go Slice 扩容的这些坑你踩过吗?.md ├── README.md ├── 「微服务」这10道Consul面试题值得一看.md ├── 初学后端,如何做好表结构设计?.md ├── 精选8道ES高频面试题和答案,后悔没早点看。 └── 精选Golang高频面试题和答案汇总.md /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 基于编辑器的 HTTP 客户端请求 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/Go.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Go Slice 扩容的这些坑你踩过吗?.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 之前对Go语言for循环做了一次踩坑经验分享[《Go for range 一不小心就掉坑里了》](https://mp.weixin.qq.com/s?__biz=MzIyNjM0MzQyNg==&mid=2247486979&idx=1&sn=b6900099b78b0b638241df62d1f1bec7&chksm=e870a16edf072878538c153f2a08e5194d152d6a4733e0affbd9a5844b08eba0cfe2eedb0b66&token=1569670706&lang=zh_CN#rd),大家直呼有用。 4 | 5 | 今天对切片Slice的append操作也做一次踩坑经验分享,希望对朋友们有所帮助,有用请三连支持。 6 | 7 | ## 知识重温 8 | 9 | **切片底层结构定义**:包含`指向底层数组的指针`、`长度`和`容量` 10 | 11 | ``` 12 | type slice struct { 13 | array unsafe.Pointer 14 | len int 15 | cap int 16 | } 17 | ``` 18 | 19 | **append操作**:可以是1个、多个、甚至整个切片(记得后面加...);添加元素时当容量不足,则会自动触发切片扩容机制,产生切片副本,同时指向底层数组的指针发生变化 20 | 21 | ``` 22 | var nums []int 23 | nums = append(nums, 1) 24 | nums = append(nums, 2, 3, 4) 25 | nums2 := []int{5, 6, 7} 26 | nums = append(nums, nums2...) 27 | fmt.Println(nums) //[1 2 3 4 5 6 7] 28 | ``` 29 | 30 | ## 案例1:传值+未扩容 31 | 32 | 先来看看下面会输出什么结果? 33 | 34 | ``` 35 | func main() { 36 | s1 := make([]int, 0, 5) 37 | fmt.Println("s1切片: ", s1) 38 | appendFunc(s1) 39 | fmt.Println("s1切片: ", s1) 40 | fmt.Println("s1切片表达式: ", s1[:5]) 41 | } 42 | 43 | func appendFunc(s2 []int) { 44 | s2 = append(s2, 1, 2, 3) 45 | fmt.Println("s2切片: ", s2) 46 | } 47 | ``` 48 | 49 | 输出结果: 50 | 51 | ``` 52 | s1切片: [] 53 | s2切片: [1 2 3] 54 | s1切片: [] 55 | s1切片表达式: [1 2 3 0 0] 56 | ``` 57 | 58 | 看到这个结果,大家就会有疑问了,**明明切片是引用类型,为什么s2 append了新元素后,s2是有值了但s1却还是空的,并且对s1用切片表达式却能获取到值呢?** 59 | 60 | 原因分析前,我们先来看看s1和s2到底是不是同一个切片,打印地址验证下 61 | 62 | ``` 63 | func main() { 64 | s1 := make([]int, 0, 5) 65 | fmt.Printf("s1切片地址: %p\n", s1) 66 | appendFunc(s1) 67 | //... 68 | } 69 | 70 | func appendFunc(s2 []int) { 71 | s2 = append(s2, 1, 2, 3) 72 | fmt.Printf("s2切片地址: %p\n", s2) 73 | //... 74 | } 75 | 76 | 输出结果: 77 | s1切片地址: 0xc000018150 78 | s2切片地址: 0xc000018150 79 | ``` 80 | 81 | 看到这就得**傻眼了,两个切片的地址都是同一个,s2修改后s1也应该同步修改,应该都有值啊** 82 | 83 | 我们还得继续再深究一下,`fmt包%p`打印的这个地址,到底是谁的地址 84 | 85 | ``` 86 | //fmt/print.go 87 | func (p *pp) fmtPointer(value reflect.Value, verb rune) { 88 | var u uintptr 89 | switch value.Kind() { 90 | case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: 91 | u = value.Pointer() 92 | default: 93 | p.badVerb(verb) 94 | return 95 | } 96 | //... 97 | } 98 | 99 | //reflect/value.go 100 | func (v Value) Pointer() uintptr { 101 | k := v.kind() 102 | switch k { 103 | //... 104 | 105 | case Slice: 106 | return (*SliceHeader)(v.ptr).Data 107 | } 108 | panic(&ValueError{"reflect.Value.Pointer", v.kind()}) 109 | } 110 | ``` 111 | 112 | 通过分析fmt包的源码,不难发现,**打印的地址,其实是切片里指向底层数组的指针存储的地址,并不是两个切片本身的地址**。同时也说明这两切片是指向同一个底层数组。 113 | 114 | **原因正式分析**: 115 | 116 | 1. **传值操作**,s1和s2是两个不同的切片变量,但是指向底层数组的指针是同一个; 117 | 1. **长度和容量的变化**:s1 Len=0和Cap=5,后来未发生过变化;s2一开始被赋值时 Len=0和Cap=5,在append操作后,Len=3和Cap=5,同时底层数组值从`[0,0,0,0,0]`被修改成了`[1,2,3,0,0]`; 118 | 1. **输出结果**,s1由于Len=0所以输出空[],而s1用切片表达式,是基于底层数组`[1,2,3,0,0]`进行切片,所以输出结果为`[1,2,3,0,0]`; 119 | 120 | ## 案例2:传值+扩容 121 | 122 | 将案例1,append的元素个数超过切片容量,触发自动扩容,输出的结果又会是怎样的呢? 123 | 124 | ``` 125 | func main() { 126 | s1 := make([]int, 0, 5) 127 | fmt.Println("s1切片: ", s1) 128 | appendFunc(s1) 129 | fmt.Println("s1切片: ", s1) 130 | fmt.Println("s1切片表达式: ", s1[:5]) 131 | } 132 | 133 | func appendFunc(s2 []int) { 134 | s2 = append(s2, 1, 2, 3, 4, 5, 6) 135 | fmt.Println("s2切片: ", s2) 136 | } 137 | ``` 138 | 139 | 输出结果: 140 | 141 | ``` 142 | s1切片: [] 143 | s2切片: [1 2 3 4 5 6] 144 | s1切片: [] 145 | s1切片表达式: [0 0 0 0 0] 146 | ``` 147 | 148 | **原因分析**: 149 | 150 | 1. **发生扩容后**,s2指向的底层数组会产生副本,导致s1和s2不再指向同一个底层数组; 151 | 1. **长度和容量的变化**:s2 append后Len=6、Cap=10和底层数组值为`[1,2,3,4,5,6,0,0,0,0]`;s2的操作完全不影响s1的数据,s1仍然是Len=0、Cap=5和底层数组值为`[0,0,0,0,0]`; 152 | 1. **输出结果**,s2由于Len=6所以输出`[1,2,3,4,5,6]`,s1由于Len=0所以输出空[],而s1用切片表达式,是基于底层数组`[0,0,0,0,0]`进行切片,所以输出结果为`[0,0,0,0,0]`; 153 | 154 | ## 案例3:传址+不关心扩容 155 | 156 | 上面两个传值操作的例子,不管扩容与否,都不会影响原切片s1的长度和容量。如果我们**期望修改s2的同时也修改原切片s1,则需要用到切片指针,基于地址传递进行操作**。 157 | 158 | ``` 159 | func main() { 160 | s1 := make([]int, 0, 5) 161 | fmt.Println("s1切片: ", s1) 162 | fmt.Printf("s1切片地址: %p len:%d cap:%d\n", &s1, len(s1), cap(s1)) 163 | appendFunc(&s1) 164 | fmt.Println("s1切片: ", s1) 165 | fmt.Println("s1切片表达式: ", s1[:5]) 166 | } 167 | 168 | func appendFunc(s2 *[]int) { 169 | fmt.Printf("s2切片地址: %p len:%d cap:%d\n", s2, len(*s2), cap(*s2)) 170 | //*s2 = append(*s2, 1, 2, 3) 171 | *s2 = append(*s2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 172 | fmt.Printf("append后s2切片地址: %p len:%d cap:%d\n", s2, len(*s2), cap(*s2)) 173 | fmt.Println("s2切片: ", *s2) 174 | } 175 | ``` 176 | 177 | 输出结果: 178 | 179 | ``` 180 | s1切片: [] 181 | s1切片地址: 0xc00000c030 len:0 cap:5 182 | s2切片地址: 0xc00000c030 len:0 cap:5 183 | append后s2切片地址: 0xc00000c030 len:10 cap:10 184 | s2切片: [1 2 3 4 5 6 7 8 9 10] 185 | s1切片: [1 2 3 4 5 6 7 8 9 10] 186 | s1切片表达式: [1 2 3 4 5] 187 | ``` 188 | 189 | 万变不离其宗,**传址操作,始终操作的是同一个切片变量**,append操作后,长度和容量都会同时发生变化,以及如果触发扩容,那么指向底层数组的指针,也都会同时发生变化。 190 | 191 | ## 总结 192 | 193 | **切片传值操作**,append未触发扩容,会同时修改底层数组的值,但不会影响原切片的长度和容量;当触发扩容,那么会产生副本,后面的修改则会和原底层数组剥离开,互不影响。 194 | 195 | 如果期望在修改切片后,对原切片也发生修改,则可以使用**传址操作**,始终基于同一个切片变量进行操作。 196 | 197 | ## 联系我 198 | 199 | 我的微信:wangzhongyang1993 200 | 201 | 视频号:王中阳Go 202 | 203 | 公众号:程序员升职加薪之旅 204 | 205 | 欢迎关注 ❤️ 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go语言学习路线图 2 | 3 | 《Go语言学习路线图》在各大技术社区平台已被累积收藏超过1万次。让你少踩坑,高效学,Let's Go! 4 | 5 | # 面试题系列 6 | 7 | [这10道Consul面试题值得一看](https://mp.weixin.qq.com/s?__biz=MzIyNjM0MzQyNg==&mid=2247488593&idx=1&sn=227d41f63b092967a4f199018ea1c90c&chksm=e870bb3cdf07322abc1fd0244843f224327e9ed308fede191d5108f352082242f9ba8ea7a93b&token=1219609477&lang=zh_CN#rd) 8 | 9 | # 前言 10 | 11 | 为了方便大家能够按顺序系统的进行学习,我把之前整理的[Go语言学习专栏](https://juejin.cn/column/7064777730532835336)文章进行了梳理。 12 | 13 | 方便大家更系统的学习Go语言,**欢迎大家在评论区推荐、自荐优秀的Go文章**。 14 | 15 | 让我们手牵着手,一起走,少走弯路,又快又好的成为**Gopher**,Let's Go。 16 | 17 | **建议大家先收藏,结合自己的情况合理安排学习计划,坚持学习打卡,可以在评论区留言。** 18 | 19 | # 概览 20 | 21 | 首先分享了我的**学习经验**:讲一讲Go语言为什么值得学习?以及我是如何高效学习Go语言的。 22 | 23 | 然后就是刻意练习了,需要大家和我一样,坚持每天手撸代码,多敲多想: 24 | 25 | 通过对**Go基础篇**的学习,可以从Go小白升级成为能用Go撸代码的gopher。 26 | 27 | 通过对**Go进阶篇**的学习,可以从Go初级程序员升级为Go中级工程师。 28 | 29 | 通过**Go PHP JAVA类比篇**的学习,可以更好的理解Go的优势,更好的理解Go的设计思想。 30 | 31 | **框架篇** 不仅对比了目前主流的Go框架,还重点讲解了GoFrame框架相关的知识点。 32 | 33 | **GoFrame**是类似PHP-Laravel, Java-SpringBoot的Go企业级开发框架,非常值得大家学习。 34 | 35 | 最后,会通过几篇**应用实践**的文章收尾,带大家体验一下用Go开发企业项目的快乐。 36 | 37 | > 说明:下面的文章没有标注作者信息的是我的文章;标明作者的都是优秀创作者投稿,经过筛选的优质文章。 38 | 39 | # Go签约文章 40 | 41 | 很荣幸被掘金签约,近期我将持续更新Go进阶实战文章,欢迎关注:[# 签约专栏:Go语言进阶实战](https://juejin.cn/column/7140137480749547528) 42 | 43 | # 为什么学Go? 44 | 45 | [# Go语言为什么值得学习?](https://juejin.cn/post/7064778754979004447) 46 | 47 | [# Go 在云原生时代的优势](https://juejin.cn/post/7110226868418117639) 48 | 作者:[# 宇宙之一粟](https://juejin.cn/user/3526889034751639) 49 | 50 | # 学习经验分享 51 | 52 | [# 回顾一下我的Go学习之旅 | Go 主题月](https://juejin.cn/post/6949109361331568670) 53 | 54 | [# [译]如何真正学习Go 语言](https://juejin.cn/post/7061777327641853960) 55 | 作者:[# 宇宙之一粟](https://juejin.cn/user/3526889034751639) 56 | 57 | [# 写Go最近踩的坑 | 日志、内聚和复用、gjson、调整心态](https://juejin.cn/post/7103887534299545613) 58 | 59 | # 基础篇 60 | 61 | ## 数据类型 62 | 63 | [#【Go基础】编译、变量、常量、基本数据类型、字符串](https://juejin.cn/post/6942795881049489438/) 64 | 65 | [# Go const和iota 使用实战](https://juejin.cn/post/7068456474133364743/) 66 | 67 | [# Go基础数据类型使用实战:int float bool](https://juejin.cn/post/7068457967133130782) 68 | 69 | ## 切片 70 | 71 | [# Go slice切片详解和实战](https://juejin.cn/post/7071960543283642404) 72 | 73 | [# Go slice切片详解和实战(2) make append copy](https://juejin.cn/post/7068573594879524894) 74 | 75 | [# 深入理解 slice 非常硬核!](https://juejin.cn/post/7122495050067476510) 76 | 作者:[# 二牛QAQ](https://juejin.cn/user/2344623087289965) 77 | 78 | ## 数组 79 | 80 | [# Go 数组详解和实战](https://juejin.cn/post/7072165701036802055) 81 | 82 | [# Go map详解和实战](https://juejin.cn/post/7072905649708859429) 83 | 84 | ## rune 85 | 86 | [# Go rune详解和实战](https://juejin.cn/post/7068726981159829541) 87 | 88 | ## 指针 89 | 90 | [# Go pointer & switch fallthrough 详解和实战](https://juejin.cn/post/7072502044170387492) 91 | 92 | ## 流程控制 93 | 94 | [# go if判断和for循环实战 goto使用的那些坑](https://juejin.cn/post/7069549500649439245) 95 | 96 | ## 函数 97 | 98 | [# Go 函数详解 func 匿名函数 闭包](https://juejin.cn/post/7073279289101123614) 99 | 100 | ## ORM 101 | 102 | [# Go 语言中操作 MySQL 数据库](https://juejin.cn/post/7103531271803912199) 103 | 作者:[# 宇宙之一粟](https://juejin.cn/user/3526889034751639) 104 | 105 | [# golang 基于 mysql 实现分布式读写锁](https://juejin.cn/post/7147214210324234271) 106 | 作者:[# 二牛QAQ](https://juejin.cn/user/2344623087289965) 107 | 108 | ## 部署 109 | 110 | [# 如何优雅的通过Shell脚本一键部署GO项目到服务器](https://juejin.cn/post/6943843305750970399) 111 | 112 | ## 扩展包 113 | 114 | [# Go时间包jsontime深入浅出 如何优雅的对时间进行格式化 |Go 主题月](https://juejin.cn/post/6943897665960689678) 115 | 116 | [# Go语言json包的使用技巧](https://juejin.cn/post/6945023713930641445) 117 | 118 | [# Go 入门很简单:如何在 Go 中使用日志包](https://juejin.cn/post/7088342353638850567) 119 | 作者:[# 宇宙之一粟](https://juejin.cn/user/3526889034751639) 120 | 121 | ## 重要概念 122 | 123 | [# Go开发web必懂的概念和底层原理,通过对比的方式让大家更好的理解 | Go主题月](https://juejin.cn/post/6950954283068031012) 124 | 125 | # 进阶篇 126 | 127 | ## 协程 128 | 129 | [# 什么时候用Goroutine?什么时候用Channel?](https://juejin.cn/post/6943952470993272845) 130 | 131 | [# Goroutine就是协程:进程 线程 协程 各自的概念以及三者的对比分析](https://juejin.cn/post/6950952506176471071) 132 | 133 | ## RPC 134 | 135 | [# Go RPC入门指南1:RPC的使用边界在哪里?如何实现跨语言调用?](https://juejin.cn/post/6946452659159171102) 136 | 137 | ## 反射 138 | 139 | [# Golang的反射reflect深入理解和示例](https://juejin.cn/post/6844903559335526407) 140 | 作者:[吴德宝AllenWu](https://juejin.cn/user/1187128287436808) 141 | 142 | [# Go语言中的反射](https://juejin.cn/post/7097534989029343246) 143 | 作者:[任沫](https://juejin.cn/user/184373685261719) 144 | 145 | ## interface 146 | 147 | [# Golang interface接口深入理解](https://juejin.cn/post/6844903555141222407) 148 | 作者:[吴德宝AllenWu](https://juejin.cn/user/1187128287436808) 149 | 150 | ## 错误处理 151 | 152 | [# Go函数并发情况的错误处理](https://juejin.cn/post/7114970981872959525) 153 | 作者:[Masters](https://juejin.cn/user/1239904847411406 "https://juejin.cn/user/1239904847411406") 154 | 155 | ## 并发安全 156 | 157 | [# Go源码解读-sync.Map的实现](https://juejin.cn/post/7068192854761275429) 158 | 作者:[Masters](https://juejin.cn/user/1239904847411406 "https://juejin.cn/user/1239904847411406") 159 | 160 | ## 部署 161 | 162 | [# Go打包 部署 优雅的把Go项目部署到Linux服务器](https://juejin.cn/post/6954309251892248612) 163 | 164 | ## 规范&技巧 165 | 166 | [# Go语言中比较优雅的写法 | 硬核!](https://juejin.cn/post/7082536852590166029) 167 | 168 | [# 爆肝分享两千字Go编程规范](https://juejin.cn/post/7086094606856618014) 169 | 170 | [# Go开发技巧和踩坑分享 | 代码结构 调试技巧 配置文件 元数据](https://juejin.cn/post/7102605823003590692) 171 | 172 | # Go对比PHP/JAVA/C 173 | 174 | [# Java VS Go 还在纠结怎么选吗,(资深后端4000字带你深度对比)](https://juejin.cn/post/7118866795418615822) 175 | 作者:[TodoCoder](https://juejin.cn/user/2472125987040093) 176 | 177 | [# 为什么我觉得GoFrame的garray比PHP的array还好用?](https://juejin.cn/post/7105012753210802213) 178 | 179 | [# GoFrame gset使用入门 | 对比PHP、Java、Redis](https://juejin.cn/post/7105231214390280200) 180 | 181 | [# 如何在 Go 代码中运行 C 语言代码](https://juejin.cn/post/7077843585088897037) 182 | 作者:[# 宇宙之一粟](https://juejin.cn/user/3526889034751639) 183 | 184 | # 好用的扩展包 185 | 186 | [# GO语言框架中如何快速集成日志模块](https://juejin.cn/post/7119390985863299085) 187 | 作者:[Masters](https://juejin.cn/user/1239904847411406) 188 | 189 | [# Go Web 编程入门:Go pongo2 模板引擎](https://juejin.cn/post/7102001354654089223) 190 | 作者:[# 宇宙之一粟](https://juejin.cn/user/3526889034751639) 191 | 192 | [# 使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API](https://juejin.cn/post/7120256019225116679) 193 | 作者:[# 宇宙之一粟](https://juejin.cn/user/3526889034751639) 194 | 195 | # 设计模式 196 | 197 | [# golang 设计模式-单例模式](https://juejin.cn/post/7124720007447052302) 198 | 作者:[# 二牛QAQ](https://juejin.cn/user/2344623087289965) 199 | 200 | # 框架篇 201 | 202 | ## 学哪个框架? 203 | 204 | [# Go主流框架对比:Gin Echo Beego Iris](https://juejin.cn/post/7067347764899741709) 205 | 206 | [# 非常适合PHP/JAVA同学使用的GO框架:GoFrame](https://juejin.cn/post/7075098594151235597) 207 | 208 | [# 12个值得一看的Go开源项目/框架](https://juejin.cn/post/7119348879820554247) 209 | 作者:[ReganYue](https://juejin.cn/user/3008695929418318) 210 | 211 | ## Gin框架&中间件 212 | 213 | [# Go gin框架封装中间件之1:用户角色权限管理中间件](https://juejin.cn/post/6943147832937447431) 214 | 215 | [# Go gin框架封装中间件之2:操作日志中间件](https://juejin.cn/post/6943503384729583652) 216 | 217 | ## GORM 218 | 219 | [# Go GORM是时候升级新版本了 2.0新特性介绍(1)](https://juejin.cn/post/6945404499850854408) 220 | 221 | [# Go GORM是时候升级新版本了 2.0新特性介绍(2)| Go主题月](https://juejin.cn/post/6946012224573931528) 222 | 223 | ## Echo 224 | 225 | [# 回声嘹亮 之 Go 的 Echo 框架指南 —— 上手初体验](https://juejin.cn/post/7068555737756073997) 226 | 作者:[# 宇宙之一粟](https://juejin.cn/user/3526889034751639) 227 | 228 | ## Beego 229 | 230 | [# go-web框架-beego的使用](https://juejin.cn/post/7121536082151211022) 231 | 作者:[# jy白了个白](https://juejin.cn/user/3421335915080093) 232 | 233 | ## GoFrame 234 | 235 | ### 数据结构 236 | 237 | [# 为什么我觉得GoFrame的garray比PHP的array还好用?](https://juejin.cn/post/7105012753210802213) 238 | 239 | [# GoFrame garray使用实践](https://juejin.cn/post/7090901734247104548) 240 | 241 | [# GoFrame gset使用入门 | 对比PHP、Java、Redis](https://juejin.cn/post/7105231214390280200) 242 | 243 | [# GoFrame gset使用实践 | 交差并补集](https://juejin.cn/post/7105572330612457486) 244 | 245 | [# GoFrame gset使用技巧总结 | 出栈、子集判断、序列化、遍历修改](https://juejin.cn/post/7106013158279479327) 246 | 247 | [# GoFrame glist 基础使用和自定义遍历](https://juejin.cn/post/7101515355062796296) 248 | 249 | [# GoFrame gmap详解 hashmap、listmap、treemap使用技巧](https://juejin.cn/post/7101797623484383246) 250 | 251 | [# GoFrame gtree 使用入门 | 养成读源码的好习惯](https://juejin.cn/post/7106458930057855013) 252 | 253 | ### 类型转换 254 | 255 | [# GoFrame代码优化:使用gconv类型转换 避免重复定义map](https://juejin.cn/post/7081078067682082823) 256 | 257 | ### 通用变量 258 | 259 | [# GoFrame 通用类型变量gvar | 对比 interface{}](https://juejin.cn/post/7106712908326764552) 260 | 261 | ### 数据校验 262 | 263 | [# GoFrame数据校验之校验对象 | 校验结构体](https://juejin.cn/post/7110222819631464485) 264 | 265 | [# GoFrame数据校验之校验结果 | Error接口对象](https://juejin.cn/post/7110952333193773064) 266 | 267 | [# GoFrame如何实现顺序性校验](https://juejin.cn/post/7113360526410776583) 268 | 269 | ### 错误处理 270 | 271 | [# GoFrame错误处理的常用方法&错误码的使用](https://juejin.cn/post/7112428421392629773) 272 | 273 | ### 上下文 274 | 275 | [# GoFrame 如何优雅的共享变量 | Context的使用](https://juejin.cn/post/7113118741776793636) 276 | 277 | [# Go 并发编程基础:什么是上下文](https://juejin.cn/post/7123200814402764831) 278 | 作者:[# 宇宙之一粟](https://juejin.cn/user/3526889034751639) 279 | 280 | ### ORM 281 | 282 | [# GoFrame ORM 使用实践分享](https://juejin.cn/post/7082278651681013773) 283 | 284 | [# GoFrame ORM原生方法 开箱体验 (上)](https://juejin.cn/post/7089980894525521957) 285 | 286 | [# GoFrame ORM原生方法 开箱体验 (下)](https://juejin.cn/post/7090358951501365278) 287 | 288 | [# GoFrame必知必会之Scan:类型转换](https://juejin.cn/post/7084569454956249101) 289 | 290 | ### 缓存管理 291 | 292 | [# GoFrame 如何优雅的缓存查询结果](https://juejin.cn/post/7109465768445607967) 293 | 294 | [# GoFrame gcache使用实践 | 缓存控制 淘汰策略](https://juejin.cn/post/7107986667293638663) 295 | 296 | [# GoFrame gredis 配置管理 | 配置文件、配置方法的对比](https://juejin.cn/post/7108272085452980261) 297 | 298 | [# GoFrame gredis 硬核解析 | DoVar、Conn连接对象、自动序列化](https://juejin.cn/post/7108698563328081928) 299 | 300 | [# GoFrame gredis 如何优雅的取值和类型转换](https://juejin.cn/post/7109092852101021704) 301 | 302 | ### 协程管理 303 | 304 | [# GoFrame gpool 对象复用池 | 对比sync.pool](https://juejin.cn/post/7102979667925139463) 305 | 306 | [# goFrame的gqueue详解 | 对比channel](https://juejin.cn/post/7103502362429358116) 307 | 308 | [# grpool goroutine池详解 | 协程管理](https://juejin.cn/post/7104661248213516319) 309 | 310 | ### 避坑指南 311 | 312 | [# GoFrame避坑指南和实践干货](https://juejin.cn/post/7081959981456556068) 313 | 314 | [# GoFrame避坑指南和实践干货(2)](https://juejin.cn/post/7085730973836378126) 315 | 316 | ### 性能测试 317 | 318 | [# GoFrame grpool性能测试 | 对比原生goroutine](https://juejin.cn/post/7110510747498594341) 319 | 320 | ### 调试&单元测试 321 | 322 | [# Go本地测试 如何解耦 任务拆解&沟通](https://juejin.cn/post/7111325551549218823) 323 | 324 | [# Go Web 编程入门: 一探优秀测试库 GoConvey](https://juejin.cn/post/7115009937847091214) 325 | 作者:[# 宇宙之一粟](https://juejin.cn/user/3526889034751639) 326 | 327 | # 应用实践 328 | 329 | [# gtoken替换jwt实现sso登录 | 带你读源码](https://juejin.cn/post/7099449898529095717) 330 | 331 | [# gtoken替换jwt实现sso登录 | 排雷避坑](https://juejin.cn/post/7102389025050361864) 332 | 333 | [# Go对接三方API实践](https://juejin.cn/post/7083479769878102030) 334 | 335 | [# Go一分钟对接ElasticSearch实践](https://juejin.cn/post/7084235852921962503) 336 | 337 | [# 瞄一眼clickhouse(附 go demo)](https://juejin.cn/post/7068192828790145060) 338 | 作者:[Masters](https://juejin.cn/user/1239904847411406 "https://juejin.cn/user/1239904847411406") 339 | 340 | # Git 341 | 342 | [# Git使用实战:多人协同开发,紧急修复线上bug的Git操作指南。](https://juejin.cn/post/7018771333173477383) 343 | 344 | [# Git 重命名远程分支 | 操作不规范,亲人两行泪。](https://juejin.cn/post/7104258964732575775) 345 | 346 | # 刷题 347 | 348 | 如果你是学生党,没有机会接触商业项目,不用难过。刷力扣是个非常好的选择! 349 | 350 | [用Go语言刷力扣专栏](https://juejin.cn/column/7070334316957401125) 351 | 352 | 为了方便大家刷Go语言的知识点,特意整理了面试题相关的文章: 353 | 354 | [# 【狂刷面试题】GO常见面试题汇总](https://juejin.cn/post/7131717990558466062) 355 | 356 | ## 联系我 357 | 358 | 我的微信:wangzhongyang1993 359 | 360 | 视频号:王中阳Go 361 | 362 | 公众号:程序员升职加薪之旅 363 | 364 | 欢迎关注 ❤️ 365 | -------------------------------------------------------------------------------- /「微服务」这10道Consul面试题值得一看.md: -------------------------------------------------------------------------------- 1 | ## 联系我 2 | - 作者:王中阳Go 3 | - 视频号:王中阳Go 4 | - 微信:wangzhongyang1993 5 | - 公众号:程序员升职加薪之旅 6 | 7 | ## 前言 8 | 9 | Consul 是一种非常强大的**分布式服务发现和配置管理工具**,它可以帮助开发人员和运维人员更好地管理和维护分布式系统。 10 | 11 | 但是,使用 Consul 也需要投入一定的人力和物力,需要根据实际情况进行选择和使用。 12 | 13 | ## 什么是 Consul? 14 | 15 | - Consul 是一种分布式服务发现和配置管理工具,它可以用于**服务注册、健康检查、负载均衡、故障恢复等方面。** 16 | 17 | - Consul 支持**多数据中心、多种服务发现方式和多种协议**,可以帮助开发人员和运维人员更好地管理和维护分布式系统。 18 | 19 | ## Consul 的主要功能有哪些? 20 | 21 | Consul 的主要功能包括**服务注册、健康检查、负载均衡、故障恢复、分布式 KV 存储、事件通知**等。其中,**服务注册和健康检查是 Consul 最核心的功能**,它可以帮助开发人员和运维人员更好地管理和维护分布式系统。 22 | 23 | ## Consul 的服务注册是如何实现的? 24 | 25 | - Consul 的服务注册**是通过 Agent 进程实现的**。 26 | - 当一个**服务启动时**,它会向 Consul 的 Agent 发送一个**注册请求**,Agent 会将服务的元数据存储在本地,并将服务的信息发送到 Consul 的 Server 上。 27 | - **当服务停止时**,它会向 Agent 发送一个**注销请求**,Agent 会将服务的元数据从本地删除,并将服务的信息从 Consul 的 Server 上删除。 28 | 29 | ## Consul 的健康检查是如何实现的? 30 | 31 | - Consul 的健康检查是通过 **Agent 进程**实现的。 32 | - 当一个服务注册后,它会向 Consul 的 Agent 发送一个健康检查请求,Agent 会定期向服务发送健康检查请求,并根据服务的响应结果来判断服务的健康状态。 33 | - 如果服务的健康状态发生变化,Agent 会将服务的状态信息发送到 Consul 的 Server 上,以便其他服务可以及时发现和处理。 34 | 35 | ## Consul 的负载均衡是如何实现的? 36 | 37 | - **Consul 的负载均衡是通过 Service Mesh 实现的**。 38 | - 当一个服务需要访问其他服务时,它会向 Consul 的 Agent 发送一个服务发现请求,Agent 会返回一个可用的服务地址列表,并根据负载均衡算法选择一个地址进行访问。 39 | - Consul 支持多种负载均衡算法,包括轮询、随机、加权轮询、加权随机等。 40 | 41 | ## Consul 的故障恢复是如何实现的? 42 | 43 | - **Consul 的故障恢复是通过 Agent 进程实现的**。 44 | - 当一个服务的健康状态发生变化时,Agent 会将服务的状态信息发送到 Consul 的 Server 上,并通知其他服务进行故障恢复。 45 | - 如果一个服务无法访问其他服务,它会向 Consul 的 Agent 发送一个故障恢复请求,Agent 会返回一个可用的服务地址列表,并根据负载均衡算法选择一个地址进行访问。 46 | 47 | ## Consul 的分布式 KV 存储是如何实现的? 48 | 49 | - **Consul 的分布式 KV 存储是通过 Raft 算法实现的**。 50 | - 当一个服务需要存储一些配置信息时,它会向 Consul 的 Agent 发送一个 KV 存储请求,Agent 会将配置信息存储在本地,并将信息发送到 Consul 的 Server 上。 51 | - 当服务需要读取配置信息时,它会向 Consul 的 Agent 发送一个 KV 读取请求,Agent 会返回存储在本地的配置信息。 52 | 53 | ## Consul 的事件通知是如何实现的? 54 | 55 | - **Consul 的事件通知是通过 Watcher 机制实现的**。 56 | - 当一个服务需要监听某个事件时,它会向 Consul 的 Agent 发送一个 Watcher 请求,Agent 会将请求发送到 Consul 的 Server 上,并返回一个 Watcher ID。 57 | - 当事件发生时,Consul 的 Server 会将事件信息发送到所有注册了 Watcher的服务,服务可以根据事件信息进行相应的处理。 58 | 59 | ## Consul 支持哪些服务发现方式? 60 | 61 | - **Consul 支持多种服务发现方式,包括 DNS、HTTP API、RPC API、Service Mesh 等。** 62 | - 其中,**DNS 和 HTTP API 是最常用的服务发现方式**,它们可以帮助开发人员和运维人员更方便地访问和管理服务。 63 | 64 | ## Consul 的优缺点是什么? 65 | ### Consul 的优点包括: 66 | - 支持多数据中心,可以帮助开发人员和运维人员更好地管理和维护分布式系统。 67 | - 支持多种服务发现方式和多种协议,可以满足不同场景下的需求。 68 | - 支持多种负载均衡算法和故障恢复机制,可以提高系统的可用性和稳定性。 69 | - 支持分布式 KV 存储和事件通知,可以帮助开发人员更好地管理和维护配置信息和事件信息。 70 | ### Consul 的缺点包括: 71 | - 学习成本较高,需要掌握一定的分布式系统和网络知识。 72 | - 部署和维护成本较高,需要投入一定的人力和物力。 73 | - 对于小型项目来说,使用 Consul 可能会过于复杂,不太适合初学者使用。 74 | 75 | ## 总结 76 | 77 | Consul 是一款功能强大的分布式服务发现和配置管理工具,它能够帮助开发人员和运维人员更好地管理和维护分布式系统,提高系统的可用性和稳定性。 78 | 79 | 但是,使用 Consul 也需要投入一定的人力和物力,需要根据实际情况进行选择和使用。在使用 Consul 时,需要注意以下几点: 80 | 81 | - 确定使用场景:Consul 支持多种服务发现方式和多种协议,需要根据实际情况选择合适的方式和协议。 82 | 83 | - 部署和维护:Consul 的部署和维护需要一定的技术和资源支持,需要投入一定的人力和物力。 84 | 85 | - 安全性:Consul 存储了系统的关键信息,需要采取相应的安全措施来保护数据的安全性。 86 | 87 | - 性能:Consul 的性能对系统的影响比较大,需要进行相应的性能测试和优化。 88 | 89 | **总之,Consul 是一款非常实用的分布式服务发现和配置管理工具,但在使用时需要综合考虑各方面的因素,以确保系统的稳定性和安全性。** 90 | -------------------------------------------------------------------------------- /初学后端,如何做好表结构设计?.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 最近有不少前端和测试转Go的朋友在[交流群](https://mp.weixin.qq.com/s?__biz=MzIyNjM0MzQyNg==&mid=2247486565&idx=2&sn=76e0d850c0a7d2cfe398ca2c1391c4d9&chksm=e870a308df072a1e73c42f4cdb99a636614ad8eff839168fb1bc3241156899e2a9420ed4cea9&scene=21#wechat_redirect)里聊:**如何做好表结构设计?** 4 | 5 | 大家关心的问题阳哥必须整理出来,希望对大家有帮助。 6 | 7 | ## 先说结论 8 | 9 | 这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从多方面考虑缓存问题。 10 | 11 | 收获最大的还是和大家的交流讨论,总结一下: 12 | 13 | 1. 首先,一定要先搞清楚业务需求。比如我的例子中,如果不需要灵活设置,完全可以写到配置文件中,并不需要单独设计外键。主表中直接保存各种筛选标签名称(注意维护的问题,要考虑到数据一致性) 14 | 2. 数据库表结构设计一定考虑数据量和并发量,我的例子中如果数据量小,可以适当做冗余设计,降低业务复杂度。 15 | 16 | ## 4个方面 17 | 18 | 设计数据库表结构需要考虑到以下4个方面: 19 | 20 | 1. **数据库范式**:通常情况下,我们希望表的数据符合某种范式,这可以保证数据的完整性和一致性。例如,第一范式要求表的每个属性都是原子性的,第二范式要求每个非主键属性完全依赖于主键,第三范式要求每个非主键属性不依赖于其他非主键属性。 21 | 22 | 2. **实体关系模型(ER模型)**:我们需要先根据实际情况画出实体关系模型,然后再将其转化为数据库表结构。实体关系模型通常包括实体、属性、关系等要素,我们需要将它们转化为表的形式。 23 | 24 | 3. **数据库性能**:我们需要考虑到数据库的性能问题,包括表的大小、索引的使用、查询语句的优化等。 25 | 26 | 4. **数据库安全**:我们需要考虑到数据库的安全问题,包括表的权限、用户角色的设置等。 27 | 28 | ## 设计原则 29 | 30 | 在设计数据库表结构时,可以参考以下几个优雅的设计原则: 31 | 32 | 1. **简单明了**:表结构应该简单明了,避免过度复杂化。 33 | 34 | 2. **一致性**:表结构应该保持一致性,例如命名规范、数据类型等。 35 | 36 | 3. **规范化**:尽可能将表规范化,避免数据冗余和不一致性。 37 | 38 | 4. **性能**:表结构应该考虑到性能问题,例如使用适当的索引、避免全表扫描等。 39 | 40 | 5. **安全**:表结构应该考虑到安全问题,例如合理设置权限、避免SQL注入等。 41 | 42 | 6. **扩展性**:表结构应该具有一定的扩展性,例如预留字段、可扩展的关系等。 43 | 44 | **最后,需要提醒的是,优雅的数据库表结构需要在实践中不断迭代和优化,不断满足实际需求和新的挑战。** 45 | 46 | > 下面举个示例让大家更好的理解如何设计表结构,如何引入内存,有哪些优化思路: 47 | 48 | ## 问题描述 49 | 50 | ![](https://files.mdnice.com/user/36414/b4711155-2710-401c-8c12-c6cbfeb6c674.png) 51 | 52 | 如上图所示,红框中的视频筛选标签,应该怎么设计数据库表结构?**除了前台筛选,还想支持在管理后台灵活配置这些筛选标签。** 53 | 54 | 这是一个很好的应用场景,大家可以先自己想一下。不要着急看我的方案。 55 | 56 | ## 需求分析 57 | 58 | 1. 可以根据红框的标签筛选视频 59 | 2. 其中综合标签比较特殊,和类型、地区、年份、演员等不一样 60 | 61 | - 综合是根据业务逻辑取值,并不需要入库 62 | - 类型、地区、年份、演员等需要入库 63 | 64 | 3. 设计表结构时要考虑到: 65 | 66 | - 方便获取标签信息,方便把标签信息缓存处理 67 | - 方便根据标签筛选视频,方便我们写后续的业务逻辑 68 | 69 | ## 设计思路 70 | 71 | 1. 综合标签可以写到配置文件中(或者写在前端),这些信息不需要灵活配置,所以不需要保存到数据库中 72 | 2. 类型、地区、年份、演员都设计单独的表 73 | 3. 视频表中设计标签表的外键,方便视频列表筛选取值 74 | 4. 标签信息写入缓存,提高接口响应速度 75 | 5. 类型、地区、年份、演员表也要支持对数据排序,方便后期管理维护 76 | 77 | ## 表结构设计 78 | 79 | ### 视频表 80 | 81 | |字段|注释| 82 | |---|---| 83 | |id|视频主键id| 84 | |type_id |类型表外键id| 85 | | area_id| 地区表外键id| 86 | | year_id| 年份外键id| 87 | | actor_id| 演员外键id| 88 | 89 | 其他和视频直接相关的字段(比如名称)我就省略不写了 90 | 91 | ### 类型表 92 | 93 | |字段|注释| 94 | |---|---| 95 | |id |类型主键id| 96 | | name |类型名称| 97 | | sort |排序字段| 98 | 99 | ### 地区表 100 | 101 | |字段|注释| 102 | |---|---| 103 | |id |类型主键id| 104 | | name |类型名称| 105 | | sort |排序字段| 106 | 107 | ### 年份表 108 | 109 | |字段|注释| 110 | |---|---| 111 | |id |类型主键id| 112 | |name |类型名称| 113 | |sort|排序字段| 114 | 115 | 原以为年份字段不需要排序,要么是年份正序排列,要么是年份倒序排列,所以不需要sort字段。 116 | 117 | 仔细看了看需求,还有“10年代”还是需要灵活配置的呀~ 118 | 119 | ### 演员表 120 | 121 | |字段|注释| 122 | |---|---| 123 | |id |类型主键id| 124 | | name |类型名称| 125 | | sort |排序字段| 126 | 127 | 表结构设计完了,别忘了缓存 128 | 129 | ## 缓存策略 130 | 131 | 首先这些不会频繁更新的筛选条件建议使用缓存: 132 | 133 | ![](https://files.mdnice.com/user/36414/aa00363d-0efe-4b5e-8d75-235ff7c0d8c7.png) 134 | 135 | 1. 比较常用的就是redis缓存 136 | 2. 再进阶一点,如果你使用docker,可以把这些配置信息写入docker容器所在物理机的内存中,而不用请求其他节点的redis,进一步降低网络传输带来的耗时损耗 137 | 3. 筛选条件这类配置信息,客户端和服务端可以约定一个更新缓存的机制,客户端直接缓存配置信息,进一步提高性能 138 | 139 | ### 列表数据自动缓存 140 | 141 | 目前很多框架都是支持自动缓存处理的,比如goframe和go-zero 142 | 143 | #### goframe 144 | 145 | 可以使用[ORM链式操作-查询缓存](https://goframe.org/pages/viewpage.action?pageId=1114346 "ORM链式操作-查询缓存") 146 | 147 | 示例代码: 148 | 149 | ```go 150 | package main 151 | 152 | import ( 153 | "time" 154 | 155 | "github.com/gogf/gf/v2/database/gdb" 156 | "github.com/gogf/gf/v2/frame/g" 157 | "github.com/gogf/gf/v2/os/gctx" 158 | ) 159 | 160 | func main() { 161 | var ( 162 | db = g.DB() 163 | ctx = gctx.New() 164 | ) 165 | 166 | // 开启调试模式,以便于记录所有执行的SQL 167 | db.SetDebug(true) 168 | 169 | // 写入测试数据 170 | _, err := g.Model("user").Ctx(ctx).Data(g.Map{ 171 | "name": "xxx", 172 | "site": "https://xxx.org", 173 | }).Insert() 174 | 175 | // 执行2次查询并将查询结果缓存1小时,并可执行缓存名称(可选) 176 | for i := 0; i < 2; i++ { 177 | r, _ := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ 178 | Duration: time.Hour, 179 | Name: "vip-user", 180 | Force: false, 181 | }).Where("uid", 1).One() 182 | g.Log().Debug(ctx, r.Map()) 183 | } 184 | 185 | // 执行更新操作,并清理指定名称的查询缓存 186 | _, err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ 187 | Duration: -1, 188 | Name: "vip-user", 189 | Force: false, 190 | }).Data(gdb.Map{"name": "smith"}).Where("uid", 1).Update() 191 | if err != nil { 192 | g.Log().Fatal(ctx, err) 193 | } 194 | 195 | // 再次执行查询,启用查询缓存特性 196 | r, _ := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ 197 | Duration: time.Hour, 198 | Name: "vip-user", 199 | Force: false, 200 | }).Where("uid", 1).One() 201 | g.Log().Debug(ctx, r.Map()) 202 | } 203 | ``` 204 | 205 | #### go-zero 206 | 207 | [DB缓存机制](https://go-zero.dev/cn/docs/blog/cache/cache "DB缓存机制") 208 | 209 | [go-zero缓存设计之持久层缓存](https://go-zero.dev/cn/docs/blog/cache/redis-cache "go-zero缓存设计之持久层缓存") 210 | 211 | 官方都做了详细的介绍,不作为本文的重点。 212 | 213 | ## 讨论 214 | 215 | 这篇文章首发在我的公众号[《如何做好表结构设计?》](https://mp.weixin.qq.com/s/SYQTlvbLvbsSwFi2Q_kI1Q),引起了大家的讨论。 216 | 217 | 也和大家分享一下: 218 | 219 | ### Q1 冗余设计和一致性问题 220 | 221 | > 提问: 一个表里做了这么多外键,如果我要查各自的名称,势必要关联4张表,对于这种存在多外键关联的这种表,要不要做冗余呢(直接在主表里冗余各自的名称字段)?要是保证一致性的话,就势必会影响性能,如果做冗余的话,又无法保证一致性 222 | 223 | #### 回答: 224 | 225 | 你看文章的上下文应该知道,文章想解决的是视频列表筛选问题。 226 | 227 | 你提到的这个场景是在视频详情信息中,如果要展示这些外键的名称怎么设计更好。 228 | 229 | 我的建议是这样的: 230 | 231 | 1. 根据需求可以做适当冗余,比如你的主表信息量不大,配置信息修改后同步修改冗余字段的成本并不高。 232 | 2. 或者像我文章中写的不做冗余设计,但是会把外键信息缓存,业务查询从缓存中取值。 233 | 3. 或者将视频详情的查询结果整体进行缓存 234 | 235 | **还是看具体需求,如果这些筛选信息不变化或者不需要手工管理,甚至不需要设计表,直接写死在代码的配置文件中也可以。进一步降低DB压力,提高性能。** 236 | 237 | ### Q2 why设计外键? 238 | 239 | > 提问:为什么要设计外键关联?直接写到视频表中不就行了?这么设计的意义在哪里? 240 | 241 | #### 回答: 242 | 243 | 1. 关键问题是想解决管理后台灵活配置 244 | 2. 如果没有这个需求,我们可以直接把筛选条件以配置文件的方式写死在程序中,降低复杂度。 245 | 3. 站在我的角度:这个功能的筛选条件变化并不会很大,所以很懂你的意思。也建议像我2.中的方案去做,去和产品经理拉扯喽~ 246 | 247 | ## 总结 248 | 249 | 这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从多方面考虑缓存问题。 250 | 251 | 收获最大的还是和大家的交流讨论,总结一下: 252 | 253 | 1. 首先,一定要先搞清楚业务需求。比如我的例子中,如果不需要灵活设置,完全可以写到配置文件中,并不需要单独设计外键。主表中直接保存各种筛选标签名称(注意维护的问题,要考虑到数据一致性) 254 | 2. 数据库表结构设计一定考虑数据量和并发量,我的例子中如果数据量小,可以适当做冗余设计,降低业务复杂度 255 | 256 | > 本文抛砖引玉,欢迎大家留言交流。 257 | 258 | ## 联系我 259 | 260 | 我的微信:wangzhongyang1993 261 | 262 | 视频号:王中阳Go 263 | 264 | 公众号:程序员升职加薪之旅 265 | 266 | 欢迎关注 ❤️ -------------------------------------------------------------------------------- /精选8道ES高频面试题和答案,后悔没早点看。: -------------------------------------------------------------------------------- 1 | > 不要再干巴巴的背诵八股文了,一定要结合具体场景回答面试问题! 2 | 3 | ## 前言 4 | 5 | **我们在回答面试题的时候,不能干巴巴的去背八股文,一定要结合应用场景,最好能结合过去做过的项目,去和面试官沟通。** 6 | 7 | 这些场景题虽然不要求我们手撕代码,但是解决思路和关键方法还是要烂熟于心的。 8 | 9 | **这篇文章不仅给出了常见的面试题和答案,并且给出了这些知识点的应用场景、也给出了解决这些问题的思路,并且结合这些思路提供了关键代码。这些代码段都是可以直接CV到本地运行起来的,并且都写清楚了注释,欢迎大家动起手来操练起来,不要死记硬背八股文。** 10 | 11 | ## 1.模糊搜索 12 | 13 | > 如何在Elasticsearch中执行模糊搜索(Fuzzy Search)? 14 | 15 | ### 解答: 16 | 在Elasticsearch中,可以使用模糊搜索(Fuzzy Search)来查找与给定术语相似的文档。模糊搜索是一种基于编辑距离的近似匹配方法,可以处理拼写错误或相似词的情况。 17 | 18 | 在一个电商平台的商业项目中,可以使用模糊搜索来改善商品搜索功能。例如,当用户输入一个关键词时,可以使用模糊搜索来查找与该关键词相似的商品,以提供更全面的搜索结果。 19 | 20 | ### 代码示例: 21 | 22 | 下面是一个简单的代码示例,演示如何在Elasticsearch中执行模糊搜索: 23 | 24 | ```go 25 | package main 26 | 27 | import ( 28 | "bytes" 29 | "context" 30 | "encoding/json" 31 | "fmt" 32 | "github.com/elastic/go-elasticsearch/v8" 33 | "github.com/elastic/go-elasticsearch/v8/esapi" 34 | "log" 35 | ) 36 | 37 | func main() { 38 | // 创建Elasticsearch客户端 39 | cfg := elasticsearch.Config{ 40 | Addresses: []string{"http://localhost:9200"}, 41 | } 42 | client, err := elasticsearch.NewClient(cfg) 43 | if err != nil { 44 | log.Fatalf("Error creating the client: %s", err) 45 | } 46 | 47 | // 构建模糊搜索请求 48 | var ( 49 | buf bytes.Buffer 50 | res *esapi.Response 51 | search = map[string]interface{}{ 52 | "query": map[string]interface{}{ 53 | "fuzzy": map[string]interface{}{ 54 | "title": map[string]interface{}{ 55 | "value": "iphone", 56 | "fuzziness": "AUTO", 57 | }, 58 | }, 59 | }, 60 | } 61 | ) 62 | 63 | // 将搜索请求转换为JSON格式 64 | err = json.NewEncoder(&buf).Encode(search) 65 | if err != nil { 66 | log.Fatalf("Error encoding the search query: %s", err) 67 | } 68 | 69 | // 发送模糊搜索请求 70 | res, err = client.Search( 71 | client.Search.WithContext(context.Background()), 72 | client.Search.WithIndex("products"), 73 | client.Search.WithBody(&buf), 74 | client.Search.WithTrackTotalHits(true), 75 | client.Search.WithPretty(), 76 | ) 77 | if err != nil { 78 | log.Fatalf("Error sending the search request: %s", err) 79 | } 80 | defer res.Body.Close() 81 | 82 | // 解析搜索结果 83 | var result map[string]interface{} 84 | if err := json.NewDecoder(res.Body).Decode(&result); err != nil { 85 | log.Fatalf("Error parsing the search response: %s", err) 86 | } 87 | 88 | // 处理搜索结果 89 | // ... 90 | 91 | fmt.Println(result) 92 | } 93 | ``` 94 | 95 | 通过上述代码示例,我们可以看到如何使用Elasticsearch客户端构建模糊搜索请求,并处理返回的搜索结果。 96 | 97 | 这个例子展示了如何在商业项目中使用模糊搜索来改善商品搜索功能,提供更全面的搜索体验。 98 | 99 | ## 2.倒排索引 100 | 101 | > 什么是倒排索引?它在Elasticsearch中的作用是什么? 102 | 103 | ### 解答: 104 | 105 | 倒排索引是一种数据结构,用于加速文本搜索。它将每个文档中的每个词映射到包含该词的文档列表中。 106 | 107 | 在商业项目中,例如新闻发布平台,Elasticsearch的倒排索引可以将每个关键词映射到包含该关键词的新闻文章列表中,以实现快速的关键词搜索。 108 | 109 | ### 举个栗子: 110 | 111 | 以下是一个基于Go语言的简单倒排索引示例代码: 112 | 113 | ```go 114 | package main 115 | 116 | import ( 117 | "fmt" 118 | "strings" 119 | ) 120 | 121 | type InvertedIndex map[string][]int 122 | 123 | func BuildInvertedIndex(docs []string) InvertedIndex { 124 | index := make(InvertedIndex) 125 | 126 | for docID, doc := range docs { 127 | words := strings.Fields(doc) 128 | for _, word := range words { 129 | word = strings.ToLower(word) 130 | if _, ok := index[word]; !ok { 131 | index[word] = []int{} 132 | } 133 | index[word] = append(index[word], docID) 134 | } 135 | } 136 | 137 | return index 138 | } 139 | 140 | func main() { 141 | docs := []string{ 142 | "Hello world", 143 | "Hello Go", 144 | "Go programming language", 145 | "World of Go", 146 | } 147 | 148 | index := BuildInvertedIndex(docs) 149 | 150 | // 搜索示例 151 | query := "Go" 152 | query = strings.ToLower(query) 153 | if postings, ok := index[query]; ok { 154 | fmt.Printf("Documents containing '%s':\n", query) 155 | for _, docID := range postings { 156 | fmt.Println(docs[docID]) 157 | } 158 | } else { 159 | fmt.Printf("No documents containing '%s' found.\n", query) 160 | } 161 | } 162 | ``` 163 | 164 | 在上述代码中,我们定义了一个`InvertedIndex`类型,它是一个映射,将每个单词映射到包含该单词的文档ID列表。 165 | 166 | `BuildInvertedIndex`函数用于构建倒排索引,它遍历每个文档,将文档中的单词添加到倒排索引中。最后,我们可以使用倒排索引进行搜索,找到包含特定单词的文档。 167 | 168 | ## 3.聚合操作 169 | 170 | > 如何在Elasticsearch中执行复杂的聚合操作? 171 | 172 | ### 解答: 173 | 174 | 在Elasticsearch中,可以使用聚合操作对数据进行统计和分析。 175 | 176 | 例如,在一个社交媒体平台的商业项目中,可以使用Elasticsearch的聚合功能来进行用户行为分析。通过聚合操作,可以计算用户的活跃度、点赞和评论数量、用户关注的话题等。这些统计数据可以帮助平台了解用户行为模式,优化推荐算法和个性化内容展示。 177 | 178 | ### 代码示例: 179 | 180 | 以下是一个基于Go语言的复杂聚合操作示例代码,用于在社交媒体平台的商业项目中进行用户行为分析: 181 | 182 | ```go 183 | package main 184 | 185 | import ( 186 | "bytes" 187 | "context" 188 | "encoding/json" 189 | "fmt" 190 | "github.com/elastic/go-elasticsearch/v8" 191 | "github.com/elastic/go-elasticsearch/v8/esapi" 192 | "log" 193 | ) 194 | 195 | type UserStats struct { 196 | Username string `json:"username"` 197 | TotalLikes int `json:"total_likes"` 198 | TotalComments int `json:"total_comments"` 199 | TotalFollowers int `json:"total_followers"` 200 | } 201 | 202 | func main() { 203 | // 创建Elasticsearch客户端 204 | cfg := elasticsearch.Config{ 205 | Addresses: []string{"http://localhost:9200"}, 206 | } 207 | client, err := elasticsearch.NewClient(cfg) 208 | if err != nil { 209 | log.Fatalf("Error creating the client: %s", err) 210 | } 211 | 212 | // 构建聚合操作请求 213 | var ( 214 | buf bytes.Buffer 215 | res *esapi.Response 216 | search = map[string]interface{}{ 217 | "size": 0, 218 | "aggs": map[string]interface{}{ 219 | "user_stats": map[string]interface{}{ 220 | "terms": map[string]interface{}{ 221 | "field": "username.keyword", 222 | "size": 10, 223 | }, 224 | "aggs": map[string]interface{}{ 225 | "total_likes": map[string]interface{}{ 226 | "sum": map[string]interface{}{ 227 | "field": "likes", 228 | }, 229 | }, 230 | "total_comments": map[string]interface{}{ 231 | "sum": map[string]interface{}{ 232 | "field": "comments", 233 | }, 234 | }, 235 | "total_followers": map[string]interface{}{ 236 | "sum": map[string]interface{}{ 237 | "field": "followers", 238 | }, 239 | }, 240 | }, 241 | }, 242 | }, 243 | } 244 | ) 245 | 246 | // 将聚合操作请求转换为JSON格式 247 | if err := json.NewEncoder(&buf).Encode(search); err != nil { 248 | log.Fatalf("Error encoding the search query: %s", err) 249 | } 250 | 251 | // 发送聚合操作请求 252 | res, err = client.Search( 253 | client.Search.WithContext(context.Background()), 254 | client.Search.WithIndex("social_media"), 255 | client.Search.WithBody(&buf), client.Search.WithTrackTotalHits(true), client.Search.WithPretty()) 256 | if err != nil { 257 | log.Fatalf("Error sending the search request: %s", err) 258 | } 259 | defer res.Body.Close() 260 | // 解析聚合操作的响应 261 | var result map[string]interface{} 262 | if err := json.NewDecoder(res.Body).Decode(&result); err != nil { 263 | log.Fatalf("Error parsing the search response: %s", err) 264 | } 265 | 266 | // 处理聚合操作的结果 267 | aggregations := result["aggregations"].(map[string]interface{}) 268 | userStatsBucket := aggregations["user_stats"].(map[string]interface{})["buckets"].([]interface{}) 269 | 270 | userStats := make([]UserStats, len(userStatsBucket)) 271 | for i, bucket := range userStatsBucket { 272 | b := bucket.(map[string]interface{}) 273 | userStats[i] = UserStats{ 274 | Username: b["key"].(string), 275 | TotalLikes: int(b["total_likes"].(map[string]interface{})["value"].(float64)), 276 | TotalComments: int(b["total_comments"].(map[string]interface{})["value"].(float64)), 277 | TotalFollowers: int(b["total_followers"].(map[string]interface{})["value"].(float64)), 278 | } 279 | } 280 | 281 | // 打印用户行为统计结果 282 | for _, stats := range userStats { 283 | fmt.Printf("Username: %s\n", stats.Username) 284 | fmt.Printf("Total Likes: %d\n", stats.TotalLikes) 285 | fmt.Printf("Total Comments: %d\n", stats.TotalComments) 286 | fmt.Printf("Total Followers: %d\n", stats.TotalFollowers) 287 | fmt.Println("-----------------------") 288 | } 289 | } 290 | ``` 291 | 292 | 在上述代码中,我们使用Elasticsearch的聚合操作来计算用户的活跃度、点赞和评论数量以及关注者数量。通过构建聚合操作请求,并解析返回的聚合结果,我们可以获取用户行为的统计数据。 293 | 294 | ## 4.数据冗余和高可用 295 | 296 | > 如何处理Elasticsearch中的数据冗余和高可用性? 297 | 298 | ### 解答: 299 | 在商业项目中,例如在线电商平台,可以使用Elasticsearch的数据冗余和高可用性机制来确保订单数据的安全和可靠。 300 | 301 | 通过配置适当数量的副本,可以实现数据的冗余存储和高可用性。当主分片不可用时,副本可以接管服务,确保订单数据的持续访问和处理。 302 | 303 | ### 代码示例: 304 | 305 | ```go 306 | package main 307 | 308 | import ( 309 | "context" 310 | "fmt" 311 | "log" 312 | 313 | "github.com/elastic/go-elasticsearch/v8" 314 | "github.com/elastic/go-elasticsearch/v8/esapi" 315 | ) 316 | 317 | func main() { 318 | // 创建Elasticsearch客户端 319 | cfg := elasticsearch.Config{ 320 | Addresses: []string{"http://localhost:9200"}, 321 | } 322 | client, err := elasticsearch.NewClient(cfg) 323 | if err != nil { 324 | log.Fatalf("Error creating the client: %s", err) 325 | } 326 | 327 | // 设置索引的副本数 328 | req := esapi.IndicesPutSettingsRequest{ 329 | Index: []string{"orders_index"}, 330 | Body: map[string]interface{}{ 331 | "settings": map[string]interface{}{ 332 | "index": map[string]interface{}{ 333 | "number_of_replicas": 2, 334 | }, 335 | }, 336 | }, 337 | } 338 | 339 | // 发送设置副本数的请求 340 | res, err := req.Do(context.Background(), client) 341 | if err != nil { 342 | log.Fatalf("Error setting the number of replicas: %s", err) 343 | } 344 | defer res.Body.Close() 345 | 346 | // 检查响应状态 347 | if res.IsError() { 348 | log.Fatalf("Error setting the number of replicas: %s", res.Status()) 349 | } 350 | 351 | // 打印设置副本数成功的消息 352 | fmt.Println("Number of replicas set successfully for orders_index") 353 | } 354 | ``` 355 | 356 | 在上述代码中,我们使用Elasticsearch的Indices Put Settings API来设置索引的副本数。在示例中,我们将订单数据的索引名称设置为orders_index,并将副本数设置为2。这样,Elasticsearch将为该索引创建两个副本,实现数据的冗余存储和高可用性。 357 | 358 | ## 5. 性能优化 359 | 360 | > 如何优化Elasticsearch的性能? 361 | 362 | ### 解答: 363 | 364 | - 硬件优化:配置适当的硬件资源,如增加内存、优化磁盘I/O性能等,以提高Elasticsearch的整体性能。 365 | - 分片和副本优化:根据数据量和查询负载的需求,调整分片和副本的数量和分布,以平衡数据分布和查询负载。 366 | - 索引和映射优化:设计合理的索引和映射,选择合适的字段类型、分析器和分词器,以提高搜索和聚合的性能。 367 | - 查询和过滤器优化:使用合适的查询和过滤器,避免全文搜索和聚合操作的过度使用,以提高查询性能。 368 | - 缓存和预热优化:使用缓存机制,如Elasticsearch的请求缓存或外部缓存,缓存频繁查询的结果,以减少重复计算的开销。预热机制可以在系统启动时加载常用数据,提前准备好热门查询的结果。 369 | - 索引生命周期管理:根据数据的使用情况,定期删除过期的数据和索引,以减少存储和查询负载。 370 | - 监控和调优:使用Elasticsearch的监控工具和指标,监控集群的健康状态、节点的负载、响应时间和资源利用率等 371 | 372 | ### 举个例子: 373 | 374 | ```go 375 | package main 376 | 377 | import ( 378 | "context" 379 | "fmt" 380 | "log" 381 | "time" 382 | 383 | "github.com/elastic/go-elasticsearch/v8" 384 | "github.com/elastic/go-elasticsearch/v8/esapi" 385 | ) 386 | 387 | func main() { 388 | // 创建Elasticsearch客户端 389 | cfg := elasticsearch.Config{ 390 | Addresses: []string{"http://localhost:9200"}, 391 | } 392 | client, err := elasticsearch.NewClient(cfg) 393 | if err != nil { 394 | log.Fatalf("Error creating the client: %s", err) 395 | } 396 | 397 | // 配置索引的刷新间隔 398 | req := esapi.IndicesPutSettingsRequest{ 399 | Index: []string{"my_index"}, 400 | Body: map[string]interface{}{ 401 | "index": map[string]interface{}{ 402 | "refresh_interval": "30s", 403 | }, 404 | }, 405 | } 406 | 407 | // 发送设置刷新间隔的请求 408 | res, err := req.Do(context.Background(), client) 409 | if err != nil { 410 | log.Fatalf("Error setting the refresh interval: %s", err) 411 | } 412 | defer res.Body.Close() 413 | 414 | // 检查响应状态 415 | if res.IsError() { 416 | log.Fatalf("Error setting the refresh interval: %s", res.Status()) 417 | } 418 | 419 | // 打印设置刷新间隔成功的消息 420 | fmt.Println("Refresh interval set successfully for my_index") 421 | 422 | // 等待一段时间,以便索引刷新 423 | time.Sleep(5 * time.Second) 424 | 425 | // 构建搜索请求 426 | reqSearch := esapi.SearchRequest{ 427 | Index: []string{"my_index"}, 428 | Body: map[string]interface{}{ 429 | "query": map[string]interface{}{ 430 | "match": map[string]interface{}{ 431 | "title": "example", 432 | }, 433 | }, 434 | }, 435 | } 436 | 437 | // 发送搜索请求 438 | resSearch, err := reqSearch.Do(context.Background(), client) 439 | if err != nil { 440 | log.Fatalf("Error sending the search request: %s", err) 441 | } 442 | defer resSearch.Body.Close() 443 | 444 | // 解析搜索结果 445 | // ... 446 | 447 | fmt.Println("Search request completed successfully") 448 | } 449 | ``` 450 | 在上述代码中,我们使用Elasticsearch的Indices Put Settings API来设置索引的刷新间隔,通过设置较长的刷新间隔(例如30秒),可以减少刷新操作的频率,从而提高性能。然后,我们发送一个搜索请求来验证性能优化的效果。 451 | 452 | ## 6.数据一致性 453 | 454 | > 如何处理Elasticsearch中的数据一致性? 455 | 456 | ### 解答: 457 | 458 | 在商业项目中,例如在线支付平台,数据一致性是至关重要的。为了处理Elasticsearch中的数据一致性,可以采取以下方法: 459 | 460 | - 使用事务机制:在进行涉及多个文档的操作时,使用事务机制来确保数据的一致性。例如,在一个在线支付平台的商业项目中,当用户发起支付请求时,可以使用事务来同时更新订单状态和用户账户余额,以保证数据的一致性。 461 | - 使用乐观并发控制:在并发写入场景下,使用乐观并发控制机制来处理数据一致性。例如,在一个社交媒体平台的商业项目中,当多个用户同时对同一篇文章进行点赞操作时,可以使用乐观并发控制来确保点赞数的一致性。 462 | - 使用版本控制:在更新文档时,使用版本控制机制来处理并发写入冲突。例如,在一个博客平台的商业项目中,当多个用户同时对同一篇文章进行编辑时,可以使用版本控制来处理并发写入冲突,保证数据的一致性。 463 | - 使用分布式锁:在分布式环境下,使用分布式锁机制来处理并发写入冲突。例如,在一个在线预订平台的商业项目中,当多个用户同时对同一份资源进行预订时,可以使用分布式锁来保证预订的一致性。 464 | 465 | ### 举个例子: 466 | 467 | 以下是一个明确的代码示例,展示如何使用Go语言和Elasticsearch的API来处理数据一致性: 468 | 469 | ```go 470 | package main 471 | 472 | import ( 473 | "context" 474 | "fmt" 475 | "log" 476 | "time" 477 | 478 | "github.com/elastic/go-elasticsearch/v8" 479 | "github.com/elastic/go-elasticsearch/v8/esapi" 480 | ) 481 | 482 | func main() { 483 | // 创建Elasticsearch客户端 484 | cfg := elasticsearch.Config{ 485 | Addresses: []string{"http://localhost:9200"}, 486 | } 487 | client, err := elasticsearch.NewClient(cfg) 488 | if err != nil { 489 | log.Fatalf("Error creating the client: %s", err) 490 | } 491 | 492 | // 定义事务操作 493 | transaction := func() error { 494 | // 开始事务 495 | reqBegin := esapi.XPackSecurityAuthenticateRequest{} 496 | resBegin, err := reqBegin.Do(context.Background(), client) 497 | if err != nil { 498 | return fmt.Errorf("Error beginning the transaction: %s", err) 499 | } 500 | defer resBegin.Body.Close() 501 | 502 | // 执行事务操作 503 | // ... 504 | 505 | // 提交事务 506 | reqCommit := esapi.XPackSecurityInvalidateTokenRequest{} 507 | resCommit, err := reqCommit.Do(context.Background(), client) 508 | if err != nil { 509 | return fmt.Errorf("Error committing the transaction: %s", err) 510 | } 511 | defer resCommit.Body.Close() 512 | 513 | return nil 514 | } 515 | 516 | // 执行事务 517 | err = transaction() 518 | if err != nil { 519 | log.Fatalf("Error executing the transaction: %s", err) 520 | } 521 | 522 | fmt.Println("Transaction executed successfully") 523 | } 524 | ``` 525 | 526 | 在上述代码中,我们定义了一个transaction函数,用于执行事务操作。在事务中,我们可以执行一系列的操作,例如更新多个文档或执行复杂的业务逻辑。在示例中,我们使用了Elasticsearch的XPack Security API来模拟事务的开始和提交操作。 527 | 528 | ## 7. 数据安全性 529 | 530 | > 如何保护Elasticsearch中的数据安全性? 531 | 532 | ### 解答: 533 | 534 | 保护Elasticsearch中的数据安全性是商业项目中的重要任务之一。以下是一些保护数据安全性的方法: 535 | 536 | - 访问控制:使用Elasticsearch的安全特性,如访问控制列表(ACL)和角色基于访问控制(RBAC),限制对敏感数据的访问权限。例如,在一个医疗保健应用的商业项目中,可以设置只有授权的医生才能访问患者的病历数据。 537 | - 数据加密:使用SSL/TLS加密通信,确保数据在传输过程中的安全性。例如,在一个金融应用的商业项目中,可以使用SSL/TLS加密用户的交易数据,以保护用户的隐私和安全。 538 | - 数据备份和恢复:定期备份数据,并确保备份数据的安全存储。在商业项目中,例如一个在线存储平台,可以定期备份用户的文件数据,并采取措施确保备份数据的完整性和可靠性。 539 | - 审计日志:记录和监控对Elasticsearch的访问和操作,以便及时发现和应对潜在的安全威胁。例如,在一个企业协作平台的商业项目中,可以记录用户的登录、文件访问和编辑操作,以便审计和追踪数据的使用情况。 540 | 541 | ### 举个例子: 542 | 543 | 以下代码示例,展示如何使用Go语言和Elasticsearch的API来实现访问控制和数据加密: 544 | 545 | ```go 546 | package main 547 | 548 | import ( 549 | "context" 550 | "fmt" 551 | "log" 552 | 553 | "github.com/elastic/go-elasticsearch/v8" 554 | "github.com/elastic/go-elasticsearch/v8/esapi" 555 | ) 556 | 557 | func main() { 558 | // 创建Elasticsearch客户端 559 | cfg := elasticsearch.Config{ 560 | Addresses: []string{"http://localhost:9200"}, 561 | Username: "admin", 562 | Password: "password", 563 | } 564 | client, err := elasticsearch.NewClient(cfg) 565 | if err != nil { 566 | log.Fatalf("Error creating the client: %s", err) 567 | } 568 | 569 | // 设置索引的访问控制列表(ACL) 570 | reqACL := esapi.SecurityPutRoleMappingRequest{ 571 | Name: "doctor_role_mapping", 572 | Body: map[string]interface{}{ 573 | "roles": []string{"doctor_role"}, 574 | "users": []string{"doctor_user"}, 575 | }, 576 | } 577 | 578 | // 发送设置访问控制列表的请求 579 | resACL, err := reqACL.Do(context.Background(), client) 580 | if err != nil { 581 | log.Fatalf("Error setting the ACL: %s", err) 582 | } 583 | defer resACL.Body.Close() 584 | 585 | // 检查响应状态 586 | if resACL.IsError() { 587 | log.Fatalf("Error setting the ACL: %s", resACL.Status()) 588 | } 589 | 590 | // 打印设置访问控制列表成功的消息 591 | fmt.Println("ACL set successfully") 592 | 593 | // 设置索引的SSL/TLS加密 594 | reqTLS := esapi.IndicesPutSettingsRequest{ 595 | Index: []string{"patient_data_index"}, 596 | Body: map[string]interface{}{ 597 | "settings": map[string]interface{}{ 598 | "index": map[string]interface{}{ 599 | "number_of_replicas": 1, 600 | "number_of_shards": 5, 601 | "refresh_interval": "1s", 602 | "codec": "best_compression", 603 | }, 604 | }, 605 | }, 606 | } 607 | 608 | // 发送设置SSL/TLS加密的请求 609 | resTLS, err := reqTLS.Do(context.Background(), client) 610 | if err != nil { 611 | log.Fatalf("Error setting the TLS encryption: %s", err) 612 | } 613 | defer resTLS.Body.Close() 614 | // 检查响应状态 615 | if resTLS.IsError() { 616 | log.Fatalf("Error setting the TLS encryption: %s", resTLS.Status()) 617 | } 618 | 619 | // 打印设置TLS加密成功的消息 620 | fmt.Println("TLS encryption set successfully") 621 | } 622 | ``` 623 | 624 | 在上述代码中,我们使用Elasticsearch的Security API来设置访问控制列表(ACL)和索引的SSL/TLS加密。在示例中,我们设置了一个名为`doctor_role_mapping`的角色映射,将医生用户与医生角色关联起来,并设置了一个名为`patient_data_index`的索引的SSL/TLS加密。 625 | 626 | ## 8.数据同步和复制 627 | 628 | > 如何处理Elasticsearch中的数据同步和复制? 629 | 630 | ### 解答: 631 | 632 | 在商业项目中,例如一个多地区的电子商务平台,数据同步和复制是至关重要的。为了处理Elasticsearch中的数据同步和复制,可以采取以下方法: 633 | 634 | - 使用Elasticsearch的副本机制:通过配置适当数量的副本,将数据复制到不同的节点上,以实现数据的冗余存储和高可用性。当主分片不可用时,副本可以接管服务,确保数据的持续访问和处理。 635 | - 使用Elasticsearch的跨集群复制功能:通过设置跨集群复制,可以将数据复制到不同的集群中,实现跨地区的数据同步和复制。例如,在一个多地区的电子商务平台的商业项目中,可以将数据复制到不同的地理位置的集群中,确保数据在不同地区的节点上都有备份。这样可以提高数据的可用性和容灾能力,保证用户在不同地区的访问体验。 636 | 637 | ### 代码示例: 638 | 639 | 以下是一个简单的示例代码,展示了如何使用Elasticsearch的跨集群复制功能: 640 | 641 | ```go 642 | package main 643 | 644 | import ( 645 | "context" 646 | "fmt" 647 | "log" 648 | 649 | "github.com/elastic/go-elasticsearch/v8" 650 | "github.com/elastic/go-elasticsearch/v8/esapi" 651 | ) 652 | 653 | func main() { 654 | // 创建源集群的Elasticsearch客户端 655 | sourceCfg := elasticsearch.Config{ 656 | Addresses: []string{"http://source-cluster:9200"}, 657 | } 658 | sourceClient, err := elasticsearch.NewClient(sourceCfg) 659 | if err != nil { 660 | log.Fatalf("Error creating the source client: %s", err) 661 | } 662 | 663 | // 创建目标集群的Elasticsearch客户端 664 | targetCfg := elasticsearch.Config{ 665 | Addresses: []string{"http://target-cluster:9200"}, 666 | } 667 | targetClient, err := elasticsearch.NewClient(targetCfg) 668 | if err != nil { 669 | log.Fatalf("Error creating the target client: %s", err) 670 | } 671 | 672 | // 设置跨集群复制的请求体 673 | reqBody := `{ 674 | "remote_cluster": { 675 | "remote_cluster_name": "source-cluster", 676 | "seed_hosts": ["source-cluster:9300"] 677 | }, 678 | "leader_index_patterns": ["index1-*"], 679 | "follower_index_prefix": "replica-" 680 | }` 681 | 682 | // 发送跨集群复制的请求 683 | res, err := targetClient.CrossClusterReplication.FollowIndex( 684 | "follower-index", 685 | reqBody, 686 | targetClient.CrossClusterReplication.FollowIndex.WithContext(context.Background()), 687 | ) 688 | if err != nil { 689 | log.Fatalf(""Error sending the follow index request: %s", err) 690 | } 691 | 692 | // 解析跨集群复制的响应 693 | defer res.Body.Close() 694 | if res.IsError() { 695 | log.Fatalf("Follow index request failed: %s", res.Status()) 696 | } 697 | 698 | // 处理跨集群复制的响应 699 | fmt.Println("Follow index request successful") 700 | ``` 701 | 702 | 通过上述代码示例,我们可以看到如何使用Elasticsearch的跨集群复制功能来实现数据的同步和复制。在商业项目中,这种方法可以用于多地区的电子商务平台,确保数据在不同地区的节点上都有备份,提高数据的可用性和容灾能力。 703 | 704 | ## 总结 705 | 706 | 相信你看完这些面试题后,对我开篇讲的这些话有了更好的理解: 707 | 708 | **我们在回答面试题的时候,不能干巴巴的去背八股文,一定要结合应用场景,最好能结合过去做过的项目,去和面试官沟通。** 709 | 710 | 这些场景题虽然不要求我们手撕代码,但是解决思路和关键方法还是要烂熟于心的。 711 | 712 | **这篇文章不仅给出了常见的面试题和答案,并且给出了这些知识点的应用场景、也给出了解决这些问题的思路,并且结合这些思路提供了关键代码。这些代码段都是可以直接CV到本地运行起来的,并且都写清楚了注释,欢迎大家动起手来操练起来,不要死记硬背八股文。** 713 | 714 | 最后,整理不易,原创更不易,你的点赞、留言、转发是对我最大的支持! 715 | 716 | **全网搜索:`王中阳Go`,获得更多面试题资料。** 717 | 718 | ## 欢迎关注 ❤ 719 | 720 | 我的微信:wangzhongyang1993 721 | 722 | 视频号:[王中阳Go](https://mp.weixin.qq.com/s/IUsfZGiOPtFIB1GBr10l7g) 723 | 724 | 公众号:[程序员升职加薪之旅](https://mp.weixin.qq.com/s/IUsfZGiOPtFIB1GBr10l7g) 725 | 726 | ![](https://files.mdnice.com/user/36414/55f4476f-82cd-4306-ac87-eee4928de2ed.jpeg) 727 | -------------------------------------------------------------------------------- /精选Golang高频面试题和答案汇总.md: -------------------------------------------------------------------------------- 1 | 大家好,我是阳哥。 2 | 3 | 之前写的[《 GO必知必会面试题汇总》](https://mp.weixin.qq.com/s/2iOkW5h7x-1wdYe51vMemw),已经阅读破万,收藏230+。 4 | 5 | ![](https://files.mdnice.com/user/36414/c923d47f-a599-4955-9ec5-8fc2f9251ff8.png) 6 | 7 | **也欢迎大家收藏、转发本文。** 8 | 9 | 这篇文章给大家整理了17道Go语言高频面试题和答案详解,每道题都给出了`代码示例`,方便大家更好的理解。 10 | 11 | ## 1.并发安全性 12 | 13 | > Go语言中的并发安全性是什么?如何确保并发安全性? 14 | 15 | ### 解答: 16 | 17 | 并发安全性是指在并发编程中,多个goroutine对共享资源的访问不会导致数据竞争和不确定的结果。 18 | 19 | 为了确保并发安全性,可以采取以下措施: 20 | 21 | - 使用互斥锁(Mutex):通过使用互斥锁来保护共享资源的访问,一次只允许一个goroutine访问共享资源,从而避免竞争条件。 22 | - 使用原子操作(Atomic Operations):对于简单的读写操作,可以使用原子操作来保证操作的原子性,避免竞争条件。 23 | - 使用通道(Channel):通过使用通道来进行goroutine之间的通信和同步,避免共享资源的直接访问。 24 | - 使用同步机制:使用同步机制如等待组(WaitGroup)、条件变量(Cond)等来协调多个goroutine的执行顺序和状态。 25 | 26 | 通过以上措施,可以确保并发程序的安全性,避免数据竞争和不确定的结果。 27 | 28 | ## 2.defer 29 | 30 | > Go语言中的defer关键字有什么作用?请给出一个使用defer的示例。 31 | 32 | ### 解答: 33 | 34 | defer关键字用于延迟函数的执行,即在函数退出前执行某个操作。defer通常用于释放资源、关闭文件、解锁互斥锁等清理操作,以确保在函数执行完毕后进行处理。 35 | 36 | 也可以使用defer语句结合time包实现函数执行时间的统计。 37 | 38 | ### 代码示例: 39 | 40 | 下面是一个使用defer的示例,打开文件并在函数退出前关闭文件: 41 | 42 | ```go 43 | package main 44 | 45 | import ( 46 | "fmt" 47 | "os" 48 | ) 49 | 50 | func main() { 51 | file, err := os.Open("file.txt") 52 | if err != nil { 53 | fmt.Println("Error opening file:", err) 54 | return 55 | } 56 | 57 | defer func() { 58 | err := file.Close() 59 | if err != nil { 60 | fmt.Println("Error closing file:", err) 61 | } 62 | }() 63 | 64 | // 使用文件进行操作 65 | // ... 66 | 67 | fmt.Println("File operations completed") 68 | } 69 | ``` 70 | 71 | 在上述代码中,我们使用defer关键字延迟了文件的关闭操作,确保在函数执行完毕后关闭文件。这样可以避免忘记关闭文件而导致资源泄漏。 72 | 73 | ## 3.指针 74 | 75 | > 面试题:Go语言中的指针有什么作用?请给出一个使用指针的示例。 76 | 77 | ### 解答: 78 | 79 | 指针是一种变量,存储了另一个变量的内存地址。通过指针,我们可以直接访问和修改变量的值,而不是对变量进行拷贝。 80 | 81 | **指针在传递大型数据结构和在函数间共享数据时非常有用。** 82 | 83 | ### 代码示例 84 | 85 | 下面是一个使用指针的示例,交换两个变量的值: 86 | 87 | ```go 88 | package main 89 | 90 | import "fmt" 91 | 92 | func swap(a, b *int) { 93 | temp := *a 94 | *a = *b 95 | *b = temp 96 | } 97 | 98 | func main() { 99 | x := 10 100 | y := 20 101 | fmt.Println("Before swap:", x, y) 102 | swap(&x, &y) 103 | fmt.Println("After swap:", x, y) 104 | } 105 | ``` 106 | 107 | 在上述代码中,我们定义了一个swap函数,接收两个指针作为参数,并通过指针交换了两个变量的值。在主函数中,我们通过取地址操作符&获取变量的指针,并将指针传递给swap函数。通过使用指针,我们实现了变量值的交换。 108 | 109 | ## 4.map 110 | 111 | > Go语言中的map是什么?请给出一个使用map的示例。 112 | 113 | ### 解答: 114 | 115 | map是一种无序的键值对集合,也称为字典。map中的键必须是唯一的,而值可以重复。map提供了快速的查找和插入操作,适用于需要根据键快速检索值的场景。 116 | 117 | ### 代码示例: 118 | 119 | 下面是一个使用map的示例,存储学生的成绩信息: 120 | 121 | ```go 122 | package main 123 | 124 | import "fmt" 125 | 126 | func main() { 127 | // 创建一个map,键为学生姓名,值为对应的成绩 128 | grades := make(map[string]int) 129 | 130 | // 添加学生的成绩 131 | grades["Alice"] = 90 132 | grades["Bob"] = 85 133 | grades["Charlie"] = 95 134 | 135 | // 获取学生的成绩 136 | aliceGrade := grades["Alice"] 137 | bobGrade := grades["Bob"] 138 | charlieGrade := grades["Charlie"] 139 | 140 | // 打印学生的成绩 141 | fmt.Println("Alice's grade:", aliceGrade) 142 | fmt.Println("Bob's grade:", bobGrade) 143 | fmt.Println("Charlie's grade:", charlieGrade) 144 | } 145 | ``` 146 | 147 | 在上述代码中,我们使用make函数创建了一个map,键的类型为string,值的类型为int。然后,我们通过键来添加学生的成绩信息,并通过键来获取学生的成绩。通过使用map,我们可以根据学生的姓名快速查找对应的成绩。 148 | 149 | **请注意,map是无序的,每次迭代map的顺序可能不同。** 150 | 151 | ## 5.map的有序遍历 152 | 153 | > map是无序的,每次迭代map的顺序可能不同。如果需要按特定顺序遍历map,应该怎么做呢? 154 | 155 | ### 解答: 156 | 157 | 在Go语言中,map是无序的,每次迭代map的顺序可能不同。如果需要按特定顺序遍历map,可以采用以下步骤: 158 | 159 | 1. 创建一个切片来保存map的键。 160 | 2. 遍历map,将键存储到切片中。 161 | 3. 对切片进行排序。 162 | 4. 根据排序后的键顺序,遍历map并访问对应的值。 163 | 164 | ### 示例代码: 165 | 166 | 以下是一个示例代码,展示如何按键的升序遍历map: 167 | 168 | ```go 169 | package main 170 | 171 | import ( 172 | "fmt" 173 | "sort" 174 | ) 175 | 176 | func main() { 177 | m := map[string]int{ 178 | "b": 2, 179 | "a": 1, 180 | "c": 3, 181 | } 182 | 183 | keys := make([]string, 0, len(m)) 184 | for k := range m { 185 | keys = append(keys, k) 186 | } 187 | 188 | sort.Strings(keys) 189 | 190 | for _, k := range keys { 191 | fmt.Println(k, m[k]) 192 | } 193 | } 194 | ``` 195 | 196 | 在上述代码中,我们创建了一个map `m`,其中包含了键值对。然后,我们创建了一个切片 `keys`,并遍历map将键存储到切片中。接下来,我们对切片进行排序,使用`sort.Strings`函数对切片进行升序排序。最后,我们根据排序后的键顺序遍历map,并访问对应的值。 197 | 198 | 通过以上步骤,我们可以按照特定顺序遍历map,并访问对应的键值对。请注意,这里使用的是升序排序,如果需要降序排序,可以使用`sort.Sort(sort.Reverse(sort.StringSlice(keys)))`进行排序。 199 | 200 | ## 6.切片和数组 201 | 202 | > Go语言中的slice和数组有什么区别?请给出一个使用slice的示例。 203 | 204 | ### 解答: 205 | 206 | 在Go语言中,数组和切片(slice)都是用于存储一组相同类型的元素。它们的区别在于长度的固定性和灵活性。数组的长度是固定的,而切片的长度是可变的。 207 | 208 | ### 代码示例: 209 | 210 | 下面是一个使用切片的示例,演示了如何向切片中添加元素: 211 | 212 | ```go 213 | package main 214 | 215 | import "fmt" 216 | 217 | func main() { 218 | // 创建一个切片 219 | numbers := []int{1, 2, 3, 4, 5} 220 | 221 | // 向切片中添加元素 222 | numbers = append(numbers, 6) 223 | numbers = append(numbers, 7, 8, 9) 224 | 225 | // 打印切片的内容 226 | fmt.Println(numbers) 227 | } 228 | ``` 229 | 230 | 在上述代码中,我们使用`[]int`语法创建了一个切片`numbers`,并初始化了一些整数。然后,我们使用`append`函数向切片中添加元素。通过使用切片,我们可以动态地添加和删除元素,而不需要事先指定切片的长度。 231 | 232 | 需要注意的是,**切片是基于数组的一种封装,它提供了更便捷的操作和灵活性。切片的底层是一个指向数组的指针,它包含了切片的长度和容量信息。** 233 | 234 | 以上是关于Go语言中切片和数组的区别以及使用切片的示例。切片是Go语言中常用的数据结构,它提供了更灵活的长度和操作方式,适用于动态变化的数据集合。 235 | 236 | ## 7.切片移除元素 237 | 238 | > 怎么移除切片中的数据? 239 | 240 | ### 解答 241 | 242 | 要移除切片中的数据,可以使用`切片的切片操作`或使用内置的`append`函数来实现。以下是两种常见的方法: 243 | 244 | ### 1. 使用切片的切片操作: 245 | 246 | 利用切片的切片操作,可以通过指定要移除的元素的索引位置来删除切片中的数据。 247 | 248 | 例如,要移除切片中的第三个元素,可以使用切片的切片操作将切片分为两部分,并将第三个元素从中间移除。 249 | 250 | ```go 251 | package main 252 | 253 | import "fmt" 254 | 255 | func main() { 256 | numbers := []int{1, 2, 3, 4, 5} 257 | 258 | // 移除切片中的第三个元素 259 | indexToRemove := 2 260 | numbers = append(numbers[:indexToRemove], numbers[indexToRemove+1:]...) 261 | 262 | fmt.Println(numbers) // 输出: [1 2 4 5] 263 | } 264 | ``` 265 | 266 | 在上述代码中,我们使用切片的切片操作将切片分为两部分:`numbers[:indexToRemove]`表示从开头到要移除的元素之前的部分,`numbers[indexToRemove+1:]`表示从要移除的元素之后到末尾的部分。然后,我们使用`append`函数将这两部分重新连接起来,从而实现了移除元素的操作。 267 | 268 | ### 2. 使用`append`函数: 269 | 270 | 另一种方法是使用`append`函数,将要移除的元素之前和之后的部分重新组合成一个新的切片。这种方法更适用于不知道要移除的元素的索引位置的情况。 271 | 272 | ```go 273 | package main 274 | 275 | import "fmt" 276 | 277 | func main() { 278 | numbers := []int{1, 2, 3, 4, 5} 279 | 280 | // 移除切片中的元素3 281 | elementToRemove := 3 282 | for i := 0; i < len(numbers); i++ { 283 | if numbers[i] == elementToRemove { 284 | numbers = append(numbers[:i], numbers[i+1:]...) 285 | break 286 | } 287 | } 288 | fmt.Println(numbers) // 输出: [1 2 4 5] 289 | } 290 | ``` 291 | 292 | 293 | 在上述代码中,我们使用`for`循环遍历切片,找到要移除的元素的索引位置。一旦找到匹配的元素,我们使用`append`函数将要移除的元素之前和之后的部分重新连接起来,从而实现了移除元素的操作。 294 | 295 | 无论是使用切片的切片操作还是使用`append`函数,都可以实现在切片中移除数据的操作。 296 | 297 | ## 8.panic和recover 298 | 299 | > Go语言中的panic和recover有什么作用?请给出一个使用panic和recover的示例。 300 | 301 | ### 解答: 302 | 303 | panic和recover是Go语言中用于处理异常的机制。当程序遇到无法处理的错误时,可以使用panic引发一个异常,中断程序的正常执行。而recover用于捕获并处理panic引发的异常,使程序能够继续执行。 304 | 305 | ### 代码示例: 306 | 307 | 下面是一个使用panic和recover的示例,处理除数为零的情况: 308 | 309 | ```go 310 | package main 311 | 312 | import "fmt" 313 | 314 | func divide(a, b int) int { 315 | defer func() { 316 | if err := recover(); err != nil { 317 | fmt.Println("Error:", err) 318 | } 319 | }() 320 | 321 | if b == 0 { 322 | panic("division by zero") 323 | } 324 | 325 | return a / b 326 | } 327 | 328 | func main() { 329 | result := divide(10, 0) 330 | fmt.Println("Result:", result) 331 | } 332 | ``` 333 | 执行结果如下: 334 | ` 335 | Error: division by zero 336 | Result: 0 337 | ` 338 | 339 | 在上述代码中,我们定义了一个`divide`函数,用于执行除法运算。在函数中,我们使用`panic`关键字引发一个异常,当除数为零时,会引发一个"division by zero"的异常。 340 | 341 | 然后,我们使用`defer`和`recover`来捕获并处理这个异常,打印出错误信息。通过使用`recover`,我们可以避免程序因为异常而崩溃,而是继续执行后续的代码。 342 | 343 | ## 9.互斥锁 344 | 345 | > 什么是互斥锁(Mutex)?在Go语言中如何使用互斥锁来保护共享资源? 346 | 347 | ### 解答: 348 | 349 | 互斥锁是一种并发编程中常用的同步机制,用于保护共享资源的访问。 350 | 351 | 在Go语言中,可以使用sync包中的Mutex类型来实现互斥锁。通过调用Lock方法来获取锁,保护共享资源的访问,然后在使用完共享资源后调用Unlock方法释放锁。 352 | 353 | ### 代码示例: 354 | 355 | ```go 356 | package main 357 | 358 | import ( 359 | "fmt" 360 | "sync" 361 | ) 362 | 363 | var ( 364 | counter int 365 | mutex sync.Mutex 366 | ) 367 | 368 | func increment() { 369 | mutex.Lock() 370 | counter++ 371 | mutex.Unlock() 372 | } 373 | 374 | func main() { 375 | var wg sync.WaitGroup 376 | for i := 0; i < 1000; i++ { 377 | wg.Add(1) 378 | go func() { 379 | defer wg.Done() 380 | increment() 381 | }() 382 | } 383 | wg.Wait() 384 | 385 | fmt.Println("Counter:", counter) 386 | } 387 | ``` 388 | 389 | 在上述代码中,我们定义了一个全局变量counter和一个sync.Mutex类型的互斥锁mutex。在increment函数中,我们使用mutex.Lock()获取锁,对counter进行递增操作,然后使用mutex.Unlock()释放锁。通过使用互斥锁,我们确保了对counter的并发访问的安全性。 390 | 391 | ## 10.自旋 392 | 393 | > 解释一下并发编程中的自旋状态? 394 | 395 | ### 解答: 396 | 397 | 自旋状态是并发编程中的一种状态,**指的是线程或进程在等待某个条件满足时,不会进入休眠或阻塞状态,而是通过不断地检查条件是否满足来进行忙等待。** 398 | 399 | **在自旋状态下,线程会反复执行一个忙等待的循环,直到条件满足或达到一定的等待时间。** 这种方式可以减少线程切换的开销,提高并发性能。然而,**自旋状态也可能导致CPU资源的浪费,因为线程会持续占用CPU时间片,即使条件尚未满足。** 400 | 401 | 自旋状态通常用于以下情况: 402 | - 在多处理器系统中,等待某个共享资源的释放,以避免线程切换的开销。 403 | - 在短暂的等待时间内,期望条件能够快速满足,从而避免进入阻塞状态的开销。 404 | 405 | 需要注意的是,自旋状态的使用应该谨慎,并且需要根据具体的场景和条件进行评估。如果自旋时间过长或条件不太可能很快满足,那么使用自旋状态可能会浪费大量的CPU资源。在这种情况下,更适合使用阻塞或休眠等待的方式。 406 | 407 | **总之,自旋状态是一种在等待条件满足时不进入休眠或阻塞状态的并发编程技术。它可以减少线程切换的开销,但需要权衡CPU资源的使用和等待时间的长短。** 408 | 409 | ## 11.原子操作和锁 410 | 411 | > 原子操作和锁的区别是什么? 412 | 413 | 原子操作和锁是并发编程中常用的两种同步机制,它们的区别如下: 414 | 415 | 1. 作用范围: 416 | - 原子操作(Atomic Operations):原子操作是一种基本的操作,可以在单个指令级别上执行,保证操作的原子性。原子操作通常用于对共享变量进行读取、写入或修改等操作,以确保操作的完整性。 417 | - 锁(Lock):锁是一种更高级别的同步机制,用于保护临界区(Critical Section)的访问。锁可以用于限制对共享资源的并发访问,以确保线程安全。 418 | 419 | 2. 使用方式: 420 | - 原子操作:原子操作是通过硬件指令或特定的原子操作函数来实现的,可以直接应用于变量或内存位置,而无需额外的代码。 421 | - 锁:锁是通过编程语言提供的锁机制来实现的,需要显式地使用锁的相关方法或语句来保护临界区的访问。 422 | 423 | 3. 粒度: 424 | - 原子操作:原子操作通常是针对单个变量或内存位置的操作,可以在非常细粒度的层面上实现同步。 425 | - 锁:锁通常是针对一段代码或一组操作的访问进行同步,可以控制更大粒度的临界区。 426 | 427 | 4. 性能开销: 428 | - 原子操作:原子操作通常具有较低的性能开销,因为它们是在硬件级别上实现的,无需额外的同步机制。 429 | - 锁:锁通常具有较高的性能开销,因为它们需要进行上下文切换和线程同步等操作。 430 | 431 | 综上所述,**原子操作和锁是两种不同的同步机制,用于处理并发编程中的同步问题。原子操作适用于对单个变量的读写操作,具有较低的性能开销。而锁适用于对一段代码或一组操作的访问进行同步,具有更高的性能开销。选择使用原子操作还是锁取决于具体的场景和需求。** 432 | 433 | 需要注意的是,**原子操作通常用于对共享变量进行简单的读写操作,而锁更适用于对临界区的访问进行复杂的操作和保护。在设计并发程序时,需要根据具体的需求和性能要求来选择合适的同步机制。** 434 | 435 | ## 12.Goroutine 436 | 437 | > Go语言中的goroutine是什么?请给出一个使用goroutine的示例。 438 | 439 | ### 解答: 440 | 441 | goroutine是Go语言中轻量级的并发执行单元,可以同时执行多个goroutine,而不需要显式地管理线程的生命周期。goroutine由Go运行时(runtime)进行调度,可以在并发编程中实现并行执行。 442 | 443 | ### 代码示例: 444 | 445 | 下面是一个使用goroutine的示例,计算斐波那契数列: 446 | ```go 447 | package main 448 | 449 | import ( 450 | "fmt" 451 | "sync" 452 | ) 453 | 454 | func fibonacci(n int, wg *sync.WaitGroup) { 455 | defer wg.Done() 456 | 457 | x, y := 0, 1 458 | for i := 0; i < n; i++ { 459 | fmt.Println(x) 460 | x, y = y, x+y 461 | } 462 | } 463 | 464 | func main() { 465 | var wg sync.WaitGroup 466 | wg.Add(2) 467 | 468 | go fibonacci(10, &wg) 469 | go fibonacci(5, &wg) 470 | 471 | wg.Wait() 472 | } 473 | ``` 474 | 在上述代码中,我们使用go关键字启动了两个goroutine,分别计算斐波那契数列的前10个和前5个数字。通过使用goroutine,我们可以并行地执行这两个计算任务,而不需要显式地创建和管理线程。 475 | 476 | ## 13.通道 477 | 478 | > Go语言中的通道(channel)是什么?请给出一个使用通道的示例。 479 | 480 | ### 解答: 481 | 482 | 通道是用于在goroutine之间进行通信和同步的机制。通道提供了一种安全的、阻塞的方式来发送和接收数据。通过通道,可以实现多个goroutine之间的数据传递和同步。 483 | 484 | ### 代码示例: 485 | 486 | 下面是一个使用通道的示例,计算两个数的和: 487 | ```go 488 | package main 489 | 490 | import "fmt" 491 | 492 | func sum(a, b int, c chan int) { 493 | result := a + b 494 | c <- result // 将结果发送到通道 495 | } 496 | 497 | func main() { 498 | // 创建一个整型通道 499 | c := make(chan int) 500 | 501 | // 启动一个goroutine来计算两个数的和 502 | go sum(10, 20, c) 503 | 504 | // 从通道接收结果 505 | result := <-c 506 | 507 | fmt.Println("Sum:", result) 508 | } 509 | ``` 510 | 511 | 在上述代码中,我们定义了一个sum函数,用于计算两个数的和,并将结果发送到通道c中。在main函数中,我们创建了一个整型通道c,然后启动一个goroutine来执行sum函数,并将结果发送到通道中。最后,我们通过从通道中接收结果,获取计算的和并打印出来。 512 | 513 | 通过使用通道,我们实现了goroutine之间的数据传递和同步。在示例中,通道c用于将计算结果从goroutine发送到主goroutine,实现了数据的传递和同步。 514 | 515 | ## 14.select 516 | 517 | > Go语言中的select语句是什么?请给出一个使用select语句的示例。 518 | 519 | ### 解答: 520 | 521 | select语句是Go语言中用于处理通道操作的一种机制。它可以同时监听多个通道的读写操作,并在其中任意一个通道就绪时执行相应的操作。 522 | 523 | ### 代码示例: 524 | 525 | 下面是一个使用select语句的示例,从两个通道中接收数据: 526 | 527 | ```go 528 | package main 529 | 530 | import "fmt" 531 | 532 | func main() { 533 | ch1 := make(chan int) 534 | ch2 := make(chan int) 535 | 536 | go func() { 537 | ch1 <- 10 538 | }() 539 | 540 | go func() { 541 | ch2 <- 20 542 | }() 543 | 544 | select { 545 | case num := <-ch1: 546 | fmt.Println("Received from ch1:", num) 547 | case num := <-ch2: 548 | fmt.Println("Received from ch2:", num) 549 | } 550 | } 551 | ``` 552 | 553 | 在上述代码中,我们创建了两个整型通道ch1和ch2。然后,我们启动了两个goroutine,分别向通道ch1和ch2发送数据。在主goroutine中,我们使用select语句监听这两个通道的读操作,并在其中任意一个通道就绪时执行相应的操作。在示例中,我们从就绪的通道中接收数据,并打印出来。 554 | 555 | **通过使用select语句,我们可以实现对多个通道的并发操作,并根据就绪的通道执行相应的操作。这在处理并发任务时非常有用。** 556 | 557 | ## 15.协程和通道 558 | 559 | > Go语言如何通过goroutine和channel实现并发的?请给出一个并发编程的示例。 560 | 561 | ### 解答: 562 | 563 | Go语言通过goroutine和channel实现并发。goroutine是一种轻量级的线程,可以同时执行多个goroutine,而不需要显式地管理线程的生命周期。 564 | 565 | channel是用于goroutine之间通信的管道。下面是一个简单的并发编程示例,计算斐波那契数列: 566 | 567 | ### 代码示例 568 | 569 | ```go 570 | package main 571 | 572 | import "fmt" 573 | 574 | func fibonacci(n int, c chan int) { 575 | x, y := 0, 1 576 | for i := 0; i < n; i++ { 577 | c <- x 578 | x, y = y, x+y 579 | } 580 | close(c) 581 | } 582 | 583 | func main() { 584 | c := make(chan int) 585 | go fibonacci(10, c) 586 | for num := range c { 587 | fmt.Println(num) 588 | } 589 | } 590 | ``` 591 | 592 | 在上述代码中,我们使用goroutine启动了一个计算斐波那契数列的函数,并通过channel进行通信。主函数从channel中接收计算结果并打印。通过goroutine和channel的结合,我们实现了并发计算斐波那契数列的功能。 593 | 594 | ## 16.runtime 595 | 596 | > Go语言中的runtime包是用来做什么的?请给出一个使用runtime包的示例。 597 | 598 | ### 解答: 599 | 600 | runtime包是Go语言的运行时系统,提供了与底层系统交互和控制的功能。它包含了与内存管理、垃圾回收、协程调度等相关的函数和变量。 601 | 602 | ### 代码示例: 603 | 604 | 下面是一个使用runtime包的示例,获取当前goroutine的数量: 605 | 606 | ```go 607 | package main 608 | 609 | import ( 610 | "fmt" 611 | "runtime" 612 | ) 613 | 614 | func main() { 615 | num := runtime.NumGoroutine() 616 | fmt.Println("Number of goroutines:", num) 617 | } 618 | ``` 619 | 620 | ## 17.垃圾回收 621 | 622 | > Go语言中的垃圾回收是如何工作的?请给出一个使用垃圾回收的示例。 623 | 624 | ### 解答: 625 | 626 | Go语言中的垃圾回收器(Garbage Collector)是自动管理内存的机制,用于回收不再使用的内存。垃圾回收器会自动检测不再使用的对象,并释放其占用的内存空间。 627 | 628 | ### 代码示例 629 | 下面是一个使用垃圾回收的示例,创建一个大量的临时对象: 630 | ```go 631 | package main 632 | 633 | import ( 634 | "fmt" 635 | "runtime" 636 | "time" 637 | ) 638 | 639 | func createObjects() { 640 | for i := 0; i < 1000000; i++ { 641 | _ = make([]byte, 1024) 642 | } 643 | } 644 | 645 | func main() { 646 | createObjects() 647 | time.Sleep(time.Second) // 等待垃圾回收器执行 648 | 649 | var stats runtime.MemStats 650 | runtime.ReadMemStats(&stats) 651 | fmt.Println("Allocated memory:", stats.Alloc) 652 | } 653 | ``` 654 | 655 | 打印结果: 656 | ` 657 | Allocated memory: 77344 658 | ` 659 | 660 | 在上述代码中,我们通过循环创建了大量的临时对象。然后,我们使用`time.Sleep`函数等待垃圾回收器执行。最后,我们使用`runtime.ReadMemStats`函数读取内存统计信息,并打印出已分配的内存大小。 661 | 662 | 通过使用垃圾回收器,我们可以自动管理内存,避免手动释放不再使用的对象。垃圾回收器会在适当的时机自动回收不再使用的内存,从而提高程序的性能和可靠性。 663 | 664 | ## 总结 665 | 666 | **我们在回答面试题的时候,不能干巴巴的去背八股文,一定要结合应用场景,最好能结合过去做过的项目,去和面试官沟通。** 667 | 668 | 这些场景题虽然不要求我们手撕代码,但是解决思路和关键方法还是要烂熟于心的。 669 | 670 | **这篇文章不仅给出了常见的面试题和答案,并且给出了这些知识点的应用场景、也给出了解决这些问题的思路,并且结合这些思路提供了关键代码。这些代码段都是可以直接CV到本地运行起来的,并且都写清楚了注释,欢迎大家动起手来操练起来,不要死记硬背八股文。** 671 | 672 | 最后,整理不易,原创更不易,你的点赞、留言、转发是对我最大的支持! 673 | 674 | **全网搜索:`王中阳Go`,获得更多面试题资料。** 675 | 676 | ## 欢迎关注 ❤ 677 | 678 | 我的微信:wangzhongyang1993 679 | 680 | 视频号:[王中阳Go](https://mp.weixin.qq.com/s/IUsfZGiOPtFIB1GBr10l7g) 681 | 682 | 公众号:[程序员升职加薪之旅](https://mp.weixin.qq.com/s/IUsfZGiOPtFIB1GBr10l7g) 683 | 684 | ![](https://files.mdnice.com/user/36414/55f4476f-82cd-4306-ac87-eee4928de2ed.jpeg) 685 | --------------------------------------------------------------------------------