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

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


  • 首页

  • 归档

  • 搜索

Kotlin Coroutines Flow 系列(三) 异

发表于 2020-02-24

attractive-beautiful-beauty-face-607891.jpg

五. Flow 异常处理

Flow 可以使用传统的 try…catch 来捕获异常:

1
2
3
4
5
6
7
8
9
10
11
12
复制代码fun main() = runBlocking {
flow {
emit(1)
try {
throw RuntimeException()
} catch (e: Exception) {
e.stackTrace
}

}.onCompletion { println("Done") }
.collect { println(it) }
}

另外,也可以使用 catch 操作符来捕获异常。

5.1 catch 操作符

上一篇文章Flow VS RxJava2曾讲述过 onCompletion 操作符。

但是 onCompletion 不能捕获异常,只能用于判断是否有异常。

1
2
3
4
5
6
7
8
9
10
11
复制代码fun main() = runBlocking {
flow {
emit(1)
throw RuntimeException()
}.onCompletion { cause ->
if (cause != null)
println("Flow completed exceptionally")
else
println("Done")
}.collect { println(it) }
}

执行结果:

1
2
3
4
复制代码1
Flow completed exceptionally
Exception in thread "main" java.lang.RuntimeException
......

catch 操作符可以捕获来自上游的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码fun main() = runBlocking {
flow {
emit(1)
throw RuntimeException()
}
.onCompletion { cause ->
if (cause != null)
println("Flow completed exceptionally")
else
println("Done")
}
.catch{ println("catch exception") }
.collect { println(it) }
}

执行结果:

1
2
3
复制代码1
Flow completed exceptionally
catch exception

上面的代码如果把 onCompletion、catch 交换一下位置,则 catch 操作符捕获到异常后,不会影响到下游。因此,onCompletion 操作符不再打印”Flow completed exceptionally”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码fun main() = runBlocking {
flow {
emit(1)
throw RuntimeException()
}
.catch{ println("catch exception") }
.onCompletion { cause ->
if (cause != null)
println("Flow completed exceptionally")
else
println("Done")
}
.collect { println(it) }
}

执行结果:

1
2
3
复制代码1
catch exception
Done

catch 操作符用于实现异常透明化处理。例如在 catch 操作符内,可以使用 throw 再次抛出异常、可以使用 emit() 转换为发射值、可以用于打印或者其他业务逻辑的处理等等。

但是,catch 只是中间操作符不能捕获下游的异常,类似 collect 内的异常。

对于下游的异常,可以多次使用 catch 操作符来解决。

对于 collect 内的异常,除了传统的 try…catch 之外,还可以借助 onEach 操作符。把业务逻辑放到 onEach 操作符内,在 onEach 之后是 catch 操作符,最后是 collect()。

1
2
3
4
5
6
7
8
9
10
复制代码fun main() = runBlocking<Unit> {
flow {
......
}
.onEach {
......
}
.catch { ... }
.collect()
}

5.2 retry、retryWhen 操作符

像 RxJava 一样,Flow 也有重试的操作符。

如果上游遇到了异常,并使用了 retry 操作符,则 retry 会让 Flow 最多重试 retries 指定的次数。

1
2
3
4
5
6
7
复制代码public fun <T> Flow<T>.retry(
retries: Long = Long.MAX_VALUE,
predicate: suspend (cause: Throwable) -> Boolean = { true }
): Flow<T> {
require(retries > 0) { "Expected positive amount of retries, but had $retries" }
return retryWhen { cause, attempt -> attempt < retries && predicate(cause) }
}

例如,下面打印了三次”Emitting 1”、”Emitting 2”,最后两次是通过 retry 操作符打印出来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码fun main() = runBlocking {

(1..5).asFlow().onEach {
if (it == 3) throw RuntimeException("Error on $it")
}.retry(2) {

if (it is RuntimeException) {
return@retry true
}
false
}
.onEach { println("Emitting $it") }
.catch { it.printStackTrace() }
.collect()
}

执行结果:

1
2
3
4
5
6
7
8
复制代码Emitting 1
Emitting 2
Emitting 1
Emitting 2
Emitting 1
Emitting 2
java.lang.RuntimeException: Error on 3
......

retry 操作符最终调用的是 retryWhen 操作符。下面的代码跟刚才的执行结果一致:

1
2
3
4
5
6
7
8
9
10
11
12
复制代码fun main() = runBlocking {

(1..5).asFlow().onEach {
if (it == 3) throw RuntimeException("Error on $it")
}
.onEach { println("Emitting $it") }
.retryWhen { cause, attempt ->
attempt < 2
}
.catch { it.printStackTrace() }
.collect()
}

因为 retryWhen 操作符的参数是谓词,当谓词返回 true 时才会进行重试。谓词还接收一个 attempt 作为参数表示尝试的次数,该次数是从0开始的。

六. Flow Lifecycle

RxJava 的 do 操作符能够监听 Observables 的生命周期的各个阶段。

Flow 并没有多那么丰富的操作符来监听其生命周期的各个阶段,目前只有 onStart、onCompletion 来监听 Flow 的创建和结束。

1
2
3
4
5
6
7
8
9
10
11
复制代码fun main() = runBlocking {

(1..5).asFlow().onEach {
if (it == 3) throw RuntimeException("Error on $it")
}
.onStart { println("Starting flow") }
.onEach { println("On each $it") }
.catch { println("Exception : ${it.message}") }
.onCompletion { println("Flow completed") }
.collect()
}

执行结果:

1
2
3
4
5
复制代码Starting flow
On each 1
On each 2
Flow completed
Exception : Error on 3

例举他们的使用场景:
比如,在 Android 开发中使用 Flow 创建网络请求时,通过 onStart 操作符调用 loading 动画以及网络请求结束后通过 onCompletion 操作符取消动画。

再比如,在借助这些操作符做一些日志的打印。

1
2
3
4
5
6
7
8
9
复制代码fun <T> Flow<T>.log(opName: String) = onStart {
println("Loading $opName")
}.onEach {
println("Loaded $opName : $it")
}.onCompletion { maybeErr ->
maybeErr?.let {
println("Error $opName: $it")
} ?: println("Completed $opName")
}

该系列的相关文章:

Kotlin Coroutines Flow 系列(一) Flow 基本使用

Kotlin Coroutines Flow 系列(二) Flow VS RxJava2

Kotlin Coroutines Flow 系列(四) 线程操作

Kotlin Coroutines Flow 系列(五) 其他的操作符

本文转载自: 掘金

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

面试题:说说UDP和TCP的区别及应用场景

发表于 2020-02-23

原创文章出自公众号:「码农富哥」,如需转载请注明出处!

上一篇聊完 一文彻底搞懂 TCP三次握手、四次挥手过程及原理
这次聊聊TCP和UDP的区别和场景

TCP/IP 中有两个具有代表性的传输层协议,分别是 TCP 和 UDP。
来看看传输层位于OSI七层协议和TCP/IP四层协议的位置:

OSI七层协议和TCP/IP四层协议

那么TCP和UDP的区别和使用场景分别是怎样的?
我们用一个图来对比一下UDP和TCP:

TCP

TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,当应用程序采用 TCP 发送消息时,虽然可以保证发送的顺序,但还是犹如没有任何间隔的数据流发送给接收端。

TCP 为提供可靠性传输,实行“顺序控制”或“重发控制”机制。此外还具备“流控制(流量控制)”、“拥塞控制”、提高网络利用率等众多功能。

TCP有以下特点:

  • TCP充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在 UDP 中都没有。
  • 此外,TCP 作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。
  • 根据 TCP 的这些机制,在 IP 这种无连接的网络上也能够实现高可靠性的通信( 主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现)。

UDP

UDP 是面向报文的,所谓面向报文,是指面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会是IP太小。

UDP 是不具有可靠性的数据报协议,细微的处理它会交给上层的应用去完成。在 UDP 的情况下,虽然可以确保发送消息的大小,却不能保证消息一定会到达。因此,应用有时会根据自己的需要进行重发处理。

