Gitlab-CICD最简单明了的入门教程

CICD是什么?

由于目前公司使用的gitlab,大部分项目使用的CICD是gitlab的CICD,少部分用的是jenkins,使用了gitlab-ci一段时间后感觉还不错,因此总结一下

介绍gitlab的CICD之前,可以先了解CICD是什么

我们的开发模式经历了如下的转变:瀑布模型->敏捷开发→DevOps(Development、Operations的组合词,是一组过程、方法与系统的统称)

后来随着DevOps的兴起,出现了持续集成(Continuous Integration)、持续交付(Continuous Delivery) 、持续部署(Continuous Deployment) 的新方法,关于持续集成、持续交付、持续部署,总结如下:

  • 持续集成的重点是将各个开发人员的工作集合到一个代码仓库中。通常,每天都要进行几次,主要目的是尽早发现集成错误,使团队更加紧密结合,更好地协作。
  • 持续交付的目的是最小化部署或释放过程中固有的摩擦。它的实现通常能够将构建部署的每个步骤自动化,以便任何时刻能够安全地完成代码发布(理想情况下)。
  • 持续部署是一种更高程度的自动化,无论何时对代码进行重大更改,都会自动进行构建/部署。

持续集成的好处是什么?

持续集成可以使问题尽早暴露,从而也降低了解决问题的难度,正如老马所说,持续集成无法消除bug,但却能大大降低修复的难度和时间。

持续交付的好处是什么?

持续交付的好处在于快速获取用户反馈;适应市场变化和商业策略的变化。开发团队保证每次提交的修改都是可上线的修改,那么决定何时上线,上线哪部分功能则完全由产品业务团队决定。

虽然持续交付有显著的优点,但也有不成立的时候,比如对于嵌入式系统的开发,往往需要软硬件的配合。

持续部署的好处是什么?

持续部署的目标是通过减少批量工作的大小,并加快团队工作的节奏,帮助开发团队在其开发流程中消除浪费。这使团队能够一直处于一种可持续的平稳流状态, 让团队更容易去创新、试验,并达到可持续的生产率

市面上的CI有很多,如果在github上搜一下ci工具,也会搜到很多,比如:

  1. Travis CI
  2. Circle CI
  3. Jenkins
  4. AppVeyor
  5. CodeShip
  6. Drone
  7. Semaphore CI
  8. Buildkite
  9. Wercker
  10. TeamCity

这里只介绍Gitlab-CI

Gitlab-CI

GitLab 是 CI/CD 领域的一个新手玩家,但它已经在 Forrester Wave 持续集成工具中占据了领先地位。在这样一个竞争对手众多而水平又很高的领域,这是一项巨大的成就。是什么让 GitLab CI 如此了不起?

  • 它使用 YAML 文件来描述整个管道。
  • 它还有一个功能叫 Auto DevOps,使比较简单的项目可以自动构建内置了若干测试的管道。
  • 使用 Herokuish 构建包来确定语言以及如何构建应用程序。有些语言还可以管理数据库,对于构建新的应用程序并在开发过程一开始就将其部署到生产环境中,这是一个很重要的功能。
  • 提供到 Kubernetes 集群的原生集成,并使用多种部署方法的一种(如基于百分比的部署和蓝绿部署)将应用程序自动部署到 Kubernetes 集群中。

除了 CI 功能之外,GitLab 还提供了许多补充功能,比如自动把 Prometheus 和你的应用程序一起部署,实现运行监控;使用 GitLab 问题(Issues)、史诗(Epics)和里程碑(Milestones)进行项目组合和项目管理;管道内置了安全检查,提供跨多个项目的聚合结果;使用 WebIDE 在 GitLab 中编辑代码的能力,它甚至可以提供预览或执行管道的一部分,以获得更快的反馈。

相关概念

pipeline(管道、流水线)

  • 一次 Pipeline 其实相当于一次构建任务,里面可以包含多个流程(Stage),比如自动构建、自动进行单元测试、自动进行代码检查等流程 ;
  • 任何提交或者 Merge Request 的合并都可以触发 Pipeline ;

Stage(构建阶段)

  • Stage表示构建阶段,就是上面提到的流程 ;
  • 可以在一次 Pipeline 中定义多个 Stage;
  • Stage有如下特点 :
    • 所有 stages 会按照顺序运行,即当一个 stage 完成后,下一个 Stage才会开始
    • 只有当所有 Stage 成功完成后,该构建任务 Pipeline 才算成功
    • 如果任何一个 Stage失败,那么后面的 Stage 不会执行,该构建任务 (Pipeline) 失败

阶段是对批量的作业的一个逻辑上的划分,每个 pipeline 都必须包含至少一个 Stage。多个 Stage是按照顺序执行的,如果其中任何一个 Stage失败,则后续的 Stage不会被执行,整个 CI 过程被认为失败。

Jobs(任务)

  • job表示构建工作,表示某个stage里面执行的工作 ;
  • 一个stage里面可以定义多个job ;
  • jobs有如下特点 :
    • 相同 stage 中的jobs 会并行执行
    • 相同 stage 中的 jobs 都执行成功时,该 stage 才会成功
    • 如果任何一个job 失败,那么该 stage 失败,即该构建任务 (Pipeline) 失败

举一个例子,比如下面这个图:

这里的四个Statge(阶段): Verify、Build、Dockerpush、Deploy四个,这四个阶段组成一条Pipeline

每个阶段都有一个job,所以总共四个job,也就是unit-testjava-packagedocker-pushservice-1这四个,当然,每个stage可以由多个job组成,比如下面这个图:

Job 的执行过程中往往会产生一些数据,默认情况下 GitLab Runner 会保存 Job 生成的这些数据,然后在下一个 Job 执行之前(甚至不局限于当次 CI/CD)将这些数据恢复。这样即便是不同的 Job 运行在不同的 Runner 上,它也能看到彼此生成的数据。

.gitlab-ci.yml中提供了 before_script 和 after_script 两个全局配置项。这两个配置项在所有 Job 的 script 执行前和执行后调用。

关于.gitlab-ci.yml、before_script、after_script是什么,先别急,在后面有介绍

在了解了 Job 配置的 script、before_script、after_script 和 cache 以后,可以将整个 Job 的执行流程用一张图概括:

所以了解了Pipeline、Stage、Jobs后,还有一个很重要的东西,就是Runner

Runner

Runner就像一个个的工人,而Gitlab-CI就是这些工人的一个管理中心,所有工人都要在Gitlab-CI里面登记注册,并且表明自己是为哪个工程服务的。当相应的工程发生变化时,Gitlab-CI就会通知相应的工人执行软件集成脚本。如下图所示:

gitlab里面的runner叫Gitlab-Runner,Gitlab-Runner是配合Gitlab-CI进行使用的。一般地,Gitlab里面的每一个工程都会定义一个属于这个工程的软件集成脚本,用来自动化地完成一些软件集成工作。当这个工程的仓库代码发生变动时,比如有人push了代码,GitLab就会将这个变动通知Gitlab-CI。这时Gitlab-CI会找出与这个工程相关联的Runner,并通知这些Runner把代码更新到本地并执行预定义好的执行脚本(也就是在Job执行流程那个图中所示的第三步:script),所以,Gitlab-Runner就是一个用来执行软件集成脚本script的东西。

Runner类型

