0%

写在前面

About ME 1.0

此博客记录了我的一些学习过程和一些项目的源码和学习笔记。有兴趣的朋友可在菜单的分类中找寻自己感兴趣的内容。有些内容可能过于浅显或者有误,欢迎大家指教和斧正。

学无止境,希望大家都能学有所成。

每一种编程语言都是一种艺术,希望大家选择编程语言不要纠结不前,还不如先出发。一直在路上。

2018/12/4

About ME 2.0

博文断断续续的写了两年多了,这两年我对技术的思考从浅显到底层,从一条线到一个平面。一路走来,收获良多。最初的信念现在还在坚守着:

仰望星空,也脚踏实地。

从一而终,终身学习,以有涯之生,追无涯之知,不亦乐乎!

2021/04/24

About ME 3.0

一个全干工程师。

2024/4/15

声明式和命令式编程

声明式(declarative)是结果导向的,命令式(imperative)是过程导向的

  • 命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。
  • 声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。

声明式编程和命令式编程的代码例子

​ 举个简单的例子,假设我们想让一个数组里的数值翻倍。 我们用命令式编程风格实现,像下面这样:

1
2
3
4
5
6
7
8
9
10
11
var numbers = [1,2,3,4,5]

var doubled = []

for(var i = 0; i < numbers.length; i++) {

var newNumber = numbers[i] * 2
doubled.push(newNumber)

}
console.log(doubled) //=> [2,4,6,8,10]

​ 我们直接遍历整个数组,取出每个元素,乘以二,然后把翻倍后的值放入新数组,每次都要操作这个双倍数组,直到计算完所有元素。

而使用声明式编程方法,我们可以用 Array.map 函数,像下面这样:

1
2
3
4
5
6
7
var numbers = [1,2,3,4,5]

var doubled = numbers.map(function(n) {

return n * 2
})
console.log(doubled) //=> [2,4,6,8,10]

map 利用当前的数组创建了一个新数组,新数组里的每个元素都是经过了传入map的函数(这里是function(n) { return n*2 })的处理。

map函数所作的事情是将直接遍历整个数组的过程归纳抽离出来,让我们专注于描述我们想要的是什么(what)。注意,我们传入map的是一个纯函数;它不具有任何副作用(不会改变外部状态),它只是接收一个数字,返回乘以二后的值。

​ 在一些具有函数式编程特征的语言里,对于list数据类型的操作,还有一些其他常用的声明式的函数方法。例如,求一个list里所有值的和,命令式编程会这样做:

1
2
3
4
5
6
7
8
9
var numbers = [1,2,3,4,5]

var total = 0

for(var i = 0; i < numbers.length; i++) {

total += numbers[i]
}
console.log(total) //=> 15

而在声明式编程方式里,我们使用 reduce 函数:

1
2
3
4
5
6
7
var numbers = [1,2,3,4,5]

var total = numbers.reduce(function(sum, n) {

return sum + n
});
console.log(total) //=> 15

reduce 函数利用传入的函数把一个 list 运算成一个值。它以这个函数为参数,数组里的每个元素都要经过它的处理。每一次调用,第一个参数(这里是sum)都是这个函数处理前一个值时返回的结果,而第二个参数(n)就是当前元素。这样下来,每此处理的新元素都会合计到sum中,最终我们得到的是整个数组的和。

​ 同样,reduce 函数归纳抽离了我们如何遍历数组和状态管理部分的实现,提供给我们一个通用的方式来把一个 list 合并成一个值。我们需要做的只是指明我们想要的是什么

阅读全文 »

我的编程、热爱、生活

​ 2016年八月份的一个下午,高考选完专业(计算机科学与技术)后,我在笔记本上敲下了第一个 “九九乘法表” 的 c 语言程序。当控制台输出熟悉的点阵数字的时候,我的编程生涯从就这一刻就开始了。

阅读全文 »

core dump 概念

core dump文件实际上是进程在某个时间点时的内存映像,当时进程使用的内存是啥样就会被原样保存下来存在文件系统的某个位置上,这个时间点一般是触发了SIGSEGV或者SIGABRT这两个信号的时候,当进程的内存映像保存完毕后进程就会异常终止,也就是大家喜闻乐见的“程序崩了”和“段错误:核心已转储”。

​ 因此 coredump 就像是程序出错崩溃后的“第一现场”,是用来排查错误的主要资源。

golang 程序生成 coredump 方法

设置环境变量和在代码里调用相关的标准库接口

​ 在这之前先用ulimit命令检测下系统当前能不能生成coredump:

1
2
$ ulimit -c
unlimited

​ 如果是unlimited就表示可以,如果是0那就不会生成,需要修改ulimit的设置。

修改GOTRACEBACK环境变量

