首页 文章 精选 留言 我的

精选列表

搜索[学习],共10000篇文章
优秀的个人博客,低调大师

docker学习系列12 轻松实现 mysql 主从同步

docker的一大好处是在本地可以很方便快速的搭建负载均衡,主从同步等需要多主机的环境。 可以说是极大方便了运维成本和难度。 本节在本地搭建mysql的一主一从的集群环境。 关于主从同步的流程图,放张网上找的流程图 image.png 以mysql5.7为例 创建 mysql-master-slave 目录,比如完整路径是 D:/docker/mysql-master-slave 目录结构如下: -- master -- data mysqld.cnf -- slave -- data mysqld.cnf 其中master目录底下的 mysqld.cnf 配置文件内容为 [mysqld] pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock datadir = /var/lib/mysql #log-error = /var/log/mysql/error.log # By default we only accept connections from localhost #bind-address = 127.0.0.1 # Disabling symbolic-links is recommended to prevent assorted security risks symbolic-links=0 # 以下是新增内容 # 标识不同的数据库服务器,而且唯一 server-id=1 # 启用二进制日志 log-bin=mysql-bin log-slave-updates=1 innodb_flush_log_at_trx_commit = 2 innodb_flush_method = O_DIRECT skip-host-cache skip-name-resolve slave 目录底下的 mysqld.cnf 内容为 [mysqld] pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock datadir = /var/lib/mysql #log-error = /var/log/mysql/error.log # By default we only accept connections from localhost #bind-address = 127.0.0.1 # Disabling symbolic-links is recommended to prevent assorted security risks symbolic-links=0 # 以下是新增内容 server-id=2 log-bin=mysql-bin log-slave-updates=1 # 多主的话需要注意这个配置,防止自增序列冲突。 auto_increment_increment=2 auto_increment_offset=2 read-only=1 slave-skip-errors = 1062 skip-host-cache skip-name-resolve 基于官方mysql镜像,运行两个容器并指定一些参数 启动 名称为mysql_master的容器作为master数据库 docker run --name mysql_master -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -v D:/docker/mysql-master-slave/master/data:/var/lib/mysql -v D:/docker/mysql-master-slave/master/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf mysql:5.7 docker run —name mysql_slave -d -p 3308:3306 -e MYSQL_ROOT_PASSWORD=123456 -v D:/docker/mysql-master-slave/slave/data:/var/lib/mysql -v D:/docker/mysql-master-slave/slave/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf mysql:5.7 这个时候宿主机的 Navicat 应该可以连上容器里的两个数据库了。 配置主从同步,新开终端进入容器docker exec -it mysql_master bashmysql -u root -p 创建一个同步数据权限的用户GRANT REPLICATION SLAVE ON *.* to 'backup'@'%' identified by '123456'; 查看状态,记住File、Position的值,在 Slave 中将用到show master status; image.png 进入slave容器docker exec -it mysql_slave bashmysql -u root -p 设置主库链接change master to master_host='172.17.0.2',master_user='backup',master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=0,master_port=3306; 启动从库同步start slave 查看状态,如果 Slave_SQL_Running_State 是 Slave has read all relay log; waiting for more updates 表示正常运行。show slave status \G image.png 测试同步,在master上新建一个数据库docker exec mysql_master mysql -uroot -p123456 -e "CREATE DATABASE test"docker exec mysql_slave mysql -uroot -p123456 -e "SHOW DATABASES" 总结: mysqld.cnf 文件的由来? 答:就是从容器内的 /etc/mysql/mysql.conf.d/mysqld.cnf 拷贝出来的 主从同步的简单原理? 答: MySQL的主从复制是一个异步的复制过程,数据库从一个Master复制到Slave数据库,在Master与Slave之间实现整个主从复制的过程是由三个线程参与完成的,其中有两个线程(SQL线程和IO线程)在Slave端,另一个线程(IO线程)在Master端。 master 数据变化时会产生bin log日志,slave上的线程拉去bin log,然后在slave上重新执行日志。这样就保证了数据一致性。 show slave status 中的Slave_IO_Running和Slave_SQL_Running的含义? 答:Slave 上会同时有两个线程在工作, I/O 线程从 Master 得到数据(Binary Log 文件),放到被称为 Relay Log 文件中进行记录。另一方面,SQL 线程则将 Relay Log 读取并执行。 为什么要有两个线程?这是为了降低同步的延迟。因为 I/O 线程和 SQL 线程都是相对很耗时的操作。 从服务器同步失败? 答:看错误日志 tail /var/log/mysql/error.log 重新执行同步stop slave;change master to master_log_file='mysql-bin.000100,master_log_pos=123' 关于 file 和 pos,需在master上执行show master status获得。 或者使用 mysqlbinlog 命令分析。 如何添加多个从节点? 和添加第一个从节点类似,先导出master的数据,复制第一个slave配置文件,唯一要改变的是server-id,不能和其他的重复。之后启动新的容器,进到容器内执行change master to ...。 还需要注意当前master没有写入等操作,最好先锁表,同步设置好后在解锁。参考 问题: 如何添加slave节点服务器,如何主主备份 更多细节还得啃官方文档 使用 docker compose 配置mysql主从 http://tarunlalwani.com/post/mysql-master-slave-using-docker/ 参考: https://www.cnblogs.com/w2206/p/6963065.htmlhttps://github.com/Junnplus/blog/issues/1

