跳到主要内容

golang类型

· 2 分钟阅读
zx06
Maintainer of Blog

golang类型

值类型

  • int
  • float
  • bool
  • string
  • struct
  • array
  • ...

引用类型

  • pointer
  • slice
  • map
  • channel
  • interface
  • function
  • ...

类型比较

值类型才能被比较,引用类型不能被比较,参考下面代码

ps: 当struct,array包含引用类型时,也不能比较了

package main

import "testing"

type V1 struct {
X int
}

type ValueType struct {
A int
B string
C bool
D V1
}

type RefType struct {
// slice 是引用类型
A []string
}

type RefType2 struct {
// array 是值类型
A [2]string
}

type RefType3 struct {
// map 是引用类型
A map[string]string
}

func TestValueTypeCompare(t *testing.T) {
v1 := ValueType{1, "a", true, V1{1}}
v2 := ValueType{1, "a", true, V1{1}}
v3 := ValueType{1, "a", false, V1{1}}
if v1 != v2 {
t.Errorf("%v != %v", v1, v2)
}
if v1 == v3 {
t.Errorf("%v == %v", v1, v3)
}

}

// 编译失败,因为结构体中的字段是引用类型
// func TestRefType(t *testing.T) {
// v1 := RefType{[]string{"a", "b"}}
// v2 := RefType{[]string{"a", "b"}}
// v3 := RefType{[]string{"a", "c"}}
// if v1 == v2 {
// t.Errorf("%v == %v", v1, v2)
// }
// if v1 != v3 {
// t.Errorf("%v != %v", v1, v3)
// }
// }

func TestRefType2(t *testing.T) {
v1 := RefType2{[2]string{"a", "b"}}
v2 := RefType2{[2]string{"a", "b"}}
v3 := RefType2{[2]string{"a", "c"}}
if v1 != v2 {
t.Errorf("%v != %v", v1, v2)
}
if v1 == v3 {
t.Errorf("%v == %v", v1, v3)
}

}

// 编译失败,因为结构体中的字段是引用类型
// func TestRefType3(t *testing.T) {
// v1:= RefType3{map[string]string{"a":"b"}}
// v2:= RefType3{map[string]string{"a":"b"}}
// v3:= RefType3{map[string]string{"a":"c"}}
// if v1 != v2 {
// t.Errorf("%v != %v", v1, v2)
// }
// if v1 == v3 {
// t.Errorf("%v == %v", v1, v3)
// }

// }

标签:

可观测系统

· 14 分钟阅读
zx06
Maintainer of Blog

可观测性( Observability )主要是指了解程序内部运行情况的能力。我们不希望应用发布上线后,对应用的内部一无所知。对于我们来说,整个应用就是一个黑盒子。即便应用出现错误或者发生崩溃,我们也可以得到崩溃前的所有相关数据,这也是飞机黑匣子( Flight Recorder )设计的出发点,如 图1 所示。 flight-recorder.jpg 目前,关于可观测性的架构设计主要涉及三个部分:日志(logging)、度量(Metrics)和追踪(Tracing)。下面就从这三个方面详细阐述可观测性架构的设计。

日志

要想了解系统的运行情况,最 简单的方法就是查看日志。 为此,我们创造了非常多的日志框架、工具和系统,如日志文件打印、日志文件采集工具、日志分析系统等。 但是,在 实际运维中,我们不能将所有信息事无巨细地全部记录下来,这样做反而没有意义。 我们需要为日志设置不同的级别,如 debug、error、info 等,在开发、测试、生产等不同环境下开启不同的日志级别,并保证在系统运行时能够实时调控这些日志级别。 通常,我们不用考虑日志处理的问题,毕竟日志处理技术经过长时间的发展,目前已经非常成熟,几乎所有的编程语言都有对应的日志框架。目前,云厂商基本上都会提供日志服务,对接非常简单,或者自行安装成熟的日志处理系统,如 ElasticStack 等。

度量

度量不仅包括 CPU 负载、内存使用量等技术指标的度量,还包括非常多的业务度量( Business Metrics ),如每分钟的交易额、每分钟会员登录数等。对于这些业务度量参数,我们在做架构设计的时候,需要以参考指标的方式全部罗列出来,以便于观测上线后的数据,并做出相应的业务决策。 这里可能会有读者产生疑问,我们已经使用日志记录了相关的数据,数据库中也保存了最终的数据,为什么还要增加对数据的记录?为了解答这个问题,我们首先看一下如下区别。

