Just Do Java

Java 's Blog


  • 首页

  • 分类

  • 作者

  • 归档

  • 关于

面试官:既然启动流程不太了解,那你知道Tomcat的生命周期是什么样子的么?

发表于 2021-01-08 | 分类于 java

上一次的文章中,阿粉在面试官面前说了对启动流程不太理解,然后和他聊了一会,然后他又提出了你既然不是特别了解启动流程的话,那你对Tomcat的生命周期熟悉么?

阅读全文 »

团队里的妹子让阿粉跟她说说怎样写出好的代码

发表于 2021-01-05 | 分类于 编程

昨天团队里的妹子很突然地就让阿粉跟她说说怎么才能写出一手好的代码

阅读全文 »

唉!拼多多女孩猝死,996再上热搜!

发表于 2021-01-04 | 分类于 Java

Hello,大家好,我是阿粉~

『上班 996,下班 ICU!』这是 2019 年上半年的热门事件,那个时候整个互联网圈子都在抵制 996,抵制无良公司。

不过随着 2020 年疫情开始,外部环境变得很差, 越来越多人无奈接受了 996 ,这个话题开始慢慢淡出了视野。

不过,最近两天 996 话题再次引爆互联网,又一上了热搜了,而事情的真实起因说起来真的令人心痛。

1 月 3 号下午,网友在脉脉平台爆料,拼多多员工在凌晨一点半下班回家路上晕倒猝死。

据网上爆料信息,这位拼多多员工是 98 年,年仅 22 岁,19 届毕业,内部花名润肺,从事拼多多新疆买菜项目的招商相关岗位。

2020 年的12 月 29 日凌晨一点半,下班路上突然晕倒,经过抢救无效去世。

事情爆料出来之后,热度越来越高,一晚上的时间该话题达到了近千万的热度,知乎话题被顶到热门,而微博 996 ,拼多多等话题也被顶上了热搜。

随后脉脉上又有内部员工爆料,拼多多 HR 正在开始删帖。

这个套路是不是很熟悉?

撤热搜,降热度,删帖子,互联网大厂对待大众舆论一套组合拳。

如果事情就到此为此,也许就没么魔幻!

2021 年 1 月 4 日早上,疑似拼多多官方知乎账号发表回应,不过 1 分钟之后就被秒删。

不过互联网都是有记忆的,许多知乎网友都看到这个回答,截图传播。

仔细看下这个回答,这什么意思❓❓❓

这是想表达 猝死=以命换钱=努力吗❓❓❓

这真的是经过脑子吗❓❓❓

随后 2020 年 1 月 4 号下午拼多多官方回应:“我们心痛如绞,我们爱你,深深的想念你。”

然后又进行辟谣,称其从未发布过上面的截图。

本来事情也许就到此为止了,官方都发声明辟谣了,但是网上那些截图真的是伪造吗?

如果真的是假的,那么作为这张截图的源头知乎,起码也要负相关的责任。

拼多多这是要把黑锅甩给知乎啊,不过知乎没怂,选择了硬刚。

知乎小管家拿出了后台数据,时间精确到了秒,啪啪打了拼多多的脸,砍了拼多多一刀。

不得不说,知乎🐂🍺。

事情的最后,拼多多最终发布道歉声明,把锅甩给了一个合作供应商的员工。

这个套路有没有感觉让人很熟悉❓❓❓

好像每次看到热点新闻,都是临时工默默抗下所有!

现在开始回顾一下这件事,拼多多这波公关,怎么说呢,真的……负分。

先是谎称官方好回答是谣言,被辟谣之后又说是营销合作方的锅,是他们在乱搞。

最后还让一个员工手写新来澄清。

一个官方的号都不能让人相信的话,这个官方号还有啥用,还有公信力吗?

建议早点换个公关团队吧。

拼多多为什么这么忙?

去年十月,拼多多五周年的时候,黄峥在内部发表称拼多多全员都要「开启硬核奋斗模式」。

什么是硬核奋斗模式?

很多大厂开始大小周的上班方案,相比这个,拼多多更加狠,据网上爆料拼多多推出超级版的大小周方案,大周 7 天,小周 6 天。

这么计算下来,每个月就休息两天。

另外据说拼多多买菜业务全年无休,今年的春节假期很有可能也是不放假!!!

这真的很硬核!!!

