MySQL MGR是什么?

术语

  • MGR:MySQL Group Replication,即MySQL组复制
  • RPO:Recovery Point Objective恢复点目标
  • RTO:Recovery Time Objective恢复时间目标

背景

公司核心应用A为公司核心应用,对数据库RPO、RTO都有较高要求。一次数据库主库磁盘expender背板坏了,影响磁盘IO通道,数据库层面出现大面积超时和错误。由于机器未完全坏掉,数据库可以连接,SQL可以执行,导致主从未能自动切换,手动切换时准备脚本,校验数据同步等问题耗时过长,导致公司业务出现重大损失。(原有方案为MHA高可用,半同步复制)
由此改为MGR集群技术方案,以期降低主从切换时效,降低损失。

MGR

MGR高可用方案中的RPO、RTO
RPO:架构模型保障了数据一致性,无需人为干预和检测
RTO:因其本身RPO的自动保障,无数据差异,准备耗时短,

MGR具备以下几个特点:

基于shared-nothing模式,所有节点都有一份完整数据,发生故障时可以直接切换。
MGR提供了数据一致性保障,默认是最终一致性,可根据业务特征需要自行调整一致性级别。
支持在线添加、删除节点,节点管理更方便。
支持故障自动检测及自动切换,发生故障时能自动切换到新的主节点,再配合MySQL Router中间件,应用层无需干预或调整。
支持单节点、多节点写入两种模式,可根据架构或业务需要选择哪种方案,不过强烈建议选用单主模式。

参考

https://dev.mysql.com/doc/refman/8.4/en/group-replication.html

https://greatsql.cn/blog-10-9.html

如何预估MySQL表空间占用大小

我们经常需要知道表空间大小,在修改表结构的时候需要知道表空间大小以预估影响时间;在设计表的时候需要预估表的数据量,预估磁盘空间等。

查看已存在的表空间大小

MySQL中information_schema有记录

