首页 文章 精选 留言 我的

精选列表

搜索[智能问数],共10000篇文章
优秀的个人博客,低调大师

面试官问:ZooKeeper 有几种节点类型?别再说 4 种啦!

好久没更新 ZK 的文章了,我想死你们啦。之前发布的 HelloZooKeeper 系列文章完结后,项目收获了将近 600 个 star。这远远超过了我自己的预期,在这里感谢大家的支持~ 后面会继续 ZooKeeper 的话题,通过单篇的形式就某个 ZK 的话题继续聊,今天我们先来看看 ZK 的节点类型。话不多说,我们进入今天的主题~ 一、关于 ZK 的节点类型 大家如果刷过 ZK 相关面试题的话,就一定会刷到过 “ZK 有几种节点类型?”,大家通常背书的答案的话是:4 种!但其实 ZK (3.6.2)服务端支持 7 种节点类型,分别是: 持久 持久顺序 临时 临时顺序 容器 持久 TTL 持久顺序 TTL 这 7 种类型之前的文章中也有提到过,但是并没有展开讲。这次更新的单篇想要把这 7 种类型的节点,认认真真的讲一遍!Let's GO 二、简单介绍 2.1 持久、临时 持久不用我多说,是用的最多的一种类型,也是默认的节点类型,临时节点相较于持久节点来说,就是它会随着客户端会话结束而被删除,通常可以用在一些特定的场景,例如分布式锁释放,健康检查等。 2.2 持久顺序、临时顺序 这两种我放在一起介绍,因为他们相对于上面两种的特性就是 ZK 会自动在这两种节点之后增加一个数字的后缀,而路径 + 数字后缀是能保证唯一的,这数字后缀的应用场景可以实现诸如分布式队列,分布式公平锁等。 2.3 容器 容器节点是 3.5 以后新增的节点类型,只要在调用 create 方法时,指定 CreateMode 为 CONTAINER 即可创建容器的节点类型,容器节点的表现形式和持久节点是一样的,但是区别是 ZK 服务端启动后,会有一个单独的线程去扫描,所有的容器节点,当发现容器节点的子节点数量为 0 时,会自动删除该节点,除此之外和持久节点没有区别,官方注释给出的使用场景是 Container nodes are special purpose nodes useful for recipes such as leader, lock, etc. 说可以用在 leader 或者锁的场景中。 2.4 持久 TTL、持久顺序 TTL 关于持久和顺序这两个关键字,不用我再解释了,这两种类型的节点重点是后面的 TTL,TTL 是 time to live 的缩写,指带有存活时间,简单来说就是当该节点下面没有子节点的话,超过了 TTL 指定时间后就会被自动删除,特性跟上面的容器节点很像,只是容器节点没有超时时间而已,但是 TTL 启用是需要额外的配置(这个之前也有提过)配置是 zookeeper.extendedTypesEnabled 需要配置成 true,否则的话创建 TTL 时会收到 Unimplemented 的报错 三、原理介绍 单纯的持久和临时节点我就不介绍了,之前的系列文章有讲 3.1 顺序关键字 客户端创建一个顺序节点的时候,服务端得知当前节点是顺序节点的时候会自动给路径加上后缀,后缀就是父节点的 cversion,代表创建子节点的个数 if (createMode.isSequential()) { path = path + String.format(Locale.ENGLISH, "%010d", parentCVersion); } 就是这么简单~ 3.2 容器、TTL 关键字 这两种其实可以放在一起讲,服务端在启动的时候会额外启动一个定时任务线程,会定期的扫描所有的容器和 TTL 的节点,逐一判断子节点的数量以及一些相关配置,来决定是否删除,首先整个逻辑是在 ContainerManager 中,定时任务是由 TimeTask 实现的,相关的配置有 配置项 默认值 说明 znode.container.checkIntervalMs 60000(毫秒) 定时任务检查的间隔 znode.container.maxPerMinute 10000 和上面的参数联合成为最小的检查间隔,每个节点间隔必须差 (60000 / 10000)毫秒(默认 6 毫秒)以上 znode.container.maxNeverUsedIntervalMs 0 如果配置不为 0 的话,当容器和 TTL 节点最后一次更新的时间和当前时间戳的差超过这个值的话,也会被删除 四、小结 持久关键字:客户端不主动删除的话,节点数据会一直存在 临时关键字:客户端连接断开后,节点数据会被一起删除 顺序关键字:服务端会自动为该节点加数字后缀 容器:服务端会定期扫描这些节点,当该节点下面没有子节点时(或其他条件时)服务端会自动删除节点 TTL:需要额外配置才能启用,基本和容器相同,当超过 TTL 时间节点下面都没有再创建子节点时会被删除,但是当创建子节点会重置该超时时间 ZKr~好久没更了,这次先挑一个简单的话题。后面大家想学哪些 ZK 的知识可以留言告诉我。 关注 HelloGitHub 公众号 第一时间收到更新。 还有更多开源项目的介绍和宝藏项目等待你的发现。

