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

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


  • 首页

  • 归档

  • 搜索

工具篇:JAVA开发之常用工具 前言 参考文章

发表于 2021-01-06

前言

各位随意看

关注公众号,一起交流,微信搜一搜: 潜行前行

Commons BeanUtils

  • 针对Bean的一个工具集。由于Bean往往是有一堆get和set组成,所以BeanUtils也是在此基础上进行一些包装。
  • 一个比较常用的功能是Bean Copy,也就是copy bean的属性。如果做分层架构开发的话就会用到,比如从PO(Persistent Object)拷贝数据到VO(Value Object)

Commons Codec

  • 是编码和解码组件,提供常用的编码和解码方法,如DES、SHA1、MD5、Base64、URL和Soundx等。

Commons Collections

  • 是一个集合组件,扩展了Java标准Collections API,对常用的集合操作进行了很好的封装、抽象和补充,在保证性能的同时大大简化代码。

Commons Compress

  • 是一个压缩、解压缩文件的组件,可以操作rar、cpio、Unix dump、tar、zip、gzip、XZ、Pack200和bzip2格式的压缩文件。

Commons Configuration

  • 是一个Java应用程序的配置管理工具,可以从properties或者xml文件中加载配置信息。

Commons CSV

  • 是一个用来读写各种Comma Separated Value(CSV)格式文件的Java类库。

Commons Daemon

  • 实现将普通的Java应用变成系统的后台服务,例如 Tomcat 就是利用这个项目来实现作为 Linux 和 Windows 的服务启动和停止的。

Commons DBCP

  • 数据库连接池。

Commons DBUtils

  • 是JDBC工具组件,对传统操作数据库的类进行二次封装,可以把结果集转化成List。

Commons Digester

  • 是XML到Java对象的映射工具集。

Commons Email

  • 是邮件操作组件,对Java Mail API进行了封装,提供了常用的邮件发送和接收类,简化邮件操作。该组件依赖Java Mail API。

Commons Exec

  • 提供一些常用的方法用来执行外部进程,如执行exe文件或命令行。

Commons FileUpload

  • 为Web应用程序或Servlet提供文件上传功能,Struts2和SpringMVC的文件上传组件。

Commons IO

  • 是处理IO的工具类包,对java.io进行扩展,提供了更加方便的IO操作。

Commons JCI

  • 提供通用的Java编译器接口。

Commons Lang3

  • 是处理Java基本对象方法的工具类包,该类包提供对字符、数组等基本对象的操作,弥补了java.lang api基本处理方法上的不足。

Commons Launcher

  • 可以跨平台独立启动的java应用程序。

Commons Logging

  • 提供统一的日志接口,同时兼顾轻量级和不依赖于具体的实现。类包给中间件/日志工具开发者一个简单的日志操作抽象,允许程序开发人员使用不同的具体日志实现工具。

Commons Math

  • 轻量级自容器的数学和统计计算方法类包,包含大多数常用的数值算法。

Commons Net

  • 封装了各种网络协议的客户端,支持FTP、NNTP、SMTP、POP3、Telnet等协议。

Commons Pool

  • 提供了一整套用于实现对象池化的框架,以及若干各具特色的对象池实现,可以有效地减少处理对象池化时的工作量。类包用于提高像文件句柄、数据库连接、socket通信这类大对象的调用效率,简单的说就是一种对象一次创建多次使用的技术。

Commons Primitives

  • 提供了一个更小,更快和更易使用的对Java基本类型的支持。

Commons Validator

  • 提供了一个简单的、可扩展的框架来在一个XML文件中定义校验器(校验方法)和校验规则。支持校验规则的和错误消息的国际化。

Apache HttpClient

  • 曾经是Apache Commons的子项目,后来独立出来。HttpClient简化HTTP客户端与服务器的各种通讯,实现HTTP客户端程序(也就是浏览器程序)的功能。

Guava

  • google实用工具库

FastJson

  • 高效的Json处理库

Orika

  • 高效的Bean映射框架

欢迎指正文中错误

参考文章

  • 关于Apache Commons的简介

本文转载自: 掘金

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

MySQL中的redolog,undolog,以及binlo

发表于 2021-01-06

MySQL中有六种日志文件,

分别是:重做日志(redo log)、回滚日志(undo log)、二进制日志(binlog)、错误日志(errorlog)、慢查询日志(slow query log)、一般查询日志(general log),中继日志(relay log)。

其中重做日志和回滚日志与事务操作息息相关,二进制日志也与事务操作有一定的关系,这三种日志,对理解MySQL中的事务操作有着重要的意义。

这里简单总结一下这三者具有一定相关性的日志。

重做日志(redo log)

作用:

  确保事务的持久性。

防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。

内容:

  物理格式的日志,记录的是物理数据页面的修改的信息,其redo log是顺序写入redo log file的物理文件中去的。

什么时候产生:

  事务开始之后就产生redo log,redo log的落盘并不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入redo log文件中。

什么时候释放:

  当对应事务的脏页写入到磁盘之后,redo log的使命也就完成了,重做日志占用的空间就可以重用(被覆盖)。

对应的物理文件:

  默认情况下,对应的物理文件位于数据库的data目录下的ib_logfile1&ib_logfile2

innodb_log_group_home_dir 指定日志文件组所在的路径,默认./ ,表示在数据库的数据目录下。

innodb_log_files_in_group 指定重做日志文件组中文件的数量,默认2

关于文件的大小和数量,由一下两个参数配置

innodb_log_file_size 重做日志文件的大小。

innodb_mirrored_log_groups 指定了日志镜像文件组的数量,默认1

其他:

  很重要一点,redo log是什么时候写盘的?前面说了是在事物开始之后逐步写盘的。

之所以说重做日志是在事务开始之后逐步写入重做日志文件,而不一定是事务提交才写入重做日志缓存,

原因就是,重做日志有一个缓存区Innodb_log_buffer,Innodb_log_buffer的默认大小为8M(这里设置的16M),Innodb存储引擎先将重做日志写入innodb_log_buffer中。

  

  然后会通过以下三种方式将innodb日志缓冲区的日志刷新到磁盘

1,Master Thread 每秒一次执行刷新Innodb_log_buffer到重做日志文件。

2,每个事务提交时会将重做日志刷新到重做日志文件。

3,当重做日志缓存可用空间 少于一半时,重做日志缓存被刷新到重做日志文件

由此可以看出,重做日志通过不止一种方式写入到磁盘,尤其是对于第一种方式,Innodb_log_buffer到重做日志文件是Master Thread线程的定时任务。

因此重做日志的写盘,并不一定是随着事务的提交才写入重做日志文件的,而是随着事务的开始,逐步开始的。

另外引用《MySQL技术内幕 Innodb 存储引擎》(page37)上的原话:

即使某个事务还没有提交,Innodb存储引擎仍然每秒会将重做日志缓存刷新到重做日志文件。

这一点是必须要知道的,因为这可以很好地解释再大的事务的提交(commit)的时间也是很短暂的。

回滚日志(undo log)

作用:

  保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读

内容:

  逻辑格式的日志,在执行undo的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,这一点是不同于redo log的。

什么时候产生:

  事务开始之前,将当前是的版本生成undo log,undo 也会产生 redo 来保证undo log的可靠性

什么时候释放:

  当事务提交之后,undo log并不能立马被删除,

而是放入待清理的链表,由purge线程判断是否由其他事务在使用undo段中表的上一个事务之前的版本信息,决定是否可以清理undo log的日志空间。

对应的物理文件:

  MySQL5.6之前,undo表空间位于共享表空间的回滚段中,共享表空间的默认的名称是ibdata,位于数据文件目录中。

MySQL5.6之后,undo表空间可以配置成独立的文件,但是提前需要在配置文件中配置,完成数据库初始化后生效且不可改变undo log文件的个数

如果初始化数据库之前没有进行相关配置,那么就无法配置成独立的表空间了。

关于MySQL5.7之后的独立undo 表空间配置参数如下

innodb_undo_directory = /data/undospace/ –undo独立表空间的存放目录

innodb_undo_logs = 128 –回滚段为128KB

innodb_undo_tablespaces = 4 –指定有4个undo log文件

  如果undo使用的共享表空间,这个共享表空间中又不仅仅是存储了undo的信息,共享表空间的默认为与MySQL的数据目录下面,其属性由参数innodb_data_file_path配置。

其他:

  undo是在事务开始之前保存的被修改数据的一个版本,产生undo日志的时候,同样会伴随类似于保护事务持久化机制的redolog的产生。

默认情况下undo文件是保持在共享表空间的,也即ibdatafile文件中,当数据库中发生一些大的事务性操作的时候,要生成大量的undo信息,全部保存在共享表空间中的。

因此共享表空间可能会变的很大,默认情况下,也就是undo 日志使用共享表空间的时候,被“撑大”的共享表空间是不会也不能自动收缩的。

因此,mysql5.7之后的“独立undo 表空间”的配置就显得很有必要了。

二进制日志(binlog):

作用:

  1,用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。

2,用于数据库的基于时间点的还原。

内容:

  逻辑格式的日志,可以简单认为就是执行过的事务中的sql语句。

但又不完全是sql语句这么简单,而是包括了执行的sql语句(增删改)反向的信息,

也就意味着delete对应着delete本身和其反向的insert;update对应着update执行前后的版本的信息;insert对应着delete和insert本身的信息。

在使用mysqlbinlog解析binlog之后一些都会真相大白。

因此可以基于binlog做到类似于oracle的闪回功能,其实都是依赖于binlog中的日志记录。

什么时候产生:

  事务提交的时候,一次性将事务中的sql语句(一个事物可能对应多个sql语句)按照一定的格式记录到binlog中。

这里与redo log很明显的差异就是redo log并不一定是在事务提交的时候刷新到磁盘,redo log是在事务开始之后就开始逐步写入磁盘。

因此对于事务的提交,即便是较大的事务,提交(commit)都是很快的,但是在开启了bin_log的情况下,对于较大事务的提交,可能会变得比较慢一些。

这是因为binlog是在事务提交的时候一次性写入的造成的,这些可以通过测试验证。

什么时候释放:

  binlog的默认是保持时间由参数expire_logs_days配置,也就是说对于非活动的日志文件,在生成时间超过expire_logs_days配置的天数之后,会被自动删除。

对应的物理文件:

  配置文件的路径为log_bin_basename,binlog日志文件按照指定大小,当日志文件达到指定的最大的大小之后,进行滚动更新,生成新的日志文件。

对于每个binlog日志文件,通过一个统一的index文件来组织。

 

其他:

  二进制日志的作用之一是还原数据库的,这与redo log很类似,很多人混淆过,但是两者有本质的不同

1,作用不同:redo log是保证事务的持久性的,是事务层面的,binlog作为还原的功能,是数据库层面的(当然也可以精确到事务层面的),虽然都有还原的意思,但是其保护数据的层次是不一样的。

2,内容不同:redo log是物理日志,是数据页面的修改之后的物理记录,binlog是逻辑日志,可以简单认为记录的就是sql语句

3,另外,两者日志产生的时间,可以释放的时间,在可释放的情况下清理机制,都是完全不同的。

4,恢复数据时候的效率,基于物理日志的redo log恢复数据的效率要高于语句逻辑日志的binlog

  关于事务提交时,redo log和binlog的写入顺序,为了保证主从复制时候的主从一致(当然也包括使用binlog进行基于时间点还原的情况),是要严格一致的,

MySQL通过两阶段提交过程来完成事务的一致性的,也即redo log和binlog的一致性的,理论上是先写redo log,再写binlog,两个日志都提交成功(刷入磁盘),事务才算真正的完成。

总结:

  MySQL中,对于以上三种日志,每一种细化起来都可以够写一个章节的,这里粗略地总结了一下三种日志的一些特点和作用,以帮助理解MySQL中的事物以及事物背后的原理。

参考:《MySQL技术内幕 Innodb 存储引擎》

本文转载自: 掘金

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

寂然解读设计模式 - 模板模式

发表于 2021-01-06
1
css复制代码I walk very slowly, but I never walk backwards

设计模式 - 模板模式


寂然

大家好,我是寂然,本节课我们进入到篇章五 - 行为型模式的学习,首先第一个,来聊行为模式中的模板模式,这个模式相对而言比较常见,同样,我们通过一个案例需求来引入

案例演示 - 豆浆制作

制作豆浆的流程 选材—>添加配料—>浸泡—>放到豆浆机打碎

通过添加不同的配料,可以制作出不同口味的豆浆(花生,黑豆)

选材、浸泡和放到豆浆机打碎这几个步骤对于制作任意口味的豆浆都是一样的

