VisualStudio Code Remote 调试方法(错误Containers Docker version 17.12.0 or later required.)

Remote Development with Visual Studio Code

最近在配置.net的remote开发环境,用了整套VS Code Remote的三大件,在用container时总是提示:

Remote - Containers Docker version 17.12.0 or later required.

一头雾水,docker版本20了都,可能的理解就是它没找到docker。可是信息有限,好在开源项目有issue,作者介绍了debug的办法,适用于进一步调试。

When you see this, could you click Cancel and then check the dev container log (F1 > Remote-Containers: Show Container Log) and the dev console (Help > Toggle Developer Tools) and post these here?

Developer Tools就是chrome的开发栏,是同一个东西,能够看到UI的日志,已经发现就是Docker version这个命令的返回值出错,作者在issue中反复提到会不会是没加入Path,我不信。最后他终于提到了,如果再vs code 运行中更新过docker就会出现这个bug,解决的办法就是在更新docker后重启一下vs code即可。

另外还有的碰到这个问题可以尝试通过更新vs code和docker desktop到最新的版本。

如果还是不可以除了打开日志查看报错外,可以尝试查看docker context,看看当前的context是不是default。

AWS Kubernetes/k8s kubeamd 初始化后kube-controller-manager pod CrashLoopBackOff 错误启动不了

问题出现在版本1.22和1.21中,同样的配置在1.19和1.20版本中成功配置没有问题。

kubeadm init
初始化后提示成功,在master第二个节点 kubeadm join时提示
could not find a JWS signature

于是回到第一个master上看一下cluster info信息
kubectl get configmap cluster-info --namespace=kube-public -o yaml
非常奇怪的是没有jws段,jws是一个证书的签名用来验证证书的token,这里提一下它并不安全最好不要用于所有节点,可以通过kubeadm create token xxx来创建。
没有jws段那么判断没有生效的token,但是用

kubeadm token list
可以看到token全部正常有效,这个问题就很奇怪了。

在阅读bootstrap-tokens鉴权kubeadm实现细节后发现,原来cluster info中的aws需要在kube-controller-manager运行后创建。

这时才发现kube-controller-manager的pod没有起来,我们知道kubeadm文档中说过如果init后pod有没有成功生效的那么就要发issue证明是kubeadm坏了,这个判断大概率不成立,肯定是kubeadm配置错误。

kubectl describe kube-controller-manager -nkube-system
kubectl logs -n kube-system kube-controller-manager

后提示:
Error: unknown flag: --horizontal-pod-autoscaler-use-rest-clients

原来1.21的kube-controller-manager不再支持这个参数。
去掉后pod成功启动

阿里云 NAS OSS 云盘价格对比 GB/小时

对比需要统一单位,这里以最小计费单位GB每小时来统一度量单位,这里对有的产品不公平比如按量付费和包月两种方式的,希望对产品选购也有意义。

NAS

最方便的使用方式,以每个月最大的存储上限计费对于存储波动大的很不公平。它的优点是灵活,缺点是io较低,依赖网路性能。性能型非常贵,指标只是提高了一倍,如果真的对io要求高的产品不建议使用nas。

通用型 0.35元/GiB/月=0.00048611/gh

性能型 0.35元/GiB/月+1.85元/GiB/月=0.00305556/gh

OSS

这个产品严格来说不适合作为横向对比,应用场景重合度不高,虽然它最便宜,可是明显这是个诱惑你的理由,为了赚流量费。

0.12元/GB/月=0.00016667/gh

云盘ESSD

默认的网盘,io比ssd低收费却一样,存储按量很贵,包月还说得过去。只有对io有较高需求的适合放在essd云盘中,这里推荐考虑ssd盘,只有ssd满足不了再上essd。

按量0.0420 /时/40g=0.00105/gh

包月0.00069444/gh

云盘高效云盘

最早的云盘,io不是特别高的可以考虑。

包月0.00048611/gh

kubernetes/k8s pod下多容器的设计模式(ambassador 大使代理模式,adapter 适配模式,sidecar 边车模式, init containers初始化容器)

英文好的可以直接阅读原文:引用原文(英文):https://learnk8s.io/sidecar-containers-patterns

TL;TR:k8s patterns包含了云原生架构中各种的最佳实践,这里面绕不开用的最多的就是pod下多容器的pattern,也是k8s与swarm区别最大的地方。利用好这些pattern可以在不修改任何代码的情况下实现不同的行为比如TLS加固。

k8s把最小单位从容器上升到了pod是它设计的核心思想,这种设计带来了与原生docker容器无法比拟的优势,我们知道容器利用了linux下的各种命名空间用来隔离各种资源,但是pod作为多个容器的上一层,它可以利用命名空间是的这些容器共享某些资源从而达到亲缘性,比如共用网络、共用存储空间实现unionfile等。

示例: 一个安全的http服务

如何利用pod下多容器模式如何实现一个ElasticSearch服务的强化:

apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: elasticsearch
 spec:
   selector:
     matchLabels:
         es.test: elasticsearch
   template:
     metadata:
       labels:
         es.test: elasticsearch
     spec:
       containers:
       - name: elasticsearch
         image: elasticsearch:7.9.3
         env:
           - name: discovery.type
             value: single-node
         ports:
         - name:  http
           containerPort:  9200
 apiVersion: v1
 kind: Service
 metadata:
   name: elasticsearch
 spec:
   selector:
     es.test: elasticsearch
   ports:
 port: 9200
 targetPort: 9200 
