career-anchor-tech

技术

分布式

为什么用分布式

分布式问题-复杂

70年代模块化编程
80年代面向事件设计
90年代基于接口/构件设计
现代 Service-Oriented Architecture面向服务的架构

SOA原则

多层架构

微服务:服务间整合通过服务编排实现,如工作流引擎、网关、容器化调度K8s

技术栈

  1. 提高性能
  1. 稳定性

关键技术

功能

CAP

  1. Consistency(一致性) :要么读取到最新数据,要么失败
  2. Availability(可用性):可读取到数据,不保证最新
  3. Partition tolerance(分区容忍性):保证服务正常运行,不管出现任何数据同步问题

弹力设计

异步,重试,幂等;熔断,限流,服务降级,服务状态;补偿事务

故障隔离

异步

系统间通讯;一个人不能同时接打很多电话,但可以接发很多邮件
同步优势:实时、系统只需要接口
同步缺点:接收请求有限(一对一,做不到一对多;吞吐量低)、调用链路长响应慢、连锁故障-耦合

异步通信方式

幂等

多次请求,只处理一次:f(x)=f(f(x))

源于
系统间调用的服务状态,timeout 不确定下游。是接收到了请求,还是收到请求是正确处理了,还是响应 成功/失败的结果时遇到网络问题

解决:接口支持幂等

HTTP的幂等性

服务状态

状态,就是程序的一些数据或上下文,如

  1. 幂等设计中的存储每次请求的唯一标识
  2. 用户登录的Session,用于判断请求合法性
  3. 服务组合的上下文Context
  4. 程序调用结果
  5. 服务配置

无状态的服务stateless
易于扩展和运维
函数思想是无状态的,只描述逻辑算法,不保存数据,不修改输入数据,返回计算结果,只要修改就要COPY
实现:服务存储分离,状态转移到 Redis/MySQL/强一致性存储/分布式文件系统;这些存储服务必须高扩展,增加缓存快速响应

有状态的服务 stateful
优势

服务状态的容错设计
数据运行时就复制给其他节点,如 Kafka、Redis、ElasticSearch,两阶段提交保证一致性
故障恢复加载大量数据会很慢,需要使用分布式文件系统,将数据持续化到硬盘,新宿主机上启动服务,可以远程挂载数据,启动时就加载大量数据,可以从其他节点只复制少量数据

补偿事务

组合服务间的强一致性一般需要底层完成,或使用 Sticky Session 在一台机器上完成;业务上大多只需要最终一致性,如果需要强一致性,用两阶段提交

关系型数据库事务 ACID,保证数据库的一致性
分布式系统中,在高性能要求下,提出BASE,允许系统出现暂时性问题,Design for Failure

ACID:同一时间不可能用多个用户下单,订单流程排队
BASE:异步批量处理订单,通知结果

日常生活也存在,当条件不满足/有变化时,需要做事务的补偿
出门旅游

技术世界中,线上运维系统要发布一个新服务或对服务水平扩展,部署系统需要管理好整个过程和相关的运行状态

业务补偿需要将服务做成幂等性的,一个事务失败或超时,不断重试,重试失败要把整个状态恢复到之前,如果请求有变化,要启动整个事务的业务更新机制

业务补偿的目标

重试

普遍的设计模式,当单体应用服务化,在一个进程内的调用变成远程调用,会涉及网络的问题。网络有很多组件:DNS、网卡、交换机、路由器、负载均衡,这些设备可能不稳定,数据传输中,只要一个环节出问题,会导致整个连接的问题

重试场景
期待:故障是暂时的
重试设计时,需要定义出重试的条件,如 调用超时,被调用端返回了可以重试的错误(繁忙中、流控中、维护中、资源不足)
不需要做重试的错误

  1. 业务错误:没有权限、非法数据
  2. 技术错误:HTTP 503-可能触发了BUG

重试策略

Spring Retry重试策略:注解配置,重试最大次数 / 超时时间内允许重试/可组合

设计重点

熔断

保护电器免被烧坏

优势

设计
熔断是 错误操作的代理,记录最近调用发生错误的次数,允许操作继续,或立即返回错误
可以使用状态机实现

熔断器设计模式在每次状态切换时会发出一个事件,这个信息可以被监控并通知到管理员

开源实例 Hystrix

设计重点

限流

对并发访问进行限速,保护系统免过载
场景

策略
一旦达到限制的速率,就触发响应的限流行为

实现方式

设计要点
目标

降级

资源不足而访问量过大的矛盾

设计要点

管理设计

分布式锁

多线程访问共享资源需要加锁,否则可能数据错误
分布式系统下需要分布式的锁,可以用 DB、Redis 实现,要求

