Just Do Java

Java 's Blog


  • 首页

  • 分类

  • 作者

  • 归档

  • 关于

收集了这么多实用技巧,帮助你的 iterm2 成为最帅的那个!

发表于 2020-01-11 | 分类于 tool

1 前言

一个适合后端仔排查问题的 iterm2 终端应该是这样的:

交代下为啥要开这么多个窗口,目前阿粉我们的应用是单机部署,一个服务部署在很多台 Linux 服务器上,构建分布式架构。(实际上服务器数量比这个更多:-O)

阅读全文 »

JDK8不是只有Lambda香,还有你更想不到的呢!

发表于 2020-01-09 | 分类于 HTTP系列

继续上次的话题,阿粉昨天带着大家伙看了 Lambda表达式,是不是感觉真香,哈哈哈,今天这个绝对是更香的存在,因为之前因为阿粉用JDK7写的代码,还没老宫吐槽了很久,他是什么呢?阿粉来带大家看一下。

阅读全文 »

你还在使用JDK7,今天阿粉带你来了解一下JDK8,不得不说,真香!

发表于 2020-01-07 | 分类于 HTTP系列

前几天阿粉还在和同事抱怨,说现在 JDK 都已经11,12了,结果自己还在用 JDK 7,于是就发生了下面一幕。

阅读全文 »

鸭血粉丝挑出来了你最爱看的文章,速来围观

发表于 2020-01-05 | 分类于 Java

大家好,我是鸭血粉丝,自从去了一趟南京以后,我已经深深爱上了鸭血粉丝无法自拔,真是蜜汁好吃。

来来来,带你看一下鸭血粉丝是模样(我特意为你找了一张图片,怎么样,我好不?)

这幅图深刻的为你揭示了鸭血粉丝的配方,不行了,馋死我了,我要赶紧把文章整理出来再去吃一碗!!!

公众号后台总有小伙伴留言说能不能把文章汇总一下,能不能把文章分类一下,现在这位小伙伴,不知道你看没看到。我要憋大招了

【Redis 系列】

Redis 中的 “SOS”,不对,是 SDS

一文带你了解 Redis 的发布与订阅的底层原理

一文带你了解 Redis 的慢日志相关底层原理

这篇文章我们来聊聊什么是程序的局部性原理

一文看懂 Redis 的内存回收策略和 Key 过期策略

来看看我们是怎么玩儿 Redis 的

【Flink 系列】

阿粉带你学习Flink中的Watermark

Flink 基础学习(八) 手把手教你搭建伪集群 HA

Flink 基础学习之窗口 Window

Flink 基础学习(六)时间 Time 和 Watermark

Flink 基础学习(五)数据存储

Flink 基础学习(四)转换 Transformation

Flink 数据源 DataSource是这个样子的?

Flink:你绕不过去的 Hello World

带你走入 Flink 的世界

【Kafka 系列】

你能说出 Kafka 这些原理吗

如何正确理解kafka重平衡流程?

副本机制和请求过程

Kafka 进阶必备之控制器

真的,关于 Kafka 入门看这一篇就够了

带你涨姿势的认识一下Kafka之消费者

带你涨姿势是认识一下Kafka Producer

带你涨姿势的认识一下kafka

【Http 系列】

面试官问:HTTP 的负载均衡你了解么?你不是说了你们用的Nginx么?说一下把。

一文带你深入理解 HTTP 的秘密。。。

别再问Cookie了,再问就崩溃了!

关于 HITP 代理,你还需要了解这些,不然面试你是过不去的!

你知道 HTTP 是如何使用 TCP 连接的吗?今天我就来告诉你!

再谈HTTP,你还要继续更新不?

重温HTTP,你到底做了什么?

【数据结构系列】

如果有人再问你 Java IO,把这篇文章砸他头上

面试必问之 ConcurrentHashMap 线程安全的具体实现方式

HashMap 在多线程环境下操作可能会导致程序死循环

你应该知道的 PriorityQueue ——深入浅出分析 PriorityQueue

