2023年4月

刚接触线程的时候懵懵懂懂,懵懵逼逼,只是依稀记得线程需要同步,至于这么做的原因好像是避免线程由于对数据的竞争导致不可预知的结果。随着头发日渐稀疏,对线程同步的理解也不断加深了。

什么是线程同步


线程同步是指多个线程之间的协调同步,按照一定的次序进行执行。
Linux中的线程同步机制主要有互斥锁、自旋锁、读写锁和条件变量四种。互斥锁与自旋锁在使用形式上比较类似,都是前一个线程在加锁后会阻止后来想要加锁线程被阻塞或者返回错误。我们可以把读写锁比作前两者的延伸,这个机制允许不同线程在同一读写锁上添加读锁,不允许在同一读写锁上添加写锁,而且读写锁相互排斥,有了读锁的读写锁不允许再添加写锁,有了写锁的读写锁也不允许再添加读锁。在条件变量中,被条件变量阻塞的线程需要另一线程释放信号来唤醒。

为什么需要线程同步


当一个线程被创建后,我们就失去了干涉该线程运行的权利。如果不加干涉地让线程自由运行,可能会在某些情境下引发很多问题。以下是由于线程不同步而常见的问题:

数据竞争
:当多个线程对同一共享数据进行读写操作时,由于并发执行的随机性,会导致它们的操作出现冲突,从而导致数据的混乱与不一致。

死锁
:当多个线程同时竞争多个共享资源时,由于资源的有限性以及线程不合理的发生顺序,会导致线程的相互等待,产生死锁情况。

饥饿
:当某些线程始终无法获得对共享资源的访问权时,就可能导致饥饿的情况,这些线程会一直处于等待状态,无法执行下去。。

如何选择同步机制


互斥锁与自旋锁在处理的场景上大致相同,不过由于其实现方式而引发了一些差异。互斥锁无法获得锁时,会进入
阻塞等待
状态,而自旋锁会一直
循环检查
所是否可用,而并不会让线程进入阻塞状态。在整个运行过程中,使用互斥锁会在用户态与内核态中切换,而自旋锁只在用户态中运行。

以上的差异导致了这两种锁在应用场景的差异。互斥锁适合等待比较长的场景,因为线程在等待锁的过程中会进入阻塞状态,不会消耗CPU资源;而自旋锁适用于县城的等待时间比较短的情景,因为县城在等待锁的过程会一直检查锁是否可用,会消耗CPU资源。

在高并发的场景下,当多个线程在竞争同一个锁,如果使用自旋锁,自旋锁会不断询问锁的执行情况,占用大量的CPU资源。相对而言,如果使用互斥锁,当一个线程获取锁失败时会进入阻塞状态,放弃CPU的运行,直到该锁可用并被唤醒后再继续执行。因此,在高并发场景中,并且锁的竞争比较激烈的时候,使用互斥锁比使用自旋锁更有效,因为这样可以节省CPU资源,提高系统的并发性能。

读写锁其实可以看做互斥锁的一种特殊情景,我们对于数据或资源的使用无外乎读取与修改,但是当一群线程对该资源或数据的使用仅限于正确的读取,那么使用互斥锁就有些大动干戈了,我们只需要保证该数据或资源的一致性,而不必要求其它读取线程的等待锁的释放。所以说,使用读写锁可以提高系统在处理读取任务时的效率。

以上三种锁,虽然在实现与使用上有些差异,但是仍然可以把它们看做一个爹妈生的,不过条件变量就不同了。如果站在锁的角度来衡量以上四种方法,在前三者方法通过用锁制约其它线程,而自己也被锁锁限制,对于条件变量的信号发送线程来说,这波它站在大气层,它就像远程的管理着锁的开闭,自己却不会受到这把“锁”的直接影响。以前觉得前三者在编程中只要不在乎效率,完全可以相互替代,那条件变量存在的意义是什么呢,思考后我发现前三者中线程的关系是平等的,它们公平的去竞争;而条件变量中信号的发起线程像是一个主宰,决定着其它线程的运行状态。

