码猿技术专栏

微信公众号:码猿技术专栏

前言

自从用了Spring Boot,个人最喜欢的就是Spring Boot的配置文件了,和Spring比起,Spring Boot更加灵活,修改的某些配置也是更加得心应手。

Spring Boot 官方提供了两种常用的配置文件格式,分别是propertiesYML格式。相比于properties来说,YML更加年轻,层级也是更加分明。

今天这篇文章就来介绍一下Spring Boot的配置文件的语法以及如何从配置文件中取值。

阅读全文 »

前言

相信从事Java开发的朋友都听说过SSM框架,这还算年轻的,老点的甚至经历过SSH,说起来有点恐怖,哈哈。比如我就是经历过SSH那个时代末流,没办法,很无奈。

当然无论是SSM还是SSH都不是今天的重点,今天要说的是Spring Boot,一个令人眼前一亮的框架,从大的说,Spring Boot取代了SSM 中的SS的角色。

今天这篇文章就来谈谈Spring Boot,这个我第一次使用直呼的框架。

阅读全文 »

前言

  • 这几年的工作过程中使用了很多的数据库工具,比如Sqlyog,DBeaver,sqlplus等工具,但是个人觉得很好用的还是Navicat。
  • 不如人意的就是目前Navicat都在收费,今天就来分享下如何安装免费的Navicat。
  • 阅读全文 »

前言

  • 今天重新装了IDEA2020,顺带重装了一些插件,毕竟这些插件都是习惯一直在用,其中一款就是Mybatis Log plugin,按照往常的思路,在IDEA插件市场搜索安装,艹,眼睛一瞟,竟然收费了,对于我这种支持盗版的人来说太难了,于是自己开始捣鼓各种尝试破解,下文分享自己的破解方式。

    阅读全文 »

导读

  • Mysql在中小型企业中是个香饽饽,目前主流的数据库之一,几乎没有一个后端开发者不会使用的,但是作为一个老司机,仅仅会用真的不够。

  • 今天陈某透过一个简单的查询语句来讲述在Mysql内部的执行过程。

select * from table where id=10;

