docker常用操作总结

1.安装注意

  • 不要直接用apt-get安装docker, apt-get安装的版本有些低。 安装用curl -sSL https://get.docker.com/ubuntu/ | sudo sh可以安装最新版
  • docker对linux内核版本有要求,内核版本不能太低, 如果太低会导致docker的一些功能不能使用, 比如docker exec命令在低版本的linux内核下不能用, 运行linux命令uname -r可以查看linux内核版本, docker官方文档说linux内核版本不能低于3.13,升级linux内核:sudo apt-get install linux-image-generic-lts-trusty手动升级方法:高版本的linux内核不支持aufs的存储类型,建议用devicemapper存储类型。
  • docker在mac下改用docker-machine,弃用boot2docker用户接口,执行eval "$(docker-machine env docker-vm)"之后即可正常使用docker

2.封装原则

  • 容器要有专职, 尽量一个容器只有一个服务,原则上说一个进程就一个容器,不要让一个容器里面有多个进程,进程越大,耦合性越大。
  • 容器内可以用supervision管理进程,防止进程异常退出。
  • 环境变量作为容器配置项,Dockerfile中可以用ENV设置环境变量, docker run命令可以用 -e 设置环境变量。注意这里设置的环境命令都是root用户的。如果想让Apache用户也使用这些环境变量,执行下面shell命令env | grep JD_ | sed "s/^/export /g" >> /etc/apache2/envvars表示以JD_开头的环境变量都设置为Apache的环境变量。

3.docker的坑

  • 最坑的 –link 链接容器。 用docker自带的–link把多个容器链接在以前, 有重启或升级的问题, 比如很多容器都依赖于 db 这个容器, 然后db容器重启了, 重启时docker分配的ip会变, 导致其他依赖于db的容器都要重启。–link 链接的容器还有启动顺序的问题, 需要先启动db容器再启动其他依赖于db的容器, 这样导致 –link和–restart=always 不能一起用, 如果一起用会发现宿主机重启了, docker容器并没有全部重启,因为这时候docker容器是同时被启动的,并不知道启动顺序。 最后决定不用 –link 链接容器了。 另外有两种链接容器的方法,一种是给容器设置固定ip ,这个方法比较复杂: 另外还有一种简单的链接方式, 可以用宿主机的端口链接, 比如一个mysql容器,先设置宿主机的3306端口映射到mysql容器中。 然后查宿主机的内网ip , 用ifconfig 查,eth0的网卡可以看见内网ip, 假设内网ip为10.128.130.175 , docker容器是可以访问这个宿主机内网ip的, 这样其他容器要链接mysql容器,链接数据库时链接10.128.130.175:3306 即可。 我们可以在docker run启动容器时用–add-host参数为容器设置一个hosts 。这样容器内代码可以用指定的域名去访问数据库,不用关心内网ip的变化。
  • pid的问题。docker容器中的进程有时会生成pid文件, 比如Apache进程会生成的pid文件为 /var/run/apache2/apache2.pid , 当进程启动时,这个pid文件就存在,当进程退出时,这个pid文件也会被删除, 我们把正在运行的容器用docker commit 提交为镜像时会把pid文件也提交到镜像中,这样从新镜像运行容器时,容器可能因为已经存在pid文件而无法启动。 以Apache为例,可能就会报 “httpd(pid 1) ready runing” 之类的错误 , 报错告诉你httpd正在运行,但其实并没有运行只是存在pid文件而已。 所以最好是用docker stop 把容器停掉, 再用docker commit 创建镜像。
    pid的问题还可能会出现在docker run时设置–restart=always 参数的时候, 设置了此参数容器退出了会自动重启, 宿主机重启了容器也能自动重启。 但是容器在重启的时候很容易pid文件存在。pid文件存在时进程会自动退出, 但又因为设置了–restart=always 进程退出的那一瞬间 容器又自动重启, 所以容器就在那里不断的退出再自动重启退出再自动重启。 此时运行命令 docker ps 是能看见容器是运行中的状态。 但是用 docker exec -it container_name bash 始终进不了容器,会报如下错误:
    Cannot run exec command 0eb8e17609dd78c9137c62d94cfaa62795de161d643fc3cb00387b60f11090be in container 8837b983fe2f08f5f3b9999259d5f255a83774b19282b6f9c21a9d688f7f7f2a: No active container exists with ID 8837b983fe2f08f5f3b9999259d5f255a83774b19282b6f9c21a9d688f7f7f2a
    No active container exists with ID 这句的意思是说 找不到有效的容器。但是docker ps又看见容器是在运行中,为什么会找不到有效的容器?这是因为这个容器现在其实一直在不断重启中,所以进不去。