竞争就好了?为什么要专政呢?

用现实生活中的例子来看,如果一群人相互竞争,谁也不服谁,那他们注定不能变的井井有条,如果是单独的任务还好,一旦遇上需要合作的情况,这时候就需要一个独裁者来统筹规划,来指明方向。再来用编程的角度来看,通过互斥的竞争手段可以保护共享资源,使得同一时间只有一个线程共享访问资源,而条件变量可以用来协调线程的行为,它让一个线程等待另一个线程的通知,从而协调线程执行的顺序和进度。

以上的讨论在大方向上给出了线程同步的用法,在使用的细节上也给大家提出些许建议。应该减少不必要的加锁时间,我们在使用锁的过程中主要有初始化、加锁、解锁和释放锁几个步骤,在代码段中的加锁与解锁的位置会直接影响该线程拥有锁的时间,从而对整体代码的效率造成影响,我们要避免将无关的代码放入代码块中,从而缩短锁的范围和时间。

锁要留给谁


我们通过编程可以决定锁由谁加,不过当一个锁被释放时正好有多个线程在等待锁,接下来锁会分配给谁则是操作系统的调度算法来实现的。

类比众多调度算法所考虑的那样,都是在效率与公平间取得平衡,锁的继承也不例外。对锁而言,公平性是指所有线程都有机会获得锁,而不是让某些锁永远没有得到锁的机会;效率是指尽可能减少线程的等待时间。

比较常见的调度算法是先进先出调度,即按照线程等待锁的先后顺序,依次将锁分配给等待时间最久的线程,另一种算法是将锁分配给还没有进入阻塞状态的线程。前者可以保证线程使用锁的公平性,后者则是通过减少阻塞与就绪态的切换来提高系统效率。

什么是SwaggerHub?

Hub 谓之
中心
, 所以 SwaggerHub即swagger中心.

什么时候需要它?

通常, 公司都拥有多个服务, 例如商品服务, 订单服务, 用户服务, 等等, 每个服务都有自己的environment, endpoint, swagger schema. 然而这些信息都分散在各处, 如果能集中在一个地方展示出来, 就能减少许多无意义的沟通协作, 另外也可以有更有全局视野查看整个公司的API's.

成熟的商业产品.

例如
https://swagger.io/tools/swaggerhub/
, 不光可以做Hub, 还有很多其他的管理功能, 实时的编辑器, 版本管理等等.
商业产品功能很好, 但是要钱.
所以... 我们可以...

使用 Swashbuckle.Swagger 搭建SwaggerHub.

在NetCore的世界里, 我们可以使用
Swashbuckle.AspNetCore
来构建一个我们自己的SwaggerHub. 而且特别简单, 我们仅需要一行代码即可

var swaggerUIOptions = new SwaggerUIOptions();
swaggerUIOptions.ConfigObject.Urls = new[] {
    new UrlDescriptor() {
        Name = "swagger name",
        Url= "swagger.json"
    }
};

app.UseSwaggerUI(swaggerUIOptions);

我们只需要配置
Urls
option, 增加多个swagger json 配置就完事儿了, 如此, Hub就完成了. 本文章到这里也就算完事儿了.
剩下的就是根据公司情况如同, 采用的方案不同而要解决的一些实际问题了.

对swaggerUIOptions的改动是实时生效的.


swaggerUIOptions
对象的任何更改都是实时生效的, 所以我们可以做到只要改配置即可实时生效.

Url 可以配置为一个endpoint, 直接由swaggerui在浏览器中下载指定的文件.

new UrlDescriptor(){Url="https://www.cnblogs.com/swagger.json"}

Url 也可以是在任何地方的一个文件

//配置url从当前项目的一个地址下载文件.
new UrlDescriptor(){Url="/swagger-file/swagger.json"}

// 从本地读取swagger文件
[HttpGet("/swagger-file/{swaggerName}.json")]
public IActionResult GetSwaggerFileAsync([FromRoute] string swaggerName)
{
    return this.File($"static-file-dir/swaggers/{serviceName}.json", "application/json");
}