Redis锁
加锁
set resource_name my_random_value NX PX 30000
解锁

// 只解自己加的锁
if redis.call("get",KEYS[1]) == ARGV[1] then 
	return redis.call("del",KEYS[1])
else 
	return 0
end	

锁的值

乐观锁到CAS
数据库也保留版本号
update xxx set xxx=xx version=version+1 where version = #{version}
这是乐观锁最常用的实现方式,可以不用分布式锁

也可以 fence token,更新提交时检查数据库时间戳,和自己更新前取到的时间戳对比

设计重点

配置中心

软件配置

分布式系统下,使用集中式的配置中心
设计

边车模式

通过给一个三轮车添加边车的方式,扩展现有的服务和功能,实现控制和逻辑的分离
服务中做好业务逻辑,由边车实现控制功能:监视、日志记录、限流、熔断、服务注册、协议适配转换

设计
边车是服务的Agent,服务的对外通信通过Agent实现,Agent和服务一起创建、停用

设计重点

服务网格

Service Mesh:Sidecar 集群
只要 Service Mesh 做好,只需要加入应用服务就好了
Service Mesh:一个基础设施,轻量级的服务通信的网络代理,对应用透明无侵入,解耦分布式中控制面的东西;像TCP协议,管理 丢包重传、拥塞控制、流量控制;

开源软件Conduit:Rust负责数据、Go负责控制;其核心的 Sidecar 叫 Envoy(使者)

K8s 和 Docker 进程的失败不会导致应用的异常运行,但 Service Mesh 不行,因为调度了流量,需要高可靠,且故障有workaround 方式:本机 Sidecar , 集中 Sidecar(Gateway实现),本机出问题走集中
Service Mesh 独立部署,而 Sidecar 和应用一起部署。Service Mesh 可以更好的和 K8s 配合

网关模式

一级 Gateway 接入所有流量,分发给子系统 (和面向对象中的Facade很像)
二级 Gateway 接入子系统流量
Gateway 封装内部系统架构,提供API给客户端。还可以做 鉴权、监控、负载均衡、缓存、熔断降级限流,请求分片和管理,静态响应处理
设计

设计重点

部署升级策略

灰度、AB测试 选择用户

性能设计

缓存

使用原因:加速数据访问
数据库四种操作

缓存模式

缓存设计重点

异步处理

异步利于统一规划,达到整体最优

异步任务处理设计

事件溯源
数据库中一个数据的值不知道是怎么得出来的,可以像银行存折一样,追加记录操作内容,也能看到每笔记录后的余额

分布式事务
强一致的场景不多,可以使用异步达到一致性

数据库扩展

读写分离
一个写库,两个读库:所有服务写一个数据库;服务A、B走从库A,服务D、E走从库B,服务C在从库A和从库B中做轮询
优势

CQRS Command and Query Responsibility Regregation 命令与查询职责分离

分库分表 Sharding
影响数据库最大的两个问题

分库

还应考虑 应用程序的业务要求及数据使用模式

设计重点

秒杀

商家低价促销
流程

挑战:倒计时按钮和按钮可以被点击的时间需要后端校准,一旦后端表示OK,会返回一个URL,可以点击
应对100w人同时下单的请求,可能宽带不够、需要很多机器、请求集中在同一条数据库记录

解决方案:CDN边缘节点抗流量,限流用户请求

双十一,想尽可能地卖出商品,要多收订单、不超库存、银行支付、库存查询和分配,就需要做高并发的架构和测试,分布式弹力设计做好

有时,边缘化方案更好,尤其是地域特征的业务,如 外卖、共享单车、打车

边缘计算

数据中心:把所有服务放在一个机房中集中处理用户请求

边缘计算产生

业务场景

编程范式

通过了解主流编程语言的特性,总结编程语言的本质,知道什么样的代码编写方法可以写出更通用,可重用,便于组合扩展的代码。

C语言是最长久的语言,几乎现在看到的语言都是以C语言为基础扩展来的,它的优点是:

缺点是:不利于代码组织和功能编程,而我们需要业务抽象型的语言,花更多时间解决业务问题而不是计算机问题。算法要通用,即实现抽象隔离,让世界变得简单
一个通用算法要适配所有数据类型+数据结构,但C语言:

适用于:开发运行快,对资源利用率高的程序,底层灵活(直接操作内存)且高效

类型和泛型

《C++语言的设计和演化》

C++对C的改进

C++ 泛型实现:
泛型的抽象:数据类型要符合通用算法,最小需求是什么?

类型系统和泛型
类型:将 数值和表达式 归类,定义操作方式
编程语言一般两种类型

编程语言类型可以保障