撸它

  • 首先通过一张图片来了解一下Mysql的基础架构,如下:

  • 从上图可以看出,Mysql大致分为Server层和存储引擎层两部分。

  • Server层包括连接器、查询缓存、分析器、优化器等,其中包含了Mysql的大多数核心功能以及所有的内置函数(如日期,时间函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

  • 存储引擎层负责数据的存储和提取。它的架构是可插拔式的,支持InnoDB、MyISAM等多个存储引擎。Mysql中主流的存储引擎是InnoDB,由于它对事务的支持让它从Mysql5.5.5版本开始成为了默认的存储引擎。

  • 大致了解了整体架构,现在说说每一个基础的模块都承担着怎样的责任。

1. 连接器

  • 顾名思义,是客户端和Mysql之间连接的媒介,负责登录、获取权限、维持连接和管理连接。连接命令一般如下:
mysql [-h] ip [- P] port -u [user] -p 
  • 在完成经典的TCP握手后,连接器会开始认证身份,要求输入密码。

    • 密码认证通过,连接器会查询出拥有的权限,即使管理员修改了权限,也不会影响你这次的连接,只有重新连接才会生效。
    • 密码认证失败,会收到提示信息Access denied for user。
  • 连接完成后,没有后续动作的连接将会变成空闲连接,你可以输入show processlist命令看到它。如下图,其中的Command这一列显示为sleep的这一行表示在系统里面有一个空闲连接。

  • 客户端如果太长时间没有执行动作,连接器将会自动断开,这个时间由参数wait_timeout控制,默认值是8小时。

  • 如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执行请求了。

2. 查询缓存【废材,8.0 版本完全删除】

  • 连接建立完成后,你就可以select语句了,执行之前会查询缓存。

  • 查询缓存在Mysql中的是默认关闭的,因为缓存命中率非常低,只要有对表执行一个更新操作,这个表的所有查询缓存都将被清空。怎么样?一句废材足以形容了!!!

  • 废材的东西不必多讲,主流的Redis的缓存你不用,别再搞这废材了。

3. 分析器

  • 如果没有命中查询缓存,就要执行查询了,但是在执行查询之前,需要对SQL语句做解析,判断你这条语句有没有语法错误。

  • 分析器会做 '词法分析' ,你输入的无非可就是多个字符串和空格组成的SQL语句,MYSQL需要识别出里面的字符串是什么,代表什么,有没有关键词等。

  • MYSQL会从你输入的select 这个关键字识别出来是一个查询语句,table是表名,id是列名。

  • 做完这些会做 '语法分析' ,根据MYSQL定义的规则来判断你的SQL语句有没有语法错误,如果你的语法不对,就会收到类似如下的提醒:

ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'elect * from t where ID=1' at line 1
  • 一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接“use near”的内容。

4. 优化器

  • 经过分析器词法和语法的分析,此时就能知道这条SQL语句是干什么的。但是在开始执行之前,MYSQL底层还要使用优化器对这条SQL语句进行优化处理。

  • MYSQL内部会对这条SQL进行评估,比如涉及到多个索引会比较使用哪个索引代价更小、多表join的时候会考虑决定各个表的连接顺序。

  • 优化器的作用一句话总结:根据MYSQL内部的算法决定如何执行这条SQL语句来达到MYSQL认为代价最小目的。

  • 优化器阶段完成后,这个语句的执行方案就确定了,接下来就交给执行器执行了。

5. 执行器

  • MYSQL通过分析器知道了要做什么,通过优化器知道了如何做,于是就进入了执行器阶段。

  • 执行器开始执行之前,需要检查一下用户对表table有没有执行的权限,没有返回权限不足的错误,有的话就执行。

  • 执行也是分类的,如果Id不是索引则全表扫描,一行一行的查找,如果是索引则在索引组织表中查询,索引的查询很复杂,其中涉及到B+树等算法,这里不再详细介绍。

总结

  • 一条SQL语句在MYSQL内部执行的过程涉及到的内部模块有:连接器、查询缓存、分析器、优化器、执行器、存储引擎。

  • 至此,MYSQL的基础架构已经讲完了。

前言

  • 在部署线上项目时,相信大家都会遇到一个问题,线上的 Bug 但是在本地不会复现,多么无奈。
  • 此时最常用的就是取到前端传递的数据用接口测试工具测试,比如 POSTMAN,复杂不,难受不?
  • 今天陈某教你一招,让你轻松调试线上的 Bug。文章目录如下:

什么是 JPDA?

  • JPDA(Java Platform Debugger Architecture),即 Java 平台调试体系,具体结构图如下图所示。

  • 其中实现调试功能的主要协议是JDWP协议,在 Java SE 5 以前版本,JVM 端的实现接口是 JVMPI(Java Virtual Machine Profiler Interface),而在 Java SE 5 及以后版本,使用 JVMTI(Java Virtual Machine Tool Interface) 来替代 JVMPI。

  • 因此,如果使用 Java SE 5 之前版本,使用调试功能的命令为:

1
java -Xdebug -Xrunjdwp:...
  • Java SE 5 及之后版本,使用调试功能的命令为:
1
java -agentlib:jdwp=...

调试命令

  • 现在开发中最常见的一条远程调试的的命令如下:
1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9091 -jar xxx.jar

参数说明

  • 基于前面的调试命令,我们来分析一下基本的参数代表什么意思。

transport

  • 指定运行的被调试应用和调试者之间的通信协议,它由几个可选值:
    1. dt_socket:主要的方式,采用socket方式连接。
    2. dt_shmem:采用共享内存方式连接,仅支持 Windows 平台。

server

  • 指定当前应用作为调试服务端还是客户端,默认为n
  • 如果你想将当前应用作为被调试应用,设置该值为 y,如果你想将当前应用作为客户端,作为调试的发起者,设置该值为n

suspend

  • 当前应用启动后,是否阻塞应用直到被连接,默认值为 y
  • 在大部分的应用场景,这个值为 n,即不需要应用阻塞等待连接。一个可能为 y的应用场景是,你的程序在启动时出现了一个故障,为了调试,必须等到调试方连接上来后程序再启动。

address

  • 暴露的调试连接端口,默认值为 8000
  • 此端口一定不能与项目端口重复,且必须是服务器开放的端口。

onthrow

  • 当程序抛出设定异常时,中断调试。

onuncaught

  • 当程序抛出未捕获异常时,是否中断调试,默认值为 n。

launch

  • 当调试中断时,执行的程序。

timeout

  • 该参数限定为java -agentlib:jdwp=…可用,单位为毫秒ms
  • suspend = y 时,该值表示等待连接的超时;当 suspend = n 时,该值表示连接后的使用超时。

参考命令

  1. -agentlib:jdwp=transport=dt_socket,server=y,address=8000:以 Socket 方式监听 8000 端口,程序启动阻塞(suspend 的默认值为 y)直到被连接。

  2. -agentlib:jdwp=transport=dt_socket,server=y,address=localhost:8000,timeout=5000:以 Socket 方式监听 8000 端口,当程序启动后 5 秒无调试者连接的话终止,程序启动阻塞(suspend 的默认值为 y)直到被连接。

  3. -agentlib:jdwp=transport=dt_shmem,server=y,suspend=n:选择可用的共享内存连接地址并使用 stdout 打印,程序启动不阻塞。

  4. -agentlib:jdwp=transport=dt_socket,address=myhost:8000:以 socket 方式连接到 myhost:8000上的调试程序,在连接成功前启动阻塞。

  5. -agentlib:jdwp=transport=dt_socket,server=y,address=8000,onthrow=java.io.IOException,launch=/usr/local/bin/debugstub:以 Socket 方式监听 8000 端口,程序启动阻塞(suspend 的默认值为 y)直到被连接。当抛出 IOException 时中断调试,转而执行 usr/local/bin/debugstub程序。

IDEA 远程调试示例

  • 首先打包 SpringBoot 项目,在服务器上运行,执行以下命令:
1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9190 -jar debug-demo.jar
  • 出现下图的界面,表示运行成功:

  • 然后在 IDEA 中,点击 Edit Configurations,在弹框中点击 + 号,然后选择Remote

  • 填写服务器的地址及端口,点击 OK 即可。

  • 配置完毕后,DEBUG 调试运行即可。

  • 配置完毕后点击保存即可,因为我配置的 suspend=n,因此服务端程序无需阻塞等待我们的连接。我们点击 IDEA 调试按钮,当我访问某一接口时,能够正常调试。

小福利

  • 作者为大家准备了接近 10M 的面试题,涵盖后端各个技术维度,老规矩,公众号内回复关键词JAVA面试题即可免费获取。
  • 关注微信公众号回复关键词:

前言

  • 文章首发于微信公众号大白话布隆过滤器,又能和面试官扯皮了~
  • 近期在做推荐系统中已读内容去重的相关内容,刚好用到了布隆过滤器,于是写了一篇文章记录分享一下。
  • 文章的篇幅不是很长,主要讲了布隆过滤器的核心思想,目录如下:

什么是布隆过滤器?

  • 布隆过滤器是由一个长度为m比特的位数组k哈希函数组成的数据结构。比特数组均初始化为0,所有哈希函数都可以分别把输入数据尽量均匀地散列。
  • 插入一个元素时,将其数据通过k个哈希函数转换成k个哈希值,这k个哈希值将作为比特数组的下标,并将数组中的对应下标的值置为1
  • 查询一个元素时,同样会将其数据通过k个哈希函数转换成k个哈希值(数组下标),查询数组中对应下标的值,如果有一个下标的值为0表明该元素一定不在集合中,如果全部下标的值都为1,表明该元素有可能在集合中。至于为什么有可能在集合中? 因为有可能某个或者多个下标的值为 1 是受到其他元素的影响,这就是所谓的假阳性,下文会详细讲述。
  • 无法删除一个元素,为什么呢?因为你删除的元素的哈希值可能和集合中的某个元素的哈希值有相同的,一旦删除了这个元素会导致其他的元素也被删除。
  • 下图示出一个m=18, k=3的布隆过滤器示例。集合中的 x、y、z 三个元素通过 3 个不同的哈希函数散列到位数组中。当查询元素 w 时,因为有一个比特为 0,因此 w 不在该集合中。

假阳性概率的计算

  • 假阳性是布隆过滤器的一个痛点,因此需要不择一切手段来使假阳性的概率降低,此时就需要计算一下假阳性的概率了。
  • 假设我们的哈希函数选择位数组中的比特时,都是等概率的。当然在设计哈希函数时,也应该尽量满足均匀分布。
  • 在位数组长度m的布隆过滤器中插入一个元素,它的其中一个哈希函数会将某个特定的比特置为1。因此,在插入元素后,该比特仍然为 0 的概率是:
  • 现有k个哈希函数,并插入n个元素,自然就可以得到该比特仍然为 0 的概率是:
  • 反过来讲,它已经被置为1的概率就是:
  • 也就是说,如果在插入n个元素后,我们用一个不在集合中的元素来检测,那么被误报为存在于集合中的概率(也就是所有哈希函数对应的比特都为1的概率)为:
  • n比较大时,根据极限公式,可以近似得出假阳性率:
  • 所以,在哈希函数个数k一定的情况下有如下结论:
    1. 位数组长度 m 越大,假阳性率越低。
    2. 已插入元素的个数 n 越大,假阳性率越高。

优点

  • 用比特数组表示,不用存储数据本身,对空间的节省相比于传统方式占据绝对的优势。
  • 时间效率很高,无论是插入还是查询,只需要简单的经过哈希函数转换,时间复杂度均为O(k)

缺点

  • 存在假阳性的概率,准确率要求高的场景不太适用。
  • 只能插入和查询,不能删除了元素。

应用场景

  • 布隆过滤器的用途很多,但是主要的作用就是去重,这里列举几个使用场景。

爬虫重复 URL 检测

  • 试想一下,百度是一个爬虫,它会定时搜集各大网站的信息,文章,那么它是如何保证爬取到文章信息不重复,它会将 URL 存放到布隆过滤器中,每次爬取之前先从布隆过滤器中判断这个 URL 是否存在,这样就避免了重复爬取。当然这种存在假阳性的可能,但是只要你的比特数组足够大,假阳性的概率会很低,另一方面,你认为百度会在意这种的误差吗,你的一篇文章可能因为假阳性概率没有收录到,对百度有影响吗?

抖音推荐功能

  • 读者朋友们应该没人没刷过抖音吧,每次刷的时候抖音给你的视频有重复的吗?他是如何保证推荐的内容不重复的呢?
  • 最容易想到的就是抖音会记录用户的历史观看记录,然后从历史记录中排除。这是一种解决办法,但是性能呢?不用多说了,有点常识的都知道这不可能。
  • 解决这种重复的问题,布隆过滤器有着绝对的优势,能够很轻松的解决。

防止缓存穿透

  • 缓存穿透是指查询一条数据库和缓存都没有的一条数据,就会一直查询数据库,对数据库的访问压力会一直增大。
  • 布隆过滤器在解决缓存穿透的问题效果也是很好,这里不再细说,后续文章会写。

如何实现布隆过滤器?

  • 了解布隆过滤器的设计思想之后,想要实现一个布隆过滤器其实很简单,陈某这里就不再搬门弄斧了,介绍一下现成的实现方式吧。

Redis 实现

  • Redis4.0 之后推出了插件的功能,下面用 docker 安装:
1
2
docker pull redislabs/rebloom
docker run -p6379:6379 redislabs/rebloom
  • 安装完成后连接 redis 即可,运行命令:
1
redis-cli
  • 至于具体的使用这里不再演示了,直接看官方文档和教程,使用起来还是很简单的。

Guava 实现

  • guava 对应布隆过滤器的实现做出了支持,使用 guava 可以很轻松的实现一个布隆过滤器。

1. 创建布隆过滤器

  • 创建布隆过滤器,如下:
1
2
3
4
5
6
7
8
BloomFilter<Integer> filter = BloomFilter.create(
Funnels.integerFunnel(),
5000,
0.01);
//插入
IntStream.range(0, 100_000).forEach(filter::put);
//判断是否存在
boolean b = filter.mightContain(1);
  • arg1:用于将任意类型 T 的输入数据转化为 Java 基本类型的数据,这里转换为 byte
  • arg2:byte 字节数组的基数
  • arg3:期望的假阳性概率

2.估计最优 m 值和 k 值

  • guava 在底层对 byte 数组的基数(m)和哈希函数的个数 k 做了自己的算法,源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
//m值的计算
static long optimalNumOfBits(long n, double p) {
if (p == 0) {
p = Double.MIN_VALUE;
}
return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
}

//k值的计算
static int optimalNumOfHashFunctions(long n, long m) {
// (m / n) * log(2), but avoid truncation due to division!
return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
}
  • 想要理解 guava 的计算原理,还要从的上面推导的过程继续。
  • 由假阳性率的近似计算方法可知,如果要使假阳性率尽量小,在 m 和 n 给定的情况下,k值应为:
  • 将 k 代入上一节的式子并化简,我们可以整理出期望假阳性率 p 与 m、n 的关系:
  • 换算而得:
  • 根据以上分析得出以下的结论:
    1. 如果指定期望假阳性率 p,那么最优的 m 值与期望元素数 n 呈线性关系。
    2. 最优的 k 值实际上只与 p 有关,与 m 和 n 都无关,即:
    3. 综上两个结论,在创建布隆过滤器的时候,确定p值和m值很重要。

总结

  • 至此,布隆过滤器的知识介绍到这里,如果觉得陈某写得不错的,转发在看点一波,读者的一份支持将会是我莫大的鼓励。
  • 另外想和陈某私聊或者想要加交流群的朋友,公众号回复关键词加群加陈某微信,陈某会第一时间拉你进群。

巨人的肩膀

前言

  • 昨天有读者朋友留言,想要陈某写一篇防止缓存穿透的文章,今天特意写了一篇。
  • 文章目录如下:

什么是缓存穿透?

  • 缓存穿透其实是指从缓存中没有查到数据,而不得不从后端系统(比如数据库)中查询的情况。
  • 缓存毕竟是在内存中,不可能所有的数据都存储在 Redis 中,因此少量的缓存穿透是不可避免的,也是系统能够承受的,但是一旦在瞬间发生大量的缓存穿透,数据库的压力会瞬间增大,后果可想而知。
  • 在开发中使用缓存的方案如下图,在查询数据库之前会先查询 Redis:
    Redis缓存
  • 缓存穿透的整个过程分为如下几个步骤:
    1. 应用查询缓存,缓存不命中
    2. DB 层查询不命中,不将空结果缓存
    3. 返回空结果
    4. 下一个请求继续重复1,2,3步。

解决方案

  • 万事万物都是相生相克,既然出现了缓存穿透,就一定有避免的方案。
  • 下面介绍两种缓存的方案,分别是缓存空值布隆过滤器

缓存空值

  • 回顾缓存穿透的定义知道,大量空值没有缓存导致重复的访问 DB 层,由此解决方案也是很明显了,直接将返回的空值也缓存即可。此时的执行步骤如下图:
    缓存空值
  • 如上图所示,如果缓存不命中,查询 DB 层之后,直接将空值缓存在 Redis 中。伪代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
Object nullValue = new Object();
try {
Object valueFromDB = getFromDB(uid); //从数据库中查询数据
if (valueFromDB == null) {
cache.set(uid, nullValue, 10); //如果从数据库中查询到空值,就把空值写入缓存,设置较短的超时时间
} else {
cache.set(uid, valueFromDB, 1000);
}
} catch(Exception e) {
// 出现异常也要写入缓存
cache.set(uid, nullValue, 10);
}
  • 通过伪代码可以很清楚的了解了缓存空值的流程,但是需要注意以下问题:
    • 缓存一定要设置过期时间:因为空值并不是准确的业务数据,并且会占用缓存空间,所以要给空值加上一个过期时间,使得能够在短期之内被淘汰。但是随之而来的一个问题就是在一定的时间窗口内缓存的数据和实际数据不一致,比如设置 10 秒钟过期时间,但是在这 10 秒之内业务又写入了数据,那么返回就不应该为空值了,所以还要考虑数据一致的问题,解决方法很简单,利用消息系统或者主动更新的方式清除掉缓存中的数据即可。

布隆过滤器

  • 1970 年布隆提出了一种布隆过滤器的算法,用来判断一个元素是否在一个集合中。这种算法由一个二进制数组和一个 Hash 算法组成。
  • 具体的算法思想这里不再详细解释了,如有不了解的可以看陈某上一篇文章大白话布隆过滤器,又能和面试官扯皮了~
  • 解决缓存穿透的大致思想:在访问缓存层和存储层之前,可以通过定时任务或者系统任务来初始化布隆过滤器,将存在的 key 用布隆过滤器提前保存起来,做第一层的拦截。例如:一个推荐系统有 4 亿个用户 id, 每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放到存储层中, 但是最新的用户由于没有历史行为, 就会发生缓存穿透的行为, 为此可以将所有推荐数据的用户做成布隆过滤器。 如果布隆过滤器认为该用户 id 不存在, 那么就不会访问存储层, 在一定程度保护了存储层。此时的结构如下图:
    布隆过滤器
  • 当然布隆过滤器的假阳性的存在导致了误判率,但是我们可以尽量的降低误判率,一个解决方案就是:使用多个 Hash 算法为元素计算出多个 Hash 值,只有所有 Hash 值对应的数组中的值都为 1 时,才会认为这个元素在集合中。
  • 这种方法适用于数据命中不高数据相对固定实时性低(通常是数据 集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少。为什么呢?因为布隆过滤器不支持删除元素,一旦数据变化,并不能及时的更新布隆过滤器。

两种方案对比

  • 两种方案各有优缺点,具体使用哪种方案还是要根据业务场景和系统体量来定。具体的区别如下表:
方案 适用场景 维护成本
缓存对象 1. 数据命中不高 2. 数据频繁变化,实时性高 代码维护点单、需要过多的缓存空间,数据一致性需要自己实现
布隆过滤器 1. 数据命中不高 2.数据相对固定,实时性低 代码维护复杂、缓存空间占用少

总结

  • 至此,如何解决缓存穿透的问题已经介绍完了,觉得写得不错的,有所收获的朋友,点点在看,分享关注一波。

前言

  • Mybatis专栏文章写到至今已经有一个月了,从基础到源码详细的介绍了每个知识点,没什么多余的废话,全是工作、面试中常用到的姿势。有读者建议将文章汇总,这样方便阅读,于是特意花费了一天时间整理成册。

  • 全册总计92页,总计想3万多字,耗时30多天完成。

  • 由于作者水平有限,如果书中有不理解和错误的内容,感谢及时把疑惑或意见提交给我,作者会及时修正。

    阅读全文 »