Gitlab-Runner可以分类两种类型:Shared Runner(共享型)Specific Runner(指定型)

  • Shared Runner:这种Runner(工人)是所有工程都能够用的。只有系统管理员能够创建Shared Runner。
  • Specific Runner:这种Runner(工人)只能为指定的工程服务。拥有该工程访问权限的人都能够为该工程创建Shared Runner。

关于Gitlab-runner的安装,见这篇文章:Gitlab Runner的安装配置

注册runner会对应一个tag,记住这个tag;

.gitlab-ci.yml简介

.gitlab-ci.yml 文件被用来管理项目的 runner 任务,Gitlab CI通过.gitlab-ci.yml文件管理配置job,该文件定义了statge顺序、job应该如何触发和工作、执行什么脚本、如何构建pipeline等流程

该文件存放于仓库的根目录, 默认名为.gitlab-ci.yml

我们先看一个简单的例子:.gitlab-ci.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
## 定义pipeline流程:verify->build->dockerpush->deploy
stages:
- verify
- build
- dockerpush
- deploy

#单元测试
unit-test:
stage: verify # 属于哪个流程
tags:
- test-cicd # 在哪个runner上面执行,在注册runner可以自定义
script:
- echo unit-test # 执行脚本

#java编译
java-package:
stage: build
tags:
- test-cicd
script:
- echo build

#push镜像
docker-push:
stage: dockerpush
tags:
- test-cicd
script:
- echo docker-push

#deploy
service-1:
stage: deploy
tags:
- test-cicd
script:
- echo deploy

该配置对应下面的pipeline,test-cicd是一个Specific Runner,执行脚本的类型是shell

所以,以unit-test这个job为例,点击该任务可以进入到log界面查看整个log执行流程

剩下的job的执行日志都大部分如此,就不一一列举了

几个重要的关键字解析

关于gitlab-ci.yml,里面有很多关键字配置,下面我主要列举一些比较常用的关键字

before_script和after_script

随着项目越来越大,Job 越来越多,Job 中包含的重复逻辑可能会让配置文件臃肿不堪。.gitlab-ci.yml 中提供了 before_scriptafter_script 两个全局配置项。这两个配置项在所有 Job 的 script 执行前和执行后调用。

before_scriptscript 在一个上下文中是串行执行的,after_script 是独立执行的。所以根据执行器(在runner注册的时候,可以选择执行器,docker,shell 等)的不同,工作树之外的变化可能不可见,例如,在before_script中执行软件的安装。

你可以在任务中定义 before_scriptafter_script,也可以将其定义为顶级元素,定义为顶级元素将为每一个任务都执行相应阶段的脚本或命令。

stages

stages的允许定义多个,灵活的场景阶段的pipline。定义的元素的顺序决定了任务执行的顺序。例如:

1
2
3
4
5
6
## 定义pipeline流程:verify->build->dockerpush->deploy
stages:
- verify
- build
- dockerpush
- deploy
  • build场景的任务将被并行执行。test、deploy 同理
  • build 任务成功后,test 执行。test 成功后,deploy 执行
  • 所有的都成功了,提交将会标记为成功
  • 任何一步任务失败了,提交标记为失败并之后的场景,任务都不回执行。

tags

tags可以从允许运行此项目的所有Runners中选择特定的Runners来执行jobs。

在注册Runner的过程中,我们可以设置Runner的标签,tags可通过tags来指定特殊的Runners来运行jobs:

1
2
3
4
5
#单元测试
unit-test:
stage: verify # 属于哪个流程
tags:
- test-cicd # 在哪个runner上面执行,在注册runner可以自定义

script

script是一段由Runner执行的shell脚本,可以执行多个,例如:

1
2
job:
script: mvn clean test

这个参数也可以使用数组包含好几条命令

1
2
3
4
job:
script:
- pwd
- mvn clean test

only and except

only和except两个参数说明了job什么时候将会被创建:

  • only定义了job需要执行的所在分支或者标签
  • except定义了job不会执行的所在分支或者标签

以下是这两个参数的几条用法规则:

  • only和except如果都存在在一个job声明中,则所需引用将会被only和except所定义的分支过滤.
  • only和except允许使用正则
  • onlyexcept可同时使用。如果onlyexcept在一个job配置中同时存在,则以only为准,跳过except(从下面示例中得出)。
  • onlyexcept允许使用特殊的关键字:branchestagstriggers
  • onlyexcept允许使用指定仓库地址但不是forks的仓库(查看示例3)。

例子解析:

1.job将会只在issue-开头的refs下执行,反之则其他所有分支被跳过:

1
2
3
4
5
6
7
job:
# use regexp
only:
- /^issue-.*$/
# use special keyword
except:
- branches

2.job只会在打了tag的分支,或者被api所触发,或者每日构建任务上运行

1
2
3
4
5
6
job:
# use special keywords
only:
- tags # tag 分支 commit 之后触发
- triggers # API 触发
- schedules # 每日构建触发

3.job将会在父仓库gitlab-org/gitlab-ce的非master分支有提交时运行。

1
2
3
4
5
job:
only:
- branches@gitlab-org/gitlab-ce
except:
- master@gitlab-org/gitlab-ce

when

when可以设置以下值:

  1. on_success - 只有前面stages的所有工作成功时才执行。 这是默认值。
  2. on_failure - 当前面stages中任意一个jobs失败后执行。
  3. always - 无论前面stages中jobs状态如何都执行。
  4. manual - 手动执行(GitLab8.10增加)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
stages:
- build
- cleanup_build
- test
- deploy
- cleanup

build_job:
stage: build
script:
- make build

cleanup_build_job:
stage: cleanup_build
script:
- cleanup build when failed
when: on_failure

test_job:
stage: test
script:
- make test

deploy_job:
stage: deploy
script:
- make deploy
when: manual

cleanup_job:
stage: cleanup
script:
- cleanup after jobs
when: always

脚本说明:

  • 只有当build_job失败的时候才会执行`cleanup_build_job 。
  • 不管前一个job执行失败还是成功都会执行`cleanup_job 。
  • 可以从GitLab界面中手动执行deploy_jobs。

manual:

  • 在GitLab的用户界面中显示该作业的“播放”按钮
  • 意味着deploy_job仅在单击“播放”按钮时才会触发job。

修改上面那个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
stages:
- verify
- build
- dockerpush
- deploy
- cleanup

before_script:
- pwd
after_script:
- echo after_script

#单元测试
unit-test:
stage: verify
tags:
- test-cicd
script:
- echo unit-test

#java编译
java-package:
stage: build
tags:
- test-cicd
script:
- echo build

#push镜像
docker-push:
stage: dockerpush
tags:
- test-cicd
script:
- echo docker-push

#deploy
service-1:
stage: deploy
tags:
- test-cicd
script:
- echo deploy
when: manual # 手动触发job,只有点击按钮才会触发

cleanup_job:
stage: cleanup
script:
- echo clean up
when: always # 前面的job成功与否,都会执行该job

pipeline如下:

allow_failure

when一起控制job执行与否的配置还有一个就是allow_failure

allow_failure可以用于当你想设置一个job失败的之后并不影响后续的CI组件的时候。失败的jobs不会影响到commit状态。

下面的这个例子中,java-packagejava-package2将会并列进行,如果java-package2失败了,它也不会影响进行中的下一个stage,因为这里有设置了allow_failure: true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
stages:
- verify
- build
- dockerpush
- deploy
- cleanup

before_script:
- pwd
after_script:
- echo after_script

#单元测试
unit-test:
stage: verify
tags:
- test-cicd
script:
- echo unit-test

#java编译
java-package:
stage: build
tags:
- test-cicd
script:
- echo build

