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

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


  • 首页

  • 归档

  • 搜索

SVN和Git 介绍,区别,优缺点,适用范围总结 SVN和G

发表于 2021-08-13

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

SVN和Git 介绍,区别,优缺点,适用范围总结

SVN SVN是Subversion的简称,是一个开放源代码的版本控制系统,支持大多数常见的操作系统。作为一个开源的版本控制系统,Subversion管理着随时间改变的数据。这些数据放置在一个中央资料档案库(repository)中。这个档案库很像一个普通的文件服务器,不过它会记住每一次文件的变动。这样你就可以把档案恢复到旧的版本,或是浏览文件的变动历史。Subversion是一个通用的系统,可用来管理任何类型的文件,其中包括了程序源码。

工作流程

集中式管理的工作流程如下图:

集中式代码管理的核心是服务器,所有开发者在开始新一天的工作之前必须从服务器获取代码,然后开发,最后解决冲突,提交。所有的版本信息都放在服务器上。如果脱离了服务器,开发者基本上可以说是无法工作的。下面举例说明: 开始新一天的工作:

1、从服务器下载项目组最新代码。 2、进入自己的分支,进行工作,每隔一个小时向服务器自己的分支提交一次代码(很多人都有这个习惯。因为有时候自己对代码改来改去,最后又想还原到前一个小时的版本,或者看看前一个小时自己修改了哪些代码,就需要这样做了)。 3、下班时间快到了,把自己的分支合并到服务器主分支上,一天的工作完成,并反映给服务器。

GIT(分布式版本控制系统) Git是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目 Git是一个开源的分布式版本控制系统,用以有效、高速的处理从很小到非常大的项目版本管理。Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。 分布式相比于集中式的最大区别在于开发者可以提交到本地,每个开发者通过克隆(git clone),在本地机器上拷贝一个完整的Git仓库.

工作流程

下图是经典的git开发过程。

Git的功能特性: 从一般开发者的角度来看,git有以下功能: 1、从服务器上克隆完整的Git仓库(包括代码和版本信息)到单机上。 2、在自己的机器上根据不同的开发目的,创建分支,修改代码。 3、在单机上自己创建的分支上提交代码。 4、在单机上合并分支。 5、把服务器上最新版的代码fetch下来,然后跟自己的主分支合并。 6、生成补丁(patch),把补丁发送给主开发者。 7、看主开发者的反馈,如果主开发者发现两个一般开发者之间有冲突(他们之间可以合作解决的冲突),就会要求他们先解决冲突,然后再由其中一个人提交。如果主开发者可以自己解决,或者没有冲突,就通过。 8、一般开发者之间解决冲突的方法,开发者之间可以使用pull 命令解决冲突,解决完冲突之后再向主开发者提交补丁。 从主开发者的角度(假设主开发者不用开发代码)看,git有以下功能: 1、查看邮件或者通过其它方式查看一般开发者的提交状态。 2、打上补丁,解决冲突(可以自己解决,也可以要求开发者之间解决以后再重新提交,如果是开源项目,还要决定哪些补丁有用,哪些不用)。 3、向公共服务器提交结果,然后通知所有开发人员。

区别

1.SVN属于集中化的版本控制系统,有个不太精确的比喻:SVN = 版本控制+ 备份服务器 SVN使用起来有点像是档案仓库的感觉,支持并行读写文件,支持代码的版本化管理,功能包括取出、导入、更新、分支、改名、还原、合并等。

Git是一个分布式版本控制系统,操作命令包括:clone,pull,push,branch ,merge ,push,rebase,Git擅长的是程序代码的版本化管理。

2.GIT跟SVN一样有自己的集中式版本库或服务器。但,GIT更倾向于被使用于分布式模式,也就是每个开发人员从中心版本库/服务器上chect out代码后会在自己的机器上克隆一个自己的版本库。可以这样说,如果你被困在一个不能连接网络的地方时,就像在飞机上,地下室,电梯里等,你仍然能够提交文件,查看历史版本记录,创建项目分支,等。对一些人来说,这好像没多大用处,但当你突然遇到没有网络的环境时,这个将解决你的大麻烦。

3.GIT把内容按元数据方式存储,而SVN是按文件 所有的资源控制系统都是把文件的元信息隐藏在一个类似.svn,.cvs等的文件夹里。如果你把.git目录的体积大小跟.svn比较,你会发现它们差距很大。因为,.git目录是处于你的机器上的一个克隆版的版本库,它拥有中心版本库上所有的东西,例如标签,分支,版本记录等。

4.分支在SVN中一点不特别,就是版本库中的另外的一个目录。如果你想知道是否合并了一个分支,你需要手工运行像这样的命令svn propget svn:mergeinfo,来确认代码是否被合并。然而,处理GIT的分支却是相当的简单和有趣。你可以从同一个工作目录下快速的在几个分支间切换。你很容易发现未被合并的分支,你能简单而快捷的合并这些文件。

5.GIT没有一个全局的版本号,而SVN有 目前为止这是跟SVN相比GIT缺少的最大的一个特征。你也知道,SVN的版本号实际是任何一个相应时间的源代码快照。我认为它是从CVS进化到SVN的最大的一个突破。因为GIT和SVN从概念上就不同,我不知道GIT里是什么特征与之对应。如果你有任何的线索,请在评论里奉献出来与大家共享。

6.GIT的内容完整性要优于SVN: GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。

优缺点

SVN 的优缺点 SVN对中文支持好,操作简单,使用没有难度,美工人员,产品人员,测试人员,实施人员都可轻松上手。使用界面统一,功能完善,操作方便。

Git的优缺点 对程序源代码进行差异化的版本管理,代码库占极少的空间。易于代码的分支化管理。不支持中文,图形界面支持差,使用难度大。不易推广。

适用范围

1)适用对象不同。Git适用于参与开源项目的开发者。他们由于水平高,更在乎的是效率而不是易用性。SVN则不同,它适合普通的公司开发团队。使用起来更加容易。

2)使用的场合不同。Git适用于通过Internet,有多个开发角色的单个项目开发,SVN适合企业内部由项目经理统一协调的多个并行项目的开发。

3)权限管理策略不同。Git没有严格的权限管理控制,只要有帐号,就可以导出、导入代码,甚至执行回退操作。SVN则有严格的权限管理,可以按组、按个人进行针对某个子目录的权限控制。区分读、写权限。更严格的,不支持回退操作。保证代码永远可以追踪。

4)分支(branch)的使用范围不一样。Git中,你只能针对整个仓库作branch,而且一旦删除,便无法恢复。而SVN中,branch可以针对任何子目录,它本质上是一个拷贝操作。所以,可以建立非常多、层次性的branch,并且,在不需要时将其删除,而以后需要时只要checkout老的SVN版本就可以了。

5)基于第三点,Git适用于单纯的软件项目,典型的就是一些开源项目,比如Linux内核、busybox等。相反,SVN擅长多项目管理。比如,你可以在一个SVN仓库中存放一个手机项目的bsp/设计文档/文件系统/应用程序/自动化编译脚本,或者在一个SVN中存放5款手机项目的文件系统。git中必须建立n(项目数)*m(组件数)个仓库。SVN中只需要最多n或者m个就可以了。

6)Git使用128位ID作为版本号,而且checkout时要注明是哪个branch,而SVN使用一个递增的序列号作为全局唯一的版本号,更加简明易懂。虽然可以使用gittag来建立一些文字化的别名,但是毕竟那只是针对特殊版本。

7)可跟踪性,git的典型开发过程为:建立分支,进行开发,提交到本地master,删除分支。这样做的后果是以前的修改细节会丢失。而在SVN下做同样的事情,不会丢失任何细节。这里是一个有趣的链接,表明了git下典型的工作方式:(以master为核心,不断创建新branch,删除旧branch):

8)局部更新,局部还原。SVN由于是在每个文件夹建立一个.svn文件夹来实现管理,所以可以很简单实现局部更新或者还原。假如你只希望更新某些部分,则svn可以很好实现。同时代码写错了,同时可以很好实现局部还原,当然git也可以通过历史版本还原,但是无法简单地实现局部还原。

SVN属于集中化的版本控制系统

1
2
复制代码 这种做法带来了许多好处,特别是相较于老式的本地VCS来说。现在,每个人都可以一定程度上看到项目中的其他人正在做些什么。而管理员也可以轻松掌控每个开发者的权限。
复制代码

事分两面,有好有坏。这么做最显而易见的缺点是中央服务器的单点故障。若是宕机一小时,那么在这一小时内,谁都无法提交更新、还原、对比等,也就无法协同工作。如果中央服务器的磁盘发生故障,并且没做过备份或者备份得不够及时的话,还会有丢失数据的风险。最坏的情况是彻底丢失整个项目的所有历史更改记录,被客户端提取出来的某些快照数据除外,但这样的话依然是个问题,你不能保证所有的数据都已经有人提取出来。 Subversion原理上只关心文件内容的具体差异。每次记录有哪些文件作了更新,以及都更新了哪些行的什么内容。

Subversion的特点概括起来主要由以下几条:

1.每个版本库有唯一的URL(官方地址),每个用户都从这个地址获取代码和数据;

2.获取代码的更新,也只能连接到这个唯一的版本库,同步以取得最新数据;

3.提交必须有网络连接(非本地版本库);

4.提交需要授权,如果没有写权限,提交会失败;

5.提交并非每次都能够成功。如果有其他人先于你提交,会提示“改动基于过时的版本,先更新再提交”… 诸如此类;

6冲突解决是一个提交速度的竞赛:手快者,先提交,平安无事;手慢者,后提交,可能遇到麻烦的冲突解决。

Git属于分布式的版本控制系统

1
2
复制代码自2005年诞生于以来,Git日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。它的速度飞快,极其适合管理大项目,它还有着令人难以置信的非线性分支管理系统,可以应付各种复杂的项目开发需求。
复制代码

与SVN不同,Git记录版本历史只关心文件数据的整体是否发生变化。Git并不保存文件内容前后变化的差异数据。实际上,Git更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,Git不会再次保存,而只对上次保存的快照作一连接。

简略的说,Git具有以下特点:

1.Git中每个克隆(clone)的版本库都是平等的。你可以从任何一个版本库的克隆来创建属于你自己的版本库,同时你的版本库也可以作为源提供给他人,只要你愿意。

2.Git的每一次提取操作,实际上都是一次对代码仓库的完整备份。提交完全在本地完成,无须别人给你授权,你的版本库你作主,并且提交总是会成功。

3.甚至基于旧版本的改动也可以成功提交,提交会基于旧的版本创建一个新的分支。

4.Git的提交不会被打断,直到你的工作完全满意了,PUSH给他人或者他人PULL你的版本库,合并会发生在PULL和PUSH过程中,不能自动解决的冲突会提示您手工完成。

5.冲突解决不再像是SVN一样的提交竞赛,而是在需要的时候才进行合并和冲突解决。

总之,公司的开发团队在进行的项目开发管理时,svn是更好的选择,团队成员共同维护公司的中心版本。 若是开源项目,则git更加适合,每个人都可以维护自己专属的版本,同时有github开源社区支持。

SVN 和 Git 哪个更适用于项目管理? SVN更适用于项目管理, Git仅适用于代码管理。 一个研发队伍的成员正常包括:需求分析、设计、美工、程序员、测试、实施、运维,每个成员在工作中都有产出物, 包括了文档、设计代码、程序代码,这些都需要按项目集中进行管理的。SVN能清楚的按目录进行分类管理, 使项目组的管理处于有序高效的状态。

参考和转载自: 伯乐在线:blog.jobbole.com/31444/ 百度百科:baike.baidu.com marko39:jingyan.baidu.com/article/676… 码迷网:www.mamicode.com/info-detail… csdn:blog.csdn.net/mine_song/a…

本文转载自: 掘金

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

手写SpringBoot框架

发表于 2021-08-13

1、什么是SpringBoot框架。

SpringBoot是一个快速整合第三方框架;简化xml配置,完全采用注解化;内置http服务器(jetty和tomcat) ,最终是以java应用程序执行。

注意:SpringCloud底层依赖于SpringBoot实现微服务接口(SpringBoot Web组件集成SpringMVC),采用SpringMVC书写接口。

2、SpringBoot原理分析。

2.1、快速整合第三方框架,原理是:maven子父依赖关系,相当于需要整合的环境的Jar封装好依赖信息。

2.2、完全无配置文件(采用注解化)。如何初始化呢?没有web.xml那么tomcat是如何启动的呢?注解是在spring2.5以上开始,SpringMVC内置注解加载整个SpringMVC容器。使用java代码编写SpringMVC配置初始化。@EnableWebMvc注解。

2.3、内置http服务器,原理是使用java语言创建tomcat容器,加载class文件。

3、代码实现。

3.1、pom.xml文件,需要依赖的jar包。

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
xml复制代码<dependencies>
<!--Java语言操作tomcat -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.16</version>
</dependency>
<!-- spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- spring-mvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- tomcat对jsp支持 -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>8.5.16</version>
</dependency>
</dependencies>