优秀的个人博客,低调大师

面试被问高并发流量控制,我脸都绿了...

云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 前言 在实际项目中,曾经遭遇过线上5W+QPS的峰值,也在压测状态下经历过10W+QPS的大流量请求,本篇博客的话题主要就是自己对高并发流量控制的一点思考。 应对大流量的一些思路 首先,我们来说一下什么是大流量? 大流量,我们很可能会冒出:TPS(每秒事务量),QPS(每秒请求量),1W+,5W+,10W+,100W+...。其实并没有一个绝对的数字,如果这个量造成了系统的压力,影响了系统的性能,那么这个量就可以称之为大流量了。 其次,应对大流量的一些常见手段是什么? 缓存:说白了,就是让数据尽早进入缓存,离程序近一点,不要大量频繁的访问DB。降级:如果不是核心链路,那么就把这个服务降级掉。打个比喻,现在的APP都讲究千人千面,拿到数据后,做个性化排序展示,如果在大流量下,这个排序就可以降级掉!关注公众号互联网架构师,回复关键字2T,获取最新架构视频限流:大家都知道,北京地铁早高峰,地铁站都会做一件事情,就是限流了!想法很直接,就是想在一定时间内把请求限制在一定范围内,保证系统不被冲垮,同时尽可能提升系统的吞吐量。 注意到,有些时候,缓存和降级是解决不了问题的,比如,电商的双十一,用户的购买,下单等行为,是涉及到大量写操作,而且是核心链路,无法降级的,这个时候,限流就比较重要了。 那么接下来,我们重点说一下,限流。 限流的常用方式 限流的常用处理手段有:计数器、滑动窗口、漏桶、令牌。 计数器 计数器是一种比较简单的限流算法,用途比较广泛,在接口层面,很多地方使用这种方式限流。在一段时间内,进行计数,与阀值进行比较,到了时间临界点,将计数器清0。 这里需要注意的是,存在一个时间临界点的问题。举个栗子,在12:01:00到12:01:58这段时间内没有用户请求,然后在12:01:59这一瞬时发出100个请求,OK,然后在12:02:00这一瞬时又发出了100个请求。这里你应该能感受到,在这个临界点可能会承受恶意用户的大量请求,甚至超出系统预期的承受。 滑动窗口 由于计数器存在临界点缺陷,后来出现了滑动窗口算法来解决。 滑动窗口原理图滑动窗口的意思是说把固定时间片,进行划分,并且随着时间的流逝,进行移动,这样就巧妙的避开了计数器的临界点问题。也就是说这些固定数量的可以移动的格子,将会进行计数判断阀值,因此格子的数量影响着滑动窗口算法的精度。 漏桶 虽然滑动窗口有效避免了时间临界点的问题,但是依然有时间片的概念,而漏桶算法在这方面比滑动窗口而言,更加先进。有一个固定的桶,进水的速率是不确定的,但是出水的速率是恒定的,当水满的时候是会溢出的。 令牌桶 注意到,漏桶的出水速度是恒定的,那么意味着如果瞬时大流量的话,将有大部分请求被丢弃掉(也就是所谓的溢出)。为了解决这个问题,令牌桶进行了算法改进。 生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。这意味,面对瞬时大流量,该算法可以在短时间内请求拿到大量令牌,而且拿令牌的过程并不是消耗很大的事情。(有一点生产令牌,消费令牌的意味) 不论是对于令牌桶拿不到令牌被拒绝,还是漏桶的水满了溢出,都是为了保证大部分流量的正常使用,而牺牲掉了少部分流量,这是合理的,如果因为极少部分流量需要保证的话,那么就可能导致系统达到极限而挂掉,得不偿失。 限流神器:Guava RateLimiter Guava不仅仅在集合、缓存、异步回调等方面功能强大,而且还给我们封装好了限流的API! Guava RateLimiter基于令牌桶算法,我们只需要告诉RateLimiter系统限制的QPS是多少,那么RateLimiter将以这个速度往桶里面放入令牌,然后请求的时候,通过tryAcquire()方法向RateLimiter获取许可(令牌)。 分布式场景下的限流 上面所说的限流的一些方式,都是针对单机而言的,其实大部分的场景,单机的限流已经足够了。分布式下限流的手段常常需要多种技术相结合,比如Nginx+Lua,Redis+Lua等去做。本文主要讨论的是单机的限流,这里就不在详细介绍分布式场景下的限流了。 一句话,让系统的流量,先到队列中排队、限流,不要让流量直接打到系统上。 【云栖号在线课堂】每天都有产品技术专家分享!课程地址:https://yqh.aliyun.com/zhibo 立即加入社群,与专家面对面,及时了解课程最新动态!【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK 原文发布时间:2020-07-17本文作者:张丰哲本文来自:“互联网架构师”,了解相关信息可以关注“互联网架构师”