请使用模板模式完成

(因为模板模式,比较简单,很容易想到,因此就直接应用,不再通过传统的方案来引出模板方法模式)

基本介绍

模板模式(Template Pattern),又叫模板方法模式(Template Method Pattern),他的基本介绍如下:

在一个抽象类公开定义了执行它的方法的模板,它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行,简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤

举例说明

概念听着云里雾里,其实这个模式是大家非常常用的,例如,实际开发中常常会遇到,代码骨架类似甚至相同,只是具体的实现不一样的场景,例如:流程都有开启、编辑、驳回、结束,每个流程都包含这几个步骤,不同的是不同的流程实例它们的内容不一样,再举个生活中的例子,共享单车都是先开锁、骑行、上锁、付款,这些大的步骤固定,不同的是每个实例的具体实现细节不一样。这些类似的业务我们都可以定义一个类,在其中一个方法把执行流程定义出来,例如先执行开锁,再执行骑行,接着,上锁和付款,并把公共的方法进行实现,例如共享单车开锁和关锁的方法,定义好了这样一个类似模板的类,那具体各个子类,也就是各个共享单车去实现业务逻辑就容易多了,其实这种思想,就是模板模式

原理类图


1609748193965.png


角色分析

AbstractClass

抽象类, 类中实现了模板方法(template),定义了算法的大致流程,具体子类需要去实现其它的抽象方法

ConcreteClassA/B

实现父类中的抽象方法,完成算法中特定子类的相关步骤

解决方案 - 模板模式

OK,同样的思路,我们先画出使用模板模式解决案例需求的类图,接着通过代码来实现

类图演示

1609750467616.png


代码演示
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
csharp复制代码//抽象类 豆浆
public abstract class SoyaMilk {
​
//make 即模板方法,可以做成final,不让子类去覆盖
final void make(){
​
select();
add();
soak();
beat();
​
}
​
//选材
void select(){
System.out.println("选择新鲜的黄豆作为材料");
}
​
//添加配料,抽象方法
abstract void add();
​
//浸泡
void soak(){
System.out.println("将所有的材料浸泡一段时间");
}
​
//打碎
void beat(){
System.out.println("将所有的材料放到豆浆机里打碎进行制作");
}
​
}
​
//黑豆豆浆
public class BlackBeanSoyaMilk extends SoyaMilk {
​
@Override
void add() {
System.out.println("添加黑豆,糖等一系列材料");
}
}
​
//花生豆浆
public class PeanutSoyaMilk extends SoyaMilk {
​
@Override
void add() {
System.out.println("添加花生,糖等一系列材料");
}
}
​
//客户端
public class Client {
​
public static void main(String[] args) {
​
//制作黑豆豆浆
SoyaMilk blackBeanSoyaMilk = new BlackBeanSoyaMilk();
​
blackBeanSoyaMilk.make();
​
//制作花生豆浆
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
​
peanutSoyaMilk.make();
}
}

所以,你可以把一个通用的方法,全部提到抽象类中去写

钩子方法

下面,我们来看下模板模式中的钩子方法

在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为 ”钩子方法”

案例需求

希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造

代码演示
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
csharp复制代码//抽象类 表示豆浆
public abstract class SoyaMilk {
​
//make 即模板方法,可以做成final,不让子类去覆盖
final void make(){
​
select();
if (needBatching()){
add();
}
soak();
beat();
​
}
​
//选材
void select(){
System.out.println("选择新鲜的黄豆作为材料");
}
​
//添加配料,抽象方法
abstract void add();
​
//浸泡
void soak(){
System.out.println("将所有的材料浸泡一段时间");
}
​
//打碎
void beat(){
System.out.println("将所有的材料放到豆浆机里打碎进行制作");
}
​
//钩子方法,决定是否要添加配料
boolean needBatching(){
return true;
}
}
​
//纯豆浆
public class PureSoyaMilk extends SoyaMilk {
​
@Override
void add() {
//空实现
//TODO...
}
​
@Override
boolean needBatching() {
return false; //不会再去加配料
}
}
​
//客户端
public class Client {
​
public static void main(String[] args) {
​
//制作纯豆浆
SoyaMilk pureSoyaMilk = new PureSoyaMilk();
​
pureSoyaMilk.make();
​
}
}

它可以很轻松的实现,某一个方法在你的模板里是否要调用,你可以设计这样一个钩子方法去进行调用,如果子类没有覆盖,那默认无实现,如果子类重写了父类的方法,那么执行子类的逻辑,子类处理时提供了很大的灵活性

注意事项

基本思想

算法只存在于一个地方,也就是在父类中,容易修改,需要修改算法时,只要修改父类的模板方 法或者已经实现的某些步骤,子类就会继承这些修改,般模板方法都加上 final 关键字, 防止子类重写模板方法

优势

实现了最大化代码复用,父类的模板方法和已实现的某些步骤会被子类继承而直接使用。

既统一了算法,也提供了很大的灵活性(钩子方法),父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现

劣势

每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大

使用场景

当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理,例如上面的举例说明

IOC源码分析

源码分析

Spring IOC 容器初始化时,就会运用到模板方法模式 ,下面我们一起来看下

我们通过 ConfigurableApplicationContext 作为入口,可以看到,它是一个接口,这个接口里面定义了很多方法,其中有一个重要的方法 refresh() ,我们进到其实现来看,这个 refresh() 方法就是一个模板方法

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
scss复制代码@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
​
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
​
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
​
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
​
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
​
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
​
// Initialize message source for this context.
initMessageSource();
​
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
​
// Initialize other special beans in specific context subclasses.
onRefresh();
​
// Check for listener beans and register them.
registerListeners();
​
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
​
// Last step: publish corresponding event.
finishRefresh();
}
​
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
​
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
​
// Reset 'active' flag.
cancelRefresh(ex);
​
// Propagate exception to caller.
throw ex;
}
​
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

可以看到,这个方法内部调了很多很多方法,并且调用的方法,是本类中定义好的,举例两个:

1
2
3
4
scss复制代码// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

我们进入 obtainFreshBeanFactory() 方法,里面调用了两个抽象方法,refreshBeanFactory(),
getBeanFactory(),同样都是本类中定义好的,让子类去实现,这样具体要取哪种 BeanFactory 容器的决定权交给了子类,当然这里我们主要看模式的应用,不去关注IOC的初始化流程

1
2
3
4
scss复制代码protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}

那大家可以看到,和我们案例中的make() 方法很类似对吧,refresh() 里面定义了运行的大致流程


refresh() 里面有没有钩子方法呢?我们接着来看这两个方法

1
2
3
4
5
scss复制代码// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
​
// Initialize other special beans in specific context subclasses.
onRefresh();

可以看到,postProcessBeanFactory() 里面都是空实现

1
2
typescript复制代码protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}

前面讲过,在模板方法模式里面,有些方法是空实现,主要价值是在于让子类覆盖他

1
2
3
csharp复制代码protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}

同理,我在 refresh() 方法里面调用了 onRefresh() ,如果子类没有覆盖,那默认无实现,如果子类重写了父类的方法,那么执行子类的逻辑,可以让子类处理的时候更加灵活,大家类比于我们案例中的制作纯豆浆,如果想要制作纯豆浆,那么子类重写方法,实现相应纯豆浆的业务逻辑

当然,ConfigurableApplicationContext 不止一个实现类,我们来看 GenericApplicationContext


1609759535325.png


这个类就对上面我们聊到的 refreshBeanFactory(),getBeanFactory() 都进行了实现

以及类 AbstractRefreshableApplicationContext,同样对 refreshBeanFactory(),getBeanFactory() 进行了实现


1609763028937.png


可能有人会说 AbstractRefreshableApplicationContext 是个抽象类啊,没关系,抽象类下面还有子类,我们在考虑设计模式的时候,主要学习它的一种思想,这个地方实现了不一定就是非抽象类,也可以是个抽象类,它的子类去进行实现

流程梳理

经过上面的分析,我们来进行梳理,首先,我们从接口 ConfigurableApplicationContext 进去,该接口声明了一个模板方法 refresh(),而 AbstractApplicationContext 对模板方法 refresh() 进行了实现,定义了运行的大致流程

同时,里面也存在钩子方法,钩子方法我们上面提到,如果子类没有覆盖,那默认无实现,如果子类重写了父类的方法,那么执行子类的逻辑,钩子方法的使用可以让子类处理的时候更加灵活,当然AbstractApplicationContext是一个抽象类,所以他下面还有具体的子类,下面我们再来画下相应的类图

类图演示

1609763204435.png


refreshBeanFactory(),getBeanFactory() 是抽象方法
postProcessBeanFactory(beanFactory),onRefresh()是钩子方法,他们都在模板方法 refresh() 中被调用了,相信分析到这,大家应该明白模板模式在 IOC源码中的应用了

下节预告

OK,到这里,模板模式的相关内容就结束了,下一节,我们开启命令模式的学习,希望大家能够一起坚持下去,真正有所收获,就像开篇那句话,我走的很慢,但是我从来不后退,哈哈,那我们下期见~

本文转载自: 掘金

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

熬夜肝了这份万字腾讯面试攻略

发表于 2021-01-06

本文一万字,以非腾讯在职人的身份,来聊腾讯面试的流程、攻略和建议,但愿能助有缘人。 标题涉及的范围很广,对多数面试腾讯的人而言,都有参考价值,看完本文之后,必有所得。退一步来讲,即便是准备面试其他公司,也有很多问题是相通的,亦可借鉴,有所广益。从毕业到现在,被别人面试过,也面试过别人,大大小小的面试,一两百次,也算久经沙场。

本文分为三个部分,如下:
Part1:面试流程
Part2:面试攻略
Part3:实用建议
话不多说,我们一起来感受吧!

Part1: 面试流程

1. 整体流程

腾讯的部门很多,岗位类型有别,职级差异较大,因此,每个面试者的面试流程肯定不尽相同,然而,很多流程基本通用,故值得一看。
郭靖(化名)是社招入职腾讯的,面试岗位是后台开发,最近跟郭靖吃了顿饭,聊了两个小时,获得许可后,便以郭靖的面试为例进行介绍。面试流程如下:

一图胜千言,要进腾讯并不那么容易。跟游戏升级一样,只有通过前一关,才有机会进入下一关。

微信部门的面试,要求更高,基本是2轮面委面,还有可能是3轮。当然,微信部门的钱也更多。

有的朋友问:组长是啥岗位?总监和总经理又是啥岗位?我们来简单介绍一下公开信息中的腾讯管理层级(副级别不单独列出):

看似层级不多,但要从下一层跨越到上一层,通常是五年十年之功,并不容易。可以算算,接近小马哥要多少年?

有的朋友还会问:面委是什么?面委就是面试委员会,会对面试者的能力进行再次考核,以便进行定岗定级。

通常来说,面试9级或9级以上的岗位,才需要面委。这里的9级是什么意思呢?且看公开信息中的腾讯技术通道职级体系(左旧右新):

一般来说,应届毕业生级别最低,组内骨干至少是9级工程师,组长至少是10、11级工程师,总监至少是12、13级工程师,依此类推。

接下来,我们来具体聊聊面试流程中的每个环节,仍以郭靖的社招面试流程为例,岗位是后台开发。

2. 简历筛选流程

郭靖第一步是要投递简历。筛选简历的是hr和业务部门。如果hr觉得郭靖简历不错,就会把简历给到业务部门。当然,业务部门也可以去人才库中找匹配的简历。

总之,简历的最终评判会落在业务部门身上。如果简历不通过,就没有下文了。如果简历通过,郭靖就有希望了。

3. 电话面流程

简历通过后,业务部门的组员或组长,会打电话给郭靖,简要了解基本情况,也可能初步问一下技术和岗位方面的问题。

极端情况下,如果电话中发现郭靖技术确实太水,啥都不会,尽是扯淡,那么就会让郭靖等后续通知,然后就没有然后了。

然而,通常情况下,既然简历通过筛选,那么还是值得给予面试机会,这个电话的主要目的,是了解基本情况,并约郭靖到腾讯面试。随后,郭靖收到一条短信,大概内容就是何时何地参加面试。就这样,郭靖需要亲自跑到腾讯去参加面试。聪明的郭靖,选择了不聪明的出行方式:开车去腾讯面试。

要知道,腾讯那地方实在是太堵了,而且还不好找停车位,郭靖急得大汗淋漓,嘴里骂骂咧咧。好在郭靖做事靠谱,提前预留了很多时间。

4. 组员面流程

组员面试,不是100%必须的环节,然而有时候组长太忙,就会让组员先面试,探探路。

