导图社区 java
本图汇总了 java基础、商城登录模块、mysql、spring全家桶、MQ、Redis、多线程、java锁、JVM、HashMap、dobbo等知识,可供参考。
编辑于2023-06-03 12:15:28 河南java
java基础
集合篇
Collection子接口
Set接口
HashSet实现类
TreeSet实现类
LinkedHashset实现类
一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及TreeSet。
List接口
ArrayList
说下ArrayList的优缺点?
优点如下: 1.根据下标遍历元素效率较高。 2.根据下标访问元素效率较高。 3.在数组的基础上封装了对元素操作的方法。 4.可以自动扩容。
缺点如下: 1.插入和删除的效率比较低。 2.根据内容查找元素的效率较低。 扩容规则:每次扩容现有容量的50%
ArrayList与LinkedList的区别?
1.ArrayList的查找和访问元素的速度较快,但新增,删除的速度较慢, LinkedList的查找和访问元素的速度较慢,但是他的新增和删除速度较快 2.两者都是线程不安全的。
ArrayList底层是一个动态数组,在我们的内存中是一个连续存储的 ,而我们的链表呢是分散在内存中的 但是数组不一样,他必须要有一个连续的内存空间,所以在内存这一块ArratList的要求会高一点,正因为他是连续储存的结构就是适合下标访问 并且数组的类型是保持一致的,所以数组的每一个元素在占的内存中长度是一样的,然后他又是一个连续的所以他去做下标访问的时候会非常的快就是采用一个元素所占的内存长度乘以下标位置就可以很快的找他下标元素的值 linkList就没有这种优势他是分散在我们的内存中,他到底在内存的那个角落我们是不知道的 所以LinkList会感觉遍历起来会特别的麻烦 所以说LinkList适合做数据的插入及删除操作,不适合做查询 因为我们的链表数据结构就是在内存中不同的位置去连接到一起的 如果有新增的数据的 只需要在两个元素的中间断开插入进去就可以了 那么为什么说我们的数组增删是比较慢的,因为我们的数组定义出来的时候就已经去指定了这个数组的长度;例如new(10)那么他就只能放10个元素这个是我们的静态数组 什么是动态数组呢?就是我们定一个的ArrayList里面去放11一个数据ArrayList肯定是可以的,但是如果是数组的话他会出现一个下标越界 ArrayList是这样做的 他会去新创建一个数组然后去把老数组里面的数据给循环遍历出来再去添加到新的数组里面去 我们的ArrayList的插入速度比较慢的还有一个原因第一个是扩容的问题 这个很好解决: 我们可以在创建数组的时候直接去指定他的长度这样就出去扩容的问题会比较少但是他会比较占用内存会比较大所以一定要提前预估好数组的长度,虽然可以通过减少扩容的次数去提高ArrayList的性能 在这边还会有一个问题的 就是数组新增的时候: 我们在去遍历一个数组的时候假设长度是6 我们需要依次都取出来然后在去添加到指定的元素中去,这这里的话我们就可以使用尾插法直接插入到尾部可以提高极大的一个性能 这也是在某写情况下ArrayList要比LinkedList的效率快
为什么线程不安全还是使用它呢?
因为我们正常的使用场景中,都是用来查询的,不会涉及太频繁的增删,如果涉及到频繁的增删可以使用LinkedList,如果需要线程安全可以是用CopyOrWriteArrayList
如何实现数组和List之间的转换?
数组转List:使用ArrayList。asList(参数)进行转换。 List转数组:使用List自带的toArray()方法。
LinkedList
有序集合(元素存入集合的顺序和去除的顺序一致),元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有ArrayList、LinkedList 和 Vector。
Map子接口
数据结构
堆
数组
优点:按照索引查询元素的速度很快; 缺点:添加、删除元素的操作很耗时间,因为要移动其他元素。
链表
优点:不需要初始化容量 新增删除比较快 缺点: 含有大量的引用,占用的内存空间比较大; 查找元素需要遍历整个链表,耗时
栈
水桶一样 :先进后出 后进先出
队列
水管一样:先进先出 后进后出 水管两头有分队头(只允许删除) 和队尾 (只允许插入)
堆
哈希表
树
无序树
二叉树
JDK1.8的新特性
lambda表达式
//list转map Map<String, String> userMap = userList.stream().collect( Collectors.toMap(User::getId, User::getName) );
Stream
子主题
为什么重写equals时必须重写hashCode方法?
反射的优缺点?
优点:运行期类型的判断,动态加载类,提高代码灵活度。 缺点:性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
反射的应用场景有哪些
JDBC连接的时候使用了Calss。froName()通过反射的方式去加载数据库的驱动程序
网关和过滤器有什么区别?
网关是对所有请求进行分析过滤,但是过滤器只是对部分模块进行过滤
重载和重写有什么区别
重载
参数类型,个数,顺序至少有一个不相同
重写
方法名,参数,返回值相同 子类的方法不能缩小父类方法的访问权限 被关键字final修饰后不能被重写
String和StringBuffer,StringBuilder的区别
String是不变的对象,操作会创建新的对象。 另外两个则是对自身对象进行操作的 StringBuffer线程安全,StringBuilder线程不安全。 如果操作少量的数据用String 单线程操作字符串缓冲区下操作大量的数据用StringBuilder 多线程操作字符串缓冲区下操作大量的数据用StringBuffer
java中创建对象除了new 还有哪些方式
使用new关键字 Class对象的newInstance()方法 构造函数对象的newInstance()方法 对象反序列化 Object对象的clone()方法 继续往下看,最后揭晓
商城登录模块
无状态登录有状态登录
无状态
服务端不保存任何客户端请求者信息 减小服务端存储压力。
有状态
服务端保存大量数据,增加服务端压力 客户端请求依赖服务端,多次请求必须访问同一台服务器
登录流程
浏览器发送登录的请求到Zuul网关然后去携带用户信息转发到授权中心,授权中心是没办法对用户的账号密码进行判断的,我们就通过feign的远程调用获取到用户的信息 如果返回的信息不为空的情况下就可以去生成一个jwt令牌去存入到我们的redis数据库中 最终去响应给我们的zuur网关经过zuur网关到浏览器 然后在去存放在Cookie中
生成
解析
- 1)获取用户的登录凭证jwt
- 2)解析jwt,获取用户身份
- 如果解析失败,证明没有登录,返回401
- 如果解析成功,继续向下
- 3)根据身份,查询用户权限信息
- 4)获取当前请求资源(微服务接口路径)
- 5)判断是否有访问资源的权限
登录模块遇到的问题
解决 cookie写入问题
在做登录的时候输入账号密码后出现密码失败的问题并且在浏览器中也没有cookie,发现浏览器中url地址不一样,然后我们去测试发现是zuul网关的过滤器导致了丢失cookie,最后在yml文件中重新配置了一下覆盖的敏感头信息
首页判断登录状态
在用户向后台发起请求后,它会自动携带cookie,然后我们在后台获取cookie中的token,检验一下这个token是否有效果,如果有效就登录如果无效就登录失败
刷新token
jwt内部设置了token的默认期有效期是30分钟,30分钟后用户的登录信息无效了,这个时候这个jtw的缺点就出现了,当时经过讨论之后采用方法是cookie即将到期的时候,重新生成一个token,比如 token的有效期是30分钟,当用户向我们发起请求的时候我们去判断这个它的token还剩下几分钟,如果超过15分钟就自动刷新token
注销登录
刚开始做的时候是直接给cookie删了就,测试后来发现删除cookie后的token还可以用,这就是gtw的另一个缺点了,,但是无论哪种思路,都绕不可一点:JWT的无法修改特性。因此我们不能修改token来标记token无效,而是在服务端记录token状态 这样解决的
刚开始做的时候是直接给cookie删除了,但是token还是存在一个活跃性,导致我们和监控项目在联调的时候出现用户下线之后状态依旧为在线的一个状态,
1 在用户进行退出的时候,检验token的有效性,并解析token的信息,
2 在把token生成的一个id存到redis,并设置token的有效期为剩余时间,
3 再去校验一下用户的登录状态,除了要正常逻辑以外,在判断了一下token的id是否存在redis,
4 如果要是存在的话就证明token无效,如果不存在就证明有效
cookie和Session的区别
存储位置
cookie是储存在客户端的 Session是存在服务端的
存储的数据大小限制不同
Cookie最对是3k Sessoin是基于内存的
存储的数据类型不同
cookie是Stirng类型 Session是Ojbect类型
mysql
数据库为什么使用B+树
B+树
最终使用的是在B树上面作出的一些优化,他是把mysql的所有索引都放到了叶子节点,他的根节点及子节点大小是16kb【Show GLOVAL STATUS like innnodb'page_size】 在根节点和叶子节点的地方会存放索引和他的磁盘地址。所有的子节点都会引用父节点的冗余 查询的时候他会先把根节点的索引加载到内存当中去进行比对,中间的话他会在内存中进行一个折半查找这样的话就会减少磁盘的io
B树
是遵从的一个左小右大的一个原则,在每一个节点中分配的磁盘空间稍微大一点可以存储多个索引
二叉树
因为二叉树是遵从的左小右大的原则,假如我们的主键索引是递增的一个状态的话,他会一直往下面插入变成一个链表的形式,这样的话跟全盘扫描是没有区别的
红黑树
又叫二插平衡树,也是遵从左小右大的一个原则,如果使用红黑树去存储索引的话,会导致他的节点无限递增,这样的话磁盘的IO次数也会比较高,
引擎
inodb引擎
子主题
SQL约束有哪几种?
NOT NULL
用于控制字段的内容一定不能为空(NULL)
UNIQUE
控件字段内容不能重复,一个表允许有多个 Unique 约束。
PRIMARY KEY
也是用于控件字段内容不能重复,但它在一个表只允许出现一个
FOREIGN KE
用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一
CHECK
用于控制字段的值范围
数据库日志分为哪几种?
通用查询日志
慢查询日志
错误日志
二进制日志
插入优化
批量插入
手动提交事务
start transaction insert into user values(“1”,Tom) commit;
主键顺序插入
顺序插入
大批量的数据插入
使用load指令一次性将本地文件插入到数据库中
SQL慢查询
原因有4种
数据量过大
表设计不合理
sql语句写的不好
没有合理使用索引
针对SQL语句的优化
应尽量避免在 where 子句中使用!=或<>操作符
应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num=10 or num=20 可以这样查询: select id from t where num=10 union all select id from t where num=20
不要返回冗余的数据(不要使用select*)
在使用like模糊查询的时候做百分号会导致索引失效
分库分表
分库分表方案:
水平分库:以字段为依据,按照一定策略(hash),将一个库的数据拆分到多个库中
水平分表:以字段为依据:按照一定策略(hash,range),讲一个表中的数据拆分到多个表中
以表为依据,按照业务归属不同,将不同的表拆分到不同的库中
垂直分表:以字段为依据,按照字段的活跃性,将表中字段拆分不同的表(主表和扩展表)中
分库分表可能遇到的问题
事务问题:需要用分布式事务
跨节点join的问题,解决这一个问题可以分两次查询实现
跨节点conut,order by,groupby以及聚合函数问题:分别在各个节点上得到结果后再应用程序端进行合并
数据迁移,容量规划,扩容等问题
id问题:数据库被切分后,不能再依赖数据库自身的逐渐生成机制了,最简单的可以使用UUID
常用的分库分表中间件
sharding当当
mycat
读写分离
索引优化
避免索引不生效
使用like关键字模糊查询时,%放在前面索引不起作用,(like‘%文’)
不要在索引上进行任何操作(计算、函数、类型转换),否则索引失效
使用联合索引时,只有查询条件中使用了这些字段中的第一个字段,索引才会生效
尽量避免在where子句中使用!=或者><操作符,否则引擎将放弃使用索引而进行全表扫描。
对查询进行优化,尽量避免全表扫描,首先应考虑在where以及order by涉及的列上建立索引
左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效
索引不适合那些场景?
数据量少的不适合加索引
更新频繁的也不适合加索引
区分度低的字段也不适合加索引例如(性别)
索引级别
system级别
只有一条数据 (很明显实际开发中不会有一条数据的)
range
子主题
事务四大特征
原子性
要么执行,要么不执行
隔离性
事务的隔离性时多个用户并发访问数据库时,数据库为每个用户开启的事务,不能被其他事务的操作数据所干预,事物之间要相互隔离
一致性
事务前后,数据总额一致
持久性
一旦事务提交,对数据的改变就是永久的
事务
脏读
假设两个线程同时在操作一个表中的数据 他的ID是1 里面字段age是10 , 线程二进来的时候去给age更新到了20但是没有进行commit操作,这是线程1进来的时候读到了age是20这也没啥问题, 但是如果线程2进行了rollback回滚操作,这个时候就会出去脏读的问题
幻读
不可重复读
死锁
查看死锁日志show engine innodb status;
找出死锁Sql
分析sql加锁情况
模拟死锁案发
分析死锁日志
分析死锁结果
数据库的数据类型
tinyint 十分小的数据 1个字节
smallint 较小的数据 2个字节
mediumint 中等大小的数据 3个字节
int 标准的整数 4个字节
bigint 较大的数据 8个字节
float 浮点数 4个字节
double 浮点数 8个字节(精度问题)
decimal 字符串形式的浮点数 金融计算的时候,一般使用decimal
数据库索引
他是mysql中的一个的排序的数据结构
索引的优点
第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
spring全家桶
springBoot
springBoot的自动装配原理?
springboot根据配置文件自动装配所属的类在用动态代理的方式注入到spring的容器里去
spring的核心注解
@Bean
它是可以标注在一个方法上,并且方法返回的这个对象就会注册到Spring容器中。
@Lazy
组件的懒加载,也就是说在用的时候才进行创建,不用的话不创建。因为默认的组件,Spring容器一启动,容器中的所有组件都会进行创建。
@Configuration
这个注解用来标注一个类,就说明这个类是一个配置类来替换以前的Spring的Xml配置文件
Component
这个用来编写Spring/WEB应用的时候,标注在一个类上,那么这个类就自动添加到容器中。
@ComponentScan
就是包扫描,可以批量扫描某一个包下的所有组件(该包和下面的子包)。
@Autowired
都是组件的装配功能
@Autowired 与@Resource的区别
Autowired是属于Spring的
Resource是属于javaEE的
Controller和RestController的区别
都是用来表示Spring某个类的是否可以接收HTTP请求
@RestController类中的所有方法只能返回String、Object、Json等实体对象,不能跳转到模版页面。
@Controller类中的方法可以直接通过返回String跳转到jsp、ftl、html等模版页面。在方法上加@ResponseBody注解,也可以返回实体对象
springCould
断路器hystrix如何解决雪崩的?
默认情况下tomcat只有一个线程池去处理客户端发送的所有服务请求,这样的话在高并发的情况下,如果客户端所有的请求堆积到同一个服务接口上,就会产生服务器的所有线程去处理该服务的接口,可能会导致其他服务接口访问延迟;
1服务降级: 在高并发的情况下,防止用户一直等待使用服务降级方式(返回一个友好的提示给客户端,不会去处理请求)目的是为了用户体验 2服务熔断在啊高并发的情况下,如果请求达到了一定的极限(可以自己设置阈值),如果超过了设置的阈值,会自动开启保护服务的功能,使用服务降级方式返回一个友好的提示 熔断机制和服务降级一起使用
springCould的5大组件
Spring Cloud Eureka:服务注册与发现
Spring Cloud Zuul:服务网关
Spring Cloud Ribbon:客户端负载均衡
Spring Cloud Feign:声明性的Web服务客户端
Feign调用原理
feign转换成HTTP的请求响应结果,解码成JavaBean,放回给调用者
Feign使用中遇到的相关问题
使用Feign客户端调用其他的微服务时,发送POST请求时,对象信息没有传递成功,关键在于加上注解@RequestBody
使用Feign客户端调用其他微服务时,报错超时,
Spring Cloud Hystrix:断路器
什么是服务熔断?什么是服务降级?
熔断机制
熔断机制是对应雪崩效应的一种微服务保护机制,例如当某个微服务不可用或者响应时长太长,会进行服务降级,然后对改节点进行熔断,快速的返回一个"错误信息",当失败的调用达到一定的阈值或者是缺省5秒调用20次,如果失败就会启动熔断机制
服务降级
一般是从整体载荷考虑.就是当某个服务熔断后,服务器不在被调用,此时客户端可以自己准备一个本地的回调函数,返回一个缺省值,这样做,虽然水平下降,比直接挂掉强
什么是服务雪崩效应
雪崩效应是在大型的互联网公司项目中,当某个服务发生宕机时,由于微服务都是相互通顺的,这样的话会将服务的不可用逐步扩大到各个其他服务中,从而使整个项目宕机
feign是如何进行服务降级的
feigh是对hystrix进行了封装的,默认情况下是关闭的,需要在配置文件进行配置的,然后对需要降级的方法加上注解HystrixCommand,然后在创建一个默认方法加上DefaultProperties这个注解,指定默认的方法就是做的降低服务
Spring Cloud Config:分布式统一配置管理
为什么使用微服务
could的优缺点
优点 1每一个服务足够内聚,代码容易理解 2开发效率高,一个服务只做一件事情 3微服务能够被小团队单独开发 4可以使用不同的语言开发,面向接口编程
缺点 1分布式系统负责性 2多服务运维难度,随着服务的增加,运维的压力也在增大 3系统部署依赖 4服务间通信成本
SpringCloud和Dobbo的区别
调用方式:dubbo是RPC springCloud是restApi
注册中心:dubbo是zookeeper springCloud是zookeeper,也可以是eureke
服务网关:dubbo本身没有实现,只能通过第三方的整合,springcloud有Zuul路由网关,作为路由服务器,进行消费者请求分发,springcloud支持断路器,与git完美集成配置文件支持版本控制,
Eureka和ZooKeeper的区别?
CAP原则:一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)
Zookeeper保证CP
选择优先保证一致性,如果zookeeper的master节点因为网络原因导致失去联系,剩余的节点他会进行选举,但是在这个过程中需要一定的时间,在这个时间内的zookeeper集群是不可用的,并且由于网络原因这中情况发生还是比较大的
Eureka保证AP
如果Eureka宕机之后他不会像Zookeeper一样去进行停止服务,他会自动的让所有请求都自动转换到可用的Eureka节点上,Eureka仍然可以接受新的注册和查询请求,但是他不会同步到其他的节点上了,(这就是为了保证当前节点依然可用)
微服务的个人理解
Nacos
nacos的注册原理
服务提供者,服务消费者,服务发现三个组件组成的
1.微服务在启动的时候会把自己的网络等信息注册到服务发现组件中去,服务发现组件会去储存这写信息 2.各个微服务与发现组件使用一定的通讯机制(例如在一定的时间内发送心跳机制) 3.服务消费者可以从服务发现组件查询服务提供者的网络地址,并使用地址调用提供者接口 4.当微服务网络地址发生改变时,会重新注册到服务发现组件
spring
AOP的应用场景有哪些呢?
1日志记录 2权限验证 3事务管理
spring代理是什么?
spring默认的是动态代理
spring的优点
1降低了组件之间的耦合性,实现了软件各层直接的解耦 2容器提供了AOP技术,利用他很容易实现权限拦截,运行期监控等功能
springBean的生命周期
子主题
spring框架中使用了那些设计模式
1工厂模式 2模板模式 3代理模式 4策略模式 5单例模式 6观察者模式 7适配器模式 8装饰着模式
工厂模式主要是通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象。 工厂模式: 1.对于调用者来说,影藏了复杂的逻辑处理过程,调用者只关心执行结果。 2.工厂要对结果负责,保证生产出符合规范的产品。
代理模式:最常见的 AOP 的实现方式就是通过代理来实现,Spring主要是使用 JDK 动态代理和 CGLIB 代理。
SpringAop的理解
在任何一个系统都是由不同的组件组成的,每个组件负责一快特定的功能,当然存在很多组件跟业务无关的比如:日志,事务,权限等核心服务组件,假设我们现在有一个项目 里面有100个方法 现在需要给每个方法都打印一下日志 现在有两种办法来解决:第一个就是全部一个一个去手动写上去,很明显这种办法是太浪费时间了, 但是如有AOP的话我们可以通过动态代理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改原来业务的逻辑代码。只需要在原来的业务逻辑基础上做一些增强
SpringIOC的理解
指的是对象的创建和和生命周期的管理全部交给托管给string容器 而传统对象的创建呢都是 通过关键字new出来的或者反射apii来创建的 这么做最大的好处就是实现了解耦和面向接口编程
springMvc
SpringMvc执行流程
SpringMvc执行流程
用户发起一个请求到前段控制器 前段控制器接受并拦截请求。 在到HandlerMapping为处理器映射器。dispatcherServlet调用HandMapping,处理器映射器再根据url查找Handler -
MQ
解耦,异步,削峰
异步
如果我们的用户下一个订单会涉及到扣库存,调用短信服务,和积分服务,如果做成同步服务的话会发现整个流程比较长耗时也是比较长的, 如果是异步的话我只需要进行扣库存然后再生成订单把消息写入消息队列到这里的时候我们的业务也就结束了。所以对我们的用户来说他只需要感受到6毫秒的一个处理时长,所以说异步的方式可以提高我们用户的一个响应,于此同时再去把异步消息发送给我们的短信服务和积分服务 于此同时我们还有第二个好处就是假如我们的短信服务和积分服务宕机了 ,但是如果加入了消息队列会把消息暂时储存到消息队列中等短信服务和积分服务恢复了之后那条消息依然可以通过异步的方式发送给用户的
解耦
假如我们的系统新增了两个服务分别是营销服务和数据分析两个模块 如果是同步的话我们就需要进行对我们的生成订单之后的业务处理的代码 如果是加入了消息队列的话就相当于新增了两个消费者 只需要对我们的消息队列进行订阅相关的消息即可
削峰
平时我们的系统每秒访问量是1000但是从下午4点开始到晚上11点的时候一直都是没秒1w的并发
RocketMq性能调优
监控暂停
rocketmq-conosloe这是官方提供了一个web项目,负责监控界面,又没有控制权限,并且消耗性能的查询操作, 我们可以写一个shell脚本,监控执行rocketmqJava进程的存活状态,
消除偏向锁
在JDK1.8有偏向锁,但是在RocketMQ都是多线程的执行,所以竞争比较激烈,可以使用 -XX:UseBiasedLocking:禁用偏向锁来提升性能
如何保证RabbitMQ不被重复消费?
先说一下RabbitMQ为什么回被重复消费,正常情况下消费消息完毕后,会发送一个消息队列,消息队列就知道这个条消息被消费过了,就会将这条消息从消息队列中删除, 由于网络波动得原因,那个确认消息没有传送到消息队列里,导致这个消息队列不知道这个条消息有没有消费过,再次将消息分发给其他得消费者, 解决方案:在写入消息队列时,做唯一标识,消费消息时,根据唯一标识判断是否消费过,
如何保证RabbitMQ不丢失
生产阶段
在生产者往队列写消息,需要处理一下队列的响应,不论是同步还是异步发送消息,都需要try-catch捕获异常,在异常代码块中重试,如果broker返回写入失败等错误消息,需要重试发送,当多次发送失败需要日志记录 这样就能保证在生产信息阶段的时候不会丢失消息
生产者
broke存储消息
这个阶段如果想要保证消息不丢失的话只能是对mq的一个集群部署,可以给配置写成至少写入两台机子之后在给生产者响应,这样的基本就能保住储存的可靠性了,一台挂了另一台还在
消费阶段
正常情况下就是消费者拿到消息之后给队列返回状态然后在去执行业务逻辑,如果这个时候宕机,这条消息就会丢失, 可以让Consumer在队列上拉去的消息不立即发送给队列而是执行完业务流程之后再向队列发送请求
消费者
Redis
string list set zset hash
共享session
一个分布式Web服务将用户的Session信息分布到了不同的服务上去,如果服务访问到了A又去访问了一下B服务这时候用户就需要做一个重新登录,这个问题是客户无法容忍的, 为了解决这个问题就可以让用户使用Redis将用户的Session 的信息进行集中管理的。
通过注解EnableRedisHttpSession进行集中管理
可以通过设置maxInactiveIntervalInSeconds来设定session的统一过期时间,
限速
比如很多网站出于安全考虑,在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人,
redsi的持久化机制
RDB快照
RDB持久化是将某个一个时间点上的redis中数据保存到一个RDB文件中去,这个RDB文件是经过压缩的二进制文件,这个文件可以还原成运行时Redis中的数据,
RDB提供了两个命令来创建文件,一个是SAVE另一个是BGsave 一般情况下我们会去使用BGsave命令,因为他可以在不阻塞服务器进程的情况下执行,所以推荐使用BGsave命令
载入RDB文件
只有在AOF持久化功能出于关闭的状态时,服务器才会使用RDB文件来还原数据。 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据
AOF日志默认关闭
载入AOF文件
因为AOF文件包含了数据裤的所有写入命令,所以Redis服务器只要读入并重新执行一遍AOF文件里面保存的写命令就可以还原Redis数据库 1他会先去创建一个不带网络的伪客户端 2然后在从AOF文件中分析并读取出一条写命令 3在使用伪客户端执行写命令 4在做一个判断是否AOF文件中的写命令是否全部执行完毕?如果没有的话直到执行完毕
子主题
Redis过期key删除策略
定时删除
在设置key的过期时间的同时,创建一个定时器,让定时器在key的过期时间来立即执行对key 的删除操作 定时操作对于CUP是不好的,如果过期的key比较多的话,起的定时器也会比较多,每一个定时器会占用到CPU的资源
惰性删除
不管key有没有过期都不主动删除,但是每次从键空间中获取键值对时,都去检查取得的key过期时间,如果过期的话,返回null,然后删除key即可
定期删除
隔一段时间,程序就对数据库进行一次检查,删除掉过期key
Redis内存淘汰策略
noeviction默认策略
不删除任何key,在进行写操作时返回错误信息
allkeys-random
从所有key中随机淘汰数据
volatile-ttl
在设置了过期时间的key中,淘汰过期时间剩余最短的
Redis为什么这么快?
1完全基于内存,大部分请求都是纯内存操作,类似于HashMap,HashMap的优势就是查找和操作时间比较快 2采用单线程,避免了不必要的上下文切换和竞争条件,,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑锁的问题,不存在加锁和释放锁的操作,没有因为可能出现死锁而导致的性能消耗;
Redis场景
缓存击穿
redis里面没有这数据,但是数据库有,此时有很多用户去访问,并发用户比较多,造成了数据库崩塌 解决方案有两种:1设置热点数据永不过期就可以解决了。 2使用互斥锁 在根据Key获得Value值为空时,先上锁,在从数据库加载,加载完毕,释放锁。若其他线程获取锁失败,则睡眠50ms后重视。 如果是单机环境用并发包的Lock锁类型就行了,集群环境则使用分布式锁(redis的setnx) 缺点 1代码复杂度增大 2存在死锁的风险
缓存穿透
在Reides中百分之99的数据都是从数据库中查的,至于我们说的缓存击穿就是在数据没有查到这条数据,reides中也没有查到这条数据,这样的话数据库中的数据就不能存在reides里了,reides就也拿不到这条数据,当每次都去查这个数据的时候都会访问数据库,并且数据库月没有反馈,就是这样的一个恶心循环导致数据库的压力较大; 解决方案:我们在这个redis里面加一个键值对,给他的Value值设置为Null就可以了
缓存雪崩
哨兵机制
集群监控
负责监控 Redis master 和 slave 进程是否正常工作。
消息通知
如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移
如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心
如果故障转移发生了,通知 client 客户端新的 master 地址
状态感知
哨兵每隔10秒会向每个master节点发送info命令,info命令返回的信息中,包含了主从拓扑关系,其中包括每个slave的地址和端口号。有了这些信息后,哨兵就会记住这些节点的拓扑信息,在后续发生故障时,选择合适的slave节点进行故障恢复
心跳检测
每隔一秒master,slave都会向其他节点执行ping命令,检测他们是否在正常运行,如果有节点在规定时间内没有响应PING命令,那么该哨兵节点认为此节点"主观下线"。
主观下线和客观下线
当前哨兵节点探测到对方没有得到响应,很有可能之间两个机器之间的网络发生了矛盾,而Master节点本身没有任何问题,此时就认为master故障是不正确的
选举哨兵领导者
假设哨兵 判断主库“主观下线”后,就会给其他 哨兵 实例发送 is-master-down-by-addr 命令 1.过滤故障的节点 2.选择最大的从节点作为主节点 3.选择复制偏移量(数据写入量的字节,记录写了多少数据。主服务器会把偏移量同步给从服务器,当主从的偏移量一致,则数据是完全同步)最大的从节点作为主节点,如不存在则继续 4.选择runid(redis每次启动的时候生成随机的runid作为redis的标识)最小的从节点作为主节点
哨兵模式的工作原理
1、心跳机制
Sentinel 与 Redis Node:Redis Sentinel 是一个特殊的 Redis 节点。在哨兵模式创建时,需要通过配置指定 Sentinel 与 Redis Master Node 之间的关系,然后 Sentinel 会从主节点上获取所有从节点的信息,之后 Sentinel 会定时向主节点和从节点发送 info 命令获取其拓扑结构和状态信息
多线程
创建线程有几种方式
继承Thread类
多线程中继承Thread 类和实现Runnable 接口的区别
由于Java是单继承,一个类继承Thread类以后不能继承其他类,扩展性不好
而实现Runnable接口则可以侧面实现了多继承
多线程中实现Runnable和Callable的接口的区别
Runnable接口的run方法只能在内部捕获异常,不能继续上抛,Callable方法可以直接抛出Exception异常,
Runnable接口中的唯一抽象方法run()方法没有返回值,callable接口中的唯一抽象方法call()是由返回值的
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
实现Runnable接口
实现Callable接口
通过过线程池的方式
线程池
线程池的好处
线程池的重用
节省了线程池的创建和销毁带来的性能影响
控制线程池的并发数
线程池的参数
corePoolSize核心线程数
queueCapacity任务队列容量
maxPoolSize最大线程数
keepAliveTime线程空闲时间
allowCoreThreadTimeout允许核心线程超时
rejectedExecutionHandler任务拒绝处理器
线程池中 submit() 和 execute() 方法有什么区别?
1、excutor没有返回值,submit有返回值,并且返回执行结果Future对象
2、excutor不能提交Callable任务,只能提交Runnable任务,submit两者任务都可以提交
3、在submit中提交Runnable任务,会返回执行结果Future对象,但是Future调用get方法将返回null(Runnable没有返回值)
线程的基本方法有哪些?
线程等待wait
线程睡眠
线程让步
线程中断
登台其他线程终止
线程唤醒
启动线程方法 start和 run有什么区别?0
当程序调用start 方法的时候,将会去创建一个新的线程去执行run()方法中的代码,但是如调用run方法的时话,会执行执行当前线程中的run()方法中的代码, 并且当一个线程启动后不能重复调用start()方法,如果重复调用会报异常,但是可以重复调用run()方法
一个线程的生命周期有哪几种状态?它们之间如何流转的?
New(新创建)
Runnable(可运行)
Blocked(被阻塞)
当线程进入synchronized代码块中没有拿到相应的锁就会导致阻塞
Waiting(等待)
当线程中调用了没有设置 Timeout 参数的 Object.wait() 方法,.join() 方法
Timed Waiting(计时等待)
线程执行了设置了时间参数的 Thread.sleep(long millis) 方法;
Terminated(被终止)
可以通过getState()方法来获取当前线程的状态
Thread类中有一个State枚举定义了这些方法
线程中的 wait和 sleep方法有什么区别?
sleep()
1、属于Thread类,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态
2、sleep方法没有释放锁
3、sleep必须捕获异常
4、sleep可以在 任何地方使用
wait()
1、属于Object,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程
2、wait方法释放了锁
3、wait不需要捕获异常
4、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用
新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?
用 join 方法。
java锁
什么场景下使用 synchronized?
多个线程数据共享,加锁保证访问 共享数据的安全性
synchronized
针对代码块
针对synchronized修改代码块的时候会看到两个字节码,可以发现他有两个指令,monitorenter,monitorexit当JVM运行到monitorenter时,会拿到监视器锁并且给对象头里面的计数器+1,表示当前线程获得锁,当本线程再次尝试获得锁对象时,只需将计数器+1,我们知道synchronized时可重入锁,就是靠这个对象里头的计数器来实现的,当jvm执行到monitorexit的时候将锁对象计数器-1计数器为0的时候,代表锁将被释放,其他线程可以获得锁
针对方法
针对同步方法,看字节码可以发现的标志位有一个ACC_synchroniezd标志,有线程要访问方法的时候,会检查方法上是否有ACC_synchronized标志,如果有设置,在执行方法之前需要先获得监视器锁,并给对象的计数器+1,在方法执行完之后再将对象的计数器-1释放监视器锁,这样其他线程就可以获得锁了
Synchronized保证原子性、有序性、可见性原理
原子性
原子性的意思就是说,执行时不被其他因素干扰,并且结果值是正确,被synchronized修饰的代码.只能被一个线程执行,如果发生CPU时间片用完的情况,因为锁对象的可重入性,也会使得线程获得CPU时间片继续执行,能保证结果不被其他因素干扰
有序性
如果一条线程里面看所有的操作执行都是有序,但是如果从另外一条线程看的话,其他线程里面的操纵指令都是无须的,因为编译器会对代码进行重新排序,用synchronized修饰的代码只能被一条xian线程访问,所以说对于一条线程是有序的
可见性
因为synchronized修饰大的代码只能被一条线程执行,并且在代码执行完释放锁之前,会把线程本地的数据回写到主存,从而保证可见性
Synchronized和Lock的区别
sync会主动释放锁,lock不会,而且容易产生死锁
synchronized是关键字,lock是一个接口
线程的获取 :synchronized假设线程A获取到锁,B线程就会等待,如果A线程阻塞B线程会一直等待 :lock锁的情况是不一样的 因为他是接口, lock():获取锁,如果锁被暂用则一直等待 unlock():释放锁 tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false否则true tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间 lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事
死锁是什么?遇到死锁问该怎么解决?
多个线程同时被阻塞,它们中的⼀个或者全部都在等待某个资源被释放。
破坏请求与保持条件 :⼀次性申请所有的资源。
破坏不剥夺条件 :占⽤部分资源的线程进⼀步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
JVM
JVM里,new出来的对象是在哪个区?
堆内存是用来存放由new创建的对象和数组,即动态申请的内存都存放在堆内存
栈内存是用来存放在函数中定义的一些基本类型的变量和对象的引用变量
Jvm运行时 内存结构
程序计数器
当前字节码的行号指示器,通过java-B的命令在idea控制台对代码进行反编译出文件 例如我们在编写一段代码他在20行的时候进入了一个方法 这个时候jvm会记录他现在所在的行数,
本地方法栈
和虚拟栈相似,只不过它服务于Native方法,线程私有
虚拟机栈
存放基本数据类型、对象的引用、方法出口等,线程私有
堆内存
java内存最大的一块,所有对象实例、数组都存放在java堆,GC回收的地方,线程共享
方法区
存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。(即永久带),回收目标主要是常量池的回收和类型的卸载,各线程共享
垃圾回收机制
什么是垃圾
jvm在判断哪些对象是垃圾的时候,也就是说需要判断哪些对象是需要回收, 几乎所有的对象实例都在堆中存放 如果需要判断的话就需要使用到垃圾回收算法
标记-清除算法
使用的是引用计数法比较简单,对程序长时间允许不发生改变的实时环境有利
效率不高 会产生大量的内存碎片 缺少大段的连续空间,这样假如来了一个大对象,会找不到足够大的连续空间来存放.于是不得不在处罚一次gc
复制算法
把内存分成两块,假设分成两个区域,每次过来之后,都放到了A区域,当A区域满了之后,把存活的对象复制到B区域,然后在去清空一下A区域 接下来的对象全部放到B区域,等B区域满了,就把存货的对象放到A区域里去,然后清空一下B区域
优点 :
不会有空间的内存碎片,
缺点:
每次只能用到一半的内存.缺点是在对象存活率较高的场景下,需要复制的东西太多,效率会下降
标记-整理算法
和标记清楚算法一样,不同的是,死亡的对象不会直接清理,而是把他们的内存都移动到一起,然后在清理
HashMap
hashMap的特性?
hash是使用键值对来存储的Key,Value,允许键为Null,key不可重复,如果Key重复会覆盖原原来的值
谈一下hashMap的原理是啥?
jdk8以后采用的是数组红黑树+链表的数据结构,我们是通过put和get方法来获取对象的,我们去调用put方法的时候会去做一个hashCode的计算来得到他在数组中存储的Entry对象,当去get他的时候先去获取这个数组的位置在通过键值对的equals()方法来返回对象。
谈一下hashMao的put原理
1先去计算hashCode的值 2如果散列列表为空时,调用resize()方法 初始化散列表 如果没有发生碰撞的话直接添加元素到散列列表中去 3如果发生碰撞(Code的值相同的话)进行三种判断 1如果Key的地址值相同或者equals方法后的内容相同,则替换旧址 2如果是红黑树的结构,就调用树的插入方法 3如果是列表结构的话,循环遍历直到某个节点为空,尾插法进行插入 插入之后再判断链表个数是否变成红黑树的阈值8,另外一种就是遍历到有节点的数据和插入的元素数据的哈希值和内容相同,进行覆盖
谈一下HashMap中啥时候需要进行扩容,扩容的resize()又是如何实现的?
通过判断数组的容量是否大于0在判断这个数组是否进行初始化过 没有进行初始化 1在判断是否调用无参构造器 调用的话:使用默认的大小和阈值 没调的话:使用构造函数初始化的容量,当然这个容量是经过tableSizefor计算后的2的次数密 扩容需要重新分配一直新的数组,新数组是老数组的两倍长,然后在去遍历整个老结构,把所有的元素挨个重新分配到新结构中去
ConcurrentHashMap
jdk1.7的结构
Segment[]默认是16个并且继承ReentanLock锁 每一把锁子都对Segment数组进行了加锁这样保证了线程安全,然后每个Segment数组都存放这一个HashEnty[]数组放入了单向列表,并且HashEnty数组是可扩容的
dobbo
Dubbo 核心的配置有哪些?
服务配置
引用配置
协议配置
应用配置
模块配置
注册中心配置
监控中心配置
提供发配置
消费方配置
方法配置
参数配置
Dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,发布者和订阅者之间还能通信么?
可以的,启动dubbo的时候消费者会从zk拉取注册的生产者地址接口,缓存到本地,每次调用的时候按照本地的地址进行调用
Dubbo里面有几种角色
暴露服务的服务提供方
provider
调用远程服务的服务消费方
服务注册与发现的注册中心
统计服务的调用次数和调用时间的监控中心
服务运行容器
dubbo默认的协议
单一的长连接和NIO异步通讯,适合并发小数据量的服务调用,以及消费者大于提供者。传输协议TCP,异步
webservice
http
memcache
Dubbo支持的注册中心
Zookeeper注册中心
基于分布式协调系统 Zookeeper 实现,采用Zookeeper 的 watch 机制实现数据变更
Dubbo使用的是什么通信框架?
NIO Netty框架
Dubbo是什么
Dubbo 是一个分布式、高性能、透明化的 RPC 服务框架,提供服务自动注册、自动发现等高效服务治理方案, 可以和 Spring 框架无缝集成
Dubbo的执行流程
1.provider 向注册中心去注册 2.consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务 3.consumer 调用 provider 4.consumer 和 provider 都异步通知监控中心
1.等级:启动Spring容器时,自动启动Dubbo的Provider Dubbo的Provider在启动后自动会去注册中心注册内容,内容包括Provider的IP,端口,接口列表,接口类
2.订阅:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务
3.通知.当Provider的信息发生变化时, 自动由Registry向Consumer推送通知
4.invoke: 调用. Consumer 调用Provider中方法
5.同步请求.消耗一定性能.但是必须是同步请求,因为需要接收调用方法后的结果
6.:次数. 每隔2分钟,provoider和consumer自动向Monitor发送访问次数.Monitor进行统计
Zookeeper
主题
浮动主题
阿松大
小白萌新起步
计算机硬件基础
WINDOWS命令基础
虚拟机的安装和使用
二进制深入讲解
Linux常用命令
javaSE核心基础
MySql必知必会知识
必备前段技术
java后端框架
设计大师起步
必备数据结构算法