集合系列- 深入浅出分析 ArrayDeque

深入浅出的分析 Set集合

深入浅出的分析 Properties

带你深入浅出的分析 HashTable 源码

深入浅出分析 IdentityHashMap

深入浅出分析 LinkedHashMap

深入浅出的分析 TreeMap

深入浅出分析 Collection 中的 List 接口

一文看懂 HashMap 中的红黑树实现原理

掌握 HashMap 看这一篇文章就够了

最全的集合干货送给大家

【灵魂拷问系列】

灵魂拷问:Java的Comparable和Comparator是兄弟俩吗?

灵魂拷问:Java的可变参数究竟是怎么一回事?

羞,Spring Bean 初始化/销毁竟然有这么多姿势

灵魂拷问:Java 的 substring() 是如何工作的?

灵魂拷问:为什么 Java 字符串是不可变的?

给你一秒钟回答我这个问题

Java,你告诉我 fail-fast 是什么鬼?

【Java极客技术重磅推荐】

2020 年已经到来,是时候总结一下过去的 2019 年了

愿你编码半生,归来仍是少年

就是因为这个,程序员成为高危职业

求你们别再学了,跟不上了!

今年的诺贝尔经济学家告诉你,怎么“脱贫”

【其他】

记一次线上 OOM 和性能优化

Gateway:数据报文走出局域网的必经之路

面试官:负载均衡的算法你了解不?

当我们谈容器的时候,我们在谈什么

线程安全之synchronized关键字

浅谈Comparable和Comparator

直击面试,聊聊 GC 机制

原生线程池这么强大,Tomcat 为何还需扩展线程池?

Java 又双叒叕发布新版本,这么多版本如何灵活管理?

程序员,别再迷恋多线程工作了

手把手教你实现热更新功能,带你了解 Arthas 热更新背后的原理

CentOS7 下搭建 Harbor 仓库以及登录

带你聊聊 Java 并发编程之线程基础

从 Java 的平台无关性引入的一系列面试题

你应该知道的 12 道经典计算机网络面试题

Dubbo 优雅停机演进之路

令人匪夷所思的 Magic 之魔数,你真的不会

这个过滤器你确定会么?

不吹不黑,这个算法,你肯定不会

还在重复写空指针检查代码?考虑使用 Optional 吧!

我是怎么倒在美团第三轮面试之下的

你知道什么是分布式事务吗

程序员的路是一行一行走出来的

阅读全文 »

2020 年的第一天,程序员鸭血粉丝又碰上生产事故

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

hello~各位读者新年好,我是鸭血粉丝(大家可以称呼我为「阿粉」),一位喜欢吃鸭血粉丝的程序员!2019 年,阿粉写了很多 bug,这不前一段时间 OOM 差点就把服务器搞挂。跨年的时刻,阿粉默默立下一个 flag,2020 年再见 bug。

可是没想到还没过几个小时,刚立的 falg 就倒下了,阿粉太难了😭。。。。

事件回顾

新年第一天,运营反馈商户收到对账文件有问题。刚接到电话时,阿粉是一万个不相信,这个代码都跑了这么久,怎么就偏偏今天不对了。可能看到阿粉不信,运营小姐姐随即发了一张商户给的账单截图。

waht???2020 年才刚来,时间怎么变成 20201231 了。。。

哎,阿粉只好先让运营稳住商户,然后赶紧起来,打开了电脑,首先定位一下问题。

问题排查

生成账单伪代码如下:

上面代码逻辑其实非常简单,将账单数据从数据库取出,然后按照按照相应的格式组装数组,写入文件。

阿粉走查了几遍代码,越看越觉得没问题啊,这么简单的问题没可能出现问题呀。没办法,只好请教一下阿粉的好朋友兼同事小黑。

很快小黑就给阿粉指出 YYYY/MM/dd HH:mm 格式不对,需要使用 yyyy/MM/dd HH:mm。阿粉修改之后,重新生成对账单,解决这个问题。

原因分析

