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

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


  • 首页

  • 归档

  • 搜索

200人面试经验,都在这里了 —1— 准备面试 —2— 请介

发表于 2020-11-27

成长&认知 丨 作者 / 袁吴范

这是pointers公众号分享的第16篇原创文章

面试,想必所有人都经历过。可以说,面试是相当重要的,因为他决定了你的起点。

从业这么多年,到现在面试的人数应该不下200人,看过的简历更是超过1000+。

那么这篇文章就从面试官的角度,分享一些面试经验给大家。


作者简介:

大厂技术总监,分享职业发展、技术管理、职场晋升、技术成长等个人多年经验和心得。一起成长!关注公众号pointers,回复:【网盘】,还可获得pandownload复活版,下载速度60M/S。

—1—

准备面试

面试的过程原本就是双方互相选择的过程。

面试者了解岗位要求,公司所处行业,业务和产品情况。

面试官了解对方是否符合招聘要求。

互联网的头部企业不管从福利待遇,工资水平、还是技术氛围、成长潜力、培养机制都在行业内首屈一指的,这也就让程序员们趋之若鹜。

如果你面试的是这些企业的岗位,对你来说恐怕这不是一个公平的选择过程,特别是应届毕业生来说,基本上是没有任何溢价的能力。

这个所谓的”公平“,是建立在你对公司有价值的基础上,你身上有企业看得上的闪光点。

例如,你的技术能力,项目管理水平,团队管理能力等等。

作为普通程序员的你,最重要的一条心法,就是要详细了解对方,匹配自己优势,然后自信的面试。

想象下这样一种场景:

你终于把你的女神约出来了,你应该做好哪些准备呢?

你是不是提前需要了解她喜欢喝什么?是奶茶,还是咖啡?

对方喜欢吃什么风味的菜,是日料,韩料,火锅,港餐,还是西餐?

对方的爱好是什么?看电影,还是喜欢看书?听音乐?

你要把妹子的方方面面都了解清楚。

每个男人应该都会这样去做的,这是非常常规,大众都能想到的方式。

找对象和找工作一样,一定要匹配才行,颜值要和职业形象不能差太远。否则你不在乎,会让人家感到不舒服。

对应到面试,其实也是一样的,就是跟你女神约会,也需要做好充分的准备。

接下来的事情就是怎样做准备了。

第一步就是要清楚企业需要怎样的人

可以通过研究岗位职责、职位描述、浏览公司网站、app产品等方式判断产品的用户量,可能用到的技术,从而做相应的准备。

第二步,了解对方公司和岗位怎么样

全面深入地了解对方的公司,包括公司业务发展历程、产品愿景、迭代周期、团队风格、技术氛围、公司目前遇到的问题和挑战等。

总之,尽可能多地详细了解对方,然后针对性地进行面试。

大致可以把职责要求分2类。

专业要求和胜任素质要求。

专业要求又可以分为专业知识和专业技能。

专业知识就是对完成工作有用的事实性及经验性信息

专业技能就是知识掌握程度及应用技能的能力

举一个面试例子:

你对Linux系统中,你最熟悉哪一块?具体怎么学的?这块内容能够具体的展开讲下吗?

另一个就是胜任素质要求,其中又包括了:思维能力、主动承担、脚踏实地、激情进取、坚韧不拔。

思维能力重点考察你的分析能力和归纳能力,也就是事物看清,看透的能力和提炼、总结的能力。

主动承担重点考察是否对自己行为和结果负责,遇到边界工作不推诿。

脚踏实地主要考察你做事是否应付了事,注重实效,不浮于表面。

激情进取主要考察是否对工作充满激情精力充沛

例如:

请介绍一个你主动为自己设立的具有挑战性的目标,为什么觉得这是一个挑战性的目标,为什么给自己设置这个目标?

坚韧不拔主要考察是否积极对待出现的问题,面对困难坚韧不拔。

例如:

_分享一件你觉得压力特别大的事情,为什么压力大?你做了哪些来调节?
_

你经过多年努力而获得的一项技能是什么?你是怎么做到的?花了多长时间?为什么坚持下来了?

那么接下来咱们就具体的典型问题,进行分析,展示出其问题的内核。


—2—

请介绍下自己

面试的本质就是将自己的优势展示给对方,并且满足对方的核心需求,甚至超出。

这个过程需要双方展示出诚意。

招聘方表现诚意的方式是对人才求贤若渴的态度。

面试者最佳的展示诚意的方式就是你很想通过这次面试环节,获得招聘岗位名额。

那怎么做呢?回答好每一个面试官的问题,第一个问题往往都是“你好,请介绍下自己”。

自我介绍本身并不难,我们认识新的朋友时都会自我介绍。这个过程就是对方对你大致了解的过程。

为什么简历上都写的?还要进行3到5分钟的自我介绍呢?

第一个原因就是对你进行熟悉。

很多公司面试官,本身可能是部门主管,肯定是非常忙的,对应聘者并不熟悉,可能你的简历在他手上才几分钟,HR就安排了就这个面试。

在你自我介绍的几分钟之内,同时也在翻阅你的简历,对你进行熟悉。

第二个原因就是了解你的语音表达能力、总结概括能力。

让你做自我介绍,还可以通过你介绍的过程,看到你的语言表达能力、总结概括能力、逻辑思维能力等等。

在你做自我介绍的时候,如果你讲得一塌糊涂,能指望你工作以后和同事沟通效率高吗。

自我介绍前言不搭后语,乱七八糟不知所谓,能指望你工作以后这个要做的事情安排的先后序井井有条吗?

我曾经面试过这样的人。

我:请麻烦你简单的介绍下自己。

他:我叫xx,我工作3年,一直从事软件开发,其他的简历上都有的,就这样。

很快的就结束了。

我都没准备好,连忙补充到:请你对你负责的业务详细展开讲下。

这次面试,我对面试者的第一印象,首先已经打了折扣。虽然还不至于直接pass,但至少已经失去了一些印象分了。以后他和同事共事,沟通上肯定有很大的隐患。

总之,越简单的问题,越不可大意,背后有大道理和深层次的逻辑。


—3—

你遇到的最大困难是什么?

“你遇到最大的困难是什么?你是如何解决的?”

作为面试中的高频题型,常常令应聘者头大。

面试官到底想考察什么?回答的侧重点又是什么?

从面试官的角度来看,这个问题的重点并不是你经历的困难本身,而是考察你面对困难所做的思考和努力以及你解决问题的能力。

事情的结果没有那么重要,克服困难固然值得鼓励,但没能顺利解决也不意味着一无所获,如何从失败的经历中总结经验并有效指导接下来的工作才是一个优秀员工应该具备的基本素质。

我也分享一个我曾经面试的人。

他自述解决了一个系统崩溃的问题,当我询问解决的细节时,支支吾吾,遮遮掩掩,回答不到问题的核心。最后追问得知,这个问题其实是他同事解决的。

切记不要说一些自己没有把握的话,

因为很多工作只有你真正的参与其中,才能够准确说出其中的难点。

当面试官就某一个困难深度挖掘时,你的弱点以及不诚实就会毫无保留的暴露出来,最终导致前面的努力功亏一篑。

整体要把握住“实在比过分夸大更有效”的回答原则,可以适当增加困难程度,衬托出自己做事能力,但不能过分夸大。

这个问题的核心方法就是要闭环**:一、遇事不乱,冷静分析已有困难;二、展现综合能力,把你的能力表现出来。三、最后进行总结和反思**。

总之,这个问题主要考察问题解决能力、随机应变能力。

什么是最大的困难?(目标很高或情况紧急;STAR法则,把控细节)。

解决的方法比结果更重要


—4—

请说下职业规划

很多面试者,听到这个问题,一时脑袋一片空白。自己根本没有想过这个问题,不知道如何回答。这个问题其实就是考察两点。

第一,自我认知:我是谁?我从哪里来?到哪里去?

我是谁,什么意思?

就是自我认知,了解自己发展的定位,了解自己的优劣势机遇挑战,明确自身发展的需要及工作定位,细分下岗位职责等

我从哪里来?什么意思?

就是经过多年以后,你是否坚持当初的理想,是否迷失了自己。

我到哪里去?

你是否有自己清晰的目标,是否有对自己未来发展的设想、职业生涯的规划。

第二、考察稳定性:即组织承诺,你到底能在公司踏实干几年?

想象下,招聘的岗位是开发,当你回答职业规划时,表示明年想干项目经理。作为面试官,你会怎么想?

现在你知道怎么回答了吗?,重要的不是回答,而是如何准备。


—5—

你为什么要离职?

离职原因,无非就是:

**主动离职,**原因无非就是钱少、活多、离家远、晋升遇到瓶颈、行业发展慢、工作环境差、领导同事不好相处,等。

**被动离职,**原因无非就是公司倒闭、裁员、末位淘汰等。

