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

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


  • 首页

  • 归档

  • 搜索

「论道架构师」拒绝无脑搬砖,从分库分表开始 「论道架构师」拒

发表于 2021-07-07

「论道架构师」拒绝无脑搬砖,从分库分表开始

⚠️本文为掘金社区首发签约文章,未获授权禁止转载

事件起因

6月初我负责的商家数据某功能由于业务原因导致数据量不断上涨,当时使用的MySQL单表已经难以提供高效的查询,因此基于商家维度(商家主键ID)对它进行了分表,问题得以解决。

6月中旬我沉浸在无止尽的需求之中,边做边发出感慨:进大厂又能怎么样呢,还不是CRUD,面试造火箭罢了。

此时,架构师笑了笑,于是乎有了本篇文章。

我的方式:取模水平分割

亮架构:说一下你上次分表的实现方案吧,Kerwin。

我:我是参照其他系统的方式进行的,利用商家主键Id(商家系统自增的Long型主键)对100取模,由此计算出它应该落在哪一张表,唯一的变动就是这个环节以及数据迁移了。

大概思路如下:

1
2
3
java复制代码// 业务层代码...
String tableName = "orgcode_datas_" + orgcode % 100;
String insertUserSql = "INSERT INTO " + tableName + " VALUES ('" ... "');";

亮架构:所以你采用的就是最简单的方式,而且还把取模逻辑耦合在了业务层吗?

我:是啊,其他系统也都是这么做的,这样搞最快,最安全。

亮架构:你的系统用的是MyBatis对吧,你稍微调研一下就知道,我们可以借助MyBatis的拦截器实现刚刚业务代码中耦合取模的那部分逻辑,虽然本质上没有高明多少,但让它的数据操作底层变得更加清晰明了了,这不就是一种进步吗?

我(略感羞愧):你说的有道理,回去我研究研究。

亮架构:再来看另一个问题,你说了这次分表涉及到数据迁移,你现在分了100个表,万一以后还不够用怎么办?难道要再全部迁移一遍吗?

我(非常羞愧):啊这个,应该不会不够用吧…

一致性哈希方案

亮架构:Kerwin,你面试的时候有没有被问到过Redis的一致性哈希问题?

我(来劲了):有,Redis中引入了一致性Hash算法。该算法对2^32 取模,将Hash值空间组成虚拟的圆环,整个圆环按顺时针方向组织,每个节点依次为0、1、2…2^32-1,之后将每个服务器进行Hash运算,确定服务器在这个Hash环上的地址,确定了服务器地址后,对数据使用同样的Hash算法,将数据定位到特定的Redis服务器上。如果定位到的地方没有Redis服务器实例,则继续顺时针寻找,找到的第一台服务器即该数据最终的服务器位置。

相关文章:「查缺补漏」巩固你的Redis知识体系(笑)

image-20210704155443807.png

亮架构:Redis的这个知识点你知道,MySQL的你就不知道了?如果我让你把简单取模法更换为一致性哈希方案,你该怎么做?

我(忐忑):如果参考Redis的话,我会这么做:

  1. 从表的命名开始,尽量取名为取模后相对均匀的名称,不以连续后缀结尾了
  2. 我需要找一个碰撞相对较少的哈希算法
  3. 取模的逻辑需要修改为两步,首先是建立数据表落点(即上图中的蓝球)并存储,再计算数据的落点(即红球),然后动态的计算它应该归属到哪一张表即可

亮架构:差不多,命名的部分可以再思考思考,但是你知道你这么做会遇到什么问题吗?提示:和Redis一样的问题。

我(信心满满):这个我知道,可能会出现数据过于集中的问题,这是由于数据和服务器分布不均匀导致的,例如上图中的情况,归属到一号服务器(顺时针第一个蓝球)的概率太大了,这个简单,我们只需要增加虚拟节点,或者从命名上考虑让它尽量平铺,均匀就好啦。

亮架构:孺子可教也,你说说看,这个方式和你刚刚的方式耗费的精力,优劣对比吧。

我(不好意思):从精力上讲,一致性哈希方案多了一步二次计算位置的动作,但是按您说的,如果我们在拦截器中去实现这一步动作的话,整体都是无感知的,只是多了一个方法而已,而从扩展性来讲,那就好多了,无论是增加节点还是减少节点,我们只需要迁移的对应的数据节点即可。

Tips:如下图中,如果我们增加了第四个服务器节点,第四个服务器节点的数据来源只可能是第一个服务器节点

image-20210704162853406.png

Range方式分表

亮架构:那我再考考你,假如让你去订单组,对订单快照或者说订单流水进行分表,你怎么做?

我(思考状):订单流水信息有一个明显的不同点在于它的主键Id是一个真正的无意义自增长整型,刚才提到的方式是可行且通用的,但是针对这种特殊场景,我会采用分段分表的方式,比如 1 - 100万为表1,100万01到200万为表2,以此类推,反正这个表只有插入和按Id查询,至于按订单人查询的功能,其他模块就满足了,不用我考虑。

我(瞬间反应过来):不对啊,那订单流水Id基于什么获取呢?我们都要分表了,难道用自增Id?

亮架构:你能想到这一点还挺好的,现在有很多分布式算法都可以解决这种问题,比如Twitter的分布式自增ID算法(Snowflake)、Redis的incr命令、百度的uid-generator算法等等,很好,你也有一点程序员的样子了,知道反问了。

把上述知识总结抽象,你能得到什么?

亮架构:你刚才提到了很关键的一点,你说你之所以用最简单的水平分割方式去处理,是因为其他系统的代码也都是这么写的?

我:是的,所以我就把它们复制粘贴,改了一下。

亮架构:OK,你看,无论是简单的水平取模方式,还是一致性哈希,亦或是Range方式,它的本质都是:SQL解析 =》SQL重写 =》 路由 =》 数据库执行 =》 结果返回,

不同点在于,几个方案的路由策略不一样而已,你把这些东西抽象成可配置化的解决方案,放到咱们的Lib库中,以后别人再用的时候是不是就没那么麻烦了?

我:是的,既省时间,又省精力,只要咱们都用的是Mybatis和MySQL就行。

亮架构(扶额):你再想想…

我(思考状):对,是我狭隘了,我们刚刚所说的策略根本不用在乎是什么数据库,是什么ORM框架,无论是MongoDB也好,MySQL也罢,它真正的流程就那么几步,我只需要依赖Spring体系,获取到数据库连接信息,然后重写SQL为它分配路由即可。

亮架构(笑了笑,起身走了):孺子可教也,你还觉得每天都是CRUD了吗?明明有一个研究和学习的机会放在你的面前,是你自己选择了最简单的方式,因为你既追求速度,又害怕出问题,所以你用了和前人一样的方式来解决这个问题,工作中很难遇到造火箭的机会,但是造个自行车,哪怕是车轮,也是挺好的吧,认真学习,以后要抓住机会啊。

我看着架构师远去的背影,心中无比的激动,感谢他把本可以摸鱼的时间拿来教导我(手动狗头🤪)。

最后

推荐另一篇具有 “设计” 思想的文章:「奇淫技巧」如何写最少的代码

如果你觉得这篇内容对你有帮助的话:

  1. 当然要点赞支持一下啦~
  2. 另外,可以搜索并关注公众号「是Kerwin啊」,一起在技术的路上走下去吧~ 😋

本文转载自: 掘金

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

广州小公司:你对List了解多少?

发表于 2021-07-07

文章以纯面试的角度去讲解,所以有很多的细节是未铺垫的。

鉴于很多同学反馈没看懂【对线面试官】系列,基础相关的知识我确实写过文章讲解过啦,但有的同学就是不爱去翻。

为了让大家有更好的体验,我把基础文章也找出来(重要的知识点我还整理过电子书,比如说像多线程、集合、Spring这种面试必考的早就已经转成PDF格式啦)

我把这些上传到网盘,你们有需要直接下载就好了。做到这份上了,不会还想白嫖吧?点赞和转发又不用钱。

链接:pan.baidu.com/s/1pQTuKBYs… 密码:3wom

欢迎关注我的微信公众号【Java3y】来聊聊Java面试

【对线面试官】系列 一周两篇持续更新中!

原创不易!!求三连!!

本文转载自: 掘金

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

B 站,Java ,起飞!

发表于 2021-07-07

Hey guys ,这里是 cxuan,欢迎你收看我最新一期的文章。

这是一篇鸽了很久的文章。。。。。。

事情还要从上回说起。。。。。。

我爱 B 站!

这篇文章我汇总了 B 站上计算机基础(操作系统、计算机网络、数据结构和算法、汇编等)学习视频,受到了很多小伙伴的认可和追更。

image-20210707092255679

甚至 CSDN 还有在催我更新的读者朋友

所以这篇文章,不能再拖了,更新起来!!!

Java 基础

  1. Java 基础 :尚硅谷宋红康 www.bilibili.com/video/BV1Qb…

宋红康老师讲课非常有意思,在讲 Java 之前,他会先把一些计算机基础知识带你了解一下,让你学习 Java 的时候不至于雾里看花。讲完 Java 基础后,后面还有项目驱动带你复习一下 Java 基础还有一些 Java 新特性的解读,强烈推荐。

image-20210707092244491

  1. 黑马 Java 基础+就业班+各种项目 idea 版本 www.bilibili.com/video/BV1T7…

使用 idea 作为工具是很多 Java 教程都不具备的,可以看到这个教程是非常新的。这个教程有 561 节,贯穿了 Java 基础、项目、数据库、MySQL、JDBC、数据库连接池,可以说学完这个视频就能够直接上手 Java web 开发了。

image-20210707092237669

  1. 动力节点 Java 零基础教程视频 www.bilibili.com/video/BV1Rx…

动力节点的这门零基础 Java 课程分类很全,几乎涵盖了所有的 Java 基础知识,有 800 多节视频,也足以可见这门零基础视频课的用心程度。

image-20210707092230303

  1. 北京尚学堂高琪(推荐) www.bilibili.com/video/BV1ct…

高琪老师的视频也非常不错,其中还夹杂着多线程和网络编程的知识点,还有一些手写集合类的视频非常有特点,这是其他视频教程所不具备的。

image-20210707092223004

  1. 求知讲堂 Java 基础教程 www.bilibili.com/video/BV1CJ…

这是一位对学生有过深入了解的老师,口碑非常好,而且几乎没有废话,无尿点,非常好的一个 Java 基础教程,有很多小伙伴认为是全网最好的 Java 基础教程

image-20210707092153200

设计模式

  1. 尚硅谷设计模式(图解 + 框架源码剖析) www.bilibili.com/video/BV1G4…

B 站上面可能评价最好的设计模式就是尚硅谷的这个视频了,不得不说,尚硅谷确实为免费视频这部分做出了很多贡献,推动了 Java 行业的进程,这种气度不是一般培训机构能有的,respect!!! 关键是跟我一样,能否免费分享知识,爱了爱了。

image-20210707092140263

  1. 黑马程序员 Java 设计模式详解 www.bilibili.com/video/BV1Np…

除了尚硅谷之外,黑马也在 B 站分享了很多 Java 学习视频,值得称赞,这个设计模式就是从设计模式的原则开始讲起,然后深入各个具体的设计模式,通过图解 + 框架源码 + 实战的方式为你剖析 23 中设计模式,值得一看。

image-20210707092132718

并发

  1. 狂神说 Java,JUC 并发编程最新版 www.bilibili.com/video/BV1B7…

我一直认为 JUC 这块通过自学 + 看书就能够理解的差不多,因为关于并发这块有非常多的书籍可以翻看,关于书籍推荐你可以阅读我的这篇文章 憋了半个月的 PDF:精通 Java。

但是狂神的视频却改变了我的想法,这个 JUC 的视频可以说讲的非常清楚了,而且评论区也能够学到很多东西,给狂神点赞。

image-20210707092121879

  1. 黑马程序员全面深入学习 Java 并发编程 www.bilibili.com/video/BV16J…

我没想到一个并发编程黑马能讲这么多节,整部视频分为了 281 节,每一节能够讲清楚一个小的知识点,可以说是非常细致了。

image-20210707092111488

JVM

尚硅谷 JVM 全套教程 www.bilibili.com/video/BV1PJ…

JVM 的这个部分,其实看 B 站宋红康的这个视频就可以了,一套 JVM 的视频讲了 381 节,这个真的太细致了。B 站真是一个优秀的白嫖圣地,关键是质量还非常高。

image-20210707092100586

关于 JVM 的视频,一个就够了,其余的可以看《深入理解 Java 虚拟机》和《Java 虚拟机规范》,一个视频两本书,JVM 这块拿捏的死死的。