郭靖屁颠屁颠地到了腾讯,参加面试,遇到的就是组员面试,这个组员,一般是组内的骨干员工,职级通常是9级,很可能就是之前电话约面试的那个人。

组员简单地接待了郭靖,顺便客气地倒上一杯水,然后递给郭靖一张试卷。咦?还有笔试环节呢!还要在纸上手写代码。

20分钟答完试卷后,那个组员问了郭靖一些基本的技术问题,组员面大概耗时30分钟。只要郭靖的技术不是太水,就有组长面的机会。

果然,在30分钟的组员面后,郭靖被告知即将进行组长面。

5. 组长面流程

前面说了,组员面不是100%存在的环节,如果没有组员面,那么组员面的内容会在组长面时体现。

组长拿着郭靖的笔试试卷,皱起了眉头,就笔试题目进行发问,问完之后,还问了别的技术问题。这个过程又持续了30分钟左右。

总共已经1个小时了,如果郭靖的技术不行,就不会去浪费总监的时间了。然后,会告诉郭靖回家等消息,那么,这种情况基本就表明面试不通过。

如果水平不错,组长就会让郭靖稍微等一下,去请总监来面试。通常来说,组长会提前跟总监打招呼,说今天有个叫郭靖的来面试,让总监预留一些时间。

果然,组长让郭靖在会议室等了几分钟,自己去跟总监沟通了一下。郭靖还是有几把刷子的,经历了1个小时左右的切磋较量后,顺利进入总监面。

6. 总监面流程

一般来说,总监差不多工作十年以上。根据面貌,就可以大概判断出来。郭靖,混迹IT江湖数年,显然是察言观色的高手。

总监对郭靖继续进行深度发问,聊了很多技术和项目问题。郭靖感觉难度明显加大了,不过还是能正常应对。最后,还要求在会议室的白板上,手写了两个题目的代码,并进行讲解。

整个总监面的时间接近1个小时,这说明总监觉得郭靖还不错,不然就会在20分钟内草草结束面试,互不耽误时间,终生不见。

无论是否通过总监面,当天的面试通常都会结束。不会进行总经理面,因为总经理时间很难约。

总监不可能跟总经理提前说:今天有个叫郭靖的人来面试,如果通过,我再给您面试。

这显然不合理,总经理那么忙,他需要知道的是确定的消息,而不是等待郭靖是否通过的消息。

总之,总监面后,无论是否通过,郭靖都要回家等消息。如果不通过,那就没啥好说的。

回家后,啥消息都没有。一天、两天、三天都是如此。郭靖有点着急了,看来是没戏了。

然而,一周后,一个电话打破了宁静。郭靖接到电话后,是个MM的声音,声音很甜。郭靖有点紧张和激动,心想,终于到hr面了,offer有希望了。

做梦!还差得远呢!这个MM可不是腾讯hr, 而是部门的秘书, 她会根据总经理的时间,跟郭靖预约面试时间。

随后,郭靖收到一条短信,大概内容就是何时何地参加GM面试。GM就是General Manager,翻译过来就是总经理。

郭靖的心情,由紧张激动,变成了一点点落寞,原来面试还没有结束。所以,郭靖还得再跑一次腾讯,去现场参加总经理面。

7. 总经理面流程

郭靖上次是开车去的,结果到处找停车位,还差点迟到。这次郭靖变聪明了,直接打车去。

总经理面,是最重要的一轮面试,在很大程度上,决定了郭靖能否拿到最终的offer.

秘书MM带着郭靖来到总经理办公室旁,郭靖觉得跟以前不一样了。嗯,单独的一间办公室。

正在思索间,里面出来一个人,我们姑且称他为杨康。郭靖算是明白了,今天是大家一起PK来了。

总经理问了很多有深度的问题,也问了一些宏观的问题,还要求在草稿纸上写了两个题目的程序。这有点出乎郭靖的预料,没想到总经理也这么懂技术。

随后,总经理问了郭靖的当前基本工资和年终奖情况,就让郭靖回家等待消息。带着不安的情绪,郭靖回家了。

一天、两天、三天,没有任何消息,郭靖等得有点着急了。终于,在一个星期后,郭靖接到了电话。电话的那头是另一个MM的声音,声音没上次甜。郭靖内心有点激动,以为到了hr面试的环节,终于快要大功告成了。

天真!有点天真!对方MM确实是腾讯hr, 但郭靖被告知还要参加面委会面试。郭靖已经两次亲自跑到腾讯去了,这次的面委面,采用视频面的方式,这倒让郭靖松了一口气。

8. 面委面流程

郭靖按要求接入视频,参加面试。面委面试官继续对技术和项目进行深度发问,在系统方案设计方面的考查力度明显加大。

面委面的目的,是对人才进行再次把关,顺便进行职级定级。一般来说,面试9级以下的岗位,是不需要面委面的。

面委面试完毕后,断掉视频,郭靖长叹了一口气。一路走来,并不容易。面试的严格程度,让人窒息。

郭靖不知道这次面试的结果,也没有最初那份兴奋了,因为,有点累了,真的有点累了。

一天、两天、三天还是没有消息,郭靖也不着急了,反正不期待,就不会失望,自然也不会着急。

一个星期后,电话再次响起,对方还是MM, 郭靖一向会察言观色,一下就听出了是上次那个hr MM.

郭靖知道,这应该就是正式的hr面试了。这次,郭靖对了。终于等到你,还好没放弃,郭靖牢记着张靓颖的鼓励。

9. hr面流程

腾讯招人的成本是非常大的,前面那么多流程,郭靖都成功突围。所以,只要郭靖情商不是太差,通过hr面试是没有问题的。

hr面试,问的就是一些基本信息和常规问题。说白了,hr面试,就是一个情商面试。关于hr面试的具体详情,会在后面的攻略部分详细阐述。

谈钱不伤感情,hr自然是要跟郭靖谈钱的。基本的一番了解和沟通之后,郭靖获得了hr的口头offer,也知道自己能拿多少钱了。hr补充说,后续会有电子offer发到邮箱。

hr为了吸引郭靖加入腾讯,开始机关枪式地介绍腾讯的各种福利待遇,大概持续了3分钟,比如年假、年终奖、社保、五险一金、商业险、团建、文娱活动、免费餐券、晋升、股票、无息贷款购房等众多福利。

郭靖听得有点晕乎,倒也不觉得烦,只是不停助谈地说:嗯,好的,明白,了解,谢谢,听说过,嗯,好的,知道,嗯,OK,了解。

hr面试结束后,郭靖心无所恋,下班后就开始玩王者荣耀,始终选择阿轲,向敌方发起攻击,然后一觉睡到大清早。

10. 电子offer流程

第二天,郭靖如期收到邮件,是正式电子offer,内容包括钱和岗位,具体信息,就不说了。此刻,郭靖异常平静。

为了更方便地沟通,hr加了郭靖的微信。接下来,hr让郭靖提供薪资证明和背景调查的基本信息。

郭靖需要提供三个人的电话:郭靖的直接领导(洪七公),郭靖的同事(黄蓉),郭靖部门的hr(华筝)。

腾讯拿到这些这些信息后,会委托第三方专门负责背景调查的公司,对郭靖进行背景调查。

同时,郭靖也跟领导洪七公进行了真诚沟通,表明自己要离职,希望去腾讯发展。洪七公是舍不得郭靖走的,但没办法,郭靖要走。

这里要强调一下,只有郭靖愿意并同意加入腾讯,且同意开始接受背景调查,腾讯和背景调查公司才能发起对郭靖进行调查。

如果郭靖还没有拿到电子offer, 或者郭靖最终不愿意加入腾讯,那么腾讯和背景调查公司就无权调查郭靖。

这很好理解,如果郭靖最终不想离职,还被调查,还打电话给他的领导洪七公,那洪七公就知道郭靖至少有想离开的想法,岂不是害了郭靖吗?

腾讯,当然是有节操的,流程也是规范的,不会犯这种低级、愚蠢且恶意的错误。大家可以放心地相信腾讯。

可是,其他某些公司,就不一定了。在面试流程还在进行时,在还没有发正式offer的情况下,用各种方式私下偷偷无耻地打听面试者的情况和表现。这种垃圾公司,不去也罢。

11. 背景调查流程

背景调查,是现在很多公司采取的策略,主要是为了保证人才质量,避免弄虚作假,降低企业招人风险。

背景调查,主要考察人品,那些还想在简历中写虚假信息的人,那些虚报当前工资的人,要注意啦。

不要有侥幸心理,背景调查公司,是干这个吃饭的,要调查郭靖的背景资料,方法多得很。串通造假,难度太大,也不正直,自己想想。

背景调查公司调查完基本信息后,开始给郭靖的直接领导(洪七公)、郭靖的同事(黄蓉)和郭靖所在部门的hr(华筝)打电话,询问郭靖的基本情况,了解郭靖的人品和平时表现。

当然,郭靖身正不怕影子斜,一切信息真实,不担心背景调查。对郭靖而言,背景调查无非就是个流程而已。

关于背景调查的具体内容和攻略,我们会在后面详细介绍。

Part2: 面试攻略

1. 简历攻略

简历的投递,可以通过腾讯招聘网站,也可以找腾讯的朋友内推。要说明的是,内推不会增加通过几率,更不会减少面试流程。

简历的基本原则是信息真实,少数求职者可能会动歪心思,比如故意延长或缩短某段工作经历,或是对学历进行美容。真别这样,一旦被发现,不只是尴尬。

简历的内容要展示自己才能,让面试官看到郭靖的厉害之处,要多花点心思来写,突出亮点,吸引面试官。

对郭靖来说,简历所写内容,必须了如指掌,因为简历上的任何东西,都可能被问到。有些简历上写研究生期间发表了几篇论文,结果面试时被问论文的内容和原理,答不出来,真让人怀疑。

简历要好好写,用心写,真实地写,而且要对简历上的每个内容都了如指掌。郭靖自己的经历,郭靖怎么能不清楚呢?

如果学校不错,建议简历文件名采用如下格式:郭靖_XX大学_本科_应聘腾讯后台开发岗位.pdf,看到这简历名,就有想看看的冲动。

如果有github账号且写得很好,或者有开源作品,也可以放到简历中,让人看到郭靖对技术的热爱和追求,这是加分项。

简历的其他注意点也很多,如下4点,不接受反驳:
a. 简历只能是PDF格式。
b. 简历不能是压缩文件。
c. 简历中不能有错别字。
d. 简历最多不超过2页。
总之,自己写完简历后,要好好检查和修改,多站在他人的角度审视自己的简历,也可以让自己信赖的朋友帮修改。

2. 技术面攻略

鸡汤大师说,如果看到地上有纸屑,要去捡起来,然后会被录用。这有点反智。真的,别去捡,没用的!要搞清楚,这是技术面,需要真刀真枪的本领。

电话面、组员面、组长面、总监面、总经理面、面委面,我们不逐一介绍攻略。这些流程,都是以考查技术为主,故统一介绍。

技术面涉及的内容实在太多,无法完全穷尽。所以,本文只聊面试前的准备方向,以及面试现场的注意事项。

还是以社招后台开发为例,在面试之前,郭靖去腾讯招聘网站看了相关岗位的要求,就知道要准备的技术内容和方向了。要准备的技术内容,纷繁驳杂,大致如下:

  1. Java基础语法
  2. 面向对象
  3. J2SE
  4. 数据库
  5. JDBC编程
  6. Web前端基础
  7. Servlet+JSP
  8. SSM
  9. SpringBoot+SpringCloud
  10. Oracle

在面试现场,基本的事情还是要注意。少数面试者匆匆赶来,满头大汗,一身汗味,显然不好。少数面试者,觉得自己牛气冲天,在现场和面试官争论不休,自然也不可取。

一些面试者在现场遇到难题后,唉声叹气,变得急躁,这是非常不成熟的表现。也有一些面试者遇到困难后,不加思考,非常轻率,直接放弃,这怎么能打动和说服面试官呢?

没有万能人,大家都有知识盲区,遇到困难太正常了。遇到困难还能现场解决,才能体现出自身价值。即便无法解决问题,也可尝试说出自己的思路,而一个人的思路,往往能反映出这个人的心智模式和未来潜力。

总之,技术准备,功在平时。还是应该修炼基本功,有了这份基本功,就不怕变化,毕竟万变不离其宗。至于该怎么提升基本功,建议多学习、多思考、多实践、多总结。

世上没有银弹,也没有万能捷径,不要浮躁地以为,临时抱佛脚,刷几个题目,就能通过腾讯面试,没那么简单的。功夫修炼,千朝万夕。用兵一时,养兵千日。

