开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Android组件化实战之利用Maven优雅地调试SDK

发表于 2021-04-09

打包sdk到Maven仓库

Maven 是 Apache 下的一个纯 Java 开发的开源项目,基于项目对象模型(缩写:POM)概念,Maven可以从中央信息管理一个项目的构建、报告和文档等步骤。Maven 也可被用于构建和管理各种项目,例如 C#,Ruby,Scala 和其他语言编写的项目。

POM介绍

POM(Project Object Model)即项目对象模型,通过xml格式保存的pom.xml文件,作用类似ant的build.xml文件,功能更强大。该文件用于管理:源代码、配置文件、开发者的信息和角色、问题追踪系统、组织信息、项目授权、项目的url、项目的依赖关系等等;

Glide:POM举个栗子

1
2
3
4
5
6
7
8
9
10
11
xml复制代码<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.bumptech.glide</groupId>
<artifactId>glide</artifactId>
<version>4.12.0</version>
<packaging>aar</packaging>
<name>Glide</name>
<description>A fast and efficient image loading library for Android focused on smooth scrolling.</description>
<url>https://github.com/bumptech/glide</url>
...
</project>

POM节点

  • groupId : 组织标识,例如:com.github.bumptech.glide,在目录下,将是: com/github/bumptech/glide目录。
  • artifactId : 项目名称,例如:Glide。
  • version : 版本号。
  • packaging : 打包的格式,可以为:pom , jar , maven-plugin , ejb , war , ear , rar , par ,aar等。

Maven的目标

  • 简化构建过程
  • 提供统一的构建系统
  • 提供优质的项目信息
  • 鼓励更好的开发实践

Maven仓库

Maven 仓库能帮助我们管理构件(主要是JAR),它就是放置所有JAR文件(WAR,ZIP,POM,AAR等等)的地方。

举个栗子:阿里&jcenter& maven 仓库

Android引入仓库 : 根 build.gradle配置 gradle—doc

  1. buildscript repositories vs 2.allprojects repositories
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
groovy复制代码 buildscript {
repositories {
jcenter()
google()
}
}
allprojects {
addRepos(repositories)
repositories{
jcenter()
google()
mavenLocal()
mavenCentral()
maven { url "http://maven.aliyun.com/nexus/content/groups/public" }
maven { url "file:///${rootProject.getRootDir()}/asdk" }
maven { url "file:///${rootProject.getRootDir()}/bsdk" }
}
}

buildscript { }:Configures the build script classpath for this project.

allprojects { }:Configures this project and each of its sub-projects.

注解:buildscript是gradle脚本执行所需依赖,allprojects是项目本身需要的依赖,所以我们将自己所需要的仓库加到allprojects配置中

通过以上我们知道,打包sdk 需要一个托管的仓库,那么下面我们就介绍一下,如何搭建一个私有仓库;基于安全考虑不用公共仓库,学习一下如何搭建私有仓库。

通过nexus搭建私有仓库

Nexus 简介:是Maven仓库管理器,如果你使用Maven,你可以从Maven中央仓库 下载所需要的构件(artifact)

简介2:World’s #1 Repository Manager(自己夸自己世界第一,其他吹牛逼的不用看了)Single source of truth for all of your components, binaries, and build artifacts Efficiently distribute parts and containers to developers Deployed at more than 100,000 organizations globally

搭建Maven私有仓库步骤

1.下载

www.sonatype.com/download-os…

2.启动

1
2
bash复制代码~ » /Users/caining/Documents/applications/nexus-3.28.1-01-mac/nexus-3.28.1-01/bin/nexus start
Starting nexus

3.访问

http://localhost:8081/

4.修改端口方法

1
2
3
4
5
6
7
8
bash复制代码cd /Users/caining/Documents/applications/nexus-3.28.1-01-mac/nexus-3.28.1-01/etc
vim nexus-default.properties

###########################配置如下

# Jetty section
application-port=8081
application-host=0.0.0.0

image-20210326202528170

5.登录

nexus 2 的默认账号密码 admin:默认密码admin123登录; nexus 3第一次启动后 nexus3 会自动生成admin 的密码,存在admin.password

image-20210326205307574

1
2
bash复制代码查看密码
~ » vim /Users/caining/Documents/applications/nexus-3.28.1-01-mac/sonatype-work/nexus3/admin.password

3866aa0a-39e2-4e14-90bb-4a31c6c303e1

image-20210326210024703

6.首次登录后设置密码

7.停止

1
2
3
bash复制代码~ » /Users/caining/Documents/applications/nexus-3.28.1-01-mac/nexus-3.28.1-01/bin/nexus stop
Shutting down nexus
Stopped.

gradle 脚本打包sdk并上传Maven repository

一、在工程下新建 maven-push.gradle 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
groovy复制代码apply plugin: 'maven'

uploadArchives {
repository(url: maven.address) { authentication(userName: maven.username, password: maven.password) }
pom.groupId = maven.MAVENGROUPID
pom.artifactId = ARTIFACT
pom.version = maven.appLibVersion
pom.packaging = 'aar'
pom.project {
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
}
}

二、配置根gralde配置

1
2
3
4
5
6
7
8
9
10
11
12
13
groovy复制代码1.新建 config.gradle
2.apply from: 'config.gradle'
3.
ext {
maven = [
address : "http://localhost:8081/repository/maven-releases/",//maven 仓库地址
username : "admin",
password : "123456",
appLibVersion : "1.0.6",//打包版本号
buildMAVEN : true ,//是否打开upload功能,为宿主工程准备、、最小侵入宿主工程
MAVENGROUPID : "com.cnn.sdk"//打包groupId 这里统一设置,如需要不同,请module中单独设置
]
}

三 、gradle ext 是什么?

  1. ext属性是ExtensionAware类型的一个特殊的属性,本质是一个Map类型的变量;
  2. 使用ext属性的好处可以在任何能获取到rootProject的地方访问这些属性
  3. gradle.properties 与 ext 的不同?由于ext 结构是map,所以可以分组,比 gradle.properties 好用一些,一些系统级的配置可以放gradle.properties ,用户基本的配置可以放ext 比如版本号啥的。

四、module 子 gradle.properties 增加artifact名字

  1. 新建module gradle.properties
  2. ARTIFACT= artifact—name // 这个不能随意起,起名规则后面讲

五、sdk-module 子 build.gradle 增加配置

1
2
3
4
5
6
groovy复制代码try {
if (maven.buildMAVEN){//这里是true 在宿主工程里,会访问不到而报错
apply from: '../maven-push.gradle'
}
}catch (all){
}

以上,执行 uploadArchives task 就会打包并upload 到仓库

1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码> Task :module01:uploadArchives
> Task :module02:writeReleaseAarMetadata
> Task :module02:bundleReleaseAar
> Task :module02:uploadArchives

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.5/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 781ms
43 actionable tasks: 41 executed, 2 up-to-date
10:46:48 AM: Task execution finished 'uploadArchives'.

image-20210330105751560
Module01 添加依赖implementation project(path: ‘:module02’),这时候再执行uploadArchives。

重点:抛出问题:如果模块间依赖,以上脚本在全量打包时会报错 如下

image-20210330123054236

原因:module01 依赖是本地包,所以报错

image-20210330134953074

如何解决?

  1. 思路1:先传module02 再修改module01依赖为线上包,再传module01 ,这个思路可行,但如果遇到module 过多,执行起来过于麻烦,这里不再演示,感兴趣的同学可自己执行。

相互依赖打包优化

  1. 问题 :填坑对于有互相依赖的包如何处理?对于关联依赖 sdk 如何一键 build 并上传Maven仓库
  2. 以上存在的问题 :以上脚本虽然初步完成了sdk 打包并上传Maven仓库,但有问题存在,加入sdk 子module 关联依赖时,子包不发布,主包无法打包成功,那么除了手动一步一步上传外,有没有办法一键上传?
  3. 优化思路:动态替换打包后groupId 和 version(由于是动态替换,这里的工程moudle 的名称应该与gradle.properties配置的artifact 保持一致)
  4. 关键脚本如下:
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
groovy复制代码 pom.withXml {
def childs = asNode().children()
childs.each {
child ->
print("childName->"+child.name().toString()+"\n")
if (child.name().toString().contains('dependencies')) {
child.children().each {
print("dependencies->"+it.text().toString()+"\n")
if (it.text().toString().contains("unspecified")) {
def iterator = it.children().iterator()
def remove = false
while (iterator.hasNext()) {
def node = iterator.next()
if (node.name().toString().contains('groupId')) {
iterator.remove()
}
if (node.name().toString().contains('version')) {
iterator.remove()
remove = true
}
}
if (remove) {
it.appendNode('version', maven.appLibVersion)
it.appendNode('groupId', maven.MAVENGROUPID)
}
print("\n return ->"+it.text().toString()+"\n")
}
}

}
}
}

宿主通过仓库引入sdk

引入方式也很简单

1
2
3
4
groovy复制代码dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "groupId:artifactId:version"//此处对应上传的maven 仓库的信息
}

通过宿主工程优雅的调试sdk

通过仓库引入的第三方sdk 在宿主工程里调试,很不方便,虽然可以断电,但源码无法修改,使得在宿主程序中,遇到问题,查问题时,非常不方便,那么有没有好的办法,在本地开发时,不使用仓库包,而是直接引用源码呢?上线时,通过修改配置,引用远程仓库。

通过gradle脚本控制引用源码or仓库

我们知道android module引入模块,是在settings.gradle中,如下:

Module常规引入

1
groovy复制代码include ':sdka'

参数控制动态引入

image-20210401135541636

  1. 宿主根gradle.properties 增加控制参数boolean useLocalLib = true/false
  2. 通过settings.gradle配置动态控制是否使用仓库
1
2
3
4
5
6
7
8
groovy复制代码if (useLocalLib.toBoolean()){
print("sdk调试使用,线上环境忽略以下内容")
include(':module01')
include(':module02')
print("path---->"+getRootDir().getParent())
project(':module01').projectDir = new File(getRootDir().getParent(),"/MavenPackage/module01")
project(':module02').projectDir = new File(getRootDir().getParent(),"/MavenPackage/module02")
}
  1. 同理,宿主工程引入lib时 ,用gradle.properties-useLocalLib字段控制引用仓库or本地module
1
2
3
4
5
6
7
groovy复制代码dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
if (useLocalLib.toBoolean())
implementation project(":module01")
else
implementation 'com.cnn.sdk:module01:1.0.0'
}

以上,可以在宿主工程里,通过参数userLocalSDK,达到引入sdk源码的目的,源码并不在宿主git 下;完成优雅的调试sdk 的目的。

总结

  • 通过Maven仓库引入的好处:组件隔离,业务隔离,减少侵入等
  • 使用Nexus搭建Maven 私有仓库,以及常用命令 初始密码等,以及使用
  • 通过gradle plugin: ‘maven’ 打包并upload to Maven Repository
  • 宿主工程引用时,动态控制指向,是否打开源码,达到调试sdk源码的目的,并且git分

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

高性能轻量化物联网

发表于 2021-04-09

安装方法

  1. 双击 aero-iot-light-install.bat将轻量化方案安装成服务
  2. 双击 aero-iot-light-remove.bat卸载iot-light服务
  3. 打开cmd管理员界面net start iot-light启动轻量化物联网
  4. net stop iot-light关闭轻量化物联网
    image.png
  5. 动态加载地址:http://localhost:47731/swagger-ui.html 需要提前将jar包放入D:\sdk\jars文件夹中
  6. web界面地址: http://localhost:47731

轻量化方案架构图架构图 (1).png

组件

  1. 接收组件:主要是用于和传感器交互,并将传感器上报上来的数据转换成我们需要的数据,并通过消息组件上传给处理组件
  2. 处理组件:主要作用是将接收组件处理好的数据入库
  3. web组件:依赖于tomcat,提供业务系统调用接口
  4. 消息组件:依赖高性能队列——Disruptor

实现功能:

  • 与传感器交互
  • sdk动态加载
  • 数据入库
  • 提供tcp、udp、mqtt交互
  • 一键部署成windows服务
  • 集成web容器
  • #单元测试
  • #动态配置
  • #集成docker

消息组件Disruptor

