2020年07月

2020-7-1 周三

Redis 简单限流

滑动窗口

 def is_action_is_allowed(user_id, action_key, period, max_count):
    key = 'hist:%s:%s' % (user_id, action_key)
    now_ts = int(time.time() * 1000)  # 毫秒时间戳
    with r.pipeline() as pipe:
        #  value:score 都是毫秒时间戳
        pipe.zadd(key, {now_ts: now_ts})
        # 移除时间窗口之前的操作记录
        pipe.zremrangebyscore(key, 0, now_ts - period * 1000)
        # 获取窗口内的行为数量
        pipe.zcard(key)
        # 设置 zet 过期时间,避免冷用户持续占用内存
        pipe.expire(key, period + 1)  # 过期时间再宽限一秒
        _, _, current_count, _ = pipe.execute()
    return current_count <= max_count

 
 for i in range(8):
    print(is_action_is_allowed("tabll6", "replay", 60, 5))  # 60秒内限制5次操作

 

返回:

 True
True
True
True
True
False
False
False

 

2020-7-2 周四

Redis 漏斗限流

基本原理就是按照代码中的逻辑来

实际情况是有更加完美的包解决的:Redis-Cell

 class Funnel(object):

    def __init__(self, capacity, leaking_rate):
        self.capacity = capacity          # 漏斗容量
        self.leaking_rate = leaking_rate  # 流水速率
        self.left_quota = capacity        # 漏斗剩余空间
        self.leaking_ts = time.time()     # 上一次漏水时间

    def  make_space(self):
        now_ts = time.time()
        # 距离上一次漏水的时间
        delta_ts = now_ts - self.leaking_ts
        # 可以腾出的空间
        delta_quota = delta_ts * self.leaking_rate
        # 如果空间不足则下次再说
        if delta_quota < 1:
            return
        self.left_quota += delta_quota  # 增加剩余空间
        self.leaking_ts = now_ts  # 记录漏水时间
        # 剩余空间不得高于容量
        if self.left_quota > self.capacity:
            self.left_quota = self.capacity
    
    def watering(self, quota):
        self.make_space()
        # 判断剩余空间是否足够
        if self.left_quota >= quota:
            self.left_quota -= quota
            return True
        return False

 
 funnels = {}  # 所有的漏斗

 
 # 检查执行是否被允许
def is_action_allowed(user_id, action_key, capacity, leaking_rate):
    key = '%s:%s' % (user_id, action_key)
    funnel = funnels.get(key)
    if not funnel:
        funnel = Funnel(capacity, leaking_rate)
        funnels[key] = funnel

    return funnel.watering(1)

 
 for i in range(20):
    print(is_action_allowed("tabll6", "replay", 15, 5))  # 容量 15, 流水速率 5/s

 

返回:

 True
True
True
True
True
True
True
True
True
True
True
True
True
True
True
False
False
False
False
False

 

2020-7-3 周五

Redis-Cell

这个也是需要额外安装并启用的模块,是比较完美的漏斗限流解决方案

 r.execute_command('CL.THROTTLE', "tabll6:get", 15, 10, 20, 1)  # key / 15漏斗容量 / 10 option 10个操作 / 20 seconds 每20秒 / need 1 quota

 

1) (integer) 0 # 0 表示允许,1 表示拒绝
2) (integer) 15 # 漏斗容量 capacity
3) (integer) 14 # 漏斗剩余空间 left_quota
4) (integer) -1 # 如果拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)
5) (integer) 2 # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)

相关文档:https://github.com/brandur/redis-cell

2020-7-6 周一

代码圈块复杂度

最近安装了 SonarLint 插件,发现项目中非常多的 function 被提示需要优化

 SonarLint: Refactor this function to reduce its Cognitive Complexity from 16 to the 15 allowed. [+15 locations] more... (Ctrl+F1)

 

圈复杂度 (Cyclomatic Complexity) 是一种代码复杂度的衡量标准,由 Thomas McCabe1976 年定义。它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。圈复杂度大说明程序代码的判断逻辑复杂,可能质量低且难于测试和维护。程序的可能错误和高的圈复杂度有着很大关系。

