0%

Redis

示例环境

1
2
3
python 3.10
redis 6.2
python redis 4.1.0

本实例所有包

1
2
3
from redis import StrictRedis, ConnectionPool
from pprint import pprint
from concurrent.futures.process import ProcessPoolExecutor

Basic

Basic Client

1
2
3
4
5
6
7
8
9
rds = StrictRedis(
host='127.0.0.1',
port=3218,
# redis 6.0 以后需要加入,默认为 default
username='default',
password='xxxxwadsad',
decode_responses=True,
db=1,
)

Basic Client Based On URL

1
2
3
rds_url = 'redis://default:xxxxwadsad@127.0.0.1:3218/1'
rds = StrictRedis.from_url(rds_url)
pprint(rds.info())

Connection Pool

Connection Pool class

1
2
3
4
5
6
7
8
9
10
11
connection_pool = ConnectionPool(
host='127.0.0.1',
port=3218,
# redis 6.0 以后需要加入,默认为 default
username='default',
password='xxxxwadsad',
decode_responses=True,
db=1,
)
connection_pool_client = StrictRedis(connection_pool=connection_pool)
pprint(connection_pool_client.info())

Connection Pool class Base on URL

1
2
3
4
rds_url = 'redis://default:xxxxwadsad@127.0.0.1:3218/1'
connection_pool = ConnectionPool.from_url(rds_url)
connection_pool_client = StrictRedis(connection_pool=connection_pool)
pprint(connection_pool_client.info())

Pipeline

Batch process, Don’t forget to use pipeline.execute() after the batch is finished

1
2
pipeline = rds.pipeline(transaction=True)
pipeline = connection_pool_client.pipeline(transaction=True)

multiprocessing batch

1
2
3
4
5
6
7
8
9
10
def process_item():


# pipeline process logic

def main():
with ProcessPoolExecutor(max_workers=6) as p:

     for _ in range(100):
p.submit(process_item)

tips: IO密集型用线程、协程,CPU密集型用进程

读书笔记

— 应对逆境时候的能力

最害怕死亡的人是那些知道自己从未真正活过的人. — 梭罗

一个人知道自己为什么而活,就可以忍受任何一种生活. —尼采

什么是逆商?

一种测量工具,可以让你相较于周围的人。一套哲学,一种生活方式。怎么样看待、应对“逆境”?

CORE维度

  • Control 掌控感
  • Ownership 担当力
  • Reach 影响度
  • Endurance 持续性

Control 掌控感

所谓掌控感,是你对自己能力的确信。面对逆境时,你是否会变得束手无策?一旦你拥有掌控感,你会发现,无论遇到什么困难你都会抓住自己能够掌控的一部分。想拥有掌控感,首先就要坚信“没有什么做不到的事情”。有了这种想法,我们才会给大脑发出积极的信号,从而促进我们对问题采取措施,而不是手忙脚乱,陷入僵局。

Ownership 担当力

所谓担当力,是一个人对责任的担当能力。敢于主动担当责任的人往往具有更高的行动力。在遇到困境时,勇于担当责任的人更善于直面困境,寻找解决办法。责任意识让他们必须采取恰当的行动。也正因为如此,具有高担当力的人有更强的战胜逆境的能力。

Reach 影响度

所谓影响度,是困境对我们生活产生的影响。一个人逆商的高低,很大程度上取决于人处于困境时,是否被消磨斗志,丧失动力。逆商低的人会无限放大一件微不足道的小事,不断给大脑消极的暗示,甚至对自己的能力和产生怀疑。长时间处于这种思维状态中,日常生活会受到严重侵蚀,甚至产生心理疾病。

Endurance 持续性

所谓持续性,是逆境对于个体来说会持续多久。对于高逆商的人来说,一时处于困境之中并无大碍,他们永远相信,今天过去,明天会更美好。而低逆商的人会持续受到一次打击的影响,长时间活在逆境造成的阴影下。他们把失败的原因归结为一些不可变因素,如认定自己智力不如他人,从而放弃挣扎。久而久之,人会变得低迷懈怠,陷入死循环中。

三大支柱

  • 认知心理:ABCDE、

    • 习得性无助
    • 归因理论和乐观精神
    • 心理韧性
    • 内外控倾向
  • 健康新论(心理神经免疫学):传递情绪化学物质会影响人的身体健康

  • 脑科学:习惯的神奇力量

为什么需要逆商?

人生不断的攀越逆境

面对逆境的三种人

  • 放弃者:只需要安逸
  • 扎营者:努力过后的安逸
  • 攀登者:一直前进

逆境来源

社会、职场、个人

危险的岔道

停止努力,安于现状

寄托于科技

打击鸡血

无助 - > 无望的绝望循环

如何提高逆商?

LEAD工具

利用LEAD工具增强掌控感,击退消极情绪

LEAD是什么?

  • Listen 聆听(自己的逆境的反应)
  • Explore 探究(对结果的担当)
  • Analyze 分析
  • Do 做点事情

漏斗法:引导自己抵抗重大挫折 -> 避开受害者心态 -> 障碍物

分心法:停止恶化与蔓延 -> 关注不相关的事物 -> 弹走负面思想 -> 积极干扰 -> 运动改变状态

重塑法:明确目标 -> 渺小化问题 -> 帮助他人

ABCDE

A:Activating event

B:Belief

C:Consequence

D:Disputation

E:Energization

php

安装php

1
2
# homebrew 安装composer、php
brew install composer

安装composer会自动安装php

配置composer

https://developer.aliyun.com/composer

1
2
3
4
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
composer self-update
composer diagnose
composer clear

Checking pubkeys: FAIL

1
2
3
4
Checking pubkeys: FAIL
Missing pubkey for tags verification
Missing pubkey for dev verification
Run composer self-update --update-keys to set them up

打开pubkeys 将图中内容放在第一次的input。

image-20220404021653505

将图中内容放在第二次的input。

image-20220404021716092

https://github.com/composer/composer/issues/4839

配置php

1
brew services restart php

配置Xdebug

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
# 下载xdebug
pecl install xdebug
# 配置xdebug
# set xdebug ini
cat << EOF > /opt/homebrew/etc/php/8.1/conf.d/ext-xdebug.ini
[xdebug]
zend_extension = "/opt/homebrew/Cellar/php/8.1.4/pecl/20210902/xdebug.so"
xdebug.mode = "debug"
xdebug.remote_mode = "req"
;是否开启远程调试自动启动
xdebug.remote_autostart = 1
;是否开启远程调试
xdebug.remote_enable = 1
;允许调试的客户端IP
xdebug.remote_host= "localhost"
;远程调试的端口(默认9000)
xdebug.remote_port = 9001
;调试插件dbgp
xdebug.remote_handler="dbgp"
;是否收集变量
xdebug.collect_vars = 1
;是否收集返回值
xdebug.collect_return = 1
;是否收集参数
xdebug.collect_params = 1
;是否开启调试内容
xdebug.profiler_enable= 1
;设置php显示的级别长度
xdebug.var_display_max_depth=10
xdebug.idekey = PHPSTROM
EOF

禁止更新

homebrew更新时会随之将路径打散,在非必要情况建议禁止自动更新php

1
brew pin php

高质量代码修炼

为什么需要高质量代码?

更规范更结构更便于开发更易修复与阅读

你写的每一句代码都是你的label,你的id card

什么是高质量代码

没有对比就没有伤害,代码亦是如此。那么什么才是高质量,好代码呢?有没有哪些可明确度量的维度呢?

  • 可维护性(maintainability)
  • 可读性(readability)
  • 可扩展性(extensibility)
  • 灵活性(flexibility)
  • 简洁性(simplicity)
  • 可复用性(reusability)
  • 可测试性(testability)

如何才能编写出高质量,好代码

写出满足这些评价标准的高质量代码,我们需要掌握一些更加细化、更加能落地的编程方法论,包括面向对象设计思想、设计原则、设计模式、编码规范、重构技巧等。而所有这些编程方法论的最终目的都是为了编写出高质量的代码。

比如,面向对象中的继承、多态能让我们写出可复用的代码;编码规范能让我们写出可读性好的代码;设计原则中的单一职责、DRY、基于接口而非实现、里式替换原则等,可以让我们写出可复用、灵活、可读性好、易扩展、易维护的代码;

设计模式可以让我们写出易扩展的代码,持续重构可以时刻保持代码的可维护性等等

核心修炼技巧

发现代码

学习与实现”好”代码

持续

Code Style

现在主流的编程范式或者是编程风格有三种,它们分别是函数式编程、面向过程、面向对象。

现在比较流行的编程语言大部分都是面向对象编程语言。大部分项目也都是基于面向对象编程风格开发的。面向对象编程因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式编码实现的基础。

面向对象的四大特性:封装、抽象、继承、多态

面向对象编程与面向过程编程的区别和联系面向对象分析、面向对象设计、面向对象编程接口和抽象类的区别以及各自的应用场景基于接口而非实现编程的设计思想多用组合少用继承的设计思想面向过程的贫血模型和面向对象的充血模型

设计原则

设计原则是指导我们代码设计的一些经验总结。设计原则这块儿的知识有一个非常大的特点,那就是这些原则听起来都比较抽象,定义描述都比较模糊,不同的人会有不同的解读。所以,如果单纯地去记忆定义,对于编程、设计能力的提高,意义并不大。对于每一种设计原则,我们需要掌握它的设计初衷,能解决哪些编程问题,有哪些应用场景。只有这样,我们才能在项目中灵活恰当地应用这些原则。

你需要透彻理解并且掌握,如何应用下面这样几个常用的设计原则。

  • SOLID 原则 -SRP 单一职责原则
  • SOLID 原则 -OCP 开闭原则
  • SOLID 原则 -LSP 里式替换原则
  • SOLID 原则 -ISP 接口隔离原则
  • SOLID 原则 -DIP 依赖倒置原则
  • DRY 原则、KISS 原则、YAGNI 原则、LOD 法则

SRP原则:一个类只能承担一个事情

OCP:实现不可修改,但可面向扩展

LSP:子类型能够替换它们的基类型

ISP:抽象不持有特定逻辑,应持有实现的公有逻辑

DIP:高底层不能互相依赖,应同时依赖抽象

DRY:不做重复的事,不写重复类似的代码