UDP有以下特点:

  • UDP 不提供复杂的控制机制,利用 IP 提供面向无连接的通信服务。
  • 传输途中出现丢包,UDP 也不负责重发。
  • 当包的到达顺序出现乱序时,UDP没有纠正的功能。
  • 并且它是将应用程序发来的数据在收到的那一刻,立即按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况,UDP 也无法进行流量控制等避免网络拥塞行为。
  • 如果需要以上的细节控制,不得不交由采用 UDP 的应用程序去处理。
  • UDP 常用于以下几个方面:
    • 1.包总量较少的通信(DNS、SNMP等);
    • 2.视频、音频等多媒体通信(即时通信);
    • 3.限定于 LAN 等特定网络中的应用通信;
    • 4.广播通信(广播、多播)。

UDP 和 TCP 的应用

他们的应用如下图:

总结:

TCP 和 UDP 的优缺点无法简单地、绝对地去做比较:TCP 用于在传输层有必要实现可靠传输的情况;
而在一方面,UDP 主要用于那些对高速传输和实时性有较高要求的通信或广播通信。TCP 和 UDP 应该根据应用的目的按需使用。

最后

文章如果对你有收获,可以收藏转发,这会给我一个大大鼓励哟!另外可以关注我公众号「码农富哥」 ,我会持续输出Python,服务器端架构,计算机基础的 原创 文章

关注后回复”1024” 免费获取 图解TCP/IP,亿级大型网站架构,Linux,HTTP,MySQL高性能等电子书

扫码关注我:码农富哥

本文转载自: 掘金

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

SpringBoot2x基础篇:Linux后台运行Jar以

发表于 2020-02-21

知识改变命运,撸码使我快乐,2020继续游走在开源界

点赞再看,养成习惯

给我来个Star吧,点击了解下基于SpringBoot的组件化接口服务落地解决方案

我们将编写的应用程序打包为Jar可执行文件后,如果在Linux服务器环境下,可直接使用java -jar xxx.jar命令运行应用程序,不过当我们关闭命令窗口后启动中的应用程序也会停止,那我们需要通过什么方式才可以成为后台服务方式运行呢?

Nohup命令

Linux系统或者OS X都提供了一个解决应用程序后台运行的命令,那就是nohup,我们使用该命令可以直接将要执行的任务放置在后台运行,想要停止运行时需要通过结束pid的方式,使用方式如下所示:

1
2
3
复制代码➜  developing-first-application git:(2.x) ✗ nohup java -jar target/service-application-0.0.1-SNAPSHOT.jar &
[1] 2349
appending output to nohup.out

我们通过以上的命令执行后可以看到控制台输出了本次运行程序的PID为 2349,我们可以使用kill命令杀死这个PID,从而达到了结束进程的效果。

注意事项:appending output to nohup.out这句话很有必要了解下,要知道我们之前通过java -jar xxx.jar直接运行应用程序时会有运行日志输出到控制台的,我们通过nohup方式运行时我们貌似并没有发现日志的输出,日志去了哪里呢?

运行日志

当你看到appending output to nohup.out这句话在控制台打印时,应该可以猜测到了,日志的内容已经输出到了名为nohup.out的文件内,该文件所处的位置就是我们运行nohup命令的同级目录(注意:不是jar文件的目录),我们可以通过tail -1000f nohup.out命令查看运行日志内容,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
复制代码➜  developing-first-application git:(2.x) ✗ tail -1000f nohup.out 

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.4.RELEASE)
...
2020-02-21 14:31:42.614 INFO 2349 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-21 14:31:42.617 INFO 2349 --- [ main] o.m.c.d.f.a.DevelopingFirstApplication : Started DevelopingFirstApplication in 1.437 seconds (JVM running for 1.75)

通过nohup执行的命令所产生的日志都会输出到默认nohup.out文件内。

指定日志文件

在同一台服务器上、同一个目录下可能会存在多个需要运行的Jar文件,为了区分每个应用程序的日志输出,这时我们就需要指定日志输出的文件名,如下所示:

1
2
复制代码➜  developing-first-application git:(2.x) ✗ nohup java -jar target/service-application-0.0.1-SNAPSHOT.jar &> service-application-0.0.1.log & 
[1] 2579

这时我们在nohup命令执行的同级目录下就可以看到创建了一个名为service-application-0.0.1.log的日志文件。

建议:日志文件的名称格式:Service ID + Service Version,相同ServiceID的服务可能存在部署不同版本的情况。

JVM Server模式

在JVM内有一个模式的概念,开发环境中一般使用的是client模式,不过生产服务器上一般都是使用server模式,我们要怎么选择呢?

推荐开发环境使用client模式,因为它启动快,可以提高一部分开发效率,节省每一次项目启动的时间,而生产环境则是推荐使用server模式,内部使用了代号为C2的重量级编译器,这样虽然导致应用程序启动速度有所提高,不过编译的比较彻底,服务在运行期间相对于client性能高一些。

设置使用server模式也比较简单,我们只需要执行java -server命令即可,如下所示:

1
2
复制代码➜  developing-first-application git:(2.x) ✗ nohup java -server -jar target/service-application-0.0.1-SNAPSHOT.jar &> service-application-0.0.1.log &
[1] 2707

初始内存(-Xms)

JVM在client模式下运行,默认Xms大小为1M,而在server模式下默认Xms大小为128M,可以根据实际情况进行修改分配,如下所示:

1
2
复制代码➜  developing-first-application git:(2.x) ✗ nohup java -server -Xms256M -jar target/service-application-0.0.1-SNAPSHOT.jar &> service-application-0.0.1.log &
[1] 2846

通过-Xms256M,修改初始化分配的内存为256M。

最大内存(-Xmx)

JVM在client模式下运行,默认Xmx大小为64M,而在server模式下默认Xmx大小为1024M,可以根据实际情况进行修改分配,如下所示:

1
2
复制代码➜  developing-first-application git:(2.x) ✗ nohup java -server -Xms256M -Xmx2048M -jar target/service-application-0.0.1-SNAPSHOT.jar &> service-application-0.0.1.log &
[1] 2340

通过-Xmx2048M,修改最大分配内存为2048M。

JVM调优脚本

JVM的调优尤为最重,服务器的配置有限,可使用的资源我们则是要珍惜,做出最大的贡献!!!

为了每次部署服务的便利性,我把启动服务的命令进行了封装,并命名为boot-jar.sh,内容如下所示:

1
2
3
4
5
复制代码#!/bin/bash
# author 恒宇少年 - 于起宇
# http://blog.yuqiyu.com
nohup java -server -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms256m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC -jar "$1" > "$1.log" 2>&1 &
tail -1000f "$1.log"

使用touch boot-jar.sh创建启动脚本,创建完成后将上面内容复制到脚本内,并通过chmod u+x boot-jar.sh命令修改权限为可执行文件。

boot-jar.sh脚本使用如下:

1
复制代码➜  developing-first-application git:(2.x) ✗ ./boot-jar.sh target/service-application-0.0.1-SNAPSHOT.jar

由于脚本内添加了tail命令,应用程序启动后会自动输出运行日志。

建议:boot-jar.sh应用程序启动脚本位置尽量放在与Jar同级目录下。


作者个人 博客

使用开源框架 ApiBoot 助你成为Api接口服务架构师

本文转载自: 掘金

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

面试官:Java的重写和重载有什么区别?

发表于 2020-02-21

老读者都知道了,七年前,我从美女很多的苏州回到美女更多的洛阳(美化了),抱着一幅“从二线城市退居三线城市”的心态,投了不少简历,也“约谈”了不少面试官,但仅有两三个令我感到满意。其中有一位叫老马,至今还活在我的微信通讯录里。他当时扔了一个面试题把我砸懵了:“王二,Java 的重写(Override)和重载(Overload)有什么区别?”

