云原生模式

云原生模式 1、什么是云原生 云原生软件是高度分布式的,必须在一个不断变化的环境中运行,而且自身也在不断地发生变化。 云与云原生 迁移到云上并不意味着你的软件就是云原生的,也不会具有云原生软件的价值。 “云”是指我们在哪里计算,而“云原生”指的是如何实现。 最终一致性是许多云原生模式的核心,这意味着当我们需要强一致性时,就不能使用这些新模式了。 云原生软件 云原生软件的设计目的是预测故障,并且即使当它所依赖的基础设施出现故障,或者发生其他变化时,它也依然能够保持稳定运行。 失败是正常规律,而不是例外 4、事件驱动 首先,在左边有两个参与方,微服务的客户端和微服务本身,二者互相依赖。在右边,只有一个参与方,这一点很重要。微服务被作为一个事件的结果而执行,但是触发该事件的原因与微服务无关。因此,这个服务的依赖更少。 事件驱动架构在很大程度上是为了解决系统过于紧耦合的问题而设计的。 对于请求/响应的方式,聚合发生在用户发出请求的时候。而对于事件驱动的方式,聚合发生在系统中数据发生变化的时候,并且这是异步的。 考虑到事件驱动的协议本质上是异步的,解决相同问题的补偿机制可能会非常不同。在这个架构中, 你会使用诸如RabbitMQ或者Apache Kafka之类的消息队列系统,来保持网络分区中的事件。你的服务应该实现能够支持此架构的协议,例如,通过一个循环来不断地检查事件存储中是否有感兴趣的新事件。 8、动态路由和服务发现 动态路由 客户端负载均衡 服务端负载均衡 基于k8s的服务端负载均衡 服务发现 CoreDNS DNS潜在问题 DNS服务本身就是一个多实例的、分布式的系统。DNS服务通常被配置为支持可用性而不是一致性。即符合最终一致性。使用DNS服务时,作为一个开发者,你必须考虑到,通过DNS服务获取到的ip地址可能是过时的。 客户端请求DNS服务时,可能得到过时的服务ip。这种情况下,在请求重试失败了几次之后,你可以再次向DNS询问一个IP地址,由于在此期间DNS被更新,现在是一致的,你得到一个新的IP地址。 客户端请求DNS服务时,得到过时的服务ip,恰好这个ip正在被另一个服务使用。为解决这种情况,服务实现或部署必须有访问控制机制,以便不允许未经授权的访问。 9、交互中的客户侧 重试 重试风暴? 为什么重试风暴会导致请求积压,并且服务恢复后需要花费一定时间消化? Posts服务不可用时,引发重试风暴 Posts服务恢复后,已经产生了积压的流量,需要时间来消减: 重试的好处 特别是对于间歇性的连接问题,重试往往会奏效,从而扼杀了一个错误,否则这个错误可能会通过构成我们云原生软件的分布式系统广泛传播。 重试策略优化 限制重试次数、每次重试之间加入一定延迟 不适合场景 例如银行卡余额减去100 点击购买按钮后没有收到回应 回退 10、交互中的服务侧 一些放在真实服务前的前置服务,熔断器、API网关 熔断器 好处 在一个复杂的分布式系统中,延迟是灾难性的,而断路器大大减少了其长度和频率。 当服务不可用时,断路器大大减少了等待时间 实现 开源库:github.com/sony/gobreaker type CircuitBreaker struct { name string maxRequests uint32 //半开启下,最大请求数;也是CB由半开启-->关闭转变的条件(连续成功请求数达到maxRequests,关闭CB) interval time.Duration//关闭状态下,cb清除内部Counts的循环周期。 timeout time.Duration//开启状态维持时间,超过进入半开启状态 readyToTrip func(counts Counts) bool//CB由关闭到开启的条件(可自定义) isSuccessful func(err error) bool //请求成功的标志(可自定义),默认err==nil onStateChange func(name string, from State, to State) mutex sync.
▶️ Read more gq

go之字符

编码规范 ASCII-单字节 ASCII 编码方案使用单个字节(byte)的二进制数来编码一个字符。标准的 ASCII 编码用一个字节的最高比特(bit)位作为奇偶校验位,而扩展的 ASCII 编码则将此位也用于表示字符。ASCII 编码支持的可打印字符和控制字符的集合也被叫做 ASCII 编码集。 ASCII 将每个英文字符以及数字和符号映射到 32 - 127 范围内的数字。这意味着每个字符都可以用单个字节(8 位)表示。低于 32 的代码被称为不可打印并用作控制字符,例如,7 使您的计算机发出哔哔声,10 用于 LF(换行)。 Unicode-多字节 我们所说的 Unicode 编码规范,实际上是另一个更加通用的、针对书面字符和文本的字符编码标准。它为世界上现存的所有自然语言中的每一个字符,都设定了一个唯一的二进制编码。 它定义了不同自然语言的文本数据在国际间交换的统一方式,并为全球化软件创建了一个重要的基础。 Unicode 编码规范以 ASCII 编码集为出发点,并突破了 ASCII 只能对拉丁字母进行编码的限制。它不但提供了可以对世界上超过百万的字符进行编码的能力,还支持所有已知的转义序列和控制代码。 将字符抽象为Unicode code point 包含地球上所有合理书写系统的单一字符集(ASCII 和 127 之前的 Unicode 彼此完全相似) String: H e l l o Unicode code point:U+0048 U+0065 U+006C U+006C U+006F Unicode 编码规范提供了三种不同的编码格式,即:UTF-8、UTF-16 和 UTF-32。其中的 UTF 是 UCS Transformation Format 的缩写。而 UCS 又是 Universal Character Set 的缩写,但也可以代表 Unicode Character Set。所以,UTF 也可以被翻译为 Unicode 转换格式。它代表的是字符与字节序列之间的转换方式。
▶️ Read more gq

go之timer

Timer 数据结构 type timer struct { //timer所在的处理器P pp puintptr when int64 period int64 f func(any, uintptr) arg any seq uintptr nextwhen int64 //最大触发时刻 1<<63 - 1 status uint32 } type p struct { timersLock mutex timers []*timer } 实现 Go 1.9 版本之前(全局四叉堆) 实现: 全局四叉堆+timerproc Goroutine loop check 缺点: 全局唯一的互斥锁,严重影响计时器的性能 Go 1.10 ~ 1.13(分片四叉堆) 实现: 64组(四叉堆+timerproc Goroutine loop check) 缺点: CPU 密集计算任务会导致 timer 唤醒延迟 将全局计时器分片的方式,虽然能够降低锁的粒度,提高计时器的性能,但是timerproc Goroutine造成的频繁的上下文切换却成为了影响计时器性能的首要因素 Go 1.14 版本之后(与P绑定) 原本用于管理计时器的 runtime.timerproc 也已经被移除,目前计时器都交由处理器的网络轮询器和调度器触发,这种方式能够充分利用本地性、减少上下文的切换开销,也是目前性能最好的实现方式。
▶️ Read more gq

go之netpoller

I/O 模型 现代的网络服务的主流已经完成从 CPU 密集型到 IO 密集型的转变,所以服务端程序对 I/O 的处理必不可少,而一旦操作 I/O 则必定要在用户态和内核态之间来回切换。 阻塞 I/O (Blocking I/O) 非阻塞 I/O (Nonblocking I/O) I/O 多路复用 (I/O multiplexing) 信号驱动 I/O (Signal driven I/O) 异步 I/O (Asynchronous I/O) 操作系统上的 I/O 是用户空间和内核空间的数据交互,因此 I/O 操作通常包含以下两个步骤: 等待网络数据到达网卡(读就绪)/等待网卡可写(写就绪) –> 读取/写入到内核缓冲区 网卡<–>内核缓冲区 从内核缓冲区复制数据 –> 用户空间(读)/从用户空间复制数据 -> 内核缓冲区(写) 内核缓冲区<–>用户空间 而判定一个 I/O 模型是同步还是异步,主要看第二步:数据在用户和内核空间之间复制的时候是不是会阻塞当前进程,如果会,则是同步 I/O,否则,就是异步 I/O。基于这个原则,这 5 种 I/O 模型中只有一种异步 I/O 模型:Asynchronous I/O,其余都是同步 I/O 模型。 由于使用 epoll 的 I/O 多路复用需要用户进程自己负责 I/O 读写,从用户进程的角度看,读写过程是阻塞的,所以 select&poll&epoll 本质上都是同步 I/O 模型,而像 Windows 的 IOCP 这一类的异步 I/O,只需要在调用 WSARecv 或 WSASend 方法读写数据的时候把用户空间的内存 buffer 提交给 kernel,kernel 负责数据在用户空间和内核空间拷贝,完成之后就会通知用户进程,整个过程不需要用户进程参与,所以是真正的异步 I/O。
▶️ Read more gq

go之同步原语

同步原语与锁 Mutex 互斥锁的演变史 初版 // CAS操作,当时还没有抽象出atomic包 func cas(val *int32, old, new int32) bool func semacquire(*int32) func semrelease(*int32) // 互斥锁的结构,包含两个字段 type Mutex struct { key int32 // 锁是否被持有的标识 sema int32 // 信号量专用,用以阻塞/唤醒goroutine } // 保证成功在val上增加delta的值 func xadd(val *int32, delta int32) (new int32) { for { v := *val if cas(val, v, v+delta) { return v + delta } } panic("unreached") } // 请求锁 func (m *Mutex) Lock() { if xadd(&m.key, 1) == 1 { //标识加1,如果等于1,成功获取到锁 return } semacquire(&m.
▶️ Read more gq