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

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


  • 首页

  • 归档

  • 搜索

一篇文章教你轻松使用fastjson

发表于 2019-12-13

前言

只有光头才能变强。

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

JSON相信大家对他也不陌生了,前后端交互中常常就以JSON来进行数据交换。而有的时候,我们也会将JSON直接保存在数据库中。

可能就有人不太理解,为什么要将JSON保存在关系型数据库中?

我在最开始的时候也有类似的疑惑,问了几个同事,得出的结论都差不多:方便扩展,如果那些字段不需要用到索引,改动比较频繁,你又不想改动表的结构,那就可以在数据库中存入JSON

虽说存JSON会方便扩展,但如果你的MySQL版本还是相对较低的话,想要用SQL查JSON里某个属性,还是比较麻烦的。

并且从数据库里边取出来也仅仅是一个String,而想要操作JSON里边的属性,自己写不太方便,所以就有fastjson给我们去用。

预览知识点

这篇文章简单讲讲fastjson的使用,希望对大家有帮助。如果有帮助,给我点个赞呀!

一、fastjson入门

以下内容来源:github.com/alibaba/fas…

它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean

说白了就是JSON和Java对象互相转换

fastjson优点:速度快、使用广泛、使用简单、功能完备、测试完备(之前爆了很多漏洞,现在我司走发布流程都强制我们升级fastjson版本了),现在使用fastjson至少升级到1.2.60版本

速度快的原因:

1、自行编写类似StringBuilder的工具类SerializeWriter。

2、使用ThreadLocal来缓存buf。

3、使用asm避免反射

4、集成jdk实现的一些优化算法

二、使用fastjson

首先我们在pom文件中引入fastjson的依赖就好了:

1
2
3
4
5
复制代码<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>x.x.x</version>
</dependency>

fastjson的使用主要是三个对象:

  • JSON
  • JSONObject
  • JSONArray

三个类

JSONArray和JSONObject继承JSON:

JSONArray和JSONObject继承JSON

2.1 JSON对象

JSON这个类主要用于转换:

  • 将Java对象序列化为JSON字符串
  • 将JSON字符串反序列化为Java对象

所以,有三个方法我们用得特别多:

  • parseObject(String text, Class<T> clazz)
  • parseArray(String text, Class<T> clazz)
  • toJSONString(Object object)

2.2 JSONObject

JSON对象(JSONObject)中的数据都是以key-value形式出现,所以它实现了Map接口:

实现了Map接口

使用起来也很简单,跟使用Map就没多大的区别(因为它底层实际上就是操作Map),常用的方法:

  • getString(String key)
  • remove(Object key)

JSONObject有常用的Map方法

2.3 JSONArray

JSONArray则是JSON数组,JSON数组对象中存储的是一个个JSON对象,所以类中的方法主要用于直接操作JSON对象

实现List接口

最常用的方法:

  • getJSONObject(int index)

三、实战

从上面的简单介绍我们已经可以知道了:

  • JSON用于将字符串反序列化为JavaBean和JavaBean序列化为JSON
  • JSONObject代表的是JSON对象,底层通过Map来操作,常用getString等方法来获取对应的值
  • JSONArray代表的是JSON对象数组,底层实际上是List,它用作于操作JSON对象

一般来说,我们从数据库拿到JSON数据以后,然后要对JSON进行修改。比如JSON串如下:

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
复制代码{
"formId": "{$formId}",
"link": "www.java3y.com",
"text": [{
"name": "java3y",
"label": "3y",
"value": {
"value": "{$tureName}",
"color": "",
"emphasis": ""
}
}, {
"name": "java4y",
"label": "3y",
"value": {
"value": "{$title}",
"color": "",
"emphasis": ""
}
}, {
"name": "java5y",
"label": "5y",
"value": {
"value": "关注我的公众号,更多干货",
"color": "#ff0040",
"emphasis": ""
}
}],
"yyyImg": "",
"yyyAge": "",
"pagepath": ""
}

我们是不会直接操作JSON的,我们会将JSON转成我们自己的JavaBean,再操作JavaBean,最后序列化为JSONString

从上面的JSON结构上来看还是相对复杂的,思路:

  • 我们可以根据JSON的结构构建对应的JavaBean
  • 使用JSON类将JSON字符串反序列化为JavaBean
  • 修改JavaBean的值
  • 最后将JavaBean序列化为JSON字符串