3.2、启动程序的main类。

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
java复制代码public class AppTomcat {
public static void main(String[] args) throws ServletException, LifecycleException {
// 使用Java内置Tomcat运行SpringMVC框架 原理:tomcat加载到
// springmvc注解启动方式,就会创建springmvc容器
start();
}
public static void start() throws ServletException, LifecycleException {
// 创建Tomcat容器
Tomcat tomcatServer = new Tomcat();
// 端口号设置
tomcatServer.setPort(9090);
// 读取项目路径 加载静态资源
StandardContext ctx = (StandardContext) tomcatServer.addWebapp("/", new File("src/main").getAbsolutePath());
// 禁止重新载入
ctx.setReloadable(false);
// class文件读取地址
File additionWebInfClasses = new File("target/classes");
// 创建WebRoot
WebResourceRoot resources = new StandardRoot(ctx);
// tomcat内部读取Class执行
resources.addPreResources(
new DirResourceSet(resources, "/WEB-INF/classes", additionWebInfClasses.getAbsolutePath(), "/"));
tomcatServer.start();
// 异步等待请求执行
tomcatServer.getServer().await();
}
}

3.3、加载SpringMVC的DispatchServlet类。

AbstractAnnotationConfigDispatcherServletInitializer这个类负责”初始化Spring容器、SpringMVC容器、配置DispatcherServlet“。getRootConfigClasses()方法用于获取Spring应用容器的配置文件,这里我们给定预先定义的RootConfig.class;getServletConfigClasses负责获取Spring MVC应用容器,这里传入预先定义好的WebConfig.class;getServletMappings()方法负责指定需要由DispatcherServlet映射的路径,这里给定的是”/“,意思是由DispatcherServlet处理所有向该应用发起的请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
scala复制代码public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

// 加载根配置信息 spring核心
protected Class<?>[] getRootConfigClasses() {
return new Class[] { RootConfig.class };
}

// springmvc 加载 配置信息
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}

// springmvc 拦截url映射 拦截所有请求
protected String[] getServletMappings() {
return new String[] { "/" };
}
}

3.4、加载Spring容器

1
2
3
4
5
6
7
8
9
less复制代码/**
* 根容器
*
*/
@Configuration
@ComponentScan("com.xxxx")
public class RootConfig {

}

3.5、加载SpringMVC容器

正如可以通过多种方式配置DispatcherServlet一样,也可以通过多种方式启动Spring MVC特性。原来我们一般在xml文件中使用mvc:annotation-driven元素启动注解驱动的Spring MVC特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
less复制代码/**
* springmvc 配置信息
*
* @EnableWebMvc 开启springmvc 功能<br>
*/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.xxx.controller" })
public class WebConfig extends WebMvcConfigurerAdapter {

// springboot 整合jsp 最好是war
// 需要配置视图转换器
// 创建SpringMVC视图解析器
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
// 可以在JSP页面中通过${}访问beans
viewResolver.setExposeContextBeansAsAttributes(true);
return viewResolver;
}

}

3.6、Controller和Service层方法。

1
2
3
4
5
6
7
8
9
10
11
kotlin复制代码@RestController
public class IndexController {
@Autowired
private UserService userService;

@RequestMapping(value = "/index", produces = "text/html;charset=UTF-8")
public String index() {
// return "纯手写SpringBoot ok啦!!!"
return userService.index();
}
}
1
2
3
4
5
6
7
typescript复制代码@Service
public class UserService {

public String index() {
return "纯手写SpringBoot2,同时加载UserService ok啦!!";
}
}

4、具体源码,请查看码云:gitee.com/llsydn_admi…

本文转载自: 掘金

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

Docker基础学习

发表于 2021-08-13
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
bash复制代码一、docker的三要素
1.镜像image       就是一个只读的模板。镜像可以用来创建Docker容器,一个镜像可以创建多容器
2.容器container   docker利用容器独立运行一个或一组应用。容器是用镜像创建的运行实列,可以把容器看做是一个简易版的linux环境。
3.仓库repository  是集中存放镜像文件的场所,最大的公开仓库是Docker Hub(https://hub.docker.com/)


二、配置阿里云镜像加速器。https://cr.console.aliyun.com/cn-hangzhou/mirrors(自己的淘宝账号即可登录)
1.对于已创建的Docker Machine实例,更换镜像源的方法如下
在windows命令行执行docker-machine ssh default 进入default的 VM bash
sudo vi /var/lib/boot2docker/profile
在--label provider=virtualbox的下一行添加 --registry-mirror https://6wicrzn3.mirror.aliyuncs.com (注:这个换成自己专属的加速器地址)
重启docker服务:sudo /etc/init.d/docker restart或者重启VM:exit退出VM bash,在windows命令行中执行docker-machine restart

2.创建一台安装有Docker环境的Linux虚拟机,指定机器名称为default,同时配置Docker加速器地址。(注:需要换成自己专属的加速器地址)
docker-machine create --engine-registry-mirror=https://6wicrzn3.mirror.aliyuncs.com -d virtualbox default

3.镜像的帮助命令
docker info;docker version;docker --help


三、docker镜像命令
(1).docker images(列出本地主机上的镜像)
REPOSITORY      TAG        IMAGE ID        CREATED        SIZE
hello-world     latest     2cb0d9787c4d    2 weeks ago    1.85kB
REPOSITORY:表示镜像的仓库源
TAG:镜像的标签
IMAGE ID:镜像ID
CREATED:镜像创建时间
SIZE:镜像大小
同一仓库源可以有多个TAG,代表这个仓库源的不同个版本,我们使用REPOSITORY:TAG来定义不同的镜像
如果你不指定一个镜像的版本标签,例如你只使用ubuntu,docker将默认使用ubuntu:latest镜像

1.1 docker images -a 列出本地主机上的镜像(含中间映像层)
1.2 docker images -q 只显示镜像ID
1.3 docker images --digests 显示镜像的摘要信息
1.4 docker images --no-trunc 显示镜像的完整信息

(2).docker search(查询某个镜像)
网站:https://hub.docker.com
2.1 docker search 镜像名
2.2 docker search -s 30 镜像名 (点赞数超过30)
2.3 docker search --no-trunc 显示镜像的完整描述
2.4 docker search --automated 只列出automated build类型的镜像

(3).docker pull(拉取镜像:[TAG])

(4).docker rmi(删除镜像:[TAG])
4.1 docker rmi  镜像名|镜像id
4.2 docker rmi -f  镜像名|镜像id (强制删除)
4.3 docker rmi -f  镜像名|镜像id 镜像名|镜像id(删除多个,中间以空格隔开)
4.4 docker rmi -f $(docker images -qa) 删除所有镜像


四、docker容器命令
(1)容器启动
docker run -it 镜像名|镜像id --name 重新命名
--name="容器新名字",为容器指定一个名称
-d:后台运行容器,并返回容器id,也即启动守护式容器
-i:以交互模式运行容器,通常与-t同时使用
-t:为容器重新分配一个伪输入终端,通常与-i同时使用
-P:随机端口映射
-p:指定端口映射,有4种格式:
ip:hostPort:containerPort;ip::containerPort;
hostPort:containerPort;containerPort

(2)列出所有正在运行的容器
docker ps -a|-l|-n 3|-q
-a:列出当前所有正在运行的容器+历史上运行过的
-l:显示最近创建的容器
-n:显示最近n个创建的容器
-q:静默模式,只显示容器编号
-no-trunc:不截断输出

(3)退出容器
exit  关闭并离开容器
ctrl+p q 离开容器  --> 再进入 docker attach 容器id|容器名

(4)重启容器
docker restart 容器id|容器名

(5)关闭容器
docker stop 容器id|容器名

(6)强制停止容器
docker kill 容器id|容器名

(7)删除已停止容器
docker rm 容器id|容器名
docker rm -f 容器id|容器名
docker rm -f $(docker ps -qa) 或 docker ps -qa | xargs docker rm

(四.1)容器命令重要。
启动守护式容器命令:docker run -d 容器id|容器名,但是docker ps查不到了
这说明:docker容器后台运行,就必须有一个前台进程,容器以后台进程模式运行,
就导致了docker前台没有运行的应用,后台会立即自杀,因为他觉得他没用。所以
最佳的方案是,将运行的程序以前台进程的形式运行。
(以下命令,会不停的输出llsydn,所以这个容器不会自杀)
docker run -d 容器id /bin/sh -c "while true; do echo hello llsydn; sleep 2; done"

查看容器日志:docker logs -f -t --tail 容器id
-f 跟随最新的日志打印(会不停的追加)
-t 加入时间戳
--tail 数字 显示最后多少条

查看容器内运行的进程:docker top 容器id

查看容器内部细节:docker inspect 容器id

重新进入容器:docker attach 容器id (直接进入容器启动命令的终端,不会启动新的进程)
             docker exec 容器id(直接进入容器启动命令的终端,并且可以启动新的进程)
             docker exec -it 容器id ls -l /tmp
             docker exec -it 容器id

从容器内拷贝文件到主机上:docker cp 容器id:容器内路径 目的主机路径
                        docker cp 229d3f057e13:/tmp/yum.log /root


五、Docker镜像
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的
软件,他包含运行某个软件所需的所有内容,包含代码、运行时、库、环境变量和配置文件

(1)UnionFS联合文件系统,Docker镜像加载,分层的镜像

(2)特点:只读;当容器启动,一个新的可写层被加载到镜像的顶部。

(3)docker commit操作。
docker commit 提交容器副本使之成为一个新的镜像。
docker commit -m="描述信息" -a="作者" 容器id 镜像名:[标签名]

docker run -it -p 8888:8080 tomcat(在docker的8080端口启动tomcat,暴露的端口8888)
docker run -it -P tomcat(随机暴露端口)


六、Docker容器数据卷。
容器的持久化,容器间继承+共享数据
(1)容器内添加数据卷。(主机和容器数据的共享)
直接命令添加,docker run -it -v /宿主机绝对路径目录:容器内目录 镜像名
            docker run -it -v /dataVolumeMain:/dataVolumeContainer centos
            使用docker inspect 容器id,可以查看到 "HostConfig": {
                                                    "Binds": [
                                                        "/dataVolumeMain:/dataVolumeContainer"
                                                    ],
                                                 }
            这样的话,在容器里面的dataVolumeContainer目录下,有创建新的文件,
            在主机的dataVolumeMain目录下,也会有的,修改也会同步。容器停止了,主机修改了文件,还是会同步的
            docker run -it -v /dataVolumeMain:/dataVolumeContainer:ro centos (加了ro表示只读)

(2)DockerFile添加数据卷。
vim Dockerfile

# volume test
FROM centos
VOLUME ["/dataVolumeContainer1","/dataVolumeContainer2"]
CMD echo "finished,---------success"
CMD /bin/bash

docker build -f dockerfile文件的路径 -t 镜像名字:[TAG] . (注意最后有个点)


七、Dockerfile解析
Dockerfile是用来构建docker镜像的构建文件,是由一系列命令和参数构成的脚本。
构建三步骤:编写Dockerfile文件 ==> docker build ==> docker run
Dockerfile是软件的原材料;Docker镜像是软件的交付品,Docker容器则可以认为是软件的运行态

(1)docker内容基础知识:
1.每条保留字指令必须为大写字母且后面要跟随至少一个参数
2.指令按照从上到下,顺序执行
3.#表示注释
4.每条指令都会创建一个新的镜像层,并对镜像进行提交

(2)docker执行Dockerfile的大致流程:
1.docker从基础镜像运行一个容器
2.执行一条指令并对容器作出修改
3.执行类似docker commit的操作提交一个新的镜像层
4.docker再基于刚提交的镜像运行一个新容器
5.执行dockerfile中下一条指令直到所有指令都执行完成

(3)Dockerfile保留字指令:
1.FROM 基础镜像,当前镜像是基于哪个镜像
2.MAINTAINER 镜像维护者的姓名和邮箱地址
3.RUN 容器构建时需要运行的命令
4.EXPOSE 当前容器对外暴露的端口
5.WORKDIR 指定在创建容器后,终端默认登陆的进来工作目录,一个落脚点
6.ENV 用来构建镜像过程中设置环境变量
7.ADD 将宿主机目录下的文件拷贝进镜像且ADD命令自动处理URL和解压tar压缩包
8.COPY 类似ADD,但不能自动处理URL和解压tar压缩包
9.VOLUME 容器数据卷,用于数据保存和持久化工作
10.CMD 指定一个容器启动时要运行的命令,Dockerfile中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run 之后的参数替换
11.ENTRYPOINT 指定一个容器启动时要运行的命令,但这个指令不会替换,只是在后面拼接
12.ONBUILD 当构建一个被继承的Dockerfile时运行命令,父镜像在被子继承后父镜像的onbuild被触发

(4)Docker安装mysql
1.下载
docker pull docker
2.运行
docker run -p 12345:3306 --name mysql -v /mysql/conf:/etc/mysql/conf.d -v /mysql/logs:/logs -e MYSQL_ROOT_PASSWORD=123456 -d mysql

(5)Docker安装redis
1.下载
docker pull redis
2.运行
docker run -p 6379:6379 --name redis -v /redis/data:/data -v /redis/conf:/usr/local/etc/redis/redis.conf -d redis redis-server /user/local/etc/redis/redis.conf --appendonly yes
3.启动客户端
docker exec -it 容器id redis-cli

(6)自定义的tomcat9的Dockerfile
FROM centos
MAINTAINER llsydn 1091391667@qq.com
#把java与tomcat添加到容器中
ADD jdk-linux-x64.tar.gz /usr/local/
ADD apache-tomcat.tar.gz /usr/local/
#安装vim编辑器
RUM yum -y install vim
#设置工作访问时候的WORKDIR路径,登录落脚点
ENV MYPATH /usr/local/
WORKDIR $MYPATH
#配置java与tomcat环境变量
ENV JAVA_HOME /usr/local/jdk1.8.0_171
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.8
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.8
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/bin:$CATALINA_BASE/bin
#容器运行时监听的端口
EXPOSE 8080
#启动时运行tomcat
CMD /usr/local/apache-tomcat-9.0.8/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.8/bin/logs/catalina.out


八、Docker本地镜像推送到阿里云
1.在阿里云开发者平台,创建仓库。(命令空间,仓库名称)
https://cr.console.aliyun.com/cn-hangzhou/repositories

2.将镜像推送到阿里云仓库Registry
docker login --username=13543844606li registry.cn-hangzhou.aliyuncs.com
docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/llsydn/mycentos:[镜像版本号]
docker push registry.cn-hangzhou.aliyuncs.com/llsydn/mycentos:[镜像版本号]

本文转载自: 掘金

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

netty框架的学习笔记 + 一个netty实现websoc

发表于 2021-08-13

一、前言

1.什么是netty?

1
2
3
复制代码高性能,事件驱动,异步非堵塞
基于nio的客户端,服务端编程框架(nio的框架)
稳定性和伸缩性

2.netty的使用场景。

1
2
3
复制代码高性能领域
多线程并发领域
异步通信领域

3.学习目录

1
2
3
4
lua复制代码io通信
netty入门
websocket入门
netty实现websocket通信案例

二.java io通信

1
2
3
4
5
6
lua复制代码客户端个数:bio(1:1) 伪异步io(m:n) nio(m:1) aio(m:0)
io类型:bio(阻塞同步io) 伪异步io(阻塞同步io) nio(非阻塞同步io) aio(非阻塞异步io)
api使用难度:bio(简单) 伪异步io(简单) nio(复杂) aio(复杂,但比nio简单)
调试难度:同上
可靠性:bio(差) 伪异步io(差) nio(好) aio(好)
吞吐量:同上

三.netty入门

1
2
复制代码1.原生nio的缺陷。类库和api复杂;入门门槛高;工作量和难度大;jdk nio存在bug
2.netty的优势。api简单;入门门槛低;性能高;成熟、稳定。

四.websocket入门

1
2
3
4
5
6
7
8
9
10
arduino复制代码1.什么是websocket?
h5协议规范;握手机制;解决客户端与服务端实时通信而产生的技术。
2.websocket的优点?
节省通信开销;服务器主动传送数据给客户端;实时通信;
3.websocket建立连接。
客户端发起握手请求;服务端响应请求;连接建立。
4.websocket生命周期。
打开事件;消息事件;错误事件;关闭事件。
5.websocket关闭连接。
服务端关闭底层tcp连接;客户端发起tcp close。

五.netty实现websocket通信案例。

1.功能介绍

1
css复制代码netty开发服务端;html实现客户端;实现服务端和客户端的实时交互。

2.代码实现

2.1存储工厂的全局配置

1
2
3
4
5
6
7
8
9
java复制代码package com.websocket.netty;

import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

/** * @author lilinshen * @title 存储工厂的全局配置 * @description 请填写相关描述 * @date 2018/5/23 10:32 */ public class NettyConfig {
/** * 储存每一个客户端进来时的 channel 对象 */ public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}

2.2处理/接收/响应客户端websocket请求的核心业务处理类

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
java复制代码package com.websocket.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;

import java.util.Date;

/** * @author lilinshen * @title 处理 / 接收 / 响应客户端 websocket 请求的核心业务处理类 * @description 请填写相关描述 * @date 2018/5/23 10:36 */ public class MyWebSocketHandler extends SimpleChannelInboundHandler<Object> {

private WebSocketServerHandshaker handshaker;
private static final String WEB_SOCKET_URL = "ws://localhost:8888/websocket" ;

/** * 客户端与服务端连接的时候调用 * * @param ctx * @throws Exception */ @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
NettyConfig.channelGroup.add(ctx.channel());
System.out.println( " 客户端与服务端连接开启 ..." );
}

/** * 客户端与服务端断开连接的时候调用 * * @param ctx * @throws Exception */ @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
NettyConfig.channelGroup.remove(ctx.channel());
System.out.println( " 客户端与服务端连接关闭 ..." );
}