解决方法, 可以在容器启动命令脚本(如 /run.sh) 中 加上一句删除pid文件的代码, 如rm /var/run/apache2/apache2.pid这样在启动进程之前强制删除了pid文件,就不会重复重启了。
docker容器的hosts文件。
在正在运行的容器 用docker exec 进入修改 /etc/hosts 文件 ,这个容器被重启后会发现 hosts文件会被还原。 所以不要直接修改hosts文件, 需要增加hosts ,在docker run时 用 –add-host 参数。
虚拟目录不会提交到镜像
Dockerfile 中 VOLUME 指定的目录 或 docker run 时 -v 参数指定的目录, 在docker commit 时不会提交到镜像中。 如果-v 参数指定的容器内的目录原本有文件, 原本的文件都会被删除, 只存在宿主机目录的文件。

4.docker容器的重启

容器一个容器运行中apache而我们要重启Apache,应该怎么重启? 可能新手会docker exec -it container_name bash进入容器, 然后运行service apache2 restart启动Apache,这样是不能启动apache的, 只会把容器停止掉。 因为容器的主进程就是Apache , 主进程退出时会退出容器, 在重启apache的时候 主进程先退出了, 这时候docker容器也跟着退出了,所以Apache不会重启。 要重启Apache 用docker命令:docker restart container_name

5.退出容器的方法

如果是docker run运行一个容器, 没有加 -d 参数让它后台运行, 这时候 ctrl+c 退出进程也会让容器停止, 如果先退出但不停止容器可以ctrl+p然后ctrl+q

6.Dockerfile编写技巧

  • 将Ubuntu的源设置为国内的源,这样安装软件会快很多RUN sed -i "s/archive.ubuntu.com/mirrors.163.com/g" /etc/apt/sources.list

  • RUN

Dockerfile 中可以RUN执行系统命令创建镜像。不能用RUN命令来常驻进程。 比如不能运行RUN gearmand -d。

  • ONBUILD

ONBUILD修饰的命令在子Dockerfile文件中也会被执行,举例说明:
一个Dockerfile中有ONBUILD命令, 如ONBUILD ADD . /app/src, 运行docker build -t Image_name .创建一个名为Image_name的镜像。 另外在创建一个Dockerfile , 第一个行是FROM Image_name现在这个Dockerfile是继承于前一个Dockerfile的,现在这个Dockerfile在build时会执行他父Dockerfile的ONBUILD命令, 所以现在这个Dockerfile在build时也会执行 ADD . /app/src 这个命令。

  • ENTRYPOINT

感觉 ENTRYPOINT 会比 CMD 更省资源, ENTRYPOINT 使用时用数组形式。如:
entrypoint ["/init.sh", "/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf"]
/init.sh是一个初始化脚本,初始化脚本内容大概为:

1
2
3
4
!/bin/bash
set -e
# 执行一些初始化代码
exec$@

最后要跟上exec"$@",这样才能让init.sh 脚本后的supervisord被执行。

  • .dockerignore文件中列的文件不会被 ADD或COPY 指令添加到容器中。

7.好用的docker镜像

  • jwilder/nginx-proxy 这是一个nginx反向代理。
    启动nginx反向代理:
    docker run --name nginx -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock -t --restart=always jwilder/nginx-proxy
    再启动其他容器, 如:
    docker run -e VIRTUAL_HOST=yourdomain.com -d tutum/apache-php
    nginx的容器会监听其他容器的启动, 并根据VIRTUAL_HOST设置域名。这样可以通过 yourdomain.com 访问刚才启动的容器中的网站了。

  • tutum/apache-php 这个镜像是一个好用的apache环境。

