BlankLin

lazy and boring

0%

  • grafana版本
    生产环境:Grafana v6.6.2
    本地docker安装相同版本:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    sudo docker run -d --name grafana-662-1 \
    -p 3000:3000 \
    -e GF_DATABASE_TYPE=mysql \
    -e GF_DATABASE_HOST=ip:port \
    -e GF_DATABASE_NAME=grafana \
    -e GF_DATABASE_USER=grafana \
    -e GF_DATABASE_PASSWORD=mysql \
    -e GF_DATABASE_URL=mysql://grafana:pwd@ip:port/grafana \
    -v /data1/docker/grafana:/var/lib/grafana \
    grafana/grafana:6.6.2

-e GF_xx,这个xx对应的是conf/grafana.ini(默认是default.ini)的[xx]

grafana-mysql

以上是用docker方式配置mysql启动grafana

  • 确认mysql的35个表已经创建成功,暂停grafana

    1
    sudo docker stop $(sudo docker ps -a | grep grafana-662-1 | awk '{print $1}')
  • 从本地mysql数据库导出表结构到生产环境

    1
    mysql -uroot -p -D grafana > grafana.sql
  • 生产环境导入表结构
    1
    2
    CREATE DATABASE grafana CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    mysql -uroot -p -D grafana < grafana.sql
  • 另存脚本sqlitedump.sh
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #!/bin/bash
    DB=$1
    TABLES=$(sqlite3 $DB .tables | sed -r 's/(\S+)\s+(\S)/\1\n\2/g' | grep -v migration_log)
    for t in $TABLES; do
    echo "TRUNCATE TABLE $t;"
    done
    for t in $TABLES; do
    echo -e ".mode insert $t\nselect * from $t;"
    done | sqlite3 $DB
  • 找到grafana.db文件

    1
    2
    cd /data1/docker/grafana
    sh sqlitedump.sh grafana.db > insert.sql
  • 生产环境导入insert.sql

    1
    mysql -uroot -p -D grafana < 生产环境导入insert.sql
  • 启动grafana的docker

    1
    sudo docker start $(sudo docker ps -a | grep grafana-662-1 | awk '{print $1}')
  • 验证
    打开web进行验证

  • sqlite_master表

    1
    2
    3
    4
    5
    6
    7
    8
    sqlite3存在系统表sqlite_master,结构如下:
    sqlite_master(
    type TEXT, #类型:table-表,index-索引,view-视图
    name TEXT, #名称:表名,索引名,视图名
    tbl_name TEXT,
    rootpage INTEGER,
    sql TEXT
    )
  • slite3转mysql的python3脚本

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

    class Sqlite3ToMysql():

    def __init__(self):
    dir = "grafana.db"
    conn = sqlite3.connect(dir)
    self.cur = conn.cursor()

    def get_table_list(self):
    self.cur.execute("select NAME from SQLITE_MASTER where TYPE='table' order by NAME;")
    rows = self.cur.fetchall()
    return rows

    def get_table_column(self, tableName):
    self.cur.execute("pragma table_info('{}');".format(tableName))
    rows = self.cur.fetchall()
    list = []
    for (columnID, columnName, columnType,columnNotNull, columnDefault, columnPK) in rows:
    dict = {
    "name": columnName,
    "type": columnType,
    "null": "NOT NULL" if columnNotNull else "",
    "default": "default '{}'".format(columnDefault) if columnDefault else "",
    "pk": columnPK,
    }
    list.append(dict)
    return list

    def get_table(self, tableName):
    self.cur.execute("SELECT * FROM `{}`;".format(tableName))
    rows = self.cur.fetchall()
    return rows

    def get_table_index(self, tableName):
    sql = "select sql from sqlite_master where tbl_name='{}' and type='index';".format(tableName)
    self.cur.execute(sql)
    rows = self.cur.fetchall()
    if len(rows) == 0:
    return {}
    list = []
    need = []
    for row in rows:
    value = str(row[0])
    value = str.replace(value, "CREATE INDEX", "KEY")
    value = str.replace(value, "CREATE UNIQUE INDEX", "UNIQUE KEY")
    value = str.replace(value, "ON `{}`".format(tableName), "")
    matchObj = re.findall(r"KEY\s*`.*`\s*\((.*)\)", value)
    if len(matchObj) > 0:
    tmp = matchObj[0].split(",")
    for i in tmp:
    i = str.replace(i, "`", "")
    need.append(i)
    list.append(" "+value)
    return {"sql": ",\n".join(list) + ",\n", "need": need}

    def ddl(self):
    handle = open("ddl.sql","w")
    rows = self.get_table_list()
    for (tableName,) in rows:
    handle.write("DROP TABLE IF EXISTS `{}`;\n".format(tableName))
    handle.write("CREATE TABLE `{}`(\n".format(tableName))
    tmp = self.get_table_column(tableName)
    pk = ""
    indexTmp = self.get_table_index(tableName)
    for item in tmp:
    # 对索引字段为text需要指定长度
    typeStr = item["type"]
    if len(indexTmp.keys()) > 0 and len(indexTmp["need"]) > 0 and typeStr == 'TEXT' and indexTmp["need"].count(item["name"]) > 0:
    typeStr = "varchar(250)"
    handle.write(" `{name}` {type} {null} {default},\n".format(
    name=item["name"],
    type=typeStr,
    null=item["null"],
    default=item["default"],
    ))
    if item["pk"]:
    pk = item["name"]
    if len(indexTmp.keys()) == 0:
    continue
    else:
    handle.write(indexTmp["sql"])
    if (len(pk) > 0):
    handle.write(" PRIMARY KEY (`{}`)\n".format(pk))
    handle.write(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n\n")
    handle.close()

    def dml(self):
    handle = open("dml.sql","w")
    rows = self.get_table_list()
    for (tableName,) in rows:
    list = self.get_table(tableName)
    if len(list) == 0:
    continue
    handle.write("INSERT INTO `{}` VALUES \n".format(tableName))
    itemList = []
    for item in list:
    valueList = []
    for value in item:
    if type(value) == bytes:
    value = value.decode("utf-8")
    if type(value) in [int, float, bool]:
    value = "'" + str(value) + "'"
    elif type(value) in [str]:
    value = "'" + value + "'"
    elif type(value) in [dict, list, tuple, set]:
    value = "'" + json.dumps(value) + "'"
    elif value is None:
    value = "''"
    valueList.append(value)
    strTmp = "(" + "," . join(valueList) + ")"
    itemList.append(strTmp)
    handle.write(",".join(itemList))
    handle.write("\n\n")
    handle.close()


    if __name__ == "__main__":
    obj = Sqlite3ToMysql()
    obj.ddl()
    obj.dml()

  • 安装epel
    EPEL 仓库中有 Nginx 的安装包。如果你还没有安装过 EPEL,可以通过运行下面的命令来完成安装:
    1
    sudo yum install epel-release
  • 安装nginx
    1
    sudo yum install nginx
  • 配置文件
    • 日志文件
      1
      2
      /var/log/nginx/access.log
      /var/log/nginx/error.log
    • conf文件
      1
      /etc/nginx/conf.d/
    • 项目文件
      1
      2
      /var/www/html/<site_name>
      /usr/share/nginx/html/<site_name>
    • nginx命令
      1
      /usr/sbin/nginx
  • 启动nginx
    1
    /usr/sbin/nginx
  • 生成密钥
    1
    2
    3
    4
    5
    6
    7
    # 使用openssl生成密钥
    printf "admin:$(openssl passwd -crypt 123456)\n" >> /etc/nginx/conf/htpasswd
    cat /etc/nginx/conf/htpasswd
    admin:xyJkVhXGAZ8tM
    # 使用htpasswd生成密钥
    yum install httpd-tools -y
    htpasswd -c -d /etc/nginx/conf/htpasswd admin
  • 添加server配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    server{
    listen 80;
    server_name localhost;

    auth_basic "登录认证";
    auth_basic_user_file /etc/nginx/conf/htpasswd;

    autoindex on;
    autoindex_exact_size on; #显示文件大小
    autoindex_localtime on; #显示文件时间

    root /var/www/html;
    index index.html index.php
    }
  • 重启nginx
    1
    /usr/sbin/nginx -s reload
    nginx-file-server

了解几个相关命令

  • 查看docker nginx容器 进程 pid
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 请务必确认您的docker container name是什么,笔者名称是nginx
    // 命令1
    sudo docker inspect --format='{{.State.Pid}}' $(sudo docker ps -a | grep nginx | awk '{print $1}')

    //命令2是命令1的缩写
    sudo docker inspect -f '{{.State.Pid}}' $(sudo docker ps -a | grep nginx | awk '{print $1}')

    //命令3是命令2的缩写
    sudo docker inspect -f '{{ .State.Pid }}' nginx

    // 命令4
    sudo docker top $(sudo docker ps -a | grep nginx | awk '{print $1}') | grep master | awk '{print $2}'
  • 通知nginx重新打开日志文件

    Nginx 官方其实给出了如何滚动日志的说明:
    Rotating Log-files
    In order to rotate log files, they need to be renamed first. After that USR1 signal should be sent to the master process. The master process will then re-open all currently open log files and assign them an unprivileged user under which the worker processes are running, as an owner. After successful re-opening, the master process closes all open files and sends the message to worker process to ask them to re-open files. Worker processes also open new files and close old files right away. As a result, old files are almost immediately available for post processing, such as compression.
    以上英文的大致意思是
    1.先把旧的日志文件重命名
    2.然后给 nginx master 进程发送 USR1 信号
    3.nginx master 进程收到信号后会做一些处理,然后要求工作者进程重新打开日志文件
    4.工作者进程打开新的日志文件并关闭旧的日志文件

    1
    kill -USR1 pid
  • logrotate使用帮助

    • 配置说明
参数 参数说明
daily 日志的轮替周期是每天
weekly 日志的轮替周期是每周
monthly 日志的轮替周期是每月
rotate 数字 保留的日志文件个数,0指没有备份
compress 当进行日志轮替时,对旧的日志进行压缩
create mode owner group 建立新日志,同时指定新日志的权限与所有者和所属组,如 create 0644 root root
copytruncate 用于还在打开中的日志文件,把当前日志备份并截断
mail address 当进行日志轮替时,输出内容通过邮件发送到指定的邮件地址,如 mail test@gmail.com
missingok 如果日志不存在,则忽略改日志的警告信息
notifempty 如果日志为空文件,则不进行日志轮替
minsize 大小 日志只有大于指定大小才进行日志轮替,而不是按照时间轮替,如 size 100k
dateext 使用日期作为日志轮替文件的后缀,如 access.log-2019-07-12
sharedscripts 在此关键词之后的脚本只执行一次
prerotate/endscript 在日志轮替之前执行脚本命令。endscript 标识 prerotate 脚本结束
postrotate/endscript 在日志轮替之前执行脚本命令。endscript 标识 postrotate脚本结束
  • 参数说明
参数 参数说明
-?或–help 在线帮助
-d或–debug 详细显示指令执行过程,便于排错或了解程序执行的情况
-f或–force 强行启动记录文件维护操作,纵使logrotate指令认为没有需要亦然
-s<状态文件>或–state=<状态文件> 使用指定的状态文件
-v或–version 显示指令执行过程
-usage 显示指令基本用法

脚本详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sudo vim /etc/ logrotate.d/nginx
//复制以下脚本,第一行是您的nginx log路径
/home/xx/docker/nginx/logs/*log {
daily
dateext
missingok
rotate 52
compress
delaycompress
notifempty
su hadoop hadoop
create 0664 hadoop hadoop
sharedscripts
postrotate
sudo docker inspect -f '{{ .State.Pid }}' nginx | xargs sudo kill -USR1
endscript
}
sudo logrotate -vf /etc/ logrotate.d/nginx

loratate

crontab 加定时脚本

1
2
crontab -e
*/30 * * * * /usr/sbin/logrotate /etc/logrotate.d/nginx > /dev/null 2>&1 &

使用docker安装nginx

  • docker run(运行容器)的参数说明
参数 说明
–-name 容器名称
–-link 连接某个镜像
–port 端口映射
–volum 持久化存储
  • 获取nginx 镜像
    1
    sudo docker pull nginx
  • 运行nginx容器
    1
    2
    // --rm标示删除旧容器 -d 标示daemon守护进程方式运行 --name标示容器名称
    docker run --name nginx --network spring-net -p 80:80 -d -v /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro -v /etc/nginx/conf.d:/etc/nginx/conf.d -v /opt/nginx/dist:/opt/nginx/dist -v /etc/localtime:/etc/localtime:ro -v /var/log/nginx:/var/log/nginx nginx
  • 拷贝宿主机的nginx配置到容器内,指定目录
    1
    docker cp nginx:/etc/nginx /etc/nginx
  • Dockerfile编写
    1
    2
    3
    4
    FROM nginx
    COPY content /usr/share/nginx/html
    COPY conf /etc/nginx
    VOLUME /var/log/nginx/log
  • docker-compose.yml编写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    web:
    image: nginx
    volumes:
    - ./mysite.template:/etc/nginx/conf.d/mysite.template
    ports:
    - "8080:80"
    environment:
    - NGINX_HOST=foobar.com
    - NGINX_PORT=80
    command: /bin/bash -c "envsubst < /etc/nginx/conf.d/mysite.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"

在vscode里使用快捷键如下

Open the Command Palette via (⇧⌘P)

在弹出框中输入命令如下

shell command
vscode

重启vscode

After executing the command, restart the terminal for the new $PATH value to take effect. You’ll be able to simply type ‘code .’ in any folder to start editing files in that folder. The “.” Simply means “current directory”

下面流程安装在unix(MacBook)环境

go的开发环境

  • 安装go

    推荐使用go1.12+版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 安装homebrew:
    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    // 安装go
    brew install go
    // 将go的配置加入到系统环境
    vim ~/.bask_profile
    // 复制下面的内容
    export GOPATH="${HOME}/.go"
    export GOROOT="$(brew --prefix golang)/libexec"
    export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
    export GOPROXY=https://goproxy.io
    export GO111MODULE=on
    // 生效
    source ~/.bash_profile
  • GO111MODULE
    在 1.12 版本之前,使用 Go modules 之前需要环境变量 GO111MODULE:
    • GO111MODULE=off: 不使用 Module-aware mode。
    • GO111MODULE=on: 使用 Module-aware mode,不会去 GOPATH 下面查找依赖包。
    • GO111MODULE=auto或unset: Golang 自己检测是不是使用Module-aware mode。

      根据官方描述在不设置GO111MODULE的情况下或者设为auto的时候,如果在当前目录或者父目录中有go.mod文件,那么就使用Module-aware mode, 而go1.12中,如果包位于GOPATH/src下,且GO111MODULE=auto, 即使有go.mod的存在,go仍然使用GOPATH mode:

  • GO PROXY
    GOPROXY环境变量是伴随着modules而生,在go1.13中得到了增强,可以设置为逗号分隔的url列表来指定多个代理,其默认值为https://proxy.golang.org,direct。
    direct:表示直接连接,所有direct后面逗号隔开都proxy都不会被使用。

    这里可以设置GOPROXY=https://goproxy.io

  • go env命令查看所有go的系统环境变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    GO111MODULE="on"
    GOARCH="amd64"
    GOBIN=""
    GOCACHE="/Users/xx/Library/Caches/go-build"
    GOENV="/Users/xx/Library/Application Support/go/env"
    GOEXE=""
    GOFLAGS=""
    GOHOSTARCH="amd64"
    GOHOSTOS="darwin"
    GOOS="darwin"
    GOPATH="/Users/xx/Documents/go"
    // 省略其他的

    go.mod的使用

  • 创建项目
    在GOPATH目录之外任意创建一个项目
    1
    2
    3
    // GOPATH="/Users/xx/Documents/go"
    cd /Users/xx/Documents/code
    mkdir hello-go-mod
  • 初始化go.mod
    进入到项目根目录
    1
    2
    3
    4
    5
    cd /Users/xx/Documents/code/hello-go-mod
    go mod init hello-go-mod
    //提示创建成功: go: creating new go.mod: module hello-go-mod
    ll
    // 看到根目录下多了go.mod文件
  • go run触发modules工作
    在项目的根目录下创建一个main.go文件,复制下面的内容到文件里
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package main

    import(
    "fmt"
    "github.com/valyala/fasthttp"
    )

    func main() {
    fmt.Print("hello go mod")
    }
    go run main.go
    发现项目目录下多了一个 go.sum
    go.sum用来记录每个 package 的版本和哈希值。
    go.mod 文件正常情况会包含 module 和 require 模块,除此之外还可以包含 replace 和 exclude 模块

调教GoLang

  1. 开启go-mod
    go-mod

golint使用

目录cd到$GOPATH/src/github.com

  • clone golint
    1
    git clone https://github.com/golang/lint.git
  • clone gotool
    1
    git clone https://github.com/golang/tools.git
  • go install
    1
    2
    cd lint/golint
    go install
  • 调教GoLang

    • 配置golint扩展
      go-lint
    • 配置golint快捷键
      go-lint-shortcut

      现在可以在GoLang中使用option+command+l 进行lint了

  • 错误提示
    go-error
    错误参考:
    https://github.com/golang/lint/tree/master/testdata

背景

用户反馈说分页数据重复,查看日志,发现说这样的sql:

1
2
3
4
5
select column1,column2 
from table
where columnx = 'xxx'
order by columny
limit current,size

原因

  • mysql版本
    mysql5.7.20
    查看mysql的changes list

    MySQL 5.6 has only one way one can check whether filesort used priority queue. You need to enable optimizer_trace (set optimizer_trace=1), and then run the query (not EXPLAIN, but the query itself). Then, you can look into the optimizer trace

  • order by
    MySQL 执行查询语句, 对于order by谓词,可能会使用filesort或者temporary。比如explain一条语句的时候,会看到Extra字段中可能会出现,using filesort和using temporary。下面我们就来探讨下两个的区别和适用场景。
  • filesort
    filesort主要用于查询数据结果集的排序操作,首先MySQL会使用sort_buffer_size大小的内存进行排序,如果结果集超过了sort_buffer_size大小,会把这一个排序后的chunk转移到file上,最后使用多路归并排序完成所有数据的排序操作。
    filesort有两种使用模式:
    模式1: sort的item保存了所需要的所有字段,排序完成后,没有必要再回表扫描。
    模式2: sort的item仅包括,待排序完成后,根据rowid查询所需要的columns。

    很明显,模式1能够极大的减少回表的随机IO。

  • priority query
  1. 打开optimizer_trace
    1
    mysql> set session optimizer_trace=’enabled=on';
  2. 查询information_schema.optimizer_trace表
    主要分为三个部分
    join_preparation:SQL的准备阶段,sql被格式化
    对应函数 JOIN::prepare
    join_optimization:SQL优化阶段
    对应函数JOIN::optimize
    join_execution:SQL执行阶段
    对应函数:JOIN::exec
  3. 查看sql执行阶段
    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
    "join_execution": {
    "select#": 1,
    "steps": [
    {
    "filesort_information": [
    {
    "direction": "asc",
    "table": "`film`",
    "field": "actor_age"
    }
    ],
    "filesort_priority_queue_optimization": {
    "usable": false,
    "cause": "not applicable (no LIMIT)"
    },
    "filesort_execution": [
    ],
    "filesort_summary": {
    "rows": 1,
    "examined_rows": 5,
    "number_of_tmp_files": 0,
    "sort_buffer_size": 261872,
    "sort_mode": "<sort_key, packed_additional_fields>"
    }
    }
    ]
    }
    当使用order by limit时,filesort_priority_queue_optimization给出的useable为true,在mysql5.6之后(包括5.6)做了一个优化,即priority queue,使用priority queue的目的是为了加快查询,如果sql中未使用到索引有序性时,order by limit会在满足buffer size设置后返回只保证返回limit size,但不保证排序出来的结果和读出来的数据顺序一致,这就是堆排序对效果,先select出符合条件的rows,再order by column,如果这个column没有明确的唯一性,则再limit后,只是会随机出limit的size条数,这个随机性不保证数据是否重复

    解决方法

  • 使用有序索引排序
    1
    select from table where xx order by column desc,id desc limit size
  • 给order by 的column加上索引
    1
    alter table add index (column)
  • 强制使用索引
    1
    select from table force index(column) order by column limit size

原因

+出现markdown语法出错

解决方案

在markdown语法里,不用使用”<>/“这些尖括号斜杆,不然会被markdown反解析后就有语法错误了!
全文搜索有出现有出现就替换掉!

原因

从github仓库clone后,没有next目录

1
ls themes/next

是否为空?

解决方案

如果确实是空的
则重新clone next仓库到指定目录

1
2
3
4
5
6
rm -fr themes/next
git clone https://github.com/theme-next/hexo-theme-next.git next
// 修改主题
cd themes/next/
vim _config.yml
// scheme: Pisces

  • 全局安装vue脚手架
    1
    npm install vue-cli -g
  • 使用webpack初始化项目
    1
    vue init webpack my-project
    下面是安装过程系统给出的提示
    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
    //项目名称
    ? Project name devops
    //项目描述
    ? Project description bigdata devops platform
    //项目作者
    ? Author blank <luck.linxiaoyu@gmail.com>
    ? Vue build standalone
    //安装vue-router(vue的路由)
    ? Install vue-router? Yes
    // 使用eslint格式化你的代码
    ? Use ESLint to lint your code? Yes
    //选择Airbnb作为eslint的格式标准
    ? Pick an ESLint preset Airbnb
    //选一个单元测试工具
    ? Set up unit tests Yes
    // 我选的是jest
    ? Pick a test runner jest
    //使用nightwatch开始e2e单元测试
    ? Setup e2e tests with Nightwatch? Yes
    //项目创建使用使用npm install进行其他包安装
    ? Should we run `npm install` for you after the project has been created?


    # Project initialization finished!
    # ========================
    // 安装完成给出提示如何启动项目
    To get started:
    cd new
    npm run dev

OLAP介绍

OLAP概述

OLAP,也叫联机分析处理(Online Analytical Processing)系统,就是我们说的数据仓库。在这样的系统中,语句的执行量不是考核标准,因为一条语句的执行时间可能会非常长,读取的数据也非常多。所以,在这样的系统中,考核的标准往往是磁盘子系统的吞吐量(带宽),如能达到多少MB/s的流量。
在OLAP系统中,比较典型的应用是BI平台,常使用分区技术、并行技术。 分区技术在OLAP系统中的重要性主要体现在数据库管理上,比如数据库加载,可以通过分区交换的方式实现,备份可以通过备份分区表空间实现,删除数据可以通过分区进行删除,至于分区在性能上的影响,它可以使得一些大表的扫描变得很快(只扫描单个分区)。另外,如果分区结合并行的话,也可以使得整个表的扫描会变得很快。总之,分区主要的功能是管理上的方便性,它并不能绝对保证查询性能的提高,有时候分区会带来性能上的提高,有时候会降低。

OLAP特点

  • 大多数是读请求
  • 数据总是以相当大的批(> 1000 rows)进行写入
  • 不修改已添加的数据
  • 每次查询都从数据库中读取大量的行,但是同时又仅需要少量的列
  • 宽表,即每个表包含着大量的列
  • 较少的查询(通常每台服务器每秒数百个查询或更少)
  • 对于简单查询,允许延迟大约50毫秒
  • 列中的数据相对较小: 数字和短字符串(例如,每个URL 60个字节)
  • 处理单个查询时需要高吞吐量(每个服务器每秒高达数十亿行)
  • 事务不是必须的
  • 对数据一致性要求低
  • 每一个查询除了一个大表外都很小
  • 查询结果明显小于源数据,换句话说,数据被过滤或聚合后能够被盛放在单台服务器的内存中

    很容易可以看出,OLAP场景与其他流行场景(例如,OLTP或K/V)有很大的不同, 因此想要使用OLTP或Key-Value数据库去高效的处理分析查询是没有意义的,例如,使用OLAP数据库去处理分析请求通常要优于使用MongoDB或Redis去处理分析请求。

行式数据库系统介绍

常见的行式数据库系统有: MySQL、Postgres和MS SQL Server。
处于同一行中的数据总是被物理的存储在一起。

Row WatchID JavaEnable
#0 11111 1
#1 22222 0
#3 33333 1

列式数据库系统介绍

常见的列式数据库有: Vertica、 Paraccel (Actian Matrix,Amazon Redshift)、 Sybase IQ、 Exasol、 Infobright、 InfiniDB、 MonetDB (VectorWise, Actian Vector)、 LucidDB、 SAP HANA、 Google Dremel、 Google PowerDrill、 Druid、 kdb+。列式数据库总是将同一列的数据存储在一起,不同列的数据也总是分开存储。

Row #0 #1 #2 |
WatchID 11111 22222 33333
JavaEnable 1 0 1

clickhouse介绍

ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS),是俄罗斯的百度——yandex公司开发的。yandex公司在处理自己公司日常数据业务中,开发了一套数据管理系统,随后进行了开源分享,命名为clickhouse

