1.  作者2013开始关注微服务, 2018年才重新关注. 曾经学习到放弃K8s数次. 说明学习曲线太陡峭

2. MicroService First,  从头创建一个粗糙的微服务架构,比重构一个单体应用要简单多. 关键现在的工具使得创建一个分布式的应用的花费比单体应用不会多很多.

3.MicroService 可以用不同的技术栈,(我的理解: 比如你要部署个blog, 你知道WordPress是最好的,但你的技术栈是NET或JAVA,假如是单体应用,你不会想用它,你会想我要不要找一个同一个技术栈的,但功能差很多的.)部署更新测试的花费要少很多.

 

因为要用到很多命令行命令, 所以我们可以把命令行窗口的起始目录改为代码目录,这样可以减少操作,参考这个

windows怎么修改cmd命令提示符的默认初始路径

Node.js 用到的代码

const express = require("express");
const fs = require("fs");  // 如果没有引用, 就会出现 ReferenceError: fs is not defined

 

书本上, 搭建微服务用到的命令


 //更改npm 淘宝镜像,速度快很多

npm init -y //创建package.json文件

npm install --save express //安装Express HTTP Server, 更新package.json文件, 并创建package-lock.json(指定依赖的具体版本号)
node index.js  //执行index.js

set PORT=3000 //假如把端口定义放在环境变量里,就要手工指定

npm install --save-dev nodemon //在开发环境安装热重载nodemon, 在package.json里定义script "start:dev": "nodemon index.js"

//如果删掉了node_modules,就要用npm install 重新安装
npm run start:dev //使用热重载nodemon,修改index.js会马上重启Nodejs,调试用这个命令,调试好之后才用docker build,docker run的命令
docker build -t video-streaming --file Dockerfile .   //建立一个Docker Image 用指定的Tagname "video-streaming" 可以把Image理解为操作系统的镜像
docker run -d -p 3000:3000 video-streaming        //根据Image创建Containter, Container(容器):容器是Image的一个具体的实例 容器的实质是进程
docer container list                 //找到容器的id,比如是6f0835f32957
docker logs <Container Id>             //查看容器里面的log 例如 docker logs 6f0835f32957

docker rmi <Image Id> --force //删除镜像,假如一个Image被多次引用,必须加上--force参数来删除

docker-compose up --build   //保证Build之后再启动Docker
docker-compose down      //相对于正常关机,尽量不用Ctrl +C
docker image prune -f //清理临时的None镜像
//编写短脚本来缩写
 down.sh for docker-compose down
 reboot.sh for docker-compose down && docker-compose up --build
 

 

 书本上讲的是用Private Docker Registery,  如果没有网络要把Image 复制到另一台机,

docker pull <image name> # 必须先 pull 镜像到本地
docker save -o <path for generated tar file> <image name>   #例如 docker save -o myDockerImage video-streaming
使用 rsync 或 scp 复制到远程服务器,然后

docker load -i <path to image tar file>

Docker Compose 是开发的最佳选择,但可能不是最好的 生产的选择。

Docker Compose is the best option for development, but probably not the best option for production.

 

我们可以通过直接连接我们的视频流微服务来到云存储提供商。 但我们不会那样做。 相反,我们将采用良好的设计原则,即关注点分离和单一职责原则;

我们将创建一个 新的微服务,其目的是成为我们文件存储提供程序的抽象。这使得以后更换存储机制和 可以为我们的应用程序支持多个存储提供程序铺平道路。

We could add cloud storage by directly connecting our video-streaming microservice
to the storage provider. We won’t do that. Instead, we’ll employ good design principles, namely, separation of concerns and single responsibility principle; and we’ll create a
new microservice whose purpose is to be an abstraction of our file storage provider.

 
如果您正在考虑将此处提供的代码移植到 AWS 或 GCP,从 Azure 存储微服务转换到另一个提供商不是一个简单的任务。

in case you were thinking of porting the code presented here over to AWS or GCP, converting from the Azure store microservice over to another provider is not a simple task.

 

对标书上的Azure的Private Container Registery,国内的是阿里云的容器镜像服务

 

 

 

创建命名空间,仓库名称后, 阿里云可以从各个代码源,根据代码变更自动更新镜像, 我们就选择本地仓库好了

创建后,页面提示具体的使用方法

1. 登录阿里云Docker Registry
$ docker login --username=<阿里云账号> registry.cn-guangzhou.aliyuncs.com
用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码。

您可以在访问凭证页面修改凭证密码。

2. 从Registry中拉取镜像
$ docker pull registry.cn-guangzhou.aliyuncs.com/<命名空间>/<仓库名>:[镜像版本号]
3. 将镜像推送到Registry
$ docker login --username=<阿里云账号> registry.cn-guangzhou.aliyuncs.com
$ docker tag [ImageId] registry.cn-guangzhou.aliyuncs.com/<命名空间>/<仓库名>:[镜像版本号]
$ docker push registry.cn-guangzhou.aliyuncs.com/<命名空间>/<仓库名>:[镜像版本号]
请根据实际镜像信息替换示例中的[ImageId]和[镜像版本号]参数。

