分布式计算
Ray
MachineLearning
Record the basic information of machine learning here
BI框架
BI(Business Intelligence)框架是一种软件工具集合,用于帮助企业从大量的数据中提取、分析和可视化有关业务运营的洞察。它提供了一套功能强大的工具和技术,用于数据仓库、数据处理、数据可视化、报表和仪表盘等方面的业务分析。
Disk
UEFI引导
Quant
统计学
梯度(斜率)
import numpy as np
np_test1 = np.array([0,0,0,0,0,0])
print("梯度1",np_test1,np.gradient(np_test1))
np_test2 = np.array([0,1,2,3,4,5])
print("梯度2",np_test2,np.gradient(np_test2))
np_test3 = np.array([0,1,2,1,0,-1])
print("梯度3",np_test3,np.gradient(np_test3))
np_test4 = np.array([-1,-1,-1,-1,-1,-1])
print("梯度4",np_test4,np.gradient(np_test4))
np_test5 = np.array([0,-1,-3,-6,-10,-15,-18,-20,-21,-21,-20,-18])
print("梯度5",np_test5,np.gradient(np_test5))
np_test6 = np.array([0,-1,-3,-6,-10,-15,-18,-20,-21,-21,-20,-18,-20])
print("梯度6",np_test6,np.gradient(np_test6))
np_test7 = np.array([0,-1,-3,-6,-10,-15,-18,-20,-21,-21,-20,-18,-18])
print("梯度7",np_test7,np.gradient(np_test7))
np_test8 = np.array([0,-1,-3,-6,-10,-15,-18,-20,-21,-21,-20,-18,-16])
print("梯度8",np_test8,np.gradient(np_test8))
OUT:
梯度1 [0 0 0 0 0 0] [0. 0. 0. 0. 0. 0.]
梯度2 [0 1 2 3 4 5] [1. 1. 1. 1. 1. 1.]
梯度3 [ 0 1 2 1 0 -1] [ 1. 1. 0. -1. -1. -1.]
梯度4 [-1 -1 -1 -1 -1 -1] [0. 0. 0. 0. 0. 0.]
梯度5 [ 0 -1 -3 -6 -10 -15 -18 -20 -21 -21 -20 -18] [-1. -1.5 -2.5 -3.5 -4.5 -4. -2.5 -1.5 -0.5 0.5 1.5 2. ]
梯度6 [ 0 -1 -3 -6 -10 -15 -18 -20 -21 -21 -20 -18 -20] [-1. -1.5 -2.5 -3.5 -4.5 -4. -2.5 -1.5 -0.5 0.5 1.5 0. -2. ]
梯度7 [ 0 -1 -3 -6 -10 -15 -18 -20 -21 -21 -20 -18 -18] [-1. -1.5 -2.5 -3.5 -4.5 -4. -2.5 -1.5 -0.5 0.5 1.5 1. 0. ]
梯度8 [ 0 -1 -3 -6 -10 -15 -18 -20 -21 -21 -20 -18 -16] [-1. -1.5 -2.5 -3.5 -4.5 -4. -2.5 -1.5 -0.5 0.5 1.5 2. 2. ]
标准差
import numpy as np
np_test1 = np.array([0,0,0,0,0])
print("标准差1",np_test1,np_test1.std())
np_test2 = np.array([0,1,0,0,0])
print("标准差2",np_test2,np_test2.std())
np_test3 = np.array([0,0,0,1,0])
print("标准差3",np_test3,np_test3.std())
np_test4 = np.array([0,0,1,0,0])
print("标准差4",np_test4,np_test4.std())
np_test5 = np.array([1,1,1,1,1])
print("标准差5",np_test5,np_test5.std())
np_test6 = np.array([1,1,0,0,0])
print("标准差6",np_test6,np_test6.std())
np_test7 = np.array([1,1,0,0,0,0])
print("标准差7",np_test7,np_test7.std())
np_test8 = np.array([1,1,1,0,0,0])
print("标准差8",np_test8,np_test8.std())
np_test9 = np.array([0.5,0.5,0.5,0,0,0])
print("标准差9",np_test9,np_test9.std())
np_test10 = np.array([0.25,0.25,0.25,0,0,0])
print("标准差10",np_test10,np_test10.std())
np_test11 = np.array([0.75,0.75,0.75,0,0,0])
print("标准差11",np_test11,np_test11.std())
np_test12 = np.array([0.75,0.75,0.75,1,1,1])
print("标准差12",np_test12,np_test12.std())
OUT:
标准差1 [0 0 0 0 0] 0.0
标准差2 [0 1 0 0 0] 0.4000000000000001
标准差3 [0 0 0 1 0] 0.4
标准差4 [0 0 1 0 0] 0.4000000000000001
标准差5 [1 1 1 1 1] 0.0
标准差6 [1 1 0 0 0] 0.48989794855663565
标准差7 [1 1 0 0 0 0] 0.47140452079103173
标准差8 [1 1 1 0 0 0] 0.5
标准差9 [0.5 0.5 0.5 0. 0. 0. ] 0.25
标准差10 [0.25 0.25 0.25 0. 0. 0. ] 0.125
标准差11 [0.75 0.75 0.75 0. 0. 0. ] 0.375
标准差12 [0.75 0.75 0.75 1. 1. 1. ] 0.125
机器学习框架环境
BTGYM
https://github.com/Kismuz/btgym
tensortrade
https://github.com/tensortrade-org/tensortrade
Baseline backtest performance
test
Environment configuration [M4]
model name : AMD Ryzen 7 6800H with Radeon Graphics [16 core]
MemTotal: 28610872 kB
MemFree: 26584252 kB
SwapTotal: 7340032 kB
SwapFree: 7340032 kB
system: Ubuntu 20.04.5 LTS
kernel: Linux 5.15.79.1-microsoft-standard-WSL2 x86_64
Python 3.8.10
backtrader 1.9.76.123
ccxt 2.6.47
pandas 1.5.2
pandas-ta 0.3.14b0
pymongo 4.3.3
[Init] 策略: 测试信号_test 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
[Init] 抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 14_200 [测试信号_test] 最终 1000000.00
real 1m20.179s
user 1m18.912s
sys 0m1.421s
Environment configuration [M4]
model name : AMD Ryzen 7 6800H with Radeon Graphics [16 core]
MemTotal: 58455856 kB
MemFree: 50016704 kB
SwapTotal: 8388608 kB
SwapFree: 8388608 kB
system: NT
kernel: MINGW64_NT-10.0-22621 3.3.6-341.x86_64 unknown
Python 3.9.13
backtrader 1.9.76.123
ccxt 2.6.39
pandas 1.4.4
pandas-ta 0.3.14b0
pymongo 4.3.3
[Init] 策略: 测试信号_test 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
[Init] 抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 14_200 [测试信号_test] 最终 1000000.00
real 1m43.855s
user 0m0.015s
sys 0m0.000s
Environment configuration [m3]
model name : Intel(R) Xeon(R) CPU E5-2660 v2 @ 2.20GHz [40 core]
MemTotal: 131970928 kB
MemFree: 124979156 kB
SwapTotal: 2097148 kB
SwapFree: 2097148 kB
system: Ubuntu 20.04.3 LTS
kernel: Linux 5.15.0-46-generic x86_64
Python 3.8.10
backtrader 1.9.76.123
ccxt 2.5.46
pandas 1.4.1
pandas-ta 0.3.14b0
pymongo 4.3.3
[Init] 策略: 测试信号_test 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
[Init] [主数据库:mongodb://192.168.1.20]
抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 14_200 [测试信号_test] 最终 1000000.00
real 5m10.304s
user 5m1.097s
sys 0m5.967s
Environment configuration [E14]
model name : AMD Ryzen 5 4500U with Radeon Graphics [6 core]
MemTotal: 12451164 kB
MemFree: 11482124 kB
SwapTotal: 4194304 kB
SwapFree: 4194304 kB
system: Ubuntu 20.04.1 LTS
kernel: Linux 5.4.72-microsoft-standard-WSL2 x86_64
Python 3.8.10
backtrader 1.9.76.123
ccxt 2.4.27
pandas 1.5.2
pandas-ta 0.3.14b0
pymongo 4.3.3
[Init] 策略: 测试信号_test 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
[Init] 抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 14_200 [测试信号_test] 最终 1000000.00
real 1m59.407s
user 1m57.261s
sys 0m1.480s
Environment configuration [M1]
model name : Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz [4 core]
MemTotal: 33452176 kB
MemFree: 18736776 kB
SwapTotal: 12582912 kB
SwapFree: 12463216 kB
system: NT
kernel: MINGW64_NT-10.0-19044 3.1.4-340.x86_64 unknown
Python 3.7.6
backtrader 1.9.76.123
ccxt 2.2.17
pandas 1.3.5
pandas-ta 0.3.14b0
pymongo 4.3.3
[Init] 策略: 测试信号_test 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
[Init] 抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 14_200 [测试信号_test] 最终 1000000.00
real 4m22.050s
user 0m0.015s
sys 0m0.016s
Environment configuration [M1]
model name : Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz [4 core]
MemTotal: 26198024 kB
MemFree: 23249568 kB
SwapTotal: 7340032 kB
SwapFree: 7340032 kB
system: Ubuntu 20.04.1 LTS
kernel: Linux 4.19.128-microsoft-standard x86_64
Python 3.8.10
backtrader 1.9.76.123
ccxt 2.5.30
pandas 1.4.4
pandas-ta 0.3.14b0
pymongo 4.3.3
[Init] 策略: 测试信号_test 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
[Init] 抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 14_200 [测试信号_test] 最终 1000000.00
real 2m27.018s
user 2m23.037s
sys 0m1.120s
Environment configuration [BRIC]
model name : Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz [4 core]
MemTotal: 13008184 kB
MemFree: 12005880 kB
SwapTotal: 4194304 kB
SwapFree: 4194304 kB
system: Ubuntu 20.04.4 LTS
kernel: Linux 5.10.16.3-microsoft-standard-WSL2 x86_64
Python 3.8.10
backtrader 1.9.76.123
ccxt 2.5.30
pandas 1.4.4
pandas-ta 0.3.14b0
pymongo 4.3.3
[Init] 策略: 测试信号_test 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
[Init] [主数据库:mongodb://192.168.3.32]
抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 14_200 [测试信号_test] 最终 1000000.00
胜率结果: {'all_num': 0, 'win_num': 0, 'los_num': 0, 'win_p': 0, 'los_p': 0}
real 3m40.160s
user 3m37.635s
sys 0m3.841s
Environment configuration [M2]
model name : Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz [16 core]
MemTotal: 13022068 kB
MemFree: 10488912 kB
SwapTotal: 4194304 kB
SwapFree: 4194304 kB
system: Ubuntu 20.04.3 LTS
kernel: Linux 5.10.16.3-microsoft-standard-WSL2 x86_64
Python 3.8.10
backtrader 1.9.76.123
ccxt 2.6.47
pandas 1.5.3
pandas-ta 0.3.14b0
pymongo 4.3.3
[Init] 策略: 测试信号_test 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
[Init] [主数据库:mongodb://192.168.1.20]
抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 14_200 [测试信号_test] 最终 1000000.00
real 4m17.342s
user 4m9.496s
sys 0m2.987s
m16_1
Environment configuration [M1]
model name : Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz [4 core]
MemTotal: 26198024 kB
MemFree: 21258576 kB
SwapTotal: 7340032 kB
SwapFree: 7340032 kB
kernel: Linux 4.19.128-microsoft-standard x86_64
Python 3.8.10
backtrader 1.9.76.123
ccxt 1.93.108
pandas 1.4.4
pandas-ta 0.3.14b0
pymongo 4.2.0
[Init] 策略: 微震荡_m16_1 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
[Init] 抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 10_600_9_65_9_29_5_3_66_6_20_25_16_4_30_1_1_27_27 [微震荡_m16_1] 最终 983616.87
胜率结果: {'all_num': 1338, 'win_num': 677, 'los_num': 661, 'win_p': 0.51, 'los_p': 0.49}
real 123m18.071s
user 122m40.520s
sys 0m39.400s
Environment configuration [BRIC]
model name : Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz [4 core]
MemTotal: 13008184 kB
MemFree: 12119408 kB
SwapTotal: 4194304 kB
SwapFree: 4194304 kB
kernel: Linux 5.10.16.3-microsoft-standard-WSL2 x86_64
Python 3.8.10
backtrader 1.9.76.123
ccxt 2.2.17
pandas 1.4.4
pandas-ta 0.3.14b0
pymongo 4.2.0
[Init] 策略: 微震荡_m16_1 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
[Init] [主数据库:mongodb://192.168.3.32]
抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 10_600_9_65_9_29_5_3_66_6_20_25_16_4_30_1_1_27_27 [微震荡_m16_1] 最终 983616.87
胜率结果: {'all_num': 1338, 'win_num': 677, 'los_num': 661, 'win_p': 0.51, 'los_p': 0.49}
real 109m52.703s
user 109m23.917s
sys 0m28.641s
Environment configuration [M2]
model name : Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz [16 core]
MemTotal: 13022068 kB
MemFree: 12341284 kB
SwapTotal: 4194304 kB
SwapFree: 4194304 kB
kernel: Linux 5.10.16.3-microsoft-standard-WSL2 x86_64
Python 3.8.10
backtrader 1.9.76.123
ccxt 2.2.5
pandas 1.4.2
pandas-ta 0.3.14b0
pymongo 4.3.3
[Init] 策略: 微震荡_m16_1 币种: BTC/USDT:USDT
[Init] 初始投资: --money 1000000.00
[Init] 自动切换到1%下注模式!
[Init] 下注方式: --sizer=1 --maxsizer=1 %
[Init] 设置佣金: --commission 0.001
抓取范围: --fromdate --todate
2020-01-01 00:00 open:7199.9 high:7199.9 low:7189.4 close:7198.2 volume:48238.0
2023-01-01 00:00 open:16591.6 high:16593.8 low:16591.6 close:16593.8 volume:2349.0
总数据量: 1578241 时间跨度: 1096.00天
-pd 10_600_9_65_9_29_5_3_66_6_20_25_16_4_30_1_1_27_27 [微震荡_m16_1] 最终 983616.87
胜率结果: {'all_num': 1338, 'win_num': 677, 'los_num': 661, 'win_p': 0.51, 'los_p': 0.49}
real 233m6.286s
user 224m16.812s
sys 0m27.302s
时序数据库
InfluxDB
InfluxDB是一个开源分布式时序、时间和指标数据库,使用 Go 语言编写,无需外部依赖。
文档:https://docs.influxdata.com/influxdb/v2.0/
Docker下安装InfluxDB
docker run -p 8086:8086 -v /app/data/influxdb:/var/lib/influxdb2 influxdb
PHP使用InfluxDB
composer require influxdata/influxdb-client-php
项目地址:https://github.com/influxdata/influxdb-client-php
TimescaleDB
TimescaleDB是一个基于PostgreSQL的 分布式时间序列数据库,可扩展到每秒超过1000万个指标,支持本机压缩,处理高基数,并提供本机时间序列功能,例如数据保留策略,连续聚合视图,下采样,数据填充和内插。
TimescaleDB还支持完整的SQL,各种数据类型(数字,文本,数组,JSON,布尔值)和ACID语义。操作上成熟的功能包括高可用性,流式备份,随时间推移升级,角色和权限以及安全性。
文档:https://docs.timescale.com/latest/main
Docker下安装TimescaleDB
docker run -d --name some-timescaledb -p 5432:5432 timescale/timescaledb
亿级数据处理
MongoDB中存在一张数据量过亿的表
目前文档数量 232,656,863
文档容量:46.35Gb
运行环境:
操作系统:Ubuntu 20.04.1 LTS
CPU: i5-4590 @ 3.30 Ghz
内存: 32G
硬盘:M.2 SSD
已在2个字段上创建索引,命中索引的话响应3ms,没命中索引80s以上。
批量导入优化
有百万条数据需导入,为加快导入速度,我们执行批量导入,一次1万条试试:
导入: 610000 [run 5309.4ms]
导入: 620000 [run 5290.9ms]
导入: 630000 [run 4837.2ms]
导入: 640000 [run 4927.6ms]
导入: 650000 [run 5749.3ms]
导入: 660000 [run 4754.5ms]
导入: 670000 [run 5378.4ms]
导入: 680000 [run 4548.4ms]
导入: 690000 [run 4558.4ms]
可以看到1万条一次需要大概5秒。
如果把索引去掉:
导入: 1200000 [run 52.5ms]
导入: 1210000 [run 53.6ms]
导入: 1220000 [run 52.5ms]
导入: 1230000 [run 53.7ms]
导入: 1240000 [run 53ms]
导入: 1250000 [run 52.8ms]
导入: 1260000 [run 60.8ms]
导入: 1270000 [run 52.7ms]
导入: 1280000 [run 55.3ms]
导入: 1290000 [run 55.3ms]
快了近100倍。
像一些冷数据,导入量比较大时,把索引去除加快导入速度。等导入完,重建索引,效率反而提升。
大量数据查询优化
当数据量返回过大时,除了必要的索引优化外,对数据集返回数的限制尤为必要。
请使用skip和limit做数据限制。
MongoDB性能分析和优化
explain
在查询语句后面跟上explain()能获取查询相关诊断信息
比如:
> db.getCollection("m_pass_base").find({_id:{$regex:/^malu/}}).explain(true)
返回:
{
"queryPlanner": {
"plannerVersion": NumberInt("1"),
"namespace": "d1.m_pass_base", // 查询的集合
"indexFilterSet": false, // 索引过滤
"parsedQuery": { // 查询条件
"_id": {
"$regex": "^malu"
}
},
"winningPlan": { // 最佳执行计划
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"_id": NumberInt("1")
},
"indexName": "_id_",
"isMultiKey": false,
"multiKeyPaths": {
"_id": [ ]
},
"isUnique": true,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": { // 当前查询具体使用的索引
"_id": [
"[\"malu\", \"malv\")",
"[/^malu/, /^malu/]"
]
}
}
},
"rejectedPlans": [ ] // 拒绝执行计划
},
"executionStats": { // executionStats会返回最佳执行计划的一些统计信息
"executionSuccess": true, // 是否执行成功
"nReturned": NumberInt("466"), // 返回结果数
"executionTimeMillis": NumberInt("0"),
"totalKeysExamined": NumberInt("467"), // 索引扫描数
"totalDocsExamined": NumberInt("466"), // 文档扫描数
"executionStages": {
"stage": "FETCH", // 扫描方式
"nReturned": NumberInt("466"),
"executionTimeMillisEstimate": NumberInt("0"),
"works": NumberInt("468"),
"advanced": NumberInt("466"),
"needTime": NumberInt("1"),
"needYield": NumberInt("0"),
"saveState": NumberInt("3"),
"restoreState": NumberInt("3"),
"isEOF": NumberInt("1"),
"invalidates": NumberInt("0"),
"docsExamined": NumberInt("466"),
"alreadyHasObj": NumberInt("0"),
"inputStage": {
"stage": "IXSCAN",
"nReturned": NumberInt("466"),
"executionTimeMillisEstimate": NumberInt("0"),
"works": NumberInt("468"),
"advanced": NumberInt("466"),
"needTime": NumberInt("1"),
"needYield": NumberInt("0"),
"saveState": NumberInt("3"),
"restoreState": NumberInt("3"),
"isEOF": NumberInt("1"),
"invalidates": NumberInt("0"),
"keyPattern": {
"_id": NumberInt("1")
},
"indexName": "_id_",
"isMultiKey": false,
"multiKeyPaths": {
"_id": [ ]
},
"isUnique": true,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": { // 当前查询具体使用的索引
"_id": [
"[\"malu\", \"malv\")",
"[/^malu/, /^malu/]"
]
},
"keysExamined": NumberInt("467"),
"seeks": NumberInt("2"),
"dupsTested": NumberInt("0"),
"dupsDropped": NumberInt("0"),
"seenInvalidated": NumberInt("0")
}
},
"allPlansExecution": [ ] // 所有执行计划
},
"serverInfo": {
"host": "M1",
"port": NumberInt("27017"),
"version": "3.6.8",
"gitVersion": "8e540c0b6db93ce994cc548f000900bdc740f80a"
},
"ok": 1
}
扫描方式stage有如下几种:
COLLSCAN:全表扫描
IXSCAN:索引扫描
FETCH:根据索引去检索指定document
SHARD_MERGE:将各个分片返回数据进行merge
SORT:表明在内存中进行了排序
LIMIT:使用limit限制返回数
SKIP:使用skip进行跳过
IDHACK:针对_id进行查询
SHARDING_FILTER:通过mongos对分片数据进行查询
COUNT:利用db.coll.explain().count()之类进行count运算
COUNTSCAN:count不使用Index进行count时的stage返回
COUNT_SCAN:count使用了Index进行count时的stage返回
SUBPLA:未使用到索引的$or查询的stage返回
TEXT:使用全文索引进行查询时候的stage返回
PROJECTION:限定返回字段时候stage的返回
所以对于查询优化,我们希望看到stage的组合是(查询的时候尽可能用上索引):
Fetch+IDHACK
Fetch+ixscan
Limit+(Fetch+ixscan)
PROJECTION+ixscan
SHARDING_FITER+ixscan
COUNT_SCAN
而不希望看到包含如下的stage:
COLLSCAN(全表扫描)
SORT(使用sort但是无index)
不合理的SKIP
SUBPLA(未用到index的$or)
COUNTSCAN(不使用index进行count)
hint
hint 可以强制 MongoDB 使用一个指定的索引,一般我们在联合索引上做优化。
hint({“$natural”:true}) 可以强制查询走全表扫描,这种情况适合在返回数据集很大的时候,不走索引反而效率更高。
MongoDB慢查询
官方文档:https://docs.mongodb.com/manual/reference/database-profiler/
开启慢查询Profiling
Profiling级别说明
0:关闭,不收集任何数据。
1:收集慢查询数据,默认是100毫秒。
2:收集所有数据
方式一:配置文件开启Profiling
修改启动mongo.conf,插入以下代码
#开启慢查询,200毫秒的记录
profile = 1
slowms = 200
方式二:通过命令开启
注意该方式只保留在内存中,重启mongo将失效
#查看状态:级别和时间
drug:PRIMARY> db.getProfilingStatus()
{ "was" : 1, "slowms" : 100 }
#查看级别
drug:PRIMARY> db.getProfilingLevel()
1
#设置级别
drug:PRIMARY> db.setProfilingLevel(2)
{ "was" : 1, "slowms" : 100, "ok" : 1 }
#设置级别和时间
drug:PRIMARY> db.setProfilingLevel(1,200)
{ "was" : 2, "slowms" : 100, "ok" : 1 }
修改“慢查询日志”的大小
#关闭Profiling
drug:PRIMARY> db.setProfilingLevel(0)
{ "was" : 0, "slowms" : 200, "ok" : 1 }
#删除system.profile集合
drug:PRIMARY> db.system.profile.drop()
true
#创建一个新的system.profile集合
drug:PRIMARY> db.createCollection( "system.profile", { capped: true, size:4000000 } )
{ "ok" : 1 }
#重新开启Profiling
drug:PRIMARY> db.setProfilingLevel(1)
{ "was" : 0, "slowms" : 200, "ok" : 1 }
慢日志示例:
{
"op": "command", // 操作类型,有insert、query、update、remove、getmore、command
"ns": "d1.m_pass_base", // 操作的集合
"command": { // 查询语句
"aggregate": "m_pass_base",
"pipeline": [
{
"$match": {
"_id": /^1/
}
},
{
"$group": {
"_id": NumberInt("1"),
"n": {
"$sum": NumberInt("1")
}
}
}
],
"allowDiskUse": false,
"cursor": { },
"$db": "d1",
"lsid": {
"id": UUID("e4f7b72e-b69a-42a9-91e5-ec85c7c11f2a")
}
},
"keysExamined": NumberInt("77810366"),
"docsExamined": NumberInt("0"),
"cursorExhausted": true,
"numYield": NumberInt("607894"),
"locks": {
"Global": {
"acquireCount": {
"r": NumberLong("1215792")
}
},
"Database": {
"acquireCount": {
"r": NumberLong("607896")
}
},
"Collection": {
"acquireCount": {
"r": NumberLong("607896")
}
}
},
"nreturned": NumberInt("1"),
"responseLength": NumberInt("111"),
"protocol": "op_msg",
"millis": NumberInt("42190"), // 消耗的时间(毫秒)
"planSummary": "IXSCAN { _id: 1 }",
"ts": ISODate("2021-04-06T13:43:56.792Z"), // 语句执行的时间
"client": "192.168.50.1",
"allUsers": [ ],
"user": ""
}
日常使用的查询
#返回最近的10条记录
db.system.profile.find().limit(10).sort({ ts : -1 }).pretty()
#返回所有的操作,除command类型的
db.system.profile.find( { op: { $ne : 'command' } } ).pretty()
#返回特定集合
db.system.profile.find( { ns : 'mydb.test' } ).pretty()
#返回大于5毫秒慢的操作
db.system.profile.find( { millis : { $gt : 5 } } ).pretty()
#从一个特定的时间范围内返回信息
db.system.profile.find(
{
ts : {
$gt : new ISODate("2021-04-06T03:00:00Z") ,
$lt : new ISODate("2021-04-06T03:40:00Z")
}
}
).pretty()
#特定时间,限制用户,按照消耗时间排序
db.system.profile.find(
{
ts : {
$gt : new ISODate("2021-04-06T03:00:00Z") ,
$lt : new ISODate("2021-04-06T03:40:00Z")
}
},
{ user : 0 }
).sort( { millis : -1 } )
可逆加密算法
不可逆信息摘要算法(也称Hash算法)主要有:MD5,SHA系列,HMAC系列。
可逆加密算法分 对称式 和 非对称式,
对称式:DES,3DES,AES系列
非对称式:RSA,ECC椭圆曲线加密相关算法
基础可逆加密算法
简单的对称可逆加密算法原理,我们可以把它理解为一道数学题:
假设A有一个数字 88,接下来A想对它加密,拿一个密钥数7来加密,比如把它相加得到密文 88+7=95
A把加密后的95传输给B,B手上有协商好的密钥数7,同时也知道解密算法,那么B只要把密文减去密钥即可得到原来数字 95-7=88
以上传输过程中,只传递了密文95,原数字和密钥数都没被传输,也就实现了加密效果。
现实过程中,我们把这个数字看成计算机中的一个存储单位,比如字节(一个字节8比特),我们只要把这个字节做一遍数学运算,就能实现加密效果。
算法实现
根据以上思路,用PHP来实现对称加解密函数:
/**
* 加密函数
* By: Malu
* @param $data
* @param $key
* @return string
*/
public function encrypt($data, $key)
{
$key = md5($key);
$x = 0;
$len = strlen($data);
$l = strlen($key);
$char = "";
$str = "";
// 循环拼接私钥md5后的字符,组装到待加密字串长度
for ($i = 0; $i < $len; $i++) {
if ($x == $l) {
$x = 0;
}
$char .= $key[$x];
$x++;
}
for ($i = 0; $i < $len; $i++) {
// ord() 函数返回字符串的首个字符的 ASCII 值。
// 给每个字符循环 加上 私钥md5后的 ASCII 与 256 求模后的值(求模是防止长度越界,比如中文字符)
// 最后把 ASCII 值转成字符
$str .= chr((ord($data[$i]) + ord($char[$i])) % 256);
}
return base64_encode($str); // 用基础的64个字符替换
}
/**
* 解密函数
* By: Malu
* @param $data
* @param $key
* @return string
*/
public function decrypt($data, $key)
{
$key = md5($key);
$x = 0;
$data = base64_decode($data);
$len = strlen($data);
$l = strlen($key);
$char = "";
$str = "";
// 循环拼接私钥md5后的字符,组装到加密字串长度一样长
for ($i = 0; $i < $len; $i++) {
if ($x == $l) {
$x = 0;
}
$char .= substr($key, $x, 1);
$x++;
}
for ($i = 0; $i < $len; $i++) {
if (ord(substr($data, $i, 1)) < ord(substr($char, $i, 1))) {
// 如果加密字串ASCII小于密文ASCII,表示长度已越界,需要补256
// 那么把 加密字串ASCII + 256 - 私钥md5后的字符串ASCII
$str .= chr((ord(substr($data, $i, 1)) + 256) - ord(substr($char, $i, 1)));
} else {
$str .= chr(ord(substr($data, $i, 1)) - ord(substr($char, $i, 1)));
}
}
return $str;
}
升级算法
以上代码在PHP下运行正常,但是在异构代码下运行呢?
比如PHP项目与Java项目加密传输,标准ASCII码共定义了128个字符,而以上算法用到了256位;
为了标准化,我把它限制到128个字符,我们来改进一下算法:
1.加密原文前做一次base64,把原文降为64个字符集。
2.把加法运算改成异或运算,这样就不用做越界处理。
下面是PHP实现的升级版可逆加密算法:
//url base64编码
public function urlsafe_b64encode($string)
{
$data = base64_encode($string);
$data = str_replace(array('+', '/', '='), array('-', '_', ''), $data);
return $data;
}
//url base64解码
public function urlsafe_b64decode($string)
{
$data = str_replace(array('-', '_'), array('+', '/'), $string);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}
return base64_decode($data);
}
/**
* 加密函数 V2
* By: Malu
* @param $data
* @param $key
* @return string
*/
public function encrypt_v2($data, $key)
{
$key = md5($key);
$x = 0;
$data = $this->urlsafe_b64encode($data);
$len = strlen($data);
$l = strlen($key); // 32
$char = "";
$str = "";
// 循环拼接私钥md5后的字符,组装到待加密字串长度
for ($i = 0; $i < $len; $i++) {
if ($x == $l) {
$x = 0;
}
$char .= $key[$x];
$x++;
}
for ($i = 0; $i < $len; $i++) {
// ord() 函数返回字符串的首个字符的 ASCII 值。
// 给每个字符循环 私钥md5后的ASCII 与 与原文处理后的字符做异或运算
// 最后把 ASCII 值转成字符
$str .= chr(ord($data[$i]) ^ ord($char[$i]));
}
$str = $this->urlsafe_b64encode($str); // 可以在URL安全传输
return $str;
}
/**
* 解密函数v2
* By: Malu
* @param $data
* @param $key
* @return string
*/
public function decrypt_v2($data, $key)
{
$key = md5($key);
$x = 0;
$data = $this->urlsafe_b64decode($data);
$len = strlen($data);
$l = strlen($key);
$char = "";
$str = "";
// 循环拼接私钥md5后的字符,组装到加密字串长度一样长
for ($i = 0; $i < $len; $i++) {
if ($x == $l) {
$x = 0;
}
$char .= substr($key, $x, 1);
$x++;
}
for ($i = 0; $i < $len; $i++) {
// 把加密字串ASCII 与 私钥md5后的字符串ASCII 做异或运算
// 最后把 ASCII 值还原成字符
$str .= chr(ord(substr($data, $i, 1)) ^ ord(substr($char, $i, 1)));
}
$str = $this->urlsafe_b64decode($str);
return $str;
}
下面是Java实现的升级版可逆加密算法:
// ord() 函数返回字符串的首个字符的 ASCII 值
public static int ord(String s) {
return s.length() > 0 ? (s.getBytes(StandardCharsets.UTF_8)[0] & 0xff) : 0;
}
/**
* 加密函数 V2
* By: Malu
*
* @param data
* @param key
* @return
*/
public String encrypt(String data, String key) {
key = md5(key);
final Base64.Encoder encoder = Base64.getUrlEncoder();
byte[] textByte = new byte[0];
try {
textByte = data.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String encodedText = encoder.encodeToString(textByte);
Integer x = 0;
Integer len = encodedText.length();
Integer l = key.length();
String char_tmp = "";
String str = "";
// 循环拼接私钥md5后的字符,组装到待加密字串长度
for (Integer i = 0; i < len; i++) {
if (x == l) {
x = 0;
}
char_tmp += key.substring(x, x + 1);
x++;
}
for (Integer i = 0; i < len; i++) {
// ord() 函数返回字符串的首个字符的 ASCII 值。
// 给每个字符循环 私钥md5后的ASCII 与 与原文处理后的字符做异或运算
// 最后把 ASCII 值转成字符
str += (char) (ord(encodedText.substring(i, i + 1)) ^ ord(char_tmp.substring(i, i + 1)));
}
try {
textByte = str.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
encodedText = encoder.encodeToString(textByte);
return encodedText.replace("=", ""); // 可以在URL安全传输
}
/**
* 解密函数 V2
* By: Malu
*
* @param data
* @param key
* @return
*/
public String decrypt(String data, String key) {
key = md5(key);
Integer x = 0;
String data_tmp = "";
try {
byte[] decodedBytes = Base64.getUrlDecoder().decode(data);
data_tmp = new String(decodedBytes, "utf-8");
} catch (Exception $e) {
}
Integer len = data_tmp.length();
Integer l = key.length();
String char_tmp = "";
String str_tmp = "";
// 循环拼接私钥md5后的字符,组装到加密字串长度一样长
for (Integer i = 0; i < len; i++) {
if (x == l) {
x = 0;
}
char_tmp += key.substring(x, x + 1);
x++;
}
for (Integer i = 0; i < len; i++) {
// 把加密字串ASCII 与 私钥md5后的字符串ASCII 做异或运算
// 最后把 ASCII 值还原成字符
str_tmp += (chr(ord(data_tmp.substring(i, i + 1)) ^ ord(char_tmp.substring(i, i + 1))));
}
try {
byte[] decodedBytes = Base64.getUrlDecoder().decode(str_tmp);
data_tmp = new String(decodedBytes, "utf-8");
} catch (Exception $e) {
}
return data_tmp;
}
算法加强
以上实现了基础的加密解密过程,我们可以自由改进其算法,比如把md5替换成其他哈希算法,也可以加盐,双重md5,甚至可以通过另外接口做成协商密钥,让密文随机变化,来加强加密强度。
PHP加密传输库
加解密库 php-encrypted-transmission 添加到 composer.json 配置文件
$ composer require malu/php-encrypted-transmission
升级 composer
$ composer update
使用示例
// If you installed via composer, just use this code to require autoloader on the top of your projects.
require 'vendor/autoload.php';
// Using Medoo namespace
use Malu\Encrypted\Encrypted;
$data = ["hello","malu","bbq"];
// 加密输出
$encrypt_data = Encrypted::encrypt(json_encode($data), "34f7e6dd6acf03192d82f0337c8c54ba");
echo $encrypt_data;
// 解密输出
echo Encrypted::decrypt($encrypt_data, "34f7e6dd6acf03192d82f0337c8c54ba");
JWT攻击
JWT是JSON Web Token的缩写,可用于身份认证,会话状态维持以及信息交换等任务。
JWT由三部分构成,分别称为 Header ,Payload 和 Signature ,各部分用“.”相连构成一个完整的Token,形如xxxxx.yyyyy.zzzzz。
遵循 RFC 7519 规范
Header
使用一个JSON格式字符串声明令牌的类型和签名用的算法等,形如:
{
"alg": "HS256",
"typ": "JWT"
}
该字符串经过Base64Url编码后形成JWT的第一部分xxxxx。
Header声明一些标准:
Token | Description | Format |
---|---|---|
typ | 令牌类型 (JWT/JWE/JWS等) | string |
alg | 用于签名或加密的算法 | string |
kid | Key ID - 用作查找 | string |
x5u | x509证书的URL | URL |
x5c | 用于签名的x509证书(作为嵌套的JSON对象) | JSON object |
jku | JWKS格式键的URL | URL |
jwk | 用于签名的JWK格式密钥(作为嵌套的JSON对象) | JSON object |
Payload
Payload 部分也是一个 JSON 对象,同样的,该字符串经过Base64Url编码形成JWT的第二部分yyyyy。
Payload7个官方字段:
Payload Key | Description | Format |
---|---|---|
iss | 签发人 (issuer) | string |
sub | 主题 (subject) | string |
aud | 受众 (audience) | string |
exp | 过期时间 (expiration time) | Date |
nbf | 生效时间 (Not Before) | Date |
iat | 签发时间 (Issued At) | Date |
jti | 编号 (JWT ID) | string |
当然也可以使用私有字段。
Signature
Signature 部分是对前两部分的签名,防止数据篡改。
将xxxxx.yyyyy使用alg指定的算法加密,然后需要指定一个私有密钥(secret),再使用 Header 里面指定的签名算法(默认是 HMAC SHA256)得到JWT的第三部分zzzzz。
签名算法:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
JWT 的几个特点
1.JWT 不仅用于认证,也携带了Payload信息。对于服务端来说这个Payload可以直接拿来使用,可降低查询数据库的次数。同时也是一种便捷的Auth0解决方案。
2.JWT 由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务端专门部署额外的逻辑。
3.JWT 最大的缺陷是认证私钥,存在于签名里,存在暴力破解可能性,私钥一旦泄露,任何人都可以获得该令牌的所有权限。(该问题可以通过RSA非对称密钥来解决)
JWT攻击
JWT存在如下几个问题
1.敏感信息泄露
由于Header和Payload部分是使用可逆base64方法编码的,因此任何能够看到令牌的人都可以读取数据。
2.算法修改攻击
JWT支持将算法设定为 None 。如果 alg 字段设为 None ,那么签名会被置空,这样任何 token 都是有效的。
如果签名算法为 RS256,那么会选择用私钥进行签名,用公钥进行解密验证。如果服务端不严谨,我们拿到了泄露的公钥 pubkey。此时我们可以尝试将 header 的 alg 算法从 RS256 改为 HS256 ,此时即非对称密码变为对称加密,如果后端的验证也是根据 header 的 alg 选择算法,那么显然正中下怀。
3.密钥可控(SQL注入)
假如header头:
{
"alg":"SH256",
"typ":"JWT",
"kid":"111"
}
其中kid为密钥key的编号id,类似逻辑:
select * from table where kid=$kid
如果在这里对 $kid 进行恶意篡改,例如:
kid = 0 union select 555
这样查询出来的结果为555,这样等同于我们控制了密钥key,拥有了密钥key,即可伪造认证。
4.暴力破解
我们知道 Signature 算法里有私钥,如果这个私钥的复杂度不够,那么显然可以通过暴力破解来攻击。
比如现成的JWT暴力破解工具:https://github.com/brendan-rius/c-jwt-cracker
生成RSA公私钥
命令行:
openssl genrsa -out rsa_private_key.pem 1024
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
在线生成:
https://www.bejson.com/enc/rsa/
注意事项
1.secret base64 encoded 对应处理办法
如果在 https://jwt.io/ 网站上勾选 secret base64 encoded,那么意味着secret密钥是经过 base64 encode 的,所以需要先 base64_decode 原来的 secret 密钥,再传入。
比如PHP里:
JWT2::encode($payload, base64_decode($secret));