KISS:keep it simple,尽量简单的代码,让代码更容易被别人理解

YANGNI:you ain’t gonna need it,只着眼必需的功能,不添加认为可能需要的功能

LOD:类应减少被外界直接访问的机会,类与类之间避免直接通信

设计模式

设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。大部分设计模式要解决的都是代码的可扩展性问题。设计模式相对于设计原则来说,没有那么抽象,而且大部分都不难理解,代码实现也并不复杂。它们又可以分为三大类:创建型、结构型、行为型

这一块的学习难点是了解它们都能解决哪些问题,掌握典型的应用场景,并且懂得不过度应用。

经典的设计模式有 23 种。随着编程语言的演进,一些设计模式(比如 Singleton)也随之过时,甚至成了反模式,一些则被内置在编程语言中(比如 Iterator),另外还有一些新的模式诞生(比如 Monostate)。

创建型

  • [x] 常用的有:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。
  • [ ] 不常用的有:原型模式。

结构型

  • [x] 常用的有:代理模式、桥接模式、装饰者模式、适配器模式。

  • [ ] 不常用的有:门面模式、组合模式、享元模式。

行为型

  • [x] 常用的有:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式。
  • [ ] 不常用的有:访问者模式、备忘录模式、命令模式、解释器模式、中介模式。

重构

在软件开发中,只要软件在不停地迭代,就没有一劳永逸的设计。随着需求的变化,代码的不停堆砌,原有的设计必定会存在这样那样的问题。

针对这些问题,我们就需要进行代码重构。重构是软件开发中非常重要的一个环节。持续重构是保持代码质量不下降的有效手段,能有效避免代码腐化到无可救药的地步。

而重构的工具就是我们前面罗列的那些面向对象设计思想、设计原则、设计模式、编码规范

实际上,设计思想、设计原则、设计模式一个最重要的应用场景就是在重构的时候。

我们前面讲过,虽然使用设计模式可以提高代码的可扩展性,但过度不恰当地使用,也会增加代码的复杂度,影响代码的可读性。在开发初期,除非特别必须,我们一定不要过度设计,应用复杂的设计模式。

而是当代码出现问题的时候,我们再针对问题,应用原则和模式进行重构。这样就能有效避免前期的过度设计。

对于重构这部分内容,需要掌握:

  • 重构的目的(why)、对象(what)、时机(when)、方法(how);

  • 保证重构不出错的技术手段:单元测试和代码的可测试性;

  • 两种不同规模的重构:大重构(大规模高层次)和小重构(小规模低层次)

五者之间的联系

关于面向对象、设计原则、设计模式、编程规范和代码重构,面向对象编程因为其具有丰富的特性(封装、抽象、继承、多态),可以实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。

设计原则是指导我们代码设计的一些经验总结,对于某些场景下,是否应该应用某种设计模式,具有指导意义。

比如,“开闭原则”是很多设计模式(策略、模板等)的指导原则。设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。应用设计模式的主要目的是提高代码的可扩展性。

从抽象程度上来讲,设计原则比设计模式更抽象。设计模式更加具体、更加可执行。编程规范主要解决的是代码的可读性问题。

编码规范相对于设计原则、设计模式,更加具体、更加偏重代码细节、更加能落地。持续的小重构依赖的理论基础主要就是编程规范。重构作为保持代码质量不下降的有效手段,利用的就是面向对象、设计原则、设计模式、编码规范这些理论。实际上,面向对象、设计原则、设计模式、编程规范、代码重构,这五者都是保持或者提高代码质量的方法论,本质上都是服务于
编写高质量代码
这一件事的。当我们追本逐源,看清这个本质之后,很多事情怎么做就清楚了,很多选择怎么选也清楚了。

Jetbrains Mono

Jetbrains Mono 字体据说是Jetbrains 公司对于开发人员而开发的字体,当然个人觉得也不错,所以在此安装以及使用一波。分别在Jetbrains和VSCode中,Mono 相关链接如下

Mono

Mono zh-cn

在Jetbrains编辑器中使用

设置

step 1

Preferences/Settings -> Editor -> Font,右边字体中选择 Jetbrains Mono,

我的设置

Size:13 Line hight:1.2 Enable Ligature

image-20211227145717002

Tip: 从 v2019.3 开始,JetBrains IDE 随附了最新版本的 JetBrains Mono。

step 2

进行了以上的设置之后可能还无法使用,此时我们需要在进一步的设置,如下

Preferences/Settings -> Editor -> Color Scheme -> Color Scheme Font

image-20211227150734888

效果如下

image-20211227151240648

在Vscode中使用

下载

image-20211227152516091

安装

image-20211227152554293

为了兼容性考虑,我建议是全部安装。当然也可仅安装Regular.tff也可

配置

在配置文件中修改为如下

存在即修改,不存在就新增。重启VS code即可

1
2
"editor.fontFamily": "JetBrains Mono, Menlo, Monaco, 'Courier New', monospace",
"editor.fontLigatures": true

显示如下所示

image-20211227153428863

算法

算法其核心主要思考的是快和省的问题,而算法复杂度可细分为时间复杂度空间复杂度。时间复杂度是指执行所需要的工作量,空间复杂度是指执行所需要的内存空间。

时间复杂度

Big O Notation

所有代码的执行时间 T(n) 与每行代码的执行次数 f(n) 成正比。

这就是大 O 时间复杂度表示法。

大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度。

image-20211224220519869

常见时间复杂度

从小到大

T(n) desc
Constant Complexity(常数复杂度)
Logarithmic Complexity(对数复杂度)
Linear Complexity(线性复杂度)
N Square Complexity(平方复杂度)
N cubic Complexity(立方复杂度)
Exponential Growth(指数)
Factorial (阶乘)

并列相加,嵌套相乘

每次折半log N,递归一般为指数

image-20211224223240782

时间复杂度分析

  1. 只关注循环执行次数最多代码块
  2. 加法原则:总复杂度等于量级最大的代码块的复杂度
  3. 乘法原则:嵌套代码的复杂度等于嵌套内外代码块复杂度的乘积

Example

1
2
3
4
# Python
x = 1
y = 2
z = x + y
1
2
3
4
i = 1
while (i <= 100):
i *= 2
print(i)
1
2
3
4
5
6
7
8
9
# Python
x = 1
y = 2

while (x <= 100):
x += 1

while (x <= 1000):
y += 1
1
2
3
4
5
6
7
8
9
10
11
12
13
# Python

for x in range(1, 10):
for y in range(1, x + 1):
print(f"{y} ^ {x} = {x * y}", end="\t")
print('')

print("-------")

for x in range(9, 0, -1):
for y in range(1, x + 1):
print(f"{y} ^ {x} = {x * y}", end="\t")
print('')

tip: x 为长, y 为高

最好、最坏、平均复杂度

1
2
3
4
5
6
7
8
def find_Girl(girl: list[int], number: int) -> int:
i, pos, n = 0, -1, len(girl)
while i < n:
if girl[i] == number:
pos = i
break
i += 1
return pos

最好情况时间复杂度(best case time complexity):最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度

最坏情况时间复杂度(worst case time complexity):最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度

平均情况时间复杂度(average case time complexity):

最好情况时间复杂度和最坏情况时间复杂度对应的都是极端情况下的代码复杂度,发生的概率其实都并不大。为了更好地表示平均情况下的复杂度, 使用平均情况时间复杂度
平均复杂度,顾名思义就是出现情况的概率,这个值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度。

均摊时间复杂度

均摊时间复杂度(amortized time complexity):

对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上。而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。尽管很多数据结构和算法书籍都花了很大力气来区分平均时间复杂度和均摊时间复杂度,均摊时间复杂度就是一种特殊的平均时间复杂度

空间复杂度

空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。

数组

空间复杂度(Space Complexity) 是对一个算法在运行过程中新开辟存储空间大小的量度.

记做

递归

不做任何优化,一般为指数阶。

bigocheatsheet

JavaScript

Hi,许久不见,我是Payne啊。

ok,那么我们先说说核心本质吧,其核心本质个人认为依旧是伪装。就像爬虫为对面对反爬虫反制,从而添加特征,不断的逼近真人。

自然在参数、环境还原这里也是。

无论是一下的补环境扣代码手动还原,也都是在模拟生成加密逻辑。

补环境

补环境的定义

补环境顾名思义就是,将获取到的关键JS在通过修补环境的方法在本地或服务器上能够正常运行获取到正确的返回值,从而完成参数的加密模拟。

当然大部分情况下补环境与抠代码密不可分(一般是扣代码,然后在补环境),扣代码移步第二部分

核心本质

无论是缺啥补啥的前的构建错误,还是使用代理监控运行。都是在做链路追踪。在补环境这里是秉承着遇河搭桥,缺啥补啥思路。

常用的方法一般有两个:

假设获取到了核心的JS代码后

一种是使用运行,从而构建错误实现报错,然后通过补方法、对象来fix。从而完成补环境,(当然这是必须的)。

当完成以上的操作后,遇见风控、遇见伏点。可以正常通过运行,但无法获取到正确的参数。此时使用Proxy来监听实现监测,协助调试。

实现

是方法写空方法arguments看传参,对象写空对象Proxy来相助,进行链路追踪,完成模拟

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
window = global;
document = {
location: {'protocol': 'https:'},
referrer: '',
getElementById: () => {
console.log("getElementById:", arguments);
},
getElementsByClassName: () => {
console.log("getElementsByClassName:", arguments);
},
getElementsByName: () => {
console.log("getElementsByName:", arguments);
},
getElementsByTagName: () => {
console.log("getElementsByTagName:", arguments);
},
getElementsByTagNameNS: () => {
console.log("getElementsByTagNameNS:", arguments);
},
createElement: () => {
console.log("createElement:", arguments);
}

};
navigator = {
appCodeName: "Mozilla",
appName: "Netscape",
appVersion: "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
language: "zh-CN",
languages: (2)['zh-CN', 'zh'],
platform: "MacIntel",
product: "Gecko",
productSub: "20030107",
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
vendor: "Google Inc.",
};
screen = {
availHeight: 1055,
availLeft: 0,
availTop: 25,
availWidth: 1920,
colorDepth: 24,
height: 1080,
pixelDepth: 24,
width: 1920,
};
location = {
href: 'https://www.xxx.com/website-login/captcha?redirectPath=https%3A%2F%2Fwww.xxx.com%2Fdiscovery%2Fitem%2F618bf8f60000000001027ccd',
hostname: 'www.xxx.com',
host: 'www.xxx.com',
};