这些原因都是可以被理解的。问题的关键不在于原因,而是给出一个合理的解释。就是重点是结合:

  1. 你之前的背景
  2. 你上一份工作情况
  3. 你现在面试的岗位

给一个合理的解释。

举一个我面试的例子。

有个面试者说他看不到晋升的可能,他从华为跳槽我们公司,这个解释是无法自圆其说的。因为华为的晋升体系是非常完善的,无法晋升的唯一可能性就是你的能力差。

很多同学说,离职原因不能说是因为钱少。我的答案是“不一定”

在我面试200个人中,大概有四分之一都是因为这个原因,本身这并不代表什么,每个人都是社会中的人,必须要考虑现实问题,我觉得是合理的。前提是你的能力必须和薪资水平保持匹配。


—6—

请有什么要问我的?

“我问完了,你有什么要问我吗?”

面试临近结束的时候,其实是一个特别好的机会。

有些同学在面试结束时,神经开始松懈,以为终于结束了。

其实,真正的考验,往往在最后出现。

你可以千万不要问下面的问题。

公司交社保吗?加班严重吗?同事年纪都是多大?一年有多少年假?

这些问题不是说不好,而是这些问题都太简单了,都是“百度一下,你就知道”的内容。

你可以这样提问:

如果我有幸加入贵公司/团队/部门,那么前期我将主要负责哪方面的工作内容呢?

目前最紧要的任务是什么?

如果我有幸应聘成功,团队对我会有哪些期望呢,

公司对我面试的这个岗位的定位是什么?

最希望这个人达成的工作指标有哪些?

公司是一群什么样的小伙伴,这个岗位所在的的团队是什么氛围?

您对我的评价是什么,您觉得我的优势和劣势是什么?

这些问题将会帮助你更好的了解到入职之后的主要工作,同时也可以让面试官了解到你对这份工作的热情与期待。


—7—

最后的总结

记住这个心法:详细了解对方,匹配自己优势,回答好重点问题,自信面试。

祝你能找到最适合自己的工作。献上!

推荐阅读(干货)

程序员成为高级管理者的三次跃升(推荐)

程序员如何打造个人品牌?

程序员你应该勇敢说不

聊一聊 软件系统中的“热力学第二定律”

谈一谈程序员的职业发展路线

送给刚毕业的程序员——7点建议

程序员进阶技术专家必备能力——深度思考

觉得不错,记得关注、转发和在看!多年经验分享,实属不易,感谢支持!


如果你有技术成长的瓶颈、对未来迷茫、关注我,帮你答疑解惑!


本文转载自: 掘金

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

带 JIT 的 PHP 8 发布了! 我们来跑个分~

发表于 2020-11-27

PHP8 Benchmark the Brainfuck Way

这篇文章的 demo 源码可以在我的 Github 找到: github.com/karminski/P…

PHP8 已经发布了, 带来了全新的, 令人兴奋的 JIT 编译引擎. 根据官方文档描述: “它在综合基准测试中的性能提高了大约 3 倍, 在某些特定的长期运行的应用程序中提高了 1.5-2 倍. 典型的应用程序性能与 PHP 7.4 相当.”.

官方性能测试结果如下图:

那么实际情况如何?

于是我写了个简单的跑分 demo, 是一个从 Go 语言移植过来的 brainfuck 语言 解释器.

源码见: run.php. 里边顺便还包含了 brainfuck 实现的 曼德博集合(Mandelbrot set). 程序即用 PHP 编写的 brainfuck 语言解释器运行 brainfuck 编写的生成曼德博集合的程序. 然后计时并比较其性能.

我的机器配置为: Intel(R) Xeon(TM) E5-2680v2 @2.8GHz.

PHP 8.0.0 没有开启J IT 测试结果

PHP 8.0.0 开启 JIT 测试结果

结论