3. hr面试攻略

hr面试考察的重点是情商,包括性格和沟通能力。掌握三个原则,万问可破:

原则一:强调自己是积极进取的人,乐于挑战自己,有更高的追求,不安于现状,对未来有清晰的思考和规划,并在付诸行动。

原则二:要以腾讯利益为目标,强调自己能为腾讯产品做出什么贡献,而不是强调自身利益和感受。

原则三:面对问题,不要直接用yes或no一棍子打死,而应该从多角度看问题,体现出严谨的逻辑性和条理性,辩证思维。

郭靖去面试,本质上是和腾讯做利益交换。郭靖是来向腾讯要钱的,自然要多体现自己能给腾讯带来什么利益。在牢记上述三个原则的前提下,我们来看hr常问的20个问题。

问题一:你为什么离职?
错误答案1:现在领导傻逼。
错误答案2:现在加班太多。
错误答案3:现在钱太少了。
错误答案4:现在部门斗争严重。
错误答案5:腾讯的福利待遇好。
错误答案6:在腾讯工作有光环。
错误答案7:想工作生活平衡一下。
错误答案8:师兄在腾讯,说腾讯好。
错误答案9:老婆在腾讯,我也想来。
错误答案10:我是一个孝子,要养父母,需要更多的钱。

点评:这个问题,绝对不是给机会让你吐槽现状或者谈主观感受。离职的原因,要强调自己有进取心,想去更大的平台,表达出看好腾讯的发展,想加入腾讯,共同成长。

问题二:我们为什么要招你?
错误答案1:因为你们缺人。
错误答案2:你们自己都不知道为什么要招我吗?
错误答案3:我之前干过类似工作,一定可以胜任。
错误答案4:因为我很优秀,你们需要我这样优秀的人。错误答案5:因为你们业务发展很快,要更多的人力投入。

点评:这个问题,部分人读不懂题意。腾讯要招聘你的原因,一定是因为你能给腾讯带来价值。价值怎么体现和证明?不是吹嘘自己有多牛逼,而是要通过以往的事例,来说明自己有价值。

问题三:你怎么看待加班?
错误答案1:我讨厌加班。
错误答案2:我喜欢加班。
错误答案3:看心情来确定。
错误答案4:有加班费吗?
错误答案5:有钱就加,没钱不加。
错误答案6:有事就加,没事不加。

点评:看问题要看到本质,别直接回答yes或no. 也别傻乎乎太强调自己的感受,要牢记腾讯的利益。这个问题很好回答。

问题四:你对腾讯有什么了解?
错误答案1:没了解过。
错误答案2:了解一些。
错误答案3:了解不多。
错误答案4:腾讯挺好的,所以我来面试。
错误答案5:腾讯是个大公司,福利待遇好。

点评:如果对目标公司和产品不了解,那就显得面试动机很不真诚。平时多了解一下腾讯公司和腾讯产品,不至于面试时把天聊死。

问题五:你职业发展规划是怎样的?
错误答案1:嗯,啊,哦…
错误答案2:这个问题我没有考虑过。
错误答案3:我是脚踏实地的人,走一步,看一步。
错误答案4:我不关心这些虚无的问题,我只想把当前的事情做好,一步一个脚印。
错误答案5:我想好了,十年后要做市级专家, 二十年后要做省级专家,三十年后要做国家级专家。
点评:如果没有思考过职业规划,说明不够用心,对自己不够负责。也别跟某某一样,海阔天空,畅想未来三十年。

问题六:最打动你的一件事是什么?
点评:别扯那些潸然泪下的感人故事。可以举一个完成了不可能完成任务的事例,深受打动,从中学到什么,对后续工作有什么帮助。

问题七:你遇到的最大困难是什么?
点评:不要诉苦和煽情,要强调遇到困难,并且千方百计解决了,学到了什么,对后续工作有什么帮助。

问题八:工作中遇到不喜欢的人怎么办?
点评:千万别说不喜欢就远离,还是要以工作和项目为重,合作完成共同目标,时刻考虑腾讯利益。

问题九:你有在应聘其他公司吗?
点评:如实陈述,如果同时在面试阿里、头条,如实说出来,也让腾讯hr知道,你是有很多选择余地的。

问题十:你喜欢怎样的上级?
点评:上级岂要看你喜欢与不喜欢?任何上级都要喜欢。一起工作,共同目的是做好产品,自己要去适应上级。回答时可以说喜欢有责任心、有目标感且为团队着想的上级。

问题十一:你期望在工作中得到什么?
点评:千万别说钱。要说自己期望得到展示能力的平台和提升自己的机会,并和公司一起成长。要牢记自己能给腾讯带来什么利益,别傻乎乎地强调自己要获得什么回报,尤其不要提钱。

问题十二:你觉得腾讯哪个产品有什么可以改进的地方?
点评:别说一切都好无需改进,这显得很没有思考且不认真。也别瞎抱怨一通,只能轻微地说有哪些地方不足,并且一定要想出改进措施。任何没有改进措施的意见,都是添乱。

问题十三:你有什么缺点?
点评:不能说自己没有缺点 ,也不能把自己的缺点无限放大,如果说自己懒惰、有拖延症,这对面试有什么好处?还有人说自己有完美主义情结,这太俗套了。可以考虑说一些人性通病,比如自己工作有时比较着急,但要强调都是为了工作项目考虑,并说自己在逐渐改进。

问题十四:你的短期目标是什么?
点评:千万别说短期目标是为了找一份工作,多么没意思的回答啊。可以说自己短期目标是学习一门新知识,希望尽快学会,并且应用到工作之中。

问题十五:如果成功应聘,你打算在腾讯做多久?
点评:如果回答1年,说明不稳定,何必要招你?如果回答20年,那就是乱扯。可以考虑一种循序渐进的方式来回答。方式多得很,也很好回答。

问题十六:谈一次失败的经历。
点评:别只说失败如何惨痛,要强调从失败中学到什么。而且,在挑选失败案例时,不要是那种损失很大的事例。假如,你让公司损失了1亿美金,也好意思说?可以选择那些损失不大但意义重大的失败经历。

问题十七:说一下你的家庭吧。
点评:别陷入温柔的陷阱,尽说家里几口人并如何和睦恩爱。这对面试没有帮助。可以考虑强调家庭教育,比如从小爸妈就教育自己要正直、积极乐观、团结进取,有责任心。你看,这和腾讯价值观又暗中结合起来了。

问题十八:你最欣赏谁?
点评:别说一些乌七八糟的明星。低俗!也别仅仅说一个名字就结束。要尽量选真正能打动自己的人物,并简要说明为什么欣赏他。

问题十九:你喜欢怎样的工作氛围?
点评:这不是你妈妈问你,所以别随心所欲敞开心扉。这是hr问你,是在面试,容不得你挑选那么多。所以,不要太强调自己的内心喜好。可以说自己喜欢有活力、有目标感、有责任感、有创造力的氛围。这种回答,没人能反驳,况且也和腾讯的氛围吻合呢。

问题二十:你为什要这么高的工资?能不能向下调整?
点评:千万别扯什么家里开支大,或者买房了,所以需要钱。也别说自己之前工资高,跳槽更要涨一波。搞清楚,你是要强调自己有什么能力和价值,配得起这么高的工资,配得起这个涨幅。至于预期工资是否可以向下调整,这是进入了讨价还价的环节。直接说可以就是在自降身价,直接说不可以就是在自断后路。前面点评那么多了,这个问题其实很好作答。

4. 接受电子offer攻略**

电子offer,就是正式的书面offer,这还要什么攻略呢?确实不需要,但也还是有些事情需要注意。

郭靖只有收到offer邮件才算数,才能开始跟自己的领导洪七公提离职,所以,这个时间节点要把控好。

收到电子offer,记得低调,千万别在公司炫耀,也别在网上忘乎所以。当然,郭靖这种职场老手,是不会犯这种低级错误的。接下来,就是背景调查了。

####5. 背景调查攻略
背景调查,其实谈不上什么攻略,姑且称之为注意事项吧。
首先,基本信息不能造假,这个很好理解,毋庸置疑,无需强调。毕竟,正直是腾讯的最重要价值观。
其次,郭靖在找同事帮做背调时,要尽量选择关系好且会说话的,比如黄蓉。如果郭靖选择欧阳克帮背调,可能招黑,百口难辩。
最后,在提离职后,进行工作交接务必尽心尽力,不能跟领导和同事把关系闹僵,背景调查会问这些东西。基本的职业道德,还是要有。

背景调查公司会通过很多方式,调查郭靖的基本信息。然后,也会打电话给郭靖的直接领导(洪七公)、郭靖的同事(黄蓉)和郭靖所在部门的hr(华筝).

那么,背景调查公司打电话给洪七公、黄蓉、华筝,都会问一些什么问题呢?其实,无非就是问郭靖的基本情况和表现,比如问郭靖所在部门,比如问郭靖的入职时间和离职时间,比如问郭靖的人品、平时表现和工作交接等问题。

洪七公作为领导,肯定不是第一次做背调,一般不会故意为难郭靖,所以基本会正常回答。华筝作为hr, 也会如实回答问题。黄蓉聪明伶俐,夸人的本领,不显山漏水,但句句话都是在用真实案例夸郭靖。

就这样,背景调查公司拿到了真实信息,很满意,并把调查结果反馈给腾讯。还是那句话,身正不怕影子歪,对郭靖而言,背景调查无非就是个流程。

有的朋友可能觉得,自己平时跟领导的关系不好,怕领导说坏话,想提供一个假领导的电话供背调,行不行呢?我的个人看法是,不建议。很容易穿帮,而且也确实不够诚实和正直。即使跟领导关系不好,也要想办法。

大家都是打工的,平时就要跟同事和领导进行良好合作,为了共同的目标而合作,弄僵关系只能说明情商不高。背景调查这段时间,是郭靖在公司的最后一段时间了,要好好珍惜。坚持做好工作,做好交接,多写文档,好好表现,贡献最后一份热量。既然爱过,就和平分手,给彼此留下最美的回忆。切记保持低调,不要跟无关人透漏即将离职,更不要透露去腾讯后的福利待遇,免得人心浮动,惹怒领导。有时,做孙子,甚至是装孙子,是一门学问。互联网圈子很小,口碑和人品很重要。

万事留一线,江湖好相见。轻轻地离开,不带走一片云彩。

Part3: 实用建议

终于,快接近尾声了,感谢你有耐心看到这里。最后,聊一下自己的个人看法,也许不成熟,姑且称之为建议吧。

1. 充分准备

鲁迅先生曾经说过,如果准备得不成功,那就准备失败吧。鲁迅先生,是不会骗人的。
如果轰炸一个地方,需要投放10个炸弹,那你就准备投放20个,这叫饱和攻击,做好双倍的准备。
如果想去腾讯,那么建议珍惜面试机会。可以提前找关系比较好的朋友帮模拟面试演练,查缺补漏。也可以先找一些小公司练练手,找找面试感觉,何尝不可?

2. 不抛弃 不放弃

如果面试不顺利,简历会被放回到人才库,其他部门仍可以让你去继续面试,只是还得从头再面。总之,机会多多,别灰心丧气。铩羽而归,总结反思,查缺补漏,继续尝试。
在这一点上,腾讯做得挺好的,是才就用,不管你是第几次面试。而其他公司,很可能是如果第一次面试不通过,要等到第二年才有资格再次面试。
退一步来讲,万一最终还是没有面试成功,也正常,并欣然接受。腾讯不应该是你唯一的目标,路子多得很,好公司多得很,失之东隅,收之桑榆。

3. 务实前行 持续努力

如果最终顺利进入腾讯,开心一下,可以理解,但也不能太得意忘形。凡尔赛文学,不能太沉迷。
路还很长,要提升的地方也很多。站在一个新的平台上,高度更高了,但要区分自身价值和平台溢价,而不是盲目自嗨,错把平台价值当做自身价值。
谦卑心态,务实前行,低调发育,别浪。更厉害且更低调的人,腾讯有的是!得多向他们学习。

Part4: 最后的话

不是每次呼唤都有回响,不是每次展翅都能飞翔。但只要呼唤过、展翅过,就不会有遗憾和后悔。
如果面试顺利,那么祝贺你,如果面试暂时不顺利,那么祝福你。人生本来就是各种经历和体验,如果暂时没有得到想要的,说不定会遇到更好的。点关注,不迷路,关注程序员曾曾,每天分享不同的Java基础知识,如果想要知道更多的有关Java的知识以及面试我这边整理了一个我自己的GitHub仓库:Java工程师修炼手册,大家如果有需要可以自行查看