#java编译
java-package2:
stage: build
tags:
- test-cicd
script:
- execute_script_that_will_fail # 该shell会导致job执行失败
allow_failure: true # 不影响后面的任务进行

#push镜像
docker-push:
stage: dockerpush
tags:
- test-cicd
script:
- echo docker-push

#deploy
service-1:
stage: deploy
tags:
- test-cicd
script:
- echo deploy
when: manual

cleanup_job:
stage: cleanup
tags:
- test-cicd
script:
- echo clean up
when: always

java-package2会执行错误

运行的pipeline如下,可见java-package2的执行错误

variables

GitLab CI允许你为.gitlab-ci.yml增加变量,该变量将会被设置入任务环境。通过两种方式可以引用

  • 美元符+大括号引用:${}
  • 美元符:$

示例如下:

1
2
3
4
5
6
7
8
9
10
variables:
SOFT_VERSION: '1.0'
TAG_NAME: 'xxx'
#构建镜像
docker-build:
stage: dockerpush
tags:
- test-cicd
script:
- docker build -t $TAG_NAME:${SOFT_VERSION}

如果有些值不想在配置文件中显示,比如密码什么的,可以在代码仓库中setting->CICD->Variables 自定义变量,跟在.gitlab-ci.yml配置变量效果是一样的

variables的保留字

gitlab-ci有一些预定义变量,这些变量大部分以CI开头

预定义变量:

Variable GitLab Runner Description
CI all 0.4 标识该job是在CI环境中执行
CI_COMMIT_REF_NAME 9.0 all 用于构建项目的分支或tag名称
CI_COMMIT_REF_SLUG 9.0 all 先将$CI_COMMIT_REF_NAME的值转换成小写,最大不能超过63个字节,然后把除了0-9a-z的其他字符转换成-。在URLs和域名名称中使用。
CI_COMMIT_SHA 9.0 all commit的版本号
CI_COMMIT_TAG 9.0 0.5 commit的tag名称。只有创建了tags才会出现。
CI_DEBUG_TRACE 9.0 1.7 debug tracing开启时才生效
CI_ENVIRONMENT_NAME 8.15 all job的环境名称
CI_ENVIRONMENT_SLUG 8.15 all 环境名称的简化版本,适用于DNS,URLs,Kubernetes labels等
CI_JOB_ID 9.0 all GItLab CI内部调用job的一个唯一ID
CI_JOB_MANUAL 8.12 all 表示job启用的标识
CI_JOB_NAME 9.0 0.5 .gitlab-ci.yml中定义的job的名称
CI_JOB_STAGE 9.0 0.5 .gitlab-ci.yml中定义的stage的名称
CI_JOB_TOKEN 9.0 1.2 用于同GitLab容器仓库验证的token
CI_REPOSITORY_URL 9.0 all git仓库地址,用于克隆
CI_RUNNER_DESCRIPTION 8.10 0.5 GitLab中存储的Runner描述
CI_RUNNER_ID 8.10 0.5 Runner所使用的唯一ID
CI_RUNNER_TAGS 8.10 0.5 Runner定义的tags
CI_PIPELINE_ID 8.10 0.5 GitLab CI 在内部使用的当前pipeline的唯一ID
CI_PIPELINE_TRIGGERED all all 用于指示该job被触发的标识
CI_PROJECT_DIR all all 仓库克隆的完整地址和job允许的完整地址
CI_PROJECT_ID all all GitLab CI在内部使用的当前项目的唯一ID
CI_PROJECT_NAME 8.10 0.5 当前正在构建的项目名称(事实上是项目文件夹名称)
CI_PROJECT_NAMESPACE 8.10 0.5 当前正在构建的项目命名空间(用户名或者是组名称)
CI_PROJECT_PATH 8.10 0.5 命名空间加项目名称
CI_PROJECT_PATH_SLUG 9.3 all $CI_PROJECT_PATH小写字母、除了0-9a-z的其他字母都替换成-。用于地址和域名名称。
CI_PROJECT_URL 8.10 0.5 项目的访问地址(http形式)
CI_REGISTRY 8.10 0.5 如果启用了Container Registry,则返回GitLab的Container Registry的地址
CI_REGISTRY_IMAGE 8.10 0.5 如果为项目启用了Container Registry,它将返回与特定项目相关联的注册表的地址
CI_REGISTRY_PASSWORD 9.0 all 用于push containers到GitLab的Container Registry的密码
CI_REGISTRY_USER 9.0 all 用于push containers到GItLab的Container Registry的用户名
CI_SERVER all all 标记该job是在CI环境中执行
CI_SERVER_NAME all all 用于协调job的CI服务器名称
CI_SERVER_REVISION all all 用于调度job的GitLab修订版
CI_SERVER_VERSION all all 用于调度job的GItLab版本
ARTIFACT_DOWNLOAD_ATTEMPTS 8.15 1.9 尝试运行下载artifacts的job的次数
GET_SOURCES_ATTEMPTS 8.15 1.9 尝试运行获取源的job次数
GITLAB_CI all all 用于指示该job是在GItLab CI环境中运行
GITLAB_USER_ID 8.12 all 开启该job的用户ID
GITLAB_USER_EMAIL 8.12 all 开启该job的用户邮箱
RESTORE_CACHE_ATTEMPTS 8.15 1.9 尝试运行存储缓存的job的次数

更多配置,可以参考官方参考文档:https://docs.gitlab.com/ee/ci/yaml/

更多精彩内容:mrxccc

设计模式-拦截过滤器

适用性

在以下情况下使用拦截过滤器模式

  • 系统使用预处理或后处理请求
  • 系统应该对请求进行身份验证/授权/记录或跟踪,然后将请求传递给相应的处理程序
  • 需要一种模块化方法来配置预处理和后处理方案

以下是此类设计模式的实体。

  • 过滤器- 过滤器将在请求处理程序执行请求之前或之后执行某些任务。
  • 过滤器链- 过滤器链带有多个过滤器,并帮助在目标上按定义的顺序执行它们。
  • Target - 目标对象是请求处理程序
  • 过滤器管理器- 过滤器管理器管理过滤器和过滤器链。
  • Client - Client 是向 Target 对象发送请求的对象。

步骤1

创建过滤器界面。

1
2
3
public interface Filter {
public void execute(String request);
}

第2步

创建认证过滤器。

1
2
3
4
5
public class AuthenticationFilter implements Filter {
public void execute(String request){
System.out.println("Authenticating request: " + request);
}
}

*debug过滤器

1
2
3
4
5
public class DebugFilter implements Filter {
public void execute(String request){
System.out.println("request log: " + request);
}
}

第 3 步

关于target,可以传递自己想要执行的某一业务方法,或者上下文都可以

1
2
3
4
5
public class Target {
public void execute(String request){
System.out.println("Executing request: " + request);
}
}

第4步

创建过滤器链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.ArrayList;
import java.util.List;

public class FilterChain {
private List<Filter> filters = new ArrayList<Filter>();
private Target target;

public void addFilter(Filter filter){
filters.add(filter);
}

public void execute(String request){
for (Filter filter : filters) {
filter.execute(request);
}
target.execute(request);
}

public void setTarget(Target target){
this.target = target;
}
}

第 5 步

创建过滤器管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class FilterManager {
FilterChain filterChain;

public FilterManager(Target target){
filterChain = new FilterChain();
filterChain.setTarget(target);
}
public void setFilter(Filter filter){
filterChain.addFilter(filter);
}

public void filterRequest(String request){
filterChain.execute(request);
}
}