优秀的个人博客,低调大师

面试必问的4种单点登录的实现方式,你知道几个?

前言 单点登录在现在的系统架构中广泛存在,他将多个子系统的认证体系打通,实现了一个入口多处使用,而在架构单点登录时,也会遇到一些小问题,在不同的应用环境中可以采用不同的单点登录实现方案来满足需求。 一、共享Session 共享Session可谓是实现单点登录最直接、最简单的方式。将用户认证信息保存于Session中,即以Session内存储的值为用户凭证,这在单个站点内使用是很正常也很容易实现的,而在用户验证、用户信息管理与业务应用分离的场景下即会遇到单点登录的问题,在应用体系简单,子系统很少的情况下,可以考虑采用Session共享的方法来处理这个问题。 这个架构我使用了基于Redis的Session共享方案。将Session存储于Redis上,然后将整个系统的全局Cookie Domain设置于顶级域名上,这样SessionID就能在各个子系统间共享。分布式Session共享解决方案,这篇推荐大家看下。这个方案存在着严重的扩展性问题,首先,ASP.NET的Session存储必须为SessionStateItemCollection对象,而存储的结构是经过序列化后经过加密存储的。并且当用户访问应用时,他首先做的就是将存储容器里的所有内容全部取出,并且反序列化为SessionStateItemCollection对象。这就决定了他具有以下约束:1.Session中所涉及的类型必须是子系统中共同拥有的(即程序集、类型都需要一致),这导致Session的使用受到诸多限制;2.跨顶级域名的情况完全无法处理; 二、基于OpenId的单点登录 这种单点登录将用户的身份标识信息简化为OpenId存放于客户端,当用户登录某个子系统时,将OpenId传送到服务端,服务端根据OpenId构造用户验证信息,多用于C/S与B/S相结合的系统,流程如下: 由上图可以看到,这套单点登录依赖于OpenId的传递,其验证的基础在于OpenId的存储以及发送。1.当用户第一次登录时,将用户名密码发送给验证服务;2.验证服务将用户标识OpenId返回到客户端;3.客户端进行存储;4.访问子系统时,将OpenId发送到子系统;5.子系统将OpenId转发到验证服务;6.验证服务将用户认证信息返回给子系统;7.子系统构建用户验证信息后将授权后的内容返回给客户端。这套单点登录验证机制的主要问题在于他基于C/S架构下将用户的OpenId存储于客户端,在子系统之间发送OpenId,而B/S模式下要做到这一点就显得较为困难。为了处理这个问题我们将引出下一种方式,这种方式将解决B/S模式下的OpenId的存储、传递问题。 三、基于Cookie的OpenId存储方案 我们知道,Cookie的作用在于充当一个信息载体在Server端和Browser端进行信息传递,而Cookie一般是以域名为分割的,例如a.xxx.com与b.xxx.com的Cookie是不能互相访问的,但是子域名是可以访问上级域名的Cookie的。即a.xxx.com和b.xxx.com是可以访问xxx.com下的Cookie的,于是就能将顶级域名的Cookie作为OpenId的载体。 验证步骤和上第二个方法非常相似:1、 在提供验证服务的站点里登录;2、 将OpenId写入顶级域名Cookie里;3、 访问子系统(Cookie里带有OpenId)4、 子系统取出OpenId通过并向验证服务发送OpenId5、 返回用户认证信息6、 返回授权后的内容在以上两种方法中我们都可以看到通过OpenId解耦了Session共享方案中的类型等问题,并且构造用户验证信息将更灵活,子系统间的验证是相互独立的,但是在第三种方案里,我们基于所有子系统都是同一个顶级域名的假设,而在实际生产环境里有多个域名是很正常的事情,那么就不得不考虑跨域问题究竟如何解决。 四、B/S多域名环境下的单点登录处理 在多个顶级域名的情况下,我们将无法让各个子系统的OpenId共享。处理B/S环境下的跨域问题,我们首先就应该想到JSONP的方案。 验证步骤如下:1、 用户通过登录子系统进行用户登录;2、 用户登录子系统记录了用户的登录状态、OpenId等信息;3、 用户使用业务子系统;4、 若用户未登录业务子系统则将用户跳转至用户登录子系统;5、 用户子系统通过JSONP接口将用户OpenId传给业务子系统;6、 业务子系统通过OpenId调用验证服务;7、 验证服务返回认证信息、业务子系统构造用户登录凭证;(此时用户客户端已经与子业务系统的验证信息已经一一对应)8、 将用户登录结果返回用户登录子系统,若成功登录则将用户跳转回业务子系统;9、 将授权后的内容返回客户端; 五、安全问题 经过以上步骤,跨域情况下的单点登录问题已经可以得到解决。而在整个开发过程初期,我们采用用户表中纪录一个OpenId字段来保存用户OpenId,而这个机制下很明显存在一些安全性、扩展性问题。这个扩展性问题主要体现在一个方面:OpenId的安全性和用户体验的矛盾。 整个单点登录的机制决定了OpenId是会出现在客户端的,所以OpenId需要有过期机制,假如用户在一个终端登录的话可以选择在用户每次登录或者每次退出时刷新OpenId,而在多终端登录的情况下就会出现矛盾:当一个终端刷新了OpenId之后其他终端将无法正常授权。欢迎大家关注我的公种浩【程序员追风】,文章都会在里面更新,整理的资料也会放在里面。 而最终,我采用了单用户多OpenId的解决方案。每次用户通过用户名/密码登录时,产生一个OpenId保存在Redis里,并且设定过期时间,这样多个终端登录就会有多个OpenId与之对应,不再会存在一个OpenId失效所有终端验证都失效的情况。 最后 欢迎大家一起交流,喜欢文章记得点个赞哟,感谢支持!