从测试数据看, 未开启 JIT 耗时 6m20.973s, 开启 JIT 耗时 2m48.527s. 单从这一测试结果讲, 性能提升了 2.26 倍. demo 程序大部分时间都在做 foo++;\`\`\`, \`\`\`foo--;, 数组查找并赋值这样的操作, 其中最多的 op\_inc\_dp 逻辑, 即 $dataPtr++; 执行了 4,453,036,023 次.

就本次测试结果而言, PHP 8 引入了 JIT 的性能提升是显著的. 这为 PHP 带来了新的可能性. 但同时我们也看到官方测试中, 一些复杂的应用 (比如WordPress) 提升很微弱. 目前还不知道是因为 JIT 仍处于初期阶段导致性能提升不显著, 还是因为 WordPress 由于是既存项目, 代码不可能为 JIT 专门优化过而导致性能提升不明显. 这部分还需要通过详细的 benchmark (比如使用火焰图) 来进行研判.

作为一个从 PHP 4 一路使用过来的老用户, 对 PHP 的感情是复杂的, PHP 现在十分缺乏一个强力的生态来重新唤醒. 而这需要广大 PHPer 的共同努力. 期待未来 PHP 能有更好的表现.

Reference

  • The Original brainfuck interpreter source code
  • The mandelbrot set fractal viewer in brainfuck written by Erik Bosman

License

  • The MIT License (MIT)

本文转载自: 掘金

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

有了 HTTP 协议,为什么还要 RPC 协议,两者有什么区

发表于 2020-11-26

很长时间以来都没有搞懂 RPC(即 Remote Procedure Call,远程过程调用)和 HTTP 调用的区别,不都是写一个服务然后在客户端调用么?这里请允许我迷之一笑~Naive!

本文简单地介绍一下两种形式的 C/S 架构,先说一下他们最本质的区别,就是 RPC 主要是基于 TCP/IP 协议的,而 HTTP 服务主要是基于 HTTP 协议的。

我们都知道 HTTP 协议是在传输层协议 TCP 之上的,所以效率来看的话,RPC 当然是要更胜一筹啦!下面来具体说一说 RPC 服务和 HTTP 服务。

OSI 网络七层模型

在说 RPC 和 HTTP 的区别之前,我觉的有必要了解一下 OSI 的七层网络结构模型(虽然实际应用中基本上都是五层)。

添加描述

它可以分为以下几层:(从上到下)

  • 第一层:应用层。定义了用于在网络中进行通信和传输数据的接口。
  • 第二层:表示层。定义不同的系统中数据的传输格式,编码和解码规范等。
  • 第三层:会话层。管理用户的会话,控制用户间逻辑连接的建立和中断。
  • 第四层:传输层。管理着网络中的端到端的数据传输。
  • 第五层:网络层。定义网络设备间如何传输数据。
  • 第六层:链路层。将上面的网络层的数据包封装成数据帧,便于物理层传输。
  • 第七层:物理层。这一层主要就是传输这些二进制数据。

添加描述

实际应用过程中,五层协议结构里面是没有表示层和会话层的。应该说它们和应用层合并了。

我们应该将重点放在应用层和传输层这两个层面。因为 HTTP 是应用层协议,而 TCP 是传输层协议。

好,知道了网络的分层模型以后我们可以更好地理解为什么 RPC 服务相比 HTTP 服务要 Nice 一些!

RPC 服务

从三个角度来介绍 RPC 服务,分别是:

  • RPC 架构
  • 同步异步调用
  • 流行的 RPC 框架

1、RPC 架构

先说说 RPC 服务的基本架构吧。我们可以很清楚地看到,一个完整的 RPC 架构里面包含了四个核心的组件。

分别是:

  • Client
  • Server
  • Client Stub
  • Server Stub(这个Stub大家可以理解为存根)

添加描述

分别说说这几个组件:

  • 客户端(Client),服务的调用方。
  • 服务端(Server),真正的服务提供者。
  • 客户端存根,存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
  • 服务端存根,接收客户端发送过来的消息,将消息解包,并调用本地的方法。

RPC 主要是用在大型企业里面,因为大型企业里面系统繁多,业务线复杂,而且效率优势非常重要的一块,这个时候 RPC 的优势就比较明显了。实际的开发当中是这么做的,项目一般使用 Maven 来管理。

比如我们有一个处理订单的系统服务,先声明它的所有的接口(这里就是具体指 Java 中的 Interface),然后将整个项目打包为一个 jar 包,服务端这边引入这个二方库,然后实现相应的功能,客户端这边也只需要引入这个二方库即可调用了。

为什么这么做?主要是为了减少客户端这边的 jar 包大小,因为每一次打包发布的时候,jar 包太多总是会影响效率。另外也是将客户端和服务端解耦,提高代码的可移植性。

2、同步调用与异步调用

什么是同步调用?什么是异步调用?同步调用就是客户端等待调用执行完成并返回结果。

异步调用就是客户端不等待调用执行完成返回结果,不过依然可以通过回调函数等接收到返回结果的通知。如果客户端并不关心结果,则可以变成一个单向的调用。

这个过程有点类似于 Java 中的 Callable 和 Runnable 接口,我们进行异步执行的时候,如果需要知道执行的结果,就可以使用 Callable 接口,并且可以通过 Future 类获取到异步执行的结果信息。

如果不关心执行的结果,直接使用 Runnable 接口就可以了,因为它不返回结果,当然啦,Callable 也是可以的,我们不去获取 Future 就可以了。

3、流行的 RPC 框架

目前流行的开源 RPC 框架还是比较多的。下面重点介绍三种:

①gRPC 是 Google 最近公布的开源软件,基于最新的 HTTP2.0 协议,并支持常见的众多编程语言。

我们知道 HTTP2.0 是基于二进制的 HTTP 协议升级版本,目前各大浏览器都在快马加鞭的加以支持。

这个 RPC 框架是基于 HTTP 协议实现的,底层使用到了 Netty 框架的支持。

②Thrift 是 Facebook 的一个开源项目,主要是一个跨语言的服务开发框架。它有一个代码生成器来对它所定义的 IDL 定义文件自动生成服务代码框架。

用户只要在其之前进行二次开发就行,对于底层的 RPC 通讯等都是透明的。不过这个对于用户来说的话需要学习特定领域语言这个特性,还是有一定成本的。

③Dubbo 是阿里集团开源的一个极为出名的 RPC 框架,在很多互联网公司和企业应用中广泛使用。协议和序列化框架都可以插拔是及其鲜明的特色。

同样的远程接口是基于 Java Interface,并且依托于 Spring 框架方便开发。可以方便的打包成单一文件,独立进程运行,和现在的微服务概念一致。

HTTP 服务

其实在很久以前,我对于企业开发的模式一直定性为 HTTP 接口开发,也就是我们常说的 RESTful 风格的服务接口。

的确,对于在接口不多、系统与系统交互较少的情况下,解决信息孤岛初期常使用的一种通信手段;优点就是简单、直接、开发方便。

利用现成的 HTTP 协议进行传输。我们记得之前本科实习在公司做后台开发的时候,主要就是进行接口的开发,还要写一大份接口文档,严格地标明输入输出是什么?说清楚每一个接口的请求方法,以及请求参数需要注意的事项等。

比如下面这个例子:

1
arduino复制代码POST http://www.baidu.com

接口可能返回一个 JSON 字符串或者是 XML 文档。然后客户端再去处理这个返回的信息,从而可以比较快速地进行开发。

但是对于大型企业来说,内部子系统较多、接口非常多的情况下,RPC 框架的好处就显示出来了,首先就是长链接,不必每次通信都要像 HTTP 一样去 3 次握手什么的,减少了网络开销。

其次就是 RPC 框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作。

总结

RPC 服务和 HTTP 服务还是存在很多的不同点的,一般来说,RPC 服务主要是针对大型企业的,而 HTTP 服务主要是针对小企业的,因为 RPC 效率更高,而 HTTP 服务开发迭代会更快。

总之,选用什么样的框架不是按照市场上流行什么而决定的,而是要对整个项目进行完整地评估,从而在仔细比较两种开发框架对于整个项目的影响,最后再决定什么才是最适合这个项目的。

一定不要为了使用 RPC 而每个项目都用 RPC,而是要因地制宜,具体情况具体分析。

Java 的知识面非常广,面试问的涉及也非常广泛,重点包括:Java 基础、Java 并发,JVM、MySQL、数据结构、算法、Spring、微服务、MQ 等等,涉及的知识点何其庞大,所以我们在复习的时候也往往无从下手,今天小编给大家带来一套 Java 面试题,题库非常全面,包括 Java 基础、Java 集合、JVM、Java 并发、Spring全家桶、Redis、MySQL、Dubbo、Netty、MQ 等等,包含 Java 后端知识点 2000 +

资料获取方式:关注公众号:“程序员白楠楠”获取

本文转载自: 掘金

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

接口不能被实例化,Mybatis的Mapper/Dao为什么

发表于 2020-11-26

接口不能被实例化,Mybatis的Mapper/Dao为什么却可以@Autowired注入?

对于我们 java 来说,接口是不能被实例化的。而且接口的所有方法都是public的。

可是为什么 Mybaits 的mapper 接口,可以直接 @Autowired 注入 使用?

接下来看看Mybatis 是如何做的。

基于SpringBoot 的 @MapperScan 注解入手,分析。

带着问题分析代码:

  1. Mybatis 的mapper接口,是怎么被扫描的?
  2. mapper接口是如何被实例化,然后可以使用@Autowired注入?

@MapperScan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

// 在使用MapperScan中,扫描包的路径。
// 填写的是 mapper 接口所在包名,对该value值下的所有文件进行扫描
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};


Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends Annotation> annotationClass() default Annotation.class;
Class<?> markerInterface() default Class.class;
String sqlSessionTemplateRef() default "";
String sqlSessionFactoryRef() default "";
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

}
1
2
3
4
5
6
7
8
9
10
java复制代码@SpringBootApplication
@MapperScan("cn.thisforyou.core.blog.mapper")
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
SpringApplication.run(App.class,args);
}
}

在SpringBoot中使用mybatis,那它的入口就在 @MapperScan中。@MapperScan注解,是在SpringBoot的启动类中。

@MapperScan中有个 @Import 注解。

@Import 注解,可以加载某个类,放到Spring的IOC中管理

在Spring中,要将Bean放到IOC容器中管理的话,有几种方式。

  • @Import 此种方法
  • @Configuration 与 @Bean 注解结合使用
  • @Controller @Service @Repository @Component
  • @ComponentScan 扫描。
  • 重写BeanFactoryPostProcessor 的postProcessBeanFactory()方法,也可以实现Bean的注入

MapperScannerRegistrar

通过@Import 注解,将MapperScannerRegistrar 注入到了IOC容器中,而MapperScannerRegistrar实现这两个接口,ImportBeanDefinitionRegistrar, ResourceLoaderAware

ImportBeanDefinitionRegistrar 在Spring需要配合@Impor使用,加载它的实现类,只有一个方法,是主要负责Bean 的动态注入的。

1
2
3
4
java复制代码public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

这个方法可以拿到 Spring的注册对象 BeanDefinitionRegistry 这也是一个接口,提供了好6、7个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public interface BeanDefinitionRegistry extends AliasRegistry
{
// 注册 BeanDefinition
void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException;

// 移除 BeanDefinition
void removeBeanDefinition(String var1) throws NoSuchBeanDefinitionException;

// 根据name 获取一个 BeanDefinition
BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException;

// 判断 是否存在
boolean containsBeanDefinition(String var1);

// BeanDefinition 的适量
String[] getBeanDefinitionNames();

int getBeanDefinitionCount();

// 是否使用中
boolean isBeanNameInUse(String var1);
}

**BeanDefinition:**是Spring对Bean解析为,Spring内部的 BeanDefinition 结构,是对类的数据包装,类全限定名,是否是单例的,是否是懒加载的,注入方式是什么…

registerBeanDefinitions 注册方法

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
java复制代码@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry registry) {

// 拿到注解,目的是拿到注解里的属性值,拿到值后进行扫描,并且对结果进行一个转换 AnnotationAttributes
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry);
}
}

/**
* 对 MapperScan 的属性值进行一个解析处理
* @param annoAttrs
* @param registry
*/
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {

/**
*
* 🌟
* 这是Mybatis的一个扫描器
* 也是 继承了 Spring的扫描器 ClassPathBeanDefinitionScanner
*/
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

// this check is needed in Spring 3.1
Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);

// ........

/**
* 读取包mapper包下的路径 和 要扫描的一组 mapper.class
*/
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));

basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));

basePackages.addAll(
Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
.map(ClassUtils::getPackageName)
.collect(Collectors.toList()));

// 🌟这是自定义扫描规则,与Spring的默认机制不同
scanner.registerFilters();

// 🌟调用扫描器 对包路径进行扫描
scanner.doScan(StringUtils.toStringArray(basePackages));
}

扫描器 ClassPathMapperScanner

classPathMapperScanner 是mybatis的一个类,继承了 ClassPathBeanDefinitionScanner,重写了doScan方法;然后也调用了 它的的扫描方法。并且定义了扫描规则,还有一些Bean的过滤,比如在一个包下,不单单有 mapper 接口的类,我们的@MapperScan主要处理的是 mapper 接口,所以将其排除:

排除掉非接口的类

1
2
3
4
java复制代码 @Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

扫描器 ClassPathMapperScanner 的doScan方法

重写了父类的doScan方法,并且也调用了它的方法,通过父类的扫描结果,就将 该包下的所有 Mapper接口,解析成了 BeanDefinitionHolder,放到了 set集合中。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码  @Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
// 对扫描结果进行处理,如果不处理的话,这个接口就当作了
// 一个普通的Bean注入IOC了,在引入调用,就会出现错误了。
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}

BeanDefinitionHolder:是BeanDefinition的持有者,包含了Be na的 名字,和Bean的别名,也包含了BeanDefinition。

processBeanDefinitions 处理BeanDefinition的BeanClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
// 循环遍历,一个个修改
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();

definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);

// 关键步骤: 这将 GenericBeanDefinition 的BeanClass 修改成了mapperFactoryBeanClass;
// 扫描结果 这个beanClass 就是 mapper.class
definition.setBeanClass(this.mapperFactoryBeanClass);

definition.getPropertyValues().add("addToConfig", this.addToConfig);

// .........
}
}
}

我们知道Spring 中有两种Bean,一种是 普通的Bean,另一种就是 FactoryBean,如果是FactoryBean在实例化的时候,就会调用它的 getObject方法获取对象。

FactoryBean 是一个接口:mybatis的 MapperFactoryBean 实现了它;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public interface FactoryBean<T> {

// 获取对像
@Nullable
T getObject() throws Exception;

// 类型
@Nullable
Class<?> getObjectType();
// 默认是单例Bean
default boolean isSingleton() {
return true;
}

}

MapperFactoryBean:

MapperFactoryBean 有两个属性,其中一个

1
java复制代码private Class<T> mapperInterface;

这个就是mapper接口的class,看它重写的getObject()方法;

1
2
3
4
java复制代码  @Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

是由它的 SqlSession的具体类去调用 全局的配置文件 Configuration 对象中的一个MapperRegister对象的一个getMapper方法,然后根据class从 MapperRegister 中的属性Map -> knownMappers 拿到 MapperProxyFactory代理工厂,通过newInstance方法代理生成对像;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码public class MapperProxyFactory<T> {

private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

// ........

// --------
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}

这就完成了一个对 mapper接口的处理。

在myBatis的启动过程中,会将扫描的mapper信息进行封装,所有的信息都会在 Configuration 中;

比如:一个 mapper 接口

1
2
3
4
java复制代码package cn.thisforyou.blog.core.mapper;
public interface PayMapper{
pay(String outNo)
}

对接口进行解析,拿到 Class: PayMapper.class

然后 调用 MapperRegistry的 addMapper方法 包装成一个代理对像 MapperProxyFactory

放到map中,就是 key-> PayMapper.class,vaue:new MapperProxyFactory(class);

在注入的时候,就会getObect()方法,最后就调用了MapperProxyFactory.newInstance生成代理对像。

MapperRegistry 在 Configuration对象中;

最后:mapper的@Autowired 注入的其实就是 MapperFactoryBean 通过它的getObject方法,代理生成接口对象。

总结:

  1. Mybatis 的mapper接口,是怎么被扫描的?

Mybatis 通过注解@MapperScan 下的@Import注解加载,MapperScannerRegistrar 类,此类继承了ImportBeanDefinitionRegistrar 类,对bean的注册处理,在注册之前 会拿到 @MapperScan 的 参数值,mapper 包路径,然后调用 new ClassPathMapperScanner(registry) 类去扫描,ClassPathMapperScanner extends ClassPathBeanDefinitionScanner,重写doScan方法,定义扫描规则,对扫描结果进行更改 BeanDefinition 的beanClass 进行替换成 MapperFactoryBeanClass;
2. mapper接口是如何被实例化,然后可以使用@Autowired注入?

mapper接口没有被实例化,是通过 FactoryBean 的方式注入到 IOC 中,通过调用getObject方法生成代理对像 @Autowired的;

本文转载自: 掘金

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

拒绝接口裸奔!开放API接口签名验证 AccessKey&S

发表于 2020-11-26

接口安全问题

  • 请求身份是否合法?
  • 请求参数是否被篡改?
  • 请求是否唯一?

AccessKey&SecretKey (开放平台)

请求身份

为开发者分配AccessKey(开发者标识,确保唯一)和SecretKey(用于接口加密,确保不易被穷举,生成算法不易被猜测)。

防止篡改

参数签名

  • 按照请求参数名的字母升序排列非空请求参数(包含AccessKey),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA;
  • 在stringA最后拼接上Secretkey得到字符串stringSignTemp;
  • 对stringSignTemp进行MD5运算,并将得到的字符串所有字符转换为大写,得到sign值。

请求携带参数AccessKey和Sign,只有拥有合法的身份AccessKey和正确的签名Sign才能放行。这样就解决了身份验证和参数篡改问题,即使请求参数被劫持,由于获取不到SecretKey(仅作本地加密使用,不参与网络传输),无法伪造合法的请求。

重放攻击

虽然解决了请求参数被篡改的隐患,但是还存在着重复使用请求参数伪造二次请求的隐患。

timestamp+nonce方案

nonce指唯一的随机字符串,用来标识每个被签名的请求。通过为每个请求提供一个唯一的标识符,服务器能够防止请求被多次使用(记录所有用过的nonce以阻止它们被二次使用)。

然而,对服务器来说永久存储所有接收到的nonce的代价是非常大的。可以使用timestamp来优化nonce的存储。

假设允许客户端和服务端最多能存在15分钟的时间差,同时追踪记录在服务端的nonce集合。当有新的请求进入时,首先检查携带的timestamp是否在15分钟内,如超出时间范围,则拒绝,然后查询携带的nonce,如存在已有集合,则拒绝。否则,记录该nonce,并删除集合内时间戳大于15分钟的nonce(可以使用redis的expire,新增nonce的同时设置它的超时失效时间为15分钟)。

实现

请求接口:api.test.com/test?name=h…

客户端

  • 生成当前时间戳timestamp=now和唯一随机字符串nonce=random
  • 按照请求参数名的字母升序排列非空请求参数(包含AccessKey)stringA=”AccessKey=access&home=world&name=hello&work=java×tamp=now&nonce=random”;
  • 拼接密钥SecretKeystringSignTemp=”AccessKey=access&home=world&name=hello&work=java×tamp=now&nonce=random&SecretKey=secret”;
  • MD5并转换为大写sign=MD5(stringSignTemp).toUpperCase();
  • 最终请求api.test.com/test?name=h…;

服务端

拒绝接口裸奔!开放API接口签名验证

Token&AppKey(APP)

在APP开放API接口的设计中,由于大多数接口涉及到用户的个人信息以及产品的敏感数据,所以要对这些接口进行身份验证,为了安全起见让用户暴露的明文密码次数越少越好,然而客户端与服务器的交互在请求之间是无状态的,也就是说,当涉及到用户状态时,每次请求都要带上身份验证信息。

Token身份验证

  • 用户登录向服务器提供认证信息(如账号和密码),服务器验证成功后返回Token给客户端;
  • 客户端将Token保存在本地,后续发起请求时,携带此Token;
  • 服务器检查Token的有效性,有效则放行,无效(Token错误或过期)则拒绝。
  • 安全隐患:Token被劫持,伪造请求和篡改参数。

Token+AppKey签名验证

与上面开发平台的验证方式类似,为客户端分配AppKey(密钥,用于接口加密,不参与传输),将AppKey和所有请求参数组合成源串,根据签名算法生成签名值,发送请求时将签名值一起发送给服务器验证。

这样,即使Token被劫持,对方不知道AppKey和签名算法,就无法伪造请求和篡改参数。再结合上述的重发攻击解决方案,即使请求参数被劫持也无法伪造二次重复请求。

实现

登陆和退出请求

拒绝接口裸奔!开放API接口签名验证

登陆和退出流程

后续请求

客户端

  • 和上述开放平台的客户端行为类似,把AccessKey改为token即可。

服务端

拒绝接口裸奔!开放API接口签名验证

服务端流程

本文转载自: 掘金

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

Python炫技操作:五种Python 转义表示法

发表于 2020-11-26

首发于:Python编程时光, 作者:写代码的明哥

原文链接:iswbm.com/162.html

  1. 为什么要有转义?

ASCII 表中一共有 128 个字符。这里面有我们非常熟悉的字母、数字、标点符号,这些都可以从我们的键盘中输出。除此之外,还有一些非常特殊的字符,这些字符,我通常很难用键盘上的找到,比如制表符、响铃这种。

为了能将那些特殊字符都能写入到字符串变量中,就规定了一个用于转义的字符 \ ,有了这个字符,你在字符串中看的字符,print 出来后就不一定你原来看到的了。

举个例子

1
2
3
4
5
6
7
python复制代码>>> msg = "hello\013world\013hello\013python"
>>> print(msg)
hello
world
hello
python
>>>

是不是有点神奇?变成阶梯状的输出了。

那个 \013 又是什么意思呢?

  • \ 是转义符号,上面已经说过
  • 013 是 ASCII 编码的八进制表示,注意前面是 0 且不可省略,而不是字母 o

把八进制的 13 转成 10 进制后是 11

对照查看 ASCII 码表,11 对应的是一个垂直定位符号,这就能解释,为什么是阶梯状的输出字符串。

  1. 转义的 5 种表示法

ASCII 有 128 个字符,如果用 八进制表示,至少得有三位数,才能将其全部表示。这就是为什么说上面的首位 0 不能省略的原因,即使现在用不上,我也得把它空出来。

而如果使用十六进制,只要两位数就其 ASCII 的字符全部表示出来。同时为了避免和八进制的混淆起来,所以在 \ 后面要加上英文字母 x 表示十六进制,后面再接两位十六进制的数值。

  • \ 开头并接三位 0-7 的数值,表示 8 进制
  • \x 开头并接两位 0-f 的数值,表示 16进制

因此,当我定义一个字符串的值为 hello + 回车 + world 时,就有了多种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
python复制代码# 第一种方法:8进制
>>> msg = "hello\012world"
>>> print(msg)
hello
world
>>>

# 第二种方法:16 进制
>>> msg = "hello\x0aworld"
>>> print(msg)
hello
world
>>>

通常我们很难记得住一个字符的 ASCII 编号,即使真记住了,也要去转换成八进制或者16进制,实在是太难了。

因此对于一些常用并且比较特殊字符,我们习惯用另一种类似别名的方式,比如使用 \n 表示换行,它与 \012 、\x0a 是等价的。

与此类似的表示法,还有如下这些

于是,要实现 hello + 回车 + world ,就有了第三种方法

1
2
3
4
5
6
python复制代码# 第三种方法:使用类似别名的方法
>>> msg = "hello\nworld"
>>> print(msg)
hello
world
>>>

到目前为止,我们掌握了 三种转义的表示法。

已经非常难得了,让我们的脑洞再大一点吧,接下来再介绍两种。

ASCII 码表所能表示字符实在太有限了,想打印一个中文汉字,抱歉,你得借助 Unicode 码。

Unicode 编码由 4 个16进制数值组合而成

1
2
python复制代码>>> print("\u4E2D")
中

什么?我为什么知道 中 的 unicode 是 \u4E2D?像下面这样打印就知道啦

1
2
3
4
python复制代码# Python 2.7
>>> a = u"中"
>>> a
u'\u4e2d'

由此,要实现 hello + 回车 + world ,就有了第四种方法。

1
2
3
4
python复制代码# 第四种方法:使用 unicode ,\u000a 表示换行
>>> print('hello\u000aworld')
hello
world

看到这里,你是不是以为要结束啦?

不,还没有。下面还有一种。

Unicode 编码其实还可以由 8 个32进制数值组合而成,为了以前面的区分开来,这里用 \U 开头。

1
2
3
4
python复制代码# 第五种方法:使用 unicode ,\U0000000A 表示换行
>>> print('hello\U0000000Aworld')
hello
world

好啦,目前我们掌握了五种转义的表示法。

总结一下:

  1. \ 开头并接三位 0-7 的数值(八进制) — 可以表示所有ASCII 字符
  2. \x 开头并接两位 0-f 的数值(十六进制) — 可以表示所有ASCII 字符
  3. \u 开头并接四位 0-f 的数值(十六进制) — 可以表示所有 Unicode 字符
  4. \U 开头并接八位 0-f 的数值(三十二进制)) — 可以表示所有 Unicode 字符
  5. \ 开头后接除 x、u、U 之外的特定字符 — 仅可表示部分字符

为什么标题说,转义也可以炫技呢?

试想一下,假如你的同事,在打印日志时,使用这种 unicode 编码,然后你在定位问题的时候使用这个关键词去搜,却发现什么都搜不到?这就扑街了。

虽然这种行为真的很 sb,但在某些人看来也许是非常牛逼的操作呢?

五种转义的表示法到这里就介绍完成,接下来是更多转义相关的内容,也是非常有意思的内容,有兴趣的可以继续往下看。

  1. raw 字符串

当一个字符串中具有转义的字符时,我们使用 print 打印后,正常情况下,输出的不是我们原来在字符串中看到的那样子。

那如果我们需要输出 hello\nworld ,不希望 Python 将 \n 转义成 换行符呢?

这种情况下,你可以在定义时将字符串定义成 raw 字符串,只要在字符串前面加个 r 或者 R 即可。

1
2
3
4
5
python复制代码>>> print(r"hello\nworld")
hello\nworld
>>>
>>> print(R"hello\nworld")
hello\nworld

然而,不是所有时候都可以加 r 的,比如当你的字符串是由某个程序/函数返回给你的,而不是你自己生成的

1
2
3
4
5
python复制代码# 假设这个是外来数据,返回 "hello\nworld"
>>> body = spider()
>>> print(body)
hello
world

这个时候打印它,\n 就是换行打印。

  1. 使用 repr

对于上面那种无法使用 r 的情况,可以试一下 repr 来解决这个需求:

1
2
3
python复制代码>>> body = repr(spider())
>>> print(body)
'hello\nworld'

经过 repr 函数的处理后,为让 print 后的结果,接近字符串本身的样子,它实际上做了两件事

  1. 将 \ 变为了 \\
  2. 在字符串的首尾添加 ' 或者 "

你可以在 Python Shell 下敲入 变量 回车,就可以能看出端倪。

首尾是添加 ' 还是 " ,取决于你原字符串。

1
2
3
4
5
6
7
8
python复制代码>>> body="hello\nworld"
>>> repr(body)
"'hello\\nworld'"
>>>
>>>
>>> body='hello\nworld'
>>> repr(body)
"'hello\\nworld'"
  1. 使用 string_escape

如果你还在使用 Python 2 ,其实还可以使用另一种方法。

那就是使用 string.encode('string_escape') 的方法,它同样可以达到 repr 的效果

1
2
3
python复制代码>>> "hello\nworld".encode('string_escape')
'hello\\nworld'
>>>
  1. 查看原生字符串

综上,想查看原生字符串有两种方法:

  1. 如果你在 Python Shell 交互模式下,那么敲击变量回车
  2. 如果不在 Python Shell 交互模式下,可先使用 repr 处理一下,再使用 print 打印
1
2
3
4
5
6
7
8
python复制代码>>> body="hello\nworld"
>>>
>>> body
'hello\nworld'
>>>
>>> print(repr(body))
'hello\nworld'
>>>
  1. 恢复转义:转成原字符串

经过 repr 处理过或者 \\ 取消转义过的字符串,有没有办法再回退出去,变成原先的有转义的字符串呢?

答案是:有。

如果你使用 Python 2,可以这样:

1
2
3
4
5
6
7
8
python复制代码>>> body="hello\\nworld"
>>>
>>> body
'hello\\nworld'
>>>
>>> body.decode('string_escape')
'hello\nworld'
>>>

如果你使用 Python 3 ,可以这样:

1
2
3
4
5
6
7
8
python复制代码>>> body="hello\\nworld"
>>>
>>> body
'hello\\nworld'
>>>
>>> bytes(body, "utf-8").decode("unicode_escape")
'hello\nworld'
>>>

什么?还要区分 Python 2 和 Python 3?太麻烦了吧。

明哥教你用一种可以兼容 Python 2 和 Python 3 的写法。

首先是在 Python 2 中的输出

1
2
3
4
5
6
python复制代码>>> import codecs 
>>> body="hello\\nworld"
>>>
>>> codecs.decode(body, 'unicode_escape')
u'hello\nworld'
>>>

然后再看看 Python 3 中的输出

1
2
3
4
5
6
python复制代码>>> import codecs
>>> body="hello\\nworld"
>>>
>>> codecs.decode(body, 'unicode_escape')
'hello\nworld'
>>>

可以看到 Pyhton 2 中的输出 有一个 u ,而 Python 3 的输出没有了 u,但无论如何 ,他们都取消了转义。

以上,就是我为大家整理的关于 Python 中转义的全部内容了,整理的过程,不断的发现新知识,帮助到大家的同时,自己也对转义的一些内容有了更深的理解。

如果本文对你有些许帮助,不如给明哥 来个四连 ~ 比心

本文转载自: 掘金

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

快速入门ASP NET Core 看这篇就够了

发表于 2020-11-26

.NET Core介绍

首先我们知道.NET Core和ASP .NET Core是不一样的,.NET Core是开放源代码的通用开发平台。基于.NET Core可以开发像ASP .NET Core应用程序。

.NET Core 还具有以下特性:

  • 跨平台: 可以在 Windows、macOS 和 Linux 操作系统上运行。
  • 跨体系结构保持一致: 在多个体系结构(包括 x64、x86 和 ARM)上以相同的行为运行代码。
  • 命令行工具: 包括用于本地开发和持续集成方案中的易于使用的命令行工具。
  • 部署灵活: 可以包含在应用或已安装的并行用户或计算机范围中。 可搭配 Docker 容器使用。
  • 兼容性:.NET Core 通过 .NET Standard与 .NET Framework、Xamarin 和 Mono 兼容。
  • 开放源:.NET Core 是一个开放源平台,使用 MIT 和 Apache 2 许可证。 .NET Core 是一个 .NET Foundation 项目。
  • 由 Microsoft 支持:.NET Core背后依托强大的Microsoft团队 进行维护。

什么是ASP.NET Core

ASP.NET Core 是一个由Microsoft创建的,用于构建 web 应用、API、微服务 的 web 框架。它使用常见的模式,诸如 MVC(Model-View-Controller)、依赖注入,和一个由中间件构成的请求处理管道。

ASP.NET Core 运行在Microsoft 的 .NET 运行时库上,类似于 Java 的 虚拟机(JVM)或者 Ruby 的解释器。有几种语言(C#,Visual Basic,F#)可以用来编写 ASP.NET Core 程序。C# 是最常见的选择,当然我大多数人都是采用C#来进行开发的。你可以在 Windows、Mac,和 Linux 上构建并运行 ASP.NET Core 应用。

ASP.NET Core 同时具有如下优点:

  • 生成 Web UI 和 Web API 的统一场景。
  • 针对可测试性进行构建。
  • Razor Pages可以使基于页面的编码方式更简单高效。
  • 能够在 Windows、macOS 和 Linux 上进行开发和运行。
  • 开放源代码和以社区为中心。
  • 内置依赖项注入。
  • 轻型的高性能模块化 HTTP 请求管道。
  • 能够在 IIS、Nginx、Apache、Docker上进行托管或在自己的进程中进行自托管。
  • 基于 .NET Core运行时,可以使用并行应用版本控制。

为什么使用ASP .NET Core开发应用程序

现存的web框架有很多,比如Node/Express, Spring, Django等。但是为什么要用ASP.NET Core开发应用程序呢?

  • 速度: ASP.NET Core 很快。因为 .NET Core 是编译运行的,执行速度远高于解释执行的语言,比如 JavaScript 或者 Ruby、ASP.NET Core 也已经为多线程和异步任务作了专门的优化。与使用 Node.js 写的代码相比,执行速度高出 5-10 倍是很正常的。
  • 生态: ASP.NET Core 可能初出茅庐,但 .NET 却已久经考验。在 NuGet(.NET 的包管理系统,类似 npm、Ruby gems,或者 Maven)上有成千上万的软件包。有现成的包可用来完成 JSON 反序列化、数据库连接、PDF生成,或者几乎你能想到的任何需求。
  • 安全性: 微软的开团队很注重安全性,ASP.NET Core 从创建基础就是安全的。它已经自动处理了 净化输入数据 和 跨域伪造请求(CSRF)。你同时还享有 .NET 编译器的静态类型检测的优势,它像个时刻警惕着,还有些强迫症的审校者。这样,在使用一个变量或者某些数据时,那些无意识的错误就插翅难逃。
  • 跨平台: 可以运行在安装了 .NET 运行时库的 Windows、Mac或者Linux上。
  • 开源: .NET Core 属于开放源(MIT 许可证)。 可由个人和企业自由采用,包括用于个人、学术或商业目的。 同时开源也就意味着在你出现问题的时候你可以阅读其源代码来获取解决问题的方法。

.NET Core环境搭建

  1. 在Google或别的浏览器上搜索 .NET Core,第一个就是Microsoft的官方下载地址,或者点击这里Download .NET,进行开发的话下载SDK,部署的话下载Runtime。
  2. 双击你下载好的sdk然后傻瓜式的一步一步的进行安装即可。
  3. 接下来按住 Shift+鼠标右键,然后选择“在此处打开Powershell窗口”或者“在此处打开命令行窗口”。然后输入dotnet --info 查看下我们已经安装的.NET Core 的信息,当前运行的环境,已经以往安装的版本信息,出现下面的界面也就说明我们的.net core开发环境已经就绪了!

快速创建一个ASP .NET Core项目进行实战演练

  1. 第一步:打开VS2019,点击”Create a new project”。

  1. 选择ASP .NET Core Web 应用程序,点击”next”。接下来可以修改project name,点击”create”。

)

  1. 接下来会选择目标框架(.NET Core或.NET Framework),接下来可以创建一个空的解决方案或者创建一个带有模板的web项目,我们选择MVC的web应用程序。

  1. 创建成功后,会有如下的标准的MVC结构。

  • wwwroot:网站的静态文件目录
  • appsettings.json:配置文件,比如数据库连接字符串等等配置信息。
  • Program.cs:程序入口文件(里面有个Main方法);
  • Startup.cs启动配置文件 ;
  • Controllers:控制器
  • Models:实体
  • Views:视图
  1. 按下键盘的F5或者按下图点击运行按钮。

  1. 如果不出意外的话,会显示下图所示界面,到这我们就运行起来了一个ASP .NET Core的MVC站点。若报500.24错误,请参考此链接

本文转载自: 掘金

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

什么是集群、分布式和微服务? 总结

发表于 2020-11-26

如何选应用架构?
最近微服务架构非常流行,10个人的小公司做个项目也要求把微服务架构做为硬性条件,网站日访问量不到1000的web应用也要求用微服务架构,那么到底使用微服务架构好不好,我来通俗的讲一下。要弄清楚这个问题,需要理解下面几个流行语。

集群

通俗解释一下集群:为了建设一栋房子,需要砌砖,一个人砌砖太慢,需要10个人砖瓦工人同事去砌,这样就大大提高了效率,我们说这10个人就组成了一个集群。集群是所有人都是干同一件事,大家一起干,每个人相互之间不依赖。放到我们的软件生产环境,集群就是通过堆积服务器硬件来做同一个工作来提高效率。

分布式

分布式,顾名思义,就是有个分工的概念。还是用砌砖的例子来说,我们砌砖,需要先把搬运砖头,放到墙边,需要水泥砂浆,然后才能开始砌砖的工作。如果和水泥砂浆,搬砖,砌墙都给同一个人做,即使是10个人,可能效率也不高,这个时候分布式就上场了。我们可以安排2个人专门和水泥砂浆,2个人搬砖运到墙下,6个人只管砌砖。这种情况下,虽然人员没有增多,但是效率肯定会提高。那可以这么理解,集群不一定是分布式,但分布式肯定是集群,它需要多个服务器来协同工作。那这个时候,还会有一个问题,如果水泥砂浆没有了,那砌砖工人需要通知和水泥砂浆暂停,赶紧把弄好的水泥砂浆运到墙边。现实中可以用嘴喊,可以手机打电话,服务器这个时候怎么通知,这就涉及到rpc(remote process communication),这个我们简单提一下,下次可以单独深入讨论。

微服务

微服务是一种架构,原理和分布式很像,它的拆分粒度很细,细到每个人仅做一件不可分解的事情,而这些细微的事情不一定每个都放在不同服务器上,一个服务器上可以放很多微服务如A服务,B服务,C服务,另外一台服务器放B服务,C服务,D服务。值得注意的是,所有服务都需要通知一个叫注册中心的地方,可以理解这个为工程项目经理,他来统一协调管理。

总结

如果你的业务很简单,访问量也很少,那所有应用放一台服务器也可以流畅的运行,这个时候连集群都不需要用。

如果你的访问量很少,但是业务很复杂,打个比方,以电商下单为例,下单的过程,需要提交订单,支付,同事需要核查仓库是否有库存,然后再把地址发给第三方物流下单,如果这些事情放一起做,需要30秒。用户需要等待30秒才能看见自己是否购买成功了,这样体验非常不好,即使你的平台一天只成交100单, 访问量很小,用户体验还是不好。这个时候你可以用分布式来解决这个问题,把支付,查库存,通知第三方物流等拆分成5个或者更多的工作。这样,用户体验大大提高,几秒就可以完成一次购物。

如果你的访问量很大,每个流程步骤很复杂,那这个时候,你可以将步骤分布式,再多分配几个服务器集群,这个时候用微服务架构就更合适了。根据之前运营app的经验,日访问量几百万,每个交互都是2秒以内的应用,只要带宽足够,将web和数据库分离加上一个redis缓存,2台主流服务器就足够了。

本文由Websoft9原创发布,转载请注明出处。

本文转载自: 掘金

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

开源认证和访问控制的利器keycloak使用简介 简介 安装

发表于 2020-11-26

简介

keycloak是一个开源的进行身份认证和访问控制的软件。是由Red Hat基金会开发的,我们可以使用keycloak方便的向应用程序和安全服务添加身份认证,非常的方便。

keycloak还支持一些高级的特性,比如身份代理,社交登录等等。

本文将会带领大家进入keycloak的神秘世界。

安装keycloak

keycloak有很多种安装模式,这里我们先介绍最简单的standalone模式。

要安装keycloak,我们需要下载keycloak的zip包。在我写这篇文章的时候,keycloak的最新版本是11.0.2。

下载链接如下: downloads.jboss.org/keycloak/11…

下载完毕,解压,

1
2
shell复制代码cd bin
./standalone.sh

我们可以简单的挑选一些启动日志来分析一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
shell复制代码=========================================================================

JBoss Bootstrap Environment

JBOSS_HOME: /Users/flydean/data/git/security/keycloak-11.0.2

JAVA: /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/bin/java

JAVA_OPTS: -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true

=========================================================================

22:08:04,231 INFO [org.jboss.modules] (main) JBoss Modules version 1.10.1.Final
22:08:08,706 INFO [org.jboss.msc] (main) JBoss MSC version 1.4.11.Final
22:08:08,721 INFO [org.jboss.threads] (main) JBoss Threads version 2.3.3.Final
22:08:08,921 INFO [org.jboss.as] (MSC service thread 1-2) WFLYSRV0049: Keycloak 11.0.2 (WildFly Core 12.0.3.Final) starting

可以看到keycloak底层实际上使用的是WildFly服务器,WildFly服务器的前身就是JBoss,也是由red hat主导的。所以keycloak使用WildFly还是很合理的。

当我们看到下面的日志的时候,就意味着keycloak启动好了。

1
2
shell复制代码22:08:26,436 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
22:08:26,437 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990

接下来,我们需要为keycloak创建admin用户。

创建admin用户

启动好keycloak之后,我们就可以登录到web页面 http://localhost:8080/auth 创建admin用户了。

这是创建用户的界面,我们输入用户名和密码,点击create,就可以创建admin用户了。

创建完admin用户,点击登录到admin console,就会跳转到admin console的登录页面 http://localhost:8080/auth/admin/

输入用户名密码,点击登录。

然后就进入到了管理界面:

可以看到管理界面提供的功能还是非常丰富的。

我们可以对realm,clients,roles,identity providers,user federation,authentication等进行配置和定义。

还可以对groups,users,sessions,events等进行管理,非常强大和方便。

创建realm和普通用户

realm翻译成中文就是域,我们可以将它看做是一个隔离的空间,在realm中我们可以创建users和applications。

keycloak中有两种realm空间,一种是Master realm,一种是Other realms。

master realm是指我们使用admin用户登录进来的realm空间,这个realm只是用来创建其他realm的。

other realms是由master realm来创建的,admin可以创建users和applications,而这些applications是由users所有的。

点击add realm按钮,我们进入add realm界面,输入realm的名字,就可以创建realm了。

上面的例子中,我们创建了一个叫做WildFly的realm。

接下来,我们为WildFly创建新的user:

输入用户名,点击save。

选择新创建user的credentials页面,输入要创建的密码,点击set password,那么新创建用户的密码则创建完毕。

接下来,我们使用新创建的用户flydean来登录realm WildFly,登录url如下:

http://localhost:8080/auth/realms/WildFly/account

输入用户名和密码,进入用户管理页面:

我们将用户所需要的资料填充完毕,以供后面使用。

使用keycloak来保护你的应用程序

因为keycloak底层使用的是WildFly,为了简单起见,这里我们也使用keycloak来保护一个WildFly程序。

我从WildFly的官网下载最新版本的WildFly,然后解压备用。

因为keycloak和WildFly都是在同一台机子上面启用。所以默认情况下端口都是一样的8080。

接下来,我们需要修改一下keycloak的端口,以避免端口冲突。

1
2
shell复制代码cd bin
./standalone.sh -Djboss.socket.binding.port-offset=100

我们重启一下keycloak,在启动命令中添加了jboss.socket.binding.port-offset,这个是相对于标准端口的偏移量。

比如之前我们的端口是8080,那么现在端口就是8180。

看一下,现在的管理页面链接是不是变到了 http://localhost:8180/auth/admin/ 。

安装WildFly client adapter

为了无缝集成WildFly,keycloak提供了多种adapter供我们使用:

可以看到除了WildFly,keycloak还可以支持Jetty和Tomcat,我们可以在后面的文章中来讲解如何集成keycloak到Jetty和Tomcat。

同时,client Adapters还有两种协议格式,openid connect和SAML 2.0,我们也会在后面的文章中具体介绍一下这两种协议,敬请期待。

好了,先下载WildFly adapter,将adapter放到WildFly的跟目录下面:

1
2
3
4
shell复制代码server/wildfly-20.0.1.Final : ls
LICENSE.txt bin domain modules
README.txt copyright.txt jboss-modules.jar standalone
appclient docs keycloak-wildfly-adapter-dist-11.0.2.zip welcome-content

解压adapter,解压之后,进入wildfly-20.0.1.Final/bin目录,运行:

1
shell复制代码./jboss-cli.sh --file=adapter-elytron-install-offline.cli

如果你得到下面的输出结果:

1
2
3
4
shell复制代码{"outcome" => "success"}
{"outcome" => "success"}
{"outcome" => "success"}
{"outcome" => "success"}

那就说明adapter安装成功了,这个脚本会修改…​/standalone/configuration/standalone.xml。

然后我们可以启动WildFly了:

1
java复制代码./standalone.sh

注册WildFly应用程序

回到我们的admin console: http://localhost:8180/auth/admin/

选择我们之前创建的realm:WildFly,在clients选项中,我们创建新的client:

创建完成之后,我们进入到installation tab:

选择keycloak OIDC JSON,点击Download,下载keycloak.json文件。

然后选择Keycloak OIDC JBoss Subsystem XML,点击下载,下载keycloak-oidc-subsystem.xml文件。

接下来,我们需要修改WildFly配置信息。

进入WildFly的standalone/configuration目录,修改standalone.xml文件如下:

1
2
3
4
5
6
7
8
9
xml复制代码        <subsystem xmlns="urn:jboss:domain:keycloak:1.1">
<secure-deployment name="vanilla.war">
<realm>WildFly</realm>
<auth-server-url>http://localhost:8180/auth/</auth-server-url>
<public-client>true</public-client>
<ssl-required>EXTERNAL</ssl-required>
<resource>vanilla</resource>
</secure-deployment>
</subsystem>

这个subsystem的内容实际上就是我们刚刚保存的keycloak-oidc-subsystem.xml里面的内容。

这里我们需要知道secure-deployment的war名字,也就是我们接下来将要部署的应用程序的名字。

重启WildFly。

安装vanilla应用程序

为了简单起见,我们直接从 github.com/keycloak/ke… 中下载示例代码项目 app-profile-jee-vanilla。

1
2
shell复制代码git clone https://github.com/keycloak/keycloak-quickstarts
cd keycloak-quickstarts/app-profile-jee-vanilla/config

将刚刚下载的keycloak.json拷贝到当前目录。

然后切换到keycloak-quickstarts父目录,执行:

1
java复制代码mvn clean wildfly:deploy

这个命令将会打包成为适合WildFly执行的war包,也就是我们要的vanilla.war。

将打包好的vanilla.war拷贝到WildFly目录下的standalone/deployments。

WildFly会自动重新部署该应用程序。

这时候我们访问下应用程序 http://localhost:8080/vanilla :

可以看到登录界面。点击登录。

先看下登录链接,自动跳转到了 http://localhost:8180/auth/realms/WildFly/protocol/openid-connect/auth?response_type=code&client_id=vanilla&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fvanilla%2Fprofile.jsp&state=8521b8ab-83f7-4fec-9ced-8c90a3d42839&login=true&scope=openid

这也就是keycloak登录域WildFly的登录界面,不过后面带上了redirect_uri参数,说明登录成功后,会跳转回vanilla程序的界面。

我们使用之前创建的用户名和密码登录看看。

登录成功。

总结

上面的例子我们演示了如何配置keycloak,并且创建一个realm供第三方程序使用。还举了一个无侵入的例子来和keycloak对接。

当然,有朋友会问了,vanilla程序是怎么和keycloak对接的呢?如果我们要写一个自己的程序,应该怎么做呢?

别急,细节我会在后面的文章进行分享,敬请期待。

本文作者:flydean程序那些事

本文链接:www.flydean.com/keycloak-st…

本文来源:flydean的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

本文转载自: 掘金

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

写代码有这16个好习惯,可以减少80%非业务的bug

发表于 2020-11-25

前言

每一个好习惯都是一笔财富,本文整理了写代码的16个好习惯,每个都很经典,养成这些习惯,可以规避多数非业务的bug!希望对大家有帮助哈,谢谢阅读,加油哦~

github地址,感谢每颗star

github.com/whx123/Java…

公众号:捡田螺的小男孩

1. 修改完代码,记得自测一下

改完代码,自测一下 是每位程序员必备的基本素养。尤其不要抱有这种侥幸心理:我只是改了一个变量或者我只改了一行配置代码,不用自测了。改完代码,尽量要求自己都去测试一下哈,可以规避很多不必要bug的。

2. 方法入参尽量都检验

入参校验也是每个程序员必备的基本素养。你的方法处理,必须先校验参数。比如入参是否允许为空,入参长度是否符合你的预期长度。这个尽量养成习惯吧,很多低级bug都是不校验参数导致的。

如果你的数据库字段设置为varchar(16),对方传了一个32位的字符串过来,你不校验参数,插入数据库直接异常了。

3. 修改老接口的时候,思考接口的兼容性。

很多bug都是因为修改了对外老接口,但是却不做兼容导致的。关键这个问题多数是比较严重的,可能直接导致系统发版失败的。新手程序员很容易就犯这个错误了哦~

所以,如果你的需求是在原来接口上修改,,尤其这个接口是对外提供服务的话,一定要考虑接口兼容。举个例子吧,比如dubbo接口,原本是只接收A,B参数,现在你加了一个参数C,就可以考虑这样处理。

1
2
3
4
5
6
7
8
scss复制代码//老接口
void oldService(A,B);{
//兼容新接口,传个null代替C
newService(A,B,null);
}

//新接口,暂时不能删掉老接口,需要做兼容。
void newService(A,B,C);

4.对于复杂的代码逻辑,添加清楚的注释

写代码的时候,是没有必要写太多的注释的,好的方法变量命名就是最好的注释。但是,如果是业务逻辑很复杂的代码,真的非常有必要写清楚注释。清楚的注释,更有利于后面的维护。

5. 使用完IO资源流,需要关闭

应该大家都有过这样的经历,windows系统桌面如果打开太多文件或者系统软件,就会觉得电脑很卡。当然,我们linux服务器也一样,平时操作文件,或者数据库连接,IO资源流如果没关闭,那么这个IO资源就会被它占着,这样别人就没有办法用了,这就造成资源浪费。

所以使用完IO流,可以使用finally关闭哈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码FileInputStream fdIn = null;
try {
fdIn = new FileInputStream(new File("/jay.txt"));
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}finally {
try {
if (fdIn != null) {
fdIn.close();
}
} catch (IOException e) {
log.error(e);
}
}

JDK 7 之后还有更帅的关闭流写法,使用try-with-resource。

1
2
3
4
5
6
7
8
9
10
java复制代码/*
* 关注公众号,捡田螺的小男孩
*/
try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) {
// use resources
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}

6.代码采取措施避免运行时错误(如数组边界溢出,被零除等)

日常开发中,我们需要采取措施规避数组边界溢出,被零整除,空指针等运行时错误。

类似代码比较常见:

1
ini复制代码String name = list.get(1).getName(); //list可能越界,因为不一定有2个元素哈

所以,应该采取措施,预防一下数组边界溢出,正例:

1
2
3
scss复制代码if(CollectionsUtil.isNotEmpty(list)&& list.size()>1){
String name = list.get(1).getName();
}

7.尽量不在循环里远程调用、或者数据库操作,优先考虑批量进行。

远程操作或者数据库操作都是比较耗网络、IO资源的,所以尽量不在循环里远程调用、不在循环里操作数据库,能批量一次性查回来尽量不要循环多次去查。(但是呢,也不要一次性查太多数据哈,要分批500一次酱紫)

正例:

1
scss复制代码remoteBatchQuery(param);

反例:

1
2
3
scss复制代码for(int i=0;i<n;i++){
remoteSingleQuery(param)
}

8.写完代码,脑洞一下多线程执行会怎样,注意并发一致性问题

我们经常见的一些业务场景,就是先查下有没有记录,再进行对应的操作(比如修改)。但是呢,(查询+修改)合在一起不是原子操作哦,脑洞下多线程,就会发现有问题了,

反例如下:

1
2
3
4
5
6
javascript复制代码if(isAvailable(ticketId){	
1、给现金增加操作
2、deleteTicketById(ticketId)
}else{
return "没有可用现金券";
}

为了更容易理解它,看这个流程图吧:

  • 1.线程A加现金
  • 2.线程B加现金
  • 3.线程A删除票标志
  • 4.线程B删除票标志

显然这样存在并发问题,正例应该利用数据库删除操作的原子性,如下:

1
2
3
4
5
kotlin复制代码if(deleteAvailableTicketById(ticketId) == 1){	
1、给现金增加操作
}else{
return “没有可用现金券”
}

因此,这个习惯也是要有的,写完代码,自己想下多线程执行,是否会存在并发一致性问题。

9.对象获取属性,先判断对象是否为空

这个点本来也属于采取措施规避运行时异常的,但是我还是把它拿出来,当做一个重点来写,因为平时空指针异常太常见了,一个手抖不注意,就导致空指针报到生产环境去了。

所以,你要获取对象的属性时,尽量不要相信理论上不为空,我们顺手养成习惯判断一下是否为空,再获取对象的属性。正例:

1
2
3
typescript复制代码if(object!=null){
String name = object.getName();
}

10.多线程异步优先考虑恰当的线程池,而不是new thread,同时考虑线程池是否隔离

为什么优先使用线程池?使用线程池有这几点好处呀

  • 它帮我们管理线程,避免增加创建线程和销毁线程的资源损耗。
  • 提高响应速度。
  • 重复利用。

同时呢,尽量不要所有业务都共用一个线程池,需要考虑线程池隔离。就是不同的关键业务,分配不同的线程池,然后线程池参数也要考虑恰当哈。之前写过几篇线程池的,觉得还不错,有兴趣的朋友可以看一下哈

  • 一文读懂线程池的工作原理(故事白话文)
  • 线程池解析
  • 源码分析-使用newFixedThreadPool线程池导致的内存飙升问题

11. 手动写完代码业务的SQL,先拿去数据库跑一下,同时也explain看下执行计划。

手动写完业务代码的SQL,可以先把它拿到数据库跑一下,看看有没有语法错误嘛。有些小伙伴不好的习惯就是,写完就把代码打包上去测试服务器,其实把SQL放到数据库执行一下,可以规避很多错误的。

同时呢,也用explain看下你Sql的执行计划,尤其走不走索引这一块。

1
sql复制代码explain select * from user where userid =10086 or age =18;

12.调用第三方接口,需要考虑异常处理,安全性,超时重试这几个点。

调用第三方服务,或者分布式远程服务的的话,需要考虑

  • 异常处理(比如,你调别人的接口,如果异常了,怎么处理,是重试还是当做失败)
  • 超时(没法预估对方接口一般多久返回,一般设置个超时断开时间,以保护你的接口)
  • 重试次数(你的接口调失败,需不需要重试,需要站在业务上角度思考这个问题)

简单一个例子,你一个http请求调别人的服务,需要考虑设置connect-time,和retry次数。

如果是转账等重要的第三方服务,还需要考虑签名验签,加密等。之前写过一篇加签验签的,有兴趣的朋友可以看一下哈

程序员必备基础:加签验签

13.接口需要考虑幂等性

接口是需要考虑幂等性的,尤其抢红包、转账这些重要接口。最直观的业务场景,就是用户连着点两次,你的接口有没有hold住。

  • 幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。
  • 在编程中.一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。

一般幂等技术方案有这几种:

  • 查询操作
  • 唯一索引
  • token机制,防止重复提交
  • 数据库的delete删除操作
  • 乐观锁
  • 悲观锁
  • Redis、zookeeper 分布式锁(以前抢红包需求,用了Redis分布式锁)
  • 状态机幂等

14. 多线程情况下,考虑线性安全问题

在高并发情况下,HashMap可能会出现死循环。因为它是非线性安全的,可以考虑使用ConcurrentHashMap。
所以这个也尽量养成习惯,不要上来反手就是一个new HashMap();

  • Hashmap、Arraylist、LinkedList、TreeMap等都是线性不安全的;
  • Vector、Hashtable、ConcurrentHashMap等都是线性安全的

15.主从延迟问题考虑

先插入,接着就去查询,这类代码逻辑比较常见,这可能会有问题的。一般数据库都是有主库,从库的。写入的话是写主库,读一般是读从库。如果发生主从延迟,,很可能出现你插入成功了,但是你查询不到的情况。

  • 如果是重要业务,需要考虑是否强制读主库,还是再修改设计方案。
  • 但是呢,有些业务场景是可以接受主从稍微延迟一点的,但是这个习惯还是要有吧。
  • 写完操作数据库的代码,想下是否存在主从延迟问题。

16.使用缓存的时候,考虑跟DB的一致性,还有(缓存穿透、缓存雪崩和缓存击穿)

通俗点说,我们使用缓存就是为了查得快,接口耗时小。但是呢,用到缓存,就需要注意缓存与数据库的一致性问题。同时,还需要规避缓存穿透、缓存雪崩和缓存击穿三大问题。

  • 缓存雪崩:指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
  • 缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。
  • 缓存击穿:指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db。

个人公众号

感兴趣的朋友,可以关注我公众号哈

原创公众号 捡田螺的小男孩,专注分享和探讨后端技术点,包括Java语言、计算机网络、数据库、数据结构与算法、操作系统、工作总结等方面。文章力求通俗易懂,简单明了~

本文转载自: 掘金

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

1…762763764…956

开发者博客

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