那年我二十三岁,正值青春年华,大约就是周杰伦发布《八度空间》的年纪,自认为所有的面试题都能对答如流,结果没想到啊,被“刁难”了——原来洛阳这块互联网的荒漠也有技术专家啊。现在回想起来,脸上不自觉地泛起了羞愧的红晕:主要是自己当时太菜了。不管怎么说,七年时间过去了,我的技术功底已经非常扎实,有能力写篇文章剖析一下 Java 的重写和重载了,只要能给后来者一些参考,我就觉得做了天大的善事。

好了,让我们来步入正题。先来看一段重写的代码吧。

1
2
3
4
5
6
7
8
9
10
11
复制代码class LaoWang{
public void write() {
System.out.println("老王写了一本《基督山伯爵》");
}
}
public class XiaoWang extends LaoWang {
@Override
public void write() {
System.out.println("小王写了一本《茶花女》");
}
}

重写的两个方法名相同,方法参数的个数也相同;不过一个方法在父类中,另外一个在子类中。就好像父类 LaoWang 有一个 write() 方法(无参),方法体是写一本《基督山伯爵》;子类 XiaoWang 重写了父类的 write() 方法(无参),但方法体是写一本《茶花女》。

来写一段测试代码。

1
2
3
4
5
6
复制代码public class OverridingTest {
public static void main(String[] args) {
LaoWang wang = new XiaoWang();
wang.write();
}
}

大家猜结果是什么?

1
复制代码小王写了一本《茶花女》

在上面的代码中,们声明了一个类型为 LaoWang 的变量 wang。在编译期间,编译器会检查 LaoWang 类是否包含了 write() 方法,发现 LaoWang 类有,于是编译通过。在运行期间,new 了一个 XiaoWang 对象,并将其赋值给 wang,此时 Java 虚拟机知道 wang 引用的是 XiaoWang 对象,所以调用的是子类 XiaoWang 中的 write() 方法而不是父类 LaoWang 中的 write() 方法,因此输出结果为“小王写了一本《茶花女》”。

再来看一段重载的代码吧。

1
2
3
4
5
6
7
8
9
复制代码class LaoWang{
public void read() {
System.out.println("老王读了一本《Web全栈开发进阶之路》");
}

public void read(String bookname) {
System.out.println("老王读了一本《" + bookname + "》");
}
}

重载的两个方法名相同,但方法参数的个数不同,另外也不涉及到继承,两个方法在同一个类中。就好像类 LaoWang 有两个方法,名字都是 read(),但一个有参数(书名),另外一个没有(只能读写死的一本书)。

来写一段测试代码。

1
2
3
4
5
6
7
复制代码public class OverloadingTest {
public static void main(String[] args) {
LaoWang wang = new LaoWang();
wang.read();
wang.read("金瓶梅");
}
}

这结果就不用猜了。变量 wang 的类型为 LaoWang,wang.read() 调用的是无参的 read() 方法,因此先输出“老王读了一本《Web全栈开发进阶之路》”;wang.read("金瓶梅") 调用的是有参的 read(bookname) 方法,因此后输出“老王读了一本《金瓶梅》”。在编译期间,编译器就知道这两个 read() 方法时不同的,因为它们的方法签名(=方法名称+方法参数)不同。

简单的来总结一下:

1)编译器无法决定调用哪个重写的方法,因为只从变量的类型上是无法做出判断的,要在运行时才能决定;但编译器可以明确地知道该调用哪个重载的方法,因为引用类型是确定的,参数个数决定了该调用哪个方法。

2)多态针对的是重写,而不是重载。

哎,后悔啊,早年我要是能把这道面试题吃透的话,也不用被老马刁难了。吟一首诗感慨一下人生吧。

青青园中葵,朝露待日晞。
阳春布德泽,万物生光辉。
常恐秋节至,焜黄华叶衰。
百川东到海,何时复西归?
少壮不努力,老大徒伤悲

另外,我想要告诉大家的是,重写(Override)和重载(Overload)是 Java 中两个非常重要的概念,新手经常会被它们俩迷惑,因为它们俩的英文名字太像了,中文翻译也只差一个字。难,太难了。

好了,我亲爱的读者朋友,以上就是本文的全部内容了。能看到这里的都是最优秀的程序员,二哥必须要伸出大拇指为你点个赞👍。原创不易,如果觉得有点用的话,请不要吝啬你手中点赞的权力,因为这将是我最强的写作动力。我们下篇文章见!

本文转载自: 掘金

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

程序员必备的「大数据入门」知识

发表于 2020-02-21

前言

只有光头才能变强。

文本已收录至我的GitHub仓库,欢迎Star:github.com/ZhongFuChen…

这篇文章主要是入门大数据,不涉及到高深的知识点和理论,我相信每个人都看得懂。如果文章有错误的地方,不妨在评论区友善指出~

一、什么是大数据?

1.1 前置知识

我有的时候给外行人讲解什么是数据库,就常常用Excel来举例子(因为大多数人认识什么是Excel)。在知乎有一个类似的题目《有excel了要数据库干啥?》,大家可以去看看:

  • www.zhihu.com/question/26…

其实很大一部分原因就是:Excel能处理的数据量远远没有数据库得多。由于我们互联网产生的数据是非常非常多的,所以我们一般选择数据库来存储数据。

Excel只有104w行,多了加载不进去的 —- @知乎 EamonLiao

众所周知,我们能存多少数据,是取决于我们硬盘的大小的。比如,我的磁盘的大小就256GB(实际能存储的大小是没有256GB的,但这里我就不展开了),这意味着我这电脑只能存储比256GB要小的数据。

硬盘的大小

为了能够更好地管理计算机的数据(访问和查找变得更加简单),我们就有了文件系统。

文件系统

有了文件系统,已经可以存储数据了(很方便我们去获取),那为什么还会有数据库呢?

文件系统存在以下缺点: 数据共享性差,冗余度大; 数据独立性差

数据库系统实现整体结构化,这是数据库系统与文件系统的本质区别。 —–《数据库系统概论》


数据库其实就是为了针对特定类型数据处理而设计的系统,而文件系统则可看作通用型的数据存储系统 @知乎 吴穗荣

再回到大数据上,大数据就看名字我们就知道:数据量很大。大到什么程度呢?一块普通的硬盘不能将一个文件存储下来。

那我还想将这个文件存下来,怎么办呢?方案其实很简单(说白了一个是垂直伸缩,一个是水平伸缩):

  • 多买几块硬盘,组成一个更大的“硬盘”,希望能容纳更多的数据。
+ RAID(独立磁盘冗余阵列)技术是将**多块普通磁盘组成一个阵列,共同对外提供服务**。
+ 比如,我现在觉得我的电脑16GB不够用了,而我的主板有两个内存槽,我多买一条16GB的内存条插入。那我就可以说,我的电脑是32GB内存的。
  • 把这个文件切开几份,存到不同的硬盘中
+ 比如我有1个TB的文件,我把它切分成5份,每份200G,存到不同的服务器中。

如果是普通的用户,肯定选择的是多买一块硬盘,升级硬件啊。但是互联网公司就不这样干,他们就选择将一个文件切分成几份,放到不同的服务器中。为什么?

  1. 顶级的电脑硬件成本很大。(单台计算机性能到一定的量上,再升级的成本就非常高)
  2. 单单一台顶级的电脑可能也无法处理掉这么大量的数据

垂直伸缩和水平伸缩

综上所述,目前互联网企业中都是选择水平伸缩在一个系统中添加计算机来满足不断增长的用户量和支撑数据的平稳运行。

1.2 解决存储问题

随着数据量越来越大,在一台机器上已经无法存储所有的数据了,那我们会将这些数据分配到不同的机器来进行存储,但是这就带来一个问题:不方便管理和维护

所以,我们就希望有一个系统可以将这些分布在不同操作服务器上的数据进行统一管理,这就有了分布式文件系统

  • HDFS是分布式文件系统的其中一种(目前用得最广泛的一种)