本文转载自: 掘金

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

Android Native Scudo内存分配器

发表于 2021-01-06

本文分析基于Android R

前言

在Android世界中,Native堆内存的分配通常由malloc负责。即便是面向对象的new,其底层也是malloc。那当我们调用malloc/free来分配/释放堆内存时,内存到底从哪里来,又归到何处去?以及这个过程是否会涉及内存的分割和碎片的合并?负责这所有工作的,我们称它为 Native Memory Allocator。

早期Android中使用jemalloc作为默认的allocator,但是从R开始,Scudo替代jemalloc成为了non-svelte configuration模式下默认的allocator。svelte模式下默认的allocator依然是jemalloc。

svelte的含义为苗条,意味着内存更加紧凑(通常是RAM较小的设备)。而Scudo不具有内存分割和碎片合并的功能,对内存的使用也更加宽松,因此在R上并没有成为svelte模式下默认的allocator。不过谷歌在源码中已经增加了scudo_svelte的实现,预计在未来的版本中会全面替代jemalloc。

Scudo这个名字源自Escudo,后者在西班牙语和葡萄牙语中表示”盾牌“。这个含义也反映了Scudo最大的特点:增强内存的安全性,抵御一些非法攻击。它的本意是为了安全,而检测出内存错误只是其附带的属性,并不是它实现的目标。因此如果要选择内存错误的调试工具,首选还是HWASAN。

从jemalloc切换到Scudo,其实是硬件资源提升带来的福利。随着64位机器和大RAM的普及,虚拟内存和物理内存的瓶颈都在不断放宽,因此给了系统更多的选择,可以在性能合理的范围内兼顾其他特性。在所有安全性问题中,内存漏洞发生的入侵占到了半数以上,因此如果能在Allocator中抵御入侵,那将极大地降低安全问题的数量。需要注意的是,单从性能角度分析Scudo未必超过jemalloc,虽然它的分配策略更加简化,但为了安全性所必须分配的chunk header会使其丧失一些性能。

目录

  1. 概述

Android中有超过半数的漏洞都来源于内存错误,因此减少非法攻击的重点就要放在对内存的防御上。常见的内存错误有heap-overflow,double-free,use-after-free等。针对这些错误Scudo主要采用了两种手段进行防御,一种是随机化,让攻击者无法定向攻击某些内存块;另一种是给每个内存块增加chunk header,在分配/释放等操作时检查header的正确性,防止非法的内存篡改。Chunk header的大小为8字节,但由于64位Scudo需要16字节对齐,所以它实际占用的空间为16字节。

Scudo中的分配器有两种类型,当需求小于256K时使用Primary Allocator,大于256K时使用Secondary Allocator。

  1. 具体实现

2.1 Primary Allocator

Primary Allocator中内存会分成不同的区域,每块区域只能分配出固定空间,譬如区域1只能分配32bytes(包含header),区域2只能分配48bytes。当我们分配小内存时,首先会检查最合适区域中是否有空闲位置,如果没有,则会去高一级区域中分配。当最高一级(38)区域中也没有合适的空闲空间时,这时将会使用Secondary Allocator进行分配。

2.1.1 内存分布

对于64位Android R而言,Primary Allocator会分成39个区域等级(Class)。Class 0用于存放内存管理的元数据,Class 138用于满足用户空间的分配需求。每一块区域为256M,其头部会随机空缺出016页以降低定向攻击的风险。以下为每块区域具体分配的大小。

Class ID 1 2 3 4 5 6 7 8 9 10
原始大小(bytes) 32 48 64 80 96 112 144 176 192 224
去除Chunk Header大小 16 32 48 64 80 96 128 160 176 208
Class ID 11 12 13 14 15 16 17 18 19 20
原始大小(bytes) 288 352 448 592 800 1104 1648 2096 2576 3120
去除Chunk Header大小 272 336 432 576 784 1088 1632 2080 2560 3104
Class ID 21 22 23 24 25 26 27 28 29
原始大小(bytes) 4112 4624 7120 8720 11664 14224 16400 18448 23056
去除Chunk Header大小 4096 4608 7104 8704 11648 14208 16K 18K 22.5K
Class ID 30 31 32 33 34 35 36 37 38
原始大小(bytes) 29456 33296 65552 98320 131088 163856 196624 229392 262160
去除Chunk Header大小 28.75K 32.5K 64K 96K 128K 160K 192K 224K 256K

在Scudo初始化阶段,系统会mmap出256M*39大小的空间。但由于声明了PORT_NONE权限,所以此时还无法访问。等到具体分配产生时,系统才会将部分区域改为PROT_READ|PROT_WRITE。

2.1.2 分配策略

在分配过程中需要重点考虑两个问题:

  1. 哪一个Class的那一块内存(chunk)可用于分配?
  2. 当多线程并发分配时,如何处理竞争关系?

一个Region中已分配和未分配的内存块,经过一段时间后必定参差交错。因此为了追踪未分配的内存块,Allocator中需要有指针来指向那些内存块。这些指针的数量最好可以按需增长,因此可以把它们设计成批量模式(也即源码中的TransferBatch),一个Batch中包含14个指针,不够时再创建新的Batch对象。这些Batch对象构建成单向链表,也即Region的FreeList。

TSD(Thread Specific Data)机制

如果是单线程运行,上面的设计已经足够了。但对于并发分配而言,如何解决竞争问题将显得十分重要。最简单的做法是加锁,但加锁会牺牲性能,对于“分配”这类高频操作显然不是最优解。既然每个Batch包含的指针不同,那让并发线程使用不同的Batch对象是不是就可以解决问题呢?这种方式就是下图中的TSD(Thread Specific Data)机制。

不过为每个线程都分配一个仅供自己使用的Batch是不经济的(A线程Batch中空间的内存无法被B线程使用),尤其是有些进程创建了一百多个线程。因此上述的TSD机制还增加了TSD pool来控制私有Batch的数量,并且采用轮询制保证每个线程尽快地完成分配。但这种方式在并发压力过大时依然免不了等待,它其实是平衡了性能和内存占用后做出的选择。

对于64位的Android R而言,TSD pool中只有两个TSD对象。每一个TSD对象含有一个SizeClassAllocatorLocalCache对象,其内部为每个class region都创建了PerClass对象。通过PerClass的chunks数组,便可以找到空闲chunk的地址。

上述的Round-robin(轮询)算法也十分有趣。它会在持有TSD对象时记录单调时间,如果当前两个TSD对象正在被其他线程使用,该线程会等待TSD持有时间更久的那个对象。因为持有更久,代表有更大可能被提早释放。

Cache机制

Primary Allocator中使用了Cache机制来加速分配。当一个线程需要分配内存时,它会通过TSD对象指向的Chunks数组来寻找合适的空闲内存。但Chunks数组的大小是有限的(28),当它们用完时就需要补充弹药了。补充弹药不需要操作class region来扩张空闲区域,而是从region的FreeList(FreeList的详细数据存放于class 0 region)中直接获取。当FreeList中的空闲对象不够时,才会最终扩张class region的空闲区域。

这个过程有点像CPU的多级缓存,详见下图。

2.1.3 释放策略

当PerClass中的Chunks数组全都是空闲指针时,需要将其中一半(14)指针退还给region的FreeList。当region的FreeList中TransferBatch过多时,需要将空闲的页解除物理映射。但对于分配内存较小的region(譬如class 1~6),由于一页中chunk数量过多,很难凑齐一页中全是free chunk的情况,因此干脆不进行物理映射解除的动作。

对需要解除物理映射的region,其采用的是madvise系统调用。

external/scudo/standalone/linux.cpp

1
2
3
4
5
6
c++复制代码86 void releasePagesToOS(uptr BaseAddress, uptr Offset, uptr Size,
87 UNUSED MapPlatformData *Data) {
88 void *Addr = reinterpret_cast<void *>(BaseAddress + Offset);
89 while (madvise(Addr, Size, MADV_DONTNEED) == -1 && errno == EAGAIN) {
90 }
91 }

通过MADV_DONTNEED参数告知系统这些物理页可以被回收。

MADV_DONTNEED

Do not expect access in the near future. (For the time being, the application is finished with the given range, so the kernel can free resources associated with it.)

2.2 Secondary Allocator

Secondary Allocator主要用于大内存的分配(>256K),直接采用mmap分配出一块新的VMA(Virtual Memory Area)。但是为了效率,它也设计了一个Cache。其内部最多可以缓存32个不超过2M的VMA。

没有放进缓存的VMA在释放时直接采用unmap的方式。

external/scudo/standalone/secondary.h

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
c++复制代码164  void empty() {
165 struct {
166 void *MapBase;
167 uptr MapSize;
168 MapPlatformData Data;
169 } MapInfo[MaxEntriesCount];
170 uptr N = 0;
171 {
172 ScopedLock L(Mutex);
173 for (uptr I = 0; I < MaxEntriesCount; I++) {
174 if (!Entries[I].Block)
175 continue;
176 MapInfo[N].MapBase = reinterpret_cast<void *>(Entries[I].MapBase);
177 MapInfo[N].MapSize = Entries[I].MapSize;
178 MapInfo[N].Data = Entries[I].Data;
179 Entries[I].Block = 0;
180 N++;
181 }
182 EntriesCount = 0;
183 IsFullEvents = 0;
184 }
185 for (uptr I = 0; I < N; I++)
186 unmap(MapInfo[I].MapBase, MapInfo[I].MapSize, UNMAP_ALL,
187 &MapInfo[I].Data);
188 }

好奇的朋友可能会问道:为什么Primary Allocator在释放物理内存时采用的是madvise,而这里采用的是unmap?

这是因为madvise只解除了虚拟地址和物理空间的映射关系,但并没有释放虚拟空间(虚拟地址无法被其他人使用)。而unmap不仅解除了映射关系,也释放了虚拟空间。当madvise解除映射关系以后,这块内存依旧可以访问,但获取到的将是一页新的物理内存,而unmap的区域再次访问时将会出错。Primary Allocator是在一整块VMA上自行进行管理,因此无需释放虚拟空间,而Secondary Allocator则是为每一次分配都单独创建VMA,不做管理。

2.3 检测机制

为了保证每一个chunk区域的完整性,Scudo增加了header检测机制,一方面可以检测内存踩踏和多次释放,另一方面也阻止了野指针的访问。

external/scudo/standalone/chunk.h

1
2
3
4
5
6
7
8
c++复制代码65 struct UnpackedHeader {
66 uptr ClassId : 8;
67 u8 State : 2; // Available = 0, Allocated = 1, Quarantined = 2
68 u8 Origin : 2; // Origin表示通过哪种方式发生的分配,譬如是new,malloc还是realloc
69 uptr SizeOrUnusedBytes : 20; //这个chunk中实际被使用的大小
70 uptr Offset : 16;
71 uptr Checksum : 16; //用于检测header是否被破坏
72 };

上面是Chunk header的详细信息,后面的数字代表每个字段所占用的bit,加起来为64bits,也即8字节。Checksum字段用于检测chunk的完整性,其生成过程如下所示。其中校验和用的是较为简单的CRC算法,这主要是基于性能的考虑。但是目前这种校验方式已经有了破解手段,估计谷歌后续会升级这块的算法吧。

在一个chunk释放时,该地址需要经过重重检测,以保证它在使用过程中是未经破坏的。下面按时间顺序列举出一个chunk需要经过的检测。

external/scudo/standalone/combined.h

1
2
c++复制代码423     if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(Ptr), MinAlignment)))
424 reportMisalignedPointer(AllocatorAction::Deallocating, Ptr);
  • 地址必须16字节对齐,如果是一个未经对齐的long型数字被错误当成了指针,这里就可以检测出来。

external/scudo/standalone/chunk.h

1
2
3
c++复制代码124   if (UNLIKELY(NewUnpackedHeader->Checksum !=
125 computeHeaderChecksum(Cookie, Ptr, NewUnpackedHeader)))
126 reportHeaderCorruption(const_cast<void *>(Ptr));
  • Checksum数字在deallocate时会再计算一遍,和Header中保存的checksum进行比较。如果二者不相等,则意味着header被破坏,或者它根本不是一个header。

external/scudo/standalone/combined.h

1
2
c++复制代码431     if (UNLIKELY(Header.State != Chunk::State::Allocated))
432 reportInvalidChunkState(AllocatorAction::Deallocating, Ptr);
  • 如果header的state不为Allocated,表明此时不应该释放这块内存,这很有可能是一个double-free的错误。

external/scudo/standalone/combined.h