从上面的JSON结构,首先我们针对text这层抽象为一个JavaBean。(实际上最里层的结构是value,但我这边不需要处理value,所以就不抽象了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码/**
* "name": "java3y",
* "label": "3y",
* "value": {
* "value": "{$tureName}",
* "color": "",
* "emphasis": ""
* }
*
* 对Text进行抽象
*/
public class TextInfo {

private String name;
private String label;

// 因为value我这边不需要操作,所以就不抽象了,如果每层都要处理,那就得抽象
private Object value;


// set get ... 省略 欢迎关注我的公众号:Javay

}

然后对外层进行抽象:

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码public class ContentValue {
private String formId;
private String link;
// 这里是一个数组,我们就抽象为List,属性名为text
private List<TextInfo> text;

private String yyyImg;
private String yyyAge;
private String pagepath;

// set get ... 省略 欢迎关注我的公众号:Javay

}

我们反序列化看一下:

1
2
3
4
5
6
7
8
9
10
11
复制代码public static void main(String[] args) {

// JSON 字符串
String s = "{\"formId\":\"{$formId}\",\"link\":\"www.java3y.com\",\"text\":[{\"name\":\"java3y\",\"label\":\"3y\",\"value\":{\"value\":\"{$tureName}\",\"color\":\"\",\"emphasis\":\"\"}},{\"name\":\"java4y\",\"label\":\"3y\",\"value\":{\"value\":\"{$title}\",\"color\":\"\",\"emphasis\":\"\"}},{\"name\":\"java5y\",\"label\":\"5y\",\"value\":{\"value\":\"关注我的公众号,更多干货\",\"color\":\"#ff0040\",\"emphasis\":\"\"}}],\"yyyImg\":\"\",\"yyyAge\":\"\",\"pagepath\":\"\"}";

// 使用JSON对象 将JSON字符串反序列化为JavaBean
ContentValue contentValue = JSON.parse(s, ContentValue.class);
System.out.println(contentValue);


}

反序列化结果:

反序列化

我们要改text里边的值,只需要操作JavaBean就好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码public static void main(String[] args) {

// JSON 字符串
String s = "{\"formId\":\"{$formId}\",\"link\":\"www.java3y.com\",\"text\":[{\"name\":\"java3y\",\"label\":\"3y\",\"value\":{\"value\":\"{$tureName}\",\"color\":\"\",\"emphasis\":\"\"}},{\"name\":\"java4y\",\"label\":\"3y\",\"value\":{\"value\":\"{$title}\",\"color\":\"\",\"emphasis\":\"\"}},{\"name\":\"java5y\",\"label\":\"5y\",\"value\":{\"value\":\"关注我的公众号,更多干货\",\"color\":\"#ff0040\",\"emphasis\":\"\"}}],\"yyyImg\":\"\",\"yyyAge\":\"\",\"pagepath\":\"\"}";

// 使用JSON对象 将JSON字符串反序列化为JavaBean
ContentValue contentValue = JSON.parse(s, ContentValue.class);
List<TextInfo> text = contentValue.getText();
for (TextInfo textInfo : text) {
textInfo.setName("Java3y");
textInfo.setLabel("关注我的公众号呗");
}


// 修改后,反序列化回去
String content = JSON.toJSONString(contentValue);
}

序列化结果:

序列化

轻松将JSON字符串里边的字段改掉。

最后

总的来说,fastjson还是足够方便好用的,它的速度也很快,只是最近漏洞有点多。

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

乐于输出干货的Java技术公众号:Java3y。公众号内有300多篇原创技术文章、海量视频资源、精美脑图,关注即可获取!

转发到朋友圈是对我最大的支持!

非常感谢人才们能看到这里,如果这个文章写得还不错,觉得「三歪」我有点东西的话 求点赞 求关注️ 求分享👥 求留言💬 对暖男我来说真的 非常有用!!!

创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

本文转载自: 掘金

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

什么鬼,面试官竟然让我用Redis实现一个消息队列? 一、什

发表于 2019-12-11

[ 转载 ]原文链接:blog.csdn.net/yelvgou9995…

众所周知,redis是一个高性能的分布式key-value存储系统,在NoSQL数据库市场上,redis自己就占据了将近半壁江山,足以见到其强大之处。同时,由于redis的单线程特性,我们可以将其用作为一个消息队列。本篇文章就来讲讲如何将redis整合到spring boot中,并用作消息队列的……

一、什么是消息队列

“消息队列”是在消息的传输过程中保存消息的容器。——《百度百科》

消息我们可以理解为在计算机中或在整个计算机网络中传递的数据。

队列是我们在学习数据结构的时候学习的基本数据结构之一,它具有先进先出的特性。

所以,消息队列就是一个保存消息的容器,它具有先进先出的特性。

为什么会出现消息队列?

  1. 异步:常见的B/S架构下,客户端向服务器发送请求,但是服务器处理这个消息需要花费的时间很长的时间,如果客户端一直等待服务器处理完消息,会造成客户端的系统资源浪费;而使用消息队列后,服务器直接将消息推送到消息队列中,由专门的处理消息程序处理消息,这样客户端就不必花费大量时间等待服务器的响应了;
  2. 解耦:传统的软件开发模式,模块之间的调用是直接调用,这样的系统很不利于系统的扩展,同时,模块之间的相互调用,数据之间的共享问题也很大,每个模块都要时时刻刻考虑其他模块会不会挂了;使用消息队列以后,模块之间不直接调用,而是通过数据,且当某个模块挂了以后,数据仍旧会保存在消息队列中。最典型的就是生产者-消费者模式,本案例使用的就是该模式;
  3. 削峰填谷:某一时刻,系统的并发请求暴增,远远超过了系统的最大处理能力后,如果不做任何处理,系统会崩溃;使用消息队列以后,服务器把请求推送到消息队列中,由专门的处理消息程序以合理的速度消费消息,降低服务器的压力。

下面一张图我们来简单了解一下消息队列

由上图可以看到,消息队列充当了一个中间人的角色,我们可以通过操作这个消息队列来保证我们的系统稳定。

二、环境准备

Java环境:jdk1.8

spring boot版本:2.2.1.RELEASE

redis-server版本:3.2.100

三、相关依赖

这里只展示与redis相关的依赖,

这里解释一下这两个依赖:

  • 第一个依赖是对redis NoSQL的支持
  • 第二个依赖是spring integration与redis的结合,这里添加这个代码主要是为了实现分布式锁

四、配置文件

这里只展示与redis相关的配置

五、代码配置

redis用作消息队列,其在spring boot中的主要表现为一RedisTemplate.convertAndSend()方法和一个MessageListener接口。所以我们要在IOC容器中注入一个RedisTemplate和一个实现了MessageListener接口的类。话不多说,先看代码

配置RedisTemplate

配置RedisTemplate的主要目的是配置序列化方式以解决乱码问题,同时合理配置序列化方式还能降低一点性能开销。

代码第12行,我们配置默认的序列化方式为GenericJackson2JsonRedisSerializer

代码第13行,我们配置键的序列化方式为StringRedisSerializer

代码第14行,我们配置哈希表的值的序列化方式为GenericJackson2JsonRedisSerializer

RedisTemplate几种序列化方式的简要介绍

六、redis队列监听器(消费者)

上面说了,与redis队列监听器相关的类为一个名为MessageListener的接口,下面是该接口的源码

可以看到,该接口仅有一个onMessage(Message message, @Nullable byte[] pattern)方法,该方法便是监听到队列中消息后的回调方法。下面解释一下这两个参数:

  • message:redis消息类,该类中仅有两个方法
    • byte[] getBody()以二进制形式获取消息体
    • byte[] getChannel()以二进制形式获取消息通道
  • pattern:二进制形式的消息通道,和message.getChannel()返回值相同

介绍完接口,我们来实现一个简单的redis队列监听器

代码很简单,就是输出参数中包含的关键信息。需要注意的是,RedisSerializer的实现要与上面配置的序列化方式一致。

队列监听器实现完以后,我们还需要将这个监听器添加到redis队列监听器容器中,代码如下:

这几行代码大概意思就是新建一个Redis消息监听器容器,然后将监听器和管道名想绑定,最后返回这个容器。

这里要注意的是,这个管道名和下面将要说的推送消息时的管道名要一致,不然监听器监听不到消息。

七、redis队列推送服务(生产者)

上面我们配置了RedisTemplate将要在这里使用到。

代码如下:

关键代码为第7行,redis.convertAndSend()这个方法的作用为,向某个通道(参数1)推送一条消息(第二个参数)。

这里还是要注意上面所说的,生产者和消费者的通道名要相同。

至此,消息队列的生产者和消费者已经全部编写完成。

八、遇到的问题及解决办法

1、spring boot使用log4j2日志框架问题

在我添加了spring-boot-starter-log4j2依赖并在spring-boot-starter-web中排除了spring-boot-starter-logging后,运行项目,还是会提示下面的错误:

1
2
3
4
5
6
7
8
9
复制代码<pre style="box-sizing: border-box; outline: 0px; margin: 0px 0px 24px; padding: 8px; position: relative; font-family: Consolas, Inconsolata, Courier, monospace; white-space: pre-wrap; word-wrap: break-word; overflow-x: auto; font-size: 14px; line-height: 22px; color: rgb(0, 0, 0);">SLF4J: Class path contains multiple SLF4J bindings.

SLF4J: Found binding in [jar:file:.....m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: Found binding in [jar:file:.....m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.12.1/log4j-slf4j-impl-2.12.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]</pre>

这个错误就是maven中有多个日志框架导致的。后来通过依赖分析,发现在spring-boot-starter-data-redis中,也依赖了spring-boot-starter-logging,解决办法也很简单,下面贴出详细代码

2、redis队列监听器线程安全问题

redis队列监听器的监听机制是:使用一个线程监听队列,队列有未消费的消息则取出消息并生成一个新的线程来消费消息。如果你还记得,我开头说的是由于redis单线程特性,因此我们用它来做消息队列,但是如果监听器每次接受一个消息就生成新的线程来消费信息的话,这样就完全没有使用到redis的单线程特性,同时还会产生线程安全问题。

单一消费者(一个通道只有一个消费者)的解决办法

最简单的办法莫过于为onMessage()方法加锁,这样简单粗暴却很有用,不过这种方式无法控制队列监听的速率,且无限制的创造线程最终会导致系统资源被占光。

那如何解决这种情况呢?线程池。

在将监听器添加到容器的配置的时候,RedisMessageListenerContainer类中有一个方法setTaskExecutor(Executor taskExecutor)可以为监听容器配置线程池。配置线程池以后,所有的线程都会由该线程池产生,由此,我们可以通过调节线程池来控制队列监听的速率。

多个消费者(一个通道有多个消费者)的解决办法

单一消费者的问题相比于多个消费者来说还是较为简单,因为Java内置的锁都是只能控制自己程序的运行,不能干扰其他的程序的运行;然而现在很多时候我们都是在分布式环境下进行开发,这时处理多个消费者的情况就很有意义了。

那么这种问题如何解决呢?分布式锁。

下面来简要科普一下什么是分布式锁:

分布式锁是指在分布式环境下,同一时间只有一个客户端能够从某个共享环境中(例如redis)获取到锁,只有获取到锁的客户端才能执行程序。

然后分布式锁一般要满足:排他性(即同一时间只有一个客户端能够获取到锁)、避免死锁(即超时后自动释放)、高可用(即获取或释放锁的机制必须高可用且性能佳)

上面讲依赖的时候,我们导入了一个spring-integration-redis依赖,这个依赖里面包含了很多实用的工具类,而我们接下来要讲的分布式锁就是这个依赖下面的一个工具包RedisLockRegistry。

首先讲一下如何使用,导入了依赖以后,首先配置一个Bean

RedisLockRegistry的构造函数,第一个参数是redis连接池,第二个参数是锁的前缀,即取出的锁,键名为“demo-lock:KEY_NAME”,第三个参数为锁的过期时间(秒),默认为60秒,当持有锁超过该时间后自动过期。

使用锁的方法,下面是对监听器的修改

上面代码的代码比起前面的监听器代码,只是多了一个注入的RedisLockRegistry,一个通过redisLockRegistry.obtain()方法获取锁,一个加锁一个解锁,然后这就完成了分布式锁的使用。

注意这个获取锁的方法redisLockRegistry.obtain(),其返回的是一个名为RedisLock的锁,这是一个私有内部类,它实现了Lock接口,因此我们不能从代码外部创建一个他的实例,只能通过obtian()方法来获取这个锁。

文章来源:blog.csdn.net/yelvgou9995…

END

本文转载自: 掘金

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

Redis面试题详解 哨兵+复制+事务+集群+持久化等

发表于 2019-12-10

file

Redis主要有哪些功能?

1.哨兵(Sentinel)和复制(Replication)

Redis服务器毫无征兆的罢工是个麻烦事,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制。

Sentinel可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能,Replication则是负责让一个Redis服务器可以配备多个备份的服务器。

Redis也是利用这两个功能来保证Redis的高可用的

2.事务

很多情况下我们需要一次执行不止一个命令,而且需要其同时成功或者失败。redis对事务的支持也是源自于这部分需求,即支持一次性按顺序执行多个命令的能力,并保证其原子性。

3.LUA脚本

在事务的基础上,如果我们需要在服务端一次性的执行更复杂的操作(包含一些逻辑判断),则lua就可以排上用场了

4.持久化

redis的持久化指的是redis会把内存的中的数据写入到硬盘中,在redis重新启动的时候加载这些数据,从而最大限度的降低缓存丢失带来的影响。

5.集群(Cluster)

单台服务器资源的总是有上限的,CPU资源和IO资源我们可以通过主从复制,进行读写分离,把一部分CPU和IO的压力转移到从服务器上,这也有点类似mysql数据库的主从同步。

在Redis官方的分布式方案出来之前,有twemproxy和codis两种方案,这两个方案总体上来说都是依赖proxy来进行分布式的。

file

Redis支持哪几种数据类型?

支持多种类型的数据结构

1.string:最基本的数据类型,二进制安全的字符串,最大512M。

2.list:按照添加顺序保持顺序的字符串列表。

3.set:无序的字符串集合,不存在重复的元素。

4.sorted set:已排序的字符串集合。

5.hash:key-value对的一种集合。

file

Redis是单进程单线程的?

Redis是单进程单线程的,Redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。

Redis为什么是单线程的?

多线程处理会涉及到锁,而且多线程处理会涉及到线程切换而消耗CPU。因为CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存或者网络带宽。单线程无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来解决。

其它开源软件采用的模型

Nginx:多进程单线程模型

Memcached:单进程多线程模型

使用Redis的优势?

1.速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

  • 支持丰富数据类型,支持string,list,set,sorted set,hash

3.支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

  • 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

Redis单点吞吐量

单点TPS达到8万/秒,QPS达到10万/秒,补充下TPS和QPS的概念

1.QPS: 应用系统每秒钟最大能接受的用户访问量

每秒钟处理完请求的次数,注意这里是处理完,具体是指发出请求到服务器处理完成功返回结果。可以理解在server中有个counter,每处理一个请求加1,1秒后counter=QPS。

2.TPS: 每秒钟最大能处理的请求数

每秒钟处理完的事务次数,一个应用系统1s能完成多少事务处理,一个事务在分布式处理中,可能会对应多个请求,对于衡量单个接口服务的处理能力,用QPS比较合理。

Redis相比memcached有哪些优势?

1.memcached所有的值均是简单的字符串,Redis作为其替代者,支持更为丰富的数据类型

2.Redis的速度比memcached快很多

3.Redis可以持久化其数据

4.Redis支持数据的备份,即master-slave模式的数据备份。

Redis有哪几种数据淘汰策略?

在Redis中,允许用户设置最大使用内存大小server.maxmemory,当Redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

1.volatile-lru:从已设置过期的数据集中挑选最近最少使用的淘汰

2.volatile-ttr:从已设置过期的数据集中挑选将要过期的数据淘汰

3.volatile-random:从已设置过期的数据集中任意挑选数据淘汰

4.allkeys-lru:从数据集中挑选最近最少使用的数据淘汰

5.allkeys-random:从数据集中任意挑选数据淘汰

6.noenviction:禁止淘汰数据

redis淘汰数据时还会同步到aof

Redis集群方案应该怎么做?都有哪些方案?

1.twemproxy

2.codis,目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新hash节点。

3.Redis cluster3.0自带的集,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。

Redis读写分离模型

通过增加Slave DB的数量,读的性能可以线性增长。为了避免Master DB的单点故障,集群一般都会采用两台Master DB做双机热备,所以整个集群的读和写的可用性都非常高。

读写分离架构的缺陷在于,不管是Master还是Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于Write-intensive类型的应用,读写分离架构并不适合。

Redis数据分片模型

为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。

可以将每个节点看成都是独立的master,然后通过业务实现数据分片。

结合上面两种模型,可以将每个master设计成由一个master和多个slave组成的模型。

Redis提供了哪几种持久化方式?

RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储

AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.

如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.

你也可以同时开启两种持久化方式, 在这种情况下, 当Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始。

如何选择合适的持久化方式?

  • Redis主要提供了两种持久化机制:**RDB和AOF;

2、RDB

默认开启,会按照配置的指定时间将内存中的数据快照到磁盘中,创建一个dump.rdb文件,Redis启动时再恢复到内存中。

Redis会单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。

需要注意的是,每次快照持久化都会将主进程的数据库数据复制一遍,导致内存开销加倍,若此时内存不足,则会阻塞服务器运行,直到复制结束释放内存;都会将内存数据完整写入磁盘一次,所以如果数据量大的话,而且写操作频繁,必然会引起大量的磁盘I/O操作,严重影响性能,并且最后一次持久化后的数据可能会丢失;

3.AOF

以日志的形式记录每个写操作(读操作不记录),只需追加文件但不可以改写文件,Redis启动时会根据日志从头到尾全部执行一遍以完成数据的恢复工作。包括flushDB也会执行。

主要有两种方式触发:有写操作就写、每秒定时写(也会丢数据)。

因为AOF采用追加的方式,所以文件会越来越大,针对这个问题,新增了重写机制,就是当日志文件大到一定程度的时候,会fork出一条新进程来遍历进程内存中的数据,每条记录对应一条set语句,写到临时文件中,然后再替换到旧的日志文件(类似rdb的操作方式)。默认触发是当aof文件大小是上次重写后大小的一倍且文件大于64M时触发。

当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。一般情况下,只要使用默认开启的RDB即可,因为相对于AOF,RDB便于进行数据库备份,并且恢复数据集的速度也要快很多。

开启持久化缓存机制,对性能会有一定的影响,特别是当设置的内存满了的时候,更是下降到几百reqs/s。所以如果只是用来做缓存的话,可以关掉持久化。

Redis常见性能问题和解决方案?

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…

这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

Redis支持的Java客户端都有哪些?官方推荐用哪个?

Redisson、Jedis、lettuce等等,官方推荐使用Redisson。

Redis哈希槽的概念?

Redis集群没有使用一致性hash,而是引入了哈希槽的概念,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。

Redis集群最大节点个数是多少?

Redis集群预分好16384个桶(哈希槽)

Redis集群的主从复制模型是怎样的?

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.

Redis集群会有写操作丢失吗?为什么?

Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。

Redis集群之间是如何复制的?

异步复制

Redis如何做内存优化?

尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面.

Redis回收进程如何工作的?

一个客户端运行了新的命令,添加了新的数据。

Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。

Redis回收使用的是什么算法?

LRU算法

Redis有哪些适合的场景?

1)Session共享(单点登录)

2)页面缓存

