首页 文章 精选 留言 我的

精选列表

搜索[笔记],共6930篇文章
优秀的个人博客,低调大师

python基础笔记

python是跨平台的,它可以运行在Windows、Linux、Mac系统中,要开始学习python程序,首先需要把python安装到电脑上,从python的官网下载需要安装的软件版本,www.python.org,Windows系统下安装完毕后 通过cmd命令调取python,exit()退出 第一个python程序 通过python交互器执行程序,最后用exit()退出python,我们第一个python程序完成,唯一的缺憾是没有保存下来,下次运行还需要再输入一遍代码。 通过print()在括号中加入字符串就可以向屏幕上输出指定的文字 再python交互式命令行中写程序,好处是一下就能得到结果,坏处是没法保存,所以实际开发中我们会使用IDE或文本编辑器来写代码,写完后保存为一个python文件,这样程序就能反复运行,创建一个hello.py文件,并输入print("hello world") 进入指定目录通过python hello.py命令打开程序 变量 在python中变量的定义规则是 变量名只能是 字母、数字或下划线的任意组合 变量名的第一个字符不能是数字 python中的关键字不能声明为变量名 定义变量 name = "Zyy" 上面的代码声明了一个变量,变量名为: name,变量name的值为:"Zyy" python的输入和输出 用print()在括号中加入字符串,就可以向屏幕上输出指定的文字 print()可以接受多个字符串,用逗号","隔开,就可以连成一串,print()会依次打印每个字符串,遇到逗号会输出一个空格,print()也可以打印整数,或者计算结果,也可以组合使用。我刚整理了一套2018最新的0基础入门和进阶教程,无私分享,加Python学习q-u-n:(二二七),(四三五), (四五零)即可获取 print()输出 输入字符使用input() input()输入 python的数据类型 可以使用内部函数type()来查看数据的类型 字符串(str) 整数型(int) 布尔型(bool) 浮点型(float) 列表(list) 元组(tuple) 字典(dict) 集合(set) 空值(None) 运算符 算数运算 + 加法 - 减法 * 乘法 / 除(使用/除时,结果为float类型) //地板除(结果为整数int) % 取余数 ** 次幂(2**2 表示2的2次幂结果4) 比较运算,输出结果为bool == 等于 != 不等于(python3.0之后不可以写作<>) > 大于 < 小于 >= 大于等于 <=小于等于 赋值运算 = 简单的赋值运算符 +=加法赋值运算符 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 除此之外还有**= //= 逻辑运算符 and 布尔与 两者都是True才是True,否则为False or 布尔或 两者都是False才是False,否则为True not 布尔非 非True就是False,非False就是True 成员运算 in 如果在指定的序列找到值,则返回True,否则为False not in 如果在指定的序列中没有找到值,返回True,否则为False 身份运算 is is是判断两个标识符是不是引用一个对象 is not is not 是判断两个标识符是不是引用不同对象 判断语句和循环语句 python的语法比较简单,采用缩进方式;以#开头表示注释,批量注释使用'''。。。''',其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块(不要tab键和空格混用,python是区分大小写的)。 判断语句 简单的if判断语句为 if 条件:#条件为True 执行结果 else: #否则 执行结果 if中还可以使用elif做更细致的判断,elif 是else if的缩写,if语句是从上往下判断,如果某个判断上是True,就会执行相应的结果,执行完毕后会忽略掉接下来的elif和else 循环语句for for...in....语句可以循环遍历list或tuple,又称作迭代,for x in ... 就是把每个元素带入变量x,然后执行缩进 循环语句while while循环是,只要条件满足,就不断循环,条件不满足时退出。在循环中,可以使用break和continue,需要配合if语句来使用,break是退出循环,continue是跳过当前循环

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

metaq学习笔记