第 6 步

创建客户端

1
2
3
4
5
6
7
8
9
10
11
public class Client {
FilterManager filterManager;

public void setFilterManager(FilterManager filterManager){
this.filterManager = filterManager;
}

public void sendRequest(String request){
filterManager.filterRequest(request);
}
}

第 7 步

使用客户端演示拦截过滤器设计模式。

1
2
3
4
5
6
7
8
9
10
11
public class InterceptingFilterDemo {
public static void main(String[] args) {
FilterManager filterManager = new FilterManager(new Target());
filterManager.setFilter(new AuthenticationFilter());
filterManager.setFilter(new DebugFilter());

Client client = new Client();
client.setFilterManager(filterManager);
client.sendRequest("HOME");
}
}

第 8 步

验证输出。

1
2
3
Authenticating request: HOME
request log: HOME
Executing request: HOME

IC中的账户及相关操作

账户分类

在IC中有如下三个账户:

  • identity:identity账户是主体标识,其可以与ICP账户(account id)相互映射,并且identity是控制canister的钥匙。

  • ledger:ledger账户是ICP账户(account id),所有和ICP相关的操作都在ledger中进行。

  • wallet:wallet账户是部署了cycle wallet程序的canister,identity必须设置了wallet才能部署canister。

principal

principal 首次使用 DFINITY Canister SDK 时,dfx 命令行工具会使用 PEM 文件中的公钥/私钥对为您创建默认的开发人员身份。开发人员身份是由派生的principal数据类型和代表principal的文本进行表示,也就是principal identifier(主体身份)。

account id

开发人员身份还可用于派生出account id(帐户标识符,类似于比特币或以太坊地址),用于在ledger canister中持有 ICP 代币。

wallet

wallet 在 Internet Computer上,钱包是一种专门用于cycle的应用程序。钱包应用程序被实现为一个canister,并在互联网计算机上运行。

钱包使您能够管理cycle余额,将ICP代币转换为cycle,将cycle分发到您自己或其他用户的canister中作为访问或提供互联网服务的一种方式。

当需要创建canister或者操作canister时,必须要绑定一个wallet canister到当前主体标识才可以进行操作。

操作

在整个IC中账户操作有如下一些:

  • DFX canister SDK第一次使用时就会关联一个主体标识(principal identity)。使用命令: dfx identity get-principal 可以获取当前主体标识。
  • 每个主体标识都会派生出一个在leger canister中唯一标识的account id,类似address。使用命令: dfx ledger account-id 可以获取当前的ICP地址。
  • 第一个canister需要使用leger canister创建,并指定canister的controller,并会向其中存入指定数量cycle。使用命令: dfx ledger --network ic create-canister tsqwz-udeik-5migd-ehrev-pvoqv-szx2g-akh5s-fkyqc-zy6q7-snAV6-uqe --amount .25 可以创建一个canister,并指定了controller主体以及初始化的cycle。
  • 第一个创建的canister是空的,需要对其部署wallet程序来创建一个wallet canister。使用命令: dfx identity --network ic deploy-wallet gastn-uqaaa-aaaae-aaafq-cai 可以在创建的一个canister中部署wallet程序。该canister称为wallet canister。
  • 默认情况下部署wallet后,该wallet canister会作为当前主体的cycle wallet,也可以使用如下命令设置当前主体的cycle wallet: dfx identity --network ic set-wallet --force gastn-uqaaa-aaaae-aaafq-cai 成功设置后,当前identity使用该cycle wallet进行canister操作。
  • 当前主体存在cycle wallet后,就可以使用canister相关操作。例如命令: dfx deploy --network ic 部署当前project中的所有canister,部署完成后,这些canister的controller为当前主体。

controller

controller是具有特殊权限的身份,该权限用于管理其控制的canister。只有controller身份可用于安装、升级或删除其控制的canister。

可以使用与user或canister关联的principal identifier来指定controller身份。

ledger canister

ledger Internet Computer将所有涉及ICP代币的交易在一个被称为ledger canister的管理canister中进行记录。ledger canister中是一个简化的并行区块链,它与其他网络管理canister一起在互联网计算机的子网中运行。

ledger canister实现了一个智能合约,该合约持有账户和余额,并保留影响账户和余额的交易历史。记录交易用以跟踪以下特定事件:

  • 铸造 ICP 代币。
  • 转移 ICP 代币。
  • 燃烧 ICP 代币。

IC上出色的基础设施(持续更新)

记录一些截止目前我所致比较出色的基础设施,帮助快速开发,避免重复造轮子

基础设施

IC-Dataset是一个基于Motoko的数据存储集合,用于交易消息可以实现跨合约的分布式事务管理和TCC模式的数据采集。

仓库:https://github.com/ICPSwap-Labs/ic-dataset

ICME开发者推出水平可扩展的NoSQL数据库

https://forum.dfinity.org/t/candb-the-first-scalable-nosql-database-on-the-internet-computer/13984?u=mike1

ic4j-agent是一个用于远程连接IC的本地 Java 库

https://github.com/ic4j/ic4j-agent

Azle(TypeScript CDK)

https://github.com/demergent-labs/azle

Sudograph(GraphQL数据库)

https://github.com/demergent-labs/sudograph

开发指导网站

motoko语言指导

https://hdcafe.com/motoco-base-manual/

dfx sdk中文手册

https://shuzhichengspace.gitbook.io/dfinity/

ic导航广场

https://jeksq-wyaaa-aaaal-qaw3a-cai.ic0.app/#/app/square

官方开发者论坛

https://forum.dfinity.org/

出色的工具网站

ic浏览器

https://icscan.io/

icDapp收纳网站

https://www.icp123.xyz/

IC 入门课第二课

课程大纲

  1. 使用 SDK 搭建一个简易网站
  2. Motoko 语言简介
  3. Canister 智能合约
  4. 用 Motoko 做后端
  5. 用 Javascript 做前端

第二课-课程要求

用 motoko 实现一个快排函数:
quicksort : [var Int] -> ()
要求:

  1. 用 moc 调试运行
  2. 把函数封装在一个 canister 里面
    public func qsort(arr: [Int]): async [Int]
  3. 部署到主网
  4. 使用主网的 Candid UI 调试运行
    https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.ic0.app

课程内容回顾

ICP上的智能合约

image-20220819102005820

Motoko 编程语言基本介绍

ICP 为什么需要一门新的语言?

  • 不是必须的,C, C++, Rust 都可以编译到 Wasm
  • 缺少一个高级语言同时满足:安全、高效、容易上手
  • 适配平台特性:Actor 模型、权限管理、代码升级、跨语言调用

image-20220819100856212

1.Motoko 语言的特点

  • 静态类型,语法接近 JavaScript/TypeScript
  • 面向对象,但不支持继承
  • 支持 await/async 异步通信
  • 结构化类型推断
  • 安全的数值计算
  • 没有 NULL 指针
  • 自动内存回收机制 (GC, copying/compacting/generational)

2.Motoko 基础概念

  • 程序 (program)
  • 声明 (declaration)
  • 表达式 (expression)
  • 值 (value)
  • 变量 (variable)
  • 类型 (type)

官方介绍文档:https://smartcontracts.org/docs/language-guide/motoko-introduction.html

注意点

  • 使⽤ := 作为通⽤赋值运算符
  • 代码块结束需 ;
  • 声明可变变量需 var 语法
  • 如果方法有返回值,最后一行代码块表示返回语句,可以不加分号