小结

  1. 缺啥补啥:通过本地运行实现错误

Proxy

空对象:使用proxy代理,空方法:使用其中的arguments,获取传入参数

获取DOM节点:当做[ ]处理

创建DOM节点:当作{ }处理

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
null_function = () => {
console.log(arguments)
};


// 代理
const proxy = (obj, obj_name) => {
return new Proxy(obj, {
get: (target, key) => {
console.log(obj_name, "Getting", key)
return target[key];
},
})
}


//吐环境
function proxy(proxy_array) {
for (let i = 0; i < proxy_array; i++) {
eval(proxy_array[i] + ' = new Proxy(' + proxy_array[i] + ',{ ' +
'get(target,key){ ' +
'debugger;' +
'console.log("====================")' +
'console.log("获取了",' + proxy_array[i] + ' 的key属性"); ' +
'console.log("====================")' +
'return target[key]; }')
}
}

//常用的proxy_array
var proxy_array = ["window", "document", "location", "navigator"]
proxy(proxy_array)

抠代码

找到关键入口,抠下来🐶~。

重在调试,不再抠(抠就是复制粘贴)

小技巧:

打开浏览器的设置,找到如下所示的code filding 勾选打开。JS代码收缩复制一气呵成,妙啊~

image-20211223145757359

手动还原

通过调试查看算法实现过程,结合加密库复现。

Tip: 可以但没必要,手撕随爽,但可不要贪杯哦

pymysql

缘由

当我们需要使用pymysql进行大量数据的提取时,发现越来越慢。直到阻塞,down。

解决

使用SSDictCursor无缓冲游标来操作,使用fetchall_unbuffered,来进行。同时辅以分页查询即可。

首先是建立连接,curror 的选择

1
2
3
4
5
6
7
8
9
10
11
12
from pymysql import connect, cursors

client = connect(**{
"host": "127.0.0.1",
"port": 3306,
"user": "root",
"password": "123321",
"database": "test",
'charset': 'utf8'
}, cursorclass=cursors.SSCursor)

cursor = client.cursor()

注意在此可以选择无缓冲游标, 来操作。即cursors.SSCursor,cursors.SSDictCursor

1
2
3
4
5
6
page_count = 1000
for pg in range(1000):
query_sql = f"""select * from xiaohongshu_comment_note_2 limit {page_number * page_count}, {page_count};"""
cursor.execute(query_sql)
for results in cursor.fetchall_unbuffered():
print(results)

当然还可以在函数中这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def unbuffer_query(page_count=1000, max_page=1000):
"""
:param page_count:
:param max_page:
:return:
"""
for page_number in range(max_page):
query_sql = f"""select * from xiaohongshu_comment_note_2 limit {page_number * page_count}, {page_count};"""
cursor.execute(query_sql)
yield from cursor.fetchall_unbuffered()


for i in unbuffer_query(page_count=999, max_page=9999):
print(i)

瑞思拜~

JetBrains

缘由

由于不可描述的原因,我需要将JetBrains的所有已安装的编辑器进行清理。但我们知道MAC上单纯将应用软件中的软件拖入废纸篓是无法进行彻底删除的。

Tip: 有Cleaner One Pro或者clean my mac 专业版的(也就是付费版)的朋友可以直接在该软件中进行软件的卸载与清理,具体操作便不再此过多赘述

关键目录

1
2
3
4
5
~/Library/Application\ Support/JetBrains
~/Library/Logs/JetBrains
~/Library/Preferences/JetBrains.*
~/Library/Caches/JetBrains
/Applications

删除

Tip:建议先将/Applications 中的内容先进行删除

1
2
3
4
rm -rf ~/Library/Application\ Support/JetBrains/*
rm -rf ~/Library/Logs/JetBrains/*
rm -rf ~/Library/Preferences/JetBrains.*
rm -rf ~/Library/Caches/JetBrains/*

Evalreset Cleanr Script

Mac/Linux

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
#!/usr/bin/env sh
# reset jetbrains ide evals

OS_NAME=$(uname -s)
JB_PRODUCTS="IntelliJIdea CLion PhpStorm GoLand PyCharm WebStorm Rider DataGrip RubyMine AppCode"

if [ $OS_NAME == "Darwin" ]; then
echo 'macOS:'

rm -rf ~/Library/Logs/JetBrains/*
rm -rf ~/Library/Caches/JetBrains/*
rm -rf ~/Library/Preferences/JetBrains.*
for PRD in $JB_PRODUCTS; do
rm -rf ~/Library/Preferences/${PRD}*/*
rm -rf ~/Library/Application\ Support/JetBrains/${PRD}*/eval
done
elif [ $OS_NAME == "Linux" ]; then
echo 'Linux:'

for PRD in $JB_PRODUCTS; do
rm -rf ~/.${PRD}*/config/eval
rm -rf ~/.config/${PRD}*/eval
done
else
echo 'unsupport'
exit
fi

echo 'done.'

Windows

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
Set oShell = CreateObject("WScript.Shell")
Set oFS = CreateObject("Scripting.FileSystemObject")
sHomeFolder = oShell.ExpandEnvironmentStrings("%USERPROFILE%")
sJBDataFolder = oShell.ExpandEnvironmentStrings("%APPDATA%") + "\JetBrains"

Set re = New RegExp
re.Global = True
re.IgnoreCase = True
re.Pattern = "\.?(IntelliJIdea|GoLand|CLion|PyCharm|DataGrip|RubyMine|AppCode|PhpStorm|WebStorm|Rider).*"

Sub removeEval(ByVal file, ByVal sEvalPath)
bMatch = re.Test(file.Name)
If Not bMatch Then
Exit Sub
End If

If oFS.FolderExists(sEvalPath) Then
oFS.DeleteFolder sEvalPath, True
End If
End Sub

If oFS.FolderExists(sHomeFolder) Then
For Each oFile In oFS.GetFolder(sHomeFolder).SubFolders
removeEval oFile, sHomeFolder + "\" + oFile.Name + "\config\eval"
Next
End If

If oFS.FolderExists(sJBDataFolder) Then
For Each oFile In oFS.GetFolder(sJBDataFolder).SubFolders
removeEval oFile, sJBDataFolder + "\" + oFile.Name + "\eval"
Next
End If

MsgBox "done"

qXHrKqdrtjJX6xtX

vrg123 : 4565

zhile

Zhile-plug: https://plugins.zhile.io

nodejs

npm 查看镜像源

1
2
# npm config (set | get | delete | list | edit)
npm config get (registry|sass_binary_site)

使用

临时使用镜像

1
2
3
npm install --registry=https://registry.npmmirror.com pkg_name -g cnpm
# 创建别名
alias npmc="npm --registry=https://registry.npmmirror.com"

全局配置

1
2
3
# npm
npm config set registry https://registry.npm.taobao.org
npm config set sass_binary_site=https://npm.taobao.org/mirrors/node-sass/phantomjs_cdn

yarn 查看镜像源

1
2
# yarn config (set | get | delete | list | current)
yarn config get(registry|sass_binary_site)

使用

临时使用镜像

1
yarn add --registry=https://registry.npm.taobao.org

全局配置

1
2
yarn config set registry https://registry.npm.taobao.org -g
yarn config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sass -g

yarn使用

1
2
3
4
5
6
7
8
9
10
11
12
13
# 初始化项目
yarn init [-y]
# 添加依赖
yarn [global] add
-D, --dev save package to your `devDependencies`(开发依赖)
-P, --peer save package to your `peerDependencies`(对等依赖)
-O, --optional save package to your `optionalDependencies`(可选依赖)
# 安装项目所有依赖 `yarn` or
yarn install [--update-checksums]
# 更新
yarn upgrade
# 移除
yarn remove

代码优化

时代高速发展,技术日新月异。各司左手分布式,右手高并发。嘴上骂骂咧咧动不动优化的卷王,近日出现的可真不少。

他卷任他卷,躺平治百痛,吾可被累死,但也要卷死尔等。

那么今天咱们也聊聊“代码优化”?

如何写出更快更好的代码?

编程技巧

  • 更清晰的代码组织结构
  • 更明了的命名,更好的逻辑结构

编码技巧

  • 更少的占用
  • 更快的运行

其实说到这个往细致了说就是 算法和数据结构的事儿,但为什么我觉得差点意思呢?

优化原则

  1. 保证代码正常工作
  2. 权衡“优化”代价
  3. 定义性能指标,集中力量解决首要问题
  4. 不要忽略可读性

优化代码

发现代码

测试与分析

  • 单元测试、基准测试

  • 性能分析、代码覆盖率分析、时间、空间负责度分析

修改代码

  • 更低的代码覆盖率,更多的可重用代码
  • 更好更省的算法
  • 更符合的语言特性
  • 更顺应度调度

保持书写代码的习惯

Python优化技巧

分析

  • 最懒的性能分析:Unix time
  • 内存分析:memory_profiler
  • 引用分析:Obj graph
  • 最详细的性能分析:cProfile、
  • pytest

建议

  1. 使用尽量低和省的算法与数据结构
  2. 尽量使用内置数据类型,尽量使用内置函数
  3. 使用LRU_Cache 缓存数据
  4. 尽量使用局部变量:与每个作用域内查找变量的速度有关,局部变量最快,类级属性中等,全局变量最慢
  5. 使用函数
  6. 提防字符串:在循环中运行 模数(%s)或 .format() 会变量,优先用 f-string
  7. 巧用多重赋值
  8. 使用生成器
  9. 使用MultiProcessing克服GIL、使用线程池、使用Coroutine
  10. 使用C/C++ 模块提高性能,使用Cython
  11. 使用JIT

一些分析工具:

vprof

pyinstrument

scalene

阿姆达尔定律:Amdahl’s law

vim

文本插入

几种进入插入模式的方法,以便于更快捷的编辑

a: 用于在光标之后进入插入模式

A: 用于当前行行末进入插入模式(与$a等价)

i: 在当前位置进入插入模式

I: 在当前行缩进之后的行首进入插入模式

o: 在光标下一行新增一行并进入插入模式