简介 metaq作为一款消息中间件,是支撑双11最为核心的系统之一,解耦收发双方,使得系统异步化,同时利用消息中间件堆积消息的功能,可以使得下游系统在可以慢慢消费消息,增强系统的缓冲能力,达到“削峰填谷”的目的 思维导图 在学习前先建立了思维导图,这样在学习过程中逐步深入,理解更加透彻 流程 发送端 1. UML图 2. 类 2.1 MetaProducer 发送端,可通过该类实例对象指定发送端分组(producerGroup),启动程序 2.2 Diamond 持久化配置中心,用于获取配置信息 2.3 MetaProducerImpl 用于初始化、注册埋点 2.4 DefaultMQProducerImpl 检查配置,注册发送方 2.5 MQClientInstance 若程序处于初始化状态,开启定时任务、拉取消息服务、负载平衡服务 2.6 MQClientAPIImpl 调用底层的 netty client 发送数据 metaq 的代码编写风格较为清爽,基本调用顺序为 MetaProducer --> MetaProducerImpl --> DefaultMQProducerImpl --> MQClientAPIImpl 3. 流程 具体流程见UML图中操作顺序,这里只列出重要的步骤 3.1 start 开启服务,程序入口 3.2 getConfig 获取配置信息,调用的是Diamond接口,本质是通过httpGet请求,使用netty从持久化配置中心下载配置文件,定位参数是指定的分组名(producerGroup) 3.3 addListener 添加监听,如果Diamond配置中心有数据变化,可获得新的数据 3.4 receive 通过设置AllowPushInterval变量,一分钟后再响应服务器配置变化 3.5 init 初始化,设置最大消息大小、埋点,这里设置的hook在最终发送消息时会依次调用 3.6 start DefaultMQProducerImpl 的 start 方法,根据serviceState决定执行流程;注意这里程序的入口包含参数 startFactory,boolean 类型判断是否需要开启服务,调用顺序为 DefaultMQProducerImpl.start( ) --> MQClientInstance.start( ) --> DefaultMQProducerImpl.start( ),只是在程序第二次调用时将标记位设为false 3.7 checkConfig 检查配置,判断分组名是否符合规范 3.8 start 开启MQClientInstance的服务,启动mqClientAPIImpl(底层netty通信客户端)、startScheduledTask(定时任务)、rebalanceService(负载均衡)、defaultMQProducerImpl 3.9 send 发送消息,此前需要对消息进行封装,指定消息主题topic、标签tag、内容body,其中通过topic即可实现收发双方的通信,接收方可以通过tag进行消息过滤 3.10 sendDefaultImpl 发送方法的默认实现,根据消息的发送方式选择不同的方法 3.11 sendKernelImpl 封装消息,执行之前设置的hook 3.1 sendMessage 调用底层的netty客户端发送消息,并返回SendResult 接收端 1. UML图 2. 类 2.1 MetaPushConsumer 接收方对象,程序入口,可订阅主题、设置监听、开启服务 2.2 DefaultMQPushConsumerImpl 默认实现方式,用于订阅和设置监听 2.3 MetaClientConfigChanged 用于获取配置 2.4 Diamond 调用接口,提供从持久化配置中心获取配置及设置监听的功能 2.5 MetaPushConsumerImpl 用于初始化服务 2.6 DefaultMQPushConusmerImpl 检查配置,开启服务 2.7 MQClientInstance 用于开启负载均衡、拉取消息等服务 可以看出,接收方和发送发的代码结构、类的命名方式极为相似,类的调用流程为 MetaPushConsumer --> DefaultMQPushConsumerImpl --> MetaPushConsumerImpl --> DefaultMQPushConsumerImpl --> MQClientInstance 3. 流程 具体流程见UML图中操作顺序,这里只列出重要的步骤 3.1 subscribe 程序入口,订阅相应主题,这里包含两个参数 3.1.1 topic:订阅主题,metaq发送发接收方通信的标识符,只要主题一致即可接收消息 3.1.2 subExpression:标记位tag,使用方式为“tag1 || tag2 || tag3”或者“*”,用于接收方过滤消息 3.2 registerMessageListener 注册消息监听,这里指定接受消息后的处理方式 3.3 getConfig 获取配置,与发送发方法一致 3.4 addListener 设置监听,当配置更新时获取新的信息 3.5 init 初始化,埋点 3.6 chechConfig 检查配置 3.7 mQClientAPIImpl.start( )、startScheduledtask( )、pullMessageService.start( )、rebalanceService.start( ) 与发送方类似 可以看出,metaq的代码编写风格极为规整,发送方接收方结构一致,方便阅读 broker broker的源代码暂无 源码解析 上述思维导图中,通过阅读源码,重点希望解决以下问题 1. 通讯建立 全网拓扑如下 各组件功能为: Name Server:注册服务器,需要将topic注册到上面,对应的操作是在metaq平台上发布topic(http://ons-api.taobao.net) Broker:存储转发服务器,每个broker需要与所有的name server建立长连接,从而获取topic信息;分为master和容灾的slaver Producer:消息发送方,需要与其中一个name server建立连接,获得路由信息,再与主题对应的broker建立长连接且定时向master发送心跳;消息由producer发送到master,再由master同步到所有broker Consumer:消息接收方,需要与其中一个name server建立连接,获得路由信息,再向提供服务的master、slaver建立长连接,具体接收消息时刻选择broker 这里需要建立的连接包括: Producer Consumer Name Server Broker Producer ---- 多对多 多对一 多对多 Consumer ---- 多对一 多对多 Name Server ---- 多对一 Broker ---- 1.1 producer <--> name server producer在初始化阶段需要与其中一个name server建立长连接,获得broker路由信息 1.1.1 获取name server 地址 源码提供了两种获取name server的地址,,默认地址为:http://jmenv.tbsite.net:8080/rocketmq/nsaddr MixAll //提供两种方式来指定获取name server地址:system environment variables 和 Java option //默认采用后者 public static String getWSAddr() { String wsDomainName = System.getProperty("rocketmq.namesrv.domain", DEFAULT_NAMESRV_ADDR_LOOKUP); String wsDomainSubgroup = System.getProperty("rocketmq.namesrv.domain.subgroup", "nsaddr"); String wsAddr = "http://" + wsDomainName + ":8080/rocketmq/" + wsDomainSubgroup; if (wsDomainName.indexOf(":") > 0) { wsAddr = "http://" + wsDomainName + "/rocketmq/" + wsDomainSubgroup; } return wsAddr; } producer在初始化时通过向固定地址发送httpGet请求,从而获得name server地址 TopAddressing public final String fetchNSAddr(boolean verbose, long timeoutMills) { String url = this.wsAddr; try { if (!UtilAll.isBlank(this.unitName)) { url = url + "-" + this.unitName + "?nofix=1"; } HttpTinyClient.HttpResult result = HttpTinyClient.httpGet(url, null, null, "UTF-8", timeoutMills); if (200 == result.code) { String responseStr = result.content; if (responseStr != null) { return clearNewLine(responseStr); } else { log.error("fetch nameserver address is null"); } } else { log.error("fetch nameserver address failed. statusCode=" + result.code); } } catch (IOException e) { if (verbose) { log.error("fetch name server address exception", e); } } if (verbose) { String errorMsg = "connect to " + url + " failed, maybe the domain name " + MixAll.getWSAddr() + " not bind in /etc/hosts"; errorMsg += FAQUrl.suggestTodo(FAQUrl.NAME_SERVER_ADDR_NOT_EXIST_URL); log.warn(errorMsg); } return null; } 1.1.2 与name server通信,获取主题路由信息,返回值为TopicRouteData,即主题对应的broker地址 在metaq中,所有请求的封装格式统一,区别在于请求码,最终通过netty client通信 MQClientAPIImpl public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis, boolean allowTopicNotExist) throws MQClientException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader(); requestHeader.setTopic(topic); RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINTO_BY_TOPIC, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; switch (response.getCode()) { case ResponseCode.TOPIC_NOT_EXIST: { if (allowTopicNotExist && !topic.equals(MixAll.DEFAULT_TOPIC)) { log.warn("get Topic [{}] RouteInfoFromNameServer is not exist value", topic); } break; } case ResponseCode.SUCCESS: { byte[] body = response.getBody(); if (body != null) { return TopicRouteData.decode(body, TopicRouteData.class); } } default: break; } throw new MQClientException(response.getCode(), response.getRemark()); } 1.2 producer <--> broker 遍历brokerAddrTable,对获取到的broker地址发送心跳sendHeartbeat建立长连接 MQClientInstance private void sendHeartbeatToAllBroker() { final HeartbeatData heartbeatData = this.prepareHeartbeatData(); final boolean producerEmpty = heartbeatData.getProducerDataSet().isEmpty(); final boolean consumerEmpty = heartbeatData.getConsumerDataSet().isEmpty(); if (producerEmpty && consumerEmpty) { log.warn("sending heartbeat, but no consumer and no producer"); return; } if (!this.brokerAddrTable.isEmpty()) { long times = this.sendHeartbeatTimesTotal.getAndIncrement(); Iterator<Entry<String, HashMap<Long, String>>> it = this.brokerAddrTable.entrySet().iterator(); while (it.hasNext()) { Entry<String, HashMap<Long, String>> entry = it.next(); String brokerName = entry.getKey(); HashMap<Long, String> oneTable = entry.getValue(); if (oneTable != null) { for (Map.Entry<Long, String> entry1 : oneTable.entrySet()) { Long id = entry1.getKey(); String addr = entry1.getValue(); if (addr != null) { if (consumerEmpty) { if (id != MixAll.MASTER_ID) continue; } try { int version = this.mQClientAPIImpl.sendHearbeat(addr, heartbeatData, 3000); if (!this.brokerVersionTable.containsKey(brokerName)) { this.brokerVersionTable.put(brokerName, new HashMap<String, Integer>(4)); } this.brokerVersionTable.get(brokerName).put(addr, version); if (times % 20 == 0) { log.info("send heart beat to broker[{} {} {}] success", brokerName, id, addr); log.info(heartbeatData.toString()); } } catch (Exception e) { if (this.isBrokerInNameServer(addr)) { log.info("send heart beat to broker[{} {} {}] failed", brokerName, id, addr); } else { log.info("send heart beat to broker[{} {} {}] exception, because the broker not up, forget it", brokerName, id, addr); } } } } } } } } consumer端连接建立过程和发送发相同,包含consumer <--> name server 和 consumer <--> broker,源码中consumer最终调用的是与producer相同的方法(MQClientInstance),这里就不再重复 broker需要与所有name server建立连接,从而获得topic信息,这里没有broker端源码,详细过程略 2. 存储方案 ata上不少文章都有对metaq物理存储的讲解,但这些内容大都混淆了metaq和kafka的存储结构,经过查阅相关资料,总结如下 2.1 kafka kafka采取的是分区+分段的方式,参考自Kafka设计解析(一)- Kafka背景及架构介绍 分区:partition,将物理broker映射为逻辑分区,实现了水平拓展,这样在传输时只需指定分区号即可,而broker可对应多个分区,这样就实现了分布式的存储 分段:segment,实际存储结构,包含数据文件和索引文件,顺序存储加快了速度,数据文件命名采用偏移量的方式,这样索引文件只需记录偏移量就可以二分查找快速定位 简单来说,kafka使用分区的方式实现了物理存储到逻辑分区的映射,能够轻松实现存储空间的扩展,而对用户透明;实际采用顺序存储的方式,用户只需指定索引(偏移量),就能快速查找;存储结构如下图所示 2.2 metaq metaq借鉴了kafka的思想(致敬kafka名作变形记),即一个topic对应一个顺序存储队列sequence,但是原有的分区的数量随着topic的增大而明显增大,本来高性能的顺序写文件会变成随机写,吞吐量会有较大的下降,因此在kafka的基础上进行了改进,取消 分区物理划分主题,而采用consume queue逻辑划分的方式,参考自阿里消息中间件架构演进之路:notify和metaq 2.1.1 commit log CommitLog属于物理队列,存储完整的消息数据,所有topic的消息都会写入到同一个CommitLog,这样就避免分区带来的影响,而划分主题的功能则交由consume queue实现 2.1.2 consume queue metaq对客户端暴露的主要是consume queue逻辑视图,提供队列访问接口,消费者通过指定consume queue的位点来读取消息,通过提交consume queue的位点来维护消费进度 2.1.3 改进 可以看出,kafka采用的是分区+分段的方式,即物理分区,分区划分主题、逻辑扩展,分段实际存储、索引查找;而metaq采取的是commitLog+consumeQueue,逻辑分区,即commitLog按照偏移量顺序存储,consumeQueue划分主题、索引查找,同时一个 broker对应多个队列,也可以达到存储拓展的功能;metaq的存储结构如下图所示 3. 推拉模式 3.1 pull 流程 程序初始化时启动pullMessageService服务,pullRequestQueue阻塞队列存储所有的pull任务,线程不停地从队列中获取新的任务并拉取消息 PullMessageService public void run() { log.info(this.getServiceName() + " service started"); while (!this.isStopped()) { try { PullRequest pullRequest = this.pullRequestQueue.take(); if (pullRequest != null) { this.pullMessage(pullRequest); } } catch (InterruptedException e) { } catch (Exception e) { log.error("Pull Message Service Run Method exception", e); } } log.info(this.getServiceName() + " service end"); } netty client 收到消息后,触发 processMessageReceived 方法,最终调用ConsumeConcurrentlyStatus status = this.messageListener.consumeMessage(msgs, content);这里的messageListener就是我们在main函数里设置的监听处理方法,同时在处理完一条消息后需要返回,只有确认正确处理才能继续传输,下面代码是处理和返回逻辑 ClientRemotingProcess private RemotingCommand consumeMessageDirectly(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException { final RemotingCommand response = RemotingCommand.createResponseCommand(null); final ConsumeMessageDirectlyResultRequestHeader requestHeader = (ConsumeMessageDirectlyResultRequestHeader) request .decodeCommandCustomHeader(ConsumeMessageDirectlyResultRequestHeader.class); final MessageExt msg = MessageDecoder.decode(ByteBuffer.wrap(request.getBody())); ConsumeMessageDirectlyResult result = this.mqClientFactory.consumeMessageDirectly(msg, requestHeader.getConsumerGroup(), requestHeader.getBrokerName()); if (null != result) { response.setCode(ResponseCode.SUCCESS); response.setBody(result.encode()); } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark(String.format("The Consumer Group <%s> not exist in this consumer", requestHeader.getConsumerGroup())); } return response; } 3.2 push 流程 metaq采用长轮询的方式模拟push,即在代码中将pull请求放入阻塞队列中,通过线程不停地从队列中take获取任务,达到push实时推送的功能;代码见3.1 pull 流程 4. 可靠性保证 4.1 producer可靠性保证 发送方与broker建立连接,使用netty传输消息message,同步传输确保消息能够到达服务器;源码如下,在得到response后需要调用processSendResponse方法对其进行分析,会根据response状态码更改responseCode,只有返回值的状态码正确才继续传输 private SendResult sendMessageSync( final String addr, final String brokerName, final Message msg, final long timeoutMillis, final RemotingCommand request ) throws RemotingException, MQBrokerException, InterruptedException { RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis); assert response != null; return this.processSendResponse(brokerName, msg, response); } 4.2 consumer可靠性保证 消费者顺序消费,只有在前一条消息成功后才会继续,如果持续失败(最多5次),则会将该条消息保存到本地,有后台进程继续尝试;具体消费流程见上述3.1 pull 流程 4.3 broker可靠性保证 notify服务器通过持久化保证消息的可靠性,但这样会带来io读写耗时大的问题,metaq对此进行了优化 4.3.1 master -- slaver 采用slaver的结构,一方面可以做到容灾,当master出现异常时可以自动顶替上去;另一方面,master--slaver的结构优化了读写操作,producer与master建立长连接,写操作进入master,再进行备份处理,而consumer与master和所有的slaver建立长连接,在读取时也可以对slaver的队列进行操作,优化了读取操作 4.3.2 持久化 metaq采用mmap(将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系)内存映射文件的方法,将消息持久到文件,提高了性能 4.4 总结 producer --> broker 、broker --> consumer 都依靠返回值的确认机制来保障,而broker端可靠性则依赖于本地文件持久化、容灾机制 5. 有序消息 metaq采用的是逻辑分区,即消息按序存储,但索引号是根据consumer queue决定,因此,只需保证producer单线程发送消息,且指定相同队列号即可保证消息的顺序性,源码中采用大小为1的semaphore信号量确保单线程传输 NettyRemotingAbstract public void invokeOnewayImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis)throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { request.markOnewayRPC(); boolean acquired = this.semaphoreOneway.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS); if (acquired) { final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreOneway); try { channel.writeAndFlush(request).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture f) throws Exception { once.release(); if (!f.isSuccess()) { log.warn("send a request command to channel <" + channel.remoteAddress() + "> failed."); } } }); } catch (Exception e) { once.release(); log.warn("write send a request command to channel <" + channel.remoteAddress() + "> failed."); throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e); } } else { if (timeoutMillis <= 0) { throw new RemotingTooMuchRequestException("invokeOnewayImpl invoke too fast"); } else { String info = String.format("invokeOnewayImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d", timeoutMillis, this.semaphoreOneway.getQueueLength(), this.semaphoreOneway.availablePermits() ); log.warn(info); throw new RemotingTimeoutException(info); } } } 6. 负载均衡 由于消费者只需根据topic从broker拉取消息即可,这里只讨论消生产者的负载均衡,broker端采用逻辑分区,在生产者看来所有的分区组成了分区列表依次使用,本质就是发送消息时合理选择分区的问题,metaq在初始化时从配置中心获取publish的topic对应的broker和分区列表,生产者在发送消息的时候必须选择一台broker上的一个分区来发送消息,默认的策略是一个轮询的路由规则,以此达到负载均衡的效果 代码演示 1. 发布topic 这里是将topic发布到name server上,便于构建TopicRouteData,为主题分配broker 2. 添加maven依赖 <dependency> <groupId>com.taobao.metaq.final</groupId> <artifactId>metaq-client</artifactId> <version>4.2.0.Final</version> </dependency> 3. Producer public class Producer { public static void main(String[] args) throws MQClientException, InterruptedException { MetaProducer producer = new MetaProducer("intern-metaq-demo-normal-consumer"); producer.start(); for (int i = 0; i < 1000; i++) { try { Message msg = new Message("intern-demo-sc",// topic "TagA",// tag ("sc " + i).getBytes()// body ); SendResult sendResult = producer.send(msg); System.out.println(sendResult.getSendStatus()); //Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); Thread.sleep(1000); } } producer.shutdown(); } } 4. Consumer public class Consumer { public static void main(String[] args) throws InterruptedException, MQClientException, MQClientException { MetaPushConsumer consumer = new MetaPushConsumer("CID-sc"); consumer.subscribe("intern-demo-sc", "*"); consumer.registerMessageListener(new MessageListenerConcurrently() { public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, //监听消息 ConsumeConcurrentlyContext context) { for (MessageExt msg : msgs) { //处理消息 System.out.println("ReceivedMsgId: " + new String(msg.getMsgId()) + " ReceivedMsgBody: " + new String(msg.getBody())); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); consumer.start(); System.out.println("Consumer Started."); } } 总结 1. 中间件与 rpc区别 1.1 业务逻辑层面 rpc:远程过程调用,强调的是解耦,将不同的功能模块拆分,但程序运行上还是作为一个整体,各个模块内部由不同平台根据具体业务场景开发,彼此之间通过rpc远程调用 中间件:解耦+异步,达到了1+1 > 2 的效果,遵循的主旨是最终一致性原则 参考自Notify和Metaq ——学习总结,以用户注册为例,需要的流程如下 在早期的业务处理流程中,会采用串行的执行方式,这样程序运行逻辑和业务处理流程相一致,但带来的问题很多,一方面程序总体耗时是单纯的线性叠加,另一方面,若果业务流程中某一环节出错,就不得不全部rollback,这样的代价太大;而中间件的着 眼点在于业务层面的解耦,仔细分析,我们可以发现该业务的主要逻辑是用户注册,而后续的准备支付宝,通知sns等操作虽然必须要完成,但却是不需要让用户等待的,经过分拆的流程如下 这样采取异步并行的方式来完成业务,极大的减少延迟,但带来的问题就是如果某一环节出错,整体流程不会因此rollback,比如用户注册成功但是却没有通过合法性验证,这样带来的就是数据上的危害,因此需要确保拆分模块运行的可靠性,经过中间件分 拆的最终模块如下 可以看出,中间件在业务逻辑优化的过程中起到了:1. 异步:解耦功能模块,并行执行缩短耗时;2. 可靠性保证:通过内部机制确保并行模块运行的可靠性 1.2 使用方式 在代码使用方面,hsf采用的是本地代理,最终通过invoke反射调用方法并返回结果;metaq则是producer -- broker、broker -- consumer 直接传输消息 2. metaq 与 notify 区别 最本质的区别就是notify采用push方式,broker不停地向consumer推送消息,这样确保了消息的可达性但却牺牲了服务器的性能,而metaq采用pull方式(pull模拟push),服务端只保存一定时间内的消息,提升了broker的性能,各方面对比见下表,可见在绝大 多数领域metaq性能更加优秀,因此notify正逐渐合并到metaq项目中 性能 metaq notify 消息推送方式 pull push 顺序性 局部有序 不提供 消息重复 部分避免 不提供 实时性 较高 高 消息堆积能力 强 较强 分布式 不支持 支持 过滤机制 单一 多种过滤机制 广播 支持 不支持 3. 削峰填谷 在metaq的介绍中,开始看到这个名词时感觉很高深,但随着深入学习,metaq的功能逐渐清晰起来。简单来说,削峰填谷是概括了中间件的功能,即异步并发,阻挡前端的数据洪峰,舒缓下游机器压力。试想这样的情景,在双11等业务场景下,用户瞬间的请 求量是爆炸式的,如果采用传统的同步顺序执行逻辑,所有的请求在没有完成前都需要挂载在服务端,服务器的压力可想而知,但采用中间件解耦业务逻辑,则用户请求在完成主体需求后可以直接退出,由中间件堆积消息并确保剩余业务的完成,这就可以容纳更多 的用户请求,因此,一个设计规范,运行良好的中间件系统对于公司业务的拓展骑着极为重要的作用 参考文章 http://www.jasongj.com/2015/03/10/KafkaColumn1/ https://www.atatech.org/articles/95456 https://www.atatech.org/articles/36160#5 https://www.atatech.org/articles/102577 https://www.atatech.org/articles/108048 https://www.atatech.org/articles/110786 https://www.cnblogs.com/huxiao-tee/p/4660352.html

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

JavaScript学习笔记

一:数组 //数组的构造的第一种方法 var arr=[1,2,3] var arr=[1,"2",true] //数组的构造的第二种方法 var arr1=new Array(5);//表示创建一个长度为5的数组 alert("arr1:"+arr1.length)//输出数组的长度 //数组的构造的第三种方法 var arr2=new Array(1,2,3); 二:方法的构造 //方法构造的第一种方法 function 方法名(参数列表){ 方法体; 返回值(可有可无) } //实例1 无参 function test(){ alert("aaa"); } //调用 test(); //实例2 有参 function test2(a,b){ var sum=a+b; alert(sum); } //调用 test(2,3); //方法构造的第二种方法 //匿名函数 var add=function(参数列表){ 方法体和返回值; } //实例 var add3= function(m,n){ alert(m+n); } //调用 add3(5,6); 三:js的全局变量和局部变量 **全局变量:在js页面标签中定义一个变量,这个变量在页面中的js部分都可以使用 - 在方法的外部使用,在方法的内部使用,在另一个Script标签使用 **局部变量:在方法内部定义一个变量,只能在方法内部使用 - 在方法外部调用这个变量会出错 【如果大家对程序员,web前端感兴趣,想要学习的,关注一下小编吧。加群:731771211。免费赠送web前端系统的学习资料!!前端学习必备公众号ID:mtbcxx】 四:js的重载 方法名相同,参数列表不同 function add1(a,b){ return (a+b); } function add2(a,b,c){ return (a+b+c); } add1(2,3); add2(2,3,4); 五:js的String对象 **创建String对象 ***var Str=""; **String属性 *** length 长度 **方法 (1)和html相关的方法 - bold();//加粗 var str1="sss"; document.write(Str1.blid); - fontcolor();//设置字符串的颜色 document.write(Str1.fontcolor("red")); - fontsize();//范围数字1-7,设置字体的大小 document.write(Str1.fontsize(2)); - link();//让字符串显示成超链接 document.write(Str1.link("hello.html")); - sub();//下标 document.write(Str1.sub()); //几次方 - sup();//上标 var str2="sss"; document.write(Str1.sup()); (2)与java相似的方法 var str1="abc"; var str2="def"; - concat();//连接字符串的方法 document.write(Str1.concat(str2));//abcdef - charAt();//返回指定位置的字符串 document.write(Str1.charAt(0));//a //字符串位置不存在,返回的是一个空的字符串 - indexOf();//返回字符串的位置 document.write(Str1.indexOf("a")); //字符串不存在返回-1 - split();//切分字符串,切分为数组 var str3="d-e-f"; var arr1=Str3.split("-"); document.write(Str3.length); - replace();//将指定得字符串替换成你将要换成的字符串 document.write(str1.replace("a","ttt")); - substr();和subString(); var str4="abcdefghuiop"; document.write(str4.sunstr(5,3));//fgh[5,3] //从第五位开始向后截取3位 document.write(str4.sunstrng(5,3));//fg //从第几位开始到第几位结束,但不包含最后一位[5,3) 六,js 的Array对象 **创建数组 -var arr1=[1,2,3]; -var arr2=new Array(3); -var arr3=new Array(1,32,3); **方法 -conact();//数组的连接 var arr1=[1,2,3]; var arr2=[4,5,6]; document.write(arr1.concat(arr2));//1,2,3,4,5,6 -join();//根据一个指定的字符进行数组分割 document.write(arr1.join("-"));//1-2-3 -push();//向数组的末尾添加一个或多个元素,返回新的数组长度 document.write(arr1.push("qwe"));//1,2,3,qwe document.write(arr1.push(arr2)); //把数组arr2添加到arr1之后,新的数组长度为四,因为是把数组arr2整体当成一个元素添加到了arr1中 -pop();//删除最后一个元素并返回 document.write(arr1.pop());//返回3 -reverse();//倒序 document.write(arr1.reverse()); 七.js的date对象 ** js获取当前时间 var date=new Date(); document.write(date);//未格式化的时间 document.write(date.toLocaleString());//格式化后的时间 -getFullYear();//获取当前的年份 document.write(date.getFullYear()); -getMonth();//获取当前月份,注意,获取的月份是(0-11) var month=date.getMonth(); document.write(month+1); -getDay();//获取当前的星期,注意,获取的星期是(0-6) -getDate();//获取当前的天 -getHours();//获取 当前的小时 -getMinutes();//获取当前的分钟 -getSeconds();//获取当前的秒 -getTime();//获取毫秒数,返回1970年1月1日至今的毫秒数 //应用场景 //使用毫秒数处理缓存http://www.baidu.com?毫秒数 八,js的Math对象 **数学 var mm=10.4; -cell(x);//向上舍入 document.write(Math.ceil(mm));//11 -floor(x);//向下舍入 document.write(Math.fllor(mm));//10 -round(x);//四舍五入 document.write(Math.round(mm));//10 -random();//得到随机数(0-1),是伪随机数 document.write(Math.random());//小数0.23131441 document.write(Math.floor(Math.random()*10));//0-9 -Max(x,y);//返回最大值 -pow(x,y);//返回x的y次方 九.js的全局函数 *由于不属于任何一个对象,直接写名称使用 -eval();//执行js代码 var str="alert('1234')"; eval(str);//1234 -encodeURI();// 对字符进行编码 var str="测试中文aaa1234"; var encode1=encodeURI(str); document.write(enocde1); -decodeURI();// 对字符进行解码 var encode2=encodeDRI(str); document.write(enocde2); -encodeURIComponent();// -decodeURIComponent();// -isNaN();//判断当前字符串是否为数字 var str="123";//false document.write(isNaN(str));//是数字返回false,如果不是数字返回的是true -parseInt();//类型转换 var str="123"; document.write(parseInt(str)+1);//124 十.js的重载 ** js的重载是否存在?不存在 **会调用最近的方法(面试题) (1)js里面不存在重载 (2)但是可以通过其他方法来模拟重载 arguments数组 十一.js的bom对象 ** broswer object model --对象 **navigater://获取浏览器的名称 document.write(navigater.appName); **screen:获取屏幕的信息 document.write(screen.height); document.write(screen.weight); **location:请求的URL地址 -href属性: ***获取请求的url地址 document.write(location.href); ***设置URL地址 一个页面中设置一个按钮,连接到另一个页面 鼠标点击事件 onclick="js的方法"; **history:请求URL的历史记录 // -到访问的上一个页面 function back(){ history.back(); //history.go(-1);//等同于back();方法 } -到访问的下一个页面 function next(){ history.forward(); //history.go(1);//等同于forward();方法 } **window(重点) *窗口对象 *顶层对象(所有的bom对象都是在window里面操作) **方法 -window.alert();//在窗口弹出一个页面 -window.confirm();//确认提示框 -var flag=window.confirm("是否删除"); alert(flag);//true -window.prompt();//输入对话框 window.prompt(text,defaulttext); window.prompt("显示输入提示","默认值"); window.prompt("请输入:","0"); -open();// window.open("连接到新的地址","","窗口特征,比如高和款等信息"); window.open("hello.html","","heigth=200,weight=300"); -close();//关闭窗口,兼容性比较差 -做定时器 ** setInterval("js代码",毫秒数); window.setInterval("alert('123')",3000);//每三秒就执行一次alert方法 ** setTimeout("js代码",毫秒数);//在指定的毫秒数之后去执行,但是只执行一次 window.setTimeout("alert('123')",3000);//在三秒之后执行alert,但是只执行一次 ** clearInterval();//清除setInterval的方法 var clear1=window.setInterval("alert('123')",3000);\ function clear1(){ clearInterval(clear1); } ** clearTimeout();//清除setTimeout的方法 var clear1=window.setTimeout("alert('123')",3000); function clear2(){ clearTimeout(clear1); } 十二.js的dom对象 * dom : document object model ** 文档 超文本标记文档 html xml ** 对象 提供了属性和方法 ** 模型 提供属性和方法操作超文本标记性文档 *** 可以使用js里面的dom对象进行操作 *** 想要对标记性超文本文档进行操作,首先需要对标记性超文本型文档里面所有的内容封装成对象 --需要把html里面的标签、属性、文本内容封装成对象 *** 解析过程 根据html的成绩结构,在内存中分配一个树形结构,需要把html中的每一个部分都封装成一个对象 -document对象:整个文档 -element对象:标签对象 -属性对象 -文本对象 --Node节点对象:这个对象时这些对象的父对象 *** 如果在这些对象里面找不到相对应的方法,这个时候需要到Node对象里面去找 * DHTML:是很多技术的简称 ** html:封装数据 ** css:使用属性和属性值设置样式 ** dom:操作html文档(标记性文档) ** JavaScript:专门指的是js的语法语句 十三.document对象:整个文档 * 表示整个文档 ** 常用方法 - write方法 (1)向页面输出变量值 document.write(str1); (2)向页面输出html代码 document.write(" "); - getElementById();//通过id得到标签,返回的是一个对象 var input=document.getElementById("nameid");//获取的是标签中的id值 //得到input里面的value值 alert(input.value); //向input里面设置一个value值 input.value="bbbb"; - getElementsByName();//通过标签的name属性值来得到标签,返回的是一个数组 var input=document.getElementsByName("name1"); alert(input.length); //遍历数组 for(var i=0;i var in=nput[i];//每次循环得到的是input的对象 alert(in.value);//得到每个input标签的value值 } - getElementsByTagName(“标签名称”);//返回的是一个数组 var in=document.getElementsByTagName("input");//传递的参数是标签名称 alert(in.length); //遍历集合,每次得一个input标签 for(var i=0;i var ip=in[i]; alert(ip.value); } *** 注意地方 //只有一个标签,这个标签智能通过name获取到,这个使用getElementByName返回的是一个数组 var in=document.getElementsByName("name1")[0]; alert(in.value); //通过input标签获取 var in=document.getElementsByName("input")[0]; alert(in.value); 十四.window弹窗案例 //实现s1的方法 //需要把num1和num2赋值到window页面 //跨页面操作 opener:创建这个窗口的窗口 得到window页面 function s1(num1,num2){ var pwin=window.opener;//得到window的页面 pwin.document.getElementById("nameId").value=num1; pwin.document.getElementById("pwdId").value=num2; window.close(); } 十五:元素对象(element) ** 要操作element对象,首先要获取element ** 方法 获取值 var input=document.getElementById("inid"); alert(input.getAttribute("value")); 设置值 input.setAttribute("参数名","haha"); input.setAttribute("class","haha"); 删除属性,但是不能删除value属性 input.removeAttribute("name"); ** 获取标签下面的子标签 childNodes()(兼容性差),getElementsByTagName()(兼容性高) 111 222 333 //获取ul标签 var ul1=document.getElementById("ulid"); //返回的是一个集合 //获取ul下面的子标签,兼容性差 var lis=ulid.childNodes; alert(lis.length); //获取ul下面的子标签,兼容性高 var lis1=ulid.getElementsByTagName("li") alert(lis.length); ** Node对象属性1 ***nodeName 节点名称 ***nodeType 节点类型 ***nodeValue 节点值 **标签节点对应的值 haha var span1=document.getElementById("spanid"); alert(span1.nodeName); // SPAN 大写标签名称 alert(span1.nodeType); // 1 alert(span1.nodeValue); // null //属性节点对应的值 var id1=spqn1.getAttributeNode("id"); alert(id1.nodeName); // id alert(id1.nodeType); // 2 alert(id1.nodeValue); // spanid //获取文本对象 var test1=span1.firstChild; //文本节点对应的值 alert(test1.nodeType); // 3 alert(test1.nodeName); // #text alert(test1.nodeValue); // haha ** Node对象属性2 111 222 333 var li1=document.getElementById("li1"); * 父节点 ul是li的父节点 parentNode:父节点 var ul1=li1.parentNode; //得到ul alert(ul1.id); * 子节点 li是ul的子节点 childNodes:得到所有的子节点,但是兼容性差 firstChild:第一个子节点 //获取ul的第一个子节点 //得到ul var ul1=document.getElementById("ulid"); var li1=ul1.firstChild; alert(li1.id); // li1 lastChild:最后一个子节点 //获取ul的最后一个子节点 //得到ul var ul1=document.getElementById("ulid"); var li4=ul1.lastChild; alert(li4.id); // li4 * 同辈对象 nextSibling:下一个兄弟节点 previousSibling:上一个兄弟节点 //获取li的id是li3的上个和先一个兄弟节点 var li=document.getElementById("li3"); alert(li.nextSibling.id); // li4 alert(li.previousSibling.id); // li2 十六.操作dom树 111 222 333 **apendChild方法 - 添加子节点到末尾,类似于剪切 //获取div2id的id var div2=document.getElementById("div2id"); //获取ulid的id var ul1=document.getElementById("ulid"); //把ulid中的li标签添加到div2中 ulid.apendChild(div2id); ** insertBefore(newNode,oldNode)方法 - 在么某个节点之前插入一个新的节点 //在 333 之前添加一个 000 111 222 333 function insert1(){ //获取li3的标签 var li3=document.getElementById("li3"); //获取ulid的标签 var ulid=document.getElementById("ulid"); //创建li标签 var li4=document.createElement("li"); //创建文本 var text4=document.createTextNode("000"); //把文本添加到ul标签里面(在 333 之前添加一个 000 ) ulid.insertBefore(li4,li3); } ** 不存在 insertAfter();方法 ** removeChild(); 删除节点 //删除li id="li3">333 //获取里标签 var li3=document.getElementById("li3"); //获取父节点 通过id获取或者是通过parentNode获取 var ulid=document.getElementById("ulid"); ulid.remove(li3); ** replaceChild(newNode,oldNode);//替换节点,通过父节点进行替换 //替换 333 为 444 111 222 333 function replace1(){ //获取li3的标签 var li3=document.getElementById("li3"); //创建新的标签 var li4=document.createElement("li"); //创建新的标签文本 var text4=document.createTextNode("444"); //把文本添加到标签 li4.apendChild(text4); //获取ulid标签 var uid=document.getElementById("ulid"); //执行替换方法 uid.replaceChild(li4,li3); } ** 赋值节点 ***cloneNode(boolean); 111 222 333 function cloneNode1(){ //复制 333 到div中 //获取ulid标签 var ulid=document.getElementById("ulid"); //执行复制方法 var copyul=ulid.cloneNode(true); //获取divv的标签 var divv=document.getElementById("divv"); //把复制的ul标签放在新的div里面 divv.apendChild(copyul); } ** 操作dom树的总结 * 获取节点使用方法 getElementById();//通过节点的id属性,查找相应的节点 getElementsByName();//通过节点的name属性,查找相应的节点 getElementsByTagName();//通过节点名称,查找相应的节点 *插入节点的方法 insertBefore方法: appendChild方法:添加到末尾 *删除方法 removeChild方法:通过父节点删除 * 替换节点的方法 replaceChild方法:通过父节点进行替换 十七.innerHTML属性 * 这个属性不是dom树的组成部分,但是大多数浏览器都支持的属性 第一个作用:获取文本的内容 haha //获取span标签 var span1=document.getElementById("spanid"); //获取标签的文本内容 alert(span1.innerHTML); 第二个作用:向标签里面设置内容(可以是HTML代码) haha //获取span标签 var span1=document.getElementById("spanid"); //获取div的标签 var div1=document.getElementById("divid"); //向div1标签里面设置内容(可以是HTML代码) div1.innerHTML=" hehehe "; * 案例 * 得到当前时间 var date =new date(); var d1= date.toLocaleString(); *需要每一秒获取时间 setInterval方法,定时器 *显示到页面上 每一秒向div写一次时间 * 使用innerHTML属性 function getd1(){ //获取当前时间 var date = new date(); //格式化当前时间 var d1 = date.toLocaleString(); //获取div的id var divv = document.getElementById("divid"); //添加到div中 divv.innerHTML = d1; } //使用定时器实现每一秒写一次时间 setInterval("getd1();",1000); 如果对你有帮助的话,可以点赞收藏哟! 【如果大家对程序员,web前端感兴趣,想要学习的,关注一下小编吧。加群:731771211。免费赠送web前端系统的学习资料!!前端学习必备公众号ID:mtbcxx】

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

Python进阶笔记

列表生成式 函数的参数类型 lambda函数 map, reduce, filter, sorted函数 eval, exec, join, zip函数 itertools中的函数 copy与deepcopy函数 模块 os、sys模块 迭代器 生成器 迭代器 参考网站: Python3教程: https://www.python-course.eu/python3_course.php Python之函数参数的使用:https://blog.csdn.net/jclian91/article/details/78309522 廖雪峰Python教程: https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000 Python之浅谈exec函数: https://blog.csdn.net/jclian91/article/details/80076512 Python官网的itertools说明: https://docs.python.org/3.6/library/itertools.html Python-copy()与deepcopy()区别: https://blog.csdn.net/qq_32907349/article/details/52190796 copy模块官网:https://docs.python.org/3.5/library/copy.html 列表生成式 列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。一般是利用原有的数据结构来生成新的列表。 # 利用range()生成[1,2,...,9,10] list(range(1,11)) # 生成[1x1, 2x2, 3x3, ..., 10x10] [x * x for x in range(1, 11)] # 可以通过占位符_代表列表中的元素 [_*_ for _ in range(1,11)] # 筛选出仅偶数的平方, 在for循环后加上if判断语句 [x * x for x in range(1, 11) if x % 2 == 0] # 利用占位符简化 [_*_ for _ in range(1, 11) if not _%2] # 两层循环,三层循环,.... [m + n for m in 'ABC' for n in 'XYZ'] [x+y+z for x in 'ab' for y in 'cd' for z in 'ef'] # 遍历字典,生成列表 d = {'x': 'A', 'y': 'B', 'z': 'C' } [k + '=' + v for k, v in d.items()] 函数的参数类型 在Python中定义函数,其参数类型有: - 位置参数 - 默认参数 - 可变参数 - 关键字参数 这4种参数都可以一起使用,或者只用其中某些,但是请注意,参数定义的顺序必须是:位置参数、默认参数、可变参数和关键字参数。 可变参数以*开头,允许传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。关键字参数以**开头,允许传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个dict。若默认参数与可变参数放在一起,则接受完默认参数后,其后参数为可变参数。 位置参数 位置参数指定名称的必须放在未指定名称的后面 def person(name,age,city): s = "info: name=%s, age=%s, city=%s"%(name,age,city) return s print(person('Jack', 25, 'NY')) print(person(name='Jack', age=25, city='NY')) print(person('Jack', 25, city='NY')) # 下面的参数使用有误,位置参数指定名称的必须放在未指定名称的后面 print(person(name='Jack', 25, 'NY')) 默认参数 默认参数必须放在非默认参数的后面,可以该表默认参数的值 def person(name, city, age=18): s = "info: name=%s, age=%s, city=%s"%(name,age,city) return s print(person('Jack', 'NY')) print(person('Jack', 'NY', 20)) 可变参数 可变参数以*开头,允许传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。函数参数的长度是可以变化的, 例如内置的sum, min, max等 def var_sum(*args): sum = 0 for i in args: sum += i return sum print(var_sum(1,2,3)) print(var_sum(1,2,3,4)) # 利用*号来分解参数 print(var_sum(*[1,2,3,4,5])) 若位置参数或默认参数与可变参数放在一起,则接受完位置参数或默认参数后,其后参数为可变参数。 def var_sum(a, *args): sum = 0 for i in args: sum += i print('a is %s, sum is %s'%(a,sum)) var_sum(1,2) var_sum(1,2,3) 关键字参数 关键字参数以**开头,允许传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个dict。 def test_args(**kwargs): print('-'*20) for key in kwargs: print('key:', key, ',value:', kwargs[key]) print() test_args(a=1,b=2) test_args(a=1,b=2,c=3) lambda函数 lambda函数即为匿名函数,用关键字lambda表示,冒号(:)前面的为参数,后面为返回值,不用写return. 如: lambda x: x*x 匿名函数有个限制,就是只能有一个表达式,一般一行代码,不用写return,返回值就是该表达式的结果。 用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数,即函数也是变量,此为函数式编程(functional programming)思想。 f = lambda x: x*x f(5) map, reduce, filter, sorted函数 map函数 map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。 可以直接作用于for循环的对象统称为可迭代对象:Iterable. 举例说明,比如我们有一个函数f(x)=x^2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下: # map函数: 一一映射 def f(x): return x * x r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) list(r) # 利用lambda简化上述代码 list(map(lambda x: x*x, range(1, 11))) 再例如: 把list所有数字转为字符串: list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])) reduce函数 reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,一个是函数,一个是Iterable. reduce把结果继续和序列的下一个元素做累积计算,其效果就是: reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) 比方说对一个序列求和,就可以用reduce实现: # 导入reduce, 这很重要 from functools import reduce def add(x, y): return x + y reduce(add, [1, 3, 5, 7, 9]) # 利用lambda函数简化 reduce(lambda x,y: x+y, range(1,10,2)) 作业: 利用reduce将序列[1, 3, 5, 7, 9]转化为整数13579. map, reduce的一个复杂例子: 将字符串列表[‘1’, ‘3’, ‘5’, ‘7’, ‘9’]转化为整数13579 from functools import reduce a = ['1', '3', '5', '7', '9'] t = reduce(lambda x,y: 10*x+y, map(int, a)) print(t) filter函数 Python内建的filter()函数用于过滤序列。 和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。 例如,在一个list中,删掉偶数,只保留奇数,可以这么写: list(filter(lambda x: x%2 == 1, [1, 2, 4, 5, 6, 9, 10, 15])) sorted函数 Python内置的sorted()函数就可以对list进行排序。 sorted([36, 5, -12, 9, -21]) 此外,sorted()函数还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序: sorted([36, 5, -12, 9, -21], key=abs) sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) 高阶函数,就是让函数的参数能够接收别的函数。map, reduce, filter, sorted都是高阶函数。 join, zip, eval, exec函数 join函数 Python中的join函数有两个,分别为: join()和os.path.join(),具体作用如下: join(): 连接字符串数组。将字符串、元组、列表中的元素以指定的字符(分隔符)连接生成一个新的字符串 os.path.join(): 将多个路径组合后返回 字符串中的join()函数的使用方法: ‘sep’.join(seq) sep:分隔符。可以为空。 seq:要连接的元素序列。 返回一个新的字符串。 seq = ['hello','good','boy','Dido'] print(' '.join(seq)) print('*'.join(seq)) zip函数 zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。 如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。 # basic use of zip x = [1, 2, 3] y = [4, 5, 6] zipped = zip(x, y) print(list(zipped)) # zip for loops for i,j in zip(x,y): print(i, "->", j) # unzip the list a = [(1,2,3), (3,4,5)] x2, y2, z2 = zip(*a) print(x2) print(y2) print(z2) # transpose a matrix mtx = [(1, 2), (3, 4), (5, 6)] print(list(zip(*mtx))) # clustering a data series into n-length groups idiom seq = range(1, 10) print(list(zip(*[iter(seq)]*3))) # dict and zip keys = ['spam', 'eggs'] vals = [42, 1729] d = dict(zip(keys, vals)) print(d) eval函数 eval函数用来计算字符串表达式的值 t = eval("23") print(t) print(type(t)) print(eval("(1+2)*(3+4)")) exec函数 exec()是Python的内置函数,不同于eval()函数只能执行计算数学表达式的结果的功能,exec()能够动态地执行复杂的Python代码,能够十分强大。 简单例子: # 执行简单的Python语句 i = 12 j = 13 exec("answer=i*j") print("Answer is %s"%answer) # 执行复杂的Python语句 func = "def fact(n):\n\treturn 1 if n==1 else n*fact(n-1)" exec(func) a = fact(5) print(a) exec函数还可以执行储存在其他文件中的Python代码,例如位于E盘的eg.txt,如下: def fact(n): if n==1: return 1 else: return n*fact(n-1) t = fact(6) print(t) 利用exec函数执行eg.txt中的代码: with open('E://eg.txt', 'r') as f: s = f.read() exec(s) 还可以在exec()函数中加入参数,参数的传递可以写成字典(dict)形式。 x = 10 expr = """ z = 30 sum = x + y + z print(sum) """ def func(): y = 20 exec(expr) exec(expr, {'x': 1, 'y': 2}) exec(expr, {'x': 1, 'y': 2}, {'y': 3, 'z': 4}) func() 输出结果为: 60 33 34 itertools模块中的函数 Python的内建模块itertools提供了非常有用的用于操作迭代对象的函数。 itertools模块提供的全部是处理迭代功能的函数,它们的返回值不是list,而是Iterator,只有用for循环迭代的时候才真正计算。 无穷迭代器 Iterator Arguments Results Example count() start, [step] start, start+step, start+2*step, … count(10) –> 10 11 12 13 14 … cycle() p p0, p1, … plast, p0, p1, … cycle(‘ABCD’) –> A B C D A B C D … repeat() elem [,n] elem, elem, elem, … endlessly or up to n times repeat(10, 3) –> 10 10 10 “有限”迭代器 Iterator Arguments Results Example accumulate() p [,func] p0, p0+p1, p0+p1+p2, … accumulate([1,2,3,4,5]) –> 1 3 6 10 15 chain() p, q, … p0, p1, … plast, q0, q1, … chain(‘ABC’, ‘DEF’) –> A B C D E F chain.from_iterable() iterable p0, p1, … plast, q0, q1, … chain.from_iterable([‘ABC’, ‘DEF’]) –> A B C D E F compress() data, selectors (d[0] if s[0]), (d[1] if s[1]), … compress(‘ABCDEF’, [1,0,1,0,1,1]) –> A C E F dropwhile() pred, seq seq[n], seq[n+1], starting when pred fails dropwhile(lambda x: x<5, [1,4,6,4,1]) –> 6 4 1 filterfalse() pred, seq elements of seq where pred(elem) is false filterfalse(lambda x: x%2, range(10)) –> 0 2 4 6 8 groupby() iterable[, keyfunc] sub-iterators grouped by value of keyfunc(v) islice() seq, [start,] stop [, step] elements from seq[start:stop:step] islice(‘ABCDEFG’, 2, None) –> C D E F G starmap() func, seq func(*seq[0]), func(*seq[1]), … starmap(pow, [(2,5), (3,2), (10,3)]) –> 32 9 1000 takewhile() pred, seq seq[0], seq[1], until pred fails takewhile(lambda x: x<5, [1,4,6,4,1]) –> 1 4 tee() it, n it1, it2, … itn splits one iterator into n zip_longest() p, q, … (p[0], q[0]), (p[1], q[1]), … zip_longest(‘ABCD’, ‘xy’, fillvalue=’-‘) –> Ax By C- D- groupby()函数 groupby()把迭代器中相邻的重复元素挑出来放在一起: for key, group in itertools.groupby('AAABBBCCAAA'): print(key, list(group)) A ['A', 'A', 'A'] B ['B', 'B', 'B'] C ['C', 'C'] A ['A', 'A', 'A'] 实际上挑选规则是通过函数完成的,只要作用于函数的两个元素返回的值相等,这两个元素就被认为是在一组的,而函数返回值作为组的key。 另一个例子 # 按身高归类 from itertools import * def height_class(h): if h>180: return 'tall' elif h<160: return 'short' else: return 'middle' friends = [191, 158, 159, 165, 170, 177, 181, 182, 190] for m,n in groupby(friends,key = height_class): print(m) print(list(n)) 作业: 对于一组身高的数据(list),利用上面代码给出的身高标准,将所以的tall, short, middle归为一类。注意与groupby()函数的区别。 tee()函数 把一个迭代器分为n个迭代器, 返回一个元组.默认是两个 from itertools import * a = "hello" c, d, e = tee(iter(a), 3) for i, j, k in zip(c, d, e): print(i, j, k) 组合生成器 Iterator Arguments Results product() p, q, … [repeat=1] cartesian product, equivalent to a nested for-loop permutations() p[, r] r-length tuples, all possible orderings, no repeated elements combinations() p, r r-length tuples, in sorted order, no repeated elements combinations_with_replacement() p, r r-length tuples, in sorted order, with repeated elements product(‘ABCD’, repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD permutations(‘ABCD’, 2) AB AC AD BA BC BD CA CB CD DA DB DC combinations(‘ABCD’, 2) AB AC AD BC BD CD combinations_with_replacement(‘ABCD’, 2) AA AB AC AD BB BC BD CC CD DD copy与deepcopy函数 copy: 浅拷贝(shallow copy), deepcopy: 深拷贝(deep copy). 我们寻常意义的复制就是深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。 而浅复制并不会产生一个独立的对象单独存在,他只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。这就和我们寻常意义上的复制有所不同了。 对于简单的 object,用 shallow copy 和 deep copy 没区别 复杂的 object, 如 list 中套着 list 的情况,shallow copy 中的 子list,并未从原 object 真的「独立」出来。也就是说,如果你改变原 object 的子 list 中的一个元素,你的 copy 就会跟着一起变。这跟我们直觉上对「复制」的理解不同。 例子: from copy import copy, deepcopy #origin 里边有三个元素:1,2,[3, 4] origin = [1, 2, [3, 4]] # cop1为浅拷贝,cop2为深拷贝 cop1 = copy(origin) cop2 = deepcopy(origin) # cop1是否与cop2内容相同 print(cop1 == cop2) # cop1是否与cop2为同一个引用 print(cop1 is cop2) # 改变origin中嵌套列表中的元素 origin[2][0] = "hey" # 查看输出 print(origin) print(cop1) print(cop2) # 改变origin中嵌套列表中的元素 origin[1] = "hello" # 查看输出 print(origin) print(cop1) print(cop2) 输出结果: True False [1, 2, ['hey', 4]] [1, 2, ['hey', 4]] [1, 2, [3, 4]] [1, 'hello', ['hey', 4]] [1, 2, ['hey', 4]] [1, 2, [3, 4]] 模块 在Python中,一个Python文件就是一个模块。 模块让你能够有逻辑地组织你的 Python 代码段。 把相关的代码分配到一个模块里能让你的代码更好用,更易懂。 模块能定义函数,类和变量,模块里也能包含可执行的代码。 一个简单的模块例子: hello.py def say_hello(name): s = 'hello, %s!'%name return s 使用模块: import module from module import … import hello print(hello.say_hello("Lee")) from hello import say_hello print(say_hello("Jack")) os、sys模块 os模块 os模块包含普遍的操作系统功能。 os常用方法及属性 os.sep 可以取代操作系统特定的路径分隔符。windows下为 “\” os.name字符串指示你正在使用的平台。比如对于Windows,它是’nt’,而对于Linux/Unix用户,它是’posix’。 os.getcwd() 函数得到当前工作目录,即当前Python脚本工作的目录路径。 os.getenv() 获取一个环境变量,如果没有返回none os.putenv(key, value) 设置一个环境变量值 os.listdir(path) 返回指定目录下的所有文件和目录名。 os.remove(path) 函数用来删除一个文件。 os.system(command) 函数用来运行shell命令。 os.linesep 字符串给出当前平台使用的行终止符。例如,Windows使用’\r\n’,Linux使用’\n’而Mac使用’\r’。 os.curdir: 返回当前目录(’.’) os.chdir(dirname): 改变工作目录到dirname os.path常用方法: os.path.isfile()和os.path.isdir()函数分别检验给出的路径是一个文件还是目录。 os.path.existe()函数用来检验给出的路径是否真地存在 os.path.getsize(name):获得文件大小,如果name是目录返回0L os.path.abspath(name):获得绝对路径 os.path.normpath(path):规范path字符串形式 os.path.split(path) :将path分割成目录和文件名二元组返回。 os.path.splitext():分离文件名与扩展名 os.path.join(path,name):连接目录与文件名或目录;使用“\”连接 os.path.basename(path):返回文件名 os.path.dirname(path):返回文件路径 sys模块 sys模块提供了一系列有关Python运行环境的变量和函数。 sys模块的常用方法 sys.argv: 实现从终端向程序传递参数。 sys.exit([arg]): 程序中间的退出,arg=0为正常退出。 sys.getdefaultencoding(): 获取系统当前编码,一般默认为ascii。 sys.setdefaultencoding(): 设置系统默认编码,执行dir(sys)时不会看到这个方法,在解释器中执行不通过,可以先执行reload(sys),在执行 setdefaultencoding(‘utf8’),此时将系统默认编码设置为utf8。(见设置系统默认编码 ) sys.getfilesystemencoding(): 获取文件系统使用编码方式,Windows下返回’mbcs’,mac下返回’utf-8’. sys.path: 获取指定模块搜索路径的字符串集合,可以将写好的模块放在得到的某个路径下,就可以在程序中import时正确找到。 sys.platform: 获取当前系统平台。 sys.stdin, sys.stdout, sys.stderr: stdin , stdout , 以及stderr 变量包含与标准I/O 流对应的流对象. 如果需要更好地控制输出,而print 不能满足你的要求, 它们就是你所需要的. 你也可以替换它们, 这时候你就可以重定向输出和输入到其它设备( device ), 或者以非标准的方式处理它们 生成器 通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。 所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。 创建generator的办法: 把一个列表生成式的[]改成() yield关键字 将列表的[]改成()的例子: # 列表生成式 L = [x * x for x in range(10)] print(type(L)) # 创建生成器 g = (x * x for x in range(10)) print(type(g)) # 获取下一个返回值 # 当没有更多元素时,会抛出StopIteration错误 print(next(g)) print(next(g)) print(next(g)) # for循环 for n in g: print(n) 通过yield创建生成器 # 普通方法生成斐波拉契数列 # 前几个斐波拉契数 def fib1(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done' fib1(6) # 通过yield创建生成器 # 注意yield的执行流程 def fib2(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done' # 将生成器函数赋值给变量f f = fib2(6) print(type(f)) for n in f: print(n) generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。 generator执行流程的理解: def odd(): print('step 1') yield 1 print('step 2') yield(3) print('step 3') yield(5) o = odd() print(next(o)) print(next(o)) print(next(o)) 迭代器 可以直接作用于for循环的数据类型有以下几种: 集合数据类型,如list、tuple、dict、set、str等; generator,包括生成器和带yield的generator function。 这些可以直接作用于for循环的对象统称为可迭代对象:==Iterable==。 可以使用isinstance()判断一个对象是否是Iterable对象: from collections import Iterable # 判断空列表是否为Iterable对象 # True print(isinstance([], Iterable)) # 判断空集合是否为Iterable对象 # True print(isinstance({}, Iterable)) # 判断字符是否为Iterable对象 # True print(isinstance('abc', Iterable)) # 判断生成器是否为Iterable对象 # True print(isinstance((x for x in range(10)), Iterable)) # 判断数字否为Iterable对象 # False print(isinstance(100, Iterable)) 可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。 可以使用isinstance()判断一个对象是否是Iterator对象: from collections import Iterator # 判断生成器是否为Iterator对象 # True print(isinstance((x for x in range(10)), Iterator)) # 判断空列表是否为Iterator对象 # False print(isinstance([], Iterator)) # 判断空集合是否为Iterator对象 # False print(isinstance({}, Iterator)) # 判断字符串是否为Iterator对象 # False print(isinstance('abc', Iterator)) 生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。 把list、dict、str等Iterable变成Iterator可以使用iter()函数。

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

Java ClassLoader笔记

.java:保存需要执行的程序逻辑 编译  .class:保存java代码转换后的虚拟机指令要使用某个类时,虚拟机将加载.class文件,并创建对应的class对象。 将class文件加载到虚拟机的内存的过程叫类加载,过程如下:①加载Loading:利用class文件创建class对象②验证Verification:确保class文件的字节流中包含信息符合虚拟机要求,不会危害虚拟机本身安全,包括四种验证:文件格式验证;元数据验证;字节码验证;符号引用验证。③准备Preparation:为类变量(static修饰的变量)分配内存并设置该类变量的初始值0(static int i = 5;将只会把i初始化为0,5的值在初始化时赋值)。注意:智利不包含用final修饰的static,因为final在编译时就会分配了;这里不会为实例变量分配初始化,连变量会分配在方法区中,而实例变量是会随着对象一起分配到java堆中。④解析Resolution:将常量池中的符号引用替换为直接引用的过程。⑤初始化Initialization:执行静态初始化器和静态初始化成员变量。其中②③④被称为链接(Linking)过程。 符号引用:符号引用就是字符串,这个字符串包含足够的信息,以供实际使用时可以找到相应的位置。你比如说某个方法的符号引用,如:“java/io/PrintStream.println:(Ljava/lang/String;)V”。里面有类的信息,方法名,方法参数等信息。当第一次运行时,要根据字符串的内容,到该类的方法表中搜索这个方法。运行一次之后,符号引用会被替换为直接引用,下次就不用搜索了。直接引用就是偏移量,通过偏移量虚拟机可以直接在该类的内存区域中找到方法字节码的起始位置。直接引用:(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)(3)一个能间接定位到目标的句柄符号引用和直接引用的解释:https://www.zhihu.com/question/30300585https://blog.csdn.net/kkdelta/article/details/17752097启动(BootStrap)类加载器:加载JVM自身需要的类,是虚拟机自身的一部分,会将JAVA_HOME/lib下的核心类库或-Xbootclasspath参数指定路径下的jar包加载到内存中(出于安全考虑,bootstrap类加载器只加载包名为java、javax、sun等开头的类)。 扩展(Extension)类加载器:Sun公司实现的sum.misc.Luncher$ExtClassLoader类,是Luncher的静态内部类,负责加载JAVA_HOME/lib/ext下或由系统变量-Djava.ext.dir指定路径中的类库,开发者可以直接使用标准扩展类加载器。 系统(System)类加载器:也称应用程序加载器,指sun公司实现的sum.misc.Launcher$AppClassLoader。负责加载系统类路径java –classpath或 –Djava.class.path下的类库。一般情况下该类加载器是程序默认的类加载器,可以通过ClassLoader#getSystemClassLoader()方法获取到该类加载器。 双亲委派模式加载: 原理说明:一个类加载器收到了类加载请求,不会自己先加载,而是把这个请求委派给父类加载器去执行,如果父类还有父类则继续向上委托,最终达到顶层的启动类加载器,从启动类加载器开始进行加载,如果成功则返回,否则子加载器才会尝试自己加载。好处:①防止重复加载:如果父加载器已经加载了此类子加载器就没必要再加载了②安全考虑:如果运行时要加载一个java.lang.Integer类,会传递到启动类加载器,而启动类加载器发现在核心java API中有这个类已被加载,就直接返回已经加载了的Integer.class。再例如:要加载一个java.lang.FakeInteger,启动,扩展类加载器的路径下都没有该类,不会加载,会反向委托给系统类加载器加载,但是这样是不行的,因为java.lang是核心API包,需要访问权限,强制加载会报如下异常:java.lang.SecurityException: Prohibited package name: java.lang 类加载器常见类:通过上面的报错堆栈,可以看出几个ClassLoader相关类的关系如下: ①ClassLoader抽象类,定义了几个基本的类加载相关方法。②SecureClassLoader扩展了ClassLoader,新增了几个与使用相关的代码源和权限定义类验证。③URLClassLoader实现了ClassLoader没实现的几个方法,如findClass 等,有一个相关类URLClassPath负责获取Class字节码流,一般自定义类加载器继承URLClassLoader即可。这两个类的构造方法都有一个必填参数URL,该参数是一个路径,可能是文件或jar包,然后根据不同路径创建FileLoader或JarLoader或默认的Loader去加载相应路径下的class文件。 类加载器常用方法:①loadClass ②findClass③defineClass将byte字节流解析成JVM能够识别的Class对象,一般defineClass()方法通常与findClass()方法一起使用,一般在自定义类加载器时会覆盖ClassLoader类的findClass方法并编写加载规则,取的要加载类的字节码后转换成流,然后调用defineClass方法成成类的Class对象。④resolveClass(Class c)对Class对象进行解析。 ExtClassLoader和AppClassLoader他们都继承自URLClassLoader,是sun.misc.Launcher的静态内部类。类结构如下: 类加载器关系:①启动类加载器:由C++实现,没有父加载器②扩展类加载器(ExtClassLoader)由Java实现,父加载器为null③系统类加载器(AppClassLoader),父加载器为ExtClassLoader④自定义类加载器,父加载器为AppClassLoader 我们实现的自定义类加载器的父加载器都是AppClassLoader。Launcher类的源码如下: 如上:Launcher初始化时会创建ExtClassLoader,然后把ExtClassLoader作为parent入参构造AppClassLoader,然后将AppClassLoader默认设置为线程上下文加载器。而ExtClassLoader的parent为null,如下代码: 类与类加载器JVM中两个class对象是否是统一个类对象的必要条件为:①类的完整类名必须一致,包括包名②加载这个类的ClassLoader必须相同 实现自己的ClassLoaderpublic class MyClassLoader extends ClassLoader{ private String classPath; public MyClassLoader(){ super(); } public MyClassLoader(String classPath){ this.classPath = classPath; } public static void main(String[] args) { try { String path = "F:\\Learn\\Java\\workspace\\spring\\MyClassLoader\\bin"; MyClassLoader mcl1 = new MyClassLoader(path); // 自定义类加载器实例1 Class clazz1 = mcl1.findClass("com.classloader.MyClassLoader"); // 自定义类加载器加载自身类 MyClassLoader mcl2 = new MyClassLoader(path); // 自定义类加载器实例2 Class clazz2 = mcl2.findClass("com.classloader.MyClassLoader"); // 自定义类加载器加载自身类 Object inst = clazz1.newInstance(); //new一个自身类的实例 System.out.println(inst.toString()); System.out.println(mcl1.getClass().hashCode());//系统默认加载器加载的class类的实例是一样的 System.out.println(mcl2.getClass().hashCode());//系统默认加载器加载的class类的实例是一样的 System.out.println(clazz1.hashCode());//自定义加载器两次加载的class实例是不一样的(使用findClass) System.out.println(clazz2.hashCode());//自定义加载器两次加载的class实例是不一样的(使用findClass) System.out.println(mcl1.loadClass("com.classloader.MyClassLoader").hashCode());//loadClass和上面findClass的class对象实例是一样,因为loadClass时会先findClass,检查类是否已经被加载 System.out.println(mcl2.loadClass("com.classloader.MyClassLoader").hashCode());//loadClass和上面findClass的class对象实例是一样,因为loadClass时会先findClass,检查类是否已经被加载 System.out.println(mcl1.getSystemClassLoader());//系统类加载器是AppClassLoader System.out.println(mcl1.getParent());//自定义类加载器的父加载器是AppClassLoader System.out.println(mcl1.getSystemClassLoader().getParent());//系统类加载器的父加载器是ExtClassLoader System.out.println(mcl1.getSystemClassLoader().getParent().getParent());//ExtClassLoader的父加载器是null } catch (Exception e) { e.printStackTrace(); } } @Override public String toString() { return "MyClassLoader"; } public Class<?> findClass(String name) throws ClassNotFoundException { // 获取class文件的byte[]数组 try { byte[] classBytes = findClassBytes(name); if(null == classBytes){ throw new ClassNotFoundException(); } return defineClass(name,classBytes,0,classBytes.length); } catch (FileNotFoundException e) { e.printStackTrace(); } return null; } public byte[] findClassBytes(String className) throws FileNotFoundException { String path = this.classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; InputStream is = null; if(this.classPath.startsWith("http")){ try { /*URL u = new URL(path); is = u.openStream();*/ } catch (Exception e) { e.printStackTrace(); } }else{ is = new FileInputStream(path); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] bs = new byte[1024]; int bytesNum = 0; try { while((bytesNum = is.read(bs)) != -1){ baos.write(bs,0,bytesNum); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } finally{ try { is.close(); baos.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } }

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

Yaml入门笔记

什么是yamlYAML是"YAML Ain't a Markup Language"(YAML不是一种置标语言)的递归缩写,早先YAML的意思其实是:"Yet Another Markup Language"(另外一种置标语言),但为了强调这种语言以数据做为中心,而不是以置标语言为重点,而用返璞词重新命名,YAML的官方定义很简单,即一种人性化的数据格式定义语言,其主要功能用途类似于XML或JSON,YAML使用空白字符和分行来分隔数据,且巧妙避开各种封闭符号,如:引号、括号等,以避免这些符号在复杂层次结构中变得难以辨认。YAML的语法与高阶语言类似,可以很简单地表述序列(java中的list)、杂凑表(java中的map)、标量(java中的基本类型等)数据结构,它重点强调可阅读性。 本文内容 image.png YAML的设计目的 容易人类阅读 适合表示程序语言的数据结构 可用于不同程序间交换数据 支持泛型工具 支持串行处理? 丰富的表达能力和可扩展性 易于使用 xml和yaml的代码块: xml代码块: <site> <name>github</name> <url>https://github.com</url> </site> <site> <name>简书</name> <url>https://www.jianshu.com</url> </site> yaml代码块: --- site: name: github url: https://github.com --- site: name: 简书 url: https://www.jianshu.com --- #或者 --- site:{name:github,url:https://github.com} --- site:{name:简书,url:https://jianshu.com} 主要标记 注释-comment 举例: # Comment Example # Profile Of Mary Mary: - name: Mary - age : 19 # age property 文档(document) # documents example --- site: name: github url: https://github.com --- site: name: 简书 url: https://www.jianshu.com 数据结构 YAML的设计者认为在配置文件中所要表达的数据内容有三种类型:标量(Scalar,如字符串和整数等)、序列(Sequence,如数组)和Mapping(类似hash的key/value pair)。 sequence.png mapping.png 例子:我们用YAML来描述一本书: # 《单元测试之道-c#版》描述 --- # begin of document 书名 : '单元测试之道-C#版' 出版社: '电子工业出版社' 原作者: ['Andrew Hunt', 'David Thomas'] 译者 : - 陈伟柱 - 陶文 前二章节 : - 第一章: 序言 - 第二章: 你的首个单元测试计划 #end document 注意:YAML推荐使用空格作为缩进,避免了在不同编辑器中对tab的表示形式不同而可能产生误解。 YAML 与 XML 优势: YAML的可读性好 YAML和脚本语言的交互性好 YAML使用实现语言的数据类型 YAML有一个一致的信息模型 YAML易于实现 上面5条是XML不足的地方,同时,YAML也具有XML的下列优点: YAML可以基于流来处理 YAML表达能力强,扩展性好 YAML类似于XML的数据描述语言,语法比XML简单很多,YAML试图用一种比XML更敏捷的方式,来完成XML所完成的任务。 YAML 与 JSON JSON的语法其实是YAML的子集,大部分的JSON文件都可以被YAML的剖析器剖析。虽然大部分的数据分层形式也可以使用类似JSON的格式,不过YAML并不建议这样使用,除非这样编写能让文件可读性增加,更重要的是,YAML的许多扩展在JSON是找不到的,如:进阶资料形态、关系锚点、字串不需要引号、映射资料形态会储存键值的顺序等。 YAML用途 脚本语言 由于实现简单,解析成本很低,YAML特别适合在脚本语言中使用。列一下现有的语言实现:Ruby,Java,Perl,Python,PHP,OCaml,JavaScript,除了Java,其他都是脚本语言。 序列化 YAML比较适合做序列化。因为它是宿主语言数据类型直转的。 配置文件 YAML做配置文件也不错。写YAML要比写XML快得多(无需关注标签或引号),并且比ini文档功能更强。 调试 由于其很强的阅读性,用于调试过程中dump出信息供分析也是一种比较方便的做法。 YAML缺陷与不足 YAML没有自己的数据类型的定义,而是使用实现语言的数据类型。一个YAML文件,在不同语言中解析后得到的数据类型可能会不同,由于其兼容性问题,不同语言间的数据流转不建议使用YAML。 YAML语法与范例 YAML使用可打印的Unicode字符,可使用UTF-8或UTF-16 使用空白字符(不能使用<kbd>Tab</kbd>)分层,同层元素左侧对齐 单行注解由井字号(<kbd> #</kbd> )开始,可以出现在行中任何位置 每个清单成员以单行表示,并用短杠+空白(<kbd>- </kbd>)起始 每个杂凑表的成员用冒号+空白(<kbd>: </kbd>)分开键和值 杂凑表的键值可以用问号 (<kbd>?</kbd>)起始,表示多个词汇组成的键值 字串一般不使用引号,但必要的时候可以用引号框住 使用双引号表示字串时,可用倒斜线(<kbd></kbd>)进行特殊字符转义 区块的字串用缩排和修饰词(非必要)来和其他资料分隔,有新行保留(使用符号<kbd>|</kbd>)或新行折叠(使用符号<kbd>></kbd>)两种方式 在单一档案中,可用连续三个连字号(<kbd>---</kbd>)区分多个档案 可选择性的连续三个点号(<kbd>...</kbd>)用来表示档案结尾(在流式传输时非常有用,不需要关闭流即可知道到达结尾处) 重复的内容可使从参考标记星号 (<kbd>*</kbd>)复制到锚点标记(<kbd>&</kbd>) 指定格式可以使用两个惊叹号 ( !! ),后面接上名称 receipt: Oz-Ware Purchase Invoice date: 2007-08-06 customer: given: Dorothy family: Gale items: - part_no: A4786 descrip: Water Bucket (Filled) price: 1.47 quantity: 4 - part_no: E1628 descrip: High Heeled "Ruby" Slippers price: 100.27 quantity: 1 bill-to: &id001 street: | 123 Tornado Alley Suite 16 city: East Westville state: KS ship-to: *id001 specialDelivery: > Follow the Yellow Brick Road to the Emerald City. Pay no attention to the man behind the curtain. ... 这个文件的的顶层由七个键值组成:其中一个键值"items",是个两个元素构成的清单,清单中的两个元素同时也是包含了四个键值的杂凑表。 文件中重复的部分处理方式:使用锚点(&)和参考(*)标签将"bill-to"杂凑表的内容复制到"ship-to"杂凑表。也可以在文件中加入选择性的空行,以增加可读性。 YAML的JAVA实现 YAML已经有了多种语言不少实现,详见YAML官网。 一般YAML文件扩展名为.yaml,比如John.yaml,其内容为: name: John Smith age: 37 children: - name: Jimmy Smith age: 15 - name: Jenny Smith age: 12 spouse: name: Jane Smith age: 25 由于yaml的超强可读性,我们了解到:John今年37岁,两个孩子Jimmy 和Jenny活泼可爱,妻子Jane年轻美貌,而且年仅25岁,一个幸福的四口之家。 对John.yaml进行java描述,抽象出一个Person类,如下: public class Person { private String name; private int age; private Person sponse; private Person[] children; // setXXX, getXXX方法略. } 现在我们使用java装配一个Jone: Person john = new Person(); john.setAge(37); john.setName("John Smith"); Person sponse = new Person(); sponse.setName("Jane Smith"); sponse.setAge(25); john.setSponse(sponse); Person[] children = {new Person(), new Person()}; children[0].setName("Jimmy Smith"); children[0].setAge(15); children[1].setName("Jenny Smith"); children[1].setAge(12); john.setChildren(children); 使用SnakeYAML实现 项目主页:http://code.google.com/p/snakeyaml/ 使用手册:https://code.google.com/p/snakeyaml/wiki/Documentation SnakeYAML是一个标准的YAML的java实现,它有以下特点: 完全支持YAML 1.1,可以跑通规范中的所有示例 支持YAML的所有类型 支持UTF-8/UTF-16的输入和输出 提供了本地java对象的序列化和反序列化的高层API 提供相对合理的错误提示信息 使用SnakeYAML将john dump出来,如果有引用相同对象,则dump出到yaml文件会自动使用<kbd>&</kbd>和<kbd></kbd>进行锚点和引用*: DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(options); //Yaml yaml = new Yaml(); String dump = yaml.dump(john); System.out.println(dump); 内容如下: !!Person age: 37 children: - age: 15 children: null name: Jimmy Smith sponse: null - age: 12 children: null name: Jenny Smith sponse: null name: John Smith sponse: age: 25 children: null name: Jane Smith sponse: null 现在用SnakeYAML把yaml load进来,如果yaml文件中使用了<kbd>&</kbd>和<kbd></kbd>,则会自动对load出来的对象赋相同的值*: Yaml yaml = new Yaml(); Object load = yaml.load(new FileInputStream(new File("jhon.yaml"))); System.out.println(load.getClass()); System.out.println(yaml.dump(load)); 或 Yaml yaml = new Yaml(options); Person person = yaml.loadAs(inputStream, Person.class); System.out.println(person.getSponse().getChildren().length); 如果一个yaml文件中有多个文档,由<kbd>---</kbd>分割,解析如下: Yaml yaml = new Yaml(); int counter = 0; for (Object data : yaml.loadAll(input)) { System.out.println(data); counter++; } 保存一个Map对象: Map<String, Object> data = new HashMap<String, Object>(); data.put("name", "Silenthand Olleander"); data.put("race", "Human"); data.put("traits", new String[] { "ONE_HAND", "ONE_EYE" }); Yaml yaml = new Yaml(); String output = yaml.dump(data); System.out.println(output); // or StringWriter writer = new StringWriter(); yaml.dump(data, writer); System.out.println(writer.toString()); 将多个文档dump出到同一个yaml文件中去: List<Integer> docs = new LinkedList<Integer>(); for (int i = 1; i < 4; i++) { docs.add(i); } DumperOptions options = new DumperOptions(); //options.setCanonical(true); options.explicitStart(true); Yaml yaml = new Yaml(options); System.out.println(yaml.dump(docs)); System.out.println(yaml.dumpAll(docs.iterator())); --- [1, 2, 3] --- 1 --- 2 --- 3 YAML JAVA !null null !!bool Boolean !!int Integer, Long, BigInteger !!float Double !!binary String !!timestamp java.util.Date, java.sql.Date, java.sql.Timestamp !!omap, !!pairs List of Object[] !!set Set !!str String !!seq List !!map Map YAML与java类型对照表: YAML JAVA !null null !!bool Boolean !!int Integer, Long, BigInteger !!float Double !!binary String !!timestamp java.util.Date, java.sql.Date, java.sql.Timestamp !!omap, !!pairs List of Object[] !!set Set !!str String !!seq List !!map Map 集合的默认实现是: List: ArrayList Map: LinkedHashMap 使用JYaml实现 JYaml(最新版本是2007年的,可以考虑放弃了),使用JYaml把Jone “Dump” 出来: File dumpfile = new File("John_dump.yaml"); Yaml.dump(john, dumpfile); 下面我们看看John_dump.yaml是什么样子: --- !yaml.test.internal.Person age: 37 children: !yaml.test.internal.Person[] - !yaml.test.internal.Person age: 15 name: Jimmy Smith - !yaml.test.internal.Person age: 12 name: Jenny Smith name: John Smith sponse: !yaml.test.internal.Person age: 25 name: Jane Smith 其中!yaml.test.internal.Person是一些类型的信息。load的时候需要用。 现在用JYaml把Jone_dump.yaml load进来: Person john2 = (Person) Yaml.loadType(dumpfile, Person.class); 还可以用下面的代码dump出没有类型信息的John.yaml: Yaml.dump(john,dumpfile, true); 我们再来看看JYaml对流处理的支持,为简便起见,我们只是把同一个john写10次: YamlEncoder enc = new YamlEncoder(new FileOutputStream(dumpfile)); for(int i=0; i<10; i++){ john.setAge(37+i); enc.writeObject(john); enc.flush(); } enc.close(); 下面再把这十个对象一个一个读出来(注意while循环退出的方式): YamlDecoder dec = new YamlDecoder(new FileInputStream(dumpfile)); int age = 37; while(true){ try{ john = (Person) dec.readObject(); assertEquals(age, john.getAge()); age++; }catch(EOFException eofe){ break; } } 参考文档: YAML Specification YAML 数据类型说明 http://blog.csdn.net/conquer0715/article/details/42108061 http://www.cnblogs.com/chwkai/archive/2009/03/01/249924.html

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

Python学习笔记

一:中文编码 1:指定编码 python-------》#!/usr/bin/python shell----------》#!/bin/bash 2:中文识别 # -*- coding: UTF-8 -*- 或 #coding=utf-8 3:多行语句 我们使用斜杠(/)将一行语句分成多行 如果是{},(),[]则直接可以进行换行 4.转义 \n表示换行、\t表示制表符、\\表示\、 如果有很多需要转义,则可使用r' ' ''' '''也可以表示换行 print('''line1 line2 line3''') 5:输出 print x,y print默认是换行的,如果再print末尾加个逗号“,”就回不换行 6.输入 shell:read -p "Please enter your age: " age python2:a = raw_input("请输入你的名字") python3:a = input("请输入你的名字") 二:变量和简单类型 python中数据类型分为5种:数字,字符串、列表、元组、字典 变量命名规则: python shell 文件名/模块名 小写,单词用“_”分割 ----->ad_stats.py 包名 小写,单词用“_”分割 类名 单词首字母大写----->ConfigUtil 全局变量名 大写字母,单词之间用_分割 ------->COLOR_WRITE 普通变量 小写字母,单词之间用_分割 ------->this_is_a_var 实例变量 以_开头,其他和普通变量一样 ----->_instance_var 普通函数 和普通变量一样-------------------->get_name() 2.1数字 整数:a=1 浮点数:a=1.234 2.2字符串 创:var1 = 'Hello World!' 查:print "var1[0]: ", var1[0] print "var2[1:5]: ", var2[1:5] 改:print "更新字符串 :- ", var1[:6] + 'Runoob!' rd()函数获取字符的整数表示,chr()函数把编码转换为对应的字符 占位符 'Hi, %s, you have $%d.' % ('Michael', 1000000) 如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串 另外一种格式化字符串的方法是format(),他会将传入的参数依次替换{1},{2},{3}......,... 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125) 2.3布尔值 布尔值只有True、False两种值 2.4空值 2.5常量 10/3=3.3333 9/3=3.0 10//3=3 10%3=1 2.6除法 余数: >>> divmod(10,3) (3, 1) 四舍五入: >>> round(10/3,4) 3.0 >>> round(10.0/3,4) 3.3333 2.7数学函数与运算优先级 1.绝对值 abs(-10)=10 2.四舍五入 round(1.234)=1 三:高级特性 1.切片 L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素 如果第一个索引是0,还可以省略-------》L[:3] 前10个数,每两个取一个----------》L[:10:2] 所有数,每5个取一个--------------》L[::5] 什么都不写,只写[:]就可以原样复制一个list-----------》L[:] 2.迭代 3.列表生成器 [x * x for x in range(1, 11)]--------》[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 四:列表(List) list是一种有序的集合,可以随时添加和删除其中的元素。 list1 = ['Michael', 'Bob', 'Tracy'] 增:list1.append('adm')、 list1.insert(1,'jack') #插入元素到指定的位置 删:list1.pop(1) #不填则删除末尾元素 del list1[2] 查:list1[-1] 改:list1[1] ='Sarah',dellist1[2]; 附加:s = ['python','java', ['asp','php'],'scheme'],s[2][1] 五:元组 Python的元组与列表类似,不同之处在于元组的元素不能修改。 tup1 = ('physics', 'chemistry', 1997, 2000); 查:tup1[2] 较:cmp(tuple1, tuple2) 长:len(tup1) 六:字典 字典是另一种可变容器模型,且可存储任意类型对象。 d = {key1 : value1, key2 : value2 } 增: 删:del d['key1']; # 删除键是'key1'的条目 d.pop(key1) #删除对应的键值对 d.clear(); # 清空词典所有条目 del d; # 删除词典 查:d['key1'] d.get(key1)---------->value1 d..has_key(key1)-------->返回true或者false d.keys()-------------->返回所以键['key2','key1'] dict.values()---------->返回所以值[value1,value2] 改:d['key1']=value3 d.update(dict2)----------->将dict2添加到d后面 判:d.has_key(key1)---------->存在则返回true,否则为false 较:cmp(dict1, dict2) 长:len(dict) 七:运算符 python中运算符分为7种:1.算术运算符 2.比较(关系)运算符 3.赋值运算符 4.逻辑运算符 5.位运算符 6.成员运算符 7.身份运算符 1.算术运算符:+、-、*、%(求余)、**(次幂a**b)、//(取整) 2.比较运算符:==(比较是否相等)、!=、<>(比较两个数是否不相等)、>=、<= 3.赋值运算符:=、+=、-=、*=、/=(除法赋值运算符)、%=(取模赋值运算符)、**=(幂赋值运算符)、//=(取整运算符) 4.位运算符:&、|、^、 5.and(与)、or(或)、not 6.in(在)、not in(不在) 7.is(是)、is not(不是) 优先级 八:语句 python分为7种:条件语句,循环语句(while循环,for循环),break语句,continue循环语句,pass循环 python循环语句不用括号()括起来 1:条件语句 num = 5 if num == 3: # 判断num的值 print 'boss' elif num == 2:print 'user' elif num == 1:print 'worker' elif num < 0: # 值小于零时输出 print 'error' else:print 'roadman' # 条件均不成立时输出 2:循环语句 while...else循环 count = 0 while count < 5: print count, " is less than 5" count = count + 1 else: print count, " is not less than 5" for循环 for num in range(10,20): # 迭代 10 到 20 之间的数字 for i in range(2,num): # 根据因子迭代 if num%i == 0: # 确定第一个因子 j=num/i # 计算第二个因子 print '%d 等于 %d * %d' % (num,i,j) break # 跳出当前循环 else: # 循环的 else 部分 print num, '是一个质数' 九:函数 9.1函数规则 1:函数代码块以def关键字开头,后接函数标识符名称和圆括号() 2:任何传入参数和变量必须放在圆括号中间,圆括号中可以用于定义参数 3:函数第一行语句可以放文档字符串,用于存放函数说明 4:函数内容以冒号起始,并且缩进 #python函数 def printinfo( name, age ): "打印任何传入的字符串" print "Name: ", name; print "Age ", age; return; #调用printinfo函数 printinfo( age=50, name="miki" ); #shell函数 function RintedConfig(){ } 9.2函数参数 1.必备参数、2.关键字参数、3.默认参数、4.不定长参数 1:必备参数 必备参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。 2:关键字参数 使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。 def printinfo( name, age ): printinfo( age=50, name="miki" ); 3:缺省参数 调用函数时,缺省参数的值如果没有传入,则被认为是默认值。 def printinfo( name, age = 35 ): printinfo( name="miki" ); 4:不定长参数 你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数, def functionname([formal_args,] *var_args_tuple ): 加了星号(*)的变量名会存放所有未命名的变量参数 9.3高阶函数 1.map() map()可接受两个参数,一个是函数,一个是Iterable,map将传入的参数作用到序的每个元素,并把结果作为新的Iterable返回。 def f(x): return x*x map(f, [1,2,3,4]) >>[1,4,9,16] 2.return() return接收一个函数和两个参数,reduce把结果继续和序列的下一个元素做累积计算。 def f(x,y): return x+y >>map(f, [1,2,3,4]) >>25 3.filter() 用于过滤序列,filter()将元素作用于每个人元素,并根据返回值是true或者false来决定元素的去留。 def is_odd(n): return n % 2==1 filter(is_odd, [1,2,3,4,5,6]) 4.sorted() sorted()可将元素进行排序, def reversed_cmp(x, y): if x > y: return -1 if x < y: return 1 return 0 >>> sorted([36, 5, 12, 9, 21], reversed_cmp) [36, 21, 12, 9, 5] 9.4返回函数 高阶函数除了可以将函数作为参数,也可以将函数作为结果返回。 def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum 9.5匿名函数 关键字lambda表示匿名函数, 匿名函数有个限制,就是只能使用一个表达式,返回值就是该表达式的结果。 map(lambda x: x*x, [1,2,3,4,5,6]) 使用匿名函数就不用担心名字冲突,同是匿名函数也是一个对象,可以将匿名函数作为参数。 ff = lambda x: x*x print ff(5) 9.6装饰器 有时候为了增强函数的功能,但又不改变函数的定义,这种在代码运行期间动态的增加函数的功能的方法叫装饰器。 def log(text): def decorator(func): def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator @log('execute') def now(): print('2015-3-25') >>> now() execute now(): 2015-3-25 9.7偏函数 十:模块 10.1作用域 有的函数和变量,我们仅仅希望在模块内使用,python中我们通过_前缀来实现。 正常的函数和变量都是被公开的,_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用。 带下划线的函数,说明该函数不能根据 from * import来导入,明显在这个模块中你导入的还只是包,然后通过了这个导入的包去调用他内部存在的函数。 十一:面向对象编程 11.1类和实例 class Student (Object): class后面接的是类名,类名手写字母必须大写,紧接的是object,表示该类所继承的类,如果没有,则继承自object类。 class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print '%s: %s' % (self.name, self.score) 11.2访问限制 如果要让内部属性,不被外部访问,可以将属性的名称前面加两个下划线,python中变量以__开头的则为私有变量(private),只有内部可以访问,外部不能被访问。 外部要获取和改变,只能通过get和set方法来进行。 class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print '%s: %s' % (self.__name, self.__score) #外部直接获取 >>> bart = Student('Bart Simpson', 98) >>> bart.__name Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name'#必须使用getname和setname方法 class Student(object): ... def get_name(self): return self.__name def get_score(self): return self.__score 11.3基础多态 继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写; 有了继承,才能有多态。在调用类实例方法的时候,尽量把变量视作父类类型,这样,所有子类类型都可以正常被接收; 11.4获取对象信息 1.type() type可以判断一个变量、函数,类的类型。 2.isinstance() isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。 3.dir() 如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list。 11.5__slots__ 如果想对向前类进行限制只添加某几个属性,可以使用__slots__来进行限制。 __slots__只对当前类起作用,对继承的类不起作用。 class Student(object): __slots__=('name','age') s = Student() s.name = 'yaohong' s.age = 90 s.socre = 99 》》 s.socre = 99 AttributeError: 'Student' object has no attribute 'socre' 11.6@property Python内置的@property装饰器就是负责把一个方法变成属性调用的。 class Student(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2014 - self._birth 11.7多继承 一个子类同时获得多个父类的所有功能。 由于Python允许使用多重继承,因此,Mixin就是一种常见的设计。 只允许单一继承的语言(如Java)不能使用Mixin的设计。 11.8定制类 __str__ class Student(object): def __init__(self, name): self.name = name def __str__(self): return self.name def __repr__(self): return self.name print Student('YAOHONG') print Student('YAOHONG') 11.9使用元类 十二:文件I/O 10.1打印到屏幕 print “”itnihao” 10.2读取输入 1.raw_input str = raw_input("请输入:"); print "你输入的内容是: ", str 2.input input可以接收一个函数表达式 str = input("请输入:"); print "你输入的内容是: ", str 3.open() file object = open(file_name [, access_mode][, buffering]) file_name:file_name变量是一个包含了你要访问的文件名称的字符串值。 access_mode:access_mode决定了打开文件的模式:只读,写入,追加等。所有可取值见如下的完全列表。这个参数是非强制的,默认文件访问模式为只读(r)。 buffering:如果buffering的值被设为0,就不会有寄存。如果buffering的值取1,访问文件时会寄存行。如果将buffering的值设为大于1的整数,表明了这就是的寄存区的缓冲大小。如果取负值,寄存区的缓冲大小则为系统默认。 4.close() close()方法刷新缓冲区里任何还没写入的信息,并关闭该文件,这之后便不能再进行写入。 5.write() write()方法可将任何字符串写入一个打开的文件 6.read() fo = open("foo.txt", "r+") str = fo.read(10); print "读取的字符串是 : ", str # 关闭打开的文件 fo.close() 7.rename() os.rename( "test1.txt", "test2.txt" ) 8.remove() os.remove("test2.txt") 8.mkdri() os.mkdir("test")在当前目录下创建新的test文件 9.chdir() os.chdir("/home/newdir")改变当前目录为/home/newdir目录 10.getcwd() os.getcwd()获取当前目录 11.rmdir() # 删除”/tmp/test”目录 os.rmdir( "/tmp/test" ) 十三:异常处理 1.捕获异常:try/except try: fh = open("testfile", "w") fh.write("这是一个测试文件,用于测试异常!!") except IOError: print "Error: 没有找到文件或读取文件失败" else: print "内容写入文件成功" fh.close() 2.try/finally try-finally 语句无论是否发生异常都将执行最后的代码。 3.触发异常 使用raise语句自己触发异常 def functionName( level ): if level < 1: raise Exception("Invalid level!", level) # 触发异常后,后面的代码就不会再执行 十四:file file使用open函数来创建 常用函数: file.close() file.flush() file.next() 返回下一行 file.read(size) 从文件读取指定的字节数,如果未给定或为负则读取所有。 file.readline(size) 读取整行 file.seek() 设置文件当前位置 file.tell() 返回文件当前位置 file.truncate() 截取文件,截取文件通过size指定 file.write() 将指定字符串写入文件 file.writelines() 向文件写入一个字符串列表 十五:OS os.access() 检测文件是否可读可写,文件是否存在,是否拥有执行权限 os.chdir(path) 切换到新的目录 os.chmod() 更改文件或者目录权限 os.chown() 更改文件所有者 os.close() 关闭文件 os.path.join('a','b')字符串拼接形成a/b os.system("ls") 执行shell命令,如assert os.system("rm -rf %s" % install_path) == 0, "卸载 失败" os.path.exists(BIN_PATH) 判断某个变量是否存在 os.unlink(path) 方法用于删除文件,如果文件是一个目录则返回一个错误。 os.remove() 删除文件 十六:open open(a.txt).read() 去取a.txt文件 十七:enumerate enumerate(list1) 枚举list,map等获取索引和元素for index, item in enumerate(list1): 十八:string str.strip("@") 去掉以@开头结尾的 str.startswith("#") 判断是否以#开头 十九:Python常见模块 python常见命令如下,点击链接查看详情 1.os 2.os.path 3.shutil 4.sys 5.inspect

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

Java synchronized笔记

1,造成线程安全问题的原因①存在线程间的共享数据(例如堆内存)②存在多个线程同时操作共享数据 2,锁类型互斥锁:(重量级锁)同一时刻有且仅有一个线程操作共享数据,当某个线程正在操作共享数据时,其他线程处于等待状态,必须等到该线程处理完后再进行.偏向锁:java1.6的新锁,针对多个线程竞争较少的场景,如果一个线程获得了锁,那么锁就进入偏向模式,对象头Mark Word里面的锁结构也编程偏向锁结构,这个线程再次请求锁时,无需做任何操作。轻量级锁:依据是大部分的锁,整个同步周期内不存在竞争,适用线程交替执行同步块的场景。自旋锁:依据是大部分线程持有锁的时间不会太长,因此自旋锁假设很快当前线程就会获得锁,所以虚拟机会让想要获取锁的线程执行几个空循环,如果循环完成后得到锁,则顺利进入。否则就会将线程在操作系统层面挂起。锁消除:指虚拟机在编译时消除无用的锁。 3,synchronized关键字作用:①保证同一时刻只有一个线程可以执行某个方法或者代码块②可以保证一个线程操作的共享数据变化能立即被其他线程所见(可替代volatile) 4,synchronized关键字用法:①修饰实例方法:作用于当前实例的方法(不包括静态方法),进入同步代码前要获取当前实例的锁(JVM通过常量池中的方法结构表的acc_synchronized标识区分一个方法是否是同步方法。)public class SynchronizedClass extends Thread{ static int share = 0; //①修饰实例方法 public synchronized void increase() { share++; } //②修饰静态方法 public synchronized static void increase4ClassLock(){ share++; } public void run() { for (int i = 0; i < 1000; i++) { increase(); } } /** * @param args */ public static void main(String[] args) { try { //场景1,t1,t2线程执行需要获得实例sc1的互斥锁 SynchronizedClass sc1 = new SynchronizedClass(); Thread t1 = new Thread(sc1); Thread t2 = new Thread(sc1); t1.start(); t2.start(); // join()方法:当前线程结束之后join()方法才返回,如果不试用join,打印出来的数据可能是0,1000,2000 t1.join(); t2.join(); System.out.println("scene1 share:" + share); //share = 0; //场景2,t3,t4线程执行各自获得实例sc2,sc3的锁,可能同时访问共享的静态资源share出现线程安全问题 SynchronizedClass sc2 = new SynchronizedClass(); SynchronizedClass sc3 = new SynchronizedClass(); Thread t3 = new Thread(sc2); Thread t4 = new Thread(sc3); t3.start(); t4.start(); t3.join(); t4.join(); System.out.println("scene2 share:" + share); } catch (Exception e) { e.printStackTrace(); } } }上面例子中:当前线程的锁是实例对象instance,java的线程同步锁可以是任意对象。注意:如果一个线程正在访问一个实例instance的synchronized方法A时,其他线程不能访问该实例的其他synchronized方法,因为一个对象实例只有一把锁,但其他线程可访问该实例的非synchronized方法。当然,如果其他线程要访问另外一个实例instance1的任何方法都是不受限制的,但是如果他们访问的是共享数据那么就会出问题,例如上面例子场景2中的静态变量share,这时候就需要使用②进行修饰。②修饰静态方法:作用于当前类对象,进入同步代码前要获得当前类(class)对象的锁。注意:两个线程可以同时分别访问static synchronized修饰的方法和non-static synchronized修饰的方法,因为他们一个是类对象的锁,一个是实例对象的锁,但是此时两个方法如果同时操作共享数据,也可能出现线程安全问题。例如上面例子中两个线程分别同时访问:increase4ClassLock()和increase()方法,就会出现线程安全问题。③修饰代码块:指定加锁对象,给指定对象家锁,进入同步代码块前要获取指定对象的锁(可以是类对象,也可以是实例对象),样例如下:static SynchronizedClass inst1 = new SynchronizedClass();public void increase2MethodBlock1(){ //使用实例锁,获取当前对象实例的锁,inst1也可用this(synchronized (inst1)) synchronized (inst1) { share++; } }public void increase2MethodBlock2(){ //使用类对象锁,获取当前class对象的锁 synchronized (SynchronizedClass.class) { share++; } } 5,sychronized实现原理jvm中synchronized的同步是通过管程(Monitor)实现的,在java的堆内存中,一个对象实例信息是按照如下方式存储的: 如上如所示结构,java对象头里面有锁信息,锁信息里有一个指向monitor(管程)对象的指针,管程对象在java虚拟机中是使用OjbectMonitor类实现的,类结构大致如下:ObjectMonitor(){_count = 0; //计数器_WaitSet = null; _EntryList = null;_owner = null; //指向拥有ObjectMonitor对象的线程……}说明:①多个线程进入同步代码时,会先进入_ EntryList队列,获取到monitor对象后修改owner指向为当前对象线程,计数器加1②线程调用wait()方法进入_WaitSet队列,owner置为空,count减1③当前线程执行完毕也会重置owner为空,复位变量 6,synchronized需要注意的点①可重入性一个线程在synchronized方法体内部可以调用另外一个synchronized方法,因为是同一个线程请求同一个锁。子类继承父类时,子类可通过重入锁调用父类的synchronized方法。②在线程sleep时调用interrupt方法会打断阻塞并抛出异常(对阻塞线程进行中断操作): ③处于非阻塞状态的线程需要我们手动进行中断检测并结束程序: 如上②和③两种情况(阻塞/和线程运行时中断中断的操作方法是不一样的[前者要捕捉异常,后者要手动判断中断状态并做中断处理]),可用下面代码兼顾两种场景: ④线程状态转换1)等待阻塞:等待阻塞(使用wait,notify/notifyAll方法进入和退出)(wait是Object类的方法)2)锁定阻塞:synchronized同步锁的获得和释放3)其他阻塞:sleep,join,IO操作等(sleep是Thread类的静态方法)4)就绪状态获得时间片就进入运行态,时间片用完或yield操作回到就绪状态5)sleep和wait的区别:sleep方法是Thread类的今天方法而wait是Object类的方法;sleep方法不会释放对象锁,sleep时其他线程任然不能访问同步块,而wait方法会释放对象锁。⑤线程中断操作对正在等待获取锁的synchronized方法或者代码块是不起作用的:一个正在等待锁的线程要么获得锁继续执行,要么继续等待。 ⑥wait(); notify(); notifyAll();方法必须放在synchronized中,否则会抛出IllegalMonitorStateException异常,因为:调用这几个方法必须获取管程(ObjectMonitor)对象,该对象放在对象头中,而synchronized可以获取到该对象。

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

Java学习笔记

代码点与代码单元 codePoint codeUnit 这里当字符串中有辅助字符时,代码点是两个代码单元,需要以下遍历: int cp = sentence.codePointAt(i); if(Character.isSupplementaryCodePoint(cp)){ i += 2; }else{ i++; } String类对象->不可变字符串 不能修改字符串中的字符:编译器可以将字符串共享,以此带来高效率。 当将一个字符串与一个非字符串的值进行拼接时,后者将被转换成字符串。 任何一个对象都可以转换成字符串。 检测两字符串是否相等,而不区分大小写,可用equalsIgnoreCase()方法 Java中允许一个数组长度为0,不同于null。 Java中的多维数组实际上是数组的数组。 一个对象变量实际上是一个对象指针。 用clone()方法获得对象的完整拷贝。 不能编写返回引用可变对象的访问器方法。 一个方法可以访问所属类的所有对象的私有数据。 Sytem类中有一个setOut方法是一个本地方法,不是用Java语言实现的,可以绕过Java的存取控制机制(out是final的)。 每个类都可以有一个main方法,可用来进行单元测试。 一个方法不能修改一个基本数据; 一个方法可以改变一个对象参数的状态; 一个方法不能让对象参数引用一个新的对象。 方法签名:方法名、参数类型。 初始化块机制 首先运行初始化快,然后才运行构造器的主体部分 静态域初始化块: static{ } 只能使用*导入一个包,如: import java.util.*; 静态导入:静态方法、静态域 import static java.lang.System.*; 如果没有指定public或private,那么这个部分(类、方法或者变量)可被同一个包中的所有方法访问 包作用域:默认情况下是包可见的 包密封:(package sealing) jar文件密封包 /*…/->文档注释 javadoc 标记+自由格式,其中标记由”@”开始 类设计技巧: 一定要将数据设为私有; 一定要对数据初始化; 不要在类中使用过多的基本数据类型; 不是所有的域都需要独立的域访问器和域更改器; 使用标准格式进行类的定义; 将职责过多的类进行分解; 类名和方法名要能够体现它们的职责。 Java中的所有继承都是公有继承:超类和子类 覆盖(Override)子类使用super调用超类的方法以及构造器 一个对象变量可以引用多种实际类型的现象叫做多态(Polymorphism) 运行能够自动地选择调用的适当方法的现象称为动态绑定 虚拟机预先为各个类创建了一个方法表 在覆盖一个方法时,子类方法不能低于超类方法的可见性 阻止继承的两种方法:final类的方法自动成为final方法(不包括域),final方法 内联的概念:如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理。 强制类型转换: 只能在继承层次内进行类型转换; 在将超类转换成子类的前应使用instanceof进行检查 抽象类可以包含非抽象方法,也可以不包含抽象方法。抽象方法充当着占位的角色,具体实现在子类中。 抽象类不能被实例化 只有子类实现了所有抽象方法,子类才不是抽象的了 子类不能访问超类的私有域 hashcode: 由对象导出的一个整型值 每个对象都有一个默认的散列码,其值为对象的存储地址 两个相等的对象要求必须返回一个相等的散列码 自动打包规范要求boolean byte char=0:x; 四个基本的抽象流类:InputStream OutputStream Reader Writer(这些对象由其他方法返回) 注意流过滤器的运用 对象序列化:ObjectOutputstream ObjectInputStream writeObject() readObject() 类必须实现Serializable 接口 Java使用SHA编码的前8字节作为类的指纹 在序列化的过程中内存地址会被替换为序列号(唯一) 流类关注的是文件内容,而File类关注的是文件在磁盘上的存储 原文出处:后端技术杂谈 原文链接 转载请与作者联系,同时请务必标明文章原始出处和原文链接及本声明。

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

seaborn学习笔记

数据集的可视化展示对于分析数据集内在的规律有着很大帮助,因此我想着先学习一点matplotlib和seaborn的画图操作。 import numpy as np import pandas as pd import seaborn as sns from scipy import stats,integrate from sklearn.datasets import load_boston import matplotlib.pyplot as plt %matplotlib inline 加载数据集: Data = load_boston() X=pd.DataFrame(Data["data"]) y=pd.DataFrame(Data["target"]) Data=pd.DataFrame(np.hstack((X,y))) data=pd.read_csv("Desktop/creditcard.csv") iris_data = sns.load_dataset('iris') Data.columns=['a','b','c','d','e','f','g','h','i','j','k','l','m','n']#从数据集提取出的数据默认列名是自然数字,但是这样下面的lmplot函数会报错,所以手动修改了列名 print(list(Data))#打印列名 打印列名: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n'] 中间取了几列做线性拟合: sns.lmplot(x='a',y='n',data=Data) sns.lmplot(x='c',y='n',data=Data) sns.lmplot(x='d',y='n',data=Data) sns.lmplot(x='sepal_length', y='sepal_width', hue='species', data=iris_data 可以看到,seaborn的lmpolt函数可以直接对数据线性回归拟合,还给出了置信区间。 lmplot中,参数x,y都是列名,data就是使用的数据集,hue是分类,按照鸢尾花的species类别进行分类 sns.distplot(data["V1"],bins=50,kde=False,fit=stats.gamma)#V1V2是数据集的列名 plt.show() sns.distplot(data["V2"],bins=20,kde=True,fit=stats.gamma)#bins为横轴等分数,kde是核分布 plt.show()#如果像lmplot一样不分开打印会导致图表重叠在一起``` 可视化结果如下: ![image](https://yqfile.alicdn.com/a0b90cef40f04e60263e7375698a9bb59ff935a4.png) ![image](https://yqfile.alicdn.com/a7c775b96a529532fb9c3d4ef53e39ee491cb90d.png) displot函数可以给出一个特征的分布情况,其中的fit=stats.gamma调用scipy中的计算高斯分布的函数,就是图上的黑色密度分布线。 类似的函数还有regplot。 sns.JointGrid(data=Data, x='e', y='n').plot(sns.regplot, sns.distplot)plt.show() 需要对图表设置样式,即加上.plot(sns.regplot, sns.distplot),否则不显示 这里默认选择了 seaborn 带有的回归拟合散点图以及单变量分布图 可视化结果为: ![image](https://yqfile.alicdn.com/5bd21401318668cf3ddda787eb5e909d7fb72d5b.png) JointGrid显示了回归结果的同时也给出边缘分布 多个特征相互比较,可以用PairGrid sns.PairGrid(data=iris_data,hue='species').map(plt.scatter) 同样也需要设置图表样式 结果的一部分: ![image](https://yqfile.alicdn.com/631f19541cdb4670ac74095096910c2100c968d7.png) 更多的还有像色彩矩阵,小提琴状图,箱形图等,不同的图适用不同的数据集。可视化的最终目的还是直观地了解数据集的嗯不状况,以方便做出最好的处理。

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

nignx笔记1

上图是单版的架构,理论一个tomcat并发就200到300,经过优化后的最多500,这很明显容量低,而且出现单点故障后应用服务就不可以访问了,比如tomcat,这样明显对于多并发是不行的。 那么如果我们从域名提供商哪里一个域名映射多个IP呢?下图,每一个IP对应一个tomcat,通过IP轮询来到底要访问哪个Tomcat,这样行不行呢?我们先看下面的图: 上图所示,这样虽然解决了单点故障,容量也提升了,但是又出现问题了,1.DNS服务器完全暴露在外网,造成不安全。2入口不集中。3.如果其中一个Tomcat挂了,那么用户通过对应的IP进入了那个坏的tomcat,还是不可以访问,这样很明显不行。 那么如果我们把用户的请求入口,集中管理起来,那么我们把所有用户的请求入口集中在nignx下,用nignx的反向代理/负载均衡的典型算法,如轮询或随机将请求分发到不同的Tomcat下,这样不就解决上面的问题了吗?如下图: 那么上图这个架构就没事了吗?明显出现了第一个架构出现的问题,还是出现单点故障了,因为如果nignx挂了,那么所有的应用就不能访问了,这样明显也是不可取的。那么我们就把nignx双机热备份,通过心跳检测,检测负载均衡服务器,如果一个挂了,立马切换虚拟IP到另外一台nignx备份机。如下图: 那么问题又来了,其实只有一台nignx在工作,另外一台只是待命,只有一台挂了,另外一台才工作,这明显利用率低。还有就是请求分发设备有流量并发上限。那么我们如何解决呢?试想,如果我们再到DNS动一下手脚呢?那么我们如果通过双击热备再加上DNS轮询 ,用户输入域名通过浏览器访问DNS服务器,得到一个虚拟IP,通过虚拟IP进入相应的双机热备份,下一个用户来了,就用过轮询,用下一个虚拟IP又进入了另外一个虚拟IP对应的双机热备份,这样不久解决了问题吗?如下图所示这样就解决了流量并发发问题

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

Hadoop 调研笔记

由于从各光伏电站采集的数据量较大,必须解决海量数据的查询、分析的问题。目前主要考虑两种方式:1. Hadoop大数据技术;2. Oracle(数据仓库)+BI; 本文仅介绍hadoop的技术要应用特征。 Hadoop 基本介绍 hadoop是一个平台,是一个适合大数据的分布式存储和计算的平台。什么是分布式存储?这就是后边我们要讲的hadoop核心之一HDFS(Hadoop Distributed File System);什么是分布式计算?这是我们后边要讲的hadoop另外一个重要的核心MapReduce。hadoop的优点一:低成本hadoop本身是运行在普通PC服务器组成的集群中进行大数据的分发及处理工作的,这些服务器集群是可以支持数千个节点的。hadoop优点二:高效性这也是hadoop的核心竞争优势所在,接受到客户的数据请求后,hadoop可以在数据所在的集群节点上并发处理。hadoop优点三:可靠性通过分布式存储,hadoop可以自动存储多份副本,当数据处理请求失败后,会自动重新部署计算任务。hadoop优点四:扩展性hadoop的分布式存储和分布式计算是在集群节点完成的,这也决定了hadoop可以扩展至更多的集群节点。hadoop安装方式|hadoop部署方式hadoop安装方式只有三种:本地安装;伪分布安装;集群安装。 Hadoop 适应的场景 1:超大文件 可以是几百M,几百T这个级别的文件。2:流式数据访问 Hadoop适用于一次写入,多次读取的场景,也就是数据复制进去之后,长时间在这些数据上进行分析。3:商业硬件 也就是说大街上到处都能买到的那种硬件,这样的硬件故障率较高,所以要有很好的容错机制。 Hadoop 不适用的场景 1:低延迟数据访问 Hadoop设计的目的是大吞吐量,所以并没有针对低延迟数据访问做一些优化,如果要求低延迟, 可以看看Hbase。2:大量的小文件 由于NameNode把文件的MetaData存储在内存中,所以大量的小文件会产生大量的MetaData。这样的话百万级别的文件数目还是可行的,再多的话就有问题了。3:多用户写入,任意修改 Hadoop现在还不支持多人写入,任意修改的功能。也就是说每次写入都会添加在文件末尾。 Hadoop 业务场景 1 在大数据背景下,Apache Hadoop已经逐渐成为一种标签性,业界对于这一开源分布式技术的了解也在不断加深。但谁才是Hadoop的最大用户呢?首先想到的当然是它的“发源 地”,像Google这样的大型互联网搜索引擎,以及Yahoo专门的广告分析系统。也许你会认为,Hadoop平台发挥作用的领域是互联网行业,用来改 善分析性能并提高扩展性。其实Hadoop的应用场景远不止这一点,深入挖掘的话你会发现Hadoop能够在许多地方发挥巨大的作用。美国着名科技博客GigaOM的专栏作家Derrick Harris跟踪云计算和Hadoop技术已有多年时间,他也在最近的一篇文章中总结了10个Hadoop的应用场景,下面分享给大家: 在线旅游:目前全球范围内80%的在线旅游网站都是在使用Cloudera公司提供的Hadoop发行版,其中SearchBI网站曾经报道过的Expedia也在其中。 移动数据:Cloudera运营总监称,美国有70%的智能手机数据服务背后都是由Hadoop来支撑的,也就是说,包括数据的存储以及无线运营商的数据处理等,都是在利用Hadoop技术。 电子商务:这一场景应该是非常确定的,eBay就是最大的实践者之一。国内的电商在Hadoop技术上也是储备颇为雄厚的。 能源开采:美国Chevron公司是全美第二大石油公司,他们的IT部门主管介绍了Chevron使用Hadoop的经验,他们利用Hadoop进行数据的收集和处理,其中这些数据是海洋的地震数据,以便于他们找到油矿的位置。 节能:另外一家能源服务商Opower也在使用Hadoop,为消费者提供节约电费的服务,其中对用户电费单进行了预测分析。 基础架构管理:这是一个非常基础的应用场景,用户可以用Hadoop从服务器、交换机以及其他的设备中收集并分析数据。 图像处理:创业公司Skybox Imaging 使用Hadoop来存储并处理图片数据,从卫星中拍摄的高清图像中探测地理变化。 诈骗检测:这个场景用户接触的比较少,一般金融服务或者政府机构会用到。利用Hadoop来存储所有的客户交易数据,包括一些非结构化的数据,能够帮助机构发现客户的异常活动,预防欺诈行为。 IT安全:除企业IT基础机构的管理之外,Hadoop还可以用来处理机器生成数据以便甄别来自恶意软件或者网络中的攻击。 医疗保健:医疗行业也会用到Hadoop,像IBM的Watson就会使用Hadoop集群作为其服务的基础,包括语义分析等高级分析技术等。医疗机构可以利用语义分析为患者提供医护人员,并协助医生更好地为患者进行诊断。 Hadoop 业务场景 2 其实我们要知道大数据的实质特性:针对增量中海量的结构化,非结构化,半结构数据,在这种情况下,如何快速反复计算挖掘出高效益的市场数据?带着这个问题渗透到业务中去分析,就知道hadoop需要应用到什么业务场景了!!!如果关系型数据库都能应付的工作还需要hadoop吗?比如:1.银行的信用卡业务,当你正在刷卡完一笔消费的那一瞬间,假如在你当天消费基础上再消费满某个额度,你就可以免费获得某种令你非常满意的利益等 等,你可能就会心动再去消费,这样就可能提高银行信用卡业务,那么这个消费额度是如何从海量的业务数据中以秒级的速度计算出该客户的消费记录,并及时反馈 这个营销信息到客户手中呢?这时候关系型数据库计算出这个额度或许就需要几分钟甚至更多时间,就需要hadoop了,这就是所谓的“秒级营销”. 针对真正的海量数据,一般不主张多表关联。2. 在淘宝,当你浏览某个商品的时候,它会及时提示出你感兴趣的同类商品的产品信息和实时销售情况,这或许也需要用到hadoop。3. 就是报表用到的年度报告或者年度环比数据报告的时候也会用到hadoop去计算。4.搜索引擎分析的时候应该也会用到。一个网友说过,其实还是看big data能否带来多大的效益!比如银行在躺着都赚钱的情况下,big data不一定是银行的项目. 况且hadoop是新兴技术,银行业对新技术还是相对保守的。 hadoop 主要用于大数据的并行计算,并行计算按计算特征分为:• 数据密集型并行计算:数据量极大,但是计算相对简单的并行处理。如:大规模Web信息搜索;• 计算密集型并行计算:数据量相对不是很大,但是计算较为复杂的并行计算。如:3-D建模与渲染,气象预报,科学计算;• 数据密集与计算密集混合型的并行计算。如:3-D电影的渲染; hadoop比较擅长的是数据密集的并行计算,它主要是对不同的数据做相同的事情,最后再整合。我知道以及曾经实验过的hadoop的例子有:• wordCount (相当于hadoop的HelloWorld的程序);• 文档倒排索引;• PageRank;• K-Means 算法;这些程序都可以从网上找到相应的解决方案。hadoop的是根据Google MapReduce 提出的开源版本。但是它的性能不是很好。 hadoop主要应用于数据量大的离线场景。特征为:1、数据量大。一般真正线上用Hadoop的,集群规模都在上百台到几千台的机器。这种情况下,T级别的数据也是很小的。Coursera上一门课了有句话觉得很不错:Don’t use hadoop, your data isn’t that big.2、离线。Mapreduce框架下,很难处理实时计算,作业都以日志分析这样的线下作业为主。另外,集群中一般都会有大量作业等待被调度,保证资源充分利用。3、数据块大。由于HDFS设计的特点,Hadoop适合处理文件块大的文件。大量的小文件使用Hadoop来处理效率会很低。举个例子,百度每天都会有用户对侧边栏广告进行点击。这些点击都会被记入日志。然后在离线场景下,将大量的日志使用Hadoop进行处理,分析用户习惯等信息。 MapReduce 的经典案例 MapReduce的一个经典实例是Hadoop。用于处理大型分布式数据库。由于Hadoop关联到云以及云部署,大多数人忽略了一点,Hadoop有些属性不适合一般企业的需求,特别是移动应用程序。下面是其中的一些特点: Hadoop的最大价值在于数据库,而Hadoop所用的数据库是移动应用程序所用数据库的10到1000倍。对于许多人来说,使用Hadoop就是杀鸡用牛刀。 Hadoop有显著的设置和处理开销。 Hadoop工作可能会需要几分钟的时间,即使相关数据量不是很大。 Hadoop在支持具有多维上下文数据结构方面不是很擅长。例如,一个定义给定地理变量值的记录,然后使用垂直连接,来连续定义一个比hadoop使用的键值对定义更复杂的数据结构关系。 Hadoop必须使用迭代方法处理的问题方面用处不大,尤其是几个连续有依赖性步骤的问题。MapReduce (EMR),这是一项Hadoop服务。Hadoop旨在同期文件系统工作,以HDFS著称。当用户用EMR创建了一个Hadoop集群,他们可以从AWS S3(亚马逊简单储存服务)或者一些其他的数据存储复制数据到集群上的HDFS,或者也可以直接从S3访问数据。HDFS使用本地存储,而且通常提供了比从S3恢复更好的性能,但是在运行Hadoop工作之前,也需要时间从S3复制数据到HDFS。如果EMR集群要运行一段时间,且针对多项工作使用相同的数据,可能值得额外的启动时间来从S3复制数据到HDFS。 为什么hadoop不适合处理实时数据 1. 概述 Hadoop已 被公认为大数据分析领域无可争辩的王者,它专注与批处理。这种模型对许多情形(比如:为网页建立索引)已经足够,但还存在其他一些使用模型,它们需要来自 高度动态的来源的实时信息。为了解决这个问题,就得借助Twitter推出得Storm。Storm不处理静态数据,但它处理预计会连续的流数据。考虑到 Twitter用户每天生成1.4亿条推文,那么就很容易看到此技术的巨大用途。 但Storm不只是一个传统的大数据分析系统:它是复杂事件处理(CEP)系统的一个示例。CEP系统通常分类为计算和面向检测,其中每个系统都是通过用户定义的算法在Storm中实现。举例而言,CEP可用于识别事件洪流中有意义的事件,然后实时的处理这些事件。 2. 为什么Hadoop不适合实时计算 这里说的不适合,是一个相对的概念。如果业务对时延要求较低,那么这个 问题就不存在了;但事实上企业中的有些业务要求是对时延有高要求的。下面我 就来说说: 2.1时延 Storm 的网络直传与内存计算,其时延必然比 Hadoop 的 HDFS 传输低得多;当计算模型比较适合流式时,Storm 的流试处理,省去了批处理的收集数据的时 间;因为 Storm 是服务型的作业,也省去了作业调度的时延。所以从时延的角 度来看,Storm 要快于 Hadoop,因而 Storm 更适合做实时流水数据处理。下面用一个业务场景来描述这个时延问题。 2.1.1业务场景 几千个日志生产方产生日志文件,需要对这些日志文件进行一些 ETL 操作存 入数据库。 我分别用 Hadoop 和 Storm 来分析下这个业务场景。假设我们用 Hadoop 来 处理这个业务流程,则需要先存入 HDFS,按每一分钟(达不到秒级别,分钟是最小纬度)切一个文件的粒度来计算。这个粒度已经极端的细了,再小的话 HDFS 上会一堆小文件。接着 Hadoop 开始计算时,一分钟已经过去了,然后再开始 调度任务又花了一分钟,然后作业运行起来,假设集群比较大,几秒钟就计算完 成了,然后写数据库假设也花了很少时间(理想状况下);这样,从数据产生到 最后可以使用已经过去了至少两分多钟。 而我们来看看流式计算则是数据产生时,则有一个程序一直监控日志的产生,产生一行就通过一个传输系统发给流式计算系统,然后流式计算系统直接处理, 处理完之后直接写入数据库,每条数据从产生到写入数据库,在资源充足(集群 较大)时可以在毫秒级别完成。 2.1.2吞吐 在吞吐量方面,Hadoop 却是比 Storm 有优势;由于 Hadoop 是一个批处理计算,相比 Storm 的流式处理计算,Hadoop 的吞吐量高于 Storm。 2.2应用领域 Hadoop 是基于 MapReduce 模型的,处理海量数据的离线分析工具,而Storm是分布式的,实时数据流分析工具,数据是源源不断产生的,比如:Twitter 的Timeline。另外,M/R 模型在实时领域很难有所发挥,它自身的设计特点决定了 数据源必须是静态的。 2.3硬件 Hadoop 是磁盘级计算,进行计算时,数据在磁盘上,需要读写磁盘;Storm是内存级计算,数据直接通过网络导入内存。读写内存比读写磁盘速度快 N 个 数量级。根据行业结论,磁盘访问延迟约为内存访问延迟的 7.5w 倍,所以从这 个方面也可以看出,Storm 从速度上更快。 3.详细分析 在分析之前,我们先看看两种计算框架的模型,首先我们看下MapReduce的模型,以WordCount为例,如下图所示: 阅读过Hadoop源码下的hadoop-mapreduce-project工程中的代码应该对这个流程会熟悉,我这里就不赘述这个流程了。 接着我们在来看下Storm的模型,如下图所示: 然后下面我们就会涉及到2个指标问题:延时和吞吐。 延时:指数据从产生到运算产生结果的时间。与“速度”息息相关。 吞吐:指系统单位时间处理的数据量。 另外,在资源相同的情况下;一般 Storm 的延时要低于 MapReduce,但是 吞吐吞吐也要低于 MapReduce,下面我描述下流计算和批处理计算的流程。 整个数据处理流程来说大致可以分为三个阶段: 1. 数据采集阶段 2. 数据计算(涉及计算中的中间存储) 3. 数据结果展现(反馈) 3.1.1数据采集阶段 目前典型的处理策略:数据的产生系统一般出自 Web 日志和解析 DB 的 Log,流计算数据采集是获取的消息队列(如:Kafka,RabbitMQ)等。批处理系统一 般将数据采集到分布式文件系统(如:HDFS),当然也有使用消息队列的。我们 暂且把消息队列和文件系统称为预处理存储。二者在这个阶段的延时和吞吐上没 太大的区别,接下来从这个预处理存储到数据计算阶段有很大的区别。流计算一 般在实时的读取消息队列进入流计算系统(Storm)的数据进行运算,批处理系 统一般回累计大批数据后,批量导入到计算系统(Hadoop),这里就有了延时的 区别。 3.1.2数据计算阶段 流计算系统(Storm)的延时主要有以下几个方面: · Storm 进程是常驻的,有数据就可以进行实时的处理。MapReduce 数据累 计一批后由作业管理系统启动任务,Jobtracker 计算任务分配,Tasktacker 启动相关的运算进程。 · Storm 每个计算单元之间数据通过网络(ZeroMQ)直接传输。MapReduce Map 任务运算的结果要写入到 HDFS,在 Reduce 任务通过网络拖过去运算。 相对来说多了磁盘读写,比较慢。 · 对于复杂运算,Storm的运算模型直接支持DAG(有向无环图,多个应用程 序存在依赖关系,后一个应用程序的 输入为前一个的输出),MapReduce 需要多个 MR 过程组成,而且有些 Map 操作没有意义。 3.1.3数据展现 流计算一般运算结果直接反馈到最终结果集中(展示页面,数据库,搜索引擎的索引)。而MapReduce 一般需要整个运算结束后将结果批量导入到结果集中。 4.总结 Storm 可以方便的在一个计算机集群中编写与扩展复杂的实时计算,Storm之于实时,就好比 Hadoop 之于批处理。Storm 保证每个消息都会得到处理,而 且速度很快,在一个小集群中,每秒可以处理数以百万计的消息。 Storm 的主要特点如下: · 简单的编程模型。类似于MR降低了并行批处理的复杂行,Storm降低了实时处理的复杂行。 · 可以使用各种编程语言。只要遵守实现Storm的通信协议即可。 · 容错性。Storm会管理工作进程和节点故障。 · 水平扩展。计算是在多个线程,进程和服务器之间并行进行的。 · 可靠的消息处理。Storm保证每个消息至少能得到处理一次完整的处理,使用 MQ 作为其底层消息队列。 · 本地模式。Storm 有一个“本地模式”,可以在处理过程中完全模拟Storm集群。这让你可以快速进行开发和单元测试。 最后总结:Hadoop 的 MR 基于 HDFS,需要切分输入数据,产生中间数据文件,排序,数据压缩,多分复制等,效率地下。而 Storm 基于ZeroMQ 这个高性能的消息通讯库,不能持久化数据。 专家说 雅虎云平台组的副总裁Hari Vasudev解释说,Hadoop在处理大量结构与非结构数据上是“非常有效的”。它适用于在传统数据仓库中对即时查询需求的支持,但不能取代针对有低潜在因素需求的传统商业智能(BI)功能的关系型数据库管理系统(RDBMS)的部署。 Vasudev曾指出Hadoop的工作量很大,注意到网络是最难确定的变量。“关键是要购买足够的网络容量,让所有节点在集群以合理的速度和合理的成本上互相交流,”他说。 Hoosenally还指出,企业在利用Hadoop上面临着“急剧上升的学习曲线”,而这将使与遗留的IT系统的整合“较为困难”。 更有甚者,当前在如何启动和确保有效使用开源框架上缺乏文档和信息,他补充说。 他又指出,招聘具有该领域专业知识的合适人才是另一种挑战。 互联网数据中心(IDC)亚太区联合副总裁Philip Carter在早先的一份报告中强调了数据人才的缺乏。他举例说,在亚洲的公司对大数据以及IT部门应该如何接近它的理解上层次较低。即使在这个领域有更多知识的IT领导人也不能确定管理信息采集所需技能的种类,该分析师说。 C# Hadoop 一、安装环境 1,前期准备:官网下载“NuGet Package Manager”,按自己已有的VS环境下载对应版本; 2,利用NuGet下载Hadoop For .NET SDK,地址“http://hadoopsdk.codeplex.com/” 3,安装。 4,通过HDInsight,安装Windows Azure,目前是预览版本。 5,参照网址“http://blogs.msdn.com/b/data_otaku/archive/2013/08/14/hadoop-for-net-developers.aspx”系统学习API。 本文转自钢钢博客园博客,原文链接:http://www.cnblogs.com/xugang/p/5580525.html,如需转载请自行联系原作者

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

桥牌笔记:阻塞

7NT。 梅花有10张,看来完成很轻松,但小心明手没有进张,会发生阻塞。 正确的打法是飞西家的梅花Q,不管西家拿1、2、3张梅花,只要带Q,都成功。东家拿2张梅花带Q,一样会阻塞完不成。 唯一失败的情况是东家拿单张梅花Q ----==== Email: slofslb (GTD) qq.com 请将(GTD)换成@ ====---- 版权声明:自由转载-非商用-非衍生-保持署名( 创意共享3.0许可证) 作者: 申龙斌的程序人生 ---- 魔方、 桥牌、 象棋、游戏人生... ---- BASIC、C++、JAVA、 C#、 Haskell、 Objective-C、 Open Inventor、程序人生... ---- GTD伴我实现人生目标 ---- SeismicPro地震剖面显示程序 ---- Eclipse油藏数值模拟二进制文件格式解析和格式转换软件 本文转自申龙斌的程序人生博客园博文,原文链接:http://www.cnblogs.com/speeding/p/3539179.html,如需转载请自行联系原作者 http://www.cnblogs.com/speeding/

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

Android笔记:SlidingDrawer

一、概述 抽屉控件,官方已不建议用;但在某些需求下直接使用这个控件还是相当方便的。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <SlidingDrawer android:id= "@+id/drawer" android:layout_width= "match_parent" android:layout_height= "match_parent" android:handle= "@+id/handle" android:content= "@+id/content" > <ImageView android:id= "@+id/handle" android:layout_width= "88dip" android:layout_height= "44dip" /> <GridView android:id= "@+id/content" android:layout_width= "match_parent" android:layout_height= "match_parent" /> </SlidingDrawer> 1.XML文件中的属性 属性名称 描述 android:allowSingleTap 是否可通过单击handle打开或关闭抽屉。默认是true。(如果是false,用户必须通过拖动,滑动或者使用轨迹球。) android:animateOnClick 顾名思义,点击的时候是否有动画。默认是true。 android:bottomOffset “手柄”距离SlidingDrawer底部的额外距离 。 android:content SlidingDrawer的内容。 android:handle SlidingDrawer的“手柄”。 android:orientation SlidingDrawer的方向。 android:topOffset “手柄”距离SlidingDrawer顶部的额外距离 。 2.一些重要的方法: voidsetOnDrawerCloseListener(SlidingDrawer.OnDrawerCloseListeneronDrawerCloseListener) 设置一个监听器,用来接收当抽屉被关闭时候的通知。 voidsetOnDrawerOpenListener(SlidingDrawer.OnDrawerOpenListeneronDrawerOpenListener) Since:API Level 3 设置一个监听器,用来接收当抽屉被打开的时候的通知。 voidsetOnDrawerScrollListener(SlidingDrawer.OnDrawerScrollListeneronDrawerScrollListener) 设置一个监听器,用来接收当抽屉处于正在打开或者正在结束的滚动时候的通知。 animateClose(): 使用动画关闭抽屉。 animateOpen(): 使用动画打开抽屉 getContent(): 获取内容 isMoving(): 指示SlidingDrawer是否在移动。 isOpened(): 指示SlidingDrawer是否已全部打开 lock(): 屏蔽触摸事件。 unlock(): 解除屏蔽触摸事件。 toggle(): 切换打开和关闭的抽屉SlidingDrawer。 3.一个简单的例子: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class SlidingDrawerDemoActivity extends Activity{ private SlidingDrawermyDrawer; private ImageViewmyImageView; private GridViewmyGridView; @Override protected void onCreate(BundlesavedInstanceState){ super .onCreate(savedInstanceState); setContentView(R.layout.main); myDrawer=(SlidingDrawer)findViewById(R.id.drawer); myImageView=(ImageView)findViewById(R.id.handle); myGridView=(GridView)findViewById(R.id.content); myDrawer.setOnDrawerOpenListener(newSlidingDrawer.OnDrawerOpenListener(){ @Override public void onDrawerOpened(){ myImageView.setImageResource(R.drawable.down); } }); myDrawer.setOnDrawerCloseListener(newSlidingDrawer.OnDrawerCloseListener(){ @Override public void onDrawerClosed(){ myImageView.setImageResource(R.drawable.up); } }); } } 二、可监听按钮点击事件的自定义SlidingDrawer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.SlidingDrawer; /** *自定义SlidingDrawer:可监听按钮点击事件 *@authorzeng * */ public class ClickableSlidingDrawer extends SlidingDrawer { private ViewGroupmHandleLayout; private final RectmHitRect= new Rect(); public ClickableSlidingDrawer(Contextcontext,AttributeSetattrs, int defStyle) { super (context,attrs,defStyle); } public ClickableSlidingDrawer(Contextcontext,AttributeSetattrs) { super (context,attrs); } @Override protected void onFinishInflate() { super .onFinishInflate(); Viewhandle=getHandle(); if (handle instanceof ViewGroup) { mHandleLayout=(ViewGroup)handle; } } @Override public boolean onInterceptTouchEvent(MotionEventevent) { if (mHandleLayout!= null ) { int childCount=mHandleLayout.getChildCount(); int handleClickX=( int )(event.getX()-mHandleLayout.getX()); int handleClickY=( int )(event.getY()-mHandleLayout.getY()); RecthitRect=mHitRect; for ( int i= 0 ;i<childCount;i++) { ViewchildView=mHandleLayout.getChildAt(i); childView.getHitRect(hitRect); if (hitRect.contains(handleClickX,handleClickY)) { return false ; } } } return super .onInterceptTouchEvent(event); } } 三、控制SlidingDrawer在屏幕低端,而不会填满整个屏幕,同时handle按钮可点击的自定义SlidingDrawer 注:解决SlidingDrawer的高度设置为wrap_content时无法做到非全屏的问题。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.SlidingDrawer; /** *自定义SlidingDrawer,控制SlidingDrawer在屏幕低端,而不会填满整个屏幕,同时handle按钮可点击 *@authorzeng * */ public class ClickableWrapSlidingDrawer extends SlidingDrawer { private ViewGroupmHandleLayout; private final RectmHitRect= new Rect(); private boolean mVertical; private int mTopOffset; public ClickableWrapSlidingDrawer(Contextcontext,AttributeSetattrs, int defStyle) { super (context,attrs,defStyle); int orientation=attrs.getAttributeIntValue( "android" , "orientation" ,ORIENTATION_VERTICAL); mTopOffset=attrs.getAttributeIntValue( "android" , "topOffset" , 0 ); mVertical=(orientation==SlidingDrawer.ORIENTATION_VERTICAL); } public ClickableWrapSlidingDrawer(Contextcontext,AttributeSetattrs) { super (context,attrs); int orientation=attrs.getAttributeIntValue( "android" , "orientation" ,ORIENTATION_VERTICAL); mTopOffset=attrs.getAttributeIntValue( "android" , "topOffset" , 0 ); mVertical=(orientation==SlidingDrawer.ORIENTATION_VERTICAL); } @Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec); final Viewhandle=getHandle(); final Viewcontent=getContent(); measureChild(handle,widthMeasureSpec,heightMeasureSpec); if (mVertical) { int height=heightSpecSize-handle.getMeasuredHeight()-mTopOffset; content.measure(widthMeasureSpec,MeasureSpec.makeMeasureSpec(height,heightSpecMode)); heightSpecSize=handle.getMeasuredHeight()+mTopOffset+content.getMeasuredHeight(); widthSpecSize=content.getMeasuredWidth(); if (handle.getMeasuredWidth()>widthSpecSize) widthSpecSize=handle.getMeasuredWidth(); } else { int width=widthSpecSize-handle.getMeasuredWidth()-mTopOffset; getContent().measure(MeasureSpec.makeMeasureSpec(width,widthSpecMode),heightMeasureSpec); widthSpecSize=handle.getMeasuredWidth()+mTopOffset+content.getMeasuredWidth(); heightSpecSize=content.getMeasuredHeight(); if (handle.getMeasuredHeight()>heightSpecSize) heightSpecSize=handle.getMeasuredHeight(); } setMeasuredDimension(widthSpecSize,heightSpecSize); } @Override protected void onFinishInflate() { super .onFinishInflate(); Viewhandle=getHandle(); if (handle instanceof ViewGroup) { mHandleLayout=(ViewGroup)handle; } } @Override public boolean onInterceptTouchEvent(MotionEventevent) { if (mHandleLayout!= null ) { int childCount=mHandleLayout.getChildCount(); int handleClickX=( int )(event.getX()-mHandleLayout.getX()); int handleClickY=( int )(event.getY()-mHandleLayout.getY()); RecthitRect=mHitRect; for ( int i= 0 ;i<childCount;i++) { ViewchildView=mHandleLayout.getChildAt(i); childView.getHitRect(hitRect); if (hitRect.contains(handleClickX,handleClickY)) { return false ; } } } return super .onInterceptTouchEvent(event); } } 本文转自 glblong 51CTO博客,原文链接:http://blog.51cto.com/glblong/1548354,如需转载请自行联系原作者

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