第一,日志记录的是发生在某个时间点的事情,其中包含非常多的细节,可以说是事无巨细的。

第二,数据库记录的是当前数据的最新快照,我们通常不会关注中间的过程,如电商网站的商品价格可能经过多次调整,但数据库通常只会记录商品的最新价格。

第三,度量统计的是一个窗口期的聚合数据,可以是平均值,也可以是累计值。如果是 CPU 负载,就统计一段时间的平均值;如果是 1 分钟内交易的订单数,就需要统计累计值。还有一类比较特殊,就是那些没有时间区间的情况,如计数器等,在应用启动后的整个运行期间,它的值会不停地累加,在应用重启后它会被重新计算。 虽然日志可以计算出一些数据,如订单数、订单金额等,但这里需要考虑数据分析的成本和实时性,以更好地实现计算资源、存储节约和快速查询等。而度量统计的是窗口期的数据,所以不需要再次计算,从而节约了计算资源;同时也不需要保存窗口期中每一条具体的数据,因此可以节约存储资源;从用户角度来说,由于数据经过了窗口期的预处理,因此查询响应的速度也会更快。 总体来说,度量部分处理的是可观测性数据中的垂直场景。当我们更关注某一窗口期的聚合数据,同时关注点主要聚焦于数据的趋势和对比时,度量刚好能够满足这类需求。

典型的度量指标主要由以下 5 个部分组成:

  1. 名称:因为度量指标的名称要表达其代表的意思,所以最好采用命名空间级联的方式,可以使用类似域名的“ . ”分隔,或者使用 Prometheus 中采用的“ _”分隔。
  2. 时间点:采集度量的时间点,通常由度量框架自动设置。
  3. 数字值:度量值只能为数字值,不能为字符串等其他值。
  4. 类型:典型的类型分别为计数器、直方图、平均比率、计时器、计量表等。
  5. 标签:主要包括一些元信息,如来源服务器标识、应用名称、分组信息、运行环境等。标签是为了方便后续的度量查询和再聚合处理。 当以上信息保存到 Prometheus 等度量系统后,我们可以根据上述结构进行查询。PromQL 是 Prometheus 提供的度量查询语言。

最后为大家介绍基于度量系统的一些预警规则。预警规则非常丰富,下面列举几条以方便大家参考:

  1. 阈值预警:当某一度量指标的值低于或高于某一预设值时,就会触发警报。例如,CPU 的负载、业务上的度量值跌至零,这些都会触发预警。
  2. 同期数据对比:在某些场景下,通过绝对值判断是不能发现系统问题的,比如,一个电商网站每天不同时段的交易额是有差别的,所以比对每周同一天同一时段的数据来判断问题会更加精确。
  3. 趋势预警:主要是针对计数器类型设置的预警,如果度量数值出现激增或骤降,或者游离在正常的曲线趋势之外,就需要引起我们的注意。 回到实际的应用开发,大多数云厂商也提供了度量集成化服务,如阿里云的Prometheus 服务。在程序中,我们基本上只需要直接对接即可,诸如度量指标的采集、存储、监控、告警、图表展现等数据监控服务。

追踪

微服务架构后基本上是分布式的架构设计。一个简单的 HTTP 请求可能涉及 5 个以上应用,一旦出现问题,就会很难快速定位。例如,用户反馈会员登录非常慢,基本要花费 5 秒以上的时间,这种情况该如何定位问题所在?定位问题涉及登录的 Web应用、账号验证服务、会员信息服务、登录的安全监控系统,还涉及 Redis、数据库等。如果没有一个高效的追踪系统,排查定位问题的复杂度可想而知。 首先,让我们看一下追踪系统的基本元素。

traceId

traceId 用来标识一个追踪链,如一个 64 位或 128 位长度的字符串。不同追踪链的 traceId 不同。但在某一个追踪链中,traceId 始终保持不变。traceId 通常在请求的入口处生成。如对于 HTTP 请求,traceId 基本上在网关层生成,也可以延后到具体的 Web 应用中生成。在产品环境中,并不是所有的请求都要启动追踪。我们只会采样部分请求,如只会追踪 2% 的请求,这样做主要是考虑到追踪对整个系统会造成额外的开销。当然,在测试环境中,为便于排查问题,建议所有请求都开启追踪。

spanId