3)队列

4)排行榜/计数器

5)发布/订阅

本文由博客一文多发平台 OpenWrite 发布!

本文转载自: 掘金

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

程序员需要了解的硬核知识之控制硬件

发表于 2019-12-10

应用和硬件的关系

我们作为程序员一般很少直接操控硬件,我们一般通过 C、Java 等高级语言编写的程序起到间接控制硬件的作用。所以大家很少直接接触到硬件的指令,硬件的控制是由 Windows 操作系统 全权负责的。

你一定猜到我要说什么了,没错,我会说但是,任何事情没有绝对性,环境的不同会造成结果的偏差。虽然程序员没法直接控制硬件,并且 Windows 屏蔽了控制硬件的细节,但是 Windows 却为你开放了 系统调用功能来实现对硬件的控制。在 Windows 中,系统调用称为 API,API 就是应用调用的函数,这些函数的实体被存放在 DLL 文件中。

下面我们来看一个通过系统调用来间接控制硬件的实例

假如要在窗口中显示字符串,就可以使用 Windows API 中的 TextOut 函数。TextOut 函数的语法(C 语言)如下

1
2
3
4
5
6
7
复制代码BOOL TextOut{
HDC hdc, // 设备描述表的句柄
int nXStart, // 显示字符串的 x 坐标
int nYStart, // 显示字符串的 y 坐标
LPCTSTR lpString, // 指向字符串的指针
int cbString // 字符串的文字数
}

那么,在处理 TextOut 函数的内容时,Windows 做了些什么呢?从结果来看,Windows 直接控制了作为硬件的显示器。但 Windows 本身也是软件,由此可见,Windows 应该向 CPU 传递了某种指令,从而通过软件控制了硬件。

Windows 提供的 TextOut 函数 API 可以向窗口和打印机输出字符。C 语言提供的 printf 函数,是用来在命令提示符中显示字符串的函数。使用 printf 函数是无法向打印机输出字符的。

支持硬件输入输出的 IN 指令和 OUT 指令

Windows 控制硬件借助的是输入和输出指令。其中具有代表性的两个输入输出指令就是 IN 和 OUT指令。这些指令也是汇编语言的助记符。

可以通过 IN 和 OUT 指令来实现对数据的读入和输出,如下图所示

也就是说,IN 指令通过指定的端口号输入数据,OUT 指令则是把 CPU 寄存器中存储的数据输出到指定端口号的端口。

那么这个端口号 和 端口是什么呢?你感觉它像不像港口一样?通过标注哪个港口然后进行货物的运送和运出?

下面我们来看一下官方是如何定义端口号和端口的

还记得计算机组成原理中计算机的五大组成部分吗,再来回顾一下:运算器、控制器、存储器、输入设备和输出设备。我们今天不谈前三个,就说说后面两个输入设备和输出设备,这两个与我们本节主题息息相关。

那么问题来了,IO设备如何实现输入和输出的呢?计算机主机中,附带了用来连接显示器以及键盘等外围设备的连接器。 而连接器的内部,都连接有用来交换计算机主机同外围设备之间电流特性的 IC。如果 IC 你不明白是什么的话,可以参考作者的文章 程序员需要了解的硬核知识之内存 进行了解。这些 IC 统称为 IO 控制器。

IO 是 Input/Output 的缩写。显示器、键盘等外围设备都有各自专用的 I/O 控制器。I/O 控制器中有用于临时保存输入输出数据的内存。这个内存就是 端口(port)。端口你就可以把它理解为我们上述说的 港口。IO 控制器内部的内存,也被称为寄存器,不要慌,这个寄存器和内存中的寄存器不一样。CPU 内存的寄存器是用于进行数据运算处理的,而IO中的寄存器是用于临时存储数据的。

在 I/O 设备内部的 IC 中,有多个端口。由于计算机中连接着很多外围设备,因此也就有很多 I/O 控制器。当然也会有多个端口,一个 I/O 控制器可以控制多个设备,不仅仅只能控制一个。各端口之间通过 端口号 进行区分。