kubectl run -it --rm --image=curlimages/curl curl \
   -- curl http://elasticsearch:9200
 {
   "name" : "elasticsearch-77d857c8cf-mk2dv",
   "cluster_name" : "docker-cluster",
   "cluster_uuid" : "z98oL-w-SLKJBhh5KVG4kg",
   "version" : {
     "number" : "7.9.3",
     "build_flavor" : "default",
     "build_type" : "docker",
     "build_hash" : "c4138e51121ef06a6404866cddc601906fe5c868",
     "build_date" : "2020-10-16T10:36:16.141335Z",
     "build_snapshot" : false,
     "lucene_version" : "8.6.2",
     "minimum_wire_compatibility_version" : "6.8.0",
     "minimum_index_compatibility_version" : "6.0.0-beta1"
   },
   "tagline" : "You Know, for Search"
 }

现在的访问是明文的,那么如何方便的使用多容器pod来实现TLS加固传输呢,如果你想到用ingress(通常用来路由外部流量到pod),这里从ingress到pod之间还是未加密的如下图:

The external traffic is routed to the Ingress and then to Pods.

那么满足zero-trust的办法就是给这个pod加入一个nginx代理tls加密流量如下图:

If you include a proxy container in the pod, you can terminate TLS in the Nginx pod.

增加一个nginx容器代理tls流量

apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: elasticsearch
 spec:
   selector:
     matchLabels:
       app.kubernetes.io/name: elasticsearch
   template:
     metadata:
       labels:
         app.kubernetes.io/name: elasticsearch
     spec:
       containers:
         - name: elasticsearch
           image: elasticsearch:7.9.3
           env:
             - name: discovery.type
               value: single-node
             - name: network.host
               value: 127.0.0.1
             - name: http.port
               value: '9201'
         - name: nginx-proxy
           image: nginx:1.19.5
           volumeMounts:
             - name: nginx-config
               mountPath: /etc/nginx/conf.d
               readOnly: true
             - name: certs
               mountPath: /certs
               readOnly: true
           ports:
             - name: https
               containerPort: 9200
       volumes:
         - name: nginx-config
           configMap:
             name: elasticsearch-nginx
         - name: certs
           secret:
             secretName: elasticsearch-tls
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: elasticsearch-nginx
 data:
   elasticsearch.conf: |
     server {
         listen 9200 ssl;
         server_name elasticsearch;
         ssl_certificate /certs/tls.crt;
         ssl_certificate_key /certs/tls.key;
     location / {         proxy_pass http://localhost:9201;     } }

前面的配置中我们利用用service可以让curl明文的访问es的接口,而这个配置中改为用nginx代理了9200,es只对localhost暴露9201,也就是从pod以外是访问不到es了。nginx在9200端口监听了https请求并转发给http的9200本地的端口给ES。

The request is proxies by Nginx on port 9220 and forwarded to port 9201 on Elastisearch

代理容器是最常用的一种Pattern

这种添加一个代理容器到一个pod的解决方式称之为:Ambassador Pattern

本文中所有模式都可以在google的研究文稿中找到详细的论述:https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45406.pdf

添加基本的TLS还只是开始,除此之外还可以用这个模式做到以下几点:

  • 如果你希望集群上的流量都用tls证书加密,你可以在所有pod上加上一个nginx代理。或者你也可以更进一步使用mutual TLS来保证所有认证的请求已经被很好的加密,正如lstio 和linkerd在service meshes中做的。
  • 在OAuth认证中也可以使用nginx代理来保证所有请求是被jwt验证的。
  • 用在连接外部的数据库,比如一些不支持TLS或者旧版本的数据库时这也是很方便的方式。

暴露一个标准的接口用来度量

假设你已经熟悉如何使用 Prometheus来监控所有集群中的服务,但是你也正在使用一些原生并不支持 Prometheus度量的服务,比如Elasticsearch。

如何在不更改代码的情况下添加 Prometheus度量?

Adapter Pattern 适配模式

以ES为例,我们可以添加一个 exporter容器以Prometheus的格式暴露ES的度量。这非常简单,用一个开源的exporter for es 即可:

apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: elasticsearch
 spec:
   selector:
     matchLabels:
       app.kubernetes.io/name: elasticsearch
   template:
     metadata:
       labels:
         app.kubernetes.io/name: elasticsearch
     spec:
       containers:
         - name: elasticsearch
           image: elasticsearch:7.9.3
           env:
             - name: discovery.type
               value: single-node
           ports:
             - name: http
               containerPort: 9200
         - name: prometheus-exporter
           image: justwatch/elasticsearch_exporter:1.1.0
           args:
             - '--es.uri=http://localhost:9200'
           ports:
             - name: http-prometheus
               containerPort: 9114
 apiVersion: v1
 kind: Service
 metadata:
   name: elasticsearch
 spec:
   selector:
     app.kubernetes.io/name: elasticsearch
   ports:
     - name: http
       port: 9200
       targetPort: http
     - name: http-prometheus
       port: 9114
       targetPort: http-prometheus

通过这种方式可以更广泛的使用prometheus度量从而达到更好的应用与基础架构的分离。

日志跟踪 / Sidecar Pattern

边车模式,我一直把他想想成老式三轮摩托车的副座,它始终与摩托车主题保持一致并提供各种辅助功能,实现方式也是添加容器来曾强pod中应用。边车最经典的应用就是日志跟踪。

在容器化的环境中最标准的做法是标准输出日志到一个中心化的收集器中用于分析和管理。但是很多老的应用是将日志写入文件,而更改日志输出有时候是一件困难的事。

那么添加一个日志跟踪的边车就意味着你可能不必去更改日志代码。回到ElasticSearch这个例子,虽然它默认是标准输出把它写入文件有点做作,这里作为示例我们可以这样部署:

apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: elasticsearch
   labels:
     app.kubernetes.io/name: elasticsearch
 spec:
   selector:
     matchLabels:
       app.kubernetes.io/name: elasticsearch
   template:
     metadata:
       labels:
         app.kubernetes.io/name: elasticsearch
     spec:
       containers:
         - name: elasticsearch
           image: elasticsearch:7.9.3
           env:
             - name: discovery.type
               value: single-node
             - name: path.logs
               value: /var/log/elasticsearch
           volumeMounts:
             - name: logs
               mountPath: /var/log/elasticsearch
             - name: logging-config
               mountPath: /usr/share/elasticsearch/config/log4j2.properties
               subPath: log4j2.properties
               readOnly: true
           ports:
             - name: http
               containerPort: 9200
         - name: logs
           image: alpine:3.12
           command:
             - tail
             - -f
             - /logs/docker-cluster_server.json
           volumeMounts:
             - name: logs
               mountPath: /logs
               readOnly: true
       volumes:
         - name: logging-config
           configMap:
             name: elasticsearch-logging
         - name: logs
           emptyDir: {}

这里的logs容器就是sidecar的一个具体实现,现实中可以使用具体的日志收集器代替比如filebeat。当app持续写入数据时,边车中的日志收集程序会不断的以只读的形式收集日志,这里的logs边车就把写入文件的logs变为标准输出而不需要修改任何代码。

其他边车模式常用的场景

  • 实时的重启ConfigMaps而不需要重启pod
  • 从Hashcorp Vault注入秘钥
  • 添加一个本地的redis作为一个低延迟的内存缓存服务

在pod前的准备工作中使用Init Containers

k8s除了提供多容器外还提供了一种叫做初始化容器的功能,顾名思义它就是在pods 容器启动前工作的容器,我一般把它当做job这样的概念,一般场景中init containers这些容器在执行完后就不再运行了处于pause状态,这里特别要注意的是它的执行会严格按照编排的从上至下的顺序逐一初始化,这种顺序也是实现初始化工作不可缺少的。下面还是以ES为例子:

ES 文档中建议生产中设置vm.max_map_count这个sysctl属性。

这就带来了一个问题,这个属性只能在节点级别才可以被修改,容器级别是没有做到隔离。

所以在不修改k8s代码的情况下你不得不使用特权级别来运行es已达到修改的目的,而这也不是你所希望的,

因为他会带来很严重的安全问题

那么使用Init Containers就可以很好地解决这个问题,做法就是只在初始化容器中提权修改设置,那么后面的es只是普通容器就可以运行。如下:

apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: elasticsearch
 spec:
   selector:
     matchLabels:
       app.kubernetes.io/name: elasticsearch
   template:
     metadata:
       labels:
         app.kubernetes.io/name: elasticsearch
     spec:
       initContainers:
         - name: update-sysctl
           image: alpine:3.12
           command: ['/bin/sh']
           args:
             - -c
             - |
               sysctl -w vm.max_map_count=262144
           securityContext:
             privileged: true
       containers:
         - name: elasticsearch
           image: elasticsearch:7.9.3
           env:
             - name: discovery.type
               value: single-node
           ports:
             - name: http
               containerPort: 9200

除了上面这种常见的做法外初始化容器还可以这么用,当你HashicCorp Vault 来管理secrets而不是k8s secrets时,你可以在初始化容器中读取并放入一个emptyDir中。比如这样:

apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: myapp
   labels:
     app.kubernetes.io/name: myapp
 spec:
   selector:
     matchLabels:
       app.kubernetes.io/name: myapp
   template:
     metadata:
       labels:
         app.kubernetes.io/name: myapp
     spec:
       initContainers:
         - name: get-secret
           image: vault
           volumeMounts:
             - name: secrets
               mountPath: /secrets
           command: ['/bin/sh']
           args:
             - -c
             - |
               vault read secret/my-secret > /secrets/my-secret
       containers:
         - name: myapp
           image: myapp
           volumeMounts:
             - name: secrets
               mountPath: /secrets
       volumes:
         - name: secrets
           emptyDir: {}

更多的初始化容器应用场景

  • 你希望在运行app前跑数据库的迁移脚本
  • 从外部读取/拉取一个超大文件时可以避免容器臃肿

总结

这些pattern非常巧妙地用很小的代价非侵入的解决现实中常见的问题,这里要特别说明的是除了初始化容器会在运行后暂停不占用资源外,pods中增加的容器都是吃资源的,实际使用中我们不希望因为解决一个小问题反倒拖累整个pod,所以在边车这类容器组件的选择上要慎重,要足够的高效轻量,常见的像nginx、go写的大部分组件就是一个很好的选择,java写的就呵呵了。

如果希望挖掘更多多容器的设计细节可以查看官方文档:https://kubernetes.io/docs/concepts/workloads/pods/,还有google的容器设计论文:https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45406.pdf

用docker搭建一个ElasticSearch集群并集成Kibana+cerebro可视化

JMeter (ElasticSearch + Kibana) | Blog

ES是一个非常流行的基于lucene的搜索和分析引擎,它提供了resetful的api非常适用于搜索、推荐、日志分析等场景,社区生态丰富,是分布式架构中非常重要的一环。下面从一个docker-compose部署一个最小化集群,并用Kibana和cerebro可视化的来一窥它的能力。

version: '3.9'
services:
  cerebro:
    image: lmenezes/cerebro
    container_name: cerebro
    ports:
      - "9000:9000"
    command:
      - -Dhosts.0.host=http://host.docker.internal:9200 
      # 因为我是mac所以用host.docker.internal代替主机名
  kibana:
    image: docker.elastic.co/kibana/kibana:7.7.0
    container_name: kibana7
    environment:
      - I18N_LOCALE=zh-CN
      - XPACK_GRAPH_ENABLED=true
      - TIMELION_ENABLED=true
      - XPACK_MONITORING_COLLECTION_ENABLED="true"
      - ELASTICSEARCH_HOSTS=http://host.docker.internal:9200
    ports:
      - "5601:5601"
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.7.0
    container_name: es7_01
    environment:
      - cluster.name=cnblogs
      - node.name=es7_01
      - bootstrap.memory_lock=true  
      # 避免使用swap,减少磁盘IO
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"  
      # 防止JVM内存回收
      - discovery.seed_hosts=172.19.0.5,172.19.0.4
      # 默认ES只搜索本机的其他节点,如果节点在其他主机上则需要在这里给出地址
      - cluster.initial_master_nodes=es7_01,es7_02   
      # 初始化时符合主节点的节点方便选举
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es7data1:/usr/share/elasticsearch/data
    ports:
      - 9200:9200

  elasticsearch2:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.7.0
    container_name: es7_02
    environment:
      - cluster.name=cnblogs
      - node.name=es7_02
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - discovery.seed_hosts=172.19.0.5,172.19.0.4
      - cluster.initial_master_nodes=es7_01,es7_02
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es7data2:/usr/share/elasticsearch/data
    expose:
      - 9200


volumes:
  es7data1:
    driver: local
  es7data2:
    driver: local

networks:
  default:
    external: true
    name: es7net

运行上述代码前请先创建一个名为es7net的网络: docker network create es7net
下面是配置集群几个常见配置的原则

  • bootstrap.memory_lock一般设置为true,避免使用swap
  • ES_JAVA_OPTS中Xms和Xmx设置为一致,避免jvm内存重新分配
  • cerebro起来比较快,kinaba很慢要等一下,多刷新就好了
  • 运行上面的集群请至少保证docker拥有3g以上,因为Elasticsearch by default deploys with a 1GB heap,开始只使用了2g时就会出现内存不足导致es启动失败,如果是下面几种错误信息可以尝试增加内存
master not discovered or elected yet, an election requires a node with id 
或
master not discovered yet, this node has not previously joined a bootstrapped (v7+) cluster, and this node must discover master-eligible nodes 
或
max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

启动后可以按照以下步骤逐步检查部署

1检查ES运行状态

   http://localhost:9200/_cat/health?v
显示green表示工作正常

2 登录cerebro

cb的概览非常适合作为看板,对于刚接触es的同学来说能更好的理解es集群的状态,虽然它不是官方的

3 登录kinaba,官方

官方样例数据就可以创建非常漂亮的看板/报表

可以查看非常细节的数据,并支持完整的索引操作

从125ms到11ms,记一次关键字检测过滤服务的优化 -python and Pythonnet

接上文:《高效的的关键字查找和检测(哈希表和Trie前缀树和FastCheck)在实际使用中的性能

动态语言是很慢的,它更多的是为了提高开发效率,这里的关键字过滤算法在生产环境中用原生python达到125ms 2千万字/每秒已经够用了。那么是不是可以适当的优化再快一点?

Cython,没事cython一下,带你飞

cython能把大部分python代码改为静态的c实现,甚至你可以混合c和python一起写。不过现实是大部分的pythoner如果碰到性能问题要么用go要么就是用csharp,很少去写cython。下面是用cython编译后的测试结果:

提升39%,马马虎虎,也可能跟Tool.Good源码的实现有关。此时文本的处理量已经到了3400万/每秒,用是够用了,那么能不能更快一点?

C# 是时候展现你的实力了

Tool.Good作者明显是一位纯粹的csharpner, 既然已经实现了3亿效率的代码为什么不拿来用呢。这里作为对比,我先写了一个参照的测试程序,测试环境与前文相同。代码如下

using System;
using ToolGood.Words;
namespace KeywordTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var stopwatch = System.Diagnostics.Stopwatch.StartNew();
           
            string post = System.IO.File.ReadAllText(@"D:\Projects\Opensource\ToolGood.Words\csharp\KeywordTest\sample_post");

            string[] spams = System.IO.File.ReadAllLines(@"D:\Projects\Opensource\ToolGood.Words\csharp\KeywordTest\SpamWordsCN.min.txt");

            StringSearch iwords = new StringSearch();
            iwords.SetKeywords(spams);
            stopwatch.Start();
            for (var i = 0; i < 500; i++)
            { var f = iwords.FindFirst(post); }

            stopwatch.Stop();
            var s = stopwatch.ElapsedMilliseconds;
            Console.WriteLine("测试用时(ms):" + s);
        }
    }
}
Customer Retention: 3 Easy Ways to WOW Your Customers | Car People Marketing
此时的表情