O: 在光标上一行新增一行并进入插入模式

窗口

横向分割窗口: split 或者sp

纵向分割窗口: vsplit 或者 vsp

窗口跳转

1
2
3
4
5
6
ctrl+w j 向下移动
ctrl+w k 向上移动
ctrl+w h 向左移动
ctrl+w l 向右移动
ctrl+w t 移动到顶端
ctrl+w b 移动到底端

vim 模式

普通模式:vi file_name,主要用于文件操作如浏览、增删改查

输入模式:在普通模式下输入 i 即可进入输入模式

命令模式:在普通模式下输入 : 进入命令模式,使用命令进行操作

可视模式:在普通模式中输入 v 进入可视模式。类似于鼠标选中进行批量操作

常用命令

g: 跳至行首,G: 跳至文末尾

cc:删除这一行进入修改模式

dd: 删除一整行

uu:撤销上次操作

yy: 复制

pp:粘贴

tips: 这里笔者使用了组合的形式来展示,其实不然,比如删除4行,可以是d4d, 删除5个单词为d4w,

当然还可以结合 可视模式(多选)来辅助批量复制粘贴等。

uiautomator2

uiautomator2 简介

UiAutomator是Google提供的用来做安卓自动化测试的一个Java库,基于Accessibility服务。功能很强,可以对第三方App进行测试,获取屏幕上任意一个APP的任意一个控件属性,并对其进行任意操作。而uiautomator2便是UiAutomator的Python实现

除了对原有的库的bug进行了修复,还增加了很多新的Feature。主要有以下部分:

  • 设备和开发机可以脱离数据线,通过WiFi互联(基于atx-agent
  • 集成了openstf/minicap达到实时屏幕投频,以及实时截图
  • 集成了openstf/minitouch达到精确实时控制设备
  • 修复了xiaocong/uiautomator经常性退出的问题
  • 代码进行了重构和精简,方便维护
  • 实现了一个设备管理平台(也支持iOS) atxserver2
  • 扩充了toast获取和展示的功能

相关链接如下:

UiAutomatorhttps://developer.android.com/training/testing/ui-automator.html

uiautomator2https://github.com/openatx/uiautomator2

QUICK_REFERENCEhttps://github.com/openatx/uiautomator2/blob/master/QUICK_REFERENCE.md

安装

由于python实现的,在此可以使用pip 直接安装即可,命令如下

1
2
# # 如果是在Windows平台下,未做`python3` 与 `python` 的link,在这里使用`python` 而不是`python3`
python3 -m pip install --upgrade uiautomator2

Tips: 建议使用虚拟环境

开发版安装

1
python3 -m pip install --upgrade --pre uiautomator2

源码安装

需要git客户端,若未安装git,可移步https://git-scm.com/downloads进行下载,

当然,源码也可以使用zip的方式进行下载,请自行探索

1
2
git clone https://github.com/openatx/uiautomator2
python3 -m pip install -e uiautomator2

校验

先准备一台(不要两台)开启了开发者选项的安卓手机,连接上电脑,确保执行adb devices可以看到连接上的设备。如下图所示

image-20211206153159233

运行python3 -m uiautomator2 init安装包含httprpc服务的apk到手机+atx-agent, minicap, minitouch

在过去的版本中,这一步是必须执行的,但是从1.3.0之后的版本,当运行python代码u2.connect()时就会自动推送这些文件了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import uiautomator2 as u2

# 连接设备(当只有只有一台设备的时候可以为空,当有多台设备可以是设备号或ip地址)
device = u2.connect()
# get uiautomator2 device info
pprint(device.info)
# get device info
pprint(device.device_info)
# get device windows size
print(device.window_size())
# get device wlan ip
pprint(device.wlan_ip)
# get serial id
pprint(device.serial)

image-20211206153746081

app开启与关闭

当我们需要打开对应的app时,需要知道对应设备的包名。获取包名有两种方式

  • 在设备上打开对应的app,获取当前app包名(app_current())
  • 获取全部的应用包(app_list_running())
1
2
3
4
# 开启app
app_start
# 关闭app
app_stop

Example

此时笔者的测试手机在已经打开app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Date : 07-12-2021
# @Author : Payne
# @Email : wuzhipeng1289690157@gmail.com
import random
import time
from loguru import logger
import uiautomator2 as u2

# content device
device = u2.connect()
logger.info(f"DEVICE INFO:{device.info}")
# check current app
current_app = device.app_current()
# get the package
device.app_start(current_app.get('package'), stop=True)
# 正态分布休眠,模拟操作
time.sleep(random.uniform(10, 20))
# stop all app
device.app_stop_all()

Tips: 复习一下adb command

1
2
3
4
5
6
7
8
9
10
11
# adb 命令 -- 获取报包名

# current (需要打开app)
adb -s {device_serial} shell dumpsys activity | grep mFocusedActivity
# all
adb -s {device_serial} shell pm list packages

# 根据关键字过滤包
adb -s {device_serial} shell pm list packages | grep “keyword”
# 查看包安装位置
adb -s {device_serial} shell pm list packages -f

点击

  • Turn on/off screen

    1
    2
    d.screen_on() # turn on the screen
    d.screen_off() # turn off the screen
  • Press hard/soft key

    1
    2
    3
    d.press("home") # press the home key, with key name
    d.press("back") # press the back key, with key name
    d.press(0x07, 0x02) # press keycode 0x07('0') with META ALT(0x02)
  • These key names are currently supported:

    • home、back、

    • left、right、up、down

    • center、menu、search

    • enter、delete ( or del)

    • recent (recent apps)

    • volume_up、volume_down、volume_mute

    • camera、power

  • Click on the screen

1
d.click(x, y)
  • Double click
1
2
d.double_click(x, y)
d.double_click(x, y, 0.1) # default duration between two click is 0.1s
  • long_click
1
2
d.long_click(x, y)
d.long_click(x, y, 0.5) # long click 0.5s (default)

滑动

Swipe

1
2
3
4
5
6
# Swipe
swipe(self, fx, fy, tx, ty, duration: Optional[float] = None, steps: Optional[int] = None)
fx, fy:起始坐标
tx, ty:目标坐标
duration: 持续时间(力度)
steps: 分开滑动次数

swipe_ext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
d.swipe_ext("right") # 手指右滑,4选1 "left", "right", "up", "down"
d.swipe_ext("right", scale=0.9) # 默认0.9, 滑动距离为屏幕宽度的90%
d.swipe_ext("right", box=(0, 0, 100, 100)) # 在 (0,0) -> (100, 100) 这个区域做滑动

# 实践发现上滑或下滑的时候,从中点开始滑动成功率会高一些
d.swipe_ext("up", scale=0.8) # 代码会vkk

# 还可以使用Direction作为参数
from uiautomator2 import Direction

d.swipe_ext(Direction.FORWARD) # 页面下翻, 等价于 d.swipe_ext("up"), 只是更好理解
d.swipe_ext(Direction.BACKWARD) # 页面上翻
d.swipe_ext(Direction.HORIZ_FORWARD) # 页面水平右翻
d.swipe_ext(Direction.HORIZ_BACKWARD) # 页面水平左翻

Drag

1
2
3
4
5
d.drag(sx, sy, ex, ey)
d.drag(sx, sy, ex, ey, 0.5)

sx, sy:起始坐标
ex, ey:目标坐标

Swipe points

1
2
3
4
# swipe from point(x0, y0) to point(x1, y1) then to point(x2, y2)
# time will speed 0.2s bwtween two points
# swipe_points(self, points, duration: float = 0.5)
d.swipe_points([(x0, y0), (x1, y1), (x2, y2)], 0.2))

选择器

选择器是一种在当前窗口中识别特定 UI 对象的便捷机制, 很多时候仅按照坐标时点击并不精确,通用型不强。

选择器支持以下参数。有关详细信息,请参阅UiSelector Java 文档

  • text, textContains, textMatches,textStartsWith
  • className, classNameMatches
  • description, descriptionContains, descriptionMatches,descriptionStartsWith
  • checkable, checked, clickable,longClickable
  • scrollable, enabled, focusable, focused,selected
  • packageName, packageNameMatches
  • resourceId, resourceIdMatches
  • index, instance

Redis

Redis 持久化

redis的持久化方式有两种,一种是RDB持久化,一种是AOF持久化。

RDB快照(snapshot)

redis把数据以快照的方式保存在磁盘上。默认的情况下,redis将数据保存在文件名为dump.rdb的二进制文件中。

redis在运行时,会把内存中的数据快照保存到磁盘上,在redis重启时,会从rdb文件中读取数据还原redis数据库的状态。

rdb是自动默认开启的,但并没有开启触发规则。

触发机制

RDB的触发可分为两大类,一类是自动触发,一类是手动触发。

自动触发

自动触发

当数据操作满足一定的规则,自动触发。详细规则如下

默认开启rdb,但没有配置规则,若需要使用或配置则需要在配置文件中将注释放开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# RDB自动持久化规则
# 当900 秒内有至少有 1 个键被改动时,自动进行数据集保存操作
save 900 1
# 当 300 秒内有至少有 10 个键被改动时,自动进行数据集保存操作
save 300 10
# 当 60 秒内有至少有 10000 个键被改动时,自动进行数据集保存操作
save 60 10000
# RDB持久化文件名
dbfilename dump.rdb
# 数据持久化文件存储目录
dir /var/lib/redis
# bgsave发生错误时是否停止写入,通常为yes
stop-writes-on-bgsave-error yes
# rdb文件是否使用压缩格式
rdbcompression yes
# 是否对rdb文件进行校验和检验,通常为yes
rdbchecksum yes

手动触发

save命令

在客户端执行save命令,会触发一次保存快照。save命令是同步命令,在save执行时,会占用主进程,导致redis其他命令无法使用。在数据量过大时,可能会导致redis反应变慢。

bgsave命令

bgsave命令是异步操作,执行bgsave命令保存快照,可以在生成快照的同时,依然可以正常处理其他命令。bgsave子进程是由主线程fock生成的,它不影响主进程的执行,同时还可以共享主进程的数据

save和bgsave对比

命令 save bgsave
IO类型 同步 异步
是否阻塞redis 是(在生成子进程时有短暂阻塞,速度很快,基本没有影响)
时间复杂度 O(n) O(n)
优点 不消耗额外内存 不阻塞客户端
缺点 阻塞客户端命令 需要fork子进程,消耗内存