虽然解决了问题,但是阿粉其实还是一知半解,所幸元旦也没什么事,阿粉就深入研究一下 YYYY 格式。原来 Java 中YYYY 与 yyyy 分别代表两种不同格式。

Y 代表 Week Year,表示当天周所在的年份。这种方式将会把一年划分成52 周/53周(类似于闰年的概念,每隔几年将会增加一周)。Week Year下每周仅属于某一年,如果某年的第一周或最后一周跨年,就会导致部分日期年份与实际不符。

Week Year 存在两种标准:

  • ISO 8601:国际标准,每周从周一开始,每年的第一周至少包含 4 天
  • Common:通用标准,每周从周日开始,每年的第一周至少包含 1 天

Calendar 对象可以通过 setFirstDayOfWeek 与 setMinimalDaysInFirstWeek 改变上面默认标准

Java 将会根据系统环境变量决定使用哪种标准,可以通过设置 Locale 改变方式。代码如下:

1
2
3
4
5
6
// 选择 20191229 这一天
Date date20191229 = DateUtils.parseDate("20191229", "yyyyMMdd");
// 将会输出 2020,使用 Common 。当前系统,Locale 默认值为 Locale.CHINA
System.out.println(DateFormatUtils.format(date20191229,"YYYY"));
// 将会输出 2019,使用 ISO 8601
System.out.println(DateFormatUtils.format(date20191229,"YYYY", Locale.FRANCE));

下面例子我们使用 ISO 8601 标准,分别看一下最后一周跨年以及第一周跨年的例子。

2015 年最后一周跨年,2016 前三天使用 YYYY 最后结果为 2015,时间看起来被回退了。

2020 年第一周跨年,2019 年最后两天使用 YYYY 结果为 2020。

其他分析

终于弄明白 YYYY ,阿粉顺便也学习一下常用的日期格式。

下面以 2019-12-31 06:06:06:666 时间为例

Letter 含义 Example
Y Week Year YYYY—->2020
y 年 yyyy——>2019
M 月 MM——->12
m 分 mm——–>06
D 一年中天数 MM——–>365
d 一月中的天数 dd———>06
H 小时(0-23) HH———>06
h 小时(1-12) hh———->06
S 毫秒 SSS———>666

技术总结

下面开始本篇文章的技术总结:

  • 第一,切记 YYYY 与 yyyy区别,年份最好统一使用 yyyy
  • 第二,怕忘记的小伙伴可以安装一下阿里的 Alibaba Java Coding Guidelines 插件,这个插件可以检测出使用 YYYY 的代码
  • 第三,阅读完整文档,消除 Bug 最好的的办法就是阅读完整的文档,奥利给
  • 第四,测试环节增加边界测试,早发现,早消灭

随便聊聊

写这篇的文章时候,发现社区有些小伙伴也踩到这个坑,哈哈,吾道不孤也。

2021,2022….阿粉相信还会有新的小伙伴将会踩到这个坑,哈哈。在这里,阿粉给未来小伙伴留个言:

未来的的小伙伴你好,当你搜索到这篇文章并且看到这里,我知道你也踩到坑了,哈哈!既然都看到这里了,别忘记点个赞哦!

img

帮助

  1. week_year

  2. Serious Security: The decade-ending “Y2K bug” that wasn’t

  3. ISO week date_wiki

  4. Difference between year-of-era and week-based-year?

阅读全文 »

记一次线上 OOM 和性能优化

发表于 2019-12-31 | 分类于 Linux

大家好,我是鸭血粉丝(大家会亲切的喊我 「阿粉」),是一位喜欢吃鸭血粉丝的程序员,回想起之前线上出现 OOM 的场景,毕竟当时是第一次遇到这么 紧脏 的大事,要好好记录下来。

1 事情回顾

2 开始分析

  • 2.1 日志分析
  • 2.2 使用 MAT 分析 dump 文件
  • 2.3 根本原因

3 性能优化

  • 3.1 业务、代码逻辑梳理
  • 3.2 通过 VisualVM 进行对比

4 技术总结

  • 4.1 开发注意点
  • 4.2 应对故障注意点