clickhouse优势

  • 线性可扩展

    • 可以部署在虚拟机上,也可以部署在有成百上千个节点(机器)的集群上,单个节点可容纳万亿行数据或超过100TB数据
  • 高效的硬件利用

    • 多核并行处理,数据高效压缩
  • 容错性和高可靠性
    • ClickHouse支持多shard(master/slave)异步复制并且能部署在多个数据中心上
    • 单节点或者整个数据中心宕机不会影响系统读写的可靠性
    • 分布式读取模式,自动(将吞吐压力)均衡于各可用的备份节点上从而避免高时延
    • 宕机恢复后备份间数据自动同步或半自动同步
  • 查询速度快
    • 面向列的DBMS,使用向量化引擎,数据压缩,每秒能处理亿级行级别的数据,简单查询时峰值处理性能可达到单服务器每秒2TB
  • 丰富的特征
    • 支持sql语法,丰富的内置分析统计函数/组件,比如基数、百分位数计算;日期、时间、时区数据函数;URL和IP地址处理函数
    • 数据组织和存储格式丰富,比如存储复杂数据的arrays, array joins, tuples and nested data structures,这些数据结构都做了读写和计算优化,因此用来处理和查询非结构化数据(也可以叫半结构化)也是非常高效率的
    • 支持本地join和分布式join,支持额外定义的字典、从外部导入的维度表,通过简单的语法句子就能无缝join数据
    • 支持近似查询处理,提高获得结果的速度,特别是在处理TB/PB级别数据的时候(比如抽样部分数据计算一个结果然后推广到总体)
    • 支持按条件汇总函数,可以用非常简单的句法查询极值和汇总数据查询

      clickhouse缺点

  • 不支持Transaction:想快就别想Transaction
  • 聚合结果必须小于一台机器的内存大小:不是大问题
  • 缺少完整的Update/Delete操作

    clickhouse架构

    clickhouse

    clickhou接入

  1. http接入
    传入用户名密码的方式
    1
    2
    3
    4
    // 读方式
    echo 'SELECT 1' | curl 'http://user:password@localhost:8123/?database=dbname' -d @-
    // 写方式
    echo 'SELECT 1' | curl 'http://localhost:8123/?database=dbname&user=user&password=password' -d @-
  2. JDBC驱动

    • ClickHouse官方有 JDBC 的驱动。 见这里

    • 第三方提供的 JDBC 驱动 clickhouse4j (推荐,依赖较少)。

    1
    2
    // jdbc的连接字符串是
    jdbc:clickhouse://<host>/<database>
  3. 可视化操作界面Tabix
    ClickHouse Web 界面 Tabix

    主要功能:

    • 浏览器直接连接 ClickHouse,不需要安装其他软件。
    • 高亮语法的编辑器。
    • 自动命令补全。
    • 查询命令执行的图形分析工具。
    • 配色方案选项。
  4. 命令行客户端
    参照 https://github.com/hatarist/clickhouse-cli 使用,要求Python 3.4+

    pip3安装请参照

    内容查看
    步骤如下:

    1
    2
    pip3 install clickhouse-cli
    vi ~/.clickhouse-cli.rc

    表引擎

    表引擎(即表的类型)决定了:

  • 数据的存储方式和位置,写到哪里以及从哪里读取数据
  • 支持哪些查询以及如何支持。
  • 并发数据访问。
  • 索引的使用(如果存在)。
  • 是否可以执行多线程请求。
  • 数据复制参数。

    MergeTree

    适用于高负载任务的最通用和功能最强大的表引擎。这些引擎的共同特点是可以快速插入数据并进行后续的后台数据处理。 MergeTree系列引擎支持数据复制(使用Replicated* 的引擎版本),分区和一些其他引擎不支持的其他功能
    该类型的引擎:
  • MergeTree
  • ReplacingMergeTree
  • SummingMergeTree
  • AggregatingMergeTree
  • CollapsingMergeTree
  • VersionedCollapsingMergeTree
  • GraphiteMergeTree

