开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

「Git 内核」实现 HEAD

发表于 2021-11-19

「这是我参与11月更文挑战的第 12 天,活动详情查看:2021最后一次更文挑战」


实现 HEAD 查询

现在我们知道了 .git/HEAD 和 .git/refs/heads 下的文件如何协同工作。开始着手实现它,首先我们定义一些相关类型:

1
2
3
4
5
6
7
8
9
10
11
12
rust复制代码const HASH_BYTES: usize = 20;

// A (commit) hash is a 20-byte identifier.
// We will see that git also gives hashes to other things.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Hash([u8; HASH_BYTES]);

// The head is either at a specific commit or a named branch
enum Head {
Commit(Hash),
Branch(String),
}

接下来,我们将希望能够在40个字符的十六进制表示和紧凑的20字节表示之间来回转换哈希值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
rust复制代码use std::fmt::{self, Display, Formatter};
use std::io::Error;
use std::str::FromStr;

impl FromStr for Hash {
type Err = Error;

fn from_str(hex_hash: &str) -> io::Result<Self> {
// Parse a hexadecimal string like "af64eba00e3cfccc058403c4a110bb49b938af2f"
// into [0xaf, 0x64, ..., 0x2f]. Returns an error if the string is invalid.
// ...
}
}

impl Display for Hash {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
// Turn the hash back into a hexadecimal string
for byte in self.0 {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}

现在我们可以编写核心逻辑:读取.git/HEAD文件并确定其对应的提交哈希值。

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
rust复制代码fn get_head() -> io::Result<Head> {
use Head::*;

let hash_contents = fs::read_to_string(HEAD_FILE)?;
// Remove trailing newline
let hash_contents = hash_contents.trim_end();
// If .git/HEAD starts with `ref: refs/heads/`, it's a branch name.
// Otherwise, it should be a commit hash.
Ok(match hash_contents.strip_prefix(REF_PREFIX) {
Some(branch) => Branch(branch.to_string()),
_ => {
let hash = Hash::from_str(hash_contents)?;
Commit(hash)
}
})
}

impl Head {
fn get_hash(&self) -> io::Result<Hash> {
use Head::*;

match self {
Commit(hash) => Ok(*hash),
Branch(branch) => {
// Copied from get_branch_head()
let ref_file = Path::new(BRANCH_REFS_DIRECTORY).join(branch);
let hash_contents = fs::read_to_string(ref_file)?;
Hash::from_str(hash_contents.trim_end())
}
}
}
}

fn main() -> io::Result<()> {
let head = get_head()?;
let head_hash = head.get_hash()?;
println!("Head hash: {}", head_hash);
Ok(())
}

现在,无论我们查看主分支还是直接查看提交哈希值,都会打印出来。

1
rust复制代码Head hash: af64eba00e3cfccc058403c4a110bb49b938af2f

我们已经成功确定了当前提交的哈希值。现在,我们该如何找出该提交所存储的信息呢?

提交中有什么?

当你在GitHub这样的网页界面或通过 git show 这样的命令查看一个提交时,你会看到该提交带来的变化(“diff”)。

所以你可能会认为,git 会把每个提交作为一个 diff 来存储。也有可能像备份一样存储每个提交,里面包含该提交的每个文件的内容。

这两种方法其实都可以:你可以从两个文件的副本中计算出一个差异,你也可以通过按顺序应用每个差异(从空的版本库或从最近的提交开始)来计算文件的内容。使用哪种方法取决于你要优化的内容。

基于diff的方法会占用较少的存储空间;它最大限度地减少了重复的信息量,因为它只存储变化的内容。然而,存储内容使得在某个特定的提交中检查代码的速度更快,因为我们不需要应用潜在的成千上万的差异。(这也使得 git clone --depth 1 的实现变得容易,它通过只下载最近的提交来加快克隆的速度)。

而且,如果改动不大,从两个提交的内容中计算差异也不会太费时:差异算法相当快,而且git可以自动跳过没有改动的目录/文件,这个我们后面会看到。

由于这些原因,git采用了 “存储每个文件的内容” 的方法。 git的实现设法只存储一个相同文件的副本,这比天真的解决方案节省了大量的存储空间。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python matplotlib 绘制图形 前言 1 m

发表于 2021-11-19

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

前言

我们前面对matplotlib模块底层结构学习,对其pyplot类(脚本层)类提供的绘制折线图、柱状图、饼图、直方图等统计图表的相关方法,列举往期文章如下。

  • matplotlib 底层结构:matplotlib模块分为脚本层、美工层和后端,协同工作流程的学习
  • matplotlib 绘制折线图:对pyplot.plot()绘制折线图相关属性进行汇总说明
  • matplotlib 绘制柱状图:对pyplot.bar()绘制绍柱状图相关属性进行汇总说明
  • matplotlib 绘制直方图:pyolot.hist()绘制直方图相关属性进行汇总说明

matplotlib 模块不仅提供了绘制统计图表的功能,还支持绘制圆形、正方形、矩形等各种图形

image.png

本期,我们来学习使用matplotlib模块绘制常见的图形吧,Let’s go~

  1. matplotlib.patches概述

matplotlib.patches 专门用来绘制图形的类,在该类是以Artist为基类

  • pathes 是专门绘制2D图形的类
  • patch 绘制的图形默认是以rc params设置
  • patch 模块提供多达10种图形方法满足日常需求

image.png

  1. 绘制图形方法

对应matplotlib模块来说,patches类提供绘制圆形、椭圆形、矩形等图形方法

方法 作用
patches.Rectangle(xy,width,height,angle=0) 绘制矩形
patches.Polygon(xy) 绘制多边形
patches.Arc(xy,width,height,angle=0) 绘制椭圆形
patches.Circle(xy,radius) 绘制圆形
patches.Ellipse(xy,width,height,angle=0.0) 绘制椭圆形
patches.Arrow(x,y,dx,dy) 绘制剪头
patches.wedge(center,r,theta1,theta2,width) 绘制锲形
patches.PathPatch() 绘制多曲线图形
patches.FancyBboxPatch() 绘制花式图形框
patches.Line2D() 绘制线条
  1. 绘制图形步骤

在matplotlib模块中,图表都是由figure、Axes和Axis三个基本元素组成,因此在绘制图形时,一般步骤主要有以下组成。

  • 导入matplotlib pyplot和patches类
1
2
python复制代码import matplotlib.pyplot as plt
import matplotlib.patches as mpatch
  • 使用subplots()创建子图Axes对象
1
python复制代码fig,ax =plt.subplots()
  • 调用pathes类绘制图形的方法如绘制矩形Rectangle()
1
python复制代码Rect = mpatch.Rectangle((0.2,0.75),0.4,0.4,color="r")
  • 子图Axes对象调用set_xlim()和set_ylim坐标轴范围
    patches默认情况下,x轴的坐标范围为(0,1),y轴的坐标范围为(0,1)
1
2
python复制代码ax.set_xlim(-2,5)
ax.set_ylim(-2,5)
  • 子图Axes对象调用add_patch()方法添加图形
1
python复制代码ax.add_patch(Rect)
  • 调用pyplot.show()展示图形
  1. 绘制图形属性