(由于时间关系,一下大部分内容摘抄置tech.meituan.com/2016/11/18/…

共享

下图是计算的基本结构。L1、L2、L3分别表示一级缓存、二级缓存、三级缓存,越靠近CPU的缓存,速度越快,容量也越小。所以L1缓存很小但很快,并且紧靠着在使用它的CPU内核;L2大一些,也慢一些,并且仍然只能被一个单独的CPU核使用;L3更大、更慢,并且被单个插槽上的所有CPU核共享;最后是主存,由全部插槽上的所有CPU核共享。

69ce8ffbe7a4ebee01c377dac174842d433755.png
当CPU执行运算的时候,它先去L1查找所需的数据、再去L2、然后是L3,如果最后这些缓存中都没有,所需的数据就要去主内存拿。走得越远,运算耗费的时间就越长。所以如果你在做一些很频繁的事,你要尽量确保数据在L1缓存中。

另外,线程之间共享一份数据的时候,需要一个线程把数据写回主存,而另一个线程访问主存中相应的数据。
下面是从CPU访问不同层级数据的时间概念:
image.png
可见CPU读取主存中的数据会比从L1中读取慢了近2个数量级。

缓存行

Cache是由很多个cache line组成的。每个cache line通常是64字节,并且它有效地引用主内存中的一块儿地址。一个Java的long类型变量是8字节,因此在一个缓存行中可以存8个long类型的变量。

CPU每次从主存中拉取数据时,会把相邻的数据也存入同一个cache line。

在访问一个long数组的时候,如果数组中的一个值被加载到缓存中,它会自动加载另外7个。因此你能非常快的遍历这个数组。事实上,你可以非常快速的遍历在连续内存块中分配的任意数据结构。
不过,这种免费加载也有一个坏处。设想如果我们有个 long 类型的变量 a,它不是数组的一部分,而是一个单独的变量,并且还有另外一个 long 类型的变量 b 紧挨着它,那么当加载 a 的时候将免费加载 b。

伪共享

如果一个 CPU 核心的线程在对 a 进行修改,另一个 CPU 核心的线程却在对 b 进行读取。
当前者修改 a 时,会把 a 和 b 同时加载到前者核心的缓存行中,更新完 a 后其它所有包含 a 的缓存行都将失效,因为其它缓存中的 a 不是最新值了。
而当后者读取 b 时,发现这个缓存行已经失效了,需要从主内存中重新加载。

多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

避免伪共享

对于伪共享,一般的解决方案是,增大数组元素的间隔使得由不同线程存取的元素位于不同的缓存行上,以空间换时间
比如:

1
2
3
4
class复制代码    volatile long x;
long p1, p2, p3, p4, p5, p6, p7;
volatile long y;
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

定制Spark SQL 一种轻量级实现方案

发表于 2021-04-09

图片

Spark SQL是Apache Spark中的最重要的功能之一。在 SQL的使用上,Spark SQL和其它适用于大规模离线数据的SQL引擎 (例如Presto/Apache Hive) 是相似的。除了SQL之外,Apache Spark还提供了更为灵活的DataFrame API。

Spark SQL和Spark DataFrame API有着不同的使用场景。

  • Spark DataFrame API的表达能力比Spark SQL更强,对于有良好编程基础和代码抽象能力的工程师来说,使用DataFrame来解决复杂的数据处理问题更加便捷;
  • Spark SQL的语法几乎和Apache Hive的SQL语法几乎一致。绝大部分数据处理需求,使用Spark SQL就能够满足。使用DataFrame API,我们需要创建一个Scala工程,编译代码并打包,最终通过spark-submit提交到集群;而Spark SQL无需构建生成中间产物,就可以通过JDBC等方式提交到集群。

我们需要定制Spark SQL

但是使用Spark SQL,我们总会遇到各种极端情况需要处理,比如:

“我有一个200列的表,我想把其中的两列排除掉,把剩下的198列保存到一个新的表里面。”

如果使用Spark DataFrame API,这个问题可以这样解决:

图片

但是用SQL,把198个列名直接放在SQL语句里面,不具备可读性,且无法应对字段增加的情况:

select col_1, col_2, col_3, col_4, col_5, col_6, col_7, col_8, col_9, …

对于这个问题,Spark SQL有一种兼容HiveQL的解决方案,需要开启配置项,才能使用如下可读性极差的语法:

set spark.sql.parser.quotedRegexColumnNames=true;

select (col\_to\_exclude\_1)?+.+ from tbl

上面是从所有字段中排除一个字段的方法,如果有人问我,如何排除两个字段,我恐怕无法回答这个问题。

在Tubi,我们采用定制Spark SQL语法的方式解决这个问题:

// select all columns excluding col\_to\_exclude\_1

select all_columns_except(“tbl”, “col_to_exclude_1”)

// select all columns excluding col\_to\_exclude\_1 and col\_to\_exclude\_2

select all_columns_except(“tbl”, “col_to_exclude_1”, “col_to_exclude_2”)

对于一些Spark SQL中易用性非常差的语法,使用宏的方式,可以很方便地规避。我们也可以使用宏来对Spark SQL的既有功能做增强,比如

select * from json.s3://bucket/dir/dt=2020-12-12/

如上语法是Spark SQL内置的功能,但很多时候,我们的数据并没有按照日期分目录组织,而是所有文件都在一个目录下:

s3://bucket/2020-12-12-13-00-00.json.gz

s3://bucket/2020-12-12-13-01-00.json.gz

s3://bucket/2020-12-12-13-02-00.json.gz

…

s3://bucket/2020-12-13-00-00-00.json.gz

s3://bucket/2020-12-13-00-00-01.json.gz

这个时候,我们希望使用如下语法访问2020年12月12日当天下午一点的所有数据:

select * from json.s3://bucket/dir/2020-12-12-13\*

但Spark SQL并不支持这样的语法,我们也可以使用自定义语法的方式来解决这个问题。

但是我们不想修改Spark SQL源代码,在Tubi维护一个定制过的Spark版本

为了定制Spark SQL的语法,我们采用了一种非常轻量级的方式,来解决这个问题:

图片

Tubi Spark SQL是在Tubi使用的能够被Spark SQL Parser解析的SQL。

在第一阶段,所有的宏都会被展开,在Tubi Spark SQL中,宏以UDF的语法形式存在,这样保证了Tubi Spark SQL的语法是能够被Spark SQL Parser解析的。值得注意的是,宏只是借用了UDF的语法形式,宏的执行是在第一阶段发生的。宏被展开之后,我们将获得一个新的Spark SQL语句。UDF可以接受column作为参数,而宏只能接受常量,通常情况下,UDF会被执行上亿次,而宏只是会在第一阶段被执行一次。

在第二阶段,通过对语法树的分析,我们可以识别Spark SQL的各种模式,比如如果是drop database或者drop table,我们就将这类SQL路由到ForbiddenDropCommand,而ForbiddenDropCommand什么都不做,只是告诉用户,这类SQL在这个SQL执行器里面无法执行。对于常规的SparkSQL,就路由到SparkSQLCommand执行,对于其它类型的SQL,我们也提供了一些必要的定制。比如,Delta Lake目前已经提供了丰富的编程接口,但是很多编程接口并没有对应的SQL语法可供使用,我们通过识别相关模式,可以将符合这些SQL模式的语法,通过Delta中丰富的编程接口实现。

案例分析:all_columns_except

对于all_columns_except这个例子,实际的执行过程如下:

// 假定tbl这个表有六个字段:col1, col2, col3, col4, col5, col6

select all_columns_except(“tbl”, “col1”, “col5”) from tbl

// —> 第一阶段:宏展开

select col2, col3, col4, col6 from tbl

// —> 第二阶段:路由到SparkSQLCommand执行

案例分析:tubi_json

对于json支持正则表达式这个例子,实际的执行过程如下:

select * from tubi_json.s3://bucket/dir/2020-12-13\*

// –> 第一阶段:宏展开(里面没有宏,所以SQL保持不变)

select * from tubi_json.s3://bucket/dir/2020-12-13\*

// –> 第二阶段:路由到TubiJSONCommand,并执行相关逻辑

  1. 从 s3://bucket/dir/2020-12-13\* 加载数据并为这些数据创建临时表temp\_vew
  1. 执行 spark.sql(“select * from temp_view”)

通过这种方式,我们就实现了对Spark SQL的定制,且具备以下优势:

  1. 不需要修改Spark源代码,维护自己的Spark定制版本;
  2. 不需要自定义Antlr 4的语法文件,复用既有的Spark SQL Parser和语法定义;
  3. 在Spark的基础上,无痛实现和升级各种定制语法的功能。

Antlr 4生成的Parser代码非常复杂,基于这些生成代码做研发,代码很难维护

在这个问题中,最麻烦的事情莫过于做宏的替换和SQL的模式识别。

对于宏的替换而言,只要了解Antlr 4中和Rewriter相关的API,问题就可以迎刃而解。

对于模式识别而言,我们需要分析抽象语法树,将符合特定模式的SQL路由到特定的Command执行。Antlr 4提供了两种模式来访问从SQL构建的抽象语法树:Listener和Visitor。Spark Catalyst模块所使用的是Visitor模式。对于Apache Spark这样的大型项目来说,Visitor模式是适用的,但是对于我们来说,无论是Vistor模式还是Listener模式,都过于复杂。如果是一次性的抽象树分析,我们还可以使用Antlr支持的XPath,采用XPath这种方式的和采用正则表达式做文本处理的缺陷是类似的,诘屈聱牙且难以维护。

Scala标准库中的集合,提供了大量易用的算子,比如map/filter/count/find等。Apache Spark的API设计也受到了Scala标准库中这些算子的影响,无论是偏底层的RDD还是应用层DataFrame,都会实现这些算子。

对于从SQL构建的抽象语法树,我们也可以设计类似的算子:

图片

比如,判断一个SQL中是否存在tubi_json.s3://xxxx/yyyy\*这种模式,基于上面的算子,相关代码如下,简洁且易于维护:

图片

对于这个话题,在上一次Scala Meetup中,我们已经使用四则运算表达式化简这个例子,完整的讲解了其中的设计实现和技术细节。本文不再赘述,感兴趣的读者阅读《2020-09 Online Scala Meetup之观众心得》的第三部分,相关视频回放也可以在“比图科技”的官方Bilibili帐号找到。

另外,此次我在Datafun的年终大会的大数据架构论坛会有更详细的一些分享,也欢迎大家一起交流:

图片

图片

本文作者:沈达@Tubi

审校:杨宇佳@Tubi

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

分布式任务队列Celery入门与进阶 分布式任务队列Cele

发表于 2021-04-09

分布式任务队列Celery入门与进阶

一、简介

Celery是由Python开发、简单、灵活、可靠的分布式任务队列,其本质是生产者消费者模型,生产者发送任务到消息队列,消费者负责处理任务。Celery侧重于实时操作,但对调度支持也很好,其每天可以处理数以百万计的任务。特点:

  • 简单:熟悉celery的工作流程后,配置使用简单
  • 高可用:当任务执行失败或执行过程中发生连接中断,celery会自动尝试重新执行任务
  • 快速:一个单进程的celery每分钟可处理上百万个任务
  • 灵活:几乎celery的各个组件都可以被扩展及自定制

应用场景举例:

  • 1.web应用:当用户在网站进行某个操作需要很长时间完成时,我们可以将这种操作交给Celery执行,直接返回给用户,等到Celery执行完成以后通知用户,大大提好网站的并发以及用户的体验感。
  • 2.任务场景:比如在运维场景下需要批量在几百台机器执行某些命令或者任务,此时Celery可以轻松搞定。
  • 3.定时任务:向定时导数据报表、定时发送通知类似场景,虽然Linux的计划任务可以帮我实现,但是非常不利于管理,而Celery可以提供管理接口和丰富的API。

二、架构&工作原理

  Celery由以下三部分构成:消息中间件(Broker)、任务执行单元Worker、结果存储(Backend),如下图:

img

工作原理:

  1. 任务模块Task包含异步任务和定时任务。其中,异步任务通常在业务逻辑中被触发并发往消息队列,而定时任务由Celery Beat进程周期性地将任务发往消息队列;
  2. 任务执行单元Worker实时监视消息队列获取队列中的任务执行;
  3. Woker执行完任务后将结果保存在Backend中;

消息中间件Broker

  消息中间件Broker官方提供了很多备选方案,支持RabbitMQ、Redis、Amazon SQS、MongoDB、Memcached 等,官方推荐RabbitMQ。

任务执行单元Worker

  Worker是任务执行单元,负责从消息队列中取出任务执行,它可以启动一个或者多个,也可以启动在不同的机器节点,这就是其实现分布式的核心。

结果存储Backend

  Backend结果存储官方也提供了诸多的存储方式支持:RabbitMQ、 Redis、Memcached,SQLAlchemy, Django ORM、Apache Cassandra、Elasticsearch。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

超全golang面试题合集+golang学习指南+golan

发表于 2021-04-09

后续文章和内容会不断更新到 github项目 中,欢迎关注。

目录(善用Ctrl+F)

基础入门

新手

  • Golang开发新手常犯的50个错误

数据类型

  • 连nil切片和空切片一不一样都不清楚?那BAT面试官只好让你回去等通知了。
  • golang面试题:字符串转成byte数组,会发生内存拷贝吗?
  • golang面试题:翻转含有中文、数字、英文字母的字符串
  • golang面试题:拷贝大切片一定比小切片代价大吗?
  • map不初始化使用会怎么样
  • map不初始化长度和初始化长度的区别
  • map的iterator是否安全?能不能一边delete一边遍历?
  • 字符串不能改,那转成数组能改吗,怎么改
  • 怎么判断一个数组是否已经排序
  • 普通map如何不用锁解决协程安全问题
  • array和slice的区别
  • golang面试题:json包变量不加tag会怎么样?
  • 零切片、空切片、nil切片是什么
  • slice深拷贝和浅拷贝
  • map触发扩容的时机,满足什么条件时扩容?
  • map扩容策略是什么
  • 自定义类型切片转字节切片和字节切片转回自动以类型切片
  • make和new什么区别
  • slice ,map,chanel创建的时候的几个参数什么含义
  • 线程安全的map怎么实现

流程控制

  • 昨天那个在for循环里append元素的同事,今天还在么?
  • golang面试官:for select时,如果通道已经关闭会怎么样?如果只有一个case呢?

进阶

包管理

  • 学go mod就够了!

优化

  • golang面试题:怎么避免内存逃逸?
  • golang面试题:简单聊聊内存逃逸?
  • 给大家丢脸了,用了三年golang,我还是没答对这道内存泄漏题
  • 内存碎片化问题
  • chan相关的goroutine泄露的问题
  • string相关的goroutine泄露的问题
  • 你一定会遇到的内存回收策略导致的疑似内存泄漏的问题
  • sync.Pool的适用场景
  • go1.13sync.Pool对比go1.12版本优化点

并发编程

  • golang面试题:对已经关闭的的chan进行读写,会怎么样?为什么?
  • golang面试题:对未初始化的的chan进行读写,会怎么样?为什么?
  • sync.map 的优缺点和使用场景
  • sync.Map的优化点

包

  • 常用官方包说明
  • 常用第三方包说明
  • 常用框架
  • 完整标准库列表
  • 优秀的第三方库
    • 音频和音乐
    • 数据结构:Go中的通用数据结构和算法
    • 分布式系统:Go中的通用数据结构和算法
    • 电子邮件:实现电子邮件创建和发送的库和工具
    • 嵌入式脚本语言:在go代码中嵌入其他语言
    • 错误处理
    • 处理文件和文件系统的库
    • 金融:会计和财务软件包
    • 游戏开发:游戏开发相关库
    • 地理位置:地理相关的位置信息和工具库
    • 编译器相关:转到其他语言
    • Goroutines:用于管理和使用Goroutines的工具
    • 图形界面:用于构建GUI应用程序的库
    • 图片:用于处理图像的库
    • 物联网:物联网设备编程库
    • JSON格式:用于处理JSON的库
    • 机器学习:常用机器学习库
    • 微软办公软件
    • 自然语言处理
    • 网络:与网络各层配合使用的库
    • 视频:用于处理视频的库

高级特性

  • golang面试题:能说说uintptr和unsafe.Pointer的区别吗?
  • golang 面试题:reflect(反射包)如何获取字段 tag?为什么 json 包不能导出私有变量的 tag?
  • 协程和线程的差别
  • 垃圾回收的过程是怎么样的?
  • 什么是写屏障、混合写屏障,如何实现?
  • 开源库里会有一些类似下面这种奇怪的用法:var _ io.Writer = (*myWriter)(nil),是为什么?
  • GMP模型
  • 协程之间是怎么调度的
  • gc的stw是怎么回事
  • 利用golang特性,设计一个QPS为500的服务器
  • 为什么gc会让程序变慢
  • 开多个线程和开多个协程会有什么区别
  • 两个interface{} 能不能比较
  • 必须要手动对齐内存的情况
  • go栈扩容和栈缩容,连续栈的缺点
  • golang怎么做代码优化
  • golang隐藏技能:怎么访问私有成员

问题排查

  • trace
  • pprof

源码阅读

  • sync.map
  • net/http
  • mutex
  • channel
  • context
  • select实现原理
  • main函数背后的启动过程
  • 内存管理
  • GC垃圾回收
  • timer

汇编

  • 汇编入门
  • 推荐书籍
  • 视频教程

实践常用工具

  • mysql建表语句转golang struct
  • json转golang struct
  • toml转golang struct
  • yaml转golang struct

其他

常用官方包

  • fmt - 实现格式化的输入输出操作,其中的fmt.Printf()和fmt.Println()是开发者使用最为频繁的函数。
  • io - 实现了一系列非平台相关的IO相关接口和实现,比如提供了对os中系统相关的IO功能的封装。我们在进行流式读写(比如读写文件)时,通常会用到该包。
  • bufio - 它在io的基础上提供了缓存功能。在具备了缓存功能后, bufio可以比较方便地提供ReadLine之类的操作。
  • strconv - 提供字符串与基本数据类型互转的能力。
  • os - 本包提供了对操作系统功能的非平台相关访问接口。接口为Unix风格。提供的功能包括文件操作、进程管理、信号和用户账号等。
  • sync - 它提供了基本的同步原语。在多个goroutine访问共享资源的时候,需要使用sync中提供的锁机制。
  • flag - 它提供命令行参数的规则定义和传入参数解析的功能。绝大部分的命令行程序都需要用到这个包。
  • encoding/json - JSON目前广泛用做网络程序中的通信格式。本包提供了对JSON的基本支持,比如从一个对象序列化为JSON字符串,或者从JSON字符串反序列化出一个具体的对象等。
  • http - 通过http包,只需要数行代码,即可实现一个爬虫或者一个Web服务器,这在传统语言中是无法想象的。

常用第三方包

  • 数据库操作 - github.com/jinzhu/gorm
    github.com/go-xorm/xor…
  • 搜索es - github.com/olivere/ela…
  • rocketmq操作 - github.com/apache/rock…
  • rabbitmq 操作 - github.com/streadway/a…
  • redis 操作 - github.com/go-redis/re…
  • etcd 操作 - github.com/coreos/etcd…
  • kafka - github.com/Shopify/sar… github.com/bsm/sarama-…
  • excel 操作 - github.com/360EntSecGr…
  • ppt 操作 - golang.org/x/tools/cmd…
  • go-svg 操作 - github.com/ajstarks/sv…
  • go 布隆过滤器实现 - github.com/AndreasBrie…
  • json相关 - github.com/bitly/go-si…
  • LRU Cache实现 - github.com/bluele/gcac… github.com/hashicorp/g…
  • go运行时函数替换 - github.com/bouk/monkey…
  • toml - github.com/toml-lang/t… github.com/naoina/toml…
  • yaml - github.com/go-yaml/yam…
  • viper - github.com/spf13/viper…
  • go key/value存储 - github.com/etcd-io/bbo…
  • 基于ringbuffer的无锁golang workpool - github.com/Dai0522/wor…
  • 轻量级的协程池 - github.com/ivpusic/grp…
  • 打印go的详细数据结构 - github.com/davecgh/go-…
  • 基于ringbuffer实现的队列 - github.com/eapache/que…
  • 拼音 - github.com/go-ego/gpy …
  • 分词 - github.com/go-ego/gse …
  • 搜索 - github.com/go-ego/riot…
  • windows COM - github.com/go-ego/ceda…
  • session - github.com/gorilla/ses…
  • 路由 - github.com/gorilla/mux…
  • websocket - github.com/gorilla/web…
  • Action handler - github.com/gorilla/han…
  • csrf - github.com/gorilla/csr…
  • context - github.com/gorilla/con…
  • 过滤html标签 - github.com/grokify/htm…
  • 可配置的HTML标签过滤 - github.com/microcosm-c…
  • 根据IP获取地理位置信息 - github.com/ipipdotnet/…
  • html转markdown - github.com/jaytaylor/h…
  • goroutine 本地存储 - github.com/jtolds/gls …
  • 彩色输出 - github.com/mgutz/ansi
  • 表格打印 - github.com/olekukonko/…
  • reflect 更高效的反射API - github.com/modern-go/r…
  • msgfmt (格式化字符串,将%更换为变量名) - github.com/modern-go/m…
  • 可取消的goroutine - github.com/modern-go/c…
  • 深度拷贝 - github.com/mohae/deepc…
  • 安全的类型转换包 - github.com/spf13/cast
  • 从文本中提取链接 - github.com/mvdan/xurls
  • 字符串格式处理(驼峰转换) - godoc.org/github.com/…
  • 文本diff实现 - github.com/pmezard/go-…
  • uuid相关 - github.com/satori/go.u… github.com/snluu/uuid
  • 去除UTF编码中的BOM - github.com/ssor/bom
  • 图片缩放 - github.com/nfnt/resize
  • 生成 mock server - github.com/otokaze/moc…
  • go 性能上报到influxdb - github.com/rcrowley/go…
  • go zookeeper客户端 - github.com/samuel/go-z…
  • go thrift - github.com/samuel/go-t…
  • MQTT 客户端 - github.com/shirou/mqtt…
  • hbase - github.com/tsuna/gohba…
  • go 性能上报到influxdb - github.com/rcrowley/go…
  • go 性能上报到prometheus - github.com/deathowl/go…
  • ps utils - github.com/shirou/gops…
  • 小数处理 - github.com/shopspring/…
  • 结构化日志处理(json) - github.com/sirupsen/lo…
  • 命令行程序框架 cli - github.com/urfave/cli
  • 命令行程序框架 cobra - github.com/spf13/cobra

必看项目

  • gin - github.com/olivere/ela… - 轻量级web框架,很多公司都是基于它进行魔改
  • beego - github.com/beego/beego - 也是web框架,比较全能
  • kratos - github.com/go-kratos/k… - bilibili开源的微服务框架,b站出品必属于精- 品
  • TiDB - github.com/pingcap/tid… - 见识过mysql性能瓶颈之后你会想要选择的一款数据库

其他优秀的开源工具分类

音频和音乐

  • EasyMIDI - EasyMidi是一个简单可靠的库,用于处理标准Midi文件(SMF)。
  • flac - 支持FLAC流的Native Go FLAC编码器/解码器。
  • gaad - 本机Go AAC比特流解析器。
  • go-sox - 用于go的libsox绑定。
  • go_mediainfo - 用于go的libmediainfo绑定。
  • gosamplerate - 用于go的libsamplerate绑定。
  • id3v2 - 用于Go的快速,稳定的ID3解析和编写库。
  • malgo - 迷你音频库。
  • minimp3 - 轻量级MP3解码器库。
  • mix - 为音乐应用程序基于序列转到本地音频混合器。
  • mp3 - Native Go MP3解码器。
  • music-theory - Go中的音乐理论模型。
  • Oto - 在多个平台上播放声音的低级库。
  • PortAudio - 用于PortAudio音频I / O库的绑定。
  • portmidi - 绑定PortMidi。
  • taglib - 为taglib绑定。
  • vorbis - “本机” Go Vorbis解码器(使用CGO,但没有依赖项)。
  • waveform - Go程序包,能够从音频流生成波形图像。

数据结构

  • algorithms - 算法和数据结构。CLRS研究。
  • binpacker - 二进制打包程序和解包程序可帮助用户构建自定义二进制流。
  • bit - 具有额外的位旋转功能的Golang设置数据结构。
  • bitset - 实现位集的Go包。
  • bloom - 在Go中实现的Bloom过滤器。
  • bloom - Golang Bloom过滤器实现。
  • boomfilters - 用于处理连续无界流的概率数据结构。
  • concurrent-writer - 高并发直接替换bufio.Writer。
  • conjungo - 一个小型,强大而灵活的合并库。
  • count-min-log - 执行Count-Min-Log草图:使用近似计数器进行近似计数(类似于Count-Min草图,但使用较少的内存)。
  • crunch - Go包实现了用于轻松处理各种数据类型的缓冲区。
  • cuckoofilter - Cuckoo过滤器:是Go中实现的计数布隆过滤器的很好替代。
  • deque - 高度优化的双端队列。
  • deque - 快速的环形缓冲区双端队列(双端队列)。
  • dict - Go的类似Python的字典(dict)。
  • encoding - Go的整数压缩库。
  • go-adaptive-radix-tree - 自适应基数树的 Go实现。
  • go-datastructures - 有用,高性能和线程安全的数据结构的集合。
  • go-ef - Elias-Fano编码的Go实现。
  • go-geoindex - 内存中的地理索引。
  • go-mcache - 快速内存键:值存储/缓存库。指针缓存。
  • go-rquad - 具有有效点定位和邻居发现功能的区域四叉树。
  • gocache - 具有多个存储(内存,memcache,redis等),可链接,可加载,指标缓存等的完整Go缓存库。
  • goconcurrentqueue - 并发FIFO队列。
  • gods - 数据结构。容器,集合,列表,堆栈,地图,BidiMap,树,HashSet等。
  • gofal - Go的小数api。
  • golang-set - Go的线程安全和非线程安全高性能集。
  • goset - Go的有用的Set集合实现。
  • goskiplist - Go中的跳过列表实现。
  • gota - Go的数据框,序列和数据整理方法的实现。
  • hide - ID类型,将其编组进/出哈希以防止将ID发送给客户端。
  • hilbert - Go程序包,用于在空间填充曲线(例如Hilbert和Peano曲线)之间映射值。
  • hyperloglog - HyperLogLog实施,具有稀疏,LogLog-Beta偏差校正和TailCut空间减少功能。
  • iter - C ++ STL迭代器和算法的实现。
  • levenshtein - Levenshtein距离和相似性度量标准,具有可自定义的编辑费用和通用前缀的类似于Winkler的奖金。
  • levenshtein - 在Go中计算levenshtein距离的实现。
  • mafsa - 具有最小完美散列的MA-FSA实现。
  • merkletree - merkle树的实现,可对数据结构的内容进行有效且安全的验证。
  • mspm - 用于信息检索的多字符串模式匹配算法。
  • null - 可空转到类型,可以被编组/解组到/从JSON。
  • parsefields - 用于解析类似JSON的日志的工具,以收集唯一的字段和事件。
  • pipeline - 具有扇入和扇出的管线的实现。
  • ptrie - 前缀树的实现。
  • remember-go - 缓存慢速数据库查询的通用接口(由redis,memcached,ristretto或内存支持)。
  • ring - 围棋实现了高性能,线程安全的布隆过滤器。
  • roaring - 实施压缩位集的软件包。
  • set - 使用LinkedHashMap的围棋设置简单的数据结构实现。
  • skiplist - 非常快的Go Skiplist实施。
  • skiplist - Go中的跳过列表实现。
  • timedmap - 具有过期的键/值对的地图。
  • treap - 使用树堆的持久快速排序的地图。
  • trie - Go中的Trie实现。
  • ttlcache - 内存中的LRU字符串接口{}映射,其中包含golang的到期时间。
  • typ - 空类型,安全的原始类型转换和从复杂结构中获取值。
  • willf/bloom - Go包实现Bloom过滤器。

分布式系统

  • celeriac - 用于在Go中添加支持以交互和监视Celery工作者,任务和事件的库。
  • consistent - 具有受限负载的一致哈希
  • dht - BitTorrent Kademlia DHT实施。
  • digota - grpc电子商务微服务。
  • dot - 使用操作转换/ OT进行分布式同步。
  • doublejump - 改进后的Google的跳转一致性哈希。
  • dragonboat - Go中功能齐全的高性能多组Raft库。
  • drmaa - 基于DRMAA标准的集群调度程序的作业提交库。
  • dynamolock - DynamoDB支持的分布式锁定实现。
  • dynatomic - 将DynamoDB用作原子计数器的库。
  • emitter-io - 使用MQTT,Websockets和love构建的高性能,分布式,安全和低延迟的发布-订阅平台。
  • flowgraph - 基于流的编程包。
  • gleam - 用纯围棋和Luajit快速和可扩展的分布式的map / reduce系统,具有Luajit的高性能结合Go的高并发,单独运行或分发。
  • glow - 易于使用的可扩展的分布式大数据处理,Map-Reduce,DAG执行,全部在纯Go中进行。
  • go-health - health-用于在服务中启用异步依赖项运行状况检查的库。
  • go-jump - Google的“ Jump”一致性哈希函数的端口。
  • go-kit - 支持服务发现,负载平衡,可插拔传输,请求跟踪等的微服务工具包
  • go-sundheit - 建立用于支持为golang服务定义异步服务运行状况检查的库。
  • gorpc - 简单,快速和可扩展的RPC库,可实现高负载。
  • grpc-go - gRPC的Go语言实现。基于HTTP / 2的RPC。
  • hprose - 十分新颖的RPC库,现在支持25种以上的语言。
  • jsonrpc - jsonrpc软件包可帮助实现JSON-RPC 2.0。
  • jsonrpc - JSON-RPC 2.0 HTTP客户端实现。
  • KrakenD - 具有中间件的超高性能API网关框架。
  • liftbridge - NATS的轻量级,容错消息流。
  • micro - 可插拔的microService工具箱和分布式系统平台。
  • NATS - 用于微服务,IoT和云本机系统的轻量级高性能消息传递系统。
  • outboxer - Outboxer是一个实现库模式的go库。
  • pglock - PostgreSQL支持的分布式锁定实现。
  • raft - HashiCorp的Raft共识协议的Golang实现。
  • raft - 围棋实施筏一致协议,由CoreOS的。
  • rain - BitTorrent客户端和库。
  • redis-lock - 使用Redis的简化分布式锁定实现。
  • resgate - 用于构建REST,实时和RPC API的实时API网关,其中所有客户端都可以无缝同步。
  • ringpop-go - Go应用程序的可扩展,容错应用程序层分片。
  • rpcx - 分布式可插拔RPC服务框架,例如阿里巴巴Dubbo。
  • sleuth - 用于在HTTP服务之间进行无主p2p自动发现和RPC的库(ZeroMQ)。
  • tendermint - 高性能中间件,用于使用Tendermint共识和区块链协议将以任何编程语言编写的状态机转换为拜占庭容错复制状态机。
  • torrent - BitTorrent客户端软件包。

电子邮件

  • chasquid - 用Go编写的SMTP服务器。
  • douceur - CSS内衬为您的HTML电子邮件。
  • email - 用于Go的强大而灵活的电子邮件库。
  • go-dkim - DKIM库,用于签名和验证电子邮件。
  • go-imap - 用于客户端和服务器的IMAP库。
  • go-message - Internet消息格式和邮件消息的流库。
  • go-premailer - Go中HTML邮件的内联样式。
  • go-simple-mail - 使用SMTP保持活动状态和两个超时发送电子邮件的非常简单的程序包:连接和发送。
  • Hectane - 提供HTTP API的轻型SMTP客户端。
  • hermes - Golang软件包,可生成干净的响应式HTML电子邮件。
  • mailchain - 将加密的电子邮件发送到用Go编写的区块链地址。
  • mailgun-go - Go库,用于使用Mailgun API发送邮件。
  • MailHog - 通过Web和API界面进行电子邮件和SMTP测试。
  • SendGrid - SendGrid的Go库,用于发送电子邮件。
  • smtp - SMTP服务器协议状态机。

嵌入式脚本语言

  • anko - 用Go语言编写的可编写脚本的解释器。
  • binder - 转到基于gopher-lua的 Lua绑定库。
  • cel-go - 具有渐进式输入功能的快速,便携式,非图灵完整表达评估。
  • expr - 可以评估表达式的引擎。
  • gentee - 可嵌入的脚本编程语言。
  • gisp - Go中的简单LISP。
  • go-duktape - Go的Duktape JavaScript引擎绑定。
  • go-lua - Lua 5.2 VM到纯Go的端口。
  • go-php - Go的PHP绑定。
  • go-python - 与CPython C-API的幼稚go绑定。
  • golua - Lua C API的绑定。
  • gopher-lua - 用Go编写的Lua 5.1 VM和编译器。
  • gval - 用Go编写的高度可定制的表达语言。
  • ngaro - 可嵌入的Ngaro VM实现,支持在Retro中编写脚本。
  • otto - 用Go编写的JavaScript解释器。
  • purl - Go中嵌入的Perl 5.18.2。
  • tengo - 用于Go的字节码编译脚本语言。

错误处理

  • emperror - Go库和应用程序的错误处理工具和最佳实践。
  • errlog - 可破解的软件包,用于确定错误的负责任的源代码(以及其他一些快速调试功能)。可插入任何现成的记录器。
  • errors - 下拉更换为标准库的错误包和github.com/pkg/errors。提供各种错误处理原语。
  • errors - 提供简单错误处理原语的软件包。
  • errors - 简单golang错误处理与分类元。
  • errorx - 具有堆栈跟踪,错误组成等的功能丰富的错误包。
  • Falcon - 一个简单但功能强大的错误处理软件包。
  • go-multierror - Go(golang)软件包,用于将错误列表表示为单个错误。
  • tracerr - 带有堆栈跟踪和源代码片段的Golang错误。
  • werr - 错误包装程序为Go中的错误类型创建了一个包装程序,该包装程序捕获了调用它的文件,行和堆栈。

文件

  • afero - Go的文件系统抽象系统。
  • afs - Go的抽象文件存储(mem,scp,zip,tar,云:s3,gs)。
  • bigfile - 文件传输系统,支持使用http api,rpc调用和ftp客户端管理文件。
  • checksum - 计算大型文件的消息摘要,例如MD5和SHA256。
  • flop - 文件操作库,旨在与GNU cp镜像功能奇偶校验。
  • go-csv-tag - tag-使用标签加载csv文件。
  • go-decent-copy - 复制human文件。
  • go-exiftool - ExifTool的Go绑定,这是众所周知的库,用于从文件(图片,PDF,office,…)提取尽可能多的元数据(EXIF,IPTC等)。
  • go-gtfs - 在go中加载gtfs文件。
  • notify - 具有简单API的文件系统事件通知库,类似于os / signal。
  • opc - 为Go加载Open Packaging Conventions(OPC)文件。
  • parquet - 读取和写入 parquet文件。
  • pdfcpu - PDF 处理器。
  • skywalker - 一种软件包,允许一个人轻松地同时通过文件系统。
  • stl - 读取和写入STL(立体光刻)文件的模块。并发读取算法。
  • tarfs - tar文件FileSystem interface接口的实现。
  • vfs - 跨多种文件系统类型(例如os,S3和GCS)的Go的一组可插拔,可扩展且自以为是的文件系统功能。

金融

  • accounting - golang的货币和货币格式。
  • currency - 高性能和准确的货币计算包。
  • decimal - 任意精度定点十进制数字。
  • go-finance - Go中的综合金融市场数据。
  • go-finance - 金融功能库,用于货币时间价值(年金),现金流量,利率转换,债券和折旧计算。
  • go-finance - 获取汇率,通过VIES检查增值税号和检查IBAN银行帐号的模块。
  • go-money - Fowler的Money模式的实现。
  • ofxgo - 查询OFX服务器和/或解析响应(使用示例命令行客户端)。
  • orderbook - 匹配引擎的限价订单在Golang。
  • techan - 具有高级市场分析和交易策略的技术分析库。
  • transaction - 以多线程模式运行的嵌入式帐户嵌入式事务数据库。
  • vat - 增值税号验证和欧盟增值税率。

游戏开发

  • Azul3D - 用Go语言编写的3D游戏引擎。
  • Ebiten - Go中死的简单2D游戏库。
  • engo - Engo是用Go语言编写的开源2D游戏引擎。它遵循实体组件系统范式。
  • g3n - Go 3D游戏引擎。
  • GarageEngine - 用Go语言编写的2D游戏引擎,可在OpenGL上使用。
  • glop - Glop(权力游戏库)是一个相当简单的跨平台游戏库。
  • go-astar - A 路径查找算法的Go实现。
  • go-collada - Go包,用于Collada文件格式。
  • go-sdl2 - Simple DirectMedia Layer的 Go绑定。
  • go3d - 用于Go的面向性能的2D/3D数学软件包。
  • gonet - 使用golang实现的游戏服务器框架。
  • goworld - 可扩展的游戏服务器引擎,具有空间实体框架和热插拔功能。
  • Leaf - 轻量级游戏服务器框架。
  • nano - 重量轻,设备,高性能的基于golang游戏服务器架构。
  • Oak - Pure Go游戏引擎。
  • Pitaya - 可扩展的游戏服务器框架,具有群集支持和通过C SDK的iOS,Android,Unity等客户端库。
  • Pixel - Go中的手工制作2D游戏库。
  • raylib-go - 去绑定raylib,简单和易于使用的库,以了解电子游戏编程。
  • termloop - Go的基于终端的游戏引擎,建立在Termbox之上。

地理位置

  • geocache - 适用于基于地理位置的应用程序的内存中缓存。
  • geoserver - geoserver是Go软件包,用于通过GeoServer REST API操纵GeoServer实例。
  • gismanager - 将 GIS数据(矢量数据)发布到PostGIS和Geoserver。
  • osm - 用于读取,编写和使用OpenStreetMap数据和API的库。
  • pbf - OpenStreetMap PBF golang编码器/解码器。
  • S2 geometry - Go中的S2几何库。
  • Tile38 - 具有空间索引和实时地理围栏的地理位置数据库。
  • WGS84 - 库坐标转换和变换(ETRS89,OSGB36,NAD83,RGF93,网络墨卡托UTM)。

编译器

  • c4go - 将C代码转换为Go代码。
  • f4go - 将FORTRAN 77代码转换为Go代码。
  • gopherjs - 从Go到JavaScript的编译器。
  • llgo - Go的基于LLVM的编译器。
  • tardisgo - Golang转换为CPP / CSharp / Java / JavaScript转译器。

Goroutines

  • ants - 用于golang的高性能goroutine池。
  • artifex - Golang使用基于工作程序的分派的简单内存中作业队列。
  • async - 一种异步执行功能的安全方法,以防万一。
  • breaker - 使执行流程可中断的灵活机制。
  • cyclicbarrier - 用于golang的CyclicBarrier。
  • go-floc - 轻松编排goroutine。
  • go-flow - 控制goroutine的执行顺序。
  • go-tools/multithreading - 使用带有简单API的轻量级库管理goroutine池。
  • go-trylock - 支持Golang的读写锁的TryLock。
  • go-waitgroup - sync.WaitGroup与错误处理和并发控制类似。
  • gohive - Go的高性能和易于使用的Goroutine池。
  • gollback - 异步简单函数实用程序,用于管理闭包和回调的执行。
  • GoSlaves - 简单和异步Goroutine池库。
  • goworker - goworker是基于Go的后台工作者。
  • gowp - gowp是并发限制goroutine池。
  • gpool - 管理可调整大小的上下文感知goroutine池以绑定并发。
  • grpool - 轻巧的Goroutine池。
  • Hunch - 预感提供功能,如:All,First,Retry,Waterfall等等,这使得异步流控制更加直观。
  • oversight - 监督是Erlang监督树的完整实现。
  • parallel-fn - 并行运行功能。
  • pool - 有限的消费者goroutine池或无限制的goroutine池,以便更轻松地处理和取消goroutine。
  • queue - 为您提供sync.WaitGroup类似的队列组可访问性。帮助您节流和限制goroutine,等待所有goroutine结束等等。
  • routine - 具有上下文和支持的例程控制:Main,Go,Pool和一些有用的Executors。
  • semaphore - 基于通道和上下文的具有锁定/解锁操作超时的信号量模式实现。
  • semaphore - 基于CAS的快速可调整大小的信号量实现(比基于通道的信号量实现更快)。
  • stl - 基于软件交易内存(STM)并发控制机制的软件交易锁。
  • threadpool - Golang线程池实现。
  • tunny - 线程池golang。
  • worker-pool - goworker是一个简单的Go异步工作池。
  • workerpool - Goroutine池,它限制了任务执行的并发性,而不是排队的任务数。

图形界面

  • app - 打包以使用GO,HTML和CSS创建应用的程序。支持:MacOS,Windows正在开发中。
  • fyne - 为Go设计的跨平台本机GUI,使用EFL呈现。支持:Linux,macOS,Windows。
  • go-astilectron - 使用GO和HTML / JS / CSS(由Electron支持)构建跨平台GUI应用。
  • go-gtk - GTK的绑定。
  • go-sciter - Go绑定:用于现代桌面UI开发的可嵌入HTML / CSS / script引擎。跨平台。
  • gotk3 - GTK3的绑定。
  • gowd - 使用GO,HTML,CSS和NW.js进行快速简单的桌面UI开发。跨平台。
  • qt - Go的Qt绑定(支持Windows / macOS / Linux / Android / iOS / Sailfish OS / Raspberry Pi)。
  • ui - Go的平台本地GUI库。跨平台。
  • Wails - 使用内置OS HTML渲染器的HTML UI的Mac,Windows,Linux桌面应用程序。
  • walk - Go的Windows应用程序库工具包。
  • webview - 具有简单双向JavaScript绑定的跨平台Webview窗口(Windows / macOS / Linux)。
  • go-appindicator - libappindicator3 C库的Go绑定。
  • gosx-notifier - Go的OSX桌面通知库。
  • mac-activity-tracker - OSX库,用于通知计算机上的任何(可插入)活动。
  • mac-sleep-notifier - golang中的OSX睡眠/唤醒通知。
  • robotgo - Go本机跨平台GUI系统自动化。控制鼠标,键盘等。
  • systray - 跨平台的Go库,用于在通知区域中放置图标和菜单。
  • trayhost - 跨平台的Go库,用于在主机操作系统的任务栏中放置一个图标。

图片

  • bild - 纯Go中图像处理算法的集合。
  • bimg - 使用libvips进行快速有效的图像处理的小包装。
  • cameron - Go的头像生成器。
  • canvas - 将矢量图形转换为PDF,SVG或光栅图像。
  • darkroom - 具有可变存储后端的图像代理和侧重于速度和弹性的图像处理引擎。
  • geopattern - 从字符串创建漂亮的生成图像图案。
  • gg - 纯Go中的2D渲染。
  • gift - 图像处理过滤器的包装。
  • gltf - 高效,强大的glTF 2.0读取器,写入器和验证器。
  • go-cairo - 用于cairo图形库的绑定。
  • go-gd - GD库的Go绑定。
  • go-nude - Go的裸露检测。
  • go-opencv - 用于OpenCV的绑定。
  • go-webcolors - webcolors库的端口,从Python到Go。
  • gocv - 使用OpenCV 3.3+进行计算机视觉的Go软件包。
  • goimagehash - Go感知图像哈希包。
  • goimghdr - imghdr模块确定Go文件中包含的图像类型。
  • govatar - 用于生成有趣头像的库和CMD工具。
  • image2ascii - 将图像转换为ASCII。
  • imagick - 绑定到ImageMagick的MagickWand C API。
  • imaginary - 用于图像大小调整的快速,简单的HTTP微服务。
  • imaging - 简单的Go图像处理包。
  • img - 选择图像处理工具。
  • ln - Go中的3D线条艺术渲染。
  • mergi - 用于图像处理(合并,裁切,调整大小,水印,动画)的Tool&Go库。
  • mort - 用Go编写的存储和图像处理服务器。
  • mpo - 用于MPO 3D照片的解码器和转换工具。
  • picfit - 用Go编写的图像大小调整服务器。
  • pt - 用Go语言编写的路径跟踪引擎。
  • resize - 使用常见的插值方法为Go 调整图像大小。
  • rez - 在纯Go和SIMD中调整图像大小。
  • smartcrop - 查找适合任何图像和尺寸的优质作物。
  • steganography - 用于LSB隐写术的Pure Go库。
  • stegify - 用于LSB隐写术的Go工具,能够隐藏图像中的任何文件。
  • svgo - 用于SVG生成的Go语言库。
  • tga - 软件包tga是TARGA图像格式的解码器/编码器。

物联网

  • connectordb - 量化自我和物联网的开源平台。
  • devices - IoT设备库套件,针对x / exp / io进行实验。
  • eywa - Project Eywa本质上是一个连接管理器,用于跟踪连接的设备。
  • flogo - Project Flogo是一个用于IoT Edge应用和集成的开源框架。
  • gatt - 盖特是一个围棋包构建低功耗蓝牙外设。
  • gobot - Gobot是机器人技术,物理计算和物联网的框架。
  • huego - 适用于Go的飞利浦Hue扩展客户端库。
  • iot - IoT是用于实现Google IoT Core设备的简单框架。
  • mainflux - 工业物联网消息和设备管理服务器。
  • periph - 外设I / O与低级别的主板设备接口。
  • sensorbee - 用于物联网的轻量级流处理引擎。

JSON格式

  • ajson - 具有JSONPath支持的golang的抽象JSON。
  • gjo - 用于创建JSON对象的小型实用程序。
  • GJSON - 使用一行代码获取JSON值。
  • go-jsonerror - Go-JsonError可让我们轻松创建遵循JsonApi规范的json响应错误。
  • go-respond - Go包,用于处理常见的HTTP JSON响应。
  • gojq - Golang中的 JSON查询。
  • gojson - 从示例JSON自动生成Go(golang)结构定义。
  • JayDiff - 用Go编写的JSON diff实用程序。
  • jettison - 用于Go的高性能,无反射JSON编码器。
  • JSON-to-Go - 将JSON转换为Go结构。
  • json2go - 高级JSON到Go结构转换。提供可以解析多个JSON文档并创建适合所有JSON的结构的包。
  • jsonapi-errors - 根据JSON API错误参考进行绑定。
  • jsonf - 突出显示格式和获取JSON的结构查询的控制台工具。
  • jsongo - Fluent API,可以更轻松地创建Json对象。
  • jsonhal - 简单的Go包,用于将自定义结构编组为HAL兼容的JSON响应。
  • kazaam - 用于JSON文档的任意转换的API。
  • mp - 简单的cli电子邮件解析器。当前,它使用标准输入并输出JSON。

机器学习

  • bayesian - 贝叶斯分类为Golang天真。
  • CloudForest - 快速,灵活,多线程的决策树集合,用于纯Go中的机器学习。
  • eaopt - 进化优化库。
  • evoli - 遗传算法和粒子群优化库。
  • fonet - 用Go编写的深度神经网络库。
  • go-cluster - k模式和k-原型聚类算法的Go实现。
  • go-deep - Go中功能丰富的神经网络库
  • go-fann - 快速人工神经网络(FANN)库的Go绑定。
  • go-galib - 用Go / golang编写的遗传算法库。
  • go-pr - Go lang中的模式识别包。
  • gobrain - 用go语言编写的神经网络
  • godist - 各种概率分布及相关方法。
  • goga - Go的遗传算法库。
  • GoLearn - 用于Go的通用机器学习库。
  • golinear - Go的liblinear绑定。
  • GoMind - Go中的简单神经网络库。
  • goml - Go中的在线机器学习。
  • Goptuna - 用于Go语言编写的黑盒函数的贝叶斯优化框架。一切都会被优化。
  • goRecommend - 用Go编写的推荐算法库。
  • gorgonia - 基于图形的计算库,例如Theano for Go,它提供了用于构建各种机器学习和神经网络算法的原语。
  • gorse - 基于Go编写的协作过滤的离线推荐系统后端。
  • goscore - 用于PMML的Go Scoring API。
  • gosseract - 使用Tesseract C ++库的OCR(光学字符识别)软件包。
  • libsvm - 基于LIBSVM 3.14 libsvm的golang版本衍生作品。
  • neat - 用于增强拓扑神经演化(NEAT)的即插即用,并行Go框架。
  • neural-go - go-在Go中实现的多层感知器网络,通过反向传播进行训练。
  • ocrserver - 一个简单的OCR API服务器,非常容易被Docker和Heroku部署。
  • onnx-go - 转到开放神经网络交换(ONNX)的接口。
  • probab - 概率分布函数。贝叶斯推断。用纯Go语言编写。
  • regommend - 建议和协作过滤引擎。
  • shield - 贝叶斯文本分类器,具有灵活的标记器和Go的存储后端。
  • tfgo - 易于使用的Tensorflow绑定:简化了官方Tensorflow Go绑定的使用。在Go中定义计算图,加载并执行经过Python训练的模型。
  • Varis - Golang神经网络。

金融

  • unioffice - Pure Go库,用于创建和处理Office Word(.docx),Excel(.xlsx)和Powerpoint(.pptx)文档。
  • excelize - Golang库用于读取和写入Microsoft Excel™(XLSX)文件。
  • go-excel - 一个简单而轻便的阅读器,可以将类似于related-db的excel读取为表格。
  • goxlsxwriter - libxlsxwriter的Golang绑定,用于编写XLSX(Microsoft Excel)文件。
  • xlsx - 用于简化在Go程序中读取Microsoft Excel最新版本使用的XML格式的库。
  • xlsx - 在Go程序中快速/安全地读取/更新您现有的Microsoft Excel文件的方法。

自然语言处理

  • getlang - 快速自然语言检测程序包。
  • go-i18n - 用于处理本地化文本的软件包和一个随附工具。
  • go-mystem - CGo与Yandex.Mystem的绑定-俄罗斯形态分析仪。
  • go-nlp - 用于处理离散概率分布的实用程序和其他可用于执行NLP工作的工具。
  • go-pinyin - CN Hanzi至Hanyu拼音转换器。
  • go-stem - 搬运程序阻止算法的实现。
  • go-unidecode - Unicode文本的ASCII音译。
  • go2vec - 用于word2vec嵌入的阅读器和实用程序功能。
  • gojieba - 这是一个围棋实施解霸其中中国分词算法。
  • golibstemmer - 雪球库libstemmer库的绑定,包括porter 2。
  • gotokenizer - 基于字典和Goram语言的Bigram语言模型的标记器。(现在仅支持中文细分)
  • gounidecode - Go的Unicode音译器(也称为unidecode)。
  • gse - 进行有效的文本分割;支持英语,中文,日语等。
  • icu - CGO结合为ICU4C C库检测和转换功能。保证与版本50.1兼容。
  • kagome - 用纯Go语言编写的JP形态分析仪。
  • libtextcat - libtextcat C库的Cgo绑定。保证与2.2版兼容。
  • MMSEGO - 这是MMSEG的GO实现,它是中文分词算法。
  • nlp - 从字符串中提取值,并用nlp填充您的结构。
  • nlp - 支持LSA(潜在语义分析)的自然语言处理库。
  • paicehusk - Paice / Husk提取算法的Golang实现。
  • petrovich - 彼得罗维奇(Petrovich)是库,在给定的语法情况下使用俄语名称。
  • porter - 这是Martin Porter的Porter干算法的C实现的相当简单的移植。
  • porter2 - 非常快的Porter 2 提取器。
  • prose - 用于文本处理的库,支持标记化,词性标记,命名实体提取等。仅限英语。
  • RAKE.go - 快速自动关键字提取算法(RAKE)的Go端口。
  • segment - 用于执行Unicode标准附件#29中所述的Unicode文本分段的Go库
  • sentences - 句子标记器:将文本转换为句子列表。
  • shamoji - shamoji是用Go编写的单词过滤程序包。
  • snowball - Go的雪球茎端口(cgo包装器)。提供单词词干提取功能Snowball本机。
  • stemmer - 用于Go编程语言的Stemmer软件包。包括英语和德语词干。
  • textcat - Go软件包,用于基于n-gram的文本分类,并支持utf-8和原始文本。
  • whatlanggo - Go的自然语言检测程序包。支持84种语言和24种脚本(书写系统,例如拉丁语,西里尔字母等)。
  • when - 自然EN和RU语言日期/时间分析器具有可插拔的规则。

网络

  • arp - 包arp实现ARP协议,如RFC 826中所述。
  • buffstreams - 通过TCP流化协议缓冲区数据变得容易。
  • canopus - CoAP客户端/服务器实施(RFC 7252)。
  • cidranger - Go的快速IP到CIDR查找。
  • dhcp6 - 软件包dhcp6实现了DHCPv6服务器,如RFC 3315中所述。
  • dns - 使用DNS的Go库。
  • ether - 用于发送和接收以太网帧的跨平台Go软件包。
  • ethernet - 程序包ethernet实施IEEE 802.3以太网II帧和IEEE 802.1Q VLAN标签的封送处理。
  • fasthttp - 软件包fasthttp是Go的一种快速HTTP实现,比net / http快10倍。
  • fortio - 负载测试库和命令行工具,高级回显服务器和Web UI。允许指定设置的每秒查询负载,并记录延迟直方图和其他有用的统计数据并对其进行图形化。Tcp,Http,gRPC。
  • ftp - 程序包ftp实现RFC 959中所述的FTP客户端。
  • gev - gev是基于Reactor模式的轻量级,快速,无阻塞的TCP网络库。
  • gmqtt - Gmqtt是一个灵活的高性能MQTT代理库,它完全实现了MQTT协议V3.1.1。
  • gnet - gnet是一个高性能的,用纯围棋轻便,非阻塞,事件循环网络库。
  • gNxI - 使用gNMI和gNOI协议的网络管理工具的集合。
  • go-getter - Go库,用于使用URL从各种来源下载文件或目录。
  • go-powerdns - Golang的 PowerDNS API绑定。
  • go-stun - STUN客户端的Go实现(RFC 3489和RFC 5389)。
  • gobgp - 使用Go编程语言实现的BGP。
  • golibwireshark - 软件包golibwireshark使用libwireshark库来解码pcap文件并分析解剖数据。
  • gopacket - Go库,用于使用libpcap绑定进行数据包处理。
  • gopcap - libpcap的包装器。
  • goshark - 软件包goshark使用tshark解码IP数据包并创建数据结构以分析数据包。
  • gosnmp - 用于执行SNMP操作的本机Go库。
  • gosocsvr - 套接字服务器变得简单。
  • gotcp - 用于快速编写tcp应用程序的Go软件包。
  • grab - 用于管理文件下载的软件包。
  • graval - 实验性FTP服务器框架。
  • HTTPLab - HTTPLabs可让您检查HTTP请求并伪造响应。
  • iplib - 受python ipaddress和ruby ipaddr启发而使用IP地址(net.IP,net.IPNet)的库
  • jazigo - Jazigo是用Go语言编写的工具,用于检索多个网络设备的配置。
  • kcp-go - KCP-快速可靠的ARQ协议。
  • kcptun - 基于KCP协议的极其简单和快速的udp隧道。
  • lhttp - 强大的websocket框架,可更轻松地构建IM服务器。
  • linkio - 用于读取器/写入器接口的网络链接速度模拟。
  • llb - 这是代理服务器的非常简单但快速的后端。对于零内存分配和快速响应的快速重定向到预定义域很有用。
  • mdns - Golang中的简单mDNS(多播DNS)客户端/服务器库。
  • mqttPaho - Paho Go客户端提供了一个MQTT客户端库,用于通过TCP,TLS或WebSockets连接到MQTT代理。
  • NFF-Go - 用于快速开发云和裸机(以前的YANFF)的高性能网络功能的框架。
  • packet - 通过TCP和UDP发送数据包。如果需要,它可以缓冲消息和热交换连接。
  • peerdiscovery - Pure Go库,用于使用UDP多播的跨平台本地对等发现。
  • portproxy - 简单的TCP代理,它将不支持它的API添加到CORS支持中。
  • publicip - 软件包publicip返回您的面向公众的IPv4地址(互联网出口)。
  • quic-go - 在纯Go中实现QUIC协议。
  • raw - 包raw允许在设备驱动程序级别为网络接口读取和写入数据。
  • sftp - 程序包sftp实现SSH文件传输协议,如filezilla-project.org/specs/draft…
  • ssh - 用于构建SSH服务器的高级API(包装crypto / ssh)。
  • sslb - 这是一个超级简单的负载均衡器,只是一个实现某种性能的小项目。
  • stun - 实施RFC 5389 STUN协议。
  • tcp_server - 用于更快地构建tcp服务器的Go库。
  • tspool - TCP库使用工作池来提高性能并保护您的服务器。
  • utp - 围棋UTP微传输协议的实现。
  • water - 简单的TUN / TAP库。
  • webrtc - WebRTC API的纯Go实现。
  • winrm - 进入WinRM客户端以在Windows计算机上远程执行命令。
  • xtcp - 具有同步全双工通信,安全关闭,自定义协议的TCP Server Framework。

视频

  • go-astisub - 在GO中处理字幕(.srt,.stl,.ttml,.webvtt,.ssa / .ass,图文电视,.smi等)。
  • go-astits - 在GO中本地解析和解复用MPEG传输流(.ts)。
  • go-m3u8 - Apple m3u8播放列表的解析器和生成器库。
  • goav - FFmpeg的综合Go绑定。
  • gst - GStreamer的绑定。

开源书籍

  • Go palyground - 不用搭建本地 Go 环境,在线就编写 Go 的代码
  • Go实战开发 - 作者是著名的 Go 开源项目 beego 的作者,他的最佳实践非常值得阅读
  • Go Web 编程 - 跟前面一本书作者是同一位,讲的是web开发
  • Go语言标准库 - 对标准库的介绍
  • Go入门指南 - 比较适合新手,内容相对基础一些
  • Go语言圣经 - 书如其名
  • Go语言中文网 - 找对圈子,学的更快
  • 菜鸟教程 - 这个网站非常适合快速上手某门语言
  • Go语言高级编程 - 内容适合进阶
  • go语言原本 - 欧神出品,虽然号称进度只有9.9%/100%,但不妨碍它的优秀,值得一看
  • golang设计模式 - 设计模式 Golang实现,《研磨设计模式》的golang实现
  • Go语言四十二章经 - 可以对比查漏补缺

视频网课

外链问题,到github项目里自取吧哈哈哈。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【译】使用 Kafka、MongoDB 和 Maxwell'

发表于 2021-04-08
  • 原文地址:Building an SQL Database Audit System Using Kafka, MongoDB and Maxwell’s Daemon
  • 原文作者:About the Author
  • 译文出自:掘金翻译计划
  • 本文永久链接:github.com/xitu/gold-m…
  • 译者:joyking7
  • 校对者:lsvih、PassionPenguin

本文要点

  • 审计日志系统的用处远不止存储数据用于审计目的。除了合规性和安全性目的之外,它还能被市场营销团队使用,便于锁定目标用户,也可以用来生成关键的告警。
  • 数据库内置的审计日志功能可能并不够用,并且对于需要处理所有用户场景的情况下,它肯定不是最佳选择。
  • 当前有许多开源工具实现审计日志功能,比如说 Maxwell’s Daemons 和 Debezium,它们能够以最少的基础设施和时间来支持这些需求。
  • Maxwell’s Daemon 能够读取 SQL binlog 并将 binlog event 发送给各种生产者,比如 Kafka、Amazon Kinesis、SQS、Rabbit MQ 。
  • SQL 数据库生成的 binlog 文件必须是 ROW 格式,这样整个配置才能正常工作。

那么假设你正在使用关系型数据库来维护你的事务型数据,并且你需要存储的某些数据的审计跟踪信息只出现在很少的表中。如果你像大多数开发人员那样做,那么最终所使用的方案可能如下所示:

1. 使用数据库审计日志功能

大多数数据库都提供了插件来支持审计日志。这些插件可以很容易地安装和配置,以便于记录数据。但是,这种方式存在以下问题:

  • 成熟完善的审计日志插件一般只有企业版才提供,社区版可能会缺少这些插件。以 MySQL 为例,审计日志插件只有企业版才能使用。值得一提的是,MySQL 社区版的用户仍然可以安装其他来自 MariaDB 或者 Percona 的审计日志插件绕过这个限制。
  • 正如慢查询日志对性能影响 和 MySQL 日志性能影响两篇文章讨论的一样,数据库级别的审计日志会给数据库服务器带来 10-20% 的额外开销。通常来讲,对于高负载的系统,我们可能只想对于慢查询部分启用审计日志,而不是对于所有查询开启。
  • 审计日志会被写入到日志文件中,这些数据不易于搜索。为了数据分析和审计,我们会更想要让审计数据存储成可搜索的格式。
  • 大量审计归档文件会消耗重要的数据库存储,因为它们与数据库存储在相同服务器上。

2. 使用你的应用来负责审计日志

你可以通过下面的方式实现:

a. 更新现有数据之前,复制现有数据到另外一个表中,然后再更新当前表中的数据。

b. 为数据添加一个版本号,每一次更新都将插入一个自增的版本号。

c. 写入到两张数据库表中,其中一张包含最新的数据,另外一张包含审计跟踪信息。

作为设计可扩展系统的一项原则,我们必须要避免多次写入相同的数据,因为这样不仅会降低系统的性能,还会引起数据不同步的问题。

为什么企业需要审计数据?

在开始介绍审计日志系统架构之前,我们首先看一下各种组织对审计日志系统的需求:

  • 合规性和审计:从审计人员的角度来看,他们需要以有意义和上下文相关的方式获取数据。数据库审计日志适合 DBA 团队却不适合审计人员。
  • 对于任何大型软件来说,一项基本要求就是在出现安全漏洞时生成关键的告警,审计日志可以用于实现此目的。
  • 你必须能够回答各种问题,比如谁访问了数据,数据在此之前的状态是什么,在更新的时候都修改了哪些内容,以及内部用户是否滥用了权限等。
  • 还有很重要的一点需要注意,因为审计跟踪信息能有助于识别渗透者,这能够增强对”内部人员”的威慑。人们如果知道自己的行为会被审查,那么他们就不太可能会访问未经授权的数据库或篡改特定的数据。
  • 所有行业,从金融、能源到餐饮服务和公共工程,都需要分析数据访问情况,并定期向各种政府机构提交详细报告。根据《健康保险携带和责任法案》(Health Insurance Portability and Accountability Act, HIPAA)的规定,HIPAA 法案要求医疗服务提供商提供所有接触他们数据记录的每个人的审计跟踪信息,信息要求细致到行级别和记录级别。新的《欧盟通用数据保护条例》(European Union General Data Protection Regulation, GDPR)也有类似的要求。《Sarbanes-Oxley 法案》(Sarbanes-Oxley Act, SOX)对公众企业提出了广泛的会计法规,这些组织需要定期分析数据访问情况并生成详细的报告。

本文中,我将会使用像 Maxwell’s Daemon 和 Kafka 这样的技术为你提供一个可扩展的解决方案,用于管理审计跟踪数据。

问题描述

构建一个独立于应用和数据模型的审计系统,该系统必须兼顾可扩展性与性价比。

架构

重要提示:本系统只适用于使用 MySQL 数据库的情况,并且使用的是基于 ROW 格式的 binlog 日志模式

在我们讨论解决方案细节之前,先让我们快速地了解一下本文所讨论到的每一项技术。

Maxwell’s Daemon

Maxwell’s Daemon(MD)是一个由 Zendesk 开发的开源项目,它会读取 MySQL binlog 并且将 ROW 的更新以 JSON 格式写入到 Kafka、Kinesis 或其他流平台。Maxwell 的运维成本很低,除了 MySQL 和一些在 Maxwell’s Daemon 文档提到的需要写入数据的地方之外,就没有别的需求了。简而言之,MD 是一个数据变更捕获(Change-Data-Capture, CDC)的工具。

市面上有相当多各异的 CDC 工具,比如 Redhat 的 Debezium、Netflix 的 DBLog 和 LinkedIn 的 Brooklyn。CDC 功能可以通过这些工具中的任意一个来实现,但是 Netflix 的 DBLog 和 LinkedIn 的 Brooklyn 是为了满足上述不同的使用场景开发的。但是 Debezium 和 MD 非常相似,可以用来替代我们架构中的 MD。该选择 MD 还是 Debezium,我简单地列出了几个需要考虑的事情:

  • Debezium 只能写入数据到 Kafka,至少这是它所主要支持的生产者。而 MD 支持包括 Kafka、Kinesis、 Google Cloud Pub/Sub、 SQS、Rabbit MQ和 Redis 在内的各种生产者。
  • MD 支持用户自己编写生产者并对其进行配置,详情可参考 Maxwell’s Daemon 生产者文档。
  • Debezium 的优势在于它可以从多种数据源读取变化数据,比如 MySQL、MongoDB、PostgreSQL、SQL Server、Cassandra、DB2和 Oracle。在新增数据源上,Debezium 十分灵活,而 MD 目前只支持 MySQL 数据源。

Kafka

Apache Kafka 是一个开源的分布式事件流平台,可用于实现高性能数据管道、流分析、数据集成和关键任务应用。

MongoDB

MongoDB 是一个通用的、基于文档的分布式数据库,它是为现代应用开发者和云时代构建的。我们使用 MongoDB 只是为了进行讲解,你也可以选择其他方案,比如 S3,也可以选择其他时序数据,如 InfluxDB 或 Cassandra。

下图展示了审计跟踪方案的数据流图:

数据流图

图 1 数据流图
审计跟踪管理系统中应包括以下步骤:

  1. 程序执行数据库写入、更新或删除操作。
  2. SQL 数据库以 ROW 格式为以上操作生成 binlog,这涉及到 SQL 数据库的相关配置。
  3. Maxwell’s Daemon 轮询 SQL binlog,读取新增内容并将其写入 Kafka 主题(Topic)中。
  4. 消费者应用轮询 Kafka 主题来读取数据并进行处理。
  5. 消费者将处理吼的数据写入到新的存储中。

设置

为了配置简单,我们要尽可能的使用 Docker 容器。如果你还没有在你的电脑安装 Dockcer,可以考虑安装 Docker Desktop。

MySQL 数据库

  1. 本地运行 mysql 服务器,下面的命令会在 3307 端口启动一个 mysql 容器。
1
bash复制代码docker run -p 3307:3306 -p 33061:33060 --name=mysql83 -d mysql/mysql-server:latest
  1. 如果是刚刚新安装的,我们并不知道 root 密码,运行下面的命令在控制台打印密码。
1
bash复制代码docker logs mysql83 2>&1 | grep GENERATED
  1. 如果有需要,可以登录容器并修改密码。
1
2
bash复制代码docker exec -it mysql83 mysql -uroot -p
alter user 'root'@'localhost' IDENTIFIED BY 'abcd1234'
  1. 为了安全的考虑,mysql docker 容器默认不允许外部应用连接。我们需要运行下面命令进行修改。
1
sql复制代码update mysql.user set host = '%' where user='root';
  1. 退出 mysql 提示窗并重启 docker 容器。
1
bash复制代码docker container restart mysql83
  1. 重新登录 mysql 客户端,运行下面的命令为 Maxwell’s Daemon 创建用户。关于该步骤的详情,可以参考 Maxwell’s Daemon 快速指南。
1
2
3
4
5
6
7
8
9
bash复制代码docker exec -it mysql83 mysql -uroot -p
set global binlog_format=ROW;
set global binlog_row_image=FULL;
CREATE USER 'maxwell'@'%' IDENTIFIED BY 'pmaxwell';
GRANT ALL ON maxwell.* TO 'maxwell'@'%';
GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'maxwell'@'%';
CREATE USER 'maxwell'@'localhost' IDENTIFIED BY 'pmaxwell';
GRANT ALL ON maxwell.* TO 'maxwell'@'localhost';
GRANT SELECT, REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'maxwell'@'localhost';

Kafka 消息代理

搭建 Kafka 是一件十分简单的事情,从该链接下载 Kafka。

运行下面的命令:

提取 Kafka 文件

1
2
bash复制代码tar -xzf kafka_2.13-2.6.0.tgz
cd kafka_2.13-2.6.0

启动 Kafka 当前所需要的 Zookeeper

1
bash复制代码bin/zookeeper-server-start.sh config/zookeeper.properties

在另外一个终端启动 Kafka

1
bash复制代码bin/kafka-server-start.sh config/server.properties

在另外一个终端创建一个 Kafka 主题

1
bash复制代码bin/kafka-topics.sh --create --topic maxwell-events --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1

上述命令会启动 Kafka 消息代理并在它内部创建一个叫做 “maxwell-events“ 的主题。

要推送消息到这个 Kafka 主题,可以在新的终端运行下面的命令:

1
bash复制代码bin/kafka-console-producer.sh --topic maxwell-events --broker-list localhost:9092

上述命令会给我们一个对话框,可以输入消息内容,敲击回车键将消息发送给 Kafka。

消费来自 Kafka 主题的消息:

1
bash复制代码bin/kafka-console-producer.sh --topic quickstart-events --broker-list localhost:9092

Maxwell’s Daemon

从该链接下载 Maxwell’s Daemon。
将其解压并运行如下命令:

1
bash复制代码bin/maxwell --user=maxwell   --password=pmaxwell --host=localhost --port=3307  --producer=kafka     --kafka.bootstrap.servers=localhost:9092 --kafka_topic=maxwell-events

这样我们就启动了 Maxwell 来监控前面搭建好的数据库 binlog。当然我们也可以只监控一些数据库或表,更多信息请参考 Maxwell’s Daemon 配置文档。

测试设置

为了测试设置是否能正常工作,我们可以连接 MySQL 并向一张表中插入一些数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sql复制代码docker exec -it mysql83 mysql -uroot -p

CREATE DATABASE maxwelltest;

USE maxwelltest;

CREATE TABLE Persons (
PersonId int NOT NULL AUTO_INCREMENT,
LastName varchar(255),
FirstName varchar(255),
City varchar(255),
primary key (PersonId)

);

INSERT INTO Persons (LastName, FirstName, City) VALUES ('Erichsen', 'Tom', 'Stavanger');

在另外一个终端运行下面命令:

1
bash复制代码bin/kafka-console-consumer.sh --topic maxwell-events --from-beginning --bootstrap-server localhost:9092

在终端中,应该能够看到如下内容:

1
JSON复制代码{"database":"maxwelltest","table":"Persons","type":"insert","ts":1602904030,"xid":17358,"commit":true,"data":{"PersonId":1,"LastName":"Erichsen","FirstName":"Tom","City":"Stavanger"}}

正如所看到的,Maxwell’s Daemon 捕获了数据库插入事件,并向 Kafka 主题写入了一个包含事件详细信息的 JSON 字符串。

搭建 MongoDB

要在本地运行 MongoDB,运行下面的命令:

1
bash复制代码docker run --name mongolocal -p 27017:27017 mongo:latest

Kafka 消费者

Kafka-Consumer 代码可以从 Github 项目 kmaxwell 获取,下载源码并参考 README 文档来了解如何运行。

最终测试

最后,我们完成了整个安装过程。登录 MySQL 数据库并运行任意插入、删除或更新命令,如果配置正确的话,我们可以在 MongoDB 的 auditlog 数据库看到相应的数据条目。我们可以开心地进行审计工作了!

总结

本文所描述的系统在实际部署中运行良好,为我们提供了一个用户数据之外的额外数据源,但在使用这种架构之前,有一些事情需要我们注意:

  1. 基础设施成本。这样的设置需要额外的基础设施,数据会经过多次跳转,从数据库到 Kafka 再到另一个数据库,还可能存到备份中,这些都会增加基础设施的成本。
  2. 因为数据会经过多次跳转,审计日志数据无法实时维护,它会存在秒级或分钟级的延迟。我们可能会讨论说“谁需要实时的审计日志数据呢”,但如果你计划使用这些数据进行实时监控,这是你必须要考虑到的。
  3. 在这个架构中,我们捕获了数据变化,而不是谁改变了数据。如果你还关注是哪个用户改变了数据的话,这种设计可能无法直接进行支持。

在强调了这种架构的一些权衡之处后,我想重申一下这种架构的收益来结束本文。主要收益如下:

  • 这样的设计减少了数据库在审计日志方面的性能损耗,并且能够满足传统数据源在市场营销和告警方面的需求
  • 这样的架构易于搭建且稳定,任何组件的任何问题都不会导致数据的丢失。例如,如果 MD 挂掉了,数据依然会被存储在 binlog 文件中,当 Daemon 下次启动时,仍能够从中断地方读取数据。如果 Kafka 消息代理挂掉,MD 能够探测它并停止从 binlog 中读取数据。如果 Kafka 消费者崩溃的话,数据将会被保存在 Kafka 消息代理中。因此,在最坏的情况下,审计日志可能会延迟但不会出现数据丢失。
  • 安装配置过程简单直接,不需要耗费过多开发精力。

关于作者

Vishal Sinha
Vishal Sinha
是一位充满激情的技术专家,对分布式计算和大型可扩展系统有着专业的知识和浓厚的兴趣,他目前在一家行业领先的印度独角兽公司担任技术总监。在 16 年多的软件行业生涯中,他曾在多家跨国公司和初创公司工作,开发过各种大型可扩展系统,并带领过一个由许多软件工程师组成的团队,他十分享受解决复杂问题及尝试各种新技术。

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【译】我希望多年前就知道的 Nginx 概念

发表于 2021-04-08
  • 原文地址:Nginx concepts I wish I knew years ago
  • 原文作者:Aemie Jariwala
  • 译文出自:掘金翻译计划
  • 本文永久链接:github.com/xitu/gold-m…
  • 译者:joyking7
  • 校对者:PassionPenguin、ningzhy3

Nginx 是一个遵循主从架构的 Web 服务器,可以用作反向代理、负载均衡器、邮件代理和 HTTP 缓存。

哇!复杂的术语和混乱的定义,里面充斥着大量令人困惑的词语,对吧?不用担心,我可以帮大家先了解 Nginx 的基本架构和术语,然后我们将安装并创建 Nginx 配置。

迷惑表情.gif

为了让事情变简单,只需要记住:Nginx 是一个神奇的 Web 服务器。

简单来说,Web 服务器就像个中间人。比如你想访问 dev.to,输入地址 https://dev.to,你的浏览器就会找出 https://dev.to 的 Web 服务器地址,然后将其定向到后台服务器,后台服务器会把响应返回给客户端。

代理 vs 反向代理

Nginx 的基本功能是代理,所以现在就需要了解什么是代理和反向代理。

代理

好的,我们有一个或多个客户端、一个中间 Web 服务器(在这种情况下,我们称它为代理)和一个服务器。这其中最主要的事情是服务器不知道哪个客户端正在请求。是不是有点困惑?让我用一张示意图来解释一下。

代理示意图

在这种情况下,客户端 client1 和 client2 通过代理服务器向服务器发送请求 request1 和 request2,后台服务器不会知道 request1 是由 client1 还是 client2 发送的,只会执行操作。

反向代理

用最简单的话来说,反向代理就是把代理的工作反过来。比方说有一个客户端、一个中间 Web 服务器和一个或多个后台服务器。让我们继续通过一张示意图解释吧!

反向代理示意图

在这种情况下,客户端将通过 Web 服务器发送一个请求,Web 服务器会通过一种算法将请求定向到众多服务器的任意一个,其中一种算法是轮询调度(Round-Robin)(最可爱的一个!),然后再将响应通过 Web 服务器返回给客户端。因此在这里,客户端并不知道与之交互的是哪一个后台服务器。

负载均衡

可恶,又是一个新词,但是这个词比较容易理解,因为它是反向代理本身的一个实际应用。

我们先说说基本的区别。在负载均衡中,必须要有两个或者更多的后台服务器,但在反向代理设置中,这不是必须的,它甚至可以只跟单台后台服务器一起使用。

让我们从幕后看一下,如果我们有大量来自客户端的请求,这个负载均衡器会检查每个后台服务器的状态并分配请求的负载,然后将响应更快地发送给客户端。

有状态应用 vs 无状态应用

好的各位,我保证我很快就要讲到 Nginx 代码了,先让我们把所有的基本概念搞清楚!

有状态应用

这个应用程序存储了一个额外的变量,用于保存只适用于单个服务器实例的信息。

有状态应用图例

我的意思是,如果后端服务器 server1 存储了一些信息,那么它不会被存储在 server2 上,因此进行交互的客户端(这里指 Bob)可能得不到想要的结果,因为它可能会与 server1 或者 server2 交互。在这种情况下,server1 将允许 Bob 查看配置文件,但 server2 不会允许。因此,即使有状态应用阻止了许多 API 调用数据库,并且速度更快,但却可能会在不同服务器上导致上述问题。

无状态应用

现在,无状态是对数据库的 API 调用更多,但客户端与不同后台服务器交互时,存在的问题就更少了。。

无状态应用图例

我知道你没有明白我的意思。简单来说,如果我从客户端通过 Web 服务器向比如说后台服务器 server1 发送一个请求,它将向客户端提供一个令牌以用于访问其他任何请求。客户端可以使用令牌并将请求发送给 Web 服务器,该 Web 服务器将请求和令牌一起发送给任意后台服务器,每个服务器都将返回相同的期望输出。

什么是 Nginx?

Nginx 就是 Web 服务器,到目前为止,我一直在整篇博客中使用 Web 服务器这个词,老实说,它就像一个中间人。

Nginx示意图

这张图并不难懂,它只是结合了我到现在为止解释的所有概念。在这张图中,我们有 3 台分别运行在 3001、3002、3003 端口的后台服务器,这些后台服务器共同使用运行在 5432 端口的数据库。

现在,当客户端向 https://localhost(默认 443 端口)发送请求 GET /employees 时,Nginx 将根据算法把这个请求发送给任意一个后台服务器,后台服务器从数据库中获取信息,然后把 JSON 结果发送回 Nginx Web 服务器,Nginx 再发送回客户端。

如果我们要使用诸如轮询调度这样的算法,Nginx 会这样做:比如 client2 发送了一个请求到 https://localhost,那么 Nginx 服务器会先把请求传到 3001 端口,然后把响应返回给客户端。对于另一个请求,Nginx 会把请求传到 3002 端口,以此类推。

这也太多概念了吧!但是到此为止,你应该已经清楚地了解了什么是 Nginx 及其相关术语。现在,我们将继续了解 Nginx 的安装和配置。

安装过程

终于到这一步了!如果你能理解 Nginx 概念并看到了代码这部分,那真是棒棒哒!

十分感动.gif

好的,老实说,在任何操作系统上安装 Nginx 都只需要一行命令。我是 Mac OSX 用户,所以会基于它来写命令。但对于 Ubuntu 和 Windows 以及其他 Linux 发行版,也有类似的命令。

1
ruby复制代码$ brew install Nginx

只需要一行命令,你的系统就已经安装上 Nginx 了!非常 Amazing!

运行 So easy!😛

运行下面的命令来检查 Nginx 是否在你的系统上运行起来了,又是非常简单的一步。

1
2
3
ruby复制代码$ nginx 
# OR
$ sudo nginx

运行完命令之后,使用你最喜欢的浏览器访问 http://localhost:8080/,你将在屏幕上看到下面的画面!

Nginx网页

基本配置和示例

好的,我们将通过一个示例来展示 Nginx 的神奇之处。首先,在本地机器上创建如下的目录结构:

1
2
3
4
5
6
7
8
9
10
11
css复制代码.
├── nginx-demo
│ ├── content
│ │ ├── first.txt
│ │ ├── index.html
│ │ └── index.md
│ └── main
│ └── index.html
└── temp-nginx
└── outsider
└── index.html

同时,在 html 和 md 文件中写上基本的上下文内容。

我们要达到什么效果?

在这里,我们有两个单独的文件夹 nginx-demo 和 temp-nginx,每个文件夹都包含静态 HTML 文件。我们将专注于在一个共同端口上运行这两个文件夹,并设置我们喜欢的规则。

现在回到正轨。我们可以通过修改位于 /usr/local/etc/nginx (译者注:默认安装路径)路径下的 nginx.conf 文件,实现对 Nginx 默认配置的任何改动。另外,我的系统中有 Vim,所以我将用 Vim 进行修改,你也可以自由使用所选的编辑器。

1
2
shell复制代码$ cd /usr/local/etc/nginx
$ vim nginx.conf

这将打开一个默认的 Nginx 配置文件,但我真的不想使用它的默认配置。因此,我通常会复制这个配置文件,然后对原文件进行修改。在这里我们也这样做。

1
2
shell复制代码$ cp nginx.conf copy-nginx.conf
$ rm nginx.conf && vim nginx.conf

现在打开一个空文件,我们将给它添加我们的配置。

  1. 添加一个基本配置。添加 events {} 是必须的,因为对于 Nginx 架构来讲,它通常被用来表示 Worker 的数量。我们在这里使用 http 来告诉 Nginx,我们将使用 OSI 模型 的第 7 层。

在这里,我们让 Nginx 监听 5000 端口,并指向 /nginx-demo/main 文件夹下的静态文件。

1
2
3
4
5
6
7
8
9
10
css复制代码  http {

server {
listen 5000;
root /path/to/nginx-demo/main/;
}

}

events {}
  1. 接下来我们将对 /content 和 /outsider URL 添加额外的规则,其中 outsider 将指向第一步中提到的根目录(/nginx-demo)以外的目录。

这里 location /content 表示无论我在子目录中定义了哪一个根目录,content 子 URL 都会被添加到定义的根目录末尾。因此,这里当我指定根目录为 root /path/to/nginx-demo/ 时,仅仅表示我告诉 Nginx 在 http://localhost:5000/path/to/nginx-demo/content/ 向我展示文件夹内静态文件的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bash复制代码  http {

server {
listen 5000;
root /path/to/nginx-demo/main/;

location /content {
root /path/to/nginx-demo/;
}

location /outsider {
root /path/temp-nginx/;
}
}

}