端口号也被称为 I/O地址 。IN 指令和 OUT 指令在端口号指定的端口和 CPU 之间进行数据的输入和输出。这跟通过内存的地址来对内存进行读写是一样的道理。

测试输入和输出程序

首先让我们利用 IN 指令和 OUT 指令,来进行一个直接控制硬件的实验。假如试验的目的是让一个计算机内置的喇叭(蜂鸣器)发出声音。蜂鸣器封装在计算机内部,但它也是外围设备的一种。

用汇编语言比较繁琐,这次我们用 C 语言来实现。在大部分 C 语言的处理(编译器的种类)中,只要使用 _asm{ 和 }括起来,就可以在其中记述助记符。也就是说,采用这种方式就能够使用 C 语言和汇编语言混合的源代码。

在 AT 兼容机中,蜂鸣器的默认端口号是 61H ,末尾的 H 表示的是十六进制数的意思。用 IN 指令通过该端口号输入数据,并将数据的低2位设定为 ON,然后再通过该端口号用 OUT 指令输出数据,这时蜂鸣器就会发出声音。同样的方法,将数据的低2位设定为 OFF 并输出后,蜂鸣器就停止工作。

位设定为 ON 指的是将该位设定为1,位设定为 OFF 指的是将该位设定为0 。把位设定为 ON,只需要把想要设定为 ON 的位设定为1,其他位设定为0后进行 OR 运算即可。由于这里需要把低2位置为1,因此就是和 03H 进行 OR 运算。03H 用8为二进制来表示的话是 00000011。由于即便高6位存在着具体意义。和0进行OR运算后也不会发生变化,因而就和 03H 进行 OR 运算。把位设定为 OFF,只需要把想要置 OFF 的位设定为0,其他位设定为1后进行 AND 运算即可。由于这里需要把低2位设定为0,因此就要和 FCH 进行 AND 运算。在源代码中,FCH 是用 0FCH 来记述的。在前面加 0 是汇编语言的规定,表示的是以 A - F 这些字符开头的十六进制数是数值的意思。0FCH 用8位二进制数来表示的话是 11111100。由于即便高6位存在着具体意义,和1进行 AND 运算后也不会产生变化,因而就是同 0FCH 进行 OR 运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
复制代码void main(){

// 计数器
int i;

// 蜂鸣器发声
_asm{
IN EAX, 61H
OR EAX, 03H
OUT 61H, EAX
}

// 等待一段时间
for(i = 0;i < 1000000;i++);

// 蜂鸣器停止发生
_asm{
IN EAX, 61H
AND EAX, 0FCH
OUT 61H, EAX
}
}

我们对上面的代码进行说明,main 是 C 语言程序起始位置的函数。在该函数中,有两个用 _asm{} 围起来的部分,它们中间有一个使用 for 循环的空循环

首先是蜂鸣器发声的部分,通过 IN EAX,61H(助记符不区分大小写)指令,把端口 61H 的数据存储到 CPU 的 EAX 寄存器中。接下来,通过 OR EAX,03H 指令,把 EAX 寄存器的低2位设定成 ON。最后,通过 OUT 61H,EAX 指令,把 EAX 寄存器的内容输出到61端口。使蜂鸣器开始发音。虽然 EAX 寄存器的长度是 32 位,不过由于蜂鸣器端口是8位,所以只需对下8位进行OR运算和AND运算就可以正常工作了。

其次是一个重复100次的空循环,主要是为了在蜂鸣器开始发音和停止发音之间稍微加上一些时间间隔。因为现在计算机器的运行速度非常快,哪怕是 100 万次循环,也几乎是瞬时间完成的。

然后是用来控制器蜂鸣器停止发声的部分。首先,通过 IN EAX,61H 指令,把端口 61H 的数据存储到 CPU 的 EAX 寄存器中。接下来,通过 AND EAX,0FCH 指令,把 EAX 寄存器的低2位设定为 OFF。最后,通过 OUT 61H,EAX 指令,把寄存器的 EAX 内容输出到61号端口,使蜂鸣器停止发音。

外围设备的中断请求

IRQ(Interrupt Request) 代表的就是中断请求。IRQ 用来暂停当前正在运行的程序,并跳转到其他程序运行的必要机制。该机制被称为 处理中断。中断处理在硬件控制中担当着重要的角色。因为如果没有中断处理,就有可能无法顺畅进行处理的情况。

从中断处理开始到请求中断的程序(中断处理程序)运行结束之前,被中断的程序(主程序)的处理是停止的。这种情况就类似于在处理文档的过程中有电话打进来,电话就相当于是中断处理。假如没有中断处理的发生,就必须等到文档处理完成后才能够接听电话。由此可见,中断处理有着巨大的价值,就像是接听完电话后会返回原来的文档作业一样,中断程序处理完成后,也会返回到主程序中继续。

实施中断请求的是连接外围设备的 I/O 控制器,负责实施中断处理的是 CPU,外围设备的中断请求会使用不同于 I/O 端口的其他编号,该编号称为中断编号。在控制面板中查看软盘驱动器的属性时,IRQ处现实的数值是 06,表示的就是用06号来识别软盘驱动器发出的请求。还有就是操作系统以及 BIOS 则会提供响应中断编号的中断处理程序。

BIOS(Basic Input Output System): 位于计算机主板或者扩张卡上内置的 ROM 中,里面记录了用来控制外围设备的程序和数据。

假如有多个外围设备进行中断请求的话, CPU 需要做出选择进行处理,为此,我们可以在 I/O 控制器和 CPU 中间加入名为中断控制器的 IC 来进行缓冲。中断控制器会把从多个外围设备发出的中断请求有序的传递给 CPU。中断控制器的功能相当于就是缓冲。下面是中断控制器功能的示意图

CPU 在接受到中断请求后,会把当前正在运行的任务中断,并切换到中断处理程序。中断处理程序的第一步处理,就是把 CPU 所有寄存器的数值保存到内存的栈中。在中断处理程序中完成外围设备的输入和输出后,把栈中保存的数值还原到 CPU 寄存器中,然后再继续进行对主程序的处理。

假如 CPU 寄存器数值还没有还原的话,就会影响到主程序的运行,甚至还有可能会使程序意外停止或发生运行时异常。这是因为主程序在运行过程中,会用到 CPU 寄存器进行处理,这时候如果突然插入其他程序的运行结果,此时 CPU 必然会受到影响。所以,在处理完中断请求后,各个寄存器的值必须要还原。只要寄存器的值保持不变,主程序就可以像没有发生过任何事情一样继续处理。

用中断来实现实时处理

中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

在程序的运行过程中,几乎无时无刻都会发生中断,其原因就是为了实时处理外部输入的数据,虽然程序也可以在不会中断的基础上处理外部数据,但是那种情况下,主程序就会频繁的检查外围设备是否会有数据输入。由于外围设备会有很多个,因此有必要按照顺序来调查。按照顺序检查多个外围设备的状态称为 轮询。对于计算机来说,这种采用轮询的方式不是很合理,如果你正在检查是否有鼠标输入,这时候发生了键盘输入该如何处理呢?结果必定会导致文字的实时处理效率。所以即时的中断能够提高程序的运行效率。

上面只是中断的一种好处,下面汇总一下利用中断能够带来的正面影响

  • 提高计算机系统效率。计算机系统中处理机的工作速度远高于外围设备的工作速度。通过中断可以协调它们之间的工作。当外围设备需要与处理机交换信息时,由外围设备向处理机发出中断请求,处理机及时响应并作相应处理。不交换信息时,处理机和外围设备处于各自独立的并行工作状态。
  • 维持系统可靠正常工作。现代计算机中,程序员不能直接干预和操纵机器,必须通过中断系统向操作系统发出请求,由操作系统来实现人为干预。主存储器中往往有多道程序和各自的存储空间。在程序运行过程中,如出现越界访问,有可能引起程序混乱或相互破坏信息。为避免这类事件的发生,由存储管理部件进行监测,一旦发生越界访问,向处理机发出中断请求,处理机立即采取保护措施。
  • 满足实时处理要求。在实时系统中,各种监测和控制装置随机地向处理机发出中断请求,处理机随时响应并进行处理。
  • 提供故障现场处理手段。处理机中设有各种故障检测和错误诊断的部件,一旦发现故障或错误,立即发出中断请求,进行故障现场记录和隔离,为进一步处理提供必要的依据。

利用 DMA 实现短时间内大量数据传输

上面我们介绍了 I/O 处理和中断的关系,下面我们来介绍一下另外一个机制,这个机制就是 DMA(Direct Memory Access)。DMA 是指在不通过 CPU 的情况下,外围设备直接和主存进行数据传输。磁盘等硬件设备都用到了 DMA 机制,通过 DMA,大量数据可以在短时间内实现传输,之所以这么快,是因为 CPU 作为中介的时间被节省了,下面是 DMA 的传输过程

I/O 端口号、IRQ、DMA 通道可以说是识别外围设备的3点组合。不过,IRQ、DMA 通道并不是所有外围设备都具备的。计算机主机通过软件控制硬件时所需要的信息的最低限,是外围设备的 I/O 端口号。IRQ 只对需要中断处理的外围设备来说是必须的,DMA 通道则只对需要 DMA 机制的外围设备来说是必须的。假如多个外围设备都设定成相同的端口号、IRQ 和 DMA 通道的话,计算机就无法正常工作,会提示 设备冲突。

文字和图片的显示机制