GOTRACEBACK是用来控制panic发生时golang程序行为的,值是字符串,具体内容如下:

行为
none 不打印任何堆栈跟踪信息,不过崩溃的原因和哪行代码触发的panic还是会打印
single 只打印当前正在运行的触发panic的goroutine的堆栈以及runtime的堆栈;如果panic是runtime里发出的,则打印所有goroutine的堆栈跟踪信息
all 打印所有用户创建的goroutine的堆栈信息(不包含runtime的)
system 在前面all的基础上把runtime相关的所有协程的堆栈信息也一起打印出来
crash 打印的内容和前面system一样,但还会额外生成对应操作系统上的 coredump 文件

​ 将这个环境变量设置成crash就可以获得信息最全面的coredump文件。

设置编译参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"math/rand"
)

func main() {
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for {
index := rand.Intn(11)
fmt.Println(arr[index])
}
}
1
go build -gcflags="all=-N -l" main.go
阅读全文 »

对上层保持简洁,对下层保持抽象

概念

接口

​ 在 Golang 中,接口是一组方法签名。 当类型为接口中的所有方法提供定义时,它被称为实现接口。 它与 OOP(面向对象编程) 非常相似。 接口指定了类型应该具有的方法,类型决定了如何实现这些方法。

结构体

结构体

​ Golang 提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称 struct

传入接口

​ 传入接口意味着,可以使调用方更加具有灵活性、可扩展、且更易于编写单元测试.

可扩展性

​ 如下面这个例子,UserService需要一个存储对象完成一些操作,如果传入db struct 实例的话,意味着如果下次新增了redis store 那么UserService的逻辑需要进行修改,如果传入接口,只要相关的store实现了接口中定义的方法即可为UserService使用,不需要额外修改代码

(推荐)

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
// db.go mysql 
package db

type Store struct {
db *sql.DB
}

func NewDB() *Store { ... } // func to initialise DB

func (s *Store) Insert(item interface{}) error { ... } // insert item
func (s *Store) Get(id int) error { ... } // get item by id

------------------------------------------------------------------------------
// user.go
package user

type UserStore interface {
Insert(item interface{}) error
Get(id int) error
}


type UserService struct {
store UserStore
}

// 接受接口
func NewUserService(s UserStore) *UserService {
return &UserService{
store: s,
}
}

func (u *UserService) CreateUser() { ... }
func (u *UserService) RetrieveUser(id int) User { ... }

阅读全文 »

VictoriaMetricsn 原理入门

简介

VictoriaMetrics,是一个快速高效、经济并且可扩展的监控解决方案和时序数据库

​ 谈到VictoriaMetrics就必须要提到Prometheus,VictoriaMetrics是一个新兴的监控解决方案。它借助Prometheus强大的exporter生态、成熟的规范、服务发现等优点等,融入到Prometheus生态中。VictoriaMetrics官网很多兼容Prometheus参数解释都是直接跳转到Prometheus官网。

​ VictoriaMetrics可以作为Prometheus的长期远程存储方案,当然 VictoriaMetrics 也可以完全取代 Prometheus,因为 VictoriaMetrics 基本支持 Prometheus配置文件、PromQL、各类API、数据格式等等。

VictoriaMetrics 优点

  • 远程存储:可作为单一或多个Prometheus的远程存储
  • 安装简单:单节点架构一条命令就可以部署完毕(集群方式稍微复杂一些,但也很好理解)
  • 兼容性:PromQL兼容和增强的MetricsQL
  • Grafana兼容:VM可替换Grafana的Prometheus数据源(经测试,线上数据源直接替换后100%兼容)
  • 低内存:更低的内存占用,官方对比Prometheus,可以释放7倍左右内存空间(线上对比大概4倍)
  • 高压缩比:提供存储数据高压缩,官方说可以比Prometheus减少7倍的存储空间(线上对比大概是4~5倍)
  • 高性能:查询性能比Prometheus更快
  • 支持水平扩容&HA:基于VM集群版实现
  • 支持多租户:主要针对集群版

VictoriaMetrics 缺点

  • 图形化做的不好,虽然有vmui,但功能很少
  • 告警功能需要单独配置vmalert,而且vmalert只有api管理和查看,暂时没用图形界面
  • 没有类似Prometheus的WAL日志,突然故障可能会丢失部分数据
阅读全文 »

Redis五个使用场景

原文转自:https://www.jdon.com/69107.html

1.缓存

最常见的用例是利用 Redis 进行缓存。这有助于保护数据库层不超载。Redis 可为缓存数据提供快速查找时间,有助于提高应用程序性能。

2.会话存储

使用 Redis 在无状态服务器之间共享用户会话数据。Redis 提供了一个集中存储会话数据的地方,并可轻松扩展服务器。

3.分布式锁