一般来说圈复杂度V(G) 与代码质量的关系如下:
V(G) ∈ 0 , 10:代码质量不错;
V(G) ∈ 11 , 15:可能存在需要拆分的代码,应当尽可能想措施重构;
V(G) ∈ [16 , ∞):必须进行重构;

参考:圈复杂度详解以及解决圈复杂度常用的方法

降低圈复杂度

Extract Method(提炼函数)

Substitute Algorithm(替换你的算法)

Decompose Conditional(分解条件式)

Consolidate Conditional Expression(合并条件式)

Consolidate Duplicate Conditional Fragments(合并重复的条件片断)

Remove Control Flag(移除控制标记)

Parameterize Method(令函数携带参数)

2020-7-7 周二

GitLab 与 GitHub 同步

GitHub 最近开启了”北极代码库”计划,会将所有的代码在北极存一份。有点末日种子库的感觉,以后人类还能看到千年前的写BUG

备份还是必要的,防止自建的GitLab有一天挂了,现在打算将GitLab上的代码都同步一份到GitHub上面去

步骤很简单,不过好像基础版的GitLab暂时不支持镜像同步的功能

首先在GitHub上面创建一个新的仓库,公开或者私有都可以,支持两边的查看权限不一致

然后在GitLab中找到仓库的设置->仓库->镜像仓库

填写仓库URL地址,注意这里的仓库URL地址需要拼接上你的用户名

比如 https://XXXXX@github.com/XXXXX/ABC.git

镜像的方向是推送,你也可以反向的从GitHub上面拉取

密码是在GitHub上面生成的Token

完成之后它不会立即同步,可能需要等一会儿,你可以点击立即同步按钮,让它立即执行

这个同步只会同步仓库的代码和commits记录,合并请求ci/cd这些都不会同步过去,目前感觉还是gitlab的ci好用

2020-7-8 周三

网页速度测试

Google 的工具

PageSpeed Insights

PageSpeed Insights

在这里不仅可以看到页面的评分,还可以看到当前可以优化的选项
需要执行什么操作都写的很清楚

Lighthouse 插件

Lighthouse

这个其实就是 Google 的工具整合成了一个网页插件,很方便的在浏览网页的时候能够随时随地分析一波

其它工具

站长之家

它可以统计并显示出国内外相关节点的访问速度

优化完成

2020-7-9 周四

VI 删除所有行

在命令行模式下输入:

 :.,$d

 

会删除从当前行直到末尾的数据

2020-7-10 周五

MySQL JSON 查询

address_ids 的格式是 [1, 2, 3],数据库中存储的类型是 JSON

查询包含了 3 的字段

 SELECT * FROM users where JSON_CONTAINS(address_ids, '3', '$');

 

假如 address_ids 的格式是 {"a": [1,2,3]}

 SELECT * FROM users where JSON_CONTAINS(address_ids, '3', '$.a');

 

2020-7-13 周一

升级 NodeJS 版本

 npm cache clean -f
npm install -g n
n stable

 
 installing : node-v12.18.3
       mkdir : /usr/local/n/versions/node/12.18.3
       fetch : https://nodejs.org/dist/v12.18.3/node-v12.18.3-linux-x64.tar.xz
   installed : v12.18.3 (with npm 6.14.6)

Note: the node command changed location and the old location may be remembered in your current shell.
         old : /usr/bin/node
         new : /usr/local/bin/node
To reset the command location hash either start a new shell, or execute PATH="$PATH"

 

2020-7-14 周二

NPM 淘宝镜像

npm install -g cnpm –registry=https://registry.npm.taobao.org

npm install sitespeed.io@13.3.0 -g

apt install XVFB

2020-7-15 周三

Apache 反向代理

