答题体系:场景、问题、解决方案
三高:高并发高性能高吞吐量
一个电商分前后台,前台商城就是有商品搜索,商品展示,商品推荐,购物车,订单,会员中心,客户服务等
后台管理包括商品管理,订单管理,营销管理,会员管理,运营管理,内容管理,权限管理,支付,履约等
运维:Git部署,Jenkins,docker,k8s
监控:sentinel熔断保护,prometheus监控报警,grafana监控可视化,ELK日志中心
组件 作用
nacos 注册中心(服务注册与发现)、配置中心(动态配置管理)
Ribbon 负载均衡
Feign 声明式Http客户端(调用远程服务)
Sentinel 服务容错(限流、降级、熔断)
Gateway API网关(webflux编程模式)
Sleuth 调用链监控
Seata 原Fescar,即分布式事务解决方案
乐观锁悲观锁:秒杀系统低昂单交易全链路优化实战一
https://blog.youkuaiyun.com/weixin_50966947/article/details/124096601
悲观锁,select for update,有可能锁全表,正确使用即走索引并且行记录存在才是行锁,悲观锁利用数据库的锁机制保证操作的独占性,对于长事务或者某些情况下加锁时间过长会造成性能损耗。乐观锁适合读多写少,并且低并发,因为悲观锁加锁释放锁也是需要耗费系统资源的。写入频繁,冲突多就用悲观锁,因为这个时候乐观锁就不适合了。
乐观锁实现,CAS和版本号。
CAS操作的ABA问题:
对于ABA问题,比较有效的方案是引入版本号。只要内存中的值发生变化,版本号就加1,在进行CAS操作的时候不仅比较内存中的值,也比较版本号,只有当二者都没有变化的时候,CAS操作才能执行成功。Java中的AtomicStampedReference类便是适用版本号来解决ABA问题的。
数据库连接:最大连接数max_connections这个参数的大小要综合很多因素来考虑,比如使用的平台所支持的线程库数量(windows只能支持到2048)、服务器的配置(特别 是内存大小)、每个连接占用资源(内存和负载)的多少、系统需要的响应时间等。
和实际数据有关的问题:
用户量:千万级注册用户
总数据千万级,没有分库分表
Mysql里存了多少数据:1000w
一天多少单:几w单
Qps:一秒一两千单,峰值5000
Tps:1500
机器:4核8G,集群部署 三台
接口性能:几百ms
订单服务有哪些接口
商品表有哪些字段: 商品id,运费模板id,品牌id,分类id(上衣,裤子,裙子),商品名称,图片,简介,上架状态,新品状态,审核状态,销量,价格,促销价格,商品描述,商品重量,库存,库存预警值,备注,促销开始时间,促销结束时间,活动限购数量,详情描述,详情标题
品牌管理:名称,首字母,是否显示,产品数量,产品评论数量,品牌logo
商品属性:商品属性分类id,名称(颜色,尺码,价格),属性选择类型(0单选,1多选),属性关联类型(0不关联,1关联),检索类型(0不需要检索,1关键字检索,2范围检索)
商品相关表:商品id,商品名称,会员昵称,评价星数,评价所在ip,创建时间,是否显示,收藏数,阅读数,内容
关于秒杀能说的:
1 系统独立出来,与其他业务分开
2 防止超卖
3 限流熔断降级,限制次数,限制总量,快速失败,降级运行
4 削峰 异步处理
5 分摊流量,验证码
6 预热,提前放入Redis,
7 动静分离,nginx做好动静分离,使用cdn网络分担后端压力
一致性:1 设置超时时间,自动过期 2 canal订阅binlog
缓存应用场景:1 访问量大,qps高,更新频率不高 2 数据实时一致性要求不高
缓存问题:
一 击穿(热点数据单个key被高并发访问) 解决:1 加锁,未命中缓存加锁防止大量请求访问数据库 2 不允许过期,不设置过期时间,逻辑上定时在后台异步更新数据 3 二级缓存,两个缓存,第一级缓存失效时间短,第二级失效时间长,都未命中就加锁让一个线程访问数据库更新,在第二级缓存获取
二 穿透(不存在的值) 1布隆过滤器 2 简单粗暴的方法,如果一个查询返回的数据为空,把空结果缓存,过期时间设置为不超过五分钟
三 雪崩(缓存多个key某一时刻同时失效) 1 散列 2 集群 3 限流降级,一部分流量能通过,另一部分走限流逻辑 4 开启Redis持久化尽快恢复缓存集群
创建一个对象分三步:
memory=allocate();//1:
初始化内存空间
ctorInstance(memory);//2:
初始化对象
instance=memory();//3:
设置
instance
指向刚分配的内存地址
new
和
newinstance
https://blog.youkuaiyun.com/jjclove/article/details/124378899
根据简历会问的问题:
- java基础 1 final关键字
1.2泛型 为什么要用泛型:集合操作object类是通用的,底层也是对object类进行增删改查,但是要操作具体类型会很不方便,比如想在动态数组里面放string类型的对象,写的时候直接ArrayList list = new ArrayList();list.add(“要放入的字符串”),这条语句会默认强行装换成object类型进行存储而不是用string类型,但是取出来的时候取出的也是object类型不是我们期望的string类型。如果接着写list.add(new Person())放入一个对象而不是字符串,此时类型就会强转失败进行引发异常。如果加了泛型,放入的时候只能按照同泛型提示放元素,避免集合中存在多种与类型元素的混杂,用person类,就是处理person类的集合,用string类,就是处理string类的集合。约定E是类型参数,可以是string,Deque,stack。除了方便我们按照类型管理我们的集合,还有一个好处是,当传入的参数不符合泛型在尖括号里时指定的类型时会编译失败而不是等到程序运行时才失败,降低危害。注意1如果在使用泛型类时候不指定类型,那么类型参数就会将其视作object 2泛型不只是java才有
泛型方法:泛型方法不一定定义在泛型类中,也可以定义在普通类中。
普通方法和泛型方法的差异:需要指定类型,类型参数只能是引用类型不能是基本类型,传入基本类型会自动转成包装类。静态方法和静态属性访问不了类上定义的泛型参数,因为指定泛型类型的时候是实例化该类的时候,晚于静态方法的构造时间,自然访问不了
- Spring:源码 设计模式、什么是代理、怎么理解作用域
Spring bean 的作用域:
定义:用于确定哪种类型的bean实例应该被从ioc容器中返回给调用者
类型:singleton prototype request session global sesssion
为什么定义作用域:bean使用安全,根据每个作用域的范围不同,bean都在各自的范围运行,实现平稳运行。
Spring都用到了哪些设计模式:
工厂模式:比如beanfactory,就是简单工厂模式的体现,用来创建对象的实例
单例模式:spring中的bean默认为单例模式
装饰器模式:beanwrapper,访问bean的属性和方法
策略模式:simpleInstantiationStategy bean的实例化策略
适配器模式:mvc中的handleadapter
责任链模式:对用户分类优惠、servlet中的filter 先过滤再拦截
直接引用和符号引用
JVM中的直接引用和符号引用_xiaoyu-Wang的博客-优快云博客_java直接引用和间接引用的区别
springboot启动过程:1 从springapplication.run()开始,方法里会创建一个ApplicationContext的实例,就是IOC容器2 将源配置类注册到IOC容器中,就是主类,被@SpringBootApplication的注解修饰的类 3 递归加载并处理所有的配置类(自动配置) 首先从IOC容器中取出当前存在的源配置类,创建配置类解析器parser,递归加载并处理应用中所有的配置类,递归就是处理@ComponentScan注解,根据@ComponentScan扫描指定的package得到一系列配置类;处理@Import注解,得到一系列被导入的配置类,对所有配置类中的@bean声明的bean对象,并处理Import导入的@ImportBeanDefinitionRegistrar,所有配置类加入一个集合中 4 实例化所有的单例bean (依赖注入和自动装配)5 如果是web应用,就启动web服务器,比如Tomcat
Import支持导入:1 普通类@bean声明 2 接口ImportSelector的实现类里面有个selectImports方法,返回值是个字符串数组,数组中的每个元素都是一个配置类的全限定名 3 接口ImportBeanDefinitionRegistrar的实现类 手动注入IOC容器
在AutoConfigurationSelector中加载处理“自动配置类”:在Spring.Factories机制加载配置类文件,找出所有的配置类,根据@Conditional过滤不必要的配置类
自动装配:Autowired 四个模式 no, nyname, bytype, constructor 根据设置会在populatebean处理
bean的生命周期 xml配置或者注解获取bean定义,spring框架加入到map,用beanfactorypostprocessor做增强(实现接口就可以,里面就一个方法),实例化(执行构造方法),aotowired属性复制,初始化(beanpostprocessor两个方法做前后置扩展)init初始化,后置处理
父类--静态代码块
子类--静态代码块
父类--非静态代码块
父类--构造函数
子类--非静态代码块
子类--构造函数
创造一个对象的过程:我们的代码被jvm编译成.class的字节码文件,然后被类加载器classloader加载到内存,在堆内存构建class对象,把静态数据转化为运行时的动态数据结构放到方区,比如类的方法,变量,修饰符,返回值等,包含这个类的完整结构信息,可以看到类的结构,就像一面镜子
类加载和卸载:加载文件到内存并用字节码文件创建class对象、验证class文件符合虚拟机要求、为static修饰的类变量准备内存、解析(常量池中的符号引用替换成直接引用)、初始化、实例化
初始化:先静态后业务代码
Bean定义:类的类型,字符串形式的作用域数据,布尔类型的是否是懒加载
Spring底层篇
1 什么是spring,谈谈对IOC和AOP的理解
Spring是个企业级java应用框架,作用是简化开发和部署环境。比如常用的jar包,web里面是servlet网络配置,webmvc提供mvc架构支持,core包里提供IOC
Spring优点:
1低侵入,对业务代码侵入低
2 spring的DI机制,将对象之间的依赖交由框架管理,减少组件耦合
3 spring提供AOP,织入通用功能,提高更好复用,比如安全权限,统计方法执行时间,记录日志、处理异常
4 spring对于主流框架提供很好地支持
控制反转:创建对象的控制权交由审spring管理,不用new对象,而是在容器启动的时候就把对象实例化并注入依赖,自动生产
IOC三种属性注入方式:setter注入(在需要注入的类里写setXXX()来注入XXX)、注解注入、有参构造器(在需要注入的类里需要有有参构造器,在参数位置写需要注入的属性)
AOP面向切面:与业务无关但是能影响多个对象的公共行为进行抽取。AOP的核心就是动态代理,JDK动态代理(基于proxy类 写一个接口逻辑,目标类通过反射去实现这个接口)和CGLIB动态代理(不需要接口,为当前类生成子类并且拦截代理类的所有方法,耗时比较长,适用于不用频繁创建对象的情况)
2 什么是bean的自动装配和方式
自动:可以指定类型:在xml配置文件中<bean>的autowired属性,写上byname,bytype(如果不止一个这个类型的bean,需要qualify指定要装配哪一个),
手动:不在autowire配置就在<bean>的ref属性里手动装配
- mybatis
- 怎么分页
- 1、第一种分页,全部查出来,然后在进行分页,这种分页不好
- 2、第二种分页,使用limit,前端传进来页码和每页的条数,在sql使用limit进行查询1、select * from Customer LIMIT 10;--检索前10行数据,显示1-10条数据;
2、select * from Customer LIMIT 1,10;--检索从第2行开始,累加10条id记录,共显示id为2....11;
- 3、第三种分页方式,用RowBounds分页,创建RowBounds对象,使用有参传开始的条数((page -1)*pagesize)和每页几条数据(pagesize),调用mapper方法讲RowBounds对象传递进去 Mybatis提供RowBounds类来实现逻辑分页。RowBounds中有2个字段offset和limit。这种方式获取所有的ResultSet,从ResultSet中的offset位置开始获取limit个记录。但这并不意味着JDBC驱动器会将所有的ResultSet存放在内存,实际上只加载小部分数据到内存,如果需要,再加载部分数据到内存。 优点,比limit简单,缺点,数据库压力比较大,因为将结果暂存到db了
- 4、使用pageHelper插件分页、主要是通过mybatis拦截器实现的
- 导入pageHelper包 PageHelper是一个第三方实现的分页拦截器插件,使用起来灵活且方便。
- 使用springboot进行脚手架搭建,导入对应依赖,写对应的yaml文件
https://www.cnblogs.com/konglxblog/articles/16456706.html
- 多线程并发编程 线程池的参数: 1 corepoolsize核心线程数,确定方法:获取机器核数,程序是CPU密集型就是CPU核数+1,是IO密集型就是核数*2 2 最大线程数,最大线程数大于核心线程数并且任务队列已满,就增加线程,当前线程数等于最大线程数,再有任务会拒绝()3 线程空闲时间,Keepalivetime,线程空闲时间达到这个值,会退出直到线程数字等于核心线程数 4 任务队列容量:当核心线程数达到最大的时候,新任务会放到队列中等待 5 允许核心线程超时,如果设置为true,线程空闲的时候数量会减退到0 6 拒绝策略:直接丢弃(不是很重要),抛异常,重要的任务用缓冲机制处理
- IO NIO
反射 反射机制能做什么
在运行时判断对象所属的类;
在运行时构造类的对象;
在运行时获取类的成员变量和方法;
在运行时调用对象的方法;
生成动态代理。
订单进行商品对象的新旧值覆盖
反射的作用:运行时获取任意一个对象所属的类的相关信息,里面的成员变量和方法,去调用任意一个对象的方法和属性,也可以根据类信息去构造任意一个类的对象,这种动态获取信息,动态调用对象的方法的功能就是反射机制。过程:一般来说都是先有类再有对象,而反射是依据对象找到对象所属的类。怎么获得类对应的class对象:1 调用运行时的对象 p.getClass()方法 2 调用运行时类的.class属性,比如Product类就是Product.class 3 调Class的静态方法,Class.forName("java.lang.String"
) 4 类加载器aClass1 =classLoader.loadClass("com.study1.Person");System.out.println(aClass1); 。而反射就是根据对象获取类型信息。优点: 灵活,运行时(编译时)能够动态获取类实例 缺点:1 性能低,反射通知Jvm做的事情比直接的java代码要慢很多。2 不安全 破坏封装性,获得累得私有方法和字段 应用: 1 动态代理机制 2 jdbc连接数据库 3 spring框架,使用了动态代理 怎么获得类对应的class对象:1 调用运行时的对象 p.getClass()方法 2 调用运行时类的.class属性,比如Product类就是Product.class 3 调class的静态方法,Class.forName("java.lang.String"
) 4 类加载器aClass1 = classLoader.loadClass("com.study1.Person");System.out.println(aClass1); 讲一下动态代理的具体实现:动态代理是代理模式的一种,代理模式就是使用代理对象来代替对真实对象的访问,这样就能在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。 从jvm层面来说,静态代理就是在编译时把接口、委托类、代理类变成.class文件。缺点:一个委托类对应一个代理类,但是多个委托类需要多个代理类,而动态代理的代理类就是通用的,具体有jdk实现和CGLib实现,JDK实现:需要委托类实现一个接口,而代理类不需要实现相同的接口,而是用处理类,将委托类注入到处理类,让处理类实现invocationHandler接口并重写invoke方法,在invoke方法中利用反射机制调用委托类的方法(所有通过动态代理实现的方法全部都通过invoke方法调用),定义创建代理对象的代理类,通过Proxy.newProxyInstance()创建委托类对象的代理对象 缺陷:必须要实现接口,只能代理实现了某个接口的实现类,CGLIB通过字节码技术生成一个子类,并在子类中拦截父类方法的调用,用inceptor做拦截并增强植入自己的代码逻辑,最后用Enhancer.create()创建委托类的代理对象。Cglib比JDK快?
1、cglib底层是ASM字节码生成框架,但是字节码技术生成代理类,在JDL1.6之前比使用java反射的效率要高
2、在jdk6之后逐步对JDK动态代理进行了优化,在调用次数比较少时效率高于cglib代理效率
3、只有在大量调用的时候cglib的效率高,但是在1.8的时候JDK的效率已高于cglib
4、Cglib不能对声明final的方法进行代理,因为cglib是动态生成代理对象,final关键字修饰的类不可变只能被引用不能被修改
Spring如何选择是用JDK还是cglib?
1、当bean实现接口时,会用JDK代理模式
2、当bean没有实现接口,用cglib实现
3、可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class=”true”/>)
分布式事务模式,缓存,分布式锁
- 锁机制原理,volatile关键字,synchronized关键字
- hashmap,concurrenthashmap
- springcloud的组件,比如网关
- 消息中间件
- 压测工具,检测工具
- Redis所有知识:数据结构以及应用、缓存穿透、缓存击穿、缓存雪崩
- nosql mysql 调优原理
- jvm垃圾分类和调优GC
你知道Jvm内存模型吗
方法区:虚拟机加载的类型信息,比如修饰符,类的直接有效名,父类的直接有效名,还有常量静态变量这些。在jvm 启动的时候创建,物理空间和堆一样不连续
栈:线程执行方法的时候创建一个键帧,里面有局部变量表(主要用于存储方法参数和定义在方法体内的局部变量),操作栈(方法执行过程中根据字节码指令,需要把数据入栈出栈,里面是计算过程中临时变量存储空间,比如被调用方法的返回值也存放在这里),动态链接(在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用),方法出口等信息
程序计数器:保存当前线程执行的字节码位置
- 微服务 dubbo框架,kafka,netty,zookeeper,rocketmq,rabbitmq
Jvm调优:
堆外内存1:1Gdb 命令dump 看内容发现重复的是后端给前端的websocket内容,使用的netty 一个框架 socketio 升级之后内存泄漏解决
表达式引擎
RES 进程空间
堆外内存2 :Mvel增强类 缓存住 fullgc
堆内内存:使用了caffine的堆缓存,空间用了600兆,机器是四核八G ,问题是发现老年代存活数量持续增长,出现full gc解决:调了晋升阈值,以及比例newratiosize,最后去stackeoverflow上问了,框架作者说不适用堆缓存,建议我们用堆外缓存,最后把缓存空间调小了就解决了
定时任务 springboot 表达式按照规则执行,服务是集群部署,三台机器同时执行,但是只想让一台机器,选需要有一个地方,只是要求同一时间只需要一个节点,让所有的节点去竞争分布式锁,谁竞争成功就是谁执行 定时任务场景:rocketmq延时消息,定时任务
微服务框架:避免单机出现问题导致系统不可用,扩容缩容简单,人员开发便利(避免新增一个模块合并之后多次重复测试),大促的时候会频繁增减机器,频繁增减域名,需要修改操作,运维开销大
通过负载均衡和反向代理实现分流,通过限流保护服务免受雪崩之灾,通过降级实现部分可用、有损服务,通过隔离实现故障隔离,通过设置合理的超时与重试机制避免请求堆积造成雪崩,通过回滚机制快速修改错误版本;通过上述原则来保护系统,使得系统高可用。
隔离:1 线程隔离 核心业务,非核心业务,线程池也是两个 2 进程隔离,拆分子系统和服务模块 3 集群隔离 4 机房隔离 5 读写隔离 6 动静隔离
通过缓存、异步并发、连接池、线程池、扩容、消息队列、分布式任务等高并发原则来提升系统吞吐量。
(给mq发消息和提交事务怎么保证一致性,就是提交事务的消息发送成功,事务也成功提交。有可能提交事务失败但是已经发送了提交消息,也有可能发送消息之后事务提交失败。)本地消息表:消息生产方需要额外建一个消息表,把这个表和业务数据放到一个事务里,记录消息的发送状态。生产方和消费方定时扫描本地消息表,根据我们写的补偿逻辑,失败的重试。如果先提交事务,再发消息给MQ,MQ挂了之后事务已经提交但是消息没办法发送,如果先发消息给MQ,事务如果提交失败会有不一致的情况,这种情况如果MQ挂了那就没办法提交事务
分布式服务之间怎么互相调用
Openfeign,1 引入依赖 2 启动类上配置扫描feign包 @enableFeignClients(basepackages) 3 比如用A服务调用B服务,A服务新建feign包,B服务在controller层,暴露接口
幂等性:1 乐观锁 版本号 2 token 交互麻烦3 Redis的setnx语句 4 全局唯一ID 5 数据库层面set或者delete天然幂等
微服务架构日志的记录:直接输出日志到控制台,通过filebeat或者promtail去收集,日志和链路最好能统一trace_id方便定位与分析问题,用ELK或者EFK系列
请求通过网关去注册中心拉注册列表,通过本地负载均衡器ribbon进行路由到我们的服务模块,集合sentinel做限流降级,服务之间的调用使用openfeign调用。
每台机器上好几个服务,qps2000,部署了几台机器,两台还是三台,真实量级是多少,压测怎么做的查分了那些微服务
压测步骤:测试环境使用jmeter进行接口压测,然后逐步调大并发度,观察系统吞吐量,然后在阿尔萨斯上监测JVM内存,CPU,线程状态等
根据产品那边给的页面访问量,用户数量,一般都是给一天有多少,qps需要自己算
测试过程:准备接口请求参数,数据表的数据填充,要看的指标:每秒请求数,并发数,请求响应时间(最大最小平均)错误率,机器性能,内存有没有剧烈抖动,cpu占用。Jmx脚本是xml文件,可以直接运行,运行完脚本生成jtl日志
秒杀系统:
特点:
1 瞬时流量极大,过了这个时间点以后流量结束,所以不能用机器去堆QPS
2 库存少
3 时间点邻近的时候刷新量大,静态资源访问激增
业务:运营策划秒杀活动,选品,提前一段时间在秒杀中台建立活动,将秒杀数据和库存等写入缓存
设计思路:1 高并发快响应 在上线之前首先做好压测,知道qps瓶颈在哪儿,针对我们对于业务的预估进行服务器准备,一般采用Redis+热数据+mq,前端静态化,CDN加速(在现有的internet中增加一层新的缓存层,把静态资源预先放到距离用户比较近的站点上)
2 防止超卖 3 防止机器人,恶意刷子,去抢占订单,导致正常用户抢不到 4 静态资源提前缓存好 5 前端秒杀按钮不到时间点不了 6 异步处理订单 7 订单失败补偿 8 服务降级
高可用:Redis集群,主从,哨兵(集群监控,主和从进程工作,消息通知,故障转移的时候做分布式选举)哨兵高可用但是不是高可靠,有可能丢消息
缓存穿透:风控校验、用户白名单、恶意攻击
1 什么是事务,事务的几大特性是?
事务是保证数据库的一组操作,要么全部成功,要么全部失败。事务在引擎层实现,但是不是所有的数据库引擎都支持事务,比如MySQL原生数据库的引擎就不支持,而目前在用的innodb引擎是支持事务的。
事务有四个特性:acid,原子性,一致性,隔离性,持久性。
原子性:事务的原子性指的是一个事务中的所有操作是不可分割的,必须是同一个逻辑单元,要么全部执行,要么全部不执行
一致性:事务前后数据的完整性保持一致
隔离性:多个事务在同时触发的时候,彼此互不干扰,当事务出现并发操作的时候要相互隔离
持久性:事务一旦提交,对数据库的产生的效果是永久的,其他事务的执行或者故障都不会对本次事务的操作结果有影响
事务的四个性质是怎么实现的?
原子性:原子性依靠undolog回滚日志实现,每次对数据做修改或者删除插入的操作都会生成一条undolog来记录操作之前的数据状态,使用rollback的语句能够将所有执行成功的SQL语句产生的效果撤销。回滚日志先于数据持久化到硬盘上。回滚就是逆向操作。
一致性:依靠其他三个性质实现,一致性指的是数据的完整性,为了保证数据的有意义状态
隔离性:通过锁机制实现,当事务操作数据的时候加锁,让事务执行前后看到的数据时候一致的,并行执行事务和串行执行事务产生的效果一样。本质有两种,悲观锁和乐观锁
持久性:持久性通过redolog重做日志实现,redolog记录的是对数据库的操作。MySQL先把存放在硬盘上的数据加载到内存中,在内存中做修改再刷回磁盘,redolog使得在事务提交的时候将数据刷回磁盘。
当数据库宕机之后,先通过redolog来恢复数据库,然后根据undolog和binlog的记录决定是要回滚还是提交
2 四种事务的隔离级别是,分别有哪些问题?
事务隔离的越严格,效率就越低。
读未提交,读已提交,可重复读和序列化。
读未提交:在这种隔离级别下,别的事务在提交之前对数据进行修改,所有事务能够读到修改后的数据。读取未提交的数据会导致脏读,脏读指的是如果事务A读到事务B修改后的数据,但是事务B执行的回滚操作,那么事务A读到的数据就是脏数据。读未提交的隔离级别几乎不会用到。
读提交:只有别的事务提交之后,本事务才能看到修改后的数据,提交之前只能看到未修改的数据。这是大多数数据库默认的隔离级别,但是不是MySQL的默认隔离级别。导致不可重复读:在当前事务中只能看见已经提交事务的执行结果,当同一事务在读取期间出现新的commit操作,读到不一样的数据
可重复读:可重复读是MySQL的默认隔离级别。 对于同一个字段事务在执行过程中读到数据一致,除非自己修改。保证当前事务不会读取到其他事务的update操作,因此会导致幻读。
幻读指的是,由于时间差(同一个事务中相同的两个查询语句会返回不一样的结果)
序列化:串行化,对于同一行记录,读会加读锁,写会加写锁,出现读写冲突的时候,后访问的事务必须等前一个事务执行完成才能继续执行。
通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
3 MySQL的锁有哪些,怎样理解MySQL的间隙锁,行锁,表锁
数据库的DDL变更,包括创建表、添加字段、添加索引、修改字段属性等。
数据库锁设计的目标就是处理并发问题,数据库作为一个多用户访问的资源,当出现并发情况的时候,数据库需要合理控制各个资源的访问规则,而锁就是用来实现这些访问规则的重要数据结构。
基于锁的粒度分类,有表锁、行锁、记录锁、间隙锁、临键锁
还有全局锁,页锁等
全局锁:全局锁就是对整个数据库实例加锁,MySQL提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。
全局锁的应用:对全库做逻辑备份,备份过程中主从库都是处于只读的状态。如果只在主库上备份,那么更新期间不能有操作,业务会停摆,而只在从库上备份,备份期间从库不能执行主库同步过来的binlog,导致主从延迟。
表级锁:有两种,一种是表锁,另一种是元数据锁mdl
表级锁特点:开销小,加锁快;不会出现死锁;粒度大,容易出现锁冲突;数据库引擎总是一次性获取所有的锁,并且按照同样的顺序获取表锁,避免死锁
元数据锁:防止并发执行ddl、mdl阻塞:用于隔离dml(数据操纵语言,对表数据进行修改)和ddl(数据定义语言,对表结构进行修改)语句,每执行一条dml或者是ddl语句的时候都会申请mdl锁,DML操作需要MDL读锁,DDL操作需要MDL写锁(MDL加锁过程是系统自动控制,无法直接干预,读读共享,读写互斥,写写互斥)
表锁有两种:表的读锁和表的写锁
语句:
表锁是MySQL中最基本的锁策略,不依赖存储引擎,开销小,粒度大,最大的负面问题是锁竞争概率高,并发性差
行锁
Innodb支持行锁,这是代替原生的myisam引擎的重要原因
行锁分为共享锁(s锁)和排他锁(x锁)
共享锁又称为读锁,多个事务对于同一个数据能共享一把锁,都能访问到最新的数据
多个事务的查询可以共享一把锁,单个事务拿到共享锁之后,该事务可以进行修改,但是多个事务拿到共享锁,所有事务都不能修改,会导致死锁
排他锁(写锁),对某一样的数据排他锁只能有一个事务拿到,其余事务不能再获取该数据行的锁,基于该数据行的操作会被阻塞,直到锁释放。
共享/排它锁的使用场景
共享锁:确保某个事务查到最新的数据;这个事务不需要对数据进行修改、删除等操作;也不允许其它事务对数据进行修改、删除等操作;其它事务也能确保查到最新的数据。
排它锁:确保某个事务查到最新的数据;并且只有该事务能对数据进行修改、删除等操作。
4 当前读和快照读是什么意思
一般的select语句,不加锁称之为快照读:select * from test;
通过加s锁或者x锁的查询语句、插入、更新、删除操作,就是当前读:
- select * from test lock in share mode;
- select * from test for update;
- insert into test values(…);
- update test set …;
- delete from test …;
为什么会出现锁等待:
没有索引的情况下,如果where语句不能通过索引进行快速过滤,SQL会对全表扫描,MySQL的server层会对所有的记录进行加X锁,然后调用innodb引擎查询
5 描述SQL语句执行的过程,涉及到wal机制,包括buffer pool三种日志
第一步 建立连接。使用连接器和客户端建立连接之后,通过验证用户名和密码,连接器到权限表查询能够获取的权限,之后进行的所有操作的权限都是依赖刚刚建立的这个权限。同时在建立连接之后,要管理和维护这个连接。
第二步 查询缓存。如果查询到之前执行过同样的语句会直接返回缓存中得到的结果。
大多数时候不建议使用缓存,因为只要一个表更新,这个表上的所有缓存数据就会被清空了。对于那些经常更新的表来说,缓存命中率很低。MYSQL8版本直接将查询缓存的整块功能删掉了。
第三步 词法分析。分析器会做词法分析,分析该条SQL语句中什么字符串代表的含义,然后做语法分析。
第四步 优化器。优化器会对SQL的执行顺序,根据使用的索引进行优化选择,看怎么执行,最后会给出一个执行计划。
第五步 执行器。拿到执行计划之后,执行器先对权限进行校验,通过之后打开对应的计划表进行执行,根据不同表的引擎定义,调用这个引擎提供的接。
引擎层要做的:评估什么时候应该走索引,什么时候全表扫描,应该用什么样的索引
update语句除了会执行上面的五步,还会涉及两个重要的日志模块。
两个重要的日志模块
redo log (重做日志):redo log 是innodb所特有的,当有一条更新语句时,innoDB引擎会先把记录写到redo log中,然后更新内存,这时候更新就算完成了。innoDB会在合适的时候将这个记录更新到磁盘中去。
特点:redo log 是固定大小的,属于循环写。redo log是物理日志,记录的是“在某个数据页上做了什么修改”。有了redo log ,InnoDB可以保证数据库发生异常重启的时候,之前提交的记录不会丢失,这个能力为crash-safe。
binlog(归档日志):binlog属于server层的日志,是逻辑日志,记录的是这个语句的原始逻辑,比如给“id =1 的一行的某个字段+2”。binlog是追加写入的,binlog写到一定的大小后切换到下一个,不会覆盖之前的。
更新语句的内部流程
update t set n = n+2 where id =1
- 执行器先找引擎找到id=1的那一行,如果这一行的数据页已经在内存中则直接返回给执行器。否则先从磁盘读入内存中,然后再返回。
- 执行器拿到了引擎返回的数据行,把这个n值+1,得到新的行数据,然后调引擎的接口写入这行新数据。
- 引擎层先将这个更新操作记录到redo log里,再实际做操作,此时rodo log属于prepare状态。然后去缓存里面实际操作,在innodb做操作,告知执行器执行完成了,随时可以提交事务了。Server层会根据操作的结果收到事务提交或者回滚。
- 事务提交,执行器生成这个操作的binlog,并把binlog写入磁盘。
- 执行器调引擎的提交事务接口,引擎把刚刚写入的redo log的状态改为commit状态,更新完成。
两段式提交:redo log的写入分为两部分,是为了保证这两份日志的逻辑一致。保证数据写到缓存就返回,又保证数据不丢失,即解决这样的问题:先写缓存再写数据库的数据丢失问题,数据是写到缓存也就是内存中的,宕机或者重启这个数据就没有了。
解决方案:先写一条redolog(引擎层)表示我要做什么,先记录一下,记录完之后再去操作实际的数字,即再去写这个缓存。如果发生了宕机,那么缓存中的数据会丢失,重启之后查看数据库里面磁盘的数据是没有操作的版本,redolog的记录是否都应用到我的缓存里面了,如果没有就再做一遍。
主从同步是通过主机把binlog同步给从机,从机拿到binlog之后再按照这个来操作保证主从的一致。之所以用binlog而不是redolog,是因为redolog是引擎层,而数据库本身分成了server层和引擎层,这样的架构分离的设计,方便了引擎进行迭代,而不影响server层。Binlog在server层做的其实是和redolog一样的事情,有这两个不同的日志,也是因为架构分离的设计,在innodb出现之前是没有redolog这个概念的。保证二者一致:二阶段提交就是为了保证原子性,两个副本一个存在就都存在,要不就都不存在。出现不一致的情形肯定是redolog写了但是binlog没有写,所以每次重启之后,自动查看redolog的状态,如果是prepare状态,就会去查看有没有对应的binlog,有binlog就表示事务已经提交,直接更改redolog为commit状态,没有binlog直接把这条redolog删除即可。
相关配置:redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置成 1,表示每次事务的 redo log 都直接持久化到磁盘。sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。
WAL,全称是Write-Ahead Logging, 预写日志系统。指的是 MySQL 的写操作并不是立刻更新到磁盘上,而是先记录在日志上,然后在合适的时间再更新到磁盘上。这样的好处是错开高峰期。日志主要分为 undo log、redo log、binlog。这三种在之前的博客已经详细说过了,作用分别是 " 完成MVCC从而实现 MySQL 的隔离级别 “、” 降低随机写的性能消耗(转成顺序写),同时防止写操作因为宕机而丢失 “、” 写操作的备份,保证主从一致 "。
什么是缓冲池?
在操作系统中,CPU与磁盘的执行速度不是一个量级的,因此CPU在磁盘中间,有一个高速缓存区,意思是,CPU将需要操作的数据存储在告诉缓存区中,并不直接操作磁盘。而操作系统另起一个线程去定期的将数据更新到磁盘中。
同样,缓冲池也是这个道理,你可以简单把缓冲池理解为缓存。
数据存储在内存中,加速数据访问,加速数据更新,避免每次查询数据都进行磁盘IO,这就是缓冲池的作用
缓冲池缓存什么数据?
缓存表数据和索引数据,将磁盘上最热最近的数据存储到缓冲池上,避免每次查询都到磁盘查询。
缓冲池的缺点
容量小,由innodb_buffer_pool_size决定,默认是128M。可以根据需求设置。
缓冲池污染当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池的所有页都替换出去,导致大量热数据被换出,MySQL性能急剧下降,这种情况叫缓冲池污染
6 mvcc了解过吗,简单说它的原理
Mvcc是多版本并发控制,由undolog辅助实现,是innodb实现事务并发与回滚的重要功能,维持一个数据的多个版本,在不添加锁的情况下让读写操作没有冲突,通过添加三个隐式字段、undolog和read view实现
DBTRXID 6字节,最近修改事务id,记录创建这条记录或者最后一次修改该记录的事务id
DBROLLPTR 7字节,回滚指针,指向这条记录的上一个版本,用于配合undolog,指向上一个旧版本
DBROWJD 6字节,数据库默认该行的主键。如果数据表没有主键,那么innodb会自动生成一个6字节的row_id
Read View是事务进行快照读操作的时候生产的读视图,在该事务执行快照读的那一刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃事务的id,事务的id值是递增的。
7 索引是什么,数据结构有哪些
8 什么是聚簇索引,什么是普通索引
聚簇索引是将主键索引和数据放在一起(叶子节点存储的是行记录),找到索引就找到了数据,普通索引把数据和索引分开存储,索引里放的是和数据关联的主键,还需要根据主键二次查找才能找到数据。
实现:如果一个主键被定义了,那么这个主键就是作为聚集索引
如果没有主键被定义,那么该表的第一个唯一非空索引被作为聚集索引
如果没有主键也没有合适的唯一索引,那么innodb内部会生成一个隐藏的主键作为聚集索引ROWID,这个隐藏的主键是一个6个字节的列,改列的值会随着数据的插入自增。
9 什么是回表,什么是索引覆盖
回表指的是,普通索引无法直接定位行记录,因此需要查询两次索引树,第一遍通过普通索引定位到主键id,然后第二遍再通过集聚索引定位到具体行记录,性能相对于只扫描一遍的集聚索引树性能要低一些
索引覆盖指的是,当前所查询的数据正好是索引,是一种避免回表查询的优化策略。具体的做法是将要查询的数据作为索引建立普通索引(可以是单列索引,也可以是一个索引语句定义所有要查询的列,即联合索引),这样的话就可以直接返回索引中的数据,不需要再通过集聚索引去定位行记录,避免了回表的情况发生。
10 索引失效的场景有哪些
11 谈谈你对锁机制的理解
12 java里面有哪些锁
13 乐观锁,悲观锁,可重入锁,公平锁,非公平锁代表什么意思,能举几个例子吗
14 怎么理解syncronized, reentrantlock。讲一下各自的原理
线程安全
1 Bufferpool,mysql的内存缓冲2 undolog 数据库的数据版本链 3 事务的两阶段提交操作,先写redolog做事务的预处理,再写binlog,之后再进行redolog的提交
为什么要redolog? 而不是直接写磁盘:数据随机分布到磁盘上,磁盘的随机读写效率很低,redolog是顺序写的物理日志,比随机高几百倍
多版本并发控制:readview读视图,有4个核心参数,1 当前提交的最大事务ID,2 当前最大已经提交的事务ID,3 当前活跃的事务列表
一个事务的提交流程和一条SQL语句执行的流程分别是什么样的
聚合索引区分度高的在前面,禁止全查,索引不要过度,索引区分度要高, insert插入语句的时候多条插入语句写成一条,尽可能少用union,用union all等
行锁和表锁:表锁粒度大,当整张表被一个事务锁定的时候,别的事务就不能操作了。反之如果用行锁的话,并发度就会很大,如果一张表有N行,1 如果都上锁的话锁的数量会很多。2 增大了死锁的概率。
使用mvcc不加锁:
Select * from table where id = 1;
如果我要在RR级别下加一个读锁
Select * from table where id = 1 lock in share mode;
加一个写锁,排他锁
Select * from table where id = 1 for update;
Update XXX where id = 1;
Delete from t where id = 1;
Insert into xxx values(1, xxx);
加行锁加的地方:如果是聚集索引就是加到聚集索引上,如果是非聚集索引就会把非聚集索引和对应的聚集索引上都加上锁
查一个不存在的SQL字段会报什么错误
Unknown column 'XX' in 'field list'。如果遇到这个报错时,去检查XX列名是否写正确,然后再去检查这张表中是否有这个字段。
慢查询和连接数过多
应用tps下降,SQL执行超时的异常告警。根据explain指令获得执行计划,可能有两个场景:一 不走索引或者扫描行数过多(执行计划的type显示为all说明没走索引全表扫描 看一下key实际使用的索引 possible_keys可能用到的key 看一下extra字段有没有覆盖索引,尽量去覆盖索引等)
二 事务并发导致等待锁(当前运行的事务信息,当前出现的锁是排他锁还是共享锁,锁等待的对应关系 通过from information_schema查询)可以通过字段得到被阻塞的事务SQL和事务状态,找到对应的代码逻辑进行优化修改,或者找到长事务kill掉
连接数过多:数据库连接达到最大连接数 set global max_connections=XXX增大最大连接数 或者show processlist 查看当前连接和定位低效率的执行SQL 杀死过多连接
从索引 架构 网络 IO吞吐量 内存 锁 SQL语句分析
大多数情况下正常,偶尔很慢:数据库在刷脏页,执行的时候遇到锁,语句有问题
查询慢查询日志slow_query_log日志记录查看
编程规范用的啥 sonarcode
Synchronized关键字的同步方法是通过方法中access_flags中设置ACC_SYNCHRONIZED标志来实现,这个标志是什么样的?
定位线上问题:有可能:1 前端进行接口返回数据的时候参数传递错误 2 拉取数据的时候mysql里面没有这条数据 3 错误地过滤掉数据 4 RPC服务在排序的时候给丢掉 5 返回了数据但是前端没有进行展示
将数据链路还原:1 JVM级别的日志,比如logback或者log4j 配置日志的输出格式、过滤的规则、目录的规范、文件的命名、滚动的规则、日志的容量 2 集中式日志管理,在代码层面,在请求到来的时候,把接口的请求数据、mysql拉取的数据、过滤的数据、RPC的数据、返回的数据记录到log对象中,比如logobject,把每一个logobject放到内存队列,比如blockingqueue,每个jvm内开一个消费者线程去消费blockingqueue的监控对象,发送到kafka集群,在线操作完成。用flink消费这些监控数据,写到ES,利用ES的快速搜索定位问题,比如用户ID去查请求参数、拉取数据、过滤数据,并利用ES做一些统计操作,或者用flink将数据落到hive,进行SQL查询。
注意1 随着日志的累加进行滚动删除,用ES的索引的生命周期策略 2 做好降级开关,在高流量下降级处理
项目中是如何处理重复请求和并发请求的:
设计模式:1 策略模式 大数据系统把文件推送过来,要求根据不同类型来采取不同的解析方式。如果采用很多个if else进行判断,会违反单一原则(一个类只能有一个发生变化的原因,如果任何对分支逻辑修改都会更改当前类的代码,就违反单一原则)和开闭原则(可以扩展但是不能修改)。
http:80
HTTPS:443
下单接口的QPS峰值5000 tps1500
一天5000-2w单 总体有1000w左右的用户数据
怎么测的:jmeter做接口测试: 1 添加测试计划 2 添加线程组threadgroup,设置线程数量和启动线程的时长、发送请求的循环次数 3 创建http请求4 定时器设置吞吐量 5 选择请求方法和接口地址 6 查看聚合报告:请求数量,响应最大最小,90%响应时间,错误请求占比,每秒接收数据 几十毫秒,几十KB
测试环境使用jmeter进行接口压测,然后逐步调大并发度,观察系统吞吐量,然后在ares上监测JVM内存,CPU,线程状态等
消息队列中间件比较:
Sentinel限流原理:设置单机阈值,一秒到达接口的请求数,写清楚来源的IP。直接、链路、关联模式进行限流,一般是直接模式,但是用的是关联模式,比如给订单模块配上限流参数,触发阈值之后对支付业务进行限流。秒杀的时候会对携带ID的请求限流,这个需要在代码层面配置 @SentinelResource("hot")
架构: 网关gateway,结合sentinel进行限流,路由到达nacos注册中心去拉服务列表,经过ribbon负载均衡,路由到我们的服务模块,openfeign进行服务调用
Redis :持久化:AOF将修改的每一条指令记录到appendonly.aof文件中(达到多少大小会重写,或者文件增长百分之多少会重写)但是重放性能慢,Redis实例很大的时候启动花费时间长 RDB快照(设置多少秒内有多少个改动当这一条件满足的时候自动保存一次)但是Redis挂了之后这一次dump和下一次之间会丢数据:混合,先加载RDB内容,后重放AOF
保证一致性:1 先更新缓存再更新数据库,如果缓存更新成功而数据库更新失败,那么缓存中是新值数据库是旧值,当到期缓存数据失效发现之前修改就相当于没改 2 先更新数据库后更新缓存:如果数据库更新成功而缓存更新失败,只能读到旧数据,而一段时间后才读到想要的变更。所以并不一定在每次数据变更的时候都去更新缓存,而且很多情况下,写到缓存中的值,并不是与数据库中的值一一对应的,很有可能是先查询数据库,再经过一系列「计算」得出一个值,才把这个值才写到缓存中。
先删除缓存再更新数据库:读写并发还是不一致
先更新数据库再删除缓存解决并发:读写并发还是不一致
但是必须是1缓存刚好失效 2 读写并发 3 更新数据库和删除缓存的时间比读数据库写缓存要短 但是写数据库一般会加锁,第三步不太可能发生
无论更新缓存还是删除缓存,如果第二步失败都会导致不一致。所以采取异步重试的方法,进一步把操作缓存放到消息队列中。如果项目重启,重试请求会丢失,但是消息队列1保证可靠性成功消费之前不会在broker丢失 2保证消息成功投递,下游拉取消息消费成功之前不会删除,在broker到consumer端保证不丢失。阿里canal帮我们写了消息投递,订阅binlog,实时得知数据库变更信息,canal 自动把数据库变更日志「投递」给下游的消息队列。
延迟双删:当主从延迟的时候,并发场景下有可能主库先改完并删除缓存,另一个线程读取从库的旧值并把缓存更新为旧值,所以大概1-5s用消息队列延时删除https://mp.weixin.qq.com/s?__biz=MzAwNTQ4MTQ4NQ==&mid=2453582112&idx=1&sn=005ffdf453d025a04b417179a2599f76&chksm=8cd1e442bba66d54263d5e78a10fa454e5511649d5c4bdf146dce377114cf90c8fc33fc87b00&scene=21#wechat_redirect
想要强一致,就得上二阶段三阶段协议,Paxos、Raft协议
1 Volatile关键字 因为不同处理器高速缓存下的线程不能通信,但是在主内存中可以互相通信,底层依靠内存屏障 读屏障(失效并拉取最新数据)和写屏障(防止指令重排,保证顺序性) 嗅探 可见性(保证从内存中拉取最新的数据)
2 DCL实现单例模式 多线程下的懒加载,synchronized,只允许一个线程去创建对象,两次null判断:
3 类加载的5个过程:加载 验证 准备(内存分配:1.指针碰撞 堆内存规整(通俗的说就是用过的内存被整齐充分的利用,用过的内存放在一边,没有用过的放在另外一边,而中间利用一个分界值指针对这两边的内存进行分界,从而掌握内存分配情况)。即在开辟内存空间时候,将分界值指针往没用过的内存方向移动向应大小位置即可)。GC收集器算法有:Serial,ParNew 2.空闲列表 堆内存不规整(虚拟机维护一个可以记录内存块是否可以用的列表来了解内存分配情况)即在开辟内存空间时候,找到一块足够大的内存块分配给该对象即可,同时更新记录列表。GC收集器算法有:CMS) 解析(Java虚拟机将常量池内的符号引用替换为直接引用的过程,不同虚拟机规范不同,但是符号引用的形式是一致的,替换的直接引用可以是直接指向这个对象的指针,类变量、类方法的直接引用可能是指向方法区的指针,相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量),一个能间接定位到目标的句柄。转换成直接引用说明要引用的目标必定已经被加载入内存中了) 初始化
4 类初始化的过程 执行类构造器方法 先父再子 子类继承了父类,就拥有了父类的所有属性和方法(除了构造方法),但不一定可以直接访问(私有属性和方法) 子类去创建对象的时候,构造方法第一句总是:super(…)来调用父类对应的构造方法。 先静态再构造 static修饰的成员变量和方法是从属于类的, 而普通变量和方法是从属于对象的,在调用某个类的构造方法之前,应该先加载类信息,包括静态初始化块!之后才能创建对象!
5 对象创建的过程:验证类加载,分配空间,赋零值,创建对象头,执行init方法
Java创建对象的过程_旋转的冬瓜皮的博客-优快云博客_java对象的创建过程
6 对象的内存布局(JOL 引入依赖):对象头(markword:对象自身的运行时数据,哈希码,GC年龄,偏向锁状态,偏向线程,偏向时间戳,锁状态标志; 还有klass pointer指针) 实例数据(属性,包括父类属性) 对齐填充(对象的起始地址是8字节的整数倍)
7 构造方法都有哪些,类是怎么构造的 Construct类,有参无参
8 懒加载:真正用这个bean的时候才去创建,在getbean方法的时候创建
9 spring容器启动会做什么: spring启动的时候创建spring容器,即ApplicationContext的对象,创建BeanFactory对象并解析传入的AppConfig.class的配置文件类似xml得到扫描路径,扫描org.springframework.beans.factory.config包拿到BeanDefinition对象,并且把配置类上有@ComponetScan注解会提供路径,还会有@import注解、@Bean注解,依次扫描,合并存入BeanDefinitionMap也就是一个BeanFactory的单例池里面,找到那些非懒加载的单例bean进行创建,按照bean创建的生命周期(使用xml或者注解定义bean,合并BeanDefinition,推断构造方法(用哪个构造方法,并确定入参的bean对象),对象实例化new instance,依赖注入就是属性填充populate bean,初始化...)而原型模式的bean不会提前存到map,而是getbean的时候创建
Spring创建单例bean之前可以通过BeanFactoryPostProcessor对beanfactory的某个beandefinition对象修改,Aop通过BeanPostProcessor接口实现类里面可以写业务上的东西。
10 封装springboot
11 构造方法的选择:默认不含参,但是如果自己写了一个含参的构造方法,spring会用这个去覆盖,自己指定的话就加个@autowired。含有的这个参数就是先byType再ByName去找,如果有重名的对象也是会覆盖
12 初始化(init)和实例化(构造方法)
Hashmap:查询和修改效率高,插入和删除效率也高的数据结构。存的是entry。存放数据用put方法,根据当前key计算得到的hashcode值找到对应索引取存储数据:1 判断数组table是否为空,为空就扩容,不为空计算得到索引位置i。2 table[i]为空,直接插入,不为空,比较第一个key的hashcode和equals方法是否都一致,一致说明是同一个对象,直接覆盖value值。3不一致就判断table[i]是否为treenode,是的话在树中插入键值对,不是的话对链表遍历看链表的长度是否>8 4是的话转化为红黑树并执行插入操作,不是的话进行头插法插入 5 插入之后判断实际的键值对大小是否超出threshold最大容量。
HashMap底层原理 - AllenForTam - 博客园
Key为null的数据可以有一条,而value为null的数据可以有很多。
出现哈希冲突:头插法插入到对应链表,因为hashmap的发明者认为后插入的entry被查找的概率大。当链表长度大于8,数组大小大于64,链表会转化为红黑树。
扩容:threshold,loadfactor
拷贝。Hashmap桶大小都是2的幂,1不同的key与2的n-1次幂做与运算算得相同index的概率特别低,能减少hash碰撞 2重新计算后的索引因为只是跟高位做与运算,得到index是当前位置或者当前位置+原数组长度。默认长度16。
线程不安全:
HashMap线程不安全原因:
原因:
- JDK1.7 中,由于多线程对HashMap进行扩容,调用了HashMap#transfer(),具体原因:某个线程执行过程中,被挂起,其他线程已经完成数据迁移,等CPU资源释放后被挂起的线程重新执行之前的逻辑,数据已经被改变,造成死循环、数据丢失。
- JDK1.8 中,由于多线程对HashMap进行put操作,调用了HashMap#putVal(),具体原因:假设两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完第六行代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。
针对jdk1.7:1.8中在resize这个扩容方法中已经使用尾插法完成了。
针对jdk1.8:用synchronized
Concurrenthashmap(初始化和扩容都能线程安全):
Jdk1.7结构:segment+entrySegment本身是基于ReentrantLock重入锁实现的加锁和释放锁的操作,这样就能保证多个线程同时访问ConcurrentHashMap时,同一时间只能有一个线程能够操作相应的节点,这样就保证了ConcurrentHashMap的线程安全。也就是说ConcurrentHashMap的线程安全是建立在Segment加锁的基础上的,所以,我们称它为分段锁或者片段锁
Jdk1.8:在数据比较多情况下,因为要遍历整个链表,会降低访问性能。所以,JDK1.8以后采用了数组加链表加红黑树的方式优化了ConcurrentHashMap的实现。主要是使用了CAS 加 volatile 或者 synchronized 的方式来保证线程安全。
Put方法添加元素时首先会判断容器是否为空,如果为空则使用 volatile 加 CAS 来初始化,如果容器不为空,则根据存储的元素计算该位置是否为空。如果根据存储的元素计算结果为空则利用 CAS 设置该节点;如果根据存储的元素计算为空不为空,则使用 synchronized ,然后,遍历桶中的数据,并替换或新增节点到桶中,最后再判断是否需要转为红黑树。这样就能保证并发访问时的线程安全了
https://www.toutiao.com/article/7035860702091067918/?source=m_redirect&wid=1663213065928
1.8初始化用到的并发技巧:
- volatile变量(sizeCtl):它是一个标记位,用来告诉其他线程这个坑位有没有人在,其线程间的可见性(获取到的值为最新)由volatile保证。Unsafe类的getObjectVolatile方法:此方法确保获取到的值为最新。
- CAS操作:CAS操作保证了设置sizeCtl标记位的原子性,保证了只有一个线程能设置成功。减小锁粒度:将Node链表的头节点作为锁,若在默认大小16情况下,将有16把锁,大大减小了锁竞争(上下文切换),将串行的部分最大化缩小,在理想情况下线程的put操作都为并行操作。同时直接锁住头节点,保证了线程安全
1.8扩容用到的并发技巧:太复杂了
ConcurrentHashMap运用各类CAS操作,将扩容操作的并发性能实现最大化,在扩容过程中,就算有线程调用get查询方法,也可以安全的查询数据,若有线程进行put操作,还会协助扩容,利用sizeCtl标记位和各种volatile变量进行CAS操作达到多线程之间的通信、协助,在迁移过程中只锁一个Node节点,即保证了线程安全,又提高了并发性能。
synchronized和reentrantlock:synchronized jvm内置锁,依赖操作系统底层的互斥原语mutex,涉及到操作系统底层的API所以是重量级锁,基于monitor管程实现,管程是操作系统层面的实现,让线程等待或者唤醒,是管理共享变量以及对共享变量进行操作的过程,主要由synchronized关键字、wait、notify、notifyall实现。以加锁阻塞方式实现线程安全,有两个队列,一个是同步等待队列(jvm层面对应的是cxq单向链表,栈结构),一开始只允许一个线程进入,其他线程会阻塞到这里,另一个是条件等待队列(调用wait之后会在这里等待唤醒,是一个双向循环链表),每个条件变量都有一个条件等待队列,需要notify唤醒后这些线程会竞争锁
Synchronized实现同步方法,通过方法中的access_flags中设置jvm层面的ACC_SYNCHRONIZED标志实现,同步代码块是通过monitorexit和monitorenter指令实现,被阻塞的线程会被挂起,一旦挂起会导致在“用户态”和“内核态”两个状态进行切换
加锁过程:1线程在单向链表栈结构cxq竞争锁 2 成功的线程执行业务代码 3 失败的线程在Entrylist同步阻塞队列里阻塞并排队等待唤醒,后来的线程先获取锁 4 释放锁之后尝试竞争锁,失败后还是到Entrylist
对象头的布局:hashcode,age,epoch偏向锁的时间戳,偏向锁标志,锁状态标志,偏向线程id
CAS尝试更改锁标记的过程:线程会复制一份对象头数据markword在自己的线程栈中,对象头上的轻量级锁的指针ptr会指向栈中锁记录。CAS比较的是当前标志位上是否是无锁
Hashcode会导致偏向锁撤销,对于一个对象,hashcode只会生成一次并保存,偏向锁是没有地方保存hashcode的,可偏向但是未偏向时,只能升级成轻量级锁。偏向锁状态会强制升级为重量级锁。轻量级锁会在锁记录中记录hashcode,重量级锁会在monitor中记录hashcode。
轻量级锁场景:线程交替执行同步块,如果同一时间多个线程访问同一把锁,就会调用park,去改锁标记,并获取monitor对象膨胀成重量级。
锁升级和降级:多线程中锁的升级ynchronized锁升级原理:在锁对象的对象头里面有一个threadid字段,在第一次访问的时候threadid为空,jvm 让其持有偏向锁,并将threadid 设置为其线程id,再次进入的时候会先判断threadid是否与其线程id一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在Java 6之后优化 synchronized的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
多线程中锁的降级具体的触发时机:在全局安全点(safepoint)中停下并做撤销,偏向锁撤销为无锁。
执行清理任务的时候会触发尝试降级锁。
当锁降级时,主要进行了以下操作:
恢复锁对象的markword对象头;
置ObjectMonitor,然后将该ObjectMonitor放入全局空闲列表,等待后续使用。
Reentrantlock允许手动实现加锁和释放锁,所以一旦有lock()方法出现都应该在finally块里unlock。Synchronized是jvm层面的锁实现,reentrantlock是jdk层次的锁实现。Synchronized是非公平锁,reentrantlock默认是非公平锁,也可以是公平锁。Synchronized不可以被中断,reentrantlock的lockinterruptibly可以被中断。Reentrantlock获取锁形式多样,比如返回立即成功的trylock,或者指定时长获取,立即失败超时失败,设置更加灵活。
Reentrantlock实现过程:先CAS比较state值是否为0,是表示可以获取锁将其置为1。不为0需要阻塞在一个双向链表里,每添加一个新节点都是尾插法,并将前一个节点的waitstatus置为-1,自己的waitstatus置为0,每次都是队列最前面的头节点才可以获取锁。加锁是用cas去修改state的值,共享锁、独占锁等都是通过链表的node节点来实现的,其中有一些字段,SHARED,EXCLUSIVE,SIGNAL这种表示属性
Jvm:
判断对象是否可回收:
1 引用计数 当引用失效时,例如一个对象的某个引用超过了生命周期(出作用域后)或者被设置为一个新值时,则之前被引用的对象的计数器的值就减 1。而那些引用计数为 0 的对象,就可以称之为垃圾,可以被收集。特别地,当一个对象被当做垃圾收集时,它引用的任何对象的计数器的值都减 1。
2 可达性分析:一个对象只有满足下述两个条件之一,就会被判断为可达的:
- 对象是属于根集中的对象
- 对象被一个可达的对象引用
GC roots:虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中的常量引用的对象
- 方法区中的类静态属性引用的对象
- 本地方法栈中 JNI(Native 方法)的引用对象
- 活跃线程(已启动且未停止的 Java 线程)
判断对象存活:1 不在引用链上的对象会被第一次标记,根据有没有必要执行finalize方法,是否执行过或者没有覆盖finalize方法都属于没必要 2 对象在finalize方法中尝试被强引用
垃圾回收算法:1 标记清除:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象。缺点:清除之后还需要维护一个空闲列表来记录空闲区域和大小,并且清除之后剩下的空闲空间是碎片化的,总体空间大但是单一空间小2 标记整理:先标记,之后被标记对象统一移动到一端,然后清理掉所有边界之外的内存。需要指针碰撞方法为新对象去分配内存。缺点:一次GC暂停的时间过长,因为需要将所有的对象都拷贝到一个新的地方,还得更新它们的引用地址,句柄也有开销。 3 复制算法 将内存分为两块,每次只用一半,当一半内存用完,就把还活着的对象给移动到另一半。所以适合存活率低的新生代。但是可一次性分配的最大内存缩小了一半。 4 分代。 JDK8以后没有永久代的概念了。
Minor GC:新生代采用空闲指针的方式来控制 GC 触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发 GC。
卡表 card table 512字节
不同的收集器:1 serial收集器 单个GC线程工作的时候是不允许工作线程进行工作的,这就是STW,STW的时间也是衡量一款收集器的标准。采用复制算法,针对的是新生代区 1* Serial OLd 老年版,单线程,标记整理算法2 ParNew收集器:多个GC线程并行收集,利用了多处理器的优势,类似Serial收集器的多线程版本,能够极大缩短STW的时间,也是复制算法。 3 Parallel Scavenge收集器, Parnew的灵活版,提供一些参数能调吞吐量和收集时间的上限,还有垃圾收集时间的比例 3* Parallel Old 老年版, 复制整理 4 CMS 标记清除,碎片多,四个阶段:初始标记、并发标记(追踪)、重新标记(囊括用户操作对象改动引用链)、并发删除 5 G1 收集器
G1(Garbage First)重新定义了堆空间,打破了原有的分代模型,将堆划分为一个个区域。这么做的目的是在进行收集时不必在全堆范围内进行,这是它最显著的特点。区域划分的好处就是带来了停顿时间可预测的收集模型:用户可以指定收集操作在多长时间内完成,即 G1 提供了接近实时的收集特性。G1 与 CMS 的特征对比如下:
特征 G1 CMS
并发和分代 是 是
最大化释放堆内存 是 否
低延时 是 是
吞吐量 高 低
可预测性 强 弱
新生代和老年代的物理隔离 否 是
使用范围 新生代和老年代 老年代
G1 具备如下特点:
并行与并发:G1 能充分利用多 CPU、多核环境下的硬件优势,使用多个 CPU 来缩短 Stop-the-world 停顿的时间,部分其他收集器原来需要停顿 Java 线程执行的 GC 操作,G1 收集器仍然可以通过并发的方式让 Java 程序继续运行。
分代收集:打破了原有的分代模型,将堆划分为一个个区域。
空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的。但无论如何,这两种算法都意味着 G1 运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。
可预测的停顿:这是 G1 相对于 CMS 的一个优势,降低停顿时间是 G1 和 CMS 共同的关注点。
在 G1 之前的其他收集器进行收集的范围都是整个新生代或者老年代,而 G1 不再是这样。在堆的结构设计时,G1 打破了以往将收集范围固定在新生代或老年代的模式,G1 将堆分成许多相同大小的区域单元,每个单元称为 Region,Region 是一块地址连续的内存空间,G1 模块的组成如下图所示:
堆内存会被切分成为很多个固定大小的 Region,每个是连续范围的虚拟内存。堆内存中一个 Region 的大小可以通过-XX:G1HeapRegionSize参数指定,其区间最小为 1M、最大为 32M,默认把堆内存按照 2048 份均分。
每个 Region 被标记了 E、S、O 和 H,这些区域在逻辑上被映射为 Eden,Survivor 和老年代。存活的对象从一个区域转移(即复制或移动)到另一个区域,区域被设计为并行收集垃圾,可能会暂停所有应用线程。
如上图所示,区域可以分配到 Eden,Survivor 和老年代。此外,还有第四种类型,被称为巨型区域(Humongous Region)。Humongous 区域是为了那些存储超过 50% 标准 Region 大小的对象而设计的,它用来专门存放巨型对象。如果一个 H 区装不下一个巨型对象,那么 G1 会寻找连续的 H 分区来存储。为了能找到连续的 H 区,有时候不得不启动 Full GC。
G1 收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集。G1 会通过一个合理的计算模型,计算出每个 Region 的收集成本并量化,这样一来,收集器在给定了“停顿”时间限制的情况下,总是能选择一组恰当的 Region 作为收集目标,让其收集开销满足这个限制条件,以此达到实时收集的目的。
对于打算从 CMS 或者 ParallelOld 收集器迁移过来的应用,按照官方的建议,如果发现符合如下特征,可以考虑更换成 G1 收集器以追求更佳性能:
实时数据占用了超过半数的堆空间;
对象分配率或“晋升”的速度变化明显;
期望消除耗时较长的GC或停顿(超过 0.5 ~ 1 秒)。
G1 收集的运作过程大致如下:
初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改 TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的 Region 中创建新对象,这阶段需要停顿线程,但耗时很短。
并发标记(Concurrent Marking):是从GC Roots开始堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。
最终标记(Final Marking):是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中,这阶段需要停顿线程,但是可并行执行。
筛选回收(Live Data Counting and Evacuation):首先对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。这个阶段也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。
G1 的 GC 模式可以分为两种,分别为:
Young GC:在分配一般对象(非巨型对象)时,当所有 Eden 区域使用达到最大阀值并且无法申请足够内存时,会触发一次 YoungGC。每次 Young GC 会回收所有 Eden 以及 Survivor 区,并且将存活对象复制到 Old 区以及另一部分的 Survivor 区。
Mixed GC:当越来越多的对象晋升到老年代时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即 Mixed GC,该算法并不是一个 Old GC,除了回收整个新生代,还会回收一部分的老年代,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些 Old 区域进行收集,从而可以对垃圾回收的耗时时间进行控制。G1 没有 Full GC概念,需要 Full GC 时,调用 Serial Old GC 进行全堆扫描。
java泛型,动态代理(两个)与反射,事务级别,行级锁,redis原子操作与mq,springboot启动过程,高并发出现的问题,设计模式,实现场景
58同城一面:
简单的自我介绍
SpringBoot的优势
JVM内存模型
GC收集算法(标记清除,标记整理,复制)
GC收集器,详细询问了CMS和G1
实现多线程同步的方式(继承Thread,实现Runnable,实现Callable)
说一个用到的设计模式,实现场景(代理模式,spring的AOP,JDK的动态代理和CGlib的动态代理,区别是什么?继承类的只能用CGlib的动态代理,实现接口的都能用,为什么?JDK的动态代理,被代理类需要继承Proxy,Java只支持单继承,CGlib是通过修改字节码来继承被代理类,对方法进行重写)
算法题:一个大数组,一个小数组,在内存不够大的条件下找到他们的交集
附加问题:如果可以丢失精度,使用什么数据结构比较合适
二面:
TCP的流量控制(滑动窗口)
滑动窗口什么时候变大变小的?
Lock和Sychronized的区别
JVM的full gc什么时候发生,需要STW么?
JVM的老年代存放什么对象?
如何查看JVM哪些对象占用了空间
LinkedList和ArrayList的区别
算法题:1-n个数里面找到唯一一个重复的数
算法题:倒转链表
算法题:二叉树的BFS可能还有一些小问题,记得不是很清楚了
· 谈谈GC,CMS的流程,新生代老生代分别用什么算法
· 3. 谈谈类加载器,类加载器有哪些,双亲委派最终是由父还是子加载
· 4. 操作系统的悲观锁、乐观锁
· 5. 数据库层面的悲观锁、乐观锁
· 6. 数据库事务讲一下
· 7. Redis的持久化机制
· 8. Redis如何实现高可用
· 9. 索引的类型,索引的底层实现原理
· 10. 谈谈消息队列
· 11. HashMap底层实现,哈希冲突怎么解决的
· 12. 各种排序算法讲一下
redis 作为高速缓存和数据库的数据一致性的问题,如果数据更新的话是先更新数据库还 是先更新缓存?若果先更新数据库再更新缓存会涉及什么问题
6、hashMap 底层?为什么 jdk1.8 要用红黑树实现?什么时候会出现线程不安全?怎么解决 线程不安全?默认初始容量是 16,如果我改成 7,容量会变成 7 么?为什么?
7、数组和链表的区别是什么?如果一个数组大小超过堆中剩下的内存大小,还会为这个数组分配内存么?
8、常见的线程池有哪些?线程池中一个线程死了,就没有线程了么?如果在线程池中 new 了一个线程,这个线程是存在还是不存在?线程池中的一些参数有哪些?newCachedPool 最 大可开启的线程数是多少?
定义一个容器,有任务只管扔,创建销毁
有需要异步执行的
Submit返回给一个Future对象(放的是结果)没完成就阻塞 等待唤醒机制 和 execute 不管结果直接执行
9、如何实现其他线程和主线程的同步?
10、volatile 关键字的特性有哪些?
11、10 个线程,如何实现和主线程的同步?
场景是:10 个人在山下聚齐之后才可以一起爬 山,怎么实现?不用 synchronized 关键字、volatile 等同步的关键字。
12、平时建 mysql 表的时候会考虑一些什么?
13、写 sql 语句的时候 where 会考虑什么?
14.epoll 和 poll 的区别
15.sychronized 和 reentrantlock 说一说
16. hash 索引和 B+树索引优缺点
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
从使用角度看,sleep是Thread线程类的方法,而wait是Object顶级类的方法。
sleep可以在任何地方使用,而wait只能在同步方法或者同步块中使用。
CPU及资源锁释放
sleep,wait调用后都会暂停当前线程并让出cpu的执行时间,但不同的是sleep不会释放当前持有的对象的锁资源,到时间后会继续执行,而wait会放弃所有锁并需要notify/notifyAll后重新获取到对象锁资源后才能继续执行。
sleep和wait的区别:
1、sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。
2、sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)。
3、它们都可以被interrupted方法中断。
具体来说:
Thread.Sleep(1000) 意思是在未来的1000毫秒内本线程不参与CPU竞争,1000毫秒过去之后,这时候也许另外一个线程正在使用CPU,那么这时候操作系统是不会重新分配CPU的,直到那个线程挂起或结束,即使这个时候恰巧轮到操作系统进行CPU 分配,那么当前线程也不一定就是总优先级最高的那个,CPU还是可能被其他线程抢占去。另外值得一提的是Thread.Sleep(0)的作用,就是触发操作系统立刻重新进行一次CPU竞争,竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。
wait(1000)表示将锁释放1000毫秒,到时间后如果锁没有被其他线程占用,则再次得到锁,然后wait方法结束,执行后面的代码,如果锁被其他线程占用,则等待其他线程释放锁。注意,设置了超时时间的wait方法一旦过了超时时间,并不需要其他线程执行notify也能自动解除阻塞,但是如果没设置超时时间的wait方法必须等待其他线程执行notify。
在这里插入图片描述
DCL
public static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){ //第一层检查,检查是否有引用指向对象,高并发情况下会有多个线程同时进入
synchronized (LazyMan.class){ //第一层锁,保证只有一个线程进入
//双重检查,防止多个线程同时进入第一层检查(因单例模式只允许存在一个对象,故在创建对象之前无引用指向对象,所有线程均可进入第一层检查)
//当某一线程获得锁创建一个LazyMan对象时,即已有引用指向对象,lazyMan不为空,从而保证只会创建一个对象
//假设没有第二层检查,那么第一个线程创建完对象释放锁后,后面进入对象也会创建对象,会产生多个对象
if(lazyMan==null){ //第二层检查
//synchronized关键字作用为禁止指令重排,保证返回Singleton对象一定在创建对象后
lazyMan = new LazyMan(); //这行代码存在的问题,不能保证原子性
实际上会执行以下内容:
//(1)在堆上开辟空间;(2)属性初始化;(3)引用指向对象
//假设以上三个内容为三条单独指令,因指令重排可能会导致执行顺序为1->3->2(正常为1->2->3),当单例模式中存在普通变量需要在构造方法中进行初始化操作时,单线程情况下,顺序重排没有影响;但在多线程情况下,假如线程1执行lazyMan = new LazyMan()语句时先1再3,由于系统调度线程2的原因没来得及执行步骤2,但此时已有引用指向对象也就是lazyMan!=null,故线程2在第一次检查时不满足条件直接返回lazyMan,此时lazyMan为null
//synchronized关键字可保证lazyMan = new LazyMan()语句执行顺序为123,因其为非原子性依旧可能存在系统调度问题(即执行步骤时被打断),但能确保的是只要lazyMan!=0,就表明一定执行了属性初始化操作;而若在步骤3之前被打断,此时lazyMan依旧为null,其他线程可进入第一层检查向下执行创建对象
}
}
}
return lazyMan;
Jvm调优:
事先开启HeadDumpOnOutOfMemoryError,这样出现OOM的时候能自动留下Dump,留好第一现场。这是最推荐的方式:JVM的启动参数中加入如下的一些参数:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/usr/local/oom
第一个参数意思是在OOM的时候自动dump内存快照出来,第二个参数是说把内存快照存放在哪里
Heap dump相关:Java如何生成Heap Dump及OOM问题排查 - 简书 .hprof结尾
显示某个应用的进程ps -ef | grep 应用名如Redis或者tasklist获取PID,占内存最多的进程号
直接查看堆内存:jmap -heap 7574
G1每个region大小是1-32MB左右,可以增大,是2的整数幂
元空间大小几十MB,到无穷大,一般不设置,设置的小会频繁fullgc
JVM内存分析工具jstack,jstat,jmap的使用_ttheng88的博客-优快云博客_jstack 内存分析
一 如果报OOM(堆OOM 元空间OOM):1 通过jmap -histo 进程号
查看系统内存使用情况,关注实例数量、占用空间大小,当前哪个实例对象最消耗内存,有没有我们自定义的实例对象 2 jhsdb jmap -heap pid 进程号 堆空间和各代新生代老年代的占用情况,总空间,使用空间未使用空间,空间利用率, 3 导出内存溢出日志(堆快照文件)的命令 jmap -dump:format=b,file=路径 pid #dump出内存信息,可用内存分析工具分析情况,format=b是通过二进制的意思 并导入jvisualvm(MAT Jprofile)分析,看类的实例数、大小定位类
元空间OOM:基本上排除堆空间之后(JVM启动参数-xx配自动生成dump文件),堆外就是元空间,尝试修改大小为512M不要很快FULL GC,在启动参数上配verbose:class打印全部的类加载信息
二 CPU使用猛增:1通过top命令查看CPU占用情况,得到进程号PID 2 top -p 进程号查看单个进程的信息 3 大写H获取每个线程的内存情况,得到线程号tid 4 将线程tid转化为十六进制 5 jstack找出占用CPU最高的线程的堆栈信息,jstack 线程号|grep -A 10 线程号对应十六进制 得到出问题的线程nid和对应类的行数
三 死锁:jstack PID看线程相关信息的类和行或者在jvisualvm里(文件-装入)
四 JVM参数调优:jstat -gc
进程
id
查看整体垃圾回收,
GC
耗时,次数,各区容量和使用情况
jstat -gc 进程ID 1000 10 打印10次间隔1000ms
五 MapStruct:
https://blog.youkuaiyun.com/Dream_Weave/article/details/113946112?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166366763516782388057654%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166366763516782388057654&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-2-113946112-null-null.142^v48^control_1,201^v3^control_1&utm_term=MapStruct&spm=1018.2226.3001.4187
cpu百分之百定位,原因有哪些:
1CAS
2死锁
3jvm内存溢出fullgc垃圾回收器退化,停掉所有用户线程去回收垃圾这时候CPU就是跑满的,举例子:mapstruct框架代替getset对属性拷贝,代码需要进行对象属性复制,原始做法就是很多个set属性,编译期的时候自动生成set方法不用自己手写,频繁反射会导致元空间的溢出,元空间的溢出有可能OOM,用户线程退化后频繁GC导致cpu打满,可以说是公司依赖的其他部门或者三方的jar包,实现了反射导致元空间溢出,频繁溢出
元空间存在的是class的元数据信息
元空间溢出OOM解决方法:先重启,因为问题的出现可能是一个接口短时间大量调用,重启之后数据就都没有了。高并发下就一会儿高频调用,但是不可能一直频繁调用。如果一直重启还是一直挂的话,立刻修,或者扩容。
优化:引入新的框架mapstruct,排查可能是OOM问题,验证是反射导致元空间GC,重启解决或者扩容,接口不好修所以扩容,接口性能800ms ->400ms
怎么解决,先讲思路再讲命令,进程,线程,具体
4核8G,一个机器只有一个应用,不推荐几个服务放到一台机器,每个服务都有集群的3台,说个大概台数
并发查询并拿回最快返回的结果
并发跑三个任务,多线程并发使用,用线程池提交任务,
商品信息变了才更新,更新数据库和缓存分布式有失败风险,十分钟是业务容忍的最长不一致时间
方法区:静态变量 + 常量 + 类信息(构造方法/接口定义) + 运行时常量池
堆空间
栈
本地方法栈
线程私有
用线程池提交任务,池化的思想本来就是避免重复创建,有限的线程资源需要管理。往线程池里放任务,按照参数来安排线程干活,异步执行的任务提交到线程池里。
线程池需要用submit方法提交任务,execute没有返回值,submit会返回一个Future对象,成功或者失败的结果会在Future对象里返回,get去拿这个结果,如果get的时候任务还是没有执行完成,就需要阻塞,涉及到java里面的等待唤醒机制。CompletableFuture,开箱即用,最快最慢都可以。
拒绝策略指的是超过最大线程数之后应该怎么办,异常处理需要
Linux命令:
1 tail -f 实时查看日志 查看历史日志用grep 管道符号
2 查找Linux服务器上的某个文件 find
3 端口被占用 netstat -apn 管道符 grep kill-9
4 查看进程 ps -ef | Redis 查看是否启动成功
5 编辑文件vim 后面加路径
6 解压文件 tar-xzvf
7 压缩文件 tar-czvf
8 创建目录或者文件夹 mkdir
9 cp 复制
10 进入指定文件夹:CD
11 创建文件:touch
12 启动一个jar包,后台运行,打印日志到指定文件:nohup java -jar jar包名>日志的文件名 &
13 查询端口的占用:lsof -i
14 查询网络情况 ping
线程提交任务的流程:把任务提交到核心线程,核心线程到达上限,等待队列(如果有界) 最大线程数量
抛异常:异常没有被处理,线程会被回收掉
Java的集合类型:collection(list,set),map(hashtable,hashmap,concurrenthashmap)
ArrayList:底层是动态数组,适合范围存储,有序可重复
Linkedlist:底层是双向链表,更加适合插入和删除操作
Hashmap并发使用会出现failfast异常:1 hashtable 每个方法加synchronized锁2 concurrenthashmap CAS+synchronized
Spring的事务:给方法加注解,这个方法里的所有操作都在transaction的控制之下,
https://blog.youkuaiyun.com/dayuiicghaid/article/details/125260092?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166374899416782417099106%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166374899416782417099106&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-4-125260092-null-null.142^v48^control_1,201^v3^control_1&utm_term=%E4%BA%8B%E5%8A%A1%E4%BC%A0%E6%92%AD%E6%9C%BA%E5%88%B6&spm=1018.2226.3001.4187
Arthas:监控进程
Sonarcube:代码检查,需要官网下载包,启动,maven引入
Docker version 20.10.17, build 100c701
redis:6.0.8
Spring常用的注解:
你觉得你有什么优点:
快照读:基于MVCC 不能解决幻读
当前读:加间隙锁,记录锁和临键锁组成 select for update
Binlog,redolog,undolog(MVCC需要记录版本链)
Mysql主从复制的原理:复制的就是binlog,依赖线程进行操作,IO线程去拿,SQL线程执行
binlog的三种模式:row记录操作,statement(基于修改SQL语句的复制,记录SQL和上下文信息),5.1.8开始的混合模式
@DataSource:数据库切源的时候用这个注解加在Service上
如果用MybatisPlus切源时要想生效不能在controller层直接调MybatisPlus的service方法,因为这里调的是IService的方法,要在service层写个方法来调,然后在这个方法里面调MybatisPlus的Mapper方法才会生效。
DataSource获取的连接来自连接池,连接池的连接其实还是从 DriverManager或类似组件中创建的
JDBC(暴露数据库接口)的实现包括三部分,具体如下:
● JDBC驱动管理器:负责注册特定的JDBC驱动器,主要通过java.sql.DriverManager类实现。
● JDBC驱动器API:由SUN公司负责制定,其中最主要的接口是java.sql.Driver接口。
● JDBC驱动器:它是一种数据库驱动,由数据库厂商创建,也称为JDBC驱动程序。JDBC驱动器实现了JDBC驱动器API,负责与特定的数据库连接,以及处理通信细节。
AOP举例:1 打日志,@LogRecordAnnotation记录一些方法的入参,出参,执行时间
2 切换数据源,有些方法想读主库,有些想读从库
3 mysql的事务注解@Transactional
实现过程:先自己定义一个注解,然后springboot会去扫描并解析这个注解
Tomcat容器(路由请求到不同的controller):1 监听端口号 2 socket服务处理客户端请求 3 执行业务逻辑 4 filter servlet
默认线程200个
Git:1 master上去拉取业务分支,dev上自测没有问题,提测的时候把分支往QA上合,再往master上合
乐观锁和悲观锁的区别:粒度上,一般并发度不大的时候可以用乐观锁,在需要原子性的几个步骤中只在最后一步进行比较。
Synchronized为什么慢:因为没有抢到锁的线程会睡眠,导致切换。为了避免线程抢不到锁就立刻睡眠,线程会CAS,然后把自己包装成一个node节点放到等待队列里,CAS是为了尽可能在入队进行睡眠之前进行CAS操作
分布式锁:解决库存超卖,根本原因在于多线程对于库存这个变量的操作不是原子性的。不会把在代码层面的while循环作为限制并发的手段,代码层面这个比较只是防御性编程中最后的兜底操作
锁就是保证同一时刻只能有一个线程对当前的资源操作
单机锁叫单机,就是因为只针对一个jvm实例,分布式锁是分布式环境下多个实例
Redis用setnx
Zookeeper用临时顺序节点
Mysql用唯一索引,只有一个线程能够插入
Redis:基于内存的IO多路复用模型,10w量级,比zookeeper快(选举,多个腹部,Zab协议导致它慢),基于对并发量的考量和Redis占了先机的非技术角度,都是用Redis做分布式锁。
但是Redis锁一定会有问题,机器宕机,锁会自动释放,但是线程依然认为自己持有锁
当大促流量突然激增到预估10倍以上,整个系统快要崩溃,这时候需要人工介入,去查各个日志推断订单状态走到哪一步,抓紧时间修数据
Watchdog过期时间30s,每过10s去查看线程是不是还持有,是的话就再续约30s。两层数据结构:第一层是SKUcode,第二层是thread ID,第一层看有没有给这个商品的库存加锁,第二层是判断是不是当前线程加的锁
话术引导:为什么上锁(防止超卖)锁怎么加的(Redis的setnx),背后原理(保证操作的原子性),有没有考虑其他方案(考虑因素,性能、习惯、不在代码层面限制并发),防御性编程
Redis的单线程意味着同一时刻只能有一个被请求的命令在执行,而且请求都是按照顺序执行的,没有其他线程的干扰(不存在),所以是原子性
Redis不能回滚
数据库:
Mysql:1 支持事务,但是性能低,依靠硬盘 2 能够用JSON形式存储数据,但是都不会去选,比如存一个队列进mysql 3 行式存储,mysql希望常用的数据放到一起,比如ID为确定值的一行所有字段都放在一个地方,要是只想读其中几个列,会把所有列都查出来,在底层引擎去挑选,经常做统计数据,统计金额,订单量不太适合 4 对搜索性能,模糊匹配做的不是很好
列式存储做统计
线程安全:能在多线程环境下仍然能运行出我们预期的结果,就是线程安全的
1 共享:threadlocal 2 单线程 3 设置成不可变
不用while判断语句(sum - num > 0,乐观锁)来代替分布式锁,原因:1 高并发下业务操作会很复杂,必须保证包含很多操作的这个事务是原子性的,就是只能放一个线程进来做这些操作而不仅仅是在最后一步操作上加锁 2 这条语句只是作为兜底的策略,而不是限制并发的手段 3 高并发下不能把所有压力都放在mysql上,如果不做分库分表,那么分布式机器的请求就都会达到mysql上
不同数据库怎么实现分布式锁:
锁就是在一个时刻只允许一个线程操作变量,分布式锁就是在多实例情况下同一时刻只允许一个实例操作变量,mysql用插入唯一索引实现锁,插入成功相当于拿到锁,Redis是通过jedis.setnx只有在key不存在的情况下才能set成功,返回1说明set成功即抢到锁,返回0说明抢锁失败(但是和设置锁过期时间一共两个操作不构成原子性,所以加锁用jedis.set()五个入参一行代码保证原子性,解锁用的是lua),zookeeper临时节点排队获取锁。
使用Redis实现分布式锁也是有问题的,1假设Redis机器宕掉,或者一次GC清理,那么拿到锁的线程就会丢失锁,其他的线程可能会拿到锁,这时候重启或者停顿后本来拿到锁的线程回来,这时候两个线程都会认为自己拿到了锁。2 如果锁设置时间少于业务的执行时间,需要reddisson的watchdog续约,设置30s到期时间,每过10s检查线程如果还是持有锁再续30s
Redis实现分布式锁的数据结构是两层hashmap,外层的key是我们要操作的sku数据,看看有没有对当前线程想操作的sku code上锁,如果没有上锁再看内层threadID是不是当前线程的ID(上没上锁,谁上的锁)
消息队列:
消息队列为什么快:使用消息队列会带来数量级的提升,由于不调接口也不走数据库事务,还有零拷贝策略导致它很快
如果想保证消息队列里面的消息尽量不丢:1发送方重试机制 2 ack标志
数据库选型:1 mysql的ACID在这个场景下需求不大,对于持久性,Redis也可以实现持久性 2 如果做一些数据统计会考虑到mysql,但是量大的情况下可能Redis会好一些,做数据统计的时候把数据抽出来放到其他列式存储中,比如HBASE
Mysql更新:每一次数据插入或者更新,先把变更放到内存里面,并记录一条日志,等到积累的足够多的时候批次写
整个电商项目涉及的业务:
用户点击提交订单之后,订单信息从前端页面到达单号服务,单号服务生成单号将单号信息返回给用户。为什么有单号
拿到单号等信息(订单信息(各种表,包括订单快照表数据(描述订单信息 商品信息会更换、修改 商品那边下订单的时候也可以记录商品版本号和sku)、订单主表,明细表(在一个店铺买好多),操作日志表(什么时候变得、变成什么了),流水表(支付表,花了多少,什么时候完成什么时候取消 系统有异常,修数据,这个订单发生了什么 是哪个人什么时间生成了一个售后单 数据有疑问都可以回答或者修复 库存扣到哪里都可以说,可能是库存那边出问题))订单服务开始生单:1 插入这些信息到数据库,落盘2去库存服务扣库存 3去优惠券服务营销方面数据库优惠券(扣库存和扣优惠券都是TCC分布式服务做的)尽可能一起扣,防止超卖、优惠券短缺,都扣减成功才算生单成功,生单成功的消息1返回给前端去通知用户支付 2将生单成功的信息通知履约。
用户支付:在前端发预支付消息给支付服务,支付服务交付单号给三方支付并从中获得支付url(二维码)返回给前端
应用(代码)服务这边有自己的数据库 但是没数据
如果支付成功,第三方支付会来回调支付服务告知支付服务,用户XX已经付款XX元,支付服务修改订单状态,回调订单服务,订单服务修改订单状态、支付状态为已支付,如果有子订单还会去修改子单(子订单:一个店铺的购买商品信息),然后通知前端
前端会把支付成功的消息告诉用户(页面下次刷新会更新订单信息)
接下来就是履约
发布订阅的模式:集群,广播
查询库存:WMS查
介绍订单服务:订单生命周期,正向逆向流程,取消订单怎么办
Tomcat默认的线程池大小是200
Mycat是什么
Controller层的入参是不是线程隔离
Synchronized为什么慢,涉及到线程调度的上下文切换,但是大部分情况下都是只有一个线程在执行任务的。锁优化的思路就是根据场景(线程数)减小锁的粒度。竞争压力小的话就是CAS
如何优雅地停止一个线程:interrupt
每个线程都有自己的中断标志位:调用interrupt()就是把标志位设置为true,
Isinterrupted查看标志位状态,interrupted是查看状态后把标志位清空(false)。
实现:线程A在执行中,线程B想要中断线程A,会调用线程A的实例的interrupt方法,threadA.interrupt(),而线程A需要响应中断(比如在while (true)循环里面写判断逻辑)才能真的实现中断
Public void lock() {
sync.acquire();
}
Reentrantlock,外面能看出是设置成true,但是点开看源码是一个三目运算符,把实现的选择权交给程序员
比如公平锁的Tryacquire里面,获取当前线程的状态c(等于0,表示当前没有持有AQS,c==0返回true),判断是不是等于0(一开始默认是1),不等于0再看是不是自己持有的,返回true。如果以上都不是,说明锁被抢占了,而且不是自己抢占的锁,返回false
非公平锁的tryacquire里面,只是没有了对于前驱的判断
Acquire()里面:
if (!Acquire() && acquireQueued(addWaiter(Node.EXCLUSIVE)),arg)
selfInterrupt()
是的话判断1有没有前驱 2 cas和1进行比较
FairSync继承了Sync,Sync又继承了抽象同步队列AQS,因为所有的抽象类必须要有一个实现,里面不能是空,可以是throws报错但是不走,必须要有东西在那里。
不管是reentrantlock,countdown latch,还是别的什么,都是实现tryacquire方法
AQS:reentrantlock是怎么基于AQS实现的:lock-sync.acquire()-Sync是个内部类,继承了AQS
抽象类和接口的区别:代码层面的区别:单继承多实现。功能性层面的区别:对于抽象类,如果我需要不同的子类去实现,就用抽象类,不同的实现类可以走各自的逻辑;接口,如果把某一个方法想让它在哪里都能用,就放到接口里面
Throws和throw的区别
Threadlocal:map是线程的变量
Rpc框架有哪些
Java里的fastfail
限流相关概念:服务熔断、服务降级、服务隔离
熔断机制是微服务链路的保护机制:某个服务不可用,响应时间太长,错误达到某个峰值,都会触发熔断,快速回复响应信息
服务降级:将无关服务降级,比如日志查询历史订单,查看历史评论
服务隔离:线程隔离(使用不同的线程池,用户服务、订单服务、库存服务都有自己的线程池),进程隔离,机房隔离。
服务限流:限制的是请求的数量。一旦达到请求速率可以拒绝请求(定向到错误页面或者告知系统正忙)、排队等待(异步消息队列)、降级(返回兜底数据或者默认数据)
常见限流方法:限制总的并发数(比如数据库连接池,线程池),限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数),限制某个时间窗口内的平均速率(RateLimiter,nginx的limit_req模块);此外还有限制RPC调用频率,限制MQ的消费速率等
google开源工具包guava提供了限流工具类RateLimiter,该类基于“令牌桶算法”
限流策略:计数器(时间窗口)、滑动时间窗口、漏桶(流量整形,不适合秒杀和微博热搜)、令牌桶(guava的rate limiter接口,获得令牌的流量可以通过)
分布式下可以用分布式令牌桶,只不过将令牌桶的发放、存储改为全局的模型。真正实现中,可以采用redis+lua的方式,通过把逻辑放在redis端,来减少调用次数。lua的逻辑如下:
1,redis中存储剩余令牌的数量cur_token,和上次获取令牌的时间last_time。
2,在每次申请令牌时,可以根据(当前时间cur_time - last_time)的时间差 乘以 令牌发放速率,算出当前可用令牌数。
3,如果有剩余令牌,则准许请求通过;否则不通过。
令牌桶算法:
1,令牌按固定速率发放,生成的令牌放入令牌桶中。2,令牌桶有容量限制,当桶满时,新生成的令牌会被丢弃。
3,请求到来时,先从令牌桶中获取令牌,如果取得,则执行请求;如果令牌桶为空,则丢弃该请求。
令牌桶算法可以把请求平均分散在时间段内,是使用较为广泛的限流算法。
浏览器中输入URL:
输入URL之后会执行什么流程?_ZNineSun的博客-优快云博客_输入url后浏览器的执行过程
1 指定DNS域名解析得到对应的IP地址 浏览器中的缓存、本地host缓存、本地DNS服务器、根域名服务器
2 本地请求封装成http数据包
3 再封装成tcp ip数据包
4 TCP连接
5 提供服务
提供服务这里实际上是Tomca服务器在做处理,比如会把请求的信息封装成一个request对象,并同时创建一个response对象,从response对象的缓冲区拿数据并组成一个http响应(包括响应头,响应行,响应体)。还会创建一个servlet对象,web容器Tomcat接到请求之后会把请求封装成一个HttpServletRequest对象传递给servlet实例
JavaEE规定只有servlet类产生的对象才能被浏览器访问到,使用注解的方式告诉Tomcat,@WebServlet(value ="
映射路径"),有一个类实现servlet接口并在实现类里面实现service方法
每次只要有请求进入Tomcat服务器,Tomcat服务器就会把请求过来的http协议信息封装好放到request对象中,根据请求的类型决定传递到service层的doGet还是doPost中使用,通过HttpServletRequest对象,获取到所有请求的信息。
https://www.jb51.net/article/197292.htm
总结:浏览器发送一次请求,会直接调用servlet类里面的service对象,间接调用继承了servlet类的HttpServlet实现类的
doPost()或者是getPost()方法
6 断开TCP连接
7 前端渲染
用户端点击URL之后怎么传送到后台的service方法:
Tomcat是一个servlet容器,负责接收和处理并返回http请求
http请求->前端控制器dispatcherservlet -> requestmapping映射- >adapter分发到对应的handler(对应的controller)
数据库连接池:常用的DBCP、c3p0、Druid
常用数据库连接池 (DBCP、c3p0、Druid) 配置说明 - shaopiing - 博客园
项目会问的:
会不会出现一个商品页面上标注的是5块钱但是用户下单的时候发现变成6块钱,这种怎么解决 不会出现。
1 为什么使用缓存
缓存解决的问题是我不需要再来一遍,计算或者IO都是这样。下单之前会去查
一下有没有这个商品,所以会考虑把这个商品的数据缓存下来,这是减少的IO方面的开销。
经常需要频繁查询的数据需要缓存起来。
在mysql这边查会耗费很多时间的数据比如排行榜,做统计或者排序的时候也需要用缓存。
缓存策略:
read through 或者write through,先去读缓存,如果缓存没有,就去读数据库,把这个读到的数据放到缓存。写的时候也是先写缓存,再更新数据库。有可能在线程1读数据库读到1的时候线程2已经把数字2写到缓存了,但是线程1返回时会把读到的1写到缓存,覆盖掉写好的2,导致缓存和数据库不一致。
Cache aside,先读缓存,缓存没有会去读数据库,把这个读到的数据放到缓存。但是写的时候先写数据库,写完之后让缓存失效。有可能线程1读缓存没有然后从数据库读到1,还没来得及写时间片就没有了,但是线程2已经写完了数据库写上2,而这个时候线程1得到时间片会把1放到缓存中,但是数据库里是2
但是线程2本来晚于线程1执行,并且一个写操作执行的还比线程1快,而线程1恰好在返回来把读到的数据写到缓存的时候失效,这个概率太小了
Refresh ahead,提前加载
在项目中如何使用缓存的:
缓存策略:
商品详情页:商品、物流(配送地址,运费模板,是否包邮)、评论、价格
过期时间是10min 能接受业务10min的不一致时间
秒杀:活动开始之前配一天半天几个小时,按照活动要求进行,因为数据就是这些数据,不用经常更新 运营怎么配我就这么测
ABtest:上功能的比例,控制流量范围上这个功能。如果要改商品的一些信息,颜色,大小,如果商品改名称或者大小,需要看这个商品属于哪个测试,这个测试有没有开等配置,商品和AB测试有绑定关系,登录的时候随机给一个选项A还是B的比例,没有必要持久化,所以停留时间可以是平均登录时间,七八分钟。
用户的信息,商品名字,标题,属性和图片、地址等:这一类不会频繁修改,一天一过期。
为什么缓存要更新:数据变了才会更新
为什么要有过期时间:
更新数据库和更新缓存是两个分布式事务不是一个本地事务里,可能有失败的风险,导致没有一起提交,那么就会出现缓存和数据库数据不一致,缓存一直是错的,商品详情页面10min中的过期时间就是业务能容忍的最大不一致时间。
对于同一批商品有批量失效(正好10分钟了商品过期,发生击穿,缓存失效):1 失效时间随机分散 2 加缓存更新的分布式锁,保证只有一个线程去mysql取数据来更新缓存 3 为了让这个时间不断掉,后台主动更新,每3分钟去查一次,如果快要过期就续约 热点商品会做这种策略
价格这种信息如果需求是只允许1分钟延迟,就单独设置一个时间
高途押题:1 快排 2 BFS二叉树遍历3 翻转链表 4 链表的插入和删除 5 java字符串去重 6 二维数组遍历 7 新增链表的插入和删除 8二维数组遍历 9 整数倒序,12345变成54321 10 统计数组中,每个数字出现的次数,求出出现次数最高的数字出现的次数 11 排列组合 12 合并有序数组 13 手写LRU 14 两个有序数组寻找第K小的数
多线程,线程状态
1.ArrayList在遍历时,删除一个元素,会不会报错?那种迭代的方式不会报错,哪种方式报错,为什么报错? 2.双亲委派机制,是怎么防止类重复加载的; 3.自己手写string的equals方法(此处涉及到String底层是使用的char数组) ; 4.springMVC关于bean初始化的注解(这个不太清楚我) ; 5.springMVC传参; 6.beanfactory和applicationcontext容器的区别,以及底层实现,还有spring bean的生命周期; 7.项目中怎么配置事务的?事务隔离级别有哪些并且默认是哪个? 8.什么是方法重载?方法重载返回值为什么不作为重载条件? 9.实现和继承有什么不同,能不能多实现多继承?接口中能不能写具体的方法(注意jdk8的特性);
索引下推是什么
热点key是什么
熟练写SQL语句
Controller层以及各层的常用注解
怎么在IDE里写并运行算法题
排序算法的复杂度分析
属性注入的注解对比:Spring注解@Resource和@Autowired区别对比 - 割肉机 - 博客园
@Autowired和@Resource:
@Autowired注解是spring提供的注解,用于字段或者属性的方法上,需要导入
org.springframework.beans.factory.annotation.Autowired包进行使用,只按照byType方式进行装配,如果想要byName方式,需要结合含有name属性的@Qualifier注解配合使用
@Autowired
@Qualifier("userDao")
private UserDao userDao;
@Resource:由J2EE使用,默认按照byName方式注入,需要导入包
javax.annotation.Resource,有两个属性,name和type,可以指定,不指定默认byname反射注入
Controller层的注解
DispatcherServlet中的doService方法里,调用了doDispatch方法来处理请求,接收到一个请求之后会把请求中的URL地址的相关接口方法及拦截器封装进mappedHandler对象中,其中handler对象中定义的bean和method方法,用来进行映射调用,Dispatcher根据mappedHandler对象中的接口方法,找到一个用来处理对应方法的适配器(HandlerAdapter)并进行封装,用来进行相应的逻辑处理,如果成功获得 HandlerAdapter 后,此时将开始 执行拦截器的 preHandler方法,提取 Request 中的模型数据,填充 (处理器对象) Handler 入参,开始执行 Handler ( Controller)
Controller层就是处理由DispatcherHandler分发的请求,把用户请求的数据封装成一个model进行展示
https://blog.youkuaiyun.com/Lonelyooacz/article/details/103357612?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166451175216800182182071%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=166451175216800182182071&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-9-103357612-null-null.142^v51^new_blog_pos_by_title,201^v3^control_1&utm_term=controller%E5%B1%82%E6%B3%A8%E8%A7%A3%E6%9C%89%E5%93%AA%E4%BA%9B&spm=1018.2226.3001.4187
@RequestMapping(
"/login")
@RequestMapping,里面可以写value属性构成url映射,可以加到XXController类上也可以加到方法上
@RequestParam,接收来源于url请求的参数值,从request里面取值。用于参数前。@RequestParam(defaultValue=xxx)可以设置参数默认值
controller方法与前端的参数传递---参数不带注解/@RequestParam/@PathVariable - 卡卡发 - 博客园
@RequestParam有以下属性:
public String test(
@RequestParam(value=
"username",required=true,defaultValue=
"admin") String appName)
Value 指定要绑定的参数名
required = true 表示该参数必须传,否则就有空指针异常;设置为 false 表示该参数可以不传
defaultValue = XXX当该参数未传时,使用指定的默认值
Name value的别名,与value作用相同
@PathVariable注解可以获取URL中的一部分值,获取url中的路径参数,用于参数前https://www.jb51.net/article/206179.html路径变量,接收请求路径中占位符的值,参数与大括号里的名字一样要相同
RequestMapping("user/get/mac/{macAddress}")
public String getByMacAddress(@PathVariable String macAddress){
//do something;
}
@CookieValue可获取请求中的Cookie值,让方法参数绑定某个cookie值。
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue(value = "JSESSIONID", required = true, defaultValue = "-999") String jsessionId) {
System.out.println("testCookieValue, JSESSIONID:" + jsessionId );
return SUCCESS;
}
@RequestHeader注解可以获取请求头信息,让方法参数绑定请求头中指定value的key
@RequestMapping("/testRequestHeader")
public String testRequestHeader(@RequestHeader(value = "key", required = true, defaultValue = "-999") String key,
@RequestHeader(value = "Accept-Encoding") String ae) {
System.out.println("testRequestHeader, key:" + key + ", Accept-Encoding:" + ae);
return "SUCCESS";
}
@RestController相当于@Controller+@ResponseBody。即标明当前类是一个Controller组件,又标明该Controller所有方法返回数据而不是视图
@RequestBody注解可以接收json格式的数据,并将其转换成对应的数据类型,使用在单独的方法上,加在参数前,标识在参数上
@RequestBody注解的原理以及使用技巧_Ba~ba~tang的博客-优快云博客_@requestbody原理
@ResponseBody注解可以将Controller的方法返回对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或XML。使用在方法上或者类上
@RequsetBody 绑定的数据类型 :
一般用来处理非Content-Type: application/x-www-form-urlencoded编码格式的数据。
GET请求中,因为没有HttpEntity,所以@RequestBody并不适用。POST请求中,通过HttpEntity传递的参数,必须要在请求头中声明数据的类型Content-Type,SpringMVC通过使用HandlerAdapter 配置的HttpMessageConverters来解析HttpEntity中的数据,然后绑定到相应的bean上。
@ModelAttribute解释:运用在参数上,会将客户端传递过来的参数按名称为key值为对象注入到指定model对象中,并且会将这个对象自动加入ModelMap中,便于View层使用
https://blog.youkuaiyun.com/huawuque004/article/details/81586914?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-81586914-blog-8349115.pc_relevant_multi_platform_whitelistv4&spm=1001.2101.3001.4242.1&utm_relevant_index=3
springboot请求处理的流程:
springboot请求处理流程_Lucien Alborán的博客-优快云博客_spring请求处理流程
Post请求的两种编码格式:application/x-www-form-urlencoded和multipart/form-data
Post请求的两种编码格式:application/x-www-form-urlencoded和multipart/form-data - 简书
让浏览器知道参数是怎样连接的,起始位置是哪里
多线程:
线程的创建,线程的启动,线程的状态切换,线程的终止
线程终止:由自己或者别的线程调用interrupt(),把当前线程的中断标志位设置为true,如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。如果线程处于被阻塞状态(例如处于sleep、wait、join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常,清除标志位重新设置为false
static boolean interrupted() 判断线程是否被中断并清除当前中断状态。这个方法做了两件事:1. 返回当前线程的中断状态,测试当前线程是否已被中断2.将当前线程的中断状态清零并重新设置为 false,清除线程的中断状态
boolean isInterrupted()判断当前线程是否被中断(通过检查中断标志位)
中断线程最好的,最受推荐的方式是,使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量,然后有秩序地中止任务
调用阻塞该线程的套接字的close()
方法。在这种情形下,如果线程被I/O操作阻塞,当调用该套接字的close方法时,该线程在调用accept地方法将接收到一个SocketException(SocketException为IOException的子异常)异常,这与使用interrupt()
方法引起一个InterruptedException异常被抛出非常相似
当线程池中线程执行任务的时候,任务出现未被捕获的异常的情况下,线程池会将允许该任务的线程从池中移除并销毁,且同时会创建一个新的线程加入到线程池中;可以通过ThreadFactory自定义线程并捕获线程内抛出的异常,也就是说甭管我们是否去捕获和处理线程池中工作线程抛出的异常,这个线程都会从线程池中被移除。
分布式事务模式:
XA模式:第一阶段,开启事务开始干活,但是不提交,然后告诉TC干完了,第二阶段如果TC发现所有参与者都干完了,再协调发命令一起commit本地事务,让整个分布式事务结束。
如果在第二阶段TC发了commit指令,其中一个节点提交失败,但是其他的节点都是成功,那就会出现不一致的情况,失败的节点会不停重试,直到成功。如果一直无法成功,那就是会出现这个事务和其他事务的数据不一致。
如果TC挂了,所有的参与者不知道应该commit还是rollback,就会有长事务的一个问题,加入分布式事务中有一个用了update,就会锁住资源,直到这个事务超时回滚,这个锁都不会被释放。
而AT中分布式事务A拿A的全局锁是不会影响到B拿B的全局锁的。本地事务的锁一般都是数据库的锁,和全局锁是两个概念。
验证项目会问的问题:
项目会问的问题
购物车服务的相关接口:1 加购 2 展示 3 凑单 凑单的时候如果量比较大,商品列表会频繁地展示到前端的页面上面,会经常请求展示的接口,所以凑单的商品列表会存放到缓存。
购物车的相关数据在Redis是怎么存放的
我记得购物车数据是一个双层map,外层key是userID,内层key是skucode,value是商品的属性(包括商户名称store_id,商品名称goods_name,商品价格price,加购的数量num,总价totalprice,选中protected,展示图片productimage,领券按钮coupon_button,凑单按钮)
属性不知道是不是这些
购物车的相关数据在数据库中是怎么存放的 没有
spu(真实项目中会有商品品牌,名称,产地吗)
sku(颜色,尺码,型号)
选中标识status
商品数量num
创建时间和修改时间
主键是什么?是用户ID和商品ID形成联合主键吗
Sku和spu是怎么存放的,是sku_color, sku_size这种吗
购物车和数据库里放的这些数据不一样吗?
加购之前怎么确定合法,比如商品是否存在,商品是否新增,所加数量是否超过库存
验证存在和新增都需要根据当前skucode先找缓存再找数据库吗
一个用户的购物车存多少件商品在缓存中,多久过期呢
Sku在mysql的表里怎么体现的
Write behind是先写到缓存再写数据库,使用这个缓存的写策略会有的问题:考虑当数据写到缓存之后,在进入数据库之前,机器宕机了,这个数据就会丢失。考虑落盘保证数据不丢失,但是如果想要严格保证缓存和数据库的一致,就体现不了write behind写策略提高写性能的优势。
所以上次和k老师聊到的缓存策略,我在写缓存的时候是先写数据库还是先写缓存呢,先写缓存要考虑避免遇到上面的数据丢失问题,先写数据库,那么写缓存失败该怎么办?这个问题也是高途的面试官问到的问题。
Write behind这个策略是直接写到内存里就返回的,并不真正写到磁盘上。
Redo log是要写到磁盘上的,所以不如写到磁盘上的的时候直接把该落盘的数据更新到磁盘上。其实不是这样的因为redolog是顺序写的,而innodb真正去数据库更新数据的时候不是追加写的,而是随机写的。
我的想法:借鉴mysql的redolog去记录,把要做的事情记到日志中,还要记录数据版本
谷前辈讲mysql的二阶段提交的时候举了一个对number字段做+1操作的例子,提过如果写缓存之后数据持久化之前宕机,会读到磁盘里面的数据是还没有+1的版本,就会去读redolog里面的记录,有哪些是真正应用到number数据上了,发现没有应用的会根据日志再来一遍。
是内部自己实现的,有会去读磁盘里面数据版本和redolog里面的记录这样的实现。
疑惑:一旦宕机就会读磁盘吗
为什么要尽可能保证强一致而不是最终一致:1 最终一致的前提,是分布式事务一定会成功,但是我扣减库存有可能失败(超卖),锁优惠券也可能失败,优惠券如果过了适用时间超时了那就锁定不了。2 而且优惠券一定要快点锁,那种大额优惠券没有及时锁,客户再下一个订单就可能重复使用
扣库存实际上就是数据库对字段做一个-1的操作,在Redis里是increby这个语句,但是我不确定订单服务涉及的那些表,流水支付订单主表这些用的是什么存储结构,是mysql还是Redis
保证幂等:是每个分布式事务都单独做幂等吗?已回答
微服务模块:生成单号的服务,加购服务,订单服务,库存服务,优惠券服务,支付服务,履约服务,营销服务
库存服务就是仓库服务吗
订单服务的下游:支付服务
上游:生产订单号的单号服务,以及支付完成之后的支付服务,会来回调我们的订单接口,修改订单支付状态以及同步用户信息
一个店铺的会生成一个单号,不同店铺生成不同的单号吗?主订单和子订单是什么样的设计结构,主订单单号是主键的话,一行记录里怎么存多个子订单号
订单服务里改完订单状态为已支付,前端看到用户已支付会去查这个字段是不是已支付
拿到单号和商品信息开始生成订单,怎么做校验,三个操作,生成订单(插入订单信息),扣减库存,锁定优惠券
多张表在一个数据库里需要用到数据库连接池吗
支付服务调订单服务需要鉴权是怎么操作的,约定https进行生效和失效
回调就是不知道什么时候自己才会被调用,使用连接池维护本服务和客户端连接,保持通信,所以订单服务等着支付服务来回调的话,也是用连接池维护通信吗
前端会有自己的local storage本地缓存来保存单号
验证能力会问的问题:
1 各层注解
2 项目配置
3 实现一个问题要怎么实现 线程池
4 spring怎么打jar包
5 开发过程中遇到什么问题
6 web中请求,怎么响应请求
1 什么情况下应该建索引,什么情况下不要建
以下情况适合创建索引:
1. 频繁作为where条件语句查询的字段,加快条件的判断速度,尤其是在数据量大的情况下,创建普通索引就可以通过where进行条件判断筛除不符合的数据,因而大幅提升数据查询的效率。
2. 经常需要搜索的列上加索引,可以加快查找的速度
2. 在经常用于连接两张表的列上,这些列主要是一些外键,可以加快连接的速度。
连接字段上创建索引是指在被驱动表上创建索引,因为是先从驱动表查出数据后,拿着数据根据ON的条件去被驱动表中查数据(这里面你可以理解为驱动表查出一条数据就去被驱动表中检索,但是实际上为了减少随机IO的发生,可能会触发MRR优化,就是先从驱动表查出多条数据,存在缓存中,这多条数据先按主键进行排序,再一下子到被驱动表中检索)
连接分为内连接和外连接,外连接的话,比较好判断被驱动表,左连接的左边就是驱动表,右边就是被驱动表,右连接与之相反,但是对于内连接我们是没发判断,驱动表和被驱动表的,因为在SQL执行前会mysql的查询优化器会分析两个表分别作驱动表和被驱动表时,谁的执行最好,最终是由查询优化器决定的,但是通常遵循”小表驱动大表“原则
哪些情况适合创建索引,哪些情况不适合创建索引_JAVA_侠的博客-优快云博客_什么情况下需要创建索引
3. 经常需要排序的字段(经常使用order by的列)可以建立索引,因为索引已经排好序,利用索引的排序缩短排序时间
4. 分组字段(经常使用group by的列)可以建立索引,因为分组的前提是排序
5. 统计字段可以建立索引,例如count(),max()
6. 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构。
7.在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的。
不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的
8
业务上具有唯一特性的字段(用DISTINCT) ,即使是组合字段,也必须建成唯一索引
9 使用字符串的前缀创建索引,并且把使用最频繁的列放到联合索引的左侧
10 比较短的字段,因为一个页的大小固定是16kb,索引越短,一个页中能够存储的数据就越多,而页是内存和磁盘交互的基本单位,那么IO从磁盘读到内存的数据就越多
11 在多个字段都要创建索引的情况下,通常联合索引优于单值索引。如果你没有对a、b建立联合索引,那么mysql也会尝试给你建立联合索引,并分析它的执行成本,这是mysql自身对索引的一种优化
不适合建立索引:
1
表记录太少
2
经常增删改的表
3
频繁更新的字段
4
数据重复且分布均匀的字段,唯一性太差,年龄性别这种
5 where条件中用不到的字段不适合,因为where本身就是在做筛选,where条件中用不到的字段做索引价值不大
6 参与列计算的列不适合
7 无序字段不适合。例如身份证、UUID(在索引比较时需要转为ASCII,并且插入时可能造成页分裂)、MD5、HASH、无序长字符串等。
什么叫页分裂
2 我应该对客户名称(占128位)建立索引吗,如果是根据客户名称去查找,有没有好的查询方案
3 外部用户想要拿去我们的用户简历,产品提出,任意一个登录的用户,在任意十分钟之内,访问职位详情页面(多次调接口)超过100次,在不暴露黑名单接口的前提下,后端能做什么 时间窗口限流
4 域名不变的情况下怎么去访问测试环境
我的理解:和分布式下调用服务是一样的,用openfeign
5怎么选择的优惠券
6 退换货的流程,取消订单的流程
是一样的吗,先通知哪个模块呢
7 请求到了服务端,带了一些参数A和B,这两个参数是怎么映射到controller的方法的
面试官想问我的是具体用哪个注解?用@RequestParam(“属性名”)
8 你觉得mysql单表到多大,多少条以后会去处理
9 生产者怎么让消费者发现自己
是用nacos吗
10 优化索引应该怎么做,什么样的是高质量的SQL呢
11 为什么会问我swagger的东西,现在大家本地测试都用什么?
12 mysql的版本 是8.0比较常用吗
实际项目中:
代码管理常用的命令、发布上线的过程、jar包是怎么管理(应该是大家统一一个包下载下来还是从库里去拿都行吗)、 Maven打包用的是什么(没明白这么问是什么意思)、线上jvm是多少(2G合适吗)
0823面试复盘
- 自我介绍需要说出业务场景和流程:2020年7月毕业,两年经验,用的语言是java,在前司负责的是商城中的订单模块和加购模块,订单模块就是用户提交订单以后,生成单号,根据单号和状态插入订单数据,扣减库存,锁住优惠券这一过程,负责数据库中的字段设计,参与了对于分布式事务模式的技术选型,对高并发下缓存的分布式锁做过加强设计;加购模块会根据用户需要把选中的商品信息放到对应购物车里,这一块主要是做数据库选型和设计
- 我们的话费推单系统,是作为中间平台承接上下游的系统,渠道来单之后,我们根据策略对下游的商户进行推单,状态比较复杂,订单量大的时候常规做只有10qps,要求是100,我们采取了一些措施提升到500
- 订单模块的并发量:高峰期下单接口qps有5000,tps1500,一天5k-2w订单量,公司做这个项目2年了,数据量在2000w左右
- redis锁实现定时任务,分布式环境集群下三个机器任意的话让他们竞争分布式锁,或者企业级就用xxljob定时任务,用它支持的轮询,广播,任意选的策略选中机器执行
- 下单模块里超时未支付处理,先放到rocketmq延时处理,为了避免消息丢失再用xxl job定时任务兜底
- 对于超卖问题,在用了分布式事务,加了分布式锁的基础上还会最后在代码层面写死where条件判断库存是不是大于零做最后防御编程,就是先悲观锁后乐观锁的思想
- 状态机为了在整个流程中实现状态流转,用户支付以后不能再支付等,状态机取代冗长的if else 判断,达到解耦目的,stateless4j
- 通知履约,用的是rocketmq延时,和本地消息表
研发:新的产品线,自研,技术栈:springcloud封装,集群,分布式,开发平台,自动化部署,深圳,南京,中高级负责前期设计,交付,新需求迭代
为什么用单表查询而不是连表查询:
1 用连接的形式去做连表查询,如果相互连接的几张表中有一个发生表结构的变更,那么整个join连接就会变得不可用,如果做的是复杂的连接,相当于推翻重来。用单表查询需要把join连接拆分成几个步骤,每次只需要修改一个步骤
2 对于缓存来说,用缓存主要是把查询的结果保存下次就可以直接跳过,而如果发生表结构的改变导致join连接语句失效,查询结果就会失效,缓存利用率低
3 连表查询效率低,join连接的连表查询需要以前一张表的结果集作为基础去和相连接的表记录依次比较,如果不走索引将会用笛卡尔积的方式导致查询量几何倍上涨,如果使用索引可能对程序员对索引有深刻理解,设计出好的索引
4 join查询会查询出很多不相干的数据导致数据冗余
5 后续数量增多需要分库分表,连表查询在这种情况下表现不好,一般需要把同一个事务中的几张表路由到一起
缓存和数据库的一致性,可以讲延时双删,一般做法都是追求最终一致性,使用监听binlog这种形式
别再问了,数据库与缓存一致性问题今天全整齐活了!_Redis_读取_旁路
1 缓存必须要有过期时间
2 缓存和数据库数据只需要最终一致即可,不需要强一致
每个分布式事务都要单独做幂等,幂等方案通过分布式锁+本地记录表,另外还有空回滚,空悬挂问题,seata通过本地数据库的行锁(悲观锁)实现
MySQL的两阶段提交
binlog适用于维护集群内数据的一致性,redo log用于崩溃恢复,undo log相对于前面两种日志更好理解些,就是为了回滚事务用的
RPC框架有哪些
1、RMI,远程方法调用;2、Hessian,基于HTTP的远程方法调用;3、Dubbo,淘宝开源的基于TCP的RPC框架。
在了解Dubbo之前,要先对Zookeeper有深入的理解,当理解了zookeeper后,Dubbo也就了无秘密了。
Dubbo的详细说明在淘宝开源里说的非常详细,在工作中很多生产项目都用了Dubbo,过程中也发现了很多需要注意的地方,尤其是那繁多的配置,设置不当都会让人烦脑,最好能再基于现有开源的Dubbo再定制优化一下。
netty和rpc的关系
完成RPC 需要两个协议,“序列化协议”和“调用控制协议”,还需要一个网络通信框架,而Netty就是一个网络通信框架。Netty和RPC有什么关系_大数据_收获啦
复习Redis
Redis单线程通过epoll实现IO多路复用,将连接信息和事件放到队列中,依次放入文件事件分派器,事件分派器将事件发送给事件处理器,Redis最多支持1w连接
mysql binlog的默认大小一般为1024M,一般情况下我们不会修改binlog大小,这样会导致每次重新构建canal服务端会花大量时间读取binlog,严重降低系统可用性。
AOP 简单总结:在什么地点 (Joinponit, 多个地点 Pointcut) 什么时间做什么 (Advice),Advice 封装了什么时候做、什么地方和做什么。
advice 是在 join point 上执行的, 而 point cut 规定了哪些 join point 可以执行哪些 advice。
首先定义一个这个业务逻辑接口的pointcut,需要在这个类(haveXXXpointcut)之前加@aspect注解,方法前加@Pointcut ("this (cn.freemethod.business.HaveResultBusiness)") 表达式注解,表示的是:实现了 cn.freemethod.business.HaveResultBusiness 接口的所有方法
定义一个Aspect类,在这个类上加注解@Aspect 表明这个类是一个切面,以及@Componet,这个类中定义了 5 中类型的 Advice (通知),每一个 Advice 使用cn.freemethod.pointcut.HaveResultBusinessPointcut.haveResultBusinessPointcut () 这一个 Pointcut。
然后写对应的配置。@
Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = {"
cn.freemethod"})
publicclass
AspectConfig
{}
如何通过 pointcut 和 advice 定位到特定的 joinpoint 上。advice就是做增强,根据对一个方法执行过程的不同时机设置不同的增强点,比如环绕,前置,后置。具体应用就是做鉴权,在用户登录之后http请求头携带的不同信息,可以为每个使用 RequestMapping 标注的方法织入 advice, 当 HTTP 请求到来时, 首先进入到 advice 代码中, 在这里我们可以分析这个 HTTP 请求是否有相应的权限, 如果有, 则执行 Controller, 如果没有, 则抛出异常. 这里的 advice 就扮演着鉴权拦截器的角色了.
Spring AOP概念Aspect、Advice、JoinPoint、JoinCut与Execution 转载
2018-04-03 09:39:33
 16点赞

Anur 
码龄6年
关注
基本知识
其实, 接触了这么久的 AOP, 我感觉, AOP 给人难以理解的一个关键点是它的概念比较多, 而且坑爹的是, 这些概念经过了中文翻译后, 变得面目全非, 相同的一个术语, 在不同的翻译下, 含义总有着各种莫名其妙的差别. 鉴于此, 我在本章的开头, 着重为为大家介绍一个 Spring AOP 的各项术语的基本含义. 为了术语传达的准确性, 我在接下来的叙述中, 能使用英文术语的地方, 尽量使用英文.
什么是 AOP
AOP(Aspect-Oriented Programming), 即 面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角.
在 OOP 中, 我们以类(class)作为我们的基本单元, 而 AOP 中的基本单元是 Aspect(切面)
术语
Aspect(切面)
aspect 由 pointcount 和 advice 组成, 它既包含了横切逻辑的定义, 也包括了连接点的定义. Spring AOP就是负责实施切面的框架, 它将切面所定义的横切逻辑织入到切面所指定的连接点中.
AOP的工作重心在于如何将增强织入目标对象的连接点上, 这里包含两个工作:
如何通过 pointcut 和 advice 定位到特定的 joinpoint 上
如何在 advice 中编写切面代码.
可以简单地认为, 使用 @Aspect 注解的类就是切面.
advice(增强)
由 aspect 添加到特定的 join point(即满足 point cut 规则的 join point) 的一段代码.
许多 AOP框架, 包括 Spring AOP, 会将 advice 模拟为一个拦截器(interceptor), 并且在 join point 上维护多个 advice, 进行层层拦截.
例如 HTTP 鉴权的实现, 我们可以为每个使用 RequestMapping 标注的方法织入 advice, 当 HTTP 请求到来时, 首先进入到 advice 代码中, 在这里我们可以分析这个 HTTP 请求是否有相应的权限, 如果有, 则执行 Controller, 如果没有, 则抛出异常. 这里的 advice 就扮演着鉴权拦截器的角色了.
连接点(join point)
a point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
程序运行中的一些时间点, 例如一个方法的执行, 或者是一个异常的处理.
在 Spring AOP 中, join point 总是方法的执行点, 即只有方法连接点.
切点(point cut)
匹配 join point 的谓词(a predicate that matches join points).
Advice 是和特定的 point cut 关联的, 并且在 point cut 相匹配的 join point 中执行.
在 Spring 中, 所有的方法都可以认为是 joinpoint, 但是我们并不希望在所有的方法上都添加 Advice, 而 pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice.
关于join point 和 point cut 的区别
在 Spring AOP 中, 所有的方法执行都是 join point. 而 point cut 是一个描述信息, 它修饰的是 join point, 通过 point cut, 我们就可以确定哪些 join point 可以被织入 Advice. 因此 join point 和 point cut 本质上就是两个不同纬度上的东西.
advice 是在 join point 上执行的, 而 point cut 规定了哪些 join point 可以执行哪些 advice
introduction
为一个类型添加额外的方法或字段. Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现). 例如我们可以使用 introduction 来为一个 bean 实现 IsModified 接口, 并以此来简化 caching 的实现.
目标对象(Target)
织入 advice 的目标对象. 目标对象也被称为 advised object.
因为 Spring AOP 使用运行时代理的方式来实现 aspect, 因此 adviced object 总是一个代理对象(proxied object)
注意, adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类.
AOP proxy
一个类被 AOP 织入 advice, 就会产生一个结果类, 它是融合了原类和增强逻辑的代理类.
在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象.
织入(Weaving)
将 aspect 和其他对象连接起来, 并创建 adviced object 的过程.
根据不同的实现技术, AOP织入有三种方式:
编译器织入, 这要求有特殊的Java编译器.
类装载期织入, 这需要有特殊的类装载器.
动态代理织入, 在运行期为目标类添加增强(Advice)生成子类的方式.
Spring 采用动态代理织入, 而AspectJ采用编译器织入和类装载期织入.
advice 的类型
before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
after return advice, 在一个 join point 正常返回后执行的 advice
after throwing advice, 当一个 join point 抛出异常后执行的 advice
after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice
为什么加分布式锁:同时扣减库存,同时修改订单状态
Redis购物车如果价格发生变化,Redis里面存的数据只有加购的数量,只有在下单的时候才会涉及得到价格,会根据商品id从数据库里查询价格
下单得到的价格是从数据库拿的,商详页是缓存里拿的,当商详页价格未过期数据库价格已经更新这时候不一致,但是用户发现下单的价格和当时看到的商品价格不一致,解决:商品信息发生变化删除缓存。确保缓存一定会删除掉:mq进行ack确认。
冷热分离:冷数据一天一更,24小时不查询会自动删除,热数据查询一次会做一次延期
Binlog订阅是有定位的,如果每次都是把全量1024M的数据同步会对mysql有负担(超过1%),所以我们可以手动修改服务端的meta.dat文件缓存的binlog定位并重启,也可以修改cannal服务端 instance.properties,查看mysql主库当前binlog定位以及获取时间戳,之后在canal的 instance.properties中进行修改
Volatile关键字:禁止重排(DCL) 可见性 寄存器-主内存
读屏障(普通读)volatile读之前不能有写
写屏障 volatile写互斥不得重排
其底层实现原理是由于java内存模型(jmm)中的封装了8个交互操作。
read:把一个主内存中的值传递到工作内存,以便load动作使用
load:把read操作从主内存获取的内存变量赋值到工作内存的变量副本
use:将工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的字节码指令的时候将会执行这个操作。
assign:从执行引擎接受到的值赋给工作内存的变量,当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store:他把工作内存中的一个变量的值传送到主内存中,以便随后的write操作使用
write:把store操作从工作内存中得到的变量的值放入主内存的变量中
lock:作用于主内存的变量,把一个变量标示一条线程独占的状态。
Unlock:作用于主内存,把一个处于锁定状态的变量释放出来。释放后的变量才可以被其他线程锁定。
原文链接:单例模式双重检测锁详解以及为何双重检测_我钟意饮王老菊的博客-优快云博客_单例模式为什么要双重检查
解决CAS的ABA问题:线程1使用CAS修改初始值为,A的变量,首先获得这个变量的值,预期使用CAS操作修改为B,线程2获得时间片将变量修改为B,随后又修改为A,当线程1来了之后发现值相等执行修改java中的CAS操作以及ABA问题_mz♪的博客-优快云博客
解决:原子类中的AtomicStampedReference和AtomicMarkableReference
线程状态线程的生命周期?线程有几种状态_猿始大猩猩的博客-优快云博客_线程生命周期的五种状态
事务注解失效
spring事务什么时候会失效_nanyan_xixi的博客-优快云博客_spring事务什么时候失效
spring声明式事务是基于AOP,AOP的实现原理是动态代理,要通过代理的方式获取到代理的具体对象。如果方法无法重写,就无法被代理。所以static和final修饰方法也同样不能支持事务。
TCC模式有什么问题:
空回滚空悬挂
TCC工作模型图:
怎么自己写一个starter:
https://blog.youkuaiyun.com/qq_42145871/article/details/96477389?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AAstarter&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-6-96477389.nonecase&spm=1018.2226.3001.4187
Starter叫做场景启动器,SpringBoot 最强大的功能就是把我们常用的场景抽取封装成了一个个starter
。依赖starter能够实现自动配置。
具体实现思路:仿照
常见的自动配置类WebMvcAutoConfiguration
编写自动配置类
1 首先我们使用IDEA的Spring Initializr直接创建工程,引入web依赖
<artifactId>spring-boot-starter-web
</artifactId>
2 然后自带的删掉启动类,测试类,maven插件。我们创建的starter属于依赖包,不需要启动类。否则在后续打jar包的时候会报错
3 创建配置类,可以为引入的starter配置相关属性
定义配置类绑定属性、业务逻辑类、自动配置类
我们需要以下这些注解:
@Configuration //指定这个类是一个配置类
@ConditionalOnXXX //在指定条件成立的情况下自动配置类才生效@AutoConfigureOrder //指定自动配置类的优先级
@AutoConfigureAfter //指定自动配置类的顺序
@Bean //给容器中添加组件
@ConfigurationPropertie //将application.properties文件中前缀为xx的属性来绑定映射到这个xxxProperties.java类对应的属性上
@EnableConfigurationProperties//让标注了@ConfigurationProperties(prefix = "xx")的配置类生效。
4 写完配置类让其生效: 在resources下创建META-INF目录,在其下面创建spring.factories文件,在spring.propertites里面声明配置类路径:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.anhe.springbootstarteruser.properties.UserAutoConfigure
或用注解:@EnableClientConfiguration
5 并同步提示信息:
在META-INF下创建文件spring-configuration-metadata.json,在其他项目配置的时候就会自动出现提示,包括配置的默认值。
自定义starter案例
动态数据源切换
AOP切面日志拦截处理
主从库读写分离数据源
Swagger接口文档配置
各文件含义:spring.factories该文件用来定义需要自动配置的类,SpringBoot启动时会进行对象的实例化,会通过加载类SpringFactoriesLoader加载该配置文件,将文件中的配置类加载到spring容器
客户端和服务端如何建立网络连接
客户端和服务端之间基于TCP协议建立网络连接最常用的途径有两种
- HTTP通信
- Socket通信
服务端如何处理请求面试官问我:如何理解BIO、NIO、AIO的区别?我差点拉胯! - 知乎
- 同步阻塞方式 (适用于连接数比较小的业务场景)
- 同步非阻塞方式 (用于连接数比较多并且请求消耗比较轻的业务场景,比如聊天服务器)
- 异步非阻塞方式 (用于连接数比较多而且请求消耗比较重的业务场景)
socket网络通信模型之select与epoll
IO复用之select、poll、epoll模型_wh柒八九的博客-优快云博客
一篇学会阿里面试问的 Select、Poll、Epoll 模型-51CTO.COM
线程池参数设置技巧
workQueue 阻塞队列
LinkedBlockingQueue:无界队列,容量Integer.MAX_VALUE,(FixedThreadPool 和 SingleThreadExecutor 线程池的线程数是固定的,用的就是LinkedBlockingQueue)
SynchronousQueue:直接提交的任务队列(CachedThreadPool 的最大线程数是 Integer.MAX_VALUE,用的是SynchronousQueue)
ArrayBlockingQueue:指定队列大小(corePoolSize >队列>maximumPoolSize )
缓冲区Buffer编程实现 JAVA Nio编程 - Buffer_北堂飘霜的博客-优快云博客_buffer编程
JVM内存结构是处于Java虚拟机层面的,是运行时对Java进程占用的内存进行的一种逻辑上的划分,通过不同数据结构来对申请的内存进行不同使用。对操作系统来说,本质上JVM还是存在于主存中。
JMM屏蔽了不同操作系统差异,是跨平台可用的内存模型,用来描述线程的数据在何时从主内存读取,何时写入主内存,解决线程间数据共享和传递的问题
JMM是围绕着并发编程中原子性、可见性、有序性这三个特征来建立的
操作线程的方法:
线程常用方法大全_study happy的博客-优快云博客_线程方法
Future类的五个方法
(Java并发基础)Future类的使用_gonghaiyu的博客-优快云博客_future类的用法
Jdk7和8的区别
抽象类和接口的区别 一个类可以实现多个接口
抽象类中的成员变量可以实现多个权限 public private protected final等,接口中只能用 public static final修饰。
java中的类只能单继承,即一个父类可以有多个子类,但是一个子类只能有一个父类
一个类可以实现无限个接口
如果一个类之中只是由抽象方法和全局常量所组成的,那么在这种情况下不会将其定义为一个抽象类,而只会将其定义为接口,所以所谓的接口严格来讲就属于一个特殊的类,而且这个类里面只有抽象方法与全局常量。
设计模式:
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
对象实例化的方式有:1、使用new关键字;2、使用Class类的newInstance方法;3、使用Constructor类的newInstance方法;4、使用clone方法;5、使用反序列化。
1、使用new关键字
这是最常见也是最简单的创建对象的方式了。通过这种方式,我们可以调用任意的构造函数(无参的和带参数的)。
2、使用Class类的newInstance方法
我们也可以使用Class类的newInstance方法创建对象。这个newInstance方法调用无参的构造函数创建对象。
3、使用Constructor类的newInstance方法
和Class类的newInstance方法很像, java.lang.reflect.Constructor类里也有一个newInstance方法可以创建对象。
newInstance方法内部调用Constructor的newInstance方法。这也是众多框架,如Spring、Hibernate、Struts等使用后者的原因。
4、使用clone方法
无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。
要使用clone方法,我们需要先实现Cloneable接口并实现其定义的clone方法。
5、使用反序列化
当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。
我们经常用到的Ping命令,来测试主机之间是否联通,原理就是UDP协议
Throws:谁调用谁处理,可以由方法的调用者进行处理,方法的定义上使用 throws 表示这个方法可能抛出某种异常
Throw:方法内的语句抛出异常,如果异常对象是非 RuntimeException(即受检异常) 则需要在方法申明时加上该异常的抛出 即需要加上 throws 语句 或者 在方法体内 try catch 处理该异常,否则编译报错
受检异常就是不处理过不了编译,非受检异常有比如空指针异常,除数为零时产生的 ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常
赋值和浅拷贝和深拷贝:
赋值指的是赋值一个栈空间的地址,这个地址也指向堆空间。 B = A,把的值赋给B,赋值之后的对象B改变,原赋值对象A也会改变,因为他们共用一个堆空间
浅拷贝 :只复制指向某个对象的指针,而不复制对象本身,相当于是新建了一个对象,该对象复制了原对象的指针,新旧对象还是共用一个内存块
深拷贝:是新建一个一模一样的对象,该对象与原对象不共享内存,修改新对象也不会影响原对象
为什么要有反射,因为反射允许动态获取对象的属性和方法,对象通过编译之后成了一个静态的存在,如果在程序运行过程中需要动态地生成新的对象,获取对象的属性和方法,就可以使用反射。反射是获取类的字节码信息进行属性或者新对象的创造
比如@TableName这个注解表示当前的实例类Student对应的数据库中的哪一张表。
@TableName("t_student")public class Student { public String nickName; private Integer age;}
通过反射获取与@TableName的value值
// 获取表名
TableName table = object.getClass().getAnnotation(TableName.class);
获取映射关系,类定义与数据库表的对应关系,根据表名,字段名构建SQL的增删改查,相当于实现mybatis的这一功能
Spring 通过 XML 配置模式装载 Bean 的过程:
- 将程序内所有 XML 或 Properties 配置文件加载入内存中
- Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息
- 使用反射机制,根据这个字符串获得某个类的Class实例
- 动态配置实例的属性
java web三大器:过滤器、拦截器、监听器 (spring框架)
java web中监听器、过滤器、拦截器以及AOP的先后顺序和理解 - 远洪 - 博客园
mybatis实现分页的几种方式
mybatis实现分页的几种方式_走出半生仍是少年的博客-优快云博客_mybatis 分页
40 个 SpringBoot 常用的注解,你知道几个
个人介绍
平时怎么学习,工作经历,项目怎么介绍,个人的优势
系统性回答好一个问题:概念的定义,来源,为了解决什么问题,怎么解决的这个问题,有哪些优化,如何应用
项目的架构是什么样的,实现细节,正常流程和异常流程的处理,难点,怎么做复盘和优化的
简历上的内容做到熟悉
分布式会有的一些问题
配置管理
服务发现
断路器
智能路由
微代理
控制总线
一次性令牌
全局锁
领导选举
分布式会话
群集状态
Sentinel:流控,断路,系统自适应保护
Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC (remote procedure call 远程过程调用)实现服务的输出和输入功能,可以和 [1] Spring框架无缝集成。
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
gRPC 由谷歌开发的一个高性能开源RPC框架,基于HTTp/2协议标准开发。
dubbo和springcloud都是通常意义上的rpc框架,只是springcloud采用http协议,dobbo基于tcp。Dubbo 底层是使用 Netty 这样的 NIO (NIO有两种解释: 一种是非阻塞IO(Non-blocking I/O) 一种是新的IO(New I/O) )框架,是基于TCP 协议传输的,配合以 Hession 序列化完成 RPC 通信。而 SpringCloud 是基于 Http 协议+Rest 接口调用远程过程的通信,相对来说,Http 请求会有更大的报文,占的带宽也会更多。但是REST 相比 RPC 更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖。
关于数据一致:mysql的半同步和异步都是不能保证强一致的
Redis的paxos协议等能做强一致
事务:
1 事务的acid含义
在关系数据库中,一个事务可以是一条SQL语句、一组SQL语句或整个程序。
事务是维护数据库完整性的基础。事务具有原子性,一致性,隔离性,持久性。
原子性:事务必须是一个自动工作的单元,要么全部执行,要么全部不执行。在一个事务中有2条sql语句,一条是修改订单表状态,一条是修改库存表库存-1 。 如果在修改订单表状态的时候出错,事务能够回滚,数据将恢复到没修改之前的数据状态,下面的修改库存也就不执行,这样确保你关系逻辑的一致,安全。
一致性:事务结束的时候,所有的内部数据都是正确的。
隔离性:并发多个事务时,各个事务不干涉内部数据,处理的都是另外一个事务处理之前或之后的数据。
持久性:事务提交之后,数据是永久性的,不可再回滚。
2 acid实现
3 mysql如何开启事务,将SQL放入事务中
在SQL语言中,定义事务的语句有三条:
BEGIN TRANSACTION COMMIT ROLLBACK
例子:
begin transaction T1
update student
set name='Tank'
where id=2006010
delete from student
where id=2006011
Commit
其实现形式就是将普通的SQL语句嵌入到Begin Tran…Commit Tran 中(或完整形式 Begin Transaction…Commit Transaction),当然,必要时还可以使用RollBack Tran 回滚事务,即撤销操作。
4 java代码层面,封装了jdbc的mybatis如何使用
注解实现CRUD
5 为什么会有锁
在多用户都用事务同时访问同一个数据资源的情况下,就会造成以下几种数据错误。
更新丢失:多个用户同时对一个数据资源进行更新,必定会产生被覆盖的数据,造成数据读写异常。
脏读:第一个事务读取第二个事务正在更新的数据表,如果第二个事务还没有更新完成,那么第一个事务读取的数据将是一半为更新过的,一半还没更新过的数据,这样的数据毫无意义。
不可重复读:如果一个用户在一个事务中多次读取一条数据,而另外一个用户则同时更新了这条数据,造成第一个用户多次读取数据不一致。
幻读:第一个事务读取一个结果集后,第二个事务,对这个结果集经行增删操作,然而第一个事务中再次对这个结果集进行查询时,数据发现丢失或新增。
锁就是为解决这些问题所生的,他的存在使得一个事务对他自己的数据块进行操作的时候,而另外一个事务则不能插足这些数据块。这就是所谓的锁定。
6 避免死锁
死锁耗时耗资源,然而在大型数据库中,高并发带来的死锁是不可避免的,所以我们只能让其变的更少。
按照同一顺序访问数据库资源,上述例子就不会发生死锁了
让事务尽可能简短,尽量不要让一个事务处理过于复杂的读写操作。事务过于复杂,占用资源会增多,处理时间增长,容易与其它事务冲突,提升死锁概率。
尽量不要在事务中要求用户响应,比如修改新增数据之后在完成整个事务的提交,这样延长事务占用资源的时间,也会提升死锁概率。
尽量减少数据库的并发量。
尽可能使用分区表,分区视图,把数据放置在不同的磁盘和文件组中,分散访问保存在不同分区的数据,减少因为表中放置锁而造成的其它事务长时间等待。
避免占用时间很长并且关系表复杂的数据操作。
使用较低的隔离级别,因为持有共享锁的时间更短。这样就减少了锁争用。
7 出现了数据库语句死锁怎么排查
死锁是不同事务之间抢占数据资源造成的。
INNODB_LOCKS, INNODB_LOCK_WAITS, INNODB_TRX是MYSQL中事务和锁相关的表。通常我们遇到事务超时或锁相关问题时,直接运行下面SQL语句即可进行简单检查:
–查看事务 这张表显示事务的属性,或者性质,包括事务的起止时间,被锁的锁ID,事务权重,事务属于的隔离级别,事务执行状态,事务插入和修改的行数
select * from information_schema.INNODB_TRX;
–查看锁 ID,锁的类型,锁住的页码
select * from information_schema.INNODB_LOCKS;
–查看锁等待 造成该事务死锁的其他事务和锁住当前事务的锁
select * from information_schema.INNODB_LOCK_WAITS;
mysql查看锁和事务_头未秃的博客-优快云博客_mysql查询事务锁
排查实例:
mysql锁问题排查_MySQL死锁原因排查技巧详解_天马行空的味道的博客-优快云博客
https://blog.youkuaiyun.com/chixushuchu/article/details/85124422?ops_request_misc=&request_id=&biz_id=102&utm_term=mysql%E5%A6%82%E6%9E%9C%E5%8F%91%E7%94%9F%E6%AD%BB%E9%94%81%E4%BA%86%EF%BC%8C%E6%88%91%E4%BB%AC%E6%80%8E%E4%B9%88%E5%8E%BB%E6%A3%80%E6%B5%8B%E5%85%B7%E4%BD%93%E5%8F%91%E7%94%9F%E6%AD%BB%E9%94%81%E7%9A%84%E6%98%AF%E5%93%AA%E6%9D%A1&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-85124422.142^v63^js_top,201^v3^control_1,213^v1^t3_control2&spm=1018.2226.3001.4187
购物车:
1 购物车需要获取用户登录状态才能对加购商品做展示。在前端项目中,使用vue的axios插件发送http请求时设置拦截器,每次向后端发送http请求都需要从cookie中获取登录的用户信息,然后放到http请求的header头部中。
用cookie而不是store中的state(js代码实现),是因为一旦用户F5刷新,先前申请的内存会被释放,然后需要重新加载js脚本计算,而cookie绕过了重新计算的过程,提升了客户端的运行效率。
使用cookie的劣势:客户端可以自行清空cookie,甚至修改cookie。如果对客户端数据持久化、安全性有要求,可以考虑localstorage本地存储空间以及sessionstorage会话存储空间
2 前端发送的http请求发往gateway模块,gateway模块会拿到用户的token进行校验,主要监测用户的登录状态是否超时
GatewayFilterChain这个类
Gateway做的事情:实现一个AuthorizationFilter过滤器 1解析出这个Authorization中的请求头 2 判断请求头是否为空,是的话抛异常 3 校验jwt是否正确或者超时都会抛异常 4 把从jwt中的用户登录信息存储到请求头中
这个校验过程是Oauth2.0实现的
3 在gateway这个校验过程通过之后,把前端传过来的参数添加到http请求的header中,继续往后端服务发送
4 后端从请求头中拿出(通过@RequestHeader注解中获取请求头中的参数)
5 前端请求发送到后端后,后端微服务之间会进行频繁的服务调用。在这些服务调用之间,依然需要同 步用户的登录状态。在当前电商项目中,会采用拦截器统一添加请求头的方式,在后端微服务之间继续 传递用户状态。例如在购物车模块tulingmall-cart中,添加了一个拦截器,在需要进行基于feign的微服务RPC调用时, 统一将memberID添加到请求头中,这样目标服务也可以通过请求头拿到登录的用户ID。
6 mysql同时100个连接就可能挂了 Redis可以达到5w以上
Mysql并发量小,JSON数据不好存储,随机读性能太低,行式存储不便于做数据统计订单量,对模糊匹配支持不好,每次修改一个字段都需要改变表结构
多线程:
1 线程来源:os中的pthread库,提供线程调度的一些方法
2 怎么调度:
thread.sleep()\start() object.run()\wait()\join()\notify()\notifyAll()\wait()
LockSupport.parkUntil()\parkNanos()\unpark()
3 线程状态
4 线程创建
5 线程中的协程思想,体现在程序计数器,保存栈信息,只是任务的切换而不是创建新的线程。而内核态指的是线程的调度创建和销毁
6 线程池
7 线程池中的接口定义线程池的行为
8 线程池的状态
9 具体的线程池 任务提交的两个方法
10 线程池监控
11 ctl 记录的是runstate和workerCount
12 线程池中的线程是怎么创建的 addWorker(),由Worker创建线程而不是去new一个线程,worker去调threadfactory
https://www.jianshu.com/p/cb414cf3f098
rocketmq消息不丢失
https://blog.youkuaiyun.com/weixin_34868242/article/details/113153016
事务传播行为
定时任务 1 耗费比较大,需要扫描所有的表再比较超时时间。2 很难把握这个时间,3分钟看一次好,还是5分钟看一次好不确定。就时间如果太长的话,就是说你那个订单可能现在就过期了,但是你过了好久,它的状态它实际上过期了,但是状态还没有变成过期状态。然后如果太短了,就是你需要频繁地去扫描这些待支付的订单,这个开销就比较大。
主动取消订单,是强一致还是最终一致? 最终一致性,因为所有操作肯定都会成功。注意最终一致在秒级,或者毫秒级都是有可能的。
正向下单和逆向下单的区别:
延时任务,就像给每一个订单设置了一个闹钟,时间到了提醒我们去查看订单状态有没有支付,没有支付就拿出消息中的订单ID到数据库中把订单状态修改为过期。如果支付了,那就什么都不干,这个消息就当成功消费了,就这样。
Rocket MQ 有 1 分钟过期,两分钟过期,5分钟过期,10分钟过期。这种对它相当于不同的时间,它对应的是不同的队列。rocketmq开源版本最长时间只能是两个小时。
不能用rabbitmq的死信队列,是因为在一个队列里,10分钟过期的订单排在5分钟过期订单前面的时候,5分钟的那个是取不出来的,必须和10分钟那个消息一起取。
redis本身是有一个功能,当这个key快要过期的时候,通过监听可以得到它给出的通知
Rocket MQ 有 1 分钟过期,两分钟过期,5分钟过期,10分钟过期。这种对它相当于不同的时间,它对应的是不同的队列。
根据别人经验猜测面试高频:
当时花了几个月充分准备,十六个字,增加深度、扩展广度、覆盖死角、挖掘项目。介绍下当时具体的准备。
花了近2个月时间,Spring源码,手画核心流程不少于3遍,面试前做到能熟练讲述Spring容器启动流程、事务切面流程、MVC流程。
两周时间,学习MyBatis源码,做到ORM框架主流程心中有数,这块学得不是特别深。
花了很长的时间深入的学习JVM和并发编程,尤其是并发编程这块,从不同角度深入理解并发编程。最终进字节这块起了很大作用。对锁有深入认识,阅读JVM synchronize关键字源码不下10遍,对其中的大部分细节了然于胸,绘制了流程图,大家可以参考看看。当然,各类其他锁也都有学习源码,能深入比较各类锁的特点。
- 一周恶补MySQL,对于索引、锁、SQL优化重点掌握(深度略有不足,后期面试过程中有一定加强)
- 面试前,一周时间突击了网络编程,重点在基本知识和Java原生网络编程方面,Netty略微学了一点。增加深度就是主要从以上6个专题的学习体现的。都是Java开发必备的基本技能,一定要有深度。
下面讲下扩展广度,各类知识广泛涉猎。
- 面试前穿插一周时间学习Redis,redis我是用过的,但是用得很浅,专题学习下来收获很大,开阔思路。重点在redis应用和高可用架构。
- 同时穿插一周学习分布式事务、分布式锁等等解决方案
- 两三天时间Dubbo基础,放弃源码部分,掌握RPC框架核心思想
- 两三天时间ZK基础,放弃源码部分,掌握ZK精华,及常问面试题
- 了解一些MQ的相关基础知识,没有过多学习,主要是没时间了
以上几个分布式相关专题略有涉猎,多年分布式的应用经验,需要有一定的知识广度,才能做出较合理的技术设计。
杀手锏:
介绍两点,准备杀手锏和项目挖掘。杀手锏就是可以吊打面试官的内容。我当时准备了3个杀手锏,要做到能跟面试官就一个问题深入聊30分钟的程度。
- 第一个杀手锏是Java锁机制(非常高频的问题),因为我对JVM synchronize关键字源码非常熟悉,并整理了流程图,所以我从偏向锁讲起,升级轻量锁、重量锁,重偏向、撤销偏向条件,甚至最新的JDK版本废弃偏向锁的原因。字节的二面命中了这个杀手锏,刻意控制时间,只讲了10多分钟,之后再谈ReentrantLock、读写锁等等,我对这个问题的回答完全超出了面试官的预期,明显感觉他对我的回答非常满意。
- 第二个杀手锏是JVM垃圾回收(这个问题也很高频),这块不多赘述,把JVM的相关内容融会贯通,能自己讲半小时自成体系。
- 第三个杀手锏是Spring容器加载流程,要能默写出来核心流程,能以自己的语言有条理地讲出来。
这三个杀手锏面试各大公司都有用到,你可以准备适合自己的杀手锏,但一定要是高频问题,否则就是浪费时间了。
项目挖掘是说挖掘深度。
自己的项目必然受到当时各种因素所限,有很大局限性。但在面试时,一定要体现出来亮点。一半以上的面试官会问一个问题,“你做过的最有挑战或最难的项目是什么?”这道题目是考察你的技术深度的。一般回答可能是你用两三分钟讲一个项目,说几个特色,等着面试官追问。我是怎么做的呢?
面试前找一个最有挑战的项目,就是前面提到的订单系统。结合当时做的,和专题学到的内容,以及后来的反思,准备能讲至少15分钟的内容。
STAR法则,Situation创业百废待兴,Task负责订单系统设计及核心模块开发,Action界定系统边界、库表设计思路、如何分库、接口设计及缓存应用等等,Result系统划分清晰、SQL查询高效、满足较长期业务增长需要、订单缓存最终一致性设计得到验证等等。
最后再提出当时设计的缺陷不足,如果现在重新来做可以从哪些方面改进。这一个问题下来至少聊10多分钟,面试官有兴趣的话可以聊半个多小时,给面试官提其他问题的时间就不多了,实践下来效果非常好。我当时总共准备了两个项目。注意要套用到专题所学概念知识,要深入讲到点子上。
这里举个例子,当时订单查询加缓存,我套用专题知识,挖掘深度,讲“缓存一致性”上的考虑,采用事务后清除缓存的方式(避免事务未提交脏数据刷回),没有采用清除失败MQ补偿的原因(实际当时压根没想这个),兜底措施缓存1小时过期,保证数据“最终一致性”。
商品筛选:录入商品的时候就放到不同的集合里,根据关键词用SINTER做交集。其实也可以用ES做,根据关键词频率进行打分,Redis这样实现也还是比mysql快的
花了近2个月时间,Spring源码,手画核心流程不少于3遍,面试前做到能熟练讲述Spring容器启动流程、事务切面流程、MVC流程。
两周时间,学习MyBatis源码,做到ORM框架主流程心中有数,这块学得不是特别深。
收集的建议:
二线业务搞熟,心里有概念
真的招人,二线也会遇到难题
字节考算法题 15分钟内给思路并且写出来
头部大厂对标 销售型公司、淘菜菜、河马生鲜 CRM
腾讯社交
华为:设备部门大部分都是C语言
金融类 券商 交易系统
软件类
电商可迁移 不影响业务全局观 国美苏宁 新零售 本地连锁 线上平台 促进销售 业务场景和性能
审时度势 跟着时代潮流