Csharp性能都这样了更别说C了,那么为什么能比cython快那么多呢。这里分两部分原因,一部分性能浪费在python和c的交互上,另一部分在cython编译时的类型推断和生成的代码优化不够,大量使用的还是pure python。咱不纠结这个,手头有这么快的csharp不用简直浪费,一脚踢走cython,黏上csharp,让它飞的更高~

接福啦- 简书

Pythonnet 闪亮登场

这可是个好东西,.net基金会项目,官方身份,神秘可靠。有了它使得python和.net交互变得简单。这里需要一提的是目前稳定版2.5.2还只支持.net 4.0,至于.net core 3和.net5需要手动安装master分支上的3.0.0dev,实现方式 也有所不同,下面是代码:

def test2():

    from clr_loader import get_coreclr
    from pythonnet import set_runtime

    rt = get_coreclr("./runtimeconfig.json")
    set_runtime(rt)
    import  clr
    clr.AddReference('ToolGood.Words')

    import clr
    from System import String
    from ToolGood.Words import StringSearch
    stringSearch=StringSearch()

    with  open('./sample_post',encoding='utf-8') as f:
        test_post = f.read()
    from System.Collections.Generic import List
    spam_words=List[String]()
    with open('././SpamWordsCN.min.txt',encoding='utf-8') as f:
        for line in [line.rstrip() for line in f]:
            spam_words.Add(String(line))

    stringSearch.SetKeywords(spam_words)
    import time
    start = time.time()
    times = 500
    while times > 0:
        f = stringSearch.FindFirst(test_post)
        times -= 1
    end = time.time()
    print('程序运行时间:%s毫秒' % ((end - start) * 1000))
{
  "runtimeOptions": {
    "tfm": "net5.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "5.0.2"
    }
  }
}
4种吃了会让人开心的食物- 每日头条
此时的心情