可能很多人不了解拼多多买菜业务,简单解释一下,其实就是社区团购业务,最近非常火热。

京东、滴滴、美团、阿里叫的上名的互联网大厂都来参了一手。

拼多多将买菜业务列为重点项目,倾其所有资源投入,All in 买菜。

也许正因为竞争太激烈,所有人都想在这个赛道抢到头筹,争取跑的比竞争对手快,所以开始无止尽的加班,无止尽压榨底层员工。

最后

其实写到这里,阿粉觉得真的有点无奈,身处现在互联网圈子,感觉个体力量真的很渺小。我们只能被资本摆布,只能逼着去 996。

不过即使这样,阿粉还是要发声,让这件事能被更多的读者看到。

哎,真的很痛心!希望这次事件能成为大家反抗 996 的一次契机。

最后提醒大家一下,真的要注意一下自己的身体健康,平时有时间的话还是要适当运动一下,毕竟身体是我们自己的。

阅读全文 »

面试官:来说说Tomcat的启动过程是什么样子的

发表于 2021-01-02 | 分类于 java

阿粉最近在疯狂的研究各种用的工具里面的源码实现,之前给大家都专门的去扣了一下 JDK 里面自带的exe程序,这次阿粉开始更加无聊,直接开始搞Tomcat。

阅读全文 »

面试官:请说下 Redis 是如何保证在宕机后数据不丢失的

发表于 2020-12-27 | 分类于 Java

Hello,大家好,我是阿粉,天气越来越冷了,但是再冷的天也不能阻挡阿粉学习的心,今天大家就跟着阿粉一起来学习一下 Redis 的数据持久化吧。

阅读全文 »

要开始远程办公了吗?

发表于 2020-12-26 | 分类于 程序生活

最近,北京的疫情开始零散出现,顺义区宣布进入战时状态;党政机关人员不经批准不允许离京;对于普通民众,提议非必要不离京,建议市民在京过年。

阅读全文 »

摸透原理|一文带你了解 Redis 列表底层的实现方式

发表于 2020-12-24 | 分类于 Java

Hello,大家好,我是阿粉~

上次我们分享 Redis 字符串的底层原理,今天我们再来看下 Redis List 列表的底层原理。

阅读全文 »

阿粉被面试官吊起来疯狂捶打,结果很尴尬

发表于 2020-12-23 | 分类于 Java

阿粉最近接到了一个面试,但是面试结果不是很尽如人意,因为虽然有些问题回答的还凑活,但是因为面试官问了一些后序的内容,阿粉不会,于是就被吊起来疯狂捶打了半天,败兴而归。

阅读全文 »

SpringBoot 整合 Quartz 实现分布式调度

发表于 2020-12-22 | 分类于 java

在上篇文章中,我们详细的介绍了 Quartz 的架构原理与单体应用,今天我们一起来分享一下 quart 的分布式调度和实践

阅读全文 »

quart 架构与单体应用介绍

发表于 2020-12-21 | 分类于 java

在上篇文章中,我们深入的介绍了单机版本定时任务的实现原理,今天我们继续来点干活,介绍在平时使用最多的一个定时任务框架 Quartz!

阅读全文 »

SpringBoot Schedule应用实践

发表于 2020-12-20 | 分类于 java

3分钟带你搞定SpringBoot中Schedule

阅读全文 »

定时任务实现原理详解

发表于 2020-12-19 | 分类于 java

定时任务,可以说是业务系统必不可少的一个部分,今天我们就一起来了解一下 JDK 定时任务实现及原理分析。

阅读全文 »

因为线程的封闭阿粉错失了一份非常不错的工作

发表于 2020-12-18 | 分类于 JDK

前几天也是蛮尴尬的,阿粉在进行电话面试的时候因为一个线程题,让自己白白的失去了一份不错的工作,是个什么问题那?那就是线程的封闭。

阅读全文 »

Redis 字符串用起来简单,但是原理可是真不简单

发表于 2020-12-15 | 分类于 Java

Hello,大家好,我是阿粉~

无论你现在使用什么编程语言,每天最高频使用的应该就是字符串。可以说字符串对象很基础,也很重要。

那么今天想跟大家聊聊 Redis 字符串相关实现,来看下这个看起来简单的字符串,为什么实现起来确实不简单?