优秀的个人博客,低调大师

docker学习系列11 多阶段镜像构建

本篇文章是转载,原文 从Docker版本 17.05.0-ce 开始,就支持了一种新的构建镜像的方法,叫做:多阶段构建(Multi-stage builds),旨在解决Docker构建应用容器中的一些痛点。在日常构建容器的场景中,经常会遇到在同一个容器中进行源码的获取,编译和生成,最终才构建为镜像。这样做的劣势在于: 不得不在容器中安装构建程序所必须的运行时环境 不得不在同一个容器中,获取程序的源码和构建所需的一些生态工具 构建出的镜像甚至包含了程序源码和一些不必要的文件,导致容器镜像尺寸偏大 当然,还有一种稍微优雅的方式,就是我们事先在外部将项目及其依赖库编译测试打包好后,再将其拷贝到构建目录中,这种虽然可以很好地规避第一种方式存在的风险点,但是也需要考虑不同镜像运行时,对于程序运行兼容性所带来的差异。 其实,这些痛点,Docker也想到了,官方提供了简便的多阶段构建 (multi-stage build) 方案。所谓多阶段构建,也即将构建过程分为多个阶段,在同一个Dockerfile中,通过不同的阶段来构建和生成所需要的应用文件,最终将这些应用文件添加到一个release的镜像中。这样做能完全规避上面所遇到的一系列问题。实现多阶段构建,主要依赖于新提供的关键字:from 和 as 。 下面举个栗子: FROM muninn/glide:alpine AS build-env ADD . /go/src/my-proj WORKDIR /go/src/my-proj RUN go get -v RUN go build -o /go/src/my-proj/my-server FROM alpine RUN apk add -U tzdata RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime COPY --from=build-env /go/src/my-proj/my-server /my-server EXPOSE 80 CMD ["my-server"] 多阶段构建的Dockerfile看起来像是把两个或者更多的Dockerfile合并在了一起,这也即多阶段的意思。as 关键字用来为构建阶段赋予一个别名,这样,在另外一个构建阶段中,可以通过 from 关键字来引用和使用对应关键字阶段的构建输出,并打包到容器中。 在多阶段构建完成之后,输出的镜像仅仅包含了最终输出的my-server应用,没有其他的源码文件和第三方源码包,非常的干净和简洁。因为 build-env 阶段只是一个构建的中间过程而已。 甚至,我们还可以使用更多的构建阶段来构建不同的应用,最终将这些构建产出的应用,合并到一个最终需要发布的镜像中。我们可以看一个更复杂一点的栗子: from debian as build-essential arg APT_MIRROR run apt-get update run apt-get install -y make gcc workdir /src from build-essential as foo copy src1 . run make from build-essential as bar copy src2 . run make from alpine copy --from=foo bin1 . copy --from=bar bin2 . cmd ... 再来一个Laravel项目的多阶段构建( 自己加的内容) 第一阶段:使用compose安装PHP依赖 第二阶段:安装node,并安装前端依赖然后生成编译后的文件 第三阶段:拷贝PHP依赖及前端build后的文件到项目运行目录 # # PHP Dependencies # FROM composer:1.7 as vendor COPY database/ database/ COPY composer.json composer.json COPY composer.lock composer.lock RUN composer install \ --ignore-platform-reqs \ --no-interaction \ --no-plugins \ --no-scripts \ --prefer-dist # # Frontend # FROM node:8.11 as frontend RUN mkdir -p /app/public COPY package.json webpack.mix.js yarn.lock /app/ COPY resources/assets/ /app/resources/assets/ WORKDIR /app RUN yarn install && yarn production # # Application # FROM php:7.2-apache-stretch COPY . /var/www/html COPY --from=vendor /app/vendor/ /var/www/html/vendor/ COPY --from=frontend /app/public/js/ /var/www/html/public/js/ COPY --from=frontend /app/public/css/ /var/www/html/public/css/ COPY --from=frontend /app/mix-manifest.json /var/www/html/mix-manifest.json 多阶段构建的好处不言而喻,既可以很方便地将多个彼此依赖的项目通过一个Dockerfile就可轻松构建出期望的容器镜像,并且不用担心镜像太大、源码泄露等风险。不得不说,这是一个非常不错的改进。 参考: https://docs.docker.com/develop/develop-images/multistage-build/https://yq.aliyun.com/articles/181178https://laravel-news.com/multi-stage-docker-builds-for-laravel