/** * 服务端接收客户端发送过来的数据结束之后调用 * * @param ctx * @throws Exception */ @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}

/** * 工程出现异常的时候调用 * * @param ctx * @param cause * @throws Exception */ @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}

/** * 服务端处理客户端 websocket 请求的核心方法 * * @param channelHandlerContext * @param o * @throws Exception */ @Override
protected void messageReceived(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
if (o instanceof FullHttpRequest) {
// 处理客户端向服务端发起 http 握手请求的业务 handHttpRequest(channelHandlerContext, (FullHttpRequest) o);
} else if (o instanceof WebSocketFrame) {
// 处理 websocket 连接业务 handWebsocketFrame(channelHandlerContext, (WebSocketFrame) o);
}
}

/** * 处理客户端向服务端发起 http 握手请求的业务 * * @param ctx * @param request */ private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
if (!request.getDecoderResult().isSuccess() || !( "websocket" ).equals(request.headers().get( "Upgrade" ))) {
sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
}
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(WEB_SOCKET_URL, null, false);
handshaker = wsFactory.newHandshaker(request);
if (null == handshaker) {
WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
} else {
handshaker.handshake(ctx.channel(), request);
}
}

/** * 服务端向客户端响应消息 * * @param ctx * @param request * @param response */ private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, DefaultFullHttpResponse response) {
if (response.getStatus().code() != 200) {
ByteBuf buf = Unpooled.copiedBuffer(response.getStatus().toString(), CharsetUtil.UTF_8);
response.content().writeBytes(buf);
buf.release();
}
// 服务端向客户端发送数据 ChannelFuture channelFuture = ctx.channel().writeAndFlush(response);
if (response.getStatus().code() != 200) {
channelFuture.addListener(ChannelFutureListener.CLOSE);
}
}

/** * 处理客户端与服务端之间的 websocket 业务 * * @param ctx * @param frame */ private void handWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
// 判断是否是关闭 websocket 的指令 if (frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(), ((CloseWebSocketFrame) frame).retain());
}
// 判断是否是 ping 的消息 if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
return;
}
// 判断是否是二进制消息,如果是二进制消息,抛出异常 if (!(frame instanceof TextWebSocketFrame)) {
System.out.println( " 目前我们不支持二进制消息 ..." );
throw new RuntimeException( " 【 " + this.getClass().getName() + " 】不支持消息 ..." );
}
// 返回应答消息 // 获取客户端向服务端发送的消息 String request = ((TextWebSocketFrame) frame).text();
System.out.println( "===>>>" + request);
TextWebSocketFrame tws = new TextWebSocketFrame(new Date().toString() + ctx.channel().id() + "===>>>" + request);
// 群发,服务端向每个连接上来的客户端发消息 NettyConfig.channelGroup.writeAndFlush(tws);
}
}

3.初始化连接时的各个组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
scala复制代码package com.websocket.netty;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;

/** * @author lilinshen * @title 初始化连接时的各个组件 * @description 请填写相关描述 * @date 2018/5/23 11:12 */ public class MyWebSocketChannelHander extends ChannelInitializer<SocketChannel> {

@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast( "http-codec" , new HttpServerCodec());
socketChannel.pipeline().addLast( "aggregator" , new HttpObjectAggregator(65536));
socketChannel.pipeline().addLast( "http-chunked" , new ChunkedWriteHandler());
socketChannel.pipeline().addLast( "handler" , new MyWebSocketHandler());
}
}

4.程序的入口,负责启动应用

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
java复制代码package com.websocket.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/** * @author lilinshen * @title 程序的入口,负责启动应用 * @description 请填写相关描述 * @date 2018/5/23 11:17 */ public class Main {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new MyWebSocketChannelHander());
System.out.println( " 服务端开启等待客户端连接 ..." );
Channel channel = bootstrap.bind(8888).sync().channel();
channel.closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 优雅的退出程序 bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}

5.websocket.html客户端代码。

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
xml复制代码<html>
<head>
<meta http-equiv= "Content-Type" content= "text/html;charset=utf-8" />
<title>websocket客户端</title>
<script type= "text/javascript" >
var socket;
if (!window.WebSocket) {
window.WebSocket = window.MozWebSocket;
}
if (window.WebSocket) {
socket = new WebSocket( "ws://localhost:8888/websocket" );
socket.onmessage = function (ev) {
var ta = document.getElementById( "responseContent" );
ta.value += ev.data + " \r\n " ;
}
socket.onopen = function (ev) {
var ta = document.getElementById( "responseContent" );
ta.value += " 您当前的浏览器支持 websocket ,请进行后续操作 \r\n " ;
}
socket.onclose = function (ev) {
var ta = document.getElementById( "responseContent" );
ta.value = "" ;
ta.value = "websocket 连接已经关闭 \r\n " ;
}
} else {
alert( " 您的浏览器不支持 websocket" );
}
function send(message) {
if (!window.WebSocket) {
return;
}
if (socket.readyState == WebSocket.OPEN) {
socket.send(message);
} else {
alert( "websocket 连接没有建立成功 " );
}
}
</script>
</head>
<body>
<form onsubmit= " return false; " >
<input type= "text" name= "message" value= "" />
<br/><br/>
<input onclick= " send(this.form.message.value) " type= "button" value= " 发送 websocket 请求消息 " />
<hr color= "red" />
<h2>客户端接收到服务端返回的应答消息</h2>
<textarea id= "responseContent" style= " width:1024px;height:300px; " ></textarea>
</form>
</body>
</html>

6.启动。

1.main.java类是程序的入口,负责启动应用。

2.将websocket.html在浏览器中打开,就可以建立一个websocket连接。

本文转载自: 掘金

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

一篇学会Redis哨兵模式✔高可用✔集群✔ 搭建详细教程

发表于 2021-08-13

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

一、Redis下载及安装

)​

1、下载地址

distfiles.macports.org/redis/redis…

)​

2、解压Redis安装包

1
2
3
4
5
6
7
8
9
10
11
csharp复制代码#解压redis
[root@open-falcon mnt]# tar -zxvf redis-5.0.7.tar.gz

#解压得到包如下
[root@open-falcon mnt]# cd redis-5.0.7

#包内文件如下
[root@open-falcon redis-5.0.7]# ls
00-RELEASENOTES CONTRIBUTING deps Makefile README.md runtest runtest-moduleapi sentinel.conf tests
BUGS COPYING INSTALL MANIFESTO redis.conf runtest-cluster runtest-sentinel src utils
[root@open-falcon redis-5.0.7]#

3、Redis编译安装

1
2
3
4
5
csharp复制代码#进入解压包
[root@open-falcon redis-5.0.7]# cd redis-5.0.7

#编译&安装
[root@open-falcon redis-5.0.7]# make && make install

三、集群搭建配置

1、总体构造

Redis高可用集群,一般选三台服务器:这样,我们在一台主节点挂了之后,另外两台可以选举其中一台担任主节点master的角色。

①slave节点配置从master节点同步数据

②哨兵sentinel故障转移机制:每个redis节点搭建一个监控哨兵,当redis的master节点挂掉之后:三个哨兵选举一个slave节点 成为master节点

1
2
3
4
5
6
7
8
9
10
11
yaml复制代码#部署在同一台服务器上,使用3个端口对外提供服务

主节点master
1台
Redis服务 192.168.154.145 6379
sentinel哨兵 192.168.154.145 26379

从节点slave
2台
Redis服务 192.168.154.145 27001
sentinel哨兵 192.168.154.145 27002

2、伪集群

由于现场没有三台服务器,我们就在一台服务器上,使用3个端口,来给他搭一个”伪集群”:

何为”伪集群”呢?

“伪集群” 架构与真正集群完全一样,故障转移、高可用都可以。

只是搭建在一台服务器上,使用3个端口对外提供服务。

生产环境下,集群环境还是需要使用3台不同的服务器,从而保证高可用。这里只是节约资源、方便搭建。

3、主节点redis服务6379配置

完整配置及步骤如下:

1
2
3
4
5
bash复制代码1、创建主节点工作目录 redis-6379
mkdir -p /usr/local/redis-6379

2、将配置文件拷贝到工作目录下
cp redis-5.0.7/redis.conf /usr/local/redis-6379

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bash复制代码3、主节点 6379 端口配置
#打开主节点redis.conf
vim /usr/local/redis-6379/redis.conf

4、修改如下内容:左侧为修改的行数,方便大家查找
69 bind 0.0.0.0
92 port 6379
136 daemonize yes
158 pidfile /var/run/redis_6379.pid
171 logfile "/usr/local/redis-6379/redis-6379.log"
#默认配置不用修改
218 save 900 1
219 save 300 10
220 save 60 10000
#全量备份文件名
253 dbfilename redis-6379.rdb
#指定路径名
263 dir /usr/local/redis-6379
#设置redis密码
509 requirepass 123456
#开启增量备份
701 appendonly yes
705 appendfilename "appendonly-6379.aof"

4、redis从节点服务 7001/7002配置

注意,从节点与主节点配置的区别主要在两个地方