在使用HDFS的时候是非常简单的:虽然HDFS是将文件存储到不同的机器上,但是我去使用的时候是把这些文件当做是存储在一台机器的方式去使用(背后却是多台机器在执行):

  • 好比:我调用了一个RPC接口,我给他参数,他返回一个response给我。RPC接口做了什么事其实我都不知道的(可能这个RPC接口又调了其他的RPC接口)—–屏蔽掉实现细节,对用户友好

HDFS使用

1.3 解决计算问题

上面我们使用HDFS作为分布式文件系统,已经可以把数据存到不同的机器上(或者在不同的机器上读取到数据)。可以通过简单命令行的方式对文件的简单的存取。

现在呢,由于数据量是非常大的,分散到不同的机器上。我们想要对数据进行处理,我们肯定会有一段写好的程序。处理的方式有两种:

  • 将数据传递给程序(机器A/B/C的数据传给机器D程序来执行)
  • 程序到数据所在的地方执行(将程序分别到机器A/B/C上执行)

选哪个?我们一般会采用”程序到数据所在的地方执行“,因为在大数据里边我们的数据量很大,如果要把机器A/B/C的数据输入到机器D上,这样不划算。

  • 数据量很大,通过网络传输大数据到某一台机器上做操作,不合适。
  • 机器D只有一台机器,处理的效率低下。

所以我们会将程序分别放到机器A/B/C上处理,本来程序就非常小,放到别的机器上是轻轻松松的。还可以使用机器A/B/C的资源做运算,这就很合适了。

「将数据传递给程序」这种就是所谓的“移动存储到计算”,而「程序到数据所在的地方执行」这种就是所谓的“移动计算到存储的观念”。

在大数据的领域里, 移动计算比移动数据更划算。MapReduce就是这样干的:

  • 每台机器执行任务的时候去检查自己有没有相应的程序,如果没有则通过网络下载程序包,然后通过反射来加载程序

二、大数据没有想象中神秘

在刚听到「大数据」这个词的时候,可能有的人会想问:所谓大数据,那数据是从哪里来的呢?

简单来说可以归类为三类:

  • 日志
  • 数据库
  • 爬虫

1、 爬虫应该很好理解,就是通过网络爬虫获取外部数据,将这些数据自己存储起来。很多的比价网站就是爬取各种电商网站的数据,然后比较各个网站的数据后得到结果。本身它们网站本身是没有这个数据的,这个数据是从别人那爬过来的。

2、数据库本来就已经存储了我们的数据,而我们要做的只是把数据库的数据导入我们的大数据平台那儿,让数据能够得到更好的分析。

3、日志这块其实我更多想说的是打点(埋点)这块。有的人会把这埋点和日志的概念分开,只是我把它给合在一起叫「日志」。日志有用户行为日志(埋点),也有系统的运行产生的日志。用户行为日志这块说白了就是:从你进去某个APP的一刻开启。几乎你所有的操作都会被记录下来(点了某个tag、在某个tag停顿了多少秒)。猜你喜欢这类的系统就是根据你以往行为来对进行推荐。

好了,现在我们有不同的地方收集到数据,我们要最终要做的就是把这个数据汇总到一起来进行存储和分析。

于是我们就需要将日志、数据库、爬虫这些不同数据源的数据导入到我们的集群中(这个集群就是上面提到的,分布式文件系统(HDFS),分布式计算系统)。

由于数据源的不同,所以会有多种的工具对数据进行导入。比如将日志的数据收集我们会有Flume,从数据库同步我们会有Sqoop。这也就是所谓的ETL(萃取「extract」、转置「transform」、加载「load」)

ETL是将业务系统的数据经过抽取、清洗转换之后加载到数据仓库的过程,目的是将企业中的分散、零乱、标准不统一的数据整合到一起,为企业的决策提供分析依据。

(注:不要被上面的Sqoop、Flume 这样的名词给吓着了,其实就是现有的成熟的框架技术)

我们把所有的数据收集起来,把这个存储数据的地方叫做数据仓库。基于数据仓库我们统计能各种的指标,基于这些指标可以指导我们是否要做一个需求或决策。

比如说:现在我们要对APP的首页改版了,但不知道用户是否能接受这次的改版。于是我们就可以先对一少部分的用户做实验(这一部分的用户看到的是改版后的首页),我们根据这一部分用户的行为来判断这一次的改版是否有比较好的效果。

  • 用户的行为我们都有收集起来。只要将实验用户关联到对应的指标,与现有的指标做一次对比,我们大概就知道这次改版是否真的合理。

这种拿一部分流量做实验,我们也称这种做法为「ABTest」,如果对ABTest感兴趣的同学可以在我的GitHub下搜索关键字「ABTest」来阅读具体的文章哦~

最后

这篇文章简单的说了一下所谓的「大数据」中的数据是从哪里来的,由于数据量很大,所以我们要解决数据的存储和计算的问题。

基于存储和计算问题我们业内就提供了很多现成的技术实现了,下面图中的技术每一种类型我后续都会讲解到,可以关注我,不迷路哦。

参考资料:《从0开始学大数据》–李智慧

如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号「Java3y」。

  • 🔥海量视频资源
  • 🔥Java精美脑图
  • 🔥Java学习路线
  • 🔥开发常用工具
  • 🔥精美整理好的PDF电子书

在公众号下回复「888」即可获取!!

本已收录至我的GitHub精选文章,欢迎Star:github.com/ZhongFuChen…

求点赞 求关注️ 求分享👥 求留言💬 对我来说真的 非常有用!!!

本文转载自: 掘金

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

SpringBoot2x基础篇:将应用程序打包为可执行Ja

发表于 2020-02-20

知识改变命运,撸码使我快乐,2020继续游走在开源界

点赞再看,养成习惯

给我来个Star吧,点击了解下基于SpringBoot的组件化接口服务落地解决方案

应用程序在编写完成后,有一个重要的阶段就是发布,当我们发布时需要将应用程序进行打包,那通过SpringBoot编写的应用程序该如何打包呢?

推荐阅读

  • SpringBoot2.x基础篇:开发你的第一个SpringBoot应用程序

打包方式

应用程序的发布一般有两种形式。

比较传统的方式是外置Tomcat,将应用程序打包成一个xx.war文件,该文件内只有应用程序源码编译后的.class以及配置文件。

而SpringBoot还提供了另外一种高效率的打包方式,在pom.xml内通过配置maven plugin,执行mvn package打包命令时会将src/main/java、src/main/resources目录下的全部文件进行打包,最终生成一个xx.jar的文件,由于SpringBoot打包时默认会将Tomcat的相关依赖一并放入到xx.jar内,所以通过java -jar xx.jar命令行的方式可以直接运行。

打包插件

我们通过IDEA创建SpringBoot项目时,一般在pom.xml文件内默认已经添加了打包maven plugin,如下所示:

1
2
3
4
5
6
7
8
xml复制代码<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

注意事项:如果你不是通过spring-boot-starter-parenter方式构建的SpringBoot应用程序,需要手动配置,有关插件的使用文档,详见 Spring Boot Maven Plugin

执行打包

使用Maven构建的SpringBoot应用程序打包方式很简单,我们只需要通过命令在应用程序的根目录下执行mvn package即可,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
routeros复制代码➜  developing-first-application git:(2.x) mvn package           
[INFO] Scanning for projects...
[INFO]
[INFO] ----------< org.minbox.chapter:developing-first-application >-----------
[INFO] Building developing-first-application 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
省略部分编译日志......
[INFO] --- maven-jar-plugin:3.1.2:jar (default-jar) @ developing-first-application ---
[INFO] Building jar: /Users/yuqiyu/study/article-source-code/spring-boot-chapter/developing-first-application/target/developing-first-application-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.2.4.RELEASE:repackage (repackage) @ developing-first-application ---
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.711 s
[INFO] Finished at: 2020-02-20T15:02:24+08:00
[INFO] ------------------------------------------------------------------------

当控制台出现BUILD SUCCESS时,证明我们本次package已经成功了,当前应用程序的可执行Jar也已经生成,位于target目录下。

