0%

sekiro

Sekiro简介

开抄!以下来自sekiro官方文档 https://sekiro.virjar.com/sekiro-doc/index.html

SEKIRO 是一个android下的API服务暴露框架,可以用在app逆向、app数据抓取、android群控等场景。同时Sekiro也是目前公开方案唯一稳定的JSRPC框架。

  1. 对网络环境要求低,sekiro 使用长链接管理服务(可以理解为每个APP内置内网穿透功能),使得 Android 手机可以分布于全国各地,甚至全球各地。手机掺合在普通用户群体,方便实现反抓突破,更加适合获取下沉数据。
  2. 不依赖 hook 框架,就曾经的 Hermes 系统来说,和 xposed 框架深度集成,在当今 hook 框架遍地开花的环境下,框架无法方便迁移。所以在 Sekiro 的设计中,只提供了 RPC 功能了。
  3. 纯异步调用,在 Hermes 和其他曾经出现过的框架中,基本都是同步调用。虽然说签名计算可以达到上百 QPS,但是如果用来做业务方法调用的话,由于调用过程穿透到目标 app 的服务器,会有大量请求占用线程。系统吞吐存在上线(
    hermes 系统达到 2000QPS 的时候,基本无法横向扩容和性能优化了)。但是 Sekiro 全程使用 NIO,理论上其吞吐可以把资源占满。
  4. client 实时状态,在 Hermes 系统我使用 http 进行调用转发,通过手机上报心跳感知手机存活状态。心跳时间至少 20s,这导致服务器调度层面对手机在线状态感知不及时,请求过大的时候大量转发调用由于 client 掉线
    timeout。在 Sekiro 长链接管理下,手机掉线可以实时感知。不再出现由于框架层面机制导致 timeout
    5.
    群控能力,一台Sekiro服务器可以轻松管理上万个手机节点或者浏览器节点,且保证他们的RPC调用没有资源干扰。你不需要关心这些节点的物理网络拓扑结构。不需要管理这些手机什么时候上线和下线。如果你是用naohttpd方案,你可能需要为手机提供一个内网环境,然后配置一套内网穿透。一个内网一个机房,你需要管理哪些机房有哪些手机。当你的手机达到一百台之后,对应的物理网络环境就将会比较复杂,且需要开发一个独立系统管理了。如果你使用的时FridaRPC方案,你可能还需要为每几个手机配置一台电脑。然后电脑再配置内网穿透,这让大批量机器管理的拓扑结构更加复杂。这也会导致手机天然集中在一个机房,存在IP、基站、Wi-Fi、定位等环境本身对抗。
  5. 多语言扩展能力。Sekiro的客户端lib库,目前已知存在Android(java)、IOS(objective-c)、js(浏览器)
    、易语言等多种客户端(不是所有的都是Sekiro官方实现)。Sekiro本身提供一个二进制协议(非常简单的二进制协议规则),只要你的语言支持socket(应该所有语言都支持)
    ,那么你就可以轻松为Sekiro实现对应客户端。接入Sekiro,享受Sekiro本身统一机群管理的好处。在Sekiro的roadmap中,我们会基于frida
    Socket实现frida的客户端,完成Frida分析阶段的代码平滑迁移到Sekiro的生产环境。尽请期待
    7.
    客户端接入异步友好。Sekiro全程异步IO设计,这一方面保证整个框架的性能,另一方面更加贴近一般的app或者浏览器本身的异步环境。如果rpc调用用在签名计算上面,大部分签名仅仅是一个算法或者一个so的函数调用。那么同步调用就可以达到非常高的并发。但是如果你想通过rpc调用业务的API(如直接根据参数调用最网络框架的上层API,把参数编解码、加解密都,逻辑拼装都看作黑盒)。此时普通同步调用将会非常麻烦和非常消耗客户端性能。异步转同步的lock信号量机制、同步线程等待导致的线程资源占用和处理任务挤压等风险。FridaRPC支持异步,但是由于他的跨语言问题,并不好去构造异步的callback。目前nanohttpd或者FridaPRC,均大部分情况用在简单的签名函数计算上面。而Sekiro大部分用在上游业务逻辑的直接RPC场景上。
  6. API友好(仅对AndroidAPI),我们为了编程方面和代码优雅,封装了一套开箱即用的API,基于SekiroAPI开发需求将会是非常快乐的。

简而言之呢,Sekiro是一个virjar开发的一款强大稳定的rpc服务暴露框架

Kubernetes上部署

首先,我们需要有一个 Kubernetes 集群,可以自己搭建,也可以使用 Minikube 或者用阿里云、腾讯云、Azure 等服务商直接提供的 Kubernetes 服务。 另外我们需要能使用 kubectl 连接和控制当前的集群,

上面的内容准备就绪之后,就可以开始 Kubernetes 搭建。搭建完成之后就开始部署Sekiro吧

在Kubernetes中容器环境部署那就离不开image,Sekiro的image为

1
registry.cn-beijing.aliyuncs.com/virjar/sekiro-server:latest

无论是Docker、还是podman、亦或者是其他的容器。现成的景象就用这个

Kubernetes部署服务的常用的有三种形式

  • kubectl cli部署
  • 基于YAML文件格式部署
  • 基于JSON文件格式部署

部署服务

首先为此创建一个名为crawleruntil 的 namespace

1
kubectl create namespace crawleruntil

image-20220410033511352

因为我已经有crawleruntil这个namespace以及其他的服务,为了方便就不删了在演示了,将图中的sekiro即可

至此已经完成四分之一了,接下来进行Sekiro的部署,yaml文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
apiVersion: apps/v1
kind: Deployment
metadata:
name: sekiro
namespace: crawleruntil
labels:
app: sekiro
spec:
replicas: 3
selector:
matchLabels:
app: sekiro
template:
metadata:
labels:
app: sekiro
spec:
containers:
- name: sekiro
image: registry.cn-beijing.aliyuncs.com/virjar/sekiro-server:latest
imagePullPolicy: Always
ports:
- containerPort: 5601
- containerPort: 5602
- containerPort: 5603
resources:
requests:
memory: "1Gi"
cpu: "0.5"
limits:
memory: "2Gi"
cpu: "1"
restartPolicy: Always


---
apiVersion: v1
kind: Service
metadata:
name: sekiro
namespace: crawleruntil
spec:
selector:
app: sekiro
type: ClusterIP
ports:
- port: 5601
name: nio
targetPort: 5601
protocol: TCP
- port: 5602
name: server
targetPort: 5602
- port: 5603
name: ws
targetPort: 5603

将其复制到服务器上,然后使用命令kubectl apply -f 此文件名,当然kubectl create -f 此文件名也可以大差不差。

稍等几秒甚至是一两分钟,看看

image-20220410034203246

确认使用kubectl get all -n crawleruntil 中显示的内容中有STATUS均为Running, 代表就部署成功了

当然这还并不足以使服务可用,因为sekiro它运行在容器内的网络,暂时还无法在本地、或者远程反问服务。此时就需要将服务暴露出去。以便于使用

服务暴露

暴露服务有NotePort, LoadBalance, 此外externalIPs也可以使各类service对外提供服务。当然最推荐的还是Ingress, 关于Ingress是什么感兴趣的请自行搜索。

首先先需要部署ingress collector,请搜索。部署完成后可采用如下YAML进行ingress服务暴露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# https

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sekiro-ingress
namespace: crawleruntil
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: sekiro
port:
name: server
- pathType: Prefix
path: /asyncInvoke
backend:
service:
name: sekiro
port:
name: nio
- pathType: Prefix
path: /websocket
backend:
service:
name: sekiro
port:
name: ws

此时访问你外网地址, 就会发现。(80需要有权限打开访问哦)

image-20220410035421312

ok,部署完成了! sekiro就可以食用啦。

以上明显还不够,主要是两个问题

没有身份验证: 那么就意味着任何人都可以调用你的这个接口,岂不是…

某些网页端的JS有证书,这样部署的sekiro无法使用此场景,需要为之加上tls证书。

Ingress 网页添加认证

想偷懒的可以直接使用这三条命令。认证令牌创建一步到位

1
2
3
yum -y install httpd
echo `echo $(hostname) | base64` | htpasswd -ic ./auth `echo $(echo $(hostname) | base64)`
kubectl create secret generic basic-auth-secret --from-file auth --namespace=crawleruntil

我这里默认采用机器的hostname base64后的字符为用户名与密码。

1
2
echo `echo $(hostname) | base64` :密码改这里面的内容
./auth `echo $(echo $(hostname) | base64)`: 用户名这里面的内容

然后直接使用kubectl create secret generic basic-auth-secret --from-file auth --namespace=crawleruntil

创建一个名为basic-auth-secret generic类型的secret。

注意需要将namespace 指定为crawleruntil,实现同意命名空间

将它挂到Ingress上去,

Ingress YAML如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# https

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sekiro-ingress
namespace: crawleruntil
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/auth-type: "basic"
traefik.ingress.kubernetes.io/auth-secret: "basic-auth-secret"
spec:
rules:
- http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: sekiro
port:
name: server
- pathType: Prefix
path: /asyncInvoke
backend:
service:
name: sekiro
port:
name: nio
- pathType: Prefix
path: /websocket
backend:
service:
name: sekiro
port:
name: ws

打开一个无痕浏览器,此时你就可以发现

image-20220410040928885

这玩意需要认证了,cool~

域名解析与TLS网络证书

域名:首先你得需要有域名,这个去阿里云、腾讯云、华为云还是等等买就是咯。

证书:将你申请点TLS证书下载到本地,具体请搜索

创建证书的secret, 如下

1
kubectl create secret tls tls-sekiro-paynewu-com --key sekiro.paynewu.com.key --cert=sekiro.paynewu.com_bundle.crt --namespace crawleruntil

最终的Ingress YAML如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sekiro-ingress
namespace: crawleruntil
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/auth-type: "basic"
traefik.ingress.kubernetes.io/auth-secret: "basic-auth-secret"
traefik.frontend.rule.type: PathPrefixStrip
traefik.ingress.kubernetes.io/redirect-entry-point: https
traefik.backend.circuitbreaker: "NetworkErrorRatio() > 0.5"
spec:
tls:
- hosts:
- sekiro.paynewu.com
secretName: tls-sekiro-paynewu-com
rules:
- host: sekiro.paynewu.com
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: sekiro
port:
name: server
- pathType: Prefix
path: /asyncInvoke
backend:
service:
name: sekiro
port:
name: nio
- pathType: Prefix
path: /websocket
backend:
service:
name: sekiro
port:
name: ws

根路径

image-20220410041714078

image-20220410043120299

注入验证

https://sekiro.virjar.com/sekiro-doc/assets/sekiro_web_client.js 中的文件复制到控制台,执行

然后在控制台调用

1
var client = new SekiroClient("wss://域名/websocket?group=ws-group-test&clientId=" + guid());

校验

image-20220410043429585

完成~

Referer

Sekiro GitHub

其他部署都可以看看邱佬的

basic-authentication

security-headers-annotations

HomeBrew

GitHub

HomeBrew官方地址

docs

docs-Formula-Cookbook

brew.idayer

Homebrew是什么

Homebrew简单来说他是类似于yum、apt,mac的包管理工具,使用它我们可以非常简单、丝滑的下载大部分的包、或者软件

安装

1
2
3
4
# 官方
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# cdn
/bin/bash -c "$(curl -fsSL https://cdn.jsdelivr.net/gh/ineo6/homebrew-install/install.sh)"

完成安装后使用

1
brew update && brew upgrade && brew doctor && brew -v

使用技巧

配置阿里云镜像

配置阿里云mirror:https://developer.aliyun.com/mirror/homebrew

完成后记得使用如下命令进行更新

1
brew update; brew upgrade; brew cleanup; brew doctor

防止更新特定的自制配方

如果要避免更新某些公式,可以使用以下brew命令将当前版本保持不变:

1
brew pin [name]

