自学习内容

需要学习的地方:
老系统 网关服务与多个服务器之间的调度 gate center login game

中心服务 拥有网关服务 登陆服务 游戏服务 连接 可以暂时理解为 中心化部署的分布式服务 可动态添加网关服务 以及各个网关主机服务所拥有的 登陆和游戏服务器

gate 连接 中心服务
db 注册rpc
center 中心服务 启动rpc服务 http服务 开启其他服务注册监听
login 初始化nats 注册到center

2021.10.12

开始首局游戏的时候,预留客户端加载资源时间 6s

引用外部包的时候时候可以更改别人包中代码,只要本地一直有修改过的版本即可,否则拉取的是外部包最新代码,没有自己修改过的版本内容。

nats相关文档

mysql 57与8.0版本差异
速度 默认字符集格式

什么算作代码整洁,代码整洁与性能优先考虑点 可阅读代码整洁之道 寻求答案

为什么系统中同时存在nsq和nats的使用 (nats不支持持久化)

有时间可以阅读的源码库:
cache2go 临时内存缓冲使用
gob rpc数据传输封装

Go源码 尽快找时间阅读

archive
bufio
bulitin
bytes
cmd
compress
container
context
crypto
database
debug
encoding
errors
expvar
flag
fmt
go
hash
html
image
index
internal
io
log
math
mime
net
os
path
plugin
reflect
regexp
runtime
sort
strconv
strings
sync
syscall
testdata
testing
text
time
unicode
unsafe
vendor

2021.11.23

数据结构:

算法:

面试题相关问题

1、golang 中 make 和 new 的区别?(基本必问)

​ make 用于slice map channel 类型的创建以及初始化,返回的是对应类型

​ new 用于分配内存空间,返回对应类型的内存指针地址

2、数组和切片的区别 (基本必问)

​ 数组是固定长度的,值类型

​ 切片是可变长度的,引用类型

3、for range 的时候它的地址会发生变化么?

​ 会,for range 的时候操作的是拷贝的数据

​ for range 对map类型遍历时候,改变map中键值的话,会影响到输出结果,可能输出改变后的值,也可能不输出或少输出

4、go defer,多个 defer 的顺序,defer 在什么时机会修改返回值?

​ a.多个defer会倒叙返回,相当于压栈 弹栈操作;

坑1:defer在匿名返回值和命名返回值函数中的不同表现

要搞清这个问题首先需要了解defer的执行逻辑,文档中说defer语句在方法返回“时”触发,也就是说return和defer是“同时”执行的。以匿名返回值方法举例,过程如下。

  • 将result赋值给返回值(可以理解成Go自动创建了一个返回值retValue,相当于执行retValue = result)
  • 然后检查是否有defer,如果有则执行
  • 返回刚才创建的返回值(retValue)

在这种情况下,defer中的修改是对result执行的,而不是retValue,所以defer返回的依然是retValue。在命名返回值方法中,由于返回值在方法定义时已经被定义,所以没有创建retValue的过程,result就是retValue,defer对于result的修改也会被直接返回。

坑2:在for循环中使用defer可能导致的性能问题

defer在紧邻创建资源的语句后生命力,看上去逻辑没有什么问题。但是和直接调用相比,defer的执行存在着额外的开销,例如defer会对其后需要的参数进行内存拷贝,还需要对defer结构进行压栈出栈操作。所以在循环中定义defer可能导致大量的资源开销,在本例中,可以将f.Close()语句前的defer去掉,来减少大量defer导致的额外资源消耗。

坑3:判断执行没有err之后,再defer释放资源

一些获取资源的操作可能会返回err参数,我们可以选择忽略返回的err参数,但是如果要使用defer进行延迟释放的的话,需要在使用defer之前先判断是否存在err,如果资源没有获取成功,即没有必要也不应该再对资源执行释放操作。如果不判断获取资源是否成功就执行释放操作的话,还有可能导致释放方法执行错误。

坑4:调用os.Exit时defer不会被执行

当发生panic时,所在goroutine的所有defer会被执行,但是当调用os.Exit()方法退出程序时,defer并不会被执行。

5、 uint 类型溢出

​ 数值类型溢出的时候,会重置为0

6、介绍 rune 类型

​ rune 相当于int32,特殊的字节类型 可以表示中文字符

​ uint8 表示一般字符

2021.11.23

设计模式:

开闭原则:对拓展开放,对修改关闭。

里氏换元原则:父类可调用的方法,子类也可调用。继承

OOP 四大特性:封装、继承、多态、抽象

创建型模式:创建型模式关注点是如何创建对象,其核心思想是要把对象的创建和使用相分离,这样使得两者能相对独立地变换。

  1. 工厂模式:对象的创建和使用分离。

  2. 抽象工厂模式:通过传递参数获取实体类的对象。意图提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

  3. 单例模式:实例只创建一次,常存在,可直接调用

  4. 生成器模式(建造者模式):多个简单的对象构建成一个复杂的对象。

  5. 原型模式:用于创建重复的对象,同时又能保证性能