①配置主节点:replicaof 192.168.154.145 6379

②配置主节点的登录密码masterauth 123456

完整配置及步骤如下:

1
2
3
bash复制代码1、创建从节点工作目录 redis-7001 和 redis-7002
mkdir -p /usr/local/redis-7001
mkdir -p /usr/local/redis-7002

1
2
3
bash复制代码2、将配置文件拷贝到工作目录下
cp redis-5.0.7/redis.conf /usr/local/redis-7001
cp redis-5.0.7/redis.conf /usr/local/redis-7002

1
2
3
4
5
6
bash复制代码3、从节点 7001 / 7002 端口配置

#分别打开从节点redis.conf

vim /usr/local/redis-7001/redis.conf
vim /usr/local/redis-7002/redis.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bash复制代码4、7001 从节点配置,左侧为修改的行数,方便大家查找

69 bind 0.0.0.0
92 port 7001
136 daemonize yes
158 pidfile /var/run/redis_7001.pid
171 logfile "/usr/local/redis-7001/redis-7001.log"
#默认配置不用修改
218 save 900 1
219 save 300 10
220 save 60 10000
#全量备份文件名
253 dbfilename redis-7001.rdb
#指定路径名
263 dir /usr/local/redis-7001
#设置主节点 地址端口+主节点连接密码
286 replicaof 192.168.154.145 6379
293 masterauth 123456
#设置redis密码
509 requirepass 123456
#开启增量备份
701 appendonly yes
705 appendfilename "appendonly-7001.aof"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bash复制代码5、7002 从节点配置,左侧为修改的行数,方便大家查找

69 bind 0.0.0.0
92 port 7002
136 daemonize yes
158 pidfile /var/run/redis_7002.pid
171 logfile "/usr/local/redis-7002/redis-7002.log"
#默认配置不用修改
218 save 900 1
219 save 300 10
220 save 60 10000
#全量备份文件名
253 dbfilename redis-7002.rdb
#指定路径名
263 dir /usr/local/redis-7002
#设置主节点 地址端口+主节点连接密码
286 replicaof 192.168.154.145 6379
293 masterauth 123456
#设置redis密码
509 requirepass 123456
#开启增量备份
701 appendonly yes
705 appendfilename "appendonly-7002.aof"

四、故障转移/高可用配置(哨兵模式sentinel)

1、总体架构

1
2
3
4
5
6
7
8
9
yaml复制代码主节点哨兵:

6379端口redis主服务,哨兵默认端口为26379

从节点哨兵:

7001端口redis服务,哨兵使用27001端口

7002端口redis服务,哨兵使用27002端口

2、主节点master配置哨兵sentinel

为了方便管理,我们把哨兵工作目录分别建在对应的redis工作目录下

注意:

①配置master的地址及命名(mymaster为集群名称):sentinel monitor mymaster 192.168.154.145 6379 2

②配置连接redis主节点的密码 sentinel auth-pass mymaster 123456

③日志目录一定要创建,因为出问题,方便查找原因。 logfile “/usr/local/redis-6379/sentinel/redis-sentinel.log”

1
2
bash复制代码1、创建主节点 sentinel工作目录
mkdir -p /usr/local/redis-6379/sentinel/

1
2
bash复制代码2、将配置文件拷贝到sentinel工作目录下
cp redis-5.0.7/sentinel.conf /usr/local/redis-6379/sentinel/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bash复制代码3、修改配置文件 左侧为修改的行数,方便大家查找
vim /usr/local/redis-6379/sentinel/sentinel.conf

#配置哨兵端口
21 port 26379
#配置进程id存储地址
32 pidfile "/usr/local/redis-6379/redis-sentinel-26379.pid"
#配置log路径
37 logfile "/usr/local/redis-6379/sentinel/redis-sentinel.log"
#配置哨兵工作目录
64 dir "/usr/local/redis-6379/sentinel"
#配置监控的redis主节点
112 sentinel monitor mymaster 192.168.154.145 6379 2
#配置主节点登录密码
120 sentinel auth-pass mymaster 123456

3、从节点slave配置哨兵sentinel

①从节点的配置,除了工作目录不同及端口,其余与主节点完全一致

1
2
3
bash复制代码1、创建从节点 sentinel工作目录
mkdir -p /usr/local/redis-7001/sentinel/
mkdir -p /usr/local/redis-7002/sentinel/

1
2
3
bash复制代码2、将配置文件拷贝到sentinel工作目录下
cp redis-5.0.7/sentinel.conf /usr/local/redis-7001/sentinel/
cp redis-5.0.7/sentinel.conf /usr/local/redis-7002/sentinel/

1
2
3
bash复制代码3、分别修改slave节点 哨兵配置文件
vim /usr/local/redis-7001/sentinel/sentinel.conf
vim /usr/local/redis-7002/sentinel/sentinel.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
python复制代码4、27001节点哨兵的配置 左侧为修改的行数,方便大家查找

#配置哨兵端口
21 port 27001
#配置进程id存储地址
32 pidfile "/usr/local/redis-7001/redis-sentinel-27001.pid"
#配置log路径
37 logfile "/usr/local/redis-7001/sentinel/redis-sentinel.log"
#配置哨兵工作目录
64 dir "/usr/local/redis-7001/sentinel"
#配置监控的redis主节点
112 sentinel monitor mymaster 192.168.154.145 6379 2
#配置主节点登录密码
120 sentinel auth-pass mymaster 123456

1
2
3
4
5
6
7
8
9
10
11
12
13
14
python复制代码5、27002哨兵节点的配置 左侧为修改的行数,方便大家查找

#配置哨兵端口
21 port 27002
#配置进程id存储地址
32 pidfile "/usr/local/redis-7002/redis-sentinel-27002.pid"
#配置log路径
37 logfile "/usr/local/redis-7002/sentinel/redis-sentinel.log"
#配置哨兵工作目录
64 dir "/usr/local/redis-7002/sentinel"
#配置监控的redis主节点
112 sentinel monitor mymaster 192.168.154.145 6379 2
#配置主节点登录密码
120 sentinel auth-pass mymaster 123456

到此全部配置完成。

五、启动集群

1、启动所有redis服务节点

1
2
3
4
csharp复制代码#分别启动三个节点的redis服务
[root@mail ~]# redis-server /usr/local/redis-6379/redis.conf
[root@mail ~]# redis-server /usr/local/redis-7001/redis.conf
[root@mail ~]# redis-server /usr/local/redis-7002/redis.conf

2、启动所有哨兵sentinel服务

1
2
3
4
csharp复制代码#启动时,根据配置文件启动
[root@mail ~]# redis-sentinel /usr/local/redis-6379/sentinel/sentinel.conf
[root@mail ~]# redis-sentinel /usr/local/redis-7001/sentinel/sentinel.conf
[root@mail ~]# redis-sentinel /usr/local/redis-7002/sentinel/sentinel.conf

3、查看服务进程

1
2
3
4
5
6
7
8
yaml复制代码#如下图:三个redis服务+三个哨兵监控 全部启动成功
[root@mail ~]# ps aux|grep redis
root 50845 0.0 0.1 158068 3904 ? Ssl 07:50 0:42 redis-server 0.0.0.0:6379
root 50166 0.0 0.1 161652 3768 ? Ssl 07:39 0:43 redis-server 0.0.0.0:7001
root 50173 0.1 0.2 161652 4884 ? Ssl 07:40 0:53 redis-server 0.0.0.0:7002
root 50370 0.1 0.1 152436 3692 ? Ssl 07:43 1:14 redis-sentinel *:26379 [sentinel]
root 50375 0.1 0.0 152436 1748 ? Ssl 07:43 1:15 redis-sentinel *:27001 [sentinel]
root 50431 0.1 0.0 152436 1712 ? Ssl 07:43 1:14 redis-sentinel *:27002 [sentinel]

六、测试:数据同步+读写分离+故障转移

1、数据同步测试

测试目标:master主节点 写入/修改/删除数据,slave从节点会自动同步master的数据操作记录。

① redis-cli链接master主节点,写入age和name 两个数据

1
2
3
4
5
6
7
ruby复制代码#链接redis,设置两个值,如下
[root@mail ~]# redis-cli -a 123456 -p 6379
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> set age 25
OK
127.0.0.1:6379> set name lex
OK

② 登录slave从节点查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ruby复制代码#登录slave从节点
[root@mail ~]# redis-cli -a 123456 -p 7001
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

#查看所有数据
127.0.0.1:7001> keys *
1) "age"
2) "name"

#查看age数据
127.0.0.1:7001> get age
"25"

#尝试修改数据
127.0.0.1:7001> set age 30
(error) READONLY You can't write against a read only replica.
#修改失败,slave从节点 数据只读

2、master/slave节点查看

① 链接哨兵:查看master节点和slave节点的分配情况

② 链接命令:redis-cli -a 123456 -p 26379 ,同样使用redis-cli,只是链接到sentinel的端口即可

查看master主节点地址:6379端口服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ruby复制代码#链接哨兵sentinel 同样使用redis-cli
[root@mail ~]# redis-cli -a 123456 -p 26379
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
#查看mymaster(我们配置的集群名) 集群的master 是哪个
127.0.0.1:26379> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "192.168.154.145"
5) "port"
6) "6379"
7) "runid"
8) "105cc1c986fcda65ad4ddba69012e8e6e0073e1f"
9) "flags"
10) "master"
...

③ 查看slave节点地址:两个slave节点分别是7001和7002服务,可以看到两个slave节点指向的master节点为6379

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
ruby复制代码#命令如下:
127.0.0.1:26379> sentinel slaves mymaster
1) 1) "name"
2) "192.168.154.145:7001"
3) "ip"
4) "192.168.154.145"
5) "port"
6) "7001"
9) "flags"
10) "slave"
31) "master-link-status"
32) "ok"
33) "master-host"
34) "192.168.154.145"
35) "master-port"
36) "6379"
...

2) 1) "name"
2) "192.168.154.145:7002"
3) "ip"
4) "192.168.154.145"
5) "port"
6) "7002"
9) "flags"
10) "slave"
31) "master-link-status"
32) "ok"
33) "master-host"
34) "192.168.154.145"
35) "master-port"
36) "6379"
...

3、故障转移(高可用)测试

测试目的:master主节点挂掉之后,我们的sentinel集群可以通过选举机制,重新在slave节点中选举一个作为新的master节点。

① 关闭主节点6379

1
2
3
4
5
csharp复制代码#链接主节点,shutdown关闭主节点
[root@mail ~]# redis-cli -a 123456 -p 6379
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> shutdown
not connected>

② 查看此时的master节点

链接到哨兵,查看主节点:此时,经过三个哨兵的选举,主节点变为 7002

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ruby复制代码#链接到哨兵,查看主节点:主节点变为 7002
[root@mail ~]# redis-cli -a 123456 -p 26379
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:26379> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "192.168.154.145"
5) "port"
6) "7002"
7) "runid"
8) "105cc1c986fcda65ad4ddba69012e8e6e0073e1f"
9) "flags"
10) "master"
...
127.0.0.1:26379>

③此时,再查看slave节点的情况

发现slave节点变为:6379和7001两个,而且6379的状态为 “s_down,slave,disconnected”,因为我们关闭了6379

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
ruby复制代码[root@mail ~]# redis-cli -a 123456 -p 26379
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:26379> sentinel slaves mymaster
1) 1) "name"
2) "192.168.154.145:6379"
3) "ip"
4) "192.168.154.145"
5) "port"
6) "6379"
7) "runid"
8) "e740b7285067b2dbb8c2640745196099460f4bb3"
9) "flags"
10) "s_down,slave,disconnected"

...
2) 1) "name"
2) "192.168.154.145:7001"
3) "ip"
4) "192.168.154.145"
5) "port"
6) "7001"
7) "runid"
8) "19a7647509ba020adfd06b1eb40f1f35d06c549d"
9) "flags"
10) "slave"
...
127.0.0.1:26379>

④ 所以,故障转移就成功了。

4、读写分离测试

① redis集群开启之后,slave从节点的权限 默认是只读的;当然我们也可以开启slave节点的写入权限。

所以,写数据:链接master主节点;读数据:链接slave从节点。

如下图:slave从节点,没有写入权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ruby复制代码#登录slave从节点
[root@mail ~]# redis-cli -a 123456 -p 7001
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

#查看所有数据
127.0.0.1:7001> keys *
1) "age"
2) "name"

#查看age数据
127.0.0.1:7001> get age
"25"

#尝试修改数据
127.0.0.1:7001> set age 30
(error) READONLY You can't write against a read only replica.
#修改失败,slave从节点 数据只读

本文转载自: 掘金

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

基于系统稳定性建设,你做了哪些事情?(上)

发表于 2021-08-13

这是我经常问的一个问题,无论是在面试一些高P的时候,还是在晋升答辩当评委的时候。绝大多数啊的答案是:限流、降级、熔断。但是,成体系性的答案,基本上很少有人说出来。

首先,什么是系统可靠性(Reliability)和可用性(Availability)?而什么又是系统稳定性(Stability)?

系统可靠性:高可靠的系统,故障次数少,频率低,在较长的时间内无故障地持续运行。

系统可用性:高可用的系统,故障时间少,止损快,在任何给定的时刻都可以及时地工作。

举个“分布式系统原理与范型”书的第八章的例子:

如果系统在每小时崩溃1ms,那么它的可用性就超过99.9999%,但它还是高度不可靠的。

如果系统从来不崩溃,但要在每年八月中停机两个星期,那么它是高度可靠的,但是系统的可用性只有96%。

系统稳定性,则是在系统可靠性和可用性之上,即降低故障频次和提升止损速度的情况下,要求系统的性能稳定,不要时快时慢。

换言之,我不但需要系统尽可能地随时可以给我提供服务,并且,我希望系统能给我提供有质量保障的服务。

那么,回到最初的问题,基于系统稳定性建设,我们可以做哪些事情?

1、提升系统可靠性,减少故障次数

图片

代码规范:

Java工程师,我认为阿里那套代码规范,应该足可以了。当然,我个人会根据多年的踩坑经验,多增加几条红线的规范。

  • mybatis select查询语句,必须加limit,且limit不超过100;没加limit,查出来100w条记录,可以把网卡全部打满。
  • mybatis select查询语句,where后面的条件项,必须有一列是必选项,且该列是加了索引的;几亿数据的大表,因为没走索引而导致全表扫描,数据库直接干挂。
  • mybatis update、delete语句,单场景单写SQL,不允许出现动态拼接SQL,且where后面的条件项,必须有一列走索引,且该索引的区分度要高;update、delete为动态拼接SQL,且漏传了条件项,会导致全表被修改或删除。
  • java工程的循环语句中,不允许进行rpc和db操作;过多的循环,可以把下游和db直接干挂,索性一刀切。

上线规范:

  • 重要等级高的服务,必须TL review过后,才能上线。
  • 重要等级高的服务,必须有灰度策略,才能上线。
  • 重要等级高的服务,如果代码改动量大,影响范围大,必须选择不影响业务的时间上线。(如果没有,那就选择业务低峰期上线)

故障复盘:

有句话,成年人最好的学习方式就是复盘,用在系统工程上,一样是真理。既然我们已经出故障交了昂贵的学费,那么我们就希望这个学费交得足够值得。

  • 5 whys,由点及面,深挖问题根因,在复盘过程中,杜绝出现这种“忘了”,“没有仔细”,“没有及时”,“时间太紧”等敷衍、流于形式的字眼,从而避免同样的、同一类型的故障反复出现。
  • 根据“重要紧急”、“重要不紧急”两个维度,制定短期和中期todo,明确执行人以及完成时间,并持续地监督跟进,直到完成所有的todo。且,todo要是真实可落地的,避免出现“下次认真点儿”,“下次高度重视”,“下次提高警惕”等比较务虚的片儿汤话。
  • 明确故障责任人。有错就要认,挨打要立正,出了问题一定要站出来担责,不要担心会产生“多做多错”的问题,因为我们要制定“奖惩规则”,多做属于奖励范畴,多错属于惩罚范畴,其实这是不矛盾的。

压测限流:

通过把系统峰值流量,进行流量回放的方式在生产环境进行压测,已经成了通用的解决方案,我们可以用逐渐加压的方式,1倍、1.5倍、2倍、2.5倍、3倍等,最终压测出,当前系统可以承载线上峰值流量的几倍,然后根据此,设置出来限流的阈值(通常会比可承载的线上峰值流量低一些,以避免误差),起到避免系统过载而挂掉的作用。

  • 我们的限流最好是多层次的,包括系统的整体限流,以及各核心接口、或者qps高、rt时间长的接口限流。
  • 压测属于日常巡检行为,根据上线发布频次以及系统的重要性来制定巡检节奏,建议高重要等级服务每两周压测一次。

防刷:

防外,也防内。

外部,DDoS攻击,全称Distributed Denial of Service,中文意思为“分布式拒绝服务”,就是利用大量合法的分布式服务器对目标发送请求,从而导致正常合法用户无法获得服务。

内部,防止上游服务、客户端、前端由于错误地定时刷新代码,而导致下游服务在短时间内,被反复执行相同请求。\

实现倒是相对简单,用户名/ip + 方法名作为redis的key,然后把过期时间设置为防刷间隔阈值即可,不过这种规范适合业务聚合的大接口,非by id型接口,同时也需要跟上游制定好规范,以防正常业务使用的时候被误杀。

另外,对于绝对意义上,半分钟也不能挂的核心服务,还有需要遵循的原则就是:

  • 最小链路闭环,尽量少依赖服务,尽量少依赖中间件。(保证自己不挂)
  • 最大限度隔离,不相干的非核心服务,尽量分开部署隔离(不被别人拖挂)

接下来会继续给大家讲,如何提升系统可用性,及系统稳定性\

未央。

本文转载自: 掘金

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

Java八股文-详细版

发表于 2021-08-13

写在最前

现在大三,双非二本,就业压力,内卷左移,我是废物…好久之前,我的朋友们建议我做一个八股文整合,我拒绝了,因为太多了!

于是我们找了一堆八股文提纲,但是每个提纲只是提纲,细致的来说还需要一篇篇介绍,于是有了这个。本文不是八股文背诵讲义,仅仅是我看到的,一些八股文可能会考的,但是讲解比较深入的文章的合计,出处即链接🔗。

本文持续更新…可以收藏或点赞以获取更新状态。

GitHub上的八股文

  • ⚽️JavaGuide
  • 🏀Java3y
  • 🏐阿秀的校招笔记
  • 🎾Java Notes

操作系统

进程/线程

进程是最基本的执行体,它拥有一个执行单元最基本的特征,包括PC,寄存器,堆栈,打开文件集,PID等。一个进程可以fork出子进程,在Linux中,子进程和父进程共享只读空间。写时复制技术用于子进程想写数据时。

每个进程有一个主线程,线程作为进程执行的实体,一个进程可以拥有多个线程,归属于同一个进程的线程共享全部进程资源。对于内核而言,调度线程取决于线程的类型。

首先,线程分为两种:用户线程和内核线程。内核线程由内核进程创建,内核本质是一个进程,它所创建的所有线程都叫内核线程;用户线程由非内核进程创建。对于内核线程,调度单位是线程,对于用户线程,调度单位是它所属的进程,所以同一进程内的用户线程需要自行安排调度规则,如果某个用户进程阻塞,则会阻塞整个进程。

为了把用户线程调度的负担丢给内核,出现了LWP轻量级进程,它只有一个主线程,每次创建线程不是在它内部创建线程,而是创建一个新的LWP。前面说过用户线程调度单位是进程,如果一个线程对应一个进程,那就可以实现每个线程由内核调度了,所以LWP就做到了这件事,同时它的组成相对于普通进程更加轻量级。

Linux和Windows都实现了这种策略,即每个线程对应一个进程,让内核调度线程,这样即不用通过创建内核线程这种高昂的方式实现内核调度,也实现了内核对用户线程的调度。

  • 🪂普通线程和内核线程
  • 🏇进程间通信和线程间通信的几种方式

内存管理

文件系统

I/O

同步和锁

这里只说死锁问题。

死锁产生的四个条件

  • 🤺互斥条件:一个资源要么被占有且不可用,要么可用(有些资源可以同时被多个线程占有,这就不能构成死锁)。
  • ⚾️占有与等待:一个拥有了资源的进程可以继续申请其他资源。
  • 🏓不可抢占:其他进程无法强制抢夺某个进程已经获取的资源。
  • 🏸环路等待:发生死锁的系统,必然存在两个或以上的进程互相等待对象资源的释放。

死锁处理:

  • 死锁预防:在程序运行之前,打破四个条件之一进行预防。
  • 死锁避免:银行家算法确保程序处于安全状态,进而避免进入死锁状态。
  • 死锁检测与恢复:通过有向环路(单个资源)/资源分配图(类似多个资源的银行家算法)进行死锁检测,如果发生了死锁则使用杀死进程/回滚进程/抢占资源三个方式之一进行恢复。

当程序已经开始运行了,检测死锁常用的是有向环路检测,通过A->B来表明A等待B的资源释放,多个进程组成一个有向图,如果出现环,说明出现死锁。

当程序还未开始运行呢,检测死锁常用的就是银行家算法,通过判断系统是否处于安全状态来允许多个进程执行与否。所谓的安全状态指的是,系统从执行第一个线程开始,到所有线程执行结束,都不会发生死锁,那么就成执行前的系统状态未安全状态。

单个资源的银行家算法比较简单,我们直接看多个资源的:

假设有M个进程,N个资源,每种资源的个数为:ResN,每个进程需要的每个资源数为Req[M]N;每个进程已经获得的每个资源数为:Have[M][N],还需要的资源为:Need[M][N]。

  • 1️⃣找到一个线程i,使它第j个资源得到满足,即Need[i][j] <= Res[j]-(Have[0][j] + … + Have[M-1][j]);即所需资源数<=这种资源剩余数。
  • 2️⃣j = j+1
  • 3️⃣重复1️⃣
  • 4️⃣如果i不满足,i = i+1,重复1️⃣
  • 5️⃣如果j == N,释放i占有的全部资源,继续1️⃣。

其他比如两阶段锁和通信锁,活锁解决均比较简单。

处理器架构(偏底层)

程序优化(偏底层)

计算机网络

DNS解析

查询过程:浏览器缓存=>操作系统缓存=>本地域名解析器缓存(比如路由器缓存)=>本地域名服务器=>根域名服务器=>一级域名服务器=>权限域名服务器=>IP地址。

HTTP

关于HTTP1.0的“一问一答”和HTTP1.1的“流水线”以及HTTP2的“多路复用”。

版本 技术 意义
HTTP1.0 一问一答式 早期内存金贵的很,所以对于连接(大约占用8KB)要尽快关闭好,这也造就了早期HTTP是一问一答,一次请求一次应答,然后结束
HTTP1.1 流水线式 一问一答每次都要开启一次TCP连接,代价太大,于是一次连接发送多个请求变成一种方案,但是响应必须按照请求顺序,否则会乱序,这就有可能造成队头阻塞,即前一个请求的响应阻塞会影响后面的请求的响应
HTTP2.0 多路复用 此时的HTTP基于二进制传输,每次传输单位为流,流由多个帧组成,每个帧指出它属于谁(头部有唯一标识),接收端不停地接收帧,根据所属流进行聚合,以此保证不会发生队头阻塞

WebSocket

  • 🐤WebSocket面试

Java

  • 🐶Java 面试知识点【背诵版 240题 约7w字】
  • 🐱JVM-锁、JMM、并发
  • 🦆JVM-GC全家桶
  • 🐍Java锁事
  • 🦅并发下的内存分配问题
  • 🦜HashMap中capacity、loadFactor、threshold、size等概念的解释
  • 🐓Java8 中 ConcurrentHashMap工作原理的要点分析

MySQL

MySQL中的redolog主要通过记录修改前的数据来实现回滚操作,undolog只要用于持久化修改,因为数据修改需要先把数据加载到buffer中再修改,然后写回磁盘,undolog是追加写的方式,属于顺序I/O,更快一些。具体操作是:写buffer=>写undolog=>异步刷新磁盘(通过定时刷新等手段)。

MySQL为了防止死锁,除了InnoDB提供的资源申请图死锁检测之外,还有一次性锁,就是一口气把所有需要的资源全部加锁。

与之对应的是MySQL提供的两阶段锁,本意就是把所有的加锁操作放到一起,加锁阶段只加不释放,之后就是解锁阶段,只释放不加。两阶段锁的引入是为了解决事务可串行化问题;这样虽然提升了并发度,但是引入了死锁问题。

  • 🍎InnoDB存储结构
  • 🍐InnoDB四大索引
  • 🍋InnoDB可能用到的锁
  • 🍌MySQL两阶段锁协议、死锁以及死锁检测
  • 🍉Innodb中的事务隔离级别和锁的关系此文约等于🍋+🍌
  • 🍇导致MySQL索引失效的几种常见写法
  • 🍓redolog与undolog
  • 🥝MySQL分页查询limit优化
  • 🍅MySQL优化大全
  • 🥕最左匹配原则到底是什么
  • 🌽MySQL - JOIN详解

NoSQL

  • 🍒Redis与MySQL双写一致性如何保证?
  • 🍑硬核!16000 字 Redis 面试知识点总结,建议收藏!
  • 🌶一文掌握Redis的三种集群方案
  • 🥦分布式锁-Redis/Zookeeper?
  • 🥥Redis的8大应用场景
  • 🍍Redis实现延时队列
  • 🍆Redis事务
  • 🥭Redis优化

在这里简述一下Redis集群:

主从集群很简单,从服务器申请同步(发送SYNC),主服务器发送快照文件以及生成快照期间发生的写命令给从服务器,从服务器利用快照文件和写命令初始化数据库,然后主服务器每次有写命令,都同步给从服务器。

哨兵集群实现了故障转移:

  • 哨兵只会监控主服务器,并且会发送INFO指令获取主服务器的信息,这些信息包括监控这台主服务器的其他哨兵,和主服务器的从服务器。
  • 如果发现了主服务器的从服务器,哨兵会建立与从服务器的连接;如果发现了其他哨兵,则会建立与其他哨兵的连接。
  • 与从服务器的连接是为了实现故障转移,与其他哨兵的连接是为了实现投票机制等。
  • 当主服务器挂了,哨兵会经历主观下线,客观下线,投票选出主哨兵,并由主哨兵发起故障转移。