events {}

好酷!现在 Nginx 不仅限于定义根 URL,还可以设置规则,以便于我可以阻止客户端访问某些文件。
3. 我们将在定义的主服务器中写入一条附加规则,用来阻止访问任何 .md 文件。我们可以在 Nginx 中使用正则表达式,规则定义如下:

1
2
3
kotlin复制代码   location ~ .md {
return 403;
}
  1. 最后我们来学习一下流行的命令 proxy_pass。现在我们已经了解了什么是代理和反向代理,这里我们先定义另一个运行在 8888 端口的后台服务器,所以现在我们已经有了 2 个分别运行在 5000 和 8888 端口的后台服务器。

我们要做的是,当客户端通过 Nginx 访问 8888 端口时,将这个请求传到 5000 端口,并向客户端返回响应!

1
2
3
4
5
6
7
8
9
10
11
arduino复制代码   server {
listen 8888;

location / {
proxy_pass http://localhost:5000/;
}

location /new {
proxy_pass http://localhost:5000/outsider/;
}
}

最后一起来看看完整的代码!😁

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
bash复制代码   http {

server {
listen 5000;
root /path/to/nginx-demo/main/;

location /content {
root /path/to/nginx-demo/;
}

location /outsider {
root /path/temp-nginx/;
}

location ~ .md {
return 403;
}
}

server {
listen 8888;

location / {
proxy_pass http://localhost:5000/;
}

location /new {
proxy_pass http://localhost:5000/outsider/;
}
}

}