优秀的个人博客,低调大师

谁偷了1/3个CPU - 诡异Go性能问题追根问底

看到过不少文章介绍自己CPU占用恶高甚至接近100%,其实那到反而清楚无遗漏了,无非哪个busy loop卡住了。这里为大家描述一个近期遇到的Go程序在空闲时候依旧在top命令里总报告30%左右CPU占用的问题,这样的性能问题更隐蔽更难琢磨。 问题发生在我自己做的高性能多组Raft库Dragonboat里,这是一个Apache2开源的Go实现的多组Raft库,它的主打就是吞吐性能吊打竟品几十倍。因为性能是核心卖点,因此每个函数的CPU耗费都了如指掌,直到有一天突然发现系统空载的时候占用一个CPU核的30%的负载,如下图: 服务器程序空闲的时候,top中看到的cpu负载通常应该是个位数低位。深入分析以后发现是一个较深的Go调度实现的问题。 观察 看到上述top的结果,strace -c 看了一下,很多futex。多启动几个这样的空闲进程,抓个火焰图看,它是这样的: 必须得假设您对Go近期版本(如1.8-1.11)的调度有一定基本了解,了解M、P、G三者的意义和作用。如暂时不了解,可参考本文或者该文的中文翻译。 火焰图中可以观察到几点: 让当前M去sleep的操作挺重的,它由tickWorkerMain试图去读一个channel引发 runtime.futex的数据和上面提到的strace所报告的情况吻合 查看代码,Dragonboat库tickWorkerMain中有一个1khz的ticker,等于每秒读ticker.C这个channel 1000次。当时第一感觉是有些疑惑,因为常识告诉我,一个简单的非严格1khz的低频ticker,在3Ghz左右的服务器处理器上,cpu占用应该在1-2%这样极低的占用才合理。只是希望程序某个函数被一秒调用1000次,怎么就占用掉近30%的cpu? 先不管成因,孤立出这个问题点来看看。为了实现“程序某个函数被一秒调用1000次”这点,在Go中用ticker可以这么做: package main import ( "time" ) func main() { ticker := time.NewTicker(time.Millisecond) defer ticker.Stop() for range ticker.C { } } 结果上述程序top报告25%的%CPU值。是Go的ticker实现的问题吗?换sleep循环看看: package main import ( "time" ) func main() { for { time.Sleep(time.Millisecond) } } 运行上述time.Sleep程序,top报告15%的%CPU。这和同样一个sleep循环的C++在1%的%CPU有天壤之别。于是开始倾向于是Go的调度器的锅。 分析 再回到上述火焰图,park_m()后一连串操作很显眼。我们已经知道M表示Machine,通常认为是一个OS Thread,park_m()后续stopm()顾名思义就是这个把当前M给停用掉,告诉系统这个M暂时不用。 一切似乎开始明朗了。每次tickWorkerMain开始等下一个tick的时候,也就是去读ticker.C这个channel的时候都因为时间没到channel为空,当前goroutine会被park掉,这个操作很轻,只需要更改一个标志。而此时因为系统空闲,并没有别的goroutine可供调度,Go的scheduler就必须让这个M去sleep,而这个操作是较重的且有锁,最终futex的syscall被调用。更具体的,这还和Go的后台timer实现、system monitor实现有关(注意火焰图中runtime.sysmon),这里不展开。人人都会告诉你协程调度是很轻的操作,这当然没错。但他们都没有告诉你更重要的一点:协程调度反复高频出现没有goroutine可供调度的代价在Go的当前实现里是显著的。 必须指出,本问题是因为系统空闲没有goroutine可以调度造成的。显然的,系统繁忙的时候,即CPU资源真正体现价值时,上述30%的%CPU的overhead并不存在,因为大概率下会有goroutine可供调度,无需去做让M去sleep这个很重的操作。 而这一问题的影响是具体客观的: 用户会反复提问为何系统空闲时占30%的%CPU,空闲时进程在top里始终排顶部不是个好事 在一个慢速的用电池的ARM核上有这东西就麻烦了 cgo解决 我们也已经知道,C/C++做同样的事代价很轻。如果从不改业务逻辑出发,首先想到的就是不让M去sleep,不发生无goroutine可调度的情况。比如,可以用一个OS线程通过cgo独立于scheduler地去产生这个1kHz的tick,每秒从C代码去调用1000次所需的Go函数。这个思路很容易实现,无非是从Go代码里调用一个C函数,这个C函数每秒1000次的从sleep中醒来去调用Go里的1khz的tick的处理函数,具体就不贴具体代码了。 用这个思路修改了Dragonboat的代码一跑,空闲时的cpu负载大幅降低: 结果 上述workaround已经让这一问题对自己软件的影响极大降低了。去golang-nuts吐槽一下,再报golang的issue tracker,根本的问题还是Go Scheduler的实现。用户用1kHz的ticker不应该是这样大费周章,标准库、runtime上直接提供更高效的实现才是真正解决方案。 在Dragonboat的开发中,这样的performance regression几乎每周都发生。从一秒十万次吞吐到一秒一千万吞吐的进化,是算法协议不断理解的深入,也是对Go runtime习性的不断熟悉的一个过程。后面陆续会风向大量这样的性能优化实践知识,均以目前互联网后台最热门Golang为语言,素材均为任何应用均会涉及的通用场景。作为最好的教材,欢迎大家试用Dragonboat,也请大家点Star支持它的持续开发。

