go map

go map 的值传递

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
package main

import "fmt"

func main() {
m := map[string]int{"hello": 1}
changeMapM(m)
fmt.Printf("After changeMapM, m1 is: %v\n", m)

n := map[string]int{"hello": 1}
changeMapN(n)
fmt.Printf("After changeMapN, m1 is: %v\n", n)
}

func changeMapM(m map[string]int) {
m["hello"] = 2
}

func changeMapN(n map[string]int) {
n = map[string]int{"hello": 2}
}

// 可以看到changeMapN并没有改变n的值,所以map是值传递
// output:
// After changeMapM, m1 is: map[hello:2]
// After changeMapN, m1 is: map[hello:1]

在 Go 语言中,map 是引用类型,但在函数参数传递时,仍然是按值传递的。这意味着当你将一个 map 传递给函数时,传递的是 map 引用的副本,而不是原始的引用。因此,在函数内部对 map 引用本身的重新赋值不会影响到原始的 map。

解释

changeMapM 函数:

传递的是 map 引用的副本。
在函数内部,通过引用修改 map 中的值,这会影响到原始的 map,因为引用指向的是同一个底层数据结构。

changeMapN 函数:

传递的是 map 引用的副本。
在函数内部,重新赋值 n,这只是改变了 n 引用的副本指向一个新的 map,不会影响到原始的 map。

解释

changeMapM 函数:

m[“hello”] = 2:修改了 map 中的值,因为 m 引用指向的是原始的 map。

changeMapN 函数:

n = map[string]int{“hello”: 2}:重新赋值 n,使其指向一个新的 map,但这不会影响到原始的 map,因为 n 只是引用的副本。

总结

在 Go 语言中,map 是引用类型,但函数参数传递是按值传递的。
在函数内部对 map 引用本身的重新赋值不会影响到原始的 map。
要修改原始的 map,需要通过引用修改其内容,而不是重新赋值引用。

go map 存储

在 Go 语言中,map 的底层实现使用了桶(bucket)来存储键值对。当桶中的键值对数量超过一定限制时,会创建溢出桶(overflow bucket)。此外,当 map 中的键值对数量增加到一定程度时,map 会进行扩容,以保持较低的哈希冲突率和较高的性能。

创建溢出桶(overflow bucket)
溢出桶是在以下情况下创建的:

桶已满:每个桶有一个固定的容量(通常是 8 个键值对)。当一个桶中的键值对数量超过这个容量时,会创建一个溢出桶来存储额外的键值对。
哈希冲突:当多个键的哈希值映射到同一个桶时,会发生哈希冲突。如果桶已满且发生哈希冲突,会创建溢出桶来存储冲突的键值对。

扩容

map 会在以下情况下进行扩容:

负载因子超过阈值:负载因子是 map 中键值对的数量与桶的数量之比。当负载因子超过某个阈值(通常是 6.5)时,map 会进行扩容。扩容时,map 的桶数量会增加一倍,以减少哈希冲突并提高性能。
溢出桶过多:如果 map 中有太多的溢出桶,说明哈希冲突较多,map 也会进行扩容,以减少溢出桶的数量。

总结
溢出桶:当桶中的键值对数量超过容量或发生哈希冲突时,会创建溢出桶。
扩容:当负载因子超过阈值或溢出桶过多时,map 会进行扩容,以减少哈希冲突并提高性能。
这些机制确保了 map 在存储大量键值对时仍能保持较高的性能和较低的哈希冲突率。

参考

If a map isn’t a reference variable, what is it?

there-is-no-pass-by-reference-in-go

https://dave.cheney.net/2015/12/07/are-go-maps-sensitive-to-data-races

https://victoriametrics.com/blog/go-map/#map-anatomy

be a software engineer

How to be a software engineer

计算机三大事: 计算,网络,存储。你要在这上面下狠功夫。

Don’t feel you aren’t smart enough

Successful software engineers are smart, but many have an insecurity that they aren’t smart enough.

必备

https://github.com/jwasham/coding-interview-university/blob/main/translations/README-cn.md

https://roadmap.sh/backend?r=backend-beginner

https://gitstar-ranking.com/repositories

网络编程实战