打包文件命名

spring-boot-maven-plugin插件打包完成后生成的文件名默认的格式为: + .jar,如:developing-first-application-0.0.1-SNAPSHOT.jar,如果这并不是你想要的格式化,可以通过如下方式进行自定义:

1
2
3
dust复制代码<build>
<finalName>service-application-${version}</finalName>
</build>

注意事项:与是同级的配置,可以使用占位符属性${属性名}的方式来进行格式化命名内容,这只是文件的名称,在打包完成后后缀名.jar会自动追加。

跳过测试

项目在打包过程中会自动运行测试,来检查项目是否可以通过运行测试以及测试脚本的执行是否有效,一般这个过程是需要一定时间的,项目内容越多需要的时间就会越久,如果你想跳过这个测试过程,只需要添加一个很简单的属性配置即可,如下所示:

1
2
3
xml复制代码<properties>
<maven.test.skip>true</maven.test.skip>
</properties>

这样我们再运行mvn package时,就会跳过测试步骤。

运行Jar

要运行该应用程序,可以使用java -jar命令,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gherkin复制代码➜  developing-first-application git:(2.x) ✗ java -jar target/service-application-0.0.1-SNAPSHOT.jar 

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.4.RELEASE)

2020-02-20 15:29:39.615 INFO 3208 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-02-20 15:29:39.626 INFO 3208 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-02-20 15:29:39.626 INFO 3208 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.30]
2020-02-20 15:29:39.953 INFO 3208 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-20 15:29:39.955 INFO 3208 --- [ main] o.m.c.d.f.a.DevelopingFirstApplication : Started DevelopingFirstApplication in 1.539 seconds (JVM running for 1.868)

如果想要退出该应用程序,请按Ctrl + C。


作者个人 博客

使用开源框架 ApiBoot 助你成为Api接口服务架构师

本文转载自: 掘金

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

「硬核干货」总结 IDEA 开发的26个常用设置

发表于 2020-02-20

前言

程序员对待IDE都是虔诚的,经常因为谁是最好的IDE而在江湖上掀起波澜,曾经我也是。

后来我遇到了IDEA,从此是它,余生都是它。

IDEA 毫无疑问是目前最强大的Java开发工具了,但是大部分的人并没有将它用到极致,很多实用的配置,插件,小技巧被忽视。就像JDK8的新特性一样,你知道它在且坚持不用,但是只要用过一次就知道什么是真滴香。

以下所有常见配置的思维导图,内容丰富,思维导图配合 Ctrl+F 搜索食用风味儿更佳。
先赞后看,养成习惯

IDEA 软件设置Settings页面

Settings是对软件本身的一些属性进行配置,例如字体 主题 背景图 插件等。

如何打开Settings设置页面

Settings设置页面结构详解

结构

  1. Appearance & Behavior 外观和行为
  2. Keymap 快捷键
  3. Editor 编辑器设置
  4. Plugins 插件
  5. Version Control 版本控制
  6. Build,Execution,Deployment 构建,执行,部署
  7. Languages & Frameworks 语言和框架
  8. Tools 工具集

IDEA 常见的xx个设置

绝大部分的设置都包含效果示意图,文章结构:设置方法–>效果示意图

Appearance & Behavior 外观和行为

设置主题

目前我的IDEA中有两套主题:Darcula, IntelliJ。

  • Darcula 就是神秘的黑色主题,我偏爱黑色
  • IntelliJ 白色主题

设置菜单字体

Keymap 快捷键

设置为Eclipse快捷键的键位

如果对 Eclipse 难以忘怀可以使用其键位,这种操作我也不推荐,感觉别扭。

修改快捷键

不要一次性修改大量的快捷键

  • Add keyboard Shortcut 添加键盘快捷键 实际上就是修改当前的键位
  • Add Mouse Shortcut 增加鼠标捷径
  • Add Abbreviation
  • Remove Ctrl+Alt+Shift+S 移除改快捷键

查看快捷键的功能

通过快捷键 查找快捷键的功能,例如:你不知道 Ctrl+F什么意思 可以通过该功能进行搜索

Editor 编辑器设置

Editor 中的设置可以说是最高频使用的设置了,编辑区的字体,自动导包,注释生成等等
什么是Editor编辑器呢?就是写代码的区域

Editor-General

设置鼠标滚轮滚动修改字体大小

设置成功之后,就可以通过 Ctrl+滚轮 实现代码字体大小的调节了

设置鼠标悬浮提示

悬停提示是一个非常有用的功能,效果如下

设置自动导包

  • Add unambiguous imports on the fly:添加明确的导入
  • Optimize imports on the fly:自动帮我们优化导入的包

设置显示行号和方法间的分隔符

效果如下

忽略大小写提示

  1. Ctrl + Alt + S 打开设置界面(或者从菜单File - settings 进来)
  2. 在搜索框输入Completion定位到Code Completion功能项
  3. 在右侧配置界面选中All letters
  4. 在右侧配置界面把Match case(匹配大小写)前面的√去掉,

  • IntelliJ IDEA 的代码提示和补充功能有一个特性:区分大小写。如上图标注所示,默认就是 First letter 区分大小写的。
  • 区分大小写的情况是这样的:比如我们在 Java 代码文件中输入 stringBuffer, IntelliJ IDEA 默认是不会帮我们提示或是代码补充的,但是如果我们输入 StringBuffer 就可以进行代码提示和补充。

设置取消单行显示标签页(Tabs)的操作

什么是单行显示

如何设置为多行?

效果如下

Editor-Font

修改当前主题代码编辑区的字体

默认字体、字体大小、字体行间距,都可以单独设置

Editor-Color Scheme

修改当前主题控制台输出的字体

修改代码中注释的字体颜色

Editor-Code Style

设置超过指定 import 个数,改为*

Editor-File and Code Template

修改类头的文档注释信息

效果如下

Editor – File Encodings

设置项目文件编码

Transparent native-to-ascii conversion 主要用于转换 ascii,一般都要勾选, 不然 Properties 文件中的注释显示的都不会是中文。

设置当前源文件的编码

对单独文件的编码修改还可以点击右下角的编码设置区。如果代码内容中包含中 文,则会弹出如上的操作选择。其中:

  1. Reload 表示使用新编码重新加载,新编码不会保存到文件中,重新打开此文 件,旧编码是什么依旧还是什么。
  2. Convert 表示使用新编码进行转换,新编码会保存到文件中,重新打开此文件, 新编码是什么则是什么。
  3. 含有中文的代码文件,Convert 之后可能会使中文变成乱码,所以在转换成请 做好备份,不然可能出现转换过程变成乱码,无法还原。

Build、Execution、Deployment

设置自动编译

Intellij Idea 默认状态为不自动编译状态,运行项目的时候才会进行编译,Eclipse 默认为自动编译。

不能自动编译是什么效果?
图中分别是Hello.java和编译后的class文件的对比

修改Java源代码,因为不能自动编译,所以class文件是不会有变化的

IDEA模式是运行时编译,所以我们运行主函数,之后可以看到如下结果

下面我们来设置自动编译,看一下效果

IDEA设置自动编译

自动编译的效果
修改源代码

稍等一会儿然后打开class文件可以看到如下效果

其他

设置为省电模式

如上图所示,IntelliJ IDEA 有一种叫做 省电模式 的状态,开启这种模式之后 IntelliJ IDEA 会关掉代码检查和代码提示等功能。所以一般也可认为这是一种阅读模式,如果你在开发过程中遇到突然代码文件不能进行检查和提示,可以来看看这里是否有开启该功能。

设置代码分屏展示

效果如下

还可以添加分屏快捷键

背景图设置

  1. 在idea中 双击shift 出现搜索框 输入: Set Background Image 单击 Set Background Image

2.选择要添加的图片的路径,设置透明度,点击OK 就可以查看效果了

效果如下

IDEA 当前项目结构设置 Project Structure