3.Motoko 基础库

数字类型:Int Int8 Int16 Int32 Int64 Nat Nat8 Nat16 Nat32 Nat64 Float
常用类型:Bool Char Array Text Option Result Iter Func None Hash
数据结构:Buffer List AssocList Stack Deque Heap RBTree HashMap Trie TrieMap TriSet

系统工具:Principal Blob Random CertifiedData Time Debug Prelude

https://smartcontracts.org/docs/base-libraries/stdlib-intro.html
https://github.com/dfinity/motoko-base

4.Motoko Canister

每个 Canister 都是一个 Actor,它的公共方法 (public method) 可供异步调用。

1
2
3
4
5
actor {
public func greet(name : Text) : async Text {
return "Hello, " # name # "!";
};
};

数据描述语言 Candid 用来规范 Canister 所提供的数据类型、服务接口等。

1
2
3
service : {
greet: (text) -> (text);
}

candid介绍:What is Candid?

5.moc编译器

1
2
3
4
5
6
7
8
9
# 查看编译器位置 
dfx cache show
# 添加编译器依赖到环境
#1、临时配置编译器依赖环境,但会话关闭就没有了
export PATH=$(dfx cache show):$PATH
#2、永久配置编译器依赖环境
echo "export PATH=\$(dfx cache show):\$PATH" >> ~/.bashrc
# 查看是否成功
which moc

VS Code 插件

主要是Vs Code插件的演示,

  • WSL插件
  • motoko代码编译检查插件:需要在project的根目录下打开vscode . 代码提示才有效果

Candid UI

本地:http://127.0.0.1:8000/?canisterId=r7inp-6aaaa-aaaaa-aaabq-cai

ic:https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.ic0.app/

Motoko Playground

地址:https://m7sm4-2iaaa-aaaab-qabra-cai.raw.ic0.app/

作业点评

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import Array "mo:base/Array";
import Int "mo:base/Int";
import Nat "mo:base/Nat";
actor {

public func qsort(arr: [Int]) : async [Int] {
// 将不可变数组转换为可变数组
var newArr:[var Int] = Array.thaw(arr);
sort(newArr, 0, newArr.size()-1);
// 将可变数组转换为不可变数组
Array.freeze(newArr)
};

func sort(arr:[var Int],low:Nat,high:Nat){
// check point 1/2
if(low>=high) return;
var temp = arr[low];
var left = low;
var right = high;
while(left < right){
while(arr[right] >= temp and right > left){
right -= 1;
};
arr[left] := arr[right]; // swap 1/3
while(arr[left] <= temp and left < right){
left += 1;
};
arr[right] := arr[left]; // swap 2/3
};
arr[right] := temp; // swap 3/3
if(left >= 1) // check point 2/2
sort(arr,low,left-1);
sort(arr,left+1,high);
};
};

官方示例:https://github.com/dfinity/examples/blob/master/motoko/quicksort/src/Quicksort.mo

IC 入门课第一课

课程大纲

  1. 使用 SDK 搭建一个简易网站
  2. Motoko 语言简介
  3. Canister 智能合约
  4. 用 Motoko 做后端
  5. 用 Javascript 做前端

第一课-课程要求

  1. 使用 SDK 搭建一个简易网站
    dfx new –no-frontend
  2. 领取 cycles 钱包
  3. 将网站部署到 ic0.app 主网
  4. 思考题:假如开发团队不再维护代码了,用户该怎么办?

DFINITY 开发者中心,下载 DFINITY Canister SDK

1
sh -ci "$(curl -fsSL https://smartcontracts.org/install.sh)"

使用 DFINITY Canister SDK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. 新建项目 

# 将以下命令中 <project_name> 替换成自己的项目名称

dfx new <project_name> --no-frontend

# 2. 启动项目,加上 --backgroud 可以运行在后台

dfx start

# 3. 部署项目到本地网络,在logs中可以看到canister_id

dfx deploy

# 4. 访问部署在本地的项目(仅限assets类型的目录)

# 用前端 assets 的 canister_id 替换以下命令中的 <canister_id>

http://<canister_id>.localhost:8000

获取cycles钱包

1
2
3
4
5
6
7
8
9
10
11
12
13
开发者需要使用 Cycles 钱包才能在主网部署合约,且需要Cycles钱包中有一定的Cycles(相比于以太网的gas,但比以太网的gas低的多的多)
一.课程学员用优惠码领取步骤:
1. 下载并安装 SDK
2. 使用 dfx 命令检查自己的 principal id
dfx identity get-principal
3. 使用 dfx 命令兑换 cycles 钱包
dfx canister --network=ic --no-wallet \
call fg7gi-vyaaa-aaaal-qadca-cai redeem '("xxxxx-yyyyy-zzzzz")'
4. 使用 dfx 关联钱包
dfx identity --network=ic set-wallet CANISTER_ID

二.官方水龙头(Cycles Faucet)领取Cycles:
参考步骤链接:https://internetcomputer.org/docs/current/developer-docs/quickstart/cycles-faucet

部署到 ic0.app 主网

1
2
3
4
5
6
7
8
9
10
# 1. 设置在 IC 网络的默认钱包 
# 将以下命令中 <Wallet ID> 替换成自己的钱包ID
dfx identity --network=ic set-wallet --force <Wallet ID>
# 2. 查询 IC 网络中的钱包余额
dfx wallet --network=ic balance
# 3. 部署项目到 IC 网络,保存
canister_id dfx deploy --network=ic
# 4. 访问部署在 IC 网络的项目
# 将以下命令中 <canister_id> 替换成自己应用的canister_id
https://<canister_id>.ic0.app

思考假如开发团队不再维护代码了,用户该怎么办?

。。。

拓展

回收cycles

1
2
3
4
5
6
7
8
9
10
11
12
13
# 回收前后,查询钱包cycles余额 dfx wallet --network=ic balance 123

# 回收 cycles 步骤,分两步:
dfx canister --network=ic stop --all
dfx canister --network=ic delete --all

# 使用canister id 回收 cycles 步骤,分三步:
# 1.查询canisterID为 <canister_id> 的canister的状态
dfx canister --network ic status <canister_id>
# 2.停止canisterID为 <canister_id> 的canister
dfx canister --network=ic stop <canister_id>
# 3.删除canisterID为 <canister_id> 的canister并回收cycles
dfx canister --network=ic delete <canister_id>

区块链共识机制:POW、POA、POS、DPOS、PBFT、DBFT

由于区块链的去中心化的特性,没有中心记账节点,需要全网对账本达成共识,因此共识机制作为区块链的关键技术之一,在业务吞吐量、交易速度、不可篡改性、准入门槛等等方面发挥重要的作用。

本次笔记归纳如下几种共识算法:

  • POW,工作量证明
  • POS,权益证明
  • DPOS,授权POS
  • POA,权威证明
  • PBFT,实用拜占庭容错算法
  • DBFT,授权拜占庭容错算法

问题场景

区块链技术中非对称加密可信的分布式网络解决拜占庭将军问题中的共识问题。

非对称加密

可以解决古代难以解决的签名问题:

  • 消息传送的私密性
  • 能够确认身份
  • 签名不可伪造、篡改

作用:保护消息内容, 并且让消息接收方确定发送方的身份。

分布式记账场景

img

下面开始归纳现区块链主流共识算法(机制)

PoW(Proof of Work,工作量证明)

“解决一道数学难题”,****“机器性能越好,挖矿时间越长,获得的回报也越多。”

