暂时未有相关云产品技术能力~
ETL是将数据从来源端经过清洗(extract)、转换(transform)、加载(load)至目的端的过程。正常的 ETL 过程应当是 E、T、L 这三个步骤逐步进行,也就是先清洗转换之后再加载进目标端(通常是数据库),最后在数据库中的只是合理的结果数据。这个过程本来很合理,但实际过程中经常被执行成ELT甚至LET,即源端数据先装载进目标库再进行清洗和转换。出现这种现象是因为源端数据可能来源多处,数据库、文件、web等等,不仅数据源多样数据质量也参差不齐,由于E和T这两个步骤会涉及大量数据计算,除了数据库以外,其他数据源并不具备多少的计算能力,想要完成这些计算就要先加载到数据库再进行,这就形成了LET。而且,即使源端是数据库也会面临多库的场景,跨库完成数据清洗和转换远没有先装载到目标库再处理方便,同样会造成ELT或LET。那么ETL变成ELT/LET会带来哪些问题呢?首先是时间成本增加。大量未经清洗和转换的原始(无用)数据装载进数据库会带来过长的时间消耗。而且数据库的计算资源有限,完成额外的E、T计算势必要消耗很长时间,进一步增加时间成本。ETL通常是有时间限制的,一般会在业务空闲的时间进行,比如前一天22点到第二天5点,如果在指定时间段没有完成就会影响第二天的业务,这就是常说的ETL时间窗口。ETL时间过长会导致时间窗口不足,影响正常业务。此外,从数据库容量的角度来看,存储大量没有经过清洗转换的原始数据会占用过多数据库空间造成数据库容量过大,导致数据库面临扩容压力。现代应用经常使用的JSON或XML格式的多层数据入库还要在数据库中建立多个关联的表来存储,会进一步加剧数据库容量问题。任务越来越多、资源越来越少、时间窗口有限,这样就陷入了恶性循环。那么,为什么要把数据加载数据库后才能做E和T这两个动作呢?如前所述,是因为数据库外的计算能力不足,先入库再计算是为了利用数据库的计算能力,如果能够提供库外计算能力,那么这个问题也就迎刃而解了,回归合理的ETL过程。使用开源集算器SPL可以实现这个目标。SPL是一款独立的开源数据计算引擎,提供了不依赖数据库的计算能力,可以对接多种数据源完成数据处理。基于SPL丰富的计算类库、敏捷语法和过程计算可以很方便地完成复杂数据计算任务,在数据库外完成数据清洗(E)和转换(T),将整理后数据加载(L)到目标库中实现真正的ETL。库外计算实现真正ETL多源支持与混合计算SPL可以对接多种数据源,这样来源端数据源无论有无计算能力都可以通过SPL完成数据清洗和转换。特别地,SPL还能实现多源混合计算,将多源数据统一清洗转换后加载到库,不需要再借助数据库的计算能力就能完成ETL工作。尤其是对JSON和XML等多层数据格式提供了很好支持,简单一个函数就能完成解析,非常方便。举个简单的例子,将json与数据库混合计算后更新到数据库的过程:A1=json(file("/data/EO.json").read())解析JSON数据2=A1.conj(Orders)3=A2.select(orderdate>=date(now()))过滤当日数据4=connect(“sourcedb”).query@x(“select ID,Name,Area from Client”)数据库数据5=join(A3:o,Client;A4:c,ID)关联计算6=A5.new(o.orderID:orderID,…)7=connect(“targetdb”).update(A6,orders)装载到目标库强计算能力与过程控制SPL提供了专业的结构化数据对象及其上的丰富运算。不仅分组汇总、循环分支、排序过滤、集合运算等基础计算可以进行,位置计算、排序排名、不规则分组也提供直接支持。SPL还提供了专门用于大数据计算的游标支持,通过游标就可以处理超过内存容量的数据,计算实现与全内存方式几乎完全一样。比如通过游标读取文件并进行分组汇总:=file(“persons.txt”).cursor@t(sex,age).groups(sex;avg(age))与全内存读取计算:=file(“persons.txt”).import@t(sex,age).groups(sex;avg(age))除了丰富的计算类库,SPL还支持过程计算,可以按自然思维思维分步编写代码,适合完成原本在数据库中使用存储过程实现的ETL复杂计算。而SPL计算在数据库外,不会对数据库造成负担,同时兼具灵活性和高性能,实现“库外存储过程”的效果,是传统存储过程的很好替代。库外计算还可以为数据库充分减负。以往要借助数据库完成的ET计算现在都在库外完成,既不需要额外消耗数据库的计算资源,也无需存储未经清洗的大量原始数据,空间占用也少,数据库的资源和容量问题都能得到很好解决。同时,SPL的语法体系比SQL和Java等也更为敏捷,涉及E和T计算尤其是复杂计算,算法实现更简洁代码更短,开发效率更高。比如在实现某保险公司车险保单ETL业务时,使用SPL不到500格(网格式编码)代码就实现了原本2000行存储过程的计算,工作量减少了1/3以上。(案例详情:开源 SPL 优化保险公司跑批优从 2 小时到 17 分钟)从技术栈的角度来看,基于SPL还可以获得一致的语法风格,面对多样性数据源ETL时可以获得通用一致的计算能力。不仅技术路线统一,开发维护也很方便,程序员无需掌握不同数据源数据的处理方法,学习成本也更低。特别的,统一的技术路线具备更强的移植性,ETL数据源变化只需要更改取数代码即可,主要的计算逻辑无需更改,具备很强的移植性。高性能保障时间窗口对于源数据读取,SPL能很方便地进行并行处理,充分发挥多CPU的优势加速数据读取和计算速度。比如并行取数:AB1fork to(n=12)=connect("sourcedb")2=B1.query@x("SELECT * FROM ORDERS WHERE MOD(ORDERID,?)=?", n, A3-1)3=A1.conj()类似的,读取大文件时也可以并行:=file(“orders.txt”).cursor@tm(area,amount;4)使用 @m 选项即可创建多路并行游标,SPL 会自动处理并行以及将结果再汇总。SPL还有很多计算函数也提供并行选项,如过滤A.select()、排序A.sort()等增加@m选项后都可以自动完成并行计算。在ELT任务中还经常出现数据落地的情况,无论是中间数据还是最后的计算结果,这都涉及数据存储。SPL提供了两种二进制存储形式,不仅存储了数据类型不必再次解析效率更高,而且还采用了适合的压缩机制可以有效平衡CUP和硬盘时间,同时提供了行式和列式存储方式适应更多场景,采用独有的倍增分段技术还可以实现单文件可追加分块方案更方便并行计算。这些高性能存储机制为计算性能提供了基础保障,要知道高性能计算依靠的就是存储和算法。SPL提供了众多高性能算法,仍是上述案例(开源 SPL 优化保险公司跑批优从 2 小时到 17 分钟)中,使用SPL不仅把代码量减少到1/3,还将计算时间从2小时缩短到17分钟。其中主要使用了SPL特有的遍历复用技术,可以在对大数据的一次遍历过程中实现多种运算,有效地减少外存访问量。而关系数据库中用SQL无法实现这样的运算,有多种运算就需要遍历多次。在本例中就涉及对一个大表进行三次关联和汇总的运算,使用SQL要将大表遍历三次,而使用SPL只需要遍历一次,所以获得了巨大的性能提升。在ETL业务中还经常出现巨大主子表关联的情况,比如订单和订单明细,这些关联是通过主键(或部分主键)的一对多关联,如果事先按照主键排序,那么关联计算可以使用有序归并算法,相对常规HASH JOIN算法,复杂度可以从O(N*M)降到O(M+N),性能将大幅提升。但数据库基于无序集合理论,SQL也很难利用数据有序来提高性能。在上面案例中也涉及这种主子关联运算,使用SPL的有序归并算法大幅提升了关联性能。将具备强计算能力的SPL作为ETL过程中的ET引擎,将数据计算从源端和目标端独立出来不与任何一端耦合在一起,这样可以获得更强的灵活性和更高的移植性,同时不对源和目标造成过大压力,享受库外计算的便利,实现了真正的ETL过程。同时基于SPL的高性能存储、高性能算法与并行计算又充分保障了ETL效率,这样就可以在有限的时间窗口内完成更多ETL任务。SPL资料 SPL官网 SPL下载 SPL源代码
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道第一名🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. 关于MQ1.1 什么是MQ?1.2 MQ是干什么用的? 1.3 MQ衡量标准 1.4 主流竞品分析 2. 关于RabbitMQ2.1 RabbitMQ的优势 2.2 RabbitMQ架构 2.3 RabbitMQ各组件功能 3. 在Docker中运行RabbitMQ 👑👑👑结束语👑👑👑 分布式系统和大数据处理平台是目前业界关注的热门技术。本篇文章将重点介绍热门的消息队列中间件RabbitMQ。 1. 关于MQ1.1 什么是MQ? 消息总线(Message Queue),是一种跨进程、异步的通信机制,用于上下游传递消息。由消息系统来确保消息的可靠传递。 1.2 MQ是干什么用的? 应用解耦、异步、流量削锋、数据分发、错峰流控、日志收集等等... 1.3 MQ衡量标准 服务性能、数据存储、集群架构 1.4 主流竞品分析 当前市面上mq的产品很多,比如RabbitMQ、Kafka、ActiveMQ、ZeroMQ和阿里巴巴捐献给Apache的RocketMQ。甚至连redis这种NoSQL都支持MQ的功能。 2. 关于RabbitMQ RabbitMQ是一个开源的消息代理和队列服务器,用来通过普通协议在不同的应用之间共享数据(跨平台跨语言)。RabbitMQ是使用Erlang语言编写,并且基于AMQP协议实现。 2.1 RabbitMQ的优势 可靠性(Reliablity):使用了一些机制来保证可靠性,比如持久化、传输确认、发布确认。灵活的路由(Flexible Routing):在消息进入队列之前,通过Exchange来路由消息。对于典型的路由功能,Rabbit已经提供了一些内置的Exchange来实现。针对更复杂的路由功能,可以将多个Exchange绑定在一起,也通过插件机制实现自己的Exchange。消息集群(Clustering):多个RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。高可用(Highly Avaliable Queues):队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。多种协议(Multi-protocol):支持多种消息队列协议,如STOMP、MQTT等。多种语言客户端(Many Clients):几乎支持所有常用语言,比如Java、.NET、Ruby等。管理界面(Management UI):提供了易用的用户界面,使得用户可以监控和管理消息Broker的许多方面。跟踪机制(Tracing):如果消息异常,RabbitMQ提供了消息的跟踪机制,使用者可以找出发生了什么。插件机制(Plugin System):提供了许多插件,来从多方面进行扩展,也可以编辑自己的插件。 2.2 RabbitMQ架构 2.3 RabbitMQ各组件功能 Broker:标识消息队列服务器实体.Virtual Host:虚拟主机。标识一批交换机、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个vhost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在链接时指定,RabbitMQ默认的vhost是 /。Exchange:交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。Queue:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。Banding:绑定,用于消息队列和交换机之间的关联。一个绑定就是基于路由键将交换机和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Channel:信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟链接,AMQP命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说,建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接。Connection:网络连接,比如一个TCP连接。Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序。Consumer:消息的消费者,表示一个从一个消息队列中取得消息的客户端应用程序。Message:消息,消息是不具名的,它是由消息头和消息体组成。消息体是不透明的,而消息头则是由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(优先级)、delivery-mode(消息可能需要持久性存储[消息的路由模式])等。 3. 在Docker中运行RabbitMQ AMQP架构中有两个主要组件:Exchange和Queue,两者都在服务端,又称Broker,由RabbitMQ实现的。客户端通常有Producer和Consumer两种类型,如下图所示。 在使用RabbitMQ过程中需要注意的是,它将数据存储在Node中,默认情况为hostname。因此在使用docker run指令运行容器的时候,应该通过-h/--hostname参数指定每一个rabbitmq daemon运行的主机名。这样就可以轻松地管理和维护数据了: $ docker run -d --hostname my-rabbit --name some-rabbit rabbitmq:3 3f28f6290e05375363ee661151170d37fbc89ada004c3235f02997b711b4cb2b 用户使用rabbitmqctl工具进行远程管理,或跨容器管理的时候,会需要设置持久化的cookie。如果需要了解关于Erlang Cookie的信息,可以参见RabbitMQ官网的集群指南。 这里可以使用RABBITMQ_ERLANG_COOKIE参数进行设置: $ docker run -d --hostname my-rabbit --name some-rabbit -e RABBITMQ_ERLANG_ COOKIE='secret cookie here' rabbitmq:3 使用cookie连接至一个独立的实例: $ docker run -it --rm --link some-rabbit:my-rabbit -e RABBITMQ_ERLANG_COOKIE= 'secret cookie here' rabbitmq:3 bash root@f2a2d3d27c75:/# rabbitmqctl -n rabbit@my-rabbit list_users Listing users ... guest [administrator] 同样,也可以使用RABBITMQ_NODENAME简化指令: $ docker run -it --rm --link some-rabbit:my-rabbit -e RABBITMQ_ERLANG_COOKIE= 'secret cookie here' -e RABBITMQ_NODENAME=rabbit@my-rabbit rabbitmq:3 bash root@f2a2d3d27c75:/# rabbitmqctl list_users Listing users ... guest [administrator] 默认情况下,rabbitmq会安装并启动一些管控插件,如rabbitmq:3- management。通常可以通过默认用户名密码以及标准管控端口15672访问这些插件: $ docker run -d --hostname my-rabbit --name some-rabbit rabbitmq:3-management 用户可以通过浏览器访问http://container-ip:15672,如果需要从宿主机外访问,则使用8080端口: $ docker run -d --hostname my-rabbit --name some-rabbit -p 8080:15672 rabbitmq: 3-management 如果需要修改默认用户名与密码(guest:guest),则可以使用RABBITMQ_DEFAULT_USER和RABBITMQ_DEFAULT_PASS环境变量: $ docker run -d --hostname my-rabbit --name some-rabbit -e RABBITMQ_DEFAULT_ USER=user -e RABBITMQ_DEFAULT_PASS=password rabbitmq:3-management 如果需要修改默认vhost,可以修改RABBITMQ_DEFAULT_VHOST环境变量: $ docker run -d --hostname my-rabbit --name some-rabbit -e RABBITMQ_DEFAULT_ VHOST=my_vhost rabbitmq:3-management 然后连接至daemon: $ docker run --name some-app --link some-rabbit:rabbit -d application-that-uses- rabbitmq 用户也可以访问官方镜像仓库,并对Dockerfile进行更多定制。 👑👑👑结束语👑👑👑
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道第一名🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. 关于NoSQL1.1 NoSQL的概念1.2 NoSQL的特性1.3 NoSQL优点2. 关于Redis 2.1 Redis持久化数据的机制2.2 Redis主从同步的原理 2.3 Redis及其优缺点 3. 在Docker中运行Redis3.1 连接Redis容器 3.2 使用自定义配置 👑👑👑结束语👑👑👑1. 关于NoSQL1.1 NoSQL的概念 NoSQL(NoSQL = Not Only SQL ),不意为反SQL运动,是一项全新的数据库革命性运动,2000年前就有人提出,发展至2009年趋势越发高涨。它是指运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。 分类 Examples举例 典型应用场景 数据模型 优点 键值(key-value) Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 Key 指向 Value 的键值对,通常用hash table来实现 查找速度快 列存储数据库 Cassandra, HBase, Riak 分布式的文件系统 以列簇式存储,将同一列数据存在一起 查找速度快,可扩展性强,更容易进行分布式扩展 文档型数据库 CouchDB, MongoDB Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) Key-Value对应的键值对,Value为结构化数据 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 图形(Graph)数据库 Neo4J, InfoGrid, Infinite Graph 社交网络,推荐系统等。专注于构建关系图谱 图结构 利用图结构相关算法。比如最短路径寻址,N度关系查找等 1.2 NoSQL的特性 NoSQL是key-value形式存储,和传统的关系型数据库不一样,不一定遵循传统数据库的一些基本要求,比如说遵循SQL标准、ACID属性、表结构等等。这类数据库主要有以下特点: 非关系型的、分布式、开源的、水平可扩展的 处理超大量数据 击碎了性能瓶颈 对数据高并发读写 对海量数据的高效率存储和访问 对数据的高扩展性和高可用性 1.3 NoSQL优点 易于数据分散,数据间相对独立,没有关联提升性能和扩展,是水平扩展的解决方案速度较快,绝大多数数据存储在内存之中 2. 关于Redis 2.1 Redis持久化数据的机制 RDB:定期的 或 具备触发条件 或 手动执行的完整备份的方式 优点:还原速度快 缺点:较大的资源浪费(时间、空间)AOF:仅仅只保存修改的数据(数据结构被替换)- 增量备份 根据企业自己需求确定开启哪一个(只做缓存都不开启也没事 2.2 Redis主从同步的原理 从服务器向主服务器发送SYNC请求;主服务器执行BGSAVE命令,将当前内存里的所有数据拍照为快照文件,将快照文件发送给从服务器,在此期间所有有修改的命令会被记录下来;从服务器执行快照;从服务器执行快照,若主服务器有数据被修改,就发送缓存的命令,从服务器执行;(等同于先发送完整备份在发送增量备份) 2.3 Redis及其优缺点 Redis是一个开源的,先进的key-value存储。它通常被称为数据结构服务器,因为键可以包含string(字符串)、hash(哈希)、list(链表)、set(集合)和zset(sorted-set--有序集合)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作。Redis和Memcached类似,它支持存储的value类型相对更多,与memcached一样,为了保证效率,数据都是缓存在内存中,区别是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。 优点 保证数据的一致性和原子性 数据更新的开销很小 可以进行复杂处理 存在较多成熟案例和模板信息缺点 大量的数据写入 表结构的变更 查询速度 集群化的难度 3. 在Docker中运行Redis 通过docker run指令可以直接启动一个redis-container容器: $ docker run --name redis-container -d redis 6f7d16f298e9c505f35ae28b61b4015877a5b0b75c60797fa4583429e4a14e24 之后可以通过docker ps指令查看正在运行的redis-container容器的容器ID: $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6f7d16f298e9 redis "docker-entrypoint.sh" 32 seconds ago Up 31 seconds 6379/tcp redis-container 同样可以通过env指令查看环境变量的配置: $ docker exec -it 6f7d16f298e9 bash root@6f7d16f298e9:/data# uptime 12:26:19 up 20 min, 0 users, load average: 0.00, 0.04, 0.10 root@6f7d16f298e9:/data# free total used free shared buffers cached Mem: 1020096 699280 320816 126800 50184 527260 -/+ buffers/cache: 121836 898260 Swap: 1181112 0 1181112 也可以通过ps指令查看当前容器运行的进程信息: root@6f7d16f298e9:/data# env HOSTNAME=6f7d16f298e9 REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.0.7.tar.gz PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PWD=/data SHLVL=1 HOME=/root REDIS_DOWNLOAD_SHA1=e56b4b7e033ae8dbf311f9191cf6fdf3ae974d1c REDIS_VERSION=3.0.7 GOSU_VERSION=1.7 _=/usr/bin/env3.1 连接Redis容器 用户可以使用--link参数,连接创建的redis-container容器: root@6f7d16f298e9:/data# ps -ef UID PID PPID C STIME TTY TIME CMD redis 1 0 0 12:16 ? 00:00:02 redis-server *:6379 root 30 0 0 12:51 ? 00:00:00 sh root 39 3 0 0 12:52 ? 00:00:00 ps -ef 进入alpine系统容器后,可以使用ping指令测试redis容器: $ docker run -it --link redis-container:db alpine sh / # ls 还可以使用nc指令(即NetCat)检测redis服务的可用性: / # ping db PING db (172.17.0.2): 56 data bytes 64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.088 ms 64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.103 ms --- db ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.088/0.095/0.103 ms 官方镜像内也自带了redis客户端,可以使用以下指令直接使用: / # nc db 6379 PING +PONG3.2 使用自定义配置 可以通过数据卷实现自定义redis配置,如下所示: $ docker run -it --link redis-container:db --entrypoint redis-cli redis -h db db:6379> ping PONG db:6379> set 1 2 OK db:6379> get 1 "2" 👑👑👑结束语👑👑👑
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道第一名🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. MongoDB介绍2. Docker中部署MongoDB2.1 使用官方镜像 2.2 连接mongodb容器 2.3 直接使用mongo cli指令 2.4 使用自定义Dockerfile 👑👑👑结束语👑👑👑1. MongoDB介绍 MongoDB是一款可扩展、高性能的开源文档数据库,是当今最流行的NoSQL数据库软件之一。它采用C++开发,支持复杂的数据类型和强大的查询语言,提供了关系数据库的绝大部分功能。由于MongoDB高性能、易部署、易使用等特点,已经在很多领域都得到了广泛的应用。 2. Docker中部署MongoDB2.1 使用官方镜像 用户可以使用docker run指令直接运行官方mongodb镜像: $ docker run --name mongo-container -d mongo ade2b5036f457a6a2e7574fd68cf7a3298936f27280833769e93392015512735 之后,可以通过docker ps指令查看正在运行的mongo-container容器的容器ID: $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ade2b5036f45 mongo "/entrypoint.sh mongo" 1 hours ago Up 22 hours 27017/tcp mongo-container 在此mongo容器启动一个bash进程,并通过mongo指令启动mongodb交互命令行,再通过db.stats()指令查看数据库状态: $ docker exec -it ade2b5036f45 sh # mongo MongoDB shell version: 3.2.6 connecting to: test > show dbs local 0.000GB > db.stats() { "db" : "test", "collections" : 1, "objects" : 1, "avgObjSize" : 39, "dataSize" : 39, "storageSize" : 16384, "numExtents" : 0, "indexes" : 1, "indexSize" : 16384, "ok" : 1 } 这里用户可以通过env指令查看环境变量的配置: root@ade2b5036f45:/bin# env HOSTNAME=ade2b5036f45 M ONGO_VERSION=3.2.6 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin GPG_KEYS=DFFA3DCF326E302C4787673A01C4E7FAAAB2461C 42F3E95A2C4F08279C4960ADD 68FA50FEA312927 PWD=/bin SHLVL=1 HOME=/root MONGO_MAJOR=3.2 GOSU_VERSION=1.7 _=/usr/bin/env OLDPWD=/ 镜像默认暴露了mongodb的服务端口:27017,可以通过该端口访问服务 2.2 连接mongodb容器 使用--link参数,连接新建的mongo-container容器: $ docker run -it --link mongo-container:db alpine sh / # ls 进入alpine系统容器后,用户可以使用ping指令测试redis容器的连通性 / # ping db PING db (172.17.0.5): 56 data bytes 64 bytes from 172.17.0.5: seq=0 ttl=64 time=0.093 ms 64 bytes from 172.17.0.5: seq=1 ttl=64 time=0.104 ms ^C--- db ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.093/0.098/0.104 ms2.3 直接使用mongo cli指令 如果想直接在宿主机器上使用mongodb镜像,可以在docker run指令后面加入entrypoint指令,这样就可以非常方便的直接进入mongo cli了。 $ docker run -it --link mongo-container:db --entrypoint mongo mongo --host db > db.version(); 3.2.6 > db.stats(); { "db" : "test", "collections" : 0, "objects" : 0, "avgObjSize" : 0, "dataSize" : 0, "storageSize" : 0, "numExtents" : 0, "indexes" : 0, "indexSize" : 0, "fileSize" : 0, "ok" : 1 } > show dbs local 0.000GB 最后,还可以使用--storageEngine参数来设置储存引擎: $ docker run --name mongo-container -d mongo --storageEngine wiredTiger2.4 使用自定义Dockerfile 首先是准备工作。新建项目目录,并在根目录新建Dockerfile,内容如下: #设置从用户之前创建的sshd镜像继承 FROM sshd MAINTAINER docker_user (user@docker.com) RUN apt-get update && \ apt-get install -y mongodb pwgen && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* #创建mongodb存放数据文件的文件夹 RUN mkdir -p /data/db VOLUME /data/db ENV AUTH yes #添加脚本 ADD run.sh /run.sh ADD set_mongodb_password.sh /set_mongodb_password.sh RUN chmod 755 ./*.sh EXPOSE 27017 EXPOSE 28017 CMD ["/run.sh"] 新建set_mongodb_password.sh脚本。此脚本主要负责配置数据库的用户名和密码: #!/bin/bash #这个脚本主要是用来设置数据库的用户名和密码。 #判断是否已经设置过密码。 if [ -f /.mongodb_password_set ]; then echo "MongoDB password already set!" exit 0 fi /usr/bin/mongod --smallfiles --nojournal & PASS=${MONGODB_PASS:-$(pwgen -s 12 1)} _word=$( [ ${MONGODB_PASS} ] && echo "preset" || echo "random" ) RET=1 while [[ RET -ne 0 ]]; do echo "=> Waiting for confirmation of MongoDB service startup" sleep 5 mongo admin --eval "help" >/dev/null 2>&1 RET=$? done #通过dockerlogs+id可以看到下面的输出。 echo "=> Creating an admin user with a ${_word} password in MongoDB" mongo admin --eval "db.addUser({user: 'admin', pwd: '$PASS', roles: [ 'userAdminAnyDatabase', 'dbAdminAnyDatabase' ]});" mongo admin --eval "db.shutdownServer();" echo "=> Done!" touch /.mongodb_password_set echo "========================================================================" echo "You can now connect to this MongoDB server using:" echo "" echo " mongo admin -u admin -p $PASS --host <host> --port <port>" echo "" echo "Please remember to change the above password as soon as possible!" echo "========================================================================" 新建run.sh,此脚本是主要的mongodb启动脚本: if [ ! -f /.mongodb_password_set ]; then /set_mongodb_password.sh fi if [ "$AUTH" == "yes" ]; then #这里大家可以自己设定Mongodb的启动参数。 export mongodb='/usr/bin/mongod --nojournal --auth --httpinterface --rest' else export mongodb='/usr/bin/mongod --nojournal --httpinterface --rest' fi if [ ! -f /data/db/mongod.lock ]; then eval $mongodb else export mongodb=$mongodb' --dbpath /data/db' rm /data/db/mongod.lock mongod --dbpath /data/db --repair && eval $mongodb fi 第二步,使用docker build指令构建镜像: $ docker build -t mongodb-image . 第三步,启动后台容器,并分别映射27017和28017端口到本地: $ docker run -d -p 27017:27017 -p 28017:28017 mongodb 通过docker logs来查看默认的admin帐户密码: $ docker logs sa9 ======================================================================= You can now connect to this MongoDB server using: mongo admin -u admin -p 5elsT6KtjrqV --host <host> --port <port> Please remember to change the above password as soon as possible! ======================================================================= 屏幕输出中的5elsT6KtjrqV就是admin用户的密码。也可以利用环境变量在容器启动时指定密码: $ docker run -d -p 27017:27017 -p 28017:28017 -e MONGODB_PASS="mypass" mongodb 甚至,设定不需要密码即可访问: $ docker run -d -p 27017:27017 -p 28017:28017 -e AUTH=no mongodb 同样,也可以使用-v参数来映射本地目录到容器。 👑👑👑结束语👑👑👑
很多免费开源数据处理引擎都可以嵌入Java应用中,其中SQLite历史悠久、用户众多;后起之秀esProc SPL功能也较强,下面对两者进行多方面的比较。基本特征语言风格SQLite使用传统的SQL代码(两者在本文等价),SQL在业界很流行,不必更多介绍。SPL是现代的数据计算语言,属于简化的面向对象的语言风格,有对象的概念,可以用点号访问属性并进行多步骤计算,但没有继承重载这些内容,不算彻底的面向对象语言。运行模式SQLite是用C语言开发的跨平台小型数据库,可嵌入其他开发语言,也可在单机执行。SPL是用Java开发的跨平台的数据计算语言,可嵌入Java,可在单机执行,可以数据计算服务的形式被远程调用。两者的代码都是解释执行的。IDESQLite官方只提供了命令行工具,图形化工具要由第三方提供,但由于SQL的特性,这些工具大都没有断点调试、变量观察等基本的IDE功能。SPL提供了图形化IDE,包括完整的调试功能、表格形式的变量观察功能。学习难度SQL历史悠久资料丰富,入门学习时难度较低,但复杂运算时难度很大。而且,SQL缺乏流程处理能力(分支结构和循环结构),要借助Java的相关功能才能实现完整的业务逻辑,所以通常还要学习Java。SPL的目标是简化Java和SQL的编码,刻意简化了许多概念,学习难度较低。SPL兼具结构化数据计算能力和流程处理能力,可以实现完整的业务逻辑,不必借助其他语言。代码量SQL进行简单计算时代码量很低,如果遇到较复杂计算,代码量会急剧增长。SQL经常要用Java配合实现完整的业务逻辑,Java的流程处理功能没有为结构化数据对象做优化,代码通常较繁琐。SPL是现代计算语言,避免了SQL的诸多弱点,无论简单还是复杂计算,代码量都很低。SPL的流程处理功能为结构化数据对象做了优化,相关代码更加简单。数据源读取自有数据格式SQLite有自有(私有)数据格式,即库文件,一个库文件包含多个表。可以用命令行命令或API(JDBC URL)创建库文件,但不能用SQL代码直接创建库文件。比如,用Java新建一个SQLite库文件,如果已存在则打开库文件Class.forName("org.sqlite.JDBC");Connection conn = DriverManager.getConnection("jdbc:sqlite: d:/ex1.db");SPL有两种自有数据格式,其中一种是组表文件,专用于大数据高性能计算,不是本文重点;另一种是集文件,专用于中小数据量和嵌入式计算。SPL不需要建库,也没有库的概念,每个集文件对应一个表。外部数据源SQLite只支持文本数据文件,包括TAB分隔的txt、逗号分隔的csv,也可自定义其它分隔符。SPL支持多种数据源,包括: JDBC(即所有的RDB) csv、TXT、JSON、XML、Excel HBase、HDFS、Hive、Spark Restful、WebService、Webcrawl Elasticsearch、MongoDB、Kafka、R2dbc、FTP Cassandra、DynamoDB、influxDB、Redis、SAP 这些数据源都可以直接使用,非常方便。对于其他未列入的数据源,SPL也提供了接口规范,只要按规范输出为SPL的结构化数据对象,就可以进行后续计算。访问自有数据格式SQLite通过SQL访问库文件中的表,生成SQL结果集,即内存中的结构化数据对象。读取表时一般伴随着查询:select * from Orders where Amount>2000 and Amount<=3000SPL通过SPL的T函数或import函数读取集文件,生成序表(相当于SQL结果集)。等价的代码:T("d:/Orders.btx").select(Amount>2000 && Amount<=3000)读取csv文件SQLite读取csv文件需要三步,第一步:进入管理命令行。#在Windows命令行,打开或新建名为dbname的数据库sqlite3.exe new.db第二步,在管理命令行新建库表,并导入数据:#当字段类型都是字符串时,不必手工建表。create table Orders( OrderID int, Client varchar(100), SellerID int, Amount float, OrderDate date ); .import --csv d:/Orders.csv Orders第三步,在Java代码中嵌入SQL查询代码:select * from Orders where Amount>2000 and Amount<=3000上面的方法需要人工参与,比较繁琐。也可以全部用Java代码实现,第一步,先在操作系统建立一个文本文件(比如importOrders.txt),内容即上面第二步。第二步,在Java代码调用操作系统命令行,即用Runtime.getRuntime().exec(…)执行命令:sqlite3.exe dbname.db < importOrders.txt第三步不变。这种方法需要额外的操作系统执行权限,安全风险比较大。如果想降低安全风险,就要用循环语句执行SQL一条条插入记录,代码冗长,修改麻烦。SPL读取csv文件,只需一步,在Java里嵌入下面的SPL代码:T("d:/Orders.csv").select(Amount>2000 && Amount<=3000)函数T不仅可以读取集文件,也可以读取csv文件,并生成序表。序表是SPL的结构化数据对象,可类比为SQL结果集。SPL导入数据时,数据类型会自动解析,不必手工指定。整个过程无需人工参与,权限要求低,代码简短,比SQLite方便多了。如果csv格式不规范,还可以使用import函数指定分隔符、字段类型、跳过行数,并处理转义符、引号、括号等,比SQLite提供的功能丰富多了。对于csv之外的数据源,SQLite都没有提供方便的导入方法,取数过程非常繁琐,而SPL支持多种数据源,取数过程简单方便,可以显著提高开发效率。读取多层结构数据Json和XML是常用的多层结构数据。SQLite架构简单,又有Json计算能力,有时会承担Json文件/RESTful的计算功能,但SQLite不能直接解析Json文件/RESTful,需要用Java代码硬写,或借助第三方类库,最后再拼成insert语句插入数据表,代码非常繁琐,这里就不展示了。SPL架构同样简单,且可以直接解析Json文件/RESTful,可以大幅简化代码,比如:json(file("d:/xml/emp_orders.json").read()).select(Amount>2000 && Amount<=3000) json(httpfile("http://127.0.0.1:6868/api/orders").read()).select(Amount>2000 && Amount<=3000)SQLite没有XML计算能力,也不能直接解析XML文件/WebService,只能借助外部Java代码解析计算,非常繁琐。 SPL可以直接读取XML文件:A1=file("d:/xml/emp_orders.xml").read()2=xml(A1,"xml/row")3=A2.select(Amount>1000 && Amount<=2000 && like@c(Client,"*business*"))也可以方便地读取WebService:A1=ws_client("http://127.0.0.1:6868/ws/RQWebService.asmx?wsdl")2=ws_call(A1,"RQWebService":"RQWebServiceSoap":"getEmp_orders")3=A2.select(Amount>1000 && Amount<=2000 && like@c(Client,"*business*"))SPL序表支持多层结构数据,比SQL库表的二维结构更容易表达Json/XML,计算代码也更简单。这部分内容不是本文重点,就此略过。跨源计算SQLite的外部数据源只支持csv文件,跨源计算就是csv文件和库表间的关联、交集、子查询等计算。SQL是封闭的计算语言,不能直接计算库外数据,需要经过一个入库的过程,把csv文件变成库表之后才能进行跨源计算。参考前面的代码可知,入库的过程比较麻烦,不能只用SQL,还要借助Java或命令行。SPL开放性较好,可以直接计算多种数据源,数据源之间可以方便地进行跨源计算。比如csv和RESTful左关联:=join@1(json(httpfile("http://127.0.0.1:6868/api/orders").read()):o,SellerId; T("d:/Emp.csv"):e,EId)写成多步骤的形式更易读:A1=Orders=json(httpfile("http://127.0.0.1:6868/api/orders").read())2=Employees=T("d:/Emp.csv")3=join@1(Orders:o,SellerId;Employees:e,EId)只用SPL语言就可以实现跨源计算,不必借助Java或命令行,代码简短易懂,比SQL的开发效率高得多。数据计算基础计算SQLite支持常见的基础计算:#选出部分字段select Client,Amount from Orders#模糊查询select * from Orders where Amount>1000 and Client like '%s%'#排序select * from Orders order by Client, Amount desc#去重select distinct Client from Orders#分组汇总select strftime('%Y',OrderDate) as y, Client, sum(Amount) as amt from Orders group by strftime('%Y',OrderDate), Client having amt>3000#并集select * from Orders9 where Amount>3000 union select * from Orders9 where strftime('%Y',OrderDate)='2009';#子查询select * from (select strftime('%Y',OrderDate) as y, Client, sum(Amount) as amt from Orders group by strftime('%Y',OrderDate), Client) where Client like '%s%';SPL实现常见的基础计算:AB1=Orders.new(Client,Amount)//选出部分字段2=Orders.select(Amount>1000 && like(Client,\"*s*\"))//模糊查询3= Orders.sort(Client,-Amount)//排序4= Orders.id(Client)//去重5=Orders.groups(year(OrderDate):y,Client;sum(Amount):amt).select(amt>3000)//分组汇总6=[Orders.select(Amount>3000),A1.select(year(OrderDate)==2009)].union()//并集7=Orders.groups(year(OrderDate):y,Client;sum(Amount):amt).select(like(Client,\"*s*\"))//子查询SQLite和SPL都有丰富的函数进行基础计算,学习掌握都不难。综合计算先看简单些的,计算TopN,SQLite:select * from Orders order by Amount limit 3SQLite代码简短,但因为不支持top函数,也不支持序号伪列(比如Oracle的rownum),只能改用limit实现,理解起来不够直观。SPL实现TopN:Orders.top(3;Amount)SPL支持真正的行号,也支持top函数,代码更短,且易于理解。组内TopN,SQLite:select * from (select *, row_number() over (partition by Client order by Amount) as row_number from Orders) where row_number<=3SQL代码略显复杂,主要因为SQLite不支持top函数,而limit函数只能限制总记录数,不能限制各组记录数,这种情况下必须用组内行号。SQLite没有真正的行号字段,要用窗口函数生成伪列再用,代码自然复杂。事实上,缺乏行号并非SQLite自身的问题,而是整个SQL体系的问题,其他数据库也没有行号。SPL实现组内TopN:Orders.group(Client).(~.top(3;Amount)).conj()SPL代码简单多了,而且很好理解,先按Client分组,再对各组(即符号~)计算TopN,最后合并各组的计算结果。SPL之所以代码简单,表面上是因为直接支持Top函数,本质是因为SPL有真正的行号字段,或者说,SPL支持有序集合。SPL代码简单,还因为集合化更加彻底,可以实现真正的分组,即只分组不汇总,这就可以直观地计算组内数据。SQL集合化不彻底,分组时必须汇总,不能直观地计算组内数据,只能借助窗口函数。再看复杂些的计算,某支股票的最大连续上涨天数,SQLite:select max(continuousdays) from ( select count(*) continuousdays from ( select sum(risingflag) over (order by day) norisingdays from ( select day, case when price> lag(price) over (order by day) then 0 else 1 end risingflag from tbl ) ) group by norisingdays )上面代码冗长复杂。SQL很难直接表达连续上涨的概念,只能换个方法变相实现,即通过累计不涨天数来计算连续上涨天数,这种方法技巧性强,编写难度大且不易理解。而且,SQL难以调试,导致维护困难。SPL求最大连续上涨天数:A1=tbl.sort(day)2=t=0,A1.max(t=if(price>price[-1],t+1,0))上面代码简单多了。SPL容易表达连续上涨的概念,先按日期排序;再遍历记录,发现上涨则计数器加1。这里既用到了循环函数max,也用到了有序集合,代码中[-1]表示上一条,是相对位置的表示方法,price[-1]表示上一个交易日的股价,比整体移行(lag函数)更直观。找出销售额占到一半的前n个客户,并按销售额从大到小排序。SQLite:with A as (select client,amount,row_number() over (order by amount) ranknumber from sales) select client,amount from (select client,amount,sum(amount) over (order by ranknumber) acc from A) where acc>(select sum(amount)/2 from sales) order by amount des上面代码复杂。SQL很难处理恰好要过线的客户,只能换个方法变相实现,即计算销售额从小到大的累计值,反过来找出累计值不在后一半的客户。这种方法技巧性强,代码冗长,而且难以调试。 SPL求销售额占到一半的前n个客户:AB2=sales.sort(amount:-1)/销售额逆序排序,可在SQL中完成3=A2.cumulate(amount)/计算累计序列4=A3.m(-1)/2/最后的累计即总额5=A3.pselect(~>=A4)/超过一半的位置6=A2(to(A5))/按位置取值上面代码相对简单。SPL集合化成更彻底,可以用变量方便地表达集合,并在下一步用变量引用集合继续计算,因此特别适合多步骤计算。将大问题分解为多个小步骤,可以方便地实现复杂的计算目标,代码不仅简短,而且易于理解。此外,多步骤计算天然支持调试,无形中提高了开发效率。从上面例子可以看出,SQL只适合较简单的计算,而SPL支持有序集合,集合化更彻底,从简单到复杂的计算都可以很好的完成。此外,SPL还支持游离记录,可以用点号直观地引用关联表,从而简化复杂的关联计算。日期和字符串函数。SQLite支持日期和字符串函数,比如日期增减、截取字符串等,但还不够丰富,很多常用函数并不直接支持,比如季度增减、工作日计算等。SPL提供了更丰富的日期和字符串函数,在数量和功能上远远超过了SOLite。比如:季度增减:elapse@q("2020-02-27",-3) //返回2019-05-27N个工作日之后的日期:workday(date("2022-01-01"),25) //返回2022-02-04判断是否全为数字:isdigit("12345") //返回true取子串前面的字符串:substr@l("abCDcdef","cd") //返回abCD按竖线拆成字符串数组:"aa|bb|cc".split("|") //返回["aa","bb","cc"]SPL还支持年份增减、求季度、按正则表达式拆分字符串、拆出SQL的where或select部分、拆出单词、按标记拆HTML等大量函数。SPL函数选项和层次参数值得一提的是,为了进一步提高开发效率,SPL还提供了独特的函数语法。有大量功能类似的函数时,SQL要用不同的名字或者参数进行区分,使用不太方便。而SPL提供了非常独特的函数选项,使功能相似的函数可以共用一个函数名,只用函数选项区分差别。比如,select函数的基本功能是过滤,如果只过滤出符合条件的第1条记录,可使用选项@1:T.select@1(Amount>1000)对有序数据用二分法进行快速过滤,使用@b:T.select@b(Amount>1000)函数选项还可以组合搭配,比如:Orders.select@1b(Amount>1000)结构化运算函数的参数有些很复杂,SQL需要用各种关键字把一条语句的参数分隔成多个组,但这会动用很多关键字,也使语句结构不统一。SPL使用层次参数简化了复杂参数的表达,即通过分号、逗号、冒号自高而低将参数分为三层:join(Orders:o,SellerId ; Employees:e,EId)数据持久化SQLite通过直接处理库表来实现数据持久化,分为增、改、删三种:insert into Orders values(201,'DSL',10,2000.0,'2019-01-01') update Orders set Client='IBM' where orderID=201 delete from Orders where orderID=201批量新增是常见的需求,SQLite代码如下:insert into Orders(OrderID,Client,SellerID,Amount,OrderDate) select 201,'DSL',10,2000.0,'2019-01-01' union all select 202,'IBM',10,3000.0,'2019-01-01'SPL集文件的持久化有两种方式。第一种方式:直接处理集文件,可实现记录新增(及批量新增)。比如:A1=create(OrderID,Client,SellerID,Amount,OrderDate)2=A1.record([201,"HDR",9,2100.0,date("2021-01-01"),202,"IBM",9,1900,date("2021-01-02")])3=file("d:/Orders.btx").export@ab(A2)第二种方式:先处理内存里的序表,再将序表覆盖写入原来的集文件。这种方式可实现增、改、删。只要将上面A3格里的export@ab改为export@b即可,@a表示追加,@b表示集文件格式。这种方式性能不如SQLite,但嵌入计算的数据量普遍不大,覆写的速度通常可接受。SPL组表支持高性能增删改,适用于大数据量高性能计算,不是本文重点。SQLite只支持库表的持久化,不支持其他数据源,包括csv文件,硬要实现的话只能借助Java硬编码或第三方类库,代码非常繁琐。SPL除了支持集文件的持久化,也支持其他数据源,同样是通过序表为媒介。file("d:/Orders.csv").export@t(A2) //csv文件 file("d:/Orders.xlsx").xlsexport@t(A2) //xls文件 file("d:/Orders.json").write(json(A2)) //json文件特别地,SPL支持任意数据库的持久化。比如:AB1=connect("orcl")/连接外部oracle2=T=A1.query("select * from salesR where SellerID=?",10)/批量查询,序表T3=NT=T.derive()/复制出新序表NT4=NT.field("SELLERID",9)/批量修改新序表5=A1.update(NT:T,sales;ORDERID)/持久化数据库的持久化依然以序表为媒介,可以明显看出这种方式的优点:函数update自动比对修改(增改删)前后的序表,可方便地实现批量数据地持久化。流程处理SQLite缺乏流程处理能力,无法实现完整的业务逻辑,只能将SQL数据对象转为Java的resultSet/List<EntityBean>,再用for/if语句处理流程,最后再转回SQL的数据对象,代码非常繁琐。复杂的业务逻辑要在SQL对象和Java对象之间转换多次,更加麻烦。SPL提供了流程控制语句,配合内置的结构化数据对象,可以方便地实现各类业务逻辑。分支结构:AB2…3if T.AMOUNT>10000=T.BONUS=T.AMOUNT*0.054else if T.AMOUNT>=5000 && T.AMOUNT<10000=T.BONUS=T.AMOUNT*0.035else if T.AMOUNT>=2000 && T.AMOUNT<5000=T.BONUS=T.AMOUNT*0.02循环结构:AB1=db=connect("db")2=T=db.query@x("select * from sales where SellerID=? order by OrderDate",9)3for T=A3.BONUS=A3.BONUS+A3.AMOUNT*0.014=A3.CLIENT=CONCAT(LEFT(A3.CLIENT,4), " co.,ltd.")5…上述代码之外,SPL还有更多针对结构化数据的流程处理功能,可进一步提高开发效率,比如:每轮循环取一批而不是一条记录;某字段值变化时循环一轮。应用结构Java集成SQLite提供了JDBC接口,可以被Java代码方便地集成:Class.forName("org.sqlite.JDBC"); Connection conn = DriverManager.getConnection("jdbc:sqlite: d:/ex1.db"); Statement statement = conn.createStatement(); ResultSet results = statement.executeQuery("select * from Orders where Amount>1000 and Client like '%s%'");SQLite的内核是C语言编写的,虽然可以被集成到Java应用中,但不能无缝集成,和Java主程序交换数据时要耗费额外的时间,在数据量较大或交互较频繁时性能就会明显下降。同样因为内核是C程序,SQLite会在一定程度上破坏Java架构的一致性和健壮性。SPL同样提供了JDBC接口,集成方法和SQLite类似:Class.forName("com.esproc.jdbc.InternalDriver"); Connection conn =DriverManager.getConnection("jdbc:esproc:local://"); Statement statement = conn.createStatement(); ResultSet result = statement.executeQuery("=T(\"D:/Orders.csv\").select(Amount>1000 && like(Client,\"*s*\"))");SPL是纯Java编写的,可被Java应用无缝集成,架构一致性强,系统更稳定,不需要耗费额外时间交换数据,性能更有保障。业务逻辑外置一般的RDB支持存储过程,可将业务逻辑外置于Java程序,但SQLite不支持存储过程,完整的业务逻辑通常要借助Java的流程处理功能才能实现,也就不能外置于Java程序。业务逻辑不能外置于Java代码,导致两者耦合性过高。SPL可实现完整的业务逻辑,业务逻辑(或复杂的、经常变化的计算代码)可保存为脚本文件,并外置于Java程序。Java程序以存储过程的形式引用脚本文件名:Class.forName("com.esproc.jdbc.InternalDriver"); Connection conn =DriverManager.getConnection("jdbc:esproc:local://"); CallableStatement statement = conn.prepareCall("{call queryOrders()}"); statement.execute();外置的SPL脚本不仅可以有效降低系统耦合性,还具有热切换的特点。SPL是解释型代码,修改后不必编译就可直接运行,也不必重启Java应用,可有效降低维护成本。内存计算SQLite可用于内存计算,一般在应用启动时将数据加载至内存(URL有变化):Connection conn= DriverManager.getConnection("jdbc:sqlite:file::memory:?cache=shared"); Statement st =conn.createStatement(); st.execute("restore from d:/ex1");需要进行业务计算时,就可以直接利用之前加载好的内存数据:Class.forName("org.sqlite.JDBC"); Connection conn= DriverManager.getConnection("jdbc:sqlite:file::memory:?cache=shared"); Statement statement = conn.createStatement(); ResultSet results = statement.executeQuery("select OrderID,Client,Amount,Name,Gender Dept from Orders left join Employees on Orders.SellerId=Empoyees.EId");SPL同样可用于内存计算,在应用启动时执行脚本,将数据加载至内存(URL不变):A1= connect("orcl").query@x("select OrderID,Client,SellerID,OrderDate,Amount from orders order by OrderID")2>env(Orders,A1)3>env(Employees,T("d:/Employees.csv"))需要进行业务计算时,可直接利用之前加载好的内存数据:Class.forName("com.esproc.jdbc.InternalDriver"); Connection conn =DriverManager.getConnection("jdbc:esproc:local://"); Statement statement = conn.createStatement(); ResultSet result = statement.executeQuery("=join@1(Orders:O,SellerId; Employees:E,EId).new(O.OrderID, O.Client,O.Amount,E.Name,E.Gender,E.Dept)");关联计算如果频繁发生,性能必然下降,改用宽表的话又太占内存,SQL没有好办法解决这个问题。SPL独有预关联技术,可大幅提升关联计算的性能,且不占用额外内存。SPL还有更多内存计算技术,通常比SQLite性能好得多,包括并行计算、指针式复用、内存压缩等。SQLite可以方便地嵌入Java,但数据源加载繁琐,计算能力不足,无法独立完成业务逻辑,架构上弱点颇多。SPL也很容易嵌入Java,且直接支持更多数据源,计算能力更强,流程处理方便,可独立实现业务逻辑。SPL还提供了多种优化体系结构的手段,代码既可外置也可内置于Java,支持解释执行和热切换,可进行高性能内存计算。SPL资料 SPL下载 SPL源代码
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1.markdowd添加正文底色框1.1 语法格式:1.2 演示1.3 markdown各种底色和字体颜色大全2. 富文本编辑器添加正文底色框2.1 第一步2.2 第二步2.3 第三步2.4 第四步2.5 展示效果👑👑👑结束语👑👑👑1.markdowd添加正文底色框1.1 语法格式: <table><tr><td bgcolor=Gainsboro>此处是你写的正文内容</td></tr></table><table><tr><td bgcolor= #固定语法Gainsboro> #底色名称,每次修改这儿就可以了。</td></tr></table> #固定名称 1.2 演示1.3 markdown各种底色和字体颜色大全2. 富文本编辑器添加正文底色框2.1 第一步2.2 第二步2.3 第三步2.4 第四步2.5 展示效果 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. heapq1.1 基本知识1.2 堆的存储2. heapq 模块2.1 heappush(heap, x)2.2 heappop(heap):删除最小元素2.3 heapify():将列表转换为堆2.4 heapreplace()3. deque 模块👑👑👑结束语👑👑👑1. heapq 堆(heap),是一种数据结构。用维基百科中的说明: 堆(英语:heap),是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。 对于这个新的概念,大家不要感觉心慌意乱或者恐惧,因为它本质上不是新东西,而是在我们已经熟知的知识基础上的扩展。 堆的实现是通过构造二叉堆,也就是一种二叉树。 1.1 基本知识 这是一颗在北京很常见的香樟树,马路两边、公园里随处可见。 但是,在编程中,我们常说的树通常不是上图那样的,而是这样的: 跟真实现实生活中看到的树反过来,也就是“根”在上面。为什么这样呢?我想主要是画着更方便吧。但是,我 觉这棵树,是完全写实的作品。我本人做为一个大懒虫,不喜欢这样的树,我画出来的是这样的: 这棵树有两根枝杈,可不要小看这两根枝杈哦,《道德经》上不是说“一生二,二生三,三生万物”。一就是下 面那个干,二就是两个枝杈,每个枝杈还可以看做下一个一,然后再有两个枝杈,如此不断重复(这简直就是递归呀),就成为了一棵大树。 我的确很佩服我偷懒后的作品。但是,我更喜欢把这棵树画成这样: 并且给它一个正规的名字:二叉树 这个也是二叉树,完全脱胎于我所画的懒人作品。但是略有不同,这幅图在各个枝杈上显示的是数 字。这种类型的“树”就编程语言中所说的二叉树,维基百科曰: 在计算机科学中,二叉樹(英语:Binary tree)是每個節點最多有兩個子樹的樹結構。通常子樹被稱作「左子樹」(left subtree)和「右子樹」(right subtree)。二叉樹常被用於實現二叉查找樹和二叉堆。 在上图的二叉树中,最顶端的那个数字就相当于树根,也就称作“根”。每个数字所在位置成为一个节点,每个 节点向下分散出两个“子节点”。就上图的二叉树,在最后一层,并不是所有节点都有两个子节点,这类二叉树 又称为完全二叉树(Complete Binary Tree),也有的二叉树,所有的节点都有两个子节点,这类二叉树称作 满二叉树(Full Binarry Tree),如下图: 下面讨论的对象是实现二叉堆就是通过二叉树实现的。其应该具有如下特点: • 节点的值大于等于(或者小于等于)任何子节点的值。 • 节点左子树和右子树是一个二叉堆。如果父节点的值总大于等于任何一个子节点的值,其为最大堆;若父节点值总小于等于子节点值,为最小堆。上面图示中的完全二叉树,就表示一个最小堆。 堆的类型还有别的,如斐波那契堆等,但很少用。所以,通常就将二叉堆也说成堆。下面所说的堆,就是二叉 堆。而二叉堆又是用二叉树实现的。 1.2 堆的存储 堆用列表(有的语言中成为数组)来表示。如下图所示: 从图示中可以看出,将逻辑结构中的树的节点数字依次填入到存储结构中。看这个图,似乎是列表中按照顺序进行排列似的。但是,这仅仅由于那个树的特点造成的,如果是下面的树: 如果将上面的逻辑结构转换为存储结构,读者就能看出来了,不再是按照顺序排列的了。 关于堆的各种,如插入、删除、排序等,本节不会专门讲授编码方法,读者可以参与有关资料。但是,下面要介绍如何用 Python 中的模块 heapq 来实现这些操作。 2. heapq 模块 heapq 中的 heap 是堆,q 就是 queue(队列)的缩写。此模块包括: >>> import heapq >>> heapq.__all__ ['heappush', 'heappop', 'heapify', 'heapreplace', 'merge', 'nlargest', 'nsmallest', 'heappushpop'] 依次查看这些函数的使用方法。 2.1 heappush(heap, x) 将 x 压入对 heap(这是一个列表) Help on built-in function heappush in module _heapq: heappush(...) heappush(heap, item) -> None. Push item onto heap, maintaining the heap invariant. >>> import heapq >>> heap = [] >>> heapq.heappush(heap, 3) >>> heapq.heappush(heap, 9) >>> heapq.heappush(heap, 2) >>> heapq.heappush(heap, 4) >>> heapq.heappush(heap, 0) >>> heapq.heappush(heap, 8) >>> heap [0, 2, 3, 9, 4, 8] 请大家注意我上面的操作,在向堆增加数值的时候,我并没有严格按照什么顺序,是随意的。但是,当我查看堆的数据时,显示给我的是一个有一定顺序的数据结构。这种顺序不是按照从小到大,而是按照前面所说的完全二叉树的方式排列。显示的是存储结构,可以把它还原为逻辑结构,看看是不是一颗二叉树。 由此可知,利用 heappush() 函数将数据放到堆里面之后,会自动按照二叉树的结构进行存储。 2.2 heappop(heap):删除最小元素 承接上面的操作: >>> heapq.heappop(heap) 0 >>> heap [2, 4, 3, 9, 8] 用 heappop() 函数,从 heap 堆中删除了一个最小元素,并且返回该值。但是,这时候的 heap 显示顺序,并 非简单地将 0 去除,而是按照完全二叉树的规范重新进行排列。 2.3 heapify():将列表转换为堆 如果已经建立了一个列表,利用 heapify() 可以将列表直接转化为堆。 >>> hl = [2, 4, 6, 8, 9, 0, 1, 5, 3] >>> heapq.heapify(hl) >>> hl [0, 3, 1, 4, 9, 6, 2, 5, 8] 经过这样的操作,列表 hl 就变成了堆(注意观察堆的顺序,和列表不同),可以对 hl(堆)使用 heappop()或者 heappush() 等函数了。否则,不可。 >>> heapq.heappop(hl) 0 >>> heapq.heappop(hl) 1 >>> hl [2, 3, 5, 4, 9, 6, 8] >>> heapq.heappush(hl, 9) >>> hl [2, 3, 5, 4, 9, 6, 8, 9] 不要认为堆里面只能放数字,之所以用数字,是因为对它的逻辑结构比较好理解。 >>> heapq.heappush(hl, "q") >>> hl [2, 3, 5, 4, 9, 6, 8, 9, 'q'] >>> heapq.heappush(hl, "w") >>> hl [2, 3, 5, 4, 9, 6, 8, 9, 'q', 'w']2.4 heapreplace() 是 heappop() 和 heappush() 的联合,也就是删除一个,同时加入一个。例如: >>> heap [2, 4, 3, 9, 8] >>> heapq.heapreplace(heap, 3.14) 2 >>> heap [3, 4, 3.14, 9, 8] 先简单罗列关于对的几个常用函数。那么堆在编程实践中的用途在哪方面呢?主要在排序上。一提到排序,读者肯定想到的是 sorted() 或者列表中的 sort(),不错,这两个都是常用的函数,而且在一般情况下已经足够使用了。如果再使用堆排序,相对上述方法应该有优势。 堆排序的优势不仅更快,更重要的是有效地使用内存,当然,另外一个也不同忽视,就是简单易用。比如前面操作的,删除数列中最小的值,就是在排序基础上进行的操作。 3. deque 模块 有这样一个问题:一个列表,比如是 [1,2,3] ,我打算在最右边增加一个数字。 这也太简单了,不就是用 append() 这个内建函数,追加一个吗? 这是简单,我要得寸进尺,能不能在最左边增加一个数字呢? 这个嘛,应该有办法。不过得想想了。兄弟们在向下阅读的时候,能不能想出一个方法来? >>> lst = [1, 2, 3] >>> lst.append(4) >>> lst [1, 2, 3, 4] >>> nl = [7] >>> nl.extend(lst) >>> nl [7, 1, 2, 3, 4] 你或许还有别的方法。但是,Python 为我们提供了一个更简单的模块,来解决这个问题。 >>> from collections import deque 这次用这种引用方法,因为 collections 模块中东西很多,我们只用到 deque。 >>> lst [1, 2, 3, 4] 还是这个列表。试试分别从右边和左边增加数 >>> qlst = deque(lst) 这是必须的,将列表转化为 deque。deque 在汉语中有一个名字,叫做“双端队列”(double-ended queue)。 >>> qlst.append(5) #从右边增加 >>> qlst deque([1, 2, 3, 4, 5]) >>> qlst.appendleft(7) #从左边增加 >>> qlst deque([7, 1, 2, 3, 4, 5]) 这样操作多么容易呀。继续看删除: >>> qlst.pop() 5 >>> qlst deque([7, 1, 2, 3, 4]) >>> qlst.popleft() 7 >>> qlst deque([1, 2, 3, 4]) 删除也分左右。下面这个,请大家仔细观察,更有点意思 >>> qlst.rotate(3) >>> qlst deque([2, 3, 4, 1]) rotate() 的功能是将[1, 2, 3, 4]的首位连起来,你就想象一个圆环,在上面有 1,2,3,4 几个数字。如果一开始正对着你的是 1,依顺时针方向排列,就是从 1 开始的数列,如下图所示: 经过 rotate() ,这个环就发生旋转了,如果是 rotate(3) ,表示每个数字按照顺时针方向前进三个位置,于是变成了: 请原谅我的超级抽象派作图方式。从图中可以看出,数列变成了[2, 3, 4, 1]。rotate() 作用就好像在拨转这个圆环。 >>> qlst deque([3, 4, 1, 2]) >>> qlst.rotate(-1) >>> qlst deque([4, 1, 2, 3]) 如果参数是复数,那么就逆时针转。 在 deque 中,还有 extend 和 extendleft 方法。大家可自己调试。 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. sys1.1 sys.argv1.2 sys.arg 在 Python 中的作用1.3 sys.exit()1.4 sys.path1.5 sys.stdin, sys.stdout, sys.stderr2. copy👑👑👑结束语👑👑👑 Python 标准库内容非常多,有人专门为此写过一本书。在我写的这几篇文章中,我不会将标准库进行完整的详细介绍,但是,我根据自己的理解和喜好,选几个呈现出来,一来显示标准库之强大功能,二来演示如何理解和使用标准库。 1. sys 这是一个跟 Python 解释器关系密切的标准库,上一节中我们使用过 sys.path.append() 。上篇文章链接在此:【python】标准库(第一讲) >>> import sys >>> print sys.__doc__ 显示了 sys 的基本文档,看第一句话,概括了本模块的基本特点。 This module provides access to some objects used or maintained by the interpreter and to functions that interact strongly with the interpreter. 在诸多 sys 函数和变量中,选择常用的(应该说是我觉得常用的)来说明。 1.1 sys.argv sys.argv 是变量,专门用来向 Python 解释器传递参数,所以名曰“命令行参数”。 先解释什么是命令行参数。 $ Python --version Python 2.7.6 这里的 --version 就是命令行参数。如果你使用 Python --help 可以看到更多: $ Python --help usage: Python [option] ... [-c cmd | -m mod | file | -] [arg] ... Options and arguments (and corresponding environment variables): -B : don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x -c cmd : program passed in as string (terminates option list) -d : debug output from parser; also PYTHONDEBUG=x -E : ignore PYTHON* environment variables (such as PYTHONPATH) -h : print this help message and exit (also --help) -i : inspect interactively after running script; forces a prompt even if stdin does not appear to be a terminal; also PYTHONINSPECT=x -m mod : run library module as a script (terminates option list) -O : optimize generated bytecode slightly; also PYTHONOPTIMIZE=x -OO : remove doc-strings in addition to the -O optimizations -R : use a pseudo-random salt to make hash() values of various types be unpredictable between separate invocations of the interpreter, as a defense against denial-of-service attacks 只选择了部分内容摆在这里。所看到的如 -B, -h 之流,都是参数,比如 Python -h ,其功能同上。那么 -h也是命令行参数。 1.2 sys.arg 在 Python 中的作用 通过它可以向解释器传递命令行参数。比如: #!/usr/bin/env Python # coding=utf-8 import sys print "The file name: ", sys.argv[0] print "The number of argument", len(sys.argv) print "The argument is: ", str(sys.argv) 将上述代码保存,文件名是 22101.py(这名称取的,多么数字化)。然后如此做: $ python 22101.py The file name: 22101.py The number of argument 1 The argument is: ['22101.py'] 将结果和前面的代码做个对照。 • 在 $ Python 22101.py 中,“22101.py”是要运行的文件名,同时也是命令行参数,是前面的 Python 这个指令的参数。其地位与 Python -h 中的参数 -h 是等同的。 • sys.argv[0] 是第一个参数,就是上面提到的 22101.py ,即文件名。 如果我们这样来试试,看看结果: $ python 22101.py beginner master www.itdiffer.com The file name: 22101.py The number of argument 4 The argument is: ['22101.py', 'beginner', 'master', 'www.itdiffer.com'] 如果在这里,用 sys.arg[1] 得到的就是 beginner ,依次类推。 1.3 sys.exit() 这是一个方法,意思是退出当前的程序。 Help on built-in function exit in module sys: exit(...) exit([status]) Exit the interpreter by raising SystemExit(status). If the status is omitted or None, it defaults to zero (i.e., success). If the status is an integer, it will be used as the system exit status. If it is another kind of object, it will be printed and the system exit status will be one (i.e., failure) 从文档信息中可知,如果用 sys.exit() 退出程序,会返回 SystemExit 异常。这里先告知大家,还有另外一退出方式,是 os._exit() ,这两个有所区别。后者会在后面介绍。 #!/usr/bin/env Python # coding=utf-8 import sys for i in range(10): if i == 5: sys.exit() else: print i 这段程序的运行结果就是: $ python 22102.py 0 1 2 3 4 需要提醒大家注意的是,在函数中,用到 return,这个的含义是终止当前的函数,并返回相应值(如果有,如果没有就是 None)。但是 sys.exit() 的含义是退出当前程序,并发起 SystemExit 异常。这就是两者的区别了。 如果使用 sys.exit(0) 表示正常退出。如果你们要测试,需要在某个地方退出的时候有一个有意义的提示,可以用 sys.exit("I wet out at here.") ,那么字符串信息就被打印出来。 1.4 sys.path sys.path 已经不陌生了,前面用过。它可以查找模块所在的目录,以列表的形式显示出来。如果用 append() 方法,就能够向这个列表增加新的模块目录。如前所演示。不在赘述。不理解的兄弟们可以往前复习。我前面的文章中已经写过啦。 1.5 sys.stdin, sys.stdout, sys.stderr 这三个放到一起,因为他们的变量都是类文件流对象,分别表示标准 UNIX 概念中的标准输入、标准输出和标准错误。与 Python 功能对照,sys.stdin 获得输入(用 raw_input() 输入的通过它获得,Python3.x 中是 impu t()),sys.stdout 负责输出了。 流是程序输入或输出的一个连续的字节序列,设备(例如鼠标、键盘、磁盘、屏幕、调制解调器和打印机)的输入和输出都是用流来处理的。程序在任何时候都可以使用它们。一般来讲,stdin(输入)并不一定来自键盘,stdout(输出)也并不一定显示在屏幕上,它们都可以重定向到磁盘文件或其它设备上。 还记得 print() 吧,在这个学习过程中,用的很多。它的本质就是 sys.stdout.write(object + '\n') 。 >>> for i in range(3): ... print i ... 0 1 2 >>> import sys >>> for i in range(3): ... sys.stdout.write(str(i)) ... 012>>> 造成上面输出结果在表象上如此差异,原因就是那个 '\n' 的有无。 >>> for i in range(3): ... sys.stdout.write(str(i) + '\n') ... 0 1 2 从这看出,两者是完全等效的。如果仅仅止于此,意义不大。关键是通过 sys.stdout 能够做到将输出内容从“控 制台”转到“文件”,称之为重定向。这样也许控制台看不到(很多时候这个不重要),但是文件中已经有了输出内容。比如: >>> f = open("stdout.md", "w") >>> sys.stdout = f >>> print "Learn Python: From Beginner to Master" >>> f.close() 当 sys.stdout = f 之后,就意味着将输出目的地转到了打开(建立)的文件中,如果使用 print(),即将内容输出到这个文件中,在控制台并无显现。 打开文件看看便知: $ cat stdout.md Learn Python: From Beginner to Master 这是标准输出。另外两个,输入和错误,也类似。大家可以自行测试。 关于对文件的操作,虽然前面这这里都涉及到一些。但是,远远不足,后面我会专门讲授对某些特殊但常用的文件读写操作。兄弟们 别急哈,订阅我的专栏是最保险的嘿嘿嘿。 2. copy>>> import copy >>> copy.__all__ ['Error', 'copy', 'deepcopy'] 这个模块中常用的就是 copy 和 deepcopy。 为了具体说明,看这样一个例子: #!/usr/bin/env Python # coding=utf-8 import copy class MyCopy(object): def __init__(self, value): self.value = value def __repr__(self): return str(self.value) foo = MyCopy(7) a = ["foo", foo] b = a[:] c = list(a) d = copy.copy(a) e = copy.deepcopy(a) a.append("abc") foo.value = 17 print "original: %r\n slice: %r\n list(): %r\n copy(): %r\n deepcopy(): %r\n" % (a,b,c,d,e) 保存并运行: $ python 22103.py original: ['foo', 17, 'abc'] slice: ['foo', 17] list(): ['foo', 17] copy(): ['foo', 17] deepcopy(): ['foo', 7] 大家可以对照结果和程序,就能理解各种拷贝的实现方法和含义了。 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. 错误和异常1.1 assert2. 装饰器(扩展)2.1 Class2.2 参数2.3 嵌套2.4 functools.wraps2.5 装饰器都能干嘛?👑👑👑结束语👑👑👑1. 错误和异常 按照一般的学习思路,掌握了前两节内容,已经足够编程所需了。但是,我还想再多一步,还是因为本教程的读者是要 from beginner to master。 1.1 assert>>> assert 1==1 >>> assert 1==0 Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError 从上面的举例中可以基本了解了 assert 的特点。assert,翻译过来是“断言”之意。assert 是一句等价于布尔真的判定,发生异常就意味着表达式为假。assert 的应用情景就有点像汉语的意思一样,当程序运行到某个节点的时候,就断定某个变量的值必然是什么,或者对象必然拥有某个属性等,简单说就是断定什么东西必然是什么,如果不是,就抛出错误。 #!/usr/bin/env Python # coding=utf-8 class Account(object): def __init__(self, number): self.number = number self.balance = 0 def deposit(self, amount): assert amount > 0 self.balance += balance def withdraw(self, amount): assert amount > 0 if amount <= self.balance: self.balance -= amount else: print "balance is not enough." 上面的程序中,deposit() 和 withdraw() 方法的参数 amount 值必须是大于零的,这里就用断言,如果不满足条件就会报错。比如这样来运行: if __name__ == "__main__": a = Account(1000) a.deposit(-10) 出现的结果是: $ python 21801.py Traceback (most recent call last): File "21801.py", line 22, in <module> a.deposit(-10) File "21801.py", line 10, in deposit assert amount > 0 AssertionError 这就是断言 assert 的引用。什么是使用断言的最佳时机?有文章做了总结: 如果没有特别的目的,断言应该用于如下情况: • 防御性的编程 • 运行时对程序逻辑的检测 • 合约性检查(比如前置条件,后置条件) • 程序中的常量 • 检查文档 不论是否理解,可以先看看,请牢记,在具体开发过程中,有时间就回来看看本教程,不断加深对这些概念的理解,这也是 master 的成就之法。最后,引用危机百科中对“异常处理”词条的说明,作为对“错误和异常”部分的总结(有所删改): 异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。各种编程语言在处理异常方面具有非常显著的不同点(错误检测与异常处理区别在于:错误检测是在正常的程序流中,处理不可预见问题的代码,例如一个调用操作未能成功结束)。某些编程语言有这样的函数:当输入存在非法数据时不能被安全地调用,或者返回值不能与异常进行有效的区别。例如,C 语言中的 atoi 函数(ASCII串到整数的转换)在输入非法时可以返回 0。在这种情况下编程者需要另外进行错误检测(可能通过某些辅助全局变量如 C 的 errno),或进行输入检验(如通过正则表达式),或者共同使用这两种方法。通过异常处理,我们可以对用户在程序中的非法输入进行控制和提示,以防程序崩溃。从进程的视角,硬件中断相当于可恢复异常,虽然中断一般与程序流本身无关。从子程序编程者的视角,异常是很有用的一种机制,用于通知外界该子程序不能正常执行。如输入的数据无效(例如除数是 0),或所需资源不可用(例如文件丢失)。如果系统没有异常机制,则编程者需要用返回值来标示发生了哪些错误。一段代码是异常安全的,如果这段代码运行时的失败不会产生有害后果,如内存泄露、存储数据混淆、或无效的输出。Python 语言对异常处理机制是非常普遍深入的,所以想写出不含 try, except 的程序非常困难。2. 装饰器(扩展) 装饰器 (Decorator) 在 Python 编程中极为常⻅,可轻松实现 Metadata、Proxy、 AOP 等模式。简单点说,装饰器通过返回包装对象实现间接调⽤,以此来插⼊额外逻辑。语法看上去和 Java Annotation、C# Attribute 类似,但不仅仅是添加元数据。 >>> @check_args ... def test(*args): ... print args 还原成容易理解的⽅式: >>> test = check_args(test) 类似的做法,我们在使⽤ staticmethod、classmethod 时就已⻅过。 >>> def check_args(func): ... def wrap(*args): ... args = filter(bool, args) ... func(*args) ... ... return wrap!! ! ! # 返回 wrap 函数对象 >>> @check_args! ! ! ! # 解释器执⾏ test = check_args(test) ... def test(*args): ... print args >>> test! ! ! ! ! # 现在 test 名字与 wrap 关联。 <function wrap at 0x108affde8> >>> test(1, 0, 2, "", [], 3)! ! # 通过 wrap(test(args)) 完成调⽤。 (1, 2, 3) 整个过程⾮常简单: • 将目标函数对象 test 作为参数传递给装饰器 check_args。 • 装饰器返回包装函数 wrap 实现对 test 的间接调⽤。 • 原函数名字 test 被重新关联到 wrap,所有对该名字的调⽤实际都是调⽤ wrap。 你完全可以把 "@" 当做语法糖,也可以直接使⽤函数式写法。只不过那样不便于代码维护,毕竟 AOP 极⼒避免代码侵⼊。装饰器不⼀定⾮得是个函数返回包装对象,也可以是个类,通过 __call__ 完成目标调⽤。 >>> class CheckArgs(object): ... def __init__(self, func): ... self._func = func ... ... def __call__(self, *args): ... args = filter(bool, args) ... self._func(*args) >>> @CheckArgs! ! ! ! ! ! # ⽣成 CheckArgs 实例。 ... def test(*args): ... print args >>> test! ! ! ! ! ! ! # 名字指向该实例。 <__main__.CheckArgs object at 0x107a237d0> >>> test(1, 0, 2, "", [], 3)! ! ! ! # 每次都是通过该实例的 __call__ 调⽤。 (1, 2, 3) ⽤类装饰器对象实例替代原函数,以后的每次调⽤的都是该实例的 __call__ ⽅法。这种写法有点啰嗦,还得注意避免在装饰器对象上保留状态 2.1 Class 为Class 提供装饰器同样简单,⽆⾮是将类型对象做为参数⽽已。 >>> def singleton(cls): ... def wrap(*args, **kwargs): ... o = getattr(cls, "__instance__", None) ... if not o: ... o = cls(*args, **kwargs) ... cls.__instance__ = o ... ... return o ... ... return wrap!! ! ! # 返回 wrap 函数,可以看做原 class 的⼯⼚⽅法。 >>> @singleton ... class A(object): ... def __init__(self, x): ... self.x = x >>> A <function wrap at 0x108afff50> >>> a, b = A(1), A(2) >>> a is b True 将 class A 替换成 func wrap 可能有些不好看,修改⼀下,返回 class wrap。 >>> def singleton(cls): ... class wrap(cls): ... def __new__(cls, *args, **kwargs): ... o = getattr(cls, "__instance__", None) ... if not o: ... o = object.__new__(cls) ... cls.__instance__ = o ... ... return o ... ... return wrap >>> @singleton ... class A(object): ... def test(self): print hex(id(self)) >>> a, b = A(), A() >>> a is b True >>> a.test() 0x1091e9990 创建继承⾃原类型的 class wrap,然后在 __new__ ⾥⾯做⼿脚就⾏了。⼤多数时候,我们仅⽤装饰器为原类型增加⼀些额外成员,那么可直接返回原类型。 >>> def action(cls): ... cls.mvc = staticmethod(lambda: "Action") ... return cls >>> @action ... class Login(object): pass >>> Login.mvc() 'Action 这就是典型的 metaprogramming 做法了 2.2 参数 参数让装饰器拥有变化,也更加灵活。只是需要两步才能完成:先传参数,后送类型。 >>> def table(name): ... def _table(cls): ... cls.__table__ = name ... return cls ... ... return _table >>> @table("t_user") ... class User(object): pass >>> @table("t_blog") ... class Blog(object): pass >>> User.__table__ 't_user' >>> Blog.__table__ 't_blog 只⽐⽆参数版本多了传递参数的调⽤,其他完全相同。 User = (table("t_user"))(User)2.3 嵌套 可以在同⼀目标上使⽤多个装饰器 >>> def A(func): ... print "A" ... return func >>> def B(func): ... print "B" ... return func >>> @A ... @B ... def test(): ... print "test" B A 分解⼀下,⽆⾮是函数嵌套调⽤。 test = A(B(test))2.4 functools.wraps 如果装饰器返回的是包装对象,那么有些东⻄必然是不同的。 >>> def check_args(func): ... def wrap(*args): ... return func(*filter(bool, args)) ... ... return wrap >>> @check_args def test(*args): ... """test function""" ... print args >>> test.__name__! ! ! # 冒牌货! 'wrap' >>> test.__doc__! ! ! # ⼭寨货连个说明书都没有! ⼀旦 test 的调⽤者要检查某些特殊属性,那么这个 wrap 就会暴露了。幸好有 functools.wraps。 >>> def check_args(func): ... @functools.wraps(func) ... def wrap(*args): ... return func(*filter(bool, args)) ... ... return wrap >>> @check_args def test(*args): """test function""" print args >>> test <function test at 0x108b026e0> >>> test.__name__ 'test' >>> test.__doc__ 'test function' >>> test(1, 0, 2, "", 3) (1, 2, 3) functools.wraps 是装饰器的装饰器,它的作⽤是将原函数对象的指定属性复制给包装函数对象,默认有 __module__、__name__、__doc__,或者通过参数选择。 2.5 装饰器都能干嘛? • AOP: ⾝份验证、参数检查、异常⽇志等等。• Proxy: 对目标函数注⼊权限管理等。• Context: 提供函数级别的上下⽂环境,⽐如 Synchronized(func) 同步。• Caching: 先检查缓存是否过期,然后再决定是否调⽤目标函数。• Metaprogramming: 这个⾃不必多说了。• 等等…… 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!编辑 try...except...是处理异常的基本方式。在原来的基础上,还可有扩展. 目录1.处理多个异常2. else 子句3. finally4. 和条件语句相比👑👑👑结束语👑👑👑1.处理多个异常 处理多个异常,并不是因为同时报出多个异常。程序在运行中,只要遇到一个异常就会有反应,所以,每次捕获到的异常一定是一个。所谓处理多个异常的意思是可以容许捕获不同的异常,有不同的 except 子句处理。 #!/usr/bin/env Python # coding=utf-8 while 1: print "this is a division program." c = raw_input("input 'c' continue, otherwise logout:") if c == 'c': a = raw_input("first number:") b = raw_input("second number:") try: print float(a)/float(b) print "*************************" except ZeroDivisionError: print "The second number can't be zero!" print "*************************" except ValueError: print "please input number." print "************************" else: break 将上节的一个程序进行修改,增加了一个 except 子句,目的是如果用户输入的不是数字时,捕获并处理这个异常。测试如下: $ python 21701.py this is a division program. input 'c' continue, otherwise logout:c first number:3 second number:"hello" #输入了一个不是数字的东西 please input number. #对照上面的程序,捕获并处理了这个异常 ************************ this is a division program. input 'c' continue, otherwise logout:c first number:4 second number:0 The second number can't be zero! ************************* this is a division program. input 'c' continue, otherwise logout:4 $ 如果有多个 except,在 try 里面如果有一个异常,就转到相应的 except 子句,其它的忽略。如果 except 没有相应的异常,该异常也会抛出,不过这是程序就要中止了,因为异常“浮出”程序顶部。除了用多个 except 之外,还可以在一个 except 后面放多个异常参数,比如上面的程序,可以将 except 部分修改为: except (ZeroDivisionError, ValueError): print "please input rightly." print "********************" 运行的结果就是: $ python 21701.py this is a division program. input 'c' continue, otherwise logout:c first number:2 second number:0 #捕获异常 please input rightly. ******************** this is a division program. input 'c' continue, otherwise logout:c first number:3 second number:a #异常 please input rightly. ******************** this is a division program. input 'c' continue, otherwise logout:d $ 需要注意的是,except 后面如果是多个参数,一定要用圆括号包裹起来。否则,后果自负。突然有一种想法,在对异常的处理中,前面都是自己写一个提示语,发现自己写的不如内置的异常错误提示更好。希望把它打印出来。但是程序还能不能中断。Python 提供了一种方式,将上面代码修改如下: while 1: print "this is a division program." c = raw_input("input 'c' continue, otherwise logout:") if c == 'c': a = raw_input("first number:") b = raw_input("second number:") try: print float(a)/float(b) print "*************************" except (ZeroDivisionError, ValueError), e: print e print "********************" else: break 运行一下,看看提示信息。 $ python 21702.py this is a division program. input 'c' continue, otherwise logout:c first number:2 second number:a #异常 could not convert string to float: a ******************** this is a division program. input 'c' continue, otherwise logout:c first number:2 second number:0 #异常 float division by zero ******************** this is a division program. input 'c' continue, otherwise logout:d $ 以上程序中,之处理了两个异常,还可能有更多的异常呢?如果要处理,怎么办?可以这样: execpt: 或者 except Exception, e ,后面什么参数也不写就好了。 2. else 子句 有了 try...except... ,在一般情况下是够用的,但总有不一般的时候出现,所以,就增加了一个 else 子句。其实,人类的自然语言何尝不是如此呢?总要根据需要添加不少东西。 >>> try: ... print "I am try" ... except: ... print "I am except" ... else: ... print "I am else" ... I am try I am else 这段演示,能够帮助读者理解 else 的执行特点。如果执行了 try,则 except 被忽略,但是 else 被执行。 >>> try: ... print 1/0 ... except: ... print "I am except" ... else: ... print "I am else" ... I am except 这时候 else 就不被执行了。理解了 else 的执行特点,可以写这样一段程序,还是类似于前面的计算,只不过这次要求,如果输入的有误,就不断要求从新输入,知道输入正确,并得到了结果,才不再要求输入内容,程序结束。在看下面的参考代码之前,读者是否可以先自己写一段呢?并调试一下,看看结果如何。 #!/usr/bin/env Python # coding=utf-8 while 1: try: x = raw_input("the first number:") y = raw_input("the second number:") r = float(x)/float(y) print r except Exception, e: print e print "try again." else: break 先看运行结果: $ python 21703.py the first number:2 the second number:0 #异常,执行 except float division by zero try again. #循环 the first number:2 the second number:a #异常 could not convert string to float: a try again. the first number:4 the second number:2 #正常,执行 try 2.0 #然后 else:break,退出程序 $ 相当满意的执行结果。需要对程序中的 except 简单说明,这次没有像前面那样写,而是 except Exception, e ,意思是不管什么异 常,这里都会捕获,并且传给变量 e,然后用 print e 把异常信息打印出来。 3. finally finally 子句,一听这个名字,就感觉它是做善后工作的。的确如此,如果有了 finally,不管前面执行的是 try,还是 except,它都要执行。因此一种说法是用 finally 用来在可能的异常后进行清理。比如: >>> x = 10 >>> try: ... x = 1/0 ... except Exception, e: ... print e ... finally: ... print "del x" ... del x ... integer division or modulo by zero del x 看一看 x 是否被删除? >>> x Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'x' is not defined 当然,在应用中,可以将上面的各个子句都综合起来使用,写成如下样式: try: do something except: do something else: do something finally do something4. 和条件语句相比 try...except... 在某些情况下能够替代 if...else.. 的条件语句。这里我无意去比较两者的性能,因为看到有人讨论这个问题。我个人觉得这不是主要的,因为它们之间性能的差异不大。主要是你的选择。一切要根据实际情况而定,不是说用一个就能包打天。 👑👑👑结束语👑👑👑编辑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入! 很多事件并非总是按照人们自己设计的意愿顺利发展,而是经常出现这样那样的异常情况 ,例如,计划周末郊游时,计划会安排得满满的 计划可能是这样的:从家里出发→到达目的地 →游泳→烧烤→回家。但天有不测风云 ,若准备烧烤时天降大雨,这时只能终 郊游提前回家。 “天降大雨”是一种异常情况,计划应该考虑到这种情况,并且应该有处理这种异常的预案为增强程序的健壮性,计算机程序的编写也需要考虑如何处理这些异常情况, Python提供了异常处理功能,本 篇文章介绍了 Pytho 异常处理机制。 目录1. 错误1.1 Python 中的错误之一是语法错误(syntax errors)1.2 错误之二是在没有语法错误之后,会出现逻辑错误2.异常2.1 常见的异常2.1.1 NameError2.1.2 ZeroDivisionError2.1.3 SyntaxError2.1.4 IndexError2.1.5 IOError2.1.6 AttributeError2.2 处理异常👑👑👑结束语👑👑👑1. 错误1.1 Python 中的错误之一是语法错误(syntax errors)比如:>>> for i in range(10) File "<stdin>", line 1 for i in range(10) ^ SyntaxError: invalid syntax 上面那句话因为缺少冒号 : ,导致解释器无法解释,于是报错。这个报错行为是由 Python 的语法分析器完成的,并且检测到了错误所在文件和行号( File "<stdin>", line 1 ),还以向上箭头 ^ 标识错误位置(后面缺少 : ),最后显示错误类型。 1.2 错误之二是在没有语法错误之后,会出现逻辑错误 逻辑错误可能会由于不完整或者不合法的输入导致,也可能是无法生成、计算等,或者是其它逻辑问题。 当 Python 检测到一个错误时,解释器就无法继续执行下去,于是抛出异常。2.异常看一个异常(让 0 做分母了,这是小学生都相信会有异常的):>>> 1/0 Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: integer division or modulo by zero 当 Python 抛出异常的时候,首先有“跟踪记录(Traceback)”,还可以给它取一个更优雅的名字“回溯”。后面显示异常的详细信息。异常所在位置(文件、行、在某个模块)。最后一行是错误类型以及导致异常的原因。 2.1 常见的异常 异常 描述 NameError 尝试访问一个没有申明的变量 ZeroDivisionError 除数为 0 SyntaxError 语法错误 IndexError 索引超出序列范围 KeyError 请求一个不存在的字典关键字 IOError 输入输出错误(比如你要读的文件不存在) AttributeError 尝试访问未知的对象属性 为了能够深入理解,依次举例,展示异常的出现条件和结果。2.1.1 NameError>>> bar Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'bar' is not defined Python 中变量需要初始化,即要赋值。虽然不需要像某些语言那样声明,但是要赋值先。因为变量相当于一个标签,要把它贴到对象上才有意义。 2.1.2 ZeroDivisionError>>> 1/0 Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: integer division or modulo by zero 貌似这样简单的错误时不会出现的,但在实际情境中,可能没有这么容易识别,所以,依然要小心为妙。 2.1.3 SyntaxError>>> for i in range(10) File "<stdin>", line 1 for i in range(10) ^ SyntaxError: invalid syntax 这种错误发生在 Python 代码编译的时候,当编译到这一句时,解释器不能讲代码转化为 Python 字节码,报错。只有改正才能继续。所以,它是在程序运行之前就会出现的(如果有错)。现在有不少编辑器都有语法校验功能,在你写代码的时候就能显示出语法的正误,这多少会对编程者有帮助。 2.1.4 IndexError>>> a = [1,2,3] >>> a[4] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range >>> d = {"python":"itdiffer.com"} >>> d["java"] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'java' 这两个都属于“鸡蛋里面挑骨头”类型,一定得报错了。不过在编程实践中,特别是循环的时候,常常由于循环条件设置不合理出现这种类型的错误。 2.1.5 IOError>>> f = open("foo") Traceback (most recent call last): File "<stdin>", line 1, in <module> IOError: [Errno 2] No such file or directory: 'foo' 如果你确认有文件,就一定要把路径写正确,因为你并没有告诉 Python 对你的 computer 进行全身搜索,所以,Python 会按照你指定位置去找,找不到就异常 2.1.6 AttributeError>>> class A(object): pass ... >>> a = A() >>> a.foo Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'A' object has no attribute 'foo' 属性不存在。这种错误前面多次见到。其实,Python 内建的异常也不仅仅上面几个,上面只是列出常见的异常中的几个。比如还有: >>> range("aaa") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: range() integer end argument expected, got str. 总之,如果读者在调试程序的时候遇到了异常,不要慌张,这是好事情,是 Python 在帮助你修改错误。只要认真阅读异常信息,再用 dir() , help() 或者官方网站文档、google 等来协助,一定能解决问题。 2.2 处理异常 在一段程序中,为了能够让程序健壮,必须要处理异常。举例: #!/usr/bin/env Python # coding=utf-8 while 1: print "this is a division program." c = raw_input("input 'c' continue, otherwise logout:") if c == 'c': a = raw_input("first number:") b = raw_input("second number:") try: print float(a)/float(b) print "*************************" except ZeroDivisionError: print "The second number can't be zero!" print "*************************" else: break 运行这段程序,显示如下过程: $ python 21601.py this is a division program. input 'c' continue, otherwise logout:c first number:5 second number:2 2.5 ************************* this is a division program. input 'c' continue, otherwise logout:c first number:5 second number:0 The second number can't be zero! ************************* this is a division program. input 'c' continue, otherwise logout:d $ 从运行情况看,当在第二个数,即除数为 0 时,程序并没有因为这个错误而停止,而是给用户一个友好的提示,让用户有机会改正错误。这完全得益于程序中“处理异常”的设置,如果没有“处理异常”,异常出现,就会导致程序终止。处理异常的方式之一,使用 try...except... 。对于上述程序,只看 try 和 except 部分,如果没有异常发生,except 子句在 try 语句执行之后被忽略;如果 try子句中有异常可,该部分的其它语句被忽略,直接跳到 except 部分,执行其后面指定的异常类型及其子句。except 后面也可以没有任何异常类型,即无异常参数。如果这样,不论 try 部分发生什么异常,都会执行 except。在 except 子句中,可以根据异常或者别的需要,进行更多的操作。比如: #!/usr/bin/env Python # coding=utf-8 class Calculator(object): is_raise = False def calc(self, express): try: return eval(express) except ZeroDivisionError: if self.is_raise: print "zero can not be division." else: raise 在这里,应用了一个函数 eval() ,它的含义是: eval(...) eval(source[, globals[, locals]]) -> value Evaluate the source in the context of globals and locals. The source may be a string representing a Python expression or a code object as returned by compile(). The globals must be a dictionary and locals can be any mapping, defaulting to the current globals and locals. If only globals is given, locals defaults to it.例如:>>> eval("3+5") 8 另外,在 except 子句中,有一个 raise ,作为单独一个语句。它的含义是将异常信息抛出。并且,except 子句用了一个判断语句,根据不同的情况确定走不同分支。 if __name__ == "__main__": c = Calculator() print c.calc("8/0") 这时候 is_raise = False ,则会: $ python 21602.py Traceback (most recent call last): File "21602.py", line 17, in <module> print c.calc("8/0") File "21602.py", line 8, in calc return eval(express) File "<string>", line 1, in <module> ZeroDivisionError: integer division or modulo by zero 如果将 is_raise 的值改为 True,就是这样了: if __name__ == "__main__": c = Calculator() c.is_raise = True #通过实例属性修改 print c.calc("8/0") 运行结果: $ python 21602.py zero can not be division. None 最后的 None 是 c.calc("8/0") 的返回值,因为有 print c.calc("8/0") ,所以被打印出来。 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录🎀1. 生成器概念🎀2. 简单的生成器🎀3. 定义和执行过程🎀4. yield🎀5. 生成器方法👑👑👑结束语👑👑👑1. 生成器概念 生成器(英文:generator)是一个非常迷人的东西,也常被认为是 Python 的高级编程技能。不过,我依然很乐意在这里跟读者——尽管你可能是一个初学者——探讨这个话题,因为我相信各位大佬看本教程的目的,绝非仅仅 将自己限制于初学者水平,一定有一颗不羁的心——要成为 Python 高手。那么,开始了解生成器吧。还记得上节的“迭代器”吗?生成器和迭代器有着一定的渊源关系。生成器必须是可迭代的,诚然它又不仅仅是迭代器,但除此之外,又没有太多的别的用途,所以,我们可以把它理解为非常方便的自定义迭代器。 2. 简单的生成器>>> my_generator = (x*x for x in range(4))这是不是跟列表解析很类似呢?仔细观察,它不是列表,如果这样的得到的才是列表:>>> my_list = [x*x for x in range(4)]以上两的区别在于是 [] 还是 () ,虽然是细小的差别,但是结果完全不一样。>>> dir(my_generator) ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']为了容易观察,我将上述结果进行了重新排版。是不是发现了在迭代器中必有的方法 __inter__() 和 next() ,这说明它是迭代器。如果是迭代器,就可以用 for 循环来依次读出其值。>>> for i in my_generator: ... print i ... 0 1 4 9 >>> for i in my_generator: ... print i ... 当第一遍循环的时候,将 my_generator 里面的值依次读出并打印,但是,当再读一次的时候,就发现没有任何结果。这种特性也正是迭代器所具有的。如果对那个列表,就不一样了: >>> for i in my_list: ... print i ... 0 1 4 9 >>> for i in my_list: ... print i ... 0 1 4 9 难道生成器就是把列表解析中的 [] 换成 () 就行了吗?这仅仅是生成器的一种表现形式和使用方法罢了,仿照列表解析式的命名,可以称之为“生成器解析式”(或者:生成器推导式、生成器表达式)。生成器解析式是有很多用途的,在不少地方替代列表,是一个不错的选择。特别是针对大量值的时候,如上节所说的,列表占内存较多,迭代器(生成器是迭代器)的优势就在于少占内存,因此无需将生成器(或者说是迭代器)实例化为一个列表,直接对其进行操作,方显示出其迭代的优势。比如: >>> sum(i*i for i in range(10)) 285 注意观察上面的 sum() 运算,不要以为里面少了一个括号,就是这么写。是不是很迷人?如果列表,你不得不: >>> sum([i*i for i in range(10)]) 285通过生成器解析式得到的生成器,掩盖了生成器的一些细节,并且适用领域也有限。下面就要剖析生成器的内部,深入理解这个魔法工具。3. 定义和执行过程 yield 这个词在汉语中有“生产、出产”之意,在 Python 中,它作为一个关键词(你在变量、函数、类的名称中就不能用这个了),是生成器的标志。 >>> def g(): ... yield 0 ... yield 1 ... yield 2 ... >>> g <function g at 0xb71f3b8c> 建立了一个非常简单的函数,跟以往看到的函数唯一不同的地方是用了三个 yield 语句。然后进行下面的操作: >>> ge = g() >>> ge <generator object g at 0xb7200edc> >>> type(ge) <type 'generator'> 上面建立的函数返回值是一个生成器(generator)类型的对象。 >>> dir(ge) ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw'] 在这里看到了 __iter__() 和 next() ,说明它是迭代器。既然如此,当然可以: >>> ge.next() 0 >>> ge.next() 1 >>> ge.next() 2 >>> ge.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 从这个简单例子中可以看出,那个含有 yield 关键词的函数返回值是一个生成器类型的对象,这个生成器对象就是迭代器。我们把含有 yield 语句的函数称作生成器。生成器是一种用普通函数语法定义的迭代器。通过上面的例子可以看出,这个生成器(也是迭代器),在定义过程中并没有像上节迭代器那样写 __inter__() 和 next() ,而是只要用了 yield 语句,那个普通函数就神奇般地成为了生成器,也就具备了迭代器的功能特性。yield 语句的作用,就是在调用的时候返回相应的值。详细剖析一下上面的运行过程: 1. ge = g() :除了返回生成器之外,什么也没有操作,任何值也没有被返回。2. ge.next() :直到这时候,生成器才开始执行,遇到了第一个 yield 语句,将值返回,并暂停执行(有的称之为挂起)。3. ge.next() :从上次暂停的位置开始,继续向下执行,遇到 yield 语句,将值返回,又暂停。4. gen.next() :重复上面的操作。5. gene.next() :从上面的挂起位置开始,但是后面没有可执行的了,于是 next() 发出异常。 从上面的执行过程中,发现 yield 除了作为生成器的标志之外,还有一个功能就是返回值。那么它跟 return 这个返回值有什么区别呢? 4. yield为了弄清楚 yield 和 return 的区别,我写了两个函数来掩饰:>>> def r_return(n): ... print "You taked me." ... while n > 0: ... print "before return" ... return n ... n -= 1 ... print "after return" ... >>> rr = r_return(3) You taked me. before return >>> rr 3 从函数被调用的过程可以清晰看出, rr = r_return(3) ,函数体内的语句就开始执行了,遇到 return,将值返回,然后就结束函数体内的执行。所以 return 后面的语句根本没有执行。这是 return 的特点下面将 return 改为 yield: >>> def y_yield(n): ... print "You taked me." ... while n > 0: ... print "before yield" ... yield n ... n -= 1 ... print "after yield" ... >>> yy = y_yield(3) #没有执行函数体内语句 >>> yy.next() #开始执行 You taked me. before yield 3 #遇到 yield,返回值,并暂停 >>> yy.next() #从上次暂停位置开始继续执行 after yield before yield 2 #又遇到 yield,返回值,并暂停 >>> yy.next() #重复上述过程 after yield before yield 1 >>> yy.next() after yield #没有满足条件的值,抛出异常 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 结合注释和前面对执行过程的分析,读者一定能理解 yield 的特点了,也深知与 return 的区别了。一般的函数,都是止于 return。作为生成器的函数,由于有了 yield,则会遇到它挂起,如果还有 return,遇到它就直接抛出 SoptIteration 异常而中止迭代。 #!/usr/bin/env Python # coding=utf-8 def fibs(max): """ 斐波那契数列的生成器 """ n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 if __name__ == "__main__": f = fibs(10) for i in f: print i , 运行结果如下: $ python 21501.py 1 1 2 3 5 8 13 21 34 55 用生成器方式实现的斐波那契数列是不是跟以前的有所不同了呢?大家可以将本教程中已经演示过的斐波那契数列实现方式做一下对比,体会各种方法的差异。经过上面的各种例子,已经明确,一个函数中,只要包含了 yield 语句,它就是生成器,也是迭代器。这种方式显然比前面写迭代器的类要简便多了。但,并不意味着上节的就被抛弃。是生成器还是迭代器,都是根据具体的使用情景而定。 5. 生成器方法 在 python2.5 以后,生成器有了一个新特征,就是在开始运行后能够为生成器提供新的值。这就好似生成器和“外界”之间进行数据交流。 >>> def repeater(n): ... while True: ... n = (yield n) ... >>> r = repeater(4) >>> r.next() 4 >>> r.send("hello") 'hello 当执行到 r.next() 的时候,生成器开始执行,在内部遇到了 yield n 挂起。注意在生成器函数中, n = (yieldn) 中的 yield n 是一个表达式,并将结果赋值给 n,虽然不严格要求它必须用圆括号包裹,但是一般情况都这么做,请大家也追随这个习惯。当执行 r.send("hello") 的时候,原来已经被挂起的生成器(函数)又被唤醒,开始执行 n = (yield n) ,也就是讲 send() 方法发送的值返回。这就是在运行后能够为生成器提供值的含义。如果接下来再执行 r.next() 会怎样? >>> r.next() 什么也没有,其实就是返回了 None。按照前面的叙述,读者可以看到,这次执行 r.next() ,由于没有传入任何值,yield 返回的就只能是 None.还要注意,send() 方法必须在生成器运行后并挂起才能使用,也就是 yield 至少被执行一次。如果不是这样: >>> s = repeater(5) >>> s.send("how") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't send non-None value to a just-started generator 就报错了。但是,可将参数设为 None: >>> s.send(None) 5 这是返回的是调用函数的时传入的值。此外,还有两个方法:close() 和 throw()• throw(type, value=None, traceback=None):用于在生成器内部(生成器的当前挂起处,或未启动时在定义处)抛出一个异常(在 yield 表达式中)。• close():调用时不用参数,用于关闭生成器。 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入! 目录1.模块制作1.1定义自己的模块1.2Python中的包2.嵌套的包2.1 目录结构2.2导入子包和使用模块2.3也可使用 from xxx import xxx 实现不同需求的导入2.4包同样支持 from xxx import *3.模块知识扩展3.1常用标准库3.2应用案例,注册和登录 👑👑👑结束语👑👑👑1.模块制作1.1定义自己的模块在Python中,每个Python文件都可以作为一个模块,模块的名字就是文件的名字。比如有这样一个文件test.py,在test.py中定义了函数add。调用自己的模块 参考上一篇文章使用__name__测试模块参考上一篇文章1.2Python中的包 Python中的包包将有联系的模块组织在一起,即放到同一个文件夹下,并且在这个文件夹创建一个名字为__init__.py 文件,那么这个文件夹就称之为包。__all__在包中的作用:在__init__.py文件中,定义一个__all__变量,它控制着 from 包名 import *时导入的模块可以在__init__.py文件中编写内容 [root@localhost ~]# mkdir msg [root@localhost ~]# cd msg/[root@localhost msg]# vim sendmsg.py def sendmsg(): print("正在发送短信...") print("已经成功发送...")[root@localhost msg]# vim recvmsg.py def recvmsg(): print("接收到一条短信.....")[root@localhost msg]# tree . ├── recvmsg.py └── sendmsg.py 0 directories, 2 files[root@localhost msg]# ipython In [1]: import msg.sendmsg In [2]: msg.sendmsg.sendmsg() 正在发送短信... 已经成功发送... In [3]: import msg.recvmsg In [4]: msg.recvmsg.recvmsg() 接收到一条短信..... In [5]: import msg.recvmsg as re In [6]: re.recvmsg() 接收到一条短信.....In [1]: from msg import sendmsg,recvmsg In [2]: sendmsg.sendmsg() 正在发送短信... 已经成功发送... In [3]: from msg.sendmsg import sendmsg In [4]: sendmsg() 正在发送短信... 已经成功发送... In [5]: from msg import * In [6]: sendmsg.sendmsg() 正在发送短信... 已经成功发送...[root@localhost msg]# vim __init__.py __all__=['sendmsg','recvmsg'] [root@localhost ~]# ipython In [1]: from msg import * In [2]: sendmsg.sendmsg() 正在发送短信... 已经成功发送...2.嵌套的包2.1 目录结构 假定我们的包的例子有如下的目录结构: Phone/ __init__.py common_util.py Voicedta/ __init__.py Pots.py Isdn.py Fax/ __init__.py G3.py Mobile/ __init__.py Analog.py igital.py Pager/ __init__.py Numeric.py2.2导入子包和使用模块 Phone 是最顶层的包,Voicedta 等是它的子包。 我们可以这样导入子包: import Phone.Mobile.Analog Phone.Mobile.Analog.dial()2.3也可使用 from xxx import xxx 实现不同需求的导入 第一种方法是只导入顶层的子包,然后使用属性/点操作符向下引用子包树: from Phone import Mobile Mobile.Analog.dial('555-1212') 此外,我们可以还引用更多的子包: from Phone.Mobile import Analog Analog.dial('555-1212') 事实上,你可以一直沿子包的树状结构导入: from Phone.Mobile.Analog import dial dial('555-1212')在我们上边的目录结构中,我们可以发现很多的 __init__.py 文件。这些是初始化模块,from-import 语句导入子包时需要用到它。 如果没有用到,他们可以是空文件。2.4包同样支持 from xxx import * 包同样支持 from-import all 语句: from package.module import *然而,这样的语句会导入哪些文件取决于操作系统的文件系统。所以我们在__init__.py 中加入 __all__ 变量。该变量包含执行这样的语句时应该导入的模块的名字。它由一个模块名、字符串列表组成。3.模块知识扩展常用模块简介Python有一套很有用的标准库。标准库会随着Python解释器,一起安装在你的电脑里。它是Python的一个组成部分。这些标准库是Python为你准备好的利器可以让编程事半功倍3.1常用标准库 标准库 说明 builtins 内建函数默认加载 os 操作系统接口 sys python自身的运行环境 functools 常用的工具 json 编码和解码json对象 logging 记录日志、调试 multiprocessing 多进程 threading 多进程 copy 拷贝 import json ''' a = ["aa","bb","cc"] f = open("test.txt","w") f.write(str(a)) f.close() ''' f = open("test.txt","r") result = f.read() #print(list(result)) print(type(result))In [4]: import sendmsg In [5]: sendmsg.test2() ----sendmsg---test2---- In [6]: import hashlib In [7]: m=hashlib.md5() In [8]: s="123456" In [9]: m.update(s.encode("utf-8")) In [10]: m.hexdigest() Out[10]: 'e10adc3949ba59abbe56e057f20f883e'3.2应用案例,注册和登录 import hashlib class LoginSys(object): #加密方法 def setMd5(self,password): m = hashlib.md5() m.update(password.encode("utf-8")) return m.hexdigest() def main(self): f = open("password.txt","a+") f.seek(0,0) content = f.read() f.close() if len(content) <=0: #注册 print("请根据提示进行注册") name = input("请输入您要注册的用户名:") password = input("请输入您要注册的密码:") fw = open("password.txt","w") fw.write(name) fw.write("\n") #加密 fw.write(self.setMd5(password)) fw.close() else: #登录 print("请根据提示进行登录!") name = input("请输入您的账号:") password = input("请输入您的密码:") f = open("password.txt","a+") f.seek(0,0) nameSave = f.readline() passwordSave = f.readline() password = self.setMd5(password) a = nameSave.split()[0] print("从文件中读取过来的账号是:",a) if name == a: if password == passwordSave: print("欢迎%s登录本系统!"%name) else: print("密码错误!") else: print("账号错误!") l = LoginSys() l.main() 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1.抽象2.封装2.1公有成员变量和私有成员变量 2.2公有方法和私有方法 2.2.1 练习3. 继承3.1继承的概念3.2重写父类方法与调用父类方法3.3多继承4.多态4.1多态的定义4.2新式类和经典类的区别👑👑👑结束语👑👑👑1.抽象 抽象是隐藏多余细节的艺术。在面向对象的概念中,抽象的直接表现形式通常为类。Python基本上提供了面向对象编程语言的所有元素,如果你已经至少掌握了一门面向对象语言,那么利用Python进行面向对象程序设计将会相当容易。忽略一个主题中与当前目标无关的东西,专注的注意与当前目标有关的方面.( 就是把现实世界中的某一类东西, 提取出来, 用程序代码表示, 抽象出来的一般叫做类或者接口).抽象并不打算了解全部问题, 而是选择其中的一部分,暂时不用部分细节.抽象包括两个方面,一个数据抽象,二是过程抽象.数据抽象 -->表示世界中一类事物的特征,就是对象的属性.比如鸟有翅膀,羽毛等(类的属性)过程抽象 -->表示世界中一类事物的行为,就是对象的行为.比如鸟会飞,会叫(类的方法) 2.封装面向对象的程序设计中,某个类把所需要的数据(也可以说是类的属性)和对数据的操作(也可以说是类的行为)全部都封装在类中,分别称为类的成员变量和方法(或成员函数)。这种把成员变量和成员函数封装在一起的编程特性称为封装。2.1公有成员变量和私有成员变量 Python中用成员变量的名字来区分是公有成员变量或者是私有成员变量。Python中,以两个下划线‘_ _’开头的变量都是私有成员变量,而其余的变量都属于公有成员变量。其中,私有的成员变量只能在类的内部访问,而共有的公有的成员变量可以在类的外部进行访问。 2.2公有方法和私有方法 类的方法是对类行为的封装。类的方法也分为公有方法和私有方法。类的私有方法只能通过对象名(在类内部也就是self)在类的内部进行访问。而公有方法可以在类的外部通过对象名进行访问。同样,公有的成员方法和私有的成员方法也是通过名字来区分的,双下划线‘__’开头的方法是私有成员方法。私有方法:只能在类的内部进行访问,对象无法访问。私有属性: 提高代码安全性,不允许别人随意修改 class Test(object): #私有方法 def __test2(self): print("私有方法,__test2") #普通方法 def test(self): print("普通方法test") #普通方法 def _test1(self): print("普通方法_test1方法") #在类内部调用私有方法 #t.__test2() self.__test2() t = Test() t.test() t._test1() #t.__test2() #调用时会报错#私有方法应用场景--发短信#私有方法应用场景--发短信 class Test: #核心私有方法,用于发送短信 def __sendMsg(self): print("---正在发送短信---") #公共方法 def sendMsg(self,newMoney): if newMoney>10000: #余额大于10000才可以调用发短信功能 self.__sendMsg() else: print("抱歉,余额不足,请先充值!") t = Test() t.sendMsg(1000000000)#帐号不允许更改 class Person(object): def __init__(self,name,sex): self.__name = name self.__sex = sex def getSex(self): return self.__sex def getName(self): return self.__name def setName(self,newName): if len(newName)>=5: self.__name = newName else: print("名字长度必须大于等于才可修改!") xiaoming = Person("hoongfu","男") print(xiaoming.getName()) print(xiaoming.getSex()) xiaoming.setName("xiaoming") print(xiaoming.getName())2.2.1 练习 定义一个类Person,类中有私有方法和普通方法,私有属性和普通属性能通过普通方法调用私有方法,也能通过普通方法更改私有属性。 class Test(object): def test(self): self.__sex = "保密" print("普通公有方法test") #调用私有方法 self.__test1() def __test1(self): print("私有方法__test1") #调用私有属性 print("私有属性__sex:",self.__sex) t = Test() t.test()3. 继承3.1继承的概念在程序中,继承描述的是事物之间的所属关系,例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物;同理,波斯猫和巴厘猫都继承自猫,而沙皮狗和斑点狗都继承狗#继承 #继承 class Animal(object): def eat(self): print("----吃----") def dirk(self): print("----喝----") def run(self): print("----跑----") def sleep(self): print("----睡觉----") class Dog(Animal): ''' def eat(self): print("----吃----") def dirk(self): print("----喝----") def run(self): print("----跑----") def sleep(self): print("----睡觉----") ''' def call(self): print("旺旺叫...") class Cat(Animal): def catch(self): print("抓老鼠....") dog = Dog() dog.call() dog.eat() tom = Cat() tom.catch() tom.sleep()#多继承#多继承 class Animal(object): def eat(self): print("----吃----") def dirk(self): print("----喝----") def run(self): print("----跑----") def sleep(self): print("----睡觉----") class Dog(Animal): def call(self): print("旺旺叫...") class XiaoTq(Dog): def fly(self): print("----飞喽-------") xtq = XiaoTq() xtq.fly() xtq.call() xtq.eat()class Cat(object): def __init__(self,name,color="白色"): self.name = name self.color = color def run(self): print("%s -- 在跑"%self.name) class Bosi(Cat): def setName(self,newName): self.name = newName def eat(self): print("%s -- 在吃"%self.name) bs = Bosi("印度猫") print(bs.name) print(bs.color) bs.eat() bs.setName("波斯猫") bs.run() 3.2重写父类方法与调用父类方法 所谓重写,就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法.使用super调用父类的方法:可以直接调用父类方法,不需要通过 父类名.父类方法名 的方式 class Cat(object): def sayHello(self,name): print("hello---1") class Bosi(Cat): def sayHello(self): print("hello---2") #Cat.sayHello(self) super().sayHello("Zhangsan") bs = Bosi() bs.sayHello() 3.3多继承 多继承举例: class Base(object): def test(self): print("----Base-----") class A(Base): def test(self): print("----test1-----") class B(Base): def test(self): print("----test2-----") class C(A,B): pass c = C() c.test() print(C.__mro__) #可以查看C类的搜索方法时的先后顺序4.多态4.1多态的定义 所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态。多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)。当子类和父类都存在相同的print_self()方法时,我们说,子类的print_self()覆盖了父类的print_self(),在代码运行的时候,总是会调用子类的print_self()。这样,我们就获得了继承的另一个好处: 多态。 class Dog(object): def printSelf(self): print("大家好,我是xxx,请大家多多关照!") class XiaoTq(Dog): def printSelf(self): print("Hello,ereybody,我是你们的老大,我是哮天神犬!") #定义一个执行函数 def exec(obj): """ #定义时的类型并不知道要调用哪个类的方法, 当运行时才能确定调用哪个类的方法,这种情况,我们就叫做多态 """ obj.printSelf() dog = Dog() exec(dog) xtq = XiaoTq() exec(xtq)4.2新式类和经典类的区别 新式类都从 object 继承,经典类不需要Python 2.x中默认都是经典类,只有显式继承了objectPython 3.x中默认都是新式类,经典类被移除,不必显式的继承object #新式类和经典类的区别 class A: def __init__(self): print('a') class B(A): def __init__(self): A().__init__() print('b') b = B() print(type(b))class A(): def __init__(self): pass def save(self): print("This is from A") class B(A): def __init__(self): pass class C(A): def __init__(self): pass def save(self): print("This is from C") class D(B,C): def __init__(self): pass fun = D() fun.save()👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1、python中的os 模块1.1使用 os 模块对文件操作1.1.1使用 os 模块对文件重命名 rename()1.1.2使用 os 模块对删除文件 remove()1.2使用 OS 对文件夹的相关操作:1.2.1创建文件夹1.2.2创建多层目录1.2.3获取当前目录1.2.4改变默认目录1.2.5获取目录列表,包含文件和目录1.2.6删除文件夹1.3批量创建1.4批量修改文件名2.Python 换行符问题2.1Python 换行符问题:2.2python中对于长句子换行的问题👑👑👑结束语👑👑👑1、python中的os 模块1.1使用 os 模块对文件操作有些时候,需要对文件进行重命名、删除等一些操作,Python 的 os 模块中都有这么功能1.1.1使用 os 模块对文件重命名 rename() rename(需要修改的文件名, 新的文件名) import os os.rename(" 程序猿自身修养.txt", " 程序员自身修养.txt")概述os.rename() 方法用于命名文件或目录,从 src 到 dst,如果dst是一个存在的目录, 将抛出OSError。语法rename()方法语法格式如下:os.rename(src, dst)参数 src -- 要修改的目录名 dst -- 修改后的目录名 返回值该方法没有返回值实例以下实例演示了 rename() 方法的使用:#!/usr/bin/python # -*- coding: UTF-8 -*- import os, sys # 列出目录 print "目录为: %s"%os.listdir(os.getcwd()) # 重命名 os.rename("test","test2") print "重命名成功。" # 列出重命名后的目录 print "目录为: %s" %os.listdir(os.getcwd())执行以上程序输出结果为: 目录为:[ 'a1.txt','resume.doc','a3.py','test' ]重命名成功[ 'a1.txt','resume.doc','a3.py','test2' ] 1.1.2使用 os 模块对删除文件 remove() remove(待删除的文件名) import os os.remove(" 程序员自身修养.txt")概述 os.remove() 方法用于删除指定路径的文件。如果指定的路径是一个目录,将抛出OSError。在Unix, Windows中有效 语法remove()方法语法格式如下:os.remove(path)参数path -- 要移除的文件路径返回值该方法没有返回值实例以下实例演示了 remove() 方法的使用:#!/usr/bin/python # -*- coding: UTF-8 -*- import os, sys # 列出目录 print "目录为: %s" %os.listdir(os.getcwd()) # 移除 os.remove("aa.txt") # 移除后列出目录 print "移除后 : %s" %os.listdir(os.getcwd())执行以上程序输出结果为: 目录为:[ 'a1.txt','aa.txt','resume.doc' ]移除后 :[ 'a1.txt','resume.doc' ] 1.2使用 OS 对文件夹的相关操作:1.2.1创建文件夹 不能创建./小鹏/test/hehe 1.2.2创建多层目录import os os.makedirs(" 小鹏/ 小李/ 小王")1.2.3获取当前目录import os os.getcwd()1.2.4改变默认目录 进入到某个目录,或者是切换目录 import os os.chdir("./../")1.2.5获取目录列表,包含文件和目录import os print(os.listdir("./"))1.2.6删除文件夹 删除目录 path,要求 path 必须是个空目录,否则抛出 OSError 错误 import os os.rmdir(" 小鹏") 删除不为空的目录 import shutil shutil.rmtree(“aa”)1.3批量创建 import os os.mkdir("./test") os.chdir("./test") print(os.getcwd()) i = 1 while i<=10: open("人民的名义-%d.avi"%i,"w") i+=1 print("创建完毕")1.4批量修改文件名import os os.chdir("./test") i = 1 while i<=10: os.rename("人民的名义-%d.avi"%i,"[小鹏出品]-人民的名义-%d.avi"%i) i+=1import os #得到要批量修改的目录名 folder = input("请输入您要批量修改的文件目录名称:") os.chdir(folder) #得到文件夹下所有文件名 fileNames = os.listdir() #遍历文件夹下所有文件并修改名称 for fileName in fileNames: print(fileName) #新名字 newFileName = "[宏福出品]-"+fileName os.rename(fileName,newFileName) print("重命名后的文件:") for fileName in os.listdir(): print(fileName)2.Python 换行符问题2.1Python 换行符问题: 先知道结果:在 linux 和 mac 系统上我们读写文本文件使用二进制方式或者文本方式都可以,因为在处理\n 都是一样的;那么读写文本文件和二进制文件的时候,可以使用 r 和 w 模式或者 rb 和 wb 模式。在 window 中的换行是\r\n,当我们使用程序以文本方式写入一个\n 的时候,默认会帮我们加上\r,这样打开文件的时候才能得到换行的效果;所有在 window 上读写文本文件的时候, 建议使用 r 或者 w 模式。在读写二进制文件的时候,建议用 rb 和 wb 模式。不同操作系统换行符是不一样的在处理文本数据的时候体现在不同操作系统处理\r\n 是不一样的不同操作系统 换行符是不一样的 linux -->\nunix-->\nmac--> 老版本(\r)-->\nwindows -->\r\n 对应换行符 windows 遇到\r\n 才换行显示2.2python中对于长句子换行的问题 首先明确两个概念,物理行和逻辑行。• 物理行物理行是指语句在文本编辑中的一行• 逻辑行逻辑行则是python语句在python编译器里的一行。所以有些很长的语句,为了方便阅读,我们可以把它放到不同的物理行中。但是python在特定情况仍然会把不同物理行当作一个逻辑行,在这种python的编译器会自动将物理行中的换行符去掉。一般情况下,在[],(),{}中的不同物理行都会被当成一个逻辑行看待。例如: a = [1,2,3,4,5] print(a) [1, 2, 3, 4, 5] a = [1,2, 3,4,5] # 这个是 [] 内部的换行编译器自动处理为一个逻辑行 print(a) [1, 2, 3, 4, 5]甚至可以在括号内加注释,例如:a = (1,#注释可以放在这 2, 3) print(a) (1, 2, 3) a = {'key1':1, # 这是一个{} 的例子 'key2':2} print(a) {'key1': 1, 'key2': 2} a = {'key1'# 也可以这样用 :1, 'key2':2} print(a) {'key1': 1, 'key2': 2}函数的参数由于是在()之内,所以也可以这样操作def my_fun(a, #函数也可以这样断行 b,c): print(a,b,c) my_fun(10,20,30) my_fun(10,# 也可以这样调用 20, 30) 10 20 30 10 20 30但是如果在非括号中,则需要用 \ 当作转义符,例如:a = 20 b = 30 c = 40 if a>5 and b>10 and c>20: print('a={},b={},c={}'.format(a,b,c)) # 也可以这样写 if a>5 \ and b>10 \ and c>20: #注意,这里 \ 后的缩进并不重要,因为编译器把这多个物理行当成一个逻辑行了 print('a={},b={},c={}'.format(a,b,c)) a=20,b=30,c=40 a=20,b=30,c=40对于字符串,如果用三个单引号,或者三个双引号包括的字符串,那么中间的所以换行符都会保留,例如:a = '''this is a string''' a 'this is a string' print(a) this is a string a = '''this is a string''' # 这里 is后边的换行符将被保留 a # 注意is 后边的换行符\n 'this is\na string' print(a) # 打印出来的话,注意是两行 this is a string如果不想保留换行符,需要用单引号'',但是需要转义符 \,如下:a = 'this is \ a string' a # 注意a中没有换行符 'this is a string' print(a) # a中没有换行符 this is a string如果需要在字符串中包含 \,则需要用 \\。a = 'this is a \\ string' #用 \\ 来表示\ a # 这时候看到两个 \ (\\),但是不要慌,因为print出来就对了 'this is a \\ string' print(a) # print a之后,只有一个 \ this is a string👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录🐊1.文件操作介绍🦓1.1文件作用🦓1.2打开文件open🐊2.文件的开关及读写🦓2.1文件的读写:🐅2.1.1写数据(write):🐅2.1.2读数据(read):🐅2.1.3读数据(readlines):🦓2.2举例🐅2.2.1练一练1🐅2.2.2练一练2🐅2.2.3练一练3🦓2.3文件读写定位🐅2.3.1python2 中的 seek🐅2.3.2python3 中的 seek结束语🏆🏆🏆1.文件操作介绍1.1文件作用就是把一些数据存放起来,可以让程序下一次执行的时候直接使用,加载到内存中,而不必重新制作一份,省时省力1.2打开文件open在Python,使用open函数,可以打开一个已经存在的文件,或者创建一个新文件open(文件名,访问模式) 示例如下: f = open( 'test.txt', 'w')w 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 访问模式 说明 r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。如果文件不存在会崩溃。Read w 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。Write a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 rb 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。如果文件不存在会崩溃 read binary wb 以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 r+ 打开一个文件用于读写。文件指针将会放在文件的开头。如果文件不存在会崩溃 w+ 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 rb+ 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。如果文件不存在会崩溃。 wb+ 以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 ab+ 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写 2.文件的开关及读写2.1文件的读写:2.1.1写数据(write): 使用write()可以完成向文件写入数据:f.write(hello world!\n) 2.1.2读数据(read): 使用read(num)可以从文件中读取数据,num表示要从文件中读取的数据的长度( 单位是字节),如果没有传入num,那么就表示读取文件中所有的数据。f.read() 2.1.3读数据(readlines):就像read没有参数时一样,readlines 可以按照行的方式把整个文件中的内容进行一次性读取,并且返回的是一个列表,其中每一行的数据为一个元素.2.2举例f = open("text.txt","w",encoding="utf-8") f.write("hello 大家好!") f.close() f = open("text.txt","r",encoding="utf-8") print(f.read()) f.close() # 关闭这个文件需求描述:2.2.1练一练1 输入文件的名字,然后程序自动完成对文件进行拷贝例如,程序运行后提示“请输入你要拷贝的文件名:”,当你输入“test.txt”的时候,会把“test.txt”文件的内容复制一份到文件名叫“[复制]-test.txt”文件中。 #打开要复制的文件 oldFileName = input("请输入您要复制的文件名:") oldFile = open(oldFileName,"r",encoding="utf-8") #创建一个新文件用于保存要复制的内容 newFileName = "[复制]-"+oldFileName newFile = open(newFileName,"w",encoding="utf-8") #读取要复制的文件,并写入到新文件中 newFile.write(oldFile.read()) #关闭两个文件 oldFile.close() newFile.close()2.2.2练一练2 需求描述:输入文件的名字,然后程序自动完成对文件进行拷贝例如,程序运行后提示“请输入你要拷贝的文件名:”,当你输入“test.txt”的时候,会把“test.txt”文件的内容复制一份到文件名叫“test[复制].txt”文件中。 #打开要复制的文件 oldFileName = input("请输入您要复制的文件名:") oldFile = open(oldFileName,"r",encoding="utf-8") #用于分割文件名 #1.得到.的位置 position = oldFileName.rfind(".") #2.得到.之前的部分 pre = oldFileName[:position] #3.得到.之后的部分,包含点 sux = oldFileName[position:] #4.拼接一个新的名字 newFileName = pre+"[复制]"+sux #创建一个新文件用于保存要复制的内容 newFile = open(newFileName,"w",encoding="utf-8") #读取要复制的文件,并写入到新文件中 newFile.write(oldFile.read()) #关闭两个文件 oldFile.close() newFile.close() 支持超大文件的复制支持超大文件的复制2.2.3练一练3 需求描述:输入文件的名字,然后程序自动完成对文件进行拷贝例如,程序运行后提示“请输入你要拷贝的文件名:”,当你输入“test.txt”的时候,会把“test.txt”文件的内容复制一份到文件名叫“test[复制].txt”文件中 #打开要复制的文件 oldFileName = input("请输入您要复制的文件名:") oldFile = open(oldFileName,"r",encoding="utf-8") #用于分割文件名 #1.得到.的位置 position = oldFileName.rfind(".") #2.得到.之前的部分 pre = oldFileName[:position] #3.得到.之后的部分,包含点 sux = oldFileName[position:] #4.拼接一个新的名字 newFileName = pre+"[复制]"+sux #创建一个新文件用于保存要复制的内容 newFile = open(newFileName,"w",encoding="utf-8") #读取要复制的文件,并写入到新文件中 #增加支持大文件复制 while True: readContent = oldFile.read(1024) if len(readContent) == 0: break newFile.write(readContent) #关闭两个文件 oldFile.close() newFile.close()2.3文件读写定位 获取当前读写的位置 tell在读写文件的过程中,如果想知道当前的位置,可以使用 tell()来获取置 定位到某个位置 seek使用 seek()seek(offset, from)有 2 个参数offset:偏移量from:方向0:表示文件开头 1:表示当前位置 2:表示文件末尾 2.3.1python2 中的 seek 定位到文件开头:seek(0,0),seek(正数,0) ;不支持 seek(负数,0)定位到当前位置:seek(0,1),seek(正数,1) ,(负数,1)定位到文件末尾:seek(0,2),seek(正数,2) ,(负数,2) 2.3.2python3 中的 seek Python3 中的 seek:定位到文件开头:seek(0,0),seek(正数,0) ;不支持 seek(负数,0)定位到当前位置:seek(0,1) ;不支持 seek(正数,1),(负数,1)定位到文件末尾:seek(0,2) ;不支持 seek(正数,2),(负数,2) f = open("test.txt","r",encoding="utf-8")str = f.read(3)print("读取的数据是:",str)#查找当前位置position = f.tell()print("当前文件位置:",position)#重新设立位置f.seek(8,0)#查找当前位置position = f.tell()print("当前文件位置:",position)f.close() 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1.函数介绍及定义1.1函数的优点1.2定义函数1.3传参函数2.函数的类型2.1无参数,无返回值类型2.2无参数,有返回值类型2.3有参数,无返回值类型2.4有参数,有返回值函数2.5函数小总结:2.6给函数添加文档说明名3.函数的参数及返回值3.1函数的参数3.2定义有参数类型的函数3.3函数中的缺省值3.3.1 不定长参数3.4返回值介绍3.4.1 带有参数的返回值3.4.2 带有多个参数的返回值1.函数介绍及定义1.1函数的优点如果在开发程序时,需要某块代码多次,但是为了提高编写的效率以及代码的重用,所以把具有独立功能的代码块组织为一个小模块,这就是函数1.2定义函数格式: def 函数名():--tab-- 代码 调用函数 定义了函数之后,就相当于有了一个具有某些功能的代码,想要让这些代码能够执行,需要调用它。调用函数很简单,通过 函数名() 即可完成调用 举例 -使用函数打印自己的信息要求:定义一个函数,能够输出自己的姓名和年龄,并且调用这个函数让它执行提示:使用def定义函数,编写完函数之后,通过 函数名() 进行调用。函数命名要见名知意 def information(): name=input("请输入你的姓名") age=input("请输入你的年龄") print(name,age) information()1.3传参函数格式: def 函数名(参数1,参数2,……):--tab-- 代码 传参函数的调用:函数名(参数1,参数2,……)举例: 让用户输入两个数,让他们相加,并返回结果 def addNum(a,b): #a和b两个参数叫做形式参数 print(a) print(b) #print("a+b=%d"%(a+b)) c = a+b return c result = addNum(100,200) #100和200为实际参数 print("result:%s"%result)2.函数的类型 函数根据有没有参数,有没有返回值,可以相互组合,一共有4种:无参数,无返回值 无参数,有返回值有参数,无返回值 有参数,有返回值 2.1无参数,无返回值类型 def 函数名(): pass #定义函数,能够打印信息 def printInfo(): print("-"*20) print(" 人生苦短,我用Python ") print("-"*20) print_info()2.2无参数,有返回值类型应用场景:此类函数,不能接收参数,但是可以返回某个数据,一般情况下,像采集数据,用此类函数 def 函数名(): return xxx #获取温度 def getTemp(): return 24 result = getTemp() print("当前的温度为:%s"%result)2.3有参数,无返回值类型此类函数,能接受参数,但是不能返回结果,一般情况下,对某些变量设置数据而不需要结果的时候,可以使用此类函数 def 函数名(参数1,参数2.....): pass #0关灯 , 1开灯 def setOnOff(isOffOn): if isOffOn == 0: print("关灯") elif isOffOn ==1: print("开灯") #调用函数 setOnOff(1) setOnOff(0)2.4有参数,有返回值函数此类函数,不仅能接受参数,还可以返回某个数据,一般情况下,像处理数据并需要结果的应用,都使用此类函数 def 函数名(参数1,参数2.....): return xxx #计算1-?之间的累计和 def calcNum(num): result = 0 i=1 while i<=num: result+=i i+=1 return result num = int(input("请输入一个数:")) result = calcNum(num) print("1-%d的累计和为:%d"%(num,result))2.5函数小总结:定义函数时,是根据实际的功能需求来设计的,所以不同开发人员编写的函数类型各不相同。2.6给函数添加文档说明名格式: def 函数名():“文档说明”代码 调用:print(help(函数名)) def add(a,b): "用来完成对2个数求和" print("%d"%(a+b))print(help(add)) #生成工作中的文档说明 def initData(): """ 初始化商品价格、 日期、 分类 :return: """ pass def upData(): ''' 用来更新商品数据 :return: ''' pass def findData(): #用来搜索数据 pass def delData(): "删除数据" pass def createData(): ''' 添加创建数据 :return: ''' pass if __name__ == '__main__': import test08 print(help(test08))3.函数的参数及返回值3.1函数的参数为了让一个函数更通用,即想让它怎么计算,就让怎么计算,在定义函数的时候可以让函数接收数据,就解决了这个问题,这就是函数的参数3.2定义有参数类型的函数格式 def 函数名(参数1,参数2,……):代码 传参函数的调用函数名(参数1,参数2,……)举例 def add(a,b,c): #a,b 称为形参 "用来完成对2个数求和" print("%d"%(a+b+c))num1 = int(input("请输入第一个数:"))num2 = int(input("请输入第二个数:"))num3 = int(input("请输入第三个数:"))add(num1,num2,num3) #实参 def add2Num(a,b): print(a,b) add2Num(1,2) add2Num(b=1,a=2) add2Num(1,b=3) #add2Num(a=1,3) #语法错误不能使用小总结 定义时小括号中的参数,用来接收参数用的,称为 “形参”调用时小括号中的参数,用来传递给函数用的,称为 “实参” 3.3函数中的缺省值调用函数时,缺省参数的值如果没有传入,则被认为是默认值。注意:带有默认值的参数一定要位于参数列表的最后面 #定义函数 ,age=18 为缺省参数def printInfo(name,age=18): print("-"*20) print(name)print(age)printInfo("张三")printInfo("李四",35)printInfo(age = 28,name = "李四") #命名参数传参方式 def test(a,b,c=30,d=40): print("-"*20) print(a) print(b) print(c) print(d) test(10,20) test(10,20,33) test(10,20,33,44) A=100 B=1000 test(b=A,a=B)3.3.1 不定长参数有时可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,声明时不会命名基本语法如下: def functionname([formal_args,] *args, **kwargs):" 函数 _ 文档字符串 "function_suitereturn [expression] #不定长参数,*args 以元组格式存储数据(有*就变成不定长参数)#让很多数相加def sum2Num2(a,b,*args): print("="*30) print(a) print(b) print(args) result = a+b for num in args: result +=num print("ta们的和是:%d"%result)sum2Num2(11,22,33,44,55,66)sum2Num2(11,22,33)sum2Num2(11,22)#sum2Num2(11) #错误,至少要传递没有传递的形参 def test(a,b,c=30,*args,**kwargs): print("="*30) print(a) print(b) print(args) print(kwargs) A=(40,50) B={"name":"小鹏","age":39} test(10,20,30,*A,**B) #拆包3.4返回值介绍所谓“返回值”,就是程序中函数完成一件事情后,最后给调用者的结果。3.4.1 带有参数的返回值想要在函数中把结果返回给调用者,需要在函数中使用return def test(a,b): result =a+b return result #result可以改成一句话result=test(10,20)print(result) 3.4.2 带有多个参数的返回值 第一种方案:把多个值封装到列表中 d = [a,b,c] return d 第一种方案的另外一种写法 return [a,b,c] 第二种方案:封装到元组中 return (a,b,c) 第二种的另一种写法,默认就是元组 return a,b,c def test(): a = 10 b = 20 c = 30 result = test() print(result) print(type(result))第三种a,b,c = test() #a,b,c = (10,20,30) print(a) print(b) print(c)举例#应用场景,计算两个数的商 和 余数 def divid(a,b): shang = a // b yushu = a % b return shang,yushu sh,yu = divid(5,2) print("他们的商是:%d\n余数是:%d"%(sh,yu))👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入! 目录01.初识1.认识python2.应用场景:2.1Web应用开发2.2自动化运维2.3科学计算2.4桌面软件2.5服务器软件2.6游戏2.7产品早期原型和迭代2.8人工智能2.9数据分析3.开发环境搭建3.1python23.2python34.python程序4.1Python执行过程解析4.2Python解释器。4.3使用交互模式写Python程序4.4在文件的头部配置Python解释器5.Linux交互模式IPython5.1Ipython简介5.2开启Python3交互模式(必须联网)02.基础一1注释2.变量以及数据类型2.1变量2.2标识符和关键字3.输入函数3.1Python2中3.2Python3版本03.基础二1.If判断语句1.1if判断语句语法1.2if 语句的应用1.3练一练1.4.运算符2.If-else2.1if-else的使用格式2.2elif的格式2.3If嵌套2.4猜丁壳游戏3.while循环3.1Whlie循环的书写方式:3.2while循环的格式3.3while循环注意事项:3.4while练习:4.For循环4.1for循环的格式4.2for-else循环的格式4.3for循环实例4.4for-循环中的break和continue4.5if 的各种真假判断4.6range()函数的使用01.初识1.认识python python之父——吉多·范罗苏姆1989年,为了打发无聊的圣诞节假期,Guido开始用C语言写Python语言的编译器Python的意思是蟒蛇,源于作者喜欢的一部电视剧。1991年,第一个Python编译器诞生。它是用C语言实现的,并能够调用C语言的库文件从一出生,Python已经具有了 :类,函数,异常处理,包含表和词典在内的核心数据类型,以及模块为基础的拓展系统。Python语法很多来自C,但又受到ABC语言的强烈影响。Python的解释器如今有多个语言实现,我们常用的是CPython(官方版本的C语言实现),其他还有Jython(可以运行在Java平台)Python目前有两个版本,Python2.x和Python3.x,最新版分别为2.7.14和3.6.3 2.应用场景:2.1Web应用开发 Python经常被用于Web开发。比如,通过mod_wsgi模块,Apache可以运行用Python编写的Web程序。Python 定义了WSGI (是Python 应用程序或框架和Web服 服务器之间的一种接口)标准应用接口来协调Http 服务器与基于Python 的Web 程序之间的通信 2.2自动化运维在很多操作系统里,Python是标准的系统组件。 大多数Linux发行版以及ubuntu、CentOS、NetBSD、OpenBSD和Mac OS X都集成了Python,可以在终端下直接运行Python。有一些Linux发行版的安装器使用Python语言编写,比如Ubuntu的Ubiquity安装器,Red HatLinux的Anaconda安装器。Python标准库包含了多个调用操作系统功能的库。通过pywin32这个第三方软件包,Python能够访问Windows的COM服务及其它Windows API。Python编写的系统管理脚本在可读性、性能、代码重用度、扩展性几方面都优于普通的shell脚本。2.3科学计算NumPy,SciPy,Matplotlib可以让Python程序员编写科学计算程序。2.4桌面软件PyQt、PySide、wxPython、PyGTK是Python快速开发桌面应用程序的利器2.5服务器软件Python对于各种网络协议的支持很完善,因此经常被用于编写 服务器软件、网络爬虫。第三方库Twisted[ˈtwɪstɪd] 支持异步网络编程和多数标准的网络协议(包含客户端和服务器),并且提供了多种工具,被广泛用于编写高性能的服务器软件。2.6游戏很多游戏使用C++编写图形显示等高性能模块,而使用Python或者Lua编写游戏的逻辑、服务器。相较于Python,Lua的功能更简单、体积更小;而Python则支持更多的特性和数据类型2.7产品早期原型和迭代饿了么、小米、瓜子、Ucloud、360、腾讯、阿里巴巴、陌陌、美团和知乎 , YouTube、Google、Yahoo!、NASA都在内部大量地使用Python。2.8人工智能 pyDatalog:Python中的逻辑编程引擎。SimpleAI:Python实现在“人工智能:一种现代的方法”这本书中描述过的人工智能的算法。它专注于提供一个易于使用,有良好文档和测试的库。EasyAI:一个双人AI游戏的python引擎(负极大值,置换表、游戏解决) 2.9数据分析 1、python下的数据分析模块pandas: :主要用于数据分析,数据 预处理以及基本的作图,这个包不涉及复杂的模型。2、python的数据可视化最常用的matplotlib,用于科学制图——基础的绘图,已经集成在pandas里。3、Python的爬虫Scrapy :Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。。用这个框架可以轻松爬下来如亚马逊商品信息之类的数据。 3.开发环境搭建3.1python2 在大多数Linux系统上都已经有集成好了Python开发环境,不需要安装就可以执行Python代码了验证Python2环境是否搭建好Ctrl + Alt + t 打开终端输入命令:python退出Python2环境命令:exit()或者Ctrl+d 3.2python3验证Python3环境是否搭建好Ctrl + Alt + t 打开终端输入命令:pythonpython3安装过程前期准备工作:安装依赖yum install -y openssl-devel openssl-static zlib-devel lzma tk-devel xz-devel bzip2-devel ncurses-devel gdbm-devel readline-devel sqlite-devel gcc libffi-devel 1.上传压缩包 rz2.解压到指定位置 tar -zxf Python-3.7.6.tgz -C /opt/app/3.执行配置文件./configure4.编译安装make && make install5.设置软链接ln -s /opt/app/Python-3.7.6/python /usr/bin/python34.python程序4.1Python执行过程解析一个用编译性语言比如C或C++写的程序可以从源文件(即C或C++语言)转换到一个你的计算机使用的语言(二进制代码,即0和1)。这个过程通过编译器和不同的标记、选项完成。当你运行你的程序的时候,连接/转载器软件把你的程序从硬盘复制到内存中并且运行。而Python语言写的程序不需要编译成二进制代码。你可以直接从源代码运行程序。在计算机内部,Python 解释器把源代码转换成称为字节码的中间形式,然后再把它翻译成计算机使用的机器语言并运行。事实上,由于你不再需要担心如何编译程序,如何确保连接转载正确的库等等,所有这一切使得使用Python更加简单。 由于你只需要把你的Python 程序拷贝到另外一台计算机上,它就可以工作了,这也使得你的Python 程序更加易于移植4.2Python解释器。 当我们编写Python代码时,我们得到的是一个包含Python代码的以.py为扩展名的文本文件。要运行代码,就需要Python解释器去执行.py文件。由于整个Python语言从规范到解释器都是开源的,所以理论上,只要水平够高,任何人都可以编写Python解释器来执行Python代码(当然难度很大)。事实上,确实存在多种CPython:当我们从Python官方网站下载并安装好Python 3.5后,我们就直接获得了一个官方版本的解释器: :CPython 。这个解释器是用C 语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。CPython是使用最广的Python解释器。IPython:IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。CPython用>>>作为提示符,而IPython用In [序号]:作为提示符。PyPy:PyPy是另一个Python解释器, 它的目标是执行速度。PyPy采 用JIT 技术,对Python代码 进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解PyPy和CPython的不同点。Jython:Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。IronPython:IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。小结:Python的解释器很多,但使用最广泛的还是CPython。如果要和Java或.Net平台交互,最好的办法不是用Jython或IronPython,而是通过网络调用来交互,确保各程序之间的独立性。 4.3使用交互模式写Python程序 第一种通过终端输入python或python3进入终端交互模式第二种通过编辑器,如vim编写Python程序 4.4在文件的头部配置Python解释器 在代码第一行写入执行时的Python解释器路径,编辑完后需要对此Python文件添加'x'权限编辑完成后正常情况下,我们会给.py文件添加可执行权限 5.Linux交互模式IPython5.1Ipython简介IPython 是一个 python 的交互式 shell,比默认的python shell 好用得多,支持变量自动补全,自动缩进,支持bash shell 命令,内置了许多很有用的功能和函数。5.2开启Python3交互模式(必须联网) 更新pip版本命令:pip3 install --upgrade pip安装命令:pip3 install ipython (需要切换到root用户进行安装)总结:ipython3的好处是可以命令自动补全,还可以执行Linux部分命令,以后测试一些小案例用交互模式要多一些。 02.基础一1注释注释的作用:以#开头,#右边的所有东西当做说明,而不是真正要执行的程序,起辅助说明作用注释的分类单行注释 多行注释让Python2程序支持中文 utf-8,又称万国码,支持中文,日文,俄文等用在网页上可以统一页面显示中文简体繁体及其它语言(如英文,日文,韩文)格式 : # -*- coding:utf-8 -*- 或者 #coding=utf-8 2.变量以及数据类型2.1变量 变量就是用来存东西的,在Python中,存储一个数据,需要一个叫做变量的东西程序就是用来处理数据的,而变量就是用来存储数据的变量起名要有意义程序中为了更充分的利用内存空间以及更有效率的管理内存,变量是有不同的类型的,如下所示: 2.2标识符和关键字标识符命名规则: 1.标识符的组成:标识符由字母、下划线和数字组成,且数字不能开头,且不能是关键字2.见名知意;标识符是区分大小写的2.驼峰命名法 小驼峰命名法: ※ 第一个单词小写,后面的每个单词首字母大写,如:userName 、 myName大驼峰命名法: ※ 每个单词的首字母都大写,如:UserName 、 FirstName 、 LastName ※ python中推荐的是用下划线"_" 来连接所有单词,如:user_name / my_name 关键字 Python一些具有特殊功能的标识符,这就是所谓的关键字,是Python已经使用的了,所以不允许开发者自己定义和关键字相同的名字的标识符可以通过以下命令进行查看当前系统中Python的关键字在交互模式下:import keywordkeyword.kwlist 3.输入函数3.1Python2中在Python中,获取键盘输入的数据的方法是采用 raw_input 函数,看如下示例:#-*-coding:utf-8-*- passwd = raw_input(" 请输入密码:") 输出数据print( " 您刚刚输入的密码是:%s" %passwd )input( )函数与raw_input( )类似,但其接受的输入必须是表达式。a=1 b=2 c= input(“请输入:”)3.2Python3版本 输入格式python3版本中没有raw_input()函数,只有input()并且 python3中的input与python2中的raw_input()功能一样 c=input("请输入:") 请输入:123 c=int(input("请输入要计算的数:")) 输入要计算的数 Python中的输出格式化输出在程序中,看到了%这样的操作符,这就是Python中格式化输出。 age = 28 name = " 阿福" print(" 我的姓名是%s, 年龄是%d"%(name,age),end=”\t”)格式化符号 格式符号 转换 %c 字符 %s 通过str() 字符串转换来格式化 %d 有符号十进制整数 %f 浮点实数 运算符 运算符 描述 实例 + 加 两个对象相加 a + b 输出结果 7 - 减 得到负数或是一个数减去另一个数 a - b 输出结果 3 * 乘 两个数相乘或是返回一个被重复若干次的字符串 a * b 输出结果10 / 除 x除以y a/ b 输出结果 2,python2中是2,python3中是2.5 // 取整除 取商,5//2得2;返回商的整数部分 9//2 输出结果 4 。 % 取余 返回除法的余数 a % b 输出结果 1 ** 幂 返回x的y次幂 a**b 为5的2次方, 输出结果 25 = 赋值运算符 把=号右边的结果给左边的变量 num=1+2*3 结果num的值为7 += 加法赋值运算符 c += a 等效于 c = c + a -= 减法赋值运算符 c -= a 等效于 c = c - a *= 乘法赋值运算符 c *= a 等效于 c = c * a /= 除法赋值运算符 c /= a 等效于 c = c / a %= 取模赋值运算符 c %= a 等效于 c = c % a **= 幂赋值运算符 c **= a 等效于 c = c ** a //= 取整除赋值运算符 c //= a 等效于 c = c // a 03.基础二1.If判断语句小总结:如果某些条件满足,才能做某件事情,而不满足时不允许做,这就是所谓的判断不仅生活中有,在软件开发中“判断”功能也经常会用到1.1if判断语句语法 if语句是用来进行判断的,其使用格式如下:if要判断的条件: 条件成立时,要做的事情 1.2if 语句的应用age = 18 print("------if 判断开始------") if age >= 18: print(" 我已经成年了,可以做成年人做的事情了") print("------if 判断结束------")注意:代码的缩进为一个tab 键,或者4 个空格1.3练一练 要求:从键盘获取自己的年龄,判断是否大于或者等于18岁,如果满足就输出“已成年,可以承担法律责任”1. 使用input从键盘中获取数据,并且存入到一个变量中2. 使用if语句,来判断 age>=18是否成立3. 支持Python2和Python3都能正常运行 age=int(input("请输入你的年龄")) if age >=18: print("已成年,可以承担法律责任") else: print("年龄不够滚蛋!!!")1.1.运算符1.1.1逻辑(关系) 运算符 运算符 逻辑表达式 描述 and x and y 布尔"与" - 如果 x 为 False,x and y 返回False,否则它返回 y 的计算值。 or x or y 布尔"或" - 如果 x 是 True,它返回 True,否则它返回 y 的计算值。 not not x 布尔"非" - 如果 x 为 True,返回 False 。如果 x 为 False,它返回 True。 1.1.2比较(关系)运算符 运算符 描述 示例 == 检查两个操作数的值是否相等,如果是则条件变为真。 如a=3,b=3则(a == b)为 True. != 检查两个操作数的值是否相等,如果值不相等,则条件变为真。 如a=1,b=3则(a != b)为 True. <> 检查两个操作数的值是否相等,如果值不相等,则条件变为真。Python和在Pascal等特有方式,java和c没有,在Python3中废弃了 如a=1,b=3则(a <> b True。这个类似于 !=运算符 > 检查左操作数的值是否大于右操作数的值,如果是,则条件成立。 如a=7,b=3则(a > b) 为 True. < 检查左操作数的值是否小于右操作数的值,如果是,则条件成立。 如a=7,b=3则(a < b) 为 False. >= 检查左操作数的值是否大于或等于右操作数的值,如果是,则条件成立。 如a=3,b=3则(a >= b)为True. <= 检查左操作数的值是否小于或等于右操作数的值,如果是,则条件成立。 如a=3,b=3则(a <= b) 为 True. 2.If-else2.1if-else的使用格式if 条件: 满足条件时要做的事情1 满足条件时要做的事情2 满足条件时要做的事情3 ...(省略)... else: 不满足条件时要做的事情1 不满足条件时要做的事情2 不满足条件时要做的事情3 ...(省略)...#去办理个人贷款买房手续,只需要你或者你媳妇去 you = input("你本人去吗?(去或者不去):") yourWife = input("你媳妇去吗?(去或者不去):") if you == "去" or yourWife == "去": print("恭喜,至少有一人前来,可以办理!") else: print("必须有一人前来办理!")2.2elif的格式if xxx1: 事情1 elif xxx2: 事情2 elif xxx3: 事情3""" 判断年龄属于哪个时期 0-3 婴幼儿期 3-12 儿童期 12-17 青春期 18-24 青年期 25-44 壮年期 45-60 中年期 60-100 老年期 100-$ 修仙期 """ age = int(input("请输入年龄:")) if age >0 and age <=3: print("婴幼儿期") elif age>3 and age <=12: print("属于儿童期") elif age>12 and age <=17: print("属于青春期") elif age>17 and age <=24: print("属于青年期") elif age>24 and age <=44: print("属于壮年期") elif age>44 and age <=60: print("属于中年期") elif age>60 and age <=100: print("属于儿老年期") elif age>100: print("属于修仙期!") else: print("这个世界你从未来过!")注意:elif 必须和if 一起使用,否则出错2.3If嵌套if 条件1: 满足条件1 做的事情1 满足条件1 做的事情2 ...(省略)... if 条件2: 满足条件2 做的事情1 满足条件2 做的事情2 ...(省略)... else: 不满足条件2做的事情1 不满足条件2做的事情2 ...(省略)...2.4猜丁壳游戏 游戏要求:用户先出,和系统所出进行比较。import random """ 0代表剪刀 1代表石头 2代表布 """ player = int(input("请输入 剪刀(0) 石头(1)布(2):")) computer = random.randint(0,2) print(player,computer) if player<=2 and player>=0: if (player==0 and computer == 2) or (player==1 and computer==0) or (player==2 and computer==1): print("你赢了") elif player==computer : print("平手") else: print("你输了") else: print("输入内容错误,请重新输入")3.while循环3.1Whlie循环的书写方式:num = 1 while num <= 10: print(num) num += 13.2while循环的格式while 条件: 条件满足时,做的事情1 条件满足时,做的事情2 条件满足时,做的事情3 ...(省略)...3.3while循环注意事项:i=i+1别忘记写,否则条件永远满足,一直执行3.4while练习: 计算 1~100 之间偶数的累积和(包含1 1 和 100 ) i = 1 sum = 0 while i<=100: if i % 2 ==0: sum = sum+i i+=1 sum print("1-100的累计和为:%s"%sum)while嵌套的格式while 条件1: 条件1满足时,做的事情1 条件1满足时,做的事情2 条件1满足时,做的事情3 ...(省略)... while 条件2: 条件2满足时,做的事情1 条件2满足时,做的事情2 条件2满足时,做的事情3用while打印下边的形状 * ** *** **** ***** i=1 while i<=5: j=1 while j<=i: print("*",end=" ") j+=1 print() i+=1 #外层循环执行一次,内层循环执行一遍。4.For循环4.1for循环的格式for 临时变量 in 列表或者字符串等: 循环满足条件时执行的代码 else: 循环不满足条件时执行的代码4.2for-else循环的格式name = '' for x in name: print(x) else: print(" 没有数据")4.3for循环实例for i in range(6): print("* "*i) for i in range(4,0,-1): print("* "*i) print("="*50) i=1 while i<=9: if i<=5: print("* "*i) else: print("* "*(10-i)) i+=14.4for-循环中的break和continue break:遇到它跳出整个循环(结束循环),如果是循环嵌套,break在内循环,退出的是内循环continue:遇到它跳出本次循环,紧接着执行下一次的循环 i = 0 while i<10: print("----") if i ==5: #continue #作用:跳过本次循环,执行下一次循环 break #作用:用来结束整个循环 i = i + 1 print(i)4.5if 的各种真假判断编辑编辑 数字0表示假非0数表示真if xxx==YYY:看是否相等,相对就是True,否则就是Falseif xxxx : 看运算结果 4.6range()函数的使用 函数语法range(start, stop[, step])参数说明: •start: 计数从 start 开始。默认是从 0 开始。例如range(5)等价于range(0, 5); •stop: 计数到 stop 结束,但不包括 stop。例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5 •step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1) >>> range (0,10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> range (1,5) [1, 2, 3, 4] >>> range (0,30,5) [0, 5, 10, 15, 20, 25] >>> range (0,-10,-1) [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]👑👑👑结束语👑👑👑为大家推荐一款刷题神奇 点击链接访问牛客网各大互联网大厂面试真题。基础题库到进阶题库等各类面试题应有尽有!牛客网面经合集,满足大厂面试技术深度,快速构建Java核心知识体系大厂面试官亲授,备战面试与技能提升,主要考点+主流场景+内功提升+真题解析
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道第一名🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!1. 数据库概念 目前,主流数据库包括关系型(SQL)和非关系型(NoSQL)两种。 关系数据库是建立在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据,支持复杂的事物处理和结构化查询。代表实现有MySQL、Oracle、PostGreSQL、MariaDB、 SQLServer等。 非关系数据库是新兴的数据库技术,它放弃了传统关系型数据库的部分强一致性限制,带来性能上的提升,使其更适用于需要大规模并行处理的场景。非关系型数据库是关系型数据库的良好补充,代表产品有MongoDB、Redis、CouchDB等。 本篇文章选取了最具代表性的Mysql来讲解基于Docker创建相关镜像并进行应用的过程。 2. 认识MySQL MySQL是全球最流行的开源的开源关系数据库软件之一,因为其高性能、成熟可靠和适应性而得到广泛应用。MySQL目前在不少大规模网站和应用中被使用,比如 Facebook、Twitter和Yahoo!等。 MySQL数据库是一种关系型数据库管理系统,是一种开源软件由瑞典MySQL AB公司开发,08年1月16日被Sun公司收购,09年Sun公司又被Oracle公司收购。由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,许多中小型网站为了降低网站总体拥有成本而选择了MySQL作为网站数据库。MySQL属于轻量级数据库,也就是小中型数据库 MySQL数据库优点: 1,成本:MySQL是免费的,并且它的技术支持也很便宜; 2,速度:MySQL胜过它的大多数竟争对手功能; 3,MySQL提供了开发人员所需要的大多数功能; 4,可移植:MySQL可以在绝大多数的操作系统中运行,易于使用和管理。 3. 在Docker中运行MySQL 使用官方镜像可以快速启动一个MySQL Server实例,如下所示: $ docker run --name hi-mysql -e MYSQL_ROOT_PASSWORD=123 -d mysql:latest e6cb906570549812c798b7b3ce46d669a8a4e8ac62a3f3c8997e4c53d16301b6 以上指令中的hi-mysql是容器名称,123为数据库的root密码。使用docker ps指令可以看到现在运行中的容器: $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e6cb90657054 mysql "docker-entrypoint.sh" 4 minutes ago Up 3 minutes 3306/tcp hi-mysql 当然,还可以使用--link标签将一个应用容器连接至MySQL容器: $ docker run --name some-app --link some-mysql:mysql -d application-that-uses-mysql MySQL服务的标准端口是3306,用户可以通过CLI工具对配置进行修改: $ docker run -it --link some-mysql:mysql --rm mysql sh -c 'exec mysql -h"$MYSQL_ PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p"$MYSQL_ENV_MYSQL_ ROOT_PASSWORD"' 官方MySQL镜像还可以作为客户端,连接非Docker或者远程的MySQL实例: $ docker run -it --rm mysql mysql -hsome.mysql.host -usome-mysql-user -p4. 系统与日志访问 用户可以使用docker exec指令调用内部系统中的bash shell,以访问容器内部系统: $ docker exec -it some-mysql bash MySQL Server日志可以使用docker logs指令查看: $ docker logs some-mysql5. 使用自定义配置文件 如果用户希望使用自定义MySQL配置,则可以创建一个目录,内置cnf配置文件,然后将其挂载至容器的/etc/mysql/conf.d目录。比如,自定义配置文件 为/my/custom/config-file.cnf,则可以使用以下指令: $ docker run --name some-mysql -v /my/custom:/etc/mysql/conf.d -e MYSQL_ROOT_ PASSWORD=my-secret-pw -d mysql:tag 这是新的容器some-mysql启动后,就会结合使用/etc/mysql/my.cnf和/etc/mysql/conf.d/config-file.cnf两个配置文件。 6. 脱离cnf文件进行配置 很多的配置选项可以通过标签(flags)传递至mysqld进程。这样用户就可以脱离cnf配置文件,对容器进行弹性的定制。比如,用户需要改变默认编码方式,将所有表格的编码方式修改为uft8mb4,则可以使用以下指令: $ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci 如果需要查看可用选项的完整列表,可以执行: $ docker run -it --rm mysql:tag --verbose --help 👑👑👑结束语👑👑👑
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道第一名🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. 关于Tomcat2. 准备工作 3. Dockerfile文件和其他脚本文件 4. 创建和测试镜像 👑👑👑结束语👑👑👑1. 关于Tomcat Tomcat是由Apache软件基金会下属的Jakarta项目开发的一个Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和Java Server Page(JSP)的支持。同时,它提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全域管理和Tomcat阀等。由于Tomcat本身也内含了一个HTTP服务器,也可以当作一个单独的Web服务器来使用。下面介绍如何定制Tomcat镜像。 首先,尝试在Docker Hub上搜索已有的Tomcat相关镜像的个数: $ docker search tomcat | wc -l 285 可以看到,已经有285个相关镜像。如是个人开发或测试,可以随意选择一个镜像,按照提示启动应用即可。 下面以Tomcat 7.0为例介绍定制Tomcat镜像的步骤。 2. 准备工作 创建tomcat7.0_jdk1.6文件夹,从www.oracle.com网站上下载sun_jdk1.6压缩包,解压为jdk目录 创建Dockerfile和run.sh文件: $ mkdir tomcat7.0_jdk1.6 $ cd tomcat7.0_jdk1.6/ $ touch Dockerfile run.sh 下载Tomcat,可以到官方网站下载最新的版本,也可以直接使用下面链接中给出的版本: $ wget http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7.0.56/bin/apache-tomcat- 7.0.56.zip 解压后,tomcat7.0_jdk1.6目录结构应如下所示(多余的压缩包文件已经被删除): $ ls Dockerfile apache-tomcat-7.0.56 jdk run.sh3. Dockerfile文件和其他脚本文件 Dockerfile文件内容如下: FROM sshd:Dockerfile #设置继承自用户创建的sshd镜像 MAINTAINER docker_user (user@docker.com) #下面是一些创建者的基本信息 #设置环境变量,所有操作都是非交互式的 ENV DEBIAN_FRONTEND noninteractive #注意这里要更改系统的时区设置 RUN echo "Asia/Shanghai" > /etc/timezone && \ dpkg-reconfigure -f noninteractive tzdata #安装跟tomcat用户认证相关的软件 RUN apt-get install -yq --no-install-recommends wget pwgen ca-certificates && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* #设置tomcat的环境变量,若读者有其他的环境变量需要设置,也可以在这里添加。 ENV CATALINA_HOME /tomcat ENV JAVA_HOME /jdk #复制tomcat和jdk文件到镜像中 ADD apache-tomcat-7.0.56 /tomcat ADD jdk /jdk ADD create_tomcat_admin_user.sh /create_tomcat_admin_user.sh ADD run.sh /run.sh RUN chmod +x /*.sh RUN chmod +x /tomcat/bin/*.sh EXPOSE 8080 CMD ["/run.sh"] 创建tomcat用户和密码脚本文件create_tomcat_admin_user.sh文件,内容为: #!/bin/bash if [ -f /.tomcat_admin_created ]; then echo "Tomcat 'admin' user already created" exit 0 fi #generate password PASS=${TOMCAT_PASS:-$(pwgen -s 12 1)} _word=$( [ ${TOMCAT_PASS} ] && echo "preset" || echo "random" ) echo "=> Creating and admin user with a ${_word} password in Tomcat" sed -i -r 's/<\/tomcat-users>//' ${CATALINA_HOME}/conf/tomcat-users.xml echo '<role rolename="manager-gui"/>' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo '<role rolename="manager-script"/>' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo '<role rolename="manager-jmx"/>' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo '<role rolename="admin-gui"/>' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo '<role rolename="admin-script"/>' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo "<user username=\"admin\" password=\"${PASS}\" roles=\"manager-gui,manager- script,manager-jmx,admin-gui, admin-script\"/>" >> ${CATALINA_HOME}/conf/ tomcat-users.xml echo '</tomcat-users>' >> ${CATALINA_HOME}/conf/tomcat-users.xml echo "=> Done!" touch /.tomcat_admin_created echo "========================================================================" echo "You can now configure to this Tomcat server using:" echo "" echo " admin:${PASS}" echo "" echo "========================================================================" 编写run.sh脚本文件,内容为: #!/bin/bash if [ ! -f /.tomcat_admin_created ]; then /create_tomcat_admin_user.sh fi /usr/sbin/sshd -D & exec ${CATALINA_HOME}/bin/catalina.sh run4. 创建和测试镜像 通过下面的命令创建镜像tomcat7.0:jdk1.6: $ docker build -t tomcat7.0:jdk1.6 . 启动一个tomcat容器进行测试: $ docker run -d -P tomcat7.0:jdk1.6 3cd4238cb32a713a3a1c29d93fbfc80cba150653b5eb8bd7629bee957e7378ed 通过docker logs得到tomcat的密码aBwN0CNCPckw: $ docker logs 3cd => Creating and admin user with a random password in Tomcat => Done! ======================================================================== You can now configure to this Tomcat server using: admin:aBwN0CNCPckw 查看映射的端口信息: $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3cd4238cb32a tomcat7.0:jdk1.6 "/run.sh" 4 seconds ago Up 3 seconds 0.0.0.0:49157->22/tcp, 0.0.0.0:49158->8080/tcp cranky_wright 在本地使用浏览器登录Tomcat管理界面,访问本地的49158端口,即http://127.0.0.1:49158,如图 输入从docker logs中得到的密码,如图所示。 成功进入管理界面,如图 在生产环境中,可以通过使用-v参数来挂载Tomcat的日志文件、程序所在目录、以及与Tomcat相关的配置。 👑👑👑结束语👑👑👑为大家推荐一款刷题神奇 点击链接访问牛客网各大互联网大厂面试真题。基础题库到进阶题库等各类面试题应有尽有!牛客网面经合集,满足大厂面试技术深度,快速构建Java核心知识体系大厂面试官亲授,备战面试与技能提升,主要考点+主流场景+内功提升+真题解析
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道第一名🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. Nginx介绍 2. 为什么Nginx比其他web服务器并发高 2.1 进程管理上的区别 2.2 网络IO模型的选择 2.3 进程的阻塞方式的区别 2.4 模块开发方向不同 2.5 Apache & Nginx差异对比3. nginx配置文件详解4.Nginx工作模式4.1 master-worker: 4.2 单进程模式: 5. Docker中运行Nginx实验5.1 使用官方镜像 5.2 自定义Web页面 👑👑👑结束语👑👑👑1. Nginx介绍 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点开发的,第一个公开版本0.1.0发布于2004年10月4日。Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是内存利用率高,并发能力强。 2. 为什么Nginx比其他web服务器并发高 处理过程:每进来一个request,会有一个worker进程去处理。但不是全程的处理,处理到可能发生阻塞的地方。比如向后端服务器转发request,并等待请求返回。那么,这个处理的worker不会这么傻等着,他会在发送完请求后,注册一个事件:“如果后端服务器返回了,告诉我一声,我再接着干”。于是他就休息去了。此时,如果再有新的request 进来,他就可以很快再按这种方式处理。而一旦后端服务器返回了,就会触发这个事件,worker才会来接手,这个request才会接着往下走。通过这种快速处理,快速释放请求的方式,达到同样的配置可以处理更大并发量的目的。 2.1 进程管理上的区别 Apache: 默认采用的是一个主进程 多个工作进程 每个工作进程管理一个线程 每 个线程管理一个连接 并发数 = 工作进程数 x 1Nginx: 一个主进程 多个工作进程 每个工作进程管理多个线程(最大到65535) 并发数 = 工作进程数 x 单进程开启的线程数 淘宝等电商用的web浏览器 Tengine ,相当与Nginx的换皮,通过nginx开源项目针对电商优化的产品 2.2 网络IO模型的选择 Apache: select模型 select就是一个简单的选择模型(如排队请求网络资源,第一个人阻塞住第二个人依然要排着)Nginx: epoll模型 epoll更智能的网络管理模型(如排队第一个人阻塞住,会先把第二个人网络的 IO 请求提交出来) 2.3 进程的阻塞方式的区别 Apache:同步 阻塞型Nginx:异步 非阻塞型 2.4 模块开发方向不同 Apache:安全模块众多Nginx:高性能模块众多 2.5 Apache & Nginx差异对比 Apache Nginx 配置繁琐 配置相对简单 原生支持动态和静态页面 支持静态页面 模块相对安全 高性能模块出产迅速、社区活跃 BUG 相对较少,消耗资源较多 BUG相对较多,节省资源 对加密支持较好 对反向代理支持较好 同步阻塞型应用 异步非阻塞型应用 3. nginx配置文件详解user www www; #程序运行用户和组 worker_processes auto; #启动进程,指定nginx启动的工作进程数量,建议按照cpu数目来指定,一般等于cpu核心数目 error_log /home/wwwlogs/nginx_error.log crit; #全局错误日志 pid /usr/local/nginx/logs/nginx.pid; #主进程PID保存文件 worker_rlimit_nofile 51200; #文件描述符数量 events { use epoll; #使用epoll模型,对于2.6以上的内核,建议使用epoll模型以提高性能 worker_connections 51200; #工作进程的最大连接数量,根据硬件调整,和前面工作进程配合起来用,尽量大,但是别把cpu跑到100%就行每个进程允许的最多连接数, 理论上每台nginx服务器的最大连接数为worker_processes*worker_connections,具体还要看服务器的硬件、带宽等。 } http #整体环境配置--网站配置 { include mime.types; default_type application/octet-stream; #设定mime类型,文件传送类型由mime.type文件定义 server_names_hash_bucket_size 128; #保存服务器名字的hash表大小 client_header_buffer_size 32k; #客户端请求头部缓冲区大小 large_client_header_buffers 4 32k; #最大客户端头缓冲大小 client_max_body_size 50m; #客户端最大上传文件大小(M) sendfile on; #sendfile 指令指定 nginx 是否调用 sendfile 函数来输出文件,对于普通应用,必须设为on。如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的uptime. tcp_nopush on; #这个是默认的,结果就是数据包不会马上传送出去,等到数据包最大时,一次性的传输出去,这样有助于解决网络堵塞。(只在sendfile on时有效) keepalive_timeout 60; #连接超时时间 tcp_nodelay on; #禁用nagle算法,也即不缓存数据。有效解决网络阻塞 fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; fastcgi_buffer_size 64k; fastcgi_buffers 4 64k; fastcgi_busy_buffers_size 128k; fastcgi_temp_file_write_size 256k; #fastcgi设置 gzip on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_http_version 1.1; gzip_comp_level 2; gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml application xml+rss; gzip_vary on; gzip_proxied expired no-cache no-store private auth; gzip_disable "MSIE [1-6]\."; #limit_conn_zone $binary_remote_addr zone=perip:10m; ##If enable limit_conn_zone,add "limit_conn perip 10;" to server section. server_tokens off; #隐藏nginx版本号(curl -I 192.168.4.154可以查看,更加安全) #log format log_format access '$remote_addr - $remote_user [$time_local] "$request"' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" $http_x_forwarded_for'; #定义日志格式 server { listen 80 default_server; #listen [::]:80 default_server ipv6only=on; #监听80端口,WEB服务的监听设置,可以采用"IP地址:端口"形式 server_name www.lnmp.org lnmp.org; #服务器名,可以写多个域名,用空格分隔 index index.html index.htm index.php; #默认网页文件 root /home/wwwroot/default; #网页主目录 #error_page 404 /404.html; include enable-php.conf; location /nginx_status { stub_status on; access_log off; } #开启status状态监测 location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ { expires 30d; } #静态文件处理,保存期30天 location ~ .*\.(js|css)?$ { expires 12h; } #js和css文件处理,保存期12小时 location ~ /\. { deny all; } access_log /home/wwwlogs/access.log access; #正确访问日志 } include vhost/*.conf; #vhost/下子配置文件生效 }4.Nginx工作模式 nginx有两种工作模式:master-worker模式和单进程模式。在master-worker模式下,有一个master进程和至少一个的worker进程,单进程模式顾名思义只有一个进程。这两种模式有各自的特点和适用场景。 4.1 master-worker: 该模式下,nginx启动成功后,会有一个master进程和至少一个的worker进程。master进程负责处理系统信号,加载配置,管理worker进程(启动,杀死,监控,发送消息/信号等)。worker进程负责处理具体的业务逻辑,也就是说,对外部来说,真正提供服务的是worker进程。生产环境下一般使用这种模式,因为这种模式有以下优点:1. 稳定性高,只要还有worker进程存活,就能够提供服务,并且一个worker进程挂掉master进程会立即启动一个新的worker进程,保证worker进程数量不变,降低服务中断的概率。2. 配合linux的cpu亲和性配置,可以充分利用多核cpu的优势,提升性能3. 处理信号/配置重新加载/升级时可以做到尽可能少或者不中断服务(热重启) 4.2 单进程模式: 单进程模式下,nginx启动后只有一个进程,nginx的所有工作都由这个进程负责。由于只有一个进程,因此可以很方便地利用gdb等工具进行调试。该模式不支持nginx的平滑升级功能,任何的信号处理都可能造成服务中断,并且由于是单进程,进程挂掉后,在没有外部监控的情况下,无法重启服务。因此,该模式一般只在开发阶段和调试时使用,生产环境下不会使用。(了解) 5. Docker中运行Nginx实验5.1 使用官方镜像 用户可以使用docker run指令直接运行官方Nginx镜像: $dockerrun-d-p80:80--namewebservernginx 34bcd01998a76f67b1b9e6abe5b7db5e685af325d6fafb1acd0ce84e81e71e5d 然后使用docker ps指令查看当前运行的docker ps指令查看当前运行容器: $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 34bcd01998a7 nginx " nginx..." 2min ago Up 0.0.0.0:80->80/tcp, 443/tcp webserver 目前Nginx容器已经在0.0.0.0:80启动,并映射了80端口,此时可以打开浏览器访问此地址,就可以看到Nginx输出的页面,如图: 5.2 自定义Web页面 同样的,创建index.html文件,并将index.html文件挂载至容器中,即可看到显示自定义的页面。 $ docker run --name nginx-container -p 80:80 -v index.html:/usr/share/nginx/ html:ro -d nginx 另外,也可以使用Dockerfile来构建新镜像。Dockerfile内容如下: FROM nginx COPY ./index.html /usr/share/nginx/html 开始构建镜像my-nginx: $ docker build -t my-nginx . 构建成功后执行docker run指令: $ docker run --name nginx-container -d my-nginx 👑👑👑结束语👑👑👑为大家推荐一款刷题神奇 点击链接访问牛客网各大互联网大厂面试真题。基础题库到进阶题库等各类面试题应有尽有!牛客网面经合集,满足大厂面试技术深度,快速构建Java核心知识体系大厂面试官亲授,备战面试与技能提升,主要考点+主流场景+内功提升+真题解析
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道第一名🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. Docker运行Apache实战 1.1 Apache介绍1.2 使用官方镜像 1.3 使用自定义镜像 👑👑👑结束语👑👑👑 Web服务和应用是目前信息技术领域的热门技术。本篇文章将重点介绍如何使用Docker来运行常见的Web服务器(包括 Apache、Nginx、Tomcat等) 本篇文章会展示两种创建镜像的过程。其中一些操作比较简单的镜像使用Dockerfile来创建,而像Weblogic这样复杂的应用,则使用commit方式来创建,读者可根据自己的需求进行选择。1. Docker运行Apache实战 1.1 Apache介绍 Apache是一个高稳定性的、商业级别的开源Web服务器。目前Apache已经是世界使用排名第一的Web服务器软件。由于其良好的跨平台和安全性,Apache被广泛应用在多种平台和操作系统上。作为Apache软件基金会支持的项目,它的开发者社区完善而高效。自1995年发布至今,一直以高标准进行维护与开发。Apache名称源自美国的西南部一个印第安人部落:阿帕奇族,它支持类UNIX和Windows系统 1.2 使用官方镜像 官方提供了名为httpd的Apache镜像,可以作为基础Web服务镜像。编写Dockerfile文件,内容如下: FROM httpd:2.4 COPY ./public-html /usr/local/apache2/htdocs/ 创建项目目录public-html,并在此目录下创建index.html文件: <!DOCTYPE html> <html> <body> <p>Hello, Docker!</p> </body> </html> 构建自定义镜像:$ docker build -t apache2-image . 构建完成后,使用docker run指令运行镜像: $ docker run -it --rm --name apache-container -p 80:80 apache2-image 通过本地的80端口即可访问静态页面,如图所示。 也可以不创建自定义镜像,直接通过映射目录方式运行Apache容器: $ docker run -it --rm --name my-apache-app -p 80:80 -v "$PWD":/usr/local/ apache2/htdocs/ httpd:2.4 再次打开浏览器,可以再次看到页面输出。 1.3 使用自定义镜像 首先,创建一个apache_ubuntu工作目录,在其中创建Dockerfile文件、run.sh文件和sample目录: $ mkdir apache_ubuntu $ cd apache_ubuntu $ touch Dockerfile run.sh $ mkdir sample 下面是Dockerfile的内容和各个部分的说明#设置环境变量,所有操作都是非交互式的 ENVDEBIAN_FRONTENDnoninteractive #安装 RUNapt-get-yqinstallapache2&&\ rm-rf/var/lib/apt/lists/* RUNecho"Asia/Shanghai">/etc/timezone&&\ dpkg-reconfigure-fnoninteractivetzdata #注意这里要更改系统的时区设置,因为在web应用中经常会用到时区这个系统变量,默认的ubuntu ?会让你的应用程序发生不可思议的效果哦 #添加用户的脚本,并设置权限,这会覆盖之前放在这个位置的脚本 ADDrun.sh/run.sh RUNchmod755/*.sh #添加一个示例的web站点,删掉默认安装在apache文件夹下面的文件,并将用户添加的示例用软链接 ?链到/var/www/html目录下面 RUNmkdir-p/var/lock/apache2&&mkdir-p/app&&rm-fr/var/www/html&&ln-s /app/var/www/html COPYsample//app #设置apache相关的一些变量,在容器启动的时候可以使用-e参数替代 ENVAPACHE_RUN_USERwww-data ENVAPACHE_RUN_GROUPwww-data ENVAPACHE_LOG_DIR/var/log/apache2 ENVAPACHE_PID_FILE/var/run/apache2.pid ENVAPACHE_RUN_DIR/var/run/apache2 ENVAPACHE_LOCK_DIR/var/lock/apache2 ENVAPACHE_SERVERADMINadmin@localhost ENVAPACHE_SERVERNAMElocalhost ENVAPACHE_SERVERALIASdocker.localhost ENVAPACHE_DOCUMENTROOT/var/www EXPOSE80 WORKDIR/app CMD["/run.sh"] 此sample站点的内容为输出Hello Docker!。然后在sample目录下创建index.html文件,内容如下: <!DOCTYPEhtml> <html> <body> <p>Hello, Docker!</p> <body> <html> run.sh脚本内容也很简单,只是启动apache服务:$ cat run.sh #! /bin/bash exec apache2 -D FOREGROUND 此时,apache_ubuntu目录下面的文件结构为: $ tree . . |-- Dockerfile |-- run.sh `-- sample `-- index.html 1 directory, 3 files 下面,用户开始创建apache:ubuntu镜像: 使用docker build命令创建apache:ubuntu镜像,注意命令最后的“.”。 $ docker build -t apache:ubuntu . 下面开始使用docker run指令测试镜像。可以使用-P参数映射需要开放的端口(22和80端口): $ docker run -d -P apache:ubuntu 64681e2ae943f18eae9f599dbc43b5f44d9090bdca3d8af641d7b371c124acfd $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 64681e2ae943 apache:ubuntu "/run.sh" 2 seconds ago Up 1 seconds 0.0.0.0:49171->22/tcp, 0.0.0.0:49172->80/tcp naughty_poincare 890c04ff8d76 sshd:Dockerfile "/run.sh" 9 hours ago Exited (0) 3 hours ago 0.0.0.0:101->22/tcp high_albattani 3ad7182aa47f sshd:ubuntu "/run.sh" 21 hours ago Exited (0) 3 hours ago 0.0.0.0:100->22/tcp focused_ptolemy 在本地主机上用curl抓取网页来验证刚才创建的sample站点:$ curl 127.0.0.1:49172 Hello Docker! 大家也可以在其他设备上通过访问宿主主机ip:49172来访问sample站点。 不知道大家有没有发现,在apache镜像的Dockerfile中只用EXPOSE定义了对外开放的80端口,而在docker ps-a命令的返回中,却看到新启动的容器映射了两个端口:22和80。 但是实际上,当尝试使用SSH登录到容器时,会发现无法登录。这是因为在run.sh脚本中并未启动SSH服务。这说明在使用Dockerfile创建镜像时,会继承父镜像的开放端口,但却不会继承启动命令。因此,需要在run.sh脚本中添加启动sshd的服务的命令:$ cat run.sh #!/bin/bash /usr/sbin/sshd & exec apache2 -D FOREGROUND 再次创建镜像: $ docker build -t apache:ubuntu . 这次创建的镜像,将默认会同时启动SSH和Apache服务。 下面,来看看如何映射本地目录。可以通过映射本地目录的方式,来指定容器内Apache服务响应的内容,例如映射本地主机上当前目录下的www目录到容器内 的/var/www目录: $ docker run -i -d -p 80:80 -p 103:22 -e APACHE_SERVERNAME=test -v `pwd`/www:/ var/www:ro apache:ubuntu 在当前目录内创建www目录,并放上自定义的页面index.html,内容如下: <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>Hi Docker</title> </head><body> <h1>Hi Docker</h1> <p>This is the first day I meet the new world.</p> <p>How are you?</p> <hr> <address>Apache/2.4.7 (Ubuntu) Server at 127.0.0.1 Port 80</address> </body></html> 在本地主机上可访问测试容器提供的Web服务,查看获取内容为新配置的index.html页面信息。 👑👑👑结束语👑👑👑为大家推荐一款刷题神奇 点击链接访问牛客网各大互联网大厂面试真题。基础题库到进阶题库等各类面试题应有尽有!牛客网面经合集,满足大厂面试技术深度,快速构建Java核心知识体系大厂面试官亲授,备战面试与技能提升,主要考点+主流场景+内功提升+真题解析
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!1. 网络启动与配置参数2. 配置容器DNS和主机名2.1 相关配置文件2.2 容器内修改配置文件2.3 通过参数指定1. 网络启动与配置参数 Docker启动时会在主机上自动创建一个docker0虚拟网桥,实际上是一个Linux网桥,可以理解为一个软件交换机,它会在挂载其上的接口之间进行转发,如下图所示: 同时,Docker随机分配一个本地未占用的私有网段(在RFC1918中定义)中的一个地址给docker0接口。比如典型的172.17.42.1,掩码为255.255.0.0。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16)的地址。 当创建一个Docker容器的时候,同时会创建了一对veth pair接口。(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即eth0;另一端在本地并被挂载到docker0网桥,名称以veth开头(例如vethAQI2QT)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。如此一来,Docker就创建了在主机和所有容器之间一个虚拟共享网络。 下面是跟Docker网络相关的命令参数。其中有些命令选项只有在Docker服务启动的时候才能配置,而且不能马上生效: -b BRIDGE or--bridge=BRIDGE #指定容器挂载的网桥; --bip=CIDR #定制docker0的掩码; -H SOCKET...or--host=SOCKET... #Docker服务端接收命令的通道; --icc=true|false #是否支持容器之间进行通信; --ip-forward=true|false #启用net.ipv4.ip_forward,即打开转发功能; --iptables=true|false #禁止Docker添加iptables规则; --mtu=BYTES #容器网络中的MTU。 下面2个命令选项既可以在启动服务时指定,也可以Docker容器启动(docker run)时候指定。在Docker服务启动的时候指定则会成为默认值,后续执行docker run时可以覆盖设置的默认值。 --dns=IP_ADDRESS... #使用指定的DNS服务器; --dns-search=DOMAIN... #指定DNS搜索域。 最后这些选项只能在docker run执行时使用,因为它是针对容器的特性内容: -h HOSTNAME or--hostname=HOSTNAME #配置容器主机名; --link=CONTAINER_NAME:ALIAS #添加到另一个容器的连接; --net=bridge|none|container: NAME_or_ID|host|user_defined_network #配置容器的桥接模式; -p SPEC or--publish=SPEC #映射容器端口到宿主主机; -P or--publish-all=true|false #映射容器所有端口到宿主主机。 其中,--net选项支持五种模式,如下所示: --net=bridge #默认配置。为容器创建独立的网络命名空间,分配网卡、IP地址等网络配置,并通过veth接口对将容器挂载到一个虚拟网桥(默认为docker0)上; --net=none #为容器创建独立的网络命名空间,但不进行网络配置,即容器内没有创建网卡、IP地址等; --net=container:NAME_or_ID #意味着新创建的容器共享指定的已存在容器的网络命名空间,两个容器内的网络配置共享,但其他资源(进程空间、文件系统等)还是相互隔离的; --net=host #意味着不为容器创建独立的网络命名空间,容器内看到的网络配置(网卡信息、路由表、Iptables规则等)均与主机上保持一致。注意其他资源还是与主机隔离的; --net=user_defined_network #用户自行用network相关命令创建一个网络,同一个网络内的容器彼此可见,可以采用更多类型的网络插件。2. 配置容器DNS和主机名 Docker支持自定义容器的主机名和DNS配置。 2.1 相关配置文件 实际上,容器中主机名和DNS配置信息都是通过三个系统配置文件来维护的:/etc/resolv.conf、/etc/hostname和/etc/hosts。启动一个容器,在容器中使用mount命令可以看到这三个文件挂载信息: $ docker run -it ubuntu root@75dbd6685305:/# mount ... /dev/mapper/ubuntu--vg-root on /etc/resolv.conf type ext4 (rw,relatime,errors= remount-ro,data=ordered) /dev/mapper/ubuntu--vg-root on /etc/hostname type ext4 (rw,relatime,errors= remount-ro,data=ordered) /dev/mapper/ubuntu--vg-root on /etc/hosts type ext4 (rw,relatime,errors=remount- ro,data=ordered) ... 其中,/etc/resolv.conf文件在创建容器时候,默认会与宿主机/etc/resolv.conf文件内容保持一致: root@75dbd6685305:/# cat /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 8.8.8.8 search my-docker-cloud.com /etc/hosts文件中默认只记录了容器自身的一些地址和名称: root@75dbd6685305:/# cat /etc/hosts 172.17.0.2 75dbd6685305 ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 127.0.0.1 localhost /etc/hostname文件则记录了容器的主机名 root@75dbd6685305:/# cat /etc/hostname 75dbd66853052.2 容器内修改配置文件 Docker 1.2.0开始支持在运行中的容器里直接编 辑/etc/hosts,/etc/hostname和/etc/resolve.conf文件。但是这些修改是临时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来。也不会被docker commit提交。 2.3 通过参数指定 如果用户想要自定义容器的配置,可以在创建或启动容器时利用下面的参数指定:·指定主机名-h HOSTNAME或者--hostname=HOSTNAME。设定容器的主机名,它会被写到容器内的/etc/hostname和/etc/hosts。但这个主机名只有容器内能看到,在容器外部则看不到,既不会在docker ps中显示,也不会在其他的容器的/etc/hosts看到。·记录其他容器主机名--link=CONTAINER_NAME:ALIAS。选项会在创建容器的时候,添加一个所连接容器的主机名到容器内/etc/hosts文件中。这样,新创建容器可以直接使用主机名来与所连接容器通信。·指定DNS服务器--dns=IP_ADDRESS。添加DNS服务器到容器的/etc/resolv.conf中,容器会用指定的服务器来解析所有不在/etc/hosts中的主机名。·指定DNS搜索域--dns-search=DOMAIN。设定容器的搜索域,当设定搜索域为.example.com时,在搜索一个名为host的主机时,DNS不仅搜索 host,还会搜索host.example.com。 👑👑👑结束语👑👑👑为大家推荐一款刷题神奇 点击链接访问牛客网各大互联网大厂面试真题。基础题库到进阶题库等各类面试题应有尽有!牛客网面经合集,满足大厂面试技术深度,快速构建Java核心知识体系大厂面试官亲授,备战面试与技能提升,主要考点+主流场景+内功提升+真题解析
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. 数据卷特性2. docker管理卷(默认挂载)3. 自管理卷(手动挂载)实验4. 容器中的数据卷5.卷的持久化问题6.Docker卷的持久化问题1. 数据卷特性1.1 认识数据卷特性 Docker 镜像由多个只读层叠加而成,启动容器时,Docker 会加载只读镜像层并在镜像栈顶部添加一个读写层> 如果运行中的容器修改了现有的一个已经存在的文件,那么该文件将会从读写层下面的的只读层复制到读写层,该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏,次即“写时复制”机制 1.2 数据卷意义 关闭并重启容器,其数据不受影响;但删除 Docker 容器,则其改变将会全部丢失 > 存在的问题 >> 存在于联合文件系统中,不易于宿主机访问 >> 容器间数据共享不便 >> 删除容器其数据会丢失 > 解决方案:“卷” >> “卷”是容器上的一个或多个“目录”,此类目录可绕过联合文件系统,与宿主机上的某目录“绑定” 1.3 数据卷结构Bind mount volume > Docker-managed volume Volume可以是物理机上的任何一个目录存储服务方式有很多种,如NFS,MFS等,容器数据共享时需要对接到的文件系统或客户端接口都是不一样的,此时需要CSI容器存储接口将存储服务和容器内部对接。存储服务器对接物理机目录,物理机目录对接容器内目录。 2. docker管理卷(默认挂载) 制作镜像时以volumes关键字会指定一个目录。docker运行时先在镜像内找volumes关键字,然后在物理机的/var/lib/docker/volumes目录下生成一个随机名称的目录,默认将此目录与容器内volumes关键字后面指定的目录挂载起来。 3. 自管理卷(手动挂载)实验 方法1:先在物理机创建一个目录,再将多个容器的目录挂载到物理机目录 [root@localhost ~]# mkdir /data #创建目录 [root@localhost ~]# docker run --name test1 -v /data:/usr/local/nginx/html -d nginx:1.21.4 #手动挂载 #-v 物理机目录:容器内目录 [root@localhost ~]# cd /data/ #进入 [root@localhost data]# ls #查看无文件 [root@localhost data]# touch {1..100} #创建100个文件 [root@localhost data]# ls #查看有1-100个文件 [root@localhost data]# docker exec -it test1 /bin/bash #进入容器 root@5fdd3cd78396:/# cd /usr/local/nginx/html/ #进入目录 root@5fdd3cd78396:/usr/local/nginx/html# ls #查看有1-100个文件。挂载成功 此时单容器完成目录共享运行第二个容器,将此容器的/usr/local/nginx/html也与物理机的/data目录挂载: [root@localhost ~]# docker run --name test2 -v /data:/usr/local/nginx/html -d nginx:1.21.4 此时物理机的/data目录分别与test1容器和test2容器的两个目录挂载。这三个目录都可以看到对方创建的文件及内容了。实现了容器与容器的文件共享。且容器删除后,物理机的挂载目录下的文件还存在。 方法2:先运行容器1让它按照默认的方式将容器的目录与物理机随机产生的目录挂载到一起,再查看到物理机上随机产生的这个目录。将其他的所有容器的目录挂载到物理机物理机上随机产生的这个与容器1默认挂载了的目录上。实现多容器数据共享 [root@localhost ~]# docker run --name test1 -d nginx:1.21.4 #运行容器1,会自动默认挂载 [root@localhost ~]# docker inspect test1 #查看,找到如下内容就能查找到默认挂载的物理机目录了 然后用-v选项可以将其他容器的目录挂载到此目录下了 方法2或者: [root@localhost ~]# docker run --name test1 -d nginx:1.21.4 #运行容器1,会自动默认挂载 [root@localhost ~]# docker run --name test2 --volumes-from test1 -d nginx:1.21.4 [root@localhost ~]# docker run --name test3 --volumes-from test1 -d nginx:1.21.4 用此命令启动其他容器,并将其他容器的目录自动挂载到容器1的挂载目录下 4. 容器中的数据卷 4.1 数据卷意义 Docker-managed Volume >> docker run -it --name roc -v MOUNTDIR roc/lamp:v1.0 >> docker inspect -f {{.Mounts}} roc > Bind-mount Volume >> docker run -it --name roc -v HOSTDIR:VOLUMEDIR roc/lamp:v1.0 > Union Volume >> docker run -it --name roc --volumes-from ContainerName roc/lamp:v1.0 4.2 存储驱动 Docker 存储驱动 ( storage driver ) 是 Docker 的核心组件,它是 Docker 实现分层镜像的基础 1、device mapper ( DM ):性能和稳定性存在问题,不推荐生产环境使用(centos6及以下的主流) 2、btrfs:社区实现了 btrfs driver,稳定性和性能存在问题 3、overlayfs:内核 3.18 overlayfs 进入主线,性能和稳定性优异,第一选择 4.3 UFS文件系统-Overlayfsmount -t overlay overlay -olowerdir=./low,upperdir=./upper,workdir=./work ./merged echo "overlay" > /etc/modules-load.d/overlay.conf cat /proc/modules|grep overlay reboot vim /etc/systemd/system/docker.service --storage-driver=overlay \5.卷的持久化问题 容器是一个强大的概念,但是有时候并不是所有想访问的事物都能立马被封装成容器。用户可能有一个存储在大的集群上的相关Oracle数据库,想要连接它做一些测试。又或者,用户可能有一台遗留的大型服务器,而它上面现有配置好的二进制程序很难重现。 刚开始使用Docker时,用户想要访问的大部分事物可能会是容器外部的数据和程序。我们将和读者一起从直接在宿主机上挂载文件转到更为复杂的容器模 式:数据容器和开发工具容器。我们还将展示一些实用的技巧,例如,只需要一个SSH连接便能跨网络进行远程挂载,以及通过BitTorrent协议与其他用户分享数据。 卷是Docker的一个核心部分,有关外部数据引用的问题也是Docker生态系统中另一个快速变化的领域。 6.Docker卷的持久化问题 容器的大部分力量源自它们能够尽可能多地封装环境的文件系统的状态,这一点的确很有用处。然而有时候用户并不想把文件放到容器里,而是想要在容器之间共享或者单独管理一些大文件。一个经典的例子便是想要容器访问一个大型的中央式数据 库,但是又希望其他的(也许更传统些的)客户端也能和新容器一样访问它。 解决方案便是卷!一种Docker用来管理容器生命周期外的文件的机制。我们可以使用Docker的卷标志,在容器里访问宿主机上的A文件。如下图则是使用卷标志和和宿主机上的文件系统交互过程 如下命令展示宿主机上的/var/db/tables目录被挂载到了/var/data1: $ docker run -v /var/db/tables:/var/data1 -it debian bash -v标志(--volume的简写)表示为容器指定一个外部的卷。随后的参数以冒号分隔两个目录的形式给出了卷的格式,告知Docker将外部 的/var/db/tables目录映射到容器里的/var/data1目录。如果外部目录和容器目录不存在均会被创建。 要注意的是,卷在Dockerfile里被设定为不是持久化的。如果添加了一个卷,然后在一个Dockefile里对该目录做了一些更改,这些变动将不会被持久化到生成的目标镜像.
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!1. Docker 进程修改 比较少见会影响到所有的容器进程 -b, --bridge=”” 指定 Docker 使用的网桥设备,默认情况下 Docker 会自动创建和使用 docker0 网桥设备,通过此参数可以使用已经存在的设备。--bip 指定 Docker0 的 IP 和掩码,使用标准的 CIDR 形式,如 10.10.10.10/24--dns 配置容器的 DNS,在启动 Docker 进程时添加,所有容器全部生效 2. 容器的四种网络模式 跟在docker run命令之后的: --dns 用于指定启动的容器的 DNS(默认是物理机的) --net 用于指定容器的网络通讯方式,有以下四个值 bridge:Docker 默认方式,网桥模式 none:容器没有网络栈(主要用于离线分析等模式,数据可以通过目录挂载的方式传输,方便又安全) container:使用其它容器的网络栈,Docker容器会加入其它容器的 network namespace(相当容器二的网卡直接进入容器一的虚拟空间内,两个容器直接通过本地回环接口通信,非常高校。一般用于只有网络采用这种模式联通,其他进程都是互相隔离的) --network container:(ContainerName)host:表示容器使用 Host 的网络,没有自己独立的网络栈。容器可以完全访问 Host 的网络,不安全! --network host 2.1 bridge模式实验[root@localhost ~]# docker images #查看镜像 [root@localhost ~]# docker run --name test1 -d nginx:latest #容器test1不指定网络模式 [root@localhost ~]# docker run --name test2 --net bridge -d nginx:latest #指定容器test2的网络模式为 bridge网桥模式 [root@localhost ~]# docker inspect test1 #查看到"Gateway": "172.17.0.1",和"IPAddress": "172.17.0.2", [root@localhost ~]# docker inspect test2 #查看到"Gateway": "172.17.0.1",和 "IPAddress": "172.17.0.3 说明两个容器的网络模式是一样的且能互相通信。即容器的默认网络模式就是bridge网桥模式 2.2 none模式实验[root@localhost ~]# docker run --name test3 --net none -d nginx:latest #指定容器test3的网络模式为 none模式 [root@localhost ~]# docker inspect test3 #查看发现没有ip和网关2.3 container模式实验[root@localhost ~]# cc #先删除掉所有的容器 [root@localhost ~]# docker images #查看镜像 [root@localhost ~]# docker run --name test1 -d nginx:latest #正常启动镜像nginx:latest [root@localhost ~]# docker run --name test2 --net container:test1 -d hub.c.163.com/public/centos:7.2-tools #container模式启动镜像hub.c.163.com/public/centos:7.2-tools [root@localhost ~]# docker exec -it test2 /bin/bash #进入test2容器 [root@5b1ad1de8d65 /]# curl localhost #发现访问到了nginx的默认页面 [root@5b1ad1de8d65 /]# ifconfig #查看eth0的ip为172.17.0.2 [root@5b1ad1de8d65 /]# exit #退出 [root@localhost ~]# docker inspect test1 #查看发现就是此容器test1的地址 说明该模式下网络已共享。 2.4 host模式实验 浏览器访问192.168.232.165,访问失败 [root@localhost ~]# netstat -anpt | grep 80 #查看端口,无80端口 [root@localhost ~]# docker run --name test1 --net host -d nginx:latest #以host网络模式启动nginx镜像 [root@localhost ~]# netstat -anpt | grep 80 #查看到80端口开启 浏览器访问192.168.232.165,访问成功 3. 自定义 Docker0 的网桥地址 修改 /etc/docker/daemon.json 文件 { "bip": "192.168.1.5/24", #指定当前的docker的地址 "fixed-cidr": "192.168.1.0/24", #安全的cidr网段(每个容器的地址都是通过dhcp获取的) "fixed-cidr-v6": "2001:db8::/64", #ipv6的安全的cidr网段 "mtu": "1500", #数据包的大小 "default-gateway": "192.168.1.1", #默认的网关 "default-gateway-v6": "2001:db8:abcd::89", #ipv6的默认网关 "dns": ["192.168.1.2","192.168.1.3"] #DNS服务器 }4. 项目隔离方式4.1 基础命令 docker network ls # 查看当前可用的网络类型例: docker network create -d 类型 #网络空间名称# 类型分为: # overlay network # bridge network 4.2 独立至不同的网络命名空间进行隔离命令: docker network create -d bridge --subnet "172.26.0.0/16" --gateway "172.26.0.1" my-bridge-network 实验步骤: 先创建my-bridge-network网络,网络类型为bridge,网段为26 [root@localhost ~]# docker network create -d bridge --subnet "172.27.0.0/16" --gateway "172.27.0.1" anxiaopeng然后创建anxiaopeng网络,网络类型也为bridge,网段为27[root@localhost ~]# docker network create -d bridge --subnet "172.27.0.0/16" --gateway "172.27.0.1" anxiaopeng 查看如下: [root@localhost ~]# docker network ls 启动镜像,以网络名定义前两个容器为同一个网桥(26网段,但ip是此网段随机的),后两个容器为同一个网桥(27网段,但 ip是此网段随机的)。但是四个容器都是bridge 网桥模式。此情景相当于工作中创建多个项目,每个项目内有多个容器,每个项目内的容器都可以互相通信,但是每个项目之间是隔离不能通信的。 [root@localhost ~]# docker run --name test1.1 --net my-bridge-network -d hub.c.163.com/public/centos:7.2-tools [root@localhost ~]# docker run --name test1.2 --net my-bridge-network -d hub.c.163.com/public/centos:7.2-tools [root@localhost ~]# docker run --name test2.1 --net anxiaopeng -d hub.c.163.com/public/centos:7.2-tools [root@localhost ~]# docker run --name test2.2 --net anxiaopeng -d hub.c.163.com/public/centos:7.2-tools命令如下:docker run -d --network=my-bridge-network --name test1 hub.c.163.com/public/centos:6.7-tools docker run -d --name test2 hub.c.163.com/public/centos:6.7-tools 4.3 使用 Linux 桥接器进行主机间的通讯[root@localhost ~]# docker run --name test1 -p 2222:22 -d hub.c.163.com/public/centos:7.2-tools [root@localhost ~]# docker exec -it test1 /bin/bash [root@a88b5196f766 /]# vim /etc/ssh/sshd_config #打开sshd配置文件 将#PermitRootLogin yes取消注释,允许root远程登录 [root@a88b5196f766 /]# passwd #修改密码为123456 然后用xshell工具远程连接IP为:192.168.232.165,端口为:2222 [root@a88b5196f766 ~]# #发现可以成功登录。说明当前容器可以当作一个ssh的远程服务了 [root@localhost ~]# docker commit test1 ssh:v0.1 #将此容器封装成ssh服务的镜像! [root@localhost ~]# cc #删除容器 [root@localhost ~]# mkdir /usr/local/script #创建一个目录 [root@localhost ~]# ifconfig #先查看一下现存的网桥,以下创建网桥时候名称不能冲突 [root@localhost ~]# vim /usr/local/script/init-br.sh #创建一个脚本,脚本内容如下: #!/bin/bash ip addr del dev ens33 192.168.232.165/24 #删除当前物理机ens33网卡的ip地址 ip link add link ens33 dev br0 type macvlan mode bridge #创建一个基于ens33网卡的br0网桥,类型为macvlan网络模式为bridge ip addr add 192.168.232.165/24 dev br0 #设置br0网桥的ip地址为本机ip(如果一个物理网卡变成网桥了,那它就不许有ip地址了,与网桥共享) ip link set dev br0 up #启动br0网桥 ip route add default via 192.168.232.1 dev br0 #给br0网桥添加一个网关 [root@localhost ~]# chmod +x /usr/local/script/init-br.sh #添加权限 [root@localhost ~]# /bin/bash /usr/local/script/init-br.sh #启动脚本 [root@localhost ~]# ifconfig #查看发现ens33网卡没有ip地址了,br0网桥有192.168.232.165IP地址了 用电脑客户端的cmd窗口ping 192.168.232.165,发现能ping通了 [root@localhost ~]# rz #上传pipework-master.zip压缩包 [root@localhost ~]# unzip pipework-master.zip #解压缩 [root@localhost ~]# cp -a pipework-master/pipework /usr/local/bin/ [root@localhost ~]# chmod a+x /usr/local/bin/pipework #添加权限 [root@localhost ~]# docker run --name test1 --net none -d ssh:v0.1 [root@localhost ~]# pipework br0 test1 192.168.232.166/24@192.168.232.1 #设置容器地址分配 然后用脑客户端的cmd窗口ping 192.168.232.166,发现能ping通 👑👑👑结束语👑👑👑为大家推荐一款刷题神奇 点击链接访问牛客网各大互联网大厂面试真题。基础题库到进阶题库等各类面试题应有尽有!牛客网面经合集,满足大厂面试技术深度,快速构建Java核心知识体系大厂面试官亲授,备战面试与技能提升,主要考点+主流场景+内功提升+真题解析
作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. 进程隔离方式2. NetWork网络隔离 2.1 传统虚拟化网络2.2 容器级虚拟化 3. bridge网桥模式实验 3.1 bridge网桥模式实验原理3.2 bridge网桥模式实验步骤3.3 Docker NS 显示3.4 firewalld 规则 👑👑👑结束语👑👑👑 网络 联通性 容器 与 容器间的通讯:veth Bridge 容器与外部网络:SNAT 外部网络与容器内部:DNAT 隔离性 NameSpace network 1. 进程隔离方式2. NetWork网络隔离 netns 是在 linux 中提供网络虚拟化的一个项目,使用 netns 网络空间虚拟化可以在本地虚拟化出多个网络环境,目前 netns 在 lxc 容器中被用来为容器提供网络使用 netns 创建的网络空间独立于当前系统的网络空间,其中的网络设备以及 iptables 规则等都是独立的,就好像进入了另外一个网络一样 . 2.1 传统虚拟化网络2.2 容器级虚拟化 3. bridge网桥模式实验 3.1 bridge网桥模式实验原理 每个容器启动时会创建一对虚拟网卡,一个网卡在容器内的虚拟空间内,一个在主机的docker网桥上,然后网卡一连接网卡二,网卡二连接网桥。其他容器的虚拟网卡也都连接到网桥(三层交换)上,就可以使不同的容器之间互相通信了。 3.2 bridge网桥模式实验步骤[root@localhost ~]# ip netns add r1 # 创建虚拟网络空间,相当于分割出新的r1空间。 [root@localhost ~]# ip netns list #可以查看到r1空间 [root@localhost ~]# ip link add veth1.1 type veth peer name veth1.2 #创建一对虚拟网络设备veth1.1和veth1.2 [root@localhost ~]# ip link set veth1.1 netns r1 #将veth1.1的虚拟网卡放入r1虚拟空间 [root@localhost ~]# ip netns exec r1 bash #进入r1虚拟网络空间 [root@localhost ~]# ip link set veth1.1 name eth0 #更改网络名称 [root@localhost ~]# ip link set lo up #启动本地回环接口,使其正常工作 [root@localhost ~]# ip link set eth0 up #启动本地回环接口内的eth0虚拟网卡,使其正常工作 [root@localhost ~]# ip addr add 192.168.34.163/24 dev eth0 #设置ip地址 [root@localhost ~]# exit #退出当前虚拟空间 [root@localhost ~]# ip netns add r2 # 再创建虚拟网络空间,相当于分割出新的r2空间。 [root@localhost ~]# ip link add veth2.1 type veth peer name veth2.2 #创建第二对虚拟网络设备veth2.1和veth2.2 [root@localhost ~]# ip link set veth2.1 netns r2 #将veth2.1的虚拟网卡放入r2虚拟空间 [root@localhost ~]# ip netns exec r2 bash #进入r2虚拟网络空间 [root@localhost ~]# ip link set veth2.1 name eth0 #更改网络名称 [root@localhost ~]# ip link set lo up #启动本地回环接口,使其正常工作 [root@localhost ~]# ip link set eth0 up #启动本地回环接口内的eth0虚拟网卡,使其正常工作 [root@localhost ~]# ip addr add 192.168.34.164/24 dev eth0 #设置ip地址(两个虚拟空间的网卡必须同一网段) [root@localhost ~]# exit #退出当前虚拟空间 [root@localhost ~]# ip link add name br0 type bridge #创建网桥br0 [root@localhost ~]# ip link set br0 up #启动网桥br0 [root@localhost ~]# ip addr add 192.168.34.162/24 dev br0 #设置网桥ip [root@localhost ~]# ip link set dev veth1.2 master br0 #将veth1.2虚拟网卡连接到网桥 [root@localhost ~]# ip link set dev veth2.2 master br0 #将veth2.2虚拟网卡连接到网桥 [root@localhost ~]# ip link set veth1.2 up #启动虚拟网卡veth1.2 [root@localhost ~]# ip link set veth2.2 up #启动虚拟网卡veth1.2互相ping一下查看网络是否连通3.3 Docker NS 显示docker inspect --format='{{.State.Pid}} ' $container_id # 查看当前容器的名字空间 ID mkdir /var/run/netns # 创建目录,防止未创建 ln -s /proc/1234/ns/net /var/run/netns/1234 #在 /proc 目录(保存进程的所有相关信息)下,把对应的网络名字空间文件链接到 /var/run/netns 下面 ip netns show ip netns exec 1234 ifconfig eth0 172.16.0.10/16... #查看或访问容器的名字空间3.4 firewalld 规则iptables -t nat -A POSTROUTING -s 172.17.0.0/16 -o docker0 -j MASQUERADE #容器访问外部网络 docker run -d -p 80:80 apache #外部网络访问容器 iptables -t nat -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER iptables -t nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80 👑👑👑结束语👑👑👑为大家推荐一款刷题神奇 点击链接访问牛客网各大互联网大厂面试真题。基础题库到进阶题库等各类面试题应有尽有!牛客网面经合集,满足大厂面试技术深度,快速构建Java核心知识体系大厂面试官亲授,备战面试与技能提升,主要考点+主流场景+内功提升+真题解析
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!基于JVM的开源数据处理语言主要有Kotlin、Scala、SPL,下面对三者进行多方面的横向比较,从中找出开发效率最高的数据处理语言。本文的适用场景设定为项目开发中常见的数据处理和业务逻辑,以结构化数据为主,大数据和高性能不作为重点,也不涉及消息流、科学计算等特殊场景。基本特征适应面Kotlin的设计初衷是开发效率更高的Java,可以适用于任何Java涉及的应用场景,除了常见的信息管理系统,还能用于WebServer、Android项目、游戏开发,通用性比较好。Scala的设计初衷是整合现代编程范式的通用开发语言,实践中主要用于后端大数据处理,其他类型的项目中很少出现,通用性不如Kotlin。SPL的设计初衷是专业的数据处理语言,实践与初衷一致,前后端的数据处理、大小数据处理都很适合,应用场景相对聚焦,通用性不如Kotlin。编程范式Kotlin以面向对象编程为主,也支持函数式编程。Scala两种范式都支持,面向对象编程比Koltin更彻底,函数式编程也比Koltin方便些。SPL可以说不算支持面向对象编程,有对象概念,但没有继承重载这些内容,函数式编程比Kotlin更方便。运行模式Kotlin和Scala是编译型语言,SPL是解释型语言。解释型语言更灵活,但相同代码性能会差一点。不过SPL有丰富且高效的库函数,总体性能并不弱,面对大数据时常常会更有优势。外部类库Kotlin可以使用所有的Java类库,但缺乏专业的数据处理类库。Scala也可以使用所有的Java类库,且内置专业的大数据处理类库(Spark)。SPL内置专业的数据处理函数,提供了大量时间复杂度更低的基本运算,通常不需要外部Java类库,特殊情况可在自定义函数中调用。IDE和调试三者都有图形化IDE和完整的调试功能。SPL的IDE专为数据处理而设计,结构化数据对象呈现为表格形式,观察更加方便,Kotlin和Scala的IDE是通用的,没有为数据处理做优化,无法方便地观察结构化数据对象。学习难度Kotlin的学习难度稍高于Java,精通Java者可轻易学会。Scala的目标是超越Java,学习难度远大于Java。SPL的目标就是简化Java甚至SQL的编码,刻意简化了许多概念,学习难度很低。代码量Kotlin的初衷是提高Java的开发效率,官方宣称综合代码量只有Java的20%,可能是数据处理类库不专业的缘故,这方面的实际代码量降低不多。Scala的语法糖不少,大数据处理类库比较专业,代码量反而比Kotlin低得多。SPL只用于数据处理,专业性最强,再加上解释型语言表达能力强的特点,完成同样任务的代码量远远低于前两者(后面会有对比例子),从另一个侧面也能说明其学习难度更低。语法数据类型原子数据类型:三者都支持,比如Short、Int、Long、Float、Double、Boolean日期时间类型:Kotlin缺乏易用的日期时间类型,一般用Java的。Scala和SPL都有专业且方便的日期时间类型。有特色的数据类型:Kotlin支持非数值的字符Char、可空类型Any?。Scala支持元组(固定长度的泛型集合)、内置BigDecimal。SPL支持高性能多层序号键,内置BigDecimal。集合类型:Kotlin和Scala支持Set、List、Map。SPL支持序列(有序泛型集合,类似List)。结构化数据类型:Kotlin有记录集合List<EntityBean>,但缺乏元数据,不够专业。Scala有专业的结构化数类型,包括Row、RDD、DataSet、DataFrame(本文以此为例进行说明)等。SPL有专业的结构化数据类型,包括record、序表(本文以此为例进行说明)、内表压缩表、外存Lazy游标等。Scala独有隐式转换能力,理论上可以在任意数据类型之间进行转换(包括参数、变量、函数、类),可以方便地改变或增强原有功能。流程处理三者都支持基础的顺序执行、判断分支、循环,理论上可进行任意复杂的流程处理,这方面不多讨论,下面重点比较针对集合数据的循环结构是否方便。以计算比上期为例,Kotlin代码:mData.forEachIndexed{index,it-> if(index>0) it.Mom= it.Amount/mData[index-1].Amount-1 }Kotlin的forEachIndexed函数自带序号变量和成员变量,进行集合循环时比较方便,支持下标取记录,可以方便地进行跨行计算。Kotlin的缺点在于要额外处理数组越界。Scala代码:val w = Window.orderBy(mData("SellerId")) mData.withColumn("Mom", mData ("Amount")/lag(mData ("Amount"),1).over(w)-1)Scala跨行计算不必处理数组越界,这一点比Kotlin方便。但Scala的结构化数据对象不支持下标取记录,只能用lag函数整体移行,这对结构化数据不够方便。lag函数不能用于通用性强的forEach,而要用withColumn之类功能单一的循环函数。为了保持函数式编程风格和SQL风格的底层统一,lag函数还必须配合窗口函数(Python的移行函数就没这种要求),整体代码看上去反而比Kotlin复杂。SPL代码:mData.(Mom=Amount/Amount[-1]-1)SPL对结构化数据对象的流程控制进行了多项优化,类似forEach这种最通用最常用的循环函数,SPL可以直接用括号表达,简化到极致。SPL也有移行函数,但这里用的是更符合直觉的“[相对位置]"语法,进行跨行计算时比Kotlin的绝对定位强大,比Scala的移行函数方便。上述代码之外,SPL还有更多针对结构化数据的流程处理功能,比如:每轮循环取一批而不是一条记录;某字段值变化时循环一轮。Lambda表达式Lambda表达式是匿名函数的简单实现,目的是简化函数的定义,尤其是变化多样的集合计算类函数。Kotlin支持Lambda表达式,但因为编译型语言的关系,难以将参数表达式方便地指定为值参数或函数参数,只能设计复杂的接口规则进行区分,甚至有所谓高阶函数专用接口,这就导致Kotin的Lambda表达式编写困难,在数据处理方面专业性不足。几个例子:"abcd".substring( 1,2) //值参数 "abcd".sumBy{ it.toInt()} //函数参数 mData.forEachIndexed{ index,it-> if(index>0) it.Mom=…} //函数参数的函数带多个参数Koltin的Lambda表达式专业性不足,还表现在使用字段时必须带上结构化数据对象的变量名(it),而不能像SQL那样单表计算时可以省略表名。同为编译型语言,Scala的Lambda表达式和Kotlin区别不大,同样需要设计复杂的接口规则,同样编写困难,这里就不举例了。计算比上期时,字段前也要带上结构化数据对象变量名或用col函数,形如mData ("Amount")或col("Amount"),虽然可以用语法糖弥补,写成$”Amount”或'Amount,但很多函数不支持这种写法,硬要弥补反而使风格不统一。SPL的Lambda表达式简单易用,比前两者更专业,这与其解释型语言的特性有关。解释型语言可以方便地推断出值参数和函数参数,没有所谓复杂的高阶函数专用接口,所有的函数接口都一样简单。几个例子:mid("abcd",2,1) //值参数 Orders.sum(Amount*Amount) //函数参数 mData.(Mom=Amount/Amount[-1]-1) //函数参数的函数带多个参数SPL可直接使用字段名,无须结构化数据对象变量名,比如:Orders.select(Amount>1000 && Amount<=3000 && like(Client,"*S*"))SPL的大多数循环函数都有默认的成员变量~和序号变量#,可以显著提升代码编写的便利性,特别适合结构化数据计算。比如,取出偶数位置的记录:Students.select(# % 2==0)求各组的前3名:Orders.group(SellerId;~.top(3;Amount))SPL函数选项和层次参数值得一提的是,为了进一步提高开发效率,SPL还提供了独特的函数语法。有大量功能类似的函数时,大部分程序语言只能用不同的名字或者参数进行区分,使用不太方便。而SPL提供了非常独特的函数选项,使功能相似的函数可以共用一个函数名,只用函数选项区分差别。比如,select函数的基本功能是过滤,如果只过滤出符合条件的第1条记录,可使用选项@1:T.select@1(Amount>1000)对有序数据用二分法进行快速过滤,使用@b:T.select@b(Amount>1000)函数选项还可以组合搭配,比如:Orders.select@1b(Amount>1000)有些函数的参数很复杂,可能会分成多层。常规程序语言对此并没有特别的语法方案,只能生成多层结构数据对象再传入,非常麻烦。SQL使用了关键字把参数分隔成多个组,更直观简单,但这会动用很多关键字,使语句结构不统一。而SPL创造性地发明了层次参数简化了复杂参数的表达,通过分号、逗号、冒号自高而低将参数分为三层:join(Orders:o,SellerId ; Employees:e,EId)数据源数据源种类Kotlin原则上可以支持所有的Java数据源,但代码很繁琐,类型转换麻烦,稳定性也差,这是因为Kotlin没有内置的数据源访问接口,更没有针对结构化数据处理做优化(JDBC接口除外)。从这个意义讲,也可以说它不直接支持任何数据源,只能使用Java第三方类库,好在第三方类库的数量足够庞大。Scala支持的数据源种类比较多,且有六种数据源接口是内置的,并针对结构化数据处理做了优化,包括:JDBC、CSV、TXT、JSON、Parquet列存格式、ORC列式存储,其他的数据源接口虽然没有内置,但可以用社区小组开发的第三方类库。Scala提供了数据源接口规范,要求第三方类库输出为结构化数据对象,常见的第三方接口有XML、Cassandra、HBase、MongoDB等。SPL内置了最多的数据源接口,并针对结构化数据处理做了优化,包括:JDBC(即所有的RDB)CSV、TXT、JSON、XML、ExcelHBase、HDFS、Hive、SparkSalesforce、阿里云Restful、WebService、WebcrawlElasticsearch、MongoDB、Kafka、R2dbc、FTPCassandra、DynamoDB、influxDB、Redis、SAP这些数据源都可以直接使用,非常方便。对于其他未列入的数据源,SPL也提供了接口规范,只要按规范输出为SPL的结构化数据对象,就可以进行后续计算。代码比较以规范的CSV文件为例,比较三种语言的解析代码。Kotlin:val file = File("D:\\data\\Orders.txt") data class Order(var OrderID: Int,var Client: String,var SellerId: Int, var Amount: Double, var OrderDate: Date) var sdf = SimpleDateFormat("yyyy-MM-dd") var Orders=file.readLines().drop(1).map{ var l=it.split("\t") var r=Order(l[0].toInt(),l[1],l[2].toInt(),l[3].toDouble(),sdf.parse(l[4])) r } var resutl=Orders.filter{ it.Amount>= 1000 && it.Amount < 3000}Koltin专业性不足,通常要硬写代码读取CSV,包括事先定义数据结构,在循环函数中手工解析数据类型,整体代码相当繁琐。也可以用OpenCSV等类库读取,数据类型虽然不用在代码中解析,但要在配置文件中定义,实现过程不见得简单。Scala专业性强,内置解析CSV的接口,代码比Koltin简短得多:val spark = SparkSession.builder().master("local").getOrCreate() val Orders = spark.read.option("header", "true").option("sep","\t").option("inferSchema", "true").csv("D:/data/orders.csv").withColumn("OrderDate", col("OrderDate").cast(DateType)) Orders.filter("Amount>1000 and Amount<=3000")Scala在解析数据类型时麻烦些,其他方面没有明显缺点。SPL更加专业,连解析带计算只要一行:T("D:/data/orders.csv").select(Amount>1000 && Amount<=3000)跨源计算JVM数据处理语言的开放性强,有足够的能力对不同的数据源进行关联、归并、集合运算,但数据处理专业性的差异,导致不同语言的方便程度区别较大。Kotlin不够专业,不仅缺乏内置数据源接口,也缺乏跨源计算函数,只能硬写代码实现。假设已经从不同数据源获得了员工表和订单表,现在把两者关联起来:data class OrderNew(var OrderID:Int ,var Client:String, var SellerId:Employee ,var Amount:Double ,var OrderDate:Date ) val result = Orders.map { o->var emp=Employees.firstOrNull{ it.EId==o.SellerId } emp?.let{ OrderNew(o.OrderID,o.Client,emp,o.Amount,o.OrderDate) } } .filter {o->o!=null}很容易看出Kotlin的缺点,代码只要一长,Lambda表达式就变得难以阅读,还不如普通代码好理解;关联后的数据结构需要事先定义,灵活性差,影响解题流畅性。Scala比Kotlin专业,不仅内置了多种数据源接口,而且提供了跨源计算的函数。同样的计算,Scala代码简单多了:val join=Orders.join(Employees,Orders("SellerId")===Employees("EId"),"Inner")可以看到,Scala不仅具备专用于结构化数据计算的对象和函数,而且可以很好地配合Lambda语言,代码更易理解,也不用事先定义数据结构。SPL更加专业,结构化数据对象更专业,跨源计算函数更方便,代码更简短:join(Orders:o,SellerId;Employees:e,EId)自有存储格式反复使用的中间数据,通常会以某种格式存为本地文件,以此提高取数性能。Kotlin支持多种格式的文件,理论上能够进行中间数据的存储和再计算,但因为在数据处理方面不专业,基本的读写操作都要写大段代码,相当于并没有自有的存储格式。Scala支持多种存储格式,其中parquet文件常用且易用。parquet是开源存储格式,支持列存,可存储大量数据,中间计算结果(DataFrame)可以和parquet文件方便地互转。遗憾的是,parquet的索引尚不成熟。val df = spark.read.parquet("input.parquet") val result=df.groupBy(data("Dept"),data("Gender")).agg(sum("Amount"),count("*")) result.write.parquet("output.parquet")SPL支持btx和ctx两种私有二进制存储格式,btx是简单行存,ctx支持行存、列存、索引,可存储大量数据并进行高性能计算,中间计算结果(序表/游标)可以和这两种文件方便地互转。A1=file("input.ctx").open()2=A1.cursor(Dept,Gender,Amount).groups(Dept,Gender;sum(Amount):amt,count(1):cnt)3=file("output.ctx").create(#Dept,#Gender,amt,cnt).append(A2.cursor())结构化数据计算结构化数据对象数据处理的核心是计算,尤其是结构化数据的计算。结构化数据对象的专业程度,深刻地决定了数据处理的方便程度。Kotlin没有专业的结构化数据对象,常用于结构化数据计算的是List<EntityBean>,其中EntityBean可以用data class简化定义过程。List是有序集合(可重复),凡涉及成员序号和集合的功能,Kotlin支持得都不错。比如按序号访问成员:Orders[3] //按下标取记录,从0开始 Orders.take(3) //前3条记录 Orders.slice(listOf(1,3,5)+IntRange(7,10)) //下标是1、3、5、7-10的记录还可以按倒数序号取成员:Orders.reversed().slice(1,3,5) //倒数第1、3、5条 Orders.take(1)+Orders.takeLast(1) //第1条和最后1条涉及顺序的计算难度都比较大,Kotlin支持有序计集合,进行相关的计算会比较方便。作为集合的一种,List擅长的功能还有集合成员的增删改、交差合、拆分等。但List不是专业的结构化数据对象,一旦涉及字段结构相关的功能,Kotlin就很难实现了。比如,取Orders中的两个字段组成新的结构化数据对象。data class CliAmt(var Client: String, var Amount: Double) var CliAmts=Orders.map{it.let{CliAmt(it.Client,it.Amount) }}上面的功能很常用,相当于简单SQL语句select Client,Amount from Orders,但Kotlin写起来就很繁琐,不仅要事先定义新结构,还要硬编码完成字段的赋值。简单的取字段功能都这么繁琐,高级些的功能就更麻烦了,比如:按字段序号取、按参数取、获得字段名列表、修改字段结构、在字段上定义键和索引、按字段查询计算。Scala也有List,与Kotlin区别不大,但Scala为结构化数据处理设计了更加专业的数据对象DataFrame(以及RDD、DataSet)。 DataFrame是有结构的数据流,与数据库结果集有些相似,都是无序集合,因此不支持按下标取数,只能变相实现。比如,第10条记录:Orders.limit(10).tail(1)(0)可以想象,凡与顺序相关的计算,DataFrame实现起来都比较麻烦,比如区间、移动平均、倒排序等。 除了数据无序,DataFrame也不支持修改(immutable特性),如果想改变数据或结构,必须生成新的DataFrame。比如修改字段名,实际上要通过复制记录来实现:Orders.selectExpr("Client as Cli")DataFrame支持常见的集合计算,比如拆分、合并、交差合并,其中并集可通过合集去重实现,但因为要通过复制记录来实现,集合计算的性能普遍不高。 虽然有不少缺点,但DataFrame是专业的结构化数据对象,字段访问方面的能力是Kotlin无法企及的。比如,获得元数据/字段名列表:Orders.schema.fields.map(it=>it.name).toList还可以方便地用字段取数,比如,取两个字段形成新dataframe:Orders.select("Client","Amount") //可以只用字段名或用计算列形成新DataFrame:Orders.select(Orders("Client"),Orders("Amount")+1000) //不能只用字段名遗憾的是,DataFrame只支持用字符串形式的名字来引用字段,不支持用字段序号或默认名字,导致很多场景下不够方便。此外,DataFrame也不支持定义索引,无法进行高性能随机查询,专业性还有缺陷。SPL的结构化数据对象是序表,优点是足够专业,简单易用,表达能力强。 按序号访问成员:Orders(3) //按下标取记录,从1开始 Orders.to(3) //前3条记录 Orders.m(1,3,5,7:10) //序号是1、3、5、7-10的记录按倒数序号取记录,独特之处在于支持负号表示倒数,比Kotlin专业且方便:Orders.m(-1,-3,-5) //倒数第1,3,5条 Orders.m(1,-1) //第1条和最后1条作为集合的一种,序表也支持集合成员的增删改、交并差合、拆分等功能。由于序表和List一样都是可变集合(mutable),集合计算时尽可能使用游离记录,而不是复制记录,性能比Scala好得多,内存占用也少。 序表是专业的结构化数据对象,除了集合相关功能外,更重要的是可以方便地访问字段。比如,获得字段名列表:Orders.fname()取两个字段形成新序表:Orders.new(Client,Amount)用计算列形成新序表:Orders.new(Client,Amount*0.2)修改字段名:Orders.alter(;OrderDate) //不复制记录有些场景需要用字段序号或默认名字访问字段,SPL都提供了相应的访问方法:Orders(Client) //按字段名(表达式取) Orders([#2,#3]) //按默认字段名取 Orders.field(“Client”) //按字符串(外部参数) Orders.field(2) //按字段序号取作为专业的结构化数据对象,序表还支持在字段上定义键和索引:Orders.keys@i(OrderID) //定义键,同时建立哈希索引 Orders.find(47) //用索引高速查找计算函数Kotlin支持部分基本计算函数,包括:过滤、排序、去重、集合的交叉合并、各类聚合、分组汇总。但这些函数都是针对普通集合的,如果计算目标改成结构化数据对象,计算函数库就显得非常不足,通常就要辅以硬编码才能实现计算。还有很多基本的集合运算是Kotlin不支持的,只能自行编码实现,包括:关联、窗口函数、排名、行转列、归并、二分查找等。其中,归并和二分查找等属于次序相关的运算,由于Kotlin List是有序集合,自行编码实现这类运算不算太难。总体来讲,面对结构化数据计算,Kotlin的函数库可以说较弱。Scala的计算函数比较丰富,且都是针对结构化数据对象设计的,包括Kotlin不支持的函数:排名、关联、窗口函数、行转列,但基本上还没有超出SQL的框架。也有一些基本的集合运算是Scala不支持的,尤其是与次序相关的,比如归并、二分查找,由于Scala DataFrame沿用了SQL中数据无序的概念,即使自行编码实现此类运算,难度也是非常大的。总的来说,Scala的函数库比Kotlin丰富,但基本运算仍有缺失。SPL的计算函数最丰富,且都是针对结构化数据对象设计的,SPL极大地丰富了结构化数据运算内容,设计了很多超出SQL的内容,当然也是Scala/Kotlin不支持的函数,比如有序计算:归并、二分查找、按区间取记录、符合条件的记录序号;除了常规等值分组,还支持枚举分组、对齐分组、有序分组;将关联类型分成外键和主子;支持主键以约束数据,支持索引以快速查询;对多层结构的数据(多表关联或Json\XML)进行递归查询等。以分组为例,除了常规的等值分组外,SPL还提供了更多的分组方案:枚举分组:分组依据是若干条件表达式,符合相同条件的记录分为一组。对齐分组:分组依据是外部集合,记录的字段值与该集合的成员相等的分为一组,组的顺序与该集合成员的顺序保持一致,允许有空组,可单独分出一组“不属于该集合的记录”。有序分组:分组依据是已经有序的字段,比如字段发生变化或者某个条件成立时分出一个新组,SPL直接提供了这类有序分组,在常规分组函数上加个选项就可以完成,非常简单而且运算性能也更好。其他语言(包括SQL)都没有这种分组,只能费劲地转换为传统的等值分组或者自己硬编码实现。下面我们通过几个常规例子来感受一下这三种语言在计算函数方式的差异。排序按Client顺序,Amount逆序排序。Kotlin:Orders.sortedBy{it.Amount}.sortedByDescending{it.Client}Kotlin代码不长,但仍有不便之处,包括:逆序正序是两个不同的函数,字段名必须带表名,代码写出的字段顺序与实际的排序顺序相反。Scala:Orders.orderBy(Orders("Client"),-Orders("Amount"))Scala简单多了,负号代表逆序,代码写出的字段顺序与排序的顺序相同。遗憾之处在于:字段仍要带表名;编译型语言只能用字符串实现表达式的动态解析,导致代码风格不统一。SPL:Orders.sort(Client,-Amount)SPL代码更简单,字段不必带表名,解释型语言代码风格容易统一。分组汇总Kotlin:data class Grp(var Dept:String,var Gender:String) data class Agg(var sumAmount: Double,var rowCount:Int) var result1=data.groupingBy{Grp(it!!.Dept,it.Gender)} .fold(Agg(0.0,0),{acc, elem -> Agg(acc.sumAmount + elem!!.Amount,acc.rowCount+1)}) .toSortedMap(compareBy<Grp> { it.Dept }.thenBy { it.Gender })Kotlin代码比较繁琐,不仅要用groupingBy和fold函数,还要辅以硬编码才能实现分组汇总。当出现新的数据结构时,必须事先定义才能用,比如分组的双字段结构、汇总的双字段结构,这样不仅灵活性差,而且影响解题流畅性。最后的排序是为了和其他语言的结果顺序保持一致,不是必须的。Scala:val result=data.groupBy(data("Dept"),data("Gender")).agg(sum("Amount"),count("*"))Scala代码简单多了,不仅易于理解,而且不用事先定义数据结构。SPL:data.groups(Dept,Gender;sum(Amount),count(1))SPL代码最简单,表达能力不低于SQL。关联计算两个表有同名字段,对其关联并分组汇总。Kotlin代码:data class OrderNew(var OrderID:Int ,var Client:String, var SellerId:Employee ,var Amount:Double ,var OrderDate:Date ) val result = Orders.map { o->var emp=Employees.firstOrNull{it.EId==o.EId} emp?.let{ OrderNew(o.OrderID,o.Client,emp,o.Amount,o.OrderDate)} } .filter {o->o!=null} data class Grp(var Dept:String,var Gender:String) data class Agg(var sumAmount: Double,var rowCount:Int) var result1=data.groupingBy{Grp(it!!.EId.Dept,it.EId.Gender)} .fold(Agg(0.0,0),{acc, elem -> Agg(acc.sumAmount + elem!!.Amount,acc.rowCount+1)}) .toSortedMap(compareBy<Grp> { it.Dept }.thenBy { it.Gender })Kotlin代码很繁琐,很多地方都要定义新数据结构,包括关联结果、分组的双字段结构、汇总的双字段结构。Scalaval join=Orders.as("o").join(Employees.as("e"),Orders("EId")===Employees("EId"),"Inner") val result= join.groupBy(join("e.Dept"), join("e.Gender")).agg(sum("o.Amount"),count("*"))Scala比Kolin简单多了,不用繁琐地定义数据结构,也不必硬编码。SPL更简单:join(Orders:o,SellerId;Employees:e,EId).groups(e.Dept,e.Gender;sum(o.Amount),count(1))综合数据处理对比CSV内容不规范,每三行对应一条记录,其中第二行含三个字段(即集合的集合),将该文件整理成规范的结构化数据对象,并按第3和第4个字段排序.Kotlin:data class Order(var OrderID: Int,var Client: String,var SellerId: Int, var Amount: Double, var OrderDate: Date) var Orders=ArrayList<Order>() var sdf = SimpleDateFormat("yyyy-MM-dd") var raw=File("d:\\threelines.txt").readLines() raw.forEachIndexed{index,it-> if(index % 3==0) { var f234=raw[index+1].split("\t") var r=Order(raw[index].toInt(),f234[0],f234[1].toInt(),f234[2].toDouble(), sdf.parse(raw[index+2])) Orders.add(r) } } var result=Orders.sortedByDescending{it.Amount}.sortedBy{it.SellerId}Koltin在数据处理方面专业性不足,大部分功能要硬写代码,包括按位置取字段、从集合的集合取字段。Scala:val raw=spark.read.text("D:/threelines.txt") val rawrn=raw.withColumn("rn", monotonically_increasing_id()) var f1=rawrn.filter("rn % 3==0").withColumnRenamed("value","OrderId") var f5=rawrn.filter("rn % 3==2").withColumnRenamed("value","OrderDate") var f234=rawrn.filter("rn % 3==1") .withColumn("splited",split(col("value"),"\t")) .select(col("splited").getItem(0).as("Client") ,col("splited").getItem(1).as("SellerId") ,col("splited").getItem(2).as("Amount")) f1.withColumn("rn1",monotonically_increasing_id()) f5=f5.withColumn("rn1",monotonically_increasing_id()) f234=f234.withColumn("rn1",monotonically_increasing_id()) var f=f1.join(f234,f1("rn1")===f234("rn1")) .join(f5,f1("rn1")===f5("rn1")) .select("OrderId","Client","SellerId","Amount","OrderDate") val result=f.orderBy(col("SellerId"),-col("Amount"))Scala在数据处理方面更加专业,大量使用结构化计算函数,而不是硬写循环代码。但Scala缺乏有序计算能力,相关的功能通常要添加序号列再处理,导致整体代码冗长。 SPL:A1=file("D:\\data.csv").import@si()2=A1.group((#-1)\3)3=A2.new(~(1):OrderID, (line=~(2).array("\t"))(1):Client,line(2):SellerId,line(3):Amount,~(3):OrderDate )4=A3.sort(SellerId,-Amount)SPL在数据处理方面最专业,只用结构化计算函数就可以实现目标。SPL支持有序计算,可以直接按位置分组,按位置取字段,从集合中的集合取字段,虽然实现思路和Scala类似,但代码简短得多。应用结构Java应用集成Kotlin编译后是字节码,和普通的class文件一样,可以方便地被Java调用。比如KotlinFile.kt里的静态方法fun multiLines(): List<Order>,会被Java正确识别,直接调用即可:java.util.List result=KotlinFileKt.multiLines(); result.forEach(e->{System.out.println(e);});Scala编译后也是字节码,同样可以方便地被Java调用。比如ScalaObject对象的静态方法def multiLines():DataFrame,会被Java识别为Dataset类型,稍做修改即可调用:org.apache.spark.sql.Dataset df=ScalaObject.multiLines(); df.show();SPL提供了通用的JDBC接口,简单的SPL代码可以像SQL一样,直接嵌入Java:Class.forName("com.esproc.jdbc.InternalDriver"); Connection connection =DriverManager.getConnection("jdbc:esproc:local://"); Statement statement = connection.createStatement(); String str="=T(\"D:/Orders.xls\").select(Amount>1000 && Amount<=3000 && like(Client,\"*s*\"))"; ResultSet result = statement.executeQuery(str);复杂的SPL代码可以先存为脚本文件,再以存储过程的形式被Java调用,可有效降低计算代码和前端应用的耦合性。Class.forName("com.esproc.jdbc.InternalDriver"); Connection conn =DriverManager.getConnection("jdbc:esproc:local://"); CallableStatement statement = conn.prepareCall("{call scriptFileName(?, ?)}"); statement.setObject(1, "2020-01-01"); statement.setObject(2, "2020-01-31"); statement.execute();SPL是解释型语言,修改后不用编译即可直接执行,支持代码热切换,可降低维护工作量,提高系统稳定性。Kotlin和Scala是编译型语言,编译后必须择时重启应用。交互式命令行Kotlin的交互式命令行需要额外下载,使用Kotlinc命令启动。Kotlin命令行理论上可以进行任意复杂的数据处理,但因为代码普遍较长,难以在命令行修改,还是更适合简单的数字计算:>>>Math.sqrt(5.0) 2.236.6797749979Scala的交互式命令行是内置的,使用同名命令启动。Scala命令行理论上可以进行数据处理,但因为代码比较长,更适合简单的数字计算:scala>100*3 rest1: Int=300SPL内置了交互式命令行,使用“esprocx -r -c”命令启动。SPL代码普遍较短,可在命令行进行简单的数据处理。(1): T("d:/Orders.txt").groups(SellerId;sum(Amount):amt).select(amt>2000) (2):^C D:\raqsoft64\esProc\bin>Log level:INFO 1 4263.900000000001 3 7624.599999999999 4 14128.599999999999 5 26942.4通过多方面的比较可知:对于应用开发中常见的数据处理任务,Kotlin因为不够专业,开发效率很低;Scala有一定的专业性,开发效率比Kotlin高,但还比不上SPL;SPL语法更简练,表达效率更高,数据源种类更多,接口更易用,结构化数据对象更专业,函数更丰富且计算能力更强,开发效率远高于Kotlin和Scala。SPL资料 SPL下载 SPL源代码 👑👑👑结束语👑👑👑编辑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录 1.commit与build优缺点比较2.commit + build镜像制作方法3. 镜像动态化👑👑👑结束语👑👑👑 1.commit与build优缺点比较 commit: 优点: 简单、所见所得 缺点 元数据信息无法注入 镜像层级的重用性弱 编译步骤不可重用 DockerFile: 优点 元数据信息可注入 镜像层级的重用性强 编译步骤不可重用 缺点: 编译速度没有宿主机快 下载程序的依赖包比在宿主机中直接下载更加耗时 会产生none镜像,占用磁盘空间 2.commit + build镜像制作方法 容器 > 镜像(简单好用但是功能性不全,不能封装启动命令。只能当中应急方案)(Docker commit + Docker build = 镜像) docker pull centos:centos7 #从官网下载一个基础镜像 [root@localhost dockerrun]# mkdir dockerrun1 #创建一个目录并进入 [root@localhost dockerrun1]# vim Dockerfile #创建一个Dockerfile文件Dockerfile文件内容如下:FROM centos:centos7 RUN touch /root/startup.sh CMD tail -f /root/startup.sh[root@localhost dockerrun1]# docker build -t axplinux/centos:7.9 . #将Dockerfile文件转化成镜像axplinux/centos:7.9 [root@localhost dockerrun1]# docker run --name nginx -p 8000:80 -d axplinux/centos:7.9 #将axplinux/centos:7.9当第二个基础镜像,用nginx容器运行该镜像。 [root@localhost dockerrun1]# docker extc -it nginx /bin/bash #进入容器内部 在容器内下载一些必须命令,并且安装一个nginx软件且修改网页文件内容并启动。浏览器访问192.168.232.165:8000能看到网页文件内容证明成功安装并运行nginx exit #退出容器 [root@localhost dockerrun1]# docker commit nginx axplinux/nginx:v0.1 #将nginx容器封装成最终镜像axplinux/nginx:v0.1 [root@localhost dockerrun1]# cc #容器已无用,可以删除,cc是删除容器的脚本,脚本内容在我前几期文章中有写 [root@localhost dockerrun1]# docker images #查看v0.1版的nginx镜像已制作完成。将此镜像作为另一个基础镜像继续升级。 [root@localhost ~]# mkdir dockerrun2 #创建另一个文件 [root@localhost ~]# cd dockerrun2 #进入 [root@localhost dockerrun2]# vim Dockerfile #再创建一个Dockerfile文件Dockerfile文件内容如下:FROM axplinux/nginx:v0.1 MAINTAINER anxiaopeng_linux LABEL nginx_version="1.21.3" RUN rm -rf /root/startup.sh EXPOSE 80 443 WORKDIR /usr/local/nginx CMD /usr/local/nginx/sbin/nginx && tail -f /usr/local/nginx/logs/access.log[root@localhost dockerrun2]# docker build -t axplinux/nginx:v2.0 . #再将Dockerfile文件转化成镜像axplinux/nginx:v2.0 [root@localhost dockerrun2]# docker image inspect axplinux/nginx:v2.0 #可以查看标签信息和作者信息 [root@localhost dockerrun2]# docker run --name nginx -p 666:80 -d axplinux/nginx:v2.0 #启动镜像 浏览器访问ip:666能看到网页文件 [root@localhost dockerrun2]# docker logs nginx #可以查看nginx的访问日志。结束!3. 镜像动态化[root@localhost dockerrun2]# cd .. [root@localhost ~]# mkdir dockerenv [root@localhost ~]# cd dockerenv/ [root@localhost dockerenv]# vim Dockerfile #创建Dockerfile文件Dockerfile文件内容如下:FROM axplinux/nginx:v2.0 ENV INDEX_DATA default ADD ./startup.sh /root RUN chmod +x /root/startup.sh ENTRYPOINT /bin/bash /root/startup.sh[root@localhost dockerenv]# vim startup.sh #创建启动脚本 创建启动脚本,脚本内容为: #!/bin/bash echo $INDEX_DATA > /usr/local/nginx/html/index.html /usr/local/nginx/sbin/nginx && tail -f /usr/local/nginx/logs/access.log[root@localhost dockerenv]# docker build -t axplinux/nginx:v3.0 . [root@localhost dockerenv]# docker run --name nginx -p 8000:80 -d axplinux/nginx:v3.0 #先正常启动镜像 浏览器访问Ip:8000,输出默认结果default [root@localhost dockerenv]# cc [root@localhost dockerenv]# docker run --name nginx -p 8000:80 --env INDEX_DATA=2222222 -d axplinux/nginx:v3.0 #动态启动镜像 浏览器访问ip:8000,输出默认结果2222222 👑👑👑结束语👑👑👑为大家推荐一款刷题神奇 点击链接访问牛客网各大互联网大厂面试真题。基础题库到进阶题库等各类面试题应有尽有!牛客网面经合集,满足大厂面试技术深度,快速构建Java核心知识体系大厂面试官亲授,备战面试与技能提升,主要考点+主流场景+内功提升+真题解析
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1.使用Dockerfile创建镜像1.1 创建工作目录1.2 编写run.sh脚本和authorized_keys文件1.3 编写Dockerfile 1.4 创建镜像 1.5 测试镜像,运行容器2. 镜像的多级构建3. Docker image Build 高级3.1 镜像 Cache 机制3.2 Cache 机制的注意事项3.3 命名方式的 stage 3.4 Google 内部精简镜像 Dockerfile被认为是构建Docker镜像的标准方式。人们常常会疑惑Dockerfile对于配置管理意味着什么。读者也可能会有许多疑问(尤其是在对一些其他配置管理工具有些经验的时候),例如:🩸如果基础镜像更改会发生什么? 🩸如果更改要安装的包然后重新构建会发生什么? 🩸它会取代Chef/Puppet/Ansible吗? 事实上,Dockerfile很简单:从给定的镜像开始,Dockerfile为Docker指定一系列的shell命令和元指令,从而产出最终所需的镜像。Dockerfile提供了一个普通、简单和通用的语言来配置Docker镜像。使用 Dockerfile,用户可以使用任何偏好的方式来达到所期望的最终状态。用户可以调用Puppet,可以从其他脚本里复制,甚至可以从一个完整的文件系统复制!并不推荐使用docker commit的方法来构建镜像。相反,推荐使用被称为Dockerfile的定义文件和docker build命令来构建镜像。Dockerfile使用基本的基于DSL(Domain Specific Language))语法的指令来构建一个Docker镜像,我们推荐使用Dockerfile方法来代替docker commit,因为通过前者来构建镜像更具备可重复性、透明性以及幂等性。 1.使用Dockerfile创建镜像1.1 创建工作目录 首先,创建一个sshd_ubuntu工作目录: $ mkdir sshd_ubuntu $ ls sshd_ubuntu 在其中,创建Dockerfile和run.sh文件: $ cd sshd_ubuntu/ $ touch Dockerfile run.sh $ ls Dockerfile run.sh1.2 编写run.sh脚本和authorized_keys文件 脚本文件run.sh的内容与前面一致: #!/bin/bash /usr/sbin/sshd -D 在宿主主机上生成SSH密钥对,并创建authorized_keys文件: $ ssh-keygen -t rsa ... $ cat ~/.ssh/id_rsa.pub >authorized_keys1.3 编写Dockerfile 下面是Dockerfile的内容及各部分的注释,可以对比上一节中利用docker commit命令创建镜像过程,所进行的操作基本一致: #设置继承镜像 FROM ubuntu:14.04 #提供一些作者的信息 MAINTAINER docker_user (user@docker.com) #下面开始运行更新命令 RUN apt-get update #安装ssh服务 RUN apt-get install -y openssh-server RUN mkdir -p /var/run/sshd RUN mkdir -p /root/.ssh #取消pam限制 RUN sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd #复制配置文件到相应位置,并赋予脚本可执行权限 ADD authorized_keys /root/.ssh/authorized_keys ADD run.sh /run.sh RUN chmod 755 /run.sh #开放端口 EXPOSE 22 #设置自启动命令 CMD ["/run.sh"]1.4 创建镜像 在sshd_ubuntu目录下,使用docker build命令来创建镜像。这里需要注意最后还有一个“.”,表示使用当前目录中的Dockerfile: $ cd sshd_ubuntu $ docker build -t sshd:Dockerfile . 如果大家使用Dockerfile创建自定义镜像,那么需要注意的是Docker会自动删除中间临时创建的层,还需要注意每一步的操作和编写的 Dockerfile中命令的对应关系。 命令执行完毕后,如果读者可见“Successfully built XXX”字样,则说明镜像创建成功。可以看到,以上命令生成的镜像ID是 570c26a9de68。在本地查看sshd:Dockerfile镜像已存在: $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE sshd Dockerfile 570c26a9de68 4 minutes ago 246.5 MB sshd ubuntu 7aef2cd95fd0 12 hours ago 255.2 MB busybox latest e72ac664f4f0 3 weeks ago 2.433 MB ubuntu 14.04 ba5877dc9bec 3 months ago 192.7 MB ubuntu latest ba5877dc9bec 3 months ago 192.7 MB 1.5 测试镜像,运行容器 使用刚才创建的sshd:Dockerfile镜像来运行一个容器。直接启动镜像,映射容器的22端口到本地的10122端口: $ docker run -d -p 10122:22 sshd:Dockerfile 890c04ff8d769b604386ba4475253ae8c21fc92d60083759afa77573bf4e8af1 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 890c04ff8d76 sshd:Dockerfile "/run.sh" 4 seconds ago Up 3 seconds 0.0.0.0:10122->22/tcp high_albattani 在宿主主机新打开一个终端,连接到新建的容器: $ ssh 192.168.1.200 -p 10122 root@890c04ff8d76:~# 效果与前面一致,镜像创建成功。 2. 镜像的多级构建 Dockerfile > 镜像(写Dockerfile文件,然后docker build -t 镜像名 Dockerfile文件路径 =镜像) 17.09以后的版本才支持,此版本之后的Ddockerfile文件中可以添加多个FROM,多级,前几级都只是充当基础编译型语言才能支持多级构建,如:golong语言,java语言等。解释性语言如php,ruby,python等不支持多级构建即:如何制作镜像时将 镜像做到最小。 安装 Docker 最新版软件 yum install -y yum-utils device-mapper-persistent-data lvm2 #先安装最新版docker所需的依赖及工具 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo #配置yum源 yum install -y docker-ce #安装软件 mkdir /etc/docker #创建 /etc/docker 目录 配置 daemon.注入以下内容: cat > /etc/docker/daemon.json <<EOF { "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "insecure-registries": ["harbor.hongfu.com"], "registry-mirrors": ["https://kfp63jaj.mirror.aliyuncs.com"] } EOFmkdir -p /etc/systemd/system/docker.service.d systemctl daemon-reload && systemctl restart docker #重启docker服务 systemctl enable docker #设为开机自启 写Dockerfile文件,在文件中可以写多个FROM调用镜像,还可以给每个被调用的镜像重命名,方便后续的FROM调用前面的FROM例如:Dockerfile文件 FROM golang:1.7.3 WORKDIR /go/src/github.com/sparkdevo/href-counter/ RUN go get -d -v golang.org/x/net/html COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/sparkdevo/href-counter/app . #--from=0即调用第一个FROM CMD ["./app"]3. Docker image Build 高级3.1 镜像 Cache 机制 Docker Daemnon 通过 Dockerfile 构建镜像时,当发现即将新构建出的镜像与已有的某镜像重复时,可以选择放弃构建新的镜像, 而是选用已有的镜像作为构建结果,也就是采取本地已经 cache 的镜像作为结果 3.2 Cache 机制的注意事项 ADD 命令与 COPY 命令:Dockerfile 没有发生任何改变,但是命令ADD run.sh / 中 Dockerfile 当前目录下的 run.sh 却发生了 变化,从而将直接导致镜像层文件系统内容的更新,原则上不应该再使用cache。那么,判断 ADD 命令或者 COPY 命令后 紧接 的文件是否发生变化,则成为是否延用cache 的重要依据。Docker 采取的策略是:获取 Dockerfile 下内容(包括文件 的部分 inode 信息),计算出一个唯一的 hash 值,若 hash 值未发生变化,则可以认为文件内容没有发生变化,可以使 用 cache 机制;反之亦然 RUN 命令存在外部依赖:一旦 RUN 命令存在外部依赖,如RUN apt-get update,那么随着时间的推移,基于同一个基础镜像, 一年的 apt-get update 和一年后的 apt-get update, 由于软件源软件的更新,从而导致产生的镜像理论上应该不同。如果继 续使用cache 机制,将存在不满足用户需求的情况。Docker 一开始的设计既考虑了外部依赖的问题,用户可以使用参数 --no-cache 确保获取最新的外部依赖,命令为docker build --no-cache -t="my_new_image" . 树状的镜像关系决定了,一次新镜像的成功构建将导致后续的 cache 机制全部失效:这一点很好理解,一旦产生一个新的镜 像,同时意味着产生一个新的镜像 ID,而当前宿主机环境中肯定不会存在一个镜像,此镜像ID的父镜像 ID 是新产生镜像 的ID。这也是为什么,书写 Dockerfile 时,应该将更多静态的安装、配置命令尽可能地放在 Dockerfile 的较前位置 3.3 命名方式的 stage 命名方式的 stage:旧版本的 docker 是不支持 multi-stage 的,只有 17.05 以及之后的版本才开始支持 FROM golang:1.7.3 as builder WORKDIR /go/src/github.com/sparkdevo/href-counter/ RUN go get -d -v golang.org/x/net/html COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /go/src/github.com/sparkdevo/href-counter/app . CMD ["./app"]3.4 Google 内部精简镜像 git clone https://github.com/GoogleContainerTools/distroless #访问不到用下方地址代理 hub.fastgit.org/GoogleContainerTools/distroless 👑👑👑结束语👑👑👑为大家推荐一款刷题神奇 各大互联网大厂面试真题。基础题库到进阶题库等各类面试题应有尽有!牛客网面经合集,满足大厂面试技术深度,快速构建Java核心知识体系大厂面试官亲授,备战面试与技能提升,主要考点+主流场景+内功提升+真题解析
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录系统功能介绍1.准备工作1.1 关闭防火墙和selinux2.安装docker2.1 更新yum索引2.2 卸载旧版本docker2.3 安装依赖包2.4 设置阿里云镜像源2.5 安装并启动docker3.docker中部署mysql3.1 安装启动mysql3.2 进入mysql实例4.docker中部署redis4.1 安装启动mysql4.2 查看容器是否正常启动5.部署启动ferry5.1 获取本机ip5.2 安装git命令并拉取ferry代码5.3 修改ferry配置文件5.4 创建needinit文件6.启动ferry6.1 创建ferry容器并启动6.2 查看容器状态7.登录工单系统页面 👑👑👑结束语👑👑👑开源软件ferry是集工单统计、任务钩子、权限管理、灵活配置流程与模版等等于一身的开源工单系统,当然也可以称之为工作流引擎。 致力于减少跨部门之间的沟通,自动任务的执行,提升工作效率与工作质量,减少不必要的工作量与人为出错率。系统功能介绍工单系统相关功能: 工单提交申请 工单统计 多维度工单列表,包括(我创建的、我相关的、我待办的、所有工单) 自定义流程 自定义模版 任务钩子 任务管理 催办 转交 手动结单 加签 多维度处理人,包括(个人,变量 (创建者、创建者负责人)) 排他网关,即根据条件判断进行工单跳转 并行网关,即多个节点同时进行审批处理 通知提醒(目前仅支持邮件) 流程分类管理 权限管理相关功能,使用 casbin 实现接口权限控制: 用户、角色、岗位的增删查改,批量删除,多条件搜索 角色、岗位数据导出 Excel 重置用户密码 维护个人信息,上传管理头像,修改当前账户密码 部门的增删查改 菜单目录、跳转、按钮及 API 接口的增删查改 登陆日志管理 左菜单权限控制 页面按钮权限控制 API 接口权限控制 本次部署环境为CentOS7操作系统1.准备工作1.1 关闭防火墙和selinux[root@xiaopeng ~]# setenforce 0 #关闭selinux [root@xiaopeng ~]# systemctl stop firewalld #关闭防火墙 [root@xiaopeng ~]# systemctl disable firewalld #设置开机不自启2.安装docker2.1 更新yum索引[root@xiaopeng ~]# yum makecache fast2.2 卸载旧版本docker[root@xiaopeng ~]# yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine2.3 安装依赖包[root@xiaopeng ~]# yum install -y yum-utils device-mapper-persistent-data lvm22.4 设置阿里云镜像源[root@xiaopeng ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo [root@xiaopeng ~]# ls /etc/yum.repos.d/ #查看有docker-ce.repo生成 bak CentOS-7.repo docker-ce.repo epel.repo2.5 安装并启动docker[root@xiaopeng ~]# yum install -y docker-ce #安装 [root@xiaopeng ~]# systemctl start docker #启动docker [root@xiaopeng ~]# systemctl disable docker #设为开机自启动 [root@xiaopeng ~]# docker version #查看docker版本3.docker中部署mysql3.1 安装启动mysql[root@xiaopeng ~]# docker run -d -p3306:3306 --name=mysql5 -e MYSQL_ROOT_PASSWORD=111111 mysql:5 #密码设置为111111如下图,镜像下载并完成容器启动,等待即可:3.2 进入mysql实例[root@xiaopeng ~]# docker exec -it mysql5 bash #进入mysql容器 root@df379c1e9abe:/# mysql -uroot -h127.0.0.1 -p111111 #登录mysql mysql> create database ferry; #创建数据库ferry mysql> exit #退出mysql root@df379c1e9abe:/# exit #退出容器4.docker中部署redis4.1 安装启动mysql[root@xiaopeng ~]# docker run --name=redis6.0 -d -p 6379:6379 redis:6.0如下图,镜像下载并完成容器启动,等待即可:4.2 查看容器是否正常启动[root@xiaopeng ~]# docker ps -a #STATUS列显示UP状态为容器正常启动,如下图:5.部署启动ferry5.1 获取本机ip[root@xiaopeng ~]# ip a5.2 安装git命令并拉取ferry代码[root@xiaopeng ~]# yum -y install git [root@xiaopeng ~]# git clone https://github.com/lanyulei/ferry.git #拉取代码成功如下图:5.3 修改ferry配置文件[root@xiaopeng ~]# cd ferry/ [root@xiaopeng ferry]# vim config/settings.yml 找到如下配置: database: dbtype: mysql host: ferry_mysql name: ferry password: 123456 port: 3306 username: root 做如下修改: mysql 配置(更改host 和密码为你自己的): host: ferry_mysql -> host: 192.168.0.3 password: 123456 -> password: 111111 找到如下配置: redis: url: redis://ferry_redis:6379 做如下修改: redis 配置(更改host为你自己的): url: redis://ferry_redis:6379 -> url: redis://192.168.0.3:63795.4 创建needinit文件[root@xiaopeng ferry]# touch config/needinit注意:在config 目录新建 needinit 文件, 第一次启动的时候db中没有数据,此时可以通过这命令初始化数据,服务正常启动后再删该文件(以防下次容器启动时候再次初始化)6.启动ferry6.1 创建ferry容器并启动[root@xiaopeng ferry]# docker run -itd --name ferry -v /root/ferry/config:/opt/workflow/ferry/config -p 8002:8002 lanyulei/ferry:1.0.1 # 命令解释 # docker run -it -v 宿主机目录绝对路径:容器目录绝对路径 镜像ID或NAME /bin/bash # -it 交互式运行容器 # -d 在后台运行容器,并且打印容器id # --name ferry 容器名称为ferry # -v 挂载volume数据卷 # 宿主机目录绝对路径 宿主机中config配置文件目录所在路径。挂载之后容器运行可以将当前目录的配置文件挂载到容器内指定的目录调用 # -p 8002:8002 端口映射,注意:p小写是将容器的端口映射到宿主机的制定端口,大写是将容器的端口映射到宿主机的随机端口若失败可以下载我安装成功后打包下来的配置文件模板直接进行修改 ,配置文件模板如下图,镜像下载并完成容器启动,等待即可:6.2 查看容器状态[root@xiaopeng ferry]# docker ps -a # mysql、redis、ferry三个容器状态都为UP则正确,如下图:7.登录工单系统页面浏览器访问ip:8002即可,如下图:输入账号:admin输入密码:123456登录进入,如图: 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录临时分叉叔块(Uncle Block)驱动着程序运行的汽油(Gas)以太坊的区块奖励实践一下除了购买矿机、连接矿池、卖币套现之外,是否有人关注过以太坊的奖励机制呢?临时分叉 区块链由于是一种去中心化的技术,全世界所有的矿工同时工作,各自独立的挖掘满足要求的区块。由于是各自独立的工作,就有可能出现两个独立的矿工先后发现了两个不同的满足要求的区块,就像下面这种情况,被称为临时分叉。注:箭头指向某区块代表它保存了前一个区块的Hash 两位矿工都发现了高度2的区块,那么该采用谁的呢?于是撕逼开始,区块链是个势利眼,只承认最长的链,黄色和绿色的区块谁先有后继区块,变成最长的链,谁就会被承认,失败的就会被抛弃。为了成为最长的链,两个矿工都拼命的把自己挖到的区块通过广播的方式告诉更多的节点,并希望他们能把自己的区块传播得更广,从而使更多的矿工在自己挖出的区块下挖掘下一个区块,最终让自己的区块变成最长链的一部分。然而,胜利者只有一个,胜利者写历史,失败者将被抛弃,其中的交易会重新被打包到之后的区块中。下面这张图就是绿色区块获得了胜利,黄色区块成为孤儿区块,被抛弃。 这种事其实每时每刻都在上演,并不罕见。区块如果被废弃了,其中包含的挖矿奖励怎么办呢?对比特币来说,赢者通吃,失败者一无所有,竹篮打水一场空。挖出黄区块的矿工心都要碎了,他在哭泣?。叔块(Uncle Block) 以太坊创造了一个新的名词叔块(Uncle Block)。对高度3的区块来说,绿色区块是他的父区块。黄色区块虽然失败了,但好歹也是高度1的区块的子区块,绿区块的兄弟区块。于是,高度3的区块就尊称这个黄区块为叔叔,叔块就是这么得名的。注意:虚线部分仅仅用来陈述关系,不表示有实际连接。 不能成为主链一部分的孤儿区块,如果有幸被后来的区块通过uncles字段收留进区块链就变成了叔块。如果一个孤儿区块没有被任何区块收留,这个孤儿区块还是会被丢弃,不会进入区块链,也就是说孤儿区块被收留后才会变成叔块。 以太坊的设计比比特币人性的多,叔块也是可以获得奖励的,矿工们再也不用担心白忙乎了。而且以后的区块谁要是把叔块收留了,收留了叔块的区块还有额外的奖励,收留叔块也被称为包含叔块。下图就是高度3的区块包含了一个叔块,不过叔块也就仅仅是被包含而已,叔块中的交易会重新回归交易池,等待重新打包。一个区块最多只能包含2个叔块。 以太坊为什么要这么设计呢?因为以太坊的区块时间是20秒左右,相对于比特币,更容易出现临时分叉和孤儿区块。而且较短的区块时间,也使得区块在整个网络中更难以充分传播,尤其是对那些网速慢的矿工,这是一种极大的不公平。为了平衡各方利益,才设计了这样一个叔块机制。叔块在全部挖掘出来的区块中占的比例叫叔块率,目前叔块率在9.7%左右。 数据来源:The ethereum blockchain explorer 驱动着程序运行的汽油(Gas) 以太坊是一个运行智能合约的去中心化平台,提供了一个以太坊虚拟机(Ethereum Virtual Machine),简称EVM,开发者可以在其上开发各种应用。你可以把这个EVM想像成你的电脑,它能够运行一些以太坊定义的指令。与比特币的脚本引擎不同,以太坊的EVM功能非常强大,号称“图灵完备”。先不管什么是“图灵完备”,你只要知道“图灵完备”的虚拟机可以实现循环语句,有了循环就一定会有小坏蛋或者不合格的程序员弄出死循环,电脑死循环了大不了死机,重启就好,不过以太坊是去中心化的,EVM要是死循环了,可没法重启。有没有办法能解决这个问题呢?很遗憾,这个问题很多年前就有人研究过了,叫图灵停机问题(The Halting Problem),已经证明不存在一种能够检测程序是否会死循环的方法。既然不能检测,还有没有别的方法阻止死循环呢? 如果让EVM上的程序的每条指令都要消耗一点儿“资源”,“资源”用光了,无论程序执行完没有,都会被强行终止,这样无论是不是死循环都没关系了。这个执行程序时要消耗的资源就被称为汽油(Gas),每一条指令都要消耗不同数量的汽油。 举几个例子:ADD:加法操作 3GasMUL:乘法操作 5GasSUB:减法操作 3GasDIV:除法操作 5GasHASH:计算哈希值 30Gas 越复杂的运算,需要消耗的Gas越多,只要给程序加上一个消耗Gas的上限,就可以防止程序出现死循环而不能停止的情况了。同时,以太坊还给每个区块包含的程序消耗的总Gas设定了上限,以免区块中包含的程序过多,影响一些性能比较弱的节点。每个区块能消耗的Gas上限也是可以调整的,由矿工们进行投票决定,目前是6725538Gas,也就是下图中的GAS LIMIT部分。数据来源:Ethereum Network Status汽油在现实生活中不是免费的,在以太坊中也不是,要用以太币购买Gas。每个程序都会给出他们愿意用多少以太币购买1单位的Gas,这被称为汽油价格(Gas Price)。 每个程序需要为Gas支付的以太币可以用如下公式计算: Gas花费 = 消耗的Gas数量 x Gas的价格你愿意支付的Gas价格越高,你的交易就会越快被矿工打包,这和比特币的交易费很类似。 以太坊的区块奖励前面介绍了叔块和Gas,下面进入核心部分,以太坊的奖励机制。前面已经说过了,以太坊的区块有两种,普通区块和叔块,我们需要分情况来讨论每种区块的奖励。 普通区块奖励: 固定奖励5ETH,每个普通区块都有 区块内包含的所有程序的Gas花费的总和 如果普通区块包含了叔块,每包含一个叔块可以得到固定奖励5ETH的1/32,也就是0.15625ETH。 叔块奖励:叔块的奖励计算有些复杂,公式为: 叔块奖励 = ( 叔块高度 + 8 - 包含叔块的区块的高度 ) * 普通区块奖励 / 8 实践一下 以太坊区块浏览器Ethereum Blocks Information,这个浏览器可以很详细的查看每个区块的奖励。我们来看一个刚挖出来的区块4222300,由于我们是在主链上看到它的,所以它是普通区块。 它的奖励包含三部分: 固定奖励:5ETH Gas总花费(也有人称之为交易费):0.281837168043699381ETH 将两个叔块包含进来的奖励:5 * ( 1 / 32 ) * 2 = 0.3125ETH 这里有一点要注意,官方文档中的原文是“an extra reward for including uncles as part of the block”,我在2015年刚接触以太坊时不少网上的文章直接说成了“包含叔块奖励”,使我误以为是得到与挖掘出这些叔块得到的奖励等同数额的奖励,也就是上图中的Uncles Reward:8.75ETH,这是错误的,“包含叔块奖励”指的是将叔块包含进区块链这个行为的奖励,希望大家能够避免踩进这个坑。 我们再来看一个叔块0x1c2cbba0403f1079dcdb70e5971a87ce0fbc03d4572be30e2d17e4e4a0f136d5,是不是看着挺别扭,叔块不方便用高度来表示,因为同一个高度上已经有了个主链区块,就是这么惨。其实叔块也是有高度的,叔块的父区块的高度+1就是叔块的高度。 直接代入公式:( 4222271 + 8 - 4222272 ) * 5 / 8 = 4.375ETH 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. 下载以太坊2. 开始安装2.1 选择测试网络2.2 输入密码2.3 注意事项2.4 安装完成,进入主界面3. 开启以太坊3.1 找到geth 命令3.2 在Geth安装目录下放置genesis.json3.3 初始化 3.4 启动 3.5 开始3.5.1 新建 一个账户3.5.2 开始3.5.3 停止3.5.4 打开客户端3.5.5 运行错误 👑👑👑结束语👑👑👑 以太坊(Ethereum)是一个运行智能合约的去中心化平台(Platform for Smart Contract),平台上的应用按程序设定运行。以太坊平台由 Golang、C++、Python 等多种编程语言实现。 1. 下载以太坊官方下载地址下载之后解压,运行2. 开始安装2.1 选择测试网络前期选测试网络, 后期再讲主干网络2.2 输入密码密码不能忘记 网络好的情况下需要1小时, 我用了2个半小时 2.3 注意事项马上下载完成了, 就不动了。 这个时候果断关闭然后重新打开,OK 。 出现如下界面 2.4 安装完成,进入主界面 3. 开启以太坊 这一步骤需要调用后台命令geth 操作geth的全称是go-ethereum,是一个以太坊客户端,用go语言编写,应该是目前最常用的客户端 3.1 找到geth 命令 找到目录: C:\Users\zzy\AppData\Roaming\Ethereum Wallet\binaries\Geth\unpacked找不到的把zzy 换成Administrator 试试。注意:请将隐藏文件显示。不会请自行百度 3.2 在Geth安装目录下放置genesis.jsonGenesis.json内容:{ "nonce":"0x0000000000000042", "mixhash":"0x0000000000000000000000000000000000000000000000000000000000000000", "difficulty": "0x4000", "alloc": {}, "coinbase":"0x0000000000000000000000000000000000000000", "timestamp": "0x00", "parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "PICC GenesisBlock", "gasLimit":"0xffffffff" } 保存到目录: C:\Users\zzy\AppData\Roaming\Ethereum Wallet\binaries\Geth\unpacked (任何位置都可以, 为了方便把目录先固定) 3.3 初始化 在C盘建立一个空文件夹 C:\ethereum命令:geth --datadir "C:\ethereum" init genesis.json 注意:geth 命令需要设置环境变量才能用, 如果不会设置,直接cd 到 C:\Users\zzy\AppData\Roaming\Ethereum Wallet\binaries\Geth\unpacked 目录使用: 如下: OK ,出现上图信息表示运行成功,后查看 C:\ethereum 目录多了几个文件: 3.4 启动 命令如下:geth --networkid 9999 --datadir C:\ethereum --dev console #启动 出现上图所示,为启动成功如果启动不成功, 先关闭以太坊客户端,再试。 3.5 开始3.5.1 新建 一个账户personal.newAccount(‘123.abc’) #新建 一个账户 3.5.2 开始miner.start();3.5.3 停止miner.stop();把上述命令复制到终端,windows不太好用自己输入有问题。 3.5.4 打开客户端看到这些跳动的数字了吧!这是你自己W的K。 3.5.5 运行错误“Fatal: Error starting protocol stack: Access is denied.”这错误是 因为你开启了2个进程, 这个时候你需要关闭 ethereum客户端 。 👑👑👑结束语👑👑👑
1. 智能合约前端页面2. 配置web服务器3. 创建前端页面3.1 index.html3.2 app.js1. 智能合约前端页面开发智能合约的前端页面,让用户可以通过前端页面与智能合约交互。这个页面的主要功能是:显示当前连接的帐户读取智能合约中存储的value值更新智能合约中存储的value值页面大概的样子:编辑为开发前端页面,需要完成下面几项工作:配置web服务器,用来部署页面创建前端的h5、js文件2. 配置web服务器首先,让我们来配置web服务器。服务器使用lite-server,安装lite-server:$ npm install lite-server --save-dev项目根目录下,创建lite-server的配置文件bs-config.json,内容如下:{ "server": { "baseDir": [ "./src", "./build/contracts" ], "routes": { "/vendor": "./node_modules" } } }baseDir配置告诉lite-server将./src和./build/contracts目录作为web服务器的根目录,所有文件都可以被访问routes把./node_modules映射为/vendor,在引用文件时,可以使用/vendor3. 创建前端页面项目根目录下,创建src目录,用于存放前端页面。前端页面包含2个文件:src/index.html src/app.js3.1 index.html添加index.html页面,内容如下:<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>以太坊 DApp Demo</title> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <h1>账号: <span id="account"></span></h1> <hr> <div id="content"> <h2>智能合约:MyContract</b></h2> <p>获取智能合约中的value值: <span id="value"></span></p> <h5>设置value值</h5> <form onSubmit="App.set(); return false;" role="form"> <div > <input id="newValue" type="text"></input> </div> <button type="submit" >设置</button> </form> </div> <div id="loader">正在加载...</div> </div> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="https://etherscan.io/jss/web3.min.js"></script> <script src="vendor/@truffle/contract/dist/truffle-contract.js"></script> <script src="app.js"></script> </body> </html>这个文件的重点是引入了几个js文件:web3.min.js – web3.js库文件,直接从https://etherscan.io/引入truffle-contract.js – truffle提供的处理智能合约的库文件安装@truffle/contract$ npm install @truffle/contract --save-dev3.2 app.js添加javascript脚本文件:app.jsApp = { web3Provider: null, contracts: {}, account: '0x0', loading: false, contractInstance: null, init: async () => { // 加载web3 await App.loadWeb3() // 加载智能合约 await App.loadContract() // 网页刷新 await App.render() }, // https://medium.com/metamask/https-medium-com-metamask-breaking-change-injecting-web3-7722797916a8 loadWeb3: async () => { if (typeof web3 !== 'undefined') { App.web3Provider = web3.currentProvider web3 = new Web3(web3.currentProvider) } else { window.alert("Please connect to Metamask.") } // MetaMask新版本… if (window.ethereum) { window.web3 = new Web3(ethereum) try { // 向用户请求帐户访问 await ethereum.enable() // 用户允许使用账户 web3.eth.sendTransaction({/* ... */ }) } catch (error) { // 用户拒绝使用账户 } } // MetaMask老版本… else if (window.web3) { App.web3Provider = web3.currentProvider window.web3 = new Web3(web3.currentProvider) // 无需向用户请求,可以直接使用账号 web3.eth.sendTransaction({/* ... */ }) } // 没有安装以太坊钱包插件(MetaMask)... else { console.log('需要安装以太坊钱包插件(例如MetaMask)才能使用!') } }, loadContract: async () => { const contract = await $.getJSON('MyContract.json') App.contracts.MyContract = TruffleContract(contract) App.contracts.MyContract.setProvider(App.web3Provider) }, render: async () => { // 如果正在加载,直接返回,避免重复操作 if (App.loading) { return } // 更新app加载状态 App.setLoading(true) // 设置当前区块链帐户 const accounts = await ethereum.enable() App.account = accounts[0] $('#account').html(App.account) // 加载智能合约 const contract = await App.contracts.MyContract.deployed() App.contractInstance = contract const value = await App.contractInstance.get() $('#value').html(value) App.setLoading(false) }, set: async () => { App.setLoading(true) const newValue = $('#newValue').val() await App.contractInstance.set(newValue, {from: App.account}) window.alert('更新成功,页面值不会马上更新,等待几秒后多刷新几次。') App.setLoading(false) }, setLoading: (boolean) => { App.loading = boolean const loader = $('#loader') const content = $('#content') if (boolean) { loader.show() content.hide() } else { loader.hide() content.show() } } } $(document).ready(function () { App.init() });在上面的代码中:loadWeb3()函数添加了用Metamask将web浏览器连接到区块链所需的配置。这是直接从Metamask的配置规范中复制粘贴的,如函数上面代码注释中的url所示。loadContract()函数使用TruffleContract库创建智能合约的javascript对象,可以用于调用智能合约中的函数。render()函数设置页面内容,包括帐户及智能合约的value值。现在启动服务器:$ npm run dev然后使用安装了MetaMask插件的Chrome浏览器,打开网址:http://localhost:3000,就可以查看前端页面,与区块链上的智能合约进行交互。
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录以太坊经典(Ethereum Classic)以太坊经典的难度计算实例为什么要延迟难度炸弹以太坊经典(Ethereum Classic)以太坊经典(Ethereum Classic)是由以太坊(Ethereum)分叉而来的另一种区块链系统。 分叉的起因是2016年6月发生的轰动事件:The DAO攻击,黑客盗取了The DAO众筹合约中的以太币。关于如何应对这个事件,社区内有两种观点: 既然大家都同意,并且可以达成新的“共识规则”,那么区块链的内容是可以修改的。 修改区块链的内容违背了区块链不可篡改的初衷,任何情况下区块链的内容都不应修改。 这两个观点各自都有支持者,支持观点1的人决定通过硬分叉的方式将以太币追回,并归还给原来的众筹参与者(这个分支也就是现在的以太坊)。支持观点2的人决定继续在原来的链上挖矿(这个分支就是现在的以太坊经典)。最后,大约在北京时间2016年7月20日晚9点20分左右,区块高度1,920,000,以太坊正式进行硬分叉,产生了两条链。 以太坊1,920,000区块 以太坊经典1,920,000区块 可以看到,在同样的高度1,920,000,出现了两个Hash不同的区块,分叉正式完成。从此以太坊和以太坊经典走上了各自的发展道路,天各一方,不相往来。以太坊和以太坊经典自分叉后就是两个区块链系统,其上的电子币虽然都叫以太币,但是互不通用。为了区分,一般把以太坊中的以太币简称ETH,把以太坊经典中的以太币简称ETC。 注意:千万不要把以太坊和以太坊经典的地址弄混,一旦把ETH发到ETC地址,或者ETC发到ETH地址,将会导致以太币丢失。以太坊经典的难度计算以太坊经典的代码同样是开源的,区块链也完全公开,没什么问题是读一遍代码弄不明白的,如果有,就再读一遍。func CalcDifficulty(config *ChainConfig, time, parentTime uint64, parentNumber, parentDiff *big.Int) *big.Int { num := new(big.Int).Add(parentNumber, common.Big1) // increment block number to current f, fork, configured := config.GetFeature(num, "difficulty") if !configured { return calcDifficultyFrontier(time, parentTime, parentNumber, parentDiff) } name, ok := f.GetString("type") if !ok { name = "" } // will fall to default panic switch name { case "ecip1010": if length, ok := f.GetBigInt("length"); ok { explosionBlock := big.NewInt(0).Add(fork.Block, length) if num.Cmp(explosionBlock) < 0 { return calcDifficultyDiehard(time, parentTime, parentDiff, fork.Block) } else { return calcDifficultyExplosion(time, parentTime, parentNumber, parentDiff, fork.Block, explosionBlock) } } else { panic(fmt.Sprintf("Length is not set for diehard difficulty at %v", num)) } case "homestead": return calcDifficultyHomestead(time, parentTime, parentNumber, parentDiff) case "frontier": return calcDifficultyFrontier(time, parentTime, parentNumber, parentDiff) default: panic(fmt.Sprintf("Unsupported difficulty '%v' for block: %v", name, num)) } }可以发现,以太坊经典对难度计算做了些小手脚,增加了两个新的算法calcDifficultyDiehard和calcDifficultyExplosion。感兴趣的小伙伴可以在这里找到和难度计算相关的全部代码:core/block_validator.go,代码中使用的配置参数在这里core/data_chainconfig.go 相比以太坊,以太坊经典修改了难度炸弹部分。3,000,000区块之前,难度计算方法与以太坊相同。3,000,000到5,000,000区块,使用calcDifficultyDiehard算法,难度炸弹暂停增长。5,000,000区块之后,使用calcDifficultyExplosion算法,难度炸弹恢复增长。 这个对难度炸弹的修改在以太坊经典的开发社区中被称为“难度炸弹延迟”。与比特币的BIP类似,以太坊经典也把共识规则的更新用文档记录下来,称为以太坊经典改进提议(Ethereum Classic Improvement Proposal),简称ECIP。这次难度炸弹延迟被记录于ECIP1010。难度计算公式的其余两项:父区块难度和难度调整,以太坊经典依然沿用了以太坊的计算方法。 实例 以太坊经典的用户较少,我只找到了两个区块链浏览器:https://gastracker.io/https://etcchain.com/遗憾的是,他们都不能精确显示难度。不过没关系,可以在自己的机器上运行一个Geth,同步完成后就可以查看所有区块的信息了。由于Geth是命令行工具,没有漂亮的界面。如果有谁知道更好的以太坊经典区块浏览器,请留言告知我,我会及时更新。还是随意挑一个区块,比如4,421,544,让我们来计算一下它的难度。 想计算区块的难度,需要知道这些信息: parent_timestamp:上一个区块产生的时间 parent_diff:上一个区块的难度 block_timestamp:当前区块产生的时间 block_number:当前区块的序号 在Geth的Javascript控制台中可以使用debug.printBlock(blockNumber)函数查看区块的详细信息,首先我们查一下父区块4,421,543:$echo -e `geth --exec "debug.printBlock(4421543)" attach` Block(#4421543): Size: 837.00 B { MinerHash: f4d068b903a67890cac5ddaff94a66d99a0464dfeef1c0bab3c7b3a7bf2965e4 Header(2762936eb0879e86c2b0309a69fdfa3566bb3101e3cf344acf54d2534f2f3498): [ ParentHash: 40f5cd2ec0be0e819c8689307c24d38edb75450358922286ec2c48e532bca427 UncleHash: 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 Coinbase: df7d7e053933b5cc24372f878c90e62dadad5d42 Root: de7c6ce1ea8c74644eba0579d42163c969e2c6b04cd57c1059470ca124b618ba TxSha 497f1a6fd4d13db0f2eb4d90c11670485e5305a39a6782ec68417a9b060ae5f2 ReceiptSha: 57bd177107249827f9a85e2d2b28d53180be4e4fb5069d0a1fd90f3822aa0097 Bloom: 00000000000000000000000000000000000000000400000000000000000000000000000000000000000000000080000008000000000000000000000000000000000100000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000800000000000000000100000000000400000000000000000400000000000000000000000 Difficulty: 132145956937056 Number: 4421543 GasLimit: 4700036 GasUsed: 58209 Time: 1504744551 Extra: ETC ethermine - EU2 MixDigest: 7f09929b10732bb47d2473adaf172d5dfc146d560a10a6ab98c80538135ad351 Nonce: 6960c3d00b731872 ] 从父区块中,我们知道了: parent_timestamp = 1504744551 parent_diff = 132145956937056 再来查一下4,421,544区块:$echo -e `geth --exec "debug.printBlock(4421544)" attach` Block(#4421544): Size: 1.55 kB { MinerHash: 89a0ecdc93471ef468f96dcb97c757b744a4a93b1fe142bed6f416552c2fd818 Header(601d2019900d12781fbc0788ca3b55535b4ba6ee5337e5d7dce822860e3f9699): [ ParentHash: 2762936eb0879e86c2b0309a69fdfa3566bb3101e3cf344acf54d2534f2f3498 UncleHash: 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347 Coinbase: 9eab4b0fc468a7f5d46228bf5a76cb52370d068d Root: 1c59d3ab4e415e217f7544b38bd73bcdda21d56865315af8fafcf7bb8726c9a9 TxSha 0ff3a73cedb8687b652df84baa46a98f6cdc24d5fa6a5e4e8335839ba7938304 ReceiptSha: 2d45de0f095c9655204fca5c1e93db9f203b284c1e7b981a30693a9971dc3f3c Bloom: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000080000000000000000020000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000 Difficulty: 132081700979477 Number: 4421544 GasLimit: 4704624 GasUsed: 254912 Time: 1504744574 Extra: nanopool.org MixDigest: 278b40bb8843841d6d5fcf843beeb0aeb2948b953696d884c367d5716685946e Nonce: 2850d644089a46c5 ] 从区块中,我们知道了: block_timestamp = 1504744574 block_number = 4421544 难度公式:block_diff = parent_diff + 难度调整 + 难度炸弹 难度调整 =parent_diff // 2048 * MAX(1 - (block_time
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. 端口映射实现访问容器1.1 从外部访问容器应用1.2 映射所有接口地址1.3 映射到指定地址的指定端口1.4 映射到指定地址的任意端口1.5 查看映射端口配置2. 互联机制实现便捷互访2.1 自定义容器命名2.2 容器互联3. 总结 👑👑👑结束语👑👑👑 在实践中,经常会碰到需要多个服务组件容器共同协作的情况,这往往需要多个容器之间有能够互相访问到对方的服务。除了通过网络访问外,Docker还提供了两个很方便的功能来满足服务访问的基本需求:一个是允许映射容器内应用的服务端口到本地宿主主机;另一个是互联机制实现多个容器间通过容器名来快速访 问。本章将分别讲解这两个很实用的功能。 1. 端口映射实现访问容器1.1 从外部访问容器应用 在启动容器的时候,如果不指定对应的参数,在容器外部是无法通过网络来访问容器内的网络应用和服务的。当容器中运行一些网络应用,要让外部访问这些应用时,可以通过-P 或-p参数来指定端口映射。当使用-P(大写的)标记时,Docker会随机映射一个49000~49900的端口到内部容器开放的网络端口: [root@localhost ~]# docker run -d -P training/webapp python app.py [root@localhost ~]# docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse 此时,可以使用docker ps看到,本地主机的49155被映射到了容器的 5000端口。访问宿主主机的49155端口即可访问容器内Web应用提供的界面。同样,可以通过docker logs命令来查看应用的信息: [root@localhost ~]# docker logs -f nostalgic_morse * Running on http://0.0.0.0:5000/ 10.0.2.2 - - [23/May/201420:16:31] "GET / HTTP/1.1" 200 - 10.0.2.2 - - [23/May/201420:16:31] "GET / favicon.ico HTTP/1.1" 404 - -p(小写的)可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有IP:HostPort:ContainerPort|IP:: ContainerPort|HostPort:ContainerPort。 1.2 映射所有接口地址 使用HostPort:ContainerPort格式将本地的5000端口映射到容器的5000端口,可以执行: [root@localhost ~]# docker run -d -p 5000:5000 training/webapp python app.py 此时默认会绑定本地所有接口上的所有地址。多次使用-p标记可以绑定多个端口。例如: [root@localhost ~]# docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.py1.3 映射到指定地址的指定端口 可以使用IP:HostPort:ContainerPort格式指定映射使用一个特定地址,比如localhost地址127.0.0.1: [root@localhost ~]# docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py1.4 映射到指定地址的任意端口 使用IP::ContainerPort绑定localhost的任意端口到容器的5000端口,本地主机会自动分配一个端口: [root@localhost ~]# docker run -d -p 127.0.0.1::5000 training/webapp python app.py 还可以使用udp标记来指定udp端口: [root@localhost ~]# docker run -d -p 127.0.0.1:5000:5000/udptraining/webapp python app.py1.5 查看映射端口配置 使用docker port命令来查看当前映射的端口配置,也可以查看到绑定的地址:注意:容器有自己的内部网络和IP地址,使用docker inspect+容器ID可以获取容器的具体信息。 2. 互联机制实现便捷互访 容器的互联(linking)是一种让多个容器中应用进行快速交互的方式。它会在源和接收容器之间创建连接关系,接收容器可以通过容器名快速访问到源容器,而不用指定具体的IP地址。 2.1 自定义容器命名 连接系统依据容器的名称来执行。因此,首先需要定义一个好记的容器名字。 虽然当创建容器的时候,系统默认会分配一个名字,但自定义容器名字有两个好处: ※ 自定义的命名比较好记,比如一个Web应用容器,我们可以给它起名叫web,一目了然; ※ 当要连接其他容器时,即便重启,也可以使用容器名而不用改变,比如连接web容器到db容器。 使用--name标记可以为容器自定义命名: [root@localhost ~]# docker run -d -P --name webtraining/webapp python app.py 使用docker ps来验证设定的命名: [root@localhost ~]# docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES aed84ee21bde training/webapp:latest python app.py 12 hours ago Up 2 seconds 0.0.0.0:49154->5000/tcp web 也可以使用docker inspect来查看容器的名字: [root@localhost ~]# docker inspect -f "{{ .Name }}" aed84ee21bde /web 注意:容器的名称是唯一的。如果已经命名了一个叫web的容器,当你要再次使用web这个名称的时候,需要先用docker rm来删除之前创建的同名容 器。在执行docker run的时候如果添加--rm标记,则容器在终止后会立刻删除。注意,--rm和-d参数不能同时使用。 2.2 容器互联 使用--link参数可以让容器之间安全地进行交互。下面先创建一个新的数据库容器: [root@localhost ~]# docker run -d --name dbtraining/postgres 删除之前创建的web容器: [root@localhost ~]# docker rm -f web 然后创建一个新的web容器,并将它连接到db容器: [root@localhost ~]# docker run -d -P --name web --linkdb:dbtraining/webapp python app.py 此时,db容器和web容器建立互联关系: --link参数的格式为--link name:alias,其中name是要连接的容器名称,alias是这个连接的别名。使用docker ps来查看容器的连接,如下所示: [root@localhost ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 349169744e49 training/postgres:latest su postgres -c '/usr About a minute ago Up About a minute 5432/tcp db, web/db aed84ee21bde training/webapp:latest python app.py 16 hours ago Up 2 minutes 0.0.0.0:49154->5000/tcp web 可以看到自定义命名的容器,db和web,db容器的names列有db也有web/db。这表示web容器连接到db容器,这允许web容器访问db容器的信息。Docker相当于在两个互联的容器之间创建了一个虚机通道,而且不用映射它们的端口到宿主主机上。在启动db容器的时候并没有使用-p和-P标记,从而避免了暴露数据库服务端口到外部网络上。Docker通过两种方式为容器公开连接信息: ※ 更新环境变量; ※ 更新/etc/hosts文件。 使用env命令来查看web容器的环境变量: [root@localhost ~]# docker run --rm --name web2 --link db:db training/webapp env . . . DB_NAME=/web2/db DB_PORT=tcp://172.17.0.5:5432 DB_PORT_5000_TCP=tcp://172.17.0.5:5432 DB_PORT_5000_TCP_PROTO=tcp DB_PORT_5000_TCP_PORT=5432 DB_PORT_5000_TCP_ADDR=172.17.0.5 . . . 其中DB_开头的环境变量是供web容器连接db容器使用的,前缀采用大写的连接别名。除了环境变量之外,Docker还添加host信息到父容器的/etc/hosts文件。下面是父容器web的hosts文件: [root@localhost ~]# docker run -t -i --rm --link db:db training/webapp /bin/bash root@aed84ee21bde:/opt/webapp# cat /etc/hosts 172.17.0.7 aed84ee21bde . . . 172.17.0.5 db 这里有两个hosts信息,第一个是web容器,web容器用自己的id作为默认主机名,第二个是db容器的IP和主机名。可以在web容器中安装ping命令来测试与db容器的连通: root@aed84ee21bde:/opt/webapp# apt-get install -yqq inetutils-ping root@aed84ee21bde:/opt/webapp# ping db PING db (172.17.0.5): 48 data bytes 56 bytes from 172.17.0.5: icmp_seq=0 ttl=64 time=0.267 ms 56 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.250 ms 56 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.256 ms 用ping来测试db容器,它会解析成172.17.0.5。用户可以连接多个子容器到父容器,比如可以连接多个web到同一个db容器上。 3. 总结 毫无疑问,容器服务的访问是很关键的一个用途。本篇文章通过具体案例讲解了Docker容器服务访问的两大基本操作,包括基础的容器端口映射机制和容器互联机制。同时,Docker目前可以成熟地支持Linux系统自带的网络服务和功能,这既可以利用现有成熟的技术提供稳定支持,又可以实现快速的高性能转发。在生产环境中,网络方面的需求更加复杂多变,包括跨主机甚至跨数据中心的通信,这时候往往就需要引入额外的机制,例如SDN(软件定义网络)或NFV(网络功能虚拟化)的相关技术。 👑👑👑结束语👑👑👑
1. 连接公链以太坊公链除了主网,还有多个测试网络。主网(Mainnet)是正式的以太坊网络,里面的以太币是真正有价值的,测试网络中的以太币没有价值,只用于测试。我们最终目标是连接到主网,但先连接到测试网络Kovan,虽然本地区块链网络(Ganache)也能测试,但与公链还是有区别的。连接到公链的步骤如下:设置钱包来管理公链帐户连接到以太坊节点更新项目设置访问以太坊节点1.1设置钱包首先需要设置一个钱包,来管理我们的公链帐户。简单起见,可以借用Ganache本地区块链钱包,由于区块链的工作原理,这个钱包在公共区块链和本地区块链上都是有效的。打开Ganache,主界面上可以看到一个名为“MNEMONIC”的部分: 这是一个种子短语,用于构建由Ganache管理的钱包。我们可以使用这个种子短语加密重建钱包,来连接到公链。复制这个值,保存到一个秘密文件,MNEMONIC是一个秘密值,需要保密。在项目根目录中创建一个.env文件,保存MNEMONIC值,如下所示:MNEMONIC="你的mnemonic"1.2 连接以太坊节点现在已经创建了钱包,下一步需要访问Ethereum节点,以便连接到公共区块链网络。有几种方法可以做到这一点,可以使用Geth或Parity运行自己的Ethereum节点。但这需要从区块链下载大量数据并保持同步,很麻烦。比较方便的方法是,使用Infura访问Ethereum节点。Infura是一个免费提供Ethereum节点的服务。在Infura上注册账号,创建项目,在项目详情页上可以查看API KEY: 使用API KEY,就可以访问以太坊网络节点。在.env文件中添加Infura api key的配置:INFURA_API_KEY="https://kovan.infura.io/v3/543526cd4d3846acbc3826484e934564" MNEMONIC="你的mnemonic"1.3 更新项目设置接下来使用MNEMONIC与INFURA_API_KEY,更新项目的网络配置,以便连接到公共区块链网络。修改truffle-config.js文件:// 导入dotenv库创用于读取`.env`文件中的设置 require('dotenv').config(); // 导入truffle-hdwallet-provider库重建钱包 const HDWalletProvider = require('truffle-hdwallet-provider'); module.exports = { networks: { development: { host: "127.0.0.1", // Localhost (default: none) port: 7545, // Standard Ethereum port (default: none) network_id: "*", // Any network (default: none) }, // Useful for deploying to a public network. // NB: It's important to wrap the provider as a function. kovan: { provider: () => new HDWalletProvider( process.env.MNEMONIC, process.env.INFURA_API_KEY ), gas: 5000000, gasPrice: 25000000000, network_id: 42 }, }, solc: { optimizer: { enabled: true, runs: 200 } } }可以看到,我们使用了.env配置文件中的MNEMONIC与INFURA_API_KEY配置了kovan网络。由于用到了dotenv与truffle-hdwallet-provider这2个库,我们需要先安装:切换到项目目录,执行以下命令npm install dotenv --save-devnpm install truffle-hdwallet-provider --save-dev注意 安装truffle-hdwallet-provider时,如果出现node-gyp相关的错误,可参考这里解决。1.4 访问以太坊节点使用truffle console连接到公共区块链网络:$ truffle console --network kovan要验证连接,可以从区块链中读取一些数据,获取一些关于最新区块的信息,在控制台上执行:web3.eth.getBlock('latest').then(console.log)输出:{ author: '0x03801efb0efe2a25ede5dd3a003ae880c0292e4d', difficulty: '340282366920938463463374607431768211454', extraData: '0xde830206028f5061726974792d457468657265756d86312e33362e30826c69', gasLimit: '0x7a1200', gasUsed: '0x17d23', hash: '0xc7390c4f492c8c1da60608135fc9e05930123b645b39f221cba33d8b3c577b2a', logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000080000000000000000000100000008000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400800000000000010000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000008000000', receiptsRoot: '0x3d05bb2ed4fcc90234eea6d840e7d0e3ce7f598a15e5314536b17bcd11c78b5b', sealFields: [ '0x84175e8801', '0xb84155a8cdb108dccec1d314124058fa6f22e7400ee200db0a94b7b165e4c3454c1818cc05f815cb7ce48f7a88b8401515740311a3566d9cf079428d506a6daca50101' ], sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', signature: '55a8cdb108dccec1d314124058fa6f22e7400ee200db0a94b7b165e4c3454c1818cc05f815cb7ce48f7a88b8401515740311a3566d9cf079428d506a6daca50101', size: 877, stateRoot: '0x03af5adce52a81ce5d332cddb9955e344214bff00859b78868116e1e839efdf7', step: '392071169', timestamp: 1568284676, totalDifficulty: '4524524338444961608702071789512829094373049115', transactions: [ '0xded7fed0842fd65ec808bc3652ec4175bc190acc11345c49c44b1fb5d954610f', '0x7e9112a46fa3c07aad813ea86355b15eebb44023c040d198ee7d15d379bbc2be' ], transactionsRoot: '0x0dd10d90686dda2684bd0ba70d1c9e1d9a5302c30ca75eb2c5b07a7b6e4498b9', uncles: [] }可以看到,已经成功连接到了公链。2. 部署智能合约到公链现在,我们将智能合约部署到公链。步骤如下:部署需要消耗Gas,获取测试以太币用于部署部署智能合约验证部署2.1获取测试以太币部署需要消耗Gas,Gas需要支付以太币,我们部署到的是公链测试网Kovan,网络中的以太币没有市场价值。可以从Kovan faucet Gitter聊天室获取测试用的伪以太币。只需把钱包地址发送出去,约5分钟内,有人会给你发测试用的伪以太币。打开Ganache并复制列表中第一个帐户的地址(钱包地址),类似下面所示:0x29920e756f41F8e691aE0b12D417C19204371E91发送到聊天室内,稍等片刻,你的账号将收到一笔以太币。2.2 部署智能合约现在帐户里已经有了资金,可以进行部署了。执行部署命令:truffle migrate --network kovan一旦部署完成,应该会看到部署成功的消息。部署命令执行详情:G:\qikegu\ethereum\mydapp>truffle migrate --network kovan Compiling your contracts... =========================== > Everything is up to date, there is nothing to compile. Migrations dry-run (simulation) =============================== > Network name: 'kovan-fork' > Network id: 42 > Block gas limit: 0x7a1200 ... Starting migrations... ====================== > Network name: 'kovan' > Network id: 42 > Block gas limit: 0x7a1200 1_initial_migration.js ====================== Deploying 'Migrations' ---------------------- > transaction hash: 0x7e30b5c716afed45888a9dd2d6af7e6f52a9fade0346e8ad7d0c268de508a26a > Blocks: 2 Seconds: 9 > contract address: 0x168A7247B58786edd259502948f5Bf9449C863AD > block number: 13447029 > block timestamp: 1568294312 > account: 0x29920e756f41F8e691aE0b12D417C19204371E91 > balance: 2.993465175 > gas used: 261393 > gas price: 25 gwei > value sent: 0 ETH > total cost: 0.006534825 ETH > Saving migration to chain. > Saving artifacts ------------------------------------- > Total cost: 0.006534825 ETH 2_deploy_contracts.js ===================== Deploying 'MyContract' ---------------------- > transaction hash: 0xc1f7ec8fee1a23e3d08d0c9e9d6e15fef24feb8ba163e0071dccb1bb90cc0eca > Blocks: 0 Seconds: 0 > contract address: 0x4D3CFaF8457CEA76c0409f989f9870115B4d2d82 > block number: 13447036 > block timestamp: 1568294340 > account: 0x29920e756f41F8e691aE0b12D417C19204371E91 > balance: 2.9850534 > gas used: 294448 > gas price: 25 gwei > value sent: 0 ETH > total cost: 0.0073612 ETH > Saving migration to chain. > Saving artifacts ------------------------------------- > Total cost: 0.0073612 ETH Summary ======= > Total deployments: 2 > Final cost: 0.013896025 ETH Summary ======= > Total deployments: 2 > Final cost: 0.013896025 ETH2.3 验证部署现在打开truffle控制台,与kovan测试网络上的智能合约进行交互:$ truffle console --network kovan在控制台中执行:truffle(kovan)> MyContract.deployed().then((c) => { contract = c })然后:truffle(kovan)> contract.get() 'myValue' truffle(kovan)> contract.set("hello world") { tx: '0x7bf63444f3a7bd70e981a7bd49228b1cf1a8c3754daf64c4c7765b8eee46bf37', receipt: { blockHash: '0xe03d0f43d85f4e41c18a90aa563ebda08899c6b9c38d0cd7779937046e2aed0c', blockNumber: 13447763, contractAddress: null, cumulativeGasUsed: 33629, from: '0x29920e756f41f8e691ae0b12d417c19204371e91', gasUsed: 33629, logs: [], logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', root: null, status: true, to: '0x4d3cfaf8457cea76c0409f989f9870115b4d2d82', transactionHash: '0x7bf63444f3a7bd70e981a7bd49228b1cf1a8c3754daf64c4c7765b8eee46bf37', transactionIndex: 0, rawLogs: [] }, logs: [] } truffle(kovan)> contract.get() 'hello world'可以看到智能合约已经成功部署。3. truffle脚本Truffle包含一个脚本运行器,可对以太坊网络执行自定义脚本。让我们创建一个脚本并执行。在项目根目录下,创建script.js文件,内容如下:module.exports = function(callback) { web3.eth.getBlock('latest').then(console.log) }该脚本将从Kovan测试网络获取最新区块的信息。执行脚本:truffle exec script.js --network kovan输出:{ author: '0x596e8221a30bfe6e7eff67fee664a01c73ba3c56', difficulty: '340282366920938463463374607431768211454', extraData: '0xde830205058f5061726974792d457468657265756d86312e33362e30826c69', gasLimit: '0x7a1200', gasUsed: '0x5e61', hash: '0x225a1e0b13fd20396af60d049ce9bb94c2f3f7df06c7db260880b62c91997004', logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', miner: '0x596e8221A30bFe6e7eFF67Fe
1. 使用 truffle console 访问智能合约truffle console 是区块链开发人员的强大工具,这是一个命令行工具,可以在命令行中执行javascript代码,与智能合约进行交互。这对于开发智能合约非常有用。我们已经成功地将智能合约部署到本地区块链网络,接下来我们将使用 truffle console 与智能合约进行交互。启动 truffle console:$ truffle console进入控制台后,让我们获取已部署智能合约的一个实例,看看能否从该合约中读取value值。从控制台运行以下代码:MyContract.deployed().then((instance) => { app = instance } )这里MyContract是之前在迁移文件中创建的变量名称,使用deployed()函数获取一个已部署合约的实例,并将其分配给promise回调函数中的一个app变量。现在可以获取智能合约中的value值:app.get() // => 'myValue'给value设置一个新值:app.set('New Value')重新获取智能合约中的value值:app.get() // => 'New Value'可以通过以下命令退出truffle console:.exit2. 智能合约测试(truffle test)类似Java中JUnit单元测试工具,Trfuffle test可以帮助我们对智能合约项目进行白盒测试。对于区块链项目,测试显得尤其重要,因为部署合约、迁移合约的成本都是相当高的,都要消耗Gas。2.1 编写测试代码现在让我们对前面章节中创建的智能合约,编写一些测试代码。整个测试过程模拟对智能合约MyContract获取value值、设置value值的过程。先确保MyContract已经正确部署到Ganache本地区块链网络中。测试中将会用到Mocha测试框架,与Chai断言库,但Truffle已经集成了这些库。测试代码用JavaScript编写,模拟与智能合约的交互,就像使用truffle console所做的那样。在项目根目录下的test目录中,添加测试脚本文件: MyContract.jsMyContract.js中的测试代码:// 首先,`require`合约并将其分配给一个变量`MyContract` const MyContract = artifacts.require('./MyContract.sol'); // 调用“contract”函数,并在回调函数中编写所有测试 // 回调函数提供一个“accounts”变量,表示本地区块链上的所有帐户。 contract('MyContract', (accounts) => { // 第1个测试:调用get()函数,检查返回值,测试合约中value初始值是否是: 'myValue' it('initializes with the correct value', async () => { // 获取合约实例 const myContract = await MyContract.deployed() const value = await myContract.get() // 使用断言测试value的值 assert.equal(value, 'myValue') }) // 第2个测试: 调用set()函数来设置value值,然后调用get()函数来确保更新了值 it('can update the value', async () => { const myContract = await MyContract.deployed() myContract.set('New Value'); const value = await myContract.get() assert.equal(value, 'New Value') }) })代码说明,请见注释。2.2 运行测试脚本执行命令行运行测试:$ truffle test测试详情:G:\qikegu\ethereum\mydapp>truffle test Using network 'development'. Compiling your contracts... =========================== > Everything is up to date, there is nothing to compile. Contract: MyContract √ initializes with the correct value (76ms) √ can update the value (78ms) 2 passing (188ms)
1. Ganache本地区块链首先启动Ganache,创建本地的以太坊区块链网络。1.1 主界面本地区块链可以模拟公共区块链,开发人员可以在本地区块链上测试智能合约。打开Ganache,界面如下图所示:本地区块链缺省有10个外部账号,每个账号都有100个假的以太币,这些可以通过设置改变。Ganache界面中有下面几个主要页面:ACCOUNTS – 账号页面,这显示了自动生成的所有帐户及其余额。BLOCKS – 区块页面,显示了在本地区块链网络上挖掘的每个区块,及其Gas成本和包含的交易。TRANSACTIONS – 交易页面,列出了在本地区块链上发生的所有交易。CONTRACS – 合约页面EVENTS – 事件页面LOGS – 日志页面界面顶部的搜索栏,可以让你搜索本地区块链网络上的区块或交易。1.2 设置可以通过设置来定制Ganache的一些功能,单击主界面右上角的设置图标进入设置页面。以下是一些主要设置:SERVER – 服务器设置页面,管理关于网络连接的详细信息,比如网络id、端口、主机名和自动挖掘状态。ACCOUNTS & KEYS – 帐户和密钥页,设置自动生成的帐户数量及其余额,缺省10个账号,每个账号余额是100 ether。CHAIN – 链页,让你为网络设置Gas限制和Gas价格。高级设置 – 日志选项设置,比如保存日志文件和配置详细输出的能力。请注意,在更改了新的设置之后,必须Restart(设置页面右上角)才能生效。2. 开发智能合约我们将使用truffle创建一个智能合约项目,该智能合约的功能是可以获取值和设置值2.1 初始化项目首先创建项目目录:$ mkdir mydapp $ cd mydapp然后使用truffle init初始化项目,将生成项目模板文件:$ truffle init我们可以查看一下生成的项目目录:G:\qikegu\ethereum\mydapp>tree /f 卷 数据 的文件夹 PATH 列表 卷序列号为 0C52-9CF4 G:. │ truffle-config.js │ ├─contracts │ Migrations.sol │ ├─migrations │ 1_initial_migration.js │ └─testcontracts 目录 智能合约源文件目录,现在已经有了一个Migrations.sol源文件,功能是迁移/部署/升级智能合约。migrations 目录 迁移文件目录,迁移文件都是javascript脚本,帮助我们把智能合约部署到以太坊。test 目录 测试代码目录。truffle-config.js 文件 Truffle项目配置文件,例如可以在里面配置网络。2.2 添加package.json文件package.json是npm用来管理包的配置文件,在项目根目录下创建此文件,内容如下:{ "name": "ethereum-demo", "version": "1.0.0", "description": "以太坊demo", "main": "truffle-config.js", "directories": { "test": "test" }, "scripts": { "dev": "lite-server", "test": "echo \"Error: no test specified\" && sexit 1" }, "author": "kevinhwu@qikegu.com", "license": "ISC", "devDependencies": { "@truffle/contract": "^4.0.33", "dotenv": "^8.1.0", "lite-server": "^2.5.4", "truffle-hdwallet-provider": "^1.0.17" } }关于依赖的包,用到时逐个安装。2.3 添加智能合约源文件 在contracts 目录中创建一个新文件MyContract.sol,内容如下所示:// 声明solidity版本 pragma solidity ^0.5.0; // 声明智能合约MyContract,合约的所有代码都包含在花括号中。 contract MyContract { // 声明一个名为value的状态变量 string value; // 合约构造函数,每当将合约部署到网络时都会调用它。 // 此函数具有public函数修饰符,以确保它对公共接口可用。 // 在这个函数中,我们将公共变量value的值设置为“myValue”。 constructor() public { value = "myValue"; } // 本函数读取值状态变量的值。可见性设置为public,以便外部帐户可以访问它。 // 它还包含view修饰符并指定一个字符串返回值。 function get() public view returns(string memory ) { return value; } // 本函数设置值状态变量的值。可见性设置为public,以便外部帐户可以访问它。 function set(string memory _value) public { value = _value; } }这个智能合约的功能是可以获取值和设置值。2.4 编译项目现在让我们编译项目:项目目录下执行命令:$ truffle compile等编译完成,可以看到多了一个build目录,该目录下生成了新文件:./build/contract/MyContract.json这个文件是智能合约ABI文件,代表“抽象二进制接口”。这个文件有很多作用,其中2个重要作用:作为可在Ethereum虚拟机(EVM)上运行的可执行文件包含智能合约函数的JSON表示,以便外部客户端可以调用这些函数3. 部署智能合约到Ganache接下来,我们将编译好的智能合约部署到本地的Ganache区块链网络。步骤如下:更新项目的配置文件,修改网络配置连接到本地区块链网络(Ganache)。创建迁移脚本,告诉Truffle如何部署智能合约。运行新创建的迁移脚本,部署智能合约。3.1 更新配置文件更新项目的配置文件,修改网络配置连接到本地区块链网络(Ganache)。打开位于项目根目录下的truffle-config.js文件,修改内容如下:module.exports = { networks: { development: { host: "127.0.0.1", port: 7545, network_id: "*" // Match any network id } }, solc: { optimizer: { enabled: true, runs: 200 } } }这些网络配置,包括ip地址、端口等,应该与Ganache的网络配置匹配:3.2 创建迁移脚本 接下来,我们将在migrations目录中创建迁移脚本,告诉Truffle如何部署智能合约,在该目录中创建文件2_deploy_contracts.js。注意,在migrations目录中所有文件都有编号,作用是让Truffle知道执行它们的顺序。2_deploy_contracts.js文件内容如下:var MyContract = artifacts.require("./MyContract.sol"); module.exports = function(deployer) { deployer.deploy(MyContract); };上面的代码中:首先,require了创建的合约,并将其分配给一个名为“MyContract”的变量。接着,将合约加入部署清单,运行迁移命令时合约将被部署。3.3 执行迁移命令现在让我们从命令行执行迁移命令, 部署智能合约。$ truffle migrate执行详情如下:G:\qikegu\ethereum\mydapp>truffle migrate Compiling your contracts... =========================== > Everything is up to date, there is nothing to compile. Starting migrations... ====================== > Network name: 'development' > Network id: 5777 > Block gas limit: 0x6691b7 1_initial_migration.js ====================== Deploying 'Migrations' ---------------------- > transaction hash: 0xe62fb8a27c9ccc894562fbd7a7797526ad9323ab67a44516ae342642bf4ffcc6 > Blocks: 0 Seconds: 0 > contract address: 0x168A7247B58786edd259502948f5Bf9449C863AD > block number: 1 > block timestamp: 1568189958 > account: 0x29920e756f41F8e691aE0b12D417C19204371E91 > balance: 99.99477214 > gas used: 261393 > gas price: 20 gwei > value sent: 0 ETH > total cost: 0.00522786 ETH > Saving migration to chain. > Saving artifacts ------------------------------------- > Total cost: 0.00522786 ETH 2_deploy_contracts.js ===================== Deploying 'MyContract' ---------------------- > transaction hash: 0xe9dcef6f70332e476684e8f93ab96969af53920555161054f1f4bcc6277116fb > Blocks: 0 Seconds: 0 > contract address: 0x4D3CFaF8457CEA76c0409f989f9870115B4d2d82 > block number: 3 > block timestamp: 1568189959 > account: 0x29920e756f41F8e691aE0b12D417C19204371E91 > balance: 99.98804272 > gas used: 294448 > gas price: 20 gwei > value sent: 0 ETH > total cost: 0.00588896 ETH > Saving migration to chain. > Saving artifacts ------------------------------------- > Total cost: 0.00588896 ETH Summary ======= > Total deployments: 2 > Final cost: 0.01111682 ETH receipt: { transactionHash: '0x83be6ef86fe542b3c94ae1dd5f2e04570c199d6b2e7997af60f3d91cda9259ec', transactionIndex: 0, blockHash: '0x6e58c2c77b5998004b8a8c66760ca923814865307c69f1c779673cc2cbca06bc', blockNumber: 5, from: '0x29920e756f41f8e691ae0b12d417c19204371e91', to: '0x4d3cfaf8457cea76c0409f989f9870115b4d2d82', gasUsed: 33501, cumulativeGasUsed: 33501, contractAddress: null, logs: [], status: true, logsBloom: '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1. 智能合约的概念智能合约(Smart contract )是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。智能合约概念于1995年由Nick Szabo首次提出。智能合约的目的是提供优于传统合约的安全方法,并减少与合约相关的其他交易成本。2. 智能合约的优点与传统合同相比,智能合约有一些显著优点:不需要中间人费用低代码就是规则区块链网络中有多个备份,不用担心丢失避免人工错误无需信任,就可履行协议匿名履行协议3. 智能合约开发3.1 支持智能合约的区块链 虽然以太坊(Ethereum)是最流行支持智能合约的区块链平台,但它并不是唯一支持智能合约的平台。超级账本(Hyperledger) 是Linux基金会于2015年发起的推进区块链数字技术和交易验证的开源项目。通过创建分布式账本的公开标准,实现虚拟和数字形式的价值交换,例如资产合约、能源交易、结婚证书、能够安全和高效低成本的进行追踪和交易。另外,还有其他很多区块链平台支持智能合约,可以参考相关资料。3.2 以太坊智能合约开发工具通常,开发智能合约需要用到工具:Mist – 以太坊节点/钱包。Truffle 框架 – 流行的以太坊开发框架,内置了智能合约编译、链接、部署等功能。Metamask – Chrome插件方式的以太坊节点/钱包。Remix – Remix是一个基于web浏览器的智能合约开发环境(IDE)。3.3 以太坊智能合约开发语言目前主要的智能合约开发语言是 Solidity语言,是一种开发以太坊智能合约的静态高级语言,语法类似于JavaScript。还有另外一些智能合约开发语言:VyperFlintIdris等等。4. 智能合约开发环境搭建4.1 准备工作为了构建开发智能合约或者dApp,我们需要安装以下模块:Node 与 NPMTruffle 框架GanacheMetamaskVScode 与 Solidity插件4.2 Node 与 NPMTruffle 框架依赖Node,需要使用npm安装。首先需要安装node,npm会同时安装,下载node,按提示安装。安装完后,可以验证一下node版本:$ node -v4.3 Truffle 框架Truffle框架是流行的以太坊开发框架,内置了智能合约编译、链接、部署等功能。使用npm安装Truffle框架:$ npm install -g truffle验证truffle安装:$ truffle --version Truffle v5.0.35 - a development framework for Ethereum ...4.4 Ganache在实际的以太坊网络上测试、部署Dapp或智能合约,需要消耗Gas。Ganache可以在本地创建区块链网络来测试我们的程序。可以从Truffle Framework网站下载Ganache来安装。它将创建一个本地区块链网络,给我们分配10个外部账号,每个帐户都有100个假的以太币。4.5 MetamaskMetamask是一个Chrome插件形式的以太坊节点/钱包。我们可以使用Metamask连接到本地区块链网络或实际的以太坊网络,并与我们的智能合约交互。要安装Metamask,请在谷歌Chrome web store中搜索Metamask Chrome插件并安装。一旦安装,请确保打开启用按钮。安装后,你会在Chrome浏览器的右上角看到狐狸图标。4.6 VS code 与 Solidity插件推荐使用vs code编辑器编写solidity代码,vs code可以安装一下Solidity插件,以便支持语法高亮功能。
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入! 目录1. 创世区块2. 主网络与测试网络3. 以太坊的测试网络3.1 Morden(已退役)3.2 Ropsten(区块链浏览器)3.3 Kovan(区块链浏览器)3.4 Rinkeby(区块链浏览器)4. 连接测试网络5. 获取测试网络上的以太币1. 创世区块 众所周知,区块链是一个类似于链表的结构,每一个区块都具有唯一的Hash值,后一个区块通过记录前一个区块的Hash值,来表明父子关系。一条区块链可以无限延伸,然而却一定要有一个开端,一个让这条区块链从无到有的区块,这个区块的名字就叫创世区块(Genesis Block)。创世区块最显著的特征就是没有父区块,通常创世区块不是由矿工挖掘出来的,而是预先生成好并将创世区块的Hash写进了钱包软件的代码中。在以太坊钱包的官方实现Geth的代码中,我们可以找到这个创世区块的Hash。 源代码:params/config.govar ( MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") // Mainnet genesis hash to enforce below configs on TestnetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") // Testnet genesis hash to enforce below configs on )其中的0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3就是创世区块的Hash了。到区块链浏览器中看一下,果然如此,高度为0的创世区块。编辑等等,下面那个TestnetGenesisHash = 0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d是什么鬼,为什么区块链浏览器中查不到?2. 主网络与测试网络前面说到一条区块链由一个创世区块开始,也就是说,一个创世区块可以创造和代表一条区块链。如果我们给钱包客户端设定不同的创世区块,它就将工作在不同的区块链上。工作在同一条区块链上的全部节点,我们称之为一个网络。 绝大多数人在使用的网络被称为主网络(Mainnet),用户在其上交易、构建智能合约,矿工在其上wakuang。由于使用的人数众多,主网络的鲁棒性很强,能够对抗攻击,区块链也不易被篡改,因此主网络是具有功能的,其上的以太币是有价值的。通常一种区块链只有一个主网络,比如比特币,莱特币,以太坊,都只有一个主网络。主网络之外可以有若干个测试网络。既然已经有了一个主网络,为什么还要弄出测试网络呢? 主网络中的以太币是有价值的,在主网络上直接进行钱包软件或者智能合约的开发将会是非常危险的,稍有不慎就会损失以太币,甚至影响整个主网络的运行。同时,因为主网络使用人数多,矿工更是不计其数,如果是在开发一个wakuang软件,用一台开发软件的笔记本电脑几乎不可能挖出一个区块,这就导致测试几乎不可行。于是,出于测试和学习的目的,便会有一小部分节点,使用与主网络不同的创世区块,开启一条全新的区块链,并在上面wakuang和测试,这就是测试网络(Testnet)。 3. 以太坊的测试网络 以太坊可以搭建私有的测试网络,不过由于以太坊是一个去中心化的平台,需要较多节点共同运作才能得到理想的测试效果,因此并不推荐自行搭建测试网络。以太坊公开的测试网络共有4个,目前仍在运行的有3个。每个网络都有自己的创世区块和名字,按开始运行时间的早晚,依次为: 3.1 Morden(已退役)Morden是以太坊官方提供的测试网络,自2015年7月开始运行。到2016年11月时,由于难度炸弹已经严重影响出块速度,不得不退役,重新开启一条新的区块链。Morden的共识机制为PoW。3.2 Ropsten(区块链浏览器) Ropsten也是以太坊官方提供的测试网络,是为了解决Morden难度炸弹问题而重新启动的一条区块链,目前仍在运行,共识机制为PoW。测试网络上的以太币并无实际价值,因此Ropsten的wakuang难度很低,目前在755M左右,仅仅只有主网络的0.07%。这样低的难度一方面使一台普通笔记本电脑的CPU也可以挖出区块,获得测试网络上的以太币,方便开发人员测试软件,但是却不能阻止攻击。PoW共识机制要求有足够强大的算力保证没有人可以随意生成区块,这种共识机制只有在具有实际价值的主网络中才会有效。测试网络上的以太币没有价值,也就不会有强大的算力投入来维护测试网络的安全,这就导致了测试网络的wakuang难度很低,即使几块普通的显卡,也足以进行一次51%攻击,或者用垃圾交易阻塞区块链,攻击的成本及其低廉。2017年2月,Ropsten便遭到了一次利用测试网络的低难度进行的攻击,攻击者发送了千万级的垃圾交易,并逐渐把区块Gas上限从正常的4,700,000提高到了90,000,000,000,在一段时间内,影响了测试网络的运行。攻击者发动这些攻击,并不能获得利益,仅仅是为了测试、炫耀、或者单纯觉得好玩儿。 3.3 Kovan(区块链浏览器) 为了解决测试网络中PoW共识机制的问题,以太坊钱包Parity的开发团队发起了一个新的测试网络Kovan。Kovan使用了权威证明(Proof-of-Authority)的共识机制,简称PoA。PoW是用工作量来获得生成区块的权利,必须完成一定次数的计算后,发现一个满足条件的谜题答案,才能够生成有效的区块。PoA是由若干个权威节点来生成区块,其他节点无权生成,这样也就不再需要wakuang。由于测试网络上的以太币无价值,权威节点仅仅是用来防止区块被随意生成,造成测试网络拥堵,完全是义务劳动,不存在作恶的动机,因此这种机制在测试网络上是可行的。Kovan与主网络使用不同的共识机制,影响的仅仅是谁有权来生成区块,以及验证区块是否有效的方式,权威节点可以根据开发人员的申请生成以太币,并不影响开发者测试智能合约和其他功能。Kovan目前仍在运行,但仅有Parity钱包客户端可以使用这个测试网络。 3.4 Rinkeby(区块链浏览器)Rinkeby也是以太坊官方提供的测试网络,使用PoA共识机制。与Kovan不同,以太坊团队提供了Rinkeby的PoA共识机制说明文档,理论上任何以太坊钱包都可以根据这个说明文档,支持Rinkeby测试网络,目前Rinkeby已经开始运行。4. 连接测试网络 目前开发人员最常用的测试网络是Rinkeby,我将演示一种最简单的连接和使用Rinkeby的方法。下载以太坊钱包:https://link.zhihu.com/?target=https%3A//github.com/ethereum/mist/releases根据使用的操作系统不同,在下图红框中选择合适的版本,下载解压。 MIST其实只是以太坊钱包的一个图形界面,后端还是官方的Geth,只是可以使用图形化的方式操作,减少了出错的几率,降低使用门槛。MIST是使用Electron开发的,具有跨平台的能力,所以在各个系统上的界面和操作应该是基本一致的。第一次启动时,记得选择测试网络。 在测试网络上创建一个钱包地址,并给钱包加个密码。 片刻之后,一个钱包地址就创建完成了,每个人的地址都不相同。 之后点击下面的“LAUNCH APPLICATION!”就可以进入主界面了。注意:一定要确保左下角有个红色的Rinkeby字样,这表明你正在Rinkeby测试网络中。点击右侧红框,就可以看到你的钱包地址的余额了,现在应该是0以太币。 首次启动时,需要同步区块链,这需要一段时间,大约1小时左右。5. 获取测试网络上的以太币 Rinkeby测试网络使用的是PoA共识机制,我们不能通过wakuang来获取以太币。想获取Rinkeby测试网络中的以太币,需要去申请,这个申请Rinkeby以太币的功能被称为水龙头(Faucet)。还真是挺形象的,水龙头会源源不断的产生以太币,并且受到权威节点控制,以确保不会被滥用。进入这个水龙头的网站:https://link.zhihu.com/?target=https%3A//faucet.rinkeby.io/ 为了确保不会出现有人滥用水龙头,无限生成Rinkeby以太币,水龙头需要借助github账号来确定申请者的身份和配额。没有Github账号的小伙伴记得要去注册一个,并且登陆。进入 https://link.zhihu.com/?target=https%3A//gist.github.com/把测试钱包账户的地址粘贴进去,并点击“Create public gist”,就像下图这样。 点击之后,把浏览器上方的地址复制下来,粘贴到水龙头页面的输入框中。并点击“Give me Ether”,有三种选项,前面是获得的以太币数量,后面是冷却时间,在冷却时间过后才能进行下一次以太币申请。 如果一切顺利,你会看到你的钱包地址已经多出了申请数量的以太币,我申请了3个以太币。 到Rinkeby的区块链浏览器查看一下,也能查到我的钱包地0xA54BF5C1059D24ac1943F332851a2F0779EDD026 如果申请的人数很多,需要排队等待一会儿,我自己在使用过程中,最多等过半小时,申请的以太币金额越大,一般需要等待越多的时间才能到账。为什么申请测试网络的以太币如此繁琐呢?以太币在以太坊平台中的设计功能是用来支付EVM中执行指令消耗的Gas,如果可以被无限制的产生,就会出现有恶意用户出于各种目的,用无限制的以太币换无限制的Gas,在EVM中执行超多的指令,并逐渐抬高区块Gas上限。EVM中的指令要在每一个以太坊节点中执行,这种攻击一旦出现,对网络将会产生很大的影响,所以测试网络中的以太币必须针对每个开发者限量供应。不过这个限量对正常的开发测试来说,几乎不会造成影响。 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1.基础命令2. 单一容器管理3. run 基础命令 👑👑👑结束语👑👑👑 Docker 指令的基本用法:docker + 命令关键字(COMMAND) + 一系列的参数 docker run --name MyWordPress --link db:mysql -p 8080:80 -d wordpress #docker run 运行#--name MyWordPress 指定容器名称,不指定则随机生成一个容器名。#--link db:mysql 链接的含义。当前MyWordPress容器想运行就必须要依赖另一个db容器(db容器调用别名mysql)db容器和mysql别名会写入到当前容器的/etc/hosts文件中#-p 8080:80 8080为访问物理机端口地址,80为docker容器内部的端口#-d wordpress 放到后台运行。容器如需运行的最低标准:必须拥有前台进程 1.基础命令命令1:docker info #守护进程的系统资源设置举例如下:命令2:docker search #镜像的查询举例如下:[root@localhost ~]# docker search nginx #只能查看镜像名,看不到版本号。命令3:docker pull #镜像的下载 举例如下:浏览器搜素https://c.163yun.com/hub#/home网易官方镜像,登录,点击镜像仓库,点击镜像中心,搜素centos,点击public/centos,点击版本,复制下载地址。hub.c.163.com/public/centos:6.7-tools [root@localhost ~]# docker pull hub.c.163.com/public/centos:7.2-tools #将版本号修改为需要的版本号用docker pull命令下载即可命令4:docker images #Docker镜像的查询举例如下:[root@localhost ~]# docker images #可以查看本地仓库下所有的镜像[root@localhost ~]# docker images --no-trunc #可以显示全部ID命令5:docker rmi #Docker镜像的删除 当一个镜像在某个容器中运行时候无法删除,只能强制删除。-f 选项可以强制删除镜像 命令6:docker ps #容器的查询举例如下:docker ps #容器的查询(当前正在工作的) docker ps -a #查询所有的容器 docker ps -a -q #简要化查询所有的容器(只输出ID号)命令7:docker run #容器的创建启动举例如下: 启动方式一: [root@localhost ~]# docker run -d hello-world:latest #镜像名:版本号 启动方式二: [root@localhost ~]# docker run -d feb5 #镜像ID号前四位命令8:docker start #容器启动 docker stop #容器停止命令9:docker rm #容器的删除 #-f 强制删除举例如下:[root@localhost ~]# docker rm -f $(docker ps -a -q) #强制删除所有容器命令10:镜像导入导出 docker save -o 镜像名.tar 镜像名:版本号 # 以打包的方式导出镜像到当前目录下docker load -i 镜像名.tar # 导入镜像,就不用再重新去下载了。 命令11:查看容器映射关系* docker port ContainerName #可以查看容器当前的映射关系 命令12:查看镜像制作历史 docker history 镜像名 --no trunc #可以查看镜像制作的历史命令2. 单一容器管理 每个容器被创建后,都会分配一个 CONTAINER ID 作为容器的唯一标示,后续对容器的启动、停止、修改、删除等所有操作,都是通过 CONTAINER ID 来完成,偏向于数据库概念中的主键. docker ps --no-trunc 查看 docker stop/start CONTAINERID 停止 docker start/stop MywordPress 通过容器别名启动/停止 docker inspect MywordPress 查看容器所有基本信息 docker logs MywordPress 查看容器日志(前台的输出信息) docker stats MywordPress 查看容器所占用的系统资源 docker exec 容器名 容器内执行的命令 容器执行命令 docker exec -it 容器名 /bin/bash或/bin/sh 登入容器的bash 3. run 基础命令命令1:--restart=always #容器的自动启动(容器会随着docker进程的启动而自动启动) 举例如下: [root@localhost ~]# docker images #先查询镜像,如下:[root@localhost ~]# docker run --name test1 -d b6306 #将wordpress镜像通过容器test1运行(为了做对比) [root@localhost ~]# docker run --name test2 --restart=always -d b6306 #将wordpress镜像通过容器test2自启动 [root@localhost ~]# docker ps -a #查看两个容器都是UP运行状态,如下图:[root@localhost ~]# systemctl stop docker [root@localhost ~]# systemctl start docker #重启容器 [root@localhost ~]# docker ps -a #再次查看发现设置了自启动的容器test2随着docker进程的启动而启动了,如图:命令2:-h x.xx.xx #设置容器主机名举例如下:[root@localhost ~]# docker run --name test3 -h axp -d b6306 #设置主机名为axp并以test3容器运行wordpress镜像 [root@localhost ~]# docker exec test3 hostname #查看当前主机名,如图:命令3:--dns xx.xx.xx.xx #设置容器使用的 DNS 服务器 --dns-search #DNS 搜索设置 设置搜索域,如:设置搜索域为xiaopeng.com,则访问www的时候会自动搜索成www.xiaopeng.com 命令4:--add-host hostname:IP #注入 hostname <> IP 解析举例如下:[root@localhost ~]# docker images #查询镜像 [root@localhost ~]# docker run --name test1 -h axp -d b6306 #将wordpress镜像通过容器test1运行 [root@localhost ~]# docker exec -it test1 /bin/bash #进入test1容器 root@axp:/var/www/html# cat /etc/host #查看到hostname<>ip解析如下图:root@axp:/var/www/html# hostname #查看主机名为axp,如图:[root@localhost ~]# docker run --name test2 --add-host axp:66.66.66.66 -d b6306 #将wordpress镜像通过容器test2运行并设置解析 [root@localhost ~]# docker exec -it test2 /bin/bash #进入test2容器 root@113a8f21e03a:/var/www/html# cat /etc/hosts #查看解析文件,如下图:root@113a8f21e03a:/var/www/html# exit #登出命令5:--rm #服务停止时自动删除举例如下:[root@localhost ~]# docker images #查看镜像 [root@localhost ~]# docker run --name test1 -d b630 #将wordpress镜像通过容器test1运行 [root@localhost ~]# docker run --name test2 --rm -d b630 #将wordpress镜像通过容器test2运行并设置随着docker进程的停止容器自动删除 [root@localhost ~]# docker ps -a #查看两个容器都是UP运行状态 [root@localhost ~]# systemctl restart docker #重启docker服务 [root@localhost ~]# docker ps -a #查看容器状态,发现test1容器虽然停止了但是还存在,test2容器已自动删除,如下图;命令6: docker run -it --rm b630 /bin/bash (充当测试机器。常用!)# /bin/bash 启动命令替换为解释器# -it 交互模式 tty接口# 进入容器内部发起测试,测试完成后exit退出,然后当前测试环境就会被自动删除了。 命令7: Dockfile文件生成镜像命令:docker build -t wangyang/nginx:v1.0 Dockfile文件路径镜像导出:docker save -o xx.xx.xx xx.xx.xx.tar镜像导入:docker load -i xx.xx.xx.tar将容器封装成镜像:docker commit nginx(容器名) axplinux/nginx:v0.1(镜像名) 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道第一名🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. Docker Hub1.1 Docker Hub注册和登录1.2 从官方仓库进行镜像拉取1.3 自动创建2. 阿里云镜像市场2.1 查看镜像2.2 下载镜像3. 搭建本地私有仓库3.1 使用registry镜像创建私有仓库3.2 Harbor-企业级docker私有仓库 👑👑👑结束语👑👑👑1. Docker Hub Docker Hub(如下图)是由Docker公司维护的一个注册中心。它拥有成千上万个镜像可供下载和运行。任何Docker用户都可以在上面创建免费账号及公共Docker镜像。除了用户提供的镜像,上面还维护着一些作为参考的官方镜像。 镜像受用户认证的保护,同时具有一个与GitHub类似的支持率打星系统。 这些官方镜像的表现形式可能是Linux发行版,如Ubuntu或Cent OS,或是预装软件包,如Node.js,或是完整的软件栈,如WordPress。 仓库(Repository)是集中存放镜像的地方,分公共仓库和私有仓库。一个容易与之混淆的概念是注册服务器(Registry)。实际上注册服务器是存放仓库的具体服务器,一个注册服务器上可以有多个仓库,而每个仓库下面可以有多个镜像。从这方面来说,可将仓库看做一个具体的项目或目录。例如对于仓库地址private- docker.com/ubuntu来说,private-docker.com是注册服务器地址,ubuntu是仓库名。 1.1 Docker Hub注册和登录 构建镜像中很重要的一环就是如何共享和发布镜像。可以将镜像推送到Docker Hub或者用户自己的私有Registry中。为了完成这项工作,需要在Docker Hub上创建一个账号,可以从 https://hub.docker.com/account/signup/加入Docker Hub 首先需要注册一个账号,并在注册之后通过收到的确认邮件进行激活。下面就可以测试刚才注册的账号是否能正常工作了。要登录到Docker Hub,可以使用docker login命令 [root@localhost ~]# docker login Username: jamtur01 Password: Email: james@lovedthanlost.net Login Succeeded 这条命令将会完成登录到Docker Hub的工作,并将认证信息保存起来以供后面使用。可以使用docker logout命令从一个Registry服务器退出。登录成功的用户可以上传个人制造的镜像。 1.2 从官方仓库进行镜像拉取 用户无需登录即可通过docker search命令来查找官方仓库中的镜像,并利用docker pull命令来将它下载到本地。根据是否为官方提供,可将这些镜像资源分为两类。一种是类似centos这样的基础镜像,称为基础或根镜像。这些镜像是由Docker公司创建、验 证、支持、提供。这样的镜像往往使用单个单词作为名字。还有一种类型,比如ansible/centos7-ansible镜像,它是由Docker用户ansible创建并维护的,带有用户名称为前缀,表明是某用户下的某仓库。可以通过用户名称前缀user_name/镜像名来指定使用某个用户提供的镜像。 另外,在查找的时候通过-s N参数可以指定仅显示评价为N星以上的镜像。下载官方centos镜像到本地,如下所示: [root@localhost ~]# docker pull centos Pullingrepositorycentos 0b443ba03958:Downloadcomplete 539c0211cd76:Downloadcomplete 511136ea3c5a:Downloadcomplete 7064731afe90:Downloadcomplete 用户也可以在登录后通过docker push命令来将本地镜像推送到Docker Hub 1.3 自动创建 Ansible是知名自动化部署配置管理工具。 自动创建(Automated Builds)功能对于需要经常升级镜像内程序来说,十分方便。有时候,用户创建了镜像,安装了某个软件,如果软件发布新版本则需要手动更新镜像。而自动创建允许用户通过Docker Hub指定跟踪一个目标网站(目前支持 GitHub或BitBucket)上的项目,一旦项目发生新的提交,则自动执行创建。 要配置自动创建,包括如下的步骤: 1)创建并登录Docker Hub,以及目标网站;*在目标网站中连接帐户到Docker Hub; 2)在Docker Hub中配置一个“自动创建”; 3)选取一个目标网站中的项目(需要含Dockerfile)和分支; 4)指定Dockerfile的位置,并提交创建。之后,可以在Docker Hub的“自动创建”页面中跟踪每次创建的状态。 2. 阿里云镜像市场 国内不少云服务商都提供了Docker镜像市场,下面阿里云镜像站为例,介绍如何使用这些市场。 2.1 查看镜像 访问https://developer.aliyun.com/mirror/,即可看到已存在的仓库和存储的镜像,包括Ubuntu、Java、Mongo、MySQL、Nginx等热门仓库和镜像。阿里云官方仓库中的镜像会保持跟DockerHub中官方镜像的同步。 以CentOS仓库为例,其中包括了centos6、centos7和centos8等镜像 2.2 下载镜像 以在linux中下载CentOS基础镜像为例: 1. 备份yum源: [root@localhost ~]# mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2. 下载新的 CentOS-Base.repo 到 /etc/yum.repos.d/centos8(centos6官方源已下线,建议切换centos-vault源) [root@localhost ~]# wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo或者:[root@localhost ~]# curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repocentos7:[root@localhost ~]# wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo或者:[root@localhost ~]# curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repocentos6(centos6官方源已下线,建议切换centos-vault源)[root@localhost ~]# wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-6.10.repo或者:[root@localhost ~]# curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-6.10.repo3. 搭建本地私有仓库3.1 使用registry镜像创建私有仓库 安装Docker后,可以通过官方提供的registry镜像来简单搭建一套本地私有仓库环境: [root@localhost ~]# docker run -d -p 5000:5000 registry 这将自动下载并启动一个registry容器,创建本地的私有仓库服务。 默认情况下,会将仓库创建在容器的/tmp/registry目录下。可以通过-v参数来将镜像文件存放在本地的指定路径。 [root@localhost ~]# docker run -d -p 5000:5000 -v /opt/data/registry:/tmp/registryregistry3.2 Harbor-企业级docker私有仓库 准备环境: [root@localhost ~]# python #查看python环境,必须是2.7或更高版本 docker引擎应为1.10或更高版本docker compose 为1.6.0或更高版本实验步骤: [root@localhost ~]# rz #上传harbor-offline-installer-v1.2.0.tgz压缩包 [root@localhost ~]# tar xf harbor-offline-installer-v1.2.0.tgz #解压缩 [root@localhost ~]# mv harbor /usr/local/ [root@localhost ~]# cd /usr/local/harbor/ [root@localhost harbor]# ls #查看 [root@localhost harbor]# vim harbor.cfg #打开配置文件,进行如下修改 1.将hostname = reg.mydomain.com仓库地址修改为hostname = hub.anxiaopeng.com2.将ui_url_protocol = http协议修改为ui_url_protocol = https 加密协议3.将db_password = root123数据库密码修改为db_password = axp1234564.max_job_workers = 3为同时只能从仓库下载3个镜像,根据需求修改查看到配置文件中的ssl_cert = /data/cert/server.crt和ssl_cert_key = /data/cert/server.key证书密钥私钥文件路径为/data/cert,所以 [root@localhost harbor]# mkdir -p /data/cert #创建证书存放路径 [root@localhost cert]# chmod -R 777 /data/cert [root@localhost cert]# openssl genrsa -des3 -out server.key 2048 #创建私钥,设置两次密码为123456 [root@localhost cert]# openssl req -new -key server.key -out server.csr #然后输入私钥密码和其他信息 [root@localhost cert]# cp server.key server.key.org #备份私钥 [root@localhost cert]# openssl rsa -in server.key.org -out server.key #退密码处理,将server.key的密码取消掉 [root@localhost cert]# openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt [root@localhost cert]# cd - 在windows的hosts文件中添加192.168.232.165 hub.anxiaopeng.com [root@localhost harbor]# ./install.sh 浏览器访问192.168.232.165,输入账号admin密码Harbor12345登录一个项目下可以存放很多个镜像,而且镜像可以是不同的。 [root@localhost harbor]# docker images #查看本地镜像 [root@localhost harbor]# docker tag centos:centos7.9.2009 hub.anxiaopeng.com/library/centos:centos7.9.2009 #在项目中标记镜像 [root@localhost harbor]# vim /etc/hosts #添加: 192.168.232.165 hub.anxiaopeng.com [root@localhost harbor]# vim /etc/docker/daemon.json #打开配置文件,修改内容,添加信任域名,注意不要加协议,注意逗号。 原内容: {"registry-mirrors": ["https://kfp63jaj.mirror.aliyuncs.com"]}修改后:{ "registry-mirrors": ["https://kfp63jaj.mirror.aliyuncs.com"], "insecure-registries":["hub.anxiaopeng.com"] }[root@localhost harbor]# docker login hub.anxiaopeng.com #输入账号admin和密码Harbor12345进行认证(docker logout hub.anxiaopeng.com可以移除认证) [root@localhost harbor]# docker push hub.anxiaopeng.com/library/centos:centos7.9.2009 #推送镜像到项目 刷新浏览器就可以看到镜像上传成功了。标签书代表子版本的个数。此方法上传的镜像是公开的,任何人不需要登录就可以直接访问到下载的url如果公司内部需要,则给不同的不能创建不同的普通用户,每个部门通过各自的普通用户进行上传和下载镜像。 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道第一名🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1.Docker守护进程详解2. 开放Docker守护进程3. 以守护进程方式运行容器4. 将Docker移动到不同分区5. Docker客户端6. 使用端口连接容器 👑👑👑结束语👑👑👑1.Docker守护进程详解 如下图,Docker守护进程是用户与Docker交互的枢纽,因而它是理解所有相关部分的最佳切入点。它控制着用户机器上的Docker访问权限,管理着容器与镜像的状态,同时代理着与外界的交互。 守护进程与服务器,守护进程是运行在后台的一个进程,不在用户的直接控制之下。服务器是负责接受客户端请求,并执行用于满足该请求所需的操作的一个进程。守护进程通常也是服务器,接收来自客户端的请求,为其执行操作。docker命令是一个客户端,而Docker守护进程则作为服务器对Docker容器和镜像进行操作。 我们来看几个技巧,这些技巧用于展示Docker作为守护进程高效运行,同时使用docker命令与其进行的交互被限制为执行操作的简单请求,就像与Web服务器进行交互一样。第一个技巧允许其他人连接你的Docker守护进程,并执行与你在宿主机上所能执行的相同操作,第二个技巧说明的是Docker容器是由守护进程管理的,而不是你的shell会话。 2. 开放Docker守护进程 虽然默认情况下Docker的守护进程只能在宿主机上访问,但是有些情况下还是需要允许其他人访问它。读者可能遇到了一个问题,需要其他人来远程调试,或者可能想让DevOps工作流中的某一部分在宿主机上启动一个进程。 在开放Docker守护进程之前,必须先停止正在运行的实例。操作的方式因操作系统而异。如果不清楚怎么做,可以首先试试这个命令: [root@localhost ~]# sudo service docker stop 如果得到一个类似下面这样的消息,说明这是一个基于systemctl的启动系统: The service command supports only basic LSB actions (start, stop, restart, try-restart, reload, force-reload, status). For other actions, please try to use systemctl. 可以试试这个命令: [root@localhost ~]# systemctl stop docker 如果这个方法有效,以下命令将看不到任何输出: ps -ef | grep -E 'docker (-d|daemon)\b' | grep -v grep 一旦Docker守护进程停止,就可以使用以下命令手工重启并向外界用户开放它: docker daemon -H tcp://0.0.0.0:2375 这个命令以守护进程方式启动Docker(docker daemon),使用-H标志定义宿主机服务器,使用TCP协议,绑定到所有IP地址上(使用0.0.0.0),并以标准的Docker服务器端口(2375)开放。如果Docker提示daemon不是一个有效的子命令,请尝试使用旧版的-d参数。 可以从外部使用如下命令进行连接: [root@localhost ~]# docker -H tcp://<宿主机IP>:2375 需要注意的是,在本地机器内部也需要这么做,因为Docker已经不再在其默认位置进行监听了。 3. 以守护进程方式运行容器 在熟悉了Docker之后,开始思考Docker的其他使用场景,首先想到的使用场景之一是以运行服务的方式来运行Docker容器。Docker容器与多数进程一样,默认在前台运行。在后台运行Docker容器最常见的方式是使用标准的&控制操作。虽然这行得通,但如果用户注销终端会话就可能出现问题,用户被迫使用nohup标志,而这将在本地目录中创建一个不得不管理的输出文件……是的,使用Docker守护进程的功能完成这一点将简洁得多。 要做到这一点,可使用-d选项: [root@localhost ~]# docker run -d -i -p 1234:1234 --name daemon ubuntu nc -l 1234 与docker run一起使用的-d标志将以守护进程方式运行容器。-i 标志则赋予容器与Telnet会话交互的能力。使用-p 将容器的1234端口公布到宿主机上。通过-- name标志赋予容器一个名称,以便后期用来对它进行引用。最后,使用 netcat(nc)在1234端口上运行一个简单的监听应答(echo)服务器。 如果现在使用Telnet连接它并发送消息,就可以使用docker logs命令看到容器已经接收到该消息,如下: [root@localhost ~]# telnet localhost 1234 Trying ::1... Connected to localhost. Escape character is '^]'. hello daemon ^] telnet> q Connection closed. [root@localhost ~]# docker logs daemon hello daemon [root@localhost ~]# docker rm daemon daemon [root@localhost ~]# (1)使用telnet命令连接到容器的netcat服务器;(2)输入发送给netcat服务器的一行文本;(3)按Ctrl+]然后按回车键退出Telnet会话;(4)输入q然后按回车键退出Telnet程序;(5)运行docker logs命令查看容器的输出;(6)使用rm命令清除容器; 4. 将Docker移动到不同分区 Docker把所有与容器和镜像有关的数据都存储在一个目录下。由于它可能会存储大量不同的镜像,这个目录可能会迅速变大。如果宿主机具有不同分区,用户可能会更快遭遇空间限制,在这种情况下,第一时间想到的是移动Docker所操作的目录。 停止Docker守护进程,并使用-g 选项指定新的位置来启动。首先必须将Docker守护进程停止。假设想在/home/dockeruser/mydocker运行Docker。运行下列命令将在这个目录中创建一组新的目录和文件: docker daemon -g /home/dockeruser/mydocker 这些目录是Docker内部使用的,对其进行操作风险还是比较大的。 这个虽然看起来把容器和镜像从之前的Docker守护进程清除了。不过不用担心。如果杀掉刚才运行的Docker进程,并重启Docker服务,Docker客户端就会指回它原来的位置,容器和镜像也将回归。如果想让这个移动永久有效,需要对宿主机系统的启动进程进行相应配置。 5. Docker客户端 如下图:Docker客户端是Docker架构中最简单的部件。在主机上输入docker run或docker pull这类命令时运行的便是它。它的任务是通过HTTP请求与Docker守护进程进行通信。 6. 使用端口连接容器 Docker容器从一开始就被设计用于运行服务。在大多数情况下,都是这样或那样的 HTTP服务。其中很大一部分是可以使用浏览器访问的Web服务.这会造成一个问题。如果有多个Docker容器运行在其内部环境中的80端口上,它们将无法全部通过宿主机的80端口进行访问。接下来的技巧将展示如何通过暴露和映射容器的端口来管理这一常见场景。 使用Docker的-p 选项将容器的端口映射到宿主机上。要从外部地址获取镜像,可以使用docker pull命令。在默认情况下,镜像将从Docker Hub下载: [root@localhost ~]# docker pull tutum/wordpress 要运行第一个博客,可使用如下命令: [root@localhost ~]# docker run -d -p 10001:80 --name xiaopeng1 tutum/wordpress 这里的docker run命令以守护进程方式(-d)及发布标志(-p)运行容器。它指定将宿主机端口(10001)映射到容器端口(80)上,并赋予该容器一个名称用于识别它(--name xiaopeng1 tutum/wordpress)。 可以对第二个博客做相同操作: [root@localhost ~]# docker run -d -p 10002:80 --name xiaopeng2 tutum/wordpress 如果现在运行这个命令: [root@localhost ~]# docker ps -a | grep xiaopeng 将看到列出的两个博客容器及其端口映射,看起来像下面这样: 9afb95ad3617 tutum/wordpress:latest "/run.sh" 9 seconds ago Up 9 seconds ➥ 3306/tcp, 0.0.0.0:10001->80/tcp xiaopeng1 31ddc8a7a2fd tutum/wordpress:latest "/run.sh" 17 seconds ago Up 16 seconds ➥ 3306/tcp, 0.0.0.0:10002->80/tcp xiaopeng2 现在可以通过浏览http://localhost:10001和http://localhost:10002来访问自己的容器。要在完成后删除这些容器(假设不想保留它们),可运行下面这个命令: [root@localhost ~]# docker rm -f xiaopeng1 xiaopeng2 -p / P 选项的使用格式: > -p <ContainerPort>:将指定的容器端口映射到物理机的随机一个端口上 > -p <HostPort>:<ContainerPort>:映射至指定的主机端口 > -p <IP>::<ContainerPort>:映射至指定的主机的 IP地址 的随机端口 > -p <IP>:<HostPort>:<ContainerPort>:映射至指定的主机 IP 的主机端口(最完整版) #主机ip 主机端口 容器端口 > -P(大):暴露所需要的所有端口 #将物理机的随机端口与容器的需要暴露的端口进行映射 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. 认识镜像1.1 Docker镜像的概念与构成1.2 Docker 组件的构建方式2. 镜像的操作2.1 查看镜像2.2 获取镜像2.3 创建镜像2.4 导入/导出镜像2.5 删除镜像👑👑👑结束语👑👑👑1. 认识镜像1.1 Docker镜像的概念与构成 镜像 是Docker 三核心概念中最重要的,自 Docker 诞生之日起镜像就是相关社区最为热门的关键词。Docker 运行容器前需要本地存在对应的镜像,如果镜像不存在, Docker 会尝试先从默认镜像仓库下载(默认使用 Docker Hub公共注册服务器中的仓库),用户也可以通过配置,使用自定义的镜像仓库 镜像的最底层是一个启动文件系统(bootfs)镜像,bootfs 的上层镜像叫做根镜像,一般来说,根镜像是一个操作系统,例如 Ubuntu、CentOS 等,用户的镜像必须构建于根镜像之上,在根镜像之上,用户可以构建出各种各样的其他镜像。 从上面的介绍读者可以看出,镜像的本质其实就是一系列文件的集合,一层套一层的结构有点类似于 Git ,也有点类似于生活中的洋葱里面的样子。Doctor的镜像基于联合文件系统UFS(union FS)读取数据时相当于从顶层向下层俯视读取。当前层有数据只读取到当前层的,如果当前层无数据或者当前层数据小于下层,则可以读取到当前层数据和当前层与下层差异的数据。Docker 镜像运行之后变成容器(docker run)镜像命名规则:仓库地址/用户名/镜像名:版本号 1.2 Docker 组件的构建方式2. 镜像的操作2.1 查看镜像 用户可以通过 docker images 命令查看本地所有镜像: [root@localhost ~]# docker images #可以查看本地仓库下所有的镜像[root@localhost ~]# docker images --no-trunc #可以显示全部ID 各个选项说明: REPOSITORY:表示镜像的仓库源 TAG:镜像的标签 IMAGE ID:镜像ID CREATED:镜像创建时间 SIZE:镜像大小 [namespace\ubuntu]:这种仓库名称由命名空间和实际的仓库名组成,中间通过 \ 隔开。当开发者在 Docker Hub 上创建一个用户时,用户名就是默认的命名空间,这个命令空间是用来区分 Docker Hub 上注册的不同用户或者组织(类似于 GitHub 上用户名的作用),如果读者想将自己的镜像上传到 Docker Hub 上供别人使用,则必须指定命名空间,否则上传会失败。[ubuntu]:这种只有仓库名,对于这种没有命名空间的仓库名,可以认为其属于顶级命名空间,该空间的仓库只用于官方的镜像,由 Docker 官方进行管理,但一般会授权给第三方进行开发维护。当然用户自己创建的镜像也可以使用这种命名方式,但是将无法上传到 Docker Hub 上共享。[hub.c.163.com/library/nginx]:这种指定 url 路径的方式,一般用于非 Docker Hub 上的镜像命名,例如一个第三方服务商提供的镜像或者开发者自己搭建的镜像中心,都可以使用这种命名方式命名。 使用 docker images 命令可以查看本地所有的镜像,如果镜像过多,可以通过通配符进行匹配:[root@localhost ~]# docker images n* 如果需要查看镜像的详细信息,也可以通过 docker inspect 命令来查看 2.2 获取镜像 镜像是运行容器的前提,官方的Docker Hub网站已经提供了数十万个镜像供大家开放下载。可以使用 docker [image] pu 命令直接从 Docker Hub 镜像源来下载镜像该命今的格式为 docke [image] pull NAME [ TAG]其中 IMAGE_NAME 表示的是镜像的名称,而 TAG 是镜像的标签,也就是说我们需要通过 “镜像 + 标签” 的方式来下载镜像。 注意:大家也可以不显式地指定 TAG, 它会默认下载 latest 标签,也就是下载仓库中最新版本的镜像。这里并不推荐下载 latest 标签,因为该镜像的内容会跟踪镜像的最新版本,并随之变化,所以它是不稳定的。在生产环境中,可能会出现莫名其妙的 bug, 推荐最好还是指定具体的 TAG[root@localhost ~]# docker search nginx #只能查看镜像名,看不到版本号。 搜索结果的参数如下: NAME:表示镜像的名称。 DESCRIPTION:表示镜像的简要描述。 STARS:表示用户对镜像的评分,评分越高越可以放心使用。 OFFICIAL:是否为官方镜像。 AUTOMATED:是否使用了自动构建。 浏览器搜素https://c.163yun.com/hub#/home网易官方镜像,登录,点击镜像仓库,点击镜像中心,搜素centos,点击public/centos,点击版本,复制下载地址。hub.c.163.com/public/centos:6.7-tools [root@localhost ~]# docker pull hub.c.163.com/public/centos:7.2-tools #将版本号修改为需要的版本号用docker pull命令下载即可。下载完成后,我们就可以使用这个镜像了。[root@localhost ~]# docker run -itd -name 163nginx -p 80:80 hub.c.163.com/library/ngxin:latest2.3 创建镜像 docker build是构建镜像用到的重要命令。从docker build的帮助信息中看到,build这 个子命令的功能非常强大。通过丰富的参数设置,可以控制镜像构建的各项细节。 常用的参数如下。 -с:控制CPU使用。 -f:选择Dockerfile名称。 , -m:设置构建内存上限。 -q:不显示构建过程的ー些信息。 -t:为构建的镜像打上标签。 构建镜像的基本命令格式是: [root@localhost ~]# docker build -t user_name/image_name . 其中命令后面的小数点符号不能省略,它表示当前目录的Dockerfile文件。Docker镜像构建是 讲究上下文的,因此不能把Dockerfile乱放,关于build的详细用法在下ー篇博文结合Dockerfile讲解。 除了使用docker build构建镜像,还可以使用docker commit提交镜像。docker commit 会把容器提交打包为镜像,这样提交的镜像会保存容器内的数据,而且第三方无法获得镜像的 Dockerfile,也就无法再构建一个完全一样的镜像出来,从这点看,并不推荐用户使用docker commit 提交镜像。但是在某些时候,我们需要使用docker commit来保存容器状态,这个时候我们还是需要使 用这个方法保存容器的。下面以ー个简单的例子说明,首先启动ー个容器: [root@localhost ~]# docker run -d --name=test ubuntu 然后进入该容器内部,在工作目录下新建一个test.txt文件,在里面写入内容: [root@localhost ~]# docker exec -it test bash container:echo "Text” > test.txt && exit 提交镜像,镜像名称是usemame/test: [root@localhost ~]# do-cker commit test username/test 再把刚オ提交的镜像运行: [root@localhost ~]# docker run -dit --rm username/test bash container:cat test.txt container:Text 可以看到:刚オtest容器新建的文件被保留下来了,usemame/test镜像里面包含了该文件 commit的参数如下〇 -a:添加作者信息,方便维护。 -с:修改 Dockerfile 指令,目前支持的有 CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | ONBUILD I USER | VOLUME | WORKDIR。 -m:类似git commit -m提交修改信息。 -p:暂停正在commit的操作。 2.4 导入/导出镜像 如果在两台主机之 间需要传输镜像,ー个办法就是把镜像推送到仓库,然后让另一台主机拉回来,但是这样有个中转, 不仅麻烦还不安全,有时候我们不希望镜像发布到互联网中。而自己搭建私有镜像仓库显然不是三 两句命令就能搞定的,于是就需要一组可以导出/导入镜像的命令了。 导出镜像:使用docker save可以导出镜像到本地文件系统: [root@localhost ~]# docker save -о ubuntu.tar ubuntu [root@localhost ~]# ls [root@localhost ~]# ubuntu.tar 导入镜像:使用docker load可以加载ー个导出的镜像包到本地仓库。 [root@localhost ~]# docker load -i ubuntu.tar或者:[root@localhost ~]# docker load < ubuntu.tar 导入镜像时不必指定镜像名称。 2.5 删除镜像镜像可以通过 docker rmi 命令进行删除,参数为镜像的 id 或者镜像名,参数可以有多个,多个参数之间用空格隔开。如下:[root@localhost ~]# docker rmi 345342433 ngxin:latest 有的时候,无法删除一个镜像,大部分原因是因为该镜像被一个容器所依赖,此时需要先删除容器,然后就可以删除镜像了.本地镜像多了,有些不需要,我们当然想要删除它们。删除镜像的命令是docker rmi,删除 镜像时不指定镜像的tag则会默认删除镜像的latest标签。可以在命令后面接上多个镜像名称,删除多个镜像。使用docker rmi命令删除镜像时,要确保没有容器使用该镜像,也就是说,没有容器是使用该 镜像启动的,オ可以删除,否则会报错。删除镜像时可以使用镜像的ID也可以使用镜像名称,docker rmi有一个参数-f,该参数可以强制删除镜像,即便有容器正在使用该镜像。但是这样只会删除镜像标签,不影响正在运行的容器, 实际上只要容器还在运行,镜像就不会被真正删除,用户可以使用docker commit操作提交容器来恢复镜像。 删除ー个镜像(默认删除latest标签) [root@localhost ~]# docker rmi hello-world Untagged: hello-world:latest Deleted: sha256:c0ec52a519810bbabO06186fe5ecl07f477885601bl3b29f0blc940d03c2ac46 Deleted: sha256:f004cl7c62d27346bd7ad32afd616d6fl35ab7b7d67fa704906c3b6790133b5'9 #删除ー个标签 [root@localhost ~]# docker rmi ubuntu:'test Untagged: ubuntu:test 镜像实际上是以ID为标准保存在Docker中的,即使镜像没有使用标签,镜像也是 可以存在的,出现这种情况的原因有很多,例如强制删除了一个正在运行着容器的镜像,又或者构 建的新镜像的tag覆盖了原来旧镜像的tag等。时间长了,我们没有tag说明这些镜像是什么作用就会很难管理,所以我们需要删除这些镜像, 数量少时我们可以手动一条一条地删除,数量多时我们可以配合Docker其他命令 删除所有未打 dangling标签的镜像: [root@localhost ~]# docker rmi $(docker images -q -f dangling=true) 删除所有镜像: [root@localhost ~]# docker rmi $(docker images -q) 拓展:创建shanchu别名删除所有容器: [root@localhost ~]# vim /usr/local/bin/shanchu #创建文件,添加如下内容: #!/bin/bash /usr/bin/docker rm -f $(/usr/bin/docker ps -a -q) [root@localhost ~]# chmod a+x /usr/local/bin/shanchu [root@localhost ~]# shanchu #即可删除所有容器👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. 裸金属架构(Vcent)2. Vcent安装流程2.1 远程控制端(个人电脑)安装VMware-viclien2.2 服务器部署安装VMWare ESXI2.3 VMware-viclien连接服务器👑👑👑结束语👑👑👑1. 裸金属架构(Vcent)优点:虚拟机不依赖于操作系统,可以支持多种操作系统,多种应用,更加灵 活缺点:虚拟层内核开发难度较大举例:VMWare ESXI Server。2. Vcent安装流程2.1 远程控制端(个人电脑)安装VMware-viclien 个人笔记本或办公室电脑安装VMware-viclient客户端:软件包下载地址:VMware vSphere Client(4.1/5.0/5.1/5.5/6.0) 客户端下载地址我这里下载的是VMware-viclient-all-6.0.0版本的,如下图: 将下载好的程序包双击运行,等待进度条加载到100%;语言选择简体中文,选择确定; 如下图,等待程序引导;此处选择下一步;点击我接受,然后点击下一步;此处可以选择更改安装路径,若不想修改可以直接选择下一步;点击安装此处等待程序安装结束,然后点击完成即可; 然后带着已经刻录好VMWare ESXI系统的U盘进入机房开始安装虚拟化平台;注:关于如何用U盘刻录操作系统,此处不进行讲解,可自行上网查找,因为非常简单所以我也没必要单独出一篇关于U盘刻录操作系统的教程,推荐使用UltraISO软件 2.2 服务器部署安装VMWare ESXI 先将服务器关机,然后将U盘插入服务器;开启服务器,然后按F11进入启动引导项(大部分服务器都是F11进入启动项修改,比如戴尔服务器、惠普服务器等等)然后将启动项修改为U盘启动注:如果需要配置raid卡,则自行上网搜索本公司服务器品牌像关的raid卡配置教程,不同的服务器操作不一样,我也没法挨个写,我也没那么多品牌的服务器可以操作; 引导启动后到如下图界面,然后直接回车继续安装,如下图: 到如下界面按F11同意协议。如下图: 然后就开始扫描硬件了,扫描完成后如下图,再按回车; 到如下界面后选择键盘方案,默认美式键盘即可,直接回车: 到达如下界面,需要设置密码,输入两次大于7个字符的密码然后回车: 到达如下界面,然后按F11进行读条安装: 到达如下界面直接回车重载: 进入到如下界面,按F2即可: 然后输入账号root,密码。回车进行下一步;到达如下界面,选择网络配置选项,回车进入: 到达如下界面,选择ipv4设置然后回车进入: 到达如下界面,进行网络配置,ip地址必须与客户端电脑的网卡ip处于同一网段; 然后按回车键保存,再按ESC键返回,弹出窗口,选择Y,然后按ESC返回到选择F2和F12的页面就可以离开机房啦! 2.3 VMware-viclien连接服务器 回到办公室远程端电脑,打开VMware vSphere Client客户端软件输入ip地址。账户名和密码连接如下图:(如果没有客户端软件,也可以打开VMware虚拟机,点击文件,点击连接服务器。) 登录进来之后,点击清单,进入到如下界面:然后:点击左侧ip地址然后右击,新建虚拟机,然后就可以根据公司需求选择安装相应的操作系统啦! 👑👑👑结束语👑👑👑
🍁作者简介:🏅云计算领域优质创作者🏅新星计划第三季python赛道TOP1🏅 阿里云ACE认证高级工程师🏅✒️个人主页:小鹏linux💊个人社区:小鹏linux(个人社区)欢迎您的加入!目录1. Docker概念2. Docker 比传统虚拟化有哪些优势?3. Docker 的安装3.1 通过脚本安装3.2 CentOS环境下安装Docker3.3 Ubuntu 环境下安装 Docker3.4 macOS 环境下安装 Docker3.5 Windows 10 环境下安装 Docker3.6 Windows 7、8 环境下安装 Docker👑👑👑结束语👑👑👑1. Docker概念 Doctor相当于一个管理工具基于Linux container 内核虚拟化技术,由 Golang语言编写的,遵从apache2.0协议,托管在github平台的一个管理引擎。 通过 UFS overlay 实现镜像的层级关连以及容器的可写层挂载 通过 namespace 名字空间实现容器级别的隔离 通过 CGROUP 实现资源的限制 通过 bridge网桥实现容器间的通讯 通过 chroot 实现伪根 2. Docker 比传统虚拟化有哪些优势? 1)能够将更多的资源提供给客户使用。2)Docker启动属于秒级启动,虚拟机启动需要几分钟去进行启动。3)Docker属于操作系统级别的虚拟化,通过Docker守护进程直接和内核进行交互,几乎没有性能损耗;虚拟机是硬件级别的虚拟化,需要通过Hypevisor层,性能损耗比较大4)Docker更轻量,占用内存小,在同样的硬件环境下,Docker运行的镜像数量要远多于虚拟机数量。5)Docker通过Namespaces和Cgroups实现对应用程序的进程之间的隔离,虚拟机从操作系统层面实现隔离,所以虚拟机的隔离性更强、安全性更好 3. Docker 的安装3.1 通过脚本安装 用户可以使用官方提供的shell脚本来在Linux系统(目前支持Ubuntu. Debian、 Ora이eserver、Fedora、Centos、OpenSuse. Gentoo 等常见发行版) 上安装 Docker 的最新正式 版本,该脚本会自动检测系统信息并进行相应配置: $ curl -fsSL https://get.docker.com/ | sh或者:$ wget -qO- https://get.docker.com/ | sh 如果想尝鲜最新功能,可以使用下面的脚本来安装最新的“尝鲜”版本。但要注意,非 稳定版本往往意味着功能还不够稳定,不要在生产环境中使用: $ curl -fsSL https://test.docker.com/ | sh 另外,也可以从 store.docker.com/search?offering=community&q=&type=edition 找到各个 平台上的Docker安装包,自行下载使用。 3.2 CentOS环境下安装Docker3.2.1 准备工作 先安装一个CentOS7.6的虚拟机。要求:硬盘大小大于等于100GB,网络我们选择仅主机模式。编辑虚拟机设置,镜像文件选择CentOS-7-x86_64-Minimal-1810.iso添加一块新网卡。网卡一:仅主机模式(工作环境都是仅主机模式)。网卡二:NAT模式(用来上网的)。将虚拟机的NAT模式dhcp获取方式勾选上开启虚拟机,进行安装。切记手动分区,/boot分区为800MB,/swap分区为4GB [root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-ens33打开网卡配置文件,修改如下:BOOTPROTO=static ONBOOT=yes IPADDR=192.168.232.165 NETMASK=255.255.255.0[root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-ens37打开网卡二的配置,修改如下:BOOTPROTO=dhcp ONBOOT=yes[root@localhost ~]# systemctl restart network #重启网卡 [root@localhost ~]# vi /etc/selinux/config关闭selinux。(若要临时关闭selinux,执行setenforce 0即可)打开配置文件,修改如下:SELINUX=disabled配置yum网易源:[root@localhost ~]# cd /etc/yum.repos.d/ [root@localhost yum.repos.d]# mkdir back [root@localhost yum.repos.d]# mv C* back/ 浏览器搜素http://mirrors.163.com/.help点击CentOS,点击下载CentOS7.然后右击浏览器下载的文件,选择复制下载地址http://mirrors.163.com/.help/CentOS7-Base-163.repo [root@localhost yum.repos.d]# curl http://mirrors.163.com/.help/CentOS7-Base-163.repo > 163.repo #配置网易网络源 [root@localhost yum.repos.d]# cd [root@localhost ~]# yum clean all #清除yum缓存 [root@localhost yum.repos.d]# yum -y install net-tools vim lrzsz iptables-services #安装常用工具 [root@localhost ~]# systemctl stop firewalld #临时关闭防火墙 [root@localhost ~]# systemctl disable firewalld #设置防火墙开机不自启动 [root@localhost ~]# iptables -F #清空防火墙规则 [root@localhost ~]# systemctl start iptables [root@localhost ~]# systemctl enable iptables [root@localhost ~]# iptables -F #再次清空防火墙规则 [root@localhost ~]# service iptables save #保存到文件防止下次启动又有规则生成 [root@localhost ~]# shutdown -h now #虚拟机的话建议关机保存快照再开机3.2.2 开始安装 Docker在CentOS中的安装方式: 方式一:> Script(不推荐)企业用的比较多的是19.03版本 curl -sSL https://get.docker.com/ | sh #官方脚本安装:自动检测当前系统并会自动选择安装当前平台能够使用的最新稳定版 systemctl start docker systemctl enable docker docker run hello-world 方式二:> Yum安装 yum install -y yum-utils cd /etc/yum.repos.d/ yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo #然后ls查看发现有docker-ce.repo 源产生 方式三:> Rpm安装(推荐) 浏览器https://mirrors.aliyun.com/docker-ce/linux/centos/7.6/x86_64/stable/Packages/ 下载所需版本的源码包安装 国外镜像,比较慢,浏览器搜素DaoCloud | Docker 极速下载:找到Docker Hub 加速器,点击一次配置无缝使用。找到curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io [root@localhost ~]# curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io [root@localhost ~]# vim /etc/docker/daemon.json #打开配置文件,修改阿里云镜像加速将{"registry-mirrors": ["http://f1361db2.m.daocloud.io"]修改为{"registry-mirrors": ["https://kfp63jaj.mirror.aliyuncs.com"]}[root@localhost ~]# systemctl restart docker #重启服务 [root@localhost ~]# docker info #查看生效了 备注:小于等于1.16 版本镜像加速方法: cp /lib/systemd/system/docker.service /etc/systemd/system/docker.service chmod 777 /etc/systemd/system/docker.service vim /etc/systemd/system/docker.service ExecStart=/usr/bin/dockerd-current --registry-mirror=https://kfp63jaj.mirror.aliyuncs.com \ systemctl daemon-reload systemctl restart docker ps -ef | grep docker阿里云Docker官网:https://dev.aliyun.com/search.html3.3 Ubuntu 环境下安装 Docker3.3.1 系统要求 Ubuntu操作系统对Docker的支持十分成熟,可以支持包括x86_64、armhf、s390x (IBM . Z)、ppc641e等系统架构,只要是64位即可。’ Docker目前支持的最低Ubuntu版本为14.04LTS,但实际上从稳定性上考虑,推荐使用 16.04 LTS或18.0.4 LTS版本,并且系统内核越新越好,以支持Docker最新的特性。用户可以通过如下命令检查自己的内核版本详细信息: $ uname -a Linux localhost 4.9.36-x86_64-generic或者:$ cat /proc/version Linux version 4.9.36-x86_64-generic (maker@linux.com) (gcc version 4.9.2 (Debian 4.9.2-10)) 如果使用Ubuntu 16.04 LTS版本,为了让Docker使用aufs存储,推荐安装如下两个 软件包: $ sudo apt-get update $ sudo apt-get install -y \ 1inux-image-extra-$(uname -r) \ 1inux-image-extra-virtual 注意:Ubuntu发行版中,LTS ( Long-Term-Support)意味着更稳定的功能和更长期(目前为 5年)的升级支持,生产环境中推荐尽量使用LTS版本。 3.3.2 添加镜像源 首先需要安装apt-transport-https等软件包支持https协议的源: $ sudo apt-get update $ sudo apt-get install \ - apt -transport-https \ ca-certificates \ curl \ software-properties-common 添加源的gpg密钥: $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - OK 确认导入指纹为 “9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C OEBF CD88” 的 GPG 公钥: $ sudo apt-key fingerprint 0EBFCD88 pub 4096R/0EBFCD88 2017-02-22 Key fingerprint = 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C OEBF CD88 uid Docker Release (CE deb) <docker@docker.com> sub 4096R/F273FCD8 2017-02-22 获取当前操作系统的代号: $ lsb_release -cs xenial 一般情况下,Ubuntu 16.04 LTS 代号为 xenial, Ubuntu 18.04 LTS 代号为 bionic接下来通过如下命令添加Docker稳定版的官方软件源,非xenia!版本的系统注意修改 为自己对应的代号: $ sudo add-apt-repository \ ndeb [arch=amd64] https://download.docker.com/linux/ubuntu \ xenial \ stable" 添加成功后,再次更新apt软件包缓存: $ sudo apt-get update3.3.3 开始安装Docker 在成功添加源之后,就可以安装最新版本的Docker 了,软件包名称为docker-ce,代表 是社区版本: $ sudo apt-get install -y docker-ce 如果系统中存在较旧版本的Docker,会提示是否先删除,选择是即可。除了基于手动添加软件源的方式之外,也可以使用官方提供的脚本来自动化安装Docker: $ sudo curl -sSL https://get.docker.com/ | sh 安装成功后,会自动启动Docker服务.用户也可以指定安装软件源中其他版本的Docker: $ sudo apt-cache madison docker-ce docker-ce | 17.11.0-ce-0~ubuntu | https://download.docker.com/linux/ubuntu xenial/edge amd64 Packages docker-ce | 17.10. 〇~ce-0~ubuntu | https : //download.d.ocker.com/linux/ubuntu xenial/edge amd64 Packages docker-ce \ 17.09. l~ce- 0~ubuntu | https : //download.d.ocker. com/1 inux/ubuntu xenial/stable amd64 Раска으es $ sudo apt-get instal1 docker-ce=17.11.〇~ce-O~ubuntu3.4 macOS 环境下安装 Docker3.4.1 使用 Homebrew 安装 macOS 我们可以使用 Homebrew 来安装 Docker。Homebrew 的 Cask 已经支持 Docker for Mac,因此可以很方便的使用 Homebrew Cask 来进行安装: $ brew install --cask --appdir=/Applications docker ==> Creating Caskroom at /usr/local/Caskroom ==> We'll set permissions properly so we won't need sudo in the future Password: # 输入 macOS 密码 ==> Satisfying dependencies ==> Downloading https://download.docker.com/mac/stable/21090/Docker.dmg ######################################################################## 100.0% ==> Verifying checksum for Cask docker ==> Installing Cask docker ==> Moving App 'Docker.app' to '/Applications/Docker.app'. &#x1f37a; docker was successfully installed!在载入 Docker app 后,点击 Next,可能会询问你的 macOS 登陆密码,你输入即可。之后会弹出一个 Docker 运行的提示窗口,状态栏上也有有个小鲸鱼的图标。 3.4.2 手动下载安装如果需要手动下载,请点击以下链接下载 Install Docker Desktop on Mac 如同 macOS 其它软件一样,安装也非常简单,双击下载的 .dmg 文件,然后将鲸鱼图标拖拽到 Application文件夹即可。从应用中找到 Docker 图标并点击运行。可能会询问 macOS 的登陆密码,输入即可。点击顶部状态栏中的鲸鱼图标会弹出操作菜单。第一次点击图标,可能会看到这个安装成功的界面,点击 "Got it!" 可以关闭这个窗口。启动终端后,通过命令可以检查安装后的 Docker 版本。$ docker --version Docker version 17.09.1-ce, build 19e2cf63.4.3 镜像加速 鉴于国内网络问题,后续拉取 Docker 镜像十分缓慢,我们可以需要配置加速器来解决,我使用的是网易的镜像地址:http://hub-mirror.c.163.com。在任务栏点击 Docker for mac 应用图标 -> Perferences... -> Daemon -> Registry mirrors。在列表中填写加速器地址即可。修改完成之后,点击 Apply & Restart 按钮,Docker 就会重启并应用配置的镜像地址了。 之后我们可以通过 docker info 来查看是否配置成功。$ docker info ... Registry Mirrors: http://hub-mirror.c.163.com Live Restore Enabled: false3.5 Windows 10 环境下安装 Docker注意:此方法仅适用于 Windows 10 操作系统专业版、企业版、教育版和部分家庭版! Docker 并不是一个通用的,它依赖于存在并运行的 Linux 内核于已环境的工具。Docker 真正的 Linux 是在已经运行的下一个隔离的文件环境,因此,它执行的效果制造于就地部署的 Linux主机。Docker 必须部署在相应的 Linux 内核的系统上。如果其他系统想部署 Docker,则必须安装一个虚拟 Linux 环境。 在 Windows 上部署 Docker 的方法都是先安装一个虚拟机,然后在安装 Linux 系统的虚拟机中运行 Docker。Docker Desktop 是 Docker 在 Windows 10 和 macOS 上运行的方法的官方安装方式,这个仍然属于先在虚拟机中安装 Linux 然后再安装 Docker 的方法。Docker Desktop 官方下载地址: Install Docker Desktop on Windows | Docker Documentation 3.5.1 安装 Hyper-V Hyper-V 是微软开发的虚拟机,类似于 VMWare 或 VirtualBox,仅适用于 Windows 10。这是 Docker Desktop for Windows 所使用的虚拟机。如果您必须在电脑上使用虚拟机(例如 Android 应用程序使用的虚拟机或以下其他版本),请不要使用 Hyper-V ! 2.5.2 开启 Hyper-V程序和功能:启用或关闭 Windows 功能检测Hyper-V也可以通过命令启用 Hyper-V ,请管理员来启动菜单并以身份运行 PowerShell,执行以下命令:Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All2.5.3 安装 Docker Desktop for Windows点击开始使用 Docker Desktop,并下载 Windows 的版本,如果你还没有登录,会注册要求登录: 2.5.4 安装运行文件点击下载 Docker for Windows 安装文件,一路完成完成安装。 安装完成后,Docker会自动启动。通知栏上会出现个小鲸鱼的图标,这表示Docker正在运行。桌边还有三个图标,如下图所示:我们可以在执行版本 docker version 查看号,docker hello-world 来编写测试。如果没启动,你可以在 Windows 搜索 Docker 来启动: 启动后,也可以在通知栏上看到小鲸鱼图标:如果启动中遇到因 WSL 2 导致地错误,请安装WSL 2。安装之后,就可以打开PowerShell并运行以下命令检测是否运行成功: 码头工人运行你好世界-世界在成功运行之后应该会出现以下信息: 3.6 Windows 7、8 环境下安装 Docker win7、win8等需要利用工具箱来安装,国内可以使用阿里云的镜像来下载,下载地址:http://mirrors.aliyun.com/docker-toolbox/windows/docker-toolbox/安装比较简单,运行起来,点自动化就可以了,可以勾选自己需要的组件: docker toolbox 是一个工具集,它主要包含以下一些内容:Docker CLI - 客户端,使用运行 docker 引擎创建和容器。Docker Machine - 可以让你在 Windows 的引擎中运行 docker 命令。Docker Compose - 使用运行 docker-compose 命令。Kitematic - 这是 Docker 的 GUI 版本。Docker QuickStart shell - 这是一个已经配置好Docker的环境。Oracle VM Virtualbox - 虚拟机。 下载完成后,安装成功后,桌边会出现三个图标,直接点击如下图所示: 点击 Docker QuickStart 图标来启动 Docker Toolbox 终端。如果系统显示用户账户控制窗口来运行VirtualBox修改你的电脑,选择Yes。 $符号那你可以输入以下命令来执行。 $ docker 运行你好世界-世界 无法在本地找到图像“hello-world:latest”无法在本地找到图像“hello-world:latest” 拉存储库hello-world拉存储库你好-世界 91c95931e552:下载完成91c95931e552 :下载完成 a8219747be10:下载完成:下载完成 状态:为 hello-world 下载了更新的图像:最新状态:为hello - world下载了更新的图像:最新 来自 Docker 的您好。来自Docker的您好。 此消息表明您的安装似乎工作正常。此消息表明您的安装似乎工作正常。 为了生成此消息,Docker 采取了以下步骤:为了生成此消息,Docker采取了以下步骤: 1. Docker Engine CLI 客户端联系了 Docker Engine 守护进程。1. Docker Engine CLI 客户端联系了Docker Engine守护进程。 2. Docker Engine 守护进程从 Docker Hub 拉取“hello-world”镜像。 (假设它还没有在本地可用。) 3. Docker 引擎守护进程从该镜像创建了一个新容器,该容器运行 生成您当前正在阅读的输出的可执行文件。 4. Docker Engine 守护程序将该输出流式传输到 Docker Engine CLI 客户端,后者将其发送 到您的终端。 要尝试更雄心勃勃的事情,您可以运行Ubuntu容器:$ docker run -it ubuntu bash
2022年10月
2022年09月
2022年08月
你对新手村的同学们有什么寄语? 如何在技术领域快速成长? 凡事预则立,不预则废。对于漫长的学习生涯而言,好的计划是成功的一半。 1、长期规划 长期规划的实施需要毅力和决心,但是做正确的长期规划还需要高瞻远瞩的眼界、超级敏感的神经和中大奖的运气。对于大部分人来说,长期规划定主要是“定方向”。但遵循如下原则能够减少犯方向性错误的概率: * 远离日暮西山的行业。 * 做自己感兴趣的事情。 * 做有积累的事情。 * 一边走一边看,切勿一条道走到黑。 2、短期规划 良好的短期规划应该在生活、成长、绩效和晋升之间取得平衡。大部分公司都会制定一个考核周期——少则一个月,多则一年。所以不妨以考核周期作为短期学习规划周期。本质上,规划是一个多目标优化问题,它有一系列的理论方案,这里不一一细说。基于相关理论,我给出一个简单易行的方案: * 确定目标优先级。比如:成长、生活、绩效。 * 确定每个目标的下限。从优化理论的角度来看,这被称为约束。比如绩效必须在一般以上,之前已经规划好的旅行不能更改,必须读完《Effective Java》等等。 * 优先为下限目标分配足够的资源。比如,事先规划好的旅行需要 10 天,这 10 天就必须预算出去。 * 按照各主目标的顺序依次分配资源。