优秀的个人博客,低调大师

这几道Java集合框架面试题在面试中几乎必问

本文是“最最最常见Java面试题总结”系列第三周的文章。主要内容: Arraylist 与 LinkedList 异同 ArrayList 与 Vector 区别 HashMap的底层实现 HashMap 和 Hashtable 的区别 HashMap 的长度为什么是2的幂次方 HashSet 和 HashMap 区别 ConcurrentHashMap 和 Hashtable 的区别 ConcurrentHashMap线程安全的具体实现方式/底层具体实现 集合框架底层数据结构总结 本文会同步更新在我开源的Java学习指南仓库 Java-Guide (一份涵盖大部分Java程序员所需要掌握的核心知识,正在一步一步慢慢完善,期待您的参与)中,地址:https://github.com/Snailclimb/Java-Guide,欢迎star、issue、pr。 Arraylist 与 LinkedList 异同 1. 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; 2. 底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向循环链表数据结构; 3. 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element) )时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。 4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而ArrayList 实现了RandmoAccess 接口,所以有随机访问功能。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index) 方法)。 5. 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。 补充:数据结构基础之双向链表 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表,如下图所示,同时下图也是LinkedList 底层使用的是双向循环链表数据结构。 ArrayList 与 Vector 区别 Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。 Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。 HashMap的底层实现 JDK1.8之前 JDK1.8 之前 HashMap 由 数组+链表 组成的(“链表散列” 即数组和链表的结合体),数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(HashMap 采用 “拉链法也就是链地址法” 解决冲突),如果定位到的数组位置不含链表(当前 entry 的 next 指向 null ),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度依然为 O(1),因为最新的 Entry 会插入链表头部,急需要简单改变引用链即可,而对于查找操作来讲,此时就需要遍历链表,然后通过 key 对象的 equals 方法逐一比对查找. 所谓 “拉链法” 就是将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 JDK1.8之后 相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。 推荐阅读: 《Java 8系列之重新认识HashMap》 :https://zhuanlan.zhihu.com/p/21673805 HashMap 和 Hashtable 的区别 线程是否安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!); 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它; 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。 初始容量大小和每次扩充容量大小的不同 : ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。 HashMap 的长度为什么是2的幂次方 为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。 这个算法应该如何设计呢? 我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。 HashSet 和 HashMap 区别 ConcurrentHashMap 和 Hashtable 的区别 ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; 实现线程安全的方式(重要): ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 两者的对比图: 图片来源:http://www.cnblogs.com/chengxiao/p/6842045.html HashTable: JDK1.7的ConcurrentHashMap:JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点Node: 链表节点): ConcurrentHashMap线程安全的具体实现方式/底层具体实现 JDK1.7(上面有示意图) 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 ConcurrentHashMap 是由 Segment 数组结构和 HahEntry 数组结构组成。 Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。 static class Segment<K,V> extends ReentrantLock implements Serializable { } 一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。 JDK1.8 (上面有示意图) ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。 synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。 集合框架底层数据结构总结 Collection 1. List Arraylist: Object数组 Vector: Object数组 LinkedList: 双向循环链表 2. Set HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素 LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。 TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。) Map HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间 LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》 HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的 TreeMap: 红黑树(自平衡的排序二叉树) 推荐阅读: jdk1.8中ConcurrentHashMap的实现原理 HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你! HASHMAP、HASHTABLE、CONCURRENTHASHMAP的原理与区别 ConcurrentHashMap实现原理及源码分析 java-并发-ConcurrentHashMap高并发机制-jdk1.8 你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号有大量资料,回复关键字“1”你可能看到想要的东西哦!

