**
一、Dapr是什么
**
官方解释:Dapr (Distributed Application Runtime)是一个可移植的、事件驱动的运行时
- 可移植:指与软件从某一环境转移到另一环境下的难易程度。
- 事件驱动:调用与被调用方解耦
自己理解:Dapr为任何语言编写的应用(Any code or framwork),提供了与中间件解耦的模块
二、Dapr解决了什么问题 (SOA,MicroService)
目前微服务架构面临哪些问题
- 微服务服务拓扑复杂,调用方式不同:HTTP,MQ,RPC。需要开发人员编写不同的Proxy实现。
- 保存状态数据会使用不同组件,用Redis或者Mysql等不同存储。需要开发人员编写不同的存储类。
- 保存密钥信息可能用Azure Keyvault或者K8S Secrets等不同密钥存储。需要开发人员集成不同SDK
- 服务监测可能用不同中间件,需要开发人员侵入式编码。
开发无法专注于业务,需要花很多时间了解很多的中间件。并且很多开发无法成为各个中间件的专家,无法正确搭建、使用中间件。
三、Sidecar架构(服务网格)【Serivce Mesh】
服务注册与发现
链路追踪
服务降级,熔断,限流,重试
服务网关
……
- Sidecar为我们解决了服务调用、网络安全和分布式跟踪等功能
- Dapr以 sidecar 架构的方式公开其API,可以是容器,也可以是进程,不需要应用代码包含任何 Dapr 运行时代码。 这使得 Dapr 与其他运行时的集成变得容易,在应用逻辑层面做了隔离处理,提高了可扩展性。
四、云平台和边缘计算的微服务构建块
在设计微服务应用时,需要考虑很多因素。 Dapr提供了一些常用功能的最佳实践,开发人员可以使用标准模式进行微服务应用的构建,并部署到任意环境中。 Dapr 通过提供分布式构建块来实现此目的。
每个构建块都是独立的,这意味着您可以采用其中一个、多个或全部来构建应用。 目前,可用的构建块如下:
4.1 服务的调用
4.1.1 介绍
通过服务调用,应用程序可以使用 gRPC 或 HTTP 这样的标准协议来发现并可靠地与其他应用程序通信。
在许多具有多个需要相互通信的服务的环境中,开发者经常会问自己以下问题:
- 我如何发现和调用不同服务上的方法?
- 我如何安全地调用其他服务?
- 我如何处理重试和瞬态错误?
- 我如何使用分布式跟踪来查看调用图来诊断生产中的问题?
Dapr 通过提供服务调用 API 来应对这些问题,这种调用 API 作为反向代理与内置的服务发现相结合, 同时利用内置分布式跟踪、计量、错误处理、加密等功能。
4.1.2 调用逻辑
- 服务 A 对服务 B 发起HTTP/gRPC的调用。
- Dapr使用在给定主机平台上运行的名称解析组件发现服务B的位置。
- Dapr 将消息转发至服务 B的 Dapr 边车
注: Dapr 边车之间的所有调用考虑到性能都优先使用 gRPC。 仅服务与 Dapr 边车之间的调用可以是 HTTP 或 gRPC - 服务 B的 Dapr 边车将请求转发至服务 B 上的特定端点 (或方法) 。 服务 B 随后运行其业务逻辑代码。
- 服务 B 发送响应给服务 A。 响应将转至服务 B 的边车。
- Dapr 将消息转发至服务 A 的 Dapr 边车。
- 服务 A 接收响应。
4.1.3 特征
提供不同调用方式
- Http
- GRPC
可插拔服务发现组件 - mDNS
- K8S DNS
- Consul
服务重试
调用安全性mTLS
https://docs.dapr.io/zh-hans/developing-applications/building-blocks/service-invocation/service-invocation-overview/
4.2 状态管理
4.2.1 介绍
状态管理是任何应用程序最常见的需求之一:无论是新是旧,是单体还是微服务。 与不同的数据库库打交道,进行测试,处理重试和故障是很费时费力的。
Dapr提供的状态管理功能包括一致性和并发选项。 在本指南中,我们将从基础知识开始。使用键/值状态API来允许应用程序保存,获取和删除状态。
4.2.2 如何存储
你的应用程序可以使用Dapr的状态管理API,使用状态存储组件保存和读取键/值对,如下图所示。 例如,通过使用HTTP POST可以保存键/值对,通过使用HTTP GET可以读取一个键并返回它的值。
4.2.3 特征
可插拔状态存储
Dapr数据存储被建模为组件,可以在不修改你的服务代码的情况下进行替换。 See supported state stores to see the list.
可配置的状态存储行为
Dapr允许开发人员在对于状态的操作请求中附加额外的元数据,这些元数据用以描述期望如何处理该请求。 你可以附加以下:
- 并发要求
- 一致性要求
默认情况下,您的应用程序应该假设数据存储是最终一致的,并使用last-write-wins并发模式。
Not all stores are created equal. 为了保证应用程序的可移植性,你可以了解下存储引擎的功能,使你的代码适应不同的存储引擎。
并发(Concurrency)
Dapr支持使用ETags的乐观并发控制(OCC)。 当一个发送请求操作状态时,Dapr会给返回的状态附加一个ETag属性。 当用户代码试图更新或删除一个状态时,它应该通过更新的请求体或删除的If-Match头附加ETag。 只有当提供的ETag与状态存储中的ETag匹配时,写操作才能成功。
Dapr之所以选择OCC,是因为在不少应用中,数据更新冲突都是很少的,因为客户端是按业务上下文自然分割的,可以对不同的数据进行操作。 然而,如果你的应用选择使用ETags,请求可能会因为不匹配的ETags而被拒绝。 建议你在使用ETags时,使用重试策略来补偿这种冲突。
如果您的应用程序在书面请求中省略了ETags,Dapr会在处理请求时跳过ETags校验。 这与ETags的last-write-wins模式相比,基本上可以实现first-write-wins模式。
Note on ETags
对于原生不支持ETags的存储引擎,要求相应的Dapr状态存储实现能够模拟ETags,并在处理状态时遵循Dapr状态管理API规范。 由于Dapr状态存储实现在技术上是底层数据存储引擎的客户端,所以这种模拟应该直接使用存储引擎提供的并发控制机制。
一致性
Dapr同时支持强一致性和最终一致性,其中最终一致性为默认行为。
当使用强一致性时,Dapr会等待所有副本(或指定的quorums)确认后才会确认写入请求。 当最终使用一致性时,Dapr 将在基本数据存储接受写入请求后立即返回,即使这是单个副本。
Read the API reference to learn how to set consistency options.
批量操作
Dapr 支持两种类型的批量操作 - bulk 或 multi。 您可以将几个相同类型的请求分组成批量(或批次)。 Dapr将请求作为单个请求批量提交给基础数据存储。 换句话说,批量(bulk)操作不是事务性的。 另一方面,您可以将不同类型的请求分组为多操作,作为原子事务处理。
4.3 发布和订阅概述
4.3.1 介绍
发布 / 订阅模式 允许微服务使用消息相互通信。 生产者或发布者 将消息发送至 主题(Topic) ,并且不知道接收消息的应用程序。 这涉及将它们写入一个输入频道。 同样,一个 消费者 将订阅该主题并收到它的消息,并且不知道什么应用程序生产了这些消息。 这涉及从输出频道接收消息。 中间消息代理(intermediary message broker)负责将每条消息从输入频道复制到所有对此消息感兴趣的订阅者的输出频道。 当您需要将微服务解偶时,此模式特别有用。
Dapr 中的发布/订阅 API 提供至少一次(at-least-once)的保证,并与各种消息代理和队列系统集成。 您的服务所使用的特定实现是可插入的,并被配置为运行时的 Dapr Pub/Sub 组件。 这种方法消除了您服务的依赖性,从而使您的服务可以更便携,更灵活地适应更改。
4.4 绑定
- 除去连接到消息传递系统 ( 如队列和消息总线 ) 并进行轮询的复杂性
- 聚焦于业务逻辑,而不是如何与系统交互的实现细节
- 使代码不受 SDK 或库的跟踪
- 处理重试和故障恢复
- 构建具有特定于环境的绑定的可移植应用程序,不需要进行代码更改
4.5 Actors
4.5.1 介绍
actor 模式 阐述了 Actors 为最低级别的“计算单元”。 换句话说,您将代码写入独立单元 ( 称为actor) ,该单元接收消息并一次处理消息,而不进行任何类型的并行或线程处理。
当代码处理一条消息时,它可以向其他参与者发送一条或多条消息,或者创建新的 Actors。 底层 运行时 将管理每个 actor 的运行方式,时机和位置,并在 Actors 之间传递消息。
大量 Actors 可以同时执行,而 Actors 可以相互独立执行。
Dapr 包含专门实现 virtual actors 模式 的运行时。 通过 Dapr 的实现,您可以根据 Actors 模型编写 Dapr Actor,而 Dapr 利用底层平台提供的可扩展性和可靠性保证。
4.5.2 何时使用Actors
与任何其他技术决策一样,您应该根据您尝试解决的问题来决定是否使用 Actors。
Actor 设计模式可以很好适应一些分布式系统问题和场景,但您首先应该考虑的是模式的约束。 一般来说,在下列情况下,考虑 actor 模式来模拟你的问题或场景: - 您的问题空间涉及大量(数千或更多) 的独立和孤立的小单位和逻辑。
- 您想要处理单线程对象,这些对象不需要外部组件的大量交互,例如在一组 Actors 之间查询状态。
- 您的 actor 实例不会通过发出I/O操作来阻塞调用方。
4.5.3 Dapr 中的 Actors
每个 actor 都定义为 actor 类型的实例,与对象是类的实例的方式相同。 例如,可能存在实现计算器功能的 actor 类型,并且该类型的许多 Actors 分布在集群的各个节点上。 每个这样的 actor 都是由一个 actor ID 确定的。
4.5.3.1 Actor 生命周期
Dapr Actors 是虚拟的,意思是他们的生命周期与他们的 in - memory 表现不相关。 因此,它们不需要显式创建或销毁。 Dapr Actors 运行时在第一次接收到该 actor ID 的请求时自动激活 actor。 如果 actor 在一段时间内未被使用,那么 Dapr Actors 运行时将回收内存对象。 如果以后需要重新启动,它还将保持对 actor 的一切原有数据。
调用 actor 方法和 reminders 将重置空闲时间,例如,reminders 触发将使 actor 保持活动状态。 不论 actor 是否处于活动状态或不活动状态 Actor reminders 都会触发,对不活动 actor ,那么会首先激活 actor。 Actor timers 不会重置空闲时间,因此 timer 触发不会使参与者保持活动状态。 Timer 仅在 actor 活跃时被触发。
空闲超时和扫描时间间隔 Dapr 运行时用于查看是否可以对 actor 进行垃圾收集。 当 Dapr 运行时调用 actor 服务以获取受支持的 actor 类型时,可以传递此信息。
Virtual actors 生命周期抽象会将一些警告作为 virtual actors 模型的结果,而事实上, Dapr Actors 实施有时会偏离此模型。
在第一次将消息发送到其 actor 标识时,将自动激活 actor ( 导致构造 actor 对象) 。 在一段时间后,actor 对象将被垃圾回收。 以后,再次使用 actor ID 访问,将构造新的 actor。 Actor 的状态比对象的生命周期更久,因为状态存储在 Dapr 运行时的配置状态提供程序中(也就是说Actor即使不在活跃状态,仍然可以读取它的状态)。
4.5.3.2 分发和故障转移
为了提供可扩展性和可靠性,Actors 实例分布在整个集群中, Dapr 会根据需要自动将对象从失败的节点迁移到健康的节点。
Actors 分布在 actor 服务的实例中,并且这些实例分布在集群中的节点之间。 每个服务实例都包含给定 Actors 类型的一组 Actors。
4.5.4 Actor 安置服务 (Actor placement service)
Dapr actor 运行时为您管理分发方案和键范围设置。 这是由 actor Placement 服务完成的。 创建服务的新实例时,相应的 Dapr 运行时将注册它可以创建的 actor 类型, Placement 服务将计算给定 actor 类型的所有实例之间的分区。 每个 actor 类型的分区信息表将更新并存储在环境中运行的每个 Dapr 实例中,并且可以随着新 actor 服务实例创建和销毁动态更改。 如下图所示。
当客户端调用具有特定标识的 actor ( 例如,actor Id 123) 时,客户端的 Dapr 实例将散列 actor 类型和 Id,并使用该信息来调用相应的 Dapr 实例,该实例可以为该特定 actor Id提供请求。 因此,始终对任何给定 actor Id 始终会落在同一分区 (或服务实例) 。 如下图所示。
这简化了一些选择,但也带有一些考虑:
- 默认情况下,Actors 被随机放入分区中,从而形成均匀的分布。
- 由于 Actors 是随机放置的,因此可知,执行操作始终需要网络通信,包括方法调用数据的序列化和去序列化,产生延迟和开销。
注: Dapr actor Placement 服务仅用于 actor 安置,因此,如果您的服务未使用 Dapr Actors,那么不需要。 The Placement service can run in all hosting environments, including self-hosted and Kubernetes.
4.5.5 Actor 通信
您可以通过 HTTP/gRPC 来与 Dapr 交互以调用 actor 方法.
Copy
POST/GET/PUT/DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/<method/state/timers/reminders>
您可以在请求主体中为 actor 方法提供任何数据,并且请求的响应在响应主体中,这是来自 actor 方法调用的数据。
Refer to Dapr Actor Features for more details.
4.5.5.1 并发(Concurrency)
Dapr Actors 运行时提供了一个简单的基于回合的访问模型,用于访问 Actors 方法。 这意味着任何时候都不能有一个以上的线程在一个 actor 对象的代码内活动。 基于回合的访问大大简化了并发系统,因为不需要同步数据访问机制。 这也意味着系统的设计必须考虑到每个 actor 实例的单线程访问性质。
单个 actor 实例一次无法处理多个请求。 如果 actor 实例预期要处理并发请求,可能会导致吞吐量瓶颈。
如果两个 Actors 之间存在循环请求,而外部请求同时向其中一个 Actors 发出外部请求,那么 Actors 可以相互死锁。 Dapr actor 运行时会自动分出 actor 调用,并向调用方引发异常以中断可能死锁的情况。
4.5.5.2基于回合的访问
一个回合包括执行 actor 方法以响应来自其他 Actors 或客户端的请求,或执行 timer/reminders 回调。 即使这些方法和回调是异步的,但 Dapr Actors 运行时并没有将它们交错(Interleave ,即并发调用它们)。 在允许新回合之前,必须完全结束之前的回合。 换句话说,在允许对方法或回调进行新调用之前,必须完全完成当前正在执行的 actor 方法或 timer/reminders 回调。 如果执行从方法或回调返回结果,并且方法或回调返回的任务已完成,则方法或回调将被视为已完成。 值得强调的是,即使在不同方法、timer和回调中,基于回合的并发也一样起作用。
Dapr Actors 运行时通过在回合开始时获取每个 Actors 的锁定并在该回合结束时释放锁定来实施基于回合的并行。 因此,基于回合的并发性是按每个 actor 执行的,而不是跨 Actors 执行的。 Actor 方法和 timer/reminders 回调可以代表不同的 Actors 同时执行。
下面的示例演示了上述概念。 现在有一个实现了两个异步方法(例如,方法 1 和方法 2)、timer 和 reminders 的 actor。 下图显示了执行这些方法的时间线的示例,并代表属于此 Actors 类型的两个 Actors ( ActorId1 和 ActorId2) 的回调。
4.6 可观测性
- W3C标准分布式跟踪
- 健康监测
- 指标收集
- 日志收集
4.7 密钥管理
应用程序通常会通过使用专用的密钥存储来秘密存储敏感信息,如连接字符串、密钥和用于与数据库、服务和外部系统进行验证的令牌。
通常这需要建立一个密钥存储,如Azure Key Vault、Hashicorp 保险库和其他仓库,并在那里存储应用程序级别的密钥。 要访问这些密钥存储,应用程序需要导入密钥存储SDK,并使用它访问这些密钥。 这可能需要相当数量的模板代码,这些代码与应用的实际业务领域无关,因此在多云场景中,可能会使用不同厂商特定的密钥存储,这就成为一个更大的挑战。
让开发人员在任何地方更容易消耗应用程序密钥, Dapr 有一个专用的密钥构建块 API ,允许开发人员从一个密钥存储获得密钥。
使用 Dapr 的密钥存储构建块通常涉及以下内容:
- 设置一个特定的密钥存储解决方案的组件。
- 在应用程序代码中使用 Dapr 密钥 API 获取密钥。
- 可选,在Dapr组件文件中引用密钥。
五、Dapr部署到Kubernetes
1、集群的每一个Node配置DNS解析和地址映射
vim /etc/resolv.conf
###使用下面的dns或者其他的dns
nameserver ip
nameserver ip1### 添加地址映射 #########
vim /etc/hosts
ip2 raw.githubusercontent.com
2、k8s集群安装Dapr环境
### 初始化dapr环境 ##
dapr init -k
## 查看dapr初始化的状态######
dapr status -k
3、配置Harbor镜像仓库
vim /etc/docker/daemon.json
"insecure-registries": [
"ip" #私服镜像仓库地址
]
systemctl daemon-reload && systemctl restart docker
### 登录镜像仓库
docker login ip
username: 输入账户
password: 输入密码
4、配置dapr相关组件
4.1、部署项目需要组件
状态管理 Redis
pubsub Redis/RabbitMQ(无缝切换组件)
链路追踪 Zipkin
## redis状态存储组件
kind: Deployment
apiVersion: apps/v1
metadata:
name: redis
labels:
service: redis
spec:
replicas: 1
selector:
matchLabels:
service: redis
template:
metadata:
labels:
app: eshop
service: redis
spec:
containers:
- name: redis
image: redis:alpine
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 6379
protocol: TCP
---
kind: Service
apiVersion: v1
metadata:
name: redis
labels:
app: eshop
service: redis
spec:
type: NodePort
ports:
- port: 6379
targetPort: 6379
nodePort: 30379
protocol: TCP
name: redis
selector:
service: redis
添加dapr状态组件
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis:6379
- name: redisPassword
value: ""
- name: actorStateStore ### 在启动Actor必须配置
value: "true"
添加pubsub组件
## 部署RabbitMQ环境
kind: Deployment
apiVersion: apps/v1
metadata:
name: rabbitmq
labels:
service: redis
spec:
replicas: 1
selector:
matchLabels:
service: rabbitmq
template:
metadata:
labels:
app: eshop
service: rabbitmq
spec:
containers:
- name: rabbitmq
image: rabbitmq:3-management-alpine
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 5672
protocol: TCP
---
kind: Service
apiVersion: v1
metadata:
name: rabbitmq
labels:
service: rabbitmq
spec:
type: NodePort
ports:
- port: 5672
targetPort: 5672
nodePort: 32672
protocol: TCP
name: rabbitmq
- port: 15672
targetPort: 15672
nodePort: 32673
protocol: TCP
name: rabbitmq1
selector:
service: rabbitmq
添加dapr的rabbitmq发布订阅组件
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: host
value: "amqp://rabbitmq:5672"
添加链路追踪Zipkin组件
## 部署链接追踪应用
kind: Deployment
apiVersion: apps/v1
metadata:
name: zipkin
labels:
service: zipkin
spec:
replicas: 1
selector:
matchLabels:
service: zipkin
template:
metadata:
labels:
app: eshop
service: zipkin
spec:
containers:
- name: zipkin
image: openzipkin/zipkin-slim
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 9411
protocol: TCP
---
kind: Service
apiVersion: v1
metadata:
name: zipkin
labels:
app: eshop
spec:
type: NodePort
ports:
- port: 9411
targetPort: 9411
nodePort: 32411
protocol: TCP
name: zipkin
selector:
service: zipkin
添加dapr的链路追踪配置
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: dapr-config
spec:
tracing:
samplingRate: "1"
zipkin:
endpointAddress: "http://zipkin:9411/api/v2/spans"
4.2 部署微服务项目
kind: Deployment
apiVersion: apps/v1
metadata:
name: dapr-deploy-front
labels:
service: front
spec:
replicas: 1
selector:
matchLabels:
service: front
template:
metadata:
labels:
service: front
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "daprfrontend"
dapr.io/app-port: "80"
dapr.io/config: "dapr-config"
spec:
containers:
- name: daprfrontend
image: ip/dapr/frontend:v3
imagePullPolicy: Always
readinessProbe:
httpGet:
path: v1.0/healthz
port: 3500
initialDelaySeconds: 100
periodSeconds: 10timeoutSeconds : 30failureThreshold : 3
ports:
- name: http
containerPort: 80
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: daprfrontend
labels:
service: front
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30002
protocol: TCP
name: http
- port: 50001
targetPort: 50001
nodePort: 30041
protocol: TCP
name: dapr-grpc
selector:
service: front
附录
## 导出镜像文件
docker save > nginx.tar nginx:latest
## 导入镜像到本地镜像列表
docker load < nginx.tar
k8s安装redis的主从
1 helm安装
#根据操作系统去获取最新二进制安装包https://github.com/helm/helm/releases
wget https://get.helm.sh/helm-v3.3.1-linux-amd64.tar.gz
#由于helm包在国外,我通过ss拉到了腾讯云cos,国内可通过以下地址访问:https://download.osichina.net/tools/k8s/helm/helm-v3.3.1-linux-amd64.tar.gz
tar -zxvf helm-v3.3.1-linux-amd64.tar.gz
cp linux-amd64/helm /usr/local/bin/
helm verison # 验证是否成功
2 helm安装redis主从
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo redis
helm install redis bitnami/redis
##########################问题###############################
kubectl describe pod redis-redis-b69965b4d-466d7 可以看到 “pod has unbound immediate PersistentVolumeClaims (repeated 2 times)”
##### 创建pv
cat > redis-pv.yml << EOF
kind: PersistentVolume
apiVersion: v1
metadata:
name: pv-volume
labels:
type: local
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/root/dapr-demo/redis/data"
EOF
kubectl apply -f redis-pv.yml
下载数据解压
helm fetch bitnami/redis --untar --untardir ./