MySQL

  1. MySQL 基础 + 高级篇 www.bilibili.com/video/BV12b…

其实 MySQL 基础这块,最好的学习方法就是实战了,当然也可以根据老师的讲解一步一步实战练习,但是一定要多多练习,MySQL 基础这块我记得我刚开始学习的时候就是无脑写 SQL 语句。。。。。。

image-20210707092050818

  1. 狂神说 MySQL www.bilibili.com/video/BV1NJ…

狂神说的这个 MySQL 不是一个基础的小白实战视频,更多当成 MySQL 进阶视频来看。

image-20210707092041272

Oracle

Oracle 从入门到精通 www.bilibili.com/video/BV1kx…

我相信我的读者朋友们还有一部分是传统企业开发,这种公司一般一般不差钱,用的大多是 Oracle 数据库,所以除了 MySQL,我也把 Oracle 相关视频呈上。

动力节点的这个 Oracle 对于萌新来说讲的还算不错,但是缺点就是有点老了。

image-20210707092009958

Maven

作为 Java 程序员,Maven 你应该再熟悉不过了吧,但是我相信绝大多数同学对于 Maven 只存在于知道是干什么的 + 会用阶段,出现各种问题和错误却不知道如何排查,所以,对于 Maven,你还是要系统学习一下。

动力节点 Maven 2020 最新教程 www.bilibili.com/video/BV1dp…

image-20210707092002177

MyBatis

当讲到 Java 开发框架的时候,我知道这就是狂神说的天下了。

  1. 狂神说 MyBatis www.bilibili.com/video/BV1NE…

狂神说的这个 MyBatis 讲的非常好,用的是 idea ,而且会接触到大量的官网中的核心概念,基本上看这个视频能把官网也撸一遍,简直不要太爽。

image-20210707091952699

  1. 尚硅谷 MyBatis 实战 www.bilibili.com/video/BV1mW…

尚硅谷的这个 MyBatis 是使用的 eclipse,如果用不惯 idea 的同学可以尝试使用 eclipse。

image-20210707091944573

Spring

  1. 狂神说 Spring 5 最新完整版 www.bilibili.com/video/BV1WE…

狂神的视频有个特点就是非常新,这个课程是基于 Spring 5 来讲,而且狂神讲课调理比较清晰,易于接受。还有一点,狂神的评论区非常有特点,有任何疑问基本上都能在评论区翻到,这个很有帮助。

image-20210707091932011

  1. 尚硅谷 Spring 5 最新完整版 www.bilibili.com/video/BV1Vf…

尚硅谷的视频对比狂神说的更有年代感,不过对于知识点的讲解来说,还是非常不错的,这个 Spring 视频信息密度很大,老师基本上没有废话,全程干货。

image-20210707091921768

JSP / Servlet

  1. Java Web 教程 JSP / Servlet www.bilibili.com/video/BV18s…

之前看到有的公众号主说 JSP/Servlet 不用学了,真是惊到我了。。。。。。天下之大,无奇不有。Servlet 是前后端交互的基石,不学 Servlet 你永远不知道前端请求是如何发送到后端的,你也不知道 Web 容器的工作机制是怎样的。至于 JSP,你可以不用当作重点,花时间过一遍就可以了。

image-20210707091912573

  1. 狂神说 Java Web www.bilibili.com/video/BV12J…

狂神的这个 Java Web 也挺好的,学完这个之后,后面的 Spring MVC 会非常 easy

image-20210707091858672

Spring MVC

  1. 动力节点 Spring MVC 教程 www.bilibili.com/video/BV1sk…

Spring MVC 其实就是 Servlet 的一层包装而已,它也是 Spring 的一个模块,有人说 Spring MVC 不用学,有些人说还是要学的,褒贬不一,但是多学点,没啥坏处。

image-20210707091850730

  1. 雷神 Spring、Spring MVC、MyBatis www.bilibili.com/video/BV1d4…

很多人说这个教程是个黑马,也有人说是全网最好的 SSM 框架体系,我信了,大家可以听听。

image-20210707091840334

Spring Boot

  1. 雷丰阳 2021 SpringBoot 2 全套教程 www.bilibili.com/video/BV19K…

基于 SpringBoot 2 的一套教程,内容涉及源码流程分析、系列整合等,非常受用。

image-20210707091833170

  1. 狂神说 SpringBoot www.bilibili.com/video/BV1PE…

狂神说的 SpringBoot 和雷丰阳的差不多,可以选择一个观看学习就行了。

image-20210707091716374

Spring Cloud

Spring Cloud 太火了,分布式这块企业基本上就是 Spring Cloud 和 Dubbo 了,Spring Cloud 的优势就是基于 Spring ,学起来简单易上手。

尚硅谷 Spring Cloud www.bilibili.com/video/BV18E…

尚硅谷又来了,尚硅谷为自学的同学打开了一扇门,让天下没有难学的技术确实不是一句空话。这个视频是基于 H 版本来讲,评论区还有很多踩坑实践,强烈推荐。

image-20210707091705804

Dubbo

Dubbo 是和 Spring Cloud 同样重要的分布式框架,现在由阿里维护,有很多公司也使用了 Dubbo 作为了企业的分布式框架,另外 Dubbo 的面试题问的次数不少。

尚硅谷 Dubbo 教程 www.bilibili.com/video/BV1ns…

image-20210707091656629

Redis

  1. 狂神说 Redis www.bilibili.com/video/BV1S5…

狂神的这个 Redis 非常不错。

image-20210707091642270

  1. 尚硅谷的 Redis 课程 www.bilibili.com/video/BV1Rv…

鉴定完毕,白嫖很香。

image-20210707091629926

Kafka

尚硅谷 Kafka 教程 www.bilibili.com/video/BV1a4…

Kafka 有录制视频课程的 up 主或者培训机构不多,尚硅谷算一个。

image-20210707091601515

ZooKeeper

尚硅谷 Kafka 教程 www.bilibili.com/video/BV1to…

中间件这块,基本上就是尚硅谷的天下了,ZooKeeper 也是推荐尚硅谷的课程

image-20210707091553787

Nginx

  1. 尚硅谷 Nginx 由浅入深 www.bilibili.com/video/BV1zJ…

Nginx 作为负载均衡器,反向代理在企业中的应用也非常广泛,这里推荐一下尚硅谷的 Nginx 视频教程。

image-20210707091544716

  1. 狂神说 Nginx www.bilibili.com/video/BV1F5…

image-20210707091536745

Netty

Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。

  1. 黑马程序员 Netty 教程 www.bilibili.com/video/BV1py…

image-20210707091529054

  1. 尚硅谷韩老师讲的 Netty www.bilibili.com/video/BV1DJ…

image-20210707091521544

Elasticsearch

狂神说 Elasticsearch www.bilibili.com/video/BV17a…

Elasticsearch是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful web 接口,在企业中的应用也非常广泛。这里推荐一下狂神的 Elasticsearch。只要学不死,就往死里学。。。。。。怕了怕了。

image-20210707091512948

Git

现在互联网几乎都用 Git 了,只有传统软件公司还在使用 svn 苦苦挣扎。Git 我认为直接看廖雪峰官网的 Git 教程就很好了,还有 gif 动图解释 www.liaoxuefeng.com/wiki/896043…

视频的话,可以看看狂神老哥的 Git 教程 www.bilibili.com/video/BV1FE…

image-20210707091456836

也可以看看尚硅谷的,尚硅谷的这个是 git 和 github 一起讲授的。 www.bilibili.com/video/BV1pW…

image-20210707091447158

Linux

  1. 兄弟连 Linux www.bilibili.com/video/BV1mW…

兄弟连的这个 Linux 评价很高,质量非常不错,课程划分的很细致

image-20210707091437792

  1. 韩顺平的图解 Linux www.bilibili.com/video/BV1Sv…

针对小白,韩老师讲的通俗易懂,推荐。

image-20210707091426499

Docker

既然说到了 Linux ,就不得不提 Docker 了,打包必备,不管是运维还是开发都要掌握

狂神聊 Docker www.bilibili.com/video/BV1og…

狂神的这个 docker 教程评价很高。

image-20210707091415131

Jenkins

现在大部分企业都会使用 Jenkins + Docker 来持续集成,关于 Jenkins ,推荐你看看

黑马程序员 Jenkins www.bilibili.com/video/BV1kJ…

image-20210707091333808

后记

另外,我自己肝了六本 PDF,全网传播超过10w+ ,微信搜索「程序员cxuan」关注公众号后,在后台回复 cxuan ,领取全部 PDF,这些 PDF 如下

六本 PDF 链接

本文转载自: 掘金

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

SpringBoot Validation参数校验 详解自定

发表于 2021-07-07

前言

Hibernate Validator 是 Bean Validation 的参考实现 。Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint
在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效。

在SpringBoot中可以使用@Validated,注解Hibernate Validator加强版,也可以使用@Valid原来Bean Validation java版本

内置校验注解

Bean Validation 中内置的 constraint

注解 作用
@Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

注解 作用
@Email 被注释的元素必须是电子邮箱地址
@Length(min=, max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=, max=) 被注释的元素必须在合适的范围内
@NotBlank 被注释的字符串的必须非空
@URL(protocol=,host=, port=, regexp=, flags=) 被注释的字符串必须是一个有效的url
@CreditCardNumber 被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性
@ScriptAssert(lang=, script=, alias=) 要有Java Scripting API 即JSR 223(“Scripting for the JavaTM Platform”)的实现
@SafeHtml(whitelistType=,additionalTags=) classpath中要有jsoup包

message支持表达式和EL表达式 ,比如message = “姓名长度限制为{min}到{max} ${1+2}”)

想把错误描述统一写到properties的话,在classpath下面新建ValidationMessages_zh_CN.properties文件(注意value需要转换为unicode编码),然后用{}格式的占位符

hibernate补充的注解中,最后3个不常用,可忽略。
主要区分下@NotNull @NotEmpty @NotBlank 3个注解的区别:

  1. @NotNull 任何对象的value不能为null
  2. @NotEmpty 集合对象的元素不为0,即集合不为空,也可以用于字符串不为null
  3. @NotBlank 只能用于字符串不为null,并且字符串trim()以后length要大于0

分组校验

如果同一个参数,需要在不同场景下应用不同的校验规则,就需要用到分组校验了。比如:新注册用户还没起名字,我们允许name字段为空,但是在更新时候不允许将名字更新为空字符。

分组校验有三个步骤:

  1. 定义一个分组类(或接口)
1
2
java复制代码public interface Update extends Default{
}
  1. 在校验注解上添加groups属性指定分组
1
2
3
4
5
java复制代码public class UserVO {
@NotBlank(message = "name 不能为空",groups = Update.class)
private String name;
// 省略其他代码...
}
  1. Controller方法的@Validated注解添加分组类
1
2
3
4
java复制代码@PostMapping("update")
public ResultInfo update(@Validated({Update.class}) UserVO userVO) {
return new ResultInfo().success(userVO);
}

自定义的Update分组接口继承了Default接口。校验注解(如: @NotBlank)和@validated默认其他注解都属于Default.class分组,这一点在javax.validation.groups.Default注释中有说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码/**
* Default Jakarta Bean Validation group.
* <p>
* Unless a list of groups is explicitly defined:
* <ul>
* <li>constraints belong to the {@code Default} group</li>
* <li>validation applies to the {@code Default} group</li>
* </ul>
* Most structural constraints should belong to the default group.
*
* @author Emmanuel Bernard
*/
public interface Default {
}

在编写Update分组接口时,如果继承了Default,下面两个写法就是等效的:
@Validated({Update.class}),@Validated({Update.class,Default.class})
如果Update不继承Default,@Validated({Update.class})就只会校验属于Update.class分组的参数字段

递归校验

如果 UserVO 类中增加一个 OrderVO 类的属性,而 OrderVO 中的属性也需要校验,就用到递归校验了,只要在相应属性上增加@Valid注解即可实现(对于集合同样适用)

1
2
3
4
5
6
7
java复制代码public class OrderVO {
@NotNull
private Long id;
@NotBlank(message = "itemName 不能为空")
private String itemName;
// 省略其他代码...
}
1
2
3
4
5
6
7
8
java复制代码public class UserVO {
@NotBlank(message = "name 不能为空",groups = Update.class)
private String name;
//需要递归校验的OrderVO
@Valid
private OrderVO orderVO;
// 省略其他代码...
}

自定义校验