当然, 我们也可以读取任何地方的swagger文件, 例如在github, 各种云存储(aws/s3, aliyun/oss)等等.

为每一个swagger配置server地址.

每个swagger可能代表这不同的服务, 大概率就有不同的endpoint, 也可以是多个环境配置地址(dev,uat,staging,pro).
给swagger.json增加servers节点即可.

{
    "servers":["server-endpoint1","server-endpoint2"]
}

可以使用各个JSON组件动态插入, 也可以用
Microsoft.OpenApi.Readers
组件来解析和改写所有swagger的内容

var doc = new OpenApiStreamReader().Read(sourceSwaggerJson, out _);
doc.Servers.Add(new OpenApiServer() { Url = "my-dev-server-endpoint" });
doc.Servers.Add(new OpenApiServer() { Url = "my-pro-server-endpoint" });
string swaggerJsonContent = targetDoc.SerializeAsJson(Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0);

Microsoft.OpenApi.Readers
可以用来解析openAPI 格式的文档. 支持v2,v3等版本, 支持json,yaml格式. 详情可查看官方文档. 所以这个netcore 的 swaggerhub 自然而然的就支持读取任何语言支持的openAPI文档(java, nodejs, 等等).

合并多个swagger文档到一个swagger文档

单一的一个服务由多个不同的服务提供服务支持. 举个例子, 商品服务由商品服务+商品搜索服务共同组成, 2个单独的服务由2个单独的team负责维护, 但是对外提供服务的时候暴露在同一个domian下, 根据path route到不同的服务上. 这个时候我们还是使用
Microsoft.OpenApi.Readers
来做合并这个事情.

//demo代码
var productDoc= download();
var productSearchDoc= download();
var targetDoc = new OpenApiDocument() { Components = new(), Paths = new() };

targetDoc.Paths.Add(productDoc.Paths)
targetDoc.Paths.Add(productSearchDoc.Paths)
targetDoc.Components.Schemas.Add(...)
//巴拉巴拉
string swaggerJsonContent = targetDoc.SerializeAsJson(Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0);

上面只是列出了我碰到的几个具体情况, 不同的公司不同的场景下还有更多可能的case. 这个就得case by case了. 但是一个万变不离其宗, 总之就是对openAPI生成是swagger文件进行自定义. 这个时候用
Microsoft.OpenApi.Readers
就完事了.

综上,
SwaggerHub, SwaggerUI 用
Swashbuckle.AspNetCore
搭建.
OpenAPI Swagger Doc 用
Microsoft.OpenApi.Readers
做定制化修改.

技术背景

分布式服务治理

所谓分布式服务治理就是对服务不断增长的复杂度的管控和管理;管控及管理包含网络拓扑变动、网络延时、通信安全、API网关、服务注册和发现、服务熔断容错、服务超时重试、服务部署、数据调用、分布式链路追踪等等;

服务治理历程

第一代服务治理(内嵌应用程序中)

提示:第一代服务治理能力的代码是内嵌在业务代码中,即所有的服务治理功能需要程序员自己编写;典型技术:SOA、ESB;这种服务治理的优势就是简单使用依赖少;劣势是代码耦合,重复性较高,运维复杂,解耦差,开发要求高;

第二代服务治理(统一抽象成SDK)

提示:第二代服务治理是把服务治理能力抽象到统一SDK实现,开发人员可以根据SDK中的api来写业务代码,从而使对应业务具有服务治理的功能;典型技术:Spring Cloud、Dubbo等;这种服务治理的优势是代码重复少,治理逻辑代码和业务代码分开;劣势是SDK语言绑定,代码侵入;基于SDK开发学习门槛较高;系统改造代价大,治理能力升级影响用户业务(即SDK升级,会导致业务代码的升级);

第三代服务治理(统一到Sidecar)