5 多说两句

阅读全文 »

2019 年是时候总结一下 Java 极客技术了

发表于 2019-12-27 | 分类于 Redis

大家好,我是鸭血粉丝,今天是 2020 年的第二天,我翻来覆去,思来想去觉得是时候应该把 Java 极客技术的 2019 年总结一下了,话不多说往下看。

01、前言

2019 年已经过去了,现在陆陆续续的都要忙着写年终总结,《Java 极客技术》也陪伴了大家十个月的时间了(虽然我鸭血粉丝才出道几天),还记得第一篇文章《Java 诞生的趣事》 发布在 2019 年 02 月 25 日,这段时间也发过很多次文章给大家介绍《Java 极客技术》公号以及对应知识星球的故事,比如今天,我为他们站台!,总结丨两个月干火一个知识星球,看看我们都做了什么 。现在到了年底也该写年终总结了,这篇文章就给大家总结一下,这几个月《Java 极客技术》的故事,且听我阿鸭慢慢道来。

阅读全文 »

想出网关?你需要懂得这些

发表于 2019-12-23 | 分类于 计算机网络

最近没事在看极客时间上刘超老师的《趣谈网络协议》那门课程,其中有一篇讲得非常有意思,也有些难以理解,我以我的角度来谈谈。

阅读全文 »

Flink 基础学习(九) 再谈 Watermark

发表于 2019-12-23 | 分类于 Flink

1 前言

在时间 Time 那一篇中,介绍了三种时间概念 Event、Ingestin 和 Process, 其中还简单介绍了乱序 Event Time 事件和它的解决方案 Watermark 水位线

(看过多篇文章后,决定喊它水位线,因为窗口触发条件是 Watermark > Window_end_time,有点像水流到达水位线后溢出,当然喊它水印也是可以的,全看个人爱好咯~)

前文请翻 时间 Time 和 Watermark,不过前面介绍比较浅,没能很好领会水位线的概念,所以本篇是作为补充,来加深理解~

阅读全文 »

一文带你了解 Redis 的发布与订阅的底层原理

发表于 2019-12-22 | 分类于 Redis

01、前言

发布订阅系统在我们日常的工作中经常会使用到,这种场景大部分情况我们都是使用消息队列的,常用的消息队列有 Kafka,RocketMQ,RabbitMQ,每一种消息队列都有其特性,关于 Kafka 的使用和源码分析,公号前面有相关的文章,大家可以前往回顾一下,另外两款消息队列大家有需要可以自行研究,后续我们会出相应的介绍文章。这篇文章主要是给大家介绍 Redis 的发布订阅系统,很多时候我们可能不需要独立部署相应的消息队列,只是简单的使用,而且数据量也不会太大,这种情况下,我们就可以使用 Redis 的 Pub/Sub 模型。

阅读全文 »

深度探测 java IO

发表于 2019-12-18 | 分类于 IO系列

想了解 Java 的 IO 基础知识,看这篇文章就够了!

阅读全文 »

面试必问之 ConcurrentHashMap 线程安全的具体实现方式

发表于 2019-12-18 | 分类于 集合系列

ConcurrentHashMap 是 Java 并发包中提供的一个线程安全且高效的 HashMap 实现,以弥补 HashMap 不适合在并发环境中操作使用的不足,本文就来分析下 ConcurrentHashMap 的实现原理,并对其实现原理进行分析!

阅读全文 »

你是否听说过 HashMap 在多线程环境下操作可能会导致程序死循环

发表于 2019-12-18 | 分类于 集合系列

关于 HashMap,以前只知道它是非线程安全,在多线程环境下操作可能会导致程序死循环,CPU直接飙到100%的线上故障,还真是第一次听说……

阅读全文 »

如何正确理解kafka重平衡流程?

发表于 2019-12-16 | 分类于 kafka

Kafka 重平衡流程一直是 kafka 比较麻烦和难以理解的地方,此篇文章通过大量的示意图带你了解一下 kafka 重平衡的过程

阅读全文 »