Android笔记:stackview

stackview相关范例:(附件) 参考资料: 1.http://sampleprogramz.com/android/stackview.php 2.http://stackoverflow.com/questions/9997753/how-to-create-custom-stackview-for-android-2-2-or-2-3 3.http://stackoverflow.com/questions/5544112/alternative-views-instead-of-stackviews-for-android-versions-lower-to-android-3 4.http://stackoverflow.com/questions/12286506/simulate-stack-view-in-android2api-5 附件:http://down.51cto.com/data/2363570 本文转自 glblong 51CTO博客,原文链接:http://blog.51cto.com/glblong/1304103,如需转载请自行联系原作者

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

[old]wireless 笔记

AD Hoc network IBSS: Independent basic service set AP是解决无线客户端访问有线设备资源AP是什么?802.11<-----> 802.3 DS:分布系统==> Used for Infrastructure Mode BSA=wireless cell(basic service area)表示一个范围 BSS :基础服务集,由多个BSS组成的网络叫ESS扩展服务集 SSID service set identifier服务标示符,相当于一个VLAN,一个subnet BSSID:基础服务集标示符,一个48bit的MAC地址,无线客户端物理关联的标示符 多个BSSID组成MBSSID 漫游的条件:两AP必须要有交集覆盖 AP工作的角色(模式): HUB竞争信道 Repeater Outdoor Bridge (包括户外网桥,多用定向天线) Wireless只关心微波波段. 信号有Free-Spare path loss:自由空间路径损失,网络上有公式算. 信号有吸收,反射,穿透,散射最终我们可能会收到multipath信号,multipath会导致信号不好<出现信号抵消> SNR: signal to noise ratio,信噪比 Signal Strength / Noise Level = SNR Decibels分贝DB,是两次数据的对比 0dB = same power 3dB = twice the power -3dB=half the power 10dB=10* the power -10DB = 1 tenth of the power DBm used for AP transmitters ===>对等单位mw 0dbm = 1 mW 30dbm= 1W -20 dbm=0.01 mw 20dbm = 100 mw DBI used for dipole antenna Gain (偶极天线) 0dBd = + 2.14 dBi EIRP = TX power /dbm(天线发射功率)+ Antenna Gain /dbi (天线增益dbi)– Loss /DB Effectivelsotropic radiated power (等效全向辐射功率) 波的三要素:频率,倒数为波长,天线长度最好为1/4波长 振幅,经过任何物体都会吸收振幅. 相位,多天线来解决多路径问题 影响的因数就是上面三种 天线计量单位:瓦特和分贝 802.11设备最大100mW 80.211帧和CSMA/CA载波侦听多路访问/冲突避免 以太网使用的是CSMA/CD冲突检测 TO DS / From DS字段:这个两个字段用来描述包的去向 Frame Type: 1-Control (Request to send RTS, clear to send CTS, ACK,PS-Poll power save poll) 2-Data (simple data, null function) 3-Management: Beacon: 100s发一次,公告SSID BSSID等信息,隐藏的SSID的AP也会发Beacon Authentication request / response Association request / response Announcement traffic indication message (ATIM) 802.11 n的优点: 天线多,会有多径问题,有相位的错位 1分集(多天线技术,不等于MIMO) ,提高吞吐量 2 MIMO:空分复用,波束赋形(根据对方的信号找到最好的路径),最大比合并(按比值信号合并) AP的接口: AP型号上字母的意思 AIR-AP1131AG-A-K9 AIR-AP1131G-A-K9 AIR-LAP1131AG-A-K9 AIR = Aironet一种产品 AP/LAP =不带L表示出厂默认胖APIOS,带L表示默认瘦APIOS 1131= 1130系列 A=5GHZ, G=2.4HZ<3502不再有这个标示> -x=代表国家代码 -A:美国-C:中国 K9:代表安全特性 AIR-SAP3502i-A-K9 AIR-CAP3502e-A-K9 SAP/CAP = S表示出厂默认胖<stay alone > AP IOS, C表示默认瘦APIOS i/e= i表示内置天线,e表示外置天线 3501/3502 :代表单双频 =================================配置试验===================================== 配置胖AP单SSID Test PC xxxxxxAP----SW---Admin PC 配置管理胖AP管理地址: Interface bvi 1 Ip add 172.16.100.11 255.255.255.0 No shut 然后web登入,默认的是enablepassword ,用户名可以不用填 修改加密方法:security----encryption manager 创建SSID: secrirty-----SSID manager—single SSID 是否广播SSID Enable radiointerface开启2.4G/5.0G射频 ------>看summary可以看到结果 配置胖AP多SSID Test PC xxxxxxAP----SW---Admin PC Show ip int b 1-service-vlan 2-Security-Encryption manager ----CIPHER---AESCCMP 3-Security-SSID Manager---Multiple SSID ----Multiple bssidbeacon ----“set ssid as guest mode” 4- Enable radiointerface 查看有多少个链接:可以在ASSOCIATION中查看到 胖瘦AP的区别: IOS的区别, w7代表胖AP IOS, W8代表瘦AP IOS 胖AP转瘦AP叫升级, 瘦AP转胖AP叫降级 降级方法:1-断电mode建还原法,2-AP已注册在wlc上,使用wlc上降级命令 Config ap tftp-downgrade xxx.tar <APName> Show ap summary 升级方法: 1-命令行下载解压(不太老款的AP) Boot system flash:可以决定由哪个IOS启动 本文转自 bilinyee博客,原文链接: http://blog.51cto.com/ericfu/1753341 如需转载请自行联系原作者

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