类型:竞争共识

概念:工作量证明,引入了对一个特定值的计算工作。**
**

POW共识算法应用场景:比特币(BTC)及其引申出来的BCH、BCD等

算法工作描述:

  1. 矿工们在挖一个新的区块时,必须对SHA-256密码散列函数进行运算,区块中的随机散列值以一个或多个0开始。
  2. 随着0数目的上升,找到这个解所需要的工作量将呈指数增长,矿工通过反复尝试找到这个解。

解决问题:

  1. 谁有权记账
  2. 如何避免记账者作弊

img

优点:

算法简单,采用大家认可的数学逻辑(找寻随机数),容易实现。安全系数最高,破坏整个系统,需要投入巨大成本。

缺点:

  1. 大量消耗能源。
  2. 它的价值回路必须要通过外部输入。也就是说,采用POW的数字货币仍旧不是理想状态的数字货币,因为它们的安全性不直接与使用者相关,而是要通过矿工这个媒介。

使用该算法的项目(包含且不限于):

Bitcoin、Ethereum、Litecoin、Dogecoin

POS(Proof of Stake,权益证明)

“拥有的币越多,有记账权的概率就越大?”

类型:竞争共识

因为POW大量消耗能源和安全性不与使用者相关的问题,POS的出现可以解决这些问题:

  1. 采用POS的货币的安全性直接与使用者相关,省去了矿工这个媒介。
  2. POS简单说就是,每当发表一条消息的时候,不用证明你付出了什么代价,而要证明你拥有一定数量的钱。而拥有钱代表着,如果你作弊损害了这个系统的安全性,你的钱会贬值,这变相地让你付出了代价。
  3. 这东西更好的一点是,如果采用POS,实际上连挖矿奖励都不需要,因为POS实际上不需要付出任何代价。

概念:

试图解决POW机制中大量资源被浪费的情况。这种机制通过计算你持有占总币数的百分比,包括你占有币数的时间来决定记账权。

优点:不需要拼算力挖矿,不会浪费电力。缩短了共识达成的时间,效率提高。

缺点:

  1. 拥有权益的参与者因为可以持币吃利息,所以卖币意愿不强烈,容易产生垄断。
  2. 所有的确认都只是概率上的表达,存在其他攻击的可能性。挖矿成本低,硬分叉十分容易。

使用该算法的项目(包含且不限于):

Ethereum、Peercoin、Nxt

DPOS(Delegated Proof of Stake,授权权益证明)

可理解为“公司董事会”。

类型:协同型共识

2014年4月由Bitshares 的首席开发者 Dan Larimer(BM)提出。它的原理是让每一个持有比特股的人进行投票,由此产生101位代表 , 我们可以将其理解为101个超级节点或者矿池,而这101个超级节点彼此的权利是完全相等的。

优点:节能;快速;高流量博客网站 Steemit 就使用了它。EOS 的块时间是 0.5 秒。

缺点:略为中心化;拥有高权益的参与者可投票使自己成为一名验证者(这是近期已在 EOS 中出现的问题)。

使用该算法的项目(包含且不限于):

BitShares、Steemit、EOS、Lisk、Ark。

POA(Proof-of-Authority,权威证明)

“投票指定一位被认可的账户作为矿工”

类型:协同型共识

基于 PoA 的网络、事务和区块,是由一些经认可的账户认证的,这些被认可的账户称为“验证者”(Validator)。验证者运行的软件,支持验证者将交易(transaction)置于区块中。该过程是自动的,无需验证者持续监控计算机,但需要维护计算机(权威节点)不妥协(uncompromised)。

使用 PoA,每个个体都具有变成验证者的权利,因此存在一旦获取就保持验证者位置的动机。通过对身份附加一个声誉,可以鼓励验证者去维护交易的过程。因为验证者并不希望让自己获得负面声誉,这会使其失去来之不易的验证者地位。

优点:节能、快速。

缺点:略为中心化;虽然可用于公有区块链,但是通常用于私有区块链和许可区块链。

使用该算法的项目(包含且不限于):

POA.Network、Ethereum Kovan testnet、VeChain

PBFT(Practical Byzantine Fault Tolerance,实用拜占庭容错算法)

类型:协同型共识

首先从“拜占庭将军问题”开始(简书:中本聪与拜占庭将军问题),拜占庭将军问题的实质就是要寻找一个方法,使得将军们在一个有版徒的非信任环境中建立对战斗计划的共识。

实用拜占庭容错(PBFT,Practical Byzantine Fault Tolerance)是首个提出的该问题解决方案,当前已被 Hyperledger Fabric 采用。PBFT 使用了较少(少于 20 个,之后会稍有增加)的预选定将军数,因此运行非常高效。它的优点是高交易通量和吞吐量,但是不足之处在于是中心化的,并用于许可网络。

拜占庭容错系统是指:在一个拥有n台节点的系统,整个系统,对每个请求满足如下条件: 所有非拜占庭节点使用相同的输入信息,产生同样的结果; 如果输入的信息正确,那么所有非拜占庭节点必须接收这个信息,并计算相应的结果。

与此同时,在拜占庭系统的实际运行过程中一般假设系统中拜占庭节点不超过m台,并且对每个请求满足2个指标:

安全性——任何已经完成的请求都不会被更改,它可以在以后请求看到;

活性——可以接受并且执行非拜占庭客户端的请求,不会被任何因素影响而导致非拜占庭客户端的请求不能执行。

优点:高速、可扩展。

缺点:通常用于私有网络和许可网络。

使用该算法的项目(包含且不限于):

Hyperledger Fabric、Stellar、Ripple、Dispatch

DBFT (Delegated Byzantine Fault Tolerance, 授权拜占庭容错算法)

类型:协同型共识

同样是为了解决拜占庭将军问题,「授权拜占庭容错」机制,是一种在NEO区块链内部实现的保证容错的共识算法。

在这个机制当中,存在两个参与者,一个是专业记账的“记账节点”,一个是系统当中的普通用户。

普通用户基于持有权益的比例来投票决定记账节点,当需要通过一项共识时,在这些记账节点中随机推选出一名发言人拟定方案,然后由其他记账节点根据拜占庭容错算法,即少数服从多数的原则进行表态,如果超过66%的节点表示同意发言人方案,则共识达成;否则,重新推选发言人,重复投票过程。

优点:快速;可扩展。

缺点:每个人都争相成为根链。其中可能存在多个根链。

使用该算法的项目:

Neo

注:转载于http://t.zoukankan.com/kumata-p-9929588.html

IC开发入门常见问题

访问不到钥匙,文件等问题

检查之前命令行是否用了 sudo
或者之前是root, 现在用了其他用户

进入私钥目录

1
cd ~/.config/dfx/identity/

1.dfx deploy 运行报错

可能是没有运行开发环境:

1
2
3
dfx stop && dfx start 
或者 dfx stop && dfx start --background
或者 dfx stop && dfx start --clean (清除当前项目的状态)

2.在原来canister发布新版本是否需要加上 –with-cycles=xxxx

不需要

3.motoko 打印array

Debug.print(debug_show(arr));

4.motoko 语法检查

dfx build –check

5.跨 Canister 调用是否有 Timeout 的机制?以防止某个调用不返回导致主调 Canister 无法停止。

timeout 仅发生在对方子网下线的情况下,其它情况则需要等待对方返回,系统保证它终将返回,可能很久。

6.冻结时,如果有未返回的跨 Canister 调用会怎样?会等待返回吗?