提示:第三代服务治理能力统一到服务网格上;即服务治理的能力,通过在业务代码周边运行一个独立的sidecar来完成;程序员只需要关注自己的业务代码的开发,服务治理能力就交给sidecar服务网格来完成;第三代服务治理的优势是独立进程,用户业务非侵入,开发和语言无关(只要能够完成对应的业务功能,用什么语言都可以);治理逻辑升级对业务没有影响;可以渐进的微服务化;劣势就是性能和资源的开销;

服务网格(Service Mesh)
服务网格概念源于Buoyant公司的CEO Willian Morgan的文章“What's a service mesh? And do I need one?”;是指专注于处理服务间通信的基础设施,它负责在现代云原生应用组成的复杂拓扑中可靠地传递请求;治理模式除了处理业务逻辑的相关功能外,每个微服务还必须实现此前单体应用模型中用于网络间通信的基础功能,甚至还包括分布式应用程序之间的通信环境中应该实现的其它网络功能,例如熔断、限流、应用跟踪、指标采集、服务发现和负载均衡等;
服务网格的基本功能
控制服务间通信: 熔断、重试、超时、故障注入、负载均衡和故障转移等;

服务发现:通过专用的服务总线发现服务端点;

可观测:指标数据采集、监控、分布式日志记录和分布式追踪;

安全性:TLS/SSL通信和密钥管理;

身份认证和授权检查:身份认证,以及基于黑白名单或RBAC的访问控制功能;

部署:对容器技术的原生支持,例如Docker和Kubernetes等;

服务间的通信协议:HTTP 1.1、HTTP 2.0和gRPC等;

健康状态检测:监测上游服务的健康状态;
Service Mesh的雏形

提示:在微服务体系架构中,我们为每个服务都使用一个专用的代理Sidecar来完成高级网络功能;各服务间仅通过Sidecar代理互相通信,各个代理代理之间形成一个网状网络,2017年,William为其创建一个专用的定义,并称之为Service Mesh;

提示:新一代服务网格架构分为控制平面和数据平面两个部分;数据平面触及系统中的每个数据包或请求,负责服务发现、健康检查、路由、负载均衡、身份验证/授权和可观测性等;控制平面主要为网格中的所有正在运行的数据平面提供策略和配置,从而将所有数据平面联合构建为分布式系统,它不接触系统中的任何数据包或请求;控制平面负责的任务包括例如确定两个服务Service X到Sevice Y之间的路由,Service Y相关集群的负载均衡机制、断路策略、流量转移机制等,并将决策下发给Service X和Service Y的Sidecar;

控制平面组件

工作负载调度程序:借助于底层的基础设施(例如kubernetes)完成服务及其Sidecar运行位置的调度决策;

服务发现:服务网格中的服务发现;

Sidecar代理配置API:各Sidecar代理以最终一致的方式从各种系统组件获取配置;

控制平面UI:管理人员的操作接口,用于配置全局级别的设置,例如部署、身份认证和授权、路由及负载均衡等;
Service Mesh解决方案极大降低了业务逻辑与网络功能之间的耦合度,能够快捷、方便地集成到现有的业务环境中,并提供了多语言、多协议支持,运维和管理成本被大大压缩,且开发人员能够将精力集中于业务逻辑本身,而无须再关注业务代码以外的其它功能;一旦启用Service Mesh,服务间的通信将遵循以下通信逻辑;
1、微服务彼此间不会直接进行通信,而是由各服务前端的称为Service Mesh的代理程序进行;
2、Service Mesh内置支持服务发现、熔断、负载均衡等网络相关的用于控制服务间通信的各种高级功能;
3、Service Mesh与编程语言无关,开发人员可以使用任何编程语言编写微服务的业务逻辑,各服务之间也可以使用不同的编程语言开发;
4、服务间的通信的局部故障可由Service Mesh自动处理;
5、Service Mesh中的各服务的代理程序由控制平面(Control Plane)集中管理;各代理程序之间的通信网络也称为数据平面(Data Plane);
6、部署于容器编排平台时,各代理程序会以微服务容器的Sidecar模式运行;
服务网格和K8S间的关系

