Kafka架构设计
Kafka是一个消息队列。被大家用在分布式消息队列或者流数据处理场景。
有几个问题,大家不知道思考过没有?
1、Kafka有哪些组件,作用分别是什么,为什么这么设计?
2、Kafka是怎么保证高性能、高并发、高可用的呢?
Kafka是一个消息队列。被大家用在分布式消息队列或者流数据处理场景。
有几个问题,大家不知道思考过没有?
1、Kafka有哪些组件,作用分别是什么,为什么这么设计?
2、Kafka是怎么保证高性能、高并发、高可用的呢?
经常会遇到的一个问题是数据库如何保证不丢数据? 同样的Redis如何保证不丢数据?
MySQL里有 redo log、bin log、undo log
Redis包含 rdb log、aof log
在原来coding pages服务和github pages服务都可以免费试用时,在域名解析时配置 国内访问coding pages部署的博客静态文件,国外访问github pages部署的静态文件。国内国外访问速度都基本都在200ms内。
自从 coding.net 停止免费的pages服务后,只有github pages可以免费试用,国内访问博客只能访问github pages服务,导致访问速度很慢。被人吐槽了一次,所以想优化一下。
优化思路比较简单,
1、国内ip访问国内的博客服务 比如gitee pages服务
2、通过缓存加快访问速度 比如CDN缓存
在做系统稳定性-下游用户接口的兜底时,当时有2种方案:
第一种是只校验用户uid(18位数字 long类型 8字节)是否合法,这种方案实现简单,但有安全风险;
另一种方案是缓存一份用户信息(uid、城市、运营单元、收货地址id、身份 等) 数据,用户接口异常时用缓存数据校验,这种方案实现稍微复杂点,但是没有安全风险。
当时有2亿多用户,别的组的同学缓存过全量(20多个字段)用户数据,用了200G内存。 由于占用内存太多,收益不高,他们很快就放弃缓存用户数据了。
由于我们并不需要缓存用户的20多个字段,只需要缓存6个字段,想自己计算一下+实验下看需要多大内存,如果占用内存少可以使用方案二。
Redis的IO模型
[1] Pattern: Backends For Frontends
[2] 蚂蚁财富的BFF实战
[3] 美团-GraphQL及元数据驱动架构在后端BFF中的实践
[4] BFF实践(通过网关的服务编排功能)
[5] 了解BFF架构
[6] 微服务架构 BFF和网关是如何演化出来的
相比于 Go 语言宣扬的“用通讯的方式共享数据”,通过共享数据的方式来传递信息和协调线程运行的做法其实更加主流,毕竟大多数的现代编程语言,都是用后一种方式作为并发编程的解决方案的。
我们来了解一下go中的互斥锁sync.Mutex
sync.Mutex是go语言里的一种互斥锁,是保证同步的一种工具。
类似生活中去医院看病时挂号等医生叫号的过程,有很多患者挂号(协程),只有一个医生(资源),被叫号的患者(拿到锁)可以到诊室里让医生看病。看完病离开诊室(释放锁)。
先看一个例子
package main
import "sync"
var sum int = 0
var wg sync.WaitGroup
var mu sync.Mutex
// 开10个协程并发执行1000次 sum++
// 如果不加 mu.lock() mu.unlock() 最后的结果不是10000 一般会小于10000
// 把 // mu.lock() // mu.unlock() 前面的注释删除,结果就是10000
// 这个是缓存导致的可见性问题。
// 具体原因是因为sum++不是原子操作,CPU分2条指令执行,CPU执行第2条指令时获取到的缓存sum值和主内存sum值不一致导致
func main() {
// fatal error: sync: unlock of unlocked mutex
// // mu.Unlock()
// 开10个协程并发执行1000次 sum=sum+1
for i := 0; i < 10; i++ {
wg.Add(1)
go funcAdd()
}
// 等所有协程执行完
wg.Wait()
println("result:", sum)
}
func funcAdd() {
for i := 0; i < 1000; i++ {
// 对sum加锁
//mu.Lock()
sum++
// 对sum解锁
//mu.Unlock()
//println("result=", sum)
}
// 没有下面这一行代码 提示 fatal error: all goroutines are asleep - deadlock!
wg.Done()
}
上面的代码主要功能是开10个协程并发执行1000次 sum++,并打印结果,可以自己执行一下,会发现执行的结果<10000。
把第33行 36行前的注释删掉,程序执行的结果就是结果就是10000
接口泛指实体把自己提供给外界的一种抽象化物(可以为另一实体),用以由内部操作分离出外部沟通方法,使其能被内部修改而不影响外界其他实体与其交互的方式。
比如有了type-c接口,大家都可以用type-c去充电。不用每种手机一种充电器。
电脑有了usb接口,只要是usb接口的硬件,都可以使用。
接口类型的基础知识,包括接口类型的声明、接口类型变量的定义与初始化以及类型断言
接口类型与 Go 并发语法恰分别是耦合设计与并发设计的主要参与者,因此 Go 应用的骨架设计离不开它们。一个良好的骨架设计又决定了应用的健壮性、灵活性与扩展性,甚至是应用的运行效率。
接口类型是由 type 和 interface 关键字定义的一组方法集合,其中,方法集合唯一确定了这个接口类型所表示的接口。
// Animal 动物接口
type Animal interface {
Say() string
Eat() string
}
多态是同一个行为具有多个不同表现形式或形态的能力。
比如动物都会叫,但是不同种类的动物叫的声音不一样。
Go 语言并没有设计诸如虚函数、纯虚函数、继承、多重继承等概念,但它通过接口却非常优雅地支持了面向对象的特性。
多态是一种运行期的行为,它有以下几个特点:
1、一种类型具有多种类型的能力
2、允许不同的对象对同一消息做出灵活的反应
3、以一种通用的方式对待个使用的对象
4、非动态语言必须通过继承和接口的方式来实现
type Animal interface {
Say() string
}
// dog
type Dog struct {
}
func (d Dog) Say() string {
// 汪汪
return "汪"
}
// cat
type Cat struct {
}
func (c Cat) Say() string {
// 喵喵
return "喵"
}
package main
import "fmt"
func main() {
animals := []Animal{Dog{}, Cat{}}
for _, animal := range animals {
fmt.Println(animal.Say())
}
}