数据副本

只有 MergeTree 系列里的表可支持副本:

  • ReplicatedMergeTree
  • ReplicatedSummingMergeTree
  • ReplicatedReplacingMergeTree
  • ReplicatedAggregatingMergeTree
  • ReplicatedCollapsingMergeTree
  • ReplicatedVersionedCollapsingMergeTree
  • ReplicatedGraphiteMergeTree
    副本是表级别的,不是整个服务器级的。所以,服务器里可以同时有复制表和非复制表。
    副本不依赖分片。每个分片有它自己的独立副本。

    对于 INSERT 和 ALTER 语句操作数据的会在压缩的情况下被复制(从master-》slave )。
    而 CREATE,DROP,ATTACH,DETACH 和 RENAME 语句只会在单个服务器上执行,不会被复制。
    所以在使用集群操作表时需要使用ON CLUSTER cluster_name来通知集群所有机器都操作这个动作。

  • The CREATE TABLE 在运行此语句的服务器上创建一个新的可复制表。如果此表已存在其他服务器上,则给该表添加新副本。

  • The DROP TABLE 删除运行此查询的服务器上的副本。
  • The RENAME 重命名一个副本。换句话说,可复制表不同的副本可以有不同的名称。
    要使用副本,需在配置文件中设置 ZooKeeper 集群的地址。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <zookeeper>
    <node index="1">
    <host>example1</host>
    <port>2181</port>
    </node>
    <node index="2">
    <host>example2</host>
    <port>2181</port>
    </node>
    <node index="3">
    <host>example3</host>
    <port>2181</port>
    </node>
    </zookeeper>
    注意:ZooKeeper 3.4.5 或更高版本。

    创建复制表

    在表引擎名称上加上 Replicated 前缀。例如:ReplicatedMergeTree。
    Replicated*MergeTree 参数
  • zoo_path — ZooKeeper 中该表的路径。
  • replica_name — ZooKeeper 中的该表的副本名称。