8.清理docker

docker info 可以查看docker的信息,/var/lib/docker/devicemapper/devicemapper 目录下存放了docker的文件, 可以用du -h –max-depth=1 看文件的大小。

删除为none的镜像,可以立马回收空间:

docker images --no-trunc| grep none | awk '{print $3}' | xargs -r docker rmi

删除退出了的容器:

docker rm docker ps -a | grep Exited | awk ‘{print $1 }’

删除没有用的镜像。 (有容器运行的镜像不会被删除):

docker rmi docker images -aq

分布式锁实现问题讨论

分布式锁除了使用Zookeeper这个常用工具外,也有使用Redis这个KV内存存储进行实现的。某人介绍了一种基于Redis的分布式锁实现方法(命名为Redlock)并刊登到了redis官网:

Redlock实现

某人之后还发文详细讨论了实现方法,并说明自己感觉这个算法并不安全

分布式锁如何实现

接着Redlock算法的原作者开始就此事发文详细讨论,说明这个算法足够安全

Redlock实现安全吗?

与此同时观众们也在发帖论战

观众开帖论战

有人跟帖

论证RedLock问题确实存在

推荐还是运用理论证明一下吧

测试方法

Java架构师问题总结

常见面试流程

  1. 技术相关

    • java语言(基本、容器、多线程、异常处理、JVM)
    • 设计模式
    • Spring
    • MySQL、Redis等(JDBC)
    • 前端相关
    • Shell相关
    • 算法题
  2. 项目相关

Java语言基础

面向对象

  1. 绝对不能在构造函数中调用会被Override的函数
  2. QA:builder模式与Config类(InstanceSpec)取舍? builder需要创建静态类
  3. QA:构造函数什么时候可以抛异常?
  4. Q:init私有方法什么时候使用? A:构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中

String使用

  1. 判断是否相等绝对不能直接使用”==”进行,可以使用equals方法
  2. 判断是否为空: StringUtils.isBlank()Str.isEmpty()Str.length()==0
  3. 循环体内,字符串的联接方式,使用 StringBuilder的append方法进行扩展

集合使用

  1. Array初始化
    1
    new ArrayList<Element>(Arrays.asList(array))

使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出 UnsupportedOperationException异常。

  1. 使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()

  2. ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException

  3. QA:集合接口的区别 list collection hashmap

  4. 不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁

  5. 高度注意Map类集合 K/V 能不能存储 null 值的情况

    | 集合类 | Key | Value | Super | 说明 |
    | —————– | ——— | ——— | ———– | —– |
    | Hashtable | 不允许为 null | 不允许为 null | Dictionary | 线程安全 |
    | ConcurrentHashMap | 不允许为 null | 不允许为 null | AbstractMap | 分段锁技术 |
    | TreeMap | 不允许为 null | 允许为 null | AbstractMap | 线程不安全 |
    | HashMap | 允许为 null | 允许为 null | AbstractMap | 线程不安全 |

  6. 利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作

Exception处理

  1. 标准Exception分为两种expcetion,一种继承自RunTimeException,不需要方法捕捉,比如NullPointerException,IllegalStateException;另一种必须catch或throws,比如InterruptedException
  2. 推荐使用Preconditions.checkNotNull等工具
  3. catch后的代码仍然会执行,返回值与抛出异常都需要满足匹配,try中抛出的异常就会被对应catch捕获,上一个catch中抛出也会被下一个catch捕获,如果分支继续抛异常来结束那么该分支可以忽略返回值
    处理方法:1. 异常转义;2. 异常链接getCause
  4. 单元测试函数应该抛出任何异常
  5. 利用jdk7新语法自动关闭文件流

    1
    2
    3
    try(InputStream is = new FileInputStream){
    //文件操作
    }
  6. 对No such method error处理:打印出具体绑定类

    1
    <Object>.class.getProtectionDomain();