看完这篇文章你可以学到:

  • Redis 字符串对象多种数据结构
  • 底层数据结构转换关系
  • SDS 动态字符串
  • Redis 采用 C 语言实现,那么字符串为什么不直接使用 C 语言的字符串?

Redis 对象

每当我们在 Redis 中保存一对新的键值对时,Redis 至少会创建两个对象,一个对象用作保存键,而另一个对象用作保存值。

Redis 中每个对象都是一个 redisObject 结构,这个结构中有三个属性:

  • type,表示对象的类型
  • encoding,表示编码
  • pstr,指向底层数据结构的指针

type 代表对象类型,目前可以使用类型为:

  • 字符串对象
  • 列表对象
  • 哈希对象
  • 集合对象
  • 有序集合对象

我们可以使用 Redis 的 TYPE 指令,查看当前键对应值对象类型。

Redis 键对象说起来很简单,它总是是一个字符串对象,而值对象就相对复杂了,它可以是字符串,也可以是集合,也可以是字典等对象。

每个对象类型底层实现的时候将会采用了多种数据结构,而 encoding 代表底层数据结构类型:

我们可以使用 Redis 的 OBJECT ENCODING 指令查看一个当前键值对象底层的编码:

每个对象类型,可能支持多种数据结构,关系如下:

Redis 字符串对象

Redis 字符串对象底层支持三种数据结构,分别是(下面使用 OBJECT ENCODING 输出):

  • int
  • embstr
  • raw

int 代表 long 类型的整数,只要字符串对象中保存的是一个整数值,就将会使用 int 。

这里需要注意,只有整数才会使用 int,如果是浮点数, Redis 内部其实先将浮点数转化为字符串值,然后再保存。

embstr 与 raw 类型底层的数据结构其实都是 SDS (简单动态字符串),Redis 内部定义 sdshdr 一种结构。

那这两者的区别其实在于,embstr类型将会调用内存分配函数,分配一块连续的内存空间,空间中依次包含 redisObject 与 sdshdr 两个数据结构。

而 raw 类型将会调用两次内存分配函数,分配两块内存空间,一块用于包含 redisObject结构,而另一块用于包含 sdshdr 结构。

SDS 简单动态字符串

接下来我们来看下简单动态字符串。

sdshdr

sds 底层的结构包含三个属性:

  • len,用于记录下面 buf[] 数组已使用的长度,即等于 SDS 所保存的字符串长度
  • free,用于记录下面buf[] 数组未使用长度
  • buf[],用户保存字符串

这里我们需要注意了,buf[]数组中最后一个字节将会保存一个空字符 \0,代表字符串结束。sdshdr 结构中的 len 长度是没有包含这个这一个空字符。

这一点设计上与 C 语言的字符串相同。

SDS 扩容

当我们对 SDS 字符串进行增长操作,如果 SDS 空间不足,SDS 将会先进行扩容,然后再执行修改操作。

假设当前 SDS 值如下所示:

现在执行一次字符串拼接指令,

1
APPEND sds " Cluster"

由于拼接完成之后字符串总长度为 13,SDS 剩余空间不足,所以 SDS 将进行扩容,重新执行一次内存重分配。

由于我们上面的例子,SDS 修改之后的长度(即 len 属性值)小于 1MB,那么程序将会分配和 len 属性同样大小的未使用空间,此时 SDS 中 len 属性与 free 属性值相同。

此时 SDS buf 数组的实际长度为:

1
13+13+1=27

如果 SDS 修改之后长度大于 1MB,那么 Redis 将会分配 1MB的未使用空间。

假设进行修改之后,SDS len 属性变为 20MB,那么程序将会分配 1 MB 的未使用空间,此时 SDS buf 数组的实际长度为:

1
20MB+1MB+1byte

接着上面的例子,如果我们再次执行字符串拼接指令:

1
APPEND sds " Guide"

这次 SDS 未使用空间足够保存拼接字符串,所以这次不需要重新分配内存。

SDS 惰性空间释放

当我们对字符串进行缩减操作, SDS 字符串值将会被缩短,这样 SDS 剩余空间将会变多。

此时程序不会立即就使用内存分配函数回收多余空间,而是使用 free 属性记录多余空间,等待后面使用。

通过这种惰性空间释放策略,SDS 避免字符串缩短所需内存重分配操作,后续 SDS 字符串增长,可以直接使用多余的空间。