需要将 A/api/inner/prs/*

代理到 B/api/*

Apache 版本 2.4

 <VirtualHost *:80>
    ServerName tabll.com
    ServerAlias sh.tabll.com sz.tabll.com hz.tabll.com
    ProxyRequests on
    DocumentRoot "E:/www/public"
    DirectoryIndex demo.html index.php
    <Directory "E:/www/public">
        Options +Indexes +FollowSymLinks +ExecCGI
        AllowOverride All
        Order allow,deny
        Allow from all
        Require all granted
    </Directory>
    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>
    <Location /api/inner/prs>
        ProxyPass http://192.168.0.161:8030/api
        ProxyPassReverse http://192.168.0.161:8030/api
    </Location>
    <Location /api/inner/crm/prs>
        ProxyPass http://192.168.0.161:8030/api
        ProxyPassReverse http://192.168.0.161:8030/api
    </Location>
</VirtualHost>

 

这里需要注意的是URL的结尾不要多一个 / 否则会报 404

2020-7-16 周四

PHP 加速

有一个 opcache 可以用

 [opcache]
;DLL地址
zend_extension=php_opcache.dll;
;开关打开
opcache.enable=1
;开启CLI
opcache.enable_cli=1
;可用内存, 酌情而定, 单位为:Mb
opcache.memory_consumption=528
;Zend Optimizer + 暂存池中字符串的占内存总量.(单位:MB)
opcache.interned_strings_buffer=8
;对多缓存文件限制, 命中率不到 100% 的话, 可以试着提高这个值
opcache.max_accelerated_files=10000
;Opcache 会在一定时间内去检查文件的修改时间, 这里设置检查的时间周期, 默认为 2, 定位为秒
opcache.revalidate_freq=1
;打开快速关闭, 打开这个在PHP Request Shutdown的时候回收内存的速度会提高
opcache.fast_shutdown=1

 

参考链接:PHP性能加速Opcache的开启与设置

2020-7-17 周五

mkdir 参数

-p

-p 直接创建一个不存在的目录下的子目录,而不会报错

-m

-m 777 创建权限为 777 的目录

-v

-v 显示操作的详细结果

2020-7-20 周一

GitLab 浏览器性能测试

按照官方文档无法正常执行

 browser_performance:
  stage: performance
  image:
    name: sitespeedio/sitespeed.io:13.3.0
    entrypoint: [""]
  tags:
    - quality
  allow_failure: true
  variables:
    URL: https://test.tabll.cn/
    SITESPEED_VERSION: 13.3.0
    SITESPEED_OPTIONS: ''
    SITESPEED_IMAGE: sitespeedio/sitespeed.io
  script:
    - mkdir -p gitlab-exporter
    - wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/1.0.1/index.js
    - mkdir -p sitespeed-results
    - /start.sh --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL -b chrome
    - mv sitespeed-results/data/performance.json browser-performance.json
  artifacts:
    paths:
      - sitespeed-results/
    reports:
      browser_performance: browser-performance.json
  needs: ["sast", "license_scan", "secret_detection", "dependency_scan"]

 

2020-7-21 周二

GitLab 负载测试

这个的官方文档也有问题,按照步骤无法正常执行

 load_performance:
  stage: performance
  tags:
    - quality
  image:
    name: loadimpact/k6:0.26.2
    entrypoint: [""]
  allow_failure: true
  variables:
    K6_IMAGE: loadimpact/k6
    K6_VERSION: 0.26.2
    K6_TEST_FILE: test.tabll.cn/js/k6-test.js
    K6_OPTIONS: ''
  script:
    - k6 run $K6_TEST_FILE --summary-export=load-performance.json
  artifacts:
    reports:
      load_performance: "load-performance.json"
  needs: ["sast", "license_scan", "secret_detection", "dependency_scan"]

 

2020-7-22 周三

哈夫曼编码

2020-7-23 周四

Docker 指定阿里云拉取

docker pull registry.cn-hangzhou.aliyuncs.com/XXX/XXX:latest

2020-7-24 周五

HTTP 2.0 的优缺点

HTTP 1.0 的缺点

连接无法复用:导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大

head of line blocking:导致带宽无法被充分利用,以及后续健康请求被阻塞。后续从应用层发出的请求只能排队,请求2,3,4,5只能等请求1的response回来之后才能逐个发出

HTTP 2.0 的优点

二进制分帧:在应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层

多路复用 (Multiplexing)、连接共享:HTTP/2 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。并行地在同一个 TCP 连接上双向交换消息
每个stream都可以设置又优先级(Priority)和依赖(Dependency)

首部压缩(Header Compression):使用了专门为首部压缩而设计的 HPACK 算法

服务端推送(Server Push):服务器可以对客户端的一个请求发送多个响应

重置连接表现更好:引入 RST_STREAM 类型的 frame,可以在不断开连接的前提下取消某个 requeststream

流量控制(Flow Control):通过sliding window的算法来做流量控制。发送方有个sending window,接收方有receive window

更安全的SSL:使用了 tls 的拓展 ALPN 来做协议升级

详细:再谈HTTP2性能提升之背后原理—HTTP2历史解剖

2020-7-27 周一

Go 语言编码规范

CodeReviewComments:https://github.com/golang/go/wiki/CodeReviewComments

2020-7-28 周二

Go 语言垃圾回收机制(GC)

触发条件

1 超过内存大小阈值
2 达到定时时间

阈值是由一个gcpercent的变量控制的,当新分配的内存占已在使用中的内存的比例超过gcprecent时就会触发。比如一次回收完毕后,内存的使用量为5M,那么下次回收的时机则是内存分配达到10M的时候。也就是说,并不是内存分配越多,垃圾回收频率越高。
一直达不到内存大小的阈值,GC就会被定时时间触发

2020-7-29 周三

GitLab Runner 里安装 Go

 wget -o https://studygolang.com/dl/golang/go1.14.6.linux-amd64.tar.gz

tar xfz go1.14.6.linux-amd64.tar.gz -C /usr/local

# 修改~/.bashrc
vim ~/.bashrc
# 添加 Gopath 路径
export GOPATH=/usr/local/go
export PATH=$GOPATH/bin:$PATH

# 激活配置
source ~/.bashrc

# 查看安装的版本
go version
warning: GOPATH set to GOROOT (/usr/local/go) has no effect
go version go1.14.6 linux/amd64


# 变量修改为以下配置
vim ~/.bashrc
export GOROOT=/usr/local/go
export GOPATH=$PATH:$GOROOT/bin

# 激活配置
source ~/.bashrc

# 查看安装的版本
go version
go version go1.14.6 linux/amd64

 

2020-7-30 周四

Go CI

原先是想在 shrunner 里面运行的,现在是使用的 dockerrunner

 # This file is a template, and might need editing before it works on your project.
image: golang:1.14.6

variables:
  # Please edit to your GitLab project
  REPO_NAME: gitlab.tabll.cn/Tabll/bellflower-go

# The problem is that to be able to use go get, one needs to put
# the repository in the $GOPATH. So for example if your gitlab domain
# is gitlab.com, and that your repository is namespace/project, and
# the default GOPATH being /go, then you'd need to have your
# repository in /go/src/gitlab.com/namespace/project
# Thus, making a symbolic link corrects this.
before_script:
  - mkdir -p $GOPATH/src/$(dirname $REPO_NAME)
  - ln -svf $CI_PROJECT_DIR $GOPATH/src/$REPO_NAME
  - cd $GOPATH/src/$REPO_NAME
  - go env -w GO111MODULE=on
  - go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/

stages:
  - test
  - build
  - deploy

format:
  stage: test
  tags:
    - quality
  script:
    - go fmt $(go list ./... | grep -v /vendor/)
    - go vet $(go list ./... | grep -v /vendor/)
    - go test -race $(go list ./... | grep -v /vendor/) -cover

compile:
  stage: build
  tags:
    - quality
  script:
    - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/mybinary
  artifacts:
    paths:
      - mybinary

 

2020-7-31 周五

开始用 Go 语言重写博客


欢迎阅读「 日志 」
唤醒精灵