优秀的个人博客,低调大师

(五)Java并发学习笔记--线程安全-同步容器

一、为什么会出现同步容器? 在Java的集合容器框架中,主要有四大类别:List、Set、Queue、Map。 List、Set、Queue接口分别继承了Collection接口,Map本身是一个接口。 注意Collection和Map是一个顶层接口,而List、Set、Queue则继承了Collection接口,分别代表数组、集合和队列这三大类容器。 像ArrayList、LinkedList都是实现了List接口,HashSet实现了Set接口,而Deque(双向队列,允许在队首、队尾进行入队和出队操作)继承了Queue接口,PriorityQueue实现了Queue接口。另外LinkedList(实际上是双向链表)实现了了Deque接口。 像ArrayList、LinkedList、HashMap这些容器都是非线程安全的。 如果有多个线程并发地访问这些容器时,就会出现问题。 因此,在编写程序时,必须要求程序员手动地在任何访问到这些容器的地方进行同步处理,这样导致在使用这些容器的时候非常地不方便。 所以,Java提供了同步容器供用户使用。 二、Java中的同步容器类 在Java中,同步容器主要包括2类: Vector、Stack、HashTable Collections类中提供的静态工厂方法创建的类 Vector实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施。 Stack也是一个同步容器,它的方法也用synchronized进行了同步,它实际上是继承于Vector类。 HashTable实现了Map接口,它和HashMap很相似,但是HashTable进行了同步处理,而HashMap没有。 Collections类是一个工具提供类,注意,它和Collection不同,Collection是一个顶层的接口。在Collections类中提供了大量的方法,比如对集合或者容器进行排序、查找等操作。最重要的是,在它里面提供了几个静态工厂方法来创建同步容器类,如下图所示: 3. 同步容器的缺陷 从同步容器的具体实现源码可知,同步容器中的方法采用了synchronized进行了同步,那么很显然,这必然会影响到执行性能,另外,同步容器就一定是真正地完全线程安全吗?不一定,这个在下面会讲到。 我们首先来看一下传统的非同步容器和同步容器的性能差异,我们以ArrayList和Vector为例: 1.性能问题 我们先通过一个例子看一下Vector和ArrayList在插入数据时性能上的差异: public class Test { public static void main(String[] args) throws InterruptedException { ArrayList<Integer> list = new ArrayList<Integer>(); Vector<Integer> vector = new Vector<Integer>(); long start = System.currentTimeMillis(); for(int i=0;i<100000;i++) list.add(i); long end = System.currentTimeMillis(); System.out.println("ArrayList进行100000次插入操作耗时:"+(end-start)+"ms"); start = System.currentTimeMillis(); for(int i=0;i<100000;i++) vector.add(i); end = System.currentTimeMillis(); System.out.println("Vector进行100000次插入操作耗时:"+(end-start)+"ms"); } } 这段代码在我机器上跑出来的结果是: 进行同样多的插入操作,Vector的耗时是ArrayList的两倍。 这只是其中的一方面性能问题上的反映。 另外,由于Vector中的add方法和get方法都进行了同步,因此,在有多个线程进行访问时,如果多个线程都只是进行读取操作,那么每个时刻就只能有一个线程进行读取,其他线程便只能等待,这些线程必须竞争同一把锁。 因此为了解决同步容器的性能问题,在Java 1.5中提供了并发容器,位于java.util.concurrent目录下,并发容器的相关知识将在下一篇文章中讲述。 2.同步容器真的是安全的吗? 也有有人认为Vector中的方法都进行了同步处理,那么一定就是线程安全的,事实上这可不一定。看下面这段代码: public class Test { static Vector<Integer> vector = new Vector<Integer>(); public static void main(String[] args) throws InterruptedException { while(true) { for(int i=0;i<10;i++) vector.add(i); Thread thread1 = new Thread(){ public void run() { for(int i=0;i<vector.size();i++) vector.remove(i); }; }; Thread thread2 = new Thread(){ public void run() { for(int i=0;i<vector.size();i++) vector.get(i); }; }; thread1.start(); thread2.start(); while(Thread.activeCount()>10) { } } } } 在我机器上运行的结果: 正如大家所看到的,这段代码报错了:数组下标越界。 也许有朋友会问:Vector是线程安全的,为什么还会报这个错?很简单,对于Vector,虽然能保证每一个时刻只能有一个线程访问它,但是不排除这种可能: 当某个线程在某个时刻执行这句时: for(int i=0;i<vector.size();i++) vector.remove(i); 将下标为9的元素删除了。 那么通过get方法访问下标为9的元素肯定就会出问题了。 因此为了保证线程安全,必须在方法调用端做额外的同步措施,如下面所示: public class Test { static Vector<Integer> vector = new Vector<Integer>(); public static void main(String[] args) throws InterruptedException { while(true) { for(int i=0;i<10;i++) vector.add(i); Thread thread1 = new Thread(){ public void run() { synchronized (Test.class) { //进行额外的同步 for(int i=0;i<vector.size();i++) vector.remove(i); } }; }; Thread thread2 = new Thread(){ public void run() { synchronized (Test.class) { for(int i=0;i<vector.size();i++) vector.get(i); } }; }; thread1.start(); thread2.start(); while(Thread.activeCount()>10) { } } } } ConcurrentModificationException异常 在对Vector等容器并发地进行迭代修改时,会报ConcurrentModificationException异常,但是在并发容器中不会出现这个问题。 ConcurrentModificationException异常出现的原因 先看下面这段代码: public class Test { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(2); Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ Integer integer = iterator.next(); if(integer==2) list.remove(integer); } } } 运行结果: 从异常信息可以发现,异常出现在checkForComodification()方法中。 我们不忙看checkForComodification()方法的具体实现,我们先根据程序的代码一步一步看ArrayList源码的实现: 首先看ArrayList的iterator()方法的具体实现,查看源码发现在ArrayList的源码中并没有iterator()这个方法,那么很显然这个方法应该是其父类或者实现的接口中的方法,我们在其父类AbstractList中找到了iterator()方法的具体实现,下面是其实现代码: public Iterator<E> iterator() { return new Itr(); } 从这段代码可以看出返回的是一个指向Itr类型对象的引用,我们接着看Itr的具体实现,在AbstractList类中找到了Itr类的具体实现,它是AbstractList的一个成员内部类,下面这段代码是Itr类的所有实现: private class Itr implements Iterator<E> { int cursor = 0; int lastRet = -1; int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } 首先我们看一下它的几个成员变量: cursor:表示下一个要访问的元素的索引,从next()方法的具体实现就可看出 lastRet:表示上一个访问的元素的索引 expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。 modCount是AbstractList类中的一个成员变量 protected transient int modCount = 0; 该值表示对List的修改次数,查看ArrayList的add()和remove()方法就可以发现,每次调用add()方法或者remove()方法就会对modCount进行加1操作。 好了,到这里我们再看看上面的程序: 当调用list.iterator()返回一个Iterator之后,通过Iterator的hashNext()方法判断是否还有元素未被访问,我们看一下hasNext()方法,hashNext()方法的实现很简单: public boolean hasNext() { return cursor != size(); } 如果下一个访问的元素下标不等于ArrayList的大小,就表示有元素需要访问,这个很容易理解,如果下一个访问元素的下标等于ArrayList的大小,则肯定到达末尾了。 然后通过Iterator的next()方法获取到下标为0的元素,我们看一下next()方法的具体实现: public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } 这里是非常关键的地方:首先在next()方法中会调用checkForComodification()方法,然后根据cursor的值获取到元素,接着将cursor的值赋给lastRet,并对cursor的值进行加1操作。初始时,cursor为0,lastRet为-1,那么调用一次之后,cursor的值为1,lastRet的值为0。注意此时,modCount为0,expectedModCount也为0。 接着往下看,程序中判断当前元素的值是否为2,若为2,则调用list.remove()方法来删除该元素。 我们看一下在ArrayList中的remove()方法做了什么: public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work } 通过remove方法删除元素最终是调用的fastRemove()方法,在fastRemove()方法中,首先对modCount进行加1操作(因为对集合修改了一次),然后接下来就是删除元素的操作,最后将size进行减1操作,并将引用置为null以方便垃圾收集器进行回收工作。 那么注意此时各个变量的值:对于iterator,其expectedModCount为0,cursor的值为1,lastRet的值为0。 对于list,其modCount为1,size为0。 接着看程序代码,执行完删除操作后,继续while循环,调用hasNext方法()判断,由于此时cursor为1,而size为0,那么返回true,所以继续执行while循环,然后继续调用iterator的next()方法: 注意,此时要注意next()方法中的第一句:checkForComodification()。 在checkForComodification方法中进行的操作是: final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } 如果modCount不等于expectedModCount,则抛出ConcurrentModificationException异常。 很显然,此时modCount为1,而expectedModCount为0,因此程序就抛出了ConcurrentModificationException异常。 到这里,想必大家应该明白为何上述代码会抛出ConcurrentModificationException异常了。 关键点就在于:调用list.remove()方法导致modCount和expectedModCount的值不一致。 注意,像使用for-each进行迭代实际上也会出现这种问题。 在单线程环境下的解决办法 既然知道原因了,那么如何解决呢? 其实很简单,细心的朋友可能发现在Itr类中也给出了一个remove()方法: public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } 在这个方法中,删除元素实际上调用的就是list.remove()方法,但是它多了一个操作: expectedModCount = modCount; 因此,在迭代器中如果要删除元素的话,需要调用Itr类的remove方法。 将上述代码改为下面这样就不会报错了: public class Test { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(2); Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ Integer integer = iterator.next(); if(integer==2) iterator.remove(); //注意这个地方 } } } 在多线程环境下的解决方法 上面的解决办法在单线程环境下适用,但是在多线程下适用吗?看下面一个例子: public class Test { static ArrayList<Integer> list = new ArrayList<Integer>(); public static void main(String[] args) { list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); Thread thread1 = new Thread(){ public void run() { Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ Integer integer = iterator.next(); System.out.println(integer); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }; }; Thread thread2 = new Thread(){ public void run() { Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ Integer integer = iterator.next(); if(integer==2) iterator.remove(); } }; }; thread1.start(); thread2.start(); } } 运行结果: 1.jpg 有可能有朋友说ArrayList是非线程安全的容器,换成Vector就没问题了,实际上换成Vector还是会出现这种错误。 原因在于,虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也即是说expectedModCount是每个线程私有。假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了。 因此一般有2种解决办法: 1)在使用iterator迭代的时候使用synchronized或者Lock进行同步; 2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。 关于并发容器的内容将在下一篇文章中讲述。

优秀的个人博客,低调大师

docker学习系列4 简单总结 docker-curriculum

来源:https://docker-curriculum.com/ 这篇文章不错,可以作为第一篇 docker 的入门,我简单总结了下。顺便重温下之前的内容。 如果你是刚学docker,最好跟着敲一遍。 安装,略,自己去官方文档查 执行 docker pull busybox 去官方拉镜像 BusyBox 是一个集成了三百多个最常用Linux命令和工具的软件。 简单的说BusyBox就好像是个大工具箱,它集成压缩了 Linux 的许多工具和命令,也包含了 Android 系统的自带的shell。 使用 docker images 查看镜像 创建容器启动 docker run busybox 会看到啥都没有发生,因为没有提供任何命令,容器启动后,运行个空命令就退出了。 如果提供个命令呢 docker run busybox echo "hello from busybox" 这个能看到输出了,但是容器执行完依然退出了。 我想查看正在运行的容器 docker ps 没有任何输出 试试 docker ps -a 可以看到刚刚运行过的容器了,注意 status 列 image.png 如果想以交互式方式运行容器,并进入容器终端,就用 docker run -it busybox sh 注意 -it 一般是同时出现的 image.png -t tty的缩写 终端控制台 -i interactive 可交互缩写 如果想知道 run 后面都能带什么参数及含义,请使用 docker run --help 一些术语: Docker Daemon - Docker为C/S架构,服务端为docker daemon,在后台运行,用于管理,构建,分发容器 Docker Client - 就是咱们用的命令行工具,还有 GUI 图形化的Kitematic Docker Hub - 分享,查找镜像资源的网站 WEBAPPS WITH DOCKER 我们运行一个容器 docker run --rm prakhar1989/static-site prakhar1989/static-site 是作者维护的镜像 --rm 当退出容器时自动移除 这里容器启动会显示了 nginx is running,但没有告诉更多的信息 image.png 按 ctrl+c 退出 使用 docker run -d -P --name static-site prakhar1989/static-site -d 放到后台运行 -P 将容器内应用运行使用的端口暴露出来 ( Publish all exposed ports to random ports) --name 给容器起个名字 image.png 端口有了,可以打开站点了,还可以使用 docker run -p 8888:80 prakhar1989/static-site 指定端口 image.png 同时运行了两个容器 image.png 暂停容器用 docker stop static-site static-site 是我们给运行时给容器起的名字,也可以用ID 后面内容是使用 Dockerfile 构建自己的镜像并上传到AWS。由于之前讲过而且aws国内使用不方便,此处略过。 当docker安装后,会自动创建三个网络 $ docker network ls NETWORK ID NAME DRIVER SCOPE c2c695315b3a bridge bridge local a875bec5d6fd host host local ead0e804a67b none null local 默认使用的是 bridge 桥接。使用 docker network inspect bridge 在 Containers 下面看到正在使用该网络方式的所有容器。默认所有的容器都会使用bridge,通过刚才的命令还可以看到每个容器分配到的内部IP。 一般是 172.17.0.xx。 为了安全及方便,我们需要使某几个容器之间使用自己的桥接网络,如何做到呢? 使用 docker network 创建一个新的bridge网络,比如 docker network create foodtrucks-net image.png 运行 Elasticsearch 容器并把刚创建的网络分配给他docker run -d --name es --net foodtrucks-net -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2 然后运行Python Flask 容器,并进到bash终端docker run -it --rm --net foodtrucks-net finleyma/foodtrucks-web bash 来测试下能否访问到 Elasticsearch 容器curl es:9200 tips: 访问容器网络没有输入容器的IP地址,用的容器名称表示,这种能力叫 automatic service discovery 备注:elasticsearch挺占内存的,我服务器4G内存,在docker运行启动后出现了警告

优秀的个人博客,低调大师

学习OpenCV3》第6章课后习题

//ExercisesatendofChapter5,《learningOpenCV3》 #include"stdafx.h" #include<opencv2/opencv.hpp> #include<iostream> usingnamespacecv; usingnamespacestd; voidhelp(constchar**argv){ cout<<"\n\n" <<"ThisprogramsolvestheExercisesattheendofChapter5\n" <<"Call:\n" <<argv[0]<<"<path/image_name>\n\n" <<"Forexample:"<<argv[0]<<"/test.jpg\n" <<endl; } intmain(intargc,constchar**argv) { help(argv); if(argc<2){ cout<<"\nERROR:Youhadtoofewparameters.\n"<<endl; return-1; } /************************************************************************/ /*5.1.Drawingpractice:loadorcreateanddisplayacolorimage.Drawoneexampleof everyshapeandlinethatOpenCVcandraw.*/ /************************************************************************/ Matsrc=imread("e:/template/lena.jpg"); cv::circle(src,Point(100,100),100,Scalar(255,255,255),2);//circle cv::rectangle(src,Point(0,0),Point(300,300),Scalar(255,255,255),2);//rectangle cv::line(src,Point(0,0),Point(300,300),Scalar(255,255,255),2);//line cv::ellipse(src,cv::Point(100,100),Size(100,100),45,0,180,Scalar(255,0,0),2);//ellipse /************************************************************************/ /*5.2.Grayscale:loadanddisplayacolorimage. a.Turnitintothree-channelgrayscale(itisstillanBGRimage,butitlooksgray totheuser). b.Drawcolortextontotheimage.*/ /************************************************************************/ //a Mattmp; cvtColor(src,tmp,COLOR_BGR2GRAY); cvtColor(tmp,src,COLOR_GRAY2BGR); //b putText(src,"puttext",Point(50,30),CV_FONT_HERSHEY_DUPLEX,1.0f,Scalar(0,255,0)); /************************************************************************/ /*5.5.Usecv::LineIteratortocountpixelsondifferentlinesegmentsin,say,a300×300image. a.Atwhatanglesdoyougetthesamenumberofpixelsfor4-connectedand 8-connectedlines? b.Forlinesegmentanglesotherthantheabove,whichcountsmorepixels: 4-connectedor8-connectedlines? c.Foragivenlinesegment,explainthedifferenceinthelengthofthelinecompared tothenumberofpixelsyoucountiteratingalongthelinefor both4-connectedand8-connected?Whichconnectednessisclosertothetrue linelength? /************************************************************************/ //a、 LineIteratorit_4_x(src,Point(0,0),Point(0,100),4); LineIteratorit_8_x(src,Point(0,0),Point(0,100),4); LineIteratorit_4_y(src,Point(0,0),Point(100,0),4); LineIteratorit_8_y(src,Point(0,0),Point(100,0),4); cout<<"it_4_x"<<it_4_x.count<<"it_8_x"<<it_8_x.count<<endl; cout<<"it_4_y"<<it_4_y.count<<"it_8_y"<<it_8_y.count<<endl; //btheansweris:4-connectedcountsmorepixelsthan8-connectedcounts LineIteratorit_4(src,Point(0,0),Point(100,100),4); LineIteratorit_8(src,Point(0,0),Point(100,100),8); cout<<"it_4"<<it_4.count<<"largethanit_8"<<it_8.count<<endl; //c //thedifferenceisthesameasthedifferencebetween4-connectedand8-connected //Ivelevethe8-connectedisclosertothetruelinelength. waitKey(); return0; } 来自为知笔记(Wiz) 目前方向:图像拼接融合、图像识别 联系方式:jsxyhelu@foxmail.com

优秀的个人博客,低调大师

docker学习系列2 保存对容器的修改

接上篇 docker容器虽然运行起来了。 但遇到了新的问题: 容器内安装的服务器是nginx,nginx对 PHPINFO 支持不好,对于ThiankPHP项目,简单的说在apache服务器下运行 http://localhost:8088/home/Index/index 能正常返回结果,而nginx返回404,必须要写成 http://localhost:8088/index.php?m=home&c=Index&a=demo 所以我需要修改nginx配置文件,使其支持。 由于容器本身是无状态的我修改完配置文件,关闭docker,下次在启动后还是原样,我需要保存修改。 下面是解决方法: 容器为了精简没有按照VIM,编辑文件不方便,要先安装 apt-get update; apt-get install vim 如果执行 apt-get update 超时了,得翻墙。 vi /etc/nginx/sites-enabled/default 编辑并修改配置文件,记得最好先备份 修改完新开个窗口 先执行 docker ps 查看正在运行的容器,复制 container id。 然后 docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] 如 docker commit cb439fb2c714 finley/phpenv:tp3.2 commit 会基于对container的修改创建一个新的镜像 具体用法请参见官方文档:commit 注意: 经查,不推荐更改运行中的容器配置,容器本身是无状态的,当然也可以通过进入容器内部的方式进行更改: docker exec -it 这样的更改是无法持久化保存的,当容器重启后,更改就丢失了,正确的做法是将需要持久化保存的数据放在挂载的存储卷中,当配置需要改变时直接删除重建。 回顾: # 从别人那拉个镜像 docker pull eriksencosta/php-dev # 基于上面的镜像加入了自己的修改并提交为自己的镜像,还打了tag docker commit cb439fb2c714 finleyma/php-dev:tp3.2 问题: 这个项目的环境是有了,但是是多人开发,我如何将我的配好的镜像分享给他人呢?见下篇

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册