当然 SDS 也有相应的 API,可以真正释放 SDS 未使用的空间,所以不用担心惰性释放策略带来的内存浪费。

为什么 Redis 不直接使用 C 语言字符串?

Redis 底层使用 C 语言编程,那么其实 C 语言也有字符串,Redis 完全可以复用 C 语言字符串~

那么为什么 Redis 还要闭门造车,重新设计一个 SDS 数据结构呢?

这是因为 C 原因这种简单的字符串形式,在安全性,效率以及功能方面不满足 Redis 需求。

首先如果我们需要获取 C 语言字符串的长度,我们可以使用以下函数:

1
strlen(str)

strlen函数实际上使用遍历的方式,对每个字符计数,直到遇到代表字符串结束的空字符。

这个操作时间复杂度 O(N)。

而 SDS 由于 len 记录当前字符串的长度,所以直接读取即可,时间复杂度仅为 O(1)。

这就确保获取字符串长度的操作不会成为 Redis 性能瓶颈。

第二点每次增长或缩短 C 语言的字符串将会进行内存的重分配,否则可能导致缓冲区溢出,也有可能导致内存泄漏。

而 SDS 由于使用空间预分配的策略,如果 SDS 连续增长 N 次,内存重分配的次数从必定 N 次降低为最多 N 次。

第三点,C 语言字符串不能包含空字符,否则第一个空字符将会被误认为字符串结尾,这就大大限制使用的场景。

而 SDS 中由于可以使用 len 属性的值判断字符串是否结束,所以没有这种困扰。

所以 SDS 字符串是二进制安全的。

字符串对象底层数据结构转换

SDS 结构由于需要额外的属性记录长度以及未使用长度,虽然这样减少系统的复杂度,提高了性能,但是还是付出相应的代价,即存在一定内存空间浪费。

所以 Redis 字符串对象底层结构并不都是采用了 SDS。

如果字符串对象保存的是整数值,那么这个整数值将会直接保存在字符串对象结构的 ptr 属性。

如果保存不是整数,但是字符串长度小于等于 39 字节,那么字符串将会使用 embstr编码方式。 最后上述两个都不满足才会使用 raw 编码方式。

另外,int 编码与 embstr,在一定条件也将会转换为 raw 编码格式。

如果对底层整数执行追加操作,使其不再是一个整数,则字符串对象的编码就会从 int 变为 raw。

对于 embstr 编码,实际其实只读的,这是因为 Redis 底层并没有提供任何程序修改 embstr 编码对象。

所以一旦我们对embstr 编码字符串进行修改,字符串对象的编码就会从 embstr 变为 raw。

总结

Redis 字符串是我们平常操作最频繁的数据结构,了解底层字符串对象,对于我们了解 Redis 非常重要的。

Redis 字符串对象底层结构可以分为整数与 SDS,当我们在操作 set 命令保存整数时,就将会直接使用整数。

而 SDS 是 Redis 内部定义另一种结构,相比于 C 语言字符串,它可以快速计算字符串长度,不需要频繁的分配的内存,最终要一点 SDS 是一种二进制安全的字符串。

SDS 设计虽然带来的很多优势,但是这种结构相比于 C 语言,至少多占用 4(len)+4(free)=8 字节内存,如果 buf []数组还存在没有使用的空间,空间浪费更加严重。

所以 Redis 字符串不是万金油,某些场景下可能会占用大量内存。但是如果换一种数据结构,可能内存占用就会变少。

所以我们一定要基于合适场景使用合适的数据机构。

站在巨人的肩膀

  1. Redis 设计与实现
阅读全文 »

JDK里面自带了这么多的exe,你都挨着试过么?

发表于 2020-12-13 | 分类于 JDK

JVM

话说面试这块,JVM算是一个经典的也是三年必问的知识点了,而且这个知识点算是最重要的一个知识点,你如果会这个内容,那么对你的在之后的面试中,能够喊出一个不错的价格。

而关于JVM和还有关于垃圾回收算法的解析,阿粉在这里就不在给大家进行讲解了。大家有兴趣的可以看一下阿粉之前推出的这几篇文章

阅读全文 »

妹子问我为啥启动线程时使用 start 而不是 run

发表于 2020-12-12 | 分类于 java