你知道文字和图片是如何显示出来的吗?事实上,如果用一句话来简单的概括一下该机制,那就是显示器中显示的信息一直存储在某内存中。该内存称为VRAM(Video RAM)。在程序中,只要往 VRAM 中写入数据,该数据就会在显示器中显示出来。实现该功能的程序,是由操作系统或者 BIOS 提供,并借助中断来进行处理。

在 MS-DOS 时代,对于大部分计算机来说,VRAM 都是主内存的一部分。在现代计算机中,显卡等专用硬件中一般都配置有与主内存相独立的 VRAM 和 GPU(Graphics Processing Unit),也叫做图形处理器或者图形芯片。这是因为,对经常描绘图形的 windows 来说,数百兆的 VRAM 都是必需的。

用软件来控制硬件听起来好像很难,但实际上只是利用输入输出指令同外围设备进行输入输出而已。中断处理是根据需要来使用的功能选项。DMA 则直接交给对应的外围设备即可。

虽然计算机领域新技术在不断涌现,但是计算机所能处理的事情,始终只是对输入的数据进行运算,并把结果输出,这一点是永远不会发生变化的。

文章参考:

《程序是怎样跑起来的》

baike.baidu.com/item/中断控制器/…

baike.baidu.com/item/中断/393…

关注公众号后台回复 191106 即可获得《程序是怎样跑起来的》电子书

本文转载自: 掘金

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

聊聊java中那些各式各样的queue 1:概述 2:各种常

发表于 2019-12-10

1:概述

队列真的是一个非常nice的数据结构,有序,规则,可以给与缓冲,就像人们心中那种秩序社会一样,那么这期小威哥就来粗浅的聊聊java中那些常见的queue。


2:各种常见queue对比

2.1:对比表格

队列 数据结构 边界 并发特征 特点
ArrayBlockingQueue 数组 有界(因为是数组) 阻塞 简单,数组结构
LinkedBlockingQueue 链表 可以指定大小(默认为MaxInt) 阻塞 链表
ConcurrentLinkedQueue 链表 无界 cas无锁 无锁,多消费者
DelayQueue 堆 无界 阻塞 实现延时效果

2.2:有界无界,并发性

  • 有界无界性实质就是指一个队列是否支持无限制的塞东西,这就和数据结构有关了,比如名字带array的基本就是个数组,数组肯定是要指定它的大小的,都是有边界的。而带有link这种一看就知道是链表,那么节点可以无限挂(只要内存够),不过像LinkedBlockingQueue还是会指定一个最大挂载量的防止内存boom!!
  • 这些队列容器的话其实都是支持并发的,只是效率高低问题,带bloking的顾名思义就是阻塞了,而带concurrent的队列就是使用cas乐观锁去实现无锁竞争了。

2.3:使用场景选择

  • 可以预估队列有界,可以选择ArrayBlockingQueue
  • 单生产者,单消费者 用 LinkedBlockingqueue
  • 多生产者,单消费者 用 LinkedBlockingqueue
  • 单生产者 ,多消费者 用 ConcurrentLinkedQueue
  • 多生产者 ,多消费者 用 ConcurrentLinkedQueue
  • 如果是有延时效果,可以选 DelayQueue

3:ConcurrentLinkedQueue的一个坑

ConcurrentLinkedQuene的size方法请千万不要使用!!!!最好用isEmpty来代替,因为size方法会去遍历链表节点来确定size

源码如下:

4:Disruptor环形队列

Disruptor队列是一个次世代的环形jvm队列,效率非常高,被log4j2所使用,关于这点可以看我的另一篇文章Disruptor队列,这里就不再赘述了。

5:分布式消息队列

当然,我们现在的很多项目或者应用都是大规模的使用了分布式的服务,不论redis,dubbo,springcloud,等等。所以很多jvm的内存队列其实满足不了我们的需求,这个时候我们就需要使用分布式的消息队列。

这里我推荐使用三个分布式消息队列

  • redis : 大多数人以为redis只是一个缓存kv数据库,其实redis可以使用监听的功能实现一个非常轻量级的消息队列。

但是redis实现的消息队列无法实现持久化,所以万一断电就丢了,所以建议如果传输一些无关紧要的埋点啊什么统计数据啊可以使用一下。

  • rocketmq:阿里出品的非常全面的消息队列,api也很友好,推荐使用,记得需要关注业务幂等哦!!
  • kafka:划时代的消息队列,引领了目前的消息队列潮流,超高的吞吐量,很适合广告,流式处理啊,日志记录啊等等高吞吐场景。但是如果是金融等高可靠性的场景还是使用rocketmq并且同步刷盘!

6:总结

队列真的是一种万金油,简单易用而且使用场景丰富,可延时,可削峰,可异步,融合编程的很多经典又精髓的感念,是开发过程中的利器!大家要多多合理使用!

本文转载自: 掘金

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

Redis配置文件详解

发表于 2019-12-10

title: Redis配置文件详解
date: 2019-11-11 17:50:50
tags:

  • Redis

启动方式

1
2
复制代码 默认情况下,redis不是在后台运行的,如果需要在后台运行,把该项的值更改为yes
daemonize no

pid文件路径

1
2
复制代码当redis在后台运行的时候,Redis默认会把pid文件放在/var/run/redis.pid,你可以配置到其他地址。 当运行多个redis服务时,需要指定不同的pid文件和端口 
pidfile /var/run/redis.pid

指定接收地址

1
2
复制代码指定redis只接收来自于该IP地址的请求,如果不进行设置,那么将处理所有请求,
> bind 127.0.0.1

超时时间

1
复制代码> timeout 0

数据库数

1
复制代码> databases 16

保存数据到磁盘,格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码#
# save <seconds> <changes>
#
# 指出在多长时间内,有多少次更新操作,就将数据同步到数据文件rdb。
# 相当于条件触发抓取快照,这个可以多个条件配合
#
# 比如默认配置文件中的设置,就设置了三个条件
#
# save 900 1 900秒内至少有1个key被改变
# save 300 10 300秒内至少有300个key被改变
# save 60 10000 60秒内至少有10000个key被改变

save 900 1
save 300 10
save 60 10000

最大连接数

1
2
3
4
5
复制代码# 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,
# 如果设置 maxclients 0,表示不作限制。
# 当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
#
# maxclients 128

指定Redis最大内存限制

1
2
3
4
5
6
7
8
9
10
复制代码# 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key
# Redis同时也会移除空的list对象
#
# 当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作
#
# 注意:Redis新的vm机制,会把Key存放内存,Value会存放在swap区
#
# maxmemory的设置比较适合于把redis当作于类似memcached的缓存来使用,而不适合当做一个真实的DB。
# 当把Redis当做一个真实的数据库使用的时候,内存使用将是一个很大的开销
# maxmemory <bytes>

当内存达到最大值的时候Redis会选择删除哪些数据?有五种方式可供选择

1
2
3
4
5
6
7
8
复制代码# volatile-lru -> 利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )
# allkeys-lru -> 利用LRU算法移除任何key
# volatile-random -> 移除设置过过期时间的随机key
# allkeys->random -> remove a random key, any key
# volatile-ttl -> 移除即将过期的key(minor TTL)
# noeviction -> 不移除任何可以,只是返回一个写错误
#
# 注意:对于上面的策略,如果没有合适的key可以移除,当写的时候Redis会返回一个错误

密码

1
2
3
4
5
6
7
8
9
复制代码# 设置客户端连接后进行任何其他指定前需要使用的密码。
# 警告:因为redis速度相当快,所以在一台比较好的服务器下,一个外部的用户可以在一秒钟进行150K次的密码尝试,这意味着你需要指定非常非常强大的密码来防止暴力破解
#
# requirepass foobared

# 当master服务设置了密码保护时(用requirepass制定的密码)
# slav服务连接master的密码
#
# masterauth <master-password>

本文转载自: 掘金

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

Spring Boot2 系列教程(三十)Spring Bo

发表于 2019-12-10

用惯了 Redis ,很多人已经忘记了还有另一个缓存方案 Ehcache ,是的,在 Redis 一统江湖的时代,Ehcache 渐渐有点没落了,不过,我们还是有必要了解下 Ehcache ,在有的场景下,我们还是会用到 Ehcache。

今天松哥就来和大家聊聊 Spring Boot 中使用 Ehcache 的情况。相信看完本文,大家对于[Spring Boot2 系列教程(二十六)Spring Boot 整合 Redis]一文中的第二种方案会有更加深刻的理解。

Ehcache 也是 Java 领域比较优秀的缓存方案之一,Ehcache 这个缓存的名字很有意思,正着念反着念,都是 Ehcache,Spring Boot 中对此也提供了很好的支持,这个支持主要是通过 Spring Cache 来实现的。

Spring Cache 可以整合 Redis,当然也可以整合 Ehcache,两种缓存方案的整合还是比较相似,主要是配置的差异,具体的用法是一模一样的,就类似于 JDBC 和 数据库驱动的关系一样。前面配置完成后,后面具体使用的 API 都是一样的。

和 Spring Cache + Redis 相比,Spring Cache + Ehcache 主要是配置有所差异,具体的用法是一模一样的。我们来看下使用步骤。

项目创建

首先,来创建一个 Spring Boot 项目,引入 Cache 依赖:

工程创建完成后,引入 Ehcache 的依赖,Ehcache 目前有两个版本:

这里采用第二个,在 pom.xml 文件中,引入 Ehcache 依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
</dependencies>