events {}

通过 sudo nginx 来运行代码。

额外的 Nginx 命令!

  1. 首次启动 Nginx Web 服务器。
1
2
3
ruby复制代码  $ nginx 
#OR
$ sudo nginx
  1. 重新加载正在运行的 Nginx Web 服务器。
1
2
3
ruby复制代码  $ nginx -s reload
#OR
$ sudo nginx -s reload
  1. 关闭正在运行的 Nginx Web 服务器。
1
2
3
ruby复制代码  $ nginx -s stop
#OR
$ sudo nginx -s stop
  1. 查找有哪些 Nginx 进程正在系统中运行
1
perl复制代码  $ ps -ef | grep Nginx

第 4 条命令很重要,当前 3 条命令出现错误时,可以使用第 4 条命令找到所有正在运行的 Nginx 进程,然后 kill 掉这些进程,重新启动 Nginx 服务。

要 kill 一个进程,你需要先知道它的 PID,然后用下面的命令 kill 它:

1
2
3
shell复制代码$ kill -9 <PID>
#OR
$ sudo kill -9 <PID>

在结束这篇文章之前,声明一下我所使用图片和视觉效果来自 Goole 图片和由 Hussein Nasser 提供的 Youtube 教程。

关于 Nginx 的基本认识和配置,我们就讲到这里。如果你对 Nginx 的进阶配置感兴趣,请通过评论告诉我。在此之前,请享受编程的乐趣,探索 Nginx 的魔力!👋

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

