数据结构与 方法(增删改查)
-
安装goland,注意版本是2024.1.1,不是2024.2.1,软件下载地址也在链接中提供了
-
‘go’ 不是内部或外部命令,也不是可运行的程序
或批处理文件。
在 Windows 搜索栏中输入“环境变量”,然后选择“编辑系统环境变量”或“编辑环境变量”。
在系统属性窗口中,点击“环境变量”按钮。
在“系统变量”区域,找到并选择“Path”变量,然后点击“编辑”。
在弹出的窗口中,点击“新建”,然后输入 Go 的 bin 目录的路径,例如 C:\Go\bin。
点击“确定”保存更改。
-
枚举用 iota自动就依次排号变成枚举了,不用你赋值了。枚举就是变量代替正整数
有类型的枚举的话,就不能 float64+ int了,因为有严格的类型校验,没指明类型的可以 -
字符串拼接可以用 +
数据不可变,它的值改变不了
字符串不可变: 意味着你不能改变字符串内部的单个字符。
变量可变: 变量本身可以被重新赋值,指向另一个字符串。
var newName string
newName = name + " " + "day"
fmt.Println(newName)
newName += "s"
fmt.Println(newName)
从行为上看,newName 的内容变了,但这是因为你指向了一个全新的字符串。在 Go 中,这种操作不是修改原有字符串,而是创建了一个新的字符串,并让 newName 指向这个新的字符串。原有的字符串 “lucky day” 仍然存在于内存中,直到 Go 的垃圾收集器决定回收它。
- 结构体就是复合类型的聚合
指针,如果p = &s(结构体) ,通过取地址运算符取到指针, 那么p就能像 s一样点出来所有内含的字段 - 数组
长度固定,存储同一类型的数据。数组是值传递的,不是引用传递,每次作为参数都是值传递,数组太大,复制会引起性能损耗。解决方案就是使用切片
切片 【指针,len, cap容量 cap>=len】
可以动态改变长度(每次大于容量再append元素,容量是翻倍的涨,比如1,2,4,8),一旦扩容,切片就会和原数组解绑,然后绑定到一个新数组上。频繁扩容耗费性能。所以初始化的时候,len可以设置为0,但是容量最好设置一个预估的值
len就是切片里元素的个数,
切片是对底层数组的描述的意思是当改动切片中的某个值,从切片用中括号截取的切片对应的值也会改变,它从根上就变化了。
容量cap就是你把原切片切出一部分,容量就是原容量减去中括号前面切去的就是现在的容量
- golang是编译语言,python是脚本语言
脚本语言:适用于快速开发、自动化任务、Web开发和应用程序扩展。它们的价值在于提高开发效率和灵活性。
编译语言:适用于高性能应用、系统编程、大型企业应用和安全性要求高的场景。它们的价值在于提供高效的执行速度和对底层系统的控制。
-
make 只能用于内置的切片、映射和通道类型,不能用于用户定义的类型或其他内置类型(如整型、浮点型等)。对于这些类型,你需要使用其他方式(如直接初始化或使用 new 函数)来创建和初始化变量。
-
map 哈希表或者字典,k-v。
遍历map的时候是无序的。map也是有容量的,如果频繁扩容也会造成性能消耗 -
切片(slice)、映射(map)和通道(channel)是引用类型,其他的是值类型
-
这里num的作用域就是if 函数内。err经常用这一招
-
for循环重复执行一段代码,直到不满足条件为止
for range用来遍历 切片 map 数组,string 等用的,下面是range和传统形式两种
[]string{“lucky”,“nike”,“allen”}中括号里没东西就是切片,有数值就是数组
-
包就是import那个,
package中 首字母大写的函数是可以被导出的,首字母小写的就是内部使用的
构建就是 go build xx.go,xx.go就是你的那个package xx脚本。然后就可以在命令行直接执行 ./xx 跟 go run xx.go一样的效果
如果有两个errors包(不需要go build的,脚本形式就行),只需要前面加上来源,比如github.com/pkg/errors,并且给它取个别名来解决包冲突
包名一般跟顶层文件夹名保持一致
交叉编译(Cross-Compilation)是指在一种平台上编译生成能够在另一种平台上运行的程序。也就是说,编译代码的机器和运行该代码的目标机器使用不同的操作系统或硬件架构。交叉编译通常用于开发需要在多个平台(如 Linux、Windows、macOS,或不同的处理器架构如 ARM、x86)上运行的程序。
Go 语言内置了对交叉编译的强大支持。Go 的编译器允许开发者通过设置两个环境变量GOOS
(目标操作系统)和GOARCH
(目标硬件架构)来进行交叉编译。
例如,想要在 Linux 上编译一个能在 Windows 运行的程序,开发者可以这样做:
GOOS=windows GOARCH=amd64 go build -o myapp.exe
同理,想要编译一个能在 ARM 架构的 Linux 设备上运行的 Go 程序,可以设置:
GOOS=linux GOARCH=arm64 go build -o myapp
-
函数
func init(){}
func 函数名(参数)(返回值){
函数体
defer 函数:当前函数结束时候调用 //主要用于释放资源。因为中间函数发生错误,return掉了,这时就没法执行关闭资源的代码,这时候defer就能派上用场了。比如ioutil.ReadAll(file)的时候,需要 file.Close()
} -
方法
相较于函数,多了一个 接收者 receiver,用来连接方法与类型,比如结构体的方法,不能是基础类型,int,string这些
receiver参数可以是值类型(用于只读),也可以是指针类型(读写)。
-
接口,里面有没有实现的方法。我开放了我的系统,但你实现要符合我的规则,也就是接口
接口组合就是 接口套接口或者 结构体里面套接口 -
并发与并行
同时运行多个任务,交替进行,不一定在同一时刻都在运行的是并发。 都在同一时刻运行的是并行。并行需要多核cpu或者分布式计算系统
并行那么棒,为啥需要并发
1)并发可以让 I/O 操作等待的时间里去做其他事情(其他协程或者线程切进来工作)。比如在等朋友的时候,你还可以玩玩手机,不是干等。
2)防止死锁
协程
用了协程,干一件事用10ms,干5件事也是10ms
创建协程,使用WaitGroup 等待协程结束(防止协程还没执行,main函数先执行完了,就都结束了)
解决并发同时访问一个资源不安全的问题,用mutex.Lock() 互斥锁。访问的时候变成了串行。锁的位置要放准,锁太多执行效率会下降
- channel 两个协程通信,生产者 消费者模型
数据写进channel,然后从channel里面读出来
ch:=make(chan int, 3) 这个3就使 channel变成有缓冲的了,这样有空间就往里面写。否则只能写一个,读一个,变成串行了,有缓冲就并发,处理快得多
如果无缓冲,又没人读,就变成死锁,报错,解决就是用 select。它监听多个channel,谁有数据时,就执行对应 case 分支
收发都有select。
channel 还可以执行上报心跳
-
error 处理 可预期的、可以处理的错误
透明错误 – 什么问题报什么
哨兵错误 – 比如搜数据库没有这条数据,就写入,用户看不到的,后台就把这个错误解决了
自定义错误 – error 中包含 错误码,追踪 id 等等
行为错误 – 给错误分组。比如网络错误,连接错误。对应分组有相应的应对方式 -
panic 适用于 不可恢复的错误
二、project
- Gin web框架(启动个后端,监听8080端口),在GitHub上有 安装和简单示例
postman可以测试它的接口 - 路由
Gin提供了 增删改查(PUT PATCH POST…) 等 用于处理 HTTP
CMS 内容管理
对应/api/cms/各种路由,api会单独拎出来
main函数加载路由表,route对应的url,而 services对应的是url对应的处理方法
-
中间件 剥洋葱
在请求被处理前后做一些额外的操作
比如 session 鉴权,就是postman带个session id -
模型绑定与验证 自动转换
将json转换为我们在go语言中定义的结构体
结构体的字段与json中的字段一一映射。这个往往是请求和响应两个结构体成对出现的,和客户端有来有回 -
注册接口
后端定义POST 注册接口 + handle 处理逻辑
密码加密 避免暴力破解
1)生成随机盐值,纵使输入相同的密码,得到的哈希值也是不同的
2)内部多次哈希的迭代次数就是工作因子
3)这样通过密码 存储的盐值和工作因子,就能验证密码是否正确
database tool
goland自带的工具,连接数据库,操作数据库
user.sql里面包含着表要创建成什么样子,就是sql语句的合集
在 Go 语言中,DAO(Data Access Object)模式是一种设计模式,主要用于将应用程序与底层数据存储(例如数据库)的交互逻辑分离。DAO 层负责处理与数据库的所有操作,如 CRUD(创建、读取、更新、删除)等。
GORM
就是go 的 ORM (对象关系映射)。数据库操作用的
用结构体映射数据库中的表,元素等。跟前端那个映射类似
先连上表,再增删改查
分表用重写TableName
分页查询用链式调用。比如 db.Where().Offset().Limit().Find()
model里面就是结构体们
- 登录接口
登录成功后 返回session id,并且session id持久化
redis
github上有 go-redis
也可以通过命令行redis-cli 登进去看
redis存了两个,一个是 userid : sessionid. 另一个是session_auth:sessionid : time.Now().Unix(),这个是鉴权用的
session id
用 go get github.com/google/uuid 这个库生成 session id := uuid.New().String()
鉴权
带着sessionID去访问,如果session_auth还在的话,就能通过鉴权,不用输入用户名和密码,直接进去
- 内容库
goland编辑器右键自带单元测试。验证写得函数是否正确,省的每次还得开关整个服务
查询的时候涉及到翻页逻辑
比如查哪个id,作者,title,查找范围是第几页的前多少个数据,这是前端传过去的。后端就会返回回来具体内容
- 加工流
在 Go 语言中,DAG(Directed Acyclic Graph, 有向无环图) 是一种数据结构,用于表示具有方向性的、无环的图形。它常被用于表示依赖关系、任务调度、数据流等问题,特别适合在分布式系统、工作流调度、数据处理流水线等场景中。
GoFlow 是 Go 语言中的一个框架,它利用 DAG 来处理数据流问题。它的主要功能是通过 DAG 来定义和执行一系列具有依赖关系的任务,使得数据能够沿着这些任务进行流动,帮助开发者轻松管理复杂的依赖关系和任务调度。
go get github.com/s8sg/goflow
Input —> 累加 1到10 —> 累加 100到200 —> output。
redis用于工作流临时存储
上面是简单的,下面是个更复杂的加工流
做两次累加(它们是并行,异步执行的,不一定谁先),需要都完成之后,再执行聚合的逻辑。直接调现成的方法完成聚合
也提供了条件分支现成的方法
- 自己项目的dag加工流
thumbnail是缩略图
加工流和内容创建是如何结合起来的呢?
三、微服务
大应用拆分成一组小型、自治的微服务
服务用http,rpc 通信
中间件
划分了微服务,但也需要中间件在中间辅助
- 服务注册与发现。注册进去,别人想通信就能发现
在微服务架构中,服务实例是动态的,它们可能随时上线、下线、扩缩容,因此它们的 IP 和端口会经常变化。如果没有注册中心,其他服务将无法知道这些变化,导致通信失败。
注册中心可以定期检查服务实例的健康状况。如果某个服务实例宕机或不可用,注册中心会从服务列表中移除它,防止向其发送请求。- 负载均衡:资源调度均衡
- API 网关,入口,根据path分发给不同的后端服务
- 消息队列,我把任务发出去,不关心谁来处理。谁关心谁就去处理
- 分布式缓存,比如多个后端服务公用一个缓存,后端服务又在不同的机器上
- 日志与监控
上图说明
该架构图展示了一个通过负载均衡器进行流量管理的系统,涉及到网关、内容管理、内容加工、注册中心、分布式缓存、日志监控等多个模块。具体解释如下:
-
网关:最外层的组件,负责处理来自外部的请求。网关会将请求转发到内部的不同服务,通常用来做统一入口管理、负载均衡和安全策略。
-
负载均衡器:网关接收到请求后,通过负载均衡器将流量分发到多个实例,以提高系统的处理能力。这个模块负责根据流量和服务的健康状况将请求分发到不同的“工作节点”(work)。
-
内容管理和内容加工:
- 内容管理:负责管理内容的增删改查功能,通常用于处理用户请求中的内容操作。它会分发到不同的“工作节点”(work 1, work 2, work 3),这些节点可能是多个副本,处理并行任务来分担负载。
- 内容加工:可能是对内容进行一些预处理或转换的模块。同样会分发任务给多个工作节点,以处理大量并发任务。
-
注册登录:系统的用户登录注册模块,这个模块也有多个工作节点来提供服务,通常与内容管理交互。
-
注册中心:注册中心可能是服务发现的模块,允许不同服务在集群中找到彼此的位置,确保服务间通信的正常进行。它是整个系统的中枢,确保不同模块之间的依赖关系。
-
分布式缓存:用于缓存数据,降低数据库的访问压力,提高系统性能。缓存可以加快频繁读取的内容的访问速度,减少对后端数据库的依赖。
-
日志与监控:这是系统的监控模块,负责记录日志和系统运行情况,帮助开发者和运维人员了解系统的健康状况,进行故障排查和性能分析。
-
消息队列:消息队列位于系统的最右侧,用于在模块之间传递消息。消息队列可以确保消息异步处理,提供可靠性和削峰填谷功能,常用于解耦系统的不同部分。
整条链路的流程可以总结为:
用户请求通过网关进入系统,经过负载均衡器,将请求分发到不同的工作节点进行处理(注册登录、内容管理、内容加工等)。这些节点依赖注册中心进行服务发现,并且利用分布式缓存加快访问。处理完成后,通过消息队列进行异步通信和日志记录,同时监控系统的健康状况。
这样设计的系统具有高扩展性、负载均衡能力强,支持分布式缓存和异步消息处理,适合大规模并发请求的场景。
RPC
RPC(Remote Procedure Call,远程过程调用)是一种让不同计算机上的程序像调用本地函数一样进行通信的技术,它解决了跨网络调用远程服务的复杂性。通过RPC,程序可以很容易地调用其他机器上的函数,而不必关心底层网络通信细节。
RPC 解决的问题:
-
隐藏了网络通信的复杂性:
通常,程序要在不同机器之间通信,需要处理网络连接、序列化数据、发送请求、接收响应等复杂步骤。RPC 将这些步骤隐藏起来,开发者只需调用函数,RPC 底层会自动完成数据的发送和接收。 -
让分布式系统的调用像本地调用一样简单:
在本地,调用函数只需要传入参数,得到返回值。RPC 模拟了这种方式,开发者可以用相同的方式调用远程函数,不需要手动处理跨网络的通信。 -
简化了跨服务通信:
在微服务或分布式系统中,服务通常分布在多个不同的机器上。RPC 提供了标准的调用方式,使得各个服务之间的通信变得简单、统一。
例子解释:
想象一下,你有两个程序,一个运行在电脑A上,一个运行在电脑B上。电脑A上的程序想调用电脑B上的某个函数。如果没有RPC,电脑A的程序要这么做:
- 打开一个网络连接,连接到电脑B。
- 将要调用的函数名称和参数手动打包(序列化)成网络消息。
- 将消息发送到电脑B。
- 等待电脑B处理请求。
- 接收到电脑B的响应后,再把返回结果解析出来(反序列化)。
- 关闭网络连接。
RPC 让这一切变得简单:
- 你只需要像在本地一样调用函数:
result = some_function(params)
- RPC 框架会自动处理所有的底层细节:它会在电脑A和电脑B之间建立连接,传递参数,调用电脑B上的函数,获取结果并返回给你。
举例:gRPC(现代RPC实现)
在微服务架构中,gRPC 是一种流行的 RPC 框架。比如,假设你有一个订单服务和一个支付服务:
- 当用户下单时,订单服务需要调用支付服务处理付款。
- 使用 gRPC,订单服务就像调用本地函数一样简单地调用支付服务:
payment_result = process_payment(order_details)
。 - 实际上,gRPC 会在订单服务和支付服务之间建立网络连接,发送订单信息,等待支付服务处理,并返回支付结果。
总结:
RPC 通过隐藏底层的网络通信细节,让开发者可以像调用本地函数一样轻松地调用远程服务。这大大简化了分布式系统和微服务之间的交互,提升了开发效率。
proto 是 Protocol Buffers(简称 protobuf)的核心部分。定义了数据结构和通信接口,解决了不同语言之间通信,
proto可以被编译成比 json XML 传输更快的高效二进制格式。
Protocol Buffers 支持向后兼容。其中一个服务增加了字段,其他服务仍可以继续使用旧的字段进行通信
有了这个 proto 文件后,你可以用 protobuf 编译器自动生成各个语言的代码,比如 user.pb.go(用于 Go 语言),user_pb2.py(用于 Python),user_pb.js(用于 JavaScript)
pb.go 文件是 proto 文件编译后生成的 Go 代码,它包含了 protobuf 的序列化、反序列化等操作,供 Go 程序直接调用。
kratos 开源微服务框架 哔哩公司的
Gin 主要关注于 Web 应用和 API 的高性能开发,适合构建轻量级的 Web 服务。
Kratos 主要关注于构建和管理微服务架构,提供了微服务所需的各种工具和功能,适合构建复杂的分布式系统。
当用户发起一个请求时,整个链路通常如下所示:
-
app 层(应用入口):
- 作用:应用的启动入口,主要负责启动 HTTP/gRPC 服务,加载配置文件和依赖的外部服务。服务启动后,会监听请求并将请求传递给下一层。
- 示例:当你启动 Kratos 服务时,它会通过
app
层创建 HTTP/gRPC 服务器,并注册相应的路由。比如启动一个订单服务后,API 路由会映射到/api/v1/order/create
,当客户端发出请求时,它会通过这个路由进入service
层。
-
service 层(服务层):
- 作用:接收并处理来自外部的请求。
service
层通常做一些基础的请求参数校验、转换,以及将请求数据转交给biz
层进行业务处理。 - 示例:客户端通过 POST 请求
/api/v1/order/create
创建一个订单,service
层首先会校验传入的参数,例如订单中的用户 ID、商品信息等是否完整,如果校验通过,将请求传递给biz
层。
- 作用:接收并处理来自外部的请求。
-
biz 层(业务逻辑层):
- 作用:这是业务逻辑的核心层,负责处理应用的复杂业务逻辑。
biz
层不会直接和数据库交互,而是通过调用data
层来获取和存储数据。 - 示例:在订单服务中,
biz
层负责处理订单的核心逻辑,比如检查商品库存、用户的购买力,计算订单价格等。如果库存充足且用户可以下单,biz
层会准备订单数据并准备传递给data
层进行存储。
- 作用:这是业务逻辑的核心层,负责处理应用的复杂业务逻辑。
-
data 层(数据层):
- 作用:与数据库、缓存、外部 API 等外部系统交互。它封装了所有与外部数据源的交互逻辑,保证
biz
层不直接依赖数据库或第三方 API。 - 示例:
data
层会处理数据库的读写操作,比如将订单信息写入数据库,并从库存系统或缓存中获取库存数据。如果商品库存不足,data
层会将库存不足的信息返回给biz
层。
- 作用:与数据库、缓存、外部 API 等外部系统交互。它封装了所有与外部数据源的交互逻辑,保证
-
pkg 层(工具库层):
- 作用:存放一些公共的工具函数和库,供
service
、biz
和data
层调用。 - 示例:
pkg
层可以包含一些通用的日志工具、配置工具等,确保每个层级都可以使用标准化的工具函数来记录日志或加载配置。
- 作用:存放一些公共的工具函数和库,供
-
middleware 层(中间件层):
- 作用:在请求进入
service
层之前,Kratos 提供了一层中间件,用来做一些全局处理,比如鉴权、限流、日志记录、监控等。 - 示例:每个请求在进入
service
层之前,都会经过中间件进行权限校验,比如检查用户是否已登录、是否有操作权限,或者记录日志跟踪请求的详细信息。
- 作用:在请求进入
-
conf 层(配置层):
- 作用:存储服务的配置信息,比如数据库连接、外部 API 地址等,方便应用加载和使用。
- 示例:
conf
层存储订单服务的数据库连接信息、Redis 缓存信息等,服务启动时,conf
层会读取这些配置文件并注入到应用中。
-
cmd 层(命令行工具层):
- 作用:主要用于生成服务启动入口的代码,Kratos 使用
cmd
目录的代码来启动应用。 - 示例:
cmd
层的main.go
文件是整个服务的启动点,服务的启动、配置加载、依赖注入等都是在这里进行的。
- 作用:主要用于生成服务启动入口的代码,Kratos 使用
-
test 层(测试层):
- 作用:用于编写单元测试、集成测试的代码,确保应用的各个功能模块工作正常。
- 示例:测试层可以包含模拟
service
层、biz
层、data
层交互的代码,以便验证不同模块是否正常工作。例如,测试创建订单的流程时,可以模拟调用service
层并检查订单是否成功写入数据库。
在 Kratos 框架中,在微服务之间通过 gRPC 进行通信时,proto
文件起到了非常重要的作用。它的作用主要体现在 API 层 和 服务之间通信 上。
api层生成pb.go
svc层实现,并调用biz
proto
文件的作用具体体现在哪些层?
-
API 层:
proto
文件定义的接口规范位于api
层,Kratos 项目通常将所有对外暴露的接口规范都存放在api
目录下。- 举例来说,订单服务的 API 接口就通过
proto
文件定义,其他服务调用这个订单服务时,直接按照proto
中定义的规范进行调用。
-
service 层:
service
层负责实现proto
文件中定义的接口逻辑。通过.pb.go
文件,Kratos 框架已经生成了接口的模板代码,开发者只需要在service
层实现具体的业务逻辑即可。- 例如,在
service
层实现CreateOrder
逻辑,处理订单创建请求,并将处理结果返回给客户端。
-
biz 层:
biz
层实现具体的业务逻辑,而service
层从proto
文件中生成的接口代码会调用biz
层来处理核心业务。- 比如,
CreateOrderRequest
中的user_id
和product_id
会传递给biz
层,biz
层完成订单的逻辑处理后,将处理结果返回给service
层。
在 gRPC 服务中,greeter.go 可能包含一个 SayHello 函数,当客户端调用此函数时,服务会返回一条“Hello, World”的消息。greeter.go 会调用业务逻辑层(biz)的相关逻辑来完成这类任务。
在一个用户管理系统中,repo 会包含用于访问数据库的代码,比如 GetUserByID 函数,用于根据用户 ID 查询用户信息。repo 提供接口,供 biz 层调用
wire.go
是用来定义“哪些组件需要依赖哪些东西”,相当于一份“依赖关系的规划图”。wire_gen.go
是wire
工具根据wire.go
自动生成的代码,负责实际“组装”这些依赖关系,确保应用能够顺利运行。
服务的注册与发现 ETCD
ttl 心跳,如果超过15秒没有回应,就说明它挂了,就要踢出去它
etcd监听的端口是2379,要在一台机器上把etcd启动起来
etcd 是键值对的。往etcd 里面注册
在 Kratos 中,etcd 主要用于服务注册与发现、配置管理和分布式协调等功能。它在微服务架构中帮助不同服务之间实现动态连接和配置,解决了服务间通信的可靠性和可扩展性问题。
etcd 解决的问题
假设你有一个基于 Kratos 的微服务项目,其中包含用户服务、订单服务和支付服务。每个服务部署了多个副本以提高可靠性和性能。
-
服务注册与发现:
- 当用户服务启动时,它会将自身的地址信息注册到 etcd 中。订单服务和支付服务可以通过查询 etcd 来找到最新的用户服务地址,而不用硬编码固定的 IP 或者端口。
- 当用户服务因为扩容增加了新的实例时,etcd 会自动更新其注册信息,其他服务不需要重启或修改配置,就可以动态发现这些变化。
-
配置管理:
- 假设支付服务的支付限额配置存储在 etcd 中,支付服务会定期从 etcd 获取最新的限额数据。如果你在 etcd 中修改了限额,支付服务可以监听这个配置的变化并自动更新,而不用手动重启支付服务。
-
分布式锁:
- 假设订单服务需要处理来自多个用户的大量订单请求,且为了防止并发冲突,某些资源只能被一个服务实例处理。通过 etcd,订单服务可以在操作之前获取一个分布式锁,确保其他实例不会同时处理相同的资源,保证一致性。
分库分表
为了提升查询性能和避免数据库压力,可以进行分库分表:
分表:将 orders 表按用户ID(user_id)进行拆分,比如把用户 ID 为 1-10000 的订单放在 orders_1 表中,ID 为 10001-20000 的订单放在 orders_2 表中。这样每个表的数据量减少,查询和写入效率提高。
分库:进一步地,假如你有多个数据库实例,可以把不同的表放在不同的数据库上。例如,将 orders_1 放在数据库 A,orders_2 放在数据库 B。这样,即使一个数据库实例过载,其他数据库实例仍然可以正常服务。
一致性哈希
减少节点变更时数据迁移的影响。
假设你有一个缓存服务(如分布式 Redis 集群),最初你有 3 个缓存节点,数据按 key 进行哈希分配到这些节点上。当业务增长后,你决定增加一个新的缓存节点:
传统哈希算法可能导致大量缓存数据从原来的 3 个节点被重新分配到 4 个节点上,引发大量的数据迁移,影响系统性能。
一致性哈希解决了这个问题。在一致性哈希下,新增的节点只会接管环上部分范围内的数据,只有这一部分数据会被重新分配到新节点,而其他大部分数据保持不变。
kartos项目
根据content_id映射到不同的表里面。内容id 替换掉了原来的 id
errgroup.Group是协程,查询多表的时候用这个查,快
分布式加工流 去中心化
server加工 content, redis就是缓冲区, work就是workflow