优秀的个人博客,低调大师

Intellij IDEA导入JAVA项目并启动(哈哈哈,天天都有人问)

最近有很多同学,竟然不知道如何使用Intellij IDEA打开Java项目并启动 现在来讲一下,希望不要忘记了 1、打开IDEA开机页面 Maven项目 2、Maven项目是以pom文件引入各项jar包的 在点击lmport Project,然后在点击pom.xml,再点击Open 3、再点击Next 4、如果是公司的私服,还需要勾选nexus 5、继续点击下一步 6、选择Jdk版本,点击下一步 7、点击Finish 8、找到主方法,点击运行 maven项目导入完毕 如果是有jar包的Java项目 1、点击import 2、找到项目的位置,点击open 3、打开 4、next 5、next 6、导入包 next 7、选择jdk 8、运行 结束

优秀的个人博客,低调大师

数仓的等待视图中,为什么会有Hashjoin-nestloop

本文分享自华为云社区《GaussDB(DWS)等待视图之Hashjoin-nestloop》,作者:Arrow0lf。 1. 业务场景 众所周知,GaussDB(DWS)中有3种常见的join方式:HashJon/MergeJoin/NestLoop 但在有一些场景中,等待视图中等待状态会显示为:HashJoin-nestloop,如下图所示。这种表示什么含义? 2. 基本原理 为了明白该状态的原因,首先思考如下场景:当业务侧两张大表join时,如果由于未做analyze或统计信息不准,导致build hash的一侧选择了大表,且该表在join列上重复值很多,会导致hashjoin时内存膨胀,当内存不足时,hashjon算子会下盘,但是由于join列上存在大量重复值,下盘文件无法有效分裂,此时,如果将整个文件都读取到内存中,会导致内存占用很高,出现内存过载,导致其他业务内存不足报错。 为了解决该场景,在向量化hashjoin时,当使用内表创建的hash表过大导致内存不足时,不再强制进行hashjoin,会通过内外表交换或执行nestloop使查询平稳进行,防止出现内存报错,此时,等待视图状态为“HashJoin-nestloop” 上述特性通过hashjoin_spill_strategy参数控制,默认为0,取值范围为0-6的整数,详情可以参考产品文档(8.1.2及以上版本),简单来讲: 取值为0或5,hashjoin时会先尝试内外表交换,如果仍然内存占用高,会选择nestloop; 取值为1或6,hashjoin时会先尝试内外标交换,如果仍然内存占用高,会强行执行hashjoin; 取值为2,hashjoin行为和原本的行为保持一致,即使内存不够,也会强制执行hashjoin 3. 业务影响 当等待视图出现Hashjoin-nestloop时,可能会导致原来内存占用高,单能执行成功的语句,在被转换成nestloop后,可能会短时间执行不出来。尤其是当数据量变化较大,统计信息差异较大时,容易出现执行计划非最优场景下的性能劣化。 4. 解决方法 如果出现上述HashJoin-nestloop时间长,导致业务超时的情况。可以将参数hashjoin_spill_strategy设置为2进行规避。不再进行内外表交换或执行nestloop,使业务行为与之前的行为保持一致。 在内存充裕的场景下,可以全局设置为2。 点击关注,第一时间了解华为云新鲜技术~