注释处理

  1. Bean注入
    @Autowired
    ApplicationContext ac;
    lockAnnotation = (LockAnnotation)ac.getBean(“LockAnnotation”)
  2. @Service与@Resouce可以配合使用
    http://bit1129.iteye.com/blog/2114126
  3. AOP
    http://www.xdemo.org/springmvc-aop-annotation/

多线程

  1. 后台定时任务

    1
    2
    3
    4
    5
    //start
    ScheduledExecutorService excutor = Excutor.newSingleThreadScheduledExecutor();
    excutor.scheduleWithFixedDelay(runnable, time);
    //end
    excutor.shutdown(); //excutor.shutdownNow();
  2. 线程池

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    List<Future<Object>> threads = Lists.newArrayList(); 
    ExcutorService excutor = Excutors.newCachedThreadPool();
    Future<Object> t = executor.submit(new Callable<Object>() {
    @Override
    public Object call() throws Exception {
    //do sth here!
    return null;
    }
    });
    threads.add(t);
    for(Future<Object> t: threads)
    t.get();
  3. CountDownLatch与Semaphore使用

    CountDownLatch设置初始值,调用wait()方法会阻塞直到被notify()方法唤醒;调用countDown()来减少值;创建时不会阻塞,需要调用await()阻塞直至计数值为0;该机制被设计为只会触发一次,因此不会有方法来还原初始值。

    Semaphore设置初始值,调用acquire()方法减少该值否则阻塞直至计数值大于0,调用release(num)来增加值,调用availablePermits()判断是否阻塞

  4. 单例模式注意使用double check方法

1
2
3
4
5
6
7
8
9
10
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper();
}
}
}
return helper;
}

Q:该模式有失效的可能? A:可参考 The “Double-Checked Locking is Broken” Declaration,推荐问题
解决方案中较为简单一种(适用于 JDK5 及以上版本),将目标属性声明为 volatile 型。

  1. executor 重要的三个参数含义
    corePoolSize, maxmumPoolSize, [keepAliveTime], BlockingQueue

  2. 多线程的方法

JVM

  1. 有哪些垃圾回收器 优缺点
  2. 四种导致full gc场景
  3. 持久代满了,gc不成功则out of memory
  4. 旧生代满了
  5. 新生代向S0和S1转移数据,S0和S1向旧生代转移数据,两边内存都设置较小,持续进行导致
  6. 直接system.gc
  7. Q: 原子变量实现? 设置unsafe 内存交换
  8. 给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump 信息。

JDBC

  1. Q: 何时使用乐观锁 悲观锁 出现场景,jdbc事务提交使用哪种锁(一般使用DB的悲观锁)

Spring

调优:tcp窗口,事务大小

  1. 常用模块
    aop, aspects, beans, core, context, expression, orm, jdbc, jms, tx, test, web, webmvc
  2. 主要接口

  3. 五种事务传播性Propagation
    required, supports, mandatory, requires_new, not_supported, never, nested

netty

结构 lf缺陷