提示:k8s主要负责解决容器编排与调度的问题,本质上是应用程序生命周期工具,为服务网格提供基础支撑;Service Mesh 主要解决分布式应用间的通信问题,本质上服务通信治理工具,是k8s在网络功能方面的扩展和延伸;

Istio是什么?

Istio是Envoy Data Plane的控制平面实现之一,Istio是一个开源的独立服务网格,可为用户成功运行分布式微服务架构提供所需的基础设施;Istio可以轻松创建带有负载均衡、service-to-service的身份认证、细粒度的可观测性等功能的服务网格,而应用程序代码却无须或很少为些而作出改变;通过在整个服务环境中为每一个应用部署一个特殊的Sidecar形式的Proxy拦截各服务之间的所有网络通信,并由控制平面Istio进行配置和管理,进而为服务无侵入式添加如下功能;
1、HTTP、gRPC、WebSocket和TCP通信的自动负载均衡;
2、通过丰富的路由规则、重试、故障转移和故障注入对流量进行细粒度控制;
3、支持访问控制、速率限制和配额的可插拔的策略层及配置API;
4、集群内所有流量的自动度量、记录日志和分布式跟踪跟踪,包括集群的入口和出口;
5、强大身份验证和授权,以及在群集中进行安全的服务间通信;
在kubernetes环境中,服务网格就像一个仪表板,用于解决问题、执行流量策略、分配限制和测试代码,它允许一个中央节点来监视、跟踪和控制所有服务之间的交互;
Istio系统架构

提示:Istio服务网格主要有两部分组成,控制平面和数据平面;控制平面核心程序istiod,主要是用于管控数据平面envoy proxy;数据平面envoy proxy主要管控主容器的进出流量;envoy proxy由控制平面istiod下发配置实现流量管控,同时它还能够发现服务网格中的其他配置和服务,也能够收集对应的指标数据;

istio体验环境部署

环境说明

kubernetes:v1.26.3

docker :23.0.1

cri-dockerd:0.3.0

istio:1.17.1

下载istio客户端程序包

root@k8s-master01:/usr/local# curl -L https://istio.io/downloadIstio | sh -
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   102  100   102    0     0    100      0  0:00:01  0:00:01 --:--:--   100
100  4856  100  4856    0     0   2347      0  0:00:02  0:00:02 --:--:--  9429

Downloading istio-1.17.1 from https://github.com/istio/istio/releases/download/1.17.1/istio-1.17.1-linux-amd64.tar.gz ...

Istio 1.17.1 Download Complete!

Istio has been successfully downloaded into the istio-1.17.1 folder on your system.

Next Steps:
See https://istio.io/latest/docs/setup/install/ to add Istio to your Kubernetes cluster.

To configure the istioctl client tool for your workstation,
add the /usr/local/istio-1.17.1/bin directory to your environment path variable with:
         export PATH="$PATH:/usr/local/istio-1.17.1/bin"

Begin the Istio pre-installation check by running:
         istioctl x precheck 

Need more information? Visit https://istio.io/latest/docs/setup/install/ 
root@k8s-master01:/usr/local# 

提示:使用上述命令,请先确定你的终端是否能够正常连接到istio的官方网站;

创建链接,将istioctl命令链接至/usr/bin/目录下

root@k8s-master01:/usr/local# ls
bin  etc  games  include  istio-1.17.1  lib  man  sbin  share  src
root@k8s-master01:/usr/local# ln -sv istio-1.17.1 istio
'istio' -> 'istio-1.17.1'
root@k8s-master01:/usr/local# ls
bin  etc  games  include  istio  istio-1.17.1  lib  man  sbin  share  src
root@k8s-master01:/usr/local# cd istio
root@k8s-master01:/usr/local/istio# ls
bin  LICENSE  manifests  manifest.yaml  README.md  samples  tools
root@k8s-master01:/usr/local/istio# ln -sv /usr/local/istio/bin/istioctl /usr/bin/
'/usr/bin/istioctl' -> '/usr/local/istio/bin/istioctl'
root@k8s-master01:/usr/local/istio# 

提示:链接至/usr/bin/目录下是因为后续方便istioctl命令的使用;