validation 为我们提供了这么多特性,几乎可以满足日常开发中绝大多数参数校验场景了。但是,一个好的框架一定是方便扩展的。有了扩展能力,就能应对更多复杂的业务场景,毕竟在开发过程中,唯一不变的就是变化本身。 Validation允许用户自定义校验

实现很简单,分两步:

  1. 自定义校验注解
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
java复制代码package cn.soboys.core.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;


/**
* @author kenx
* @version 1.0
* @date 2021/1/21 20:49
* 日期验证 约束注解类
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {IsDateTimeValidator.class}) // 标明由哪个类执行校验逻辑
public @interface IsDateTime {

// 校验出错时默认返回的消息
String message() default "日期格式错误";
//分组校验
Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

//下面是我自己定义属性
boolean required() default true;

String dateFormat() default "yyyy-MM-dd";


}

注意:message用于显示错误信息这个字段是必须的,groups和payload也是必须的
@Constraint(validatedBy = { HandsomeBoyValidator.class})用来指定处理这个注解逻辑的类

  1. 编写校验者类
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
java复制代码package cn.soboys.core.validator;

import cn.hutool.core.util.StrUtil;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
* @author kenx
* @version 1.0
* @date 2021/1/21 20:51
* 日期验证器
*/
public class IsDateTimeValidator implements ConstraintValidator<IsDateTime, String> {

private boolean required = false;
private String dateFormat = "yyyy-MM-dd";

/**
* 用于初始化注解上的值到这个validator
* @param constraintAnnotation
*/
@Override
public void initialize(IsDateTime constraintAnnotation) {
required = constraintAnnotation.required();
dateFormat = constraintAnnotation.dateFormat();
}

/**
* 具体的校验逻辑
* @param value
* @param context
* @return
*/
public boolean isValid(String value, ConstraintValidatorContext context) {
if (required) {
return ValidatorUtil.isDateTime(value, dateFormat);
} else {
if (StrUtil.isBlank(value)) {
return true;
} else {
return ValidatorUtil.isDateTime(value, dateFormat);
}
}
}

}

注意这里验证逻辑我抽出来单独写了一个工具类,ValidatorUtil

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
java复制代码package cn.soboys.core.validator;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* @author kenx
* @version 1.0
* @date 2021/1/21 20:51
* 验证表达式
*/
public class ValidatorUtil {
private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}");
private static final Pattern money_pattern = Pattern.compile("^[0-9]+\\.?[0-9]{0,2}$");

/**
* 验证手机号
*
* @param src
* @return
*/
public static boolean isMobile(String src) {
if (StrUtil.isBlank(src)) {
return false;
}
Matcher m = mobile_pattern.matcher(src);
return m.matches();
}


/**
* 验证枚举值是否合法 ,所有枚举需要继承此方法重写
*
* @param beanClass 枚举类
* @param status 对应code
* @return
* @throws Exception
*/
public static boolean isEnum(Class<?> beanClass, String status) throws Exception {
if (StrUtil.isBlank(status)) {
return false;
}

//转换枚举类
Class<Enum> clazz = (Class<Enum>) beanClass;
/**
* 其实枚举是语法糖
* 是封装好的多个Enum类的实列
* 获取所有枚举实例
*/
Enum[] enumConstants = clazz.getEnumConstants();

//根据方法名获取方法
Method getCode = clazz.getMethod("getCode");
Method getDesc = clazz.getMethod("getDesc");
for (Enum enums : enumConstants) {
//得到枚举实例名
String instance = enums.name();
//执行枚举方法获得枚举实例对应的值
String code = getCode.invoke(enums).toString();
if (code.equals(status)) {
return true;
}
String desc = getDesc.invoke(enums).toString();
System.out.println(StrFormatter.format("实列{}---code:{}desc{}", instance, code, desc));
}
return false;
}

/**
* 验证金额0.00
*
* @param money
* @return
*/
public static boolean isMoney(BigDecimal money) {
if (StrUtil.isEmptyIfStr(money)) {
return false;
}
if (!NumberUtil.isNumber(String.valueOf(money.doubleValue()))) {
return false;
}
if (money.doubleValue() == 0) {
return false;
}
Matcher m = money_pattern.matcher(String.valueOf(money.doubleValue()));
return m.matches();
}


/**
* 验证 日期
*
* @param date
* @param dateFormat
* @return
*/
public static boolean isDateTime(String date, String dateFormat) {
if (StrUtil.isBlank(date)) {
return false;
}
try {
DateUtil.parse(date, dateFormat);
return true;
} catch (Exception e) {
return false;
}
}

}

我自定义了补充了很多验证器,包括日期验证,枚举验证,手机号验证,金额验证

自定义校验注解使用起来和内置注解无异,在需要的字段上添加相应注解即可

校验流程解析

使用 Validation API 进行参数效验步骤整个过程如下图所示,用户访问接口,然后进行参数效验 ,如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理

全局异常出来请参考我这篇文章SpringBoot优雅的全局异常处理

关注公众号猿人生获取更多干货分享

发送参数校验 获取完整自定义源码

本文转载自: 掘金

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

【熬夜肝了】一篇干货满满的线程池

发表于 2021-07-07

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」

img

目录结构

一、Doug Lea在JCP JSR-166 专家组成员撰写的文档

二、JAVA8源代码中6种线程状态的定义

三、线程池的核心参数及工作详细流程(addwork,runwork,线程回收….)

四、线程池线程数量、拒绝策略、阻塞队列’配置详解

五、实战线程池配置、扩展线程池功能

img

前言导读

由Doug Lea在JCP JSR-166 专家组成员的协助下撰写,并已发布到公共领域,如 creativecommons.org/publicdomai…

一个ExecutorService ,它使用可能是多个池线程中的一个来执行每个提交的任务,通常使用Executors工厂方法对其进行配置。
线程池解决了两个不同的问题:由于减少了每个任务的调用开销,它们通常在执行大量异步任务时提供改进的性能,并且它们提供了一种绑定和管理资源(包括线程)的方法,该资源在执行集合时消耗掉了任务。 每个ThreadPoolExecutor还维护一些基本统计信息,例如已完成任务的数量。
为了在广泛的上下文中有用,该类提供了许多可调整的参数和可扩展性挂钩。 但是,建议程序员使用更方便的Executors工厂方法Executors.newCachedThreadPool (无边界线程池,具有自动线程回收), Executors.newFixedThreadPool (固定大小的线程池)和Executors.newSingleThreadExecutor (单个后台线程),这些方法可以预先配置设置。最常见的使用场景。 否则,在手动配置和调整此类时,请使用以下指南:

核心和最大池大小

ThreadPoolExecutor将根据corePoolSize(请参见getCorePoolSize )和getCorePoolSize (请参见getMaximumPoolSize )设置的界限自动调整池大小(请参见getPoolSize )。 当在方法execute(Runnable)提交新任务,并且正在运行的线程少于corePoolSize线程时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理请求。 如果运行的线程数大于corePoolSize但小于maximumPoolSize,则仅在队列已满时才创建新线程。 通过将corePoolSize和maximumPoolSize设置为相同,可以创建固定大小的线程池。 通过将maximumPoolSize设置为一个本质上不受限制的值(例如Integer.MAX_VALUE ,可以允许池容纳任意数量的并发任务。 最典型地,核心和最大池大小仅在构造时设置,但也可以使用setCorePoolSize和setMaximumPoolSize动态更改。

按需施工

默认情况下,甚至核心线程也仅在新任务到达时才开始创建和启动,但是可以使用prestartCoreThread或prestartAllCoreThreads方法动态地覆盖它。 如果使用非空队列构造池,则可能要预启动线程。

创建新线程

使用ThreadFactory创建新线程。 如果没有另外指定,则使用Executors.defaultThreadFactory ,该线程创建的线程全部位于相同的ThreadGroup并且具有相同的NORM_PRIORITY优先级和非守护程序状态。 通过提供其他ThreadFactory,可以更改线程的名称,线程组,优先级,守护程序状态等。如果在通过询问newThread返回null来询问ThreadFactory无法创建线程时,执行器将继续执行,但可能无法执行执行任何任务。 线程应具有“ modifyThread” RuntimePermission 。 如果使用该池的工作线程或其他线程不具有此许可权,则服务可能会降级:配置更改可能不会及时生效,并且关闭池可能保持在可能终止但未完成的状态。

保活时间

如果当前池中的线程数超过corePoolSize,则多余的线程将在空闲时间超过keepAliveTime时终止(请参见getKeepAliveTime(TimeUnit) )。 当不积极使用池时,这提供了一种减少资源消耗的方法。 如果池稍后变得更加活跃,则将构建新线程。 也可以使用setKeepAliveTime(long, TimeUnit)方法动态更改此参数。 使用Long.MAX_VALUE TimeUnit.NANOSECONDS的值Long.MAX_VALUE有效地使空闲线程永远不会在关闭之前终止。 默认情况下,仅当corePoolSize线程数多时,保持活动策略才适用。 但是,只要keepAliveTime值不为零,方法allowCoreThreadTimeOut(boolean)还可用于将此超时策略应用于核心线程。

排队

任何BlockingQueue均可用于传输和保留提交的任务。 此队列的使用与池大小交互:
如果正在运行的线程少于corePoolSize线程,则执行程序总是喜欢添加新线程,而不是排队。
如果正在运行corePoolSize或更多线程,则执行程序总是更喜欢对请求进行排队,而不是添加新线程。
如果无法将请求放入队列中,则将创建一个新线程,除非该线程超过了maximumPoolSize,在这种情况下,该任务将被拒绝。

有三种一般的排队策略:

直接交接。 对于工作队列,一个很好的默认选择是SynchronousQueue ,它可以将任务移交给线程,而不必另外保留它们。 在这里,如果没有立即可用的线程来运行任务,则尝试将其排队的尝试将失败,因此将构造一个新线程。 在处理可能具有内部依赖项的请求集时,此策略避免了锁定。 直接切换通常需要无限制的maximumPoolSizes以避免拒绝新提交的任务。 反过来,当平均而言,命令继续以比其处理速度更快的速度到达时,这可能会带来无限线程增长的可能性。

无限队列。 使用无界队列(例如,没有预定义容量的LinkedBlockingQueue )将在所有corePoolSize线程繁忙时使新任务在队列中等待。 因此,将仅创建corePoolSize线程。 (因此,maximumPoolSize的值没有任何作用。)当每个任务完全独立于其他任务时,这可能是适当的,因此任务不会影响彼此的执行。 例如,在网页服务器中。 尽管这种排队方式对于消除短暂的请求突发很有用,但它承认当命令平均继续以比处理速度更快的速度到达时,无限制的工作队列增长是可能的。

有界队列。 当与有限的maximumPoolSizes一起使用时,有界队列(例如ArrayBlockingQueue )有助于防止资源耗尽,但调优和控制起来会更加困难。 队列大小和最大池大小可能会相互折衷:使用大队列和小池可以最大程度地减少CPU使用率,操作系统资源和上下文切换开销,但会导致人为地降低吞吐量。 如果任务频繁阻塞(例如,如果它们受I / O约束),则系统可能能够安排比您原先允许的线程更多的时间。 使用小队列通常需要更大的池大小,这会使CPU繁忙,但可能会遇到无法接受的调度开销,这也会降低吞吐量。

被拒绝的任务

在方法提交新的任务execute(Runnable)将在执行程序已关闭了拒绝,并且也当执行器使用有限的边界两个最大线程和工作队列容量,且饱和。 在任一情况下, execute方法调用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)其的方法RejectedExecutionHandler 。 提供了四个预定义的处理程序策略:

在默认的ThreadPoolExecutor.AbortPolicy ,处理程序在拒绝时会抛出运行时RejectedExecutionException 。

在ThreadPoolExecutor.CallerRunsPolicy ,调用execute自己的线程运行任务。 这提供了一种简单的反馈控制机制,该机制将减慢新任务的提交速度。

在ThreadPoolExecutor.DiscardPolicy ,简单地删除了无法执行的任务。

在ThreadPoolExecutor.DiscardOldestPolicy ,如果未关闭执行程序,则将丢弃工作队列开头的任务,然后重试执行(该操作可能再次失败,导致重复执行此操作)。

可以定义和使用其他种类的RejectedExecutionHandler类。 这样做需要格外小心,尤其是在设计策略仅在特定容量或排队策略下才能工作时。

挂钩方法

此类提供protected可重写的beforeExecute(Thread, Runnable)和afterExecute(Runnable, Throwable)方法,这些方法在每个任务执行前后被调用。 这些可以用来操纵执行环境。 例如,重新初始化ThreadLocals,收集统计信息或添加日志条目。 另外,一旦执行程序完全终止,可以terminated方法terminated以执行需要执行的任何特殊处理。
如果钩子或回调方法引发异常,内部工作线程可能进而失败并突然终止。