事实上如果增加压力还能提高性能,因为交互部分压力越大性价比越高。此时的处理速度已经到了2.5亿/每秒。那么能不能再快点?烦不烦,其实也不烦,生产中怎么可能不并行呢,动不动八核十几核的,线程再来double一下,妥妥的几十亿。

为什么不直接用csharp直接做服务

当然可以,如果负载确实高换纯csharp顶上,还不够再换go,还不够把分布式也弄上。但现实是高负载的需求可能永远不会到来,为什么不把时间省下来去做更有意义的事呢?

高效的的关键字查找和检测(哈希表和Trie前缀树和FastCheck)在实际使用中的性能

Implement TRIE | Leetcode #208 - YouTube

前言:看到dudu发的博文中似乎最近的db压力来源于关键字检测,以前只关注了倒排索引,于是好奇经典的关键字查找在实际生产中性能到底是一个什么数量级?

为什么不用倒排索引

在一个文本中找到给定的关键字最快的做法是倒排索引,比如平常使用的各种搜索框如google,还有咱们在生产中分析日志时用的ES也是。它的优点是海量的文本和海量的关键字,缺点是搜索的文本加入现有索引很慢。所以它不适合:评论、即时通讯、微博、脏字过滤等对发布时间有要求的场景,而这些场景正好跟倒排索引是反过来的。

如果不用Trie树

那么如何实现一个高效的及时关键字检测算法,这里为了简单期间只关注FindFirst,而FindAll 同理。首先第一个否定的算法就是IndexOf(keyword)方法,这里假定输入文本长度n,检测的关键词数量m,关键字平均长度l,复杂度就是O(n*m*l),复杂度很高。在不知道Trie树之前很容易想到的实现是Hashtable,我想大多数程序员也是这样想的,复杂度直接降为O(n+m*l),这里要提一下好多写java和csharp的同学在实现这个算法的时候会调用substring,然后导致内存飙升,其实这个临时空间完全不需要的。下面是我用python写的一个实现,这里为简单期间回避了hashtable中碰撞的情况,一般用单向链表还有树来解决,因为知道trie了没必要去做的很完善,只是提供一个对比的数量级,而哈希表的完整实现只会比这个更慢。

def test3():

    with  open('./sample_post', encoding='utf-8') as f:
        test_post = f.read()
    spam_words={}
    with open('././SpamWordsCN.min.txt', encoding='utf-8') as f:
        words=[line.strip() for line in f]
    for w in words:
        if len(w)>0:
            spam_words[w[0]]=w
    import time

    start = time.time()
    times = 500
    while times > 0:
        for i,c in enumerate(test_post):
            if c in spam_words:
                is_found=True
                for j,sc in enumerate(spam_words):
                    if sc!=test_post[i+j]:
                        # compare failed
                        is_found=False
                        break
                # found
                if is_found:
                    #print('找到了%s'%spam_words)
                    break
        times -= 1
    end = time.time()
    print('程序运行时间:%s毫秒' % ((end - start) * 1000))

测试条件和运行环境

  • 输入文本长度:5388 from word count
  • 关键字数量:575
  • 平均长度:3 目测
  • 测试次数:500
  • 运行环境:Ryzen 5 3600 6-core 3.6GHz
多次运行后会降为650ms左右

HashTable 总结

哈希的原理就是通过对关键字的预处理,把首字母的关键字检索降为O(1),整个过滤只需要O(n)即可5k的字符过滤650ms时间对大多数小项目小网站足够了,如果是即时通信、站内消息也是勉强足够了。但是作为一个服务的话,100并发下50ms都太大了,而且这里还只是非重复的关键字,重复的关键字是脏词最常见的形式,那么可不可以更快一点。

Trie 出场

在计算机科学中,trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。— from 维基百科

关于Trie的原理可以查看Tool.Good的文章:https://www.cnblogs.com/toolgood/p/6284718.html

Trie的实现

高达2.3k星的实现:ToolGood.Words,每秒3亿的过滤效率,有多个语言版本的实现,其中以ccharp为主,python的 “咳咳”有点尴尬,从文件命名上看就不友好,而且似乎作者用来检测的字词有点过于简短,四五个关键词,不超过20个字符的文本输入完全测试不出压力。接下来试试Trie的威力,同样先从python试起,下面是测试代码:

