代码魔改师
2023-09-19T04:21:28+00:00
zhiyong
younthu@gmail.com
云计算笔记-1 揭开云计算的神秘面纱
2023-09-18T14:01:00+00:00
/2023/09/18/云计算笔记-1-揭开云计算的神秘面纱
<p>做了一段时间的云桌面开发,工作主要集中在后台service。算是真正意义上的云计算平台开发吧。开发的服务主要是做硬件设备管理,网络管理,存储管理. 这些概念和openstack里面的概念差不多。</p>
<p>这种开发和传统的互联网服务开发不大一样。今天主要就是想分享一下差别在什么地方。</p>
<h1 id="云计算平台开发">云计算平台开发</h1>
<p>云计算就是指通过互联网,以按需服务的形式提供计算资源。 这样企业就无需自行采购、配置或管理资源,而且只需要为实际使用的资源付费。</p>
<p>云计算服务模型有三种:基础架构即服务提供计算和存储服务、平台即服务提供用于开发和部署的开发环境以构建云端应用,而软件即服务则以服务的形式提供应用。</p>
<h2 id="什么是-saas">什么是 SaaS?</h2>
<p>借助 SaaS(软件即服务),软件托管在远程服务器上,客户可通过 Web 浏览器或标准 Web 集成随时随地访问软件。SaaS 提供商负责备份、维护和更新。SaaS 解决方案包括企业资源规划 (ERP)、客户关系管理 (CRM)、项目管理等。</p>
<p>这算是传统互联网软件开发的范畴, 目标用户是使用者,消费者。讲究的是API同步执行,快速返回,如果用户量大,请求多,性能压力就上来了。所以,这种平台的开发难度在性能优化,想办法让API快速完成任务,快速返回。</p>
<h2 id="什么是-paas">什么是 PaaS?</h2>
<p>PaaS 是指平台即服务。这是一种基于云的应用开发环境,能够为开发人员提供构建和部署应用所需的一切。借助 PaaS,开发人员可以通过订阅或按使用付费的方式选择所需的功能和云服务。</p>
<p>简单一点讲,这是针对开发者的,是开发平台。云数据库, 短信服务,托管服务,K8S, CI/CD等都属于PaaS范畴。</p>
<h2 id="什么是-iaas">什么是 IaaS?</h2>
<p>IaaS 是指基础架构即服务。借助 IaaS 服务,企业可以通过按使用付费的方式,“租用”服务器、网络、存储器和操作系统等计算资源。而且,基础架构会不断扩展,客户无需投资硬件。</p>
<p>硬件托管应该是IaaS最大的特点。用户需要用到硬件,但是不需要自己管理,只要掏钱就可以了。</p>
<p>云服务器托管和云桌面托管都属于IaaS范畴。</p>
<p>我参与的是云桌面开发,IaaS服务。我们叫它云托管平台开发吧。</p>
<h1 id="云托管平台开发">云托管平台开发</h1>
<p>云托管开发最大的特点是要管理很多硬件资源,CPU,内存,磁盘,然后还要管理网络资源。 这些硬件资源的调度管理没法像传统rest api那样立刻完成返回。所以都需要做成异步操作。</p>
<p>我详细解释一下云托管开发的异步操作是什么样的一个概念:</p>
<ol>
<li>大部分请求还是通过Rest API 触发。因为不需要保持长连接,每个请求都是无状态的,大大降低编程难度。</li>
<li>Rest API只是用来触发某个操作。真正的执行还是由大量的后台job来完成, Azure里面叫Function.</li>
<li>所以可以看出,传统互联网服务都是同步请求,快速完成任务,立马返回。开发人员大部分精力就放在了优化响应速度,优化负载均衡上面,保证每个用户都能得到及时的响应。</li>
<li>而云托管不一样,你点了某个按钮以后,Rest API会触发一些后台job,然后离开返回,返回的时候一般会带回来一个job id, 用于后期查看job的状态。</li>
<li>这个Job什么时候能完成是没法保证的。能不能完成也没法保证。前端只能不停地轮训(一般也是rest api),查看job完成状态,直到job完成或者失败。</li>
<li>云托管开发你可以理解为一大堆后台job,常驻内存跑着。这样好理解。</li>
<li>这些job会牵扯到一大堆的硬件操作,这是更加广泛的一个话题,我接触不多,本文不讨论。</li>
</ol>
<p>这样看来,云托管平台开发和一般的软件开发差别不大,也是一堆代码调来调去,只是异步操作很多,设计到很多状态和出错管理,还有超时管理,这些在传统web开发里面不大需要考虑。
另外,因为平台硬件资源有限,并且硬件成本高,往往不能提前准备很多硬件资源给你调用,所以资源编排调度是非常核心的功能。经常会遇到硬件资源不够用的情况。</p>
<p>如果想深入体验云托管平台开发,建议试一试openstack.</p>
免费开源软件方案系列
2023-03-31T14:41:00+00:00
/2023/03/31/免费软件方案系列
<h1 id="背景">背景</h1>
<p>开源代码是宝贵的社会财富, 是开发者智慧的结晶,是大家合作的宝贵成果,这些代码往往都对行业理解很深,有很高的业务价值。</p>
<p>这些代码应该被好好发掘利用,让它们能发挥它应有的巨大价值.</p>
<p>我想写一个文章系列,专门分析现有的开源技术方案。</p>
<ol>
<li>
<p>一是想给所有的创业者,想创业的人,或者正在创业的人, 以及需要通过技术来腾飞的老板们提供一些参考建议,降低大家的技术成本和风险,帮助大家提高成功的概率,以及,规模化现有的业务。通过技术让流程更标准,数据更准确更实时,合作成本更加低。</p>
</li>
<li>
<p>二是向所有的人推广免费的软件。让大家能有免费</p>
</li>
<li>
<p>二是想和技术同侪一起讨论这些技术,多一些学习和成长的机会。</p>
</li>
<li>
<p>四是想把自己的一些经验分享给大家。没有做成什么伟大的作品,那就分享一些自己的专业知识和经验吧。当作自己对社会的回馈。😊</p>
</li>
</ol>
<p>也欢迎有兴趣的同学一起分享。</p>
<h1 id="分享的原则">分享的原则</h1>
<p>这个系列大概会遵循以下基本原则:</p>
<ol>
<li>挑几个行业应用方向,每个方向选一款或几款开源软件,分析它的优缺点,以及应用价值.</li>
<li>挑选的软件要可以运行,并且能正常工作。</li>
<li>回顾该款开源软件的历史.</li>
<li>每个软件尽量挑选一个成功的应用案例,分析它是怎么帮助别人成功的。</li>
<li>分析它是否方便二次开发,文档是否成熟, 技术框架是否好维护。</li>
<li>分析它可以用于哪些场景,扛多大规模. 有一点可以提前说明一下,大部分找免费软件的公司,他们的业务规模是不需要考虑系统是否能承受得住的。</li>
<li>列举对标的商业软件。</li>
</ol>
<h1 id="方向">方向</h1>
<ol>
<li>任务管理系统</li>
<li>电商系统</li>
<li>会员系统</li>
<li>分销系统</li>
<li>ERP</li>
<li>MES系统</li>
<li>CMS, 内容管理系统</li>
<li>CRM, 客户关系管理系统</li>
<li>官网模版系统</li>
<li>小程序模版系统</li>
<li>免费小工具合集
<ol>
<li>网页版</li>
<li>小程序版</li>
</ol>
</li>
<li>程序监控系统sentry</li>
<li>仓储管理系统</li>
<li>低代码方案</li>
<li>棋牌系统</li>
<li>物联网IoT项目</li>
<li>大屏应用</li>
<li>数据分析</li>
<li>报表系统</li>
<li>爬虫系统</li>
<li>金融情报系统</li>
<li>图片处理工具</li>
<li>私有云方案</li>
<li>免费小工具集</li>
<li>音频处理工具</li>
<li>单机开源游戏</li>
<li>在线开源游戏</li>
<li>网页版开源游戏</li>
</ol>
<p>我也不是每个行业都了解,所以分析的时候可能会存在对业务需求理解不充分的情况,大家可以留言提出自己的需求和疑问,我尽量回答。</p>
<p>如果精力允许,我可能会尝试分析一下怎么打通各个系统。</p>
K8S基本概念 - Deployment
2023-02-26T02:30:00+00:00
/2023/02/26/k8s基本概念-deployment
<h1 id="k8s基本概念">K8S基本概念</h1>
<h2 id="一何为deployment">一、何为Deployment</h2>
<p>Deployment是一个定义及管理多副本应用(即多个副本 Pod)的新一代对象,与Replication Controller相比,它提供了更加完善的功能,使用起来更加简单方便。</p>
<p>如果Pod出现故障,对应的服务也会挂掉,所以Kubernetes提供了一个Deployment的概念 ,目的是让Kubernetes去管理一组Pod的副本,也就是副本集 ,这样就能够保证一定数量的副本一直可用,不会因为某一个Pod挂掉导致整个服务挂掉。</p>
<p>Deployment 还负责在 Pod 定义发生变化时,对每个副本进行滚动更新(Rolling Update)。</p>
<p>这样使用一种 API 对象(Deployment)管理另一种 API 对象(Pod)的方法,在 k8s 中,叫作”控制器”模式(controller pattern)。Deployment 扮演的正是 Pod 的控制器的角色。</p>
<h2 id="二k8s创建deployment资源流程">二、K8S创建Deployment资源流程</h2>
<ol>
<li>==用户通过 kubectl 创建 Deployment。==</li>
<li>==Deployment 创建 ReplicaSet。==</li>
<li>==ReplicaSet 创建 Pod。==</li>
</ol>
<p>对象的命名方式是:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>子对象的名字 = 父对象名字 + 随机字符串或数字
</code></pre></div></div>
<h2 id="三创建deployment">三、创建Deployment</h2>
<p>例1:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span> <span class="c1">#注意,如果用这个api就必须用标签选择器</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">nginx-deployment</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">selector</span><span class="pi">:</span>
<span class="na">matchLabels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">replicas</span><span class="pi">:</span> <span class="m">2</span>
<span class="na">template</span><span class="pi">:</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">nginx:1.7.9</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">80</span>
</code></pre></div></div>
<p>例2:在上面yaml的基础上添加了volume</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">nginx-deployment</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">selector</span><span class="pi">:</span>
<span class="na">matchLabels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">replicas</span><span class="pi">:</span> <span class="m">2</span>
<span class="na">template</span><span class="pi">:</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">nginx:1.8</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">80</span>
<span class="na">volumeMounts</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">mountPath</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/usr/share/nginx/html"</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">nginx-vol</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">nginx-vol</span>
<span class="na">emptyDir</span><span class="pi">:</span> <span class="pi">{}</span>
</code></pre></div></div>
<h2 id="四deployment资源属性">四、Deployment资源属性</h2>
<p>metadata</p>
<blockquote>
<p>指定一些meta信息,包括名字或标签之类的。
每一个 API 对象都有一个叫作 Metadata 的字段,这个字段是 API 对象的”标识”,即元数据,也是我们从 Kubernetes 里找到这个对象的主要依据。</p>
</blockquote>
<p>labels</p>
<blockquote>
<p>Labels是最主要的字段,是一组 key-value 格式的标签,k8s中的所有资源都支持携带label,默认情况下,pod的label会复制rc的label
k8s使用用户自定义的key-value键值对来区分和标识资源集合(就像rc、pod等资源),这种键值对称为label。
像 Deployment 这样的控制器对象,就可以通过这个 Labels 字段从 Kubernetes 中过滤出它所关心的被控制对象。</p>
</blockquote>
<p>Annotations</p>
<blockquote>
<p>在 Metadata 中,还有一个与 Labels 格式、层级完全相同的字段叫 Annotations,它专门用来携带 key-value 格式的内部信息。所谓内部信息,指的是对这些信息感兴趣的,是 Kubernetes 组件本身,而不是用户。所以大多数 Annotations,都是在 Kubernetes 运行过程中,被自动加在这个 API 对象上。</p>
</blockquote>
<p>selector</p>
<blockquote>
<p>过滤规则的定义,是在 Deployment 的”spec.selector.matchLabels”字段。一般称之为:Label Selector。
pod的label会被用来创建一个selector,用来匹配过滤携带这些label的pods。
可以通过kubectl get请求这样一个字段来查看template的格式化输出:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># kubectl get rc my-nginx -o template --template=""</span>
map[app:nginx11]
</code></pre></div> </div>
</blockquote>
<p>spec</p>
<blockquote>
<p>一个 k8s 的 API 对象的定义,大多可以分为 Metadata 和 Spec 两个部分。前者存放的是这个对象的元数据,对所有 API 对象来说,这一部分的字段和格式基本上是一样的;而后者存放的,则是属于这个对象独有的定义,用来描述它所要表达的功能。
这里定义需要两个副本,此处可以设置很多属性,主要是受此Deployment影响的Pod的选择器
spec 选项的template其实就是对Pod对象的定义
可以在Kubernetes v1beta1 API 参考中找到完整的Deployment可指定的参数列表.</p>
</blockquote>
<p>replicas</p>
<blockquote>
<p>定义的 Pod 副本个数 (spec.replicas) 是:2</p>
</blockquote>
<p>template</p>
<blockquote>
<p>定义了一个 Pod 模版(spec.template),这个模版描述了想要创建的 Pod 的细节。例子里,这个 Pod 里只有一个容器,这个容器的镜像(spec.containers.image)是 nginx:1.7.9,这个容器监听端口(containerPort)是 80。</p>
</blockquote>
<p>volumes</p>
<blockquote>
<p>是属于 Pod 对象的一部分。需要修改 template.spec 字段
例3中,在 Deployment 的 Pod 模板部分添加了一个 volumes 字段,定义了这个 Pod 声明的所有 Volume。它的名字叫作 nginx-vol,类型是 emptyDir。
关于emptyDir 类型:
等同于 Docker 的隐式 Volume 参数,即:不显式声明宿主机目录的 Volume。所以,Kubernetes 也会在宿主机上创建一个临时目录,这个目录将来就会被绑定挂载到容器所声明的 Volume 目录上。
k8s 的 emptyDir 类型,只是把 k8s 创建的临时目录作为 Volume 的宿主机目录,交给了 Docker。这么做的原因,是 k8s 不想依赖 Docker 自己创建的那个 _data 目录。</p>
</blockquote>
<p>volumeMounts</p>
<blockquote>
<p>Pod 中的容器,使用的是 volumeMounts 字段来声明自己要挂载哪个 Volume,并通过 mountPath 字段来定义容器内的 Volume 目录,比如:/usr/share/nginx/html。</p>
</blockquote>
<p>hostPath</p>
<blockquote>
<p>k8s 也提供了显式的 Volume 定义,它叫做 hostPath。比如下面的这个 YAML 文件:
…<br />
volumes:
- name: nginx-vol
hostPath:
path: /var/data
这样,容器 Volume 挂载的宿主机目录,就变成了 /var/data</p>
</blockquote>
<p>Configmap</p>
<blockquote>
<p>configmap是k8s的一个配置管理组件,可以将配置以key-value的形式传递,通常用来保存不需要加密的配置信息,加密信息则需用到Secret,主要用来应对以下场景:
使用k8s部署应用,当你将应用配置写进代码中,就会存在一个问题,更新配置时也需要打包镜像,configmap可以将配置信息和docker镜像解耦。
使用微服务架构的话,存在多个服务共用配置的情况,如果每个服务中单独一份配置的话,那么更新配置就很麻烦,使用configmap可以友好的进行配置共享。
其次,configmap可以用来保存单个属性,也可以用来保存配置文件。</p>
</blockquote>
<p>RKE</p>
<blockquote>
<p>RKE 全称是 Rancher Kubernetes Engine。可以通过 CLI 的方式独立于 Rancher 2.x 使用。可以在安装好 docker 的 linux 主机上,快速方便的搭建 Kubernetes 集群。在搭建生产可用的 Kubernetes 集群的工具里,RKE 的易用性应该是最好的。关于 RKE 和 Rancher 的关系,RKE 是 Rancher 2.x 中的一个重要组成部分,在 UI 上通过“自定义主机”创建的集群和通过“主机驱动”创建的集群,都是 Rancher Server 调用 RKE 模块来实现的。我们一般叫这种集群为 RKE 集群。英文文档和 Release Notes 里叫 Rancher-Launched Kubernetes cluster。</p>
</blockquote>
<p>应用服务网格(Istio)</p>
<blockquote>
<p>Istio 是一个提供连接、保护、控制以及观测功能的开放平台。
Rancher 集成了应用服务网格,支持完整的生命周期管理和流量治理能力,兼容 Kubernetes 和 Istio 生态。开启应用服务网格后即可提供非侵入的智能流量治理解决方案,其功能包括负载均衡、熔断、限流等多种治理能力。应用服务网格内置金丝雀、蓝绿等多种灰度发布流程,提供一站式自动化的发布管理。</p>
</blockquote>
<h1 id="refs">Refs</h1>
<ol>
<li><a href="https://zhuanlan.zhihu.com/p/126292353">【大强哥-k8s从入门到放弃12】Deployment资源详解</a>, 对K8S基本概念讲得很清楚。</li>
</ol>
Ubuntu搭建K3S+rancher
2023-02-26T01:45:00+00:00
/2023/02/26/ubuntu搭建k3s-rancher
<h1 id="mac上搭建">Mac上搭建</h1>
<p>k3s不能直接跑在Mac上,在Mac上可以通过K3D搭建,把k3s跑在容器里。 具体步骤参考<a href="https://itnext.io/kubernetes-rancher-cluster-manager-2-6-on-your-macos-laptop-with-k3d-k3s-in-5-min-8acdb94f3376">Kubernetes + Rancher Cluster Manager 2.6 on your macOS laptop with k3d/k3s in 5 min</a></p>
<p>步骤:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">brew install k3d kubectl helm</code></li>
<li>Use k3d to spin up a single-node Kubernetes cluster (using the k3s distro)
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>k3d cluster create k3d-rancher <span class="se">\</span>
<span class="nt">--api-port</span> 6550 <span class="se">\</span>
<span class="nt">--servers</span> 1 <span class="se">\</span>
<span class="nt">--image</span> rancher/k3s:v1.20.10-k3s1 <span class="se">\</span>
<span class="nt">--port</span> 443:443@loadbalancer <span class="se">\</span>
<span class="nt">--wait</span> <span class="nt">--verbose</span>
</code></pre></div> </div>
</li>
<li>Use helm to bootstrap cert-manager then Rancher to the cluster
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">### Install cert-manager with helm</span>
helm repo add jetstack https://charts.jetstack.io
helm repo update
kubectl create namespace cert-manager
helm <span class="nb">install </span>cert-manager jetstack/cert-manager <span class="se">\</span>
<span class="nt">--namespace</span> cert-manager <span class="se">\</span>
<span class="nt">--version</span> v1.5.3 <span class="se">\</span>
<span class="nt">--set</span> <span class="nv">installCRDs</span><span class="o">=</span><span class="nb">true</span> <span class="nt">--wait</span> <span class="nt">--debug</span>
kubectl <span class="nt">-n</span> cert-manager rollout status deploy/cert-manager
<span class="nb">date</span>
<span class="c">### Install the helm repos for rancher</span>
helm repo add rancher-latest https://releases.rancher.com/server-charts/latest
helm repo update
kubectl create namespace cattle-system
helm <span class="nb">install </span>rancher rancher-latest/rancher <span class="se">\</span>
<span class="nt">--namespace</span> cattle-system <span class="se">\</span>
<span class="nt">--version</span><span class="o">=</span>2.6.1 <span class="se">\</span>
<span class="nt">--set</span> <span class="nb">hostname</span><span class="o">=</span>rancher.localhost <span class="se">\</span>
<span class="nt">--set</span> <span class="nv">bootstrapPassword</span><span class="o">=</span>congratsthanandayme <span class="se">\</span>
<span class="nt">--wait</span> <span class="nt">--debug</span>
kubectl <span class="nt">-n</span> cattle-system rollout status deploy/rancher
kubectl <span class="nt">-n</span> cattle-system get all,ing
<span class="nb">date</span>
</code></pre></div> </div>
</li>
<li>Use the Rancher GUI to observe the cluster. browse to https://rancher.localhost. If you see this screen where you can’t manually accept the risk, you may have to use the thisisunsafe chrome trick. You do this trick by clicking on the red warning triangle and literally typing “thisisunsafe” (believe me!)
<ol>
<li>bootstrap password: <code class="language-plaintext highlighter-rouge">congratsthanandayme</code></li>
<li>bootstrap password还可以通过docker命令获取: <code class="language-plaintext highlighter-rouge">docker logs container-id 2>&1 | grep "Bootstrap Password:"</code></li>
<li>或者通过helm获取: <code class="language-plaintext highlighter-rouge">kubectl get secret --namespace cattle-system bootstrap-secret -o go-template='\n'</code></li>
</ol>
</li>
<li></li>
<li>可以通过<code class="language-plaintext highlighter-rouge">.localhost</code>来访问<code class="language-plaintext highlighter-rouge">127.0.0.1</code></li>
<li><code class="language-plaintext highlighter-rouge">brew install k3d kubectl helm</code></li>
<li><code class="language-plaintext highlighter-rouge">k3d version</code></li>
<li><code class="language-plaintext highlighter-rouge">k3d cluster create devcluster</code></li>
</ol>
<h1 id="在linux上搭建">在linux上搭建</h1>
<ol>
<li>搭建云服务, ubuntu系统.</li>
<li>安装docker: <code class="language-plaintext highlighter-rouge">apt install docker.io</code></li>
<li>安装k3s master node:
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl <span class="nt">-sfL</span> https://get.k3s.io | sh <span class="nt">-s</span> - <span class="nt">--docker</span> <span class="c"># 自动安装, 并且以docker为容器运行时,默认为containerd.</span>
<span class="nb">sudo </span>k3s kubectl get nodes <span class="c"># confirm it is running</span>
<span class="nb">cat</span> /var/lib/rancher/k3s/server/node-token <span class="c"># 获取master node token</span>
</code></pre></div> </div>
<p>K3s 包含并默认为containerd, 一个行业标准的容器运行时。要使用 Docker 而不是 containerd,可以用下面两种方法:</p>
<ol>
<li>使用<code class="language-plaintext highlighter-rouge">--docker</code>选项安装K3s: <code class="language-plaintext highlighter-rouge">curl -sfL https://get.k3s.io | sh -s - --docker</code></li>
<li>在 K3s 节点上安装 Docker。可以使用 Rancher 的一个Docker 安装脚本来安装 Docker:<code class="language-plaintext highlighter-rouge">curl https://releases.rancher.com/install-docker/19.03.sh | sh</code></li>
<li>参考<a href="https://docs.rancher.cn/docs/k3s/advanced/_index/">使用 Docker 作为容器运行时</a>.
卸载k3s: <code class="language-plaintext highlighter-rouge">/usr/local/bin/k3s-uninstall.sh</code></li>
</ol>
</li>
<li>查看containers: ` k3s ctr task ls`</li>
<li>安装K3S worker nodes:
需要用到上一步在master node获取到的master node token.
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-sfL</span> http://get.k3s.io | <span class="nv">K3S_URL</span><span class="o">=</span>https://<master_IP>:6443 <span class="nv">K3S_TOKEN</span><span class="o">=</span><join_token> sh <span class="nt">-s</span> - <span class="nt">--docker</span> <span class="c"># 用docker作为运行时。默认是 containerd作为运行时.</span>
</code></pre></div> </div>
</li>
<li>install helm:
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-fsSL</span> <span class="nt">-o</span> get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 <span class="nb">chmod </span>700 get_helm.sh ./get_helm.sh
</code></pre></div> </div>
<p>Notes: helm包下载很慢,容易断,如果脚本安装不了,就本地下载helm tar.gz, 然后scp拷贝到服务器上,然后解压安装,官方有文档:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">tar</span> <span class="nt">-zxvf</span> helm-v3.0.0-linux-amd64.tar.gz
<span class="nb">mv </span>linux-amd64/helm /usr/local/bin/helm
</code></pre></div> </div>
</li>
<li>更新helm repo
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> helm repo add stable https://charts.helm.sh/stable
helm repo add rancher-stable https://releases.rancher.com/server-charts/stable
helm repo update
</code></pre></div> </div>
</li>
<li>(optional)安装 <a href="https://cert-manager.io/docs/installation/kubectl/#installing-with-regular-manifests">cert-manager</a>, <code class="language-plaintext highlighter-rouge">kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml</code>
<ol>
<li><code class="language-plaintext highlighter-rouge">kubectl create namespace cattle-system && kubectl create namespace cert-manager</code></li>
</ol>
</li>
<li>通过helm安装rancher:</li>
</ol>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"># 注意rancher.localhost, localhost后缀是可以自动解析到本地127.0.0.1的</span>
helm <span class="nb">install </span>rancher rancher-stable/rancher <span class="se">\</span>
<span class="nt">--namespace</span> cattle-system <span class="se">\</span>
<span class="nt">--set</span> <span class="nb">hostname</span><span class="o">=</span>rancher.localhost <span class="se">\</span>
<span class="nt">--set</span> <span class="nv">bootstrapPassword</span><span class="o">=</span>verySecretivePasswordIndeed <span class="se">\</span>
<span class="nt">--set</span> ingress.tls.source<span class="o">=</span>secret
</code></pre></div></div>
<p>或者</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> helm <span class="nb">install </span>rancher rancher-latest/rancher <span class="nt">--namespace</span> cattle-system <span class="nt">--set</span> <span class="nb">hostname</span><span class="o">=</span>k3s-rancher.localhost
kubectl <span class="nt">-n</span> cattle-system rollout status deploy/rancher
kubectl <span class="nt">-n</span> cattle-system get deploy rancher
</code></pre></div></div>
<ol>
<li>获取setup link: <code class="language-plaintext highlighter-rouge">echo https://rancher.mydomain.com/dashboard/?setup=$(kubectl get secret --namespace cattle-system bootstrap-secret -o go-template='')</code></li>
<li>helm版本rancher获取rancher登录密码: <code class="language-plaintext highlighter-rouge">k3s kubectl get secret --namespace cattle-system bootstrap-secret -o go-template='\n'</code></li>
<li>rancher.ilibrary.me账号密码: <code class="language-plaintext highlighter-rouge">admin/BtfEQI1E7yajt9lx</code></li>
</ol>
<h1 id="在rancher里创建cluster">在Rancher里创建cluster</h1>
<p>参考<a href="https://selectfrom.dev/multiple-k8s-cluster-management-with-rancher-k3s-lightweight-k8s-cluster-for-edge-and-eea1f71175d0">multiple k8s cluster management with rancher k3s lightweight k8s cluster for edge</a></p>
<ol>
<li>按照说明里做。</li>
<li>做了以后可能要等1个多小时cluster才会从reconciling变成active.</li>
<li>变成active以后可以按页面的说明部署一个hello world玩玩。</li>
</ol>
<h1 id="notes">Notes</h1>
<ol>
<li><code class="language-plaintext highlighter-rouge">Error: INSTALLATION FAILED: repo rancher-stable not found</code>
<ol>
<li>helm repo add rancher-stable https://releases.rancher.com/server-charts/stable</li>
</ol>
</li>
<li><code class="language-plaintext highlighter-rouge">Error: INSTALLATION FAILED: Kubernetes cluster unreachable: Get "http://localhost:8080/version": dial tcp [::1]:8080: connect: connection refused</code>
<ol>
<li>export KUBECONFIG=/etc/rancher/k3s/k3s.yaml</li>
</ol>
</li>
<li><code class="language-plaintext highlighter-rouge">Error: INSTALLATION FAILED: chart requires kubeVersion: < 1.25.0-0 which is incompatible with Kubernetes v1.25.6+k3s1</code>
<ol>
<li>安装指定版本: <code class="language-plaintext highlighter-rouge">curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.23.16+k3s1 sh -</code></li>
<li>如果curl命令超时,可以手动下载k3s binary, 放<code class="language-plaintext highlighter-rouge">/usr/local/bin</code>目录下, 然后再运行上面的curl命令,脚本会跳过k3s下载步骤,走后面的配置流程。</li>
</ol>
</li>
<li>rainbow部署以后通过ingress发布,https://ranxxx.ixxx.me/rainbow不工作。
<ol>
<li>chart里面的host写的不对,改成目标host就好了。</li>
</ol>
</li>
<li><code class="language-plaintext highlighter-rouge">Error: INSTALLATION FAILED: unable to build kubernetes objects from release manifest: resource mapping not found for name: "rancher" namespace: "" from "": no matches for kind "Issuer" in version "cert-manager.io/v1" ensure CRDs are installed first</code>
<ol>
<li></li>
</ol>
</li>
<li></li>
</ol>
<h1 id="手动打包部署">手动打包部署</h1>
<ol>
<li>创建chart: <code class="language-plaintext highlighter-rouge">helm create mychart</code></li>
<li>打包: <code class="language-plaintext highlighter-rouge">helm package mychart</code> 或者指定输出的版本<code class="language-plaintext highlighter-rouge">helm package mychart --version=1.0.1</code></li>
<li>上传<code class="language-plaintext highlighter-rouge">mychart-1.0.1.tgz</code>到服务器: <code class="language-plaintext highlighter-rouge">scp mychart-1.0.1.tgz root@xxxx.com:~/</code></li>
<li>ssh到服务器, 安装chart:
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>helm <span class="nb">install </span>mychart ./mychart-1.0.1.tgz
<span class="c"># or</span>
helm <span class="nb">install</span> <span class="nt">-n</span> my-app <span class="nt">--namespace</span> myspace mychart-1.0.1.tgz
<span class="c"># or</span>
<span class="nb">tar</span> <span class="nt">-zxvf</span> mychart-1.0.1.tgz
helm <span class="nb">install</span> <span class="nt">-n</span> my-app <span class="nt">--namespace</span> myspace mychart/
</code></pre></div> </div>
</li>
<li>如果有在线tgz包,也可以在命令行直接安装在线包: <code class="language-plaintext highlighter-rouge">helm install jenkins https://example.com/charts/jenkins-1.2.3.tgz</code></li>
</ol>
<h1 id="自动打包部署">自动打包部署</h1>
<p><a href="https://docs.rancher.cn/docs/rancher2/pipelines/_index#%E9%85%8D%E7%BD%AE%E6%B5%81%E6%B0%B4%E7%BA%BF">参考Rancher官方CI/CD流水线文档</a></p>
<ol>
<li>进入Rancher CD页面</li>
<li>点击<code class="language-plaintext highlighter-rouge">git repos</code> -> <code class="language-plaintext highlighter-rouge">Create</code>,</li>
<li>填名称</li>
<li>填git repo地址: <code class="language-plaintext highlighter-rouge">git@github.com:xxxxx/myk8s.git</code>, <code class="language-plaintext highlighter-rouge">xxxxx/myk8s</code>代表个人github repo.
<ol>
<li>Rancher有个<a href="https://docs.rancher.cn/docs/rancher2/pipelines/example-repos/_index">官方example</a>: <code class="language-plaintext highlighter-rouge">https://github.com/rancher/pipeline-example-go.git</code></li>
</ol>
</li>
<li>手动生成ssh key pair, 把private/pub key填入rancher</li>
<li>把pub key设置到repo的deploy keys里面去。</li>
</ol>
<h1 id="rancher管理">Rancher管理</h1>
<ol>
<li></li>
</ol>
<h1 id="删除rancher">删除Rancher</h1>
<ol>
<li>列出所有安装好的App: <code class="language-plaintext highlighter-rouge">helm list -A</code></li>
<li>删除指定namespace下面的指定App: <code class="language-plaintext highlighter-rouge">helm uninstall rancher -n cattle-system</code></li>
<li></li>
</ol>
<h1 id="删除k3d">删除k3d</h1>
<ol>
<li>k3d是一个binary, 它会管理所有k3s相关的容器.</li>
<li><code class="language-plaintext highlighter-rouge">which k3d</code>, 查看k3d的路径.</li>
<li><code class="language-plaintext highlighter-rouge">k3d cluster delete -a</code>, 删除所有k3d创建的资源.</li>
</ol>
<h1 id="scripts">scripts</h1>
<p>k3d</p>
<ol>
<li>k3d cluster list</li>
<li>k3d cluster delete -a</li>
<li>k3d cluster create devcluster</li>
<li>k3d cluster create devhacluster –servers 3 –agents 1</li>
<li>k3d cluster delete devhacluster</li>
</ol>
<p>k3s</p>
<ol>
<li>kubectl get nodes</li>
<li></li>
</ol>
<p>Helm</p>
<ol>
<li>helm repo add jetstack https://charts.jetstack.io</li>
<li>helm repo update</li>
<li></li>
<li>安装的时候监控日志直到安装完成:
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>helm <span class="nb">install </span>cert-manager jetstack/cert-manager <span class="se">\</span>
<span class="nt">--namespace</span> cert-manager <span class="se">\</span>
<span class="nt">--version</span> v1.5.3 <span class="se">\</span>
<span class="nt">--set</span> <span class="nv">installCRDs</span><span class="o">=</span><span class="nb">true</span> <span class="nt">--wait</span> <span class="nt">--debug</span>
</code></pre></div> </div>
</li>
<li></li>
</ol>
<p>kubectl</p>
<ol>
<li>kubectl create namespace cert-manager</li>
<li>kubectl -n cert-manager rollout status deploy/cert-manager</li>
<li>kubectl cluster-info</li>
<li>kubectl get po -o wide -A</li>
</ol>
<h1 id="ref">Ref</h1>
<ol>
<li>https://computingforgeeks.com/install-kubernetes-on-ubuntu-using-k3s/</li>
<li>https://selectfrom.dev/multiple-k8s-cluster-management-with-rancher-k3s-lightweight-k8s-cluster-for-edge-and-eea1f71175d0</li>
<li><a href="https://www.hangge.com/blog/cache/detail_2428.html">K8s - Kubernetes重要概念介绍(Cluster、Master、Node、Pod、Controller、Service、Namespace)</a></li>
<li><a href="https://feisky.gitbooks.io/kubernetes/content/introduction/">Kubernetes指南</a></li>
<li><a href="https://jasonkayzk.github.io/2022/10/21/%E5%8D%95%E6%9C%BA%E9%83%A8%E7%BD%B2k3s/">单机部署k3s</a>, 提供了一个sh, <a href="https://gist.github.com/JasonkayZK/670d578f9bb494c56c6061466dd7314f">k3s-helm-install.sh</a></li>
<li><a href="https://github.com/cnrancher/autok3s">Autok3s</a>, Rancher 中国团队推出了一款针对 K3s 的效率提升工具:AutoK3s。只需要输入一行命令,即可快速创建 K3s 集群并添加指定数量的 master 节点和 worker 节点.</li>
<li><a href="https://vmguru.com/2021/04/how-to-install-rancher-on-k3s/">How to install rancher on k3s</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/126292353">【大强哥-k8s从入门到放弃12】Deployment资源详解</a>, 对K8S基本概念讲得很清楚。</li>
<li><a href="https://itnext.io/kubernetes-rancher-cluster-manager-2-6-on-your-macos-laptop-with-k3d-k3s-in-5-min-8acdb94f3376">Kubernetes + Rancher Cluster Manager 2.6 on your macOS laptop with k3d/k3s in 5 min</a></li>
<li><a href="https://medium.com/cetbiz/provision-k3s-cluster-on-macbook-using-k3d-60b807e12986">Provision K3S cluster on mac using K3D</a></li>
<li><a href="https://blog.yowko.com/helm-package/">打包Helm Chart</a></li>
<li><a href="https://docs.rancher.cn/docs/rancher2/pipelines/_index">Rancher CI/CD 官方流水线</a></li>
</ol>
找到自己的天赋和特长
2023-01-18T04:50:00+00:00
/2023/01/18/找到自己的天赋和特长
<p>想写这篇文章主要是因为最近玩游戏,排名怎么都上不去,很是气愤。</p>
<p>想了一下,其实自己很努力了,玩得也还可以,还这么不开心,是不是自己的方法不对,或者找错方向了?换件自己更加擅长的事情是不是更加容易有成就感?</p>
<p>于是想到写一篇日记,分析一下怎么找自己的天赋和特长,让自己能在成就感中开心快乐成长, 更重要的是,不要把时间和情绪浪费在自己不擅长的事情上面。</p>
<h1 id="天赋存在吗">天赋存在吗?</h1>
<p>我们从一个裹脚布话题开始,天赋存在吗?</p>
<p>首先,回答这个问题前,我们要承认,天赋是各种各样的。只有承认各种天赋的存在,才能客观的认可别人,各行各业都有牛人,人才。只有承认各种天赋的存在,才能找到自己的天赋和长处。</p>
<p>天赋存在于我们生活方方面面,下面举例来帮助承认天赋的存在。</p>
<ol>
<li>各种专业人士都是天赋高于普通人的。科学家,音乐家,发明者,奥运会冠军…</li>
<li>专业人士可能离我们有点距离,大家不一定服气,让我们来看看生活中的例子. 抖音上有各种才艺表演,唱歌,素描画画,跳舞,数钱,走平衡木等等. 这些都是普通人,但是在某一方向上达到的高度是远超普通人的。还有达人秀节目,里面各种奇特技能,不服不行。</li>
<li>再说简单一点的, 下面这些活动的表现都存在非常大的个体差异。
<ol>
<li>斗地主,算牌. 有的人轻松保持50%以上的胜率,有的人豆子一直负分。</li>
<li>下象棋</li>
<li>打台球</li>
<li>轮滑</li>
</ol>
</li>
</ol>
<h1 id="被碾压的情况">被碾压的情况</h1>
<ol>
<li>天赋被碾压.
<ol>
<li>生下来就有天赋上的差异,被碾压是正常的。</li>
<li>也有一种可能,你在某方面很有天赋,但是换了一个方向,一个不是你擅长的方向,那被别人的天赋碾压也是正常的。</li>
</ol>
</li>
<li>资源碾压
<ol>
<li>拿创业来说,人家二代钱多,资源多,可以同时尝试很多项目,只要一个项目成功,所有的成本就都赚回来了。普通人没有试错的成本。</li>
<li>打游戏来说,人家氪金,各种属性就拉满了,本来就不是一个公平的游戏了,被碾压很正常。</li>
</ol>
</li>
<li>时间,训练熟练度碾压
<ol>
<li>专业的技能需要多年的训练和培养。刚入行的人肯定是被碾压的。</li>
<li>对各种规则和属性不熟,不花精力去熟悉游戏规则,也不看别人的教程,最后就是蒙着眼睛被人打,被碾压。</li>
<li>熟悉了规则和属性,不去练习,真正上手的时候还是会踩坑。</li>
</ol>
</li>
<li>规模碾压
<ol>
<li>你天赋再牛,你的精力也是有限的,也不可能拼得过一群专业的人。团队分工合作,输出远超一个天赋牛的人。所以,拉人打群架也是一种非常明显的优势,可以一定程度上弥补天赋的办法。大部分情况下,我们做的事情还不需要特别高的天赋。</li>
<li>做内容,一旦某人/组织把内容做出规模优势以后,后面的人就很难再搞进来了。比如各种专业的数据机构,知网。</li>
<li>做服务的,一旦用户都到你平台上来了,竞争者就很难做了。</li>
</ol>
</li>
<li>出生碾压
<ol>
<li>你上个世纪出生在中国和出生在美国,人生轨迹肯定是完全不一样的。在美国,读书,出来后做技术,做金融的概率高。在中国,辍学,进工厂的概率高。没法比,直接碾压。</li>
</ol>
</li>
<li>不公平的游戏规则
<ol>
<li>有很多游戏是不公平的,比如中国的买房。买的早的对买的晚的那是无情的碾压。</li>
</ol>
</li>
</ol>
<h1 id="如何避免被碾压">如何避免被碾压</h1>
<ol>
<li>找自己擅长的方向。
<ol>
<li>学会专业知识,长时间训练积累。</li>
<li>坚持做某一件事情,形成规模优势。</li>
</ol>
</li>
<li>避开自己不会的方向, 或者不擅长的地方。</li>
<li>用策略,而不是蛮干。
<ol>
<li>找一些漏洞,技巧来取胜。</li>
<li>利用规则来取胜。游戏中长久不活跃的情况下再次进入会匹配较差的对手,可以赢。</li>
</ol>
</li>
<li>学会用杠杆
<ol>
<li>金钱杠杆。用钱买服务,拼凑自己的东西。</li>
<li>人员杠杆。简单一点说就是雇佣别人干活。</li>
<li>出书,写软件。这属于知识杠杆,边际成本低,可以规模化。</li>
</ol>
</li>
<li>形成良好的习惯</li>
<li>好的学习方式
<ol>
<li>用费曼学习法,强化自己的知识。</li>
<li>坚持做笔记。</li>
<li>坚持写博客。</li>
</ol>
</li>
<li>好的时间管理
<ol>
<li>不要把时间浪费在不重要的事情上。</li>
<li>不要把时间浪费在不重要的人身上。</li>
<li>集中精力,做好某一件事情后再规划其它事情。</li>
<li>一件事情尽量不要中途中断,中断以后捡起来的成本是非常高的。</li>
</ol>
</li>
</ol>
<h1 id="如何找到自己的天赋">如何找到自己的天赋</h1>
<p>人类智能的分类:</p>
<ol>
<li>语言智能,能够有效地用语言文字表达自己或者是理解他人,天生阅读、写作和沟通能力比较强,比如孔子、曹雪芹。</li>
<li>逻辑数字智能,对于数字和逻辑推理比较敏感,善于用数字工具来理解事物,比如说柏拉图、牛顿。</li>
<li>视觉空间智能,可以准确地识别出视觉空间的结构,比较善于把他们看到的、感觉到的,用具体形状的东西表现出来,代表人物就有毕加索、罗丹。</li>
<li>音乐智能,比如有一些孩子三岁就能在钢琴上弹肖邦的曲子。</li>
<li>人际智能,可以很敏锐地察觉别人的情绪、动机,这种人善于和别人合作,特别讨人喜欢。</li>
<li>内省智能,这类人自我反省能力、自我分析能力特别强,能够向内观测到自己的行为和动机,例如苏格拉底、亚里士多德。</li>
<li>运动智能,这类人善于运动,善于用动作来表达情绪,有很强的运动欲望,就比如有些人天生喜欢跳舞,喜欢户外运动。</li>
<li>自然观察智能,这类人喜欢观察外部环境,例如达尔文、爱因斯坦。</li>
</ol>
<p>找到自己的天赋,或者擅长的东西,或者喜欢的东西,这三样东西能帮助自己很好地成长。</p>
<p>找到自己擅长或者喜欢的东西需要不停地尝试,摸索。</p>
<p>如何找到天赋</p>
<ol>
<li>认识到我们的生命是独一无二的</li>
<li>相信自己可以创造属于自己的人生</li>
<li>生命是一个有机体,没有人能完全预测他人的生活,因为生活不是线性的,而是随机的,我们的生活就是一个不断即兴创作的过程。</li>
<li>屏蔽干扰</li>
<li>变换角度</li>
<li>勇敢地尝试</li>
<li>把热情当作提示器</li>
<li>评估自己的优势. SWOT 分析法。S 代表优势,包括你所有能动用的资源;W 代表弱势,最好的办法就是找到和你互补的人形成合作,让一个团队的能力达到最佳;O 代表机遇,做哪些事、认识哪些人、去哪个地方,可以让你做成这个事的几率更大一些,这些都是机遇;T 代表风险,如果风险太大或者失败了就没有再来一次的机会了,那最好就别做这个事。</li>
<li>找到你的小伙伴.</li>
</ol>
<p>最后,天赋拼不过的时候就拼积累! 从现在开始积累属于你自己的东西: 代码,博客,技能,公众号,工具, 人脉, 金钱, 善事, 善缘, 人品, 个人形象。</p>
营销方案大集合
2023-01-16T00:54:00+00:00
/2023/01/16/营销方案大集合
<p>营销以曝光,销售或者拉用户为目的。</p>
<p>注意: 有效不代表正确,以下很多营销方案都可能存在道德争议。</p>
<ol>
<li>抖音主播</li>
<li>KFC疯狂星期四
<ol>
<li>每周四,搞一篇情感文章,末尾找人买KFC安慰自己。今天是KFC疯狂星期四,打折,谁可以请我吃份套餐吗?</li>
</ol>
</li>
<li>杜蕾斯擦边球
<ol>
<li>中国足球射不进</li>
</ol>
</li>
<li>脑白金洗脑广告</li>
<li>小黄文场景带入</li>
<li>婆媳关系文章</li>
<li>彩礼,男女婚恋文章</li>
<li>吃瓜文章
<ol>
<li>各种明星出轨</li>
<li>官员作风问题</li>
<li>万小刀的文章</li>
</ol>
</li>
<li>以赚钱为引子</li>
<li>制造焦虑</li>
<li>工作,失业</li>
<li>孩子教育</li>
<li>保险</li>
<li>错误营销
<ol>
<li>故意制造系统bug,给人薅羊毛.</li>
</ol>
</li>
<li>假微信群二维码(吃瓜群二维码, 王思聪二维码),二维码扫码后是广告页。</li>
<li>积分到期提醒假短信.</li>
<li>紧缺物资营销(口罩,防疫用品)</li>
<li>进各种微信群发搞笑段子,软文</li>
<li>椰树卖椰奶,从小喝到大,大胸妹抖音直播</li>
<li>文件分享引流.</li>
<li>各种颠覆三观的引流文章.</li>
<li>营销行业,美女似乎是万能的.</li>
<li>猎奇。UFO,水怪,幽灵等话题.</li>
<li>同一个话题,正说反说,把双方粉丝通吃。</li>
<li>特殊话题营销
<ol>
<li>星座</li>
<li>姓氏</li>
<li>手相面相</li>
<li>抽签</li>
<li>婆媳关系</li>
<li>男人女人之间的矛盾
<ol>
<li>男人该买房买车</li>
<li>女人美貌不值钱</li>
<li>正反反复说。</li>
</ol>
</li>
<li></li>
</ol>
</li>
</ol>
从excel里面读取图片
2022-07-20T14:56:00+00:00
/2022/07/20/从excel里面读取图片
<p>Excel里面可以插入图片。插入以后用代码读出来比较麻烦。</p>
<h1 id="ruby">Ruby</h1>
<p>没有直接的解决方案。</p>
<p>一个间接的解决方案是把excel另存为html, 然后用<code class="language-plaintext highlighter-rouge">nokogiri</code>解析html, 获取图片引用.</p>
<p>Notes:</p>
<ol>
<li>实战显示, 嵌套的<code class="language-plaintext highlighter-rouge">frameset[1]/frame[1]</code>下面嵌套的<code class="language-plaintext highlighter-rouge">#document/html/body/table</code>没法正常通过nokogiri xpath读出, chrome <code class="language-plaintext highlighter-rouge">$x('//table')</code>也不行. 通过css也不行。里面的<code class="language-plaintext highlighter-rouge">#document</code>节点可能是js动态写入的。
<ol>
<li>读了一下htm的源码,确实是js动态写入的。最外层htm文件只是一个壳,它会另外加载<code class="language-plaintext highlighter-rouge">*.fld/</code>目录下的sheet001.htm文件,</li>
</ol>
</li>
<li>解决办法是手动解析<code class="language-plaintext highlighter-rouge">*.fld/</code>下的<code class="language-plaintext highlighter-rouge">sheet001.htm</code>文件. 这个方法有2个问题,另存为的图片是压缩图片, 得不到高清图片。第2个问题是内嵌在htm里面的base64binary不知道该如何解码, 也得不到高清图片.
<ol>
<li>(有问题,图片是缩略图, 解决办法见step 2)<code class="language-plaintext highlighter-rouge">sheet001.htm</code>里面有<code class="language-plaintext highlighter-rouge"><td> <v:shape o:gfxdata="xxxxx..." > <v:imagedata src="image060.png" o:title=""/></code>通过<code class="language-plaintext highlighter-rouge">base64</code>编码的图片内容,不用管。<code class="language-plaintext highlighter-rouge">td</code> 里面还嵌套了一个table, 里面有一个<code class="language-plaintext highlighter-rouge"><td><img width=68 height=68 src=image060.png v:shapes="图片_x0020_62"></td></code>, 可以读取图片.</li>
<li>缩略图的问题可以通过读取htm里面base64编码的原图片来解决. 问题又来了,这段base64编码的图片在一段comment里面, 读取起来有点痛苦.
<ol>
<li>先用<code class="language-plaintext highlighter-rouge">td.xpath("./comment()[1]")</code>获取comment的内容. 获取到comment以后还要去掉头尾到<code class="language-plaintext highlighter-rouge"><!-- --></code> 标签, 可以用<code class="language-plaintext highlighter-rouge">gsub!</code>去除.
<ol>
<li>这是一段完整的读取图片的代码<code class="language-plaintext highlighter-rouge">bnr1base64 = Nokogiri::XML(cols[index_map.with_indifferent_access["顶部图片1"]].xpath("./comment()[1]").text.gsub!("[if gte vml 1]>", "").gsub!("<![endif]", "")).children[0].attributes.filter{|k,v| v.name == "o:gfxdata"}.values.first.value</code></li>
<li>读取到base64以后就不知道该怎么解码了,这是<code class="language-plaintext highlighter-rouge">base64binary</code>, 不熟悉这个格式,尝试用工具解码为<code class="language-plaintext highlighter-rouge">jpeg</code>, <code class="language-plaintext highlighter-rouge">png</code>,都不能正常打开。</li>
<li>放弃。</li>
</ol>
</li>
<li>https://www.docx4java.org/forums/docx-java-f6/how-to-read-an-image-bar-pie-chart-from-docx-file-t2096.html</li>
</ol>
</li>
</ol>
</li>
<li>另外一种办法是在2的基础上, 解压xlsx文件,等到高清图片. 从html获取到图片名称以后,把图片名称映射到解压目录里的图片上. 这个方式也有无法修复的bug,见下面:
<ol>
<li>解压xlsx文件: <code class="language-plaintext highlighter-rouge">7z e test.xlsx</code></li>
<li>html里面获取的都是<code class="language-plaintext highlighter-rouge">image001.png</code>这种png格式。</li>
<li>xlsx解压后的图片是用户贴进去的格式,可能是png,也可能是jpeg,文件名格式是<code class="language-plaintext highlighter-rouge">image1.png</code>, <code class="language-plaintext highlighter-rouge">image22.jpeg</code></li>
<li>html的图片名需要映射(查找)到解压后的文件名上去.</li>
<li>致命Bug: 两者文件名末尾的数字不是一一对应的,会对歪。<code class="language-plaintext highlighter-rouge">image055</code>可能会对应到<code class="language-plaintext highlighter-rouge">image54.jpeg</code>上.</li>
<li>放弃.</li>
</ol>
</li>
<li>坑太深了,准备放弃ruby+nokogiri方案,用python搞一下看看.</li>
<li>放弃了纯ruby方案。用了python+ruby混合方案.方案如下:
<ol>
<li>先用python预处理, 抽取出所有的图片,并且按单元格名称命名, 保存为png格式(如果保存为jpeg格式,会抛透明通道无法保存的错误), 如”A1.png”, “Z1.png”等.</li>
<li>然后用ruby解析excel里面的文字内容,同时通过单元格位置去磁盘上查找指定的文件.</li>
<li>python code:
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">openpyxl</span>
<span class="kn">from</span> <span class="nn">openpyxl_image_loader</span> <span class="kn">import</span> <span class="n">SheetImageLoader</span>
<span class="kn">import</span> <span class="nn">os.path</span>
<span class="kn">import</span> <span class="nn">click</span>
<span class="s">""""""</span>
<span class="c1"># extract_excel_images.py
# 抽取excel里面的图片, 按单元格名称保存图片,保存格式为png
</span><span class="s">""""""</span>
<span class="o">@</span><span class="n">click</span><span class="p">.</span><span class="n">command</span><span class="p">()</span>
<span class="o">@</span><span class="n">click</span><span class="p">.</span><span class="n">option</span><span class="p">(</span><span class="s">'--path'</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">'Excel的路径'</span><span class="p">)</span>
<span class="o">@</span><span class="n">click</span><span class="p">.</span><span class="n">option</span><span class="p">(</span><span class="s">'--sheet'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s">"商品表格"</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">'从哪个sheet抽取'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">sheet</span><span class="p">):</span>
<span class="n">extract_image</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="c1"># '../doc/商品明细/飞天272/漫威56.xlsx'
</span><span class="k">def</span> <span class="nf">extract_image</span><span class="p">(</span><span class="n">file_path</span><span class="p">):</span>
<span class="c1"># loading the Excel File and the sheet
</span> <span class="n">pxl_doc</span> <span class="o">=</span> <span class="n">openpyxl</span><span class="p">.</span><span class="n">load_workbook</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
<span class="n">sheet</span> <span class="o">=</span> <span class="n">pxl_doc</span><span class="p">.</span><span class="n">worksheets</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="c1"># calling the image_loader
</span> <span class="n">image_loader</span> <span class="o">=</span> <span class="n">SheetImageLoader</span><span class="p">(</span><span class="n">sheet</span><span class="p">)</span>
<span class="c1"># get/create image folder
</span> <span class="nb">dir</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
<span class="n">image_dir</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">file_path</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># creaet folder with the same name as xlsx file, no extention.
</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">exists</span><span class="p">(</span><span class="n">image_dir</span><span class="p">):</span>
<span class="n">os</span><span class="p">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">image_dir</span><span class="p">)</span>
<span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># max row
</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">sheet</span><span class="p">.</span><span class="n">max_row</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
<span class="k">for</span> <span class="n">col</span> <span class="ow">in</span> <span class="p">[</span><span class="s">'O'</span><span class="p">,</span> <span class="s">'P'</span><span class="p">,</span> <span class="s">'Q'</span><span class="p">,</span> <span class="s">'R'</span><span class="p">,</span> <span class="s">'S'</span><span class="p">,</span> <span class="s">'T'</span><span class="p">,</span> <span class="s">'U'</span><span class="p">,</span> <span class="s">'V'</span><span class="p">,</span> <span class="s">'W'</span><span class="p">,</span> <span class="s">'X'</span><span class="p">,</span> <span class="s">'Y'</span><span class="p">]:</span>
<span class="k">try</span><span class="p">:</span>
<span class="c1"># get the image (put the cell you need instead of 'A1')
</span> <span class="n">image</span> <span class="o">=</span> <span class="n">image_loader</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"{}{}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">col</span><span class="p">,</span> <span class="n">row</span><span class="p">))</span> <span class="c1"># "A1" like cell name
</span> <span class="k">print</span><span class="p">(</span><span class="s">"Saving {}/{}{}.png"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span> <span class="n">image_dir</span><span class="p">,</span> <span class="n">col</span><span class="p">,</span> <span class="n">row</span><span class="p">))</span>
<span class="n">image</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="s">"{}/{}{}.png"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span> <span class="n">image_dir</span><span class="p">,</span> <span class="n">col</span><span class="p">,</span> <span class="n">row</span><span class="p">))</span> <span class="c1">#"xxx/A1.png",
</span> <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="c1"># can not be saved as jpeg, OSError: cannot write mode RGBA as JPEG
</span> <span class="k">except</span> <span class="nb">ValueError</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"图片{}{}不存在"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">col</span><span class="p">,</span> <span class="n">row</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s">"总共找到{}图片."</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">count</span><span class="p">))</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
<span class="n">hello</span><span class="p">()</span>
</code></pre></div> </div>
</li>
</ol>
</li>
</ol>
<p>下面一段是excel导出为htm文件后解析htm文件的代码,base64binary解码没有成功,去不到高清原图,没有啥实际意义.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 读取sheet里面的内容</span>
<span class="n">doc</span> <span class="o">=</span> <span class="no">Nokogiri</span><span class="o">::</span><span class="no">HTML</span><span class="p">(</span><span class="no">URI</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="s1">'./doc/data/bird.fld/sheet001.htm'</span><span class="p">))</span> <span class="c1"># 读取sheet的htm</span>
<span class="n">rows</span> <span class="o">=</span> <span class="n">doc</span><span class="p">.</span><span class="nf">xpath</span><span class="p">(</span><span class="s1">'//body/table/tr'</span><span class="p">)</span> <span class="c1"># 所有的行</span>
<span class="n">row</span> <span class="o">=</span> <span class="n">b</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># 第一行</span>
<span class="n">cols</span> <span class="o">=</span> <span class="n">row</span><span class="p">.</span><span class="nf">xpath</span><span class="p">(</span><span class="s1">'./td'</span><span class="p">)</span> <span class="c1"># 这一行所有的cell</span>
<span class="c1"># 读取base64binary image data</span>
<span class="n">bnr1base64</span> <span class="o">=</span> <span class="no">Nokogiri</span><span class="o">::</span><span class="no">XML</span><span class="p">(</span><span class="n">cols</span><span class="p">[</span><span class="n">index_map</span><span class="p">.</span><span class="nf">with_indifferent_access</span><span class="p">[</span><span class="s2">"顶部图片1"</span><span class="p">]].</span><span class="nf">xpath</span><span class="p">(</span><span class="s2">"./comment()[1]"</span><span class="p">).</span><span class="nf">text</span><span class="p">.</span><span class="nf">gsub!</span><span class="p">(</span><span class="s2">"[if gte vml 1]>"</span><span class="p">,</span> <span class="s2">""</span><span class="p">).</span><span class="nf">gsub!</span><span class="p">(</span><span class="s2">"<![endif]"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)).</span><span class="nf">children</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">attributes</span><span class="p">.</span><span class="nf">filter</span><span class="p">{</span><span class="o">|</span><span class="n">k</span><span class="p">,</span><span class="n">v</span><span class="o">|</span> <span class="n">v</span><span class="p">.</span><span class="nf">name</span> <span class="o">==</span> <span class="s2">"o:gfxdata"</span><span class="p">}.</span><span class="nf">values</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">value</span>
</code></pre></div></div>
<h1 id="python">python</h1>
<p>python可以用<code class="language-plaintext highlighter-rouge">openpyxl</code>和<code class="language-plaintext highlighter-rouge">openpyxl-image-loader</code>来实现.</p>
<p>这种方案有一个bug:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">pxl_doc = openpyxl.load_workbook(file_path)</code>, 这一句,如果sheet里面column编号超出<code class="language-plaintext highlighter-rouge">Z</code>, 则会加载excel失败,抛<code class="language-plaintext highlighter-rouge">string out of index</code>的错误,很愚蠢。</li>
</ol>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># installing the modules</span>
pip3 <span class="nb">install </span>openpyxl
pip3 <span class="nb">install </span>openpyxl-image-loader
</code></pre></div></div>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#Importing the modules
</span><span class="kn">import</span> <span class="nn">openpyxl</span>
<span class="kn">from</span> <span class="nn">openpyxl_image_loader</span> <span class="kn">import</span> <span class="n">SheetImageLoader</span>
<span class="c1">#loading the Excel File and the sheet
</span><span class="n">pxl_doc</span> <span class="o">=</span> <span class="n">openpyxl</span><span class="p">.</span><span class="n">load_workbook</span><span class="p">(</span><span class="s">'myfile.xlsx'</span><span class="p">)</span>
<span class="n">sheet</span> <span class="o">=</span> <span class="n">pxl_doc</span><span class="p">[</span><span class="s">'Sheet_name'</span><span class="p">]</span>
<span class="c1">#calling the image_loader
</span><span class="n">image_loader</span> <span class="o">=</span> <span class="n">SheetImageLoader</span><span class="p">(</span><span class="n">sheet</span><span class="p">)</span>
<span class="c1">#get the image (put the cell you need instead of 'A1')
</span><span class="n">image</span> <span class="o">=</span> <span class="n">image_loader</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'A1'</span><span class="p">)</span>
<span class="c1">#showing the image
</span><span class="n">image</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
<span class="c1">#saving the image
</span><span class="n">image</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="s">'my_path/image_name.jpg'</span><span class="p">)</span>
</code></pre></div></div>
机器视觉
2022-06-22T03:06:00+00:00
/2022/06/22/机器视觉
<h1 id="todo">TODO</h1>
<ol class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://www.kaggle.com/c/severstal-steel-defect-detection">Kaggle Stell Defect Detection</a>
<ol class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />下面的答案看得不明不白.</li>
</ol>
</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://zhuanlan.zhihu.com/p/309224023">用实力给自己正名,YOLOv5:道路损伤检测我最强!GRDDC’2020大赛报告</a>
<ol class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://link.zhihu.com/?target=https%3A//github.com/USC-InfoLab/rddc2020">冠军方案, YOLOv5</a>, 有代码</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://github.com/kevaldoshi17/IEEE-Big-Data-2020">第二名方案, YOLOv4</a>, 有代码</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />第三名好像只能有模型, 代码:https://pan.baidu.com/share/init?surl=VjLuNBVJGS34mMMpDkDRGQ 提取码: xzc6</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://github.com/titanmu/RoadCrackDetection">第四名方案</a></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />有评论说这个比赛没有大佬入场,得分都不高。</li>
</ol>
</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://github.com/sundyCoder/DEye">DEye (Keep an Eye on Defects Inspection) </a>, 基于tensorflow 1.4版的实现,多种缺陷检测。</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://blog.csdn.net/qq_29462849/article/details/84772263">基于yolov3的铁轨缺陷/裂纹检测</a></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://blog.csdn.net/qq_27871973/article/details/85009026">tiny YOLO v3做缺陷检测实战</a></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://github.com/WZMIAOMIAO/deep-learning-for-image-processing">深度学习图片处理视频教程</a></li>
</ol>
<h1 id="yolo">YOLO</h1>
<ol>
<li><a href="https://pjreddie.com/darknet/yolo/">YOLO3 教程</a>
<ol>
<li>检测图片内容: <code class="language-plaintext highlighter-rouge">./darknet detect cfg/yolov3.cfg yolov3.weights dog.jpg</code></li>
<li>等价于<code class="language-plaintext highlighter-rouge">./darknet detector test cfg/coco.data cfg/yolov3.cfg yolov3.weights dog.jpg</code></li>
<li>文章末尾有training教程</li>
</ol>
</li>
<li><a href="https://alexeyab84.medium.com/yolov4-the-most-accurate-real-time-neural-network-on-ms-coco-dataset-73adfd3602fe">YOLOv4教程</a>
<ol>
<li><code class="language-plaintext highlighter-rouge">make</code></li>
<li>Download weights: <code class="language-plaintext highlighter-rouge">https://github.com/AlexeyAB/darknet/releases/download/yolov4/yolov4.weights</code></li>
<li>检测: <code class="language-plaintext highlighter-rouge">./darknet detect cfg/yolov4.cfg yolov4.weights dinner.jpeg</code></li>
</ol>
</li>
<li><a href="https://zhuanlan.zhihu.com/p/172121380">YOLOv5教程</a>, <a href="https://github.com/ultralytics/yolov5">YOLOv5</a>
<ol>
<li><code class="language-plaintext highlighter-rouge">git clone https://github.com/ultralytics/yolov5</code></li>
<li><code class="language-plaintext highlighter-rouge">pip install -r requirements.txt</code></li>
<li><code class="language-plaintext highlighter-rouge">python detect.py --source 0</code>, 检测摄像头里面的内容</li>
</ol>
</li>
<li><a href="https://zhuanlan.zhihu.com/p/143747206">深入浅出Yolo系列之Yolov3&Yolov4&Yolov5&Yolox核心基础知识完整讲解</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/172121380">深入浅出Yolo系列之Yolov5核心基础知识完整讲解</a></li>
<li><a href="https://github.com/WongKinYiu/yolov7">YOLOv7</a></li>
</ol>
<h1 id="kaggle-检测题">Kaggle 检测题</h1>
<ol class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://www.kaggle.com/c/severstal-steel-defect-detection">Kaggle Stell Defect Detection</a>
<ol class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />里面的答案看得有点不明不白,还得继续努力.</li>
</ol>
</li>
<li class="task-list-item">[ ]</li>
</ol>
<h1 id="天池检测题">天池检测题</h1>
<ol class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://tianchi.aliyun.com/competition/entrance/231682/information?from=oldUrl">天池-铝型材表面瑕疵识别</a>
<ol>
<li><a href="https://tianchi.aliyun.com/competition/entrance/231682/forum">题解</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/343053914">知乎分析</a></li>
</ol>
</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://tianchi.aliyun.com/competition/entrance/531846/information">天池-瓷砖表面瑕疵检测</a></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://tianchi.aliyun.com/course?spm=5176.21852664.J_3941670930.8.80697d64vClduA">天池-AI学习知识库</a></li>
</ol>
<h1 id="mmdetection">MMDetection</h1>
<h1 id="百度飞桨">百度飞桨</h1>
<h1 id="工具">工具</h1>
<ol>
<li><a href="https://github.com/lutzroeder/netron">Netron</a>, 网络可视化工具.
<ol>
<li>Mac下可以brew安装: <code class="language-plaintext highlighter-rouge">brew install netron</code></li>
<li>https://netron.app/, 上传model, 在线浏览model.</li>
<li>git repo里面有sample model的链接。</li>
</ol>
</li>
<li><a href="https://github.com/heartexlabs/labelImg">labelimg</a>, 图片标记工具, python+qt</li>
<li><a href="">labelme</a>, 图片标记工具</li>
<li><a href="https://github.com/heartexlabs/label-studio">label studio</a></li>
<li><a href="https://github.com/developer0hye/Yolo_Label">Yolo Label</a></li>
</ol>
<h1 id="sample-codes">Sample codes</h1>
<ol class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://github.com/ageitgey/face_recognition">face_recognition</a>, 非常简单易懂的人脸识别命令行工具, 封装了<a href="http://dlib.net/">dlib</a>, 里面有很多example,有CNN,DNN训练人脸的代码,还有人脸识别的flask web api.
<ol class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />knn实验结果: 自带的图片里面人脸识别效果很好,用自己的照片,加上小孩的照片,小孩都被识别为我自己了。亚洲脸盲?</li>
</ol>
</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://github.com/sundyCoder/DEye">DEye (Keep an Eye on Defects Inspection) </a>, 基于tensorflow 1.4版的实现,多种缺陷检测。</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://github.com/topics/defect-detection">Github defect detection topic</a></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://github.com/adipandas/one-shot-steel-surfaces">One-Shot Recognition of Manufacturing Defects in Steel Surfaces</a>, 有paper,有code, 有website。</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://paperswithcode.com/task/defect-detection">For automatic detection of surface defects in various products</a>, 一个精选的papers实现列表,都有github代码.</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://github.com/Eatzhy/surface-defect-detection">surface defect detection papers</a>, 收集papers, 有qq群.</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://blog.csdn.net/qq_29462849/article/details/84772263">YOLOv3做裂纹检测 CSDN</a></li>
<li>
<table>
<tbody>
<tr>
<td>[ ] [缺陷检测算法汇总(传统+深度学习方式)</td>
<td>综述、源码](https://cloud.tencent.com/developer/article/1818274), you很多github链接,分类好了的。</td>
</tr>
</tbody>
</table>
</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />CSDN YOLO文章</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://blog.csdn.net/weixin_56184890/article/details/116563828?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-1-116563828-blog-84772263.pc_relevant_multi_platform_whitelistv2&spm=1001.2101.3001.4242.2&utm_relevant_index=4">yolov5——基于yolov5的钢材表面缺陷识别</a></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://blog.csdn.net/Alveus/article/details/125211061?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-4-125211061-blog-84772263.pc_relevant_multi_platform_whitelistv2&spm=1001.2101.3001.4242.3&utm_relevant_index=7">基于YOLOv5的汽车座椅缺陷检测</a></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://blog.csdn.net/qq_41821678/article/details/124827240?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-124827240-blog-125211061.pc_relevant_multi_platform_whitelistv1&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-124827240-blog-125211061.pc_relevant_multi_platform_whitelistv1&utm_relevant_index=1">基于yolov5-6.0版本的PCB板缺陷检测(Python/C++部署)</a></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://blog.csdn.net/weixin_39735688/article/details/121390085?spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-6-121390085-blog-84772263.pc_relevant_multi_platform_whitelistv2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-6-121390085-blog-84772263.pc_relevant_multi_platform_whitelistv2&utm_relevant_index=10">工业缺陷检测项目实战(二)——基于深度学习框架yolov5的钢铁表面缺陷检测</a></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://blog.csdn.net/yangjayhui/article/details/89485387?spm=1001.2101.3001.6650.7&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-7-89485387-blog-84772263.pc_relevant_multi_platform_whitelistv2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-7-89485387-blog-84772263.pc_relevant_multi_platform_whitelistv2&utm_relevant_index=11">基于YOLO-V3(Darknet框架)的缺陷检测</a></li>
<li>
<table>
<tbody>
<tr>
<td>[ ] [ECCV 2022 Oral</td>
<td>无需微调即可推广,上交大、上海人工智能实验室等提出基于配准的少样本异常检测框架](https://mp.weixin.qq.com/s/Q2kCTPJt-GGqkG5EBY-U0w), 有代码<a href="https://github.com/MediaBrain-SJTU/RegAD">Registration based Few-Shot Anomaly Detection</a>.</td>
</tr>
</tbody>
</table>
</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="https://github.com/zuruoke/watermark-removal">watermark-removal</a>, 水印去除.</li>
</ol>
<h1 id="视频教程">视频教程</h1>
<ol>
<li><a href="https://www.bilibili.com/video/BV1yi4y1g7ro/?spm_id_from=333.788.recommend_more_video.0&vd_source=472fb6e92fd30d1ed74891f42c6b5a38">Yolo系列讲解</a>, 有<a href="https://github.com/WZMIAOMIAO/deep-learning-for-image-processing">github repo</a>.</li>
<li></li>
</ol>
<h1 id="refs">Refs</h1>
<ol>
<li><a href="https://github.com/Charmve/computer-vision-in-action/blob/main/res/10%E5%BC%A0%E5%9B%BE%E5%B8%A6%E4%BD%A0%E8%AE%A4%E8%AF%86%E5%9B%BE%E5%83%8F%E5%88%86%E5%89%B2%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F.pdf">10张图带你认识图像分割的前世今生</a></li>
<li><a href="https://zh.d2l.ai/">动手学深度学习</a>, 面向中文读者的能运行、可讨论的深度学习教科书,含 NumPy/MXNet、PyTorch 和 TensorFlow 实现, 被全球 55 个国家 300 所大学用于教学</li>
<li><a href="https://github.com/titu1994/Neural-Style-Transfer">Neural Style Keras implementation</a></li>
<li><a href="https://blog.csdn.net/Cowry5/article/details/81037767">Neural Style Transfer神经风格迁移详解</a></li>
<li><a href="https://www.cnblogs.com/yifanrensheng/p/12547660.html">风格迁移论文理解–A Neural Algorithm of Artistic Style</a></li>
<li><a href="https://github.com/Charmve/Surface-Defect-Detection#1small-sample-problem">表面缺陷检测数据集</a>, 数据集非常多.</li>
<li><a href="https://zhuanlan.zhihu.com/p/195699093">知乎整理的28种数据集网盘下载链接</a></li>
<li><a href="https://tryolabs.com/blog/2017/08/30/object-detection-an-overview-in-the-age-of-deep-learning">Object Detection with Deep Learning: The Definitive Guide</a>, 2017年的文章, 对原理有些讲解, 里面包含很多有价值的链接和概念,对扩充知识面很有帮助.
<ol>
<li>有提到Haar和HOG. Haar是Opencv自带的人脸检测。</li>
<li>还有提到overfeat算法, <a href="https://blog.csdn.net/hjimce/article/details/50187881">基于Overfeat的图片分类、定位、检测</a></li>
<li>提到了YOLO</li>
<li><a href="https://tryolabs.com/blog/2018/01/18/faster-r-cnn-down-the-rabbit-hole-of-modern-object-detection">详解Fast R-CNN</a></li>
</ol>
</li>
<li><a href="https://mp.weixin.qq.com/s/gktWxh1p2rR2Jz-A7rs_UQ">经典卷积神经网络(CNN)结构可视化工具</a></li>
<li><a href="https://blog.csdn.net/qq_37541097/article/details/104410535">VGG网络结构详解与模型的搭建</a></li>
<li>VGG
<ol>
<li><a href="https://github.com/machrisaa/tensorflow-vgg">开源tensorflow vgg</a>, <a href="https://www.cs.toronto.edu/~frossard/post/vgg16/">怎么运行代码</a></li>
</ol>
</li>
<li><a href="https://blog.csdn.net/yishuihanq/category_11292436.html">CSDN 机器视觉专栏</a></li>
<li><a href="https://github.com/open-mmlab/mmdetection">MMDetection</a></li>
</ol>
免奔溃核酸码系统2-单体应用代码实现
2022-05-03T06:46:00+00:00
/2022/05/03/免奔溃核酸码系统2-代码实现
<p><a href="2022-04-26-免奔溃核酸码系统.md">上一篇文章</a>我们对核酸检测系统做了需求分析和技术选型,并完成了架构设计。</p>
<p>本文实现免奔溃核酸码系统, 先用rails把单体应用实现. 为了快速实现功能,会用一个开源的rails engine <a href="https://github.com/younthu/panda">panda</a>来实现.</p>
<p><a href="https://github.com/younthu/panda">panda</a>是一个业务代码聚合引擎,Rails Engine. 所有功能开箱即用。常用的用户系统,缓存和任务系统, 消息推送, 短信验证码, 管理后台, etc. 欢迎大家提交pr。</p>
<h1 id="基础框架搭建">基础框架搭建</h1>
<ol>
<li>生成rails project: <code class="language-plaintext highlighter-rouge">rails new crash-free-hesuan</code></li>
<li>添加panda engine到<code class="language-plaintext highlighter-rouge">Gemfile</code>: <code class="language-plaintext highlighter-rouge">gem 'panda', git: 'https://github.com/younthu/panda'</code>
<ol>
<li>这个engine自带用户管理系统,注册、登陆、登出API</li>
</ol>
</li>
<li>安装gem包: <code class="language-plaintext highlighter-rouge">bundle install</code></li>
<li>在<code class="language-plaintext highlighter-rouge">seeds.rb</code>里面加入:
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="no">Panda</span><span class="o">::</span><span class="no">AdminUser</span><span class="p">.</span><span class="nf">create!</span> <span class="ss">email: </span><span class="s1">'admin@example.com'</span><span class="p">,</span> <span class="ss">password: </span><span class="s1">'password'</span>
<span class="no">Panda</span><span class="o">::</span><span class="no">User</span><span class="p">.</span><span class="nf">create!</span> <span class="ss">email: </span><span class="s1">'test@test.com'</span><span class="p">,</span> <span class="ss">mobile: </span><span class="s1">'13712345678'</span><span class="p">,</span> <span class="ss">password: </span><span class="s1">'12345678'</span>
</code></pre></div> </div>
</li>
<li>初始化数据库: <code class="language-plaintext highlighter-rouge">rails db:create db:migrate db:seed</code></li>
<li>mount pand, 在<code class="language-plaintext highlighter-rouge">routes.rb</code>加入下面语句: ` mount Panda::Engine => “/panda”`</li>
<li>起服务器: <code class="language-plaintext highlighter-rouge">rails s</code></li>
<li>可以进入管理后台看现有的功能<code class="language-plaintext highlighter-rouge">http://localhost:3000/admin</code>,</li>
<li>至此, 系统的用户系统搭建完毕
<ol>
<li>创建用户: <code class="language-plaintext highlighter-rouge">/api/v1/users/create</code></li>
<li>登陆用户: ` /api/v1/users/sign_in`</li>
<li>获取个人信息: <code class="language-plaintext highlighter-rouge">/api/v1/users/my_info</code></li>
</ol>
</li>
</ol>
<h1 id="建模和api实现">建模和API实现</h1>
<ol>
<li>用户建模可以用panda里面自带的,不用重新设计。</li>
<li>核酸码建模
<ol>
<li>核酸码里面主要包含身份证,姓名,核酸码生成日期和一个hash, hash由前面三个字段+一个key(这个key由服务器生成, 固定)通过md5生成. 这4样信息编码到二维码里面, 医护人员验码的时候可以扫描二维码离线核验了。</li>
<li>生成核酸码模型: <code class="language-plaintext highlighter-rouge">rails g model code name id_number</code></li>
<li>二维码API, 这个API返回用户的<code class="language-plaintext highlighter-rouge">name</code>, <code class="language-plaintext highlighter-rouge">id_number</code>, 一个时间标签和一个hash值.</li>
<li>创建<code class="language-plaintext highlighter-rouge">app/controllers/codes_controller.rb</code>, 填入如下内容:
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">class</span> <span class="nc">CodesController</span> <span class="o"><</span> <span class="no">Panda</span><span class="o">::</span><span class="no">Api</span><span class="o">::</span><span class="no">BaseController</span>
<span class="k">def</span> <span class="nf">show</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">current_user</span>
<span class="n">time</span> <span class="o">=</span> <span class="no">DateTime</span><span class="p">.</span><span class="nf">now</span>
<span class="n">md5</span> <span class="o">=</span> <span class="no">Digest</span><span class="o">::</span><span class="no">MD5</span><span class="p">.</span><span class="nf">new</span>
<span class="nb">hash</span> <span class="o">=</span> <span class="n">md5</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="nf">name</span> <span class="o">+</span> <span class="n">user</span><span class="p">.</span><span class="nf">id_number</span> <span class="o">+</span> <span class="n">time</span><span class="p">)</span>
<span class="n">render</span> <span class="ss">json: </span><span class="p">{</span><span class="ss">name: </span><span class="n">user</span><span class="p">.</span><span class="nf">name</span><span class="p">,</span> <span class="ss">id_number: </span><span class="n">user</span><span class="p">.</span><span class="nf">id_number</span><span class="p">,</span> <span class="ss">time: </span><span class="n">time</span><span class="p">,</span> <span class="ss">hash: </span><span class="nb">hash</span><span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div> </div>
</li>
<li></li>
</ol>
</li>
<li>核酸检测分组上传建模:
<ol>
<li>每个分组包含小区信息,分组时间,分组包含哪10个居民,分组的试管编号, 测试结果</li>
<li>为了降低服务器压力,分组不在服务器预生成,由医护端先生成本地分组数据,等检测信息收集完毕以后一次性上传分组测试信息.</li>
<li>生成核酸检测分组模型: <code class="language-plaintext highlighter-rouge">rails g model group test_id community tested_at:datetime ids:jsonb status</code></li>
<li>分组上传API, 该API接受参数<code class="language-plaintext highlighter-rouge">test_id</code>,<code class="language-plaintext highlighter-rouge">community</code>, <code class="language-plaintext highlighter-rouge">tested_at</code>, <code class="language-plaintext highlighter-rouge">ids</code>, 分别表示试管id, 哪个小区, 什么时候测试的,该组里面所有接受检测的人的身份证id.
<ol>
<li>该API的数据不直接入库,先进入kafka, 然后入库.</li>
</ol>
</li>
<li>创建<code class="language-plaintext highlighter-rouge">app/controllers/groups_controller.rb</code>, 填入下面内容:
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">class</span> <span class="nc">GroupsController</span> <span class="o"><</span> <span class="no">Panda</span><span class="o">::</span><span class="no">Api</span><span class="o">::</span><span class="no">BaseController</span>
<span class="k">def</span> <span class="nf">create</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">current_user</span>
<span class="c1"># TODO: 把下面的内容用kafka来削峰填谷</span>
<span class="n">code</span> <span class="o">=</span> <span class="no">Code</span><span class="p">.</span><span class="nf">create!</span><span class="p">(</span><span class="ss">test_id: </span><span class="n">params</span><span class="p">[</span><span class="ss">:test_id</span><span class="p">],</span> <span class="ss">community: </span><span class="n">params</span><span class="p">[</span><span class="ss">:community</span><span class="p">],</span> <span class="ss">tested_at: </span><span class="n">params</span><span class="p">[</span><span class="ss">:tested_at</span><span class="p">],</span> <span class="ss">ids: </span><span class="n">params</span><span class="p">[</span><span class="ss">:ids</span><span class="p">])</span>
<span class="n">render</span> <span class="ss">json: </span><span class="n">code</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div> </div>
</li>
</ol>
</li>
<li>添加理由到<code class="language-plaintext highlighter-rouge">routes.rb</code>:
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">scope</span> <span class="ss">:api</span> <span class="k">do</span>
<span class="n">resource</span> <span class="ss">:code</span><span class="p">,</span> <span class="ss">only: </span><span class="p">[</span><span class="ss">:show</span><span class="p">]</span>
<span class="n">resource</span> <span class="ss">:group</span><span class="p">,</span> <span class="ss">only: </span><span class="p">[</span><span class="ss">:create</span><span class="p">]</span>
<span class="k">end</span>
</code></pre></div> </div>
</li>
<li>没了. 所有代码上传到<a href="https://github.com/younthu/crash-free-hesuan">crash-free-hesuan</a></li>
</ol>
<p>接下来会做压力测试,然后上K8S系统。</p>
<h1 id="ref">ref</h1>
<p>欢迎加微信群讨论,扫下面的二维码拉入群, 请备注’核酸码’:
<img src="/assets/img/wechat_qr.JPG" alt="wechat" /></p>
免奔溃核酸码系统
2022-04-26T04:17:00+00:00
/2022/04/26/免奔溃核酸码系统
<h1 id="背景">背景</h1>
<p>上海在4月初的时候换了核酸系统,切换到了某软用的核酸码系统。然后不出所料,上线之日就是系统崩溃之时。核酸码系统崩了。 然后为了避开高峰不让核酸码系统崩溃,还有凌晨安排搞核酸的。</p>
<p>匆忙上系统,难免会遇到需求不清晰,应用场景理解不透,测试不充分导致的业务逻辑方面的问题,性能的问题,安全的问题等。</p>
<p>当然,如果遇到经验不足的程序员,写出从服务器端拉二维码图片的代码,那怎么对服务器都没有办法了。</p>
<p>注: 本文本着研究的目的尝试分析问题和寻找解决方案,所有的分析、方案和代码都没有经过实际检验,不具备实际生产的价值。</p>
<h1 id="需求分析">需求分析</h1>
<p>我们先对核酸码系统做一些简单的需求分析,列出一些关键指标. 为了防止核酸码系统再次崩溃,我们会在分析的时候尽量把要求提高一点,防止突发情况把系统搞崩溃。</p>
<p>简单列出需求如下:</p>
<ol>
<li>2500万人,每人每天一次</li>
<li>核酸从早上9点开始,到下午3点结束。按平均算的话最低rps为 25000000/(6<em>60</em>60) = 1157</li>
<li>不强制要求核酸点错开做核酸。并发量计算如下:
<ol>
<li>假设每个小区为5千人, 那么整个上海5000个小区,</li>
<li>5组医护人员同时测。平均5秒测试完一个人。</li>
<li>所有小区在同一时间点开搞,这有一个前提,上海得有那么多医护人员。</li>
<li>那么意味着每秒请求量为5* 5 * 5000 = 125000</li>
</ol>
</li>
<li>按上一步的估算,假设取125000rps为压力上线。</li>
<li>完成一套流程至少需要一下四个API:
<ol>
<li>用户登陆</li>
<li>二维码生成</li>
<li>核验二维码.</li>
<li>上传检测分组数据.</li>
</ol>
</li>
<li>核酸检测中用户登陆,用户二维码生成、医生核验二维码和上传分组数据这四个基本步骤为一组,成为基本套件。
<ol>
<li>所以总rps得放大3倍,到375000rps</li>
<li>后面会分步分析每个API需要的资源以及对应的方案。实际的rps会按具体场景和技术方案做进一步矫正。</li>
</ol>
</li>
<li>假设本系统不连公安系统,不存在从内网拉数据的需求,不会存在特别耗时的数据同步场景.</li>
<li>要防止邪恶势力攻击.</li>
</ol>
<p>目前为止,我们能得到核心的需求,在正确满足业务需求的情况下,我们要能达到最大375000rps的吞吐量。</p>
<h1 id="性能分析">性能分析</h1>
<p>本系统最核心的性能问题为响应速度的问题。本文假设磁盘,内存都是可以无限的扩展的。</p>
<p>对于恶意攻击,这个话题太大,不在本文覆盖范围.</p>
<p>系统的性能瓶颈主要在以下几个方面:</p>
<ol>
<li>数据库读写操作。这是最容易遇到瓶颈的地方。</li>
<li>网络传输。</li>
<li>服务器端并发读请求。</li>
</ol>
<p>现在对三个核心api一一分析系统的性能问题。</p>
<p>登陆API:</p>
<ol>
<li>登陆的身份认证是走微信和支付宝的. 假设系统拿到小程序的认证信息以后不需要去支付宝或者微信做二次验证,可以直接用secret key完成验证.这样就不存在大量请求在服务器端阻塞的情况。</li>
<li>登陆的用户都可以通过身份证号来识别,这给直接在内存数据库登陆提供了可能。 在支付宝完成身份认证,把身份证id传回来后就可以直接把登陆状态写入redis这类内存数据库,完成系统登陆。</li>
<li>从上面的分析来看,登陆API相对简单,通过redis缓存数据可以消除大量读写数据库的情况,也不存在大量请求同时出去的情况.
<ol>
<li>假设一台redis可以完成10w请求每秒,那么125000并发用户只需要2台左右redis服务器.</li>
<li>前端服务器可以按5k/台配置, 配25台服务器. 这个也看具体的技术框架和计算量。如果是rails这种框架,堆服务器也没用了。而像nodejs,基于事件的异步系统,非常适合这种场景。go也很适合。java得看具体实现者的水平, 锁操作不好会造成很大资源浪费, 严重降低性能. 我以前做过一个各种框架并发性能对比的项目, 有总结过一些数据, 对比数据和代码可以看这里<a href="https://github.com/younthu/WebFrameworkBenchmark">NodeBenchmark</a>, 欢迎提pr讨论.</li>
<li>nginx一台可以满足需求. 假设每个请求能在100ms内完成, 那么nginx只要撑住1.25w并发就可以了, 不会触摸到65k tcp端口的上限, 系统层面一个tcp链接需要占用一个端口,最高只能有65536个端口可用,还得扣除系统保留的一些端口.</li>
</ol>
</li>
</ol>
<p>用户二维码API:</p>
<ol>
<li>从健康码解出来的字符串如下,这是一个离线码: https://qrcode.sh.gov.cn/enterprise/scene?f=2&m=QzVEMTk2MTMwRUIyNzdDMDhFNjA5Q0Y3MTcwQkFEMkIyMjc2Rjk0QzBFQkM2RkI5OERDMkE5Nzc2xxxxxxxxxx==687d6af4459ca2f9d908ca75f4b0081649746xxxxxxxxxxxxxxxx</li>
<li>从上面的二维码推测,
<ol>
<li>f代表码类型</li>
<li>m代表消息载体</li>
<li>m被’==’分割成两部分,前面一半应该是身份证信息和生成时间, ‘==’后面是类似key的东西,解码前半部分用的.</li>
<li>因为是离线码,可以猜测, 医护人员验身份的时候是离线扫二维码然后抽取信息直接登记在本地。满一管的时候再上传。所以医护人员验码的时候如果是十人一管,那么请求数量可以变为十分之一.</li>
</ol>
</li>
<li>从上面的分析可以看出,这个API可以在内存数据库完成,甚至可以在登陆的时候就把数据准备好,小程序里面根据登陆的数据直接生成二维码。</li>
<li>按上面的假设,这个API请求量只有登陆api的十分之一,所以对服务器要求更小. 服务器数量可以在登陆API预估服务器数量基础上加10%左右. web服务器加3台,总数达到28台。redis不需要加.</li>
</ol>
<p>核酸码核验API:</p>
<ol>
<li>根据前面的分析,核验逻辑可以写死在小程序里面,只需要医护人员启动小程序的时候把参数初始化好就可以了,这些不会成为性能瓶颈,跳过分析,也不用加机器.</li>
</ol>
<p>上传分组数据:</p>
<ol>
<li>这个API根据10人一管的原则,rps可以降低为前面的十分之一,也就是12500.</li>
<li>但是这个API会有大量的数据写入,会产生瓶颈,这是需要重点设计的地方。</li>
<li>因为这个数据写入不需要实时写入,可以通过message queue延时写入,所以数据写入压力大大降低。</li>
<li>本文会以Kafka为Message Queue,所有的分组数据写入Kafka,再延时写入数据库。后面数据库用sql还是nosql关系不是非常大了(有的数据库数据量上千万以后写入会变慢,值得仔细分析)。</li>
<li>Kafka可以轻松上百万qps, 所以配一台Kafka是够的。</li>
<li>在数据库写入方面,可以通过身份证日期做分裤分表, 达到提高写入数据的目的。也可以考虑用es, 或者mongodb做nosql,直接提高数据库写入速度。</li>
</ol>
<p>到目前为止我们大致得出一个初步预估,需要大概30台服务器,能扛住极端情况。</p>
<h1 id="技术选型及规划">技术选型及规划</h1>
<ol>
<li>nginx做web容器,每个nginx后面挂若干web服务器</li>
<li>先实现单体应用,然后扩展成分布式架构,通过K8S做集群部署。方便自动扩容,自动伸缩.</li>
<li>web用golang、nodejs或者java, 能满足高并发的需求。</li>
<li>Redis做缓存,用户登陆信息尽量入redis</li>
<li>在鼻拭子分组采集数据入库方面用Kafka做延时写入,削峰填谷。</li>
<li>管理后台用脚本语言实现.</li>
<li>mysql/psql分库分表,或者用es, mongo db等nosql数据库, 提高数据库写入速度.</li>
</ol>
<p>系统架构图如下:
<img src="../assets/img/crashfreehesuan.jpg" alt="免奔溃核酸系统" /></p>
<p>本文到此为止需求分析和技术选型架构设计完毕。</p>
<p>接下来会做代码<a href="2022-05-03-%E5%85%8D%E5%A5%94%E6%BA%83%E6%A0%B8%E9%85%B8%E7%A0%81%E7%B3%BB%E7%BB%9F2-%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0.md">实现</a>.</p>
<h1 id="参考">参考</h1>
<ol>
<li><a href="https://zhuanlan.zhihu.com/p/425247346">百万级QPS,支撑淘宝双11需要哪些技术</a></li>
<li><a href="https://blog.csdn.net/weixin_41709748/article/details/104625459">QPS,TPS,RPS你知道多少?</a></li>
<li><a href="https://segmentfault.com/a/1190000039702782">Kafka 性能篇:为何 Kafka 这么快?</a></li>
</ol>
<p>欢迎加微信群讨论,扫下面的二维码拉入群, 请备注’核酸码’:
<img src="/assets/img/wechat_qr.JPG" alt="wechat" /></p>
关于合作-怎么提高整体产出
2022-04-22T00:40:00+00:00
/2022/04/22/关于合作-怎么提高整体产出
<h1 id="合作">合作</h1>
<p>合作是指为了共同利益,每个人贡献自己的专业知识和劳动,创造社会价值的过程。
合作设计几大要素:</p>
<ol>
<li>时间。时间才是最宝贵的,后面说的提升效率就是围绕节省时间这个核心。</li>
<li>专业知识。这是资本家出钱的前提,如果没有专业技能,只能卖苦力. 买苦力也分熟练工和新手,也是有专业和业余的区别。</li>
<li>角色分工. 角色分工不同是为了更好更高效地利用合作者的时间和专业知识创造最大价值.理论上给一个人足够多的时间,他可以学会所有的专业知识。
<ol>
<li>但是,时间不允许.</li>
<li>并且,即使他懂了所有专业知识和技能,他的精力也不允许他同时充当那么多角色。</li>
</ol>
</li>
<li>钱, 或者报酬。劳动是为了收获,大部分人最直接的收获就是钱。还有的人收获的是成就感,也有人收获的是成长。 报酬的分配对激励合作者也非常重要,但不会是本文的重点.</li>
</ol>
<h1 id="为什么要合作">为什么要合作</h1>
<p>合作是为了做单个人做不了的事情,也是为了把每个个体产出最大化。</p>
<p>为什么要分工合作:</p>
<ol>
<li>做大事。每个人的能力是有限的,专业知识也是有限的。合作可以把不同的专业的人整合起来,做一个人做不了的事情。这也是工程存在的意义。
<ol>
<li>工程是指以某组设想的目标为依据,应用有关的科学知识和技术手段,通过有组织的一群人将某个(或某些)现有实体(自然的或人造的)转化为具有预期使用价值的人造产品过程。</li>
</ol>
</li>
<li>每个人做自己擅长的事情。分工越细,越专业,个体效率越高,单个流程出错的概率也越低。因为一个人在某个领域反复锻炼,必定会越来越精通,越来越熟练,踩的坑越多,出错的概率也会越少。分工以后合作就是必然的了。</li>
<li>每个人做自己喜欢的事情。做自己喜欢的事情得到的精神上的奖励比金钱奖励激励效果要好很多,也更容易带来幸福感。
4.</li>
</ol>
<h1 id="怎么提高合作效率">怎么提高合作效率</h1>
<p>怎么提高合作效率。可以从个人角度来分析,也可以团队利益角度来分析。</p>
<h2 id="相互帮助">相互帮助</h2>
<p>合作第一要义是相互帮助,相互分享,相互提高。这样解释可能太抽象了。</p>
<p>举个软件开发的例子,从公共知识领域和私有领域来说明为何需要相互帮助.</p>
<p>一个软件开发项目会牵涉到很多公共知识领域,各种开源组件,各种服务,这些都需要大量的精力去学。</p>
<ol>
<li>学习成本高。学那些公共知识的使用方法肯定是有成本的,有各种配置,会踩坑,会需要摸索最佳实践,还会需要根据自己的项目实际需求做一些调整。这些都会需要时间。</li>
<li>如果是涉及一些大型基础设施的搭建,那个学习成本就非常高了,因为大型的基础设施搭建会牵涉到很多各种概念和术语,让人抓不住重点,把时间浪费在不重要的东西上。如果有人踩过坑,几句话就可以指出方向,那么就可以节省大把的学习时间。</li>
<li>相互分享能显著节省大家的学习成本和时间,加快项目进度。</li>
</ol>
<p>软件开发项目也牵涉到很多私有的代码。我相信大部分人都不愿意接受陌生的私有代码。原因在于一下几点:</p>
<ol>
<li>代码没有说明文档。没有说明文档的情况下很难理解和掌握代码。</li>
<li>代码里面会有很多假设,但是往往没有注释说明。这种就是巨坑了。难度一点也不亚于女生让你猜她想要的是什么。</li>
<li>各种脏代码,冗余代码,淘汰的代码,这些都会消耗掉你大量的精力。</li>
<li>理解代码需要数据。那些数据很多时候只在实际生产中会有,开发环境下没有。没有具体的数据就很难理解业务代码。</li>
<li>在这些前提下,老人带新人,业务知识口口相传就非常重要了。</li>
</ol>
<p>除了知识部分外,还有一些规则性的东西,流畅性的东西,摸索成本也非常高。比如怎么开通权限,去哪里构建服务,找谁申请资源等等,这些如果让每个人自己在内网寻找,那是非常耗时,对团队是不利的。对自己也不利,你不帮别人,反过来,你需要帮助的时候别人也不会帮你。</p>
<p>从上面三个方面来讲,相互之间的知识传授,团队内部的培训是非常有必要的。</p>
<p>把知识和经验作为自己的私有财产无可厚非,但是从个人发展角度来讲,能帮助别人的人更容易得到别人的帮助和认可,也更容易得到更多的发展空间。</p>
<h2 id="任务明确">任务明确</h2>
<p>前面从节省个人时间成本的角度来说怎么提高合作效率。个人效率上去了,是不是就一切都OK了呢?</p>
<p>个人效率高是不够的。对于极个别特别突出的表现者,他们能自我驱动,给任务以后就可以自我推进快速完成。但是对于大部分合作者来说,必须明确任务范围.</p>
<p>如果没有明确的任务范围</p>
<ol>
<li>没人做交叉的任务。有些交叉的任务,到底该谁做?都可以做的事情不明确指定人就没人会去做。</li>
<li>没人做本来该做的任务。不明确定义范围,那么有些隐式的需求,比如安全性的问题,性能的问题,兼容性的问题,可扩展性的问题,大部分会被忽略掉。为何?因为这些非功能性的问题范围非常广,一个功能随随便便可以延伸出十几个非功能性的需求,显然,这些非功能性的需求是没法都做的。如果只做一部分,那肯定还是会有问题。反正都是会错,大部分人就干脆选择不做了。</li>
<li>相互牵制。一些依赖性的活没人做,那大家就会干等着。</li>
<li>有的人会被累死。有的人干活很积极,别人不做的事情他们都会想办法帮忙解决掉。对于这种积极性,我们需要给予肯定和鼓励,同时也需要保护。一直压着他们超负荷运转,他们迟早有一天会扛不住。这种人一般经验非常丰富,我们需要保护他们,帮忙把他们的精力尽可能地腾出来去辅导别人,而不是把他们累死。辅导别人是做乘法,拼命给他们堆活是简单的加法。</li>
</ol>
<p>需要明确责任范围和时间以及资源.</p>
<ol>
<li>明确的责任范围确保每个人都能知道自己该干什么,</li>
<li>明确的时间能让每个人都有都清晰的目标,</li>
<li>明确的资源是任务快速推进的保障。</li>
</ol>
<h2 id="别指责">别指责</h2>
<p>指责会带入坏情绪,对士气打击很大,也会破坏合作者之间的关系。
上级别指责下级,同级之间也别指责。</p>
<p>可能会有人说那要不要赏罚分明呢?需要的。这个最好交给制度去处理。把考核指标设立好,定期验收就行。或者交给团队外的人去处理,比如HR。或者需要提高的提高,需要提供帮助的提供帮助。指责没有帮助。</p>
<h2 id="有领队">有领队</h2>
<p>一定要有一个领队,他需要辅导别人,培训别人,协调资源, 攻坚,帮忙扫除障碍。</p>
<p>小公司的成败大部分情况下就看老板一个人, 小的团队看领队。领队能力要强,格局还得大。</p>
<p>领队还有一个很重要的角色,做决策,替成员背锅。如果成员出错了就得背锅,成员会自然而然的想要逃避所有的责任,不愿意干活,毕竟谁愿意背锅呢?领队可以通过决策来把责任摊自己头上,出了问题决策者来背锅。</p>
<h2 id="放权和信任">放权和信任</h2>
<ol>
<li>一定要让成员放手去干。成员需要成就感,需要展示自己的聪明智慧。鼓励个人自由发挥,能提高个人的创造性。 如果真要约束,可以提前把规则定好,架构提前设计好,每个人都在规则允许范围内,架构框架里发挥就可以了。</li>
<li>另外,应该允许犯错。多做多错,不做不错,说的就是不允许犯错的情况下,大家都会选择不做。鼓励尝试,允许犯错,犯错以后及时修正,反倒对快速迭代演进会有很大帮助。</li>
</ol>
<h2 id="不阻碍别人-不要让别人阻碍自己">不阻碍别人, 不要让别人阻碍自己</h2>
<ol>
<li>别人有依赖的事情优先做。不要挡着别人的道。</li>
<li>流程性的工作优先做,因为这样往往有时间限制,容易过期,招致不必要的吗。</li>
<li>需要帮忙的地方第一时间说出来。哪怕后面发现自己就能解决也没有关系。整体效率最重要。流程性的问题,配置的问题,私有代码的问题尽量问别人。</li>
<li>有依赖别人的工作需要第一通知别人时间做.不要被别人给挡着了。
<h2 id="规划和清单">规划和清单</h2>
<p>规划和清单与合作关系不大了,是从单一的视角来看怎么提高工作效率。</p>
</li>
</ol>
<p>无论是个人还是团队,都需要有规划和清单。规划是一个整体的安排,能保证事情有序的发生。清单是执行层面的待办列表,保证做事不错不漏。</p>
<h2 id="事情一件一件地做">事情一件一件地做</h2>
<p>如果能同时做很多事情当然很好。但是,不停地在不同事情间切换会极大地消耗精力,因为工作的中断成本比较高,做到一半停下来,等后面再捡起来的时候是需要成本去回顾问题,捋清思路的。最好的办法是事情一件一件地干掉,每件事情干掉打勾,再干下一件。</p>
<p>每件事都做了,每件事情都没有做好,极度消耗精力,也消耗意志,打击积极性和激情。所以要一件一件地做,不要同时做很多事情。</p>
<h2 id="敢于说不抛出风险敢于求助">敢于说不,抛出风险,敢于求助</h2>
<p>总会有做不完的事情,总会没法实现的需求。 这个时候需要敢于说不,把风险抛出来。不能把风险踹自己身上,误了大事。</p>
<p>不能为了图表现或者怕批评,把本来没法完成的事情承诺下来。事情做不完或者做不了是团队的风险,不应该个人承担。</p>
<ol>
<li>把问题理清楚然后把风险抛出去,这是专业的表现。</li>
<li>把问题承揽下来,风险不上报,会误导团队的决策。最后烂了,影响的是团队。</li>
<li>理清楚待办的工作最重要。不理待办事项大家就看不清需要做的工作和潜在的风险。</li>
</ol>
<p>搞不定的时候立马找别人帮忙,这是最明智的做法.</p>
<h2 id="透明沟通充分沟通">透明沟通,充分沟通</h2>
<p>宁愿多说,丰富啰嗦,也不要漏说,少说。</p>
<ol>
<li>很多拖延都是沟通不充分,不透明导致的。
<ol>
<li>被Block了不说,导致自己的任务没法按时交付</li>
<li>无法按时交付也不说,有风险也不提示,导致任务延期,耽误别人,耽误团队。</li>
</ol>
</li>
<li>很多误解也是沟通不充分,不透明导致的。
<ol>
<li>沟通不充分导致重复劳动,相互覆盖。</li>
<li>沟通不充分导致需求理解错误,多做错做。</li>
</ol>
</li>
</ol>
<h2 id="结对干活paired-coding">结对干活/Paired coding</h2>
<p>结对编程绝对是最高效的,这一经验也适用于结对干活。</p>
<p>结对的时候相互监督,一起讨论,相互答疑解惑,相互分享经验,相互学习,效率翻倍。</p>
<p>拉一个会,一起解决一个问题,速度快,效率高。注意要拉小会,不要拉大会,拉大会浪费大家时间。</p>
<h2 id="共同的愿景喜欢所做的工作">共同的愿景,喜欢所做的工作</h2>
<p>从团队角度来讲,大家要有共同的愿景,朝着同一个目标前进。这样不需要时不时地停下来同步大家的状态。成员也不会迷茫。</p>
<p>从个人角度来讲,要从工作中找到能让自己开心的东西,喜欢上所做的工作。</p>
<ol>
<li>有共同的愿景有助于大家力往一处使,推动产品快速发展迭代。</li>
<li>共同的愿景</li>
</ol>
<h2 id="每个人都能找到自己想要的东西">每个人都能找到自己想要的东西</h2>
<p>有自己想要的东西,心中有希望,个人才会有幸福感和充实感。</p>
<p>可以把下面几项做成问卷,问每一个成员优先级最高的3个地方。</p>
<ol>
<li>有的人想要学习技术,个人成长。</li>
<li>有的人想要研究业务,了解行业。</li>
<li>有的人需要挑战,满足自己。</li>
<li>有的人想要做伟大的产品,成就自己</li>
<li>有的人想要升职。</li>
<li>有的人想要加薪。</li>
<li>有的人想要工作生活平衡,能工作的同时兼顾家人。</li>
<li>有的人想要一份工作,在工作中找到有规律的社交生活。</li>
<li>有的人是为了体验生活(富二代们).</li>
<li>其它.
<h1 id="结尾">结尾</h1>
<p>本文没有讨论文化,也没有讨论冲突解决办法。只讨论了在简化的小技术团队内合作的问题。具体到大团队,跨团队的合作,需要考虑的点更多,甚至会牵扯到一些办公室政治问题,并且不可避免的需要向公司文化靠齐。</p>
</li>
</ol>
<p>大团队所有的合作都应该放在具体的文化氛围下进行,</p>
<ol>
<li>文化决定了具体的合作方式。</li>
<li>利益分配规则。</li>
<li>冲突解决方法。</li>
<li>小团队人少,大部分情况下没有明确的文化。</li>
<li>大公司的合作肯定是在文化约束下进行的。你肯定不能在一家外企里面强制大家天天加班,你也不可能在一家996公司里鼓励大家工作生活平衡。</li>
</ol>
<p>本文讨论的内容仅限于技术团队的合作,不适用业务团队的合作。做业务的因为牵涉到太多的利益冲突,竞争大于合作,大部分时候在撕逼,不撕逼不正常。</p>
K8S service部署笔记
2022-04-12T06:25:00+00:00
/2022/04/12/k8s-service部署笔记
<p>从零开始部署一个完整的服务到K8S集群。</p>
<p>前面开发了一个工具,一直在本地跑。现在需要发布到K8S里面,方便维护和远程访问,因为别人需要使用这个工具.</p>
<p>部署一个新的K8S需要覆盖下面的内容:</p>
<ol>
<li>创建Dockerfile. 这个用于build image.</li>
<li>创建Jenkins文件,用于Jenkins Job使用. build image和上传image.</li>
<li>创建Service chart</li>
<li>创建Jenkins Job用来打包docker image和service chart. 需要用到前面3步创建的内容。service chart可以用Jenkins打包,也可以不打包,手动改参数, 然后让ArgoCD自己去读git repo的目录解析chart.</li>
<li>部署servie chart到开发环境</li>
<li>数据持久化</li>
<li>数据库初始化</li>
<li>添加sidekiq pod</li>
<li>域名绑定</li>
</ol>
<p>初次构建K8S的时候尽量把一些安全方面的问题,合规方面的问题先不要考虑。尽量用公开的image,用简单常用的脚步和步骤,简化image的build, 简化chart的build.</p>
<p>用到的工具:</p>
<ol>
<li>Helm, 创建模版chart.</li>
<li>Docker, build image</li>
<li>Jenkins, 编译docker image.</li>
<li>ArgoCD, 部署集群. 在ArgoCD上新建APP, 指定chart路径,让ArgoCD构建App.</li>
<li>Rancher, 远程运维service.
6.
<h1 id="创建service-chart">创建service chart</h1>
</li>
</ol>
<p>拷贝了一份service chart(<code class="language-plaintext highlighter-rouge">adserver</code>下面拷贝的)到<code class="language-plaintext highlighter-rouge">mobile-mobile-dev-config</code>的<code class="language-plaintext highlighter-rouge">dce</code>分支下面, 里面包含如下内容:</p>
<ol>
<li>templates, 这下面的内容会被用来生成service chart.
<ol>
<li>_helpers.tpl,放置可以通过chart复用的模板辅助对象</li>
<li>deployment.yaml, 用来定义和部署pod, label, app name,ports, readiness/liveness等. service.yaml里面定义的服务会把pod暴露给集群。</li>
<li>hpa.yaml, HorizontalPodAutoscaler, 水平扩张,高可用的东西. 需要多少replicas(minReplicas, maxReplicas), memory, cpu等.</li>
<li>ingress.yaml, 域名配置
<ol>
<li>servicePort的值长度要在15个字符以内<code class="language-plaintext highlighter-rouge">crashreporthttp</code></li>
</ol>
</li>
<li>NOTES.txt, helm install时展示给用户的.</li>
<li>service.yaml,为你的工作负载创建一个 service终端基本清单。用来暴露deployment.yaml里面创建的内容。</li>
</ol>
</li>
<li>Chart.yaml,</li>
<li>values.yaml</li>
</ol>
<h1 id="ingress-配置">Ingress 配置</h1>
<ol>
<li>在<code class="language-plaintext highlighter-rouge">deployment.yaml</code>里面暴露pod端口<code class="language-plaintext highlighter-rouge">3000</code>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="s">---</span>
<span class="s">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">crashreport-app</span> <span class="c1"># Deployment name</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreport-app</span> <span class="c1"># 标签(Labels) 是附加到 Kubernetes 对象(比如 Pods)上的键值对。 标签旨在用于指定对用户有意义且相关的对象的标识属性,但不直接对核心系统有语义含义。</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">strategy</span><span class="pi">:</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">RollingUpdate</span>
<span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">revisionHistoryLimit</span><span class="pi">:</span> <span class="m">0</span>
<span class="na">selector</span><span class="pi">:</span>
<span class="na">matchLabels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreport-app</span> <span class="c1"># 匹配label</span>
<span class="na">template</span><span class="pi">:</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreport-app</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">serviceAccountName</span><span class="pi">:</span>
<span class="na">imagePullSecrets</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">crashreport-main</span>
<span class="s">image</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/:"</span> <span class="c1"># docker image url</span>
<span class="na">imagePullPolicy</span><span class="pi">:</span> <span class="s">Always</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">crashreporthttp</span>
<span class="s">containerPort</span><span class="pi">:</span> <span class="c1"># 暴露给NAT的端口.</span>
</code></pre></div> </div>
</li>
<li>
<p>在<code class="language-plaintext highlighter-rouge">service.yaml</code>里面把<code class="language-plaintext highlighter-rouge">targetPort</code>绑定到<code class="language-plaintext highlighter-rouge">app(pod)</code>上去, 请求来的时候会随机从<code class="language-plaintext highlighter-rouge">Deployment</code> 里选一个<code class="language-plaintext highlighter-rouge">pod</code>做转发.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">vip-crashreport</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreport-app</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">ClusterIP</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">port</span><span class="pi">:</span>
<span class="na">targetPort</span><span class="pi">:</span> <span class="s">crashreporthttp</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">crashreporthttp</span>
<span class="na">selector</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreport-app</span>
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">ingress.yaml</code>里面指定nginx, 然后指定host(domain name), path(/), 绑定serviceName和servicePort(第2步里面定义的).</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">extensions/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">crashreport-nginx</span>
<span class="na">annotations</span><span class="pi">:</span>
<span class="s">kubernetes.io/ingress.class</span><span class="pi">:</span>
<span class="s">nginx.ingress.kubernetes.io/proxy-body-size</span><span class="pi">:</span> <span class="s">100m</span>
<span class="s">nginx.ingress.kubernetes.io/enable-cors</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
<span class="s">nginx.ingress.kubernetes.io/configuration-snippet</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">more_set_headers "Server: K8SDemo";</span>
<span class="s">location /api/v1/notification-deactivation {</span>
<span class="s">return 404;</span>
<span class="s">}</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">tls</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span>
<span class="pi">-</span>
<span class="na">secretName</span><span class="pi">:</span>
<span class="na">rules</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">host</span><span class="pi">:</span>
<span class="na">http</span><span class="pi">:</span>
<span class="na">paths</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/"</span>
<span class="na">backend</span><span class="pi">:</span>
<span class="na">serviceName</span><span class="pi">:</span> <span class="s">vip-crashreport</span>
<span class="na">servicePort</span><span class="pi">:</span> <span class="s">crashreporthttp</span>
</code></pre></div> </div>
<p>4.</p>
</li>
</ol>
<h1 id="k8s网络结构">K8S网络结构</h1>
<ol>
<li>Pod内的容器之间可以通过容器名来访问.</li>
<li>每个 Service 都会自动分配一个 cluster IP(仅在集群内部可访问的虚拟地址)和 DNS 名,其他容器可以通过该地址或 DNS 来访问服务,而不需要了解后端容器的运行。<a href="https://feisky.gitbooks.io/kubernetes/content/introduction/concepts.html">Kubernetes概念</a></li>
<li>对外暴露service可以通过ingress来配置。</li>
</ol>
<h1 id="k8s数据库">K8S数据库</h1>
<p>数据库是有状态的,数据需要持久化,和app集群的搭建有点不一样。</p>
<p>搭建数据库需要引入下面的概念:</p>
<ol>
<li>StatefulSet, 替代Deployment的概念. Deployment用于部署无状态服务,StatefulSet用来部署有状态服务。这里会指定运行哪个db image,传哪些数据库参数。限制内存,cpu。
<ol>
<li>StatefulSet 是用于管理有状态应用的工作负载对象,与 ReplicaSet 和 Deployment 这两个对象不同,StatefulSet 不仅能管理 Pod 的对象,还它能够保证这些 Pod 的顺序性和唯一性。</li>
<li>StatefulSet里面必须要要有serviceName.这个会被用作域名的一部分,必须符合互联网域名命名规范。</li>
<li>与 Deployment 一样,StatefulSet 也使用规格中声明的 template 模板来创建 Pod 资源,但是这些 Pod 相互之间是不能替换的;除此之外 StatefulSet 会为每个 Pod 设置一个单独的持久标识符,这些用于标识序列的标识符在发生调度时也不会丢失。</li>
</ol>
</li>
<li>Service, 暴露StatefulSet用的. 可以通过statufulset-name.service-name这种域名的形式访问, 集群起来以后会默认有一个crashreport-db0.crashreport-db域名供使用, 该host-name crashreport-db0可以从endpoint的subsets里找到, crashreport-db是service name. 更加有区分性的例子是dev-config,dce分支下<code class="language-plaintext highlighter-rouge">cache/cache-sts.yaml</code>里面的内容.</li>
<li>StorageClass, 定义PV用的, PV是静态的磁盘, StorageClass可以动态扩展PV。PV的动态创建依赖于StorageClass对象。我们不需要手动创建任何PV,所有的工作都由StorageClass为我们完成。可以选postgresql或者mysql.由 StorageClass 动态创建的 PersistentVolume 将使用类中 mountOptions 字段指定的挂载选项。StorageClass要每个数据库一个独立的name, 避免串掉。</li>
<li>Secret. 保存一些私密信息,密码。</li>
<li>PersistentVolumeClaim. 把PersistentVolume挂StatefulSet上,挂载到某个路径上,给数据库保存数据用. storageClassName指向StorageClass里面指定的name. PVC和PV的绑定是通过StorageClassName进行的。</li>
</ol>
<h2 id="数据库初始化">数据库初始化</h2>
<p>正规的方法是起一个job.登录到pod里面去初始化,不折腾了。</p>
<h2 id="sidekiq部署">Sidekiq部署</h2>
<p>因为sidekiq和主app代码完全一样,所以直接在主app的deployment里面复制了一份deployment,改了一下环境变量,然后在app <code class="language-plaintext highlighter-rouge">entrypoint.sh</code>里面通过环境变量决定是起app还是起sidekiq. sidekiq通过redis通信,所以不需要把sidekiq的deployment通过service暴露给app, 省了一个service.</p>
<p>注意, 除了复制deployment外,还可以通过_helpers.tpl里面的模板来生成多份deployment, 不过那个模板语法需要学习,OPS不是我的最终目标,就不去折腾了。</p>
<p>下面是更新后的deployment文件:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span> <span class="c1"># app</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">crashreport-app</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreport-app</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">strategy</span><span class="pi">:</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">RollingUpdate</span>
<span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">revisionHistoryLimit</span><span class="pi">:</span> <span class="m">0</span>
<span class="na">selector</span><span class="pi">:</span>
<span class="na">matchLabels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreport-app</span>
<span class="na">template</span><span class="pi">:</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreport-app</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">serviceAccountName</span><span class="pi">:</span>
<span class="na">imagePullSecrets</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">crashreport-main</span>
<span class="na">image</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/:"</span>
<span class="na">imagePullPolicy</span><span class="pi">:</span> <span class="s">Always</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">crashreporthttp</span>
<span class="na">containerPort</span><span class="pi">:</span>
<span class="na">env</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">RAILS_ENV</span>
<span class="na">value</span><span class="pi">:</span> <span class="s">production</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">SERVICE_TYPE</span>
<span class="na">value</span><span class="pi">:</span> <span class="s">crashreport</span>
<span class="nn">---</span> <span class="c1">#sidekiq statefulset</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">StatefulSet</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">crashreportsidekiq</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreportkiq</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">updateStrategy</span><span class="pi">:</span> <span class="c1"># 这地方和Deployment不同,Deployment里面就叫strategy</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">RollingUpdate</span>
<span class="na">replicas</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">revisionHistoryLimit</span><span class="pi">:</span> <span class="m">0</span>
<span class="na">serviceName</span><span class="pi">:</span> <span class="s">crashreport-sidekiq</span> <span class="c1"># 必须要,作为namespace/domain的一部分, 要符合域名命名规范。</span>
<span class="na">selector</span><span class="pi">:</span>
<span class="na">matchLabels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreportkiq</span>
<span class="na">template</span><span class="pi">:</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">crashreportkiq</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">serviceAccountName</span><span class="pi">:</span>
<span class="na">imagePullSecrets</span><span class="pi">:</span>
<span class="na">containers</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">crashreport-main</span>
<span class="na">image</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/:"</span>
<span class="na">imagePullPolicy</span><span class="pi">:</span> <span class="s">Always</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">crashreporthttp</span>
<span class="na">containerPort</span><span class="pi">:</span>
<span class="na">env</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">RAILS_ENV</span>
<span class="na">value</span><span class="pi">:</span> <span class="s">production</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">SERVICE_TYPE</span>
<span class="na">value</span><span class="pi">:</span> <span class="s">sidekiq</span>
<span class="na">volumeMounts</span><span class="pi">:</span> <span class="c1"># PVC绑定</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">crashlog-disk</span>
<span class="na">mountPath</span><span class="pi">:</span> <span class="s">/app/crashlogs</span>
<span class="na">volumeClaimTemplates</span><span class="pi">:</span> <span class="c1"># PVC template</span>
<span class="pi">-</span> <span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">crashlog-disk</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">accessModes</span><span class="pi">:</span> <span class="pi">[</span> <span class="s2">"</span><span class="s">ReadWriteOnce"</span> <span class="pi">]</span>
<span class="na">resources</span><span class="pi">:</span>
<span class="na">requests</span><span class="pi">:</span>
<span class="na">storage</span><span class="pi">:</span> <span class="s">1000Gi</span>
</code></pre></div></div>
<p>#</p>
<h1 id="问题记录">问题记录</h1>
<ol>
<li>push docker image的时候每次tag应该不一样,要不然会抛错: <code class="language-plaintext highlighter-rouge">manifest invalid: manifest invalid</code></li>
<li><a href="https://www.hangge.com/blog/cache/detail_2428.html">K8s - Kubernetes重要概念介绍(Cluster、Master、Node、Pod、Controller、Service、Namespace)</a></li>
</ol>
<h1 id="ref">Ref</h1>
<ol>
<li><a href="https://feisky.gitbooks.io/kubernetes/content/introduction/concepts.html">Kubernetes概念</a></li>
</ol>
疫情,旅游,战争,封城
2022-04-05T14:57:00+00:00
/2022/04/05/疫情-旅游-战争-封城
<ol>
<li>2020年1月,新冠疫情爆发,1月23日10点,武汉封城. 全国封闭,戒严, 封闭一直到4月份.</li>
<li>2021年, 和朋友们全国跑了一圈,开心的一年。</li>
<li>2022年2月,俄罗斯攻打乌克兰,原以为是闪电战,结果打成了持久战。西方各国参与各种制裁俄罗斯。乌克兰被推前面和俄罗斯干,实质性亡国,可是被西方操纵者,成了一个傀儡和小丑。各种阴谋都出来了。强者暴力,弱者阴险,背后的人最坏,最喜欢两边都打死,好捡便宜。</li>
<li>2022年3月,上海爆发严重疫情, 陆续封控。月底封城,先封浦东(3月28日),后封浦西(4月1日)。线下物价暴涨,高价还买不到菜,不少居民食物短缺。</li>
<li>2022年3月21日,东航飞机坠毁。</li>
</ol>
<p>回顾一些细节。</p>
<p>2020年1月武汉封城的时候:</p>
<ol>
<li>乱。没有经验,乱成一锅粥吧。</li>
<li>全国老百姓把武汉人,湖北人当瘟疫。面对同胞的不幸,不仅不同情,施以援手,反而把同胞如瘟疫一样驱赶,这是愚蠢和自私的体现,残忍而又怕死。</li>
<li>乡下为了管理方便,把路都挖断了,禁止通行。</li>
<li>具体到社区。社区人对从外面回来的人就像对待犯罪一样,虽然人家啥事也没干,也没有被诊断为阳性。在那种情况下,感觉人人都有当审判长的冲动,想把别人判个永久囚禁,又有当执行官的热血,巴不得一起把可疑的人围殴致死。和战争没有太大的区别。愚蠢的人类。</li>
<li>人人都成了红卫兵,证明自己没有感染,然后到处找被感染的邻居,想知道到底是哪个倒霉蛋,千万要离他远一点,最好把他关笼子里,如果法律允许,我是说如果,最好直接把他打死。谁让他带毒呢,我还有大好前程,不想被感染。</li>
<li>谣言满天飞。有出于恐惧造谣的,有出于赚钱造谣的,有愚蠢乱起哄传播谣言的, 也有存粹为黑而黑的。唯独找不到真相,让人无所适从。非常痛恨谣言!</li>
<li>很多人担心会死,却不担心为何活着。</li>
</ol>
<p>2022年3月4月上海封城的时候, 自己亲身经历,体验更深刻,让人费解(阴暗)的事情就非常多了.</p>
<ol>
<li>商家发国难财. 8元3根的蜗居,快要封闭的时候超市28块卖2根。这个还不算夸张。</li>
<li>官老爷发国难财。官老爷的家属假扮官方领导,在各个群里卖货, 问他身份他不理,盖红章的文件总是第一个拿出来。10块不到的垃圾菜叶,卖68/78一套。 物业,居委,楼长,一个个都像个人精一样,啥也不说,不敢保证这些人没有拿分红,没有拿红包。(这可不是瞎编的,个人亲身经历)</li>
<li>大家买不到菜。有钱都买不到东西。 电商平台开了几天又被关闭。中间有临时放出去3小时让买菜的, 菜场人爆满,菜被一抢而光。</li>
<li>官方说人手不够,志愿者不够, 大家报名官方又不收, 费解。大家想报名志愿者,被志愿者和居委呵斥回去。百思不得其解。搞明白官老爷发财的逻辑以后明白了,志愿者,楼长,物业,居委,官老爷,商家,这一条链路有太多的秘密。
<ol>
<li>一开始别的区发生捐赠物资被挤压,老百姓没东西吃的情况,我以为是个别情况,也不相信阴谋。</li>
<li>直到我们小区的蔬菜被官老爷家属垄断,高价买垃圾菜的时候我才反应过来,事情不落自己头上的时候是不会。</li>
<li>我们有太多的疑问,但是每个参与到利益链条的人都支支吾吾,或者想办法支开话题,对问题避而不答。楼长多次支开一些关键问题,做为编外(编外都算不上)最底层的,能这么卖力的维护一些秘密,真的是不容易。希望这个社会少一些秘密,多一些公开透明。</li>
</ol>
</li>
<li>爆出核酸检测数据造假,这个我没有去查过,不知道真假。不过上海4月9号用随申办替换了原有的核酸码系统(健康云), 有文章分析是上面不相信以前的数据,所以用新系统。第一天随申办核酸码系统如期奔溃。</li>
<li>志愿者累得要死,还被骂。有的人太紧张了,非常敏感。有骂志愿者没有提醒大家站远一点的,有骂查核酸的时候检测者没有用酒精消毒手的,有骂菜没有及时送的… 人真的是可怜,能害怕成那样。</li>
<li>志愿者累的要死,还有一些高档社区批量团购shake shack,咖啡,昂贵汉堡,社区之间凡尔赛,徒增志愿者工作量,那些物品都需要志愿者转运。</li>
<li>那位徐总(徐新)在微信群里问了一句哪里有面包买, 想抢牛奶面包,引发截屏并全网转发。好像那一瞬间,大家都忘记了疫情,忘记了苦难。大家对有钱人的关注高于一切。一个跪舔的社会。</li>
<li>各种薅流量的人开始出现,写各种博眼球的文章。痛恨!</li>
<li>不可避免的站队,出现对立两派。有顶的,有骂的。各说各有理。这种事情不想理,让大家吵去吧。</li>
<li>邻居之间相互人肉,一定要找出那个阳性患者。</li>
<li>全上海大部分物资包里面发了中药。真的是匪夷所思。这到底是食材还是药材?是食材的话它不能果腹。是药材的话大家没病喝这个干什么?官方都明确说了,这个在没有感染的时候不能喝,喝了没用。难道全上海都感染了?</li>
<li>所有群里都在讨论防疫政策。没意义,讨论了和没有讨论一样,毛改变也做不了,都是口嗨。不如老老实实听政府的。</li>
<li>所有群里都在讨论抢菜。为了菜凌晨5点起来打开app抢菜,人的安全感太可怜了。</li>
<li>后面爆出很多保供物资是过期霉变的,还有很多无牌冒牌的各种产品,被行政处罚过的产品。甚至有人大胆猜测,这些产品一直存在,一直供应外卖的,现在封城,没有销路,自然进入政府采购渠道了。</li>
<li>捐赠的物资被截留,还有别人捐赠的物资里面珍贵一点的石斛之类的物品全被取出来了, 结果</li>
<li>后期很多人(小区邻居)在支付宝上查询到自己被冒充自愿者去工作了很久,这是被领空饷了吗?就是这么玄幻而又灵异!
<ol>
<li>这好像也解释了为何物业一边说志愿者不够,一边又不让大家当自愿者这事了。</li>
</ol>
</li>
<li>阴谋论也出来了。有人说高层两派抗疫政策不统一,在斗争。有说外部势力在引导舆论,制造对立。我不相信阴谋论,还是等事实真相吧。</li>
<li>封闭政策变化太快。一开始是封闭两天,然后续两天,再续两天…, 到后来浦东封闭4天直接变成十多天,浦西也是。所以,政策这东西不要往乐观了想。</li>
<li>没有人愿意负责,都机械地执行指令。一会儿挨家挨户贴封条,转头又立马把封条撕掉。没有新冠的重病患者没有核酸证明也进不了医院。组织度非常高,损失的是灵活性。这个问题不知道怎么评价,也许如果把指令细节整详细一点,大家就有机会灵活处理了。</li>
<li>不备吃的容易挨饿。有不少单身小伙伴瘦了几斤(他们自己说的)。没有吃的人容易没有安全感,容易慌,干啥事都受影响。米,面,糖,蛋,肉,纸,蔬菜,多备吃的。</li>
<li>这已经不是一场单纯的抗疫之战,更像是各股势力的抢钱大战。</li>
<li>不怕病毒,怕这些人。</li>
<li>天天看这些消息,看形形色色的人和各种奇葩事,很气愤,很压抑,很郁闷。唯有不理,才得片刻安宁。还是老老实实做我的事情,不管这些窗外事比较安心踏实。再次提醒,在家里多备一些食物和生活用品,反正是要吃要用的。</li>
</ol>
<p>社会也是有温情的:</p>
<ol>
<li>公司给准备了肉菜大礼包,看着挺丰盛的。我没有去领,留给需要的同事吧。</li>
<li>公司给每个人发1千元慰问金。如果农民也能拿到这1千元慰问金,对他们帮助应该挺大的。</li>
<li>收到很多亲戚朋友的关心问候, 很温馨。</li>
</ol>
<h1 id="2021年">2021年</h1>
<p>这一年疫情缓和不少,我出去跑了不少地方。</p>
<ol>
<li>大西北去了敦煌,新疆,西藏这三个地方,感受了祖国的大。那边自然环境和厚重的历史文化形成鲜明的对比,让人痴迷。</li>
<li>非常感谢陪我一起出去跑的小伙伴。人这一辈子太短了,能和你一起跑,一起睡同一间房的人非常少, 值得珍惜。</li>
</ol>
<h1 id="俄乌战争">俄乌战争</h1>
<p>2022年2月24日,俄罗斯对乌克兰发起特别军事行动,俄乌战争爆发。
强者喜欢暴力,弱者喜欢耍阴的,背后的人才是最坏的,两边都害。</p>
<ol>
<li>俄罗斯打乌克兰,肯定属于暴力的一方了。</li>
<li>乌克兰据说敢炮击自己老百姓,和老蒋挖黄河有得一拼了。</li>
<li>西方世界拼命制裁俄罗斯,拼命给乌克兰送武器。这样子搞下去,对立只会越来越严重,战争越大越严重,老百姓不得安宁。</li>
<li>到处都是阴谋. 俄军撤出布查后乌宣称俄有在布查对平民进行大规模屠杀,联合国随即开始调查布查惨案。外界看得云里雾里.</li>
<li>战争中的老百姓都是可怜,需要同情和帮助的。但是战争的双方以及参与制裁的国家,没有一个是干净的,战争背后都是肮脏的政治与阴谋。阻碍和谈的任何个人和集体势力都应该被历史唾弃。</li>
<li>停止战争,和平谈判才是解决问题的唯一途径。</li>
<li>愿世界和平,没有战争。</li>
</ol>
<h1 id="飞机失事">飞机失事</h1>
<p>2022年3月21日,东航MU5735失事,直接插进山里了。目前具体原因未知.</p>
ruby中的block
2022-03-08T02:07:00+00:00
/2022/03/08/ruby中的block
<h1 id="block-proc-lambda">Block, Proc, Lambda</h1>
<p>先贴一下基本概念,这些概念我一直没有搞太清楚,单纯去记忆太费脑子了。不过通过后面的例子,大家应该能轻松的区别这3概念。</p>
<ol>
<li>Block是一类,Proc和Lambda是另外一类。</li>
<li>Proc 和 Lambda 都是对象,而 Block 不是</li>
<li>参数列表中最多只能有一个 Block,但是可以有多个 Proc 或 Lambda</li>
<li>Proc和Lambda都是Proc对象</li>
<li>Lambda 对参数的检查很严格,而 Proc 则比较宽松</li>
<li>Proc 和 Lambda 中return关键字的行为是不同的
<ol>
<li>在lambda中,returen是指从lambda对象返回;</li>
<li>在proc中,returen不是从proc返回,而是从定义proc的作用域返回。</li>
</ol>
</li>
</ol>
<p>我已经开始头晕了。</p>
<h1 id="block的简单理解">Block的简单理解</h1>
<p>你可以理解为C/C++, Java, C#或者任何常见语言里面的代码块.
Block前面的函数调用理解为<code class="language-plaintext highlighter-rouge">while</code>,<code class="language-plaintext highlighter-rouge">for</code>循环之类的条件判断语句.</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kt">void</span> <span class="nf">func1</span><span class="p">()</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="n">a</span><span class="o">++</span> <span class="o"><</span> <span class="mi">10</span><span class="p">)</span> <span class="p">{</span>
<span class="n">cout</span> <span class="o"><<</span> <span class="s">"hello"</span> <span class="o"><<</span> <span class="n">endl</span><span class="p">;;</span>
<span class="k">if</span><span class="p">(</span><span class="n">a</span> <span class="o">==</span> <span class="mi">8</span><span class="p">)</span> <span class="k">return</span> <span class="n">a</span><span class="p">;</span> <span class="c1">// 注意这地方,这个return效果和下面ruby block里面return效果一样的, 退出当前方法。</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>和下面效果一样.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">condition</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="k">while</span> <span class="n">a</span> <span class="o"><</span> <span class="mi">10</span> <span class="k">do</span>
<span class="k">yield</span> <span class="n">a</span>
<span class="n">a</span><span class="o">+=</span><span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">func1</span><span class="p">()</span>
<span class="n">condition</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span><span class="o">|</span><span class="n">a</span><span class="o">|</span>
<span class="nb">puts</span> <span class="n">a</span>
<span class="k">return</span> <span class="n">a</span> <span class="k">if</span> <span class="n">a</span> <span class="o">==</span> <span class="mi">8</span><span class="p">;</span> <span class="c1"># 注意这地方,这个return效果和下面C++里面return效果一样的, 退出当前方法。</span>
<span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>
<p>可以简单理解ruby block和C++的block或者编辑器基本的block基本上一样,不过ruby的block还能接受参数。</p>
<h2 id="block举例">Block举例</h2>
<p>下面举的一个例子是作为配置管理工具, 我们希望在<code class="language-plaintext highlighter-rouge">dev</code>和<code class="language-plaintext highlighter-rouge">prod</code>两种不同环境下有不同的处理逻辑。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">in_dev</span> <span class="n">call_index</span>
<span class="k">return</span> <span class="k">unless</span> <span class="nb">self</span><span class="p">.</span><span class="nf">dev</span>
<span class="nb">puts</span> <span class="s2">"in dev </span><span class="si">#{</span><span class="n">call_index</span><span class="si">}</span><span class="s2">"</span>
<span class="k">yield</span>
<span class="nb">puts</span> <span class="s2">"return from dev </span><span class="si">#{</span><span class="n">call_index</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">in_prod</span>
<span class="k">return</span> <span class="k">unless</span> <span class="nb">self</span><span class="p">.</span><span class="nf">prod</span>
<span class="nb">puts</span> <span class="s2">"in production"</span>
<span class="k">yield</span>
<span class="nb">puts</span> <span class="s2">"return from production"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">condition_test</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">dev</span> <span class="o">=</span> <span class="kp">true</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">in_dev</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"{in_dev with block1}"</span> <span class="p">}</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">in_dev</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"{in_dev with block2}"</span><span class="p">;</span> <span class="k">return</span> <span class="s2">"in_dev"</span><span class="p">}</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">in_prod</span> <span class="p">{</span><span class="k">return</span> <span class="s1">'bbb'</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>
<p>output</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">in </span>dev 1
<span class="o">{</span>in_dev with block1<span class="o">}</span>
<span class="k">return </span>from dev 1
<span class="k">in </span>dev 2
<span class="o">{</span>in_dev with block2<span class="o">}</span>
</code></pre></div></div>
<p>可以看到,如果当前环境是<code class="language-plaintext highlighter-rouge">dev</code>环境,<code class="language-plaintext highlighter-rouge">condition_test</code>只会执行第<code class="language-plaintext highlighter-rouge">2</code>,<code class="language-plaintext highlighter-rouge">3</code>两条语句, 因为第3条语句会<code class="language-plaintext highlighter-rouge">return</code>. 如果是prod环境, 只会执行第<code class="language-plaintext highlighter-rouge">4</code>条语句<code class="language-plaintext highlighter-rouge">self.in_prod {return 'bbb' }</code>.</p>
<h1 id="proc和lambda">Proc和Lambda</h1>
<p>目前没有找到很好的场景去理解它们俩。</p>
activeadmin后台filter性能问题
2022-01-29T08:34:00+00:00
/2022/01/29/activeadmin后台filter性能问题
<ol>
<li>3w条log</li>
<li>1千多条issue.</li>
<li>log和issue是多对一关系。</li>
<li>issue页面本地渲染要30多秒。</li>
<li>把filter去掉以后300ms渲染完成.</li>
</ol>
jwt认证
2022-01-24T04:04:00+00:00
/2022/01/24/jwt认证
<p>K8S不同services直接鉴权以前用secret key, 现在想换成基于x509证书的。</p>
<h1 id="jwt">JWT</h1>
<p>JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.</p>
<p>包含3部分:</p>
<ol>
<li>Header
<pre><code class="language-json5">{
"alg": "HS256",// 签名算法
"typ": "JWT" // token的类型,JWT token统一写为JWT
}
</code></pre>
</li>
<li>Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
</code></pre></div> </div>
<p>除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
</code></pre></div> </div>
</li>
<li>
<p>Signature
Signature 部分是对前两部分的签名,防止数据篡改。</p>
<p>首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。</p>
</li>
<li>Base64URL
可以用Base64URL打包JWT,这样就可以把token放url末尾了。Base64URL是base64的变体,主要是在base64的基础上把url的一些特殊字符做处理。
<h2 id="jwt和x509">JWT和X.509</h2>
</li>
<li>JWT是一种基于数字证书的认证方法.</li>
<li>JWT是 JSON Web Tokens的缩写。</li>
<li>X.509是密码学里公钥证书的格式标准</li>
</ol>
<h1 id="refs">refs</h1>
<ol>
<li><a href="jwt.io">JWT Debugger</a></li>
<li><a href="https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html">JSON Web Toke 入门教程, 阮一峰</a></li>
</ol>
分析一个超级慢的psql插入
2022-01-06T01:54:00+00:00
/2022/01/06/分析一个超级慢的psql插入
<p>项目组做一个日志分析的工具,每天会导入大概3万条日志,插入到psql. 导入后再做分析,分给对应的人。</p>
<p>日志导入这一块一直是个头疼的事情。先描述基本数据:</p>
<ol>
<li>每次导入的日志总量大概在3w条. json文件大小在1-2G。
<ol>
<li>2G日志压缩后16M上传到百度网盘了,链接: https://pan.baidu.com/s/12vO-aGGYfEGySDCe20V-oA 密码: ok7s</li>
</ol>
</li>
<li>每次导入时内存占用在十多个G。用的MacBook Pro, 32G内存.</li>
<li>导入时通过批量插入的方式完成, 一次插入1000条记录, 总共3万条记录.</li>
<li>有一张日志表,用来关联日志和分析结果的,一对一的关系,3万条记录。</li>
<li>为了后续分析方便,每条日志都会把原始数据original_json保存到一个字段,jsonb格式,大小大概在50k-70k左右, 小的有11k的, 大的有到280K的. 这个字段早期就创建了。</li>
<li>为了后续分析方便,每条日志都会把stacktrace保存到一个字段,jsonb格式,大小大概在50k左右, 大的有到120K的.</li>
<li>导入完成后会做分析,产生分析结果。分析结果主要是需要上报的bug。bug数量不多,总数最多也就几百吧。</li>
<li>每次导入前会把以前的日志清除。但是保留分析结果。</li>
<li>最开始导入的时候大概20多分钟可以搞定。</li>
<li>导入几次以后就变成800多分钟才能搞定了。</li>
<li>再后来变成1400多分钟,一整天。数据量没有变过。</li>
<li>确定不是后端代码的问题。把插入语句注释掉以后所有流程跑完只需要几分钟.</li>
</ol>
<p>前期分析:</p>
<ol>
<li>内存应该是够的。不排除内存交换到磁盘太多导致的速度问题。</li>
<li>CPU够。</li>
<li>速度慢主要在批量插入那一块。</li>
<li>所以下一步主要方向先分析psql性能日志.</li>
</ol>
<h1 id="postgres-日志分析">postgres 日志分析</h1>
<p>用pgbadger分析日志</p>
<ol>
<li>安装pgbadger: <code class="language-plaintext highlighter-rouge">brew install pgbadger</code></li>
<li>分析日志: <code class="language-plaintext highlighter-rouge">pgbadger '/Users/xxxx/Library/Application Support/Postgres/var-12/postgresql.log'</code></li>
<li>获取分析报告: <code class="language-plaintext highlighter-rouge">open ./out.html</code>
<ol>
<li>parsed events: 2878958</li>
<li>啥也没有看出来。</li>
</ol>
</li>
</ol>
<p>现在不知道怎么搞了。</p>
<p>试试进psql进行单条插入和count查询,看看速度。</p>
<h1 id="解决方法">解决方法</h1>
<p>把batch insert 1000 改成10条每batch, 时间瞬间降下来了,同样的数据71分钟导入完成.</p>
<p>具体原因未知。留一个历史疑案吧。</p>
<p>更新2:</p>
<p>71分钟还是慢了一点,后来做成增量式插入了。增量一次大概几分钟可以搞定.</p>
<h1 id="refs">refs</h1>
<ol>
<li><a href="https://www.jianshu.com/p/ac4cb5a1f842">记一次PostgreSQL数据库超级慢故障排除</a></li>
</ol>
ubuntu编译opencv
2021-12-30T14:26:00+00:00
/2021/12/30/ubuntu编译opencv
<p>ubuntu下面编译opencv, 有坑,记录一下.</p>
<p>根据官方教程编译,坑少一点。</p>
<ol>
<li>教程地址: https://docs.opencv.org/4.x/d7/d9f/tutorial_linux_install.html</li>
<li>下载opencv: wget -O opencv.zip https://github.com/opencv/opencv/archive/4.x.zip</li>
<li>下载扩展包, opencv contrib: wget -O opencv_contrib.zip https://github.com/opencv/opencv_contrib/archive/4.x.zip</li>
<li>解压并配置目录:
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> unzip opencv.zip
unzip opencv_contrib.zip
<span class="c"># Create build directory and switch into it</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> build <span class="o">&&</span> <span class="nb">cd </span>build
</code></pre></div> </div>
</li>
<li>配置: <code class="language-plaintext highlighter-rouge">cmake -DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib-4.5.5/modules -D BUILD_opencv_python=OFF -D OPENCV_ENABLE_NONFREE=ON ../opencv-4.5.5</code>
<ol>
<li>DOPENCV_EXTRA_MODULES_PATH, 编译扩展包。没有扩展包很多功能用不了。</li>
<li>BUILD_opencv_python=OFF, 关闭python extension. 在ubuntu下python extension总是编译不过。先跳过。</li>
<li>OPENCV_ENABLE_NONFREE=ON, 下载编译专利算法包。要不然surf算法用不了。</li>
</ol>
</li>
<li>编译: <code class="language-plaintext highlighter-rouge">make -j8</code>, 8线程编译.</li>
<li>安装: <code class="language-plaintext highlighter-rouge">sudo make install</code></li>
</ol>
django笔记
2021-12-26T01:53:00+00:00
/2021/12/26/django笔记
<h1 id="基础">基础</h1>
<ol>
<li>安装django以后就可以用<code class="language-plaintext highlighter-rouge">django-admin</code>
<ol>
<li>创建project: <code class="language-plaintext highlighter-rouge">django-admin startproject HelloWorld</code></li>
<li>创建app: ``</li>
</ol>
</li>
<li>引入当前目录的内容: <code class="language-plaintext highlighter-rouge">from . import views</code></li>
<li>安装xmlsec失败,先安装依赖: <code class="language-plaintext highlighter-rouge">brew install libxml2 libxmlsec1 pkg-config</code> , https://blog.csdn.net/yuyexiaohan/article/details/107145624</li>
<li>Disallowed Host: <code class="language-plaintext highlighter-rouge">Invalid HTTP_HOST header: '127.0.0.1:8000'. You may need to add '127.0.0.1' to ALLOWED_HOSTS</code>
<ol>
<li>修改settings.py->’ALLOWED_HOSTS’</li>
</ol>
</li>
<li>django admin app: <code class="language-plaintext highlighter-rouge">'django.contrib.admin'</code></li>
<li>django admin后台地址: <code class="language-plaintext highlighter-rouge">http://localhost:8000/admin</code></li>
<li>django admin的默认账号,没有。 需要通过命令创建: <code class="language-plaintext highlighter-rouge">python manage.py createsuperuser</code></li>
<li>给django manager添加命令<a href="https://stackoverflow.com/a/65570954">Django : How can I see a list of urlpatterns?</a></li>
<li>django admin列出所有的urls(rails routes等价物), 需要装django-extension:
<ol>
<li><code class="language-plaintext highlighter-rouge">pip install django-extensions</code></li>
<li>安装app, settings.py->INSTALLED_APPS, 添加<code class="language-plaintext highlighter-rouge">'django_extensions',</code></li>
</ol>
</li>
<li>Linkedin登录:
<ol>
<li><a href="https://github.com/python-social-auth/social-examples/tree/master/example-django">官方django例子</a>, 需要把抛错的app删除,然后配置key&secret, 见下面.</li>
<li><a href="https://python-social-auth.readthedocs.io/en/latest/configuration/settings.html">python social auth settings</a>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1"># settings.py
</span> <span class="n">SOCIAL_AUTH_LINKEDIN_OAUTH2_KEY</span> <span class="o">=</span> <span class="s">'77xqioxxxxxxxx'</span>
<span class="n">SOCIAL_AUTH_LINKEDIN_OAUTH2_SECRET</span> <span class="o">=</span> <span class="s">'zILxe7jcxxxxxxxx'</span>
<span class="n">SOCIAL_AUTH_LOGIN_REDIRECT_URL</span> <span class="o">=</span> <span class="s">'/home/'</span>
<span class="n">SOCIAL_AUTH_LOGIN_URL</span> <span class="o">=</span> <span class="s">'/'</span>
</code></pre></div> </div>
</li>
</ol>
</li>
<li>pip 加速:
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"># ~/.pip/pip.conf</span>
<span class="o">[</span>global]
index-url <span class="o">=</span> https://mirrors.aliyun.com/pypi/simple/
<span class="o">[</span><span class="nb">install</span><span class="o">]</span>
trusted-host<span class="o">=</span>mirrors.aliyun.com
</code></pre></div> </div>
<p>11.</p>
</li>
</ol>
<h1 id="django-shell">Django shell</h1>
<p>Django shell是和rails console类似的东西. Django的manage工具提供了shell命令,帮助我们配置好当前工程的运行环境(如连接好数据库等),以便可以直接在终端中执行测试python语句。</p>
<ol>
<li>打开Django shell: <code class="language-plaintext highlighter-rouge">python manage.py shell</code></li>
<li>导入需要的类型: <code class="language-plaintext highlighter-rouge">from exchange_rate.models import ExchangeRate</code></li>
<li>查看指定模型的所有记录: <code class="language-plaintext highlighter-rouge">ExchangeRate.objects.all()</code></li>
<li>查看指定模型的所有记录的数据: <code class="language-plaintext highlighter-rouge">ExchangeRate.objects.all().values()</code></li>
<li>计数: <code class="language-plaintext highlighter-rouge">ExchangeRate.objects.all().count()</code></li>
<li>查找指定的记录: <code class="language-plaintext highlighter-rouge">ExchangeRate.objects.get(id=1)</code></li>
<li>筛选: <code class="language-plaintext highlighter-rouge">ExchangeRate.objects.filter(author='xxx')</code></li>
<li>创建model object:
<ol>
<li><code class="language-plaintext highlighter-rouge">er = ExchangeRate(name='xxx');er.save()</code></li>
<li><code class="language-plaintext highlighter-rouge">ExchangeRate.objects.create(name='xxx')</code></li>
</ol>
</li>
<li>删除object: <code class="language-plaintext highlighter-rouge">ExchangeRate.objects.filter(id='xxx').delete()</code></li>
</ol>
<h1 id="jupyter-notebook">Jupyter notebook</h1>
<p>django extensions 支持notebook</p>
<ol>
<li>安装依赖: <code class="language-plaintext highlighter-rouge">pip install jupyter</code>, <code class="language-plaintext highlighter-rouge">pip install ipython</code></li>
<li>启动notebook: <code class="language-plaintext highlighter-rouge">./manage.py shell_plus --notebook</code>
<h1 id="django-admin">Django admin</h1>
<p>django-admin.py是Django的一个用于管理任务的命令行工具,manage.py是对django-admin.py的简单包装,每个Django Project里面都会包含一个manage.py.</p>
</li>
</ol>
<p>查看有那些命令: <code class="language-plaintext highlighter-rouge">python manage.py help</code></p>
<h2 id="常用子命令">常用子命令:</h2>
<ol>
<li>startproject:创建一个项目(*)</li>
<li>startapp:创建一个app(*)</li>
<li>runserver:运行开发服务器(*)</li>
<li>shell:进入django shell(*)</li>
<li>dbshell:进入django dbshell</li>
<li>check:检查django项目完整性</li>
<li>flush:清空数据库</li>
<li>compilemessages:编译语言文件</li>
<li>makemessages:创建语言文件</li>
<li>makemigrations:生成数据库同步脚本(*)</li>
<li>migrate:同步数据库(*)</li>
<li>showmigrations:查看生成的数据库同步脚本(*)</li>
<li>sqlflush:查看生成清空数据库的脚本(*)</li>
<li>sqlmigrate:查看数据库同步的sql语句(*)</li>
<li>dumpdata:导出数据</li>
<li>loaddata:导入数据</li>
<li>diffsettings:查看你的配置和django默认配置的不同之处</li>
</ol>
<h2 id="managepy特有的一些子命令">manage.py特有的一些子命令:</h2>
<ol>
<li>createsuperuser:创建超级管理员(*)</li>
<li>changepassword:修改密码(*)</li>
<li>clearsessions:清除session</li>
</ol>
<h2 id="admin后台">Admin后台</h2>
<p>Django有默认的管理后台。可以自己注册模型,自动生成后台管理页面。</p>
<p>生成后台管理页面的步骤如下:</p>
<ol>
<li>生成模型app: <code class="language-plaintext highlighter-rouge">./manage.py startapp communit</code></li>
<li>把生成的app注册到apps/settings.py里面去:
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Application definition
</span>
<span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">[</span>
<span class="s">'django.contrib.admin'</span><span class="p">,</span>
<span class="s">'django.contrib.auth'</span><span class="p">,</span>
<span class="c1">#...
</span> <span class="s">'community'</span>
<span class="p">]</span>
</code></pre></div> </div>
</li>
<li>Community目录下创建<code class="language-plaintext highlighter-rouge">admin.py</code>, 填入一下内容:
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">admin</span>
<span class="c1"># Register your models here.
</span><span class="kn">from</span> <span class="nn">community.models</span> <span class="kn">import</span> <span class="n">Community</span>
<span class="k">class</span> <span class="nc">CommunityAdmin</span><span class="p">(</span><span class="n">admin</span><span class="p">.</span><span class="n">ModelAdmin</span><span class="p">):</span>
<span class="c1"># Text, Link
</span> <span class="n">list_display</span> <span class="o">=</span> <span class="p">(</span><span class="s">'id'</span><span class="p">,</span> <span class="s">'text'</span><span class="p">,</span> <span class="s">'link'</span><span class="p">)</span>
<span class="c1"># list_display_links = ('id',)
</span>
<span class="c1"># Register your models here.
</span><span class="n">admin</span><span class="p">.</span><span class="n">site</span><span class="p">.</span><span class="n">register</span><span class="p">(</span><span class="n">Community</span><span class="p">,</span> <span class="n">CommunityAdmin</span><span class="p">)</span>
</code></pre></div> </div>
</li>
</ol>
<h2 id="model-query">Model Query</h2>
<p>F表达式:</p>
<ol>
<li>An F() object represents the value of a model field. It makes it possible to refer to model field values and perform database operations using them without actually having to pull them out of the database into Python memory.
Instead, Django uses the F() object to generate a SQL expression that describes the required operation at the database level.</li>
<li>F 大概是 field的意思.</li>
<li>在遇到F表达式的时候会重写python为SQL语句,而不执行python语句</li>
<li>F表达式直接在数据库层面操作</li>
<li><a href="https://docs.djangoproject.com/en/4.0/ref/models/expressions/#f-expressions">F Expression</a></li>
<li>一个F()对象代表一个模型字段的值或注释列。使用它可以直接引用模型字段的值并执行数据库操作而不用把它们导入到python的内存中。</li>
</ol>
<p>F表达式的应用场景:</p>
<ol>
<li>避免竞争条件</li>
<li>当两个线程同时进行操作,在第一个线程正在操作还未保存的时候,第二个进程进行取值,修改,保存。则第一个操作的值被覆盖丢失。但是由数据库操作的时候则不会出现此种情况。</li>
<li>用于Queryset中的过滤</li>
<li>F()的实例作为查询中的模型字段的引用。可以用于两个不同字段比较</li>
<li>支持加减乘除模幂操作,支持位操作</li>
</ol>
<p>Q表达式,~Q表达式:</p>
<ol>
<li>A Q() object, like an F object, encapsulates a SQL expression in a Python object that can be used in database-related operations.
In general, Q() objects make it possible to define and reuse conditions. This permits the construction of complex database queries using | (OR) and & (AND) operators; in particular, it is not otherwise possible to use OR in QuerySets.</li>
<li>将SQL表达式封装在可用于数据库相关操作的Python对象中</li>
<li>支持构造复杂的数据库查询</li>
<li>支持 OR AND NOT操作
<h1 id="celery">Celery</h1>
</li>
</ol>
<h2 id="hello-world">Hello world</h2>
<ol>
<li><a href="https://www.jianshu.com/p/fb323923b566">异步任务神器 Celery 快速入门教程</a></li>
<li>创建<code class="language-plaintext highlighter-rouge">tasks.py</code>:
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># -*- coding: utf-8 -*-
</span><span class="kn">import</span> <span class="nn">time</span>
<span class="kn">from</span> <span class="nn">celery</span> <span class="kn">import</span> <span class="n">Celery</span>
<span class="n">broker</span> <span class="o">=</span> <span class="s">'redis://127.0.0.1:6379'</span>
<span class="n">backend</span> <span class="o">=</span> <span class="s">'redis://127.0.0.1:6379/0'</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Celery</span><span class="p">(</span><span class="s">'my_task'</span><span class="p">,</span> <span class="n">broker</span><span class="o">=</span><span class="n">broker</span><span class="p">,</span> <span class="n">backend</span><span class="o">=</span><span class="n">backend</span><span class="p">)</span>
<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">task</span>
<span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
<span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="c1"># 模拟耗时操作
</span> <span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
</code></pre></div> </div>
</li>
<li><code class="language-plaintext highlighter-rouge">celery worker -A tasks --loglevel=info</code></li>
<li>在python shell里面:
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">tasks</span> <span class="kn">import</span> <span class="n">add</span>
</code></pre></div> </div>
</li>
</ol>
<h2 id="djcelery-integration">Djcelery integration</h2>
<p><a href="https://github.com/celery/django-celery">Django Celery source code git</a></p>
<ol>
<li>enable <code class="language-plaintext highlighter-rouge">djcelery</code> in <code class="language-plaintext highlighter-rouge">INSTALLED_APPS</code>: <code class="language-plaintext highlighter-rouge">INSTALLED_APPS += ("djcelery", )</code></li>
<li>add following lines in <code class="language-plaintext highlighter-rouge">settings.py</code>:
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">djcelery</span>
<span class="n">djcelery</span><span class="p">.</span><span class="n">setup_loader</span><span class="p">()</span>
</code></pre></div> </div>
<h2 id="常用命令">常用命令</h2>
</li>
<li>python manage.py celery worker</li>
<li>python manage.py celery beat</li>
<li>supervisorctl
<ol>
<li>tail -f djceler_work</li>
</ol>
</li>
<li>@shared_task 装饰器能让你在没有具体的 Celery 实例时创建任务.</li>
</ol>
<h2 id="refs">refs</h2>
<ol>
<li><a href="https://www.jianshu.com/p/7a869a73b92f">在 Django 项目中使用 Celery</a></li>
<li><a href="https://www.jianshu.com/p/fb323923b566">异步任务神器 Celery 快速入门教程</a></li>
<li><a href="https://docs.celeryproject.org/en/latest/django/first-steps-with-django.html">DJCelery official doc</a></li>
<li><a href="https://github.com/celery/django-celery">Django Celery source code git</a>
<h1 id="django-extensions">Django extensions</h1>
<p><a href="https://github.com/django-extensions/django-extensions">Django extensions</a> 是django admin的扩展。</p>
</li>
<li>show urls: <code class="language-plaintext highlighter-rouge">./manage.py show_urls</code></li>
<li>shell plus: <code class="language-plaintext highlighter-rouge">./manage.py shell_plus</code></li>
<li>运行脚本, django extension自动导入django配置:
<ol>
<li>在项目根目录下,创建script文件夹: <code class="language-plaintext highlighter-rouge">mkdir scripts && touch scripts/__init__.py</code></li>
<li>创建一个脚本文件: <code class="language-plaintext highlighter-rouge">touch scripts/delete_all_questions.py</code></li>
<li>再次,写脚本。注:代码必须写在 run() 函数下。
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1"># scripts/delete_all_questions.py
</span>
<span class="kn">from</span> <span class="nn">polls.models</span> <span class="kn">import</span> <span class="n">Question</span>
<span class="k">def</span> <span class="nf">run</span><span class="p">():</span>
<span class="c1"># Fetch all questions
</span> <span class="n">questions</span> <span class="o">=</span> <span class="n">Question</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nb">all</span><span class="p">()</span>
<span class="c1"># Delete questions
</span> <span class="n">questions</span><span class="p">.</span><span class="n">delete</span><span class="p">()</span>
</code></pre></div> </div>
</li>
<li>最后,运行脚本
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> python manage.py runscript delete_all_questions
python manage.py runscript delete_all_questions <span class="nt">--traceback</span> <span class="c"># 开启 Debug 模式</span>
</code></pre></div> </div>
</li>
</ol>
</li>
<li>启动jupyter notebook: <code class="language-plaintext highlighter-rouge">./manage.py shell_plus --notebook</code>
<ol>
<li>需要安装依赖: <code class="language-plaintext highlighter-rouge">pip install jupyter && pip install ipython</code></li>
<li>不太好用。第二次打开shell_plus book的时候会抛错。</li>
</ol>
</li>
</ol>
<h1 id="http">Http</h1>
<ol>
<li>
<p>返回json数据
把数据库记录打包成json格式返回.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="o">@</span><span class="n">action</span><span class="p">(</span><span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">],</span> <span class="n">detail</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">index</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">):</span>
<span class="n">start</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">query_params</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'start_at'</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">start</span><span class="p">:</span>
<span class="n">start</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="n">now</span><span class="p">()</span> <span class="o">-</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">days</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span> <span class="c1"># by default, start from 30 days before
</span>
<span class="n">end</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">query_params</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'end_at'</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">end</span> <span class="ow">or</span> <span class="n">start</span> <span class="o">>=</span> <span class="n">end</span><span class="p">:</span>
<span class="n">end</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">.</span><span class="n">now</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="s">"querying exchange rate from {} to {}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">))</span>
<span class="n">exchanges</span> <span class="o">=</span> <span class="n">ExchangeRate</span><span class="p">.</span><span class="n">objects</span><span class="p">.</span><span class="nb">filter</span><span class="p">(</span><span class="n">timestamp__range</span><span class="o">=</span><span class="p">[</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">]).</span><span class="nb">all</span><span class="p">()</span>
<span class="n">serializer_class</span> <span class="o">=</span> <span class="n">ExchangeRateSerializer</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">serializers</span><span class="p">.</span><span class="n">serialize</span><span class="p">(</span><span class="s">'json'</span><span class="p">,</span> <span class="n">exchanges</span><span class="p">)</span>
<span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">content_type</span><span class="o">=</span><span class="s">'application/json'</span><span class="p">)</span>
</code></pre></div> </div>
</li>
<li>
<p>返回json数据
把hash打包成json格式返回.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">ret</span> <span class="o">=</span> <span class="p">{</span> <span class="n">a</span><span class="p">:</span> <span class="s">'bbb'</span><span class="p">}</span>
<span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">ret</span><span class="p">),</span> <span class="n">content_type</span><span class="o">=</span><span class="s">'application/json;charset=utf8'</span><span class="p">)</span>
</code></pre></div> </div>
</li>
</ol>
<h1 id="python基础">Python基础</h1>
<ol>
<li>多重继承. <code class="language-plaintext highlighter-rouge">ClassA.__mro__</code>会打印继承/多重继承的继承链. 函数重名的情况下以这个继承链里第一个找到的符号为准. <a href="https://blog.hszofficial.site/TutorialForPython/%E8%AF%AD%E6%B3%95%E7%AF%87/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%83%AF%E7%94%A8%E6%B3%95/%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF%E5%92%8CMixin.html">重继承</a>,<a href="https://blog.hszofficial.site/TutorialForPython/%E8%AF%AD%E6%B3%95%E7%AF%87/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%83%AF%E7%94%A8%E6%B3%95/%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF%E5%92%8CMixin.html#mixin">mixin</a>
2.</li>
</ol>
<h1 id="python-env">Python Env</h1>
<p>django的很多库对Python版本敏感。建议用conda指定python小版本创建env。</p>
<h1 id="refs-1">refs</h1>
<ol>
<li><a href="https://yeraydiazdiaz.medium.com/django-rails-cheat-sheet-50adf2441913">Django/Rails cheat sheet</a></li>
<li><a href="https://jiaxin.im/blog/django-kai-fa-li-qi-django-ext/">Django 开发利器 django-extensions</a></li>
<li><a href="https://medium.com/@ayarshabeer/django-best-practice-settings-file-for-multiple-environments-6d71c6966ee2">Django Best Practice: Settings file for multiple environments</a></li>
</ol>
常用服务器维护命令
2021-12-21T12:49:00+00:00
/2021/12/21/常用服务器维护命令
<h1 id="磁盘清理">磁盘清理</h1>
<ol>
<li>找大文件,大于1G的文件: <code class="language-plaintext highlighter-rouge">find / -size +1000M</code></li>
<li>container log也容易积累很大: /data/docker/containers/, 遇到过一个25G的日志</li>
<li><code class="language-plaintext highlighter-rouge">ls -lh</code> 查看日志文件大小</li>
<li>切换到root下清除日志: <code class="language-plaintext highlighter-rouge">sudo -i; echo "" > /path/to/logs</code></li>
<li>或者用find来删除: <code class="language-plaintext highlighter-rouge">find /var/lib/docker/containers/ -type f -name "*.log" -delete</code>
3.</li>
</ol>
<h1 id="docker">docker</h1>
<ol>
<li>查看容器日志,跳过历史记录: <code class="language-plaintext highlighter-rouge">docker logs -f --tail 100 current_web_1</code>
2.
<h1 id="远程访问-文件传输">远程访问, 文件传输</h1>
</li>
<li>列出远程目录的内容: <code class="language-plaintext highlighter-rouge">ssh root@remote.com -t ls ~/</code></li>
</ol>