4. 选择合适的镜像仓库地址
从ECS推送镜像时,可以选择使用镜像仓库内网地址。推送速度将得到提升并且将不会损耗您的公网流量。

如果您使用的机器位于VPC网络,请使用 registry-vpc.cn-guangzhou.aliyuncs.com 作为Registry的域名登录。

5. 示例
使用"docker tag"命令重命名镜像,并将它通过专有网络地址推送至Registry。

$ docker images
REPOSITORY                                                         TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry.aliyuncs.com/acs/agent                                    0.7-dfb6816         37bb9c63c8b2        7 days ago          37.89 MB
$ docker tag 37bb9c63c8b2 registry-vpc.cn-guangzhou.aliyuncs.com/acs/agent:0.7-dfb6816
使用 "docker push" 命令将该镜像推送至远程。

$ docker push registry-vpc.cn-guangzhou.aliyuncs.com/acs/agent:0.7-dfb6816

 

容器镜像服务已停止控制台授权功能,请先删除控制台授权再迁移至RAM控制台设置子账号权限,具体参考文档 《RAM权限管理文档》

RAM 访问控制 (aliyun.com)

 

如果你用书上用Image的名字来打Tag,不能Push上去,错误提示如下,一定要用<ImageID>

bad4f9146914: Preparing
0c3f34be7e88: Preparing
4da12ed8ecbc: Preparing
23c426cdc5d6: Preparing
58130d3fd6ad: Preparing
c99dbbb91c1e: Waiting
743fabfe0b03: Waiting
529f2cda666c: Waiting
1a058d5342cc: Waiting
denied: requested access to the resource is denied

docker tag  a244a617f4cf  registry.cn-guangzhou.aliyuncs.com<命名空间>/<仓库名>:video-streaming

docker push registry.cn-guangzhou.aliyuncs.com/<命名空间>/<仓库名>:video-streaming

The push refers to repository [registry.cn-guangzhou.aliyuncs.com/meta88/metabase]
bad4f9146914: Pushed
0c3f34be7e88: Pushed
4da12ed8ecbc: Pushed
23c426cdc5d6: Pushed
58130d3fd6ad: Pushed
c99dbbb91c1e: Pushed
743fabfe0b03: Pushed
529f2cda666c: Pushed
1a058d5342cc: Pushed
video-streaming: digest: sha256:11119fcaa3d54b36904336cbf38318bab7bfe0e2c019033588d3faf8ac5e42fd size: 2203

 

Dockerfile里面注意有基准路径,假如没有把video目录copy进去, const path ="./videos/SampleVideo_1280x720_1mb.mp4"; 这句代码就找不到路径了

FROM node:12.22.8-alpine

WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install 
COPY ./src ./src
COPY ./videos ./videos
CMD npm run start:dev

 第4章,把Video的存储在Azure里作为一个单独的服务,注意Azure的Container的名字是否和代码的一样,否则会找不到

 

 每个单独的服务,最好在每个微服务目录下面单独写一个up.bat,例如存储这个微服务

set PORT=3000
set STORAGE_ACCOUNT_NAME=XXX
set STORAGE_ACCESS_KEY=XXXXXXXXXXXXXXX

npm run start:dev 

 

用Docker-Compose 命令是把批量的container都启动起来,假如发现问题时要修改代码,我要修改重启某一个微服务时,要怎样做呢?

用DockerDesktop管理界面,可以停止重启,但是不会重新build的,

docker-compose restart history

 解决方法: 在第5章126页, 不要把代码拷贝进Container,    COPY ./src ./src, 用Volumes映射,左边是宿主机的路径,右边是container的路径,为啥是/usr/src/app?

    volumes:
      - /tmp/video-streaming/npm-cache:/root/.npm:z
      - ./video-streaming/src:/usr/src/app/src:z
      - ./video-streaming/videos:/usr/src/app/videos:z

 

To enable live reload on a larger scale, we synchronize our code between our development workstation and the container so that changes to the code automatically propagate through into the container

 

假如我输入的中文的引号,编译错误会这样的