当然,您可以取消固定公式以再次对其进行更新:

1
brew unpin [formula]
1
2
3
4
5
6
7
8
9
10
11
12
# 查看使用brew安装的服务列表
brew services list
# 启动服务(仅启动不注册)
brew services run formula|--all
# 启动服务,并注册
brew services start formula|--all
# 停止服务,并取消注册
brew services stop formula|--all
# 重启服务,并注册
brew services restart formula|--all
# 清除已卸载应用的无用的配置
brew services cleanup

基于软连接实现多版本控制

当hombrew中有多个版本时,可以基于link or unlink 实现包版本的控制

  • 当并不需要配置环境变量时
1
brew link [name]

Linux 下Homebrew安装

1
2
3
4
5
# Debian or Ubuntu
sudo apt-get install -y build-essential procps curl file git
# Fedora, CentOS, or Red Hat
sudo yum groupinstall -y 'Development Tools'
sudo yum install -y procps-ng curl file git libxcrypt-compat

https://docs.brew.sh/Homebrew-on-Linux#requirements

TAP

1
2
3
4
5
6
7
homebrew/cask
homebrew/core
homebrew/services
isacikgoz/taps
mongodb/brew
petere/postgresql
shivammathur/php

云源生

虚拟化系统

  • 进程虚拟化
  • Namespaces 进行虚拟化隔离
  • Control Groups 进行对资源的限制,
  • 联合文件 UFS(Union File Systems)来快速构建(可复用的镜像层)

Docker

Docker的前世今生

Docker的架构与容器生命周期:created、running、stopped、paused、deleted

Docker 环境搭建

Docker 基本操作

Dockerfile 编写与各指令使用

Docker 网络

Docker 存储

Docker 编排:Docker、Docker-compose、Docker-Swarm

Docker DevOpts

Kubernetes

Kubernetes 的前世今生(Borg 系统)

Kubernetes 架构与生命周期

Kubernetes 基本术语

Kubernetes 环境搭建

Pod

  • 什么是Pod
  • Pod使用
  • Pod生命周期
  • Pod健康检查
  • Pod 调度
    • 调度器:Deployment、ReplicationController、ReplicaSet、StatefulSets、DaemonSet、Jobs、CronJobs
    • Pod、Node 亲和性
    • Pod调度优先级
    • 污点和容忍
  • Pod扩缩容机制

Kubernetes 网络

  • Services

  • DNS

  • Ingress

Kubernetes 存储

  • 存储机制
  • 存储卷:PV、PVC、StorageClass
  • GlusterFS
  • CSI

Kubernetes 安全

  • API Server认证与授权
  • Admission Control
  • Service Account
  • Secret

Kubernetes 运维管理与监控

  • Helm
  • DevOps
  • Dashboard
  • Prometheus(thanos) + Granfana
  • Kafka、ElasticSearch、FileBaet、Kibana

Kubernetes组件及运行机制

  • API Server
  • Controller Manager
  • Scheduler
  • kubelet
  • Kube-proxy
  • etcd
  • Kubernetes调度器与控制器

服务治理-Istio

Envoy

学习资源

官方网站:https://kubernetes.io

GitHub: http://github.com/kubernetes

aliyun:https://help.aliyun.com/product/85222.html

Docker

Docker从入门到实践

深入浅出Docker

Docker容器与容器云

第一本Docker书

Kubernetes

Kubernetes Handbook

istio-handbook

Kubernetes权威指南

Kubernetes进阶实战

Kubernetes网络权威指南

Kubernetes生产化与实践之路

深入剖析Kubernetes

Kubernetes源码剖析

Kubernetes in Action

云原生服务网格istio

性能之巅:洞悉系统、企业与云计算洞悉系统、企业与云计算

BPF之巅洞悉Linux系统和应用性能

社区

https://www.kubernetes.org.cn/

https://jimmysong.io/

视频

https://www.bilibili.com/video/BV13Q4y1C7hS

爬虫

爬虫需要学什么,怎么学?小子不才,今日与诸位妄谈爬虫需要学什么,怎么学? 这一话题,个人拙见将自我的学习路程分为如下几个阶段

  • 第一层:编程语言
  • 第二层:网络知识,各工具熟练使用
  • 第三层:高性能的爬虫
  • 第四层:JS逆向
  • 第五层:小程序逆向
  • 第六层:Android逆向
  • 第七层:分布式爬虫
  • 第八层:爬虫架构
  • 第九层:工具链构建

第一层:编程语言

编程语言自然不必多说,至少得会一门语言。推荐的语言有Java、Python、Go、JS

编程语言可简单细分为如下步骤

  • 语言背景与历史
  • 环境搭建
  • 常量变量
  • 基础数据类型(bytes、String、int、bool)等
  • 运算与运算符(+、-、*、÷、|、&、^、)、位运算
  • 条件分支语句(if、switch等)
  • 循环语句(forloop、while loop)
  • 函数、匿名函数、嵌套函数、闭包
  • 常见算法(递归、分治、回溯)、排序算法、动态规划、贪心算法、遍历算法
  • 错误、异常处理
  • 文件操作
  • 并发编程
  • 网络编程
  • 面向对象
  • runtime
  • gc
  • 数据库与中间件(MySQL、MongoDB、Redis、ES)

广度优先,重复学

第二层:网络知识与各工具使用

  • 网络模型,ISO、TCP/IP
  • 网络协议:HTTP、HTTPS
  • 网络知识:URN、URI、URL
  • 认证:cookie、session、Token、JWT

工具

浏览器:Chrome

抓包工具:Fiddler、Charles、Mitmproxy(中间人抓包原理,网络原理)

Postman

学完这部分,恭喜你。你已经可以独立开发一个简单的爬虫了

爬虫基本开发步骤如下:

网络抓包,确定目标地址(URN)

发送请求,获取响应

解析响应、获取数据

数据存储(将提取的数据存储到txt或JSON、MySQL、MongoDB、Redis、ES)

第三层:高性能的爬虫

在完成了第二层之后,在其到基础上加速爬虫

  • 进程、线程、协程
  • 并发并行
  • 阻塞非阻塞、I/O模型

第四层:JS逆向

——“想爬?偏不给你爬!”

  • 反抓包

    • 证书

    • 双向验证

    • 竟然不用HTTP(S)!RPC来搅局?

反请求

  • 请求头反抓

    • UA
    • Sign、token、sale
    • Header
  • 代码防护

    • 布局混淆
    • 数字混淆
    • 字符串混淆
    • 控制流混淆
    • 预防混淆
  • 验证码

    • 识别点选或输入型

    • 滑轨、滑块型

    • 计算型

    • 空值补齐型

    • 空间推理型

    • 短信或语音验证型

  • 数据防护

    • 动态字体
    • CSS偏移
    • 内容加密映射
    • SVG映射等
    • 内容图片化
  • 反调试

    • 控制台检测
    • 无限debugger来了!
    • 函数居然被重写?
    • 不讲武德,参数生产后删除js!
  • 其他反抓

    • 风控(如IP、用户行为)
    • 浏览器指纹
    • 代码运行环境检测等
    • JS虚拟机、jsvm、jsvmp
    • wasm

金盆洗手?自动化工具来助威! RPC半真半假远程调用

加密算法怎么定位?奇银技巧来助威! Hook、各种搜索断点、debug(XHR、事件)中场休息助一臂之力

插桩妙调,算法自吐

代码怎么扣?环境怎么补?加密算法怎么还原??

AST混淆代码对抗,逐一击破,稳扎稳扎。相信自己会逐渐强大

第五层:小程序逆向

在完成第四层JS逆向的基础上,来看看新宠儿小程序?

小程序包如何来,小程序又是如何验证的?

小程序项目结构又如何?

静态调试,全局游走gank

第六层:Android逆向

新的征程?!

Android正向开发也要学!apk、四大组件、项目布局、配置

反编译神器jadx初尝

又抓不到包了啊!

  • 不走代理端口了啊
  • 证书校验
  • 居然魔改网络协议?

居然还有代码壳,保护代码

“强盗打劫!,移花接木”

  • Frida

  • ratel

  • Xpose

smail、汇编、C/C++、So

IDA动静调试一探究竟

AOSP

完了,芭比扣了

补补Java、JNI、Android吧

第七层:分布式爬虫

任务过多,工人不够?

消息队列抽离任务全局可用,一代多

任务supervisor一带多

配上docker虚拟化,K8s虚拟化,一起拥抱云源生

有始有终,奴奴++

第八层:爬虫架构

跑在云上等,网络角逐者

云监控:thanosgrafana

日志监控:KafkaElasticSearchFileBaetkibana

第九层:工具链构建

UA Pool

Proxy Pool

Account Pool

DL识别验证码

智能解析算法

Apache Kafka

工欲善其事,必先利其器。先把Kafka跑起来!为了资源更有效的利用,需要考虑磁盘、网络带宽

资源规划

磁盘容量

需要考虑几个因素:

  • 新增消息数
  • 消息留存时间
  • 平均消息大小
  • 备份数
  • 是否启用压缩

计算公式为:新增消息数 消息留存时间 平均消息大小 备份数 压缩率 * (1 + 10 %)(索引以及其他数据)

假设有个业务每天需要向 Kafka 集群发送 1 亿条消息,每条消息保存两份以防止数据丢失,另外消息默认保存两周时间。现在假设消息的平均大小是 1KB,那么你能说出你的 Kafka 集群需要为这个业务预留多少磁盘空间吗?

每天 1 亿条 1KB 大小的消息,保存两份且留存两周的时间,那么总的空间大小就等于

$10 ^ 8 1 2 = 2 * 10^8 KB = 0.2 TB$

加上索引以及其他类型数据 在原有基础上增加 10%,那就是0.22TB

保留两周:0.22TB * 14 = 3.08 TB

压缩率为80%: 3.08 * 0.8 = 2.464 TB ≈ 2.5 TB

保险起见建议预留3 TB的存储空间

网络带宽

对于 Kafka 这种通过网络大量进行数据传输的框架而言,带宽特别容易成为瓶颈。事实上,在接触的真实案例当中,带宽资源不足导致 Kafka 出现性能问题的比例至少占 60% 以上

当规划带宽时到不如说是部署kafka服务器数量

通常情况下只能假设 Kafka 会用到 70% 的带宽资源,因为总要为其他应用或进程留一些资源。根据实际使用经验,超过 70% 的阈值就有网络丢包的可能性了,故 70% 的设定是一个比较合理的值,也就是说单台 Kafka
服务器最多也就能使用大约 700Mb 的带宽资源。

稍等,这只是它能使用的最大带宽资源,你不能让 Kafka 服务器常规性使用这么多资源,故通常要再额外预留出 2/3 的资源,即单台服务器使用带宽 700Mb / 3 ≈ 240Mbps。需要提示的是,这里的 2/3
其实是相当保守的,你可以结合你自己机器的使用情况酌情减少此值。

好了,有了 240Mbps,我们就可以计算 1 小时内处理 1TB 数据所需的服务器数量了。根据这个目标,我们每秒需要处理 2336Mb 的数据,除以 240,约等于 10 台服务器。如果消息还需要额外复制两份,那么总的服务器台数还要乘以
3,即 30 台。

参数配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
config
├── connect-console-sink.properties
├── connect-console-source.properties
├── connect-distributed.properties
├── connect-file-sink.properties
├── connect-file-source.properties
├── connect-log4j.properties
├── connect-mirror-maker.properties
├── connect-standalone.properties
├── consumer.properties
├── kraft
│ ├── README.md
│ ├── broker.properties
│ ├── controller.properties
│ └── server.properties
├── log4j.properties
├── producer.properties
├── server.properties
├── tools-log4j.properties
├── trogdor.conf
└── zookeeper.properties

JVM 参数与垃圾回收算法

Kafka 服务器端代码是用 Scala 语言编写的,但终归还是会编译成 .Class 文件在 JVM 上运行,因此 JVM 参数设置对于 Kafka 集群的重要性不言而喻。