stopping 状态是就是等待所有已发出的调用返回,所有都返回了才能进入 stopped 状态

7.跨 Canister 调用,如何在本地环境调试?可以把主链非自己所有的 Canister 部署到本地环境吗?还是必须自己

是的,需要把依赖的 canister 自己部署一遍

8.为什么在例子中,发送了消息 Cycles.balance 没有消费?

本地起的环境运行 canister 都不消耗 cycles

9.如果多个人并发调用一个 Canister 中的 Update 方法,是需要排队等待吗?

发给 canister 的消息都是先进入队列,然后依次执行

10.HttpRequest/ HttpResponse 这些类型是否可以从某些标准库 import 过来?

目前没在标准库里

11.没有 Blob[Nat8] 的标准方法吗?

Blob.toArray(blob)

12.http_request的actor部署在主网之后为什么是显示“Body does not pass verification”

用这个访问 CANISTERID.raw.ic0.app

13.运行报错The Replica returned an error: code 5, message: “Canister xxxx exceeded the cycles limit for single message execution.”

检查下程序是不是有死循环

14.升级dfx后报错问题

检查后台运行的dfx start是否还在旧版本运行

15.降级dfx版本到0.8.4版本

DFX_VERSION=0.8.4 sh -ci “$(curl -fsSL https://sdk.dfinity.org/install.sh)”

16.Warning:The versionofDFXused(0.9.2) is different than the version beingrun(0.8.4

This might happen because your dfx.json specifies an older version, or DFX_VERSION is set in your environment.
删除或者修改 dfx.json里面的版本version

17.playground 上部署的内容是否会有销毁时间

是的

18.是否有类似javascrpt的Promise.all的方法

目前没有

19.TEXT 连接用什么符号

用#

20. cycles不够了怎么办?

用命令行把ICP转成cycles充值: dfx ledger top-up -h提示
或者在nns钱包canister里面,link到canister后top-up

21. canister query方法是否支持异步调用

不支持,会报错

21. 大数据存储

https://github.com/PrimLabs/Bucket

IC区块链与经济模型

Dfinity区块链与经济模型

Dfinity(下称ICP区块链)是一条不保留旧区块的区块链,其通过运行ICP协议来链接节点,形成区块链系统

在ICP区块链中,主要有两种代币:

Cycle:

  • 价值与XDR进行锚定,XDR是综合一系列法币进行加权求和得到的值,可以认为是稳定币。
  • 作用主要是为给Canister充值,维持Canister运行

ICP:

  • 可以通过兑换为Cycle,“充值”给Canister(智能合约容器),维持Canister的运行。
  • 质押ICP,成为神经元,参与投票。投票可以得到新的ICP,可以认为是“投票挖矿”

子网

  • 在ICP区块链中,分布在世界各地的节点通过NNS被划分为若干子网。每个节点可以参与多个子网。每个子网都是一个单独的区块链,运行ICP协议,达成共识。不同于以太坊分片策略的是,ICP区块链没有“母网”,只有不同的子网。
  • NNS可以对子网进行重新组合,重新生成与恢复

ChainKey

  • 阈值签名:子网的节点对信息进行签名的算法是阈值BLS,一个子网中,对一个信息达成共识的条件是2f+1(总量3f)个节点使用自己的子密钥对一个信息进行签名,达成条件后即可生成完整私钥签名。
  • 子网公钥:每个子网都根据root key生成自己的链公钥,子网间通信可以直接通过子网公钥核查数据真实性,无需下载旧区块数据。
  • IC公开根公钥,用户可以通过根公钥验证一个消息是否为IC返回的消息。
  • Catch up packaqe:不保留旧区块,每个子网每隔200个区块进行一次“打包”这次打包将包含当前所有副本的一致性数据,打包后进行阈值签名,超过2f+1个节点签名后,就认为达成一致,此时就可以删去以前的区块,只保留这个“包”。catch up包可以用来恢复数据,同步数据。

Canister智能合约模型

  • 在ICP中,一个Canister就是一个WebAssembly容器。
  • ICP合约原生语言为Motoko,所有可以编译为wasm的语言都可以写ICP合约,当前支持Rust的cdk比较成熟。
  • 一个Canister包含一个合约的运行时字节码和数据状态。
  • Canister对外提供两种方法处理方案:
    • 第一种为update方法,这种方法将对内存数据进行状态修改,需要子网内达成共识,消息处理模型是单线程。
    • 第二种为query方法,原理是Canister使用Actor模型,每个query方法会在节点内对当前数据状态进行一次“内存快照”,然后进行处理,这种方法的调用不修改数据状态,可以进行并发处理。

Actor模型与原子性

得益于Actor模型,Canister可以并行进行多个线程,处理不需要更改状态数据的消息,提升服务性能。

但是,又由于Canister使用Actor模型,不同的Canister之间可以通信,但是数据状态相互隔离,没有状态锁,因此ICP区块链不能保证操作原子性,需要自己实现一些简化的分布式一致性协议来保证原子性。

前后端都在链上的优劣

前后端都在链上的优劣

  • ICP区块链的前后端可以说都在“链上”。部署时,前后端都编译为wasm部署到节点上。
  • 优点:因前端wasm在“链上”,或者说在节点上因此浏览器获取到前端wasm后可以验证前端是否是安全的(通过icroot公钥)。
  • 缺点:当前并不是所有的浏览器都支持wasm运行部分用户无法访问ICP的应用。

注:转自赵杲老师的技术分享

常用软件的docker-compose安装文件

使用docker-compose安装一些常用软件

Mysql

docker-compose.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '2'
services:
mysql-service:
restart: always
image: mysql:8.0.15
container_name: mysql
ports:
- 3307:3306
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: Xc58525456
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
volumes:
- /usr/local/docker/mysql/data:/var/lib/mysql

nginx

docker-compose.yaml

1
2
3
4
5
6
7
8
9
10
11
version: '2'
services:
nginx:
restart: always
image: nginx
container_name: nginx
ports:
- 80:80
volumes:
- ./conf/nginx.conf:/etc/nginx/nginx.conf
- ./wwwroot:/usr/share/nginx/wwwroot

nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
worker_processes  1;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
charset utf-8; # 设置编码格式

server {
listen 80;
server_name 192.168.56.10;
access_log logs/pbootcms.access.log;
error_log logs/pbootcms.error.log;
index index.html index.htm index.php;
root /home/www/pbootcms;

#include /usr/local/nginx/conf/rewrite/none.conf;
#error_page 404 /404.html;
#error_page 502 /502.html;

location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php/$1 last;
#rewrite ^(.*)$ /index.php?s=$1 last;
break;
}
}


location ~ [^/]\.php(/|$) {
fastcgi_pass 127.0.0.1:9000;
#fastcgi_pass unix:/dev/shm/php-cgi.sock;
fastcgi_index index.php;
include /etc/nginx/fastcgi.conf;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;
fastcgi_param PATH_INFO $path_info;
fastcgi_param CI_ENV 'development';
try_files $fastcgi_script_name =404;
}


location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
expires 30d;
access_log off;
}
location ~ .*\.(js|css)?$ {
expires 7d;
access_log off;
}
location ~ /\.ht {
deny all;
}
}
}