关闭RDB

虽然这种方式可能不需要,但若需要关闭rdb的时候,在redis-shell中只需要执行即可

1
config set save ""

当然也可以在配置文件中将 save "" 注释打开,也可以。

AOF(Append Only File)

默认情况下,Redis 异步转储磁盘上的数据集。RDB模式在很多应用场景中已经足够好了,但是 Redis 进程的问题或断电可能会导致几分钟的写入丢失(取决于配置的保存点)。 Append Only File 是另一种持久性模式,可提供更好的持久性。
例如,使用默认的数据 fsync 策略(见后面的配置文件)Redis 可能会在服务器断电等戏剧性事件中丢失一秒钟的写入,或者如果 Redis 进程本身发生问题,则会丢失一次写入,但是操作系统仍然正常运行。可以同时启用 AOF 和 RDB 持久化。如果在启动时启用了 AOF,Redis 将加载 AOF,即具有更好持久性保证的文件。

AOF是通过将修改的每一条指令写入一个记录文件件appendonly.aof中(先写入os cache,每隔一段时间 fsync到磁盘)。这样子的话,在redis重启时,可以通过读取指令来重新写入数据达到重建数据库的目的。

开启AOF

可以通过修改配置文件来打开AOF功能与命令,配置文件中如下:

在配置文件中将 appendonly yes注释打开即可

1
appendonly yes

命令

1
config set appendonly yes

AOF保存策略

  • appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
  • appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
  • appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。

推荐(也就是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。

AOF重写

由于AOF不断的将命令追加到文件的末尾,因此随着命令的不断增加,AOF文件的体积会变的越来越大。

例如执行INCR命令执行了1000次,在AOF内会生成1000个操作命令。但实际上来说,只需要SET到当前值的命令就可以存储了,前面的999次INCR都是无意义的。

实际上可能不止这一种多余的废操作,因此Redis可以对AOF文件进行重写,会把命令进行精简整合成一个新的AOF文件,新的文件里包含生成当前数据的最少命令。

执行AOF重写的方式也有两种,一种是命令,一种是配置文件

配置文件配置aof重写

默认开启重写,但需要先开始aof

1
2
3
4
# 当前大小大于指定的百分比触发重写
auto-aof-rewrite-percentage 100
# AOF 文件指定最小大小
auto-aof-rewrite-min-size 64mb

将此基本大小与当前大小进行比较。如果当前大小大于指定的百分比,则触发重写。此外,您还需要为要重写的 AOF 文件指定最小大小,这对于避免重写 AOF 文件(即使达到百分比增加但仍然很小)很有用。指定百分比为零以禁用自动 AOF 重写功能。

命令

1
bgrewriteaof

RDB 与 AOF对比

RDB

优点

  • 文件紧凑,很适合进行数据备份和容灾恢复
  • 恢复大量数据时RDB速度快

缺点

  • 数据量越大,二进制保存到磁盘会耗时越久
  • 遇见意外的情况下,如宕机等,可能导致未持久化的数据完全丢失

AOF

优点

  • 可以使用不同的fsync策略,在默认的每秒fsync下,最多丢失1s的数据
  • AOF的持久化记录是文件追加,保存速度快
  • 存储的是操作命令,AOF文件易读,可以轻易的进行文件分析

缺点

  • 文件体积较RDB大
  • 重启时恢复数据速度较慢
类型 RDB AOF
启动优先级
体积
宕机恢复速度
数据安全性 可能丢失数据 每秒fsync最多丢失1s数据

是rdb还是aof?

使用可以根据需要来自行选择,如果对数据丢失不敏感的,使用rdb即可。当然在生产环境使用时,可以两种方式都启用。rdb文件可以用来做备份,aof文件来保证数据的安全性

RDB与AOF混用

必须先开启aof

在redis4.0之后,出现了一个新的持久化选项——混合持久化。    可以通过以下配置开启混合持久化
1
2
# 在配置文件中设置
aof‐use‐rdb‐preamble yes

开启混合持久化后,AOF在重写时,不是单纯的把命令写入AOF文件,而是把重写这一刻之前的内存数据做RDB快照处理,在重写之后的还是继续使用AOF命令的形式保存。这样aof文件里就有历史的RDB快照和增量的AOF命令。
我们知道,RDB文件的恢复速度比AOF快的多,因此这种混合的模式,在redis重启的时候能大大提升效率。

Redis淘汰机制

将内存使用限制设置为指定的字节数。当达到内存限制时,Redis 将尝试根据选择的驱逐策略删除键。

MAXMEMORY 策略:

当达到 maxmemory 时,Redis 将如何选择要删除的内容。可以从以下行为中选择一项

noeviction:默认禁止驱逐数据。内存不够使用时,对申请内存的命令报错。

volatile-lru:从设置了过期时间的数据集中淘汰最近没使用的数据。

volatile-ttl:从设置了过期时间的数据集中淘汰即将要过期的数据。

volatile-random:从设置了过期时间的数据中随机淘汰数据。

allkeys-lru:淘汰最近没使用的数据。

allkeys-random:随机淘汰数据。

注意:使用上述任何一种策略,当没有合适的键用于驱逐时,Redis 将在需要更多内存的写操作时返回错误。这些通常是创建新密钥、添加数据或修改现有密钥的命令。一些示例是:SET、INCR、HSET、LPUSH、SUNIONSTORE、SORT(由于 STORE 参数)和 EXEC(如果事务包括任何需要内存的命令)。

如果 Redis 无法根据策略删除键,或者如果策略设置为“noeviction”,Redis 将开始对会使用更多内存的命令(如 SET、LPUSH 等)回复错误信息,并将继续回复像 GET
这样的只读命令。简而言之…如果附加了副本,建议为 maxmemory 设置一个下限,以便系统上有一些空闲 RAM 用于副本输出缓冲区(但如果策略是“noeviction”,则不需要这样做)

警告:如果您将副本附加到启用了 maxmemory 的实例,则从使用的内存计数中减去提供副本所需的输出缓冲区的大小,以便网络问题重新同步不会触发密钥被逐出的循环,并且在将副本的输出缓冲区填满,删除键的 DEL
触发删除更多键,依此类推,直到数据库完全清空

Visual Studio Code

配置

lines
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
{
"workbench.colorTheme": "Solarized Light",
"files.autoSave": "afterDelay",
"editor.formatOnSave": true,
"editor.fontSize": 14,
"editor.fontFamily": "JetBrains Mono, Menlo, Monaco, 'Courier New', monospace",
"editor.fontLigatures": true,
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/target": true,
"**/logs": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/*.js": {
"when": "$(basename).ts"
},
"**/.idea": true
},
"files.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/target": true,
"**/logs": true,
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/*.js": {
"when": "$(basename).ts"
},
"**/.idea": true
},
}

代码提示配置

settings -搜索-> prevent -> none

插件

Program Language

Python

  • Python Docstring Generator
  • Python
  • Python Environment Manager
  • Visual Studio IntelliCode
  • Python Indent

PHP

  • php-extension-pack

Cloud

  • DevOps Cloud Extension pack

Reception

  • VS Code JavaScript (ES6) snippets
  • open in browser
  • auto complete Tag
  • auto rename Tag
  • auto close Tag
  • Import Cost

Other

  • vscode-icons
  • vsc-essentials
  • gitlens: git config pull.rebase false

code studio pack

常用快捷键

Window

Alt + → :代码前进下一级

Alt + ← :代码返回上一级


Ctrl + K + 0: 收缩所有代码(0是代码折叠级别,同理可以换用1,2,3等)

Ctrl + K + J: 取消收缩所有代码


Ctrl + K + S:快捷键文档

Alt + Shift + F :代码格式化

Ctrl + Shift + P: 显示所有命令

Ctrl + P: 显示所有命令

Mac

  • Ctrl + -:代码返回上一级

  • Ctrl + Shift + -: 代码前进下一级


  • Command + K + 0: 收缩所有代码(0是代码折叠级别,同理可以换用1,2,3等)

  • Command + K + J 取消收缩所有代码


  • Command + K + S:快捷键文档

  • Opt + Shift + F :代码格式化

  • Command + Shift + P: 显示所有命令

  • Command + p :打开最近文件

Mac

重置 Visual Studio Code所有配置

随着使用期限的增长,各种问题便逐渐出现啦。当我们对于Visual Studio Code因为配置问题而造成使用不适,此处有一剂良药——初始化配置

1
2
rm -rf ~/.vscode
rm -rf ~/Library/Application\ Support/Code

Go

Viper简介

Viper是一个完整的Go语言项目的配置解决方案。它可以处理所有类型的配置需求和格式,相关链接如下

包文档:https://pkg.go.dev/github.com/spf13/viper

github:https://github.com/spf13/viper

Viper的优势

在构建Golang程序时可以不必担心配置文件格式而更专注于实现。

viper主要包含以下操作:

  1. 查找、加载和反序列化 “json”, “toml”, “yaml”, “yml”, “properties”, “props”, “prop”, “hcl”, “tfvars”, “dotenv”, “env”, “ini”
  2. 提供一种机制来为不同的配置选项设置默认值。
  3. 提供一种机制来为通过命令行参数设置指定覆盖值。
  4. 提供别名,以在不破坏现有代码的情况下轻松重命名参数。
  5. 使区分用户何时提供与默认值相同的命令行或配置文件变得容易。

每个项目的优先级都高于它下面的项目,Viper优先顺序。

  • 显式调用 Set
  • 命令行参数(flag)
  • 环境变量
  • 配置文件
  • key/value存储
  • 默认值

重要提示: Viper 配置键不区分大小写。正在进行关于使之成为可选项的讨论。

Viper使用场景

  • 设置默认值
  • “json”, “toml”, “yaml”, “yml”, “properties”, “props”, “prop”, “hcl”, “tfvars”, “dotenv”, “env”, “ini”文件中读取载入
  • 实时观看和重新读取配置文件(可选)
  • 从环境变量中读取
  • 从远程配置系统(etcd 或 Consul)读取,并观察变化
  • 从命令行标志读取
  • 从缓冲区读取
  • 设置显式值

Viper 可以被认为是满足所有应用程序配置需求的注册表