数据结构与算法之美

MySQL 实战 45 讲

Kafka 核心技术与实战

Redis 核心技术与实战

设计模式之美

算法

leetcode 150 题

Hello 算法

吴师兄学算法

漫画算法

春水煎茶

visual 可视算法运行过程

network

https://github.com/clowwindy/Awesome-Networking

https://github.com/kowsertusher/Book

https://csdiy.wiki/en/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/topdown/

https://friskfrysefrugt.github.io/awesome-networking/

https://www.practicalnetworking.net/series/packet-traveling/packet-traveling/

others

Software Engineering at Google

https://google.github.io/building-secure-and-reliable-systems/raw/toc.html

https://blog.uxtly.com/freebsd-jails-network-setup

https://news.ycombinator.com/item?id=27650775

go slice

https://victoriametrics.com/blog/go-slice/index.html

https://pkg.go.dev/unsafe#SliceData

slice 在传递给函数的过程中,如果没有指定内存地址传递,则传递的是 slice 的一个副本,
如果你希望改变 slice 的值,最好传递内存地址。

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
package main

import "fmt"

func main() {
s := []int{1, 2, 3}
changeSlice(s)
fmt.Printf("s address: %p\n", &s)
fmt.Println("s is ", s)

fmt.Println("==change slice by reference==")

t := []int{1, 2, 3}
changeSliceByReference(&t)
fmt.Printf("t address: %p\n", &t)
fmt.Println("t is ", t)
}

func changeSlice(s []int) {
s[0] = 100
s = append(s, 200, 300)
fmt.Printf("s address: %p\n", &s)
}

func changeSliceByReference(slice *[]int) {
(*slice)[0] = 100
*slice = append(*slice, 200, 300)
fmt.Printf("t address: %p\n", slice)
}

/*
* output
* s address: 0xc000010030
* s address: 0xc000010018
* s is [100 2 3]
* ==change slice by reference==
* t address: 0xc000010060
* t address: 0xc000010060
* t is [100 2 3 200 300]
*/

利用go build命令可以查看变量是否逃逸到heap

1
2
3
4
5
6
7
8
9
10
11
12
package main

func main() {
a := [10 * 1024 * 1024]byte{}
println(&a)

b := [10*1024*1024 + 1]byte{}
println(&b)
}

// 执行命令 go build -gcflags="-m" slice_in_go.go
// output: ./slice_in_go.go:7:2: moved to heap: b

tips

  1. 初始化 slice 的时候最好预估数据量大小来设置 capacity,来避免频繁的创建新数组和复制数据。

  2. 修改 slice 最好传递引用,因为对于 int, string, array, slice 这些数据类型都是 passed-by-value 的形式传递参数的。

  3. In Go, everything is passed by value.

参考

there-is-no-pass-by-reference-in-go

kafka

kafka

https://towardsdev.com/kafka-101-a-beginners-guide-to-understanding-kafka-2cd797864614

/images/kafka.webp

https://highscalability.com/untitled-2/

Kafka 重平衡策略:深入解读

什么是 Kafka 重平衡?

Kafka 集群中的分区(Partition)会不断地重新分配给不同的 Broker,以保证数据的高可用性和负载均衡。这个过程就叫做重平衡

为什么需要重平衡?

  • 负载均衡: 保证每个 Broker 上的分区数量大致相等,避免单个 Broker 过载。
  • 故障恢复: 当 Broker 发生故障时,其上的分区会重新分配到其他 Broker 上。
  • 拓扑变更: 当集群中的 Broker 增加或减少时,需要重新分配分区以适应新的集群拓扑。

Kafka 的重平衡策略