泛型的本质

泛型的本质:屏蔽数据和操作数据的细节,让算法更为通用,让开发者更多关注算法结构,而不是类型处理

函数式

编程工作更多的是解决业务问题。函数式编程使用函数拼接业务逻辑

函数式编程
函数:定义输入数据和输出数据的关系,即映射 mapping

特征

函数式编程

# 3辆车有70%的概率移动一步,一共5次机会
# 普通编程
from random import random
 
time = 5
car_positions = [1, 1, 1]
 
while time:
    # decrease time
    time -= 1
 
    print ''
    for i in range(len(car_positions)):
        # move car
        if random() > 0.3:
            car_positions[i] += 1
 
        # draw car
        print '-' * car_positions[i]


# 函数式:没有临时变量,没有共享变量,通过参数和返回值来传递函数
from random import random
 
def move_cars(car_positions):
	# `car_positions` 数组中的每个元素 `x`,如果随机生成的一个小数大于 0.3,则将 `x` 加一;否则保持不变
    return map(lambda x: x + 1 if random() > 0.3 else x,
               car_positions)
 
def output_car(car_position):
    return '-' * car_position
 
def run_step_of_race(state):
    return {'time': state['time'] - 1,
            'car_positions': move_cars(state['car_positions'])}
# 每辆车的位置信息转成字符串,用换行符连接后打印 
def draw(state):
    print ''
    print '\n'.join(map(output_car, state['car_positions']))
 
def race(state):
    draw(state)
    if state['time']:
        race(run_step_of_race(state))
 
race({'time': 5,
      'car_positions': [1, 1, 1]})

# 管道,平铺函数调用,避免嵌套函数
# 数组中N个数字:filter 偶数,每个数字*3,打印
pipeline_func(nums, [even_filter,
                     multiply_by_three,
                     convert_to_string])
# pipeline 函数可以实现成
def pipeline_func(data, fns):
    return reduce(lambda a, x: x(a),   fns,   data)
    

函数三件套

修饰器
和Java Annotation 很像,扩展现有函数功能,内部函数作为参数传递给外部函数,由外部函数来调用,实现函数的组合拼接,适用于分离非业务,控制型(如 for-loop、函数路由、日志打印、求函数运行时间)的代码

def hello(fn):
	def wrapper():
		print "hi,%s" % fn._name_
		fn()
		print "bye,%s" % fn._name_
	return wrapper

@hello
def A():
	print "I am A"
	
A()

# 输出
# hi,A
# I am A
# bye,A

面向对象

函数式编程不处理状态,面对对象来接收、处理、转发数据

面向对象三大特性

桥接模式:四个物体:木桌子,木椅子,塑料桌子,塑料椅子;四个属性:燃点,密度,价格,重量
Material材质类(Wood木,Plastic塑料):燃点、密度;Furniture家具类(Table桌子,Desk椅子):价格、体积;Furniture构造函数中定义具体的Material是Wood还是Plastic;于是,根据密度和体积,可以计算出价格

策略模式(最经典的设计模式):电商订单打折HappyHourStrategy还是不打折NormalStrategy
interface BillingStrategy { double getActPrice( double rowPrice ); } class NormalStrategy/HappyHourStrategy impl BillingStrategy
class OrderItem{ billStrategy; }
class Class { orderItems; add() payBill( orderItems.for.billStrategy.getActPrice ) }
分离定价策略和订单处理流程,配置不同商品使用不同的价格计算策略
现实中还会有会员价,打折卡,商品打包价,可以使用函数式pipeline来实现

代理模式:try finally 的资源释放

IOC控制反转:通过一种标准,让业务更规范

  1. Switch开关{ Light }控制Light灯
  2. 开关声明电源接口,灯和电视适配电源接口 ISwitchable { turnOn{} turnOff{} } Switch{ ISwitchable } Light/TV impl ISwitchable

优点

基于原型编程

直接使用对象,主流语言是 JavaScript
基于类的语言关注类和类间关系,基于原型的语言关注对象实例的行为之后才关注如何将对象划分到最近的使用方式相似的原型对象
基于原型的对象提倡运行时修改原型
构造对象:1. 复制已有的对象 2. 扩展空对象

JavaScript的原型

Go语言的委托模式

编程本质和逻辑

Programs=Algorithms ↑ (Logic + Control ↑)+ Data Structure

Logic 和 Control 分离,代码会更易改进和维护

分离 Logic 和 Control :

编程范式分类:声明式what,面向对象how
人类左脑

区块链

机器学习

分类

基本方法

算法
监督学习

课程、图书

数据安全

故障处理

故障发生时:定位、恢复

故障前

复盘