  • 设置透明度

+ 关键字:alpha
+ 取值类型为:浮点型
  • 设置颜色

+ 设置图形关键字:color
+ 设置边框关键字:edgecolor
+ 取值可选:
    - 表示颜色的英文单词:如红色"red"
    - 表示颜色单词的简称如:红色"r",黄色"y"
    - RGB格式:十六进制格式如"#88c999";(r,g,b)元组形式
  1. 小试牛刀

学习完上面几节的知识后,我们在图表中绘画圆形、矩形和直线吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
python复制代码def drawpicture():

fig,ax =plt.subplots()

Rect = mpatch.Rectangle((1,0.75),0.4,0.4,color="yellow",alpha=0.5)

Cri = mpatch.Circle((0,0),1,angle=30,color="pink",alpha=0.2,capstyle="round")

Py = mpatch.Arrow(1,2,2,2)

ax.set_xlim(-1,5)
ax.set_ylim(-1,5)

ax.add_patch(Rect)
ax.add_patch(Cri)
ax.add_patch(Py)
plt.show()

drawpicture()

image.png

总结

本期,我们对matplotlib绘制图形相关方法和步骤进行学习,在实际操作中,还需要大量进行练习才能更加熟练地使用

以上是本期内容,欢迎大佬们点赞评论,下期见~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot基础之声明式事务和切面事务和编程式事务

发表于 2021-11-19

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

前言

事务是一个需要同时保证原子性、隔离性、一致性和持久性的一个或多个数据库操作

本文会说明,springBoot中两种事务的实现方式,编程式事务配置和声明式事务配置还有切面事务,当然在此之前会说一些基础的东西:事务的四大特征,事务的隔离级别,事务的传播行为

事务的四大特征(ACID)

  1. 原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部执行,要么均不执行.
  2. 一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序串行执行的结果相一致.
  3. 隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的.
  4. 持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失.

开启事务

1
css复制代码@EnableTransactionManagement

SpringBoot声明式事务

声明式事务@Transactional可以使用在类上,也可以使用在public方法上.
如果是使用在类上,则是对所有的public方法都开启事务,如果类和方法上都有则方法上的事务生效

可以在类上使用

1
2
3
java复制代码@Transactional(rollbackFor=Exception.class)
public class TransactionServiceImpl implements TransactionService {
}

更多的是在方法上使用

1
2
3
4
less复制代码@Override
@Transactional(rollbackFor=Exception.class)
public void t1(Student one) {
}

@Transactional的参数

在使用@Transactional的时候会用到它的一些参数

参数 作用
value 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器.
transactionManager 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器.
propagation 事务的传播行为,默认值为 Propagation.REQUIRED
isolation 事务的隔离级别,默认值为 Isolation.DEFAULT
timeout 事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚事务.
readOnly 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为 true.
rollbackFor 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型.
rollbackForClassName 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型.
noRollbackFor 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型.
noRollbackForClassName 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型.

value和transactionManager相同
rollbackFor和rollbackForClassName相同
noRollbackFor和noRollbackForClassName相同

事务的隔离级别(isolation)

isolation中会用到隔离级别

参数 事务隔离级别 脏读 不可重复读 幻读
READ_UNCOMMITTED 读未提交(read-uncommitted) 是 是 是
READ_COMMITTED 不可重复读(read-committed) 否 是 是
REPEATABLE_READ 可重复读(repeatable-read) 否 否 是
SERIALIZABLE 串行化(serializable) 否 否 否

事务的传播行为(propagation)

propagation会用到这个传播行文

传播行为 解释
REQUIRED 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务.
SUPPORTS 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行.
MANDATORY 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常.
REQUIRES_NEW 重新创建一个新的事务,如果当前存在事务,暂停当前的事务.
NOT_SUPPORTED 以非事务的方式运行,如果当前存在事务,暂停当前的事务.
NEVER 以非事务的方式运行,如果当前存在事务,则抛出异常.
NESTED 和REQUIRED效果一样.

SpringBoo编程式事务

在需要的地方注入TransactionTemplate

1
2
java复制代码@Autowired 
private TransactionTemplate transactionTemplate;

然后在代码中使用

1
2
3
4
5
6
7
8
typescript复制代码@Override
public final void save2() {
transactionTemplate.execute((status)->{
mapper.saveStudent(newOne());
mapper.saveStudent(newOne());
return Boolean.TRUE;
});
}

这样两个mapper.saveStudent(newOne());就在一个事务中执行了

SpringBoo切面编程式事务

此种方式基于AOP功能,所以需要添加

1
2
3
4
xml复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

写配置类

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
78
79
java复制代码@Aspect
@Configuration
public class MyTransactionConfig {

/**
* 配置方法过期时间,默认-1,永不超时
*/
private final static int TX_METHOD_TIME_OUT = 10;

/**
* 全局事务位置配置 在哪些地方需要进行事务处理
* 配置切入点表达式
*/
private static final String POITCUT_EXPRESSION = "execution(* zdc.enterprise.service.impl.*.*(..))";

@Autowired
private PlatformTransactionManager platformTransactionManager;


@Bean
public TransactionInterceptor txadvice() {

/*只读事物、不做更新删除等*/
/*事务管理规则*/
RuleBasedTransactionAttribute readOnlyRule = new RuleBasedTransactionAttribute();
/*设置当前事务是否为只读事务,true为只读*/
readOnlyRule.setReadOnly(true);
/* transactiondefinition 定义事务的隔离级别;
*如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。*/
readOnlyRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);

/*增删改事务规则*/
RuleBasedTransactionAttribute requireRule = new RuleBasedTransactionAttribute();
/*抛出异常后执行切点回滚 建议自定义异常*/
requireRule.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
/*PROPAGATION_REQUIRED:事务隔离性为1,若当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。 */
requireRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
/*设置事务失效时间,超过10秒*/
requireRule.setTimeout(TX_METHOD_TIME_OUT);

/** 配置事务管理规则
nameMap声明具备需要管理事务的方法名.
这里使用addTransactionalMethod 使用setNameMap
*/
Map<String, TransactionAttribute> nameMap = new HashMap<>();
nameMap.put("add*", requireRule);
nameMap.put("save*", requireRule);
nameMap.put("insert*", requireRule);
nameMap.put("update*", requireRule);
nameMap.put("delete*", requireRule);
nameMap.put("remove*", requireRule);

/*进行批量操作时*/
nameMap.put("batch*", requireRule);
nameMap.put("get*", readOnlyRule);
nameMap.put("query*", readOnlyRule);
nameMap.put("find*", readOnlyRule);
nameMap.put("select*", readOnlyRule);
nameMap.put("count*", readOnlyRule);

NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
source.setNameMap(nameMap);

TransactionInterceptor transactionInterceptor = new TransactionInterceptor(platformTransactionManager, source);

return transactionInterceptor;
}

/**
* 设置切面=切点pointcut+通知TxAdvice
* @return
*/
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(POITCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txadvice());
}
}

有了这个切面配置类,就不要用在类或者每个方法上使用@Transactional了,当然方法名前缀要能和设置的匹配上. RuleBasedTransactionAttribute的参数大致和@Transactional的参数相同,里面有详细的注释,就不过多解释了

END

切面事务粒度最粗,如果方法名不匹配容易漏方法,声明式事务粒度中等,但是遇到大事务就会出现问题,编程式事务粒度最细,可以考虑在特殊的时候使用它.

1
2
3
4
arduino复制代码    作者:ZOUZDC
链接:https://juejin.cn/post/7028963866063306760
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

《MySQL 入门教程》第 25 篇 DML 语句之删除数据

发表于 2021-11-19

「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战」。

上一篇我们介绍了如何使用 UPDATE 语句更新数据,本篇继续学习 MySQL 中的数据删除操作,也就是DELETE语句。

25.1 单表删除

MySQL 使用 DELETE 语句删除表中的数据,基本的语法如下:

1
2
3
4
5
sql复制代码DELETE
FROM table_name
[WHERE conditions]
[ORDER BY ...]
[LIMIT row_count];