1
2
3
SELECT concat(round(sum((data_length+index_length)/1024/1024),2),'MB' as data
FROM information_schema.tables
WHERE table_schema='mydb' and table_name='mytable';

新建表空间大小测算

从上面可以看到表空间大小由数据大小+索引大小两部分组成
下面通过一个例子来实际测算一下
DDL

1
2
3
4
5
6
7
8
9
10
mysql> desc City;
+-------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+----------------+
| ID | int(11) | NO | PRI | NULL | auto_increment |
| Name | char(35) | NO | | | |
| CountryCode | char(3) | NO | MUL | | |
| District | char(20) | NO | | | |
| Population | int(11) | NO | | 0 | |
+-------------+----------+------+-----+---------+----------------+

  • 数据大小测算
    根据表结构中字段大小来测算
    66 bytes per row of data(4+35+3+20+4)

  • 索引大小测算
    4 bytes per row for the primary key
    7 bytes per row for country code index

    • 3 bytes for the country
    • 4 bytes for Clustered Key attached to the country code

这不考虑BTREE或表空间碎片的内务管理
对于一百万行,这将是77000000字节(73.43 MB)

参考
How to estimate/predict data size and index size of a table in MySQL

更详细的还需要看InnoDB : Tablespace Space Management

自定义ShardingSphere的JSON加解密器

基于数据安全的目的,需要对敏感数据进行加密存储。其中有整个字段是敏感内容的数据,也有部分灵活内容存储为JSON,JSON中的部分path为敏感内容。
针对这部分内容,需要对JSON指定path加解密,以避免对整个JSON加解密造成存储空间、应用缓存资源浪费。

ShardingSphere整体架构

加密规则

加密配置主要分为四部分:数据源配置,加密算法配置,加密表配置以及查询属性配置,其详情如下图所示:

JSON加解密器实现在加密配置->用户自定义处

配置

加密器类型配置
1
2
3
4
5
6
7
spring:
shardingsphere:
rules:
encrypt:
encryptors:
json_encryptor:
type: json
加密字段配置
1
2
3
4
5
6
7
8
9
10
11
spring:
shardingsphere:
rules:
encrypt:
tables:
t_user:
columns:
info:
plainColumn: info
cipherColumn: info_cipher
encryptorName: json_encryptor
JSON path加密配置
1
2
3
4
5
6
7
8
9
spring:
shardingsphere:
rules:
encrypt:
encryptors:
json_encryptor:
props:
column0_path: bankCard <!-- {唯一名字}_path=需要加密的json路径 -->
column0_path_encryptor: bankCard <!-- {唯一名字}_path_encryptor=加密类型 -->

自定义加密

接下来就是自定义ShardingSphere加密部分
可以查看ShardingSphere官网,EncryptAlgorithm
继承EncryptAlgorithm

  • 重写getType为json(加密器类型配置处使用)
  • 重写setProps(JSON path加密配置会从此处拿到)
  • 重写encrypt、decrypt,在遇到加密字段配置中的SQL时,触发改写SQL,可以根据props中配置的json path自定义加密。

实现SPI
在代码的resources路径下创建META-INF\services\org.apache.shardingsphere.spi.encrypt.ShardingEncryptor
文件内容为自定义加密类的全路径,如:com.company.shardingsphere.encrypt.JSONEncryptor

MySQL中的float、double的精度是如何丢失的?

前言

朋友在设计表的时候很疑惑小数的时候到底该用Float、Double还是Decimal,什么情况下使用?
我们总听说Float、Double会丢失精度,如果是金钱则使用Decimal。但是在业务场景里面,我们期望的是程序是可靠的,所有数据都是准确的。那是不是意味着所有的字段都要用Decimal,那Float、Double还有什么用?
所以我们需要理解到精度到底是怎么丢失的,什么情况下丢失,什么情况下不丢失?才能得出Float、Double在怎样情况下是可靠的,才能在需要使用的时候判断出该使用什么数据类型。

Float为什么会丢失?

Float、Double存储的是近似值。为什么是近似值,先看看各数据类型空间占用情况

类型名称 说明 存储需求
Float 单精度浮点数 4字节
Double 双精度浮点数 8字节
Decimal 压缩的“严格”定点数 Decimal(M,D),如果M>D,为M+2否则为D+2字节

存储Float、Double时采用将数据转换为二进制进行存储。
存储格式为

比如8.25用二进制表示可表示为1000.01,转成指数的形式1.00001*2^3,在计算机中

这其中小数的二进制计算方式与整数不同,需要使用小数部分2取整数,直到为0
例如0.32的二进制计算方式如下
0.32
2 = 0.64 0
0.642 = 1.28 1
0.28
2 = 0.56 0
0.562 = 1.12 1
0.12
2 = 0.24 0
0.242 = 0.48 0
0.48
2 = 0.96 0
0.962 = 1.92 1
0.92
2 = 1.84 1
0.842 = 1.68 1
0.68
2 = 1.36 1
0.36*2 = 0.72 0

对于这样整除不尽或者超过32位的情况,就一定会丢失精度,或者四舍五入后得到的近似值
针对float情况,至少我们可以得出结论:
1.如果一个float型数据转成二进制后的第32位之后都是0,那么数据是准的
2.如果一个float型数据转成二进制后的第32位之后不全为0,则数据就会存在误差

重新说明float(M, D)两个参数的意义

这两个参数表示一共能存M位,其中小数点后占D位。比如float(3,1)表示一共3位,其中小数点后1位数字。这里会有两个误区

数据的精度总是能精确到D位,也就是数据的不精确一定出现在小数点后
数据存储的时候只能存储到D位小数

  • 第一个误区,如果对于float4字节的存储空间连整数的存储不下的时候,连整数都有误差的,更何况小数,所以存储空间大小决定存储精度,和D值无关。来看这样一个例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    mysql> create table f2 (f1 float(15,2));
    Query OK, 0 rows affected (0.01 sec)
    mysql> insert into f2 values (123456789.39);
    Query OK, 1 row affected (0.00 sec)
    mysql> select * from f2;
    +--------------+
    | f1 |
    +--------------+
    | 123456792.00 |
    +--------------+
    1 row in set (0.00 sec)

    最后你会发现,连整数都不准了,小数被完全抹去了。

  • 第二个误区,对于存储而言,是和D无关的一个参数。因为浮点型数据最终都要被转成二进制进行存储。并且对于float,这个二进制只能有32位0和1的组合。看下面的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    mysql> select * from f;
    +-----------+
    | f1 |
    +-----------+
    | 131072.31 |
    +-----------+
    1 row in set (0.00 sec)
    mysql> alter table f modify f1 float(10,4);
    Query OK, 0 rows affected (0.02 sec)
    Records: 0 Duplicates: 0 Warnings: 0
    mysql> select * from f;
    +-------------+
    | f1 |
    +-------------+
    | 131072.3125 |
    +-------------+
    1 row in set (0.00 sec)

    可以看到,修改一下显示宽度D,这个时候可以看到MySQL真正存储的数字是131072.3125

怎么样才能存储一个准确的数据

如果采用float或者double类型的话,数据有时候完全准确的,有时候是不准确的,怎么才能存储一个准确的数字,完全看你需要存什么样的数据,假如存储一个8.25这样的数字,那永远都是准确的。但是如果存储0.9这样的数字,则永远存不准确。

所以如果一个实数在MySQL中存储准确的话,会出现以下三种情况

  • 数据真的准确,数据能在有限的存储空间里完全存储起来
  • 数据存储被截断,但是通过四舍五入依然能够将数据显示准确
  • 数据存储被截断,通过四舍五入不能将数字正确显示

关于decimal类型

通过前面的分析,了解了float和double类型的区别和误差来源。但是decimal类型是MySQL官方唯一指定能精确存储的类型,也是DBA强烈推荐和金钱相关的类型都要存储为decimal类型,如果猜想decimal类型的存储格式的话,那么一下两种可以保持数据的准确性

  • 继续扩大存储空间,比double更大一个级别,比如128位甚至更多
  • 通过字符串化或者其他的方式特殊存储起来

这两种方式都能实现decimal精确存储,但是由于MySQL指定decimal类型最大长度为65.在我们能测试的范围内,decimal并没有出现误差。

如何选择float,double,decimal

结论总是放在最后,根据上面的分析:可以得出以下结论

  1. 如果你要表示的浮点型数据转成二进制之后能被32位float存储,或者可以容忍截断,则使用float,这个范围大概为要精确保存6位数字左右的浮点型数据 比如10分制的店铺积分可以用float存储,小商品零售价格(1000块之内)

  2. 如果你要表示的浮点型数据转成二进制之后能被64位double存储,或者可以容忍截断,这个范围大致要精确到保存13位数字左右的浮点型数据 比如汽车价格,几千万的工程造价

  3. 相比double,已经满足我们大部分浮点型数据的存储精度要求,如果还要精益求精,则使用decimal定点型存储 比如一些科学数据,精度要求很高的金钱

写在最后

理论上的东西永远比不上实践,应用场景大于一切理论。选择float或者double或者decimal有时候也要看场景,比如我们可以用double存储一个小商铺的季度营业额(几千万),单独用double存储的时候没有问题,当多个季度,多个年份算总3年内的营业额是,就会出现问题,再也算不出一个准确的答案。所以,如果考虑情况没那么有把握的情况下,推荐使用decimal,最后,也可以通过其他手段避开这些问题,比如存储商品价格可以使用 乘以100的形式存储,展示价格的时候再除以100

B.3.4.8 Problems with Floating-Point Values
谈谈MySQL如何选择float, double, decimal

手机资费套餐

想找一个全网最低的资费套餐,网上也有0月租的,但是没有验证。
工信部查询出44个运营商官网拿到资费信息筛选出6元及以下的套餐如下

运营商 月租/资费 套餐名称 套餐详情 套餐URL
阿里通信 6 亲心6元套餐 60分钟国内语音
国内接听免费
国内语音0.15元/分钟
国内流量2元/日/随心用
当日有效
国内短信0.1元/条
赠送来电显示
https://aliqin.aliyuncs.com/#/prod
日日顺通信 5 顺意套餐 ①、国内语音拨打资费:0.15元/分钟;
②、国内流量:0.2元/M;
③、国内点到点短信:0.1元/条;
④、来电显示5元/月;
https://rrstel.com/businessHall/localpage/zifeizone.jsp
丰信移动 6 丰信6元A卡 1.月租6元/月,赠送来电显示,赠送60分钟国内语音
2.国内语音:0.15元/分钟
3.国内流量:1元包500M/日
4.国内短/彩信:0.1元/条。
http://www.phtion.com/account/index
蓝猫移动 3.9 蓝猫标准流量卡 https://www.lanmaomobile.com/?list_8/232.html
朗玛移动 6 小象阳光卡6元 语音:0分钟
流量:0GB
流量:0.1元/1M
短信:0.1元/条
语音:0.15元/分钟
https://www.langma.cn/langma-jx
天音移动 6 天音卡-联通版 打电话0.15元/分钟
上网流量0.2元/M
短信0.1元/条
彩信0.3元/条
来电显示月租5元
https://rrstel.com/businessHall/localpage/zifeizone.jsp
普泰移动 6 普泰惠享卡 https://rrstel.com/businessHall/localpage/zifeizone.jsp
苏宁互联 5 至简套餐 https://rrstel.com/businessHall/localpage/zifeizone.jsp
电信 5 无忧卡 适用范围:全部公众用户
有效期限:2025年11月30日
销售渠道:线下及线上渠道
可售范围:全国可售,但受各省销售安排所限
合约要求:不限制
https://www.189.cn/cq/zfzq/#tList_4

SPI机制

SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦
SPI整体机制图如下:
SPI

服务提供方提供了接口实现后,需要在classpath下的META-INF/services/目录下创建以服务接口命名的文件,文件内容为接口的实现类名。
其他程序使用服务时,会通过查找这个jar的META-INF/services/中文件,获取实现类名,进行加载实例化,该服务就可以使用了。JDK中查找服务实现的类为java.util.ServiceLoader。

应用-JDBC

JDBC接口定义

在java中定义了接口java.sql.Driver,并没有实现,具体实现由不通厂商实现。

MySQL实现

MySQL的jar包(mysql-connector-java-8.0.30.jar)中,META-INF/services目录下有文件名java.sql.Driver的内容为com.mysql.cj.jdbc.Driver
SPI META-INF/services

使用方法
1
Connection conn = DriverManager.getConnection(url,username,password);
SPI如何实现

在使用的时候并没有指定使用哪个Driver来连接,那如何使用上MySQL的驱动的呢?这就是我们SPI在起作用。

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
package java.sql;
public class DriverManager {

static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}

AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
}
return null;
}
});