队列维护

方法getQueue()允许访问工作队列,以进行监视和调试。 强烈建议不要将此方法用于任何其他目的。 当取消大量排队的任务时,可以使用提供的两种方法remove(Runnable)和purge来帮助回收存储。
定案
这在程序不再被引用,也没有剩余的线程将成为池shutdown自动。 如果即使在用户忘记调用shutdown也要确保回收未引用的池,则必须通过使用零核心线程的下限和/或设置allowCoreThreadTimeOut(boolean)来设置适当的保活时间,以安排未使用的线程最终死掉allowCoreThreadTimeOut(boolean) 。

扩展示例。 此类的大多数扩展都覆盖一个或多个受保护的hook方法。 例如,以下是一个子类,它添加了一个简单的暂停/继续功能:

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
java复制代码   class PausableThreadPoolExecutor extends ThreadPoolExecutor {
private boolean isPaused;
private ReentrantLock pauseLock = new ReentrantLock();
private Condition unpaused = pauseLock.newCondition();

public PausableThreadPoolExecutor(...) { super(...); }

protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();
try {
while (isPaused) unpaused.await();
} catch (InterruptedException ie) {
t.interrupt();
} finally {
pauseLock.unlock();
}
}

public void pause() {
pauseLock.lock();
try {
isPaused = true;
} finally {
pauseLock.unlock();
}
}

public void resume() {
pauseLock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
pauseLock.unlock();
}
}
}

img

想要了解透彻线程池,先了解一下线程吧

以下基于JDK1.8介绍:

摘自源码片短:一些核心的定义

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
java复制代码    private volatile String name; // 线程的名字
// 线程的优先级,默认为5,可自行设置,越大代表可以获得的时间片几率越高
private int priority;
​
/* 是否是守护线程,守护线程在JVM结束时自动销毁 */
private boolean daemon = false;
​
/* 将要运行的目标. */
private Runnable target;
​
/* 线程组-就是给线程分组,挺简单,初始化会被分配,与线程池无直接联系 */
private ThreadGroup group;
/* 此线程的上下文ClassLoader */
private ClassLoader contextClassLoader;
​
/* The inherited AccessControlContext of this thread */
private AccessControlContext inheritedAccessControlContext;
​
/* 用于命名是哪个线程的编号 */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
​
/* 与此线程有关的ThreadLocal值。该映射由ThreadLocal类维护 */
ThreadLocal.ThreadLocalMap threadLocals = null;
​
/*
*与此线程有关的InheritableThreadLocal值。该映射由InheritreadLableThocal类维护.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
​
/*
此线程请求的堆栈大小,如果创建者未指定堆栈大小,则为0。
VM可以根据此数字执行*喜欢的任何事情;一些虚拟机将忽略它.
*/
private long stackSize;
​
/*
* Thread ID
*/
private long tid;
​
/* 用于生成线程ID */
private static long threadSeqNumber;
​
/* Java thread status
*/
private volatile int threadStatus = 0;

注意几个重要的方法:

1.有一个start方法,这个方法里面调用了操作系统,利用操作系统去调用我们的run方法。

1
java复制代码private native void start0();

\2. interruput方法,这只是一个标志,不会立即中断

interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态

isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态

\3. join 面试常问:其实是通过wait来阻塞线程,例如:t1.join(),无限制阻塞t1完成,在继续执行下面的方法。

\4. getAllStackTraces 获取所有线程的堆栈信息,可以用来扩展监控。

其他方法大家看看就行。

下面讲讲线程的状态:

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
java复制代码/**
尚未启动的线程的线程状态
*/
NEW,
​
/**
可运行线程的线程状态。状态为可运行的线程正在Java虚拟机中执行,
但是可能正在等待来自操作系统的其他资源,例如处理器。
*/
RUNNABLE,
​
/**
线程的线程状态被阻塞,等待监视器锁定。处于阻塞状态的线程正在等待监视
器锁定输入同步块/方法或调用Object.wait后重新输入同步块/方法。
区别就是有个while
*/
// synchronized(this)
// {
// while (flag)
// {
// obj.wait();
// }
// }
BLOCKED,
​
/**
*等待线程的线程状态。由于调用以下其中一种方法,线程处于等待状态:
Object.wait无超时
Thread.join没有超时
LockSupport.park 等待状态
正在等待另一个线程执行特定操作。例如,在某个对象上调用
Object.wait()的线程正在等待另一个线程调用 Object.notify()
或该对象上的Object.notifyAll()名为 Thread.join的线程正在等待指定
的线程终止。
*/
WAITING,
​
/**
具有指定等待时间的等待线程的线程状态。线程由于以指定的正等待时间调用以下
方法之一而处于定时等待状态:
Thread.sleep,
Object.wait(long)
Thread.join(long)
LockSupport.parkNanos
LockSupport.parkUntil
*/
TIMED_WAITING,
​
/**
终止线程的线程状态。*线程已完成执行
*/
TERMINATED;

线程了解差不多了,接下来看看线程池吧!

线程池ThreadPoolExecutor

看看线程池的UML图吧

img

我们从上往下依次分析:

1
2
3
4
5
6
7
8
java复制代码/ ** 
*在将来的某个时间执行给定命令。由 Executor实现决定,命令可以在新线程
池或调用线程中执行。 @param命令可运行任务,如果无法接受此任务,
则@throws RejectedExecutionException
如果命令为null,则@throws NullPointerException
*
/
void execute(Runnable command);

简单来说就是调度线程来执行任务,用户只需提供Runnable对象,将任务的运行**逻辑提交到执行器****(**Executor)中

ExecutorService接口增加了一些能力:(1)扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;(2)提供了管控线程池的方法,比如停止线程池的运行。可以从上面UML图简单看出。

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
java复制代码public interface ExecutorService extends Executor {
// 请求关闭、发生超时或者当前线程中断,无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。
boolean awaitTermination(long timeout, TimeUnit unit);
// 执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks);
// 执行给定的任务,当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);
// 执行给定的任务,如果某个任务已成功完成(也就是未抛出异常),则返回其结果。
<T> T invokeAny(Collection<? extends Callable<T>> tasks);
// 执行给定的任务,如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);
// 如果此执行程序已关闭,则返回 true。
boolean isShutdown();
// 如果关闭后所有任务都已完成,则返回 true。
boolean isTerminated();
// 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
void shutdown();
// 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
List<Runnable> shutdownNow();
// 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
<T> Future<T> submit(Callable<T> task);
// 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
Future<?> submit(Runnable task);
// 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
<T> Future<T> submit(Runnable task, T result);
}

AbstractExecutorService则是上层的抽象类,这里延伸出Future,简单的说就是获取异步执行的结果,例如在Netty中,我们处理消息是通过一个双向链表来处理的,需要对消息一层层处理,所以说这里也用到了Future来获取消息处理的结果。

最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。

线程池生命周期

线程池的生命周期也就是线程池在运行时所经历的线程池状态。线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount)。在具体实现中,线程池将运行状态(runState)、线程数量 (workerCount)两个关键参数的维护放在了一起,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; // 32 -3
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 1 << 29 - 1 = 2^29 -1
​
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS; // -2^29 = ‭11100000000000000000000000000000‬
private static final int SHUTDOWN = 0 << COUNT_BITS; // 0 = 00000000000000000000000000000000‬
private static final int STOP = 1 << COUNT_BITS; // 2^29 = ‭00100000000000000000000000000000‬
private static final int TIDYING = 2 << COUNT_BITS; // 2*2^29 = ‭01000000000000000000000000000000‬
private static final int TERMINATED = 3 << COUNT_BITS; // 3*2^29 = ‭01100000000000000000000000000000‬
​
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; } // 线程池运行状态
private static int workerCountOf(int c) { return c & CAPACITY; } // 线程数量
private static int ctlOf(int rs, int wc) { return rs | wc; }

ctl这个AtomicInteger类型,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它同时包含两部分的信息:线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),高3位保存runState,低29位保存workerCount,两个变量之间互不干扰。用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。通过阅读线程池源代码也可以发现,经常出现要同时判断线程池运行状态和线程数量的情况。线程池也提供了若干方法去供用户获得线程池当前的运行状态、线程个数。这里都使用的是位运算的方式,相比于基本运算,速度也会快很多。

如上代码中给出了线程池状态的二进制数据,下面分别描述一下

  • RUNNING: 能接受新提交的任务,并且也能处理阻塞队列中的任务。
  • SHUTDOWN: 关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。
  • STOP : 不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。
  • TIDYING : 所有的任务都已经终止了,workerCount(有效线程数)为0。
  • TERMINATED : 在terminated()方法执行完成后进入该状态。

线程池运行流程

本文的核心重点。

首先,所有任务的调度都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务

img

我们直接看一下源码,这样比较直观,印象比较深刻,代码不难。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { // 如果小于核心线程数
if (addWorker(command, true))
return;
c = ctl.get();
}
// offer就是如果队列未满就添加到队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果队列也满了,就直接起一个线程,失败走拒绝策略
else if (!addWorker(command, false))
reject(command);
}
1
java复制代码

下面我们来看看addwork相关部分代码去掉了部分条件判断

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
java复制代码 private boolean addWorker(Runnable firstTask, boolean core) {
if (compareAndIncrementWorkerCount(c))
break retry; // 增加线程数,跳出循环
try {
w = new Worker(firstTask); //this.thread = getThreadFactory().newThread(this);
final Thread t = w.thread; // 这里通过线程工厂new一个线程
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); // 独占锁-
try {
int rs = runStateOf(ctl.get());// 获取线程池状态
if (rs < SHUTDOWN || // 线程池在运行或者shutdown
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);// 添加任务到阻塞队列
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); // 启动线程
workerStarted = true;
}
}
return workerStarted;

上面可以看到t.start()开启了系统线程调度,接下来在跟下run方法

1
2
3
java复制代码  public void run() {
runWorker(this);
}

可以看到,接下来执行了runworker(this),this就是刚刚加入的w任务。

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
java复制代码final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 以独占的方式释放资源
boolean completedAbruptly = true;
try {
// 如果task!= null,就getTask获取一个任务
while (task != null || (task = getTask()) != null) {
w.lock(); // 1.以独占额方式获得资源,忽略异常
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 2.可扩展:用于重新初始化 threadlocals 或者执行日志记录。
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();// 任务执行
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 线程回收
processWorkerExit(w, completedAbruptly);
}
}

分析一下getTask

1
2
3
4
java复制代码     boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();

简单的说如果设置了核心线程可以超时=true或者当前线程数>核心线程数,就限时获取任务,否则就阻塞获取任务。

逻辑其实都很简单,有些东西还是需要我们仔细分析一下:例如代码中

第一点