jenkins笔记(1)

一、相关概念 1.1 Jenkins概念: Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目,无论用的是什么平台。这是一个免费的源代码,可以处理任何类型的构建或持续集成。集成Jenkins可以用于一些测试和部署技术。Jenkins是一种软件允许持续集成。 1.2 Jenkins目的: 1、持续、自动地构建/测试软件项目。 2、监控软件开放流程,快速问题定位及处理,提示开放效率。 1.3 特性: 开源的java语言开发持续集成工具,支持CI,CD。 易于安装部署配置:可通过yum安装,或下载war包以及通过docker容器等快速实现安装部署,可方便web界面配置管理。 消息通知及测试报告:集成RSS/E-mail通过RSS发布构建结果或当构建完成时通过e-mail通知,生成JUnit/TestNG测试报告。 分布式构建:支持Jenkins能够让多台计算机一起构建/测试。 文件识别:Jenkins能够跟踪哪次构建生成哪些jar,哪次构建使用哪个版本的jar等。 丰富的插件支持:支持扩展插件,你可以开发适合自己团队使用的工具,如git,svn,maven,docker等。 1.4 产品发布流程: 产品设计成型 -> 开发人员开发代码 -> 测试人员测试功能 -> 运维人员发布上线 持续集成 (Continuous integration,简称CI) 持续交付(Continuous delivery) 持续部署(continuous deployment) 二、安装部署 2.1 yum安装 最低配置: 不少于256M内存,不低于1G磁盘,jdk版本>=8 安装jdk1.8 yum install -y java-1.8.0-openjdk && \ wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat/jenkins.repo && \ rpm --import https://pkg.jenkins.io/redhat/jenkins.io.key &&\ yum clean all && yum makecache && \ yum install -y jenkins && \ systemctl start jenkins less /var/log/jenkins/jenkins.log //查询admin密码 访问http://ip:8080/进行安装 jenkins相关目录:rpm -ql jenkins#查看jenkins安装相关目录 安装目录/var/lib/jenkins 配置文件 /etc/sysconfig/jenkins 日志目录 /var/log/jenkins 2.2 下载war包安装 yum install -y java-1.8.0-openjdk && mkdir -p /usr/local/jenkins/ && \ wget -c -O /usr/local/jenkins/jenkins.war http://mirrors.jenkins.io/war-stable/latest/jenkins.war &&\ nohup java -jar /usr/local/jenkins/jenkins.war & 密码查看:/root/.jenkins/secrets/initialAdminPassword 访问http://ip:8080/进行安装 2.3 docker形式安装 yum install docker -y && systemctl start docker && \ curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s && systemctl restart docker docker search jenkins docker pull docker.io/jenkins docker run -d -p 8080:8080 docker.io/jenkins docker exec -it 52873fa3dce4 /bin/bashcat /var/jenkins_home/secrets#查看密码 访问http://ip:8080/进行安装 2.4 相关工具安装 工具安装可在jenkins的web界面系统管理---管理插件---可选插件勾选需要的,也可以手动安装。 a.git安装 yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc-c++ perl-ExtUtils-MakeMaker wget autoconf -y wget https://www.kernel.org/pub/software/scm/git/git-2.7.3.tar.gz tar xf git-2.7.3.tar.gz cd git-2.7.3 make configure ./configure --prefix=/usr/local/git make profix=/usr/local/git make install echo "export PATH=$PATH:/usr/local/git/bin" > /etc/profile.d/git.sh source /etc/profile.d/git.sh b.maven安装 cd /usr/local/ && wget -c wget http://mirror.bit.edu.cn/apache/maven/maven-3/3.5.2/binaries/apache-maven-3.5.2-bin.tar.gz &&\ tar -zxf apache-maven-3.5.2-bin.tar.gz && \ echo "export PATH=$PATH:/usr/local/apache-maven-3.5.2/bin" > /etc/profile.d/maven.sh && \ source /etc/profile.d/maven.sh mvn --version 查看版本 c.svn安装 yum install subversion -y mkdir /svnrepo svnadmin create /svnrepo/project1 #创建一个新的Subversion项目 启动项目1:svnserve -d -r /svnrepo/project1/ 启动整个项目:svnserve -d -r /svnrepo/ d.jdk安装 某些情况下会遇到openjdk无法编译java程序,或在编译中存在异常,需要我们手动安装jdk,首先去Oracle官网下载对应版本的jdk tar -zxvf jdk-8u121-linux-x64.gz -C /usr/local && \ ln -sv /usr/local/jdk1.8.0_121 /usr/local/jdk1.8 && \ echo "export PATH=$PATH:/usr/local/jdk1.8/bin" >/etc/profile.d/java.sh source /etc/profile.d/java.sh 三、WEB界面配置 3.1 密码破解 cd /var/lib/jenkins/users/admin #此为admin用户的目录,也可以对应自己创建的用户 vim config.xml //定位到<passwordHash>那一行 删除改行,改为 <passwordHash>#jbcrypt:$2a$10$pDQks0ytOkCfmpdgpLygrOC3uY7i/XnZHBKRQDhrBPwKoN2f5Kz8C</passwordHash> 重启一下jenkins 新密码为admin 3.2 maven配置 针对之前手动安装的maven需要在web界面配置进行结合。 系统管理---全局工具配置(Maven Configuration)配置: 此处为手动安装的maven,需要取消自动安装maven,name可以自定义,MAVEN_HOME指定下载安装的maven具体文件目录 3.3 GIT配置 手动安装的git,web界面只需添加具体可执行路径即可 3.4 jdk配置 对应手动安装的jdk,需要web配置java_home路径 3.5 邮件配置 jenkins有内置的邮件功能但由于其对于成功的发布不会发送邮件可定制化程度低,在此直接配置插件邮件(Editable Email Notification),2.32以上版本的jenkins,常用的插件都已经在最初部署的时候安装了(如果你当初选择的是suggest的plugins)。 可以使用qq邮件服务器或163等其他邮件服务器,在此配置使用163邮箱,首先需要开启163邮箱的smtp服务。 需要记录此处开启smtp时的密码。 在jenkins 系统管理---系统设置---Extended E-mail Notification 在默认的触发器 Default Triggers 标签下,可以勾选邮件通知触发的情况,如任何情况下均需要选择always,也可以在仅失败情况下发送,也可以对应的邮件接受人不通,进行邮件分类。 以下的内容,如邮件主题,内容均可自定义,可以参考url: 至此全局插件邮件已经配置完毕,需要在具体的项目中使用此邮件进行发送通知。 四、实战php程序自动发布 4.1 插件安装 系统管理---管理插件---已安装 检查是否有“Git plugin”和“Publish Over SSH”两个插件,如果没有,则需点击“可选插件”,找到它并安装 安装好两个插件后,点击“系统管理”-- “系统设置” 4.2 SSH配置 系统管理---系统设置---Publish over SSH 在key内填写jenkins服务器的私钥,如果没有需要先在jenkins服务器生成私钥与公钥。ssh-keygen -t rsa回车后会在登录用户的家目录下生成一个.ssh 的目录,此目录下存在id_rsa私钥与id_rsa.pub公钥。且讲公钥发布至代码发布的目标服务器上ssh-copy-id -i /root/.ssh/id_rsa.pub root@IP。 SSH Server配置 name:需要将php程序发布到目标服务器的名称,可自定义 Hostname:填写目录服务器的IP地址 Username:使用那个用户进行发布,此处为进行密钥互信的用户 Remote Directory:此出为发布到目标服务器的相对根路径,建议填写/,防止后续填写路径异常。 注:如果为多台目标服务器,可以继续添加,如果目标服务器存在代理,也可设置proxy 4.3 构建项目 新建Item---填入项目名称---选择构建一个自由风格的软件项目---确定源码管理选择git Repository URL 填写具体git上的仓库url,如果为私有,需要继续添加Credentials,如果为公有直接填写url即可,Credentials为none, 构建后够操作 选择(Send files or execute commands over SSH) SSH Server选择目标服务器如:php-server Source files:/#将git拉去下来的原始文件 Remote directory:/var/www/html #发布到目标服务器的制定目录 Exec command:chown apache:apache -R /var/www/html/* #制定后续的操作 此时可以选择Editable Email Notification来构建邮件通知。 在此处,之前的邮件主题,内容均可以自定义,在高级里面,选择邮件接受人。 点击保存,并立即构建,可以点击console output查看日志 此时打开php程序发现程序文件已经成功发布到目标服务器上 此时可以查看邮件也已经发送成功。 本文转自 KaliArch 51CTO博客,原文链接:http://blog.51cto.com/kaliarch/2050862,如需转载请自行联系原作者

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