SyntaxError: Invalid or unexpected token
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1031:15)
    at Module._compile (node:internal/modules/cjs/loader:1065:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

 

我们可以将应用程序的文件和数据存储在 Kubernetes 集群中,但是我更喜欢我的生产集群是无状态的。

我们使用云存储而不是集群存储,因为它很简单,它适用于我们在开发中运行,它很便宜,并且为我们管理。这意味着我可以摧毁和重建随意集群,没有丢失数据的风险。

 

我们的应用程序最终将成为许多微服务之间通信的蜘蛛网。 一个服务的变化将有可能导致整个应用程序出现指数级的问题。

精心构建微服务之间的接口,尽量减少它们之间的耦合,帮助我们充分利用我们的微服务架构。

 

Docker里面微服务的端口尽量不要和宿主机的端口重复,比如MongoDB默认的端口是27017,因为宿主机可能已经用了27017,

On our host operating system (OS), we have mapped the port to 4000. That’s an arbitrary choice. We could have given it any number, including 27017. I prefer not to give it the standard MongoDB port because that would conflict with an instance of MongoDB that we might have running on our host OS.

 

如果我们使用路径来识别我们的视频,那么以后很难将视频移动到一个不同的位置,如果将来我们决定要重组我们的存储文件系统。

因为是各种其他数据库和记录已经引用了我们的视频路径

If we use paths to identify our videos, that makes it difficult to later move videos to a different location if in the future we decide we’d like to restructure our storage filesystem. The reason this is a problem is that various other databases and records will need to refer to our videos. 

 

在VSCode里安装MongoDB的扩展,可以查询管理MongoDB. Working with MongoDB in Visual Studio Code

 

添加数据库Video-streaming和Collection

 

 添加记录,

MongoDB 中存储的文档必须有一个"_id" 键。这个键的值可以是任何类型的,默认是个ObjectId 对象。

ObjectId 使用12 字节的存储空间,每个字节两位十六进制数字,是一个24 位的字符串。如果插入文档的时候没有"_id" 键,系统会自动帮你创建一个

假如要制定OjectId,就要这样写

// The current database to use.
use('video-streaming');

db.videos.insertOne({
  "_id":ObjectId("5d9e690ad76fe06a3d7ae416"),
 "videoPath" : "SampleVideo_1280x720_1mb.mp4" 
})

 

刚才我们手工插入数据到MongoDB, 假如用Docker-Compose命令重启后,数据就消失了.要重新插入

 

第4章例子3的 这个写法,为啥不会替换成变量值呢???  要用模板字符串的话不能用单双引号(" '),要用反引号(`)。Tab键上面那个。

console.log(`Translated id ${videoId} to path ${videoRecord.videoPath}.`); 

===============================================================================

数据库要防止Docker的集群里吗,建议不要,因为数据库是有状态的, 集群最好是没有状态的

每个微服务应该有自己的数据库,而且数据库不应该共享出来,共用数据库会导致事实上的耦合,系统脆弱很难扩容

==================================================================================

 The HTTP GET request received by the video-streaming microservice was forwarded to the video-storage microservice.

This is the first and simplest form of communication that one microservice can use to request or delegate tasks to another.

 

某些用例通常需要直接消息传递。 它有一个主要的缺点 它需要将位于两端的两个微服务紧密耦合沟通。 通常,我们更愿意避免微服务之间的紧密耦合,

因此,我们将频繁使用间接消息传递而不是直接消息

 

We direct the request to the history microservice using the URL http://history/viewed.

This URL incorporates both the hostname (history in this case) and the route (viewed in this case)

docker 自带DNS???  对应的是Container的名字, docker-compose.yml里面有多个history,我们把他们改名就知道对应哪一个了

 history0:
    image: history1
    build: 
      context: ./history
      dockerfile: Dockerfile-dev
    container_name: history3
    volumes:
      - /tmp/history/npm-cache:/root/.npm:z
      - ./history/src:/usr/src/app/src:z
    ports:
      - "4002:80"
    environment:
      - PORT=80
      - DBHOST=mongodb://db:27017
      - DBNAME=history
      - NODE_ENV=development
    depends_on:
      - db
    restart: "no"

 另外有个疑问,为什么我访问一次 http://localhost:4001/video,却会有2条history的记录呢?   第5章的第2个例子,查看F12,看到有2个请求,一个是document,一个是media

{"history":[{"_id":"61d5b4e00e2266003d3166f7","videoPath":"./videos/SampleVideo_1280x720_1mb.mp4"},{"_id":"61d5b4e00e2266003d3166f8","videoPath":"./videos/SampleVideo_1280x720_1mb.mp4"}]}

 

 Node.js 用到的Promise 例子,  ES6引入了Promise,ES2017引入了Async/Await都可以解决异步回调。

promise避免了回调地狱,它将callback inside callback改写成了then的链式调用形式。

但是链式调用并不方便阅读和调试。于是出现了async和await。

async和await将链式调用改成了类似程序顺序执行的语法,从而更加方便理解和调试。  一文秒懂nodejs中的异步编程

//
// Start the HTTP server.
//
function startHttpServer(messageChannel) {
    return new Promise(resolve => { // Wrap in a promise so we can be notified when the server has started.
        const app = express();
        setupHandlers(app, messageChannel);

        const port = process.env.PORT && parseInt(process.env.PORT) || 3000;
        app.listen(port, () => {
            resolve(); // HTTP server is listening, resolve the promise.
        });
    });
}

 

npx wait-port rabbit:5672  等待RabbitMQ的端口启动成功,再启动微服务

 

Multiple-recipient messages are one-to-many:

a message is sent from only a single microservice but potentially received by many others. This is a great way of publishing notifications within your application.

To make this work with RabbitMQ, we must now use a message exchange

 

 RabbitMQ最佳实践 - 无知者云 - 博客园

 RabbitMQ实战:消息通信模式和最佳实践 - 掘金

  RabbitMQ 可靠性、重复消费、顺序性、消息积压解决方案 

=================================================可靠性 处理方法=================================

  • 生产者消息没到交换机,相当于生产者弄丢消息
  • 交换机没有把消息路由到队列,相当于生产者弄丢消息
  • RabbitMQ 宕机导致队列、队列中的消息丢失,相当于 RabbitMQ 弄丢消息
  • 消费者消费出现异常,业务没执行,相当于消费者弄丢消息

企业并不会在(生产者弄丢消息)这两个监听里面去做重发,为什么呢?成本太高了

(RabbitMQ 弄丢消息)不开启持久化的情况下 RabbitMQ 重启之后所有队列和消息都会消失,所以我们创建队列时设置持久化,发送消息时再设置消息的持久化即可(设置 deliveryMode 为 2 就行了)。一般来说在实际业务中持久化是必须开的。

(消费者弄丢消息) 

  • 当消费失败后将此消息存到 Redis,记录消费次数,如果消费了三次还是失败,就丢弃掉消息,记录日志落库保存
  • 直接填 false ,不重回队列,记录日志、发送邮件等待开发手动处理

对于需要保证顺序消费的业务,我们可以只部署一个消费者实例,然后设置 RabbitMQ 每次只推送一个消息,再开启手动 ack 即可,prefetch: 1 #每次只推送一个消息

=====================================================================================

RabbitMQ 有3种模式 :单机模式,普通集群模式,镜像集群模式.

单机模式就是自己开发玩了, 普通集群模式就是消息只会存在某一台MQ上, 各个节点都保存一份交换机队列名的元数据,分内存节点和磁盘节点,磁盘节点最好要2个

镜像集群是元数据和消息都镜像保存, 但对带宽,磁盘消耗太多,基本就是个摆设...

 

kafka一个最基本的架构认识:多个broker组成,每个broker是一个节点;你创建一个topic,

这个topic可以划分为多个partition,每个partition可以存在于不同的broker上,每个partition就放一部分数据。

这就是天然的分布式消息队列,就是说一个topic的数据,是分散放在多个机器上的,每个机器就放一部分数据。

 ===================================================================================

Jest mock 通过替换Required的MongoDB,Express的库来达到模拟的目的,不用真正打开HTTP服务器和DataBase服务器

CyPress 自动测试,没有UI,  

=========================第10章=================================================

There are many techniques for ensuring reliability and fault tolerance of our microservices,

including replicas and load balancing, automatic restarts, timeouts, retries, job queues, and circuit breakers.

=========================第11章=====================================================

当你要扩容时, 当微服务和团队数量增加到一定程度后, 代码库Repo和CD要单独配置,但一开始小规模团队的时候不要.  

每个微服务Repo=>CD=>(Daily) DEV Machine =>(weekly) Staging Server =>(2 weeks) Prod Server with Blue/Green Strategy

当性能遇到瓶颈时, 首先你要监控知道性能瓶颈出现在哪里

 

 1.集群垂直扩容, 比如一个K8s有3个小VM,你可以升级为大VM 比如微软云的  Standard_B2ms 升级到 Standard_ B4ms

 2.集群水平扩容, 比如一个K8s有3个小VM,你可以增加到6个, 通常水平扩容的费用会比垂直的便宜

3. 微服务水平扩容 , 在Terraform的tf文件里设置 replicas = 3 ,为每个微服务开启3个实例 (是3个Pod,还是1个Pod里面3个容器呢?)

4. Node弹性扩容,根据负载来确定, 设定Node的大小,Node相当于虚拟机

default_node_pool {
 name = "default"
 vm_size = "Standard_B2ms"
 enable_auto_scaling = true 
 min_count = 3 
 max_count = 20
}

5. Pod 弹性扩容 

resource "kubernetes_horizontal_pod_autoscaler" "service_autoscaler" {
     metadata {
         name = var.service_name
     }
     spec {
         min_replicas = 3 
         max_replicas = 20

        scale_target_ref {
             kind = "Deployment"
             name = var.service_name
         }
     }
}
                          

 

posted on 2021-12-19 23:59  Gu  阅读(230)  评论(0编辑  收藏  举报