其中,DELETE 表示删除数据;table_name 是表名;只有满足 WHERE 条件的数据行才会被删除,如果没有指定条件将会删除表中的全部数据;如果指定了 ORDER BY 子句,按照顺序删除数据行;如果指定了 LIMIT 子句,最多会删除 row_count 行数据。

例如,以下语句删除了 emp_devp 表中姓名为“关平”的员工:

1
2
3
sql复制代码DELETE
FROM emp_devp
WHERE emp_name = '关平';

以下语句使用 ORDER BY 和 LIMIT 子句删除了 emp_devp 表中月薪最高的员工:

1
2
3
4
sql复制代码DELETE
FROM emp_devp
ORDER BY salary DESC
LIMIT 1;

LIMIT 子句可以实现大数据量的多批次删除。

如果删除了外键约束中的父表数据,可能会导致违反外键约束。例如,以下删除语句违反了外键约束:

1
2
3
4
sql复制代码DELETE
FROM department
WHERE dept_id = 1;
ERROR 1452 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`ds`.`employee`, CONSTRAINT `fk_emp_dept` FOREIGN KEY (`dept_id`) REFERENCES `department` (`dept_id`))

由于员工表中存在部门编号为 1 的数据,所以无法删除部门表中的父记录。此时,如果在 DELETE 语句中使用了IGNORE选项,将会忽略执行过程的中错误,当然也不会删除数据。例如:

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码mysql> DELETE IGNORE
-> FROM department
-> WHERE dept_id = 1;
Query OK, 1 row affected, 1 warning (0.09 sec)

mysql> show warnings;
+---------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1451 | Cannot delete or update a parent row: a foreign key constraint fails (`ds`.`employee`, CONSTRAINT `fk_emp_dept` FOREIGN KEY (`dept_id`) REFERENCES `department` (`dept_id`)) |
+---------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

如果想要在删除父表数据的同时删除子表中相关的数据,可以使用外键的级联删除(ON DELETE CASCADE)功能。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sql复制代码CREATE TABLE t_parent(id int NOT NULL PRIMARY KEY, c1 int);
INSERT INTO t_parent VALUES (1, 1), (2, 2);

CREATE TABLE t_child(
id int NOT NULL PRIMARY KEY,
pid int NOT NULL,
FOREIGN KEY (pid)
REFERENCES t_parent (id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO t_child VALUES (1, 1), (2, 1), (3, 2);

SELECT * FROM t_child;
id|pid|
--|---|
1| 1|
2| 1|
3| 2|

其中,t_child 的 pid 字段引用了 t_parent 字段的主键;外键约束中的 ON DELETE CASCADE 表示删除父表记录时级联删除子表中的数据,ON UPDATE CASCADE 表示更新父表记录时级联更新子表中的数据。以下语句演示了级联删除的效果:

1
2
3
4
5
6
7
8
sql复制代码DELETE 
FROM t_parent
WHERE id = 1;

SELECT * FROM t_child;
id|pid|
--|---|
3| 2|

删除 t_parent 中 id 等于 1 的记录之后,t_child 中相应的记录也被删除。

如果 DELETE 语句中没有指定 WHERE 条件,将会删除表中的全部数据。此时,我们可以使用TRUNCATE语句快速删除所有数据:

1
sql复制代码TRUNCATE [TABLE] table_name;

例如,以下语句删除了 emp_devp 中的所有数据:

1
sql复制代码TRUNCATE TABLE emp_devp;

TRUNCATE 语句相当于 DROP TABLE 加上 CREATE TABLE。虽然 TRUNCATE 和 DELETE 的逻辑效果类似,但是它属于 DDL 语句,而且它们之间存在一些区别:

  • TRUNCATE 删除并重建表,通常比 DELETE 快很多,尤其对于大表而言;
  • TRUNCATE 会进行隐式提交,所以无法回滚;
  • TRUNCATE 语句需要获取表上的独占锁;
  • 如果存在外键约束,父表无法使用 TRUNCATE 语句;除非是自引用的外键约束;
  • TRUNCATE 语句通常返回“0 rows affected”,不代表被删除的数据行数;
  • TRUNCATE 语句会将表中的 AUTO_INCREMENT 重置为初始值;
  • TRUNCATE 语句不会触发 ON DELETE 触发器。

25.2 跨表删除

与 UPDATE JOIN 语句实现跨表更新类似,DELETE JOIN语句也可以通过关联其他表中的数据进行删除操作。

1
2
3
4
5
6
7
8
9
10
sql复制代码DELETE table_name
FROM table_name
[INNER JOIN | LEFT JOIN] other_table ON conditions
WHERE conditions;

DELETE
FROM table_name
USING table_name
[INNER JOIN | LEFT JOIN] other_table ON conditions
WHERE conditions;

其中,table_name 是需要删除数据的表名;第一种语法只删除 DELETE 后面表中的数据,第二种语法只删除 FROM 后面表中的数据;INNER JOIN或者LEFT JOIN用于连接其他的表;跨表删除不支持 ORDER BY 和 LIMIT 子句。

例如,以下语句通过关联 employee 中的数据删除 emp_devp 中的数据:

1
2
3
4
5
6
7
8
9
10
sql复制代码DELETE ed
FROM emp_devp ed
LEFT JOIN employee e ON (e.emp_id = ed.emp_id)
WHERE e.emp_id IS NULL;

DELETE
FROM ed
USING emp_devp ed
LEFT JOIN employee e ON (e.emp_id = ed.emp_id)
WHERE e.emp_id IS NULL;

该语句可以用于删除 emp_devp 中不属于 employee 表的数据。以上关联删除语句也可以通过一个子查询实现:

1
2
3
sql复制代码DELETE
FROM emp_devp
WHERE emp_id NOT IN (SELECT emp_id FROM employee);

25.3 多表删除

MySQL 中的DELETE JOIN语句也可以用于同时删除多个表中的数据。

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码DELETE t1, t2
FROM t1
[INNER JOIN | LEFT JOIN] t2 ON conditions
[INNER JOIN | LEFT JOIN] other_table ON conditions
WHERE conditions;

DELETE
FROM t1, t2
USING t1
[INNER JOIN | LEFT JOIN] t2 ON conditions
[INNER JOIN | LEFT JOIN] other_table ON conditions
WHERE conditions;

与上面的跨表更新类似,第一种语法只删除 DELETE 后面表中的数据,第二种语法只删除 FROM 后面表中的数据;连接其他的表 other_table 是可选项,也可以连接多个表;多表删除也不支持 ORDER BY 和 LIMIT 子句。

例如:

1
2
3
4
5
sql复制代码DELETE
FROM p, c
USING t_parent p
INNER JOIN t_child c ON (c.pid = p.id)
WHERE p.c1 = 2;

该语句通过连接操作同时删除了 t_parent 和 t_child 中的数据。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

「Git 内核」提交实质

发表于 2021-11-19

「这是我参与11月更文挑战的第 12 天,活动详情查看:2021最后一次更文挑战」


提交在哪

几乎所有的 git 命令都需要知道当前准备提交是什么。例如,git commit 需要知道当前的提交在哪,因为它将成为马上新提交的父本。

举个例子,在我某一个仓库中,我们正处于 af64eba 提交。git 是怎么知道的?

你可能知道 HEAD 可以用来指代当前的提交。

例如,git show HEAD 显示了当前提交的提交信息和它所引入的修改。所有的 git 状态都存储在 .git 目录中,这包括告诉我们当前位置的文件:.git/HEAD。

写段代码,让我们看一下它的内容。(注意:这些例子使用同步I/O,以便关注git内部。有很多机会可以并行地执行文件系统操作,所以使用异步I/O可以显著提高性能)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rust复制代码use std::fs;
use std::io;

const HEAD_FILE: &str = ".git/HEAD";

fn get_head() -> io::Result<String> {
fs::read_to_string(HEAD_FILE)
}

fn main() -> io::Result<()> {
let head = get_head()?;
println!("Head file: {:?}", head);
Ok(())
}

运行该代码,会得到以下结果:

1
swift复制代码Head file: "ref: refs/heads/main\n"

.git/HEAD 文件结果告诉我们,我们已经切出了主分支。

这是我们想知道的部分内容,但我们仍然没有弄清楚git如何知道main指的是什么提交。

事实证明,refs/heads/main 实际上是.git目录下一个文件的名字。来读读它的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rust复制代码use std::path::Path;

const BRANCH_REFS_DIRECTORY: &str = ".git/refs/heads";

fn get_branch_head(branch: &str) -> io::Result<String> {
let ref_file = Path::new(BRANCH_REFS_DIRECTORY).join(branch);
fs::read_to_string(ref_file)
}

fn main() -> io::Result<()> {
let main_head = get_branch_head("main")?;
println!("main: {:?}", main_head);
Ok(())
}

结果显示:

1
rust复制代码main: "af64eba00e3cfccc058403c4a110bb49b938af2f\n"

如果你还记得,我们创建的提交被称为:af64eba,.git/refs/heads/main 以这些十六进制数字开头并不是巧合。

这个由40个十六进制数字(代表20个字节)组成的完整字符串被称为 “提交hash”。

我们很快就会看到它是如何计算的(以及它为什么被称为 “hash”),但现在你可以把它看作是提交文件的唯一标识符。

check out 操作也有可能(虽然不太常见)切出一个特定的提交,而不是一个分支。例如,我们可以运行 git checkout af64eba 来切出当前的提交。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rust复制代码Note: switching to 'af64eba'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

git switch -c <new-branch-name>

Or undo this operation with:

git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at af64eba Initial commit

在这种情况下,.git/HEAD文件会直接指定提交hash:

1
rust复制代码Head file: "af64eba00e3cfccc058403c4a110bb49b938af2f\n"

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

请求过程中的那些事 请求参数处理 普通参数

发表于 2021-11-19

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

请求参数处理

请求映射

之前我们定义路径使用getUser这种风格的,但是当路径多了之后就会显得十分麻烦,现在我们使用新的风格去开发,Rest的方式(使用HTTP请求方式动词来表示对资源的操作)

例如

/user GET-获取用户 DELETE-删除用户

多余的我们就不展示了,就简单举两个例子

1
2
3
4
5
6
7
8
9
typescript复制代码 	@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-用户";
}

@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-用户";
}