JVM 端设置,堆大小这个参数至关重要,无脑通用的建议:将 JVM 堆大小设置成 6GB

垃圾回收器的设置,也就是平时常说的 GC 设置。

手动设置使用 G1 收集器。在没有任何调优的情况下,G1 表现得要比 CMS 出色,主要体现在更少的 Full GC,需要调整的参数更少等,所以使用 G1 就好了。

KAFKA_HEAP_OPTS:指定堆大小。

KAFKA_JVM_PERFORMANCE_OPTS:指定 GC 参数。

1
2
3
export KAFKA_HEAP_OPTS=--Xms6g  --Xmx6g
export KAFKA_JVM_PERFORMANCE_OPTS= -server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent -Djava.awt.headless=true
kafka-server-start.sh ${KAFKA_HOME}/config/server.properties

操作系统参数

通常情况下,Kafka 并不需要设置太多的 系统参数

下面这几个在此较为重要:

  • 文件描述符限制:比如ulimit -n 1000000
  • 文件系统类型: 根据官网的测试报告,XFS 的性能要强于 ext4,所以生产环境最好还是使用 XFS甚至是ZFS。
  • swap:建议将 swappniess 配置成一个接近 0 但不为 0 的值,比如 1。
  • 提交时间:适当的增加提交间隔来降低物理磁盘的写操作

部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 安装jdk
sudo yum group install -y "development tools"
sudo yum install -y java-1.8.0-openjdk.x86_64
# 下载kafka
wget -c https://mirrors.tuna.tsinghua.edu.cn/apache/kafka/3.1.0/kafka_2.13-3.1.0.tgz
# 解压缩
tar -zxf kafka_2.13-3.1.0.tgz -C /usr/local

# 配置环境变量
# kafka env config
export KAFKA_HOME=/usr/local/kafka_2.13-3.1.0
export KAFKA_BIN=${KAFKA_HOME}/bin
export PATH=${KAFKA_BIN}:PATH

source /etc/profile

# 初始化
kafka-storage.sh format -c ${KAFKA_HOME}/config/kraft/server.properties -t `kafka-storage.sh random-uuid`
# 启动服务
kafka-server-start.sh ${KAFKA_HOME}/config/kraft/server.properties

验证

1
2
3
4
5
6
# 创建quickstart-events主题
kafka-topics.sh --create --topic quickstart-events --bootstrap-server localhost:9092

kafka-console-producer.sh --topic quickstart-events --bootstrap-server localhost:9092

kafka-console-consumer.sh --topic quickstart-events --from-beginning --bootstrap-server localhost:9092

Kafka监控工具

  • JMXTool: 可以实时查看kafka JMX 指标,但仅限于简单的监控场景
  • Logi-KM: didi开源的一站式Apache Kafka集群指标监控与运维管控平台
  • JMXTrans + InfluxDB + Grafana
  • EFAK
  • Kafka tool
  • CMAK:雅虎源的kafka监控器,阿里在用

Apache Kafka

Kafka 发行版本

Apache Kafka

Apache Kafka 是最“正宗”的 Kafka,也应该最熟悉的发行版。

自 Kafka 开源伊始,它便在 Apache 基金会孵化并最终毕业成为顶级项目,它也被称为社区版 Kafka。咱们专栏就是以这个版本的 Kafka
作为模板来学习的。更重要的是,它是后面其他所有发行版的基础。也就是说,后面提到的发行版要么是原封不动地继承了 Apache Kafka,要么是在此之上扩展了新功能,总之 Apache Kafka 是我们学习和使用 Kafka 的基础

对 Apache Kafka 而言,它现在依然是开发人数最多、版本迭代速度最快的 Kafka。在 2018 年度 Apache 基金会邮件列表开发者数量最多的 Top 5 排行榜中,Kafka 社区邮件组排名第二位。如果你使用 Apache
Kafka 碰到任何问题并提交问题到社区,社区都会比较及时地响应你。这对于我们 Kafka 普通使用者来说无疑是非常友好的。

Apache Kafka 的劣势在于它仅仅提供最最基础的组件,特别是对于前面提到的 Kafka Connect 而言,社区版 Kafka
只提供一种连接器,即读写磁盘文件的连接器,而没有与其他外部系统交互的连接器,在实际使用过程中需要自行编写代码实现,这是它的一个劣势。

另外 Apache Kafka 没有提供任何监控框架或工具。显然在线上环境不加监控肯定是不可行的,你必然需要借助第三方的监控框架实现对 Kafka 的监控。好消息是目前有一些开源的监控框架可以帮助用于监控 Kafka(比如
Kafka manager
)。

总而言之,如果仅仅需要一个消息引擎系统亦或是简单的流处理应用场景,同时需要对系统有较大把控度,那么推荐你使用 Apache Kafka。

Confluent Kafka

它主要从事商业化 Kafka 工具开发,并在此基础上发布了 Confluent Kafka。Confluent Kafka 提供了一些 Apache Kafka 没有的高级特性,

比如跨数据中心备份、Schema 注册中心以及集群监控工具等。

Confluent Kafka 目前分为免费版和企业版两种。

前者和 Apache Kafka 非常相像,除了常规的组件之外,免费版还包含 Schema 注册中心和 REST proxy 两大功能。

前者是帮助你集中管理 Kafka 消息格式以实现数据前向 / 后向兼容;

后者用开放 HTTP 接口的方式允许你通过网络访问 Kafka 的各种功能,这两个都是 Apache Kafka 所没有的。

除此之外,免费版包含了更多的连接器,它们都是 Confluent 公司开发并认证过的,可以免费使用它们。

至于企业版,它提供的功能就更多了。最有用的当属跨数据中心备份和集群监控两大功能了。

多个数据中心之间数据的同步以及对集群的监控历来是 Kafka 的痛点,Confluent Kafka 企业版提供了强大的解决方案帮助你“干掉”它们

Confluent 公司暂时没有发展国内业务的计划,相关的资料以及技术支持都很欠缺,很多国内 Confluent Kafka 使用者甚至无法找到对应的中文文档,因此目前 Confluent Kafka 在国内的普及率是比较低的。

如果需要用到 Kafka 的一些高级特性,那么推荐使用 Confluent Kafka。

Cloudera/Hortonworks Kafka

Cloudera 提供的 CDH 和 Hortonworks 提供的 HDP 是非常著名的大数据平台,里面集成了目前主流的大数据框架,能够帮助用户实现从分布式存储、集群调度、流处理到机器学习、实时数据库等全方位的数据处理。

很多创业公司在搭建数据平台时首选就是这两个产品。不管是 CDH 还是 HDP 里面都集成了 Apache Kafka,因此把这两款产品中的 Kafka 称为 CDH Kafka 和 HDP Kafka。

大数据平台天然集成了 Apache Kafka,通过便捷化的界面操作将 Kafka 的安装、运维、管理、监控全部统一在控制台中。所有的操作都可以在前端 UI 界面上完成,而不必去执行复杂的 Kafka 命令。

这样做的结果是直接降低了你对 Kafka 集群的掌控程度。

另一个弊端在于它的滞后性。

由于它有自己的发布周期,因此是否能及时地包含最新版本的 Kafka 就成为了一个问题。比如 CDH 6.1.0 版本发布时 Apache Kafka 已经演进到了 2.1.0 版本,但 CDH 中的 Kafka 依然是 2.0.0
版本,显然那些在 Kafka 2.1.0 中修复的 Bug 只能等到 CDH 下次版本更新时才有可能被真正修复。

小结

  • Apache Kafka,也称社区版 Kafka。

    • 优势在于迭代速度快,社区响应度高,使用它可以让你有更高的把控度
    • 缺陷在于仅提供基础核心组件,缺失一些高级的特性。
  • Confluent Kafka,Confluent 公司提供的 Kafka。

    • 优势在于集成了很多高级特性且由 Kafka 原班人马打造,质量上有保证
    • 缺陷在于相关文档资料不全,普及率较低,没有太多可供参考的范例。
  • CDH/HDP Kafka,大数据云公司提供的 Kafka,内嵌 Apache Kafka。
    • 优势在于操作简单,节省运维成本
    • 缺陷在于把控度低,演进速度较慢。

kafka 版本号

Kafka 版本命名

image-20220329191711226

其中前半部分为 Scala语言版本,后才为kafka版本,如上图所示

他们均符合x.y.z 命名规范

Kafka 版本演进

Kafka 目前总共演进了 7 个大版本,分别是 0.7.x、0.8.x、0.9.x、0.10.x、0.11.x、1.x 、 2.x,3.x 其中的小版本和 Patch 版本很

多。

本文书写 时最新版本为 3.10(2022-03-29)

0.7.x版本

很老的Kafka版本,它只有基本的消息队列功能,连消息副本机制都没有,不建议使用。

0.8.x版本

两个重要特性,

一个是Kafka 0.8.0增加了副本机制,

另一个是Kafka 0.8.2.0引入了新版本Producer API。

新旧版本Producer API如下:

1
2
3
4
5
//旧版本Producer
kafka.javaapi.producer.Producer<K,V>

//新版本Producer
org.apache.kafka.clients.producer.KafkaProducer<K,V>

与旧版本相比,新版本Producer
API有点不同,一是连接Kafka方式上,旧版本的生产者及消费者API连接的是Zookeeper,而新版本则连接的是Broker;二是新版Producer采用异步方式发送消息,比之前同步发送消息的性能有所提升。但此时的新版Producer
API尚不稳定,不建议生产使用。

0.9.x版本

Kafka 0.9 是一个重大的版本迭代,增加了非常多的新特性,主要体现在三个方面:

  • 安全方面:在0.9.0之前,Kafka安全方面的考虑几乎为0。Kafka 0.9.0 在安全认证、授权管理、数据加密等方面都得到了支持,包括支持Kerberos等。
  • 新版本Consumer API:Kafka 0.9.0 重写并提供了新版消费端API,使用方式也是从连接Zookeeper切到了连接Broker,但是此时新版Consumer
    API也不太稳定、存在不少Bug,生产使用可能会比较痛苦;而0.9.0版本的Producer API已经比较稳定了,生产使用问题不大。
  • Kafka Connect:Kafka 0.9.0 引入了新的组件 Kafka Connect ,用于实现Kafka与其他外部系统之间的数据抽取。

0.10.x版本

Kafka 0.10 是一个重要的大版本,因为Kafka 0.10.0.0 引入了 Kafka Streams,使得Kafka不再仅是一个消息引擎,而是往一个分布式流处理平台方向发展。0.10 大版本包含两个小版本:0.10.1 和
0.10.2,它们的主要功能变更都是在 Kafka Streams 组件上。

值得一提的是,自 0.10.2.2 版本起,新版本 Consumer API 已经比较稳定了,而且 Producer API 的性能也得到了提升,因此对于使用 0.10.x 大版本的用户,建议使用或升级到 Kafka 0.10.2.2
版本。

0.11.x版本

Kafka 0.11 是一个里程碑式的大版本,主要有两个大的变更,一是Kafka从这个版本开始支持 Exactly-Once 语义
即精准一次语义,主要是实现了Producer端的消息幂等性,以及事务特性,这对于Kafka流式处理具有非常大的意义。

另一个重大变更是Kafka消息格式的重构,Kafka 0.11主要为了实现Producer幂等性与事务特性,重构了投递消息的数据结构。这一点非常值得关注,因为Kafka
0.11之后的消息格式发生了变化,所以我们要特别注意Kafka不同版本间消息格式不兼容的问题。

1.x版本

Kafka 1.x 更多的是Kafka Streams方面的改进,以及Kafka Connect的改进与功能完善等。但仍有两个重要特性,一是Kafka 1.0.0实现了磁盘的故障转移
,当Broker的某一块磁盘损坏时数据会自动转移到其他正常的磁盘上,Broker还会正常工作,这在之前版本中则会直接导致Broker宕机,因此Kafka的可用性与可靠性得到了提升;