spanId 用于在一个追踪链中记录一个跨时间段的操作。例如,我们访问数据库或者进行 RPC 调用的过程,就对应于一个 spanId。在一个区间(span)中,ID 的作用是便于识别。ID 通常是一个 64 位的 long 型数值,名称的作用是便于用户了解是什么操作,起始时间和结束时间的作用是便于了解操作时长。另外,区间还可以包含其他元信息。总的来说,一个追踪链是由多个区间组成的。区间提供具体的操作信息。区间的生成会涉及应用中的代码,我们称之为区间的埋点。

parentId

在追踪链中,我们可能需要对一些区间进行分组,如将某一应用内部的多个区间归在一起,这样就可以了解该应用在整个调用链中消耗的时间。其解决方案是为区间添加parentId,将不同类别的区间归在一起。通常,我们在进入一个应用时会进行 parentId 的设置。例如,进入会员登录应用时会设置一个 parentId,在进入账号验证服务时会设置一个 parentId ,这样我们就能根据不同的应用对区间进行归类。在同一个应用内部,我们还可以基于应用的 parentId 设置子 parentId。如果想要归类数据库相关的操作,则将操作全部列在数据库的 parentId 下。 追踪链可以将整个请求在不同应用和系统中的操作信息串联起来。我们只要输入traceId ,就可以在追踪系统中了解整个调用链的详细信息。那么,在不同的应用和系统中,路径和区间信息又是如何采集的呢?Zipkin 是一款知名的路径跟踪产品,其中 Brave SDK 可以实现路径和区间信息的采集。Brave SDK 负责创建路径和区间,同时将这些信息异步上报给 Zipkin,完成追踪链的数据采集工作。由于路径和区间信息的采集是通过远程调用实现的,因此这个采集过程一定要是异步实现的,只有这样,才能确保不会影响到正常的业务操作。最典型的采集方法就是对接 gRPC、Kafka 和 RSocket 等异步协议或系统,以确保数据的采集全部是异步的。

opentelemetry

· 4 分钟阅读
zx06
Maintainer of Blog

k8s yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: jaeger-query
spec:
selector:
matchLabels:
app: jaeger-query
template:
metadata:
labels:
app: jaeger-query
spec:
containers:
- name: jaeger-query
image: jaegertracing/jaeger-query:1.37.0
envFrom:
- configMapRef:
name: jaeger
resources:
limits:
memory: "1024Mi"
cpu: "1000m"
ports:
- containerPort: 16686

---
apiVersion: v1
kind: Service
metadata:
name: jaeger-query
spec:
selector:
app: jaeger-query
ports:
- name: jaeger-query
port: 16686
targetPort: 16686
sessionAffinity: None
type: ClusterIP

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jaeger-query
labels:
name: jaeger-query
spec:
rules:
- host: jaeger.otel.dev
http:
paths:
- backend:
serviceName: jaeger-query
servicePort: 16686
path: /

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jaeger-collector
spec:
selector:
matchLabels:
app: jaeger-collector
template:
metadata:
labels:
app: jaeger-collector
spec:
containers:
- name: jaeger-collector
image: jaegertracing/jaeger-collector:1.37.0
envFrom:
- configMapRef:
name: jaeger
resources:
limits:
memory: "1024Mi"
cpu: "1000m"
ports:
- containerPort: 14250
---
apiVersion: v1
kind: Service
metadata:
name: jaeger-collector
spec:
selector:
app: jaeger-collector
ports:
- name: jaeger-collector-grpc
port: 14250
targetPort: 14250
sessionAffinity: None
type: ClusterIP

---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: spark-dependencies
spec:
schedule: "0 * * * *"
successfulJobsHistoryLimit: 10
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
containers:
- name: spark-dependencies
image: jaegertracing/spark-dependencies:latest
envFrom:
- configMapRef:
name: jaeger
restartPolicy: OnFailure

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: otel-collector
spec:
selector:
matchLabels:
app: otel-collector
template:
metadata:
labels:
app: otel-collector
spec:
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.58.0
args:
- --config
- /etc/otelcol/otel-collector-config.yml
volumeMounts:
- mountPath: /etc/otelcol/
name: otel-collector-config
resources:
limits:
memory: "1024Mi"
cpu: "1000m"
ports:
# otel-grpc
- containerPort: 4317
# otel-http
- containerPort: 4318
# jaeger thrift_http
- containerPort: 14268
# jaeger thrift_compact
- containerPort: 6831
# prometheus
- containerPort: 8889
volumes:
- name: otel-collector-config
configMap:
name: otel-collector
items:
- key: otel-collector-config
path: otel-collector-config.yml