MySQL

  1. 设置AUTO_INCREMENT
    http://www.searchdatabase.com.cn/showcontent_38510.htm
  2. 使用datetime或timestamp类型,查询范围写法:send_time <= ‘2015-09-24 15:24:16’

    区别:首先 DATETIM和TIMESTAMP类型所占的存储空间不同,前者8个字节,后者4个字节,这样造成的后果是两者能表示的时间范围不同。前者范围为1000-01-01 00:00:00 ~ 9999-12-31 23:59:59,后者范围为1970-01-01 08:00:01到2038-01-19 11:14:07。所以可以看到TIMESTAMP支持的范围比DATATIME要小,容易出现超出的情况.
    其次,TIMESTAMP类型在默认情况下,insert、update 数据时,TIMESTAMP列会自动以当前时间(CURRENT_TIMESTAMP)填充/更新。
    第三,TIMESTAMP比较受时区timezone的影响以及MYSQL版本和服务器的SQL MODE的影响
    所以一般来说,我比较倾向选择DATETIME,至于你说到索引的问题,选择DATETIME作为索引,如果碰到大量数据查询慢的情况,也可以分区表解决。
    最佳实践: 只要用int类型存格林时间的unix timestamp值就好了,使用TIMESTAMP主要是为了方便记current_timestamp

  3. 使用txt类型取代nvarchar
    SQLserver 中使用nvarchar存储变长unicode字符串

  4. 使用tinyint(8)或bool(1)取代bit类型, int(32), bigint(64)
  5. DEFAULT 为0,则可以查询’’(空字符串);DEFAULT为NULL,则查询’’(空字符串)不匹配
  6. 业务ID自增设计
    insert msgId into [table] value(select max(msgId) from [table] + 1)
    这里max函数会导致锁表
    解决方法:单记一个max version表,利用行锁进行多线程同步
  7. 当发生Too many connections时,即使是DBA也无法登录到数据库,一般的做法是修改配置文件的max_connections参数,然后重启数据库,这样业务就有几秒钟的中断。
    还有一个hack的方法,用过gdb直接修改mysqld内存中max_connections的值,具体做法如下:
    gdb -ex "set max_connections=5000" -batch -p 'pgrep -x mysqld'
  8. 小数类型为 decimal,禁止使用 float 和 double。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
  9. 表必备三字段:id, gmt_create, gmt_modified。
  10. 不得使用外键与级联,一切外键概念必须在应用层解决。更禁止使用存储过程。

JBOSS

调优方法

  1. connector 改为Nio
  2. 使用G1回收器,+UseG1GC 提供一种计算方法预期最大停止时间
  3. 调整内存使用

Nginx

运用功能 模块实现 核心组件 upstream配置

  1. 高并发服务器建议调小 TCP 协议的 time_wait 超时时间,变更/etc/sysctl.conf 文件:net.ipv4.tcp_fin_timeout = 30

Redis

  1. 连接数
  2. 集群设置 集群算法

Curator

  1. 使用InterProcessMutex中的RevocationListener因为在另一个专用线程因此没法直接调用release释放,Revoke唤醒需要使用对应的lock节点而不是目录
    QA:这个设计很奇怪,按常理revoke应该调用在lock的path上然后自动通知当前获得锁的lock结构,并且应该有方法允许释放【可能状态比较难统一】
  2. 2.9版本之后可以自动清理锁目录,需要设置container.checkIntervalMs
  3. 共享锁实现
    http://www.tuicool.com/articles/yQBVfeA
  4. start方法只能在初始化后调用一次,close调用后只能销毁

Dubbo

  1. 使用transient关键字保证成员不被串行化

实现思路

1. WS服务转RPC

分为两个难点:wsdl信息提取,RPC服务端的API生成
wsdl信息提取:例如从BPM提取出ReceiveMsg方法与参数
RPC服务端API生成:
http://dubbo.io/User+Guide-zh.htm#UserGuide-zh-API%E9%85%8D%E7%BD%AE
API方法所依赖的接口service与实现serviceImpl可以使用通用的泛化实现接口与实现
http://dubbo.io/Generic+Implementation-zh.htm

2. RPC服务转WS

分为两个难点:参考Dubbo客户端实现RPC客户端自动生成,WS服务端API生成
RPC信息提取:
WS服务端API生成:

Open Chain系统设计

OpenChain简介

openchain使用C#开发的分布式总账系统,有比较强的横向扩展能力,并利用比特币的区块链保证不可更改性。

常见问题

  1. openchain是一种区块链吗?
    答:不是,其不使用块这个概念,并直接使用事务链。
  2. openchain是一条侧链吗?
    答:提供了模块将openchain部署为侧链,但并非必要步骤。

部署方法

提供docker方法直接部署openchain的服务器,使用sqlite作为默认存储,运行在.NET Core和.NET Framework上面

系统架构

  1. 事务流
    openchain的服务节点可以分为两种:validator节点、observer节点:前者接收事务并进行验证,只有通过验证的才计入总账(验证规则由管理员配置);后者会下载所有通过验证的事务信息流(形成备份?)
  2. 利用区块链
    即利用公链提交事务形成hash(使用OP_RETURN)
  3. 数据结构
    使用Protocol Buffers进行客户端与服务器之间的编解码操作
  4. 总账结构
    kv结构的有层级的总账