swift 笔记2

swift交流群:342581988,欢迎增加。 今天真郁闷啊,把mac升级到10.10了。如今好了,曾经的程序都跑不了了。哎,不说了,让我郁闷会再。 说说条件推断吧,事实上这些基本的语法大家都知道肯定差点儿相同。好吧,我就自己写写看吧。 上代码 let individualScores = [78,32,32,53,23] var teamScore = 0 for score in individualScore{ if score > 50{ teamScore += 3 }else{ teamScore += 1 } } teamScore 这个事实上和曾经的oc差点儿相同了,我想大家都注意到了。就是这个Swift把曾经用圆弧括号的地方所有去掉,把分号也去掉。大概就是这样子 在这里。要说说这个if推断语句,这个if后面的条件必须是个Boolean表达式。换句话说,假设上面这样写 if score{ ... } 那就是错的了 由于score是一个非Boolean值,这个在曾经都是会自己主动与0相比較,非零值就会运行if后面语句。 这个地方if和let能够一起使用。let修饰变量时可选的,能够是一个值。也能够是nil。在类型后面加一个?表示他是可选的。 let v : int;已分配内存; let v : int ? 还没分配内存 以下if里面用来推断optionName是否存在。 var optionString:String? = "Hello" optionString == nil var optionName: String ? = "John Appleseed" var greeting = "Hello!" if let name = optionName{ greeting = "Hello, \(name)" } 假设上面optionName = nil;那么结果将会不一样,会运行你后面else部分。 以下说说switch吧,这个我也还没搞太懂,所以大家一定要拍砖 这个switch支持不论什么数据 的比較操作。不不过整形之类的, let vegetable = "red pepper" switch vegetable { case "celery": let vegetableComment = "Add some raisins and make ants on a log." case "cucumber","watercress": let vegetableComment = "That would make a good team sandwich." case let x where x.hasSuffix("pepper"): let vegetableComment = "Is it a spicy \(x)?" default: let vegetableComment = "Everything tastes good in soup." } 这个地方后面的default是不能少的,不然就会报错,只是这个地方有点变化的是,break不见了。对的。break不要了。 再说说for-in迭代循环吧 let interestingNumbers = [ "Prime": [2, 3, 5, 7, 11, 13], "Fibonacci": [1, 1, 2, 3, 5, 8], "Square": [1, 4, 9, 16, 25], ] var largest = 0 for (kind, numbers) in interestingNumbers { for number in numbers { if number > largest { largest = number } } } largest 事实上这个没什么可说的了,看看即可了, 然后在看看while和do...while吧 var n = 2; while n < 100{ n = n * 2 } n var m = 2 do{ m = m * 2 }while m < 100 m 注意确保循环进行至少一次 好吧,再写最后一个,应该算是一种新的写法吧 var firstForLoop = 0 for i in 0..3{ firstForLoop += i } firstForLoop var secondForLoop = 0 for var i = 0;i < 3; ++i{ secondForLoop += 1 } secondForLoop ..代表值递增。貌似包含端点值。 好吧,今天就写这点吧,感觉好慢啊,以后不能这样写了。必须 加速了,捡重点写点吧。 本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5112570.html,如需转载请自行联系原作者

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