项目结构配置就是关于当前模块的配置,只对当前模块生效,例如Jar包,包结构,源码文件夹,输出路径,依赖和项目构建信息。

如何打开 Project Structure 页面

Project Structure 页面结构讲解

project

image.png

image.png

Modules

可以查看每一个子模块的信息

Artifacts

IDEA带包配置,有时候用IDEA打的包有问题,部署到Tomcat中跑不起来,可以再打包一次。

Facets

当前项目配置文件的相关信息

修改IDEA默认设置

总结

以上就是我总结的IDEA开发常见的所有配置,如果本文对你有帮助,不要忘了收藏点赞支持一些。

关于IDEA的插件和一些使用技巧,请持续关注。

本文转载自: 掘金

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

老背题却记不住TCP为什么三次握手?

发表于 2020-02-19

前言

今天肥朝和大家唠唠老生常谈的一个问题,TCP为什么要三次握手。为什么会突然想起这个话题呢?

按照肥朝以往的风格,网上一搜就一大把的文章,肥朝一般都是不愿意去提及的。其实每个技术人都或多或少萌生过写博客的想法,但是后面发现想写的内容网上似乎都有,最后就断了这个念头。

因此,肥朝的定位就两个,一个是成体系的源码解析类文章(类似17年的Dubbo源码解析和19年的图解SpringBoot源码),一个是真实场景源码实战,既然是真实场景,那么雷同的概率就几乎忽略不计了。

既然如此,那为啥要提TCP的三次握手?这些别说网上了,大学课本都大把,而且还权威。

对于学习的一些思考

在计算机原理、网络、编译原理、数据结构与算法等标配计算机知识上,我们经常遇到的问题是,学了面试完就忘,下次面试又要重新“背”题,这样就会导致时间成本过大。因此这段时间肥朝也在思考如何学习这个问题。因为这些标配的知识点,往往晦涩难懂,需要一定的功力和工作年限才能做到理解性记忆。我在思考,学习知识的过程,能否也像架构演进一样,从0到1,从1到10,从10到100这样呢?具体是什么意思呢?

就像架构演进一样,我们先用最简单的单机应用快速搭建业务抢占市场,而不是一上来就直接分布式、高可用,容器化。

同理我们理解一个知识点,是不是一开始并不一定要这么权威,而是先粗略了解他的大致基本思路,等工作经验、知识体系逐步丰满完善,我们再回过头,再进一步学习,这样效果是否会更好?

回到主题

要说TCP为什么是三次握手,那么其实换个角度而言,就是要证明2次太少,4次又没必要。

所谓的三次握手,他的本质是什么?TCP是可靠传输协议,他的本质其实就是要确定客户端和服务端,他们的发送和接收能力是没有问题的。也就是客户端要考察服务端的发送和接收能力,服务端也要考察客户端的发送和接收能力。

第一次握手

肥朝小声逼逼:第一次握手服务端收到了数据包,确定了客户端的发送能力是没有问题的(如图)

第二次握手

肥朝小声逼逼:第二次握手客户端收到了数据包,这个时候,就能证明服务端的发送能力是没有问题的。但是服务端的接收能力有没有问题呢?第一次握手的时候,我们提出的问题是“怎么找到肥朝的公众号?”,收到的回答是“公众号搜索名称肥朝就可以了,你试下可以了告诉我”。回答正好是我们刚才问的,那就说明,服务端的接收能力是没有问题的(如图)

那么,回到刚开始的问题,为啥两次握手不行,因为这个时候,客户端已经确定了服务端的发送和接收能力都没有问题,但是服务端,却没有确定,客户端的接收能力,到底有没有问题。

第三次握手

肥朝小声逼逼:第三次握手,服务端收到了数据包,发现确实是刚才提问的答案,那么,就确定了客户端的接收能力是没有问题的。

再次回到刚才的问题,3次,是完成双方考察发送和接收能力的最少次数。这也就是为啥不是2次,也不是4次。

写在最后

当然你想看更专业的TCP相关知识,大学和考研教辅资料也是不错的选择。


本文转载自: 掘金

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

Spring Boot2 系列教程(三十二)Spring B

发表于 2020-02-17

在 Spring Boot 中做权限管理,一般来说,主流的方案是 Spring Security ,但是,仅仅从技术角度来说,也可以使用 Shiro。

今天松哥就来和大家聊聊 Spring Boot 整合 Shiro 的话题!

一般来说,Spring Security 和 Shiro 的比较如下:

  1. Spring Security 是一个重量级的安全管理框架;Shiro 则是一个轻量级的安全管理框架
  2. Spring Security 概念复杂,配置繁琐;Shiro 概念简单、配置简单
  3. Spring Security 功能强大;Shiro 功能简单
  4. …

虽然 Shiro 功能简单,但是也能满足大部分的业务场景。所以在传统的 SSM 项目中,一般来说,可以整合 Shiro。

在 Spring Boot 中,由于 Spring Boot 官方提供了大量的非常方便的开箱即用的 Starter ,当然也提供了 Spring Security 的 Starter ,使得在 Spring Boot 中使用 Spring Security 变得更加容易,甚至只需要添加一个依赖就可以保护所有的接口,所以,如果是 Spring Boot 项目,一般选择 Spring Security 。

这只是一个建议的组合,单纯从技术上来说,无论怎么组合,都是没有问题的。

在 Spring Boot 中整合 Shiro ,有两种不同的方案:

  1. 第一种就是原封不动的,将 SSM 整合 Shiro 的配置用 Java 重写一遍。
  2. 第二种就是使用 Shiro 官方提供的一个 Starter 来配置,但是,这个 Starter 并没有简化多少配置。

原生的整合

  • 创建项目

创建一个 Spring Boot 项目,只需要添加 Web 依赖即可:

项目创建成功后,加入 Shiro 相关的依赖,完整的 pom.xml 文件中的依赖如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
  • 创建 Realm

接下来我们来自定义核心组件 Realm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
if (!"javaboy".equals(username)) {
throw new UnknownAccountException("账户不存在!");
}
return new SimpleAuthenticationInfo(username, "123", getName());
}
}

在 Realm 中实现简单的认证操作即可,不做授权,授权的具体写法和 SSM 中的 Shiro 一样,不赘述。这里的认证表示用户名必须是 javaboy ,用户密码必须是 123 ,满足这样的条件,就能登录成功!

  • 配置 Shiro

接下来进行 Shiro 的配置:

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
复制代码@Configuration
public class ShiroConfig {
@Bean
MyRealm myRealm() {
return new MyRealm();
}

@Bean
SecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm());
return manager;
}

@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager());
bean.setLoginUrl("/login");
bean.setSuccessUrl("/index");
bean.setUnauthorizedUrl("/unauthorizedurl");
Map<String, String> map = new LinkedHashMap<>();
map.put("/doLogin", "anon");
map.put("/**", "authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
}

在这里进行 Shiro 的配置主要配置 3 个 Bean :

  1. 首先需要提供一个 Realm 的实例。
  2. 需要配置一个 SecurityManager,在 SecurityManager 中配置 Realm。
  3. 配置一个 ShiroFilterFactoryBean ,在 ShiroFilterFactoryBean 中指定路径拦截规则等。
  4. 配置登录和测试接口。

其中,ShiroFilterFactoryBean 的配置稍微多一些,配置含义如下:

  • setSecurityManager 表示指定 SecurityManager。
  • setLoginUrl 表示指定登录页面。
  • setSuccessUrl 表示指定登录成功页面。
  • 接下来的 Map 中配置了路径拦截规则,注意,要有序。

这些东西都配置完成后,接下来配置登录 Controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
复制代码@RestController
public class LoginController {
@PostMapping("/doLogin")
public void doLogin(String username, String password) {
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password));
System.out.println("登录成功!");
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("登录失败!");
}
}
@GetMapping("/hello")
public String hello() {
return "hello";
}
@GetMapping("/login")
public String login() {
return "please login!";
}
}

