<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>My Blog</title><link>/</link><description>Recent content on My Blog</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sun, 01 May 2022 00:00:00 +0000</lastBuildDate><atom:link href="/index.xml" rel="self" type="application/rss+xml"/><item><title>Interactions between Pod priority and quality of service(Qos)</title><link>/2022/05/01/interactions-between-pod-priority-and-quality-of-serviceqos/</link><pubDate>Sun, 01 May 2022 00:00:00 +0000</pubDate><guid>/2022/05/01/interactions-between-pod-priority-and-quality-of-serviceqos/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="pod-优先级和服务质量之间的相互作用"&gt;Pod 优先级和服务质量之间的相互作用&lt;/h2&gt;
&lt;p&gt;集群中为了保障核心服务正常运行，有时候会舍弃掉不那么核心的服务。在通过Kubernetes实现的时候发现有两个手段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置pod的服务质量。Guaranteed, Burstable, BestEffort&lt;/li&gt;
&lt;li&gt;设置pod的优先级&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可是当这两个发生冲突的时候，优先考虑谁呢？比如现在这种情况：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;在资源不够用的情况下，一个高优先级，但是Qos属于Burstable或BestEffort的pod会抢占，低优先级但Qos属于Guaranteed的pod吗？
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;挠头不？&lt;/p&gt;
&lt;p&gt;进过查找资料，发现官方给出的答案是这样的：&lt;/p&gt;
&lt;p&gt;&lt;img alt="01" loading="lazy" src="/2022/05/01/interactions-between-pod-priority-and-quality-of-serviceqos/01.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="02" loading="lazy" src="/2022/05/01/interactions-between-pod-priority-and-quality-of-serviceqos/02.png"&gt;&lt;/p&gt;
&lt;p&gt;其实这里是要分情况来看，简单来说是两个环节：&lt;strong&gt;pod调度环节&lt;/strong&gt; 和 &lt;strong&gt;节点压力驱逐环节&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="pod调度环节"&gt;pod调度环节&lt;/h3&gt;
&lt;p&gt;这里有最核心的一句话。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;The scheduler&amp;#39;s preemption logic does not consider QoS when choosing preemption targets.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调度器的抢占逻辑在选择抢占目标时不考虑 QoS。只看pod的优先级，所以无论Guaranteed, Burstable, BestEffort，只要你的优先级最低，并且能腾出空间给新的pod运行，那很可能就是你了。&lt;/p&gt;
&lt;h3 id="节点压力驱逐环节"&gt;节点压力驱逐环节&lt;/h3&gt;
&lt;p&gt;而对于节点压力驱逐环节可能会繁琐一点&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;As a result, kubelet ranks and evicts pods in the following order:
BestEffort or Burstable pods where the usage exceeds requests. These pods are evicted based on their Priority and then by how much their usage level exceeds the request.
Guaranteed pods and Burstable pods where the usage is less than requests are evicted last, based on their Priority.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;第一条：usage exceeds requests&lt;/p&gt;</description></item><item><title>kubernetes常见问题</title><link>/2021/05/01/kubernetes%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/</link><pubDate>Sat, 01 May 2021 00:00:00 +0000</pubDate><guid>/2021/05/01/kubernetes%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="rancher平台使用问题记录"&gt;Rancher平台使用问题记录&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;平台问题&lt;/th&gt;
&lt;th style="text-align: left"&gt;详细说明&lt;/th&gt;
&lt;th style="text-align: left"&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;rancher与k8s&lt;br&gt;异常断连&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;br&gt;&lt;img alt="image" loading="lazy" src="/2021/05/01/kubernetes%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/image.png"&gt;&lt;br&gt;&lt;img alt="image-1" loading="lazy" src="/2021/05/01/kubernetes%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/image-1.png"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;1.在查看大量日志时出现过该问题&lt;br&gt;2.一些官方组织的交流群中建议优化 k8s系统参数，增加ip port range&lt;br&gt;&lt;br&gt;&lt;img alt="image-2" loading="lazy" src="/2021/05/01/kubernetes%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/image-2.png"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;k8s grpc-lb无效&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;方案有4种：&lt;br&gt;1、grpc-client + k8s headless&lt;br&gt;2、envoy (grpc proxy)&lt;br&gt;3、server mesh (istio/linkerd)&lt;br&gt;4、&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;rancher平台部署&lt;br&gt;节点亲和性&lt;br&gt;pod亲和性/反亲和性&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;删除deployment，对应service未删除&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;headless服务部署&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;节点选择/在页面打污点&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;k3s部署 指定reserve预留资源&lt;br&gt;agent 无法设置reserve？&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://docs.rancher.cn/docs/k3s/installation/install-options/server-config/_index/#agent-%E7%BD%91%E7%BB%9C"&gt;https://docs.rancher.cn/docs/k3s/installation/install-options/server-config/_index/#agent-%E7%BD%91%E7%BB%9C&lt;/a&gt;&lt;br&gt;&lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/"&gt;https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;zoomkeeper更新pod始终有一个没有update&lt;/td&gt;
&lt;td style="text-align: left"&gt;rollingUpdatePartition: 0 即可&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions"&gt;https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Kafka部署extralaccess&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;longhorn storage 在etcd恢复中是否可用？&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Kubernetes在在删除namespace后的恢复问题？&lt;/td&gt;
&lt;td style="text-align: left"&gt;1.通过etcd恢复所有的资源配置&lt;br /&gt;2.配置所有的storageclass创建pv的回收策略为：retain&lt;br /&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="开发人员使用需求汇总"&gt;开发人员使用需求汇总&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;需求内容&lt;/th&gt;
&lt;th style="text-align: left"&gt;详细说明&lt;/th&gt;
&lt;th style="text-align: left"&gt;时间&lt;/th&gt;
&lt;th style="text-align: left"&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;pod对cpu/memery资源可见性限制&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;pod支持绑定宿主机CPU核&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;api-gateway&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;nuclio pod 统一对外开放访问&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;有状态数据库服务更新手动确认&lt;/td&gt;
&lt;td style="text-align: left"&gt;更新要求：&lt;br /&gt;1.先只更新一个pod&lt;br /&gt;2.手动确认第一个pod运行正常之后再更新其他pod&lt;br /&gt;3.pod确认时间几分钟到数小时不等，无法使用readness进行确认&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions"&gt;https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="k3s-在混用dockercontainerd之后重启docker导致的pod-hostport模式中iptables路由混乱问题测试验证"&gt;k3s 在混用docker/containerd之后，重启docker导致的pod hostport模式中iptables路由混乱问题测试验证&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;hostport&lt;/strong&gt;添加分析&lt;/p&gt;</description></item><item><title>趴趴语法</title><link>/2021/04/10/%E8%B6%B4%E8%B6%B4%E8%AF%AD%E6%B3%95/</link><pubDate>Sat, 10 Apr 2021 00:00:00 +0000</pubDate><guid>/2021/04/10/%E8%B6%B4%E8%B6%B4%E8%AF%AD%E6%B3%95/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;</description></item><item><title>趴趴语法01-课程概述</title><link>/2021/04/10/%E8%B6%B4%E8%B6%B4%E8%AF%AD%E6%B3%9501-%E8%AF%BE%E7%A8%8B%E6%A6%82%E8%BF%B0/</link><pubDate>Sat, 10 Apr 2021 00:00:00 +0000</pubDate><guid>/2021/04/10/%E8%B6%B4%E8%B6%B4%E8%AF%AD%E6%B3%9501-%E8%AF%BE%E7%A8%8B%E6%A6%82%E8%BF%B0/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1课程概述"&gt;1.课程概述&lt;/h1&gt;
&lt;p&gt;语法: Grammar, rules, patterns&lt;/p&gt;
&lt;h2 id="11-语法的几要素"&gt;1.1 语法的几要素&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;词法：morphology&lt;/li&gt;
&lt;li&gt;句法：syntax&lt;/li&gt;
&lt;li&gt;语用学：pragmatics&lt;/li&gt;
&lt;li&gt;语义学：sematics&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="12-学习方法"&gt;1.2 学习方法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;语法规则&lt;/li&gt;
&lt;li&gt;借助母语，对比参考，first language&lt;/li&gt;
&lt;li&gt;术语&lt;/li&gt;
&lt;li&gt;development：语言历史，拉丁语/希腊语等。对比中文的文言文&lt;/li&gt;
&lt;li&gt;variation：地区不同，British English，American English，Canada English，aave&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="2名词"&gt;2.名词&lt;/h1&gt;
&lt;p&gt;noun&lt;/p&gt;
&lt;p&gt;名词内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;人&lt;/li&gt;
&lt;li&gt;物体&lt;/li&gt;
&lt;li&gt;地点&lt;/li&gt;
&lt;li&gt;抽象概念：快乐，&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="21名词"&gt;2.1名词&lt;/h2&gt;
&lt;p&gt;常见名词后缀, suffix&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;er&lt;/strong&gt;: teach &amp;ndash;&amp;gt; teacher&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;or&lt;/strong&gt;: educate &amp;ndash;&amp;gt; educator&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ist&lt;/strong&gt;: art &amp;ndash;&amp;gt; artist&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ion&lt;/strong&gt;: educate &amp;ndash;&amp;gt; education action&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ment&lt;/strong&gt;: improve &amp;ndash;&amp;gt; improvement movement&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ness&lt;/strong&gt;: happy &amp;ndash;&amp;gt; happiness loneliness&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ce&lt;/strong&gt;: important &amp;ndash;&amp;gt; importance differance&lt;/p&gt;
&lt;p&gt;名词分类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通名词：common
&lt;ul&gt;
&lt;li&gt;concrete/ abstract&lt;/li&gt;
&lt;li&gt;countable/ uncountable&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;专有名词：proper，首字母一定大写，无论位置。
&lt;ul&gt;
&lt;li&gt;名称，具体的人，地点&lt;/li&gt;
&lt;li&gt;月份，星期&lt;/li&gt;
&lt;li&gt;节日&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;countable&lt;/th&gt;
&lt;th&gt;uncountable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;裸奔&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;I have milk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a/an/ s&lt;/td&gt;
&lt;td&gt;I have a cat.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;some/any&lt;/td&gt;
&lt;td&gt;some/ any cats.&lt;/td&gt;
&lt;td&gt;some/any milk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;一些&lt;/td&gt;
&lt;td&gt;a few cats.&lt;/td&gt;
&lt;td&gt;a little milk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;许多&lt;/td&gt;
&lt;td&gt;a lot of cats.&lt;/td&gt;
&lt;td&gt;a lot of milk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;许多&lt;/td&gt;
&lt;td&gt;many cats.&lt;/td&gt;
&lt;td&gt;much milk.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Common&lt;/th&gt;
&lt;th&gt;Proper&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;person&lt;/td&gt;
&lt;td&gt;Alex&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;city&lt;/td&gt;
&lt;td&gt;Paris&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;book&lt;/td&gt;
&lt;td&gt;One Day&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;holiday&lt;/td&gt;
&lt;td&gt;Christmas&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;uncountable nouns 度量方式: 量词&lt;/p&gt;</description></item><item><title>k3s 安装</title><link>/2021/04/01/k3s-%E5%AE%89%E8%A3%85/</link><pubDate>Thu, 01 Apr 2021 00:00:00 +0000</pubDate><guid>/2021/04/01/k3s-%E5%AE%89%E8%A3%85/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="k3s-简介"&gt;k3s 简介&lt;/h1&gt;
&lt;p&gt;K3s 是一个轻量级的 Kubernetes 发行版，它针对边缘计算、物联网等场景进行了高度优化。由于运行 K3s 所需的资源相对较少，所以 K3s 也适用于开发和测试场景。&lt;/p&gt;
&lt;p&gt;部署起来非常简单，所有服务打包为单个二进制文件，同时启动程序自动处理了众多TLS认证的内容，存储也是用轻量级数据库或者内置etcd等。&lt;/p&gt;
&lt;h1 id="k3s安装"&gt;k3s安装&lt;/h1&gt;
&lt;h2 id="第一个master"&gt;第一个master&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# first master&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -sfL http://rancher-mirror.cnrancher.com/k3s/k3s-install.sh | &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;K3S_TOKEN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;262c73d1ac2e7a8179c861a8a47640f8&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INSTALL_K3S_EXEC&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;server --cluster-init&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INSTALL_K3S_MIRROR&lt;span style="color:#f92672"&gt;=&lt;/span&gt;cn &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INSTALL_K3S_VERSION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;v1.20.4+k3s1 sh -s -
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# check&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl status k3s.service
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="第二个master"&gt;第二个master&lt;/h2&gt;
&lt;p&gt;只需要在第一个master的基础上，指定第一个master的IP &lt;code&gt;K3S_URL=&amp;quot;https://${first_master_ip}:6443&amp;quot;&lt;/code&gt; ，同时去掉初始化选项 &lt;code&gt;--cluster-init&lt;/code&gt; 即可。如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# other master&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;first_master_ip&lt;span style="color:#f92672"&gt;=&lt;/span&gt;172.26.186.176
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -sfL http://rancher-mirror.cnrancher.com/k3s/k3s-install.sh | &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;K3S_TOKEN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;262c73d1ac2e7a8179c861a8a47640f8&amp;#39;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;K3S_URL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;https://&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;first_master_ip&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:6443&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INSTALL_K3S_EXEC&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;server&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INSTALL_K3S_MIRROR&lt;span style="color:#f92672"&gt;=&lt;/span&gt;cn &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INSTALL_K3S_VERSION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;v1.20.4+k3s1 sh -s -
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# check&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl status k3s.service
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="node节点"&gt;node节点&lt;/h2&gt;
&lt;p&gt;计算节点再去掉 &lt;code&gt;INSTALL_K3S_EXEC&lt;/code&gt; 参数，直接去加入集群即可&lt;/p&gt;</description></item><item><title>Pritunl安装使用</title><link>/2021/01/01/pritunl%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8/</link><pubDate>Fri, 01 Jan 2021 00:00:00 +0000</pubDate><guid>/2021/01/01/pritunl%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="pritunl-安装"&gt;Pritunl 安装&lt;/h1&gt;
&lt;p&gt;Pritunl 是一款VPN server，可以方便的搭建和管理，下面介绍一下如何在ubuntu中安装pritunl。&lt;/p&gt;
&lt;h2 id="mongodb安装"&gt;mongodb安装&lt;/h2&gt;
&lt;p&gt;Pritunl 默认使用mongodb保存数据。安装过程就不详细介绍了，直接上代码&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# mongodb 4
curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
echo &amp;#34;deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse&amp;#34; | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
sudo apt install mongodb-org
sudo systemctl start mongod.service
sudo systemctl status mongod
# mongodb 5
sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list &amp;lt;&amp;lt; EOF
deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/5.0 multiverse
EOF
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add -
sudo apt update
# WireGuard server support
sudo apt -y install wireguard wireguard-tools
sudo ufw disable
sudo apt -y install mongodb-org
sudo systemctl enable mongod
sudo systemctl start mongod
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="pritunl"&gt;pritunl&lt;/h2&gt;
&lt;p&gt;apt安装过程&lt;/p&gt;</description></item><item><title>容器核心技术</title><link>/2021/01/01/%E5%AE%B9%E5%99%A8%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF/</link><pubDate>Fri, 01 Jan 2021 00:00:00 +0000</pubDate><guid>/2021/01/01/%E5%AE%B9%E5%99%A8%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1容器的重要特征"&gt;1.容器的重要特征&lt;/h1&gt;
&lt;p&gt;随着互联网和软件技术的发展，容器技术越来越为大家所熟知和使用，那么容器技术到底好在哪里呢？ 主要是他有如下四个&lt;strong&gt;重要特征&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;隔离性：基于Linux kernel提供的 namespace资源隔离方案。&lt;/p&gt;
&lt;p&gt;安全性：资源隔离，资源访问自然受到了严格的限制，因此同时也把安全问题解决了&lt;/p&gt;
&lt;p&gt;便捷性：相对于虚拟机技术，容器技术启动速度非常快&lt;/p&gt;
&lt;p&gt;可配额：基于Linux 的Cgroups 即：Control Groups，可以对一个或一组资源控制和监控&lt;/p&gt;
&lt;h2 id="docker优势"&gt;docker优势&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;资源利用率高&lt;/li&gt;
&lt;li&gt;启动快&lt;/li&gt;
&lt;li&gt;运行环境一致
&lt;ul&gt;
&lt;li&gt;便于持续交付和部署&lt;/li&gt;
&lt;li&gt;便于迁移&lt;/li&gt;
&lt;li&gt;便于维护和扩展&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;​&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="docker劣势"&gt;docker劣势&lt;/h3&gt;
&lt;h1 id="2-linux-namespace"&gt;2 Linux namespace&lt;/h1&gt;
&lt;p&gt;Linux Namespace是Linux提供的一种内核级别环境隔离的方法。提供了对UTS、IPC、mount、PID、network、User等的隔离机制。&lt;/p&gt;
&lt;p&gt;&lt;img alt="linux_namespace" loading="lazy" src="/2021/01/01/%E5%AE%B9%E5%99%A8%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF/linux_namespace.png"&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分类&lt;/th&gt;
&lt;th&gt;系统调用参数&lt;/th&gt;
&lt;th&gt;相关内核版本&lt;/th&gt;
&lt;th&gt;隔离内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mount namespaces&lt;/td&gt;
&lt;td&gt;CLONE_NEWNS&lt;/td&gt;
&lt;td&gt;Linux 2.4.19&lt;/td&gt;
&lt;td&gt;挂载点（文件系统）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UTS namespaces&lt;/td&gt;
&lt;td&gt;CLONE_NEWUTS&lt;/td&gt;
&lt;td&gt;Linux 2.6.19&lt;/td&gt;
&lt;td&gt;主机名与域名,影响uname(hostname, domainname)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IPC namespaces&lt;/td&gt;
&lt;td&gt;CLONE_NEWIPC&lt;/td&gt;
&lt;td&gt;Linux 2.6.19&lt;/td&gt;
&lt;td&gt;信号量、消息队列和共享内存, inter-process communication，有全局id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PID namespaces&lt;/td&gt;
&lt;td&gt;CLONE_NEWPID&lt;/td&gt;
&lt;td&gt;Linux 2.6.24&lt;/td&gt;
&lt;td&gt;进程编号&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network namespaces&lt;/td&gt;
&lt;td&gt;CLONE_NEWNET&lt;/td&gt;
&lt;td&gt;Linux 2.6.29&lt;/td&gt;
&lt;td&gt;网络设备、网络栈、端口等等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User namespaces&lt;/td&gt;
&lt;td&gt;CLONE_NEWUSER&lt;/td&gt;
&lt;td&gt;Linux 3.8&lt;/td&gt;
&lt;td&gt;用户和用户组&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="三个系统调用"&gt;三个系统调用&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;调用&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;clone()&lt;/td&gt;
&lt;td&gt;实现线程的系统调用，用来创建一个新的进程，并可以通过设计上述参数达到隔离。 创建时传入 flags参数，可选值有 CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWUTS, CLONE_NEWUSER， 分别对应上面六种namespace。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unshare()&lt;/td&gt;
&lt;td&gt;使某进程脱离某个namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setns()&lt;/td&gt;
&lt;td&gt;把某进程加入到某个namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;常用操作&lt;/p&gt;</description></item><item><title>Windows10 Cordova环境搭建打包Android安装包</title><link>/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/</link><pubDate>Mon, 10 Feb 2020 00:00:00 +0000</pubDate><guid>/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;之前写了&lt;strong&gt;Vue项目打包成Android和iOS安装包&lt;/strong&gt;，不过那是基于Linux的环境。最近疫情紧张，都在家里远程办公，,于是整理了Windows10下Cordova环境搭建以及打包Android安装包。&lt;/p&gt;
&lt;h3 id="cordova环境依赖"&gt;Cordova环境依赖:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;win10系统&lt;/li&gt;
&lt;li&gt;Node环境&lt;/li&gt;
&lt;li&gt;Java环境&lt;/li&gt;
&lt;li&gt;AndroidStudio&lt;/li&gt;
&lt;li&gt;Ant&lt;/li&gt;
&lt;li&gt;Gradle&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="安装node环境"&gt;安装node环境:&lt;/h4&gt;
&lt;p&gt;1.使用node官网网址下载node包，最好使用稳定版本。https://nodejs.org/&lt;/p&gt;
&lt;p&gt;&lt;img alt="js_download_page" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/js_download_page.png"&gt;&lt;/p&gt;
&lt;p&gt;2.一路安装next，然后在CMD中使用命令查看node版本&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;node -v
npm -v
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt="js_npm_version" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/js_npm_version.png"&gt;&lt;/p&gt;
&lt;h4 id="java环境配置"&gt;Java环境配置：&lt;/h4&gt;
&lt;p&gt;jdk8下载地址：&lt;a href="https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html"&gt;https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Windows 10 就下载 &lt;strong&gt;jdk-8u241-windows-x64.exe&lt;/strong&gt;这个文件。&lt;/p&gt;
&lt;p&gt;记住安装地址，我是安装在D盘下&lt;/p&gt;
&lt;p&gt;&lt;img alt="jdk_install_path" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/jdk_install_path.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步&lt;/strong&gt;：在桌面上依次 右键单击计算机—属性—高级系统设置—环境变量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步&lt;/strong&gt;：新建一个名为JAVA_HOME的系统变量，第二栏的值即为你自己jdk的安装路径，这里的是我的，自己的依照自己的情况来。&lt;/p&gt;
&lt;p&gt;&lt;img alt="java_home" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/java_home.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步&lt;/strong&gt;：新建一个名为CLASSPATH的系统变量，在第二栏一字不差地输入 %JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar。看下一个图。&lt;/p&gt;
&lt;p&gt;第四步：找到一个叫PATH的系统变量，双击其编辑，在最后加上下图中圈出的语句，然后确定。&lt;/p&gt;
&lt;p&gt;&lt;img alt="java_path" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/java_path.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第五步&lt;/strong&gt;：使用CMD检查，使用javac命令，配置成功则出现以下&lt;/p&gt;
&lt;p&gt;&lt;img alt="javav" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/javav.png"&gt;&lt;/p&gt;
&lt;h4 id="安装安卓sdk"&gt;安装安卓SDK:&lt;/h4&gt;
&lt;p&gt;第一步：推荐安装AndroidStudio，会自动配置SDK以及SDK-tools等相关的，可以在编辑器里选择要下载使用的SDK版本，安装好后，仿照配置Java环境的方式配置ANDROID_HOME。&lt;/p&gt;
&lt;p&gt;安装AS：&lt;/p&gt;
&lt;p&gt;&lt;img alt="android-studio-install" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/android-studio-install.png"&gt;&lt;/p&gt;
&lt;p&gt;安装SDK：&lt;/p&gt;
&lt;p&gt;&lt;img alt="sdk-install" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/sdk-install.png"&gt;&lt;/p&gt;
&lt;p&gt;SDK Tools：&lt;/p&gt;
&lt;p&gt;&lt;img alt="sdk-tools" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/sdk-tools.png"&gt;&lt;/p&gt;
&lt;p&gt;如果你无法访问Google，那么恭喜你，估计你很难完整的下载到SDK。下面告诉你如何解决：&lt;/p&gt;
&lt;p&gt;修改DNS，进入网站&lt;a href="http://ping.chinaz.com/"&gt;http://ping.chinaz.com/&lt;/a&gt;，进行 &lt;code&gt;dl.google.com&lt;/code&gt; ping检查，选择大陆响应时间最短的IP地址，用这个IP添加到host里面，hosts文件地址：&lt;code&gt;C:\WINDOWS\System32\drivers\etc\hosts&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="ping-dl-google-com" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/ping-dl-google-com.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="host-dl-google-com" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/host-dl-google-com.png"&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;203.208.41.41 dl.google.com
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;第二步：配置其他安卓环境变量，也是在Path中进行配置&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ANDROID_HOME C:\Users\alex\AppData\Local\Android\Sdk
%ANDROID_HOME%\build-tools\29.0.3
%ANDROID_HOME%\platform-tools
%ANDROID_HOME%\tools
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt="android-home" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/android-home.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：在配置&lt;code&gt;build-tools&lt;/code&gt;的时候，版本号不要搞错了。随着你安装时间的不同，这个版本号会不一样。&lt;/p&gt;
&lt;p&gt;&lt;img alt="sdk-build-version" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/sdk-build-version.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="android-path" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/android-path.png"&gt;&lt;/p&gt;
&lt;p&gt;第三步：终端中输入adb，出现下图表示成功&lt;/p&gt;
&lt;p&gt;&lt;img alt="adb-version" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/adb-version.png"&gt;&lt;/p&gt;
&lt;h4 id="安装ant"&gt;安装Ant&lt;/h4&gt;
&lt;p&gt;第一步：https://ant.apache.org/bindownload.cgi 进行安装，选择1.10.*版本的，该版本对应的JDK8。&lt;/p&gt;
&lt;p&gt;&lt;img alt="ant" loading="lazy" src="/2020/02/10/windows10-cordova%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E6%89%93%E5%8C%85android%E5%AE%89%E8%A3%85%E5%8C%85/ant.png"&gt;&lt;/p&gt;
&lt;p&gt;解压目录：&lt;/p&gt;</description></item><item><title>Vue项目打包成Android和iOS安装包</title><link>/2019/10/13/vue%E9%A1%B9%E7%9B%AE%E6%89%93%E5%8C%85%E6%88%90android%E5%92%8Cios%E5%AE%89%E8%A3%85%E5%8C%85/</link><pubDate>Sun, 13 Oct 2019 00:00:00 +0000</pubDate><guid>/2019/10/13/vue%E9%A1%B9%E7%9B%AE%E6%89%93%E5%8C%85%E6%88%90android%E5%92%8Cios%E5%AE%89%E8%A3%85%E5%8C%85/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="准备环境配置"&gt;准备环境配置&lt;/h2&gt;
&lt;p&gt;准备环境需要安装&lt;code&gt;Android Sdk &lt;/code&gt; , &lt;code&gt;jdk&lt;/code&gt; , &lt;code&gt;Gradle&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="安装android-sdk"&gt;安装Android Sdk&lt;/h3&gt;
&lt;p&gt;Android Sdk 可以通过安装 android-studio， 然后再编辑器里面选择安装SDK即可。&lt;/p&gt;
&lt;p&gt;也可以通过sdk-tools来安装，这种方式无需界面，对于远程Linux的操作非常方便。下面介绍这种方式：&lt;/p&gt;
&lt;p&gt;下载sdk-tolls: &lt;a href="https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip"&gt;https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;解压到指定目录&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cd /home/alex/android-sdk
unzip sdk-tools-linux-4333796.zip
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;编辑 &lt;code&gt;~/.profile&lt;/code&gt; 配置环境变量&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;export ANDROID_HOME=/home/alex/android-sdk
export PATH=$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;sdkmanager &amp;ndash;list&lt;/strong&gt;即可查看所有已经安装的和未安装的packages，如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Installed packages:
Path | Version | Description | Location
------- | ------- | ------- | -------
build-tools;24.0.3 | 24.0.3 | Android SDK Build-Tools 24.0.3 | build-tools/24.0.3/
build-tools;25.0.3 | 25.0.3 | Android SDK Build-Tools 25.0.3 | build-tools/25.0.3/
emulator | 26.1.3 | Android Emulator | emulator/
extras;android;m2repository | 47.0.0 | Android Support Repository | extras/android/m2repository/
extras;google;m2repository | 57 | Google Repository | extras/google/m2repository/
extras;m2reposi...ut-solver;1.0.2 | 1 | Solver for ConstraintLayout 1.0.2 | extras/m2reposi...t-solver/1.0.2/
extras;m2reposi...nt-layout;1.0.2 | 1 | ConstraintLayout for Android 1... | extras/m2reposi...t-layout/1.0.2/
patcher;v4 | 1 | SDK Patch Applier v4 | patcher/v4/
platform-tools | 26.0.0 | Android SDK Platform-Tools 26 | platform-tools/
platforms;android-24 | 2 | Android SDK Platform 24 | platforms/android-24/
platforms;android-25 | 3 | Android SDK Platform 25 | platforms/android-25/
tools | 26.0.2 | Android SDK Tools | tools/
Available Packages:
Path | Version | Description
------- | ------- | -------
add-ons;addon-g..._apis-google-15 | 3 | Google APIs
add-ons;addon-g..._apis-google-16 | 4 | Google APIs
add-ons;addon-g..._apis-google-17 | 4 | Google APIs
add-ons;addon-g..._apis-google-18 | 4 | Google APIs
add-ons;addon-g..._apis-google-19 | 20 | Google APIs
add-ons;addon-g..._apis-google-21 | 1 | Google APIs
add-ons;addon-g..._apis-google-22 | 1 | Google APIs
add-ons;addon-g..._apis-google-23 | 1 | Google APIs
add-ons;addon-g..._apis-google-24 | 1 | Google APIs
add-ons;addon-g...e_gdk-google-19 | 11 | Glass Development Kit Preview
build-tools;19.1.0 | 19.1.0 | Android SDK Build-Tools 19.1
build-tools;20.0.0 | 20.0.0 | Android SDK Build-Tools 20
build-tools;21.1.2 | 21.1.2 | Android SDK Build-Tools 21.1.2
build-tools;22.0.1 | 22.0.1 | Android SDK Build-Tools 22.0.1
build-tools;23.0.1 | 23.0.1 | Android SDK Build-Tools 23.0.1
build-tools;23.0.2 | 23.0.2 | Android SDK Build-Tools 23.0.2
build-tools;23.0.3 | 23.0.3 | Android SDK Build-Tools 23.0.3
build-tools;24.0.0 | 24.0.0 | Android SDK Build-Tools 24
build-tools;24.0.1 | 24.0.1 | Android SDK Build-Tools 24.0.1
build-tools;24.0.2 | 24.0.2 | Android SDK Build-Tools 24.0.2
build-tools;24.0.3 | 24.0.3 | Android SDK Build-Tools 24.0.3
build-tools;25.0.0 | 25.0.0 | Android SDK Build-Tools 25
build-tools;25.0.1 | 25.0.1 | Android SDK Build-Tools 25.0.1
build-tools;25.0.2 | 25.0.2 | Android SDK Build-Tools 25.0.2
build-tools;25.0.3 | 25.0.3 | Android SDK Build-Tools 25.0.3
build-tools;26.0.0 | 26.0.0 | Android SDK Build-Tools 26
build-tools;26.0.1 | 26.0.1 | Android SDK Build-Tools 26.0.1
cmake;3.6.4111459 | 3.6.4111459 | CMake 3.6.4111459
docs | 1 | Documentation for Android SDK
emulator | 26.1.3 | Android Emulator
extras;android;gapid;1 | 1.0.3 | GPU Debugging tools
extras;android;gapid;3 | 3.1.0 | GPU Debugging tools
extras;android;m2repository | 47.0.0 | Android Support Repository
extras;google;auto | 1.1 | Android Auto Desktop Head Unit...
extras;google;g...e_play_services | 43 | Google Play services
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;下载地址&lt;/code&gt; &lt;code&gt;http://www.android-studio.org/&lt;/code&gt; 解压到/usr/local/目录下&lt;/li&gt;
&lt;li&gt;&lt;code&gt;运行&lt;/code&gt; 到android-studio文件夹下的bin &lt;code&gt;./studio.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;配置&lt;code&gt;Android SDK&lt;/code&gt;环境变量,&lt;code&gt;vim ~/.profile&lt;/code&gt;中加入下面代码&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;export ANDROID_HOME=$HOME/Android/Sdk (不存在请单独安装Android Sdk)
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;sdkmanager &amp;ndash;install&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>ubuntu18 命令行设置wireless 连接WiFi热点</title><link>/2019/04/25/ubuntu18-%E5%91%BD%E4%BB%A4%E8%A1%8C%E8%AE%BE%E7%BD%AEwireless-%E8%BF%9E%E6%8E%A5wifi%E7%83%AD%E7%82%B9/</link><pubDate>Thu, 25 Apr 2019 00:00:00 +0000</pubDate><guid>/2019/04/25/ubuntu18-%E5%91%BD%E4%BB%A4%E8%A1%8C%E8%AE%BE%E7%BD%AEwireless-%E8%BF%9E%E6%8E%A5wifi%E7%83%AD%E7%82%B9/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="nmcli"&gt;nmcli&lt;/h2&gt;
&lt;p&gt;查看可用WiFi&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ nmcli dev wifi
IN-USE SSID MODE CHAN RATE SIGNAL BARS SECURITY
HiWiFi_5489B6 Infra 3 130 Mbit/s 70 ▂▄▆_ WPA1 WPA2
PING Infra 11 195 Mbit/s 54 ▂▄__ WPA2
H3C Infra 1 270 Mbit/s 44 ▂▄__ WPA1 WPA2
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="rfkill"&gt;rfkill&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# rfkill list
0: bluedroid_pm: Bluetooth
Soft blocked: yes
Hard blocked: no
1: phy0: Wireless LAN
Soft blocked: no
Hard blocked: no
2: brcmfmac-wifi: Wireless LAN
Soft blocked: no
Hard blocked: no
# rfkill block 1
# rfkill block 2
# rfkill list
0: bluedroid_pm: Bluetooth
Soft blocked: yes
Hard blocked: no
1: phy0: Wireless LAN
Soft blocked: yes
Hard blocked: no
2: brcmfmac-wifi: Wireless LAN
Soft blocked: yes
Hard blocked: no
# rfkill unblock 1
# rfkill unblock 2
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="iw"&gt;iw&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.扫描可用的WiFi
# 不加less可能会产生太多输出
iw dev wlan0 scan |less
# 或者
iwlist wlan0 scanning
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="ifconfig"&gt;ifconfig&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# ifconfig wlan0
wlan0: flags=4098&amp;lt;BROADCAST,MULTICAST&amp;gt; mtu 1500
ether 00:04:4b:8c:55:d4 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 70 bytes 7620 (7.6 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# ifconfig wlan0 down
ip link set wlan0 down
# ifconfig wlan0 up
# ip link set wlan0 up
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;wpa&lt;/p&gt;</description></item><item><title>代理、VPN和内网穿透</title><link>/2019/04/25/%E4%BB%A3%E7%90%86vpn%E5%92%8C%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/</link><pubDate>Thu, 25 Apr 2019 00:00:00 +0000</pubDate><guid>/2019/04/25/%E4%BB%A3%E7%90%86vpn%E5%92%8C%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="代理"&gt;代理&lt;/h2&gt;
&lt;p&gt;代理的意思就是代替你处理。在这里指网络服务相关的代理，做代理的服务器就叫代理服务器。有一些东西你自己无法直接获取，而代理服务器能做，那这些事情就可以交给代理服务器去做。这就是代理服务的价值所在。比如公司限制内部电脑不能上网，需要上网的员工可以申请连接到代理服务器上，这样员工的电脑就可以上网了。于是公司达到了网络管控的效果。&lt;/p&gt;
&lt;p&gt;代理服务一般分为两种：正向代理和反向代理。唯一的差别在于代理服务器是作为客户端使用，还是作为服务端使用。&lt;/p&gt;
&lt;h3 id="正向代理"&gt;正向代理&lt;/h3&gt;
&lt;p&gt;正向代理中代理服务作为客户端，代替真实的客户端去访问服务。就比如前面提到的员工利用代理服务访问网络的例子。&lt;/p&gt;
&lt;p&gt;&lt;img alt="正向代理" loading="lazy" src="/2019/04/25/%E4%BB%A3%E7%90%86vpn%E5%92%8C%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/forward-proxy.png"&gt;&lt;/p&gt;
&lt;h3 id="反向代理"&gt;反向代理&lt;/h3&gt;
&lt;p&gt;反向代理中，代理服务代替真实的服务器来迎接客户端的请求，然后将请求转发给真实的服务器。这样可以很好的隐藏真实的服务器地址，只需要让客户端知道代理服务器地址即可。在这里代理服务器可以做很多过滤和限制从而达到更好的保护好服务器正常的提供服务。&lt;/p&gt;
&lt;p&gt;&lt;img alt="反向代理" loading="lazy" src="/2019/04/25/%E4%BB%A3%E7%90%86vpn%E5%92%8C%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/reverse-proxy.png"&gt;&lt;/p&gt;
&lt;h2 id="vpn"&gt;VPN&lt;/h2&gt;
&lt;h2 id="内网穿透"&gt;内网穿透&lt;/h2&gt;</description></item><item><title>Go语言的线程</title><link>/2019/04/06/go%E8%AF%AD%E8%A8%80%E7%9A%84%E7%BA%BF%E7%A8%8B/</link><pubDate>Sat, 06 Apr 2019 00:00:00 +0000</pubDate><guid>/2019/04/06/go%E8%AF%AD%E8%A8%80%E7%9A%84%E7%BA%BF%E7%A8%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="线程与锁"&gt;线程与锁&lt;/h2&gt;
&lt;p&gt;现代CPU一般含有多个核，并且一个核可能支持多线程。换句话说，现代CPU可以同时执行多条指令流水线。 为了将CPU的能力发挥到极致，我们常常需要使我们的程序支持并发（concurrent）计算。并发计算是指若干计算可能在某些时间片段内同时运行的情形。 在并行计算中，多个计算在任何时间点都在同时运行。并行计算属于特殊的并发计算。&lt;/p&gt;
&lt;p&gt;而并发编程会存在数据竞争（data race）的情况，在不同线程同时修改统一内存控制时。并发编程的一大任务就是要调度不同计算，控制它们对资源的访问时段，以使数据竞争的情况不会发生。 此任务常称为并发同步（或者数据同步）。&lt;/p&gt;
&lt;p&gt;锁是解决数据竞争的一种方法，在go语言中提供了sync包。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Mutex&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;互斥锁&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;RWMutex&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;读写分离锁&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WaitGroup&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;等待一组goroutine&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;返回&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Once&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;保证某段代码只执行一次&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Cond&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;让一组goroutine&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;在满足特定条件时被唤醒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="syncmutex"&gt;sync.Mutex&lt;/h3&gt;
&lt;p&gt;Lock()加锁，Unlock()解锁&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Mutex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;f1&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Lock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Unlock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;doSomething&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;f2&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Lock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;doSomething&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Unlock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="syncrwmutex"&gt;sync.RWMutex&lt;/h3&gt;
&lt;p&gt;简单来说：不限制并发读，只限制并发写和并发读写&lt;/p&gt;
&lt;p&gt;详细来说：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;一个RWMutex值常称为一个读写互斥锁，它的内部包含两个锁：一个写锁和一个读锁。 对于一个可寻址的RWMutex值rwm，数据写入者可以通过方法调用rwm.Lock()对rwm加写锁，或者通过rwm.RLock()方法调用对rwm加读锁。 方法调用rwm.Unlock()和rwm.RUnlock()用来解开rwm的写锁和读锁。 rwm的读锁维护着一个计数。当rwm.RLock()调用成功时，此计数增1；当rwm.Unlock()调用成功时，此计数减1； 一个零计数表示rwm的读锁处于未加锁状态；反之，一个非零计数（肯定大于零）表示rwm的读锁处于加锁状态。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;对于一个可寻址的RWMutex值rwm，下列规则存在：
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rwm的写锁只有在它的写锁和读锁都处于未加锁状态时才能被成功加锁。 换句话说，rwm的写锁在任何时刻最多只能被一个数据写入者成功加锁，并且rwm的写锁和读锁不能同时处于加锁状态。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;当rwm的写锁正处于加锁状态的时候，任何新的对之加写锁或者加读锁的操作试图都将导致当前协程进入阻塞状态，直到此写锁被解锁，这样的操作试图才有机会成功。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;当rwm的读锁正处于加锁状态的时候，新的加写锁的操作试图将导致当前协程进入阻塞状态。 但是，一个新的加读锁的操作试图将成功，只要此操作试图发生在任何被阻塞的加写锁的操作试图之前（见下一条规则）。 换句话说，一个读写互斥锁的读锁可以同时被多个数据读取者同时加锁而持有。 当rwm的读锁维护的计数清零时，读锁将返回未加锁状态。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;假设rwm的读锁正处于加锁状态的时候，为了防止后续数据写入者没有机会成功加写锁，后续发生在某个被阻塞的加写锁操作试图之后的所有加读锁的试图都将被阻塞。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;假设rwm的写锁正处于加锁状态的时候，（至少对于标准编译器来说，）为了防止后续数据读取者没有机会成功加读锁，发生在此写锁下一次被解锁之前的所有加读锁的试图都将在此写锁下一次被解锁之后肯定取得成功，即使所有这些加读锁的试图发生在一些仍被阻塞的加写锁的试图之后。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;后两条规则是为了确保数据读取者和写入者都有机会执行它们的操作。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;请注意：一个锁并不会绑定到一个协程上，即一个锁并不记录哪个协程成功地加锁了它。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;example&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;runtime&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;sync&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Counter&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;RWMutex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Counter&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;() &lt;span style="color:#66d9ef"&gt;uint64&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;RLock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;RUnlock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Counter&lt;/span&gt;) &lt;span style="color:#a6e22e"&gt;Increase&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;delta&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint64&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Lock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;delta&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;m&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Unlock&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Counter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &amp;lt; &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;go&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;k&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;k&lt;/span&gt; &amp;lt; &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;k&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Increase&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;() &amp;lt; &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;runtime&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Gosched&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;()) &lt;span style="color:#75715e"&gt;// 10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="syncwatigroup"&gt;sync.WatiGroup&lt;/h3&gt;
&lt;p&gt;每个sync.WaitGroup值在内部维护着一个计数，此计数的初始默认值为零。&lt;/p&gt;</description></item><item><title>Go基础使用</title><link>/2019/04/04/go%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/</link><pubDate>Thu, 04 Apr 2019 00:00:00 +0000</pubDate><guid>/2019/04/04/go%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="go控制语句"&gt;go控制语句&lt;/h2&gt;
&lt;h3 id="if"&gt;if&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;基本形式&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 大括号的左边必须跟在语句的后面&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;condition1&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// do something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;condition2&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// do something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;} &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// catch-all or default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;简短语句&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 处理完分号“;”前面的语句之后，再做判断&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v&lt;/span&gt;&lt;span style="color:#f92672"&gt;:=&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;x&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;v&lt;/span&gt;&amp;lt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// do something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="switch"&gt;switch&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;fallthrough: 划过执行下一个case&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;switch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;var1&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;value1&lt;/span&gt;: &lt;span style="color:#75715e"&gt;// do sth&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;value2&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fallthrough&lt;/span&gt; &lt;span style="color:#75715e"&gt;// do next case&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;value3&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// so sth&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="for"&gt;for&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// eg 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;:=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&amp;lt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;sum&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// eg 2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; ; &lt;span style="color:#a6e22e"&gt;sum&lt;/span&gt; &amp;lt; &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;sum&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sum&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// always loop&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// do something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;condition&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="for-range"&gt;for range&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;,&lt;span style="color:#a6e22e"&gt;char&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;myString&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// do something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// map&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;k&lt;/span&gt;,&lt;span style="color:#a6e22e"&gt;v&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;myMap&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// array or slice&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;index&lt;/span&gt;,&lt;span style="color:#a6e22e"&gt;value&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;myArray&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="数据结构"&gt;数据结构&lt;/h2&gt;
&lt;h3 id="variable"&gt;variable&lt;/h3&gt;
&lt;p&gt;var varName type&lt;/p&gt;</description></item><item><title>Go工具链</title><link>/2019/04/02/go%E5%B7%A5%E5%85%B7%E9%93%BE/</link><pubDate>Tue, 02 Apr 2019 00:00:00 +0000</pubDate><guid>/2019/04/02/go%E5%B7%A5%E5%85%B7%E9%93%BE/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="go"&gt;Go&lt;/h1&gt;
&lt;p&gt;为了从任意目录运行Go，安装目录下的&lt;code&gt;bin&lt;/code&gt;子目录路径必须配置在&lt;code&gt;PATH&lt;/code&gt;环境变量中。&lt;/p&gt;
&lt;h2 id="1go环境变量"&gt;1、Go环境变量&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GOPATH: 此环境变量的默认值为当前用户的HOME目录下的名为&lt;code&gt;go&lt;/code&gt;文件夹对应的目录路径。 GOPATH文件夹中的pkg子文件夹用来缓存被本地项目所依赖的Go模块（一个Go模块为若干Go库包的集合）的版本。src子文件夹用来存放源码。&lt;/li&gt;
&lt;li&gt;GOBIN: &lt;code&gt;GOBIN&lt;/code&gt;环境变量用来指定&lt;code&gt;go install&lt;/code&gt;子命令产生的Go应用程序二进制可执行文件应该存储在何处。 它的默认值为&lt;code&gt;GOPATH&lt;/code&gt;文件夹中的&lt;code&gt;bin&lt;/code&gt;子目录所对应的目录路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="2go子命令"&gt;2、Go子命令&lt;/h2&gt;
&lt;h3 id="go-run"&gt;go run&lt;/h3&gt;
&lt;p&gt;编译和运行main包中的go程序。比如有一个 example.go 的文件，只需要执行 &lt;code&gt;go run example.go&lt;/code&gt; 即可。&lt;/p&gt;
&lt;p&gt;如果程序的main包中有多个go源码文件，我们可以指定目录。例如：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ls
a.go b.go
$ go run ./
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="go-install"&gt;go install&lt;/h3&gt;
&lt;p&gt;编译和安装main包中的go程序，并不会执行，而是将可执行文件放入GOBIN指定的目录中。&lt;/p&gt;
&lt;p&gt;我们可以运行&lt;code&gt;go install example.com/program@latest&lt;/code&gt;来安装一个第三方Go程序的最新版本（至&lt;code&gt;GOBIIN&lt;/code&gt;目录）。&lt;/p&gt;
&lt;p&gt;在1.16版本之前，可以是用&lt;code&gt;go get -u example.com/program&lt;/code&gt; 来安装。&lt;/p&gt;
&lt;h3 id="go-build"&gt;go build&lt;/h3&gt;
&lt;p&gt;编译main包中的go程序，并不会安装，也不会执行。&lt;/p&gt;
&lt;h3 id="go-vet"&gt;go vet&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;go vet&lt;/code&gt;子命令可以用来检查可能的代码逻辑错误（即警告）。&lt;/p&gt;
&lt;h3 id="go-fmt"&gt;go fmt&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;go fmt&lt;/code&gt;子命令来用同一种代码风格格式化Go代码。&lt;/p&gt;
&lt;h3 id="go-test"&gt;go test&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;go test&lt;/code&gt;子命令来运行单元和基准测试用例。&lt;/p&gt;
&lt;h3 id="go-doc"&gt;go doc&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;go doc&lt;/code&gt;子命令用来（在终端中）查看Go代码库包的文档。&lt;/p&gt;
&lt;h3 id="go-mod-init"&gt;go mod init&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;go mod init &lt;/code&gt;命令可以用来在当前目录中生成一个&lt;code&gt;go.mod&lt;/code&gt;文件。此&lt;code&gt;go.mod&lt;/code&gt;文件将被用来记录当前项目需要的依赖模块和版本信息。&lt;/p&gt;
&lt;h3 id="go-mod-tidy"&gt;go mod tidy&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;go mod tidy&lt;/code&gt;命令用来通过扫描当前项目中的所有代码来添加未被记录的依赖至&lt;code&gt;go.mod&lt;/code&gt;文件或从&lt;code&gt;go.mod&lt;/code&gt;文件中删除不再被使用的依赖。&lt;/p&gt;</description></item><item><title>Go语言简洁</title><link>/2019/04/01/go%E8%AF%AD%E8%A8%80%E7%AE%80%E6%B4%81/</link><pubDate>Mon, 01 Apr 2019 00:00:00 +0000</pubDate><guid>/2019/04/01/go%E8%AF%AD%E8%A8%80%E7%AE%80%E6%B4%81/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="go语言简介"&gt;Go语言简介&lt;/h1&gt;
&lt;p&gt;Go是一门编译型和静态型的编程语言。&lt;/p&gt;
&lt;h2 id="1go语言卖点"&gt;1、Go语言卖点&lt;/h2&gt;
&lt;p&gt;1、做为一门静态语言，Go却和很多动态脚本语言一样得灵活&lt;/p&gt;
&lt;p&gt;2、节省内存、程序启动快和代码执行速度快&lt;/p&gt;
&lt;p&gt;3、内置并发编程支持&lt;/p&gt;
&lt;p&gt;4、良好的代码可读性，Go的语法很简洁并且和其它流行语言相似。&lt;/p&gt;
&lt;p&gt;5、良好的跨平台支持&lt;/p&gt;
&lt;p&gt;6、一个稳定的Go核心设计和开发团队以及一个活跃的社区&lt;/p&gt;
&lt;p&gt;7、Go拥有一个比较齐全的标准库&lt;/p&gt;
&lt;p&gt;和C家族语言相比有以下优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;程序编译时间短&lt;/li&gt;
&lt;li&gt;像动态语言一样灵活&lt;/li&gt;
&lt;li&gt;内置并发支持&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="2go语言特性"&gt;2、Go语言特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;内置并发编程支持：
&lt;ul&gt;
&lt;li&gt;使用协程（goroutine）做为基本的计算单元。轻松地创建协程。&lt;/li&gt;
&lt;li&gt;使用通道（channel）来实现协程间的同步和通信。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;内置了映射（map）和切片（slice）类型。&lt;/li&gt;
&lt;li&gt;支持多态（polymorphism）。&lt;/li&gt;
&lt;li&gt;使用接口（interface）来实现裝盒（value boxing）和反射（reflection）。&lt;/li&gt;
&lt;li&gt;支持指针。&lt;/li&gt;
&lt;li&gt;支持函数闭包（closure）。&lt;/li&gt;
&lt;li&gt;支持方法。&lt;/li&gt;
&lt;li&gt;支持延迟函数调用（defer）。&lt;/li&gt;
&lt;li&gt;支持类型内嵌（type embedding）。&lt;/li&gt;
&lt;li&gt;支持类型推断（type deduction or type inference）。&lt;/li&gt;
&lt;li&gt;内存安全。&lt;/li&gt;
&lt;li&gt;自动垃圾回收。&lt;/li&gt;
&lt;li&gt;良好的代码跨平台性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3开源go项目"&gt;3、开源Go项目&lt;/h2&gt;
&lt;p&gt;图片来源于极客时间&lt;img alt="GitHub_go_projects" loading="lazy" src="/2019/04/01/go%E8%AF%AD%E8%A8%80%E7%AE%80%E6%B4%81/GitHub_go_projects.png"&gt;&lt;/p&gt;</description></item><item><title>Ceph学习笔记-03-luminous版本部署</title><link>/2019/03/13/ceph%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-03-luminous%E7%89%88%E6%9C%AC%E9%83%A8%E7%BD%B2/</link><pubDate>Wed, 13 Mar 2019 00:00:00 +0000</pubDate><guid>/2019/03/13/ceph%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-03-luminous%E7%89%88%E6%9C%AC%E9%83%A8%E7%BD%B2/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="1配置cephrepo并安装批量管理工具ceph-deploy"&gt;1、配置ceph.repo并安装批量管理工具ceph-deploy&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@ceph-node1 ~]# vim /etc/yum.repos.d/ceph.repo
[ceph]
name=Ceph packages for $basearch
baseurl=http://mirrors.aliyun.com/ceph/rpm-luminous/el7/$basearch
enabled=1
gpgcheck=1
priority=1
type=rpm-md
gpgkey=https://mirrors.aliyun.com/ceph/keys/release.asc
[ceph-noarch]
name=Ceph noarch packages
baseurl=http://mirrors.aliyun.com/ceph/rpm-luminous/el7/noarch
enabled=1
gpgcheck=1
priority=1
type=rpm-md
gpgkey=https://mirrors.aliyun.com/ceph/keys/release.asc
[ceph-source]
name=Ceph source packages
baseurl=http://mirrors.aliyun.com/ceph/rpm-luminous/el7/SRPMS
enabled=0
gpgcheck=1
type=rpm-md
gpgkey=https://mirrors.aliyun.com/ceph/keys/release.asc
priority=1
[root@ceph-node1 ~]# yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
[root@ceph-node1 ~]# yum makecache
[root@ceph-node1 ~]# yum update -y
[root@ceph-node1 ~]# yum install -y ceph-deploy
​```
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2ceph的节点部署"&gt;2、ceph的节点部署&lt;/h3&gt;
&lt;h4 id="1安装ntp-在所有-ceph-节点上安装-ntp-服务特别是-ceph-monitor-节点以免因时钟漂移导致故障"&gt;（1）安装NTP 在所有 Ceph 节点上安装 NTP 服务（特别是 Ceph Monitor 节点），以免因时钟漂移导致故障&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@ceph-node1 ~]# yum install -y ntp ntpdate ntp-doc
[root@ceph-node2 ~]# yum install -y ntp ntpdate ntp-doc
[root@ceph-node3 ~]# yum install -y ntp ntpdate ntp-doc
[root@ceph-node1 ~]# ntpdate ntp1.aliyun.com
31 Jul 03:43:04 ntpdate[973]: adjust time server 120.25.115.20 offset 0.001528 sec
[root@ceph-node1 ~]# hwclock
Tue 31 Jul 2018 03:44:55 AM EDT -0.302897 seconds
[root@ceph-node1 ~]# crontab -e
*/5 * * * * /usr/sbin/ntpdate ntp1.aliyun.com
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;确保在各 Ceph 节点上启动了 NTP 服务，并且要使用同一个 NTP 服务器&lt;/p&gt;</description></item><item><title>Ceph学习笔记-02-工作原理及流程</title><link>/2019/03/11/ceph%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-02-%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E6%B5%81%E7%A8%8B/</link><pubDate>Mon, 11 Mar 2019 00:00:00 +0000</pubDate><guid>/2019/03/11/ceph%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-02-%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E6%B5%81%E7%A8%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="一rados的对象寻址"&gt;一、RADOS的对象寻址&lt;/h3&gt;
&lt;p&gt;　　Ceph 存储集群从 &lt;a href="http://docs.ceph.org.cn/glossary/#term-67"&gt;&lt;em&gt;Ceph 客户端&lt;/em&gt;&lt;/a&gt;接收数据——不管是来自 &lt;a href="http://docs.ceph.org.cn/glossary/#term-38"&gt;&lt;em&gt;Ceph 块设备&lt;/em&gt;&lt;/a&gt;、 &lt;a href="http://docs.ceph.org.cn/glossary/#term-30"&gt;&lt;em&gt;Ceph 对象存储&lt;/em&gt;&lt;/a&gt;、 &lt;a href="http://docs.ceph.org.cn/glossary/#term-45"&gt;&lt;em&gt;Ceph 文件系统&lt;/em&gt;&lt;/a&gt;、还是基于 &lt;code&gt;librados&lt;/code&gt; 的自定义实现——并存储为对象。每个对象是文件系统中的一个文件，它们存储在&lt;a href="http://docs.ceph.org.cn/glossary/#term-52"&gt;&lt;em&gt;对象存储设备&lt;/em&gt;&lt;/a&gt;上。由 Ceph OSD 守护进程处理存储设备上的读/写操作。　&lt;/p&gt;
&lt;p&gt;　　在传统架构里，客户端与一个中心化的组件通信（如网关、中间件、 API 、前端等等），它作为一个复杂子系统的唯一入口，它引入单故障点的同时，也限制了性能和伸缩性（就是说如果中心化组件挂了，整个系统就挂了）。&lt;/p&gt;
&lt;p&gt;　　Ceph 消除了集中网关，允许客户端直接和 Ceph OSD 守护进程通讯。 Ceph OSD 守护进程自动在其它 Ceph 节点上创建对象副本来确保数据安全和高可用性；为保证高可用性，监视器也实现了集群化。为消除中心节点， Ceph 使用了 CRUSH 算法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;img alt="img" loading="lazy" src="/2019/03/11/ceph%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-02-%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E6%B5%81%E7%A8%8B/1349539-20180803162209076-1452295982.png"&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;File&lt;/strong&gt; —— 此处的file就是用户需要存储或者访问的文件。当用户要将数据存储到Ceph集群时，存储数据都会被分割成多个object。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ojbect&lt;/strong&gt; —— 每个object都有一个object id，每个object的大小是可以设置的，默认是4MB，object可以看成是Ceph存储的最小存储单元。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PG（Placement Group）&lt;/strong&gt;—— 顾名思义，PG的用途是对object的存储进行组织和位置映射。由于object的数量很多，所以Ceph引入了PG的概念用于管理object，每个object最后都会通过CRUSH计算映射到某个pg中，一个pg可以包含多个object。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OSD&lt;/strong&gt; —— 即object storage device，PG也需要通过CRUSH计算映射到osd中去存储，如果是二副本的，则每个pg都会映射到二个osd，比如[osd.1,osd.2]，那么osd.1是存放该pg的主副本，osd.2是存放该pg的从副本，保证了数据的冗余。&lt;/p&gt;
&lt;h4 id="1file---object映射"&gt;（1）File -&amp;gt; object映射&lt;/h4&gt;
&lt;p&gt;　　这次映射的目的是，将用户要操作的file，映射为RADOS能够处理的object。其映射十分简单，本质上就是按照object的最大size对file进行切分，相当于RAID中的条带化过程。这种切分的好处有二：一是让大小不限的file变成最大size一致、可以被RADOS高效管理的object；二是让对单一file实施的串行处理变为对多个object实施的并行化处理。&lt;/p&gt;
&lt;p&gt;　　每一个切分后产生的object将获得唯一的oid，即object id。其产生方式也是线性映射，极其简单。图中，ino是待操作file的元数据，可以简单理解为该file的唯一id。ono则是由该file切分产生的某个object的序号。而oid就是将这个序号简单连缀在该file id之后得到的。举例而言，如果一个id为filename的file被切分成了三个object，则其object序号依次为0、1和2，而最终得到的oid就依次为filename0、filename1和filename2。&lt;/p&gt;
&lt;p&gt;　　这里隐含的问题是，ino的唯一性必须得到保证，否则后续映射无法正确进行。&lt;/p&gt;
&lt;h4 id="2object---pg映射"&gt;（2）Object -&amp;gt; PG映射&lt;/h4&gt;
&lt;p&gt;　　在file被映射为一个或多个object之后，就需要将每个object独立地映射到一个PG中去。这个映射过程也很简单，如图中所示，其计算公式是：&lt;/p&gt;
&lt;p&gt;　　&lt;strong&gt;hash(oid) &amp;amp; mask -&amp;gt; pgid&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;　　由此可见，其计算由两步组成。首先是使用Ceph系统指定的一个静态哈希函数计算oid的哈希值，将oid映射成为一个近似均匀分布的伪随机值。然后，将这个伪随机值和mask按位相与，得到最终的PG序号（pgid）。根据RADOS的设计，给定PG的总数为m（m应该为2的整数幂），则mask的值为m-1。因此，哈希值计算和按位与操作的整体结果事实上是从所有m个PG中近似均匀地随机选择一个。基于这一机制，当有大量object和大量PG时，RADOS能够保证object和PG之间的近似均匀映射。又因为object是由file切分而来，大部分object的size相同，因而，这一映射最终保证了，各个PG中存储的object的总数据量近似均匀。&lt;/p&gt;
&lt;p&gt;　　从介绍不难看出，这里反复强调了“大量”。只有当object和PG的数量较多时，这种伪随机关系的近似均匀性才能成立，Ceph的数据存储均匀性才有保证。为保证“大量”的成立，一方面，object的最大size应该被合理配置，以使得同样数量的file能够被切分成更多的object；另一方面，Ceph也推荐PG总数应该为OSD总数的数百倍，以保证有足够数量的PG可供映射。&lt;/p&gt;
&lt;h4 id="3pg---osd映射"&gt;（3）PG -&amp;gt; OSD映射&lt;/h4&gt;
&lt;p&gt;　　第三次映射就是将作为object的逻辑组织单元的PG映射到数据的实际存储单元OSD。如图所示，RADOS采用一个名为CRUSH的算法，将pgid代入其中，然后得到一组共n个OSD。这n个OSD即共同负责存储和维护一个PG中的所有object。前已述及，n的数值可以根据实际应用中对于可靠性的需求而配置，在生产环境下通常为3。具体到每个OSD，则由其上运行的OSD deamon负责执行映射到本地的object在本地文件系统中的存储、访问、元数据维护等操作。&lt;/p&gt;</description></item><item><title>Ceph学习笔记-01-初识Ceph</title><link>/2019/03/10/ceph%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-01-%E5%88%9D%E8%AF%86ceph/</link><pubDate>Sun, 10 Mar 2019 00:00:00 +0000</pubDate><guid>/2019/03/10/ceph%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-01-%E5%88%9D%E8%AF%86ceph/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;官方中文文档：http://docs.ceph.org.cn/&lt;/p&gt;
&lt;h3 id="一元数据和元数据管理"&gt;&lt;strong&gt;一、元数据和元数据管理&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;（1）元数据&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;　　在学习Ceph之前，需要了解元数据的概念。元数据又称为中介数据、中继数据，为描述数据的数据。主要描述数据属性的信息，用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。通俗地说，就 是用于描述一个文件的特征的系统数据，比如访问权限、文件拥有者以及文件数据库的分布信息（inode)等等。在集群文件系统中，分布信息包括文件在磁盘上的位置以 及磁盘在集群中的位置。用户需要操作一个文件就必须首先得到它的元数据，才能定位到文件的位置并且得到文件的内容或相关属性。&lt;/p&gt;
&lt;p&gt;　　使用stat命令，可以显示文件的元数据&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@ceph-node1 ~]# stat 1.txt
File: ‘1.txt’
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: 802h/2050d Inode: 33889728 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Context: unconfined_u:object_r:admin_home_t:s0
Access: 2018-08-05 16:38:22.137566272 +0800
Modify: 2018-08-05 16:38:22.137566272 +0800
Change: 2018-08-05 16:38:22.137566272 +0800
Birth: -
File：文件名
Size：文件大小（单位：B）
Blocks：文件所占扇区个数，为8的倍数（通常的Linux扇区大小为512B，连续八个扇区组成一个block）
IO Block：每个数据块的大小（单位：B）
regular file：普通文件（此处显示文件的类型）
Inode：文件的Inode号
Links：硬链接次数
Access：权限
Uid：属主id/属主名
Gid：属组id/属组名
Access：最近访问时间
Modify：数据改动时间
Change：元数据改动时间
以上的参数均属于文件的元数据，元数据即用来描述数据的数据。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;（2）元数据管理&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>JWT及JJWT签发与验证token</title><link>/2019/03/01/jwt%E5%8F%8Ajjwt%E7%AD%BE%E5%8F%91%E4%B8%8E%E9%AA%8C%E8%AF%81token/</link><pubDate>Fri, 01 Mar 2019 00:00:00 +0000</pubDate><guid>/2019/03/01/jwt%E5%8F%8Ajjwt%E7%AD%BE%E5%8F%91%E4%B8%8E%E9%AA%8C%E8%AF%81token/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="jwt"&gt;JWT&lt;/h3&gt;
&lt;p&gt;JSON Web Token（JWT）是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。&lt;/p&gt;
&lt;p&gt;一个JWT实际上就是一个字符串，它由三部分组成，头部、载荷与签名。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;头部（Header）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;头部用于描述关于该JWT的最基本的信息，例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。&lt;/p&gt;
&lt;p&gt;{&amp;ldquo;typ&amp;rdquo;:&amp;ldquo;JWT&amp;rdquo;,&amp;ldquo;alg&amp;rdquo;:&amp;ldquo;HS256&amp;rdquo;}&lt;/p&gt;
&lt;p&gt;在头部指明了签名算法是HS256算法。 我们进行BASE64编码&lt;a href="https://links.jianshu.com/go?to=http%3A%2F%2Fbase64.xpcha.com%2F"&gt;http://base64.xpcha.com/&lt;/a&gt;，编码后的字符串如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;载荷（playload）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品，这些有效信息包含三个部分&lt;/p&gt;
&lt;p&gt;（1）标准中注册的声明（建议但不强制使用）&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间，这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前，该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识，主要用来作为一次性token。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;（2）公共的声明&lt;/p&gt;
&lt;p&gt;公共的声明可以添加任何的信息，一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息，因为该部分在客户端可解密.&lt;/p&gt;
&lt;p&gt;（3）私有的声明&lt;/p&gt;
&lt;p&gt;私有声明是提供者和消费者所共同定义的声明，一般不建议存放敏感信息，因为base64是对称解密的，意味着该部分信息可以归类为明文信息。&lt;/p&gt;
&lt;p&gt;这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于：JWT规定的claim，JWT的接收方在拿到JWT之后，都知道怎么对这些标准的claim进行验证(还不知道是否能够验证)；而private claims不会验证，除非明确告诉接收方要对这些claim进行验证以及规则才行。&lt;/p&gt;
&lt;p&gt;定义一个payload:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{&amp;#34;sub&amp;#34;:&amp;#34;1234567890&amp;#34;,&amp;#34;name&amp;#34;:&amp;#34;John Doe&amp;#34;,&amp;#34;admin&amp;#34;:true}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后将其进行base64加密，得到Jwt的第二部分。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;签证（signature）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;jwt的第三部分是一个签证信息，这个签证信息由三部分组成：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;header (base64后的)&lt;/p&gt;
&lt;p&gt;payload (base64后的)&lt;/p&gt;
&lt;p&gt;secret&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串，然后通过header中声明的加密方式进行加盐secret组合加密，然后就构成了jwt的第三部分。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：secret是保存在服务器端的，jwt的签发生成也是在服务器端的，secret就是用来进行jwt的签发和jwt的验证，所以，它就是你服务端的私钥，在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。&lt;/p&gt;
&lt;h3 id="jjwt签发与验证token"&gt;JJWT签发与验证token&lt;/h3&gt;
&lt;p&gt;JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License，版本2.0)，JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面，隐藏了它的大部分复杂性。&lt;/p&gt;
&lt;p&gt;官方文档：&lt;/p&gt;
&lt;p&gt;&lt;a href="https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fjwtk%2Fjjwt"&gt;https://github.com/jwtk/jjwt&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="创建token"&gt;创建token&lt;/h4&gt;
&lt;p&gt;（1）新建项目中的pom.xml中添加依赖：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
&amp;lt;groupId&amp;gt;io.jsonwebtoken&amp;lt;/groupId&amp;gt;
&amp;lt;artifactId&amp;gt;jjwt&amp;lt;/artifactId&amp;gt;
&amp;lt;version&amp;gt;0.9.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;（2）创建测试类，代码如下&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;JwtBuilder builder= Jwts.builder()
.setId(&amp;#34;888&amp;#34;) //设置唯一编号
.setSubject(&amp;#34;小白&amp;#34;)//设置主题 可以是JSON数据
.setIssuedAt(new Date())//设置签发日期
.signWith(SignatureAlgorithm.HS256,&amp;#34;hahaha&amp;#34;);//设置签名 使用HS256算法，并设置SecretKey(字符串)
//构建 并返回一个字符串
System.out.println( builder.compact() );
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;运行打印结果：&lt;/p&gt;</description></item><item><title>Nginx通过CORS实现跨域</title><link>/2019/02/20/nginx%E9%80%9A%E8%BF%87cors%E5%AE%9E%E7%8E%B0%E8%B7%A8%E5%9F%9F/</link><pubDate>Wed, 20 Feb 2019 00:00:00 +0000</pubDate><guid>/2019/02/20/nginx%E9%80%9A%E8%BF%87cors%E5%AE%9E%E7%8E%B0%E8%B7%A8%E5%9F%9F/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="什么是cors"&gt;&lt;strong&gt;什么是CORS&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;CORS是一个W3C标准，全称是跨域资源共享(Cross-origin resource sharing)。它允许浏览器向跨源服务器，发出XMLHttpRequest请求，从而克服了AJAX只能同源使用的限制。&lt;/p&gt;
&lt;p&gt;当前几乎所有的浏览器(Internet Explorer 8+， Firefox 3.5+， Safari 4+和 Chrome 3+)都可通过名为跨域资源共享(Cross-Origin Resource Sharing)的协议支持AJAX跨域调用。&lt;/p&gt;
&lt;p&gt;Chrome,Firefox,Opera,Safari都使用的是XMLHttpRequest2对象，IE使用XDomainRequest。&lt;/p&gt;
&lt;p&gt;简单来说就是跨域的目标服务器要返回一系列的Headers，通过这些Headers来控制是否同意跨域。跨域资源共享(CORS)也是未来的跨域问题的标准解决方案。&lt;/p&gt;
&lt;p&gt;CORS提供如下Headers，Request包和Response包中都有一部分。&lt;/p&gt;
&lt;h3 id="http-response-header"&gt;&lt;strong&gt;HTTP Response Header&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Access-Control-Allow-Origin&lt;/li&gt;
&lt;li&gt;Access-Control-Allow-Credentials&lt;/li&gt;
&lt;li&gt;Access-Control-Allow-Methods&lt;/li&gt;
&lt;li&gt;Access-Control-Allow-Headers&lt;/li&gt;
&lt;li&gt;Access-Control-Expose-Headers&lt;/li&gt;
&lt;li&gt;Access-Control-Max-Age&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="http-request-header"&gt;&lt;strong&gt;HTTP Request Header&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Access-Control-Request-Method&lt;/li&gt;
&lt;li&gt;Access-Control-Request-Headers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中最敏感的就是Access-Control-Allow-Origin这个Header, 它是W3C标准里用来检查该跨域请求是否可以被通过。(Access Control Check)。如果需要跨域，解决方法就是在资源的头中加入Access-Control-Allow-Origin 指定你授权的域。&lt;/p&gt;
&lt;h2 id="启用cors请求"&gt;&lt;strong&gt;启用CORS请求&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;假设您的应用已经在example.com上了，而您想要从www.example2.com提取数据。一般情况下，如果您尝试进行这种类型的AJAX调用，请求将会失败，而浏览器将会出现源不匹配的错误。利用CORS后只需www.example2.com 服务端添加一个HTTP Response头，就可以允许来自example.com的请求。&lt;/p&gt;
&lt;p&gt;将Access-Control-Allow-Origin添加到某网站下或整个域中的单个资源&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Credentials: true (可选)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;将允许任何域向您提交请求&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true (可选)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="提交跨域请求"&gt;&lt;strong&gt;提交跨域请求&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;如果服务器端已启用了CORS，那么提交跨域请求就和普通的XMLHttpRequest请求没什么区别。例如现在example.com可以向www.example2.com提交请求。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;var xhr = new XMLHttpRequest();
// xhr.withCredentials = true; //如果需要Cookie等
xhr.open(&amp;#39;GET&amp;#39;, &amp;#39;http://www.example2.com/hello.json&amp;#39;);
xhr.onload = function(e) {
var data = JSON.parse(this.response);
...
}
xhr.send();
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="服务端nginx配置"&gt;&lt;strong&gt;服务端Nginx配置&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;要实现CORS跨域，服务端需要下图中这样一个流程&lt;/p&gt;</description></item><item><title>aiohttp框架及其常用模块</title><link>/2019/01/30/aiohttp%E6%A1%86%E6%9E%B6%E5%8F%8A%E5%85%B6%E5%B8%B8%E7%94%A8%E6%A8%A1%E5%9D%97/</link><pubDate>Wed, 30 Jan 2019 00:00:00 +0000</pubDate><guid>/2019/01/30/aiohttp%E6%A1%86%E6%9E%B6%E5%8F%8A%E5%85%B6%E5%B8%B8%E7%94%A8%E6%A8%A1%E5%9D%97/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="简介"&gt;简介&lt;/h1&gt;
&lt;p&gt;aiohttp是一个为Python提供异步HTTP 客户端/服务端编程，基于asyncio的异步库。他的核心功能如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同时支持客户端使用(可以理解为request的异步执行方式)和服务端使用。&lt;/li&gt;
&lt;li&gt;同时支持服务端WebSockets组件和客户端WebSockets组件，开箱即用呦。&lt;/li&gt;
&lt;li&gt;web服务器具有中间件，信号组件和可插拔路由的功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个可插拔的路由意思就是说，你可以在代码运行的过程中增加某个接口，或者减少某个接口。&lt;/p&gt;
&lt;p&gt;安装方式：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pip install aiohttp
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;客户端例子:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import aiohttp
import asyncio
import async_timeout
async def fetch(session, url):
with async_timeout.timeout(10):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, &amp;#39;http://python.org&amp;#39;)
print(html)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;服务端例子:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from aiohttp import web
async def handle(request):
name = request.match_info.get(&amp;#39;name&amp;#39;, &amp;#34;Anonymous&amp;#34;)
text = &amp;#34;Hello, &amp;#34; + name
return web.Response(text=text)
app = web.Application()
app.router.add_get(&amp;#39;/&amp;#39;, handle)
app.router.add_get(&amp;#39;/{name}&amp;#39;, handle)
web.run_app(app)
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="常见扩展模块"&gt;常见扩展模块&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模块名称&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;aiohttp_session&lt;/td&gt;
&lt;td&gt;处理用户会话&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiohttp-session-mongo&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiomysql&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiopg&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aioredis&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiohttp_cors&lt;/td&gt;
&lt;td&gt;解决跨域问题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiojobs&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiohttp_jinja2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiobotocore&lt;/td&gt;
&lt;td&gt;aws文件存储服务器的异步模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pytest-aiohttp&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiohttp-swagger3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aioelasticsearch&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiologstash&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiokafka&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;更多可以去aio-libs github官方仓库查看 &lt;a href="https://github.com/aio-libs"&gt;https://github.com/aio-libs&lt;/a&gt;&lt;/p&gt;</description></item><item><title>aiortc</title><link>/2018/12/01/aiortc/</link><pubDate>Sat, 01 Dec 2018 00:00:00 +0000</pubDate><guid>/2018/12/01/aiortc/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="aiortc"&gt;aiortc&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/aiortc/aiortc"&gt;aiortc&lt;/a&gt;是 WebRTC 和 ORTC 的Python异步实现。&lt;/p&gt;
&lt;h2 id="install"&gt;install&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt install libavdevice-dev libavfilter-dev libopus-dev libvpx-dev pkg-config
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ffmpeg &amp;gt;= 3.2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pip install aiortc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="example"&gt;example&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; aiortc &lt;span style="color:#f92672"&gt;import&lt;/span&gt; RTCPeerConnection, RTCSessionDescription
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; aiortc &lt;span style="color:#f92672"&gt;import&lt;/span&gt; VideoStreamTrack
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;offer&lt;/span&gt;(paramse):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 解析 offer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; request_offer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; RTCSessionDescription(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sdp&lt;span style="color:#f92672"&gt;=&lt;/span&gt;params[&lt;span style="color:#e6db74"&gt;&amp;#39;sdp&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type&lt;span style="color:#f92672"&gt;=&lt;/span&gt;params[&lt;span style="color:#e6db74"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 创建一个连接class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pc &lt;span style="color:#f92672"&gt;=&lt;/span&gt; RTCPeerConnection()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pcs&lt;span style="color:#f92672"&gt;.&lt;/span&gt;add(pc)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 监听数据通道&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@pc.on&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;datachannel&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_datachannel&lt;/span&gt;(channel):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 监听接收数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@channel.on&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_message&lt;/span&gt;(message):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; message_recieve &lt;span style="color:#f92672"&gt;=&lt;/span&gt; json&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loads(message)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#34;message_recieve:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{message_recieve}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 监听断开连接&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@pc.on&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;iceconnectionstatechange&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_iceconnectionstatechange&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;ICE connection state is &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;pc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iceConnectionState&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; pc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iceConnectionState &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;failed&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; pc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pcs&lt;span style="color:#f92672"&gt;.&lt;/span&gt;discard(pc)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@pc.on&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;track&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_track&lt;/span&gt;(track):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;Track &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt; received&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; track&lt;span style="color:#f92672"&gt;.&lt;/span&gt;kind)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; track&lt;span style="color:#f92672"&gt;.&lt;/span&gt;kind &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;audio&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 接收语音数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; frame &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; audio_track&lt;span style="color:#f92672"&gt;.&lt;/span&gt;recv()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; track&lt;span style="color:#f92672"&gt;.&lt;/span&gt;kind &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;video&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; local_video &lt;span style="color:#f92672"&gt;=&lt;/span&gt; VideoTransformTrack(track)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;addTrack(local_video)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@track.on&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;ended&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on_ended&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;Track &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt; ended&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; track&lt;span style="color:#f92672"&gt;.&lt;/span&gt;kind)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 设置 请求端 连接信息： request_offer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; pc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;setRemoteDescription(request_offer)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 设置 响应端 连接信息： answer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; answer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; pc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;createAnswer()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; pc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;setLocalDescription(answer)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 将 响应端 信息返回给 请求端： offer_result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; offer_result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; json&lt;span style="color:#f92672"&gt;.&lt;/span&gt;dumps({&lt;span style="color:#e6db74"&gt;&amp;#39;sdp&amp;#39;&lt;/span&gt;: pc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;localDescription&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sdp, &lt;span style="color:#e6db74"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;: pc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;localDescription&lt;span style="color:#f92672"&gt;.&lt;/span&gt;type})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; offer_result
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;更多例子: &lt;a href="https://github.com/aiortc/aiortc/tree/master/examples"&gt;https://github.com/aiortc/aiortc/tree/master/examples&lt;/a&gt;&lt;/p&gt;</description></item><item><title>webrtc</title><link>/2018/12/01/webrtc/</link><pubDate>Sat, 01 Dec 2018 00:00:00 +0000</pubDate><guid>/2018/12/01/webrtc/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="webrtc"&gt;webrtc&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;WebRTC&lt;/strong&gt;，名称源自&lt;strong&gt;网页即时通信&lt;/strong&gt;（英语：Web Real-Time Communication）的缩写，是一个支持网页浏览器进行实时语音对话或视频对话的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准&lt;/p&gt;
&lt;p&gt;&lt;a href="https://webrtc.github.io/samples/"&gt;官方教程&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API"&gt;mozilla学习地址&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="重要api"&gt;重要API&lt;/h2&gt;
&lt;p&gt;WebRTC原生APIs文件是基于WebRTC规格书[&lt;a href="https://zh.wikipedia.org/wiki/WebRTC#cite_note-21"&gt;21]&lt;/a&gt;撰写而成，这些API可分成Network Stream API、 RTCPeerConnection、Peer-to-peer Data API三类。&lt;/p&gt;
&lt;h3 id="network-stream-api"&gt;Network Stream API&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;MediaStream：MediaStream用来表示一个媒体数据流。&lt;/li&gt;
&lt;li&gt;MediaStreamTrack在浏览器中表示一个媒体源。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="rtcpeerconnection"&gt;RTCPeerConnection&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;RTCPeerConnection：一个RTCPeerConnection对象允许用户在两个浏览器之间直接通讯。&lt;/li&gt;
&lt;li&gt;RTCIceCandidate：表示一个ICE协议的候选者。&lt;/li&gt;
&lt;li&gt;RTCIceServer：表示一个ICE Server。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="peer-to-peer-data-api"&gt;Peer-to-peer Data API&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;DataChannel：数据通道（DataChannel）接口表示一个在两个节点之间的双向的数据通道。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="webrtc协议"&gt;WebRTC协议&lt;/h2&gt;
&lt;h3 id="ice"&gt;ICE&lt;/h3&gt;
&lt;p&gt;交互式连接建立&lt;a href="http://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment"&gt;Interactive Connectivity Establishment (ICE)&lt;/a&gt; 是一个允许你的浏览器和对端浏览器建立连接的协议框架。在实际的网络当中，有很多原因能导致简单的从A端到B端直连不能如愿完成。这需要绕过阻止建立连接的防火墙，给你的设备分配一个唯一可见的地址（通常情况下我们的大部分设备没有一个固定的公网地址），如果路由器不允许主机直连，还得通过一台服务器转发数据。ICE通过使用以下几种技术完成上述工作。&lt;/p&gt;
&lt;h3 id="stun"&gt;STUN&lt;/h3&gt;
&lt;p&gt;NAT的会话穿越功能&lt;a href="http://en.wikipedia.org/wiki/STUN"&gt;Session Traversal Utilities for NAT (STUN)&lt;/a&gt; (缩略语的最后一个字母是NAT的首字母)是一个允许位于NAT后的客户端找出自己的公网地址，判断出路由器阻止直连的限制方法的协议。&lt;/p&gt;
&lt;p&gt;客户端通过给公网的STUN服务器发送请求获得自己的公网地址信息，以及是否能够被（穿过路由器）访问。&lt;/p&gt;
&lt;p&gt;&lt;img alt="webrtc-stun" loading="lazy" src="/2018/12/01/webrtc/webrtc-stun.png"&gt;&lt;/p&gt;
&lt;h3 id="nat"&gt;NAT&lt;/h3&gt;
&lt;p&gt;网络地址转换协议&lt;a href="http://en.wikipedia.org/wiki/NAT"&gt;Network Address Translation (NAT)&lt;/a&gt; 用来给你的（私网）设备映射一个公网的IP地址的协议。一般情况下，路由器的WAN口有一个公网IP，所有连接这个路由器LAN口的设备会分配一个私有网段的IP地址（例如192.168.1.3）。私网设备的IP被映射成路由器的公网IP和唯一的端口，通过这种方式不需要为每一个私网设备分配不同的公网IP，但是依然能被外网设备发现。&lt;/p&gt;
&lt;p&gt;一些路由器严格地限定了部分私网设备的对外连接。这种情况下，即使STUN服务器识别了该私网设备的公网IP和端口的映射，依然无法和这个私网设备建立连接。这种情况下就需要转向TURN协议。&lt;/p&gt;
&lt;h3 id="turn"&gt;TURN&lt;/h3&gt;
&lt;p&gt;一些路由器使用一种“对称型NAT”的NAT模型。这意味着路由器只接受和对端先前建立的连接（就是下一次请求建立新的连接映射）。&lt;/p&gt;
&lt;p&gt;NAT的中继穿越方式&lt;a href="http://en.wikipedia.org/wiki/TURN"&gt;Traversal Using Relays around NAT (TURN)&lt;/a&gt; 通过TURN服务器中继所有数据的方式来绕过“对称型NAT”。你需要在TURN服务器上创建一个连接，然后告诉所有对端设备发包到服务器上，TURN服务器再把包转发给你。很显然这种方式是开销很大的，所以只有在没得选择的情况下采用。&lt;/p&gt;
&lt;p&gt;&lt;img alt="webrtc-turn" loading="lazy" src="/2018/12/01/webrtc/webrtc-turn.png"&gt;&lt;/p&gt;
&lt;h3 id="sdp"&gt;SDP&lt;/h3&gt;
&lt;p&gt;会话描述协议&lt;a href="http://en.wikipedia.org/wiki/Session_Description_Protocol"&gt;Session Description Protocol (SDP)&lt;/a&gt; 是一个描述多媒体连接内容的协议，例如分辨率，格式，编码，加密算法等。所以在数据传输时两端都能够理解彼此的数据。本质上，这些描述内容的元数据并不是媒体流本身。&lt;/p&gt;</description></item><item><title>wave librosa pyaudio</title><link>/2018/11/17/wave-librosa-pyaudio/</link><pubDate>Sat, 17 Nov 2018 00:00:00 +0000</pubDate><guid>/2018/11/17/wave-librosa-pyaudio/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="wave"&gt;wave&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/zh-cn/3/library/wave.html#module-wave"&gt;&lt;code&gt;wave&lt;/code&gt;&lt;/a&gt; 模块提供了一个处理 WAV 声音格式的便利接口。它不支持压缩/解压，但是支持单声道/立体声。&lt;/p&gt;
&lt;h2 id="install"&gt;install&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pip install wave
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="example"&gt;example&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 读取音频文件数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;read_wav&lt;/span&gt;(audio_name):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; wave&lt;span style="color:#f92672"&gt;.&lt;/span&gt;open(audio_name, &lt;span style="color:#e6db74"&gt;&amp;#39;rb&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; rf:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; frames &lt;span style="color:#f92672"&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; frame &lt;span style="color:#f92672"&gt;=&lt;/span&gt; rf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;readframes(&lt;span style="color:#ae81ff"&gt;3200&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; frame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; frames&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(frame)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; frame &lt;span style="color:#f92672"&gt;=&lt;/span&gt; rf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;readframes(&lt;span style="color:#ae81ff"&gt;3200&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; frames
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 保存音频文件数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;write_wav&lt;/span&gt;(save_file, frames, CHANNELS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, SIMPLE_SIZE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, RATE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;16000&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; save_file &lt;span style="color:#f92672"&gt;is&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wf &lt;span style="color:#f92672"&gt;=&lt;/span&gt; wave&lt;span style="color:#f92672"&gt;.&lt;/span&gt;open(save_file, &lt;span style="color:#e6db74"&gt;&amp;#39;wb&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;setnchannels(CHANNELS)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;setsampwidth(SIMPLE_SIZE)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;setframerate(RATE)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;writeframes(&lt;span style="color:#e6db74"&gt;b&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;join(frames))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;wave可以读取和保存音频文件，但是不能做时频处理、特征提取等问题，如果你读取rate=16000的文件，保存为rate=8000的文件，音频的时长增加了一倍，播放速度降低了一倍。&lt;/p&gt;
&lt;p&gt;参考：https://docs.python.org/zh-cn/3/library/wave.html#module-wave&lt;/p&gt;
&lt;h1 id="librosa"&gt;Librosa&lt;/h1&gt;
&lt;p&gt;&lt;a href="http://librosa.github.io/librosa/tutorial.html"&gt;Librosa&lt;/a&gt; 是一个用于音频、音乐分析、处理的python工具包，一些常见的时频处理、特征提取、绘制声音图形等功能应有尽有，功能十分强大。&lt;/p&gt;
&lt;h2 id="install-1"&gt;install&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pip install librosa
# conda install
conda install -c conda-forge librosa
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="example-1"&gt;example&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# 改变频谱并保存
def change_sample_rate(read_file, save_file, orig_sr=48000, target_sr=8000):
y, sr = librosa.load(read_file, sr=orig_sr)
y_16k = librosa.resample(y, sr, target_sr)
librosa.output.write_wav(save_file, y_16k, target_sr)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;参考： &lt;a href="http://librosa.github.io/librosa/tutorial.html"&gt;http://librosa.github.io/librosa/tutorial.html&lt;/a&gt;&lt;/p&gt;</description></item><item><title>flask框架及其常用模块</title><link>/2018/11/10/flask%E6%A1%86%E6%9E%B6%E5%8F%8A%E5%85%B6%E5%B8%B8%E7%94%A8%E6%A8%A1%E5%9D%97/</link><pubDate>Sat, 10 Nov 2018 00:00:00 +0000</pubDate><guid>/2018/11/10/flask%E6%A1%86%E6%9E%B6%E5%8F%8A%E5%85%B6%E5%B8%B8%E7%94%A8%E6%A8%A1%E5%9D%97/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;</description></item><item><title>ffmepg</title><link>/2018/10/25/ffmepg/</link><pubDate>Thu, 25 Oct 2018 00:00:00 +0000</pubDate><guid>/2018/10/25/ffmepg/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="ffmpeg"&gt;FFmpeg&lt;/h1&gt;
&lt;p&gt;ffmpeg也可以读取文件和从设备中读取视频信号，还可以从多个音视频文件中读取，然后输出多个音视频文件。&lt;/p&gt;
&lt;h2 id="centos"&gt;centos&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum install ffmpeg
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# centos系统自带的ffmpeg一般都是 2.x的版本，很难满足现在很多需求，可以添加其他第三方源来安装，或者编译安装。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum install yum-utils
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum-config-manager --add-repo https://negativo17.org/repos/epel-multimedia.repo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum remove libva1-1.3.1-11.el7.x86_64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum install ffmpeg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="ubuntu"&gt;ubuntu&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo apt install ffmpeg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="conda"&gt;conda&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# conda安装的ffmpeg值存在于当前env环境中。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;conda install ffmpeg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;语法格式如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url} ...
# 从视频文件读取
-i test.avi
# 从设备读取
-i /dev/video0
# 从视频流读取
-i rtsp://your_ip:port/
# 设置尺寸
-s 640*480
# 输出为文件，直接在后面写出文件名即可
ffmpeg -f video4linux -r 10 -i /dev/video0 test.asf
# 输出到视频流
ffmpeg -i /dev/video0 -f mpegts -codec:v mpeg1video http://localhost:8081/supersecret
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;ffmpeg功能强大，参数巨多。详情请看&lt;a href="https://www.ffmpeg.org/ffmpeg.html"&gt;ffmpeg官方文档&lt;/a&gt;&lt;/p&gt;</description></item><item><title>opencv</title><link>/2018/10/24/opencv/</link><pubDate>Wed, 24 Oct 2018 00:00:00 +0000</pubDate><guid>/2018/10/24/opencv/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="opencv"&gt;opencv&lt;/h1&gt;
&lt;p&gt;opencv既可以从视频文件读取，也可以从视频设备上获取&lt;/p&gt;
&lt;h2 id="install"&gt;install&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pip install opencv-python
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="how-to-use"&gt;how to use&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; cv2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# video_capture = cv2.VideoCapture(&amp;#34;/home/alex/1.mp4&amp;#34;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# video_capture = cv.CaptureFromFile(&amp;#39;rtsp://192.168.1.2:8080/out.h264&amp;#39;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;video_capture &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;VideoCapture(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _, frame &lt;span style="color:#f92672"&gt;=&lt;/span&gt; video_capture&lt;span style="color:#f92672"&gt;.&lt;/span&gt;read()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;imshow(&lt;span style="color:#e6db74"&gt;&amp;#34;image&amp;#34;&lt;/span&gt;, frame)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;waitKey(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0xFF&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; ord(&lt;span style="color:#e6db74"&gt;&amp;#39;q&amp;#39;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以从本地文件，视频设备，或者远程视频流中获取视频信号。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# 查看本地视频设备号
$ ls /dev/video*
/dev/video0
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="常见实用示例"&gt;常见实用示例&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 图片读写&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;image_detector&lt;/span&gt;(self, imname, wait&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 读取图片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;imread(imname)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 得到新的图片image&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;detect(image)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;draw_result(image, result)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 显示图片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# cv2.imshow(&amp;#39;Image&amp;#39;, image)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# cv2.waitKey(wait)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 保存图片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;imwrite(imname&lt;span style="color:#f92672"&gt;.&lt;/span&gt;replace(&lt;span style="color:#e6db74"&gt;&amp;#34;.jpg&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;_result.jpg&amp;#34;&lt;/span&gt;), image)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 写入视频&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;camera_detector&lt;/span&gt;(self, cap, wait&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 获取 原视频的fps，fourcc编码，height，wieght等信息&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fps &lt;span style="color:#f92672"&gt;=&lt;/span&gt; int(cap&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CAP_PROP_FPS))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# fourcc = int(cap.get(cv2.CAP_PROP_FOURCC))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fourcc &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;VideoWriter_fourcc(&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;MJPG&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; height &lt;span style="color:#f92672"&gt;=&lt;/span&gt; int(cap&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CAP_PROP_FRAME_HEIGHT))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; weidth &lt;span style="color:#f92672"&gt;=&lt;/span&gt; int(cap&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CAP_PROP_FRAME_WIDTH))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#34;fourcc: &amp;#34;&lt;/span&gt;, fourcc)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 创建新的写入writer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; videowriter &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;VideoWriter(&lt;span style="color:#e6db74"&gt;&amp;#39;test/test.avi&amp;#39;&lt;/span&gt;, fourcc, fps, (weidth, height))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 读取一帧图片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ret, frame &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cap&lt;span style="color:#f92672"&gt;.&lt;/span&gt;read()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; frame &lt;span style="color:#f92672"&gt;is&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 得到新的图片image&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;detect(frame)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;draw_result(frame, result)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# cv2.imshow(&amp;#39;Camera&amp;#39;, frame)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# if cv2.waitKey(wait) &amp;amp; 0xFF == ord(&amp;#39;q&amp;#39;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 写入一帧图片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; videowriter&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write(frame)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 读取下一帧图片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ret, frame &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cap&lt;span style="color:#f92672"&gt;.&lt;/span&gt;read()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 释放writer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; videowriter&lt;span style="color:#f92672"&gt;.&lt;/span&gt;release()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 调用 camera_detector&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cap &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cv2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;VideoCapture(video_read)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;detector&lt;span style="color:#f92672"&gt;.&lt;/span&gt;camera_detector(cap, wait&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="opencv-mac-error"&gt;opencv mac error&lt;/h1&gt;
&lt;p&gt;正常情况下，在mac上面使用opencv是没有问题的，但是在Python子进程中使用opencv会发现子进程的代码会无厘头的没有结果。参考 &lt;a href="https://github.com/opencv/opencv/issues/5150"&gt;这里&lt;/a&gt; ，不要问我怎么知道的 /(ㄒoㄒ)/~~&lt;/p&gt;</description></item><item><title>k8s学习笔记-26-kubeasz+ansible部署集群</title><link>/2018/10/05/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-26-kubeasz-ansible%E9%83%A8%E7%BD%B2%E9%9B%86%E7%BE%A4/</link><pubDate>Fri, 05 Oct 2018 00:00:00 +0000</pubDate><guid>/2018/10/05/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-26-kubeasz-ansible%E9%83%A8%E7%BD%B2%E9%9B%86%E7%BE%A4/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;官方学习文档：https://github.com/easzlab/kubeasz&lt;/p&gt;
&lt;h1 id="1环境说明"&gt;1、环境说明&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;IP&lt;/th&gt;
&lt;th&gt;主机名&lt;/th&gt;
&lt;th&gt;角色&lt;/th&gt;
&lt;th&gt;虚拟机配置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;192.168.56.11&lt;/td&gt;
&lt;td&gt;k8s-master&lt;/td&gt;
&lt;td&gt;deploy、master1、lb1、etcd&lt;/td&gt;
&lt;td&gt;4c4g&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.56.12&lt;/td&gt;
&lt;td&gt;k8s-master2&lt;/td&gt;
&lt;td&gt;master2、lb2&lt;/td&gt;
&lt;td&gt;4c4g&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.56.13&lt;/td&gt;
&lt;td&gt;k8s-node01&lt;/td&gt;
&lt;td&gt;etcd、node&lt;/td&gt;
&lt;td&gt;2c2g&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.56.14&lt;/td&gt;
&lt;td&gt;k8s-node02&lt;/td&gt;
&lt;td&gt;etcd、node&lt;/td&gt;
&lt;td&gt;2c2g&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192.168.56.110&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;vip&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;系统内核&lt;/td&gt;
&lt;td&gt;3.10&lt;/td&gt;
&lt;td&gt;docker版本&lt;/td&gt;
&lt;td&gt;18.09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;k8s版本&lt;/td&gt;
&lt;td&gt;1.13&lt;/td&gt;
&lt;td&gt;etcd版本&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id="2准备工作"&gt;2、准备工作&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;四台机器，全部执行：&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum install -y epel-release
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum update -y
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum install python -y
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;deploy节点安装ansible并配置密钥认证&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum install -y ansible
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh-keygen
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; ip in &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;12&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;13&lt;/span&gt; 14;&lt;span style="color:#66d9ef"&gt;do&lt;/span&gt; ssh-copy-id 192.168.56.$ip;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;deploy节点编排K8S&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;root@k8s-master ~&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&lt;span style="color:#75715e"&gt;# git clone https://github.com/gjmzj/kubeasz.git&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;root@k8s-master ~&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&lt;span style="color:#75715e"&gt;# mv kubeasz/* /etc/ansible/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以根据自己所需版本，下载对应的tar包，这里我下载1.13
经过一番折腾，最终把k8s.1-13-5.tar.gz的tar包放到了depoly上&lt;/p&gt;</description></item><item><title>k8s学习笔记-25-Helm程序包管理器</title><link>/2018/09/25/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-25-helm%E7%A8%8B%E5%BA%8F%E5%8C%85%E7%AE%A1%E7%90%86%E5%99%A8/</link><pubDate>Tue, 25 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/25/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-25-helm%E7%A8%8B%E5%BA%8F%E5%8C%85%E7%AE%A1%E7%90%86%E5%99%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="1helm的概念和架构"&gt;1、Helm的概念和架构&lt;/h2&gt;
&lt;p&gt;每个成功的软件平台都有一个优秀的打包系统，比如 Debian、Ubuntu 的 apt，Redhat、Centos 的 yum。而 Helm 则是 Kubernetes 上的包管理器。&lt;/p&gt;
&lt;p&gt;**思考？？**Helm 到底解决了什么问题？为什么 Kubernetes 需要 Helm？&lt;/p&gt;
&lt;p&gt;Kubernetes 能够很好地组织和编排容器，但它缺少一个更高层次的应用打包工具，而 Helm 就是来干这件事的。&lt;/p&gt;
&lt;p&gt;举个例子，我们需要部署一个MySQL服务，Kubernetes则需要部署以下对象：&lt;/p&gt;
&lt;p&gt;① 为了能够让外界访问到MySQL，需要部署一个mysql的service；&lt;/p&gt;
&lt;p&gt;②需要进行定义MySQL的密码，则需要部署一个Secret；&lt;/p&gt;
&lt;p&gt;③Mysql的运行需要持久化的数据存储，此时还需要部署PVC；&lt;/p&gt;
&lt;p&gt;④保证后端mysql的运行，还需要部署一个Deployment，以支持以上的对象。&lt;/p&gt;
&lt;p&gt;针对以上对象，我们可以使用YAML文件进行定义并部署，但是仅仅对于单个的服务支持，如果应用需要由一个甚至几十个这样的服务组成，并且还需要考虑各种服务的依赖问题，可想而知，这样的组织管理应用的方式就显得繁琐。为此就诞生了一个工具Helm，就是为了解决Kubernetes这种应用部署繁重的现象。&lt;/p&gt;
&lt;p&gt;Helm的核心术语：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chart：一个helm程序包，是创建一个应用的信息集合，包含各种Kubernetes对象的配置模板、参数定义、依赖关系、文档说明等。可以将Chart比喻为yum中的软件安装包；&lt;/li&gt;
&lt;li&gt;Repository：Charts仓库，用于集中存储和分发Charts；&lt;/li&gt;
&lt;li&gt;Config：应用程序实例化安装运行时所需要的配置信息；&lt;/li&gt;
&lt;li&gt;Release：特定的Chart部署于目标集群上的一个实例，代表这一个正在运行的应用。当chart被安装到Kubernetes集群，就会生成一个release，chart可以多次安装到同一个集群，每次安装都是一个release。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Helm的程序架构：&lt;/p&gt;
&lt;p&gt;Helm主要由Helm客户端、Tiller服务器和Charts仓库组成，如下图：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;helm：客户端，GO语言编写，实现管理本地的Chart仓库，可管理Chart，与Tiller服务进行交互，用于发送Chart，实例安装、查询、卸载等操作。&lt;/li&gt;
&lt;li&gt;Tiller：服务端，通常运行在K8S集群之上。用于接收helm发来的Charts和Conifg，合并生成release，完成部署。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;简单的说：Helm 客户端负责管理 chart；Tiller 服务器负责管理 release。&lt;/p&gt;
&lt;h2 id="2部署helm"&gt;2、部署Helm&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://docs.helm.sh/using_helm/#quickstart-guide"&gt;helm部署文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Helm的部署方式有两种：预编译的二进制程序和源码编译安装，这里使用二进制的方式进行安装&lt;/p&gt;
&lt;h3 id="1下载helm"&gt;（1）下载helm&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;root@k8s-master ~&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&lt;span style="color:#75715e"&gt;# wget https://storage.googleapis.com/kubernetes-helm/helm-v2.9.1-linux-amd64.tar.gz --no-check-certificate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;root@k8s-master ~&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&lt;span style="color:#75715e"&gt;# tar -xf helm-v2.9.1-linux-amd64.tar.gz &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;root@k8s-master ~&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&lt;span style="color:#75715e"&gt;# cd linux-amd64/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;root@k8s-master linux-amd64&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&lt;span style="color:#75715e"&gt;# ls&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm LICENSE README.md
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;root@k8s-master linux-amd64&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&lt;span style="color:#75715e"&gt;# mv helm /usr/bin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;root@k8s-master linux-amd64&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&lt;span style="color:#75715e"&gt;# helm version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Client: &amp;amp;version.Version&lt;span style="color:#f92672"&gt;{&lt;/span&gt;SemVer:&lt;span style="color:#e6db74"&gt;&amp;#34;v2.9.1&amp;#34;&lt;/span&gt;, GitCommit:&lt;span style="color:#e6db74"&gt;&amp;#34;20adb27c7c5868466912eebdf6664e7390ebe710&amp;#34;&lt;/span&gt;, GitTreeState:&lt;span style="color:#e6db74"&gt;&amp;#34;clean&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2部署tiller"&gt;（2）部署Tiller&lt;/h3&gt;
&lt;p&gt;helm第一次init时，需要链接api-server并进行认证，所以在运行helm时，会去读取kube-config文件，所以必须确认当前用户存在kube-config文件。&lt;/p&gt;</description></item><item><title>k8s学习笔记-24-Prometheus监控</title><link>/2018/09/19/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-24-prometheus%E7%9B%91%E6%8E%A7/</link><pubDate>Wed, 19 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/19/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-24-prometheus%E7%9B%91%E6%8E%A7/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1prometheus概述"&gt;1、Prometheus概述&lt;/h1&gt;
&lt;p&gt;除了前面的资源指标（如CPU、内存）以外，用户或管理员需要了解更多的指标数据，比如&lt;code&gt;Kubernetes&lt;/code&gt;指标、容器指标、节点资源指标以及应用程序指标等等。自定义指标API允许请求任意的指标，其指标API的实现要指定相应的后端监视系统。而&lt;code&gt;Prometheus&lt;/code&gt;是第一个开发了相应适配器的监控系统。这个适用于&lt;code&gt;Prometheus&lt;/code&gt;的&lt;code&gt;Kubernetes Customm Metrics Adapter&lt;/code&gt;是属于Github上的&lt;a href="https://github.com/DirectXMan12/k8s-prometheus-adapter"&gt;k8s-prometheus-adapter&lt;/a&gt;项目提供的。其原理图如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/19/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-24-prometheus%E7%9B%91%E6%8E%A7/1349539-20190323102150508-489908309.png"&gt;&lt;/p&gt;
&lt;p&gt;要知道的是&lt;code&gt;prometheus&lt;/code&gt;本身就是一监控系统，也分为&lt;code&gt;server&lt;/code&gt;端和&lt;code&gt;agent&lt;/code&gt;端，&lt;code&gt;server&lt;/code&gt;端从被监控主机获取数据，而&lt;code&gt;agent&lt;/code&gt;端需要部署一个&lt;code&gt;node_exporter&lt;/code&gt;，主要用于数据采集和暴露节点的数据，那么 在获取Pod级别或者是mysql等多种应用的数据，也是需要部署相关的&lt;code&gt;exporter&lt;/code&gt;。我们可以通过&lt;code&gt;PromQL&lt;/code&gt;的方式对数据进行查询，但是由于本身&lt;code&gt;prometheus&lt;/code&gt;属于第三方的 解决方案，原生的k8s系统并不能对&lt;code&gt;Prometheus&lt;/code&gt;的自定义指标进行解析，就需要借助于&lt;code&gt;k8s-prometheus-adapter&lt;/code&gt;将这些指标数据查询接口转换为标准的&lt;code&gt;Kubernetes&lt;/code&gt;自定义指标。&lt;/p&gt;
&lt;p&gt;Prometheus是一个开源的服务监控系统和时序数据库，其提供了通用的数据模型和快捷数据采集、存储和查询接口。它的核心组件Prometheus服务器定期从静态配置的监控目标或者基于服务发现自动配置的目标中进行拉取数据，新拉取到的数据大于配置的内存缓存区时，数据就会持久化到存储设备当中。Prometheus组件架构图如下：
&lt;img alt="img" loading="lazy" src="/2018/09/19/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-24-prometheus%E7%9B%91%E6%8E%A7/1349539-20190323104228352-317414087.png"&gt;&lt;/p&gt;
&lt;p&gt;如上图，每个被监控的主机都可以通过专用的&lt;code&gt;exporter&lt;/code&gt;程序提供输出监控数据的接口，并等待&lt;code&gt;Prometheus&lt;/code&gt;服务器周期性的进行数据抓取。如果存在告警规则，则抓取到数据之后会根据规则进行计算，满足告警条件则会生成告警，并发送到&lt;code&gt;Alertmanager&lt;/code&gt;完成告警的汇总和分发。当被监控的目标有主动推送数据的需求时，可以以&lt;code&gt;Pushgateway&lt;/code&gt;组件进行接收并临时存储数据，然后等待&lt;code&gt;Prometheus&lt;/code&gt;服务器完成数据的采集。&lt;/p&gt;
&lt;p&gt;任何被监控的目标都需要事先纳入到监控系统中才能进行时序数据采集、存储、告警和展示，监控目标可以通过配置信息以静态形式指定，也可以让Prometheus通过服务发现的机制进行动态管理。下面是组件的一些解析：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;监控代理程序：如node_exporter：收集主机的指标数据，如平均负载、CPU、内存、磁盘、网络等等多个维度的指标数据。&lt;/li&gt;
&lt;li&gt;kubelet（cAdvisor）：收集容器指标数据，也是K8S的核心指标收集，每个容器的相关指标数据包括：CPU使用率、限额、文件系统读写限额、内存使用率和限额、网络报文发送、接收、丢弃速率等等。&lt;/li&gt;
&lt;li&gt;API Server：收集API Server的性能指标数据，包括控制队列的性能、请求速率和延迟时长等等&lt;/li&gt;
&lt;li&gt;etcd：收集etcd存储集群的相关指标数据&lt;/li&gt;
&lt;li&gt;kube-state-metrics：该组件可以派生出k8s相关的多个指标数据，主要是资源类型相关的计数器和元数据信息，包括制定类型的对象总数、资源限额、容器状态以及Pod资源标签系列等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prometheus 能够直接把 Kubernetes API Server作为服务发现系统使用进而动态发现和监控集群中的所有可被监控的对象。这里需要特别说明的是，Pod 资源需要添加下列注解信息才能被Prometheus 系统自动发现并抓取其内建的指标数据。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1） prometheus. io/ scrape： 用于 标识 是否 需要 被 采集 指标 数据， 布尔 型 值， true 或 false。&lt;/li&gt;
&lt;li&gt;2） prometheus. io/ path： 抓取 指标 数据 时 使用 的 URL 路径， 一般 为/ metrics。&lt;/li&gt;
&lt;li&gt;3） prometheus. io/ port： 抓取 指标 数据 时 使 用的 套 接 字 端口， 如 8080。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外， 仅 期望 Prometheus 为 后端 生成 自定义 指标 时 仅 部署 Prometheus 服务器 即可， 它 甚至 也不 需要 数据 持久 功能。 但 若要 配置 完整 功能 的 监控 系统， 管理员 还需 要在 每个 主机 上 部署 node_exporter、 按需部署其他特有类型的 exporter 以及Alertmanager。&lt;/p&gt;</description></item><item><title>k8s学习笔记-23-资源指标和集群监控</title><link>/2018/09/18/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-23-%E8%B5%84%E6%BA%90%E6%8C%87%E6%A0%87%E5%92%8C%E9%9B%86%E7%BE%A4%E7%9B%91%E6%8E%A7/</link><pubDate>Tue, 18 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/18/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-23-%E8%B5%84%E6%BA%90%E6%8C%87%E6%A0%87%E5%92%8C%E9%9B%86%E7%BE%A4%E7%9B%91%E6%8E%A7/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1资源指标和资源监控"&gt;1、资源指标和资源监控&lt;/h1&gt;
&lt;p&gt;一个集群系统管理离不开监控，同样的Kubernetes也需要根据数据指标来采集相关数据，从而完成对集群系统的监控状况进行监测。这些指标总体上分为两个组成：监控集群本身和监控Pod对象，通常一个集群的衡量性指标包括以下几个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;节点资源状态：主要包括网络带宽、磁盘空间、CPU和内存使用率&lt;/li&gt;
&lt;li&gt;节点的数量：即时性了解集群的可用节点数量可以为用户计算服务器使用的费用支出提供参考。&lt;/li&gt;
&lt;li&gt;运行的Pod对象：正在运行的Pod对象数量可以评估可用节点数量是否足够，以及节点故障时是否能平衡负载。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另一个方面，对Pod资源对象的监控需求大概有以下三类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes指标：监测特定应用程序相关的Pod对象的部署过程、副本数量、状态信息、健康状态、网络等等。&lt;/li&gt;
&lt;li&gt;容器指标：容器的资源需求、资源限制、CPU、内存、磁盘空间、网络带宽的实际占用情况。&lt;/li&gt;
&lt;li&gt;应用程序指标：应用程序自身的内建指标，和业务规则相关&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="2weave-scope监控集群"&gt;2、Weave Scope监控集群&lt;/h1&gt;
&lt;p&gt;Weave Scope 是 Docker 和 Kubernetes 可视化监控工具。Scope 提供了至上而下的集群基础设施和应用的完整视图，用户可以轻松对分布式的容器化应用进行实时监控和问题诊断。 对于复杂的应用编排和依赖关系，scope可以使用清晰的图标一览应用状态和拓扑关系。&lt;/p&gt;
&lt;h2 id="1weave-scope部署"&gt;（1）Weave Scope部署&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master mainfests]# kubectl apply -f &amp;#34;https://cloud.weave.works/k8s/scope.yaml?k8s-version=$(kubectl version | base64 | tr -d &amp;#39;\n&amp;#39;)&amp;#34;
namespace/weave created #创建名称空间weave，也可以在创建时指定名称空间
serviceaccount/weave-scope created #创建serviceaccount
clusterrole.rbac.authorization.k8s.io/weave-scope created
clusterrolebinding.rbac.authorization.k8s.io/weave-scope created
deployment.apps/weave-scope-app created #创建deployment
service/weave-scope-app created #创建service
daemonset.extensions/weave-scope-agent created #创建deamonset
[root@k8s-master mainfests]# kubectl get ns
NAME STATUS AGE
default Active 68d
ingress-nginx Active 28d
kube-public Active 68d
kube-system Active 68d
weave Active 1m
[root@k8s-master mainfests]# kubectl get deployment -n weave
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
weave-scope-app 1 1 1 1 1m
[root@k8s-master mainfests]# kubectl get svc -n weave
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
weave-scope-app ClusterIP 10.97.229.215 &amp;lt;none&amp;gt; 80/TCP 33s
[root@k8s-master mainfests]# kubectl get pod -n weave
NAME READY STATUS RESTARTS AGE
weave-scope-agent-5876w 1/1 Running 0 1m
weave-scope-agent-d6jgt 1/1 Running 0 1m
weave-scope-agent-t9p5g 1/1 Running 0 1m
weave-scope-app-578556559-nfxrf 1/1 Running 0 1m
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;DaemonSet weave-scope-agent，集群每个节点上都会运行的 scope agent 程序，负责收集数据。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;Deployment &lt;code&gt;weave-scope-app&lt;/code&gt;，scope 应用，从 agent 获取数据，通过 Web UI 展示并与用户交互。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;Service &lt;code&gt;weave-scope-app&lt;/code&gt;，默认是 ClusterIP 类型，为了方便已通过 &lt;code&gt;kubectl edit&lt;/code&gt; 修改为 &lt;code&gt;NodePort&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master mainfests]# kubectl edit svc/weave-scope-app -n weave
将service的type改为NodePort
service/weave-scope-app edited
[root@k8s-master mainfests]# kubectl get svc -n weave
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
weave-scope-app NodePort 10.97.229.215 &amp;lt;none&amp;gt; 80:32313/TCP 11m
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2使用-scope"&gt;（2）使用 Scope&lt;/h2&gt;
&lt;p&gt;浏览器访问 &lt;code&gt;http://192.168.56.11:32313/&lt;/code&gt;，Scope 默认显示当前所有的 Controller（Deployment、DaemonSet 等）。&lt;/p&gt;</description></item><item><title>k8s学习笔记-20-K8S组件运行原理详解总结</title><link>/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-20-k8s%E7%BB%84%E4%BB%B6%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3%E6%80%BB%E7%BB%93/</link><pubDate>Sat, 15 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-20-k8s%E7%BB%84%E4%BB%B6%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3%E6%80%BB%E7%BB%93/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="一看图说k8s"&gt;一、看图说K8S&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;先从一张大图来观看一下K8S是如何运作的，再具体去细化K8S的概念、组件以及网络模型。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-20-k8s%E7%BB%84%E4%BB%B6%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3%E6%80%BB%E7%BB%93/1349539-20190307152625497-1629030475.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从上图，我们可以看到K8S组件和逻辑及其复杂，但是这并不可怕，我们从宏观上先了解K8S是怎么用的，再进行庖丁解牛。从上图我们可以看出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes集群主要由Master和Node两类节点组成&lt;/li&gt;
&lt;li&gt;Master的组件包括：apiserver、controller-manager、scheduler和etcd等几个组件，其中apiserver是整个集群的网关。&lt;/li&gt;
&lt;li&gt;Node主要由kubelet、kube-proxy、docker引擎等组件组成。kubelet是K8S集群的工作与节点上的代理组件。&lt;/li&gt;
&lt;li&gt;一个完整的K8S集群，还包括CoreDNS、Prometheus（或HeapSter）、Dashboard、Ingress Controller等几个附加组件。其中cAdivsor组件作用于各个节点（master和node节点）之上，用于收集及收集容器及节点的CPU、内存以及磁盘资源的利用率指标数据，这些统计数据由Heapster聚合后，可以通过apiserver访问。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;要了解K8S的所有组件，没去走一遍，永远不知道它是怎么跑起来的，那么下面就带着几个新手疑问来看K8S&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1、K8S是如何对容器编排？&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;在K8S集群中，容器并非最小的单位，K8S集群中最小的调度单位是Pod，容器则被封装在Pod之中。由此可知，一个容器或多个容器可以同属于在一个Pod之中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2、Pod是怎么创建出来的？&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Pod并不是无缘无故跑出来的，它是一个抽象的l逻辑概念，那么Pod是如何创建的呢？Pod是由Pod控制器进行管理控制，其代表性的Pod控制器有Deployment、StatefulSet等。这里我们先有这样的一个概念，后面再详细解刨。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;3、Pod资源组成的应用如何提供外部访问的？&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Pod组成的应用是通过Service这类抽象资源提供内部和外部访问的，但是service的外部访问需要端口的映射，带来的是端口映射的麻烦和操作的繁琐。为此还有一种提供外部访问的资源叫做Ingress。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;4、Service又是怎么关联到Pod呢？&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;在上面说的Pod是由Pod控制器进行管理控制，对Pod资源对象的期望状态进行自动管理。而在Pod控制器是通过一个YAML的文件进行定义Pod资源对象的。在该文件中，还会对Pod资源对象进行打标签，用于Pod的辨识，而Servcie就是通过标签选择器，关联至同一标签类型的Pod资源对象。这样就实现了从service&amp;ndash;&amp;gt;pod&amp;ndash;&amp;gt;container的一个过程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;5、Pod的怎么创建逻辑流程是怎样的？&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;（1）客户端提交创建请求，可以通过API Server的Restful API，也可以使用kubectl命令行工具。支持的数据类型包括JSON和YAML。&lt;/li&gt;
&lt;li&gt;（2）API Server处理用户请求，存储Pod数据到etcd。&lt;/li&gt;
&lt;li&gt;（3）调度器通过API Server查看未绑定的Pod。尝试为Pod分配主机。&lt;/li&gt;
&lt;li&gt;（4）过滤主机 (调度预选)：调度器用一组规则过滤掉不符合要求的主机。比如Pod指定了所需要的资源量，那么可用资源比Pod需要的资源量少的主机会被过滤掉。&lt;/li&gt;
&lt;li&gt;（5）主机打分(调度优选)：对第一步筛选出的符合要求的主机进行打分，在主机打分阶段，调度器会考虑一些整体优化策略，比如把容一个Replication Controller的副本分布到不同的主机上，使用最低负载的主机等。&lt;/li&gt;
&lt;li&gt;（6）选择主机：选择打分最高的主机，进行binding操作，结果存储到etcd中。&lt;/li&gt;
&lt;li&gt;（7）kubelet根据调度结果执行Pod创建操作： 绑定成功后，scheduler会调用APIServer的API在etcd中创建一个boundpod对象，描述在一个工作节点上绑定运行的所有pod信息。运行在每个工作节点上的kubelet也会定期与etcd同步boundpod信息，一旦发现应该在该工作节点上运行的boundpod对象没有更新，则调用Docker API创建并启动pod内的容器。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;从上面的几个疑问，大致了解了K8S怎么工作的，那么现在再从三个面去了解Kubernetes，分别是Kubernetes概念和术语、集群组件、网络模型。&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id="二k8s的概念和术语"&gt;二、K8S的概念和术语&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Kubernetes是利用共享网络将多个物理机或者虚拟机组成一个集群，在各个服务器之间进行通信，该集群是配置Kubernetes的所有租金啊啊、功能和负载的物理平台。&lt;/p&gt;
&lt;p&gt;一个Kubernetes集群由master和node组成。如下图：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Master：是集群的网关和中枢枢纽，主要作用：暴露API接口，跟踪其他服务器的健康状态、以最优方式调度负载，以及编排其他组件之间的通信。单个的Master节点可以完成所有的功能，但是考虑单点故障的痛点，生产环境中通常要部署多个Master节点，组成Cluster。&lt;/li&gt;
&lt;li&gt;Node：是Kubernetes的工作节点，负责接收来自Master的工作指令，并根据指令相应地创建和销毁Pod对象，以及调整网络规则进行合理路由和流量转发。生产环境中，Node节点可以有N个。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-20-k8s%E7%BB%84%E4%BB%B6%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3%E6%80%BB%E7%BB%93/1349539-20180706110354540-513539099.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Kubernetes从宏观上看分为2个角色：Master和Node，但是在Master节点和Node节点上都存在着多个组件来支持内部的业务逻辑，其包括：运行应用、应用编排、服务暴露、应用恢复等等，在Kubernetes中这些概念被抽象为Pod、Service、Controller等资源类型。先来了解一下这些常用概念和术语：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;（1）Pod&lt;/p&gt;
&lt;p&gt;从上图，我们可以看到K8S并不直接地运行容器，而是被一个抽象的资源对象&amp;ndash;Pod所封装，它是K8S最小的调度单位。这里要注意的是，Pod可以封装一个活多个容器！同一个Pod中共享网络名称空间和存储资源，而容器之间可以通过本地回环接口：lo 直接通信，但是彼此之间又在Mount、User和Pid等名称空间上保持了隔离。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;（2）资源标签（Label）&lt;/p&gt;
&lt;p&gt;标签（Label）是将资源进行分类的标识符，就好像超市的商品分类一般。资源标签具体化的就是一个键值型（key/values)数据，相信了解redis的友友应该知道什么是键值数据。使用标签是为了对指定对象进行辨识，比如Pod对象。标签可以在对象创建时进行附加，也可以创建后进行添加或修改。要知道的是一个对象可以有多个标签，一个标签页可以附加到多个对象。如图：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-20-k8s%E7%BB%84%E4%BB%B6%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3%E6%80%BB%E7%BB%93/1349539-20190119113920669-1808345614.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;（3）标签选择器（Selector）&lt;/p&gt;
&lt;p&gt;有标签，当然就有标签选择器，它是根据Label进行过滤符合条件的资源对象的一种 机制。比如将含有标签&lt;code&gt;role: backend&lt;/code&gt;的所有Pod对象挑选出来归并为一组。通常在使用过程中，会通过标签对资源对象进行分类，然后再通过标签选择器进行筛选，最常见的应用就是讲一组这样的Pod资源对象创建为某个Service的端点。如图：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-20-k8s%E7%BB%84%E4%BB%B6%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3%E6%80%BB%E7%BB%93/1349539-20190119113954245-190016216.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;（4）Pod控制器（Controller）&lt;/p&gt;
&lt;p&gt;虽然Pod是K8S的最小调度单位，但是K8S并不会直接地部署和管理Pod对象，而是要借助于另外一个抽象资源&amp;ndash;Controller进行管理。其实一种管理Pod生命周期的资源抽象，并且它是一类对象，并非单个的资源对象，其中包括：ReplicationController、ReplicaSet、Deployment、StatefulSet、Job等。&lt;/p&gt;
&lt;p&gt;以Deployment为例，它负责确保定义的Pod对象的副本数量符合预期的设置，这样用户只需要声明应用的期望状态，控制器就会自动地对其进行管理。如图：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-20-k8s%E7%BB%84%E4%BB%B6%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3%E6%80%BB%E7%BB%93/1349539-20190119114016205-1879336161.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;（5）服务资源（Service）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Service是建立在一组Pod对象之上的资源对象，在前面提过，它是通过标签选择器选择一组Pod对象，并为这组Pod对象定义一个统一的固定访问入口（通常是一个IP地址），如果K8S存在DNS附件（如coredns）它就会在Service创建时为它自动配置一个DNS名称，用于客户端进行服务发现。&lt;/p&gt;
&lt;p&gt;通常我们直接请求Service IP，该请求就会被负载均衡到后端的端点，即各个Pod对象，从这点上，是不是有点像负载均衡器呢，因此Service本质上是一个4层的代理服务，另外Service还可以将集群外部流量引入至集群，这就需要节点对Service的端口进行映射了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;（6）存储卷（Volume）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在使用容器时，我们知道，当数据存放于容器之中，容器销毁后，数据也会随之丢失。这就是需要一个外部存储，以保证数据的持久化存储。而存储卷就是这样的一个东西。&lt;/p&gt;
&lt;p&gt;存储卷（Volume）是独立于容器文件系统之外的存储空间，常用于扩展容器的存储空间并为其提供持久存储能力。存储卷在K8S中的分类为：临时卷、本地卷和网络卷。临时卷和本地卷都位于Node本地，一旦Pod被调度至其他Node节点，此类型的存储卷将无法被访问，因为临时卷和本地卷通常用于数据缓存，持久化的数据通常放置于持久卷（persistent volume）之中。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;（7）Name和Namespace&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;名称（Name）是K8S集群中资源对象的标识符，通常作用于名称空间（Namespace），因此名称空间是名称的额外的限定机制。在同一个名称空间中，同一类型资源对象的名称必须具有唯一性。&lt;/p&gt;</description></item><item><title>k8s学习笔记-22-Pod资源调度</title><link>/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-22-pod%E8%B5%84%E6%BA%90%E8%B0%83%E5%BA%A6/</link><pubDate>Sat, 15 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-22-pod%E8%B5%84%E6%BA%90%E8%B0%83%E5%BA%A6/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;API Server在接受客户端提交Pod对象创建请求后，然后是通过调度器（kube-schedule）从集群中选择一个可用的最佳节点来创建并运行Pod。而这一个创建Pod对象，在调度的过程当中有3个阶段：节点预选、节点优选、节点选定，从而筛选出最佳的节点。如图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-22-pod%E8%B5%84%E6%BA%90%E8%B0%83%E5%BA%A6/1349539-20190315110744164-1442880527.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;节点预选：基于一系列的预选规则对每个节点进行检查，将那些不符合条件的节点过滤，从而完成节点的预选&lt;/li&gt;
&lt;li&gt;节点优选：对预选出的节点进行优先级排序，以便选出最合适运行Pod对象的节点&lt;/li&gt;
&lt;li&gt;节点选定：从优先级排序结果中挑选出优先级最高的节点运行Pod，当这类节点多于1个时，则进行随机选择&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当我们有需求要将某些Pod资源运行在特定的节点上时，我们可以通过组合节点标签，以及Pod标签或标签选择器来匹配特定的预选策略并完成调度，如&lt;code&gt;MatchInterPodAfinity、MatchNodeSelector、PodToleratesNodeTaints&lt;/code&gt;等预选策略，这些策略常用于为用户提供自定义Pod亲和性或反亲和性、节点亲和性以及基于污点及容忍度的调度机制。&lt;/p&gt;
&lt;h1 id="1常用的预选策略"&gt;1、常用的预选策略&lt;/h1&gt;
&lt;p&gt;预选策略实际上就是节点过滤器，例如节点标签必须能够匹配到Pod资源的标签选择器（MatchNodeSelector实现的规则），以及Pod容器的资源请求量不能大于节点上剩余的可分配资源（PodFitsResource规则）等等。执行预选操作，调度器会逐一根据规则进行筛选，如果预选没能选定一个合适的节点，此时Pod会一直处于Pending状态，直到有一个可用节点完成调度。其常用的预选策略如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CheckNodeCondition：检查是否可以在节点报告磁盘、网络不可用或未准备好的情况下将Pod对象调度其上。&lt;/li&gt;
&lt;li&gt;HostName：如果Pod对象拥有spec.hostname属性，则检查节点名称字符串是否和该属性值匹配。&lt;/li&gt;
&lt;li&gt;PodFitsHostPorts：如果Pod对象定义了ports.hostPort属性，则检查Pod指定的端口是否已经被节点上的其他容器或服务占用。&lt;/li&gt;
&lt;li&gt;MatchNodeSelector：如果Pod对象定义了spec.nodeSelector属性，则检查节点标签是否和该属性匹配。&lt;/li&gt;
&lt;li&gt;NoDiskConflict：检查Pod对象请求的存储卷在该节点上可用。&lt;/li&gt;
&lt;li&gt;PodFitsResources：检查节点上的资源（CPU、内存）可用性是否满足Pod对象的运行需求。&lt;/li&gt;
&lt;li&gt;PodToleratesNodeTaints：如果Pod对象中定义了spec.tolerations属性，则需要检查该属性值是否可以接纳节点定义的污点（taints）。&lt;/li&gt;
&lt;li&gt;PodToleratesNodeNoExecuteTaints：如果Pod对象定义了spec.tolerations属性，检查该属性是否接纳节点的NoExecute类型的污点。&lt;/li&gt;
&lt;li&gt;CheckNodeLabelPresence：仅检查节点上指定的所有标签的存在性，要检查的标签以及其可否存在取决于用户的定义。&lt;/li&gt;
&lt;li&gt;CheckServiceAffinity：根据当前Pod对象所属的Service已有其他Pod对象所运行的节点调度，目前是将相同的Service的Pod对象放在同一个或同一类节点上。&lt;/li&gt;
&lt;li&gt;MaxEBSVolumeCount：检查节点上是否已挂载EBS存储卷数量是否超过了设置的最大值，默认值：39&lt;/li&gt;
&lt;li&gt;MaxGCEPDVolumeCount：检查节点上已挂载的GCE PD存储卷是否超过了设置的最大值，默认值：16&lt;/li&gt;
&lt;li&gt;MaxAzureDiskVolumeCount：检查节点上已挂载的Azure Disk存储卷数量是否超过了设置的最大值，默认值：16&lt;/li&gt;
&lt;li&gt;CheckVolumeBinding：检查节点上已绑定和未绑定的PVC是否满足Pod对象的存储卷需求。&lt;/li&gt;
&lt;li&gt;NoVolumeZoneConflct：在给定了区域限制的前提下，检查在该节点上部署Pod对象是否存在存储卷冲突。&lt;/li&gt;
&lt;li&gt;CheckNodeMemoryPressure：在给定了节点已经上报了存在内存资源压力过大的状态，则需要检查该Pod是否可以调度到该节点上。&lt;/li&gt;
&lt;li&gt;CheckNodePIDPressure：如果给定的节点已经报告了存在PID资源压力过大的状态，则需要检查该Pod是否可以调度到该节点上。&lt;/li&gt;
&lt;li&gt;CheckNodeDiskPressure：如果给定的节点存在磁盘资源压力过大，则检查该Pod对象是否可以调度到该节点上。&lt;/li&gt;
&lt;li&gt;MatchInterPodAffinity：检查给定的节点能否可以满足Pod对象的亲和性和反亲和性条件，用来实现Pod亲和性调度或反亲和性调度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在上面的这些预选策略里面，CheckNodeLabelPressure和CheckServiceAffinity可以在预选过程中结合用户自定义调度逻辑，这些策略叫做可配置策略。其他不接受参数进行自定义配置的称为静态策略。&lt;/p&gt;
&lt;h1 id="2优选函数"&gt;2、优选函数&lt;/h1&gt;
&lt;p&gt;预选策略筛选出一个节点列表就会进入优选阶段，在这个过程调度器会向每个通过预选的节点传递一系列的优选函数来计算其优先级分值，优先级分值介于0-10之间，其中0表示不适用，10表示最适合托管该Pod对象。&lt;/p&gt;
&lt;p&gt;另外，调度器还支持给每个优选函数指定一个简单的值，表示权重，进行节点优先级分值计算时，它首先将每个优选函数的计算得分乘以权重，然后再将所有优选函数的得分相加，从而得出节点的最终优先级分值。权重可以让管理员定义优选函数倾向性的能力，其计算优先级的得分公式如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + ......
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;下图是关于优选函数的列表图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-22-pod%E8%B5%84%E6%BA%90%E8%B0%83%E5%BA%A6/1349539-20190315110812120-2048644331.png"&gt;&lt;/p&gt;
&lt;h1 id="3节点亲和调度"&gt;3、节点亲和调度&lt;/h1&gt;
&lt;p&gt;节点亲和性是用来确定Pod对象调度到哪一个节点的规则，这些规则基于节点上的自定义标签和Pod对象上指定的标签选择器进行定义。&lt;/p&gt;
&lt;p&gt;定义节点亲和性规则有2种：硬亲和性（require）和软亲和性（preferred）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;硬亲和性：实现的是强制性规则，是Pod调度时必须满足的规则，否则Pod对象的状态会一直是Pending&lt;/li&gt;
&lt;li&gt;软亲和性：实现的是一种柔性调度限制，在Pod调度时可以尽量满足其规则，在无法满足规则时，可以调度到一个不匹配规则的节点之上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;定义节点亲和规则的两个要点：一是节点配置是否合乎需求的标签，而是Pod对象定义合理的标签选择器，这样才能够基于标签选择出期望的目标节点。&lt;/p&gt;
&lt;p&gt;需要注意的是&lt;code&gt;preferredDuringSchedulingIgnoredDuringExecution&lt;/code&gt;和&lt;code&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/code&gt;名字中后半段字符串&lt;code&gt;IgnoredDuringExecution&lt;/code&gt;表示的是，在Pod资源基于节点亲和性规则调度到某个节点之后，如果节点的标签发生了改变，调度器不会讲Pod对象从该节点上移除，因为该规则仅对新建的Pod对象有效。&lt;/p&gt;
&lt;h2 id="31节点硬亲和性"&gt;3.1、节点硬亲和性&lt;/h2&gt;
&lt;p&gt;下面的配置清单中定义的Pod对象，使用节点硬亲和性和规则定义将当前Pod调度到标签为zone=foo的节点上：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
name: with-require-nodeaffinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone,operator: In,values: [&amp;#34;foo&amp;#34;]}
containers:
- name: myapp
image: ikubernetes/myapp:v1
#创建Pod对象
[root@k8s-master ~]# kubectl apply -f require-nodeAffinity-pod.yaml
pod/with-require-nodeaffinity created
#由于集群中并没有节点含有节点标签为zone=foo，所以创建的Pod一直处于Pending状态
[root@k8s-master ~]# kubectl get pods with-require-nodeaffinity
NAME READY STATUS RESTARTS AGE
with-require-nodeaffinity 0/1 Pending 0 35s
#查看Pending具体的原因
[root@k8s-master ~]# kubectl describe pods with-require-nodeaffinity
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 3s (x21 over 1m) default-scheduler 0/3 nodes are available: 3 node(s) didn&amp;#39;t match node selector.
#给node01节点打上zone=foo的标签，可以看到成功调度到node01节点上
[root@k8s-master ~]# kubectl label node k8s-node01 zone=foo
node/k8s-node01 labeled
[root@k8s-master ~]# kubectl describe pods with-require-nodeaffinity
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 58s (x25 over 2m) default-scheduler 0/3 nodes are available: 3 node(s) didn&amp;#39;t match node selector.
Normal Pulled 4s kubelet, k8s-node01 Container image &amp;#34;ikubernetes/myapp:v1&amp;#34; already present on machine
Normal Created 4s kubelet, k8s-node01 Created container
Normal Started 4s kubelet, k8s-node01 Started container
[root@k8s-master ~]# kubectl get pods with-require-nodeaffinity -o wide
NAME READY STATUS RESTARTS AGE IP NODE
with-require-nodeaffinity 1/1 Running 0 6m 10.244.1.12 k8s-node01
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在定义节点亲和性时，&lt;code&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/code&gt;字段的值是一个对象列表，用于定义节点硬亲和性，它可以由一个或多个&lt;code&gt;nodeSelectorTerms&lt;/code&gt;定义的对象组成，此时值需要满足其中一个&lt;code&gt;nodeSelectorTerms&lt;/code&gt;即可。&lt;/p&gt;</description></item><item><title>k8s学习笔记-21-k8s的网络模型和网络策略</title><link>/2018/09/12/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-21-k8s%E7%9A%84%E7%BD%91%E7%BB%9C%E6%A8%A1%E5%9E%8B%E5%92%8C%E7%BD%91%E7%BB%9C%E7%AD%96%E7%95%A5/</link><pubDate>Wed, 12 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/12/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-21-k8s%E7%9A%84%E7%BD%91%E7%BB%9C%E6%A8%A1%E5%9E%8B%E5%92%8C%E7%BD%91%E7%BB%9C%E7%AD%96%E7%95%A5/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1kubernetes网络模型和cni插件"&gt;1、Kubernetes网络模型和CNI插件&lt;/h1&gt;
&lt;p&gt;在Kubernetes中设计了一种网络模型，要求无论容器运行在集群中的哪个节点，所有容器都能通过一个扁平的网络平面进行通信，即在同一IP网络中。需要注意的是：在K8S集群中，IP地址分配是以Pod对象为单位，而非容器，同一Pod内的所有容器共享同一网络名称空间。&lt;/p&gt;
&lt;h2 id="11docker网络模型"&gt;1.1、Docker网络模型&lt;/h2&gt;
&lt;p&gt;了解Docker的友友们都应该清楚，Docker容器的原生网络模型主要有3种：Bridge（桥接）、Host（主机）、none。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bridge：借助虚拟网桥设备为容器建立网络连接。&lt;/li&gt;
&lt;li&gt;Host：设置容器直接共享当前节点主机的网络名称空间。&lt;/li&gt;
&lt;li&gt;none：多个容器共享同一个网络名称空间。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#使用以下命令查看docker原生的三种网络
[root@localhost ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
0efec019c899 bridge bridge local
40add8bb5f07 host host local
ad94f0b1cca6 none null local
#none网络，在该网络下的容器仅有lo网卡，属于封闭式网络，通常用于对安全性要求较高并且不需要联网的应用
[root@localhost ~]# docker run -it --network=none busybox
/ # ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
#host网络，共享宿主机的网络名称空间，容器网络配置和host一致，但是存在端口冲突的问题
[root@localhost ~]# docker run -it --network=host busybox
/ # ip addr
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc pfifo_fast qlen 1000
link/ether 00:0c:29:69:a7:23 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.4/24 brd 192.168.1.255 scope global dynamic eth0
valid_lft 84129sec preferred_lft 84129sec
inet6 fe80::20c:29ff:fe69:a723/64 scope link
valid_lft forever preferred_lft forever
3: docker0: &amp;lt;NO-CARRIER,BROADCAST,MULTICAST,UP&amp;gt; mtu 1500 qdisc noqueue
link/ether 02:42:29:09:8f:dd brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:29ff:fe09:8fdd/64 scope link
valid_lft forever preferred_lft forever
/ # hostname
localhost
#bridge网络，Docker安装完成时会创建一个名为docker0的linux bridge，不指定网络时，创建的网络默认为桥接网络，都会桥接到docker0上。
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024229098fdd no
[root@localhost ~]# docker run -d nginx #运行一个nginx容器
c760a1b6c9891c02c992972d10a99639d4816c4160d633f1c5076292855bbf2b
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024229098fdd no veth3f1b114
一个新的网络接口veth3f1b114桥接到了docker0上，veth3f1b114就是新创建的容器的虚拟网卡。进入容器查看其网络配置：
[root@localhost ~]# docker exec -it c760a1b6c98 bash
root@c760a1b6c989:/# apt-get update
root@c760a1b6c989:/# apt-get iproute
root@c760a1b6c989:/# ip a
1: lo: &amp;lt;LOOPBACK,UP,LOWER_UP&amp;gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
38: eth0@if39: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从上可以看到容器内有一个网卡&lt;code&gt;eth0@if39&lt;/code&gt;，实际上&lt;code&gt;eth0@if39&lt;/code&gt;和&lt;code&gt;veth3f1b114&lt;/code&gt;是一对&lt;code&gt;veth pair&lt;/code&gt;。&lt;code&gt;veth pair&lt;/code&gt;是一种成对出现的特殊网络设备，可以想象它们由一根虚拟的网线进行连接的一对网卡，&lt;code&gt;eth0@if39&lt;/code&gt;在容器中，&lt;code&gt;veth3f1b114&lt;/code&gt;挂在网桥&lt;code&gt;docker0&lt;/code&gt;上，最终的效果就是&lt;code&gt;eth0@if39&lt;/code&gt;也挂在了&lt;code&gt;docker0上&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>k8s学习笔记-19-Kubernetes dashboard认证访问</title><link>/2018/09/10/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-19-kubernetes-dashboard%E8%AE%A4%E8%AF%81%E8%AE%BF%E9%97%AE/</link><pubDate>Mon, 10 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/10/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-19-kubernetes-dashboard%E8%AE%A4%E8%AF%81%E8%AE%BF%E9%97%AE/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Dashboard:https://github.com/kubernetes/dashboard&lt;/p&gt;
&lt;h2 id="一dashboard部署"&gt;一、Dashboard部署&lt;/h2&gt;
&lt;p&gt;由于需要用到k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.0，这里有2种方式进行pull 镜像。docker search该镜像名称，直接pull，再重新进行tag；另外一种方式是通过谷歌容器镜像拉取。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-node01 ~]# docker pull siriuszg/kubernetes-dashboard-amd64
[root@k8s-node01 ~]# docker tag siriuszg/kubernetes-dashboard-amd64:latest k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.0
或者是
[root@k8s-node01 ~]# docker pull mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;再看其部署的过程：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master ~]# kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
secret/kubernetes-dashboard-certs created
serviceaccount/kubernetes-dashboard created
role.rbac.authorization.k8s.io/kubernetes-dashboard-minimal created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard-minimal created
deployment.apps/kubernetes-dashboard created
service/kubernetes-dashboard created
[root@k8s-master ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-78fcdf6894-nmcmz 1/1 Running 1 54d
coredns-78fcdf6894-p5pfm 1/1 Running 1 54d
etcd-k8s-master 1/1 Running 2 54d
kube-apiserver-k8s-master 1/1 Running 9 54d
kube-controller-manager-k8s-master 1/1 Running 5 54d
kube-flannel-ds-n5c86 1/1 Running 1 54d
kube-flannel-ds-nrcw2 1/1 Running 1 52d
kube-flannel-ds-pgpr7 1/1 Running 5 54d
kube-proxy-glzth 1/1 Running 1 52d
kube-proxy-rxlt7 1/1 Running 2 54d
kube-proxy-vxckf 1/1 Running 4 54d
kube-scheduler-k8s-master 1/1 Running 3 54d
kubernetes-dashboard-767dc7d4d-n4clq 1/1 Running 0 3s
[root@k8s-master ~]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 &amp;lt;none&amp;gt; 53/UDP,53/TCP 54d
kubernetes-dashboard ClusterIP 10.105.204.4 &amp;lt;none&amp;gt; 443/TCP 30m
[root@k8s-master ~]# kubectl patch svc kubernetes-dashboard -p &amp;#39;{&amp;#34;spec&amp;#34;:{&amp;#34;type&amp;#34;:&amp;#34;NodePort&amp;#34;}}&amp;#39; -n kube-system #以打补丁方式修改dasboard的访问方式
service/kubernetes-dashboard patched
[root@k8s-master ~]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 &amp;lt;none&amp;gt; 53/UDP,53/TCP 54d
kubernetes-dashboard NodePort 10.105.204.4 &amp;lt;none&amp;gt; 443:32645/TCP 31m
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;浏览器访问：https://192.168.56.12:32645，如图：&lt;strong&gt;这里需要注意的是谷歌浏览器会禁止不安全证书访问，建议使用火狐浏览器，并且需要在高级选项中添加信任&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>k8s学习笔记-18-认证、授权和准入控制</title><link>/2018/09/08/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-18-%E8%AE%A4%E8%AF%81%E6%8E%88%E6%9D%83%E5%92%8C%E5%87%86%E5%85%A5%E6%8E%A7%E5%88%B6/</link><pubDate>Sat, 08 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/08/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-18-%E8%AE%A4%E8%AF%81%E6%8E%88%E6%9D%83%E5%92%8C%E5%87%86%E5%85%A5%E6%8E%A7%E5%88%B6/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;API Server作为Kubernetes网关，是访问和管理资源对象的唯一入口，其各种集群组件访问资源都需要经过网关才能进行正常访问和管理。每一次的访问请求都需要进行合法性的检验，其中包括身份验证、操作权限验证以及操作规范验证等，需要通过一系列验证通过之后才能访问或者存储数据到etcd当中。如下图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/08/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-18-%E8%AE%A4%E8%AF%81%E6%8E%88%E6%9D%83%E5%92%8C%E5%87%86%E5%85%A5%E6%8E%A7%E5%88%B6/1349539-20190307160125484-1365856991.png"&gt;&lt;/p&gt;
&lt;h2 id="一serviceaccount"&gt;一、ServiceAccount&lt;/h2&gt;
&lt;p&gt;Service account是为了方便Pod里面的进程调用Kubernetes API或其他外部服务而设计的。它与User account不同&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User account是为人设计的，而service account则是为Pod中的进程调用Kubernetes API而设计；&lt;/li&gt;
&lt;li&gt;User account是跨namespace的，而service account则是仅局限它所在的namespace；&lt;/li&gt;
&lt;li&gt;每个namespace都会自动创建一个default service account&lt;/li&gt;
&lt;li&gt;Token controller检测service account的创建，并为它们创建&lt;a href="https://www.kubernetes.org.cn/secret"&gt;secret&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;开启ServiceAccount Admission Controller后
&lt;ul&gt;
&lt;li&gt;每个Pod在创建后都会自动设置spec.serviceAccount为default（除非指定了其他ServiceAccout）&lt;/li&gt;
&lt;li&gt;验证Pod引用的service account已经存在，否则拒绝创建&lt;/li&gt;
&lt;li&gt;如果Pod没有指定ImagePullSecrets，则把service account的ImagePullSecrets加到Pod中&lt;/li&gt;
&lt;li&gt;每个container启动后都会挂载该service account的token和ca.crt到/var/run/secrets/kubernetes.io/serviceaccount/&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当创建 pod 的时候，如果没有指定一个 service account，系统会自动在与该pod 相同的 namespace 下为其指派一个default service account。而pod和apiserver之间进行通信的账号，称为serviceAccountName。如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
filebeat-ds-hxgdx 1/1 Running 1 34d
filebeat-ds-s466l 1/1 Running 2 34d
myapp-0 1/1 Running 0 3h
myapp-1 1/1 Running 0 3h
myapp-2 1/1 Running 0 4h
myapp-3 1/1 Running 0 4h
pod-vol-demo 2/2 Running 0 2d
redis-5b5d6fbbbd-q8ppz 1/1 Running 1 2d
[root@k8s-master ~]# kubectl get pods/myapp-0 -o yaml |grep &amp;#34;serviceAccountName&amp;#34;
serviceAccountName: default
[root@k8s-master ~]# kubectl describe pods myapp-0
Name: myapp-0
Namespace: default
......
Volumes:
......
default-token-j5pf5:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-j5pf5
Optional: false
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从上面可以看到每个Pod无论定义与否都会有个存储卷，这个存储卷为default-token-*** token令牌，这就是pod和serviceaccount认证信息。通过secret进行定义，由于认证信息属于敏感信息，所以需要保存在secret资源当中，并以存储卷的方式挂载到Pod当中。从而让Pod内运行的应用通过对应的secret中的信息来连接apiserver，并完成认证。每个 namespace 中都有一个默认的叫做 default 的 service account 资源。进行查看名称空间内的secret，也可以看到对应的default-token。让当前名称空间中所有的pod在连接apiserver时可以使用的预制认证信息，从而保证pod之间的通信。&lt;/p&gt;</description></item><item><title>k8s学习笔记-17-statefulset控制器</title><link>/2018/09/03/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-17-statefulset%E6%8E%A7%E5%88%B6%E5%99%A8/</link><pubDate>Mon, 03 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/03/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-17-statefulset%E6%8E%A7%E5%88%B6%E5%99%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="一statefulset简介"&gt;一、statefulset简介&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;    从前面的学习我们知道使用Deployment创建的pod是无状态的，当挂载了Volume之后，如果该pod挂了，Replication Controller会再启动一个pod来保证可用性，但是由于pod是无状态的，pod挂了就会和之前的Volume的关系断开，新创建的Pod无法找到之前的Pod。但是对于用户而言，他们对底层的Pod挂了是没有感知的，但是当Pod挂了之后就无法再使用之前挂载的存储卷。&lt;/p&gt;
&lt;p&gt;    为了解决这一问题，就引入了StatefulSet用于保留Pod的状态信息。&lt;/p&gt;
&lt;p&gt;    StatefulSet是为了解决有状态服务的问题（对应Deployments和ReplicaSets是为无状态服务而设计），其应用场景包括：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;1、稳定的持久化存储，即Pod重新调度后还是能访问到相同的持久化数据，基于PVC来实现&lt;/li&gt;
&lt;li&gt;2、稳定的网络标志，即Pod重新调度后其PodName和HostName不变，基于Headless Service（即没有Cluster IP的Service）来实现&lt;/li&gt;
&lt;li&gt;3、有序部署，有序扩展，即Pod是有顺序的，在部署或者扩展的时候要依据定义的顺序依次依次进行（即从0到N-1，在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态），基于init containers来实现&lt;/li&gt;
&lt;li&gt;4、有序收缩，有序删除（即从N-1到0）&lt;/li&gt;
&lt;li&gt;5、有序的滚动更新&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;从上面的应用场景可以发现，StatefulSet由以下几个部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Headless Service（无头服务）用于为Pod资源标识符生成可解析的DNS记录。&lt;/li&gt;
&lt;li&gt;volumeClaimTemplates （存储卷申请模板）基于静态或动态PV供给方式为Pod资源提供专有的固定存储。&lt;/li&gt;
&lt;li&gt;StatefulSet，用于管控Pod资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h1 id="二为什么要有headless"&gt;二、为什么要有headless？？&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;    在deployment中，每一个pod是没有名称，是随机字符串，是无序的。而statefulset中是要求有序的，每一个pod的名称必须是固定的。当节点挂了，重建之后的标识符是不变的，每一个节点的节点名称是不能改变的。pod名称是作为pod识别的唯一标识符，必须保证其标识符的稳定并且唯一。
    为了实现标识符的稳定，这时候就需要一个headless service 解析直达到pod，还需要给pod配置一个唯一的名称。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id="三为什么要-有volumeclaintemplate"&gt;三、为什么要 有volumeClainTemplate？？&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;    大部分有状态副本集都会用到持久存储，比如分布式系统来说，由于数据是不一样的，每个节点都需要自己专用的存储节点。而在deployment中pod模板中创建的存储卷是一个共享的存储卷，多个pod使用同一个存储卷，而statefulset定义中的每一个pod都不能使用同一个存储卷，由此基于pod模板创建pod是不适应的，这就需要引入volumeClainTemplate，当在使用statefulset创建pod时，会自动生成一个PVC，从而请求绑定一个PV，从而有自己专用的存储卷。Pod名称、PVC和PV关系图如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="1349539-20190307152625497-1629030475" loading="lazy" src="/2018/09/03/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-17-statefulset%E6%8E%A7%E5%88%B6%E5%99%A8/1349539-20190307152625497-1629030475.png"&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id="四statefulset使用演示"&gt;四、statefulSet使用演示&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;在创建StatefulSet之前需要准备的东西，值得注意的是创建顺序非常关键，创建顺序如下：
1、Volume
2、Persistent Volume
3、Persistent Volume Claim
4、Service
5、StatefulSet
Volume可以有很多种类型，比如nfs、glusterfs等，我们这里使用的ceph RBD来创建。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="1查看statefulset的定义"&gt;（1）查看statefulset的定义&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master ~]# kubectl explain statefulset
KIND: StatefulSet
VERSION: apps/v1
DESCRIPTION:
StatefulSet represents a set of pods with consistent identities. Identities
are defined as: - Network: A single stable DNS and hostname. - Storage: As
many VolumeClaims as requested. The StatefulSet guarantees that a given
network identity will always map to the same storage identity.
FIELDS:
apiVersion &amp;lt;string&amp;gt;
kind &amp;lt;string&amp;gt;
metadata &amp;lt;Object&amp;gt;
spec &amp;lt;Object&amp;gt;
status &amp;lt;Object&amp;gt;
[root@k8s-master ~]# kubectl explain statefulset.spec
KIND: StatefulSet
VERSION: apps/v1
RESOURCE: spec &amp;lt;Object&amp;gt;
DESCRIPTION:
Spec defines the desired identities of pods in this set.
A StatefulSetSpec is the specification of a StatefulSet.
FIELDS:
podManagementPolicy &amp;lt;string&amp;gt; #Pod管理策略
replicas &amp;lt;integer&amp;gt; #副本数量
revisionHistoryLimit &amp;lt;integer&amp;gt; #历史版本限制
selector &amp;lt;Object&amp;gt; -required- #选择器，必选项
serviceName &amp;lt;string&amp;gt; -required- #服务名称，必选项
template &amp;lt;Object&amp;gt; -required- #模板，必选项
updateStrategy &amp;lt;Object&amp;gt; #更新策略
volumeClaimTemplates &amp;lt;[]Object&amp;gt; #存储卷申请模板，列表对象形式
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2清单定义statefulset"&gt;（2）清单定义StatefulSet&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;如上所述，一个完整的StatefulSet控制器由一个Headless Service、一个StatefulSet和一个volumeClaimTemplate组成。如下资源清单中的定义：&lt;/p&gt;</description></item><item><title>k8s学习笔记-16-存储卷</title><link>/2018/09/02/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-16-%E5%AD%98%E5%82%A8%E5%8D%B7/</link><pubDate>Sun, 02 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/02/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-16-%E5%AD%98%E5%82%A8%E5%8D%B7/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="一存储卷的概念和类型"&gt;一、存储卷的概念和类型&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;为了保证数据的持久性，必须保证数据在外部存储在&lt;code&gt;docker&lt;/code&gt;容器中，为了实现数据的持久性存储，在宿主机和容器内做映射，可以保证在容器的生命周期结束，数据依旧可以实现持久性存储。但是在&lt;code&gt;k8s&lt;/code&gt;中，由于&lt;code&gt;pod&lt;/code&gt;分布在各个不同的节点之上，并不能实现不同节点之间持久性数据的共享，并且，在节点故障时，可能会导致数据的永久性丢失。为此，&lt;code&gt;k8s&lt;/code&gt;就引入了外部存储卷的功能。
k8s的存储卷类型：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master ~]# kubectl explain pod.spec.volumes #查看k8s支持的存储类型
KIND: Pod
VERSION: v1
常用分类：
emptyDir（临时目录）:Pod删除，数据也会被清除，这种存储成为emptyDir，用于数据的临时存储。
hostPath(宿主机目录映射):
本地的SAN(iSCSI,FC)、NAS(nfs,cifs,http)存储
分布式存储（glusterfs，rbd，cephfs）
云存储（EBS，Azure Disk）
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;persistentVolumeClaim &amp;ndash;&amp;gt;PVC(存储卷创建申请)
当你需要创建一个存储卷时，只需要进行申请对应的存储空间即可使用，这就是PVC。其关联关系如图：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/02/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-16-%E5%AD%98%E5%82%A8%E5%8D%B7/1349539-20181009150238161-243800675.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;上图解析：在Pod上定义一个PVC，该PVC要关联到当前名称空间的PVC资源，该PVC只是一个申请，PVC需要和PV进行关联。PV属于存储上的一部分存储空间。但是该方案存在的问题是，我们无法知道用户是什么时候去创建Pod，也不知道创建Pod时定义多大的PVC，那么如何实现按需创建呢？？？&lt;/p&gt;
&lt;p&gt;不需要PV层，把所有存储空间抽象出来，这一个抽象层称为存储类，当用户创建PVC需要用到PV时，可以向存储类申请对应的存储空间，存储类会按照需求创建对应的存储空间，这就是PV的动态供给，如图：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/02/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-16-%E5%AD%98%E5%82%A8%E5%8D%B7/1349539-20181009150251762-2029671134.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;那么PV的动态供给，其重点是在存储类的定义，其分类大概是对存储的性能进行分类的，如图：金存储类、银存储类、铜存储类等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/09/02/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-16-%E5%AD%98%E5%82%A8%E5%8D%B7/1349539-20181009150328995-1548599425.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;总结：
k8s要使用存储卷，需要2步：
1、在pod定义volume，并指明关联到哪个存储设备
2、在容器使用volume mount进行挂载&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id="二emptydir存储卷演示"&gt;二、emptyDir存储卷演示&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;一个emptyDir 第一次创建是在一个pod被指定到具体node的时候，并且会一直存在在pod的生命周期当中，正如它的名字一样，它初始化是一个空的目录，pod中的容器都可以读写这个目录，这个目录可以被挂在到各个容器相同或者不相同的的路径下。当一个pod因为任何原因被移除的时候，这些数据会被永久删除。注意：一个容器崩溃了不会导致数据的丢失，因为容器的崩溃并不移除pod.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;emptyDir 磁盘的作用：
（1）普通空间，基于磁盘的数据存储
（2）作为从崩溃中恢复的备份点
（3）存储那些那些需要长久保存的数据，例web服务中的数据
默认的，emptyDir 磁盘会存储在主机所使用的媒介上，可能是SSD，或者网络硬盘，这主要取决于你的环境。当然，我们也可以将emptyDir.medium的值设置为Memory来告诉Kubernetes 来挂在一个基于内存的目录tmpfs，因为
tmpfs速度会比硬盘块度了，但是，当主机重启的时候所有的数据都会丢失。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master ~]# kubectl explain pods.spec.volumes.emptyDir #查看emptyDir存储定义
[root@k8s-master ~]# kubectl explain pods.spec.containers.volumeMounts #查看容器挂载方式
[root@k8s-master ~]# cd mainfests &amp;amp;&amp;amp; mkdir volumes &amp;amp;&amp;amp; cd volumes
[root@k8s-master volumes]# cp ../pod-demo.yaml ./
[root@k8s-master volumes]# mv pod-demo.yaml pod-vol-demo.yaml
[root@k8s-master volumes]# vim pod-vol-demo.yaml #创建emptyDir的清单
apiVersion: v1
kind: Pod
metadata:
name: pod-demo
namespace: default
labels:
app: myapp
tier: frontend
annotations:
magedu.com/create-by:&amp;#34;cluster admin&amp;#34;
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
volumeMounts: #在容器内定义挂载存储名称和挂载路径
- name: html
mountPath: /usr/share/nginx/html/
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: html
mountPath: /data/ #在容器内定义挂载存储名称和挂载路径
command: [&amp;#39;/bin/sh&amp;#39;,&amp;#39;-c&amp;#39;,&amp;#39;while true;do echo $(date) &amp;gt;&amp;gt; /data/index.html;sleep 2;done&amp;#39;]
volumes: #定义存储卷
- name: html #定义存储卷名称
emptyDir: {} #定义存储卷类型
[root@k8s-master volumes]# kubectl apply -f pod-vol-demo.yaml
pod/pod-vol-demo created
[root@k8s-master volumes]# kubectl get pods
NAME READY STATUS RESTARTS AGE
pod-vol-demo 2/2 Running 0 27s
[root@k8s-master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
......
pod-vol-demo 2/2 Running 0 16s 10.244.2.34 k8s-node02
......
在上面，我们定义了2个容器，其中一个容器是输入日期到index.html中，然后验证访问nginx的html是否可以获取日期。以验证两个容器之间挂载的emptyDir实现共享。如下访问验证:
[root@k8s-master volumes]# curl 10.244.2.34 #访问验证
Tue Oct 9 03:56:53 UTC 2018
Tue Oct 9 03:56:55 UTC 2018
Tue Oct 9 03:56:57 UTC 2018
Tue Oct 9 03:56:59 UTC 2018
Tue Oct 9 03:57:01 UTC 2018
Tue Oct 9 03:57:03 UTC 2018
Tue Oct 9 03:57:05 UTC 2018
Tue Oct 9 03:57:07 UTC 2018
Tue Oct 9 03:57:09 UTC 2018
Tue Oct 9 03:57:11 UTC 2018
Tue Oct 9 03:57:13 UTC 2018
Tue Oct 9 03:57:15 UTC 2018
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="三hostpath存储卷演示"&gt;三、hostPath存储卷演示&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;hostPath宿主机路径，就是把pod所在的宿主机之上的脱离pod中的容器名称空间的之外的宿主机的文件系统的某一目录和pod建立关联关系，在pod删除时，存储数据不会丢失。&lt;/p&gt;</description></item><item><title>k8s学习笔记-15-Ingress和Ingress Controller</title><link>/2018/09/01/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-15-ingress%E5%92%8Cingress-controller/</link><pubDate>Sat, 01 Sep 2018 00:00:00 +0000</pubDate><guid>/2018/09/01/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-15-ingress%E5%92%8Cingress-controller/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="一什么是ingress"&gt;一、什么是Ingress？&lt;/h3&gt;
&lt;p&gt;从前面的学习，我们可以了解到&lt;code&gt;Kubernetes&lt;/code&gt;暴露服务的方式目前只有三种：&lt;code&gt;LoadBlancer Service、ExternalName、NodePort Service、Ingress&lt;/code&gt;；而我们需要将集群内服务提供外界访问就会产生以下几个问题：&lt;/p&gt;
&lt;h4 id="1pod-漂移问题"&gt;1、Pod 漂移问题&lt;/h4&gt;
&lt;p&gt;Kubernetes 具有强大的副本控制能力，能保证在任意副本（Pod）挂掉时自动从其他机器启动一个新的，还可以动态扩容等，通俗地说，这个 Pod 可能在任何时刻出现在任何节点上，也可能在任何时刻死在任何节点上；那么自然随着 Pod 的创建和销毁，Pod IP 肯定会动态变化；那么如何把这个动态的 Pod IP 暴露出去？这里借助于 Kubernetes 的 Service 机制，Service 可以以标签的形式选定一组带有指定标签的 Pod，并监控和自动负载他们的 Pod IP，那么我们向外暴露只暴露 Service IP 就行了；这就是 NodePort 模式：即在每个节点上开起一个端口，然后转发到内部 Pod IP 上，如下图所示：
此时的访问方式：&lt;a href="http://nodeip/"&gt;http://nodeip&lt;/a&gt;:nodeport/
&lt;img alt="img" loading="lazy" src="/2018/09/01/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-15-ingress%E5%92%8Cingress-controller/1349539-20180929174515031-619898952.png"&gt;&lt;/p&gt;
&lt;h4 id="2端口管理问题"&gt;2、端口管理问题&lt;/h4&gt;
&lt;p&gt;采用 NodePort 方式暴露服务面临问题是，服务一旦多起来，NodePort 在每个节点上开启的端口会及其庞大，而且难以维护；这时，我们可以能否使用一个Nginx直接对内进行转发呢？众所周知的是，Pod与Pod之间是可以互相通信的，而Pod是可以共享宿主机的网络名称空间的，也就是说当在共享网络名称空间时，Pod上所监听的就是Node上的端口。那么这又该如何实现呢？简单的实现就是使用 DaemonSet 在每个 Node 上监听 80，然后写好规则，因为 Nginx 外面绑定了宿主机 80 端口（就像 NodePort），本身又在集群内，那么向后直接转发到相应 Service IP 就行了，如下图所示：
&lt;img alt="img" loading="lazy" src="/2018/09/01/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-15-ingress%E5%92%8Cingress-controller/1349539-20180929175905911-113975562.png"&gt;&lt;/p&gt;
&lt;h4 id="3域名分配及动态更新问题"&gt;3、域名分配及动态更新问题&lt;/h4&gt;
&lt;p&gt;从上面的方法，采用 Nginx-Pod 似乎已经解决了问题，但是其实这里面有一个很大缺陷：当每次有新服务加入又该如何修改 Nginx 配置呢？？我们知道使用Nginx可以通过虚拟主机域名进行区分不同的服务，而每个服务通过upstream进行定义不同的负载均衡池，再加上location进行负载均衡的反向代理，在日常使用中只需要修改nginx.conf即可实现，那在K8S中又该如何实现这种方式的调度呢？？？&lt;/p&gt;
&lt;p&gt;假设后端的服务初始服务只有ecshop，后面增加了bbs和member服务，那么又该如何将这2个服务加入到Nginx-Pod进行调度呢？总不能每次手动改或者Rolling Update 前端 Nginx Pod 吧！！此时 Ingress 出现了，如果不算上面的Nginx，Ingress 包含两大组件：Ingress Controller 和 Ingress。
&lt;img alt="img" loading="lazy" src="/2018/09/01/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-15-ingress%E5%92%8Cingress-controller/1349539-20180930094041632-683389969.png"&gt;&lt;/p&gt;</description></item><item><title>k8s学习笔记-13-Pod控制器--DaemonSet</title><link>/2018/08/29/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-13-pod%E6%8E%A7%E5%88%B6%E5%99%A8--daemonset/</link><pubDate>Wed, 29 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/29/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-13-pod%E6%8E%A7%E5%88%B6%E5%99%A8--daemonset/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="一什么是daemonset"&gt;一、什么是DaemonSet？&lt;/h2&gt;
&lt;p&gt;DaemonSet 确保全部（或者一些）Node 上运行一个 Pod 的副本。当有 Node 加入集群时，也会为他们新增一个 Pod 。当有 Node 从集群移除时，这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。&lt;/p&gt;
&lt;p&gt;使用 DaemonSet 的一些典型用法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运行集群存储 daemon，例如在每个 Node 上运行 &lt;code&gt;glusterd&lt;/code&gt;、&lt;code&gt;ceph&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在每个 Node 上运行日志收集 daemon，例如&lt;code&gt;fluentd&lt;/code&gt;、&lt;code&gt;logstash&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在每个 Node 上运行监控 daemon，例如 &lt;a href="https://github.com/prometheus/node_exporter"&gt;Prometheus Node Exporter&lt;/a&gt;、&lt;code&gt;collectd&lt;/code&gt;、Datadog 代理、New Relic 代理，或 Ganglia &lt;code&gt;gmond&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个简单的用法是，在所有的 Node 上都存在一个 DaemonSet，将被作为每种类型的 daemon 使用。 一个稍微复杂的用法可能是，对单独的每种类型的 daemon 使用多个 DaemonSet，但具有不同的标志，和/或对不同硬件类型具有不同的内存、CPU要求。&lt;/p&gt;
&lt;h2 id="二编写daemonset-spec"&gt;二、编写DaemonSet Spec&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;（1）必需字段&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;和其它所有 Kubernetes 配置一样，DaemonSet 需要 &lt;code&gt;apiVersion&lt;/code&gt;、&lt;code&gt;kind&lt;/code&gt; 和 &lt;code&gt;metadata&lt;/code&gt;字段。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master ~]# kubectl explain daemonset
KIND: DaemonSet
VERSION: extensions/v1beta1
DESCRIPTION:
DEPRECATED - This group version of DaemonSet is deprecated by
apps/v1beta2/DaemonSet. See the release notes for more information.
DaemonSet represents the configuration of a daemon set.
FIELDS:
apiVersion &amp;lt;string&amp;gt;
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resources
kind &amp;lt;string&amp;gt;
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
metadata &amp;lt;Object&amp;gt;
Standard object&amp;#39;s metadata. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
spec &amp;lt;Object&amp;gt;
The desired behavior of this daemon set. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
status &amp;lt;Object&amp;gt;
The current status of this daemon set. This data may be out of date by some
window of time. Populated by the system. Read-only. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;（2）Pod模板&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>k8s学习笔记-14-服务发现Service</title><link>/2018/08/29/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-14-%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0service/</link><pubDate>Wed, 29 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/29/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-14-%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0service/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="一service的概念"&gt;一、Service的概念&lt;/h2&gt;
&lt;p&gt;　　运行在Pod中的应用是向客户端提供服务的守护进程，比如，nginx、tomcat、etcd等等，它们都是受控于控制器的资源对象，存在生命周期，我们知道Pod资源对象在自愿或非自愿终端后，只能被重构的Pod对象所替代，属于不可再生类组件。而在动态和弹性的管理模式下，Service为该类Pod对象提供了一个固定、统一的访问接口和负载均衡能力。&lt;/p&gt;
&lt;p&gt;　　其实，就是说Pod存在生命周期，有销毁，有重建，无法提供一个固定的访问接口给客户端。并且为了同类的Pod都能够实现工作负载的价值，由此Service资源出现了，可以为一类Pod资源对象提供一个固定的访问接口和负载均衡，类似于阿里云的负载均衡或者是LVS的功能。&lt;/p&gt;
&lt;p&gt;　　但是要知道的是，Service和Pod对象的IP地址，一个是虚拟地址，一个是Pod IP地址，都仅仅在集群内部可以进行访问，无法接入集群外部流量。而为了解决该类问题的办法可以是在单一的节点上做端口暴露（hostPort）以及让Pod资源共享工作节点的网络名称空间（hostNetwork）以外，还可以使用NodePort或者是LoadBalancer类型的Service资源，或者是有7层负载均衡能力的Ingress资源。&lt;/p&gt;
&lt;p&gt;　　Service是Kubernetes的核心资源类型之一，Service资源基于标签选择器将一组Pod定义成一个逻辑组合，并通过自己的IP地址和端口调度代理请求到组内的Pod对象，如下图所示，它向客户端隐藏了真实的，处理用户请求的Pod资源，使得从客户端上看，就像是由Service直接处理并响应一样，是不是很像负载均衡器呢！&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/08/29/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-14-%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0service/1349539-20190226153708890-1966593460.png"&gt;&lt;/p&gt;
&lt;p&gt;　　Service对象的IP地址也称为Cluster IP，它位于为Kubernetes集群配置指定专用的IP地址范围之内，是一种虚拟的IP地址，它在Service对象创建之后保持不变，并且能够被同一集群中的Pod资源所访问。Service端口用于接受客户端请求，并将请求转发至后端的Pod应用的相应端口，这样的代理机制，也称为端口代理，它是基于TCP/IP 协议栈的传输层。&lt;/p&gt;
&lt;h2 id="二service的实现模型"&gt;二、Service的实现模型&lt;/h2&gt;
&lt;p&gt;　　在 Kubernetes 集群中，每个 Node 运行一个 &lt;code&gt;kube-proxy&lt;/code&gt; 进程。&lt;code&gt;kube-proxy&lt;/code&gt; 负责为 &lt;code&gt;Service&lt;/code&gt; 实现了一种 VIP（虚拟 IP）的形式，而不是 &lt;code&gt;ExternalName&lt;/code&gt; 的形式。 在 Kubernetes v1.0 版本，代理完全在 userspace。在 Kubernetes v1.1 版本，新增了 iptables 代理，但并不是默认的运行模式。 从 Kubernetes v1.2 起，默认就是 iptables 代理。在Kubernetes v1.8.0-beta.0中，添加了ipvs代理。在 Kubernetes v1.0 版本，&lt;code&gt;Service&lt;/code&gt; 是 “4层”（TCP/UDP over IP）概念。 在 Kubernetes v1.1 版本，新增了 &lt;code&gt;Ingress&lt;/code&gt; API（beta 版），用来表示 “7层”（HTTP）服务。&lt;/p&gt;
&lt;p&gt;kube-proxy 这个组件始终监视着apiserver中有关service的变动信息，获取任何一个与service资源相关的变动状态，通过watch监视，一旦有service资源相关的变动和创建，kube-proxy都要转换为当前节点上的能够实现资源调度规则（例如：iptables、ipvs）&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/08/29/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-14-%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0service/1349539-20180907165901269-453271720.png"&gt;&lt;/p&gt;
&lt;h3 id="21userspace代理模式"&gt;2.1、userspace代理模式&lt;/h3&gt;
&lt;p&gt;　　这种模式，当客户端Pod请求内核空间的service iptables后，把请求转到给用户空间监听的kube-proxy 的端口，由kube-proxy来处理后，再由kube-proxy将请求转给内核空间的 service ip，再由service iptalbes根据请求转给各节点中的的service pod。&lt;/p&gt;</description></item><item><title>k8s学习笔记-12-Pod控制器--ReplicaSet、Deployment</title><link>/2018/08/28/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-12-pod%E6%8E%A7%E5%88%B6%E5%99%A8--replicasetdeployment/</link><pubDate>Tue, 28 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/28/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-12-pod%E6%8E%A7%E5%88%B6%E5%99%A8--replicasetdeployment/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="一pod控制器及其功用"&gt;一、Pod控制器及其功用&lt;/h2&gt;
&lt;p&gt;Pod控制器是用于实现管理pod的中间层，确保pod资源符合预期的状态，pod的资源出现故障时，会尝试 进行重启，当根据重启策略无效，则会重新新建pod的资源。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pod控制器有多种类型：&lt;/strong&gt;
&lt;strong&gt;ReplicaSet:&lt;/strong&gt; 代用户创建指定数量的pod副本数量，确保pod副本数量符合预期状态，并且支持滚动式自动扩容和缩容功能。
ReplicaSet主要三个组件组成：
　　（1）用户期望的pod副本数量
　　（2）标签选择器，判断哪个pod归自己管理
　　（3）当现存的pod数量不足，会根据pod资源模板进行新建
帮助用户管理无状态的pod资源，精确反应用户定义的目标数量，但是RelicaSet不是直接使用的控制器，而是使用Deployment。
**Deployment：**工作在ReplicaSet之上，用于管理无状态应用，目前来说最好的控制器。支持滚动更新和回滚功能，还提供声明式配置。
**DaemonSet：**用于确保集群中的每一个节点只运行特定的pod副本，通常用于实现系统级后台任务。比如ELK服务
特性：服务是无状态的
服务必须是守护进程
**Job：**只要完成就立即退出，不需要重启或重建。
**Cronjob：**周期性任务控制，不需要持续后台运行，
**StatefulSet：**管理有状态应用&lt;/p&gt;
&lt;h2 id="二replicaset控制器"&gt;二、ReplicaSet控制器&lt;/h2&gt;
&lt;p&gt;ReplicationController用来确保容器应用的副本数始终保持在用户定义的副本数，即如果有容器异常退出，会自动创建新的Pod来替代；而如果异常多出来的容器也会自动回收。&lt;/p&gt;
&lt;p&gt;在新版本的Kubernetes中建议使用ReplicaSet来取代ReplicationController。ReplicaSet跟ReplicationController没有本质的不同，只是名字不一样，并且ReplicaSet支持集合式的selector。&lt;/p&gt;
&lt;p&gt;虽然ReplicaSet可以独立使用，但一般还是建议使用 Deployment 来自动管理ReplicaSet，这样就无需担心跟其他机制的不兼容问题（比如ReplicaSet不支持rolling-update但Deployment支持）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ReplicaSet示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;（1）命令行查看ReplicaSet清单定义规则
[root@k8s-master ~]# kubectl explain rs
[root@k8s-master ~]# kubectl explain rs.spec
[root@k8s-master ~]# kubectl explain rs.spec.template
（2）新建ReplicaSet示例
[root@k8s-master ~]# vim rs-demo.yaml
apiVersion: apps/v1　　#api版本定义
kind: ReplicaSet　　#定义资源类型为ReplicaSet
metadata:　　#元数据定义
name: myapp
namespace: default
spec:　　#ReplicaSet的规格定义
replicas: 2　　#定义副本数量为2个
selector:　　　　#标签选择器，定义匹配pod的标签
matchLabels:
app: myapp
release: canary
template:　　#pod的模板定义
metadata:　　#pod的元数据定义
name: myapp-pod　　　#自定义pod的名称　
labels: 　　#定义pod的标签，需要和上面定义的标签一致，也可以多出其他标签
app: myapp
release: canary
environment: qa
spec:　　#pod的规格定义
containers:　　#容器定义
- name: myapp-container　　#容器名称
image: ikubernetes/myapp:v1　　#容器镜像
ports:　　#暴露端口
- name: http
containerPort: 80
（3）创建ReplicaSet定义的pod
[root@k8s-master ~]# kubectl create -f rs-demo.yaml
[root@k8s-master ~]# kubectl get pods　　#获取pod信息
[root@k8s-master ~]# kubectl describe pods myapp-***　　#查看pod详细信息
（4）修改pod的副本数量
[root@k8s-master ~]# kubectl edit rs myapp
replicas: 5
[root@k8s-master ~]# kubectl get rs -o wide
（5）修改pod的镜像版本
[root@k8s-master ~]# kubectl edit rs myapp
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;image: ikubernetes/myapp:v2　　
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master ~]# kubectl delete pods myapp-*** 　　#修改了pod镜像版本，pod需要重建才能达到最新版本
[root@k8s-master ~]# kubectl create -f rs-demo.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="三deployment控制器"&gt;三、Deployment控制器&lt;/h2&gt;
&lt;p&gt;Deployment为Pod和ReplicaSet（下一代Replication Controller）提供声明式更新。&lt;/p&gt;</description></item><item><title>k8s学习笔记-11-Pod状态和生命周期管理</title><link>/2018/08/27/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-11-pod%E7%8A%B6%E6%80%81%E5%92%8C%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%AE%A1%E7%90%86/</link><pubDate>Mon, 27 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/27/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-11-pod%E7%8A%B6%E6%80%81%E5%92%8C%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%AE%A1%E7%90%86/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="一什么是pod"&gt;一、什么是Pod？&lt;/h2&gt;
&lt;p&gt;Pod是kubernetes中你可以创建和部署的最小也是最简的单位。一个Pod代表着集群中运行的一个进程。&lt;/p&gt;
&lt;p&gt;Pod中封装着应用的容器（有的情况下是好几个容器），存储、独立的网络IP，管理容器如何运行的策略选项。Pod代表着部署的一个单位：kubernetes中应用的一个实例，可能由一个或者多个容器组合在一起共享资源。&lt;/p&gt;
&lt;p&gt;在Kubrenetes集群中Pod有如下两种使用方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个Pod中运行一个容器。“每个Pod中一个容器”的模式是最常见的用法；在这种使用方式中，你可以把Pod想象成是单个容器的封装，kuberentes管理的是Pod而不是直接管理容器。&lt;/li&gt;
&lt;li&gt;在一个Pod中同时运行多个容器。一个Pod中也可以同时封装几个需要紧密耦合互相协作的容器，它们之间共享资源。这些在同一个Pod中的容器可以互相协作成为一个service单位——一个容器共享文件，另一个“sidecar”容器来更新这些文件。Pod将这些容器的存储资源作为一个实体来管理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pod中共享的环境包括Linux的namespace，cgroup和其他可能的隔绝环境，这一点跟Docker容器一致。在Pod的环境中，每个容器中可能还有更小的子隔离环境。&lt;/p&gt;
&lt;p&gt;Pod中的容器共享IP地址和端口号，它们之间可以通过&lt;code&gt;localhost&lt;/code&gt;互相发现。它们之间可以通过进程间通信，需要明白的是同一个Pod下的容器是通过lo网卡进行通信。例如SystemV信号或者POSIX共享内存。不同Pod之间的容器具有不同的IP地址，不能直接通过IPC通信。&lt;/p&gt;
&lt;p&gt;Pod中的容器也有访问共享volume的权限，这些volume会被定义成pod的一部分并挂载到应用容器的文件系统中。&lt;/p&gt;
&lt;p&gt;就像每个应用容器，pod被认为是临时实体。在Pod的生命周期中，pod被创建后，被分配一个唯一的ID（UID），调度到节点上，并一致维持期望的状态直到被终结（根据重启策略）或者被删除。如果node死掉了，分配到了这个node上的pod，在经过一个超时时间后会被重新调度到其他node节点上。一个给定的pod（如UID定义的）不会被“重新调度”到新的节点上，而是被一个同样的pod取代，如果期望的话甚至可以是相同的名字，但是会有一个新的UID（查看replication controller获取详情）。&lt;/p&gt;
&lt;h2 id="二pod中如何管理多个容器"&gt;二、Pod中如何管理多个容器？&lt;/h2&gt;
&lt;p&gt;Pod中可以同时运行多个进程（作为容器运行）协同工作。同一个Pod中的容器会自动的分配到同一个 node 上。同一个Pod中的容器共享资源、网络环境和依赖，它们总是被同时调度。&lt;/p&gt;
&lt;p&gt;注意在一个Pod中同时运行多个容器是一种比较高级的用法。只有当你的容器需要紧密配合协作的时候才考虑用这种模式。例如，你有一个容器作为web服务器运行，需要用到共享的volume，有另一个“sidecar”容器来从远端获取资源更新这些文件，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/08/27/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-11-pod%E7%8A%B6%E6%80%81%E5%92%8C%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%AE%A1%E7%90%86/1349539-20180901112128577-178843.png"&gt;&lt;/p&gt;
&lt;p&gt;Pod中可以共享两种资源：网络和存储。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;网络&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;　　每个Pod都会被分配一个唯一的IP地址。Pod中的所有容器共享网络空间，包括IP地址和端口。Pod内部的容器可以使用&lt;code&gt;localhost&lt;/code&gt;互相通信。Pod中的容器与外界通信时，必须分配共享网络资源（例如使用宿主机的端口映射）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;存储：&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;　　可以Pod指定多个共享的Volume。Pod中的所有容器都可以访问共享的volume。Volume也可以用来持久化Pod中的存储资源，以防容器重启后文件丢失。&lt;/p&gt;
&lt;h2 id="三使用pod"&gt;三、使用Pod&lt;/h2&gt;
&lt;p&gt;通常把Pod分为两类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**自主式Pod ：**这种Pod本身是不能自我修复的，当Pod被创建后（不论是由你直接创建还是被其他Controller），都会被Kuberentes调度到集群的Node上。直到Pod的进程终止、被删掉、因为缺少资源而被驱逐、或者Node故障之前这个Pod都会一直保持在那个Node上。Pod不会自愈。如果Pod运行的Node故障，或者是调度器本身故障，这个Pod就会被删除。同样的，如果Pod所在Node缺少资源或者Pod处于维护状态，Pod也会被驱逐。&lt;/li&gt;
&lt;li&gt;**控制器管理的Pod：**Kubernetes使用更高级的称为Controller的抽象层，来管理Pod实例。Controller可以创建和管理多个Pod，提供副本管理、滚动升级和集群级别的自愈能力。例如，如果一个Node故障，Controller就能自动将该节点上的Pod调度到其他健康的Node上。虽然可以直接使用Pod，但是在Kubernetes中通常是使用Controller来管理Pod的。如下图：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;每个Pod都有一个特殊的被称为“根容器”的Pause 容器。 Pause容器对应的镜像属于Kubernetes平台的一部分，除了Pause容器，每个Pod还包含一个或者多个紧密相关的用户业务容器。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/08/27/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-11-pod%E7%8A%B6%E6%80%81%E5%92%8C%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%AE%A1%E7%90%86/1349539-20180901112712875-923099036.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kubernetes设计这样的Pod概念和特殊组成结构有什么用意？？？？？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;原因一：在一组容器作为一个单元的情况下，难以对整体的容器简单地进行判断及有效地进行行动。比如，一个容器死亡了，此时是算整体挂了么？那么引入与业务无关的Pause容器作为Pod的根容器，以它的状态代表着整个容器组的状态，这样就可以解决该问题。&lt;/p&gt;
&lt;p&gt;原因二：Pod里的多个业务容器共享Pause容器的IP，共享Pause容器挂载的Volume，这样简化了业务容器之间的通信问题，也解决了容器之间的文件共享问题。&lt;/p&gt;
&lt;h2 id="四pod的持久性和终止"&gt;四、Pod的持久性和终止&lt;/h2&gt;
&lt;h3 id="1pod的持久性"&gt;&lt;strong&gt;（1）Pod的持久性&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Pod在设计支持就不是作为持久化实体的。在调度失败、节点故障、缺少资源或者节点维护的状态下都会死掉会&lt;strong&gt;被驱逐&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;通常，用户不需要手动直接创建Pod，而是应该使用controller（例如&lt;a href="https://jimmysong.io/kubernetes-handbook/concepts/deployment.html"&gt;Deployments&lt;/a&gt;），即使是在创建单个Pod的情况下。Controller可以提供集群级别的自愈功能、复制和升级管理。&lt;/p&gt;
&lt;h3 id="2pod的终止"&gt;&lt;strong&gt;（2）Pod的终止&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;因为Pod作为在集群的节点上运行的进程，所以在不再需要的时候能够优雅的终止掉是十分必要的（比起使用发送KILL信号这种暴力的方式）。用户需要能够放松删除请求，并且知道它们何时会被终止，是否被正确的删除。用户想终止程序时发送删除pod的请求，在pod可以被强制删除前会有一个宽限期，会发送一个TERM请求到每个容器的主进程。一旦超时，将向主进程发送KILL信号并从API server中删除。如果kubelet或者container manager在等待进程终止的过程中重启，在重启后仍然会重试完整的宽限期。&lt;/p&gt;
&lt;p&gt;示例流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户发送删除pod的命令，默认宽限期是30秒；&lt;/li&gt;
&lt;li&gt;在Pod超过该宽限期后API server就会更新Pod的状态为“dead”；&lt;/li&gt;
&lt;li&gt;在客户端命令行上显示的Pod状态为“terminating”；&lt;/li&gt;
&lt;li&gt;跟第三步同时，当kubelet发现pod被标记为“terminating”状态时，开始停止pod进程：
&lt;ol&gt;
&lt;li&gt;如果在pod中定义了preStop hook，在停止pod前会被调用。如果在宽限期过后，preStop hook依然在运行，第二步会再增加2秒的宽限期；&lt;/li&gt;
&lt;li&gt;向Pod中的进程发送TERM信号；&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;跟第三步同时，该Pod将从该service的端点列表中删除，不再是replication controller的一部分。关闭的慢的pod将继续处理load balancer转发的流量；&lt;/li&gt;
&lt;li&gt;过了宽限期后，将向Pod中依然运行的进程发送SIGKILL信号而杀掉进程。&lt;/li&gt;
&lt;li&gt;Kublete会在API server中完成Pod的的删除，通过将优雅周期设置为0（立即删除）。Pod在API中消失，并且在客户端也不可见。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;删除宽限期默认是30秒。&lt;strong&gt;kubectl delete&lt;/strong&gt; 命令支持 &lt;code&gt;—grace-period=&amp;lt;seconds&amp;gt;&lt;/code&gt; 选项，允许用户设置自己的宽限期。如果设置为0将强制删除pod。在kubectl&amp;gt;=1.5版本的命令中，你必须同时使用 &lt;code&gt;--force&lt;/code&gt; 和 &lt;code&gt;--grace-period=0&lt;/code&gt; 来强制删除pod。&lt;/p&gt;</description></item><item><title>k8s学习笔记-10-资源清单定义</title><link>/2018/08/25/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-10-%E8%B5%84%E6%BA%90%E6%B8%85%E5%8D%95%E5%AE%9A%E4%B9%89/</link><pubDate>Sat, 25 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/25/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-10-%E8%B5%84%E6%BA%90%E6%B8%85%E5%8D%95%E5%AE%9A%E4%B9%89/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="一kubernetes常用资源"&gt;一、Kubernetes常用资源&lt;/h2&gt;
&lt;p&gt;以下列举的内容都是 kubernetes 中的 Object，这些对象都可以在 yaml 文件中作为一种 API 类型来配置。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类别&lt;/th&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;工作负载型资源对象&lt;/td&gt;
&lt;td&gt;Pod Replicaset ReplicationController Deployments StatefulSets Daemonset Job CronJob&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;服务发现及负载均衡&lt;/td&gt;
&lt;td&gt;Service Ingress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;配置与存储&lt;/td&gt;
&lt;td&gt;Volume、Persistent Volume、CSl 、 configmap、 secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;集群资源&lt;/td&gt;
&lt;td&gt;Namespace Node Role ClusterRole RoleBinding ClusterRoleBinding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;元数据资源&lt;/td&gt;
&lt;td&gt;HPA PodTemplate LimitRang&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="二理解kubernetes中的对象"&gt;二、理解Kubernetes中的对象&lt;/h2&gt;
&lt;p&gt;在 Kubernetes 系统中，Kubernetes 对象 是持久化的条目。Kubernetes 使用这些条目去表示整个集群的状态。特别地，它们描述了如下信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;什么容器化应用在运行（以及在哪个 Node 上）&lt;/li&gt;
&lt;li&gt;可以被应用使用的资源&lt;/li&gt;
&lt;li&gt;关于应用如何表现的策略，比如重启策略、升级策略，以及容错策略&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kubernetes 对象是 “目标性记录” —— 一旦创建对象，Kubernetes 系统将持续工作以确保对象存在。通过创建对象，可以有效地告知 Kubernetes 系统，所需要的集群工作负载看起来是什么样子的，这就是 Kubernetes 集群的 期望状态。&lt;/p&gt;
&lt;p&gt;与 Kubernetes 对象工作 —— 是否创建、修改，或者删除 —— 需要使用 Kubernetes API。当使用 &lt;code&gt;kubectl&lt;/code&gt; 命令行接口时，比如，CLI 会使用必要的 Kubernetes API 调用，也可以在程序中直接使用 Kubernetes API。&lt;/p&gt;</description></item><item><title>k8s学习笔记-09-kubernetes命令式快速创建应用</title><link>/2018/08/19/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-09-kubernetes%E5%91%BD%E4%BB%A4%E5%BC%8F%E5%BF%AB%E9%80%9F%E5%88%9B%E5%BB%BA%E5%BA%94%E7%94%A8/</link><pubDate>Sun, 19 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/19/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-09-kubernetes%E5%91%BD%E4%BB%A4%E5%BC%8F%E5%BF%AB%E9%80%9F%E5%88%9B%E5%BB%BA%E5%BA%94%E7%94%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="1使用命令kubectl-run创建应用"&gt;1、使用命令kubectl run创建应用&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;语法:
kubectl run NAME --image=image [--env=&amp;#34;key=value&amp;#34;] [--port=port] [--replicas=replicas] [--dry-run=bool]
[--overrides=inline-json] [--command] -- [COMMAND] [args...] [options]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;实用举例&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master ~]# kubectl run nginx-deploy --image=nginx:1.14-alpine --port=80 --replicas=1 #创建一个nginx的应用，副本数为1
deployment.apps/nginx-deploy created
[root@k8s-master ~]# kubectl get deployment　　#获取应用信息，查看应用是否符合预期状态
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx-deploy 1 1 1 1 40s
[root@k8s-master ~]# kubectl get pods　　#获取pod信息
NAME READY STATUS RESTARTS AGE
nginx-deploy-5b595999-44zwq 1/1 Running 0 1m
[root@k8s-master ~]# kubectl get pods -o wide #查看pod运行在哪个节点上
NAME READY STATUS RESTARTS AGE IP NODE
nginx-deploy-5b595999-44zwq 1/1 Running 0 1m 10.244.2.2 k8s-node02
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从上面创建的应用可以得知，nginx-deploy应用的pod的ip为10.244.2.2，这是一个pod ip，仅仅可以在集群内部访问，如下：&lt;/p&gt;</description></item><item><title>k8s学习笔记-08-Kubeadm部署集群</title><link>/2018/08/18/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-08-kubeadm%E9%83%A8%E7%BD%B2%E9%9B%86%E7%BE%A4/</link><pubDate>Sat, 18 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/18/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-08-kubeadm%E9%83%A8%E7%BD%B2%E9%9B%86%E7%BE%A4/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="一环境说明"&gt;一、环境说明&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;节点名称&lt;/th&gt;
&lt;th&gt;ip地址&lt;/th&gt;
&lt;th&gt;部署说明&lt;/th&gt;
&lt;th&gt;Pod 网段&lt;/th&gt;
&lt;th&gt;Service网段&lt;/th&gt;
&lt;th&gt;系统说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;k8s-master&lt;/td&gt;
&lt;td&gt;192.168.56.11&lt;/td&gt;
&lt;td&gt;docker、kubeadm、kubectl、kubelet&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.244.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.96.0.0/12&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Centos 7.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;k8s-node01&lt;/td&gt;
&lt;td&gt;192.168.56.12&lt;/td&gt;
&lt;td&gt;docker、kubeadm、kubelet&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.244.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.96.0.0/12&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Centos 7.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;k8s-node02&lt;/td&gt;
&lt;td&gt;192.168.56.13&lt;/td&gt;
&lt;td&gt;docker、kubeadm、kubelet&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.244.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.96.0.0/12&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Centos 7.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="1配置源"&gt;（1）配置源&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@k8s-master ~]# cd /etc/yum.repos.d/
配置阿里云的源：https://opsx.alibaba.com/mirror
[root@k8s-master yum.repos.d]# wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo　　#配置dokcer源
[root@k8s-master ~]# cat &amp;lt;&amp;lt;EOF &amp;gt; /etc/yum.repos.d/kubernetes.repo　　#配置kubernetes源
&amp;gt; [kubernetes]
&amp;gt; name=Kubernetes
&amp;gt; baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
&amp;gt; enabled=1
&amp;gt; gpgcheck=1
&amp;gt; repo_gpgcheck=1
&amp;gt; gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
&amp;gt; EOF
[root@k8s-master yum.repos.d]# yum repolist #查看可用源
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;将源拷贝到node01和node02节点&lt;/p&gt;</description></item><item><title>k8s学习笔记-07-Coredns和Dashboard部署</title><link>/2018/08/17/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-07-coredns%E5%92%8Cdashboard%E9%83%A8%E7%BD%B2/</link><pubDate>Fri, 17 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/17/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-07-coredns%E5%92%8Cdashboard%E9%83%A8%E7%BD%B2/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="一coredns部署"&gt;一、CoreDNS部署&lt;/h3&gt;
&lt;p&gt;在 Cluster 中，除了可以通过 Cluster IP 访问 Service，Kubernetes 还提供了更为方便的 DNS 访问。&lt;/p&gt;
&lt;h4 id="1编辑corednsyaml文件"&gt;&lt;strong&gt;（1）编辑coredns.yaml文件&lt;/strong&gt;&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# vim coredns.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: coredns
namespace: kube-system
labels:
kubernetes.io/cluster-service: &amp;#34;true&amp;#34;
addonmanager.kubernetes.io/mode: Reconcile
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
kubernetes.io/bootstrapping: rbac-defaults
addonmanager.kubernetes.io/mode: Reconcile
name: system:coredns
rules:
- apiGroups:
- &amp;#34;&amp;#34;
resources:
- endpoints
- services
- pods
- namespaces
verbs:
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: &amp;#34;true&amp;#34;
labels:
kubernetes.io/bootstrapping: rbac-defaults
addonmanager.kubernetes.io/mode: EnsureExists
name: system:coredns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:coredns
subjects:
- kind: ServiceAccount
name: coredns
namespace: kube-system
---
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
labels:
addonmanager.kubernetes.io/mode: EnsureExists
data:
Corefile: |
.:53 {
errors
health
kubernetes cluster.local. in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
proxy . /etc/resolv.conf
cache 30
}
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: coredns
namespace: kube-system
labels:
k8s-app: coredns
kubernetes.io/cluster-service: &amp;#34;true&amp;#34;
addonmanager.kubernetes.io/mode: Reconcile
kubernetes.io/name: &amp;#34;CoreDNS&amp;#34;
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
selector:
matchLabels:
k8s-app: coredns
template:
metadata:
labels:
k8s-app: coredns
spec:
serviceAccountName: coredns
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
- key: &amp;#34;CriticalAddonsOnly&amp;#34;
operator: &amp;#34;Exists&amp;#34;
containers:
- name: coredns
image: coredns/coredns:1.0.6
imagePullPolicy: IfNotPresent
resources:
limits:
memory: 170Mi
requests:
cpu: 100m
memory: 70Mi
args: [ &amp;#34;-conf&amp;#34;, &amp;#34;/etc/coredns/Corefile&amp;#34; ]
volumeMounts:
- name: config-volume
mountPath: /etc/coredns
ports:
- containerPort: 53
name: dns
protocol: UDP
- containerPort: 53
name: dns-tcp
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 60
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 5
dnsPolicy: Default
volumes:
- name: config-volume
configMap:
name: coredns
items:
- key: Corefile
path: Corefile
---
apiVersion: v1
kind: Service
metadata:
name: coredns
namespace: kube-system
labels:
k8s-app: coredns
kubernetes.io/cluster-service: &amp;#34;true&amp;#34;
addonmanager.kubernetes.io/mode: Reconcile
kubernetes.io/name: &amp;#34;CoreDNS&amp;#34;
spec:
selector:
k8s-app: coredns
clusterIP: 10.1.0.2
ports:
- name: dns
port: 53
protocol: UDP
- name: dns-tcp
port: 53
protocol: TCP
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="2创建coredns"&gt;&lt;strong&gt;（2）创建coredns&lt;/strong&gt;&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# kubectl create -f coredns.yaml
serviceaccount &amp;#34;coredns&amp;#34; created
clusterrole.rbac.authorization.k8s.io &amp;#34;system:coredns&amp;#34; created
clusterrolebinding.rbac.authorization.k8s.io &amp;#34;system:coredns&amp;#34; created
configmap &amp;#34;coredns&amp;#34; created
deployment.extensions &amp;#34;coredns&amp;#34; created
service &amp;#34;coredns&amp;#34; created
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="3查看coredns服务"&gt;&lt;strong&gt;（3）查看coredns服务&lt;/strong&gt;&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# kubectl get deployment -n kube-system
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
coredns 2 2 2 0 1m
[root@linux-node1 ~]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
coredns ClusterIP 10.1.0.2 &amp;lt;none&amp;gt; 53/UDP,53/TCP 1m
[root@linux-node1 ~]# kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-77c989547b-d84n8 1/1 Running 0 2m
coredns-77c989547b-j4ms2 1/1 Running 0 2m
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="4pod容器中进行域名解析测试"&gt;&lt;strong&gt;（4）Pod容器中进行域名解析测试&lt;/strong&gt;&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# kubectl run alpine --rm -ti --image=alpine -- /bin/sh
If you don&amp;#39;t see a command prompt, try pressing enter.
/ # nslookup httpd-svc
nslookup: can&amp;#39;t resolve &amp;#39;(null)&amp;#39;: Name does not resolve
Name: httpd-svc
Address 1: 10.1.230.129
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.1.230.129:8080)
index.html 100% |********************************************************************************************************************************************| 45 0:00:00 ETA
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="二dashboard部署"&gt;二、Dashboard部署&lt;/h3&gt;
&lt;p&gt;从github上下载dashboard的yaml文件：https://github.com/unixhot/salt-kubernetes&lt;/p&gt;</description></item><item><title>k8s学习笔记-06-创建K8S应用</title><link>/2018/08/16/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-06-%E5%88%9B%E5%BB%BAk8s%E5%BA%94%E7%94%A8/</link><pubDate>Thu, 16 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/16/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-06-%E5%88%9B%E5%BB%BAk8s%E5%BA%94%E7%94%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="一deployment的概念"&gt;&lt;strong&gt;一、Deployment的概念&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;K8S本身并不提供网络的功能，所以需要借助第三方网络插件进行部署K8S中的网络，以打通各个节点中容器的互通。
&lt;strong&gt;POD&lt;/strong&gt;，是K8S中的一个逻辑概念，K8S管理的是POD，一个POD中包含多个容器，容器之间通过localhost互通。而POD需要ip地址。每个POD都有一个标签&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;POD–&amp;gt;RC–&amp;gt;RS–&amp;gt;Deployment （发展历程）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deployment&lt;/strong&gt;，表示用户对K8S集群的一次更新操作。Deployment是一个比RS应用模式更广的API对象。用于保证Pod的副本的数量。&lt;/p&gt;
&lt;p&gt;可以是创建一个新的服务，更新一个新的服务，也可以是滚动升级一个服务。滚动升级一个服务。实际是创建一个新的RS，然后将新的RS中副本数增加到理想状态，将旧的RS中的副本数减小到0的复合操作； 这样的一个复合操作用一个RS是不太好描述的，所以用一个更通用的Deployment来描述。&lt;/p&gt;
&lt;p&gt;RC、RS和Deployment只是保证了支撑服务的POD数量，但是没有解决如何访问这些服务的问题。一个POD只是一个运行服务的实例，随时可以能在一个节点上停止，在另一个节点以一个新的IP启动一个新的POD，因此不能以确定的IP和端口号提供服务。&lt;/p&gt;
&lt;p&gt;要稳定地提供服务需要服务发现和负载均衡能力。服务发现完成的工作，是针对客户端访问的服务，找到对应的后端服务实例。&lt;/p&gt;
&lt;p&gt;在K8S的集中当中，客户端需要访问的服务就是Service对象。每个Service会对应一个集群内部有效的虚拟IP，集群内部通过虚拟IP访问一个服务。&lt;/p&gt;
&lt;h3 id="二创建k8s的第一个应用"&gt;二、创建K8S的第一个应用&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# kubectl run net-test --image=alpine --replicas=2 sleep 36000　　#创建名称为net-test的应用，镜像指定为alpine，副本数为2个
deployment.apps &amp;#34;net-test&amp;#34; created
[root@linux-node1 ~]# kubectl get pod -o wide　　#查看pod的状态信息，此时是API Server从etcd中读取这些数据
NAME READY STATUS RESTARTS AGE IP NODE
net-test-7b949fc785-2v2qz 1/1 Running 0 56s 10.2.87.2 192.168.56.120
net-test-7b949fc785-6nrhm 0/1 ContainerCreating 0 56s &amp;lt;none&amp;gt; 192.168.56.130
[root@linux-node1 ~]# kubectl get deployment net-test
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
net-test 2 2 2 2 22h
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get deployment命令可以查看net-test的状态，输出显示两个副本正常运行。还可以在创建的过程中，通过kubectl describe deployment net-test了解详细的信息。
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# kubectl describe deployment net-test
Name: net-test
Namespace: default
CreationTimestamp: Thu, 16 Aug 2018 15:41:29 +0800
Labels: run=net-test
Annotations: deployment.kubernetes.io/revision=1
Selector: run=net-test
Replicas: 2 desired | 2 updated | 2 total | 2 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 1 max surge
Pod Template:
Labels: run=net-test
Containers:
net-test:
Image: alpine
Port: &amp;lt;none&amp;gt;
Host Port: &amp;lt;none&amp;gt;
Args:
sleep
360000
Environment: &amp;lt;none&amp;gt;
Mounts: &amp;lt;none&amp;gt;
Volumes: &amp;lt;none&amp;gt;
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: &amp;lt;none&amp;gt;
NewReplicaSet: net-test-5767cb94df (2/2 replicas created)
Events: &amp;lt;none&amp;gt;　　
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Events是Deployment的日志，记录整个RelicaSet的启动过程，从上面的创建过程，可以看到Deployment是通过ReplicaSet来管理Pod。&lt;/p&gt;</description></item><item><title>k8s学习笔记-05-Flannel部署</title><link>/2018/08/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-05-flannel%E9%83%A8%E7%BD%B2/</link><pubDate>Wed, 15 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/15/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-05-flannel%E9%83%A8%E7%BD%B2/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="一k8s的ip地址"&gt;一、K8S的ip地址&lt;/h2&gt;
&lt;p&gt;**Node IP：**节点设备的IP，如物理机，虚拟机等容器宿主的实际IP。&lt;/p&gt;
&lt;p&gt;**Pod IP：**Pod的IP地址，是根据docker0网络IP段进行分配的。&lt;/p&gt;
&lt;p&gt;**Cluster IP：**Service的IP，是一个虚拟IP，仅作用于service对象，由K8S管理和分配，需要结合service port才能使用，单独的IP没有通信功能，集群外访问需要一些修改。&lt;/p&gt;
&lt;p&gt;在K8S集群内部，node ip、pod ip、clustere ip的通信机制是由k8s指定的路由规则，不是IP路由。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.1.0.1 &amp;lt;none&amp;gt; 443/TCP 3h
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="二flannel网络部署"&gt;二、Flannel网络部署&lt;/h2&gt;
&lt;h3 id="1为flannel生成证书"&gt;（1）为Flannel生成证书&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ssl]# vim flanneld-csr.json
{
&amp;#34;CN&amp;#34;: &amp;#34;flanneld&amp;#34;,
&amp;#34;hosts&amp;#34;: [],
&amp;#34;key&amp;#34;: {
&amp;#34;algo&amp;#34;: &amp;#34;rsa&amp;#34;,
&amp;#34;size&amp;#34;: 2048
},
&amp;#34;names&amp;#34;: [
{
&amp;#34;C&amp;#34;: &amp;#34;CN&amp;#34;,
&amp;#34;ST&amp;#34;: &amp;#34;BeiJing&amp;#34;,
&amp;#34;L&amp;#34;: &amp;#34;BeiJing&amp;#34;,
&amp;#34;O&amp;#34;: &amp;#34;k8s&amp;#34;,
&amp;#34;OU&amp;#34;: &amp;#34;System&amp;#34;
}
]
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2生成证书"&gt;（2）生成证书&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ssl]# cfssl gencert -ca=/opt/kubernetes/ssl/ca.pem \
&amp;gt; -ca-key=/opt/kubernetes/ssl/ca-key.pem \
&amp;gt; -config=/opt/kubernetes/ssl/ca-config.json \
&amp;gt; -profile=kubernetes flanneld-csr.json | cfssljson -bare flanneld
[root@linux-node1 ssl]# ll flannel*
-rw-r--r-- 1 root root 997 May 31 11:13 flanneld.csr
-rw-r--r-- 1 root root 221 May 31 11:13 flanneld-csr.json
-rw------- 1 root root 1675 May 31 11:13 flanneld-key.pem
-rw-r--r-- 1 root root 1391 May 31 11:13 flanneld.pem
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3分发证书"&gt;（3）分发证书&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ssl]# cp flanneld*.pem /opt/kubernetes/ssl/
[root@linux-node1 ssl]# scp flanneld*.pem 192.168.56.120:/opt/kubernetes/ssl/
flanneld-key.pem 100% 1675 127.2KB/s 00:00
flanneld.pem 100% 1391 308.3KB/s 00:00
[root@linux-node1 ssl]# scp flanneld*.pem 192.168.56.130:/opt/kubernetes/ssl/
flanneld-key.pem 100% 1675 291.1KB/s 00:00
flanneld.pem 100% 1391 90.4KB/s 00:00
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="4下载flannel软件包"&gt;（4）下载Flannel软件包&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# cd /usr/local/src
# wget
https://github.com/coreos/flannel/releases/download/v0.10.0/flannel-v0.10.0-linux-amd64.tar.gz
[root@linux-node1 src]# tar zxf flannel-v0.10.0-linux-amd64.tar.gz
[root@linux-node1 src]# cp flanneld mk-docker-opts.sh /opt/kubernetes/bin/
复制到linux-node2和linux-node3节点
[root@linux-node1 src]# scp flanneld mk-docker-opts.sh 192.168.56.120:/opt/kubernetes/bin/
[root@linux-node1 src]# scp flanneld mk-docker-opts.sh 192.168.56.130:/opt/kubernetes/bin/
复制对应脚本到/opt/kubernetes/bin目录下。
[root@linux-node1 ~]# cd /usr/local/src/kubernetes/cluster/centos/node/bin/
[root@linux-node1 bin]# cp remove-docker0.sh /opt/kubernetes/bin/
[root@linux-node1 bin]# scp remove-docker0.sh 192.168.56.120:/opt/kubernetes/bin/
[root@linux-node1 bin]# scp remove-docker0.sh 192.168.56.130:/opt/kubernetes/bin/
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="5配置flannel"&gt;（5）配置Flannel&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# vim /opt/kubernetes/cfg/flannel
FLANNEL_ETCD=&amp;#34;-etcd-endpoints=https://192.168.56.110:2379,https://192.168.56.120:2379,https://192.168.56.130:2379&amp;#34;
FLANNEL_ETCD_KEY=&amp;#34;-etcd-prefix=/kubernetes/network&amp;#34;
FLANNEL_ETCD_CAFILE=&amp;#34;--etcd-cafile=/opt/kubernetes/ssl/ca.pem&amp;#34;
FLANNEL_ETCD_CERTFILE=&amp;#34;--etcd-certfile=/opt/kubernetes/ssl/flanneld.pem&amp;#34;
FLANNEL_ETCD_KEYFILE=&amp;#34;--etcd-keyfile=/opt/kubernetes/ssl/flanneld-key.pem&amp;#34;
复制配置到其它节点上
[root@linux-node1 ~]# scp /opt/kubernetes/cfg/flannel 192.168.56.120:/opt/kubernetes/cfg/
[root@linux-node1 ~]# scp /opt/kubernetes/cfg/flannel 192.168.56.130:/opt/kubernetes/cfg/
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="6设置flannel系统服务"&gt;（6）设置Flannel系统服务&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# vim /usr/lib/systemd/system/flannel.service
[Unit]
Description=Flanneld overlay address etcd agent
After=network.target
Before=docker.service
[Service]
EnvironmentFile=-/opt/kubernetes/cfg/flannel
ExecStartPre=/opt/kubernetes/bin/remove-docker0.sh
ExecStart=/opt/kubernetes/bin/flanneld ${FLANNEL_ETCD} ${FLANNEL_ETCD_KEY} ${FLANNEL_ETCD_CAFILE} ${FLANNEL_ETCD_CERTFILE} ${FLANNEL_ETCD_KEYFILE}
ExecStartPost=/opt/kubernetes/bin/mk-docker-opts.sh -d /run/flannel/docker
Type=notify
[Install]
WantedBy=multi-user.target
RequiredBy=docker.service
复制系统服务脚本到其它节点上
# scp /usr/lib/systemd/system/flannel.service 192.168.56.120:/usr/lib/systemd/system/
# scp /usr/lib/systemd/system/flannel.service 192.168.56.130:/usr/lib/systemd/system/
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="三flannel-cni集成"&gt;三、Flannel CNI集成&lt;/h2&gt;
&lt;h3 id="1下载cni插件"&gt;（1）下载CNI插件&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;https://github.com/containernetworking/plugins/releases
wget https://github.com/containernetworking/plugins/releases/download/v0.7.1/cni-plugins-amd64-v0.7.1.tgz
[root@linux-node1 ~]# mkdir /opt/kubernetes/bin/cni
[root@linux-node2 ~]# mkdir /opt/kubernetes/bin/cni
[root@linux-node3 ~]# mkdir /opt/kubernetes/bin/cni
[root@linux-node1 src]# tar zxf cni-plugins-amd64-v0.7.1.tgz -C /opt/kubernetes/bin/cni
[root@linux-node1 src]# scp -r /opt/kubernetes/bin/cni/* 192.168.56.120:/opt/kubernetes/bin/cni/
[root@linux-node1 src]# scp -r /opt/kubernetes/bin/cni/* 192.168.56.130:/opt/kubernetes/bin/cni/
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2创建etcd的key"&gt;（2）创建Etcd的key&lt;/h3&gt;
&lt;p&gt;此步的操作是为了创建POD的网段，并在ETCD中存储，而后FLANNEL从ETCD中取出并进行分配&lt;/p&gt;</description></item><item><title>k8s学习笔记-04-Node节点二进制部署</title><link>/2018/08/14/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-04-node%E8%8A%82%E7%82%B9%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2/</link><pubDate>Tue, 14 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/14/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-04-node%E8%8A%82%E7%82%B9%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="k8s-node节点部署"&gt;K8S Node节点部署&lt;/h3&gt;
&lt;h3 id="1部署kubelet"&gt;1、部署kubelet&lt;/h3&gt;
&lt;h5 id="1二进制包准备"&gt;（1）二进制包准备&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# cd /usr/local/src/kubernetes/server/bin/
[root@linux-node1 bin]# cp kubelet kube-proxy /opt/kubernetes/bin/
[root@linux-node1 bin]# scp kubelet kube-proxy 192.168.56.120:/opt/kubernetes/bin/
[root@linux-node1 bin]# scp kubelet kube-proxy 192.168.56.130:/opt/kubernetes/bin/
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="2创建角色绑定"&gt;（2）创建角色绑定&lt;/h5&gt;
&lt;p&gt;kubelet启动时会向kube-apiserver发送tsl bootstrap请求，所以需要将bootstrap的token设置成对应的角色，这样kubectl才有权限创建该请求。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --user=kubelet-bootstrap
clusterrolebinding &amp;#34;kubelet-bootstrap&amp;#34; created
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="3创建-kubelet-bootstrapping-kubeconfig-文件-设置集群参数"&gt;（3）创建 kubelet bootstrapping kubeconfig 文件 设置集群参数&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# cd /usr/local/src/ssl
[root@linux-node1 ssl]# kubectl config set-cluster kubernetes \
--certificate-authority=/opt/kubernetes/ssl/ca.pem \
--embed-certs=true \
--server=https://192.168.56.110:6443 \
--kubeconfig=bootstrap.kubeconfig
Cluster &amp;#34;kubernetes&amp;#34; set.
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="4设置客户端认证参数"&gt;（4）设置客户端认证参数&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ssl]# kubectl config set-credentials kubelet-bootstrap \
--token=ad6d5bb607a186796d8861557df0d17f \
--kubeconfig=bootstrap.kubeconfig
User &amp;#34;kubelet-bootstrap&amp;#34; set.
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="5设置上下文参数"&gt;（5）设置上下文参数&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ssl]# kubectl config set-context default \
--cluster=kubernetes \
--user=kubelet-bootstrap \
--kubeconfig=bootstrap.kubeconfig
Context &amp;#34;default&amp;#34; created.
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="6选择默认上下文"&gt;（6）选择默认上下文&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ssl]# kubectl config use-context default --kubeconfig=bootstrap.kubeconfig
Switched to context &amp;#34;default&amp;#34;.
[root@linux-node1 ssl]# cp bootstrap.kubeconfig /opt/kubernetes/cfg
[root@linux-node1 ssl]# scp bootstrap.kubeconfig 192.168.56.120:/opt/kubernetes/cfg
[root@linux-node1 ssl]# scp bootstrap.kubeconfig 192.168.56.130:/opt/kubernetes/cfg
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2部署kubelete-1设置cni支持"&gt;2、部署kubelete 1.设置CNI支持&lt;/h3&gt;
&lt;h5 id="1配置cni"&gt;（1）配置CNI&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node2 ~]# mkdir -p /etc/cni/net.d
[root@linux-node2 ~]# vim /etc/cni/net.d/10-default.conf
{
&amp;#34;name&amp;#34;: &amp;#34;flannel&amp;#34;,
&amp;#34;type&amp;#34;: &amp;#34;flannel&amp;#34;,
&amp;#34;delegate&amp;#34;: {
&amp;#34;bridge&amp;#34;: &amp;#34;docker0&amp;#34;,
&amp;#34;isDefaultGateway&amp;#34;: true,
&amp;#34;mtu&amp;#34;: 1400
}
}
[root@linux-node3 ~]# mkdir -p /etc/cni/net.d
[root@linux-node2 ~]# scp /etc/cni/net.d/10-default.conf 192.168.56.130:/etc/cni/net.d/10-default.conf
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="2创建kubelet数据存储目录"&gt;（2）创建kubelet数据存储目录&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node2 ~]# mkdir /var/lib/kubelet
[root@linux-node3 ~]# mkdir /var/lib/kubelet
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="3创建kubelet服务配置"&gt;（3）创建kubelet服务配置&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node2 ~]# vim /usr/lib/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=docker.service
Requires=docker.service
[Service]
WorkingDirectory=/var/lib/kubelet
ExecStart=/opt/kubernetes/bin/kubelet \
--address=192.168.56.120 \
--hostname-override=192.168.56.120 \
--pod-infra-container-image=mirrorgooglecontainers/pause-amd64:3.0 \
--experimental-bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig \
--kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig \
--cert-dir=/opt/kubernetes/ssl \
--network-plugin=cni \
--cni-conf-dir=/etc/cni/net.d \
--cni-bin-dir=/opt/kubernetes/bin/cni \
--cluster-dns=10.1.0.2 \
--cluster-domain=cluster.local. \
--hairpin-mode hairpin-veth \
--allow-privileged=true \
--fail-swap-on=false \
--logtostderr=true \
--v=2 \
--logtostderr=false \
--log-dir=/opt/kubernetes/log
Restart=on-failure
RestartSec=5
[root@linux-node3 ~]# vim /usr/lib/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=docker.service
Requires=docker.service
[Service]
WorkingDirectory=/var/lib/kubelet
ExecStart=/opt/kubernetes/bin/kubelet \
--address=192.168.56.130 \
--hostname-override=192.168.56.130 \
--pod-infra-container-image=mirrorgooglecontainers/pause-amd64:3.0 \
--experimental-bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig \
--kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig \
--cert-dir=/opt/kubernetes/ssl \
--network-plugin=cni \
--cni-conf-dir=/etc/cni/net.d \
--cni-bin-dir=/opt/kubernetes/bin/cni \
--cluster-dns=10.1.0.2 \
--cluster-domain=cluster.local. \
--hairpin-mode hairpin-veth \
--allow-privileged=true \
--fail-swap-on=false \
--logtostderr=true \
--v=2 \
--logtostderr=false \
--log-dir=/opt/kubernetes/log
Restart=on-failure
RestartSec=5
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="4启动kubelet"&gt;（4）启动Kubelet&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node2 ~]# systemctl daemon-reload
[root@linux-node2 ~]# systemctl enable kubelet
[root@linux-node2 ~]# systemctl start kubelet
[root@linux-node2 kubernetes]# systemctl status kubelet
[root@linux-node3 ~]# systemctl daemon-reload
[root@linux-node3 ~]# systemctl enable kubelet
[root@linux-node3 ~]# systemctl start kubelet
[root@linux-node3 kubernetes]# systemctl status kubelet
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在查看kubelet的状态，发现有如下报错&lt;code&gt;Failed to get system container stats for &amp;quot;/system.slice/kubelet.service&amp;quot;: failed to...&lt;/code&gt;此时需要调整&lt;code&gt;kubelet&lt;/code&gt;的启动参数。&lt;/p&gt;</description></item><item><title>k8s学习笔记-03-Mater节点二进制部署</title><link>/2018/08/13/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-03-mater%E8%8A%82%E7%82%B9%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2/</link><pubDate>Mon, 13 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/13/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-03-mater%E8%8A%82%E7%82%B9%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="k8s-mater节点部署"&gt;K8S Mater节点部署&lt;/h2&gt;
&lt;h3 id="1部署kubernetes-api服务部署"&gt;1、部署Kubernetes API服务部署&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;apiserver提供集群管理的REST API接口，包括认证授权、数据校验以及集群状态变更等。&lt;/li&gt;
&lt;li&gt;只有API Server才能直接操作etcd；&lt;/li&gt;
&lt;li&gt;其他模块通过API Server查询或修改数据&lt;/li&gt;
&lt;li&gt;提供其他模块之间的数据交互和通信枢纽&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="1准备软件包"&gt;（1）准备软件包&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# cd /usr/local/src/kubernetes
[root@linux-node1 kubernetes]# cp server/bin/kube-apiserver /opt/kubernetes/bin/
[root@linux-node1 kubernetes]# cp server/bin/kube-controller-manager /opt/kubernetes/bin/
[root@linux-node1 kubernetes]# cp server/bin/kube-scheduler /opt/kubernetes/bin/只需要在linux-node1上拷贝
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="2创建生成csr的-json-配置文件"&gt;（2）创建生成CSR的 JSON 配置文件&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# cd /usr/local/src/ssl[root@linux-node1 ssl]# vim kubernetes-csr.json
{
&amp;#34;CN&amp;#34;: &amp;#34;kubernetes&amp;#34;,
&amp;#34;hosts&amp;#34;: [
&amp;#34;127.0.0.1&amp;#34;,
&amp;#34;192.168.56.110&amp;#34;,　　#Master的ip地址
&amp;#34;10.1.0.1&amp;#34;,
&amp;#34;kubernetes&amp;#34;,
&amp;#34;kubernetes.default&amp;#34;,
&amp;#34;kubernetes.default.svc&amp;#34;,
&amp;#34;kubernetes.default.svc.cluster&amp;#34;,
&amp;#34;kubernetes.default.svc.cluster.local&amp;#34;
],
&amp;#34;key&amp;#34;: {
&amp;#34;algo&amp;#34;: &amp;#34;rsa&amp;#34;,
&amp;#34;size&amp;#34;: 2048
},
&amp;#34;names&amp;#34;: [
{
&amp;#34;C&amp;#34;: &amp;#34;CN&amp;#34;,
&amp;#34;ST&amp;#34;: &amp;#34;BeiJing&amp;#34;,
&amp;#34;L&amp;#34;: &amp;#34;BeiJing&amp;#34;,
&amp;#34;O&amp;#34;: &amp;#34;k8s&amp;#34;,
&amp;#34;OU&amp;#34;: &amp;#34;System&amp;#34;
}
]
}
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="3生成-kubernetes-证书和私钥"&gt;（3）生成 kubernetes 证书和私钥&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ssl]# cfssl gencert -ca=/opt/kubernetes/ssl/ca.pem \
-ca-key=/opt/kubernetes/ssl/ca-key.pem \
-config=/opt/kubernetes/ssl/ca-config.json \
-profile=kubernetes kubernetes-csr.json | cfssljson -bare kubernetes
[root@linux-node1 ssl]# cp kubernetes*.pem /opt/kubernetes/ssl/
[root@linux-node1 ssl]# scp kubernetes*.pem 192.168.56.120:/opt/kubernetes/ssl/
[root@linux-node1 ssl]# scp kubernetes*.pem 192.168.56.130:/opt/kubernetes/ssl/
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="4-创建-kube-apiserver-使用的客户端-token-文件"&gt;（4） 创建 kube-apiserver 使用的客户端 token 文件&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# head -c 16 /dev/urandom | od -An -t x | tr -d &amp;#39; &amp;#39;
ad6d5bb607a186796d8861557df0d17f
[root@linux-node1 ~]# vim /opt/kubernetes/ssl/bootstrap-token.csv
ad6d5bb607a186796d8861557df0d17f,kubelet-bootstrap,10001,&amp;#34;system:kubelet-bootstrap&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="5-创建基础用户名密码认证配置"&gt;（5） 创建基础用户名/密码认证配置&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# vim /opt/kubernetes/ssl/basic-auth.csv
admin,admin,1
readonly,readonly,2
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="6-部署kubernetes-api-server"&gt;（6） 部署Kubernetes API Server&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# vim /usr/lib/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target
[Service]
ExecStart=/opt/kubernetes/bin/kube-apiserver \
--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota,NodeRestriction \
--bind-address=192.168.56.110 \
--insecure-bind-address=127.0.0.1 \
--authorization-mode=Node,RBAC \
--runtime-config=rbac.authorization.k8s.io/v1 \
--kubelet-https=true \
--anonymous-auth=false \
--basic-auth-file=/opt/kubernetes/ssl/basic-auth.csv \
--enable-bootstrap-token-auth \
--token-auth-file=/opt/kubernetes/ssl/bootstrap-token.csv \
--service-cluster-ip-range=10.1.0.0/16 \
--service-node-port-range=20000-40000 \
--tls-cert-file=/opt/kubernetes/ssl/kubernetes.pem \
--tls-private-key-file=/opt/kubernetes/ssl/kubernetes-key.pem \
--client-ca-file=/opt/kubernetes/ssl/ca.pem \
--service-account-key-file=/opt/kubernetes/ssl/ca-key.pem \
--etcd-cafile=/opt/kubernetes/ssl/ca.pem \
--etcd-certfile=/opt/kubernetes/ssl/kubernetes.pem \
--etcd-keyfile=/opt/kubernetes/ssl/kubernetes-key.pem \
--etcd-servers=https://192.168.56.110:2379,https://192.168.56.120:2379,https://192.168.56.130:2379 \
--enable-swagger-ui=true \
--allow-privileged=true \
--audit-log-maxage=30 \
--audit-log-maxbackup=3 \
--audit-log-maxsize=100 \
--audit-log-path=/opt/kubernetes/log/api-audit.log \
--event-ttl=1h \
--v=2 \
--logtostderr=false \
--log-dir=/opt/kubernetes/log
Restart=on-failure
RestartSec=5
Type=notify
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;h5 id="7-启动api-server服务"&gt;（7） 启动API Server服务&lt;/h5&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# systemctl daemon-reload
[root@linux-node1 ~]# systemctl enable kube-apiserver
[root@linux-node1 ~]# systemctl start kube-apiserver
[root@linux-node1 ~]# systemctl status kube-apiserver
[root@linux-node1 ssl]# netstat -tulnp |grep kube-apiserver
tcp 0 0 192.168.56.110:6443 0.0.0.0:* LISTEN 5052/kube-apiserver
tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN 5052/kube-apiserver
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;从监听端口可以看到api-server监听在6443端口，同时也监听了本地的8080端口，是提供kube-schduler和kube-controller使用。&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>mask rcnn 数据基础介绍</title><link>/2018/08/13/mask-rcnn-%E6%95%B0%E6%8D%AE%E5%9F%BA%E7%A1%80%E4%BB%8B%E7%BB%8D/</link><pubDate>Mon, 13 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/13/mask-rcnn-%E6%95%B0%E6%8D%AE%E5%9F%BA%E7%A1%80%E4%BB%8B%E7%BB%8D/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="概念理解"&gt;概念理解&lt;/h1&gt;
&lt;p&gt;Mask R-CNN (Regional Convolutional Neural Network: 区域卷积神经网络)&lt;/p&gt;
&lt;p&gt;&lt;img alt="ComputerVisionTasks" loading="lazy" src="/2018/08/13/mask-rcnn-%E6%95%B0%E6%8D%AE%E5%9F%BA%E7%A1%80%E4%BB%8B%E7%BB%8D/ComputerVisionTasks.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;图像分类 (Classification)&lt;/strong&gt;: 图片中有气球。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;语义分割 (Semantic Segmentation)&lt;/strong&gt;: 这些是图片中组成所有气球的像素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;目标检测 (Object Detection)&lt;/strong&gt;: 这里是图片中7个气球的位置。我们需要识别被遮挡的物体。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;实例分割 (Instance Segmentation)&lt;/strong&gt;: 这是是图片中7个气球的位置，包括组成每一个气球的像素。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;mAP: 均值平均精度(Mean Average Precision)&lt;/p&gt;
&lt;p&gt;Backbone: 骨干网络&lt;/p&gt;
&lt;p&gt;backbone feature map : 骨干特征图&lt;/p&gt;
&lt;p&gt;Feature Pyramid Network (FPN): 特征金字塔网络&lt;/p&gt;
&lt;p&gt;&lt;img alt="FPNFlowChart" loading="lazy" src="/2018/08/13/mask-rcnn-%E6%95%B0%E6%8D%AE%E5%9F%BA%E7%A1%80%E4%BB%8B%E7%BB%8D/FPNFlowChart.png"&gt;&lt;/p&gt;
&lt;p&gt;Mask RCNN 的实现采用的是 ResNet101 + FPN 作为骨干网络&lt;/p&gt;
&lt;p&gt;Region Proposal Network (RPN): 区域提议网络&lt;/p&gt;
&lt;p&gt;Anchor: 锚点, RPN 通过扫描骨干特征图（backbone feature map ） 得到锚点的. SSD 中称为先验框Prior boxes&lt;/p&gt;
&lt;p&gt;Non-max Suppression : 非最大抑制, 在 RPN 预测过程中，我们会选择最可能包含物体的锚点，然后去修正它们的位置以及尺寸。如果有些锚点重叠得太多，我们会保留前景得分最高的一个，而其他的锚点就丢弃掉（这种方式成为非最大抑制 Non-max Suppression ）。&lt;/p&gt;</description></item><item><title>mask rcnn 数据预处理解读</title><link>/2018/08/13/mask-rcnn-%E6%95%B0%E6%8D%AE%E9%A2%84%E5%A4%84%E7%90%86%E8%A7%A3%E8%AF%BB/</link><pubDate>Mon, 13 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/13/mask-rcnn-%E6%95%B0%E6%8D%AE%E9%A2%84%E5%A4%84%E7%90%86%E8%A7%A3%E8%AF%BB/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; os
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; sys
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; json
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; skimage.draw
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; skimage.io
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; skimage.color
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; imgaug &lt;span style="color:#f92672"&gt;import&lt;/span&gt; augmenters &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; iaa
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; numpy &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; np
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; random
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; matplotlib.pyplot &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; plt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# plt.switch_backend(&amp;#39;agg&amp;#39;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Root directory of the project&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ROOT_DIR &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;abspath(&lt;span style="color:#e6db74"&gt;&amp;#34;../../&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sys&lt;span style="color:#f92672"&gt;.&lt;/span&gt;path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(ROOT_DIR)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Import Mask RCNN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; mrcnn &lt;span style="color:#f92672"&gt;import&lt;/span&gt; visualize
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; mrcnn.config &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Config
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; mrcnn &lt;span style="color:#f92672"&gt;import&lt;/span&gt; model &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; modellib, utils
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; mrcnn.model &lt;span style="color:#f92672"&gt;import&lt;/span&gt; log
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; samples.cells &lt;span style="color:#f92672"&gt;import&lt;/span&gt; run_via_json
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;COCO_MODEL_PATH &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;join(ROOT_DIR, &lt;span style="color:#e6db74"&gt;&amp;#34;mask_rcnn_coco.h5&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DEFAULT_LOGS_DIR &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;join(ROOT_DIR, &lt;span style="color:#e6db74"&gt;&amp;#34;logs&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;class_names &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;BG&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;cells1&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;cells2&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;train_class_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;cells1&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;cells2&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;show_class_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;cells2&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;train_config &lt;span style="color:#f92672"&gt;=&lt;/span&gt; run_via_json&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TrainConfig()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;config &lt;span style="color:#f92672"&gt;=&lt;/span&gt; train_config
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dataset &lt;span style="color:#f92672"&gt;=&lt;/span&gt; run_via_json&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CellDataset()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dataset&lt;span style="color:#f92672"&gt;.&lt;/span&gt;load_data(train_config&lt;span style="color:#f92672"&gt;.&lt;/span&gt;dataset_dir, train_config&lt;span style="color:#f92672"&gt;.&lt;/span&gt;train_dir_name, class_names,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; annotation&lt;span style="color:#f92672"&gt;=&lt;/span&gt;train_config&lt;span style="color:#f92672"&gt;.&lt;/span&gt;train_annotation_json)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dataset&lt;span style="color:#f92672"&gt;.&lt;/span&gt;prepare()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>k8s学习笔记-02-ETCD集群二进制部署</title><link>/2018/08/12/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-02-etcd%E9%9B%86%E7%BE%A4%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2/</link><pubDate>Sun, 12 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/12/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-02-etcd%E9%9B%86%E7%BE%A4%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="etcd集群部署"&gt;ETCD集群部署&lt;/h3&gt;
&lt;p&gt;所有持久化的状态信息以KV的形式存储在ETCD中。类似zookeeper，提供分布式协调服务。之所以说kubenetes各个组件是无状态的，就是因为其中把数据都存放在ETCD中。由于ETCD支持集群，这里在三台主机上都部署上ETCD。&lt;/p&gt;
&lt;p&gt;&lt;img alt="1349539-20180706111531842-16242459" loading="lazy" src="/2018/08/12/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-02-etcd%E9%9B%86%E7%BE%A4%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2/1349539-20180706111531842-16242459.png"&gt;&lt;/p&gt;
&lt;h4 id="1准备etcd软件包"&gt;（1）准备etcd软件包&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;wget https://github.com/coreos/etcd/releases/download/v3.2.18/etcd-v3.2.18-linux-amd64.tar.gz
[root@linux-node1 src]# tar zxf etcd-v3.2.18-linux-amd64.tar.gz　　#解压etcd
[root@linux-node1 src]# cd etcd-v3.2.18-linux-amd64　　#有2个文件，etcdctl是操作etcd的命令
[root@linux-node1 etcd-v3.2.18-linux-amd64]# cp etcd etcdctl /opt/kubernetes/bin/
[root@linux-node1 etcd-v3.2.18-linux-amd64]# scp etcd etcdctl 192.168.56.120:/opt/kubernetes/bin/
[root@linux-node1 etcd-v3.2.18-linux-amd64]# scp etcd etcdctl 192.168.56.130:/opt/kubernetes/bin/
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="2创建-etcd-证书签名请求"&gt;（2）创建 etcd 证书签名请求&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# cd /usr/local/src/ssl
[root@linux-node1 ssl]# vim etcd-csr.json
{
&amp;#34;CN&amp;#34;: &amp;#34;etcd&amp;#34;,
&amp;#34;hosts&amp;#34;: [　　#此处的ip是etcd集群中各个节点的ip地址
&amp;#34;127.0.0.1&amp;#34;,
&amp;#34;192.168.56.110&amp;#34;,
&amp;#34;192.168.56.120&amp;#34;,
&amp;#34;192.168.56.130&amp;#34;
],
&amp;#34;key&amp;#34;: {
&amp;#34;algo&amp;#34;: &amp;#34;rsa&amp;#34;,
&amp;#34;size&amp;#34;: 2048
},
&amp;#34;names&amp;#34;: [
{
&amp;#34;C&amp;#34;: &amp;#34;CN&amp;#34;,
&amp;#34;ST&amp;#34;: &amp;#34;BeiJing&amp;#34;,
&amp;#34;L&amp;#34;: &amp;#34;BeiJing&amp;#34;,
&amp;#34;O&amp;#34;: &amp;#34;k8s&amp;#34;,
&amp;#34;OU&amp;#34;: &amp;#34;System&amp;#34;
}
]
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="3生成-etcd-证书和私钥"&gt;（3）生成 etcd 证书和私钥&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# cfssl gencert -ca=/opt/kubernetes/ssl/ca.pem \
-ca-key=/opt/kubernetes/ssl/ca-key.pem \
-config=/opt/kubernetes/ssl/ca-config.json \
-profile=kubernetes etcd-csr.json | cfssljson -bare etcd
会生成以下证书文件
[root@linux-node1 ~]# ls -l etcd*
-rw-r--r-- 1 root root 1045 Mar 5 11:27 etcd.csr
-rw-r--r-- 1 root root 257 Mar 5 11:25 etcd-csr.json
-rw------- 1 root root 1679 Mar 5 11:27 etcd-key.pem
-rw-r--r-- 1 root root 1419 Mar 5 11:27 etcd.pem
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="4将证书拷贝到optkubernetesssl目录下"&gt;（4）将证书拷贝到/opt/kubernetes/ssl目录下&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@linux-node1 ~]# cp etcd*.pem /opt/kubernetes/ssl
[root@linux-node1 ~]# scp etcd*.pem 192.168.56.120:/opt/kubernetes/ssl
[root@linux-node1 ~]# scp etcd*.pem 192.168.56.130:/opt/kubernetes/ssl
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="5配置etcd配置文件"&gt;（5）配置ETCD配置文件&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;2379端口用于外部通信，2380用于内部通信&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>k8s学习笔记-01-概念和创建证书</title><link>/2018/08/11/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-01-%E6%A6%82%E5%BF%B5%E5%92%8C%E5%88%9B%E5%BB%BA%E8%AF%81%E4%B9%A6/</link><pubDate>Sat, 11 Aug 2018 00:00:00 +0000</pubDate><guid>/2018/08/11/k8s%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-01-%E6%A6%82%E5%BF%B5%E5%92%8C%E5%88%9B%E5%BB%BA%E8%AF%81%E4%B9%A6/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1kubernetes的重要概念"&gt;&lt;strong&gt;1、Kubernetes的重要概念&lt;/strong&gt;&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cluster&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cluster 是计算、存储和网络资源的集合，Kubernetes 利用这些资源运行各种基于容器的应用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Master&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Master 是 Cluster 的大脑，它的主要职责是调度，即决定将应用放在哪里运行。Master 运行 Linux 操作系统，可以是物理机或者虚拟机。为了实现高可用，可以运行多个 Master。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Node&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Node 的职责是运行容器应用。Node 由 Master 管理，Node 负责监控并汇报容器的状态，并根据 Master 的要求管理容器的生命周期。Node 运行在 Linux 操作系统，可以是物理机或者是虚拟机。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pod&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pod 是 Kubernetes 的最小工作单元。每个 Pod 包含一个或多个容器。Pod 中的容器会作为一个整体被 Master 调度到一个 Node 上运行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kubernetes 引入 Pod 主要基于下面两个目的：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;可管理性。
有些容器天生就是需要紧密联系，一起工作。Pod 提供了比容器更高层次的抽象，将它们封装到一个部署单元中。Kubernetes 以 Pod 为最小单位进行调度、扩展、共享资源、管理生命周期。&lt;/li&gt;
&lt;li&gt;通信和资源共享。
Pod 中的所有容器使用同一个网络 namespace，即相同的 IP 地址和 Port 空间。它们可以直接用 localhost 通信。同样的，这些容器可以共享存储，当 Kubernetes 挂载 volume 到 Pod，本质上是将 volume 挂载到 Pod 中的每一个容器。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Pods 有两种使用方式：&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>tensorRT安装</title><link>/2018/07/15/tensorrt%E5%AE%89%E8%A3%85/</link><pubDate>Sun, 15 Jul 2018 00:00:00 +0000</pubDate><guid>/2018/07/15/tensorrt%E5%AE%89%E8%A3%85/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="tensorrt简介"&gt;tensorRT简介&lt;/h2&gt;
&lt;p&gt;深度学习的发展带动了一批深度学习框架，caffe、tensorflow、pytorch等，对于计算量庞大的CNN，效率一直是大家所关注的，接触过深度网络压缩的同学应该知道网络压缩最关键的两个思路，剪枝和量化。
TensorRT就是量化，将FP32位权值数据优化为 FP16 或者 INT8，而推理精度不发生明显的降低。
关于TensorRT首先要清楚以下几点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;TensorRT是NVIDIA开发的深度学习推理工具，只支持推理，不支持训练；
目前TensorRT4已经支持Caffe、Caffe2、TensorFlow、MxNet、Pytorch等主流深度学习库；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TensorRT底层针对NVIDIA显卡做了多方面的优化，不仅仅是量化，可以和 CUDA CODEC SDK 结合使用，也就是另一个开发包DeepStream；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TensorRT独立于深度学习框架，通过解析框架文件来实现，不需要额外安装DL库；&lt;/p&gt;
&lt;p&gt;参考示意图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="tensorrt_01" loading="lazy" src="/2018/07/15/tensorrt%E5%AE%89%E8%A3%85/tensorrt_01.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="tensorrt安装"&gt;tensorRT安装&lt;/h2&gt;
&lt;p&gt;1、安装tensorRT之前准备&lt;/p&gt;</description></item><item><title>Deep Learning Video Classification and Captioning</title><link>/2018/07/07/deep-learning-video-classification-and-captioning/</link><pubDate>Sat, 07 Jul 2018 00:00:00 +0000</pubDate><guid>/2018/07/07/deep-learning-video-classification-and-captioning/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="基本术语"&gt;基本术语&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;视频分类&lt;/strong&gt;（Video Classification）：将基于视频的语义内容如人类行为和复杂事件等，将视频片段自动分类至单个或多个类别，视频分类的研究内容主要包括多标签的通用视频分类和人类行为识别等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;视频描述生成&lt;/strong&gt;（Video Captioning）：试图基于视频分类的标签，形成完整的自然语句，为视频生成包含最多动态信息的描述说明。&lt;/li&gt;
&lt;li&gt;词袋模型（Bag of Words）&lt;/li&gt;
&lt;li&gt;3D CNN&lt;/li&gt;
&lt;li&gt;Two-stream CNN&lt;/li&gt;
&lt;li&gt;TSN模型&lt;/li&gt;
&lt;li&gt;LSTM&lt;/li&gt;
&lt;li&gt;GRU-RNN&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="研究方法"&gt;研究方法&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;一、&lt;/strong&gt; &lt;strong&gt;传统视频分类方法研究&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在深度学习方法广泛应用之前，大多数的视频分类方法采用基于人工设计的特征和典型的机器学习方法研究行为识别和事件检测。&lt;/p&gt;
&lt;p&gt;传统的视频分类研究专注于采用对局部时空区域的运动信息和表观（Appearance）信息编码的方式获取视频描述符，然后利用词袋模型（Bag of Words）等方式生成视频编码，最后利用视频编码来训练分类器（如SVM），区分视频类别。视频的描述符依赖人工设计的特征，如使用运动信息获取局部时空特征的梯度直方图（Histogram of Oriented Gradients，HOG），使用不同类型的轨迹的光流直方图（Histogram of Optical Flow, HOF）和运动边界直方图（Motion Boundary Histogram，MBH）。通过词袋模型或Fisher向量方法，这些特征可以生成视频编码。&lt;/p&gt;
&lt;p&gt;当前，基于轨迹的方法（尤其是DT和IDT）是最高水平的人工设计特征算法的基础[&lt;a href="https://zhuanlan.zhihu.com/p/28179049/edit#_ENREF_2"&gt;2&lt;/a&gt;]。许多研究者正在尝试改进IDT，如通过增加字典的大小和融合多种编码方法，通过开发子采样方法生成DT特征的字典，在许多人体行为数据集上取得了不错的性能。&lt;/p&gt;
&lt;p&gt;然而，随着深度神经网络的兴起，特别是CNN、LSTM、GRU等在视频分类中的成功应用，其分类性能逐渐超越了基于DT和IDT的传统方法，使得这些传统方法逐渐淡出了人们的视野。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二、&lt;/strong&gt; &lt;strong&gt;深度网络方法研究&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;**深度网络为解决大规模视频分类问题提供了新的思路和方法。**近年来得益于深度学习研究的巨大进展，特别是卷积神经网络（Convolutional Neural Networks, CNN）作为一种理解图像内容的有效模型，在图像识别、分割、检测和检索等方面取得了最高水平的研究成果。卷积神经网络CNN在静态图像识别问题中取得了空前的成功，其中包括MNIST、CIFAR和ImageNet大规模视觉识别挑战问题。CNN采用卷积与池化操作，可以自动学习图像中包含的复杂特征，在视觉对象识别任务中表现出很好的性能。基于CNN这些研究成果，国内外开始研究将CNN等深度网络应用到视频和行为分类任务中。&lt;/p&gt;
&lt;p&gt;与图像识别相比，视频分类任务中视频比静态图像可以提供更多的信息，包括随时间演化的复杂运动信息等。视频（即使是短视频）中包含成百上千帧图像，但并不是所有图像都有用，处理这些帧图像需要大量的计算。最简单的方法是将这些视频帧视为一张张静态图像，应用CNN识别每一帧，然后对预测结果进行平均处理来作为该视频的最终结果。然而，这个方法使用了不完整的视频信息，因此使得分类器可能容易发生混乱。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（1）&lt;/strong&gt; &lt;strong&gt;监督学习方法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;i.&lt;/em&gt; 基于图像的视频分类：将视频片段视为视频帧的集合，每个视频帧的特征通过ImageNet数据集上预先训练的最高水平的深度模型（如&lt;em&gt;AlexNet，VGGNet，GoogLeNet，ResNet&lt;/em&gt;）进行获取*。最终，帧层特征汇聚为视频层特征，作为标准分类器（如SVM）识别的输入。*&lt;/p&gt;
&lt;p&gt;ii. 端到端的CNN网络：关注于利用CNN模型学习视频隐含的时空模式，如3D CNN，Two-stream CNN，TSN模型等。&lt;/p&gt;
&lt;p&gt;iii. 双流（Two-stream）法中的时间CNN只能获取很短时间窗口内的运动信息，难以处理长时间多种行为组成的复杂事件和行为。因此，&lt;strong&gt;引入RNN来建模长期时间动态过程&lt;/strong&gt;，常用的模型有LSTM，GRU-RNN等。LSTM避免了梯度消失的问题，在许多图像和视频摘要、语音分析任务中非常有效。&lt;/p&gt;
&lt;p&gt;iv. 视频中包含了很多帧，处理所有的视频帧计算代价很大，也会降低识别那些与类别相关的视频帧的性能。因此，&lt;strong&gt;引入视觉注意力机制&lt;/strong&gt;来识别那些与目标语义直接相关的最有判别力的时空特征&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（2）&lt;/strong&gt; &lt;strong&gt;非监督学习方法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;采用非监督学习的方法，整合空间和时间上下文信息，是发现和描述视频结构的一种很有前途的方法。&lt;/p&gt;
&lt;h1 id="datasets"&gt;datasets&lt;/h1&gt;
&lt;p&gt;近年来为推动视频分类的研究，也陆续发布了相关的视频数据集。小型标注良好的数据集如KTH，Hollywood2，Weizmann；中型的数据集如UCF101，Thumos’14和HMDB51，这些数据集超过了50类行为类别；大型数据集如Sports-1M，YFCC-100M，FCVID数据集，ActivityNet数据集，YouTube-8M等。&lt;/p&gt;
&lt;p&gt;![Deep Learning Video Classification and Captioning](Deep Learning Video Classification and Captioning.jpg)&lt;/p&gt;</description></item><item><title>机器学习常用术语</title><link>/2018/06/13/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E6%9C%AF%E8%AF%AD/</link><pubDate>Wed, 13 Jun 2018 00:00:00 +0000</pubDate><guid>/2018/06/13/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E6%9C%AF%E8%AF%AD/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="概念理解"&gt;概念理解&lt;/h1&gt;
&lt;p&gt;Mask R-CNN (Regional Convolutional Neural Network: 区域卷积神经网络)&lt;/p&gt;
&lt;p&gt;&lt;img alt="ComputerVisionTasks" loading="lazy" src="/2018/06/13/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E6%9C%AF%E8%AF%AD/ComputerVisionTasks.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;图像分类 (Classification)&lt;/strong&gt;: 图片中有气球。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;语义分割 (Semantic Segmentation)&lt;/strong&gt;: 这些是图片中组成所有气球的像素。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;目标检测 (Object Detection)&lt;/strong&gt;: 这里是图片中7个气球的位置。我们需要识别被遮挡的物体。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;实例分割 (Instance Segmentation)&lt;/strong&gt;: 这是是图片中7个气球的位置，包括组成每一个气球的像素。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;mAP: 均值平均精度(Mean Average Precision)&lt;/p&gt;
&lt;p&gt;Backbone: 骨干网络&lt;/p&gt;
&lt;p&gt;backbone feature map : 骨干特征图&lt;/p&gt;
&lt;p&gt;Feature Pyramid Network (FPN): 特征金字塔网络&lt;/p&gt;
&lt;p&gt;&lt;img alt="FPNFlowChart" loading="lazy" src="/2018/06/13/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%B8%B8%E7%94%A8%E6%9C%AF%E8%AF%AD/FPNFlowChart.png"&gt;&lt;/p&gt;
&lt;p&gt;Mask RCNN 的实现采用的是 ResNet101 + FPN 作为骨干网络&lt;/p&gt;
&lt;p&gt;Region Proposal Network (RPN): 区域提议网络&lt;/p&gt;
&lt;p&gt;Anchor: 锚点, RPN 通过扫描骨干特征图（backbone feature map ） 得到锚点的. SSD 中称为先验框Prior boxes&lt;/p&gt;
&lt;p&gt;Non-max Suppression : 非最大抑制, 在 RPN 预测过程中，我们会选择最可能包含物体的锚点，然后去修正它们的位置以及尺寸。如果有些锚点重叠得太多，我们会保留前景得分最高的一个，而其他的锚点就丢弃掉（这种方式成为非最大抑制 Non-max Suppression ）。&lt;/p&gt;</description></item><item><title>Overfitting and Regularization</title><link>/2018/05/20/overfitting-and-regularization/</link><pubDate>Sun, 20 May 2018 00:00:00 +0000</pubDate><guid>/2018/05/20/overfitting-and-regularization/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1-underfitting-and-overfitting"&gt;1. underfitting and overfitting&lt;/h1&gt;
&lt;p&gt;我们利用多项式回归获得更加准确的拟合曲线，实现了对训练数据更好的拟合。然而，我们也发现，过渡地对训练数据拟合也会丢失信息规律。看两个概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;欠拟合（underfitting）&lt;/strong&gt;：拟合程度不高，数据距离拟合曲线较远，如下左图所示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;过拟合（overfitting）&lt;/strong&gt;：过度拟合，貌似拟合几乎每一个数据，但是丢失了信息规律，如下右图所示，房价随着房屋面积的增加反而降低了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="underfitting_and_overfitting" loading="lazy" src="underfitting_and_overfitting.png"&gt;&lt;/p&gt;
&lt;p&gt;我们有如下策略来解决过拟合问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;减少特征数，显然这只是权宜之计，因为特征意味着信息，放弃特征也就等同于丢弃信息，要知道，特征的获取往往也是艰苦卓绝的。&lt;/li&gt;
&lt;li&gt;不放弃特征，而是拉伸曲线使之更加平滑以解决过拟合问题，为了拉伸曲线，也就要弱化一些高阶项（曲线曲折的罪魁祸首）。由于高阶项中的&lt;strong&gt;特征 x&lt;/strong&gt; 无法更改，因此特征是无法弱化的，我们能弱化的只有高阶项中的&lt;strong&gt;系数 θi&lt;/strong&gt;。我们把这种弱化称之为是对参数 θ 的惩罚（penalize）。**Regularization（正规化）**正是完成这样一种惩罚的“侩子手”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如下例所示，我们将 θ3 及 θ4 减小（惩罚）到趋近于 0，原本过拟合的曲线就变得更加平滑，趋近于一条二次曲线（在本例中，二次曲线显然更能反映住房面积和房价的关系），也就能够更好的根据住房面积来预测房价。要知道，预测才是我们的最终目的，而非拟合。&lt;/p&gt;
&lt;p&gt;&lt;img alt="regularization" loading="lazy" src="regularization.png"&gt;&lt;/p&gt;
&lt;h1 id="2-regularized-linear-regression"&gt;2. Regularized Linear Regression&lt;/h1&gt;
&lt;p&gt;在线性回归中，我们的预测代价如下评估：&lt;/p&gt;
&lt;p&gt;$$J(\theta)=\frac{1}{2m}\sum\limits_{i=1}^m(h_\theta(x^{(i)})-y^{(i)})^2$$&lt;/p&gt;
&lt;p&gt;为了在最小化 J(θ) 的过程中，也能尽可能使 $θ$ 变小，我们将上式更改为:&lt;/p&gt;
&lt;p&gt;$$\begin{align*} J(\theta) &amp;amp;= \frac{1}{2m}\sum\limits_{i=1}^m(h_\theta(x^{(i)})-y^{(i)})^2+\lambda\sum\limits_{i=1}^{n}\theta_j^2 \ &amp;amp;= \frac{1}{2m}(X\theta-y)^T(X\theta-y)+\lambda\sum\limits_{i=1}^{n}\theta_j^2 \end{align*}$$&lt;/p&gt;
&lt;p&gt;其中，参数 λ 主要是完成以下两个任务:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;保证对数据的拟合良好&lt;/li&gt;
&lt;li&gt;保证 $θ$ 足够小，避免过拟合问题。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;λ 越大，要使 $J(θ)$ 变小，惩罚力度就要变大，这样 θ 会被惩罚得越惨（越小），即要避免过拟合，我们显然应当增大 λλ 的值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;那么，梯度下降也发生相应变化：&lt;/p&gt;
&lt;p&gt;$$\begin{align*} \theta_0 &amp;amp;=\theta_0-\alpha\frac{1}{m}\sum\limits_{i=1}^{m}(h_\theta(x^{(i)})-y^{(i)})x_0^{(i)} \ \theta_j &amp;amp;=\theta_j-\alpha\big(\frac{1}{m}\sum\limits_{i=1}^{m}(h_\theta(x^{(i)})-y^{(i)})x_j^{(i)}+\frac{\lambda}{m}\theta_j\big) \quad (1) \ \mbox {即：}&amp;amp; \ \theta &amp;amp;= \theta-\alpha*(\frac{1}{m} X^T(y-X\theta) + \frac{\lambda}{m}\theta_{j}) \quad j \neq 0 \end{align*}$$&lt;/p&gt;</description></item><item><title>Programming Exercise 2 Logistic Regression</title><link>/2018/05/15/programming-exercise-2-logistic-regression/</link><pubDate>Tue, 15 May 2018 00:00:00 +0000</pubDate><guid>/2018/05/15/programming-exercise-2-logistic-regression/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1-introduction"&gt;1. Introduction&lt;/h1&gt;
&lt;p&gt;任务所要用到的文件
ex2.m - 运行主文件1
ex2 reg.m - 运行主文件1
ex2data1.txt - 主文件1训练数据
ex2data2.txt - 主文件2训练数据
submit.m - 提交任务函数文件
mapFeature.m - Function to generate polynomial features
plotDecisionBoundary.m - Function to plot classifier’s decision bound-
ary
[?] plotData.m - Function to plot 2D classification data
[?] sigmoid.m - Sigmoid Function
[?] costFunction.m - Logistic Regression Cost Function
[?] predict.m - Logistic Regression Prediction Function
[?] costFunctionReg.m - Regularized Logistic Regression Cost
? 表示完成任务所要修改的文件&lt;/p&gt;</description></item><item><title>ARP原理和ARP攻击</title><link>/2018/05/13/arp%E5%8E%9F%E7%90%86%E5%92%8Carp%E6%94%BB%E5%87%BB/</link><pubDate>Sun, 13 May 2018 00:00:00 +0000</pubDate><guid>/2018/05/13/arp%E5%8E%9F%E7%90%86%E5%92%8Carp%E6%94%BB%E5%87%BB/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;ARP（Address Resolution Protocol）地址解析协议，目的是实现IP地址到MAC地址的转换。&lt;/p&gt;
&lt;p&gt;在TCP/IP协议栈中，最不安全的协议莫过于ARP了，我们经常听到的网络扫描，内网渗透，流量欺骗等等，他们基本上都与ARP有关系，甚至可以说，他们的底层都是基于ARP实现的。但是ARP的是实现仅需一问一答的两个包即可，实现上很简单。&lt;/p&gt;
&lt;p&gt;目录&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ARP协议&lt;/li&gt;
&lt;li&gt;ARP数据包信息&lt;/li&gt;
&lt;li&gt;ARP攻击&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="一arp协议"&gt;一、ARP协议&lt;/h3&gt;
&lt;p&gt;ARP（Address Resolution Protocol）地址解析协议，目的是实现IP地址到MAC地址的转换。&lt;/p&gt;
&lt;p&gt;在计算机间通信的时候，计算机要知道目的计算机是谁（就像我们人交流一样，要知道对方是谁），这中间需要涉及到MAC地址，而MAC是真正的电脑的唯一标识符。&lt;/p&gt;
&lt;p&gt;为什么需要ARP协议呢？因为在OSI七层模型中，对数据从上到下进行封装发送出去，然后对数据从下到上解包接收，但是上层（网络层）关心的IP地址，下层关心的是MAC地址，这个时候就需要映射IP和MAC。&lt;/p&gt;
&lt;p&gt;ARP之简单请求应答&lt;/p&gt;
&lt;p&gt;​ 当两台计算机在同一个局域网通信，我们以ping命令为例，该命令使用的ICMP协议&lt;/p&gt;
&lt;p&gt;&lt;img alt="image-20211028215821598" loading="lazy" src="/2018/05/13/arp%E5%8E%9F%E7%90%86%E5%92%8Carp%E6%94%BB%E5%87%BB/image-20211028215821598.png"&gt;&lt;/p&gt;
&lt;p&gt;PC1依据OSI模型①依次从上至下对数据进行封装，包括对ICMP Date加IP包头的封装，但是到了封装MAC地址的时候，②PC1首先查询自己的ARP缓存表，发现没有IP2和他的MAC地址的映射，这个时候MAC数据帧封装失败。我们使用ping命令的时候，是指定PC2的IP2的，计算机是知道目的主机的IP地址，能够完成网络层的数据封装，因为设备通信还需要对方的MAC地址，但是PC1的缓存表里没有，所以在MAC封装的时候填入不了目的MAC地址。&lt;/p&gt;
&lt;p&gt;那么PC1为了获取PC2的MAC地址，③PC1要发送询问信息，询问PC2的MAC地址，询问信息包括PC1的IP和MAC地址、PC2的IP地址，这里我们想到一个问题，即使是询问信息，也是需要进行MAC数据帧的封装，那这个询问信息的目的MAC地址填什么呢，规定当目的MAC地址为ff-ff-ff-ff-ff-ff时，就代表这是一个询问信息，也即使后面我要说的广播。&lt;/p&gt;
&lt;p&gt;PC2收到这个询问信息后，将这里面的IP1和MAC1（PC1的IP和MAC）添加到本地的ARP缓存表中，然后④PC2发送应答信息，对数据进行IP和MAC的封装，发送给PC1，因为缓存表里已经有PC1的IP和MAC的映射了呢。这个应答信息包含PC2的IP2和MAC2。PC1收到这个应答信息，理所应当的就获取了PC2的MAC地址，并添加到自己的缓存表中。&lt;/p&gt;
&lt;p&gt;经过这样交互式的一问一答，PC1和PC2都获得了对方的MAC地址，值得注意的是，目的主机先完成ARP缓存，然后才是源主机完成ARP缓存。之后PC1和PC2就可以真正交流了。&lt;/p&gt;
&lt;p&gt;ARP之广播请求单播回应&lt;/p&gt;
&lt;p&gt;​ 上图面的图解是不完全的ARP协议，因为在局域网里边不会只有两台主机，这里就要考虑如何在局域网众多主机里获得目的主机的MAC。&lt;/p&gt;
&lt;p&gt;&lt;img alt="blob.png" loading="lazy" src="/2018/05/13/arp%E5%8E%9F%E7%90%86%E5%92%8Carp%E6%94%BB%E5%87%BB/1520254833289026.png"&gt;&lt;/p&gt;
&lt;p&gt;和上面的一样，刚开始PC1并不知道PC2的MAC地址，同样需要发送ARP请求，但是这个局域网里主机很多，怎么唯独获取PC2的MAC呢，①我们想到和一群陌生人交流一样，可以挨着询问一遍，这就是我们要说的广播，首先PC1广播发送询问信息（信息和上一张图介绍的一样），在这个普通交换机上连接的设备都会受到这个PC1发送的询问信息。&lt;/p&gt;
&lt;p&gt;接下来②需要做的是，所有在这个交换机上的设备需要判断此询问信息，如果各自的IP和要询问的IP不一致，则丢弃，如图PC3、Route均丢弃该询问信息，而对于PC2判断该询问信息发现满足一致的要求，则接受，同样的写入PC1的IP和MAC到自己的ARP映射表中。&lt;/p&gt;
&lt;p&gt;最后，③PC2单播发送应答信息给PC1，告诉PC1自己的IP和MAC地址。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="二arp数据包信息"&gt;二、ARP数据包信息&lt;/h3&gt;
&lt;p&gt;ARP数据的详细信息列表如下&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hardware type&lt;/th&gt;
&lt;th&gt;硬件类型，标识链路层协议&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Protocol type&lt;/td&gt;
&lt;td&gt;协议类型，标识网络层协议&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware size&lt;/td&gt;
&lt;td&gt;硬件地址大小，标识MAC地址长度，这里是6个字节（48bit）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocol size&lt;/td&gt;
&lt;td&gt;协议地址大小，标识IP地址长度，这里是4个字节（32bit）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opcode&lt;/td&gt;
&lt;td&gt;操作代码，标识ARP数据包类型，1表示请求，2表示回应&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sender MAC address&lt;/td&gt;
&lt;td&gt;发送者MAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sender IP address&lt;/td&gt;
&lt;td&gt;发送者IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target MAC address&lt;/td&gt;
&lt;td&gt;目标MAC，此处全0表示在请求&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target IP address&lt;/td&gt;
&lt;td&gt;目标IP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ARP请求包&lt;/p&gt;
&lt;p&gt;&lt;img alt="blob.png" loading="lazy" src="/2018/05/13/arp%E5%8E%9F%E7%90%86%E5%92%8Carp%E6%94%BB%E5%87%BB/1520257991176324.png"&gt;&lt;/p&gt;
&lt;p&gt;ARP应答包&lt;/p&gt;
&lt;p&gt;内容格式和上图相似，不过会有目的地址对应的MAC地址，ARP数据包类型字段为2。&lt;/p&gt;</description></item><item><title>Underfitting and Overfitting</title><link>/2018/05/10/underfitting-and-overfitting/</link><pubDate>Thu, 10 May 2018 00:00:00 +0000</pubDate><guid>/2018/05/10/underfitting-and-overfitting/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1-underfitting-and-overfitting"&gt;1. underfitting and overfitting&lt;/h1&gt;
&lt;p&gt;我们利用多项式回归获得更加准确的拟合曲线，实现了对训练数据更好的拟合。然而，我们也发现，过渡地对训练数据拟合也会丢失信息规律。看两个概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;欠拟合（underfitting）&lt;/strong&gt;：拟合程度不高，数据距离拟合曲线较远，如下左图所示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;过拟合（overfitting）&lt;/strong&gt;：过度拟合，貌似拟合几乎每一个数据，但是丢失了信息规律，如下右图所示，房价随着房屋面积的增加反而降低了。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="underfitting_and_overfitting" loading="lazy" src="/2018/05/10/underfitting-and-overfitting/underfitting_and_overfitting.png"&gt;&lt;/p&gt;
&lt;p&gt;我们有如下策略来解决过拟合问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;减少特征数，显然这只是权宜之计，因为特征意味着信息，放弃特征也就等同于丢弃信息，要知道，特征的获取往往也是艰苦卓绝的。&lt;/li&gt;
&lt;li&gt;不放弃特征，而是拉伸曲线使之更加平滑以解决过拟合问题，为了拉伸曲线，也就要弱化一些高阶项（曲线曲折的罪魁祸首）。由于高阶项中的&lt;strong&gt;特征 x&lt;/strong&gt; 无法更改，因此特征是无法弱化的，我们能弱化的只有高阶项中的&lt;strong&gt;系数 θi&lt;/strong&gt;。我们把这种弱化称之为是对参数 θ 的惩罚（penalize）。**Regularization（正规化）**正是完成这样一种惩罚的“侩子手”。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如下例所示，我们将 θ3 及 θ4 减小（惩罚）到趋近于 0，原本过拟合的曲线就变得更加平滑，趋近于一条二次曲线（在本例中，二次曲线显然更能反映住房面积和房价的关系），也就能够更好的根据住房面积来预测房价。要知道，预测才是我们的最终目的，而非拟合。&lt;/p&gt;
&lt;p&gt;&lt;img alt="regularization" loading="lazy" src="/2018/05/10/underfitting-and-overfitting/regularization.png"&gt;&lt;/p&gt;
&lt;h1 id="2-regularized-linear-regression"&gt;2. Regularized Linear Regression&lt;/h1&gt;
&lt;p&gt;在线性回归中，我们的预测代价如下评估：&lt;/p&gt;
&lt;p&gt;$$J(\theta)=\frac{1}{2m}\sum\limits_{i=1}^m(h_\theta(x^{(i)})-y^{(i)})^2$$&lt;/p&gt;
&lt;p&gt;为了在最小化 J(θ) 的过程中，也能尽可能使 $θ$ 变小，我们将上式更改为:&lt;/p&gt;
&lt;p&gt;$$\begin{align*} J(\theta) &amp;amp;= \frac{1}{2m}\sum\limits_{i=1}^m(h_\theta(x^{(i)})-y^{(i)})^2+\lambda\sum\limits_{i=1}^{n}\theta_j^2 \ &amp;amp;= \frac{1}{2m}(X\theta-y)^T(X\theta-y)+\lambda\sum\limits_{i=1}^{n}\theta_j^2 \end{align*}$$&lt;/p&gt;
&lt;p&gt;其中，参数 λ 主要是完成以下两个任务:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;保证对数据的拟合良好&lt;/li&gt;
&lt;li&gt;保证 $θ$ 足够小，避免过拟合问题。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;λ 越大，要使 $J(θ)$ 变小，惩罚力度就要变大，这样 θ 会被惩罚得越惨（越小），即要避免过拟合，我们显然应当增大 λλ 的值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;那么，梯度下降也发生相应变化：&lt;/p&gt;
&lt;p&gt;$$\begin{align*} \theta_0 &amp;amp;=\theta_0-\alpha\frac{1}{m}\sum\limits_{i=1}^{m}(h_\theta(x^{(i)})-y^{(i)})x_0^{(i)} \ \theta_j &amp;amp;=\theta_j-\alpha\big(\frac{1}{m}\sum\limits_{i=1}^{m}(h_\theta(x^{(i)})-y^{(i)})x_j^{(i)}+\frac{\lambda}{m}\theta_j\big) \quad (1) \ \mbox {即：}&amp;amp; \ \theta &amp;amp;= \theta-\alpha*(\frac{1}{m} X^T(y-X\theta) + \frac{\lambda}{m}\theta_{j}) \quad j \neq 0 \end{align*}$$&lt;/p&gt;</description></item><item><title>Logistic Regression</title><link>/2018/05/03/logistic-regression/</link><pubDate>Thu, 03 May 2018 00:00:00 +0000</pubDate><guid>/2018/05/03/logistic-regression/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1-classification-and-representation"&gt;1. Classification and Representation&lt;/h1&gt;
&lt;p&gt;使用线性回归来处理 0/1 分类问题总是困难重重的，如下两图，在第一幅图中，拟合曲线成功的区分了 0、1 两类，在第二幅图中，如果我们新增了一个输入（右上的 &lt;strong&gt;X&lt;/strong&gt; 所示），此时拟合曲线发生变化，由第一幅图中的&lt;strong&gt;紫色线&lt;/strong&gt;旋转到第二幅图的&lt;strong&gt;蓝色线&lt;/strong&gt;，导致本应被视作 1 类的 X 被误分为了 0 类：&lt;/p&gt;
&lt;p&gt;&lt;img alt="线性回归处理分类问题1" loading="lazy" src="/2018/05/03/logistic-regression/LR_deal_classification_01.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="线性回归处理分类问题2" loading="lazy" src="/2018/05/03/logistic-regression/LR_deal_classification_02.png"&gt;&lt;/p&gt;
&lt;p&gt;因此，人们定义了逻辑回归来完成 0/1 分类问题。&lt;/p&gt;
&lt;h2 id="11-sigmoid"&gt;1.1 sigmoid&lt;/h2&gt;
&lt;p&gt;用线性回归预测函数hθ(x)来处理回归问题不太有效，特别对于0/1分类问题。新的逻辑回归预测函数&lt;/p&gt;
&lt;p&gt;&lt;img alt="回归预测函数" loading="lazy" src="/2018/05/03/logistic-regression/Hypothesis_Representation.png"&gt;&lt;/p&gt;
&lt;p&gt;g(z) 称之为 Sigmoid Function，亦称 Logic Function，其函数图像如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="sigmoid" loading="lazy" src="/2018/05/03/logistic-regression/sigmoid_function.png"&gt;&lt;/p&gt;
&lt;p&gt;预测函数hθ(x) 被很好地限制在0、1之间。阈值为 0.5，大于则为 1 类，反之为 0 类。函数曲线过渡光滑自然。&lt;/p&gt;
&lt;h2 id="12-decision-boundary"&gt;1.2 Decision Boundary&lt;/h2&gt;
&lt;p&gt;决策边界，顾名思义，就是用来&lt;strong&gt;划清界限&lt;/strong&gt;的边界，边界的形态可以不定，可以是点，可以是线，也可以是平面。Andrew Ng 在公开课中强调：&lt;strong&gt;“决策边界是预测函数 hθ(x)hθ(x) 的属性，而不是训练集属性”&lt;/strong&gt;，这是因为能作出“划清”类间界限的只有 hθ(x)，而训练集只是用来训练和调节参数的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;线性决策边界&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img alt="线性决策边界" loading="lazy" src="/2018/05/03/logistic-regression/liner_decision_boundary.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;非线性决策边界&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img alt="非线性决策边界" loading="lazy" src="/2018/05/03/logistic-regression/no_liner_decision_boundary.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="2-logistic-regression-model"&gt;2. Logistic Regression Model&lt;/h1&gt;
&lt;h2 id="21-cost-function"&gt;2.1 Cost Function&lt;/h2&gt;
&lt;p&gt;&lt;img alt="cost_function" loading="lazy" src="/2018/05/03/logistic-regression/cost_function.png"&gt;&lt;/p&gt;
&lt;p&gt;当y=0时：&lt;/p&gt;
&lt;p&gt;&lt;img alt="cost_function_y0" loading="lazy" src="/2018/05/03/logistic-regression/cost_function_y0.png"&gt;&lt;/p&gt;
&lt;p&gt;当y=1时：&lt;/p&gt;
&lt;p&gt;&lt;img alt="cost_function_y1" loading="lazy" src="/2018/05/03/logistic-regression/cost_function_y1.png"&gt;&lt;/p&gt;
&lt;p&gt;可以看到，当 hθ(x)≈y 时，cost≈0，预测正确。&lt;/p&gt;</description></item><item><title>Programming Exercise 1 Linear Regression</title><link>/2018/04/25/programming-exercise-1-linear-regression/</link><pubDate>Wed, 25 Apr 2018 00:00:00 +0000</pubDate><guid>/2018/04/25/programming-exercise-1-linear-regression/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1-introduction"&gt;1. Introduction&lt;/h1&gt;
&lt;p&gt;任务所要用到的文件&lt;/p&gt;
&lt;p&gt;Files included in this exercise
ex1.m - Octave/MATLAB script that steps you through the exercise
ex1 multi.m - Octave/MATLAB script for the later parts of the exercise
ex1data1.txt - Dataset for linear regression with one variable
ex1data2.txt - Dataset for linear regression with multiple variables
submit.m - Submission script that sends your solutions to our servers
[?] warmUpExercise.m - Simple example function in Octave/MATLAB
[?] plotData.m - Function to display the dataset
[?] computeCost.m - Function to compute the cost of linear regression
[?] gradientDescent.m - Function to run gradient descent
[†] computeCostMulti.m - Cost function for multiple variables
[†] gradientDescentMulti.m - Gradient descent for multiple variables
[†] featureNormalize.m - Function to normalize features
[†] normalEqn.m - Function to compute the normal equations
? indicates files you will need to complete
† indicates optional exercises&lt;/p&gt;</description></item><item><title>Linear Regression with Multiple Variables (week2)</title><link>/2018/04/20/linear-regression-with-multiple-variables-week2/</link><pubDate>Fri, 20 Apr 2018 00:00:00 +0000</pubDate><guid>/2018/04/20/linear-regression-with-multiple-variables-week2/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="multiple-features"&gt;Multiple Features&lt;/h1&gt;
&lt;p&gt;多个特征的数据&lt;/p&gt;
&lt;p&gt;&lt;img alt="Multiple_Features_01" loading="lazy" src="/2018/04/20/linear-regression-with-multiple-variables-week2/Multiple_Features_01.png"&gt;&lt;/p&gt;
&lt;p&gt;对于线性回归：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Multiple_Features_02" loading="lazy" src="/2018/04/20/linear-regression-with-multiple-variables-week2/Multiple_Features_02.png"&gt;&lt;/p&gt;
&lt;p&gt;其中：X0 = 1&lt;/p&gt;
&lt;h2 id="gradient-descent-for-multiple-variables"&gt;Gradient Descent for Multiple Variables&lt;/h2&gt;
&lt;p&gt;对于m个样本，n个特征的，&lt;/p&gt;
&lt;p&gt;&lt;img alt="Gradient_Descent_for_Multiple_Variables_02" loading="lazy" src="/2018/04/20/linear-regression-with-multiple-variables-week2/GD_02.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Gradient_Descent_for_Multiple_Variables_01" loading="lazy" src="/2018/04/20/linear-regression-with-multiple-variables-week2/GD_01.png"&gt;&lt;/p&gt;
&lt;h2 id="feature-scaling"&gt;Feature Scaling&lt;/h2&gt;
&lt;p&gt;对训练数据进行特征缩放可以加快训练速度。&lt;/p&gt;
&lt;p&gt;有两种帮助的技术是&lt;strong&gt;特征缩放&lt;/strong&gt;和&lt;strong&gt;均值归一化&lt;/strong&gt;。特征缩放涉及将输入值除以输入变量的范围（即最大值减去最小值），从而产生仅1的新范围。均值归一化涉及从该值中减去输入变量的平均值。输入变量导致输入变量的新平均值仅为零。要实现这两种技术，请调整输入值，如下面的公式所示：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Multiple_Features_03" loading="lazy" src="/2018/04/20/linear-regression-with-multiple-variables-week2/Multiple_Features_03.png"&gt;&lt;/p&gt;
&lt;h2 id="features-and-polynomial-regression"&gt;Features and Polynomial Regression&lt;/h2&gt;
&lt;p&gt;特征和多项式回归不一样，We can improve our features and the form of our hypothesis function in a couple different ways.&lt;/p&gt;
&lt;p&gt;可以通过组合特征生成新的特征，We can &lt;strong&gt;combine&lt;/strong&gt; multiple features into one. For example, we can combine x_1 and x_2 into a new feature x_3 by taking x_1⋅x_2.&lt;/p&gt;
&lt;p&gt;可以通过取平方，立方，平方根等方法增加多项式，We can &lt;strong&gt;change the behavior or curve&lt;/strong&gt; of our hypothesis function by making it a quadratic, cubic or square root function (or any other form).&lt;/p&gt;</description></item><item><title>model and cost function (week1)</title><link>/2018/04/13/model-and-cost-function-week1/</link><pubDate>Fri, 13 Apr 2018 00:00:00 +0000</pubDate><guid>/2018/04/13/model-and-cost-function-week1/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1model-representation"&gt;1.Model Representation&lt;/h1&gt;
&lt;p&gt;定义：给定一个训练数据集，学习一个函数作为预测器（假设函数hypothesis），这个预测器就是模型。&lt;/p&gt;
&lt;p&gt;given a training set, to learn a function h : X → Y so that h(x) is a “good” predictor for the corresponding value of y. this function h is called a hypothesis.&lt;/p&gt;
&lt;p&gt;&lt;img alt="model_representation_01" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/model_representation_01.png"&gt;&lt;/p&gt;
&lt;h1 id="2cost-function"&gt;2.Cost Function&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;损失函数&lt;/strong&gt; 定义：用来衡量假设函数的准确性。&lt;/p&gt;
&lt;p&gt;对给定的输入x，用预测值(h(x)和真实值y的函数f(h(x), y) 计算得，不同的数据和模型有不同的函数。如果预测结果和真实值越接近，则说明模型学习的越好。如下例子：&lt;/p&gt;
&lt;p&gt;&lt;img alt="cost_function_01" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/cost_function_01.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="cost_function_02" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/cost_function_02.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="cost_function_03" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/cost_function_03.png"&gt;&lt;/p&gt;
&lt;p&gt;不同的模型参数theta得到不同的模型，对应的损失函数值也不同，通过最小化损失函数来寻找最好的模型。&lt;/p&gt;
&lt;p&gt;对于不同的模型，拥有不同的损失函数曲线图，等高线上面的损失值是相等的，虽然模型参数值可能不同。&lt;/p&gt;
&lt;p&gt;&lt;img alt="cost_function_04" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/cost_function_04.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="cost_function_05" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/cost_function_05.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="cost_function_06" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/cost_function_06.png"&gt;&lt;/p&gt;
&lt;h1 id="3gradient-descent"&gt;3.Gradient Descent&lt;/h1&gt;
&lt;p&gt;梯度下降：每个模型有对应的模型参数，损失函数用来衡量模型与数据之间的匹配程度。为了让模型更加的匹配数据，需要对模型参数进行调整，而&lt;strong&gt;梯度下降是一种更新模型参数的方法&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img alt="gradient_descent_01" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/gradient_descent_01.png"&gt;&lt;/p&gt;
&lt;p&gt;注意：在更新参数的时候，计算偏导数 Partial derivative 的时候，使用的是本轮迭代参数更新之前的参数，而不是前面更新了参数，立马在后面的求偏导数中使用更新的参数计算。&lt;/p&gt;
&lt;p&gt;&lt;img alt="gradient_descent_02" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/gradient_descent_02.png"&gt;&lt;/p&gt;
&lt;p&gt;对于学习率的选择：&lt;/p&gt;
&lt;p&gt;&lt;img alt="learning_rate_01" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/learning_rate_01.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="learning_rate_02" loading="lazy" src="/2018/04/13/model-and-cost-function-week1/learning_rate_02.png"&gt;&lt;/p&gt;
&lt;h2 id="gradient-descent-for-linear-regression"&gt;Gradient Descent For Linear Regression&lt;/h2&gt;
&lt;p&gt;看公式：&lt;/p&gt;</description></item><item><title>octave安装和基本使用</title><link>/2018/04/10/octave%E5%AE%89%E8%A3%85%E5%92%8C%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/</link><pubDate>Tue, 10 Apr 2018 00:00:00 +0000</pubDate><guid>/2018/04/10/octave%E5%AE%89%E8%A3%85%E5%92%8C%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="octabe-install"&gt;Octabe install&lt;/h1&gt;
&lt;p&gt;推荐 &lt;a href="http://wiki.octave.org/Octave_for_GNU/Linux"&gt;使用Octave安装包安装&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On Ubuntu, you can use:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install octave
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On Fedora, you can use:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo yum install octave-forge
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="octave-使用"&gt;Octave 使用&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://octave.org/doc/interpreter/"&gt;官方文档&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GNU Octave, version 4.2.2
Copyright (C) 2018 John W. Eaton and others.
Octave was configured for &amp;#34;x86_64-pc-linux-gnu&amp;#34;.
...
For more information, visit http://www.octave.org/get-involved.html
&amp;gt;&amp;gt; 1 ==2
ans = 0
&amp;gt;&amp;gt; 1~=2
ans = 1
# 9x9 随机 0~1
&amp;gt;&amp;gt; rand(9,9)
ans =
0.3560386 0.4607871 0.8059847 0.2042094 0.2411058 0.9311803 0.8023873 0.8208079 0.9631931
0.4604231 0.0222901 0.6693275 0.5887816 0.0076518 0.1670580 0.6335819 0.1711659 0.1405882
0.0519485 0.3400714 0.2909762 0.3687556 0.7300303 0.3436051 0.1679526 0.2550348 0.0342722
0.9379501 0.7760394 0.0859048 0.3292712 0.2385854 0.8567406 0.2433711 0.8027390 0.4103872
0.8297066 0.5138018 0.3540072 0.7688630 0.7550758 0.0676738 0.3528168 0.5365513 0.8800598
0.3350162 0.8376844 0.7863446 0.6448189 0.1355422 0.0235755 0.2975952 0.2879286 0.8854084
0.2484960 0.2086548 0.4821656 0.5838678 0.2676164 0.8016253 0.5552146 0.2787187 0.7647289
0.7559019 0.2639478 0.4199944 0.5312557 0.5202589 0.7852354 0.5681352 0.1088001 0.6572705
0.9477478 0.4998028 0.4179308 0.0513819 0.8775790 0.5860457 0.3993368 0.3765168 0.2140254
&amp;gt;&amp;gt; ones(9,9)
ans =
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
&amp;gt;&amp;gt; W = zeros(9,9);
&amp;gt;&amp;gt; W
W =
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
&amp;gt;&amp;gt; rand(1, 2)
ans =
0.76409 0.77792
&amp;gt;&amp;gt; sqrt(9)
ans = 3
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;
&amp;gt;&amp;gt; A = rand(1, 100000);
&amp;gt;&amp;gt; hist(A, 20)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt="octave_04" loading="lazy" src="/2018/04/10/octave%E5%AE%89%E8%A3%85%E5%92%8C%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/octave_04.png"&gt;&lt;/p&gt;</description></item><item><title>nvidia jetson TX2 刷机</title><link>/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/</link><pubDate>Sat, 07 Apr 2018 00:00:00 +0000</pubDate><guid>/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;TX2 出厂时，已经自带了 Ubuntu 16.04 系统，可以直接启动。但一般我们会选择刷机，目的是更新到最新的 JetPack L4T，并自动安装最新的驱动、CUDA Toolkit、cuDNN、TensorRT。&lt;/p&gt;
&lt;p&gt;刷机注意以下几点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;开发板刷机过程全程联网&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;除了Jetson TX2之外，&lt;strong&gt;您还需要另一台带有Intel或AMD x86处理器的台式机或笔记本电脑。（所以自己要是win电脑，要安装VMware 虚拟机，并在虚拟机上安装Ubuntu 的操作系统，我安装的是Ubuntu -16.04.3 64 位）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;这些类型的机器通常被称为PC的个人电脑。该计算机被称为烧录过程的主机。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JetPack是一个x86二进制文件，不能在基于ARM的机器上运行&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;先推荐一个国外刷机技术视频：&lt;a href="https://v.qq.com/x/page/b0515967lbr.html"&gt;https://v.qq.com/x/page/b0515967lbr.html&lt;/a&gt;
这个视频挺好，虽然是JetPack 3.0刷机，但步骤一模一样。跟着他的步骤刷机就可以，不过有些地方可能不够详细，可以自己先看一遍，有个大致印象。&lt;/p&gt;
&lt;h2 id="1-从官网下载jetpack31"&gt;1. 从官网下载JetPack3.1&lt;/h2&gt;
&lt;p&gt;下载地址为&lt;a href="https://developer.nvidia.com/embedded/jetpack"&gt;jetpack&lt;/a&gt;， 理论上此处下载需要NVIDIA的帐号。&lt;/p&gt;
&lt;h3 id="安装jetpack31"&gt;安装JetPack3.1&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;现在把刚下载的软件包上传的虚拟机中Ubuntu主机中，&lt;strong&gt;可以通过共享文件夹的形式，将JetPack3.1从win电脑copy到vmware虚拟机中的ununtu&lt;/strong&gt;，这个方法大家可以网上搜下，这里不做赘述。&lt;/li&gt;
&lt;li&gt;然后在&lt;strong&gt;虚拟机上，终端定位到Jetpack3.1所在文件夹&lt;/strong&gt;
更改执行权限：
&lt;code&gt;$ chmod +x ./JetPack-L4T-3.1-linux-x64.run&lt;/code&gt;
执行安装
&lt;code&gt;$ sudo ./JetPack-L4T-3.1-linux-x64.run&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后进入了软件界面，&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_001.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_002" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_002.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;选择板子&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_003" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_003.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;随后进入了component manager界面，如图所示：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_004" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_004.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_005" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_005.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;成功后，就要下载各种包了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_006" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_006.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_007" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_007.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_008" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_008.png"&gt;&lt;/p&gt;
&lt;p&gt;连接方式&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_009" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_009.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_010" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_010.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_011" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_011.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_012" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_012.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_013" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_013.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="jetpack_1_begin_014" loading="lazy" src="/2018/04/07/nvidia-jetson-tx2-%E5%88%B7%E6%9C%BA/jetpack_1_begin_014.png"&gt;&lt;/p&gt;</description></item><item><title>Prometheus简介</title><link>/2018/03/28/prometheus%E7%AE%80%E4%BB%8B/</link><pubDate>Wed, 28 Mar 2018 00:00:00 +0000</pubDate><guid>/2018/03/28/prometheus%E7%AE%80%E4%BB%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1prometheus介绍和架构"&gt;1、Prometheus介绍和架构&lt;/h1&gt;
&lt;h2 id="11-prometheus介绍"&gt;1.1 Prometheus介绍&lt;/h2&gt;
&lt;p&gt;Prometheus是一个开源的系统监视和警报工具包，自2012成立以来，许多公司和组织采用了Prometheus。它现在是一个独立的开源项目，并独立于任何公司维护。在2016年，Prometheus加入云计算基金会作为Kubernetes之后的第二托管项目。&lt;/p&gt;
&lt;h3 id="111-prometheus的关键特性"&gt;&lt;strong&gt;1.1.1 Prometheus的关键特性&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;由度量名和键值对标识的时间序列数据的多维数据模型&lt;/li&gt;
&lt;li&gt;灵活的查询语言&lt;/li&gt;
&lt;li&gt;不依赖于分布式存储；单服务器节点是自治的&lt;/li&gt;
&lt;li&gt;通过HTTP上的拉模型实现时间序列收集&lt;/li&gt;
&lt;li&gt;通过中间网关支持推送时间序列&lt;/li&gt;
&lt;li&gt;通过服务发现或静态配置发现目标&lt;/li&gt;
&lt;li&gt;图形和仪表板支持的多种模式&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="112-prometheus的组件"&gt;1.1.2 Prometheus的组件：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如上图，Prometheus主要由以下部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prometheus Server：用于抓取和存储时间序列化数据&lt;/li&gt;
&lt;li&gt;Exporters：主动拉取数据的插件&lt;/li&gt;
&lt;li&gt;Pushgateway：被动拉取数据的插件&lt;/li&gt;
&lt;li&gt;Altermanager：告警发送模块&lt;/li&gt;
&lt;li&gt;Prometheus web UI：界面化，也包含结合Grafana进行数据展示或告警发送&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;prometheus本身是一个以进程方式启动，之后以多进程和多线程实现监控数据收集、计算、查询、更新、存储的这样一个C/S模型运行模式。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="113-prometheus的整体架构"&gt;&lt;strong&gt;1.1.3 Prometheus的整体架构&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Prometheus从jobs获取度量数据，也直接或通过推送网关获取临时jobs的度量数据。它在本地存储所有被获取的样本，并在这些数据运行规则，对现有数据进行聚合和记录新的时间序列，或生成警报。通过Grafana或其他API消费者，可以可视化的查看收集到的数据。下图显示了Pometheus的整体架构和生态组件：&lt;/p&gt;
&lt;p&gt;&lt;img alt="architecture" loading="lazy" src="/2018/03/28/prometheus%E7%AE%80%E4%BB%8B/architecture.svg"&gt;&lt;/p&gt;
&lt;p&gt;Prometheus的整体工作流程：&lt;/p&gt;
&lt;p&gt;1）Prometheus 服务器定期从配置好的 jobs 或者 exporters 中获取度量数据；通过推送网关获取临时jobs的度量数据. -Retrieval 负责获取&lt;/p&gt;
&lt;p&gt;2）Prometheus 服务器在本地存储收集到的度量数据，并对这些数据进行聚合；- 由TSDB负责存储&lt;/p&gt;
&lt;p&gt;3）运行已定义好的 alert.rules，记录新的时间序列或者向告警管理器推送警报。&lt;/p&gt;
&lt;p&gt;4）告警管理器根据配置文件，对接收到的警报进行处理，并通过email等途径发出告警。&lt;/p&gt;
&lt;p&gt;5）Grafana等图形工具获取到监控数据，并以图形化的方式进行展示。&lt;/p&gt;
&lt;h2 id="12-prometheus关键概念"&gt;1.2 Prometheus关键概念&lt;/h2&gt;
&lt;h3 id="121-数据模型"&gt;&lt;strong&gt;1.2.1 数据模型&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Prometheus从根本上将所有数据存储为时间序列：属于相同度量标准和同一组标注尺寸的时间戳值流。除了存储的时间序列之外，普罗米修斯可能会生成临时派生时间序列作为查询的结果。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;度量名称和标签&lt;/strong&gt;：每个时间序列都是由度量标准名称和一组键值对（也称为标签）组成唯一标识。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;度量名称&lt;/strong&gt;指定被测量的系统的特征（例如：http_requests_total-接收到的HTTP请求的总数）。它可以包含ASCII字母和数字，以及下划线和冒号。它必须匹配正则表达式[a-zA-Z_:][a-zA-Z0-9_:]。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签&lt;/strong&gt;启用Prometheus的维度数据模型：对于相同度量标准名称，任何给定的标签组合都标识该度量标准的特定维度实例。查询语言允许基于这些维度进行筛选和聚合。更改任何标签值（包括添加或删除标签）都会创建新的时间序列。标签名称可能包含ASCII字母，数字以及下划线。他们必须匹配正则表达式[a-zA-Z_][a-zA-Z0-9_]*。以__开始的标签名称保留给供内部使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;样本&lt;/strong&gt;：实际的时间序列，每个序列包括：一个 float64 的值和一个毫秒级的时间戳。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**格式：**给定度量标准名称和一组标签，时间序列通常使用以下格式来标识：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;metric name&amp;gt;{&amp;lt;label name&amp;gt;=&amp;lt;label value&amp;gt;, ...}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;例如，时间序列的度量名称为api_http_requests_total，标签method=”POST”和handler=”/messages”，则标记为：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;api_http_requests_total{method=&amp;#34;POST&amp;#34;, handler=&amp;#34;/messages&amp;#34;}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如下，是在进行数据收集后，在Prometheus中的data目录的数据&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# tree
.
├── 01EHSWYREVQ296VKWT5MAJEX90
│   ├── chunks
│   │   └── 000001
│   ├── index
│   ├── meta.json
│   └── tombstones
├── 01EHTAP73QPZY1VH6C2K9GHG6H
│   ├── chunks
│   │   └── 000001
│   ├── index
│   ├── meta.json
│   └── tombstones
...
├── chunks_head
│   ├── 000009
│   └── 000010
├── index.html
├── lock
├── queries.active
└── wal
├── 00000006
├── 00000007
├── 00000008
├── 00000009
└── checkpoint.00000005
└── 00000000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;prometheus采用time-series(时间序列)方式，存储在本地硬盘&lt;/p&gt;</description></item><item><title>horovod</title><link>/2018/03/10/horovod/</link><pubDate>Sat, 10 Mar 2018 00:00:00 +0000</pubDate><guid>/2018/03/10/horovod/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="官方介绍"&gt;官方介绍&lt;/h1&gt;
&lt;p&gt;Horovod is a distributed training framework for TensorFlow, Keras, and PyTorch. The goal of Horovod is to make distributed Deep Learning fast and easy to use.&lt;/p&gt;
&lt;p&gt;官方测试效果&lt;/p&gt;
&lt;p&gt;&lt;img alt="training" loading="lazy" src="/2018/03/10/horovod/horovod.png"&gt;&lt;/p&gt;
&lt;h1 id="running-horovod"&gt;Running Horovod&lt;/h1&gt;
&lt;p&gt;The example commands below show how to run distributed training. See the &lt;a href="https://github.com/uber/horovod/blob/master/docs/running.md"&gt;Running Horovod&lt;/a&gt; page for more instructions, including RoCE/InfiniBand tweaks and tips for dealing with hangs.&lt;/p&gt;
&lt;h2 id="1-单机4卡"&gt;1. 单机4卡:&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# docker
nvidia-docker run -it 172.16.10.10:5000/horovod:0.12.1-tf1.8.0-py3.5
mpirun -np 4 -H localhost:4 python keras_mnist_advanced.py
# singularity
singularity shell --nv /scratch/containers/ubuntu.simg
mpirun -np 4 -H localhost:4 python keras_mnist_advanced.py
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2-多机多卡"&gt;2. 多机多卡:&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ mpirun -np 16 \
-H server1:4,server2:4,server3:4,server4:4 \
...
python train.py
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="3--完整-docker-使用horovod"&gt;3. &lt;a href="https://github.com/uber/horovod/blob/master/docs/docker.md"&gt;完整 Docker 使用horovod &lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;​&lt;/p&gt;</description></item><item><title>Kubernetes之yaml文件详解</title><link>/2018/02/18/kubernetes%E4%B9%8Byaml%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/</link><pubDate>Sun, 18 Feb 2018 00:00:00 +0000</pubDate><guid>/2018/02/18/kubernetes%E4%B9%8Byaml%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="一yaml基础"&gt;一、YAML基础&lt;/h2&gt;
&lt;p&gt;YAML是专门用来写配置文件的语言，非常简洁和强大，使用比json更方便。它实质上是一种通用的数据串行化格式。&lt;/p&gt;
&lt;p&gt;YAML语法规则：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;大小写敏感
使用缩进表示层级关系
缩进时不允许使用Tal键，只允许使用空格
缩进的空格数目不重要，只要相同层级的元素左侧对齐即可
”#” 表示注释，从这个字符一直到行尾，都会被解析器忽略　　
在Kubernetes中，只需要知道两种结构类型即可：
Lists
Maps
123456789
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="11-yaml-maps"&gt;1.1 YAML Maps&lt;/h4&gt;
&lt;p&gt;Map顾名思义指的是字典，即一个Key:Value 的键值对信息。例如：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apiVersion: v1
kind: Pod
　　注：---为可选的分隔符 ，当需要在一个文件中定义多个结构的时候需要使用。上述内容表示有两个键apiVersion和kind，分别对应的值为v1和Pod。
123
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Maps的value既能够对应字符串也能够对应一个Maps。例如：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
name: kube100-site
labels:
app: web
123456
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注：上述的YAML文件中，metadata这个KEY对应的值为一个Maps，而嵌套的labels这个KEY的值又是一个Map。实际使用中可视情况进行多层嵌套。&lt;/p&gt;
&lt;p&gt;YAML处理器根据行缩进来知道内容之间的关联。上述例子中，使用两个空格作为缩进，但空格的数据量并不重要，只是至少要求一个空格并且所有缩进保持一致的空格数 。例如，name和labels是相同缩进级别，因此YAML处理器知道他们属于同一map；它知道app是lables的值因为app的缩进更大。&lt;/p&gt;
&lt;p&gt;注意：在YAML文件中绝对不要使用tab键&lt;/p&gt;
&lt;h4 id="12-yaml-lists"&gt;1.2 YAML Lists&lt;/h4&gt;
&lt;p&gt;List即列表，说白了就是数组，例如：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;args
-beijing
-shanghai
-shenzhen
-guangzhou
12345
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以指定任何数量的项在列表中，每个项的定义以破折号（-）开头，并且与父元素之间存在缩进。在JSON格式中，表示如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{
&amp;#34;args&amp;#34;: [&amp;#34;beijing&amp;#34;, &amp;#34;shanghai&amp;#34;, &amp;#34;shenzhen&amp;#34;, &amp;#34;guangzhou&amp;#34;]
}
123
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然Lists的子项也可以是Maps，Maps的子项也可以是List，例如：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
name: kube100-site
labels:
app: web
spec:
containers:
- name: front-end
image: nginx
ports:
- containerPort: 80
- name: flaskapp-demo
image: jcdemo/flaskapp
ports: 8080
123456789101112131415
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如上述文件所示，定义一个containers的List对象，每个子项都由name、image、ports组成，每个ports都有一个KEY为containerPort的Map组成，转成JSON格式文件：&lt;/p&gt;</description></item><item><title>k8s 常见问题</title><link>/2018/01/27/k8s-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/</link><pubDate>Sat, 27 Jan 2018 00:00:00 +0000</pubDate><guid>/2018/01/27/k8s-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;自己在搭建k8s集群的时候遇到的问题,和解决方法记录下来.&lt;/p&gt;
&lt;h1 id="1etcd服务"&gt;1.etcd服务&lt;/h1&gt;
&lt;p&gt;kubernetes和flannel存储数据等，单台etcd不稳定，最好是搭建集群。搭建集群的时候，集群之间通信如果要添加认证的话，需要签署证书。同时把证书拷贝到制定文件目录，在etcd.service中制定证书文件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat &amp;gt; etcd-csr.json &lt;span style="color:#e6db74"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;CN&amp;#34;: &amp;#34;etcd&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;hosts&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;127.0.0.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;${NODE_IP}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;key&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;algo&amp;#34;: &amp;#34;rsa&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;size&amp;#34;: 2048
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;names&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;C&amp;#34;: &amp;#34;CN&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;ST&amp;#34;: &amp;#34;BeiJing&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;L&amp;#34;: &amp;#34;BeiJing&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;O&amp;#34;: &amp;#34;k8s&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;OU&amp;#34;: &amp;#34;System&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cfssl gencert -ca&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/kubernetes/ssl/ca.pem &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -ca-key&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/kubernetes/ssl/ca-key.pem &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -config&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/kubernetes/ssl/ca-config.json &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -profile&lt;span style="color:#f92672"&gt;=&lt;/span&gt;kubernetes etcd-csr.json | cfssljson -bare etcd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -p /etc/etcd/ssl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp etcd*.pem /etc/etcd/ssl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -p /var/lib/etcd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat &amp;gt; etcd.service &lt;span style="color:#e6db74"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;[Unit]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Description=Etcd Server
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;After=network.target
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;After=network-online.target
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Wants=network-online.target
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Documentation=https://github.com/coreos
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;[Service]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Type=notify
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;WorkingDirectory=/var/lib/etcd/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;ExecStart=/root/local/bin/etcd \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --name=${NODE_NAME} \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --cert-file=/etc/etcd/ssl/etcd.pem \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --key-file=/etc/etcd/ssl/etcd-key.pem \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --peer-cert-file=/etc/etcd/ssl/etcd.pem \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --peer-key-file=/etc/etcd/ssl/etcd-key.pem \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --trusted-ca-file=/etc/kubernetes/ssl/ca.pem \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --peer-trusted-ca-file=/etc/kubernetes/ssl/ca.pem \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --initial-advertise-peer-urls=https://${NODE_IP}:2380 \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --listen-peer-urls=https://${NODE_IP}:2380 \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --listen-client-urls=https://${NODE_IP}:2379,http://127.0.0.1:2379 \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --advertise-client-urls=https://${NODE_IP}:2379 \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --initial-cluster-token=etcd-cluster-0 \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --initial-cluster=${ETCD_NODES} \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --initial-cluster-state=new \\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --data-dir=/var/lib/etcd
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Restart=on-failure
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;RestartSec=5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;LimitNOFILE=65536
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;[Install]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;WantedBy=multi-user.target
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp etcd.service /etc/systemd/system/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="2flannel网络"&gt;2.flannel网络&lt;/h1&gt;
&lt;p&gt;flannel网络也可以添加认证&lt;/p&gt;</description></item><item><title>k8s学习内容汇总</title><link>/2018/01/10/k8s%E5%AD%A6%E4%B9%A0%E5%86%85%E5%AE%B9%E6%B1%87%E6%80%BB/</link><pubDate>Wed, 10 Jan 2018 00:00:00 +0000</pubDate><guid>/2018/01/10/k8s%E5%AD%A6%E4%B9%A0%E5%86%85%E5%AE%B9%E6%B1%87%E6%80%BB/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;根据马哥教育k8s教程学习。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;k8s学习大纲
- 基础大纲
- 集群部署及陈述式命令管理
- 资源类型及配置清单
- pod资源
- pod 控制器
- service资源
- 存储卷
- configmap与secret资源
- statefulset控制器
- 认证、授权及准入控制
- 网络模型及网络策略
- pod资源调度
- crd、自定义资源、自定义控制器及自定义API server, cni=Custom/Container Network Interface, crd=CustomResourceDefinition, CRI=Container Runtime Interface
- 资源指标与HPA控制器
- Helm管理器
- 高可用kubernetes
Cloud Native Apps: 云原生应用，程序开发出来就是运行在云平台上，而非单机上的应用。
Serverless: 与云原生应用组合，成为新的发展趋势，FaaS。 Knative。
单体应用程序: 亦称巨石应用，牵一发而动全身。
分成架构服务: 每个团队维护一个层次。（比如用户，商品，支付）
微服务(Microservice): 服务拆分成多个小功能，单独运行。
- 服务注册和服务发现: 分布式和微服务存在的问题
- 三五个程序运行支撑的服务转变为 三五十个微型服务，服务之间的调用成为网状结构。
- 非静态配置，动态服务发现，服务总线
- 服务编排系统: 解决运维部署难的问题
- 容器编排系统: 解决系统异构，服务依赖环境各不相同。 服务编排系统 --&amp;gt; 容器编排系统
容器编排系统: (主流: k8s, Borg, Docker Swarm, Apache Mesos Marathon DC/OS )
what is container orchestration?
- container orchestration is all about managing the lifecycles of containers, especially in large, dynamic environments.
- software teams use container orchestration to control and automade many task:
- provisioning and deployment of containers
- redundancy and availability of containers
- scaling up or removing containers to spread application load evently across host infrastructure
- movement of containers from one host to another if there is a shortage of resources in host, or if a host dies.
- allocation of resources between containers
- exteral exposure of services running in a container with the outside world
- load balancing of service discovery between containers
- heath monitoring of containers and hosts
- configuration of an application in relation to the containers runing it
简单来说，容器编排是指容器应用的自动布局、协同及管理，它主要负责完成以下具体内容：
service discovery
load balancing
secrets/configuration/storage management
heath checks
auto-[scaling/restart/healing] of containers and nodes
zero-downtime deploys
- pod资源
- pod 控制器:
pod创建方式:
- 自助式pod: 直接创建pod
- 由控制器管理pod: 创建deployment,service等
ReplicationControler:
ReplicaSet/rs:
- 副本数
- 标签选择
- pod资源摸版
- kubectl explain rs
- kubectl explain rs.spec
- replicas
- selector
- template
Deployment: 无状态任务,关注群体行为. 只关注数量,不关注个体.
- kubectl explain deploy.spec
- replicas
- selector
- template
- strategy: 更新策略
- rollingUpdate
- maxSurge: 最多超出目标pod数量 1/20%
- maxUnavailable: 最多不可用数量. 1/80%
- type
- Recreate
- rollingUpdate
- minReadySeconds
- revisionHistoryLimit: 保存历史版本数量限制
DaemonSet/ds: 每个node节点只运行一个,eg:ELK.
- kubectl explain ds.spec
- selector
- template
- minReadySeconds
- revisionHistoryLimit: 保存历史版本数量限制
- updateStrategy
Service:
工作模式: userspace, iptables, ipvs. (kube-proxy)
userspace: 1.1-
iptables: 1.10-
ipvs: 1.11+
- NodePort/ClusterIP : client --&amp;gt; NodeIP:NodePort --&amp;gt; ClusterIP:ServicePort --&amp;gt; PodIP:containerPort
- LBAAS(LoadBalancerAsAService): 公有云环境中.
- LoadBalancer
- ExternalName
- FQDN
- CNAME -&amp;gt; FQDN
- No ClusterIP: Headless Service
- ServiceName -&amp;gt; PodIP
- kubectl explain svc
- type: ExternalName, ClusterIP, NodePort, LoadBalancer
- port:
- NodePort # type=NodePort 时 可用. client --&amp;gt; NodeIP:NodePort --&amp;gt; ClusterIP:ServicePort --&amp;gt; PodIP:containerPort
- LBAAS(LoadBalancerAsAService): 公有云环境中.
- LoadBalancer
- ExternalName
- FQDN
- CNAME -&amp;gt; FQDN
-
- port
- targetPort
Ingress:
Service: ingress-nginx (NodePort, DaemonSet - HostNetwork)
IngressController: ingress-nginx
Ingress:
- site1.ikubernetes.io (virtual host)
- site2.ikubernetes.io (virtual host)
- example.com/path1
- example.com/path2
- Service: site1
- pod1
- pod2
- Service: site2
- pod3
- pod4
- 存储卷
- emptyDir
- 临时目录, 内存使用, 没有持久性
- gitRepo
- hostPath
- 共享存储
- SAN: iSCSI
- NAS: nfs, cifs, http
- 分布式存储:
- glusterfs
- ceph: rbd
- cephfs:
- 云存储
- EBS
- Azure Disk
- pvc: persistentVolumeClaim
- pv
- pvc
- pod - volumes -
- secret
base64 的 configmap
- configmap: 配置中心
配置容器化应用的方式:
1. 自定义命令行参数;
args: []
2.把配置文件直接copy进镜像
3.环境变量
1.Cloud Native的应用程序一般可直接通过环境变量加载配置
2.通过entrypoint.sh脚本来预处理变量为配置文件中的配置信息
4.存储卷
- 变量注入 --&amp;gt; pod 读取变量
- 挂载存储卷 --&amp;gt; pod 读取配置
- 自定义命令行参数
- StatefulSet:
PetSet -&amp;gt; StatefulSet
应对有以下要求的服务:
1.稳定且唯一的网络标识符
2.稳定且持久的存储
3.有序平滑的部署和扩展
4.有序平滑的删除和终止
5.有序的滚动更新
三个组件:
- headless service: clusterIP: None
- StatefulSet
- volumeClaimTemplate
- 认证、授权及准入控制
- Authentication
- restful API: token
- tls: 双向认证
- user/password
- Authorization
- rbac
- role
- rolebinding
- ClusterRole
- ClusterRoleBinding
- webhook
- abac
- Admission Control
client --&amp;gt; API Server
pod --&amp;gt; api server
- ServiceAccount?
- secret
- ServiceAccountName
kubectl get secret
kubectl get sa | serviceaccount
kubectl create serviceaccount my-sa -o yaml --dry-run
kubectl get pods myapp -o yaml --export
API request path: http://IP:port/apis/apps/v1/namespaces/default/deployments/myapp-deploy/
http request ver:
get, post, put, delete
http request --&amp;gt; API requests verb:
get, list, create, update, patch, watch, proxy, redirect, delete, delectcollection
Resource:
Subresource:
Namespace:
API group:
- ServiceAccount:
alex.crt
(umask 077; openssl genrsa -out alex.key 2048)
openssl req -new -key alex.key -out alex.csr -subj &amp;#34;/CN=alex&amp;#34;
openssl x509 -req -in alex.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out alex.crt -days 3650
openssl x509 -in alex.crt -text -noout
kubectl config set-cluster alex-cluster --server=&amp;#34;https://192.168.137.131:6443&amp;#34; --certificate-authority=/etc/kubernetes/pki/ca.crt --embed-certs=true --kubeconfig=/tmp/alex.conf
kubectl config set-credentials admin \
--client-certificate=datasets/work/admin/admin.pem \
--client-key=datasets/work/admin/admin-key.pem \
--embed-certs=true \
--kubeconfig=/tmp/alex.conf
kubectl config set-context alex@kubernetes --cluster=kubernetes --user=admin --kubeconfig=/tmp/alex.conf
kubectl config use-context kubernetes --kubeconfig=/tmp/alex.conf
kubectl get pods --kubeconfig=/tmp/alex.conf (error from server forbidden)
- RBAC
- role, clusterrole
object:
resource group
resource
non-resource url
action:
get, list, watch, patch, delete, deletecollection, ...
- rolebinding, clusterrolebinding
subject:
user
group
serviceaccount
role:
- role:
- operations
- objects
- rolebinding:
- user account or service account
- role
- clusterrole
- clusterrolebinding
# kubectl create role --help
# kubectl create rolebinding --help
# kubectl create role pods-reader --verb=get,list,watch --resource=pods --dry-run=client -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
creationTimestamp: null
name: pods-reader
namespace: default
rules:
- apiGroups:
- &amp;#34;&amp;#34;
resources:
- pods
verbs:
- get
- list
- watch
# kubectl create rolebinding alex-read-pods --role=pods-reader --user=alex --dry-run=client -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: null
name: alex-read-pods
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pods-reader
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alex
# 接着上面 ServiceAccount 的例子
# kubectl config use-context kubernetes --kubeconfig=/tmp/alex.conf
# kubectl get pods --kubeconfig=/tmp/alex.conf
# kubectl get pods -n kube-system --kubeconfig=/tmp/alex.conf (error from server forbidden)
# kubectl create clusterrole --help
# kubectl create clusterrolebinding --help
- Kubernetes Dashboard
- helm install kubernetes-dashboard/kubernetes-dashboard --version 2.3.0 --name=k8s-dashboard [--namespaces=dashboard]
- helm fetch kubernetes-dashboard/kubernetes-dashboard --version 2.3.0
- tar xf kubernetes-dashboard-2.3.0.tgz
- vim kubernetes-dashboard/values.yaml
- helm install kubernetes-dashboard/kubernetes-dashboard --version 2.3.0 --name=k8s-dashboard -f kubernetes-dashboard/values.yaml [--namespaces=dashboard]
- kubectl create clusterrolebinding k8s-dashboard-admin --clusterrole=cluster-admin --serviceaccount=default:k8s-dashboard / [--serviceaccount=dashboard:k8s-dashboard]
- kubectl describe sa k8s-dashboard [-n dashboard]
- kubectl describe secret k8s-dashboard-token-xxxx [-n dashboard]
- 网络模型及网络策略
flannel
kubectl get daemonset -n kube-system
kubectl get pods -o wide -n kube-system |grep -i kube-flannel
kubectl get configmap -n kube-system
kubectl get configmap kube-flannel-cfg -o json -n kube-system
from 10.244.1.59 ping 10.244.2.76
tcpdump -i cni0 -nn icmp
tcpdump -i flannel.1 -nn
tcpdump -i ens32 -nn host 192.168.137.131
overlay otv
calico
- pod资源调度
调度器:
预选策略:
优先函数:
节点选择器: nodeSelector, nodeName
节点亲和调度: nodeAffinity
taint的effect定义对Pod排斥效果:
NoSchedule:仅影响调度过程,对现存的Pod对象不产生影响;
NoExecute:既影响调度过程,也影响现在的Pod对象;不容忍的Pod对象将被驱逐;
PreferNoSchedule:
- crd、自定义资源、自定义控制器及自定义API server
- HeapSter (数据采集)
- cAdvisor (数据指标检测)
- InfluxDB (历史数据记录: 时序数据库系统)
- Grafana (数据展示)
- RBAC
资源指标:
metrics-server: k8s资源聚合器
自定义指标:
- prometheus
- k8s-prometheus-adapter
- MertricServer
- PrometheusOperator
- NodeExporter
- kubeStateMetrics
- Prometheus
- Grafana
新一代架构:
核心指标流水线:
由kubelet、metrics-server以及由API server提供的api组成;CPU累积使用率、内存实时使用率、Pod的资源占用率及容器的磁盘占用率;
监控流水线:
用于从系统收集各种指标数据并提供终端用户、存储系统以及HPA,它们包含核心指标及许多非核心指标。非核心指标本身不能被k8s所解析,
metrics-server:
API server
- Helm管理器: chart repository
Tiller:
chart:
- 配置清单
- 模板文件
-
helm install mem1 stable/memcached
# kubectl explain ingress.spec
FIELDS:
backend &amp;lt;Object&amp;gt;
resource &amp;lt;Object&amp;gt;
serviceName &amp;lt;string&amp;gt;
servicePort &amp;lt;string&amp;gt;
ingressClassName &amp;lt;string&amp;gt;
rules &amp;lt;[]Object&amp;gt;
host &amp;lt;string&amp;gt;
http &amp;lt;Object&amp;gt;
paths &amp;lt;[]Object&amp;gt; -required-
backend &amp;lt;Object&amp;gt; -required-
path &amp;lt;string&amp;gt;
pathType &amp;lt;string&amp;gt;
tls &amp;lt;[]Object&amp;gt;
Ingress Controller:
- Nginx
- Traefik
- Envoy
namespace: ingress-nginx
Job: 一次性任务
Cronjob: 周期性任务
StatefulSet: 关注个体. 对
EDR: Custom Defined Resources, 1.8+
Operator: etcd
example:
- # ReplicaSet
- vim rs-demo.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myapp
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp
release: canary
template:
metadata:
name: myapp-pod
labels:
app: myapp
release: canary
environments: qa
sepe:
containers:
- name: myapp-container
image: ikubernetes/myapp:v1
ports:
- name: http
containerPort: 80
- kubectl create -f rs-demo.yaml
- kubectl edit rs myapp --&amp;gt; replicas: 5
- kubectl get pods
- kubectl edit rs myapp --&amp;gt; image: ikubernetes/myapp:v2
- kubectl get rs -o wide
- curl xx.xx.xxx.xx # 只有重建的pod会使用新的image
- # Deployment
- vim deploy-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp
release: canary
template:
metadata:
name: myapp-pod
labels:
app: myapp
release: canary
environments: qa
sepe:
containers:
- name: myapp-container
image: ikubernetes/myapp:v1
ports:
- name: http
containerPort: 80
- kubectl apply -f deploy-demo.yaml
- kubectl get deploy
- kubectl get rs -o wide
myapp-deploy-69b47bc96d --&amp;gt; 模板的hash值
- kubectl get pods
- kubectl get pods -l app=myapp -w # 新开窗口
- vim deploy-demo.yaml --&amp;gt; image: ikubernetes/myapp:v2
- kubectl apply -f deploy-demo.yaml
- kubectl get rs -o wide
- kubectl rollout history deployment myapp-deploy
# 打补丁方式增加pod
- kubectl patch deployment myapp-deploy -p &amp;#39;{&amp;#34;spec&amp;#34;:{&amp;#34;replicas&amp;#34;:5}}&amp;#39;
- kubectl get pods
- kubectl patch deployment myapp-deploy -p &amp;#39;{&amp;#34;spec&amp;#34;:{&amp;#34;strategy&amp;#34;:{&amp;#34;rollingUpdate&amp;#34;:{&amp;#34;maxSurge&amp;#34;:1,&amp;#34;maxUnavailable&amp;#34;:0}}}}&amp;#39;
- kubectl describe deployment myapp-deploy
- kubectl get pods -l app=myapp -w # 新开窗口-1
- kubectl set image deployment myapp-deploy myapp=ikubernetes/myapp:v3 &amp;amp;&amp;amp; kubectl rollout pause deployment myapp-deploy # 暂停rollout, 金丝雀发布
- kubectl rollout status deployment myapp-deploy # 新开窗口-2
- kubectl rollout resume deployment myapp-deploy --&amp;gt; # 查看新开窗口 1, 2
- kubectl rollout history deployment myapp-deploy # 查看历史版本
- kubectl get rs -o wide
# 回滚版本
- kubectl rollout undo --help
- kubectl rollout undo deployment myapp-deploy --to-revision=1
- kubectl rollout history deployment myapp-deploy # 查看历史版本
- # DaemonSet
- vim ds-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespaces: default
spec:
replicas: 1
selector:
matchLabels:
app: redis
role: logstor
template:
metadata:
labels:
app: redis
role: logstor
spec:
containers:
- name: redis
image: redis:4.0-alpine
ports:
- name: redis
containerPort: 6379
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat-ds
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: filebeat
release: stable
template:
metadata:
name: filebeat-pod
labels:
app: filebeat
release: stable
sepe:
containers:
- name: filebeat
image: ikubernetes/filebeat:5.6.5-alphine
env:
- name: REDIS_HOST
value: redis.default.svc.cluster.local
- name: REDIS_LOG_LEVEL
value: info
- kubectl apply -f ds-demo.yaml
- kubectl get pods
- kubectl expose deployment redis --port=6379
- kubect get svc
- kubectl exec -it redist-5bxxxx-xxx -- /bin/sh
# netstat -tnl
# nslookup redis.default.svc.cluster.local
# redis-cli -h redis.default.svc.cluster.local
# kyes *
# DaemonSet 滚动更新
- kubectl set image demonsets filebeat-ds filebeat=ikubernets/filebeat-5.6.6-alphine
- kubectl get pods -w # 一个一个的更新.
- # Service
- vim redis-svc.yaml
apiVersion: v1
kind: Service
medatada:
name: redis
namespace: default
spec:
selector:
app: redis
role: logstor
# clusterIP: 10.96.96.96
type: ClusterIP
port:
- port: 6349
targetPort: 6379
- kubectl apply -f redis-svc.yaml
- kubectl get svc
- kubectl describe svc redis
-
- curl xx.xx.xxx.xx # 只有重建的pod会使用新的image
- service资源
Helm: like Linux yum/apt/apk ...
k8s Server
Master
API Server
- port: 6443
- auth: 双向认证, /etc/kubernetes/pki
- config: ~/.kube/config
- kubectl config view
- restful api
- kubectl api-versions
- json
- kubectl get pod == curl https://master:6443/v1/...
- communicate with all the service
- kubectl
- kube-controller-manager
- kube-scheduler
- etcd
- kubelet
- kube-proxy
- api 接口中的资源分成多个逻辑组合 apiVersion
- 和解循环（Reconciliation Loop）: status --&amp;gt; spec
Scheduler
Controller
- 和解循环（Reconciliation Loop）: status --&amp;gt; spec
Node:
- pod
- service -&amp;gt; iptables/ipvs -&amp;gt; kube-proxy
- kube-proxy
Resource
资源有两个级别：
- 集群级别
- Node
- Namespace
- Role
- ClusterRole
- RoleBinding
- ClusterRoleBinding
- PersistentVolume
- 名称空间级别
- pod
- service
- deploy
- 元数据型资源
- HPA
- PodTemplate
- LimitRange
资源组成部分：
- apiVersion: group/version (kubectl api-versions)
- kind: 资源类别
- metadata
- spec: 资源期望状态
- labels/tag: kubectl label
- annotations: kubectl annotate
- initContainers: kubectl explain pods.spec.initContainers: 容器初始化, 运行前, 运行后, 运行时 (存活检测, 就绪检测)
- lifecycle
- livenessProbe
- readinessProbe
- startupProbe
-
- Containers: kubectl explain pods.spec.Containers: 容器初始化, 运行前, 运行后, 运行时 (存活检测, 就绪检测)
- lifecycle: preStart hook, preStop hook
- livenessProbe
- readinessProbe
- startupProbe
- name:
- command: [&amp;#34;/bin/bash&amp;#34; &amp;#34;-c&amp;#34; &amp;#34;sleep 3600&amp;#34;]
- args:
- image:
- imagePullPolicy:
- Never
- Always
- IfNotPresent
- port:
- name:
- hostIP:
- hostPort:
- protocol
- containerPort
- status: 资源当前状态
资源引用Object URL:
/apis/&amp;lt;GROUP&amp;gt;/&amp;lt;VERSION&amp;gt;/namespaces/&amp;lt;NAMESPACE_NAME&amp;gt;/&amp;lt;KIND&amp;gt;[/OBJECT_ID]/
- /api/GROUP/VERSION/namespaces/NAMESPACE/TYPE/NAME
- kubectl get pod/nginx-ds-s4hpn
- selfLink: /api/v1/namespaces/default/pods/nginx-ds-s4hpn
资源记录:
SVC_NAME.NS_NAME.DOMAIN.LTD.
redis.default.svc.cluster.local
- Pod
- Pod Controller
- Deployment: 类型 --&amp;gt; ngx-deploy --&amp;gt; nginx pod
- Service
- nginx-svc --&amp;gt; 关联到 nginx pod
kubeadm
kubectl
- kubectl explain pods.spec.initContainers
- kubectl -h
- basic commands beginner
- create
- expose
- run
- set
- basic commands intermediate
- explan
- get
- edit
- delete
- deploy commands
- rollout
- scale
- autoscale
- cluster management commands
- certificate
- cluster-info
- top
- cordon
- uncordon
- drain
- taint
- troubleshooting and debugging commands
- describe
- logs
- attach
- exec: kubectl exec -it PodName -c ContainerName -- /bin/sh
- port-forward
- kubectl config view [-o wide/json/yaml]
# kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://192.168.137.131:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
- kubectl api-resources: 支持的资源类型 以及缩写
- kubectl get [-o wide/json/yaml] [-n default/kube-system/...] [-L label-name] [-l label-name ==/!= label-value]
- all
- nodes
- pods
- ns/namespace
- default
- kube-public
- kube-system
- deploy
- svc/service
- kubectl create [ -f file]
- job
- namespace
- kubectl create namespace testing
- kubectl create namespace prod
- kubectl create namespace develop
- kubectl get ns
- kubectl delete namespace testing
- kubectl delete ns/prod ns/develop
- deployment
- kubectl create deployment nginx-deploy --image=nginx:1.14-alpine
- kubectl get all -o wide
- pod/nginx-deploy-xxxx (ip: 10.244.1.2)
- deployment.apps/nginx-deploy
- replicaset.apps/nginx-deploy-xxxx
- curl 10.244.1.2 ( welcome nginx!)
- kubectl delete pod/nginx-deploy-xxxx
- kubectl get all -o wide
- pod/nginx-deploy-xxxx (ip: 10.244.3.2)
...
- curl 10.244.3.2 ( welcome nginx!)
- service
- kubectl create service -h
- ClusterIP
- NodePort
- kubectl create service clusterip nginx-deploy --tcp=80:80 (名字和上面deploy保持一致，就会自动分配IP)
- kubectl get svc/nginx-deploy -o yaml
- clusterip： 10.110.129.64
- endpoints: 10.244.3.2
- kubectl describe svc/nginx-deploy
# 删除 pod 测试
- curl 10.110.129.64 ( welcome nginx!)
- kubectl delete pod/nginx-deploy-xxxx
- kubectl get all -o wide
- pod/nginx-deploy-xxxx (ip: 10.244.1.3)
...
- kubectl get svc/nginx-deploy -o yaml
- clusterip： 10.110.129.64
- endpoints: 10.244.1.3 (自动关联到最新的pod)
# 删除 service 测试
- curl nginx-deploy.default.svc.cluster.local. ( welcome nginx!)
- kubectl delete svc/nginx-deploy
- kubectl create service clusterip nginx-deploy --tcp=80:80 (名字和上面deploy保持一致，就会自动分配IP)
- kubectl describe svc/nginx-deploy -o yaml
- clusterip： 10.111.215.249
- endpoints: 10.244.3.2
- curl nginx-deploy.default.svc.cluster.local. ( welcome nginx!)
# 按需伸缩 pod 测试
- kubectl create deploy myapp --image=ikubernetes/myapp:v1
- kubectl get deploy
- kubectl get pods -o wide
- ip : 10.244.3.3
- curl 10.244.3.3
- curl 10.244.3.3/hostname.html (show the pod name: myapp-xxxx-yyyy)
- kubectl create service clusterip myapp --tcp=80:80
- kubectl describe svc/myapp
- IP 10.100.182.218
- Endpoints: 10.244.3.3
- curl nginx-deploy.default.svc.cluster.local. ( welcome myapp!)
- curl nginx-deploy.default.svc.cluster.local/hostname.html (show the pod name: myapp-xxxx-yyyy)
- kubectl scale --replicas=3 myapp (deploy name)
- kubectl describe svc/myapp
- IP 10.100.182.218
- Endpoints: 10.244.3.3, 10.244.1.4, 10.244.2.2
- curl nginx-deploy.default.svc.cluster.local/hostname.html (随机显示不同IP的pod name，多次重复执行查看效果)
- kubectl scale --replicas=2 myapp
- kubectl describe svc/myapp
- IP 10.100.182.218
- Endpoints: 10.244.3.3, 10.244.1.4
# nodeport 外网访问
- kubectl delete svc/myapp
- kubectl create service nodeport -h
- kubectl create service nodeport myapp --tcp=80:80
- kubectl get svc
- ports: 80:31996/TCP
- 集群外部访问所有nodes的 http://nodesip:31996/hostname.html
- 自动创建规则在每一个节点的iptables --&amp;gt; kube-proxy
- ssh nodes &amp;#34;iptables -t nat -vnL&amp;#34;
eg:
- kubectl expose
- kubectl set image deployment myapp myapp=ikubernetes/myapp:v2
- kubectl label [--overwrite] (-f FILENAME | TYPE NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--resource-version=version]
kubectl label pods -n dev-namespaces apptag=my-app release=stable deltag-
- kubectl api-versions
- kubectl describe
Network
- node network
- service network: service 注册/发现
- pod network
外网访问:
- Service: NodePort
- hostport:
- hostNetwork:
ipvs/iptables 4 层调度器 ingress 7 层调度器
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>k8s 安装</title><link>/2017/12/24/k8s-%E5%AE%89%E8%A3%85/</link><pubDate>Sun, 24 Dec 2017 00:00:00 +0000</pubDate><guid>/2017/12/24/k8s-%E5%AE%89%E8%A3%85/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="一在master节点上的操作"&gt;一、在master节点上的操作&lt;/h1&gt;
&lt;p&gt;众多需要下载和安装的包我都已经下载好了放在集群上，所以后面的安装等就是直接从集群backup目录拷贝过来直接用。&lt;/p&gt;
&lt;p&gt;master节点配置的服务有，etcd，flannel，docker，kubectl，kube-apiserver，kube-controller-manager，kube-scheduler，kubelete，kube-proxy，kubedns（插件），kube-dashboard（插件）&lt;/p&gt;
&lt;h2 id="1首先设置环境变量"&gt;1.首先设置环境变量&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat /atlas/backup/kubernetes-1.7.6/environment.sh
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/usr/bin/bash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;BOOTSTRAP_TOKEN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;41f7e4ba8b7be874fcff18bf5cf41a7c&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SERVICE_CIDR&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;10.254.0.0/16&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CLUSTER_CIDR&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;172.30.0.0/16&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export NODE_PORT_RANGE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;8400-9000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCD_ENDPOINTS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;http://172.16.10.18:2379,http://172.16.10.10:2379&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export FLANNEL_ETCD_PREFIX&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/flannel/network&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export CLUSTER_KUBERNETES_SVC_IP&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;10.254.0.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export CLUSTER_DNS_SVC_IP&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;10.254.0.2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export CLUSTER_DNS_DOMAIN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;cluster.local.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="2下载cfssl"&gt;2.下载cfssl&lt;/h2&gt;
&lt;p&gt;后面使用cfssl来生成certificate authority&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd /atlas/backup/kubernetes-1.7.6/cfssl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp cfssl_linux-amd64 /usr/local/bin/cfssl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp cfssljson_linux-amd64 /usr/local/bin/cfssljson
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp cfssl-certinfo_linux-amd64 /usr/local/bin/cfssl-certinfo
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="3创建ca配置文件ca-configjson和签名请求ca-csrjson"&gt;3.创建CA配置文件ca-config.json和签名请求ca-csr.json&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir /atlas/backup/kubernetes-1.7.6/ssl
cd /atlas/backup/kubernetes-1.7.6/ssl/
# vim /atlas/backup/kubernetes-1.7.6/ssl/ca-config.json
cat &amp;gt; ca-config.json &amp;lt;&amp;lt;EOF
{
&amp;#34;signing&amp;#34;: {
&amp;#34;default&amp;#34;: {
&amp;#34;expiry&amp;#34;: &amp;#34;8760h&amp;#34;
},
&amp;#34;profiles&amp;#34;: {
&amp;#34;kubernetes&amp;#34;: {
&amp;#34;usages&amp;#34;: [
&amp;#34;signing&amp;#34;,
&amp;#34;key encipherment&amp;#34;,
&amp;#34;server auth&amp;#34;,
&amp;#34;client auth&amp;#34;
],
&amp;#34;expiry&amp;#34;: &amp;#34;8760h&amp;#34;
}
}
}
}
EOF
# vim /atlas/backup/kubernetes-1.7.6/ssl/ca-csr.json
cat &amp;gt; ca-csr.json &amp;lt;&amp;lt;EOF
{
&amp;#34;CN&amp;#34;: &amp;#34;kubernetes&amp;#34;,
&amp;#34;key&amp;#34;: {
&amp;#34;algo&amp;#34;: &amp;#34;rsa&amp;#34;,
&amp;#34;size&amp;#34;: 2048
},
&amp;#34;names&amp;#34;: [
{
&amp;#34;C&amp;#34;: &amp;#34;CN&amp;#34;,
&amp;#34;ST&amp;#34;: &amp;#34;BeiJing&amp;#34;,
&amp;#34;L&amp;#34;: &amp;#34;BeiJing&amp;#34;,
&amp;#34;O&amp;#34;: &amp;#34;k8s&amp;#34;,
&amp;#34;OU&amp;#34;: &amp;#34;System&amp;#34;
}
]
}
EOF
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="4生成证书和私钥"&gt;4.生成证书和私钥&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cfssl gencert -initca ca-csr.json | cfssljson -bare ca
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls ca*
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ca-config.json ca.csr ca-csr.json ca-key.pem ca.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir -pv /etc/kubernetes/ssl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cp /atlas/backup/kubernetes-1.7.6/ssl/ca* /etc/kubernetes/ssl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="5加载环境变量"&gt;5.加载环境变量&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export NODE_NAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;etcd-host0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export NODE_IP&lt;span style="color:#f92672"&gt;=&lt;/span&gt;172.16.10.10
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export NODE_IPS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;172.16.10.10 172.16.10.18&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCD_NODES&lt;span style="color:#f92672"&gt;=&lt;/span&gt;etcd-host0&lt;span style="color:#f92672"&gt;=&lt;/span&gt;http://172.16.10.10:2380,etcd-host1&lt;span style="color:#f92672"&gt;=&lt;/span&gt;http://172.16.10.18:2380
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;source /atlas/backup/kubernetes-1.7.6/environment.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="6配置etcd服务"&gt;6.配置etcd服务&lt;/h2&gt;
&lt;p&gt;kuberntes 系统使用 etcd 存储所有数据，同时flannel网络服务也会把数据保存到etcd中，etcd服务集群只在login节点和master节点搭建。有些教程在etcd集群和flannel集群中加入了ca认证过程，这里为了简单就没有加密。&lt;/p&gt;</description></item><item><title>01-Kubernetes 基础概念和架构解析</title><link>/2017/12/17/01-kubernetes-%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5%E5%92%8C%E6%9E%B6%E6%9E%84%E8%A7%A3%E6%9E%90/</link><pubDate>Sun, 17 Dec 2017 00:00:00 +0000</pubDate><guid>/2017/12/17/01-kubernetes-%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5%E5%92%8C%E6%9E%B6%E6%9E%84%E8%A7%A3%E6%9E%90/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1服务部署的发展"&gt;1.服务部署的发展&lt;/h1&gt;
&lt;p&gt;为什么我们需要Kubernetes? 我们先来看看服务部署的发展. 如下图:&lt;/p&gt;
&lt;p&gt;&lt;img alt="container_evolution" loading="lazy" src="/2017/12/17/01-kubernetes-%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5%E5%92%8C%E6%9E%B6%E6%9E%84%E8%A7%A3%E6%9E%90/container_evolution.svg"&gt;&lt;/p&gt;
&lt;h2 id="11-传统部署时代"&gt;1.1 &lt;strong&gt;传统部署时代&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;早期，组织在物理服务器上运行应用程序。无法为物理服务器中的应用程序定义资源边界，这会导致资源分配问题。例如，如果在物理服务器上运行多个应用程序，则可能会出现一个应用程序占用大部分资源的情况，结果可能导致其他应用程序的性能下降。一种解决方案是在不同的物理服务器上运行每个应用程序，但是由于资源利用不足而无法扩展，并且组织维护许多物理服务器的成本很高。&lt;/p&gt;
&lt;h2 id="12-虚拟化部署时代"&gt;1.2 &lt;strong&gt;虚拟化部署时代&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;作为解决方案，引入了虚拟化功能，它允许您在单个物理服务器的 CPU 上运行多个虚拟机（VM）。于是有了xen, kvm, vmware 和 virualbox 等虚拟化应用的出现.&lt;/p&gt;
&lt;p&gt;虚拟化功能允许应用程序在 VM 之间隔离，并提供安全级别，因为一个应用程序的信息不能被另一应用程序自由地访问。&lt;/p&gt;
&lt;p&gt;因为虚拟化可以轻松地添加或更新应用程序、降低硬件成本等等，所以虚拟化可以更好地利用物理服务器中的资源，并可以实现更好的可伸缩性。&lt;/p&gt;
&lt;p&gt;每个 VM 是一台完整的计算机，在虚拟化硬件之上运行所有组件，包括其自己的操作系统。&lt;/p&gt;
&lt;h2 id="13-容器部署时代"&gt;1.3 &lt;strong&gt;容器部署时代&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;容器类似于 VM，但是它们具有轻量级的隔离属性，可以在应用程序之间共享操作系统（OS）。因此，容器被认为是轻量级的。容器与 VM 类似，具有自己的文件系统、CPU、内存、进程空间等。由于它们与基础架构分离，因此可以跨云和 OS 分发进行移植。&lt;/p&gt;
&lt;p&gt;容器因具有许多优势而变得流行起来。于是docker闪亮登场, 下面列出了容器的一些好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;敏捷应用程序的创建和部署：与使用 VM 镜像相比，提高了容器镜像创建的简便性和效率。&lt;/li&gt;
&lt;li&gt;持续开发、集成和部署：通过快速简单的回滚(由于镜像不可变性)，提供可靠且频繁的容器镜像构建和部署。&lt;/li&gt;
&lt;li&gt;关注开发与运维的分离：在构建/发布时而不是在部署时创建应用程序容器镜像，从而将应用程序与基础架构分离。&lt;/li&gt;
&lt;li&gt;可观察性不仅可以显示操作系统级别的信息和指标，还可以显示应用程序的运行状况和其他指标信号。&lt;/li&gt;
&lt;li&gt;跨开发、测试和生产的环境一致性：在便携式计算机上与在云中相同地运行。&lt;/li&gt;
&lt;li&gt;云和操作系统分发的可移植性：可在 Ubuntu、RHEL、CoreOS、本地、Google Engine 和其他任何地方运行。&lt;/li&gt;
&lt;li&gt;以应用程序为中心的管理：提高抽象级别，从在虚拟硬件上运行 OS 到使用逻辑资源在 OS 上运行应用程序。&lt;/li&gt;
&lt;li&gt;松散耦合、分布式、弹性、解放的微服务：应用程序被分解成较小的独立部分，并且可以动态部署和管理 - 而不是在一台大型单机上整体运行。&lt;/li&gt;
&lt;li&gt;资源隔离：可预测的应用程序性能。&lt;/li&gt;
&lt;li&gt;资源利用：高效率和高密度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;容器是打包和运行应用程序的好方式。在生产环境中，您需要管理运行应用程序的容器，并确保不会停机。例如，如果一个容器发生故障，则需要启动另一个容器。如果系统处理此行为，会不会更容易？&lt;/p&gt;
&lt;p&gt;这就是 Kubernetes 的救援方法！&lt;/p&gt;
&lt;h1 id="2-kubernetes简介"&gt;2 Kubernetes简介&lt;/h1&gt;
&lt;p&gt;Kubernetes 是一个可移植的，可扩展的开源平台，用于管理容器化的工作负载和服务，方便了声明式配置和自动化。它拥有一个庞大且快速增长的生态系统。Kubernetes 的服务，支持和工具广泛可用。&lt;/p&gt;
&lt;h2 id="21-kubernetes-功能"&gt;2.1 Kubernetes 功能&lt;/h2&gt;
&lt;p&gt;Kubernetes 提供的功能：&lt;/p&gt;</description></item><item><title>Kubernetes设计架构</title><link>/2017/11/27/kubernetes%E8%AE%BE%E8%AE%A1%E6%9E%B6%E6%9E%84/</link><pubDate>Mon, 27 Nov 2017 00:00:00 +0000</pubDate><guid>/2017/11/27/kubernetes%E8%AE%BE%E8%AE%A1%E6%9E%B6%E6%9E%84/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="kubernetes设计架构"&gt;Kubernetes设计架构&lt;/h1&gt;
&lt;p&gt;Kubernetes集群包含有节点代理kubelet和Master组件(APIs, scheduler, etc)，一切都基于分布式的存储系统。下面这张图是Kubernetes的架构图。&lt;/p&gt;
&lt;p&gt;&lt;img alt="k8s-architecture" loading="lazy" src="/2017/11/27/kubernetes%E8%AE%BE%E8%AE%A1%E6%9E%B6%E6%9E%84/k8s-architecture.png"&gt;&lt;/p&gt;
&lt;p&gt;Kubernetes主要由以下几个核心组件组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;etcd保存了整个集群的状态；&lt;/li&gt;
&lt;li&gt;apiserver提供了资源操作的唯一入口，并提供认证、授权、访问控制、API注册和发现等机制；&lt;/li&gt;
&lt;li&gt;controller manager负责维护集群的状态，比如故障检测、自动扩展、滚动更新等；&lt;/li&gt;
&lt;li&gt;scheduler负责资源的调度，按照预定的调度策略将Pod调度到相应的机器上；&lt;/li&gt;
&lt;li&gt;kubelet负责维护容器的生命周期，同时也负责Volume（CVI）和网络（CNI）的管理；&lt;/li&gt;
&lt;li&gt;Container runtime负责镜像管理以及Pod和容器的真正运行（CRI）；&lt;/li&gt;
&lt;li&gt;kube-proxy负责为Service提供cluster内部的服务发现和负载均衡；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了核心组件，还有一些推荐的Add-ons：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;kube-dns负责为整个集群提供DNS服务&lt;/li&gt;
&lt;li&gt;Ingress Controller为服务提供外网入口&lt;/li&gt;
&lt;li&gt;Heapster提供资源监控&lt;/li&gt;
&lt;li&gt;Dashboard提供GUI&lt;/li&gt;
&lt;li&gt;Federation提供跨可用区的集群&lt;/li&gt;
&lt;li&gt;Fluentd-elasticsearch提供集群日志采集、存储与查询&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="kubernetes节点"&gt;Kubernetes节点&lt;/h2&gt;
&lt;p&gt;一个培训机构的kubernetes学习课程，用来参考做学习：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Chapter 1：课程介绍
理解 Kubernetes 设计原则、原理
了解 Kubernetes 的过去、现在和未来
了解并学会使用 Kubernetes 最重要的资源 -- API
学会如何创建和管理应用，并配置应用外部访问
理解 Kubernetes 网络、存储
掌握 Kubernetes 调度的原理和策略
Kubernetes 一些新功能的概念
了解 Kubernetes 的日志、监控方案
具备基本的故障排查的运维能力
Chapter 2：Kubernetes 基本概念
了解什么是 Kubernetes
了解 Kubernetes 的主要特性
理解为什么需要 Kubernetes
了解 Kubernetes 的过去、现在和未来
了解目前 Kubernetes 社区的情况和被采用情况
了解 Kubernetes 的基本架构
获得一些学习资料推荐
Chapter 3：Kubernetes 架构及原理
理解 Kubernetes 设计原则
深入理解 Kubernetes 集群中的组件及功能
了解 Kubernetes 集群对网络的预置要求
深入理解 Kubernetes 的工作原理
深入理解 Kubernetes 中 Pod 的设计思想
Chapter 4：Kubernetes 安装和配置
了解部署 Kubernetes 的多种方式
可以单机部署 Kubernetes（学习演示使用）
可以在宿主机部署一套 Kubernetes 集群（非生产使用）
Chapter 5：Kubernetes API 及集群访问
了解 Kubernetes 的 API
理解 Kubernetes 中 API 资源的结构定义
了解 kubectl 工具的使用
了解 Kubernetes 中 API 之外的其他资源
Chapter 6：ReplicaController，ReplicaSets 和 Deployments
理解 RC
理解 label 和 selector 的作用
理解 RS
理解 Deployments 并且可操作 Deployments
理解 rolling update 和 rollback
Chapter 7：Volume、配置文件及密钥
了解 Kubernetes 存储的管理，支持存储类型
理解 Pod 使用 volume 的多种工作流程以及演化
理解 pv 和 pvc 的原理
理解 storage class 的原理
理解 configmaps 的作用和使用方法
理解 secrets 的作用和使用方法资源结构
Chapter 8：Service 及服务发现
了解 Docker 网络和 Kubernetes 网络
了解 Flannel 和 Calico 网络方案
理解 Pod 在 Kubernetes 网络中的工作原理
理解 Kubernetes 中的 Service
理解 Service 在 Kubernetes 网络中的工作原理
理解 Kubernetes 中的服务发现
掌握 Kubernetes 中外部访问的几种方式
Chapter 9：Ingress 及负载均衡
理解 Ingress 和 Ingress controller 的工作原理
掌握如何创建 Ingress 规则
掌握如何部署 Ingress controller
Chapter 10：DaemonSets，StatefulSets，Jobs，HPA，RBAC
了解 DaemonSet 资源和功能
了解 StatefulSet 资源和功能
了解 Jobs 资源和功能
了解 HPA 资源和功能
了解 RBAC 资源和功能
Chapter 11：Kubernetes 调度
理解 Pod 调度的相关概念
深度理解 Kubernetes 调度策略和算法
深度理解调度时的 Node 亲和性
深度理解调度时的 Pod 亲和性和反亲和性
深度理解污点和容忍对调度的影响
深度理解强制调度 Pod 的方法
Chapter 12：日志、监控、Troubleshooting
理解 Kubernetes 集群的日志方案
理解 Kubernetes 集群的监控方案
了解相关开源项目：Heapster，Fluentd，Prometheus 等
掌握常用的集群，Pod，Service 等故障排查和运维手段
Chapter 13：自定义资源 CRD
理解和掌握 Kubernetes 中如何自定义 API 资源
可以通过 kubectl 管理 API 资源
了解用于自定义资源的 Controller 及相关使用示例
了解 TPR 和 CRD
Chapter 14：Kubernetes Federation
了解 Kubernetes 中 Federation 的作用和原理
了解 Federation 的创建过程
了解 Federation 支持的 API 资源
了解集群间平衡 Pod 副本的方法
Chapter 15：应用编排 Helm，Chart
了解 Kubernetes 中如何进行应用编排
了解 Helm 的作用和工作原理
了解 Tiller 的作用和工作原理
了解 Charts 的作用和工作原理
Chapter 16：Kubernetes 安全
了解 Kubernetes 中 API 访问过程
了解 Kubernetes 中的 Authentication
了解 Kubernetes 中的 Authorization
了解 ABAC 和 RBAC 两种授权方式
了解 Kubernetes 中的 Admission
了解 Pod 和容器的操作权限安全策略
了解 Network Policy 的作用和资源配置方法
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>OpenStack架构简介</title><link>/2017/11/27/openstack%E6%9E%B6%E6%9E%84%E7%AE%80%E4%BB%8B/</link><pubDate>Mon, 27 Nov 2017 00:00:00 +0000</pubDate><guid>/2017/11/27/openstack%E6%9E%B6%E6%9E%84%E7%AE%80%E4%BB%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1云计算"&gt;1.云计算&lt;/h1&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; 云计算是一种按使用量付费的模式，这种模式提供可用的、便捷的、按需的网络访问， 进入可配置的计算资源共享池（资源包括网络，服务器，存储，应用软件，服务），这些资源能够被快速提供，只需投入很少的管理工作，或与服务供应商进行很少的交互。
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="2it系统架构的发展到目前为止大致可以分为3个阶段"&gt;2.IT系统架构的发展到目前为止大致可以分为3个阶段：&lt;/h1&gt;
&lt;h2 id="物理机架构"&gt;物理机架构&lt;/h2&gt;
&lt;p&gt;这一阶段，应用部署和运行在物理机上。
比如企业要上一个ERP系统，如果规模不大，可以找3台物理机，分别部署Web服务器、应用服务器和数据库服务器。
如果规模大一点，各种服务器可以采用集群架构，但每个集群成员也还是直接部署在物理机上。
我见过的客户早期都是这种架构，一套应用一套服务器，通常系统的资源使用率都很低，达到20%的都是好的。&lt;/p&gt;
&lt;p&gt;虚拟化架构
摩尔定律决定了物理服务器的计算能力越来越强，虚拟化技术的发展大大提高了物理服务器的资源使用率。
这个阶段，物理机上运行若干虚拟机，应用系统直接部署到虚拟机上。
虚拟化的好处还体现在减少了需要管理的物理机数量，同时节省了维护成本。&lt;/p&gt;
&lt;h2 id="云计算架构"&gt;云计算架构&lt;/h2&gt;
&lt;p&gt;虚拟化提高了单台物理机的资源使用率，随着虚拟化技术的应用，IT环境中有越来越多的虚拟机，这时新的需求产生了：
如何对IT环境中的虚拟机进行统一和高效的管理。
有需求就有供给，云计算登上了历史舞台。&lt;/p&gt;
&lt;p&gt;云平台是一个面向服务的架构，按照提供服务的不同分为 IaaS、PaaS 和 SaaS。
请看下图&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="/2017/11/27/openstack%E6%9E%B6%E6%9E%84%E7%AE%80%E4%BB%8B/1349539-20180707163310096-320236091.png"&gt;&lt;/p&gt;
&lt;p&gt;IaaS（Infrastructure as a Service）提供的服务是虚拟机。
IaaS 负责管理虚机的生命周期，包括创建、修改、备份、启停、销毁等。
使用者从云平台得到的是一个已经安装好镜像（操作系统+其他预装软件）的虚拟机。
使用者需要关心虚机的类型（OS）和配置（CPU、内存、磁盘），并且自己负责部署上层的中间件和应用。
IaaS 的使用者通常是数据中心的系统管理员。
典型的 IaaS 例子有 AWS、Rackspace、阿里云等&lt;/p&gt;
&lt;p&gt;PaaS（Platform as a Service）提供的服务是应用的运行环境和一系列中间件服务（比如数据库、消息队列等）。
使用者只需专注应用的开发，并将自己的应用和数据部署到PaaS环境中。
PaaS负责保证这些服务的可用性和性能。
PaaS的使用者通常是应用的开发人员。
典型的 PaaS 有 Google App Engine、IBM BlueMix 等&lt;/p&gt;
&lt;p&gt;SaaS（Software as a Service）提供的是应用服务。
使用者只需要登录并使用应用，无需关心应用使用什么技术实现，也不需要关系应用部署在哪里。
SaaS的使用者通常是应用的最终用户。
典型的 SaaS 有 Google Gmail、Salesforce 等&lt;/p&gt;
&lt;h1 id="3openstack"&gt;3.OpenStack&lt;/h1&gt;
&lt;p&gt;OpenStack 对数据中心的计算、存储和网络资源进行统一管理。
由此可见，OpenStack 针对的是 IT 基础设施，是 IaaS 这个层次的云操作系统。&lt;/p&gt;</description></item><item><title>acme.sh配合letsencrypt配置泛域名</title><link>/2017/10/03/acme.sh%E9%85%8D%E5%90%88letsencrypt%E9%85%8D%E7%BD%AE%E6%B3%9B%E5%9F%9F%E5%90%8D/</link><pubDate>Tue, 03 Oct 2017 00:00:00 +0000</pubDate><guid>/2017/10/03/acme.sh%E9%85%8D%E5%90%88letsencrypt%E9%85%8D%E7%BD%AE%E6%B3%9B%E5%9F%9F%E5%90%8D/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。&lt;/p&gt;
&lt;p&gt;MongoDB 是一个介于关系数据库和非关系数据库之间的产品，是非关系数据库当中功能最丰富，最像关系数据库的。&lt;/p&gt;
&lt;h1 id="1-安装-acmesh"&gt;1. 安装 acme.sh&lt;/h1&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl https://get.acme.sh | sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;将会安装到 &lt;strong&gt;~/.acme.sh/&lt;/strong&gt; 目录下，以后所有的配置默认也在这个目录下&lt;/p&gt;
&lt;h1 id="2-生成证书"&gt;2. 生成证书&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;acme.sh&lt;/strong&gt; 实现了 &lt;strong&gt;acme&lt;/strong&gt; 协议支持的所有验证协议. 一般有两种方式验证: http 和 dns 验证，这里仅介绍 DNS 方式&lt;/p&gt;
&lt;p&gt;具体 &lt;a href="https://github.com/Neilpang/acme.sh/tree/master/dnsapi"&gt;dnsapi 链接&lt;/a&gt;), 这里以 阿里云 为例：&lt;/p&gt;
&lt;p&gt;首先获取你的阿里云API Key: &lt;a href="https://usercenter.console.aliyun.com/#/manage/ak"&gt;ak-console.aliyun.com/#/accesskey&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;之后在你的终端配置文件中设置：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;export Ali_Key=&amp;#34;Your AccessKey ID&amp;#34;
export Ali_Secret=&amp;#34;Your Access Key Secret&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;之后直接使用如下命令发起申请(注意这里第一个域名为顶级域名，后面个为泛域名。)：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;acme.sh --issue --dns dns_ali -d example.com -d *.example.com *.second.example.com
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;
curl https://get.acme.sh | sh -s email=xxx@xx.com
exit
# tencent api token address:
# https://console.dnspod.cn/account/token
export DP_Id=&amp;#34;xxx&amp;#34;
export DP_Key=&amp;#34;xxx&amp;#34;
acme.sh --issue --dns dns_dp -d lsbgb.com -d *.lsbgb.com
acme.sh --issue --dns dns_dp -d lsbgb.com -d *.lsbgb.com --fullchain-file /etc/nginx/ssl/lsbgb.com/fullchain.cer --key-file /etc/nginx/ssl/lsbgb.com/lsbgb.com.key --dnssleep
acme.sh --issue --dns dns_dp -d k8s.lsbgb.com -d *.k8s.lsbgb.com
acme.sh --issue --dns dns_dp -d k8s.lsbgb.com -d *.k8s.lsbgb.com --fullchain-file /etc/nginx/ssl/k8s.lsbgb.com/fullchain.cer --key-file /etc/nginx/ssl/k8s.lsbgb.com/k8s.lsbgb.com.key --dnssleep
# crontab -l
# cat ~/.acme.sh/account.conf
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="三级域名申请"&gt;三级域名申请&lt;/h2&gt;
&lt;p&gt;申请了顶级域名和二级域名，但在使用三级域名的时候还是会报不安全的错误，所以如果你想要用三级域名的话，也必须在申请的时候写上&lt;/p&gt;</description></item><item><title>shadowsocks server</title><link>/2017/10/01/shadowsocks-server/</link><pubDate>Sun, 01 Oct 2017 00:00:00 +0000</pubDate><guid>/2017/10/01/shadowsocks-server/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;vultr的vps性价比越来越高了，带宽不限，5美刀的流量都有1024G/月 .废话不多说，直接开始说搭建。&lt;/p&gt;
&lt;p&gt;首先连接上vultr的一个vps。&lt;/p&gt;
&lt;h1 id="安装shadowsocks"&gt;安装shadowsocks&lt;/h1&gt;
&lt;h2 id="ubuntu-18"&gt;ubuntu 18&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt install shadowsocks
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="其它系统"&gt;其它系统&lt;/h2&gt;
&lt;p&gt;安装依赖shadowsocks依赖包&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt install python-pip python-m2crypto -y
pip install setuptools
pip install wheel
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;安装shadowsocks&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pip install shadowsocks
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="修改配置文件"&gt;修改配置文件&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cat /etc/shadowsocks.json
{
&amp;#34;server&amp;#34;: &amp;#34;0.0.0.0&amp;#34;,
&amp;#34;server_port&amp;#34;: 55555, #设置你的ss服务端口
# &amp;#34;server_port&amp;#34;: [55555, 44444], #开启多个端口
&amp;#34;password&amp;#34;: &amp;#34;your_password&amp;#34;, #设置ss服务连接密码
&amp;#34;timeout&amp;#34;: 300,
&amp;#34;method&amp;#34;: &amp;#34;aes-256-cfb&amp;#34;,
&amp;#34;fast_open&amp;#34;: false
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="开启ss服务"&gt;开启ss服务&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ssserver -c /etc/shadowsocks.json -d start
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;系统重启之后，自动开启&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cat /etc/rc.local
ulimit -n 51200
ssserver -c /etc/shadowsocks.json -d start
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="添加用户和端口"&gt;添加用户和端口&lt;/h1&gt;
&lt;p&gt;为什么要添加用户？&lt;/p&gt;
&lt;p&gt;root用户密码太他喵的难敲了！一堆很长的随机字符串，各种配合shift的都有，登录一次贼费劲.&lt;/p&gt;
&lt;p&gt;&lt;img alt="vultr" loading="lazy" src="/2017/10/01/shadowsocks-server/vultr_information.png"&gt;&lt;/p&gt;
&lt;p&gt;而且在console端不可以粘贴。所以换一个用户，帮助太大了。&lt;/p&gt;</description></item><item><title>frp</title><link>/2017/09/15/frp/</link><pubDate>Fri, 15 Sep 2017 00:00:00 +0000</pubDate><guid>/2017/09/15/frp/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="frp"&gt;frp&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/fatedier/frp"&gt;frp&lt;/a&gt; 是一个可用于内网穿透的高性能的反向代理应用，支持 tcp, udp 协议，为 http 和 https 应用协议提供了额外的能力，且尝试性支持了点对点穿透。&lt;/p&gt;
&lt;p&gt;当然还有一些其他的内网穿透工具，比如 &lt;a href="https://telebit.cloud/"&gt;telebit&lt;/a&gt; &lt;a href="https://github.com/inconshreveable/ngrok"&gt;ngrok&lt;/a&gt; . 但效果都不太理想.&lt;/p&gt;
&lt;p&gt;首先telebit, 服务器在国外，国内使用延迟特别高，当然可以自己搭建 &lt;a href="https://git.coolaj86.com/coolaj86/telebit-relay.js"&gt;telebit-relay&lt;/a&gt; 但是坑特别多，我提issue想让作者处理一下，得到的回复是这样的：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Thanks for the report.
We don’t have update install instructions for the relay server yet and we don’t have time to update and test them right now, so you’ll have to use the official server https://telebit.cloud until we do.
Right now our efforts are focused on improving the Telebit Remote client.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;于是果断放弃。&lt;/p&gt;</description></item><item><title>关于CGI和FastCGI的理解</title><link>/2017/09/03/%E5%85%B3%E4%BA%8Ecgi%E5%92%8Cfastcgi%E7%9A%84%E7%90%86%E8%A7%A3/</link><pubDate>Sun, 03 Sep 2017 00:00:00 +0000</pubDate><guid>/2017/09/03/%E5%85%B3%E4%BA%8Ecgi%E5%92%8Cfastcgi%E7%9A%84%E7%90%86%E8%A7%A3/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="关于cgi和fastcgi的理解"&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html"&gt;关于CGI和FastCGI的理解&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;阅读目录&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label0"&gt;0.CGI的引入&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;1.关于CGI
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label1_0"&gt;1.1.什么是CGI？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label1_1"&gt;1.2.CGI程序的工作原理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label1_2"&gt;1.3.CGI程序的特点&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label1_3"&gt;1.4.CGI程序的应用领域&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2.关于FastCGI
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label2_0"&gt;2.1.什么是FastCGI？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label2_1"&gt;2.2.FastCGI程序的工作原理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label2_2"&gt;2.3.FastCGI对进程的管理方式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label2_3"&gt;2.4.FastCGI的特点：&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label2_4"&gt;2.5.FastCGI程序的应用领域&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label2_5"&gt;2.6.关于CGI和FastCGI的总结&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3.关于PHP-CGI，PHP-FPM和Spawn-FCGI
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label3_0"&gt;3.1.PHP-CGI是什么？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label3_1"&gt;3.2.关于php-fpm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label3_2"&gt;3.3.关于Spawn-FCGI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label3_3"&gt;3.4.PHP-FPM与spawn-CGI对比&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4.PHP运行的5种模式
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label4_0"&gt;4.1.cli模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label4_1"&gt;4.2.CGI模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label4_2"&gt;4.3.FastCGI模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label4_3"&gt;4.4.ISAPI运行模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label4_4"&gt;4.5.apache模块运行模式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label4_5"&gt;4.6.总结一下&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_label5"&gt;5.参考文章：&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;转载：&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html"&gt;关于CGI和FastCGI的理解&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;在搭建 LAMP/LNMP 服务器时，会经常遇到 PHP-FPM、FastCGI和CGI 这几个概念。如果对它们一知半解，很难搭建出高性能的服务器。&lt;/p&gt;
&lt;h2 id="0cgi的引入"&gt;0.CGI的引入&lt;/h2&gt;
&lt;p&gt;在网站的整体架构中，Web Server（如nginx，apache）只是内容的分发者，对客户端的请求进行应答。&lt;/p&gt;
&lt;p&gt;如果客户端请求的是index.html这类静态页面，那么Web Server就去文件系统中找对应的文件，找到返回给客户端（一般是浏览器），在这里Web Server分发的就是是静态数据。&lt;/p&gt;
&lt;p&gt;整个过程如下图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="1211667-20190114110548267-1086537722.png"&gt;&lt;/p&gt;
&lt;p&gt;对于像index.php这类的动态页面请求，Web Server根据配置文件知道这个不是静态文件，则会调用PHP 解析器进行处理然后将返回的数据转发给客户端（浏览器）。&lt;/p&gt;
&lt;p&gt;整个过程如下图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="1211667-20190114112158766-480550737.png"&gt;&lt;/p&gt;
&lt;p&gt;在这个过程中，Web Server并不能直接处理静态或者动态请求，对于静态请求是直接查找然后返回数据或者报错信息，对于动态数据也是交付给其他的工具（这里的PHP解析器）进行处理。&lt;/p&gt;
&lt;p&gt;那么Web Server和处理工具（这里的php-fpm）是怎样进行交互的呢？传输的是那些数据呢？这些数据的格式又是怎样的呢？&lt;/p&gt;
&lt;p&gt;由此便引出了今天的主角：&lt;strong&gt;CGI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.cnblogs.com/tssc/p/10255590.html#_labelTop"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="1关于cgi"&gt;&lt;strong&gt;1.关于CGI&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id="11什么是cgi"&gt;1.1.什么是CGI？&lt;/h3&gt;
&lt;p&gt;1）CGI（Common Gateway Interface）全称是“通用网关接口”，是一种让客户端（web浏览器）与Web服务器（nginx等）程序进行通信（数据传输）的协议。&lt;/p&gt;
&lt;p&gt;用来规范web服务器传输到php解释器中的数据类型以及数据格式，包括URL、查询字符串、POST数据、HTTP header等，也就是为了保证web server传递过来的数据是标准格式的。&lt;/p&gt;
&lt;p&gt;2）CGI可以用任何一种具有标准输入、输出和环境变量的语言编写，如php、perl、tcl等。&lt;/p&gt;
&lt;p&gt;不同类型语言写的程序只要符合cgi标准，就能作为一个cgi程序与web服务器交互，早期的cgi大多都是c或c++编写的。&lt;/p&gt;
&lt;p&gt;3）一般说的CGI指的是用各种语言编写的能实现该功能的程序。&lt;/p&gt;
&lt;h3 id="12cgi程序的工作原理"&gt;1.2.CGI程序的工作原理&lt;/h3&gt;
&lt;p&gt;1）每次当web server收到index.php这种类型的动态请求后，会启动对应的CGI程序（PHP的解析器）；
2）PHP解析器会解析php.ini配置文件，初始化运行环境，然后处理请求，处理完成后将数据按照CGI规定的格式返回给web server然后退出进程；
3）最后web server再把结果返回给浏览器。&lt;/p&gt;</description></item><item><title>Celery简介以及与Redis的集成使用</title><link>/2017/08/15/celery%E7%AE%80%E4%BB%8B%E4%BB%A5%E5%8F%8A%E4%B8%8Eredis%E7%9A%84%E9%9B%86%E6%88%90%E4%BD%BF%E7%94%A8/</link><pubDate>Tue, 15 Aug 2017 00:00:00 +0000</pubDate><guid>/2017/08/15/celery%E7%AE%80%E4%BB%8B%E4%BB%A5%E5%8F%8A%E4%B8%8Eredis%E7%9A%84%E9%9B%86%E6%88%90%E4%BD%BF%E7%94%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="celery"&gt;Celery&lt;/h1&gt;
&lt;h2 id="1什么是clelery"&gt;1.什么是Clelery&lt;/h2&gt;
&lt;p&gt;Celery 是一个专注于实时处理和任务调度的分布式任务队列, 同时提供操作和维护分布式系统所需的工具.&lt;/p&gt;
&lt;h3 id="celery架构"&gt;Celery架构&lt;/h3&gt;
&lt;p&gt;&lt;img alt="2522678-d369b6a4c4265225" loading="lazy" src="/2017/08/15/celery%E7%AE%80%E4%BB%8B%E4%BB%A5%E5%8F%8A%E4%B8%8Eredis%E7%9A%84%E9%9B%86%E6%88%90%E4%BD%BF%E7%94%A8/2522678-d369b6a4c4265225.png"&gt;&lt;/p&gt;
&lt;p&gt;Celery的架构由三部分组成，消息中间件（message broker），任务执行单元（worker）和任务执行结果存储（task result store）组成。&lt;/p&gt;
&lt;h4 id="消息中间件"&gt;消息中间件&lt;/h4&gt;
&lt;p&gt;Celery本身不提供消息服务，但是可以方便的和第三方提供的消息中间件集成。包括，RabbitMQ, Redis等等&lt;/p&gt;
&lt;h4 id="任务执行单元"&gt;任务执行单元&lt;/h4&gt;
&lt;p&gt;Worker是Celery提供的任务执行的单元，worker并发的运行在分布式的系统节点中。&lt;/p&gt;
&lt;h4 id="任务结果存储"&gt;任务结果存储&lt;/h4&gt;
&lt;p&gt;Task result store用来存储Worker执行的任务的结果，Celery支持以不同方式存储任务的结果，包括AMQP, redis等&lt;/p&gt;
&lt;h3 id="版本支持情况"&gt;版本支持情况&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Celery version 4.0 runs on
Python ❨2.7, 3.4, 3.5❩
PyPy ❨5.4, 5.5❩
This is the last version to support Python 2.7, and from the next version (Celery 5.x) Python 3.5 or newer is required.
If you’re running an older version of Python, you need to be running an older version of Celery:
Python 2.6: Celery series 3.1 or earlier.
Python 2.5: Celery series 3.0 or earlier.
Python 2.4 was Celery series 2.2 or earlier.
Celery is a project with minimal funding, so we don’t support Microsoft Windows. Please don’t open any issues related to that platform.
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2使用场景"&gt;2.使用场景&lt;/h2&gt;
&lt;p&gt;异步任务：将耗时操作任务提交给Celery去异步执行，比如发送短信/邮件、消息推送、音视频处理等等&lt;/p&gt;</description></item><item><title>Redis持久化</title><link>/2017/08/12/redis%E6%8C%81%E4%B9%85%E5%8C%96/</link><pubDate>Sat, 12 Aug 2017 00:00:00 +0000</pubDate><guid>/2017/08/12/redis%E6%8C%81%E4%B9%85%E5%8C%96/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Redis支持RDB和AOF两种持久化机制，持久化功能有效地避免因进程退出造成的数据丢失问题，当下次重启时利用之前持久化的文件即可实现数 据恢复。理解掌握持久化机制对于Redis运维非常重要&lt;/p&gt;
&lt;h1 id="1rdb持久化"&gt;1.RDB持久化&lt;/h1&gt;
&lt;p&gt;RDB持久化是把当前进程数据生成快照保存到硬盘的过程，触发RDB持久化过程分为手动触发和自动触发&lt;/p&gt;
&lt;h4 id="1触发机制"&gt;1）触发机制&lt;/h4&gt;
&lt;p&gt;手动触发分别对应save和bgsave命令&lt;/p&gt;
&lt;p&gt;·save命令：阻塞当前Redis服务器，直到RDB过程完成为止，对于内存 比较大的实例会造成长时间阻塞，线上环境不建议使用&lt;/p&gt;
&lt;p&gt;·bgsave命令：Redis进程执行fork操作创建子进程，RDB持久化过程由子 进程负责，完成后自动结束。阻塞只发生在fork阶段，一般时间很短&lt;/p&gt;
&lt;h4 id="2自动触发rdb的持久"&gt;2）自动触发RDB的持久&lt;/h4&gt;
&lt;p&gt;1）使用save相关配置，如“save m n”。表示m秒内数据集存在n次修改 时，自动触发bgsave。&lt;/p&gt;
&lt;p&gt;2）如果从节点执行全量复制操作，主节点自动执行bgsave生成RDB文件并发送给从节点，更多细节见6.3节介绍的复制原理。&lt;/p&gt;
&lt;p&gt;3）执行debug reload命令重新加载Redis时，也会自动触发save操作。&lt;/p&gt;
&lt;p&gt;4）默认情况下执行shutdown命令时，如果没有开启AOF持久化功能则 自动执行bgsave。&lt;/p&gt;
&lt;p&gt;bgsave是主流的触发RDB持久化方式&lt;/p&gt;
&lt;p&gt;&lt;img alt="img" loading="lazy" src="webp"&gt;&lt;/p&gt;
&lt;p&gt;1）执行bgsave命令，Redis父进程判断当前是否存在正在执行的子进 程，如RDB/AOF子进程，如果存在bgsave命令直接返回。&lt;/p&gt;
&lt;p&gt;2）父进程执行fork操作创建子进程，fork操作过程中父进程会阻塞，通 过info stats命令查看latest_fork_usec选项，可以获取最近一个fork操作的耗时，单位为微秒&lt;/p&gt;
&lt;p&gt;3）父进程fork完成后，bgsave命令返回“Background saving started”信息并不再阻塞父进程，可以继续响应其他命令。&lt;/p&gt;
&lt;p&gt;4）子进程创建RDB文件，根据父进程内存生成临时快照文件，完成后 对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的 时间，对应info统计的rdb_last_save_time选项。&lt;/p&gt;
&lt;p&gt;5）进程发送信号给父进程表示完成，父进程更新统计信息，具体见 info Persistence下的rdb_*相关选项。&lt;/p&gt;
&lt;p&gt;RDB文件的处理&lt;/p&gt;
&lt;p&gt;保存：RDB文件保存在dir配置指定的目录下，文件名通过dbfilename配 置指定。可以通过执行config set dir{newDir}和config set dbfilename{newFileName}运行期动态执行，当下次运行时RDB文件会保存到新目录。&lt;/p&gt;
&lt;h1 id="rdb的优缺点"&gt;RDB的优缺点&lt;/h1&gt;
&lt;h4 id="rdb的优点"&gt;RDB的优点：&lt;/h4&gt;
&lt;p&gt;·RDB是一个紧凑压缩的二进制文件，代表Redis在某个时间点上的数据 快照。非常适用于备份，全量复制等场景。比如每6小时执行bgsave备份， 并把RDB文件拷贝到远程机器或者文件系统中（如hdfs），用于灾难恢复。&lt;/p&gt;
&lt;p&gt;·Redis加载RDB恢复数据远远快于AOF的方式。&lt;/p&gt;
&lt;h4 id="rdb的缺点"&gt;RDB的缺点：&lt;/h4&gt;
&lt;p&gt;·RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运 行都要执行fork操作创建子进程，属于重量级操作，频繁执行成本过高。&lt;/p&gt;
&lt;p&gt;·RDB文件使用特定二进制格式保存，Redis版本演进过程中有多个格式 的RDB版本，存在老版本Redis服务无法兼容新版RDB格式的问题。&lt;/p&gt;
&lt;p&gt;针对RDB不适合实时持久化的问题，Redis提供了AOF持久化方式来解决。&lt;/p&gt;
&lt;h1 id="2aof持久化"&gt;2.AOF持久化&lt;/h1&gt;
&lt;p&gt;AOF（append only file）持久化：以独立日志的方式记录每次写命令， 重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用 是解决了数据持久化的实时性，目前已经是Redis持久化的主流方式&lt;/p&gt;
&lt;h4 id="1使用aof"&gt;1）使用AOF&lt;/h4&gt;
&lt;p&gt;开启AOF功能需要设置配置：appendonly yes，默认不开启。AOF文件名 通过appendfilename配置设置，默认文件名是appendonly.aof。保存路径同 RDB持久化方式一致，通过dir配置指定。AOF的工作流程操作：命令写入 （append）、文件同步（sync）、文件重写（rewrite）、重启加载 （load）&lt;/p&gt;</description></item><item><title>MinIO</title><link>/2017/08/05/minio/</link><pubDate>Sat, 05 Aug 2017 00:00:00 +0000</pubDate><guid>/2017/08/05/minio/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="minio"&gt;MinIO&lt;/h1&gt;
&lt;p&gt;MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口，非常适合于存储大容量非结构化的数据，例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等，而一个对象文件可以是任意大小，从几kb到最大5T不等。&lt;/p&gt;
&lt;p&gt;MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合，类似 NodeJS, Redis 或者 MySQL。&lt;/p&gt;
&lt;h2 id="docker-install"&gt;docker install&lt;/h2&gt;
&lt;p&gt;官方强烈推荐使用docker安装。轻量服务嘛。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker pull minio/minio
docker run -p 9000:9000 minio/minio server /data
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其他方式参考&lt;a href="https://docs.min.io/"&gt;官方&lt;/a&gt;咯， 安装由于很简单，这里就不写了。&lt;/p&gt;
&lt;h2 id="minio-sdk"&gt;MinIO SDK&lt;/h2&gt;
&lt;p&gt;minio有支持各种语言的SDK，你甚至可以用它来链接aws的s3， 下面一Python为例，客户端连接服务。&lt;/p&gt;
&lt;p&gt;首先安装minio模块&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pip install minio
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;链接minio server&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from minio import Minio
from minio.error import ResponseError
minioClient = Minio(&amp;#39;minio.domain.com&amp;#39;,
access_key=&amp;#39;xxxxx&amp;#39;,
secret_key=&amp;#39;xxxxx&amp;#39;,
secure=True)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;链接aws s3 server&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from minio import Minio
from minio.error import ResponseError
s3Client = Minio(&amp;#39;s3.amazonaws.com&amp;#39;,
access_key=&amp;#39;YOUR-ACCESSKEYID&amp;#39;,
secret_key=&amp;#39;YOUR-SECRETACCESSKEY&amp;#39;,
secure=True)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;操作的话主要分为四类： &lt;code&gt;Bucket operations&lt;/code&gt; ， &lt;code&gt;Object operations&lt;/code&gt; ， &lt;code&gt;Presigned operations&lt;/code&gt; 和 &lt;code&gt;Bucket policy&lt;/code&gt; ，怎么样？ 是不是跟aws的s3一样一样的。具体参考 &lt;a href="https://docs.min.io/docs/python-client-api-reference.html"&gt;python-client-api-reference&lt;/a&gt;&lt;/p&gt;</description></item><item><title>infiniband安装配置</title><link>/2017/08/01/infiniband%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE/</link><pubDate>Tue, 01 Aug 2017 00:00:00 +0000</pubDate><guid>/2017/08/01/infiniband%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="基本概念"&gt;基本概念&lt;/h2&gt;
&lt;h3 id="rdma"&gt;RDMA&lt;/h3&gt;
&lt;p&gt;Remote Direct Memory Access: 为了解决网络传输中服务器端数据处理的延迟而产生,通过网络把资料直接传入计算机的存储区，将数据从一个系统快速移动到远程系统存储器中，而不对操作系统造成任何影响，不需要用到多少计算机的处理功能。&lt;/p&gt;
&lt;h3 id="ib"&gt;IB&lt;/h3&gt;
&lt;p&gt;infiniband,是一个用于高性能计算的计算机网络通信标准，它具有极高的吞吐量和极低的延迟，用于计算机与计算机之间的数据互连。InfiniBand也用作服务器与存储系统之间的直接或交换互连，以及存储系统之间的互连,它用到了RDMA技术。&lt;/p&gt;
&lt;h3 id="ipoib"&gt;IPoIB&lt;/h3&gt;
&lt;p&gt;Internet Protocol over InfiniBand,利用物理IB网络通过IP协议进行连接，并进行数据传输。性能稍低于rdma，但是比普通的千兆网要快很多。目前在集群nfs上测试，可以10Gb/s&lt;/p&gt;
&lt;h2 id="安装infiniband"&gt;安装infiniband&lt;/h2&gt;
&lt;h3 id="安装包"&gt;安装包&lt;/h3&gt;
&lt;p&gt;infiniband驱动模块是需要在所有infiniband节点安装。&lt;/p&gt;
&lt;p&gt;驱动模块，以下软件包之一：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;以下软件包之一：infinipath-psm
、libcxgb3、libcxgb4、libehca、libipathverbs、libmthca、libmlx4、libmlx5、libnes 及 libocrdma
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;libibverbs 提供InfiniBand Verbs API&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;yum install libibverbs
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;yum install rdma #RDMA栈内核初始化
systemctl start rdma
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="opensm"&gt;opensm&lt;/h3&gt;
&lt;p&gt;opensm可以搭建管理子网络。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;yum install opensm
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="测试工具"&gt;测试工具&lt;/h3&gt;
&lt;p&gt;infiniband-diags 或 ibutils 为 InfiniBand 结构管理提供大量有用的调试工具。这些工具只
为 iWARP 或 RoCE 提供有限功能，因为大多数工具可在 InfiniBank 链接层工作，但无法在 Verbs API 层
使用。&lt;/p&gt;
&lt;p&gt;qperf和perftest 用于各种 RDMA 通讯类型的性能测试应用程序。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;------------------------------------
查看所有node的配置信息
------------------------------------
iblinkinfo
CA: node5 mlx5_0:
0x248a07030058f544 1 1[ ] ==( 4X 25.78125 Gbps Active/ LinkUp)==&amp;gt; 11 34[ ] &amp;quot;MF0;switch-ab29a6:MSB7700/U1&amp;quot; ( )
CA: node6 mlx5_0:
0x248a07030058f650 8 1[ ] ==( 4X 25.78125 Gbps Active/ LinkUp)==&amp;gt; 11 32[ ] &amp;quot;MF0;switch-ab29a6:MSB7700/U1&amp;quot; ( )
11 32[ ] ==( 4X 25.78125 Gbps Active/ LinkUp)==&amp;gt; 8 1[ ] &amp;quot;node6 mlx5_0&amp;quot; ( )
11 34[ ] ==( 4X 25.78125 Gbps Active/ LinkUp)==&amp;gt; 1 1[ ] &amp;quot;node5 mlx5_0&amp;quot; ( )
------------------------------------
ibping test RDMA
------------------------------------
首先启动opensm服务
然后查看每个node节点的infiniband信息 ibv_devinfo 或者 ibstat都行
[root@master rdma]#ibv_devinfo &amp;amp;&amp;amp; ibstat
hca_id: mlx5_0
transport: InfiniBand (0)
fw_ver: 12.17.1010
node_guid: 248a:0703:0058:f504
sys_image_guid: 248a:0703:0058:f504
vendor_id: 0x02c9
vendor_part_id: 4115
hw_ver: 0x0
board_id: MT_2180110032
phys_port_cnt: 1
port: 1
state: PORT_ACTIVE (4)
max_mtu: 4096 (5)
active_mtu: 4096 (5)
sm_lid: 2
port_lid: 2
port_lmc: 0x00
link_layer: InfiniBand
CA 'mlx5_0'
CA type: MT4115
Number of ports: 1
Firmware version: 12.17.1010
Hardware version: 0
Node GUID: 0x248a07030058f504
System image GUID: 0x248a07030058f504
Port 1:
State: Active
Physical state: LinkUp
Rate: 100
Base lid: 2
LMC: 0
SM lid: 2
Capability mask: 0x2659e84a
Port GUID: 0x248a07030058f504
Link layer: InfiniBand
在服务端
ibping -S -C mlx5_0 -P 1 -L 2
ibping -S -C mlx5_0 -P 1 -G 0x248a07030058f504
-S 以server启动
-C 就是指明CA的名称
-P 指明port
-L 指明port_lid/Base lid
-G 指明GUID
在客户端
ibping -c 10000 -f -C mlx5_0 -P 1 -L 2
指明你要ping的服务端的 -C -P -G -L等
结果如下：
[root@node2 ~]#ibping -c 10000 -f -C mlx5_0 -P 1 -L 2
--- master.(none) (Lid 2) ibping statistics ---
10000 packets transmitted, 10000 received, 0% packet loss, time 1364 ms
rtt min/avg/max = 0.034/0.136/0.849 ms
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="ipoib-1"&gt;IPoIB&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;------------------------------------
IPoIB 配置
------------------------------------
#cat /etc/sysconfig/network-scripts/ifcfg-mlx5_ib0
# Generated by dracut initrd
DEVICE=mlx5_ib0 #这里必须对应/etc/udev/rules.d/70-persistent-ipoib.rules 中的 NAME=&amp;#34;mlx5_ib0&amp;#34;
TYPE=InfiniBand #这里必须是InfiniBand
ONBOOT=yes
HARDWAR=80:00:00:66:fe:80:00:00:00:00:00:00:24:8a:07:03:00:58:f5:04
BOOTPROTO=none
IPADDR=172.16.20.10
NETMASK=255.255.255.0
BROADCAST=172.16.20.255
NAME=mlx5_ib0 #这里对应ifconfig中的网卡名称
MTU=65520 #设置连接模式最大MTU
STARTMODE=&amp;#39;auto&amp;#39;
NM_CONTROLLED=no
CONNECTED_MODE=yes #设置连接模式
#ifup mlx5_ib0
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="通过ipoib配置nfs"&gt;通过IPoIB配置nfs&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; 服务端：
yum install nfs-utils
modprobe svcrdma
systemctl start nfs
echo rdma 20049 &amp;gt; /proc/fs/nfsd/portlist
cat /proc/fs/nfsd/portlist
rdma 20049
udp 2049
tcp 2049
客户端：
modprobe xprtrdma
mount -o rdma,port=20049 172.16.20.10:/atlas /atlas
cat /etc/fstab
172.16.20.10:/atlas /atlas nfs defaults 0 0
/dev/sda1 /scratch ext4 defaults 0 0
mount | grep my_directory
...proto=rdma,port=20049,...
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>slurm 安装文档</title><link>/2017/07/26/slurm-%E5%AE%89%E8%A3%85%E6%96%87%E6%A1%A3/</link><pubDate>Wed, 26 Jul 2017 00:00:00 +0000</pubDate><guid>/2017/07/26/slurm-%E5%AE%89%E8%A3%85%E6%96%87%E6%A1%A3/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;SLURM是一款集群资源管理调度软件,适合深度学习集群管理调度.&lt;/p&gt;
&lt;h1 id="清理软件"&gt;清理软件&lt;/h1&gt;
&lt;p&gt;卸载已安装的软件&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;yum remove -y munge*
yum remove -y slurm*
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;清除自建文件和目录&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;log文件&lt;/li&gt;
&lt;li&gt;/var/spool/ 下的目录和文件
注意检查文件的权属&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="在master节点安装mariadb-数据库"&gt;在master节点安装MariaDB 数据库&lt;/h1&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;yum install mariadb-server mariadb-devel -y
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="安装-munge"&gt;安装 munge&lt;/h1&gt;
&lt;h2 id="1-在-master-节点创建-uid-和-gid"&gt;1. 在 master 节点创建 UID 和 GID&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;export MUNGEUSER=1050
groupadd -g $MUNGEUSER munge
useradd -m -c &amp;#34;MUNGE Uid &amp;#39;N&amp;#39; Gid Emporium&amp;#34; -d /var/lib/munge -u $MUNGEUSER -g munge -s /sbin/nologin munge
export SlurmUSER=1051
groupadd -g $SlurmUSER slurm
useradd -m -c &amp;#34;Slurm workload manager&amp;#34; -d /var/lib/slurm -u $SlurmUSER -g slurm -s /bin/bash slurm
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2-在所有计算节点创建相同的uid-和-gid"&gt;2. 在所有计算节点创建相同的UID 和 GID&lt;/h2&gt;
&lt;h1 id="进行-munge-安装"&gt;进行 munge 安装&lt;/h1&gt;
&lt;h2 id="1-先安装最新的-epel-release-rpm"&gt;1. 先安装最新的 epel-release RPM&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;yum install epel-release
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2-安装-munge-rpm-包"&gt;2. 安装 MUNGE RPM 包&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;yum install munge munge-libs munge-devel -y
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="3-检查加密方式"&gt;3. 检查加密方式&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;munge -C
munge -M
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="4-在mater-节点创建所有-node-需要的秘钥"&gt;4. &lt;strong&gt;在mater 节点创建所有 node 需要的秘钥。&lt;/strong&gt;&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;yum install rng-tool -y
/usr/sbin/create-munge-key -r
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;dd if=/dev/urandom bs=1 count=1024 &amp;gt; /etc/munge/munge.key
chown munge: /etc/munge/munge.key
chmod 400 /etc/munge/munge.key
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="5-将-etcmungemungekey-拷贝到其他节点"&gt;5. &lt;strong&gt;将 /etc/munge/munge.key 拷贝到其他节点&lt;/strong&gt;&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;export NODE=172.16.10.18
scp /etc/munge/munge.key root@login:/etc/munge
scp /etc/munge/munge.key root@node1:/etc/munge
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="6-在所有节点上设置权限和所属-"&gt;6. 在所有节点上设置权限和所属 :&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;chown -R munge: /etc/munge/ /var/log/munge/
chmod 0700 /etc/munge/ /var/log/munge/
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="7-在所有节点上运行-munge"&gt;7. 在所有节点上运行 munge:&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl enable munge
systemctl start munge
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="8-测试"&gt;8. 测试&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;munge -n
munge -n | unmunge # Displays information about the MUNGE key
munge -n | ssh somehost unmunge
remunge
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;hr&gt;
&lt;h1 id="二安装-slurm"&gt;二、安装 Slurm&lt;/h1&gt;
&lt;h2 id="1-先安装一下支持的软件包"&gt;1. 先安装一下支持的软件包:&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;yum install rpm-build gcc openssl openssl-devel pam-devel numactl numactl-devel \
hwloc hwloc-devel lua lua-devel readline-devel rrdtool-devel ncurses-devel \
gtk2-devel man2html libibmad libibumad perl-Switch perl-ExtUtils-MakeMaker
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="2-下载最新的slurm-版本到存储节点nfs文件下"&gt;2. 下载最新的slurm 版本到存储节点NFS文件下&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cd /gensoft/slurm-rpms
export VER=17.02.0
wget http://www.schedmd.com/download/latest/slurm-17.02.0.tar.bz2
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="3-在所有节点上编译并安装"&gt;3. 在所有节点上编译并安装&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rpmbuild -ta slurm-$VER.tar.bz2
cd /root/rpmbuild/RPMS/x86_64
yum install slurm-$VER*rpm slurm-devel-$VER*rpm slurm-munge-$VER*rpm \
slurm-perlapi-$VER*rpm slurm-plugins-$VER*rpm slurm-torque-$VER*rpm \
slurm-seff-$VER*rpm
# OR
yum install slurm*rpm
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="4-配置-slurm"&gt;4. 配置 slurm&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;访问网站 &lt;a href="http://slurm.schedmd.com/configurator.html"&gt;http://slurm.schedmd.com/configurator.html&lt;/a&gt; 进行配置填写，完成后下载文件&lt;/p&gt;</description></item><item><title>高堂</title><link>/2017/07/17/%E9%AB%98%E5%A0%82/</link><pubDate>Mon, 17 Jul 2017 00:00:00 +0000</pubDate><guid>/2017/07/17/%E9%AB%98%E5%A0%82/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="高堂"&gt;高堂&lt;/h1&gt;
&lt;p&gt;高堂：原意是房屋的正室厅堂，后来也指对父母的敬称。&lt;/p&gt;
&lt;p&gt;《后汉书·&lt;a href="https://baike.baidu.com/item/%E9%A9%AC%E8%9E%8D"&gt;马融&lt;/a&gt;传》：“常坐高堂，施绛纱帐，前授生徒，后列女乐。&lt;/p&gt;
&lt;p&gt;例：&lt;/p&gt;
&lt;p&gt;君不见，高堂明镜悲白发，朝如青丝暮成雪。&lt;/p&gt;
&lt;p&gt;一拜天地，二拜高堂，夫妻对拜，送入洞房&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;一要感恩天地，珍惜美好姻缘
二要感恩父母，共同孝敬老人
三要互相感恩，珍惜对方，尊重对方，
最后礼拜结束入洞房。
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>openhpc</title><link>/2017/07/09/openhpc/</link><pubDate>Sun, 09 Jul 2017 00:00:00 +0000</pubDate><guid>/2017/07/09/openhpc/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="什么是openhpc"&gt;什么是OpenHPC&lt;/h1&gt;
&lt;p&gt;OpenHPC是一个聚合了众多常见部署应用和管理的系统框架.简单来说就是,集群中想要部署什么应用只需要一次制作好,集群中各个节点运行相同的image,完全统一,适合做高性能计算集群的搭建 .官方原话如下:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Welcome to the OpenHPC site. OpenHPC is a collaborative, community effort that initiated from a desire to aggregate a number of common ingredients required to deploy and manage High Performance Computing (HPC) Linux clusters including provisioning tools, resource management, I/O clients, development tools, and a variety of scientific libraries. Packages provided by OpenHPC have been pre-built with HPC integration in mind with a goal to provide re-usable building blocks for the HPC community.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我这是在CentOS7上面搭建的,官方给出了相应的&lt;a href="http://www.openhpc.community/downloads/"&gt;安装文档&lt;/a&gt;,不过里面东西很多,没告诉你为什么这么做,照着做也会出很多问题.下面我就结合自己制作的操作过程记录如下:&lt;/p&gt;</description></item><item><title>munge编译安装</title><link>/2017/07/08/munge%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85/</link><pubDate>Sat, 08 Jul 2017 00:00:00 +0000</pubDate><guid>/2017/07/08/munge%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="master-节点"&gt;master 节点&lt;/h2&gt;
&lt;h3 id="创建文档"&gt;创建文档&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo mkdir -p /atlas/configs/munge_v0.5.12/munge
sudo mkdir -p /atlas/gensoft/munge_v0.5.12/lib/munge
sudo mkdir -p /atlas/gensoft/munge_v0.5.12/sbin/nologin
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="创建-munge-id"&gt;创建 munge ID&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;export MUNGEUSER=2001
sudo groupadd -g $MUNGEUSER munge
sudo useradd -m -c &amp;#34;MUNGE Uid &amp;#39;N&amp;#39; Gid Emporium&amp;#34; -d /atlas/gensoft/munge_v0.5.12/lib/munge -u $MUNGEUSER -g munge -s /atlas/gensoft/munge_v0.5.12/sbin/nologin munge
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="munge下载"&gt;munge下载&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;到 https://dun.github.io/munge/ 下载 tar.gz 最新版本
文件放置到atlas/backup/munge_v0.5.12 中
解压
sudo tar zxvf dun-munge-munge-0.5.12-24-gd11b5fe.tar.gz
cd dun-munge-d11b5fe
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="安装"&gt;安装&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo ./configure --prefix=/atlas/gensoft/munge_v0.5.12 --sysconfdir=/atlas/configs/munge_v0.5.12 --localstatedir=/atlas/gensoft/munge_v0.5.12
sudo make -j 4
sudo make install 
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="配置环境变量"&gt;配置环境变量&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vim /etc/profile.d/munge.sh
export PATH=/atlas/gensoft/munge_v0.5.12/usr/bin:$PATH
export PATH=/atlas/gensoft/munge_v0.5.12/usr/sbin:$PATH
. /etc/profile.d/munge.sh
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="检查加密方式"&gt;检查加密方式&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;munge -C
munge -M
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="在mater-节点创建所有-node-需要的秘钥"&gt;在mater 节点创建所有 node 需要的秘钥&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dd if=/dev/urandom bs=1 count=1024 &amp;gt; /atlas/configs/munge_v0.5.12/munge/munge.key
chown munge: /atlas/configs/munge_v0.5.12/munge/munge.key
chmod 400 /atlas/configs/munge_v0.5.12/munge/munge.key
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="将atlasconfigsmunge_v0512-mungemungekey-拷贝到其他节点"&gt;将/atlas/configs/munge_v0.5.12 /munge/munge.key 拷贝到其他节点&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;login
scp /atlas/configs/munge_v0.5.12/munge/munge.key root@172.16.10.18:/atlas/configs/munge_v0.5.12/munge
node5
scp /atlas/configs/munge_v0.5.12/munge/munge.key root@172.16.10.15:/atlas/configs/munge_v0.5.12/munge
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;hr&gt;
&lt;h2 id="在所有节点上设置权限和所属"&gt;在所有节点上设置权限和所属&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;chown -R munge: /atlas/configs/munge_v0.5.12/munge/ /atlas/gensoft/munge_v0.5.12/log/munge/
chmod 0700 /atlas/configs/munge_v0.5.12/munge/ /atlas/gensoft/munge_v0.5.12/log/munge/
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="在所有节点上运行-munge"&gt;在所有节点上运行 munge&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; /atlas/configs/munge_v0.5.12/rc.d/init.d/munge start
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>基于singularity的deepchem-GPU镜像制作</title><link>/2017/06/21/%E5%9F%BA%E4%BA%8Esingularity%E7%9A%84deepchem-gpu%E9%95%9C%E5%83%8F%E5%88%B6%E4%BD%9C/</link><pubDate>Wed, 21 Jun 2017 00:00:00 +0000</pubDate><guid>/2017/06/21/%E5%9F%BA%E4%BA%8Esingularity%E7%9A%84deepchem-gpu%E9%95%9C%E5%83%8F%E5%88%B6%E4%BD%9C/</guid><description>&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="准备前提"&gt;准备前提&lt;/h3&gt;
&lt;p&gt;首先你的pc要安装了singularity,下载好了cuda,cudnn,anaconda.而在集群上这些都已经准备好了.&lt;/p&gt;
&lt;p&gt;制作基础系统镜像&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir ~/deepchem-build &amp;amp;&amp;amp; cd ~/deepchem-build
cp /atlas/backup/images/centos.def ./
singularity build --sandbox centos centos.def
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;准备下载好的应用.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cp /atlas/backup/software/{Anaconda3-5.1.0-Linux-x86_64.sh, cudnn-9.1-linux-x64-v7.1.tgz } ./
cp /etc/yum.repos.d/base.repo ./
# base.repo 中是在安装软件过程中所依赖的绝大部分安装包和cuda, 用本地源提高速度节省时间.
cat /etc/yum.repos.d/base.repo
[development]
name=development
baseurl=ftp://172.16.10.10/centos7.2
gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
[epel]
name=epel
baseurl=ftp://172.16.10.10/epel
gpgcheck=0
[cuda]
name=cuda
baseurl=ftp://172.16.10.10/cuda/cuda
gpgcheck=0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在镜像中安装应用.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;singularity shell -w centos
cp base.repo /etc/yum.repos.d/
yum repolist
yum -y install vim wget perl git curl bzip2
export LANG=en_US.UTF-8 #设定系统字符编码
# install Anaconda3
bash Anaconda3-5.1.0-Linux-x86_64.sh -f -b -p -s /usr/local/anaconda3
export PATH=/usr/local/anaconda3/bin:$PATH
# install nvidia-driver cuda
yum install cuda
tar xf cudnn-9.1-linux-x64-v7.1.tgz -C /usr/local/
# test
nvidia-smi
nvcc --version
# if you can not get gpu information by nvidia-smi
# reload the nvidia modules
lsmod | grep nvidia
systemctl stop gmond
rmmod nvidia_drm
rmmod nvidia_modeset
rmmod nvidia_uvm
rmmod nvidia
nvidia-smi
#or cannot to reboot , can change the nvidia kernel module by hand
rm -rf /lib/modules/3.10.0-327.el7.x86_64/extra/nvidia*
cp /var/lib/dkms/nvidia/387.26/3.10.0-327.el7.x86_64/x86_64/module/nvidia* /lib/modules/3.10.0-327.el7.x86_64/extra/
nvidia-smi
# add environment value
cat /environment
export LANG=en_US.UTF-8
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
export PATH=/usr/local/anaconda3/bin:/usr/local/cuda/bin:$PATH
# install tensorflow-gpu and keras and other modules can install by conda
cat ~/.condarc
channels:
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/menpo/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/mro/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/pro/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r/
- defaults
show_channel_urls: true
conda install tensorflow-gpu==1.6.0
conda install keras-gpu
conda install pytest pytest-html pytest-cov pydicom gevent numba autobahn pymongo redis rq txaio twisted
conda install -c menpo opencv3
# install deepchem
conda install -c deepchem -c rdkit -c conda-forge -c omnia deepchem-gpu=2.0.0
# install openslide and autobahn_autoreconnect etc. can only install by pip
pip install openslide-python autobahn_autoreconnect
wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz &amp;amp;&amp;amp; tar xf libiconv-1.15.tar.gz &amp;amp;&amp;amp; cd libiconv-1.15
./configure &amp;amp;&amp;amp; make -j 12 &amp;amp;&amp;amp; make install
ln -sv /usr/local/lib/libiconv.so.2 /usr/lib/libiconv.so.2
ldconfig
cd .. &amp;amp;&amp;amp; rm -rf libiconv-1.15
conda clean -a
yum clean all
exit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;生成只读镜像&lt;/p&gt;</description></item><item><title>singularity 安装教程</title><link>/2017/06/11/singularity-%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B/</link><pubDate>Sun, 11 Jun 2017 00:00:00 +0000</pubDate><guid>/2017/06/11/singularity-%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;首先是下载源码，singularity下载地址：&lt;a href="http://singularity.lbl.gov/all-releases"&gt;http://singularity.lbl.gov/all-releases&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;下载到本地之后，解压&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;tar xvf singularity-2.2.1.tar.gz
mkdir /gensoft/singularity
cd singularity-2.2.1/
./configure
make
make install
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;到这里其实singularity已经安装完毕，下面是参照官网说明做的一个镜像&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;BootStrap: yum
OSVersion: 7
MirrorURL: http://mirror.centos.org/centos/7/os/x86_64/
Include: yum
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;[yhu@master ~]$sudo&lt;/p&gt;
&lt;p&gt;[root@7-2 singularity-2.2.1]#singularity shell &amp;ndash;contain /tmp/Centos7.img&lt;/p&gt;
&lt;p&gt;Singularity: Invoking an interactive shell within container&amp;hellip;&lt;/p&gt;
&lt;p&gt;Singularity.Centos7.img&amp;gt; id&lt;/p&gt;
&lt;p&gt;uid=0(root) gid=0(root) groups=0(root)&lt;/p&gt;
&lt;p&gt;Singularity.Centos7.img&amp;gt; ls /&lt;/p&gt;
&lt;p&gt;bin dev etc lib lost+found mnt proc run singularity sys usr&lt;/p&gt;
&lt;p&gt;boot environment home lib64 media opt root sbin srv tmp var&lt;/p&gt;
&lt;p&gt;Singularity.Centos7.img&amp;gt; exit&lt;/p&gt;
&lt;p&gt;exit&lt;/p&gt;</description></item><item><title>singularity使用手册</title><link>/2017/06/09/singularity%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/</link><pubDate>Fri, 09 Jun 2017 00:00:00 +0000</pubDate><guid>/2017/06/09/singularity%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="制作容器"&gt;制作容器&lt;/h1&gt;
&lt;h2 id="选项一-导入docker容器"&gt;选项一 导入docker容器&lt;/h2&gt;
&lt;p&gt;使用现成的docker容器，比如docker hub上面的tensorflow：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo singularity create --size 4000 tensorflow.img
sudo singularity import tensorflow.img docker://tensorflow/tensorflow:latest
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="选项二-自己制作singularity容器"&gt;选项二 自己制作singularity容器&lt;/h2&gt;
&lt;p&gt;制作一个singularity容器需要两步，需要在任何一台有root权限的linux计算机上进行：&lt;/p&gt;
&lt;p&gt;1 创建空白镜像文件&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo singularity create --size 1200 keras2.0_cpu.img&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;其中 size用来制定大小，单位是MB，上面将创建一个1.2GB的空白镜像文件。&lt;/p&gt;
&lt;p&gt;2 接下来需要在这个镜像中写入系统和预装的软件&lt;/p&gt;
&lt;p&gt;创建 keras2.0_cpu.def 文件，内容通过符号%分割成几个部分，分别代表不同的功能。&lt;/p&gt;
&lt;p&gt;我们主要要写的内容是1） %setup； 2）%post&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# 基于docker的ubuntu镜像
BootStrap: docker
From: ubuntu:16.04
%runscript
echo &amp;#34;This is what happens when you run the container...&amp;#34;
bash
%setup
# 1）这一步可以先设置好环境变量，通过wget等下载好必要的软件库等，注意这些指令都只是在host机器上运行，不是container里面。
# 内容请参考https://git.oschina.net/sg-ai/singularityimages/blob/master/keras-tf-1.0.1-gpu.setup
%post
# non interactive debian
DEBIAN_FRONTEND=noninteractive
# Install the necessary packages (from repo)
apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends curl ca-certificates
# 2）这些指令都在container里面运行，在这里写你要安装和设置的东西，可以使用apt install之类的安装需要的软件，这样就会被安装到容器里面去
# 内容请参考https://git.oschina.net/sg-ai/singularityimages/blob/master/keras-tf-1.0.1-gpu.post
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接着运行：&lt;/p&gt;</description></item><item><title>docker repository使用方法</title><link>/2017/05/31/docker-repository%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95/</link><pubDate>Wed, 31 May 2017 00:00:00 +0000</pubDate><guid>/2017/05/31/docker-repository%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="1查看repository中的image"&gt;1.查看repository中的image&lt;/h2&gt;
&lt;p&gt;假设集群上docker 私有仓库做到了master节点，172.16.10.10:5000。&lt;/p&gt;
&lt;h3 id="获取已有image"&gt;获取已有image&lt;/h3&gt;
&lt;p&gt;私有仓库目前已有images如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$curl http://172.16.10.10:5000/v2/_catalog|python2 -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 289 100 289 0 0 1125 0 --:--:-- --:--:-- --:--:-- 1128
{
&amp;#34;repositories&amp;#34;: [
&amp;#34;centos&amp;#34;,
&amp;#34;gitlab/gitlab-ce&amp;#34;,
&amp;#34;gitlab/gitlab-runner&amp;#34;,
&amp;#34;gitlab-runner&amp;#34;,
&amp;#34;keras&amp;#34;,
&amp;#34;mongo&amp;#34;,
&amp;#34;nvidia/cuda&amp;#34;,
&amp;#34;nvidia/digits&amp;#34;,
&amp;#34;paddledev/paddle&amp;#34;,
&amp;#34;paddlepaddle/book&amp;#34;,
&amp;#34;paddlepaddle/paddle&amp;#34;,
&amp;#34;redis&amp;#34;,
&amp;#34;registry&amp;#34;,
&amp;#34;superman/registry&amp;#34;,
&amp;#34;ubuntu&amp;#34;,
&amp;#34;yhu/centos&amp;#34;,
&amp;#34;yhu/gitlab-runner&amp;#34;,
&amp;#34;yhu/registry&amp;#34;
]
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="获取制定image的所有版本号"&gt;获取制定image的所有版本号&lt;/h3&gt;
&lt;p&gt;如果想要查看对应的images拥有哪些版本， 例如paddlepaddle/paddle 这个镜像的版本&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$curl http://172.16.10.10:5000/v2/paddlepaddle/paddle/tags/list|python2 -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 84 100 84 0 0 20771 0 --:--:-- --:--:-- --:--:-- 28000
{
&amp;#34;name&amp;#34;: &amp;#34;paddlepaddle/paddle&amp;#34;,
&amp;#34;tags&amp;#34;: [
&amp;#34;latest&amp;#34;,
&amp;#34;0.10.0&amp;#34;,
&amp;#34;0.10.0-dev&amp;#34;,
&amp;#34;0.10.0-gpu&amp;#34;
]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;只需要修改中间对应的名字即可&lt;/p&gt;</description></item><item><title>docker镜像上增加安装包</title><link>/2017/05/25/docker%E9%95%9C%E5%83%8F%E4%B8%8A%E5%A2%9E%E5%8A%A0%E5%AE%89%E8%A3%85%E5%8C%85/</link><pubDate>Thu, 25 May 2017 00:00:00 +0000</pubDate><guid>/2017/05/25/docker%E9%95%9C%E5%83%8F%E4%B8%8A%E5%A2%9E%E5%8A%A0%E5%AE%89%E8%A3%85%E5%8C%85/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="前提"&gt;前提&lt;/h2&gt;
&lt;p&gt;有时候我们会遇到这样的问题,一个镜像能满足我们的大部分需求,想在此基础上添加一部分功能.有三种方法&lt;/p&gt;
&lt;h3 id="1dockerfile构建"&gt;1.dockerfile构建&lt;/h3&gt;
&lt;p&gt;找到这个镜像的dockerfile,修改后build.&lt;/p&gt;
&lt;h3 id="2作为基础镜像"&gt;2.作为基础镜像&lt;/h3&gt;
&lt;p&gt;也是写一个dockerfile,只不过这次把这个有较多功能的镜像作为基础镜像,添加所需功能.然后build.&lt;/p&gt;
&lt;h3 id="3导出container为image"&gt;3.导出container为image&lt;/h3&gt;
&lt;p&gt;直接在运行的container中添加所需功能,然后commit成image.&lt;/p&gt;
&lt;h4 id="第一步"&gt;第一步&lt;/h4&gt;
&lt;p&gt;使用root用户进入一个新容器，不要用&lt;code&gt;--rm&lt;/code&gt;（否则当你退出容器的时候，容器没有了 你添加的功能自然就不复存在了）&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker run --user 0 -it --name superman sgdockerfilebox/mitosis_cpu:latest bash
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="第二步"&gt;第二步&lt;/h4&gt;
&lt;p&gt;在container中直接添加你要的功能&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;apt update
apt install ...
npm install -g n
n stable ...
pip3 install ...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后退出容器&lt;code&gt; exit&lt;/code&gt;&lt;/p&gt;
&lt;h4 id="第三步"&gt;第三步&lt;/h4&gt;
&lt;p&gt;新的容器 &lt;code&gt;commit&lt;/code&gt;成新的&lt;code&gt;image &lt;/code&gt;
&lt;code&gt;docker commit superman sgdockerfilebox/wewo_cpu:v1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;（这里的&lt;code&gt;sgdockerfilebox/wewo_cpu:v1 &lt;/code&gt;名字和版本号自己定义）&lt;/p&gt;
&lt;p&gt;查看一下&lt;code&gt;commit&lt;/code&gt;的&lt;code&gt;image&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@gyw:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
sgdockerfilebox/wewo_cpu v1 e4f2c829d1eb 23 minutes ago 4.42GB
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;没有问题就可以删除 之前创建的容器了，
&lt;code&gt;docker rm superman&lt;/code&gt;&lt;/p&gt;</description></item><item><title>docker设置devicemapper存储驱动</title><link>/2017/05/20/docker%E8%AE%BE%E7%BD%AEdevicemapper%E5%AD%98%E5%82%A8%E9%A9%B1%E5%8A%A8/</link><pubDate>Sat, 20 May 2017 00:00:00 +0000</pubDate><guid>/2017/05/20/docker%E8%AE%BE%E7%BD%AEdevicemapper%E5%AD%98%E5%82%A8%E9%A9%B1%E5%8A%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;在 Ubuntu/Debian 上有 &lt;code&gt;UnionFS&lt;/code&gt; 可以使用，如 &lt;code&gt;aufs&lt;/code&gt; 或者 &lt;code&gt;overlay2&lt;/code&gt;，而 CentOS 和 RHEL 的内核中没有相关驱动。因此对于这类系统，一般使用 &lt;code&gt;devicemapper&lt;/code&gt; 驱动利用 LVM 的一些机制来模拟分层存储。这样的做法除了性能比较差外，稳定性一般也不好，而且配置相对复杂。Docker 安装在 CentOS/RHEL 上后，会默认选择 &lt;code&gt;devicemapper&lt;/code&gt;，但是为了简化配置，其 &lt;code&gt;devicemapper&lt;/code&gt; 是跑在一个稀疏文件模拟的块设备上，也被称为 &lt;code&gt;loop-lvm&lt;/code&gt;。这样的选择是因为不需要额外配置就可以运行 Docker，这是自动配置唯一能做到的事情。但是 &lt;code&gt;loop-lvm&lt;/code&gt; 的做法非常不好，其稳定性、性能更差，无论是日志还是 &lt;code&gt;docker info&lt;/code&gt; 中都会看到警告信息。&lt;a href="https://docs.docker.com/storage/storagedriver/device-mapper-driver/"&gt;官方文档&lt;/a&gt;有明确的文章讲解了如何配置块设备给 &lt;code&gt;devicemapper&lt;/code&gt; 驱动做存储层的做法，这类做法也被称为配置 &lt;code&gt;direct-lvm&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;除了前面说到的问题外，&lt;code&gt;devicemapper&lt;/code&gt; + &lt;code&gt;loop-lvm&lt;/code&gt; 还有一个缺陷，因为它是稀疏文件，所以它会不断增长。用户在使用过程中会注意到 &lt;code&gt;/var/lib/docker/devicemapper/devicemapper/data&lt;/code&gt; 不断增长，而且无法控制。这个稀疏文件的空间释放后基本不进行垃圾回收的问题。因此往往会出现即使删除了文件内容，空间却无法回收，随着使用这个稀疏文件一直在不断增长。&lt;/p&gt;
&lt;p&gt;所以对于 CentOS/RHEL 的用户来说，在没有办法使用 &lt;code&gt;UnionFS&lt;/code&gt; 的情况下，一定要配置 &lt;code&gt;direct-lvm&lt;/code&gt; 给 &lt;code&gt;devicemapper&lt;/code&gt;，无论是为了性能、稳定性还是空间利用率。&lt;/p&gt;
&lt;h2 id="配置过程"&gt;配置过程&lt;/h2&gt;
&lt;h3 id="1直接修改daemonjson"&gt;1.直接修改daemon.json&lt;/h3&gt;
&lt;p&gt;官方文档里面有两种方法,第一种就是直接修改&lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; 这个文档,然后重启docker即可.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{
&amp;#34;storage-driver&amp;#34;: &amp;#34;devicemapper&amp;#34;,
&amp;#34;storage-opts&amp;#34;: [
&amp;#34;dm.directlvm_device=/dev/xdf&amp;#34;, #这里修改成主机存储谁被,可以整个硬盘,或者一个分区
&amp;#34;dm.thinp_percent=95&amp;#34;,
&amp;#34;dm.thinp_metapercent=1&amp;#34;,
&amp;#34;dm.thinp_autoextend_threshold=80&amp;#34;,
&amp;#34;dm.thinp_autoextend_percent=20&amp;#34;,
&amp;#34;dm.directlvm_device_force=false&amp;#34;
]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改完了之后,重启docker即可&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo systemctl restart docker
docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 17.03.1-ce
Storage Driver: devicemapper #看这里
....
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是有些时候修改了配置,并不会生效.需要手动修改添加逻辑卷等.&lt;/p&gt;</description></item><item><title>docker常用命令</title><link>/2017/05/13/docker%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</link><pubDate>Sat, 13 May 2017 00:00:00 +0000</pubDate><guid>/2017/05/13/docker%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="1基本概念"&gt;1.基本概念&lt;/h2&gt;
&lt;p&gt;Docker 包括三个基本概念&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;镜像（&lt;code&gt;Image&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;Docker 镜像是一个特殊的&lt;strong&gt;文件系统&lt;/strong&gt;，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。镜像&lt;strong&gt;不包含任何动态数据&lt;/strong&gt;，其内容在构建之后也&lt;strong&gt;不会改变&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;容器（&lt;code&gt;Container&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;仓库（&lt;code&gt;Repository&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;一个集中的存储、分发镜像的服务。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理解了这三个概念，就理解了 Docker 的整个生命周期。&lt;/p&gt;
&lt;h2 id="2docker安装"&gt;2.docker安装&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://docs.docker.com/engine/installation/#supported-platforms"&gt;官方安装教程&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/NVIDIA/nvidia-docker/tree/v2.0.3"&gt;nvidia-docker安装&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://yq.aliyun.com/articles/110806?spm=a2c1q.8351553.0.0.9e3d4f2NpVgK"&gt;阿里云镜像源docker安装方式&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#ubuntu
sudo apt-get update
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository &amp;#34;deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable&amp;#34;
sudo apt-get -y update
sudo apt-get -y install docker-ce
#CentOS
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sudo yum makecache fast
sudo yum -y install docker-ce
sudo service docker start
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="3docker命令"&gt;3.docker命令&lt;/h2&gt;
&lt;h3 id="a镜像相关"&gt;a、镜像相关&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker image --help
Usage: docker image COMMAND
Commands:
ls List images
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rm Remove one or more images
save Save one or more images to a tar archive (streamed to STDOUT by default)
load Load an image from a tar archive or STDIN
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
build Build an image from a Dockerfile
Run &amp;#39;docker image COMMAND --help&amp;#39; for more information on a command.
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="1docker-images"&gt;1.&lt;code&gt;docker images &lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;列出主机上已存在镜像&lt;/p&gt;</description></item><item><title>django的使用3-编写视图</title><link>/2017/02/12/django%E7%9A%84%E4%BD%BF%E7%94%A83-%E7%BC%96%E5%86%99%E8%A7%86%E5%9B%BE/</link><pubDate>Sun, 12 Feb 2017 00:00:00 +0000</pubDate><guid>/2017/02/12/django%E7%9A%84%E4%BD%BF%E7%94%A83-%E7%BC%96%E5%86%99%E8%A7%86%E5%9B%BE/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="编写视图"&gt;编写视图&lt;/h1&gt;
&lt;p&gt;一个视图函数（或简称为视图）是一个 Python 函数，它接受 Web 请求并返回一个 Web 响应。这个响应可以是 Web 页面的 HTML 内容，或者重定向，或者404错误，或者 XML 文档，或一个图片&amp;hellip;或是任何内容。视图本身包含返回响应所需的任何逻辑。这个代码可以存在任何地方，只要它在你的 Python 路径上就行。可以说，不需要其他东西，这里并没有魔法。为了将代码放置在某处，约定将视图放在名为 &lt;code&gt;views.py&lt;/code&gt; 的文件里，这个文件放置在项目或应用目录里。&lt;/p&gt;
&lt;h2 id="一个简单的视图"&gt;一个简单的视图&lt;/h2&gt;
&lt;p&gt;参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.djangoproject.com/"&gt;https://docs.djangoproject.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>django的使用3-URL调度器</title><link>/2017/02/10/django%E7%9A%84%E4%BD%BF%E7%94%A83-url%E8%B0%83%E5%BA%A6%E5%99%A8/</link><pubDate>Fri, 10 Feb 2017 00:00:00 +0000</pubDate><guid>/2017/02/10/django%E7%9A%84%E4%BD%BF%E7%94%A83-url%E8%B0%83%E5%BA%A6%E5%99%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Django 允许你自由地设计你的URL，不受框架束缚。&lt;/p&gt;
&lt;h2 id="概况"&gt;概况&lt;/h2&gt;
&lt;p&gt;对于高质量的Web 应用来说，使用简洁、优雅的URL 模式是一个非常值得重视的细节。&lt;/p&gt;
&lt;p&gt;为了给一个应用设计URL，你需要创建一个Python 模块，通常被称为&lt;strong&gt;URLconf&lt;/strong&gt;(URL configuration)。这个模块是纯粹的Python 代码，包含URL 模式(简单的正则表达式)到Python 函数(你的视图)的简单映射。&lt;/p&gt;
&lt;p&gt;映射可短可长，随便你。它可以引用其它的映射。而且，因为它是纯粹的Python 代码，它可以动态构造。&lt;/p&gt;
&lt;h2 id="django-如何处理一个请求"&gt;Django 如何处理一个请求&lt;/h2&gt;
&lt;p&gt;当一个用户请求Django 站点的一个页面，下面是Django 系统决定执行哪个Python 代码使用的算法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Django 确定使用根 URLconf 模块。通常，这是 ROOT_URLCONF 设置的值，但如果传入 HttpRequest 对象拥有 urlconf 属性（通过中间件设置），它的值将被用来代替 ROOT_URLCONF 设置。&lt;/li&gt;
&lt;li&gt;Django 加载该 Python 模块并寻找可用的 urlpatterns 。它是 django.urls.path() 和(或) django.urls.re_path() 实例的序列(sequence)。&lt;/li&gt;
&lt;li&gt;Django 会按顺序遍历每个 URL 模式，然后会在所请求的URL匹配到第一个模式后停止，并与 path_info 匹配。&lt;/li&gt;
&lt;li&gt;一旦有 URL 匹配成功，Djagno 导入并调用相关的视图，这个视图是一个Python 函数（或基于类的视图 class-based view ）。视图会获得如下参数：
&lt;ul&gt;
&lt;li&gt;一个 HttpRequest 实例。&lt;/li&gt;
&lt;li&gt;如果匹配的 URL 包含未命名组，那么来自正则表达式中的匹配项将作为位置参数提供。&lt;/li&gt;
&lt;li&gt;关键字参数由路径表达式匹配的任何命名部分组成，并由 django.urls.path() 或 django.urls.re_path() 的可选 kwargs 参数中指定的任何参数覆盖。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果没有 URL 被匹配，或者匹配过程中出现了异常，Django 会调用一个适当的错误处理视图。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="实例"&gt;实例&lt;/h2&gt;
&lt;p&gt;下面是一个简单的 URLconf:&lt;/p&gt;</description></item><item><title>django的使用3-聚合</title><link>/2017/02/07/django%E7%9A%84%E4%BD%BF%E7%94%A83-%E8%81%9A%E5%90%88/</link><pubDate>Tue, 07 Feb 2017 00:00:00 +0000</pubDate><guid>/2017/02/07/django%E7%9A%84%E4%BD%BF%E7%94%A83-%E8%81%9A%E5%90%88/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="聚合"&gt;聚合&lt;/h1&gt;
&lt;p&gt;Django 数据库抽象 API 描述了使用 Django queries 来增删查改单个对象的方法。 然而，有时候你要获取的值需要根据一组对象聚合后才能得到。&lt;/p&gt;
&lt;p&gt;模型示例： 作者，出版社，书，商店。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
class Publisher(models.Model):
name = models.CharField(max_length=300)
class Book(models.Model):
name = models.CharField(max_length=300)
pages = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
rating = models.FloatField()
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
pubdate = models.DateField()
class Store(models.Model):
name = models.CharField(max_length=300)
books = models.ManyToManyField(Book)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="常见的聚合查询"&gt;常见的聚合查询&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4 id="expressions"&gt;&lt;code&gt;expressions&lt;/code&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id="output_field"&gt;&lt;code&gt;output_field&lt;/code&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id="filter"&gt;&lt;code&gt;filter&lt;/code&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id="avg"&gt;&lt;code&gt;Avg&lt;/code&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id="count"&gt;&lt;code&gt;Count&lt;/code&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id="max"&gt;&lt;code&gt;Max&lt;/code&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id="min"&gt;&lt;code&gt;Min&lt;/code&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id="sum"&gt;&lt;code&gt;Sum&lt;/code&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id="variance-返回给定表达式中数据的方差"&gt;&lt;code&gt;Variance&lt;/code&gt;： 返回给定表达式中数据的方差&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id="stddev-返回给定表达式中数据的标准差"&gt;&lt;code&gt;StdDev&lt;/code&gt;： 返回给定表达式中数据的标准差&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Total number of books.
&amp;gt;&amp;gt;&amp;gt; Book.objects.count()
2452
# Total number of books with publisher=BaloneyPress
&amp;gt;&amp;gt;&amp;gt; Book.objects.filter(publisher__name=&amp;#39;BaloneyPress&amp;#39;).count()
73
# Average price across all books.
&amp;gt;&amp;gt;&amp;gt; from django.db.models import Avg
&amp;gt;&amp;gt;&amp;gt; Book.objects.all().aggregate(Avg(&amp;#39;price&amp;#39;))
{&amp;#39;price__avg&amp;#39;: 34.35}
# Max price across all books.
&amp;gt;&amp;gt;&amp;gt; from django.db.models import Max
&amp;gt;&amp;gt;&amp;gt; Book.objects.all().aggregate(Max(&amp;#39;price&amp;#39;))
{&amp;#39;price__max&amp;#39;: Decimal(&amp;#39;81.20&amp;#39;)}
# Difference between the highest priced book and the average price of all books.
&amp;gt;&amp;gt;&amp;gt; from django.db.models import FloatField
&amp;gt;&amp;gt;&amp;gt; Book.objects.aggregate(
... price_diff=Max(&amp;#39;price&amp;#39;, output_field=FloatField()) - Avg(&amp;#39;price&amp;#39;))
{&amp;#39;price_diff&amp;#39;: 46.85}
# All the following queries involve traversing the Book&amp;lt;-&amp;gt;Publisher
# foreign key relationship backwards.
# Each publisher, each with a count of books as a &amp;#34;num_books&amp;#34; attribute.
&amp;gt;&amp;gt;&amp;gt; from django.db.models import Count
&amp;gt;&amp;gt;&amp;gt; pubs = Publisher.objects.annotate(num_books=Count(&amp;#39;book&amp;#39;))
&amp;gt;&amp;gt;&amp;gt; pubs
&amp;lt;QuerySet [&amp;lt;Publisher: BaloneyPress&amp;gt;, &amp;lt;Publisher: SalamiPress&amp;gt;, ...]&amp;gt;
&amp;gt;&amp;gt;&amp;gt; pubs[0].num_books
73
# Each publisher, with a separate count of books with a rating above and below 5
&amp;gt;&amp;gt;&amp;gt; from django.db.models import Q
&amp;gt;&amp;gt;&amp;gt; above_5 = Count(&amp;#39;book&amp;#39;, filter=Q(book__rating__gt=5))
&amp;gt;&amp;gt;&amp;gt; below_5 = Count(&amp;#39;book&amp;#39;, filter=Q(book__rating__lte=5))
&amp;gt;&amp;gt;&amp;gt; pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5)
&amp;gt;&amp;gt;&amp;gt; pubs[0].above_5
23
&amp;gt;&amp;gt;&amp;gt; pubs[0].below_5
12
# The top 5 publishers, in order by number of books.
&amp;gt;&amp;gt;&amp;gt; pubs = Publisher.objects.annotate(num_books=Count(&amp;#39;book&amp;#39;)).order_by(&amp;#39;-num_books&amp;#39;)[:5]
&amp;gt;&amp;gt;&amp;gt; pubs[0].num_books
1323
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="aggregate"&gt;aggregate&lt;/h2&gt;
&lt;p&gt;Django 提供了两种生成聚合的方法。第一种方法是从整个 &lt;code&gt;QuerySet&lt;/code&gt; 生成汇总值。比如你想要计算所有在售书的平均价格。Django 的查询语法提供了一种用来描述所有图书集合的方法：&lt;/p&gt;</description></item><item><title>django的使用3-数据库抽象API</title><link>/2017/02/05/django%E7%9A%84%E4%BD%BF%E7%94%A83-%E6%95%B0%E6%8D%AE%E5%BA%93%E6%8A%BD%E8%B1%A1api/</link><pubDate>Sun, 05 Feb 2017 00:00:00 +0000</pubDate><guid>/2017/02/05/django%E7%9A%84%E4%BD%BF%E7%94%A83-%E6%95%B0%E6%8D%AE%E5%BA%93%E6%8A%BD%E8%B1%A1api/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="数据库抽象api"&gt;数据库抽象API&lt;/h1&gt;
&lt;p&gt;一旦创建数据模型后，Django 自动给予你一套数据库抽象 API，允许你创建，检索，更新和删除对象。下面介绍这些API。&lt;/p&gt;
&lt;p&gt;先创建三个模型：博客，作者，记录&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField()
number_of_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __str__(self):
return self.headline
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="创建新数据"&gt;创建新数据&lt;/h2&gt;
&lt;p&gt;一个模型类代表一张数据表，一个模型类的实例代表数据库表中的一行记录。要创建一个对象，用关键字参数初始化它，然后调用 save() 将其存入数据库。&lt;/p&gt;</description></item><item><title>django的使用2-模型</title><link>/2017/02/01/django%E7%9A%84%E4%BD%BF%E7%94%A82-%E6%A8%A1%E5%9E%8B/</link><pubDate>Wed, 01 Feb 2017 00:00:00 +0000</pubDate><guid>/2017/02/01/django%E7%9A%84%E4%BD%BF%E7%94%A82-%E6%A8%A1%E5%9E%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="模型"&gt;模型&lt;/h2&gt;
&lt;p&gt;模型准确且唯一的描述了数据。它包含您储存的数据的重要字段和行为。一般来说，每一个模型都映射一张数据库表。有如下几个点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个模型都是一个 Python 的类，这些类继承 django.db.models.Model&lt;/li&gt;
&lt;li&gt;模型类的每个属性都相当于一个数据库的字段。&lt;/li&gt;
&lt;li&gt;利用这些，Django 提供了一个自动生成访问数据库的 API；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="example"&gt;example&lt;/h3&gt;
&lt;p&gt;定义了一个 Person 模型，拥有 first_name 和 last_name:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;first_name 和 last_name 是模型的 字段。每个字段都被指定为一个类属性，并且每个属性映射为一个数据库列。&lt;/p&gt;
&lt;p&gt;上面的 Person 模型会创建一个如下的数据库表：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;CREATE TABLE myapp_person (
&amp;#34;id&amp;#34; serial NOT NULL PRIMARY KEY,
&amp;#34;first_name&amp;#34; varchar(30) NOT NULL,
&amp;#34;last_name&amp;#34; varchar(30) NOT NULL
);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;该表的名称规则 myapp_person = app名称_class名称，可修改。
id 字段会被自动添加，可修改。
创建数据表的语法是根据配置文件setting.py中设置的数据类型来定。&lt;/p&gt;
&lt;h3 id="字段类型"&gt;字段类型&lt;/h3&gt;
&lt;p&gt;模型中每一个字段都应该是某个 Field 类的实例， Django 利用这些字段类来实现以下功能：&lt;/p&gt;</description></item><item><title>django的使用1-简介</title><link>/2017/01/30/django%E7%9A%84%E4%BD%BF%E7%94%A81-%E7%AE%80%E4%BB%8B/</link><pubDate>Mon, 30 Jan 2017 00:00:00 +0000</pubDate><guid>/2017/01/30/django%E7%9A%84%E4%BD%BF%E7%94%A81-%E7%AE%80%E4%BB%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="简介"&gt;简介&lt;/h1&gt;
&lt;p&gt;Django 是一个用python语言写的&lt;strong&gt;web开发框架&lt;/strong&gt;，最初被设计用于具有快速开发需求的新闻类站点，目的是要实现简单快捷的网站开发。
著名的MVC模式：所谓MVC就是把web应用分为模型(M),控制器(C),视图(V)三层；他们之间以一种插件似的，松耦合的方式连接在一起。&lt;/p&gt;
&lt;p&gt;模型负责业务对象与数据库的对象(ORM),视图负责与用户的交互(页面)，控制器(C)接受用户的输入调用模型和视图完成用户的请求。&lt;/p&gt;
&lt;p&gt;Django的MTV模式本质上与MVC模式没有什么差别，也是各组件之间为了保持松耦合关系，只是定义上有些许不同，Django的MTV分别代表：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Model(模型)：负责业务对象与数据库的对象(ORM)&lt;/li&gt;
&lt;li&gt;Template(模版)：负责如何把页面展示给用户&lt;/li&gt;
&lt;li&gt;View(视图)：负责业务逻辑，并在适当的时候调用Model和Template&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此外，Django还有一个url分发器，它的作用是将一个个URL的页面请求分发给不同的view处理，view再调用相应的Model和Template&lt;/p&gt;
&lt;h2 id="django安装与基本使用"&gt;Django安装与基本使用&lt;/h2&gt;
&lt;h3 id="django安装"&gt;Django安装&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pip install django
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="创建项目"&gt;创建项目&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;django-admin startproject your-project-name
# example -----------------------------------------------------------
(dj) alex@Alex:~/github$ django-admin startproject my_project
(dj) alex@Alex:~/github$ tree my_project
my_project
├── manage.py
└── my_project
├── asgi.py
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这些目录和文件的用处是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;manage.py: 一个让你用各种方式管理 Django 项目的命令行工具&lt;/li&gt;
&lt;li&gt;my_project/ 目录包含你的项目，它是一个纯 Python 包。它的名字就是当你引用它内部任何东西时需要用到的 Python 包名。&lt;/li&gt;
&lt;li&gt;my_project/&lt;strong&gt;init&lt;/strong&gt;.py：一个空文件，告诉 Python 这个目录应该被认为是一个 Python 包。&lt;/li&gt;
&lt;li&gt;my_project/settings.py：Django 项目的配置文件。&lt;/li&gt;
&lt;li&gt;my_project/urls.py：Django 项目的 URL 声明，路由配置。&lt;/li&gt;
&lt;li&gt;my_project/asgi.py：作为你的项目的运行在 ASGI 兼容的 Web 服务器上的入口。&lt;/li&gt;
&lt;li&gt;my_project/wsgi.py：作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="创建应用"&gt;创建应用&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;python manage.py startapp app01
# example -----------------------------------------------------------
(dj) alex@Alex:~/github$ cd my_project
(dj) alex@Alex:~/github/my_project$ ls
manage.py my_project
(dj) alex@Alex:~/github/my_project$ python manage.py startapp app01
(dj) alex@Alex:~/github/my_project$ tree
.
├── app01
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
└── my_project
├── asgi.py
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="启动项目"&gt;启动项目&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;python manage.py runserver
# example -----------------------------------------------------------
(dj) alex@Alex:~/github/my_project$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里省略了host 和port的设置。&lt;code&gt;python manage.py runserver 0.0.0.0:8000&lt;/code&gt;&lt;/p&gt;</description></item><item><title>Python线程、进程和协程详解</title><link>/2016/10/03/python%E7%BA%BF%E7%A8%8B%E8%BF%9B%E7%A8%8B%E5%92%8C%E5%8D%8F%E7%A8%8B%E8%AF%A6%E8%A7%A3/</link><pubDate>Mon, 03 Oct 2016 00:00:00 +0000</pubDate><guid>/2016/10/03/python%E7%BA%BF%E7%A8%8B%E8%BF%9B%E7%A8%8B%E5%92%8C%E5%8D%8F%E7%A8%8B%E8%AF%A6%E8%A7%A3/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Python被人诟病最多的大概就是性能差，在这里讲一下 Python 的多进程，多线程与协程。首先声明这不是教程，看完这篇文章，大概能够对 Python 的多进程与多线程有一定的了解。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;解释器环境：python3.5.1
我们都知道python网络编程的两大必学模块socket和socketserver，其中的socketserver是一个支持IO多路复用和多线程、多进程的模块。一般我们在socketserver服务端代码中都会写这么一句：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;server = socketserver.ThreadingTCPServer(settings.IP_PORT, MyServer)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;ThreadingTCPServer&lt;/code&gt;这个类是一个支持多线程和TCP协议的socketserver，它的继承关系是这样的：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class ThreadingTCPServer(ThreadingMixIn, TCPServer):
pass
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;右边的TCPServer实际上是它主要的功能父类，而左边的ThreadingMixIn则是实现了多线程的类，它自己本身则没有任何代码。
MixIn在python的类命名中，很常见，一般被称为“混入”，戏称“乱入”，通常为了某种重要功能被子类继承。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class ThreadingMixIn:
daemon_threads = False
def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
def process_request(self, request, client_address):
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在&lt;code&gt;ThreadingMixIn&lt;/code&gt;类中，其实就定义了一个属性，两个方法。在&lt;code&gt;process_request&lt;/code&gt;方法中实际调用的正是python内置的多线程模块threading。这个模块是python中所有多线程的基础，socketserver本质上也是利用了这个模块。&lt;/p&gt;
&lt;h1 id="一线程"&gt;一、线程&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;线程，有时被称为轻量级进程(Lightweight Process，LWP），是程序执行流的最小单元。一个标准的线程由线程ID，当前指令指针(PC），寄存器集合和堆栈组成。另外，线程是进程中的一个实体，是被系统独立调度和分派的基本单位，线程自己不独立拥有系统资源，但它可与同属一个进程的其它线程共享该进程所拥有的全部资源。一个线程可以创建和撤消另一个线程，同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约，致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件，逻辑上可以运行，在等待处理机；运行状态是指线程占有处理机正在运行；阻塞状态是指线程在等待一个事件（如某个信号量），逻辑上不可执行。每一个应用程序都至少有一个进程和一个线程。线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的被划分成一块一块的工作，称为多线程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以上那一段，可以不用看！举个例子，厂家要生产某个产品，在它的生产基地建设了很多厂房，每个厂房内又有多条流水生产线。所有厂房配合将整个产品生产出来，某个厂房内的所有流水线将这个厂房负责的产品部分生产出来。每个厂房拥有自己的材料库，厂房内的生产线共享这些材料。而每一个厂家要实现生产必须拥有至少一个厂房一条生产线。那么这个厂家就是某个应用程序；每个厂房就是一个进程；每条生产线都是一个线程。&lt;/p&gt;
&lt;h2 id="11-普通的多线程"&gt;1.1 普通的多线程&lt;/h2&gt;
&lt;p&gt;在python中，threading模块提供线程的功能。通过它，我们可以轻易的在进程中创建多个线程。下面是个例子：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import threading
import time
def show(arg):
time.sleep(1)
print(&amp;#39;thread&amp;#39;+str(arg))
for i in range(10):
t = threading.Thread(target=show, args=(i,))
t.start()
print(&amp;#39;main thread stop&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上述代码创建了10个“前台”线程，然后控制器就交给了CPU，CPU根据指定算法进行调度，分片执行指令。
下面是Thread类的主要方法：
start 线程准备就绪，等待CPU调度
setName 为线程设置名称
getName 获取线程名称
setDaemon 设置为后台线程或前台线程（默认）
如果是后台线程，主线程执行过程中，后台线程也在进行，主线程执行完毕后，后台线程不论成功与否，均停止。如果是前台线程，主线程执行过程中，前台线程也在进行，主线程执行完毕后，等待前台线程也执行完成后，程序停止。
join 逐个执行每个线程，执行完毕后继续往下执行，该方法使得多线程变得无意义。
run 线程被cpu调度后自动执行线程对象的run方法&lt;/p&gt;</description></item><item><title>pycharm profession active</title><link>/2016/09/22/pycharm-profession-active/</link><pubDate>Thu, 22 Sep 2016 00:00:00 +0000</pubDate><guid>/2016/09/22/pycharm-profession-active/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;profession的版本要比免费版多些功能,这里就不多介绍了,自己看官方.&lt;/p&gt;
&lt;h2 id="1授权服务器license-server-urls"&gt;1.授权服务器(License Server URLS):&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://idea.imsxm.com"&gt;http://idea.imsxm.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;**使用方法：**激活时选择License server 填入http://&lt;a href="http://idea.imsxm.com/"&gt;idea.imsxm.com&lt;/a&gt; 点击Active即可 &lt;img alt="active.jpg" loading="lazy" src="how-to-active.jpg"&gt; 。&lt;/p&gt;
&lt;p&gt;how-to-active: when active,type the url in License server address input box,and then press the Active button:)&lt;/p&gt;
&lt;h2 id="2local-proxy"&gt;2.local proxy&lt;/h2&gt;
&lt;p&gt;如果第一种方法被拌掉了,那么下载这个实现本地proxy代理 [&lt;a href="http://img.imsxm.com/idea_active_proxy.exe"&gt;idea_active_proxy.exe&lt;/a&gt;]&lt;/p&gt;
&lt;p&gt;open it and use &lt;a href="http://localhost:8888/"&gt;http://localhost:8888&lt;/a&gt; to active JB.when done,you can close it.&lt;/p&gt;
&lt;p&gt;意思就是下载这个idea_active_proxy.exe，实现反向代理，然后把http://idea.imsxm.com 换成 http://localhost:8888 即可。&lt;/p&gt;
&lt;p&gt;这样在新建项目的时候就可以看到除了pure Python意外的其他项目了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="new_project.png" loading="lazy" src="pycharm%E4%B8%93%E4%B8%9A%E7%89%88%E6%96%B0%E5%BB%BAproject.png"&gt;&lt;/p&gt;
&lt;p&gt;这种方法还是有点不好,那就是每次你运行pycharm的时候都要先运行这个&lt;code&gt;idea_active_proxy.exe&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="3localhost"&gt;3.localhost&lt;/h2&gt;
&lt;p&gt;第三种就更好了.&lt;/p&gt;
&lt;p&gt;首先在你的host文件中添加 &lt;code&gt;0.0.0.0 account.jetbrains.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;然后就是打开pycharm,在那个激活页面选择 &lt;code&gt;Activation code&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="active.jpg" loading="lazy" src="how-to-active.jpg"&gt;&lt;/p&gt;
&lt;p&gt;复制通用注册码,激活即可.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;EB101IWSWD-eyJsaWNlbnNlSWQiOiJFQjEwMUlXU1dEIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiYXNzaWduZWVOYW1lIjoiIiwiYXNzaWduZWVFbWFpbCI6IiIsImxpY2Vuc2VSZXN0cmljdGlvbiI6IkZvciBlZHVjYXRpb25hbCB1c2Ugb25seSIsImNoZWNrQ29uY3VycmVudFVzZSI6ZmFsc2UsInByb2R1Y3RzIjpbeyJjb2RlIjoiSUkiLCJwYWlkVXBUbyI6IjIwMTgtMTAtMTQifSx7ImNvZGUiOiJSUzAiLCJwYWlkVXBUbyI6IjIwMTgtMTAtMTQifSx7ImNvZGUiOiJXUyIsInBhaWRVcFRvIjoiMjAxOC0xMC0xNCJ9LHsiY29kZSI6IlJEIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In0seyJjb2RlIjoiUkMiLCJwYWlkVXBUbyI6IjIwMTgtMTAtMTQifSx7ImNvZGUiOiJEQyIsInBhaWRVcFRvIjoiMjAxOC0xMC0xNCJ9LHsiY29kZSI6IkRCIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In0seyJjb2RlIjoiUk0iLCJwYWlkVXBUbyI6IjIwMTgtMTAtMTQifSx7ImNvZGUiOiJETSIsInBhaWRVcFRvIjoiMjAxOC0xMC0xNCJ9LHsiY29kZSI6IkFDIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In0seyJjb2RlIjoiRFBOIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In0seyJjb2RlIjoiUFMiLCJwYWlkVXBUbyI6IjIwMTgtMTAtMTQifSx7ImNvZGUiOiJDTCIsInBhaWRVcFRvIjoiMjAxOC0xMC0xNCJ9LHsiY29kZSI6IlBDIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In0seyJjb2RlIjoiUlNVIiwicGFpZFVwVG8iOiIyMDE4LTEwLTE0In1dLCJoYXNoIjoiNjk0NDAzMi8wIiwiZ3JhY2VQZXJpb2REYXlzIjowLCJhdXRvUHJvbG9uZ2F0ZWQiOmZhbHNlLCJpc0F1dG9Qcm9sb25nYXRlZCI6ZmFsc2V9-Gbb7jeR8JWOVxdUFaXfJzVU/O7c7xHQyaidCnhYLp7v32zdeXiHUU7vlrrm5y9ZX0lmQk3plCCsW+phrC9gGAPd6WDKhkal10qVNg0larCR2tQ3u8jfv1t2JAvWrMOJfFG9kKsJuw1P4TozZ/E7Qvj1cupf/rldhoOmaXMyABxNN1af1RV3bVhe4FFZe0p7xlIJF/ctZkFK62HYmh8V3AyhUNTzrvK2k+t/tlDJz2LnW7nYttBLHld8LabPlEEjpTHswhzlthzhVqALIgvF0uNbIJ5Uwpb7NqR4U/2ob0Z+FIcRpFUIAHEAw+RLGwkCge5DyZKfx+RoRJ/In4q/UpA==-MIIEPjCCAiagAwIBAgIBBTANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMB4XDTE1MTEwMjA4MjE0OFoXDTE4MTEwMTA4MjE0OFowETEPMA0GA1UEAwwGcHJvZDN5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxcQkq+zdxlR2mmRYBPzGbUNdMN6OaXiXzxIWtMEkrJMO/5oUfQJbLLuMSMK0QHFmaI37WShyxZcfRCidwXjot4zmNBKnlyHodDij/78TmVqFl8nOeD5+07B8VEaIu7c3E1N+e1doC6wht4I4+IEmtsPAdoaj5WCQVQbrI8KeT8M9VcBIWX7fD0fhexfg3ZRt0xqwMcXGNp3DdJHiO0rCdU+Itv7EmtnSVq9jBG1usMSFvMowR25mju2JcPFp1+I4ZI+FqgR8gyG8oiNDyNEoAbsR3lOpI7grUYSvkB/xVy/VoklPCK2h0f0GJxFjnye8NT1PAywoyl7RmiAVRE/EKwIDAQABo4GZMIGWMAkGA1UdEwQCMAAwHQYDVR0OBBYEFGEpG9oZGcfLMGNBkY7SgHiMGgTcMEgGA1UdIwRBMD+AFKOetkhnQhI2Qb1t4Lm0oFKLl/GzoRykGjAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBggkA0myxg7KDeeEwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4ICAQC9WZuYgQedSuOc5TOUSrRigMw4/+wuC5EtZBfvdl4HT/8vzMW/oUlIP4YCvA0XKyBaCJ2iX+ZCDKoPfiYXiaSiH+HxAPV6J79vvouxKrWg2XV6ShFtPLP+0gPdGq3x9R3+kJbmAm8w+FOdlWqAfJrLvpzMGNeDU14YGXiZ9bVzmIQbwrBA+c/F4tlK/DV07dsNExihqFoibnqDiVNTGombaU2dDup2gwKdL81ua8EIcGNExHe82kjF4zwfadHk3bQVvbfdAwxcDy4xBjs3L4raPLU3yenSzr/OEur1+jfOxnQSmEcMXKXgrAQ9U55gwjcOFKrgOxEdek/Sk1VfOjvS+nuM4eyEruFMfaZHzoQiuw4IqgGc45ohFH0UUyjYcuFxxDSU9lMCv8qdHKm+wnPRb0l9l5vXsCBDuhAGYD6ss+Ga+aDY6f/qXZuUCEUOH3QUNbbCUlviSz6+GiRnt1kA9N2Qachl+2yBfaqUqr8h7Z2gsx5LcIf5kYNsqJ0GavXTVyWh7PYiKX4bs354ZQLUwwa/cG++2+wNWP+HtBhVxMRNTdVhSm38AknZlD+PTAsWGu9GyLmhti2EnVwGybSD2Dxmhxk3IPCkhKAK+pl0eWYGZWG3tJ9mZ7SowcXLWDFAk0lRJnKGFMTggrWjV8GYpw5bq23VmIqqDLgkNzuoog==
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>git常见问题</title><link>/2016/08/26/git%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/</link><pubDate>Fri, 26 Aug 2016 00:00:00 +0000</pubDate><guid>/2016/08/26/git%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="1git删除文件"&gt;1.git删除文件&lt;/h2&gt;
&lt;p&gt;当我们想要删除某个文件,直接删除是没有用的.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@vultr hexo]# git status
# On branch master
nothing to commit (working directory clean)
[root@vultr hexo]# rm source/_posts/test.txt
rm: remove regular empty file `source/_posts/test.txt&amp;#39;? y
[root@vultr hexo]# git status
# On branch master
# Changed but not updated:
# (use &amp;#34;git add/rm &amp;lt;file&amp;gt;...&amp;#34; to update what will be committed)
# (use &amp;#34;git checkout -- &amp;lt;file&amp;gt;...&amp;#34; to discard changes in working directory)
#
# deleted: source/_posts/test.txt
#
no changes added to commit (use &amp;#34;git add&amp;#34; and/or &amp;#34;git commit -a&amp;#34;)
[root@vultr hexo]# git add .
[root@vultr hexo]# git commit -m &amp;#34;delete test file &amp;#34;
# On branch master
# Changed but not updated:
# (use &amp;#34;git add/rm &amp;lt;file&amp;gt;...&amp;#34; to update what will be committed)
# (use &amp;#34;git checkout -- &amp;lt;file&amp;gt;...&amp;#34; to discard changes in working directory)
#
# deleted: source/_posts/test.txt
#
no changes added to commit (use &amp;#34;git add&amp;#34; and/or &amp;#34;git commit -a&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;直接使用&lt;code&gt;rm&lt;/code&gt; 删除的仅仅是工作区的文件,并没有删除暂存区,所以&lt;code&gt;commit&lt;/code&gt; 不会生效. 由于平时使用基本都是修改或者增加新文件,大家习惯用 &lt;code&gt;git add &lt;/code&gt; 命令,而忽略了 &lt;code&gt;git rm&lt;/code&gt; , 其实在&lt;code&gt;git status&lt;/code&gt; 都有提示,仔细看看就知道了.&lt;/p&gt;</description></item><item><title>Gitlab CI</title><link>/2016/07/22/gitlab-ci/</link><pubDate>Fri, 22 Jul 2016 00:00:00 +0000</pubDate><guid>/2016/07/22/gitlab-ci/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="gitlab-ci-cd"&gt;GitLab CI CD&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;持续集成(Continuous Integration)&lt;/strong&gt; , 合并存储库中的更改之前触发管道来构建，测试和验证新代码。就是先测试和验证代码，然后再合并。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;持续交付(Continuous Delivery）&lt;/strong&gt; 将CI验证的代码&lt;strong&gt;交付&lt;/strong&gt;给您的应用程序。就是将合并的代码更新部署到线上。&lt;/p&gt;
&lt;h2 id="cicd的优点"&gt;CI/CD的优点：&lt;/h2&gt;
&lt;h3 id="持续集成"&gt;持续集成&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;尽快发现错误：&lt;/strong&gt; 在开发人员重新思考的同时解决问题&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;减少集成问题：&lt;/strong&gt; 更小的问题更容易消化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免复杂的问题：&lt;/strong&gt; 使团队更加自信地发展更快&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="持续交付"&gt;持续交付&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;确保每个更改都是可发布的：&lt;/strong&gt; 在完成之前测试所有内容，包括部署&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;降低每次发布的风险：&lt;/strong&gt; 使发布简单&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更加频繁地交付价值：&lt;/strong&gt; 可靠的部署意味着更多的发布&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;紧密的客户反馈循环：&lt;/strong&gt; 快速而频繁的客户对变更的反馈&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参考：https://about.gitlab.com/product/continuous-integration/&lt;/p&gt;
&lt;h2 id="gitlab-runner"&gt;GitLab Runner&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;GitLab Runner&lt;/strong&gt; 是一个处理构建的应用程序。它可以单独部署，并通过API与GitLab CI / CD一起使用。为了让你提交的代码自动执行构建，测试和部署等功能，你需要有一个可以执行构建测试和部署的服务，Gitlab Runner就是这么一个服务。&lt;/p&gt;
&lt;h3 id="install"&gt;install&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;添加官方的安装源&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# For Debian/Ubuntu/Mint
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
# For RHEL/CentOS/Fedora
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
&lt;li&gt;安装gitlab-runner&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# For Debian/Ubuntu/Mint
sudo apt-get install gitlab-runner
# For RHEL/CentOS/Fedora
sudo yum install gitlab-runner
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="3"&gt;
&lt;li&gt;修改配置，链接到Gitlab&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Run the following command:
sudo gitlab-runner register
Enter your GitLab instance URL:
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
https://gitlab.com
Enter the token you obtained to register the Runner:
Please enter the gitlab-ci token for this runner
xxx
Enter a description for the Runner, you can change this later in GitLab’s UI:
Please enter the gitlab-ci description for this runner
[hostname] my-runner
Enter the tags associated with the Runner, you can change this later in GitLab’s UI:
Please enter the gitlab-ci tags for this runner (comma separated):
my-tag,another-tag
Enter the Runner executor:
Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
docker
If you chose Docker as your executor, you’ll be asked for the default image to be used for projects that do not define one in .gitlab-ci.yml:
Please enter the Docker image (eg. ruby:2.1):
alpine:latest
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;参考：&lt;/p&gt;</description></item><item><title>在gitlab上搭建hexo博客</title><link>/2016/07/17/%E5%9C%A8gitlab%E4%B8%8A%E6%90%AD%E5%BB%BAhexo%E5%8D%9A%E5%AE%A2/</link><pubDate>Sun, 17 Jul 2016 00:00:00 +0000</pubDate><guid>/2016/07/17/%E5%9C%A8gitlab%E4%B8%8A%E6%90%AD%E5%BB%BAhexo%E5%8D%9A%E5%AE%A2/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;这篇文章讲解怎样用hexo把你写的Markdown快速搭建成博客(呐，你正在看的这篇博客呢就是一个Markdown写出来的 &lt;code&gt;^_^&lt;/code&gt; )。&lt;/p&gt;
&lt;h2 id="hexo"&gt;hexo&lt;/h2&gt;
&lt;h3 id="1什么是hexo"&gt;1.什么是hexo？&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://hexo.io/"&gt;Hexo&lt;/a&gt; 是一个快速、简洁且高效的博客框架。Hexo 使用 &lt;a href="http://daringfireball.net/projects/markdown/"&gt;Markdown&lt;/a&gt;（或其他渲染引擎）解析文章，在几秒内，即可利用靓丽的主题生成静态网页。&lt;/p&gt;
&lt;h3 id="2安装前提"&gt;2.安装前提&lt;/h3&gt;
&lt;p&gt;安装 Hexo 相当简单。然而在安装前，您必须检查电脑中是否已安装下列应用程序：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://nodejs.org/"&gt;Node.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://git-scm.com/"&gt;Git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3安装"&gt;3.安装&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;就是这么简单！&lt;/p&gt;
&lt;h3 id="4使用"&gt;4.使用&lt;/h3&gt;
&lt;p&gt;简单三部曲：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hexo init blog
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd blog &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm install
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hexo server
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1.初始化一个hexo 取名blog&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2.进入blog 构建要用到的模块包&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 3.启动服务, 打开浏览器输入：127.0.0.1:3000 看看吧&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;就这样，你没有写任何文档这个博客也就起来了。&lt;/p&gt;
&lt;p&gt;这个初始化的hexo下面，目录结构URU型下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.
├── _config.yml #配置信息
├── package.json #应用程序的信息，npm install就是安装这些应用，生成node_modules目录
├── scaffolds
├── source
| ├── _drafts
| └── _posts #资源文件
└── themes #主题
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果你想要把你的Markdown内容放入这个博客，简单，将你写的Markdown放入&lt;code&gt;blog/source/_post&lt;/code&gt; 目录中即可。&lt;/p&gt;
&lt;h3 id="5生成器"&gt;5.生成器&lt;/h3&gt;
&lt;p&gt;上面的 &lt;code&gt;hexo server&lt;/code&gt; 只是在你的本地主机上运行了一个临时的博客，方便你看效果，如果没有问题那你就把他生成静态文件，放到任何地方做成博客啦。那在生成静态文件的时候，有一些配置需要配好。&lt;/p&gt;</description></item><item><title>keras回调函数Callbacks</title><link>/2016/07/16/keras%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0callbacks/</link><pubDate>Sat, 16 Jul 2016 00:00:00 +0000</pubDate><guid>/2016/07/16/keras%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0callbacks/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="回调函数使用"&gt;回调函数使用&lt;/h2&gt;
&lt;p&gt;回调函数是一个函数的合集，会在训练的阶段中所使用。你可以使用回调函数来查看训练模型的内在状态和统计。你可以传递一个列表的回调函数（作为 &lt;code&gt;callbacks&lt;/code&gt; 关键字参数）到 &lt;code&gt;Sequential&lt;/code&gt;或 &lt;code&gt;Model&lt;/code&gt; 类型的 &lt;code&gt;.fit()&lt;/code&gt; 方法。在训练时，相应的回调函数的方法就会在各自的阶段被调用。&lt;/p&gt;
&lt;h3 id="callback"&gt;Callback&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;keras.callbacks.Callback()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用来组建新的回调函数的抽象基类。&lt;/p&gt;
&lt;h3 id="baselogger"&gt;BaseLogger&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;keras.callbacks.BaseLogger()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;会积累训练轮平均评估的回调函数。&lt;/p&gt;
&lt;p&gt;这个回调函数被自动应用到每一个 Keras 模型上面。&lt;/p&gt;
&lt;h3 id="terminateonnan"&gt;TerminateOnNaN&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;keras.callbacks.TerminateOnNaN()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当遇到 NaN 损失会停止训练的回调函数。&lt;/p&gt;
&lt;h3 id="progbarlogger"&gt;ProgbarLogger&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;keras.callbacks.ProgbarLogger(count_mode=&amp;#39;samples&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;会把评估以标准输出打印的回调函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;count_mode&lt;/strong&gt;: &amp;ldquo;steps&amp;rdquo; 或者 &amp;ldquo;samples&amp;rdquo;。 进度条是否应该计数看见的样本或步骤（批量）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;触发&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ValueError&lt;/strong&gt;: 防止不正确的 &lt;code&gt;count_mode&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="history"&gt;History&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;keras.callbacks.History()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;把所有事件都记录到 &lt;code&gt;History&lt;/code&gt; 对象的回调函数。&lt;/p&gt;
&lt;p&gt;这个回调函数被自动启用到每一个 Keras 模型。&lt;code&gt;History&lt;/code&gt; 对象会被模型的 &lt;code&gt;fit&lt;/code&gt; 方法返回。&lt;/p&gt;
&lt;h3 id="modelcheckpoint"&gt;ModelCheckpoint&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;keras.callbacks.ModelCheckpoint(filepath, monitor=&amp;#39;val_loss&amp;#39;, verbose=0, save_best_only=False, save_weights_only=False, mode=&amp;#39;auto&amp;#39;, period=1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在每个训练期之后保存模型。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;filepath&lt;/code&gt; 可以包括命名格式选项，可以由 &lt;code&gt;epoch&lt;/code&gt; 的值和 &lt;code&gt;logs&lt;/code&gt; 的键（由 &lt;code&gt;on_epoch_end&lt;/code&gt; 参数传递）来填充。&lt;/p&gt;
&lt;p&gt;例如：如果 &lt;code&gt;filepath&lt;/code&gt; 是 &lt;code&gt;weights.{epoch:02d}-{val_loss:.2f}.hdf5&lt;/code&gt;， 那么模型被保存的的文件名就会有训练轮数和验证损失。&lt;/p&gt;
&lt;h3 id="earlystopping"&gt;EarlyStopping&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;keras.callbacks.EarlyStopping(monitor=&amp;#39;val_loss&amp;#39;, min_delta=0, patience=0, verbose=0, mode=&amp;#39;auto&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当被监测的数量不再提升，则停止训练。&lt;/p&gt;</description></item><item><title>gitlab-pages配置说明</title><link>/2016/07/15/gitlab-pages%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E/</link><pubDate>Fri, 15 Jul 2016 00:00:00 +0000</pubDate><guid>/2016/07/15/gitlab-pages%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="pages-介绍"&gt;pages 介绍&lt;/h3&gt;
&lt;p&gt;gitlab-pages是gitlab-ci的一个功能. 它上传静态页面文件到gitlab中, gitlab就使用这些静态页面作为网站展示出来.&lt;/p&gt;
&lt;p&gt;gitlab-pages 功能:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Create websites for your GitLab projects, groups, or user account.
# 为你的gitlab项目搭建网站
Use any static website generator: Jekyll, Middleman, Hexo, Hugo, Pelican, and more.
# 可以使用各种网站静态生成器
Connect your custom domain(s) and TLS certificates.
# 自定义域名 以及支持TLS证书安全保证
Host your static websites on GitLab.com for free, or on your own GitLab instance.
# 你的网站可以免费放到gitlab上
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="pages-配置"&gt;pages 配置&lt;/h3&gt;
&lt;p&gt;搭建pages必须满足两个条件&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;所有的静态内容必须放到&lt;code&gt;public&lt;/code&gt; 目录下&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.gitlab-ci.yml&lt;/code&gt; 文件必须配置 &lt;code&gt;artifacts&lt;/code&gt; 选项的&lt;code&gt;paths&lt;/code&gt; 参数为&lt;code&gt;public&lt;/code&gt;&lt;/p&gt;</description></item><item><title>keras图像预处理</title><link>/2016/07/15/keras%E5%9B%BE%E5%83%8F%E9%A2%84%E5%A4%84%E7%90%86/</link><pubDate>Fri, 15 Jul 2016 00:00:00 +0000</pubDate><guid>/2016/07/15/keras%E5%9B%BE%E5%83%8F%E9%A2%84%E5%A4%84%E7%90%86/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="class-imagedatagenerator"&gt;class: ImageDataGenerator&lt;/h2&gt;
&lt;p&gt;生成批次的带实时数据增益的张量图像数据。数据将按批次无限循环。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;keras.preprocessing.image.ImageDataGenerator(featurewise_center=False,
samplewise_center=False,
featurewise_std_normalization=False,
samplewise_std_normalization=False,
zca_whitening=False,
zca_epsilon=1e-6,
rotation_range=0.,
width_shift_range=0.,
height_shift_range=0.,
shear_range=0.,
zoom_range=0.,
channel_shift_range=0.,
fill_mode=&amp;#39;nearest&amp;#39;,
cval=0.,
horizontal_flip=False,
vertical_flip=False,
rescale=None,
preprocessing_function=None,
data_format=K.image_data_format())
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;常用参数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rotation_range: 整数。随机旋转的度数范围。
width_shift_range: 浮点数（总宽度的比例）。随机水平移动的范围。
height_shift_range: 浮点数（总高度的比例）。随机垂直移动的范围。
shear_range: 浮点数。剪切强度（以弧度逆时针方向剪切角度）。
zoom_range: 浮点数 或 [lower, upper]。随机缩放范围。如果是浮点数，[lower, upper] = [1-zoom_range, 1+zoom_range]。
channel_shift_range: 浮点数。随机通道转换的范围。
fill_mode: {&amp;#34;constant&amp;#34;, &amp;#34;nearest&amp;#34;, &amp;#34;reflect&amp;#34; or &amp;#34;wrap&amp;#34;} 之一。输入边界以外的点根据给定的模式填充：
&amp;#34;constant&amp;#34;: kkkkkkkk|abcd|kkkkkkkk (cval=k)
&amp;#34;nearest&amp;#34;: aaaaaaaa|abcd|dddddddd
&amp;#34;reflect&amp;#34;: abcddcba|abcd|dcbaabcd
&amp;#34;wrap&amp;#34;: abcdabcd|abcd|abcdabcd
cval: 浮点数或整数。当 fill_mode = &amp;#34;constant&amp;#34; 时，用于边界之外的点的值。
horizontal_flip: 布尔值。随机水平翻转。
vertical_flip: 布尔值。随机垂直翻转。
rescale: 重缩放因子。默认为 None。如果是 None 或 0，不进行缩放，否则将数据乘以所提供的值（在应用任何其他转换之前）。
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="类的方法"&gt;类的方法&lt;/h2&gt;
&lt;p&gt;类的方法我就用了两个：&lt;code&gt;flow&lt;/code&gt; 和&lt;code&gt;flow_from_directory&lt;/code&gt;&lt;/p&gt;</description></item><item><title>gitlab</title><link>/2016/07/12/gitlab/</link><pubDate>Tue, 12 Jul 2016 00:00:00 +0000</pubDate><guid>/2016/07/12/gitlab/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="gitlab"&gt;GitLab&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;GitLab&lt;/strong&gt;是用Ruby开发的完全免费的开源软件，按照 MIT 许可证分发的Git仓库管理工具，且具有wiki和issue跟踪功能。特别适合公司搭建自己的Git仓库，可局域网使用。&lt;/p&gt;
&lt;h2 id="install"&gt;install&lt;/h2&gt;
&lt;p&gt;如果不是特大公司，千人以上的开发人员。中小型企业直接使用docker运行gitlab基本是足够的了，公司使用两三年了，基本没有遇到什么问题。&lt;/p&gt;
&lt;p&gt;运行示例：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker pull gitlab-ce:9.5.10-ce.0
docker run --detach \
--hostname git.domain.com \
--env GITLAB_OMNIBUS_CONFIG=&amp;#34;external_url &amp;#39;https://git.domain.com/&amp;#39;;&amp;#34; \
--env GITLAB_HOST=&amp;#34;git.domain.com&amp;#34; \
--publish 80:80 \
--publish 2289:22 \
--name gitlab \
--restart always \
--volume /gitlab/config:/etc/gitlab \
--volume /gitlab/logs:/var/log/gitlab \
--volume /gitlab/data:/var/opt/gitlab \
--log-opt max-size=100m --log-opt max-file=10 \
gitlab-ce:9.5.10-ce.0
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="backup"&gt;backup&lt;/h2&gt;
&lt;p&gt;备份很简单，&lt;code&gt;itlab-rake gitlab:backup:create&lt;/code&gt; 即可搞定，备份以&lt;code&gt;tar&lt;/code&gt; 压缩包保存。格式如下：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;1545649962_2017_12_24_9.5.10_gitlab_backup.tar&lt;/code&gt; ，中间有备份时间和gitlab版本。docker备份：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker exec -t gitlab gitlab-rake gitlab:backup:create
ls /gitlab/data/backups/1545649962_2017_12_24_9.5.10_gitlab_backup.tar
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="restore"&gt;restore&lt;/h2&gt;
&lt;p&gt;恢复必须在相同的gitlab版本上面，比如上面的例子是在 9.5.10&lt;/p&gt;</description></item><item><title>运维自动化工具Ansible</title><link>/2016/07/02/%E8%BF%90%E7%BB%B4%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7ansible/</link><pubDate>Sat, 02 Jul 2016 00:00:00 +0000</pubDate><guid>/2016/07/02/%E8%BF%90%E7%BB%B4%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7ansible/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="一ansible概述"&gt;一、Ansible概述&lt;/h1&gt;
&lt;p&gt;Ansible是今年来越来越火的一款开源运维自动化工具，通过Ansible可以实现运维自动化，提高运维工程师的工作效率，减少人为失误。Ansible通过本身集成的非常丰富的模块可以实现各种管理任务，其自带模块超过上千个。更为重要的是，它操作非常简单，即使小白也可以轻松上手，但它提供的功能又非常丰富，在运维领域，几乎可以做任何事。&lt;/p&gt;
&lt;h2 id="1ansible特点"&gt;1、Ansible特点&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Ansible自2012年发布以来，很快在全球流行，其特点如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ansible基于Python开发，运维工程师对其二次开发相对比较容易；&lt;/li&gt;
&lt;li&gt;Ansible丰富的内置模块，几乎可以满足一切要求；&lt;/li&gt;
&lt;li&gt;管理模式非常简单，一条命令可以影响上千台主机；&lt;/li&gt;
&lt;li&gt;无客户端模式，底层通过SSH通信；&lt;/li&gt;
&lt;li&gt;Ansible发布后，也陆续被AWS、Google Cloud Platform、Microsoft Azure、Cisco、HP、VMware、Twitter等大公司接纳并投入使用；&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h1 id="二ansible的角色"&gt;二、Ansible的角色&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;使用者：如何使用Ansible实现自动化运维？&lt;/li&gt;
&lt;li&gt;Ansible工具集：Ansible可以实现的功能？&lt;/li&gt;
&lt;li&gt;作用对象：Ansible可以影响哪些主机？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="1使用者"&gt;1、使用者&lt;/h2&gt;
&lt;p&gt;如下图所示：Ansible使用者可以采用多种方式和Ansible交互，图中展示了四种方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CMDB：CMDB存储和管理者企业IT架构中的各项配置信息，是构建ITIL项目的核心工具，运维人员可以组合CMDB和Ansible，通过CMDB直接下发指令调用Ansible工具集完成操作者所希望达到的目标；&lt;/li&gt;
&lt;li&gt;PUBLIC/PRIVATE方式：Ansible除了丰富的内置模块外，同时还提供丰富的API语言接口，如PHP、Python、PERL等多种流行语言，基于PUBLIC/PRIVATE，Ansible以API调用的方式运行；&lt;/li&gt;
&lt;li&gt;Ad-Hoc命令集：Users直接通过Ad-Hoc命令集调用Ansible工具集来完成任务；&lt;/li&gt;
&lt;li&gt;Playbooks：Users预先编写好Ansible Playbooks，通过执行
Playbooks中预先编排好的任务集，按序执行任务；
&lt;img alt="简单聊一聊Ansible自动化运维" loading="lazy" src="/2016/07/02/%E8%BF%90%E7%BB%B4%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B7%A5%E5%85%B7ansible/1dd392c497a0154708e000017ef378b0.png"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="2ansible工具集"&gt;2、Ansible工具集&lt;/h2&gt;
&lt;p&gt;Ansible工具集包含Inventory、Modules、Plugins和API。其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Inventory：用来管理设备列表，可以通过分组实现，对组的调用直接影响组内的所有主机；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Modules：是各种执行模块，几乎所有的管理任务都是通过模块执行的；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Plugins：提供了各种附加功能；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;API：为编程人员提供一个接口，可以基于此做Ansible的二次开发；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;具体表现如下：&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Ansible Playbooks：任务脚本，编排定义Ansible任务及的配置文件，由Ansible按序依次执行，通常是JSON格式的YML文件；&lt;/li&gt;
&lt;li&gt;Inventory：Ansible管理主机清单；&lt;/li&gt;
&lt;li&gt;Modules：Ansible执行命令功能模块，多数为内置的核心模块，也可自定义；&lt;/li&gt;
&lt;li&gt;Plugins：模块功能的补充，如连接类型插件、循环插件、变量插件、过滤插件等，该功能不太常用；&lt;/li&gt;
&lt;li&gt;API：供第三方程序调用的应用程序编程接口；&lt;/li&gt;
&lt;li&gt;Ansible：该部分图中表现得不太明显，组合Inventory、API、Modules、Plugins可以理解为是Ansible命令工具，其为核心执行工具；&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id="3作用对象"&gt;3、作用对象&lt;/h2&gt;
&lt;p&gt;Ansible的作用对象不仅仅是Linux和非Linux操作系统的主机，也可以作用于各类PUBLIC/PRIVATE、商业和非商业设备的网络设施。&lt;/p&gt;
&lt;p&gt;使用者使用Ansible或Ansible-Playbooks时，在服务器终端输入Ansible的Ad-Hoc命令集或Playbooks后，Ansible会遵循预选安排的规则将Playbooks逐步拆解为Play，再将Play组织成Ansible可以识别的任务，随后调用任务涉及的所有模块和插件，根据Inventory中定义的主机列表通过SSH将任务集以临时文件或命令的形式传输到远程客户端执行并返回执行结果，如果是临时文件则执行完毕后自动删除。&lt;/p&gt;
&lt;h1 id="三ansible的配置"&gt;三、Ansible的配置&lt;/h1&gt;
&lt;h2 id="1ansible安装"&gt;1、Ansible安装&lt;/h2&gt;
&lt;p&gt;Ansible的安装部署非常简单，以RPM安装为例，其依赖软件只有Python和SSH，且系统默认均已安装。Ansible的管理端只能是Linux，如Redhat、Debian、Centos。&lt;/p&gt;
&lt;h3 id="1通过yum安装ansible"&gt;1）通过YUM安装Ansible&lt;/h3&gt;
&lt;p&gt;可以自行从互联网上直接下载Ansible所需软件包&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@centos01 ~]# cd /mnt/ansiblerepo/ansiblerepo/repodata/
[root@centos01 ansiblerepo]# vim /etc/yum.repos.d/local.repo
[local]
name=centos
baseurl=file:///mnt/ansiblerepo/ansiblerepo &amp;lt;!--修改yum路径--&amp;gt;
enabled=1
gpgcheck=0
[root@centos01 ~]# yum -y install ansible
&amp;lt;!--安装Ansible自动化运维工具--&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2验证安装结果"&gt;2）验证安装结果&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@centos01 ~]# ansible --version
&amp;lt;!--如果命令可以正常执行，则表示Ansible工具安装成功--&amp;gt;
ansible 2.3.1.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
python version = 2.7.5 (default, Nov 6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3创建ssh免交互登录"&gt;3）创建SSH免交互登录&lt;/h3&gt;
&lt;p&gt;Ansible通过SSH对设备进行管理，而SSH包含两种认证方式：一种是通过密码认证，另一种是通过密钥对验证。前者必须和系统交互，而后者是免交互登录。如果希望通过Ansible自动管理设备，应该配置为免交互登录被管理设备。&lt;/p&gt;</description></item><item><title>Python异步aiohttp</title><link>/2016/07/01/python%E5%BC%82%E6%AD%A5aiohttp/</link><pubDate>Fri, 01 Jul 2016 00:00:00 +0000</pubDate><guid>/2016/07/01/python%E5%BC%82%E6%AD%A5aiohttp/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;&lt;code&gt;asyncio&lt;/code&gt;可以实现单线程并发IO操作。如果仅用在客户端，发挥的威力不大。如果把&lt;code&gt;asyncio&lt;/code&gt;用在服务器端，例如Web服务器，由于HTTP连接就是IO操作，因此可以用单线程+&lt;code&gt;coroutine&lt;/code&gt;实现多用户的高并发支持。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;asyncio&lt;/code&gt;实现了TCP、UDP、SSL等协议，&lt;code&gt;aiohttp&lt;/code&gt;则是基于&lt;code&gt;asyncio&lt;/code&gt;实现的HTTP框架。&lt;/p&gt;
&lt;p&gt;我们先安装&lt;code&gt;aiohttp&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pip install aiohttp
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后编写一个HTTP服务器，分别处理以下URL：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt; - 首页返回&lt;code&gt;b'&amp;lt;h1&amp;gt;Index&amp;lt;/h1&amp;gt;'&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/hello/{name}&lt;/code&gt; - 根据URL参数返回文本&lt;code&gt;hello, %s!&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代码如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import asyncio
from aiohttp import web
async def index(request):
await asyncio.sleep(0.5)
return web.Response(body=b&amp;#39;&amp;lt;h1&amp;gt;Index&amp;lt;/h1&amp;gt;&amp;#39;)
async def hello(request):
await asyncio.sleep(0.5)
text = &amp;#39;&amp;lt;h1&amp;gt;hello, %s!&amp;lt;/h1&amp;gt;&amp;#39; % request.match_info[&amp;#39;name&amp;#39;]
return web.Response(body=text.encode(&amp;#39;utf-8&amp;#39;))
async def init(loop):
app = web.Application(loop=loop)
app.router.add_route(&amp;#39;GET&amp;#39;, &amp;#39;/&amp;#39;, index)
app.router.add_route(&amp;#39;GET&amp;#39;, &amp;#39;/hello/{name}&amp;#39;, hello)
srv = await loop.create_server(app.make_handler(), &amp;#39;127.0.0.1&amp;#39;, 8000)
print(&amp;#39;Server started at http://127.0.0.1:8000...&amp;#39;)
return srv
loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注意&lt;code&gt;aiohttp&lt;/code&gt;的初始化函数&lt;code&gt;init()&lt;/code&gt;也是一个&lt;code&gt;coroutine&lt;/code&gt;，&lt;code&gt;loop.create_server()&lt;/code&gt;则利用&lt;code&gt;asyncio&lt;/code&gt;创建TCP服务。&lt;/p&gt;</description></item><item><title>Python异步async await</title><link>/2016/07/01/python%E5%BC%82%E6%AD%A5async-await/</link><pubDate>Fri, 01 Jul 2016 00:00:00 +0000</pubDate><guid>/2016/07/01/python%E5%BC%82%E6%AD%A5async-await/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;用&lt;code&gt;asyncio&lt;/code&gt;提供的&lt;code&gt;@asyncio.coroutine&lt;/code&gt;可以把一个generator标记为coroutine类型，然后在coroutine内部用&lt;code&gt;yield from&lt;/code&gt;调用另一个coroutine实现异步操作。&lt;/p&gt;
&lt;p&gt;为了简化并更好地标识异步IO，从Python 3.5开始引入了新的语法&lt;code&gt;async&lt;/code&gt;和&lt;code&gt;await&lt;/code&gt;，可以让coroutine的代码更简洁易读。&lt;/p&gt;
&lt;p&gt;请注意，&lt;code&gt;async&lt;/code&gt;和&lt;code&gt;await&lt;/code&gt;是针对coroutine的新语法，要使用新的语法，只需要做两步简单的替换：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把&lt;code&gt;@asyncio.coroutine&lt;/code&gt;替换为&lt;code&gt;async&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;把&lt;code&gt;yield from&lt;/code&gt;替换为&lt;code&gt;await&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;让我们对比一下上一节的代码：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;@asyncio.coroutine
def hello():
print(&amp;#34;Hello world!&amp;#34;)
r = yield from asyncio.sleep(1)
print(&amp;#34;Hello again!&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用新语法重新编写如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;async def hello():
print(&amp;#34;Hello world!&amp;#34;)
r = await asyncio.sleep(1)
print(&amp;#34;Hello again!&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;剩下的代码保持不变。&lt;/p&gt;
&lt;h3 id="小结"&gt;小结&lt;/h3&gt;
&lt;p&gt;Python从3.5版本开始为&lt;code&gt;asyncio&lt;/code&gt;提供了&lt;code&gt;async&lt;/code&gt;和&lt;code&gt;await&lt;/code&gt;的新语法；&lt;/p&gt;
&lt;p&gt;注意新语法只能用在Python 3.5以及后续版本，如果使用3.4版本，则仍需使用上一节的方案。&lt;/p&gt;
&lt;h3 id="try-it-"&gt;try it ?&lt;/h3&gt;
&lt;p&gt;使用async异步获取sina、sohu和163的网站首页。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import asyncio
async def wget(host):
print(&amp;#39;wget %s...&amp;#39; % host)
connect = asyncio.open_connection(host, 80)
[reader, writer] = await connect
header = &amp;#39;GET / HTTP/1.0\r\nHost: %s\r\n\r\n&amp;#39; % host
writer.write(header.encode(&amp;#39;utf-8&amp;#39;))
await writer.drain()
while True:
line = await reader.readline()
if line == b&amp;#39;\r\n&amp;#39;:
break
print(&amp;#39;%s header &amp;gt; %s&amp;#39; % (host, line.decode(&amp;#39;utf-8&amp;#39;).rstrip()))
# Ignore the body, close the socket
writer.close()
if __name__ == &amp;#39;__main__&amp;#39;:
loop = asyncio.get_event_loop()
tasks = [wget(host) for host in [&amp;#39;www.sina.com.cn&amp;#39;, &amp;#39;www.sohu.com&amp;#39;, &amp;#39;www.163.com&amp;#39;]]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Python异步asyncio</title><link>/2016/07/01/python%E5%BC%82%E6%AD%A5asyncio/</link><pubDate>Fri, 01 Jul 2016 00:00:00 +0000</pubDate><guid>/2016/07/01/python%E5%BC%82%E6%AD%A5asyncio/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;&lt;code&gt;asyncio&lt;/code&gt;是Python 3.4版本引入的标准库，直接内置了对异步IO的支持。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;asyncio&lt;/code&gt;的编程模型就是一个消息循环。我们从&lt;code&gt;asyncio&lt;/code&gt;模块中直接获取一个&lt;code&gt;EventLoop&lt;/code&gt;的引用，然后把需要执行的协程扔到&lt;code&gt;EventLoop&lt;/code&gt;中执行，就实现了异步IO。&lt;/p&gt;
&lt;p&gt;用&lt;code&gt;asyncio&lt;/code&gt;实现&lt;code&gt;Hello world&lt;/code&gt;代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; asyncio
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@asyncio.coroutine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello world!&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 异步调用asyncio.sleep(1):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; r &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;yield from&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sleep(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello again!&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 获取EventLoop:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loop &lt;span style="color:#f92672"&gt;=&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_event_loop()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 执行coroutine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loop&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run_until_complete(hello())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loop&lt;span style="color:#f92672"&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;@asyncio.coroutine&lt;/code&gt;把一个generator标记为coroutine类型，然后，我们就把这个&lt;code&gt;coroutine&lt;/code&gt;扔到&lt;code&gt;EventLoop&lt;/code&gt;中执行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hello()&lt;/code&gt;会首先打印出&lt;code&gt;Hello world!&lt;/code&gt;，然后，&lt;code&gt;yield from&lt;/code&gt;语法可以让我们方便地调用另一个&lt;code&gt;generator&lt;/code&gt;。由于&lt;code&gt;asyncio.sleep()&lt;/code&gt;也是一个&lt;code&gt;coroutine&lt;/code&gt;，所以线程不会等待&lt;code&gt;asyncio.sleep()&lt;/code&gt;，而是直接中断并执行下一个消息循环。当&lt;code&gt;asyncio.sleep()&lt;/code&gt;返回时，线程就可以从&lt;code&gt;yield from&lt;/code&gt;拿到返回值（此处是&lt;code&gt;None&lt;/code&gt;），然后接着执行下一行语句。&lt;/p&gt;
&lt;p&gt;把&lt;code&gt;asyncio.sleep(1)&lt;/code&gt;看成是一个耗时1秒的IO操作，在此期间，主线程并未等待，而是去执行&lt;code&gt;EventLoop&lt;/code&gt;中其他可以执行的&lt;code&gt;coroutine&lt;/code&gt;了，因此可以实现并发执行。&lt;/p&gt;
&lt;p&gt;我们用Task封装两个&lt;code&gt;coroutine&lt;/code&gt;试试：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; threading
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; asyncio
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@asyncio.coroutine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;Hello world! (&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; threading&lt;span style="color:#f92672"&gt;.&lt;/span&gt;currentThread())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;yield from&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sleep(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;Hello again! (&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; threading&lt;span style="color:#f92672"&gt;.&lt;/span&gt;currentThread())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loop &lt;span style="color:#f92672"&gt;=&lt;/span&gt; asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_event_loop()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tasks &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [hello(), hello()]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loop&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run_until_complete(asyncio&lt;span style="color:#f92672"&gt;.&lt;/span&gt;wait(tasks))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loop&lt;span style="color:#f92672"&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;观察执行过程：&lt;/p&gt;</description></item><item><title>Python协程</title><link>/2016/06/30/python%E5%8D%8F%E7%A8%8B/</link><pubDate>Thu, 30 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/30/python%E5%8D%8F%E7%A8%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;协程，又称微线程。英文名Coroutine。&lt;/p&gt;
&lt;p&gt;协程的概念很早就提出来了，但直到最近几年才在某些语言（如Lua）中得到广泛应用。&lt;/p&gt;
&lt;p&gt;子程序，或者称为函数，在所有语言中都是层级调用，比如A调用B，B在执行过程中又调用了C，C执行完毕返回，B执行完毕返回，最后是A执行完毕。&lt;/p&gt;
&lt;p&gt;所以子程序调用是通过栈实现的，一个线程就是执行一个子程序。&lt;/p&gt;
&lt;p&gt;子程序调用总是一个入口，一次返回，调用顺序是明确的。而协程的调用和子程序不同。&lt;/p&gt;
&lt;p&gt;协程看上去也是子程序，但执行过程中，在子程序内部可中断，然后转而执行别的子程序，在适当的时候再返回来接着执行。&lt;/p&gt;
&lt;p&gt;注意，在一个子程序中中断，去执行其他子程序，不是函数调用，有点类似CPU的中断。比如子程序A、B：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def A():
print(&amp;#39;1&amp;#39;)
print(&amp;#39;2&amp;#39;)
print(&amp;#39;3&amp;#39;)
def B():
print(&amp;#39;x&amp;#39;)
print(&amp;#39;y&amp;#39;)
print(&amp;#39;z&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;假设由协程执行，在执行A的过程中，可以随时中断，去执行B，B也可能在执行过程中中断再去执行A，结果可能是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1
2
x
y
3
z
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是在A中是没有调用B的，所以协程的调用比函数调用理解起来要难一些。&lt;/p&gt;
&lt;p&gt;看起来A、B的执行有点像多线程，但协程的特点在于是一个线程执行，那和多线程比，协程有何优势？&lt;/p&gt;
&lt;p&gt;最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换，而是由程序自身控制，因此，没有线程切换的开销，和多线程比，线程数量越多，协程的性能优势就越明显。&lt;/p&gt;
&lt;p&gt;第二大优势就是不需要多线程的锁机制，因为只有一个线程，也不存在同时写变量冲突，在协程中控制共享资源不加锁，只需要判断状态就好了，所以执行效率比多线程高很多。&lt;/p&gt;
&lt;p&gt;因为协程是一个线程执行，那怎么利用多核CPU呢？最简单的方法是多进程+协程，既充分利用多核，又充分发挥协程的高效率，可获得极高的性能。&lt;/p&gt;
&lt;p&gt;Python对协程的支持是通过generator实现的。&lt;/p&gt;
&lt;p&gt;在generator中，我们不但可以通过&lt;code&gt;for&lt;/code&gt;循环来迭代，还可以不断调用&lt;code&gt;next()&lt;/code&gt;函数获取由&lt;code&gt;yield&lt;/code&gt;语句返回的下一个值。&lt;/p&gt;
&lt;p&gt;但是Python的&lt;code&gt;yield&lt;/code&gt;不但可以返回一个值，它还可以接收调用者发出的参数。&lt;/p&gt;
&lt;p&gt;来看例子：&lt;/p&gt;
&lt;p&gt;传统的生产者-消费者模型是一个线程写消息，一个线程取消息，通过锁机制控制队列和等待，但一不小心就可能死锁。&lt;/p&gt;
&lt;p&gt;如果改用协程，生产者生产消息后，直接通过&lt;code&gt;yield&lt;/code&gt;跳转到消费者开始执行，待消费者执行完毕后，切换回生产者继续生产，效率极高：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;consumer&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; r &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; n &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;yield&lt;/span&gt; r
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; n:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;[CONSUMER] Consuming &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;...&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; n)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; r &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;200 OK&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;produce&lt;/span&gt;(c):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; c&lt;span style="color:#f92672"&gt;.&lt;/span&gt;send(&lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; n &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; n &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; n &lt;span style="color:#f92672"&gt;=&lt;/span&gt; n &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;[PRODUCER] Producing &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;...&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; n)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; r &lt;span style="color:#f92672"&gt;=&lt;/span&gt; c&lt;span style="color:#f92672"&gt;.&lt;/span&gt;send(n)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;[PRODUCER] Consumer return: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; r)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; c&lt;span style="color:#f92672"&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c &lt;span style="color:#f92672"&gt;=&lt;/span&gt; consumer()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;produce(c)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行结果：&lt;/p&gt;</description></item><item><title>Python异步IO</title><link>/2016/06/29/python%E5%BC%82%E6%AD%A5io/</link><pubDate>Wed, 29 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/29/python%E5%BC%82%E6%AD%A5io/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在IO编程一节中，我们已经知道，CPU的速度远远快于磁盘、网络等IO。在一个线程中，CPU执行代码的速度极快，然而，一旦遇到IO操作，如读写文件、发送网络数据时，就需要等待IO操作完成，才能继续进行下一步操作。这种情况称为同步IO。&lt;/p&gt;
&lt;p&gt;在IO操作的过程中，当前线程被挂起，而其他需要CPU执行的代码就无法被当前线程执行了。&lt;/p&gt;
&lt;p&gt;因为一个IO操作就阻塞了当前线程，导致其他代码无法执行，所以我们必须使用多线程或者多进程来并发执行代码，为多个用户服务。每个用户都会分配一个线程，如果遇到IO导致线程被挂起，其他用户的线程不受影响。&lt;/p&gt;
&lt;p&gt;多线程和多进程的模型虽然解决了并发问题，但是系统不能无上限地增加线程。由于系统切换线程的开销也很大，所以，一旦线程数量过多，CPU的时间就花在线程切换上了，真正运行代码的时间就少了，结果导致性能严重下降。&lt;/p&gt;
&lt;p&gt;由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配，多线程和多进程只是解决这一问题的一种方法。&lt;/p&gt;
&lt;p&gt;另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时，它只发出IO指令，并不等待IO结果，然后就去执行其他代码了。一段时间后，当IO返回结果时，再通知CPU进行处理。&lt;/p&gt;
&lt;p&gt;可以想象如果按普通顺序写出的代码实际上是没法完成异步IO的,所以，同步IO模型的代码是无法实现异步IO模型的。&lt;/p&gt;
&lt;p&gt;异步IO模型需要一个消息循环，在消息循环中，主线程不断地重复“读取消息-处理消息”这一过程：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;loop = get_event_loop()
while True:
event = loop.get_event()
process_event(event)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;消息模型其实早在应用在桌面应用程序中了。一个GUI程序的主线程就负责不停地读取消息并处理消息。所有的键盘、鼠标等消息都被发送到GUI程序的消息队列中，然后由GUI程序的主线程处理。&lt;/p&gt;
&lt;p&gt;由于GUI线程处理键盘、鼠标等消息的速度非常快，所以用户感觉不到延迟。某些时候，GUI线程在一个消息处理的过程中遇到问题导致一次消息处理时间过长，此时，用户会感觉到整个GUI程序停止响应了，敲键盘、点鼠标都没有反应。这种情况说明在消息模型中，处理一个消息必须非常迅速，否则，主线程将无法及时处理消息队列中的其他消息，导致程序看上去停止响应。&lt;/p&gt;
&lt;p&gt;消息模型是如何解决同步IO必须等待IO操作这一问题的呢？当遇到IO操作时，代码只负责发出IO请求，不等待IO结果，然后直接结束本轮消息处理，进入下一轮消息处理过程。当IO操作完成后，将收到一条“IO完成”的消息，处理该消息时就可以直接获取IO操作结果。&lt;/p&gt;
&lt;p&gt;在“发出IO请求”到收到“IO完成”的这段时间里，同步IO模型下，主线程只能挂起，但异步IO模型下，主线程并没有休息，而是在消息循环中继续处理其他消息。这样，在异步IO模型下，一个线程就可以同时处理多个IO请求，并且没有切换线程的操作。对于大多数IO密集型的应用程序，使用异步IO将大大提升系统的多任务处理能力。&lt;/p&gt;</description></item><item><title>Python网络编程TCP</title><link>/2016/06/28/python%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8Btcp/</link><pubDate>Tue, 28 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/28/python%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8Btcp/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”，而打开一个Socket需要知道目标计算机的IP地址和端口号，再指定协议类型即可。&lt;/p&gt;
&lt;h3 id="客户端"&gt;客户端&lt;/h3&gt;
&lt;p&gt;大多数连接都是可靠的TCP连接。创建TCP连接时，主动发起连接的叫客户端，被动响应连接的叫服务器。&lt;/p&gt;
&lt;p&gt;举个例子，当我们在浏览器中访问新浪时，我们自己的计算机就是客户端，浏览器会主动向新浪的服务器发起连接。如果一切顺利，新浪的服务器接受了我们的连接，一个TCP连接就建立起来的，后面的通信就是发送网页内容了。&lt;/p&gt;
&lt;p&gt;所以，我们要创建一个基于TCP连接的Socket，可以这样做：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# 导入socket库:
import socket
# 创建一个socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect((&amp;#39;www.sina.com.cn&amp;#39;, 80))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建&lt;code&gt;Socket&lt;/code&gt;时，&lt;code&gt;AF_INET&lt;/code&gt;指定使用IPv4协议，如果要用更先进的IPv6，就指定为&lt;code&gt;AF_INET6&lt;/code&gt;。&lt;code&gt;SOCK_STREAM&lt;/code&gt;指定使用面向流的TCP协议，这样，一个&lt;code&gt;Socket&lt;/code&gt;对象就创建成功，但是还没有建立连接。&lt;/p&gt;
&lt;p&gt;客户端要主动发起TCP连接，必须知道服务器的IP地址和端口号。新浪网站的IP地址可以用域名&lt;code&gt;www.sina.com.cn&lt;/code&gt;自动转换到IP地址，但是怎么知道新浪服务器的端口号呢？&lt;/p&gt;
&lt;p&gt;答案是作为服务器，提供什么样的服务，端口号就必须固定下来。由于我们想要访问网页，因此新浪提供网页服务的服务器必须把端口号固定在&lt;code&gt;80&lt;/code&gt;端口，因为&lt;code&gt;80&lt;/code&gt;端口是Web服务的标准端口。其他服务都有对应的标准端口号，例如SMTP服务是&lt;code&gt;25&lt;/code&gt;端口，FTP服务是&lt;code&gt;21&lt;/code&gt;端口，等等。端口号小于1024的是Internet标准服务的端口，端口号大于1024的，可以任意使用。&lt;/p&gt;
&lt;p&gt;因此，我们连接新浪服务器的代码如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;s.connect((&amp;#39;www.sina.com.cn&amp;#39;, 80))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注意参数是一个&lt;code&gt;tuple&lt;/code&gt;，包含地址和端口号。&lt;/p&gt;
&lt;p&gt;建立TCP连接后，我们就可以向新浪服务器发送请求，要求返回首页的内容：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# 发送数据:
s.send(b&amp;#39;GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;TCP连接创建的是双向通道，双方都可以同时给对方发数据。但是谁先发谁后发，怎么协调，要根据具体的协议来决定。例如，HTTP协议规定客户端必须先发请求给服务器，服务器收到后才发数据给客户端。&lt;/p&gt;
&lt;p&gt;发送的文本格式必须符合HTTP标准，如果格式没问题，接下来就可以接收新浪服务器返回的数据了：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# 接收数据:
buffer = []
while True:
# 每次最多接收1k字节:
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b&amp;#39;&amp;#39;.join(buffer)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接收数据时，调用&lt;code&gt;recv(max)&lt;/code&gt;方法，一次最多接收指定的字节数，因此，在一个while循环中反复接收，直到&lt;code&gt;recv()&lt;/code&gt;返回空数据，表示接收完毕，退出循环。&lt;/p&gt;
&lt;p&gt;当我们接收完数据后，调用&lt;code&gt;close()&lt;/code&gt;方法关闭Socket，这样，一次完整的网络通信就结束了：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# 关闭连接:
s.close()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接收到的数据包括HTTP头和网页本身，我们只需要把HTTP头和网页分离一下，把HTTP头打印出来，网页内容保存到文件：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;header, html = data.split(b&amp;#39;\r\n\r\n&amp;#39;, 1)
print(header.decode(&amp;#39;utf-8&amp;#39;))
# 把接收的数据写入文件:
with open(&amp;#39;sina.html&amp;#39;, &amp;#39;wb&amp;#39;) as f:
f.write(html)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在，只需要在浏览器中打开这个&lt;code&gt;sina.html&lt;/code&gt;文件，就可以看到新浪的首页了。&lt;/p&gt;</description></item><item><title>Python网络编程UDP</title><link>/2016/06/28/python%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8Budp/</link><pubDate>Tue, 28 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/28/python%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8Budp/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;TCP是建立可靠连接，并且通信双方都可以以流的形式发送数据。相对TCP，UDP则是面向无连接的协议。&lt;/p&gt;
&lt;p&gt;使用UDP协议时，不需要建立连接，只需要知道对方的IP地址和端口号，就可以直接发数据包。但是，能不能到达就不知道了。&lt;/p&gt;
&lt;p&gt;虽然用UDP传输数据不可靠，但它的优点是和TCP比，速度快，对于不要求可靠到达的数据，就可以使用UDP协议。&lt;/p&gt;
&lt;p&gt;我们来看看如何通过UDP协议传输数据。和TCP类似，使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定端口:
s.bind((&amp;#39;127.0.0.1&amp;#39;, 9999))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建Socket时，&lt;code&gt;SOCK_DGRAM&lt;/code&gt;指定了这个Socket的类型是UDP。绑定端口和TCP一样，但是不需要调用&lt;code&gt;listen()&lt;/code&gt;方法，而是直接接收来自任何客户端的数据：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;print(&amp;#39;Bind UDP on 9999...&amp;#39;)
while True:
# 接收数据:
data, addr = s.recvfrom(1024)
print(&amp;#39;Received from %s:%s.&amp;#39; % (addr, data))
s.sendto(b&amp;#39;Hello, %s!&amp;#39; % data, addr)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;recvfrom()&lt;/code&gt;方法返回数据和客户端的地址与端口，这样，服务器收到数据后，直接调用&lt;code&gt;sendto()&lt;/code&gt;就可以把数据用UDP发给客户端。&lt;/p&gt;
&lt;p&gt;注意这里省掉了多线程，因为这个例子很简单。&lt;/p&gt;
&lt;p&gt;客户端使用UDP时，首先仍然创建基于UDP的Socket，然后，不需要调用&lt;code&gt;connect()&lt;/code&gt;，直接通过&lt;code&gt;sendto()&lt;/code&gt;给服务器发数据：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b&amp;#39;Superman&amp;#39;, b&amp;#39;Hooby&amp;#39;, b&amp;#39;Huyu&amp;#39;]:
# 发送数据:
s.sendto(data, (&amp;#39;127.0.0.1&amp;#39;, 9999))
# 接收数据:
print(s.recv(1024).decode(&amp;#39;utf-8&amp;#39;))
s.close()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从服务器接收数据仍然调用&lt;code&gt;recv()&lt;/code&gt;方法。&lt;/p&gt;
&lt;p&gt;仍然用两个命令行分别启动服务器和客户端测试，结果如下：&lt;/p&gt;
&lt;p&gt;server:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Bind UDP on 9999...
Received from (&amp;#39;127.0.0.1&amp;#39;, 58531):b&amp;#39;Superman&amp;#39;.
Received from (&amp;#39;127.0.0.1&amp;#39;, 58531):b&amp;#39;Hooby&amp;#39;.
Received from (&amp;#39;127.0.0.1&amp;#39;, 58531):b&amp;#39;Huyu&amp;#39;.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;client:&lt;/p&gt;</description></item><item><title>Python环境之virtualenv</title><link>/2016/06/27/python%E7%8E%AF%E5%A2%83%E4%B9%8Bvirtualenv/</link><pubDate>Mon, 27 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/27/python%E7%8E%AF%E5%A2%83%E4%B9%8Bvirtualenv/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在开发Python应用程序的时候，系统安装的Python3只有一个版本：3.4。所有第三方的包都会被&lt;code&gt;pip&lt;/code&gt;安装到Python3的&lt;code&gt;site-packages&lt;/code&gt;目录下。&lt;/p&gt;
&lt;p&gt;如果我们要同时开发多个应用程序，那这些应用程序都会共用一个Python，就是安装在系统的Python 3。如果应用A需要jinja 2.7，而应用B需要jinja 2.6怎么办？&lt;/p&gt;
&lt;p&gt;这种情况下，每个应用可能需要各自拥有一套“独立”的Python运行环境。virtualenv就是用来为一个应用创建一套“隔离”的Python运行环境。&lt;/p&gt;
&lt;p&gt;首先，我们用&lt;code&gt;pip&lt;/code&gt;安装virtualenv：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ pip3 install virtualenv
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后，假定我们要开发一个新的项目，需要一套独立的Python运行环境，可以这么做：&lt;/p&gt;
&lt;p&gt;第一步，创建目录：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Mac:~ michael$ mkdir myproject
Mac:~ michael$ cd myproject/
Mac:myproject michael$
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;第二步，创建一个独立的Python运行环境，命名为&lt;code&gt;venv&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Mac:myproject michael$ virtualenv --no-site-packages venv
Using base prefix &amp;#39;/usr/local/.../Python.framework/Versions/3.4&amp;#39;
New python executable in venv/bin/python3.4
Also creating executable in venv/bin/python
Installing setuptools, pip, wheel...done.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;命令&lt;code&gt;virtualenv&lt;/code&gt;就可以创建一个独立的Python运行环境，我们还加上了参数&lt;code&gt;--no-site-packages&lt;/code&gt;，这样，已经安装到系统Python环境中的所有第三方包都不会复制过来，这样，我们就得到了一个不带任何第三方包的“干净”的Python运行环境。&lt;/p&gt;
&lt;p&gt;新建的Python环境被放到当前目录下的&lt;code&gt;venv&lt;/code&gt;目录。有了&lt;code&gt;venv&lt;/code&gt;这个Python环境，可以用&lt;code&gt;source&lt;/code&gt;进入该环境：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Mac:myproject michael$ source venv/bin/activate
(venv)Mac:myproject michael$
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注意到命令提示符变了，有个&lt;code&gt;(venv)&lt;/code&gt;前缀，表示当前环境是一个名为&lt;code&gt;venv&lt;/code&gt;的Python环境。&lt;/p&gt;
&lt;p&gt;下面正常安装各种第三方包，并运行&lt;code&gt;python&lt;/code&gt;命令：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(venv)Mac:myproject michael$ pip install jinja2
...
Successfully installed jinja2-2.7.3 markupsafe-0.23
(venv)Mac:myproject michael$ python myapp.py
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在&lt;code&gt;venv&lt;/code&gt;环境下，用&lt;code&gt;pip&lt;/code&gt;安装的包都被安装到&lt;code&gt;venv&lt;/code&gt;这个环境下，系统Python环境不受任何影响。也就是说，&lt;code&gt;venv&lt;/code&gt;环境是专门针对&lt;code&gt;myproject&lt;/code&gt;这个应用创建的。&lt;/p&gt;</description></item><item><title>Python模块之psutil</title><link>/2016/06/26/python%E6%A8%A1%E5%9D%97%E4%B9%8Bpsutil/</link><pubDate>Sun, 26 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/26/python%E6%A8%A1%E5%9D%97%E4%B9%8Bpsutil/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;用Python来编写脚本简化日常的运维工作是Python的一个重要用途。在Linux下，有许多系统命令可以让我们时刻监控系统运行的状态，如&lt;code&gt;ps&lt;/code&gt;，&lt;code&gt;top&lt;/code&gt;，&lt;code&gt;free&lt;/code&gt;等等。要获取这些系统信息，Python可以通过&lt;code&gt;subprocess&lt;/code&gt;模块调用并获取结果。但这样做显得很麻烦，尤其是要写很多解析代码。&lt;/p&gt;
&lt;p&gt;在Python中获取系统信息的另一个好办法是使用&lt;code&gt;psutil&lt;/code&gt;这个第三方模块。顾名思义，psutil = process and system utilities，它不仅可以通过一两行代码实现系统监控，还可以跨平台使用，支持Linux／UNIX／OSX／Windows等，是系统管理员和运维小伙伴不可或缺的必备模块。&lt;/p&gt;
&lt;h3 id="安装psutil"&gt;安装psutil&lt;/h3&gt;
&lt;p&gt;如果安装了Anaconda，psutil就已经可用了。否则，需要在命令行下通过pip安装：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ pip install psutil
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="获取cpu信息"&gt;获取CPU信息&lt;/h3&gt;
&lt;p&gt;我们先来获取CPU的信息：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import psutil
&amp;gt;&amp;gt;&amp;gt; psutil.cpu_count() # CPU逻辑数量
4
&amp;gt;&amp;gt;&amp;gt; psutil.cpu_count(logical=False) # CPU物理核心
2
# 2说明是双核超线程, 4则是4核非超线程
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;统计CPU的用户／系统／空闲时间：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; psutil.cpu_times()
scputimes(user=10963.31, nice=0.0, system=5138.67, idle=356102.45)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;再实现类似&lt;code&gt;top&lt;/code&gt;命令的CPU使用率，每秒刷新一次，累计10次：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; for x in range(10):
... psutil.cpu_percent(interval=1, percpu=True)
...
[14.0, 4.0, 4.0, 4.0]
[12.0, 3.0, 4.0, 3.0]
[8.0, 4.0, 3.0, 4.0]
[12.0, 3.0, 3.0, 3.0]
[18.8, 5.1, 5.9, 5.0]
[10.9, 5.0, 4.0, 3.0]
[12.0, 5.0, 4.0, 5.0]
[15.0, 5.0, 4.0, 4.0]
[19.0, 5.0, 5.0, 4.0]
[9.0, 3.0, 2.0, 3.0]
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="获取内存信息"&gt;获取内存信息&lt;/h3&gt;
&lt;p&gt;使用psutil获取物理内存和交换内存信息，分别使用：&lt;/p&gt;</description></item><item><title>Python模块之chardet</title><link>/2016/06/25/python%E6%A8%A1%E5%9D%97%E4%B9%8Bchardet/</link><pubDate>Sat, 25 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/25/python%E6%A8%A1%E5%9D%97%E4%B9%8Bchardet/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;字符串编码一直是令人非常头疼的问题，尤其是我们在处理一些不规范的第三方网页的时候。虽然Python提供了Unicode表示的&lt;code&gt;str&lt;/code&gt;和&lt;code&gt;bytes&lt;/code&gt;两种数据类型，并且可以通过&lt;code&gt;encode()&lt;/code&gt;和&lt;code&gt;decode()&lt;/code&gt;方法转换，但是，在不知道编码的情况下，对&lt;code&gt;bytes&lt;/code&gt;做&lt;code&gt;decode()&lt;/code&gt;不好做。&lt;/p&gt;
&lt;p&gt;对于未知编码的&lt;code&gt;bytes&lt;/code&gt;，要把它转换成&lt;code&gt;str&lt;/code&gt;，需要先“猜测”编码。猜测的方式是先收集各种编码的特征字符，根据特征字符判断，就能有很大概率“猜对”。&lt;/p&gt;
&lt;p&gt;当然，我们肯定不能从头自己写这个检测编码的功能，这样做费时费力。chardet这个第三方库正好就派上了用场。用它来检测编码，简单易用。&lt;/p&gt;
&lt;h3 id="安装chardet"&gt;安装chardet&lt;/h3&gt;
&lt;p&gt;如果安装了Anaconda，chardet就已经可用了。否则，需要在命令行下通过pip安装：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ pip install chardet
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="使用chardet"&gt;使用chardet&lt;/h3&gt;
&lt;p&gt;当我们拿到一个&lt;code&gt;bytes&lt;/code&gt;时，就可以对其检测编码。用chardet检测编码，只需要一行代码：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; chardet.detect(b&amp;#39;Hello, world!&amp;#39;)
{&amp;#39;encoding&amp;#39;: &amp;#39;ascii&amp;#39;, &amp;#39;confidence&amp;#39;: 1.0, &amp;#39;language&amp;#39;: &amp;#39;&amp;#39;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;检测出的编码是&lt;code&gt;ascii&lt;/code&gt;，注意到还有个&lt;code&gt;confidence&lt;/code&gt;字段，表示检测的概率是1.0（即100%）。&lt;/p&gt;
&lt;p&gt;我们来试试检测GBK编码的中文：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; data = &amp;#39;离离原上草，一岁一枯荣&amp;#39;.encode(&amp;#39;gbk&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; chardet.detect(data)
{&amp;#39;encoding&amp;#39;: &amp;#39;GB2312&amp;#39;, &amp;#39;confidence&amp;#39;: 0.7407407407407407, &amp;#39;language&amp;#39;: &amp;#39;Chinese&amp;#39;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;检测的编码是&lt;code&gt;GB2312&lt;/code&gt;，注意到GBK是GB2312的超集，两者是同一种编码，检测正确的概率是74%，&lt;code&gt;language&lt;/code&gt;字段指出的语言是&lt;code&gt;'Chinese'&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;对UTF-8编码进行检测：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; data = &amp;#39;离离原上草，一岁一枯荣&amp;#39;.encode(&amp;#39;utf-8&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; chardet.detect(data)
{&amp;#39;encoding&amp;#39;: &amp;#39;utf-8&amp;#39;, &amp;#39;confidence&amp;#39;: 0.99, &amp;#39;language&amp;#39;: &amp;#39;&amp;#39;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们再试试对日文进行检测：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; data = &amp;#39;最新の主要ニュース&amp;#39;.encode(&amp;#39;euc-jp&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; chardet.detect(data)
{&amp;#39;encoding&amp;#39;: &amp;#39;EUC-JP&amp;#39;, &amp;#39;confidence&amp;#39;: 0.99, &amp;#39;language&amp;#39;: &amp;#39;Japanese&amp;#39;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可见，用chardet检测编码，使用简单。获取到编码后，再转换为&lt;code&gt;str&lt;/code&gt;，就可以方便后续处理。&lt;/p&gt;
&lt;p&gt;chardet支持检测的编码列表请参考官方文档&lt;a href="https://chardet.readthedocs.io/en/latest/supported-encodings.html"&gt;Supported encodings&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id="小结"&gt;小结&lt;/h3&gt;
&lt;p&gt;使用chardet检测编码非常容易，chardet支持检测中文、日文、韩文等多种语言。&lt;/p&gt;</description></item><item><title>Python模块之requests</title><link>/2016/06/24/python%E6%A8%A1%E5%9D%97%E4%B9%8Brequests/</link><pubDate>Fri, 24 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/24/python%E6%A8%A1%E5%9D%97%E4%B9%8Brequests/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Python内置的模块有urllib，用于访问网络资源。但是，它用起来比较麻烦，而且，缺少很多实用的高级功能。&lt;/p&gt;
&lt;p&gt;更好的方案是使用requests。它是一个Python第三方库，处理URL资源特别方便。&lt;/p&gt;
&lt;h3 id="安装requests"&gt;安装requests&lt;/h3&gt;
&lt;p&gt;如果安装了Anaconda，requests就已经可用了。否则，需要在命令行下通过pip安装：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ pip install requests
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="使用requests"&gt;使用requests&lt;/h3&gt;
&lt;p&gt;要通过GET访问一个页面，只需要几行代码：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import requests
&amp;gt;&amp;gt;&amp;gt; r = requests.get(&amp;#39;https://www.douban.com/&amp;#39;) # 豆瓣首页
&amp;gt;&amp;gt;&amp;gt; r.status_code
200
&amp;gt;&amp;gt;&amp;gt; r.text
r.text
&amp;#39;&amp;lt;!DOCTYPE HTML&amp;gt;\n&amp;lt;html&amp;gt;\n&amp;lt;head&amp;gt;\n&amp;lt;meta name=&amp;#34;description&amp;#34; content=&amp;#34;提供图书、电影、音乐唱片的推荐、评论和...&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于带参数的URL，传入一个dict作为&lt;code&gt;params&lt;/code&gt;参数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; r = requests.get(&amp;#39;https://www.douban.com/search&amp;#39;, params={&amp;#39;q&amp;#39;: &amp;#39;python&amp;#39;, &amp;#39;cat&amp;#39;: &amp;#39;1001&amp;#39;})
&amp;gt;&amp;gt;&amp;gt; r.url # 实际请求的URL
&amp;#39;https://www.douban.com/search?q=python&amp;amp;cat=1001&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;requests自动检测编码，可以使用&lt;code&gt;encoding&lt;/code&gt;属性查看：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; r.encoding
&amp;#39;utf-8&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;无论响应是文本还是二进制内容，我们都可以用&lt;code&gt;content&lt;/code&gt;属性获得&lt;code&gt;bytes&lt;/code&gt;对象：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; r.content
b&amp;#39;&amp;lt;!DOCTYPE html&amp;gt;\n&amp;lt;html&amp;gt;\n&amp;lt;head&amp;gt;\n&amp;lt;meta http-equiv=&amp;#34;Content-Type&amp;#34; content=&amp;#34;text/html; charset=utf-8&amp;#34;&amp;gt;\n...&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;requests的方便之处还在于，对于特定类型的响应，例如JSON，可以直接获取：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; r = requests.get(&amp;#39;https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20%3D%202151330&amp;amp;format=json&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; r.json()
{&amp;#39;query&amp;#39;: {&amp;#39;count&amp;#39;: 1, &amp;#39;created&amp;#39;: &amp;#39;2017-11-17T07:14:12Z&amp;#39;, ...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;需要传入HTTP Header时，我们传入一个dict作为&lt;code&gt;headers&lt;/code&gt;参数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; r = requests.get(&amp;#39;https://www.douban.com/&amp;#39;, headers={&amp;#39;User-Agent&amp;#39;: &amp;#39;Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit&amp;#39;})
&amp;gt;&amp;gt;&amp;gt; r.text
&amp;#39;&amp;lt;!DOCTYPE html&amp;gt;\n&amp;lt;html&amp;gt;\n&amp;lt;head&amp;gt;\n&amp;lt;meta charset=&amp;#34;UTF-8&amp;#34;&amp;gt;\n &amp;lt;title&amp;gt;豆瓣(手机版)&amp;lt;/title&amp;gt;...&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;要发送POST请求，只需要把&lt;code&gt;get()&lt;/code&gt;方法变成&lt;code&gt;post()&lt;/code&gt;，然后传入&lt;code&gt;data&lt;/code&gt;参数作为POST请求的数据：&lt;/p&gt;</description></item><item><title>Python模块之pillow</title><link>/2016/06/23/python%E6%A8%A1%E5%9D%97%E4%B9%8Bpillow/</link><pubDate>Thu, 23 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/23/python%E6%A8%A1%E5%9D%97%E4%B9%8Bpillow/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;除了内建的模块外，Python还有大量的第三方模块,而且是相当相当的多。&lt;/p&gt;
&lt;p&gt;基本上，所有的第三方模块都会在&lt;a href="https://pypi.python.org/"&gt;PyPI - the Python Package Index&lt;/a&gt;上注册，只要找到对应的模块名字，即可用pip安装。也可以安装&lt;a href="https://www.anaconda.com/"&gt;Anaconda&lt;/a&gt;，安装后，数十个常用的第三方模块就已经就绪，不用pip手动安装。下面讲解一下pillow这个第三方模块.&lt;/p&gt;
&lt;p&gt;PIL：Python Imaging Library，已经是Python平台事实上的图像处理标准库了。PIL功能非常强大，但API却非常简单易用。&lt;/p&gt;
&lt;p&gt;由于PIL仅支持到Python 2.7，加上年久失修，于是一群志愿者在PIL的基础上创建了兼容的版本，名字叫&lt;a href="https://github.com/python-pillow/Pillow"&gt;Pillow&lt;/a&gt;，支持最新Python 3.x，又加入了许多新特性，因此，我们可以直接安装使用Pillow。&lt;/p&gt;
&lt;h3 id="安装pillow"&gt;安装Pillow&lt;/h3&gt;
&lt;p&gt;如果安装了Anaconda，Pillow就已经可用了。否则，需要在命令行下通过pip安装：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ pip install pillow
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="操作图像"&gt;操作图像&lt;/h3&gt;
&lt;p&gt;来看看最常见的图像缩放操作，只需三四行代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; PIL &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Image
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 打开一个jpg图像文件，注意是当前路径:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;im &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Image&lt;span style="color:#f92672"&gt;.&lt;/span&gt;open(&lt;span style="color:#e6db74"&gt;&amp;#39;test.jpg&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 获得图像尺寸:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;w, h &lt;span style="color:#f92672"&gt;=&lt;/span&gt; im&lt;span style="color:#f92672"&gt;.&lt;/span&gt;size
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(&lt;span style="color:#e6db74"&gt;&amp;#39;Original image size: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;x&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; (w, h))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 缩放到50%:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;im&lt;span style="color:#f92672"&gt;.&lt;/span&gt;thumbnail((w&lt;span style="color:#f92672"&gt;//&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, h&lt;span style="color:#f92672"&gt;//&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(&lt;span style="color:#e6db74"&gt;&amp;#39;Resize image to: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;x&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; (w&lt;span style="color:#f92672"&gt;//&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, h&lt;span style="color:#f92672"&gt;//&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 把缩放后的图像用jpeg格式保存:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;im&lt;span style="color:#f92672"&gt;.&lt;/span&gt;save(&lt;span style="color:#e6db74"&gt;&amp;#39;thumbnail.jpg&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;jpeg&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其他功能如切片、旋转、滤镜、输出文字、调色板等一应俱全。&lt;/p&gt;
&lt;p&gt;比如，模糊效果也只需几行代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; PIL &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Image, ImageFilter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 打开一个jpg图像文件，注意是当前路径:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;im &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Image&lt;span style="color:#f92672"&gt;.&lt;/span&gt;open(&lt;span style="color:#e6db74"&gt;&amp;#39;test.jpg&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 应用模糊滤镜:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;im2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; im&lt;span style="color:#f92672"&gt;.&lt;/span&gt;filter(ImageFilter&lt;span style="color:#f92672"&gt;.&lt;/span&gt;BLUR)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;im2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;save(&lt;span style="color:#e6db74"&gt;&amp;#39;blur.jpg&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;jpeg&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;效果如下：&lt;/p&gt;</description></item><item><title>Python内建模块HTMLParser</title><link>/2016/06/22/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97htmlparser/</link><pubDate>Wed, 22 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/22/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97htmlparser/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;如果我们要编写一个搜索引擎，第一步是用爬虫把目标网站的页面抓下来，第二步就是解析该HTML页面，看看里面的内容到底是新闻、图片还是视频。&lt;/p&gt;
&lt;p&gt;假设第一步已经完成了，第二步应该如何解析HTML呢？&lt;/p&gt;
&lt;p&gt;HTML本质上是XML的子集，但是HTML的语法没有XML那么严格，所以不能用标准的DOM或SAX来解析HTML。&lt;/p&gt;
&lt;p&gt;好在Python提供了HTMLParser来非常方便地解析HTML，只需简单几行代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; html.parser &lt;span style="color:#f92672"&gt;import&lt;/span&gt; HTMLParser
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; html.entities &lt;span style="color:#f92672"&gt;import&lt;/span&gt; name2codepoint
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MyHTMLParser&lt;/span&gt;(HTMLParser):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_starttag&lt;/span&gt;(self, tag, attrs):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;lt;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; tag)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_endtag&lt;/span&gt;(self, tag):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;lt;/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; tag)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_startendtag&lt;/span&gt;(self, tag, attrs):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;lt;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/&amp;gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; tag)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_data&lt;/span&gt;(self, data):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_comment&lt;/span&gt;(self, data):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;lt;!--&amp;#39;&lt;/span&gt;, data, &lt;span style="color:#e6db74"&gt;&amp;#39;--&amp;gt;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_entityref&lt;/span&gt;(self, name):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;amp;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handle_charref&lt;/span&gt;(self, name):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;amp;#&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;parser &lt;span style="color:#f92672"&gt;=&lt;/span&gt; MyHTMLParser()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;parser&lt;span style="color:#f92672"&gt;.&lt;/span&gt;feed(&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;&amp;lt;html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;lt;body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;lt;!-- test html parser --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;lt;p&amp;gt;Some &amp;lt;a href=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;#&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;gt;html&amp;lt;/a&amp;gt; HTML&amp;amp;nbsp;tutorial...&amp;lt;br&amp;gt;END&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;feed()&lt;/code&gt;方法可以多次调用，也就是不一定一次把整个HTML字符串都塞进去，可以一部分一部分塞进去。&lt;/p&gt;</description></item><item><title>Python内建模块xml</title><link>/2016/06/21/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97xml/</link><pubDate>Tue, 21 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/21/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97xml/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;XML虽然比JSON复杂，在Web中应用也不如以前多了，不过仍有很多地方在用，所以，有必要了解如何操作XML。&lt;/p&gt;
&lt;h3 id="dom-vs-sax"&gt;DOM vs SAX&lt;/h3&gt;
&lt;p&gt;操作XML有两种方法：DOM和SAX。DOM会把整个XML读入内存，解析为树，因此占用内存大，解析慢，优点是可以任意遍历树的节点。SAX是流模式，边读边解析，占用内存小，解析快，缺点是我们需要自己处理事件。&lt;/p&gt;
&lt;p&gt;正常情况下，优先考虑SAX，因为DOM实在太占内存。&lt;/p&gt;
&lt;p&gt;在Python中使用SAX解析XML非常简洁，通常我们关心的事件是&lt;code&gt;start_element&lt;/code&gt;，&lt;code&gt;end_element&lt;/code&gt;和&lt;code&gt;char_data&lt;/code&gt;，准备好这3个函数，然后就可以解析xml了。&lt;/p&gt;
&lt;p&gt;举个例子，当SAX解析器读到一个节点时：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;a href=&amp;#34;/&amp;#34;&amp;gt;python&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;会产生3个事件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;start_element事件，在读取&lt;code&gt;&amp;lt;a href=&amp;quot;/&amp;quot;&amp;gt;&lt;/code&gt;时；&lt;/li&gt;
&lt;li&gt;char_data事件，在读取&lt;code&gt;python&lt;/code&gt;时；&lt;/li&gt;
&lt;li&gt;end_element事件，在读取&lt;code&gt;&amp;lt;/a&amp;gt;&lt;/code&gt;时。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用代码实验一下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; xml.parsers.expat &lt;span style="color:#f92672"&gt;import&lt;/span&gt; ParserCreate
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DefaultSaxHandler&lt;/span&gt;(object):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;start_element&lt;/span&gt;(self, name, attrs):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;sax:start_element: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, attrs: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; (name, str(attrs)))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;end_element&lt;/span&gt;(self, name):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;sax:end_element: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;char_data&lt;/span&gt;(self, text):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;sax:char_data: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; text)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;xml &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;r&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;&amp;lt;?xml version=&amp;#34;1.0&amp;#34;?&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;lt;ol&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;/python&amp;#34;&amp;gt;Python&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;lt;li&amp;gt;&amp;lt;a href=&amp;#34;/ruby&amp;#34;&amp;gt;Ruby&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;lt;/ol&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;handler &lt;span style="color:#f92672"&gt;=&lt;/span&gt; DefaultSaxHandler()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;parser &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ParserCreate()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;parser&lt;span style="color:#f92672"&gt;.&lt;/span&gt;StartElementHandler &lt;span style="color:#f92672"&gt;=&lt;/span&gt; handler&lt;span style="color:#f92672"&gt;.&lt;/span&gt;start_element
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;parser&lt;span style="color:#f92672"&gt;.&lt;/span&gt;EndElementHandler &lt;span style="color:#f92672"&gt;=&lt;/span&gt; handler&lt;span style="color:#f92672"&gt;.&lt;/span&gt;end_element
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;parser&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CharacterDataHandler &lt;span style="color:#f92672"&gt;=&lt;/span&gt; handler&lt;span style="color:#f92672"&gt;.&lt;/span&gt;char_data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;parser&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Parse(xml)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;需要注意的是读取一大段字符串时，&lt;code&gt;CharacterDataHandler&lt;/code&gt;可能被多次调用，所以需要自己保存起来，在&lt;code&gt;EndElementHandler&lt;/code&gt;里面再合并。&lt;/p&gt;</description></item><item><title>Python内建模块contextlib</title><link>/2016/06/20/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97contextlib/</link><pubDate>Mon, 20 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/20/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97contextlib/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在Python中，读写文件这样的资源要特别注意，必须在使用完毕后正确关闭它们。正确关闭文件资源的一个方法是使用&lt;code&gt;try...finally&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;try:
f = open(&amp;#39;/path/to/file&amp;#39;, &amp;#39;r&amp;#39;)
f.read()
finally:
if f:
f.close()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;写&lt;code&gt;try...finally&lt;/code&gt;非常繁琐。Python的&lt;code&gt;with&lt;/code&gt;语句允许我们非常方便地使用资源，而不必担心资源没有关闭，所以上面的代码可以简化为：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;with open(&amp;#39;/path/to/file&amp;#39;, &amp;#39;r&amp;#39;) as f:
f.read()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;并不是只有&lt;code&gt;open()&lt;/code&gt;函数返回的fp对象才能使用&lt;code&gt;with&lt;/code&gt;语句。实际上，任何对象，只要正确实现了上下文管理，就可以用于&lt;code&gt;with&lt;/code&gt;语句。&lt;/p&gt;
&lt;p&gt;实现上下文管理是通过&lt;code&gt;__enter__&lt;/code&gt;和&lt;code&gt;__exit__&lt;/code&gt;这两个方法实现的。例如，下面的class实现了这两个方法：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Query(object):
def __init__(self, name):
self.name = name
def __enter__(self):
print(&amp;#39;Begin&amp;#39;)
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
print(&amp;#39;Error&amp;#39;)
else:
print(&amp;#39;End&amp;#39;)
def query(self):
print(&amp;#39;Query info about %s...&amp;#39; % self.name)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样我们就可以把自己写的资源对象用于&lt;code&gt;with&lt;/code&gt;语句：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;with Query(&amp;#39;Bob&amp;#39;) as q:
q.query()
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="contextmanager"&gt;@contextmanager&lt;/h3&gt;
&lt;p&gt;编写&lt;code&gt;__enter__&lt;/code&gt;和&lt;code&gt;__exit__&lt;/code&gt;仍然很繁琐，因此Python的标准库&lt;code&gt;contextlib&lt;/code&gt;提供了更简单的写法，上面的代码可以改写如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from contextlib import contextmanager
class Query(object):
def __init__(self, name):
self.name = name
def query(self):
print(&amp;#39;Query info about %s...&amp;#39; % self.name)
@contextmanager
def create_query(name):
print(&amp;#39;Begin&amp;#39;)
q = Query(name)
yield q
print(&amp;#39;End&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;@contextmanager&lt;/code&gt;这个decorator接受一个generator，用&lt;code&gt;yield&lt;/code&gt;语句把&lt;code&gt;with ... as var&lt;/code&gt;把变量输出出去，然后，&lt;code&gt;with&lt;/code&gt;语句就可以正常地工作了：&lt;/p&gt;</description></item><item><title>Python内建模块urllib</title><link>/2016/06/20/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97urllib/</link><pubDate>Mon, 20 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/20/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97urllib/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;urllib提供了一系列用于操作URL的功能。&lt;/p&gt;
&lt;h3 id="get"&gt;Get&lt;/h3&gt;
&lt;p&gt;urllib的&lt;code&gt;request&lt;/code&gt;模块可以非常方便地抓取URL内容，也就是发送一个GET请求到指定的页面，然后返回HTTP的响应：&lt;/p&gt;
&lt;p&gt;例如，对豆瓣的一个URL&lt;code&gt;https://api.douban.com/v2/book/2129650&lt;/code&gt;进行抓取，并返回响应：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; urllib &lt;span style="color:#f92672"&gt;import&lt;/span&gt; request
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; request&lt;span style="color:#f92672"&gt;.&lt;/span&gt;urlopen(&lt;span style="color:#e6db74"&gt;&amp;#39;https://api.douban.com/v2/book/2129650&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; f:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; f&lt;span style="color:#f92672"&gt;.&lt;/span&gt;read()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;Status:&amp;#39;&lt;/span&gt;, f&lt;span style="color:#f92672"&gt;.&lt;/span&gt;status, f&lt;span style="color:#f92672"&gt;.&lt;/span&gt;reason)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; k, v &lt;span style="color:#f92672"&gt;in&lt;/span&gt; f&lt;span style="color:#f92672"&gt;.&lt;/span&gt;getheaders():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; (k, v))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;Data:&amp;#39;&lt;/span&gt;, data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode(&lt;span style="color:#e6db74"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到HTTP响应的头和JSON数据：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Status: 200 OK
Server: nginx
Date: Tue, 26 May 2015 10:02:27 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2049
Connection: close
Expires: Sun, 1 Jan 2006 01:00:00 GMT
Pragma: no-cache
Cache-Control: must-revalidate, no-cache, private
X-DAE-Node: pidl1
Data: {&amp;#34;rating&amp;#34;:{&amp;#34;max&amp;#34;:10,&amp;#34;numRaters&amp;#34;:16,&amp;#34;average&amp;#34;:&amp;#34;7.4&amp;#34;,&amp;#34;min&amp;#34;:0},&amp;#34;subtitle&amp;#34;:&amp;#34;&amp;#34;,&amp;#34;author&amp;#34;:[&amp;#34;廖雪峰编著&amp;#34;],&amp;#34;pubdate&amp;#34;:&amp;#34;2007-6&amp;#34;,...}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果我们要想模拟浏览器发送GET请求，就需要使用&lt;code&gt;Request&lt;/code&gt;对象，通过往&lt;code&gt;Request&lt;/code&gt;对象添加HTTP头，我们就可以把请求伪装成浏览器。例如，模拟iPhone 6去请求豆瓣首页：&lt;/p&gt;</description></item><item><title>Python内建模块itertools</title><link>/2016/06/19/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97itertools/</link><pubDate>Sun, 19 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/19/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97itertools/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Python的内建模块&lt;code&gt;itertools&lt;/code&gt;提供了非常有用的用于操作迭代对象的函数。&lt;/p&gt;
&lt;p&gt;首先，我们看看&lt;code&gt;itertools&lt;/code&gt;提供的几个“无限”迭代器：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import itertools
&amp;gt;&amp;gt;&amp;gt; natuals = itertools.count(1)
&amp;gt;&amp;gt;&amp;gt; for n in natuals:
... print(n)
...
1
2
3
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因为&lt;code&gt;count()&lt;/code&gt;会创建一个无限的迭代器，所以上述代码会打印出自然数序列，根本停不下来，只能按&lt;code&gt;Ctrl+C&lt;/code&gt;退出。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cycle()&lt;/code&gt;会把传入的一个序列无限重复下去：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import itertools
&amp;gt;&amp;gt;&amp;gt; cs = itertools.cycle(&amp;#39;ABC&amp;#39;) # 注意字符串也是序列的一种
&amp;gt;&amp;gt;&amp;gt; for c in cs:
... print(c)
...
&amp;#39;A&amp;#39;
&amp;#39;B&amp;#39;
&amp;#39;C&amp;#39;
&amp;#39;A&amp;#39;
&amp;#39;B&amp;#39;
&amp;#39;C&amp;#39;
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;同样停不下来。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;repeat()&lt;/code&gt;负责把一个元素无限重复下去，不过如果提供第二个参数就可以限定重复次数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; ns = itertools.repeat(&amp;#39;A&amp;#39;, 3)
&amp;gt;&amp;gt;&amp;gt; for n in ns:
... print(n)
...
A
A
A
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;无限序列只有在&lt;code&gt;for&lt;/code&gt;迭代时才会无限地迭代下去，如果只是创建了一个迭代对象，它不会事先把无限个元素生成出来，事实上也不可能在内存中创建无限多个元素。&lt;/p&gt;
&lt;p&gt;无限序列虽然可以无限迭代下去，但是通常我们会通过&lt;code&gt;takewhile()&lt;/code&gt;等函数根据条件判断来截取出一个有限的序列：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; natuals = itertools.count(1)
&amp;gt;&amp;gt;&amp;gt; ns = itertools.takewhile(lambda x: x &amp;lt;= 10, natuals)
&amp;gt;&amp;gt;&amp;gt; list(ns)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;itertools&lt;/code&gt;提供的几个迭代器操作函数更加有用：&lt;/p&gt;</description></item><item><title>Python内建模块hashlib</title><link>/2016/06/18/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97hashlib/</link><pubDate>Sat, 18 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/18/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97hashlib/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="摘要算法简介"&gt;摘要算法简介&lt;/h3&gt;
&lt;p&gt;Python的hashlib提供了常见的摘要算法，如MD5，SHA1等等。&lt;/p&gt;
&lt;p&gt;什么是摘要算法呢？摘要算法又称哈希算法、散列算法。它通过一个函数，把任意长度的数据转换为一个长度固定的数据串（通常用16进制的字符串表示）。&lt;/p&gt;
&lt;p&gt;举个例子，你写了一篇文章，内容是一个字符串&lt;code&gt;'how to use python hashlib - by Michael'&lt;/code&gt;，并附上这篇文章的摘要是&lt;code&gt;'2d73d4f15c0db7f5ecb321b6a65e5d6d'&lt;/code&gt;。如果有人篡改了你的文章，并发表为&lt;code&gt;'how to use python hashlib - by Bob'&lt;/code&gt;，你可以一下子指出Bob篡改了你的文章，因为根据&lt;code&gt;'how to use python hashlib - by Bob'&lt;/code&gt;计算出的摘要不同于原始文章的摘要。&lt;/p&gt;
&lt;p&gt;可见，摘要算法就是通过摘要函数&lt;code&gt;f()&lt;/code&gt;对任意长度的数据&lt;code&gt;data&lt;/code&gt;计算出固定长度的摘要&lt;code&gt;digest&lt;/code&gt;，目的是为了发现原始数据是否被人篡改过。&lt;/p&gt;
&lt;p&gt;摘要算法之所以能指出数据是否被篡改过，就是因为摘要函数是一个单向函数，计算&lt;code&gt;f(data)&lt;/code&gt;很容易，但通过&lt;code&gt;digest&lt;/code&gt;反推&lt;code&gt;data&lt;/code&gt;却非常困难。而且，对原始数据做一个bit的修改，都会导致计算出的摘要完全不同,这种现象称为雪崩效应。&lt;/p&gt;
&lt;p&gt;我们以常见的摘要算法MD5为例，计算出一个字符串的MD5值：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import hashlib
md5 = hashlib.md5()
md5.update(&amp;#39;how to use md5 in python hashlib?&amp;#39;.encode(&amp;#39;utf-8&amp;#39;))
print(md5.hexdigest())
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;计算结果如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d26a53750bc40b38b65a520292f69306
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果数据量很大，可以分块多次调用&lt;code&gt;update()&lt;/code&gt;，最后计算的结果是一样的：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import hashlib
md5 = hashlib.md5()
md5.update(&amp;#39;how to use md5 in &amp;#39;.encode(&amp;#39;utf-8&amp;#39;))
md5.update(&amp;#39;python hashlib?&amp;#39;.encode(&amp;#39;utf-8&amp;#39;))
print(md5.hexdigest())
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;试试改动一个字母，看看计算的结果是否完全不同。&lt;/p&gt;
&lt;p&gt;MD5是最常见的摘要算法，速度很快，生成结果是固定的128 bit字节，通常用一个32位的16进制字符串表示。&lt;/p&gt;
&lt;p&gt;另一种常见的摘要算法是SHA1，调用SHA1和调用MD5完全类似：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import hashlib
sha1 = hashlib.sha1()
sha1.update(&amp;#39;how to use sha1 in &amp;#39;.encode(&amp;#39;utf-8&amp;#39;))
sha1.update(&amp;#39;python hashlib?&amp;#39;.encode(&amp;#39;utf-8&amp;#39;))
print(sha1.hexdigest())
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SHA1的结果是160 bit字节，通常用一个40位的16进制字符串表示。&lt;/p&gt;</description></item><item><title>Python内建模块hmac</title><link>/2016/06/18/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97hmac/</link><pubDate>Sat, 18 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/18/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97hmac/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;通过哈希算法，我们可以验证一段数据是否有效，方法就是对比该数据的哈希值，例如，判断用户口令是否正确，我们用保存在数据库中的&lt;code&gt;password_md5&lt;/code&gt;对比计算&lt;code&gt;md5(password)&lt;/code&gt;的结果，如果一致，用户输入的口令就是正确的。&lt;/p&gt;
&lt;p&gt;为了防止黑客通过彩虹表根据哈希值反推原始口令，在计算哈希的时候，不能仅针对原始输入计算，需要增加一个salt来使得相同的输入也能得到不同的哈希，这样，大大增加了黑客破解的难度。&lt;/p&gt;
&lt;p&gt;如果salt是我们自己随机生成的，通常我们计算MD5时采用&lt;code&gt;md5(message + salt)&lt;/code&gt;。但实际上，把salt看做一个“口令”，加salt的哈希就是：计算一段message的哈希时，根据不通口令计算出不同的哈希。要验证哈希值，必须同时提供正确的口令。&lt;/p&gt;
&lt;p&gt;这实际上就是Hmac算法：Keyed-Hashing for Message Authentication。它通过一个标准算法，在计算哈希的过程中，把key混入计算过程中。&lt;/p&gt;
&lt;p&gt;和我们自定义的加salt算法不同，Hmac算法针对所有哈希算法都通用，无论是MD5还是SHA-1。采用Hmac替代我们自己的salt算法，可以使程序算法更标准化，也更安全。&lt;/p&gt;
&lt;p&gt;Python自带的hmac模块实现了标准的Hmac算法。我们来看看如何使用hmac实现带key的哈希。&lt;/p&gt;
&lt;p&gt;我们首先需要准备待计算的原始消息message，随机key，哈希算法，这里采用MD5，使用hmac的代码如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import hmac
&amp;gt;&amp;gt;&amp;gt; message = b&amp;#39;Hello, world!&amp;#39;
&amp;gt;&amp;gt;&amp;gt; key = b&amp;#39;secret&amp;#39;
&amp;gt;&amp;gt;&amp;gt; h = hmac.new(key, message, digestmod=&amp;#39;MD5&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; # 如果消息很长，可以多次调用h.update(msg)
&amp;gt;&amp;gt;&amp;gt; h.hexdigest()
&amp;#39;fa4ee7d173f2d97ee79022d1a7355bcf&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可见使用hmac和普通hash算法非常类似。hmac输出的长度和原始哈希算法的长度一致。需要注意传入的key和message都是&lt;code&gt;bytes&lt;/code&gt;类型，&lt;code&gt;str&lt;/code&gt;类型需要首先编码为&lt;code&gt;bytes&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="练习"&gt;练习&lt;/h3&gt;
&lt;p&gt;将上一节的salt改为标准的hmac算法，验证用户口令：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# -*- coding: utf-8 -*-
import hmac, random
def hmac_md5(key, s):
return hmac.new(key.encode(&amp;#39;utf-8&amp;#39;), s.encode(&amp;#39;utf-8&amp;#39;), &amp;#39;MD5&amp;#39;).hexdigest()
class User(object):
def __init__(self, username, password):
self.username = username
self.key = &amp;#39;&amp;#39;.join([chr(random.randint(48, 122)) for i in range(20)])
self.password = hmac_md5(self.key, password)
db = {
&amp;#39;michael&amp;#39;: User(&amp;#39;michael&amp;#39;, &amp;#39;123456&amp;#39;),
&amp;#39;bob&amp;#39;: User(&amp;#39;bob&amp;#39;, &amp;#39;abc999&amp;#39;),
&amp;#39;alice&amp;#39;: User(&amp;#39;alice&amp;#39;, &amp;#39;alice2008&amp;#39;)
}
def login(username, password):
user = db[username]
return user.password == hmac_md5(user.key, password)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;测试:&lt;/p&gt;</description></item><item><title>Python内建模块struct</title><link>/2016/06/16/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97struct/</link><pubDate>Thu, 16 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/16/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97struct/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;准确地讲，Python没有专门处理字节的数据类型。但由于&lt;code&gt;b'str'&lt;/code&gt;可以表示字节，所以，字节数组＝二进制str。而在C语言中，我们可以很方便地用struct、union来处理字节，以及字节和int，float的转换。&lt;/p&gt;
&lt;p&gt;在Python中，比方说要把一个32位无符号整数变成字节，也就是4个长度的&lt;code&gt;bytes&lt;/code&gt;，你得配合位运算符这么写：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; n = 10240099
&amp;gt;&amp;gt;&amp;gt; b1 = (n &amp;amp; 0xff000000) &amp;gt;&amp;gt; 24
&amp;gt;&amp;gt;&amp;gt; b2 = (n &amp;amp; 0xff0000) &amp;gt;&amp;gt; 16
&amp;gt;&amp;gt;&amp;gt; b3 = (n &amp;amp; 0xff00) &amp;gt;&amp;gt; 8
&amp;gt;&amp;gt;&amp;gt; b4 = n &amp;amp; 0xff
&amp;gt;&amp;gt;&amp;gt; bs = bytes([b1, b2, b3, b4])
&amp;gt;&amp;gt;&amp;gt; bs
b&amp;#39;\x00\x9c@c&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;非常麻烦。如果换成浮点数就无能为力了。&lt;/p&gt;
&lt;p&gt;好在Python提供了一个&lt;code&gt;struct&lt;/code&gt;模块来解决&lt;code&gt;bytes&lt;/code&gt;和其他二进制数据类型的转换。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;struct&lt;/code&gt;的&lt;code&gt;pack&lt;/code&gt;函数把任意数据类型变成&lt;code&gt;bytes&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import struct
&amp;gt;&amp;gt;&amp;gt; struct.pack(&amp;#39;&amp;gt;I&amp;#39;, 10240099)
b&amp;#39;\x00\x9c@c&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;pack&lt;/code&gt;的第一个参数是处理指令，&lt;code&gt;'&amp;gt;I'&lt;/code&gt;的意思是：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;表示字节顺序是big-endian，也就是网络序，&lt;code&gt;I&lt;/code&gt;表示4字节无符号整数。&lt;/p&gt;
&lt;p&gt;后面的参数个数要和处理指令一致。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;unpack&lt;/code&gt;把&lt;code&gt;bytes&lt;/code&gt;变成相应的数据类型：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; struct.unpack(&amp;#39;&amp;gt;IH&amp;#39;, b&amp;#39;\xf0\xf0\xf0\xf0\x80\x80&amp;#39;)
(4042322160, 32896)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;根据&lt;code&gt;&amp;gt;IH&lt;/code&gt;的说明，后面的&lt;code&gt;bytes&lt;/code&gt;依次变为&lt;code&gt;I&lt;/code&gt;：4字节无符号整数和&lt;code&gt;H&lt;/code&gt;：2字节无符号整数。&lt;/p&gt;
&lt;p&gt;所以，尽管Python不适合编写底层操作字节流的代码，但在对性能要求不高的地方，利用&lt;code&gt;struct&lt;/code&gt;就方便多了。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;struct&lt;/code&gt;模块定义的数据类型可以参考Python官方文档：&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/library/struct.html#format-characters"&gt;https://docs.python.org/3/library/struct.html#format-characters&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Python内建模块base64</title><link>/2016/06/15/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97base64/</link><pubDate>Wed, 15 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/15/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97base64/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Base64是一种用64个字符来表示任意二进制数据的方法。&lt;/p&gt;
&lt;p&gt;用记事本打开&lt;code&gt;exe&lt;/code&gt;、&lt;code&gt;jpg&lt;/code&gt;、&lt;code&gt;pdf&lt;/code&gt;这些文件时，我们都会看到一大堆乱码，因为二进制文件包含很多无法显示和打印的字符，所以，如果要让记事本这样的文本处理软件能处理二进制数据，就需要一个二进制到字符串的转换方法。Base64是一种最常见的二进制编码方法。&lt;/p&gt;
&lt;p&gt;Base64的原理很简单，首先，准备一个包含64个字符的数组：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;, ... &amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, ... &amp;#39;0&amp;#39;, &amp;#39;1&amp;#39;, ... &amp;#39;+&amp;#39;, &amp;#39;/&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后，对二进制数据进行处理，每3个字节一组，一共是&lt;code&gt;3x8=24&lt;/code&gt;bit，划为4组，每组正好6个bit：&lt;/p&gt;
&lt;p&gt;&lt;img alt="base64-encode" loading="lazy" src="001399415038305edba53df7d784a7fa76c6b7f6526873b000"&gt;&lt;/p&gt;
&lt;p&gt;这样我们得到4个数字作为索引，然后查表，获得相应的4个字符，就是编码后的字符串。&lt;/p&gt;
&lt;p&gt;所以，Base64编码会把3字节的二进制数据编码为4字节的文本数据，长度增加33%，好处是编码后的文本数据可以在邮件正文、网页等直接显示。&lt;/p&gt;
&lt;p&gt;如果要编码的二进制数据不是3的倍数，最后会剩下1个或2个字节怎么办？Base64用&lt;code&gt;\x00&lt;/code&gt;字节在末尾补足后，再在编码的末尾加上1个或2个&lt;code&gt;=&lt;/code&gt;号，表示补了多少字节，解码的时候，会自动去掉。&lt;/p&gt;
&lt;p&gt;Python内置的&lt;code&gt;base64&lt;/code&gt;可以直接进行base64的编解码：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import base64
&amp;gt;&amp;gt;&amp;gt; base64.b64encode(b&amp;#39;binary\x00string&amp;#39;)
b&amp;#39;YmluYXJ5AHN0cmluZw==&amp;#39;
&amp;gt;&amp;gt;&amp;gt; base64.b64decode(b&amp;#39;YmluYXJ5AHN0cmluZw==&amp;#39;)
b&amp;#39;binary\x00string&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;由于标准的Base64编码后可能出现字符&lt;code&gt;+&lt;/code&gt;和&lt;code&gt;/&lt;/code&gt;，在URL中就不能直接作为参数，所以又有一种&amp;quot;url safe&amp;quot;的base64编码，其实就是把字符&lt;code&gt;+&lt;/code&gt;和&lt;code&gt;/&lt;/code&gt;分别变成&lt;code&gt;-&lt;/code&gt;和&lt;code&gt;_&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; base64.b64encode(b&amp;#39;i\xb7\x1d\xfb\xef\xff&amp;#39;)
b&amp;#39;abcd++//&amp;#39;
&amp;gt;&amp;gt;&amp;gt; base64.urlsafe_b64encode(b&amp;#39;i\xb7\x1d\xfb\xef\xff&amp;#39;)
b&amp;#39;abcd--__&amp;#39;
&amp;gt;&amp;gt;&amp;gt; base64.urlsafe_b64decode(&amp;#39;abcd--__&amp;#39;)
b&amp;#39;i\xb7\x1d\xfb\xef\xff&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;还可以自己定义64个字符的排列顺序，这样就可以自定义Base64编码，不过，通常情况下完全没有必要。&lt;/p&gt;
&lt;p&gt;Base64是一种通过查表的编码方法，不能用于加密，即使使用自定义的编码表也不行。&lt;/p&gt;
&lt;p&gt;Base64适用于小段内容的编码，比如数字证书签名、Cookie的内容等。&lt;/p&gt;
&lt;p&gt;由于&lt;code&gt;=&lt;/code&gt;字符也可能出现在Base64编码中，但&lt;code&gt;=&lt;/code&gt;用在URL、Cookie里面会造成歧义，所以，很多Base64编码后会把&lt;code&gt;=&lt;/code&gt;去掉：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# 标准Base64:
&amp;#39;abcd&amp;#39; -&amp;gt; &amp;#39;YWJjZA==&amp;#39;
# 自动去掉=:
&amp;#39;abcd&amp;#39; -&amp;gt; &amp;#39;YWJjZA&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;去掉&lt;code&gt;=&lt;/code&gt;后怎么解码呢？因为Base64是把3个字节变为4个字节，所以，Base64编码的长度永远是4的倍数，因此，需要加上&lt;code&gt;=&lt;/code&gt;把Base64字符串的长度变为4的倍数，就可以正常解码了。&lt;/p&gt;
&lt;p&gt;小结&lt;/p&gt;
&lt;p&gt;Base64是一种任意二进制到文本字符串的编码方法，常用于在URL、Cookie、网页中传输少量二进制数据。&lt;/p&gt;
&lt;p&gt;练习&lt;/p&gt;
&lt;p&gt;请写一个能处理去掉&lt;code&gt;=&lt;/code&gt;的base64解码函数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# -*- coding: utf-8 -*-&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; base64
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;safe_base64_decode&lt;/span&gt;(s):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;测试:&lt;/p&gt;</description></item><item><title>Python内建模块collections</title><link>/2016/06/14/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97collections/</link><pubDate>Tue, 14 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/14/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97collections/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;collections是Python内建的一个集合模块，提供了许多有用的集合类。&lt;/p&gt;
&lt;h3 id="namedtuple"&gt;namedtuple&lt;/h3&gt;
&lt;p&gt;我们知道&lt;code&gt;tuple&lt;/code&gt;可以表示不变集合，例如，一个点的二维坐标就可以表示成：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = (1, 2)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是，看到&lt;code&gt;(1, 2)&lt;/code&gt;，很难看出这个&lt;code&gt;tuple&lt;/code&gt;是用来表示一个坐标的。&lt;/p&gt;
&lt;p&gt;定义一个class又小题大做了，这时，&lt;code&gt;namedtuple&lt;/code&gt;就派上了用场：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from collections import namedtuple
&amp;gt;&amp;gt;&amp;gt; Point = namedtuple(&amp;#39;Point&amp;#39;, [&amp;#39;x&amp;#39;, &amp;#39;y&amp;#39;])
&amp;gt;&amp;gt;&amp;gt; p = Point(1, 2)
&amp;gt;&amp;gt;&amp;gt; p.x
1
&amp;gt;&amp;gt;&amp;gt; p.y
2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;namedtuple&lt;/code&gt;是一个函数，它用来创建一个自定义的&lt;code&gt;tuple&lt;/code&gt;对象，并且规定了&lt;code&gt;tuple&lt;/code&gt;元素的个数，并可以用属性而不是索引来引用&lt;code&gt;tuple&lt;/code&gt;的某个元素。&lt;/p&gt;
&lt;p&gt;这样一来，我们用&lt;code&gt;namedtuple&lt;/code&gt;可以很方便地定义一种数据类型，它具备tuple的不变性，又可以根据属性来引用，使用十分方便。&lt;/p&gt;
&lt;p&gt;可以验证创建的&lt;code&gt;Point&lt;/code&gt;对象是&lt;code&gt;tuple&lt;/code&gt;的一种子类：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; isinstance(p, Point)
True
&amp;gt;&amp;gt;&amp;gt; isinstance(p, tuple)
True
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;类似的，如果要用坐标和半径表示一个圆，也可以用&lt;code&gt;namedtuple&lt;/code&gt;定义：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# namedtuple(&amp;#39;名称&amp;#39;, [属性list]):
Circle = namedtuple(&amp;#39;Circle&amp;#39;, [&amp;#39;x&amp;#39;, &amp;#39;y&amp;#39;, &amp;#39;r&amp;#39;])
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="deque"&gt;deque&lt;/h3&gt;
&lt;p&gt;使用&lt;code&gt;list&lt;/code&gt;存储数据时，按索引访问元素很快，但是插入和删除元素就很慢了，因为&lt;code&gt;list&lt;/code&gt;是线性存储，数据量大的时候，插入和删除效率很低。&lt;/p&gt;
&lt;p&gt;deque是为了高效实现插入和删除操作的双向列表，适合用于队列和栈：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from collections import deque
&amp;gt;&amp;gt;&amp;gt; q = deque([&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;])
&amp;gt;&amp;gt;&amp;gt; q.append(&amp;#39;x&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; q.appendleft(&amp;#39;y&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; q
deque([&amp;#39;y&amp;#39;, &amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;x&amp;#39;])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;deque&lt;/code&gt;除了实现list的&lt;code&gt;append()&lt;/code&gt;和&lt;code&gt;pop()&lt;/code&gt;外，还支持&lt;code&gt;appendleft()&lt;/code&gt;和&lt;code&gt;popleft()&lt;/code&gt;，这样就可以非常高效地往头部添加或删除元素。&lt;/p&gt;</description></item><item><title>Python内建模块datetime</title><link>/2016/06/13/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97datetime/</link><pubDate>Mon, 13 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/13/python%E5%86%85%E5%BB%BA%E6%A8%A1%E5%9D%97datetime/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Python之所以自称“batteries included”，就是因为内置了许多非常有用的模块，无需额外安装和配置，即可直接使用。&lt;/p&gt;
&lt;p&gt;datetime是Python处理日期和时间的标准库。&lt;/p&gt;
&lt;h3 id="获取当前日期和时间"&gt;获取当前日期和时间&lt;/h3&gt;
&lt;p&gt;我们先看如何获取当前日期和时间：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from datetime import datetime
&amp;gt;&amp;gt;&amp;gt; now = datetime.now() # 获取当前datetime
&amp;gt;&amp;gt;&amp;gt; print(now)
2015-05-18 16:28:07.198690
&amp;gt;&amp;gt;&amp;gt; print(type(now))
&amp;lt;class &amp;#39;datetime.datetime&amp;#39;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注意到&lt;code&gt;datetime&lt;/code&gt;是模块，&lt;code&gt;datetime&lt;/code&gt;模块还包含一个&lt;code&gt;datetime&lt;/code&gt;类，通过&lt;code&gt;from datetime import datetime&lt;/code&gt;导入的才是&lt;code&gt;datetime&lt;/code&gt;这个类。&lt;/p&gt;
&lt;p&gt;如果仅导入&lt;code&gt;import datetime&lt;/code&gt;，则必须引用全名&lt;code&gt;datetime.datetime&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;datetime.now()&lt;/code&gt;返回当前日期和时间，其类型是&lt;code&gt;datetime&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="获取指定日期和时间"&gt;获取指定日期和时间&lt;/h3&gt;
&lt;p&gt;要指定某个日期和时间，我们直接用参数构造一个&lt;code&gt;datetime&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from datetime import datetime
&amp;gt;&amp;gt;&amp;gt; dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
&amp;gt;&amp;gt;&amp;gt; print(dt)
2015-04-19 12:20:00
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="datetime转换为timestamp"&gt;datetime转换为timestamp&lt;/h3&gt;
&lt;p&gt;在计算机中，时间实际上是用数字表示的。我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time，记为&lt;code&gt;0&lt;/code&gt;（1970年以前的时间timestamp为负数），当前时间就是相对于epoch time的秒数，称为timestamp。&lt;/p&gt;
&lt;p&gt;你可以认为：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;timestamp = 0 = 1970-1-1 00:00:00 UTC+0:00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对应的北京时间是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;timestamp = 0 = 1970-1-1 08:00:00 UTC+8:00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可见timestamp的值与时区毫无关系，因为timestamp一旦确定，其UTC时间就确定了，转换到任意时区的时间也是完全确定的，这就是为什么计算机存储的当前时间是以timestamp表示的，因为全球各地的计算机在任意时刻的timestamp都是完全相同的（假定时间已校准）。&lt;/p&gt;</description></item><item><title>Python正则表达式</title><link>/2016/06/12/python%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/</link><pubDate>Sun, 12 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/12/python%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;字符串是编程时涉及到的最多的一种数据结构，对字符串进行操作的需求几乎无处不在。比如判断一个字符串是否是合法的Email地址，虽然可以编程提取&lt;code&gt;@&lt;/code&gt;前后的子串，再分别判断是否是单词和域名，但这样做不但麻烦，而且代码难以复用。&lt;/p&gt;
&lt;p&gt;正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则，凡是符合规则的字符串，我们就认为它“匹配”了，否则，该字符串就是不合法的。&lt;/p&gt;
&lt;p&gt;所以我们判断一个字符串是否是合法的Email的方法是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建一个匹配Email的正则表达式；&lt;/li&gt;
&lt;li&gt;用该正则表达式去匹配用户的输入来判断是否合法。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因为正则表达式也是用字符串表示的，所以，我们要首先了解如何用字符来描述字符。&lt;/p&gt;
&lt;p&gt;在正则表达式中，如果直接给出字符，就是精确匹配。用&lt;code&gt;\d&lt;/code&gt;可以匹配一个数字，&lt;code&gt;\w&lt;/code&gt;可以匹配一个字母或数字，所以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;'00\d'&lt;/code&gt;可以匹配&lt;code&gt;'007'&lt;/code&gt;，但无法匹配&lt;code&gt;'00A'&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;'\d\d\d'&lt;/code&gt;可以匹配&lt;code&gt;'010'&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;'\w\w\d'&lt;/code&gt;可以匹配&lt;code&gt;'py3'&lt;/code&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;.&lt;/code&gt;可以匹配任意&lt;strong&gt;单个&lt;/strong&gt;字符，所以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;'py.'&lt;/code&gt;可以匹配&lt;code&gt;'pyc'&lt;/code&gt;、&lt;code&gt;'pyo'&lt;/code&gt;、&lt;code&gt;'py!'&lt;/code&gt;等等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要匹配变长的字符，在正则表达式中，用&lt;code&gt;*&lt;/code&gt;表示任意个字符（包括0个），用&lt;code&gt;+&lt;/code&gt;表示至少一个字符，用&lt;code&gt;?&lt;/code&gt;表示0个或1个字符，用&lt;code&gt;{n}&lt;/code&gt;表示n个字符，用&lt;code&gt;{n,m}&lt;/code&gt;表示n-m个字符：&lt;/p&gt;
&lt;p&gt;来看一个复杂的例子：&lt;code&gt;\d{3}\s+\d{3,8}&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我们来从左到右解读一下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;\d{3}&lt;/code&gt;表示匹配3个数字，例如&lt;code&gt;'010'&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\s&lt;/code&gt;可以匹配一个空格（也包括Tab等空白符），所以&lt;code&gt;\s+&lt;/code&gt;表示至少有一个空格，例如匹配&lt;code&gt;' '&lt;/code&gt;，&lt;code&gt;' '&lt;/code&gt;等；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\d{3,8}&lt;/code&gt;表示3-8个数字，例如&lt;code&gt;'1234567'&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;综合起来，上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。&lt;/p&gt;
&lt;p&gt;如果要匹配&lt;code&gt;'010-12345'&lt;/code&gt;这样的号码呢？由于&lt;code&gt;'-'&lt;/code&gt;是特殊字符，在正则表达式中，要用&lt;code&gt;'\'&lt;/code&gt;转义，所以，上面的正则是&lt;code&gt;\d{3}\-\d{3,8}&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;但是，仍然无法匹配&lt;code&gt;'010 - 12345'&lt;/code&gt;，因为带有空格。所以我们需要更复杂的匹配方式。&lt;/p&gt;
&lt;h3 id="进阶"&gt;进阶&lt;/h3&gt;
&lt;p&gt;要做更精确地匹配，可以用&lt;code&gt;[]&lt;/code&gt;表示范围，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[0-9a-zA-Z\_]&lt;/code&gt;可以匹配一个数字、字母或者下划线；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[0-9a-zA-Z\_]+&lt;/code&gt;可以匹配至少由一个数字、字母或者下划线组成的字符串，比如&lt;code&gt;'a100'&lt;/code&gt;，&lt;code&gt;'0_Z'&lt;/code&gt;，&lt;code&gt;'Py3000'&lt;/code&gt;等等；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[a-zA-Z\_][0-9a-zA-Z\_]*&lt;/code&gt;可以匹配由字母或下划线开头，后接任意个由一个数字、字母或者下划线组成的字符串，也就是Python合法的变量；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}&lt;/code&gt;更精确地限制了变量的长度是1-20个字符（前面1个字符+后面最多19个字符）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;A|B&lt;/code&gt;可以匹配A或B，所以&lt;code&gt;(P|p)ython&lt;/code&gt;可以匹配&lt;code&gt;'Python'&lt;/code&gt;或者&lt;code&gt;'python'&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;^&lt;/code&gt;表示行的开头，&lt;code&gt;^\d&lt;/code&gt;表示必须以数字开头。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$&lt;/code&gt;表示行的结束，&lt;code&gt;\d$&lt;/code&gt;表示必须以数字结束。&lt;/p&gt;
&lt;p&gt;你可能注意到了，&lt;code&gt;py&lt;/code&gt;也可以匹配&lt;code&gt;'python'&lt;/code&gt;，但是加上&lt;code&gt;^py$&lt;/code&gt;就变成了整行匹配，就只能匹配&lt;code&gt;'py'&lt;/code&gt;了。&lt;/p&gt;
&lt;h3 id="re模块"&gt;re模块&lt;/h3&gt;
&lt;p&gt;有了准备知识，我们就可以在Python中使用正则表达式了。Python提供&lt;code&gt;re&lt;/code&gt;模块，包含所有正则表达式的功能。由于Python的字符串本身也用&lt;code&gt;\&lt;/code&gt;转义，所以要特别注意：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;s = &amp;#39;ABC\\-001&amp;#39; # Python的字符串
# 对应的正则表达式字符串变成：
# &amp;#39;ABC\-001&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因此我们强烈建议使用Python的&lt;code&gt;r&lt;/code&gt;前缀，就不用考虑转义的问题了：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;s = r&amp;#39;ABC\-001&amp;#39; # Python的字符串
# 对应的正则表达式字符串不变：
# &amp;#39;ABC\-001&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;先看看如何判断正则表达式是否匹配：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import re
&amp;gt;&amp;gt;&amp;gt; re.match(r&amp;#39;^\d{3}\-\d{3,8}$&amp;#39;, &amp;#39;010-12345&amp;#39;)
&amp;lt;_sre.SRE_Match object; span=(0, 9), match=&amp;#39;010-12345&amp;#39;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; re.match(r&amp;#39;^\d{3}\-\d{3,8}$&amp;#39;, &amp;#39;010 12345&amp;#39;)
&amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;match()&lt;/code&gt;方法判断是否匹配，如果匹配成功，返回一个&lt;code&gt;Match&lt;/code&gt;对象，否则返回&lt;code&gt;None&lt;/code&gt;。常见的判断方法就是：&lt;/p&gt;</description></item><item><title>Python多任务处理</title><link>/2016/06/11/python%E5%A4%9A%E4%BB%BB%E5%8A%A1%E5%A4%84%E7%90%86/</link><pubDate>Sat, 11 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/11/python%E5%A4%9A%E4%BB%BB%E5%8A%A1%E5%A4%84%E7%90%86/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;前两天写了多进程和多线程，这是实现多任务最常用的两种方式。现在，我们来讨论一下这两种方式的优缺点。&lt;/p&gt;
&lt;p&gt;首先，要实现多任务，通常我们会设计Master-Worker模式，Master负责分配任务，Worker负责执行任务，因此，多任务环境下，通常是一个Master，多个Worker。&lt;/p&gt;
&lt;p&gt;如果用多进程实现Master-Worker，主进程就是Master，其他进程就是Worker。&lt;/p&gt;
&lt;p&gt;如果用多线程实现Master-Worker，主线程就是Master，其他线程就是Worker。&lt;/p&gt;
&lt;p&gt;多进程模式最大的优点就是稳定性高，因为一个子进程崩溃了，不会影响主进程和其他子进程。（当然主进程挂了所有进程就全挂了，但是Master进程只负责分配任务，挂掉的概率低）著名的Apache最早就是采用多进程模式。&lt;/p&gt;
&lt;p&gt;多进程模式的缺点是创建进程的代价大，在Unix/Linux系统下，用&lt;code&gt;fork&lt;/code&gt;调用还行，在Windows下创建进程开销巨大。另外，操作系统能同时运行的进程数也是有限的，在内存和CPU的限制下，如果有几千个进程同时运行，操作系统连调度都会成问题。&lt;/p&gt;
&lt;p&gt;多线程模式通常比多进程快一点，但是也快不到哪去，而且，多线程模式致命的缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃，因为所有线程共享进程的内存。在Windows上，如果一个线程执行的代码出了问题，你经常可以看到这样的提示：“该程序执行了非法操作，即将关闭”，其实往往是某个线程出了问题，但是操作系统会强制结束整个进程。&lt;/p&gt;
&lt;p&gt;在Windows下，多线程的效率比多进程要高，所以微软的IIS服务器默认采用多线程模式。由于多线程存在稳定性的问题，IIS的稳定性就不如Apache。为了缓解这个问题，IIS和Apache现在又有多进程+多线程的混合模式，真是把问题越搞越复杂。&lt;/p&gt;
&lt;h3 id="线程切换"&gt;线程切换&lt;/h3&gt;
&lt;p&gt;无论是多进程还是多线程，只要数量一多，效率肯定上不去，为什么呢？&lt;/p&gt;
&lt;p&gt;我们打个比方，假设你不幸正在准备中考，每天晚上需要做语文、数学、英语、物理、化学这5科的作业，每项作业耗时1小时。&lt;/p&gt;
&lt;p&gt;如果你先花1小时做语文作业，做完了，再花1小时做数学作业，这样，依次全部做完，一共花5小时，这种方式称为单任务模型，或者批处理任务模型。&lt;/p&gt;
&lt;p&gt;假设你打算切换到多任务模型，可以先做1分钟语文，再切换到数学作业，做1分钟，再切换到英语，以此类推，只要切换速度足够快，这种方式就和单核CPU执行多任务是一样的了，以幼儿园小朋友的眼光来看，你就正在同时写5科作业。&lt;/p&gt;
&lt;p&gt;但是，切换作业是有代价的，比如从语文切到数学，要先收拾桌子上的语文书本、钢笔（这叫保存现场），然后，打开数学课本、找出圆规直尺（这叫准备新环境），才能开始做数学作业。操作系统在切换进程或者线程时也是一样的，它需要先保存当前执行的现场环境（CPU寄存器状态、内存页等），然后，把新任务的执行环境准备好（恢复上次的寄存器状态，切换内存页等），才能开始执行。这个切换过程虽然很快，但是也需要耗费时间。如果有几千个任务同时进行，操作系统可能就主要忙着切换任务，根本没有多少时间去执行任务了，这种情况最常见的就是硬盘狂响，点窗口无反应，系统处于假死状态。&lt;/p&gt;
&lt;p&gt;所以，多任务一旦多到一个限度，就会消耗掉系统所有的资源，结果效率急剧下降，所有任务都做不好。&lt;/p&gt;
&lt;h3 id="计算密集型-vs-io密集型"&gt;计算密集型 vs. IO密集型&lt;/h3&gt;
&lt;p&gt;是否采用多任务的第二个考虑是任务的类型。我们可以把任务分为计算密集型和IO密集型。&lt;/p&gt;
&lt;p&gt;计算密集型任务的特点是要进行大量的计算，消耗CPU资源，比如计算圆周率、对视频进行高清解码等等，全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成，但是任务越多，花在任务切换的时间就越多，CPU执行任务的效率就越低，所以，要最高效地利用CPU，计算密集型任务同时进行的数量应当等于CPU的核心数。&lt;/p&gt;
&lt;p&gt;计算密集型任务由于主要消耗CPU资源，因此，代码运行效率至关重要。Python这样的脚本语言运行效率很低，完全不适合计算密集型任务。对于计算密集型任务，最好用C语言编写。&lt;/p&gt;
&lt;p&gt;第二种任务的类型是IO密集型，涉及到网络、磁盘IO的任务都是IO密集型任务，这类任务的特点是CPU消耗很少，任务的大部分时间都在等待IO操作完成（因为IO的速度远远低于CPU和内存的速度）。对于IO密集型任务，任务越多，CPU效率越高，但也有一个限度。常见的大部分任务都是IO密集型任务，比如Web应用。&lt;/p&gt;
&lt;p&gt;IO密集型任务执行期间，99%的时间都花在IO上，花在CPU上的时间很少，因此，用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言，完全无法提升运行效率。对于IO密集型任务，最合适的语言就是开发效率最高（代码量最少）的语言，脚本语言是首选，C语言最差。&lt;/p&gt;
&lt;h3 id="异步io"&gt;异步IO&lt;/h3&gt;
&lt;p&gt;考虑到CPU和IO之间巨大的速度差异，一个任务在执行的过程中大部分时间都在等待IO操作，单进程单线程模型会导致别的任务无法并行执行，因此，我们才需要多进程模型或者多线程模型来支持多任务并发执行。&lt;/p&gt;
&lt;p&gt;现代操作系统对IO操作已经做了巨大的改进，最大的特点就是支持异步IO。如果充分利用操作系统提供的异步IO支持，就可以用单进程单线程模型来执行多任务，这种全新的模型称为事件驱动模型，Nginx就是支持异步IO的Web服务器，它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上，可以运行多个进程（数量与CPU核心数相同），充分利用多核CPU。由于系统总的进程数量十分有限，因此操作系统调度非常高效。用异步IO编程模型来实现多任务是一个主要的趋势。&lt;/p&gt;
&lt;p&gt;对应到Python语言，单线程的异步编程模型称为协程，有了协程的支持，就可以基于事件驱动编写高效的多任务程序。后面再写协程.&lt;/p&gt;</description></item><item><title>Python ThreadLocal</title><link>/2016/06/10/python-threadlocal/</link><pubDate>Fri, 10 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/10/python-threadlocal/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在多线程环境下，每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好，因为局部变量只有线程自己能看见，不会影响其他线程，而全局变量的修改必须加锁。&lt;/p&gt;
&lt;p&gt;但是局部变量也有问题，就是在函数调用的时候，传递起来很麻烦：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def process_student(name):
std = Student(name)
# std是局部变量，但是每个函数都要用它，因此必须传进去：
do_task_1(std)
do_task_2(std)
def do_task_1(std):
do_subtask_1(std)
do_subtask_2(std)
def do_task_2(std):
do_subtask_2(std)
do_subtask_2(std)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;每个函数一层一层调用都这么传参数那还得了？用全局变量？也不行，因为每个线程处理不同的&lt;code&gt;Student&lt;/code&gt;对象，不能共享。&lt;/p&gt;
&lt;p&gt;如果用一个全局&lt;code&gt;dict&lt;/code&gt;存放所有的&lt;code&gt;Student&lt;/code&gt;对象，然后以&lt;code&gt;thread&lt;/code&gt;自身作为&lt;code&gt;key&lt;/code&gt;获得线程对应的&lt;code&gt;Student&lt;/code&gt;对象如何？&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;global_dict = {}
def std_thread(name):
std = Student(name)
# 把std放到全局变量global_dict中：
global_dict[threading.current_thread()] = std
do_task_1()
do_task_2()
def do_task_1():
# 不传入std，而是根据当前线程查找：
std = global_dict[threading.current_thread()]
...
def do_task_2():
# 任何函数都可以查找出当前线程的std变量：
std = global_dict[threading.current_thread()]
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这种方式理论上是可行的，它最大的优点是消除了&lt;code&gt;std&lt;/code&gt;对象在每层函数中的传递问题，但是，每个函数获取&lt;code&gt;std&lt;/code&gt;的代码有点丑。&lt;/p&gt;
&lt;p&gt;有没有更简单的方式？&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ThreadLocal&lt;/code&gt;应运而生，不用查找&lt;code&gt;dict&lt;/code&gt;，&lt;code&gt;ThreadLocal&lt;/code&gt;帮你自动做这件事：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import threading
# 创建全局ThreadLocal对象:
local_school = threading.local()
def process_student():
# 获取当前线程关联的student:
std = local_school.student
print(&amp;#39;Hello, %s (in %s)&amp;#39; % (std, threading.current_thread().name))
def process_thread(name):
# 绑定ThreadLocal的student:
local_school.student = name
process_student()
t1 = threading.Thread(target= process_thread, args=(&amp;#39;Alice&amp;#39;,), name=&amp;#39;Thread-A&amp;#39;)
t2 = threading.Thread(target= process_thread, args=(&amp;#39;Bob&amp;#39;,), name=&amp;#39;Thread-B&amp;#39;)
t1.start()
t2.start()
t1.join()
t2.join()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行结果：&lt;/p&gt;</description></item><item><title>Python多线程</title><link>/2016/06/09/python%E5%A4%9A%E7%BA%BF%E7%A8%8B/</link><pubDate>Thu, 09 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/09/python%E5%A4%9A%E7%BA%BF%E7%A8%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="线程"&gt;线程&lt;/h2&gt;
&lt;p&gt;在一个进程内部，要同时干多件事，就需要同时运行多个“子任务”，我们把进程内的这些“子任务”称为线程（Thread）。&lt;/p&gt;
&lt;p&gt;多任务可以由多进程完成，也可以由一个进程内的多线程完成。&lt;/p&gt;
&lt;p&gt;我们前面提到了进程是由若干线程组成的，一个进程至少有一个线程。&lt;/p&gt;
&lt;p&gt;由于线程是操作系统直接支持的执行单元，因此，高级语言通常都内置多线程的支持，Python也不例外，并且，Python的线程是真正的Posix Thread，而不是模拟出来的线程。&lt;/p&gt;
&lt;p&gt;Python的标准库提供了两个模块：&lt;code&gt;_thread&lt;/code&gt;和&lt;code&gt;threading&lt;/code&gt;，&lt;code&gt;_thread&lt;/code&gt;是低级模块，&lt;code&gt;threading&lt;/code&gt;是高级模块，对&lt;code&gt;_thread&lt;/code&gt;进行了封装。绝大多数情况下，我们只需要使用&lt;code&gt;threading&lt;/code&gt;这个高级模块。&lt;/p&gt;
&lt;p&gt;启动一个线程就是把一个函数传入并创建&lt;code&gt;Thread&lt;/code&gt;实例，然后调用&lt;code&gt;start()&lt;/code&gt;开始执行：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import time, threading
# 新线程执行的代码:
def loop():
print(&amp;#39;thread %s is running...&amp;#39; % threading.current_thread().name)
n = 0
while n &amp;lt; 5:
n = n + 1
print(&amp;#39;thread %s &amp;gt;&amp;gt;&amp;gt; %s&amp;#39; % (threading.current_thread().name, n))
time.sleep(1)
print(&amp;#39;thread %s ended.&amp;#39; % threading.current_thread().name)
print(&amp;#39;thread %s is running...&amp;#39; % threading.current_thread().name)
t = threading.Thread(target=loop, name=&amp;#39;LoopThread&amp;#39;)
t.start()
t.join()
print(&amp;#39;thread %s ended.&amp;#39; % threading.current_thread().name)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行结果如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;thread MainThread is running...
thread LoopThread is running...
thread LoopThread &amp;gt;&amp;gt;&amp;gt; 1
thread LoopThread &amp;gt;&amp;gt;&amp;gt; 2
thread LoopThread &amp;gt;&amp;gt;&amp;gt; 3
thread LoopThread &amp;gt;&amp;gt;&amp;gt; 4
thread LoopThread &amp;gt;&amp;gt;&amp;gt; 5
thread LoopThread ended.
thread MainThread ended.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;由于任何进程默认就会启动一个线程，我们把该线程称为主线程，主线程又可以启动新的线程，Python的&lt;code&gt;threading&lt;/code&gt;模块有个&lt;code&gt;current_thread()&lt;/code&gt;函数，它永远返回当前线程的实例。主线程实例的名字叫&lt;code&gt;MainThread&lt;/code&gt;，子线程的名字在创建时指定，我们用&lt;code&gt;LoopThread&lt;/code&gt;命名子线程。名字仅仅在打印时用来显示，完全没有其他意义，如果不起名字Python就自动给线程命名为&lt;code&gt;Thread-1&lt;/code&gt;，&lt;code&gt;Thread-2&lt;/code&gt;……&lt;/p&gt;</description></item><item><title>Python多进程</title><link>/2016/06/09/python%E5%A4%9A%E8%BF%9B%E7%A8%8B/</link><pubDate>Thu, 09 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/09/python%E5%A4%9A%E8%BF%9B%E7%A8%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="进程"&gt;进程&lt;/h2&gt;
&lt;p&gt;一个任务就是一个进程（Process）&lt;/p&gt;
&lt;p&gt;要让Python程序实现多进程（multiprocessing），我们先了解操作系统的相关知识。&lt;/p&gt;
&lt;p&gt;Unix/Linux操作系统提供了一个&lt;code&gt;fork()&lt;/code&gt;系统调用，它非常特殊。普通的函数调用，调用一次，返回一次，但是&lt;code&gt;fork()&lt;/code&gt;调用一次，返回两次，因为操作系统自动把当前进程（称为父进程）复制了一份（称为子进程），然后，分别在父进程和子进程内返回。&lt;/p&gt;
&lt;p&gt;子进程永远返回&lt;code&gt;0&lt;/code&gt;，而父进程返回子进程的ID。这样做的理由是，一个父进程可以fork出很多子进程，所以，父进程要记下每个子进程的ID，而子进程只需要调用&lt;code&gt;getppid()&lt;/code&gt;就可以拿到父进程的ID。&lt;/p&gt;
&lt;p&gt;Python的&lt;code&gt;os&lt;/code&gt;模块封装了常见的系统调用，其中就包括&lt;code&gt;fork&lt;/code&gt;，可以在Python程序中轻松创建子进程：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import os
print(&amp;#39;Process (%s) start...&amp;#39; % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
print(&amp;#39;I am child process (%s) and my parent is %s.&amp;#39; % (os.getpid(), os.getppid()))
else:
print(&amp;#39;I (%s) just created a child process (%s).&amp;#39; % (os.getpid(), pid))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;运行结果如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Process (876) start...
I (876) just created a child process (877).
I am child process (877) and my parent is 876.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;由于Windows没有&lt;code&gt;fork&lt;/code&gt;调用，上面的代码在Windows上无法运行。由于Mac系统是基于BSD（Unix的一种）内核，所以，在Mac下运行是没有问题的，推荐大家用Mac学Python , Linux 系统更是没有问题的啦, 推荐使用Ubuntu！&lt;/p&gt;</description></item><item><title>Python序列化</title><link>/2016/06/08/python%E5%BA%8F%E5%88%97%E5%8C%96/</link><pubDate>Wed, 08 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/08/python%E5%BA%8F%E5%88%97%E5%8C%96/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="序列化"&gt;序列化&lt;/h2&gt;
&lt;p&gt;把变量从内存中变成可存储或传输的过程称之为序列化，在Python中叫pickling&lt;/p&gt;
&lt;p&gt;在程序运行的过程中，所有的变量都是在内存中，比如，定义一个dict：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d = dict(name=&amp;#39;Bob&amp;#39;, age=20, score=88)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以随时修改变量，比如把&lt;code&gt;name&lt;/code&gt;改成&lt;code&gt;'Bill'&lt;/code&gt;，但是一旦程序结束，变量所占用的内存就被操作系统全部回收。如果没有把修改后的&lt;code&gt;'Bill'&lt;/code&gt;存储到磁盘上，下次重新运行程序，变量又被初始化为&lt;code&gt;'Bob'&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;序列化在Python中叫pickling，在其他语言中也被称之为serialization，marshalling，flattening等等，都是一个意思。&lt;/p&gt;
&lt;p&gt;序列化之后，就可以把序列化后的内容写入磁盘，或者通过网络传输到别的机器上。&lt;/p&gt;
&lt;p&gt;反过来，把变量内容从序列化的对象重新读到内存里称之为反序列化，即unpickling。&lt;/p&gt;
&lt;p&gt;Python提供了&lt;code&gt;pickle&lt;/code&gt;模块来实现序列化。&lt;/p&gt;
&lt;p&gt;首先，我们尝试把一个对象序列化并写入文件：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import pickle
&amp;gt;&amp;gt;&amp;gt; d = dict(name=&amp;#39;Bob&amp;#39;, age=20, score=88)
&amp;gt;&amp;gt;&amp;gt; pickle.dumps(d)
b&amp;#39;\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;pickle.dumps()&lt;/code&gt;方法把任意对象序列化成一个&lt;code&gt;bytes&lt;/code&gt;，然后，就可以把这个&lt;code&gt;bytes&lt;/code&gt;写入文件。或者用另一个方法&lt;code&gt;pickle.dump()&lt;/code&gt;直接把对象序列化后写入一个file-like Object：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = open(&amp;#39;dump.txt&amp;#39;, &amp;#39;wb&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; pickle.dump(d, f)
&amp;gt;&amp;gt;&amp;gt; f.close()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;看看写入的&lt;code&gt;dump.txt&lt;/code&gt;文件，一堆乱七八糟的内容，这些都是Python保存的对象内部信息。&lt;/p&gt;
&lt;p&gt;当我们要把对象从磁盘读到内存时，可以先把内容读到一个&lt;code&gt;bytes&lt;/code&gt;，然后用&lt;code&gt;pickle.loads()&lt;/code&gt;方法反序列化出对象，也可以直接用&lt;code&gt;pickle.load()&lt;/code&gt;方法从一个&lt;code&gt;file-like Object&lt;/code&gt;中直接反序列化出对象。我们打开另一个Python命令行来反序列化刚才保存的对象：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = open(&amp;#39;dump.txt&amp;#39;, &amp;#39;rb&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; d = pickle.load(f)
&amp;gt;&amp;gt;&amp;gt; f.close()
&amp;gt;&amp;gt;&amp;gt; d
{&amp;#39;age&amp;#39;: 20, &amp;#39;score&amp;#39;: 88, &amp;#39;name&amp;#39;: &amp;#39;Bob&amp;#39;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;变量的内容又回来了！&lt;/p&gt;
&lt;p&gt;当然，这个变量和原来的变量是完全不相干的对象，它们只是内容相同而已。&lt;/p&gt;
&lt;p&gt;Pickle的问题和所有其他编程语言特有的序列化问题一样，就是它只能用于Python，并且可能不同版本的Python彼此都不兼容，因此，只能用Pickle保存那些不重要的数据，不能成功地反序列化也没关系。&lt;/p&gt;
&lt;h3 id="json"&gt;JSON&lt;/h3&gt;
&lt;p&gt;如果我们要在不同的编程语言之间传递对象，就必须把对象序列化为标准格式，比如XML，但更好的方法是序列化为JSON，因为JSON表示出来就是一个字符串，可以被所有语言读取，也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式，并且比XML更快，而且可以直接在Web页面中读取，非常方便。&lt;/p&gt;
&lt;p&gt;JSON表示的对象就是标准的JavaScript语言的对象，JSON和Python内置的数据类型对应如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JSON类型&lt;/th&gt;
&lt;th&gt;Python类型&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;{}&lt;/td&gt;
&lt;td&gt;dict&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;ldquo;string&amp;rdquo;&lt;/td&gt;
&lt;td&gt;str&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1234.56&lt;/td&gt;
&lt;td&gt;int或float&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;true/false&lt;/td&gt;
&lt;td&gt;True/False&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Python内置的&lt;code&gt;json&lt;/code&gt;模块提供了非常完善的Python对象到JSON格式的转换。我们先看看如何把Python对象变成一个JSON：&lt;/p&gt;</description></item><item><title>Python操作文件和目录</title><link>/2016/06/07/python%E6%93%8D%E4%BD%9C%E6%96%87%E4%BB%B6%E5%92%8C%E7%9B%AE%E5%BD%95/</link><pubDate>Tue, 07 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/07/python%E6%93%8D%E4%BD%9C%E6%96%87%E4%BB%B6%E5%92%8C%E7%9B%AE%E5%BD%95/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;如果我们要操作文件、目录，可以在命令行下面输入操作系统提供的各种命令来完成。比如&lt;code&gt;dir&lt;/code&gt;、&lt;code&gt;cp&lt;/code&gt;等命令。&lt;/p&gt;
&lt;p&gt;如果要在Python程序中执行这些目录和文件的操作怎么办？其实操作系统提供的命令只是简单地调用了操作系统提供的接口函数，Python内置的&lt;code&gt;os&lt;/code&gt;模块也可以直接调用操作系统提供的接口函数。&lt;/p&gt;
&lt;p&gt;打开Python交互式命令行，我们来看看如何使用&lt;code&gt;os&lt;/code&gt;模块的基本功能：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import os
&amp;gt;&amp;gt;&amp;gt; os.name # 操作系统类型
&amp;#39;posix&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果是&lt;code&gt;posix&lt;/code&gt;，说明系统是&lt;code&gt;Linux&lt;/code&gt;、&lt;code&gt;Unix&lt;/code&gt;或&lt;code&gt;Mac OS X&lt;/code&gt;，如果是&lt;code&gt;nt&lt;/code&gt;，就是&lt;code&gt;Windows&lt;/code&gt;系统。&lt;/p&gt;
&lt;p&gt;要获取详细的系统信息，可以调用&lt;code&gt;uname()&lt;/code&gt;函数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; os.uname()
posix.uname_result(sysname=&amp;#39;Darwin&amp;#39;, nodename=&amp;#39;MichaelMacPro.local&amp;#39;, release=&amp;#39;14.3.0&amp;#39;, version=&amp;#39;Darwin Kernel Version 14.3.0: Mon Mar 23 11:59:05 PDT 2015; root:xnu-2782.20.48~5/RELEASE_X86_64&amp;#39;, machine=&amp;#39;x86_64&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注意&lt;code&gt;uname()&lt;/code&gt;函数在Windows上不提供，也就是说，&lt;code&gt;os&lt;/code&gt;模块的某些函数是跟操作系统相关的。&lt;/p&gt;
&lt;h3 id="环境变量"&gt;环境变量&lt;/h3&gt;
&lt;p&gt;在操作系统中定义的环境变量，全部保存在&lt;code&gt;os.environ&lt;/code&gt;这个变量中，可以直接查看：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; os.environ
environ({&amp;#39;VERSIONER_PYTHON_PREFER_32_BIT&amp;#39;: &amp;#39;no&amp;#39;, &amp;#39;TERM_PROGRAM_VERSION&amp;#39;: &amp;#39;326&amp;#39;, &amp;#39;LOGNAME&amp;#39;: &amp;#39;michael&amp;#39;, &amp;#39;USER&amp;#39;: &amp;#39;michael&amp;#39;, &amp;#39;PATH&amp;#39;: &amp;#39;/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin&amp;#39;, ...})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;要获取某个环境变量的值，可以调用&lt;code&gt;os.environ.get('key')&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; os.environ.get(&amp;#39;PATH&amp;#39;)
&amp;#39;/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin&amp;#39;
&amp;gt;&amp;gt;&amp;gt; os.environ.get(&amp;#39;x&amp;#39;, &amp;#39;default&amp;#39;)
&amp;#39;default&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="操作文件和目录"&gt;操作文件和目录&lt;/h3&gt;
&lt;p&gt;操作文件和目录的函数一部分放在&lt;code&gt;os&lt;/code&gt;模块中，一部分放在&lt;code&gt;os.path&lt;/code&gt;模块中，这一点要注意一下。查看、创建和删除目录可以这么调用：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# 查看当前目录的绝对路径:
&amp;gt;&amp;gt;&amp;gt; os.path.abspath(&amp;#39;.&amp;#39;)
&amp;#39;/Users/michael&amp;#39;
# 在某个目录下创建一个新目录，首先把新目录的完整路径表示出来:
&amp;gt;&amp;gt;&amp;gt; os.path.join(&amp;#39;/Users/michael&amp;#39;, &amp;#39;testdir&amp;#39;)
&amp;#39;/Users/michael/testdir&amp;#39;
# 然后创建一个目录:
&amp;gt;&amp;gt;&amp;gt; os.mkdir(&amp;#39;/Users/michael/testdir&amp;#39;)
# 删掉一个目录:
&amp;gt;&amp;gt;&amp;gt; os.rmdir(&amp;#39;/Users/michael/testdir&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;把两个路径合成一个时，不要直接拼字符串，而要通过&lt;code&gt;os.path.join()&lt;/code&gt;函数，这样可以正确处理不同操作系统的路径分隔符。在Linux/Unix/Mac下，&lt;code&gt;os.path.join()&lt;/code&gt;返回这样的字符串：&lt;/p&gt;</description></item><item><title>Python StringIO和BytesIO</title><link>/2016/06/06/python-stringio%E5%92%8Cbytesio/</link><pubDate>Mon, 06 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/06/python-stringio%E5%92%8Cbytesio/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="stringio"&gt;StringIO&lt;/h3&gt;
&lt;p&gt;很多时候，数据读写不一定是文件，也可以在内存中读写。&lt;/p&gt;
&lt;p&gt;StringIO顾名思义就是在内存中读写str。&lt;/p&gt;
&lt;p&gt;要把str写入StringIO，我们需要先创建一个StringIO，然后，像文件一样写入即可：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from io import StringIO
&amp;gt;&amp;gt;&amp;gt; f = StringIO()
&amp;gt;&amp;gt;&amp;gt; f.write(&amp;#39;hello&amp;#39;)
5
&amp;gt;&amp;gt;&amp;gt; f.write(&amp;#39; &amp;#39;)
1
&amp;gt;&amp;gt;&amp;gt; f.write(&amp;#39;world!&amp;#39;)
6
&amp;gt;&amp;gt;&amp;gt; print(f.getvalue())
hello world!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;getvalue()&lt;/code&gt;方法用于获得写入后的str。&lt;/p&gt;
&lt;p&gt;要读取StringIO，可以用一个str初始化StringIO，然后，像读文件一样读取：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from io import StringIO
&amp;gt;&amp;gt;&amp;gt; f = StringIO(&amp;#39;Hello!\nHi!\nGoodbye!&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; while True:
... s = f.readline()
... if s == &amp;#39;&amp;#39;:
... break
... print(s.strip())
...
Hello!
Hi!
Goodbye!
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="bytesio"&gt;BytesIO&lt;/h3&gt;
&lt;p&gt;StringIO操作的只能是str，如果要操作二进制数据，就需要使用BytesIO。&lt;/p&gt;
&lt;p&gt;BytesIO实现了在内存中读写bytes，我们创建一个BytesIO，然后写入一些bytes：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from io import BytesIO
&amp;gt;&amp;gt;&amp;gt; f = BytesIO()
&amp;gt;&amp;gt;&amp;gt; f.write(&amp;#39;中文&amp;#39;.encode(&amp;#39;utf-8&amp;#39;))
6
&amp;gt;&amp;gt;&amp;gt; print(f.getvalue())
b&amp;#39;\xe4\xb8\xad\xe6\x96\x87&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;请注意，写入的不是str，而是经过UTF-8编码的bytes。&lt;/p&gt;</description></item><item><title>Python文件读写</title><link>/2016/06/05/python%E6%96%87%E4%BB%B6%E8%AF%BB%E5%86%99/</link><pubDate>Sun, 05 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/05/python%E6%96%87%E4%BB%B6%E8%AF%BB%E5%86%99/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;读写文件是最常见的IO操作。Python内置了读写文件的函数，用法和C是兼容的。&lt;/p&gt;
&lt;p&gt;读写文件前，我们先必须了解一下，在磁盘上读写文件的功能都是由操作系统提供的，现代操作系统不允许普通的程序直接操作磁盘，所以，读写文件就是请求操作系统打开一个文件对象（通常称为文件描述符），然后，通过操作系统提供的接口从这个文件对象中读取数据（读文件），或者把数据写入这个文件对象（写文件）。&lt;/p&gt;
&lt;h3 id="读文件"&gt;读文件&lt;/h3&gt;
&lt;p&gt;要以读文件的模式打开一个文件对象，使用Python内置的&lt;code&gt;open()&lt;/code&gt;函数，传入文件名和标示符：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = open(&amp;#39;/Users/michael/test.txt&amp;#39;, &amp;#39;r&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;标示符&amp;rsquo;r&amp;rsquo;表示读，这样，我们就成功地打开了一个文件。&lt;/p&gt;
&lt;p&gt;如果文件不存在，&lt;code&gt;open()&lt;/code&gt;函数就会抛出一个&lt;code&gt;IOError&lt;/code&gt;的错误，并且给出错误码和详细的信息告诉你文件不存在：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f=open(&amp;#39;/Users/michael/notfound.txt&amp;#39;, &amp;#39;r&amp;#39;)
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
FileNotFoundError: [Errno 2] No such file or directory: &amp;#39;/Users/michael/notfound.txt&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果文件打开成功，接下来，调用&lt;code&gt;read()&lt;/code&gt;方法可以一次读取文件的全部内容，Python把内容读到内存，用一个&lt;code&gt;str&lt;/code&gt;对象表示：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f.read()
&amp;#39;Hello, world!&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最后一步是调用&lt;code&gt;close()&lt;/code&gt;方法关闭文件。文件使用完毕后必须关闭，因为文件对象会占用操作系统的资源，并且操作系统同一时间能打开的文件数量也是有限的：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f.close()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;由于文件读写时都有可能产生&lt;code&gt;IOError&lt;/code&gt;，一旦出错，后面的&lt;code&gt;f.close()&lt;/code&gt;就不会调用。所以，为了保证无论是否出错都能正确地关闭文件，我们可以使用&lt;code&gt;try ... finally&lt;/code&gt;来实现：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;try:
f = open(&amp;#39;/path/to/file&amp;#39;, &amp;#39;r&amp;#39;)
print(f.read())
finally:
if f:
f.close()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是每次都这么写实在太繁琐，所以，Python引入了&lt;code&gt;with&lt;/code&gt;语句来自动帮我们调用&lt;code&gt;close()&lt;/code&gt;方法：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;with open(&amp;#39;/path/to/file&amp;#39;, &amp;#39;r&amp;#39;) as f:
print(f.read())
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这和前面的&lt;code&gt;try ... finally&lt;/code&gt;是一样的，但是代码更佳简洁，并且不必调用&lt;code&gt;f.close()&lt;/code&gt;方法。&lt;/p&gt;
&lt;p&gt;调用&lt;code&gt;read()&lt;/code&gt;会一次性读取文件的全部内容，如果文件有10G，内存就爆了，所以，要保险起见，可以反复调用&lt;code&gt;read(size)&lt;/code&gt;方法，每次最多读取size个字节的内容。另外，调用&lt;code&gt;readline()&lt;/code&gt;可以每次读取一行内容，调用&lt;code&gt;readlines()&lt;/code&gt;一次读取所有内容并按行返回&lt;code&gt;list&lt;/code&gt;。因此，要根据需要决定怎么调用。&lt;/p&gt;
&lt;p&gt;如果文件很小，&lt;code&gt;read()&lt;/code&gt;一次性读取最方便；如果不能确定文件大小，反复调用&lt;code&gt;read(size)&lt;/code&gt;比较保险；如果是配置文件，调用&lt;code&gt;readlines()&lt;/code&gt;最方便：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;for line in f.readlines():
print(line.strip()) # 把末尾的&amp;#39;\n&amp;#39;删掉
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="file-like-object"&gt;file-like Object&lt;/h3&gt;
&lt;p&gt;像&lt;code&gt;open()&lt;/code&gt;函数返回的这种有个&lt;code&gt;read()&lt;/code&gt;方法的对象，在Python中统称为file-like Object。除了file外，还可以是内存的字节流，网络流，自定义流等等。file-like Object不要求从特定类继承，只要写个&lt;code&gt;read()&lt;/code&gt;方法就行。&lt;/p&gt;</description></item><item><title>Python文档测试</title><link>/2016/06/03/python%E6%96%87%E6%A1%A3%E6%B5%8B%E8%AF%95/</link><pubDate>Fri, 03 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/03/python%E6%96%87%E6%A1%A3%E6%B5%8B%E8%AF%95/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;如果你经常阅读Python的官方文档，可以看到很多文档都有示例代码。比如&lt;a href="https://docs.python.org/3/library/re.html"&gt;re模块&lt;/a&gt;就带了很多示例代码：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import re
&amp;gt;&amp;gt;&amp;gt; m = re.search(&amp;#39;(?&amp;lt;=abc)def&amp;#39;, &amp;#39;abcdef&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; m.group(0)
&amp;#39;def&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以把这些示例代码在Python的交互式环境下输入并执行，结果与文档中的示例代码显示的一致。&lt;/p&gt;
&lt;p&gt;这些代码与其他说明可以写在注释中，然后，由一些工具来自动生成文档。既然这些代码本身就可以粘贴出来直接运行，那么，可不可以自动执行写在注释中的这些代码呢？&lt;/p&gt;
&lt;p&gt;答案是肯定的。&lt;/p&gt;
&lt;p&gt;当我们编写注释时，如果写上这样的注释：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def abs(n):
&amp;#39;&amp;#39;&amp;#39;
Function to get absolute value of number.
Example:
&amp;gt;&amp;gt;&amp;gt; abs(1)
1
&amp;gt;&amp;gt;&amp;gt; abs(-1)
1
&amp;gt;&amp;gt;&amp;gt; abs(0)
0
&amp;#39;&amp;#39;&amp;#39;
return n if n &amp;gt;= 0 else (-n)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;无疑更明确地告诉函数的调用者该函数的期望输入和输出。&lt;/p&gt;
&lt;p&gt;并且，Python内置的“文档测试”（doctest）模块可以直接提取注释中的代码并执行测试。&lt;/p&gt;
&lt;p&gt;doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候，可以用&lt;code&gt;...&lt;/code&gt;表示中间一大段烦人的输出。&lt;/p&gt;
&lt;p&gt;让我们用doctest来测试上次编写的&lt;code&gt;Dict&lt;/code&gt;类：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# mydict2.py
class Dict(dict):
&amp;#39;&amp;#39;&amp;#39;
Simple dict but also support access as x.y style.
&amp;gt;&amp;gt;&amp;gt; d1 = Dict()
&amp;gt;&amp;gt;&amp;gt; d1[&amp;#39;x&amp;#39;] = 100
&amp;gt;&amp;gt;&amp;gt; d1.x
100
&amp;gt;&amp;gt;&amp;gt; d1.y = 200
&amp;gt;&amp;gt;&amp;gt; d1[&amp;#39;y&amp;#39;]
200
&amp;gt;&amp;gt;&amp;gt; d2 = Dict(a=1, b=2, c=&amp;#39;3&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; d2.c
&amp;#39;3&amp;#39;
&amp;gt;&amp;gt;&amp;gt; d2[&amp;#39;empty&amp;#39;]
Traceback (most recent call last):
...
KeyError: &amp;#39;empty&amp;#39;
&amp;gt;&amp;gt;&amp;gt; d2.empty
Traceback (most recent call last):
...
AttributeError: &amp;#39;Dict&amp;#39; object has no attribute &amp;#39;empty&amp;#39;
&amp;#39;&amp;#39;&amp;#39;
def __init__(self, **kw):
super(Dict, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r&amp;#34;&amp;#39;Dict&amp;#39; object has no attribute &amp;#39;%s&amp;#39;&amp;#34; % key)
def __setattr__(self, key, value):
self[key] = value
if __name__==&amp;#39;__main__&amp;#39;:
import doctest
doctest.testmod()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;运行&lt;code&gt;python mydict2.py&lt;/code&gt;：&lt;/p&gt;</description></item><item><title>Python单元测试</title><link>/2016/06/02/python%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/</link><pubDate>Thu, 02 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/02/python%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;如果你听说过“测试驱动开发”（TDD：Test-Driven Development），单元测试就不陌生。&lt;/p&gt;
&lt;p&gt;单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。&lt;/p&gt;
&lt;p&gt;比如对函数&lt;code&gt;abs()&lt;/code&gt;，我们可以编写出以下几个测试用例：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入正数，比如&lt;code&gt;1&lt;/code&gt;、&lt;code&gt;1.2&lt;/code&gt;、&lt;code&gt;0.99&lt;/code&gt;，期待返回值与输入相同；&lt;/li&gt;
&lt;li&gt;输入负数，比如&lt;code&gt;-1&lt;/code&gt;、&lt;code&gt;-1.2&lt;/code&gt;、&lt;code&gt;-0.99&lt;/code&gt;，期待返回值与输入相反；&lt;/li&gt;
&lt;li&gt;输入&lt;code&gt;0&lt;/code&gt;，期待返回&lt;code&gt;0&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;输入非数值类型，比如&lt;code&gt;None&lt;/code&gt;、&lt;code&gt;[]&lt;/code&gt;、&lt;code&gt;{}&lt;/code&gt;，期待抛出&lt;code&gt;TypeError&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把上面的测试用例放到一个测试模块里，就是一个完整的单元测试。&lt;/p&gt;
&lt;p&gt;如果单元测试通过，说明我们测试的这个函数能够正常工作。如果单元测试不通过，要么函数有bug，要么测试条件输入不正确，总之，需要修复使单元测试能够通过。&lt;/p&gt;
&lt;p&gt;单元测试通过后有什么意义呢？如果我们对&lt;code&gt;abs()&lt;/code&gt;函数代码做了修改，只需要再跑一遍单元测试，如果通过，说明我们的修改不会对&lt;code&gt;abs()&lt;/code&gt;函数原有的行为造成影响，如果测试不通过，说明我们的修改与原有行为不一致，要么修改代码，要么修改测试。&lt;/p&gt;
&lt;p&gt;这种以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候，可以极大程度地保证该模块行为仍然是正确的。&lt;/p&gt;
&lt;p&gt;我们来编写一个&lt;code&gt;Dict&lt;/code&gt;类，这个类的行为和&lt;code&gt;dict&lt;/code&gt;一致，但是可以通过属性来访问，用起来就像下面这样：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; d = Dict(a=1, b=2)
&amp;gt;&amp;gt;&amp;gt; d[&amp;#39;a&amp;#39;]
1
&amp;gt;&amp;gt;&amp;gt; d.a
1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;mydict.py&lt;/code&gt;代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Dict&lt;/span&gt;(dict):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;(self, &lt;span style="color:#f92672"&gt;**&lt;/span&gt;kw):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; super()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;(&lt;span style="color:#f92672"&gt;**&lt;/span&gt;kw)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__getattr__&lt;/span&gt;(self, key):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; self[key]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;KeyError&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AttributeError&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;r&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;Dict&amp;#39; object has no attribute &amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; key)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__setattr__&lt;/span&gt;(self, key, value):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self[key] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; value
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;为了编写单元测试，我们需要引入Python自带的&lt;code&gt;unittest&lt;/code&gt;模块，编写&lt;code&gt;mydict_test.py&lt;/code&gt;如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; unittest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; mydict &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Dict
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TestDict&lt;/span&gt;(unittest&lt;span style="color:#f92672"&gt;.&lt;/span&gt;TestCase):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_init&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; d &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Dict(a&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, b&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;test&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;assertEqual(d&lt;span style="color:#f92672"&gt;.&lt;/span&gt;a, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;assertEqual(d&lt;span style="color:#f92672"&gt;.&lt;/span&gt;b, &lt;span style="color:#e6db74"&gt;&amp;#39;test&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;assertTrue(isinstance(d, dict))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_key&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; d &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Dict()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; d[&lt;span style="color:#e6db74"&gt;&amp;#39;key&amp;#39;&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;assertEqual(d&lt;span style="color:#f92672"&gt;.&lt;/span&gt;key, &lt;span style="color:#e6db74"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_attr&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; d &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Dict()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; d&lt;span style="color:#f92672"&gt;.&lt;/span&gt;key &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;assertTrue(&lt;span style="color:#e6db74"&gt;&amp;#39;key&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;in&lt;/span&gt; d)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;assertEqual(d[&lt;span style="color:#e6db74"&gt;&amp;#39;key&amp;#39;&lt;/span&gt;], &lt;span style="color:#e6db74"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_keyerror&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; d &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Dict()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;assertRaises(&lt;span style="color:#a6e22e"&gt;KeyError&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; value &lt;span style="color:#f92672"&gt;=&lt;/span&gt; d[&lt;span style="color:#e6db74"&gt;&amp;#39;empty&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_attrerror&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; d &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Dict()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;assertRaises(&lt;span style="color:#a6e22e"&gt;AttributeError&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; value &lt;span style="color:#f92672"&gt;=&lt;/span&gt; d&lt;span style="color:#f92672"&gt;.&lt;/span&gt;empty
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;编写单元测试时，我们需要编写一个测试类，从&lt;code&gt;unittest.TestCase&lt;/code&gt;继承。&lt;/p&gt;</description></item><item><title>Python调试</title><link>/2016/06/01/python%E8%B0%83%E8%AF%95/</link><pubDate>Wed, 01 Jun 2016 00:00:00 +0000</pubDate><guid>/2016/06/01/python%E8%B0%83%E8%AF%95/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="print方法"&gt;print方法&lt;/h3&gt;
&lt;p&gt;程序能一次写完并正常运行的概率很小，基本不超过1%。总会有各种各样的bug需要修正。有的bug很简单，看看错误信息就知道，有的bug很复杂，我们需要知道出错时，哪些变量的值是正确的，哪些变量的值是错误的，因此，需要一整套调试程序的手段来修复bug。&lt;/p&gt;
&lt;p&gt;第一种方法简单直接粗暴有效，就是用&lt;code&gt;print()&lt;/code&gt;把可能有问题的变量打印出来看看：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;foo&lt;/span&gt;(s):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; n &lt;span style="color:#f92672"&gt;=&lt;/span&gt; int(s)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;gt;&amp;gt;&amp;gt; n = &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%d&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;%&lt;/span&gt; n)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; n
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; foo(&lt;span style="color:#e6db74"&gt;&amp;#39;0&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行后在输出中查找打印的变量值：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ python err.py
&amp;gt;&amp;gt;&amp;gt; n = 0
Traceback (most recent call last):
...
ZeroDivisionError: integer division or modulo by zero
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用&lt;code&gt;print()&lt;/code&gt;最大的坏处是将来还得删掉它，想想程序里到处都是&lt;code&gt;print()&lt;/code&gt;，运行结果也会包含很多垃圾信息。所以，我们又有第二种方法。&lt;/p&gt;
&lt;h3 id="断言"&gt;断言&lt;/h3&gt;
&lt;p&gt;凡是用&lt;code&gt;print()&lt;/code&gt;来辅助查看的地方，都可以用断言（assert）来替代：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;foo&lt;/span&gt;(s):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; n &lt;span style="color:#f92672"&gt;=&lt;/span&gt; int(s)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;assert&lt;/span&gt; n &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;n is zero!&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; n
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; foo(&lt;span style="color:#e6db74"&gt;&amp;#39;0&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;assert&lt;/code&gt;的意思是，表达式&lt;code&gt;n != 0&lt;/code&gt;应该是&lt;code&gt;True&lt;/code&gt;，否则，根据程序运行的逻辑，后面的代码肯定会出错。&lt;/p&gt;
&lt;p&gt;如果断言失败，&lt;code&gt;assert&lt;/code&gt;语句本身就会抛出&lt;code&gt;AssertionError&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ python err.py
Traceback (most recent call last):
...
AssertionError: n is zero!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;程序中如果到处充斥着&lt;code&gt;assert&lt;/code&gt;，和&lt;code&gt;print()&lt;/code&gt;相比也好不到哪去。不过，启动Python解释器时可以用&lt;code&gt;-O&lt;/code&gt;参数来关闭&lt;code&gt;assert&lt;/code&gt;：&lt;/p&gt;</description></item><item><title>Python错误处理</title><link>/2016/05/31/python%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/</link><pubDate>Tue, 31 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/31/python%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在程序运行过程中，总会遇到各种各样的错误。&lt;/p&gt;
&lt;p&gt;有的错误是程序编写有问题造成的，比如本来应该输出整数结果输出了字符串，这种错误我们通常称之为bug，bug是必须修复的。&lt;/p&gt;
&lt;p&gt;有的错误是用户输入造成的，比如让用户输入email地址，结果得到一个空字符串，这种错误可以通过检查用户输入来做相应的处理。&lt;/p&gt;
&lt;p&gt;还有一类错误是完全无法在程序运行过程中预测的，比如写入文件的时候，磁盘满了，写不进去了，或者从网络抓取数据，网络突然断掉了。这类错误也称为异常，在程序中通常是必须处理的，否则，程序会因为各种问题终止并退出。&lt;/p&gt;
&lt;p&gt;Python内置了一套异常处理机制，来帮助我们进行错误处理。&lt;/p&gt;
&lt;p&gt;在程序运行的过程中，如果发生了错误，可以事先约定返回一个错误代码，这样，就可以知道是否有错，以及出错的原因。在操作系统提供的调用中，返回错误码非常常见。比如打开文件的函数&lt;code&gt;open()&lt;/code&gt;，成功时返回文件描述符（就是一个整数），出错时返回&lt;code&gt;-1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;用错误码来表示是否出错十分不便，因为函数本身应该返回的正常结果和错误码混在一起，造成调用者必须用大量的代码来判断是否出错：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;foo&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; r &lt;span style="color:#f92672"&gt;=&lt;/span&gt; some_function()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; r&lt;span style="color:#f92672"&gt;==&lt;/span&gt;(&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; (&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# do something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; r
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bar&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; r &lt;span style="color:#f92672"&gt;=&lt;/span&gt; foo()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; r&lt;span style="color:#f92672"&gt;==&lt;/span&gt;(&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;Error&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一旦出错，还要一级一级上报，直到某个函数可以处理该错误（比如，给用户输出一个错误信息）。&lt;/p&gt;
&lt;p&gt;所以高级语言通常都内置了一套&lt;code&gt;try...except...finally...&lt;/code&gt;的错误处理机制，Python也不例外。&lt;/p&gt;
&lt;h3 id="try命令"&gt;try命令&lt;/h3&gt;
&lt;p&gt;让我们用一个例子来看看&lt;code&gt;try&lt;/code&gt;的机制：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Python" data-lang="Python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;try...&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; r &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;result:&amp;#39;&lt;/span&gt;, r)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ZeroDivisionError&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; e:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;except:&amp;#39;&lt;/span&gt;, e)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;finally&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#39;finally...&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(&lt;span style="color:#e6db74"&gt;&amp;#39;END&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当我们认为某些代码可能会出错时，就可以用&lt;code&gt;try&lt;/code&gt;来运行这段代码，如果执行出错，则后续代码不会继续执行，而是直接跳转至错误处理代码，即&lt;code&gt;except&lt;/code&gt;语句块，执行完&lt;code&gt;except&lt;/code&gt;后，如果有&lt;code&gt;finally&lt;/code&gt;语句块，则执行&lt;code&gt;finally&lt;/code&gt;语句块，至此，执行完毕。&lt;/p&gt;
&lt;p&gt;上面的代码在计算&lt;code&gt;10 / 0&lt;/code&gt;时会产生一个除法运算错误：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;try...
except: division by zero
finally...
END
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从输出可以看到，当错误发生时，后续语句&lt;code&gt;print('result:', r)&lt;/code&gt;不会被执行，&lt;code&gt;except&lt;/code&gt;由于捕获到&lt;code&gt;ZeroDivisionError&lt;/code&gt;，因此被执行。最后，&lt;code&gt;finally&lt;/code&gt;语句被执行。然后，程序继续按照流程往下走。&lt;/p&gt;
&lt;p&gt;如果把除数&lt;code&gt;0&lt;/code&gt;改成&lt;code&gt;2&lt;/code&gt;，则执行结果如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;try...
result: 5
finally...
END
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;由于没有错误发生，所以&lt;code&gt;except&lt;/code&gt;语句块不会被执行，但是&lt;code&gt;finally&lt;/code&gt;如果有，则一定会被执行（可以没有&lt;code&gt;finally&lt;/code&gt;语句）。&lt;/p&gt;</description></item><item><title>Python元类</title><link>/2016/05/27/python%E5%85%83%E7%B1%BB/</link><pubDate>Fri, 27 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/27/python%E5%85%83%E7%B1%BB/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="type"&gt;type()&lt;/h3&gt;
&lt;p&gt;动态语言和静态语言最大的不同，就是函数和类的定义，不是编译时定义的，而是运行时动态创建的。&lt;/p&gt;
&lt;p&gt;比方说我们要定义一个&lt;code&gt;Hello&lt;/code&gt;的class，就写一个&lt;code&gt;hello.py&lt;/code&gt;模块：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Hello(object):
def hello(self, name=&amp;#39;world&amp;#39;):
print(&amp;#39;Hello, %s.&amp;#39; % name)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当Python解释器载入&lt;code&gt;hello&lt;/code&gt;模块时，就会依次执行该模块的所有语句，执行结果就是动态创建出一个&lt;code&gt;Hello&lt;/code&gt;的class对象，测试如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from hello import Hello
&amp;gt;&amp;gt;&amp;gt; h = Hello()
&amp;gt;&amp;gt;&amp;gt; h.hello()
Hello, world.
&amp;gt;&amp;gt;&amp;gt; print(type(Hello))
&amp;lt;class &amp;#39;type&amp;#39;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; print(type(h))
&amp;lt;class &amp;#39;hello.Hello&amp;#39;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;type()&lt;/code&gt;函数可以查看一个类型或变量的类型，&lt;code&gt;Hello&lt;/code&gt;是一个class，它的类型就是&lt;code&gt;type&lt;/code&gt;，而&lt;code&gt;h&lt;/code&gt;是一个实例，它的类型就是class &lt;code&gt;Hello&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我们说class的定义是运行时动态创建的，而创建class的方法就是使用&lt;code&gt;type()&lt;/code&gt;函数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;type()&lt;/code&gt;函数既可以返回一个对象的类型，又可以创建出新的类型，比如，我们可以通过&lt;code&gt;type()&lt;/code&gt;函数创建出&lt;code&gt;Hello&lt;/code&gt;类，而无需通过&lt;code&gt;class Hello(object)...&lt;/code&gt;的定义：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; def fn(self, name=&amp;#39;world&amp;#39;): # 先定义函数
... print(&amp;#39;Hello, %s.&amp;#39; % name)
...
&amp;gt;&amp;gt;&amp;gt; Hello = type(&amp;#39;Hello&amp;#39;, (object,), dict(hello=fn)) # 创建Hello class
&amp;gt;&amp;gt;&amp;gt; h = Hello()
&amp;gt;&amp;gt;&amp;gt; h.hello()
Hello, world.
&amp;gt;&amp;gt;&amp;gt; print(type(Hello))
&amp;lt;class &amp;#39;type&amp;#39;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; print(type(h))
&amp;lt;class &amp;#39;__main__.Hello&amp;#39;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;要创建一个class对象，&lt;code&gt;type()&lt;/code&gt;函数依次传入3个参数：&lt;/p&gt;</description></item><item><title>Python枚举类</title><link>/2016/05/26/python%E6%9E%9A%E4%B8%BE%E7%B1%BB/</link><pubDate>Thu, 26 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/26/python%E6%9E%9A%E4%B8%BE%E7%B1%BB/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;当我们需要定义常量时，一个办法是用大写变量通过整数来定义，例如月份：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;好处是简单，缺点是类型是&lt;code&gt;int&lt;/code&gt;，并且仍然是变量。&lt;/p&gt;
&lt;p&gt;更好的方法是为这样的枚举类型定义一个class类型，然后，每个常量都是class的一个唯一实例。Python提供了&lt;code&gt;Enum&lt;/code&gt;类来实现这个功能：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from enum import Enum
Month = Enum(&amp;#39;Month&amp;#39;, (&amp;#39;Jan&amp;#39;, &amp;#39;Feb&amp;#39;, &amp;#39;Mar&amp;#39;, &amp;#39;Apr&amp;#39;, &amp;#39;May&amp;#39;, &amp;#39;Jun&amp;#39;, &amp;#39;Jul&amp;#39;, &amp;#39;Aug&amp;#39;, &amp;#39;Sep&amp;#39;, &amp;#39;Oct&amp;#39;, &amp;#39;Nov&amp;#39;, &amp;#39;Dec&amp;#39;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样我们就获得了&lt;code&gt;Month&lt;/code&gt;类型的枚举类，可以直接使用&lt;code&gt;Month.Jan&lt;/code&gt;来引用一个常量，或者枚举它的所有成员：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;for name, member in Month.__members__.items():
print(name, &amp;#39;=&amp;gt;&amp;#39;, member, &amp;#39;,&amp;#39;, member.value)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;value&lt;/code&gt;属性则是自动赋给成员的&lt;code&gt;int&lt;/code&gt;常量，默认从&lt;code&gt;1&lt;/code&gt;开始计数。&lt;/p&gt;
&lt;p&gt;如果需要更精确地控制枚举类型，可以从&lt;code&gt;Enum&lt;/code&gt;派生出自定义类：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;@unique&lt;/code&gt;装饰器可以帮助我们检查保证没有重复值。&lt;/p&gt;</description></item><item><title>Python定制类</title><link>/2016/05/24/python%E5%AE%9A%E5%88%B6%E7%B1%BB/</link><pubDate>Tue, 24 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/24/python%E5%AE%9A%E5%88%B6%E7%B1%BB/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;看到类似&lt;code&gt;__slots__&lt;/code&gt;这种形如&lt;code&gt;__xxx__&lt;/code&gt;的变量或者函数名就要注意，这些在Python中是有特殊用途的。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;__slots__&lt;/code&gt;我们已经知道怎么用了，&lt;code&gt;__len__()&lt;/code&gt;方法我们也知道是为了能让class作用于&lt;code&gt;len()&lt;/code&gt;函数。&lt;/p&gt;
&lt;p&gt;除此之外，Python的class中还有许多这样有特殊用途的函数，可以帮助我们定制类。&lt;/p&gt;
&lt;h3 id="__str__-__repr__"&gt;&lt;code&gt;__str__&lt;/code&gt;, &lt;code&gt;__repr__&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;我们先定义一个&lt;code&gt;Student&lt;/code&gt;类，打印一个实例：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; class Student(object):
... def __init__(self, name):
... self.name = name
...
&amp;gt;&amp;gt;&amp;gt; print(Student(&amp;#39;Michael&amp;#39;))
&amp;lt;__main__.Student object at 0x109afb190&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;打印出一堆&lt;code&gt;&amp;lt;__main__.Student object at 0x109afb190&amp;gt;&lt;/code&gt;，不好看。&lt;/p&gt;
&lt;p&gt;怎么才能打印得好看呢？只需要定义好&lt;code&gt;__str__()&lt;/code&gt;方法，返回一个好看的字符串就可以了：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return &amp;#39;Student object (name: %s)&amp;#39; % self.name
...
&amp;gt;&amp;gt;&amp;gt; print(Student(&amp;#39;Michael&amp;#39;))
Student object (name: Michael)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样打印出来的实例，不但好看，而且容易看出实例内部重要的数据。&lt;/p&gt;
&lt;p&gt;但是细心的朋友会发现直接敲变量不用&lt;code&gt;print&lt;/code&gt;，打印出来的实例还是不好看：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; s = Student(&amp;#39;Michael&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; s
&amp;lt;__main__.Student object at 0x109afb310&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这是因为直接显示变量调用的不是&lt;code&gt;__str__()&lt;/code&gt;，而是&lt;code&gt;__repr__()&lt;/code&gt;，两者的区别是&lt;code&gt;__str__()&lt;/code&gt;返回用户看到的字符串，而&lt;code&gt;__repr__()&lt;/code&gt;返回程序开发者看到的字符串，也就是说，&lt;code&gt;__repr__()&lt;/code&gt;是为调试服务的。&lt;/p&gt;</description></item><item><title>Python多重继承</title><link>/2016/05/23/python%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF/</link><pubDate>Mon, 23 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/23/python%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="多重继承"&gt;多重继承&lt;/h2&gt;
&lt;p&gt;一个子类同时获得多个父类的所有功能。&lt;/p&gt;
&lt;p&gt;继承是面向对象编程的一个重要的方式，因为通过继承，子类就可以扩展父类的功能。&lt;/p&gt;
&lt;p&gt;回忆一下&lt;code&gt;Animal&lt;/code&gt;类层次的设计，假设我们要实现以下4种动物：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dog - 狗狗；&lt;/li&gt;
&lt;li&gt;Bat - 蝙蝠；&lt;/li&gt;
&lt;li&gt;Parrot - 鹦鹉；&lt;/li&gt;
&lt;li&gt;Ostrich - 鸵鸟。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果按照哺乳动物和鸟类归类，我们可以设计出这样的类的层次：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; ┌───────────────┐
│ Animal │
└───────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Mammal │ │ Bird │
└─────────────┘ └─────────────┘
│ │
┌─────┴──────┐ ┌─────┴──────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Dog │ │ Bat │ │ Parrot │ │ Ostrich │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是如果按照“能跑”和“能飞”来归类，我们就应该设计出这样的类的层次：&lt;/p&gt;</description></item><item><title>Python内置@property装饰器实现方法转属性</title><link>/2016/05/20/python%E5%86%85%E7%BD%AE@property%E8%A3%85%E9%A5%B0%E5%99%A8%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%B3%95%E8%BD%AC%E5%B1%9E%E6%80%A7/</link><pubDate>Fri, 20 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/20/python%E5%86%85%E7%BD%AE@property%E8%A3%85%E9%A5%B0%E5%99%A8%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%B3%95%E8%BD%AC%E5%B1%9E%E6%80%A7/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在绑定属性时，如果我们直接把属性暴露出去，虽然写起来很简单，但是，没办法检查参数，导致可以把成绩随便改：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;s = Student()
s.score = 9999
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这显然不合逻辑。为了限制score的范围，可以通过一个&lt;code&gt;set_score()&lt;/code&gt;方法来设置成绩，再通过一个&lt;code&gt;get_score()&lt;/code&gt;来获取成绩，这样，在&lt;code&gt;set_score()&lt;/code&gt;方法里，就可以检查参数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError(&amp;#39;score must be an integer!&amp;#39;)
if value &amp;lt; 0 or value &amp;gt; 100:
raise ValueError(&amp;#39;score must between 0 ~ 100!&amp;#39;)
self._score = value
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在，对任意的Student实例进行操作，就不能随心所欲地设置score了：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; s = Student()
&amp;gt;&amp;gt;&amp;gt; s.set_score(60) # ok!
&amp;gt;&amp;gt;&amp;gt; s.get_score()
60
&amp;gt;&amp;gt;&amp;gt; s.set_score(9999)
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是，上面的调用方法又略显复杂，没有直接用属性这么直接简单。&lt;/p&gt;</description></item><item><title>Python变量__slots__属性限制</title><link>/2016/05/19/python%E5%8F%98%E9%87%8F__slots__%E5%B1%9E%E6%80%A7%E9%99%90%E5%88%B6/</link><pubDate>Thu, 19 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/19/python%E5%8F%98%E9%87%8F__slots__%E5%B1%9E%E6%80%A7%E9%99%90%E5%88%B6/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;正常情况下，当我们定义了一个class，创建了一个class的实例后，我们可以给该实例绑定任何属性和方法，这就是动态语言的灵活性。先定义class：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Student(object):
pass
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后，尝试给实例绑定一个属性：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; s = Student()
&amp;gt;&amp;gt;&amp;gt; s.name = &amp;#39;Michael&amp;#39; # 动态给实例绑定一个属性
&amp;gt;&amp;gt;&amp;gt; print(s.name)
Michael
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;还可以尝试给实例绑定一个方法：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
&amp;gt;&amp;gt;&amp;gt; from types import MethodType
&amp;gt;&amp;gt;&amp;gt; s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
&amp;gt;&amp;gt;&amp;gt; s.set_age(25) # 调用实例方法
&amp;gt;&amp;gt;&amp;gt; s.age # 测试结果
25
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是，给一个实例绑定的方法，对另一个实例是不起作用的：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; s2 = Student() # 创建新的实例
&amp;gt;&amp;gt;&amp;gt; s2.set_age(25) # 尝试调用方法
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
AttributeError: &amp;#39;Student&amp;#39; object has no attribute &amp;#39;set_age&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;为了给所有实例都绑定方法，可以给class绑定方法：&lt;/p&gt;</description></item><item><title>Python实例属性和类属性</title><link>/2016/05/18/python%E5%AE%9E%E4%BE%8B%E5%B1%9E%E6%80%A7%E5%92%8C%E7%B1%BB%E5%B1%9E%E6%80%A7/</link><pubDate>Wed, 18 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/18/python%E5%AE%9E%E4%BE%8B%E5%B1%9E%E6%80%A7%E5%92%8C%E7%B1%BB%E5%B1%9E%E6%80%A7/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;由于Python是动态语言，根据类创建的实例可以任意绑定属性。&lt;/p&gt;
&lt;p&gt;给实例绑定属性的方法是通过实例变量，或者通过&lt;code&gt;self&lt;/code&gt;变量：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Student&lt;/span&gt;(object):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__init__&lt;/span&gt;(self, name):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;s &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Student(&lt;span style="color:#e6db74"&gt;&amp;#39;Bob&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;s&lt;span style="color:#f92672"&gt;.&lt;/span&gt;score &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;90&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但是，如果&lt;code&gt;Student&lt;/code&gt;类本身需要绑定一个属性呢？可以直接在class中定义属性，这种属性是类属性，归&lt;code&gt;Student&lt;/code&gt;类所有：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Student&lt;/span&gt;(object):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Student&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当我们定义了一个类属性后，这个属性虽然归类所有，但类的所有实例都可以访问到。来测试一下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; class Student(object):
... name = &amp;#39;Student&amp;#39;
...
&amp;gt;&amp;gt;&amp;gt; s = Student() # 创建实例s
&amp;gt;&amp;gt;&amp;gt; print(s.name) # 打印name属性，因为实例并没有name属性，所以会继续查找class的name属性
Student
&amp;gt;&amp;gt;&amp;gt; print(Student.name) # 打印类的name属性
Student
&amp;gt;&amp;gt;&amp;gt; s.name = &amp;#39;Michael&amp;#39; # 给实例绑定name属性
&amp;gt;&amp;gt;&amp;gt; print(s.name) # 由于实例属性优先级比类属性高，因此，它会屏蔽掉类的name属性
Michael
&amp;gt;&amp;gt;&amp;gt; print(Student.name) # 但是类属性并未消失，用Student.name仍然可以访问
Student
&amp;gt;&amp;gt;&amp;gt; del s.name # 如果删除实例的name属性
&amp;gt;&amp;gt;&amp;gt; print(s.name) # 再次调用s.name，由于实例的name属性没有找到，类的name属性就显示出来了
Student
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从上面的例子可以看出，在编写程序的时候，千万不要对实例属性和类属性使用相同的名字，因为相同名称的实例属性将屏蔽掉类属性，但是当你删除实例属性后，再使用相同的名称，访问到的将是类属性。&lt;/p&gt;</description></item><item><title>Python</title><link>/2016/05/17/python/</link><pubDate>Tue, 17 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/17/python/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="路径和名字的获取方式"&gt;路径和名字的获取方式&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import os,sys
if __name__ == &amp;#39;__main__&amp;#39;:
filename = __file__
print(&amp;#34;filename : &amp;#34;, filename)
filepath = os.path.abspath(filename)
print(&amp;#34;filepath : &amp;#34;, filepath)
dirname = os.path.dirname(__file__)
print(&amp;#34;dirname : &amp;#34;, dirname)
dirpath = os.path.abspath(dirname)
print(&amp;#34;dirpath : &amp;#34;, dirpath)
输出：
filename : C:/Users/dell/Desktop/git/SimpleStorageServer-Windows10/test.py
filepath : C:\Users\dell\Desktop\git\SimpleStorageServer-Windows10\test.py
dirname : C:/Users/dell/Desktop/git/SimpleStorageServer-Windows10
dirpath : C:\Users\dell\Desktop\git\SimpleStorageServer-Windows10
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="oswalk的用法"&gt;os.walk的用法&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;os.walk(directorypath, topdown=True, onerror=None, followlinks=False)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这里directorypath 必须是一个目录的路径，如果是一个文件的路径，则不会得到任何东西&lt;/p&gt;
&lt;p&gt;第一个为起始路径，第二个为起始路径下的文件夹，第三个是起始路径下的文件。
dirpath 是一个string，代表目录的路径，
dirnames 是一个list，包含了dirpath下所有子目录的名字。
filenames 是一个list，包含了非目录文件的名字。
这些名字不包含路径信息，如果需要得到全路径，需要使用os.path.join(dirpath, name).通过for循环自动完成递归枚举&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import os,sys
if __name__ == &amp;#39;__main__&amp;#39;:
filename = __file__
print(&amp;#34;filename : &amp;#34;, filename)
filepath = os.path.abspath(filename)
print(&amp;#34;filepath : &amp;#34;, filepath)
dirname = os.path.dirname(__file__)
print(&amp;#34;dirname : &amp;#34;, dirname)
dirpath = os.path.abspath(dirname)
print(&amp;#34;dirpath : &amp;#34;, dirpath)
# for dirpaths, dirnames, filenames in os.walk(filepath):
# print(dirpaths, dirnames, filenames)
# for dirpaths, dirnames, filenames in os.walk(dirpath, topdown=False):
for dirpaths, dirnames, filenames in os.walk(dirpath):
print(dirpaths, dirnames, filenames)
# print(dirpaths, filenames)
# for i in filenames:
# print(os.path.join(dirpaths,i))
# for j in dirnames:
# print(os.path.join(dirpaths, j))
# pass
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;输出:&lt;/p&gt;</description></item><item><title>Python获取对象信息</title><link>/2016/05/17/python%E8%8E%B7%E5%8F%96%E5%AF%B9%E8%B1%A1%E4%BF%A1%E6%81%AF/</link><pubDate>Tue, 17 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/17/python%E8%8E%B7%E5%8F%96%E5%AF%B9%E8%B1%A1%E4%BF%A1%E6%81%AF/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;当我们拿到一个对象的引用时，如何知道这个对象是什么类型、有哪些方法呢？&lt;/p&gt;
&lt;h3 id="使用type"&gt;使用type()&lt;/h3&gt;
&lt;p&gt;首先，我们来判断对象类型，使用&lt;code&gt;type()&lt;/code&gt;函数：&lt;/p&gt;
&lt;p&gt;基本类型都可以用&lt;code&gt;type()&lt;/code&gt;判断：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; type(123)
&amp;lt;class &amp;#39;int&amp;#39;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; type(&amp;#39;str&amp;#39;)
&amp;lt;class &amp;#39;str&amp;#39;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; type(None)
&amp;lt;type(None) &amp;#39;NoneType&amp;#39;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果一个变量指向函数或者类，也可以用&lt;code&gt;type()&lt;/code&gt;判断：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; type(abs)
&amp;lt;class &amp;#39;builtin_function_or_method&amp;#39;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; type(a)
&amp;lt;class &amp;#39;__main__.Animal&amp;#39;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是&lt;code&gt;type()&lt;/code&gt;函数返回的是什么类型呢？它返回对应的Class类型。如果我们要在&lt;code&gt;if&lt;/code&gt;语句中判断，就需要比较两个变量的type类型是否相同：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; type(123)==type(456)
True
&amp;gt;&amp;gt;&amp;gt; type(123)==int
True
&amp;gt;&amp;gt;&amp;gt; type(&amp;#39;abc&amp;#39;)==type(&amp;#39;123&amp;#39;)
True
&amp;gt;&amp;gt;&amp;gt; type(&amp;#39;abc&amp;#39;)==str
True
&amp;gt;&amp;gt;&amp;gt; type(&amp;#39;abc&amp;#39;)==type(123)
False
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="types"&gt;types&lt;/h3&gt;
&lt;p&gt;判断基本数据类型可以直接写&lt;code&gt;int&lt;/code&gt;，&lt;code&gt;str&lt;/code&gt;等，但如果要判断一个对象是否是函数怎么办？可以使用&lt;code&gt;types&lt;/code&gt;模块中定义的常量：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import types
&amp;gt;&amp;gt;&amp;gt; def fn():
... pass
...
&amp;gt;&amp;gt;&amp;gt; type(fn)==types.FunctionType
True
&amp;gt;&amp;gt;&amp;gt; type(abs)==types.BuiltinFunctionType
True
&amp;gt;&amp;gt;&amp;gt; type(lambda x: x)==types.LambdaType
True
&amp;gt;&amp;gt;&amp;gt; type((x for x in range(10)))==types.GeneratorType
True
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="使用isinstance"&gt;使用isinstance()&lt;/h3&gt;
&lt;p&gt;对于class的继承关系来说，使用&lt;code&gt;type()&lt;/code&gt;就很不方便。我们要判断class的类型，可以使用&lt;code&gt;isinstance()&lt;/code&gt;函数。&lt;/p&gt;</description></item><item><title>Python类的继承和多态</title><link>/2016/05/16/python%E7%B1%BB%E7%9A%84%E7%BB%A7%E6%89%BF%E5%92%8C%E5%A4%9A%E6%80%81/</link><pubDate>Mon, 16 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/16/python%E7%B1%BB%E7%9A%84%E7%BB%A7%E6%89%BF%E5%92%8C%E5%A4%9A%E6%80%81/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="继承"&gt;继承&lt;/h2&gt;
&lt;p&gt;在OOP程序设计中，当我们定义一个class的时候，可以从某个现有的class继承，新的class称为子类（Subclass），而被继承的class称为基类、父类或超类（Base class、Super class）。&lt;/p&gt;
&lt;p&gt;比如，我们已经编写了一个名为&lt;code&gt;Animal&lt;/code&gt;的class，有一个&lt;code&gt;run()&lt;/code&gt;方法可以直接打印：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Animal(object):
def run(self):
print(&amp;#39;Animal is running...&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当我们需要编写&lt;code&gt;Dog&lt;/code&gt;和&lt;code&gt;Cat&lt;/code&gt;类时，就可以直接从&lt;code&gt;Animal&lt;/code&gt;类继承：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Dog(Animal):
pass
class Cat(Animal):
pass
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于&lt;code&gt;Dog&lt;/code&gt;来说，&lt;code&gt;Animal&lt;/code&gt;就是它的父类，对于&lt;code&gt;Animal&lt;/code&gt;来说，&lt;code&gt;Dog&lt;/code&gt;就是它的子类。&lt;code&gt;Cat&lt;/code&gt;和&lt;code&gt;Dog&lt;/code&gt;类似。&lt;/p&gt;
&lt;p&gt;继承有什么好处？最大的好处是子类获得了父类的全部功能。由于&lt;code&gt;Animial&lt;/code&gt;实现了&lt;code&gt;run()&lt;/code&gt;方法，因此，&lt;code&gt;Dog&lt;/code&gt;和&lt;code&gt;Cat&lt;/code&gt;作为它的子类，什么事也没干，就自动拥有了&lt;code&gt;run()&lt;/code&gt;方法：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dog = Dog()
dog.run()
cat = Cat()
cat.run()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;运行结果如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Animal is running...
Animal is running...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然，也可以对子类增加一些方法，比如Dog类：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Dog(Animal):
def run(self):
print(&amp;#39;Dog is running...&amp;#39;)
def eat(self):
print(&amp;#39;Eating meat...&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="多态"&gt;多态&lt;/h2&gt;
&lt;p&gt;继承的第二个好处需要我们对代码做一点改进。你看到了，无论是&lt;code&gt;Dog&lt;/code&gt;还是&lt;code&gt;Cat&lt;/code&gt;，它们&lt;code&gt;run()&lt;/code&gt;的时候，显示的都是&lt;code&gt;Animal is running...&lt;/code&gt;，符合逻辑的做法是分别显示&lt;code&gt;Dog is running...&lt;/code&gt;和&lt;code&gt;Cat is running...&lt;/code&gt;，因此，对&lt;code&gt;Dog&lt;/code&gt;和&lt;code&gt;Cat&lt;/code&gt;类改进如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Dog(Animal):
def run(self):
print(&amp;#39;Dog is running...&amp;#39;)
class Cat(Animal):
def run(self):
print(&amp;#39;Cat is running...&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;再次运行，结果如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Dog is running...
Cat is running...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当子类和父类都存在相同的&lt;code&gt;run()&lt;/code&gt;方法时，我们说，子类的&lt;code&gt;run()&lt;/code&gt;覆盖了父类的&lt;code&gt;run()&lt;/code&gt;，在代码运行的时候，总是会调用子类的&lt;code&gt;run()&lt;/code&gt;。这样，我们就获得了继承的另一个好处：多态。&lt;/p&gt;</description></item><item><title>Python类的访问限制</title><link>/2016/05/15/python%E7%B1%BB%E7%9A%84%E8%AE%BF%E9%97%AE%E9%99%90%E5%88%B6/</link><pubDate>Sun, 15 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/15/python%E7%B1%BB%E7%9A%84%E8%AE%BF%E9%97%AE%E9%99%90%E5%88%B6/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在Class内部，可以有属性和方法，而外部代码可以通过直接调用实例变量的方法来操作数据，这样，就隐藏了内部的复杂逻辑。&lt;/p&gt;
&lt;p&gt;但是，从前面Student类的定义来看，外部代码还是可以自由地修改一个实例的&lt;code&gt;name&lt;/code&gt;、&lt;code&gt;score&lt;/code&gt;属性：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; bart = Student(&amp;#39;Bart Simpson&amp;#39;, 59)
&amp;gt;&amp;gt;&amp;gt; bart.score
59
&amp;gt;&amp;gt;&amp;gt; bart.score = 99
&amp;gt;&amp;gt;&amp;gt; bart.score
99
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果要让内部属性不被外部访问，可以把属性的名称前加上两个下划线&lt;code&gt;__&lt;/code&gt;，在Python中，实例的变量名如果以&lt;code&gt;__&lt;/code&gt;开头，就变成了一个私有变量（private），只有内部可以访问，外部不能访问，所以，我们把Student类改一改：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print(&amp;#39;%s: %s&amp;#39; % (self.__name, self.__score))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;改完后，对于外部代码来说，没什么变动，但是已经无法从外部访问&lt;code&gt;实例变量.__name&lt;/code&gt;和&lt;code&gt;实例变量.__score&lt;/code&gt;了：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; bart = Student(&amp;#39;Bart Simpson&amp;#39;, 59)
&amp;gt;&amp;gt;&amp;gt; bart.__name
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
AttributeError: &amp;#39;Student&amp;#39; object has no attribute &amp;#39;__name&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样就确保了外部代码不能随意修改对象内部的状态，这样通过访问限制的保护，代码更加健壮。&lt;/p&gt;
&lt;p&gt;但是如果外部代码要获取name和score怎么办？可以给Student类增加&lt;code&gt;get_name&lt;/code&gt;和&lt;code&gt;get_score&lt;/code&gt;这样的方法：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果又要允许外部代码修改score怎么办？可以再给Student类增加&lt;code&gt;set_score&lt;/code&gt;方法：&lt;/p&gt;</description></item><item><title>Python类和实例</title><link>/2016/05/14/python%E7%B1%BB%E5%92%8C%E5%AE%9E%E4%BE%8B/</link><pubDate>Sat, 14 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/14/python%E7%B1%BB%E5%92%8C%E5%AE%9E%E4%BE%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;面向对象最重要的概念就是类（Class）和实例（Instance），必须牢记类是抽象的模板，比如Student类，而实例是根据类创建出来的一个个具体的“对象”，每个对象都拥有相同的方法，但各自的数据可能不同。&lt;/p&gt;
&lt;p&gt;仍以Student类为例，在Python中，定义类是通过&lt;code&gt;class&lt;/code&gt;关键字：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Student(object):
pass
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;class&lt;/code&gt;后面紧接着是类名，即&lt;code&gt;Student&lt;/code&gt;，类名通常是大写开头的单词，紧接着是&lt;code&gt;(object)&lt;/code&gt;，表示该类是从哪个类继承下来的，继承的概念我们后面再讲，通常，如果没有合适的继承类，就使用&lt;code&gt;object&lt;/code&gt;类，这是所有类最终都会继承的类。&lt;/p&gt;
&lt;p&gt;定义好了&lt;code&gt;Student&lt;/code&gt;类，就可以根据&lt;code&gt;Student&lt;/code&gt;类创建出&lt;code&gt;Student&lt;/code&gt;的实例，创建实例是通过类名+()实现的：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; bart = Student()
&amp;gt;&amp;gt;&amp;gt; bart
&amp;lt;__main__.Student object at 0x10a67a590&amp;gt;
&amp;gt;&amp;gt;&amp;gt; Student
&amp;lt;class &amp;#39;__main__.Student&amp;#39;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，变量&lt;code&gt;bart&lt;/code&gt;指向的就是一个&lt;code&gt;Student&lt;/code&gt;的实例，后面的&lt;code&gt;0x10a67a590&lt;/code&gt;是内存地址，每个object的地址都不一样，而&lt;code&gt;Student&lt;/code&gt;本身则是一个类。&lt;/p&gt;
&lt;p&gt;可以自由地给一个实例变量绑定属性，比如，给实例&lt;code&gt;bart&lt;/code&gt;绑定一个&lt;code&gt;name&lt;/code&gt;属性：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; bart.name = &amp;#39;Bart Simpson&amp;#39;
&amp;gt;&amp;gt;&amp;gt; bart.name
&amp;#39;Bart Simpson&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;由于类可以起到模板的作用，因此，可以在创建实例的时候，把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的&lt;code&gt;__init__&lt;/code&gt;方法，在创建实例的时候，就把&lt;code&gt;name&lt;/code&gt;，&lt;code&gt;score&lt;/code&gt;等属性绑上去：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;注意：特殊方法&lt;code&gt;__init__&lt;/code&gt;前后分别有两个下划线！！！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;注意到&lt;code&gt;__init__&lt;/code&gt;方法的第一个参数永远是&lt;code&gt;self&lt;/code&gt;，表示创建的实例本身，因此，在&lt;code&gt;__init__&lt;/code&gt;方法内部，就可以把各种属性绑定到&lt;code&gt;self&lt;/code&gt;，因为&lt;code&gt;self&lt;/code&gt;就指向创建的实例本身。&lt;/p&gt;
&lt;p&gt;有了&lt;code&gt;__init__&lt;/code&gt;方法，在创建实例的时候，就不能传入空的参数了，必须传入与&lt;code&gt;__init__&lt;/code&gt;方法匹配的参数，但&lt;code&gt;self&lt;/code&gt;不需要传，Python解释器自己会把实例变量传进去：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; bart = Student(&amp;#39;Bart Simpson&amp;#39;, 59)
&amp;gt;&amp;gt;&amp;gt; bart.name
&amp;#39;Bart Simpson&amp;#39;
&amp;gt;&amp;gt;&amp;gt; bart.score
59
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;和普通的函数相比，在类中定义的函数只有一点不同，就是第一个参数永远是实例变量&lt;code&gt;self&lt;/code&gt;，并且，调用时，不用传递该参数。除此之外，类的方法和普通函数没有什么区别，所以，你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。&lt;/p&gt;
&lt;h3 id="数据封装"&gt;数据封装&lt;/h3&gt;
&lt;p&gt;面向对象编程的一个重要特点就是数据封装。在上面的&lt;code&gt;Student&lt;/code&gt;类中，每个实例就拥有各自的&lt;code&gt;name&lt;/code&gt;和&lt;code&gt;score&lt;/code&gt;这些数据。我们可以通过函数来访问这些数据，比如打印一个学生的成绩：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; def print_score(std):
... print(&amp;#39;%s: %s&amp;#39; % (std.name, std.score))
...
&amp;gt;&amp;gt;&amp;gt; print_score(bart)
Bart Simpson: 59
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是，既然&lt;code&gt;Student&lt;/code&gt;实例本身就拥有这些数据，要访问这些数据，就没有必要从外面的函数去访问，可以直接在&lt;code&gt;Student&lt;/code&gt;类的内部定义访问数据的函数，这样，就把“数据”给封装起来了。这些封装数据的函数是和&lt;code&gt;Student&lt;/code&gt;类本身是关联起来的，我们称之为类的方法：&lt;/p&gt;</description></item><item><title>Python面向对象初印象</title><link>/2016/05/14/python%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E5%88%9D%E5%8D%B0%E8%B1%A1/</link><pubDate>Sat, 14 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/14/python%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E5%88%9D%E5%8D%B0%E8%B1%A1/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;面向对象编程——Object Oriented Programming，简称OOP，是一种程序设计思想。OOP把对象作为程序的基本单元，一个对象包含了数据和操作数据的函数。&lt;/p&gt;
&lt;p&gt;面向过程的程序设计把计算机程序视为一系列的命令集合，即一组函数的顺序执行。为了简化程序设计，面向过程把函数继续切分为子函数，即把大块函数通过切割成小块函数来降低系统的复杂度。&lt;/p&gt;
&lt;p&gt;而面向对象的程序设计把计算机程序视为一组对象的集合，而每个对象都可以接收其他对象发过来的消息，并处理这些消息，计算机程序的执行就是一系列消息在各个对象之间传递。&lt;/p&gt;
&lt;p&gt;在Python中，所有数据类型都可以视为对象，当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类（Class）的概念。&lt;/p&gt;
&lt;p&gt;我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。&lt;/p&gt;
&lt;p&gt;假设我们要处理学生的成绩表，为了表示一个学生的成绩，面向过程的程序可以用一个dict表示：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;std1 = { &amp;#39;name&amp;#39;: &amp;#39;Michael&amp;#39;, &amp;#39;score&amp;#39;: 98 }
std2 = { &amp;#39;name&amp;#39;: &amp;#39;Bob&amp;#39;, &amp;#39;score&amp;#39;: 81 }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而处理学生成绩可以通过函数实现，比如打印学生的成绩：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def print_score(std):
print(&amp;#39;%s: %s&amp;#39; % (std[&amp;#39;name&amp;#39;], std[&amp;#39;score&amp;#39;]))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果采用面向对象的程序设计思想，我们首选思考的不是程序的执行流程，而是&lt;code&gt;Student&lt;/code&gt;这种数据类型应该被视为一个对象，这个对象拥有&lt;code&gt;name&lt;/code&gt;和&lt;code&gt;score&lt;/code&gt;这两个属性（Property）。如果要打印一个学生的成绩，首先必须创建出这个学生对应的对象，然后，给对象发一个&lt;code&gt;print_score&lt;/code&gt;消息，让对象自己把自己的数据打印出来。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print(&amp;#39;%s: %s&amp;#39; % (self.name, self.score))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;给对象发消息实际上就是调用对象对应的关联函数，我们称之为对象的方法（Method）。面向对象的程序写出来就像这样：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;bart = Student(&amp;#39;Bart Simpson&amp;#39;, 59)
lisa = Student(&amp;#39;Lisa Simpson&amp;#39;, 87)
bart.print_score()
lisa.print_score()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;面向对象的设计思想是从自然界中来的，因为在自然界中，类（Class）和实例（Instance）的概念是很自然的。Class是一种抽象概念，比如我们定义的Class——Student，是指学生这个概念，而实例（Instance）则是一个个具体的Student，比如，Bart Simpson和Lisa Simpson是两个具体的Student。&lt;/p&gt;
&lt;p&gt;所以，面向对象的设计思想是抽象出Class，根据Class创建Instance。&lt;/p&gt;
&lt;p&gt;面向对象的抽象程度又比函数要高，因为一个Class既包含数据，又包含操作数据的方法。&lt;/p&gt;</description></item><item><title>Python偏函数</title><link>/2016/05/11/python%E5%81%8F%E5%87%BD%E6%95%B0/</link><pubDate>Wed, 11 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/11/python%E5%81%8F%E5%87%BD%E6%95%B0/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="标准文件模板"&gt;标准文件模板&lt;/h2&gt;
&lt;p&gt;Python本身就内置了很多非常有用的模块，只要安装完毕，这些模块就可以立刻使用。&lt;/p&gt;
&lt;p&gt;我们以内建的&lt;code&gt;sys&lt;/code&gt;模块为例，编写一个&lt;code&gt;hello&lt;/code&gt;的模块：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/usr/bin/env python3
# -*- coding: utf-8 -*-
&amp;#39; a test module &amp;#39;
__author__ = &amp;#39;superman hooby&amp;#39;
import sys
def test():
args = sys.argv
if len(args)==1:
print(&amp;#39;Hello, world!&amp;#39;)
elif len(args)==2:
print(&amp;#39;Hello, %s!&amp;#39; % args[1])
else:
print(&amp;#39;Too many arguments!&amp;#39;)
if __name__==&amp;#39;__main__&amp;#39;:
test()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;第1行和第2行是标准注释，第1行注释可以让这个&lt;code&gt;hello.py&lt;/code&gt;文件直接在Unix/Linux/Mac上运行，第2行注释表示.py文件本身使用标准UTF-8编码；&lt;/p&gt;
&lt;p&gt;第4行是一个字符串，表示模块的文档注释，任何模块代码的第一个字符串都被视为模块的文档注释；&lt;/p&gt;
&lt;p&gt;第6行使用&lt;code&gt;__author__&lt;/code&gt;变量把作者写进去，这样当你公开源代码后别人就可以瞻仰你的大名；&lt;/p&gt;
&lt;p&gt;以上就是Python模块的标准文件模板，当然也可以全部删掉不写，但是，按标准办事肯定没错。&lt;/p&gt;
&lt;p&gt;后面开始就是真正的代码部分。&lt;/p&gt;
&lt;h3 id="导入模块"&gt;导入模块&lt;/h3&gt;
&lt;p&gt;你可能注意到了，使用&lt;code&gt;sys&lt;/code&gt;模块的第一步，就是导入该模块：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import sys
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;导入&lt;code&gt;sys&lt;/code&gt;模块后，我们就有了变量&lt;code&gt;sys&lt;/code&gt;指向该模块，利用&lt;code&gt;sys&lt;/code&gt;这个变量，就可以访问&lt;code&gt;sys&lt;/code&gt;模块的所有功能。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sys&lt;/code&gt;模块有一个&lt;code&gt;argv&lt;/code&gt;变量，用list存储了命令行的所有参数。&lt;code&gt;argv&lt;/code&gt;至少有一个元素，因为第一个参数永远是该.py文件的名称，例如：&lt;/p&gt;
&lt;p&gt;运行&lt;code&gt;python3 hello.py&lt;/code&gt;获得的&lt;code&gt;sys.argv&lt;/code&gt;就是&lt;code&gt;['hello.py']&lt;/code&gt;；&lt;/p&gt;
&lt;p&gt;运行&lt;code&gt;python3 hello.py Michael&lt;/code&gt;获得的&lt;code&gt;sys.argv&lt;/code&gt;就是&lt;code&gt;['hello.py', 'Michael]&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="main"&gt;main&lt;/h3&gt;
&lt;p&gt;最后，注意到这两行代码：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;if __name__==&amp;#39;__main__&amp;#39;:
test()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当我们在命令行运行&lt;code&gt;hello&lt;/code&gt;模块文件时，Python解释器把一个特殊变量&lt;code&gt;__name__&lt;/code&gt;置为&lt;code&gt;__main__&lt;/code&gt;，而如果在其他地方导入该&lt;code&gt;hello&lt;/code&gt;模块时，&lt;code&gt;if&lt;/code&gt;判断将失败，因此，这种&lt;code&gt;if&lt;/code&gt;测试可以让一个模块通过命令行运行时执行一些额外的代码，最常见的就是运行测试。&lt;/p&gt;
&lt;p&gt;我们可以用命令行运行&lt;code&gt;hello.py&lt;/code&gt;看看效果：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ python3 hello.py
Hello, world!
$ python hello.py hooby
Hello, hooby!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果启动Python交互环境，再导入&lt;code&gt;hello&lt;/code&gt;模块：&lt;/p&gt;</description></item><item><title>Python安装模块</title><link>/2016/05/11/python%E5%AE%89%E8%A3%85%E6%A8%A1%E5%9D%97/</link><pubDate>Wed, 11 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/11/python%E5%AE%89%E8%A3%85%E6%A8%A1%E5%9D%97/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在Python中，安装第三方模块，是通过包管理工具pip完成的。&lt;/p&gt;
&lt;p&gt;如果你正在使用Mac或Linux，安装pip本身这个步骤就可以跳过了。&lt;/p&gt;
&lt;p&gt;如果你正在使用Windows，确保安装时勾选了&lt;code&gt;pip&lt;/code&gt;和&lt;code&gt;Add python.exe to Path&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;在命令提示符窗口下尝试运行&lt;code&gt;pip&lt;/code&gt;，如果Windows提示未找到命令，可以重新运行安装程序添加&lt;code&gt;pip&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;注意：Mac或Linux上有可能并存Python 3.x和Python 2.x，因此对应的pip命令是&lt;code&gt;pip3&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;例如，我们要安装一个第三方库——Python Imaging Library，这是Python下非常强大的处理图像的工具库。不过，PIL目前只支持到Python 2.7，并且有年头没有更新了，因此，基于PIL的Pillow项目开发非常活跃，并且支持最新的Python 3。&lt;/p&gt;
&lt;p&gt;一般来说，第三方库都会在Python官方的&lt;a href="https://pypi.python.org/"&gt;pypi.python.org&lt;/a&gt;网站注册，要安装一个第三方库，必须先知道该库的名称，可以在官网或者pypi上搜索，比如Pillow的名称叫&lt;a href="https://pypi.python.org/pypi/Pillow/"&gt;Pillow&lt;/a&gt;，因此，安装Pillow的命令就是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pip install Pillow
# pip3 install Pillow
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;耐心等待下载并安装后，就可以使用Pillow了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="且慢" loading="lazy" src="l"&gt;&lt;/p&gt;
&lt;h3 id="安装常用模块"&gt;安装常用模块&lt;/h3&gt;
&lt;p&gt;在使用Python时，我们经常需要用到很多第三方库，例如，上面提到的Pillow，以及MySQL驱动程序，Web框架Flask，科学计算Numpy等。用pip一个一个安装费时费力，还需要考虑兼容性。推荐直接使用&lt;a href="https://www.anaconda.com/"&gt;Anaconda&lt;/a&gt;，这是一个基于Python的数据处理和科学计算平台，它已经内置了许多非常有用的第三方库，我们装上Anaconda，就相当于把数十个第三方模块自动安装好了，非常简单易用。&lt;/p&gt;
&lt;p&gt;可以从&lt;a href="https://www.anaconda.com/download/"&gt;Anaconda官网&lt;/a&gt;下载GUI安装包，安装包有500~600M，所以需要耐心等待下载。网速慢的同学请移步&lt;a href="https://pan.baidu.com/s/1kU5OCOB#list/path=%2Fpub%2Fpython"&gt;国内镜像&lt;/a&gt;。下载后直接安装，Anaconda会把系统Path中的python指向自己自带的Python，并且，Anaconda安装的第三方模块会安装在Anaconda自己的路径下，不影响系统已安装的Python目录。安装好Anaconda后，重新打开命令行窗口，输入python，可以看到Anaconda的信息：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────┐
│Command Prompt - python - □ x │
├────────────────────────────────────────────────────────┤
│Microsoft Windows [Version 10.0.0] │
│(c) 2015 Microsoft Corporation. All rights reserved. │
│ │
│C:\&amp;gt; python │
│Python 3.6.3 |Anaconda, Inc.| ... on win32 │
│Type &amp;#34;help&amp;#34;, ... for more information. │
│&amp;gt;&amp;gt;&amp;gt; import numpy │
│&amp;gt;&amp;gt;&amp;gt; _ │ │
└────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以尝试直接&lt;code&gt;import numpy&lt;/code&gt;等已安装的第三方模块。&lt;/p&gt;</description></item><item><title>Python模块的概念</title><link>/2016/05/11/python%E6%A8%A1%E5%9D%97%E7%9A%84%E6%A6%82%E5%BF%B5/</link><pubDate>Wed, 11 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/11/python%E6%A8%A1%E5%9D%97%E7%9A%84%E6%A6%82%E5%BF%B5/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="module"&gt;Module&lt;/h2&gt;
&lt;p&gt;在计算机程序的开发过程中，随着程序代码越写越多，在一个文件里代码就会越来越长，越来越不容易维护。&lt;/p&gt;
&lt;p&gt;为了编写可维护的代码，我们把很多函数分组，分别放到不同的文件里，这样，每个文件包含的代码就相对较少，很多编程语言都采用这种组织代码的方式。在Python中，一个.py文件就称之为一个模块（Module）。&lt;/p&gt;
&lt;p&gt;使用模块有什么好处？&lt;/p&gt;
&lt;p&gt;最大的好处是大大提高了代码的可维护性。其次，编写代码不必从零开始。当一个模块编写完毕，就可以被其他地方引用。我们在编写程序的时候，也经常引用其他模块，包括Python内置的模块和来自第三方的模块。&lt;/p&gt;
&lt;p&gt;使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中，因此，我们自己在编写模块时，不必考虑名字会与其他模块冲突。但是也要注意，尽量不要与内置函数名字冲突。点&lt;a href="http://docs.python.org/3/library/functions.html"&gt;这里&lt;/a&gt;查看Python的所有内置函数。&lt;/p&gt;
&lt;h2 id="package"&gt;Package&lt;/h2&gt;
&lt;p&gt;你也许还想到，如果不同的人编写的模块名相同怎么办？为了避免模块名冲突，Python又引入了按目录来组织模块的方法，称为包（Package）。&lt;/p&gt;
&lt;p&gt;举个例子，一个&lt;code&gt;abc.py&lt;/code&gt;的文件就是一个名字叫&lt;code&gt;abc&lt;/code&gt;的模块。&lt;/p&gt;
&lt;p&gt;现在，假设我们的&lt;code&gt;abc&lt;/code&gt;和&lt;code&gt;xyz&lt;/code&gt;这两个模块名字与其他模块冲突了，于是我们可以通过包来组织模块，避免冲突。方法是选择一个顶层包名，比如&lt;code&gt;mycompany&lt;/code&gt;，按照如下目录存放：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mycompany
├─ __init__.py
├─ abc.py
└─ xyz.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;引入了包以后，只要顶层的包名不与别人冲突，那所有模块都不会与别人冲突。现在，&lt;code&gt;abc.py&lt;/code&gt;模块的名字就变成了&lt;code&gt;mycompany.abc&lt;/code&gt;，类似的，&lt;code&gt;xyz.py&lt;/code&gt;的模块名变成了&lt;code&gt;mycompany.xyz&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;请注意，每一个包目录下面都会有一个&lt;code&gt;__init__.py&lt;/code&gt;的文件，这个文件是&lt;strong&gt;必须&lt;/strong&gt;存在的，否则，Python就把这个目录当成普通目录，而不是一个包。&lt;code&gt;__init__.py&lt;/code&gt;可以是空文件，也可以有Python代码，因为&lt;code&gt;__init__.py&lt;/code&gt;本身就是一个模块，而它的模块名就是&lt;code&gt;mycompany&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;类似的，可以有多级目录，组成多级层次的包结构。比如如下的目录结构：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mycompany
├─ web
│ ├─ __init__.py
│ ├─ utils.py
│ └─ www.py
├─ __init__.py
├─ abc.py
└─ xyz.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;文件&lt;code&gt;www.py&lt;/code&gt;的模块名就是&lt;code&gt;mycompany.web.www&lt;/code&gt;，两个文件&lt;code&gt;utils.py&lt;/code&gt;的模块名分别是&lt;code&gt;mycompany.utils&lt;/code&gt;和&lt;code&gt;mycompany.web.utils&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自己创建模块时要注意命名，不能和Python自带的模块名称冲突。例如，系统自带了sys模块，自己的模块就不可命名为sys.py，否则将无法导入系统自带的sys模块。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mycompany.web&lt;/code&gt;也是一个模块，请指出该模块对应的.py文件。&lt;/p&gt;
&lt;p&gt;总结&lt;/p&gt;
&lt;p&gt;模块是一组Python代码的集合，可以使用其他模块，也可以被其他模块使用。&lt;/p&gt;
&lt;p&gt;创建自己的模块时，要注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模块名要遵循Python变量命名规范，不要使用中文、特殊字符；&lt;/li&gt;
&lt;li&gt;模块名不要和系统模块名冲突，最好先查看系统是否已存在该模块，检查方法是在Python交互环境执行&lt;code&gt;import abc&lt;/code&gt;，若成功则说明系统存在此模块。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Python偏函数</title><link>/2016/05/10/python%E5%81%8F%E5%87%BD%E6%95%B0/</link><pubDate>Tue, 10 May 2016 00:00:00 +0000</pubDate><guid>/2016/05/10/python%E5%81%8F%E5%87%BD%E6%95%B0/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Python的&lt;code&gt;functools&lt;/code&gt;模块提供了很多有用的功能，其中一个就是偏函数（Partial function）。要注意，这里的偏函数和数学意义上的偏函数不一样。&lt;/p&gt;
&lt;p&gt;在介绍函数参数的时候，我们讲到，通过设定参数的默认值，可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;int()&lt;/code&gt;函数可以把字符串转换为整数，当仅传入字符串时，&lt;code&gt;int()&lt;/code&gt;函数默认按十进制转换：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; int(&amp;#39;12345&amp;#39;)
12345
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但&lt;code&gt;int()&lt;/code&gt;函数还提供额外的&lt;code&gt;base&lt;/code&gt;参数，默认值为&lt;code&gt;10&lt;/code&gt;。如果传入&lt;code&gt;base&lt;/code&gt;参数，就可以做N进制的转换：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; int(&amp;#39;12345&amp;#39;, base=8)
5349
&amp;gt;&amp;gt;&amp;gt; int(&amp;#39;12345&amp;#39;, 16)
74565
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;假设要转换大量的二进制字符串，每次都传入&lt;code&gt;int(x, base=2)&lt;/code&gt;非常麻烦，于是，我们想到，可以定义一个&lt;code&gt;int2()&lt;/code&gt;的函数，默认把&lt;code&gt;base=2&lt;/code&gt;传进去：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def int2(x, base=2):
return int(x, base)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样，我们转换二进制就非常方便了：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; int2(&amp;#39;1000000&amp;#39;)
64
&amp;gt;&amp;gt;&amp;gt; int2(&amp;#39;1010101&amp;#39;)
85
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;functools.partial&lt;/code&gt;就是帮助我们创建一个偏函数的，不需要我们自己定义&lt;code&gt;int2()&lt;/code&gt;，可以直接使用下面的代码创建一个新的函数&lt;code&gt;int2&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import functools
&amp;gt;&amp;gt;&amp;gt; int2 = functools.partial(int, base=2)
&amp;gt;&amp;gt;&amp;gt; int2(&amp;#39;1000000&amp;#39;)
64
&amp;gt;&amp;gt;&amp;gt; int2(&amp;#39;1010101&amp;#39;)
85
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;所以，简单总结&lt;code&gt;functools.partial&lt;/code&gt;的作用就是，把一个函数的某些参数给固定住（也就是设置默认值），返回一个新的函数，调用这个新函数会更简单。&lt;/p&gt;
&lt;p&gt;注意到上面的新的&lt;code&gt;int2&lt;/code&gt;函数，仅仅是把&lt;code&gt;base&lt;/code&gt;参数重新设定默认值为&lt;code&gt;2&lt;/code&gt;，但也可以在函数调用时传入其他值：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; int2(&amp;#39;1000000&amp;#39;, base=10)
1000000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最后，创建偏函数时，实际上可以接收函数对象、&lt;code&gt;*args&lt;/code&gt;和&lt;code&gt;**kw&lt;/code&gt;这3个参数，当传入：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;int2 = functools.partial(int, base=2)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;实际上固定了int()函数的关键字参数&lt;code&gt;base&lt;/code&gt;，也就是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;int2(&amp;#39;10010&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;相当于：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kw = { &amp;#39;base&amp;#39;: 2 }
int(&amp;#39;10010&amp;#39;, **kw)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当传入：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;max2 = functools.partial(max, 10)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;实际上会把&lt;code&gt;10&lt;/code&gt;作为&lt;code&gt;*args&lt;/code&gt;的一部分自动加到左边，也就是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;max2(5, 6, 7)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;相当于：&lt;/p&gt;</description></item><item><title>Python装饰器</title><link>/2016/04/30/python%E8%A3%85%E9%A5%B0%E5%99%A8/</link><pubDate>Sat, 30 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/30/python%E8%A3%85%E9%A5%B0%E5%99%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="装饰器定义"&gt;装饰器定义&lt;/h1&gt;
&lt;p&gt;在代码运行期间动态增加功能的方式，称之为“装饰器”（Decorator）。&lt;/p&gt;
&lt;p&gt;由于函数也是一个对象，而且函数对象可以被赋值给变量，所以，通过变量也能调用该函数。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; def now():
... print(&amp;#39;2015-3-25&amp;#39;)
...
&amp;gt;&amp;gt;&amp;gt; f = now
&amp;gt;&amp;gt;&amp;gt; f()
2015-3-25
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;函数对象有一个&lt;code&gt;__name__&lt;/code&gt;属性，可以拿到函数的名字：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; now.__name__
&amp;#39;now&amp;#39;
&amp;gt;&amp;gt;&amp;gt; f.__name__
&amp;#39;now&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在，假设我们要增强&lt;code&gt;now()&lt;/code&gt;函数的功能，比如，在函数调用前后自动打印日志，但又不希望修改&lt;code&gt;now()&lt;/code&gt;函数的定义,这种情况就可以使用装饰器。&lt;/p&gt;
&lt;p&gt;本质上，decorator就是一个返回函数的高阶函数。所以，我们要定义一个能打印日志的decorator，可以定义如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def log(func):
def wrapper(*args, **kw):
print(&amp;#39;call %s():&amp;#39; % func.__name__)
return func(*args, **kw)
return wrapper
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;观察上面的&lt;code&gt;log&lt;/code&gt;，因为它是一个decorator，所以接受一个函数作为参数，并返回一个函数。我们要借助Python的@语法，把decorator置于函数的定义处：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;@log
def now():
print(&amp;#39;2015-3-25&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调用&lt;code&gt;now()&lt;/code&gt;函数，不仅会运行&lt;code&gt;now()&lt;/code&gt;函数本身，还会在运行&lt;code&gt;now()&lt;/code&gt;函数前打印一行日志：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; now()
call now():
2015-3-25
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;把&lt;code&gt;@log&lt;/code&gt;放到&lt;code&gt;now()&lt;/code&gt;函数的定义处，相当于执行了语句：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;now = log(now)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;由于&lt;code&gt;log()&lt;/code&gt;是一个decorator，返回一个函数，所以，原来的&lt;code&gt;now()&lt;/code&gt;函数仍然存在，只是现在同名的&lt;code&gt;now&lt;/code&gt;变量指向了新的函数，于是调用&lt;code&gt;now()&lt;/code&gt;将执行新函数，即在&lt;code&gt;log()&lt;/code&gt;函数中返回的&lt;code&gt;wrapper()&lt;/code&gt;函数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wrapper()&lt;/code&gt;函数的参数定义是&lt;code&gt;(*args, **kw)&lt;/code&gt;，因此，&lt;code&gt;wrapper()&lt;/code&gt;函数可以接受任意参数的调用。在&lt;code&gt;wrapper()&lt;/code&gt;函数内，首先打印日志，再紧接着调用原始函数。&lt;/p&gt;
&lt;p&gt;如果decorator本身需要传入参数，那就需要编写一个返回decorator的高阶函数，写出来会更复杂。比如，要自定义log的文本：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def log(text):
def decorator(func):
def wrapper(*args, **kw):
print(&amp;#39;%s %s():&amp;#39; % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这个3层嵌套的decorator用法如下：&lt;/p&gt;</description></item><item><title>Python匿名函数</title><link>/2016/04/28/python%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0/</link><pubDate>Thu, 28 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/28/python%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;当我们在传入函数时，有些时候，不需要显式地定义函数，直接传入匿名函数更方便。&lt;/p&gt;
&lt;p&gt;在Python中，对匿名函数提供了有限支持。还是以&lt;code&gt;map()&lt;/code&gt;函数为例，计算f(x)=x2时，除了定义一个&lt;code&gt;f(x)&lt;/code&gt;的函数外，还可以直接传入匿名函数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;通过对比可以看出，匿名函数&lt;code&gt;lambda x: x * x&lt;/code&gt;实际上就是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def f(x):
return x * x
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;关键字&lt;code&gt;lambda&lt;/code&gt;表示匿名函数，冒号前面的&lt;code&gt;x&lt;/code&gt;表示函数参数。&lt;/p&gt;
&lt;p&gt;匿名函数有个限制，就是只能有一个表达式，不用写&lt;code&gt;return&lt;/code&gt;，返回值就是该表达式的结果。&lt;/p&gt;
&lt;p&gt;用匿名函数有个好处，因为函数没有名字，不必担心函数名冲突。此外，匿名函数也是一个函数对象，也可以把匿名函数赋值给一个变量，再利用变量来调用该函数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = lambda x: x * x
&amp;gt;&amp;gt;&amp;gt; f
&amp;lt;function &amp;lt;lambda&amp;gt; at 0x101c6ef28&amp;gt;
&amp;gt;&amp;gt;&amp;gt; f(5)
25
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;同样，也可以把匿名函数作为返回值返回，比如：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(x, y):
return lambda: x * x + y * y
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="try-it-"&gt;try it ?&lt;/h3&gt;
&lt;p&gt;请用匿名函数改造下面的代码：&lt;/p&gt;</description></item><item><title>Python返回函数</title><link>/2016/04/27/python%E8%BF%94%E5%9B%9E%E5%87%BD%E6%95%B0/</link><pubDate>Wed, 27 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/27/python%E8%BF%94%E5%9B%9E%E5%87%BD%E6%95%B0/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="函数作为返回值"&gt;函数作为返回值&lt;/h3&gt;
&lt;p&gt;高阶函数除了可以接受函数作为参数外，还可以把函数作为结果值返回。&lt;/p&gt;
&lt;p&gt;我们来实现一个可变参数的求和。通常情况下，求和的函数是这样 定义的：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是，如果不需要立刻求和，而是在后面的代码中，根据需要再计算怎么办？可以不返回求和的结果，而是返回求和的函数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当我们调用&lt;code&gt;lazy_sum()&lt;/code&gt;时，返回的并不是求和结果，而是求和函数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = lazy_sum(1, 3, 5, 7, 9)
&amp;gt;&amp;gt;&amp;gt; f
&amp;lt;function lazy_sum.&amp;lt;locals&amp;gt;.sum at 0x101c6ed90&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调用函数&lt;code&gt;f&lt;/code&gt;时，才真正计算求和的结果：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f()
25
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在这个例子中，我们在函数&lt;code&gt;lazy_sum&lt;/code&gt;中又定义了函数&lt;code&gt;sum&lt;/code&gt;，并且，内部函数&lt;code&gt;sum&lt;/code&gt;可以引用外部函数&lt;code&gt;lazy_sum&lt;/code&gt;的参数和局部变量，当&lt;code&gt;lazy_sum&lt;/code&gt;返回函数&lt;code&gt;sum&lt;/code&gt;时，相关参数和变量都保存在返回的函数中，这种称为“闭包（Closure）”的程序结构拥有极大的威力。&lt;/p&gt;
&lt;p&gt;请再注意一点，当我们调用&lt;code&gt;lazy_sum()&lt;/code&gt;时，每次调用都会返回一个新的函数，即使传入相同的参数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f1 = lazy_sum(1, 3, 5, 7, 9)
&amp;gt;&amp;gt;&amp;gt; f2 = lazy_sum(1, 3, 5, 7, 9)
&amp;gt;&amp;gt;&amp;gt; f1==f2
False
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;f1()&lt;/code&gt;和&lt;code&gt;f2()&lt;/code&gt;的调用结果互不影响。&lt;/p&gt;</description></item><item><title>Python高阶函数之sorted</title><link>/2016/04/24/python%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0%E4%B9%8Bsorted/</link><pubDate>Sun, 24 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/24/python%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0%E4%B9%8Bsorted/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="排序算法"&gt;排序算法&lt;/h3&gt;
&lt;p&gt;排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序，排序的核心是比较两个元素的大小。如果是数字，我们可以直接比较，但如果是字符串或者两个dict呢？直接比较数学上的大小是没有意义的，因此，比较的过程必须通过函数抽象出来。&lt;/p&gt;
&lt;p&gt;Python内置的&lt;code&gt;sorted()&lt;/code&gt;函数就可以对list进行排序：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;此外，&lt;code&gt;sorted()&lt;/code&gt;函数也是一个高阶函数，它还可以接收一个&lt;code&gt;key&lt;/code&gt;函数来实现自定义的排序，例如按绝对值大小排序：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;key指定的函数将作用于list的每一个元素上，并根据key函数返回的结果进行排序。对比原始的list和经过&lt;code&gt;key=abs&lt;/code&gt;处理过的list：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;list = [36, 5, -12, 9, -21]
keys = [36, 5, 12, 9, 21]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后&lt;code&gt;sorted()&lt;/code&gt;函数按照keys进行排序，并按照对应关系返回list相应的元素：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;keys排序结果 =&amp;gt; [5, 9, 12, 21, 36]
| | | | |
最终结果 =&amp;gt; [5, 9, -12, -21, 36]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们再看一个字符串排序的例子：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; sorted([&amp;#39;bob&amp;#39;, &amp;#39;about&amp;#39;, &amp;#39;Zoo&amp;#39;, &amp;#39;Credit&amp;#39;])
[&amp;#39;Credit&amp;#39;, &amp;#39;Zoo&amp;#39;, &amp;#39;about&amp;#39;, &amp;#39;bob&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;默认情况下，对字符串排序，是按照ASCII的大小比较的，由于&lt;code&gt;'Z' &amp;lt; 'a'&lt;/code&gt;，结果，大写字母&lt;code&gt;Z&lt;/code&gt;会排在小写字母&lt;code&gt;a&lt;/code&gt;的前面。&lt;/p&gt;</description></item><item><title>Python高阶函数之filter</title><link>/2016/04/23/python%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0%E4%B9%8Bfilter/</link><pubDate>Sat, 23 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/23/python%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0%E4%B9%8Bfilter/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="filter介绍"&gt;filter介绍&lt;/h2&gt;
&lt;p&gt;Python内建的&lt;code&gt;filter()&lt;/code&gt;函数用于过滤序列。&lt;/p&gt;
&lt;p&gt;和&lt;code&gt;map()&lt;/code&gt;类似，&lt;code&gt;filter()&lt;/code&gt;也接收一个函数和一个序列。和&lt;code&gt;map()&lt;/code&gt;不同的是，&lt;code&gt;filter()&lt;/code&gt;把传入的函数依次作用于每个元素，然后根据返回值是&lt;code&gt;True&lt;/code&gt;还是&lt;code&gt;False&lt;/code&gt;决定保留还是丢弃该元素。&lt;/p&gt;
&lt;p&gt;例如，在一个list中，删掉偶数，只保留奇数，可以这么写：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;把一个序列中的空字符串删掉，可以这么写：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def not_empty(s):
return s and s.strip()
#str.strip([chars]); 去除字符串首位的字符(chars)
list(filter(not_empty, [&amp;#39;A&amp;#39;, &amp;#39;&amp;#39;, &amp;#39;B&amp;#39;, None, &amp;#39;C&amp;#39;, &amp;#39; &amp;#39;]))
# 结果: [&amp;#39;A&amp;#39;, &amp;#39;B&amp;#39;, &amp;#39;C&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可见用&lt;code&gt;filter()&lt;/code&gt;这个高阶函数，关键在于正确实现一个“筛选”函数。&lt;/p&gt;
&lt;p&gt;注意到&lt;code&gt;filter()&lt;/code&gt;函数返回的是一个&lt;code&gt;Iterator&lt;/code&gt;，也就是一个惰性序列，所以要强迫&lt;code&gt;filter()&lt;/code&gt;完成计算结果，需要用&lt;code&gt;list()&lt;/code&gt;函数获得所有结果并返回list。&lt;/p&gt;
&lt;h2 id="filter求素数"&gt;filter求素数&lt;/h2&gt;
&lt;p&gt;计算&lt;a href="http://baike.baidu.com/view/10626.htm"&gt;素数&lt;/a&gt;的一个方法是&lt;a href="http://baike.baidu.com/view/3784258.htm"&gt;埃氏筛法&lt;/a&gt;，它的算法理解起来非常简单：&lt;/p&gt;
&lt;p&gt;首先，列出从&lt;code&gt;2&lt;/code&gt;开始的所有自然数，构造一个序列：&lt;/p&gt;
&lt;p&gt;2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, &amp;hellip;&lt;/p&gt;</description></item><item><title>Python高阶函数之map和reduce</title><link>/2016/04/19/python%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0%E4%B9%8Bmap%E5%92%8Creduce/</link><pubDate>Tue, 19 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/19/python%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0%E4%B9%8Bmap%E5%92%8Creduce/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Python内建了&lt;code&gt;map()&lt;/code&gt;和&lt;code&gt;reduce()&lt;/code&gt;函数。&lt;/p&gt;
&lt;p&gt;如果你读过Google的那篇大名鼎鼎的论文“&lt;a href="http://research.google.com/archive/mapreduce.html"&gt;MapReduce: Simplified Data Processing on Large Clusters&lt;/a&gt;”，你就能大概明白map/reduce的概念。&lt;/p&gt;
&lt;h2 id="map"&gt;map&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;map()&lt;/code&gt;函数接收两个参数，一个是函数，一个是&lt;code&gt;Iterable&lt;/code&gt;，&lt;code&gt;map&lt;/code&gt;将传入的函数依次作用到序列的每个元素，并把结果作为新的&lt;code&gt;Iterator&lt;/code&gt;返回。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;map(fun, Iteratble)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;举例说明，比如我们有一个函数&lt;code&gt;f(x) = x * x&lt;/code&gt;，要把这个函数作用在一个list &lt;code&gt;[1, 2, 3, 4, 5, 6, 7, 8, 9]&lt;/code&gt;上，就可以用&lt;code&gt;map()&lt;/code&gt;实现如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; f(x) = x * x
│
│
┌───┬───┬───┬───┼───┬───┬───┬───┐
│ │ │ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
[ 1 2 3 4 5 6 7 8 9 ]
│ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
[ 1 4 9 16 25 36 49 64 81 ]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在，我们用Python代码实现：&lt;/p&gt;</description></item><item><title>Python高阶函数</title><link>/2016/04/18/python%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0/</link><pubDate>Mon, 18 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/18/python%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="高阶函数"&gt;高阶函数&lt;/h2&gt;
&lt;p&gt;高阶函数英文叫Higher-order function。把函数作为参数传入，这样的函数称为高阶函数，编写高阶函数，就是让函数的参数能够接收别的函数。&lt;/p&gt;
&lt;h3 id="变量可以指向函数"&gt;变量可以指向函数&lt;/h3&gt;
&lt;p&gt;以Python内置的求绝对值的函数&lt;code&gt;abs()&lt;/code&gt;为例，调用该函数用以下代码：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; abs(-10)
10
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是，如果只写&lt;code&gt;abs&lt;/code&gt;呢？&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; abs
&amp;lt;built-in function abs&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可见，&lt;code&gt;abs(-10)&lt;/code&gt;是函数调用，而&lt;code&gt;abs&lt;/code&gt;是函数本身。&lt;/p&gt;
&lt;p&gt;要获得函数调用结果，我们可以把结果赋值给变量：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; x = abs(-10)
&amp;gt;&amp;gt;&amp;gt; x
10
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是，如果把函数本身赋值给变量呢？&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = abs
&amp;gt;&amp;gt;&amp;gt; f
&amp;lt;built-in function abs&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;结论：函数本身也可以赋值给变量，即：变量可以指向函数。&lt;/p&gt;
&lt;p&gt;如果一个变量指向了一个函数，那么，可否通过该变量来调用这个函数？用代码验证一下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = abs
&amp;gt;&amp;gt;&amp;gt; f(-10)
10
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;成功！说明变量&lt;code&gt;f&lt;/code&gt;现在已经指向了&lt;code&gt;abs&lt;/code&gt;函数本身。直接调用&lt;code&gt;abs()&lt;/code&gt;函数和调用变量&lt;code&gt;f()&lt;/code&gt;完全相同。&lt;/p&gt;
&lt;h3 id="函数名也是变量"&gt;函数名也是变量&lt;/h3&gt;
&lt;p&gt;那么函数名是什么呢？函数名其实就是指向函数的变量！对于&lt;code&gt;abs()&lt;/code&gt;这个函数，完全可以把函数名&lt;code&gt;abs&lt;/code&gt;看成变量，它指向一个可以计算绝对值的函数！&lt;/p&gt;
&lt;p&gt;如果把&lt;code&gt;abs&lt;/code&gt;指向其他对象，会有什么情况发生？&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; abs = 10
&amp;gt;&amp;gt;&amp;gt; abs(-10)
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
TypeError: &amp;#39;int&amp;#39; object is not callable
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;把&lt;code&gt;abs&lt;/code&gt;指向&lt;code&gt;10&lt;/code&gt;后，就无法通过&lt;code&gt;abs(-10)&lt;/code&gt;调用该函数了！因为&lt;code&gt;abs&lt;/code&gt;这个变量已经不指向求绝对值函数而是指向一个整数&lt;code&gt;10&lt;/code&gt;！&lt;/p&gt;
&lt;p&gt;当然实际代码绝对不能这么写，这里是为了说明函数名也是变量。要恢复&lt;code&gt;abs&lt;/code&gt;函数，请重启Python交互环境。&lt;/p&gt;
&lt;p&gt;注：由于&lt;code&gt;abs&lt;/code&gt;函数实际上是定义在&lt;code&gt;import builtins&lt;/code&gt;模块中的，所以要让修改&lt;code&gt;abs&lt;/code&gt;变量的指向在其它模块也生效，要用&lt;code&gt;import builtins; builtins.abs = 10&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Python常见问题总结</title><link>/2016/04/16/python%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/</link><pubDate>Sat, 16 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/16/python%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1lambda"&gt;1.lambda&lt;/h1&gt;
&lt;p&gt;Python 内置函数&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="描述"&gt;描述&lt;/h2&gt;
&lt;p&gt;python 使用 lambda 来创建匿名函数。&lt;/p&gt;
&lt;h2 id="语法"&gt;语法&lt;/h2&gt;
&lt;p&gt;lambda 函数的语法只包含一个语句，如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lambda [arg1 [,arg2,.....argn]]:expression
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="注意"&gt;注意&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lambda (x, y): x + y
# tuple parameter unpacking is not supported in python3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样的使用在python3中已经被废弃了，会提示上面注释中的错误&lt;/p&gt;
&lt;p&gt;那么元组tuple如何作为参数呢，will be translated into:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lambda x_y: x_y[0] + x_y[1]
# 即在传入参数的时候，整个变量作为元组的参数，使用的时候，引用整个元组的位置参数
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="2map"&gt;2.map()&lt;/h1&gt;
&lt;p&gt;Python 内置函数&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="描述-1"&gt;描述&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;map()&lt;/strong&gt; 会根据提供的函数对指定序列做映射。&lt;/p&gt;
&lt;p&gt;第一个参数 function 以参数序列中的每一个元素调用 function 函数，返回包含每次 function 函数返回值的新列表。&lt;/p&gt;
&lt;h2 id="语法-1"&gt;语法&lt;/h2&gt;
&lt;p&gt;map() 函数语法：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;map(function, iterable, ...)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="参数"&gt;参数&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;function &amp;ndash; 函数，有两个参数&lt;/li&gt;
&lt;li&gt;iterable &amp;ndash; 一个或多个序列&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="返回值"&gt;返回值&lt;/h2&gt;
&lt;p&gt;Python 2.x 返回列表。&lt;/p&gt;</description></item><item><title>Python sdk打包</title><link>/2016/04/14/python-sdk%E6%89%93%E5%8C%85/</link><pubDate>Thu, 14 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/14/python-sdk%E6%89%93%E5%8C%85/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="setuptools"&gt;setuptools&lt;/h1&gt;
&lt;p&gt;setuptools 是一个优秀的，可靠的 Pthon 包安装与分发工具。&lt;/p&gt;
&lt;h2 id="包格式"&gt;包格式&lt;/h2&gt;
&lt;p&gt;Python 库打包的格式包括 &lt;code&gt;Wheel&lt;/code&gt; 和 &lt;code&gt;Egg&lt;/code&gt;。Egg 格式是由 setuptools 在 2004 年引入，而 Wheel 格式是由 PEP427 在 2012 年定义。使用 &lt;code&gt;Wheel&lt;/code&gt; 和 &lt;code&gt;Egg&lt;/code&gt; 安装都不需要重新构建和编译，其在发布之前就应该完成测试和构建。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Egg&lt;/code&gt; 和 &lt;code&gt;Wheel&lt;/code&gt; 本质上都是一个 zip 格式包，Egg 文件使用 &lt;code&gt;.egg&lt;/code&gt; 扩展名，Wheel 使用 &lt;code&gt;.whl&lt;/code&gt; 扩展名。Wheel 的出现是为了替代 Egg，其现在被认为是 Python 的二进制包的标准格式。&lt;/p&gt;
&lt;p&gt;以下是 Wheel 和 Egg 的主要区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wheel 有一个官方的 PEP427 来定义，而 Egg 没有 PEP 定义&lt;/li&gt;
&lt;li&gt;Wheel 是一种分发格式，即打包格式。而 Egg 既是一种分发格式，也是一种运行时安装的格式，并且是可以被直接 import&lt;/li&gt;
&lt;li&gt;Wheel 文件不会包含 .pyc 文件&lt;/li&gt;
&lt;li&gt;Wheel 使用和 PEP376 兼容的 .dist-info 目录，而 Egg 使用 .egg-info 目录&lt;/li&gt;
&lt;li&gt;Wheel 有着更丰富的命名规则。&lt;/li&gt;
&lt;li&gt;Wheel 是有版本的。每个 Wheel 文件都包含 wheel 规范的版本和打包的实现&lt;/li&gt;
&lt;li&gt;Wheel 在内部被 sysconfig path type 管理，因此转向其他格式也更容易&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;详细描述可见：&lt;a href="https://packaging.python.org/discussions/wheel-vs-egg/"&gt;Wheel vs Egg&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Python函数式编程</title><link>/2016/04/10/python%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/</link><pubDate>Sun, 10 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/10/python%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="函数式编程概念"&gt;函数式编程概念&lt;/h1&gt;
&lt;p&gt;函数式编程就是指高度抽象的编程范式。&lt;/p&gt;
&lt;p&gt;函数是Python内建支持的一种封装，我们通过把大段代码拆成函数，通过一层一层的函数调用，就可以把复杂任务分解成简单的任务，这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。&lt;/p&gt;
&lt;p&gt;而函数式编程（请注意多了一个“式”字）——Functional Programming，虽然也可以归结到面向过程的程序设计，但其思想更接近数学计算。&lt;/p&gt;
&lt;p&gt;我们首先要搞明白计算机（Computer）和计算（Compute）的概念。&lt;/p&gt;
&lt;p&gt;在计算机的层次上，CPU执行的是加减乘除的指令代码，以及各种条件判断和跳转指令，所以，汇编语言是最贴近计算机的语言。&lt;/p&gt;
&lt;p&gt;而计算则指数学意义上的计算，越是抽象的计算，离计算机硬件越远。&lt;/p&gt;
&lt;p&gt;对应到编程语言，就是越低级的语言，越贴近计算机，抽象程度低，执行效率高，比如C语言；越高级的语言，越贴近计算，抽象程度高，执行效率低，比如Lisp语言。&lt;/p&gt;
&lt;p&gt;函数式编程就是一种抽象程度很高的编程范式，纯粹的函数式编程语言编写的函数没有变量，因此，任意一个函数，只要输入是确定的，输出就是确定的，这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言，由于函数内部的变量状态不确定，同样的输入，可能得到不同的输出，因此，这种函数是有副作用的。&lt;/p&gt;
&lt;p&gt;函数式编程的一个特点就是，允许把函数本身作为参数传入另一个函数，还允许返回一个函数！&lt;/p&gt;
&lt;p&gt;Python对函数式编程提供部分支持。由于Python允许使用变量，因此，Python不是纯函数式编程语言。&lt;/p&gt;
&lt;h1 id="函数式编程内容"&gt;函数式编程内容&lt;/h1&gt;
&lt;p&gt;Python里面的函数式编程有:&lt;/p&gt;
&lt;p&gt;1.高阶函数&lt;/p&gt;
&lt;p&gt;2.返回函数&lt;/p&gt;
&lt;p&gt;3.匿名函数&lt;/p&gt;
&lt;p&gt;4.装饰器&lt;/p&gt;
&lt;p&gt;5.偏函数&lt;/p&gt;
&lt;p&gt;&amp;hellip;&lt;/p&gt;
&lt;p&gt;后面会吧这几个分别写出来.&lt;/p&gt;</description></item><item><title>Python之sort和sorted</title><link>/2016/04/02/python%E4%B9%8Bsort%E5%92%8Csorted/</link><pubDate>Sat, 02 Apr 2016 00:00:00 +0000</pubDate><guid>/2016/04/02/python%E4%B9%8Bsort%E5%92%8Csorted/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="sort-or-sorted-"&gt;sort or sorted ?&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sort 与 sorted 区别：
sort 是应用在 list 上的方法，sorted 可以对所有可迭代的对象进行排序操作。
list 的 sort 方法返回的是对已经存在的列表进行操作，而内建函数 sorted 方法返回的是一个新的 list，而不是在原来的基础上进行的操作。
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="listsorted的用法"&gt;list.sorted的用法&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sorted(iterable)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;----------------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;a &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; b &lt;span style="color:#f92672"&gt;=&lt;/span&gt; sorted(a) &lt;span style="color:#75715e"&gt;# 保留原列表&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; a
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; b
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;----------------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sorted(iterable, cmp)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sorted(iterable, key)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sorted(iterable, cmp, key)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sorted(iterable, cmp, key, reverse&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;----------------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; L&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[(&lt;span style="color:#e6db74"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;),(&lt;span style="color:#e6db74"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;),(&lt;span style="color:#e6db74"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;),(&lt;span style="color:#e6db74"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; sorted(L, cmp&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;lambda&lt;/span&gt; x,y:cmp(x[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;],y[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;])) &lt;span style="color:#75715e"&gt;# 利用cmp函数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[(&lt;span style="color:#e6db74"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; sorted(L, key&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;lambda&lt;/span&gt; x:x[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]) &lt;span style="color:#75715e"&gt;# 利用key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[(&lt;span style="color:#e6db74"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; students &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [(&lt;span style="color:#e6db74"&gt;&amp;#39;john&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;15&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;jane&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;dave&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; sorted(students, key&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;lambda&lt;/span&gt; s: s[&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]) &lt;span style="color:#75715e"&gt;# 按年龄排序&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[(&lt;span style="color:#e6db74"&gt;&amp;#39;dave&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;jane&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;john&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;15&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; sorted(students, key&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;lambda&lt;/span&gt; s: s[&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;], reverse&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;) &lt;span style="color:#75715e"&gt;# 按降序&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[(&lt;span style="color:#e6db74"&gt;&amp;#39;john&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;15&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;jane&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;), (&lt;span style="color:#e6db74"&gt;&amp;#39;dave&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;----------------------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="与listsort-的使用"&gt;与list.sort 的使用&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sort(self, key=None, reverse=False)
if __name__ == &amp;#39;__main__&amp;#39;:
a = [5, 7, 6, 3, 4, 1, 2]
b = [(&amp;#39;dave&amp;#39;, &amp;#39;B&amp;#39;, 10), (&amp;#39;jane&amp;#39;, &amp;#39;B&amp;#39;, 12), (&amp;#39;john&amp;#39;, &amp;#39;A&amp;#39;, 15)]
a.sort(reverse=True)
print(a)
b.sort(key=lambda x:x[0])
print(b)
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Python特性之迭代器</title><link>/2016/03/31/python%E7%89%B9%E6%80%A7%E4%B9%8B%E8%BF%AD%E4%BB%A3%E5%99%A8/</link><pubDate>Thu, 31 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/31/python%E7%89%B9%E6%80%A7%E4%B9%8B%E8%BF%AD%E4%BB%A3%E5%99%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="iterable"&gt;Iterable&lt;/h1&gt;
&lt;p&gt;我们已经知道，可以直接作用于&lt;code&gt;for&lt;/code&gt;循环的数据类型有以下几种：&lt;/p&gt;
&lt;p&gt;一类是集合数据类型，如&lt;code&gt;list&lt;/code&gt;、&lt;code&gt;tuple&lt;/code&gt;、&lt;code&gt;dict&lt;/code&gt;、&lt;code&gt;set&lt;/code&gt;、&lt;code&gt;str&lt;/code&gt;等；&lt;/p&gt;
&lt;p&gt;一类是&lt;code&gt;generator&lt;/code&gt;，包括生成器和带&lt;code&gt;yield&lt;/code&gt;的generator function。&lt;/p&gt;
&lt;p&gt;这些可以直接作用于&lt;code&gt;for&lt;/code&gt;循环的对象统称为可迭代对象：&lt;code&gt;Iterable&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;可以使用&lt;code&gt;isinstance()&lt;/code&gt;判断一个对象是否是&lt;code&gt;Iterable&lt;/code&gt;对象：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from collections import Iterable
&amp;gt;&amp;gt;&amp;gt; isinstance([], Iterable)
True
&amp;gt;&amp;gt;&amp;gt; isinstance({}, Iterable)
True
&amp;gt;&amp;gt;&amp;gt; isinstance(&amp;#39;abc&amp;#39;, Iterable)
True
&amp;gt;&amp;gt;&amp;gt; isinstance((x for x in range(10)), Iterable)
True
&amp;gt;&amp;gt;&amp;gt; isinstance(100, Iterable)
False
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="iterator"&gt;Iterator&lt;/h1&gt;
&lt;p&gt;而生成器不但可以作用于&lt;code&gt;for&lt;/code&gt;循环，还可以被&lt;code&gt;next()&lt;/code&gt;函数不断调用并返回下一个值，直到最后抛出&lt;code&gt;StopIteration&lt;/code&gt;错误表示无法继续返回下一个值了。&lt;/p&gt;
&lt;p&gt;可以被&lt;code&gt;next()&lt;/code&gt;函数调用并不断返回下一个值的对象称为迭代器：&lt;code&gt;Iterator&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;可以使用&lt;code&gt;isinstance()&lt;/code&gt;判断一个对象是否是&lt;code&gt;Iterator&lt;/code&gt;对象：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from collections import Iterator
&amp;gt;&amp;gt;&amp;gt; isinstance((x for x in range(10)), Iterator)
True
&amp;gt;&amp;gt;&amp;gt; isinstance([], Iterator)
False
&amp;gt;&amp;gt;&amp;gt; isinstance({}, Iterator)
False
&amp;gt;&amp;gt;&amp;gt; isinstance(&amp;#39;abc&amp;#39;, Iterator)
False
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;生成器都是&lt;code&gt;Iterator&lt;/code&gt;对象，但&lt;code&gt;list&lt;/code&gt;、&lt;code&gt;dict&lt;/code&gt;、&lt;code&gt;str&lt;/code&gt;虽然是&lt;code&gt;Iterable&lt;/code&gt;，却不是&lt;code&gt;Iterator&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="iteratble转iterator"&gt;Iteratble转Iterator&lt;/h2&gt;
&lt;p&gt;把&lt;code&gt;list&lt;/code&gt;、&lt;code&gt;dict&lt;/code&gt;、&lt;code&gt;str&lt;/code&gt;等&lt;code&gt;Iterable&lt;/code&gt;变成&lt;code&gt;Iterator&lt;/code&gt;可以使用&lt;code&gt;iter()&lt;/code&gt;函数：&lt;/p&gt;</description></item><item><title>Python shutil移动文件</title><link>/2016/03/29/python-shutil%E7%A7%BB%E5%8A%A8%E6%96%87%E4%BB%B6/</link><pubDate>Tue, 29 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/29/python-shutil%E7%A7%BB%E5%8A%A8%E6%96%87%E4%BB%B6/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="python-终端输出字体设置"&gt;Python 终端输出字体设置&lt;/h2&gt;
&lt;p&gt;shutil可以实现文件的复制，移动&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 复制文件：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;shutil&lt;span style="color:#f92672"&gt;.&lt;/span&gt;copyfile(&lt;span style="color:#e6db74"&gt;&amp;#34;oldfile&amp;#34;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#34;newfile&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# oldfile和newfile都只能是文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;shutil&lt;span style="color:#f92672"&gt;.&lt;/span&gt;copy(&lt;span style="color:#e6db74"&gt;&amp;#34;oldfile&amp;#34;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#34;newfile&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# oldfile只能是文件夹，newfile可以是文件，也可以是目标目录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 复制文件夹：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;shutil&lt;span style="color:#f92672"&gt;.&lt;/span&gt;copytree(&lt;span style="color:#e6db74"&gt;&amp;#34;olddir&amp;#34;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#34;newdir&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# olddir和newdir都只能是目录，且newdir必须不存在&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 重命名文件（目录）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rename(&lt;span style="color:#e6db74"&gt;&amp;#34;oldname&amp;#34;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#34;newname&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 文件或目录都是使用这条命令&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 移动文件（目录）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;shutil&lt;span style="color:#f92672"&gt;.&lt;/span&gt;move(&lt;span style="color:#e6db74"&gt;&amp;#34;oldpos&amp;#34;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#34;newpos&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Python特性之生成器</title><link>/2016/03/29/python%E7%89%B9%E6%80%A7%E4%B9%8B%E7%94%9F%E6%88%90%E5%99%A8/</link><pubDate>Tue, 29 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/29/python%E7%89%B9%E6%80%A7%E4%B9%8B%E7%94%9F%E6%88%90%E5%99%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="生成器generator"&gt;生成器(generator)&lt;/h1&gt;
&lt;p&gt;在生成列表的过程中，根据需要动态生成列表的机制成为生成器。&lt;/p&gt;
&lt;p&gt;通过列表生成式，我们可以直接创建一个列表。但是，受到内存限制，列表容量肯定是有限的。而且，创建一个包含100万个元素的列表，不仅占用很大的存储空间，如果我们仅仅需要访问前面几个元素，那后面绝大多数元素占用的空间都白白浪费了。&lt;/p&gt;
&lt;p&gt;所以，如果列表元素可以按照某种算法推算出来，那我们是否可以在循环的过程中不断推算出后续的元素呢？这样就不必创建完整的list，从而节省大量的空间。在Python中，这种一边循环一边计算的机制，称为生成器：generator。&lt;/p&gt;
&lt;h2 id="next调用"&gt;next()调用&lt;/h2&gt;
&lt;p&gt;要创建一个generator，有很多种方法。第一种方法很简单，只要把一个列表生成式的&lt;code&gt;[]&lt;/code&gt;改成&lt;code&gt;()&lt;/code&gt;，就创建了一个generator：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; L = [x * x for x in range(6)]
&amp;gt;&amp;gt;&amp;gt; L
[0, 1, 4, 9, 16, 25]
&amp;gt;&amp;gt;&amp;gt; g = (x * x for x in range(6))
&amp;gt;&amp;gt;&amp;gt; g
&amp;lt;generator object &amp;lt;genexpr&amp;gt; at 0x1022ef630&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;创建&lt;code&gt;L&lt;/code&gt;和&lt;code&gt;g&lt;/code&gt;的区别仅在于最外层的&lt;code&gt;[]&lt;/code&gt;和&lt;code&gt;()&lt;/code&gt;，&lt;code&gt;L&lt;/code&gt;是一个list，而&lt;code&gt;g&lt;/code&gt;是一个generator。&lt;/p&gt;
&lt;p&gt;我们可以直接打印出list的每一个元素，但我们怎么打印出generator的每一个元素呢？&lt;/p&gt;
&lt;p&gt;如果要一个一个打印出来，可以通过&lt;code&gt;next()&lt;/code&gt;函数获得generator的下一个返回值：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; next(g)
0
&amp;gt;&amp;gt;&amp;gt; next(g)
1
&amp;gt;&amp;gt;&amp;gt; next(g)
4
&amp;gt;&amp;gt;&amp;gt; next(g)
9
&amp;gt;&amp;gt;&amp;gt; next(g)
16
&amp;gt;&amp;gt;&amp;gt; next(g)
25
&amp;gt;&amp;gt;&amp;gt; next(g)
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
StopIteration
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们讲过，generator保存的是算法，每次调用&lt;code&gt;next(g)&lt;/code&gt;，就计算出&lt;code&gt;g&lt;/code&gt;的下一个元素的值，直到计算到最后一个元素，没有更多的元素时，抛出&lt;code&gt;StopIteration&lt;/code&gt;的错误。&lt;/p&gt;</description></item><item><title>Python输出颜色</title><link>/2016/03/27/python%E8%BE%93%E5%87%BA%E9%A2%9C%E8%89%B2/</link><pubDate>Sun, 27 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/27/python%E8%BE%93%E5%87%BA%E9%A2%9C%E8%89%B2/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="python-终端输出字体设置"&gt;Python 终端输出字体设置&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;实现过程：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;​ 终端的字符颜色是用转义序列控制的，是文本模式下的系统显示功能，和具体的语言无关。&lt;/p&gt;
&lt;p&gt;​ 转义序列是以ESC开头,即用\033来完成（ESC的ASCII码用十进制表示是27，用八进制表示就是033）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;书写格式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;​ &lt;strong&gt;开头部分&lt;/strong&gt;：\033[显示方式;前景色;背景色m + &lt;strong&gt;结尾部分：&lt;/strong&gt;\033[m&lt;/p&gt;
&lt;p&gt;​ 注意：开头部分的三个参数：显示方式，前景色，背景色是可选参数，可以只写其中的某一个；另外由于表示三个参数不同含义的数值都是唯一的没有重复的，所以三个参数的书写先后顺序没有固定要求，系统都能识别；但是，建议按照默认的格式规范书写。&lt;/p&gt;
&lt;p&gt;​ 对于结尾部分，其实也可以省略，但是为了书写规范，建议\033[***开头，\033[0m结尾。很多人在结尾部分写成\033[0m，很多文章也是这么说，多半估计他们都是直接抄的别人的blog，其实连0 都是可以省略掉的，我自己试过没有问题。&lt;/p&gt;
&lt;p&gt;那这些参数分别是什么值，又代表什么意思呢，请看下边的表，&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数值表示的参数含义：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;显示方式&lt;/th&gt;
&lt;th&gt;0（默认值）&lt;/th&gt;
&lt;th&gt;22（非粗体）&lt;/th&gt;
&lt;th&gt;4（下划线）&lt;/th&gt;
&lt;th&gt;24（非下划线）&lt;/th&gt;
&lt;th&gt;5（闪烁）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;显示方式&lt;/td&gt;
&lt;td&gt;25（非闪烁）&lt;/td&gt;
&lt;td&gt;7（反显）&lt;/td&gt;
&lt;td&gt;27（非反显）&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;前景色&lt;/td&gt;
&lt;td&gt;30（黑色）&lt;/td&gt;
&lt;td&gt;31（红色）&lt;/td&gt;
&lt;td&gt;32（绿色）&lt;/td&gt;
&lt;td&gt;33（黄色）&lt;/td&gt;
&lt;td&gt;34（蓝色）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;前景色&lt;/td&gt;
&lt;td&gt;35（洋红）&lt;/td&gt;
&lt;td&gt;36（青色）&lt;/td&gt;
&lt;td&gt;37（白色）&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;背景色&lt;/td&gt;
&lt;td&gt;40（黑色）&lt;/td&gt;
&lt;td&gt;41（红色）&lt;/td&gt;
&lt;td&gt;42（绿色）&lt;/td&gt;
&lt;td&gt;43（黄色）&lt;/td&gt;
&lt;td&gt;44（蓝色）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;背景色&lt;/td&gt;
&lt;td&gt;45（洋红）&lt;/td&gt;
&lt;td&gt;46（青色）&lt;/td&gt;
&lt;td&gt;47（白色）&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;常见开头格式&lt;/strong&gt;：
\033[m 默认字体正常显示，不高亮
\033[31m 红色字体正常显示
\033[1;32;40m 显示方式: 高亮 字体前景色：绿色 背景色：黑色
\033[31;46m 显示方式: 正常 字体前景色：红色 背景色：青色&lt;/p&gt;</description></item><item><title>Python</title><link>/2016/03/22/python/</link><pubDate>Tue, 22 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/22/python/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="什么是列表生成式"&gt;什么是列表生成式&lt;/h1&gt;
&lt;p&gt;列表生成式即List Comprehensions，是Python内置的非常简单却强大的可以用来创建list的生成式。&lt;/p&gt;
&lt;p&gt;举个例子，要生成list &lt;code&gt;[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]&lt;/code&gt;可以用&lt;code&gt;list(range(1, 11))&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但如果要生成&lt;code&gt;[1x1, 2x2, 3x3, ..., 10x10]&lt;/code&gt;怎么做？方法一是循环：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; L = []
&amp;gt;&amp;gt;&amp;gt; for x in range(1, 11):
... L.append(x * x)
...
&amp;gt;&amp;gt;&amp;gt; L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是循环太繁琐，而列表生成式则可以用一行语句代替循环生成上面的list：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;写列表生成式时，把要生成的元素&lt;code&gt;x * x&lt;/code&gt;放到前面，后面跟&lt;code&gt;for&lt;/code&gt;循环，就可以把list创建出来，十分有用，多写几次，很快就可以熟悉这种语法。&lt;/p&gt;</description></item><item><title>Python特性之迭代</title><link>/2016/03/22/python%E7%89%B9%E6%80%A7%E4%B9%8B%E8%BF%AD%E4%BB%A3/</link><pubDate>Tue, 22 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/22/python%E7%89%B9%E6%80%A7%E4%B9%8B%E8%BF%AD%E4%BB%A3/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="迭代"&gt;迭代&lt;/h3&gt;
&lt;p&gt;如果给定一个list或tuple，我们可以通过&lt;code&gt;for&lt;/code&gt;循环来遍历这个list或tuple，这种遍历我们称为迭代（Iteration）。&lt;/p&gt;
&lt;h4 id="list和tuple"&gt;list和tuple&lt;/h4&gt;
&lt;p&gt;在Python中，迭代是通过&lt;code&gt;for ... in&lt;/code&gt;来完成的，而很多语言比如C语言，迭代list是通过下标完成的，比如Java代码：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;for (i=0; i&amp;lt;list.length; i++) {
n = list[i];
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看出，Python的&lt;code&gt;for&lt;/code&gt;循环抽象程度要高于C的&lt;code&gt;for&lt;/code&gt;循环，因为Python的&lt;code&gt;for&lt;/code&gt;循环不仅可以用在list或tuple上，还可以作用在其他可迭代对象上。&lt;/p&gt;
&lt;h4 id="dict"&gt;dict&lt;/h4&gt;
&lt;p&gt;list这种数据类型虽然有下标，但很多其他数据类型是没有下标的，但是，只要是可迭代对象，无论有无下标，都可以迭代，比如dict就可以迭代：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; d = {&amp;#39;a&amp;#39;: 1, &amp;#39;b&amp;#39;: 2, &amp;#39;c&amp;#39;: 3}
&amp;gt;&amp;gt;&amp;gt; for key in d:
... print(key)
...
a
c
b
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因为dict的存储不是按照list的方式顺序排列，所以，迭代出的结果顺序很可能不一样。&lt;/p&gt;
&lt;p&gt;默认情况下，dict迭代的是key。如果要迭代value，可以用&lt;code&gt;for value in d.values()&lt;/code&gt;，如果要同时迭代key和value，可以用&lt;code&gt;for k, v in d.items()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;由于字符串也是可迭代对象，因此，也可以作用于&lt;code&gt;for&lt;/code&gt;循环：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; for ch in &amp;#39;ABC&amp;#39;:
... print(ch)
...
A
B
C
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;所以，当我们使用&lt;code&gt;for&lt;/code&gt;循环时，只要作用于一个可迭代对象，&lt;code&gt;for&lt;/code&gt;循环就可以正常运行，而我们不太关心该对象究竟是list还是其他数据类型。&lt;/p&gt;
&lt;p&gt;那么，如何判断一个对象是可迭代对象呢？方法是通过collections模块的Iterable类型判断：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from collections import Iterable
&amp;gt;&amp;gt;&amp;gt; isinstance(&amp;#39;abc&amp;#39;, Iterable) # str是否可迭代
True
&amp;gt;&amp;gt;&amp;gt; isinstance([1,2,3], Iterable) # list是否可迭代
True
&amp;gt;&amp;gt;&amp;gt; isinstance(123, Iterable) # 整数是否可迭代
False
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="迭代索引"&gt;迭代索引&lt;/h4&gt;
&lt;p&gt;最后一个小问题，如果要对list实现类似Java那样的下标循环怎么办？Python内置的&lt;code&gt;enumerate&lt;/code&gt;函数可以把一个list变成索引-元素对，这样就可以在&lt;code&gt;for&lt;/code&gt;循环中同时迭代索引和元素本身：&lt;/p&gt;</description></item><item><title>Python特性之切片</title><link>/2016/03/20/python%E7%89%B9%E6%80%A7%E4%B9%8B%E5%88%87%E7%89%87/</link><pubDate>Sun, 20 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/20/python%E7%89%B9%E6%80%A7%E4%B9%8B%E5%88%87%E7%89%87/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h4 id="切片"&gt;切片&lt;/h4&gt;
&lt;hr&gt;
&lt;p&gt;取一个list或tuple的部分元素是非常常见的操作。比如，一个list如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; L = [&amp;#39;Michael&amp;#39;, &amp;#39;Sarah&amp;#39;, &amp;#39;Tracy&amp;#39;, &amp;#39;Bob&amp;#39;, &amp;#39;Jack&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;取前3个元素，应该怎么做？&lt;/p&gt;
&lt;p&gt;笨办法：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; [L[0], L[1], L[2]]
[&amp;#39;Michael&amp;#39;, &amp;#39;Sarah&amp;#39;, &amp;#39;Tracy&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;之所以是笨办法是因为扩展一下，取前N个元素就没辙了。&lt;/p&gt;
&lt;p&gt;取前N个元素，也就是索引为0-(N-1)的元素，可以用循环：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; r = []
&amp;gt;&amp;gt;&amp;gt; n = 3
&amp;gt;&amp;gt;&amp;gt; for i in range(n):
... r.append(L[i])
...
&amp;gt;&amp;gt;&amp;gt; r
[&amp;#39;Michael&amp;#39;, &amp;#39;Sarah&amp;#39;, &amp;#39;Tracy&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对这种经常取指定索引范围的操作，用循环十分繁琐，因此，Python提供了切片（Slice）操作符，能大大简化这种操作。&lt;/p&gt;
&lt;p&gt;对应上面的问题，取前3个元素，用一行代码就可以完成切片：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; L[0:3]
[&amp;#39;Michael&amp;#39;, &amp;#39;Sarah&amp;#39;, &amp;#39;Tracy&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;L[0:3]&lt;/code&gt;表示，从索引&lt;code&gt;0&lt;/code&gt;开始取，直到索引&lt;code&gt;3&lt;/code&gt;为止，但不包括索引&lt;code&gt;3&lt;/code&gt;。即索引&lt;code&gt;0&lt;/code&gt;，&lt;code&gt;1&lt;/code&gt;，&lt;code&gt;2&lt;/code&gt;，正好是3个元素。&lt;/p&gt;
&lt;p&gt;如果第一个索引是&lt;code&gt;0&lt;/code&gt;，还可以省略：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; L[:3]
[&amp;#39;Michael&amp;#39;, &amp;#39;Sarah&amp;#39;, &amp;#39;Tracy&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;也可以从索引1开始，取出2个元素出来：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; L[1:3]
[&amp;#39;Sarah&amp;#39;, &amp;#39;Tracy&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;类似的，既然Python支持&lt;code&gt;L[-1]&lt;/code&gt;取倒数第一个元素，那么它同样支持倒数切片，试试：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; L[-2:]
[&amp;#39;Bob&amp;#39;, &amp;#39;Jack&amp;#39;]
&amp;gt;&amp;gt;&amp;gt; L[-2:-1]
[&amp;#39;Bob&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;记住倒数第一个元素的索引是&lt;code&gt;-1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;切片操作十分有用。我们先创建一个0-99的数列：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; L = list(range(100))
&amp;gt;&amp;gt;&amp;gt; L
[0, 1, 2, 3, ..., 99]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以通过切片轻松取出某一段数列。比如前10个数：&lt;/p&gt;</description></item><item><title>Python函数的参数</title><link>/2016/03/16/python%E5%87%BD%E6%95%B0%E7%9A%84%E5%8F%82%E6%95%B0/</link><pubDate>Wed, 16 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/16/python%E5%87%BD%E6%95%B0%E7%9A%84%E5%8F%82%E6%95%B0/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="函数的参数"&gt;函数的参数&lt;/h3&gt;
&lt;p&gt;定义函数的时候，我们把参数的名字和位置确定下来，函数的接口定义就完成了。对于函数的调用者来说，只需要知道如何传递正确的参数，以及函数将返回什么样的值就够了，函数内部的复杂逻辑被封装起来，调用者无需了解。&lt;/p&gt;
&lt;p&gt;Python的函数定义非常简单，但灵活度却非常大。除了正常定义的必选参数外，还可以使用默认参数、可变参数和关键字参数，使得函数定义出来的接口，不但能处理复杂的参数，还可以简化调用者的代码。&lt;/p&gt;
&lt;h3 id="位置参数"&gt;位置参数&lt;/h3&gt;
&lt;p&gt;我们先写一个计算x^2^的函数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def power(x):
return x * x
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于&lt;code&gt;power(x)&lt;/code&gt;函数，参数&lt;code&gt;x&lt;/code&gt;就是一个位置参数。&lt;/p&gt;
&lt;p&gt;当我们调用&lt;code&gt;power&lt;/code&gt;函数时，必须传入有且仅有的一个参数&lt;code&gt;x&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; power(5)
25
&amp;gt;&amp;gt;&amp;gt; power(15)
225
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在，如果我们要计算x^3^怎么办？可以再定义一个&lt;code&gt;power3&lt;/code&gt;函数，但是如果要计算x^4^、x^5^……怎么办？我们不可能定义无限多个函数。&lt;/p&gt;
&lt;p&gt;你也许想到了，可以把&lt;code&gt;power(x)&lt;/code&gt;修改为&lt;code&gt;power(x, n)&lt;/code&gt;，用来计算x^n^，说干就干：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def power(x, n):
s = 1
while n &amp;gt; 0:
n = n - 1
s = s * x
return s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于这个修改后的&lt;code&gt;power(x, n)&lt;/code&gt;函数，可以计算任意n次方：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; power(5, 2)
25
&amp;gt;&amp;gt;&amp;gt; power(5, 3)
125
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改后的&lt;code&gt;power(x, n)&lt;/code&gt;函数有两个参数：&lt;code&gt;x&lt;/code&gt;和&lt;code&gt;n&lt;/code&gt;，这两个参数都是位置参数，调用函数时，传入的两个值按照位置顺序依次赋给参数&lt;code&gt;x&lt;/code&gt;和&lt;code&gt;n&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="默认参数"&gt;默认参数&lt;/h3&gt;
&lt;p&gt;新的&lt;code&gt;power(x, n)&lt;/code&gt;函数定义没有问题，但是，旧的调用代码失败了，原因是我们增加了一个参数，导致旧的代码因为缺少一个参数而无法正常调用：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; power(5)
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
TypeError: power() missing 1 required positional argument: &amp;#39;n&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Python的错误信息很明确：调用函数&lt;code&gt;power()&lt;/code&gt;缺少了一个位置参数&lt;code&gt;n&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Python函数的使用</title><link>/2016/03/14/python%E5%87%BD%E6%95%B0%E7%9A%84%E4%BD%BF%E7%94%A8/</link><pubDate>Mon, 14 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/14/python%E5%87%BD%E6%95%B0%E7%9A%84%E4%BD%BF%E7%94%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="调用函数"&gt;调用函数&lt;/h3&gt;
&lt;p&gt;Python内置了很多有用的函数，我们可以直接调用。&lt;/p&gt;
&lt;p&gt;要调用一个函数，需要知道函数的名称和参数，比如求绝对值的函数&lt;code&gt;abs&lt;/code&gt;，只有一个参数。可以直接从Python的官方网站查看文档：&lt;/p&gt;
&lt;p&gt;&lt;a href="http://docs.python.org/3/library/functions.html#abs"&gt;http://docs.python.org/3/library/functions.html#abs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;也可以在交互式命令行通过&lt;code&gt;help(abs)&lt;/code&gt;查看&lt;code&gt;abs&lt;/code&gt;函数的帮助信息。&lt;/p&gt;
&lt;p&gt;调用&lt;code&gt;abs&lt;/code&gt;函数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; abs(100)
100
&amp;gt;&amp;gt;&amp;gt; abs(-20)
20
&amp;gt;&amp;gt;&amp;gt; abs(12.34)
12.34
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;调用函数的时候，如果传入的参数数量不对，会报&lt;code&gt;TypeError&lt;/code&gt;的错误，并且Python会明确地告诉你：&lt;code&gt;abs()&lt;/code&gt;有且仅有1个参数，但给出了两个：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; abs(1, 2)
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
TypeError: abs() takes exactly one argument (2 given)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果传入的参数数量是对的，但参数类型不能被函数所接受，也会报&lt;code&gt;TypeError&lt;/code&gt;的错误，并且给出错误信息：&lt;code&gt;str&lt;/code&gt;是错误的参数类型：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; abs(&amp;#39;a&amp;#39;)
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
TypeError: bad operand type for abs(): &amp;#39;str&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而&lt;code&gt;max&lt;/code&gt;函数&lt;code&gt;max()&lt;/code&gt;可以接收任意多个参数，并返回最大的那个：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; max(1, 2)
2
&amp;gt;&amp;gt;&amp;gt; max(2, 3, 1, -5)
3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;###数据类型转换&lt;/p&gt;</description></item><item><title>CentOS安装Docker</title><link>/2016/03/13/centos%E5%AE%89%E8%A3%85docker/</link><pubDate>Sun, 13 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/13/centos%E5%AE%89%E8%A3%85docker/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h2 id="docker-安装"&gt;Docker 安装&lt;/h2&gt;
&lt;p&gt;docker 简单又实用,一起来学习吧.现在官方也给出了比较全面(各种系统各种版本)的安装方法.&lt;a href="https://docs.docker.com/install/"&gt;链接在此&lt;/a&gt;.下面简单记录一下centos下的安装.&lt;/p&gt;
&lt;h3 id="卸载旧版本"&gt;卸载旧版本&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="yum源安装"&gt;yum源安装&lt;/h3&gt;
&lt;p&gt;通过添加docker仓库,只要有网在哪里都可以下载.有版本更新也可以直接一条命令解决.&lt;/p&gt;
&lt;h4 id="安装依赖包"&gt;安装依赖包&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="添加docker仓库"&gt;添加docker仓库&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="一键安装"&gt;一键安装&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo yum install docker-ce
sudo usermod -aG docker your-user #普通用户要使用docker需要添加到docker组
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="启动docker"&gt;启动docker&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo systemctl start docker
docker version #查看版本
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="rpm包安装"&gt;rpm包安装&lt;/h3&gt;
&lt;p&gt;下载好的rpm安装快速,可离线安装.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo yum install /path/to/package.rpm
sudo usermod -aG docker your-user #普通用户要使用docker需要添加到docker组
sudo systemctl start docker
docker version #查看版本
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="脚本安装"&gt;脚本安装&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker your-user #普通用户要使用docker需要添加到docker组
sudo systemctl start docker
docker version #查看版本
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="镜像加速器"&gt;镜像加速器&lt;/h3&gt;
&lt;p&gt;在国内下载docker镜像很可能会很慢,甚至有的都不能下载.使用加速器将会提升在国内获取Docker官方镜像的速度.其实就是阿里等先把官方的镜像下载到自己的机房,定时更新然后做成一个仓库站点,供国内使用,所以要快很多.添加方法很简单,通过修改daemon配置文件&lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;来使用加速器.&lt;/p&gt;</description></item><item><title>Python字典dict和集set</title><link>/2016/03/02/python%E5%AD%97%E5%85%B8dict%E5%92%8C%E9%9B%86set/</link><pubDate>Wed, 02 Mar 2016 00:00:00 +0000</pubDate><guid>/2016/03/02/python%E5%AD%97%E5%85%B8dict%E5%92%8C%E9%9B%86set/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="dict"&gt;dict&lt;/h3&gt;
&lt;p&gt;Python内置了字典：dict的支持，dict全称dictionary，在其他语言中也称为map，使用键-值（key-value）存储，具有极快的查找速度。&lt;/p&gt;
&lt;p&gt;举个例子，假设要根据同学的名字查找对应的成绩，如果用list实现，需要两个list：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;names = [&amp;#39;Michael&amp;#39;, &amp;#39;Bob&amp;#39;, &amp;#39;Tracy&amp;#39;]
scores = [95, 75, 85]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;给定一个名字，要查找对应的成绩，就先要在names中找到对应的位置，再从scores取出对应的成绩，list越长，耗时越长。&lt;/p&gt;
&lt;p&gt;如果用dict实现，只需要一个“名字”-“成绩”的对照表，直接根据名字查找成绩，无论这个表有多大，查找速度都不会变慢。用Python写一个dict如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; d = {&amp;#39;Michael&amp;#39;: 95, &amp;#39;Bob&amp;#39;: 75, &amp;#39;Tracy&amp;#39;: 85}
&amp;gt;&amp;gt;&amp;gt; d[&amp;#39;Michael&amp;#39;]
95
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;为什么dict查找速度这么快？因为dict的实现原理和查字典是一样的。假设字典包含了1万个汉字，我们要查某一个字，一个办法是把字典从第一页往后翻，直到找到我们想要的字为止，这种方法就是在list中查找元素的方法，list越大，查找越慢。&lt;/p&gt;
&lt;p&gt;第二种方法是先在字典的索引表里（比如部首表）查这个字对应的页码，然后直接翻到该页，找到这个字。无论找哪个字，这种查找速度都非常快，不会随着字典大小的增加而变慢。&lt;/p&gt;
&lt;p&gt;dict就是第二种实现方式，给定一个名字，比如&lt;code&gt;'Michael'&lt;/code&gt;，dict在内部就可以直接计算出&lt;code&gt;Michael&lt;/code&gt;对应的存放成绩的“页码”，也就是&lt;code&gt;95&lt;/code&gt;这个数字存放的内存地址，直接取出来，所以速度非常快。&lt;/p&gt;
&lt;p&gt;你可以猜到，这种key-value存储方式，在放进去的时候，必须根据key算出value的存放位置，这样，取的时候才能根据key直接拿到value。&lt;/p&gt;
&lt;p&gt;把数据放入dict的方法，除了初始化时指定外，还可以通过key放入：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; d[&amp;#39;Adam&amp;#39;] = 67
&amp;gt;&amp;gt;&amp;gt; d[&amp;#39;Adam&amp;#39;]
67
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;由于一个key只能对应一个value，所以，多次对一个key放入value，后面的值会把前面的值冲掉：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; d[&amp;#39;Jack&amp;#39;] = 90
&amp;gt;&amp;gt;&amp;gt; d[&amp;#39;Jack&amp;#39;]
90
&amp;gt;&amp;gt;&amp;gt; d[&amp;#39;Jack&amp;#39;] = 88
&amp;gt;&amp;gt;&amp;gt; d[&amp;#39;Jack&amp;#39;]
88
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果key不存在，dict就会报错：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; d[&amp;#39;Thomas&amp;#39;]
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
KeyError: &amp;#39;Thomas&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;要避免key不存在的错误，有两种办法，一是通过&lt;code&gt;in&lt;/code&gt;判断key是否存在：&lt;/p&gt;</description></item><item><title>Python判断和循环</title><link>/2016/02/28/python%E5%88%A4%E6%96%AD%E5%92%8C%E5%BE%AA%E7%8E%AF/</link><pubDate>Sun, 28 Feb 2016 00:00:00 +0000</pubDate><guid>/2016/02/28/python%E5%88%A4%E6%96%AD%E5%92%8C%E5%BE%AA%E7%8E%AF/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="条件判断"&gt;条件判断&lt;/h3&gt;
&lt;p&gt;计算机之所以能做很多自动化的任务，因为它可以自己做条件判断。&lt;/p&gt;
&lt;p&gt;比如，输入用户年龄，根据年龄打印不同的内容，在Python程序中，用&lt;code&gt;if&lt;/code&gt;语句实现：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;age = 20
if age &amp;gt;= 18:
print(&amp;#39;your age is&amp;#39;, age)
print(&amp;#39;adult&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;根据Python的缩进规则，如果&lt;code&gt;if&lt;/code&gt;语句判断是&lt;code&gt;True&lt;/code&gt;，就把缩进的两行print语句执行了，否则，什么也不做。&lt;/p&gt;
&lt;p&gt;也可以给&lt;code&gt;if&lt;/code&gt;添加一个&lt;code&gt;else&lt;/code&gt;语句，意思是，如果&lt;code&gt;if&lt;/code&gt;判断是&lt;code&gt;False&lt;/code&gt;，不要执行&lt;code&gt;if&lt;/code&gt;的内容，去把&lt;code&gt;else&lt;/code&gt;执行了：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;age = 3
if age &amp;gt;= 18:
print(&amp;#39;your age is&amp;#39;, age)
print(&amp;#39;adult&amp;#39;)
else:
print(&amp;#39;your age is&amp;#39;, age)
print(&amp;#39;teenager&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注意不要少写了冒号&lt;code&gt;:&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;当然上面的判断是很粗略的，完全可以用&lt;code&gt;elif&lt;/code&gt;做更细致的判断：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;age = 3
if age &amp;gt;= 18:
print(&amp;#39;adult&amp;#39;)
elif age &amp;gt;= 6:
print(&amp;#39;teenager&amp;#39;)
else:
print(&amp;#39;kid&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;elif&lt;/code&gt;是&lt;code&gt;else if&lt;/code&gt;的缩写，完全可以有多个&lt;code&gt;elif&lt;/code&gt;，所以&lt;code&gt;if&lt;/code&gt;语句的完整形式就是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;if &amp;lt;条件判断1&amp;gt;:
&amp;lt;执行1&amp;gt;
elif &amp;lt;条件判断2&amp;gt;:
&amp;lt;执行2&amp;gt;
elif &amp;lt;条件判断3&amp;gt;:
&amp;lt;执行3&amp;gt;
else:
&amp;lt;执行4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;if&lt;/code&gt;语句执行有个特点，它是从上往下判断，如果在某个判断上是&lt;code&gt;True&lt;/code&gt;，把该判断对应的语句执行后，就忽略掉剩下的&lt;code&gt;elif&lt;/code&gt;和&lt;code&gt;else&lt;/code&gt;，所以，请测试并解释为什么下面的程序打印的是&lt;code&gt;teenager&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;age = 20
if age &amp;gt;= 6:
print(&amp;#39;teenager&amp;#39;)
elif age &amp;gt;= 18:
print(&amp;#39;adult&amp;#39;)
else:
print(&amp;#39;kid&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;if&lt;/code&gt;判断条件还可以简写，比如写：&lt;/p&gt;</description></item><item><title>Python列表list和元组tuple</title><link>/2016/02/27/python%E5%88%97%E8%A1%A8list%E5%92%8C%E5%85%83%E7%BB%84tuple/</link><pubDate>Sat, 27 Feb 2016 00:00:00 +0000</pubDate><guid>/2016/02/27/python%E5%88%97%E8%A1%A8list%E5%92%8C%E5%85%83%E7%BB%84tuple/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="list"&gt;list&lt;/h3&gt;
&lt;p&gt;Python内置的一种数据类型是列表：list。list是一种有序的集合，可以随时添加和删除其中的元素。&lt;/p&gt;
&lt;p&gt;比如，列出班里所有同学的名字，就可以用一个list表示：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; classmates = [&amp;#39;Michael&amp;#39;, &amp;#39;Bob&amp;#39;, &amp;#39;Tracy&amp;#39;]
&amp;gt;&amp;gt;&amp;gt; classmates
[&amp;#39;Michael&amp;#39;, &amp;#39;Bob&amp;#39;, &amp;#39;Tracy&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;变量&lt;code&gt;classmates&lt;/code&gt;就是一个list。用&lt;code&gt;len()&lt;/code&gt;函数可以获得list元素的个数：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; len(classmates)
3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用索引来访问list中每一个位置的元素，记得索引是从&lt;code&gt;0&lt;/code&gt;开始的：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; classmates[0]
&amp;#39;Michael&amp;#39;
&amp;gt;&amp;gt;&amp;gt; classmates[1]
&amp;#39;Bob&amp;#39;
&amp;gt;&amp;gt;&amp;gt; classmates[2]
&amp;#39;Tracy&amp;#39;
&amp;gt;&amp;gt;&amp;gt; classmates[3]
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
IndexError: list index out of range
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当索引超出了范围时，Python会报一个&lt;code&gt;IndexError&lt;/code&gt;错误，所以，要确保索引不要越界，记得最后一个元素的索引是&lt;code&gt;len(classmates) - 1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果要取最后一个元素，除了计算索引位置外，还可以用&lt;code&gt;-1&lt;/code&gt;做索引，直接获取最后一个元素：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; classmates[-1]
&amp;#39;Tracy&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;以此类推，可以获取倒数第2个、倒数第3个：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; classmates[-2]
&amp;#39;Bob&amp;#39;
&amp;gt;&amp;gt;&amp;gt; classmates[-3]
&amp;#39;Michael&amp;#39;
&amp;gt;&amp;gt;&amp;gt; classmates[-4]
Traceback (most recent call last):
File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
IndexError: list index out of range
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然，倒数第4个就越界了。&lt;/p&gt;</description></item><item><title>Python字符串和编码</title><link>/2016/02/26/python%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%92%8C%E7%BC%96%E7%A0%81/</link><pubDate>Fri, 26 Feb 2016 00:00:00 +0000</pubDate><guid>/2016/02/26/python%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%92%8C%E7%BC%96%E7%A0%81/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="字符编码"&gt;字符编码&lt;/h3&gt;
&lt;p&gt;我们已经讲过了，字符串也是一种数据类型，但是，字符串比较特殊的是还有一个编码问题。&lt;/p&gt;
&lt;p&gt;因为计算机只能处理数字，如果要处理文本，就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特（bit）作为一个字节（byte），所以，一个字节能表示的最大的整数就是255（二进制11111111=十进制255），如果要表示更大的整数，就必须用更多的字节。比如两个字节可以表示的最大整数是&lt;code&gt;65535&lt;/code&gt;，4个字节可以表示的最大整数是&lt;code&gt;4294967295&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;由于计算机是美国人发明的，因此，最早只有127个字符被编码到计算机里，也就是大小写英文字母、数字和一些符号，这个编码表被称为&lt;code&gt;ASCII&lt;/code&gt;编码，比如大写字母&lt;code&gt;A&lt;/code&gt;的编码是&lt;code&gt;65&lt;/code&gt;，小写字母&lt;code&gt;z&lt;/code&gt;的编码是&lt;code&gt;122&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;但是要处理中文显然一个字节是不够的，至少需要两个字节，而且还不能和ASCII编码冲突，所以，中国制定了&lt;code&gt;GB2312&lt;/code&gt;编码，用来把中文编进去。&lt;/p&gt;
&lt;p&gt;你可以想得到的是，全世界有上百种语言，日本把日文编到&lt;code&gt;Shift_JIS&lt;/code&gt;里，韩国把韩文编到&lt;code&gt;Euc-kr&lt;/code&gt;里，各国有各国的标准，就会不可避免地出现冲突，结果就是，在多语言混合的文本中，显示出来会有乱码。&lt;/p&gt;
&lt;p&gt;&lt;img alt="char-encoding-problem" loading="lazy" src="0"&gt;&lt;/p&gt;
&lt;p&gt;因此，Unicode应运而生。Unicode把所有语言都统一到一套编码里，这样就不会再有乱码问题了。&lt;/p&gt;
&lt;p&gt;Unicode标准也在不断发展，但最常用的是用两个字节表示一个字符（如果要用到非常偏僻的字符，就需要4个字节）。现代操作系统和大多数编程语言都直接支持Unicode。&lt;/p&gt;
&lt;p&gt;现在，捋一捋ASCII编码和Unicode编码的区别：ASCII编码是1个字节，而Unicode编码通常是2个字节。&lt;/p&gt;
&lt;p&gt;字母&lt;code&gt;A&lt;/code&gt;用ASCII编码是十进制的&lt;code&gt;65&lt;/code&gt;，二进制的&lt;code&gt;01000001&lt;/code&gt;；&lt;/p&gt;
&lt;p&gt;字符&lt;code&gt;0&lt;/code&gt;用ASCII编码是十进制的&lt;code&gt;48&lt;/code&gt;，二进制的&lt;code&gt;00110000&lt;/code&gt;，注意字符&lt;code&gt;'0'&lt;/code&gt;和整数&lt;code&gt;0&lt;/code&gt;是不同的；&lt;/p&gt;
&lt;p&gt;汉字&lt;code&gt;中&lt;/code&gt;已经超出了ASCII编码的范围，用Unicode编码是十进制的&lt;code&gt;20013&lt;/code&gt;，二进制的&lt;code&gt;01001110 00101101&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;你可以猜测，如果把ASCII编码的&lt;code&gt;A&lt;/code&gt;用Unicode编码，只需要在前面补0就可以，因此，&lt;code&gt;A&lt;/code&gt;的Unicode编码是&lt;code&gt;00000000 01000001&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;新的问题又出现了：如果统一成Unicode编码，乱码问题从此消失了。但是，如果你写的文本基本上全部是英文的话，用Unicode编码比ASCII编码需要多一倍的存储空间，在存储和传输上就十分不划算。&lt;/p&gt;
&lt;p&gt;所以，本着节约的精神，又出现了把Unicode编码转化为“可变长编码”的&lt;code&gt;UTF-8&lt;/code&gt;编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节，常用的英文字母被编码成1个字节，汉字通常是3个字节，只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符，用UTF-8编码就能节省空间：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字符&lt;/th&gt;
&lt;th&gt;ASCII&lt;/th&gt;
&lt;th&gt;Unicode&lt;/th&gt;
&lt;th&gt;UTF-8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;01000001&lt;/td&gt;
&lt;td&gt;00000000 01000001&lt;/td&gt;
&lt;td&gt;01000001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;01001110 00101101&lt;/td&gt;
&lt;td&gt;11100100 10111000 10101101&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;从上面的表格还可以发现，UTF-8编码有一个额外的好处，就是ASCII编码实际上可以被看成是UTF-8编码的一部分，所以，大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。&lt;/p&gt;
&lt;p&gt;搞清楚了ASCII、Unicode和UTF-8的关系，我们就可以总结一下现在计算机系统通用的字符编码工作方式：&lt;/p&gt;
&lt;p&gt;在计算机内存中，统一使用Unicode编码，当需要保存到硬盘或者需要传输的时候，就转换为UTF-8编码。&lt;/p&gt;
&lt;p&gt;用记事本编辑的时候，从文件读取的UTF-8字符被转换为Unicode字符到内存里，编辑完成后，保存的时候再把Unicode转换为UTF-8保存到文件：&lt;/p&gt;
&lt;p&gt;&lt;img alt="rw-file-utf-8" loading="lazy" src="0"&gt;&lt;/p&gt;
&lt;p&gt;浏览网页的时候，服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器：&lt;/p&gt;
&lt;p&gt;&lt;img alt="web-utf-8" loading="lazy" src="0"&gt;&lt;/p&gt;
&lt;p&gt;所以你看到很多网页的源码上会有类似&lt;code&gt;&amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;gt;&lt;/code&gt;的信息，表示该网页正是用的UTF-8编码。&lt;/p&gt;
&lt;h3 id="python的字符串"&gt;Python的字符串&lt;/h3&gt;
&lt;p&gt;搞清楚了令人头疼的字符编码问题后，我们再来研究Python的字符串。&lt;/p&gt;
&lt;p&gt;在最新的Python 3版本中，字符串是以Unicode编码的，也就是说，Python的字符串支持多语言，例如：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print(&amp;#39;包含中文的str&amp;#39;)
包含中文的str
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于单个字符的编码，Python提供了&lt;code&gt;ord()&lt;/code&gt;函数获取字符的整数表示，&lt;code&gt;chr()&lt;/code&gt;函数把编码转换为对应的字符：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; ord(&amp;#39;A&amp;#39;)
65
&amp;gt;&amp;gt;&amp;gt; chr(66)
&amp;#39;B&amp;#39;
#注意下面两个在jupyter notebook中会报错，但是在python命令行中却不会错, 看看你的locale charmap是否为utf-8
&amp;gt;&amp;gt;&amp;gt; ord(&amp;#39;中&amp;#39;)
20013
&amp;gt;&amp;gt;&amp;gt; chr(25991)
&amp;#39;文&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果知道字符的整数编码，还可以用十六进制这么写&lt;code&gt;str&lt;/code&gt;：&lt;/p&gt;</description></item><item><title>python数据类型和变量</title><link>/2016/02/25/python%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%8F%98%E9%87%8F/</link><pubDate>Thu, 25 Feb 2016 00:00:00 +0000</pubDate><guid>/2016/02/25/python%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%8F%98%E9%87%8F/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在Python中，能够直接处理的数据类型有以下几种：&lt;/p&gt;
&lt;h3 id="整数"&gt;整数&lt;/h3&gt;
&lt;p&gt;Python可以处理任意大小的整数，当然包括负整数，在程序中的表示方法和数学上的写法一模一样，例如：&lt;code&gt;1&lt;/code&gt;，&lt;code&gt;100&lt;/code&gt;，&lt;code&gt;-8080&lt;/code&gt;，&lt;code&gt;0&lt;/code&gt;，等等。&lt;/p&gt;
&lt;p&gt;计算机由于使用二进制，所以，有时候用十六进制表示整数比较方便，十六进制用&lt;code&gt;0x&lt;/code&gt;前缀和0-9，a-f表示，例如：&lt;code&gt;0xff00&lt;/code&gt;，&lt;code&gt;0xa5b4c3d2&lt;/code&gt;，等等。&lt;/p&gt;
&lt;h3 id="浮点数"&gt;浮点数&lt;/h3&gt;
&lt;p&gt;浮点数也就是小数，之所以称为浮点数，是因为按照科学记数法表示时，一个浮点数的小数点位置是可变的，比如，1.23x109和12.3x108是完全相等的。浮点数可以用数学写法，如&lt;code&gt;1.23&lt;/code&gt;，&lt;code&gt;3.14&lt;/code&gt;，&lt;code&gt;-9.01&lt;/code&gt;，等等。但是对于很大或很小的浮点数，就必须用科学计数法表示，把10用e替代，1.23x109就是&lt;code&gt;1.23e9&lt;/code&gt;，或者&lt;code&gt;12.3e8&lt;/code&gt;，0.000012可以写成&lt;code&gt;1.2e-5&lt;/code&gt;，等等。&lt;/p&gt;
&lt;p&gt;整数和浮点数在计算机内部存储的方式是不同的，整数运算永远是精确的（除法难道也是精确的？是的！），而浮点数运算则可能会有四舍五入的误差。&lt;/p&gt;
&lt;h3 id="字符串"&gt;字符串&lt;/h3&gt;
&lt;p&gt;字符串是以单引号&lt;code&gt;'&lt;/code&gt;或双引号&lt;code&gt;&amp;quot;&lt;/code&gt;括起来的任意文本，比如&lt;code&gt;'abc'&lt;/code&gt;，&lt;code&gt;&amp;quot;xyz&amp;quot;&lt;/code&gt;等等。请注意，&lt;code&gt;''&lt;/code&gt;或&lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;本身只是一种表示方式，不是字符串的一部分，因此，字符串&lt;code&gt;'abc'&lt;/code&gt;只有&lt;code&gt;a&lt;/code&gt;，&lt;code&gt;b&lt;/code&gt;，&lt;code&gt;c&lt;/code&gt;这3个字符。如果&lt;code&gt;'&lt;/code&gt;本身也是一个字符，那就可以用&lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;括起来，比如&lt;code&gt;&amp;quot;I'm OK&amp;quot;&lt;/code&gt;包含的字符是&lt;code&gt;I&lt;/code&gt;，&lt;code&gt;'&lt;/code&gt;，&lt;code&gt;m&lt;/code&gt;，空格，&lt;code&gt;O&lt;/code&gt;，&lt;code&gt;K&lt;/code&gt;这6个字符。&lt;/p&gt;
&lt;p&gt;如果字符串内部既包含&lt;code&gt;'&lt;/code&gt;又包含&lt;code&gt;&amp;quot;&lt;/code&gt;怎么办？可以用转义字符&lt;code&gt;\&lt;/code&gt;来标识，比如：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;#39;I\&amp;#39;m \&amp;#34;OK\&amp;#34;!&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;表示的字符串内容是：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;I&amp;#39;m &amp;#34;OK&amp;#34;!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;转义字符&lt;code&gt;\&lt;/code&gt;可以转义很多字符，比如&lt;code&gt;\n&lt;/code&gt;表示换行，&lt;code&gt;\t&lt;/code&gt;表示制表符，字符&lt;code&gt;\&lt;/code&gt;本身也要转义，所以&lt;code&gt;\\&lt;/code&gt;表示的字符就是&lt;code&gt;\&lt;/code&gt;，可以在Python的交互式命令行用&lt;code&gt;print()&lt;/code&gt;打印字符串看看：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print(&amp;#39;I\&amp;#39;m ok.&amp;#39;)
I&amp;#39;m ok.
&amp;gt;&amp;gt;&amp;gt; print(&amp;#39;I\&amp;#39;m learning\nPython.&amp;#39;)
I&amp;#39;m learning
Python.
&amp;gt;&amp;gt;&amp;gt; print(&amp;#39;\\\n\\&amp;#39;)
\
\
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果字符串里面有很多字符都需要转义，就需要加很多&lt;code&gt;\&lt;/code&gt;，为了简化，Python还允许用&lt;code&gt;r''&lt;/code&gt;表示&lt;code&gt;''&lt;/code&gt;内部的字符串默认不转义，可以自己试试：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print(&amp;#39;\\\t\\&amp;#39;)
\ \
&amp;gt;&amp;gt;&amp;gt; print(r&amp;#39;\\\t\\&amp;#39;)
\\\t\\
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果字符串内部有很多换行，用&lt;code&gt;\n&lt;/code&gt;写在一行里不好阅读，为了简化，Python允许用&lt;code&gt;'''...'''&lt;/code&gt;的格式表示多行内容，可以自己试试：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print(&amp;#39;&amp;#39;&amp;#39;line1
... line2
... line3&amp;#39;&amp;#39;&amp;#39;)
line1
line2
line3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上面是在交互式命令行内输入，注意在输入多行内容时，提示符由&lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;变为&lt;code&gt;...&lt;/code&gt;，提示你可以接着上一行输入，注意&lt;code&gt;...&lt;/code&gt;是提示符，不是代码的一部分：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────┐
│Command Prompt - python _ □ x │
├────────────────────────────────────────────────────────┤
│&amp;gt;&amp;gt;&amp;gt; print(&amp;#39;&amp;#39;&amp;#39;line1 │
│... line2 │
│... line3&amp;#39;&amp;#39;&amp;#39;) │
│line1 │
│line2 │
│line3 │
│ │
│&amp;gt;&amp;gt;&amp;gt; _
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当输入完结束符```` &lt;code&gt;和括号&lt;/code&gt;)`后，执行该语句并打印结果。&lt;/p&gt;</description></item><item><title>Python基础</title><link>/2016/02/24/python%E5%9F%BA%E7%A1%80/</link><pubDate>Wed, 24 Feb 2016 00:00:00 +0000</pubDate><guid>/2016/02/24/python%E5%9F%BA%E7%A1%80/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="python简介"&gt;Python简介&lt;/h1&gt;
&lt;p&gt;Python由荷兰数学和计算机科学研究学会的Guido van Rossum 于1990 年代初设计，作为一门叫做ABC语言的替代品。Python提供了高效的高级数据结构，还能简单有效地面向对象编程。Python语法和动态类型，以及解释型语言的本质，使它成为多数平台上写脚本和快速开发应用的编程语言，随着版本的不断更新和语言新功能的添加，逐渐被用于独立的、大型项目的开发。
Python解释器易于扩展，可以使用C或C++（或者其他可以通过C调用的语言）扩展新的功能和数据类型。Python 也可用于可定制化软件中的扩展程序语言。Python丰富的标准库，提供了适用于各个主要系统平台的源码或机器码。&lt;/p&gt;
&lt;p&gt;Python的语法比较简单，采用缩进方式，写出来的代码就像下面的样子：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# print absolute value of an integer:
a = 100
if a &amp;gt;= 0:
print(a)
else:
print(-a)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;以&lt;code&gt;#&lt;/code&gt;开头的语句是注释，注释是给人看的，可以是任意内容，解释器会忽略掉注释。其他每一行都是一个语句，当语句以冒号&lt;code&gt;:&lt;/code&gt;结尾时，缩进的语句视为代码块。&lt;/p&gt;
&lt;p&gt;缩进有利有弊。好处是强迫你写出格式化的代码，但没有规定缩进是几个空格还是Tab。按照约定俗成的管理，应该始终坚持使用4个空格的缩进。&lt;/p&gt;
&lt;p&gt;缩进的另一个好处是强迫你写出缩进较少的代码，你会倾向于把一段很长的代码拆分成若干函数，从而得到缩进较少的代码。&lt;/p&gt;
&lt;p&gt;缩进的坏处就是“复制－粘贴”功能失效了，这是最坑爹的地方。当你重构代码时，粘贴过去的代码必须重新检查缩进是否正确。此外，IDE很难像格式化Java代码那样格式化Python代码。&lt;/p&gt;
&lt;p&gt;最后，请务必注意，Python程序是大小写敏感的，如果写错了大小写，程序会报错。&lt;/p&gt;</description></item><item><title>Git 使用指南</title><link>/2016/02/13/git-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/</link><pubDate>Sat, 13 Feb 2016 00:00:00 +0000</pubDate><guid>/2016/02/13/git-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="git-使用指南"&gt;Git 使用指南&lt;/h1&gt;
&lt;p&gt;&lt;code&gt; git命令&lt;/code&gt; &lt;code&gt; 版本控制&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="git-简介"&gt;Git 简介&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Git是目前世界上最先进的分布式版本控制系统。&lt;/p&gt;
&lt;p&gt;如果你是小白，可以先看&lt;a href="http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000"&gt;廖雪峰的官方教程&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="git操作流程图" loading="lazy" src="/2016/02/13/git-%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/git%E6%93%8D%E4%BD%9C%E6%B5%81%E7%A8%8B%E5%9B%BE.PNG"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;专用名词&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Workspace: 工作区&lt;/li&gt;
&lt;li&gt;Index / Stage: 暂存区&lt;/li&gt;
&lt;li&gt;Repository: 仓库区 ( 本地版本库 )&lt;/li&gt;
&lt;li&gt;Remote: 远程仓库 ( 远程仓库 / 远程版本库 )&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="代码库"&gt;代码库&lt;/h2&gt;
&lt;p&gt;在当前目录新建一个Git代码库&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;新建一个目录，将其初始化为Git代码库&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git init [project-name]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;克隆代码库&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git clone [url]
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="git-配置"&gt;Git 配置&lt;/h2&gt;
&lt;p&gt;Git的配置文件为 &lt;code&gt;.git/config&lt;/code&gt; ，它可以在用户主目录下（全局配置），也可以在项目目录下（项目配置）。&lt;/p&gt;
&lt;p&gt;显示当前Git配置&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git config --list
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;编辑Git配置文件&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git config -e [--global]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;设置代码时的用户信息 &lt;strong&gt;建议设置此项，后面的操作会很方便&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git config [--global] user.name &amp;#34;[name]&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;git config [--global] user.email &amp;#34;[email address]&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="增加删除文件"&gt;增加/删除文件&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;操作之前先执行 &lt;code&gt;git status &lt;/code&gt; 查看一下状态&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>openLDAP安装说明</title><link>/2016/01/05/openldap%E5%AE%89%E8%A3%85%E8%AF%B4%E6%98%8E/</link><pubDate>Tue, 05 Jan 2016 00:00:00 +0000</pubDate><guid>/2016/01/05/openldap%E5%AE%89%E8%A3%85%E8%AF%B4%E6%98%8E/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="安装-openldap-准备"&gt;安装 openLDAP 准备&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;完成各个相互通信的节点的域名解析（DNS）&lt;/li&gt;
&lt;li&gt;完成 NFS 将master节点 home目录挂载到其他节点同一路径下&lt;/li&gt;
&lt;li&gt;服务器IP、域名、主机名：172.16.10.10 maste.example.com master&lt;/li&gt;
&lt;li&gt;关闭防火墙&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="搭建-ldap-服务器"&gt;搭建 LDAP 服务器&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;yum 下载安装&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ yum install openldap* migrationtools -y
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;设置 LDAP 管理密码&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ slappasswd
New password:
Re-enter new password:
{SSHA}b28RwTpbqZ5/kxro785tKExdK4uyOX7T ///此密码自行妥善保存
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;进入配置目录&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ cd /etc/openldap/slapd.d/cn=config
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;编辑文件&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ vim olcDatabase={2}hdb.ldif
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;替换dc=my-domain,dc=com
olcSuffix: dc=sg-ai,dc=com
olcRootDN: cn=Manager,dc=sg-ai,dc=com
在文档底部加上：
olcRootPW: {SSHA}b28RwTpbqZ5/kxro785tKExdK4uyOX7T
olcTLSCertificateFile: /etc/pki/tls/certs/sg-ai.pem
olcTLSCertificateKeyFile: /etc/pki/tls/certs/sg-aikey.pem
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;$ vim olcDatabase={1}monitor.ldif
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;同样替换dc=my-domain,dc=com
olcAccess: {0}to * by dn.base=&amp;ldquo;gidNumber=0+uidNumber=0,cn=peercred,cn=external, cn=auth&amp;rdquo; read by dn.base=&amp;ldquo;cn=Manager,dc=linuxhelp,dc=com&amp;rdquo; read by * none&lt;/p&gt;</description></item><item><title>MongoDB</title><link>/2016/01/01/mongodb/</link><pubDate>Fri, 01 Jan 2016 00:00:00 +0000</pubDate><guid>/2016/01/01/mongodb/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。&lt;/p&gt;
&lt;p&gt;MongoDB 是一个介于关系数据库和非关系数据库之间的产品，是非关系数据库当中功能最丰富，最像关系数据库的。&lt;/p&gt;
&lt;h1 id="install"&gt;install&lt;/h1&gt;
&lt;p&gt;下载完安装包，并解压 &lt;strong&gt;tgz&lt;/strong&gt;。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6.tgz # 下载
tar -zxvf mongodb-linux-x86_64-3.0.6.tgz # 解压
mv mongodb-linux-x86_64-3.0.6/ /usr/local/mongodb # 将解压包拷贝到指定目录
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MongoDB 的可执行文件位于 bin 目录下，所以可以将其添加到 &lt;strong&gt;PATH&lt;/strong&gt; 路径中：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;export PATH=/usr/local/mongodb/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="docker"&gt;docker&lt;/h1&gt;
&lt;p&gt;这年头有了docker之后，运行MongoDB简直不能太简单了，非常适合开发使用。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker pull mongodb:3.4
docker run --name mongodb-dev -p 27017:27017 -v /srv/monggo/mongodb:/data/db -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=password -d mongo:3.4
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="mongodb-shell"&gt;mongodb shell&lt;/h1&gt;
&lt;p&gt;MongoDB Shell是MongoDB自带的交互式Javascript shell,用来对MongoDB进行操作和管理的交互式环境，在MongoDB安装目录下的bin目录，执行mongo命令文件。&lt;/p&gt;
&lt;p&gt;当你进入mongoDB后台后，它默认会链接到 test 文档（数据库）：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker exec -it mongodb-dev bash
mongo
use admin
db.auth(&amp;#39;admin&amp;#39;,&amp;#39;password&amp;#39;)
exit
exit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;下面是一些常见命令：&lt;/p&gt;</description></item><item><title>Markdown 语法(Typora)</title><link>/2015/12/12/markdown-%E8%AF%AD%E6%B3%95typora/</link><pubDate>Sat, 12 Dec 2015 00:00:00 +0000</pubDate><guid>/2015/12/12/markdown-%E8%AF%AD%E6%B3%95typora/</guid><description>&lt;h1 id="typora-for-markdown-语法"&gt;Typora For Markdown 语法&lt;/h1&gt;
&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h3 id="数学表达式"&gt;数学表达式&lt;/h3&gt;
&lt;p&gt;要启用这个功能，首先到&lt;code&gt;Preference&lt;/code&gt;-&amp;gt;&lt;code&gt;Editor&lt;/code&gt;中启用。然后使用&lt;code&gt;$&lt;/code&gt;符号包裹Tex命令，例如：&lt;code&gt;$lim_{x \to \infty} \ exp(-x)=0$&lt;/code&gt;将产生如下的数学表达式：&lt;/p&gt;
&lt;p&gt;$$\lim_{x \to \infty} \exp(-x)=0$$&lt;/p&gt;
&lt;h3 id="下标"&gt;下标&lt;/h3&gt;
&lt;p&gt;下标使用&lt;code&gt;~&lt;/code&gt;包裹，例如：&lt;code&gt;H~2~O&lt;/code&gt;将产生H&lt;del&gt;2&lt;/del&gt;O, 即水的分子式。&lt;/p&gt;
&lt;h3 id="上标"&gt;上标&lt;/h3&gt;
&lt;p&gt;上标使用&lt;code&gt;^&lt;/code&gt;包裹，例如：&lt;code&gt;y^2^=4&lt;/code&gt;将产生表达式 y^2^ = 4&lt;/p&gt;
&lt;h3 id="插入表情happy"&gt;插入表情:happy:&lt;/h3&gt;
&lt;p&gt;使用&lt;code&gt;:happy:&lt;/code&gt;输入表情:happy:,使用&lt;code&gt;:sad:&lt;/code&gt;输入表情:sad:,使用&lt;code&gt;:cry:&lt;/code&gt;输入表情:cry:等。以此类推！&lt;/p&gt;
&lt;h3 id="下划线"&gt;下划线&lt;/h3&gt;
&lt;p&gt;用HTML的语法&lt;code&gt;&amp;lt;u&amp;gt;Underline&amp;lt;/u&amp;gt;&lt;/code&gt;将产生下划线&lt;u&gt;Underline&lt;/u&gt;.&lt;/p&gt;
&lt;h3 id="删除线"&gt;删除线&lt;/h3&gt;
&lt;p&gt;GFM添加了删除文本的语法，这是标准的Markdown语法木有的。使用&lt;code&gt;~~&lt;/code&gt;包裹的文本将会具有删除的样式，例如&lt;code&gt;~删除文本~&lt;/code&gt;将产生&lt;del&gt;删除文本&lt;/del&gt;的样式。&lt;/p&gt;
&lt;h3 id="代码"&gt;代码&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用`包裹的内容将会以代码样式显示，例如&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;使用`printf()`
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;则会产生&lt;code&gt;printf()&lt;/code&gt;样式。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;输入&lt;code&gt;~~~&lt;/code&gt;或者```然后回车，可以输入代码块，并且可以选择代码的语言。例如：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;```java
public Class HelloWorld{
System.out.println(&amp;#34;Hello World!&amp;#34;);
}
​```
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;将会产生&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Class HelloWorld{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; System.&lt;span style="color:#a6e22e"&gt;out&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello World!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="强调"&gt;强调&lt;/h3&gt;
&lt;p&gt;使用两个*号或者两个_包裹的内容将会被强调。例如&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;**使用两个*号强调内容**
__使用两个下划线强调内容__
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;将会输出&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用两个*号强调内容&lt;/strong&gt;
&lt;strong&gt;使用两个下划线强调内容&lt;/strong&gt;
Typroa 推荐使用两个*号。&lt;/p&gt;
&lt;h3 id="斜体"&gt;斜体&lt;/h3&gt;
&lt;p&gt;在标准的Markdown语法中，*和_包裹的内容会是斜体显示，但是GFM下划线一般用来分隔人名和代码变量名，因此我们推荐是用星号来包裹斜体内容。如果要显示星号，则使用转义：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;\*
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="插入图片"&gt;插入图片&lt;/h3&gt;
&lt;p&gt;我们可以通过拖拉的方式，将本地文件夹中的图片或者网络上的图片插入。&lt;/p&gt;
&lt;p&gt;&lt;img alt="drag and drop image" loading="lazy" src="drag-img.gif"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="插入url连接"&gt;插入URL连接&lt;/h3&gt;
&lt;p&gt;使用尖括号包裹的url将产生一个连接，例如：&lt;code&gt;&amp;lt;www.baidu.com&amp;gt;&lt;/code&gt;将产生连接:&amp;lt;www.baidu.com&amp;gt;.&lt;/p&gt;
&lt;p&gt;如果是标准的url，则会自动产生连接，例如:www.google.com&lt;/p&gt;
&lt;h3 id="目录列表table-of-contentstoc"&gt;目录列表Table of Contents（TOC）&lt;/h3&gt;
&lt;p&gt;输入[toc]然后回车，将会产生一个目录，这个目录抽取了文章的所有标题，自动更新内容。&lt;/p&gt;</description></item><item><title>初识memcached</title><link>/2015/12/12/%E5%88%9D%E8%AF%86memcached/</link><pubDate>Sat, 12 Dec 2015 00:00:00 +0000</pubDate><guid>/2015/12/12/%E5%88%9D%E8%AF%86memcached/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;memcached：
memcached is a high-performance, distributed memory object caching system, generic in nature, but intended for use in speeding up dynamic web applications by alleviating database load.
缓存服务器：
缓存：cache，无持久存储功能；
bypass缓存
k/v cache，仅支持存储流式化数据；
LiveJournal旗下的Danga Interactive研发，
特性：
k/v cache：仅可序列化数据；存储项：k/v；
智能性一半依赖于客户端（调用memcached的API开发程序），一半依赖于服务端；
分布式缓存：互不通信的分布式集群；
分布式系统请求路由方法：取模法，一致性哈希算法；
算法复杂度：O(1)
清理过期缓存项：
缓存耗尽：LRU
缓存项过期：惰性清理机制
安装配置：
由CentOS 7 base仓库直接提供：
监听的端口：
11211/tcp, 11211/udp
主程序：/usr/bin/memcached
配置文件：/etc/sysconfig/memcached
Unit File：memcached.service
协议格式：memcached协议
文本格式
二进制格式
命令：
统计类：stats, stats items, stats slabs, stats sizes
存储类：set, add, replace, append, prepend
命令格式：&amp;lt;command name&amp;gt; &amp;lt;key&amp;gt; &amp;lt;flags&amp;gt; &amp;lt;exptime&amp;gt; &amp;lt;bytes&amp;gt;
&amp;lt;cas unique&amp;gt;
检索类：get, delete, incr/decr
清空：flush_all
示例：
telnet&amp;gt; add KEY &amp;lt;flags&amp;gt; &amp;lt;expiretime&amp;gt; &amp;lt;bytes&amp;gt; \r
telnet&amp;gt; VALUE
memcached程序的常用选项：
-m &amp;lt;num&amp;gt;：Use &amp;lt;num&amp;gt; MB memory max to use for object storage; the default is 64 megabytes.
-c &amp;lt;num&amp;gt;：Use &amp;lt;num&amp;gt; max simultaneous connections; the default is 1024.
-u &amp;lt;username&amp;gt;：以指定的用户身份来运行进程；
-l &amp;lt;ip_addr&amp;gt;：监听的IP地址，默认为本机所有地址；
-l &amp;lt;ip_addr&amp;gt;：监听的IP地址，默认为本机所有地址；
-p &amp;lt;num&amp;gt;：监听的TCP端口， the default is port 11211.
-U &amp;lt;num&amp;gt;：Listen on UDP port &amp;lt;num&amp;gt;, the default is port 11211, 0 is off.
-M：内存耗尽时，不执行LRU清理缓存，而是拒绝存入新的缓存项，直到有多余的空间可用时为止；
-f &amp;lt;factor&amp;gt;：增长因子；默认是1.25；
-t &amp;lt;threads&amp;gt;：启动的用于响应用户请求的线程数；
memcached默认没有认证机制，可借用于SASL进行认证；
SASL：Simple Authentication Secure Layer
API:
php-pecl-memcache
php-pecl-memcached
python-memcached
libmemcached
libmemcached-devel
命令行工具：
memcached-tool SERVER:PORT COMMAND
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>MySQL</title><link>/2015/12/02/mysql/</link><pubDate>Wed, 02 Dec 2015 00:00:00 +0000</pubDate><guid>/2015/12/02/mysql/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="mysql-or-mariadb"&gt;MySQL or MariaDB&lt;/h1&gt;
&lt;p&gt;MySQL 是最流行的关系型数据库管理系统，在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System：关系数据库管理系统)应用软件之一。&lt;/p&gt;
&lt;h2 id="run"&gt;run&lt;/h2&gt;
&lt;p&gt;用docker可以快速运行一个mariadb。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker run -v /mnt/mariadb:/var/lib/mysql --name mariadb -e MYSQL_ROOT_PASSWORD=alex -p 3306:3306 -d mariadb:5.5.60
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="detail"&gt;detail&lt;/h2&gt;
&lt;p&gt;在本教程中，会让大家快速掌握 MySQL 的基本知识，并轻松使用 MySQL 数据库。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;
MariaDB or MySQL：
层次模型 --&amp;gt; 网状模型 --&amp;gt; (Codd) 关系模型
DBMS --&amp;gt; RDBMS
CRUD：Insert, Select, Update, Delete
RDBMS：
范式：第一范式、第二范式、第三范式；
表：row, column；
关系运算：
选择
投影
连接
数据库：表、索引、视图（虚表）、SQL、存储过程、存储函数、触发器、事件调度器；
DDL：CREATE，ALTER，DROP
DML：INSERT/UPDATE/DELETE/SELECT
约束：
主键约束：惟一、非空；一张表只能有一个；PRIMARY KEY;
惟一键约束：惟一，可以存在多个；UNIQUE KEY;
外键约束：参考性约束；FOREIGN KEY;
检查性约束：check；
三层模型：
物理层 --&amp;gt; SA
逻辑层 --&amp;gt; DBA
视图层 --&amp;gt; Coder
实现：
Oracle, DB2, Sybase, Infomix, SQL Server；
MySQL, MariaDB, PostgreSQL, SQLite；
MySQL：
2000, 开源，GPL；
2001, InnoDB, 支持事务，支持行级锁；MySQL 4.0
2005, MySQL 5.0，游标、存储过程、触发器、视图和事务；
MariaDB：5.5.x --&amp;gt; 10.x
特性：
插件式存储引擎
单进程多线程
安装MySQL：
OS Vendor：rpm
MySQL：
source code：cmake
binary package：
i686, x86_64;
glibc VERSION
prepackage：rpm, deb
os, arch,
服务端程序：
mysqld, mysqld_safe, mysqld_multi
客户端程序：
mysql, mysqldump, mysqlbinlog, mysqldmin, ...
非客户端类程序：
myisamchk, myisampack, ...
ini风格的配置文件，为多个程序同时提供配置；
配置文件：
读取多处的多个配置文件，而且会以指定的次序的进行；
# my_print_defaults
Default options are read from the following files in the given order:
/etc/mysql/my.cnf /etc/my.cnf ~/.my.cnf
不同的配置文件中出现同一参数且拥有不同值时，后读取将为最终生效值；
修改默认读取的配置文件（mysqld_safe命令）：
--defaults-file=file_name
于读取的默认配置文件之外再加载一个文件：
--defaults-extra-file=path
配置文件格式：ini风格的配置文件，能够为mysql的各种应用程序提供配置信息：
[mysqld]
[mysqld_safe]
[mysqld_multi]
[server]
[mysql]
[mysqldump]
[client]
...
PARAMETER = VALUE
PARAMETER：
innodb_file_per_table
innodb-file-per-table
程序文件：
服务端程序：mysqld_safe, mysqld_multi
客户端程序：mysql, mysqldump, mysqladmin
工具程序：myisampack, ...
mysql --&amp;gt; mysql protocol --&amp;gt; mysqld
mysql：交互式CLI工具；命令化运行；
mysql [options] db_name
常用选项：
--host=host_name, -h host_name：服务端地址；
--user=user_name, -u user_name：用户名；
--password[=password], -p[password]：用户密码；
--port=port_num, -P port_num：服务端端口；
--protocol={TCP|SOCKET|PIPE|MEMORY}：
本地通信：基于本地回环地址进行请求，将基于本地通信协议；
Linux：SOCKET
Windows：PIPE，MEMORY
非本地通信：使用非本地回环地址进行的请求； TCP协议；
--socket=path, -S path
--database=db_name, -D db_name：
--compress, -C：数据压缩传输
--execute=statement, -e statement：非交互模式执行SQL语句；
--vertical, -E：查询结果纵向显示；
命令：
客户端命令：于客户端执行；
服务端命令：SQL语句，需要一次性完整地发往服务端；语句必须有结束符；
? (\?) Synonym for `help&amp;#39;.
clear (\c) Clear the current input statement.
connect (\r) Reconnect to the server. Optional arguments are db and host.
delimiter (\d) Set statement delimiter.
edit (\e) Edit command with $EDITOR.
ego (\G) Send command to mysql server, display result vertically.
exit (\q) Exit mysql. Same as quit.
go (\g) Send command to mysql server.
help (\h) Display this help.
nopager (\n) Disable pager, print to stdout.
notee (\t) Don&amp;#39;t write into outfile.
pager (\P) Set PAGER [to_pager]. Print the query results via PAGER.
print (\p) Print current command.
prompt (\R) Change your mysql prompt.
quit (\q) Quit mysql.
rehash (\#) Rebuild completion hash.
source (\.) Execute an SQL script file. Takes a file name as an argument.
status (\s) Get status information from the server.
system (\!) Execute a system shell command.
tee (\T) Set outfile [to_outfile]. Append everything into given outfile.
use (\u) Use another database. Takes database name as argument.
charset (\C) Switch to another charset. Might be needed for processing binlog with multi-byte charsets.
warnings (\W) Show warnings after every statement.
nowarning (\w) Don&amp;#39;t show warnings after every statement.
mysql命令的使用帮助：
# man mysql
# mysql --help --verbose
sql脚本运行：
mysql [options] [DATABASE] &amp;lt; /PATH/FROM/SOME_SQL_SCRIPT
mysqld服务器程序：工作特性的定义方式
命令行选项
配置文件参数
服务器参数/变量：设定MySQL的运行特性；
mysql&amp;gt; SHOW [GLOBAL | SESSION] VARIABLES [like_or_where]；
状态（统计）参数/变量：保存MySQL运行过程中的统计数据或状态数据；
mysql&amp;gt; SHOW [GLOBAL | SESSION] STATUS [like_or_where];
显示单个变量设定值的方法：
mysql&amp;gt; SELECT @@[global.|session.]system_var_name
%：匹配任意长度的任意字符；
_：匹配任意单个字符；
变量/参数级别：
全局：为所有会话设定默认；
会话：跟单个会话相关；会话建立会从全局继承；
服务器变量的调整方式：
运行时修改：
global：仅对修改后新建立的会话有效；
session：仅对当前会话有效，且立即生效；
通过配置文件修改：
重启后生效；
运行时修改服务器变量值操作方法：
mysql&amp;gt; HELP SET
SET [GLOBAL | SESSION] system_var_name = expr
SET [@@global. | @@session. | @@]system_var_name = expr
安装完成后的安全初始化：
mysql_secure_installation
运行前常修改的参数：
innodb_file_per_table=ON
skip_name_resolve=ON
sql_safe_updates=ON
...
SQL接口：
SQL：ANSI SQL
SQL-86, SQL-89, SQL-92, SQL-99, SQL-03, ...
MySQL的数据类型：
字符型
数值型
日期时间型
内建类型
字符型：
CHAR(#)， BINARY(#)：定长型；CHAR不区分字符大小写，而BINARY区分；
VARCHAR(#)， VARBINARY(#)：变长型
TEXT：TINYTEXT，TEXT，MEDIUMTEXT，LONGTEXT
BLOB：TINYBLOB，BLOB，MEDIUMBLOB， LONGBLOB
Binary Large OBject
数值型：
浮点型：近似
FLOAT
DOUBLE
REAL
BIT
整型：精确
INTEGER：TINYINT，SMALLINT，MEDIUMINT，INT，BIGINT
DECIMAL
日期时间型：
日期：DATE
时间：TIME
日期j时间：DATETIME
时间戳：TIMESTAMP
年份：YEAR(2), YEAR(4)
内建：
ENUM：枚举
ENUM(&amp;#39;Sun&amp;#39;,&amp;#39;Mon&amp;#39;,&amp;#39;Tue&amp;#39;,&amp;#39;Wed&amp;#39;)
SET：集合
类型修饰符：
字符型：NOT NULL，NULL，DEFALUT ‘STRING’，CHARACET SET ‘CHARSET’，COLLATION ‘collocation&amp;#39;
整型：NOT NULL， NULL， DEFALUT value, AUTO_INCREMENT, UNSIGNED
日期时间型：NOT NULL， NULL， DEFAULT
help &amp;#39;Data Types&amp;#39;
SQL MODE：定义mysqld对约束等违反时的响应行为等设定；
常用的MODE：
TRADITIONAL
STRICT_TRANS_TABLES
STRICT_ALL_TABLES
修改方式：
mysql&amp;gt; SET GLOBAL sql_mode=&amp;#39;MODE&amp;#39;;
mysql&amp;gt; SET @@global.sql_mode=&amp;#39;MODE&amp;#39;;
SQL：DDL，DML
DDL：Data Defination Language
mysql&amp;gt; HELP Data Definition
CREATE, ALTER, DROP
DATABASE, TABLE
INDEX, VIEW, USER
FUNCTION, FUNCTION UDF, PROCEDURE, TABLESPACE, TRIGGER, SERVER
DML: Data Manipulation Language
mysql&amp;gt; HELP Data Manipulation
INSERT/REPLACE, DELETE, SELECT, UPDATE
数据库：
CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] db_name CHARACTER SET [=] charset_name COLLATE [=] collation_name
ALTER {DATABASE | SCHEMA} [db_name] CHARACTER SET [=] charset_name COLLATE [=] collation_name
DROP {DATABASE | SCHEMA} [IF EXISTS] db_name
表：
CREATE
(1) CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name
(create_definition,...)
[table_options]
[partition_options]
CREATE TABLE [IF NOT EXISTS] tble_name (col_name data_typ|INDEX|CONSTRAINT);
table_options：
ENGINE [=] engine_name
查看支持的所有存储引擎：
mysql&amp;gt; SHOW ENGINES;
查看指定表的存储引擎：
mysql&amp;gt; SHOW TABLE STATUS LIKE clause;
ROW_FORMAT [=] {DEFAULT|DYNAMIC|FIXED|COMPRESSED|REDUNDANT|COMPACT}
(2) CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name
[(create_definition,...)]
[table_options]
[partition_options]
select_statement
直接创建表，并将查询语句的结果插入到新创建的表中；
(3) CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name
{ LIKE old_tbl_name | (LIKE old_tbl_name) }
复制某存在的表的结构来创建新的空表；
DROP:
DROP [TEMPORARY] TABLE [IF EXISTS] tbl_name [, tbl_name];
ALTER：
ALTER TABLE tbl_name
[alter_specification [, alter_specification] ...]
可修改内容：
(1) table_options
(2) 添加定义：ADD
字段、字段集合、索引、约束
(3) 修改字段：
CHANGE [COLUMN] old_col_name new_col_name column_definition [FIRST|AFTER col_name]
MODIFY [COLUMN] col_name column_definition [FIRST | AFTER col_name]
(4) 删除操作：DROP
字段、索引、约束
表重命名：
RENAME [TO|AS] new_tbl_name
查看表结构定义：
DESC tbl_name；
查看表定义：
SHOW CREATE TABLE tbl_name
查看表属性信息：
SHOW TABLE STATUS [{FROM | IN} db_name] [LIKE &amp;#39;pattern&amp;#39; | WHERE expr]
索引：数据结构
创建：
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name [index_type] ON tbl_name (index_col_name,...)
查看：
SHOW {INDEX | INDEXES | KEYS} {FROM | IN} tbl_name [{FROM | IN} db_name] [WHERE expr]
删除：
DROP INDEX index_name ON tbl_name
索引类型：
聚集索引、非聚集索引：索引是否与数据存在一起；
主键索引、辅助索引
稠密索引、稀疏索引：是否索引了每一个数据项；
BTREE（B+）、HASH、R Tree、FULLTEXT
BTREE：左前缀；
EXPLAIN：分析查询语句的执行路径；
视图：VIEW
虚表：存储下来的SELECT语句；
创建：
CREATE VIEW view_name [(column_list)] AS select_statement
修改：
ALTER VIEW view_name [(column_list)] AS select_statement
删除：
DROP VIEW [IF EXISTS] view_name [, view_name] ...
DML：
INSERT/REPLACE，DELETE，UPDATE，SELECT
INSERT：
单行插入
批量插入
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name [(col_name,...)]
{VALUES | VALUE} ({expr | DEFAULT},...),(...),...
[ ON DUPLICATE KEY UPDATE
col_name=expr
[, col_name=expr] ... ]
Or:
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name
SET col_name={expr | DEFAULT}, ...
[ ON DUPLICATE KEY UPDATE
col_name=expr
[, col_name=expr] ... ]
Or:
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name [(col_name,...)]
SELECT ...
[ ON DUPLICATE KEY UPDATE
col_name=expr
[, col_name=expr] ... ]
DELETE：
DELETE FROM tbl_name [WHERE where_condition] [ORDER BY ...] [LIMIT row_count]
注意：一定要有限制条件，否则将清空整个表；
限制条件：
[WHERE where_condition]
[ORDER BY ...] [LIMIT row_count]
UPDATE：
UPDATE table_reference SET col_name1={expr1|DEFAULT} [, col_name2={expr2|DEFAULT}] ...
[WHERE where_condition]
[ORDER BY ...]
[LIMIT row_count]
注意：一定要有限制条件，否则将修改整个表中指定字段的数据；
限制条件：
[WHERE where_condition]
[ORDER BY ...] [LIMIT row_count]
注意：sql_safe_updates变量可阻止不带条件更新操作；
SELECT：
Query Cache：缓存查询的执行结果；
key：查询语句的hash值；
value：查询语句的执行结果；
SQL语句的编写方式：
SELECT name FROM tbl2;
select name from tbl2;
查询执行路径：
请求--&amp;gt;查询缓存：命中后返回；
请求--&amp;gt;查询缓存--&amp;gt;解析器--&amp;gt;预处理器--&amp;gt;优化器--&amp;gt;查询执行引擎--&amp;gt;存储引擎--&amp;gt;缓存--&amp;gt;响应
SELECT语句的执行流程：
FROM --&amp;gt; WHERE --&amp;gt; Group By --&amp;gt; Having --&amp;gt; Order BY --&amp;gt; SELECT --&amp;gt; Limit
单表查询：
SELECT
[ALL | DISTINCT | DISTINCTROW ]
[SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
select_expr [, select_expr ...]
[FROM table_references
[WHERE where_condition]
[GROUP BY {col_name | expr | position}
[ASC | DESC], ... [WITH ROLLUP]]
[HAVING where_condition]
[ORDER BY {col_name | expr | position}
[ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
用法：
SELECT col1, col2, ... FROM tble_name；
SELECT col1, col2, ... FROM tble_name WHERE clause；
SELECT col1, col2, ... FROM tble_name [WHERE clause] GROUP BY col_name [HAVING clause]；
分组的目的在于聚合计算：avg, max, min, count, sum,
DISTINCT：数据去重；
SQL_CACHE：显式指定缓存查询语句的结果；
SQL_NO_CACHE：显式指定不缓存查询语句的结果；
query_cache_type服务器变量有三个值：
ON：启用；
SQL_NO_CACHE：不缓存；默认符合缓存条件都缓存；
OFF：关闭；
DEMAND：按需缓存；
SQL_CACHE：缓存；默认不缓存；
字段可以使用别名 ：
col1 AS alias1, col2 AS alias2, ...
WHERE子句：指明过滤条件以实现“选择”功能；
过滤条件：布尔型表达式；
[WHERE where_condition]
算术操作符：+, -, *, /, %
比较操作符：=, &amp;lt;&amp;gt;, !=, &amp;lt;=&amp;gt;, &amp;gt;, &amp;gt;=, &amp;lt;, &amp;lt;=
IS NULL， IS NOT NULL
区间：BETWEEN min AND max
IN：列表；
LIKE：模糊比较，%和_；
RLIKE或REGEXP
逻辑操作符：
AND， OR， NOT， XOR
GROUP BY：根据指定的字段把查询的结果进行“分组”以用于“聚合”运算；
avg(), max(), min(), sum(), count()
HAVING：对分组聚合后的结果进行条件过滤；
ORDER BY：根据指定的字段把查询的结果进行排序；
升序：ASC
降序：DESC
LIMIT：对输出结果进行数量限制
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
多表查询：
连接操作：
交叉连接：笛卡尔乘积；
内连接：
等值连接：让表之间的字段以等值的方式建立连接；
不等值连接：
自然连接
自连接
外连接：
左外连接：
FROM tb1 LEFT JOIN tb2 ON tb1.col = tb2.col
右外连接：
FROM tb1 RIGHT JOIN tb2 ON tb1.col = tb2.col
子查询：在查询中嵌套查询；
用于WHERE子句中的子查询；
(1) 用于比较表达式中的子查询：子查询仅能返回单个值；
(2) 用于IN中的子查询：子查询可以返回一个列表值；
(3) 用于EXISTS中的子查询：
用于FROM子句中的子查询；
SELECT tb_alias.col1, ... FROM (SELECT clause) AS tb_alias WHERE clause;
联合查询：将多个查询语句的执行结果相合并；
UNION
SELECT clause UNION SELECT cluase；
练习：导入hellodb.sql生成数据库
(1) 在students表中，查询年龄大于25岁，且为男性的同学的名字和年龄；
(2) 以ClassID为分组依据，显示每组的平均年龄；
(3) 显示第2题中平均年龄大于30的分组及平均年龄；
(4) 显示以L开头的名字的同学的信息；
(5) 显示TeacherID非空的同学的相关信息；
(6) 以年龄排序后，显示年龄最大的前10位同学的信息；
(7) 查询年龄大于等于20岁，小于等于25岁的同学的信息；用三种方法；
练习：导入hellodb.sql，以下操作在students表上执行
1、以ClassID分组，显示每班的同学的人数；
2、以Gender分组，显示其年龄之和；
3、以ClassID分组，显示其平均年龄大于25的班级；
4、以Gender分组，显示各组中年龄大于25的学员的年龄之和；
练习：导入hellodb.sql，完成以下题目：
1、显示前5位同学的姓名、课程及成绩；
2、显示其成绩高于80的同学的名称及课程；
3、求前8位同学每位同学自己两门课的平均成绩，并按降序排列；
4、显示每门课程课程名称及学习了这门课的同学的个数；
思考：
1、如何显示其年龄大于平均年龄的同学的名字？
2、如何显示其学习的课程为第1、2，4或第7门课的同学的名字？
3、如何显示其成员数最少为3个的班级的同学中年龄大于同班同学平均年龄的同学？
4、统计各班级中年龄大于全校同学平均年龄的同学。
存储引擎：
表类型：表级别概念，不建议在同一个库中的表上使用不同的ENGINE；
CREATE TABLE ... ENGINE[=]STORAGE_ENGINE_NAME ...
SHOW TABLE STATUS
常见的存储引擎：
MyISAM, Aria, InnoDB, MRG_MYISAM, CSV, BLACKHOLE, MEMORY, PERFORMANCE_SCHEMA, ARCHIVE, FEDERATED
InnoDB：InnoBase
Percona-XtraDB, Supports transactions, row-level locking, and foreign keys
数据存储于“表空间(table space)&amp;#34;中：
(1) 所有InnoDB表的数据和索引存储于同一个表空间中；
表空间文件：datadir定义的目录中
文件：ibdata1, ibdata2, ...
(2) innodb_file_per_table=ON，意味着每表使用单独的表空间文件；
数据文件（数据和索引，存储于数据库目录）: tbl_name.ibd
表结构的定义：在数据库目录，tbl_name.frm
事务型存储引擎，适合对事务要求较高的场景中；但较适用于处理大量短期事务；
基于MVCC（Mutli Version Concurrency Control）支持高并发；支持四个隔离级别，默认级别为REPEATABLE-READ；间隙锁以防止幻读；
使用聚集索引（主键索引）；
支持”自适应Hash索引“；
锁粒度：行级锁；间隙锁；
总结：
数据存储：表空间；
并发：MVCC，间隙锁，行级锁；
索引：聚集索引、辅助索引；
性能：预读操作、内存数据缓冲、内存索引缓存、自适应Hash索引、插入操作缓存区；
备份：支持热备；
MyISAM：
支持全文索引（FULLTEXT index）、压缩、空间函数（GIS）；
不支持事务
锁粒度：表级锁
崩溃无法保证表安全恢复
适用场景：只读或读多写少的场景、较小的表（以保证崩溃后恢复的时间较短）；
文件：每个表有三个文件，存储于数据库目录中
tbl_name.frm：表格式定义；
tbl_name.MYD：数据文件；
tbl_name.MYI：索引文件；
特性：
加锁和并发：表级锁；
修复：手动或自动修复、但可能会丢失数据；
索引：非聚集索引；
延迟索引更新；
表压缩；
行格式：
{DEFAULT|DYNAMIC|FIXED|COMPRESSED|REDUNDANT|COMPACT}
其它的存储引擎：
CSV：将CSV文件（以逗号分隔字段的文本文件）作为MySQL表文件；
MRG_MYISAM：将多个MyISAM表合并成的虚拟表；
BLACKHOLE：类似于/dev/null，不真正存储数据；
MEMORY：内存存储引擎，支持hash索引，表级锁，常用于临时表；
FEDERATED: 用于访问其它远程MySQL服务器上表的存储引擎接口；
MariaDB额外支持很多种存储引擎：
OQGraph、SphinxSE、TokuDB、Cassandra、CONNECT、SQUENCE、...
搜索引擎：
lucene, sphinx
lucene：Solr, Elasticsearch
并发控制：
锁：Lock
锁类型 ：
读锁：共享锁，可被多个读操作共享；
写锁：排它锁，独占锁；
锁粒度：
表锁：在表级别施加锁，并发性较低；
行锁：在行级另施加锁，并发性较高；
锁策略：在锁粒度及数据安全性之间寻求一种平衡机制；
存储引擎：级别以及何时施加或释放锁由存储引擎自行决定；
MySQL Server：表级别，可自行决定，也允许显式请求；
锁类别：
显式锁：用户手动请求的锁；
隐式锁：存储引擎自行根据需要施加的锁；
显式锁的使用：
(1) LOCK TABLES
LOCK TABLES tbl_name read|write, tbl_name read|write, ...
UNLOCK TABLES
(2) FLUSH TABLES
FLUSH TABLES tbl_name,... [WITH READ LOCK];
UNLOCK TABLES;
(3) SELECT cluase
[FOR UPDATE | LOCK IN SHARE MODE]
回顾：
连接查询：内连接、外连接；
存储引擎：InnoDB, MyISAM
InnoDB：
表空间文件：
(1) 所有表空间使用同一个文件；
(2) 每表使用单独的表空间；
事务日志：
事务日志组：2+ files
事务日志组镜像：1
MyISAM：
表级锁
tbl.frm, tbl.MYD, tbl.MYI
全文索引、空间索引
锁：
LOCK TABLES
FLUSH TABLES WITH READ LOCK
UNLOCK TABLES
MySQL（2）
事务：
事务：一组原子性的SQL查询、或者是一个或多个SQL语句组成的独立工作单元；
事务日志：
innodb_log_files_in_group
innodb_log_group_home_dir
innodb_log_file_size
innodb_mirrored_log_groups
ACID测试：
A：AUTOMICITY，原子性；整个事务中的所有操作要么全部成功执行，要么全部失败后回滚；
C：CONSISTENCY，一致性；数据库总是应该从一个一致性状态转为另一个一致性状态；
I：ISOLATION，隔离性；一个事务所做出的操作在提交之前，是否能为其它事务可见；出于保证并发操作之目的，隔离有多种级别；
D：DURABILITY，持久性；事务一旦提交，其所做出的修改会永久保存；
自动提交：单语句事务
mysql&amp;gt; SELECT @@autocommit;
+------------------------+
| @@autocommit |
+------------------------+
| 1 |
+------------------------+
mysql&amp;gt; SET @@session.autocommit=0；
手动控制事务：
启动：START TRANSACTION
提交：COMMIT
回滚：ROLLBACK
事务支持savepoints：
SAVEPOINT identifier
ROLLBACK [WORK] TO [SAVEPOINT] identifier
RELEASE SAVEPOINT identifier
事务隔离级别：
READ-UNCOMMITTED：读未提交 --&amp;gt; 脏读；
READ-COMMITTED：读提交--&amp;gt; 不可重复读；
REPEATABLE-READ：可重复读 --&amp;gt; 幻读；
SERIALIZABLE：串行化；
mysql&amp;gt; SELECT @@session.tx_isolation;
+----------------------------------+
| @@session.tx_isolation |
+----------------------------------+
| REPEATABLE-READ |
+----------------------------------+
查看InnoDB存储引擎的状态信息：
SHOW ENGINE innodb STATUS;
MySQL用户和权限管理
用户账号：user@host
user：账户名称；
host：此账户可通过哪些客户端主机请求创建连接线程；
%：任意长度牟任意字符；
_：任意单个字符；
MySQL权限类别：
库级别：
表级别：
字段级别：
管理类：
程序类：
管理类：
CREATE USER
RELOAD
LOCK TABLES
REPLICATION CLIENT， REPLICATION SLAVE
SHUTDOWN
FILE
SHOW DATABASES
PROCESS
SUPER
程序类：
FUNCTION，PROCEDURE，TRIGGER
操作：CREATE，ALTER，DROP，EXECUTE
库和表级别：
CREATE，ALTER，DROP
INDEX
CREATE VIEW
SHOW VIEW
GRANT
OPTION：能够把自己获得的权限生成一个副本转赠给其它用户；
数据操作：
表：
INSERT/DELETE/UPDATE/SELECT
字段：
SELECT(col1,col2,...)
UPDATE(col1,col2,...)
INSERT(col1,col2,...)
所有权限：ALL， ALL PRIVILEGES
元数据数据库（数据字典）：mysql
授权：
db, host, user
tables_priv, column_priv, procs_priv, proxies_priv
MySQL用户管理：
&amp;#39;user&amp;#39;@&amp;#39;host&amp;#39;;
host:
IP
主机名
NETWORK
%, _
skip_name_resolve={ON|OFF}
创建用户：
CREATE USER &amp;#39;user&amp;#39;@&amp;#39;host&amp;#39; [IDENTIFIED BY [PASSWORD] &amp;#39;password&amp;#39;] [,&amp;#39;user&amp;#39;@&amp;#39;host&amp;#39; [IDENTIFIED BY [PASSWORD] &amp;#39;password&amp;#39;]...]
重命名：RENAME USER
RENAME USER old_user TO new_user[, old_user TO new_user] ...
删除用户：
DROP USER &amp;#39;user&amp;#39;@&amp;#39;host&amp;#39; [, &amp;#39;user&amp;#39;@&amp;#39;host&amp;#39;] ...
修改用户密码：
(1) SET PASSWORD [FOR &amp;#39;user&amp;#39;@&amp;#39;host&amp;#39;] = PASSWORD(&amp;#39;cleartext password&amp;#39;);
(2) UPDATE mysql.user SET Password=PASSWORD(&amp;#39;cleartext password&amp;#39;) WHERE User=&amp;#39;USERNAME&amp;#39; AND Host=&amp;#39;HOST&amp;#39;;
(3) mysqladmin -uUSERNAME -hHOST -p password &amp;#39;NEW_PASS&amp;#39;
生效：FLUSH PRIVILEGES
忘记管理员密码的解决办法：
(1) 启动mysqld进程时，使用--skip-grant-tables和--skip-networking选项；
CentOS 7：mariadb.service
CentOS 6：/etc/init.d/mysqld
(2) 通过UPDATE命令修改管理员密码；
(3) 以正常 方式启动mysqld进程；
授权：GRANT
GRANT priv_type [(column_list)] [, priv_type [(column_list)]] ...
ON [object_type] priv_level
TO user_specification [, user_specification] ...
[REQUIRE {NONE | ssl_option [[AND] ssl_option] ...}]
[WITH with_option ...]
object_type:
TABLE
| FUNCTION
| PROCEDURE
priv_level:
*
| *.*
| db_name.*
| db_name.tbl_name
| tbl_name
| db_name.routine_name
ssl_option:
SSL
| X509
| CIPHER &amp;#39;cipher&amp;#39;
| ISSUER &amp;#39;issuer&amp;#39;
| SUBJECT &amp;#39;subject&amp;#39;
with_option:
GRANT OPTION
| MAX_QUERIES_PER_HOUR count
| MAX_UPDATES_PER_HOUR count
| MAX_CONNECTIONS_PER_HOUR count
| MAX_USER_CONNECTIONS count
查看授权：SHOW GRANTS
SHOW GRANTS [FOR &amp;#39;user&amp;#39;@&amp;#39;host&amp;#39;]
取消授权：REVOKE
REVOKE priv_type [(column_list)][, priv_type [(column_list)]] ...
ON [object_type] priv_level
FROM &amp;#39;user&amp;#39;@&amp;#39;host&amp;#39; [, &amp;#39;user&amp;#39;@&amp;#39;host&amp;#39;] ...
REVOKE ALL PRIVILEGES, GRANT OPTION
FROM user [, user] ...
练习：授权一个仅能够通过ssl连接的用户账号，而后完成连接测试；
查询缓存：
缓存：k/v
key：查询语句的hash值
value：查询语句的执行结果
如何判断缓存是否命中：
通过查询语句的哈希值判断：哈希值考虑的因素包括
查询本身、要查询数据库、客户端使用的协议版本、...
哪些查询可能不会被缓存？
查询语句中包含UDF
存储函数
用户自定义变量
临时表
mysql系统表或者是包含列级别权限的查询
有着不确定结果值的函数(now())；
查询缓存相关的服务器变量：
query_cache_limit：能够缓存的最大查询结果；（单语句结果集大小上限）
有着较大结果集的语句，显式使用SQL_NO_CACHE，以避免先缓存再移出；
query_cache_min_res_unit：内存块的最小分配单位；缓存过小的查询结果集会浪费内存空间；
较小的值会减少空间浪费，但会导致更频繁地内存分配及回收操作；
较大值的会带来空间浪费；
query_cache_size：查询缓存空间的总共可用的大小；单位是字节，必须是1024的整数倍；
query_cache_strip_comments
query_cache_type：缓存功能启用与否；
ON：启用；
OFF：禁用；
DEMAND：按需缓存，仅缓存SELECT语句中带SQL_CACHE的查询结果；
query_cache_wlock_invalidate：如果某表被其它连接锁定，是否仍然可以从查询缓存中返回查询结果；默认为OFF，表示可以；ON则表示不可以；
状态变量：
mysql&amp;gt; SHOW GLOBAL STATUS LIKE &amp;#39;Qcache%&amp;#39;;
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 1 |
| Qcache_free_memory | 16759688 |
| Qcache_hits | 0 |
| Qcache_inserts | 0 |
| Qcache_lowmem_prunes | 0 |
| Qcache_not_cached | 0 |
| Qcache_queries_in_cache | 0 |
| Qcache_total_blocks | 1 |
+-------------------------+----------+
命中率：
Qcache_hits/Com_select
回顾：事务、权限和用户
事务：ACID测试、事务隔离级别
用户：user@host
权限：
管理类、程序类、数据类；
GRANT，REVOKE
MySQL(4)
MySQL的索引：
索引：提取索引的创建在的表上字段中的数据，构建出一个独特的数据结构；
索引的作用：加速查询操作；
表中数据子集：把表中某个或某些字段的数据提取出来另存为一个特定数据结构组织的数据；
某个字段或某些字段：WHERE子句中用到的字段；
索引类型：B+ TREE，HASH
B+ TREE：顺序存储，每一个叶子结点到根结点的距离相同；左前缀索引，适合于范围类型的数据查询；
适用于B+ TREE索引的查询类型：全键值、键值范围或键前缀；
全值匹配：精确匹配某个值；
WHERE CLOUMN = &amp;#39;value&amp;#39;;
匹配最左前缀：只精确起头的部分；
WEHRE COLUMN LIKE &amp;#39;PREFIX%&amp;#39;;
匹配范围值：
精确匹配某一列，范围匹配另一列；
只用访问索引的查询：覆盖索引；
index(Name)
SELECT Name FROM students WHERE Name LIKE &amp;#39;L%&amp;#39;;
不适用B+ TREE索引：
如查条件不是从最左侧列开始，索引无效；
index(age,name), WHERE name=&amp;#39;Jerry&amp;#39;;, WHERE age&amp;gt;30;
不能跳过索引中的某列；
index(name,age,gender)
WHERE second_name=&amp;#39;black&amp;#39; and age &amp;gt; 30;
如果查询中的某个列是为范围查询，那么其右侧的列都无法再使用索引优化查询；
Hash索引：基于哈希表实现，特别适用于值的精确匹配查询；
适用场景：
只支持等值比较查询，例如=, IN(), &amp;lt;=&amp;gt;
不用场景：
所有非精确值查询；MySQL仅对memory存储引擎支持显式的hash索引；
索引优点：
降低需要扫描的数据量，减少了IO次数；
可以帮助避免排序操作，避免使用临时表；
帮助将随机IO转为顺序IO；
高性能索引策略：
(1) 在WHERE中独立使用列，尽量避免其参与运算；
WHERE age &amp;gt; 30 ;
(2) 左前缀索引：索引构建于字段的最左侧的多少个字符，要通过索引选择性来评估
索引选择性：不重复的索引值和数据表的记录总数的比值；
(3) 多列索引：
AND连接的多个查询条件更适合使用多列索引，而非多个单键索引；
(4) 选择合适的索引列次序：选择性最高的放左侧；
EXPLAIN来分析索引有效性：
EXPLAIN [explain_type] SELECT select_options
explain_type:
EXTENDED
| PARTITIONS
输出结果：
id: 1
select_type: SIMPLE
table: students
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: const
rows: 1
Extra:
id：当前查询语句中，第个SELECT语句的编号；
复杂的查询的类型主要三种：
简单子查询
用于FROM中的子查询
联合查询
注意：联合查询的分析结果会出现一个额外的匿名临时表；
select_type：查询类型：
简单查询：SIMPLE
复杂查询：
简单子查询：SUBQUERY
用于FROM中的子查询：DERIVED
联合查询中的第一个查询：PRIMARY
联合查询中的第一个查询之后的其它查询：UNION
联合查询生成的临时表：UNION RESULT
table：查询针对的表；
type：关联类型，或称为访问类型，即MySQL如何去查询表中的行
ALL：全表扫描；
index：根据索引的顺序进行的全表扫描；但同时如果Extra列出现了&amp;#34;Using index”表示使用了覆盖索引；
range：有范围限制地根据索引实现范围扫描；扫描位置始于索引中的某一项，结束于另一项；
ref：根据索引返回的表中匹配到某单个值的所有行（匹配给定值的行不止一个）；
eq_ref：根据索引返回的表中匹配到某单个值的单一行，仅返回一个行，但需要与某个额外的参考值比较，而不是常数；
const，system：与某个常数比较，且只返回一行；
possiable_keys：查询中可能会用到的索引；
key：查询中使用的索引；
key_len：查询中用到的索引长度；
ref：在利用key字段所显示的索引完成查询操作时所引用的列或常量值；
rows：MySQL估计出的为找到所有的目标项而需要读取的行数；
Extra：额外信息
Using index：使用了覆盖索引进行的查询；
Using where：拿到数据后还要再次进行过滤；
Using temporary：使用了临时表以完成查询；
Using filesort：对结果使用了一个外部索引排序；
日志：
查询日志：general_log
慢查询日志：log_slow_queries
错误日志：log_error， log_warnings
二进制日志：binlog
中继日志：relay_log
事务日志：innodb_log
1、查询日志
记录查询语句，日志存储位置：
文件：file
表：table (mysql.general_log)
general_log={ON|OFF}
general_log_file=HOSTNAME.log
log_output={FILE|TABLE|NONE}
2、慢查询日志
慢查询：运行时间超出指定时长的查询；
long_query_time
存储位置：
文件：FILE
表：TABLE，mysql.slog_log
log_slow_queries={ON|OFF}
slow_query_log={ON|OFF}
slow_query_log_file=
log_output={FILE|TABLE|NONE}
log_slow_filter=admin,filesort,filesort_on_disk,full_join,full_scan,query_cache,query_cache_miss,tmp_table,tmp_table_on_disk
log_slow_rate_limit
log_slow_verbosity
3、错误日志
记录信息：
(1) mysqld启动和关闭过程 输出的信息；
(2) mysqld运行中产生的错误信息；
(3) event scheduler运行时产生的信息；
(4) 主从复制架构中，从服务器复制线程启动时产生的日志；
log_error=
log_warnings={ON|OFF}
4、二进制日志
用于记录引起数据改变或存在引起数据改变的潜在可能性的语句（STATEMENT）或改变后的结果（ROW），也可能是二者混合；
功用：“重放”
binlog_format={STATEMENT|ROW|MIXED}
STATEMENT：语句；
ROW：行；
MIXED：混编；
查看二进制日志文件列表：
SHOW MASTER|BINARY LOGS;
查看当前正在使用的二进制日志文件：
SHOW MASTER STATUS；
查看二进制 日志文件中的事件：
SHOW BINLOG EVENTS [IN &amp;#39;log_name&amp;#39;] [FROM pos] [LIMIT [offset,] row_count]
服务器变量：
log_bin=/PATH/TO/BIN_LOG_FILE
sql_log_bin={ON|OFF}
max_binlog_size=1073741824
sync_binlog={1|0}
mysqlbinlog：
YYYY-MM-DD hh:mm:ss
--start-datetime=
--stop-datetime=
-j, --start-position=#
--stop-position=#
--user, --host, --password
二进制日志事件格式：
# at 553
#160831 9:56:08 server id 1 end_log_pos 624 Query thread_id=2 exec_time=0 error_code=0
SET TIMESTAMP=1472608568/*!*/;
BEGIN
/*!*/;
事件发生的日期时间：#160831 9:56:08
事件发生的服务器id：server id 1
事件的结束位置：end_log_pos 624
事件的类型：Query
事件发生时所在服务器执行此事件的线程的ID： thread_id=2
语句的时间戳与将其写入二进制日志文件中的时间差：exec_time=0
错误代码：error_code=0
事件内容：SET TIMESTAMP=1472608568/*!*/;
中继日志：
从服务器上记录下来从主服务器的二进制日志文件同步过来的事件；
事务日志：
事务型存储引擎innodb用于保证事务特性的日志文件：
redo log
undo log
备份和恢复（数据）：
备份：存储的数据副本；
原始数据：持续改变；
恢复：把副本应用到线上系统；
仅能恢复至备份操作时刻的数据状态；
时间点恢复：
binary logs;
为什么备份？
灾难恢复：硬件故障（冗余）、软件故障（bug）、自然灾害、黑客攻击、误操作、...
测试；
备份时应该注意事项：
能容忍最多丢失多少数据；
恢复数据需要在多长时间内完成；
需要恢复哪些数据；
做恢复演练：
测试备份的可用性；
增强恢复操作效率；
...
备份类型：
备份的数据的集范围：
完全备份和部分备份
完全备份：整个数据集；
部分备份：数据集的一部分，比如部分表；
完全备份、增量备份、差异备份：
完全备份
增量备份：仅备份自上一次完全备份或 增量备份以来变量的那部数据；
差异备份：仅备份自上一次完全备份以来变量的那部数据；
物理备份、逻辑备份：
物理备份：复制数据文件进行备份；
逻辑备份：从数据库导出数据另存在一个或多个文件中；
根据数据服务是否在线：
热备：读写操作均可进行的状态下所做的备份；
温备：可读但不可写状态下进行的备份；
冷备：读写操作均不可进行的状态下所做的备份；
备份需要考虑因素：
锁定资源多长时间？
备份过程的时长？
备份时的服务器负载？
恢复过程的时长？
备份策略：
完全+差异
完全+增量
备份手段：物理、逻辑
备份什么？
数据
二进制日志、InnoDB的事务日志；
代码（存储过程、存储函数、触发器、事件调度器）
服务器的配置文件
备份工具：
mysqldump：mysql服务自带的备份工具；逻辑备份工具；
完全、部分备份；
InnoDB：热备；
MyISAM：温备；
cp/tar
lvm2：快照（请求一个全局锁），之后立即释放锁，达到几乎热备的效果；物理备份；
注意：不能仅备份数据文件；要同时备份事务日志；
前提：要求数据文件和事务日志位于同一个逻辑卷；
xtrabackup：
由Percona提供，开源工具，支持对InnoDB做热备，物理备份工具；
完全备份、部分备份；
完全备份、增量备份；
完全备份、差异备份；
mysqlhotcopy
select：
备份：SELECT cluase INTO OUTFILE &amp;#39;FILENAME&amp;#39;;
恢复：CREATE TABLE
导入：LOAD DATA
回顾：索引、日志、备份
索引：B+ TREE，有序数据；范围查询，左前缀；
EXPLAIN SELECT clause;
日志：二进制日志、慢查询日志、查询日志、错误日志、事务日志、中继日志；
备份：
完全/部分，热备/温备/冷备，完全/差异/增量，物理/逻辑
MySQL（05）
备份策略：
完全+差异+binlog
完全+增量+binlog
备份，多久一次？
数据变化量；
可用的备份存储空间；
mysqldump：
逻辑备份、完全备份、部分备份；
二次封装工具：
mydumper
phpMyAdmin
Usage:
mysqldump [OPTIONS] database [tables]
OR mysqldump [OPTIONS] --databases [OPTIONS] DB1 [DB2 DB3...]
OR mysqldump [OPTIONS] --all-databases [OPTIONS]
MyISAM存储引擎：支持温备，备份时要锁定表；
-x, --lock-all-tables：锁定所有库的所有表，读锁；
-l, --lock-tables：锁定指定库所有表；
InnoDB存储引擎：支持温备和热备；
--single-transaction：创建一个事务，基于此快照执行备份；
其它选项：
-R, --routines：存储过程和存储函数；
--triggers
-E, --events
--master-data[=#]
1：记录为CHANGE MASTER TO语句，此语句不被注释；
2：记录为CHANGE MASTER TO语句，此语句被注释；
--flush-logs：锁定表完成后，即进行日志刷新操作；
作业：备份脚本
基于lvm2的备份：
前提：数据目录位于逻辑卷，包含了数据文件和事务日志；
(1) 请求锁定所有表；
mysql&amp;gt; FLUSH TABLES WITH READ LOCK;
(2) 记录二进制文件事件位置；
mysql&amp;gt; FLUSH LOGS;
mysql&amp;gt; SHOW MASTER STATUS;
mysql -e &amp;#39;SHOW MASTER STATUS;&amp;#39; &amp;gt;&amp;gt; /PATH/TO/SOME_POS_FILE
(3) 创建快照卷
lvcreate -L # -s -p r - SNAM-NAME /dev/VG-NAME/LV-NAME
(4) 释放锁
mysql&amp;gt; UNLOCK TABLES
(5) 挂载快照卷，并执行备份，备份完成后删除快照卷；
(6) 周期性备份二进制日志；
percona：
InnoDB --&amp;gt; XtraDB (mariadb)
Innobackup --&amp;gt; Xtrabackup
Xtrabackup：
MyISAM：温备，不支持增量备份；
InnoDB：热备，增量；
物理备份，速率快、可靠；备份完成后自动校验备份结果集是否可用；还原速度快；
Usage: [innobackupex [--defaults-file=#] --backup | innobackupex [--defaults-file=#] --prepare] [OPTIONS]
The innobackupex tool is a Perl script that acts as a wrapper for the xtrabackup C program.
完全备份：
完全+binlog(总结)：
备份：innobackupex --user --password= --host= /PATH/TO/BACKUP_DIR
准备：innobackupex --apply-log /PATH/TO/BACKUP_DIR
恢复：innobackupex --copy-back
注意：--copy-back需要在mysqld主机本地进行，mysqld服务不能启动；
innodb_log_file_size可能要重新设定；
总结：完全+增量+binlog
备份：完全+增量+增量+...
完全+差异
准备：
innobackupex --apply-log --redo-only BASEDIR
innobackupex --apply-log --redo-only BASEDIR --incremental-dir=INCREMENTAL-DIR
恢复：
innobackupex --copy-back BASEDIR
备份单库：
--databases
注意：未尽的内容，请参考官方文档；
总结：
mysqldump+binlog
lvm2+cp/tar+binlog
xtrabackup(innodb)+binlog
博客作业：mysqldump和xtrabackup的使用；
回顾：mysqldump, xtrabackup
mysqldump：
--lock-all-tables
--lock-tables
--single-transaction
--databases, --all-databases
--routines, --trigger
xtrabackup：innobackupex
--user, --host, --password, --port, --socket
--apply-log
--redo-only
--copy-back
MySQL Replication：
Master/Slave
Master: write/read
Slaves: read
为什么？
冗余：promte（提升为主），异地灾备
人工
工具程序：MHA
负载均衡：转移一部分“读”请求；
支援安全的备份操作；
测试；
...
主/从架构：
异步复制：
半同步复制：
一主多从；
一从一主；
级联复制；
循环复制；
双主复制；
一从多主：
每个主服务器提供不同的数据库；
配置：
时间同步；
复制的开始位置：
从0开始；
从备份中恢复到从节点后启动的复制；
主从服务器mysqld程序版本不一致？
主服务器：
配置文件my.cnf
server_id=#
log_bin=log-bin
启动服务：
mysql&amp;gt; GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO &amp;#39;USERNAME&amp;#39;@&amp;#39;HOST&amp;#39; IDENTIFIED BY &amp;#39;YOUR_PASSWORD&amp;#39;;
mysql&amp;gt; FLUSH PRIVILEGES;
从服务器：
配置文件my.cnf
server_id=#
relay_log=relay-log
启动服务：
mysql&amp;gt; CHANGE MASTER TO MASTER_HOST=&amp;#39;HOST&amp;#39;,MASTER_USER=&amp;#39;USERNAME&amp;#39;,MASTER_PASSWORD=&amp;#39;YOUR_PASSWORD&amp;#39;,MASTER_LOG_FILE=&amp;#39;BINLOG&amp;#39;,MASTER_LOG_POS=#;
mysql&amp;gt; START SLAVE [IO_THREAD|SQL_THREAD];
mysql&amp;gt; SHOW SLAVE STATUS;
课外作业：基于SSL的复制的实现；
主主复制：
互为主从：两个节点各自都要开启binlog和relay log；
1、数据不一致；
2、自动增长id；
定义一个节点使用奇数id
auto_increment_offset=1
auto_increment_increment=2
另一个节点使用偶数id
auto_increment_offset=2
auto_increment_increment=2
配置：
1、server_id必须要使用不同值；
2、均启用binlog和relay log；
3、存在自动增长id的表，为了使得id不相冲突，需要定义其自动增长方式；
服务启动后执行如下两步：
4、都授权有复制权限的用户账号；
5、各把对方指定为主节点；
复制时应该注意的问题：
1、从服务设定为“只读”；
在从服务器启动read_only，但仅对非SUPER权限的用户有效；
阻止所有用户：
mysql&amp;gt; FLUSH TABLES WITH READ LOCK;
2、尽量确保复制时的事务安全
在master节点启用参数：
sync_binlog = ON
如果用到的是InnoDB存储引擎：
innodb_flush_logs_at_trx_commit=ON
innodb_support_xa=ON
3、从服务器意外中止时尽量避免自动启动复制线程
4、从节点：设置参数
sync_master_info=ON
sync_relay_log_info=ON
半同步复制
支持多种插件：/usr/lib64/mysql/plugins/
需要安装方可使用：
mysql&amp;gt; INSTALL PLUGIN plugin_name SONAME &amp;#39;shared_library_name&amp;#39;;
半同步复制：
semisync_master.so
semisync_slave.so
主节点：
INSTALL PLUGIN rpl_semi_sync_master SONAME &amp;#39;semisync_master.so&amp;#39;;
MariaDB [mydb]&amp;gt; SHOW GLOBAL VARIABLES LIKE &amp;#39;rpl_semi%&amp;#39;;
+------------------------------------+-------+
| Variable_name | Value |
+------------------------------------+-------+
| rpl_semi_sync_master_enabled | OFF |
| rpl_semi_sync_master_timeout | 10000 |
| rpl_semi_sync_master_trace_level | 32 |
| rpl_semi_sync_master_wait_no_slave | ON |
+------------------------------------+-------+
MariaDB [mydb]&amp;gt; SET GLOBAL rpl_semi_sync_master_enabled=ON;
从节点：
INSTALL PLUGIN rpl_semi_sync_slave SONAME &amp;#39;semisync_slave.so&amp;#39;;
MariaDB [mydb]&amp;gt; SHOW GLOBAL VARIABLES LIKE &amp;#39;rpl_semi%&amp;#39;;
+---------------------------------+-------+
| Variable_name | Value |
+---------------------------------+-------+
| rpl_semi_sync_slave_enabled | OFF |
| rpl_semi_sync_slave_trace_level | 32 |
+---------------------------------+-------+
MariaDB [mydb]&amp;gt; STOP SLAVE IO_THREAD;
MariaDB [mydb]&amp;gt; SHOW GLOBAL VARIABLES LIKE &amp;#39;rpl_semi%&amp;#39;;
MariaDB [mydb]&amp;gt; START SLAVE IO_THREAD;
判断方法：
主节点：
MariaDB [mydb]&amp;gt; SELECT @@global.rpl_semi_sync_master_clients；
复制过滤器：
仅复制有限一个或几个数据库相关的数据，而非所有；由复制过滤器进行；
有两种实现思路：
(1) 主服务器
主服务器仅向二进制日志中记录有关特定数据库相关的写操作；
问题：其它库的point-recovery将无从实现；
binlog_do_db=
binlog_ignore_db=
(2) 从服务器
从服务器的SQL THREAD仅重放关注的数据库或表相关的事件，并将其应用于本地；
问题：网络IO和磁盘IO；
Replicate_Do_DB=
Replicate_Ignore_DB=
Replicate_Do_Table=
Replicate_Ignore_Table=
Replicate_Wild_Do_Table=
Replicate_Wild_Ignore_Table=
作业：基于SSL复制的实现
前提：启用SSL功能；
复制的监控和维护：
(1) 清理日志：PURGE
PURGE { BINARY | MASTER } LOGS { TO &amp;#39;log_name&amp;#39; | BEFORE datetime_expr };
(2) 复制监控
MASTER:
SHOW MASTER STATUS;
SHOW BINLOG EVENTS;
SHOW BINARY LOGS;
SLAVE:
SHOW SLAVE STATUS;
判断从服务器是否落后于主服务器：
Seconds_Behind_Master: 0
(3) 如何确定主从节点数据是否一致？
通过表的CHECKSUM检查；
使用percona-tools中pt-table-checksum；
(4) 主人数据不一致时的修复方法？
重新复制；
主从复制的读写分离：
mysql-proxy --&amp;gt; atlas
amoeba
AliSQL：
mysqlrouter：
语句透明路由服务；
MySQL Router 是轻量级 MySQL 中间件，提供应用与任意 MySQL 服务器后端的透明路由。MySQL Router 可以广泛应用在各种用案例中，比如通过高效路由数据库流量提供高可用性和可伸缩的 MySQL 服务器后端。Oracle 官方出品。
作业：简单复制、双主复制及半同步复制；
master/slave：
切分：
垂直切分：切库，把一个库中的多个表分组后放置于不同的物理服务器上；
水平切分：切表，分散其行至多个不同的table partitions中；
range, list, hash
sharding(切片)：
数据库切分的框架：
cobar
gizzard
Hibernat Shards
HiveDB
...
qps: queries per second
tps: transactions per second
MHA:
manager: 10.1.0.6
master: 10.1.0.67
slave1: 10.1.0.68
slave2: 10.1.0.69
博客作业：
MHA，以及zabbix完成manager启动；
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Java简要介绍</title><link>/2015/11/25/java%E7%AE%80%E8%A6%81%E4%BB%8B%E7%BB%8D/</link><pubDate>Wed, 25 Nov 2015 00:00:00 +0000</pubDate><guid>/2015/11/25/java%E7%AE%80%E8%A6%81%E4%BB%8B%E7%BB%8D/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Java：
Sun, Green Project, Oak, James Gosling;
1995：Java 1.0, Write once, Run Anywhere;
1996：JDK（Java Development Kit），包含类库、开发工具(javac)、JVM（SUN Classic VM）
JDK 1.0, Applet, AWT
1997：JDK 1.1
1998: JDK 1.2
Sun分拆Java技术为三个方向：
J2SE：Standard Edition
J2EE：Enterprise Edition
J2ME：Mobile Edition
代表性技术：EJB，java plugin, Swing, JIT(Just In Time，即时编译)
2000：JDK 1.3
HotSpot VM
2002：JDK 1.4
2006：Sun开源了Java技术，GPL，建立一个称为OpenJDK组织；
Java 2 SE, Java 2 EE, Java 2 ME
2011：JDK 1.7
2014：JDK 1.8
2016：JDK 1.9
Java代码的运行：
*.java(source code) --&amp;gt; javac --&amp;gt; *.class(bytecode)
jvm：class loader，加载程序的类文件，及程序的类文件依赖到的其它的类文件而后运行； 整个运行表现为一个jvm进程；
threads；
java技术体系：
Java编程语言
Java Class文件格式
Java API
Java VM
class loader
执行引擎
JVM运行时区域：
方法区：线程共享； 用于存储被JVM加载的class信息、常量、静态变量、方法等；
堆：是jvm所管理的内存中占用空间最大的一部分；也是GC管理的主要区域；存储对象；
Java栈：线程私有，存储 线程自己的局部变量；
PC寄存器：线程私有的内存空间，程序的指令指针；
本地方法栈：
安装JDK
了解当前的java环境：
~]# java -version
OpenJDK：
java-VERSION-openjdk：
The OpenJDK runtime environment.
java-VERSION-openjdk-headless：
The OpenJDK runtime environment without audio and video support.
java-VERSION-openjdk-devel：
The OpenJDK development tools.
CentOS 7：
VERSION：1.6.0, 1.7.0, 1.8.0
java-1.8.0-openjdk-devel
注意：多版本并存时，可使用 alternatives命令设定默认使用的版本；
Oracle JDK：
安装相应版本的rpm包；
jdk-VERSION-OS-ARCH.rpm
例如:jdk-1.8.0_25-linux-x64.rpm
注意：安装完成后，要配置JAVA_HOME环境变量，指向java的安装路径；
OpenJDK：
JAVA_HOME=/usr
Oracle JDK:
JAVA_HOME=/usr/java/jdk_VERSION
Java 2 EE：
CGI: Common Gateway Interface
Servlet：
类库；web app；
Servlet container, Servlet Engine
JSP: Java Server Page
&amp;lt;html&amp;gt;
&amp;lt;title&amp;gt;TITLE&amp;lt;/title&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;...&amp;lt;/h1&amp;gt;
&amp;lt;%
... java code...
%&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
.jsp --&amp;gt;jasper--&amp;gt; .java --&amp;gt; javac --&amp;gt; .class --&amp;gt; jvm
注意：基于jasper将静态输出的数据转为java代码进行输出；
JSP Container：
JSP + Servlet Container
Java Web Server：JWS
ASF：JServ
Tomcat 3.x
Tomcat 4.x
Catalina
http://tomcat.apache.org/
商业实现：
WebSphere, WebLogic, Oc4j, Glassfish, Geronimo, JOnAS, JBoss, ...
开源实现：
Tomcat, Jetty, Resin, ...
Tomcat： Java 2 EE技术体系的不完整实现；
JVM常用的分析工具：
jps：用来查看运行的所有jvm进程；
jinfo：查看进程的运行环境参数，主要是jvm命令行参数；
jstat：对jvm应用程序的资源和性能进行实时监控；
jstack：查看所有线程的运行状态；
jmap：查看jvm占用物理内存的状态；
jconsole：
jvisualvm：
官方文档：https://docs.oracle.com/javase/8/docs/technotes/tools/unix/
jps：Java virutal machine Process Status tool，
jps [-q] [-mlvV] [&amp;lt;hostid&amp;gt;]
-q：静默模式；
-v：显示传递给jvm的命令行参数；
-m：输出传入main方法的参数；
-l：输出main类或jar完全限定名称；
-V：显示通过flag文件传递给jvm的参数；
[&amp;lt;hostid&amp;gt;]：主机id，默认为localhost；
jinfo：输出给定的java进程的所有配置信息；
jinfo [option] &amp;lt;pid&amp;gt;
-flags：to print VM flags
-sysprops：to print Java system properties
-flag &amp;lt;name&amp;gt;：to print the value of the named VM flag
jstack：查看指定的java进程的线程栈的相关信息；
jstack [-l] &amp;lt;pid&amp;gt;
jstack -F [-m] [-l] &amp;lt;pid&amp;gt;
-l：long listings，会显示额外的锁信息，因此，发生死锁时常用此选项；
-m：混合模式，既输出java堆栈信息，也输出C/C++堆栈信息；
-F：当使用“jstack -l PID&amp;#34;无响应，可以使用-F强制输出信息；
jstat：输出指定的java进程的统计信息
jstat -help|-options
jstat -&amp;lt;option&amp;gt; [-t] [-h&amp;lt;lines&amp;gt;] &amp;lt;vmid&amp;gt; [&amp;lt;interval&amp;gt; [&amp;lt;count&amp;gt;]]
# jstat -options
-class：class loader
-compiler：JIT
-gc：gc
-gccapacity：统计堆中各代的容量
-gccause：
-gcmetacapacity
-gcnew：新生代
-gcnewcapacity
-gcold：老年代
-gcoldcapacity
-gcutil
-printcompilation
[&amp;lt;interval&amp;gt; [&amp;lt;count&amp;gt;]]
interval：时间间隔，单位是毫秒；
count：显示的次数；
-gc：
S0C ：S0 总大小
S1C ：S1 总大小
S0U ：S0 已用大小
S1U ：S1 已用大小
EC ：Eden区总大小
EU ：Eden区已用大小
OC ：老年代总大小
OU ：老年代已用大小
MC ：Metaspace 总大小
MU ：Metaspace 已用大小
CCSC ：CompressedClassSpace总大小
CCSU ：CompressedClassSpace已用大小
YGC ：Young GC 次数
YGCT ：Young GC 消耗总时间
FGC ：FullGC 次数
FGCT ：FullGC 消耗总时间
GCT ：GC总消耗时间
参考文档：https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
jmap：Memory Map, 用于查看堆内存的使用状态；
jhat：Java Heap Analysis Tool
jmap [option] &amp;lt;pid&amp;gt;
查看堆空间的详细信息：
jmap -heap &amp;lt;pid&amp;gt;
查看堆内存中的对象的数目：
jmap -histo[:live] &amp;lt;pid&amp;gt;
live：只统计活动对象；
保存堆内存数据至文件中，而后使用jvisualvm或jhat进行查看：
jmap -dump:&amp;lt;dump-options&amp;gt; &amp;lt;pid&amp;gt;
dump-options:
live dump only live objects; if not specified, all objects in the heap are dumped.
format=b binary format
file=&amp;lt;file&amp;gt; dump heap to &amp;lt;file&amp;gt;
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>tomcat简要介绍</title><link>/2015/11/25/tomcat%E7%AE%80%E8%A6%81%E4%BB%8B%E7%BB%8D/</link><pubDate>Wed, 25 Nov 2015 00:00:00 +0000</pubDate><guid>/2015/11/25/tomcat%E7%AE%80%E8%A6%81%E4%BB%8B%E7%BB%8D/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat 实际上运行JSP 页面和Servlet.它还是一个Servlet和JSP容器.Tomcat： Java 2 EE技术体系的不完整实现&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Tomcat：
使用java语言编写：
Tomcat的核心组件：server.xml
&amp;lt;Server&amp;gt;
&amp;lt;Service&amp;gt;
&amp;lt;connector/&amp;gt;
&amp;lt;connector/&amp;gt;
...
&amp;lt;Engine&amp;gt;
&amp;lt;Host&amp;gt;
&amp;lt;Context/&amp;gt;
&amp;lt;Context/&amp;gt;
...
&amp;lt;/Host&amp;gt;
&amp;lt;Host&amp;gt;
...
&amp;lt;/Host&amp;gt;
...
&amp;lt;/Engine&amp;gt;
&amp;lt;/Service&amp;gt;
&amp;lt;/Server&amp;gt;
每一个组件都由一个Java“类”实现，这些组件大体可分为以下几个类型：
顶级组件：Server
服务类组件：Service
连接器组件connector：http, https, ajp
容器类：Engine, Host, Context
被嵌套类：valve, logger, realm, loader, manager, ...
集群类组件：listener, cluster, ...
安装Tomcat:
Base Repo：
tomcat, tomcat-lib, tomcat-admin-webapps, tomcat-webapps, tomcat-docs-webapp
Tomcat binary release：
# tar xf apache-tomcat-VERSION.tar.gz -C /usr/local/
# cd /usr/local
# ln -sv apache-tomcat-VERSION tomcat
/etc/profile.d/tomcat.sh
export CATALINA_BASE=/usr/local/tomcat
export PATH=$CATALINA_BASE/bin:$PATH
tomcat程序环境：
tomcat的目录结构
bin：脚本，及启动时用到的类；
conf：配置文件目录；
lib：库文件，Java类库，jar；
logs：日志文件目录；
temp：临时文件目录；
webapps：webapp的默认目录；
work：工作目录；
rpm包安装的程序环境：
配置文件目录：/etc/tomcat
主配置文件：server.xml
webapps存放位置：/var/lib/tomcat/webapps/
examples
manager
host-manager
docs
Unit File：tomcat.service
环境配置文件：/etc/sysconfig/tomcat
tomcat的配置文件：
server.xml：主配置文件；
web.xml：每个webapp只有“部署”后才能被访问，它的部署方式通常由web.xml进行定义，其存放位置为WEB-INF/目录中；此文件为所有的webapps提供默认配置；
context.xml：每个web都可以专用的配置文件，它通常由专用的配置文件context.xml来定义，其存放位置为WEB-INF/目录中；此文件为所有的webapps提供默认配置；
tomcat-users.xml：用户认证的账号和密码文件；
catalina.policy：当使用-security选项启动tomcat时，用于为tomcat设置安全策略；
catalina.properties：Java属性的定义文件，用于设定类加载器路径，以及一些与JVM调优相关参数；
logging.properties：日志系统相关的配置；
# catalina.sh --help
debug Start Catalina in a debugger
debug -security Debug Catalina with a security manager
jpda start Start Catalina under JPDA debugger
run Start Catalina in the current window
run -security Start in the current window with security manager
start Start Catalina in a separate window
start -security Start in a separate window with security manager
stop Stop Catalina, waiting up to 5 seconds for the process to end
stop n Stop Catalina, waiting up to n seconds for the process to end
stop -force Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running
stop n -force Stop Catalina, wait up to n seconds and then use kill -KILL if still running
configtest Run a basic syntax check on server.xml - check exit code for result
version What version of tomcat are you running?
JSP WebAPP的组织结构：
/: webapps的根目录
index.jsp：主页；
WEB-INF/：当前webapp的私有资源路径；通常用于存储当前webapp的web.xml和context.xml配置文件；
META-INF/：类似于WEB-INF/；
classes/：类文件，当前webapp所提供的类；
lib/：类文件，当前webapp所提供的类，被打包为jar格式；
webapp归档格式：
.war：webapp
.jar：EJB的类打包文件；
.rar：资源适配器类打包文件；
.ear：企业级webapp；
部署(deploy)webapp的相关操作：
deploy：将webapp的源文件放置于目标目录(网页程序文件存放目录)，配置tomcat服务器能够基于web.xml和context.xml文件中定义的路径来访问此webapp；将其特有的类和依赖的类通过class loader装载至JVM；
部署有两种方式：
自动部署：auto deploy
手动部署:
冷部署：把webapp复制到指定的位置，而后才启动tomcat；
热部署：在不停止tomcat的前提下进行部署；
部署工具：manager、ant脚本、tcd(tomcat client deployer)等；
undeploy：反部署，停止webapp，并从tomcat实例上卸载webapp；
start：启动处于停止状态的webapp；
stop：停止webapp，不再向用户提供服务；其类依然在jvm上；
redeploy：重新部署；
手动提供一测试类应用，并冷部署：
# mkidr -pv /usr/local/tomcat/webapps/test/{classes,lib,WEB-INF}
创建文件/usr/local/tomcat/webapps/test/index.jsp
&amp;lt;%@ page language=&amp;#34;java&amp;#34; %&amp;gt;
&amp;lt;%@ page import=&amp;#34;java.util.*&amp;#34; %&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Test Page&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;% out.println(&amp;#34;hello world&amp;#34;);
%&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
tomcat的两个管理应用:
manager
host-manager
tomcat的常用组件配置：
Server：代表tomcat instance，即表现出的一个java进程；监听在8005端口，只接收“SHUTDOWN”。各server监听的端口不能相同，因此，在同一物理主机启动多个实例时，需要修改其监听端口为不同的端口；
Service：用于实现将一个或多个connector组件关联至一个engine组件；
Connector组件：
负责接收请求，常见的有三类http/https/ajp；
进入tomcat的请求可分为两类：
(1) standalone : 请求来自于客户端浏览器；
(2) 由其它的web server反代：来自前端的反代服务器；
nginx --&amp;gt; http connector --&amp;gt; tomcat
httpd(proxy_http_module) --&amp;gt; http connector --&amp;gt; tomcat
httpd(proxy_ajp_module) --&amp;gt; ajp connector --&amp;gt; tomcat
属性：
port=&amp;#34;8080&amp;#34;
protocol=&amp;#34;HTTP/1.1&amp;#34;
connectionTimeout=&amp;#34;20000&amp;#34;
address：监听的IP地址；默认为本机所有可用地址；
maxThreads：最大并发连接数，默认为150；
enableLookups：是否启用DNS查询功能；
acceptCount：等待队列的最大长度；
secure：
sslProtocol：
Engine组件：Servlet实例，即servlet引擎，其内部可以一个或多个host组件来定义站点； 通常需要通过defaultHost来定义默认的虚拟主机；
属性：
name=
defaultHost=&amp;#34;localhost&amp;#34;
jvmRoute=
Host组件：位于engine内部用于接收请求并进行相应处理的主机或虚拟主机，示例：
&amp;lt;Host name=&amp;#34;localhost&amp;#34; appBase=&amp;#34;webapps&amp;#34;
unpackWARs=&amp;#34;true&amp;#34; autoDeploy=&amp;#34;true&amp;#34;&amp;gt;
&amp;lt;/Host&amp;gt;
常用属性说明：
(1) appBase：此Host的webapps的默认存放目录，指存放非归档的web应用程序的目录或归档的WAR文件目录路径；可以使用基于$CATALINA_BASE变量所定义的路径的相对路径；
(2) autoDeploy：在Tomcat处于运行状态时，将某webapp放置于appBase所定义的目录中时，是否自动将其部署至tomcat；
示例：
&amp;lt;Host name=&amp;#34;tc1.magedu.com&amp;#34; appBase=&amp;#34;/appdata/webapps&amp;#34; unpackWARs=&amp;#34;true&amp;#34; autoDeploy=&amp;#34;true&amp;#34;&amp;gt;
&amp;lt;/Host&amp;gt;
# mkdir -pv /appdata/webapps
# mkdir -pv /appdata/webapps/ROOT/{lib,classes,WEB-INF}
提供一个测试页即可；
Context组件:
示例：
&amp;lt;Context path=&amp;#34;/PATH&amp;#34; docBase=&amp;#34;/PATH/TO/SOMEDIR&amp;#34; reloadable=&amp;#34;&amp;#34;/&amp;gt;
Valve组件：
&amp;lt;Valve className=&amp;#34;org.apache.catalina.valves.AccessLogValve&amp;#34; directory=&amp;#34;logs&amp;#34;
prefix=&amp;#34;localhost_access_log&amp;#34; suffix=&amp;#34;.txt&amp;#34;
pattern=&amp;#34;%h %l %u %t &amp;amp;quot;%r&amp;amp;quot; %s %b&amp;#34; /&amp;gt;
Valve存在多种类型：
定义访问日志：org.apache.catalina.valves.AccessLogValve
定义访问控制：org.apache.catalina.valves.RemoteAddrValve
&amp;lt;Valve className=&amp;#34;org.apache.catalina.valves.RemoteAddrValve&amp;#34; deny=&amp;#34;172\.16\.100\.67&amp;#34;/&amp;gt;
LNMT：Linux Nginx MySQL Tomcat
Client (http) --&amp;gt; nginx (reverse proxy)(http) --&amp;gt; tomcat (http connector)
location / {
proxy_pass http://tc1.magedu.com:8080;
}
location ~* \.(jsp|do)$ {
proxy_pass http://tc1.magedu.com:8080;
}
LAMT：Linux Apache(httpd) MySQL Tomcat
httpd的代理模块：
proxy_module
proxy_http_module：适配http协议客户端；
proxy_ajp_module：适配ajp协议客户端；
Client (http) --&amp;gt; httpd (proxy_http_module)(http) --&amp;gt; tomcat (http connector)
Client (http) --&amp;gt; httpd (proxy_ajp_module)(ajp) --&amp;gt; tomcat (ajp connector)
Client (http) --&amp;gt; httpd (mod_jk)(ajp) --&amp;gt; tomcat (ajp connector)
proxy_http_module代理配置示例：
&amp;lt;VirtualHost *:80&amp;gt;
ServerName tc1.magedu.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
&amp;lt;Proxy *&amp;gt;
Require all granted
&amp;lt;/Proxy&amp;gt;
ProxyPass / http://tc1.magedu.com:8080/
ProxyPassReverse / http://tc1.magedu.com:8080/
&amp;lt;Location /&amp;gt;
Require all granted
&amp;lt;/Location&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
proxy_ajp_module代理配置示例：
&amp;lt;VirtualHost *:80&amp;gt;
ServerName tc1.magedu.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
&amp;lt;Proxy *&amp;gt;
Require all granted
&amp;lt;/Proxy&amp;gt;
ProxyPass / ajp://tc1.magedu.com:8009/
ProxyPassReverse / ajp://tc1.magedu.com:8009/
&amp;lt;Location /&amp;gt;
Require all granted
&amp;lt;/Location&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
课外实践：client --&amp;gt; nginx --&amp;gt; httpd --&amp;gt; tomcat
proxy_http_module)(http) --&amp;gt; tomcat (http connector)
Client (http) --&amp;gt; httpd (proxy_ajp_module)(ajp) --&amp;gt; tomcat (ajp connector)
Client (http) --&amp;gt; httpd (mod_jk)(ajp) --&amp;gt; tomcat (ajp connector)
proxy_http_module代理配置示例：
&amp;lt;VirtualHost *:80&amp;gt;
ServerName tc1.magedu.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
&amp;lt;Proxy *&amp;gt;
Require all granted
&amp;lt;/Proxy&amp;gt;
ProxyPass / http://tc1.magedu.com:8080/
ProxyPassReverse / http://tc1.magedu.com:8080/
&amp;lt;Location /&amp;gt;
Require all granted
&amp;lt;/Location&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
proxy_ajp_module代理配置示例：
&amp;lt;VirtualHost *:80&amp;gt;
ServerName tc1.magedu.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
&amp;lt;Proxy *&amp;gt;
Require all granted
&amp;lt;/Proxy&amp;gt;
ProxyPass / ajp://tc1.magedu.com:8009/
ProxyPassReverse / ajp://tc1.magedu.com:8009/
&amp;lt;Location /&amp;gt;
Require all granted
&amp;lt;/Location&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
课外实践：client --&amp;gt; nginx --&amp;gt; httpd --&amp;gt; tomcat
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>nginx</title><link>/2015/11/23/nginx/</link><pubDate>Mon, 23 Nov 2015 00:00:00 +0000</pubDate><guid>/2015/11/23/nginx/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Nginx：engine X
NGINX is a free, open-source, high-performance HTTP server and reverse proxy, as well as an IMAP/POP3 proxy server. NGINX is known for its high performance, stability, rich feature set, simple configuration, and low resource consumption.(nginx是一个自由的，开源的，高性能的http服务和反向代理。同时也可以是IMAP/POP3反向代理。以他的高性能，高稳定性，丰富的特性设置，简单的配置以及很低的资源消耗而闻名)。
功能特点如下：
http协议：web服务器（类似于httpd）、http reverse proxy（类似于httpd）、imap/pop3 reverse proxy
C10K（10K Connections）超10k的并发连接数。
nginx的衍生版本：Tengine, OpenResty.
Nginx的程序架构：
master/worker：一个master进程，多个worker进程
master：负载加载和分析配置文件、管理worker进程、平滑升级
worker：处理并响应用户请求
缓存相关的进程：
cache loader：载入缓存对象
cache manager：管理缓存对象
特性：异步、事件驱动和非阻塞
并发请求处理：通过kevent/epoll/select，/dev/poll
文件IO：高级IO sendfile，异步，mmap
nginx高度模块：高度模块化，但其模块早期不支持DSO机制；近期版本支持动态装载和卸载；
模块分类：
核心模块：core module
标准模块：
HTTP modules：
Standard HTTP modules
Optional HTTP modules
Mail modules
Stream modules
3rd party modules
nginx的功用：
静态的web资源服务器；(图片服务器，或js/css/html/txt等静态资源服务器)
结合FastCGI/uwSGI/SCGI等协议反代动态资源请求；
http/https协议的反向代理；
imap4/pop3协议的反向代理；
tcp/udp协议的请求转发；
nginx的安装配置：
官方的预制包：
http://nginx.org/packages/centos/7/x86_64/RPMS/
Fedora-EPEL:
编译安装：
# yum install pcre-devel openssl-devel zlib-devel（依赖工具包）
# useradd -r nginx
# ./configure --prefix=/usr/local/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --user=nginx --group=nginx --with-http_ssl_module --with-http_v2_module --with-http_dav_module --with-http_stub_status_module --with-threads --with-file-aio
# make &amp;amp;&amp;amp; make install
配置：
配置文件的组成部分：
主配置文件：/etc/nginx/nginx.conf
include /etc/nginx/conf.d/*.conf
fastcgi， uwsgi，scgi等协议相关的配置文件
mime.types：支持的mime类型
主配置文件的配置指令：
directive value [value2 ...];
注意：
(1) 指令必须以分号结尾；
(2) 支持使用配置变量；
内建变量：由Nginx模块引入，可直接引用；所有的内建变量可在ngx_http_core_module 模块说明文档的 Embedded Variables 部分找到
自定义变量：由用户使用set命令定义；
set variable_name value;
引用变量：$variable_name
主配置文件结构：
main block：主配置段，也即全局配置段；
event { ... }：事件驱动相关的配置；
http { ... }：http/https 协议相关的配置段；
mail { ... }：邮件工具的配置段；
stream { ... }：TCP udp 相关
http协议相关的配置结构
http {
...
...：各server的公共配置
server {
...
}：每个server用于定义一个虚拟主机；
server {
...
server_name
root
alias
location [OPERATOR] URL {
...
if CONDITION {
...
}
}
}
}
main配置段常见的配置指令：
分类：
正常运行必备的配置
优化性能相关的配置
用于调试及定位问题相关的配置
事件驱动相关的配置
正常 运行必备的配置：
1、user
Syntax: user user [group];
Default: user nobody nobody;
Context: main
Defines user and group credentials used by worker processes. If group is omitted, a group whose name equals that of user is used.
2、pid /PATH/TO/PID_FILE;
指定存储nginx主进程进程号码的文件路径；
3、include file | mask;
指明包含进来的其它配置文件片断；
4、load_module file;
指明要装载的动态模块；
性能优化相关的配置：
1、worker_processes number | auto;
worker进程的数量；通常应该为当前主机的cpu的物理核心数；
2、worker_cpu_affinity cpumask ...;
worker_cpu_affinity auto [cpumask];
CPU MASK：
00000001：0号CPU
00000010：1号CPU
... ...
3、worker_priority number;
指定worker进程的nice值，设定worker进程优先级；[-20,20]
4、worker_rlimit_nofile number;
worker进程所能够打开的文件数量上限；
调试、定位问题：
1、daemon on|off;
是否以守护进程方式运行Nignx；
2、master_process on|off;
是否以master/worker模型运行nginx；默认为on；
3、error_log file [level];
事件驱动相关的配置:
events {
...
}
1、worker_connections number;
每个worker进程所能够打开的最大并发连接数数量；
worker_processes * worker_connections
2、use method;
指明并发连接请求的处理方法；
use epoll;
3、accept_mutex on | off;
处理新的连接请求的方法；on意味着由各worker轮流处理新请求，Off意味着每个新请求的到达都会通知所有的worker进程；
Nginx配置：
http协议的相关配置：
http {
... ...
server {
...
server_name
root
location [OPERATOR] /uri/ {
...
}
}
server {
...
}
}
与套接字相关的配置：
1、server { ... }
配置一个虚拟主机；
server {
listen address[:PORT]|PORT;
server_name SERVER_NAME;
root /PATH/TO/DOCUMENT_ROOT;
}
2、listen PORT|address[:port]|unix:/PATH/TO/SOCKET_FILE
listen address[:port] [default_server] [ssl] [http2 | spdy] [backlog=number] [rcvbuf=size] [sndbuf=size]
default_server：设定为默认虚拟主机；
ssl：限制仅能够通过ssl连接提供服务；
backlog=number：后援队列长度；
rcvbuf=size：接收缓冲区大小；
sndbuf=size：发送缓冲区大小；
3、server_name name ...;
指明虚拟主机的主机名称；后可跟多个由空白字符分隔的字符串；
支持*通配任意长度的任意字符；server_name *.magedu.com www.magedu.*
支持~起始的字符做正则表达式模式匹配；server_name ~^www\d+\.magedu\.com$
匹配机制：
(1) 首先是字符串精确匹配;
(2) 左侧*通配符；
(3) 右侧*通配符；
(4) 正则表达式；
练习：定义四个虚拟主机，混合使用三种类型的虚拟主机；
仅开放给来自于本地网络中的主机访问；
4、tcp_nodelay on | off;
在keepalived模式下的连接是否启用TCP_NODELAY选项；
5、sendfile on | off;
是否启用sendfile功能；
定义路径相关的配置：
6、root path;
设置web资源路径映射；用于指明用户请求的url所对应的本地文件系统上的文档所在目录路径；可用的位置：http, server, location, if in location；
7、location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
在一个server中location配置段可存在多个，用于实现从uri到文件系统的路径映射；ngnix会根据用户请求的URI来检查定义的所有location，并找出一个最佳匹配，而后应用其配置；
=：对URI做精确匹配；例如, http://www.magedu.com/, http://www.magedu.com/index.html
location = / {
...
}
~：对URI做正则表达式模式匹配，区分字符大小写；
~*：对URI做正则表达式模式匹配，不区分字符大小写；
^~：对URI的左半部分做匹配检查，不区分字符大小写；
不带符号：匹配起始于此uri的所有的url；
匹配优先级：=, ^~, ～/～*，不带符号；
root /vhosts/www/htdocs/
http://www.magedu.com/index.html --&amp;gt; /vhosts/www/htdocs/index.html
server {
root /vhosts/www/htdocs/
location /admin/ {
root /webapps/app1/data/
}
}
8、alias path;
定义路径别名，文档映射的另一种机制；仅能用于location上下文；
注意：location中使用root指令和alias指令的意义不同；
(a) root，给定的路径对应于location中的/uri/左侧的/；
(b) alias，给定的路径对应于location中的/uri/右侧的/；
9、index file ...;
默认资源；http, server, location；
10、error_page code ... [=[response]] uri;
Defines the URI that will be shown for the specified errors.
11、try_files file ... uri;
定义客户端请求的相关配置
12、keepalive_timeout timeout [header_timeout];
设定保持连接的超时时长，0表示禁止长连接；默认为75s；
13、keepalive_requests number;
在一次长连接上所允许请求的资源的最大数量，默认为100;
14、keepalive_disable none | browser ...;
对哪种浏览器禁用长连接；
15、send_timeout time;
向客户端发送响应报文的超时时长，此处，是指两次写操作之间的间隔时长；
16、client_body_buffer_size size;
用于接收客户端请求报文的body部分的缓冲区大小；默认为16k；超出此大小时，其将被暂存到磁盘上的由client_body_temp_path指令所定义的位置；
17、client_body_temp_path path [level1 [level2 [level3]]];
设定用于存储客户端请求报文的body部分的临时存储路径及子目录结构和数量；
16进制的数字；
client_body_temp_path path /var/tmp/client_body 1 2 2
对客户端进行限制的相关配置：
18、limit_rate rate;
限制响应给客户端的传输速率，单位是bytes/second，0表示无限制；
19、limit_except method ... { ... }
限制对指定的请求方法之外的其它方法的使用客户端；
limit_except GET {
allow 192.168.1.0/24;
deny all;
}
文件操作优化的配置
20、aio on | off | threads[=pool];
是否启用aio功能；
21、directio size | off;
在Linux主机启用O_DIRECT标记，此处意味文件大于等于给定的大小时使用，例如directio 4m;
22、open_file_cache off;
open_file_cache max=N [inactive=time];
nginx可以缓存以下三种信息：
(1) 文件的描述符、文件大小和最近一次的修改时间；
(2) 打开的目录结构；
(3) 没有找到的或者没有权限访问的文件的相关信息；
max=N：可缓存的缓存项上限；达到上限后会使用LRU算法实现缓存管理；
inactive=time：缓存项的非活动时长，在此处指定的时长内未被命中的或命中的次数少于open_file_cache_min_uses指令所指定的次数的缓存项即为非活动项；
23、open_file_cache_valid time;
缓存项有效性的检查频率；默认为60s;
24、open_file_cache_min_uses number;
在open_file_cache指令的inactive参数指定的时长内，至少应该被命中多少次方可被归类为活动项；
25、open_file_cache_errors on | off;
是否缓存查找时发生错误的文件一类的信息；
ngx_http_access_module模块：
实现基于ip的访问控制功能
26、allow address | CIDR | unix: | all;
27、deny address | CIDR | unix: | all;
http, server, location, limit_except
ngx_http_auth_basic_module模块
实现基于用户的访问控制，使用basic机制进行用户认证；
28、auth_basic string | off;
29、auth_basic_user_file file;
location /admin/ {
alias /webapps/app1/data/;
auth_basic &amp;#34;Admin Area&amp;#34;;
auth_basic_user_file /etc/nginx/.ngxpasswd;
}
注意：htpasswd命令由httpd-tools所提供；
ngx_http_stub_status_module模块
用于输出nginx的基本状态信息；
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
Active connections: 活动状态的连接数；
accepts：已经接受的客户端请求的总数；
handled：已经处理完成的客户端请求的总数；
requests：客户端发来的总的请求数；
Reading：处于读取客户端请求报文首部的连接的连接数；
Writing：处于向客户端发送响应报文过程中的连接数；
Waiting：处于等待客户端发出请求的空闲连接数；
30、stub_status;
配置示例：
location /basic_status {
stub_status;
}
ngx_http_log_module模块
the ngx_http_log_module module writes request logs in the specified format.
31、log_format name string ...;
string可以使用nginx核心模块及其它模块内嵌的变量；
课外作业：为nginx定义使用类似于httpd的combined格式的访问日志；
32、access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
access_log off;
访问日志文件路径，格式及相关的缓冲的配置；
buffer=size
flush=time
33、open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
open_log_file_cache off;
缓存各日志文件相关的元数据信息；
max：缓存的最大文件描述符数量；
min_uses：在inactive指定的时长内访问大于等于此值方可被当作活动项；
inactive：非活动时长；
valid：验正缓存中各缓存项是否为活动项的时间间隔；
ngx_http_gzip_module：
The ngx_http_gzip_module module is a filter that compresses responses using the “gzip” method. This often helps to reduce the size of transmitted data by half or even more.
1、gzip on | off;
Enables or disables gzipping of responses.
2、gzip_comp_level level;
Sets a gzip compression level of a response. Acceptable values are in the range from 1 to 9.
3、 gzip_disable regex ...;
Disables gzipping of responses for requests with “User-Agent” header fields matching any of the specified regular expressions.
4、 gzip_min_length length;
启用压缩功能的响应报文大小阈值；
5、gzip_buffers number size;
支持实现压缩功能时为其配置的缓冲区数量及每个缓存区的大小；
6、gzip_proxied off | expired | no-cache | no-store | private | no_last_modified | no_etag | auth | any ...;
nginx作为代理服务器接收到从被代理服务器发送的响应报文后，在何种条件下启用压缩功能的；
off：对代理的请求不启用
no-cache, no-store，private：表示从被代理服务器收到的响应报文首部的Cache-Control的值为此三者中任何一个，则启用压缩功能；
7、gzip_types mime-type ...;
压缩过滤器，仅对此处设定的MIME类型的内容启用压缩功能；
示例：
gzip on;
gzip_comp_level 6;
gzip_min_length 64;
gzip_proxied any;
gzip_types text/xml text/css application/javascript;
ngx_http_ssl_module模块：
1、 ssl on | off;
Enables the HTTPS protocol for the given virtual server.
2、ssl_certificate file;
当前虚拟主机使用PEM格式的证书文件；
3、ssl_certificate_key file;
当前虚拟主机上与其证书匹配的私钥文件；
4、ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2];
支持ssl协议版本，默认为后三个；
5、ssl_session_cache off | none | [builtin[:size]] [shared:name:size];
builtin[:size]：使用OpenSSL内建的缓存，此缓存为每worker进程私有；
[shared:name:size]：在各worker之间使用一个共享的缓存；
6、ssl_session_timeout time;
客户端一侧的连接可以复用ssl session cache中缓存 的ssl参数的有效时长；
配置示例：
server {
listen 443 ssl;
server_name www.magedu.com;
root /vhosts/ssl/htdocs;
ssl on;
ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
ssl_session_cache shared:sslcache:20m;
}
ngx_http_rewrite_module模块：
The ngx_http_rewrite_module module is used to change request URI using PCRE regular expressions, return redirects, and conditionally select configurations.
bbs.magedu.com/ --&amp;gt; www.magedu.com/bbs/, http://www.magedu.com/ --&amp;gt; https://www.magedu.com/
http://www.magedu.com/login.php;username=tom --&amp;gt; http://www.magedu.com/tom/
将用户请求的URI基于regex所描述的模式进行检查，而后完成替换；
1、rewrite regex replacement [flag]
将用户请求的URI基于regex所描述的模式进行检查，匹配到时将其替换为replacement指定的新的URI；
注意：如果在同一级配置块中存在多个rewrite规则，那么会自下而下逐个检查；被某条件规则替换完成后，会重新一轮的替换检查，因此，隐含有循环机制；[flag]所表示的标志位用于控制此循环机制；
如果replacement是以http://或https://开头，则替换结果会直接以重向返回给客户端；
301：永久重定向；
[flag]：
last：重写完成后停止对当前URI在当前location中后续的其它重写操作，而后对新的URI启动新一轮重写检查；提前重启新一轮循环；
break：重写完成后停止对当前URI在当前location中后续的其它重写操作，而后直接跳转至重写规则配置块之后的其它配置；结束循环；
redirect：重写完成后以临时重定向方式直接返回重写后生成的新URI给客户端，由客户端重新发起请求；不能以http://或https://开头；
permanent:重写完成后以永久重定向方式直接返回重写后生成的新URI给客户端，由客户端重新发起请求；
2、return
return code [text];
return code URL;
return URL;
Stops processing and returns the specified code to a client.
3、 rewrite_log on | off;
是否开启重写日志；
4、 if (condition) { ... }
引入一个新的配置上下文 ；条件满足时，执行配置块中的配置指令；server, location；
condition：
比较操作符：
==
!=
~：模式匹配，区分字符大小写；
~*：模式匹配，不区分字符大小写；
!~：模式不匹配，区分字符大小写；
!~*：模式不匹配，不区分字符大小写；
文件及目录存在性判断：
-e, !-e
-f, !-f
-d, !-d
-x, !-x
5、set $variable value;
用户自定义变量 ；
ngx_http_referer_module模块：
The ngx_http_referer_module module is used to block access to a site for requests with invalid values in the “Referer” header field.
1、valid_referers none | blocked | server_names | string ...;
定义referer首部的合法可用值；
none：请求报文首部没有referer首部；
blocked：请求报文的referer首部没有值；
server_names：参数，其可以有值作为主机名或主机名模式；
arbitrary_string：直接字符串，但可使用*作通配符；
regular expression：被指定的正则表达式模式匹配到的字符串；要使用~打头，例如 ~.*\.magedu\.com；
配置示例：
valid_referers none block server_names *.magedu.com *.mageedu.com magedu.* mageedu.* ~\.magedu\.;
if($invalid_referer) {
return 403;
}
ngx_http_proxy_module模块：
The ngx_http_proxy_module module allows passing requests to another server.（该模块可以是实现请求转发功能）
1、proxy_pass URL;
Context: location, if in location, limit_except
注意：proxy_pass后面的路径不带uri时，其会将location的uri传递给后端主机；
server {
...
server_name HOSTNAME;
location /uri/ {
proxy http://host[:port];
}
...
}
http://HOSTNAME/uri --&amp;gt; http://host/uri
proxy_pass后面的路径是一个uri时，其会将location的uri替换为proxy_pass的uri；
server {
...
server_name HOSTNAME;
location /uri/ {
proxy http://host/new_uri/;
}
...
}
http://HOSTNAME/uri/ --&amp;gt; http://host/new_uri/
如果location定义其uri时使用了正则表达式的模式，则proxy_pass之后必须不能使用uri; 用户请求时传递的uri将直接附加代理到的服务的之后；
server {
...
server_name HOSTNAME;
location ~|~* /uri/ {
proxy http://host;
}
...
}
http://HOSTNAME/uri/ --&amp;gt; http://host/uri/；
2、proxy_set_header field value;
设定发往后端主机的请求报文的请求首部的值；Context: http, server, location
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
3、proxy_cache_path
定义可用于proxy功能的缓存；Context: http
proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time];
4、proxy_cache zone | off;
指明要调用的缓存，或关闭缓存机制；Context: http, server, location
5、 proxy_cache_key string;
缓存中用于“键”的内容；
默认值：proxy_cache_key $scheme$proxy_host$request_uri;
6、proxy_cache_valid [code ...] time;
定义对特定响应码的响应内容的缓存时长；
定义在http{...}中；
proxy_cache_path /var/cache/nginx/proxy_cache levels=1:1:1 keys_zone=pxycache:20m max_size=1g;
定义在需要调用缓存功能的配置段，例如server{...}；
proxy_cache pxycache;
proxy_cache_key $request_uri;
proxy_cache_valid 200 302 301 1h;
proxy_cache_valid any 1m;
7、proxy_cache_use_stale error | timeout | invalid_header | updating | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | off ...;
Determines in which cases a stale cached response can be used when an error occurs during communication with the proxied server.(当与代理服务器通信错误时，应该返回那种结果给客户端)
8、proxy_cache_methods GET | HEAD | POST ...;
If the client request method is listed in this directive then the response will be cached. “GET” and “HEAD” methods are always added to the list, though it is recommended to specify them explicitly. 设定允许客户端请求的方式，get和head默认都会有
9、proxy_hide_header field;
By default, nginx does not pass the header fields “Date”, “Server”, “X-Pad”, and “X-Accel-...” from the response of a proxied server to a client. The proxy_hide_header directive sets additional fields that will not be passed.设置隐藏服务器返回给客户端的报文的头部信息，
10、proxy_connect_timeout time;
Defines a timeout for establishing a connection with a proxied server. It should be noted that this timeout cannot usually exceed 75 seconds.链接超时时间默认为60s；
ngx_http_headers_module模块
The ngx_http_headers_module module allows adding the “Expires” and “Cache-Control” header fields, and arbitrary fields, to a response header.
向由代理服务器响应给客户端的响应报文添加自定义首部，或修改指定首部的值；
1、add_header name value [always];
添加自定义首部；可用上下文：http, server, location, if in location
add_header X-Via $server_addr;
add_header X-Accel $server_name;
2、expires [modified] time;
expires epoch | max | off;
用于定义Expire或Cache-Control首部的值；
ngx_http_fastcgi_module模块：
The ngx_http_fastcgi_module module allows passing requests to a FastCGI server.
1、fastcgi_pass address;
address为fastcgi server的地址； location, if in location；
2、fastcgi_index name;
fastcgi默认的主页资源;
3、fastcgi_param parameter value [if_not_empty];
Sets a parameter that should be passed to the FastCGI server. The value can contain text, variables, and their combination.
配置示例1：
前提：配置好fpm server和mariadb-server服务；
location ~* \.php$ {
root /usr/share/nginx/html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html$fastcgi_script_name;
include fastcgi_params;
}
配置示例2：通过/pm_status和/ping来获取fpm server状态信息；
location ~* ^/(pm_status|ping)$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
}
4、fastcgi_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time];
定义fastcgi的缓存；缓存位置为磁盘上的文件系统，由path所指定路径来定义；
levels=levels：缓存目录的层级数量，以及每一级的目录数量；levels=ONE:TWO:THREE
leves=1:2:2
keys_zone=name:size
k/v映射的内存空间的名称及大小
inactive=time
非活动时长
max_size=size
磁盘上用于缓存数据的缓存空间上限
5、fastcgi_cache zone | off;
调用指定的缓存空间来缓存数据；http, server, location
6、fastcgi_cache_key string;
定义用作缓存项的key的字符串；
7、fastcgi_cache_methods GET | HEAD | POST ...;
为哪些请求方法使用缓存；
8、fastcgi_cache_min_uses number;
缓存空间中的缓存项在inactive定义的非活动时间内至少要被访问到此处所指定的次数方可被认作活动项；
9、fastcgi_cache_valid [code ...] time;
不同的响应码各自的缓存时长；
示例：
http {
...
fastcgi_cache_path /var/cache/nginx/fastcgi_cache levels=1:2:1 keys_zone=fcgi:20m inactive=120s;
...
server {
...
location ~* \.php$ {
...
fastcgi_cache fcgi;
fastcgi_cache_key $request_uri;
fastcgi_cache_valid 200 302 10m;
fastcgi_cache_valid 301 1h;
fastcgi_cache_valid any 1m;
...
}
...
}
...
}
10、fastcgi_keep_conn on | off;
By default, a FastCGI server will close a connection right after sending the response. However, when this directive is set to the value on, nginx will instruct a FastCGI server to keep connections open.
博客作业：以上所有内容；
练习：实现lnmp，提供多个虚拟主机；
(1) http, 提供wordpress；
(2) https, 提供pma；
ngx_http_upstream_module模块
The ngx_http_upstream_module module is used to define groups of servers that can be referenced by the proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, and memcached_pass directives.
1、upstream name { ... }
定义后端服务器组，会引入一个新的上下文；Context: http
upstream httpdsrvs {
server ...
server...
...
}
2、server address [parameters];
在upstream上下文中server成员，以及相关的参数；Context: upstream
address的表示格式：
unix:/PATH/TO/SOME_SOCK_FILE
IP[:PORT]
HOSTNAME[:PORT]
parameters：
weight=number
权重，默认为1；
max_fails=number
失败尝试最大次数；超出此处指定的次数时，server将被标记为不可用；
fail_timeout=time
设置将服务器标记为不可用状态的超时时长；
max_conns
当前的服务器的最大并发连接数；
backup
将服务器标记为“备用”，即所有服务器均不可用时此服务器才启用；
down
标记为“不可用”；
3、least_conn;
最少连接调度算法，当server拥有不同的权重时其为wlc;
4、 ip_hash;
源地址hash调度方法；能够将来自同一个源IP地址的请求始终发往同一个upstream server；
5、hash key [consistent];
基于指定的key的hash表来实现对请求的调度，此处的key可以直接文本、变量或二者的组合；
作用：将请求分类，同一类请求将发往同一个upstream server；
consistent：参数，指定使用一致性hash算法；
If the consistent parameter is specified the ketama consistent hashing method will be used instead.
示例：
hash $request_uri consistent;
hash $remote_addr;
6、keepalive connections;
为每个worker进程保留的空闲的长连接数量；
(8) health_check [parameters];
定义对后端主机的健康状态检测机制；只能用于location上下文；
可用参数：
interval=time：检测频率，默认为每隔5秒钟；
fails=number：判断服务器状态转为失败需要检测的次数；
passes=number：判断服务器状态转为成功需要检测的次数；
uri=uri：判断其健康与否时使用的uri；
match=name：基于指定的match来衡量检测结果的成败；
port=number：使用独立的端口进行检测；
仅Nginx Plus有效；
7、 least_conn;
最少连接调度算法； 当server拥有不同的权重时为wlc；
8、 least_time header | last_byte;
最短平均响应时长和最少连接；
header：response_header;
last_byte: full_response;
仅Nginx Plus有效；
9、 match name { ... }
Defines the named test set used to verify responses to health check requests.
定义衡量某检测结果是否为成功的衡量机制；
专用指令：
status：期望的响应码；
status CODE
status ! CODE
...
header：基于响应报文的首部进行判断
header HEADER=VALUE
header HEADER ~ VALUE
...
body：基于响应报文的内容进行判断
body ~ &amp;#34;PATTERN&amp;#34;
body !~ &amp;#34;PATTERN&amp;#34;
仅Nginx Plus有效；
nginx的其它的二次发行版：
tengine
OpenResty
ngx_stream_core_module模块
模拟反代基于tcp或udp的服务连接，即工作于传输层的反代或调度器；
1、 stream { ... }
定义stream相关的服务；Context:main
stream {
upstream sshsrvs {
server 192.168.22.2:22;
server 192.168.22.3:22;
least_conn;
hash $remote_addr consistent;
}
server {
listen 10.1.0.6:22022;
proxy_pass sshsrvs;
}
}
2、 listen
listen address:port [ssl] [udp] [proxy_protocol] [backlog=number] [bind] [ipv6only=on|off] [reuseport] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux网页缓存之varnish</title><link>/2015/11/13/linux%E7%BD%91%E9%A1%B5%E7%BC%93%E5%AD%98%E4%B9%8Bvarnish/</link><pubDate>Fri, 13 Nov 2015 00:00:00 +0000</pubDate><guid>/2015/11/13/linux%E7%BD%91%E9%A1%B5%E7%BC%93%E5%AD%98%E4%B9%8Bvarnish/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;程序的运行具有局部性特征：
时间局部性：一个数据被访问过之后，可能很快会被再次访问
空间局部性：一个数据被访问时，其周边的数据也有可能被访问到
cache：命中
热区：局部性；
时效性：
缓存空间耗尽：LRU
过期：缓存清理
缓存命中率：hit/(hit+miss)
(0,1)
页面命中率：基于页面数量进行衡量
字节命中率：基于页面的体积进行衡量
缓存与否：
私有数据：private，private cache；
公共数据：public, public or private cache;
Cache-related Headers Fields
The most important caching header fields are:
Expires：过期时间；
Expires:Thu, 22 Oct 2026 06:34:30 GMT
Cache-Control
Etag
If-None-Match
Last-Modified
If-Modified-Since
Vary
Age
缓存有效性判断机制：
过期时间：Expires
HTTP/1.0
Expires
HTTP/1.1
Cache-Control: maxage=
Cache-Control: s-maxage=
条件式请求：
Last-Modified/If-Modified-Since
Etag/If-None-Match
Expires:Thu, 13 Aug 2026 02:05:12 GMT
Cache-Control:max-age=315360000
ETag:&amp;#34;1ec5-502264e2ae4c0&amp;#34;
Last-Modified:Wed, 03 Sep 2014 10:00:27 GMT
缓存层级：
私有缓存：用户代理附带的本地缓存机制；
公共缓存：反向代理服务器的缓存功能；
User-Agent &amp;lt;--&amp;gt; private cache &amp;lt;--&amp;gt; public cache &amp;lt;--&amp;gt; public cache 2 &amp;lt;--&amp;gt; Original Server
请求报文用于通知缓存服务如何使用缓存响应请求：
cache-request-directive =
&amp;#34;no-cache&amp;#34;，
| &amp;#34;no-store&amp;#34;
| &amp;#34;max-age&amp;#34; &amp;#34;=&amp;#34; delta-seconds
| &amp;#34;max-stale&amp;#34; [ &amp;#34;=&amp;#34; delta-seconds ]
| &amp;#34;min-fresh&amp;#34; &amp;#34;=&amp;#34; delta-seconds
| &amp;#34;no-transform&amp;#34;
| &amp;#34;only-if-cached&amp;#34;
| cache-extension
响应报文用于通知缓存服务器如何存储上级服务器响应的内容：
cache-response-directive =
&amp;#34;public&amp;#34;
| &amp;#34;private&amp;#34; [ &amp;#34;=&amp;#34; &amp;lt;&amp;#34;&amp;gt; 1#field-name &amp;lt;&amp;#34;&amp;gt; ]
| &amp;#34;no-cache&amp;#34; [ &amp;#34;=&amp;#34; &amp;lt;&amp;#34;&amp;gt; 1#field-name &amp;lt;&amp;#34;&amp;gt; ]，可缓存，但响应给客户端之前需要revalidation；
| &amp;#34;no-store&amp;#34; ，不允许存储响应内容于缓存中；
| &amp;#34;no-transform&amp;#34;
| &amp;#34;must-revalidate&amp;#34;
| &amp;#34;proxy-revalidate&amp;#34;
| &amp;#34;max-age&amp;#34; &amp;#34;=&amp;#34; delta-seconds
| &amp;#34;s-maxage&amp;#34; &amp;#34;=&amp;#34; delta-seconds
| cache-extension
开源解决方案：
squid：
varnish：
varnish官方站点： http://www.varnish-cache.org/
Community
Enterprise
This is Varnish Cache, a high-performance HTTP accelerator.
程序架构：
Manager进程
Cacher进程，包含多种类型的线程：
accept, worker, expiry, ...
shared memory log：
统计数据：计数器；
日志区域：日志记录；
varnishlog, varnishncsa, varnishstat...
配置接口：VCL
Varnish Configuration Language,
vcl complier --&amp;gt; c complier --&amp;gt; shared object
varnish的程序环境：
/etc/varnish/varnish.params： 配置varnish服务进程的工作特性，例如监听的地址和端口，缓存机制；
/etc/varnish/default.vcl：配置各Child/Cache线程的缓存工作属性；
主程序：
/usr/sbin/varnishd
CLI interface：
/usr/bin/varnishadm
Shared Memory Log交互工具：
/usr/bin/varnishhist
/usr/bin/varnishlog
/usr/bin/varnishncsa
/usr/bin/varnishstat
/usr/bin/varnishtop
测试工具程序：
/usr/bin/varnishtest
VCL配置文件重载程序：
/usr/sbin/varnish_reload_vcl
Systemd Unit File：
/usr/lib/systemd/system/varnish.service
varnish服务
/usr/lib/systemd/system/varnishlog.service
/usr/lib/systemd/system/varnishncsa.service
日志持久的服务；
varnish的缓存存储机制( Storage Types)：
-s [name=]type[,options]
・ malloc[,size]
内存存储，[,size]用于定义空间大小；重启后所有缓存项失效；
・ file[,path[,size[,granularity]]]
文件存储，黑盒；重启后所有缓存项失效；
・ persistent,path,size
文件存储，黑盒；重启后所有缓存项有效；实验；
varnish程序的选项：
程序选项：/etc/varnish/varnish.params文件
-a address[:port][,address[:port][...]，默认为6081端口；
-T address[:port]，默认为6082端口；
-s [name=]type[,options]，定义缓存存储机制；
-u user
-g group
-f config：VCL配置文件；
-F：运行于前台；
...
运行时参数：/etc/varnish/varnish.params文件， DEAMON_OPTS
DAEMON_OPTS=&amp;#34;-p thread_pool_min=5 -p thread_pool_max=500 -p thread_pool_timeout=300&amp;#34;
-p param=value：设定运行参数及其值； 可重复使用多次；
-r param[,param...]: 设定指定的参数为只读状态；
重载vcl配置文件：
~ ]# varnish_reload_vcl
varnishadm
-S /etc/varnish/secret -T [ADDRESS:]PORT
help [&amp;lt;command&amp;gt;]
ping [&amp;lt;timestamp&amp;gt;]
auth &amp;lt;response&amp;gt;
quit
banner
status
start
stop
vcl.load &amp;lt;configname&amp;gt; &amp;lt;filename&amp;gt;
vcl.inline &amp;lt;configname&amp;gt; &amp;lt;quoted_VCLstring&amp;gt;
vcl.use &amp;lt;configname&amp;gt;
vcl.discard &amp;lt;configname&amp;gt;
vcl.list
param.show [-l] [&amp;lt;param&amp;gt;]
param.set &amp;lt;param&amp;gt; &amp;lt;value&amp;gt;
panic.show
panic.clear
storage.list
vcl.show [-v] &amp;lt;configname&amp;gt;
backend.list [&amp;lt;backend_expression&amp;gt;]
backend.set_health &amp;lt;backend_expression&amp;gt; &amp;lt;state&amp;gt;
ban &amp;lt;field&amp;gt; &amp;lt;operator&amp;gt; &amp;lt;arg&amp;gt; [&amp;amp;&amp;amp; &amp;lt;field&amp;gt; &amp;lt;oper&amp;gt; &amp;lt;arg&amp;gt;]...
ban.list
配置文件相关：
vcl.list
vcl.load：装载，加载并编译；
vcl.use：激活；
vcl.discard：删除；
vcl.show [-v] &amp;lt;configname&amp;gt;：查看指定的配置文件的详细信息；
运行时参数：
param.show -l：显示列表；
param.show &amp;lt;PARAM&amp;gt;
param.set &amp;lt;PARAM&amp;gt; &amp;lt;VALUE&amp;gt;
缓存存储：
storage.list
后端服务器：
backend.list
VCL：
”域“专有类型的配置语言；
state engine：状态引擎；
VCL有多个状态引擎，状态之间存在相关性，但状态引擎彼此间互相隔离；每个状态引擎可使用return(x)指明关联至哪个下一级引擎；每个状态引擎对应于vcl文件中的一个配置段，即为subroutine
vcl_hash --&amp;gt; return(hit) --&amp;gt; vcl_hit
Client Side：
vcl_recv, vcl_pass, vcl_hit, vcl_miss, vcl_pipe, vcl_purge, vcl_synth, vcl_deliver
vcl_recv：
hash：vcl_hash
pass: vcl_pass
pipe: vcl_pipe
synth: vcl_synth
purge: vcl_hash --&amp;gt; vcl_purge
vcl_hash：
lookup：
hit: vcl_hit
miss: vcl_miss
pass, hit_for_pass: vcl_pass
purge: vcl_purge
Backend Side：
vcl_backend_fetch, vcl_backend_response, vcl_backend_error
两个特殊的引擎：
vcl_init：在处理任何请求之前要执行的vcl代码：主要用于初始化VMODs；
vcl_fini：所有的请求都已经结束，在vcl配置被丢弃时调用；主要用于清理VMODs；
vcl的语法格式：
(1) VCL files start with vcl 4.0;
(2) //, # and /* foo */ for comments;
(3) Subroutines are declared with the sub keyword; 例如sub vcl_recv { ...}；
(4) No loops, state-limited variables（受限于引擎的内建变量）；
(5) Terminating statements with a keyword for next action as argument of the return() function, i.e.: return(action)；用于实现状态引擎转换；
(6) Domain-specific;
The VCL Finite State Machine
(1) Each request is processed separately;
(2) Each request is independent from others at any given time;
(3) States are related, but isolated;
(4) return(action); exits one state and instructs Varnish to proceed to the next state;
(5) Built-in VCL code is always present and appended below your own VCL;
三类主要语法：
sub subroutine {
...
}
if CONDITION {
...
} else {
...
}
return(), hash_data()
VCL Built-in Functions and Keywords
函数：
regsub(str, regex, sub)
regsuball(str, regex, sub)
ban(boolean expression)
hash_data(input)
synthetic(str)
Keywords:
call subroutine， return(action)，new，set，unset
操作符：
==, !=, ~, &amp;gt;, &amp;gt;=, &amp;lt;, &amp;lt;=
逻辑操作符：&amp;amp;&amp;amp;, ||, !
变量赋值：=
举例：obj.hits
if (obj.hits&amp;gt;0) {
set resp.http.X-Cache = &amp;#34;HIT via &amp;#34; + server.ip;
} else {
set resp.http.X-Cache = &amp;#34;MISS via &amp;#34; + server.ip;
}
变量类型：
内建变量：
req.*：request，表示由客户端发来的请求报文相关；
req.http.*
req.http.User-Agent, req.http.Referer, ...
bereq.*：由varnish发往BE主机的httpd请求相关；
bereq.http.*
beresp.*：由BE主机响应给varnish的响应报文相关；
beresp.http.*
resp.*：由varnish响应给client相关；
obj.*：存储在缓存空间中的缓存对象的属性；只读；
常用变量：
bereq.*, req.*：
bereq.http.HEADERS
bereq.request：请求方法；
bereq.url：请求的url；
bereq.proto：请求的协议版本；
bereq.backend：指明要调用的后端主机；
req.http.Cookie：客户端的请求报文中Cookie首部的值；
req.http.User-Agent ~ &amp;#34;chrome&amp;#34;
beresp.*, resp.*：
beresp.http.HEADERS
beresp.status：响应的状态码；
reresp.proto：协议版本；
beresp.backend.name：BE主机的主机名；
beresp.ttl：BE主机响应的内容的余下的可缓存时长；
obj.*
obj.hits：此对象从缓存中命中的次数；
obj.ttl：对象的ttl值
server.*
server.ip
server.hostname
client.*
client.ip
用户自定义：
set
unset
示例1：强制对某类资源的请求不检查缓存：
vcl_recv {
if (req.url ~ &amp;#34;(?i)^/(login|admin)&amp;#34;) {
return(pass);
}
}
示例2：对于特定类型的资源，例如公开的图片等，取消其私有标识，并强行设定其可以由varnish缓存的时长；
if (beresp.http.cache-control !~ &amp;#34;s-maxage&amp;#34;) {
if (bereq.url ~ &amp;#34;(?i)\.(jpg|jpeg|png|gif|css|js)$&amp;#34;) {
unset beresp.http.Set-Cookie;
set beresp.ttl = 3600s;
}
}
缓存对象的修剪：purge, ban
(1) 能执行purge操作
sub vcl_purge {
return (synth(200,&amp;#34;Purged&amp;#34;));
}
(2) 何时执行purge操作
sub vcl_recv {
if (req.method == &amp;#34;PURGE&amp;#34;) {
return(purge);
}
...
}
添加此类请求的访问控制法则：
acl purgers {
&amp;#34;127.0.0.0&amp;#34;/8;
&amp;#34;10.1.0.0&amp;#34;/16;
}
sub vcl_recv {
if (req.method == &amp;#34;PURGE&amp;#34;) {
if (!client.ip ~ purgers) {
return(synth(405,&amp;#34;Purging not allowed for &amp;#34; + client.ip));
}
return(purge);
}
...
}
如何设定使用多个后端主机：
backend default {
.host = &amp;#34;172.16.100.6&amp;#34;;
.port = &amp;#34;80&amp;#34;;
}
backend appsrv {
.host = &amp;#34;172.16.100.7&amp;#34;;
.port = &amp;#34;80&amp;#34;;
}
sub vcl_recv {
if (req.url ~ &amp;#34;(?i)\.php$&amp;#34;) {
set req.backend_hint = appsrv;
} else {
set req.backend_hint = default;
}
...
}
Director：
varnish module；
使用前需要导入：
import director；
示例：
import directors; # load the directors
backend server1 {
.host =
.port =
}
backend server2 {
.host =
.port =
}
sub vcl_init {
new GROUP_NAME = directors.round_robin();
GROUP_NAME.add_backend(server1);
GROUP_NAME.add_backend(server2);
}
sub vcl_recv {
# send all traffic to the bar director:
set req.backend_hint = GROUP_NAME.backend();
}
BE Health Check：
backend BE_NAME {
.host =
.port =
.probe = {
.url=
.timeout=
.interval=
.window=
.threshhold=
}
}
.probe：定义健康状态检测方法；
.url：检测时请求的URL，默认为”/&amp;#34;;
.request：发出的具体请求；
.request =
&amp;#34;GET /.healthtest.html HTTP/1.1&amp;#34;
&amp;#34;Host: www.magedu.com&amp;#34;
&amp;#34;Connection: close&amp;#34;
.window：基于最近的多少次检查来判断其健康状态；
.threshhold：最近.window中定义的这么次检查中至有.threshhold定义的次数是成功的；
.interval：检测频度；
.timeout：超时时长；
.expected_response：期望的响应码，默认为200；
健康状态检测的配置方式：
(1) probe PB_NAME = { }
backend NAME = {
.probe = PB_NAME;
...
}
(2) backend NAME {
.probe = {
...
}
}
示例：
probe check {
.url = &amp;#34;/.healthcheck.html&amp;#34;;
.window = 5;
.threshold = 4;
.interval = 2s;
.timeout = 1s;
}
backend default {
.host = &amp;#34;10.1.0.68&amp;#34;;
.port = &amp;#34;80&amp;#34;;
.probe = check;
}
backend appsrv {
.host = &amp;#34;10.1.0.69&amp;#34;;
.port = &amp;#34;80&amp;#34;;
.probe = check;
}
varnish的运行时参数：
线程模型：
cache-worker
cache-main
ban lurker
acceptor：
epoll/kqueue：
...
线程相关的参数：
在线程池内部，其每一个请求由一个线程来处理； 其worker线程的最大数决定了varnish的并发响应能力；
thread_pools：Number of worker thread pools. 最好小于或等于CPU核心数量；
thread_pool_max：The maximum number of worker threads in each pool. 每线程池的最大线程数；
thread_pool_min：The minimum number of worker threads in each pool. 额外意义为“最大空闲线程数”；
最大并发连接数=thread_pools * thread_pool_max
thread_pool_timeout：Thread idle threshold. Threads in excess of thread_pool_min, which have been idle for at least this long, will be destroyed.
thread_pool_add_delay：Wait at least this long after creating a thread.
thread_pool_destroy_delay：Wait this long after destroying a thread.
设置方式：
vcl.param
param.set
永久有效的方法：
varnish.params
DEAMON_OPTS=&amp;#34;-p PARAM1=VALUE -p PARAM2=VALUE&amp;#34;
varnish日志区域：
shared memory log
计数器
日志信息
1、varnishstat - Varnish Cache statistics
-1
-1 -f FILED_NAME
-l：可用于-f选项指定的字段名称列表；
MAIN.cache_hit
MAIN.cache_miss
# varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss
2、varnishtop - Varnish log entry ranking
-1 Instead of a continously updated display, print the statistics once and exit.
-i taglist，可以同时使用多个-i选项，也可以一个选项跟上多个标签；
-I &amp;lt;[taglist:]regex&amp;gt;
-x taglist：排除列表
-X &amp;lt;[taglist:]regex&amp;gt;
3、varnishlog - Display Varnish logs
4、 varnishncsa - Display Varnish logs in Apache / NCSA combined log format
内建函数：
hash_data()：指明哈希计算的数据；减少差异，以提升命中率；
regsub(str,regex,sub)：把str中被regex第一次匹配到字符串替换为sub；主要用于URL Rewrite
regsuball(str,regex,sub)：把str中被regex每一次匹配到字符串均替换为sub；
return()：
ban(expression)
ban_url(regex)：Bans所有的其URL可以被此处的regex匹配到的缓存对象；
synth(status,&amp;#34;STRING&amp;#34;)：purge操作；
总结：
varnish： state engine, vcl
varnish 4.0：
vcl_init
vcl_recv
vcl_hash
vcl_hit
vcl_pass
vcl_miss
vcl_pipe
vcl_waiting
vcl_purge
vcl_purge
vcl_deliver
vcl_synth
vcl_fini
vcl_backend_fetch
vcl_backend_response
vcl_backend_error
sub VCL_STATE_ENGINE
backend BE_NAME {}
probe PB_NAME {}
acl ACL_NAME {}
博客作业：以上所有内容；
课外实践：(1) zabbix监控varnish业务指标；
(2) ansible实现varnish快速部署；
(3) 两个lamp部署wordpress，用Nginx反代，做压测；nginx后部署varnish缓存，调整vcl，多次压测；
ab, http_load, webbench, seige, jmeter, loadrunner,...
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux系统高可用之HA-Cluster</title><link>/2015/11/03/linux%E7%B3%BB%E7%BB%9F%E9%AB%98%E5%8F%AF%E7%94%A8%E4%B9%8Bha-cluster/</link><pubDate>Tue, 03 Nov 2015 00:00:00 +0000</pubDate><guid>/2015/11/03/linux%E7%B3%BB%E7%BB%9F%E9%AB%98%E5%8F%AF%E7%94%A8%E4%B9%8Bha-cluster/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;
HA Cluster：
集群类型：LB（lvs/nginx（http/upstream, stream/upstream））、HA、HP
SPoF: Single Point of Failure
系统可用性的公式：A=MTBF/（MTBF+MTTR）
(0,1), 95%
几个9（指标）: 99%, ..., 99.999%，99.9999%；
99%： %1, 99.9%， 0.1%
系统故障：
硬件故障：设计缺陷、wear out、自然灾害、……
软件故障：设计缺陷、
提升系统高用性的解决方案之降低MTTR：
手段：冗余（redundant）
active/passive（主备），active/active（双主）
active --&amp;gt; HEARTBEAT --&amp;gt; passive
active &amp;lt;--&amp;gt; HEARTBEAT &amp;lt;--&amp;gt; active
高可用的是“服务”：
HA nginx service：
vip/nginx process[/shared storage]
资源：组成一个高可用服务的“组件”；
(1) passive node的数量？
(2) 资源切换？
shared storage：
NAS：文件共享服务器；
SAN：存储区域网络，块级别的共享；
Network partition：网络分区
隔离设备：
node：STONITH = Shooting The Other Node In The Head
资源：fence
quorum：
with quorum： &amp;gt; total/2
without quorum: &amp;lt;= total/2
TWO nodes Cluster？
辅助设备：ping node, quorum disk;
Failover：故障切换，即某资源的主节点故障时，将资源转移至其它节点的操作；
Failback：故障移回，即某资源的主节点故障后重新修改上线后，将转移至其它节点的资源重新切回的过程；
HA Cluster实现方案:
vrrp协议的实现
keepalived
ais：完备HA集群
RHCS（cman）
heartbeat
corosync
keepalived：
vrrp协议：Virtual Redundant Routing Protocol
术语：
虚拟路由器：Virtual Router
虚拟路由器标识：VRID(0-255)
物理路由器：
master：主设备
backup：备用设备
priority：优先级
VIP：Virtual IP
VMAC：Virutal MAC (00-00-5e-00-01-VRID)
通告：心跳，优先级等；周期性；
抢占式，非抢占式；
安全工作：
认证：
无认证
简单字符认证
MD5
工作模式：
主/备：单虚拟路径器；
主/主：主/备（虚拟路径器1），备/主（虚拟路径器2）
keepalived:
vrrp协议的软件实现，原生设计的目的为了高可用ipvs服务：
vrrp协议完成地址流动；
为vip地址所在的节点生成ipvs规则（在配置文件中预先定义）；
为ipvs集群的各RS做健康状态检测；
基于脚本调用接口通过执行脚本完成脚本中定义的功能，进而影响集群事务；
组件：
核心组件：
vrrp stack
ipvs wrapper
checkers
控制组件：配置文件分析器
IO复用器
内存管理组件
HA Cluster的配置前提：
(1) 各节点时间必须同步；
ntp, chrony
(2) 确保iptables及selinux不会成为阻碍；
(3) 各节点之间可通过主机名互相通信（对KA并非必须）；
建议使用/etc/hosts文件实现；
(4) 各节点之间的root用户可以基于密钥认证的ssh服务完成互相通信；（并非必须）
keepalived安装配置：
CentOS 6.4+
程序环境：
主配置文件：/etc/keepalived/keepalived.conf
主程序文件：/usr/sbin/keepalived
Unit File：keepalived.service
Unit File的环境配置文件：/etc/sysconfig/keepalived
配置文件组件部分：
TOP HIERACHY
GLOBAL CONFIGURATION
Global definitions
Static routes/addresses
VRRPD CONFIGURATION
VRRP synchronization group(s)：vrrp同步组；
VRRP instance(s)：每个vrrp instance即一个vrrp路由器；
LVS CONFIGURATION
Virtual server group(s)
Virtual server(s)：ipvs集群的vs和rs；
单主配置示例：
! Configuration File for keepalived
global_defs {
notification_email {
root@localhost
}
notification_email_from keepalived@localhost
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id node1
vrrp_mcast_group4 224.0.100.19
}
vrrp_instance VI_1 {
state BACKUP
interface eno16777736
virtual_router_id 14
priority 98
advert_int 1
authentication {
auth_type PASS
auth_pass 571f97b2
}
virtual_ipaddress {
10.1.0.91/16 dev eno16777736
}
}
配置语法：
配置虚拟路由器：
vrrp_instance &amp;lt;STRING&amp;gt; {
....
}
专用参数：
state MASTER|BACKUP：当前节点在此虚拟路由器上的初始状态；只能有一个是MASTER，余下的都应该为BACKUP；
interface IFACE_NAME：绑定为当前虚拟路由器使用的物理接口；
virtual_router_id VRID：当前虚拟路由器的惟一标识，范围是0-255；
priority 100：当前主机在此虚拟路径器中的优先级；范围1-254；
advert_int 1：vrrp通告的时间间隔；
authentication {
auth_type AH|PASS
auth_pass &amp;lt;PASSWORD&amp;gt;
}
virtual_ipaddress {
&amp;lt;IPADDR&amp;gt;/&amp;lt;MASK&amp;gt; brd &amp;lt;IPADDR&amp;gt; dev &amp;lt;STRING&amp;gt; scope &amp;lt;SCOPE&amp;gt; label &amp;lt;LABEL&amp;gt;
192.168.200.17/24 dev eth1
192.168.200.18/24 dev eth2 label eth2:1
}
track_interface {
eth0
eth1
...
}
配置要监控的网络接口，一旦接口出现故障，则转为FAULT状态；
nopreempt：定义工作模式为非抢占模式；
preempt_delay 300：抢占式模式下，节点上线后触发新选举操作的延迟时长；
定义通知脚本：
notify_master &amp;lt;STRING&amp;gt;|&amp;lt;QUOTED-STRING&amp;gt;：当前节点成为主节点时触发的脚本；
notify_backup &amp;lt;STRING&amp;gt;|&amp;lt;QUOTED-STRING&amp;gt;：当前节点转为备节点时触发的脚本；
notify_fault &amp;lt;STRING&amp;gt;|&amp;lt;QUOTED-STRING&amp;gt;：当前节点转为“失败”状态时触发的脚本；
notify &amp;lt;STRING&amp;gt;|&amp;lt;QUOTED-STRING&amp;gt;：通用格式的通知触发机制，一个脚本可完成以上三种状态的转换时的通知；
双主模型示例：
! Configuration File for keepalived
global_defs {
notification_email {
root@localhost
}
notification_email_from keepalived@localhost
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id node1
vrrp_mcast_group4 224.0.100.19
}
vrrp_instance VI_1 {
state MASTER
interface eno16777736
virtual_router_id 14
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 571f97b2
}
virtual_ipaddress {
10.1.0.91/16 dev eno16777736
}
}
vrrp_instance VI_2 {
state BACKUP
interface eno16777736
virtual_router_id 15
priority 98
advert_int 1
authentication {
auth_type PASS
auth_pass 578f07b2
}
virtual_ipaddress {
10.1.0.92/16 dev eno16777736
}
}
示例通知脚本：
#!/bin/bash
#
contact=&amp;#39;root@localhost&amp;#39;
notify() {
mailsubject=&amp;#34;$(hostname) to be $1, vip floating&amp;#34;
mailbody=&amp;#34;$(date +&amp;#39;%F %T&amp;#39;): vrrp transition, $(hostname) changed to be $1&amp;#34;
echo &amp;#34;$mailbody&amp;#34; | mail -s &amp;#34;$mailsubject&amp;#34; $contact
}
case $1 in
master)
notify master
;;
backup)
notify backup
;;
fault)
notify fault
;;
*)
echo &amp;#34;Usage: $(basename $0) {master|backup|fault}&amp;#34;
exit 1
;;
esac
脚本的调用方法：
notify_master &amp;#34;/etc/keepalived/notify.sh master&amp;#34;
notify_backup &amp;#34;/etc/keepalived/notify.sh backup&amp;#34;
notify_fault &amp;#34;/etc/keepalived/notify.sh fault&amp;#34;
虚拟服务器：
配置参数：
virtual_server IP port |
virtual_server fwmark int
{
...
real_server {
...
}
...
}
常用参数：
delay_loop &amp;lt;INT&amp;gt;：服务轮询的时间间隔；
lb_algo rr|wrr|lc|wlc|lblc|sh|dh：定义调度方法；
lb_kind NAT|DR|TUN：集群的类型；
persistence_timeout &amp;lt;INT&amp;gt;：持久连接时长；
protocol TCP：服务协议，仅支持TCP；
sorry_server &amp;lt;IPADDR&amp;gt; &amp;lt;PORT&amp;gt;：备用服务器地址；
real_server &amp;lt;IPADDR&amp;gt; &amp;lt;PORT&amp;gt;
{
weight &amp;lt;INT&amp;gt;
notify_up &amp;lt;STRING&amp;gt;|&amp;lt;QUOTED-STRING&amp;gt;
notify_down &amp;lt;STRING&amp;gt;|&amp;lt;QUOTED-STRING&amp;gt;
HTTP_GET|SSL_GET|TCP_CHECK|SMTP_CHECK|MISC_CHECK { ... }：定义当前主机的健康状态检测方法；
}
HTTP_GET|SSL_GET：应用层检测
HTTP_GET|SSL_GET {
url {
path &amp;lt;URL_PATH&amp;gt;：定义要监控的URL；
status_code &amp;lt;INT&amp;gt;：判断上述检测机制为健康状态的响应码；
digest &amp;lt;STRING&amp;gt;：判断上述检测机制为健康状态的响应的内容的校验码；
}
nb_get_retry &amp;lt;INT&amp;gt;：重试次数；
delay_before_retry &amp;lt;INT&amp;gt;：重试之前的延迟时长；
connect_ip &amp;lt;IP ADDRESS&amp;gt;：向当前RS的哪个IP地址发起健康状态检测请求
connect_port &amp;lt;PORT&amp;gt;：向当前RS的哪个PORT发起健康状态检测请求
bindto &amp;lt;IP ADDRESS&amp;gt;：发出健康状态检测请求时使用的源地址；
bind_port &amp;lt;PORT&amp;gt;：发出健康状态检测请求时使用的源端口；
connect_timeout &amp;lt;INTEGER&amp;gt;：连接请求的超时时长；
}
TCP_CHECK {
connect_ip &amp;lt;IP ADDRESS&amp;gt;：向当前RS的哪个IP地址发起健康状态检测请求
connect_port &amp;lt;PORT&amp;gt;：向当前RS的哪个PORT发起健康状态检测请求
bindto &amp;lt;IP ADDRESS&amp;gt;：发出健康状态检测请求时使用的源地址；
bind_port &amp;lt;PORT&amp;gt;：发出健康状态检测请求时使用的源端口；
connect_timeout &amp;lt;INTEGER&amp;gt;：连接请求的超时时长；
}
高可用的ipvs集群示例：
! Configuration File for keepalived
global_defs {
notification_email {
root@localhost
}
notification_email_from keepalived@localhost
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id node1
vrrp_mcast_group4 224.0.100.19
}
vrrp_instance VI_1 {
state MASTER
interface eno16777736
virtual_router_id 14
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 571f97b2
}
virtual_ipaddress {
10.1.0.93/16 dev eno16777736
}
notify_master &amp;#34;/etc/keepalived/notify.sh master&amp;#34;
notify_backup &amp;#34;/etc/keepalived/notify.sh backup&amp;#34;
notify_fault &amp;#34;/etc/keepalived/notify.sh fault&amp;#34;
}
virtual_server 10.1.0.93 80 {
delay_loop 3
lb_algo rr
lb_kind DR
protocol TCP
sorry_server 127.0.0.1 80
real_server 10.1.0.69 80 {
weight 1
HTTP_GET {
url {
path /
status_code 200
}
connect_timeout 1
nb_get_retry 3
delay_before_retry 1
}
}
real_server 10.1.0.71 80 {
weight 1
HTTP_GET {
url {
path /
status_code 200
}
connect_timeout 1
nb_get_retry 3
delay_before_retry 1
}
}
}
博客作业：第一部分
双主模式的lvs集群，拓扑、实现过程；
配置示例（一个节点）：
! Configuration File for keepalived
global_defs {
notification_email {
root@localhost
}
notification_email_from kaadmin@localhost
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id node1
vrrp_mcast_group4 224.0.100.67
}
vrrp_instance VI_1 {
state MASTER
interface eno16777736
virtual_router_id 44
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass f1bf7fde
}
virtual_ipaddress {
172.16.0.80/16 dev eno16777736 label eno16777736:0
}
track_interface {
eno16777736
}
notify_master &amp;#34;/etc/keepalived/notify.sh master&amp;#34;
notify_backup &amp;#34;/etc/keepalived/notify.sh backup&amp;#34;
notify_fault &amp;#34;/etc/keepalived/notify.sh fault&amp;#34;
}
vrrp_instance VI_2 {
state BACKUP
interface eno16777736
virtual_router_id 45
priority 98
advert_int 1
authentication {
auth_type PASS
auth_pass f2bf7ade
}
virtual_ipaddress {
172.16.0.90/16 dev eno16777736 label eno16777736:1
}
track_interface {
eno16777736
}
notify_master &amp;#34;/etc/keepalived/notify.sh master&amp;#34;
notify_backup &amp;#34;/etc/keepalived/notify.sh backup&amp;#34;
notify_fault &amp;#34;/etc/keepalived/notify.sh fault&amp;#34;
}
virtual_server fwmark 3 {
delay_loop 2
lb_algo rr
lb_kind DR
nat_mask 255.255.0.0
protocol TCP
sorry_server 127.0.0.1 80
real_server 172.16.0.69 80 {
weight 1
HTTP_GET {
url {
path /
status_code 200
}
connect_timeout 2
nb_get_retry 3
delay_before_retry 3
}
}
real_server 172.16.0.6 80 {
weight 1
HTTP_GET {
url {
path /
status_code 200
}
connect_timeout 2
nb_get_retry 3
delay_before_retry 3
}
}
}
keepalived调用外部的辅助脚本进行资源监控，并根据监控的结果状态能实现优先动态调整；
分两步：(1) 先定义一个脚本；(2) 调用此脚本；
vrrp_script &amp;lt;SCRIPT_NAME&amp;gt; {
script &amp;#34;&amp;#34;
interval INT
weight -INT
}
track_script {
SCRIPT_NAME_1
SCRIPT_NAME_2
...
}
示例：高可用nginx服务
! Configuration File for keepalived
global_defs {
notification_email {
root@localhost
}
notification_email_from keepalived@localhost
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id node1
vrrp_mcast_group4 224.0.100.19
}
vrrp_script chk_down {
script &amp;#34;[[ -f /etc/keepalived/down ]] &amp;amp;&amp;amp; exit 1 || exit 0&amp;#34;
interval 1
weight -5
}
vrrp_script chk_nginx {
script &amp;#34;killall -0 nginx &amp;amp;&amp;amp; exit 0 || exit 1&amp;#34;
interval 1
weight -5
}
vrrp_instance VI_1 {
state MASTER
interface eno16777736
virtual_router_id 14
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 571f97b2
}
virtual_ipaddress {
10.1.0.93/16 dev eno16777736
}
track_script {
chk_down
chk_nginx
}
notify_master &amp;#34;/etc/keepalived/notify.sh master&amp;#34;
notify_backup &amp;#34;/etc/keepalived/notify.sh backup&amp;#34;
notify_fault &amp;#34;/etc/keepalived/notify.sh fault&amp;#34;
}
博客作业：以上所有内容；
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux安全策略之tcp_wrapper</title><link>/2015/11/01/linux%E5%AE%89%E5%85%A8%E7%AD%96%E7%95%A5%E4%B9%8Btcp_wrapper/</link><pubDate>Sun, 01 Nov 2015 00:00:00 +0000</pubDate><guid>/2015/11/01/linux%E5%AE%89%E5%85%A8%E7%AD%96%E7%95%A5%E4%B9%8Btcp_wrapper/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;tcp_wrapper：
库文件：libwrap.so，tcp包装器；
判断一个服务程序是否能够由tcp_wrapper进行访问控制的方法：
(1) 动态链接至libwrap.so库；
ldd /PATH/TO/PROGRAM
libwrap.so
(2) 静态编译libwrap.so库文件至程序中：
strings /PATH/TO/PGRGRAM
hosts_access
配置文件：/etc/hosts.allow, /etc/hosts.deny
See &amp;#39;man 5 hosts_options&amp;#39; and &amp;#39;man 5 hosts_access&amp;#39; for information on rule syntax.
配置文件语法：
daemon_list : client_list[ : option : option ...]
daemon_list：程序文件名称列表
(1) 单个应用程序文件名；
(2) 程序文件名列表，以逗号分隔；
(3) ALL：所有受tcp_wrapper控制的应用程序文件；
client_list：
(1) 单个IP地址或主机名；
(2) 网络地址：n.n.n.n/m.m.m.m，n.n.n.；
(3) 内建的ACL:
ALL：所有客户端主机；
LOCAL：Matches any host whose name does not contain a dot character.
UNKNOWN
KNOWN
PARANOID
OPERATORS：
EXCEPT
list1 EXCEPT list2 EXCEPT list3
sshd: 172.16. EXCEPT 172.16.100. EXCEPT 172.16.100.68
[ : option : option ...]
deny：拒绝，主要用于hosts.allow文件中定义“拒绝”规则；
allow：允许，主要用于hosts.deny文件中定义”允许“规则；
spawn：生成，发起，触发执行用户指定的任意命令，此处通常用于记录日志；
vsftpd: 172.16. : spawn /bin/echo $(date) login attempt from %c to %s &amp;gt;&amp;gt; /var/log/tcp_wrapper.log
练习：仅开放本机的sshd服务给172.16.0.0/16网络中除了172.16.0.0/24网络中的主机之外的所有主机，但允许172.16.0.200访问； 每次的用户访问都要记录于日志文件中；
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>CentOS 7 PXE 无盘系统配置说明</title><link>/2015/10/13/centos-7-pxe-%E6%97%A0%E7%9B%98%E7%B3%BB%E7%BB%9F%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E/</link><pubDate>Tue, 13 Oct 2015 00:00:00 +0000</pubDate><guid>/2015/10/13/centos-7-pxe-%E6%97%A0%E7%9B%98%E7%B3%BB%E7%BB%9F%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E/</guid><description>&lt;p&gt;PXE无盘工作站系统是指由一台或多台“系统服务器”和多台“PXE客户端(无盘工作站)”通过 交换机 相连组成的局域网系统。&lt;/p&gt;
&lt;p&gt;&lt;img alt="xitong.png" loading="lazy" src="/2015/10/13/centos-7-pxe-%E6%97%A0%E7%9B%98%E7%B3%BB%E7%BB%9F%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E/xitong.png"&gt;&lt;/p&gt;
&lt;p&gt;系统服务器：通过DHCP+TFTP+NFS服务向无盘工作站提供系统支持&lt;/p&gt;
&lt;p&gt;　　· DHCP服务：向PXE客户端分发IP地址、子网掩码、网关等，并指定启动引导文件所在服务器（TFTP服务器）的地址和PXE启动文件（pxelinux.0）&lt;/p&gt;
&lt;p&gt;　　· TFTP服务：向PXE客户端传输PXE启动文件、PXE引导配置文件、linux内核vmlinuz，以及系统启动文件initrd.img&lt;/p&gt;
&lt;p&gt;　　· NFS服务：向PXE客户端发布工作站的系统（整个根目录“/”的克隆）；为了避免磁盘IO资源的冲突，建议将克隆的系统部署在存储服务器上&lt;/p&gt;
&lt;p&gt;PXE客户端：PXE客户端无需硬盘，只需要一块支持PXE启动的网卡,将“网卡启动”设置为首选&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;操作系统&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CentOS 7.2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;软件环境&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;tftp-server-5.2-12.el7.x86_64&lt;/td&gt;
&lt;td&gt;向无盘工作站传输系统启动文件等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nfs-utils-1.3.0-0.21.el7.x86_64&lt;/td&gt;
&lt;td&gt;共享发布工作站系统&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;syslinux-4.05-12.el7.x86_64&lt;/td&gt;
&lt;td&gt;提供引导程序&amp;quot;pxelinux.0&amp;quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dhcp-4.2.5-42.el7.centos.x86_64&lt;/td&gt;
&lt;td&gt;提供DHCP 服务；指定TFTP 地址及PXE 启动文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dracut-033-359.el7.x86_64&lt;/td&gt;
&lt;td&gt;用来制作启动initrd 镜像&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dracut-network-033-359.el7.x86_64&lt;/td&gt;
&lt;td&gt;依赖包，否则将导致PXE无法启动&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="首先克隆好工作站的系统模板"&gt;首先，克隆好工作站的系统模板&lt;/h3&gt;
&lt;p&gt;配置之前关掉防火墙和selinux&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl stop firewalld #我还是比较习惯用iptables 所以关掉了 firewalld
systemctl disable firewalld
iptables -F #清空防火墙策略，如果之前有布置防火墙策略的需要查看下是否真要清空
iptables -vnL #查看filter防火墙策略
setenforce 0 #临时关闭selinux，重启后失效
vim /etc/sysconfig/selinux
SELINUX=disabled #关闭selinux，重启后生效
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;\1. 创建工作站系统模板的存放目录（/nodiskos/workstation）和启动引导文件存放目录（/nodiskos/tftpboot）&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir /nodiskos # 系统模板+启动文件存放目录
mkdir /nodiskos/tftpboot # 工作站系统启动文件存放目录
mkdir /nodiskos/workstation # 工作站系统模板存放目录
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;\2. 使用rsync 工具将整个&amp;quot;/&amp;ldquo;目录拷贝到/nodiskos/workstation 目录下，去除不需要的文件目录&lt;/p&gt;</description></item><item><title>Linux负载均衡之lvs</title><link>/2015/10/13/linux%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E4%B9%8Blvs/</link><pubDate>Tue, 13 Oct 2015 00:00:00 +0000</pubDate><guid>/2015/10/13/linux%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E4%B9%8Blvs/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;单台主机的处理能力是有限的，一台主机同时处理500个动态网页请求已经是极限了。这里有两种方法提升处理性能，一，定制高性能的计算机（Scale UP），众所周知计算机硬件设计成本极其高，性能每提高10%成本就要增加一倍，这也是intel被称为挤牙膏公司的原因，不是其研发能力不行而是计算机硬件性能提升的难度太大，这个方法貌似不可行。二，使用两台主机处理业务，这就会引来一个问题怎么把请求分发到多个主机（Scale Out），这里我们引入了负载均衡器。
使用负载均衡有两个方法，一是采用商用负载均衡硬件的，这个方法是最好的方法，能上硬件一定不要使用软件，又是硬件同样的问题也是价格，只要是涉及到基础设施的服务一定要使用。一方面是硬件的稳定性，比软件好很多，虽然软件可维护性和拓展性比较好，但是对于一些金融公司，他们玩的就是公信力和大众信心，每个故障都是竞争对手拆台的理由，一旦故障硬件背锅，理赔。常用的硬件F5的Big-IP，Citrix的Netscaler，A10的A10
另一种是采用软件负载均衡，只要考虑成本使用负载均衡软件就对了，它的性能比硬件也不会差别太大，并且灵活。
在做负载均衡的时候，我们在7层模型的哪一层做负载均衡，ip层基于主机做负载均衡粒度太大可用性太差基本不使用，更低的层基本上没有价值了，tcp层的话我们就可以把不同服务的请求采取不同的方式负载均衡，应用层的话我们可以根据具体协议拆包分析后负载均衡，既然涉及到拆开数据包，那处理的数据量就会大大增加，那么对计算机性能要求也越高，性能就越差，并且负载均衡器的通用性也越差。
传输层：lvs，nginx：(stream)，haproxy：(mode tcp)
应用层：
http：nginx, httpd, haproxy(mode http)
fastcgi：nginx, httpd
mysql：mysql-proxy
Linux Cluster：
Cluster：计算机集合，为解决某个特定问题组合起来形成的单个系统；
Linux Cluster类型：
LB：Load Balancing，负载均衡；
HA：High Availiablity，高可用；
A=MTBF/（MTBF+MTTR），MTBF:mean time bettween failure,平均工作时长或者平均故障间隔时间。MTTR:mean time to restoration/repair，平局修复时长
(0,1)：90%, 95%, 99%, 99.5%, 99.9%, 99.99%, 99.999%, 99.9999%
HP：High Performance，高性能；
分布式系统：
分布式存储
分布式计算
系统扩展方式：
Scale UP：向上扩展
Scale Out：向外扩展，增加计算机数量，也就形成了集群
LB Cluster：
LB Cluster的实现：
硬件：
F5 Big-IP
Citrix Netscaler
A10 A10
软件：
lvs：Linux Virtual Server
nginx
haproxy
ats：apache traffic server
perlbal
pound
基于工作的协议层次划分：
传输层（通用）：（DPORT）
lvs：
nginx：（stream）
haproxy：（mode tcp）
应用层（专用）：（自定义的请求模型分类）
proxy sferver：
http：nginx, httpd, haproxy(mode http), ...
fastcgi：nginx, httpd, ...
mysql：mysql-proxy, ...
...
站点指标：
PV：Page View
UV：Unique Vistor
IP：
会话保持：
(1) session sticky
Source IP
Cookie
(2) session replication;
session cluster
(3) session server
lvs：Linux Virtual Server 作者：章文嵩；alibaba --&amp;gt; didi
VS: Virtual Server
RS: Real Server
lvs: ipvsadm/ipvs 类似于 iptables/netfilter
ipvsadm：用户空间的命令行工具，规则管理器，用于管理集群服务及RealServer；
ipvs：工作于内核空间的netfilter的INPUT钩子之上的框架；
lvs集群类型中的术语：
vs：Virtual Server, Director, Dispatcher, Balancer
rs：Real Server, upstream server, backend server
CIP：Client IP, VIP: Virtual serve IP, RIP: Real server IP, DIP: Director IP
CIP &amp;lt;--&amp;gt; VIP == DIP &amp;lt;--&amp;gt; RIP
lvs集群的类型：
lvs-nat：修改请求报文的目标IP；多目标IP的DNAT；
lvs-dr：操纵封装新的MAC地址；
lvs-tun：在原请求IP报文之外新加一个IP首部；
lvs-fullnat：修改请求报文的源和目标IP；
lvs-nat：
多目标IP的DNAT，通过将请求报文中的目标地址和目标端口修改为某挑出的RS的RIP和PORT实现转发；
（1）RIP和DIP必须在同一个IP网络，且应该使用私网地址；RS的网关要指向DIP；
（2）请求报文和响应报文都必须经由Director转发；Director易于成为系统瓶颈；
（3）支持端口映射，可修改请求报文的目标PORT；
（4）vs必须是Linux系统，rs可以是任意系统；
lvs-dr：
Direct Routing，直接路由；
通过为请求报文重新封装一个MAC首部进行转发，源MAC是DIP所在的接口的MAC，目标MAC是某挑选出的RS的RIP所在接口的MAC地址；源IP/PORT，以及目标IP/PORT均保持不变；
Director和各RS都得配置使用VIP；
(1) 确保前端路由器将目标IP为VIP的请求报文发往Director：
(a) 在前端网关做静态绑定；
(b) 在RS上使用arptables；
(c) 在RS上修改内核参数以限制arp通告及应答级别；
arp_announce
arp_ignore
(2) RS的RIP可以使用私网地址，也可以是公网地址；RIP与DIP在同一IP网络；RIP的网关不能指向DIP，以确保响应报文不会经由Director；
(3) RS跟Director要在同一个物理网络；
(4) 请求报文要经由Director，但响应不能经由Director，而是由RS直接发往Client；
(5) 不支持端口映射；
lvs-tun：
转发方式：不修改请求报文的IP首部（源IP为CIP，目标IP为VIP），而在原IP报文之外再封装一个IP首部（源IP是DIP，目标IP是RIP），将报文发往挑选出的目标RS；RS直接响应给客户端（源IP是VIP，目标IP是CIP）；
(1) DIP, VIP, RIP都应该是公网地址；
(2) RS的网关不能，也不可能指向DIP；
(3) 请求报文要经由Director，但响应不能经由Director；
(4) 不支持端口映射；
(5) RS的OS得支持隧道功能；
lvs-fullnat：
通过同时修改请求报文的源IP地址和目标IP地址进行转发；
CIP --&amp;gt; DIP
VIP --&amp;gt; RIP
(1) VIP是公网地址，RIP和DIP是私网地址，且通常不在同一IP网络；因此，RIP的网关一般不会指向DIP；
(2) RS收到的请求报文源地址是DIP，因此，只需响应给DIP；但Director还要将其发往Client；
(3) 请求和响应报文都经由Director；
(4) 支持端口映射；
注意：此类型默认不支持；
总结：
lvs-nat, lvs-fullnat：请求和响应报文都经由Director；
lvs-nat：RIP的网关要指向DIP；
lvs-fullnat：RIP和DIP未必在同一IP网络，但要能通信；
lvs-dr, lvs-tun：请求报文要经由Director，但响应报文由RS直接发往Client；
lvs-dr：通过封装新的MAC首部实现，通过MAC网络转发；
lvs-tun：通过在原IP报文之外封装新的IP报文实现转发，支持远距离通信；
ipvs scheduler：
根据其调度时是否考虑各RS当前的负载状态，可分为静态方法和动态方法两种：
静态方法：仅根据算法本身进行调度；
RR：roundrobin，轮询；
WRR：Weighted RR，加权轮询；
SH：Source Hashing，实现session sticy，源IP地址hash；将来自于同一个IP地址的请求始终发往第一次挑中的RS，从而实现会话绑定；
DH：Destination Hashing；目标地址哈希，将发往同一个目标地址的请求始终转发至第一次挑中的RS，典型使用场景是正向代理缓存场景中的负载均衡；
动态方法：主要根据每RS当前的负载状态及调度算法进行调度；
Overhead=
LC：least connections 选中最少连接数的RS服务器
Overhead=activeconns*256+inactiveconns
WLC：Weighted LC 默认调度方法
Overhead=(activeconns*256+inactiveconns)/weight
SED：Shortest Expection Delay 最短期望延迟，
Overhead=(activeconns+1)*256/weight
NQ：Never Queue 永不排队，权重小的不会空闲
LBLC：Locality-Based LC，动态的DH算法；
LBLCR：LBLC with Replication，带复制功能的LBLC；
ipvsadm/ipvs：
ipvs：
~]# grep -i -C 10 &amp;#34;ipvs&amp;#34; /boot/config-VERSION-RELEASE.x86_64
支持的协议：TCP， UDP， AH， ESP， AH_ESP, SCTP；
ipvs集群：
集群服务
服务上的RS
ipvsadm：
程序包：ipvsadm
Unit File: ipvsadm.service
主程序：/usr/sbin/ipvsadm
规则保存工具：/usr/sbin/ipvsadm-save
规则重载工具：/usr/sbin/ipvsadm-restore
配置文件：/etc/sysconfig/ipvsadm-config
ipvsadm命令：
核心功能：
集群服务管理：增、删、改；
集群服务的RS管理：增、删、改；
查看：
ipvsadm -A|E -t|u|f service-address [-s scheduler] [-p [timeout]] [-M netmask] [--pe persistence_engine] [-b sched-flags]
ipvsadm -D -t|u|f service-address
ipvsadm -C
ipvsadm -R
ipvsadm -S [-n]
ipvsadm -a|e -t|u|f service-address -r server-address [options]
ipvsadm -d -t|u|f service-address -r server-address
ipvsadm -L|l [options]
ipvsadm -Z [-t|u|f service-address]
管理集群服务：增、改、删；
增、改：
ipvsadm -A|E -t|u|f service-address [-s scheduler] [-p [timeout]]
删：
ipvsadm -D -t|u|f service-address
service-address：
-t|u|f：
-t: TCP协议的端口，VIP:TCP_PORT
-u: TCP协议的端口，VIP:UDP_PORT
-f：firewall MARK，是一个数字；
[-s scheduler]：指定集群的调度算法，默认为wlc；
管理集群上的RS：增、改、删；
增、改：
ipvsadm -a|e -t|u|f service-address -r server-address [-g|i|m] [-w weight]
删：
ipvsadm -d -t|u|f service-address -r server-address
server-address：
rip[:port]
选项：
lvs类型：
-g: gateway, dr类型
-i: ipip, tun类型
-m: masquerade, nat类型
-w weight：权重；
清空定义的所有内容：
ipvsadm -C
查看：
ipvsadm -L|l [options]
--numeric, -n：numeric output of addresses and ports
--exact：expand numbers (display exact values)
--connection， -c：output of current IPVS connections
--stats：output of statistics information
--rate ：output of rate information
保存和重载：
ipvsadm -S = ipvsadm-save
ipvsadm -R = ipvsadm-restore
负载均衡集群设计时要注意的问题：
(1) 是否需要会话保持；
(2) 是否需要共享存储；
共享存储：NAS， SAN， DS（分布式存储）
数据同步：
课外作业：rsync+inotify实现数据同步
lvs-nat：
设计要点：
(1) RIP与DIP在同一IP网络, RIP的网关要指向DIP；
(2) 支持端口映射；
(3) Director要打开核心转发功能；
ipvsadm -A -t 172.16.252.127:80 -s rr // -s rr 课不写 默认为wlc
ipvsadm -a -t 172.16.252.127:80 -r 192.168.0.5:80 -m //[-w weight_num]
ipvsadm -a -t 172.16.252.127:80 -r 192.168.0.6:80 -m //[-w weight_num]
ipvsadm -L
echo 1 &amp;gt; /proc/sys/net/ipv4/ip_forward 转发功能打开
ifconfig eth0 192.168.191.131/24
route add default gw 192.168.191.1
测试：
for i in {1..10} ; do curl 172.16.252.127 ; done
实践作业（博客）：负载均衡一个php应用（wordpress）；
测试：(1) 是否需要会话保持；(2) 是否需要共享存储；
Demo：
lvs-dr：
dr模型中，各主机上均需要配置VIP，解决地址冲突的方式有三种：
(1) 在前端网关做静态绑定；
(2) 在各RS使用arptables；
(3) 在各RS修改内核参数，来限制arp响应和通告的级别；
限制响应级别：arp_ignore
0：默认值，表示可使用本地任意接口上配置的任意地址进行响应；
1: 仅在请求的目标IP配置在本地主机的接收到请求报文接口上时，才给予响应；
限制通告级别：arp_announce
0：默认值，把本机上的所有接口的所有信息向每个接口上的网络进行通告；
1：尽量避免向非直接连接网络进行通告；
2：必须避免向非本网络通告；
RS的预配置脚本：
#!/bin/bash
#
vip=10.1.0.5
mask=&amp;#39;255.255.255.255&amp;#39;
case $1 in
start)
echo 1 &amp;gt; /proc/sys/net/ipv4/conf/all/arp_ignore
echo 1 &amp;gt; /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 &amp;gt; /proc/sys/net/ipv4/conf/all/arp_announce
echo 2 &amp;gt; /proc/sys/net/ipv4/conf/lo/arp_announce
ifconfig lo:0 $vip netmask $mask broadcast $vip up
route add -host $vip dev lo:0
;;
stop)
ifconfig lo:0 down
echo 0 &amp;gt; /proc/sys/net/ipv4/conf/all/arp_ignore
echo 0 &amp;gt; /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 0 &amp;gt; /proc/sys/net/ipv4/conf/all/arp_announce
echo 0 &amp;gt; /proc/sys/net/ipv4/conf/lo/arp_announce
;;
*)
echo &amp;#34;Usage $(basename $0) start|stop&amp;#34;
exit 1
;;
esac
VS的配置脚本：
#!/bin/bash
#
vip=&amp;#39;10.1.0.5&amp;#39;
iface=&amp;#39;eno16777736:0&amp;#39;
mask=&amp;#39;255.255.255.255&amp;#39;
port=&amp;#39;80&amp;#39;
rs1=&amp;#39;10.1.0.7&amp;#39;
rs2=&amp;#39;10.1.0.8&amp;#39;
scheduler=&amp;#39;wrr&amp;#39;
type=&amp;#39;-g&amp;#39;
case $1 in
start)
ifconfig $iface $vip netmask $mask broadcast $vip up
iptables -F
ipvsadm -A -t ${vip}:${port} -s $scheduler
ipvsadm -a -t ${vip}:${port} -r ${rs1} $type -w 1
ipvsadm -a -t ${vip}:${port} -r ${rs2} $type -w 1
;;
stop)
ipvsadm -C
ifconfig $iface down
;;
*)
echo &amp;#34;Usage $(basename $0) start|stop&amp;#34;
exit 1
;;
esac
课外扩展作业：vip与dip/rip不在同一网段的实验环境设计及配置实现；
博客作业：lvs的详细应用
讲清楚类型、调度方法；并且给出nat和dr类型的设计拓扑及具体实现；
FWM：FireWall Mark
netfilter：
target: MARK, This target is used to set the Netfilter mark value associated with the packet.
--set-mark value
借助于防火墙标记来分类报文，而后基于标记定义集群服务；可将多个不同的应用使用同一个集群服务进行调度；
打标记方法（在Director主机）：
# iptables -t mangle -A PREROUTING -d $vip -p $proto --dport $port -j MARK --set-mark NUMBER
基于标记定义集群服务：
# ipvsadm -A -f NUMBER [options]
lvs persistence：持久连接
持久连接模板：实现无论使用任何调度算法，在一段时间内，能够实现将来自同一个地址的请求始终发往同一个RS；
ipvsadm -A|E -t|u|f service-address [-s scheduler] [-p [timeout]]
port Affinity：
每端口持久：每个端口对应定义为一个集群服务，每集群服务单独调度；
每防火墙标记持久：基于防火墙标记定义集群服务；可实现将多个端口上的应用统一调度，即所谓的port Affinity；
每客户端持久：基于0端口定义集群服务，即将客户端对所有应用的请求统统调度至后端主机，必须定义为持久模式；
保存及重载规则：
保存：建议保存至/etc/sysconfig/ipvsadm
ipvsadm-save &amp;gt; /PATH/TO/IPVSADM_FILE
ipvsadm -S &amp;gt; /PATH/TO/IPVSADM_FILE
systemctl stop ipvsadm.service
重载：
ipvsadm-restore &amp;lt; /PATH/FROM/IPVSADM_FILE
ipvsadm -R &amp;lt; /PATH/FROM/IPVSADM_FILE
systemctl restart ipvsadm.service
考虑：
(1) Director不可用，整个系统将不可用；SPoF
解决方案：高可用
keepalived
heartbeat/corosync
(2) 某RS不可用时，Director依然会调度请求至此RS；
解决方案：对各RS的健康状态做检查，失败时禁用，成功时启用；
keepalived
heartbeat/corosync, ldirectord
检测方式：
(a) 网络层检测；
(b) 传输层检测，端口探测；
(c) 应用层检测，请求某关键资源；
ok --&amp;gt; failure
failure --&amp;gt; ok
ldirectord：
Daemon to monitor remote services and control Linux Virtual Server. ldirectord is a daemon to monitor and administer real servers in a cluster of load balanced virtual servers. ldirectord typically is started from heartbeat but can also be run from the command line.
配置示例：
checktimeout=3
checkinterval=1
fallback=127.0.0.1:80
autoreload=yes
logfile=&amp;#34;/var/log/ldirectord.log&amp;#34;
quiescent=no
virtual=5
real=172.16.0.7:80 gate 2
real=172.16.0.8:80 gate 1
fallback=127.0.0.1:80 gate
service=http
scheduler=wrr
checktype=negotiate
checkport=80
request=&amp;#34;index.html&amp;#34;
receive=&amp;#34;CentOS&amp;#34;
补充：共享存储
NAS：Network Attached Storage
nfs/cifs
文件系统接口
SAN：Storage Area Network
“块”接口
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux负载均衡之lvs-dr模式</title><link>/2015/10/13/linux%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E4%B9%8Blvs-dr%E6%A8%A1%E5%BC%8F/</link><pubDate>Tue, 13 Oct 2015 00:00:00 +0000</pubDate><guid>/2015/10/13/linux%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E4%B9%8Blvs-dr%E6%A8%A1%E5%BC%8F/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lvs-dr：
dr模型中，各主机上均需要配置VIP，解决地址冲突的方式有三种：
(1) 在前端网关做静态绑定；
(2) 在各RS使用arptables；
(3) 在各RS修改内核参数，来限制arp响应和通告的级别；
限制响应级别：arp_ignore
0：默认值，表示可使用本地任意接口上配置的任意地址进行响应；
1: 仅在请求的目标IP配置在本地主机的接收到请求报文接口上时，才给予响应；
限制通告级别：arp_announce
0：默认值，把本机上的所有接口的所有信息向每个接口上的网络进行通告；
1：尽量避免向非直接连接网络进行通告；
2：必须避免向非本网络通告；
RS的预配置脚本：
#!/bin/bash
#
vip=10.1.0.5
mask=&amp;#39;255.255.255.255&amp;#39;
case $1 in
start)
echo 1 &amp;gt; /proc/sys/net/ipv4/conf/all/arp_ignore
echo 1 &amp;gt; /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 &amp;gt; /proc/sys/net/ipv4/conf/all/arp_announce
echo 2 &amp;gt; /proc/sys/net/ipv4/conf/lo/arp_announce
ifconfig lo:0 $vip netmask $mask broadcast $vip up
route add -host $vip dev lo:0//当发往本机的请求报文目标地址为 vip 的时候，本机的响应报文从lo:o应答
;;
stop)
ifconfig lo:0 down
echo 0 &amp;gt; /proc/sys/net/ipv4/conf/all/arp_ignore
echo 0 &amp;gt; /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 0 &amp;gt; /proc/sys/net/ipv4/conf/all/arp_announce
echo 0 &amp;gt; /proc/sys/net/ipv4/conf/lo/arp_announce
;;
*)
echo &amp;#34;Usage $(basename $0) start|stop&amp;#34;
exit 1
;;
esac
VS
ifconfig eno16777736:0 172.16.0.50 netmask 255.255.255.255 broadcast 172.16.0.50 up
vip=172.16.0.50
VS的配置脚本：
#!/bin/bash
#
vip=&amp;#39;10.1.0.5&amp;#39;
iface=&amp;#39;eno16777736:0&amp;#39;
mask=&amp;#39;255.255.255.255&amp;#39;
port=&amp;#39;80&amp;#39;
rs1=&amp;#39;10.1.0.7&amp;#39;
rs2=&amp;#39;10.1.0.8&amp;#39;
scheduler=&amp;#39;wrr&amp;#39;
type=&amp;#39;-g&amp;#39;
case $1 in
start)
ifconfig $iface $vip netmask $mask broadcast $vip up
iptables -F
ipvsadm -A -t ${vip}:${port} -s $scheduler
ipvsadm -a -t ${vip}:${port} -r ${rs1} $type -w 1
ipvsadm -a -t ${vip}:${port} -r ${rs2} $type -w 1
;;
stop)
ipvsadm -C
ifconfig $iface down
;;
*)
echo &amp;#34;Usage $(basename $0) start|stop&amp;#34;
exit 1
;;
esac
课外扩展作业：vip与dip/rip不在同一网段的实验环境设计及配置实现；
博客作业：lvs的详细应用
讲清楚类型、调度方法；并且给出nat和dr类型的设计拓扑及具体实现；
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>PXE配置文档</title><link>/2015/10/13/pxe%E9%85%8D%E7%BD%AE%E6%96%87%E6%A1%A3/</link><pubDate>Tue, 13 Oct 2015 00:00:00 +0000</pubDate><guid>/2015/10/13/pxe%E9%85%8D%E7%BD%AE%E6%96%87%E6%A1%A3/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;目前所做为测试，即在虚拟机上的测试。先简单介绍一下整体环境，两台centos7.2系统，7-2-a（安装vsftp tftp）和7-2-b（安装dhcp服务）&lt;/p&gt;
&lt;p&gt;在7-2-a ip:192.168.199.149 的操作：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl stop firewalld
setenforce 0
yum install -y vsftpd tftp-server syslinux
cp -a /misc/cd/* /var/ftp
cp /root/anaconda-ks.cfg /var/ftp/ks.cfg
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;//这里ks.cfg需要根据自己需求简单修改配置，后面会说明用system-config-kickstart 如何配置ks.cfg&lt;/p&gt;
&lt;p&gt;内容修改如下&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#platform=x86, AMD64, or Intel EM64T
#version=DEVEL
# Install OS instead of upgrade
install
# Keyboard layouts
keyboard &amp;#39;us&amp;#39;
# Root password
rootpw --iscrypted $1$gfl.Vz5t$mqzEhPMYDiANGwDB8gj/E/
# System timezone
timezone Asia/Shanghai
user --name=superman --password=$6$cq8y5jAZQCGd4muD$8G7zz.fpZFK3rI0AA7dyAxeD2B3mIjgu1HY/yeOm.PdsYHAYsD77K8uXTjbEPvJs9z7M6s0TECr6FtL2Bc5to/ --iscrypted --gecos=&amp;#34;superman&amp;#34; #为服务器添加一个用户
# Use network installation
url --url=&amp;#34;ftp://192.168.199.149&amp;#34;
# System language
lang en_US
# Firewall configuration
firewall --disabled
# System authorization information
auth --useshadow --passalgo=sha512
# Use text mode install
text
firstboot --enable
# SELinux configuration
selinux --disabled
# Network information
network --bootproto=dhcp --device=eno16777736 --ipv6=auto --activate
network --hostname=7-2-test
# Reboot after installation
reboot
# System bootloader configuration
bootloader --location=mbr --boot-drive=sda
# Partition clearing information
clearpart --all --initlabel
# Disk partitioning information
part / --fstype=&amp;#34;xfs&amp;#34; --size=12040
part /boot --fstype=&amp;#34;xfs&amp;#34; --size=10240
part /home --fstype=&amp;#34;xfs&amp;#34; --size=10240
part /var --fstype=&amp;#34;xfs&amp;#34; --size=20480
part /usr --fstype=&amp;#34;xfs&amp;#34; --size=10240
part /tmp --fstype=&amp;#34;xfs&amp;#34; --size=10240
%packages
@^minimal
@core
@ftp-server
%end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;配置tftp-server&lt;/p&gt;</description></item><item><title>Linux程序包的编译安装</title><link>/2015/09/20/linux%E7%A8%8B%E5%BA%8F%E5%8C%85%E7%9A%84%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85/</link><pubDate>Sun, 20 Sep 2015 00:00:00 +0000</pubDate><guid>/2015/09/20/linux%E7%A8%8B%E5%BA%8F%E5%8C%85%E7%9A%84%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在有些源代码程序没有被编译成rpm的时候，或者其他人写了一个源代码程序，要把它安装在服务器上要怎么做呢？
那就需要对源代码进行编译安装了。
C代码编译安装三步骤：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1、./configure：
(1)通过选项传递参数，指定启用特性、安装路径等；执行时会参考用户的指定以及makefile.in文件生成makefile
(2) 检查依赖到的外部环境，如依赖的软件包
2、make：根据makefile文件，构建应用程序
3、make install:复制文件到相应路径
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开发工具：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;autoconf: 生成configure脚本
automake：生成Makefile.in
注意：安装前查看INSTALL，README
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例子:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;下面就以编译安装HTTP服务为例进行说明：
首先要 获取到HTTP源代码，此处通过FTP获取
yum install lftp 安装FTP--源代码获取工具
ftp://172.16.0.1/pub/Sources/sources/httpd/httpd-2.2.29.tar.bz2 --下载源代码
tar xvf httpd-2.2.29.tar.bz2 --解压源代码--&amp;gt; 得到http-2.2.29 目录
由于要用到autoconf和automake工具 因此可以安装 工具包
yum groupinstall &amp;#34;Development Tools&amp;#34;
安装工具安装好了之后进入http目录执行 ./configure命令，注意这个命令要指定该软件安装的程序所在目录，以及配置文件所在目录。所以执行./configure命令之前要先看一下目录下的INSTALL、README两个文件
http服务中--prefix=程序安装目录 --sysconfdir=配置文件目录 如果不加这两个选项，是有默认安装路径的。在man帮助中有，接下来执行：
./configure --prefix=/usr/local/huyuhttp --sysconfdir=/etc/huyuhttp/
make 和 make install
这样呢其实已经安装完了http服务，但是此时http还没有启动，帮助文档还不能方便查看，调用的库也不行，共享库有哪些也不知道，所以要进行接下来的配置。
1、在/etc/profile.d/目录下创建一个以.sh结尾的文件，修改path变量，加入http二进制程序路径，这样就可以不用加路径直接执行http程序了。
vim /etc/profile.d/huyuhttp.sh
PATH=$PATH:/usr/local/huyuhttp/bin
. /etc/profile.d/huyuhttp.sh 刷新path变量值
2、添加man手册，可以方便查看帮助
vim /etc/man_db.conf /usr/local/huyuhttp/man
3、添加lib图文件目录
vim /etc/ld.so.conf.d/huyuhttp.conf /usr/local/huyuhttp/lib
ldconfig 刷新库文件缓存
4、添加头文件，这里只需要在include的目录中加入http的include目录链接即可
ln -s /usr/local/huyuhttp/include/ /usr/include/huyuhttp
5、关闭防火墙
iptables -F
6、开启http
apachectl start
7、查看端口是否打开
netstat -ant 查看80
8、测试查看网页
links ip
curl ip
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>dd命令制作U盘启动</title><link>/2015/09/19/dd%E5%91%BD%E4%BB%A4%E5%88%B6%E4%BD%9Cu%E7%9B%98%E5%90%AF%E5%8A%A8/</link><pubDate>Sat, 19 Sep 2015 00:00:00 +0000</pubDate><guid>/2015/09/19/dd%E5%91%BD%E4%BB%A4%E5%88%B6%E4%BD%9Cu%E7%9B%98%E5%90%AF%E5%8A%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1、插入U盘，df -h查看U盘文件系统挂载情况，然后使用umount /dev/sdb*卸载U盘文件系统；
2、执行命令：sudo mkfs.vfat -I /dev/sdb格式化U盘为FAT格式；
3、dd if=*/*.iso   of=/dev/sdb  bs=4M  (数据块大小，每个数据块只能存一个文件的数据)
4、执行sync，同步缓存中的数据至U盘；
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux sed</title><link>/2015/09/13/linux-sed/</link><pubDate>Sun, 13 Sep 2015 00:00:00 +0000</pubDate><guid>/2015/09/13/linux-sed/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="sed--stream-editor"&gt;sed : Stream EDitor&lt;/h1&gt;
&lt;p&gt;sed是一种流编辑器，它一次处理一行内容。处理时，把当前处理的行存储在临时缓冲区中，称为“模式空间”（pattern space），接着用sed命令处理缓冲区中的内容，处理完成后，把缓冲区的内容送往屏幕。接着处理下一行，这样不断重复，直到文件末尾。文件内容并没有改变，除非你使用重定向存储输出。Sed主要用来自动编辑一个或多个文件,简化对文件的反复操作,编写转换程序等&lt;/p&gt;
&lt;h2 id="用法"&gt;用法&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sed [option]... &amp;#39;script&amp;#39; inputfile...
常用选项：
-n：不输出模式空间内容到屏幕，即不自动打印,默认在输出前会打印源内容
-e: 多点编辑
-f：/PATH/SCRIPT_FILE: 从指定文件中读取编辑脚本
-r: 支持使用扩展正则表达式
-i: 原处编辑
script:
&amp;#39;地址命令&amp;#39;= [地址定界] + 编辑命令 + [替换标记]
地址定界：
(1) 不给地址：对全文进行处理
(2) 单地址：
#: 指定的行
/pattern/：被此处模式所能够匹配到的每一行
(3) 地址范围：
#,#
#,+#
/pat1/,/pat2/
#,/pat1/
(4) ~：步进
1~2 奇数行
2~2 偶数行
编辑命令：
d: 删除模式空间匹配的行
p: 显示模式空间中的内容
a [\]text：在指定行后面追加文本，支持使用\n实现多行追加
i[\]text：在行前面插入文本
c [\]text：替换行为单行或多行文本
w /path/somefile: 保存模式匹配的行至指定文件
r /path/somefile：读取指定文件的文本至模式空间中匹配到的行后
=: 为模式空间中的行打印行号
!:模式空间中匹配行取反处理
s///：查找替换,支持使用其它分隔符，s@@@，s###
替换标记：
g: 行内全局替换
p: 显示替换成功的行
w /PATH/TO/SOMEFILE：将替换成功的行保存至文件中
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="实例"&gt;实例&lt;/h2&gt;
&lt;p&gt;例子来啦!没有例子呢,的确是很难懂的.那么下面就尽量给多一点例子,来详细分析上面的意思.&lt;/p&gt;</description></item><item><title>CentOS系统yum的配置</title><link>/2015/08/25/centos%E7%B3%BB%E7%BB%9Fyum%E7%9A%84%E9%85%8D%E7%BD%AE/</link><pubDate>Tue, 25 Aug 2015 00:00:00 +0000</pubDate><guid>/2015/08/25/centos%E7%B3%BB%E7%BB%9Fyum%E7%9A%84%E9%85%8D%E7%BD%AE/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;yum仓库使用起来特别方便，然而使用之前当然是要配置的啦。下面就介绍一下怎么从0 配置一个yum仓库。
首先要创建yum仓库，当然不能使只给一台服务器用，那要给多个服务器用的话，就需要网络服务。yum仓库支持的网络服务有两种，FTP和HTTP。用yum主要用到的是数据传输，因此FTP更适合创建yum仓库，下面就以FTP为例，说明一下yum仓库的配置。
第一步是要先在自己的服务器上提供FTP服务，而这个时候还没有yum仓库，所以只能使用rpm的方式安装vsftp，把FTP服务搭起来。这个包必须要提前有，或者从光盘中获取。
获取安装 FTP服务 并启用&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir /mnt/cdrom
mount /dev/cdrom /mnt/cdrom
rpm -ivh /mnt/cdrom Packages/vsftpd-3.0.2-10.el7.x86_64.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;注意，在CentOS 6 和CentOS 7 上开启FTP服务的命令式不一样的。
iptables -F（在CentOS 7 上 也可以使用systemctl stop firewalld 来关闭防火墙）
CentOS-6 开启FTP服务
chkconfig vsftpd on === 立即打开FTP，但重启后失效
service vsftpd start === 开启FTP服务，但不会立即生效
CentOS-7 开启FTP服务
systemctl enable vsftpd === 立即打开FTP，但重启后失效
systemctl start vsftpd === 开启FTP服务，但不会立即生效&lt;/p&gt;
&lt;p&gt;到此为止FTP服务就开启啦，在一个局域网内的电脑只需要在浏览器里面输入 ftp://ip 就可以访问这个服务器啦
但是里面说明内容的没有，顶多只有一个lost+found 和pub 的文件夹，这个目录就是本机的/var/ftp/ 共享文件要放到这个文件里面。
需要注意一点，共享的文件所有FTP客户端都可以访问，所以我们这里如果创建yum仓库的话是可以给众多使用yum的系统提供yum仓库的，只不过不同系统例如CentOS 5 、CentOS 6.7 CentOS6.8 CentOS 7 等不同版本的系统使用的程序安装包不同，所以只需要在这里分类创建文件夹，供各类系统使用即可。
实例：&lt;/p&gt;</description></item><item><title>Linux查找命令find和locate</title><link>/2015/08/24/linux%E6%9F%A5%E6%89%BE%E5%91%BD%E4%BB%A4find%E5%92%8Clocate/</link><pubDate>Mon, 24 Aug 2015 00:00:00 +0000</pubDate><guid>/2015/08/24/linux%E6%9F%A5%E6%89%BE%E5%91%BD%E4%BB%A4find%E5%92%8Clocate/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;有些时候我们是想要在系统中查找某个具体的文件，却不知道路径在哪里，只是知道其中的某些特性，比如大小或者名字什么的。这时候就要用到查找工具啦。
在文件系统上查找符合条件的文件命令有两个，locate和find，其中locate是非实时查找即数据库查找。而find是实时查找&lt;/p&gt;
&lt;p&gt;locate：
用法：locate [OPTION]&amp;hellip; [PATTERN]&amp;hellip;
[option]
-i 不区分大小写
-n # 只列举前N个匹配项目
-r 使用扩展正则表达式 查询
locate foo 搜索名称或路径中带有foo的文件
locate -r &amp;lsquo;.foo$&amp;rsquo; 使用扩展正则表达式来搜索以 &amp;ldquo;.foo&amp;rdquo; 结尾的文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;查询系统上的文件索引数据库
/var/lib/mlocate/mlocate.db
依赖于实现构建的索引，
索引的构建是在系统较为空闲时自动进行的（周期性任务），也可管理员手动更新数据库（updatedb）
索引构建过程需要遍历整个根文件系统，极消耗资源
工作特点：
查找速度快（只查找数据库中有的数据）
模糊查找
非实时查找（在索引构建之后创建的文件时不能找到的）
搜索的是文件的全路径，不仅仅是文件名
可能只搜索用户具备读取和执行权限的目录
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;find：
find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path&amp;hellip;] [expression]
即：find [option] [查找路径] [查找条件] [处理动作]
查找路径：指定具体目标路径，【默认为当前目录】
查找条件：指定的查找标准，可以文件名、大小、类型、权限等。默认找出指定路径下的所有文件
处理动作：对符合条件的文件做操作，默认输出至屏幕
【查找条件】
根据文件名和inode查找：
-name “文件名称”： 支持使用glob （文件名通配符） &lt;em&gt;，？，[],[^]
-iname “文件名称” ： 不区分字母大小写
inum n 按inode号查找
-samefile name 相同inode号的文件
-links n 链接数为n的文件
-regex “PATTERN”：以pattern匹配整个文件路径字符串，而不仅仅是文件名称
根据属主属组查找：
-user username：
-group groupname：
-uid userid：
-gid groupid：
-nouser：查找没有属主的文件
-nogroup：查找没有属组的文件
根据文件类型查找：
-type type： f,d,l,s,b,c,p
根据组合条件查找：
-a -o -not=!
根据文件大小来查找：
-size [+|-]#UNIT (k,M,G)
#UNIT (#-1,#] &amp;ndash;&amp;gt; 如6k 即（5k,6k] 5k到6k 但不包括5k
-#UNIT:[0,#-1] &amp;ndash;&amp;gt;如6k 即[0,5k]
+#UNIT:(#,∞) 大于# 但不包括#
根据时间戳查找：
以 【天】为单位
-atime[+|-]# ： [0,#) [#,#+1) [#+1,∞]
-mtime[+|-]# ：
-ctime[+|-]# ：
以【分钟】为单位
-amin[+|-]# ：
-mmin[+|-]# ：
-cmin[+|-]# ：
根据权限查找：
-perm [/|-] mode
mode : 精确匹配，不能多不能少
/mode 或+mode ： 或关系 ugo中 只要有一个匹配即可
-mode : ugo必须至少拥有或比mode更多权限
【处理动作】
-print：默认处理动作
-ls：类似于对查找到的文件执行 ls -l
-delete:删除查找到的文件
-fls file：查找到的所有文件的长格式信息保存至指定文件中
-ok commond {} ; 对查找到的每个文件执行有commond指定的命令，每个文件执行前都要交互式确认
-exec commond {} ; 对查找到的每个文件执行有commond指定的命令
{}：用于引用查找到的文件名称自身
例：
find -name “&lt;/em&gt;.conf” -exec cp {} {}.orig ;
•备份配置文件，添加.orig这个扩展名
find /tmp -ctime +3 -user joe -ok rm {} ;
•提示删除存在时间超过３天以上的joe的临时文件
find ~ -perm -002 -exec chmod o-w {} \ ;
•在你的主目录中寻找可被其它用户写入的文件
find /data –type f -perm 644 -name “*.sh” –exec chmod 755 {} ;
•在 /data 目录中 找出所有 以.sh 结尾没有执行全选的文件 增加执行权限
find /home -type d -ls
找出所有/home 目录下的 目录 列出详细信息&lt;/p&gt;</description></item><item><title>Linux安装命令yum的使用</title><link>/2015/08/23/linux%E5%AE%89%E8%A3%85%E5%91%BD%E4%BB%A4yum%E7%9A%84%E4%BD%BF%E7%94%A8/</link><pubDate>Sun, 23 Aug 2015 00:00:00 +0000</pubDate><guid>/2015/08/23/linux%E5%AE%89%E8%A3%85%E5%91%BD%E4%BB%A4yum%E7%9A%84%E4%BD%BF%E7%94%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;YUM: yellowdog update modifier ，rpm的前端程序，用来解决软件包相关依赖性，可以在多个库之间定位软件包。
yum repository：yum repo，存储了众多RPM包，以及包相关的元数据文件，放置于特定目录repodata下。
yum 访问的文件服务器主要有三种，ftp，http，file。&lt;/p&gt;
&lt;p&gt;yum客户端配置文件：
【/etc/yum.conf】:为所有仓库提供公共配置,man /etc/yum.conf 有英文详解
[main]
cachedir=/var/cache/yum
//yum 缓存的目录，yum 在此存储下载的rpm 包和数据库，默认设置为/var/cache/yum
keepcache=0
　　//安装完成后是否保留软件包，0为不保留（默认为0），1为保留
debuglevel=2
　　//Debug 信息输出等级，范围为0-10，缺省为2
logfile=/var/log/yum.log
　　//yum 日志文件位置。用户可以到/var/log/yum.log 文件去查询过去所做的更新。
pkgpolicy=newest
//包的策略。一共有两个选项，newest 和last，这个作用是如果你设置了多个repository，而同一软件在不同的repository 中同时存在，yum 应该安装哪一个，如果是newest，则yum 会安装最新的那个版本。如果是last，则yum 会将服务器id 以字母表排序，并选择最后的那个服务器上的软件安装。一般都是选newest。
distroverpkg=centos-release
　　//指定一个软件包，yum 会根据这个包判断你的发行版本，默认是 centos-release，也可以是安装的任何针对自己发行版的rpm 包。
tolerant=1
//有1和0两个选项，表示yum 是否容忍命令行发生与软件包有关的错误，比如你要安装1,2,3三个包，而其中3此前已经安装了，如果你设为1,则yum 不会出现错误信息。默认是0。
exactarch=1
　　//有1和0两个选项，设置为1，则yum 只会安装和系统架构匹配的软件包，例如，yum 不会将i686的软件包安装在适合i386的系统中。默认为1。
retries=6
　　//网络连接发生错误后的重试次数，如果设为0，则会无限重试。默认值为6.
obsoletes=1
　　//这是一个update 的参数，具体请参阅yum(8)，简单的说就是相当于upgrade，允许更新陈旧的RPM包。
plugins=1
//是否启用插件，默认1为允许，0表示不允许。我们一般会用yum-fastestmirror这个插件。官方yum源的速度实在让人不敢恭维，而非官方的yum源又五花八门，让人难以取舍。幸运的是，yum-fastestmirror插件弥补了这一缺陷：自动选择最快的yum源。安装之后，生成配置文件/etc/yum/pluginconf.d/fastestmirror.conf。配置文件中的hostfilepath字段，用于定义yum源的配置文件（通常是/var/cache/yum/x86/7/timedhosts.txt），然后我们就可以将所知道的yum源统统写入这个txt文件
bugtracker_url=http://bugs.centos.org/set_project.php?project_id=16&amp;amp;ref=http://bugs.centos.org/bug_report_page.php?category=yum&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; metadata_expire=1h
installonly_limit = 5
【/etc/yum.repo.d/*.repo】:为仓库的指向提供配置 主要配置 [repositoryID] baseurl gpgcheck 三个即可使用仓库
[repositoryID] //仓库ID
name=Some name for this repository//仓库名称
baseurl=url://path/to/repository/ //仓库路径，路径必须给到 repodata文件夹所在的目录
URL可以有一下三种，file，ftp，http
enabled={1|0} //使能
gpgcheck={1|0} //是否检查来源合法性
gpgkey=URL //gpg证书来源路径
enablegroups={1|0
failovermethod={roundrobin|priority}
默认为：roundrobin，意为随机挑选；
cost= 默认为1000
yum的repo配置文件中可用的变量：
$releasever: 当前OS的发行版的主版本号
$arch: 平台，i386,i486,i586,x86_64等
$basearch：基础平台；i386
$YUM0-$YUM9:自定义变量
实例:
baseurl=http://server/centos/$releasever/$basearch/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;yum命令的用法：yum [options] [command] [package &amp;hellip;]
yum-config-manager &amp;ndash;add-repo= http://172.16.0.1/cobbler/ks_mirror/CentOS-X-x86_64/ 快速非交互式创建yum仓库
yum-config-manager &amp;ndash;disable “仓库名&amp;quot; 禁用仓库
yum-config-manager &amp;ndash;enable “仓库名” 启用仓库
yum repolist [all|enabled|disabled] 显示仓库 所有的|可用的|禁用的
yum list [all | glob_exp1] [glob_exp2] [&amp;hellip;] 根据需求筛选需要的RPM包
yum list {available|installed|updates} [glob_exp1]
yum install package1 [package2] [&amp;hellip;]
yum reinstall package1 [package2] [&amp;hellip;] 重新安装
yum remove | erase package1 [package2] [&amp;hellip;] 卸载
yum provides | whatprovidesfeature1 [feature2] [&amp;hellip;]查看指定的特性(可以是某文件)是由哪个程序包所提供：
yum clean [ packages | metadata | expire-cache | rpmdb| plugins | all ]清理本地缓存：
yum search string1 [string2] [&amp;hellip;] 以指定的关键字搜索程序包名及summary信息
yum history
yum history info 6
yum history undo 6
yum history redo 6
包组管理的相关命令：
yum groupinstall group1 [group2] [&amp;hellip;]
yum groupupdate group1 [group2] [&amp;hellip;]
yum grouplist [hidden] [groupwildcard] [&amp;hellip;]
yum groupremove group1 [group2] [&amp;hellip;]
yum groupinfo group1 [&amp;hellip;]
-y: 自动回答为“yes” 可实现非交互式
创建yum仓库：
createrepo[options] &lt;directory&gt;&lt;/p&gt;</description></item><item><title>Linux用户资源配额</title><link>/2015/08/19/linux%E7%94%A8%E6%88%B7%E8%B5%84%E6%BA%90%E9%85%8D%E9%A2%9D/</link><pubDate>Wed, 19 Aug 2015 00:00:00 +0000</pubDate><guid>/2015/08/19/linux%E7%94%A8%E6%88%B7%E8%B5%84%E6%BA%90%E9%85%8D%E9%A2%9D/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;用户资源系统配额
在系统中，允许系统用户使用存储空间，但是不能限制的让用户随意使用存储空间，对用户存储空间的设定就是用户资源系统配额。
下面以/home 为例，通常如果系统中有一定的用户，会把用户的家目录作为一个单独的分区，从而实现更好的管理。下面具体讲解如何把用户家目录从根系统分区分离出来进行磁盘配额。&lt;/p&gt;
&lt;h1 id="1备份数据"&gt;1.备份数据&lt;/h1&gt;
&lt;p&gt;拿出一个未使用的分区，把用户家目录的数据拷贝到新的分区&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;fdisk /dev/sdb --&amp;gt; n（新建分区）--&amp;gt; p(主分区)--&amp;gt; +10G--&amp;gt; w (保存)
mkfs.ext4 -L home_dir /dev/sdb1 格式化新建的分区，卷标名称：home_dir
(如果新建分区没有跟新到内存，partx -a /dev/sdb --&amp;gt;centos6 partprobe --&amp;gt;centos7|5)
mkdir /mnt/home
mount /mnt/home /dev/sdb1 将分区临时挂载到一个目录，备份home目录中数据
cp -a /home/* /mnt/home
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样就将home家目录中的历史数据备份到新的分区里面了，但要想将之后的数据也自动存储到新建分区的中必须重新挂载，如果直接将新的分区挂载到/home 目录下，存在以下问题。第一、挂载过程中存在用户正在使用家目录的情况，挂载到/home后原来/家目录中的数据时没有用的，而且会自动隐藏掉。所以，安全起见，必须实现通知所有用户，即将要迁移家目录，选一个时间停掉服务，在init 1 模式下实现重新挂载&lt;/p&gt;
&lt;h1 id="2单用户模式运行"&gt;2.单用户模式运行&lt;/h1&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rm -rf /home/*
vim /etc/fstab --&amp;gt; /dev/sdb1 /home ext4 defaults.usrquota,grpquota 0 0
mount -a 将分区重新挂载到家目录下
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="3init-3-重新回到正常模式"&gt;3、init 3 重新回到正常模式&lt;/h1&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cd /home --&amp;gt; quotacheck -cug /home --&amp;gt;可以看到 aquota.group aquota.user两个数据库
quotaon -p /home quotaon /home 查看和打开系统配额功能
（如果系统配额没有打开，有可能是selinux功能没有关闭 ）
getenforce 查看selinux setenforce=0 关闭selinux功能
repquota -a 可以查看所有用户的磁盘配额情况，显示如下
Block limits File limits
User used soft hard grace used soft hard grace
----------------------------------------------------------------------
root -- 4 0 0 1 0 0
huyu -- 56 10240 40960 19 0 0
superman -- 48 0 0 12 0 0
supergirl -- 48 0 0 12 0 0
user/group ：用户或者组名
userd：用户或者组 已经使用的存储空间，以byte为单位
soft：存储空间使用过多警告提示
hard：空间使用极限，提示如下
sdb1: warning, user block quota exceeded.
sdb1: write failed, user block limit reached.
grace： 宽限期。在这个有效时间内，存储空间数据可以使用修改
后面的分别表示 inode节点的限制，内容和空间相同，只是数值表示限制节点个数
repquota -u /dir|/dev/sdb1 repquota -g /dir|/dev/sdb1 这两个可以看分区用户和组的配额
修改用户/组 de 存储空间配额
edquota -u/g huyu --&amp;gt; 进入修改
edquota -p huyu user2 配额复制，user2配额 如同 huyu
setquota username soft hard soft hard 直接命令配置，没有限制，则用0 代替
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="4去掉磁盘配额"&gt;4、去掉磁盘配额&lt;/h1&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1、 quotaoff /dir|/dev/sdb1
2、 rm -rf aquota.user|group
3、 vim /etc/fstab
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux防火策略iptables</title><link>/2015/08/10/linux%E9%98%B2%E7%81%AB%E7%AD%96%E7%95%A5iptables/</link><pubDate>Mon, 10 Aug 2015 00:00:00 +0000</pubDate><guid>/2015/08/10/linux%E9%98%B2%E7%81%AB%E7%AD%96%E7%95%A5iptables/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="防火墙分类"&gt;防火墙分类&lt;/h1&gt;
&lt;p&gt;按照硬件组成部分划分：
​ 硬件防火墙：在硬件级别实现部分功能的防火墙；另一个部分功能基于软件实现；
​ 软件防火墙：应用软件处理逻辑运行于通用硬件平台之上的防火墙；
按照服务范围划分：
​ 主机防火墙：服务范围为当前主机；
​ 网络防火墙：服务范围为防火墙被的局域网；&lt;/p&gt;
&lt;h1 id="netfilter"&gt;netfilter&lt;/h1&gt;
&lt;p&gt;netfilter是位于内核空间的一个防火墙框架，通过iptables这样一个规则管理工具，向框架中添加规则，从而实现防火墙功能。&lt;/p&gt;
&lt;h1 id="iptables"&gt;iptables&lt;/h1&gt;
&lt;p&gt;Firewall：防火墙的意思，只是一个隔离工具；Packets Filter Firewall，主要实现经过该网络的数据包的过滤功能；工作于主机或网络的边缘，对经由的报文根据预先定义的规则（匹配条件）进行检测，对于能够被规则匹配到的报文实行某预定义的处理机制的一套组件；&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;netfilter：防火墙框架，framework；位于内核空间；
iptables：命令行工具程序，位于用户空间；规则管理工具；
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="传说中的四表五链"&gt;传说中的四表五链&lt;/h2&gt;
&lt;p&gt;要处理这些数据包总要一种规则，然而这些表和链就组成了一个规则。来看一张完整图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="&lt;img loading=\"lazy\" src=\"complete\"&gt;" loading="lazy" src="13-Linux%E4%B9%8B%E9%98%B2%E7%81%AB%E5%A2%99iptables/complete.png"&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;表：
filter表--过滤数据包
nat表--用于网络地址转换（IP、端口）
mangle表--修改数据包的服务类型、TTL、并且可以配置路由实现QOS
raw表--决定数据包是否被状态跟踪机制处理
链：
INPUT链--进来的数据包应用此规则链中的策略
OUTPUT链--外出的数据包应用此规则链中的策略
FORWARD链--转发数据包时应用此规则链中的策略
PREROUTING链--对数据包作路由选择前应用此链中的规则（所有的数据包进来的时侯都先由这个链处理）
POSTROUTING链--对数据包作路由选择后应用此链中的规则（所有的数据包出来的时侯最后都由这个链处理）
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上图十分清晰的表明数据包的流向。&lt;/p&gt;
&lt;p&gt;由于四表中我们常用的就只有filter和nat，所以再来一张鸟哥版精简图：&lt;/p&gt;
&lt;p&gt;&lt;img alt="simple" loading="lazy" src="/2015/08/10/linux%E9%98%B2%E7%81%AB%E7%AD%96%E7%95%A5iptables/simple.png"&gt;&lt;/p&gt;
&lt;h2 id="iptables的命令组成"&gt;iptables的命令组成&lt;/h2&gt;
&lt;p&gt;匹配条件：
​ 网络层首部：Source IP, Destination IP, &amp;hellip;
​ 传输层首部：Source Port, Destination Port, TCP Flags(SYN,ACK,FIN,URG,RST,PSH), &amp;hellip;
​ 扩展检查机制：
处理动作：target
​ ACCEPT，DROP，REJECT,&amp;hellip;&lt;/p&gt;
&lt;h2 id="iptables-语法"&gt;iptables 语法&lt;/h2&gt;
&lt;p&gt;iptables 不是固定的一种使用格式，而是有好多种。来看看帮助文档的写法吧。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# man iptables
iptables [-t table] {-A|-C|-D} chain rule-specification
iptables [-t table] -I chain [rulenum] rule-specification
iptables [-t table] -R chain rulenum rule-specification
iptables [-t table] -D chain rulenum
iptables [-t table] -S [chain [rulenum]]
iptables [-t table] {-F|-L|-Z} [chain [rulenum]] [options...]
iptables [-t table] -N chain
iptables [-t table] -X [chain]
iptables [-t table] -P chain target
iptables [-t table] -E old-chain-name new-chain-name
其中：
rule-specification = [matches...] [target]
match = -m matchname [per-match-options]
target = -j targetname [per-target-options]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;总结起来大概就是这样&lt;/p&gt;</description></item><item><title>Linux日志工具之rsyslog</title><link>/2015/08/07/linux%E6%97%A5%E5%BF%97%E5%B7%A5%E5%85%B7%E4%B9%8Brsyslog/</link><pubDate>Fri, 07 Aug 2015 00:00:00 +0000</pubDate><guid>/2015/08/07/linux%E6%97%A5%E5%BF%97%E5%B7%A5%E5%85%B7%E4%B9%8Brsyslog/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;日志：历史事件；
历史事件：时间、地点、事件；
syslog：
klogd：kernel
syslogd：system(application)
事件记录格式：
日期时间 主机 进程[pid]: 事件内容；
C/S架构：通过TCP或UDP协议的服务完成日志记录传送；
rsyslog：
rsyslog的特性：
多线程；
UDP/TCP/SSL/TLS/RELP；
存储日志信息于MySQL, PGSQL, Oracle等RDBMS；
强大的过滤器，实现过滤日志信息中的任何部分的内容；
自定义的输出格式；
……
ELK：
E: elasticsearch, L: logstash, K: kibana
rsyslog日志收集器的基本术语：
facility：设施，收束日志数据流为有限几个；
auth, authpriv, cron, daemon, kern, lpr, mail, mark, news, security, user, uucp, syslog, local0-local7
proirity：优先级
debug, info, notice, warn(warning), err(error), crit(critical), alert, emerg(panic)
程序包：rsyslog
程序环境：
配置文件：/etc/rsyslog.conf，/etc/rsyslog.d/*.conf
主程序：/usr/sbin/rsyslogd
CentOS 6：service rsyslogs {start|stop|restart|status}
CentOS 7：/usr/lib/systemd/system/rsyslog.service
配置文件格式：
由三部分组成：
MODULES：模块配置
GLOBAL DIRECTIVES：全局配置
RULES：日志记录相关的配置
RULES:
配置格式：
facility.priority target
facility：
*: 所有的facility；
f1,f2,f3,...：指定的facility列表；
priority：
*: 所有级别；
none：没有级别；
PRIORITY：指定级别（含）以上的所有级别；
=PRIORITY：仅记录指定级别的日志信息；
target：
文件：将日志信息记录到指定的文件中；文件路径前的-表示异步写入；
用户：将日志事件通知给指定的用户；
日志服务器：@host，把日志通过网络送往指定的服务器记录，而非由本地记录；
管道： | COMMAND
配置rsyslog成为日志服务器：
#### MODULES ####
# Provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514
# Provides TCP syslog reception
$ModLoad imtcp
$InputTCPServerRun 514
其它的日志文件：
/var/log/secure：系统安装日志，应该周期性分析；
/var/log/btmp：当前系统上，用户的失败尝试登录相关的日志信息，lastb命令进行查看；
/var/log/wtmp：当前系统上，用户正常 登录系统的相关日志信息，last命令可以查看；
lastlog命令：用于查看当前系统上每一个用户最近一次的登录信息；
/var/log/messages：系统日志信息；
/var/log/dmesg：系统引导过程中的日志信息；
文本查看工具查看；
也可以使用专用命令dmesg查看；
rsyslog将日志记录于MySQL中：
(1) 准备MySQL Server；
(2) 在mysql server上授权rsyslog能连接至当前服务器；
mysql&amp;gt; GRANT ALL ON Syslog.* TO &amp;#39;USER&amp;#39;@&amp;#39;HOST&amp;#39; IDENTIFIED BY &amp;#39;PASSWORD&amp;#39;;
(3) 在rsyslog主机上安装mysql模块相关的程序包；
yum install rsyslog-mysql
(4) 为rsyslog创建数据库及表；
mysql -uUSERNAME -hHOST -pPASSWORD &amp;lt; /usr/share/doc/rsyslog-7.4.7/mysql-createDB.sql
(5) 配置rsyslog将日志保存到mysql中；
#### MODULES ####
$ModLoad ommysql
#### RULES ####
facility.priority :ommysql:DBHOST,DBNAME,DBUSER,DBUSERPASSWORD
(6) 重启rsyslog服务；
通过loganalyzer展示数据库中的日志：
(1) 准备amp或nmp组合；
# yum install httpd php php-mysql php-gd
(2) 安装LogAnalyzer：
# tar xf loganalyzer-3.6.5.tar.gz
# cp -a loganalyzer-3.6.5/src /var/www/html/loganalyzer
# cd /var/www/html/loganalyzer
# touch config.php
# chmod 666 config.php
(3) 配置loganalyzer：
systemctl start httpd.service
http://HOST/loganalyzer
MySQL Native, Syslog Fields, Monitorware
(4) 安全加强
# cd /var/www/html/loganalyzer
# chmod 644 config.php
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux的inode软链接和硬链接</title><link>/2015/08/05/linux%E7%9A%84inode%E8%BD%AF%E9%93%BE%E6%8E%A5%E5%92%8C%E7%A1%AC%E9%93%BE%E6%8E%A5/</link><pubDate>Wed, 05 Aug 2015 00:00:00 +0000</pubDate><guid>/2015/08/05/linux%E7%9A%84inode%E8%BD%AF%E9%93%BE%E6%8E%A5%E5%92%8C%E7%A1%AC%E9%93%BE%E6%8E%A5/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;Linux系统中的链接有两种，软连接和硬链接。要充分了解他们最好先了解下inode.&lt;/p&gt;
&lt;h1 id="inode"&gt;inode&lt;/h1&gt;
&lt;p&gt;简单来说就是系统文件记录方式用的是inode,二文件名称只是inode的一个别名,就好像机房有100台主机,每台主机有自己的编号(inode),而有两个主机用来做管理,所以取名叫master1,master2(文件名).&lt;/p&gt;
&lt;p&gt;为什么要这么做呢?文件系统是数据在硬盘上的一种排列组织方式，linux每个分区都可以格式化为不同的文件系统，所以可以说每个分区都是一个文件系统，都有自己的目录层次结构。而在目录之下，linux已经将所有的资源处理成为文件，不论是目录还是命令，之后linux会将这些分属不同分区的、单独的文件系统按一定的方式形成一个系统的总的目录层次结构，实现对不同分区的数据进行访问。但是通过目录层次访问磁盘上的文件，只是表面现象。要知道数据在磁盘上以块(block)存储(一个block由8个扇区组成，每个扇区0.5K，扇区是硬盘存储数据的最小单位)数据的大小不一，造成了块的数量也不尽相同，增删改查都会影响数据块的排列方式，那么如何在众多的可能不连续的数据块中，如何精准的找到数据对应的那些块，必须依附在一张索引列表之上，这就是inode。当然，inode中不仅仅存储着指针，还存储着对应资源的权限、属主、属组和大小.&lt;/p&gt;
&lt;p&gt;inode也是存储在block上的一段数据，所以其也是占用磁盘空间的，每个分区都有自己的inode上限，所以有时候如果显示磁盘空间已满，inode上限也需要作为一个原因，可使用&lt;code&gt;df -i&lt;/code&gt;查看&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[yhu@login ~]$df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda2 419430400 1008127 418422273 1% /
/dev/sda1 2097152 374 2096778 1% /boot
[yhu@login ~]$df -ih
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda2 400M 985K 400M 1% /
/dev/sda1 2.0M 374 2.0M 1% /boot
[yhu@login ~]$df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 400G 144G 257G 36% /
/dev/sda1 2.0G 169M 1.9G 9% /boot
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;查看文件的inode，使用&lt;code&gt;ls -i&lt;/code&gt;&lt;/p&gt;</description></item><item><title>Linux终端类型</title><link>/2015/08/03/linux%E7%BB%88%E7%AB%AF%E7%B1%BB%E5%9E%8B/</link><pubDate>Mon, 03 Aug 2015 00:00:00 +0000</pubDate><guid>/2015/08/03/linux%E7%BB%88%E7%AB%AF%E7%B1%BB%E5%9E%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1终端的概念"&gt;1.终端的概念&lt;/h1&gt;
&lt;p&gt;所谓终端就是在一个连接主机的端子上面接入鼠标、键盘、显示器等所组成的一个组合。终端用于用户与主机之间的交互。早期的时候，在大型主机上，想使用主机的用户很多，但是主机却只有一个，也不能实现人手一台。于是为了让一台主机给多个用户使用，便产生了多个终端和多个用户，这样每个用户只要有一个终端变可以与主机交互了。&lt;/p&gt;
&lt;h1 id="2终端的类型"&gt;2.终端的类型&lt;/h1&gt;
&lt;p&gt;终端的类型有四类：物理终端，虚拟终端，图形终端和伪终端。&lt;/p&gt;
&lt;h2 id="物理终端"&gt;物理终端：&lt;/h2&gt;
&lt;p&gt;将鼠标、键盘、显示器直接连接到主机的接口上。也称为物理控制台（console）&amp;ndash;/dev/console。&lt;/p&gt;
&lt;h2 id="虚拟终端"&gt;虚拟终端：&lt;/h2&gt;
&lt;p&gt;附加在物理终端之上，用软件的方式实现虚拟终端。centos中有6个虚拟中断，通过Alt+Ctrl+（F1-F6）进行切换。&lt;/p&gt;
&lt;h2 id="图形终端"&gt;图形终端：&lt;/h2&gt;
&lt;p&gt;图形终端也是附加在物理终端之上，用软件的方式实现。但提供桌面环境，切换方式 Ctrl+Alt+F7.&lt;/p&gt;
&lt;h2 id="伪终端"&gt;伪终端：&lt;/h2&gt;
&lt;p&gt;在图形界面下打开的的命令行界面，或者基于ssh协议或Telnet协议等远程打开的命令行界面。例如用xshell或者secureCRT远程连接的命令行界面就是属于伪终端。&lt;/p&gt;
&lt;p&gt;在这里我们要注意的是，实际操作中当我们的系统启动还没有完全运行起来的时候，在我们的设备上运行的终端是物理终端，在服务完全启动之后映射的就是虚拟终端或者图形终端啦。&lt;/p&gt;</description></item><item><title>Linux用户和组的权限</title><link>/2015/08/02/linux%E7%94%A8%E6%88%B7%E5%92%8C%E7%BB%84%E7%9A%84%E6%9D%83%E9%99%90/</link><pubDate>Sun, 02 Aug 2015 00:00:00 +0000</pubDate><guid>/2015/08/02/linux%E7%94%A8%E6%88%B7%E5%92%8C%E7%BB%84%E7%9A%84%E6%9D%83%E9%99%90/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1文件的权限分类"&gt;1、文件的权限分类&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;文件的权限对象分三类：属主（u）、属组（g）、其他（o），每个对象都有rwx，读写执行三类权限。
对于文件
r：可查看文件内容
w：可修改其类容
x：可把此文件提请内核启动为一个进程
对于目录
r：可使用ls查看此目录中文件列表
w：可在此目录中创建和删除文件
x：可使用ls查看目录中文件列表，可以cd进入此目录
X：只给目录x权限，不给文件x权限
在访问文件之前，必须要先能够进入文件的父目录
前提：进程有属主和属组；文件有属主和属组
(1) 任何一个可执行程序文件能不能启动为进程：取决发起者对程序文件是否拥有执行权限
(2) 启动为进程之后，其进程的属主为发起者；进程的属组为发起者所属的组
(3) 进程访问文件时的权限，取决于进程的发起者
(a) 进程的发起者，同文件的属主：则应用文件属主权限
(b) 进程的发起者，属于文件属组；则应用文件属组权限
(c) 应用文件“其它”权限
root 没有了 x 执行权限不能执行，但是可以给自己加权限
root用户没有读和写权限也可以直接修改查看。
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="2chmod-用法"&gt;2、chmod 用法&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;a、chmod [option]... 八进制 file
eg：chmod -R 755 /dir
b、chmod [option]... mode ... file
eg：chmod u=rwx,g=rx,u= file/dir
或者 chmod a=rx file/dir 或者 chmod u+w file/dir
c、chmod [option] ... --reference = rfile file
参考rfile权限 复制到 file
chmod -R a=rwX dir
目录下的文件如果有x权限 则都有x权限
如果没有x权限 则 所有的文件都没有x权限
但是所有的目录都有x权限
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="3新建文件和目录的默认权限"&gt;3、新建文件和目录的默认权限&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;新建FILE权限:666-umask如果所得结果某位存在执行（奇数）权限，则将其权限+1
新建DIR权限: 777-umask,非特权用户umask是002,root的umask是022
umask: 查看 umask#: 设定 #umask -S --&amp;gt; u=rwx,g=rx,o=rx #umask -p --&amp;gt; umask 0022
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="4linux文件系统上的特殊权限suid-sgid-sticky"&gt;4、Linux文件系统上的特殊权限，SUID, SGID, Sticky&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;chmod 4755 file
chmod u+s /usr/bin/passwd
-rwsr-xr-x. 1 root root 30768 Feb 22 2012 /usr/bin/passwd
当用户使用passwd【具有suid权限的二进制程序文件】命令，用户权限临时切换成【passwd文件所有者】权限
启动为进程之后，其进程的属主为原程序文件的属主
/bin 文件夹下面有众多 红色背景的程序均是这样。
chmod 2755
/usr/bin/passwd chmod g+s /usr/bin/passwd
-rwxr-sr-x. 1 root root 30768 Feb 22 2012 /usr/bin/passwd
当用户使用passwd【具有suid权限的二进制程序文件】命令，用户权限临时切换成【passwd文件所属组】权限 --黄色背景
启动为进程之后，其进程的属主为原程序文件的属组
chmod 1777 /dir
[root@localhost conf]#ll -d /tmp
drwxrwxrwt. 18 root root 4096 Oct 16 02:52 /tmp
可以在目录中删除【自己的】文件，而不能删除别人的文件
chmod 2755 dir
chmod g+s /dir
-rwxr-sr-x. 1 root root 30768 Feb 22 2012 /dir
当该目录【所属组用户】在该目录下创建文件的时候，创建的文件所属组均为目录的所属组
小坑一个： 用0777，并不能把sst等权限去掉
没有执行权限则该位置显示为【S、T】
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="5设定文件特定属性attrattribute"&gt;5、设定文件特定属性（attr&amp;ndash;attribute）&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;chattr +i 不能删除，改名，更改 +a 只能追加（&amp;gt;&amp;gt;）
lsattr 显示文件特定属性。
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="6acl"&gt;6、ACL&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;ACL：Access Control List，实现灵活的权限管理，除了u，g，o可以对更多的用户设置权限
ACL生效顺序：所有者（属主，属组），自定义用户，自定义组，其他人
setfacl [-bkndRLP] { -m|-M|-x|-X ... } file ...
-b,--remove-all：删除所有扩展的acl规则，基本的acl规则(所有者，群组，其他）将被保留。
-k,--remove-default：删除缺省的acl规则。如果没有缺省规则，将不提示。
-n，--no-mask：不要重新计算有效权限。setfacl默认会重新计算ACL mask，除非mask被明确的制定。
--mask：重新计算有效权限，即使ACL mask被明确指定。
-d，--default：设定默认的acl规则。
--restore=file：从文件恢复备份的acl规则（这些文件可由getfacl-R产生）。通过这种机制可以恢复整个目录树的acl规则。
此参数不能和除--test以外的任何参数一同执行。
--test：测试模式，不会改变任何文件的acl规则，操作后的acl规格将被列出。
-R，--recursive：递归的对所有文件及目录进行操作。
-L，--logical：跟踪符号链接，默认情况下只跟踪符号链接文件，跳过符号链接目录。
-P，--physical：跳过所有符号链接，包括符号链接文件。
--version：输出setfacl的版本号并退出。 --help：输出帮助信息。
--：标识命令行参数结束，其后的所有参数都将被认为是文件名
-：如果文件名是-，则setfacl将从标准输入读取文件名。
Usage: setfacl [-bkndRLP] { -m|-M|-x|-X ... } file ...
-m, --modify=acl modify the current ACL(s) of file(s)
-M, --modify-file=file read ACL entries to modify from file
-x, --remove=acl remove entries from the ACL(s) of file(s)
-X, --remove-file=file read ACL entries to remove from file
例子：
mount -o acl /directory
getfacl file|directory --获取文件或者目录的acl
setfacl -m u:wang:rwx file|directory --增加文件或者目录的用户acl
setfacl -Rm g:sales:rwX directory --目录及其子目录以及文件增加用户acl，新增加文件或者目录不会自带acl
setfacl -M file.acl file|directory --参照file.acl 给目录或者文件增加acl
setfacl -m g:salesgroup:rw file| directory --增加文件或者目录的用户组acl
setfacl -m d:u:wang:rx directory --增加该目录的acl，新增文件或者目录的时候【自带acl】，目录下原有的文件或者目录acl不变
setfacl -x u:wang file|directory --删除文件或者目录中，对应用户的acl ，mask还在
setfacl -X file.acl directory --参照file.acl 删除目录中的acl ，mask还在
setfacl -Rb directory --清空目录下的所有acl，包括mask
setfacl-k dir --删除默认ACL权限
【注意】：
默认拷贝不会拷贝文件或者目录的acl，用cp来复制文件的时候我们现在可以加上-p选项。对于不能拷贝的ACL属性将给出警告。
mv命令将会默认地移动文件的ACL属性，同样如果操作不允许的情况下会给出警告。
如果你的文件系统不支持ACL的话，你也许需要重新mount你的file system： mount -o remount, acl [mount point]
如果用chmod命令改变Linux file permission的时候相应的ACL值也会改变，反之改变ACL的值，相应的file permission也会改变。
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux文本查看工具</title><link>/2015/08/01/linux%E6%96%87%E6%9C%AC%E6%9F%A5%E7%9C%8B%E5%B7%A5%E5%85%B7/</link><pubDate>Sat, 01 Aug 2015 00:00:00 +0000</pubDate><guid>/2015/08/01/linux%E6%96%87%E6%9C%AC%E6%9F%A5%E7%9C%8B%E5%B7%A5%E5%85%B7/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在Linux系统中，一个核心理念就是：一切皆文件。正确的对系统中文本的编辑配置与管理是Linux系统得以稳定运行的一个重要大前提。
因此Linux系统中的文件与管理特别重要，下面简单说一下在对文件处理过程中经常用到的一些工具。
文本查看命令：&lt;code&gt;cat,tac,rev,more,less&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat [option]...[file]...
-E:显示行结束符$
-n:显示行号
-A:显示所有控制符eg：tab键就显示为 ^I
-b:非空行编号，即空行不显示行号，略过
-s:压缩连续的空行成一行
tac [option]...[file]...
-b, --before 在行前而非行尾添加分隔标志【默认换行】
-r, --regex 将分隔标志视作正则表达式来解析
-s, --separator=字符串 使用指定字符串代替换行作为分隔标志 -s=&amp;quot;#&amp;quot;
rev [options] [file ...]
reversing the order of characters in every line：将每一行的显示翻转过来
注意这里tac和rev不同的地方是，tac是将整个文档以行为单位翻转，而rev是以每一个字符为单位，在一行的范围内翻转。ABC--&amp;gt;CBA 而tac 是 ABC 【第一行】 123【第二行】 --&amp;gt;123【第一行】ABC【第二行】
more [options] file [...] 翻到底自动退出
less [options] file [...] 翻到底不会自动退出（man帮助查看就是用的less用法）
head [OPTION]... [FILE]...
-c # :指定获取前#个字节
-n # 或 -# : 指定获取行数
tail [OPTION]... [FILE]...
-c # :指定获取前#个字节
-n # 或 -# : 指定获取行数
-f ：跟踪显示文件新追加的内容，常用语日志监控
cut [OPTION]... [FILE]...
-d : 指明分隔符，默认tab 。eg：-d&amp;quot; &amp;quot;,以空格为分割
-f FILEDS: #: 第#个字段 #,#[,#]：离散的多个字段，例如1,3,6
#-#：连续的多个字段, 例如1-6 混合使用：1-3,7
-c按字符切割 cut -c 1-10 anaconda-ks.cfg
--output-delimiter=STRING指定输出分隔符
paste 合并两个文件同行号的列到一行
paste [OPTION]... [FILE]...
-d 分隔符:指定分隔符，默认用TAB
-s : 所有行合成一行显示
paste f1 f2
paste -s f1 f2 先合并成一行，在追加。n个文件就显示n行。
wc [OPTION]... [FILE]...
-c, --bytes 显示 字节数
-m, --chars 显示 字符数
-l, --lines 显示 行号
sort [OPTION]... [FILE]...
-r执行反方向（由上至下）整理
-n执行按数字大小整理 默认是按字符排序，即1,11,12...19,2,21...
-f选项忽略（fold）字符串中的字符大小写
-u选项（独特，unique）删除输出中的重复行
uniq[OPTION]... [FILE]...
-c: 显示每行重复出现的次数
-d: 仅显示重复过的行
-u: 仅显示不曾重复的行
【连续且完全相同方为重复】
diff foo.conf-brokenfoo.conf-works
比较两个文件的不同
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux用户和组</title><link>/2015/07/30/linux%E7%94%A8%E6%88%B7%E5%92%8C%E7%BB%84/</link><pubDate>Thu, 30 Jul 2015 00:00:00 +0000</pubDate><guid>/2015/07/30/linux%E7%94%A8%E6%88%B7%E5%92%8C%E7%BB%84/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="1用户"&gt;1、用户&lt;/h1&gt;
&lt;p&gt;个人理解的是，给使用者一个在系统中使用的身份，即用户。
用户分两种：管理员和普通用户。
而每一个用户都有一些属性，每一个属性都是用冒号分割开来。配置文件存储在【/etc/passwd】中。
例如，&lt;code&gt;sarash:x:507:508::/home/sarash:/bin/nologin&lt;/code&gt;
他们分别是，用户名，密码，ID号，主组ID号，注释，家目录，默认登录shell
用户名是用来登录的账号，密码位置并没有存储真正的密码在这里，只是一个占位符。如果的确想要看到密码存储结果，可以使用命令：pwunconv。
其中 pwconv, pwunconv, grpconv, grpunconv - convert to and from shadow passwords and groups 就是用户密码隐藏 显示 组密码隐藏显示的命令
增加用户的常用命令是：
&lt;code&gt; useradd [options] login&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-u UID: [UID_MIN, UID_MAX]定义在/etc/login.defs
-o 配合-u 选项，不检查UID的唯一性
-g GID：指明用户所属基本组，可为组名，也可以GID
-c &amp;quot;COMMENT&amp;quot;：用户的注释信息
-d HOME_DIR:以指定的路径(不存在)为家目录
-s SHELL: 指明用户的默认shell程序 在/etc/shells文件中有系统可用shell
-G GROUP1[,GROUP2,...]：为用户指明附加组，组必须事先存在
-N 不创建私用组做主组，使用users组做主组
-r: 创建系统用户CentOS 6: ID&amp;lt;500，CentOS 7: ID&amp;lt;1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt; userdel [options] LOGIN&lt;/code&gt;
-f, &amp;ndash;force 删除用户的主目录和邮箱，即使用户登录中，或者邮箱公用
-r 删除用户家目录及邮箱
【注意】：在新建服务型用户的时候（例如MySQL），由于这些用户不需要登录（/bin/nologin），也不需要家目录，所以定义成系统用户最好。可以防止恶意的登录。
一般来说， 1&lt;del&gt;99 会保留给系统预设的帐号，另外 100&lt;/del&gt;499 则保留给一些服务来使用。centos7 就加倍。&lt;/p&gt;</description></item><item><title>Linux系统grep的使用</title><link>/2015/07/28/linux%E7%B3%BB%E7%BB%9Fgrep%E7%9A%84%E4%BD%BF%E7%94%A8/</link><pubDate>Tue, 28 Jul 2015 00:00:00 +0000</pubDate><guid>/2015/07/28/linux%E7%B3%BB%E7%BB%9Fgrep%E7%9A%84%E4%BD%BF%E7%94%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="grep"&gt;grep:&lt;/h1&gt;
&lt;p&gt;Global search REgular expression and Print out the line .&lt;/p&gt;
&lt;p&gt;文本过滤工具，根据用户指定的模式对目标文本进行匹配检查；打印匹配到的行。过滤即不改变原来文本数据。这对于在输出较多信息或者文本内容较多的信息中寻找指定信息很有帮助.&lt;/p&gt;
&lt;p&gt;比如想查看文件ifcfg-eth0中配置的网络是多少,你只需要显示有IPADDR这一行:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#grep IPADDR ifcfg-eth0
IPADDR=172.16.10.18
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样,就会显示ifcfg-eth0 文件中包含有IPADDR的行.&lt;/p&gt;
&lt;p&gt;当然grep还有很多参数可以帮助我们更精确的找到想要的内容,下面列举几个最常用的参数&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;grep [options] PATTERN [file...]
--color=auto： 对匹配到的文本着色显示
-v：显示不被pattern匹配到的行
-i：忽略大小写 #上面的就可以写成 grep -i ipaddr ifcfg-eth0
-n：显示匹配的行号
-c：统计匹配的行数
-o：仅显示匹配到的字符串
-q：静默模式，不输出任何模式
-A #：after，后#行一并显示
-B #：before，前#行一并显示
-C #：context，前后#行一并显示
-e:实现多个选择间的逻辑或关系--&amp;gt; grep -e ‘cat’ -e ‘dog’ file
grep的PATTERN 支持正则表达式，支持正则表达式的还有 vim，less，nginx等
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;grep还可以配合管道命令使用:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[root@node7 ~]#lspci | grep -i network #lspci的结果直接通过grep过滤显示
81:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
81:00.1 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="正则表达式"&gt;正则表达式&lt;/h1&gt;
&lt;p&gt;regexp，由一类特殊字符及文本字符所编写的模式，其中有些字符不表示字符字面意义，而表示控制或通配的功能.比如:&lt;/p&gt;</description></item><item><title>Linux awk</title><link>/2015/07/25/linux-awk/</link><pubDate>Sat, 25 Jul 2015 00:00:00 +0000</pubDate><guid>/2015/07/25/linux-awk/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;h1 id="awk介绍"&gt;awk介绍&lt;/h1&gt;
&lt;p&gt;awk是Linux三剑客中功能最强大，使用最复杂的命令。强大到awk都成为一种语言了，它适合文本处理和报表生成，其语法较为常见，借鉴了某些语言的一些精华，如 C 语言等。在 linux 系统日常处理工作中，发挥很重要的作用，掌握了 awk将会使你的工作变的高大上。 awk 是三剑客的老大，利剑出鞘，必会不同凡响。&lt;/p&gt;
&lt;h1 id="awk基本用法"&gt;awk基本用法&lt;/h1&gt;
&lt;p&gt;输入awk就可以看到awk简要帮助，下面就结合我自己查找资料和使用之后的一个总结：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;awk基本用法
awk [options] &amp;#39;program&amp;#39; var=value file…
awk [options] -f programfile var=value file…
&amp;#39;program&amp;#39; = &amp;#39;BEGIN{ action;… } pattern{ action;… } END{ action;… }&amp;#39;
options（选项）:
-f progfile --file=progfile #从文件中读取program语法
-F fs --field-separator=fs #字段分隔符，默认空格
-v var=val --assign=var=val #自定义变量
#官方简单例子，将/etc/passwd以冒号&amp;#34;:&amp;#34;分隔，将得到的的第一列打印
#gawk -F: &amp;#39;{ print $1 }&amp;#39; /etc/passwd
root
bin
daemon
...
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="1分隔符域和记录"&gt;1.分隔符.域和记录&lt;/h2&gt;
&lt;p&gt;awk执行时,参数&lt;code&gt;-F &lt;/code&gt;的值就是分隔符,不写默认为空格.&lt;/p&gt;
&lt;p&gt;分割出来的字段就是域,从前往后一次标记为&lt;code&gt;$1,$2,$3...$n&lt;/code&gt; 称为域标示.&lt;code&gt;$0&lt;/code&gt;为所有域.&lt;/p&gt;
&lt;p&gt;文件的每一行称为一条记录.&lt;/p&gt;
&lt;h2 id="2awk语法"&gt;2.awk语法&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;program: &amp;#39;BEGIN{ action;… } pattern{ action;… } END{ action;… }&amp;#39;
pattern和action：
pattern部分决定动作语句何时触发及触发事件（BEGIN,END 也是属于pattern中的特殊部分吧）
action statements对数据进行处理，放在{}内指明
（print, printf）
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="3awk工作过程"&gt;3.awk工作过程&lt;/h2&gt;
&lt;p&gt;awk的工作过程:
第一步: 执行 BEGAIN{action;&amp;hellip;} 语句块中的语句,如果没有就跳过此步骤.
第二步: 从文件和标准输入读取一行(就是一条记录),然后按照pattern中匹配中了就执行后面的 { action;… } ,然后读取下一条记录匹配执行,重复这个过程知道文件全部读取完毕.
第三步: 执行 END{ action;… } 语句块中的语句,如果没有就跳过此步骤.&lt;/p&gt;</description></item><item><title>Linux磁盘管理</title><link>/2015/07/20/linux%E7%A3%81%E7%9B%98%E7%AE%A1%E7%90%86/</link><pubDate>Mon, 20 Jul 2015 00:00:00 +0000</pubDate><guid>/2015/07/20/linux%E7%A3%81%E7%9B%98%E7%AE%A1%E7%90%86/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;在系统中，常见的硬盘接口有分两类：并行的和串行的
并行：
IDE: 133MB/s
SCSI: 640MB/s
串行：
SATA: 6Gbps
SAS: 6Gbps
USB: 480MB/s
存储设备的设备文件命名方式大致为：/dev/DEV_FILE
IDE: /dev/hd# #--&amp;gt;0,1,2,3
SCSI,SATA,SAS,USB: /dev/sdx x表示a-z eg：/dev/sda,/dev/sdb
光盘中显示的是 /dev/sr0,/dev/sr1...
同一设备上的不同分区：1,2,3... eg：/dev/sda1,/dev/sda2...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;想要使用一块没有用过的磁盘，需要做一下几部：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1、设备识别，即首先要讲磁盘接入设备中，在系统中能够看到设备（/dev/DEV_FILE）
2、设备分区，全新的磁盘是没有分区的
3、创建文件系统，主要明确在设备上存储的数据是什么格式的.(ext3,ext4,NTFS...)
4、标记文件系统
5、在/etc/fstab文件中创建条目
6、挂在新的文件系统
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;磁盘分区：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;磁盘分区的理由:
1、优化i/o性能
2、实现磁盘空间配额限制
3、提高修复速度
4、隔离系统和程序
5、安装多个OS
6、采用不同文件系统
两种分区方式：MBR,GPT
MBR: master boot record ，使用32位表示山区数，分区不超过2T，按柱面分区
在0磁道0扇区的512bytes中，前446bytes：BootLoader，64bytes：分区表（其中16byte表示一个分区，因此主分区+扩展分区&amp;lt;=4）最后2byte：55AA
GPT:GUID(global unique identifiers) partition table 支持128个分区，使用64位，支持8Z（512byte/block） 64Z（4096byte/block）
使用128位UUID（universally unique identifier ）表示磁盘和分区，GPT分区表自动备份在头和尾两份，并有CRC校验位
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;管理分区：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;列出块设备：lsblk
创建分区：fdisk（MBR），gdisk（GPT），parted 高级分区操作（创建，复制，调整大小等）
partprobe 重新设置内存中的内核分区表版本
fdisk gdisk命令类似，下面主要介绍fdisk的使用
fdisk [options] &amp;lt;disk&amp;gt; change partition table
fdisk [options] -l &amp;lt;disk&amp;gt; list partition table(s)
交互式fdisk命令其实更适合使用，分区之后可查看，可修改，只有在保存的时候才会真正的自该磁盘的分区。
子命令：
p 分区列表
t 更改分区类型
n 创建分区
d 删除分区
w 保存并退出
q 不保存退出
m 查看帮助，打印菜单选择项
在修改分区完成之后，可查看内核是否已经识别新的分区 cat /proc/partations
如果内核没有识别到新的分区，可通过命令是内核重新读取硬盘分区表
在 centos 6 上 partx -a -n M:N /dev/DEVICE 或者 kpartx -a /dev/DEVICE
删除分区：partx -d -n M:N /dev/DEVICE
在centos 5，7上面直接使用partprobe 就可以了 partprobe [/dev/DEVICE]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;文件系统:&lt;/p&gt;</description></item><item><title>Linux压缩和解压</title><link>/2015/07/18/linux%E5%8E%8B%E7%BC%A9%E5%92%8C%E8%A7%A3%E5%8E%8B/</link><pubDate>Sat, 18 Jul 2015 00:00:00 +0000</pubDate><guid>/2015/07/18/linux%E5%8E%8B%E7%BC%A9%E5%92%8C%E8%A7%A3%E5%8E%8B/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;在Linux系统中压缩、解压以及归档的工具主要有一下几种：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;file-roller ： 这个工具适合在桌面系统下进行解压操作。
compress/uncompress ：这是一个比较老的解压工具，压缩后会添加.Z为后缀
gzip/gunzip : .gz结尾
bzip2/bunzip2 : .bz2
xz/unxz : .xz
zip/unzip .zip
tar
cpio
主要命令参数介绍
compress [-dvc] [file...]
-d: 解压，相当于uncompress
-c: 解压结果标准输出，不删除源文件
-v: 显示详情
zcat file.Z --&amp;gt;不解压的情况下查看文件
zcat file.Z &amp;gt;file --&amp;gt;解压保留源文件
gzip [-dc#] file...
-d:解压缩 =gunzip
-c:标准输出
-#：1-9,指定压缩比，默认为6
zcat file.gz --&amp;gt;不解压的情况下查看文件
zcat file.gz &amp;gt;file --&amp;gt;解压保留源文件
bzip2 [-kd#] file...
-k:keep,即保留源文件
-d:解压缩=bunzip2
-#：指定压缩比，默认6
bzcat ：不解压下查看文件内容
xz [-kd#] file...
-k:keep,即保留源文件
-d:解压缩=unxz
-#：指定压缩比，默认6
xzcat ：不解压下查看文件内容
zip
-r : 递归压缩，文件及目录
zip –r /testdir/sysconfig.zip /etc/sysconfig/ ...
-d :删除压缩文件中指定的文件
-m :添加
zip -d myfile.zip old.txt
zip -m myfile.zip new.txt
unzip -p message.zip &amp;gt; message
tar [] tape archive 磁带 归档
-c create
-f file.tar
-v 可视化
-x 解包
-r add追加
-t 预览文件名
-C 展开到指定目录
-z 归档后压缩成 .tar.gz
-j .tar.bz2
-J .tar.xz
-T 指定输入文件
-X 指定要排除文件
常用组合
tar -cvf archive.tar file ... 归档
tar -tvf archive.tar 查看归档文件名列表
tar -xvf archive.tar 解包
cpio: copy input output,可解压以.cpio 或者.tar结尾的文件
cpio [option] &amp;gt; file or devicename
cpio [option] &amp;lt; file.cpio or devicename
-o :打包生成file.cpio
-i ：从cpio中解包
-t :预览文件名
-v :显示过程
-d ：解包生成目录
常用组合
find ./etc |cpio -ov &amp;gt; etc.cpio
cpio -tv &amp;lt; etc.cpio
cpio -iv &amp;lt; etc.cpio
cpio -idv &amp;lt; etc.cpio
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux程序包管理器</title><link>/2015/07/15/linux%E7%A8%8B%E5%BA%8F%E5%8C%85%E7%AE%A1%E7%90%86%E5%99%A8/</link><pubDate>Wed, 15 Jul 2015 00:00:00 +0000</pubDate><guid>/2015/07/15/linux%E7%A8%8B%E5%BA%8F%E5%8C%85%E7%AE%A1%E7%90%86%E5%99%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;源代码命名方式，name-version.tar.gz|bz2|xz, &amp;ndash;&amp;gt;version:major.minor.release
要将一个源代码程序安装到Linux系统上，一般有两个方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1.找到源代码，手动编译安装。解压，./configure --&amp;gt; make --&amp;gt; make install
2.使用软件包管理器（程序包管理器）
3.直接解压做好的二进制包后使用
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="1程序包管理器"&gt;1.程序包管理器：&lt;/h1&gt;
&lt;p&gt;将编译好的应用程序文件打包成一个或者几个，从而方便快捷的实现程序包的安装，卸载，查询，升级和校验等管理操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Linux系统不同版本有不同的程序包管理器。目前主要分两类
1、debian：deb文件，dpkg包管理器
2、RedHat：rpm文件，rpm包管理器，RPM：RedHat package manager（原名），现在已是 RPM package manager 递归缩写
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="2rpm包命名方式"&gt;2.RPM包命名方式：&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;name-version-release.arch.rpm --&amp;gt;version:major.minor.release --&amp;gt;release:release.os
eg:bash-4.2.46-19.el7.x86_64.rpm
常见arch：x86，x86_64，PowerPC 或者noarch（）
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="3rpm包的分类和拆包"&gt;3.RPM包的分类和拆包&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;Application-VERSION-ARCH.rpm: 主包
Application-devel-VERSION-ARCH.rpm开发子包
Application-utils-VERSION-ARHC.rpm其它子包
Application-libs-VERSION-ARHC.rpm其它子包
包与包之间可能存在依赖关系，甚至循环依赖。解决方法：yum（RedHat，centos）
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="4rpm包文件组成"&gt;4.RPM包文件组成&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;a、RPM包内的文件。 b、RPM包的元数据，如名称，版本，依赖性，描述等。 c、安装或者卸载时运行的脚本。
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="5获取程序包的途径方法"&gt;5.获取程序包的途径方法：&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;1、系统发版的光盘或者官方服务器；
CentOS镜像
http:www.centos.org/download/
http://mirror.aliyun.com（阿里云） 搜狐 163等
2、项目官方站点
3、第三方组织
Fedora-EPEL：
Extra Packages for Enterprise Linux
Rpmforge:RHEL推荐，包很全
搜索引擎：
http://pkgs.org
http://rpmfind.net
http://rpm.pbone.net
https://sourceforge.net/
4、自己制作
注意：非官方下载的程序包要检查其合法性：来源的合法以及程序包的完整性
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="6库文件"&gt;6.库文件&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;程序的运行需要依赖库文件 ldd /path/to/binary_file 可以查看二进制程序所依赖的库文件
库文件的配置文件：/etc/ld.so.conf /etc/ld.so.conf.d/*.conf 即ld.so.conf 文件和 /etc/ld.so.conf.d/ 目录下所有以 .conf结尾的文件
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Linux安装命令rpm的使用</title><link>/2015/07/13/linux%E5%AE%89%E8%A3%85%E5%91%BD%E4%BB%A4rpm%E7%9A%84%E4%BD%BF%E7%94%A8/</link><pubDate>Mon, 13 Jul 2015 00:00:00 +0000</pubDate><guid>/2015/07/13/linux%E5%AE%89%E8%A3%85%E5%91%BD%E4%BB%A4rpm%E7%9A%84%E4%BD%BF%E7%94%A8/</guid><description>&lt;!-- toc --&gt;
&lt;p&gt;[TOC]&lt;/p&gt;
&lt;p&gt;rpm命令，安装（-i，&amp;ndash;install）、卸载、升级（-U，-F）、查询（-q，&amp;ndash;query）、校验（-V，&amp;ndash;verify）、数据库维护&lt;/p&gt;
&lt;h1 id="1安装"&gt;1.安装：&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;rpm {-i|--install} [install-options] PACKAGE_FILE…
v：可视化
h：以#显示进度
rpm -ivh package_file
[install-options]
--test：测试安装
--nodeps：忽略依赖关系
--replacepkgs |replacefiles
--nosignalture：不检查来源合法性
--noscripts：不执行程序包脚本
--nopre,--nopost,--nopreun,--nopostun 安装前后，卸载前后
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="2查询"&gt;2.查询：&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;rpm {-q|--query} [select-options][query-options]
[select-options]
-a：所有包
-f:查看指定文件有那个程序安装包生成
-p rpmfile：正对尚未安装的程序包做查询操作
--whatprovides capability：查看指定的capability由哪个包所提供
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="3卸载"&gt;3.卸载：&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;rpm {-e|--erase} [--allmatches][--nodeps] [--noscripts][--notriggers] [--test] PACKAGE_NAME ...&lt;/code&gt;&lt;/p&gt;
&lt;h1 id="4包校验"&gt;4.包校验：&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;rpm {-V|--verify} [select-options][verify-options]
S file Size differs
M Mode differs (includes permissions and file type)
5 digest (formerly MD5 sum) differs
D Device major/minor number mismatch
L readLink(2) path mismatch
U User ownership differs
G Group ownership differs
T mTimediffers
P capabilities differ
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="5导入所需要公钥"&gt;5.导入所需要公钥：&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;rpm -K|checksig rpmfile检查包的完整性和签名
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
CentOS 7发行版光盘提供：RPM-GPG-KEY-CentOS-7
rpm -qagpg-pubkey*
&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="6常用"&gt;6.常用&lt;/h1&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rpm -ivh package_file
rpm -ql app_name
rpm -qf file
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
&lt;/code&gt;&lt;/pre&gt;</description></item></channel></rss>