Docker 学习笔记

一、Docker 简介 Docker 两个主要部件: Docker: 开源的容器虚拟化平台 Docker Hub: 用于分享、管理 Docker 容器的 Docker SaaS 平台 -- Docker Hub Docker 使用客户端-服务器 (C/S) 架构模式。Docker 客户端会与 Docker 守护进程进行通信。Docker 守护进程会处理复杂繁重的任务,例如建立、运行、发布你的 Docker 容器。Docker 客户端和守护进程可以运行在同一个系统上,当然你也可以使用 Docker 客户端去连接一个远程的 Docker 守护进程。Docker 客户端和守护进程之间通过 socket 或者 RESTful API 进行通信。 1.1 Docker 守护进程 如上图所示,Docker 守护进程运行在一台主机上。用户并不直接和守护进程进行交互,而是通过 Docker 客户端间接和其通信。 1.2 Docker 客户端 Docker 客户端,实际上是 docker 的二进制程序,是主要的用户与 Docker 交互方式。它接收用户指令并且与背后的 Docker 守护进程通信,如此来回往复。 1.3 Docker 内部 要理解 Docker 内部构建,需要理解以下三种部件: Docker 镜像 - Docker images Docker 仓库 - Docker registeries Docker 容器 - Docker containers Docker 镜像 Docker 镜像是 Docker 容器运行时的只读模板,每一个镜像由一系列的层 (layers) 组成。Docker 使用 UnionFS 来将这些层联合到单独的镜像中。UnionFS 允许独立文件系统中的文件和文件夹(称之为分支)被透明覆盖,形成一个单独连贯的文件系统。正因为有了这些层的存在,Docker 是如此的轻量。当你改变了一个 Docker 镜像,比如升级到某个程序到新的版本,一个新的层会被创建。因此,不用替换整个原先的镜像或者重新建立(在使用虚拟机的时候你可能会这么做),只是一个新 的层被添加或升级了。现在你不用重新发布整个镜像,只需要升级,层使得分发 Docker 镜像变得简单和快速。 Docker 仓库 Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。同样的,Docker 仓库也有公有和私有的概念。公有的 Docker 仓库名字是 Docker Hub。Docker Hub 提供了庞大的镜像集合供使用。这些镜像可以是自己创建,或者在别人的镜像基础上创建。Docker 仓库是 Docker 的分发部分。 Docker 容器 Docker 容器和文件夹很类似,一个Docker容器包含了所有的某个应用运行所需要的环境。每一个 Docker 容器都是从 Docker 镜像创建的。Docker 容器可以运行、开始、停止、移动和删除。每一个 Docker 容器都是独立和安全的应用平台,Docker 容器是 Docker 的运行部分。 1.4 libcontainer Docker 从 0.9 版本开始使用 libcontainer 替代 lxc,libcontainer 和 Linux 系统的交互图如下: 图片来源: Docker 0.9: introducing execution drivers and libcontainer 1.5 命名空间「Namespaces」 pid namespace 不同用户的进程就是通过 pid namespace 隔离开的,且不同 namespace 中可以有相同 PID。具有以下特征: 每个 namespace 中的 pid 是有自己的 pid=1 的进程(类似 /sbin/init 进程) 每个 namespace 中的进程只能影响自己的同一个 namespace 或子 namespace 中的进程 因为 /proc 包含正在运行的进程,因此在 container 中的 pseudo-filesystem 的 /proc 目录只能看到自己 namespace 中的进程 因为 namespace 允许嵌套,父 namespace 可以影响子 namespace 的进程,所以子 namespace 的进程可以在父 namespace 中看到,但是具有不同的 pid 参考文档:Introduction to Linux namespaces – Part 3: PID mnt namespace 类似 chroot,将一个进程放到一个特定的目录执行。mnt namespace 允许不同 namespace 的进程看到的文件结构不同,这样每个 namespace 中的进程所看到的文件目录就被隔离开了。同 chroot 不同,每个 namespace 中的 container 在 /proc/mounts 的信息只包含所在 namespace 的 mount point。 net namespace 网络隔离是通过 net namespace 实现的, 每个 net namespace 有独立的 network devices, IP addresses, IP routing tables, /proc/net 目录。这样每个 container 的网络就能隔离开来。 docker 默认采用 veth 的方式将 container 中的虚拟网卡同 host 上的一个 docker bridge 连接在一起。 参考文档:Introduction to Linux namespaces – Part 5: NET uts namespace UTS ("UNIX Time-sharing System") namespace 允许每个 container 拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非 Host 上的一个进程。 参考文档:Introduction to Linux namespaces – Part 1: UTS ipc namespace container 中进程交互还是采用 Linux 常见的进程间交互方法 (interprocess communication - IPC), 包括常见的信号量、消息队列和共享内存。然而同 VM 不同,container 的进程间交互实际上还是 host 上具有相同 pid namespace 中的进程间交互,因此需要在IPC资源申请时加入 namespace 信息 - 每个 IPC 资源有一个唯一的 32bit ID。 参考文档:Introduction to Linux namespaces – Part 2: IPC user namespace 每个 container 可以有不同的 user 和 group id, 也就是说可以以 container 内部的用户在 container 内部执行程序而非 Host 上的用户。 有了以上 6 种 namespace 从进程、网络、IPC、文件系统、UTS 和用户角度的隔离,一个 container 就可以对外展现出一个独立计算机的能力,并且不同 container 从 OS 层面实现了隔离。 然而不同 namespace 之间资源还是相互竞争的,仍然需要类似 ulimit 来管理每个 container 所能使用的资源 - cgroup。 Reference Docker Getting Start: Related Knowledge Docker 介绍以及其相关术语、底层原理和技术 1.6 资源配额「cgroups」 cgroups 实现了对资源的配额和度量。 cgroups 的使用非常简单,提供类似文件的接口,在 /cgroup 目录下新建一个文件夹即可新建一个 group,在此文件夹中新建 task 文件,并将 pid 写入该文件,即可实现对该进程的资源控制。具体的资源配置选项可以在该文件夹中新建子 subsystem ,{子系统前缀}.{资源项} 是典型的配置方法, 如 memory.usageinbytes 就定义了该 group 在 subsystem memory 中的一个内存限制选项。 另外,cgroups 中的 subsystem 可以随意组合,一个 subsystem 可以在不同的 group 中,也可以一个 group 包含多个 subsystem - 也就是说一个 subsystem。 memory 内存相关的限制 cpu 在 cgroup 中,并不能像硬件虚拟化方案一样能够定义 CPU 能力,但是能够定义 CPU 轮转的优先级,因此具有较高 CPU 优先级的进程会更可能得到 CPU 运算。 通过将参数写入 cpu.shares ,即可定义改 cgroup 的 CPU 优先级 - 这里是一个相对权重,而非绝对值 blkio block IO 相关的统计和限制,byte/operation 统计和限制 (IOPS 等),读写速度限制等,但是这里主要统计的都是同步 IO devices 设备权限限制 参考文档:how to use cgroup 二、Docker 安装 docker 的相关安装方法这里不作介绍,具体安装参考 官档 获取当前 docker 版本 $ sudo docker version Client version: 1.3.2 Client API version: 1.15 Go version (client): go1.3.3 Git commit (client): 39fa2fa/1.3.2 OS/Arch (client): linux/amd64 Server version: 1.3.2 Server API version: 1.15 Go version (server): go1.3.3 Git commit (server): 39fa2fa/1.3.2 三、Docker 基础用法 Docker HUB : Docker镜像首页,包括官方镜像和其它公开镜像 因为国情的原因,国内下载 Docker HUB 官方的相关镜像比较慢,可以使用 docker.cn 镜像,镜像保持和官方一致,关键是速度块,推荐使用。 3.1 Search images $ sudo docker search ubuntu 3.2 Pull images $ sudo docker pull ubuntu # 获取 ubuntu 官方镜像 $ sudo docker images # 查看当前镜像列表 3.3 Running an interactive shell $ sudo docker run -i -t ubuntu:14.04 /bin/bash docker run - 运行一个容器 -t - 分配一个(伪)tty (link is external) -i - 交互模式 (so we can interact with it) ubuntu:14.04 - 使用 ubuntu 基础镜像 14.04 /bin/bash - 运行命令 bash shell 注: ubuntu 会有多个版本,通过指定 tag 来启动特定的版本 [image]:[tag] $ sudo docker ps # 查看当前运行的容器, ps -a 列出当前系统所有的容器 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6c9129e9df10 ubuntu:14.04 /bin/bash 6 minutes ago Up 6 minutes cranky_babbage 3.4 相关快捷键 退出:Ctrl-Dorexit detach:Ctrl-P + Ctrl-Q attach:docker attach CONTAINER-ID 四、Docker 命令帮助 4.1 docker help docker command $ sudo docker # docker 命令帮助 Commands: attach Attach to a running container # 当前 shell 下 attach 连接指定运行镜像 build Build an image from a Dockerfile # 通过 Dockerfile 定制镜像 commit Create a new image from a container's changes # 提交当前容器为新的镜像 cp Copy files/folders from the containers filesystem to the host path # 从容器中拷贝指定文件或者目录到宿主机中 create Create a new container # 创建一个新的容器,同 run,但不启动容器 diff Inspect changes on a container's filesystem # 查看 docker 容器变化 events Get real time events from the server # 从 docker 服务获取容器实时事件 exec Run a command in an existing container # 在已存在的容器上运行命令 export Stream the contents of a container as a tar archive # 导出容器的内容流作为一个 tar 归档文件[对应 import ] history Show the history of an image # 展示一个镜像形成历史 images List images # 列出系统当前镜像 import Create a new filesystem image from the contents of a tarball # 从tar包中的内容创建一个新的文件系统映像[对应 export] info Display system-wide information # 显示系统相关信息 inspect Return low-level information on a container # 查看容器详细信息 kill Kill a running container # kill 指定 docker 容器 load Load an image from a tar archive # 从一个 tar 包中加载一个镜像[对应 save] login Register or Login to the docker registry server # 注册或者登陆一个 docker 源服务器 logout Log out from a Docker registry server # 从当前 Docker registry 退出 logs Fetch the logs of a container # 输出当前容器日志信息 port Lookup the public-facing port which is NAT-ed to PRIVATE_PORT # 查看映射端口对应的容器内部源端口 pause Pause all processes within a container # 暂停容器 ps List containers # 列出容器列表 pull Pull an image or a repository from the docker registry server # 从docker镜像源服务器拉取指定镜像或者库镜像 push Push an image or a repository to the docker registry server # 推送指定镜像或者库镜像至docker源服务器 restart Restart a running container # 重启运行的容器 rm Remove one or more containers # 移除一个或者多个容器 rmi Remove one or more images # 移除一个或多个镜像[无容器使用该镜像才可删除,否则需删除相关容器才可继续或 -f 强制删除] run Run a command in a new container # 创建一个新的容器并运行一个命令 save Save an image to a tar archive # 保存一个镜像为一个 tar 包[对应 load] search Search for an image on the Docker Hub # 在 docker hub 中搜索镜像 start Start a stopped containers # 启动容器 stop Stop a running containers # 停止容器 tag Tag an image into a repository # 给源中镜像打标签 top Lookup the running processes of a container # 查看容器中运行的进程信息 unpause Unpause a paused container # 取消暂停容器 version Show the docker version information # 查看 docker 版本号 wait Block until a container stops, then print its exit code # 截取容器停止时的退出状态值 Run 'docker COMMAND --help' for more information on a command. docker option Usage of docker: --api-enable-cors=false Enable CORS headers in the remote API # 远程 API 中开启 CORS 头 -b, --bridge="" Attach containers to a pre-existing network bridge # 桥接网络 use 'none' to disable container networking --bip="" Use this CIDR notation address for the network bridge's IP, not compatible with -b # 和 -b 选项不兼容,具体没有测试过 -d, --daemon=false Enable daemon mode # daemon 模式 -D, --debug=false Enable debug mode # debug 模式 --dns=[] Force docker to use specific DNS servers # 强制 docker 使用指定 dns 服务器 --dns-search=[] Force Docker to use specific DNS search domains # 强制 docker 使用指定 dns 搜索域 -e, --exec-driver="native" Force the docker runtime to use a specific exec driver # 强制 docker 运行时使用指定执行驱动器 --fixed-cidr="" IPv4 subnet for fixed IPs (ex: 10.20.0.0/16) this subnet must be nested in the bridge subnet (which is defined by -b or --bip) -G, --group="docker" Group to assign the unix socket specified by -H when running in daemon mode use '' (the empty string) to disable setting of a group -g, --graph="/var/lib/docker" Path to use as the root of the docker runtime # 容器运行的根目录路径 -H, --host=[] The socket(s) to bind to in daemon mode # daemon 模式下 docker 指定绑定方式[tcp or 本地 socket] specified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd. --icc=true Enable inter-container communication # 跨容器通信 --insecure-registry=[] Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16) --ip="0.0.0.0" Default IP address to use when binding container ports # 指定监听地址,默认所有 ip --ip-forward=true Enable net.ipv4.ip_forward # 开启转发 --ip-masq=true Enable IP masquerading for bridge's IP range --iptables=true Enable Docker's addition of iptables rules # 添加对应 iptables 规则 --mtu=0 Set the containers network MTU # 设置网络 mtu if no value is provided: default to the default route MTU or 1500 if no default route is available -p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file # 指定 pid 文件位置 --registry-mirror=[] Specify a preferred Docker registry mirror -s, --storage-driver="" Force the docker runtime to use a specific storage driver # 强制 docker 运行时使用指定存储驱动 --selinux-enabled=false Enable selinux support # 开启 selinux 支持 --storage-opt=[] Set storage driver options # 设置存储驱动选项 --tls=false Use TLS; implied by tls-verify flags # 开启 tls --tlscacert="/root/.docker/ca.pem" Trust only remotes providing a certificate signed by the CA given here --tlscert="/root/.docker/cert.pem" Path to TLS certificate file # tls 证书文件位置 --tlskey="/root/.docker/key.pem" Path to TLS key file # tls key 文件位置 --tlsverify=false Use TLS and verify the remote (daemon: verify client, client: verify daemon) # 使用 tls 并确认远程控制主机 -v, --version=false Print version information and quit # 输出 docker 版本信息 4.2 docker search $ sudo docker search --help Usage: docker search TERM Search the Docker Hub for images # 从 Docker Hub 搜索镜像 --automated=false Only show automated builds --no-trunc=false Don't truncate output -s, --stars=0 Only displays with at least xxx stars 示例: $ sudo docker search -s 100 ubuntu # 查找 star 数至少为 100 的镜像,找出只有官方镜像 start 数超过 100,默认不加 s 选项找出所有相关 ubuntu 镜像 NAME DESCRIPTION STARS OFFICIAL AUTOMATED ubuntu Official Ubuntu base image 425 [OK] 4.3 docker info $ sudo docker info Containers: 1 # 容器个数 Images: 22 # 镜像个数 Storage Driver: devicemapper # 存储驱动 Pool Name: docker-8:17-3221225728-pool Pool Blocksize: 65.54 kB Data file: /data/docker/devicemapper/devicemapper/data Metadata file: /data/docker/devicemapper/devicemapper/metadata Data Space Used: 1.83 GB Data Space Total: 107.4 GB Metadata Space Used: 2.191 MB Metadata Space Total: 2.147 GB Library Version: 1.02.84-RHEL7 (2014-03-26) Execution Driver: native-0.2 # 存储驱动 Kernel Version: 3.10.0-123.el7.x86_64 Operating System: CentOS Linux 7 (Core) 4.4 docker pull && docker push $ sudo docker pull --help # pull 拉取镜像 Usage: docker pull [OPTIONS] NAME[:TAG] Pull an image or a repository from the registry -a, --all-tags=false Download all tagged images in the repository $ sudo docker push # push 推送指定镜像 Usage: docker push NAME[:TAG] Push an image or a repository to the registry 示例: $ sudo docker pull ubuntu # 下载官方 ubuntu docker 镜像,默认下载所有 ubuntu 官方库镜像 $ sudo docker pull ubuntu:14.04 # 下载指定版本 ubuntu 官方镜像 $ sudo docker push 192.168.0.100:5000/ubuntu # 推送镜像库到私有源[可注册 docker 官方账户,推送到官方自有账户] $ sudo docker push 192.168.0.100:5000/ubuntu:14.04 # 推送指定镜像到私有源 4.5 docker images 列出当前系统镜像 $ sudo docker images --help Usage: docker images [OPTIONS] [NAME] List images -a, --all=false Show all images (by default filter out the intermediate image layers) # -a 显示当前系统的所有镜像,包括过渡层镜像,默认 docker images 显示最终镜像,不包括过渡层镜像 -f, --filter=[] Provide filter values (i.e. 'dangling=true') --no-trunc=false Don't truncate output -q, --quiet=false Only show numeric IDs 示例: $ sudo docker images # 显示当前系统镜像,不包括过渡层镜像 $ sudo docker images -a # 显示当前系统所有镜像,包括过渡层镜像 $ sudo docker images ubuntu # 显示当前系统 docker ubuntu 库中的所有镜像 REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ubuntu 12.04 ebe4be4dd427 4 weeks ago 210.6 MB ubuntu 14.04 e54ca5efa2e9 4 weeks ago 276.5 MB ubuntu 14.04-ssh 6334d3ac099a 7 weeks ago 383.2 MB 4.6 docker rmi 删除一个或者多个镜像 $ sudo docker rmi --help Usage: docker rmi IMAGE [IMAGE...] Remove one or more images -f, --force=false Force removal of the image # 强制移除镜像不管是否有容器使用该镜像 --no-prune=false Do not delete untagged parents # 不要删除未标记的父镜像 4.7 docker run $ sudo docker run --help Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] Run a command in a new container -a, --attach=[] Attach to stdin, stdout or stderr. -c, --cpu-shares=0 CPU shares (relative weight) # 设置 cpu 使用权重 --cap-add=[] Add Linux capabilities --cap-drop=[] Drop Linux capabilities --cidfile="" Write the container ID to the file # 把容器 id 写入到指定文件 --cpuset="" CPUs in which to allow execution (0-3, 0,1) # cpu 绑定 -d, --detach=false Detached mode: Run container in the background, print new container id # 后台运行容器 --device=[] Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc) --dns=[] Set custom dns servers # 设置 dns --dns-search=[] Set custom dns search domains # 设置 dns 域搜索 -e, --env=[] Set environment variables # 定义环境变量 --entrypoint="" Overwrite the default entrypoint of the image # ? --env-file=[] Read in a line delimited file of ENV variables # 从指定文件读取变量值 --expose=[] Expose a port from the container without publishing it to your host # 指定对外提供服务端口 -h, --hostname="" Container host name # 设置容器主机名 -i, --interactive=false Keep stdin open even if not attached # 保持标准输出开启即使没有 attached --link=[] Add link to another container (name:alias) # 添加链接到另外一个容器 --lxc-conf=[] (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" -m, --memory="" Memory limit (format: <number><optional unit>, where unit = b, k, m or g) # 内存限制 --name="" Assign a name to the container # 设置容器名 --net="bridge" Set the Network mode for the container # 设置容器网络模式 'bridge': creates a new network stack for the container on the docker bridge 'none': no networking for this container 'container:<name|id>': reuses another container network stack 'host': use the host network stack inside the container. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. -P, --publish-all=false Publish all exposed ports to the host interfaces # 自动映射容器对外提供服务的端口 -p, --publish=[] Publish a container's port to the host # 指定端口映射 format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort (use 'docker port' to see the actual mapping) --privileged=false Give extended privileges to this container # 提供更多的权限给容器 --restart="" Restart policy to apply when a container exits (no, on-failure[:max-retry], always) --rm=false Automatically remove the container when it exits (incompatible with -d) # 如果容器退出自动移除和 -d 选项冲突 --security-opt=[] Security Options --sig-proxy=true Proxify received signals to the process (even in non-tty mode). SIGCHLD is not proxied. -t, --tty=false Allocate a pseudo-tty # 分配伪终端 -u, --user="" Username or UID # 指定运行容器的用户 uid 或者用户名 -v, --volume=[] Bind mount a volume (e.g., from the host: -v /host:/container, from docker: -v /container) # 挂载卷 --volumes-from=[] Mount volumes from the specified container(s) # 从指定容器挂载卷 -w, --workdir="" Working directory inside the container # 指定容器工作目录 示例: $ sudo docker images ubuntu REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ubuntu 14.04 e54ca5efa2e9 4 weeks ago 276.5 MB ... ... $ sudo docker run -t -i -c 100 -m 512MB -h test1 -d --name="docker_test1" ubuntu /bin/bash # 创建一个 cpu 优先级为 100,内存限制 512MB,主机名为 test1,名为 docker_test1 后台运行 bash 的容器 a424ca613c9f2247cd3ede95adfbaf8d28400cbcb1d5f9b69a7b56f97b2b52e5 $ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a424ca613c9f ubuntu:14.04 /bin/bash 6 seconds ago Up 5 seconds docker_test1 $ sudo docker attach docker_test1 root@test1:/# pwd / root@test1:/# exit exit 关于cpu优先级: By default all groups have 1024 shares. A group with 100 shares will get a ~10% portion of the CPU time - archlinux cgroups 4.8 docker start|stop|kill... ... dockerstart|stop|kill|restart|pause|unpause|rm|commit|inspect|logs docker start CONTAINER [CONTAINER...] # 运行一个或多个停止的容器 docker stop CONTAINER [CONTAINER...] # 停掉一个或多个运行的容器-t选项可指定超时时间 docker kill [OPTIONS] CONTAINER [CONTAINER...] # 默认 kill 发送 SIGKILL 信号-s可以指定发送 kill 信号类型 docker restart [OPTIONS] CONTAINER [CONTAINER...] # 重启一个或多个运行的容器-t选项可指定超时时间 docker pause CONTAINER # 暂停一个容器,方便 commit docker unpause CONTAINER # 继续暂停的容器 docker rm [OPTIONS] CONTAINER [CONTAINER...] # 移除一个或多个容器 -f, --force=false Force removal of running container -l, --link=false Remove the specified link and not the underlying container -v, --volumes=false Remove the volumes associated with the container docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] # 提交指定容器为镜像 -a, --author="" Author (e.g., "John Hannibal Smith hannibal@a-team.com") -m, --message="" Commit message -p, --pause=true Pause container during commit # 默认 commit 是暂停状态 docker inspect CONTAINER|IMAGE [CONTAINER|IMAGE...] # 查看容器或者镜像的详细信息 docker logs CONTAINER # 输出指定容器日志信息 -f, --follow=false Follow log output # 类似 tail -f -t, --timestamps=false Show timestamps --tail="all" Output the specified number of lines at the end of logs (defaults to all logs) 参考文档:Docker Run Reference 4.9 Docker 1.3 新增特性和命令 Digital Signature Verification Docker 1.3 版本将使用数字签名自动验证所有官方库的来源和完整性,如果一个官方镜像被篡改或者被破坏,目前 Docker 只会对这种情况发出警告而并不阻止容器的运行。 Inject new processes withdocker exec docker exec --help Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] Run a command in an existing container -d, --detach=false Detached mode: run command in the background -i, --interactive=false Keep STDIN open even if not attached -t, --tty=false Allocate a pseudo-TTY 为了简化调试,可以使用docker exec命令通过 Docker API 和 CLI 在运行的容器上运行程序。 $ docker exec -it ubuntu_bash bash 上例将在容器 ubuntu_bash 中创建一个新的 Bash 会话。 Tune container lifecycles withdocker create 我们可以通过docker run <image name>命令创建一个容器并运行其中的程序,因为有很多用户要求创建容器的时候不启动容器,所以docker create应运而生了。 $ docker create -t -i fedora bash 6d8af538ec541dd581ebc2a24153a28329acb5268abe5ef868c1f1a261221752 上例创建了一个可写的容器层 (并且打印出容器 ID),但是并不运行它,可以使用以下命令运行该容器: $ docker start -a -i 6d8af538ec5 bash-4.2# Security Options 通过--security-opt选项,运行容器时用户可自定义 SELinux 和 AppArmor 卷标和配置。 $ docker run --security-opt label:type:svirt_apache -i -t centos \ bash 上例只允许容器监听在 Apache 端口,这个选项的好处是用户不需要运行 docker 的时候指定--privileged选项,降低安全风险。 参考文档:Docker 1.3: signed images, process injection, security options, Mac shared directories 4.10 Docker 1.5 新特性 参考文档:Docker 1.5 新特性 五、Docker 端口映射 # Find IP address of container with ID <container_id> 通过容器 id 获取 ip $ sudo docker inspect <container_id> | grep IPAddress | cut -d ’"’ -f 4 无论如何,这些 ip 是基于本地系统的并且容器的端口非本地主机是访问不到的。此外,除了端口只能本地访问外,对于容器的另外一个问题是这些 ip 在容器每次启动的时候都会改变。 Docker 解决了容器的这两个问题,并且给容器内部服务的访问提供了一个简单而可靠的方法。Docker 通过端口绑定主机系统的接口,允许非本地客户端访问容器内部运行的服务。为了简便的使得容器间通信,Docker 提供了这种连接机制。 5.1 自动映射端口 -P使用时需要指定--expose选项,指定需要对外提供服务的端口 $ sudo docker run -t -P --expose 22 --name server ubuntu:14.04 使用docker run -P自动绑定所有对外提供服务的容器端口,映射的端口将会从没有使用的端口池中 (49000..49900) 自动选择,你可以通过docker ps、docker inspect <container_id>或者docker port <container_id> <port>确定具体的绑定信息。 5.2 绑定端口到指定接口 基本语法 $ sudo docker run -p [([<host_interface>:[host_port]])|(<host_port>):]<container_port>[/udp] <image> <cmd> 默认不指定绑定 ip 则监听所有网络接口。 绑定 TCP 端口 # Bind TCP port 8080 of the container to TCP port 80 on 127.0.0.1 of the host machine. $ sudo docker run -p 127.0.0.1:80:8080 <image> <cmd> # Bind TCP port 8080 of the container to a dynamically allocated TCP port on 127.0.0.1 of the host machine. $ sudo docker run -p 127.0.0.1::8080 <image> <cmd> # Bind TCP port 8080 of the container to TCP port 80 on all available interfaces of the host machine. $ sudo docker run -p 80:8080 <image> <cmd> # Bind TCP port 8080 of the container to a dynamically allocated TCP port on all available interfaces $ sudo docker run -p 8080 <image> <cmd> 绑定 UDP 端口 # Bind UDP port 5353 of the container to UDP port 53 on 127.0.0.1 of the host machine. $ sudo docker run -p 127.0.0.1:53:5353/udp <image> <cmd> 六、Docker 网络配置 图: Docker - container and lightweight virtualization Dokcer 通过使用 Linux 桥接提供容器之间的通信,docker0 桥接接口的目的就是方便 Docker 管理。当 Docker daemon 启动时需要做以下操作: creates the docker0 bridge if not present # 如果 docker0 不存在则创建 searches for an IP address range which doesn’t overlap with an existing route # 搜索一个与当前路由不冲突的 ip 段 picks an IP in the selected range # 在确定的范围中选择 ip assigns this IP to the docker0 bridge # 绑定 ip 到 docker0 6.1 Docker 四种网络模式 四种网络模式摘自 Docker 网络详解及 pipework 源码解读与实践 docker run 创建 Docker 容器时,可以用 --net 选项指定容器的网络模式,Docker 有以下 4 种网络模式: host 模式,使用 --net=host 指定。 container 模式,使用 --net=container:NAMEorID 指定。 none 模式,使用 --net=none 指定。 bridge 模式,使用 --net=bridge 指定,默认设置。 host 模式 如果启动容器的时候使用 host 模式,那么这个容器将不会获得一个独立的 Network Namespace,而是和宿主机共用一个 Network Namespace。容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。 例如,我们在 10.10.101.105/24 的机器上用 host 模式启动一个含有 web 应用的 Docker 容器,监听 tcp 80 端口。当我们在容器中执行任何类似 ifconfig 命令查看网络环境时,看到的都是宿主机上的信息。而外界访问容器中的应用,则直接使用 10.10.101.105:80 即可,不用任何 NAT 转换,就如直接跑在宿主机中一样。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。 container 模式 这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。 none模式 这个模式和前两个不同。在这种模式下,Docker 容器拥有自己的 Network Namespace,但是,并不为 Docker容器进行任何网络配置。也就是说,这个 Docker 容器没有网卡、IP、路由等信息。需要我们自己为 Docker 容器添加网卡、配置 IP 等。 bridge模式 图:The Container World | Part 2 Networking bridge 模式是 Docker 默认的网络设置,此模式会为每一个容器分配 Network Namespace、设置 IP 等,并将一个主机上的 Docker 容器连接到一个虚拟网桥上。当 Docker server 启动时,会在主机上创建一个名为 docker0 的虚拟网桥,此主机上启动的 Docker 容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。接下来就要为容器分配 IP 了,Docker 会从 RFC1918 所定义的私有 IP 网段中,选择一个和宿主机不同的IP地址和子网分配给 docker0,连接到 docker0 的容器就从这个子网中选择一个未占用的 IP 使用。如一般 Docker 会使用 172.17.0.0/16 这个网段,并将 172.17.42.1/16 分配给 docker0 网桥(在主机上使用 ifconfig 命令是可以看到 docker0 的,可以认为它是网桥的管理接口,在宿主机上作为一块虚拟网卡使用) 6.2 列出当前主机网桥 $ sudo brctl show # brctl 工具依赖 bridge-utils 软件包 bridge name bridge id STP enabled interfaces docker0 8000.000000000000 no 6.3 查看当前 docker0 ip $ sudo ifconfig docker0 docker0 Link encap:Ethernet HWaddr xx:xx:xx:xx:xx:xx inet addr:172.17.42.1 Bcast:0.0.0.0 Mask:255.255.0.0 在容器运行时,每个容器都会分配一个特定的虚拟机口并桥接到 docker0。每个容器都会配置同 docker0 ip 相同网段的专用 ip 地址,docker0 的 IP 地址被用于所有容器的默认网关。 6.4 运行一个容器 $ sudo docker run -t -i -d ubuntu /bin/bash 52f811c5d3d69edddefc75aff5a4525fc8ba8bcfa1818132f9dc7d4f7c7e78b4 $ sudo brctl show bridge name bridge id STP enabled interfaces docker0 8000.fef213db5a66 no vethQCDY1N 以上, docker0 扮演着 52f811c5d3d6 container 这个容器的虚拟接口 vethQCDY1N interface 桥接的角色。 使用特定范围的 IP Docker 会尝试寻找没有被主机使用的 ip 段,尽管它适用于大多数情况下,但是它不是万能的,有时候我们还是需要对 ip 进一步规划。Docker 允许你管理 docker0 桥接或者通过-b选项自定义桥接网卡,需要安装bridge-utils软件包。 基本步骤如下: ensure Docker is stopped # 确保 docker 的进程是停止的 create your own bridge (bridge0 for example) # 创建自定义网桥 assign a specific IP to this bridge # 给网桥分配特定的 ip start Docker with the -b=bridge0 parameter # 以 -b 的方式指定网桥 # Stopping Docker and removing docker0 $ sudo service docker stop $ sudo ip link set dev docker0 down $ sudo brctl delbr docker0 # Create our own bridge $ sudo brctl addbr bridge0 $ sudo ip addr add 192.168.5.1/24 dev bridge0 $ sudo ip link set dev bridge0 up # Confirming that our bridge is up and running $ ip addr show bridge0 4: bridge0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state UP group default link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff inet 192.168.5.1/24 scope global bridge0 valid_lft forever preferred_lft forever # Tell Docker about it and restart (on Ubuntu) $ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker $ sudo service docker start 参考文档: Network Configuration 6.5 不同主机间容器通信 不同容器之间的通信可以借助于 pipework 这个工具: $ git clone https://github.com/jpetazzo/pipework.git $ sudo cp -rp pipework/pipework /usr/local/bin/ 安装相应依赖软件 $ sudo apt-get install iputils-arping bridge-utils -y 桥接网络 桥接网络可以参考 日常问题处理 Tips 关于桥接的配置说明,这里不再赘述。 # brctl show bridge name bridge id STP enabled interfaces br0 8000.000c291412cd no eth0 docker0 8000.56847afe9799 no vetheb48029 可以删除 docker0,直接把 docker 的桥接指定为 br0。也可以保留使用默认的配置,这样单主机容器之间的通信可以通过 docker0,而跨主机不同容器之间通过 pipework 新建 docker 容器的网卡桥接到 br0,这样跨主机容器之间就可以通信了。 ubuntu $ sudo service docker stop $ sudo ip link set dev docker0 down $ sudo brctl delbr docker0 $ echo 'DOCKER_OPTS="-b=br0"' >> /etc/default/docker $ sudo service docker start CentOS 7/RHEL 7 $ sudo systemctl stop docker $ sudo ip link set dev docker0 down $ sudo brctl delbr docker0 $ cat /etc/sysconfig/docker | grep 'OPTIONS=' OPTIONS=--selinux-enabled -b=br0 -H fd:// $ sudo systemctl start docker pipework 不同容器之间的通信可以借助于 pipework 这个工具给 docker 容器新建虚拟网卡并绑定 IP 桥接到 br0 $ git clone https://github.com/jpetazzo/pipework.git $ sudo cp -rp pipework/pipework /usr/local/bin/ $ pipework Syntax: pipework <hostinterface> [-i containerinterface] <guest> <ipaddr>/<subnet>[@default_gateway] [macaddr][@vlan] pipework <hostinterface> [-i containerinterface] <guest> dhcp [macaddr][@vlan] pipework --wait [-i containerinterface] 如果删除了默认的 docker0 桥接,把 docker 默认桥接指定到了 br0,则最好在创建容器的时候加上--net=none,防止自动分配的 IP 在局域网中有冲突。 $ sudo docker run --rm -ti --net=none ubuntu:14.04 /bin/bash root@a46657528059:/# $ # Ctrl-P + Ctrl-Q 回到宿主机 shell,容器 detach 状态 $ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a46657528059 ubuntu:14.04 "/bin/bash" 4 minutes ago Up 4 minutes hungry_lalande $ sudo pipework br0 -i eth0 a46657528059 192.168.115.10/24@192.168.115.2 # 默认不指定网卡设备名,则默认添加为 eth1 # 另外 pipework 不能添加静态路由,如果有需求则可以在 run 的时候加上 --privileged=true 权限在容器中手动添加, # 但这种安全性有缺陷,可以通过 ip netns 操作 $ sudo docker attach a46657528059 root@a46657528059:/# ifconfig eth0 eth0 Link encap:Ethernet HWaddr 86:b6:6b:e8:2e:4d inet addr:192.168.115.10 Bcast:0.0.0.0 Mask:255.255.255.0 inet6 addr: fe80::84b6:6bff:fee8:2e4d/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:8 errors:0 dropped:0 overruns:0 frame:0 TX packets:9 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:648 (648.0 B) TX bytes:690 (690.0 B) root@a46657528059:/# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.115.2 0.0.0.0 UG 0 0 0 eth0 192.168.115.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 使用ip netns添加静态路由,避免创建容器使用--privileged=true选项造成一些不必要的安全问题: $ docker inspect --format="` `.`State`.`Pid `" a46657528059 # 获取指定容器 pid 6350 $ sudo ln -s /proc/6350/ns/net /var/run/netns/6350 $ sudo ip netns exec 6350 ip route add 192.168.0.0/16 dev eth0 via 192.168.115.2 $ sudo ip netns exec 6350 ip route # 添加成功 192.168.0.0/16 via 192.168.115.2 dev eth0 ... ... 在其它宿主机进行相应的配置,新建容器并使用 pipework 添加虚拟网卡桥接到 br0,测试通信情况即可。 另外,pipework 可以创建容器的 vlan 网络,这里不作过多的介绍了,官方文档已经写的很清楚了,可以查看以下两篇文章: Pipework 官方文档 Docker 网络详解及 pipework 源码解读与实践 七、Dockerfile Docker 可以通过 Dockerfile 的内容来自动构建镜像。Dockerfile 是一个包含创建镜像所有命令的文本文件,通过docker build命令可以根据 Dockerfile 的内容构建镜像,在介绍如何构建之前先介绍下 Dockerfile 的基本语法结构。 Dockerfile 有以下指令选项: FROM MAINTAINER RUN CMD EXPOSE ENV ADD COPY ENTRYPOINT VOLUME USER WORKDIR ONBUILD 7.1 FROM 用法: FROM <image> 或者 FROM <image> FROM指定构建镜像的基础源镜像,如果本地没有指定的镜像,则会自动从 Docker 的公共库 pull 镜像下来。 FROM必须是 Dockerfile 中非注释行的第一个指令,即一个 Dockerfile 从FROM语句开始。 FROM可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile 中创建多个镜像。 如果FROM语句没有指定镜像标签,则默认使用latest标签。 7.2 MAINTAINER 用法: MAINTAINER <name> 指定创建镜像的用户 RUN 有两种使用方式 RUN RUN "executable", "param1", "param2" 每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像,后续的RUN都在之前RUN提交后的镜像为基础,镜像是分层的,可以通过一个镜像的任何一个历史提交点来创建,类似源码的版本控制。 exec 方式会被解析为一个 JSON 数组,所以必须使用双引号而不是单引号。exec 方式不会调用一个命令 shell,所以也就不会继承相应的变量,如: RUN [ "echo", "$HOME" ] 这种方式是不会达到输出 HOME 变量的,正确的方式应该是这样的 RUN [ "sh", "-c", "echo", "$HOME" ] RUN产生的缓存在下一次构建的时候是不会失效的,会被重用,可以使用--no-cache选项,即docker build --no-cache,如此便不会缓存。 7.3 CMD CMD有三种使用方式: CMD "executable","param1","param2" CMD "param1","param2" CMD command param1 param2 (shell form) CMD指定在 Dockerfile 中只能使用一次,如果有多个,则只有最后一个会生效。 CMD的目的是为了在启动容器时提供一个默认的命令执行选项。如果用户启动容器时指定了运行的命令,则会覆盖掉CMD指定的命令。 CMD会在启动容器的时候执行,build 时不执行,而RUN只是在构建镜像的时候执行,后续镜像构建完成之后,启动容器就与RUN无关了,这个初学者容易弄混这个概念,这里简单注解一下。 7.4 EXPOSE EXPOSE <port> [<port>...] 告诉 Docker 服务端容器对外映射的本地端口,需要在 docker run 的时候使用-p或者-P选项生效。 7.5 ENV ENV <key> <value> # 只能设置一个变量 ENV <key>=<value> ... # 允许一次设置多个变量 指定一个环节变量,会被后续RUN指令使用,并在容器运行时保留。 例子: ENV myName="John Doe" myDog=Rex\ The\ Dog \ myCat=fluffy 等同于 ENV myName John Doe ENV myDog Rex The Dog ENV myCat fluffy 7.6 ADD ADD <src>... <dest> ADD复制本地主机文件、目录或者远程文件 URLS 从 并且添加到容器指定路径中 。 支持通过 GO 的正则模糊匹配,具体规则可参见 Go filepath.Match ADD hom* /mydir/ # adds all files starting with "hom" ADD hom?.txt /mydir/ # ? is replaced with any single character 路径必须是绝对路径,如果 不存在,会自动创建对应目录 路径必须是 Dockerfile 所在路径的相对路径 如果是一个目录,只会复制目录下的内容,而目录本身则不会被复制 7.7 COPY COPY <src>... <dest> COPY复制新文件或者目录从 并且添加到容器指定路径中 。用法同ADD,唯一的不同是不能指定远程文件 URLS。 7.8 ENTRYPOINT ENTRYPOINT "executable", "param1", "param2" ENTRYPOINT command param1 param2 (shell form) 配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖,而CMD是可以被覆盖的。如果需要覆盖,则可以使用docker run --entrypoint选项。 每个 Dockerfile 中只能有一个ENTRYPOINT,当指定多个时,只有最后一个生效。 Exec form ENTRYPOINT 例子 通过ENTRYPOINT使用 exec form 方式设置稳定的默认命令和选项,而使用CMD添加默认之外经常被改动的选项。 FROM ubuntu ENTRYPOINT ["top", "-b"] CMD ["-c"] 通过 Dockerfile 使用ENTRYPOINT展示前台运行 Apache 服务 FROM debian:stable RUN apt-get update && apt-get install -y --force-yes apache2 EXPOSE 80 443 VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"] ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"] Shell form ENTRYPOINT 例子 这种方式会在/bin/sh -c中执行,会忽略任何CMD或者docker run命令行选项,为了确保docker stop能够停止长时间运行ENTRYPOINT的容器,确保执行的时候使用exec选项。 FROM ubuntu ENTRYPOINT exec top -b 如果在ENTRYPOINT忘记使用exec选项,则可以使用CMD补上: FROM ubuntu ENTRYPOINT top -b CMD --ignored-param1 # --ignored-param2 ... --ignored-param3 ... 依此类推 7.9 VOLUME VOLUME ["/data"] 创建一个可以从本地主机或其他容器挂载的挂载点,后续具体介绍。 7.10 USER USER daemon 指定运行容器时的用户名或 UID,后续的RUN、CMD、ENTRYPOINT也会使用指定用户。 7.11 WORKDIR WORKDIR /path/to/workdir 为后续的RUN、CMD、ENTRYPOINT指令配置工作目录。可以使用多个WORKDIR指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。 WORKDIR /a WORKDIR b WORKDIR c RUN pwd 最终路径是/a/b/c。 WORKDIR指令可以在ENV设置变量之后调用环境变量: ENV DIRPATH /path WORKDIR $DIRPATH/$DIRNAME 最终路径则为 /path/$DIRNAME。 7.12 ONBUILD ONBUILD [INSTRUCTION] 配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。 例如,Dockerfile 使用如下的内容创建了镜像 image-A: [...] ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src [...] 如果基于 image-A 创建新的镜像时,新的 Dockerfile 中使用 FROM image-A 指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。 # Automatically run the following ADD . /app/src RUN /usr/local/bin/python-build --dir /app/src 使用ONBUILD指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild。 7.13 Dockerfile Examples # Nginx # # VERSION 0.0.1 FROM ubuntu MAINTAINER Victor Vieux <victor@docker.com> RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server # Firefox over VNC # # VERSION 0.3 FROM ubuntu # Install vnc, xvfb in order to create a 'fake' display and firefox RUN apt-get update && apt-get install -y x11vnc xvfb firefox RUN mkdir ~/.vnc # Setup a password RUN x11vnc -storepasswd 1234 ~/.vnc/passwd # Autostart firefox (might not be the best way, but it does the trick) RUN bash -c 'echo "firefox" >> /.bashrc' EXPOSE 5900 CMD ["x11vnc", "-forever", "-usepw", "-create"] # Multiple images example # # VERSION 0.1 FROM ubuntu RUN echo foo > bar # Will output something like ===> 907ad6c2736f FROM ubuntu RUN echo moo > oink # Will output something like ===> 695d7793cbe4 # Youll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with # /oink. 7.14 docker build $ docker build --help Usage: docker build [OPTIONS] PATH | URL | - Build a new image from the source code at PATH --force-rm=false Always remove intermediate containers, even after unsuccessful builds # 移除过渡容器,即使构建失败 --no-cache=false Do not use cache when building the image # 不实用 cache -q, --quiet=false Suppress the verbose output generated by the containers --rm=true Remove intermediate containers after a successful build # 构建成功后移除过渡层容器 -t, --tag="" Repository name (and optionally a tag) to be applied to the resulting image in case of success 参考文档:Dockerfile Reference 7.15 dockerfile 最佳实践 使用.dockerignore文件 为了在docker build过程中更快上传和更加高效,应该使用一个.dockerignore文件用来排除构建镜像时不需要的文件或目录。例如,除非.git在构建过程中需要用到,否则你应该将它添加到.dockerignore文件中,这样可以节省很多时间。 避免安装不必要的软件包 为了降低复杂性、依赖性、文件大小以及构建时间,应该避免安装额外的或不必要的包。例如,不需要在一个数据库镜像中安装一个文本编辑器。 每个容器都跑一个进程 在大多数情况下,一个容器应该只单独跑一个程序。解耦应用到多个容器使其更容易横向扩展和重用。如果一个服务依赖另外一个服务,可以参考 Linking Containers Together。 最小化层 我们知道每执行一个指令,都会有一次镜像的提交,镜像是分层的结构,对于Dockerfile,应该找到可读性和最小化层之间的平衡。 多行参数排序 如果可能,通过字母顺序来排序,这样可以避免安装包的重复并且更容易更新列表,另外可读性也会更强,添加一个空行使用\换行: RUN apt-get update && apt-get install -y \ bzr \ cvs \ git \ mercurial \ subversion 创建缓存 镜像构建过程中会按照Dockerfile的顺序依次执行,每执行一次指令 Docker 会寻找是否有存在的镜像缓存可复用,如果没有则创建新的镜像。如果不想使用缓存,则可以在docker build时添加--no-cache=true选项。 从基础镜像开始就已经在缓存中了,下一个指令会对比所有的子镜像寻找是否执行相同的指令,如果没有则缓存失效。在大多数情况下只对比Dockerfile指令和子镜像就足够了。ADD和COPY指令除外,执行ADD和COPY时存放到镜像的文件也是需要检查的,完成一个文件的校验之后再利用这个校验在缓存中查找,如果检测的文件改变则缓存失效。RUN apt-get -y update命令只检查命令是否匹配,如果匹配就不会再执行更新了。 为了有效地利用缓存,你需要保持你的 Dockerfile 一致,并且尽量在末尾修改。 Dockerfile 指令 FROM: 只要可能就使用官方镜像库作为基础镜像 RUN: 为保持可读性、方便理解、可维护性,把长或者复杂的RUN语句使用\分隔符分成多行 不建议RUN apt-get update独立成行,否则如果后续包有更新,那么也不会再执行更新 避免使用RUN apt-get upgrade或者dist-upgrade,很多必要的包在一个非privileged权限的容器里是无法升级的。如果知道某个包更新,使用apt-get install -y xxx 标准写法 RUN apt-get update && apt-get install -y package-bar package-foo 例子: RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ btrfs-tools \ build-essential \ curl \ dpkg-sig \ git \ iptables \ libapparmor-dev \ libcap-dev \ libsqlite3-dev \ lxc=1.0* \ mercurial \ parallel \ reprepro \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.0* CMD: 推荐使用CMD [“executable”, “param1”, “param2”…]这种格式,CMD [“param”, “param”]则配合ENTRYPOINT使用 EXPOSE: Dockerfile 指定要公开的端口,使用docker run时指定映射到宿主机的端口即可 ENV: 为了使新的软件更容易运行,可以使用ENV更新PATH变量。如ENV PATH /usr/local/nginx/bin:$PATH确保CMD ["nginx"]即可运行 ENV也可以这样定义变量: ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && … ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH ADDorCOPY:ADD比COPY多一些特性「tar 文件自动解包和支持远程 URL」,不推荐添加远程 URL 如不推荐这种方式: ADD http://example.com/big.tar.xz /usr/src/things/ RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things RUN make -C /usr/src/things all 推荐使用 curl 或者 wget 替换,使用如下方式: RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.gz \ | tar -xJC /usr/src/things \ && make -C /usr/src/things all 如果不需要添加 tar 文件,推荐使用COPY。 参考文档: Best practices for writing Dockerfiles Dockerfile最佳实践(一) Dockerfile最佳实践(二) 八、容器数据管理 docker管理数据的方式有两种: 数据卷 数据卷容器 8.1 数据卷 数据卷是一个或多个容器专门指定绕过Union File System的目录,为持续性或共享数据提供一些有用的功能: 数据卷可以在容器间共享和重用 数据卷数据改变是直接修改的 数据卷数据改变不会被包括在容器中 数据卷是持续性的,直到没有容器使用它们 添加一个数据卷 你可以使用-v选项添加一个数据卷,或者可以使用多次-v选项为一个 docker 容器运行挂载多个数据卷。 $ sudo docker run --name data -v /data -t -i ubuntu:14.04 /bin/bash # 创建数据卷绑定到到新建容器,新建容器中会创建 /data 数据卷 bash-4.1# ls -ld /data/ drwxr-xr-x 2 root root 4096 Jul 23 06:59 /data/ bash-4.1# df -Th Filesystem Type Size Used Avail Use% Mounted on ... ... ext4 91G 4.6G 82G 6% /data 创建的数据卷可以通过docker inspect获取宿主机对应路径 $ sudo docker inspect data ... ... "Volumes": { "/data": "/var/lib/docker/vfs/dir/151de401d268226f96d824fdf444e77a4500aed74c495de5980c807a2ffb7ea9" }, # 可以看到创建的数据卷宿主机路径 ... ... 或者直接指定获取 $ sudo docker inspect --format="` `.`Volumes `" data map[/data: /var/lib/docker/vfs/dir/151de401d268226f96d824fdf444e77a4500aed74c495de5980c807a2ffb7ea9] 挂载宿主机目录为一个数据卷 -v选项除了可以创建卷,也可以挂载当前主机的一个目录到容器中。 $ sudo docker run --name web -v /source/:/web -t -i ubuntu:14.04 /bin/bash bash-4.1# ls -ld /web/ drwxr-xr-x 2 root root 4096 Jul 23 06:59 /web/ bash-4.1# df -Th ... ... ext4 91G 4.6G 82G 6% /web bash-4.1# exit 默认挂载卷是可读写的,可以在挂载时指定只读 $ sudo docker run --rm --name test -v /source/:/test:ro -t -i ubuntu:14.04 /bin/bash 8.2 创建和挂载一个数据卷容器 如果你有一些持久性的数据并且想在容器间共享,或者想用在非持久性的容器上,最好的方法是创建一个数据卷容器,然后从此容器上挂载数据。 创建数据卷容器 $ sudo docker run -t -i -d -v /test --name test ubuntu:14.04 echo hello 使用--volumes-from选项在另一个容器中挂载 /test 卷。不管 test 容器是否运行,其它容器都可以挂载该容器数据卷,当然如果只是单独的数据卷是没必要运行容器的。 $ sudo docker run -t -i -d --volumes-from test --name test1 ubuntu:14.04 /bin/bash 添加另一个容器 $ sudo docker run -t -i -d --volumes-from test --name test2 ubuntu:14.04 /bin/bash 也可以继承其它挂载有 /test 卷的容器 $ sudo docker run -t -i -d --volumes-from test1 --name test3 ubuntu:14.04 /bin/bash 8.3 备份、恢复或迁移数据卷 备份 $ sudo docker run --rm --volumes-from test -v $(pwd):/backup ubuntu:14.04 tar cvf /backup/test.tar /test tar: Removing leading `/' from member names /test/ /test/b /test/d /test/c /test/a 启动一个新的容器并且从test容器中挂载卷,然后挂载当前目录到容器中为 backup,并备份 test 卷中所有的数据为 test.tar,执行完成之后删除容器--rm,此时备份就在当前的目录下,名为test.tar。 $ ls # 宿主机当前目录下产生了 test 卷的备份文件 test.tar test.tar 恢复 你可以恢复给同一个容器或者另外的容器,新建容器并解压备份文件到新的容器数据卷 $ sudo docker run -t -i -d -v /test --name test4 ubuntu:14.04 /bin/bash $ sudo docker run --rm --volumes-from test4 -v $(pwd):/backup ubuntu:14.04 tar xvf /backup/test.tar -C / # 恢复之前的文件到新建卷中,执行完后自动删除容器 test/ test/b test/d test/c test/a 8.4 删除 Volumes Volume 只有在下列情况下才能被删除: docker rm -v删除容器时添加了-v选项 docker run --rm运行容器时添加了--rm选项 否则,会在/var/lib/docker/vfs/dir目录中遗留很多不明目录。 参考文档: Managing Data in Containers 深入理解Docker Volume(一) 深入理解Docker Volume(二) 九、链接容器 docker 允许把多个容器连接在一起,相互交互信息。docker 链接会创建一种容器父子级别的关系,其中父容器可以看到其子容器提供的信息。 9.1 容器命名 在创建容器时,如果不指定容器的名字,则默认会自动创建一个名字,这里推荐给容器命名: 1、给容器命名方便记忆,如命名运行 web 应用的容器为 web 2、为 docker 容器提供一个参考,允许方便其他容器调用,如把容器 web 链接到容器 db 可以通过--name选项给容器自定义命名: $ sudo docker run -d -t -i --name test ubuntu:14.04 bash $ sudo docker inspect --format="` `.`Nmae `" test /test 注:容器名称必须唯一,即你只能命名一个叫test的容器。如果你想复用容器名,则必须在创建新的容器前通过docker rm删除旧的容器或者创建容器时添加--rm选项。 9.2 链接容器 链接允许容器间安全通信,使用--link选项创建链接。 $ sudo docker run -d --name db training/postgres 基于 training/postgres 镜像创建一个名为 db 的容器,然后下面创建一个叫做 web 的容器,并且将它与 db 相互连接在一起 $ sudo docker run -d -P --name web --link db:db training/webapp python app.py --link <name or id>:alias选项指定链接到的容器。 查看 web 容器的链接关系: $ sudo docker inspect -f "` `.`HostConfig`.`Links `" web [/db:/web/db] 可以看到 web 容器被链接到 db 容器为/web/db,这允许 web 容器访问 db 容器的信息。 容器之间的链接实际做了什么?一个链接允许一个源容器提供信息访问给一个接收容器。在本例中,web 容器作为一个接收者,允许访问源容器 db 的相关服务信息。Docker 创建了一个安全隧道而不需要对外公开任何端口给外部容器,因此不需要在创建容器的时候添加-p或-P指定对外公开的端口,这也是链接容器的最大好处,本例为 PostgreSQL 数据库。 Docker 主要通过以下两个方式提供连接信息给接收容器: 环境变量 更新/etc/hosts文件 环境变量 当两个容器链接,Docker 会在目标容器上设置一些环境变量,以获取源容器的相关信息。 首先,Docker 会在每个通过--link选项指定别名的目标容器上设置一个<alias>_NAME环境变量。如果一个名为 web 的容器通过--link db:webdb被链接到一个名为 db 的数据库容器,那么 web 容器上会设置一个环境变量为WEBDB_NAME=/web/webdb. 以之前的为例,Docker 还会设置端口变量: $ sudo 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_5432_TCP=tcp://172.17.0.5:5432 # <name>_PORT_<port>_<protocol> 协议可以是 TCP 或 UDP DB_PORT_5432_TCP_PROTO=tcp DB_PORT_5432_TCP_PORT=5432 DB_PORT_5432_TCP_ADDR=172.17.0.5 . . . 注:这些环境变量只设置给容器中的第一个进程,类似一些守护进程 (如 sshd ) 当他们派生 shells 时会清除这些变量 更新/etc/hosts文件 除了环境变量,Docker 会在目标容器上添加相关主机条目到/etc/hosts中,上例中就是 web 容器。 $ sudo 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 /etc/host文件在源容器被重启之后会自动更新 IP 地址,而环境变量中的 IP 地址则不会自动更新的。 十、构建私有库 Docker 官方提供了 docker registry 的构建方法 docker-registry 10.1 快速构建 快速构建 docker registry 通过以下两步: 安装 docker 运行 registry:docker run -p 5000:5000 registry 这种方法通过 Docker hub 使用官方镜像 official image from the Docker hub 10.2 不使用容器构建 registry 安装必要的软件 $ sudo apt-get install build-essential python-dev libevent-dev python-pip liblzma-dev 配置 docker-registry sudo pip install docker-registry 或者 使用 github clone 手动安装 $ git clone https://github.com/dotcloud/docker-registry.git $ cd docker-registry/ $ cp config/config_sample.yml config/config.yml $ mkdir /data/registry -p $ pip install . 运行 docker-registry 高级启动方式 [不推荐] 使用gunicorn控制: gunicorn -c contrib/gunicorn_config.py docker_registry.wsgi:application 或者对外监听开放 gunicorn --access-logfile - --error-logfile - -k gevent -b 0.0.0.0:5000 -w 4 --max-requests 100 docker_registry.wsgi:application 10.3 提交指定容器到私有库 $ docker tag ubuntu:12.04 私有库IP:5000/ubuntu:12.04 $ docker push 私有库IP:5000/ubuntu 更多的配置选项推荐阅读官方文档: Docker-Registry README Docker-Registry advanced use 本文转自 运维小当家 51CTO博客,原文链接:http://blog.51cto.com/solin/1886455,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

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

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册