from StringSearch import StringSearch
with  open('./sample_post',encoding='utf-8') as f:
    test_post = f.read()
with open('././SpamWordsCN.min.txt',encoding='utf-8') as f:
    spam_words = [line.rstrip() for line in f]
import time
search = StringSearch()
search.SetKeywords(spam_words)
start = time.time()
times = 500
while times > 0:

    f = search.FindFirst(test_post)
    times -= 1
end = time.time()
print('程序运行时间:%s毫秒' % ((end - start) * 1000))

Trie的实现只有之前哈希表的五分之一

测试环境下过滤效率大概是 2千万/每秒 ,看来csharp 3亿确实是可能的。 它之所以快是因为哈西表只解决了输入字符中完全不需要匹配的n,而Trie把关键字的复杂度也降低为O(max(l))。白话一点就是哈西表把indexof中遍历m遍降低为一遍,而Trie把哈希表中关键词的检测也降低为max(l),这里的l指最长的关键字长度,也可以理解为关键字的每一个字匹配都像哈希中的实现一样只需要一次匹配。O(n+max(l))的复杂度已经很低,效率极高。对于要求不高的项目直接可以耦合进现在的处理中。那么可不可以更快一点?

Fastcheck 和 TTMP

这里还有两个号称更快的算法FastCheck和TTMP,咳咳,只搜索到了博客园的帖子,不信你google。因为是改进算法,其中fastcheck在tool.goods中有实现,期待将来能再做一个评测比较。他们比Trie快在,适当的增加了空间换了可观的时间,比如fastcheck利用了类似bitmap的原理考虑了字符出现的位置,所以它在短关键字和过滤替换场景上的表现会很好。

还是前面的问题可以不可以更快一点

接下篇《从125ms到17ms,一次关键字检测过滤服务的优化》

FastAPI 中的Async (并发和async/await)

引用文地址:https://fastapi.tiangolo.com/async/

前言:fastapi是一个广泛使用的高效的restful api框架,他的作者在这篇讲解框架中使用async的说明详细举例解释了异步编程、并发和并行的区别,堪称经典,于是手痒总结如下(不敢说翻译)

async使用指导:

  • 如果这个请求比较慢,比如连接数据库读取数据、文件IO、rpc调用等一般加上async,我想普通项目里大部分请求都会多多少少跟数据库打交道,所以加的往往比较多;
  • 比较快,稳定的使用def即可,也可以把没有上面情况调用的都按照这类处理;
  • 分不清加不加就不加,放心这不是什么大的问题,我在测试中发现不加效率高一丢丢,毕竟async是要开销的,特别适合首页这种重缓存的场景;

异步编程:

Asynchronous Code是给语言层面提供一种方式描述程序在某一点等待慢操作比如慢文件读写或者需要其它资源协作,从而让计算机可以在等待中去执行其它工作。文中作者详细列举了相对于cpu和内存操作来说慢的多的慢操作如:

  • 网络通信
  • 文件读写
  • 远程api调用如rpc调用
  • 数据库操作和查询

异步是相对同步编程来说的,在执行顺序上并没有改变。实际效果就是同一台机器如果有比较多的慢操作使用异步编程可以提高吞吐率。这里再吐槽一下,大部分语言比如python、csharp、go等的async都是协程实现的,只有java是线程实现的。代价:进程>线程>协程,java不是不能用协程来实现完全是因为历史包袱,线程相对也不会丢失太多的性能。

并发和汉堡的故事

异步的描述通常称之为并发,它跟并行不是一回事,虽然他们经常都是描述在同一时间内处理不同的事情。在细节上两者大不相同,可以下面就是用汉堡的故事来理解:
你带着心爱的小妹挤进忙碌的快餐店,排队等着付钱买汉堡。
轮到你的时候,你付钱买了两个很赞的大汉堡。
付钱
收银员告诉厨房新的订单,哪怕厨房还在准备前面的汉堡
收银员告诉你取餐号码
然后你和小妹找张桌子谈谈人生谈谈理想,因为汉堡太赞需要时间慢慢做
你趁这个时间对小妹一阵猛夸,如何如何的魅力,如何如何的动人
轮到你了后去柜台取走汉堡
然后就可以和小妹一起慢慢品尝

设想一下,如果是计算机中这个故事会怎么样
排队的时候你可能需要被迫傻等,而不是做点有意义的事比如调情,因为收银员得一个个收钱
当轮到你的时候你才会忙着看菜单,点单,付钱,确认找回来的钱,就算这样你还是没有拿到汉堡,只能去找张桌子等待
直到这个时候你才可以做些有意义的事
因为你有取餐号,所以直到显示屏上通知你可以取餐都不用担心你的汉堡被偷了
最后你去柜台取餐,道谢后算是完成任务并开始任务品尝美味的汉堡。

并行汉堡

现在设想下如果不是并发,而是并行,那么你跟你的小妹来到汉堡店就不是排一个队伍了,而是并行比如8个队伍,没有收银员只有厨师。
你前面每个人都在等他们的汉堡,拿到汉堡后才能离开柜台,厨师只有交出汉堡后才会收下一位的订单。
终于等到你了,付钱,然后等待厨师去厨房做汉堡
没有取餐号,因为没人在你前面,这时你和你的小妹只能干一件事就是确保自己在第一个不让别人抢走汉堡
等待很久以后才能拿走,这里几乎没有时间给你调情,真是个悲伤的故事。
在这个并行场景中你和你的小妹是两个处理器都等待了很久。
快餐店需要8个处理器,而并发下只需要2个。
当然,这个体验肯定不好。