因为不想写前端页面了,所以直接使用Postman进行测试,效果都是一样的

)

可以看我们的路径是一样的,但是我们请求的方式不同,我们的得到的反馈就不同

如果是写页面测试的小伙伴,可能会遇到一个问题,就是除了get和post这两种方式好使以外,其他的请求方式不好使,这时候在method="post",下面再加上一个隐藏参数,<input name="_method" type="hidden" type="xxx",这样子做就可以了

当我们使用不同方式请求的时候,就会显得很麻烦,他就有一种新的注解@GetMapping("/user")

映射原理

SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping。访问/能访问到index.html;

SpringBoot自动配置了默认的 RequestMapping

请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。

  • 如果有就找到这个请求对应的handler …如果有就找到这个请求对应的处理器
  • 如果没有就是下一个 HandlerMapping …如果没有就是下一个处理映射

我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。

1
2
3
4
5
6
7
8
9
10
11
kotlin复制代码	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

普通参数

我们传参的方式主要有四种

注解

使用@RequestParam,我们可以通过路径的方式去获取信息

1
2
3
4
5
6
typescript复制代码 	@GetMapping("/car/{id}")
public Map<String,Object> getCar(@PathVariable("id") Integer id){
Map<String,Object> map = new HashMap<>();
map.put("id",id);
return null;
}

其中的ID我们就可以通过指定的路径去获取

我们可以这样一个一个去获取值,也可以使用一个Map,都收集起来

1
2
3
4
5
6
7
8
9
10
11
less复制代码	@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv){
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("name",name);
map.put("pv",pv);

return map;
}

我们的测试工具还是postman

小插曲

postman官网下载地址

www.postman.com/downloads/

postman汉化包

github.com/hlmd/Postma…

我们只需要将汉化包放在postman的文件夹下

C:\Users\79367\AppData\Local\Postman\app-9.1.5\resources

上面是我的路径

回归正题,我们可以使用注解去获取请求头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
less复制代码	@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String,String> pv,
@RequestHeader("User-Agent") String userAgent,
@RequestHeader Map<String,String> herder){
Map<String,Object> map = new HashMap<>();
map.put("id",id);
map.put("name",name);
map.put("pv",pv);
map.put("userAgent",userAgent);
map.put("headers",herder);


return map;
}

使用HttpServletRequest request可以将信息传到域中