二是Kafka 1.1.0开始支持副本跨路径迁移
,分区副本可以在同一Broker不同磁盘目录间进行移动,这对于磁盘的负载均衡非常有意义。

2.x版本

Kafka 2.x 更多的也是Kafka Streams、Connect方面的性能提升与功能完善,以及安全方面的增强等。一个使用特性,Kafka
2.1.0开始支持ZStandard的压缩方式,提升了消息的压缩比,显著减少了磁盘空间与网络io消耗。

3.x版本

  • 不再支持 Java 8 和 Scala 2.12
  • Kafka Raft 支持元数据主题的快照以及自我管理的仲裁中的其他改进
  • 为默认启用的 Kafka 生产者提供更强的交付保证
  • 弃用消息格式 v0 和 v1
  • OffsetFetch 和 FindCoordinator 请求的优化
  • 更灵活的 Mirror Maker 2 配置和 Mirror Maker 1 的弃用
  • 能够在 Kafka Connect 中的一次调用中重新启动连接器的任务
  • 现在默认启用连接器日志上下文和连接器客户端覆盖
  • Kafka Streams 中时间戳同步的增强语义
  • 改进了 Stream 的 TaskId 的公共 API
  • Kafka 中的默认 serde 变为 null

Kafka版本建议

  1. 遵循一个基本原则,Kafka客户端版本和服务端版本应该保持一致,否则可能会遇到一些问题。
  2. 根据是否用到了Kafka的一些新特性来选择,假如要用到Kafka生产端的消息幂等性,那么建议选择Kafka 0.11 或之后的版本。
  3. 选择一个自己熟悉且稳定的版本,如果说没有比较熟悉的版本,建议选择一个较新且稳定、使用比较广泛的版本。

referer

Apache Kafka 版本演进及特性介绍

RELEASE_NOTES

calendar

在一般情况下iPhone的日历是无法显示中国节假日等信息,就非常容易的忘掉一些重要的节日(我对于节日就是后知后觉的),那么有什么办法可以在iPhone原有的基础上加上这属于咱们中国节假日呢。方法当然是有滴,毕竟只要思想不滑坡办法总比问题多嘛。

在全球最大的同性交友网站github闲逛时,一个偶然的机会发现它——china-holiday-calender

基于订阅的方式,实现组件化的方式嵌入到iPhone原生的日历,cool~

  1. 节假日信息来自中国政府网,一手信息、权威准确
  2. 包含最近3年的节假日信息,机器人自动维护,更新及时
  3. 日历标题包含放假、补班的天数信息
  4. 日历标题包含放假、补班等关键字,方便脚本开发。例如使用iPhone快捷指令应用编写工作日闹钟
  5. 每个补班日程自动设置上班提醒,默认时间为09:00~18:00、提前一个小时提醒
  6. 支持个性化定制补班日程的开始、结束时间和提醒时间(例如提前一天提醒)

订阅地址如下:

任选其一,主要推荐国内订阅地址(**支持定制补班日程**) ,其次是jsDelivr订阅地址

操作

  1. 打开日历app(返回首页,如下图所示),点击中间的 “日历”(如下图红色方框所示)

image-20220320072755412

  1. 在日历设置页(如下图所示),点击左下角添加日历 -> 添加订阅日历(如下图红色方框所示)

image-20220320072952006

  1. 将网址输入框中,如下图所示

image-20220320073246216

  1. 输入 https://www.shuyz.com/githubfiles/china-holiday-calender/master/holidayCal.ics 点击订阅即可

完成啦~

image-20220320073717862

image-20220320073853232

macbook pro 日历

Referer

china-holiday-calender: https://github.com/lanceliao/china-holiday-calender

监控

俗话说“无监控,不运维”, 一套监控预警系统尤为重要

日志监控系统

目前市面上日志监控系统主要是以Elastic家族实现的日志系统,比较盛名的有

  • ELK
  • EFK
  • KEFK

其中

E为Elastic Search,也就是咱们常说的es

L为logstash,数据处理管道

F为Filebaet,轻量型日志采集器:用于转发和汇总日志与文件

K为Kibana,可拓展的用户展示界面

主要思路基于ETL抽取(extract)、转换(transform)、加载(load)

ox1: ELK架构

最简

image-20220310134833384

优点

部署简单,轻量

缺点

Logstash同时兼顾了抽取(extract)、转换(transform)、加载(load)。较为损耗资源

改进

基于TCP推送至LogStash

image-20220310135258151

优点:较于最简版,大大的减少了服务器负载

缺点:基于SDK开发,有代码侵入。耦合性强

Ox2: EFK

image-20220310140216214

优点:代码无侵入、占用资源少

缺点:日志数据共享困难;FileBaet只能配置一个output源

0x3: KEFK

Kafka、ElasticSearch、FileBaet、Kibana

image-20220310140739079

优点:基于消息队列实现共享,稳定性能性高

缺点:组件多,运维成本大

GPL

Granfan

Prometheus

loki

GTL

Granfan

thanos:Thanos 是一组组件,可以组成一个具有无限存储容量的高度可用的指标系统,可以在现有的 Prometheus 部署之上无缝添加。

loki

GTI与Zabbix

influxdb+telegraf+Grafana

telegraf+influxdb+Grafana Zabbix
部署及使用简单 部署使用相对复杂
内置监控项丰富 内置监控项支持相对少一些,但是社区提供了丰富的监控采集方案
不支持跨机房部署 支持跨机房部署
审计功能相对较弱 审计功能成熟完善
出图能力灵活强大 出图功能相对弱一些,图形化定制方面操作复杂
告警功能简单 告警强大,支持告警依赖,告警升级
支持通过webhook方式触发命令 支持服务器端/客户端的命令自动触发,支持命令推送
权限管理相对简单 支持细粒度的权限定制,权限体系成熟完善
数据采集方式相对单一,仅支持自动上报,但支持较为丰富的数据源 支持多种数据采集方式/协议,数据源相对单一,v3.4.7版本开始支持ES存储历史数据

Zabbix

相关地址

elasticsearch: https://www.elastic.co/cn/elasticsearch/

kibana: https://www.elastic.co/cn/kibana/

logstash: https://www.elastic.co/cn/logstash/

Kafka: https://kafka.apache.org/

Granfan:https://grafana.com/

Prometheus:https://prometheus.io/

loki: https://grafana.com/oss/loki/

thanos:https://thanos.io/

Zabbix:https://www.zabbix.com/

telegraf:https://docs.influxdata.com/telegraf/

Other

ETL、ELT区别

无监控,不运维

云原生

监控方案汇总

二叉树

Brush the topic-BinaryTree

大家好,这是Brush the topic的第一章节,BinaryTree。首先我说一下为什么把这个放在刷题的第一节呢?

原因如下:

  • 培养、训练自己的计算机的思维。
  • 锻炼模版化,抽象化思维

下面让我们一起去完成一个壮举,那就是完全解决二叉树的遍历问题,以及相关问题。are you ok?

二叉树的遍历

由于对于二叉树的遍历顺序不同,构造出三种不同的遍历方式

  • 前序遍历-根左右
  • 中序遍历-左根右
  • 后序遍历-左右根

递归代码模版如下

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right

# 前序遍历
def preOreder(self, root):
if root:
self.traverse_path.append(root.val)
preOreder(self.left)
preOreder(self.right)


# 中序遍历
def inOreder(self, root):
if root:
preOreder(self.left)
self.traverse_path.append(root.val)
preOreder(self.right)


# 后序遍历
def postOreder(self, root):
if root:
preOreder(self.left)
preOreder(self.right)
self.traverse_path.append(root.val)

Go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 前序遍历
func preOreder(root *TreeNode) {
result := []int{}
if root == nil {return}
result = append(result, root.value)
preOreder(root.Left)
preOreder(root.Right)
}

// 中序遍历
func inOreder(root *TreeNode) {
result := []int{}
if root == nil {return}
preOreder(root.Left)
result = append(result, root.value)
preOreder(root.Right)
}


// 后序遍历
func postOreder(root *TreeNode) {
result := []int{}
if root == nil {return}
postOreder(root.Left)
postOreder(root.Right)
result = append(result, root.value)
}

practice

基于此我们可以拿下以下题目,完全二叉树递归模版解题

144. 二叉树的前序遍历-Python

Recursive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right

# Recursive-1
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
result = []
self.helper(root, result)
return result

def helper(self, root, result):
if root is None: return
result.append(root.val)
self.helper(root.left, result)
self.helper(root.right, result)


# Recursive-2 Another way Anonymous function
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
def helper(root: TreeNode):
if not root: return
res.append(root.val)
helper(root.left)
helper(root.right)

res = list()
helper(root)
return res


# Recursive-3 more clean code
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if not root: return []
res = []
res.append(root.val)
res += self.preorderTraversal(root.left)
res += self.preorderTraversal(root.right)
return res

Iterative

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right

# Solution-1
class Solution1:
def preorderTraversal(self, root: TreeNode) -> List[int]:
stack, result = [], []
while stack or root:
while root:
# 前序遍历-根左右,先拿根
result.append(root.val)
# 压栈
stack.append(root)
# 拿完根之后拿左儿子
root = root.left
# 左儿子拿出来,拿右儿子
node = stack.pop()
root = node.right
# # 完成
return result


# Solution-2 简化Solution-1
class Solution2:
def preorderTraversal(self, root: TreeNode) -> List[int]:
stack, result = [], []
while stack or root:
if root:
result.append(root.val)
stack.append(root)
root = root.left
else:
node = stack.pop()
root = node.right
return result


# Solution-3
class Solution3:
def preorderTraversal(self, root: TreeNode) -> List[int]:
stack, result = [root], []
while stack:
# 拿出根
node = stack.pop()
if node:
# 前序遍历拿出,先拿根的值
result.append(node.val)
# 模仿栈,先入后出。后拿右孩子
stack.append(node.right)
stack.append(node.left)
return result

94. 二叉树的中序遍历-Python

Recursive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right

# Recursive-1
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
result = []
self.helper(root, result)
return result

def helper(self, root, result):
if root is None: return
self.helper(root.left, result)
result.append(root.val)
self.helper(root.right, result)


# Recursive-2 Another way Anonymous function
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
def helper(root: TreeNode):
if not root: return
helper(root.left)
res.append(root.val)
helper(root.right)

res = list()
helper(root)
return res


# Recursive-3 more clean code
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if not root: return []
res = []
res += self.preorderTraversal(root.left)
res.append(root.val)
res += self.preorderTraversal(root.right)
return res

Iterative

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right

# Solution - 1
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if not root: return
stack, result = [], []
while stack or root:
while root:
stack.append(root)
root = root.left
node = stack.pop()
result.append(node.val)
root = node.right
return result


# Solution - 2 简化Solution-1
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
stack, result = [], []
while stack or root:
if root:
stack.append(root)
root = root.left
else:
node = stack.pop()
result.append(node.val)
root = node.right
return result


# Solution - 3
class Solution2:
def inorderTraversal(self, root: TreeNode) -> List[int]:
stack, result = [], []
while stack or root:
if root:
stack.append(root)
root = root.left
else:
node = stack.pop()
result.append(node.val)
root = node.right
return result

145. 二叉树的后序遍历

Recursive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right

# Recursive-1
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
result = []
self.helper(root, result)
return result

def helper(self, root, result):
if root is None: return
self.helper(root.left, result)
self.helper(root.right, result)
result.append(root.val)


# Recursive-2 Another way Anonymous function
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
def helper(root: TreeNode):
if not root: return
helper(root.left)
helper(root.right)
res.append(root.val)

res = list()
helper(root)
return res


# Recursive-3 more clean code
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
if not root: return []
res = []
res += self.preorderTraversal(root.left)
res += self.preorderTraversal(root.right)
res.append(root.val)
return res