测试时,首先访问 /hello 接口,由于未登录,所以会自动跳转到 /login 接口:

然后调用 /doLogin 接口完成登录:

再次访问 /hello 接口,就可以成功访问了:

使用 Shiro Starter

上面这种配置方式实际上相当于把 SSM 中的 XML 配置拿到 Spring Boot 中用 Java 代码重新写了一遍,除了这种方式之外,我们也可以直接使用 Shiro 官方提供的 Starter 。

  • 创建工程,和上面的一样

创建成功后,添加 shiro-spring-boot-web-starter ,这个依赖可以代替之前的 shiro-web 和 shiro-spring 两个依赖,pom.xml 文件如下:

1
2
3
4
5
6
7
8
9
10
11
复制代码<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
  • 创建 Realm

这里的 Realm 和前面的一样,我就不再赘述。

  • 配置 Shiro 基本信息

接下来在 application.properties 中配置 Shiro 的基本信息:

1
2
3
4
5
6
复制代码shiro.sessionManager.sessionIdCookieEnabled=true
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
shiro.unauthorizedUrl=/unauthorizedurl
shiro.web.enabled=true
shiro.successUrl=/index
shiro.loginUrl=/login

配置解释:

  1. 第一行表示是否允许将sessionId 放到 cookie 中
  2. 第二行表示是否允许将 sessionId 放到 Url 地址拦中
  3. 第三行表示访问未获授权的页面时,默认的跳转路径
  4. 第四行表示开启 shiro
  5. 第五行表示登录成功的跳转页面
  6. 第六行表示登录页面
  • 配置 ShiroConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码@Configuration
public class ShiroConfig {
@Bean
MyRealm myRealm() {
return new MyRealm();
}
@Bean
DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm());
return manager;
}
@Bean
ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
definition.addPathDefinition("/doLogin", "anon");
definition.addPathDefinition("/**", "authc");
return definition;
}
}

这里的配置和前面的比较像,但是不再需要 ShiroFilterFactoryBean 实例了,替代它的是 ShiroFilterChainDefinition ,在这里定义 Shiro 的路径匹配规则即可。

这里定义完之后,接下来的登录接口定义以及测试方法都和前面的一致,我就不再赘述了。大家可以参考上文。

总结

本文主要向大家介绍了 Spring Boot 整合 Shiro 的两种方式,一种是传统方式的 Java 版,另一种则是使用 Shiro 官方提供的 Starter,两种方式,不知道大家有没有学会呢?

本文案例,我已经上传到 GitHub ,欢迎大家 star:github.com/lenve/javab…

关于本文,有问题欢迎留言讨论。

本文转载自: 掘金

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

量子波动速读《计算机网络:自顶向下方法》

发表于 2020-02-16

本文为《计算机网络:自顶向下方法》的阅读笔记,一些过于简单和偏门的知识点会被忽略,例如经常接触的应用层知识就不需要赘述了。

运输层

无连接运输:UDP

UDP仅仅在IP协议的基础上实现了多路复用(端口)和校验,不保证交付以及按序交付,虽然简陋但是有以下优点:

  • 关于何时、发送什么数据的应用层控制更为精细;
  • 无需连接建立;
  • 无连接状态;
  • 分组首部开销小。

首部只有四个字段:源端口、目标端口、长度和校验和。发送方的UDP对报文段中的所有16比特字的和进行反码运算,求和时遇到的任何溢出都被回卷。在接收方,全的16比特字(包括检验和)加在一起,如果该分组中没有引入差错,则显然在接收方处该和将是1111111111111111。

可靠数据传输原理

如何在不可靠的信道上实现可靠的传输,我们可以根据信道的特性从简到繁一步步得到可靠的传输协议。

经完全可靠信道的可靠数据传输

在可靠信道只需要简单地发送接收就行。

  • 发送端

  • 发送端

经具有比特差错信道的可靠数据传输

这种情况需要自动重传请求协议来处理,需要三种机制处理比特差错:

  1. 差错检测,需要一种机制以使接收方检测到何时出现了比特差错;
  2. 接收方反馈:接收方回答的“肯定确认”(ACK)和“否定确认”(NAK);
  3. 重传:接收方收到有差错的分组时,发送方将重传该分组文。

一种直观的方案是发送收到ACK后发送下个分组,收到NAK后重新发送,然后确认分组本身也会损坏。当发送方收到损坏的确认分组时,需重传当前数据分组即可。 然而,这种方法在发送方到接收方的信道中引人了冗余分组,这就需要发送方对其数据分组编号来确认收到的分组是否为重传数据。另外,如果不发送 NAK, 而是对上次正确接收的分组发送一个ACK,发送方就知道接收方没有正确接收到跟在被确认两次的分组后面的分组。

  • 发送方

  • 接收方

经具有比特差错的丢包信道的可靠数据传输

对于丢包信道,一方面需要重传丢失数据,另外需要一个倒计数定时器来实现超时重传,发送方需要做到:

  1. 每次发送一个分组时,便启动一个定时器。
  2. 响应定时器中断。
  3. 终止定时器。
  • 发送方

流水线可靠数据传输协议

停等协议的信道利用率低,更好的方法是使用流水线,一次发送和确认多个分组,协议需要做出以下扩展:

  • 必须增加序号范围;
  • 协议的发送方和接收方两端也许必须缓存多个分组;
  • 解决流水线的差错恢复有两种基本方法是:回退N步和选择重传。
回退N步

回退N步也成为滑动窗口协议,要求流水线中未确认的分组数不能超过窗口大小N,协议的发送方看到的分组流水线如下:

  • 基序号(base)为最早的未确认分组的序号;
  • 下一个序号(nextseqnum)为最小的未使用序号;

发送方必须响应三种类型的事件:

  • 上层的调用:当上层调用 rdt_send()时,发送方首先检查发送窗口是否已满。如果窗口未满,则产生一个分组并将其发送,并相应地更新变量。如果窗口已满,发送方告知上层该窗口已满;
  • 收到一个ACK:对序号为n的分组的确认采取累积确认的方式,表明接收方已正确接收到序号为几的以前且包括在内的所有分组。
  • 超时事件:如果出现超时,发送方重传所有已发送但还未被确认过的分组。

因此完整的协议如下:

  • 发送方

  • 接收方

选择重传

回退N步会传递已经收到的分组,造成了性资源的浪费,而选择重传只需要重传丢失或者损坏的分组即可。发送方和接收方看到的流水线如下:

发送方的事件与动作如下:

  1. 从上层收到数据。和回退N步相同;
  2. 超时。现在每个分组必须拥有其自己的逻辑定时器,因为超时发生后只能发送一个分组;
  3. 收到ACK。如果收到ACK,倘若该分组序号在窟口内,则发送方将那个被确认的分组标记为已接收。如果该分组的序号等于send_base,则窗口基序号向前移动到具有最小序号的未确认分组处。如果窗口移动了并且有序号落在窗口内的未发送分组,则发送这些分组。

面向连接的运输:TCP

TCP在不可靠的IP协议之上实现了一个端到端的可靠数据传输通道(可靠、有序)。TCP协议是一道字节流流水线,每次从缓冲区取出最大报文段长度组成分组。TCP报文段首部如下

  • 32比特的序号字段和32比特的确认号字段;
  • 16比特的接收窗口字段,该字段用于指示接收方愿意接受的字节数;
  • 首部长度字段,该字段指示了以32比特的字为单位TCP首部长度;
  • 可选与变长的选项字段;
  • 6比特的标志字段,ACK比特用于指示确认字段中的值是有效的,RST、SYN和FIN比特用于连接建立和拆除。

TCP的序号是建立在传送的字节流之上,因此一个报文段的序号 是该报文段首字节的字节流编号,主机填充进报文段的确认号是主机期望从主机 收到的下一字节的序号,TCP使用了累计确认。

TCP发送方的整体运行逻辑如下

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
复制代码NextSeqNum=InitialSeqNumber
SendBase=InitialSeqNumber

