# Nicksxs's Blog
learn from zero,技术博客，Nicksxs，史学森

What hurts more, the pain of hard work or the pain of regret?

0%

## Comparator使用小记

发表于 2020-04-05 分类于 Java ， 集合 阅读次数： Disqus：

在Java8的stream之前，将对象进行排序的时候，可能需要对象实现Comparable接口，或者自己实现一个Comparator，

比如这样子

我的对象是Entity

    public class Entity {

        private Long id;

        private Long sortValue;

        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public Long getSortValue() {
            return sortValue;
        }

        public void setSortValue(Long sortValue) {
            this.sortValue = sortValue;
        }
    }

Comparator

    public class MyComparator implements Comparator {
        @Override
        public int compare(Object o1, Object o2) {
            Entity e1 = (Entity) o1;
            Entity e2 = (Entity) o2;
            if (e1.getSortValue() < e2.getSortValue()) {
                return -1;
            } else if (e1.getSortValue().equals(e2.getSortValue())) {
                return 0;
            } else {
                return 1;
            }
        }
    }

比较代码

    private static MyComparator myComparator = new MyComparator();

        public static void main(String[] args) {
            List<Entity> list = new ArrayList<Entity>();
            Entity e1 = new Entity();
            e1.setId(1L);
            e1.setSortValue(1L);
            list.add(e1);
            Entity e2 = new Entity();
            e2.setId(2L);
            e2.setSortValue(null);
            list.add(e2);
            Collections.sort(list, myComparator);

看到这里的e2的排序值是null，在Comparator中如果要正常运行的话，就得判空之类的，这里有两点需要，一个是不想写这个MyComparator，然后也没那么好排除掉list里排序值，那么有什么办法能解决这种问题呢，应该说java的这方面真的是很强大

看一下nullsFirst的实现

    final static class NullComparator<T> implements Comparator<T>, Serializable {
            private static final long serialVersionUID = -7569533591570686392L;
            private final boolean nullFirst;
            // if null, non-null Ts are considered equal
            private final Comparator<T> real;

            @SuppressWarnings("unchecked")
            NullComparator(boolean nullFirst, Comparator<? super T> real) {
                this.nullFirst = nullFirst;
                this.real = (Comparator<T>) real;
            }

            @Override
            public int compare(T a, T b) {
                if (a == null) {
                    return (b == null) ? 0 : (nullFirst ? -1 : 1);
                } else if (b == null) {
                    return nullFirst ? 1: -1;
                } else {
                    return (real == null) ? 0 : real.compare(a, b);
                }
            }

核心代码就是下面这段，其实就是帮我们把前面要做的事情做掉了，是不是挺方便的，小记一下哈

## docker使用中发现的echo命令的一个小技巧及其他

发表于 2020-03-29 分类于 Linux ， Docker ， 命令 ， echo ， 发行版本 阅读次数： Disqus：

最近做 docker 系列，会经常进到 docker 内部，如上一篇介绍的，这些镜像一般都有用 ubuntu 或者alpine 这样的 Linux 系统作为底包，如果构建镜像的时候没有替换源的话，因为你懂的原因，在内部想编辑下东西要安装 vim 就会很慢很慢，thousand years later~
而且如果在容器内部想改源配置的话也要编辑器，就陷入了一个鸡生蛋，跟蛋生鸡的问题中，对于 linux 大神来说应该有一万种方法解决这个问题，对于我这个渣渣来说可能只想到了这个土方法，先 cp backup 一下 sources.list, 再 echo “xxx” > sources.list, 这里就碰到了一个问题，这个 sources.list 一般不止一行，直接 echo 的话就解析不了了，不过 echo 可以支持”\n”转义，就是加-e
看一下解释和示例
还有一点也是在这个时候要安装 vim 之类的，得知道是什么镜像底包，如果是用 uname 指令，其实看到的是宿主机的系统，得用`cat /etc/issue`
这里稍稍记一下

## docker比一般多一点的初学者介绍三

发表于 2020-03-21 分类于 Docker ， 介绍 阅读次数： Disqus：

## 运行第一个 Dockerfile

上一篇的 Dockerfile 我们停留在构建阶段，现在来把它跑起来

    docker run -d -p 80 --name static_web nicksxs/static_web \
    nginx -g "daemon off;"

这里的`-d`表示以分离模型运行docker (detached),然后-p 是表示将容器的 80 端口开放给宿主机，然后容器名就叫 static_web,使用了我们上次构建的 static_web 镜像，后面的是让 nginx 在前台运行
可以看到返回了个容器 id，但是具体情况没出现，也没连上去，那我们想看看怎么访问在 Dockerfile 里写的静态页面，我们来看下docker 进程
发现为我们随机分配了一个宿主机的端口，32768，去服务器的防火墙把这个外网端口开一下，看看是不是符合我们的预期呢
好像不太对额，应该是 ubuntu 安装的 nginx 的默认工作目录不对，我们来进容器看看，再熟悉下命令`docker exec -it 4792455ca2ed /bin/bash`
记得容器 id 换成自己的，进入容器后得找找 nginx 的配置文件，通常在`/etc/nginx`,`/usr/local/etc`等目录下，然后找到我们的目录是在这
所以把刚才的内容复制过去再试试
目标达成，give me five✌️

## 第二个 Dockerfile

然后就想来动态一点的，毕竟写过 PHP，就来试试 PHP
再建一个目录叫 dynamic_web，里面创建 src 目录，放一个 index.php
内容是

    <?php
    echo "Hello World!";

然后在 dynamic_web 目录下创建 Dockerfile，

    FROM trafex/alpine-nginx-php7:latest
    COPY src/ /var/www/html
    EXPOSE 80

Dockerfile 虽然只有三行，不过要着重说明下，这个底包其实不是docker 官方的，有两点考虑，一点是官方的基本都是 php apache 的镜像，还有就是 alpine这个，截取一段中文介绍

> Alpine 操作系统是一个面向安全的轻型 Linux 发行版。它不同于通常 Linux 发行版，Alpine 采用了 musl libc 和 busybox 以减小系统的体积和运行时资源消耗，但功能上比 busybox 又完善的多，因此得到开源社区越来越多的青睐。在保持瘦身的同时，Alpine 还提供了自己的包管理工具 apk，可以通过 https://pkgs.alpinelinux.org/packages 网站上查询包信息，也可以直接通过 apk 命令直接查询和安装各种软件。
> Alpine 由非商业组织维护的，支持广泛场景的 Linux发行版，它特别为资深/重度Linux用户而优化，关注安全，性能和资源效能。Alpine 镜像可以适用于更多常用场景，并且是一个优秀的可以适用于生产的基础系统/环境。

> Alpine Docker 镜像也继承了 Alpine Linux 发行版的这些优势。相比于其他 Docker 镜像，它的容量非常小，仅仅只有 5 MB 左右（对比 Ubuntu 系列镜像接近 200 MB），且拥有非常友好的包管理机制。官方镜像来自 docker-alpine 项目。

> 目前 Docker 官方已开始推荐使用 Alpine 替代之前的 Ubuntu 做为基础镜像环境。这样会带来多个好处。包括镜像下载速度加快，镜像安全性提高，主机之间的切换更方便，占用更少磁盘空间等。

一方面在没有镜像的情况下，拉取 docker 镜像还是比较费力的，第二个就是也能节省硬盘空间，所以目前有大部分的 docker 镜像都将 alpine 作为基础镜像了
然后再来构建下
这里还有个点，就是上面的那个镜像我们也是 EXPOSE 80端口，然后外部宿主机会随机映射一个端口，为了偷个懒，我们就直接指定外部端口了
`docker run -d -p 80:80 dynamic_web`打开浏览器发现访问不了，咋回事呢
因为我们没看`trafex/alpine-nginx-php7:latest`这个镜像说明,它内部的服务是 8080 端口的，所以我们映射的暴露端口应该是 8080，再用`docker run -d -p 80:8080 dynamic_web`这个启动，

## docker比一般多一点的初学者介绍二

发表于 2020-03-15 分类于 Docker ， 介绍 阅读次数： Disqus：

## 限制下 docker 的 cpu 使用率

这里我们开始玩一点有意思的，我们在容器里装下 vim 和 gcc，然后写这样一段 c 代码

    #include <stdio.h>
    int main(void)
    {
        int i = 0;
        for(;;) i++;
        return 0;
    }

就是一个最简单的死循环，然后在容器里跑起来

    $ gcc 1.c
    $ ./a.out

然后我们来看下系统资源占用（CPU）
上图是在容器里的，可以看到 cpu 已经 100%了
然后看看容器外面的
可以看到一个核的 cpu 也被占满了，因为是个双核的机器，并且代码是单线程的
然后呢我们要做点啥
因为已经在这个 ubuntu 容器中装了 vim 和 gcc，考虑到国内的网络，所以我们先把这个容器 commit 一下，

    docker commit -a "nick" -m "my ubuntu" f63c5607df06 my_ubuntu:v1

然后再运行起来

    docker run -it --cpus=0.1 my_ubuntu:v1 bash

我们的代码跟可执行文件都还在，要的就是这个效果，然后再运行一下
结果是这个样子的，有点神奇是不，关键就在于 run 的时候的`--cpus=0.1`这个参数，它其实就是基于我前一篇说的 cgroup 技术，能将进程之间的cpu，内存等资源进行隔离

## 开始第一个 Dockerfile

上一面为了复用那个我装了 vim 跟 gcc 的容器，我把它提交到了本地，使用了`docker commit`命令，有点类似于 git 的 commit，但是这个不是个很好的操作方式，需要手动介入，这里更推荐使用 Dockerfile 来构建镜像

    From ubuntu:latest
    MAINTAINER Nicksxs "nicksxs@hotmail.com"
    RUN  sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list
    RUN apt-get clean
    RUN apt-get update && apt install -y nginx
    RUN echo 'Hi, i am in container' \
        > /usr/share/nginx/html/index.html
    EXPOSE 80

先解释下这是在干嘛，首先是这个`From ubuntu:latest`基于的 ubuntu 的最新版本的镜像，然后第二行是维护人的信息，第三四行么作为墙内人你懂的，把 ubuntu 的源换成阿里云的，不然就有的等了，然后就是装下 nginx，往默认的 nginx 的入口 html 文件里输入一行欢迎语，然后暴露 80 端口
然后我们使用`sudo docker build -t="nicksxs/static_web" .`命令来基于这个 Dockerfile 构建我们自己的镜像，过程中是这样的
可以看到图中，我的 Dockerfile 是 7 行，里面就执行了 7 步，并且每一步都有一个类似于容器 id 的层 id 出来，这里就是一个比较重要的东西，docker 在构建的时候其实是有这个层的概念，Dockerfile 里的每一行都会往上加一层，这里有还注意下命令后面的`.`，代表当前目录下会自行去寻找 Dockerfile 进行构建，构建完了之后我们再看下我们的本地镜像
我们自己的镜像出现啦
然后有个问题，如果这个构建中途报了错咋办呢，来试试看，我们把 nginx 改成随便的一个错误名，nginxx(不知道会不会运气好真的有这玩意)，再来 build 一把
找不到 nginxx 包，是不是这个镜像就完全不能用呢，当然也不是，因为前面说到了，docker 是基于层去构建的，可以看到前面的 4 个 step 都没报错，那我们基于最后的成功步骤创建下容器看看
也就是`sudo docker run -t -i bd26f991b6c8 /bin/bash`
答案是可以的，只是没装成功 nginx
还有一点注意到没，前面的几个 step 都有一句 `Using cache`，说明 docker 在构建镜像的时候是有缓存的，这也更能说明 docker 是基于层去构建镜像，同样的底包，同样的步骤，这些层是可以被复用的，这就是 docker 的构建缓存，当然我们也可以在 build 的时候加上`--no-cache`去把构建缓存禁用掉。

## docker比一般多一点的初学者介绍

发表于 2020-03-08 分类于 Docker ， 介绍 阅读次数： Disqus：

因为最近想搭一个phabricator用来做看板和任务管理，一开始了解这个是Easy大大有在微博推荐过，后来苏洋也在群里和博客里说到了，看上去还不错的样子，因为主角是docker所以就不介绍太多，后面有机会写一下。

docker最开始是之前在某位大佬的博客看到的，看上去有点神奇，感觉是一种轻量级的虚拟机，但是能做的事情好像差不多，那时候是在Ubuntu系统的vps里起一个Ubuntu的docker，然后在里面装个nginx，配置端口映射就可以访问了，后来也草草写过一篇使用docker搭建mysql集群，但是最近看了下好像是因为装docker的大佬做了一些别名还是什么操作，导致里面用的操作都不具有普遍性，而且主要是把搭的过程写了下，属于囫囵吞枣，没理解docker是干啥的，为啥用docker，就是操作了下，这几天借着搭phabricator的过程，把一些原来不理解，或者原来理解错误的地方重新理一下。

之前写的 mysql 集群，一主二备，这种架构在很多小型应用里都是这么配置的，而且一般是直接在三台 vps 里启动三个 mysql 实例，但是如果换成 docker 会有什么好处呢，其实就是方便部署，比如其中一台备库挂了，我要加一台，或者说备库的 qps 太高了，需要再加一个，如果要在 vps 上搭建的话，首先要买一台机器，等初始化，然后在上面修改源，更新，装 mysql ，然后配置主从，可能还要处理防火墙等等，如果把这些打包成一个 docker 镜像，并且放在自己的 docker registry，那就直接run 一下就可以了；还有比如在公司要给一个新同学整一套开发测试环境，以 Java 开发为例，要装 git，maven，jdk，配置 maven settings 和各种 rc，整合在一个镜像里的话，就会很方便了；再比如微服务的水平扩展。

但是为啥 docker 会有这种优势，听起来好像虚拟机也可以干这个事，但是虚拟机动辄上 G，而且需要 VMware，virtual box 等支持，不适合在Linux服务器环境使用，而且占用资源也会非常大。说得这么好，那么 docker 是啥呢

docker 主要使用 Linux 中已经存在的两种技术的一个整合升级，一个是 namespace，一个是cgroups，相比于虚拟机需要完整虚拟出一个操作系统运行基础，docker 基于宿主机内核，通过 namespace 和 cgroups 分隔进程，理念就是提供一个隔离的最小化运行依赖，这样子相对于虚拟机就有了巨大的便利性，具体的 namespace 和 cgroups 就先不展开讲，可以参考耗子叔的文章

## 安装

那么我们先安装下 docker，参考官方的教程，安装，我的系统是 ubuntu 的，就贴了 ubuntu 的链接，用其他系统的可以找到对应的系统文档安装，安装完了的话看看 docker 的信息

    sudo docker info

输出以下信息

## 简单运行

然后再来运行个 hello world 呗，

    sudo docker run hello-world

输出了这些

看看这个运行命令是怎么用的，一般都会看到这样子的，sudo docker run -it ubuntu bash`, 前面的 docker run 反正就是运行一个容器的意思，`-it`是啥呢，还有这个什么 ubuntu bash，来看看`docker run`的命令帮助信息

    -i, --interactive                    Keep STDIN open even if not attached

就是要有输入，我们运行的时候能输入

    -t, --tty                            Allocate a pseudo-TTY

要有个虚拟终端，

    Usage:	docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

    Run a command in a new container

## 镜像

上面说的-it 就是这里的 options，后面那个 ubuntu 就是 image 辣，image 是啥呢

Docker 把应用程序及其依赖，打包在 image 文件里面，可以把它理解成为类似于虚拟机的镜像或者运行一个进程的代码，跑起来了的叫docker 容器或者进程，比如我们将要运行的`docker run -it ubuntu bash`的ubuntu 就是个 ubuntu 容器的镜像，将这个镜像运行起来后，我们可以进入容器像使用 ubuntu 一样使用它，来看下我们的镜像，使用`sudo docker image ls`就能列出我们宿主机上的 docker 镜像了

一个 ubuntu 镜像才 64MB，非常小巧，然后是后面的`bash`，我通过交互式启动了一个 ubuntu 容器，然后在这个启动的容器里运行了 bash 命令，这样就可以在容器里玩一下了

## 在容器里看下进程，

只有刚才运行容器的 bash 进程和我刚执行的 ps，这里有个可以注意下的，bash 这个进程的 pid 是 1，其实这里就用到了 linux 中的PID Namespace，容器会隔离出一个 pid 的名字空间，这里面的进程跟外部的 pid 命名独立

## 查看宿主机上的容器

    sudo docker ps -a

## 如何进入一个正在运行中的 docker 容器

这个应该是比较常用的，因为比如是一个微服务容器，有时候就像看下运行状态，日志啥的

    sudo docker exec -it [containerID] bash

## 查看日志

    sudo docker logs [containerID]

我在运行容器的终端里胡乱输入点啥，然后通过上面的命令就可以看到啦