println("DriverManager.initialize: jdbc.drivers = " + drivers);

if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
}

其中
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
负责查找classpath下及jar包中META-INF/services目录下java.sql.Driver文件中的内容获取具体实现。

SPI机制的缺陷

通过上面的解析,可以发现,我们使用SPI机制的缺陷:

  • 不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
  • 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用 ServiceLoader 类的实例是不安全的.

如何生成requirements.txt

写了一个简单的python项目,希望生成一个requirements.txt文件方便在Github或其他地方运行安装依赖。
作为新手我首先是一个一个从import里面去找到写到文件中的,结果发现居然会报错。
后面找到一个工具pipreqs可以自动识别出项目中用到的所有依赖生成requirements.txt文件

How

Installation

1
pip install pipreqs

Usage

1
2
$ pipreqs /home/project/location
Successfully saved requirements file in /home/project/location/requirements.txt

生成requirements.txt内容如

1
2
3
wheel==0.23.0
Yarg==0.1.9
docopt==0.6.2

更详细的参数用法参照pipreqs Usage

Java类文件在JVM运行的生命周期

java Class文件结构

Java .class 文件是 Java 编程语言的关键组件,遵循精确且定义的结构。 这种结构不仅对于 Java 虚拟机 (JVM) 正确加载和执行字节码至关重要,而且还提供了有关编译后的 Java 代码的大量信息。 下面,我们深入研究类文件结构的基本元素,详细说明每个组件及其在整体架构中的重要性。

