基于docker构建微服务架构


前言

文章简介

作为一个编程人员,不管是刚入门的,还是已经工作了几年的,都应该有自己的职业规划,一步一步去实现它。当你决定了自己将成为一个业界大牛,那么你首先得具备自己学习的能力,工作中能学习的东西其实很有限,而且你的领头也不会对你倾囊相授,大多数的朋友在工作中成长的很慢,要想超过别人,拿比别人更高的年薪,进更好的公司,我们就得在工作之余的时间里奋斗。根据三八理论,当别人在刷剧,在打游戏的时候,我们在敲代码,一年的时间下来差距可是很明显的。

适宜阅读的人群

1. 工作几年,技术遇到瓶颈,无法提升自己

2. 公司技术老套,无法接触新型技术,没有实战经验

3. 希望面试分布式、微服务相关职位

4. 希望了解架构方面的知识

5. 有一定这些中间件使用基础的,熟悉 linux 系统的

内容简介

本篇文章的所有操作将会基于 docker 部署,我将采用阿里云服务器安装 docker 服务,以下的操作都将基于 docker 进行:

1. 分布式缓存 redis 集群

2. zookeeper 集群

3. kafka 集群

4. mysql 集群

5. mycat 分库分表

6. springcloud 微服务搭建

----------

Docker

简介

什么是docker?`A container is a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another.`

简而言之docker就是一个容器,将代码、依赖、环境配置等打包,打包好的容器可以发布到任何流行的linux上,能做到一次配置,到处运行。

Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。

容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。

Docker 和传统虚拟化方式的不同之处,可见容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而传统方式则是在硬件层面实现。

docker 中主要有两个概念 image (镜像)、container (容器),镜像是只读的,可移植;容器是可写的,可执行的,我们运行的都是容器,在容器里配置我们的服务并启动,配置完整的容器可以 commit 成为新的镜像。举个例子说明,镜像就像是 redis 的官方压缩包,里面有它自己的一些默认配置,这些配置都是制作镜像的人自己配置的,而容器就是解压以后的 redis ,我们可以对其做定向修改并启动它,同时我们可以将修改了配置以后的 redis 再次压缩成为一个压缩包,它便成了一个新的镜像。

----------

安装docker

笔者是基于 centos7.6 安装 docker,7.0 以上的版本可以参考以下的操作安装:

删除有关docker的旧版本依赖:

        sudo yum remove docker \

                  docker-client \

                  docker-client-latest \

                  docker-common \

                  docker-latest \

                  docker-latest-logrotate \

                  docker-logrotate \

                  docker-engine

安装docker仓库:

    sudo yum install -y yum-utils \

      device-mapper-persistent-data \

      lvm2

设置docker仓库:

        sudo yum-config-manager \

        --add-repo \

        https://download.docker.com/linux/centos/docker-ce.repo

启用docker仓库:

    sudo yum-config-manager --enable docker-ce-nightly

安装最新的docker ce:

    sudo yum install docker-ce docker-ce-cli containerd.io

启动docker服务:

    sudo systemctl start docker

启动hello-world:

    sudo docker run hello-world

那么以上便成功地安装并启动了 docker 最后执行的命令`docker run hello-world`在我们学习一个新的工具的时候总会是 hello-world ,docker也给我们提供了 hello-world 的镜像供初学者学习,执行该命令以后会出现以下的提示:

    Unable to find image 'hello-world:latest' locally

    latest: Pulling from library/hello-world

    1b930d010525: Pull complete

    Digest: sha256:92695bc579f31df7a63da6922075d0666e565ceccad16b59c3374d2cf4e8e50e

    Status: Downloaded newer image for hello-world:latest

    Hello from Docker!

    This message shows that your installation appears to be working correctly.

此时我们已经成功的实现 docker 的第一个运用!注意,我们目前 docker 访问的仓库是国外的服务器,速度很慢,我们可以配置国内的地址将 /etc/docker/daemon/json 文件编写以下内容即可:

    {"registry-mirrors": ["https://registry.docker-cn.com"]}

当然,如果熟悉docker的有阿里云帐号的可以配置阿里云的服务器地址,甚至可以上传自己制作的镜像到阿里云,这里便不做过多赘述了。

----------

Docker的简单运用

在我们接下来的实战中将用 docker 进行很多操作,所以我得先简单介绍下后文将使用的 docker 的一些命令与 Dockerfile 的相关编写

image 命令:

- docker images : 查看docker镜像

- docker build :由Dockerfile构建镜像

- docker rmi :删除镜像

- docker create : 由镜像构建容器

- docker search : 搜索镜像

- docker pull : 拉取镜像

container 命令:

- docker ps : 查看容器

- docker rm : 删除容器

- docker attach: 连接正在运行的容器

- docker exec : 在容器中执行命令

- docker commit : 由容器构建新的镜像

- docker stop : 关闭容器

- ctrl + p + q : 退出容器(不会关闭容器)

- exit : 退出容器(会关闭容器)

Dockerfile 指令:

- FROM: 声明基于基础镜像,不可缺少,原始镜像为scratch;基于centos部署,from centos:7

- LABEL : 为镜像指定标签

- MAINTAINER : 指定镜像的作者

- RUN : 执行shell命令

- ENV : 设置环境变量

- ADD : 复制并解压文件(可以是网络资源)

- COPY : 复制文件(只能是本地资源)

- VOLUME: 挂载目录,容器使用 AUFS 文件系统,在容器关闭以后所有的更改都将丢失,应对的办法是将容器的目录挂载到源主机的目录做持久化

- CMD :容器启动时候执行命令(一个 dockerfile 只能有一个cmd命令生效,且容器启动命令带参数将会覆盖dockerfile中的命令)

- ENTRYPOINT : 功能同 CMD ,但是不会被覆盖,启动命令参数将会成为 ENTRYPOINT 命令的参数

以上便是 docker 的一些基础操作,与 dockerfile 指令的一些介绍,在接下来的实战中我将会用到这些命令,到时候会涉及到这些命令的具体用法与需要注意的地方,如果你对任何命令有不懂的地方执行 docker --help 或者 docker [具体指令比如 create ] --help ,那么都会有使用说明,只要多运用,你就可以无师自通!

----------

Redis

简介

什么是 redis ?`Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster`