1
2
3
4
5
6
7
8
9
c++复制代码433     if (Options.DeallocTypeMismatch) {
434 if (Header.Origin != Origin) {
435 // With the exception of memalign'd chunks, that can be still be free'd.
436 if (UNLIKELY(Header.Origin != Chunk::Origin::Memalign ||
437 Origin != Chunk::Origin::Malloc))
438 reportDeallocTypeMismatch(AllocatorAction::Deallocating, Ptr,
439 Header.Origin, Origin);
440 }
441 }
  • 如果allocate时的方法和deallocate的方法不匹配,也会报错(前提是打开DeallocTypeMismatch选项)。譬如一块内存通过new申请,却使用free去释放(应该使用delete)。

external/scudo/standalone/combined.h

1
2
3
4
C++复制代码444     if (DeleteSize && Options.DeleteSizeMismatch) {
445 if (UNLIKELY(DeleteSize != Size))
446 reportDeleteSizeMismatch(Ptr, DeleteSize, Size);
447 }
  • 如果deallocate时的size和chunk中的size不相等,报错。

以下是Android Scudo官方文档中对于典型错误信息的分析,其中不少都可以和上述的检测环节关联起来。

source.android.com/devices/tec…

  • corrupted chunk header:区块头的校验和验证失败。可能原因有二:区块头被部分或全部覆盖,也可能是传递给函数的指针不是区块。
  • race on chunk header:两个不同的线程会同时尝试操控同一区块头。这种症状通常是在对该区块执行操作时出现争用情况或通常未进行锁定造成的。
  • invalid chunk state:对于指定操作,区块未处于预期状态,例如,在尝试释放区块时其处于未分配状态,或者在尝试回收区块时其未处于隔离状态。双重释放是造成此错误的典型原因。
  • misaligned pointer:强制执行基本对齐要求:32 位平台上为 8 个字节,64 位平台上为 16 个字节。如果传递给函数的指针不适合这些函数,传递给其中一个函数的指针就不会对齐。
  • allocation type mismatch:启用此选项后,在区块上调用的取消分配函数必须与用于分配区块而调用的函数类型一致。类型不一致会引发安全问题。
  • invalid sized delete:如果使用的是符合 C++14 标准的删除运算符,在启用可选检查之后,取消分配区块时传递的大小与分配区块时请求的大小会出现不一致的情况。这通常是由于编译器出现问题或是对要取消分配的对象产生了类型混淆。
  • RSS limit exhausted:已超出选择性指定的 RSS 大小上限。
  1. 总结

本文重点阐述了Scudo的核心原理。

和以往的写作方式不同,这一次我尽力删减了繁杂的源码解读,而是将核心概念绘制成图。通过图片理解,我想可能会更加清楚。此外,关于Scudo的配置和使用并不是本文的重点,因为官方文档中写的已经足够清楚,我就没有必要再狗尾续貂了。

本文转载自: 掘金

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

centos 安装k8s集群 1 服务器 2 安装docke

发表于 2021-01-06

1 服务器

三台linux centos服务器

2 安装docker环境

2.1 更新yum

yum update

2.2 设置yum源

1
arduino复制代码*Sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo*

image

可以查看所有仓库中所有docker版本,并选择特定版本安装:

1
bash复制代码yum list docker-ce --showduplicates | sort -r

2.3 安装

1
复制代码sudo yum install docker-ce-18.06.0.ce

2.4 启动、设置开启开机启动

1
sql复制代码sudo systemctl start docker
1
bash复制代码sudo systemctl enable docker

验证安装是否成功(有client和service两部分表示docker安装启动都成功了):

1
复制代码docker version

3 安装k8s集群

3.1 关闭防火墙

1
2
3
arduino复制代码systemctl stop firewalld

systemctl disable firewalld

3.2 关闭selinux

1
2
3
bash复制代码setenforce 0  # 临时关闭

sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config # 永久关闭

1.3.3 关闭swap

1
2
3
4
5
bash复制代码swapoff -a    # 临时关闭;关闭swap主要是为了性能考虑

free            # 可以通过这个命令查看swap是否关闭了

sed -ri 's/.*swap.*/#&/' /etc/fstab  # 永久关闭

3.4 添加主机名与IP对应的关系

1
shell复制代码$ vim /etc/hosts

添加如下内容:

1
2
3
4
5
复制代码192.168.190.128 k8s-master

192.168.190.129 k8s-node1

192.168.190.130 k8s-node2

3.5 将桥接的IPV4流量传递到iptables 的链

1
2
3
4
5
6
7
bash复制代码$ cat > /etc/sysctl.d/k8s.conf << EOF

net.bridge.bridge-nf-call-ip6tables = 1

net.bridge.bridge-nf-call-iptables = 1

EOF
1
css复制代码$ sysctl --system

3.6 添加阿里云YUM软件源

1
2
3
4
5
6
7
8
9
10
11
12
13
ini复制代码cat > /etc/yum.repos.d/kubernetes.repo << EOF

[k8s]

name=k8s

enabled=1

gpgcheck=0

baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/

EOF

3.7 安装kubeadm,kubelet和kubectl

1
2
3
4
5
bash复制代码kubelet # 运行在 Cluster 所有节点上,负责启动 Pod 和容器。

kubeadm # 用于初始化 Cluster。

kubectl # 是 Kubernetes 命令行工具。通过 kubectl 可以部署和管理应用,查看各种资源,创建、删除和更新各种组件。

在部署kubernetes时,要求master node和worker node上的版本保持一致,否则会出现版本不匹配导致奇怪的问题出现。本文将介绍如何在CentOS系统上,使用yum安装指定版本的Kubernetes。

我们需要安装指定版本的kubernetes。那么如何做呢?在进行yum安装时,可以使用下列的格式来进行安装:

1
2
3
xml复制代码yum install -y kubelet-<version> kubectl-<version> kubeadm-<version>

yum install kubelet-1.19.6 kubeadm-1.19.6 kubectl-1.19.6 -y

输出

1
2
3
4
5
makefile复制代码Installed:

  kubeadm.x86_64 0:1.20.1-0  kubectl.x86_64 0:1.20.1-0 

  kubelet.x86_64 0:1.20.1-0

3.8 设置自启动kubelet

此时,还不能启动kubelet,因为此时配置还不能,现在仅仅可以设置开机自启动

1
bash复制代码systemctl enable kubelet

3.9 部署Kubernetes Master

3.9.1 初始化kubeadm

1
2
3
4
5
6
7
8
9
10
11
ini复制代码kubeadm init \

  --apiserver-advertise-address=192.168.190.128 \

  --image-repository registry.aliyuncs.com/google_containers \

  --kubernetes-version v1.19.6 \

  --service-cidr=10.1.0.0/16 \

  --pod-network-cidr=10.244.0.0/16
1
2
3
4
5
6
7
shell复制代码# –image-repository string:    这个用于指定从什么位置来拉取镜像(1.13版本才有的),默认值是k8s.gcr.io,我们将其指定为国内镜像地址:registry.aliyuncs.com/google_containers

# –kubernetes-version string:  指定kubenets版本号,默认值是stable-1,会导致从https://dl.k8s.io/release/stable-1.txt下载最新的版本号,我们可以将其指定为固定版本(v1.15.1)来跳过网络请求。

# –apiserver-advertise-address  指明用 Master 的哪个 interface 与 Cluster 的其他节点通信。如果 Master 有多个 interface,建议明确指定,如果不指定,kubeadm 会自动选择有默认网关的 interface。

# –pod-network-cidr            指定 Pod 网络的范围。Kubernetes 支持多种网络方案,而且不同网络方案对  –pod-network-cidr有自己的要求,这里设置为10.244.0.0/16 是因为我们将使用 flannel 网络方案,必须设置成这个 CIDR。

输出:

1
2
3
4
5
sql复制代码Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.190.128:6443 --token 09eyru.hv3d4mkq2rxgculb \

    --discovery-token-ca-cert-hash sha256:d0141a8504afef937cc77bcac5a67669f42ff32535356953b5ab217d767f85aa

稍后在其他两台服务器中执行以上命令。

3.9.2 使用kubectl工具

复制如下命令直接执行(master)

1
2
3
4
5
bash复制代码mkdir -p $HOME/.kube

sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

sudo chown $(id -u):$(id -g) $HOME/.kube/config

下面就可以直接使用kubectl命令了(master)

3.9.3 安装Pod网络插件(CNI)(master)

1
ruby复制代码kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

或者:

1
复制代码kubectl apply -f kube-flannel.yml

通过命令查看安装服务状态

1
sql复制代码kubectl get pods -n kube-system

4 设置NFS存储

4.1 集群所有节点安装nfs客户端

1
复制代码yum install -y nfs-utils

4.2 安装插件

1
复制代码kubectl apply -f deploymentnfs.yaml
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
yaml复制代码apiVersion: apps/v1

kind: Deployment

metadata:

  name: nfs-client-provisioner

  labels:

    app: nfs-client-provisioner

  # replace with namespace where provisioner is deployed

  namespace: arcgis

spec:

  replicas: 1

  strategy:

    type: Recreate

  selector:

    matchLabels:

      app: nfs-client-provisioner

  template:

    metadata:

      labels:

        app: nfs-client-provisioner

    spec:

      serviceAccountName: nfs-client-provisioner

      containers:

        - name: nfs-client-provisioner

          image: quay.io/external_storage/nfs-client-provisioner:latest

          volumeMounts:

            - name: nfs-client-root

              mountPath: /persistentvolumes

          env:

            - name: PROVISIONER_NAME

              value: mynfs

            - name: NFS_SERVER

              value: 192.168.190.81

            - name: NFS_PATH

              value: /gis

      volumes:

        - name: nfs-client-root

          nfs:

            server: 192.168.190.81

            path: /gis
1
复制代码kubectl apply –f rbacnfs.yaml
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
yaml复制代码apiVersion: v1

kind: ServiceAccount

metadata:

  name: nfs-client-provisioner

  # replace with namespace where provisioner is deployed

  namespace: arcgis

---

kind: ClusterRole

apiVersion: rbac.authorization.k8s.io/v1

metadata:

  name: nfs-client-provisioner-runner

rules:

  - apiGroups: [""]

    resources: ["persistentvolumes"]

    verbs: ["get", "list", "watch", "create", "delete"]

  - apiGroups: [""]

    resources: ["persistentvolumeclaims"]

    verbs: ["get", "list", "watch", "update"]

  - apiGroups: ["storage.k8s.io"]

    resources: ["storageclasses"]

    verbs: ["get", "list", "watch"]

  - apiGroups: [""]

    resources: ["events"]

    verbs: ["create", "update", "patch"]

---

kind: ClusterRoleBinding

apiVersion: rbac.authorization.k8s.io/v1

metadata:

  name: run-nfs-client-provisioner

subjects:

  - kind: ServiceAccount

    name: nfs-client-provisioner

    # replace with namespace where provisioner is deployed

    namespace: arcgis

roleRef:

  kind: ClusterRole

  name: nfs-client-provisioner-runner

  apiGroup: rbac.authorization.k8s.io

---

kind: Role

apiVersion: rbac.authorization.k8s.io/v1

metadata:

  name: leader-locking-nfs-client-provisioner

  # replace with namespace where provisioner is deployed

  namespace: arcgis

rules:

  - apiGroups: [""]

    resources: ["endpoints"]

    verbs: ["get", "list", "watch", "create", "update", "patch"]

---

kind: RoleBinding

apiVersion: rbac.authorization.k8s.io/v1

metadata:

  name: leader-locking-nfs-client-provisioner

  # replace with namespace where provisioner is deployed

  namespace: arcgis

subjects:

  - kind: ServiceAccount

    name: nfs-client-provisioner

    # replace with namespace where provisioner is deployed

    namespace: arcgis

roleRef:

  kind: Role

  name: leader-locking-nfs-client-provisioner

  apiGroup: rbac.authorization.k8s.io

4.3 添加存储类

1
复制代码kubectl apply –f classnfs.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
vbnet复制代码apiVersion: storage.k8s.io/v1

kind: StorageClass

metadata:

  name: storage-class-default

provisioner: mynfs # or choose another name, must match deployment's env PROVISIONER_NAME'

parameters:

  archiveOnDelete: "false"

5 安装ingress

1
复制代码kubectl apply -f mandatory-ingress.yaml
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
yaml复制代码apiVersion: v1

kind: Namespace

metadata:

  name: ingress-nginx

---

apiVersion: apps/v1

kind: Deployment

metadata:

  name: default-http-backend

  labels:

    app.kubernetes.io/name: default-http-backend

    app.kubernetes.io/part-of: ingress-nginx

  namespace: ingress-nginx