并发编程-深入JMM内存模型与Volatile 一、CPU高

发表于 2021-04-08

一、CPU高速缓存的由来

CPU高速发展期间,内存和硬盘的发展速度远远跟不上CPU,这就导致了CPU去内存读写数据的速度相对缓慢。

针对这个问题,CPU厂商在CPU中内置了三级高速缓存(L1、L2、L3)来解决IO速度和CPU速度不匹配的问题,通过三级缓存,减少了CPU与内存的交互。

image.png

寄存器: CPU中的数据存储单元,数量有限

缓存行: 上面所说的三级高速缓存,它们的虽小存储单元叫做缓存行,缓存行的大小通常为64byte,比如说L1的缓存大小是512kb,而缓存行又占64byte,即L1区域有((512*1024)/64)个缓存行。

离CPU越近的速度越快,同时内存越小,即:

  • 按速度快慢排序:寄存器>L1>L2>L3>内存
  • 按内存大小排序:寄存器<L1<L2<L3<内存

CPU读取数据的过程:

  1. 如果取的是寄存器的数据,直接读取即可
  2. 如果取的是L1的数据,会先锁住该数据对应的缓存行,然后把数据放入寄存器,最后解锁。
  3. 如果取的是L2的数据,会先去L1取,L1没有的话,会先锁住该数据在L2对应的缓存行,然后把数据放入L1,再执行上一步读取L1的过程,最后解锁。
  4. 读取L3同理。
  5. 如果取的是内存的数据,会先通知内存加一个总线锁,然后往内存发送读强求,响应的数据存放在L3,再重复上面的步骤,完成之后解除总线锁。