Spring

  • 🥬Spring中Bean的生命周期是怎样的? - 大闲人柴毛毛的回答 - 知乎
  • 🥒Spring源码剖析——依赖注入实现原理
  • 🌽Spring AOP的实现原理 ? - bravo1988的回答 - 知乎
  • 🥕Spring事务失效的 8 大原因,这次可以吊打面试官了!

分布式

Kafka

  • 🚲Kafka面试
  • 🛵Kafka重平衡
  • 🏍Kafka导致重复消费原因和解决方案
  • 🛴Kafka丢不丢消息?

其他

  • 🚗限流算法
  • 🚑布隆过滤器

集群

Zookeeper

  • 🚌Zookeeper简述
  • 🛴Zookeeper的ZAB协议
  • 🚁通俗易懂 强一致性、弱一致性、最终一致性、读写一致性、单调读、因果一致性 的区别与联系 - Pickle Pee的文章 - 知乎

在这里我总结一下分布式系统的一致性问题:

分布式系统的一致性分为两种:强一致性和弱一致性。

  • 强一致性:指的是主库(主服务)同步对从库(从服务)写入数据,每次写入需要等待从库给予响应才算结束,可以保证主从库绝对的一致,但是缺点就是响应时间过久,因为写入从库涉及到很多的网络I/O。
  • 弱一致性:指的是主库异步对从库写入数据,每次发起对从库的写,但是不会等待回应,因此整个请求只需要等待主库完成即可。

关于弱一致性,分为很多变种:最终一致性,读写一致性,单调读,因果一致性等。

  • 最终一致性:系统保证在一定的时间之后,主从一致,也就是让数据写入“飞一会儿”。我们不能保证立刻看到一致性,但是可以在几秒或者几分钟后,整个分布式系统达到一致性状态。
  • 读写一致性:对于某些特定的读,从主库完成,其余从库读取。比如我在知乎回答了一个问题,我发布完就想立刻看到,此时针对我的回答,我自己去看,可以从主库去读,这样可以读到最新的,但是其他人查看我的问题,则可以通过从库去读,这样实现了分布式读,同时又实现了我想立刻看到我的回答的效果。
  • 单调读:使用映射的方式,把针对某一特定记录的读全部请求到同一数据库。如果我们跨库读取,可能读到历史数据;比如我在A库读到了今天的数据,但是由于数据同步有延迟,下一次我在B库刷新数据时反而看到了昨天的,这样我刷新读取最新的反而读到了旧数据,这就像发生了时光倒流一样。而如果我们读的是同一个数据库,这个问题就迎刃而解了。
  • 因果一致性:详见向量时钟。

网络

  • 🚀30张图解: TCP 重传、滑动窗口、流量控制、拥塞控制
  • 🛸一文搞定所有计算机网络面试题

首先我们需要明确,TCP的是发送一个请求,得到一个接收确认。如果某次请求之后,未获得接收方的ACK确认,那我们可以认为出了问题,这个问题成为超时。这个问题包括两个原因:一个是丢包,一个是网络阻塞(堵车了)。

为什么TCP的拥塞控制算法至今还在完善?很大一部分原因是因为我们不知道当发生超时了,到底是因为简单的丢包还是因为发生了网络阻塞,前者简单重发即可,后者设计网络阻塞的恢复。所以目前的算法都在根据网络带宽,流量,时延,ACK回复来判断到底是哪种,以此来完善算法。

如果发生了超时,需要进行重传。超时的原因有两个:发送丢包,返回的ACK丢包,无论是哪个都需要进行重新发送,这涉及超时时间的判定,表现为RTO时间,RTO又和RTT有关,Linux中对于RTO的计算有自己的方式,且每次RTO=之前的二倍。

此外,如果某一次发送丢包了,接收端发现这个包后面的包都收到了,唯独这个包没有收到,那就可以每次接收新的包,发送丢失的包的前一个包的ACK来实现对发送端的通知;如果发送端发现出现了连续三次相同的ACK,那就是发生了丢包,需要涉及到重传,这就是快速重传。

为了知道快速重传是重传全部还是某一包需要依赖SACK,它里面有一个数据段指出了接收端接受了哪些数据,这样发送者就可以选择数据重传了。

但是SACK无法处理ACK丢失引起的重传,它只能处理发送时包丢失的问题,如果发送成功,但是回复ACK丢失,就只能通过D-SACK(Duplicate-SACK)来解决。此外,它还能解决因为网络延迟导致的问题,如果某个包因为网络延迟而未到达,触发快速重传,发送第二个包,而后被延迟的包到达,便可以通过DSACK通知发送者引起重传的原因不是丢包,而是网络延迟。

如果每一次请求都要等待回复,那通信效率就会变得非常低效,此时我们需要通过窗口来解决。发送窗口维护了四个部分:

  • 1️⃣已发送且已确认
  • 2️⃣已发送但未确认
  • 3️⃣未发送但可发送
  • 4️⃣未发送且不可发送

同样的,接收窗口也维护了四个部分:

  • 1️⃣已使用
  • 2️⃣已接收且已确认
  • 3️⃣未接收但可接收
  • 4️⃣未接收且不可接收

通过这两个窗口,我们可以知道如何规划发送数据和接收数据。发送窗口的大小约等于接收窗口的大小,通过响应数据的windows参数指明接收端窗口的大小。

每次把发送端把ACK中指明的位置标记为已接收,同时右滑窗口;接收端对于接收到的数据同样右滑窗口来实现。

Netty

  • 🚅设计一个百万级的消息推送系统
  • 🚄Netty源码浅析

I/O

  • ✈️谈一谈Java的网络编程

设计模式

常见的设计模式

  • 🔨常用的几种设计模式

算法与数据结构

排序算法

  • 🍺十大经典排序算法
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
Java复制代码    public static void insertSort(int[] nums) {
// [0, i]是有序的
for (int i = 0; i < nums.length - 1; ++i) {
int j = i + 1;
int tmp = nums[i + 1];
// 帮i+1找到一个合适的位置,并插入
for (; j - 1 >= 0 && tmp < nums[j - 1]; --j) {
nums[j] = nums[j - 1];
}
nums[j] = tmp;
}
}

public static void selectSort(int[] nums) {
for (int i = 0; i < nums.length; ++i) {
int min = nums[i];
int minIdx = i;
// 在[i+1, nums.length-1]中找到小于nums[i]且最小的元素进行交换
for (int j = i + 1; j < nums.length; ++j) {
if (nums[j] < min) {
min = nums[j];
minIdx = j;
}
}
nums[minIdx] = nums[i];
nums[i] = min;
}
}

public static void bubbleSort(int[] nums) {
for (int i = 0; i < nums.length; ++i) {
// 不停地暴力交换
for (int j = 0; j < nums.length - 1; ++j) {
if (nums[j] > nums[j + 1]) {
swap(nums, j, j + 1);
}
}
}
}

public static void shellSort(int[] nums) {
for (int gap = nums.length / 2; gap > 0; gap /= 2) {
// 同插入排序,但是每次处理的数据间隙是逐步减小的
for (int i = 0; i + gap < nums.length; i += gap) {
int tmp = nums[i + gap];
int j = i + gap;
for (; j - gap >= 0 && tmp < nums[j - gap]; j -= gap) {
nums[j] = nums[j - gap];
}
nums[j] = tmp;
}
}
}

public static void mergeSort(int[] nums) {
mergeSort0(nums, 0, nums.length-1);
}

// [from, to]
private static void mergeSort0(int[] nums, int from, int to) {
if (from >= to) {
return ;
}
int mid = (from + to) / 2;
mergeSort0(nums, from, mid);
mergeSort0(nums, mid+1, to);
int[] tmp = new int[to-from+1];
int idxA = from, idxB = mid+1, idx = 0;
while (idxA <= mid && idxB <= to) {
if (nums[idxA] < nums[idxB]) {
tmp[idx++] = nums[idxA++];
} else {
tmp[idx++] = nums[idxB++];
}
}
while (idxA <= mid) {
tmp[idx++] = nums[idxA++];
}
while (idxB <= to) {
tmp[idx++] = nums[idxB++];
}
System.arraycopy(tmp, 0, nums, from, tmp.length);
}

public static void quickSort(int[] nums) {
quickSort0(nums, 0, nums.length-1);
}

// [from, to]
private static void quickSort0(int[] nums, int from, int to) {
if (from >= to) {
return ;
}
int cmp = nums[from];
int l = from, r = to;
while (l < r) {
while (l <= r && nums[l] <= cmp) {
++l;
}
while (l <= r && nums[r] > cmp) {
--r;
}
if (l < r) {
swap(nums, l, r);
}
}
swap(nums, from, r);
quickSort0(nums, from, r-1);
quickSort0(nums, r+1, to);
}

public void add(int[] nums, int i, int val){
nums[i] = val;
int curIndex = i;
while (curIndex > 0) {
int parentIndex = (curIndex - 1) / 2;
if (nums[parentIndex] < nums[curIndex])
swap(nums, parentIndex, curIndex);
else break;
curIndex = parentIndex;
}
}

public int remove(int[] nums, int size){
int result = nums[0];
nums[0] = nums[size - 1];
int curIndex = 0;
while (true) {
int leftIndex = curIndex * 2 + 1;
int rightIndex = curIndex * 2 + 2;
if (leftIndex >= size) break;
int maxIndex = leftIndex;
if (rightIndex < size && nums[maxIndex] < nums[rightIndex])
maxIndex = rightIndex;
if (nums[curIndex] < nums[maxIndex])
swap(nums, curIndex, maxIndex);
else break;
curIndex = maxIndex;
}
return result;
}

private static void swap(int[] nums, int idxA, int idxB) {
int tmp = nums[idxA];
nums[idxA] = nums[idxB];
nums[idxB] = tmp;
}

本文转载自: 掘金

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

🌹史上最全的后端必备Linux常用命令汇总(超全面!超详细!

发表于 2021-08-13

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

🌈往期回顾

**感谢阅读,希望能对你有所帮助,博文若有瑕疵请在评论区留言或在主页个人介绍中添加我私聊我,感谢每一位小伙伴不吝赐教。我是XiaoLin,既会写bug也会唱rap的男人**
  • 🌈MySQL真的就CRUD吗?✨来看看2k和12k之间的差距(下)
  • 🌈MySQL真的就CRUD吗?✨来看看2k和12k之间的差距(上)
  • 💛任职要求中的熟系Redis,你真的熟悉吗?💛

一、引言

在这里插入图片描述

1.1、Linux引言

Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于`POSIX和Unix`的`多用户`、`多任务`、`支持多线程和多CPU的操作系统`。伴随着互联网的发展,`Linux得到了来自全世界软件爱好者、组织、公司的支持`。它除了在`服务器操作系统`方面保持着强劲的发展势头以外,在个人电脑、嵌入式系统上都有着长足的进步。目前Linux存在着许多不同的Linux发行版本,`但它们都使用了Linux内核`。Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、台式计算机。

image-20191011200827437

1.2、Linux的诞生

20世纪80年代,计算机硬件的性能不断提高,PC的市场不断扩大,当时可供计算机选用的操作系统主要有Unix、DOS和MacOS这几种。Unix价格昂贵,不能运行于PC。DOS显得简陋,且源代码被软件厂商严格保密;


MacOS是一种专门用于苹果计算机的操作系统。此时,计算机科学领域迫切需要一个更加完善、强大、廉价和完全开放的操作系统。由于供教学使用的典型操作系统很少,因此当时在荷兰当教授的美国人AndrewS.Tanenbaum编写了一个操作系统,名为MINIX,为了向学生讲述操作系统内部工作原理。MINIX虽然很好,但只是一个用于教学目的的简单操作系统,而不是一个强有力的实用操作系统,然而最大的好处就是公开源代码。全世界学计算机的学生都通过钻研MINIX源代码来了解电脑里运行的MINIX操作系统,芬兰赫尔辛基大学大学二年级的学生Linus Torvalds就是其中一个,在吸收了MINIX精华的基础上,Linus于1991年写出了属于自己的Linux操作系统,版本为Linux0.01,是Linux时代开始的标志。他利用Unix的核心,去除繁杂的核心程序,改写成适用于一般计算机的x86系统,并放在网络上供大家下载,1994年推出完整的核心Version1.0,至此,Linux逐渐成为功能完善、稳定的操作系统,并被广泛使用。


Linux出现于1991年,是由芬兰赫尔辛基大学学生,Linus Torvalds和后来加入的众多爱好者共同开发完成。

image-20191011201019566

1.3、Linux的特点

1.3.1、完全免费

Linux是一款免费的操作系统,用户可以通过网络或其他途径免费获得,并可以任意修改其源代码。这是其他的操作系统所做不到的。正是由于这一点,来自全世界的无数程序员参与了Linux的修改、编写工作,程序员可以根据自己的兴趣和灵感对其进行改变,这让Linux吸收了无数程序员的精华,不断壮大。

1.3.2、多用户、多任务

Linux支持多用户,各个用户对于自己的文件设备有自己特殊的权利,保证了各用户之间互不影响。**多任务**则是现在电脑最主要的一个特点,**Linux可以使多个程序同时并独立地运行,丰富的网络功能,可靠的系统安全,良好的可移植性,具有标准兼容性,出色的速度性能。**

1.4、Centos

1.4.1、简介

CentOS(Community Enterprise Operating System,中文意思是社区企业操作系统)是Linux发行版之一,它是来自于Red Hat Enterprise Linux依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定性的服务器,)以CentOS替代商业版的Red Hat、Enterprise Linux使用。两者的不同,在于**CentOS完全开源**。