spec:

  replicas: 1

  selector:

    matchLabels:

      app.kubernetes.io/name: default-http-backend

      app.kubernetes.io/part-of: ingress-nginx

  template:

    metadata:

      labels:

        app.kubernetes.io/name: default-http-backend

        app.kubernetes.io/part-of: ingress-nginx

    spec:

      terminationGracePeriodSeconds: 60

      containers:

        - name: default-http-backend

          # Any image is permissible as long as:

          # 1\. It serves a 404 page at /

          # 2\. It serves 200 on a /healthz endpoint

          image: 192.168.190.126:5000/defaultbackend-amd64:1.5

          livenessProbe:

            httpGet:

              path: /healthz

              port: 8080

              scheme: HTTP

            initialDelaySeconds: 30

            timeoutSeconds: 5

          ports:

            - containerPort: 8080

          resources:

            limits:

              cpu: 10m

              memory: 20Mi

            requests:

              cpu: 10m

              memory: 20Mi

---

apiVersion: v1

kind: Service

metadata:

  name: default-http-backend

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: default-http-backend

    app.kubernetes.io/part-of: ingress-nginx

spec:

  ports:

    - port: 80

      targetPort: 8080

  selector:

    app.kubernetes.io/name: default-http-backend

    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap

apiVersion: v1

metadata:

  name: nginx-configuration

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap

apiVersion: v1

metadata:

  name: tcp-services

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap

apiVersion: v1

metadata:

  name: udp-services

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

---

apiVersion: v1

kind: ServiceAccount

metadata:

  name: nginx-ingress-serviceaccount

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

---

apiVersion: rbac.authorization.k8s.io/v1

kind: ClusterRole

metadata:

  name: nginx-ingress-clusterrole

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

rules:

  - apiGroups:

      - ""

    resources:

      - configmaps

      - endpoints

      - nodes

      - pods

      - secrets

    verbs:

      - list

      - watch

  - apiGroups:

      - ""

    resources:

      - nodes

    verbs:

      - get

  - apiGroups:

      - ""

    resources:

      - services

    verbs:

      - get

      - list

      - watch

  - apiGroups:

      - "extensions"

    resources:

      - ingresses

    verbs:

      - get

      - list

      - watch

  - apiGroups:

      - ""

    resources:

      - events

    verbs:

      - create

      - patch

  - apiGroups:

      - "extensions"

    resources:

      - ingresses/status

    verbs:

      - update

---

apiVersion: rbac.authorization.k8s.io/v1

kind: Role

metadata:

  name: nginx-ingress-role

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

rules:

  - apiGroups:

      - ""

    resources:

      - configmaps

      - pods

      - secrets

      - namespaces

    verbs:

      - get

  - apiGroups:

      - ""

    resources:

      - configmaps

    resourceNames:

      # Defaults to "<election-id>-<ingress-class>"

      # Here: "<ingress-controller-leader>-<nginx>"

      # This has to be adapted if you change either parameter

      # when launching the nginx-ingress-controller.

      - "ingress-controller-leader-nginx"

    verbs:

      - get

      - update

  - apiGroups:

      - ""

    resources:

      - configmaps

    verbs:

      - create

  - apiGroups:

      - ""

    resources:

      - endpoints

    verbs:

      - get

---

apiVersion: rbac.authorization.k8s.io/v1

kind: RoleBinding

metadata:

  name: nginx-ingress-role-nisa-binding

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

roleRef:

  apiGroup: rbac.authorization.k8s.io

  kind: Role

  name: nginx-ingress-role

subjects:

  - kind: ServiceAccount

    name: nginx-ingress-serviceaccount

    namespace: ingress-nginx

---

apiVersion: rbac.authorization.k8s.io/v1

kind: ClusterRoleBinding

metadata:

  name: nginx-ingress-clusterrole-nisa-binding

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

roleRef:

  apiGroup: rbac.authorization.k8s.io

  kind: ClusterRole

  name: nginx-ingress-clusterrole

subjects:

  - kind: ServiceAccount

    name: nginx-ingress-serviceaccount

    namespace: ingress-nginx

---

apiVersion: apps/v1

kind: Deployment

metadata:

  name: nginx-ingress-controller

  namespace: ingress-nginx

  labels:

    app.kubernetes.io/name: ingress-nginx

    app.kubernetes.io/part-of: ingress-nginx

spec:

  replicas: 1

  selector:

    matchLabels:

      app.kubernetes.io/name: ingress-nginx

      app.kubernetes.io/part-of: ingress-nginx

  template:

    metadata:

      labels:

        app.kubernetes.io/name: ingress-nginx

        app.kubernetes.io/part-of: ingress-nginx

      annotations:

        prometheus.io/port: "10254"

        prometheus.io/scrape: "true"

    spec:

      hostNetwork: true

      serviceAccountName: nginx-ingress-serviceaccount

      containers:

        - name: nginx-ingress-controller

          image: 192.168.190.126:5000/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0

          args:

            - /nginx-ingress-controller

            - --default-backend-service=$(POD_NAMESPACE)/default-http-backend

            - --configmap=$(POD_NAMESPACE)/nginx-configuration

            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services

            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services

            - --publish-service=$(POD_NAMESPACE)/ingress-nginx

            - --annotations-prefix=nginx.ingress.kubernetes.io

          securityContext:

            capabilities:

              drop:

                - ALL

              add:

                - NET_BIND_SERVICE

            # www-data -> 33

            runAsUser: 33

          env:

            - name: POD_NAME

              valueFrom:

                fieldRef:

                  fieldPath: metadata.name

            - name: POD_NAMESPACE

              valueFrom:

                fieldRef:

                  fieldPath: metadata.namespace

          ports:

            - name: http

              containerPort: 80

            - name: https

              containerPort: 443

          livenessProbe:

            failureThreshold: 3

            httpGet:

              path: /healthz

              port: 10254

              scheme: HTTP

            initialDelaySeconds: 10

            periodSeconds: 10

            successThreshold: 1

            timeoutSeconds: 1

          readinessProbe:

            failureThreshold: 3

            httpGet:

              path: /healthz

              port: 10254

              scheme: HTTP

            periodSeconds: 10

            successThreshold: 1

            timeoutSeconds: 1

---

本文转载自: 掘金

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

如何在Spring Boot项目中集成微信支付V3

发表于 2021-01-06

Payment Spring Boot 是微信支付V3的Java实现,仅仅依赖Spring内置的一些类库。配置简单方便,可以让开发者快速为Spring Boot应用接入微信支付。

演示例子: payment-spring-boot-samples

文档:Payment Spring Boot文档

有疑问请提交ISSUE会及时跟进,欢迎Star **,欢迎PR**贡献。

1.0.3.SNAPSHOT 已经进入测试阶段

目前功能

  • 实现微信支付多商户
  • 集成支付宝SDK、快速接入Spring Boot
  • 实现微信支付V3 基础支付
  • 实现微信支付V3 合单支付
  • 实现微信支付V3 代金券
  • 实现微信支付V3 微信支付分
  • 实现微信支付V3 先享卡

Maven 中央仓库坐标

最新版本的坐标为:

1
2
3
4
5
xml复制代码<dependency>
<groupId>cn.felord</groupId>
<artifactId>payment-spring-boot-starter</artifactId>
<version>1.0.2.RELEASE</version>
</dependency>

更新日志

  • feat: 接入微信支付分
  • feat: 接入为微信支付先享卡
  • fix: 支付回调参数不全的问题

关注公众号:Felordcn获取更多资讯

个人博客:https://felord.cn

本文转载自: 掘金

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

微信授权扫码点餐-新特性React16

发表于 2021-01-05

适用人群
对微信公众号开发感兴趣,系统学习React新特性及其生态圈相关知识的人群

本课程集合前端项目开发热门技术,致力于打造一个企业级的微信授权扫码点餐平台;课程对React16新特性及其生态圈工具(React-Router、Mobk、Hooks)做了全面深入的剖析讲解,并带你从Webpack搭建、React项目配置开始,实现微信授权点餐平台的开发全过程;通过基础知识详解+项目实践的形式,能够助你快速掌握企业级React项目开发的必备知识体系,熟悉微信公众号的全新开发模式及主流技术架构,收货真实项目开发经验及技巧。攻城狮

目录

01 项目构建与React基础-导学

02 工具安装

03 手动创建应用以及目录的介绍

04 React简介

05 JSX语法

06 React组件的生命周期及基本语法(上)

07 React组件的生命周期及基本语法(下)

08 React组件数据

09 React中CSS的样式书写

10 React事件处理

11 React的条件判断

12 React列表渲染和keys

13 React受控组件

14 React的组件通讯方式

15 React基础-总结

16 组合与继承

17 错误边界处理

18 高阶组件应用

19 Refs and the Dom

20 最新Hooks基础应用

21 最新Hooks高级应用-自定义Hooks

22 最新Hooks高级应用-useContext的使用

23 最新Hooks高级应用-useReducer的使用

24 最新Hooks高级应用-useCallback与useMemo的使用

25 最新Hooks高级应用-useRef的使用

26 构建React-Router配置-React

27 构建React-Router配置-Route路由配置项的相关属性路由的最基本配置

28 构建React-Router配置-React-Router的其他内置组件

29 构建React-Router配置-React-Router路由的按需加载

30 构建React-Router配置-子路由的定义及路由的嵌套

31 Redux的配置和使用(上)

32 Redux的配置和使用(下)

33 构建mobx的配置与使用(上)

34 构建mobx的配置与使用(下)

35 Redux与Mobx的区别

36 项目构建与React基础-总结

37 微信授权扫码点餐平台分析-导学

38 项目的设计图划分

39 明确项目功能

40 根据需求规划交互导图

41 根据需求划分项目结构

42 公共组件规划

43 公共逻辑规划

44 Mock的使用与配置

45 Mock的合理化

46 基于Node实现文件数据的增删改查方法

47 微信授权扫码点餐平台分析-总结

48 欢迎页-导学

49 根据商铺id定制商铺信息接口

50 模拟当前用户信息(后期通过微信自动获取)

51 定制当前桌用餐人数接口

52 项目页面初始化

53 页面结构样式书写

54 数据处理与services定制

55 路由参数获取与数据渲染

56 用餐人数选择页结构样式书写

57 数据获取与提交

58 消息提示组件

59 欢迎页开发-总结

60 本章导学

61 根据商铺id定制商铺菜单接口

62 呼叫服务员接口开发

63 列表页结构布局

64 开发头部nav-1

64 开发头部nav-2

65 列表渲染

66 公共组件封装-1

66 公共组件封装-2

67 分类菜单定位-结构样式-1

67 分类菜单定位-结构样式-2

68 分类菜单定位-关联渲染

69 弹窗导航开发

70 服务铃(呼叫功能)开发-1

70 服务铃(呼叫功能)开发-2

71 热销榜结构与返回组件-1

71 热销榜结构与返回组件-2

72 热销榜列表渲染-1

72 热销榜列表渲染-2

73 搜索功能开发-1

73 搜索功能开发-2

74 总结75 本章导学-1

75 本章导学-2

76 获取菜单详情接口

77 加入购物车接口

78 获取购物车详情接口-1

78 获取购物车详情接口-2

79 修改用餐人数接口

80 购物车菜单数量修改接口

81 清空购物车接口

82 下单接口

83 详情页结构样式开发-1

83 详情页结构样式开发-2

84 菜单详情数据交互

85 购物车页面数据请求与结构渲染

86 购物车样式书写与修改用餐人数

87 Mobx数据管理与useMemo总价计算

88 清空购物车与下单

89 本章总结

90 本章导学-1

90 本章导学-2

91 获取订单接口

92 支付接口开发

93 数据渲染与结构开发(上)

94 数据渲染与结构开发(下)

95 折叠菜单与提交支付

96 本章总结

97 本章导学-1

97 本章导学-2

98 接口开发-1

98 接口开发-2

99 结构页开发

100 个人信息展示与修改(上)-1

100 个人信息展示与修改(上)-2

101 个人信息展示与修改(下)

102 获取订单列表

103 订单评价-1

103 订单评价-2

104 本章总结

105 本章导学

106 阿里云域名配置-1

106 阿里云域名配置-2

107 linux公网服务器之ftp服务端配置

108 Windows客户端配置

109 微信公众号配置

110 微信公众号开发配置

111 微信客户端code获取

112 服务端使用code获取用户信息

113 微信授权用户信息缓存

114 本章总结

115 本章导学

116 创建项目(express项目构建)

117 打包与上传项目

118 服务器搭建与项目启动

119 本章总结

本文转载自: 掘金

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

数据库表数据量大读写缓慢如何优化(1)【冷热分离】 业务场景