Viper的安装

1
go get -u -v github.com/spf13/viper

Viper使用实例

使用默认值

一个好的配置系统对于默认值拥有良好的支持,其重要性不言而喻。在Viper中的默认值使用如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"github.com/spf13/viper"
)

func main() {
viper.SetDefault(`Name`, `Payne`)
viper.SetDefault(`Age`, 20)
viper.SetDefault(`hobby`, map[string]string{
`First hobby`: `sing`,
`Second hobby`: `jump`,
`Third hobby`: `Rap`,
`fourth hobby`: `Play Basketball`,
})
fmt.Println(viper.Get(`Name`))
fmt.Println(viper.Get(`Age`))
fmt.Println(viper.Get(`hobby`))
for _, i := range viper.GetStringMapString(`hobby`) {
fmt.Println(i)
}
}

覆盖设置

这些可能来自命令行标志,也可能来自你自己的应用程序逻辑。

1
2
viper.Set("Verbose", true)
viper.Set("LogFile", LogFile)

注册和使用别名

别名允许多个键引用单个值

1
2
3
4
5
6
7
viper.RegisterAlias("loud", "Verbose")  // 注册别名(此处loud和Verbose建立了别名)

viper.Set("verbose", true)
viper.Set("loud", true)

viper.GetBool("loud") // true
viper.GetBool("verbose") // true

配置文件使用

读取配置文件

抽离统一化管理成为配置文件,将所有的配置写在文件中便于管理修改与编辑。Viper 支持 “json”, “toml”, “yaml”, “yml”, “properties”, “props”, “prop”, “hcl”, “
tfvars”, “dotenv”, “env”, “ini” 属性文件。Viper 可以搜索多个路径,但目前单个 Viper 实例仅支持单个配置文件。Viper
不会默认任何配置搜索路径,将默认决定留给应用程序。不需要任何特定路径,但应至少提供一个需要配置文件的路径。以下是如何使用 Viper 搜索和读取配置文件的示例。

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
// 指定配置文件路径
viper.SetConfigFile("./config.yaml")
// 配置文件名称(无扩展名)
viper.SetConfigName("config")
// 如果配置文件的名称中没有扩展名,则需要配置此项
viper.SetConfigType("yaml")
// 查找配置文件所在的路径
viper.AddConfigPath("/etc/appname/")
// 多次调用以添加多个搜索路径
viper.AddConfigPath("$HOME/.appname")
// 还可以在工作目录中查找配置
viper.AddConfigPath(".")
// 查找并读取配置文件
err := viper.ReadInConfig()
// 处理读取配置文件的错误
if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}

// 配置文件读取异常处理
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found; ignore error if desired
log.Println("no such config file")
} else {
// Config file was found but another error was produced
log.Println("read config error")
}
log.Fatal(err) // 读取配置文件失败致命错误
}

注意若采用setConfigName则只会使用第一个配置文件夹

推荐使用SetConfigFile("path/file_name") 来完成配置文件的载入

从io.Reader读取配置

Viper预先定义了许多配置源,如文件、环境变量、标志和远程K/V存储,但也可以实现自己所需的配置源并将其提供给viper。

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
import (
"bytes"
"fmt"
"github.com/spf13/viper"
)

func yamlConf() {
viper.SetConfigType("yaml")
ExampleYaml := []byte(`
name: Payne
Age: 18
`)
viper.ReadConfig(bytes.NewBuffer(ExampleYaml))
fmt.Println(viper.Get("NAME"))
fmt.Println(viper.Get("Age"))

}

func jsonConf() {
viper.SetConfigType(`json`)

ExampleJSON := []byte(`{
"name": "payne",
"age": 21
}`)
viper.ReadConfig(bytes.NewBuffer(ExampleJSON))
fmt.Println(viper.Get("name"))
fmt.Println(viper.GetInt("age"))
}

写入配置文件

从配置文件中读取是很有用的,但有时你想存储在运行时所作的所有修改都比较繁琐。viper提供了相关功能

  • WriteConfig - 将当前的viper配置写入预定义的路径并覆盖(如果存在的话)。如果没有预定义的路径,则报错。
  • SafeWriteConfig - 将当前的viper配置写入预定义的路径。如果没有预定义的路径,则报错。如果存在,将不会覆盖当前的配置文件。
  • WriteConfigAs - 将当前的viper配置写入给定的文件路径。将覆盖给定的文件(如果它存在的话)。
  • SafeWriteConfigAs - 将当前的viper配置写入给定的文件路径。不会覆盖给定的文件(如果它存在的话)。

根据经验,标记为safe的所有方法都不会覆盖任何文件,而是直接创建(如果不存在),而默认行为是创建或截断。

监听配置文件

Viper支持在运行时实时读取配置文件的功能。

需要重新启动服务器以使配置生效的日子已经一去不复返了,viper驱动的应用程序可以在运行时读取配置文件的更新,而不会错过任何消息。

只需告诉viper实例watchConfig。可选地,你可以为Viper提供一个回调函数,以便在每次发生更改时运行。

确保在调用WatchConfig()之前添加了所有的配置路径。

1
2
3
4
5
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
// 配置文件发生变更之后会调用的回调函数
fmt.Println("Config file changed:", e.Name)
})

实例

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
package main

import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"log"
"time"
)

func main() {
for {
viper.SetConfigFile(`./example/config.yaml`)
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found; ignore error if desired
log.Println("no such config file")
} else {
// Config file was found but another error was produced
log.Println("read config error")
}
log.Fatal(err) // 读取配置文件失败致命错误
}
viper.SetDefault(`a`, `b`)
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
// 配置文件发生变更之后会调用的回调函数
fmt.Println("Config file changed:", e.Name)
})
fmt.Println(viper.Get(`port`))
time.Sleep(time.Second * 2)
}
}

环境变量

Viper完全支持环境变量。以下几种方法进行对ENV协作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// AllowEmptyEnv 告诉 Viper 将设置但为空的环境变量视为有效值,而不是回退。出于向后兼容性的原因,默认情况下这是错误的
AllowEmptyEnv(allowEmptyEnv bool)

// AutomaticEnv 使 Viper 检查环境变量是否与任何现有键(配置、默认值或标志)匹配。如果找到匹配的环境变量,则将它们加载到 Viper 中
AutomaticEnv()

// BindEnv 将 Viper 键绑定到 ENV 变量。ENV 变量区分大小写。如果只提供了一个键,它将使用与键匹配的 env 键,大写。如果提供了更多参数,它们将表示应绑定到此键的环境变量名称,并将按指定顺序使用。当未提供 env 名称时,将在设置时使用 EnvPrefix。
func BindEnv(input ...string) error

// SetEnvPrefix 定义了 ENVIRONMENT 变量将使用的前缀
func SetEnvPrefix(in string)

// SetEnvKeyReplacer允许你使用strings.Replacer对象在一定程度上重写 Env 键
func SetEnvKeyReplacer(r *strings.Replacer)

使用ENV变量时,务必要意识到Viper将ENV变量视为区分大小写。

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
// case 1
i := 0
for {
viper.SetDefault(`Val`, `Original`)
viper.BindEnv(`Val`)
fmt.Println(viper.Get(`Val`))

// 通常是在应用程序之外完成的
if i == 3 {
os.Setenv("VAL", "changed")
}

fmt.Println(i)
i += 1
time.Sleep(1 * time.Second)
}

// case 2
i := 0
for {
viper.SetDefault(`Val`, `Original`)
viper.SetEnvPrefix(`CUSTOM`) // 将自动转为大写
viper.BindEnv(`Val`)
fmt.Println(viper.Get(`Val`))

// 通常是在应用程序之外完成的
if i == 3 {
os.Setenv("CUSTOM_VAL", "changed")
}

fmt.Println(i)
i += 1
time.Sleep(1 * time.Second)
}

当第四次输出时VAL,将输出change

小技巧:在使用环境变量的时候推荐采用全大写,避免混淆

使用viper获取值

获取函数如下所示,具体作用见名思意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Get(key string) interface{}
Sub(key string) *Viper
GetBool(key string) bool
GetDuration(key string) time.Duration
GetFloat64(key string) float64
GetInt(key string) int
GetInt32(key string) int32
GetInt64(key string) int64
GetIntSlice(key string) []int
GetSizeInBytes(key string) uint
GetString(key string) string
GetStringMap(key string) map[string]interface{}
GetStringMapString(key string) map[string]string
GetStringMapStringSlice(key string) map[string][]string
GetStringSlice(key string) []string
GetTime(key string) time.Time
GetUint(key string) uint
GetUint32(key string) uint32
GetUint64(key string) uint64
InConfig(key string) bool
IsSet(key string) bool
AllSettings() map[string]interface{}

访问嵌套的键

访问器方法也接受深度嵌套键的格式化路径。例如,如果加载下面的JSON文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"host": {
"address": "localhost",
"port": 5799
},
"datastore": {
"metric": {
"host": "127.0.0.1",
"port": 3099
},
"warehouse": {
"host": "198.21.112.32",
"port": 2112
}
}
}

Viper可以通过传入.分隔的路径来访问嵌套字段:

1
2
GetString("datastore.datastore.warehouse.host") 
// 返回 "198.21.112.32"

这遵守上面建立的优先规则;搜索路径将遍历其余配置注册表,直到找到为止。(译注:因为Viper支持从多种配置来源,例如磁盘上的配置文件>命令行标志位>环境变量>远程Key/Value存储>
默认值,我们在查找一个配置的时候如果在当前配置源中没找到,就会继续从后续的配置源查找,直到找到为止。)

例如,在给定此配置文件的情况下,datastore.metric.hostdatastore.metric.port均已定义(并且可以被覆盖)。如果另外在默认值中定义了datastore.metric.protocol
,Viper也会找到它。然而,如果datastore.metric被直接赋值覆盖(被flag,环境变量,set()方法等等…),那么datastore.metric
的所有子键都将变为未定义状态,它们被高优先级配置级别“遮蔽”(shadowed)了。最后,如果存在与分隔的键路径匹配的键,则返回其值。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"datastore.metric.host": "0.0.0.0",
"host": {
"address": "localhost",
"port": 5799
},
"datastore": {
"metric": {
"host": "127.0.0.1",
"port": 3099
},
"warehouse": {
"host": "198.0.0.1",
"port": 2112
}
}
}