优秀的个人博客,低调大师

数仓调优实践丨SQL改写消除相关子查询

本文分享自华为云社区《【调优实践】SQL改写消除相关子查询》,作者: 门前一棵葡萄树 。 一、子查询 GaussDB(DWS)根据子查询在SQL语句中的位置把子查询分成了子查询、子链接两种形式。 子查询SubQuery:对应于查询解析树中的范围表RangeTblEntry,更通俗一些指的是出现在FROM语句后面的独立的SELECT语句。 子链接SubLink:对应于查询解析树中的表达式,更通俗一些指的是出现在where/on子句、targetlist里面的语句。 1.1 非相关子查询 子查询的执行不依赖于外层父查询的任何属性值。这样子查询具有独立性,可独自求解,形成一个子查询计划先于外层的查询求解。示例: select t1.c1,t1.c2 from t1 where t1.c1 in ( select c2 from t2 where t2.c2 IN (2,3,4) ); 1.2 相关子查询 子查询的执行依赖于外层父查询的一些属性值(如下列示例t2.c1 = t1.c1条件中的t1.c1)作为内层查询的一个AND-ed条件。这样的子查询不具备独立性,需要和外层查询按分组进行求解。 select t1.c1,t1.c2 from t1 where t1.c1 in ( select c2 from t2 where t2.c1 = t1.c1 AND t2.c2 in (2,3,4) ); 二、调优实战 2.1 案例: UPDATE t1 SET (c1,c2)=( SELECT COALESCE(t2.c1, t1.c2),c2 FROM t2 WHERE t1.i1 = t2.i1 -- 相关标量子查询 ); 其中子查询SELECT COALESCE(t2.c1, t1.c2),c2 FROM t2 WHERE t1.i1 = t2.i1 依赖于外层父查询的t1表,因此属于相关子查询。执行计划: QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------- id | operation | A-time | A-rows | E-rows | E-distinct | Peak Memory | E-memory | A-width | E-width | E-costs ----+-----------------------------------------------+----------------+--------+--------+------------+----------------+----------+---------+---------+--------- 1 | -> Streaming (type: GATHER) | 8.998 | 0 | 1 | | 24KB | | | 17 | 9.83 2 | -> Update on public.t1 | [0.086, 0.096] | 2 | 2 | | [308KB, 308KB] | | | 17 | 9.74 3 | -> Seq Scan on public.t1 | [0.058, 0.074] | 2 | 2 | | [32KB, 32KB] | 1MB | | 17 | 3.73 4 | -> Result [3, SubPlan 1] | [0.033, 0.034] | 2 | 10 | | [16KB, 16KB] | 1MB | | 6 | 1.36 5 | -> Materialize | [4.167, 4.458] | 20 | 10 | | [16KB, 16KB] | 16MB | [24,24] | 6 | 1.36 6 | -> Streaming(type: BROADCAST) | [4.105, 4.406] | 10 | 10 | | [48KB, 48KB] | 2MB | | 6 | 1.33 7 | -> Seq Scan on public.t2 | [0.013, 0.013] | 5 | 5 | | [32KB, 32KB] | 1MB | | 6 | 1.02 8 | -> Result [3, SubPlan 2] | [0.006, 0.021] | 2 | 10 | | [16KB, 16KB] | 1MB | | 6 | 1.36 9 | -> Materialize | [0.055, 0.061] | 20 | 10 | | [16KB, 16KB] | 16MB | [24,24] | 6 | 1.36 10 | -> Streaming(type: BROADCAST) | [0.034, 0.040] | 10 | 10 | | [48KB, 48KB] | 2MB | | 6 | 1.33 11 | -> Seq Scan on public.t2 | [0.005, 0.009] | 5 | 5 | | [32KB, 32KB] | 1MB | | 6 | 1.02 2.2 子查询消除 改写策略就是解除子查询与父查询依赖关系,改写方案参考: UPDATE t1 SET (c1,c2)=(t3.c1,t3.c2) FROM ( SELECT t2.i1,COALESCE(t2.c1, t1.c2) c1,t2.c2 FROM t1,t2 WHERE t1.i1 = t2.i1 )t3 WHERE t1.i1 = t3.i1; 改写后,子查询独立,不再依赖父查询中元素。执行计划: QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- id | operation | A-time | A-rows | E-rows | E-distinct | Peak Memory | E-memory | A-width | E-width | E-costs ----+-----------------------------------------------------+----------------+--------+--------+------------+----------------+----------+---------+---------+--------- 1 | -> Streaming (type: GATHER) | 13.141 | 0 | 1 | | 24KB | | | 33 | 10.56 2 | -> Update on public.t1 | [6.242, 6.362] | 2 | 2 | | [308KB, 308KB] | | | 33 | 10.47 3 | -> Streaming(type: RESTORE) | [6.186, 6.310] | 2 | 2 | | [48KB, 48KB] | 2MB | | 33 | 4.46 4 | -> Nested Loop (5,11) | [4.082, 4.801] | 2 | 2 | | [32KB, 32KB] | 1MB | | 33 | 4.44 5 | -> Streaming(type: BROADCAST) | [3.804, 4.541] | 4 | 4 | | [48KB, 48KB] | 2MB | | 27 | 2.36 6 | -> Nested Loop (7,8) | [2.972, 4.267] | 2 | 2 | | [32KB, 32KB] | 1MB | | 27 | 2.20 7 | -> Seq Scan on public.t1 | [0.010, 0.011] | 2 | 2 | | [16KB, 16KB] | 1MB | | 14 | 1.01 8 | -> Materialize | [2.724, 4.055] | 6 | 4 | | [16KB, 16KB] | 16MB | [28,28] | 13 | 1.17 9 | -> Streaming(type: BROADCAST) | [2.667, 4.008] | 4 | 4 | | [48KB, 48KB] | 2MB | | 13 | 1.17 10 | -> Seq Scan on public.t1 | [0.008, 0.012] | 2 | 2 | | [16KB, 16KB] | 1MB | | 13 | 1.01 11 | -> Materialize | [0.018, 0.022] | 12 | 5 | | [16KB, 16KB] | 16MB | [32,32] | 14 | 2.03 12 | -> Seq Scan on public.t2 | [0.007, 0.009] | 5 | 5 | 点击关注,第一时间了解华为云新鲜技术~

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册