redis-cli -c 的误解

内部系统使用的一个AWS Elasticcache集群,不知不觉就扩容到8个node节点。被迫从AWS Elasticcache Redis上导出snapshot文件到S3,准备用作做分析。

分析工具是一个python实现的rbdtools

1
2
pip install rbdtools
rdb -c memory /tmp/dump.rdb

实际上导出的中共有8个rbd文件,分析结果会生成csv文件。分析后,发现是大量登陆token,没有设置ttl。开发后续核对代码,才知道登陆时设置ttl了,但在更新权限时,忘记带上ttl值。于是日积月累产生越来越多无用的数据。

接下来就是清理了。

  • 方案一:

取出没ttl的key的空闲时间,进行倒序排列,删除半年都没人访问的。

1
2
redis-cli -c -h 10.20.0.111 -p 6379 --scan --pattern erpToken* | xargs -r -t -n1  redis-cli -c -h 10.20.0.112 -p 6379 object idletime 
// 取出后,通过程序删除
  • 方案二:

通过rename命令,变更key的name。后续再清理

  • 方案三:

直接清空全部 key name 为erpToken* 模式的数据。

然而,此处有一个坑,redis-cli -c 的cluster 模式,并不会帮你跳转到所有节点

1
2
3
4
5
6
redis-cli -c -h 10.20.0.118 -p 6379 --scan --pattern erpToken* 
redis-cli -c -h 10.20.0.118 -p 6379 keys erpToken*
// 这两条命令,都只是在client连接到的节点上列出数据

redis-cli -c -h 10.20.0.111 -p 6379 --scan --pattern erpToken* | xargs -r -t -n1 redis-cli -c -h 10.20.0.112 -p 6379 del
// 其实也只是清空了其中一个节点而已。正确的做法,只能通过for循环,取出master节点,逐个连接上去清理。

其实 redis-cli 的 -c 参数,help如下:Enable cluster mode (follow -ASK and -MOVED redirections)。所以只是针对取特定值的时候,帮助做跳转而已。


python virtual enviroment
1
2
3
4
5
6
7
8
9
10
# 创建虚拟环境
python -m venv .venv
# 切换到虚拟环境
source .venv/bin/active
# 添加环境中的python Kernel到juypter,当然下面的projectname 还是改一个合适的名称哦
ipython kernel install --user --name=projectname
# 切到项目工作空间
cd workspace
# 运行jupyter
jupyter notebook

最终就能在下图位置中,找到虚拟的环境了。如果要需要安装第三方包,jupyter也可以直接开启termial安装。用来做调试小爬虫很轻巧呢。

image-20200506110943033


BeautifulSoup

HTML说到底还是XML标记语言,因此解析XML在爬虫抓取结果后,对数据进行拆解分析,变少不了BeautifulSoup4了。

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
~/code/algorithm via algorithm 
➜ cat bf4.py
#!/bin/env python3
from bs4 import BeautifulSoup

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""
soup = BeautifulSoup(html_doc, 'lxml')
print(soup.prettify())%

~/code/algorithm via algorithm
➜ python bf4.py

<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>

Traceback (most recent call last):
File "bf4.py", line 19, in <module>
soup = BeautifulSoup(html_doc, 'lxml')
File "/Users/chandler.kwok/code/algorithm/.venv/lib/python3.7/site-packages/bs4/__init__.py", line 228, in __init__
% ",".join(features))
bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: lxml. Do you need to install a parser library?

简单的按照例子,结果第一步便是报错。原因是 pip3 install beautifulsoup4 还不足以。


Promethues Consul监控

一边在做数据库备份,一边写😭。

下午抽空,在K8S的Promethues上接入了consul。完成了微服务的JVM监控。

image.png

记录一下步骤:

  1. 创建generic secret ,内容为Prometheus 的 consul自动发现配置。

    1
    2
    3
    4
    5
    6
    7
    8
    [root@ip-10-20-1-93 consul-prometheus]# cat prometheus-additional.yaml
    - job_name: 'consul-dop-dev1'
    scrape_interval: 15s
    consul_sd_configs:
    - server: dop-otter-consul-dev1.dop-dev1.svc.cs-software.local:8500
    [root@ip-10-20-1-93 consul-prometheus]# kubectl create secret generic additional-scrape-configs --from-file=prometheus-additional.yaml --dry-run -oyaml > additional-scrape-configs.yaml
    [root@ip-10-20-1-93 consul-prometheus]# kubectl apply -f additional-scrape-configs.yaml -n prometheus
    secret/additional-scrape-configs configured
  2. 修改CRD Prometheus

    1
    2
    3
    4
    5
    # 在spec中增加additionalScrapeConfigs的定义
    spec:
    additionalScrapeConfigs:
    key: prometheus-additional.yaml
    name: additional-scrape-configs
  3. Reload 一下

    1
    [root@ip-10-20-1-93 consul-prometheus]# curl -X POST http://10.20.210.204:9090/-/reload
  4. Grafana 导入 Spring Boot 2.1 Statistics

    很快看到结果。


mysql锁

内容仅为个人理解,我也在不断的纠正自己的理解。