添加 Ehcache 配置

在 resources 目录下,添加 ehcache 的配置文件 ehcache.xml ,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码<ehcache>
<diskStore path="java.io.tmpdir/shiro-spring-sample"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<cache name="user"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
</ehcache>

配置含义:

  1. name:缓存名称。
  2. maxElementsInMemory:缓存最大个数。
  3. eternal:对象是否永久有效,一但设置了,timeout将不起作用。
  4. timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
  5. timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
  6. overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
  7. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
  8. maxElementsOnDisk:硬盘最大缓存个数。
  9. diskPersistent:是否缓存虚拟机重启期数据。
  10. diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
  11. memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
  12. clearOnFlush:内存数量最大时是否清除。
  13. diskStore 则表示临时缓存的硬盘目录。

注意

默认情况下,这个文件名是固定的,必须叫 ehcache.xml ,如果一定要换一个名字,那么需要在 application.properties 中明确指定配置文件名,配置方式如下:

1
复制代码spring.cache.ehcache.config=classpath:aaa.xml

开启缓存

开启缓存的方式,也和 Redis 中一样,如下添加 @EnableCaching 依赖即可:

1
2
3
4
5
6
7
复制代码@SpringBootApplication
@EnableCaching
public class EhcacheApplication {
public static void main(String[] args) {
SpringApplication.run(EhcacheApplication.class, args);
}
}

其实到这一步,Ehcache 就算配置完成了,接下来的用法,和松哥之前讲 Redis 的文章一模一样。不过这里松哥还是带大家使用下。

使用缓存

这里主要向小伙伴们介绍缓存中几个核心的注解使用。

@CacheConfig

这个注解在类上使用,用来描述该类中所有方法使用的缓存名称,当然也可以不使用该注解,直接在具体的缓存注解上配置名称,示例代码如下:

1
2
3
4
复制代码@Service
@CacheConfig(cacheNames = "user")
public class UserService {
}

@Cacheable

这个注解一般加在查询方法上,表示将一个方法的返回值缓存起来,默认情况下,缓存的 key 就是方法的参数,缓存的 value 就是方法的返回值。示例代码如下:

1
2
3
4
5
复制代码@Cacheable(key = "#id")
public User getUserById(Integer id,String username) {
System.out.println("getUserById");
return getUserFromDBById(id);
}

当有多个参数时,默认就使用多个参数来做 key ,如果只需要其中某一个参数做 key ,则可以在 @Cacheable 注解中,通过 key 属性来指定 key ,如上代码就表示只使用 id 作为缓存的 key ,如果对 key 有复杂的要求,可以自定义 keyGenerator 。当然,Spring Cache 中提供了root对象,可以在不定义 keyGenerator 的情况下实现一些复杂的效果,root 对象有如下属性:

也可以通过 keyGenerator 自定义 key ,方式如下:

1
2
3
4
5
6
7
复制代码@Component
public class MyKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+Arrays.toString(params);
}
}

然后在方法上使用该 keyGenerator :

1
2
3
4
5
6
7
8
复制代码@Cacheable(keyGenerator = "myKeyGenerator")
public User getUserById(Long id) {
User user = new User();
user.setId(id);
user.setUsername("lisi");
System.out.println(user);
return user;
}

@CachePut

这个注解一般加在更新方法上,当数据库中的数据更新后,缓存中的数据也要跟着更新,使用该注解,可以将方法的返回值自动更新到已经存在的 key 上,示例代码如下:

1
2
3
4
复制代码@CachePut(key = "#user.id")
public User updateUserById(User user) {
return user;
}

@CacheEvict

这个注解一般加在删除方法上,当数据库中的数据删除后,相关的缓存数据也要自动清除,该注解在使用的时候也可以配置按照某种条件删除( condition 属性)或者或者配置清除所有缓存( allEntries 属性),示例代码如下:

1
2
3
4
复制代码@CacheEvict()
public void deleteUserById(Integer id) {
//在这里执行删除操作, 删除是去数据库中删除
}

总结

本文主要向大家了 Spring Boot 整合 Ehcache 的用法,其实说白了还是 Spring Cache 的用法。相信读完本文,大家对于 Redis + Spring Cache 的用法会有更深的认识。

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

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

扫码关注松哥,公众号后台回复 2TB,获取松哥独家 超2TB 免费 Java 学习干货

本文转载自: 掘金

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

硬核! 逛了4年Github ,一口气把我收藏的 Java

发表于 2019-12-09

好像忘记贴项目地址了:github.com/Snailclimb/…

Awsome Java

Great Java project on Github(Github 上非常棒的 Java 开源项目).

English Version

大家都知道 Github 是一个程序员福地,这里有各种厉害的开源框架、软件或者教程。这些东西对于我们学习和进步有着莫大的进步,所以我有了这个将 Github 上非常棒的 Java 开源项目整理下来的想法。我会按照几个维度对项目进行分类,以便大家查阅。当然,如果你觉得不错的话,欢迎给本项目点个 Star。我会用我的业余时间持续完善这份名单,谢谢🙏。

欢迎大家推荐自己觉得不错的 Java 项目,下面项目的排序很大程度是根据当前项目的 Star 数量。

教程

Java

  1. JavaGuide :【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
  2. CS-Notes :技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
  3. advanced-java :互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。
  4. architect-awesome :后端架构师技术图谱。
  5. toBeTopJavaer :Java工程师成神之路 。
  6. tutorials:该项目是一系列小而专注的教程 - 每个教程都涵盖 Java 生态系统中单一且定义明确的开发领域。 当然,它们的重点是 Spring Framework - Spring,Spring Boot 和 Spring Securiyt。 除了 Spring 之外,还有以下技术:核心 Java,Jackson,HttpClient,Guava。
  7. JCSprout :处于萌芽阶段的Java核心知识库。
  8. JavaFamily :【互联网一线大厂面试+学习指南】进阶知识完全扫盲。
  9. JGrowing :Java is Growing up but not only Java。Java成长路线,但学到不仅仅是Java。

数据结构/算法

  1. LeetCodeAnimation :Demonstrate all the questions on LeetCode in the form of animation.(用动画的形式呈现解LeetCode题目的思路)。
  2. TheAlgorithms-Java :All Algorithms implemented in Java。

SpringBoot

  1. SpringAll :循序渐进,学习Spring Boot、Spring Boot & Shiro、Spring Cloud、Spring Security & Spring Security OAuth2,博客Spring系列源码。
  2. springboot-learning-example :Spring Boot 实践学习案例,是 Spring Boot 初学者及核心技术巩固的最佳实践。

SpringCloud

  1. SpringCloudLearning : 《史上最简单的Spring Cloud教程源码》。
  2. SpringCloud :基于SpringCloud2.1的微服务开发脚手架,整合了spring-security-oauth2、nacos、feign、sentinel、springcloud-gateway等。服务治理方面引入elasticsearch、skywalking、springboot-admin、zipkin等,让项目开发快速进入业务开发,而不需过多时间花费在架构搭建上。

大数据

  1. BigData-Notes :大数据入门指南 ⭐️。
  2. flink-learning :含 Flink 入门、概念、原理、实战、性能调优、源码解析等内容。

设计模式

  1. java-design-patterns : Design patterns implemented in Java。

框架

  1. spring-boot :Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,内置 web 服务器让你可以像运行普通 Java 程序一样运行项目。另外,大部分Spring Boot项目只需要少量的配置即可,这有别于 Spring 的重配置。
  2. flink :Apache Flink 是一个框架和分布式处理引擎,用于在无边界和有边界数据流上进行有状态的计算。Flink 能在所有常见集群环境中运行,并能以内存速度和任意规模进行计算。
  3. Sentinel :A lightweight powerful flow control component enabling reliability and monitoring for microservices. (轻量级的流量控制、熔断降级 Java 库)。
  4. dubbo :Apache Dubbo是一个基于Java的高性能开源RPC框架。
  5. spring-cloud-kubernetes : Kubernetes 集成 Spring Cloud Discovery Client, Configuration, etc…。
  6. seata : Seata 是一种易于使用,高性能,基于 Java 的开源分布式事务解决方案。
  7. skywalking : 针对分布式系统的应用性能监控,尤其是针对微服务、云原生和面向容器的分布式系统架构。

软件/系统

开发

  1. elasticsearch:开源,分布式,RESTful搜索引擎。
  2. zipkin :Zipkin是一个分布式跟踪系统。它有助于收集解决服务体系结构中的延迟问题所需的时序数据。功能包括该数据的收集和查找。
  3. apollo :Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
  4. canal :阿里巴巴 MySQL binlog 增量订阅&消费组件。
  5. DataX :DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL、Oracle、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、DRDS 等各种异构数据源之间高效的数据同步功能。
  6. cat : CAT 作为服务端项目基础组件,提供了 Java, C/C++, Node.js, Python, Go 等多语言客户端,已经在美团点评的基础架构中间件框架(MVC框架,RPC框架,数据库框架,缓存框架等,消息队列,配置系统等)深度集成,为美团点评各业务线提供系统丰富的性能指标、健康状况、实时告警等。
  7. server : 野火IM是一套跨平台、核心功能开源的即时通讯解决方案。
  8. EasyScheduler : Easy Scheduler是一个分布式工作流任务调度系统,主要解决“复杂任务依赖但无法直接监控任务健康状态”的问题。Easy Scheduler以DAG方式组装任务,可以实时监控任务的运行状态。同时,它支持重试,重新运行等操作… 。