class文件格式

类文件由单个 ClassFile 结构组成。 该结构由 JVM 规范定义并遵循特定格式,其中包括以下主要部分:

  • Magic Number魔数:固定值 (0xCAFEBABE)。 此唯一标识符验证该文件是否是 JVM 可读的有效类文件。
  • Version Information版本号:major_version、minor_version,java版本号
  • Constant Pool常量池
  • Access Flags访问标识
  • This Class, Super Class,and Interfaces类索引、父类索引与接口索引集合
  • Fields字段集合
  • Methods方法表集合
  • Attributes属性表集合

以上信息如何查看?

1
javap [options] classes...

JVM需要使用上述信息来正确加载、验证和执行

其是常量池,它是一个集中的字典,经常被类文件中的其他部分引用,突出了它在整个架构中的重要性。

Class文件在JVM中的生命周期

The Class file Lifecycle of a Java Application

1. Loading加载

类加载过程执行以下三个功能:
从clas文件创建二进制数据流
根据内部数据结构解析二进制数据
创建 java.lang.Class 的实例
完成此操作后,类实例就可以进行链接了。

2. Linking链接
2.1 Verification验证

此步骤可确保安全性和完整性。JVM验证class文件的正确性,文件格式验证、语法是否有效、是否符合Java语言规范。

2.2 Preparation准备

在准备过程中,JVM 会为类静态变量分配内存,并将其初始化为默认值。

2.3 Resolution解析