1.4.2、 centos 和 readheat区别

目前的Linux操作系统主要应用于生产环境,`主流企业的Linux系统仍旧是RedHat或者CentOS`,他们出自于同样的源代码,但centos完全免费。其独有的yum命令支持在线升级,可以即时更新系统,不像RedHat 那样需要花钱购买支持服务!

1.5、Linux目录结构

image-20191011205611690

  1. bin (binaries):存放二进制可执行文件。
  2. sbin (super user binaries):存放二进制可执行文件,只有root才能访问。
  3. etc (etcetera):存放系统配置文件。
  4. usr (unix shared resources):用于存放共享的系统资源。
  5. home:存放用户文件的根目录。
  6. root :超级用户目录。
  7. dev (devices):用于存放设备文件。
  8. lib (library):存放跟文件系统中的程序运行所需要的共享库及内核模块。
  9. mnt (mount):系统管理员安装临时文件系统的安装点。
  10. boot 存放用于系统引导时使用的各种文件。
  11. tmp (temporary)用于存放各种临时文件。
  12. var (variable)用于存放运行时需要改变数据的文件。

二、Linux常用命令

2.1、Linux命令格式

1
2
3
shell复制代码命令  -选项  参数    

如: ls -l /usr

2.2、ls

2.2.1、概述

ls(list)命令用于显示文件和目录列表

2.2.2、常用参数

  1. -l(long):长格式显示文件和目录信息。ls -l 简写就是ll。
  2. -a(all):显示所有文件和目录。
  3. -R:递归显示指定目录下的文件清单,即会显示指定目录分支内各子目录中的文件清单。
  4. -h:人性化显示文件大小。

2.2.3、细节

image-20210427103235175

1
2
shell复制代码文件类型   文件权限       链接数    属主      属组    大小     日期     时间     文件名
d rwxr -xr -x 8 xiaolin root 4096 Apr 22 09:42 jdk11

第一个信息(第一位):

  1. d:表示当前类型为目录
  2. -:代表当前类型问文件

第二个信息(后面9位):

  1. 第一个3位:代表当前文件创建者( user)的操作权限 r(读) 、w(写)、x(执行)。
  2. 中间的3位 :代表当前文件创建者的同组(group)用户对文件的操作权限 。
  3. 最后的3位 :代表当前文件创建者的不同组(other)用户对该文件的操作权限。

第三个信息(再后面1位):表示文件链接数。

第四个信息:链接数后面的表示当前文件的创建者。

第五个信息:当前文件创建者后面表示当前文件创建者所在的组是哪个组。

第六个信息:所在组后面表示当前文件占用的空间大小。

第七个信息:文件最后一次修改的时间。

第八个信息:文件名或者目录名。

2.3、操作文件命令

2.3.1、cd

cd命令用来切换目录。可以随意切换到想要的目录。

2.3.2、pwd(print working directory)

显示当前工作目录

2.3.3、touch

创建空文件

2.3.4、mkdir(make directoriy)

创建目录,他有一个参数:-p(parents),表示如果父目录不存在的话先生成父目录。

2.3.5、cp(copy)

1
2
shell复制代码cp  文件名  目录:复制文件到指定目录中
cp -r(recursive) 目录名 目录:复制指定目录到指定目录中,-r表示递归处理,将指定目录下的文件与子目录一并拷贝。

2.3.6、mv(move)

移动文件或目录、文件或目录改名
1
2
3
shell复制代码mv 文件名 新文件名:表示文件改名
mv 文件名 目录名: 表示文件移动
mv 目录名 不存在目录名:表示目录改名

2.3.7、rm

他有几个常用的参数
1
2
shell复制代码-r:同时删除该目录下的所有文件(recursive)
-f:强制删除文件或目录(force)
经常使用组合命令是:rm -rf

2.3.8、rmdir(remove directoriy)

删除空目录,如果不是空目录会提示报错。

2.3.9、cat(catenate)

显示文本文件内容,用来展示少量内容,仅限一个屏幕

2.3.10、head

查看文本中开头或结尾部分的内容
1
2
shell复制代码# 查看a.log文件的前5行
head -n 5 a.log

2.3.11、tail

1
2
shell复制代码# 循环读取(fellow,实时展示文件的内容变化)
tail -f b.log

2.3.12、echo

输出命令
1
2
3
4
5
shell复制代码# 用来向屏幕输出一句话
echo I love baby

# 将这段内容输入到 aa.txt文件中,运行两次就是追加
echo I Love baby >> aa.txt

2.4、网络相关命令

2.4.1、ip addr

查看IP地址,简化写法为:ip a

2.4.2、ping

测试网络连通性

2.5、tar命令

tar命令相当于windows的好压,用于解压或者压缩文件。

1
2
shell复制代码# 打包命令
tar -cvf 打包文件名 源文件

他有几个参数:

  1. -c(create):建立一个压缩文件的参数指令。
  2. -x(extract):解开一个压缩文件的参数指令。
  3. -z :是否需要用 gzip 压缩, .tar.gz(打包并且用了gzip的压缩)。
  4. -v :压缩的过程中显示文件(verbose)。
  5. -f:使用档名。

通常使用的组合命令:

  1. tar -cvf aaa.tar file1.txt file2.txt:将file1和file2打包成aaa.tar。
  2. tar -zcvf aaa.tar.gz file1.txt file2.txt :将file1和file2打包成aaa.tar
  3. tar -zxvf aaa.tar.gz:将aaa.tar.gz解压

2.6、systemctl 服务命令(Centos独有)

他用于查看服务的状态。

1
2
3
4
5
6
7
8
9
10
11
shell复制代码# 查看某个服务的运行状态
systemctl status 服务名

# 启动某个服务
systemctl start 服务名

# 重启某个服务
systemctl restart 服务名

# 停止某个服务
systemctl stop 服务名

2.7、进程相关命令

2.7.1、ps

查询在当前控制台上运行的进程。

1
2
3
4
5
6
7
shell复制代码# 查询系统中所有运行的进程,包括后台进程,其中参数a是所有进程,参数x包括不占用控制台的进程,参数u显示用户。
ps -aux

# 查询系统中所有运行的进程,包括后台进程,而且可以显示出每个进程的父进程号。
ps -ef

ps -aux|grep 进程 服务名

2.7.2、top

动态显示系统进程

2.7.3、kill

杀死进程,

1
2
3
4
5
shell复制代码# 上述命令中1314是进程号;一般在执行kill命令之前,先用ps或pstree来查询一下将要被杀掉的进程的进程号。 
kill 1314

# 强制终止1314号进程的运行,其中参数-9代表强制的意思,实际上kill命令是向该进程发送信号,该进程接到信号后决定是否停止运行,有些守护进程必须要收到参数9才终止运行。
kill -9 1314

2.8、vi/vim编辑器

2.8.1、概述

vi / vim是Unix / Linux上最常用的文本编辑器而且功能非常强大。

image-20191011213625815

2.8.2、三种模式

vi编辑器有三种模式:

  1. 编辑模式(命令模式) :所有的机键动作都会理解为编辑整个文档的操作,默认为编辑模式。
  2. 输入模式:大部分机键动作都会理解为输入的字符。
  3. 末行模式:在末行模式,输入很多文件管理命令。

模式之间可以互相转换:

  1. 编辑模式–>输入模式:

(1). i:在光标所在字符前开始插入。

(2). a:在光标所在字符后开始插入。

(3). o:在光标所在行的下面另起一新行插入。

(4). s:删除光标所在的字符并开始插入。

(5). I:在光标所在行的行首开始插入 如果行首有空格则在空格之后插入。

(6).A:在光标所在你行的行尾开始插入。

(7).O:在光标所在行的上面另起一行开始插入。

(8).S:删除光标所在行并开始插入。
2. 输入模式–>编辑模式:ESC(键盘左上方的按键)。
3. 编辑模式—>末行模式:输入:。
4. 末行模式:–>编辑模式:ESC(键盘左上方的按键)(一次或者多次)

2.8.3、常用命令

  1. i:在光标前插入。
  2. I:在光标当前行开始插入。
  3. a:在光标后插入。
  4. A:在光标当前行末尾插入。
  5. o:在光标当前行的下一行插入新行。
  6. O:在光标当前行的上一行插入新行。
  7. :set nu:显示行号。
  8. :set nonu:取消行号。
  9. gg:到文本的第一行。
  10. G:到文本的最后一行。
  11. :n:到文本的第n行。
  12. u:undo,取消上一步操作。
  13. Ctrl + r:redo,返回到undo之前。
  14. :wq:保存退出。
  15. 按住shift:保存退出,与“:wq”作用相同。
  16. :wq!:强制保存退出
  17. :q:退出不保存。
  18. :q!:强制退出不保存。
  19. yy:复制光标所在行 (编辑模式)。
  20. 2yy 从光标行开始复制2行(编辑模式)。
  21. p::粘贴命令。
  22. u:撤销(编辑模式)。
  23. 1,$d:全部删除(末行模式)。

2.8.4、移动光标和翻屏操作

2.8.4.1、逐字符移动

  1. h:左移动。
  2. l:右移动。
  3. j:向下移动。
  4. k:向上移动。

2.8.4.2、行内跳转和行间跳转

  1. 0:跳转到行首。
  2. $:跳转到行尾。
  3. G::跳转行最后一行(需要在编辑模式下输入按键盘的G)。
  4. gg: 跳转到第一行(需要在编辑模式下按键盘两下gg)。
  5. ctrl+f:向下翻一页。
  6. ctrl+b:向上翻一页。

2.8.4、查找和替换

  1. /需要匹配的条件:从前往后查找。
1
shell复制代码/s
  1. ?需要匹配的条件:从后往前查找。
1
shell复制代码?s
  1. n: 下一个匹配的字符串(编辑模式)。
  2. N: 上一个匹配的字符串(编辑模式)。
  3. g:全局替换。
  4. i:忽略大小写

2.9、软件相关命令

2.9.1、rpm命令

2.9.1.1、rpm概述

RPM是RedHat Package Manager(RedHat软件包管理工具)的缩写,这一文件格式名称虽然打上了RedHat的标志,但是其原始设计理念是开放式的,现在包括RedHat、CentOS、SUSE等Linux的分发版本都有采用,可以算是公认的行业标准了。RPM文件在Linux系统中的安装最为简便,

2.9.1.2、rpm命令

rpm命令常用的参数:

  1. i(install):安装应用程序。
  2. e(erase):卸载应用程序。
  3. vh(verbose hash):显示安装进度。
  4. U(update) :升级软件包。
  5. qa(query all): 显示所有已安装软件包。
1
2
shell复制代码# 显示进度安装 gcc-c++-4.4.7-3.el6.x86_64.rpm
rmp -ivh gcc-c++-4.4.7-3.el6.x86_64.rpm

2.9.2、yum命令