简而言之 redis 就是一个基于内存的数据库,支持的数据结构包括 string、hash、list、set、可范围查询的有序 set、bitmap、hyperloglog、带有半径查询和流的空间索引;redis 具有内置的复制机制、Lua 脚本、lru 淘汰算法、事务和不同等级的硬盘持久化,redis 提供了高可用性、哨兵、集群自动分区。

redis 由于是基于内存的,所以广泛应用于分布式缓存,在官方的介绍中和很多项目中 redis 也可用作分布式锁,但比较熟悉 redis 的朋友可能知道,redis 是 ap 模型,作为分布式锁有很多棘手的问题比如:

1. 续约问题:在设置的过期时间内如果无法处理完业务,难以续约

2. 主从复制问题: redis 的数据同步是异步的,如果一个线程获取了锁,此时主服务器宕机,从服务器没来得及备份便选举成为了主服务器。第一个线程已经在处理中,现在来了第二个线程,访问到刚升级的主服务器的同一个锁,便会出现不安全的问题。

3. 其他不可确定的问题: java 中的 fullgc 是不可预料的,可能发生在任何时间,如果在一个线程获取锁的过程中,刚好发生了 fullgc ,fullgc可能长达几分钟甚至十多分钟,远远超过 key 的过期时间,这时候其他线程便可以拿到锁,也会出现不安全的问题。

redis 实现的分布式锁不是完全安全的,不是非常严格的情况下仍然可以采用,如果要严格的控制锁的安全,可以采用 zookeeper ,zookeeper 是 cp 模型,对一致性的实现更加完美,没有续约问题,创建的临时节点手动删除,如果服务器宕机则自动删除,解决了redis难以解决的过期时间的问题,也不会出现 redis 分布式锁的不确定因素导致的安全性问题。缺点是可用性比不上 redis ,在 zookeeper 选举的过程中服务是不可用的,即任何线程都无法获取锁。

----------

Redis的Docker部署

在 dockerhub 上其实已经有redis的官方镜像redis/Dockerfile at dcc0a2a343ce499b78ca617987e8621e7d31515b · docker-library/redis · GitHub可见,redis镜像是基于 debian 的 dockerhub 上下载的 redis 官方镜像只有 100MB 左右,而一个 centos 镜像已经 200MB 了,所以这里,我不打算基于 centos 构建 redis 镜像。

首先我们得知道 redis 的官方 docker 镜像是什么样的。Docker Hub第一个便是官方的redis镜像!