解析阶段包括将类文件中的符号引用解析为直接引用。这就是 JVM 常量池发挥关键作用的地方。主要针对类或接口、字段、类方法、方法类型等。

3. Initializes初始化

执行静态块: 这一阶段涉及执行静态初始化程序和静态块。JVM 会初始化静态字段,并按照它们在类文件中出现的顺序执行任何静态初始化块。
设置最终值: 为类的最终变量分配值,这些值在类的生命周期内不可更改。

4. Usage使用

实例化: JVM 根据应用程序的需要创建类的实例。
执行: 根据运行程序的要求调用和执行方法,访问字段。JVM 会将字节码解释或即时编译为机器代码以便执行。

5. Unloading卸载

垃圾回收: 当一个类不再需要,也没有对其实例的实时引用时,它就可以被卸载。JVM 的垃圾回收器会回收分配给类的内存。

The Anatomy of a Java Virtual Machine Class File
The Execution Lifecycle of a Java Application
javap
Chapter 4. The class File Format

Makefile如何使用

工程化编译项目时,Java用Maven\Gradle,前端用npm,C/C++用Make
使用Make命令编译C/C++时,是通过Make工具实现。推荐使用w64devkit
w64devkit支持Linux命令

  • 根据更改的源文件,自动确定需要更新哪些文件。它还自动确定更新文件的正确顺序,以防一个非源文件依赖于另一个非来源文件。
    因此,如果您更改一些源文件,然后运行Make,则不需要重新编译所有程序。它只更新那些直接或间接依赖于您更改的源文件的非源文件。
    如何确定依赖的呢,来源与我们在Makefile中指定的dependencies
  • Make不限于任何特定的语言。所有能在命令行运行的编程语言都能处理(Java\Golang\Python…)。另外基于文件的改变然后更新另外的文件也可以。

Makefile中的一条规则告诉Make如何执行一系列命令,以便从源文件构建目标文件。它还指定了目标文件的依赖项列表。此列表应包括用作规则中命令输入的所有文件(无论是源文件还是其他目标文件)。

1
2
3
target:   dependencies ...
commands
...
1
2
3
4
hello: hello.o
g++ -o hello hello.o
hello.o: hello.cpp
g++ -c hello.cpp

更新机制

运行Make时,可以指定要更新的特定目标;否则,Make会更新makefile中列出的第一个目标。当然,必须首先更新生成这些目标所需的任何其他目标文件作为输入。
Make使用makefile来确定哪些目标文件应该更新,然后确定哪些文件实际上需要更新。如果目标文件比其所有依赖项都新,那么它已经是最新的,不需要重新生成。其他目标文件确实需要更新,但顺序正确:每个目标文件都必须重新生成,然后才能用于重新生成其他目标。

Makefile文件命名

Make自动查找makefile文件,顺序为GNUmakefile>makefile>Makefile
GUNmakefile:不建议使用,只能支持GUN make
makefile:所有版本都能识别
Makefile:推荐,最常用

运行make时没有找到上述文件会报错,但可以手动指定文件名

1
2
make -f <filename>
make --file=<filename>

香港卡全攻略

为什么要办香港卡?

保险?参与港美股?参与加密货币?OpenAI付费?公司股票不转回?对于我来说,听着牛逼想要但又不是必须的理由。最后决定办理是因为觉得港股券商入金有奖励,家人可以顺便去玩一圈。

选择哪个银行?

有那么多银行,我们可以办哪些,哪些又比较好呢?
从发币行角度考虑

  • 香港上海汇丰银行
  • 渣打银行(香港)
  • 中国银行(香港)
    三家银行都是在全球开展业务。从网上的信息来看汇丰和中国银行很好办,渣打不太好办。
    另外虚拟银行众安银行、天星银行、蚂蚁银行等,没有考虑和办理就不列了。
香港上海汇丰银行 中国银行
总部 伦敦 中国
办理难度 容易 容易
转汇费用 据说同名内地香港互转不要手续费 同名内地香港互转不要手续费

听说汇丰和中国银行从内地卡转入香港卡不要手续费。我办了汇丰之后就飘了不想再办就出去玩了。回来内地后去汇丰银行办理内地卡,没想到她直接告诉我只接待Premier客户,有存款要求不然有管理费。

怎么办理?

两种方式

  • 直接到营业网点线下排队办理

    如果人多去晚了可能无法办理,或者预约的人数多也可能被拒绝

  • 网上预约,再到网点办理

    网上预约后无需再早去现场排队拿号,直接告诉接待人员,然后就给你安排了

Read More