1
2
3
4
5
6
7
java复制代码1.w.lock()
2.public void lock() { acquire(1); }
3.public final void acquire(int arg) { // class AbstractQueuedSynchronizer
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

可以看到这里是直接调用的AQS的独占锁-公平锁实现方式,而在线程回收processWorkerExit 这个方法使用的是AQS的独占锁-非公平锁

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
java复制代码private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
// 默认使用非公平锁 new NonfairSync()
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 锁内实现移除任务,同时也移除了Thread引用
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
// 尝试中断线程,如果线程池正在关闭,则关闭线程池
tryTerminate();
​
int c = ctl.get();
if (runStateLessThan(c, STOP)) { // 如果线程池没有停止
if (!completedAbruptly) { // 没有异常结束
// 线程池最小空闲数,允许core thread超时就是0,否则就是corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 如果min == 0但是队列不为空要保证有1个线程来执行队列中的任务
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// // 线程数不为空
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 1.线程异常退出
// 2.线程池为空,但是队列中还有任务没执行,看addWoker方法对这种情况的处理
addWorker(null, false);
}
}

简单分析一下线程回收流程:

  1. lock方法一旦获取了独占锁,表示当前线程正在执行任务中。
  2. 如果正在执行任务,则不应该中断线程。
  3. 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断。
  4. 线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收。
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码    final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 这里可以看到只要线程数!=0,线程就可以被回收
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 这里看到进行了trylock判断
if (!t.isInterrupted() && w.tryLock()) {
try {
// 进行线程中断标识
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}

例如:A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

  再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会**unpark()**主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

  一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

任务拒绝

线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。

拒绝策略是一个公共接口,说明我们可以自定义扩展,其设计如下:

1
2
3
java复制代码public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

我们看看JDK提供的几种拒绝策略:

img

一般业务线程采用:调用提交任务的线程去处理(前提是所有任务都执行完毕)

ThreadPoolExecutor.CallerRunsPolicy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
​
/**
调用者线程中执行任务r
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}

业务实战

场景1:快速响应用户请求 B/S

从用户体验角度看,这个结果响应的越快越好,如果一个页面半天都刷不出,用户可能就放弃查看这个商品了。而面向用户的功能聚合通常非常复杂,伴随着调用与调用之间的级联、多级级联等情况,业务开发同学往往会选择使用线程池这种简单的方式,将调用封装成任务并行的执行,缩短总体响应时间。另外,使用线程池也是有考量的,这种场景最重要的就是获取最大的响应速度去满足用户,所以应该不设置队列去缓冲并发任务,调高****corePoolSize和maxPoolSize去尽可能创造多的线程快速执行任务。

场景2:快速处理批量任务

离线的大量计算任务,需要快速执行。比如说,统计某个报表,需要计算出全国各个门店中有哪些商品有某种属性,用于后续营销策略的分析,那么我们需要查询全国所有门店中的所有商品,并且记录具有某属性的商品,然后快速生成报表

这种场景需要执行大量的任务,我们也会希望任务执行的越快越好。这种情况下,也应该使用多线程策略,并行计算。但与响应速度优先的场景区别在于,这类场景任务量巨大,并不需要瞬时的完成,而是关注如何使用有限的资源,尽可能在单位时间内处理更多的任务,也就是吞吐量优先的问题。所以应该设置队列去缓冲并发任务,调整合适的corePoolSize去设置处理任务的线程数。在这里,设置的线程数过多可能还会引发线程上下文切换频繁的问题,也会降低处理任务的速度,降低吞吐量。

场景3:队列设置过长

由于队列设置过长,最大线程数设置失效,导致请求数量增加时,大量任务堆积在队列中,任务执行时间过长,最终导致下游服务的大量调用超时失败

那么具体数值怎么配置呢?

img

可以看出,这些计算公式都偏离了实际的业务场景。I/O密集型和CPU密集型差别很大,不过都跟CPU核心数挂钩的,I/O密集型任务常常需要我们进行线程池参数动态化,所有线程池也非常友好的提供了几个公共方法,供我们动态配置线程池的线程核心数和线程最大数和阻塞队列大小。

除了这些,我们前面提到的拒绝策略和任务执行前处理和任务执行后处理都可以作为我们对线程池的扩展。通过这些配置,我们可以实现对线程池的动态参数调整,任务执行情况,队列负载情况,监控,日志等等。

这里给出任务前置/后置处理的扩展做一个监控:

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
java复制代码public class TimingThreadPool extends ThreadPoolExecutor {
​
public TimingThreadPool() {
super(1, 1, 0L, TimeUnit.SECONDS, null);
}
private static final Logger logger = LoggerFactory.getLogger(TimingThreadPool.class);
private final ThreadLocal<Long> startTime = new ThreadLocal<Long>();
private final AtomicLong numTasks = new AtomicLong();
private final AtomicLong totalTime = new AtomicLong();
​
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
logger.info(String.format("Thread %s: start %s", t, r));
startTime.set(System.nanoTime());
}
​
@Override
protected void afterExecute(Runnable r, Throwable t) {
try {
long endTime = System.nanoTime();
long taskTime = endTime - startTime.get();
numTasks.incrementAndGet();
totalTime.addAndGet(taskTime);
logger.info(String.format("Thread %s: end %s, time=%dns",
t, r, taskTime));
} finally {
super.afterExecute(r, t);
}
}
​
@Override
protected void terminated() {
try {
logger.info(String.format("Terminated: avg time=%dns",
totalTime.get() / numTasks.get()));
} finally {
super.terminated();
}
}
}

延伸阅读

  • JDK8 : 源码
  • 《Java并发编程实战》
  • creativecommons.org/publicdomai…

点关注,不迷路

好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。
我后面会每周都更新几篇一线互联网大厂面试和常用技术栈相关的文章,非常感谢人才们能看到这里,如果这个文章写得还不错,觉得有点东西的话 求点赞👍 求关注❤️ 求分享👥 对暖男我来说真的 非常有用!!!

如果本篇博客有任何错误,请批评指教,不胜感激 !

本文转载自: 掘金

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

【熬夜肝了】一篇数据库规范,你应该用的上

发表于 2021-07-07

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」

【熬夜打卡】相信大多数的同学都非常了解这些条条款款了,之前我也认为是这样的,但是写出来才发现有好些点之前都没有深刻理解,比如覆盖索引、预编译、mysql驱动那块、还有那些行记录格式,COLLATE 这些,收货满满。

  1. 数据库命名规范

采用小写字母、数字(通常不需要)和下划线组成。禁止使用’-’,命名简洁、含义明确。
2. #### 表命名

  • 根据业务类型不同,采用不同的前缀,小写字母、下划线组成
  • 长度控制在30个字符以内

推荐的命名规则

类型 前缀 说明
业务表 tb_
关系表 tr_
历史表 th_
统计表 ts_
日志表 tl_xx_log
系统表、字典表、码表 sys_
临时表 tmp_ 禁止使用
备份表 bak_xx_ymd
视图 view_ 避免使用
  1. 引擎

使用默认Innodb引擎(5.5以后默认)

支持事务、支持行级锁、更好的恢复性、高并发下性能更好。
4. #### 字符集 – 拔剑起蒿莱👥👥👥👥

* 数据库和表的字符集统一,尽量使用UTF8(根据业务需求)
* 兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效
* UTF8和UTF8MB4字段进行关联,会导致索引失效
* 除非特殊情况,禁止建表指定字符集(采用库默认字符集),降低出现字符集不统一导致性能问题的风险。
* 无特殊要求,禁止指定表COLLATE -----


COLLATE主要的作用是排序的规则以及检索的规则,utf8字符集默认的是 utf8\_general\_ci ,utf8mb4字符集默认的是utf8mb4\_general\_ci,结尾的ci意思是不区分大小写。


COLLATE会影响到ORDER BY语句的顺序,会影响到WHERE条件中大于小于号筛选出来的结果,会影响**DISTINCT**、**GROUP BY**、**HAVING**语句的查询结果。比如:select \* from test where name like 'A%',在 utf8\_bin字符集下,是无法检索出 ‘abc’字段的,并且排序的情况下Abc和abc所在的顺序是不一致的。
* 慎重选择row\_format(行记录格式)


Barracuda: 新的文件格式。它支持InnoDB的所有行格式,包括新的行格式:**COMPRESSED** 和 **DYNAMIC**


在 msyql 5.7.9 及以后版本,默认行格式由innodb\_default\_row\_format变量决定,它的默认值是**DYNAMIC**


db默认的innodb\_file\_format 为 Barracuda,默认的innodb\_default\_row\_format为 dynamic;其中COMPRESSED 压缩比经测试最大也就 1/2,但读取和写入会有额外cpu开销,并且申请内存是按照解压后的原大小申请,在高并发情况下容易导致性能问题。


Dynamic行格式,列存储是否放到off-page页,主要取决于行大小,他会把行中最长的一列放到off-page,直到数据页能存放下两行。TEXT或BLOB列<=40bytes时总是存在于数据页。这种方式可以避免compact那样把太多的大列值放到B-tree Node(数据页中只存放20个字节的指针,实际的数据存放在Off Page中,之前的Compact 和 Redundant 两种格式会存放768个字前缀字节)。


Compressed物理结构上与Dynamic类似,Compressed行记录格式的另一个功能就是存储在其中的行数据会以zlib的算法进行压缩,因此对于BLOB、TEXT、VARCHAR这类大长度数据能够进行有效的存储(减少40%,但对CPU要求更高)。
  1. 字段设计 – 人生感意气 功名谁复论👍👍👍

* 所有表和字段都需要添加注释,使用comment从句添加表和列的备注 从一开始就进行数据字典的维护
* 尽量控制单表数据量的大小,建议控制在500万以内


500万并不是MySQL数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题,可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小
* 谨慎使用MySQL分区表


分区表在物理上表现为多个文件,在逻辑上表现为一个表。谨慎选择分区键,跨分区查询效率可能更低,另外,对于表结构维护,分区表的维护造成的开销更集中,建议采用物理分表的方式管理大数据
* 建议将大字段,访问频度低的字段拆分到单独的表中存储,分离冷热数据,尽量做到冷热数据分离,减小表的宽度
* MySQL限制每个表最多存储4096列,并且每一行数据的大小不能超过65535字节。为减少磁盘IO,保证热数据的内存缓存命中率(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的IO),更有效的利用缓存,避免读入无用的冷数据,经常一起使用的列放到一个表中(避免更多的关联操作)。对于非常用字段,建议采用扩展表的方式进行分表。


注意:每一行数据的65535字节中,utf8字符集下,varchar每一个长度占用3个字节,utf8mb4字符集下,每一个长度占用4个字节
* 尽量不在表中建立预留字段


预留字段的命名很难做到见名识义,预留字段无法确认存储的数据类型,所以无法选择合适的类型。对预留字段类型的修改,会对表进行锁
* 禁止使用外键约束


外键使得表之间相互耦合,影响update/delete等SQL性能,有可能造成死锁,高并发情况下容易成为数据库瓶颈。建议在业务端实现。
  1. 数据库字段设计规范—愿君学长松 慎勿作桃李🏆🏆🏆🏆

* 关于数据长度


够用前提下,越短越好,这样能够消耗更少的存储空间;因排序申请的内存大小和字段长度有关,需要进行排序时,长度小的字段消耗更少的内存空间;优先选择符合存储需要的最小的数据类型
* 禁止使用TEXT/BLOB类型,禁止在数据库中存储图片,文件等大的二进制数据


通常文件很大,会短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机IO操作,文件很大时,IO操作很耗时。通常存储于文件服务器,数据库只存储文件地址信息
* 避免使用ENUM(枚举)类型


修改ENUM值需要使用ALTER语句;ENUM类型的ORDER BY操作效率低,需要额外操作;禁止使用数值作为ENUM的枚举值
* 尽可能把所有列定义为NOT NULL


索引NULL列需要额外的空间来保存,所以要占用更多的空间


进行比较和计算时要对NULL值做特别的处理


NULL只能采用IS NULL或者IS NOT NULL,而在=/!=/in/not in时很容易造成查询结果与设计逻辑不符
* 使用TIMESTAMP(4个字节)或DATETIME类型(5个字节)存储时间


网上很多博客都说DATETIME是8个字节,其实在5.6.4版本一上就减少到5个字节


[mysql 源码 github 地址](https://github.com/mysql/mysql-server)



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
java复制代码longlong TIME_to_longlong_datetime_packed(const MYSQL_TIME &my_time) {
longlong ymd = ((my_time.year * 13 + my_time.month) << 5) | my_time.day;
longlong hms = (my_time.hour << 12) | (my_time.minute << 6) | my_time.second;
longlong tmp = my_packed_time_make(((ymd << 17) | hms), my_time.second_part);
assert(!check_datetime_range(my_time)); /* Make sure no overflow */
return my_time.neg ? -tmp : tmp;
}
根据上述算法,计算极限时间 9999-12-31 23:59:59
时间各部分依次是 year-month-day hour:minute:second

1. 计算 longlong ymd
year*13 + month = 9999*13 + 12 = 129999
将 129999 左移 5 位,再与 31 进行或运算
‬0000 0000 0011 1111 0111 1001 111[0 0000] --- 129999 左移 5 位 (年*13 + 月)
0000 0000 0000 0000 0000 0000 ‭0001 1111‬ --- 31 (日)
= ‬0000 0000 0011 1111 0111 1001 1111 1111 --- 得出 longlong ymd 低位,极限有 22 位

2. 计算 longlong hms
将 hour 左移 12 位,与 minute 左移 6 位,再与 second 进行或运算
0001 0111 [0000 0000 0000] --- 23 左移 12 位 (时)
1110 11‬[00 0000] --- 59 左移 6 位 (分)
11 1011 --- 59 (秒)
= 0001 0111 1110 1111 1011 --- 得出 longlong hms 的低位,极限有 17 位

3. 计算 longlong tmp
ymd 右移 17 位,与 hms 进行或运算,这样刚好存到 39 位。(至此,再加上 1 位标识位,也 就刚好 40 位,为 5 字节了)
再使用 my_packed_time_make()函数,将 ymd 与 小数秒部分 连起来。
TIMESTAMP存储的时间范围:1970-01-01 00:00:01 ~ 2038-01-19-03:14:07。 TIMESTAMP占用4字节和INT相同,但比INT可读性高 超出TIMESTAMP取值范围的使用DATETIME类型存储。 * 同财务相关的金额类数据{设计使用小数}必须使用decimal类型 Decimal类型为精准浮点数,在计算时不会丢失精度。 * 同一意义的字段定义必须相同 * 同一意义的字段定义包括字段类型和长度范围必须相同 * 增加字段时禁止指定after * VARCHAR(N),N尽可能小 如果N<256时会使用一个字节来存储长度,如果N>=256则使用两个字节来存储长度。 * 数值型字段,default值建议选用0
  1. 索引设计规范 —共矜然诺心 各负纵横志❤❤❤❤

* 创建表一定要有主键(PRIMARY KEY),推荐使用雪花或梨花。
* 不要使用UUID、MD5、HASH、字符串列作为主键(无法保证数据的顺序增长)。
* 限制每张表上的索引数量


索引并不是越多越好!索引可以提高效率同样可以降低效率。索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。因为mysql优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,就会**增加mysql优化器生成执行计划的时间**,同样会降低查询性能。
* 区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数);
* 尽量把字段长度小的列放在联合索引的最左侧(因为字段长度越小,一页能存储的数据量越大,IO性能也就越好);
* 使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引)。
* 避免建立冗余索引和重复索引---因为这样会增加查询优化器生成执行计划的时间。