点击最新版本的 redis 查看 dockerfile ,根据上面介绍的 dockerfile 的一些指令,大家应该对该 dockerfile 的意义不难理解了:

    FROM debian:stretch-slim

    # add our user and group first to make sure their IDs get assigned consistently, regardless of whatever    dependencies get added

    RUN groupadd -r redis && useradd -r -g redis redis

    # grab gosu for easy step-down from root

    # https://github.com/tianon/gosu/releases

    ENV GOSU_VERSION 1.10

    RUN set -ex; \

    \

    fetchDeps=" \

    ca-certificates \

    dirmngr \

    gnupg \

    wget \

    "; \

    apt-get update; \

    apt-get install -y --no-install-recommends $fetchDeps; \

    rm -rf /var/lib/apt/lists/*; \

    \

    dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \

    wget -O /usr/local/bin/gosu    "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \

    wget -O /usr/local/bin/gosu.asc    "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \

    export GNUPGHOME="$(mktemp -d)"; \

    gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys    B42F6819007F00F88E364FD4036A9C25BF357DD4; \

    gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \

    gpgconf --kill all; \

    rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc; \

    chmod +x /usr/local/bin/gosu; \

    gosu nobody true; \

    \

    apt-get purge -y --auto-remove $fetchDeps

    ENV REDIS_VERSION 5.0.4

    ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-5.0.4.tar.gz

    ENV REDIS_DOWNLOAD_SHA    3ce9ceff5a23f60913e1573f6dfcd4aa53b42d4a2789e28fa53ec2bd28c987dd

    # for redis-sentinel see: http://redis.io/topics/sentinel

    RUN set -ex; \

    \

    buildDeps=' \

    ca-certificates \

    wget \

    \

    gcc \

    libc6-dev \

    make \

    '; \

    apt-get update; \

    apt-get install -y $buildDeps --no-install-recommends; \

    rm -rf /var/lib/apt/lists/*; \

    \

    wget -O redis.tar.gz "$REDIS_DOWNLOAD_URL"; \

    echo "$REDIS_DOWNLOAD_SHA *redis.tar.gz" | sha256sum -c -; \

    mkdir -p /usr/src/redis; \

    tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \

    rm redis.tar.gz; \

    \

    # disable Redis protected mode [1] as it is unnecessary in context of Docker

    # (ports are not automatically exposed when running inside Docker, but rather explicitly by specifying -p    / -P)

    # [1]: https://github.com/antirez/redis/commit/edd4d555df57dc84265fdfb4ef59a4678832f6da

    grep -q '^#define CONFIG_DEFAULT_PROTECTED_MODE 1$' /usr/src/redis/src/server.h; \

    sed -ri 's!^(#define CONFIG_DEFAULT_PROTECTED_MODE) 1$!\1 0!' /usr/src/redis/src/server.h; \

    grep -q '^#define CONFIG_DEFAULT_PROTECTED_MODE 0$' /usr/src/redis/src/server.h; \

    # for future reference, we modify this directly in the source instead of just supplying a default    configuration flag because apparently "if you specify any argument to redis-server, [it assumes] you are    going to specify everything"

    # see also https://github.com/docker-library/redis/issues/4#issuecomment-50780840

    # (more exactly, this makes sure the default behavior of "save on SIGTERM" stays functional by    default)

    \

    make -C /usr/src/redis -j "$(nproc)"; \

    make -C /usr/src/redis install; \

    \

    rm -r /usr/src/redis; \

    \

    apt-get purge -y --auto-remove $buildDeps

    RUN mkdir /data && chown redis:redis /data

    VOLUME /data

    WORKDIR /data

    COPY docker-entrypoint.sh /usr/local/bin/

    ENTRYPOINT ["docker-entrypoint.sh"]

    EXPOSE 6379

    CMD ["redis-server"]

1. FROM 是基于基础镜像,debian 是一款 linux 系统,它的镜像比 centos 还精简一半有余,所以 redis 官方采用了灵活小巧的 debian

2. ENV 指令都是设置一些环境变量

3. RUN 就是执行 shell 命令,shell 命令跟操作系统有关系,这里的 debian 跟 centos 的命令便有一些不一样的地方

4. WORKDIR 是设置工作目录,当我们启动容器以后会默认进入该目录

5. VOLUME 是设置挂载目录,这里 dockerfile 中设置的挂在目录只能指定容器中的目录,主机的目录会自动分配,我们可以根据命令 docker inspect 容器id 来查看对应的挂载目录

6. COPY 是复制文件到容器文件夹下

7. EXPOSE 是暴露容器端口,一般与 redis 配置文件中的端口对应

8. ENTRYPOINT 和 CMD 上面已经介绍过,CMD 执行的命令可以被覆盖,一个 Dockerfile 只能执行一个 CMD 命令,如果在 docker run 后面增加参数,参数将会覆盖 CMD 的指令参数;此时有 ENTRYPOINT 和 CMD 都是在容器启动时候执行命令,那么这时候生效的只有 ENTRYPOINT ,且 CMD 的指令将会作为参数附加给 ENTRYPOINT ,如果存在 ENTRYPOINT 和 CMD 命令了,又在 docker run 后面增加了新的参数,那么新的参数将覆盖 CMD 的指令,并作为 ENTRYPOINT 指令的参数执行

----------

现在是不是很容易理解 redis 的 dockerfile 了?可以看到官方的镜像最后只是运行了 redis-server 甚至没有指定任何配置文件,而我们要做的就是在启动 redis 的时候指定自己的配置文件,那就很简单了,首先我们得配置集群相关的配置文件,然后将配置文件复制到挂载目录中,最后指定配置文件启动。由于配置文件内容过多,我便只说明相关的需要修改的,其他的配置项不动

    #注释掉绑定的ip

    #bind 127.0.0.1

    #设置端口

    port 7001

    #设置密码

    requirepass 123456

    #设置主服务器的密码,用于从服务器连接主服务器(我们将所有服务器都统一一个密码)

    masterauth 123456

    #设置日志名称

    logfile redis-7001.log

    #配置可集群

    cluster-enabled yes

    #配置集群节点配置文件(配置名称就好,集群会自动生成)

    cluster-config-file nodes-7001.conf

    #集群节点连接超时时间

    cluster-node-timeout 15000

    #开启aof持久化策略

    appendonly yes

    #设置进程文件

    pidfile /var/run/redis_7001.pid

那么以上便是一个节点的需要修改的一些配置项。我将会搭建一个 1:1 的 3 主 3 从的 redis 集群,由于采用了 docker ,所以我将会在一台 linux 服务器上搭建集群,换作多台服务器的话操作也是一样的。那么首先建立一个存放配置文件的文件夹,因为这些配置我们可能会作多次修改,所以得挂载到源服务器。6 个节点分别为 7001、7002、7003、7004、7005、7006,将以上修改的配置中7001的部分修改成节点对应的编号便好。

此时不急着启动 redis 节点,让我们再想想redis的集群还需要什么,再回忆一下,redis 的 dockerfile 中有没有什么值得思考的点......,对了!redis 的 dockerfile 中只暴露了 6379 端口,而 redis 的集群只需要一个端口吗?显然不是,6379 是 client 端访问 server 的端口,节点之间还得传输数据,使用的是另外一个端口,默认的是在 client 端口加 10000 ,即配置 port 为 6379 那么节点交互的端口为 16379 ,如果我们的宿主机不暴露 16379 ,那么节点之间是不能通信的,除非将整个集群都配置到一台宿主机上,这显然不是一个正式项目该有的样子。那么接下来我们对原 redis 的 docker 镜像做点定制吧,暴露 7001~7006 和 17001~17006 端口,所以我们写第一个简单的 dockerfile :

    FROM redis

    EXPOSE 7001 7002 7003 7004 7005 7006 17001 17002 17003 17004 17005 17006

    CMD ["redis-server"]

接下来我们基于该Dockerfile制作我们自己的redis

    docker build -t myredis .

上面这行命令是由 Dockerfile 构建一个镜像 -t是指定镜像名称和标签 格式为 镜像名称:标签号 ,如果不写标签号则默认为 latest (最新),注意,运行该命令的目录下必须要有 Dockerfile,名字也不能错,当然你可以自己指定一个文件 -f 文件名 命令最后有一个点不要忘记了。

docker images 查看本地镜像,可以看到我们刚制作的myredis镜像,我们只是在原 redis 镜像的基础上暴露了几个用于集群的端口。

将所有节点的配置文件准备好以后,接下来我们直接执行 docker run,docker 会找到最新版本的redis

    docker run --name redis-7001 -d -v /data/redis-cluster/redis-7001.conf:/etc/redis/redis.conf -v /data/redis-cluster/7001/:/data -p 7001:7001-p17001:17001 myredis redis-server /etc/redis/redis.conf

以上这段命令能创建出 redis-7001 的容器,并启动它,docker run 其实包含两个操作 docker create 创建容器 和 docker start 启动容器,--name 指定容器名称,如果不指定,那么会由 docker 自动创建一个名称,-d 表示后台运行,-v 表示指定挂载的目录 源主机目录:宿主机目录,-p 是指定端口 源主机端口:宿主机端口。后面的两个参数 redis-server /etc/redis/redis.conf 大家明白是怎么回事了吗?没明白的话再去看看 redis 的 dockerfile ,如果你能自己搞明白这是怎么回事,那么你的学习能力是非常好的,记得上面说过, redis 的 dockerfile 最后执行的是 CMD redis-server 那么这里的 redis-server /etc/redis/redis.conf 实际上是覆盖了 CMD 的操作,并且将 redis-server /etc/redis/redis.conf 作为参数,传给了ENTRYPOINT,所以最后执行的命令是 docker-entrypoint.sh redis-server /etc/redis/redis.conf。

将上面 docker run 的命令修改一下,便可创建并启动 6 个节点的 redis 。成功启动 6 个节点以后目前 redis 还不可用,redis 的集群采用了 hash slot 的概念,整个集群有 0-16383 共 16384 个 hash 槽,首先得根据主服务器的数量均分 hash 槽,比如目前有 3 个主节点,节点 1 的 hash 槽为 0-5460 ,节点 2 的 hash 槽为 5461-10922 ,节点 3 的 hash 槽为 10923-16383 。整个集群得保证 0-16383 的所有 slot 都可用能被访问才可用,如果有任意主节点宕机了,它的从节点可以升级为主节点,也可用,但如果唯一的主节点(已经没有了从节点)也宕机了,那么整个集群服务是不可用的,即使访问的是没有宕机的节点的 hash 槽。

当我们启动了所有的节点可以用 docker ps 查看正在运行的容器,如果没有找到 redis ,那么就是启动失败了,可以用 docker ps -a 命令查看所有的容器,至于启动失败的原因,99% 是因为配置文件的问题,再看看上面说的点,是不是哪里的配置遗漏了。

----------

创建集群

此时的 redis 还不可用,hash 槽还没有分配,所以接下来我们得创建集群,在 redis3、4 版本需要使用 redis-trib.rb 建立集群,在 redis5 中可以直接使用 redis-cli 建立集群

docker run --name redis-cluster -it myredis redis-cli -a 123456 --cluster create ip:7001 ip:7002 ip:7003 ip:7004 ip:7005 ip:7006 --cluster-replicas 1

以上这段命令 docker run 大家不会再陌生了,-it 是让我们目前的服务器能交互访问容器 在 myredis 后面的参数都将成为覆盖 CMD 的参数附加给 ENTRYPOINT ,进入容器找到 ENTRYPOINT 执行命令的目录我们可以看到该目录有 redis 的执行文件,我们可以执行这些文件的命令!

如此便成功创建了 redis 集群。

----------

连接集群

接下来我们测试集群

docker run --name redis-cli -it myredis redis-cli -c -h ip -p 7001 -a 123456

连接到 7001 节点,这时候不管 get 还是 set 任何 key 都会根据 key 哈希映射到某个节点上,如此便成功了。

----------

Zookeeper

简介

ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 的重要组件,CDH 版本中更是使用它进行 Namenode 的协调控制。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。ZooKeeper 的目标就是封装好复杂易出错的关键服务,提供简单易用的接口和性能高效、功能稳定的系统提供给用户。分布式应用程序可以基于它实现同步服务,配置维护和命名服务等

在 zookeeper 中有 3 种角色,leader、follower、observer;leader 负责发起投票和决议,follower 用于接收客户端请求并返回结果,在选举过程中参与投票,observer 用于接收客户端请求并返回结果,与 follower 不同的是它不参与投票,observer 的目的是扩展系统,提高 zookeeper 集群的并发性。

----------

Zookeeper的Docker部署

不管用 docker 做任何镜像,我们都得先去 dockerhub 上看看有没有官方的镜像,zookeeper 也有官方的镜像,首先得研究下官方 Dockerfile 做了什么,暴露了哪些端口,挂载了哪些目录等,很容易能看到 zookeeper 的官方镜像中暴露了 2181、2888、3888 端口,挂载了目录文件夹和数据文件夹。zookeeper 比 redis 多一个使用的端口,2181 是供客户端访问的端口,2888 是节点通信的端口,而 3888 是选举 leader 的端口。我们需要的几乎官方的镜像都做了配置,所以我们可以直接用官方镜像创建 zookeeper 的集群。

首先启动一个 zookeeper 容器 :

docker run -d --name zookeeper zookeeper

进入容器 :

docker exec -it zookeeper /bin/bash

查看/下的 docker-entrypoint.sh:

#!/bin/bash

set -e

# Allow the container to be started with `--user`

if [[ "$1" = 'zkServer.sh' && "$(id -u)" = '0' ]]; then

    chown -R "$ZOO_USER" "$ZOO_DATA_DIR" "$ZOO_DATA_LOG_DIR" "$ZOO_LOG_DIR" "$ZOO_CONF_DIR"

    exec su-exec "$ZOO_USER" "$0" "$@"

fi

# Generate the config only if it doesn't exist

if [[ ! -f "$ZOO_CONF_DIR/zoo.cfg" ]]; then

CONFIG="$ZOO_CONF_DIR/zoo.cfg"

echo "clientPort=$ZOO_PORT" >> "$CONFIG"

echo "dataDir=$ZOO_DATA_DIR" >> "$CONFIG"

echo "dataLogDir=$ZOO_DATA_LOG_DIR" >> "$CONFIG"

echo "tickTime=$ZOO_TICK_TIME" >> "$CONFIG"

echo "initLimit=$ZOO_INIT_LIMIT" >> "$CONFIG"

echo "syncLimit=$ZOO_SYNC_LIMIT" >> "$CONFIG"

echo "autopurge.snapRetainCount=$ZOO_AUTOPURGE_SNAPRETAINCOUNT" >> "$CONFIG"

echo "autopurge.purgeInterval=$ZOO_AUTOPURGE_PURGEINTERVAL" >> "$CONFIG"

echo "maxClientCnxns=$ZOO_MAX_CLIENT_CNXNS" >> "$CONFIG"

for server in $ZOO_SERVERS; do

    echo "$server" >> "$CONFIG"

done

fi

# Write myid only if it doesn't exist

if [[ ! -f "$ZOO_DATA_DIR/myid" ]]; then

    echo "${ZOO_MY_ID:-1}" > "$ZOO_DATA_DIR/myid"

fi

exec "$@"

可以看到,该脚本主要是用于设置启动zookeeper的用户,以及创建zoo.cfg配置文件。

其中有一段是:

for server in $ZOO_SERVERS; do

        echo "$server" >> "$CONFIG"

    done

# Write myid only if it doesn't exist

if [[ ! -f "$ZOO_DATA_DIR/myid" ]]; then

    echo "${ZOO_MY_ID:-1}" > "$ZOO_DATA_DIR/myid"

fi

即只要传入 ZOO_SERVERS 参数,我们就可以设置各个 zookeeper server节点的 host 信息,设置 ZOO_MY_ID 参数就可以写入本节点的serverID。

----------

创建集群

这里我们首先创建一个 docker 网络,将 zookeeper 节点都部署到该网络上,节点之间便可通过宿主机交互

docker network create zl_net

接下来只需要启动容器便可:

docker run -d -e ZOO_SERVERS="server.1=zk1:2888:3888 server.2=zk2:2888:3888 server.3=zk3:2888:3888" -e ZOO_MY_ID=1 --name zk1 --net zk_net -p 2181:2181 zookeeper

docker run -d -e ZOO_SERVERS="server.1=zk1:2888:3888 server.2=zk2:2888:3888 server.3=zk3:2888:3888" -e ZOO_MY_ID=2 --name zk2 --net zk_net -p 2182:2181 zookeeper

docker run -d -e ZOO_SERVERS="server.1=zk1:2888:3888 server.2=zk2:2888:3888 server.3=zk3:2888:3888" -e ZOO_MY_ID=3 --name zk3 --net zk_net -p 2183:2181 zookeeper

如此,zookeeper 集群便完成了。

----------

连接集群

docker run --name zk-cli -it zkCli.sh -server 源主机ip:2181

zookeeper 集群已经ok了,接下来就看大家自由发挥了!

----------

Kafka

简介

Kafka 是一种分布式的,基于发布/订阅的消息系统。主要设计目标如下:

1. 以时间复杂度为 O(1) 的方式提供消息持久化能力,并保证即使对 TB 级以上数据也能保证常数时间的访问性能高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒 100K 条消息的传输

2. 支持 Kafka Server 间的消息分区,及分布式消息消费,同时保证每个 partition 内的消息顺序传输

3. 同时支持离线数据处理和实时数据处理

kafka是mq产品中比较典型的一款,而我们在实际项目中对mq的选型得根据具体的业务场景来,其他mq产品比较好的包括rocketmq、rabbitmq等。

----------

Kafka的Docker部署

首先还是得去 dockerhub 上搜索看有没有kafka的官方镜像,很遗憾 kafka 至今还没有官方的 docker 镜像,但有很多其他开发人员上传的镜像,有兴趣可以去研究一下。既然没有官方的镜像,那我们便自己制作一个。

kafka 是基于 zookeeper 的,上面我们已经成功部署了 zookeeper 的集群,那么接下来的 kafka 就采用上面部署的 zookeeper 作为注册中心。

首先去官网下载 kafka 到服务器,启动 kafka 需要 jdk 环境,所以我们还得下载 jdk 到服务器(也可以在 dockerfile 中用 wget 到宿主机,但是为了节约重试的时间,我们直接下载到源主机)

编写 dockerfile :

FROM centos

ADD kafka_2.11-2.2.0.tgz /

ADD jdk-8u202-linux-x64.tar.gz?AuthParam=1556516170_6056f2093c9005cb80d3fac5d8050f3a /usr/local/

ENV JAVA_HOME /usr/local/jdk1.8.0_202

ENV CLASSPATH ${JAVA_HOME}/lib/

ENV PATH $PATH:${JAVA_HOME}/bin

RUN mv /kafka_2.11-2.2.0 /kafka

WORKDIR /kafka/bin

EXPOSE 9092 9093 9094

CMD ["/kafka/bin/kafka-server-start.sh","/kafka/config/server.properties"]

该 dockerfile 只是安装了 kafka 运行的环境,也没有挂载配置文件( dockerfile 中挂载配置文件不能指定源主机的目录),所以我们得事先准备 3 个 properties 文件,在我们下载的 kafka 压缩包中的 config 下的 server.properties ,将 server.properties 复制到专门存放配置文件的目录,我将它放在 /data/kafka/ 下

配置内容如下:

broker.id=1

listeners=PLAINTEXT://:9092

num.network.threads=3

num.io.threads=8

socket.send.buffer.bytes=102400

socket.receive.buffer.bytes=102400

socket.request.max.bytes=104857600

log.dirs=/tmp/kafka-logs

num.partitions=1

num.recovery.threads.per.data.dir=1

offsets.topic.replication.factor=1

transaction.state.log.replication.factor=1

transaction.state.log.min.isr=1

log.retention.hours=168

log.segment.bytes=1073741824

log.retention.check.interval.ms=300000

zookeeper.connect=zk1:2181,zk2:2181,zk3:2181

zookeeper.connection.timeout.ms=10000

group.initial.rebalance.delay.ms=0

zookeeper.connect 我们配置了 zk1,zk2,zk3 意味着 kafka 必须和 zookeeper 在同一个网络中, broker.id 每个节点的配置必须是唯一的。

创建 kafka 的镜像:

docker build -t kafka .

注意:kafka镜像需要的jdk和kafka压缩包都得跟Dockerfile同目录

----------

创建集群

创建好镜像便直接启动容器:

docker run --name ka1 -p 9092:9092 -v /data/kafka/ka-1.properties:/kafka/config/server.properties -it --net zk_net kafka

docker run --name ka2 -p 9093:9093 -v /data/kafka/ka-2.properties:/kafka/config/server.properties -it --net zk_net kafka

docker run --name ka3 -p 9094:9094 -v /data/kafka/ka-3.properties:/kafka/config/server.properties -it --net zk_net kafka

----------

连接测试

正常情况下能成功启动 kafka 集群了,如果没有启动成功,可能是配置文件的问题,也可能是内存不足了,这种情况下关掉没用的进程或容器,也可以用单点 zookeeper 进行 kafka 集群的测试。

现在我们可以进行创建 topic :

docker run --name ka-topic -it kafka ./kafka-topics.sh --create --zookeeper 源主机ip:2181 --replication-factor 1 --partitions 1 --topic test

我们最好还是进入开启的 kafka 服务端容器进行后面的测试

docker exec -it ka1 /bin/bash

./kafka-topics.sh --list --zookeeper zk1:2181

生产消息

./kafka-console-producer.sh --broker-list localhost:9092 --topic test

消费消息(新打开一个终端进入另一个kafka节点容器)

docker exec -it ka2 /bin/bash

./kafka-console-consumer.sh --bootstrap-server localhost:9093 --topic test --from-beginning

现在已经成功的消费了消息,并且是在集群下消费。

----------

Mysql

简介

MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件。相信大家对 mysql 再熟悉不过了,工作中一定没少用到。

----------

Mysql的Docker部署

dockerhub 中也有官方的 mysql 镜像,而且部署主从的 mysql 也非常简单,不过我们今天使用其他的方式来完成 mysql 的集群 —— percona。

Percona Server 为 MySQL 数据库服务器进行了改进,在功能和性能上较 MySQL 有着很显著的提升。该版本提升了在高负载情况下的 InnoDB 的性能、为 DBA 提供一些非常有用的性能诊断工具;另外有更多的参数和命令来控制服务器行为。percona 官方推出了 percona-xtradb-cluster 镜像用于数据库的集群。

首先拉取 percona-xtradb-cluster 镜像:

docker pull percona/percona-xtradb-cluster

将镜像改个简单的名字:

docker tag percona/percona-xtradb-cluster pxc

创建网络,让所有的数据库都在同一网络上:

docker network create --subnet=172.18.0.0/16 net1

创建第一个mysql节点:

docker run -d -p 30001:3306 -e MYSQL_ROOT_PASSWORD=db123456 -e CLUSTER_NAME=clu -e XTRABACKUP_PASSWORD=db123456 --privileged --name=db1 --net=net1 --ip 172.18.10.11 pxc

创建第二个mysql节点:

docker run -d -p 30002:3306 -e MYSQL_ROOT_PASSWORD=db123456 -e CLUSTER_NAME=clu -e XTRABACKUP_PASSWORD=db123456 -e CLUSTER_JOIN=db1 --privileged --name=db2 --net=net1 --ip 172.18.10.12 pxc

创建第三个mysql节点:

docker run -d -p 30003:3306 -e MYSQL_ROOT_PASSWORD=db123456 -e CLUSTER_NAME=clu -e XTRABACKUP_PASSWORD=db123456 -e CLUSTER_JOIN=db1 --privileged --name=db3 --net=net1 --ip 172.18.10.13 pxc

percona-xtradb-cluster 镜像有几个配置参数: CLUSTER_NAME、MYSQL_ROOT_PASSWORD、MYSQL_ALLOW_EMPTY_PASSWORD、MYSQL_RANDOM_ROOT_PASSWORD、XTRABACKUP_PASSWORD

CLUSTER_NAME 是必须指定的,集群名称;MYSQL_ROOT_PASSWORD 是 mysql root 用户的初始密码;MYSQL_ALLOW_EMPTY_PASSWORD 是否允许 root 用户的密码为空,该参数对应的值为: yes;MYSQL_RANDOM_ROOT_PASSWORD 为 root 用户生成随机密码;XTRABACKUP_PASSWORD是从节点的参数,备份数据的账户的密码,账户为 xtrabackup 。

----------

测试连接

目前一个 mysql 集群便成功搭建好了,可以使用可视化连接 mysql 测试是否能成功同步数据。在上面的命令中没有使用 -v 挂载数据库的目录,原因是在mysql的镜像中是默认做了目录的挂载包括 /var/lib/mysql、/varlog/mysql 目录,如果我们再指定这些目录挂载,那么就会出现数据紊乱,下次启动 mysql 便可能会崩溃,如果出现这种情况,先将容器删除了,再清理 volume 便可,docker volume prune 清理没有用的挂载数据。

----------

Mycat

简介

MyCat 是一个彻底开源的,面向企业应用开发的大数据库集群,支持事务、ACID、可以替代MySQL的加强版数据库,可以视为MySQL集群的企业级数据库,用来替代昂贵的Oracle集群,它是融合内存缓存技术、NoSQL技术、HDFS大数据的新型SQL Server,它是结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品,一个新颖的数据库中间件产品。用最简单的话说,mycat就是一款解决分布式数据库的中间件,而分布式数据库包括数据库的主从集群、分库、分表,这些操作都是为了解决海量数据的存储问题和数据的查询、修改的效率问题。

----------

Docker部署Mycat

在 dockerhub 上没有 mycat 的官方镜像,我们得自己来制作一个 mycat 镜像,那么首先我们的去[官方网站](http://www.mycat.io/)下载 mycat ,mycat 需要基于 jdk 环境,所以我们还需要 jdk ,接下来编写 dockerfile :

FROM centos

RUN yum -y install net-tools

# install java

ADD ./jdk-8u202-linux-x64.tar.gz?AuthParam=1556516170_6056f2093c9005cb80d3fac5d8050f3a /usr/local/

ENV JAVA_HOME /usr/local/jdk1.8.0_202

ENV CLASSPATH ${JAVA_HOME}/lib/

ENV PATH $PATH:${JAVA_HOME}/bin

ENV WORK_PATH /usr/local/mycat

WORKDIR $WORK_PATH

#install mycat

ADD Mycat-server-1.6.6.1-release-20181031195535-linux.tar.gz /usr/local/

VOLUME /usr/local/mycat/conf

EXPOSE 8066 9066

ENTRYPOINT ["/usr/local/mycat/mycat"]

CMD ["start"]

可以看到,该镜像暴露出了 8066 和 9066 端口,8066 是 mycat 的客户端口,访问 8066 ,你就相当于访问一个正常的数据库,mycat 会对你的 sql 做解析最后返回正确的结果。9066 是 mycat 的管理端口,访问 9066 ,你就相当于访问了一个做 mysql 管理的服务器。

创建 mycat 镜像:

docker build -t mycat .

目前我们需要了解两个地方的配置 conf 下的 server.xml与schema.xml。

----------

创建mysql集群

我们将使用 3 个 mysql 集群,每个集群只需要两个节点就行了,创建 3 个集群:

创建网络( mycat 必须和 mysql 在同一网络上,即 mycat 能直接访问到 mysql ):

docker network create --subnet=192.18.0.0/16 net2

docker run -d -p 31001:3306 -e MYSQL_ROOT_PASSWORD=db123456 -e CLUSTER_NAME=mysql -e XTRABACKUP_PASSWORD=db123456 --privileged --name=db11 --net=net2 --ip 192.18.10.11 pxc

docker run -d -p 31002:3306 -e MYSQL_ROOT_PASSWORD=db123456 -e CLUSTER_NAME=mysql -e XTRABACKUP_PASSWORD=db123456 -e CLUSTER_JOIN=db11 --privileged --name=db12 --net=net2 --ip 192.18.10.12 pxc

docker run -d -p 32001:3306 -e MYSQL_ROOT_PASSWORD=db123456 -e CLUSTER_NAME=mysql -e XTRABACKUP_PASSWORD=db123456 --privileged --name=db21 --net=net2 --ip 192.18.10.13 pxc

docker run -d -p 32002:3306 -e MYSQL_ROOT_PASSWORD=db123456 -e CLUSTER_NAME=mysql -e XTRABACKUP_PASSWORD=db123456 -e CLUSTER_JOIN=db21 --privileged --name=db22 --net=net2 --ip 192.18.10.14 pxc

docker run -d -p 33001:3306 -e MYSQL_ROOT_PASSWORD=db123456 -e CLUSTER_NAME=mysql -e XTRABACKUP_PASSWORD=db123456 --privileged --name=db31 --net=net2 --ip 192.18.10.15 pxc

docker run -d -p 33002:3306 -e MYSQL_ROOT_PASSWORD=db123456 -e CLUSTER_NAME=mysql -e XTRABACKUP_PASSWORD=db123456 -e CLUSTER_JOIN=db31 --privileged --name=db32 --net=net2 --ip 192.18.10.16 pxc

在启动集群之后节点之间会做数据同步可能会等上一段时间才能登录到 mysql 。

创建 mycat 容器:

docker run --name mycat --net net2 -p 8066:8066 -p 9066:9066 -itd mycat

创建容器以后 mycat 并没有真正的启动,因为在我们的 dockerfile 中没有配置启动脚本,首先我们要做的是修改 mycat 配置文件。

查看容器信息:

docker inspect mycat

在一大堆json信息中找到以下信息:

"Mounts": [

        {

            "Type": "volume",

            "Name": "7f310f6926ecb09520ae7cfcbf2fb15144a9e0be2576d01fc3c2de1f27d7ee57",

            "Source": "/var/lib/docker/volumes/7f310f6926ecb09520ae7cfcbf2fb15144a9e0be2576d01fc3c2de1f27d7ee57/_data",

            "Destination": "/usr/local/mycat/conf",

            "Driver": "local",

            "Mode": "",

            "RW": true,

            "Propagation": ""

        }

    ]

看到 type volume 大家都知道是什么意思了吧,这是容器的挂载目录,我们将 mycat 的配置文件挂载到了源服务器,那么去修改配置文件吧,server.xml 不必多说,主要是 schema.xml :

<?xml version="1.0"?>

<!DOCTYPE mycat:schema SYSTEM "schema.dtd">

<mycat:schema xmlns:mycat="http://io.mycat/">

<schema name="test" checkSQLschema="false" sqlMaxLimit="100">

<!-- auto sharding by id (long) -->

<table name="user" dataNode="dn1,dn2,dn3" rule="mod-long" />

</schema>

<dataNode name="dn1" dataHost="host1" database="test" />

<dataNode name="dn2" dataHost="host2" database="test" />

<dataNode name="dn3" dataHost="host3" database="test" />

<!--<dataNode name="dn3" dataHost="host3" database="test" />-->

<dataHost name="host1" maxCon="1000" minCon="10" balance="0"

  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">

<heartbeat>select user()</heartbeat>

<!-- can have multi write hosts -->

<writeHost host="hostw1" url="192.18.10.11:3306" user="root"

  password="db123456">

<!-- can have multi read hosts -->

<readHost host="hostr1" url="192.18.10.12:3306" user="root" password="db123456" />

</writeHost>

</dataHost>

<dataHost name="host2" maxCon="1000" minCon="10" balance="0"

  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">

<heartbeat>select user()</heartbeat>

<!-- can have multi write hosts -->

<writeHost host="hostw2" url="192.18.10.13:3306" user="root"

  password="db123456">

<!-- can have multi read hosts -->

<readHost host="hostr2" url="192.18.10.14:3306" user="root" password="db123456" />

</writeHost>

</dataHost>

<dataHost name="host3" maxCon="1000" minCon="10" balance="0"

  writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">

<heartbeat>select user()</heartbeat>

<!-- can have multi write hosts -->

<writeHost host="hostw3" url="192.18.10.15:3306" user="root"

  password="db123456">

<!-- can have multi read hosts -->

<readHost host="hostr3" url="192.18.10.16:3306" user="root" password="db123456" />

</writeHost>

</dataHost>

</mycat:schema>

以上的配置信息应该很易懂了这里只配置了一个表供测试使用,不过这个表得我们手动创建,随便你怎么创建,在每个集群的主节点创建便好了,数据结构得一样,每个节点的数据库名称得对应 dataNode 的配置。

配置了以上的可能还不够,在我自己配置的途中,出现了内存不够的情况,为了防范内存不够报错,我们再去配置下 wrapper.conf :

wrapper.java.additional.3=-XX:MaxPermSize=256M

wrapper.java.initmemory=20

wrapper.java.maxmemory=512

基本上以上的配置够目前使用了,如果报错的话,那就看日志对症下药吧

最后我们在容器中启动 mycat :

docker exec -it mycat bash

cd bin

./mycat start

那么顺利的话现在 mycat 和 3 个 mysql 集群便成功的启动了,赶紧去测试吧!

----------

SpringCloud

简介

目前最流行的微服务框架springcloud、dubbo相信大家都比较了解,而springcloud由于有完整的分布式治理体系,那么我们要实现的是 docker 部署 springcloud 项目,相信你现在对此应该已经有自己的思路了,只需要一台 centos 一个 jdk 环境就够了不是吗?甚至连 tomcat 都不需要,当然如果你有其他需求的话可以自己在配置个 tomcat 的环境。

为了模拟一个服务高可用的性能我们需要至少3个服务来实现:服务注册中心、生产服务、消费服务,因为我们需要保证整个服务环境是高可用的,那么以上3个服务都至少得部署两套。

----------

注册服务

build.gradle :

plugins {

    id 'org.springframework.boot' version '2.1.3.RELEASE'

    id 'java'

}

apply plugin: 'io.spring.dependency-management'

group = 'cn.jk'

version = '0.0.1-SNAPSHOT'

sourceCompatibility = '1.8'

repositories {

    mavenCentral()

}

ext {

    set('springCloudVersion', 'Greenwich.SR1')

}

dependencies {

    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'

}

dependencyManagement {

    imports {

        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"

    }

}

启动类:

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication

@EnableEurekaServer

public class EurekaServerApplication {

    public static void main(String[] args) {

        SpringApplication.run(EurekaServerApplication.class, args);

    }

}

bootstrap.yml:

spring:

  application:

    name: eureka-server

server:

  port: 8080

eureka:

  client:

    register-with-eureka: false #wheather to register this project

    fetch-registry: false #wheather to fetch the register project

  server:

    enable-self-preservation: false #关闭自我保护机制

----------

服务提供者

build.gradle :

plugins {

    id 'org.springframework.boot' version '2.1.3.RELEASE'

    id 'java'

}

apply plugin: 'io.spring.dependency-management'

group = 'cn.jk'

version = '0.0.1-SNAPSHOT'

sourceCompatibility = '1.8'

repositories {

    mavenCentral()

}

ext {

    set('springCloudVersion', 'Greenwich.SR1')

}

dependencies {

    implementation 'org.springframework.boot:spring-boot-starter-web-services'

    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'

}

dependencyManagement {

    imports {

        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"

    }

}

bootstrap.yml:

server:

  port: 8080

spring:

  application:

    name: provider

eureka:

  client:

    service-url:

      defaultZone: http://eureka-server:8080/eureka

启动类:

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication

@EnableDiscoveryClient

public class RibbonProviderApplication {

    public static void main(String[] args) {

        SpringApplication.run(RibbonProviderApplication.class, args);

    }

}

----------

服务消费者

build.gradle :

plugins {

    id 'org.springframework.boot' version '2.1.3.RELEASE'

    id 'java'

}

apply plugin: 'io.spring.dependency-management'

group = 'cn.jk'

version = '0.0.1-SNAPSHOT'

sourceCompatibility = '1.8'

repositories {

    mavenCentral()

}

ext {

    set('springCloudVersion', 'Greenwich.SR1')

}

dependencies {

    implementation 'org.springframework.boot:spring-boot-starter-web-services'

    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'

    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-ribbon'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'

}

dependencyManagement {

    imports {

        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"

    }

}

bootstrap.yml:

server:

  port: 8080

spring:

  application:

    name: consumer

eureka:

  client:

    service-url:

      defaultZone: http://eureka-server:8080/eureka

配置负载均衡:

import org.springframework.cloud.client.loadbalancer.LoadBalanced;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.client.RestTemplate;

@Configuration

public class BeanConfig {

    @Bean

    @LoadBalanced

    public RestTemplate restTemplate(){

        return new RestTemplate();

    }

}

controller:

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.client.RestTemplate;

@RestController

@RequestMapping("/consumer")

public class Consumer {

    @Autowired

    private RestTemplate restTemplate;

    @RequestMapping

    public String getResponse(){

        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://provider/provider", String.class);

        return forEntity.getBody();

    }

}

启动类:

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication

@EnableDiscoveryClient

public class RibbonConsumerApplication {

    public static void main(String[] args) {

        SpringApplication.run(RibbonConsumerApplication.class, args);

    }

}

----------

构建jdk环境容器

用 gradle 将这 3 个项目打成 jar 包,接下来开始制作 jdk 环境的容器:

Dockerfile :

FROM centos

ADD jdk-8u202-linux-x64.tar.gz?AuthParam=1556516170_6056f2093c9005cb80d3fac5d8050f3a /usr/local/

ENV JAVA_HOME /usr/local/jdk1.8.0_202

ENV CLASSPATH ${JAVA_HOME}/lib/

ENV PATH $PATH:${JAVA_HOME}/bin:$PATH

EXPOSE 8080

docker build -t mycentos .

----------

搭建服务环境

首先我们能确定的是我们需要把所有服务部署到同一网络上,这样才能通过主机名远程调用服务,然后我们需要给每个服务容器指定一个挂载目录,可以方便我们发布服务,之所以没在 dockerfile 中指定,是因为用 -v 指令更能灵活地指定我们方便的目录。

构建 eureka 服务容器:

docker run --name eureka-server --net microservice -p 9001:8080 -v /service/eureka-server:/data -itd mycentos

构建服务提供者容器:

docker run --name provider-server -p 9002:8080 -v /service/provider/:/data -itd --net microservice mycentos

构建服务提供者容器:

docker run --name provider-server-2 -p 9003:8080 -v /service/provider2/:/data -itd --net microservice mycentos

构建服务消费者容器:

docker run --name consumer-server -p 9004:8080 -v /service/consumer/:/data -itd --net microservice mycentos

docker ps:

CONTAINER ID        IMAGE              COMMAND            CREATED              STATUS              PORTS                    NAMES

1c82b5847bb0        mycentos            "/bin/bash"        5 seconds ago        Up 4 seconds        0.0.0.0:9004->8080/tcp  consumer-server

bc9bfb8098d4        mycentos            "/bin/bash"        43 seconds ago      Up 41 seconds      0.0.0.0:9003->8080/tcp  provider-server-2

e4ccb94a73f6        mycentos            "/bin/bash"        About a minute ago  Up About a minute  0.0.0.0:9002->8080/tcp  provider-server

3e20ae361334        mycentos            "/bin/bash"        5 minutes ago        Up 5 minutes        0.0.0.0:9001->8080/tcp  eureka-server

那么我们现在已经准备就绪了,直接把打好的 jar 包拖到服务器,然后进入容器启动服务,按注册中心、服务提供者、服务消费者的顺序启动。

那么现在我们用源主机的 ip 访问注册中心服务应该就能看见我们所有注册的服务了。

现在去访问服务消费提供的服务便能拿到 hello world ,只要 provider 有任意节点在注册中心能拿到,那么这个服务便是可用的。

我们测试将其中一个 provider 停掉,再去调 consumer ,发现还是能访问,这是因为另一个 provider 还能提供服务,不过细心的朋友能发现,虽然我们现在还能访问服务,但是总是一下好一下坏的,注意看上面的配置文件,在注册中心的配置文件里面我关闭了自我保护机制,如果不关呢,我们停掉一个服务 eureka 是不会自动清除这个不可用的服务的,因为我们目前只有这 3 个服务,停掉一个服务意味着整个系统有 1/3 的服务不可用了,那么 eureka 就会进入自我保护模式,不会直接清除没有了心跳的服务,虽然目前我们已经将自我保护机制关闭了, eureka 也需要在一定时间内检验出服务真的不可用以后才会踢除这些不可用的服务。

那么以上便是我们在分布式系统中常用的一些中间件和服务的搭建,这些都是基本功,要想更深入地了解和研究分布式这一领域,最起码的是能搭建一套分布式环境,然后能自己做项目测试,当然如果你有合适的分布式项目最好。

----------

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容