使用 Redis 分布式锁来授予对共享资源的互斥访问权限。这可以防止分布式系统中的竞赛条件。Redis 锁易于实现,并能自动过期。

4.计数器和速率限制器

使用 Redis 来跟踪社交媒体应用程序上的点赞数、浏览数等。Redis 计数器提供原子增量/减量。我们还使用 Redis 对 API 端点执行速率限制。这有助于防止滥用。

5.排行榜

排序集让 Redis 可以轻松实现游戏排行榜。可以从排行榜中添加、更新或删除用户,并高效地查询范围。

what-happens-when(浏览器一次请求的历程)

原文来自:alex/what-happens-when: An attempt to answer the age old interview question “What happens when you type google.com into your browser and press enter?” — 亚历克斯/什么时候会发生什么:试图回答古老的面试问题“当你在浏览器中输入 google.com 并按回车键时会发生什么? (github.com)

接下来的内容介绍了物理键盘和系统中断的工作原理,但是有一部分内容却没有设计

按下"g"键

​ 当你按下“g”键,浏览器接收到这个消息之后,会触发自动完成机制。浏览器根据自己的算法,以及你是否处于隐私浏览模式,会在浏览器的地址框下方给出输入建议。大部分算法会优先考虑根据你的搜索历史和书签等内容给出建议。你打算输入 “google.com”,因此给出的建议并不匹配。

​ 但是输入过程中仍然有大量的代码在后台运行,你的每一次按键都会使得给出的建议更加准确。甚至有可能在你输入之前,浏览器就将 “google.com” 建议给你。

回车键按下

​ 为了从零开始,我们选择键盘上的回车键被按到最低处作为起点。在这个时刻,一个专用于回车键的电流回路被直接地或者通过电容器间接地闭合了,使得少量的电流进入了键盘的逻辑电路系统。这个系统会扫描每个键的状态,对于按键开关的电位弹跳变化进行噪音消除(debounce),并将其转化为键盘码值。在这里,回车的码值是13。键盘控制器在得到码值之后,将其编码,用于之后的传输。现在这个传输过程几乎都是通过通用串行总线(USB)或者蓝牙(Bluetooth)来进行的,以前是通过PS/2或者ADB连接进行。

USB键盘:

  • 键盘的USB元件通过计算机上的USB接口与USB控制器相连接,USB接口中的第一号针为它提供了5V的电压
  • 键码值存储在键盘内部电路一个叫做"endpoint"的寄存器内
  • USB控制器大概每隔10ms便查询一次"endpoint"以得到存储的键码值数据,这个最短时间间隔由键盘提供
  • 键值码值通过USB串行接口引擎被转换成一个或者多个遵循低层USB协议的USB数据包
  • 这些数据包通过D+针或者D-针(中间的两个针),以最高1.5Mb/s的速度从键盘传输至计算机。速度限制是因为人机交互设备总是被声明成"低速设备"(USB 2.0 compliance)
  • 这个串行信号在计算机的USB控制器处被解码,然后被人机交互设备通用键盘驱动进行进一步解释。之后按键的码值被传输到操作系统的硬件抽象层

虚拟键盘(触屏设备):

  • 在现代电容屏上,当用户把手指放在屏幕上时,一小部分电流从传导层的静电域经过手指传导,形成了一个回路,使得屏幕上触控的那一点电压下降,屏幕控制器产生一个中断,报告这次“点击”的坐标
  • 然后移动操作系统通知当前活跃的应用,有一个点击事件发生在它的某个GUI部件上了,现在这个部件是虚拟键盘的按钮
  • 虚拟键盘引发一个软中断,返回给OS一个“按键按下”消息
  • 这个消息又返回来向当前活跃的应用通知一个“按键按下”事件
阅读全文 »

Prometheus 概念入门

数据模型

​ Prometheus 所有采集的监控数据均以指标(metric)的形式保存在内置的时间序列数据库当中(TSDB):属于同一指标名称,同一标签集合的、有时间戳标记的数据流。除了存储的时间序列,Prometheus 还可以根据查询请求产生临时的、衍生的时间序列作为返回结果。

指标名称和标签

每一条时间序列由指标名称(Metrics Name)以及一组标签(键值对)唯一标识。其中指标的名称(metric name)可以反映被监控样本的含义(例如,http_requests_total — 表示当前系统接收到的 HTTP 请求总量),指标名称只能由 ASCII 字符、数字、下划线以及冒号组成,同时必须匹配正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*

[info] 注意

冒号用来表示用户自定义的记录规则,不能在 exporter 中或监控对象直接暴露的指标中使用冒号来定义指标名称。