重复索引示例:primary key(id)、index(id)、unique index(id)


冗余索引示例:index(a,b,c)、index(a,b)、index(a)
* 优先考虑覆盖索引


对于频繁的查询优先考虑使用覆盖索引。覆盖索引就是包含了所有查询字段(where,select,ordery by,group by包含的字段)的索引


覆盖索引的好处:1.可以把随机IO变成顺序IO加快查询效率;2.能够避免回表查询,提升查询效率
* 一定要在表与表之间的关联键上建立索引
  1. sql开发规划 — 月缺不改光 剑折不改刚❤️❤️❤️❤️

* 建议使用预编译语句进行数据库操作


预编译语句可以重复使用这些计划,减少SQL编译所需要的时间,还可以解决动态SQL所带来的SQL注入的问题;只传参数,比传递SQL语句更高效;相同语句可以一次解析,多次使用,提高处理效率。


在实际生产环境中,如MyBatis等ORM框架大量使用了预编译语句,最终底层调用都会走到MySQL驱动里,从驱动中了解相关实现细节有助于更好地理解预编译语句


就像我们熟悉的#{}是经过预编译的,是安全的;${}是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入


MySQL驱动里对于server预编译的情况维护了两个**基于LinkedHashMap使用LRU策略的cache**,分别是serverSideStatementCheckCache用于缓存sql语句是否可以由服务端来缓存以及serverSideStatementCache用于缓存服务端预编译sql语句,这两个缓存的大小由**prepStmtCacheSize**参数控制。
* 避免数据类型的隐式转换


隐式转换会导致索引失效。如:select name,phone from customer where id = '111';
* 充分利用表上已经存在的索引
* 避免使用双%号的查询条件


如a like '%123%',(如果无前置%,只有后置%,是可以用到列上的索引的)。
* 一个SQL只能利用到复合索引中的一列进行范围查询


如:有 a,b,c列的联合索引,在查询条件中有a列的范围查询,则在b,c列上的索引将不会被用到,在定义联合索引时,如果a列要用到范围查找的话,就要把a列放到联合索引的右侧。
* WHERE从句中禁止对列进行函数转换和计算


不推荐:where date(create\_time)=20190101


推荐:where create\_time >= 20190101 and create\_time < 20190102
* 在明显不会有重复值时使用**UNION ALL**而不是UNION


UNION会把两个结果集的所有数据放到临时表中后再进行去重和排序操作


UNION ALL不会再对结果集进行去重和排序操作
* 拆分复杂的大SQL为多个小SQL
* SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
* 不要使用 count(列名)或 count(常量)来替代 count(*),count(*)就是 SQL92 定义 的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。

及时当勉励 岁月不待人

能看到这里的人呀,都是
菁英。❤❤️❤️❤️❤

非常感谢
菁英们能看到这里,如果这个文章写得还不错,觉得有点东西的话 求点赞👍 求关注❤️ 求分享👥 对暖男我来说真的 非常有用!!!

如果本篇博客有任何错误,请批评指教,不胜感激 !

本文转载自: 掘金

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

大话--对象存储 OSS

发表于 2021-07-06

这是我参与新手入门的第2篇文章

前言

  对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。

一、什么是对象存储

  对象象存储OSS(Object Storage Service)是提供的海量、安全、低成本、高持久的云存储服务。

OSS特点

  • 持久性
  • 安全
  • 成本低
  • 智能存储
  • 方便、快捷
  • 强大、灵活
  • 数据冗余机制
  • 丰富、强大的处理功能

二、应用场景

1、图片和音视频等应用的海量存储

  OSS可用于图片、音视频、日志等海量文件的存储。各种终端设备、Web网站程序、移动应用可以直接向OSS写入或读取数据。OSS支持流式写入和文件写入两种方式。

2、网页或者移动应用的静态和动态资源分离

  利用海量互联网带宽,OSS可以实现海量数据的互联网并发下载。OSS提供原生的传输加速功能,支持上传加速、下载加速,提升跨国、跨洋数据上传、下载的体验。同时,OSS也可以结合CDN产品,提供静态内容存储、分发到边缘节点的解决方案,利用CDN边缘节点缓存的数据,提升同一个文件被同一地区客户大量重复并发下载的体验。

3、云端数据处理

  上传文件到OSS后,可以配合媒体处理服务和图片处理服务进行云端的数据处理。

三、快速使用

1、方式一:在Maven项目中加入依赖项(推荐方式)

在Maven工程中使用OSS Java SDK,只需在pom.xml中加入相应依赖即可。以3.10.2版本为例,在中加入如下内容:

1
2
3
4
5
xml复制代码<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>

如果使用的是Java 9及以上的版本,则需要添加jaxb相关依赖。添加jaxb相关依赖示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
xml复制代码<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>

2、方式二:在Eclipse项目中导入JAR包

以3.10.2版本为例,步骤如下:

  1. 下载Java SDK 开发包。
  2. 解压该开发包。
  3. 将解压后文件夹中的文件aliyun-sdk-oss-3.10.2.jar以及lib文件夹下的所有文件拷贝到您的项目中。
  4. 在Eclipse中选择您的工程,右键选择Properties > Java Build Path > Add JARs。
  5. 选中拷贝的所有JAR文件,导入到Libraries中。

3、方式三:在Intellij IDEA项目中导入JAR包

以3.10.2版本为例,步骤如下:

  1. 下载Java SDK 开发包。
  2. 解压该开发包。
  3. 将解压后文件夹中的文件aliyun-sdk-oss-3.10.2.jar以及lib文件夹下的所有JAR文件拷贝到您的项目中。
  4. 在Intellij IDEA中选择您的工程,右键选择File > Project Structure > Modules > Dependencies > + > JARs or directories 。
  5. 选中拷贝的所有JAR文件,导入到External Libraries中。

四、使用示例

1、上传文件

1
2
3
4
5
6
7
8
9
js复制代码 public static void uploadFile(File file, String goodsImgPath) {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上传文件。<yourLocalFile>由本地文件路径加文件名包括后缀组成,例如/users/local/myfile.txt。
ossClient.putObject(bucketName, goodsImgPath, file);
ossClient.setObjectAcl(bucketName, goodsImgPath, CannedAccessControlList.PublicRead);
// 关闭OSSClient。
ossClient.shutdown();
}

2、上传文件流

1
2
3
4
5
6
7
8
9
js复制代码  public static void uploadFileInputStream(InputStream inputStream , String goodsImgPath) {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 上传文件流。
ossClient.putObject(bucketName, goodsImgPath, inputStream);
ossClient.setObjectAcl(bucketName, goodsImgPath, CannedAccessControlList.PublicRead);
// 关闭OSSClient。
ossClient.shutdown();
}

3、下载文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
js复制代码  public static void downloadFileInputStream(InputStream inputStream , String goodsImgPath) {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

// ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
OSSObject ossObject = ossClient.getObject(bucketName, objectName);

// 读取文件内容。
System.out.println("Object content:");
BufferedReader reader = new BufferedReader(new InputStreamReader(ossObject.getObjectContent()));
while (true) {
String line = reader.readLine();
if (line == null) break;

System.out.println("\n" + line);
}
// 关闭流。
reader.close();

// 关闭OSSClient。
ossClient.shutdown();
}

五、结语

  OSS适合存储论坛网站与软件应用中的附件、高清图片、音视频、备份文件等,以及各种App应用、多终端同步软件、网盘下载站的文件。更多功能大家查阅相关文档挖掘发现。

本文转载自: 掘金

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

Spring boot单元测试小试牛刀与注解介绍

发表于 2021-07-06

这是我参与新手入门的第1篇文章

单元测试是对程序中的某个函数、某个模块的功能进行测试的代码,一般是由开发在开发代码时写的,通过单元测试,能够将代码逻辑的bug尽早的暴露出来,在较小的粒度下保证代码的正确性。

不知道你有没有听说过测试驱动开发TDD:“Test-driven Development”,TDD将单元测试视为编码的第一步,首先完成一个单元测试的代码,由于功能代码的缺失,测试会失败,甚至编译都无法通过;然后开发功能代码,知道刚刚写的单元测试通过;接着再写下一个功能的测试代码,就这样反复迭代,知道完成编码。

我个人觉得TDD是一个很好的开发流程,能够保证单元测试的覆盖率,但是TDD在实际实践的过程中会发现难度比较大,我个人习惯是先写完功能逻辑,然后再写单元测试进行测试 : )

下面我为大家介绍一下在Spring Boot工程中如何编写单元测试,对我们写的功能代码进行测试。

Spring Boot Test配置

  • Spring boot项目配置

首先在Spring boot的工程中加入spring-boot-starter-test的依赖,在spring boot 2.4之后,spring-boot-starter-test依赖Junit5,如果还想使用Junit4的话,可以为Junit4单独添加依赖

Maven pom.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
xml复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>

Gradle build.gradle:

1
2
gradle复制代码testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile group: 'junit', name: 'junit', version: '4.12'

Spring Boot测试Demo

假设我们的Spring Boot工程中有一个AddServer,实现了两个数字的相加,其接口和实现如下:

1
2
3
4
5
6
7
8
9
10
java复制代码public interface AddService {
long add(long a, long b);
}

@Service
public class AddServiceImpl implements AddService {
public long add(long a, long b) {
return a + b;
}
}

对add方法的测试类如下:

1
2
3
4
5
6
7
8
9
10
11
java复制代码@RunWith(SpringRunner.class)
@SpringBootTest
public class AddServiceTest {
@Autowired
private AddService addService;

@Test
private void addTest(){
Assert.assertEquals(2, addService.add(1,1));
}
}

@RunWith(SpringRunner.class)提供了 Spring Boot 测试特性和 JUnit 之间的桥梁。每当我们在 JUnit 测试中使用任何 Spring Boot 测试功能时,都需要此注释。

@SpringBootTest将启用应用程序中完整的context,任何一个Spring容器管理的Bean都可以注入到测试中,例如上面的Service组件AddServiceImpl就被注入到addService中。

@Test注解是Junit的注解,表示该方法是测试方法。

Assert.assertEquals是Junit提供的断言,通过断言对程序的输出结果进行判断是否符合预期,类似的断言还有如下几个:

  • assertTrue
  • assertFalse
  • assertNotEquals
  • assertArrayEquals
  • assertNull
  • assertNotNull
  • assertSame
  • assertNotSame

其含义如字面,按需使用就好

Spring Boot测试之注解

测试类上的注解可以分为几类:

  • @RunWith(SpringRunner.class)
  • @SpringBootTest等@xxxxTest
  • @AutoConfigureXXXX等

@RunWith(SpringRunner.class)前面介绍了,提供了 Spring Boot 测试特性和 JUnit 之间的桥梁,测试中使用到Spring boot的功能的都要使用该注解修饰

@SpringBootTest等@xxxxTest