Kafka 的重平衡策略主要由 消费者组 (Consumer Group) 来控制。每个消费者组都有一个唯一的组 ID,组内的消费者共同消费一个或多个主题。

  • 消费者组的协调者: 每个消费者组都会选举出一个协调者(Coordinator),负责管理该组的重平衡过程。
  • 重平衡触发条件:
    • 消费者加入或退出组: 当有新的消费者加入或已有消费者离开组时,会触发重平衡。
    • 分区数量发生变化: 当主题的分区数量发生变化时,也会触发重平衡。
    • 消费者配置发生变化: 消费者配置的改变,例如订阅主题的变化,也会触发重平衡。
  • 重平衡过程:
    1. 协调者发起重平衡: 协调者向组内所有消费者发送重平衡请求。
    2. 消费者准备重平衡: 消费者收到请求后,会停止消费消息,并进入重平衡准备状态。
    3. 分配分区: 协调者根据一定的分配策略,将所有分区重新分配给组内的消费者。
    4. 消费者提交偏移量: 消费者提交最新的偏移量,以便在重平衡完成后从正确的位置开始消费。
    5. 消费者开始新一轮消费: 消费者从新分配的分区中获取最新的偏移量,并开始消费。

Kafka 的重平衡策略分类

  • Range 分配策略: 将分区按照范围均匀地分配给消费者。
  • Round-robin 分配策略: 轮询的方式将分区分配给消费者。
  • 自定义分配策略: 用户可以自定义分配策略,以满足特定的业务需求。

重平衡对应用的影响

  • 性能影响: 重平衡期间,消费者会停止消费消息,这会导致应用的延迟增加。
  • 数据一致性: 重平衡可能会导致数据重复消费或丢失,需要采取相应的措施来保证数据一致性。

如何优化重平衡

  • 减少重平衡次数: 避免频繁地修改消费者组配置或分区数量。
  • 增加消费者数量: 增加消费者数量可以减少每个消费者需要处理的分区数量,从而提高消费性能。
  • 优化自定义分配策略: 根据业务需求,设计合理的自定义分配策略,以提高重平衡效率。
  • 使用事务: 利用 Kafka 的事务特性,保证在重平衡期间数据的准确性。

总结

Kafka 的重平衡机制是保证集群高可用性和负载均衡的关键。通过了解重平衡的原理和影响因素,我们可以更好地优化 Kafka 集群的性能,并保证应用的稳定性。

如何避免重平衡

https://dzone.com/articles/kafka-streams-tips-on-how-to-decrease-rebalancing

https://dzone.com/articles/kafka-streams-tips-on-how-to-decrease-rebalancing

https://medium.com/trendyol-tech/rebalance-and-partition-assignment-strategies-for-kafka-consumers-f50573e49609

一致性hash算法

一致性 hash 算法主要应用在分布式系统中,试想 nginx 中配置的负载均衡策略是 hash,
即同一 IP 的请求都分发到同一台机器上,那么在添加一台或下线一台服务器后,假设你用的是
session 保存用户态的方式,你觉得它会出问题吗

实际上在 nginx 中也支持用一致性 hash 来避免这个问题

1
2
3
4
5
6
upstream my_upstream {
server 127.0.0.1:8080 weight=1;
server 127.0.0.2:8080 weight=1;
# 表示使用请求URI进行一致性哈希。
hash $request_uri consistent;
}

参考

https://zh.wikipedia.org/wiki/%E4%B8%80%E8%87%B4%E5%93%88%E5%B8%8C

https://gallery.selfboot.cn/zh/algorithms/hashring

https://leehao.me/%E4%B8%80%E8%87%B4%E6%80%A7Hash-Consistent-Hashing-%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90/

https://mp.weixin.qq.com/s/yimfkNYF_tIJJqUIzV7TFA

awesome js

浏览器工作原理

https://developer.mozilla.org/zh-CN/docs/Web/HTTP

https://developer.mozilla.org/zh-CN/docs/Glossary/TCP

vanilla-js 是纯粹的 js,你可以从 vanilla.js 开始学习,而不是一开始就学习框架
https://github.com/snipcart/learn-vanilla-js

https://github.com/wesbos/JavaScript30

https://github.com/bradtraversy/vanillawebprojects

https://www.solidjs.com/

Svelte 是一个编译型的前端框架,与传统的 React、Vue 等虚拟 DOM 框架不同,Svelte 将大部分工作放在了编译阶段。它会将你的 Svelte 组件编译成高效的 JavaScript 代码,直接操作 DOM,从而提高应用的性能。

https://github.com/sveltejs/svelte

/images/js框架比较.png

Next UI

https://ui.shadcn.com/

https://nextui.org/