今天团队里面的妹子问阿粉,为什么在启动线程的时候,都使用 start 方法,而不是 run 方法呢

阅读全文 »

面试官:请你说下对于一张很大的表应该如何做查询优化

发表于 2020-12-12 | 分类于 Java

Hello,大家好,我是阿粉,今天是周六,你是在放假还是跟阿粉一样在加班呢?今天的课题是跟大伙聊聊数据表的优化问题,这个问题小伙伴们在工作应该会遇到而且在面试中还经常会问到。

阅读全文 »

RPC 用着好好的?为什么还需要使用 MQ?

发表于 2020-12-09 | 分类于 Java

阿粉最近看了一场架构师之路沈剑老师的一场直播,最近又重温了一下,根据自己的认知总结了一下,分享给大家。

MQ 想必大家或多或少都用过,接入 MQ 之后的整体架构如下:

可以看到使用 MQ 之后,上下游通信就变成图上的这种方式。

这种跨进程的通信方式,我们还有一种常用的解决方案,使用 Dubbo 等这类 RPC 服务。

理论上使用 RPC 的跨进程通信的场景,使用 MQ 也能解决,当然反过来也能说通。

那为什么不都用 RPC,或者 MQ 来解决那?

这其实都是业务场景决定的,抛开业务场景来谈架构都是耍流氓!没有全能的架构,只要适合的架构。

下面我们来看看那些场景适合 RPC,而那些场景适合 MQ。

RPC 场景

使用 RPC 的场景一般都是上游服务需要实时依赖下游服务的返回。

我们以一个登录服务为例,架构图如下:

用户发起的登录请求首先由对外的 WEB 服务接受,然后 WEB 服务服务调用用户服务查询用户信息,然后比对用户密码。

也就是说我们的 WEB 应用需要实时依赖用户服务返回的用户信息,如果没有返回,这次登录将会失败。

假如这个场景我们用 MQ 代替, WEB 应用发送 MQ 消息之后,然后流程就结束了,此时 WEB 应用无法拿到用户信息。

所以说对于这种需要强依赖下游返回的场景,使用 MQ 将会带来以下不足:

  • 上游无法直接得到下游结果
  • 增加一个 MQ 组件,系统更复杂

MQ 场景

上游不关心下游结果的场景

举个例子,在我们第三方支付系统中,每支付成功一笔,都需要计算手续费。

这个场景我们显然可以使用 RPC 完成调用,但是实际上,支付系统是不关心的计费系统的结果,两个系统不存在直接强依赖的关系。

大家可以想象一下,用户实际上已经收到银行卡扣款短信了,但是支付系统因为计费系统失败,导致对外返回是失败的结果。这对于用户来讲,不能接受啊。我都付钱了,你却告诉我支付异常。

所以对于这种场景,直接使用 RPC 调用由以下几点不足:

  • 系统整体调用延时增加
  • 下游服务异常,影响上游服务。两者物理以及逻辑依赖严重
  • 若后面再增加一个下游系统,需要知道支付成功的结果,上游系统需要改动代码。这种情况对于上游情况来讲,就会很烦。明明与上游系统没有什么关系,却需要修改代码。

那一定要用 MQ 解决吗?

其实不一定,对于我们上面举的场景,我们其实可以使用异步 RPC 或者线程池异步调用 RPC 就可以解决。

毕竟增加一个 MQ, 系统就变得更加复杂,我们还要单独运维 MQ,这对于小团队来讲,工作量还是很大的。

但是这种方式,还是解决不了,增加一个下游系统,上游系统还要改动的代码囧境。

增加 MQ 解耦

这个场景使用 MQ 解耦,带来几点优点:

  • 任务一:上游系统执行时间变短
  • 任务二:上下游逻辑解耦,物理解耦
  • 任务三:最重要一点,增加一个下游服务,其只要订阅即可,上游服务无需要改动代码

数据驱动的定时任务场景

举个例子,支付公司每日都需要对账,主要目的是核实自己系统的应收的钱与支付渠道端是否一致,主要流程分为以下几步:

  • 定时任务下载渠道对账文件,下载方式可能为 Http 接口下载,也有可能 SFTP 下载
  • 定时任务解析对账文件,然后将对账数据入库
  • 定时任务将自己本端支付数据与对账数据核对

上面的定时任务使用 Spring-Schedule 调度,假设各个定时任务下载时间如下所示:

上图中三个任务,任务二需要依赖任务一完成,而任务三有需要依赖任务二完成。

我们之前使用这种模式,通常会碰到几个问题:

  • 通常 06:00 就能下载到对账文件,但是有时候渠道端对账文件延迟,就会导致任务一执行失败,这样就会后续两个定时任务也会执行失败
  • 假设任务二数据过多,执行时间过长,任务三执行时还没结束,这就导致任务三无法拿到全量数据,导致对账异常
  • 整体任务执行时间过长
  • 任务一若调整时间,可能导致任务二,任务三都需要调整时间

使用 MQ解耦

使用 MQ 解耦之后架构图如下:

这种方式,只要任务一的定时任务准时启动,任务一完成之后发送 MQ 消息,任务二收到之后就会启动任务,结束之后再发送消息给 MQ。任务三流程同任务二

使用这种方式存在优点为:

  • 下游任务只要收到消息就能立刻执行,不需要额外等待,整体任务执行时间变短
  • 上游任务时间变动,无需修改下游任务时间。我们这个例子,只需要任务一的实际即可

总结

对于上游需要关注下游返回结果的场景,不适合使用 MQ。

适合使用 MQ 的场景有:

  • 上游不关心下游结果的场景
  • 数据驱动的定时任务依赖

阅读全文 »

网传虾米音乐将在明年一月关闭,网友直呼不要

发表于 2020-12-07 | 分类于 博客

Hello,大家好,我是阿粉,不知道大家最近有没有看到这个新闻,说虾米音乐将在明年一月份关闭,在被腾讯系音乐软件包围的大环境下,现在真的很少有引用软件能够发展起来,阿里旗下的也不例外。

现在市面上常用的音乐软件有网易云引用,QQ 音乐,酷我音乐,酷狗音乐,虾米音乐,咪咕音乐,其中虾米音乐所占的份额并不是特别多。

在网上传出这个说法的时候,网友直呼满满的都是回忆,不舍得虾米音乐被关闭。

其实阿粉不属于虾米音乐的粉丝,平日里用的比较多的还是 QQ 音乐和网易云音乐,因为感觉 QQ 音乐里面的都是青春,想当年阿粉还是 QQ 音乐三巨头的忠实歌迷,现在用的比较多的是网易云音乐,喜欢网易云音乐的每日推荐和私人 FM,总是能推荐一些不错的音乐。相反的 QQ 音乐用的少了一点,毕竟过了那个年纪~~扎心了。

虾米音乐已经发展了十多年了,在音乐届也绝对属于元老级的音乐平台。国内最早的一批独立音乐人,都是靠虾米整个平台,才让更多人听到了自己的作品,从初创开始,虾米就致力于扶持原创音乐和音乐人。

2013 年 1 月的时候,虾米被阿里收购,2015 年 3 月与天天动听合并为阿里音乐,说到这里其实阿粉当年还是挺喜欢天天动听这款音乐软件的,可惜后面渐渐落寞了。虾米的落寞很大的原因是当年的版权问题,导致很多原创歌曲被下架,渐渐的就流失了很多用户,从而导致这样的局面。

关于虾米音乐是否会被关闭的事件,目前官方还没有给出明确的答复,我们不做判断,但是从这件事我们还是可以看出,像阿里这样的大厂也不能说做什么一定成功的,更何况很多创业型小公司了,说实话要是虾米音乐在某个小公司手里,早就被关闭了,毕竟每年的版权投入是非常巨大的。

阅读全文 »

并发编程把我整的是服服气气的了

发表于 2020-12-07 | 分类于 Java

阿粉因为原来的编程习惯,已经很久没有去考虑并发的问题了,结果之前在面试的问题就回答的不是很完善,而阿粉也用心学习了并发编程这一块的所有内容,一起来分享给大家。

阅读全文 »
1 … 6 7 8 … 32
Java Geek Tech

Java Geek Tech

一群热爱 Java 的技术人

633 日志
116 分类
24 作者
RSS
GitHub 知乎
Links
  • 纯洁的微笑
  • 沉默王二
  • 子悠
  • 江南一点雨
  • 炸鸡可乐
  • 郑璐璐
  • 程序通事
  • 懿
© 2019 - 2022 Java Geek Tech
由 Jekyll 强力驱动
主题 - NexT.Mist