使用ON CLUSTER cluster功能在集群创建本地表

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE db.table_local 
ON CLUSTER cluster_name (
`name` String COMMENT '姓名',
`age` String,
`pt` String,
)
ENGINE = ReplicatedMergeTree(
'/clickhouse/tables/{shard}/db/table_local',
'{replica}'
)
PARTITION BY (pt)
ORDER BY (age)
SETTINGS index_granularity = 8192

如上例所示,这些参数可以包含宏替换的占位符,即大括号的部分。它们会被替换为配置文件里 ‘macros’ 那部分配置的值。示例:
1
2
3
4
<macros>
<shard>02</shard>
<replica>example05-02-1.yandex.ru</replica>
</macros>

ZooKeeper 中该表的路径对每个可复制表都要是唯一的。不同分片(shard)上的表要有不同的路径。 这种情况下,路径包含下面这些部分:
/clickhouse/tables/ 是公共前缀,我们推荐使用这个。
{shard} 是分片标识部分。保留 {shard} 占位符即可,它会替换展开为分片标识。
db/table_local 是该表在 ZooKeeper 中的名称。使其与 ClickHouse 中的表名相同加后缀_local来区分。
注意:也可以显式指定这些参数,而不是使用宏替换。对于测试和配置小型集群这可能会很方便。但是,这种情况下,则不能使用分布式 DDL 语句(ON CLUSTER)。

  • 使用ON CLUSTER cluster功能在集群创建分布式表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    CREATE TABLE db.table 
    ON CLUSTER cluster_name
    AS db.table_local
    ENGINE = Distributed (
    cluster_name,
    db,
    table_local,
    rand()
    )