CPU访问内存的局部性原理:

主要是两点:时间和空间

  • 时间局部性:如果一个信息正在被访问,那么近期它可能还会被访问(比如循环、递归等等),通俗理解为热点数据的缓存。
  • 空间局部性:某一个数据被访问了,与之相邻的数据也会被访问到。

下面我们来看一下空间局部性的例子:

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
js复制代码public class MyTest {

private static int arr[][] = new int[1024*1024][6];

static {
for (int i=0;i<1024*1024;i++){
for (int j=0;j<6;j++){
arr[i][j]=j;
}
}
System.err.println("arr二维数组初始化完成");
}

public static void main(String[] args) {
//一行一行的读,地址是连续的,且CPU的空间局限性,CPU读取一行数据只需要访问一次内存,所以快
int sum = 0;
long startTime = new Date().getTime();
for (int i=0;i<1024*1024;i++){
for (int j=0;j<6;j++){
sum+=arr[i][j];
}
}
System.err.println("耗时:"+(new Date().getTime()-startTime)+"ms,结果: "+sum);

//一列一列的读,地址不是连续的,CPU访问一次内存只能拿到一个数据,所以慢
sum = 0;
startTime = new Date().getTime();
for (int i=0;i<6;i++){
for (int j=0;j<1024*1024;j++){
sum+=arr[j][i];
}
}
System.err.println("耗时:"+(new Date().getTime()-startTime)+"ms,结果: "+sum);

}
}