所有的@xxxTest注解都被@BootstrapWith注解,它们可以启动ApplicationContext,是测试的入口,所有的测试类必须声明一个@xxTest注解。

  • @SpringBootTest 自动侦测并加载@SpringBootApplication或@SpringBootConfiguration中的配置,默认web环境为MOCK,不监听任务端口
  • @DataRedisTest 测试对Redis操作,自动扫描被@RedisHash描述的类,并配置Spring Data Redis的库
  • @DataJpaTest 测试基于JPA的数据库操作,同时提供了TestEntityManager替代JPA的EntityManager
  • @DataJdbcTest 测试基于Spring Data JDBC的数据库操作
  • @JsonTest 测试JSON的序列化和反序列化
  • @WebMvcTest 测试Spring MVC中的controllers
  • @WebFluxTest 测试Spring WebFlux中的controllers
  • @RestClientTest 测试对REST客户端的操作
  • @DataLdapTest 测试对LDAP的操作
  • @DataMongoTest 测试对MongoDB的操作
  • @DataNeo4jTest 测试对Neo4j的操作

通常情况下使用@SpringBootTest即可,可以扫描到工程中所有Spring配置,其他的有需要的,可以通过下面的AutoConfigurexxx的注解再添加相应的配置

@AutoConfigureXXXX等

  • @AutoConfigureJdbc 自动配置JDBC
  • @AutoConfigureCache 自动配置缓存
  • @AutoConfigureDataLdap 自动配置LDAP
  • @AutoConfigureJson 自动配置JSON
  • @AutoConfigureJsonTesters 自动配置JsonTester
  • @AutoConfigureDataJpa 自动配置JPA
  • @AutoConfigureTestEntityManager 自动配置TestEntityManager
  • @AutoConfigureRestDocs 自动配置Rest Docs
  • @AutoConfigureMockRestServiceServer 自动配置 MockRestServiceServer
  • @AutoConfigureWebClient 自动配置 WebClient
  • @AutoConfigureWebFlux 自动配置 WebFlux
  • @AutoConfigureWebTestClient 自动配置 WebTestClient
  • @AutoConfigureMockMvc 自动配置 MockMvc
  • @AutoConfigureWebMvc 自动配置WebMvc
  • @AutoConfigureDataNeo4j 自动配置 Neo4j
  • @AutoConfigureDataRedis 自动配置 Redis
  • @AutoConfigureJooq 自动配置 Jooq
  • @AutoConfigureTestDatabase 自动配置Test Database,可以使用内存数据库

这些注解可以搭配@xxxTest使用,用于开启在@xxxTest中未自动配置的功能。例如@SpringBootTest和@AutoConfigureMockMvc组合后,就可以注入org.springframework.test.web.servlet.MockMvc

小结

Spring Boot Test为我们提供了较为方便的单元测试工具,可以方便的获取我们在程序中配置的Bean,除此之外,还提供了Mock功能和WebMvc测试、MockMvc等功能,配合JsonPath Expressions可以对collector层、service层进行方便的单元测试,这些我们下次再进行介绍。

本文转载自: 掘金

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

起底JVM内存管理及性能调优【80+页Keynote私享】

发表于 2021-07-06

本文出自二爷箱底下的一份陈年文稿,近日整理资料时被倒腾出来。从内容上看,内容都还没有太过时,于是稍作调整后分享给掘金的同学。不过,限于二爷的水平,文稿难免存在错误,欢迎评论区批评指正。

由于图片较多(86张),可能引起部分同学不适,你可以直接划拉到本文底部,有PDF版本的获取提示,可以直接获取PDF版本阅读、收藏。

起底JVM内存管理及性能调优0706.001.png

起底JVM内存管理及性能调优0706.002.png

起底JVM内存管理及性能调优0706.003.png

起底JVM内存管理及性能调优0706.004.png

起底JVM内存管理及性能调优0706.005.png

起底JVM内存管理及性能调优0706.006.png

起底JVM内存管理及性能调优0706.007.png

起底JVM内存管理及性能调优0706.008.png

起底JVM内存管理及性能调优0706.009.png

起底JVM内存管理及性能调优0706.010.png

起底JVM内存管理及性能调优0706.011.png

起底JVM内存管理及性能调优0706.012.png

起底JVM内存管理及性能调优0706.013.png

起底JVM内存管理及性能调优0706.014.png

起底JVM内存管理及性能调优0706.015.png

起底JVM内存管理及性能调优0706.016.png

起底JVM内存管理及性能调优0706.017.png

起底JVM内存管理及性能调优0706.018.png

起底JVM内存管理及性能调优0706.019.png

起底JVM内存管理及性能调优0706.020.png

起底JVM内存管理及性能调优0706.021.png

起底JVM内存管理及性能调优0706.022.png

起底JVM内存管理及性能调优0706.023.png

起底JVM内存管理及性能调优0706.024.png

起底JVM内存管理及性能调优0706.025.png

起底JVM内存管理及性能调优0706.026.png

起底JVM内存管理及性能调优0706.027.png

起底JVM内存管理及性能调优0706.028.png

起底JVM内存管理及性能调优0706.029.png

起底JVM内存管理及性能调优0706.030.png

起底JVM内存管理及性能调优0706.031.png

起底JVM内存管理及性能调优0706.032.png

起底JVM内存管理及性能调优0706.033.png

起底JVM内存管理及性能调优0706.034.png

起底JVM内存管理及性能调优0706.035.png

起底JVM内存管理及性能调优0706.036.png

起底JVM内存管理及性能调优0706.037.png

起底JVM内存管理及性能调优0706.038.png

起底JVM内存管理及性能调优0706.039.png

起底JVM内存管理及性能调优0706.040.png

起底JVM内存管理及性能调优0706.041.png

起底JVM内存管理及性能调优0706.042.png

起底JVM内存管理及性能调优0706.043.png

起底JVM内存管理及性能调优0706.044.png

起底JVM内存管理及性能调优0706.045.png

起底JVM内存管理及性能调优0706.046.png

起底JVM内存管理及性能调优0706.047.png

起底JVM内存管理及性能调优0706.048.png

起底JVM内存管理及性能调优0706.049.png

起底JVM内存管理及性能调优0706.050.png

起底JVM内存管理及性能调优0706.051.png

起底JVM内存管理及性能调优0706.052.png

起底JVM内存管理及性能调优0706.053.png

起底JVM内存管理及性能调优0706.054.png

起底JVM内存管理及性能调优0706.055.png

起底JVM内存管理及性能调优0706.056.png

起底JVM内存管理及性能调优0706.057.png

起底JVM内存管理及性能调优0706.058.png

起底JVM内存管理及性能调优0706.059.png

起底JVM内存管理及性能调优0706.060.png

起底JVM内存管理及性能调优0706.061.png

起底JVM内存管理及性能调优0706.062.png

起底JVM内存管理及性能调优0706.063.png

起底JVM内存管理及性能调优0706.064.png

起底JVM内存管理及性能调优0706.065.png

起底JVM内存管理及性能调优0706.066.png

起底JVM内存管理及性能调优0706.067.png

起底JVM内存管理及性能调优0706.068.png

起底JVM内存管理及性能调优0706.069.png

起底JVM内存管理及性能调优0706.070.png

起底JVM内存管理及性能调优0706.071.png

起底JVM内存管理及性能调优0706.072.png

起底JVM内存管理及性能调优0706.073.png

起底JVM内存管理及性能调优0706.074.png

起底JVM内存管理及性能调优0706.075.png

起底JVM内存管理及性能调优0706.076.png

起底JVM内存管理及性能调优0706.077.png

起底JVM内存管理及性能调优0706.078.png

起底JVM内存管理及性能调优0706.079.png

起底JVM内存管理及性能调优0706.080.png

起底JVM内存管理及性能调优0706.081.png

起底JVM内存管理及性能调优0706.082.png

起底JVM内存管理及性能调优0706.083.png

起底JVM内存管理及性能调优0706.084.png

起底JVM内存管理及性能调优0706.086.png

PDF版本获取方式

《起底JVM内存管理与性能调优》

  • 下载链接: pan.baidu.com/s/1L3_MGmZ4…
  • 提取码: 4cnk

如果提取码失效(30天),请评论区留言或关注公众号【MetaThoughts】回复“02”获取最新提取码。

下载成功的同学记得回来点个赞,二爷给你鞠躬致谢了🌹!

其他品质干货推荐:

  • 《美丽代码的秘密-《重构》如何让你的代码和你一样赏心悦目》
  • 《写给JAVA开发者的自动化测试不完全指南【90+页Keynote纯享干货】》

本文转载自: 掘金

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

重度“懒癌”的我写了4000多行Shell脚本,终于实现了一

发表于 2021-07-06

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」

作者简介

  • 作者:LuciferLiu,中国DBA联盟(ACDU)成员。
  • 目前从事Oracle DBA工作,曾从事 Oracle 数据库开发工作,主要服务于生产制造,汽车金融等行业。
  • 现拥有Oracle OCP,OceanBase OBCA认证,擅长Oracle数据库运维开发,备份恢复,安装迁移,Linux自动化运维脚本编写等。

镇楼

前言

作为IT人,相信大家多多少少都接触使用过Oracle数据库,但是很少有人安装过Oracle数据库,因为这种活一般都是DBA干的,比如博主😬。那么,如果自己想安装一套Oracle数据库进行测试,如何安装呢?

  • 首先想要成功安装一套Oracle数据库,至少需要满足以下几个条件:
  • 了解基础的硬件资源配置:硬盘,内存,CPU等
  • 熟悉如何安装Linux系统,包括多种虚拟机的使用
  • 熟悉Linux常用命令和系统服务
  • 熟悉Oracle数据库的整体安装流程,可参考官方文档
  • 具体步骤可以参考:
  • 安装Linux操作系统
  • 配置存储,网络
  • 配置操作系统服务和参数等
  • 配置Oracle相关用户目录等
  • 上传解压安装介质
  • 安装Oracle软件
  • 创建数据库实例

安装部署流程

  • 可参考文档:
  • Windows主机如何玩转虚拟机Linux安装
  • 教你三步在MacOS上安装Linux系统
  • 手把手教你Linux安装Oracle数据库
  • 一步步教你Linux7安装Oracle RAC

如果本篇文章只是如此,不免过于标题党,俗话说的好,抛转引玉,接下来就介绍下本文的主角:

  • Oracle一键安装脚本,建库只需短短一行命令,一杯茶的功夫,敲代码的同时也不忘养生。

Oracle一键安装

一、介绍

俗说得好: “懒人”推动世界的发展。 既然能用脚本解决的事情,为什么还要那么麻烦,干就完事儿了。

1 功能介绍

  • 本脚本有哪些功能?支持哪些版本?有哪些参数?不急,功能太多,待我慢慢道来:
  • 支持Oracle版本:11GR2、12C、18C、19C
  • 支持Linux版本(x86_64):6、7、8
  • 支持安装模式:单机,单机集群,RAC
  • 帮助命令查看参数
  • 安装日志记录
  • 配置操作系统
  • 安装Grid软件
  • 安装Oracle软件
  • 安装PSU&&RU补丁
  • 创建数据库

2 参数介绍

  • 本脚本通过参数来预配置脚本命令,可通过帮助命令来查看有哪些参数:

执行 ./OracleShellInstall –help 可以查看参数:

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
bash复制代码-i,	--PUBLICIP			PUBLICIP NETWORK ADDRESS
-n, --HOSTNAME HOSTNAME(orcl)
-o, --ORACLE_SID ORACLE_SID(orcl)
-c, --ISCDB IS CDB OR NOT(TRUE|FALSE)
-pb, --PDBNAME PDBNAME
-op, --ORAPASSWD ORACLE USER PASSWORD(oracle)
-b, --ENV_BASE_DIR ORACLE BASE DIR(/u01/app)
-s, --CHARACTERSET ORACLE CHARACTERSET(ZHS16GBK|AL32UTF8)
-rs, --ROOTPASSWD ROOT USER PASSWORD
-gp, --GRIDPASSWD GRID USER PASSWORD(oracle)
-pb1, --RAC1PUBLICIP RAC NODE ONE PUBLIC IP
-pb2, --RAC2PUBLICIP RAC NODE SECONED PUBLIC IP
-vi1, --RAC1VIP RAC NODE ONE VIRTUAL IP
-vi2, --RAC2VIP RAC NODE SECOND VIRTUAL IP
-pi1, --RAC1PRIVIP RAC NODE ONE PRIVATE IP
-pi2, --RAC2PRIVIP RAC NODE SECOND PRIVATE IP
-pi3, --RAC1PRIVIP1 RAC NODE ONE PRIVATE IP
-pi4, --RAC2PRIVIP1 RAC NODE SECOND PRIVATE IP
-puf, --RACPUBLICFCNAME RAC PUBLIC FC NAME
-prf, --RACPRIVFCNAME RAC PRIVATE FC NAME
-prf1, --RACPRIVFCNAME1 RAC PRIVATE FC NAME
-si, --RACSCANIP RAC SCAN IP
-dn, --ASMDATANAME RAC ASM DATADISKGROUP NAME(DATA)
-on, --ASMOCRNAME RAC ASM OCRDISKGROUP NAME(OCR)
-dd, --DATA_BASEDISK RAC DATADISK DISKNAME
-od, --OCRP_BASEDISK RAC OCRDISK DISKNAME
-or, --OCRREDUN RAC OCR REDUNDANCY(EXTERNAL|NORMAL|HIGH)
-dr, --DATAREDUN RAC DATA REDUNDANCY(EXTERNAL|NORMAL|HIGH)
-tsi, --TIMESERVERIP RAC TIME SERVER IP
-txh --TuXingHua Tu Xing Hua Install
-udev --UDEV Whether Auto Set UDEV
-dns --DNS RAC CONFIGURE DNS(Y|N)
-dnss --DNSSERVER RAC CONFIGURE DNSSERVER LOCAL(Y|N)
-dnsn --DNSNAME RAC DNSNAME(orcl.com)
-dnsi --DNSIP RAC DNS IP
-m, --ONLYCONFIGOS ONLY CONFIG SYSTEM PARAMETER(Y|N)
-g, --ONLYINSTALLGRID ONLY INSTALL GRID SOFTWARE(Y|N)
-w, --ONLYINSTALLORACLE ONLY INSTALL ORACLE SOFTWARE(Y|N)
-ocd, --ONLYCREATEDB ONLY CREATE DATABASE(Y|N)
-gpa, --GRID RELEASE UPDATE GRID RELEASE UPDATE(32072711)
-opa, --ORACLE RELEASE UPDATE ORACLE RELEASE UPDATE(32072711)