CMU课程15-721数据库系统设计(第三周)

CMU课程15-721最新一期开课啦!

课程安排

索引技术课程(三节)介绍的技术实际跟并发控制结合很紧密,接下来讨论查询相关技术(穿插了日志技术内容)

Query Execution & Scheduling

重点介绍查询执行与调度,特别是多用户DB中查询如何并行执行

处理进程模型:

  • 进程-woker 依赖OS调度,使用共享内存,例:DB2、Postgres、Oracle;还是因为当时没有linux设计
  • 进程池 CPU缓存支持不好
  • 线程-worker DB管控调度,新DB都用这个模型规避共享内存

利用多核并发加速执行:

  • Intra-Operator 在子集上独立运行相同函数(mapreduce)
  • Inter-Operator 运行类似流水线,做一部分工作(stream)

个人补充:MySQL仍然是一个线程完成一个work

补充一点内存访问架构(实际是体系结构中重要部分,由硬件设计决定)

  • UMA:多cpu与多内存都必须走中心bus
  • NUMA:几个cpu之间组成bus,cpu与内存一一对应(目前常用架构!)

数据分配策略:

  • 内存分配与cpu绑定,参考linux的move_pages
  • 使用malloc

将查询语句变为可调度的执行计划,OLTP简单,OLAP比较难。Hyper就是一个NUMA架构下多核横向分布动态调度架构:

Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework for the Many-Core Age

每个核分配一个worker(并行),pull接收分配的任务,数据分布采用RR算法

Join Alogrithms I — Hashing

原先不懂为何hash join算法作为第一个项目作业,而不是在介绍这个之后再布置。后来发现实际上这里讲解的是最新研究的多核版本算法

Join Alogrithms II — Sort-Merge

Logging & Recovery I — Physical Logging

传统DB磁盘日志基于ARIES算法,使用fuzzy cp规则,管理Active Transan Table与Dirty Page Table,赋予LSN并在易失与非易失中都要维护,过程为:analysis,redo,undo。最慢的过程发生在其他事务等待log刷到磁盘。

mem DB就不需要维护Dirty Page Table,因为所有数据都在内存。undo记录也没必要存储。

实例是Silo系统,其将log,cp,恢复操作都并行化优化,论文将这些设计考虑与最终选择都讲解的非常清晰。

该系统假设每个CPU socket对应有一个存储设备。每个设备分配一个日志线程,CPU socket对应一组工作线程。日志线程每10个epoch创建一个文件,将旧日志文件重命名并记录最大epoch。日志维护两个队列:Free Buffers与Flushing Buffers。会有一个特殊日志线程跟踪最新已经持久化的epoch(即各个日志线程中持久化的epoch最大值),小于此epoch的事务信息才可以丢弃。

每个disk对应一个cp线程,该线程有可能因为分区而写多个文件(voltDB甚至直接使用MVCC的version来做cp),cp的频率。恢复也可以并行进行。使用YCSB和TPC-C测试,吞吐率损失10%,lantency没有统计。

Logging & Recovery II — Alternative Methods

讨论一下逻辑日志,内存checkpoint以及Facebook的快速恢复方法(共享内存恢复)。

OLTP中节点失效很少见(数据不大,通常<10个节点),因此比较适合使用逻辑日志,记录格式通常包括(存储方法名,输入参数,额外安全检测信息),注意逻辑日志协议设计要满足可确定性,因此需要:

  • 事务执行前顺序已经固定
  • 事务逻辑是确定的

voltDB设计日志协议:在事务运行前日志就已经记录命令,且此时顺序已经分配完毕(个人理解类似MySQL),cp操作由特殊事务进行并将系统切换为COW模式,另一个特殊线程负责扫描表并制作快照,无视cp发起后的所有变化操作;DBMS同步不需要2PC协议

本站总访问量