minio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
version: '3.1'
services:
minio:
image: minio/minio
container_name: minio
restart: always
ports:
- 9001:9000
- 8000:8000
command: server --address '0.0.0.0:9000' --console-address "0.0.0.0:8000" /data
logging:
options:
max-size: "50M" # 最大文件上传限制
max-file: "10"
driver: json-file
environment:
MINIO_ACCESS_KEY: minio #管理后台用户名
MINIO_SECRET_KEY: minio123 #管理后台密码,最小8个字符
volumes:
- ./data:/data #映射当前目录下的data目录至容器内/data目录
- ./config:/root/.minio/ #映射配置目录
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9001/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3

elastic-kibana

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
version: '2.2'
services:
cerebro:
image: lmenezes/cerebro:0.8.3
container_name: cerebro
ports:
- "5701:9000"
command:
- -Dhosts.0.host=http://elasticsearch:9200
networks:
- es7net
kibana:
image: docker.elastic.co/kibana/kibana:7.1.0
container_name: kibana7
environment:
- I18N_LOCALE=zh-CN
- XPACK_GRAPH_ENABLED=true
- TIMELION_ENABLED=true
- XPACK_MONITORING_COLLECTION_ENABLED="true"
- ELASTICSEARCH_USERNAME=kibana
- ELASTICSEARCH_PASSWORD=demo_password
ports:
- "5601:5601"
networks:
- es7net
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.1.0
container_name: es7_01
environment:
- cluster.name=test-es
- node.name=es7_01
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- "TZ=Asia/Shanghai"
- discovery.type=single-node
- path.data=node0_data
- xpack.security.enabled=true
- xpack.security.transport.ssl.enabled=false
ulimits:
memlock:
soft: -1
hard: -1
privileged: true
volumes:
- /data/es7data1:/usr/share/elasticsearch/data
ports:
- 9200:9200
networks:
- es7net

networks:
es7net:
driver: bridge

portainer

1
2
3
4
5
6
7
8
portainer:
image: portainer/portainer
restart: always
ports:
- "9000:9000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /data/docker/portainer/data:/data

confluence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
version: '2'
services:
confluence-mysql:
image: postgres:9.4
container_name: confluence-db
ports:
- "5432:5432"
restart: always
environment:
POSTGRES_PASSWORD: pg123456.
volumes:
- /usr/local/confluence/pgsql-data:/var/lib/postgresql/data
confluence:
image: cptactionhank/atlassian-confluence:7.2.0
container_name: confluence
volumes:
- "/home/confluence/apps/confluence:/opt/atlassian/confluence"
- "/home/confluence/data:/var/atlassian/confluence"
- "/usr/share/zoneinfo/Asia/Shanghai:/etc/localtime"
restart: always
user: root
ports:
- "8090:8090"
environment:
JAVA_OPTS: -Duser.timezone=Asia/Shanghai
CATALINA_OPTS: -Xms256m -Xmx2g
depends_on:
- confluence-mysql

redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
version: '2'
services:
master:
image: redis
container_name: redis-master
ports:
- 6379:6379
restart: always
slave1:
image: redis
container_name: redis-slave-1
ports:
- 6380:6379
command: redis-server --slaveof redis-master 6379

slave2:
image: redis
container_name: redis-slave-2
ports:
- 6381:6379
command: redis-server --slaveof redis-master 6379

wordpress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
version: '3.3'

services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress

wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
db_data: {}

zookeeper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
version: '2'

services:
zoo1:
image: zookeeper:3.4.11
restart: always
hostname: zoo1
container_name: zookeeper_1
#domainname:
ports:
- 2181:2181
volumes:
- /usr/local/docker_app/zookeeper/zoo1/data:/data
- /usr/local/docker_app/zookeeper/zoo1/datalog:/datalog
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

zoo2:
image: zookeeper:3.4.11
restart: always
hostname: zoo2
container_name: zookeeper_2
ports:
- 2182:2181
volumes:
- /usr/local/docker_app/zookeeper/zoo2/data:/data
- /usr/local/docker_app/zookeeper/zoo2/datalog:/datalog
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

zoo3:
image: zookeeper:3.4.11
restart: always
hostname: zoo3
container_name: zookeeper_3
ports:
- 2183:2181
volumes:
- /usr/local/docker_app/zookeeper/zoo3/data:/data
- /usr/local/docker_app/zookeeper/zoo3/datalog:/datalog
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

yapi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Use root/example as user/password credentials
version: '3.1'

services:
mongo:
image: mongo:4
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: Xc58525456
MONGO_INITDB_DATABASE: yapi
volumes:
- ./mongo-conf:/docker-entrypoint-initdb.d
- ./mongo/etc:/etc/mongo
- ./mongo/data/db:/data/db
ports:
- 27017:27017
healthcheck:
test: ["CMD", "netstat -anp | grep 27017"]
interval: 2m
timeout: 10s
retries: 3
yapi:
build:
context: ./
dockerfile: Dockerfile
image: yapi
# 第一次启动使用
# command: "yapi server"
# 之后使用下面的命令
command: "node /my-yapi/vendors/server/app.js"
volumes:
- ./my-yapi:/my-yapi
ports:
- 9090:9090
- 3000:3000
depends_on:
- mongo

mongo-confi=====init-mongo.js

1
2
3
4
5
6
7
8
9
10
11
12
db.createUser({ user: 'admin', pwd: 'admin123456', roles: [ { role: "root", db: "admin" } ] });

db.auth("admin", "admin123456");
db.createUser({
user: 'yapi',
pwd: 'yapi123456',
roles: [
{ role: "dbAdmin", db: "yapi" },
{ role: "readWrite", db: "yapi" }
]

});

Dockerfile

1
2
3
4
5
6
FROM node:12-alpine
COPY repositories /etc/apk/repositories

RUN npm install -g yapi-cli --registry https://registry.npm.taobao.org

EXPOSE 3000 9090

repositories

1
2
3
https://mirrors.aliyun.com/alpine/v3.6/main/

https://mirrors.aliyun.com/alpine/v3.6/community/

readme

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
YApi Docker镜像
==============


YApi: https://github.com/YMFE/yapi/releases

制作本地的yapi docker镜像, docker-compose一键维护和部署.

## How

1. 初始化db, 开启自定义配置

```
git clone https://github.com/Ryan-Miao/docker-yapi.git
cd docker-yapi
docker-compose up
```

打开 localhost:9090

- 默认部署路径为`/my-yapi`(需要修改docker-compose.yml才可以更改)
- 修改管理员邮箱 `ryan.miao@demo.com` (随意, 修改为自己的邮箱)
- 修改数据库地址为 `mongo` 或者修改为自己的mongo实例 (docker-compose配置的mongo服务名称叫mongo)
- 打开数据库认证
- 输入数据库用户名: `yapi`(mongo配置的用户名, 见mongo-conf/init-mongo.js)
- 输入密码: `yapi123456`(mongo配置的密码, 见mongo-conf/init-mongo.js)

点击开始部署.

![](doc/init.jpg)
![](doc/init-2.jpg)

2. 部署完毕后, 修改docker-compose.yml
启用

```
yapi:
build:
context: ./
dockerfile: Dockerfile
image: yapi
# 第一次启动使用
# command: "yapi server"
# 之后使用下面的命令
command: "node /my-yapi/vendors/server/app.js"
```

重启服务:

```
docker-compose up
```

访问 localhost:3000

- 输入用户名ryan.miao@demo.com(自己输入的管理员邮箱)
- 输入密码ymfe.org(默认的初始化密码, 之后可以修改)

然后可以导入一个swagger的接口数据试试:

![](doc/start-1.jpg)
![](doc/start-2.jpg)


3. 后台启动

前台启动确认没问题后, 直接