GetString("datastore.metric.host")
// 返回 "0.0.0.0"

提取子树

从Viper中提取子树,viper实例现在代表了以下配置:

1
2
3
4
5
6
7
app:
cache1:
max-items: 100
item-size: 64
cache2:
max-items: 200
item-size: 80

执行后:

1
subv := viper.Sub("app.cache1")

subv现在就代表:

1
2
max-items: 100
item-size: 64

假设我们现在有这么一个函数:

1
func NewCache(cfg *Viper) *Cache {...}

它基于subv格式的配置信息创建缓存。现在,可以轻松地分别创建这两个缓存,如下所示:

1
2
3
4
5
cfg1 := viper.Sub("app.cache1")
cache1 := NewCache(cfg1)

cfg2 := viper.Sub("app.cache2")
cache2 := NewCache(cfg2)

反序列化

你还可以选择将所有或特定的值解析到结构体、map等。

有两种方法可以做到这一点:

  • Unmarshal(rawVal interface{}) : error
  • UnmarshalKey(key string, rawVal interface{}) : error
1
2
3
4
5
6
7
8
9
10
11
12
type config struct {
Port int
Name string
PathMap string `mapstructure:"path_map"`
}

var C config

err := viper.Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}

如果你想要解析那些键本身就包含.(默认的键分隔符)的配置,你需要修改分隔符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
v := viper.NewWithOptions(viper.KeyDelimiter("::"))

v.SetDefault("chart::values", map[string]interface{}{
"ingress": map[string]interface{}{
"annotations": map[string]interface{}{
"traefik.frontend.rule.type": "PathPrefix",
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
},
},
})

type config struct {
Chart struct{
Values map[string]interface{}
}
}

var C config

v.Unmarshal(&C)

Viper还支持解析到嵌入的结构体:

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
/*
Example config:

module:
enabled: true
token: 89h3f98hbwf987h3f98wenf89ehf
*/
type config struct {
Module struct {
Enabled bool

moduleConfig `mapstructure:",squash"`
}
}

// moduleConfig could be in a module specific package
type moduleConfig struct {
Token string
}

var C config

err := viper.Unmarshal(&C)
if err != nil {
t.Fatalf("unable to decode into struct, %v", err)
}

Viper在后台使用github.com/mitchellh/mapstructure来解析值,其默认情况下使用mapstructuretag。

注意 当我们需要将viper读取的配置反序列到我们定义的结构体变量中时,一定要使用mapstructuretag!

序列化成字符串

你可能需要将viper中保存的所有设置序列化到一个字符串中,而不是将它们写入到一个文件中。你可以将自己喜欢的格式的序列化器与AllSettings()返回的配置一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
import (
yaml "gopkg.in/yaml.v2"
// ...
)

func yamlStringSettings() string {
c := viper.AllSettings()
bs, err := yaml.Marshal(c)
if err != nil {
log.Fatalf("unable to marshal config to YAML: %v", err)
}
return string(bs)
}

远程Key/Value存储支持

在Viper中启用远程支持,需要在代码中匿名导入viper/remote这个包。

1
import _ "github.com/spf13/viper/remote"

Viper将读取从Key/Value存储(例如etcd或Consul)中的路径检索到的配置字符串(如JSONTOMLYAMLHCLenvfileJava properties
格式)。这些值的优先级高于默认值,但是会被从磁盘、flag或环境变量检索到的配置值覆盖。(译注:也就是说Viper加载配置值的优先级为:磁盘上的配置文件>命令行标志位>环境变量>远程Key/Value存储>默认值。)

Viper使用crypt从K/V存储中检索配置,这意味着如果你有正确的gpg密匙,你可以将配置值加密存储并自动解密。加密是可选的。

你可以将远程配置与本地配置结合使用,也可以独立使用。

crypt有一个命令行助手,你可以使用它将配置放入K/V存储中。crypt默认使用在http://127.0.0.1:4001的etcd。

1
2
$ go get github.com/bketelsen/crypt/bin/crypt
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json

确认值已经设置:

1
$ crypt get -plaintext /config/hugo.json

有关如何设置加密值或如何使用Consul的示例,请参见crypt文档。

远程Key/Value存储示例-未加密

etcd

1
2
3
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
viper.SetConfigType("json") // 因为在字节流中没有文件扩展名,所以这里需要设置下类型。支持的扩展名有 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()

Consul

你需要 Consul Key/Value存储中设置一个Key保存包含所需配置的JSON值。例如,创建一个keyMY_CONSUL_KEY将下面的值存入Consul key/value 存储:

1
2
3
viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY")
viper.SetConfigType("json") // 需要显示设置成json
err: = viper.ReadRemoteConfig()

Firestore

1
2
3
viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
viper.SetConfigType("json") // 配置的格式: "json", "toml", "yaml", "yml"
err := viper.ReadRemoteConfig()

当然,你也可以使用SecureRemoteProvider

远程Key/Value存储示例-加密

1
2
3
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
viper.SetConfigType("json") // 因为在字节流中没有文件扩展名,所以这里需要设置下类型。支持的扩展名有 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
err := viper.ReadRemoteConfig()

监控etcd中的更改-未加密

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
// 或者你可以创建一个新的viper实例
var runtime_viper = viper.New()

runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
runtime_viper.SetConfigType("yaml") // 因为在字节流中没有文件扩展名,所以这里需要设置下类型。支持的扩展名有 "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"

// 第一次从远程读取配置
err := runtime_viper.ReadRemoteConfig()

// 反序列化
runtime_viper.Unmarshal(&runtime_conf)

// 开启一个单独的goroutine一直监控远端的变更
go func(){
for {
time.Sleep(time.Second * 5) // 每次请求后延迟一下

// 目前只测试了etcd支持
err := runtime_viper.WatchRemoteConfig()
if err != nil {
log.Errorf("unable to read remote config: %v", err)
continue
}

// 将新配置反序列化到我们运行时的配置结构体中。你还可以借助channel实现一个通知系统更改的信号
runtime_viper.Unmarshal(&runtime_conf)
}
}()

基于Viper实现的环境变量动态链接

1
2
3
4
5
6
7
8
9
10
11
import (
"github.com/spf13/viper"
)

// DynamicEnv is a dynamic adapter that interoperates with environment variables
func DynamicEnv(envName, Prefix string, defaultVal interface{}) interface{} {
viper.SetDefault(envName, defaultVal)
viper.SetEnvPrefix(Prefix)
viper.BindEnv(envName)
return viper.Get(envName)
}

Go

无路是在开发、测试、亦或者调试有一好的日志,都会事半功倍。本节我来学习一下 go 语言明星日志库 logrus。相关链接如下所示
github: https://github.com/sirupsen/logrus
pkghttps://pkg.go.dev/logur.dev/adapter/logrus

Logrus 简介

Logrus 是 Go 语言结构化的 logger,与标准库 logger 完全 API 兼容。

它有以下特点:

  • 完全兼容标准日志库,拥有七种日志级别:Trace, Debug, Info, Warning, Error, Fataland Panic
  • 可选的日志输出格式,内置了两种日志格式 JSONFormater 和 TextFormatter,还可以自定义日志格式
  • Field 机制,通过 Filed 机制进行结构化的日志记录
  • 可扩展的 Hook 机制,允许使用者通过 Hook 的方式将日志分发到任意地方,如本地文件系统,logstash,elasticsearch 或者 mq 等,或者通过 Hook 定义日志内容和格式等
  • 线程安全

logrus 的安装

logrus 安装也非常的简单,直接使用go get即可,安装命令如下所示

1
go get -v -u logur.dev/adapter/logrus

其中

-v 为显示包安装信息

-u 为安装最新版

logrus 的使用

介绍的包的使用,或许可以从几个维度,初始化、基本使用、骚操作及拓展。源于包使用,但不限于包使用

初始化

logrus 相关于初始化的方面,一共有三种方式。基于New()Logger不操作

直接使用

直接使用相对来说更便捷,更清亮。也是相对来说性能最高的,但不足也显而易言,那就是不能有更自由的操作了,如设置 log Level、Hook、Format 等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
logrus.Trace("trace msg")
logrus.Tracef("trace %v", "msg")

logrus.Debug("debug msg")
logrus.Debugf("debug %v", "msg")

logrus.Info("info msg")
logrus.Infof("info %v", "msg")

logrus.Warn("warn msg")
logrus.Warnf("warn %v", "msg")

logrus.Error("error msg")
logrus.Errorf("error %v", "msg")

logrus.Fatal("fatal msg")
logrus.Fatalf("fatal %v", "msg")

logrus.Panic("panic msg")
logrus.Panicf("panic %v", "msg")

当然根据默认的规则会忽略掉一些输出信息

使用 New 初始化于定制

相对直接使用,使用 New 初始化,拥有更多的操作空间。

首先声明一个全局变量log,代码如下所示

1
var log = logrus.New()

常规情况下对于日志的定制主要在以下几个方面

  • 日志可见等级
  • 日志格式配置
  • 执行调用信息
  • 日志另存为

日志可见等级

相关源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const (
// PanicLevel level, highest level of severity. Logs and then calls panic with the
// message passed to Debug, Info, ...
PanicLevel Level = iota
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
// logging level is set to Panic.
FatalLevel
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service.
ErrorLevel
// WarnLevel level. Non-critical entries that deserve eyes.
WarnLevel
// InfoLevel level. General operational entries about what's going on inside the
// application.
InfoLevel
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
DebugLevel
// TraceLevel level. Designates finer-grained informational events than the Debug.
TraceLevel
)

简而言之,登记越高(数字越大),显示的等级越全。如 6 显示所有的日志,0 只显示Panic

设置可见等级之需要在log.SetLevel()(log 为var log = logrus.New()而来)设置整形(6-0)即可。

日志格式化配置

日志格式化主要分为文本格式化、JSON 格式化、自定义格式化或第三方插件格式化