其他

  1. halo :Halo 可能是最好的 Java 博客系统。

实战

  1. mall :mall 项目是一套电商系统,包括前台商城系统及后台管理系统,基于 SpringBoot+MyBatis 实现。
  2. mall-swarm : mall-swarm是一套微服务商城系统,采用了 Spring Cloud Greenwich、Spring Boot 2、MyBatis、Docker、Elasticsearch等核心技术,同时提供了基于Vue的管理后台方便快速搭建系统。
  3. litemall : 又一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。
  4. vhr :微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。
  5. FEBS-Shiro :Spring Boot 2.1.3,Shiro1.4.0 & Layui 2.5.4 权限管理系统。

工具

  1. guava :Guava 是一组核心库,其中包括新的集合类型(例如multimap 和 multiset),不可变集合,图形库以及用于并发、I / O、哈希、原始类型、字符串等的实用程序!
  2. p3c :Alibaba Java Coding Guidelines pmd implements and IDE plugin。Eclipse 和 IDEA 上都有该插件,推荐使用!
  3. arthas : Arthas 是Alibaba开源的Java诊断工具。
  4. hutool : Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。
  5. thingsboard :开源物联网平台 - 设备管理,数据收集,处理和可视化。

开源项目推荐

作者的其他开源项目推荐:

  1. JavaGuide:【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
  2. springboot-guide : 适合新手入门以及有经验的开发人员查阅的 Spring Boot 教程(业余时间维护中,欢迎一起维护)。
  3. programmer-advancement : 我觉得技术人员应该有的一些好习惯!
  4. spring-security-jwt-guide :从零入门 !Spring Security With JWT(含权限验证)后端部分代码。

公众号

我的公众号

安利一下阿里云双 12 的活动,1 核 2g 只要 89 一年,薅波羊毛,感觉甚爽,不过最低的优惠都是新人才能享有的,我是用我女朋友的账号买的,没有女朋友的,emm…..,可以考虑一下亲人的。

本文转载自: 掘金

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

基于JDK18的JVM 内存结构【JVM篇三】

发表于 2019-12-09

在我的上一篇文章别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】中,相信大家已经对java类加载机制有一个比较全面的理解了,那么类加载之后,字节码数据在 Java 虚拟机内存中是如何存放的 ?Java 虚拟机在为类实例或成员变量分配内存是如何分配的 ?是的,这两个问题就涉及到了JVM 内存结构的知识了,那么这篇文章将进行解答。

@[toc]

1、内存结构还是运行时数据区?

要解答本篇上面的这些问题,我们首先需要了解一下 Java 虚拟机的内存结构。

从某一角度来说,Java 虚拟机的内存结构 == 运行时数据区,在《Java 虚拟机规范》中用的是【运行时数据区】术语的,并没有内存结构这么一说法。内存结构只是听着更加贴切,更加形象,因此知道内存结构就是运行时数据区的意思就好了!也没必要钻牛角尖纠结这个问题~

2、运行时数据区

JVM被分为三个主要的子系统:类加载器子系统、运行时数据区和执行引擎 。而今天的这篇文章主要讲解其中的运行时数据区(Runtime Data Areas)

在这里插入图片描述

在 Java 虚拟机规范中,定义了五种运行时数据区,分别是 Java 堆、方法区、虚拟机栈、本地方法区、程序计数器 !
顺道提一句运行时常量池也会进入方法区,也就是说方法区中就已经包括了常量池。

特别注意其中Java 堆和方法区是 线程共享的。其他都是 线程私有的。

在这里插入图片描述

3、线程共享:Java堆、方法区

我们首先来了解了解一下线程共享的Java堆和方法区!

3.1、Java堆

Java 堆是所有线程共享的,它在虚拟机启动时就会被创建

Java 堆是内存空间占据的最大一块区域了,Java 堆是用来存放对象实例及数组,也就是说我们代码中通过 new 关键字 new 出来的对象都存放在这里。所以这里也就成为了垃圾回收器的主要活动营地了,于是它就有了一个别名叫做 GC 堆,并且单个 JVM 进程有且仅有一个 Java 堆。根据垃圾回收器的规则,我们可以对 Java 堆进行进一步的划分,具体 Java 堆内存结构如下图所示:

在这里插入图片描述

从上图可以看出Java 堆并不是单纯的一整块区域,实际上java堆是根据对象存活时间的不同,Java 堆还被分为年轻代、老年代两个区域,年轻代还被进一步划分为 Eden 区、From Survivor 0、To Survivor 1 区。并且默认的虚拟机配置比例是Eden:from :to = 8:1:1 。简单来说就是:

Java堆 = 老年代 + 新生代

新生代 = Eden + S0 + S1

默认Eden:from :to = 8:1:1

仔细看过上面的 Java 堆结构图童鞋可能会发现了-Xms和-Xmn的字样,是的这个正是控制堆的JVM的参数,实际上我们是可以通过JVM参数动态控制 Java 堆中的各空间大小的,关于JVM的参数是有很多的,但是常用的也就那么几个,不多的,用的多了都会很容易记住的,下面我们来讲讲关于堆的JVM常见的参数:

-Xms: 堆容量初始大小(堆包括新生代和老年代)。 例如:-Xms 20M
-Xmx: 堆总共(最大)大小。 例如:-Xmx 30M
注意:建议将 -Xms 和 -Xmx 设为相同值,避免每次垃圾回收完成后JVM重新分配内存!
-Xmn: 新生代容量大小。例如:-Xmn 10M
-XX: SurvivorRatio 设置参数Eden、form和to的比例 【比例参数Eden、form和to默认是8:1:1】例如:-XX: SurvivorRatio=8 代表比例8:1:1

虽然没有直接设置老年代的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制:
老年代空间大小 = 堆空间大小 - 年轻代大空间大小

当我们的 Java 堆内有足够的空间去完成实例分配时,并且堆也无法扩展,将会抛出我们常见的OutOfMemoryError 异常,也就是我们常说的OOM 异常

3.2、 JVM 堆内存溢出后,其他线程是否可继续工作?

JVM 堆内存溢出后也就是OOM 异常,网上有一道非常火的面试题:JVM 堆内存溢出后,其他线程是否可继续工作?

实际上这个问题需要具体的场景分析。但是就一般情况下,发生OOM的线程都会终结(除非代码写的太烂),该线程持有的对象占用的heap都会被gc了,释放内存。因为发生OOM之前要进行gc,就算其他线程能够正常工作,也会因为频繁gc产生较大的影响。

也就是说发生OOM的线程一般情况下会死亡,也就是会被终结掉,该线程持有的对象占用的heap都会被gc了,释放内存。因为发生OOM之前要进行gc,就算其他线程能够正常工作,也会因为频繁gc产生较大的影响。

3.3、方法区

拿HotSpot 虚拟机来说,在 JDK1.7的时候,方法区被称作为永久代, 从JDK1.8开始,Metaspace (元空间)也就是我们所谓的方法区!

也就是说,如果你身边的小伙伴还在说着永久代,那绝壁是在扯1.8之前的概念了,1.8之后已经废弃了永久代这个概念!

方法区(Method Area)与上面讲的Java堆一样,都是各个线程共享的,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

Java虚拟机规范中是这样定义方法区的:
它存储了每个类的结构信息,例如运行时常量池、字段、方法数据、构造函数和普通方法的字节码内容,还包括一些在类、实例、接口初始化时用到的特殊方法。

3.4、JDK1.8 之前的方法区

就以HotSpot 虚拟机来说,在 JDK1.8 之前,方法区也被称作为永久代,这个方法区会发生我们常见的 java.lang.OutOfMemoryError: PermGen space 异常,注意是永久代异常信息,我们也可以通过启动参数来控制方法区的大小:

-XX:PermSize 设置方法区最小空间
-XX:MaxPermSize 设置方法区最大空间

在JDK7之前的HotSpot虚拟机中,纳入字符串常量池的字符串被存储在永久代中,因此导致了一系列的性能问题和内存溢出错误。特别突出的例子就是String的intern()方法

3.5、JDK1.8 之后的方法区

JDK8之后就没有永久代这一说法变成叫做元空间(meta space),而且将老年代与元空间剥离。元空间放置于本地的内存中,因此元空间的最大空间就是系统的内存空间了,从而不会再出现像永久代的内存溢出错误了,也不会出现泄漏的数据移到交换区这样的事情。用户可以为元空间设置一个可用空间最大值,不设置默认根据类的元数据大小动态增加元空间的容量。对于一个 64 位的服务器端 JVM 来说,其默认的–XX:MetaspaceSize 值为 21MB。也就是说默认的元空间大小是21MB。

==只要类加载器还存活,其加载的类的元数据也是存活的,不会被回收掉!也就是同生共死==

在这里插入图片描述

3.6、JDK1.8 之后的方法区为何变化如此之大?

做这个改变呢也许主要是基于以下两点原因:

1、由于 永久代(PermGen)内存经常会溢出,引发恼人的 java.lang.OutOfMemoryError: PermGen,因此 JVM 的开发者希望这一块内存可以更灵活地被管理,不要再经常出现这样的 OOM错误。

2、移除 永久代(PermGen)可以促进 HotSpot JVM 与 JRockit VM 的融合,因为 JRockit 没有永久代。