汉堡总结

这个场景中有非常多的等待,所以它更适合并发系统。这也是web程序遇到的问题。太多太多的用户,但是你的服务器要等待不那么快的连接,然后还要等待回应。虽然这里的等待是以毫秒记,但是加起来就很恐怖了。所以这就是为什么Web APIs都用异步。
对于大多数现有的python框架都是在异步特性出现前设计的,所以他们大多以并行的方式实现异步,性能不强。

是不是并发比并行更好?

不,使用场景不同,并发适合web程序,但不是所有。比如科学计算,图形/音频处理,机器学习,深度学习等都适合并行计算。并行计算主要解决cpu瓶颈。

阿里云vs华为云 的容器镜像服务swr使用体验

容器镜像服务是CI构建中很重要的一换,毕竟dockerhub没有私有镜像服务,它除了给你的docker镜像加速外还可以存放你的构建,最最重要的都是免费的,目前阿里华为都有类似的服务。我一直以来使用的都是阿里云容器镜像服务,速度刚刚的,今天在整镜像的时候发现死活登录不上去,检查了一遍确认都没错,一脸懵逼,就阿里一直以来的niaoxing万一是他的问题呢于是想该用华为并对使用体验做个对比。下面是阿里登录容器镜像的截图:

这里是个人凭证页,可能企业版有所不同
  • 吐槽1:固定密码也就是我一直用的,很好很方便,虽然有点小担心,但是确实门槛低,我就不喷了。
  • 吐槽2:看到我红色涂的那个了么,没错我的账号,因为特殊原因第一个原生的账号被别人用了,经历了一顿复杂的操作后我才拿到的第二个账号,这里吐槽下阿里的账号流程乱的不行,肯定没有很好的产品梳理,最后只能人工介入,是不是很阿里的smell,有经验的在用阿里产品的多多少少应该有类似的情况喷到。
  • 吐槽3:2的问题就算了,新的账号居然给我弄一堆***,阿里的同学们请问这是什么逻辑,有重复的或者任何原因用任何字符都行,来一堆***,让人总是认为背后真实的数字是什么,结果它真实的id就是星星。。。。
  • 吐槽4:星星带来的结果就是命令行里头你会提示错误,机智如我,你可以用ID来代替,ID是一串数字,可以在右上角找到

果断开始注册华为,这里赞一下登录时候的提示,当输入手机号后它会提示你选择,如果你曾经在华为不同的业务上注册的账号里选一个。另外找回账号时它不要求你全答对,这就很严谨又很人性化,最后能把账号顺利注销开始新生活。废话不多说,进入华为swr后傻眼了

原文链接:https://support.huaweicloud.com/usermanual-swr/swr_01_1000.html

这里最重要的是第三步,得到第三步就可以拼凑出4中的登录命令。

  1. 图中3里头就是用你的ak+sk hash出了登录密码,这里要赞一下的是这种方式不会丢失你的私钥,安全比阿里好多了,个人设置的固定密钥一般都比较简单。实在偷懒把生成的后的密钥记录下来即可。
  2. 最后生成命令这里更取巧,直接用了ak,ak本身就是密钥id,也就是说只要你这个密钥还用就成立,同样的你的这个ak销毁了,登录也就失效了。
  3. 这里的区域项目名可以在华为提供的可用区域里选择。我挺好奇的是阿里只给了一个杭州节点,华为给了north和east两个,难道阿里不是份额最大的厂么。

最后吐槽一下,发现使用阿里的各种镜像服务越来越不稳定,应该不是马爸爸缺钱了,估计是被人玩坏了,比如容器镜像,最近有人利用它不限制容量的漏洞来做网盘。。。华为就不行,它有2g上限,谁的镜像能上2g啊。名族之光,技术中流不是吹的。。。

理工男对衣架的选择/衣柜收纳之衣架篇

创作立场声明:拍不好,借淘宝图一用,希望给同样在思考收纳工具的值一点参考。

衣柜困局

衣柜困局

天气冷了,又到了翻衣服的时候了

首先我是个懒人,衣服叠的乱七八糟,虽然努力过把衣物分门别类,但是怪就怪总有几天是忽冷忽热的天气。作为一个理工男抓重点是咱的习惯,衣服么格子衫牛仔裤就行了,然后就这么蹉跎讲究了十几年。这几天又鼓起勇气打算用算法来科学的规划下。

首先理顺的原则非常重要,凡是都要从我懒的根本基础出发,所以太过细致繁琐的收纳一律不考虑,所有改变都要服从很顺手很方便的原则。在经历了数个小时的苦思冥想后决定之前之所以总是失败是抓错了重点,不能以整洁作为收纳的目标而忽视了天气才是决定衣物分类的最最大的影响因素。

从天气出发那么考虑的周期必须是一年中的变化,看了很多收纳文章都是按照四级来区分,天哪,我这理工男要把衣物分成四类,拜托别开玩笑了。其实回忆一下几十个春秋冬夏,不难发现真正衣物有大的区分的时候只有冬天最冷和夏天之间,也就是说夏天的时候咱不可能穿棉袄,反之依然,好吧,这是废话,但是收纳咱就可以按照这个原则来了。

当季区,缓冲区,储藏区

虽然我分为了三个区,但是实际上使用中只会分两个地方,衣柜和储藏间,下面就说说我是怎么分的。

理工男对衣架的选择/衣柜收纳之衣架篇

储藏区