列出profile

root@k8s-master01:/usr/local/istio# cd
root@k8s-master01:~# istioctl profile list
Istio configuration profiles:
    ambient
    default
    demo
    empty
    external
    minimal
    openshift
    preview
    remote
root@k8s-master01:~# 

提示:测试环境我们使用demo即可,生产环境使用default;

安装istio

root@k8s-master01:~# istioctl install --set profile=demo -y
✔ Istio core installed                                                                                                                     
✔ Istiod installed                                                                                                                         
✔ Egress gateways installed                                                                                                                
✔ Ingress gateways installed                                                                                                               
✔ Installation complete                                                                                                                    
Making this installation the default for injection and validation.

Thank you for installing Istio 1.17.  Please take a few minutes to tell us about your install/upgrade experience!  https://forms.gle/hMHGiwZHPU7UQRWe9

验证:istio-system名称空间中是否有pod running?

root@k8s-master01:~# kubectl get pods -n istio-system
NAME                                   READY   STATUS    RESTARTS   AGE
istio-egressgateway-774d6846df-fv97t   1/1     Running   0          21m
istio-ingressgateway-69499dc-pdgld     1/1     Running   0          21m
istiod-65dcb8497-9skn9                 1/1     Running   0          26m
root@k8s-master01:~# 

验证:istio 版本信息

root@k8s-master01:~# istioctl version 
client version: 1.17.1
control plane version: 1.17.1
data plane version: 1.17.1 (2 proxies)
root@k8s-master01:~# 

提示:可以看到现在数据平面有两个代理,这是因为我们安装istio时,在istio-system名称空间下运行了一个ingressgateway和egressgateway pod,这两个pod就是istio的数据平面,istiod是控制平面;

查看istio-system名称空间下创建的服务资源

提示:可以看到ingressgateway 处于pending状态,这是因为没有设置外部IP地址;

修改ingressgateway网关地址

~# kubectl edit svc istio-ingressgateway -n istio-system

提示:在spec字段下,找一个位置加上externalIPs字段,来指定一个IP地址即可;这个IP地址必须是集群节点空余IP地址;

验证:查看istio-system名称空间下的svc 看看对应ingressgateway 外部IP地址是否修改?

root@k8s-master01:~# kubectl get svc -n istio-system                      
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)                                                                      AGE
istio-egressgateway    ClusterIP      10.106.179.126   <none>          80/TCP,443/TCP                                                               34m
istio-ingressgateway   LoadBalancer   10.102.211.120   192.168.0.252   15021:32639/TCP,80:31338/TCP,443:30597/TCP,31400:31714/TCP,15443:32154/TCP   34m
istiod                 ClusterIP      10.96.6.69       <none>          15010/TCP,15012/TCP,443/TCP,15014/TCP                                        39m
root@k8s-master01:~# 

提示:可以看到ingressgateway 对应外部IP地址就修改成我们刚才指定的IP地址了;

为default名称空间打标签,允许istio注入envoy sidecar

root@k8s-master01:~# kubectl label namespace default istio-injection=enabled
namespace/default labeled
root@k8s-master01:~# kubectl get ns --show-labels
NAME              STATUS   AGE   LABELS
default           Active   14h   istio-injection=enabled,kubernetes.io/metadata.name=default
istio-system      Active   47m   kubernetes.io/metadata.name=istio-system
kube-node-lease   Active   14h   kubernetes.io/metadata.name=kube-node-lease
kube-public       Active   14h   kubernetes.io/metadata.name=kube-public
kube-system       Active   14h   kubernetes.io/metadata.name=kube-system
root@k8s-master01:~# 

提示:这个名称空间可以根据自己的环境来;我们在给那个名称空间打上上述标签,对应在那个名称空间下部署pod都会注入一个sidecar而实现service mesh功能;

测试:在defuault名称空间下,部署一个pod,看看对应是否会给注入一个sidecar呢?

root@k8s-master01:~# kubectl run test --image=nginx --restart=Never 
pod/test created
root@k8s-master01:~# kubectl get pods
NAME   READY   STATUS    RESTARTS   AGE
test   2/2     Running   0          37s
root@k8s-master01:~# 