行为型模式:行为型模式主要涉及算法和对象间的职责分配。通过使用对象组合,行为型模式可以描述一组对象应该如何协作来完成一个整体任务。

  1. 策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
  2. 观察者模式(Observer)又称发布-订阅模式(Publish-Subscribe:Pub/Sub)。它是一种通知机制,让发送通知的一方(被观察方)和接收通知的一方(观察者)能彼此分离,互不影响。
  3. 状态模式:对象不同状态之间的切换。

2022.03.10

数据结构:

  1. 数组:

​ 查找时间复杂度 O(1)

​ 修改时间复杂度O(n)

  1. 链表:

​ 查找时间复杂度 O(n)

​ 修改时间复杂度O(1)

2022.06.07

找时间整一个批量更新文件的脚本 便于更新测试服和正式服

温习一下容器相关内容,虽然本地不能部署容器 但是有时间还是可以玩玩的。

2022.06.10

linux备份策略:

  1. 完全备份;
    • 每次都备份全部内容;
  2. 累计增量备份;
    • 每次只备份相对比前一次备份增加的内容;
  3. 差异增量备份;
    • 每次只备份相对比第一次备份增加的内容;

2022.07.20

go语言高级编程:书籍阅读

第一章:

数组字符串和切片:

  1. 数组:

数组的长度由下标值决定。

1
2
3
4
var a [3]int                    // 定义长度为3的int型数组, 元素全部为0
var b = [...]int{1, 2, 3} // 定义长度为3的int型数组, 元素为 1, 2, 3
var c = [...]int{2: 3, 1: 2} // 定义长度为3的int型数组, 元素为 0, 2, 3
var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func TestCode(t *testing.T) {
m := [...]int{
'a': 1,
'b': 2,
'c': 3,
}
m['a'] = 3
fmt.Println(len(m))

// 我们知晓 c 的 ASCII 码是 99,这道题相当于这样
/* m := [...]int{
97: 1,
98: 2,
99: 3,
}
m[97] = 3
fmt.Println(len(m)) */
}
  1. 切片:

使用append 时候尽量避免扩容

由于append函数返回新的切片,也就是它支持链式操作。我们可以将多个append操作组合起来,实现在切片中间插入元素:

1
2
3
var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片

对切面元素进行操作的时候可以使用copyappend组合

可以用copyappend组合可以避免创建中间的临时切片,同样是完成添加元素的操作

1
2
3
a = append(a, 0)     // 切片扩展1个空间
copy(a[i+1:], a[i:]) // a[i:]向后移动1个位置
a[i] = x // 设置新添加的元素
  1. 避免切片内存泄漏

如前面所说,切片操作并不会复制底层的数据。底层的数组会被保存在内存中,直到它不再被引用。但是有时候可能会因为一个小的内存引用而导致底层整个数组处于被使用的状态,这会延迟自动内存回收器对底层数组的回收。

例如,FindPhoneNumber函数加载整个文件到内存,然后搜索第一个出现的电话号码,最后结果以切片方式返回。

1
2
3
4
func FindPhoneNumber(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return regexp.MustCompile("[0-9]+").Find(b)
}

这段代码返回的[]byte指向保存整个文件的数组。因为切片引用了整个原始数组,导致自动垃圾回收器不能及时释放底层数组的空间。一个小的需求可能导致需要长时间保存整个文件数据。这虽然这并不是传统意义上的内存泄漏,但是可能会拖慢系统的整体性能。

要修复这个问题,可以将感兴趣的数据复制到一个新的切片中(数据的传值是Go语言编程的一个哲学,虽然传值有一定的代价,但是换取的好处是切断了对原始数据的依赖):

1
2
3
4
5
func FindPhoneNumber(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = regexp.MustCompile("[0-9]+").Find(b)
return append([]byte{}, b...)
}

类似的问题,在删除切片元素时可能会遇到。假设切片里存放的是指针对象,那么下面删除末尾的元素后,被删除的元素依然被切片底层数组引用,从而导致不能及时被自动垃圾回收器回收(这要依赖回收器的实现方式):

1
2
var a []*int{ ... }
a = a[:len(a)-1] // 被删除的最后一个元素依然被引用, 可能导致GC操作被阻碍

保险的方式是先将需要自动内存回收的元素设置为nil,保证自动回收器可以发现需要回收的对象,然后再进行切片的删除操作:

1
2
3
var a []*int{ ... }
a[len(a)-1] = nil // GC回收最后一个元素内存
a = a[:len(a)-1] // 从切片删除最后一个元素

当然,如果切片存在的周期很短的话,可以不用刻意处理这个问题。因为如果切片本身已经可以被GC回收的话,切片对应的每个元素自然也就是可以被回收的了。