发表于 2021-01-05

今天讨论的内容是冷热分离,也许概念并不陌生,对其使用场景也比较熟悉,但涉及锁的内容时仍然需要认真思考,这部分内容在我们实际开发中的“坑”还是不少的。

业务场景一

曾经经历过供应链相关的架构优化,当时平台上有一个订单功能,里面的主表有几千万数据量,加上关联表,数据量达到上亿。

这么庞大的数据量,让平台的查询订单变得格外迟缓,查询一次都要二三十秒,而且多点击几次就会出现宕机。比如业务员多次查询时,数据库的 CPU 会立马狂飙,服务器线程也降不下来。

当时,我们尝试了优化表结构、业务代码、索引、SQL 语句等办法来提高响应速度,但这些方法治标不治本,查询速度还是很慢。

考虑到我们手头上还有其他优先级高的需求需要处理,为此,我们跟业务方反馈:“这功能以后你们能不用就不用,暂时先忍受一下。”可经过一段时间后,业务方实在受不了了,直接跟我们放狠话,无奈之下我们屈服了。

最终,我们决定采用一个性价比高的解决方案,简单方便地解决了这个问题。在处理数据时,我们将数据库分成了冷库和热库 2 个库,不常用数据放冷库,常用数据放热库。

通过这样的方法处理后,因为业务员查询的基本是近期常用的数据,常用的数据量大大减少了,就再也不会出现宕机的情况了,也大大提升了数据库响应速度。

其实上面这个方法,就是“冷热分离”。

一、什么是冷热分离

冷热分离就是在处理数据时将数据库分成冷库和热库 2 个库,冷库指存放那些走到了终态的数据的数据库,热库指存放还需要修改的数据的数据库。

二、什么情况下使用冷热分离?

假设业务需求出现了如下情况,就可以考虑使用冷热分离的解决方案:

  • 数据走到终态后,只有读没有写的需求,比如订单完结状态;
  • 用户能接受新旧数据分开查询,比如有些电商网站默认只让查询3个月内的订单,如果你要查询3个月前的订单,还需要访问另外的单独页面。

三、冷热分离实现思路

在实际操作过程中,冷热分离整体实现思路如下:

1、如何判断一个数据到底是冷数据还是热数据?

2、如何触发冷热数据分离?

3、如何实现冷热数据分离?

4、如何使用冷热数据?

接下来,我们针对以上4个问题进行详细的分析。

(一)如何判断一个数据到底是冷数据还是热数据?

一般而言,在判断一个数据到底是冷数据还是热数据时,我们主要采用主表里的 1 个或多个字段组合的方式作为区分标识。其中,这个字段可以是时间维度,比如“下单时间”这个字段,我们可以把 3 个月前的订单数据当作冷数据,3 个月内的当作热数据。

当然,这个字段也可以是状态维度,比如根据“订单状态”字段来区分,已完结的订单当作冷数据,未完结的订单当作热数据。

我们还可以采用组合字段的方式来区分,比如我们把下单时间 > 3 个月且状态为“已完结”的订单标识为冷数据,其他的当作热数据。

而在实际工作中,最终究竟使用哪种字段来判断,还是需要根据你的实际业务来定。

关于判断冷热数据的逻辑,这里还有 2 个注意要点必须说明:

  • 如果一个数据被标识为冷数据,业务代码不会再对它进行写操作;
  • 不会同时存在读冷/热数据的需求。

(二)如何触发冷热数据分离?

了解了冷热数据的判断逻辑后,我们就要开始考虑如何触发冷热数据分离了。一般来说,冷热数据分离的触发逻辑分3种。

1、直接修改业务代码,每次修改数据时触发冷热分离(比如每次更新了订单的状态,就去触发这个逻辑);

在这里插入图片描述

2、如果不想修改原来业务代码,可通过监听数据库变更日志binlog的方式来触发(数据库触发器也可);

在这里插入图片描述

3、通过定时扫描数据的方式来触发(数据库定时任务或通过程序定时任务来触发);

在这里插入图片描述

针对以上三种触发逻辑,选择哪种比较好呢?看完以下表格的分析,你心里就有答案了。

根据表格内容对比,我们可以得出每种出发逻辑的建议场景。

  1. 修改写操作的业务代码:建议在业务代码比较简单,并且不按照时间区分冷热数据时使用。
  2. 监听数据库变更日志:建议在业务代码比较复杂,不能随意变更,并且不按照时间区分冷热数据时使用。
  3. 定时扫描数据库:建议在按照时间区分冷热数据时使用。

(三)如何分离冷热数据?

分离冷热数据的基本逻辑如下:

1、判断数据是冷是热;

2、将要分离的数据插入冷数据中;

3、再从热数据库中删除分离的数据。

这个逻辑看起来简单,而实际做方案时,以下三点我们都得考虑在内,这一点就不简单了。

(1)一致性:同时修改过个数据库,如何保证数据的一致性

这里提到的一致性要求,指我们如何保证任何一步出错后数据还是一致的,解决方案为只要保证每一步都可以重试且操作都有幂等性就行,具体逻辑分为四步。

  • 在热数据库中,给要搬的数据加个标识: flag=1。(1代表冷数据,0代表热数据)
  • 找出所有待搬的数据(flag=1):这步是为了确保前面有些线程因为部分原因失败,出现有些待搬的数据没有搬的情况。
  • 在冷数据库中保存一份数据,但在保存逻辑中需加个判断以此保证幂等性(这里需要用事务包围起来),通俗点说就是假如我们保存的数据在冷数据库已经存在了,也要确保这个逻辑可以继续进行。
  • 从热数据库中删除对应的数据。

(2)数据量大:假设数据量大,一次性处理不完,该怎么办?是否需要使用批量处理?

前面说的3种冷热分离的触发逻辑,前 2 种基本不会出现数据量大的问题,因为每次只需要操作那一瞬间变更的数据,但如果采用定时扫描的逻辑就需要考虑数据量这个问题了。

这个实现逻辑也很简单,在搬数据的地方我们加个批量逻辑就可以了。为方便理解,我们来看一个示例。

假设我们每次可以搬 50 条数据:

a. 在热数据库中给要搬的数据加个标识:flag=1;

b. 找出前 50 条待搬的数据(flag=1);

c. 在冷数据库中保存一份数据;

d. 从热数据库中删除对应的数据;

e. 循环执行 b。

(3)并发性:假设数据量大到要分到多个地方并行处理,该怎么办?

在定时搬运冷热数据的场景里(比如每天),假设每天处理的数据量大到连单线程批量处理都来不及,我们该怎么办?这时我们就可以开多个线程并发处理了。(虽然大部分情况下多线程较快,但我曾碰到过这种情况:当单线程 batch size 到一定数值时效率特别高,比多线程任何 batch size 都快。所以,需要留意:如果遇到多线程速度不快,我们就考虑控制单线程。)

当多线程同时搬运冷热数据,我们需要考虑如下实现逻辑。

第 1 步:如何启动多线程?

因为我们采用的是定时器触发逻辑,这种触发逻辑性价比最高的方式是设置多个定时器,并让每个定时器之间的间隔短一些,然后每次定时启动一个线程就开始搬运数据。

还有一个比较合适的方式是自建一个线程池,然后定时触发后面的操作:先计算待搬动的热数据的数量,再计算要同时启动的线程数,如果大于线程池的数量就取线程池的线程数,假设这个数量为 N,最后循环 N 次启动线程池的线程搬运冷热数据。

第 2 步:某线程宣布某个数据正在操作,其他线程不要动(锁)。

关于这个逻辑,我们需要考虑 3 个特性。

  • 获取锁的原子性: 当一个线程发现某个待处理的数据没有加锁,然后给它加锁,这 2 步操作必须是原子性的,即要么一起成功,要么一起失败。实际操作为先在表中加上 LockThread 和 LockTime 两个字段,然后通过一条 SQL 语句找出待迁移的未加锁或锁超时的数据,再更新 LockThread=当前线程,LockTime=当前时间,最后利用 MySQL 的更新锁机制实现原子性。
  • 获取锁必须与开始处理保证一致性: 当前线程开始处理这条数据时,需要再次检查下操作的数据是否由当前线程锁定成功,实际操作为再次查询一下 LockThread= 当前线程的数据,再处理查询出来的数据。
  • 释放锁必须与处理完成保证一致性: 当前线程处理完数据后,必须保证锁释放出去。

第 3 步:某线程正常处理完后,数据不在热库,直接跑到了冷库,这是正常的逻辑,倒没有什么特别需要注意的点。

第 4 步:某线程失败退出了,结果锁没释放怎么办(锁超时)?

锁无法释放: 如果锁定这个数据的线程异常退出了且来不及释放锁,导致其他线程无法处理这个数据,此时该怎么办?解决方案为给锁设置一个超时时间,如果锁超时了还未释放,其他线程可正常处理该数据。

设置超时时间时,我们还应考虑如果正在处理的线程并未退出,因还在处理数据导致了超时,此时又该怎么办?解决方案为尽量给超时的时间设置成超过处理数据的合理时间,且处理冷热数据的代码里必须保证是幂等性的。

最后,我们还得考虑一个极端情况:如果当前线程还在处理数据,此时正在处理的数据的锁超时了,另外一个线程把正在处理的数据又进行了加锁,此时该怎么办?我们只需要在每一步加判断容错即可,因为搬运冷热数据的代码比较简单,通过这样的操作当前线程的处理就不会破坏数据的一致性。

(四)如何使用冷数据

在功能设计的查询界面上,一般都会有一个选项供我们选择需要查询冷数据还是热数据,如果界面上没有提供,我们可以直接在业务代码里区分。(说明:在判断是冷数据还是热数据时,我们必须确保用户不允许有同时读冷热数据的需求。)

历史数据如何迁移? 一般而言,只要跟持久化层有关的架构方案,我们都需要考虑历史数据的迁移问题,即如何让旧架构的历史数据适用于新的架构?

因为前面的分离逻辑在考虑失败重试的场景时,刚好覆盖了这个问题,所以这个问题的解决方案也很简单,我们只需要给所有的历史数据加上标识:flag=1 后,程序就会自动迁移了。

冷热分离解决方案的不足

不得不说,冷热分离解决方案确实能解决写操作慢和热数据慢的问题,但仍然存在诸多不足。

不足一: 用户查询冷数据速度依旧很慢,如果查询冷数据的用户比例很低,比如只有 1%,那么这个方案就没问题。

不足二: 业务无法再修改冷数据,因为冷数据多到一定程度时,系统承受不住。(这点可以通过冷库再分库来解决,后面再来探讨)

看到这里了,点个赞,转个发不行吗? 嘿嘿!

关注个人公众号“服务端技术精选”,还有更多好玩的等你哟!

原文链接:数据库表数据量大读写缓慢如何优化(1)【冷热分离】

本文转载自: 掘金

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

在minio自有存储的基础上做图片处理(thumbor)

发表于 2021-01-05

回顾之前的
minio自有存储的搭建

环境

1
复制代码centos 7.8

安装 thumbor

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
shell复制代码#查看python 版本
python -v
#安装python的包管理工具pip
yum install epel-release
yum install python-devel
yum install python-pip
pip install --upgrade pip #升级pip版本
#安装编译工具
yum install gcc
#安装thumbor
pip install thumbor

#创建运行thumbor的用户:
groupadd thumbor
useradd -g thumbor -s /sbin/nologin thumbor
#创建thumbor的配置文件
mkdir -p /etc/thumbor
thumbor-config > /etc/thumbor/thumbor.conf
#创建service 文件
vi /etc/systemd/system/thumbor.service

#以下为thumbor.service的内容
[Unit]
Description=thumbor
After=network.target

[Service]
ExecStart=/usr/bin/thumbor \
--port=8199 \
--conf=/etc/thumbor/thumbor.conf
User=thumbor
Restart=on-failure
Type=simple
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

#启动
systemctl start thumbor
systemctl enable thumbor

访问地址nginx配置 /etc/nginx/conf.d/thumbor.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
shell复制代码#这里访问的是minio的存储服务地址
server {
listen 8008;
server_name 192.168.2.1;
location ~/([A-Za-z0-9._-]+)/(.*)$ {
proxy_http_version 1.1;
proxy_set_header Connection "keep-alive";
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8199/unsafe/$2/http://127.0.0.1:9000/test/$1;
}
location ~/([A-Za-z0-9._-]+)$ {
proxy_http_version 1.1;
proxy_set_header Connection "keep-alive";
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:9000/test/$1;
}
}

本文转载自: 掘金

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

1…742743744…956

开发者博客

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