Iterative

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# Solution - 1
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
stack, result = [], []
while root or stack:
while root:
result.append(root.val)
stack.append(root)
root = root.right
node = stack.pop()
root = node.left
return result[::-1]


# Solution - 2
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
stack, result = [], []
while stack or root:
if root:
result.append(root.val)
stack.append(root)
root = root.right
else:
node = stack.pop()
root = node.left
return result[::-1]


# Solution - 3
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
if not root: return
stack, result = [root], []
while stack:
node = stack.pop()
if node:
result.append(node.val)
stack.append(node.left)
stack.append(node.right)
return result[::-1]

二叉树迭代遍历模版-Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 前序遍历
# Solution-1
class Solution1:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if not root: return
stack, result = [], []
while root or stack:
while root:
result.append(root.val)
stack.append(root)
root = root.left
tmp = stack.pop()
root = tmp.right
return result


# 中序遍历
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if not root: return
stack, result = [], []
while stack or root:
while root:
stack.append(root)
root = root.left
node = stack.pop()
result.append(node.val)
root = node.right
return result

由递归到迭代,基本的思想就是由递归中由系统维护的栈,转为手动维护。

python

什么是GIL

GIL(Global Interpreter Lock,即全局解释器锁),是最流行的 Python 解释器 CPython 中的一个技术术语。它的意思是全局解释器锁,本质上是类似操作系统的 Mutex
,它可以帮助CPython解决其在内存管理中存在的线程不安全问题。

每一个 Python 线程,在 CPython 解释器中执行时,都会先锁住自己的线程,阻止别的线程执行。

简而言之就是任意时刻,Python 只有一个线程在同时运行

为什么需要GIL

在CPython中,全局解释器锁(GIL)是一个互斥体,用于保护对Python对象的访问,防止多个线程同时执行Python bytecodes。GIL的存在可防止竞争确保线程安全

所以说,CPython 引进 GIL 主要原因:

  • 设计者为了规避类似于内存管理这样的复杂的竞争风险问题(race condition);
  • 因为 CPython 大量使用 C 语言库,但大部分 C 语言库都不是原生线程安全的(线程安全会降低性能和增加复杂度)。

为什么 CPython 需要 GIL 呢?这其实和 CPython 的实现有关。Python 的内存管理机制, CPython 使用引用计数来管理内存,所有 Python
脚本中创建的实例,都会有一个引用计数,来记录有多少个指针指向它。当引用计数只有 0 时,则会自动释放内存。

GIL工作原理

  1. 某个线程拿到GIL
  2. 该线程执行代码,直到达到了check_interval*
  3. 解释器让当前线程释放GIL
  4. 所有的线程开始竞争GIL
  5. 竞争到GIL锁的线程又从第1步开始执行

Python2中,check_interavl是当前线程遇见IO操作或者ticks计数达到100

在Python3中是执行时间达到阈值(默认为15毫秒)

GIL存在的利弊

GIL 的设计,主要是为了方便 CPython 解释器层面的编写者

  • 在单线程任务中更快;
  • 在多线程任务中,对于I/O密集型程序运行更快;
  • 在多线程任务中,对于用C语言包来实现CPU密集型任务的程序运行更快;
  • 在写C扩展的时候更加容易,因为除非你在扩展中允许,否则Python解释器不会切换线程;
  • 在打包C库时更加容易。我们不用担心线程安全性。,因为如果该库不是线程安全的,则只需在调用GIL时将其锁定即可。

这种 GIL 锁的设计对于只使用单线程运行的code来说其实没有什么影响。但是对于计算密集型的程序(CPU-bound)和基于多线程的程序来说,Python 的 GIL 设计很有可能会造成性能瓶颈。

例子:

因为有GIL的存在,由CPython做解释器(虚拟机)的多线程Python程序只能利用多核处理器的一个核来运行。

例如,我们将一个8线程的JAVA程序运行在4核的处理器上,那么每个核会运行1个线程,然后利用时间片轮转,轮流运行每一个线程。

但是,我们将一个8线程的Python程序(由CPython作解释器)运行在一个4核处理器上,那么总共只会有1个核在工作,8个线程都要在这一个核上面时间片轮转。

Python 的线程安全

有了 GIL,并不意味着我们 Python 编程者就不用去考虑线程安全了。即使我们知道,GIL 仅允许一个 Python 线程执行,但前面我也讲到了,Python 还有 check interval 这样的抢占机制。

所以有了 GIL 并不意味着你的Python程序就可以高枕无忧了,我们仍然需要去注意线程安全。

GIL 的设计,主要是为了方便 CPython 解释器层面的编写者,而不是 Python 应用层面的程序员。作为 Python 的使用者,我们还是需要 lock 等锁,来确保线程安全。

Python多线程

CPython 会做一些小把戏,轮流执行 Python 线程。这样一来,用户看到的就是“伪并行”——Python 线程在交错执行,来模拟真正并行的线程。

所以说Python的多线程是伪多线程

GIL 到底锁的是什么?

GIL 的全称是 Global Interpreter Lock, 全局解释器锁。它锁的是解释器而不是你的 Python 代码。它防止多线程同时执行 Python 的字节码(bytecodes),防止多线程同时访问 Python 的对象。

在 Python 官方文档Releasing the GIL from extension code中,有这样一段话:

Here is how these functions work: the global interpreter lock is used to protect the pointer to the current thread
state.
When releasing the lock and saving the thread state, the current thread state pointer must be retrieved before
the lock is released (since another thread could immediately acquire the lock and store its own thread state in the
global variable). Conversely, when acquiring the lock and restoring the thread state, the lock must be acquired before
storing the thread state pointer.

其中加黑的这一句话是说:GIL 锁用来保护指向当前进程状态的指针

再看文档Thread State and the Global Interpreter Lock中提到的这样一句话:

Without the lock, even the simplest operations could cause problems in a multi-threaded program: for example, when two
threads simultaneously increment the reference count of the same object, the reference count could end up being
incremented only once instead of twice.

当两个线程同时提高同一个对象的引用计数时,(如果没有 GIL 锁)那么引用计数只会被提高了 1 次而不是 2 次。

大家注意这两段应用中的指针引用计数。其中指针是 C 语言的概念,Python 没有指针;引用计数是 Python 底层的概念。你平时写的 Python 代码,引用计数是在你调用变量的时候自动增加的,不需要你去手动加 1.

所以 GIL 锁住的东西,都是不需要你的代码直接交互的东西。

Python 的解释器通过切换线程来模拟多线程并发的情况,如上面举的例子,虽然同一个时间只有一个线程在活动,但仍然可以导致并发冲突。

GIL 对 Python 多线程开发的影响

在提到开发性能瓶颈的时候,我们经常把对资源的限制分为两类,

  • 一类是计算密集型(CPU-bound)
  • 一类是 I/O 密集型(I/O-bound)。

计算密集型的程序是指的是把 CPU 资源耗尽的程序,也就是说想要提高性能速度,就需要提供更多更强的 CPU,比如矩阵运算,图片处理这类程序。

I/O 密集型的程序只的是那些花费大量时间在等待 I/O 运行结束的程序,比如从用户指定的文件中读取数据,从数据库或者从网络中读取数据,I/O 密集型的程序对 CPU 的资源需求不是很高。

如何加速?

一般来说 IO 密集型用多线程、协程来加速,CPU 密集型用多进程来加速。

结合来看IO密集型使用协程 + 多进程 不失为“最佳”方案:

aiomultiprocess:https://pypi.org/project/aiomultiprocess/

如何绕过GIL?

你并不需要过多考虑 GIL。因为如果多线程计算成为性能瓶颈,往往已经有 Python 库来解决这个问题了。

绕过 GIL 的大致思路有这么两种:

  • 绕过 CPython,使用 JPython(Java 实现的 Python 解释器)等别的实现;
  • 把关键性能代码,放到别的语言(一般是 C++)中实现。

referer

也许你对 Python GIL 锁的理解是 错的。—— kingname

mitmproxy

MitmProxy是一组优秀的网络代理拦截工具,可为 HTTP/1、HTTP/2 和 WebSockets 提供交互式、支持 SSL/TLS 的拦截代理。它提供 拦截 HTTP 和 HTTPS 请求和响应并动态修改它们、保存完整的 HTTP
会话,以供重放攻击和分析、反向代理模式将流量转发到指定服务器、macOS 和 Linux 上的透明代理、对 HTTP 流量进行脚本化更改等功能。

安装

Python3安装

直接使用pip即可,使用如下命令进行安装

1
2
3
4
# 升级pip
python3 -m pip install -U pip
# 安装
python3 -m pip install -U mitmproxy

由于windows话默认是没有python3的(如果你没做兼容也就是将python复制一份副本并重命名为python3),使用python即可

Mac

Mac 下推荐使用 homebrew安装,尤其是m1的,注意啦!!!

别问我怎么知道的,说多了都是泪

1
brew install mitmproxy

mitmproxy组成

mitmproxy 由mitmproxy、mitmdump、mitmweb组成

mitmproxy

mitmproxy是用于调试、测试、隐私测量和渗透测试的瑞士军刀。它可用于拦截、检查、修改和重放 Web 流量,例如 HTTP/1、HTTP/2、WebSockets 或任何其他受 SSL/TLS 保护的协议。您可以美化和解码从 HTML
到 Protobuf 的各种消息类型,即时截取特定消息,在它们到达目的地之前对其进行修改,并稍后将它们重播到客户端或服务器。

mitmdump

强大的插件功能与python api集成,提供了对mitmproxy的完全控制,可以自动修改消息、重定向流量、可视化消息或实现自定义命令。基于mitmdump可实现拓展,完全自由定制。实现基于此的流量转发代理中间件。

mitmproxy

在图形界面中使用 mitmproxy 的主要功能 mitmweb。mitmweb 为您提供任何其他应用程序或设备的类似体验,以及请求拦截和重放等附加功能。

证书安装与配置

对于任何中间人抓包工具来说,若需要完整的捕获HTTPS请求,必须需要配置HTTPS证书。由于mitmproxy的证书在安装时便已经自带了,所以不必多次安装。只需配置证书即可。

手机上需要下载直接进入 http://mitm.it/ 即可(需要先连接上mitmproxy的代理)

mitmproxy界面

mitmproxy有许多的功能界面主要有以下几个

index

打开代理时的index界面,此界面为中心界面,一进来就是这个,简要的介绍了包

image-20220307012744921

包详情界面

使用jk (或者上下方向键)实现包之间的移动,enter(回车)进入包的详情界面,可以使用tab进行切换。如下图所示

当然是要大写的 P 也可以进入这里

image-20220307012909484

帮助界面

每个CLI基本上都有help,而mitmproxy自然也有, 如下

?: 进入

image-20220307013538512

还有过滤帮助,可以使用TAB 实现切换。如下所示

image-20220307013639677

当使用f 快速进入过滤命令中 再加上过滤语法即可实现过滤,并在其中输入 ~u baidu如下所示

image-20220307013812180

实现对url为 baidu 的实现过滤展示

Key Bindings界面

shift + k 也就是大写的K进入此界面

按键绑定界面,这里展示了所有的按键在mitmproxy中的功能,当然也可以修改其绑定,其界面如下所示。

image-20220307013411247

Events界面

此界面可以查看捕获流量的所有事件,使用E 进入,如下所示

image-20220307014113646

Command Reference界面

所有的输入的命令都可以在这里找到,当然需要一些英文的识别能力, 如下所示

shift + c :进入Command Reference界面

: 进入命令输入状态

image-20220307014328724

Options界面

参数选项界面,可以认为这是mitmproxy设置界面, 如下所示

大写的o 进入

image-20220307014527641

一些常用的按键

移动

快捷键 command 说明
q console.view.pop 返回:界面间的返回
g console.nav.start 跳到第一行
G console.nav.end 跳到最后一行
h console.nav.left 跳到左面
j console.nav.down 跳到下一行
k console.nav.up 跳到上一行
l console.nav.right 跳到右面
space console.nav.pagedown 跳到本页最后一行
ctrl b console.nav.pageup 跳到本页第一行
ctrl f console.nav.pagedown 跳到本页最后一行
tab console.nav.next