提示:可以看到我们在default名称空间下跑了一个nginx pod,对应pod里有两个容器;

验证:查看pod的详细信息,是否注入了envoy proxy容器?

提示:我们可以看到pod里除了有nginx镜像,还有一个istio/proxy,这个容器就是istiod注入至nginx pod中,从而实现高级流量管理;至此istio的部署就基本完成;

需求:假设有N台服务器,N台服务器之间都需要配置相互间免密登录

步骤1:在一台服务器上安装ansible

yum -y install epel-release &&  yum -y install ansible

步骤2:在这台服务器上生成密钥对

ssh-keygen

步骤3:生成authorized_keys文件,并修改为600权限

cp id_rsa.pub authorized_keys && chmod 600 authorized_keys

步骤4:ansible 关闭ssh首次连接时提示,关闭后不需要指纹验证

使用vim /etc/ansible/ansible.cfg编辑,将下面一行注释取消

host_key_checking = False

步骤5:编辑/etc/ansible/hosts,新增一个web组,添加要连接的服务ip,用户和密码,并测试连接是否成功

步骤6:使用ansbile批量新建.ssh文件夹,并设置权限

ansible web -m file -a "path=/root/.ssh state=directory mode=0700"

步骤7:使用ansbile拷贝
authorized_keys到远端/root/.ssh/目录下,并设置权限

ansible web -m copy -a "src=/root/.ssh/authorized_keys dest=/root/.ssh mode=0600"

步骤8:
使用ansbile拷贝
id_rsa到远端/root/.ssh/目录下,并设置权限

ansible web -m copy -a "src=/root/.ssh/id_rsa dest=/root/.ssh mode=0600"


步骤9:测试

前言

在开发过程中,取消需求是很常见的,但很容易被忽略。然而,取消需求的好处也很大。例如,在页面中会发送很多请求。如果页面被切走并处于不可见状态,就需要取消未完成的请求任务。如果未及时取消,则可能会导致以下负面影响:

  1. 消耗用户额外数据流量。
  2. 任务回调持有全局上下文变量,未及时释放存在内存泄漏风险
  3. 异步请求过多消耗大量系统资源,拖慢 UI 线程,造成卡顿。

在 Flutter 中,如何取消已经在进行的任务呢?首先需要掌握一些基础知识。

前置知识

Future#any 方法

传入一个 Future 任务列表,返回第一个完成的异步任务,无论成功或失败。

定义

用法

如下 5 个异步任务,代码第五行虽然第二执行,但是最先执行完毕,所以第一个返回,至此整个 Future.any 函数执行完毕。

结果输入如下:

总结

  • Future.any 实际就是一个 Completer,N 个 Future 叠加在一起,谁先完成算谁的。
  • Future.any 可以理解成百米赛跑,其中每个选手就是一个 Future,谁跑最快到终点谁就赢了。

Dio 取消实现解析

dio 版本

dio: dev v5.0.3

git: 67f07b86a0976c14a6e19061563832d92ed6772b

branch: main

如何取消

请求中传入 CancelToken 对象,然后调用 token.cancel 方法即可

final cancelToken = CancelToken();
dio.get(url, cancelToken: cancelToken).catchError((DioError err) {
  if (CancelToken.isCancel(err)) {
    print('Request canceled: ${err.message}');
  } else{
    // handle error.
  }
});
// Cancel the requests with "cancelled" message.
token.cancel('cancelled');

流程解析

思路:在实际任务执行前使用 Future.any 函数插入取消任务。如果实际任务没有执行完,就有机会取消它。

如下代码,黄色标注按照步骤来读就行。

总结:CancelToken 就像一个渣男,而 Future.any 则提供了公平竞争的机会。只要妹子还没有交男朋友,渣男就有机会中途得手。

说明

取消任务不仅限于网络请求。任何实际业务中包含不必要的耗时操作都可以通过 Future.any 配合 CancelToken 来实现取消。