Yum(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及SUSE、CentOS中的Shell前端软件包管理器。基於RPM包管理,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软件包,无须繁琐地一次次下载、安装。**使用YUM命令必须连接外部网络**

2.10、用户和组相关命令

用户的定义是一类用户具有访问某个资源或服务的能力。
用户组(存放一些列用户的容器),同时用户组还拥有具有访问某个资源的权限
  1. whoami:查看当前用户。
  2. exit:退出用户。
  3. groupadd:添加组.
  4. groupdel:删除组。
  5. useradd:添加用户,可以带一个参数-g,指定组名称,如果创建用户的时候,不指定组名,那么系统会自动创建一个和用户名一样的组名。
  6. passwd 用户名:设置用户密码。
  7. su 用户名:切换用户。

2.11、权限相关命令

权限的定义是某一个特定的人资源或者服务的访问能力。
Linux文件有`三种`典型的权限,`即r读权限、w写权限和x执行权限`。在长格式输出中在文件类型的后面有9列权限位,实际上这是针对不同用户而设定的。`r=4,w=2,x=1`。


r(read):表示可读取,对于一个目录,如果没有r权限,那么就意味着不能通过ls查看这个目录的内容。
w(write):表表示可写入,对于一个目录,如果没有w权限,那么就意味着不能在目录下创建新的文件。
x(excute):表表示可写入,表示可执行,对于一个目录,如果没有x权限,那么就意味着不能通过cd进入这个目录。

2.12、搜索相关命令

2.12.3、文件相关

  1. whereis 命令名:搜索该命令所在路径及帮助文档所在位置。
1
shell复制代码whereis  ls

image-20210427192137748

  1. which:搜索命令所在路径及别名
1
she复制代码which ls

image-20210427192443412

  1. find 搜索范围 搜索条件:find是在系统当中搜索符合条件的文件名,如果需要匹配,使用通配符匹配,通配符是完全匹配。
1
2
shell复制代码# 不区分大小写
find /root -iname install.log

2.12.4、字符串搜索

基本格式为:grep [选项] 字符串 文件名。

可选项为:

  1. -i:忽略大小写。
  2. -v:排除指定字符串。

2.12.5、find命令和grep命令的区别

find命令:在系统中搜索符合条件的文件名。

grep命令:在文件中搜索符合条件的字符串。

2.13、防火墙

centos7的防火墙程序服务名为:**firewalld**,他的相关命令如下:
  1. 防火墙配置命令:firewall-cmd。
  2. 防火墙帮助:fireweall-cmd –help。
  3. 查看防火墙状态:firewall-cmd –state。
  4. 查看所有打开的端口:firewall-cmd –zone=public –list-ports。
  5. 开启端口:firewall-cmd –zone=public –add-port=端口号/tcp –permanent(–permanent参数为永久生效,不加则重启后失效)
  6. 更新防火墙规则:firewall-cmd –reload。
  7. 删除端口:firewall-cmd –zone=public –remove-port=端口号/tcp –permanent。

本文转载自: 掘金

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

一键生成数据库文档 建议收藏

发表于 2021-08-13

废话不说 直接进入主题

今天的主角 SCREW

1
2
复制代码首先当然是先创建一个项目 我这边呢直接创建的是boot项目 当然呢maven项目也可以
然后导入数据库依赖和今天的主角screw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码     <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!-- 数据库连接 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId>
<version>1.0.5</version>
</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
80
81
82
83
java复制代码import cn.smallbun.screw.core.Configuration;
import cn.smallbun.screw.core.engine.EngineConfig;
import cn.smallbun.screw.core.engine.EngineFileType;
import cn.smallbun.screw.core.engine.EngineTemplateType;
import cn.smallbun.screw.core.execute.DocumentationExecute;
import cn.smallbun.screw.core.process.ProcessConfig;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Collections;

public class ScrewMain {

private static final String DB_URL = "jdbc:mysql://localhost:3306"; //数据库链接
private static final String DB_NAME = "shop"; //数据库名称
private static final String DB_USERNAME = "root"; //账户
private static final String DB_PASSWORD = "000000"; //密码
private static final String FILE_OUTPUT_DIR = "F:\\资料"; //生成位置
private static final EngineFileType FILE_OUTPUT_TYPE = EngineFileType.HTML; // 可以设置 Word 或者 Markdown 格式
private static final String DOC_FILE_NAME = "mysql数据库文档";
private static final String DOC_VERSION = "1.0.0";
private static final String DOC_DESCRIPTION = "shop文档描述";

public static void main(String[] args) {
// 创建 screw 的配置
Configuration config = Configuration.builder()
.version(DOC_VERSION) // 版本
.description(DOC_DESCRIPTION) // 描述
.dataSource(buildDataSource()) // 数据源
.engineConfig(buildEngineConfig()) // 引擎配置
.produceConfig(buildProcessConfig()) // 处理配置
.build();

// 执行 screw,生成数据库文档
new DocumentationExecute(config).execute();
}

/**
* 创建数据源
*/
private static DataSource buildDataSource() {
// 创建 HikariConfig 配置类
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver"); //数据库驱动
hikariConfig.setJdbcUrl(DB_URL + "/" + DB_NAME+"?serverTimezone=UTC");
hikariConfig.setUsername(DB_USERNAME);
hikariConfig.setPassword(DB_PASSWORD);
hikariConfig.addDataSourceProperty("useInformationSchema", "true"); // 设置可以获取 tables remarks 信息
// 创建数据源
return new HikariDataSource(hikariConfig);
}

/**
* 创建 screw 的引擎配置
*/
private static EngineConfig buildEngineConfig() {
return EngineConfig.builder()
.fileOutputDir(FILE_OUTPUT_DIR) // 生成文件路径
.openOutputDir(false) // 打开目录
.fileType(FILE_OUTPUT_TYPE) // 文件类型
.produceType(EngineTemplateType.freemarker) // 文件类型
.fileName(DOC_FILE_NAME) // 自定义文件名称
.build();
}

/**
* 创建 screw 的处理配置,一般可忽略
* 指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时,将生成指定表,其余表不生成、并跳过忽略表配置
*/
private static ProcessConfig buildProcessConfig() {
return ProcessConfig.builder()
.designatedTableName(Collections.<String>emptyList()) // 根据名称指定表生成
.designatedTablePrefix(Collections.<String>emptyList()) //根据表前缀生成
.designatedTableSuffix(Collections.<String>emptyList()) // 根据表后缀生成
.ignoreTableName(Arrays.asList("test_user", "test_group")) // 忽略表名
.ignoreTablePrefix(Collections.singletonList("test_")) // 忽略表前缀
.ignoreTableSuffix(Collections.singletonList("_test")) // 忽略表后缀
.build();
}

}

HTML文档
WORD文档
Markdown文档

本文转载自: 掘金

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

如何评价《Java 并发编程艺术》这本书?

发表于 2021-08-13

对于书评这件事情,我其实是不想写的,因为每个人都有自己的一个衡量标准,每个人眼中都有自己的哈姆雷特,是好是坏每个人都褒贬不一。如果对于书中的知识你都掌握了,你只是想把它作为一种知识串联的记忆体的话,那么你可能感兴趣的事整本书的知识框架和体系脉络,所以如果书中的知识具有连贯性,能够在你脑海中编织出你想要的记忆蓝图,那么我相信它对你来说就没那么好;如果你想把它作为打开新世界的大门,你想了解一下并发编程的奥妙,我相信它对你来说也是一本好书,能够拓展你的知识深度;如果你是面向面试来看它,它也是一本好书,因为绝大多数知识都是面试的常考点。

下面我会从上面的几个假如来带你过一遍这本书。

前置知识

首先先来混个脸熟,我相信大家都不陌生了,上书!!!

首先,这本书是由三个人撰写的,这三位都是阿里人,我用一幅图来说明这三个人都写了哪些内容。

如果是由三个人合力撰写的一本书,如果不能做到对书整体脉络有把控的话,就会陷入聋子听哑巴说瞎子看见鬼了这种逻辑上不能自洽的局面,但是我能从书中看出来,出版社和方老师在对整体知识体系的脉络上面做到了严格把控,但我认为还是差点。

谨言慎行,我会尽量做到以极其客观的角度来解读这本书。

首先第一章并发编程的挑战这个立意非常不错,把整本书所讲的主要内容先引出来,使读者大致了解一下并发编程都会带来哪些问题,然后切入并发编程的痛点,即并发编程的性能瓶颈。

然后第二章直接深入 Java 并发机制的底层实现,这一章的内容和深度我认为有待商榷,但是如果从精读的角度来看,这并没有什么不妥,同时也会对那些”有一定 Java 并发编程基础,想要更深层次了解 Java 并发底层实现”的同学来说,很容易眼前一亮。但是对那些”还没接触过并发编程,只想要了解一下 Java 并发”的同学来说,这第二章直接劝退了,说实话我在刚开始的时候接触这本书,看到第二章我直接垫显示器了。

所以这就是一个选择的问题,这个问题深层次的拷问就是:你到底想给什么样的读者呈现什么样的内容?

然后我们假定第一种情况,即”有一定 Java 并发编程基础,想要更深层次了解 Java 并发底层实现”的这类同学,这类同学看到这里我相信会直接喊出 yyds 这个词,因为这一章真的非常惊艳,能够写出这样的内容得益于方老师百年如一日的坚持和长期在一线打拼的知识积累。第一章和第二章的衔接就像是你刚学会了 1 + 1 = 1,然后就让你论证为什么 1 + 1 = 1 ?

社死了有木有???????

第二章直接带我们上难度,从 volatile 讲到 synchronized ,从对象头讲到锁的升级降级(撤销),从 CAS 讲到 ABA,从原子操作讲到内存屏障,我直呼好家伙!非常惊艳。

但是,第二章对于一些细节的打磨还是差了一些,比如说在锁升级这块的流程图就画的不是那么显而易见,反正我是看了图,然后结合了大量网上的参考才看的略知一二。还有就是方老师上来直接讲应用和底层原理,都没有前戏的吗???volatile 是什么,怎么我认为也应该从一个简单的小例子引出来吧。

然后第三章,切换选手,上来直接全程硬菜,这一章从内存模型入手,讲了 volatile 、final、synchronized 的内存语意,as-if-serial 和 happens-before 原则,Java 内存模型是怎样的以及 JSR-133 对内存模型做了哪些改变,可以说能介绍的都介绍完了。

但是,我又要说但是了,这一章有一些内容应该是从外文网站上翻译出来的,很多地方有些生硬,感觉像是堆砌,不像是在讲课。这种讲解风格我认为不如方老师讲的犀利,能够直接切入重点。这一章我刚开始看的还行,但是到了这一章后半程,明显感觉吃力,不知道是我菜还是怎么着,看着看和明显犯困,不知道手机/电脑前的小伙伴们怎么认为的。

撸完第三章,再看第四章,就感觉有点虐小号的意思,为什么 Java 并发编程基础放在第四章讲??????为什么基础要放在底层实现的后面??????这不是本末倒置吗?如果你是先看目录直接从第四章看起来的倒还好,如果你从第一章慢慢慢慢看到第四章,你很可能会怀疑人生,这是什么破玩意?真的没有搞明白为什么基础要放在第四章才开始讲。而且第四章这部分内容很多都是一带而过,让我不得不怀疑这位选手应该没怎么写过文章,我身边这些公众号主,但凡做的好的,有一些流量的原创号主,都不可能把文章写成这样,大量的代码堆砌,讲解一带而过,这着实没什么用,真的建议作者把《Java并发编程实践》再好好读一读。

看到第五章,我终于明白了没有点 Java 并发基础的话,最好不要看这本书,因为很容易头大。

第六章到第十一章其实讲解的有些过于基础了,如果说这本书是想要带读者深入理解并发的话,书中并没有涉及很多源码,但是对于那些”还没接触过并发编程,只想要了解一下 Java 并发”的同学来说,却是一个了解 Java 并发一个比较好的工具书和参考书,对于那些希望面试背题的同学来说也是一样的。

面试的时候一般会分为四个阶段来问:

  • 你知道 xxx 吗?你用过 xxx 吗?(一问看你有没有接触过,一般没接触过就不会问这个方向了)
  • 你在日常开发的过程中是如何使用 xxx 的呢?(再次判断你有没有接触过,如果你能回答上来应用场景,才会继续向下问,不然面试官就认为你在扯皮)
  • 那你知道 xxx 的原理吗?(问你原理就是判断你私下来有没有研究过这个东西,可以回答 xxx 的工作机制是怎样的,比如线程池的原理,你就可以回答线程池的工作机制)
  • 最后还可能会问你更细一些的问题,比如 HashMap 的 put 过程是怎样的?(这个就需要熟悉源码了)

所以,这本书给大家带来的知识点仅局限于前三点,事实上,从整个行业上来说,我也没有看到哪位作者把源码撸出来写成一本书的,所以这也是我们程序员们努力的方向。

这里,给读者朋友们推荐一下我写的关于并发方面的文章:

2w字 + 40张图带你参透并发编程!

我工作三年了,该懂并发了(干货)

Atomic XXX 原理解析

一文搞懂 CountDownLatch 用法和源码!

呵,Semaphore ,就这?

我真不想学 happens - before 了!

cxuan 连这 10 个问题都不会…

synchronized 的超多干货!

volatile

肝完这篇线程池,我咳血了

有解释,有源码,有案例,是不是要来学习一波

为什么说这本书好?

所以,为什么说这本书好呢?

因为这本书符合我们中国人的调性,毕竟是出自中国人之手,这本书不同于《Java 并发编程实战》,Java 并发编程实战更多注重的是”如何编写一手优秀的并发代码”,而 《Java 并发编程的艺术》更多的是围绕 Java 底层知识来讲解的,他们的侧重点不同。并发编程实战毕竟是外国人写的,它经过翻译的过程会损失掉一些原书的魅力,所以大家尽量还是要看原版书,最好不要穿二手鞋。

Java 并发编程的艺术还好在比较贴合现如今的面试氛围,大家知道现在面试就是各种八股文一股脑直接冲,这种面试造火箭的面试方式我们大家都嗤之以鼻,但是又无力改变这种现状,所以只能适应,而这本书就是一个让你更好适应八股文面试的一本书。我上面说,这本书有几个章节讲的还是比较不错的,这几个章节搞懂了,基本上相关面试你十拿九稳。但是后面有一些章节比较浅显,需要贴合网上的博客再详细了解。

为什么说这本书不好?

为什么说这本书不好?

我不认可这本书的叙述方式和体系结构,很容易把人给劝退。我认为这是需要反思的一点。

而且阿里的同学作为业界尤其是 Java 技术栈的顶级程序员,如果不能把自己的技术栈和知识体系的完备性作为最高标准要求自己,那么我们普通程序员还能够相信谁呢?

这本书让我有一种能够离开它但却离不开它的一种感觉。我看《深入理解 Java 虚拟机》的时候,就没有这种情愫。因为看完周老师的深入理解 JVM 之后,我甚至觉得 《Java 虚拟机规范》你就完全可以扔在一边了。

但是,《Java 并发编程实战》和 《Java 并发编程艺术》其实是一种互补品,缺少哪一本书的知识,我都觉得少点东西。但是只看 Java 并发编程艺术这一本书又不够,所以。。。。。。为什么不再认真打磨一下呢?

所以,希望还能有下一版的《Java 并发编程艺术》,让我们大家一起期待。

文章的最后,给大家推荐一个 github ,github.com/crisxuan/be… 这个 github 是我呕心沥血所有文章的汇总,欢迎 star 。

本文转载自: 掘金

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

1…565566567…956

开发者博客

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