:我家的储藏间离房间隔着一个客厅还是挺远的,这个每个人都不一样,但是我对储藏的定义是它可以远一点,就好象是电脑上的硬盘,你可以慢一点,但是必须得够大,咱实际上的储藏间不可能大但是能装很多是一样的道理。储藏间的原则就是当季绝对不会用的衣物才存放在这里,原则上一个月甚至两个月才会进行一次储藏间的调整,哎呀越说越像硬盘了。这里遵循之前说的最远距离原则,比如夏天,储藏间里应该是羽绒服、帽子、围巾、这些东西。这里要注意的是,但凡有可能冬夏都用到的东西(嘿嘿,我不多,可能有的人多,咱不细说),只要是可能当季用到的都不放。最后在储藏间的收纳上,建议按照队列原则,即是先进后出,换句话说就是你可以从左往右移动收纳,这样先放进去的东西最后下个周期才会用到。这个时候可能有人会觉得冬夏一年就一次,春秋是通用的有两次,队列会出现两次,其实不然,看完缓冲区您再品,细品。

理工男对衣架的选择/衣柜收纳之衣架篇

下面是购买地址:

https://item.taobao.com/item.htm?id=6332490120

当季区、缓冲区:

这个基本就是卧室里的衣柜了,如果你跟我一样有一个上图中带滚轮的室内衣架那么恭喜你,这不仅方便,还是懒人必备,当季区完全可以由它担当,其他的衣物放衣柜里作为过度区。这里安利下上面的带滚轮衣架,太好用了,不仅可以随手拉来拉去快速找衣服,下面还可以放一些内衣和袜子,比起衣柜不要太方便,高度可调节到手最舒服的位置,嗯,没错,懒人抬手也是很累的好吧。

之所以这么分配几个区域就是从衣物的使用频率来区分的,当季区就像是cpu内的缓存可以最快的速度满足当前的需求,假如温度变化大了,那么才值得多花两分钟去缓冲区就像是内存,比如夏天的时候短袖就是当季区里的,长袖的衬衫就是缓冲区里的,对了我冬季也会把衬衫放在缓冲区的,秋天衬衫是在当季区。怎么区分三个区域的衣物就想想使用频率就可以知道了。下面说说在这两个区域收纳中我使用到的神器

衣架的选择

便宜就是王道

便宜就是王道

这个必须重头戏,衣物的收纳最最用到的就是衣架了,那么怎么选衣架呢,比如上图就是我选的,没错平平无奇的衣架。选它的理由无他,便宜量足,够用,所以买一把放着解决主要矛盾:挂衣服,哈哈哈哈啊理工男对衣架的选择/衣柜收纳之衣架篇

理工男对衣架的选择/衣柜收纳之衣架篇

买上面普通衣架的时候在这家小店发现了这么个新奇的东西,哎唷我去,踏破贴无米处呀。没错,懒人么除了囊肿羞涩买的普通衣架外主力肯定得靠懒人衣架了咯,上面衣架完美解决我的各种使用场景,能挂所有衣物理工男对衣架的选择/衣柜收纳之衣架篇 。

外观展示

领口插入衣架

领口插入衣架

懒人衣架的标志就是可以从领口插进去,也是我挑选的第一标准,这里还有几款:

1号,最常见的

1号,最常见的

2号,也很常见

2号,也很常见

3号,少见

3号,少见

4号,没见过

4号,没见过

衣领插入,最重要的特点

红色标记的距离就是能插进的领口的大小,这里1号适用性最差,2号也好不到哪去,12基本有大半衣服是插不了的,这个特性聊胜于无,3号基本能适应大部分场景,但是跟我选的4号来还是差的远。最震惊的是,哪怕冬天那小口的高领毛衣它也可以轻松的进去而不会撑开哪怕一点点,就那么一下尝试就把握圈粉了。

叠挂,神奇的功能

叠挂,神奇的功能

叠挂,节省空间,神奇的分类

之前用过类似的衣架,说实话空间确实有省下来,但是并没有那么明显的作用,反倒是对于分类有奇效,哈哈没错,分类。比如哈对于懒人来说格子衫我会挂几件差不多的在上面,然后把刚洗过的挂最下面,每次只拿最上面的,对于懒人来说有时候反复穿同一件就是因为分不清哪一个是最近洗的。当然还有其他作用,比如把差不多的衣服,差不多的裤子挂一起。这个功能还常用在我会把搭配好的一整套用三个衣架挂一起,上衣+裤子+围巾,出门一手拎出来即可。

理工男对衣架的选择/衣柜收纳之衣架篇

啥都可以挂,一个衣架解决了所有衣柜里的问题

经过我的开发,发现衣柜里头不管是衣服还是裤子,甚至围巾,皮带等等都可以用这一款衣架解决。然后我就发现假如我都买这一款衣架那么一致性带来的美感会对整个衣柜的整洁度大大加分。。。。

总结

这次的衣物收纳整理以发现了一款无人问津的小衣架结束了,很久没有动力写东西了,也许这款衣架的厂家都不知道我的这些用途吧,没办法,很多用途不是懒人你是想不到的。比如我那么执着于 从领口插进去,理工男格子衫多,总是拧纽扣我会疯的,又比如用一种衣架解决所有衣物的问题,这就像是代码重构,这里头还有点小欣喜。

这家小店铺还真的不好找。

衣架店铺名是:圣乔日用品店铺

地址是:renren3d.taobao.com

衣架1:https://item.taobao.com/item.htm?id=620851237203

衣架2:https://item.taobao.com/item.htm?id=630433034430

文章转发自:https://post.smzdm.com/p/560972/