loop (forever) {
switch(event) {
event: data received from application above
create TCP segment with sequence number NextSeqNum
if (timer currently not running)
start timer
pass segment to IP
NextSeqNum=NextSeqNum+length(data)
break;
event: timer timeout
retransmit not-yet-acknowledged segment with
smallest sequence number
start timer
break;
event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase=y
if (there are currently any not-yet-acknowledged segments)
start timer
}
break;
}
} /* end of loop forever */

TCP涉及到以下技术点:

  • 往返时间的估计与超时:需要统计出RTT平均值(EstimatedRTT)和方差(DevRTI),从而可以计算出超时时间:Timeoutlnterval = EstimatedRTT + 4 · DevRTT;
  • 可靠数据传输
    • 超时间隔加倍:每次TCP重传时都会将下一次的超时间隔设为先前值的两倍。
    • 快速重传:一且收到3个冗余ACK,TCP就执行快速重传。
    • 流量控制:TCP通过让发送方维护一个称为接收窗口,接收窗口告知发送方接收方还有多少可用的缓存空间。

TCP的连接通过三次握手建立

  1. 客户端发送SYN报文,并告知随机选择的初始序号(client_isn);
  2. 服务端回复SYNACK报文,告知随机选择的初始序号(server_isn),以及确认收到client_isn;
  3. 客户端回复送ACK报文,确认收到server_isn。

TCP关闭需要四次握手,一方发送FIN,另外一方回复ACK。

TCP拥塞控制

拥塞会带来诸多问题:

  • 当分组的到达速率接近链路容量时,分组经历巨大的排队时延。
  • 发送方必须执行重传以补偿因为缓存溢出而丢弃(丢失)的分组。
  • 发送方在遇到大时延时所进行的不必要重传会引起路由器利用其链路带宽来转发不必要的分组副本。
  • 当一个分组沿一条路径被丢弃时,每个上游路由器用于转发该分组到丢弃该分组而使用的传榆容量最终被浪费掉了。

TCP使用拥塞窗口cwnd对发送流量的速率进行了限制,TCP定义“丢包事件”为:要么出现超时,要么收到来自接收方的个冗余ACK。拥塞窗口的调整机制如下:

网络层

因特网的网路协议提供的是尽力而为服务,尽力而为服务看起来是根本无服务的一种委婉说法。

路由器工作原理

  • 输入端口:功能包括执行将一条输入的物理链路与路由器相连接的物理层功能、执行需要与位于人链路远端的数据链路层交互的数据链路层功能,以及通过查询转发表决定路由器的输出端口(最长前缀匹配);
  • 交换结构:交换结构将路巾器的输入端口与输出端口相连接,有经内存交换、经总线交换和经互联网络交换三种形式;
  • 输出端口:输出端口存储从交换结构接收的分组,并通过执行必要的链路层和物理层功能在输入链路上传输这些分组;
  • 路由选择处理器:路由选择处理器执行路由选择协议,维护路由选择表以及连接的链路状态信息,并为路由器计算转发表。

当输出端口等待分组超过一次发送能力时就需要排队,当无内存可用于存储到达的分组时将会出现丢包。在输出端口上的一个分组调度程序须在这些排队的分组中选出一个来发送,如先来先服务调度、加权公平排队等。如果没有足够的内存来缓存一个人分组,那么必须选择丢弃的分组,策略有弃尾和主动队列管理(例如随机早期检测)等。

网际协议:因特网中的转发和编址

IPv4数据报首部定义如下

关键字段如下:

  • 版本:IPv4还是IPv6;
  • 首部长度;
  • 服务类型;
  • 数据报长度;
  • 标识、标志、片偏移:与1P分片有关;
  • 寿命:每当数据报由一台路由器处理时,该字段的值减一。TTL字段减为0,则该数据报必须丢弃;
  • 协议:数据部分应交给哪个特定的运输层协议;
  • 首部检验和:在每台路由器上必须重新计算检验和并再次存放到原处,因为TTL字段以及可能的选项字段会改变;
  • 源和目的IP地址;
  • 选项;
  • 数据。

当分组大小超过链路的最大传送单元,发送方需要将分组分片,分片后的分组使用标识、标志和片偏移字段,由最终的接收端完成组装。一个典型的分片如下

片 字节 ID 偏移 标志
第1片 1480 777 0 1
第2片 1480 777 185 1
第3片 1020 777 370 0

IP地址可以人为分配,或者通过DHCP动态分配,DHCP的工作流程如下:

  • DHCP服务器发现:新加入的主机广播一个DHCP发现报文;
  • DHCP服务器提供:广播一个DHCP提供报文向客户作出响应;
  • DHCP请求:并向选中的服务器提供用一个DHCP请求报文;
  • DHCP ACK:服务器用DHCP ACK报文证实所要求的参数。

路由选择算法

路由选择问题可归结为最短路径问题,算法按照是全局式的还是分散式可以分为全局式路由选择算法(链路状态算法)和分散式路由选择算法(距离向量算法),按照静态的还是动态分为静态路由选择算法和动态路由选择算法,按照负载敏感的还是负载迟钝可以分为负载敏感算法和负载迟钝算法。

  • 链路状态算法:让每个结点向网络中所有其他结点广播链路状态分组,然后使用最短路径算法求出路径;
  • 距离向量算法:从相邻路由获取路由信息,然后使用Bellman-Ford公式更新路由表d_x(y) = \min_v|c(x,v) + d_v(y)|,然而距离向量算法存在无穷计数问题,需要毒性逆转技术解决。

两者的区别如下

链路状态算法 距离向量算法
报文复杂性 高 低
收敛速度 快 慢
健壮性 高 低

链路层

链路层协议负责单一链路上的数据传输,可能的服务包括:成帧、链路接入、可靠交付、差错检测和纠正,链路层协议通常由网络适配器实现。

差错检测和纠正技术

  • 奇偶校验:数据加上校验位的1数量为偶数或者奇数,采用二维奇偶校验甚至可以实现纠错。
  • 检验和方法:将数据被作为一个k比特整数的序列处理,将这k比特整数加起来,并且用得到的和作为差错检测比特。
  • 循环冗余检测:对于d位数据,接收方用G(r+l位)去除接收到的d+r比特如果余数为非零,接收方知道出现了差错;否则认为数据正确而被接收。

交换局域网

链路层主要依赖MAC地址进行寻址,将MAC地址和IP地址对应的是ARP协议。每台主机或路由器在其内存中具有一个ARP表,这张表包含IP地址到 MAC地址的映射关系和一个寿命(TTL)值。当主机遇到未知IP地址时,构造并广播一
个ARP分组,与之匹配的主机回复响应ARP分组。

以太网协议的帧格式如下

  • 数据字段:以太网的最大传输单元为1500字节;
  • 目的地址和源地址;
  • 类型字段:得知将数据段的内容传递给哪个网络层协议;
  • CRC:检测帧中是否引入了差错;
  • 前同步码:该前同步码的前7字节的值都是10101010,最后一个字节是10101011。

链路层交换机维护交换机表完成转发和过滤,假定目的地址为xx
帧从交换机接口x到达:

  • 表中没有对于xx的表项,交换机向接口x无外的所有接口前面的输出缓存转发该帧的副本;
  • 表中有一个表项将xx与接口x联系起来,丢弃该帧即可;
  • 表中有一个表项将xx与接口y!=x联系起来,交换机通过将该帧放到接口y的输出缓存完成转发功能。

交换机表是自学习的:

  1. 交换机表初始为空;
  2. 对于在每个接口接收到的每个入帧,该交换机在其表中存储;
  3. 如果在一段时后,交换机没有接收到以该地址作为源地址的帧,就在表中删除这个地址.

链路层交换机的优点包括:消除碰撞、连接异质的链路和便于管理。链路层交换机相对于路由器的劣势包括:无法处理广播帧的循环、设备数量相关的ARP表,以及无法处理广播风暴。

本文转载自: 掘金

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

1…832833834…956

开发者博客

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