线程安全之synchronized关键字

发表于 2019-12-16 | 分类于 并发编程系列

之前我讲了关于 线程基础方面的相关知识,本篇文章将会带着大家来学习下线程安全相关的知识。

阅读全文 »

浅谈Comparable和Comparator

发表于 2019-12-16 | 分类于 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
public class Strategy {
    public static void main(String[] args) {
        int[] arr = {5, 3, 1, 7, 2};
        new DataSorter().sort(arr);//调用工具类进行排序
        for(int i = 0; i < arr.length; i++){
            System.out.println(arr[i]);
        }
    }
}

class DataSorter{//用于排序的工具类
    public void sort(int[] arr){//调用sort方法进行排序, 此处使用冒泡排序
        for(int i = arr.length - 1; i > 0; i--){
            for(int j = 0; j < i; j++){
                if(arr[j] > arr[j + 1])
                    swap(arr, j, j  + 1);
            }
        }
    }

    private void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

Comparable接口

通过上面的代码, 我们能够轻易地对整形数组进行排序, 那么如果现在有了新需求, 需要对浮点类型数据进行排序, 排序工具类应该如何做呢?

或许你会想, 不如就新添加一个排序方法, 方法的参数类型为 float 类型, 把 int 类型数组的排序算法复制一遍不就可以了吗?

那如果我继续追问, 如果现在要对一只猫进行排序, 那应该怎么做呢? 猫的类如下:

1
2
3
4
5
6
class Cat{
    private int age;//猫的年龄
    private int weight;//猫的体重

    //get / set 方法...
}

你也许会顺着原来的思路回答, 照样 copy 一份排序的算法, 修改方法参数, 然后在比较的地方指定比较猫的年龄或体重不就可以了吗?

1
2
3
4
5
6
7
8
public void sort(Cat[] arr){//以猫数组作为参数
    for(int i = arr.length - 1; i > 0; i--){
        for(int j = 0; j < i; j++){
            if(arr[j].getAge() > arr[j + 1].getAge())//根据猫的年龄作比较
                swap(arr, j, j  + 1);
        }
    }
}

但仔细想想, 如果还要继续比较小狗, 小鸡, 小鸭等各种对象, 那么这个排序工具类的代码量岂不是变得很大? 为了能让排序算法的可重用性高一点, 我们希望排序工具中的 sort() 方法可以对任何调用它的对象进行排序.

你可能会想: 到对任何对象都能排序, 把 sort() 方法的参数改为 Object 类型不久可以了嘛. 这个方向是对的, 但是问题是, 当拿到两个 Object 类型对象, 应该根据什么规则进行比较呢?

这个时候我们自然而然地就希望调用工具类进行排序的对象本身就具备自己的比较法则, 这样在排序的时候就能直接调用对象的排序法则进行排序了.

我们把比较法则抽象为 Comparable 接口, 凡是要进行比较的类都要实现 Comparable 接口, 并且定义自己的比较法则, 也就是 CompareTo() 方法.

这样当我们在封装工具时, 就可以直接对实现了 Comparable 接口的对象进行比较, 不用担心比较的细节了.

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
public class Strategy {
public class Strategy {
    public static void main(String[] args) {
//      Integer[] arr = {5, 3, 1, 7, 2};//注意这里把int改为Integer, Integer是Object的子类
        Cat[] arr = {new Cat(3, 3), new Cat(1, 1), new Cat(5, 5)};
        DataSorter ds = new DataSorter();
        ds.sort(arr);
        for(int i = 0; i < arr.length; i++){
            System.out.println(arr[i]);
        }
    }
}
}

class DataSorter{//用于排序的工具类
    public void sort(Object[] arr){//参数类型为Object
        for(int i = arr.length - 1; i > 0; i--){
            for(int j = 0; j < i; j++){
                Comparable c1 = (Comparable) arr[j];//先转为Comparable类型
                Comparable c2 = (Comparable) arr[j + 1];
                if(c1.CompareTo(c2) == 1)//调用CompareTo()进行比较, 不关心具体的实现
                    swap(arr, j, j  + 1);
            }
        }
    }