---
apiVersion: v1
kind: Service
metadata:
name: otel-collector
spec:
selector:
app: otel-collector
ports:
- name: prometheus
port: 8889
targetPort: 8889
- name: otel-collector-grpc
port: 4317
targetPort: 4317
- name: otel-collector-http
port: 4318
targetPort: 4318
sessionAffinity: None
type: ClusterIP

---
apiVersion: v1
kind: Service
metadata:
name: otel-collector-node-port
spec:
selector:
app: otel-collector
ports:
- name: otel-collector-grpc
port: 4317
targetPort: 4317
- name: otel-collector-http
port: 4318
targetPort: 4318
sessionAffinity: None
type: NodePort

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
spec:
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
containers:
- name: prometheus
image: prom/prometheus:v2.38.0
volumeMounts:
- mountPath: /etc/prometheus/
name: prometheus-config
- mountPath: /prometheus/
name: prometheus-data
resources:
limits:
memory: "1024Mi"
cpu: "1000m"
ports:
- containerPort: 9090
volumes:
- name: prometheus-config
configMap:
name: prometheus-config
items:
- key: prometheus-config
path: prometheus.yml
- name: prometheus-data
persistentVolumeClaim:
claimName: prom-pvc

---
apiVersion: v1
kind: Service
metadata:
name: prometheus
spec:
selector:
app: prometheus
ports:
- name: prometheus
port: 9090
targetPort: 9090
sessionAffinity: None
type: ClusterIP

---
apiVersion: v1
kind: ConfigMap
metadata:
name: jaeger
data:
SPAN_STORAGE_TYPE: elasticsearch
ES_SERVER_URLS: http://elastic:9200
METRICS_STORAGE_TYPE: prometheus
PROMETHEUS_SERVER_URL: http://prometheus:9090

# spark-dependencies
STORAGE: elasticsearch
ES_NODES: http://elastic:9200

---
apiVersion: v1
kind: ConfigMap
metadata:
name: otel-collector
data:
otel-collector-config: |-
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
jaeger:
protocols:
thrift_http:
endpoint: 0.0.0.0:14268
thrift_compact:
endpoint: 0.0.0.0:6831


# Dummy receiver that's never used, because a pipeline is required to have one.
otlp/spanmetrics:
protocols:
grpc:
endpoint: "localhost:65535"

exporters:
prometheus:
endpoint: "0.0.0.0:8889"

jaeger:
endpoint: "jaeger-collector:14250"
tls:
insecure: true

processors:
batch:
memory_limiter:
check_interval: 1s
limit_mib: 200
spanmetrics:
metrics_exporter: prometheus

service:
pipelines:
traces:
receivers: [otlp,jaeger]
processors: [spanmetrics, batch]
exporters: [jaeger]
# The exporter name in this pipeline must match the spanmetrics.metrics_exporter name.
# The receiver is just a dummy and never used; added to pass validation requiring at least one receiver in a pipeline.
metrics/spanmetrics:
receivers: [otlp/spanmetrics]
exporters: [prometheus]

---
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
data:
prometheus-config: |-
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).

scrape_configs:
- job_name: aggregated-trace-metrics
static_configs:
- targets: ['otel-collector:8889']

为你的博客配置analytics

· 1 分钟阅读
zx06
Maintainer of Blog

可以参考此处一键部署 running-on-railway
但是我这边选择的是使用docker镜像在Railway上部署

创建一个github项目

新建一个Dockerfile

FROM ghcr.io/mikecao/umami:postgresql-v1.31.0
ARG PORT
ARG DATABASE_TYPE postgresql
ARG DATABASE_URL
ARG HASH_SALT

去railway上新建一个项目

todo

debian安装docker

· 2 分钟阅读
zx06
Maintainer of Blog

支持的系统版本

  • Buster 10
  • Stretch 9 (stable) / Raspbian Stretch

删除旧版本

$ sudo apt-get remove docker docker-engine docker.io containerd runc

安装Docker Engine社区版

设置apt仓库

1. 更新apt包索引:

$ sudo apt-get update

2. 安装一些包让apt支持https:

$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common

3. 添加Docker官方GPG key:

$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88

pub 4096R/0EBFCD88 2017-02-22
Key fingerprint = 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88
uid Docker Release (CE deb) <docker@docker.com>
sub 4096R/F273FCD8 2017-02-22

4. 添加Docker源:

$ sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/debian \
$(lsb_release -cs) \
stable"

安装Docker社区版

1. 更新apt包索引:

$ sudo apt-get update

