java面试题大全
2023-03-03 21:43:59 70 举报
AI智能生成
java面试题大全,内容全面
作者其他创作
大纲/内容
它是一轻量级/高性能的反向代理服务器,能做反向代理,负载均衡,还支持2-3万并发连接数,官方测试5万并发量
什么是Nginx?
跨平台、配置简单、反向代理、高并发连接,处理2-3万并发连接数,官方测试5万并发量Nginx它有个健康检查功能:如果有一个服务器宕机,它会做一个健康检查,再发送请求就不会发送到宕机的服务器上,会发送到其他的节点上节省带宽:支持GZIP压缩,可以添加浏览器本地缓存稳定性高:宕机的概率非常小接收用户请求是异步的
为什么要用Nginx?
他有事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一个队列,排队解决
为什么Nginx性能这么高?
nginx接收一个请求后,首先有listen和server_name指令匹配server模块,再匹配server模块里的location,location就是实际地址
Nginx怎么处理请求的?
正向代理就是一个人发送一个请求直接就到达了目标的服务器反方代理就是请求统一被Nginx接收,nginx反向代理服务器接收到之后,按照一定的规 则分发给了后端的业务处理服务器进行处理了
什么是正向代理和反向代理?
反向代理服务器可以隐藏源服务器的存在和特征。它充当互联网云和web服务器之间的中间层。这对于安全方面来说是很好的,特别是当您使用web托管服务时。
使用“反向代理服务器的优点是什么?
Nginx的优缺点?
Nginx应用场景?
[root@localhost ~]# tree /usr/local/nginx/usr/local/nginx├── client_body_temp├── conf # Nginx所有配置文件的目录│ ├── fastcgi.conf # fastcgi相关参数的配置文件│ ├── fastcgi.conf.default # fastcgi.conf的原始备份文件│ ├── fastcgi_params # fastcgi的参数文件│ ├── fastcgi_params.default │ ├── koi-utf│ ├── koi-win│ ├── mime.types # 媒体类型│ ├── mime.types.default│ ├── nginx.conf # Nginx主配置文件│ ├── nginx.conf.default│ ├── scgi_params # scgi相关参数文件│ ├── scgi_params.default │ ├── uwsgi_params # uwsgi相关参数文件│ ├── uwsgi_params.default│ └── win-utf├── fastcgi_temp # fastcgi临时数据目录├── html # Nginx默认站点目录│ ├── 50x.html # 错误页面优雅替代显示文件,例如当出现502错误时会调用此页面│ └── index.html # 默认的首页文件├── logs # Nginx日志目录│ ├── access.log # 访问日志文件│ ├── error.log # 错误日志文件│ └── nginx.pid # pid文件,Nginx进程启动后,会把所有进程的ID号写到此文件├── proxy_temp # 临时目录├── sbin # Nginx命令目录│ └── nginx # Nginx的启动命令├── scgi_temp # 临时目录└── uwsgi_temp # 临时目录
Nginx目录结构有哪些?
worker_processes 1; # worker进程的数量events { # 事件区块开始 worker_connections 1024; # 每个worker进程支持的最大连接数} # 事件区块结束http { # HTTP区块开始 include mime.types; # Nginx支持的媒体类型库文件 default_type application/octet-stream; # 默认的媒体类型 sendfile on; # 开启高效传输模式 keepalive_timeout 65; # 连接超时 server { # 第一个Server区块开始,表示一个独立的虚拟主机站点 listen 80; # 提供服务的端口,默认80 server_name localhost; # 提供服务的域名主机名 location / { # 第一个location区块开始 root html; # 站点的根目录,相当于Nginx的安装目录 index index.html index.htm; # 默认的首页文件,多个用空格分开 } # 第一个location区块结果 error_page 500502503504 /50x.html; # 出现对应的http状态码时,使用50x.html回应客户 location = /50x.html { # location区块开始,访问50x.html root html; # 指定对应的站点目录为html } }
Nginx配置文件nginx.conf有哪些属性模块?
静态资源访问,就是存放在nginx的html页面
Nginx静态资源?
使用Nginx转发请求。把跨域的接口写成调用域的接口,然后将这些接口转发到真正的请求地址。
如何使用Nginx解决前端跨域问题?
1、基于域名的虚拟主机,通过域名来区分虚拟主机——应用:外部网站2、基于端口的虚拟主机,通过端口来区分虚拟主机——应用:公司内部网站,外部网站的管理后台3、基于ip的虚拟主机。
Nginx虚拟主机怎么配置?
span style=\
location的作用是什么?
Nginx限流就是限制用户请求速度,防止服务器受不了限流有3种正常限制访问频率(正常流量)突发限制访问频率(突发流量)限制并发连接数Nginx的限流都是基于漏桶流算法,底下会说道什么是桶铜流
限流怎么做的?
为什么要做动静分离?
先设置location路径,然后把静态资源放在该路径下,然后重启nginx,重启好了后浏览器就可以直接访问该静态资源了
Nginx怎么做的动静分离?
~代表自己输入的英文字母
location的语法能说出来吗?
为了避免服务器崩溃,大家会通过负载均衡的方式来分担服务器压力。将对台服务器组成一个集群,当用户访问时,先访问到一个转发服务器,再由转发服务器将访问分发到压力更小的服务器。Nginx负载均衡实现的策略有以下五种:1、轮询(默认):每个请求按时间顺序逐一分配到不同的后端服务器,如果后端某个服务器宕机,能自动剔除故障系统。2、权重weight:weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下。其次是为在主从的情况下设置不同的权值,达到合理有效的地利用主机资源。3、ip_hash( IP绑定):每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题4、fair(第三方插件):对比 weight、ip_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,响应时间短的优先分配。5、url_hash(第三方插件):按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。
Nginx负载均衡的算法怎么实现的?策略有哪些?
1、指定上游服务器负载均衡服务器2、设置nginx与上游服务器超时时间和后端服务器连接的超时时间3、设置nginx发送给上游服务器超时时间4、设置nginx接受上游服务器超时时间
Nginx配置高可用性怎么配置?
Nginx怎么判断别IP不可访问?
全局变量
Rewrite全局变量是什么?
使用if判断$http_user_agent ~ +浏览器名,是就返回500
怎么限制浏览器访问?
可以
nginx能监听同一端口吗?
1、Active connections 当前活跃的用户连接2、accepts 接收到的用户连接总数3、handled Nginx处理的用户连接总数4、requests 用户请求总数5、Reading 当前连接中Nginx读取请求首部的个数6、Writing 当前连接中Nginx写返回给用户的个数7、Waiting 当前没有请求的活跃用户连接数
nginx提供哪些监控参数?
Nginx网页显示监控参数,默认是关闭的需要使用使用ngx_http_stub_status_module来解锁,使用Nginx -s reload命令查看监控参数
如何获取Nginx性能监控参数?
nginx
各服务通过nacos client向注册中心发送注册请求,发送的注册请求数据放在一个阻塞队列中(set集合)。客户端即刻返回注册成功
Nacos server通过一个异步任务去阻塞队列中进行消费,然后去写进注册表,最终完成注册。
1.服务注册
解答:通过内存队列+异步任务的形式
2.注册过程中如何实现高并发?
1.nacos client通过一个心跳任务来保持与注册中心的连接,也就是保活
2.ZK、netty也是通过心跳机制进行保活,但是使用的是轻量级的socket实现;Nacos底层使用的是http接口;
3.心跳机制:通过线程池,延时启动,默认5秒里面有个方法是setBeat();
服务端需要更新本次心跳的时间,也就是最后更新时间。
4.接收到心跳之后,服务端需要做什么?
Helthcheck()方法,进行健康检查任务,runnable 默认延时5秒处理
服务端拿到Instance的set集合后,遍历判断使用(当前时间-最后更新时间>设置的健康超时时间(默认15秒)),如果超过了15秒,则将状态改为 不健康。里面还有一个遍历判断:使用(当前时间-最后更新时间>设置的健康超时时间(默认30秒)),如果超过了30秒,则认为服务挂掉了,则从注册表中将该服务剔除
5.服务端如何进行感知心跳的?
3.心跳机制
如何读写数据避免数据错乱?保持读写的一致性?一种是加锁,但加锁无法实现读写的高并发。一种是写时复制COW。也就是在注册时:复制一份注册表(根据Instance,复制其中的一部分),在副本中进行写,写完进行替换。复制的内容其实是一个set集合,也就是Map结构里面的内层结构,根据Instance名称进行定位,并非复制的整个注册表。服务发现时,读取原数据,拉取到本地,然后经过frign接口去调用其他服务,当然frign调用的时候也要借助ribbon进行负载均衡,所以说ribbon实际上是做的客户端的负载均衡。实际上nacos是一个读写分离的机制。从而实现了高并发。
那么问题来了:假设两个服务同时注册、同时都复制了一份然后进行副本修改,替换的时候会产生并发覆盖问题吗?解答:不会。因为消费的时候是从阻塞队列中进行消费,是一个单线程的消费,所以不会出现并发覆盖的问题。
那么问题又来了:既然是单线程的消费,会不会导致队列任务的积压,因为单线程毕竟消费慢?解答:一、这是一个极小的概率事件,内存队列也比较的快,可以处理的来。二、就算出现了也无所谓,稍微延迟一点没有关系,eureka还延迟一分钟以上呢。除非你同时启动上千台才会出现这种情况。注:nacos的TPS为13000/S.TPS为吞吐量,吞吐量为每秒内的访问数值,QPS为峰值时的每秒钟访问数值。
4.Nacos如何处理注册表的读写高并发冲突?
bootstrap.yml -> application.yml -> application-stg.yml -> order-service.yml -> order-service-stg.yaml
5.nacos配置文件的加载顺序?
可以在bootstrap.yml中利用Spring.profiles进行配置
6.nacos如何配置不同环境或多环境
7.项目在启动中
我们使用的版本号1.4.1其他版本:1.4.22.0.32.1.0
Nacos的版本是多少?
nacos
(1)保存在redis,最常用,也是分布式下的验证token的解决方案,(2)数据库存储,性能比redis稍差,速度稍慢(3)不做保存,下次验证的时候直接用jwt.decode验证(服务端为express),存储的压力给到了客户端,但是每次从客户端传到服务器端的数据量会稍微大一些
生成的token后在服务端有哪些存储方式?
无状态的优点:可扩展性,安全性,多平台跨域
token是有状态的还是无状态的?
如何实现jwt续期
jwt ( json web token)优点: 无状态,json格式简单,不需要在服务端存储缺点 一旦创建无法销毁或者修改状态,没有办法做权限 控制security优点: 和spring无缝结合,可以对用户状态坐控制,可以做权限控制缺点 用起来比较复杂,权限控制需要一连串过滤连,消耗服务器内存空间
jwt和security的区别
生成token时,有效期设置短一些可以和redis结合使用,退出时销毁redis的token。判断redis的token即可还可以添加黑名单功能,对其进行拦截
如何销毁jwt
在验证用户登录状态的代码中,添加一段逻辑:判断cookie即将到期时,重新生成一个token。比如token有效期为30分钟,当用户请求我们时,我们可以判断如果用户的token有效期还剩下10分钟,那么就重新生成token。因此用户只要在操作我们的网站,就会续签token
既然token有效期短,怎么解决token失效后的续签问题?
可以设置cokkie的状态为httponly可以在请求中添加防盗链可以把token存放在请求头中或者local storage中,由于无状态的token过长,很容易达到4kb由于容易被篡改,payload中不要放用户重要信息,如密码
如何防止cookie被篡改
普通用户一般不知道cookie,也不会禁用,如何禁用我们对其友好提示,让其开启
如果用户禁用cookie怎么办
token是没办法做异地的判断的,这时我们可以采取传统的session方式
如何防止token异地登陆
我们有一套权限控制服务,保存用户的角色和路径信息、当用户发起请求的时候,通过网关的的全局pre过滤器拦截所有请求,获取请求信息,解析jwt参数,根据参数获取用户,根据用户明去查询数据库的用户是否存在,以及当前用户有无访问此地址的权限,没有即拦截
首先因为用户访问的时候走的是我们nginx的代理,微服务对外暴漏的可能性很低,即使暴漏了,我们每个微服务也有一套的鉴权校验策略;首先有一张表存每个微服务的id,密钥;服务器启动的时候使用id和密钥去授权中心生成jwt令牌,去访问其他的时候访问携带令牌
你们微服务地址对外暴漏了怎么办
如何防止token被篡改
首先我们有权限管理的服务,管理用户的各种权限,及可访问路径等在网关zuul中利用Pre过滤器,拦截一切请求,在过滤器中,解析jwt,获取用户身份,查询用户权限,判断用户身份可以访问当前路径
如何完成权限校验的?
多次登录生成的token都是一样的吗?都是可用的吗?
1、时间上:一般来说用户在获取验证码都设置有时间间隔,一般为60秒,如果用户在规定的时间内没有收到短信验证码,可以点击再次获取,来重新取得短信验证码。而验证码的有效时间限制是为了防止不法分子利用暴力手段破解后使用。一般短信服务商都会提醒企业设置验证码的有效时间,超过有效时间验证码即失效。2、次数上:短信验证码虽然很方便,但是它的使用次数是有限制的,不是用户想使用几次就可以使用几次的,这也是为了避免一些无聊用户频繁获取短信验证码而设置的一道坎。正常讲,大多数人肯定不会在注册账号或者更改个人信息的次数很频繁,所以如果遇到用户频繁多次的向后台发送验证码请求(而且都是同一个手机号),达到限制要求后就不能再正常使用。一般来说是设置的是一个手机号同一天时间只能发送5次验证码请求。3、频率上:一般用户在更改密码、注册某些帐号时,对于短信验证码总是不能正确输入,那么该验证就会视为无效。因为一般正常规范的验证码短信在极短时间内就能送达,有效时间内用户是完全能输入完成的。如果想再次输入短信验证码只能再次获取,再次录入。
手机号一天能进行多少次登录校验?
Token鉴权
(1) docker从基础镜像运行一个容器(2)执行一条指令并对容器作出修改(3)执行类似docker commit的操作提交一个 新的镜像层(4) docker再基 于刚提交的镜像运行个新容器(5)执行dockerfile中的下一 条指令 直到所有指令都执行完成
1.Dockerfile的构建过程
从应用软件的角度来看,Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段,* Dockerfile是软件的原材料* Docker镜像是软件的交付品* Docker容器则可以认为是软件的运行态。Dockerfile面向开发,Docker镜像成为交付标准,Docker容器则涉及部署与运维,三者缺一不可,合力充当Docker体系的基石。
1.Dockerfile,需要定义一个Dockerfile,Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计namespace的权限控制)等等;
2.Docker镜像,在用Dockerfile定义一个文件之后,docker build时会产生一个Docker镜像,当运行 Docker镜像时,会真正开始提供服务;
3.Docker容器,容器是直接提供服务的。
2.docker组成
Docker 是一种流行的开源软件平台,可简化创建、管理、运行和分发应用程序的过程。它使用容器来打包应用程序及其依赖项。我们也可以将容器视为 Docker 镜像的运行时实例。
什么是 Docker 容器?
Docker 是轻量级的沙盒,在其中运行的只是应用,虚拟机里面还有额外的系统。
Docker 和虚拟机有什么不同?
Dockerfile 是一个文本文件,其中包含我们需要运行以构建 Docker 镜像的所有命令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。Docker 使用 Dockerfile 中的指令自动构建镜像。我们可以 docker build 用来创建按顺序执行多个命令行指令的自动构建。
什么是 DockerFile?
Docker Compose 是一个用来定义和运行复杂应用的Docker工具。一个使用Docker容器的应用,通常由多个容器组成。使用Docker Compose不再需要使用shell脚本来启动容器。Compose 通过一个配置文件来管理多个Docker容器。简单理解:Docker Compose 是docker的管理工具。
使用Docker Compose时如何保证容器A先于容器B运行?
DockerClient 客户端Docker Daemon 守护进程Docker Image 镜像DockerContainer 容器
一个完整的Docker由哪些部分组成?
查看本地主机的所用镜像:`docker images``搜索镜像:`docker search mysql``下载镜像:docker pull mysql,没写 tag 就默认下载最新的 lastest下载指定版本的镜像:`docker pull mysql:5.7``删除镜像:`docker rmi -f 镜像id 镜像id 镜像id``docker --help查看帮助命令
docker常用命令
创建容器运行容器暂停容器(可选)取消暂停容器(可选)启动容器停止容器重启容器杀死容器销毁容器
描述 Docker 容器的生命周期。
host类型的网络bridge类型的网络overlay类型的网络macvlan类型的网络none类型的网络
Docker网络有哪几种?
Linux中的PID、IPC、网络等资源是全局的,而Linux的NameSpace机制是一种资源隔离方案,在该机制下这些资源就不再是全局的了,而是属于某个特定的NameSpace,各个NameSpace下的资源互不干扰。Namespace实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容。对于宿主机来说,这些被“隔离”了的进程跟其他进程并没有区别。虽然有了NameSpace技术可以实现资源隔离,但进程还是可以不受控的访问系统资源,比如CPU、内存、磁盘、网络等,为了控制容器中进程对资源的访问,Docker采用control groups技术(也就是cgroup),有了cgroup就可以控制容器中进程对系统资源的消耗了,比如你可以限制某个容器使用内存的上限、可以在哪些CPU上运行等等。
docker容器之间怎么隔离?
Docker 和虚拟机有啥不同?
Docker 利用了 Linux 内核中很多安全特性来保证不同容器之间的隔离,并且通 过签名机制来对镜像进行验证。大量生产环境的部署证明,Docker 虽然隔离性无法与 虚拟机相比,但仍然具有极高的安全性。
Docker 安全么?
整体远侧上,尽量保持镜像功能的明确和内容的精简,要点包括: l 尽量选取满足需求但较小的基础系统镜像,建议选择 debian:wheezy 镜像,仅有 86MB 大小 l 清理编译生成文件、安装包的缓存等临时文件 l 安装各个软件时候要指定准确的版本号,并避免引入不需要的依赖 l 从安全的角度考虑,应用尽量使用系统的库和依赖 l 使用 Dockerfile 创建镜像时候要添加.dockerignore 文件或使用干净的工作目录 容器相关
构建 Docker 镜像应该遵循哪些原则?
容器退出后会处于终止(exited)状态,此时可以通过 docker ps -a 查看,其中 数据不会丢失,还可以通过 docker start 来启动,只有删除容器才会清除数据。
容器退出后,通过 docker ps 命令查看不到,数据会丢失么
一般不推荐在同一个容器内运行多个应用进程,如果有类似需求,可以通过额外 的进程管理机制,比如 supervisord 来管理所运行的进程
可以在一个容器中同时运行多个应用进程吗?
Docker 不是虚拟机,而是进程隔离,对于资源的消耗很少,单一开发环境下 Vagrant 是虚拟机上的封装,虚拟机本身会消耗资源。 Other FAQ
开发环境中 Docker 与 Vagrant 该如何选择?
容器退出后会处于终止(exited)状态,此时可以通过 docker ps -a 查看,其中 数据不会丢失,还可以通过 docker start 来启动,只有删除容器才会清除数据
容器退出后,通过 docker ps 命令查看不到,数据会丢失么?
两者的定位完全不同 Vagrant 类似于 Boot2Docker(一款运行 Docker 的最小内核),是一套虚拟机的管 理环境,Vagrant 可以在多种系统上和虚拟机软件中运行,可以在 Windows。Mac 等非 Linux 平台上为 Docker 支持,自身具有较好的包装性和移植性。 原生 Docker 自身只能运行在 Linux 平台上,但启动和运行的性能都比虚拟机要快, 往往更适合快速开发和部署应用的场景。
Docker 与 Vagrant 有何不同?
停止 Docker 服务,将整个 docker 存储文件复制到另外一台宿主机上,然后调整 另外一台宿主机的配置即可
如何将一台宿主机的 docker 环境迁移到另外一台宿主机?
首先,仓库是存放一组关联镜像的集合,比如同一个应用的不同版本的镜像,注 册服务器是存放实际的镜像的地方,注册索引则负责维护用户的账号,权限,搜索, 标签等管理。注册服务器利用注册索引来实现认证等管理。
仓库(Repository)、注册服务器(Registry)、注册索引(Index)有何关系
docker
解耦:传统的软件开发模式,各个模块之间相互调用,数据共享,每个模块都要时刻关注其他模块的是否更改或者是否挂掉等等,使用消息队列,可以避免模块之间直接调用,将所需共享的数据放在消息队列中,对于新增业务模块,只要对该类消息感兴趣,即可订阅该类消息,对原有系统和业务没有任何影响,降低了系统各个模块的耦合度,提高了系统的可扩展性。
异步:消息队列提供了异步处理机制,在很多时候应用不想也不需要立即处理消息,允许应用把一些消息放入消息中间件中,并不立即处理它,在之后需要的时候再慢慢处理
削峰:在访问量骤增的场景下,需要保证应用系统的平稳性,但是这样突发流量并不常见,如果以这类峰值的标准而投放资源的话,那无疑是巨大的浪费。使用消息队列能够使关键组件支撑突发访问压力,不会因为突发的超负荷请求而完全崩溃。消息队列的容量可以配置的很大,如果采用磁盘存储消息,则几乎等于“无限”容量,这样一来,高峰期的消息可以被积压起来,在随后的时间内进行平滑的处理完成,而不至于让系统短时间内无法承载而导致崩溃。在电商网站的秒杀抢购这种突发性流量很强的业务场景中,消息队列的强大缓冲能力可以很好的起到削峰作用。
消息队列有很多使用场景,比较常见的有3个:解耦、异步、削峰。
系统可用性降低系统的复杂度提高了一致性问题
缺点:
MQ有什么用?
方案一:拆分多个队列,每个队列一个消费者方案二:一个队列对应一个消费者,然后这个消费者内部用内存队列做排队,然后分发给底层不同的worker来处理
消息队列如何保证顺序消费?
RabbitMQ丢失消息分为如下几种情况:生产者丢消息:生产者将数据发送到RabbitMQ的时候,可能在传输过程中因为网络等问题而将数据弄丢了。RabbitMQ自己丢消息:如果没有开启RabbitMQ的持久化,那么RabbitMQ一旦重启数据就丢了。所以必须开启持久化将消息持久化到磁盘,这样就算RabbitMQ挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢失。除非极其罕见的情况,RabbitMQ还没来得及持久化自己就挂了,这样可能导致一部分数据丢失。消费端丢消息:主要是因为消费者消费时,刚消费到还没有处理,结果消费者就挂了,这样你重启之后,RabbitMQ就认为你已经消费过了,然后就丢了数据。
针对上述三种情况,RabbitMQ可以采用如下方式避免消息丢失:生产者丢消息:可以选择使用RabbitMQ提供是事务功能,就是生产者在发送数据之前开启事务,然后发送消息,如果消息没有成功被RabbitMQ接收到,那么生产者会受到异常报错,这时就可以回滚事务,然后尝试重新发送。如果收到了消息,那么就可以提交事务。这种方式有明显的缺点,即RabbitMQ事务开启后,就会变为同步阻塞操作,生产者会阻塞等待是否发送成功,太耗性能会造成吞吐量的下降。可以开启confirm模式。在生产者那里设置开启了confirm模式之后,每次写的消息都会分配一个唯一的id,然后如何写入了RabbitMQ之中,RabbitMQ会给你回传一个ack消息,告诉你这个消息发送OK了。如果RabbitMQ没能处理这个消息,会回调你一个nack接口,告诉你这个消息失败了,你可以进行重试。而且你可以结合这个机制知道自己在内存里维护每个消息的id,如果超过一定时间还没接收到这个消息的回调,那么你可以进行重发。事务机制是同步的,你提交了一个事物之后会阻塞住,但是confirm机制是异步的,发送消息之后可以接着发送下一个消息,然后RabbitMQ会回调告知成功与否。 一般在生产者这块避免丢失,都是用confirm机制。RabbitMQ自己丢消息:设置消息持久化到磁盘,设置持久化有两个步骤:创建queue的时候将其设置为持久化的,这样就可以保证RabbitMQ持久化queue的元数据,但是不会持久化queue里面的数据。发送消息的时候讲消息的deliveryMode设置为2,这样消息就会被设为持久化方式,此时RabbitMQ就会将消息持久化到磁盘上。 必须要同时开启这两个才可以。而且持久化可以跟生产的confirm机制配合起来,只有消息持久化到了磁盘之后,才会通知生产者ack,这样就算是在持久化之前RabbitMQ挂了,数据丢了,生产者收不到ack回调也会进行消息重发。消费端丢消息:使用RabbitMQ提供的ack机制,首先关闭RabbitMQ的自动ack,然后每次在确保处理完这个消息之后,在代码里手动调用ack。这样就可以避免消息还没有处理完就ack。
RabbitMQ:
Kafka丢失消息分为如下几种情况:生产者丢消息:生产者没有设置相应的策略,发送过程中丢失数据。Kafka自己丢消息:比较常见的一个场景,就是Kafka的某个broker宕机了,然后重新选举partition的leader时。如果此时follower还没来得及同步数据,leader就挂了,然后某个follower成为了leader,它就少了一部分数据。消费端丢消息:消费者消费到了这个数据,然后消费之自动提交了offset,让Kafka知道你已经消费了这个消息,当你准备处理这个消息时,自己挂掉了,那么这条消息就丢了。
Kafka:
消息丢失可能发生在生产者发送消息、MQ本身丢失消息、消费者丢失消息3个方面。
消息队列如何保证消息不丢?
一个生成者对应一个消费者和一个队列
简单队列
工作队列
发布/订阅模式
路由模式
RabbitMQ有哪些工作模式?
交换机的类型:交换机主要包括如下4种类型:Direct exchange(直连交换机)Fanout exchange(扇型交换机)Topic exchange(主题交换机)Headers exchange(头交换机)Direct exchange(直连交换机)直连型交换机(direct exchange)是根据消息携带的路由键(routing key)将消息投递给对应队列的,步骤如下:将一个队列绑定到某个交换机上,同时赋予该绑定一个路由键(routing key)当一个携带着路由值为R的消息被发送给直连交换机时,交换机会把它路由给绑定值同样为R的队列。Fanout exchange(扇型交换机)扇型交换机(funout exchange)将消息路由给绑定到它身上的所有队列。不同于直连交换机,路由键在此类型上不启任务作用。如果N个队列绑定到某个扇型交换机上,当有消息发送给此扇型交换机时,交换机会将消息的发送给这所有的N个队列Topic exchange(主题交换机)主题交换机(topic exchanges)中,队列通过路由键绑定到交换机上,然后,交换机根据消息里的路由值,将消息路由给一个或多个绑定队列。扇型交换机和主题交换机异同:对于扇型交换机路由键是没有意义的,只要有消息,它都发送到它绑定的所有队列上对于主题交换机,路由规则由路由键决定,只有满足路由键的规则,消息才可以路由到对应的队列上Headers exchange(头交换机)类似主题交换机,但是头交换机使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。此交换机有个重要参数:”x-match”当”x-match”为“any”时,消息头的任意一个值被匹配就可以满足条件当”x-match”设置为“all”的时候,就需要消息头的所有值都匹配成功
RabbitMQ有哪几种交换机
RocketMq由于我们系统的qps压力比较大,所以性能是首要考虑的要素。开发语言,由于我们的开发语言是java,主要是为了方便二次开发。对于高并发的业务场景是必须的,所以需要支持分布式架构的设计。功能全面,由于不同的业务场景,可能会用到顺序消息、事务消息等。
那你们使用什么mq?基于什么做的选型?
所谓生产者-消费者问题,实际上主要是包含了两类线程。一种是生产者线程用于生产数据,另一种是消费者线程用于消费数据,为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库。生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为。而消费者只需要从共享数据区中去获取数据,就不再需要关心生产者的行为。但是,这个共享数据区域中应该具备这样的线程间并发协作的功能:1.如果共享数据区已满的话,阻塞生产者继续生产数据放置入内;2.如果共享数据区为空的话,阻塞消费者继续消费数据。在Java语言中,实现生产者消费者问题时,可以采用三种方式:1.使用 Object 的 wait/notify 的消息通知机制;2.使用 Lock 的 Condition 的 await/signal 的消息通知机制;3.使用 BlockingQueue 实现。
说一说生产者与消费者模式
因为考虑到时消费者消费⼀直出错的问题,那么我们可以从以下⼏个⻆度来考虑:1. 消费者出错,肯定是程序或者其他问题导致的,如果容易修复,先把问题修复,让consumer恢复正常消费2. 如果时间来不及处理很麻烦,做转发处理,写⼀个临时的consumer消费⽅案,先把消息消费,然后再转发到⼀个新的topic和MQ资源,这个新的topic的机器资源单独申请,要能承载住当前积压的消息3. 处理完积压数据后,修复consumer,去消费新的MQ和现有的MQ数据,新MQ消费完成后恢复原状
你说到消费者消费失败的问题,那么如果一直消费失败导致消息积压怎么处理?
最初,我们发送的消息记录是落库保存了的,⽽转发发送的数据也保存了,那么我们就可以通过这部分数据来找到丢失的那部分数据,再单独跑个脚本重发就可以了。如果转发的程序没有落库,那就和消费⽅的记录去做对⽐,只是过程会更艰难⼀点。
那如果消息积压达到磁盘上限,消息被删除了怎么办?
RocketMQ由NameServer注册中心集群、Producer生产者集群、Consumer消费者集群和若干Broker(RocketMQ进程)组成,它的架构原理是这样的:Broker在启动的时候去向所有的NameServer注册,并保持长连接,每30s发送一次心跳Producer在发送消息的时候从NameServer获取Broker服务器地址,根据负载均衡算法选择一台服务器来发送消息Conusmer消费消息的时候同样从NameServer获取Broker地址,然后主动拉取消息来消费
说了这么多,那你说说RocketMQ实现原理吧?
我认为有以下⼏个点是不使⽤zookeeper的原因:1. 根据CAP理论,同时最多只能满⾜两个点,⽽zookeeper满⾜的是CP,也就是说zookeeper并不能保证服务的可⽤性,zookeeper在进⾏选举的时候,整个选举的时间太⻓,期间整个集群都处于不可⽤的状态,⽽这对于⼀个注册中⼼来说肯定是不能接受的,作为服务发现来说就应该是为可⽤性⽽设计。2. 基于性能的考虑,NameServer本身的实现⾮常轻量,⽽且可以通过增加机器的⽅式⽔平扩展,增加集群的抗压能⼒,⽽zookeeper的写是不可扩展的,⽽zookeeper要解决这个问题只能通过划分领域,划分多个zookeeper集群来解决,⾸先操作起来太复杂,其次这样还是⼜违反了CAP中的A的设计,导致服务之间是不连通的。3. 持久化的机制来带的问题,ZooKeeper 的 ZAB 协议对每⼀个写请求,会在每个 ZooKeeper 节点上保持写⼀个事务⽇志,同时再加上定期的将内存数据镜像(Snapshot)到磁盘来保证数据的⼀致性和持久性,⽽对于⼀个简单的服务发现的场景来说,这其实没有太⼤的必要,这个实现⽅案太重了。⽽且本身存储的数据应该是⾼度定制化的。4. 消息发送应该弱依赖注册中⼼,⽽RocketMQ的设计理念也正是基于此,⽣产者在第⼀次发送消息的时候从NameServer获取到Broker地址后缓存到本地,如果NameServer整个集群不可⽤,短时间内对于⽣产者和消费者并不会产⽣太⼤影响。
为什么RocketMQ不使用Zookeeper作为注册中心呢?
消息在master和slave之间的同步是根据raft协议来进⾏的:1. 在broker收到消息后,会被标记为uncommitted状态2. 然后会把消息发送给所有的slave3. slave在收到消息之后返回ack响应给master4. master在收到超过半数的ack之后,把消息标记为committed5. 发送committed消息给所有slave,slave也修改状态为committed
Master和Slave之间是怎么同步数据的呢?
是因为使⽤了顺序存储、Page Cache和异步刷盘。1. 我们在写⼊commitlog的时候是顺序写⼊的,这样⽐随机写⼊的性能就会提⾼很多2. 写⼊commitlog的时候并不是直接写⼊磁盘,⽽是先写⼊操作系统的PageCache3. 最后由操作系统异步将缓存中的数据刷到磁盘
你知道RocketMQ为什么速度快吗?
事务消息就是MQ提供的类似XA的分布式事务能⼒,通过事务消息可以达到分布式事务的最终⼀致性。半事务消息就是MQ收到了⽣产者的消息,但是没有收到⼆次确认,不能投递的消息。实现原理如下:1. ⽣产者先发送⼀条半事务消息到MQ2. MQ收到消息后返回ack确认3. ⽣产者开始执⾏本地事务4. 如果事务执⾏成功发送commit到MQ,失败发送rollback5. 如果MQ⻓时间未收到⽣产者的⼆次确认commit或者rollback,MQ对⽣产者发起消息回查6. ⽣产者查询事务执⾏最终状态7. 根据查询事务状态再次提交⼆次确认最终,如果MQ收到⼆次确认commit,就可以把消息投递给消费者,反之如果是rollback,消息会保存下来并且在3天后被删除。
什么是事务、半事务消息?怎么实现的?
死信队列
MQ
数据量增大是模糊查询会放弃索引,效率低下。而es做全文索引提高查询速度。
为什么要使用ES
ES怎么做数据同步?
在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"query\": { \"match_all\": {} } }# \"query\":这里的 query 代表一个查询对象,里面可以有不同的查询属性# \"match_all\":查询类型,例如:match_all(代表查询所有), match,term , range 等等# {查询条件}:查询条件会根据类型的不同,写法也有差异
1.查询所有文档
match 匹配类型查询,会把查询条件进行分词,然后进行查询,多个词条之间是 or 的关系在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"query\": { \"match\": { \"name\":\"zhangsan\" } } }
2.匹配查询
multi_match 与 match 类似,不同的是它可以在多个字段中查询。在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"query\": { \"multi_match\": { \"query\": \"zhangsan\
3.字段匹配查询
term 查询,精确的关键词匹配查询,不对查询条件进行分词。在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"query\": { \"term\": { \"name\": { \"value\": \"zhangsan\" } } } }
4.关键字精确查询
terms 查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件,类似于 mysql 的 in在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"query\": { \"terms\": { \"name\": [\"zhangsan\
5.多关键字精确查询
默认情况下,Elasticsearch 在搜索的结果中,会把文档中保存在_source 的所有字段都返回。如果我们只想获取其中的部分字段,我们可以添加_source 的过滤在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"_source\": [\"name\
6.指定字段查询
includes:来指定想要显示的字段excludes:来指定不想要显示的字段在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"_source\": { \"includes\": [\"name\
7.过滤字段
`bool`把各种其它查询通过`must`(必须 )、`must_not`(必须不)、`should`(应该)的方式进行组合在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"query\": { \"bool\": { \"must\": [ { \"match\": { \"name\": \"zhangsan\
8.组合查询
range 查询找出那些落在指定区间内的数字或者时间。
9.范围查询
返回包含与搜索字词相似的字词的文档。编辑距离是将一个术语转换为另一个术语所需的一个字符更改的次数。这些更改可以包括: 更改字符(box → fox) 删除字符(black → lack) 插入字符(sic → sick) 转置两个相邻字符(act → cat)为了找到相似的术语,fuzzy 查询会在指定的编辑距离内创建一组搜索词的所有可能的变体或扩展。然后查询返回每个扩展的完全匹配。通过 fuzziness 修改编辑距离。一般使用默认值 AUTO,根据术语的长度生成编辑距离。
10.模糊查询
sort 可以让我们按照不同的字段进行排序,并且通过 order 指定排序的方式。desc 降序,asc升序。在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"query\": { \"match\": { \"name\":\"zhangsan\
11.单字段排序
假定我们想要结合使用 age 和 _score 进行查询,并且匹配的结果首先按照年龄排序,然后按照相关性得分排序在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"query\": { \"match_all\
12.多字段排序
在进行关键字搜索时,搜索出的内容中的关键字会显示不同的颜色,称之为高亮。
Elasticsearch 可以对查询内容中的关键字部分,进行标签和样式(高亮)的设置。在使用 match 查询的同时,加上一个 highlight 属性: pre_tags:前置标签 post_tags:后置标签 fields:需要高亮的字段 title:这里声明 title 字段需要高亮,后面可以为这个字段设置特有配置,也可以空在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"query\": { \"match\": { \"name\": \"zhangsan\
13.高亮查询
from:当前页的起始索引,默认从 0 开始。 from = (pageNum - 1) * sizesize:每页显示多少条在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search{ \"query\": { \"match_all\
14.分页查询
聚合允许使用者对 es 文档进行统计分析,类似与关系型数据库中的 group by,当然还有很多其他的聚合,例如取最大值、平均值等等。 对某个字段取最大值 max在 Postman 中,向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search
15.聚合查询
全文检索、匹配查询、精准查询、过滤查询、组合查询、范围查询、模糊查询、排序查询、高亮查询、分页查询、聚合查询
ES有哪些查询方式?
1.选主是ZenDiscovery模块负责,主要包含Ping和Unicast这两部分。2.对所有可以成为master的节点(node.master:true)根据nodeId字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个节点,暂且认为它是master节点。3.如果某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master.否则重新选举直到满足上述条件。4.mast节点的职责主要包括集群,节点和索引的管理,不负责文档级别的管理;data节点可以关闭http功能。
ES的master选举流程
ES集群脑裂问题
ES索引文档的流程
1.删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更;2.磁盘上的每个段都有一个相应的.del 文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del 文件中被标记为删除的文档将不会被写入新段。3.在新的文档被创建时,Elasticsearch 会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
ES跟新和删除文档的流程
1.搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch;2. 在初始查询阶段时,查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。 每个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。PS:在搜索的时候是会查询Filesystem Cache 的,但是有部分数据还在 Memory Buffer,所以搜索是近实时的。3.每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。 接下来就是取回阶段,协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并丰富文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了,协调节点返回结果给客户端。4.Query Then Fetch 的搜索类型在文档相关性打分的时候参考的是本分片的数据,这样在文档数量较少的时候可能不够准确,DFS Query Then Fetch 增加了一个预查询的处理,询问 Term 和 Document frequency,这个评分更准确,但是性能会变差。
ES搜索的流程
ES在部署时,对Linux的设置有哪些优化方法
GC方面,在使用ES时要注意什么?
Elasticsearch 提供的首个近似聚合是 cardinality 度量。它提供一个字段的基数,即该字段的 distinct或者 unique 值的数目。它是基于 HLL 算法的。HLL 会先对我们的输入作哈希运算,然后根据哈希运算的结果中的 bits 做概率估算从而得到基数。其特点是:可配置的精度,用来控制内存的使用(更精确 = 更多内存);小的数据集精度是非常高的;我们可以通过配置参数,来设置去重需要的固定内存使用量。无论数千还是数十亿的唯一值,内存使用量只与你配置的精确度相关
ES对大数据量(上亿量级)的聚合如何实现?
可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突; 另外对于写操作,一致性级别支持 quorum/one/all,默认为 quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。 对于读操作,可以设置 replication 为 sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置 replication 为 async 时,也可以通过设置搜索请求参数_preference 为 primary 来查询主分片,确保文档是最新版本。
在并发情况下,ES如何保证读写一致?
elasticsearch-head 插件通过 Kibana 监控 Elasticsearch。你可以实时查看你的集群健康状态和性能,也可以分析过去的集群、索引和节点指标
如何监控ES集群状态?
字典树又称单词查找树,Trie 树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。 Trie 的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。它有 3 个基本性质: 根节点不包含字符,除根节点外每一个节点都只包含一个字符。 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。 每个节点的所有子节点包含的字符都不相同。对于中文的字典树,每个节点的子节点用一个哈希表存储,这样就不用浪费太大的空间,而且查询速度上可以保留哈希的复杂度 O(1)。
是否了解字典树?
1.集群是一个或多个节点(服务器)的集合,它们共同保存您的整个数据,并提供跨所有节点的联合索引和搜索功能。群集由唯一名称标识,默认情况下为“elasticsearch”。此名称很重要,因为如果节点设置为按名称加入群集,则该节点只能是群集的一部分。 2.节点是属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能。 3.索引就像关系数据库中的“数据库”。它有一个定义多种类型的映射。索引是逻辑名称空间,映射到一个或多个主分片,并且可以有零个或多个副本分片。 MySQL =>数据库 Elasticsearch =>索引 4.文档类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构(字段),但是对于通用字段应该具有相同的数据类型。 MySQL => Databases => Tables => Columns / Rows Elasticsearch => Indices => Types =>具有属性的文档 类型是索引的逻辑类别/分区,其语义完全取决于用户。
ES中的集群,节点,索引,文档,类型是什么?
倒排索引是搜索引擎的核心。搜索引擎的主要目标是在查找发生搜索条件的文档时提供快速搜索。ES中的倒排索引其实就是 lucene 的倒排索引,区别于传统的正向索引,倒排索引会再存储数据时将关键词和数据进行关联,保存到倒排表中,然后查询时,将查询内容进行分词后在倒排表中进行查询,最后匹配数据即可。
ES中的倒排索引是什么?
集群是一个或多个节点(服务器)的集合,它们共同保存您的整个数据,并提供跨所有节点的联合索引和搜索功能。群集由唯一名称标识,默认情况下为“elasticsearch”。此名称很重要,因为如果节点设置为按名称加入群集,则该节点只能是群集的一部分。
节点是属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能。
索引就像关系数据库中的“数据库”。它有一个定义多种类型的映射。索引是逻辑名称空间,映射到一个或多个主分片,并且可以有零个或多个副本分片。 MySQL =>数据库 Elasticsearch =>索引
文档类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构(字段),但是对于通用字段应该具有相同的数据类型。 MySQL => Databases => Tables => Columns / Rows Elasticsearch => Indices => Types =>具有属性的文档
类型是索引的逻辑类别/分区,其语义完全取决于用户。
Elasticsearch 中的集群、节点、索引、文档、类型是什么?
ElasticSearch
redis: redis 即 Remote Dictionary Server,用中文翻译过来可以理解为远程数据服务或远程字典服务。其是使用 C 语言的编写的key-value存储系统应用场景:缓存,数据库,消息队列,分布式锁,点赞列表,排行榜等等
什么是 redis?它能做什么?
字符串:redis没有直接使⽤C语⾔传统的字符串表示,⽽是⾃⼰实现的叫做简单动态字符串SDS的抽象类型。C语⾔的字符串不记录⾃身的⻓度信息,⽽SDS则保存了⻓度信息,这样将获取字符串⻓度的时间由O(N)降低到了O(1),同时可以避免缓冲区溢出和减少修改字符串⻓度时所需的内存重分配次数。
链表linkedlist:redis链表是⼀个双向⽆环链表结构,很多发布订阅、慢查询、监视器功能都是使⽤到了链表来实现,每个链表的节点由⼀个listNode结构来表示,每个节点都有指向前置节点和后置节点的指针,同时表头节点的前置和后置节点都指向NULL。
字典hashtable:⽤于保存键值对的抽象数据结构。redis使⽤hash表作为底层实现,每个字典带有两个hash表,供平时使⽤和rehash时使⽤,hash表使⽤链地址法来解决键冲突,被分配到同⼀个索引位置的多个键值对会形成⼀个单向链表,在对hash表进⾏扩容或者缩容的时候,为了服务的可⽤性,rehash的过程不是⼀次性完成的,⽽是渐进式的。
跳跃表skiplist:跳跃表是有序集合的底层实现之⼀,redis中在实现有序集合键和集群节点的内部结构中都是⽤到了跳跃表。redis跳跃表由zskiplist和zskiplistNode组成,zskiplist⽤于保存跳跃表信息(表头、表尾节点、⻓度等),zskiplistNode⽤于表示表跳跃节点,每个跳跃表的层⾼都是1-32的随机数,在同⼀个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是唯⼀的,节点按照分值⼤⼩排序,如果分值相同,则按照成员对象的⼤⼩排序。
整数集合intset:⽤于保存整数值的集合抽象数据结构,不会出现重复元素,底层实现为数组。
压缩列表ziplist:压缩列表是为节约内存⽽开发的顺序性数据结构,他可以包含多个节点,每个节点可以保存⼀个字节数组或者整数值。
基于这些基础的数据结构,redis封装了⾃⼰的对象系统,包含字符串对象string、列表对象list、哈希对象hash、集合对象set、有序集合对象zset,每种对象都⽤到了⾄少⼀种基础的数据结构。
redis通过encoding属性设置对象的编码形式来提升灵活性和效率,基于不同的场景redis会⾃动做出优化。不同对象的编码如下:1. 字符串对象string:int整数、embstr编码的简单动态字符串、raw简单动态字符串2. 列表对象list:ziplist、linkedlist3. 哈希对象hash:ziplist、hashtable4. 集合对象set:intset、hashtable5. 有序集合对象zset:ziplist、skiplist
redis 总共有八种数据结构,五种基本数据类型和三种特殊数据类型。
五种基本数据类型:1.string:字符串类型,常被用来存储计数器,粉丝数等,简单的分布式锁也会用到该类型2.hashmap:key - value 形式的,value 是一个map3.list:基本的数据类型,列表。在 Redis 中可以把 list 用作栈、队列、阻塞队列。4.set:集合,不能有重复元素,可以做点赞,收藏等5.zset:有序集合,不能有重复元素,有序集合中的每个元素都需要指定一个分数,根据分数对元素进行升序排序。可以做排行榜
三种特殊数据类型:1.geospatial: Redis 在 3.2 推出 Geo 类型,该功能可以推算出地理位置信息,两地之间的距离。2.hyperloglog:基数:数学上集合的元素个数,是不能重复的。这个数据结构常用于统计网站的 UV。3.bitmap: bitmap 就是通过最小的单位 bit 来进行0或者1的设置,表示某个元素对应的值或者状态。一个 bit 的值,或者是0,或者是1;也就是说一个 bit 能存储的最多信息是2。bitmap 常用于统计用户信息比如活跃粉丝和不活跃粉丝、登录和未登录、是否打卡等。
redis 有哪八种数据类型?有哪些应用场景?
这是IO模型的一种,即经典的Reactor设计模式,I/O 多路复用,简单来说就是通过监测文件的读写事件再通知线程执行相关操作,保证 Redis 的非阻塞 I/O 能够顺利执行完成的机制。多路指的是多个socket连接,复用指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。 epoll是最新的也是目前最好的多路复用技术。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。
1:完全基于内存操作2:使用单线程模型来处理客户端的请求,避免了上下文的切换3:IO 多路复用机制4:自身使用 C 语言编写,有很多优化机制,比如全局哈希表,动态字符串 sds
redis为什么这么快?
Redis 内部使用文件事件处理器 file event handler ,这个文件事件处理器是单线程的,所以Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket ,根据 socket 上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:1. 多个 socket 。 2. IO 多路复用程序。3. 文件事件分派器。4. 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)。
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
Redis的线程模型?
Redis 6.0版本之前的单线程指的是其网络I/O和键值对读写是由一个线程完成的。
Redis6.0引入的多线程指的是网络请求过程采用了多线程,而键值对读写命令仍然是单线程 处理的,所以Redis依然是并发安全的。
redis到底是单线程还是多线程?
不会其实 redis 还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程,所以是不会有线程安全的问题。之所以加入了多线程因为 redis 的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高redis的性能。
听说 redis 6.0之后又使用了多线程,不会有线程安全的问题吗?
redis持久化⽅案分为RDB和AOF两种。
AOF重写:1 # auto‐aof‐rewrite‐min‐size 64mb //aof文件至少要达到64M才会自动重写,文件太 小恢复速度本来就很快,重写的意义不大 2 # auto‐aof‐rewrite‐percentage 100 //aof文件自上一次重写后文件大小增长了100% 则再次触发重写
AOF和RDB不同,AOF是通过保存redis服务器所执⾏的写命令来记录数据库状态的。AOF通过追加、写⼊、同步三个步骤来实现持久化机制。1. 当AOF持久化处于激活状态,服务器执⾏完写命令之后,写命令将会被追加append到aof_buf缓冲区的末尾2. 在服务器每结束⼀个事件循环之前,将会调⽤flushAppendOnlyFile函数决定是否要将aof_buf的内容保存到AOF⽂件中,可以通过配置appendfsync来决定。如果不设置,默认选项将会是everysec,因为always来说虽然最安全(只会丢失⼀次事件循环的写命令),但是性能较差,⽽everysec模式只不过会可能丢失1秒钟的数据,⽽no模式的效率和everysec相仿,但是会丢失上次同步AOF⽂件之后的所有写命令数据。
RDB持久化可以⼿动执⾏也可以根据配置定期执⾏,它的作⽤是将某个时间点上的数据库状态保存到RDB⽂件中,RDB⽂件是⼀个压缩的⼆进制⽂件,通过它可以还原某个时刻数据库的状态。由于RDB⽂件是保存在硬盘上的,所以即使redis崩溃或者退出,只要RDB⽂件存在,就可以⽤它来恢复还原数据库的状态。可以通过SAVE或者BGSAVE来⽣成RDB⽂件。SAVE命令会阻塞redis进程,直到RDB⽂件⽣成完毕,在进程阻塞期间,redis不能处理任何命令请求,这显然是不合适的。BGSAVE则是会fork出⼀个⼦进程,然后由⼦进程去负责⽣成RDB⽂件,⽗进程还可以继续处理命令请求,不会阻塞进程。
redis 如何做持久化
所谓热key问题就是,突然有⼏⼗万的请求去访问redis上的某个特定key,那么这样会造成流量过于集中,达到物理⽹卡上限,从⽽导致这台redis的服务器宕机引发雪崩。
解决方案:可以将结果缓存到本地内存中将热 key 分散到不同的服务器中设置永不过期
知道什么时热key吗?热 key 问题怎么解决?
缓存击穿:就是单个key并发访问过⾼,过期时导致所有请求直接打到db上,这个和热key的问题⽐较类似,只是说的点在于过期导致请求全部打到DB上⽽已。
1. 加锁更新,⽐如请求查询A,发现缓存中没有,对A这个key加锁,同时去数据库查询数据,写⼊缓存,再返回给⽤户,这样后⾯的请求就可以从缓存中拿到数据了。(分布式锁/sychronized单机锁)2. 永不过期
缓存穿透:缓存穿透是指用户请求的数据在缓存中不存在并且在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍,然后返回空。
1.加锁(类似击穿)2.返回空对象
缓存雪崩:当某⼀时刻发⽣⼤规模的缓存失效的情况,⽐如你的缓存服务宕机了,会有⼤量的请求进来直接打到DB上,这样可能导致整个系统的崩溃,称为雪崩。雪崩和击穿、热key的问题不太⼀样的是,他是指⼤规模的缓存都过期失效了。
1. 针对不同key设置不同的过期时间,避免同时过期2. 限流,如果redis宕机,可以限流,避免同时刻⼤量请求打崩DB3. ⼆级缓存,同热key的⽅案。
缓存击穿、缓存穿透、缓存雪崩是什么?怎么解决呢?
Redis Cluster 将所有数据划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。 槽位的信息存储于每个节点中。 当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存 在客户端本地。这样当客户端要查找某个 key 时,可以根据槽位定位算法定位到目标节 点
槽位定位算法:Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。 HASH_SLOT = CRC16(key) mod 16384 再根据槽位值和Redis节点的对应关系就可以定位到key具体是落在哪个Redis节点上的。
Redis集群数据hash分片算法是怎么回事?
真实世界的机房网络往往并不是风平浪静的,它们经常会发生各种各样的小问题。比如网络 抖动就是非常常见的一种现象,突然之间部分连接变得不可访问,然后很快又恢复正常。 为解决这种问题,Redis Cluster 提供了一种选项clusternodetimeout,表示当某个 节点持续 timeout 的时间失联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换 (数据的重新复制)
Redis集群网络抖动导致频繁主从切换怎么处理?
因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个 master节点,当其中一个挂了,是达不到选举新master的条件的。
Redis集群为什么至少需要三个master节点?
因为新master的选举需要大于半数的集群master节点同意才能选举成功,奇数个master节 点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点 的集群相比,大家如果都挂了一个master节点都能选举新master节点,如果都挂了两个 master节点都没法选举新master节点了,所以奇数的master节点更多的是从节省机器资源 角度出发说的。
Redis集群为什么推荐奇数个节点?
对于类似mset,mget这样的多个key的原生批量操作命令,redis集群只支持所有key落在 同一slot的情况,如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前 面加上{XXX},这样参数数据分片hash计算的只会是大括号里的值,这样能确保不同的key 能落到同一slot里去,示例如下: 1 mset {user1}:1:name zhuge {user1}:1:age 18 假设name和age计算的hash slot值不一样,但是这条命令在集群下执行,redis只会用大括 号里的 user1 做hash slot计算,所以算出来的slot值肯定相同,最后都能落在同一slot。
Redis集群支持批量操作命令吗?
Redis官方规定Lua脚本如果想在Redis集群里执行,需要Lua脚本里操作的所有Redis Key 落在集群的同一个节点上,这种的话我们可以给Lua脚本的Key前面加一个相同的hash tag,就是{XXX},这样就能保证Lua脚本里所有Key落在相同的节点上了。
Lua脚本能在Redis集群里执行吗?
1.当一个从数据库启动时,它会向主数据库发送一个SYNC命令,master收到后,在后台保存快照,也就是我们说的RDB持久化,当然保存快照是需要消耗时间的,并且redis是单线程的,在保存快照期间redis收到的命令会缓存起来2.快照完成后会将缓存的命令以及快照一起打包发给slave节点,从而保证主从数据库的一致性。3.从数据库接受到快照以及缓存的命令后会将这部分数据写入到硬盘上的临时文件当中,写入完成后会用这份文件去替换掉RDB快照文件,当然,这个操作是不会阻塞的,可以继续接收命令执行,具体原因其实就是fork了一个子进程,用子进程去完成了这些功能。
因为不会阻塞,所以,这部分初始化完成后,当主数据库执行了改变数据的命令后,会异步的给slave,这也就是我们说的复制同步阶段,这个阶段会贯穿在整个主从同步的过程中,直到主从同步结束后,复制同步才会终止。
主从同步原理是怎样的?
如果Redis主节点有很多从节点,在某一时刻如果所有从节点都同时连接主节点,那么主节 点会同时把内存快照RDB发给多个从节点,这样会导致Redis主节点压力非常大,这就是所 谓的Redis主从复制风暴问题。
Redis主从复制风暴是怎么回事?
Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件;如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次;为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内;尽量避免在压力很大的主库上增加从库;主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <-Slave3….;这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。
Redis 常见性能问题和解决方案有哪些?
如何保证Redis和数据库数据一致性?
总览:
len,记录了字符串长度。这样获取字符串长度的时候,只需要返回这个成员变量值就行,时间复杂度只需要 O(1)。
alloc,分配给字符数组的空间长度。这样在修改字符串的时候,可以通过 alloc - len 计算出剩余的空间大小,可以用来判断空间是否满足修改需求,如果不满足的话,就会自动将 SDS 的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说的缓冲区溢出的问题。
flags,用来表示不同类型的 SDS。一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64,后面在说明区别之处。
buf[],字符数组,用来保存实际数据。不仅可以保存字符串,也可以保存二进制数据。
O(1)复杂度获取字符串长度
因为 SDS 不需要用 “\\0” 字符来标识字符串结尾了,而是有个专门的 len 成员变量来记录长度,所以可存储包含 “\\0” 的数据。但是 SDS 为了兼容部分 C 语言标准库的函数, SDS 字符串结尾还是会加上 “\\0” 字符。因此, SDS 的 API 都是以处理二进制的方式来处理 SDS 存放在 buf[] 里的数据,程序不会对其中的数据做任何限制,数据写入的时候时什么样的,它被读取时就是什么样的。通过使用二进制安全的 SDS,而不是 C 字符串,使得 Redis 不仅可以保存文本数据,也可以保存任意格式的二进制数据。
二进制安全
C 语言的字符串标准库提供的字符串操作函数,大多数(比如 strcat 追加字符串函数)都是不安全的,因为这些函数把缓冲区大小是否满足操作需求的工作交由开发者来保证,程序内部并不会判断缓冲区大小是否足够用,当发生了缓冲区溢出就有可能造成程序异常结束。所以,Redis 的 SDS 结构里引入了 alloc 和 len 成员变量,这样 SDS API 通过 alloc - len 计算,可以算出剩余可用的空间大小,这样在对字符串做修改操作的时候,就可以由程序内部判断缓冲区大小是否足够用。而且,当判断出缓冲区大小不够用时,Redis 会自动将扩大 SDS 的空间大小(小于 1MB 翻倍扩容,大于 1MB 按 1MB 扩容),以满足修改所需的大小。在扩展 SDS 空间之前,SDS API 会优先检查未使用空间是否足够,如果不够的话,API 不仅会为 SDS 分配修改所必须要的空间,还会给 SDS 分配额外的「未使用空间」。这样的好处是,下次在操作 SDS 时,如果 SDS 空间够的话,API 就会直接使用「未使用空间」,而无须执行内存分配,有效的减少内存分配次数。所以,使用 SDS 即不需要手动修改 SDS 的空间大小,也不会出现缓冲区溢出的问题。#节省内存空间
不会发生缓冲区溢出
优点:
SDS(简单动态字符串)
listNode 链表节点的结构里带有 prev 和 next 指针,获取某个节点的前置节点或后置节点的时间复杂度只需O(1),而且这两个指针都可以指向 NULL,所以链表是无环链表;list 结构因为提供了表头指针 head 和表尾节点 tail,所以获取链表的表头节点和表尾节点的时间复杂度只需O(1);list 结构因为提供了链表节点数量 len,所以获取链表中的节点数量的时间复杂度只需O(1);listNode 链表节使用 void* 指针保存节点值,并且可以通过 list 结构的 dup、free、match 函数指针为节点设置该节点类型特定的函数,因此链表节点可以保存各种不同类型的值;
链表每个节点之间的内存都是不连续的,意味着无法很好利用 CPU 缓存。能很好利用 CPU 缓存的数据结构就是数组,因为数组的内存是连续的,这样就可以充分利用 CPU 缓存来加速访问。还有一点,保存一个链表节点的值都需要一个链表节点结构头的分配,内存开销较大。
双向链表
未完。。。
redis的数据结构?
redis是线程安全的吗?
跳表:将有序链表改造为支持近似“折半查找”算法,可以进行快速的插入、删除、查找操 作。
Redis底层数据是如何用跳表来存储的?
Redis持久化RDB、AOF、混合持久化是怎么回事?
定时删除:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性删除:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期删除:默认每隔100ms,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
Redis的过期键的删除策略有哪些?
实际上Redis定义了「8种内存淘汰策略」用来处理redis内存满的情况:1.noeviction:直接返回错误,不淘汰任何已经存在的redis键2.allkeys-lru:所有的键使用lru算法进行淘汰3.volatile-lru:有过期时间的使用lru算法进行淘汰4.allkeys-random:随机删除redis键5.volatile-random:随机删除有过期时间的redis键6.volatile-ttl:删除快过期的redis键7.volatile-lfu:根据lfu算法从有过期时间的键删除8.allkeys-lfu:根据lfu算法从所有键删除
Redis的内存满了怎么办?
LRU 算法(Least Recently Used,最近最少使用):淘汰很久没被访问过的数据,以最近一 次访问时间作为参考。 LFU 算法(Least Frequently Used,最不经常使用):淘汰最近一段时间被访问次数最少的 数据,以次数作为参考。 绝大多数情况我们都可以用LRU策略,当存在大量的热点缓存数据时,LFU可能更好点。
Redis淘汰Key的算法LRU与LFU区别?
有可能的,我们看下DEL Key命令的时间复杂度:删除单个字符串类型的 key ,时间复杂度为 O(1)。 删除单个列表、集合、有序集合或哈希表类型的 key ,时间复杂度为 O(M), M 为以上数据结构内的元素数量。
如果删除的是列表、集合、有序集合或哈希表类型的 key,如果集合元素过多,是会阻塞 Redis的。对于这种情况我们可以借助scan这样的命令循环删除元素。
如果删除的是字符串类型的 key,但是key对应value比较大,比如有几百M,那么也是会 阻塞Redis的。这种bigkey是我们要尽量减少出现的情况。
删除Key的命令会阻塞Redis吗?
主从复制:在主从复制这种集群部署模式中,我们会将数据库分为两类,第一种称为主数据库(master),另一种称为从数据库(slave)。主数据库会负责我们整个系统中的读写操作,从数据库会负责我们整个数据库中的读操作。其中在职场开发中的真实情况是,我们会让主数据库只负责写操作,让从数据库只负责读操作,就是为了读写分离,减轻服务器的压力。
哨兵模式:哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。它具备自动故障转移、集群监控、消息通知等功能。
cluster集群模式:在redis3.0版本中支持了cluster集群部署的方式,这种集群部署的方式能自动将数据进行分片,每个master上放一部分数据,提供了内置的高可用服务,即使某个master挂了,服务还可以正常地提供。
Redis 有哪些部署方式?
哨兵可以同时监视多个主从服务器,并且在被监视的master下线时,⾃动将某个slave提升为master,然后由新的master继续接收命令
整个过程如下:1. 初始化sentinel,将普通的redis代码替换成sentinel专⽤代码2. 初始化masters字典和服务器信息,服务器信息主要保存ip:port,并记录实例的地址和ID3. 创建和master的两个连接,命令连接和订阅连接,并且订阅sentinel:hello频道4. 每隔10秒向master发送info命令,获取master和它下⾯所有slave的当前信息5. 当发现master有新的slave之后,sentinel和新的slave同样建⽴两个连接,同时每个10秒发送info命令,更新master信息6. sentinel每隔1秒向所有服务器发送ping命令,如果某台服务器在配置的响应时间内连续返回⽆效回复,将会被标记为下线状态7. 选举出领头sentinel,领头sentinel需要半数以上的sentinel同意8. 领头sentinel从已下线的的master所有slave中挑选⼀个,将其转换为master9. 让所有的slave改为从新的master复制数据10. 将原来的master设置为新的master的从服务器,当原来master重新回复连接时,就变成了新master的从服务器sentinel会每隔1秒向所有实例(包括主从服务器和其他sentinel)发送ping命令,并且根据回复判断是否已经下线,这种⽅式叫做主观下线。当判断为主观下线时,就会向其他监视的sentinel询问,如果超过半数的投票认为已经是下线状态,则会标记为客观下线状态,同时触发故障转移。
哨兵的作用和选举过程是怎么样的?
一个cluster集群中总共有16384个节点,集群会将这16384个节点平均分配给每个节点
cluster集群模式是怎么存放数据的?
前面的面试题讲过Redis对于过期Key的清理策略是定时删除与惰性删除两种方式结合来做 的,而 RANDOMKEY 在随机拿出一个 key 后,首先会先检查这个 key 是否已过期,如果 该 key 已经过期,那么 Redis 会删除它,这个过程就是惰性删除。但清理完了还不能结 束,Redis 还要找出一个没过期的 key,返回给客户端。 此时,Redis 则会继续随机拿出一个 key,然后再判断它是否过期,直到找出一个没过期的 key 返回给客户端。 这里就有一个问题了,如果此时 Redis 中,有大量 key 已经过期,但还未来得及被清理 掉,那这个循环就会持续很久才能结束,而且,这个耗时都花费在了清理过期 key 以及寻 找不过期 key 上,导致的结果就是,RANDOMKEY 执行耗时变长,影响 Redis 性能。以上流程,其实是在 master 上执行的。 如果在 slave 上执行 RANDOMEKY,那么问题会更严重。 slave 自己是不会清理过期 key,当一个 key 要过期时,master 会先清理删除它,之后 master 向 slave 发送一个 DEL 命令,告知 slave 也删除这个 key,以此达到主从库的数据一致性。 假设Redis 中存在大量已过期还未被清理的 key,那在 slave 上执行 RANDOMKEY 时,就 会发生以下问题: 1、slave 随机取出一个 key,判断是否已过期。 2、key 已过期,但 slave 不会删除它,而是继续随机寻找不过期的 key。 3、由于大量 key 都已过期,那 slave 就会寻找不到符合条件的 key,此时就会陷入死循 环。也就是说,在 slave 上执行 RANDOMKEY,有可能会造成整个 Redis 实例卡死。 这其实是 Redis 的一个 Bug,这个 Bug 一直持续到 5.0 才被修复,修复的解决方案就是在 slave中最多找一定的次数,无论是否能找到,都会退出循环。
如果你想随机查看 Redis 中的一个 key,Redis里有一个 RANDOMKEY 命令可以从 Redis 中随机取出一个 key,这个命令可能导致Redis死循环阻塞。
Redis执行命令竟然有死循环阻塞Bug?
我们假设,slave 的机器时钟比 master 走得快很多。 此时,Redis master里设置了过期时间的key,从 slave 角度来看,可能会有很多在 master 里没过期的数据其实已经过期了。 如果此时操作主从切换,把 slave 提升为新的 master。 它成为 master 后,就会开始大量清理过期 key,此时就会导致以下结果: 1. master 大量清理过期 key,主线程可能会发生阻塞,无法及时处理客户端请 求。2. Redis 中数据大量过期,引发缓存雪崩。 当 master与slave 机器时钟严重不一致时,对业务的影响非常大。 所以,我们一定要保证主从库的机器时钟一致性,避免发生这些问题。
一次线上事故,Redis主从切换导致了缓存雪崩
判断故障的逻辑其实与哨兵模式有点类似,在集群中,每个节点都会定期的向其他节点发送ping命令,通过有没有收到回复来判断其他节点是否已经下线。
如果长时间没有回复,那么发起ping命令的节点就会认为目标节点疑似下线,也可以和哨兵一样称作主观下线,当然也需要集群中一定数量的节点都认为该节点下线才可以
1.当A节点发现目标节点疑似下线,就会向集群中的其他节点散播消息,其他节点就会向目标节点发送命令,判断目标节点是否下线2.如果集群中半数以上的节点都认为目标节点下线,就会对目标节点标记为下线,从而告诉其他节点,让目标节点在整个集群中都下线
cluster的故障恢复是怎么做的?
1.master禁用了RDB快照时,发生了主从同步(复制初始化)操作,也会生成RDB快照,但是之后如果master发成了重启,就会用RDB快照去恢复数据,这份数据可能已经很久了,中间就会丢失数据2.在这种一主多从的结构中,master每次和slave同步数据都要进行一次快照,从而在硬盘中生成RDB文件,会影响性能
为了解决这种问题,redis在后续的更新中也加入了无硬盘复制功能,也就是说直接通过网络发送给slave,避免了和硬盘交互,但是也是有io消耗
无硬盘复制是什么?
redis
SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至篡改数据库。 Sql 注入攻击是通过将恶意的 Sql 查询或添加语句插入到应用的输入参数中,再在后台 Sql 服务器上解析执行进行的攻击,它目前黑客对数据库进行攻击的最常用手段之一
SQL注入
sql 注入产生原因及威胁
数据库信息泄漏:数据库中存放的用户的隐私信息的泄露。网页篡改:通过操作数据库对特定网页进行篡改。网站被挂马,传播恶意软件:修改数据库一些字段的值,嵌入网马链接,进行挂马攻击。数据库被恶意操作:数据库服务器被攻击,数据库的系统管理员帐户被窜改。服务器被远程控制,被安装后门。经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统。破坏硬盘数据,瘫痪全系统。
SQL注入漏洞对于数据安全的影响
安全问题
1.普通索引的 B+ 树上存放的是主键索引的值,如果该值较大,会「导致普通索引的存储空间较大」
2.使用自增 id 做主键索引新插入数据只要放在该页的最尾端就可以,直接「按照顺序插入」,不用刻意维护
3.页分裂容易维护,当插入数据的当前页快满时,会发生页分裂的现象,如果主键索引不为自增 id,那么数据就可能从页的中间插入,页的数据会频繁的变动,「导致页分裂维护成本较高」
为什么推荐使用自增 id 作为主键?
索引是一种帮助快速查找数据的数据结构,可以把它理解为书的目录,通过索引能够快速找到数据所在位置。
索引数据结构有:Hash表(通过hash算法快速定位数据,但不适合范围查询,因为需要每个key都进行一次hash)、二叉树(查找和修改效率都比较高),但是在InnoDB引擎中使用的索引是B+Tree,相较于二叉树,B+Tree这种多叉树,更加矮宽,更适合存储在磁盘中。使用索引增加了数据查找的效率,但是相对的由于索引也需要存储到磁盘,所以增加了存储的压力,并且新增数据时需要同步维护索引。但是合理的使用索引能够极大提高我们的效率!
什么是索引?以及好处和坏处
「InnoDB支持事务,MyISAM不支持」。
「InnoDB 支持外键,而 MyISAM 不支持」。
「InnoDB是聚集索引」,使用B+Tree作为索引结构,数据文件是和索引绑在一起的,必须要有主键。「MyISAM是非聚集索引」,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
「InnoDB 不保存表的具体行数」。「MyISAM 用一个变量保存了整个表的行数」。
Innodb 有 「redolog」 日志文件,MyISAM 没有
「InnoDB 支持表、行锁,而 MyISAM 支持表级锁」
「Innodb存储文件有frm、ibd,而Myisam是frm、MYD、MYI」Innodb:frm是表定义文件,ibd是数据文件Myisam:frm是表定义文件,myd是数据文件,myi是索引文件
MyISAM 与 InnoDB 的区别是什么?
聚集索引:指索引项的排序方式和表中数据记录排序方式一致的索引。聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。术语“聚簇”表示数据行和相邻的键值紧凑的存储在一起。也就是说聚集索引的顺序就是数据的物理存储顺序。它会根据聚集索引键的顺序来存储表中的数据,即对表的数据按索引键的顺序进行排序,然后重新存储到磁盘上。因为数据在物理存放时只能有一种排列方式,所以一个表只能有一个聚集索引。聚集索引的使用场合为: a.查询命令的回传结果是以该字段为排序依据的; b.查询的结果返回一个区间的值; c.查询的结果返回某值相同的大量结果集。
非聚集索引: 索引顺序与物理存储顺序不同非聚集索引的使用场合为: a.查询所获数据量较少时; b.某字段中的数据的唯一性比较高时;
稠密索引:每个索引键值都对应有一个索引项稠密索引的优点是:它所占空间更小,且插入和删除时的维护开销也小。
mysql中有哪些索引以及使用场景?
主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。全文索引:是目前搜索引擎使用的一种关键技术,对文本的内容进行分词、搜索。覆盖索引:查询列要被所建的索引覆盖,不必读取数据行组合索引:多列值组成一个索引,用于组合搜索,效率大于索引合并
索引有哪几种类型?
业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引不要以为唯一索引影响了insert的速度,这个速度损耗可以忽略,但提高查询速度是明显的频繁作为where查询条件的字段添加index普通索引font color=\"#f44336\
适合添加索引的情况
联合索引非最左匹配
对索引使用函数/表达式计算
不能继续使用索引中范围条件(bettween、<、>、in等)右边的列
索引字段上使用(!= 或者 < >)判断时,会导致索引失效而转向全表扫描
索引字段上使用 is null / is not null 判断时,会导致索引失效而转向全表扫描。
索引字段使用like以通配符开头(‘%字符串’)时,会导致索引失效而转向全表扫描,也是最左前缀原则。
索引字段是字符串,但查询时不加单引号,会导致索引失效而转向全表扫描
索引字段使用 or 时,会导致索引失效而转向全表扫描
索引失效的场景有哪些?
按「数据结构」分类:B+tree索引、Hash索引、Full-text索引。按「物理存储」分类:聚集索引(主键索引)、二级索引(辅助索引)。按「字段特性」分类:主键索引、唯一索引、普通索引、前缀索引。按「字段个数」分类:单列索引、联合索引。
索引的分类?
最左前缀匹配原则频繁作为查询条件的字段才去创建索引频繁更新的字段不适合创建索引索引列不能参与计算,不能有函数操作优先考虑扩展索引,而不是新建索引,避免不必要的索引在order by或者group by子句中,创建索引需要注意顺序区分度低的数据列不适合做索引列(如性别)定义有外键的数据列一定要建立索引。对于定义为text、image数据类型的列不要建立索引。删除不再使用或者很少使用的索引
创建索引有什么原则呢?
1、在执行create table时创建索引2、使用alter table命令添加索引3、使用create index命令创建
创建索引的三种方式
最左前缀原则,就是最左优先,在创建多列索引时,要根据业务需求,font color=\"#f44336\
什么是最左前缀原则?什么是最左匹配原则?
font color=\"#f44336\
什么情况下不合适场景索引?
count(*)=count(1)>count(主键)>count(字段)
count(*)和count(1)有什么区别?
原子性:列或者字段不能再分,要求属性具有原子性,不可再分解唯一性:一张表只说一件事,是对记录的唯一性约束,要求记录有唯一标识直接性:数据不能存在传递关系,即每个属性都跟主键有直接关系,而不是间接关系
说一说三大范式
B树与B+树的区别有两个:1、B树的非叶子节点是存储数据的,而B+树的非叶子节点只存储索引信息2、B+树的非最右侧的叶子节点向右会指向右侧的叶子节点,形成一个有序的连表在查找数据的时候,B树需要根据数据是比较查找查询次数比较多,由于B+树存储的是数据的索引,所以只有一次IO就可以查找到数据而且B树非叶子节点只存储索引,所以能存储更多的索引,使树的高度降低
B树和B+数的区别
事务A、B交替执行,事务A被事务B干扰到了,因为font color=\"#f44336\
什么是幻读,脏读,不可重复读呢?
覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取,可以减少回表的次数
什么是覆盖索引?
1、尽量避免全表扫描,使用where及order by涉及的列上建立索引2、尽量避免在where语句中对字段进行null值判断3、尽量避免在where语句中使用!=或<>操作符4、尽量避免在where语句中使用or来连接条件5、in 和 not in 会导致全表扫描6、使用模糊查询时也会导致全表扫描7、尽量避免在where语句中对字段进行表达式操作8、尽量避免在where语句中对字段进行函数操作9、不要在where语句中的“=”左边进行函数、算术运算或表达式运算10、使用in的时候用exists代替in
SQL优化?
1.「读提交」:即能够「读取到那些已经提交」的数据2.「读未提交」:即能够「读取到没有被提交」的数据3.「可重复读」:可重复读指的是在一个事务内,最开始读到的数据和事务结束前的「任意时刻读到的同一批数据都是一致的」4.「可串行化」:最高事务隔离级别,不管多少事务,都是「依次按序一个一个执行」
「脏读」脏读指的是「读到了其他事务未提交的数据」,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读「不可重复读」对比可重复读,不可重复读指的是在同一事务内,「不同的时刻读到的同一批数据可能是不一样的」。「幻读」幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现「好像刚刚的更改对于某些数据未起作用」,但其实是事务B刚插入进来的这就叫幻读
事务的隔离级别?
分析语句,是否加载了不必要的字段/数据。分析SQl执行句话,是否命中索引等。如果SQL很复杂,优化SQL结构如果表数据量太大,考虑分表
优化慢查询?
「1.没有用到索引」比如函数导致的索引失效,或者本身就没有加索引
「2.表数据量太大」考虑分库分表吧
「3.优化器选错了索引」「考虑使用」 force index 强制走索引
一条 Sql 语句查询一直慢会是什么原因?
1.「表结构优化」1.1拆分字段1.2字段类型的选择1.3字段类型大小的限制1.4合理的增加冗余字段1.5新建字段一定要有默认值
2.「索引方面」2.1索引字段的选择2.2利用好mysql支持的索引下推,覆盖索引等功能2.3唯一索引和普通索引的选择
3.「查询语句方面」3.1避免索引失效3.2合理的书写where条件字段顺序3.3小表驱动大表3.4可以使用force index()防止优化器选错索引
4.「分库分表」
说说你的 Sql 调优思路吧
1.「本地消息表」2.「消息事务」3.「二阶段提交」4.「三阶段提交」5.「TCC」6.「最大努力通知」7.「Seata 框架」
分布式式事务怎么实现?
步骤一:主库的更新事件(update、insert、delete)被写到binlog步骤二:从库发起连接,连接到主库。步骤三:此时主库创建一个binlog dump thread,把binlog的内容发送到从库。步骤四:从库启动之后,创建一个I/O线程,读取主库传过来的binlog内容并写入到relay log步骤五:还会创建一个SQL线程,从relay log里面读取内容,从Exec_Master_Log_Pos位置开始执行读取到的更新事件,将更新内容写入到slave的db
主从复制步骤
主从同步延迟问题以及解决办法
最左前缀其实说的是,在 where 条件中出现的字段,「如果只有组合索引中的部分列,则这部分列的触发索引顺序」,是按照定义索引的时候的顺序从前到后触发,最左面一个列触发不了,之后的所有列索引都无法触发。
什么是最左前缀原则?
Inner join 内连接,在两张表进行连接查询时,只保留两张表中完全匹配的结果集left join 在两张表进行连接查询时,会返回左表所有的行,即使在右表中没有匹配的记录。right join 在两张表进行连接查询时,会返回右表所有的行,即使在左表中没有匹配的记录。
mysql 的内连接、左连接、右连接有什么区别?
内连接(inner join):取得两张表中满足存在连接匹配关系的记录。外连接(outer join):取得两张表中满足存在连接匹配关系的记录,以及某张表(或两张表)中不满足匹配关系的记录。交叉连接(cross join):显示两张表所有记录一一对应,没有匹配关系进行筛选,也被称为:笛卡尔积。
什么是内连接、外连接、交叉连接、笛卡尔积呢?
from<左表的名字>on<join的条件><join的类型><右表的名字>where<where的条件>group by<group by的字段>Having<having的条件>selectdistinct<要查询的字段>order by<order by的条件>limit<limit的数字>
一条Sql的执行顺序?
排查过程:使用top 命令观察,确定是mysqld导致还是其他原因。如果是mysqld导致的,show processlist,查看session情况,确定是不是有消耗资源的sql在运行。找出消耗高的 sql,看看执行计划是否准确, 索引是否缺失,数据量是否太大。处理:kill 掉这些线程(同时观察 cpu 使用率是否下降),进行相应的调整(比如说加索引、改 sql、改内存参数)重新跑这些 SQL。其他情况:也有可能是每个 sql 消耗资源并不多,但是突然之间,有大量的 session 连进来导致 cpu 飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等
使用Mysql内存飙到100%怎么排查?
如果存在某些被索引的列的判断条件时,MySQL 将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合 MySQL 服务器传递的条件,「只有当索引符合条件时才会将数据检索出来返回给 MySQL 服务器」 。
什么是索引下推?
先写 redolog 后写binlog。假设在 redolog 写完,binlog 还没有写完的时候,MySQL 进程异常重启,这时候 binlog 里面就没有记录这个语句。然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 「binlog 丢失」,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。
先写 binlog 后写 redolog。如果在 binlog 写完之后 crash,由于 redolog 还没写,崩溃恢复以后这个事务无效,所以这一行c的值是0。但是 binlog 里面已经记录了“把c从0改成1”这个日志。所以,在之后用 binlog 来恢复的时候就「多了一个事务出来」,恢复出来的这一行 c 的值就是 1,与原库的值不同。
可以看到,「如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致」。
Innodb 事务为什么要两阶段提交?
B+ 树只在叶子结点储存数据,非叶子结点不存具体数据,只存 key,查询更稳定,增大了广度,而一个节点就是磁盘一个内存页,内存页大小固定,那么相比 B 树,B- 树这些「可以存更多的索引结点」,宽度更大,树高矮,节点小,拉取一次数据的磁盘 IO 次数少,并且 B+ 树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,效率更高。
主键索引 加速查询,列值唯一,一个表只有1个,不允许有空值 ALTER TABLE table_name ADD PRIMARY KEY ( filed_name )
覆盖索引 索引包含所需要的值,不需要“回表”查询,比如查询 两个字段,刚好是 组合索引 的两个字段
全文索引 对内容进行分词搜索,仅可用于Myisam, 更多用ElasticSearch做搜索 ALTER TABLE table_name ADD FULLTEXT ( filed_name )
聚簇索引:B+树是左⼩右⼤的顺序存储结构,节点只包含id索引列,⽽叶⼦节点包含索引列和数据,这种数据和索引在⼀起存储的索引⽅式叫做聚簇索引,⼀张表只能有⼀个聚簇索引。假设没有定义主键,InnoDB会选择⼀个唯⼀的⾮空索引代替,如果没有的话则会隐式定义⼀个主键作为聚簇索引。
非聚簇索引:⾮聚簇索引(⼆级索引)保存的是主键id值,这⼀点和myisam保存的是数据地址是不同的
mysql的索引有哪些,聚簇和⾮聚簇索引⼜是什么?
回表就是先通过数据库索引扫描出该索引树中数据所在的行,取到主键 id,再通过主键 id 取出主键索引数中的数据,即基于非主键索引的查询需要多扫描一棵索引树.
什么是回表?
普通索引和唯一索引该怎么选择?
事务是指是程序中一系列操作必须全部成功完成,有一个失败则全部失败。
A原⼦性由undo log⽇志保证,它记录了需要回滚的⽇志信息,事务回滚时撤销已经执⾏成功的sql
C⼀致性⼀般由代码层⾯来保证
I隔离性由MVCC来保证
D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,事务提交的时候通过redo log刷盘,宕机的时候可以从redo log恢复
什么是事务?其特性是什么?
InnoDB 的 redo log 是固定大小的,比如可以配置为一组4个文件,每个文件的大小是1GB,那么总共就可以记录4GB的操作。「从头开始写,写到末尾就又回到开头循环写」。
所以,如果数据写满了但是还没有来得及将数据真正的刷入磁盘当中,那么就会发生「内存抖动」现象,从肉眼的角度来观察会发现 mysql 会宕机一会儿,此时就是正在刷盘了。
redolog 是怎么记录日志的?
「1. 数据库在刷新脏页」比如 「redolog 写满了」,「内存不够用了」释放内存如果是脏页也需要刷,mysql 「正常空闲状态刷脏页」
「2. 没有拿到锁」
一条 Sql 语句查询偶尔慢会是什么原因?
业务性能1、应用上线前会审查业务新增的sql,和分析sql执行计划 比如是否存在 select * ,索引建立是否合理 2、开启慢查询日志,定期分析慢查询日志 3、监控CPU/内存利用率,读写、网关IO、流量带宽 随着时间的变化统计图 4、吞吐量QPS/TPS,一天内读写随着时间的变化统计图
数据安全1、短期增量备份,比如一周一次。 定期全量备份,比如一月一次 2、检查是否有非授权用户,是否存在弱口令,网络防火墙检查 3、导出数据是否进行脱敏,防止数据泄露或者黑产利用 4、数据库 全量操作日志审计,防止数据泄露 5、数据库账号密码 业务独立,权限独立控制,防止多库共用同个账号密码 6、高可用 主从架构,多机房部署
针对线上的数据库,你会做哪些监控,业务性能 + 数据安全 角度分析
要安全的修改同一行数据,就要保证一个线程在修改时其它线程无法更新这行记录。一般有悲观锁和乐观锁两种方案~使用悲观锁悲观锁思想就是,当前线程要进来修改数据时,别的线程都得拒之门外~比如,可以使用select…for update ~select * from User where name=‘jay’ for update以上这条sql语句会锁定了User表中所有符合检索条件(name=‘jay’)的记录。本次事务提交之前,别的线程都无法修改这些记录。使用乐观锁乐观锁思想就是,有线程过来,先放过去修改,如果看到别的线程没修改过,就可以修改成功,如果别的线程修改过,就修改失败或者重试。实现方式:乐观锁一般会使用版本号机制或CAS算法实现。
在高并发情况下,如何做到安全的修改同一行数据?
原子性: 事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。一致性: 指在事务开始之前和事务结束以后,数据不会被破坏,假如A账户给B账户转10块钱,不管成功与否,A和B的总金额是不变的。隔离性: 多个事务并发访问时,事务之间是相互隔离的,即一个事务不影响其它事务运行效果。简言之,就是事务之间是进水不犯河水的。持久性: 表示事务完成以后,该事务对数据库所作的操作更改,将持久地保存在数据库之中。
mysql事务的四大特性
查看是否涉及多表和子查询,优化Sql结构,如去除冗余字段,是否可拆表等优化索引结构,看是否可以适当添加索引数量大的表,可以考虑进行分离/分表(如交易流水表)数据库主从分离,读写分离explain分析sql语句,查看执行计划,优化sql查看mysql执行日志,分析是否有其他方面的问题
一条sql执行过长的时间,你如何优化,从哪些方面入手?
插入缓冲(insert buffer)二次写(double write)自适应哈希索引(ahi)预读(read ahead)
InnoDB引擎的4大特性,了解过吗
什么是数据库事务?
一个6亿的表a,一个3亿的表b,通过外间tid关联,你如何最快的查询出满足条件的第50000到第50200中的这200条数据记录。
图解:
连接器:建立连接,管理连接、校验用户身份;查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL 8.0 已删除该模块;解析 SQL:通过解析器对 SQL 查询语句进行词法分析、语法分析,然后构建语法树,方便后续模块读取表名、字段、语句类型;执行 SQL:执行 SQL 共有三个阶段:**预处理阶段:检查表或字段是否存在;将 select * 中的 * 符号扩展为表上的所有列。**优化阶段:基于查询成本的考虑, 选择查询成本最小的执行计划;**执行阶段:根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端;
总结:
执行一条 select 语句,期间发生了什么?
用以下语句来举例,c 字段无索引,id 为主键索引update T set c=c+1 where id=2;
1.执行器先找引擎取 id=2 这一行。id 是主键,引擎直接用树搜索找到这一行如果 id=2 这一行所在的数据页本来就「在内存中」,就「直接返回」给执行器「不在内存」中,需要先从磁盘「读入内存」,然后再「返回」
2.执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口「写入这行新数据」
3.引擎将这行新数据更新到内存中,同时将这个更新操作「记录到 redo log 里面」,此时 redo log 处于 「prepare」 状态。然后告知执行器执行完成了,随时可以提交事务
4.执行器「生成这个操作的 binlog」,并把 binlog 「写入磁盘」
5.执行器调用引擎的「提交事务」接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,「更新完成」
使用 Innodb 的情况下,一条更新语句是怎么执行的?
字段有唯一性where条件经常用到的字段group by和order by经常用到的字段
适合:
字段频繁变化字段数据大量重复不经常用的字段数据太少
不适合:
什么时候适合用索引?什么时候不适合?
type 字段就是描述了找到所需数据时使用的扫描方式是什么,常见扫描类型的执行效率从低到高的顺序为:ALL(全表扫描);index(全索引扫描);range(索引范围扫描);ref(非唯一索引扫描);eq_ref(唯一索引扫描);const(结果只有一条的主键或唯一索引扫描)。
possible_keys 字段表示可能用到的索引;key 字段表示实际用的索引,如果这一项为 NULL,说明没有使用索引;key_len 表示索引的长度;rows 表示扫描的数据行数。type 表示数据扫描类型
执行计划的参数有哪些?
WAL 就是 Write-Ahead Logging,其实就是「所有的修改都先被写入到日志中,然后再写磁盘」,用于保证数据操作的原子性和持久性。
好处:1.「读和写可以完全地并发执行」,不会互相阻塞2.先写入 log 中,磁盘写入从「随机写变为顺序写」,降低了 client 端的延迟就。并且,由于顺序写入大概率是在一个磁盘块内,这样产生的 io 次数也大大降低3.写入日志当数据库崩溃的时候「可以使用日志来恢复磁盘数据」
WAl 是什么?有什么好处?
mysql索引优化?
1. 基本规则SQL 可以写在一行或者多行。为了提高可读性,各子句分行写,必要时使用缩进每条命令以 ; 或 \\g 或 \\G 结束关键字不能被缩写也不能分行关于标点符号必须保证所有的()、单引号、双引号是成对结束的必须使用英文状态下的半角输入方式字符串型和日期时间类型的数据可以使用单引号(' ')表示列的别名,尽量使用双引号(\" \
SQL书写规范?
系统内存飙到100%怎么去排查?
binlog 是归档日志,属于 Server 层的日志,是一个二进制格式的文件,用于「记录用户对数据库更新的SQL语句信息」。
主要作用主从复制数据恢复
binlog 是做什么的?
undolog 是 InnoDB 存储引擎的日志,用于保证数据的原子性,「保存了事务发生之前的数据的一个版本,也就是说记录的是数据是修改之前的数据,可以用于回滚」,同时可以提供多版本并发控制下的读(MVCC)。
主要作用事务回滚实现多版本控制(MVCC)
undolog 是做什么的?
relaylog 是中继日志,「在主从同步的时候使用到」,它是一个中介临时的日志文件,用于存储从master节点同步过来的binlog日志内容。
master 主节点的 binlog 传到 slave 从节点后,被写入 relay log 里,从节点的 slave sql 线程从 relaylog 里读取日志然后应用到 slave 从节点本地。从服务器 I/O 线程将主服务器的二进制日志读取过来记录到从服务器本地文件,然后 SQL 线程会读取 relay-log 日志的内容并应用到从服务器,从而「使从服务器和主服务器的数据保持一致」。
relaylog 是做什么的?
redolog 是 「InnoDB 存储引擎所特有的一种日志」,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。
可以做「数据恢复并且提供 crash-safe 能力」
当有增删改相关的操作时,会先记录到 Innodb 中,并修改缓存页中的数据,「等到 mysql 闲下来的时候才会真正的将 redolog 中的数据写入到磁盘当中」。
redolog 是做什么的?
1.「redolog」 是 「Innodb」 独有的日志,而 「binlog」 是 「server」 层的,所有的存储引擎都有使用到
2.「redolog」 记录了「具体的数值」,对某个页做了什么修改,「binlog」 记录的「操作内容」
3.「binlog」 大小达到上限或者 flush log 「会生成一个新的文件」,而 「redolog」 有固定大小「只能循环利用」
4.「binlog 日志没有 crash-safe 的能力」,只能用于归档。而 redo log 有 crash-safe 能力。
redolog 和 binlog 的区别是什么?
MVCC:多版本并发控制,是现代数据库(包括 MySQL、Oracle、PostgreSQL 等)引擎实现中常用的处理读写冲突的手段,目的在于「提高数据库高并发场景下的吞吐性能」。
在 MVCC 协议下,每个读操作会看到一个一致性的快照,「这个快照是基于整个库的」,并且可以实现非阻塞的读,用于「支持读提交和可重复读隔离级别的实现」
MVCC 允许数据具有多个版本,这个版本可以是时间戳或者是全局递增的事务 ID,在同一个时间点,不同的事务看到的数据是不同的,这个修改的数据是「记录在 undolog 中」的。
失效场景:两个连续的快照读中间有个当前读
说一说 mvcc 吧,有什么作用?
快照读就是最普通的Select查询语句
当前读指执行insert,update,delete,select 。。。for update,select。。。lock in share mode语句时进行读取数据的方式
当前读和快照读?
间隙锁是可重复读级别下才会有的锁,结合MVCC和间隙锁可以解决幻读的问题。
需要注意的是唯⼀索引是不会有间隙索引的。
那你知道什么是间隙锁吗?
1.master 主库将此次更新的事件类型「写入到主库的 binlog 文件」中2.master 「创建 log dump 线程通知 slave」 需要更新数据3.「slave」 向 master 节点发送请求,「将该 binlog 文件内容存到本地的 relaylog 中」4.「slave 开启 sql 线程」读取 relaylog 中的内容,「将其中的内容在本地重新执行一遍」,完成主从数据同步
「同步策略」:1.「全同步复制」:主库强制同步日志到从库,等全部从库执行完才返回客户端,性能差2.「半同步复制」:主库收到至少一个从库确认就认为操作成功,从库写入日志成功返回ack确认
Mysql 主从之间是怎么同步数据的?
1.MySQL 5.6 版本以后,提供了一种「并行复制」的方式,通过将 SQL 线程转换为多个 work 线程来进行重放2.「提高机器配置」(王道)3.在业务初期就选择合适的分库、分表策略,「避免单表单库过大」带来额外的复制压力4.「避免长事务」5.「避免让数据库进行各种大量运算」6.对于一些对延迟很敏感的业务「直接使用主库读」
主从延迟要怎么解决?
在使用 delete 删除数据时,其实对应的数据行并不是真正的删除,是「逻辑删除」,InnoDB 仅仅是将其「标记成可复用的状态」,所以表空间不会变小
当定义varchar长度小于等于255时,长度标识位需要一个字节(utf-8编码)
当大于255时,长度标识位需要两个字节,并且建立的「索引也会失效」
为什么 VarChar 建议不要超过255?
mysql锁分为共享锁和排他锁,也叫做读锁和写锁。读锁是共享的,可以通过lock in share mode实现,这时候只能读不能写。写锁是排他的,它会阻塞其他的写锁和读锁。从颗粒度来区分,可以分为表锁和⾏锁两种。表锁会锁定整张表并且阻塞其他⽤户对该表的所有读写操作,⽐如alter修改表结构的时候会锁表。⾏锁⼜可以分为乐观锁和悲观锁,悲观锁可以通过for update实现,乐观锁则通过版本号实现。
Mysql 中有哪些锁?
1.并发情况下,数据库「连接池容易被撑爆」2.「容易造成大量的阻塞和锁超时」长事务还占用锁资源,也可能拖垮整个库,3.执行时间长,容易造成「主从延迟」4.「回滚所需要的时间比较长」事务越长整个时间段内的事务也就越多5.「undolog 日志越来越大」长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间
为什么不要使用长事务?
buffer pool 是一块内存区域,为了「提高数据库的性能」,当数据库操作数据的时候,把硬盘上的数据加载到 buffer pool,不直接和硬盘打交道,操作的是 buffer pool 里面的数据,数据库的增删改查都是在 buffer pool 上进行
buffer pool 里面缓存的数据内容也是一个个数据页
其中「有三大双向链表」:「free 链表」用于帮助我们找到空闲的缓存页「flush 链表」用于找到脏缓存页,也就是需要刷盘的缓存页「lru 链表」用来淘汰不常被访问的缓存页,分为热数据区和冷数据区,冷数据区主要存放那些不常被用到的数据
预读机制:Buffer Pool 有一项特技叫预读,存储引擎的接口在被 Server 层调用时,会在响应的同时进行预判,将下次可能用到的数据和索引加载到 Buffer Pool
buffer pool 是做什么的?
1. 可以做⼀个mapping表,⽐如这时候商家要查询订单列表怎么办呢?不带user_id查询的话你总不能扫全表吧?所以我们可以做⼀个映射关系表,保存商家和⽤户的关系,查询的时候先通过商家查询到⽤户列表,再通过user_id去查询。2. 打宽表,⼀般⽽⾔,商户端对数据实时性要求并不是很⾼,⽐如查询订单列表,可以把订单表同步到离线(实时)数仓,再基于数仓去做成⼀张宽表,再基于其他如es提供查询服务。3. 数据量不是很⼤的话,⽐如后台的⼀些查询之类的,也可以通过多线程扫表,然后再聚合结果的⽅式来做。或者异步的形式也是可以的。
分表后⾮sharding_key的查询怎么处理呢?
Mysql存储流程?
mysql
(1)Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。(2)MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。(3)通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)
什么是MyBatis
优点:(1)基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写 在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。(2)与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;(3)很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。(4)能够与Spring很好的集成;(5)提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。缺点(1)SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。(2)SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
说说MyBatis的优点和缺点
#{}是预编译处理,${}是字符串替换。Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;Mybatis在处理${}时,就是把${}替换成变量的值。使用#{}可以有效的防止SQL注入,提高系统安全性。
#{}和${}的区别是什么?
第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
第2种: 通过来映射字段名和实体类属性名的一一对应的关系。
当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接拼写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页,比如:MySQL数据的时候,在原有SQL后面拼写limit。分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
Mybatis是如何进行分页的?分页插件的原理是什么?
第一种是使用标签,逐一定义数据库列名和对象属性名之间的映射关系。第二种是使用sql列的别名功能,将列的别名书写为对象属性名。有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
加上动态sql的9个标签,其中为sql片段标签,通过标签引入sql片段,为不支持自增的主键生成策略标签
Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
MyBatis实现一对一有几种方式?具体怎么操作的?
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
1. MyBatis 一级缓存的生命周期和 SqlSession 一致。2. MyBatis 一级缓存内部设计简单,只是一个没有容量限定的 HashMap,在缓存的功能性上有所欠缺。3. MyBatis 的一级缓存最大范围是 SqlSession 内部,有多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为 Statement。
一级缓存localCache在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的 SQL,MyBatis 提供了一级缓存的方案优化这部分场景,如果是相同的 SQL 语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。每个 SqlSession 中持有了 Executor,每个 Executor 中有一个 LocalCache。当用户发起查询时,MyBatis 根据当前执行的语句生成 MappedStatement,在 Local Cache 进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入 Local Cache,最后返回结果给用户
二级缓存开启后,同一个 namespace 下的所有操作语句,都影响着同一个 Cache,即二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程为:二级缓存 -> 一级缓存 -> 数据库1. MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时粒度更加细,能够到 namespace 级别,通过 Cache 接口实现类不同的组合,对 Cache 的可控性也更强。2. MyBatis 在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。3. 在分布式环境下,由于默认的 MyBatis Cache 实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将 MyBatis 的 Cache 接口实现,有一定的开发成本,直接使用 Redis、Memcached 等分布式缓存可能成本更低,安全性也更高。
二级缓存在上文中提到的一级缓存中,其最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用 CachingExecutor 装饰Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询
说说Mybatis的缓存机制
1.装载相应的数据库的 JDBC 驱动并进行初始化:2.建立 JDBC 和数据库之间的 Connection 连接:Class.forName(\"com.mysql.jdbc.Driver\");Connection c = DriverManager.getConnection(\"jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8\
JDBC 编程有哪些步骤?
使用JDK动态代理+MapperProxy。本质上调用的是MapperProxy的invoke方法。
MyBatis 中比如 UserMapper.java 是接口,为什么没有实现类还能调用?
读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。
MyBatis的执行流程?
MyBatis
Eureka:注册中心,服务注册和发现Ribbon:负载均衡,实现服务调用的负载均衡Hystrix:熔断器Feign:远程调用Zuul:网关Spring Cloud Config:配置中心
springCloud的组键有哪些?
我们使用的是:Hoxton.SR10,对应springboot对应的版本是2.3.9
SpringCloud使用的是那个版本?
springCloud
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项。@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })@ComponentScan:Spring组件扫描
1.SpringBoot启动类注解?它是由哪些注解组成?
1. 直接执行 main 方法运行2. 命令行 java -jar 的方式 打包用命令或者放到容器中运行3.用 Maven/ Gradle 插件运行
2.SpringBoot启动方式?
不需要,内置了 Tomcat/Jetty。
3.SpringBoot需要独立的容器运行?
@EnableAutoConfiguration (开启自动配置) 该注解引入了AutoConfigurationImportSelector,该类中的方法会扫描所有存在META-INF/spring.factories的jar包。
4.SpringBoot自动配置原理?
在启动类加: @ImportResource(locations = {\"classpath:spring.xml\"})
5.SpringBoot如何兼容Spring项目?
1、初始化环境变量2、初始化环境变量完成3、应用启动4、应用已启动完成5、应用刷新6、应用停止7、应用关闭
SpringBoot生命周期
Springboot三大核心注解是:Configuration,EnableAuto,ComponentScan
SpringBoot三大核心注解
@PatchMapping@PostMapping@GetMapping@PutMapping@DeleteMapping
6.针对请求访问的几个组合注解?
@SpringBootTest
7.编写测试用例的注解?
@ControllerAdvice@ExceptionHandler
8.SpringBoot异常处理相关注解?
@PropertySource@Value@Environment@ConfigurationProperties
9.SpringBoot读取配置相关注解有?
10.SpringBoot Starter的工作原理
跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross\u0002origin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。
11.Spring Boot 中如何解决跨域问题 ?
如何快速搭建
springboot
第一个:@RequestParam注解作用:是将请求参数绑定到你的控制器的方法参数上,是springmvc中的接收普通参数的注解,要好好的理解,在实际开发中,很常用的注解之一。属性:value是请求参数中的名称。required是请求参数是否必须提供参数,它的默认是true,意思是表示必须提供,假如你不提供就会报错啦。第二个:@RequestBody注解作用:如果作用在方法上,就表示该方法的返回结果是直接按写入的Http responsebody中(一般在异步获取数据时使用的注解)。属性:required是否必须有请求体,它的默认也是true,在使用该注解时,值得注意的当为true时get的请求方式是报错的,如果你取值为false的话,get的请求是空的null。第三个:@PathVaribale注解作用:该注解是用于绑定url中的占位符,但是注意,spring3.0以后,url才开始支持占位符的,它是springmvc支持的rest风格url的一个重要的标志,什么是占位符呢,我去个例子比如:请求的url中/user/{id},这个{id}就是url的占位符。第四个:@Controller注解作用:该注解是用来标记一个类的,如果被一个类被标注为Controller的话,那么它就会被spring扫面机制扫面到,然后会自动将其注册为spring应用程序的上下文里的一个Bean,controller的真正作用是负责是处理由DispatcherServlet分发的请求,它把用户请求的数据经过业务处理层处理的,然后我们通过调用对应的Service,最后封装成一个Model,然后把Model返回给View显示到前端给用户的。第五个:@RequsetMappring注解作用:该注解的作用就是用来处理请求地址映射的,也就是说将其中的所有处理器方法都映射到url路径上,他有6个属性。属性:method:是让你指定请求的method的类型,比如常用的有get和post。value:是指请求的实际地址,如果是多个地址就用{}来指定就可以啦。produces:指定返回的内容类型,当request请求头中的Accept类型中包含指定的类型才可以返回的。consumes:指定处理请求的提交内容类型,比如一些json、html、text等的类型。headers:指定request中必须包含那些的headed值时,它才会用该方法处理请求的。params:该属性是指定request中一定要有的参数值,它才会使用该方法处理请求
SpringMVC常用的注解?
SpringMVC有哪些组件?
1、用户发送请求到前端控制器(DispatcherServlet)。2、前端控制器请求处理器映射器(HandlerMapping)去查找处理器(Handler)。3、找到以后处理器映射器(HandlerMappering)向前端控制器返回执行链(HandlerExecutionChain)。4、前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)。5、处理器适配器去执行Handler。6、处理器执行完给处理器适配器返回ModelAndView。7、处理器适配器向前端控制器返回ModelAndView。8、前端控制器请求视图解析器(ViewResolver)去进行视图解析。9、视图解析器向前端控制器返回View。10、前端控制器对视图进行渲染。11、前端控制器向用户响应结果。
SpringMVC执行流程
springMVC
笼统的说法启动Spring时: 1. ⾸先会进⾏扫描,扫描得到所有的BeanDefinition对象,并存在⼀个Map中 2. 然后筛选出⾮懒加载的单例BeanDefinition进⾏创建Bean,对于多例Bean不需要在启动过程中去 进⾏创建,对于多例Bean会在每次获取Bean时利⽤BeanDefinition去创建 3. 利⽤BeanDefinition创建Bean就是Bean的创建⽣命周期,这期间包括了合并BeanDefinition、推 断构造⽅法、实例化、属性填充、初始化前、初始化、初始化后等步骤,其中AOP就是发⽣在初始 化后这⼀步骤中 4. 单例Bean创建完了之后,Spring会发布⼀个容器启动事件 5. Spring启动结束
spring的启动流程?
首先 IOC 是一个「容器」,是用来装载对象的,它的核心思想就是「控制反转」那么究竟「什么是控制反转」?控制反转就是说,「把对象的控制权交给了 spring,由 spring 容器进行管理」,我们不进行任何操作那么为「什么需要控制反转」?我们想象一下,没有控制反转的时候,我们需要「自己去创建对象,配置对象」,还要「人工去处理对象与对象之间的各种复杂的依赖关系」,当一个工程的量起来之后,这种关系的维护是非常令人头痛的,所以就有了控制反转这个概念,将对象的创建、配置等一系列操作交给 spring 去管理,我们在使用的时候只要去取就好了
说一下你理解的 IOC 是什么?
DI 就是依赖注入,其实和 IOC 大致相同,只不过是「同一个概念使用了不同的角度去阐述」DI 所描述的「重点是在于依赖」,我们说了 「IOC 的核心功能就是在于在程序运行时动态的向某个对象提供其他的依赖对象」,而这个功能就是依靠 DI 去完成的,比如我们需要注入一个对象 A,而这个对象 A 依赖一个对象 B,那么我们就需要把这个对象 B 注入到对象 A 中,这就是依赖注入font color=\"#f44336\
那么 DI 又是什么?
AOP面向切面编程,简单的理解就是,在不修改原有代码的基础上对某些功能进行增强。通常用于,事务,日志,权限等AOP 意为:「面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术」。AOP 是 「OOP(面向对象编程) 的延续」,是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。「AOP 实现主要分为两类:」「静态 AOP 实现」, AOP 框架「在编译阶段」对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ「动态 AOP 实现」, AOP 框架「在运行阶段」对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP
spring 中 AOP 的实现是「通过动态代理实现的」,如果是实现了接口就会使用 JDK 动态代理,否则就使用 CGLIB 代理。JDK动态代理:只能代理接口,不能代理类CGLIB代理:是通过继承的方式做的动态代理,如果类被final修饰它就无法使用CGLIB做动态代理
「有 5 种通知类型:」前置通知 Before advice:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常后置通知 After returning advice:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行异常通知 After throwing advice:在连接点抛出异常后执行最终通知 After (finally) advice:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容环绕通知 Around advice:环绕通知围绕在连接点前后,能在方法调用前后自定义一些操作,还需要负责决定是继续处理 join point (调用 ProceedingJoinPoint 的 proceed 方法)还是中断执行
说说 AOP 是什么?
它有四个阶段:实例化 -》属性赋值 -》 初始化 -》销毁
Spring中Bean的生命周期
spring事务失效场景
轻量:Spring 是轻量的,基本的版本大约2MB。控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。容器:Spring 包含并管理应用中对象的生命周期和配置。MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。
你们项目中为什么使用Spring框架?
1、工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;2、单例模式:Bean默认为单例模式。3、代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;4、font color=\"#f44336\
比如通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象
工厂模式
Spring 中的 AOP 中 AdvisorAdapter 类,它有三个实现:MethodBeforAdviceAdapter、AfterReturnningAdviceAdapter、ThrowsAdviceAdapter。Spring会根据不同的 AOP 配置来使用对应的 Advice,与策略模式不同的是,一个方法可以同时拥有多个Advice。Spring 存在很多以 Adapter 结尾的,大多数都是适配器模式。
适配器模式
Spring 中 jdbcTemplate 等以 Template 结尾的对数据库操作的类,都会使用到模板方法设计模式,一些通用的功能
模板方法模式
我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源
包装器模式
spring 中都用到了哪些设计模式?
在你项目中使用到了哪些设计模式?使用在了哪里?为什么使用它?
1.判断事务是否已经完成,如果完成抛出异常2.判断事务是否已经被标记成回滚,则执行回滚操作3.嵌入事务标记回滚,如果嵌入事务抛出了异常执行了回滚,但是在调用方把嵌入事务的异常个捕获没有抛出,就会执行这一步。4.提交事务
spring怎么提交事务的?
这两个是「不同的产物」「BeanFactory 是 IOC 容器」,是用来承载对象的「FactoryBean 是一个接口」,为 Bean 提供了更加灵活的方式,通过代理一个Bean对象,对方法前后做一些操作。
那 BeanFactory 和 FactoryBean 又有什么区别?
BeanFactory在启动的时候不会去实例化Bean,从容器中拿Bean的时候才会去是实例化ApplicationContext在启动的时候就把所有的Bean全部实例化了
BeanFactory和ApplicationContext的区别?
这四个注解的「本质都是一样的,都是将被该注解标识的对象放入 spring 容器当中,只是为了在使用上区分不同的应用分层」@Repository:dao层@Service:service层@Controller:controller层@Compent:其他不属于以上三层的统一使用该注解
@Repository、@Service、@Compent、@Controller它们有什么区别?
「静态代理」由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了静态代理通常只代理一个类静态代理事先知道要代理的是什么「动态代理」在程序运行时,运用反射机制动态创建而成动态代理是代理一个接口下的多个实现类动态代理不知道要代理什么东西,只有在运行时才知道
动态代理和静态代理有什么区别?
使用@PostConstruct注解,该注解作用于void方法上在配文件中配置init-method方法将类实现InitializingBean接口,实现afterPropertiesSet方法
三种实现指定初始化方法
利⽤动态代理技术来实现AOP,⽐如JDK动态代理或Cglib动态代理,利⽤动态代理技术,可以针对某个类 ⽣成代理对象,当调⽤代理对象的某个⽅法时,可以任意控制该⽅法的执⾏,⽐如可以先打印执⾏时间, 再执⾏该⽅法,并且该⽅法执⾏完成后,再次打印执⾏时间。 项⽬中,⽐如事务、权限控制、⽅法执⾏时⻓⽇志都是通过AOP技术来实现的,凡是需要对某些⽅法做统 ⼀处理的都可以⽤AOP来实现,利⽤AOP可以做到业务⽆侵⼊。
如何实现AOP,项⽬哪些地⽅⽤到了AOP?
在Spring中的对象默认是单例的,但是也可以配置为多例单例bean对象对应的类存在可变的成员变量并且其中存在改变这个变量的线程时,多线程操作该bean对象是会出现线程安全问题原因是:多线程操作如果改变成员变量,其他线程无法访问该bean对象,造成数据混乱解决办法:在bean对象中避免定义可变成员变量; 在bean对象中定义一个ThreadLocal成员变量,将要的可变成员变量保存在ThreadLocal中
Spring的对象默认是单例的还是多例的?单例bean存不存在线程安全问题?
通过三级缓存:一级缓存,用于保存实例化,注入,初始化完成的bean实例二级缓存,用于保存实例化完成的bean实例三级缓存,用于保存bean创建工厂
Spring如果解决循环依赖?
构造器注入setter注入接口注入
依赖注入的方式有几种,各是什么?
「@Resource 是 Java 自己的注解」,@Resource 有两个属性是比较重要的,分是 name 和 type;Spring 将 @Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型。所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。如果既不指定 name 也不指定 type 属性,这时将通过反射机制使用 byName 自动注入策略。「@Autowired 是spring 的注解」,是 spring2.5 版本引入的,Autowired 只根据 type 进行注入,「不会去匹配 name」。如果涉及到 type 无法辨别注入对象时,那需要依赖 @Qualifier 或 @Primary 注解一起来修饰。
@Autowired 和 @Resource 有什么区别?
Spring容器中的bean可以分为5个范围:(1)singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。(2)prototype:为每一个bean请求提供一个实例。(3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。(4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。(5)global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。
解释Spring支持的几种bean的作用域?
1. Spring事务底层是基于数据库事务和AOP机制的 2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean 3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解 4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接 5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮常重 要的⼀步 6. 然后执⾏当前⽅法,⽅法中会执⾏sql 7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务 8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务 9. Spring事务的隔离级别对应的就是数据库的隔离级别 10. Spring事务的传播机制是Spring事务⾃⼰实现的,也是Spring事务中最复杂的 11. Spring事务的传播机制是基于数据库连接来做的,⼀个数据库连接⼀个事务,如果传播机制配置为需要 新开⼀个事务,那么实际上就是先建⽴⼀个数据库连接,在此新数据库连接上执⾏sql
说一下spring的事务机制?
DEFAULT:采用 DB 默认的事务隔离级别READ_UNCOMMITTED:读未提交READ_COMMITTED:读已提交REPEATABLE_READ:可重复读SERIALIZABLE:串行化
spring 事务隔离级别有哪些?
1、编程式事务:beginTransaction()、commit()、rollback()等事务管理相关的方法2、声明式事务利用注解Transactional或者aop配置
spirng的事务管理?
它有七种传播行为:propagation_required:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务propagation_supports:如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行propagaion_mandatory:如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常propagation_requires_new:无论当前存不存在事务,都创建新事务。propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起propagation_never:以非事务方式执行,如果当前存在事务,则抛出异常propagation_nested:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则按required属性执行
Spring事务的传播行为
spring事务是怎么回滚的?
1.「propagation_required」当前方法「必须在一个具有事务的上下文中运行」,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚)
2.「propagation_supports」当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行
3.「propagation_mandatory」表示当前方法「必须在一个事务中运行」,如果没有事务,将抛出异常
4.「propagation_nested」如果当前方法正有一个事务在运行中,则该方法应该「运行在一个嵌套事务」中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同propagation_required的一样
5.「propagation_never」当方法务不应该在一个事务中运行,如果「存在一个事务,则抛出异常」
6.「propagation_requires_new」当前方法「必须运行在它自己的事务中」。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。
7.「propagation_not_supported」方法不应该在一个事务中运行。「如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行」
spring 事务的传播机制有哪些?
数据源:表示具体的事务性资源,是事务的真正处理者,如MySQL等。事务管理器:像一个大管家,从整体上管理事务的处理过程,如打开、提交、回滚等。事务应用和属性配置:像一个标识符,表明哪些方法要参与事务,如何参与事务,以及一些相关属性如隔离级别、超时时间等。
事务三要素是什么?
1.错误的传播特性
2.自己吞了异常
3.手动抛了别的异常
4.自定义了回滚异常
5.嵌套事务回滚多了
spring事务不回滚场景
在spring中主要用到的设计模式有:工厂模式、单例模式、代理模式、模板模式、观察者模式、适配器模式。
IOC控制反转也叫依赖注入,它就是典型的工厂模式,通过sessionfactory去注入实例
解释:将对象交给容器管理,你只需要在spring配置文件总配置相应的bean,以及设置相关属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你 需要调用这些bean的类(假设这个类名是A),分配的方法就是调用A的setter方法来注入,而不需要你在A类里面new这些bean了。
总结:对象实例化与初始化进行解耦
1.工厂模式
Spring中JavaBean默认为单例,因为spring上下文中会有很多个dao\\service\\action对象,如果用多例模式的话,就每次要用到的时候,都会重新创建一个新的对象,内存中就会有很多重复的对象,所以单例模式的特点就是能减少我们的内存空间,节约性能。
还有常用Spring中 @Repository、@Component、@Configuration @Service注解作用下的类默认都是单例模式的,所以,我目前认为在Spring下使用单例最优的方式是将类@Component注册为组件。使用场景主要有:数据库配置、Redis配置、权限配置、Filter过滤、webMvcConfig、swagger及自定义的时间转换器、类型转换器、对接第三方硬件时,调用硬件的dll、so文件等。单独使用@Component注解,只能控制到类上,使用@Configuration+@Bean可以控制到方法级别粒度,但是尽量避免@Component+@Bean组合使用,因为@Component+@Bean并不是单例,在调用过程中可能会出现多个Bean实例,导致蜜汁错误。
并不是所有的注解默认都是单例模式,@RestController就是多例
2.单例模式
Spring中的AOP就是典型的代理模式,首先我们先聊聊AOP(面向切面编程)的的一个设计原理:AOP可以说是对OOP的补充和完善 OOP引入了封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,oop允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能,日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在OOP设计中,它导致大量代码重复,而不利于各个模块的重用。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码简单点解释,比方说你想在你的biz层所有类中都加上一个打印‘你好’的功能,这时就可以用AOP思想来做,你先写个类写个类方法,方法经实现打印‘你好’,然后IOC这个类ref=\"biz.* \"让每个类都注入即可实现。
3.代理模式
定义:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤目的:1.使用模版方法模式的目的是避免编写重复代码,以便开发人员可以专注于核心业务逻辑的实现 2.解决接口与接口实现类之间继承矛盾问题
4.模板模式
定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新例如:报社的业务就是出版报纸。向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户、你就会一直收到新报纸。当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅报纸。报社:被观察者订户:观察者一个报社对应多个订户
在Spring中有一个ApplicationListener,采用观察者模式来处理的,ApplicationEventMulticaster作为主题,里面有添加,删除,通知等。spring有一些内置的事件,当完成某种操作时会发出某些事件动作,他的处理方式也就上面的这种模式,当然这里面还有很多,可以了解下spring的启动过程。
在java.util 包下 除了常用的 集合 和map之外还有一个Observable类,他的实现方式其实就是观察者模式。里面也有添加、删除、通知等方法。
5.观察者模式
定义:将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类能在一起工作,做法是将类自己的接口包裹在一个已存在的类中例子:以手机充电为例,电压220V,手机支持5.5V,充电器相当于适配器
AOP和MVC中,都有用到适配器模式。spring aop框架对BeforeAdvice、AfterAdvice、ThrowsAdvice三种通知类型的支持实际上是借助适配器模式来实现的,这样的好处是使得框架允许用户向框架中加入自己想要支持的任何一种通知类型,上述三种通知类型是spring aop框架定义的,它们是aop联盟定义的Advice的子类型。Spring中的AOP中AdvisorAdapter类,它有三个实现:MethodBeforAdviceAdapter、AfterReturnningAdviceAdapter、ThrowsAdviceAdapter。
6.适配器模式
2.spring中使用了哪些设计模式?
MVC:MVC是一种设计模式
M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)V-View 视图(做界面的展示 jsp,html……)C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)
1、 用户发送请求至前端控制器DispatcherServlet。 2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 4、 DispatcherServlet调用HandlerAdapter处理器适配器。5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。 6、 Controller执行完成返回ModelAndView。 7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。 8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。9、 ViewReslover解析后返回具体View。10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。11、 DispatcherServlet响应用户。
前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。处理器映射器(HandlerMapping):根据URL去查找处理器。处理器(Handler):需要程序员去写代码处理逻辑的。处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用)。视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面。
工作原理:
说说你对Spring MVC的理解
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
SpringMVC常用的注解有哪些?
Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。
什么是spring?
1.「Spring Core」:Spring核心,它是框架最基础的部分,提供IOC和依赖注入DI特性
2.「Spring Context」:Spring上下文容器,它是 BeanFactory 功能加强的一个子接口
3.「Spring Web」:它提供Web应用开发的支持
4.「Spring MVC」:它针对Web应用中MVC思想的实现
5.「Spring DAO」:提供对JDBC抽象层,简化了JDBC编码,同时,编码更具有健壮性
6.「Spring ORM」:它支持用于流行的ORM框架的整合,比如:Spring + Hibernate、Spring + iBatis、Spring + JDO的整合等
7.「Spring AOP」:即面向切面编程,它提供了与AOP联盟兼容的编程实现
spring 中有哪些核心模块?
spring 主要提供了「两种 IOC 容器」,一种是 「BeanFactory」,还有一种是 「ApplicationContext」它们的区别就在于,BeanFactory 「只提供了最基本的实例化对象和拿对象的功能」,而 ApplicationContext 是继承了 BeanFactory 所派生出来的产物,是其子类,它的作用更加的强大,比如支持注解注入、国际化等功能
ApplicationContext 继承了 BeanFactory,BeanFactory 是 Spring 中比较原始的Factory,它不支持 AOP、Web 等 Spring 插件。而 ApplicationContext 不仅包含了 BeanFactory的所有功能,还支持 Spring 的各种插件,还以一种面向框架的方式工作以及对上下文进行分层和实现继承。BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;而 ApplicationContext 面向使用Spring 的开发者,相比 BeanFactory 提供了更多面向实际应用的功能,几乎所有场合都可以直接使用 ApplicationContext,而不是底层的 BeanFactory。
spring 中的 IOC 容器有哪些?有什么区别?
实现AOP的步骤?
JDK 动态代理时业务类「必须要实现某个接口」,它是「基于反射的机制实现的」,生成一个实现同样接口的一个代理类,然后通过重写方法的方式,实现对代码的增强。CGLIB 动态代理是使用字节码处理框架 ASM,其原理是通过字节码技术为一个类「创建子类,然后重写父类的方法」,实现对代码的增强。
JDK 动态代理和 CGLIB 代理有什么区别?
Spring AOP 是运行时增强,是通过「动态代理实现」的AspectJ AOP 是编译时增强,需要特殊的编译器才可以完成,是通过「修改代码来实现」的,支持「三种织入方式」「编译时织入」:就是在编译字节码的时候织入相关代理类「编译后织入」:编译完初始类后发现需要 AOP 增强,然后织入相关代码「类加载时织入」:指在加载器加载类的时候织入
主要区别 Spring AOP AspecjtJ AOP增强方式 运行时增强 编译时增强实现方式 动态代理 修改代码编译器 javac 特殊的编译器 ajc效率 较低(运行时反射损耗性能) 较高织入方式 运行时 编译时、编译后、类加载时
Spring AOP 和 AspectJ AOP 有什么区别?
spring 中 Bean 的生命周期是怎样的?
循环依赖就是说两个对象相互依赖,形成了一个环形的调用链路spring 使用三级缓存去解决循环依赖的,其「核心逻辑就是把实例化和初始化的步骤分开,然后放入缓存中」,供另一个对象调用「第一级缓存」:用来保存实例化、初始化都完成的对象「第二级缓存」:用来保存实例化完成,但是未初始化完成的对象「第三级缓存」:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象
spring 解决循环依赖有两个前提条件:1.「不全是构造器方式」的循环依赖(否则无法分离初始化和实例化的操作)2.「必须是单例」(否则无法保证是同一对象)
spring 是怎么解决循环依赖的?
不可以,主要是为了⽣成代理对象。因为三级缓存中放的是⽣成具体对象的匿名内部类,他可以⽣成代理对象,也可以是普通的实例对象。使⽤三级缓存主要是为了保证不管什么时候使⽤的都是⼀个对象。假设只有⼆级缓存的情况,往⼆级缓存中放的显示⼀个普通的Bean对象, BeanPostProcessor 去⽣成代理对象之后,覆盖掉⼆级缓存中的普通Bean对象,那么多线程环境下可能取到的对象就不⼀致了。
为什么要使用三级缓存,二级缓存不能解决吗?
Spring中的后置处理器分为BeanFactory后置处理器和Bean后置处理器,它们是Spring底层源码架构设计 中⾮常重要的⼀种机制,同时开发者也可以利⽤这两种后置处理器来进⾏扩展。BeanFactory后置处理器 表示针对BeanFactory的处理器,Spring启动过程中,会先创建出BeanFactory实例,然后利⽤ BeanFactory处理器来加⼯BeanFactory,⽐如Spring的扫描就是基于BeanFactory后置处理器来实现的;Bean后置处理器也类似,Spring在创建⼀个Bean的过程中,⾸先会实例化得到⼀个对象,然后再 利⽤Bean后置处理器来对该实例对象进⾏加⼯,⽐如我们常说的依赖注⼊就是基于⼀个Bean后置处理器 来实现的,通过该Bean后置处理器来给实例对象中加了@Autowired注解的属性⾃动赋值,还⽐如我们常 说的AOP,也是利⽤⼀个Bean后置处理器来实现的,基于原实例对象,判断是否需要进⾏AOP,如果需 要,那么就基于原实例对象进⾏动态代理,⽣成⼀个代理对象。
Spring中后置处理器的作⽤?
一、独立运行Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。二、简化配置spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。 三、自动配置Spring Boot能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter\u0002web启动器就能拥有web的功能,无需其他配置。四、无代码生成和XML配置Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一。五、应用监控Spring Boot提供一系列端点可以监控服务及应用,做健康检测。
为什么要用SpringBoot?
1. @SpringBootApplication注解:这个注解标识了⼀个SpringBoot⼯程,它实际上是另外三个注解的组 合,这三个注解是: --a. @SpringBootConfiguration:这个注解实际就是⼀个@Configuration,表示启动类也是⼀个配 置类 --b. @EnableAutoConfiguration:向Spring容器中导⼊了⼀个Selector,⽤来加载ClassPath下 SpringFactories中所定义的⾃动配置类,将这些⾃动加载为配置Bean --c. @ComponentScan:标识扫描路径,因为默认是没有配置实际扫描路径,所以SpringBoot扫描的 路径是启动类所在的当前⽬录 2. @Bean注解:⽤来定义Bean,类似于XML中的<bean>标签,Spring在启动时,会对加了@Bean注解 的⽅法进⾏解析,将⽅法的名字做为beanName,并通过执⾏⽅法得到bean对象 3. @Controller、@Service、@ResponseBody、@Autowired都可以说
说说常⽤的SpringBoot注解,及其实现
1.容器在启动的时候会调用 EnableAutoConfigurationImportSelector.class 的 selectImports方法「获取一个全面的常用 BeanConfiguration 列表」2.之后会读取 spring-boot-autoconfigure.jar 下面的spring.factories,「获取到所有的 Spring 相关的 Bean 的全限定名 ClassName」3.之后继续「调用 filter 来一一筛选」,过滤掉一些我们不需要不符合条件的 Bean4.最后把符合条件的 BeanConfiguration 注入默认的 EnableConfigurationPropertie 类里面的属性值,并且「注入到 IOC 环境当中」
springBoot 自动装配原理?
1. 准备环境,根据不同的环境创建不同的Environment2. 准备、加载上下⽂,为不同的环境选择不同的Spring Context,然后加载资源,配置Bean3. 初始化,这个阶段刷新Spring Context,启动应⽤4. 最后结束流程
最后,说说Spring Boot 启动流程吧?
spring
类加载器⾃顶向下分为:1. Bootstrap ClassLoader启动类加载器:默认会去加载JAVA_HOME/lib⽬录下的jar2. Extention ClassLoader扩展类加载器:默认去加载JAVA_HOME/lib/ext⽬录下的jar3. Application ClassLoader应⽤程序类加载器:⽐如我们的web应⽤,会加载web程序中ClassPath下的类4. User ClassLoader⽤户⾃定义类加载器:由⽤户⾃⼰定义
当我们在加载类的时候,⾸先都会向上询问⾃⼰的⽗加载器是否已经加载,如果没有则依次向上询问,如果没有加载,则从上到下依次尝试是否能加载当前类,直到加载成功。
「好处:」说这个问题前我要先和大家说一个概念,「Jvm 中类的唯一性是由类本身和加载这个类的类加载器决定的」,简单的说,如果有个a类,如果被两个不同的类加载器加载,那么他们必不相等。你看到这里会不会想到所有类的父类都是 Object 是怎么实现的了吗?是因为无论哪一个类加载器加载 Object 类,都会交给最顶层的启动类加载器去加载,这样就「保证了 Object 类在 Jvm 中是唯一的」
「1.程序计数器」程序计数器是「程序控制流的指示器,循环,跳转,异常处理,线程的恢复等工作都需要依赖程序计数器去完成」。程序计数器是「线程私有」的,它的「生命周期是和线程保持一致」的,我们知道,N 个核心数的 CPU 在同一时刻,最多有 N个线程同时运行,在我们真实的使用过程中可能会创建很多线程,JVM 的多线程其实是通过线程轮流切换,分配处理器执行时间来实现的。既然涉及的线程切换,所以每条线程必须有一个独立的程序计数器。
「2.虚拟机栈」虚拟机栈,其描述的就是线程内存模型,「也可以称作线程栈」,也是每个「线程私有」的,「生命周期与线程保持一致」。在每个方法执行的时候,jvm 都会同步创建一个栈帧去存储局部变量表,操作数栈,动态连接,方法出口等信息。一个方法的生命周期就贯彻了一个栈帧从入栈到出栈的全部过程。
「3.本地方法栈」本地方法栈的概念很好理解,我们知道,java底层用了很多c的代码去实现,而其调用c端的方法上都会有native,代表本地方法服务,而本地方法栈是为本地方法服务的
「4.堆」Java中的堆是用来存储对象本身的以及数组(数组引用是存放在Java栈中的)。堆是被所有线程共享的,在JVM中只有一个堆。
「5.方法区」方法区也是所有「线程共享」的区域,它「存储」了被 jvm 加载的「类型信息、常量、静态变量等数据」。运行时常量池就是方法区的一部分,编译期生成的各种字面量与符号引用就存储在常量池中。
「6.直接内存」这部分数据并「不是 jvm 运行时数据区的一部分」,nio 就会使用到直接内存,也可以说「堆外内存」,通常会「配合虚引用一起去使用」,就是为了资源释放,会将堆外内存开辟空间的信息存储到一个队列中,然后GC会去清理这部分空间。堆外内存优势在 IO 操作上,对于网络 IO,使用 Socket 发送数据时,能够节省堆内存到堆外内存的数据拷贝,所以性能更高。看过 Netty 源码的同学应该了解,Netty 使用堆外内存池来实现零拷贝技术。对于磁盘 IO 时,也可以使用内存映射,来提升性能。另外,更重要的几乎不用考虑堆内存烦人的 GC 问题。但是既然是内存。也会受到本机总内存的限制,
说说 JVM 内存区域
启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static voidmain(String[] args)函数的class都可以作为JVM实例运行的起点运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以表明自己创建的线程是守护线程消亡。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出
JVM生命周期
调优命令有哪些?
-Xms2g:初始化推大小为 2g;-Xmx2g:堆最大内存为 2g;-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;-XX:+PrintGC:开启打印 gc 信息;-XX:+PrintGCDetails:打印 gc 详细信息。
常用的 jvm 调优的参数都有哪些?
分析系统系统运行情况:分析GC日志及dump文件,判断是否需要优化,确定瓶颈问题点;确定JVM调优量化目标;确定JVM调优参数(根据历史JVM参数来调整);依次确定调优内存、延迟、吞吐量等指标;对比观察调优前后的差异;不断的分析和调整,直到找到合适的JVM参数配置;找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。
jvm调优步骤?
分两种情况:1.系统还在运行:通过公司的监控平台查看jvm的运行情况,比如gc频率,线程数等等--每次gc回收的垃圾多不,如果回收的多,可以尝试把新生代的内存调大--系统此时的访问量大不大,系统压力如何--每次fullgc只回收一点内存,看下dump文件,看下内存里都是有哪些对象,看下代码是不是有问题,比如说system.gc()具体场景具体分析2.系统挂了:一般都会设置当OOM生成dump文件,然后找运维要dump⽂件(生产机器开发一般没权限);然后把dump文件放到mat工具中进行分析,mat工具挺好用的,会直接提示哪里的代码可能会有问题,先按提示排查问题总之,调优不是⼀蹴⽽就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题
项目如何排查JVM的问题?
我们在前面说明了根可达算法是通过 GC Roots 来找到存活的对象的,也定义了 GC Roots,那么垃圾回收器是怎样寻找GC Roots 的呢?首先,「「为了保证结果的准确性,GC Roots枚举时是要在STW的情况下进行的」」,但是由于 JAVA 应用越来越大,所以也不能逐个检查每个对象是否为 GC Root,那将消耗大量的时间。一个很自然的想法是,能不能用空间换时间,在某个时候把栈上代表引用的位置全部记录下来,这样到真正 GC 的时候就可以直接读取,而不用再一点一点的扫描了。事实上,大部分主流的虚拟机也正是这么做的,比如 HotSpot ,它使用一种叫做 「「OopMap」」 的数据结构来记录这类信息。
垃圾回收器是怎样寻找 GC Roots 的?
进行垃圾回收的过程中,会涉及对象的移动。为了保证对象引用更新的正确性,必须暂停所有的用户线程,像这样的停顿,虚拟机设计者形象描述为「Stop The World」。也简称为STW。
在HotSpot中,有个数据结构(映射表)称为「OopMap」。一旦类加载动作完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,记录到OopMap。在即时编译过程中,也会在「特定的位置」生成 OopMap,记录下栈上和寄存器里哪些位置是引用。
从线程角度看,安全点可以理解成是在「「代码执行过程中」」的一些「「特殊位置」」,当线程执行到这些位置的时候,说明「「虚拟机当前的状态是安全」」的。比如:「「方法调用、循环跳转、异常跳转等这些地方才会产生安全点」」。如果有需要,可以在这个位置暂停,比如发生GC时,需要暂停所有活动线程,但是线程在这个时刻,还没有执行到一个安全点,所以该线程应该继续执行,到达下一个安全点的时候暂停,等待 GC 结束。那么如何让线程在垃圾回收的时候都跑到最近的安全点呢?这里有「「两种方式」」:
「抢先式中断」抢先式中断:就是在stw的时候,先让所有线程「「完全中断」」,如果中断的地方不在安全点上,然后「「再激活」」,「「直到运行到安全点的位置」」再中断。「主动式中断」主动式中断:在安全点的位置打一个标志位,每个线程执行都去轮询这个标志位,如果为真,就在最近的安全点挂起。
这些特定的位置主要在:1.循环的末尾(非 counted 循环)2.方法临返回前 / 调用方法的call指令后3.可能抛异常的位置这些位置就叫作「安全点(safepoint)。」 用户程序执行时并非在代码指令流的任意位置都能够在停顿下来开始垃圾收集,而是必须是执行到安全点才能够暂停。
什么是Stop The World ? 什么是OopMap?什么是安全点?
逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。通俗点讲,如果一个对象的指针被多个方法或者线程引用时,那么我们就称这个对象的指针发生了逃逸。
「逃逸分析的好处」栈上分配,可以降低垃圾收集器运行的频率。同步消除,如果发现某个对象只能从一个线程可访问,那么在这个对象上的操作可以不需要同步。标量替换,把对象分解成一个个基本类型,并且内存分配不再是分配在堆上,而是分配在栈上。这样的好处有,一、减少内存使用,因为不用生成对象头。二、程序内存回收效率高,并且GC频率也会减少。
什么是逃逸分析?
两大原因:(1)Java 文件经过编译后生成和平台无关的 class 文件。(2)而Java虚拟机(JVM)是不跨平台的,java工具会把统一的class文件,加载到对应的JVM,又因为该JVM是和这个系统是对应的,所以就可以运行。
JVM为什么能跨平台?
类加载器(ClassLoader)运行时数据区(Runtime Data Area)执行引擎(Execution Engine)本地库接口(Native Interface)
「Class loader(类装载):」 根据给定的全限定名类名(如:java.lang.Object)来装载class文件到运行时数据区的方法区中。「Execution engine(执行引擎)」:执行class的指令。「Native Interface(本地接口):」 与native lib交互,是其它编程语言交互的接口。「Runtime data area(运行时数据区域)」:即我们常说的JVM的内存。
说一下 jvm 的主要组成部分?及其作用?
Serial是一个「「单线程」」的垃圾回收器,「「采用复制算法负责新生代」」的垃圾回收工作,可以与 CMS 垃圾回收器一起搭配工作。
ParNew:Serial的多线程版本,⽤于和CMS配合使⽤
「1.初始标记」初始标记只是标记出来「「和 GC Roots 直接关联」」的对象,整个速度是非常快的,为了保证标记的准确,这部分会在 「「STW」」 的状态下运行。「2.并发标记」并发标记这个阶段会直接根据第一步关联的对象找到「「所有的引用」」关系,这一部分时刻用户线程「「并发运行」」的,虽然耗时较长,但是不会有很大的影响。「3.重新标记」重新标记是为了解决第二步并发标记所导致的标错情况,这里简单举个例子:并发标记时a没有被任何对象引用,此时垃圾回收器将该对象标位垃圾,在之后的标记过程中,a又被其他对象引用了,这时候如果不进行重新标记就会发生「「误清除」」。这部分内容也是在「「STW」」的情况下去标记的。「4.并发清除」这一步就是最后的清除阶段了,将之前「「真正确认为垃圾的对象回收」」,这部分会和用户线程一起并发执行。
CMS收集器是以获取最短停顿时间为⽬标的收集器,相对于其他的收集器STW的时间更短暂,可以并⾏收集是他的特点,同时他基于标记-清除算法,整个GC的过程分为4步。1. 初始标记:标记GC ROOT能关联到的对象,需要STW2. 并发标记:从GCRoots的直接关联对象开始遍历整个对象图的过程,不需要STW3. 重新标记:为了修正并发标记期间,因⽤户程序继续运作⽽导致标记产⽣改变的标记,需要STW4. 并发清除:清理删除掉标记阶段判断的已经死亡的对象,不需要STW从整个过程来看,并发标记和并发清除的耗时最⻓,但是不需要停⽌⽤户线程,⽽初始标记和重新标记的耗时较短,但是需要停⽌⽤户线程,总体⽽⾔,整个过程造成的停顿时间较短,⼤部分时候是可以和⽤户线程⼀起⼯作的。
G1作为JDK9之后的服务端默认收集器,且不再区分年轻代和⽼年代进⾏垃圾回收,他把内存划分为多个Region,每个Region的⼤⼩可以通过-XX:G1HeapRegionSize设置,⼤⼩为1~32M,对于⼤对象的存储则衍⽣出Humongous的概念,超过Region⼤⼩⼀半的对象会被认为是⼤对象,⽽超过整个Region⼤⼩的对象被认为是超级⼤对象,将会被存储在连续的N个Humongous Region中,G1在进⾏回收的时候会在后台维护⼀个优先级列表,每次根据⽤户设定允许的收集停顿时间优先回收收益最⼤的Region。
总的来说除了并发标记之外,其他⼏个过程也还是需要短暂的STW,G1的⽬标是在停顿和延迟可控的情况下尽可能提⾼吞吐量。
G1的回收过程分为以下四个步骤:1. 初始标记:标记GC ROOT能关联到的对象,需要STW2. 并发标记:从GCRoots的直接关联对象开始遍历整个对象图的过程,扫描完成后还会重新处理并发标记过程中产⽣变动的对象3. 最终标记:短暂暂停⽤户线程,再处理⼀次,需要STW4. 筛选回收:更新Region的统计数据,对每个Region的回收价值和成本排序,根据⽤户设置的停顿时间制定回收计划。再把需要回收的Region中存活对象复制到空的Region,同时清理旧的Region。需要STW
常见的垃圾回收器?
默认是15岁,年龄在对象头中记录
年龄达到15岁
当前放对象的Survivor区域里,一批对象的总大小大于这块区域的50%,那么此时大于等于这批年龄的对象直接进入老年代
动态年龄判断
大对象直接进入老年代
老年代空间分配担保机制
什么情况下内存对象会从新生代进入老年代?
「1.强引用」\"Object o = new Object()\" 就是一种强引用关系,这也是我们在代码中最常用的一种引用关系。无论任何情况下,只要强引用关系还存在,垃圾回收器就不会回收掉被引用的对象。
「2.软引用」当内存空间不足时,就会回收软引用对象。
「3.弱引用」弱引用要比软引用更弱一点,它「「只能够存活到下次垃圾回收之前」」。也就是说,垃圾回收器开始工作,会回收掉所有只被弱引用关联的对象。
「4.虚引用」虚引用是最弱的一种引用关系,它的唯一作用是用来作为一种通知。如零拷贝(Zero Copy),开辟了堆外内存,虚引用在这里使用,会将这部分信息存储到一个队列中,以便于后续对堆外内存的回收管理。
java 有哪四种引用类型?
栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),所在区域不连续,会有碎片。1、功能不同栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。2、共享性不同栈内存是线程私有的。 堆内存是所有线程共有的。3、异常错误不同如果栈内存或者堆内存不足都会抛出异常。 栈空间不足:java.lang.StackOverFlowError。 堆空间不足:java.lang.OutOfMemoryError。 4、空间大小栈的空间大小远远小于堆的。
说一下堆栈的区别?
队列和栈都是被用来预存储数据的。队列允许先进先出检索元素,但也有例外的情况,Deque 接口允许从两端检索元素。栈和队列很相似,但它运行对元素进行后进先出进行检索。
队列和栈是什么?有什么区别?
加载:根据查找路径找到相应的 class 文件然后导入;验证:检查加载的 class 文件的正确性;准备:给类中的静态变量分配内存空间;解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;初始化:对静态变量和静态代码块执行初始化工作,如果存在⽗类,先对⽗类进⾏初始化Ps:静态代码块是绝对线程安全的,只能隐式被java虚拟机在类加载过程中初始化调⽤!
当虚拟机遇⻅new关键字时候,实现判断当前类是否已经加载,如果类没有加载,⾸先执⾏类的加载机制,加载完成后再为对象分配空间、初始化等
1. ⾸先为对象分配合适⼤⼩的内存空间2. 接着为实例变量赋默认值3. 设置对象的头信息,对象hash码、GC分代年龄、元数据信息等4. 执⾏构造函数(init)初始化
当类加载完成之后,紧接着就是对象分配内存空间和初始化的过程
知道new⼀个对象的过程吗?
如何自定义类加载器?
「1.引用计数算法」
这也是「「JVM 默认使用」」的寻找垃圾算法它的原理就是定义了一系列的根,我们把它称为 「「\"GC Roots\"」」 ,从 「「\"GC Roots\"」」 开始往下进行搜索,走过的路径我们把它称为 「「\"引用链\"」」 ,当一个对象到 「「\"GC Roots\"」」 之间没有任何引用链相连时,那么这个对象就可以被当做垃圾回收了。
「2.根可达算法」
垃圾对象是怎么找到的?
两个栈: Java栈 和 Native 栈中所有引用的对象;两个方法区:方法区中的常量和静态变量;所有线程对象;所有跨代引用对象;和已知 GCRoots 对象同属一个CardTable 的其他对象
GC Roots 有哪些?
大多数的垃圾回收器都遵循了分代收集的理论进行设计,它建立在两个分代假说之上:「「弱分代假说」」:绝大多数对象都是朝生夕灭的。「「强分代假说」」:熬过越多次数垃圾回收过程的对象就越难消亡。
这两种假说的设计原则都是相同的:垃圾收集器「「应该将jvm划分出不同的区域」」,把那些较难回收的对象放在一起(一般指老年代),这个区域的垃圾回收频率就可以降低,减少垃圾回收的开销。剩下的区域(一般指新生代)可以用较高的频率去回收,并且只需要去关心那些存活的对象,也不用标记出需要回收的垃圾,这样就能够以较低的代价去完成垃圾回收。
说一说分代收集理论
除了system.gc()外;1.老年代内存不足或者达到阈值2.方法区内存不足3.survivor放不下,老年代也放不下4.young gc时统计得到进入老年代的平均大小,大于老年代的剩余大小
什么时候会触发FullGC?
「1.标记清除算法」这种算法的实现是很简单的,有两种方式1.标记出垃圾,然后清理掉2.标记出存货的对象,回收其他空间
这种算法有两个「缺点」1.随着对象越来越多,那么所需要消耗的时间就会越来越多2.标记清除后会导致碎片化,如果有大对象分配很有可能分配不下而出发另一次的垃圾收集动作
「2.标记复制算法」这种算法解决了第一种算法碎片化的问题。就是「「开辟两块完全相同的区域」」,对象只在其中一篇区域内分配,然后「「标记」」出那些「「存活的对象,按顺序整体移到另外一个空间」」,如下图,可以看到回收后的对象是排列有序的,这种操作只需要移动指针就可以完成,效率很高,「「之后就回收移除前的空间」」。
这种算法的缺点也是很明显的浪费过多的内存,使现有的「「可用空间变为」」原先的「「一半」」
「3.标记整理算法」这种算法可以说是结合了前两种算法,既有标记删除,又有整理功能。
这种算法就是通过标记清除算法找到存活的对象,然后将所有「「存活的对象,向空间的一端移动」」,然后回收掉其他的内存。
垃圾收集算法有哪些?
常见调优工具有哪些
Java 中「「Stop-The-World机制简称 STW」」 ,是在执行垃圾收集算法时,Java 应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。「Java 中一种全局暂停现象,全局停顿」,所有 Java 代码停止,native 代码可以执行,但不能与 JVM 交互。
什么是 STW ?
在 java 应用程序中「「引用关系」」是不断发生「「变化」」的,那么就会有会有很多种情况来导致「「垃圾标识」」出错。想想一下如果 Object a 目前是个垃圾,GC 把它标记为垃圾,但是在清除前又有其他对象指向了 Object a,那么此刻 Object a 又不是垃圾了,那么如果没有 STW 就要去无限维护这种关系来去采集正确的信息。再举个例子,到了秋天,道路上洒满了金色的落叶,环卫工人在打扫街道,却永远也无法打扫干净,因为总会有不断的落叶。
为什么需要 STW?
1.增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof,当 OOM 发生时自动 dump 堆内存信息到指定目录;2.同时 jstat 查看监控 JVM 的内存和 GC 情况,先观察问题大概出在什么区域;3.使用工具载入到 dump 文件,分析大对象的占用情况。
如何排查 OOM 的问题?
安全区域是什么?解决了什么问题
这里我们又提到了一个概念叫做 「「SATB 原始快照」」,关于SATB会延伸出有一个概念,「「三色标记算法」」,也就是垃圾回收器标记垃圾的时候使用的算法,这里我们简单说下:将对象分为「「三种颜色」」:白色:没被 GC 访问过的对象(被 GC 标记完后还是白色代表是垃圾)黑丝:存活的对象灰色:被 GC 访问过的对象,但是对象引用链上至少还有一个引用没被扫描过我们知道在 「「并发标记」」 的时候 「「可能会」」 出现 「「误标」」 的情况,这里举两个例子:1.刚开始标记为 「「垃圾」」 的对象,但是在并发标记过程中 「「变为了存活对象」」2.刚开始标记为 「「存活」」 的对象,但是在并发标记过程中 「「变为了垃圾对象」」第一种情况影响还不算很大,只是相当于垃圾没有清理干净,待下一次清理的时候再清理一下就好了。第二种情况就危险了,正在使 「「用的对象的突然被清理掉」」 了,后果会很严重。那么 「「产生上述第二种情况的原因」」 是什么呢?1.「「新增」」 一条或多条 「「黑色到白色」」 对象的新引用2.删除 「「了」」 灰色 「「对象」」 到该白色对象 「「的直接」」 引用或间接引用。当这两种情况 「「都满足」」 的时候就会出现这种问题了。所以为了解决这个问题,引入了 「「增量更新」」 (Incremental Update)和 「「原始快照」」 (SATB)的方案:增量更新破坏了第一个条件:「「增加新引用时记录」」 该引用信息,在后续 STW 扫描中重新扫描(CMS的使用方案)。原始快照破坏了第二个条件:「「删除引用时记录下来」」,在后续 STW 扫描时将这些记录过的灰色对象为根再扫描一次(G1的使用方案)。
说说三色标记
Java 栈内存溢出可能抛出两种异常,两种异常虽然都发生在栈内存,但是两者导致内存溢出的根本原因是不一样的:1.「如果线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量的时候」,Java 虚拟机将抛出一个 StackOverFlowError 异常。2.如果 Java 虚拟机栈可以动态拓展,并且扩展的动作已经尝试过,但是目前「无法申请到足够的内存去完成拓展,或者在建立新线程的时候没有足够的内存去创建对应的虚拟机栈」,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异常。
什么情况下会发生栈内存溢出?
不一定的,JVM通过「逃逸分析」,那些逃不出方法的对象会在栈上分配。
对象一定分配在堆中吗?有没有了解逃逸分析技术?
说一说对象的栈上分配吧?
一般情况下,JVM的对象都放在堆内存中(发生逃逸分析除外)。当类加载检查通过后,Java虚拟机开始为新生对象分配内存。如果Java堆中内存是绝对规整的,所有被使用过的的内存都被放到一边,空闲的内存放到另外一边,中间放着一个指针作为分界点的指示器,所分配内存仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的实例,这种分配方式就是 指针碰撞。
什么是指针碰撞?
如果Java堆内存中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,不可以进行指针碰撞啦,虚拟机必须维护一个列表,记录哪些内存是可用的,在分配的时候从列表找到一块大的空间分配给对象实例,并更新列表上的记录,这种分配方式就是空闲列表。
什么是空闲列表?
什么是TLAB?
JVM
在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
多线程锁的升级原理是什么?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操作系统层面的一个错误,是进程死锁的简称,最早在 1965 年由 Dijkstra 在研究银行家算法时提出的,它是计算机操作系统乃至整个并发程序设计领域最难处理的问题之一。
什么是死锁?
互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。
怎么防止死锁?
1. 互斥条件:一个资源每次只能被一个线程使用2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放3. 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
产生死锁的四个必要条件?
synchronized是java提供的原⼦性内置锁,这种内置的并且使⽤者看不到的锁也被称为监视器锁,使⽤synchronized之后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令,他依赖操作系统底层互斥锁实现。他的作⽤主要就是实现原⼦性操作和解决共享变量的内存可⻅性问题。执⾏monitorenter指令时会尝试获取对象锁,如果对象没有被锁定或者已经获得了锁,锁的计数器+1。此时其他竞争锁的线程则会进⼊等待队列中。执⾏monitorexit指令时则会把计数器-1,当计数器值为0时,则锁释放,处于等待队列中的线程再继续竞争锁。synchronized是排它锁,当⼀个线程获得锁之后,其他线程必须等待该线程释放锁后才能获得锁,⽽且由于Java中的线程和操作系统原⽣线程是⼀⼀对应的,线程被阻塞或者唤醒时时会从⽤户态切换到内核态,这种转换⾮常消耗性能。从内存语义来说,加锁的过程会清除⼯作内存中的共享变量,再从主内存读取,⽽释放锁的过程则是将⼯作内存中的共享变量写回主内存。
如果再深⼊到源码来说,synchronized实际上有两个队列waitSet和entryList。1. 当多个线程进⼊同步代码块时,⾸先进⼊entryList2. 有⼀个线程获取到monitor锁后,就赋值给当前线程,并且计数器+13. 如果线程调⽤wait⽅法,将释放锁,当前线程置为null,计数器-1,同时进⼊waitSet等待被唤醒,调⽤notify或者notifyAll之后⼜会进⼊entryList竞争锁4. 如果线程执⾏完毕,同样释放锁,计数器-1,当前线程置为null
知道synchronized原理吗?
从JDK1.6版本之后,synchronized本身也在不断优化锁的机制,有些情况下他并不会是⼀个很重量级的锁了。优化机制包括⾃适应锁、⾃旋锁、锁消除、锁粗化、轻量级锁和偏向锁。锁的状态从低到⾼依次为⽆锁->偏向锁->轻量级锁->重量级锁,升级的过程就是从低到⾼,降级在⼀定条件也是有可能发⽣的
⾃旋锁:由于⼤部分时候,锁被占⽤的时间很短,共享变量的锁定时间也很短,所有没有必要挂起线程,⽤户态和内核态的来回上下⽂切换严重影响性能。⾃旋的概念就是让线程执⾏⼀个忙循环,可以理解为就是啥也不⼲,防⽌从⽤户态转⼊内核态,⾃旋锁可以通过设置-XX:+UseSpining来开启,⾃旋的默认次数是10次,可以使⽤-XX:PreBlockSpin设置。当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取
⾃适应锁:⾃适应锁就是⾃适应的⾃旋锁,⾃旋的时间不是固定时间,⽽是由前⼀次在同⼀个锁上的⾃旋时间和锁的持有者状态来决定。
锁消除:锁消除指的是JVM检测到⼀些同步的代码块,完全不存在数据竞争的场景,也就是不需要加锁,就会进⾏锁消除。
锁粗化:锁粗化指的是有很多操作都是对同⼀个对象进⾏加锁,就会把锁的同步范围扩展到整个操作序列之外。
偏向锁:当线程访问同步块获取锁时,会在对象头和栈帧中的锁记录⾥存储偏向锁的线程ID,之后这个线程再次进⼊同步块时都不需要CAS来加锁和解锁了,偏向锁会永远偏向第⼀个获得锁的线程,如果后续没有其他线程获得过这个锁,持有锁的线程就永远不需要进⾏同步,反之,当有其他线程竞争偏向锁时,持有偏向锁的线程就会释放偏向锁。可以⽤过设置-XX:+UseBiasedLocking开启偏向锁。
轻量级锁:JVM的对象的对象头中包含有⼀些锁的标志位,代码进⼊同步块的时候,JVM将会使⽤CAS⽅式来尝试获取锁,如果更新成功则会把对象头中的状态位标记为轻量级锁,如果更新失败,当前线程就尝试⾃旋来获得锁。
简单点说,偏向锁就是通过对象头的偏向线程ID来对⽐,甚⾄都不需要CAS了,⽽轻量级锁主要就是通过CAS修改对象头锁记录和⾃旋来实现,重量级锁则是除了拥有锁的线程其他全部阻塞。
那锁的优化机制了解吗?
CAS叫做CompareAndSwap,⽐较并交换,主要是通过处理器的指令来保证操作的原⼦性,它包含三个操作数:1. 变量内存地址,V表示2. 旧的预期值,A表示3. 准备设置的新值,B表示当执⾏CAS指令时,只有当V等于A时,才会⽤B去更新V的值,否则就不会执⾏更新操作。
CAS的原理呢?
synchronized是java关键字,lock是java类synchronized无法判断是否获取锁的状态,lock可以判断是否获取到锁的状态synchronized会自动释放锁,lock需要手工释放锁synchronized不可中断、非公平锁,lock可中断、公平锁和非公共锁
synchronized 和 ReentrantLock 有什么不同?
分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
什么是分布式锁
处于不同节点上不同的服务,它们可能需要顺序的访问一些资源,这里需要一把分布式的锁。分布式锁具有以下特性:写锁、读锁、时序锁。写锁:在zk上创建的一个临时的无编号的节点。由于是无序编号,在创建时不会自动编号,导致只能客户端有一个客户端得到锁,然后进行写入。读锁:在zk上创建一个临时的有编号的节点,这样即使下次有客户端加入是同时创建相同的节点时,他也会自动编号,也可以获得锁对象,然后对其进行读取。时序锁:在zk上创建的一个临时的有编号的节点根据编号的大小控制锁。
什么是分布式锁?
在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行高可用的获取锁与释放锁高性能的获取锁与释放锁具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)具备锁失效机制,即自动解锁,防止死锁具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
分布式锁应该具备哪些条件
基于数据库的分布式锁基于数据库的锁实现也有两种方式,一是基于数据库表的增删,另一种是基于数据库排他锁。1、基于数据库表的增删:基于数据库表增删是最简单的方式,首先创建一张锁的表主要包含下列字段:类的全路径名+方法名,时间戳等字段。具体的使用方式:当需要锁住某个方法时,往该表中插入一条相关的记录。类的全路径名+方法名是有唯一性约束的,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。执行完毕之后,需要delete该记录。(这里只是简单介绍一下,对于上述方案可以进行优化,如:应用主从数据库,数据之间双向同步;一旦挂掉快速切换到备库上;做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍;使用while循环,直到insert成功再返回成功;记录当前获得锁的机器的主机信息和线程信息,下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了,实现可重入锁)2、基于数据库排他锁:基于MySql的InnoDB引擎,在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁。获得排它锁的线程即可获得分布式锁,当获得锁之后,可以执行方法的业务逻辑,执行完方法之后,释放锁connection.commit()。当某条记录被加上排他锁之后,其他线程无法获取排他锁并被阻塞。3、基于数据库锁的优缺点:上面两种方式都是依赖数据库表,一种是通过表中的记录判断当前是否有锁存在,另外一种是通过数据库的排他锁来实现分布式锁。优点是直接借助数据库,简单容易理解。缺点是操作数据库需要一定的开销,性能问题需要考虑。
一基于数据库的分布式锁
基于Zookeeper的分布式锁基于zookeeper临时有序节点可以实现的分布式锁。每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。 (第三方库有 Curator,Curator提供的InterProcessMutex是分布式锁的实现)Zookeeper实现的分布式锁存在两个个缺点:(1)性能上可能并没有缓存服务那么高,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同步到所有的Follower机器上。(2)zookeeper的并发安全问题:因为可能存在网络抖动,客户端和ZK集群的session连接断了,zk集群以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。
二基于Zookeeper的分布式锁
三基于Redis的分布式锁
从理解的难易程度角度(从低到高)数据库 > 缓存 > Zookeeper从实现的复杂性角度(从低到高)Zookeeper >= 缓存 > 数据库从性能角度(从高到低)缓存 > Zookeeper >= 数据库从可靠性角度(从高到低)Zookeeper > 缓存 > 数据库
分布式锁三种方式的一个比较
锁
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群。
并行和并发有什么区别?
简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。
线程和进程的区别?
wait来自Object类,sleep来自Thread类wait等待的过程中会释放锁,sleep等待的过程中不会释放锁wait必须在同步代码块中使用,sleep可以在任何地方使用wait不需要捕获异常,sleep需要捕获异常
sleep() 和 wait() 有什么区别?
定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。创建Thread子类的实例,即创建了线程对象。调用线程对象的start()方法来启动该线程。
继承Thread类创建线程类,实现run方法
定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。调用线程对象的start()方法来启动该线程。
通过Runnable接口创建线程类,实现run方法
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。使用FutureTask对象作为Thread对象的target创建并启动新线程。调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
通过Callable和Future创建线程,实现call方法
通过线程池进行创建
创建线程有哪几种方式?
1.线程只是实现Runnable接口或Callable接口,还可以继承其他类(有点像接口和抽象类的区别,java是单继承的,但可以实现多个接口)2.实现接口的方式多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形3.如果需要访问当前线程,必须调用Thread.currentThread方法4.继承Thread类的线程类不能再继承其他父类(java单继承决定)因此,一般推荐采用实现接口的方式来创建线程。
四种创建线程的区别?
Runnable接口run方法无返回值,Callable接口call方法有返回值,支持泛型Runnable接口run方法只能抛出运行时异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以捕获异常信息
说一下 runnable 和 callable 有什么区别?
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
线程有哪些状态?
ArrayBlockingQueue 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。LinkedBlockingQueue 一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue 。SynchronousQueue 一个不存储元素的阻塞队列。PriorityBlockingQueue 一个具有优先级的无限阻塞队列。 PriorityBlockingQueue 也是基于最小二叉堆实现DelayQueue只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue 是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
Java线程池中队列常用类型有哪些?
线程安全在三个方面体现:原子性:提供互斥访问,同一时刻只能有一个线程对数据font color=\"#f44336\
在 java 程序中怎么保证多线程的运行安全?
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。
ThreadLocal 有一个「静态内部类 ThreadLocalMap」,ThreadLocalMap 又包含了一个 Entry 数组,「Entry 本身是一个弱引用」,他的 key 是指向 ThreadLocal 的弱引用,「font color=\"#f44336\
但是这样还是会存在内存泄露的问题,假如 key 和 ThreadLocal 对象被回收之后,entry 中就存在 key 为 null ,但是 value 有值的 entry 对象,但是永远没办法被访问到,同样除非线程结束运行。「解决方法就是调用 remove 方法删除 entry 对象」。
ThreadLocal 是什么?有哪些使用场景?
notify可能会导致死锁,而notifyAll则不会任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码font color=\"#f44336\
notify()和 notifyAll()有什么区别?
相⽐synchronized的加锁⽅式来解决共享变量的内存可⻅性问题,volatile就是更轻量的选择,他没有上下⽂切换的额外开销成本。使⽤volatile声明的变量,可以确保值被更新的时候对其他线程⽴刻可⻅(缓存一致性协议)。volatile使⽤内存屏障来保证不会发⽣指令重排,解决了内存可⻅性的问题。
说内存屏障的问题,volatile修饰之后会加⼊不同的内存屏障来保证可⻅性的问题能正确执⾏。这⾥写的屏障基于书中提供的内容,但是实际上由于CPU架构不同,重排序的策略不同,提供的内存屏障也不⼀样,⽐如x86平台上,只有StoreLoad⼀种内存屏障。1. StoreStore屏障,保证上⾯的普通写不和volatile写发⽣重排序2. StoreLoad屏障,保证volatile写与后⾯可能的volatile读写不发⽣重排序3. LoadLoad屏障,禁⽌volatile读与后⾯的普通读重排序4. LoadStore屏障,禁⽌volatile读和后⾯的普通写重排序
volatile原理知道吗?
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:1)保证了不同线程对这个变量进行操作时的font color=\"#f44336\
volatile是什么?可以保证有序性吗?
可以用线程类的join方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。
原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile 就是负责保证可见性的。有序性,是保证线程内串行语义,避免指令重排等。
线程安全需要保证几个基本特征?
线程之间的通信有两种方式:wait和notify或者共享变量
说一下线程之间是如何通信的?
1. AbortPolicy:直接丢弃任务,抛出异常,这是默认策略2. CallerRunsPolicy:只用调用者所在的线程来处理任务3. DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执行当前任务4. DiscardPolicy:直接丢弃任务,也不抛出异常
线程时的拒绝策略有哪些?
简单说一下AQS,AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。如果说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS实际上以双向队列的形式连接所有的Entry,比方说ReentrantLock,所有等待的线程都被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行。AQS定义了对双向队列所有的操作,而只开放了tryLock和tryRelease方法给开发者使用,开发者可以根据自己的实现重写tryLock和tryRelease方法,以实现自己的并发功能。
什么是AQS?
「强引用 StrongReference」Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收垃圾回收器不会回收被引用的对象,哪怕内存不足时,JVM 也会直接抛出 OutOfMemoryError,除非赋值为 null。
「软引用 SoftReference」软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。
「弱引用 WeakReference」弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。
「虚引用 PhantomReference」虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用,NIO 的堆外内存就是靠其管理
介绍一下四种引用类型?
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
线程的 run()和 start()有什么区别?
1. 最⼤线程数maximumPoolSize2. 核⼼线程数corePoolSize3. 活跃时间keepAliveTime4. 阻塞队列workQueue5. 拒绝策略RejectedExecutionHandler
当提交⼀个新任务到线程池时,具体的执⾏流程如下:1. 当我们提交任务,线程池会根据corePoolSize⼤⼩创建若⼲任务数量线程执⾏任务2. 当任务的数量超过corePoolSize数量,后续的任务将会进⼊阻塞队列阻塞排队3. 当阻塞队列也满了之后,那么将会继续创建(maximumPoolSize-corePoolSize)个数量的线程来执⾏任务,如果任务处理完成,maximumPoolSize-corePoolSize额外创建的线程等待keepAliveTime之后被⾃动销毁4. 如果达到maximumPoolSize,阻塞队列还是满的状态,那么将根据不同的拒绝策略对应处理
主要有4种拒绝策略:1. AbortPolicy:直接丢弃任务,抛出异常,这是默认策略2. CallerRunsPolicy:只⽤调⽤者所在的线程来处理任务3. DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执⾏当前任务4. DiscardPolicy:直接丢弃任务,也不抛出异常
线程池的原理/执行流程/拒绝策略?
线程是操作系统调度执行的最小单位,协程是比线程更加轻量级的存在,区别在于协程不是被操作系统内核所管理,而完全被程序进行控制。协程在一个线程中执行,消除了线程切换的开销,性能优势明显。协程不需要多线程的锁机制,具有极高的执行效率
线程和协程的区别?
守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程。
守护线程是什么?
命令: jstack pid
如何查看线程死锁?
明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象
interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。
Java中interrupted 和 isInterruptedd方法的区别?
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程
newFixedThreadPool(int nThreads)
newCachedThreadPool()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
newSingleThreadExecutor()
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
newScheduledThreadPool(int corePoolSize)
创建线程池有哪几种方式?
线程池有5种状态:Running、ShutDown、Stop、Tidying、Terminated。
1.RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0。2.SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。调用线程池的shutdown()方法时,线程池由RUNNING -> SHUTDOWN。3.STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。调用线程池的shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN ) -> STOP。4.TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。因为terminated()在ThreadPoolExecutor类中是空的,所以用户想在线程池变为TIDYING时进行相应的处理;可以通过重载terminated()函数来实现。 当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。5.TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
线程池都有哪些状态?
接收的参数不一样submit有返回值,而execute没有submit方便Exception处理
线程池中 submit()和 execute()方法有什么区别?
对象头包含两部分内容,Mark Word中的内容会随着锁标志位⽽发⽣变化,所以只说存储结构就好了。1. 对象⾃身运⾏时所需的数据,也被称为Mark Word,也就是⽤于轻量级锁和偏向锁的关键点。具体的内容包含对象的hashcode、分代年龄、轻量级锁指针、重量级锁指针、GC标记、偏向锁线程ID、偏向锁时间戳。2. 存储类型指针,也就是指向类的元数据的指针,通过这个指针才能确定对象是属于哪个类的实例。如果是数组的话,则还包含了数组的⻓度(占用4字节)
实例数据: 对象实际数据,对象实际数据包括了对象的所有成员变量,其大小由各个成员变量的大小决定。(这里不包括静态成员变量,因为其是在方法区维护的)
font color=\"#ff0000\
在我们常⽤的Hotspot虚拟机中,对象在内存中布局实际包含3个部分:1. 对象头2. 实例数据3. 对⻬填充
那对象头具体都包含哪些内容?
CAS的缺点主要有3点:ABA问题:ABA的问题指的是在CAS更新的过程中,当读取到的值是A,然后准备赋值的时候仍然是A,但是实际上有可能A的值被改成了B,然后⼜被改回了A,这个CAS更新的漏洞就叫做ABA。只是ABA的问题⼤部分场景下都不影响并发的最终效果。Java中有AtomicStampedReference来解决这个问题,他加⼊了预期标志和更新后标志两个字段,更新时不光检查值,还要检查当前的标志是否等于预期标志,全部相等的话才会更新。循环时间⻓开销⼤:⾃旋CAS的⽅式如果⻓时间不成功,会给CPU带来很⼤的开销。只能保证⼀个共享变量的原⼦操作:只对⼀个共享变量操作可以保证原⼦性,但是多个则不⾏,多个可以通过AtomicReference来处理或者使⽤锁synchronized实现。
那么CAS有什么缺点吗?
多线程环境可以使⽤Collections.synchronizedMap同步加锁的⽅式,还可以使⽤HashTable,但是同步的⽅式显然性能不达标,⽽ConurrentHashMap更适合⾼并发场景使⽤。ConcurrentHashmap在JDK1.7和1.8的版本改动⽐较⼤,1.7使⽤Segment+HashEntry分段锁的⽅式实现,1.8则抛弃了Segment,改为使⽤CAS+synchronized+Node实现,同样也加⼊了红⿊树,避免链表过⻓导致性能的问题
1.7分段锁从结构上说,1.7版本的ConcurrentHashMap采⽤分段锁机制,⾥⾯包含⼀个Segment数组,Segment继承与ReentrantLock,Segment则包含HashEntry的数组,HashEntry本身就是⼀个链表的结构,具有保存key、value的能⼒能指向下⼀个节点的指针。实际上就是相当于每个Segment都是⼀个HashMap,默认的Segment⻓度是16,也就是⽀持16个线程的并发写,Segment之间相互不会受到影响。
put流程1. ⾸先计算hash,遍历node数组,如果node是空的话,就通过CAS+⾃旋的⽅式初始化2. 如果当前数组位置是空则直接通过CAS⾃旋写⼊数据3. 如果hash==MOVED,说明需要扩容,执⾏扩容4. 如果都不满⾜,就使⽤synchronized写⼊数据,写⼊数据同样判断链表、红⿊树,链表写⼊和HashMap的⽅式⼀样,key hash⼀样就覆盖,反之就尾插法,链表⻓度超过8就转换成红⿊树
get查询get很简单,通过key计算hash,如果key hash相同就返回,如果是红⿊树按照红⿊树获取,都不是就遍历链表获取。
1.8CAS+synchronized1.8抛弃分段锁,转为⽤CAS+synchronized来实现,同样HashEntry改为Node,也加⼊了红⿊树的实现
那多线程环境怎么使⽤Map呢?ConcurrentHashmap了解过吗?
JMM 就是 「Java内存模型」(java memory model)。因为在不同的硬件生产商和不同的操作系统下,内存的访问有一定的差异,所以会造成相同的代码运行在不同的系统上会出现各种问题。所以java内存模型(JMM)「屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果」。Java内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。「线程不能直接读写主内存中的变量」。每个线程的工作内存都是独立的,「线程操作数据只能在工作内存中进行,然后刷回到主存」。这是 Java 内存模型定义的线程基本工作方式。
原⼦性:Java内存模型通过read、load、assign、use、store、write来保证原⼦性操作,此外还有lock和unlock,直接对应着synchronized关键字的monitorenter和monitorexit字节码指令。可⻅性:Java保证可⻅性可以认为通过volatile、synchronized、final来实现。有序性:由于处理器和编译器的重排序导致的有序性问题,Java通过volatile、synchronized来保证。
那么说说你对JMM内存模型的理解?为什么需要JMM?
(1)程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。(2)监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。(3)volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。(4)传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。(5)start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。(6)Join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。(7)程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。(8)对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。
happen-before规则
Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。
Atomic系列的类中的核心方法都会调用unsafe类中的几个本地方法。我们需要先知道一个东西就是Unsafe类,全名为:sun.misc.Unsafe,这个类包含了大量的对C代码的操作,包括很多直接内存分配以及原子操作的调用,而它之所以标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐患,需要小心使用,否则会导致严重的后果,例如在通过unsafe分配内存的时候,如果自己指定某些区域可能会导致一些类似C++一样的指针越界到其他进程的问题。
说一下 atomic 的原理?
多线程
1.int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,再任何引用使用前,必须为其指定一个对象,否则会报错。
2.基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另一个数组时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看的见。
虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。使用int的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。
八种基本数据类型的大小,以及他们的封装类?
可以使用StringBuffer调用reverse方法
String中abc怎么变成cba?
当我们使用“+”拼接字符串的时候,实际上是创建了一个StringBuilder对象,并使用该对象的append方法进行字符串拼接,然后将得到的字符串通过toString方法返回给字符串对象。
String它是final修饰怎么进行a+b?
1.被final修饰的类不能被继承2.被final修饰的方法不能被重写3.被final修饰的变量不能被更改,引用类型是引用不能改4.被final修饰的常量会在编译阶段放入常量池
final有哪些用法?
1.静态变量2.静态方法3.静态代码块4.静态内部类5.静态导包
static都有哪些用法?
1、普通内部类和静态内部类的区别:a)普通内部类持有对外部类的引用,静态内部类没有持有外部类的引用。b)普通内部类能够访问外部类的静态和非静态成员,静态内部类不能访问外部类的非静态成员,他只能访问外部类的静态成员。c)一个普通内部类不能脱离外部类实体被创建,且可以访问外部类的数据和方法,因为他就在外部类里面。2、区别还有a)第一,内部类可以访问其所在类的属性(包括所在类的私有属性),内部类创建自身对象需要先创建其所在类的对象b)第二,可以定义内部接口,且可以定义另外一个内部类实现这个内部接口c)第三,可以在方法体内定义一个内部类,方法体内的内部类可以完成一个基于虚方法形式的回调操作d)第四,内部类不能定义static元素e)第五,内部类可以多嵌套f)static内部类是内部类中一个比较特殊的情况,Java文档中是这样描述static内部类的:一旦内部类使用static修饰,那么此时这个内部类就升级为顶类。也就是说,除了写在一个类的内部以外,static内部类具备所有外部类的特性(和外部类完全一样)。
普通内部类和静态内部类的区别?
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
如何将字符串反转?
indexOf():返回指定字符的索引。charAt():返回指定索引处的字符。replace():字符串替换。trim():去除字符串两端空白。split():分割字符串,返回一个分割后的字符串数组。getBytes():返回字符串的 byte 类型数组。length():返回字符串长度。toLowerCase():将字符串转成小写字母。toUpperCase():将字符串转成大写字符。substring():截取字符串。equals():字符串比较。
String 类的常用方法都有那些?
String类中使用Final关键字修饰,对象是不可变的,线程安全的StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的StringBuilder并没有对方法进行加同步锁,所以是非线程安全的
String、Stringuffer、StringBuilder三者之间的区别?
1.接口是抽象类的变体,「接口中所有的方法都是抽象的」。而抽象类是声明方法的存在而不去实现它的类。2.接口可以多继承,抽象类只能单继承。3.接口中方法不能实现,默认是 「public abstract」,而抽象类可以实现部分方法。4.接口中基本数据类型为 「public static final」 并且需要给出初始值,而抽类象不用。
接口和抽象类有什么区别?
重写:发生在父子类中1.参数列表必须「完全与被重写的方法」相同,否则不能称其为重写而是重载.2.「返回的类型必须一致与被重写的方法的返回类型相同」,否则不能称其为重写而是重载。3.访问「修饰符的限制一定要大于被重写方法的访问修饰符」4.重写方法一定「不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常」。
重载:发生在同一类中1.必须具有「不同的参数列表」;2.可以有不同的返回类型,只要参数列表不同就可以了;3.可以有「不同的访问修饰符」;4.可以抛出「不同的异常」;
重载和重写什么区别?
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
BIO、NIO、AIO 有什么区别?
CollectionCollection是单列集合包含List和SetListList包含ArrayList、LinkList和VectorSetSet包含HashSet、LinkedHashSet和TreeSetMapMap包含HashTable、HashMap、LinkedHashMap、WeakHashMap、TreeMap和IdentifyHashMap
java中有哪些集合?
HashMap是非线程安全的,HashTable是线程安全的HashMap的键和值都允许有null值存在,HashTable不允许HashMap效率比HashTable高Hashtable是同步的,HashMap不同步
HashMap 和 Hashtable 有什么区别?
list:有序,可重复set:无序,不可重复Map:无序,它的键不允许重复,它的值允许重复
list、set和Map的区别?
ArrayList:
List,set和Map的实现类
一.给 hashMap 「font color=\"#f44336\
那么 hashMap 线程不安全怎么解决?
在 「hashMap1.7 中扩容」的时候,因为采用的是头插法,所以会可能会有循环链表产生,导致数据有问题,在 1.8 版本已修复,改为了尾插法在任意版本的 hashMap 中,如果在「插入数据时多个线程命中了同一个槽」,可能会有数据覆盖的情况发生,导致线程不安全。
hashMap 线程不安全体现在哪里?
JDK1.8之前数组+链表,JDK1.8之后是数组+链表+红黑树,当链表中的元素超过了8个以后,就会将链表转为红黑树,当红黑数节点小于等于6个的时候又会退化成链表结构首次创建HashMap的长度为16,HashMap中负载因子大小为0.75,所以HashMap中元素个数达到12个时它就会进行扩容,扩容为它的两倍。
HashMap底层原理?
1. 根据Key通过哈希算法与与运算得出数组下标 2. 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是 Node对象)并放⼊该位置 3. 如果数组下标位置元素不为空,则要分情况讨论 --a. 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成Entry对 象,并使⽤头插法添加到当前位置的链表中 --b. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node ---i. 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个过 程中会判断红⿊树中是否存在当前key,如果存在则更新value ---ii. 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插法插 ⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否
说一下HashMap的put方法?
1.根据key通过HashCode方法得出哈希值,并通过哈希算法转换成数组的下标2.判断该数组下标是否有数据,什么都没有就返回null,有它就会拿着参数key和单向列表上的每一个节点的Key进行equals比较,如果为false就返回null,如果其中一个节点返回true,就把该value进行返回。
HashMap的get方法?
ArrayList底层是数组结构,LinkedList底层是链表结构ArrayList查询快,增删慢,LinkedList查询慢,增删快
ArrayList 和 LinkedList 的区别?
Vector是同步的,而ArrayList不同步。 ArrayList比Vector快,它因为有同步,不会过载。 Vector扩容是2倍,ArrayList扩容是1.5倍
ArrayList 和 Vector 的区别是什么?
hashMap 1.7 和 hashMap 1.8 的区别?
ArrayList默认大小是10个元素
ArrayList默认长度?
HashMap默认大小是16个元素
HashMap默认长度?
==比较基本类型:比较的是值,比较引用类型:比较的就是地址值equals比较引用类型比较的是地址值,方法被重写后比较的是类容
== 和 equals 的区别是什么?
如果创建一个list没有指定元素大小,会在add元素的时候会通过ensureCapacityInternal()方法确认大小,一般是指定为10的大小。然后再通过ensureExplicitCapacity()方法确认list的显式大小。如果需要的最小容量大于list集合的大小时,会调用grow()再进行扩容,新的数组大小是原来数组的1.5倍。如果扩容后的新数组大小小于最小容量大小,则将数组的大小设定为最小容量大小。如果扩容后的新数组容量大小大于数组最大容量大小Integer.max.array.value,则调用hugeCapacity方法(),判断所需最小容量大小是否大于Integer.max.array.value。大于返回最大容量大小Integer.max.value。否则将容量大小设置为Integer.max.array.value.
ArrayList底层是怎么进行扩容的?
数组、链表、栈、队列、树、堆、图、哈希表
有哪些数据结构?
进程是程序的⼀次执⾏,是系统进⾏资源分配和调度的独⽴单位,他的作⽤是是程序能够并发执⾏提⾼资源利⽤率和吞吐率。由于进程是资源分配和调度的基本单位,因为进程的创建、销毁、切换产⽣⼤量的时间和空间的开销,进程的数量不能太多,⽽线程是⽐进程更⼩的能独⽴运⾏的基本单位,他是进程的⼀个实体,可以减少程序并发执⾏时的时间和空间开销,使得操作系统具有更好的并发性。线程基本不拥有系统资源,只有⼀些运⾏时必不可少的资源,⽐如程序计数器、寄存器和栈,进程则占有堆、栈。
说说进程和线程的区别?
反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
反射的实现方式:第一步:获取Class对象,有4中方法: 1)Class.forName(“类的路径”); 2)类名.class 3)对象名.getClass() 4)基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象
反射机制的优缺点:优点: 1)能够运行时动态获取类的实例,提高灵活性; 2)与动态编译结合 缺点: 1)使用反射性能较低,需要解析字节码,将内存中的对象进行解析。 解决方案: 1、通过setAccessible(true)关闭JDK的安全检查来提升反射速度; 2、多次创建一个类的实例时,有缓存会快很多 3、ReflectASM工具类,通过字节码生成的方式加快反射速度 2)相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)
反射的作用和原理?
接口的幂等性?
java 是一门开源的跨平台的面向对象的计算机语言.
1.「封装」两层含义:一层含义是把对象的属性和行为看成一个密不可分的整体,将这两者'封装'在一个不可分割的「独立单元」(即对象)中另一层含义指'信息隐藏,把不需要让外界知道的信息隐藏起来,有些对象的属性及行为允许外界用户知道或使用,但不允许更改,而另一些属性或行为,则不允许外界知晓,或只允许使用对象的功能,而尽可能「隐藏对象的功能实现细节」。「优点」:1.良好的封装能够「减少耦合」,符合程序设计追求'高内聚,低耦合'2.「类内部的结构可以自由修改」3.可以对成员变量进行更「精确的控制」4.「隐藏信息」实现细节
2.「继承」继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。「优点」:1.提高类代码的「复用性」2.提高了代码的「维护性」
3.「多态」1.「方法重载」:在一个类中,允许多个方法使用同一个名字,但方法的参数不同,完成的功能也不同。2.「对象多态」:子类对象可以与父类对象进行转换,而且根据其使用的子类不同完成的功能也不同(重写父类的方法)。多态是同一个行为具有多个不同表现形式或形态的能力。Java语言中含有方法重载与对象多态两种形式的多态:「优点」「消除类型之间的耦合关系」「可替换性」「可扩充性」「接口性」「灵活性」「简化性」
面向对象有几个特点:
介绍一下 java 吧
面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发
面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。 但是性能上来说,比面向过程要低。
面向对象和面向过程的区别?
JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。
JDK 和 JRE 有什么区别?
「为什么需要包装类」:基本数据类型方便、简单、高效,但泛型不支持、集合元素不支持不符合面向对象思维包装类提供很多方法,方便使用,如 Integer 类 toHexString(int i)、parseInt(String s) 方法等等
「Java 中有 8 个基本类型,分别对应的 8 个包装类」byte -- Byteboolean -- Booleanshort -- Shortchar -- Characterint -- Integerlong -- Longfloat -- Floatdouble -- Double
什么是包装类?为什么需要包装类?
Integer a = 1000,Integer b = 1000,a==b 的结果是什么?那如果 a,b 都为1,结果又是什么?
3*0.1 == 0.3返回值是什么?
a=a+b与a+=b有什么区别吗?
标识符的含义: 是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等等,都是标识符。命名规则:(硬性要求) 标识符可以包含英文字母,0-9的数字,$以及_ 标识符不能以数字开头 标识符不是关键字命名规范:(非硬性要求) 类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。 变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。 方法名规范:同变量名。
标识符的命名规则?
装箱就是自动将基本数据类型转换为包装器类型(int-->Integer);调用方法:Integer的valueOf(int) 方法拆箱就是自动将包装器类型转换为基本数据类型(Integer-->int)。调用方法:Integer的intValue方法
Java自动装箱与拆箱
1、不管有木有出现异常,finally块中代码都会执行;2、当try和catch中有return时,finally仍然会执行;3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
try catch finally,try里有return,finally还执行么?
java的集合有两类,一类是List,还有一类是Set。前者有序可重复,后者无序不重复。当我们在set中插入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。但是如果元素太多,用这样的方法就会比较满。于是有人发明了哈希算法来提高集合中查找元素的效率。 这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域。hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
hashcode的作用?
为什么重写equals方法也要重写hashcode方法
String,StringBuffer,StringBuilder
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
java中操作字符串都有哪些类,他们的区别?
不一样,因为内存的分配方式不一样。String str=\"i\"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(\"i\") 则会被分到堆内存中。
String str=\"i\"与 String str=new String(\"i\")一样吗?
java 有哪些数据类型?
不需要,抽象类不一定非要有抽象方法。
抽象类必须要有抽象方法吗?
普通类不能包含抽象方法,抽象类可以包含抽象方法。抽象类不能直接实例化,普通类可以直接实例化。
普通类和抽象类有什么区别
Java可抛出(Throwable)的结构分为三种类型:被检查的异常(CheckedException),运行时异常(RuntimeException),错误(Error)。
Excption与Error包结构?
NullPointerException 空指针异常ArrayIndexOutOfBoundsException 索引越界异常InputFormatException 输入类型不匹配SQLException SQL异常IllegalArgumentException 非法参数NumberFormatException 类型转换异常 等等....
常见的异常有哪些?
Java标准库内建了一些通用的异常,这些类以Throwable为顶层父类。Throwable又派生出「Error类和Exception类」。错误:Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。异常:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
处理方法:1.try catch2.throw3.throws
异常要怎么解决?
按功能来分:输入流(input)、输出流(output)。按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
java 中 IO 流分为几种?
Files.exists():检测文件路径是否存在。Files.createFile():创建文件。Files.createDirectory():创建文件夹。Files.delete():删除一个文件或目录。Files.copy():复制文件。Files.move():移动文件。Files.size():查看文件个数。Files.read():读取文件。Files.write():写入文件。
Files的常用方法都有哪些?
List
Queue
Set
Collection
HashMap
Map
java 容器都有哪些?
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
Collection 和 Collections 有什么区别?
List、Set、Map 之间的区别是什么?
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
如何决定使用 HashMap 还是 TreeMap?
HashSet底层由HashMap实现HashSet的值存放于HashMap的key上HashMap的value统一为PRESENT
说一下 HashSet 的实现原理?
Array可以容纳基本类型和对象,而ArrayList只能容纳对象。 Array是指定大小的,而ArrayList大小是固定的。 Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等
Array 和 ArrayList 有何区别?
List转换成为数组:调用ArrayList的toArray方法。数组转换成为List:调用Arrays的asList方法。
如何实现数组和 List 之间的转换?
poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
在 Queue 中 poll()和 remove()有什么区别?
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
迭代器 Iterator 是什么?
Iterator 怎么使用?有什么特点?
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。 Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。 ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
Iterator 和 ListIterator 有什么区别?
介绍一下 hashset 吧
泛型:「把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型」
使用泛型的好处?以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置了Object即可,添加的数据全部都可向上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。
什么是泛型?
因为泛型其实只是在编译器中实现的而虚拟机并不认识泛型类项,所以要在虚拟机中将泛型类型进行擦除。也就是说,「在编译阶段使用泛型,运行阶段取消泛型,即擦除」。擦除是将泛型类型以其父类代替,如String 变成了Object等。其实在使用的时候还是进行带强制类型的转化,只不过这是比较安全的转换,因为在编译阶段已经确保了数据的一致性。
泛型擦除是什么?
有「五种创建对象的方式」1、new关键字Person p1 = new Person();2.Class.newInstancePerson p1 = Person.class.newInstance();3.Constructor.newInstanceConstructor<Person> constructor = Person.class.getConstructor();Person p1 = constructor.newInstance();4.clonePerson p1 = new Person();Person p2 = p1.clone();5.反序列化Person p1 = new Person();byte[] bytes = SerializationUtils.serialize(p1);Person p2 = (Person)SerializationUtils.deserialize(bytes);
创建对象有哪些方式
// 懒汉式public class Singleton {// 延迟加载保证多线程安全 Private volatile static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized(Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; }}使用 volatile 是「防止指令重排序,保证对象可见」,防止读到半初始化状态的对象第一层if(singleton == null) 是为了防止有多个线程同时创建synchronized 是加锁防止多个线程同时进入该方法创建对象第二层if(singleton == null) 是防止有多个线程同时等待锁,一个执行完了后面一个又继续执行的情况
讲讲单例模式懒汉式吧
浅拷贝并不是真的拷贝,只是「复制指向某个对象的指针」,而不复制对象本身,新旧对象还是共享同一块内存。
深拷贝会另外「创造一个一模一样的对象」,新对象跟原对象不共享内存,修改新对象不会改到原对象。
深拷贝、浅拷贝是什么?
Java基础
1.调度中心:负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。
2.执行模块(执行器):负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;接收“调度中心”的执行请求、终止请求和日志请求等。
注:生产中使用xxl-job2.2.0版本。使用注解:@XxlJob(\"userStatisticsTiming\")
1.框架系统组成
调度密集或者耗时任务可能会导致任务阻塞,集群情况下调度组件小概率情况下会重复触发;针对上述情况,可以通过结合 “单机路由策略(如:第一台、一致性哈希)” + “阻塞策略(如:单机串行、丢弃后续调度)” 来规避,最终避免任务重复执行。
1.如何避免任务重复执行
2.分片任务:一个任务在多台服务器上同时都执行,降低任务处理时间,调度器会调用配置的所有机器
3.执行器用的端口和该执行器本身的端口没有关系,在启动时可以指定执行器端口,xxljob会自己启一个服务用于调度,如果不指定该端口,默认为9999
2.问题总结
4.1 老版本自有bug,句柄数过多导致任务调度失败,修改源码修复老版本GULE(shell)模式,调用远程接口时,打开连接,没有关闭资源,随着任务的执行,未关闭的句柄数越来越多,最高为65535,达到后就无法继续调度任务,重启可以解决,但是每隔一段时间就会出现该问题。4.2 任务重复执行,有可能的原因,1.tigger重复调度(老版本使用quartz,quartz本身bug,单台机器也可能出现重复调度)。新版本摒弃quartz,自己解析cron表达式,计算下次执行时间,单机不会重复调度;集群使用行锁避免重复调度(需要mysql的引擎为InnoDB,myisam不支持行锁)2.glue模式,curl调用定时任务接口,会走域名,走ng,ng默认配置请求服务器发生错误或者超时,会尝试调用别的机器,导致重复调用;解决方式,使用BEAN模式配置定时任务
3.遇到的问题
5.1 自己解析cron表达式,自己计算下次调度时间5.2 调度策略:触发任务(tigger)使用线程池,维护了两个线程池(快、慢),正常调度都会走快的线程池(默认 core:10 max:200 queue:1000 丢弃策略:默认,抛出异常),当有任务调度时间超过500ms,并且出现10次,则会将该任务放入慢的线程池,目的是为了不影响其他任务调度
4.新版本特性
1.丰富告警通道2.加入prometheus埋点,记录不同时刻所有的调度任务数3.调度线程池满,默认会抛出异常丢弃任务,我们捕获异常,发送告警4.勾子机制,发布重启时先让线程池中的任务执行完,再关闭服务7.注册和销毁没有使用zk,而是使用了DB,每30s将注册信息写入到DB,admin每30s读取数据库,获取当前可用的执行器;执行器关闭时会调用destory方法,更新数据库,删除对应的记录
5.我们做的改造
为了避免多个服务器同时调度任务, 通过mysql悲观锁实现分布式锁(for update语句)
1 setAutoCommit(false)关闭隐式自动提交事务,2 启动事务select lock for update(排他锁)3 读db任务信息 -> 拉任务到内存时间轮 -> 更新db任务信息4 commit提交事务,同时会释放for update的排他锁(悲观锁)任务处理完毕后,释放悲观锁,准备等待下一次循环。
6.一致性保证
7.如何触发
xxljob
Hbase 适合存储 PB 级别的海量数据,在 PB 级别的数据以及采用廉价 PC 存储的情况下,能在几十到百毫秒内返回数据。这与 Hbase 的极易扩展性息息相关。正式因为 Hbase 良好的扩展性,才为海量数据的存储提供了便利。
1.海量存储
这里的列式存储其实说的是列族存储,Hbase 是根据列族来存储数据的。列族下面可以有非常多的列,列族在创建表的时候就必须指定。
2.列式存储
Hbase 的扩展性主要体现在两个方面,一个是基于上层处理能力(RegionServer)的扩展,一个是基于存储的扩展(HDFS)
通过横向添加 RegionSever 的机器,进行水平扩展,提升 Hbase 上层的处理能力,提升 Hbsae服务更多 Region 的能力
3.极易扩展
由于目前大部分使用 Hbase 的架构,都是采用的廉价 PC,因此单个 IO 的延迟其实并不小,一般在几十到上百 ms 之间。这里说的高并发,主要是在并发的情况下,Hbase 的单个IO 延迟下降并不多。能获得高并发、低延迟的服务。
4.高并发
稀疏主要是针对 Hbase 列的灵活性,在列族中,你可以指定任意多的列,在列数据为空的情况下,是不会占用存储空间的
5.稀疏
1.hbase特点
Hbase 是由 Client、Zookeeper、Master、HRegionServer、HDFS 等几个组件组成,下面来介绍一下几个组件的相关功能:
Client 包含了访问 Hbase 的接口,另外 Client 还维护了对应的 cache 来加速 Hbase 的访问,比如 cache 的.META.元数据的信息
1.client
HBase 通过 Zookeeper 来做 master 的高可用、RegionServer 的监控、元数据的入口以及集群配置的维护等工作。具体工作如下:
1.通过 Zoopkeeper 来保证集群中只有 1 个 master 在运行,如果 master 异常,会通过竞争机制产生新的 master 提供服务
2.通过 Zoopkeeper 来监控 RegionServer 的状态,当 RegionSevrer 有异常的时候,通过回调的形式通知 Master RegionServer 上下线的信息
3.通过 Zoopkeeper 存储元数据的统一入口地址
2.Zookeeper
master 节点的主要职责如下:1.为 RegionServer 分配 Region2.维护整个集群的负载均衡3.维护集群的元数据信息4.发现失效的 Region,并将失效的 Region 分配到正常的 RegionServer 上 5.当 RegionSever 失效的时候,协调对应 Hlog 的拆分
3.Hmaster
HregionServer 直接对接用户的读写请求,是真正的“干活”的节点。它的功能概括如下:1.管理 master 为其分配的 Region2.处理来自客户端的读写请求3.负责和底层 HDFS 的交互,存储数据到 HDFS4.负责 Region 变大以后的拆分5.负责 Storefile 的合并工作
4.HregionServer
HDFS 为 Hbase 提供最终的底层数据存储服务,同时为 HBase 提供高可用(Hlog 存储在HDFS)的支持,具体功能概括如下:提供元数据和表数据的底层分布式存储服务;数据多副本,保证的高可靠和高可用性
5.HDFS
2.hbase架构
1.监控 RegionServer2.处理 RegionServer 故障转移3.处理元数据的变更4.处理 region 的分配或转移 5.在空闲时间进行数据的负载均衡6.通过 Zookeeper 发布自己的位置给客户端
1.HMaster
1.负责存储 HBase 的实际数据2.处理分配给它的 Region3.刷新缓存到 HDFS4.维护 Hlog5.执行压缩6.负责处理 Region 分片
2.RegionServer
1.Write-Ahead logs
Hbase 表的分片,HBase 表会根据 RowKey值被切分成不同的 region 存储在 RegionServer中,在一个 RegionServer 中可以有多个不同的 region。
2.Region
HFile 存储在 Store 中,一个 Store 对应 HBase 表中的一个列族
3.Store
顾名思义,就是内存存储,位于内存中,用来保存当前的数据操作,所以当数据保存在WAL 中之后,RegsionServer 会在内存中存储键值对
4.MemStore
这是在磁盘上保存原始数据的实际的物理文件,是实际的存储文件。StoreFile 是以 Hfile的形式存储在 HDFS 的。
5.HFile
3.其他组件
3.HBase 中的角色
1.进入 HBase 客户端命令行:bin/hbase shell
2.查看当前数据库中有哪些表:list
6.查看表结构:describe ‘student’
8.统计表数据行数:count 'student'
9.删除数据:
10.清空表数据:truncate 'student'提示:清空表的操作顺序为先 disable,然后再 truncate。
首先需要先让该表为 disable 状态:hbase(main):019:0> disable 'student'然后才能 drop 这个表:hbase(main):020:0> drop 'student'提示:如果直接 drop 表,会报错:ERROR: Table student is enabled. Disable it first.
11.删除表
12.变更表信息
4.HBase Shell 操作
1.通过单个 RowKey 访问
2.通过 RowKey 的 range(正则)
3.全表扫描
2.访问 HBASE table 中的行,只有三种方式:
3.RowKey 行键 (RowKey)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为10-100bytes),在 HBASE 内部,RowKey 保存为字节数组。存储时,数据按照 RowKey 的字典序(byte order)排序存储。设计 RowKey 时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)
1.RowKey
列族:HBASE 表中的每个列,都归属于某个列族。列族是表的 schema 的一部 分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。例如 courses:history,courses:math都属于 courses 这个列族。
2.Column Family:列族
关键字:无类型、字节码
3.Cell
HBASE 中通过 rowkey和 columns 确定的为一个存贮单元称为cell。每个 cell都保存 着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64 位整型。时间戳可以由 HBASE(在数据写入时自动 )赋值,此时时间戳是精确到毫秒 的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版 本冲突,就必须自己生成具有唯一性的时间戳。每个 cell 中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
版本回收:为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,HBASE 提供 了两种数据版本回收方式。一是保存数据的最后 n 个版本,二是保存最近一段 时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。
4.Time Stamp
1.Table:表,所有的表都是命名空间的成员,即表必属于某个命名空间,如果没有指定,则在 default 默认的命名空间中。
2.RegionServer group:一个命名空间包含了默认的 RegionServer Group
3.Permission:权限,命名空间能够让我们来定义访问控制列表 ACL(Access Control List)。例如,创建表,读取表,删除,更新等等操作。
4.Quota:限额,可以强制一个命名空间可包含的 region 的数量。
5.命名空间(HBase NameSpaces)
5.HBase 数据结构
1.Client 先访问 zookeeper,从 meta 表读取 region 的位置,然后读取 meta 表中的数据。meta中又存储了用户表的 region 信息
2.根据 namespace、表名和 rowkey 在 meta 表中找到对应的 region 信息;
3.找到这个 region 对应的 regionserver;
4.查找对应的 region;
5.先从 MemStore 找数据,如果没有,再到 BlockCache 里面读;
6.BlockCache 还没有,再到 StoreFile 上读(为了读取的效率);
7.如果是从 StoreFile 里面读取的数据,不是直接返回给客户端,而是先写入 BlockCache,再返回给客户端
1.读流程
1.Client 向 HregionServer 发送写请求;
2.HregionServer 将数据写到 HLog(write ahead log)。为了数据的持久化和恢复;
3.HregionServer 将数据写到内存(MemStore);
4.反馈 Client 写成功。
2.写流程
1.当 MemStore 数据达到阈值(默认是 128M,老版本是 64M),将数据刷到硬盘,将内存中的数据删除,同时删除 HLog 中的历史数据;
2.并将数据存储到 HDFS 中;
3.在 HLog 中做标记点。
3.数据 flush 过程
1.当数据块达到 4 块,Hmaster 触发合并操作,Region 将数据块加载到本地,进行合并;
2.当合并的数据超过 256M,进行拆分,将拆分后的 Region 分配给不同的 HregionServer管理
3.当HregionServer宕机后,将HregionServer上的hlog拆分,然后分配给不同的HregionServer加载,修改.META.;
4.注意:HLog 会同步到 HDFS。
4.数据合并过程
6.Hbase原理
在 HBase 中 Hmaster 负责监控 RegionServer 的生命周期,均衡 RegionServer 的负载,如果 Hmaster 挂掉了,那么整个 HBase 集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以 HBase 支持对 Hmaster 的高可用配置
1.关闭 HBase 集群(如果没有开启则跳过此步)bin/stop-hbase.sh
2.在 conf 目录下创建 backup-masters 文件 touch conf/backup-masters
3.在 backup-masters 文件中配置高可用 HMaster 节点:echo hadoop103 > conf/backup-masters
4.将整个 conf 目录 scp 到其他节点:[atguigu@hadoop102 hbase]$ scp -r conf/ hadoop103:/opt/module/hbase/[atguigu@hadoop102 hbase]$ scp -r conf/ hadoop104:/opt/module/hbase/
5.打开页面测试查看: http://hadooo102:16010
1.高可用
每一个 region 维护着 startRow 与 endRowKey,如果加入的数据符合某个 region 维护的rowKey 范围,则该数据交给这个 region 维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高 HBase 性能。
2.预分区
一条数据的唯一标识就是 rowkey,那么这条数据存储于哪个分区,取决于 rowkey 处于哪个一个预分区的区间内,设计 rowkey的主要目的 ,就是让数据均匀的分布于所有的 region中,在一定程度上防止数据倾斜。接下来我们就谈一谈 rowkey 常用的设计方案
比如:原 本 rowKey 为 1001 的 , SHA1 后变成:dd01903921ea24941c26a48f2cec24e0bb0e8cc7原 本 rowKey 为 3001 的 , SHA1 后变成:49042c54de64a1e9bf0b33e00245660ef92dc7bd原 本 rowKey 为 5001 的 , SHA1 后变成:7b61dec07e02c188790670af43e717f0f46e8913在做此操作之前,一般我们会选择从数据集中抽取样本,来决定什么样的 rowKey 来 Hash后作为每个分区的临界值
1.生成随机数、hash、散列值
20170524000001 转成 1000004250710220170524000002 转成 20000042507102这样也可以在一定程度上散列逐步 put 进来的数据。
2.字符串反转
20170524000001_a12e20170524000001_93i7
3.字符串拼接
如果RowKey是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将RowKey的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer实现负载均衡的几率,如果没有散列字段,首字段直接是时间信息,将产生所有数据都在一个RegionServer上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer,降低查询效率。
RowKey散列原则
RowKey是按照字典排序存储的,因此,设计RowKey时候,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。
举个例子:如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为RowKey的一部分,由于是字段排序,所以可以使用Long.MAX_VALUE-timeStamp作为RowKey,这样能保证新写入的数据在读取时可以别快速命中。
RowKey唯一原则
3.RowKey 设计
HBase 操作过程中需要大量的内存开销,毕竟 Table 是可以缓存在内存中的,一般会分配整个可用内存的 70%给 HBase 的 Java 堆。但是不建议分配非常大的堆内存,因为 GC 过程持续太久会导致 RegionServer 处于长期不可用状态,一般 16~48G 内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。
4.内存优化
1.允许在 HDFS 的文件中追加内容
2.优化 DataNode 允许的最大文件打开数
3.优化延迟高的数据操作的等待时间
4.优化数据的写入效率
5.设置 RPC 监听数量
6.优化 HStore 文件大小
7.优化 hbase 客户端缓存
8.指定 scan.next 扫描 HBase 所获取的行数
9.flush、compact、split 机制
5.基础优化
7.HBase优化
hbase
3.时间复杂度:O(N^2)
1.冒泡排序
2.交换第一个索|处和最小值所在的索引|处的值
2.选择排序
4.时间复杂度:O(N^2)
3.插入排序
2.对分好组的每一组数据完成插入排序;
4.希尔排序
4.时间复杂度:O(nlogn)
5.归并排序
6.快速排序
1.java算法
2.雪花算法
3.Leaf算法(美团)
数据结构与算法
为了增强通⽤性和兼容性,计算机⽹络都被设计成层次机构,每⼀层都遵守⼀定的规则。因此有了OSI这样⼀个抽象的⽹络通信参考模型,按照这个标准使计算机⽹络系统可以互相连接。物理层:通过⽹线、光缆等这种物理⽅式将电脑连接起来。传递的数据是⽐特流,0101010100。数据链路层: ⾸先,把⽐特流封装成数据帧的格式,对0、1进⾏分组。电脑连接起来之后,数据都经过⽹卡来传输,⽽⽹卡上定义了全世界唯⼀的MAC地址。然后再通过⼴播的形式向局域⽹内所有电脑发送数据,再根据数据中MAC地址和⾃身对⽐判断是否是发给⾃⼰的。⽹络层:⼴播的形式太低效,为了区分哪些MAC地址属于同⼀个⼦⽹,⽹络层定义了IP和⼦⽹掩码,通过对IP和⼦⽹掩码进⾏与运算就知道是否是同⼀个⼦⽹,再通过路由器和交换机进⾏传输。IP协议属于⽹络层的协议。传输层:有了⽹络层的MAC+IP地址之后,为了确定数据包是从哪个进程发送过来的,就需要端⼝号,通过端⼝来建⽴通信,⽐如TCP和UDP属于这⼀层的协议。会话层:负责建⽴和断开连接表示层:为了使得数据能够被其他的计算机理解,再次将数据转换成另外⼀种格式,⽐如⽂字、视频、图⽚等。应⽤层:最⾼层,⾯对⽤户,提供计算机⽹络与最终呈现给⽤户的界⾯
TCP/IP则是四层的结构,相当于是对OSI模型的简化。1. 数据链路层,也有称作⽹络访问层、⽹络接⼝层。他包含了OSI模型的物理层和数据链路层,把电脑连接起来。2. ⽹络层,也叫做IP层,处理IP数据包的传输、路由,建⽴主机间的通信。3. 传输层,就是为两台主机设备提供端到端的通信。4. 应⽤层,包含OSI的会话层、表示层和应⽤层,提供了⼀些常⽤的协议规范,⽐如FTP、SMPT、HTTP等。
总结下来,就是物理层通过物理⼿段把电脑连接起来,数据链路层则对⽐特流的数据进⾏分组,⽹络层来建⽴主机到主机的通信,传输层建⽴端⼝到端⼝的通信,应⽤层最终负责建⽴连接,数据格式转换,最终呈现给⽤户。
谈⼀谈你对TCP/IP四层模型,OSI七层模型的理解?
建⽴连接前server端需要监听端⼝,所以初始状态是LISTEN。1. client端建⽴连接,发送⼀个SYN同步包,发送之后状态变成SYN_SENT2. server端收到SYN之后,同意建⽴连接,返回⼀个ACK响应,同时也会给client发送⼀个SYN包,发送完成之后状态变为SYN_RCVD3. client端收到server的ACK之后,状态变为ESTABLISHED,返回ACK给server端。server收到之后状态也变为ESTABLISHED,连接建⽴完成。
说说TCP 3次握⼿的过程?
因为TCP是双⼯传输模式,不区分客户端和服务端,连接的建⽴是双向的过程。如果只有两次,⽆法做到双向连接的建⽴,从建⽴连接server回复的SYN和ACK合并成⼀次可以看出来,他也不需要4次。挥⼿为什么要四次?因为挥⼿的ACK和FIN不能同时发送,因为数据发送的截⽌时间不同。
为什么要3次?2次,4次不⾏吗?
1. client端向server发送FIN包,进⼊FIN_WAIT_1状态,这代表client端已经没有数据要发送了2. server端收到之后,返回⼀个ACK,进⼊CLOSE_WAIT等待关闭的状态,因为server端可能还有没有发送完成的数据3. 等到server端数据都发送完毕之后,server端就向client发送FIN,进⼊LAST_ACK状态4. client收到ACK之后,进⼊TIME_WAIT的状态,同时回复ACK,server收到之后直接进⼊CLOSED状态,连接关闭。但是client要等待2MSL(报⽂最⼤⽣存时间)的时间,才会进⼊CLOSED状态。
那么四次挥⼿的过程呢?
1. 为了保证连接的可靠关闭。如果server没有收到最后⼀个ACK,那么就会重发FIN。2. 为了避免端⼝重⽤带来的数据混淆。如果client直接进⼊CLOSED状态,⼜⽤相同端⼝号向server建⽴⼀个连接,上⼀次连接的部分数据在⽹络中延迟到达server,数据就可能发⽣混淆了。
第四次挥手为什么要等待2MSL(60s)
校验和:发送⽅在发送数据之前计算校验和,接收⽅收到数据后同样计算,如果不⼀致,那么传输有误。确认应答,序列号:TCP进⾏传输时数据都进⾏了编号,每次接收⽅返回ACK都有确认序列号。超时重传:如果发送⽅发送数据⼀段时间后没有收到ACK,那么就重发数据。连接管理:三次握⼿和四次挥⼿的过程。流量控制:TCP协议报头包含16位的窗⼝⼤⼩,接收⽅会在返回ACK时同时把⾃⼰的即时窗⼝填⼊,发送⽅就根据报⽂中窗⼝的⼤⼩控制发送速度。拥塞控制:刚开始发送数据的时候,拥塞窗⼝是1,以后每次收到ACK,则拥塞窗⼝+1,然后将拥塞窗⼝和收到的窗⼝取较⼩值作为实际发送的窗⼝,如果发⽣超时重传,拥塞窗⼝重置为1。这样做的⽬的就是为了保证传输过程的⾼效性和可靠性。
TCP怎么保证传输过程的可靠性?
DNS:这是最简单的负载均衡的⽅式,⼀般⽤于实现地理级别的负载均衡,不同地域的⽤户通过DNS的解析可以返回不同的IP地址,这种⽅式的负载均衡简单,但是扩展性太差,控制权在域名服务商。Http重定向:通过修改Http响应头的Location达到负载均衡的⽬的,Http的302重定向。这种⽅式对性能有影响,⽽且增加请求耗时。反向代理:作⽤于应⽤层的模式,也被称作为七层负载均衡,⽐如常⻅的Nginx,性能⼀般可以达到万级。这种⽅式部署简单,成本低,⽽且容易扩展。IP:作⽤于⽹络层的和传输层的模式,也被称作四层负载均衡,通过对数据包的IP地址和端⼝进⾏修改来达到负载均衡的效果。常⻅的有LVS(Linux Virtual Server),通常性能可以⽀持10万级并发。按照类型来划分的话,还可以分成DNS负载均衡、硬件负载均衡、软件负载均衡。其中硬件负载均衡价格昂贵,性能最好,能达到百万级,软件负载均衡包括Nginx、LVS这种。
负载均衡有哪些实现⽅式?
BIO:同步阻塞IO,每⼀个客户端连接,服务端都会对应⼀个处理线程,对于没有分配到处理线程的连接就会被阻塞或者拒绝。相当于是⼀个连接⼀个线程。
NIO:同步⾮阻塞IO,基于Reactor模型,客户端和channel进⾏通信,channel可以进⾏读写操作,通过多路复⽤器selector来轮询注册在其上的channel,⽽后再进⾏IO操作。这样的话,在进⾏IO操作的时候再⽤⼀个线程去处理就可以了,也就是⼀个请求⼀个线程。
AIO:异步⾮阻塞IO,相⽐NIO更进⼀步,完全由操作系统来完成请求的处理,然后通知服务端开启线程去进⾏处理,因此是⼀个有效请求⼀个线程。
说说BIO/NIO/AIO的区别?
⾸先,可以认为⼀个IO操作包含两个部分:1. 发起IO请求2. 实际的IO读写操作同步和异步在于第⼆个,实际的IO读写操作,如果操作系统帮你完成了再通知你,那就是异步,否则都叫做同步。
阻塞和⾮阻塞在于第⼀个,发起IO请求,对于NIO来说通过channel发起IO操作请求后,其实就返回了,所以是⾮阻塞。
那么你怎么理解同步和阻塞?
Reactor模型包含两个组件:1. Reactor:负责查询、响应IO事件,当检测到IO事件时,分发给Handlers处理。2. Handler:与IO事件绑定,负责IO事件的处理
单线程Reactor这个模式reactor和handler在⼀个线程中,如果某个handler阻塞的话,会导致其他所有的handler⽆法执⾏,⽽且⽆法充分利⽤多核的性能。
单Reactor多线程由于decode、compute、encode的操作并⾮IO的操作,多线程Reactor的思路就是充分发挥多核的特性,同时把⾮IO的操作剥离开。但是,单个Reactor承担了所有的事件监听、响应⼯作,如果连接过多,还是可能存在性能问题。
多Reactor多线程为了解决单Reactor的性能问题,就产⽣了多Reactor的模式。其中mainReactor建⽴连接,多个subReactor则负责数据读写。
谈⼀下你对Reactor模型的理解?
HTTP 协议是基于 TCP 协议实现的,它是一个超文本传输协议,其实就是一个简单的请求-响应协议,它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。它主要是负责点对点之间通信的。超文本就是用超链接的方法,将各种不同空间的文字信息组织在一起的网状文本。比如说html,内部定义了很多图片视频的链接,放在浏览器上就呈现出了画面。协议就是约定俗称的东西,比如说 moon 要给读者送一本书,读者那里只接受顺丰快递,那么 moon 觉得可以,发快递的时候选择的顺丰,那么我们彼此之间共同约定好的就叫做协议。传输这个就很好理解了,比如刚才举的例子,将书发给读者,要通过骑车或者飞机的方式,传递的这个过程就是运输。
介绍一下 HTTP 协议吧
GET 和 POST 本质上就是 TCP 链接,并无差别。但是由于 HTTP 的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
GET 和 POST有什么区别?
PING 主要的作用就是测试在两台主机之间能否建立连接,如果 PING 不通就无法建立连接。它其实就是向目的主机发送多个 ICMP 回送请求报文如果没有响应则无法建立连接如果有响应就可以根据目的主机返回的回送报文的时间和成功响应的次数估算出数据包往返时间及丢包率
PING 的作用?
1xx 信息,服务器收到请求,需要请求者继续执行操作2xx 成功,操作被成功接收并处理3xx 重定向,需要进一步的操作以完成请求4xx 客户端错误,请求包含语法错误或无法完成请求5xx 服务器错误,服务器在处理请求的过程中发生了错误
常见的 HTTP 状态码有哪些
HTTP1.1 和 HTTP1.0 的区别有哪些?
1.SSL安全协议HTTP 是超⽂本传输协议,信息是明⽂传输,存在安全⻛险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在TCP 和 HTTP ⽹络层之间加⼊了 SSL/TLS 安全协议,使得报⽂能够加密传输。2.建立连接HTTP 连接建⽴相对简单, TCP 三次握⼿之后便可进⾏ HTTP 的报⽂传输。HTTPS 在 TCP 三次握⼿之后,还需进⾏ SSL/TLS 的握⼿过程,才可进⼊加密报⽂传输。3.端口号HTTP 的端⼝号是 80。HTTPS 的端⼝号是 443。4.CA证书HTTPS 协议需要向 CA(证书权威。机构)申请数字证书来保证服务器的身份是可信的。
HTTPS 和 HTTP 的区别是什么?
1. ⽤户通过浏览器请求https⽹站,服务器收到请求,选择浏览器⽀持的加密和hash算法,同时返回数字证书给浏览器,包含颁发机构、⽹址、公钥、证书有效期等信息。2. 浏览器对证书的内容进⾏校验,如果有问题,则会有⼀个提示警告。否则,就⽣成⼀个随机数X,同时使⽤证书中的公钥进⾏加密,并且发送给服务器。3. 服务器收到之后,使⽤私钥解密,得到随机数X,然后使⽤X对⽹⻚内容进⾏加密,返回给浏览器4. 浏览器则使⽤X和之前约定的加密算法进⾏解密,得到最终的⽹⻚内容
知道HTTPS的⼯作原理吗?
1.头部压缩在 HTTP2 当中,如果你发出了多个请求,并且它们的头部(header)是相同的,那么 HTTP2 协议会帮你消除同样的部分。(其实就是在客户端和服务端维护一张索引表来实现)2.二进制格式HTTP1.1 采用明文的形式HTTP/2 全⾯采⽤了⼆进制格式,头信息和数据体都是⼆进制3.数据流HTTP/2 的数据包不是按顺序发送的,同⼀个连接⾥⾯连续的数据包,可能属于不同的回应。(对数据包做了标记,标志其属于哪一个请求,其中规定客户端发出的数据流编号为奇数,服务器发出的数据流编号为偶数。客户端还可以指定数据流的优先级,优先级⾼的请求,服务器就先响应该请求)4.IO多路复用如:在⼀个连接中,服务器收到了客户端 A 和 B 的两个请求,但是发现在处理 A 的过程中⾮常耗时,索性就先回应 A 已经处理好的部分,再接着回应 B 请求,最后再回应 A 请求剩下的部分。HTTP/2 可以在⼀个连接中并发多个请求或回应。5.服务器推送服务器可以主动向客户端发送请求
HTTP2 和 HTTP1.1 的区别是什么?
1.协议不同HTTP2 是基于 TCP 协议实现的HTTP3 是基于 UDP 协议实现的2.QUICHTTP3 新增了 QUIC 协议来实现可靠性的传输3.握手次数HTTP2 是基于 HTTPS 实现的,建立连接需要先进行 TCP 3次握手,然后再进行 TLS 3次握手,总共6次握手HTTP3 只需要 QUIC 的3次握手
HTTP3 和 HTTP2 的区别是什么?
TCP 滑动窗⼝是什么?
TCP:面向链接,UDP:源端和终端不需要建立链接
对系统资源的要求(TCP较多,UDP少)
UDP程序结构较简单;
流模式与数据报模式 ;
TCP保证数据正确性,UDP可能丢包
TCP保证数据顺序,UDP不保证。
TCP和UDP的区别
如果接收方处理不过来,发送方就会触发重试机制再次发送数据,然而这个是有性能损耗的,为了解决这个问题,TCP 就提出了流量控制,为的就是让发送方知道接受方的处理能力。也就是说,每次接收方接受到数据后会将剩余可处理数据的大小告诉发送方。比如接受方滑动窗口可用大小为400字节,发送方发送过来100字节的数据,那么接收方剩余可用滑动窗口大小就为300字节,这是发送方就知道下次返送数据的大小范围了。但是这里有一个问题,数据会存放在缓冲区,但是这个缓冲区是操作系统控制的,当系统繁忙的时候,会缩减缓冲区减小,可能就会造成丢包的问题。
发送方一直发送数据,但是接收方处理不过来怎么办?(流量控制)
服务端收到客户端发出的 SYN 请求后,会把这个连接信息存储到半链接队列(SYN 队列)。服务端收到第三次握⼿的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到全连接队列(accept 队列),等待进程调⽤ accept 函数时把连接取出来。这两个队列都是有大小限制的,当超过容量后就会将链接丢弃,或者返回 RST 包。
TCP 半连接队列和全连接队列是什么?
TCP 发送数据时会根据 TCP 缓冲区的实际情况进行包的划分,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是 TCP 粘包和拆包问题。
发生 TCP 粘包的原因:1.发送的数据小于 TCP 缓冲区大小,TCP将缓冲区中的数据(数据属于多条业务内容)一次发送出去可能就会发生粘包。2.接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。发生 TCP 拆包的原因:1.待发送数据大于最大报文长度,TCP 在传输前将进行拆包。2.发送的数据大于 TCP 发送缓冲区剩余空间大小,将会发生拆包。
1.发送端给每个数据包添加包首部,首部中包含数据包的长度,这样接收端在接收到数据后,通过该字段就可以知道每个数据包的实际长度了。2.发送端将每个数据包设置固定长度,这样接收端每次从读取固定长度的数据把每个数据包拆分开。3.可以在数据包之间设置边界,如添加特殊符号,接收端可以通过这个特殊符号来拆分包。
粘包/拆包是怎么发生的?怎么解决这个问题?
浏览器地址栏输入网站按回车后发生了什么?
网络
SpringSecurity安全框架
tar:创建一个新的tar文件 创建:tar cvf 文件名 路径 解压:tar xvf 文件名 查看:tar tvf 文件名grep:在文件中查找字符串find:查找指定文件名的文件ssh:登录到远程主机vim:打开文件sort:以升序对文件内容排序ls:以易读的方式显示文件大小pwd:输出当前工作目录ps:显示正在运行中的进程的信息top:当前系统中占用资源最最多的一些进程
linux常用命令
vim命令
ps [-Aau] 查看当前系统后台运行状况 -A 查看所有进程 -au 显示所有包含其他使用者的进程 -aux a所有进程 u用户 x 不连续的终端,系统中 -le 查看系统中所有进程,使用Linux标准命令格式
ps命令
grep -iv [指定字串] [文件/搜索内容] 在文件中搜索字串匹配的行并输出 -i 不区分大小写 -v 排除指定字串 ^# 表示#不管在前面、中间、后面都排除 -n 输出行号 --color=auto 搜索出的关键字用
grep命令
linux
复刻(fork) 是对存储仓库(repository)进行的远程的、服务器端的拷贝,从源头上就有所区别。复刻实际上不是 Git 的范畴。它更像是个政治/社会概念。克隆(clone) 不是复刻,克隆是个对某个远程仓库的本地拷贝。克隆时,实际上是拷贝整个源存储仓库,包括所有历史记录和分支。分支(branch) 是一种机制,用于处理单一存储仓库中的变更,并最终目的是用于与其他部分代码合并。
什么是 Git 复刻(fork)?复刻(fork)、分支(branch)和克隆(clone)之间有什么区别?
分支(branch) 是代码的一个独立版本。拉取请求(pull request) 是当有人用仓库,建立了自己的分支,做了些修改并合并到该分支(把自己修改应用到别人的代码仓库)。
“拉取请求(pull request)”和“分支(branch)”之间有什么区别?
当你使用 pull,Git 会试着自动为你完成工作。它是上下文(工作环境)敏感的,所以 Git 会把所有拉取的提交合并到你当前处理的分支中。pull 则是 自动合并提交而没有让你复查的过程。如果你没有细心管理你的分支,你可能会频繁遇到冲突。当你 fetch,Git 会收集目标分支中的所有不存在的提交,并将这些提交存储到本地仓库中。但Git 不会把这些提交合并到当前分支中。这种处理逻辑在当你需要保持仓库更新,在更新文件时又希望处理可能中断的事情时,这将非常实用。而将提交合并到主分支中,则该使用 merge。
“git pull”和“git fetch”之间有什么区别?
要知道某个分支是否已合并为master,你可以使用以下命令:git branch –merged 它列出了已合并到当前分支的分支。git branch –no-merged 它列出了尚未合并的分支。
如果分支是否已合并为master,你可以通过什么手段知道?
功能分支(Feature branching)要素分支模型将特定要素的所有更改保留在分支内。当通过自动化测试对功能进行全面测试和验证时,该分支将合并到主服务器中。任务分支(Task branching)在此模型中,每个任务都在其自己的分支上实现,任务键包含在分支名称中。很容易看出哪个代码实现了哪个任务,只需在分支名称中查找任务键。发布分支(Release branching)一旦开发分支获得了足够的发布功能,你就可以克隆该分支来形成发布分支。创建该分支将会启动下一个发布周期,所以在此之后不能再添加任何新功能,只有错误修复,文档生成和其他面向发布的任务应该包含在此分支中。一旦准备好发布,该版本将合并到主服务器并标记版本号。此外,它还应该再将自发布以来已经取得的进展合并回开发分支。
描述一下你所使用的分支策略?
要创建存储库,先为项目创建一个目录(如果该目录不存在),然后运行命令 git init。通过运行此命令,将在项目的目录中创建 .git 目录。
如何在Git中创建存储库?
1.新增文件的命令:git add file或者git add .2.提交文件的命令:git commit –m或者git commit –a3.查看工作区状况:git status –s4.拉取合并远程分支的操作:git fetch/git merge或者git pull5.查看提交记录命令:git reflog6.创建仓库:git init7.查看仓库的状态:git status8.这次相较上次修改了哪些内容:git diff9.将添加的文件放到栈存区中:git add10.将栈存区内容提交到代码区中:git commit11.将远程仓库的代码克隆到本地:git clone git地址12.查看当前分支:git branch13.切换分支:git checkout
列举工作中常用的git命令
因为在合并分支的时候,master分支和dev分支恰好有人都修改了同一个文件,GIT不知道应该以哪一个人的文件为准,所以就产生了冲突了。我是直接在js代码中手动解决冲突,将冲突的代码进行手动合并
可以先用git reflog查看历史提交记录1.软撤销 本地代码不会变化,只是 git 转改会恢复为 commit 之前的状态 不删除工作空间改动代码,撤销 commit,不撤销 git add . git reset --soft HEAD~1 //表示撤销最后一次的 commit ,1 可以换成其他更早的数字2.硬撤销 本地代码会直接变更为指定的提交版本,慎用 删除工作空间改动代码,撤销 commit,撤销 git add . git reset --hard HEAD~1 //注意完成这个操作后,就恢复到了上一次的commit状态 如果仅仅是 commit 的消息内容填错了 输入git commit --amend 进入 vim 模式,对 message 进行更改 还有一个 --mixed git reset --mixed HEAD~1 意思是:不删除工作空间改动代码,撤销commit,并且撤销git add . 操作 这个为默认参数,git reset --mixed HEAD~1 和 git reset HEAD~1 效果是一样的。
1.首先确保本地仓库与远程之间是连同的。如果提交失败,则需要进行下面的命令进行连通:git remote add origin XXXX 注意:XXXX是你的远程仓库地址。2.如果是第一次推送,则进行下面命令:git push -u origin master 注意:-u 是指定origin为默认主分支3.之后的提交,只需要下面的命令: git push origin master
如何把本地仓库的内容推向一个空的远程仓库?
首先在github上创建一个项目,然后为小组成员添加权限,给小组长搭好框架,把本地项目上传到远程项目 ,新建一个开发分支,所有成员都切换到开发分支中,在开发分支中进行开发,最后小组长将开发分支合并到master分支上,并解决一些冲突
请说一下团队内部协作开发的流程
首先需要在其他团队里申请到访问权限,然后在其他的团队那里克隆一下文件,然后在本地运行这个文件,创建一个分支,在这个分支里做进一步的操作,最后合并分支解决冲突提交到远程仓库就行
请说一下远程跨团队协作开发的流程
mster 主分支用来发布dev 日常开发用的分支test 测试用的分支分支最后会由小组长合并
你所参与的多人协同开发时候,项目都有哪些分支,分支名是什么,每个分支代表什么,以及分支是由谁合并
方案一:用命令行将本地仓库推送到远程仓库。首先先创建一个远程仓库待用,然后在本地建立git版本管理,在项目目录下进行以下操作git init 项目目录下会多一个.git文件git add . 添加所有文件git commit -m“first commit” 提交文件git remote add origin //添加到远程仓库git push -u origin master //第一次推送到远程仓库需要 -u,将本地master推送到远程master,以后就直接Git push 就好了方案二:用命令行创建一个新的仓库如果已经拉取了git仓库,那我们只需要在该仓库下进行项目的新建等操作即可首先先创建一个本地文件然后git init 初始化gitgit add 文件 添加本地文件到暂存区git commit -m“first commit” 提交文件git remote add origin //添加到远程仓库git push -u origin master //第一次推送到远程仓库需要 -u,将本地master推送到远程master,以后就直接Git push 就好了
请写出将工作区文件推送到远程仓库的思路?
Git
总的来说,Kafka是分为三个角色:Producer、Kafka集群以及Consumer,生产者将消息发送到Kafka集群,然后消费者再去Kafka集群进行消息的消费
Consumer Group:消费者组,消费者组内每个消费者负责消费不同分区的数据,提高消费能力。逻辑上的一个订阅者。Topic:可以理解为一个队列,Topic 将消息分类,生产者和消费者面向的是同一个 Topic。Partition:为了实现扩展性,提高并发能力,一个Topic 以多个Partition的方式分布到多个 Broker上,每个 Partition 是一个 有序的队列。一个 Topic 的每个Partition都有若干个副本(Replica),一个Leader 和若干个 Follower。生产者发送数据的对象,以及消费者消费数据的对象,都是 Leader。Follower负责实时从 Leader 中同步数据,保持和 Leader 数据的同步。Leader 发生故障时,某个Follower 还会成为新的 Leader。Offset:消费者消费的位置信息,监控数据消费到什么位置,当消费者挂掉再重新恢复的时候,可以从消费位置继续消费。Zookeeper:Kafka 集群能够正常工作,需要依赖于 Zookeeper,Zookeeper 帮助 Kafka 存储和管理集群信息。
简述Kafka的架构设计?
消费者:自动commit;解决方案:手动commit
broker:刷盘解决方案:减小刷盘间隔
丢失的情况:生产者消费者broker
Kafka什么时候会出现消息丢失以及解决方案?
pull,默认一次最多500条
Kafka是pull还是push?
/brokers/ids:临时节点,保存所有broker节点信息,存储broker的物理地址、版本信息、启动时间等,节点名称为brokerID,broker定时发送心跳到zk,如果断开则该brokerID会被删除/brokers/topics:临时节点,节点保存broker节点下所有的topic信息,每一个topic节点下包含一个固定的partitions节点,partitions的子节点就是topic的分区,每个分区下保存一个state节点、保存着当前leader分区和ISR的brokerID,state节点由leader创建,若leader宕机该节点会被删除,直到有新的leader选举产生、重新生成state节点/consumers/[group_id]/owners/[topic]/[broker_id-partition_id]:维护消费者和分区的注册关系/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id]:分区消息的消费进度Offsetclient通过topic找到topic树下的state节点、获取leader的brokerID,到broker树中找到broker的物理地址,但是client不会直连zk,而是通过配置的broker获取到zk中的信息
简单的说就是zk负责存储broker信息,topic信息,partitions信息,消费者和分区的注册关系,分区的消费进度
Kafka中zk的作用?
rebalance(重平衡)其实就是重新进行 partition 的分配,从而使得 partition 的分配重新达到平衡状态
触发条件:当消费者组内的消费者数量发生变化(增加或者减少),就会产生重新分配patition分区数量发生变化时(即 topic 的分区数量发生变化时)
Kafka的reblance机制?
kafka不基于内存,而是硬盘存储,因此消息堆积能力更强
顺序写:利用磁盘的顺序访问速度可以接近内存,kafka的消息都是append操作,partition是有序的,节省了磁盘的寻道时间,同时通过批量操作、节省写入次数,partition物理上分为多个segment存储,方便删除
对应零拷贝技术有mmap及sendfilemmap:小文件传输快sendfile:大文件传输比mmap快
零拷贝:直接将内核缓冲区的数据发送到网卡传输使用的是操作系统的指令支持
kafka不太依赖jvm,主要理由操作系统的pageCache,如果生产消费速率相当,则直接用pageCache交换数据,不需要经过磁盘IO
Kafka的性能好在哪里?
简单来说,就是搞⼀个 acceptor 线程,基于底层操作系统的⽀持,实现连接请求监听。 如果有某个设备发送了建⽴连接的请求过来,那么那个线程就把这个建⽴好的连接交给 processor 线程。 每个 processor 线程会被分配 N 多个连接,⼀个线程就可以负责维持 N 多个连接,他同样会基 于底层操作系统的⽀持监听 N 多连接的请求。 如果某个连接发送了请求过来,那么这个 processor 线程就会把请求放到⼀个请求队列⾥去。 接着后台有⼀个线程池,这个线程池⾥有⼯作线程,会从请求队列⾥获取请求,处理请求,接 着将请求对应的响应放到每个 processor 线程对应的⼀个响应队列⾥去。 最后,processor 线程会把⾃⼰的响应队列⾥的响应发送回给客户端。
Reactor多路复用
Kafka如何应对大量的客户端连接?
ISR 全称是 “In-Sync Replicas”,也就是保持同步的副本,他的含义就是,跟 Leader 始终保持同 步的 Follower 有哪些
Kafka的ISR机制是什么?
batch机制:多条消息打包成一个batch(默认16kb的大小)
request机制:多个batch打包成一个request
Kafka与生产者的网络通信优化?
Kafka
1、数据库中存放文件路径,所有文件保存在 MINIO 中,文件名即是文件的 MD5。2、当用户上传文件时,首先判断该文件信息是否存在在数据库中,如果存在则直接显示上传成功(急速上传),若不存在则执行上传操作。3、文件在真正上传之前先判断文件大小,太小的不需要创建分片上传任务,一次性上传即可。4、后台调用 MINIO 的 API 创建分片上传任务(得到一个任务 ID ),并为该任务生成分片上传链接(上传地址列表)后返回给前端,前端将对应分片按照到对应的连接传递到 MINIO 中。5、分片上传成功后更新进度信息。6、所有分片上传结束后,调用 MINIO 的 API 将当前任务的分片全部合并形成整个文件。
MinIO怎么做分片上传?
MinIO怎么做断点续传?
MinIO如何保证高可用?
MinIO
Eureka作为SpringCloud的服务注册功能服务器,他是服务注册中心,系统中的其他服务使用Eureka的客户端将其连接到Eureka Service中,并且保持心跳,这样工作人员可以通过EurekaService来监控各个微服务是否运行正常。
什么是Eureka?
集群吧,注册多台Eureka,然后把SpringCloud服务互相注册,客户端从Eureka获取信息时,按照Eureka的顺序来访问。
Eureka怎么实现高可用?
默认情况下,如果Eureka Service在一定时间内没有接收到某个微服务的心跳,Eureka Service会进入自我保护模式,在该模式下Eureka Service会保护服务注册表中的信息,不在删除注册表中的数据,当网络故障恢复后,Eureka Servic 节点会自动退出自我保护模式
什么是Eureka的自我保护模式?
可以从注册中心中根据服务别名获取注册的服务器信息。
DiscoveryClient的作用?
客户端向服务端注册Eureka它的注册请求会携带哪些信息?
Eureka它的服务端和客户端是如何保持续约的?
eureka
优先级消费者Method>提供者method>消费者Reference>提供者Service>消费者全局配置provider>提供者全局配置consumer。1:在服务提供者的实现累加 timeout的是ms@DubboService(timeout = 10000)public class ReportConfigServiceImpl implements ReportConfigService {在消费者调用方加@DubboReference(timeout = 10000)ReportConfigService reportConfigService;2:全局配置生产者设置超时时间dubbo.provider.timeout = 10000消费者设置超时时间dubbo.consumer.timeout = 10000
dubbo怎么设置超时时间?
dubbo
1.各服务启动的时候去zk注册信息(创建的都是临时节点)
2.客户端获取当前在线的服务列表,并注册监听事件
3.如果服务器节点下线
4.发送服务器节点上下线事件通知
5.客户端重新再去获取服务器列表,并注册监听
1.zookeeper工作机制
1.Zookeeper: 一个领导者(Leader) ,多个跟随者(Follower) 组成的集群。
2.集群中只要有半数以上节点存活,Zookeeper集 群就能正常服务
4.更新请求顺序进行,来自同一个Clent的更新请求按其发送顺序依次执行。
5.数据更新原子性,一次数据更新要么成功,要么失败。
6.实时性,在一定时间范围内,Client 能读到最新数据。
2.zookeeper的特点
3.zookeeper的数据结构
1.统一命名服务
2.统一集群管理、统一配置管理
3.服务动态上下线
4.软负载均衡
4.zookeeper的应用场景
Zookeeper中的配置文件zoo.cfg中
Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。(session的最小超时时间是2*tickTime)
1.tickTime =2000:通信心跳数,Zookeeper 服务器与客户端心跳时间,单位毫秒
集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器接到Leader的时限。
2.initLimit =10:LF 初始通信时限
集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
3.syncLimit =5:LF 同步通信时限
主要用于保存 Zookeeper 中的数据。
4.dataDir:数据文件目录+数据持久化路径
5.clientPort =2181:客户端连接端口
5.配置参数解读
如果你了解过2PC协议的话,理解起来就简单很多了,消息广播的过程实际上是一个简化版本的二阶段提交过程。通俗的理解就比较简单了,我是领导,我要向各位传达指令,不过传达之前我先问一下大家支不支持我,若有一半以上的人支持我,那我就向各位传达指令了
1.Leader将客户端的request转化成一个Proposal(提议),leader首先把proposal发送到FIFO队列里
2.FIFO取出队头proposal给Follower;Follower反馈一个ACK给队列;队列把ACK交给leader
3.leader收到半数以上ACK,就会发送commit指令给FIFO队列;FIFO队列把commit给Follower。
1.消息广播模式
leader就是一个领导,既然领导挂了,整个组织肯定不会散架,毕竟离开谁都能活下去是不是,这时候我们只需要选举一个新的领导即可,而且还要把前leader还未完成的工作做完,也就是说不仅要进行leader服务器选取,而且还要进行崩溃恢复。我们一个一个来解决
1.leader选举
既然要恢复,有些场景是不能恢复的,ZAB协议崩溃恢复要求满足如下2个要求:第一:确保已经被leader 提交的proposal必须最终被所有的follower服务器提交。第二:确保丢弃已经被leader出的但是没有被提交的proposal。
2.崩溃恢复
2.崩溃恢复模式
1.Zab协议
持久(Persistent):客户端和服务器端断开连接后,创建的节点不删除
短暂(Ephemeral):客户端和服务器端断开连接后,创建的节点自己删除
说明:创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护
注意:在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序
2.节点类型
1.半数机制:集群中半数以上机器存活,集群可用。所以 Zookeeper 适合安装奇数台服务器
2.Zookeeper 虽然在配置文件中并没有指定 Master 和 Slave。但是,Zookeeper 工作时,是有一个节点为 Leader,其他则为 Follower,Leader 是通过内部的选举机制临时产生的。
场景:假设有五台服务器组成的 Zookeeper 集群,它们的 id 从 1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么
1.服务器 1 启动,发起一次选举。服务器 1 投自己一票。此时服务器 1 票数一票,不够半数以上(3 票),选举无法完成,服务器 1 状态保持为 LOOKING
2.服务器 2 启动,再发起一次选举。服务器 1 和 2 分别投自己一票并交换选票信息:此时服务器 1 发现服务器 2 的 ID 比自己目前投票推举的(服务器 1)大,更改选票为推举服务器 2。此时服务器 1 票数 0 票,服务器 2 票数 2 票,没有半数以上结果,选举无法完成,服务器 1,2 状态保持 LOOKING
3.服务器 3 启动,发起一次选举。此时服务器 1 和 2 都会更改选票为服务器 3。此次投票结果:服务器 1 为 0 票,服务器 2 为 0 票,服务器 3 为 3 票。此时服务器 3 的票数已经超过半数,服务器 3 当选 Leader。服务器 1,2 更改状态为 FOLLOWING,服务器 3 更改状态为 LEADING;
4.服务器 4 启动,发起一次选举。此时服务器 1,2,3 已经不是 LOOKING 状态,不会更改选票信息。交换选票信息结果:服务器 3 为 3 票,服务器 4 为 1 票。此时服务器 4服从多数,更改选票信息为服务器 3,并更改状态为 FOLLOWING;
5.服务器 5 启动,同 4 一样当小弟。
3.选举机制举例
3.选举机制(面试重点)
1.首先要有一个main()线程
2.在main线程中创建Zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connet),一个负责监听(listener)
3.通过connect线程将注册的监听事件发送给Zookeeper。
4.在Zookeeper的注册监听器列表中将注册的监听事件添加到列表中。
5.Zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程。
6.listener线程内部调用了process()方法。
1.监听节点数据的变化 get path[watch]
2.监听子节点增减的变化 ls path[watch]
注:常见的监听
4.监听器原理(面试重点)
1.Client向ZooKeeper的Server1上写数据,发送一个写请求。
2.如果Server1不是Leader,那么Server1会把接受到的请求进一步转发给Leader,因为每个ZooKeeper的Server里面有一个是Leader。这个Leader会将写请求广播给各个Server,比如Server1和Server2,各个Server会将该写请求加入待写队列,并向Leader发送成功信息。
3.当Leader收到半数以上Server的成功信息,说明该写操作可以执行。Leader会向各个Server发送提交信息,各个Server收到信息后会落实队列里的写请求,此时写成功。
4.Server1会进一步通知Client数据写成功了,这时就认为整个写操作成功。
5.写数据流程
6.zk内部原理
ls path [watch] :使用 ls 命令来查看当前 znode 中所包含的内容
ls2 path [watch]:查看当前节点数据并能看到更新次数等数据
普通创建-s 含有序列-e 临时(重启或者超时消失)
create:
get path [watch]:获得节点的值
set:设置节点的具体值
stat:查看节点状态
delete:删除节点
rmr:递归删除节点
7.zk的常用命令
5. ZooKeeper保证的是CP,Eureka保证的是AP;CAP: C:一致性>Consistency; 取舍:(强一致性、单调一致性、会话一致性、最终一致性、弱一致性) A:可用性>Availability; P:分区容错性>Partition tolerance;
zookeeper
Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持,可以方便的对微服务各个环境下的配置进行集中式管理。Spring Cloud Config分为Config Server和Config Client两部分。Config Server负责读取配置文件,并且暴露Http API接口,Config Client通过调用ConfigServer的接口来读取配置文件。
1.什么是Spring Cloud Config?
Apollo、zookeeper、springcloud config、nacos
2.分布式配置中心有那些框架?
动态变更项目配置信息而不必重新部署项目。
3.
springcloud config实时刷新采用SpringCloud Bus消息总线。
4.SpringCloud Config 可以实现实时刷新吗?
Config
Feign 是一个声明web服务客户端,这使得编写web服务客户端更容易
他将我们需要调用的服务方法定义成抽象方法保存在本地就可以了,不需要自己构建Http请求了,直接调用接口就行了,不过要注意,调用方法要和本地抽象方法的签名完全一致。
什么是Feign?
RestTemplate
Feign
SpringCloud有几种调用接口方式?
1.调用方式同:Ribbon需要我们自己构建Http请求,模拟Http请求然后通过RestTemplate发给其他服务,步骤相当繁琐
2.而Feign则是在Ribbon的基础上进行了一次改进,采用接口的形式,将我们需要调用的服务方法定义成抽象方法保存在本地就可以了,不需要自己构建Http请求了,直接调用接口就行了,不过要注意,调用方法要和本地抽象方法的签名完全一致。
Ribbon和Feign调用服务的区别?
Feign的远程调用的实现流程?
添加feigh客户端依赖启动类上添加feign注解:@EnableFeignClientsapplication.yml配置文件添加配置信息创建一个feign客户端,用于引入远程模块的接口:@FeignClient在需要进行远程调用的方法里注入该接口,并调用对应的api接口方法
Feign使用步骤?
- none:不记录任何日志信息,这是默认值。- basic:仅记录请求的方法,URL以及响应状态码和执行时间- headers:在BASIC的基础上,额外记录了请求和响应的头信息- full:记录所有请求和响应的明细,包括头信息、请求体、元数据。
Feign的四种日志级别?
它默认使用的是URLConnection,它是没有连接池的我们只需要使用HttpClient或者OKHttp带连接池的替换掉它就好了日志级别尽量使用basic步骤:1.引入feign-httpClient依赖2.配置文件开启httpClient功能,设置连接池参数,参数有:最大的连接数,每个路径的最大连接数
Feign的调优
当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)
断路器有三种状态:打开状态:一段时间内 达到一定的次数无法调用 并且多次监测没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务半开状态:短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭关闭状态:当服务一直处于正常状态 能正常调用
么是断路器?
在分布式系统,我们一定会依赖各种服务,那么这些个服务一定会出现失败的情况,就会导致雪崩,Hystrix就是这样的一个工具,防雪崩利器,它具有服务降级,服务熔断,服务隔离,监控等一些防止雪崩的技术。
服务熔断:接口调用失败就会进入调用接口提前定义好的一个熔断的方法,返回错误信息
服务降级:接口调用失败就调用本地的方法返回一个空
服务隔离:隔离服务之间相互影响
Hystrix有四种防雪崩方式:
什么是 Hystrix?
雪崩效应是在大型互联网项目中,当某个服务发生宕机时,调用这个服务的其他服务也会发生宕机,大型项目的微服务之间的调用是互通的,这样就会将服务的不可用逐步扩大到各个其他服务中,从而使整个项目的服务宕机崩溃.发生雪崩效应的原因有以下几点
1.单个服务的代码存在bug. 2请求访问量激增导致服务发生崩溃(如大型商城的枪红包,秒杀功能). 3.服务器的硬件故障也会导致部分服务不可用.
谈谈服务雪崩效应?
在微服务中,如何保护服务?
服务降级:当客户端请求服务器端的时候,防止客户端一直等待,不会处理业务逻辑代码,直接返回一个友好的提示给客户端
服务熔断:是在服务降级的基础上更直接的一种保护方式,当在一个统计时间范围内的请求失败数量达到设定值(requestVolumeThreshold)或当前的请求错误率达到设定的错误率阈值(errorThresholdPercentage)时开启断路,之后的请求直接走fallback方法,在设定时间(sleepWindowInMilliseconds)后尝试恢复。
服务隔离:就是Hystrix为隔离的服务开启一个独立的线程池,这样在高并发的情况下不会影响其他服务。服务隔离有线程池和信号量两种实现方式,一般使用线程池方式
谈谈服务降级、服务熔断、服务隔离
Hystrix实现服务降级的功能是通过重写HystrixCommand中的getFallback()方法,当Hystrix的run方法或construct执行发生错误时转而执行getFallback()方法。
服务降级底层是如何实现的?
Hystrix使用场景?
Hystrix
简单来说: 先将集群,集群就是把一个的事情交给多个人去做,假如要做1000个产品给一个人做要10天,我叫10个人做就是一天,这就是集群,负载均衡的话就是用来控制集群,他把做的最多的人让他慢慢做休息会,把做的最少的人让他加量让他做多点。
在计算中,负载平衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间并避免任何单一资源的过载。使用多个组件进行负载平衡而不是单个组件可能会通过冗余来提高可靠性和可用性。负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务器进程。
1.负载平衡的意义什么?
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法Ribbon客户端组件提供一系列完善的配置项,如连接超时,重试等。简单的说,就是在配置文件中列出后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随即连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。(有点类似Nginx)
2.Ribbon是什么?
答:Nginx是反向代理同时可以实现负载均衡,nginx拦截客户端请求采用负载均衡策略根据upstream配置进行转发,相当于请求通过nginx服务器进行转发。Ribbon是客户端负载均衡,从注册中心读取目标服务器信息,然后客户端采用轮询策略对服务直接访问,全程在客户端操作。
3.Nginx与Ribbon的区别?
答:Ribbon使用discoveryClient从注册中心读取目标服务信息,对同一接口请求进行计数,使用%取余算法获取目标服务集群索引,返回获取到的目标服务信息。
4.Ribbon底层实现原理?
答:开启客户端负载均衡
5.@LoadBalanced注解的作用?
Ribbon
Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关作为流量的,在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用。
使用了一个RouteLocatorBuilder的bean去创建路由,除了创建路由RouteLocatorBuilder可以让你添加各种predicates和filters,predicates断言的意思,顾名思义就是根据具体的请求的规则,由具体的route去处理,filters是各种过滤器,用来对请求做各种判断和修改。
gateway介绍
统一管理微服务请求,权限控制、负载均衡、路由转发、监控、安全控制黑名单和白名单等
网关的作用是什么?
对外暴露,权限校验,服务聚合,日志审计等
网关的应用场景有哪些?
网关是对所有服务的请求进行分析过滤,过滤器是对单个服务而言。
网关与过滤器有什么区别?
通过path配置拦截请求,通过ServiceId到配置中心获取转发的服务列表,gateway内部使用Ribbon实现本地负载均衡和转发。
如何实现动态gateway网关路由转发
1.客户端携带用户密码登录之后,在服务端进行账号密码的验证。
2.如果登录成功,则根据用户密码生成token,同时将token写入redis缓存,设置过期时间。
3.客户端携带token请求业务系统,经过网关服务,从redis缓存中读取并对token进行验证;
4.token验证成功,则路由到业务系统;验证失败则返回权限校验失败的错误码401;
gateway如何实现鉴权检验?
在 Spring Cloud Gateway 上实现限流是个不错的选择,只需要编写一个过滤器就可以了。Spring Cloud Gateway 已经内置了一个RequestRateLimiterGatewayFilterFactory,我们可以直接使用。目前RequestRateLimiterGatewayFilterFactory的实现依赖于 Redis,所以我们还要引入spring-boot-starter-data-redis-reactive。
在上面的配置文件,配置了 redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:burstCapacity:令牌桶总容量。replenishRate:令牌桶每秒填充平均速率。key-resolver:用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
可以按照path接口地址限流、可以根据ip来限流或者自定义限流器
配置文件
持续高速访问某个路径,速度过快时,返回 HTTP ERROR 429 。
gateway网关如何进行限流?
自定义全局过滤器实现IP访问限制(黑白名单)
黑名单实际可以去数据库或者redis中查询
思路:获取客户端ip,判断是否在⿊名单中,在的话就拒绝访问,不在的话就放⾏ // 从上下⽂中取出request和response对象// 从request对象中获取客户端ip// 拿着clientIp去⿊名单中查询,存在的话就决绝访问
gateway网关如何进行黑名单、白名单设置?
gateway的过滤器
gateway的过滤器使用场景
gateway网关中有哪些必须的配置?
gateway网关
在高并发场景的架构里,幂等性是必须得保证的。比如说支付功能,用户发起支付,如果后台没有做幂等校验,刚好用户手抖多点了几下,于是后台就可能多次受到同一个订单请求,不做幂等很容易就让用户重复支付了,这样用户是肯定不能忍的。
分布式幂等性如何设计?
1、 DNS 解析(通过访问的域名找出其 IP 地址,递归搜索)。 2、HTTP 请求,当输入一个请求时,建立一个 Socket 连接发起 TCP的 3 次握手。如果是 HTTPS 请求,会略微有不同。等到 HTTPS 小节,我们在来讲。3.1、客户端向服务器发送请求命令(一般是 GET 或 POST 请求)。这个是补充内容,面试一般不用回答。客户端的网络层不用关心应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器,这些都是由路由器来完成的工作,我不作过多的描述,无非就是通过查找路由表决定通过那个路径到达服务器。客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定 IP 地址的 MAC 地址,然后发送 ARP 请求查找目的地址,如果得到回应后就可以使用 ARP 的请求应答交换的 IP 数据包现在就可以传输了,然后发送 IP 数据包到达服务器的地址。3.2、客户端发送请求头信息和数据。4.1、服务器发送应答头信息。4.2、服务器向客户端发送数据。5、服务器关闭 TCP 连接(4次挥手)。这里是否关闭 TCP 连接,也根据 HTTP Keep-Alive 机制有关。同时,客户端也可以主动发起关闭 TCP 连接。6、客户端根据返回的 HTML 、 CSS 、 JS 进行渲染。
简单一次完整的 HTTP 请求所经历的步骤?
指数据库事务正确执行的四个基本要素:1. 原子性(Atomicity) 2. 一致性(Consistency) 3. 隔离性(Isolation) 4. 持久性(Durability)
ACID
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容忍性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。一致性:在分布式系统中的所有数据备份,在同一时刻是否同样的值。可用性:在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。分区容忍性:以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
CAP
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。Basically Available(基本可用)Soft state(软状态)Eventually consistent(最终一致性)
BASE理论
说说你对分布式事务的了解
两阶段提交2PC是分布式事务中最强大的事务类型之一,两段提交就是分两个阶段提交:第一阶段询问各个事务数据源是否准备好。第二阶段才真正将数据提交给事务数据源。为了保证该事务可以满足ACID,就要引入一个协调者(Cooradinator)。其他的节点被称为参与者(Participant)。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务进行提交。
阶段一a) 协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待答复。b) 各参与者执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。c) 如参与者执行成功,给协调者反馈 yes,否则反馈 no。阶段二如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(rollback)消息;否则,发送提交(commit)消息。两种情况处理如下:情况1:当所有参与者均反馈 yes,提交事务a) 协调者向所有参与者发出正式提交事务的请求(即 commit 请求)。b) 参与者执行 commit 请求,并释放整个事务期间占用的资源。c) 各参与者向协调者反馈 ack(应答)完成的消息。d) 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。情况2:当有一个参与者反馈 no,回滚事务a) 协调者向所有参与者发出回滚请求(即 rollback 请求)。b) 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。c) 各参与者向协调者反馈 ack 完成的消息。d) 协调者收到所有参与者反馈的 ack 消息后,即完成事务。
问题1) 性能问题:所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易导致性能瓶颈。2) 可靠性问题:如果协调者存在单点故障问题,或出现故障,提供者将一直处于锁定状态。3) 数据一致性问题:在阶段 2 中,如果出现协调者和参与者都挂了的情况,有可能导致数据不一致。优点:尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)。缺点:实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
什么是二阶段提交?
三阶段提交是在二阶段提交上的改进版本,3PC最关键要解决的就是协调者和参与者同时挂掉的问题,所以3PC把2PC的准备阶段再次一分为二,这样三阶段提交。
阶段一a) 协调者向所有参与者发出包含事务内容的 canCommit 请求,询问是否可以提交事务,并等待所有参与者答复。b) 参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈 yes 并进入预备状态,否则反馈 no。阶段二协调者根据参与者响应情况,有以下两种可能。情况1:所有参与者均反馈 yes,协调者预执行事务a) 协调者向所有参与者发出 preCommit 请求,进入准备阶段。b) 参与者收到 preCommit 请求后,执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。c) 各参与者向协调者反馈 ack 响应或 no 响应,并等待最终指令。情况2:只要有一个参与者反馈 no,或者等待超时后协调者尚无法收到所有提供者的反馈,即中断事务a) 协调者向所有参与者发出 abort 请求。b) 无论收到协调者发出的 abort 请求,或者在等待协调者请求过程中出现超时,参与者均会中断事务。阶段三该阶段进行真正的事务提交,也可以分为以下两种情况。情况 1:所有参与者均反馈 ack 响应,执行真正的事务提交a) 如果协调者处于工作状态,则向所有参与者发出 do Commit 请求。b) 参与者收到 do Commit 请求后,会正式执行事务提交,并释放整个事务期间占用的资源。c) 各参与者向协调者反馈 ack 完成的消息。d) 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。情况2:只要有一个参与者反馈 no,或者等待超时后协调组尚无法收到所有提供者的反馈,即回滚事务。a) 如果协调者处于工作状态,向所有参与者发出 rollback 请求。b) 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。c) 各参与者向协调组反馈 ack 完成的消息。d) 协调组收到所有参与者反馈的 ack 消息后,即完成事务回滚。优点:相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题。阶段 3 中协调者出现问题时,参与者会继续提交事务。缺点:数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 do commite 指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
什么是三阶段提交?
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个步骤:Try 阶段主要是对业务系统做检测及资源预留。Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。举个例子,假入你要向 老田 转账,思路大概是: 我们有一个本地方法,里面依次调用步骤: 1、首先在 Try 阶段,要先调用远程接口把 你 和 老田 的钱给冻结起来。 2、在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。 3、如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。优点:性能提升:具体业务来实现控制资源锁的粒度变小,不会锁定整个资源。数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。可靠性:解决了 XA 协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。缺点:TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。
什么是补偿事务?
1. 两阶段提交(2PC)2. 三阶段提交(3PC)3. 补偿事务(TCC=Try-Confirm-Cancel)4. 本地消息队列表(MQ)
你知道哪些分布式事务解决方案?
UUID算法的核心思想是结合机器的网卡、当地时间、一个随记数来生成UUID。优点:本地生成,生成简单,性能好,没有高可用风险缺点:长度过长,存储冗余,且无序不可读,查询效率低
数据库自增ID使用数据库的id自增策略,如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。优点:数据库生成的ID绝对有序,高可用实现方式简单缺点:需要独立部署数据库实例,成本高,有性能瓶颈
批量生成ID一次按需批量生成多个ID,每次生成都需要访问数据库,将数据库修改为最大的ID值,并在内存中记录当前值及最大值。优点:避免了每次生成ID都要访问数据库并带来压力,提高性能缺点:属于本地生成策略,存在单点故障,服务重启造成ID不连续
Twitter的snowflake算法(重点) Twitter 利用 zookeeper 实现了一个全局ID生成的服务 Snowflake如上图的所示,Twitter 的 Snowflake 算法由下面几部分组成:1位符号位:由于 long 类型在 java 中带符号的,最高位为符号位,正数为 0,负数为 1,且实际系统中所使用的ID一般都是正数,所以最高位为 0。41位时间戳(毫秒级):需要注意的是此处的 41 位时间戳并非存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 起始时间戳),这里的起始时间戳一般是ID生成器开始使用的时间戳,由程序来指定,所以41位毫秒时间戳最多可以使用 (1 << 41) / (1000x60x60x24x365) = 69年 。10位数据机器位:包括5位数据标识位和5位机器标识位,这10位决定了分布式系统中最多可以部署 1 << 10 = 1024s个节点。超过这个数量,生成的ID就有可能会冲突。12位毫秒内的序列:这 12 位计数支持每个节点每毫秒(同一台机器,同一时刻)最多生成 1 << 12 = 4096个ID加起来刚好64位,为一个Long型。优点:高性能,低延迟,按时间有序,一般不会造成ID碰撞缺点:需要独立的开发和部署,依赖于机器的时钟
分布式ID的特性唯一性:确保生成的ID是全网唯一的。有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。高可用性:确保任何时候都能正确的生成ID。带时间:ID里面包含时间,一眼扫过去就知道哪天的交易。
分布式ID生成有几种方案?
轮询,加权轮询,随机,最少连接,源地址hash
常见负载均衡算法有哪些?
计数器算法是使用计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数。此算法在单机还是分布式环境下实现都非常简单,使用redis的incr原子自增性和线程安全即可轻松实现。这个算法通常用于QPS限流和统计总访问量,对于秒级以上的时间周期来说,会存在一个非常严重的问题,那就是临界问题假设10S内服务器的负载能力为100,因此一个周期的访问量限制在100,然而在第一个周期的最后5秒和下一个周期的开始5秒时间段内,分别涌入100的访问量,虽然没有超过每个周期的限制量,但是整体上10秒内已达到200的访问量,已远远超过服务器的负载能力,由此可见,计数器算法方式限流对于周期比较长的限流,存在很大的弊端。
滑动窗口算法是将时间周期分为N个小周期,分别记录每个小周期内访问次数,并且根据时间滑动删除过期的小周期。当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。此算法可以很好的解决固定窗口算法的临界问题。但是不能完全解决
漏桶算法是访问请求到达时直接放入漏桶,如当前容量已达到上限(限流值),则进行丢弃(触发限流策略)。漏桶以固定的速率进行释放访问请求(即请求通过),直到漏桶为空。
令牌桶算法是程序以r(r=时间周期/限流值)的速度向令牌桶中增加令牌,直到令牌桶满,请求到达时向令牌桶请求令牌,如获取到令牌则通过请求,否则触发限流策略
计数器算法(固定窗口)滑动窗口漏桶算法令牌桶算法
你知道哪些限流算法?
使用分布式系统。部署多台服务器,并做负载均衡。使用缓存(Redis)集群。数据库分库分表 + 读写分离。引入消息中间件集群。
如何提高系统的并发能力?
Cluster-->Set<Instance> persistentInstances-->Set<Instance> ephemeralInstances
注册表结构
1.修改客户端续约数量和预期每分钟的续约阈值2.实例信息放入注册表3.将变动放入最近变化队列4.清除读写缓存
服务端
注册数据
1.构建注册数据,并且为临时节点启动定时续约任务,若续约表示未注册,则发起注册请求2.向服务端发起注册请求
客户端
1.第一次注册会先创建service,同时service内部启动一个定时任务,每5秒检查一次心跳,若15秒未续约则把健康状态改为false,若30秒为续约则删除实例2.待续
服务注册
注册时,启动一个异步任务,每30秒向服务端发送一次心跳
注册时,启动一个异步任务,每5秒向服务端发送一次心跳
服务续约
注册中心
Redis分布式锁如何防止多重死锁
TC (Transaction Coordinator) - 事务协调者: 维护全局和分支事务的状态,驱动全局事务提交或回滚。TM (Transaction Manager) - 事务管理器: 定义全局事务的范围:开始全局事务、提交或回滚全局事务。RM (Resource Manager) - 资源管理器: 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
Seata三大模块?
不受数据影响
性能会比XA好一些
无代码侵入性
优点
最终一致性的
有可能会导致数据无法回滚
缺点
优缺点
一阶段在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。二阶段提交二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。二阶段回滚二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
它分为两个阶段:第一阶段: TM开启全局事务 RM注册各个分支事务 分支事务做完提交生成一个快照第二阶段: TC判断各个分支事务的执行状态 都成功异步删除快照 有一个失败基于快照恢复数据删除快照
原理流程
TC的数据库需要导入一张全局锁的表(lock_table)
有参与事务的每个微服务的数据库都要导入一张undo_log表(用来记录快照数据)
分布式事务的模式设置为:AT
@GlobalTransational
分布式事务全局事务的方法入口添加一个注解
使用方式
AT模式
基于XA规范实现的,实现起来很简单
没有代码侵入
强一致性
数据库得支持XA规范
保证强一致性,意味着性能很差
全局事务注册到事务协调者,开启全局事务注册分支事务执行分支事务所有的事务都执行完后,有TC判断是提交还是回滚
修改配置文件,配置使用XA模式
给所有参与的微服务
给全局事务入口
XA模式
资源的预留
Try
T
业务的提交
Confrim
C
资源的释放
Cancel
最终一致性
AP
TM开启全局事务
RM注册分支事务
直接提交
Try进行资源预留
第一阶段
TC检查各个分支事务
删除预留的资源
如果都成功
回滚资源
执行cancel
如果失败了
第二阶段
快
不依赖数据库,也不用快照,也不需要全局锁
代码由侵入性,需要自己编码实现TCC过程
考虑接口的幂等性
费程序员
还没有进行资源预留
做空回滚的时候,插入一条数据,标记状态是回滚状态
执行了回滚操作
判断预留表里面,有没有资源预留的记录
空回滚
做了空回滚之后
来进行资源的预留
没有,可以预留
有,不能进行资源的预留
判断表里面,这个事务id有没有对应的数据
业务悬挂
@LocalTCC
需要编写一个接口类
@TwoPhaseBusinessAction(name=\"try-method\
try
confirm
cancel
写三个方法
RootConetxt
BussinessActionContext
获取全局事务id
不需要
留着这条记录,以后发生业务悬挂或者空回滚可以拿来判断
cancel的时候,需要预留的记录删除掉吗?
然后实现这些接口
需要编写三个接口
TCC模式
优点一阶段提交本地数据库事务,无锁,高薪能补偿服务即正向服务的 “反向”,高吞吐参与者可异步执行,高吞吐缺点有代码入侵
Sage模式
Seata有哪些模式?
c:一致性;a:可用性;p:必须保证分区容错性
最多只能同时满足两个:cp或ap
什么是CAP?
BA:基本可用;S:软状态;E:最终一致性
什么是BASE理论?
保证一致性和分区容错性各个分支事务先不提交,等所有事务都处理完毕后一起提交,如果有一个事务执行失败就全部回滚
CP模式
保证了可用性和分区容错性各个分支事务处理完成后直接提交,整个流程执行完毕后只要发现有一个事务执行失败就全部回滚
AP模式
TC:事务协调者
RM:资源管理器
TM:事务管理者
什么是事务原理?
XA是强一致性,AT是最终一致性XA需要依赖数据库,AT不需要依赖数据库XA资源锁定,AT不会资源锁定AT比XA性能更好
XA和AT的区别?
Seata分布式
java面试题大全
0 条评论
回复 删除
下一页