通过使用标签,Prometheus 开启了强大的多维数据模型:对于相同的指标名称,通过不同标签列表的集合,会形成特定的度量维度实例(例如:所有包含度量名称为 /api/tracks 的 http 请求,打上 method=POST 的标签,就会形成具体的 http 请求)。该查询语言在这些指标和标签列表的基础上进行过滤和聚合。改变任何度量指标上的任何标签值(包括添加或删除指标),都会创建新的时间序列。

标签的名称只能由 ASCII 字符、数字以及下划线组成并满足正则表达式 [a-zA-Z_][a-zA-Z0-9_]*。其中以 __ 作为前缀的标签,是系统保留的关键字,只能在系统内部使用。标签的值则可以包含任何 Unicode 编码的字符。

样本

在时间序列中的每一个点称为一个样本(sample),样本由以下三部分组成:

  • 指标(metric):指标名称和描述当前样本特征的 labelsets;
  • 时间戳(timestamp):一个精确到毫秒的时间戳;
  • 样本值(value): 一个 folat64 的浮点型数据表示当前样本的值。
阅读全文 »

引子

监控系统必要性

​ 作为运维者,第一个接触的基本上是监控平台,各种各样的监控,看各种各样的指标,好像没有监控就觉得不正常,那么为什么需要监控呢?

  • 预防故障,例如当磁盘空间增长到一定的程度的时候,就会产生故障,这个时候监控系统的作用就是当达到一个阀值的时候,发出告警,然后进行处理。
  • 预测变化趋势,例如我的分布式文件系统,每天数据增长1T空间,那么我总共有多少空间,剩余空间大小,是否要进行扩容等操作。
  • 当故障发生的时候,能提供给我基本信息给与我排查的思路,例如redis不可读,是否能看到是哪个实例,能看到相关的日志信息,能测试是否刻读写,能查看哪个是master。
  • 监控系统关键指标,例如对于web服务器来说,响应速度,来判断是否中间件有问题,是否数据库有问题,还是网络有问题;活跃的用户数,每天我的网站有多少用户访问;有多少新注册的用户。

简介

​ 夜莺监控( Nightingale )是一款国产、开源云原生监控分析系统,采用 All-In-One 的设计,集数据采集、可视化、监控告警、数据分析于一体。于 2020 年 3 月 20 日,在 github 上发布 v1 版本,已累计迭代 60 多个版本。从 v5 版本开始与 Prometheus、VictoriaMetrics、Grafana、Telegraf、Datadog 等生态紧密协同集成,提供开箱即用的企业级监控分析和告警能力,已有众多企业选择将 Prometheus + AlertManager + Grafana 的组合方案升级为使用夜莺监控。夜莺监控,由滴滴开发和开源,并于 2022 年 5 月 11 日,捐赠予中国计算机学会开源发展委员会(CCF ODC),为 CCF ODC 成立后接受捐赠的第一个开源项目。夜莺监控的核心开发团队,也是Open-Falcon项目原核心研发人员。

产品介绍

img

  • 开箱即用:支持 Docker、Helm Chart、云服务等多种部署方式;集数据采集、监控告警、可视化为一体;内置多种监控仪表盘、快捷视图、告警规则模板,导入即可快速使用;大幅降低云原生监控系统的建设成本、学习成本、使用成本
  • 云原生:以交钥匙的方式快速构建企业级的云原生监控体系,支持 Categraf、Telegraf、Grafana-agent 等多种采集器,支持 Prometheus、VictoriaMetrics、M3DB、ElasticSearch 等多种数据库,兼容支持导入 Grafana 仪表盘,与云原生生态无缝集成
  • 专业告警:可视化的告警配置和管理,支持丰富的告警规则,提供屏蔽规则、订阅规则的配置能力,支持告警多种送达渠道,支持告警自愈、告警事件管理等
  • 灵活扩展、中心化管理:夜莺监控,可部署在 1 核 1G 的云主机,可在上百台机器集群化部署,可运行在 K8s 中;也可将时序库、告警引擎等组件下沉到各机房、各 Region,兼顾边缘部署和中心化统一管理,解决数据割裂,缺乏统一视图的难题
  • 高性能、高可用:得益于夜莺的多数据源管理引擎,和夜莺引擎侧优秀的架构设计,借助于高性能时序库,可以满足数亿时间线的采集、存储、告警分析场景,节省大量成本;夜莺监控组件均可水平扩展,无单点,已在上千家企业部署落地,经受了严苛的生产实践检验

​ Nightingale 可以接收各种采集器上报的监控数据,转存到时序库(可以支持Prometheus、M3DB、VictoriaMetrics、Thanos等),并提供告警规则、屏蔽规则、订阅规则的配置能力,提供监控数据的查看能力,提供告警自愈机制(告警触发之后自动回调某个webhook地址或者执行某个脚本),提供历史告警事件的存储管理、分组查看的能力。

阅读全文 »