可参考 help 界面

e: 快速生成请求

mitmdump具体实现

一个基于mitmdump 实现的流式流量转发处理平台: mitmdumpMan

抓包tips

  • 使用类似与SwitchyOmega实现端口塞选效果更佳

nginx

开源官方网站

下载界面

官方文档

openresty

商业nginx

商业openresty

什么是Nginx

Nginx (engine x) 是一款轻量级的Web 服务器 、反向代理服务器及电子邮件(IMAP/POP3)代理服务器。

反向代理:

反向代理(Reverse
Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

正向代理:

是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。

正向代理和反向代理区别?

正向代理是在客户端的。比如需要访问某些国外网站,我们可能需要购买vpn。并且vpn是在我们的用户浏览器端设置的(并不是在远端的服务器设置)。浏览器先访问vpn地址,vpn地址转发请求,并最后将请求结果原路返回来。

反向代理是作用在服务器端的,是一个虚拟ip(VIP)。对于用户的一个请求,会转发到多个后端处理器中的一台来处理该具体请求。

为什么是Nginx?

互联网数据量快速增长,高并发

摩尔定律:性能提升

比Apache(一个连接对应一个进程)更快

Nginx优点

  • 高并发、高性能
  • 可拓展性好(插件)
  • 高可靠
  • 热部署
  • BSD认证

Nginx 主要使用场景

image-20220302040819009

image-20220302040819009

Nginx组成

  • Nginx,二进制可执行文件
  • Nginx config 配置文件: 控制nginx行为
  • access log : 记录每一条请求
  • error log:错误日志

Nginx 版本

Mainline version:主线版本,最新功能等

Stable version:稳定版,

Legacy versions:历史版本

tenginx

nginx各版本更新日志

  • Bugfix: 修复bug
  • Change: 修改
  • Feature: 新特性

编译安装Nginx

安装Nginx 通常有两张方式,其一,那便是使用包管理工具如yumapt-gethomebrewwinget等直接安装,而另外一种便是编译安装。

个人推荐使用编译安装,其主要原因为可以将各组件一起编译生成,兼容其强大的生态。

1
2
3
4
5
6
7
8
9
10
11
# Download
curl -OC - https://nginx.org/download/nginx-1.20.2.tar.gz
# unzip
tar -xzf nginx-1.20.2.tar.gz && cd nginx-1.20.2
# 编译(默认参数)
# yum -y install gcc gcc-c++ make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel
./configure --prefix=/opt
# 编译
make
# 安装
make install
1
2
3
4
5
6
7
8
9
10
11
./configure --prefix=/usr/local/nginx 
--pid-path=/var/run/nginx/nginx.pid
--lock-path=/var/lock/nginx.lock
--error-log-path=/var/log/nginx/error.log
--http-log-path=/var/log/nginx/access.log
--with-http_gzip_static_module
--http-client-body-temp-path=/var/temp/nginx/client
--http-proxy-temp-path=/var/temp/nginx/proxy
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi
--http-scgi-temp-path=/var/temp/nginx/scgi

相关参数

1051646169511_.pic

nginx源码目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
drwxr-xr-x 6 1001 1001   4096 Mar  2 03:09 auto: 编译时所用
-rw-r--r-- 1 1001 1001 312251 Nov 16 22:44 CHANGES:版本变更
-rw-r--r-- 1 1001 1001 476577 Nov 16 22:44 CHANGES.ru:版本变更(俄语)
drwxr-xr-x 2 1001 1001 4096 Mar 2 03:09 conf nginx配置示例文件
-rwxr-xr-x 1 1001 1001 2590 Nov 16 22:44 configure:用来生产中间文件,执行编译前的必备动作
drwxr-xr-x 4 1001 1001 4096 Mar 2 03:09 contrib:vim工具,
drwxr-xr-x 2 1001 1001 4096 Mar 2 03:09 html: 500错误,index.html
-rw-r--r-- 1 1001 1001 1397 Nov 16 22:44 LICENSE
drwxr-xr-x 2 1001 1001 4096 Mar 2 03:09 man: nginx 帮助文件
-rw-r--r-- 1 1001 1001 49 Nov 16 22:44 README:
drwxr-xr-x 9 1001 1001 4096 Nov 16 22:44 src: nginx源代码

---
# tree . -d 1

├── auto
│   ├── cc:用于编译
│   ├── lib:外部目录
│   │   ├── geoip
│   │   ├── google-perftools
│   │   ├── libatomic
│   │   ├── libgd
│   │   ├── libxslt
│   │   ├── openssl
│   │   ├── pcre
│   │   ├── perl
│   │   └── zlib
│   ├── os: 操作系统判断
│   └── types
├── conf: 配置文件示意
├── contrib
│   ├── unicode2nginx
│   └── vim
│   ├── ftdetect
│   ├── ftplugin
│   ├── indent
│   └── syntax
├── html
├── man
└── src
├── core
├── event
│   └── modules
├── http
│   ├── modules
│   │   └── perl
│   └── v2
├── mail
├── misc
├── os
│   └── unix
└── stream

tips

将 contrib 中的文件夹copy 到vim目录下,以便于使用vim对配置文件等进行便捷的编辑,如下command

1
2
3
4
5
DIR="ftdetect ftplugin indent syntax"
for dir in $DIR; do
mkdir -p ~/.vim/${dir}
cp -r contrib/vim/${dir}/nginx.vim ~/.vim/${dir}/nginx.vim
done

Nginx 配置

语法

  • 配置文件由指令与指令块构成
  • 每条指令以; 结尾,指令与参数之间以空格符号分割
  • 指令块以{} 将多条指令组织在一起
  • include语句允许组合多个配置文件,提升可维护性
  • #: 实现注释
  • $:使用变量
  • 部分指令支持regexp, 如路径匹配等

参数

时间

  • ms: MilliSeconds
  • s: Seconds
  • m: minutes
  • h: hours
  • d: days
  • w: weeks
  • m: months
  • years

存储空间

bytes (default)

k/K: kilobytes

m/M: megabytes

g/G: gigabytes

配置指令块

image-20220302040819009

http: 表示所有的指令都是http解析的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
http {
include mine.types
# 上游服务
upstream thwp {
server 127.0.0.1:8080
}
# (一组)域名
server {
listen 80
location ~*.(gif|jpg|jpeg)$ {

}
}
}

配置实践

配置upstream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http {
// proxycluster: 服务组名字
upstream proxycluster {
server 10.21.200.101:8081;
server 10.21.200.121:8081;
server 10.21.200.111:8081;
server 10.21.200.191:8081;
}

server {
listen 8081;
location / {
proxy_pass http://proxycluster;
auth_basic "Restricted";
}
}

weight=number: 服务器的权重值,默认是1;

max_conns=number: 设置允许的最大活动连接数。默认是0,表示不限制。(设置keepalive时,如果有多个worker共享内存,则连接数可能会超过设置的值)

max_fails=number: 设置在fail_timeout参数设置的时间段内允许的失败最大尝试次数,默认是1,设置0表示失败不会尝试。

fail_timeout=time: fail_timeout的值包含两层意思:

  1. 在这个时间段内服务器通信失败次数决定服务状态是否为不可用;

  2. 服务器被视为不可用状态的时间段。默认是10秒。

backup: 标记服务器是备用服务器,只有在其他服务器都不可用的情况下,才会请求该服务器。(在哈希、IP哈希、随机三种负载均衡模式下不可用)

down: 标记服务器永久不可用状态。

正则表达式匹配路由

go

Book

官方wiki

经典书籍

《The Way To Go》- Go 语言百科全书

《Go 101》- Go 语言参考手册

《Go 语言学习笔记》- Go 源码剖析与实现原理探索

《Go 语言实战》- 实战系列经典之作,紧扣 Go 语言的精华

《Go 程序设计语言》- 人手一本的 Go 语言“圣经”

《Go语法树入门——开启自制编程语言和编译器之旅》

《golang编译器代码解析》

值得一读

《go语言精进之路》(全两册)—白明

《Go语言高级编程》

《go语言底层原理剖析》

《go语言并发之道》

在线图书

《go语言设计与实现》

《go语言原本》

《Golang 编译器》

《Go 语言高性能编程》

《learn-go-with-tests》

《go2》

《algorithm-pattern》

go语言地鼠文档

地鼠文档

仅个人见解

文档

官方文档:项目主线分支(master)上最新开发版本的文档:

Go语言规范 The Go Programming Language Specification - The Go Programming Language

Go mod参考文档 Go Modules Reference - The Go Programming Language

Go语言命令参考手册 Command Documentation - The Go Programming Language

Effective Go Effective Go - The Go Programming Language

Go语言标准库参考手册

go 常见问答 Frequently Asked Questions (FAQ) - The Go Programming Language

相关博客

Go语言官方博客 The Go Blog - The Go Programming Language

Go 语言之父 Rob Pike 的个人博客 command center

Go 核心团队技术负责人 Russ Cox 的个人博客

Go 核心开发者 Josh Bleecher Snyder 的个人博客 Don’t Panic (commaok.xyz)

Go 核心团队前成员 Jaana Dogan 的个人博客

Go 鼓吹者 Dave Cheney 的个人博客

Go 语言培训机构 Ardan Labs 的博客

GoCN社区

Go 语言百科全书 The golang.design Initiative

Go 项目的代码 review 站点

技术演讲

Go 官方的技术演讲归档 talks - The Go Programming Language

GopherCon 技术大会

GopherCon Europe 技术大会 GopherCon Europe - YouTube

GopherConUK 技术大会 GopherCon UK - YouTube

GoLab 技术大会 GoLab conference - YouTube

Go Devroom@FOSDEM FOSDEM - YouTube

GopherChina技术大会

Go夜读

其他

代码安全指南

Go语言标准库文档中文版

go学习线路图 · Go语言中文文档 (topgoer.com)

gobyexample

Uber代码规范

Uber Go 语言编码规范中文版

awesome-go

Moby Project - a collaborative project for the container ecosystem to assemble container-based systems

设计模式

《Go设计模式》:

-Go Patterns · GitBook (tmrts.com)

-golang-design-pattern

设计模式Golang实现:设计模式 Golang实现-《研磨设计模式》

算法

leetcode cookbook

algorithms:

Github Top 100

go-collection

referer

Go语言第一课 — 白明

image-20220323002441627

Apache Kafka

官方网址:kafka.apache.org

Mirror of Apache Kafka github: https://github.com/apache/kafka

什么是Kafka

Apache Kafka 是一款开源的消息引擎系统, 也是一个分布式流处理平台(Distributed Streaming Platform)

  • 处理实时数据提供一个统一、高吞吐、低延迟的平台。
  • 它使用的是纯二进制的字节序列, 以时间复杂度为 O(1) 的方式提供消息持久化能力,即使对 TB 级以上数据也能保证常数时间复杂度的访问性能。
  • 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒 100K 条以上消息的传输。
  • 支持 Kafka Server 间的消息分区,及分布式消费,同时保证每个 Partition 内的消息顺序传输。
  • 同时支持离线数据处理和实时数据处理。
  • Scale out:支持在线水平扩展。

Kafka功能

削峰填谷解耦合

KafKa传输模型

Kfaka有两种传输模型,分别是基于一对一、多对多的思想。

一对一:一般也称之为消息队列模型,系统 A 发送的消息只能被系统 B 接收,其他任何系统都不能读取 A 发送的消息。

多对多:一般称之为发布订阅模型。与上面不同的是,它有一个主题(Topic)
的概念,该模型也有发送方和接收方,只不过提法不同。发送方也称为发布者(Publisher)接收方称为订阅者(Subscriber)。和点对点模型不同的是,这个模型可能存在多个发布者向相同的主题发送消息,而订阅者也可能存在多个,它们都能接收到
相同主题
的消息。

Kafka术语

message

主题(Topic):在 Kafka 中,发布订阅的对象是主题(Topic),你可以为每个业务、每个应用甚至是每类数据都创建专属的主题。

分区(Partitioning):将每个主题划分成多个分区(Partition),每个分区是一组有序的消息日志。生产者生产的每条消息只会被发送到一个分区中

消息(Record):Kafka 是消息引擎,这里的消息就是指 Kafka 处理的主要对象。

客户端

生产者(Producer)

向主题发布消息的客户端应用程序,生产者程序通常持续不断地向一个或多个主题发送消息。

消费者(Consumer)

订阅这些主题消息的客户端应用程序。和生产者类似,消费者也能够同时订阅多个主题的消息。

消费者组(Consumer Group)

多个消费者实例共同组成的一个组,同时消费多个分区以实现高吞吐。

消费者实例(Consumer Instance)

运行消费者应用的进程,也可以是一个线程。

服务端

Broker

Kafka 的服务器端由被称为 Broker 的服务进程构成,即一个 Kafka 集群由多个 Broker 组成,Broker 负责接收和处理客户端发送过来的请求,以及对消息进行持久化。虽然多个 Broker
进程能够运行在同一台机器上,但更常见的做法是将不同的 Broker 分散运行在不同的机器上,这样如果集群中某一台机器宕机,即使在它上面运行的所有 Broker 进程都挂掉了,其他机器上的 Broker 也依然能够对外提供服务。

Replication

把相同的数据拷贝到多台机器上,而这些相同的数据拷贝在 Kafka 中被称为副本(Replica)

副本的数量是可以配置的,这些副本保存着相同的数据,但却有不同的角色和作用。

Kafka 定义了两类副本:

  • 领导者副本(Leader Replica):对外提供服务,这里的对外指的是与客户端程序进行交互;
  • 追随者副本(Follower Replica):被动地追随领导者副本,不能与外界进行交互。

重平衡(Rebalance)

消费者组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。Rebalance 是 Kafka 消费者端实现高可用的重要手段。

消费者位移(Consumer Offset)

表征消费者消费进度,每个消费者都有自己的消费者位移。

为什么需要使用消息系统

解耦: 消息系统在处理过程中插入了一个隐含的,基于数据接口, 两边的处理过程都要实现这一接口.这允许独立拓展或修改两边的处理过程. 只要确保他们遵循同样的接口约束

而基于消息发布订阅的机制, 可以联动多个业务下流子系统,能够不侵入的情况下的情况下分布编排和开发,来保证数据一致性

冗余:

有些情况下,处理数据的过程中会失败.除非数据被持久化,否则将造成丢失.消息队列吧数据进行持久化直到已经完全被处理, 通过这一方式可规避数据丢失,许多消息队列所采用的”插入-获取-删除”
的范式,把一个消息从队列中删除之前,需要处理系统明确指出该消息已经被完全处理完毕, 从而确保你的数据被安全的保存直到使用完毕

拓展:

消息解耦了处理过程, 所以增大消息入队和处理的频率是简单的,只需要在对应的端加速处理即可. 无需修改代码,修改参数,扩展非常简单

灵活 & 峰值处理能力

在访问量剧增的情况下, 应用仍然需要继续发挥作用,但这样的突发流量并不常见; 如果对此特定时间为标准投入资源,无疑是巨大的浪费. 使用消息队列能使关键组件顶住突发的压力,而不是因为突发的超负荷的请求完全崩溃

可恢复性

系统的一部分组件失效时,不会影响到整个系统. 消息队列降低了进程间的耦合度, 即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在恢复后被处理

顺序

在大多使用厂家下,数据处理的顺序都很重要. 大部分消息队列本来就是排序的,并且能保证数据按照特定的顺序来处理. kafka保证一个Partition内的消息有序性

缓冲

在任何重要的系统中,都会有需要不同的处理时间因素, 消息队列通过一个缓冲层来帮助业务最高效的执行-写入队列的处理尽可能的快速. 缓冲有助于控制和优化数据流和系统的速度

异步通讯

很多时候并不需要立即处理消息,而消息队列提供了异步处理机制, 允许将消息放入到队列,但不立即处理它. 只需要到一定的时间点处理即可

referer

Kafka 核心技术与实战

Kafka【入门】就这一篇! - 知乎 (zhihu.com)

git

git安装

mac

1
brew install git git-lfs

Linux(centos)

1
yum install -y git git-lfs

git配置初始化

1
2
3
4
5
6
git config --global user.name "paynewu"
git config --global user.email "wuzhipeng1289690157@gmail.com"
git config --global credential.helper store
git config --global core.longpaths true
git config --global core.quotepath off
git lfs install --skip-repo

实践

初始化repo

1
2
# 创建空的git仓库或重新初始化一个现有的仓库
git init

添加文件到本地仓库暂存区

1
2
3
4
5
6
# 将文件添加到暂存区
git add demofile
# 将所有文件添加到暂存区
git add --all
# 保存所有改动的文件和删除的文件,不包括新的文件
git add -u

添加到本地仓库

1
2
3
4
5
# 此命令代表确认提交到本地仓库。
git commit -m 'v1'

# 将所有暂存区提交到本地仓库。
git commit -am 'v1'

可以使用 git status 查看状态

查看提交日志

1
2
3
4
5
6
7
8
# 查看日志
git log

# 一行展示
git log --pretty=oneline

# 参考日志(Reference logs),记录每一次操作
git relog

版本回退

1
2
3
4
5
6
7
8
9
10
11
12
# 回退到上一版
git reset --hard HEAD^

# 回退到上上个版本
git reset --hard HEAD^^

# 如果回退的版本过多则不用加那么多的 ^ 号
# 比如回退到上 10 版本,则可以用
git reset --hard HEAD~10

# 回退到指定hash的分支
git reset --hard hash

撤销修改

总结:

  • 场景 1:当改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令 git checkout — file。

  • 场景 2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令 git reset HEAD file,就回到了场景 1,第二步按场景 1 操作。

  • 场景 3:已经提交了不合适的修改到版本库时,想要撤销本次提交,进行版本回退,不过前提是没有推送到远程库。 8. 删除文件 假如现在你新建了一个 hello.txt 文件,你已经 add 并 commit 到了本地分支之中。
    现在你想删除,如果直接执行

分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看本地分支
git branch

# 新建本地分支
git branch dev

# 切换到 dev 分支
git checkout dev

# 创建并切换到该分支
git checkout -b dev

# 合并分支
git merge

push

1
git push -u origin branch-name

tag

1
2
3
4
5
6
7
8
9
10
11
# 增加一个标签
git tag -a "v1.0.0" -m "Version 1.0.0"

# 删除一个标签
git tag -d v1.0.0

# 删除远程标签
git push --delete origin v1.0.0

# 推送所有标签
git push origin --tags

删除未监视文件

1
2
3
4
5
6
7
8
9
10
11
12
git clean -f

# 连 untracked 的目录也一起删掉
git clean -fd

# 连 gitignore 的untrack 文件/目录也一起删掉 (慎用,一般这个是用来删掉编译出来的 .o之类的文件用的)
git clean -xfd

# 在用上述 git clean 前,墙裂建议加上 -n 参数来先看看会删掉哪些文件,防止重要文件被误删
git clean -nxfd
git clean -nf
git clean -nfd

子模块

1
2
3
4
5
git clone <repository> --recursive 递归的方式克隆整个项目
git submodule add <repository> <path> 添加子模块
git submodule init 初始化子模块
git submodule update 更新子模块
git submodule foreach git pull 拉取所有子模块

VimIDE-spacevim

SpaceVim 是一个社区驱动的模块化的 Vim IDE,以模块的方式组织管理插件以及相关配置, 为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全, 语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱即用的 Vim IDE。

相关链接如下:

官方网站

中文官方网站

github

gitee

入门指南

安装

前置条件: 需要安装完成vimneovim

1
2
3
4
5
# 前置安装
## centos
python3 -m pip install -U pip && python3 -m pip install pynvim && pip3 install neovim && yum -y install neovim
## Mac
python3 -m pip install -U pip && python3 -m pip install pynvim && pip3 install neovim && brew install neovim

Windows

Windows 下最快捷的安装方法是下载安装脚本 install.cmd 并运行。

Linux 或 macOS

1
2
3
4
5
6
7
8
9
10
11
12
13
# 配置
cat >> /etc/profile << EOF
export EDITOR=nvim
alias vim="nvim"
EOF
source /etc/profile/

# 创建配置文件夹
mkdir -p $HOME/.config
# 创建文件夹软连接
ln -s $HOME/.vim $HOME/.config/nvim
# 创建配置文件软连接
ln -s $HOME/.vimrc $HOME/.config/nvim/init.vim
1
2
3
4
# 安装
curl -sLf https://spacevim.org/cn/install.sh | bash
# 如果需要获取安装脚本的帮助信息,可以执行如下命令,包括定制安装、更新和卸载等。
curl -sLf https://spacevim.org/cn/install.sh | bash -s -- -h

安装结束后,初次打开 vim 或者 neovim 时,SpaceVim 会自动下载并安装插件。

SpaceVim离线安装 (github.com)

环境搭建请参考:

在线教程

更多sao操作情参考官方文档,瑞思拜~

Go

什么是方法

​ 方法一般是面向对象编程(OOP)的一个特性,在cpp中方法对应一个类对象的成员函数,是关联到具体对象上的虚表中的。但是Go语言的方法却是关联到类型的,这样可以在编译阶段完成方法的静态绑定。

​ 一个面向对象的程序会用方法来表达其属性对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这些事情。面向对象编程(OOP)
进入主流开发领域一般认为是从cpp开始的,cpp就是在兼容C语言的基础之上支持了class等面向对象的特性。然后Java编程则号称是纯粹的面向对象语言,因为Java中函数是不能独立存在的,每个函数都必然是属于某个类的。

面向对象编程更多的只是一种思想,很多号称支持面向对象编程的语言只是将经常用到的特性内置到语言中了而已。

方法的声明

Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver),其中方法接收器(receiver)参数、函数 / 方法参数,以及返回值变量对应的作用域范围,都是函数 /
方法体对应的显式代码块。

1
2
3
func (接收者变量 接收者类型(值类型或指针类型)) 方法名(参数列表) (返回参数) {
方法体
}
  • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而非selfthis之类的命名。例如,Person类型的接收者变量应该命名为 pConnector类型的接收者变量应该命名为c
    等。
  • 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
  • 方法名、参数列表、返回参数:具体格式与函数定义相同。

一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import "fmt"

type people struct {
name string
age uint16
height float32
weight float32
hometown string
currentResidence string
lifeMotto string
Hobby map[string]string
Job string
}

// 构造函数
func newPeople(name string, age uint16, height float32, weight float32, hometown string, currentResidence string, lifeMotto string, hobby map[string]string, job string) *people {
return &people{
name: name,
age: age,
height: height,
weight: weight,
hometown: hometown,
currentResidence: currentResidence,
lifeMotto: lifeMotto,
Hobby: hobby,
Job: job,
}
}

func main() {
people := newPeople(
`Payne`,
23,
1.767,
75,
`HuNan`,
`ShangHai`,
`stay hungry,stay foolish`,
map[string]string{
`first`: `reading`,
},
`data engineer`,
)
fmt.Println(people)
}

接收者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//方法(值类型接受者)
// 当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。
// 在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
func (p people) introduce() {
fmt.Println(p)
}

// 方法(指针类型接受者)
// 指针类型的接收者由一个结构体的指针组成,由于指针的特性,
// 调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。
// 这种方式就十分接近于其他语言中面向对象中的this或者self。
func (p *people) introduceMe() {
fmt.Println(p)
}

什么时候应该使用指针类型接收者

  1. 需要修改接收者中的值
  2. 接收者是拷贝代价比较大的大对象
  3. 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

Tips

构造函数优化

在上面的例子中发现过多的参数,这个写法乍看之下是没啥问题的,但是一旦 people 结构内部的字段发生了变化,增加或者减少了,那么这个初始化函数 newPeople 就怎么看都觉的别扭了。

​ 如果参数继续增加呢?那么所有调用了这个 newPeople 方法的地方也都需要进行修改,且按照代码整洁的逻辑,参数多于 5 个,这个函数就很难使用了。而且,如果这 5 个参数都是可有可无的参数,就是有的参数可以不填写,

有默认值,比如 age 这个字段,即使我们不填写,在后续的业务逻辑中可能也没有很多影响,那么我在实际调用 newPeople 的时候,age 这个字段还需要传递 0 值, 以及hometown为空值,那么构造方法newPeople如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
people := newPeople(
`Payne`,
0,
1.767,
75,
``,
`ShangHai`,
`stay hungry,stay foolish`,
map[string]string{
`first`: `reading`,
},
`data engineer`,
)

乍看这行代码,你可能会以为我创建了一个 people,它的年龄为 0以及hometown为空值,但是实际上我们是希望表达这里使用了一个“缺省值”,这种代码的语义逻辑就不对了。那改怎么如何呢?

请使用options写法

options 为可选参数,以上面的代码为例,我们只需name, age,job 为必选,其他为可选参数的,那此时可以这么写,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package main

import "fmt"

type people struct {
name string
age uint16
height float32
weight float32
hometown string
currentResidence string
lifeMotto string
Hobby map[string]string
Job string
}

// peopleOptions 可选参数
type peopleOptions func(p *people)

func withHeight(height float32) peopleOptions {
return func(p *people) {
p.height = height
}
}

func withWeight(weight float32) peopleOptions {
return func(p *people) {
p.weight = weight
}
}

func withHometown(hometown string) peopleOptions {
return func(p *people) {
p.hometown = hometown
}
}

func withCurrentResidence(currentResidence string) peopleOptions {
return func(p *people) {
p.currentResidence = currentResidence
}
}

func withLifeMotto(lifeMotto string) peopleOptions {
return func(p *people) {
p.lifeMotto = lifeMotto
}
}

func withHobby(Hobby map[string]string) peopleOptions {
return func(p *people) {
p.Hobby = Hobby
}
}

// 构造函数
// 必选: name, age, job
func newPeopleOptions(name string, age uint16, job string, options ...peopleOptions) *people {
p := &people{
name: name,
age: age,
Job: job,
}
for _, option := range options {
option(p)
}
return p
}

//方法(值类型接受者)
func (p people) introduce() {
fmt.Println(p)
}

//方法(指针类型接受者)
func (p *people) introduceMe() {
fmt.Println(p)
}

func main() {
people := newPeopleOptions(
`Payne`,
21,
`data engineer`,
withHeight(1.767),
)
people.introduce()
//people.introduceMe()
}

其中with代表可选的, 如果我们后续 Foo 多了一个可变属性,那么我们只需要多一个 WithXXX 的方法就可以了,而 NewFoo 函数不需要任何变化,调用方只要在指定这个可变属性的地方增加 WithXXX 就可以了,扩展性非常好。

源码赏析

以下是go爬虫框架colly的初始化部分,大致结构如下

  • 可选函数类型:CollectorOption func(*Collector)

  • 采集器结构体:Collector

  • 初始化方法:Init

  • 虚拟环境处理方法:parseSettingsFromEnv

  • 构造函数:NewCollector

gocolly/colly.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// A CollectorOption sets an option on a Collector.
type CollectorOption func(*Collector)

// Collector provides the scraper instance for a scraping job
type Collector struct {
// UserAgent is the User-Agent string used by HTTP requests
UserAgent string
// MaxDepth limits the recursion depth of visited URLs.
// Set it to 0 for infinite recursion (default).
MaxDepth int
// AllowedDomains is a domain whitelist.
// Leave it blank to allow any domains to be visited
AllowedDomains []string
// DisallowedDomains is a domain blacklist.
DisallowedDomains []string
// DisallowedURLFilters is a list of regular expressions which restricts
// visiting URLs. If any of the rules matches to a URL the
// request will be stopped. DisallowedURLFilters will
// be evaluated before URLFilters
// Leave it blank to allow any URLs to be visited
DisallowedURLFilters []*regexp.Regexp
// URLFilters is a list of regular expressions which restricts
// visiting URLs. If any of the rules matches to a URL the
// request won't be stopped. DisallowedURLFilters will
// be evaluated before URLFilters

// Leave it blank to allow any URLs to be visited
URLFilters []*regexp.Regexp

// AllowURLRevisit allows multiple downloads of the same URL
AllowURLRevisit bool
// MaxBodySize is the limit of the retrieved response body in bytes.
// 0 means unlimited.
// The default value for MaxBodySize is 10MB (10 * 1024 * 1024 bytes).
MaxBodySize int
// CacheDir specifies a location where GET requests are cached as files.
// When it's not defined, caching is disabled.
CacheDir string
// IgnoreRobotsTxt allows the Collector to ignore any restrictions set by
// the target host's robots.txt file. See http://www.robotstxt.org/ for more
// information.
IgnoreRobotsTxt bool
// Async turns on asynchronous network communication. Use Collector.Wait() to
// be sure all requests have been finished.
Async bool
// ParseHTTPErrorResponse allows parsing HTTP responses with non 2xx status codes.
// By default, Colly parses only successful HTTP responses. Set ParseHTTPErrorResponse
// to true to enable it.
ParseHTTPErrorResponse bool
// ID is the unique identifier of a collector
ID uint32
// DetectCharset can enable character encoding detection for non-utf8 response bodies
// without explicit charset declaration. This feature uses https://github.com/saintfish/chardet
DetectCharset bool
// RedirectHandler allows control on how a redirect will be managed
// use c.SetRedirectHandler to set this value
redirectHandler func(req *http.Request, via []*http.Request) error
// CheckHead performs a HEAD request before every GET to pre-validate the response
CheckHead bool
// TraceHTTP enables capturing and reporting request performance for crawler tuning.
// When set to true, the Response.Trace will be filled in with an HTTPTrace object.
TraceHTTP bool
// Context is the context that will be used for HTTP requests. You can set this
// to support clean cancellation of scraping.
Context context.Context

store storage.Storage
debugger debug.Debugger
robotsMap map[string]*robotstxt.RobotsData
htmlCallbacks []*htmlCallbackContainer
xmlCallbacks []*xmlCallbackContainer
requestCallbacks []RequestCallback
responseCallbacks []ResponseCallback
responseHeadersCallbacks []ResponseHeadersCallback
errorCallbacks []ErrorCallback
scrapedCallbacks []ScrapedCallback
requestCount uint32
responseCount uint32
backend *httpBackend
wg *sync.WaitGroup
lock *sync.RWMutex
}

// Init initializes the Collector's private variables and sets default
// configuration for the Collector
func (c *Collector) Init() {
c.UserAgent = "colly - https://github.com/gocolly/colly/v2"
c.MaxDepth = 0
c.store = &storage.InMemoryStorage{}
c.store.Init()
c.MaxBodySize = 10 * 1024 * 1024
c.backend = &httpBackend{}
jar, _ := cookiejar.New(nil)
c.backend.Init(jar)
c.backend.Client.CheckRedirect = c.checkRedirectFunc()
c.wg = &sync.WaitGroup{}
c.lock = &sync.RWMutex{}
c.robotsMap = make(map[string]*robotstxt.RobotsData)
c.IgnoreRobotsTxt = true
c.ID = atomic.AddUint32(&collectorCounter, 1)
c.TraceHTTP = false
c.Context = context.Background()
}



// NewCollector creates a new Collector instance with default configuration
func NewCollector(options ...CollectorOption) *Collector {
c := &Collector{}
c.Init()

for _, f := range options {
f(c)
}

c.parseSettingsFromEnv()

return c
}

func (c *Collector) parseSettingsFromEnv() {
for _, e := range os.Environ() {
if !strings.HasPrefix(e, "COLLY_") {
continue
}
pair := strings.SplitN(e[6:], "=", 2)
if f, ok := envMap[pair[0]]; ok {
f(c, pair[1])
} else {
log.Println("Unknown environment variable:", pair[0])
}
}
}

svn

svn使用 命令行实现rollback

1
2
3
4
5
6
7
# 查看前100条提交日志
svn log -v -l 100
svn log -v -l 100 > svn_do.log
# 尝试回滚
svn merge --dry-run -r 当前版本号:目标版本号 target_repo_addr
# 回滚
svn merge -r 当前版本号:目标版本号 target_repo_addr

neovim

安装

1
2
3
4
5
6
7
8
9
10
11
# 普通安装
brew install neovim
# 开发版本安装
brew install --HEAD luajit
brew install --HEAD neovim
# 包安装
curl -LO https://github.com/neovim/neovim/releases/download/nightly/nvim-macos.tar.gz
tar xzf nvim-macos.tar.gz
./nvim-osx64/bin/nvim
# 验证安装
nvim --version

熟悉的所有Vim命令都可以在Neovim中使用,Neovim的配置文件格式也与Vim相同。不过,.vimrc在Neovim中不会自动加载。Neovim的配置文件遵守XDG基本目录结构,即所有的配置文件都放在~/.config
目录中。Neovim的配置文件被保存在~/.config/nvim

  • ~/.config/nvim/init.vim对应于~/.vimrc

  • ~/.config/nvim/对应于~/.vim/

基本配置

当然可以将Neovim的配置文件链接到Vim的配置文件

1
2
3
4
5
6
# 创建配置文件夹
mkdir -p $HOME/.config
# 创建文件夹软连接
ln -s $HOME/.vim $HOME/.config/nvim
# 创建配置文件软连接
ln -s $HOME/.vimrc $HOME/.config/nvim/init.vim

初次使用neovim

当在终端输入nvim时,将展示如下界面

image-20220125112813654

使用shfit + : 并输入checkhealth进行依赖检查

1
2
3
python3 -m pip install neovim
npm install -g neovim
cpan Neovim::ext

Neovim的默认选项与Vim有很大的不同。在现代的计算机世界里,文本编辑器的默认值需要对用户比较友好。默认情况下Vim的.vimrc文件并不包含任何默认设置,而Neovim默认已经设置好语法高亮、合理的缩进设置、wildmenu、高亮显示搜索结果和增量搜索(incsearch)等。可通过查看:help nvim-defaults
了解Neovim的默认选项。

我的配置:https://github.com/Payne-Wu/Bracket/blob/master/SystemInitialization/Vim/vimrc

miniconda

在科学计算、深度学习、机器学习等领域 Python 语言拥有举足轻重的地位,Python 开发环境,相关链接如下:

安装 Python 的方式有很多种,但根据所需,Anconda 无疑是最好的选择。在这里以 Miniconda 为例,在 Windows、macOS 平台下的安装。

安装

1
2
3
4
5
6
# Mac x86
wget -c https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh && sh Miniconda3-latest-MacOSX-x86_64.sh
# Mac m1
wget -c https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh && sh Miniconda3-latest-MacOSX-arm64.sh
# Linux
wget -c https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && sh Miniconda3-latest-Linux-x86_64.sh

点击此Windows 下载安装程序

配置终端显示

Linux or Mac

1
conda config --set auto_activate_base false

Windows

1
2
3
4
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
conda init --all
conda config --set changeps1 false
conda config --set auto_activate_base false

注意: 需要在管理员权限下

设置镜像源

不得不说Anconda是个非常不错的科学计算包管理工具(当然不限与Python,而笔者主要用conda来管理虚拟环境等),使用conda来管理的时 候难免会遇到国外网络的各种意外。对于此最简单的方法就是使用咱们国内的镜像源。使用方式如下

1
2
3
4
5
# 增加 镜像地址
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/

查看配置

1
conda config --show-sources

image-20211206140953931

当然也可以通过配置文件.condarc来修改,但笔者并不建议。在此遍不再过多赘述。