1
2
3
-- 以下讨论都基于:Innodb,事务隔离机制采用RR
select @@global.tx_isolation;
REPEATABLE-READ

First Of All,Mysql数据库在事务隔离级别为RR的情况下,如果要避免幻读,就要通过间隙锁。那么如何最简单的实现间隙锁,就是通过select语句中 for update 排他锁。正常的select语句是不会加锁的。而是通过访问记录的版本链的过程,实现MVCC(多版本并发控制),也可以简单的理解为快照读,并不加锁。在select for update, 还有 insert, update, delete 的时候,就需要考虑锁机制了。另外mysql的锁,锁定的是索引,并不是数据。

行锁算法(注意是算法哦)

  • 普通行锁

    唯一索引,且查询的记录存在

  • 间隙锁

    锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。

    常见于,键值不存在条件范围内,叫做间隙(GAP),引擎会对该“间隙”加锁。

  • Next-key Lock(行锁&间隙锁)

    相当于两种锁的结合。锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。

锁的类型:

共享锁(S)、排他锁(X)、意向共享(IS)、意向排他(IX)

行锁,表锁其实是锁的粒度的概念,共享锁和排它锁是具体的实现。

共享锁(S): 允许其他事务来读取,但是阻止其他事务,在该行获取该行的排它锁。简单的记忆:能读不能写。共享锁的查询举例: select … lock in share mode。

排它锁(X): 允许持有排它锁的事务读写数据,阻止其他事务获取该行的共享锁和排它锁。但是其他事务还是可以读的。(普通的select语句,并不影响数据查询)。

1
2
3
4
5
-- 排它锁的举例:
update ...
delete ...
insert ...
select ... for update

间隙锁之举例

前言:始终不要忘记间隙锁的目的,是为了解决幻读。所以开发工作中,要主动思考,规避在某些并发环境下,会出现幻读,常见做法便是:在sql中加入for update,或是乐观锁。在运维工作中,也要留意数据库,出现同样间隙锁的性能问题。(遇到过,数据库大量的insert 导致的间隙锁)


Docker Cache

Docker 镜像在build的过程中,默认会有一套缓存机制。

用自己的语言直白描述 ,能更容易记住呢。

  1. Docker 镜像默认是一层一层的叠加上去。dockerfile中的每一行,对应就是一层。

  2. 每次构建,Docker会比对每一层是否有变化,如果构建命令没有–no-cache=true 参数,默认都会尽量用上缓存。

  3. Docker如何判断这一层是否变化呢。ADD,COPY的层,docker会去比对文件,计算文件的校验和。(校验和中不考虑最后修改和最后访问时间)。其余的层,会去比对字符串。一旦其中一层发生了变化,在此之上的所有层,都不会再用到缓存了。

学了原理,当然要知道怎么优雅的使用呢:

  • 据一个🌰
1
2
3
from centos-alpha
RUN yum update -y && yum install epel -y && yum xxxx
CMD COMMAND

如上,我们在调试dockerfile的时候,常常会多次构建,过程中,可能发现缺少依赖包,变反复修改第二层的RUN指令。

而yum update -y ,却是每次最耗时的指令。如果独立层单独一层,就发现速度飞快了。

  • 再举一个🌰
1
2
3
FROM node-alpha
COPY . /src
RUN cd /src && npm install

如上,第三层中,每一次npm install 会安装package.json中指定的依赖。而如果一个项目在定型后,package.json在变更的频率会非常少,于是聪明的做法是将其改为

1
2
3
4
5
FROM node-alpha
COPY package.json
RUN npm install
COPY . /src
RUN xxxx

这样,在第二层package.json文件不变的情况下,在jenkins 构建编译的时候,可以重复利用第二层。速度也随之加快多了。

好的,那么新的问题来了,docker 镜像在开发测试与生产环境物理距离遥远的企业中,应该如何放置呢。像现在的公司,开发测试等环节都在内部的办公机房里。而生产环境远在aws 俄勒冈。镜像的同步受网络条件制约。对此我们内部出现了多种设想。

  1. 异地编译,生成镜像。同事说在aws环境内,单独完成生产环境的构建编译镜像封装。

  2. 镜像同步,开发测试环境,在稳定后将镜像同步到特定的仓库。改仓库再通过mirror同步到海外的registey。

    我选择了2。原因是容器的目的之一,便是将运行环境也纳入到版本控制中,方案一存在巨大的漏洞,两次构建编译,其中存在不确定因素,所以被彻底否决。


aws s3 static website

Aws S3 加上cloudfront,可以直接代替nginx。并且有更高的可用性。于是告诉给架构师这个方案后,就开始配置了。

  1. 开bucket,设置静态托管