如下

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
// log.SetFormatter(&logrus.TextFormatter{
// DisableColors: false,
// FullTimestamp: true,
// })
type TextFormatter struct {
// Set to true to bypass checking for a TTY before outputting colors.
ForceColors bool

// Force disabling colors.
DisableColors bool

// Force quoting of all values
ForceQuote bool

// DisableQuote disables quoting for all values.
// DisableQuote will have a lower priority than ForceQuote.
// If both of them are set to true, quote will be forced on all values.
DisableQuote bool

// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
EnvironmentOverrideColors bool

// Disable timestamp logging. useful when output is redirected to logging
// system that already adds timestamps.
DisableTimestamp bool

// Enable logging the full timestamp when a TTY is attached instead of just
// the time passed since beginning of execution.
FullTimestamp bool

// TimestampFormat to use for display when a full timestamp is printed.
// The format to use is the same than for time.Format or time.Parse from the standard
// library.
// The standard Library already provides a set of predefined format.
TimestampFormat string

// The fields are sorted by default for a consistent output. For applications
// that log extremely frequently and don't use the JSON formatter this may not
// be desired.
DisableSorting bool

// The keys sorting function, when uninitialized it uses sort.Strings.
SortingFunc func([]string)

// Disables the truncation of the level text to 4 characters.
DisableLevelTruncation bool

// PadLevelText Adds padding the level text so that all the levels output at the same length
// PadLevelText is a superset of the DisableLevelTruncation option
PadLevelText bool

// QuoteEmptyFields will wrap empty fields in quotes if true
QuoteEmptyFields bool

// Whether the logger's out is to a terminal
isTerminal bool

// FieldMap allows users to customize the names of keys for default fields.
// As an example:
// formatter := &TextFormatter{
// FieldMap: FieldMap{
// FieldKeyTime: "@timestamp",
// FieldKeyLevel: "@level",
// FieldKeyMsg: "@message"}}
FieldMap FieldMap

// CallerPrettyfier can be set by the user to modify the content
// of the function and file keys in the data when ReportCaller is
// activated. If any of the returned value is the empty string the
// corresponding key will be removed from fields.
CallerPrettyfier func(*runtime.Frame) (function string, file string)

terminalInitOnce sync.Once

// The max length of the level text, generated dynamically on init
levelTextMaxLength int
}

格式 JSON 化

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
// log.SetFormatter(&logrus.JSONFormatter{})
type JSONFormatter struct {
// TimestampFormat sets the format used for marshaling timestamps.
// The format to use is the same than for time.Format or time.Parse from the standard
// library.
// The standard Library already provides a set of predefined format.
TimestampFormat string

// DisableTimestamp allows disabling automatic timestamps in output
DisableTimestamp bool

// DisableHTMLEscape allows disabling html escaping in output
DisableHTMLEscape bool

// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
DataKey string

// FieldMap allows users to customize the names of keys for default fields.
// As an example:
// formatter := &JSONFormatter{
// FieldMap: FieldMap{
// FieldKeyTime: "@timestamp",
// FieldKeyLevel: "@level",
// FieldKeyMsg: "@message",
// FieldKeyFunc: "@caller",
// },
// }
FieldMap FieldMap

// CallerPrettyfier can be set by the user to modify the content
// of the function and file keys in the json data when ReportCaller is
// activated. If any of the returned value is the empty string the
// corresponding key will be removed from json fields.
CallerPrettyfier func(*runtime.Frame) (function string, file string)

// PrettyPrint will indent all json logs
PrettyPrint bool
}

https://github.com/sirupsen/logrus#formatters

image-20210926173746478

写入文件夹 SetOutput

O_RDONLY:只读模式(read-only)

O_WRONLY:只写模式(write-only)

O_RDWR:读写模式(read-write)

O_APPEND:追加模式(append)

O_CREATE:文件不存在就创建(create a new file if none exists.)

O_EXCL:与 O_CREATE 一起用,构成一个新建文件的功能,它要求文件必须不存在(used with O_CREATE, file must not exist)

O_SYNC:同步方式打开,即不使用缓存,直接写入硬盘 O_TRUNC:打开并清空文件

示例代码如下

1
2
3
4
5
6
7
8
9
10
func init(){
logDirPath := `log/` + time.Now().Format("2006-01-02")
logFilePath := filepath.Join(logDirPath, time.Now().Format(`15`))
os.MkdirAll(logFilePath, 0775)
file, err := os.OpenFile(logFilePath + `/` + time.Now().Format(`04`) + `.log`, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Error(`open file error`)
}
log.SetOutput(file)
}

log.SetReportCaller(): 显示调用关系,开启这个模式会增加性能开销(成本在 20% 到 40% 之间)。

Hook

logrus 通过实现 Hook接口扩展 hook 机制,可以根据需求将日志分发到任意的存储介质, 比如 es, mq 或者监控报警系统,及时获取异常日志。可以说极大的提高了日志系统的可扩展性。

1
2
3
4
5
6
7
type Hook interface {
// 定义哪些等级的日志触发 hook 机制
Levels() []Level
// hook 触发器的具体执行操作
// 如果 Fire 执行失败,错误日志会重定向到标准错误流
Fire(*Entry) error
}

具体 Hook 示列可参考 https://github.com/sirupsen/logrus/blob/master/hooks/syslog/README.md

referer

https://blog.csdn.net/wangzhezhilu001/article/details/95363789

https://blog.csdn.net/sserf/article/details/103388133

爬虫

操作键

image-20210920184244435

resume/pause script execution: 恢复/暂停脚本执行

step over next function call: 跨过,实际表现是不遇到函数时,执行下一步。遇到函数时,不进入函数直接执行下一步。

step into next function call: 跨入,实际表现是不遇到函数时,执行下一步。遇到到函数时,进入函数执行上下文。

step out of current function:跳出当前函数

deactivate breakpoints:停用断点

don‘t pause on exceptions:不暂停异常捕获

Watch

image-20210920184426027

变量监听

定位到关键值时加入Watch中实现实时监听,可根据监听内容变化预估此变化。

BreakPoints

所有断点列表,且自动按照执行顺序排序

image-20210920184528622

Scope

该范围内所有变量的值

image-20210920185155365

调用栈

一个 procedure(通常译作“过程”)吃进来一些参数,干一些事情,再吐出去一个返回值(或者什么也不吐)

image-20210920185959949

XHR/fetch Breakpints

XHR/fetch Breakpints:请求断点(拦截),当发生符合要求的将触发定位到请求发送前一步

image-20210920190017589

DOM Break points

当符合条件时触发定位到BOM

image-20210920190528135

Global Listeners

全局时间监听,包含所有时间,如点击、滑动等

image-20210920190640937

Event Listener Break points

事件侦听器断点,监听所有事件与断点实现。

image-20210920190803044

预览几种不同的breakpoint类型

众人皆知的breakpoint类型是line-of-code。但是line-of-code型breakpoint有的时候没法设置(其实就是没法在代码左边点出一个绿点来
),或者如果你正在使用一个大型的代码库。通过学习如何和何时使用这些不同类型的breakpoint debug,会大大节约你的时间。

断点类型 当你想Pause的时候使用
Line-of-code 代码具体某一行(其实就是没法在代码左边点出一个绿点来
Conditional line-of-code 代码具体某一行,但是只有在一些条件为true时
DOM 在改变或者移除一个DOM节点或者它的DOM子节点时
XHR 当一个XHR URL包含一个string pattern
Event Listener 在运行了某个特定事件后的代码上,例如click事件触发
Exception 在抛出了一个caught或者uncaught的exception时
Function 当一个函数被调用时

this指向

全局作用域 this = window

局部作用域 this = 调用者

类的方法里面 this = 类自己

referer

https://blog.csdn.net/xc_zhou/article/details/106269239

https://blog.csdn.net/qq_27324983/article/details/102467199

数据库

Server version: 8.0.20 Source distribution

SHOW ENGINES

Engine Support Comment Transactions XA Savepoints
FEDERATED NO Federated MySQL storage engine NULL NULL *

NULL* | |
| MEMORY | YES | Hash based, stored in memory, useful for temporary… | NO | NO | NO | |
| InnoDB | DEFAULT | Supports transactions, row-level locking, and fore… | YES | YES | YES | |
| PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO | |
| MyISAM | YES | MyISAM storage engine | NO | NO | NO | |
| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO | |
| BLACKHOLE | YES | /dev/null storage engine (anything you write to it… | NO | NO | NO | |
| CSV | YES | CSV storage engine | NO | NO | NO | |
| ARCHIVE | YES | Archive storage engine | NO | NO | NO | |

MySQL中常用的存储引擎有InnoDB、MyISAM、MEMORY、ARCHIVE和CSV

存储引擎

InnoDB存储引擎

支持事务 锁级别为行锁,比MyISAM存储引擎支持更高的并发 能够通过二进制日志恢复数据 支持外键操作
在索引存储上,索引和数据存储在同一个文件中,默认按照B+Tree组织索引的结构。同时,主键索引的叶子节点存储完整的数据记录,非主键索引的叶子节点存储主键的值。

在MySQL 5.6版本之后,默认使用InnoDB存储引擎。

在MySQL 5.6版本之后,InnoDB存储引擎支持全文索引。

MyISAM存储引擎

不支持事务。

锁级别为表锁,在要求高并发的场景下不太适用。

如果数据文件损坏,难以恢复数据。

不支持外键。

在索引存储上,索引文件与数据文件分离。

支持全文索引。

MEMORY存储引擎

不支持TEXT和BLOB数据类型,只支持固定长度的字符串类型。例如,在MEMORY存储引擎中,会将VARCHAR类型自动转化成CHAR类型。

锁级别为表锁,在高并发场景下会成为瓶颈。

通常会被作为临时表使用,存储查询数据时产生中间结果。

数据存储在内存中,重启服务器后数据会丢失。如果是需要持久化的数据,不适合存储在MEMORY存储引擎的数据表中。

ARCHIVE存储引擎

支持数据压缩,在存储数据前会对数据进行压缩处理,适合存储归档的数据。

只支持数据的插入和查询,插入数据后,不能对数据进行更改和删除,而只能查询。

只支持在整数自增类型的字段上添加索引。

CSV存储引擎

主要存储的是csv格式的文本数据,可以直接打开存储的文件进行编辑。

可以将MySQL中某个数据表中的数据直接导出为csv文件,也可以将.csv文件导入数据表中。

https://dev.mysql.com/doc/refman/8.0/en/storage-engines.html

https://dev.mysql.com/doc/refman/8.0/en/innodb-storage-engine.html