结果:

1
2
3
js复制代码arr二维数组初始化完成
耗时:15ms,结果: 15728640
耗时:152ms,结果: 15728640

二、MESI缓存一致性协议

我们上面说了CPU的高速缓存,那么在多核多CPU的情况下,是不是又会引出缓存一致性的问题?

没错,很久之前的方案是加总线锁,但总线锁是不是就浪费了我们的多核多CPU的处理能力?随后而来的解决方案就是我们要说的MESI缓存一致性协议。

MESI每个字母分别代表四种状态,标记的是缓存行,如下:

  • M(Modified修改):对缓存行有效,说明数据被修改了,和内存中的数据不一致。且该数据只存在于当前缓存行。
  • E(Exclusive独占):对缓存行有效,数据和内存的数据一致。且该数据只存在于当前缓存中。
  • S(Share共享):对缓存行有效,数据和内存的数据一致。且该数据也存在于其他缓存中。
  • I(Invalid无效):对缓存行无效,即该数据为垃圾数据。

我们以 i++为例,来看一下具体是怎么执行的:

单核操作:
CPU从内存读到数据之后,先放到CPU缓存,状态置为E(独占)状态,然后在寄存器中执行i+1操作,把结果返回给CPU缓存,并把i的状态由E(独占)改为M(修改),然后写会主内存,完成之后i又会置为E(独占)状态

image.png

多核操作:

  1. CPU1从内存读到数据i之后,先放到CPU缓存,数据i的状态置为E(独占)状态。
  2. 同时CPU2也从内存中读取数据i, CPU1监测到地址冲突,则CPU1和CPU2上的该数据i都会置为S(共享)状态。
  3. 然后CPU1在寄存器中执行i+1操作,把结果返回给CPU缓存,并把数据i的状态由E(独占)改为M(修改),同时发给总线一个数据已经修改了的消息。
  4. 其他CPU监听到这个消息之后会把对应缓存行的数据置为I(无效)状态,并响应成功(相当于ACK)。
  5. CPU1收到响应成功的消息之后,会把数据写会主内存,完成之后i又会置为E(独占)状态。而其他线程得重新去主内存读取。

image.png

那么大家想想,既然数据状态的切换需要ACK来完成,那是不是可能会因为ACK的时间过长从而导致CPU阻塞呢?

为了避免这种问题,Store Bufferes(存储缓存)被引入使用,CPU把需要写入到内存中的值先写入到Store Bufferes中,然后继续去处理其他指令。当其他CPU都返回了失效确认时,数据才会被最终提交。但是这种优化又会带来另外的问题:

  1. Store Bufferes缓存的时间没办法把控
  2. 当前CPU在执行其他指令时,会继续往Store Bufferes中读数据,而Store Bufferes的数据还没有提交,最终会导致结果有误。

三、JMM内存模型

JMM内存模型全称是Java Memory Model,它并不是真实存在的,只是一种抽象的概念,描述的是一组规则或规范,通过这些规范定义了程序中变量的 访问方式。

  1. JMM规定所有的变量都存储在主内存,主内存是共享区域,所有线程都可以访问
  2. 每个线程都有自己的工作内存,各个线程之间相互隔离,线程对变量的操作必须在工作内存中进行,首先将变量从主内存拷贝到自己的工作内存,然后进行读写操作,操作完成后写会主内存

如图:

image.png

那么大家想一想,如果多个线程操作的是同一个变量,同时往主内存写的话,是不是会存在并发问题?从而导致线程不安全?

JMM提供了八大原子操作来解决这个问题(必须按以下顺序执行)

  1. lock(锁定): 作用于主内存的变量,把该变量标记为线程独占的状态
  2. read(读取): 读取主内存的变量,以便随后的load操作
  3. load(加载):把read的变量放到工作内存中
  4. use(使用): 作用于工作内存的变量,把工作内存的变量传递给CPU的执行引擎
  5. assign(赋值): 作用于工作内存的变量,把执行引擎处理之后的值传递到工作内存
  6. store(存储): 作用于工作内存的变量,把该变量传递到主内存中,以便随后的write操作
  7. write(写入): 作用于工作内存的变量,把store操作的变量从工作内存传递到主内存
  8. unlock(解锁): 作用于主内存的变量,把该变量从锁定的状态释放出来,释放之后才能被其他线程访问

如图:

image.png

我们来看一下并发编程中的可见性,原子性与有序性的问题

  • 原子性:操作不可中断,即时在多线程环境下,操作一旦开始就不会被其他线程影响。
  • 可见性: 一个线程对共享变量的操作,能够被其他线程立马感知到。
  • 有序性: 不管是单线程还是多线程,代码的执行顺序都是依次执行的(指令重排后的就不是依次执行)

那么JMM模型怎么解决并发编程的原子性、可见性、有序性问题呢?

原子性问题:除了JVM自身提供的Atomic操作基本数据类型以外,还可以通过 synchronized和Lock实现原子性。

可见性问题: volatile关键字保证可见性,当一个共享变量被volatile修饰后,它能保证某一个线程修改之后的值能够及时被其他线程看到。synchronized和Lock也可以保证可见性,因为它们保证同一时间只能有一个线程操作共享资源。

有序性问题: volatile关键字保证有序性,synchronized和Lock也可以保证。

Happens-Before原则

如上所说,单纯的靠synchronized和volatile来保证原子性、可见性、有序性,那么开发人员的工作会相当麻烦。我们的JDK爸爸想到了这一点,提供了一套happens-before原则来辅助保证程序执行的原子性、可见性、有序性,具体原则如下:

  1. 程序执行顺序规则:在一个线程内,必须保证按照代码的顺序执行。
  2. 锁规则:同一个锁的情况下,解锁操作必须发生在后一个加锁操作之前。
  3. volatile变量规则:volatile变量的写操作必须发生在对这个变量的读操作之前,这保证了volatile的及时可见性。
  4. 传递性规则:如果操作A发生在操作B之前,而操作B又发生在操作C之前,则可以得出操作A发生在操作C之前
  5. 线程启动规则: 线程的start方法发生在此线程任意操作之前。
  6. 线程中断规则:线程的interrupt方法发生在线程中断之前。
  7. 线程终止规则: 线程中的任意操作都发生在线程终止之前。
  8. 对象终结规则:一个对象的初始化完成(构造函数执行结束)发生在它的finalize()方法开始之前。

了解一下指令重排

java语言规定,只要程序的最终结果与它有序执行的结果相等,那么指令的执行顺序可以与代码不一致,这个过程叫做指令重排。

指令重排有什么意义?

JVM根据CPU的特性(多级缓存、多核处理器)适当的对机器指令进行指令重排,使机器指令更符合CPU的执行特征,最大限度的发挥CPU的作用

指令重排的原则:as-if-serial

意思是不管怎样重排序,单线程的执行结果不能被改变。

四、Volatile关键字

volatile是JVM提供的轻量级的同步机制,有以下两个作用:

  • 保证被volatile修饰的共享变量能及时的被其他线程可见,也就是说当一个线程对volatile修饰的共享变量进行了修改,其他线程能立马感知到。
  • 禁止指令重排序优化

那么volatile的怎么实现这两个功能的呢?

  1. 及时可见性

我们看一段简单的代码:

1
js复制代码    private volatile static int a = 1;

这段代码啊在生成汇编代码时会在volatile修饰的共享变量进行写操作时,JVM会向CPU发出一条Lock前缀指令,而CPU识别到这个Lock前缀指令,就会触发我们上面所说的缓存一致性协议,通过总线的嗅探机制,及时通知其他处理器该共享变量已经被修改了,使得其他处理器的该变量变为失效状态,并且及时把数据写入主内存。

  1. 禁止指令重排序优化

我们都知道,为了性能优化,JMM在不改变正确语义的前提下,会允许编译器和CPU对指令顺序进行重排序,那如果想阻止重排序要怎么办?答案是可以添加内存屏障。

volatile就是通过内存屏障实现了禁止指令重排

Intel硬件级别的内存屏障:

  1. ifence: 是一种Load Barrier读屏障
  2. sfence: 是一种Store Barrier写屏障
  3. mfence: 是一种全能性屏障,具备ifence和sfence的能力
  4. 上面说的Lock前缀:Lock本身不是一种内存屏障,但可以实现类似内存屏障的功能。

不同硬件实现的内存屏障的方式不同,JMM屏蔽了这种硬件的差异,由JVM来为不同硬件平台生成对应的机器码,JVM提供了以下内存屏障:

image.png

java编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序。为了实现volatile的内存语义,JMM会限制特定类型的编译器和处理器重排序,JMM会针对编译器制定volatile重排序规则表:

image.png

“NO”表示禁止重排序。为了实现volatile内存语义时,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎是不可能的,为此,JMM采取了保守策略:

  1. 在每个volatile写操作的前面插入一个StoreStore屏障;
  2. 在每个volatile写操作的后面插入一个StoreLoad屏障;
  3. 在每个volatile读操作的后面插入一个LoadLoad屏障;
  4. 在每个volatile读操作的后面插入一个LoadStore屏障。

需要注意的是:volatile写是在前面和后面分别插入内存屏障,而volatile读操作是在后面插入两个内存屏障

StoreStore屏障:禁止上面的普通写和下面的volatile写重排序;

StoreLoad屏障:防止上面的volatile写与下面可能有的volatile读/写重排序

LoadLoad屏障:禁止下面所有的普通读操作和上面的volatile读重排序

LoadStore屏障:禁止下面所有的普通写操作和上面的volatile读重排序

如图:

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL查询优化(一):如何分析查询性能?

发表于 2021-04-08

查询优化、索引优化和表设计优化是环环相扣的。如果你有丰富的编写MySQL查询语句的经验,你就会知道如何设计表和索引来支持有效的查询。同样的,知晓表设计同样有助于了解表结构如何对查询语句产生影响。因此,即便表设计和索引都设计得很好,但如果查询语句写得很糟糕,那查询的性能也会很糟糕。

在尝试编写快速的查询语句前,务必记住快速都是基于响应时间进行评估的。查询语句是一组由多个子任务组成的大任务,每一个子任务都会消耗时间。为了优化查询,我们需要尽可能地减少子任务的数量,或者让子任务执行得更快。
注:有些时候我们也需要考虑查询对系统其他查询的影响,在这种情况下,还需要尽可能地减少资源消耗。
_
通常,我们可以认为查询的生命周期贯穿于客户端到服务端的整个交互时序图中,包括了查询语句解析、查询计划、执行过程和数据返回到客户端。执行是查询过程中最为重要的一环,包括了从存储引擎获取数据行而发起的大量调用,以及获取数据后的处理,例如分组和排序。

当完成所有这些任务后,查询还会在网络传错、CPU处理、数据统计和策略规划、等待锁、从存储引擎获取数据行的操作中消耗时间。这些调用会在内存操作、CPU操作和I/O操作中消耗时间。在每一种情况中,如果这些操作被滥用、执行次数过多、或过慢,就会导致额外的时间开销。查询优化的目标是避免这些情况——通过消除或减少操作,或者让操作运行更快。

需要注意的是,我们没法绘制一个精确的查询生命周期图,我们的目的是展示理解查询生命周期的重要性,并思考这些环节的耗时。有了这个基础,就能够着手去优化查询语句。

慢查询基础:优化数据获取

查询性能差的最基础的原因是处理了太多的数据。有些查询必须从大量数据中进行筛选,这种情况就没法优化。但这是不太正常的情况。大部分糟糕的查询可以通过访问更少的数据进行优化。下面的两个步骤对分析性能差的查询十分有用:

  1. 找出应用是不是获取了你需要之外的数据。通常这意味着应用获取了太多的数据行或数据列。
  2. 找出MySQL服务器是不是分析了超过需要的行。

检查是否向数据库请求了不必要的数据

有些查询会向数据库服务器请求所需要的数据,然后将这些数据丢弃。这会增加MySQL服务器的工作、加重网络负荷、消耗更多内存和应用服务器的CPU资源。下面是一些典型的错误:

  1. 获取不需要的数据行:一个常见的误区是假设MySQL只提供需要的结果,而不是计算和返回全部的结果集。通常这种错误发生在熟悉其他数据库系统的人身上。这些开发者习惯于使用返回很多行的SELECT语句,然后从中取出前N行,之后不再使用返回的结果集(例如从一个资讯网站获取最近的100篇文章,然后在前端仅仅展示其中的10条)。他们会认为MySQL在拿到10行数据后就会停止查询,而实际MySQL会获取完整的数据集合。然后,客户端或获取全部的数据再将其中的大部分丢弃。最佳的解决方案是在查询中加上LIMIT条件。
  2. 在一个多表联合查询终获取全部列:如果你需要获取恐龙时代这部电影的全部演员,不要像下面那样写你的SQL语句:
1
2
3
4
sql复制代码SELECT * FROM sakila.actor
INNER JOIN sakila.file_actor USING(actor_id)
INNER JOIN sakila.file USING (film_id)
WHERE sakila.film.title = 'Academy Dinosaur';

这会返回参与联合查询的三张表的全部列。更好的做法是,像下面那样写:

1
2
3
4
sql复制代码SELECT sakila.actor.* FROM sakila.actor
INNER JOIN sakila.file_actor USING(actor_id)
INNER JOIN sakila.file USING (film_id)
WHERE sakila.film.title = 'Academy Dinosaur';
  1. 获取全部数据列:在你看到SELECT *这样的查询时,一定要保持怀疑:真的需要全部的列吗?很可能不是的。获取全部的数据列会让覆盖索引失效、增加I/O负担、内存消耗和CPU负荷。有些DBA直接因为这个禁用SELECT *,并且可以减少人员修改表的列后引发的问题。当然,请求不必要的数据并不总是糟糕。在调查中发现,这种方式可以简化开发工作,因为这样可以提高代码的复用性。只要你知道这会影响性能,那会是一个正当的理由。同样的,如果在应用中使用了某些缓存机制,也会提高缓存的命中率。获取和缓存全部对象可以通过运行多个获取部分对象的独立的查询来处理会更好。
  2. 重复获取相同数据:如果粗心的话,很容易在应用中编写获取相同数据的代码。例如,如果你要在评论列表中展示用户个人信息中的头像,你可能再每一条评论都获取一次。更有效的方式是第一次获取后缓存起来直接在评论列表使用。

检查MySQL是不是处理了过多的数据

一旦确定了查询语句没有获取不必要的数据,就可以查找那些在返回结果前处理过多数据的查询。在MySQL中,最简单的查询消耗标准是:

  1. 响应时间
  2. 处理的数据行数量
  3. 返回的数据行数量

这些标准没有一个是完美的查询性能评估手段,但它们大致反映了MySQL执行查询语句时在内部处理过程中获取的数据量和查询运行的速度。这三个标准都在慢查询日志中记录,因此从慢查询日志中去发现数据处理过多的查询是查询优化的最佳实践方式。

响应时间
首先,注意查询响应时间是我们看到的一个表象。实际上,响应时间比我们想象的要更为复杂。响应时间由两部分组成:服务时间和队列时间。服务时间是服务端实际处理查询的时间。队列时间是服务端并没有真正执行查询的那部分时间——它在等待某些资源,例如I/O操作的完成、行锁释放等等。问题在于,你没法准确将响应时间拆分成这两部分——除非你能够单独测量这两部分的时间,而这是很难做到的。最常见和最重要的情形是I/O阻塞和等待锁,但不是百分之百都是这样。

结果就是,响应时间在不同负荷情况下并不是一成不变的。其他的因素,例如存储引擎锁、高并发和硬件都会影响响应时间。因此,当检查响应时间的时候,首先要决定这个响应时间是不是仅仅是这个查询引起的。可以通过计算查询的快速上限估计(QUBE)方法来评估其响应时间:通过检查查询计划和使用的索引,来决定需要的顺序和随机I/O访问操作,然后乘以机器的硬件执行每次操作的时间来评估。通过将全部的时间求和可以评估查询响应慢是因为查询本身引起的还是其他原因。

处理和返回的数据行数量
在分析查询语句时,思考处理行的数量十分有用,因为这样可以直观地知道查询是如何获取我们所需的数据。然而,这对查找糟糕的查询并不是完美的测量工具。并不是所有的行访问都是一致的。更少的行访问速度更快,而从内存中获取数据行比在磁盘获取要快很多。

理想情况下,处理的数据行和返回的数据行是相等的,但是实际上很少会这样。例如,使用联合索引构建返回行时,服务端必须从多个行中获取数据以产生返回的行数据。处理的数据行和返回的数据行的比例通常很小,在1:1到10:1之间,但有时候可能是更大的数量级。

数据行处理和获取类型

当思考查询的代价时,可以考虑从数据表获取单独一行的代价。MySQL使用多种获取方法去查找和返回一行数据。有些需要处理多行,而有些则可能不需要检查直接得到返回结果。

获取数据的方法在EXPLAIN输出结果的type列。包括了全表扫描、索引扫描、范围扫描、唯一索引查找和常量。由于数据读取量依次减少,因此上述的每一种方法都比它之前的要快。我们不需要记住获取类型,但需要理解其中的基本概念。

如果没有好的获取类型,最佳解决问题的方式是增加一个合适的索引。索引使得MySQL检查更少的数据,从而更有效地查询数据行。例如,以下面的简单查询为例:

1
sql复制代码EXPLAIN SELECT * FROM sakila.film_actor WHERE file_id=1;

这个查询会返回10行数据,然后EXPLAIN指令显示了MySQL在idx_fk_film_id索引上使用了ref类型执行查询语句。

1
2
3
4
5
6
7
8
9
10
11
sql复制代码***********************1. row************************
id: 1
select_type: SIMPLE
table:film_actor
type: ref
possile)keys: idx_fk_film_id
key: idx_fk_film_id
key_len: 2
ref: const
rows: 10
Extra:

EXPLAIN指令显示MySQL估计仅仅需要获取10行完成查询。换言之,查询优化器知道如何选择获取类型来让查询更有效。如果查询没有合适的索引会怎么样?MySQL必须使用次优的获取类型,当删除掉表索引后再来看结果。

1
2
3
sql复制代码ALTER TABLE sakila.film_actor DROP FOREIGN KEY fk_film_actor_film;
ALTER TABLE sakila.film_actor DROP DROP KEY idx_fk_film_id;
EXPLAIN SELECT * FROM sakila.film_actor WHERE file_id=1;
1
2
3
4
5
6
7
8
9
10
11
sql复制代码***********************1. row************************
id: 1
select_type: SIMPLE
table:film_actor
type: ALL
possile)keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 5073
Extra: Using where

如同预期的那样,获取类型变成了全表扫描(ALL),MySQL估计需要处理5073行数据才能完成查询。在Extra列中的Using where显示MySQL服务器使用了WHERE条件来丢弃存储引擎读取的其他不符合条件的数据。通常,MySQL会在下面三种方式中使用WHERE条件,效果依次是从好到差:

  1. 通过索引查找操作去除不匹配的数据行,这发生在存储引擎层;
  2. 使用覆盖索引(在Extra列显示是Using index)去避免数据行访问,并在获取到结果后将不符合条件的数据过滤掉。这发生在服务器层,但不需要从数据表读取数据行。
  3. 从数据表获取数据行,然后在过滤掉不匹配的数据(在Extra列显示为Using where)。这发生在服务器层,并且需要在过滤数据前从数据表读取数据行。

下面的例子演示了有好的索引的重要性。好的索引有助于使用好的数据获取类型并且只需要处理所需要的数据行。然而,添加索引并不总是意味着MySQL获取和返回的数据行是一致的。例如,下面的COUNT()聚合方法。

1
sql复制代码SELECT actor_id, COUNT(*) FROM sakila.film_actor GROUP BY actor_id;

这个查询只返回200行,但是在构建返回结果集前需要读取数千行数据。这种查询语句,即便有索引也无法减少需要的处理的数据行数。

不幸的是,MySQL并不会告知获取了多少行来构建返回结果集,它仅仅告知获取的总行数。很多行通过WHERE条件过滤掉了,而对返回结果集没有任何作用。在前面的例子中,移除sakila.film_actor索引后,查询获取了数据表的全部行,但是只从中取了10条数据作为结果集返回。理解服务器获取的数据行数量和返回的数据行数量有助于理解查询本身。
如果发现了需要获取大量数据行而只是在结果使用很少的行,可以通过下面的方式修复这个问题:

  1. 使用覆盖索引,这使得存储引擎不需要获取完整的数据行(直接从索引中获取)。
  2. 修改查询表,一个例子是构建汇总表来查询统计数据。
  3. 重写复杂的查询语句,使得MySQL查询优化器能够以更优的方式执行。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

一不小心,它成为了 GitHub Alibaba Group

发表于 2021-04-08

来源 | 阿里巴巴云原生公众号

Arthas Star 突破 2.5 万啦

1.png

  • 开源地址:https://github.com/alibaba/arthas
  • 文档:https://arthas.aliyun.com/doc/

随着微服务的流行,应用更加轻量和高效,但是带来的困境是线上问题排查越来越复杂困难。传统的 Java 排查问题,需要重启应用再进行调试,但是重启应用之后现场会丢失,问题难以复现。

因此自 2018 年 9 月,阿里巴巴开源了久经考验,深受开发者喜爱的应用诊断利器 Arthas。

Arthas 通过创新的字节码织入技术,可以在应用无需重启时,查看调用上下文,高效排查问题;结合火焰图,可以直接定位热点,发现性能瓶颈;通过字节码替换,实现在线热更新代码;同时支持黑屏化和白屏化诊断,可以连接诊断大规模的集群。

在 2020 年 5 月时,我们做了 Arthas Star 破 2 万的回顾:

  • 精益求精 | 开源应用诊断利器 Arthas GitHub Star 突破两万

冬去春又来,转眼间一年过去了,Arthas 的 Star 数突破 2.5 万了~

下面来回顾 Arthas 去年的一些数据和工作。

Arthas 过去一年的数据

  1. Arthas Github Star 数突破 2.5W

2.png

  1. Arthas Github Contributors 数

Arthas 的开源贡献者人数从 85 增长到 119,非常感谢他们的工作:

3.png

  1. Arthas 登记公司数从 117 增长到 151 家

过去一年,Arthas 在工商银行、中原银行、朴朴科技、贝壳找房、斗鱼等生产场景落地,欢迎更多用户登记:https://github.com/alibaba/arthas/issues/111。

  • 工商银行打造在线诊断平台的探索与实践
  1. Arthas 在线教程学习人次:133,996,学习时长:51798小时

人均体验时长 23 分钟以上。

4.png

  1. Arthas zip 包月均下载 6.5w 次

上线了 Arthas 新网站之后,我们统计平均每个月 arthas zip 包下载 6.5 万次。所以保守估计,Arthas 平均每个月诊断 6W+ 台机器。

  1. Arthas 在 ATA 年度技术搜索排行第 6

阿里内部的技术论坛 ATA 发布年度热搜关键词 top 100,Arthas 作为 Java 诊断神器是唯一进入 top 10 的非集团指定产品。Arthas 在阿里内部的受欢迎程度可见一斑。

5..png

Arthas 过去一年的工作

在过去的一年里,Arthas 发布了 19 个 release 版本,做了大量的改进,下面列出一些重点:

  • 全新的 Bytekit 字节码增强框架
  • 完整支持 HTTP API,所有命令都完成适配
  • Tenlet/WebSocket/HTTP API 支持统一的鉴权方案
  • 全新的热更新命令 retransform
  • Tunnel Server 支持集群部署,支持查看火焰图,内部上线支持流计算应用
  • 增加 arthas-spring-boot-starter 模块,并支持 endpoint,用户可以用编程方式引入 Arthas
  • 上线 arthas.aliyun.com 网站,更好服务国内用户
  1. 全新的 Bytekit 字节码增强框架

Github:https://github.com/alibaba/bytekit

Bytekit 框架可以通过简洁的注解来实现字节码增强,具体功能点:

  • 丰富的注入点支持
  • 动态的 Binding
  • 可编程的异常处理
  • 比如在函数入口做增强:
1
2
3
4
5
6
7
8
9
10
less复制代码    public static class SampleInterceptor {
@AtEnter(inline = true, suppress = RuntimeException.class,
suppressHandler = PrintExceptionSuppressHandler.class)
public static void atEnter(@Binding.This Object object,
@Binding.Class Object clazz,
@Binding.Args Object[] args,
@Binding.MethodName String methodName,
@Binding.MethodDesc String methodDesc) {
System.out.println("atEnter, args[0]: " + args[0]);
}
  • inline 支持
  • invokeOrigin 技术

比如在 Dubbo Filter 里插入 APM 代码:

1
2
3
4
5
6
7
8
9
10
kotlin复制代码@Instrument(Interface = "org.apache.dubbo.rpc.Filter")
public abstract class DubboFilter_APM {
public Result invoke(Invoker<?> invoker, Invocation invocation)
throws RpcException {
System.err.println("invoker class: " +
this.getClass().getName());
Result result = InstrumentApi.invokeOrigin();
return result;
}
}

通过 Bytekit 框架,Arthas:

  • 解决了多个 watch/trace 命令会重复某个类的问题
  • 统一使用一个 Transformer,解决了多个增强命令冲突问题
  • 实现了动态增强功能,通过指定 ListenerId,watch/trace 命令可以一起协作
  1. HTTP API 支持

  • https://arthas.aliyun.com/doc/http-api.html

Http API 提供类似 RESTful 的交互接口,请求和响应均为 JSON 格式的数据。相对于 Telnet/WebConsole 的输出非结构化文本数据,Http API 可以提供结构化的数据,支持更复杂的交互功能。

  1. 统一鉴权

在今天,应用的安全越来越受到重视。因此,诊断工具在提升诊断效率的同时,也要注意自身的安全性。
因为 Arthas 增加了 auth 命令,并且统一了 Telnet/WebSocket/HTTP API 的鉴权,参考:

  • https://arthas.aliyun.com/doc/auth.html
  1. 全新的热更新命令 retransform

  • https://arthas.aliyun.com/doc/retransform.html

之前,Arthas 里的 redefine 命令已经支持热更新功能,但是容易和 jad 命令或者其它 java agent 冲突。因此,我们开发了全新的 retransform 命令。

retransform 命令和 watch/trace 命令等是同一机制下实现的。如果对同一个类执行多个命令,则会经过下面的处理:

1
rust复制代码retransform 命令 -> watch 命令 -> trace命令

可以看到,retransform 命令执行后,不会影响 watch/trace 命令。

  1. Tunnel Server 支持集群部署

  • https://arthas.aliyun.com/doc/tunnel.html

通过 Arthas Tunnel Server/Client 可以远程管理/连接多个 Agent。Tunnel Server 新增加功能:

  • 支持集群部署,支持 redis 存储
  • 支持 http proxy,查看火焰图
  1. arthas-spring-boot-starter

  • https://arthas.aliyun.com/doc/spring-boot-starter.html

通过 arthas-spring-boot-starter,用户可以直接以编程方式引入 Arthas,结合 Tunnel Server,可以轻松实现集群化管理。

  1. 全新的网站 arthas.aliyun.com

  • https://arthas.aliyun.com/

之前,Arthas 的文档放在 github io 的域名下,经常访问失败。为了改进访问速度,因此,我们建设了全新网站,用户访问文档和下载 Arthas,都更加方便快捷。

Arthas 有奖征文活动

Arthas 征文活动一共办了七期,共收到投稿 30+ 篇,下面是一些优秀的文章:

  • 工商银行打造在线诊断平台的探索与实践
  • Spring Boot 微服务性能下降九成!使用 Arthas 定位根因
  • 是谁在调用我?使用 arthas+jprofiler 做复杂链路分析
  • Arthas 定位 Dubbo 手动注册 Eureka 异常
  • 用 Arthas 神器来诊断 HBase 异常进程

有奖征文活动还在继续,欢迎大家分享~

投稿地址:http://alibabacloud.mikecrm.com/9khcRrs

Arthas 规划

去年,我们规划了三个目标:

  • RESTful API 支持
  • 全新的字节码框架 ByteKit
  • 插件化支持

实际上完成了 2.5 个,其中插件化支持,我们孵化出全新的 One Java Agent 项目来实现。

  • github.com/alibaba/one…

One Java Agent 项目的目标:

  • 提供插件化支持,统一管理众多的 Java Agent
  • 插件支持 install/unstall,需要插件方实现接口
  • 支持传统的 java agent,即已经开发好的 java agent

从开源到现在,Arthas 在 Github 上一共有 1200 多个 Issue,最近我们回收了第一个 Issue:

6.png

在不断增强功能的同时,我们一直在持续改进 Arthas 的易用性。

  • 不断改进帮助文档
  • 上线 arthas.aliyun.com ,改进国内用户访问速度
  • 为大部分命令准备交互式的在线教程

我们相信:赠人玫瑰之手,经久犹有余香,感谢广大用户的支持和喜爱。

欢迎登陆 start.aliyun.com 知行动手实验室体验 Arthas 57 个动手实验:
start.aliyun.com/handson-lab…

1617788975354-c95a0286-f1af-4bba-b19b-7b2d5e44df6d.gif

Arthas 实验预览

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…689690691…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%