看到上面的参数,是否感觉参数太多,但是这些参数都有用,容我一个个慢慢道来:

  • -i 全称 PUBLICIP:当前主机用于访问的IP,必填参数。

使用方式:-i 10.211.55.100

  • -n 全称 HOSTNAME:当前主机的主机名,默认值为 orcl。

使用方式:-n orcl

如果选择rac模式,节点1、2主机名自动取为:orcl01、orcl02。

rac主机名

  • -o 全称 ORACLE_SID:Oracle实例名称,默认值为 orcl。

使用方式:-o orcl

  • -c 全称 ISCDB:判断是否为CDB模式,11GR2不支持该参数,默认值为FALSE。

使用方式:-c TRUE

  • -pb 全称 PDBNAME:创建PDB的名称,11GR2不支持该参数。

使用方式:-pb pdb01

  • -op 全称 ORAPASSWD:oracle用户的密码,默认值为oracle。

使用方式:-op oracle

  • -b 全称 ENV_BASE_DIR:Oracle基础安装目录,默认值为/u01/app。

使用方式:-b /u01/app

  • -s 全称 CHARACTERSET:Oracle数据库字符集,默认值为AL32UTF8。

使用方式:-s AL32UTF8

以下为RAC模式安装的参数:

  • -rs 全称 ROOTPASSWD:root用户的密码,默认值为oracle。

使用方式:-rs oracle

  • -gp 全称 GRIDPASSWD:grid用户的密码,默认值为oracle。

使用方式:-gp oracle

  • -pb1 全称 RAC1PUBLICIP:节点一的主机访问IP,必填参数。

使用方式:-pb1 10.211.55.100

  • -pb2 全称 RAC2PUBLICIP:节点二的主机访问IP,必填参数。

使用方式:-pb2 10.211.55.101

  • -vi1 全称 RAC1VIP:节点一的主机虚拟IP,必填参数,与主机访问IP网段必须相同。

使用方式:-vi1 10.211.55.102

  • -vi2 全称 RAC2VIP:节点二的主机虚拟IP,必填参数 ,与主机访问IP网段必须相同。

使用方式:-vi2 10.211.55.103

  • -pi1 全称 :RAC1PRIVIP,节点一的主机私有IP,必填参数 ,可凭借喜好进行自定义。

使用方式:-pi1 10.10.1.1

  • -pi2 全称 :RAC2PRIVIP,节点二的主机私有IP,必填参数,可凭借喜好进行自定义。

使用方式:-pi2 10.10.1.2

  • -pi3 全称 :RAC1PRIVIP1,节点一的第二个主机私有IP,可选参数 ,可凭借喜好进行自定义。

使用方式:-pi3 1.1.1.1

  • -pi4 全称 :RAC2PRIVIP1,节点二的第二个主机私有IP,可选参数 ,可凭借喜好进行自定义。

使用方式:-pi4 1.1.1.2

  • -puf 全称 :RACPUBLICFCNAME,主机的访问IP对应的网卡名称,必填参数 ,节点1,2必须名称一致。

使用方式:-puf eth0

  • -prf 全称 RACPRIVFCNAME:主机的私有IP对应的网卡名称,必填参数 ,节点1,2必须名称一致。

使用方式:-prf eth1

  • -prf1 全称 RACPRIVFCNAME1:主机的第二私有IP对应的网卡名称,可选参数 ,节点1,2必须名称一致。

使用方式:-prf1 eth2

  • -si 全称 RACSCANIP:主机的SCANIP,必填参数 ,与主机访问IP网段必须相同。当配置DNS解析时,最多可支持填写3个IP,通过逗号隔开。

使用方式:-si 10.211.55.104,10.211.55.105,10.211.55.106

  • -dn 全称 ASMDATANAME:ASM数据盘名称,默认值为DATA。

使用方式:-dn DATA

  • -on 全称 ASMOCRNAME:ASM裁决盘名称,默认值为OCR。

使用方式:-on OCR

  • -dd 全称 DATA_BASEDISK:数据盘对应的磁盘名称,必填参数 。支持多块磁盘填写,用逗号隔开。

使用方式:-dd /dev/sdb,/dev/sdc,/dev/sdd

  • -od 全称 OCR_BASEDISK:裁决盘对应的磁盘名称,必填参数 。支持多块磁盘填写,用逗号隔开。

使用方式:-od /dev/sde,/dev/sdf

  • -or 全称 OCRREDUN:裁决盘的冗余选项,默认值为EXTERNAL。冗余选项EXTERNAL、NORMAL、HIGH对应磁盘最小数量为1、3、5。

使用方式:-or EXTERNAL

  • -dr 全称 OCRREDUN:裁决盘的冗余选项,默认值为EXTERNAL。冗余选项EXTERNAL、NORMAL、HIGH对应磁盘最小数量为1、2、3。

使用方式:-dr EXTERNAL

  • -tsi 全称 TIMESERVERIP:时间同步服务器IP,可选参数 ,根据实际情况进行填写。

使用方式:-tsi 10.211.55.200

  • -txh 全称 TuXingHua:图形化界面安装,默认值为N。选择Y后将安装图形化界面所需依赖。

使用方式:-txh Y

  • -udev 全称 UDEV:自动配置multipath+UDEV绑盘,默认值为Y。

使用方式:-udev Y

以下参数为配置DNS解析:

  • -dns 全称 DNS:配置DNS解析,默认值为N。

使用方式:-dns N

  • -dnss 全称 DNSSERVER:当前主机配置为DNS服务器,默认值为N。前提是 -dns Y 才生效。

使用方式:-dnss N

  • -dnsn 全称 DNSNAME:DNS服务器的解析名称,前提是 -dns Y 才生效。

使用方式:-dnsn orcl.com

  • -dnsi 全称 DNSIP:DNS服务器的IP,前提是 -dns Y 才生效。

使用方式:-dnsi 10.211.55.200

  • -m 全称 ONLYCONFIGOS:仅配置操作系统参数,默认值为N。值为Y时,脚本只执行到操作系统配置完成就结束,不会进行安装,通常可用于图形化安装的初始化。

使用方式:-m Y

  • -g 全称 ONLYINSTALLGRID:仅安装Grid软件,默认值为N。

使用方式:-g Y

  • -w 全称 ONLYINSTALLORACLE:仅安装Oracle软件,默认值为N。

使用方式:-w Y

  • -ocd 全称 ONLYCREATEDB:仅创建Oracle数据库实例,默认值为N。

使用方式:-ocd Y

  • -gpa 全称 GRID RELEASE UPDATE:Grid软件的PSU或者RU补丁的补丁号。

使用方式:-gpa 32072711

  • -opa 全称 ORACLE RELEASE UPDATE:Oracle软件的PSU或者RU补丁的补丁号。

使用方式:-opa 32072711

通过以上的参数介绍,相信大家对脚本的功能已经一览无余了,可以说是非常强大。是不是已经心动不如行动,想要尝试下进行安装了呢?接下来将介绍如何使用脚本。

二、使用

既然已经了解脚本的功能和参数,接下来就是了解如何使用脚本。

脚本流程图

直接上命令: ./OracleShellInstall.sh -i 10.211.55.100

Notes: 最便捷安装方式,默认参数不设置,只需加上主机IP,即可一键安装Oracle数据库。

注意:脚本下载地址见文末

1 创建软件目录,例如:/soft

1
bash复制代码mkdir /soft

2 挂载Linux安装镜像

1
2
3
4
bash复制代码## 1.通过cdrom挂载
mount /dev/cdrom /mnt
## 2.通过安装镜像源挂载
mount -o loop /soft/rhel-server-7.9-x86_64-dvd.iso /mnt

镜像挂载

3 上传安装介质和脚本到软件目录

1
2
3
4
5
6
7
bash复制代码## 一键安装shell脚本
140K OracleShellInstall.sh
## oracle 11GR2官方安装包
1.3G p13390677_112040_Linux-x86-64_1of7.zip
1.1G p13390677_112040_Linux-x86-64_2of7.zip
## 授权脚本执行权限
chmod +x OracleShellInstall.sh

安装介质

4 执行安装:

1
css复制代码./OracleShellInstall.sh -i 10.211.55.100

执行安装

等待5-10分钟左右,安装成功。

安装成功提示)数据库信息

5 数据库连接使用

不知道如何安装PLSQL的同学,可以参考:零基础如何玩转PL/SQL DEVELOPER?

  • 创建连接用户:

创建连接用户

  • plsql连接:

plsql连接

测试数据

通过如上简单的使用教程,轻松安装Oracle数据库,大大缩减人工和时间成本。

三、示例

1 单实例安装

1
2
3
4
5
6
7
bash复制代码./OracleShellInstall.sh -i 10.211.55.100 `#Public ip`\
-n orcl `# hostname`\
-o orcl `# oraclesid`\
-op oracle `# oracle user password`\
-b /u01/app `# install basedir`\
-s AL32UTF8 `# characterset`\
-opa 31537677 `# oracle psu number`

2 RAC安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bash复制代码./OracleShellInstall.sh -i 10.211.55.100 `#Public ip`\
-n rac `# hostname`\
-rs oracle `# root password`\
-op oracle `# oracle password`\
-gp oracle `# grid password`\
-b /u01/app `# install basedir`\
-o orcl `# oraclesid`\
-s AL32UTF8 `# characterset`\
-pb1 10.211.55.100 -pb2 10.211.55.101 `# node public ip`\
-vi1 10.211.55.102 -vi2 10.211.55.103 `# node virtual ip`\
-pi1 10.10.1.1 -pi2 10.10.1.2 `# node private ip`\
-puf eth0 -prf eth1 `# network fcname`\
-si 10.211.55.105 `# scan ip`\
-dd /dev/sde,/dev/sdf `# asm data disk`\
-od /dev/sdb,/dev/sdc,/dev/sdd `# asm ocr disk`\
-or EXTERNAL `# asm ocr redundancy`\
-dr EXTERNAL `# asm data redundancy`\
-on OCR `# asm ocr diskgroupname`\
-dn DATA `# asm data diskgroupname`\
-gpa 32580003 `# GRID PATCH`

如果能够合理使用该脚本,可以在Linux系统轻松安装Oracle数据库,释放双手,养生敲代码不是梦!!!

更多更详细的脚本使用方式可以订阅专栏:Oracle一键安装脚本。

  • 15分钟!一键部署Oracle 12CR2单机CDB+PDB
  • 20分钟!一键部署Oracle 18C单机CDB+PDB
  • 25分钟!一键部署Oracle 11GR2 HA 单机集群
  • 30分钟!一键部署Oracle 19C单机CDB+PDB
  • 1.5小时!一键部署Oracle 11GR2 RAC 集群

脚本获取方式:

  • GitHub 持续保持更新中🔥
  • Gitee 持续保持更新中🔥

本次分享到此结束啦~

如果觉得文章对你有帮助,点赞、收藏、关注、评论,一键四连支持,你的支持就是我创作最大的动力。

本文转载自: 掘金

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

1…618619620…956

开发者博客

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