eslint安装

eslint:Find and fix problems in your JavaScript code

  • 打开Teminal,进入到你的项目根目录后,输入下面命令
    1
    npm install eslint --save-dev
  • vscode安装eslint插件
    如上面截图的logo,请安装对应的eslint插件
  • 验证安装成功
    1
    ./node_modules/.bin/eslint -v
  • 生成eslintrc.js配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    ./node_modules/.bin/eslint --init

    // 下面是安装流程提示文字
    ? How would you like to use ESLint?
    To check syntax and find problems
    ? What type of modules does your project use?
    JavaScript modules (import/export)
    ? Which framework does your project use?
    Vue.js
    ? Does your project use TypeScript?
    No
    ? Where does your code run?
    Browser
    ? What format do you want your config file to be in?
    JavaScript
    The config that you've selected requires the following dependencies:
  • 最终生成eslintrc.js文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    module.exports = {
    "env": {
    "browser": true,
    "es6": true
    },
    "extends": [
    "eslint:recommended" // eslint
    ],
    "globals": {
    "Atomics": "readonly",
    "SharedArrayBuffer": "readonly"
    },
    "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
    },
    "plugins": [
    "vue"
    ],
    "rules": {
    "semi": ["error", "always"],
    "quotes": ["error", "double"],
    }
    };

    eslintrc配置规则(rules)
    “off” or 0 - turn the rule off
    “warn” or 1 - turn the rule on as a warning (doesn’t affect exit code)
    “error” or 2 - turn the rule on as an error (exit code will be 1)

  • 安装eslint-plugin-vue
    Official ESLint plugin for Vue.js.
    This plugin allows us to check the template and script of .vue files with ESLint.
    Finds syntax errors.
    Finds the wrong use of Vue.js Directives.
    Finds the violation for Vue.js Style Guide.
    1
    npm install --save-dev eslint-plugin-vue
    ++ 修改eslintrc.js文件
    1
    2
    3
    4
    5
    // ... other options
    extends: [
    "plugin:vue/essential" //eslint规则
    'plugin:vue/recommended' //eslint-plugin-vue规则
    ],
  • 安装eslint-loader
    1
    npm install eslint-loader eslint-friendly-formatter --save-dev
    ++ 修改webpack配置
    这样你的 .vue 文件会在开发期间每次保存时自动检验。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // webpack.base.config.js
    module.exports = {
    // ... other options
    module: {
    rules: [
    {
    test: /\.(js|vue)$/,
    loader: "eslint-loader",
    exclude: /node_modules/
    enforce: "pre",
    include: [resolve("src"), resolve("test")],
    options: {
    formatter: require("eslint-friendly-formatter")
    }
    }
    ]
    }
    }
  • 修改.vscode/settings.json
    You have to configure the eslint.validate option of the extension to check .vue files because the extension targets only .js or .jsx files by default.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    "editor.formatOnSave": true, //保存时自动格式化
    "eslint.validate": [
    "javascript",
    "javascriptreact",
    {
    "language": "html",
    "autoFix": true
    },
    {
    "language": "vue",
    "autoFix": true
    }
    ],
    "eslint.autoFixOnSave": true

总结

首先推荐使用vscode这个编辑器来开发vuejs项目,最优秀的轻量级编辑器,迅速加载项目,即使你的项目里有数百个node_modules。
其实eslint可以帮助你解决日常开发中遇到的代码bug,有友好的提示效果。
vue官方推荐使用eslint-plugin-vue插件来兼容eslint对于vue代码的检查。
另外想要关闭eslint的检查,可以在文件顶部加入

1
2
3
You may use special comments to disable some warnings.
Use // eslint-disable-next-line to ignore the next line.
Use /* eslint-disable */ to ignore all warnings in a file.

参考地址如下:
https://vue-loader-v14.vuejs.org/zh-cn/workflow/linting.html
https://eslint.org/docs/user-guide/getting-started
https://eslint.vuejs.org/user-guide/#faq