2. 安装:

$ sudo apt-get install docker-ce docker-ce-cli containerd.io

安装docker-compose(可选)

1. 下载:

sudo curl -L "https://github.com/docker/compose/releases/download/1.25.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

2. 增加可执行权限:

sudo chmod +x /usr/local/bin/docker-compose

配置镜像站

daocloud 提供了一个一键配置镜像的脚本

# 命令行最后面的网址可以换成别的镜像站,如腾讯: https://mirror.ccs.tencentyun.com
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io

Markdown——入门指南

· 4 分钟阅读
zx06
Maintainer of Blog

Markdown 是一种轻量级的「标记语言」,它的优点很多,目前也被越来越多的写作爱好者,撰稿者广泛使用。

Markdown的一些简要规则

标题

在 Markdown 中,如果一段文字被定义为标题,只要在这段文字前加 # 号即可。

## 二级标题
### 三级标题
###### 六级标题

在行首插入 1 到 6 个 # ,对应到标题 1 到 6 阶

Markdown 支持有序列表和无序列表。 无序列表使用星号、加号或是减号作为列表标记:

* a
+ b
- c

显示的结果都是一样:

有序列表则使用数字接着一个英文句点:

1. a
2. b
3. c

但是数字的顺序却并不影响生成的有序列表结果 像:

1. a
1. b
1. c

或者:

3. a
8. b
4. c

最后生成的结果都是

所以当你需要在行首输入类似3.14这样的带.数字时需要在.前面加上''就像3\.14

引用

如果你需要引用一小段别处的句子,那么就要用引用的格式。 在句子前面加上大于号例如: > 这是引用 显示效果:

这是引用

图片与链接

链接的格式是这样: [链接显示的文字](链接地址) 图片的区别在于前面多了一个'!'

粗体和斜体

Markdown 的粗体和斜体也非常简单,用**包含一段文本就是粗体的语法,用*包含一段文本就是斜体的语法。

**粗体**

*斜体*

表格

	| ABCD | EFGH | IJKL |
|------|:----:|-----:|
| a | b | c |
| d | e | f |
| g | h | i |

上面的格式就能生成以下表格
其中':'是标记对其方式,放后面就是右对齐两边都有就是居中了,当然放前面就是默认的格式左对齐了。

不同的软件表格显示的效果也不一样 表格应该是markdown中最坑的地方了,反正markdown可以嵌去html语句,完全可以用它来画。

代码

如果你只想高亮语句中的某个函数名或关键字,可以使用 function_name() 实现通常编辑器根据代码片段适配合适的高亮方法,但你也可以用 ``` 包裹一段代码,并指定一种语言

public class Hello{
public static void mian(String [] args){
System.out.printls("Hello World!");
}
}

注意`是数字1左边那个键

分割线

连续三个以上*-

line1


line2

参考文档

修改rc.local文件,设置开机启动

· 2 分钟阅读
zx06
Maintainer of Blog

为了在树莓派启动的时候运行一个命令或程序,你需要将命令添加到rc.local文件中。这对于想要在树莓派接通电源后无需配置直接运行程序,或者不希望每次都手动启动程序的情况非常有用。

  • 编辑rc.local文件

pi@raspberrypi / $ sudo nano /etc/rc.local
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
printf "My IP address is %s\n" "$_IP"
fi

exit 0

在eixt 0 前面加入要执行的命令 '/bin/oraynewph status &' #如果你的命令需要长时间运行(例如死循环)或者运行后不能退出,那么你必须确保在命令的最后添加“&”符号让命令运行在其它进程

树莓派安装samba

· 1 分钟阅读
zx06
Maintainer of Blog

通过使用samba服务,可以自由访问修改树莓派home目录下的文件

  • 安装 smaba:

sudo apt-get install samba samba-common-bin
  • 修改配置文件

sudo nano /etc/samba/smb.conf

下面的配置是让用户可以访问自己的 home 目录

  1. 开启用户认证,找到“##### Authentication #####”,添加“security = user”
  2. 配置用户可以读写自己的 home 目录,在“[homes]”节中,把 “read only = yes” 改为 “read only = no”
  3. 找到browseable=no改为yes 否则等会访问时此文件夹会隐藏
  • 重启samba服务

sudo /etc/init.d/samba restart
  • 把系统默认用户pi添加到samba

sudo smbpasswd -a pi

根据提示输入密码

  • windows访问

打开文件资源管理器,输入\192.168.1.104\pi 文件资源管理器