还有需要注意一点的是永久代的移除并不代表自定义的类加载器泄露问题就解决了。因此,你还必须监控你的内存消耗情况,因为一旦发生泄漏,会占用你的大量本地内存,并且还可能导致交换区交换更加糟糕。

4、线程私有:程序计数器、Java 虚拟机栈、本地方法栈

Java 堆以及方法区的数据是共享的,但是有一些部分则是线程私有的。线程私有部分可以分为:程序计数器、Java 虚拟机栈、本地方法栈三大部分。

4.1、Java 虚拟机栈(JVM Stacks)

1、 Java 虚拟机的每一条线程都有自己私有的 Java 虚拟机栈,这个 Java 虚拟机栈跟线程同时创建,所以它跟线程有相同的生命周期。

2、Java 虚拟机栈描述的是 ==Java 方法==执行的内存模型:每一个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,==每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中的入栈到出栈的过程==。

3、局部变量表存放了编译期可知的各种基本数据类型、对象引用和 returnAddress 类型。

1、基本类型:八种基本类型
2、对象引用:reference 类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置。
3、 returnAddress 类型:指向了一条字节码指令的地址。

其中 64 位长度的 long 和 double 类型的数据会占用 2 个局部变量空间(Slot),其余的数据类型只占用 1 个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

4、Java 虚拟机栈既允许被实现成固定的大小,也允许根据计算动态来扩展和收缩,如果采用固定大小的话,每一个线程的 Java 虚拟机栈容量可以在线程创建的时候独立选定。在 Java 虚拟机栈中会发生两种异常,这个在虚拟机规范中有指出:

  • 如果线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量,Java 虚拟机将会抛出 StackOverflowError 异常;也就是栈溢出错误!方法递归调用产生StackOverflowError 异常这种结果。
  • 如果 Java 虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存或者在创建新的线程时没有足够的内存去创建对应的 Java 虚拟机栈,那么虚拟机将会抛出 OutOfMemoryError 异常。也就是OOM内存溢出错误!(线程启动过多)

当然,可以通过参数 -Xss 去调整JVM栈的大小!

4.2、本地方法栈(Native Method Stacks)

==和虚拟栈相似,只不过它服务于Native方法==,线程私有。当 Java 虚拟机使用其他语言(例如 C 语言)来实现指令集解释器时,也会使用到本地方法栈。如果 Java 虚拟机不支持 natvie 方法,并且自己也不依赖传统栈的话,可以无需支持本地方法栈。

与 Java 虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

HotSpot虚拟机直接就把本地方法栈和虚拟机栈合二为一。

4.3、程序计数器

当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有。

需要特别注意的是,程序计数器是唯一一个在Java虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

5、JVM 内存结构总结

在这里插入图片描述

程序计数器:

1、 当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有。
2、程序计数器是唯一一个在Java虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

Java虚拟栈:

1、存放基本数据类型、对象的引用、方法出口等,线程私有。
2、栈容量超过 Java 虚拟机栈的最大容量,会抛出 StackOverflowError 异常;也就是栈溢出错误!方法递归产生
3、如果 Java 虚拟机栈可以动态扩展,无法申请到足够的内存或者在创建新的线程时没有足够的内存去创建对应的 Java 虚拟机栈,会抛出 OutOfMemoryError 异常。也就是OOM内存溢出错误!(线程启动过多)
4、参数 -Xss 调整JVM栈的大小

Native方法栈:

1、和虚拟栈相似,只不过它服务于Native方法,线程私有。
2、HotSpot虚拟机直接就把本地方法栈和虚拟机栈合二为一。

Java堆:

java内存最大的一块,所有对象实例、数组都存放在java堆,GC回收的地方,线程共享。

Java堆 = 老年代 + 新生代

新生代 = Eden + S0 + S1

默认Eden:from :to = 8:1:1

方法区:

1、存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等,回收目标主要是常量池的回收和类型的卸载,各线程共享
2、方法区在JDK1.7的时候叫做永久代,到JDK1.8之后废弃了永久代改为元空间(meta space)

如果本文对你有一点点帮助,那么请点个赞呗,谢谢~

最后,若有不足或者不正之处,欢迎指正批评,感激不尽!如果有疑问欢迎留言,绝对第一时间回复!

欢迎各位关注我的公众号,一起探讨技术,向往技术,追求技术,说好了来了就是盆友喔…

在这里插入图片描述

本文转载自: 掘金

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

抽象思维实践——ddl2plantuml开发记录

发表于 2019-12-08

项目源码地址: github.com/wangyuheng/…

背景

利用plantuml绘制架构评审图时,发现数据库ER图手写字段信息成本太大,需要一个把DB表结构转换为plantuml格式的工具。
搜索了一番,没有发现支持工具,所以准备手撸一个,并记录下设计及编码实现的过程。

需求

一句话需求: 读取数据库表结构,转换为plantuml格式的ER图。

设计

根据需求抽象出下列概念

基础元素(一个输入一个输出)

  1. ER -> plantuml格式er图
  2. db_schema -> 数据库结构,ddl语句是其中一种实现形式

扩展概念

  1. table -> 表结构信息
  2. template -> 模板,定义er图输出格式

操作行为

  1. reader -> 读取数据库结构,识别 table
  2. parser -> 根据 table 和 template 转换为ER图
  3. writer -> 输出ER图文件

整体交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码storage "Context" {
node ddl
node db_schema
node ER
node template
node table
usecase parser
usecase reader
usecase writer
}
ddl -down-|> db_schema
db_schema -down-> reader
reader -> table
table -> parser
parser <-up- template
parser -down-> writer
writer -> ER

design

选型

都是基本的文件及String操作,通过 druid 进行sql解析

编码实现

Reader

读取ddl.sql文件,解析成 table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
复制代码interface Reader {

fun read(dbType: String? = DEFAULT_DB_TYPE): Iterable<Table>

fun extract(dbType: String, sql: String): Table {
...
}
}

class FileReader(private val path: String) : Reader {

override fun read(dbType: String?): Iterable<Table> {
return Files.readAllLines(Paths.get(path))
.filter { !it.startsWith("#") }
.joinToString("")
.split(";")
.filter { it.isNotBlank() }
.map { extract(dbType?: DEFAULT_DB_TYPE, it) }
.toList()
}

}

Writer

template通过resource文件管理,接收table输出plantuml格式ER图

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
复制代码interface Writer {

fun write(tables: Iterable<Table>)

fun parse(tables: Iterable<Table>): String {
val template = Thread.currentThread().contextClassLoader.getResource("dot.template")!!.readText()

val content = tables.joinToString("") { table ->
val columns = table.columnList.joinToString("\n") { "${it.notNullNameWrapper()} ${it.type} ${it.defaultValue} ${it.comment}" }
"Table(${table.name}, \"${table.name}\\n(${table.comment})\"){ \n $columns + \n } \n"
}

return template.replace("__content__", content)
}

private fun Column.notNullNameWrapper(): String {
return if (this.notNull) {
"not_null(${this.name})"
} else {
this.name
}
}
}

class FileWriter(private val path: String) : Writer {

override fun write(tables: Iterable<Table>) {
Files.write(Paths.get(path), parse(tables).toByteArray())
}

}

Main

1
2
3
4
5
6
7
8
9
10
复制代码fun main(args: Array<String>) {

val inPath = args[0]
val outPath = args[1]
val dbType = args.getOrNull(2)

FileReader(inPath).read(dbType)
.apply { FileWriter(outPath).write(this) }

}

效果

1
复制代码java -jar ddl2plantuml.jar ddl.sql er.puml

程序会读取当前目录下的ddl.sql文件,并转换生成er.puml文件。

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
复制代码@startuml

!define Table(name,desc) class name as "desc" << (T,#FFAAAA) >>
!define primary_key(x) <color:red><b>x</b></color>
!define unique(x) <color:green>x</color>
!define not_null(x) <u>x</u>
hide methods
hide stereotypes
Table(table_1, "table_1\n(This is table 1)"){
not_null(id) bigint column_1
not_null(prod_name) varchar column_2
not_null(prod_type) tinyint '0' column_3 0:活期 1:定期
not_null(start_time) time 停止交易开始时间
not_null(end_time) time 停止交易结束时间
not_null(online_type) tinyint '0' 0:上线 1:未上线
not_null(prod_info) varchar '' 产品介绍
not_null(over_limit) tinyint '0' 超额限制 0:限制 1:不限制
not_null(created_time) datetime CURRENT_TIMESTAMP
not_null(updated_time) datetime CURRENT_TIMESTAMP
}

Table(table_2, "table_2\n(This is table 2)"){
not_null(id) bigint
not_null(user_id) bigint 用户id
not_null(user_name) varchar 用户名称
not_null(prod_id) bigint 产品id
interest_date dateNULL计息日期
not_null(created_time) datetime CURRENT_TIMESTAMP 创建时间
not_null(updated_time) datetime CURRENT_TIMESTAMP 更新时间
}


Table(table_3, "table_3\n(This is table 3)"){
not_null(id) bigint
not_null(user_id) bigint 用户id
not_null(user_name) varchar 用户名称
not_null(prod_id) bigint 产品id
interest_date dateNULL计息日期
not_null(created_time) datetime CURRENT_TIMESTAMP 创建时间
not_null(updated_time) datetime CURRENT_TIMESTAMP 更新时间
}

@enduml

result

本文转载自: 掘金

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

1…842843844…956

开发者博客

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