image.png

  1. 设置bucket policy,所有资源均为public

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    "Version": "2012-10-17",
    "Id": "Policy1586756429373",
    "Statement": [
    {
    "Sid": "Stmt1586756xxxx5",
    "Effect": "Allow",
    "Principal": "*",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::xxx-speedup/*"
    }
    ]
    }
  2. 开始Cloudfront,限定只允许GET,HEAD 两种method。还有开启Gzip。暂时用最简单的配置。

    image.png

  3. 上传资源到S3

    Jenkins plugin 本有一个S3插件,可是依赖maven 3.5版本,由于现在开发团队在用3.4,于是放弃该插件。

    改用传统的步骤:jenkins执行shell,

    AWS_ACCESS_KEY_ID与AWS_SECRET_ACCESS_KEY,就取巧通过参数化构建过程带入吧!恰巧这个插件会将参数设置为环境变量,所以就这么办了。(Pipeline + Credentials会更加优雅)

    注意上传前,自己先gzip一下,因为S3可是不会自动帮你压缩的。当然这个也可以通过event驱动Lambda来优雅实现。

    1
    2
    find ./ -type f -exec gzip "{}" \; -exec mv "{}.gz" "{}" \;
    aws s3 mv ./ s3://xxx-speedup --recursive --exclude ".git/*" --content-encoding "gzip"

    image.png


WHY docker?

Docker/Kubernetes 天天弄,可是又有多少底层技术深刻的记在心里呢。

原理:COW (Copy On Write)

Docker 镜像由多个只读层叠加而成,启动容器时,Docker会加载只读镜像层,并在镜像栈顶部添加一个读写成。如果运行的容器修改了一个已经存在的文件。那么文件就会从只读层复制到读写层,该文件的只读版本依然存在。

理解原理后,便是要思考如何运用到工作中🥶。

  • 容器最后的读写层,可能一般包含了修改的文件。 所以应该避免将容器,docker commit 的方式提交到registry。

  • docker ps -s 可以看到容器总共的size(含读写层),以及读写层的size。

  • docker history 看到的 某些层为missing,没有标注ID。其实是因为这些层不在这台电脑上封装。

    image.png

base 镜像 - Linux最小安装的Linux发行版

  • 在Dockerhub上拉去一个centos镜像,看到只有200mb。困惑。其实这是因为docker镜像在运行时,使用的是docker宿主机的kernel。Linux操作系统由内核空间和用户空间组成。

    image.png

My Question

那么相同的两个镜像,做了少许改动后,push到harbor,客户端需要把镜像每一层都重新推到harbor上吗?答案是no。registry 会做一个判断,如果层的校验值一致,其实会提示Layer already exists。直接跳过。

image.png


网络地址分配

A类地址: 1.0.0.0 ~ 127.255.255.255

B类地址:128.0.0.0 ~ 191.255.255.255

C类地址:192.0.0.0 ~ 223.255.255.255

D类地址:224.0.0.0 ~ 239.255.255.255 (用作广播用途)

E类地址:240.0.0.0 ~ 255.255.255.255(保留)

这就将全球的所有ipv4地址划分成了5类,那么又再其中的部分划出来,给局域网使用。

A类地址中的: 10.0.0.0/8

B类地址中的: 127.16.0.0/12

C类地址中的: 192.168.0.0/16

接下来快速计算一下: 192.168.1.0/255.255.255.252 ,即掩码为30

网络地址:192.168.0000 0001.0

掩码:1111 1111.1111 1111.1111 1111.1111 1100

与运算后结果,即为网络IP:192.168.1.0

根据判断两个ip是否在同一个子网的方法:与掩码运算后,结果一致,就是同一个子网内。

这么推理。

最小IP: 192.168.1.0

最大IP:192.168.1.255

当然最大最小,分别留作网络ID还有广播地址。

子网数:


MySQL事务隔离级别
  1. 什么是脏读: 读到没有commit的数据

  2. 什么是不可重复读:同一个事务中,两次读取到的数据不一样。

  3. 什么是幻读同一个事务中,两次读取到的数据不一样。这个流程看起来和不可重复读差不多,但幻读强调的集合的增减,而不是单独一条数据的修改。最常见的场景便是

事务的隔离级别

1
select @@global.tx_isolation;

级别逐级别提高,越高数据隔离的越彻底。可是代价是付出性能。

  • value 为0

    读未提交(Read Uncommitted),在这个级别下,所有事务都能看到其他未提交的事务的数据,基本很少用到。

  • value 为1

    一个事务只能看见已经提交事务所做的改变。解决脏读的问题,存在不可重复读,幻读的问题。

  • 可重复读 REPEATABLE-READ | 2

    MySQL默认的级别,解决脏读,不可重复读。但是存在幻读的。接下来就是要讨论如何避免了。

  • 序列化 SERIALIZABLE | 3

    强迫事务排序,串行执行事务,从而杜绝上面的所有问题,但是会带来大量的等待,基本没有使用。

在RR(READTABLE-READ)级别下,如何避免幻读,答案是显式加锁。

简单的场景:

A 事务:查询表是否存在id=10000,如果无,就插入数据。

就在插入的前一刻,另外一个事务插入了id=10000的数据,此刻A事务就会莫名收到数据已经存在的错误,也无法插入成功。这就是典型的幻读了。如何解决呢,用SELECT FOR UPDATE,无论数据是否存在,都会被加上锁。(当记录不存在的时候,mysql 会给索引加上gap间隙锁,对此可以模拟一下哦)