    private void swap(Object[] arr, int i, int j){
        Object temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

class Cat implements Comparable{
    private int age;
    private int weight;

    @Override
    public int CompareTo(Object o) {
        if(o instanceof Cat){//先判断传入的是否是Cat类对象, 不是则抛异常
            Cat c = (Cat) o;
            if(this.age > c.age) return 1;
            else if (this.age < c.age) return -1;
            else return 0;
        }
        throw null == o ? new NullPointerException() : new ClassCastException();
    }

    // get / set ...
    //toString() ...
}

interface Comparable{
    public int CompareTo(Object o);
}

Comparator接口

相信看了上面的 Comparable 接口来由, 大家会感觉整个设计又美好了一些, 但是其中还有漏洞. 我们在 Cat 类的 CompareTo() 方法中, 对猫的比较策略是写死的, 现在我们按猫的年龄比较大小, 如果哪天我们想按照猫的体重比较大小, 又要去修改源码了. 有没有扩展性更好的设计?

我们可以让用户自己定义一个比较器类, 对象可以根据用户指定的比较器比较大小.

整个逻辑是: 如果这个对象需要进行比较, 那么它必须实现 Comparable 接口, 但是它具体是怎么比较的, 则通过具体的 Comparator 比较器进行比较.

当然这里少不了多态, 我们首先要定义一个比较器接口 Comparator, 用户的比较器需要实现 Comparator 接口, 下面上代码:

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
public class Strategy {
    public static void main(String[] args) {
        Cat[] arr = {new Cat(3, 3), new Cat(1, 1), new Cat(5, 5)};
        DataSorter ds = new DataSorter();
        ds.sort(arr);
        for(int i = 0; i < arr.length; i++){
            System.out.println(arr[i]);
        }
    }
}

class DataSorter{//用于排序的工具类
    public void sort(Object[] arr){//参数类型为Object
        for(int i = arr.length - 1; i > 0; i--){
            for(int j = 0; j < i; j++){
                Comparable c1 = (Comparable) arr[j];//先转为Comparable类型
                Comparable c2 = (Comparable) arr[j + 1];
                if(c1.CompareTo(c2) == 1)//背后已经转换为使用Comparator的定义的规则进行比较
                    swap(arr, j, j  + 1);
            }
        }
    }

    private void swap(Object[] arr, int i, int j){
        Object temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

class Cat implements Comparable{
    private int age;
    private int weight;
    private Comparator comparator = new CatAgeComparator();//默认持有年龄比较器

    @Override
    public int CompareTo(Object o) {
        return comparator.Compare(this, o);//调用比较器比较而不是直接在此写比较法则
    }

    // get / set / toString ...
}

interface Comparable{
    public int CompareTo(Object o);
}

interface Comparator{
    public int Compare(Object o1, Object o2);
}

//用户自己定义的, 按照猫的年龄比较大小的比较器
class CatAgeComparator implements Comparator{
    @Override
    public int Compare(Object o1, Object o2) {
        Cat c1 = (Cat) o1;
        Cat c2 = (Cat) o2;
        if(c1.getAge() > c2.getAge()) return 1;
        else if(c1.getAge() < c2.getAge()) return -1;
        else return 0;
    }
}

//按照猫的体重比较大小的比较器
class CatWeightComparator implements Comparator{
    @Override
    public int Compare(Object o1, Object o2) {
        Cat c1 = (Cat) o1;
        Cat c2 = (Cat) o2;
        if(c1.getWeight() > c2.getWeight()) return 1;
        else if(c1.getWeight() < c2.getWeight()) return -1;
        else return 0;
    }
}

关于策略模式的思考

在上面的例子中, 我们自己定义了 Comparable 接口和 Comparator 接口, 其实这两个接口都是 Java 自带的, 通过上面的代码示例, 想必大家也应该知道了为什么会有这两个接口。

其实 Comparable 定义的就是一种比较的策略, 这里的策略你可以理解为一个功能, 然而策略有了, 我们还需要有具体的策略实现, 于是便有了 Comparator 接口。

在比较对象的 Comparable 和 Comparator 中运用策略模式我认为有以下的好处:

  • 不同的类在比较时的行为不同,策略模式可以让它们在运行时动态选择具体的比较逻辑。
  • 方便对一个对象在不同的时候使用不同的比较策略,方便未来添加不同的比较策略。

但也有一个主要的缺点:

  • 因为每个具体的比较策略类都会产生一个新类,随着时间增长,系统需要维护的类数量可能会增加。
阅读全文 »

直击面试,聊聊 GC 机制

发表于 2019-12-14 | 分类于 Java

前言

GC 中文直译垃圾回收,是一种回收内存空间避免内存泄漏的机制。当 JVM 内存紧张,通过执行 GC 有效回收内存,转而分配给新对象从而实现内存的再利用。 JVM GC 机制虽然无需开发主动参与,减轻不少工作量,但是某些情况下,自动 GC 将会导致系统性能下降,响应变慢,所以这就需要我们提前了解掌握 GC 机制。当面对这种情况时,才能从容不迫的解决问题。另外 GC 机制也是 Java 面试高频考题,了解掌握 GC 是一项必备技能。

阅读全文 »

for循环用了那么多次,但你真的了解它么?

发表于 2019-12-14 | 分类于 java基础

其实我们写代码的时候一直都在使用for循环,但是偶尔还是会纠结用哪一个循环。

阅读全文 »

面试官:负载均衡的算法你了解不?

发表于 2019-12-14 | 分类于 HTTP系列

上一篇文章我讲了关于负载均衡的三种算法,轮询法,随机法,最小连接法,这三种负载均衡的算法,但是关于负载均衡还有其他的算法,我们也需要你去看,而且在面试的过程中,很有可能是会问到的呦。

阅读全文 »

一文带你了解 Redis 的慢日志相关底层原理

发表于 2019-12-12 | 分类于 Redis

01、前言

相信很多小伙伴在使用 Redis 的时候都知道 Redis 有相关慢日志的查询功能,并且多多少少都看过。那 Redis 底层到底是如果创建慢日志以及慢日志的结构是什么样子的呢?这篇文章就带大家认识一下。我们先看一张慢日志的截图

1使用slowlog get 2命令查看最近的两条慢日志信息,如上图,我们可以看到每条日志中包含的信息有六个部分组成,从上到下编号为 0-5,依次代表的意思是

  • 0:日志的唯一编号 ID
  • 1:命令执行的当前时间戳
  • 2:命令执行的耗时时长,单位微妙
  • 3:具体的执行命令和参数
  • 4:客户端的 ip 和端口(4.0 版本以上才支持)
  • 5:客户端名称(4.0 版本以上支持)

如上图所示,第一条慢日志的 ID 是 41,命令执行的时间戳是 1575729996,并且执行了 16129 微妙,具体执行的命令就是slowlog get,ip 和端口是27.38.56.88:8223,客户端的名称没有设置。

02、慢日志命令设置

查看命令

上面我们已经大概的知道的一条慢日志的格式,自然的我们可以想到的问题是一个命令执行多长时间,我们就可以认为是慢查询,以及慢日志最多能保存多少条。

我们可以通过config get slowlog-log-slower-than 命令来查看 Redis 的时长设置,以及通过config get slowlog-max-len 来查看最大慢日志条数。如下图。

2

设置命令

上面我们使用config get 命令查看了时长设置和条数设置,相反的我们可以用config set来设置相关参数,如下图,我们先查看一下配置,然后再通过config set slowlog-log-slower-than 1000 命令和 config set slowlog-max-len 64 命令来设置具体的值:

3

通过上面的操作我们可以看到相关的配置已经更改生效了。

  1. slowlog-log-slower-than:这个参数的意思是任何命令执行超过这个时间就会被记录为慢日志,单位是微秒。
  2. slowlog-max-len:这个参数表示记录的慢日志的最大条数,设置了这个值过后,新的日志加进来,ID 最小的日志就会被删除。

为了验证上面的第二点,我这边将slowlog-log-slower-than设置为 10 微秒,slowlog-max-len 设置为 5 条来进行试验,首先第一次使用slowlog get命令查询的时候 5 条慢日志的编号是从 83-87,

4

再次使用slowlog get命令查询的编号结果是84-88,说明 ID 为 83 的那一条已经被删除了。

5

03、慢日志的存储原理

存储结构

1
2
3
4
5
6
struct redisServer {
	long long slowlog_entry_id;//下一条慢查询日志的 ID
	list *slowlog;//保存了所有慢查询日志的链表
	long long slowlog_log_slower_than;//服务器配置 slowlog-log-slower-than 选项的值
	unsigned long slowlog_max_len;//服务器配置的 slowlog-max-len 的值
}
  • 在 Redis 的服务器状态中保存了慢日志的相关属性slowlog_entry_id 属性的初始值是 0 每创建一条慢日志的时候就会增加 1。
  • slowlog 链表里面存储了所有的慢日志,链表是由slowlogEntry结构组成的,每个slowlogEntry代表一条慢日志。
  • slowlog_log_slower_than 和 slowlog_max_len 是前面服务器配置的相关参数。

slowlog 链表

1
2
3
4
5
6
7
typedef struct slowlogEntry {
	long long id;//唯一标识符
	time_t time; //命令执行的时间,格式为 unix 时间戳
	long long duration;//命令消耗的时间,以微妙为单位
	robj **argv;//命令与命令参数
	int argc; //命令与命令参数个数
} slowlogEntry;

image-20191211220858341

slowlogEntry 实体的相关字段含义如下:

  • id: 标识慢日志的唯一 ID
  • time: 命令执行的时间戳
  • duration: 命令执行是耗时,单位为微妙
  • agrv: 命令和参数数组
  • argc: 命令和参数的个数

如上图就表示了在执行命令set number 520 发生了慢日志,命令执行耗时 10 微妙。

04、操作慢日志

知道了慢日志的存储结构,我们就需要考虑在执行命令的时候,如何根据条件去创建慢日志。

首先我们需要在命令的执行前后记录时间戳,然后相减计算出命令的执行耗时,然后根据 Redis 服务器配置slowlog-log-slower-than 进行对比,决定是否记录慢日志,另外在记录慢日志的时候需要根据slowlog_max_len 值判断是否要删除最久的日志信息。伪代码如下:

1
2
3
4
5
6
7
8
//1. 记录命令执行前的时间戳
long before = now();
//2. 执行命令
execute(argv, argc);
//3. 记录命令执行后的时间戳
long after = now();
//4. 调用创建慢日志函数
slowlogPushEntryIfNeed(argv, argc, after - before);

slowlogPushEntryIfNeed 函数主要用来判断是否插入数据,以及是否删除旧数据。

1
2
3
4
5
6
7
8
9
10
11
void slowlogPushEntryIfNeed(robj **argv, int argc, long long duration) {
	//1. 判断是否开启慢日志
	if (server.slowlog_log_slower_than < 0) return;
	//2. 如果超时,则插入慢日志
  if (duraton > server.slowlog_log_slower_than){
    //插入
  }
  while (listLength(server.slowlog) > server.slowlog_max_len) {
    //删除
  }
}

我们先判断服务器是否配置了超时参数,如果超时参数小于 0 则直接返回,否则再比较命令执行时间是否超时,如果超时则插入慢日志;最后在比较慢日志的条数是否达到上限,如果达到则进行删除。

05、总结

Redis 在日常工作中经常会使用,其中还有很多细节需要我们慢慢研究和学习,公号已经发过好多关于 Redis 相关的文章了,后续还会有其他内容的相关文章,欢迎关注。最后欢迎大家到我们《Java 极客技术》知识星球中来跟我们一起学习,一起进步。

阅读全文 »
1 … 17 18 19 … 32
Java Geek Tech

Java Geek Tech

一群热爱 Java 的技术人

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