1
2
3
4
5
typescript复制代码	@GetMapping("/goto")
public String geto(HttpServletRequest request){
request.setAttribute("msg","111");
return "s";
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

包装类型、拆线装箱和常量池缓存

发表于 2021-11-19

包装类型

数据类型

Java中存在8种基本类型和5种引用类型。

基本数据类型

基本数据类型分为3类:字符,布尔,数值类型

  • char:16位
  • byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。
  • short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。
  • int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。
  • long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。
  • float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。
  • double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。
  • boolean:只有true和false两个取值。

对于较小、基础的的变量,作为对象储存不如直接作为值储存在堆栈中

引用数据类型

五种引用类型分别为:

  • 类
  • 接口
  • 数组
  • 枚举
  • 标注

包装类型的出现

每个基本类型都有对应的包装类型

Java是一个面向对象的语言,基本类型并不具有对象的性质,为了与其他对象“接轨”就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

区别

基本类型的优势:数据存储相对简单,运算效率比较高 包装类的优势:提供对数据更多操作,比如集合的元素必须是对象类型,满足了java一切皆是对象的思想 声明方式不同,基本类型不适用new关键字,而包装类型需要使用new关键字来在堆中分配存储空间; 存储方式及位置不同,基本类型是直接将变量值存储在堆栈中,而包装类型是将对象放在堆中,然后通过引用来使用; 初始值不同,基本类型的初始值如int为0,boolean为false,而包装类型的初始值为null 使用方式不同,基本类型直接赋值直接使用就好,而包装类型在集合如Collection、Map时会使用到

装箱拆箱

装箱即基本类型转为对应包装类型的过程,相应地拆箱即逆过程。

自动拆箱装箱

当对基本类型使用包装类操作或者对包装类进行基本类型操作(如:操作符)时,自动地转换为相应的包装类或基本类型。以达到屏蔽转换的过程,对外只表现为对变量的操作。

JDK源码

使用了了包装类中的newValueOf和xxxValue()方法,也可以由我们自己手动调用,相当于一种语法糖.

缓存机制

以Integer类为例,装箱机制创建包装类对象时,首先会判断数值是否在-128—-127的范围内,如果满足条件,则会从缓存(常量池)中寻找指定数值,若找到缓存,则不会新建对象,只是指向指定数值对应的包装类对象,否则,新建对象。

对应JDK源码为:IntegerCache

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
ini复制代码private static class IntegerCache {
       static final int low = -128;
       static final int high;
       static final Integer cache[];//声明为final,所以缓存的对象会被放入常量池中;声明为statci,所以是在类加载的时候就创建好了
       //创建-128~127的值的包装类对象
       static {
           // high value may be configured by property
           int h = 127;
           String integerCacheHighPropValue =
               sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
           if (integerCacheHighPropValue != null) {
               try {
                   int i = parseInt(integerCacheHighPropValue);
                   i = Math.max(i, 127);
                   // Maximum array size is Integer.MAX_VALUE
                   h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
              } catch( NumberFormatException nfe) {
                   // If the property cannot be parsed into an int, ignore it.
              }
          }
           high = h;
​
           cache = new Integer[(high - low) + 1];
           int j = low;
           for(int k = 0; k < cache.length; k++)
               cache[k] = new Integer(j++);
​
           // range [-128, 127] must be interned (JLS7 5.1.7)
           assert IntegerCache.high >= 127;
      }
​
       private IntegerCache() {}
}
​

当我们定义两个Integer的范围在【-128—+127】之间,并且值相同的时候,用==比较值为true;

当大于127或者小于-128的时候即使两个数值相同,也会new一个integer,那么比较的是两个对象,用==比较的时候返回false

享元模式,服用常用的变量对象

Integer 、Byte 、Short 、Long 、Character 五大包装类都有缓冲机制,且缓冲的默认值范围都是-128~127

而Float,Double,Boolean 三大包装类并没有缓冲机制。

缓存机制的设计

IntegerCache是Integer的内部类,用来将-128——high之间的对象进行实例化

这边固定了缓存的下限,但是上限可以通过设置jdk的AutoBoxCacheMax参数调整,自动缓存区间设置为[-128,N];

IntegerCache 不会有实例化,它是 private static class IntegerCache,在 Integer 中都是直接使用其 static 方法

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

HotSpot 垃圾收集器 HotSpot 垃圾收集器

发表于 2021-11-19

HotSpot 垃圾收集器

HotSpot 虚拟机提供了多种垃圾收集器,每种收集器都有各自的特点,虽然我们要对各个收集器进行比较,但并非为了挑选出一个最好的收集器。我们选择的只是对具体应用最合适的收集器。

新生代垃圾收集器

Serial 垃圾收集器(单线程)

只开启一条 GC 线程进行垃圾回收,并且在垃圾收集过程中停止一切用户线程,即 Stop The World。

一般客户端应用所需内存较小,不会创建太多对象,而且堆内存不大,因此垃圾收集器回收时间短,即使在这段时间停止一切用户线程,也不会感觉明显卡顿。因此 Serial 垃圾收集器适合客户端使用。

由于 Serial 收集器只使用一条 GC 线程,避免了线程切换的开销,从而简单高效。

Serial

ParNew 垃圾收集器(多线程)

ParNew 是 Serial 的多线程版本。由多条 GC 线程并行地进行垃圾清理。但清理过程依然需要 Stop The World。

ParNew 追求“低停顿时间”,与 Serial 唯一区别就是使用了多线程进行垃圾收集,在多 CPU 环境下性能比 Serial 会有一定程度的提升;但线程切换需要额外的开销,因此在单 CPU 环境中表现不如 Serial。

ParNew

Parallel Scavenge 垃圾收集器(多线程)

Parallel Scavenge 和 ParNew 一样,都是多线程、新生代垃圾收集器。但是两者有巨大的不同点:

  • Parallel Scavenge:追求 CPU 吞吐量,能够在较短时间内完成指定任务,因此适合没有交互的后台计算。
  • ParNew:追求降低用户停顿时间,适合交互式应用。
1
scss复制代码吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

追求高吞吐量,可以通过减少 GC 执行实际工作的时间,然而,仅仅偶尔运行 GC 意味着每当 GC 运行时将有许多工作要做,因为在此期间积累在堆中的对象数量很高。单个 GC 需要花更多的时间来完成,从而导致更高的暂停时间。而考虑到低暂停时间,最好频繁运行 GC 以便更快速完成,反过来又导致吞吐量下降。

  • 通过参数 -XX:GCTimeRadio 设置垃圾回收时间占总 CPU 时间的百分比。
  • 通过参数 -XX:MaxGCPauseMillis 设置垃圾处理过程最久停顿时间。
  • 通过命令 -XX:+UseAdaptiveSizePolicy 开启自适应策略。我们只要设置好堆的大小和 MaxGCPauseMillis 或 GCTimeRadio,收集器会自动调整新生代的大小、Eden 和 Survivor 的比例、对象进入老年代的年龄,以最大程度上接近我们设置的 MaxGCPauseMillis 或 GCTimeRadio。

老年代垃圾收集器

Serial Old 垃圾收集器(单线程)

Serial Old 收集器是 Serial 的老年代版本,都是单线程收集器,只启用一条 GC 线程,都适合客户端应用。它们唯一的区别就是:Serial Old 工作在老年代,使用“标记-整理”算法;Serial 工作在新生代,使用“复制”算法。

Parallel Old 垃圾收集器(多线程)

Parallel Old 收集器是 Parallel Scavenge 的老年代版本,追求 CPU 吞吐量。

CMS 垃圾收集器

CMS(Concurrent Mark Sweep,并发标记清除)收集器是以获取最短回收停顿时间为目标的收集器(追求低停顿),它在垃圾收集时使得用户线程和 GC 线程并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。

  • 初始标记:Stop The World,仅使用一条初始标记线程对所有与 GC Roots 直接关联的对象进行标记。
  • 并发标记:使用多条标记线程,与用户线程并发执行。此过程进行可达性分析,标记出所有废弃对象。速度很慢。
  • 重新标记:Stop The World,使用多条标记线程并发执行,将刚才并发标记过程中新出现的废弃对象标记出来。
  • 并发清除:只使用一条 GC 线程,与用户线程并发执行,清除刚才标记的对象。这个过程非常耗时。

并发标记与并发清除过程耗时最长,且可以与用户线程一起工作,因此,总体上说,CMS 收集器的内存回收过程是与用户线程一起并发执行的。

CMS

CMS 的缺点:

  • 吞吐量低
  • 无法处理浮动垃圾,导致频繁 Full GC
  • 使用“标记-清除”算法产生碎片空间

在执行‘并发清理’步骤时,用户线程也会同时产生一部分可回收对象,但是这部分可回收对象只能在下次执行清理是才会被回收。如果在清理过程中预留给用户线程的内存不足就会出现‘Concurrent Mode Failure’,一旦出现此错误时便会切换到SerialOld收集方式。

对于产生碎片空间的问题,可以通过开启 -XX:+UseCMSCompactAtFullCollection,在每次 Full GC 完成后都会进行一次内存压缩整理,将零散在各处的对象整理到一块。设置参数 -XX:CMSFullGCsBeforeCompaction 告诉 CMS,经过了 N 次 Full GC 之后再进行一次内存整理。

G1 通用垃圾收集器

G1 是一款面向服务端应用的垃圾收集器,它没有新生代和老年代的概念,而是将堆划分为一块块独立的 Region。当要进行垃圾收集时,首先估计每个 Region 中垃圾的数量,每次都从垃圾回收价值最大的 Region 开始回收,因此可以获得最大的回收效率。

Region中还有一类Humongous区域,专门用于储存大对象,每个Region在1~32MB之间,大小可以通过G1HeapRegionSize调整,超过Region大小一半就会判定为Humongous

从整体上看, G1 是基于“标记-整理”算法实现的收集器,从局部(两个 Region 之间)上看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。

记忆集与PRT

一个对象和它内部所引用的对象可能不在同一个 Region 中,那么当垃圾回收时,是否需要扫描整个堆内存才能完整地进行一次可达性分析?

每个 Region 都有一个 Remembered Set,用于记录本区域中所有对象引用的对象所在的区域,进行可达性分析时,只要在 GC Roots 中再加上 Remembered Set 即可防止对整个堆内存进行遍历。

并且记忆集记录了指向自己的指针,并标记这些指针所属卡表范围。本质是哈希表,储存了其他Region的起始地址与卡表索引的映射。这种复杂的双向卡表,即复杂又占用更多内存。

PRT

RSet在内部使用Per Region Table(PRT)记录分区的引用情况。由于RSet的记录要占用分区的空间,如果一个分区非常”受欢迎”,那么RSet占用的空间会上升,从而降低分区的可用空间。G1应对这个问题采用了改变RSet的密度的方式,在PRT中将会以三种模式记录引用:

  • 稀少:直接记录引用对象的卡片索引
  • 细粒度:记录引用对象的分区索引
  • 粗粒度:只记录引用情况,每个分区对应一个比特位

扫描效率与记录粒度成反比

RSet的维护

由于不能整堆扫描,又需要计算分区确切的活跃度,因此,G1需要一个增量式的完全标记并发算法,通过维护RSet,得到准确的分区引用信息。在G1中,RSet的维护主要来源两个方面:写屏障(Write Barrier)和并发优化线程(Concurrence Refinement Threads)

G1的卡表结构复杂,维护Rset的写后屏障操作复杂,又为了实现STAB算法,还需要用写前屏障跟踪应用变化。因此写屏障实现十分复杂。采用了日志记录,异步更新的方案。

写屏障

写前屏障 Pre-Write Barrrier

即将执行一段赋值语句时,等式左侧对象将修改引用到另一个对象,那么等式左侧对象原先引用的对象所在分区将因此丧失一个引用,那么JVM就需要在赋值语句生效之前,记录丧失引用的对象。JVM并不会立即维护RSet,而是通过批量处理,在将来RSet更新(见SATB)。

写后屏障 Post-Write Barrrier

当执行一段赋值语句后,等式右侧对象获取了左侧对象的引用,那么等式右侧对象所在分区的RSet也应该得到更新。同样为了降低开销,写后屏障发生后,RSet也不会立即更新,同样只是记录此次更新日志,在将来批量处理(见Concurrence Refinement Threads)。

原始快照算法

SATB会创建一个对象图,相当于堆的逻辑快照,从而确保并发标记阶段所有的垃圾对象都能通过快照被鉴别出来。当赋值语句发生时,应用将会改变了它的对象图,那么JVM需要记录被覆盖的对象。因此写前屏障会在引用变更前,将值记录在SATB日志或缓冲区中。每个线程都会独占一个SATB缓冲区,初始有256条记录空间。

并发优化线程

并发优化线程(Concurrence Refinement Threads),只专注扫描日志缓冲区记录的卡片来维护更新RSet,线程最大数目可通过-XX:G1ConcRefinementThreads(默认等于-XX:ParellelGCThreads)设置。并发优化线程永远是活跃的,一旦发现全局列表有记录存在,就开始并发处理。如果记录增长很快或者来不及处理,那么通过阈值-X:G1ConcRefinementGreenZone/-XX:G1ConcRefinementYellowZone/-XX:G1ConcRefinementRedZone,G1会用分层的方式调度,使更多的线程处理全局列表。如果并发优化线程也不能跟上缓冲区数量,则Mutator线程(Java应用线程)会挂起应用并被加进来帮助处理,直到全部处理完。因此,必须避免此类场景出现。

并发标记

为了解决并发过程中,对象消失问题,基于三色标记法,采用原始快照(STAB算法),记录被删除的灰色到白色的引用。

在用户创建新对象的过程中,G1为每一个Region设计了两个名为TAMS的指针,把一部分空间划分出来,用于并发过程中新对象的分配。默认这部分的对象是隐式标记的,即默认存活。当这部分的对象分配速度大于回收释放空间的速度,就会被迫冻结用户线程。

为什么G1采用SATB而不用incremental update?

因为采用incremental update把黑色重新标记为灰色后,之前扫描过的还要再扫描一遍,效率太低。G1有RSet与SATB相配合。Card Table里记录了RSet,RSet里记录了其他对象指向自己的引用,这样就不需要再扫描其他区域,。

也就是说 灰色–>白色 引用消失时,如果没有 黑色–>白色,引用会被push到堆栈,下次扫描时拿到这个引用,由于有RSet的存在,不需要扫描整个堆去查找指向白色的引用,效率比较高。

通过在用户程序运行期间增加跟踪引用变化的消耗,减少了并发标记和重新标记阶段的消耗。

活动周期与收集步骤

全局并发标记(并发标记周期)

  • 初始标记:Stop The World,仅使用一条初始标记线程对所有与 GC Roots 直接关联的对象进行标记(压入搜索栈)。使用外部bitmap记录mark信息(而不是对象头)。借用Minor GC的时候同步完成(与MinGC逻辑上独立)。
  • 根区域扫描:从Survior区对象出发,标记被引用到老年代的对象,再压入扫描栈。并发执行,必须在MinGC开始前完成。
  • 并发标记:使用一条标记线程与用户线程并发执行。此过程进行可达性分析,不断从扫描栈取出引用递归扫描整个堆里的对象。每扫描到一个对象就会对其标记,并将其字段压入扫描栈。重复扫描过程直到扫描栈清空。过程中还会扫描SATB write barrier所记录下的引用。Concurrent Marking 可以被MinGC中断
  • 最终标记:在完成并发标记后,每个Java线程还会有一些剩下的SATB write barrier记录的引用尚未处理。这个阶段就负责把剩下的引用处理完。同时这个阶段也进行弱引用处理(reference processing)
  • 筛选回收:统计Region回收成本和价值的排序,根据参数指定任意的回收计划。回收废弃对象,此时也要 Stop The World,并使用多条筛选回收线程并发执行。

分代式G1模式下有两种选定CSet的子模式,分别对应young GC与mixed GC:

在MIXGC中的Cset是选定所有young gen里的region,外加根据global concurrent marking统计得出收集收益高的若干old gen region。

在MinGC中的Cset是选定所有young gen里的region。通过控制young gen的region个数来控制young GC的开销。

MinGC与MIXGC都是采用多线程复制清除,整个过程会STW。 G1的低延迟原理在于其回收的区域变得精确并且范围变小了。

分代式G1的正常工作流程就是在young GC与mixed GC之间视情况切换,背后定期做做全局并发标记。Initial marking默认搭在young GC上执行;当全局并发标记正在工作时,G1不会选择做mixed GC,反之如果有mixed GC正在进行中G1也不会启动initial marking。 在正常工作流程中没有full GC的概念,old gen的收集全靠mixed GC来完成。

可靠停顿预测模型

以衰减均值为理论基础,在垃圾收集过程中,记录每一个Region的回收耗时、每个Region记忆集里脏卡数量等可测量的步骤花费的成本.

新型垃圾收集器

\

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

elastic的聚合搜索 集群认识 冷热集群架构 重新认识e

发表于 2021-11-19
  • 我们先来举个栗子,一堆糖果,我们想按颜色分类
    在这里插入图片描述
  • 我们也看来求一些最大值 最小值,平均值(注意:聚合内部是可以嵌套的)
    在这里插入图片描述

冷热集群架构

  • elastic的节点是支持指定类型的,比如指定哪些节点是热节点和哪些是冷节点,配置方式有以下两种
    通过elasticsearch.yml配置
1
2
bash复制代码node.attr.hotwarm_type: hot #热
node.attr.hotwarm_type: warm #冷
  • 创建索引时指定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bash复制代码PUT /logs_2019-10-01
{
"settings": {
"index.routing.allocation.require.hotwarm_type": "hot",
"number_of_replicas": 0
}
}


PUT /logs_2019-08-01
{
"settings": {
"index.routing.allocation.require.hotwarm_type": "warm",
"number_of_replicas": 0
}
}
  • 通过模板指定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bash复制代码PUT _template/logs_2019-08-template
{
"index_patterns": "logs_2019-08-*",
"settings": {
"index.number_of_replicas": "0",
"index.routing.allocation.require.hotwarm_type": "warm"
}
}
PUT _template/logs_2019-10-template
{
"index_patterns": "logs_2019-10-*",
"settings": {
"index.number_of_replicas": "0",
"index.routing.allocation.require.hotwarm_type": "hot"
}
}

重新认识elastic集群

  • cluster
    集群,一个ES集群由一个或多个节点(Node)组成,每个集群都有一个cluster name作为标识
  • node
    节点,一个ES实例就是一个node,一个机器可以有多个实例,所以并不能说一台机器就是一个node,大多数情况下每个node运行在一个独立的环境或虚拟机上。
  • index
    索引,即一系列documents的集合
  • shard
    分片,ES是分布式搜索引擎,每个索引有一个或多个分片,索引的数据被分配到各个分片上,相当于一桶水用了N个杯子装,分片有助于横向扩展,N个分片会被尽可能平均地(rebalance)分配在不同的节点上(例如你有2个节点,4个主分片(不考虑备份),那么每个节点会分到2个分片,后来你增加了2个节点,那么你这4个节点上都会有1个分片,这个过程叫relocation,ES感知后自动完成),分片是独立的,对于一个Search Request的行为,每个分片都会执行这个Request.另外,每个分片都是一个Lucene Index,所以一个分片只能存放 Integer.MAX_VALUE - 128 = 2,147,483,519 个docs。建议单个分片大小设置在 30-50 GB 之间。一个分片默认最大文档数量是20亿。 宿主机内存大小的一半和31GB,取最小值。
  • replica
    复制,可以理解为备份分片,相应地有primary shard(主分片),主分片和备分片不会出现在同一个节点上(防止单点故障),默认情况下一个索引创建5个分片一个备份(即5primary+5replica=10个分片),如果你只有一个节点,那么5个replica都无法分配(unassigned),此时cluster status会变成Yellow。replica的作用主要包括:

1.容灾:primary分片丢失,replica分片就会被顶上去成为新的主分片,同时根据这个新的主分片创建新的replica,集群数据安然无恙
2.提高查询性能:replica和primary分片的数据是相同的,所以对于一个query既可以查主分片也可以查备分片,在合适的范围内多个replica性能会更优(但要考虑资源占用也会提升[cpu/disk/heap]),另外index request只能发生在主分片上,replica不能执行index request。
对于一个索引,除非重建索引否则不能调整分片的数目(主分片数, number_of_shards),但可以随时调整replica数(number_of_replicas)。

存储空间

如果你的 Elasticsearch 集群节点的磁盘空间不足,则会影响集群性能。
一旦可用存储空间低于特定阈值限制,它将开始阻止写入操作,进而影响数据进入集群。
不少同学可能会遇到过如下的错误:ElasticsearchStatusException[Elasticsearch exception [type=cluster_block_exception, reason=blocked by: [FORBIDDEN/12/index read-only / allow这就是磁盘快满了做的保护机制提示。

磁盘的三个默认警戒水位线。

  • 低警戒水位线
    默认为磁盘容量的85%。Elasticsearch不会将新的分片分配给磁盘使用率超过85%的节点。它也可以设置为绝对字节值(如500mb),以防止 Elasticsearch 在小于指定的可用空间量时分配分片。此设置不会影响新创建的索引的主分片,特别是之前从未分配过的分片。
1
bash复制代码cluster.routing.allocation.disk.watermark.low
  • 高警戒水位线
    默认为磁盘容量的90%。Elasticsearch 将尝试对磁盘使用率超过90%的节点重新分配分片(将当前节点的数据转移到其他节点)。它也可以设置为绝对字节值,以便在节点小于指定的可用空间量时将其从节点重新分配。此设置会影响所有分片的分配,无论先前是否分配。
1
bash复制代码cluster.routing.allocation.disk.watermark.high
  • 洪水警戒水位线
    默认为磁盘容量的95%。Elasticsearch对每个索引强制执行只读索引块(index.blocks.read_only_allow_delete)。这是防止节点耗尽磁盘空间的最后手段。只读模式待磁盘空间充裕后,需要人工解除。因此,监视集群中的可用存储空间至关重要。
1
bash复制代码cluster.routing.allocation.disk.watermark.flood_stage

文档的删除

elasticsearch中的文档无法修改,并且是不可改版的,所以所有的更新操作都会将已有的文档标记为删除状态,包括删除文档,其实也不会马上就得对文档进行删除,而是标记为删除状态。当我们再次搜索的时候,会搜索全部然后过滤掉有删除标记的文档。因此,该索引所占的空间并不会随着该API的操作磁盘空间会马上释放掉,只有等到下一次段合并的时候才真正被物理删除,这个时候磁盘空间才会释放。相反,在被查询到的文档标记删除过程同样需要占用磁盘空间,这个时候,你会发现触发该API操作的时候磁盘不但没有被释放,反而磁盘使用率上升了。

一般生产环境中,使用该API操作的索引都很大,文档都是千万甚至数亿级别。索引大小在几百G甚至几个T,因此,这个操作建议在业务低峰期或者晚上进行操作,因为大数据量情况下删除的需要消耗较多的i/o CPU 资源,容易对生产集群造成影响。在删除过程中要确定集群磁盘有一定的余量,因为标记删除需要占用磁盘空间。如果磁盘空间不够,这个操作的失败率还是很大的。

Elasticsearch会有后台线程根据lucene的合并规则定期进行段合并操作,一般不需要用户担心或者采取任何行动。被删除的文档在合并时,才会被真正删除掉。再次之前,它仍然会占用着JVM heap和操作系统的文件cache、磁盘等资源。在某些特定情况下,我们需要ES强制进行段合并,以释放其占用的大量系统、磁盘等资源。POST /index_name/_forcemerge。

_forcemerge 命令可强制进行segment合并,并删除所有标记为删除的文档。Segment merging要消耗CPU,以及大量的I/O资源,所以一定要在你的ElasticSearch集群处于维护窗口期间,并且有足够的I/O空间的(如:SSD)的条件下进行;否则很可能造成集群崩溃和数据丢失。

文档的刷新

Elasticsearch refresh 刷新操作是使文档可搜索的过程。
默认情况下,每秒刷新一次。如果主要目标是调整摄取速度的索引,则可以将 Elasticsearch 的默认刷新间隔从1秒更改为30秒。30秒后,这将使文档可见以供搜索,从而优化索引速度。

  • 也可以对某个索引指定刷新频率
1
2
3
4
5
6
bash复制代码PUT my_index/_settings
{
"index": {
"refresh_interval": "30s"
}
}

副本

副本为了避免主分片出现问题导致数据丢失的问题,主分片和副本不会出现在同一个节点上。但是这样势必影响写的效率,对吧,因为副本是需要从主分片去同步数据的,所以在数据初始化的时候,可以先禁用副本,初始化完毕再开启副本

字段类型的选择

在这里插入图片描述

慢查询日志的开启

建议你在 Elasticsearch 集群中启用慢速查询日志,以解决性能问题并捕获运行时间较长或超过设置阈值的查询。
例如,如果您的搜索SLA为 2 秒,则可以按以下方式配置搜索查询,超过该阈值的任何查询都将被记录。

1
2
3
4
bash复制代码PUT my_index/_settings
{
"index.search.slowlog.threshold.query.warn" : "2s"
}

设置用户名密码【单机版】

  • 使用docker启动单机服务
1
2
bash复制代码zhangguofu@zhangguofudeMacBook-Pro Downloads $ docker run -d --name es -p 9212:9200 -p 9313:9300 -e "discovery.type=single-node"   elasticsearch:7.2.0
be9b65b0aa5770c5421752e935c54343c17cd9b86edfbe9f09b721a6e2d3bbca
  • 进入容器配置文件
1
2
3
bash复制代码zhangguofu@zhangguofudeMacBook-Pro Downloads $ docker exec -it be bash
[root@be9b65b0aa57 elasticsearch]# ls
LICENSE.txt NOTICE.txt README.textile bin config data jdk lib logs modules plugins
  • 参考文章点击查看
    在这里插入图片描述
  • 重启容器并增加用户名密码(如果之前没设置过,需要重启,如果没有更改过elastic.yml 文件,则不需要重启)
    在这里插入图片描述

多节点配置

  • 给elastic 多个节点配置用户名密码的话,我们需要通过颁发证书的方式参考文章
  • 下载elastic包,并解压,并复制出一份,来做一个elastic集群,我们大概看一下目录
  • 在这里插入图片描述
  • 我们配置一下elastic1,也就是节点1的elasticsearch.yml(在 elasticsearch1/config/elasticsearch.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bash复制代码# 集群的名字
cluster.name: cluster2
#节点的名字
node.name: node1
# 可以作为主节点
node.master: true
#可以作为数据节点
node.data: true
# 不支持远程连接
cluster.remote.connect: false
# 本机的地址(如果需要外网访问,请填写具体ip,127.0.0.1局域网访问不到)
network.host: 172.16.131.4
# 对外服务的端口
http.port: 9200
# 集群数据交换的段开口
transport.port:9300
# 种子节点 9201 9301是 其他节点的端口号
discovery.seed_hosts: ["172.16.131.4:9300","172.16.131.4:9301"]
# 初始化的主节点(此处注意,后面会有变化)
cluster.initial_master_nodes: ["172.16.131.4:9300","172.16.131.4:9301"]
  • 配置节点2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bash复制代码# 集群的名字
cluster.name: cluster2
#节点的名字
node.name: node2
# 可以作为主节点
node.master: true
#可以作为数据节点
node.data: true
# 不支持远程连接
cluster.remote.connect: false
# 本机的地址(如果需要外网访问,请填写具体ip,127.0.0.1局域网访问不到)
network.host: 172.16.131.4
# 对外服务的端口
http.port: 9201
# 集群数据交换的段开口
transport.port:9301
# 种子节点
discovery.seed_hosts: ["172.16.131.4:9300","172.16.131.4:9301"]
# 初始化的主节点(此处注意,后面会有变化)
cluster.initial_master_nodes: ["172.16.131.4:9300","172.16.131.4:9301"]

配置完毕后,我们分别启动节点

  • 注意,由于我之前有一次没有配置节点,直接启动,后来再加入新的节点加不进去,后来删除 elastic1/data/nodes 目录后重新启动生效,估计如果第一次启动如果是单机启动,后面启动新的节点也不会被发现

生成证书

  • 关停各个服务节点
  • 讲单机版加密的时候我们也说过,需要在elasticsearch.yml里面加入以下配置信息
1
2
bash复制代码xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
  • 那么在集群中,我们需要先加入xpack.security.enabled: true 这个配置,来说明 这个集群是要使用安全加密的。
  • 生成证书
1
2
3
4
bash复制代码# 生成证书
bin/elasticsearch-certutil ca
# 生成证书和私钥
bin/elasticsearch-certutil cert --ca elastic-stack-ca.p12
  • 这个时候在elastic1目录下会生成elastic-certificates.p12文件,我们把这个文件复制到各个节点的config目录下,并在每个elasticsearch.yml里面配置,然后启动各个节点
1
2
3
4
5
bash复制代码xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
# 这个就是我们刚才生成证书,放在了config目录下,如果你想指定 相对或者绝对路径都可以(相对 默认目录是config)
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
  • 在我启动的时候,发现报了一个错误
1
bash复制代码{"error":{"root_cause":[{"type":"master_not_discovered_exception","reason":null}],"type":"master_not_discovered_exception","reason":null},"status":503}
  • 就是主节点没有发现,那么这个时候就要指定一下主节点,我使用elasticsearch2,也就是2节点为主节点,看一下节点2的配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bash复制代码cluster.name: cluster2 
node.name: node2
node.master: true
node.data: true
cluster.remote.connect: false
network.host: 172.16.131.4
http.port: 9201
transport.port:9301
discovery.seed_hosts: ["172.16.131.4:9300","172.16.131.4:9301"]
#是这里直接指定为主节点
cluster.initial_master_nodes: node2
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
  • 全部启动完毕后,我在主节点的目录下生成密码,在我测试过程中,如果主节点发生变化,比如变成节点1了,那么就要对节点1设置用户名密码
1
bash复制代码bin/elasticsearch-setup-passwords interactive

在这里插入图片描述

  • 设置完毕密码,当你再次访问9200的时候,就需要输入用户名密码了,用户名就是elastic,密码就是你设置的那个密码
    在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Widows和Linux下如何安装MySQL MySQL

发表于 2021-11-19

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」


相关文章

MySQL:MySQL系列


一、在Widows下安装MySQL

  • 前提: 本人不建议使用exe安装包来进行安装,原因是如果有错误,很难卸载干净,以下的教程是以压缩包形式手动安装,可控性较高!
  • ①下载安装包:
  • 因为MySQL是国外的网站,推荐去镜像站寻找安装包 点击跳转
    在这里插入图片描述
  • 点击进入后搜索winx64,找到合适的版本点击下载!
    在这里插入图片描述
  • ②解压
  • 还是解压到一个固定的目录底下。
    在这里插入图片描述
  • 进入bin目录
    在这里插入图片描述
  • 确认解压后是37个文件,文件数量不对的话重新下载解压!务必保证文件正常!
    在这里插入图片描述
  • ③添加环境变量
  • 点击新增:
    在这里插入图片描述
  • 添加–你的mysql 安装文件下面的bin文件夹
    在这里插入图片描述
  • ④编辑 my.ini 文件 ,注意替换路径位置
    在这里插入图片描述
  • 添加内容:
1
2
3
4
5
java复制代码[mysqld]
basedir=D:\Environment\mysql-5.7.23-winx64\ #这里的路径要修改成自己的
datadir=D:\Environment\mysql-5.7.23-winx64\data\ #这里的文件夹不要手动去创建,等启动的时候mysql服务会自动创建
port=3306 #端口
skip-grant-tables #跳过密码验证
  • ⑤启动管理员模式的cmd
    在这里插入图片描述
  • ⑥再输入 mysqld –initialize-insecure –user=mysql 初始化数据文件
  • ⑦然后再次启动mysql 然后用命令 mysql –u root –p 进入mysql管理界面(密码可为空)
  • ⑧进入界面后更改root密码
1
java复制代码update mysql.user set authentication_string=password('123456') where user='root' and Host = 'localhost';
  • ⑨刷新权限
1
java复制代码flush privileges;
  • ⑩修改 my.ini文件删除最后一句skip-grant-tables
  • 最后:重启mysql即可正常使用

二、在Linux下安装MySQL

  • 两篇文章教你安装MySQL
  • 安装MySQL
  • 打开远程连接权限
  • 亲测可以成功哦!我就不全部重写一遍了,没啥坑,不像Windows手动安装那样,这个很简单啦!

路漫漫其修远兮,吾必将上下求索~

如果你认为i博主写的不错!写作不易,请点赞、关注、评论给博主一个鼓励吧~hahah

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…275276277…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%