《SpringBoot2.X+Vue+UniAPP,全栈开发医疗小程序》
《SpringBoot2.X+Vue+UniAPP,全栈开发医疗小程序》
https://segmentfault.com/a/1190000020258483 Linux命令
https://juejin.cn/post/7112826654291918855 nginx
steam:
oceanszd
Whz@!12zd
&developPWD1212
docker run -p 3306:3306 --name mysql
-e MYSQL_ROOT_PASSWORD=root
-d mysql:5.7
gitbook搜书
gitbook.io <spring实战>
Java基础
1、数组和集合的区别
数组固定大小
固定对象,而集合可以泛型
2、接口的作用
规范
3、面向对象的理解
面向对象 盖浇饭 菜 饭 菜饭分离 可替换
面向过程 蛋炒饭 一步一步做 准备蛋,准备饭,炒一下
4、设计模式的原则
5、mysql说的理解 乐观锁 悲观锁
6、mybatis的方法能重载吗
不能
7、for update会锁表吗,索引会锁行吗
8、rabbitmq实现延迟消息
通过延迟插件实现
message.getMessageProperties().setDelay(10 * 1000);
6、项目亮点
DepencyManagement应用场景:
在我们项目顶层的POM文件中,我们会看到dependencyManagement元素。通过它元素来管理jar包的版本,让子项目中引用一个依赖而不用显示的列出版本号。Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用在这个dependencyManagement元素中指定的版本号。
Stream
名词
JNDI:Java Naming and Directory Interface java命名与目录接口 在J2EE规范中是重要的规范之一
EJB:Enterprise Java Beans 企业级java beans技术
IDEA快捷键
Ctrl+F12 查找当前类的所有方法
JVM
双亲委派
Spring
为了降低 Java 开发的复杂性,Spring 采取了以下 4 种关键策略:
基于 POJO 的轻量级和最小侵入性编程;
通过依赖注入和面向接口实现松耦合;
基于切面和惯例进行声明式编程;
通过切面和模板减少样板式代码。
Spring的核心特性
1、核心特性
- IOC容器(IOC Container)
- Spring事件(Events)
- 资源管理(Resources)
- 国际化(i18n)
- 校验(Validation)
- 数据绑定(Data Binding)
- 类型转换(Type Conversion)
- Spring表达式(Spring Express Language)
- 面向切面编程(AOP)
2、数据存储
- JDBC
- 事务抽象(Transactions)
- DAO支持(DAO Support)
- O/R映射(O/R Mapping)
- XML编列(XML MArshalling)
3、Web技术
-
Web Servlet技术栈 同步
- Spring MVC Servlet技术引擎
- WebSocket
- SockJS
-
Web Reactive技术栈 异步
- Spring WebFlux 一般是netty的Web server技术引擎 当然也可Servlet引擎实现
- WebClient
- WebSocket
4、技术整合
- 远程调用(Remoting) 同步
- RMI
- Hessian的dubbo
- Java消息服务(JMS)异步
- Java连接架构(JCA)
- Java管理扩展(JMX)
- Java邮件客户端(Email)
- 本地服务(Tasks)
- 本地调度(Scheduling)
- 缓存抽象(Caching)
5、Spring的测试
- 模拟对象(Mock Objects)
- TestContext框架(TextContext FrameWork)
- Spring MVC测试
- Web测试客户端(WebTestClient)
Spring模块化设计
spring-aop
spring-aspects
spring-context-indexer
spring-context-support
spring-context
spring-core
spring-expression
spring-instrument
spring-jcl
spring-jdbc
spring-jms
spring-messaging
spring-orm
spring-oxm
spring-test
spring-tx
spring-web
spring-webflux
spring-webmvc
spring-websocket
1.1、spring-aop模块
面向切面编程时使用。Spring通过"横切"的方式将贯穿于多业务中的公共功能独立抽取出来,形成单独的切面,并指定切面的具体动作,在需要使用该功能时,动态地将该功能切入到需要的地方。
1.2、spring-aspects模块
用来实现AspectJ框架的集成。而AspectJ是一个通过对java扩展出之后的框架,框架里面定义了AOP的语法,通过特殊的编译器完成编译期间的代码织入,最后生成增强之后的Class文件。
1.3、spring-beans模块
完成Spring框架的基本功能,里面定义了大量和Bean有关的接口,类及注解。例如:bean定义的顶层接口BeanDefinition、bean装配相关的注解Autowired/Qualifier/Value、用来创建bean的工厂接口BeanFactory及一些具体的工厂方法等。
1.4、spring-context模块
用来实现Spring上下文功能,及Spring的IOC,例如初始化Spring容器时所使用的ApplicationContext接口及常用的抽象实现类AnnotationConfigApplicatoinContext或者ClasspathXmlApplicationContext等。
1.5、spring-context-indexer模块
用来创建Spring应用启动时候选组件的索引,以提高应用的启动速度。通常情况下,应用启动的时候会去扫描类路径下的所有组件,但是如果组件特别多,会导致应用启动特别缓慢。该模块可以在应用的编译器对应用的类路径下的组件创建索引,在启动的时候通过索引去加载和初始化组件,可以大大提升应用启动的速度。
1.6、spring-context-support模块
用来提供Spring上下文的一些扩展模块,例如实现邮件服务、视图解析、缓存(定义了对下面几种缓存的支持:caffeine,ehcache,jcache)、定时任务调度等。
1.7、spring-core模块
Spring的核心功能实现,例如:控制反转(IOC)、依赖注入(DI)、asm以及cglib的实现。
1.8、spring-expression模块
提供Spring表达式语言的支持,SPEL。
1.9、spring-framework-bom模块
通过该模块,可以解决Spring中的模块与其他框架整合时产生jar包版本的冲突,默认为空实现。
1.10、spring-instrument模块
实现Spring对服务器的代理接口功能实现,实现的是类级别或者ClassLoader级别的代理功能。
1.11、spring-jcl模块
通过适配器设计模式实现的一个用来统一管理日志的框架,对外体统统一的接口,采用"适配器类"将日志的操作全部委托给具体的日志框架,提供了对多种日志框架的支持。
1.12、spring-jdbc模块
Spring对JDBC(Java Data Base Connector)功能的支持,里面定义了用于操作数据的多种API,常用的即:JdbcTemplate,通过模板设计模式将数据库的操作和具体业务分离,降低了数据库操作和业务功能的耦合。
1.13、spring-jms模块
对Java消息服务的支持,对JDK中的JMS API进行了简单的封装。
1.14、spring-messaging模块
实现基于消息来构建服务的功能。
1.15、spring-orm模块
提供了一些整合第三方ORM框架的抽象接口,用来支持与第三方ORM框架进行整合,例如:MyBatis,Hibernate,Spring JPA等。
1.16、spring-oxm模块
Spring用来对对象和xml(Object/xml)映射的支持,完成xml和object对象的相互转换。
1.17、spring-test模块
Spring对Junit测试框架的简单封装,用来快速构建应用的单元测试功能及Mock测试。
1.18、spring-tx模块
Spring对一些数据访问框架提供的声明式事务或者编程式事务(通过配置文件进行事务的声明)的支持。例如:Hibernate,MyBatis,JPA等。
1.19、spring-web模块
用来支持Web系统的功能。例如:文件上传,与JSF的集成,过滤器Filter的支持等。
1.20、spring-webflux模块
Spring5中新增的一个通过响应式编程来实现web功能的框架。内部支持了reactive和非阻塞式的功能,例如可以通过tcp的长连接来实现数据传输。webmvc的升级版,webmvc是基于servlet的,而webflux是基于reactive的。
1.21、spring-webmvc模块
用来支持SpringMVC的功能,包括了和SpringMVC框架相关的所有类或者接口,例如常用的DispatcherServlet、ModelAndView、HandlerAdaptor等。另外提供了支持国际化、标签、主题、FreeMarker、Velocity、XSLT的相关类。注意:如果使用了其他类似于smart-framework的独立MVC框架,则不需要使用该模块中的任何类。
1.22、spring-websocket模块
Spring对websocket的简单封装,提供了及时通信的功能,常用于一些即时通讯功能的开发,例如:聊天室。
Spring各版本和Java语法特性
JDK核心API
Spring编程模型
1、面向对象编程
2、面向切面编程
3、面向元编程
4、函数驱动
5、模块驱动
Spring核心价值
Spring面试题
什么是SpringFramework
spring让应用开发更方便。在java开发应用时它提供了你所需要的任何环境。
SpringFramework有哪些核心模块
spring-core:一些基础的api模块,资源管理(Resource类),泛型的处理(GenericTypeResolver类)
spring-beans:依赖查找(BeanFactory接口的getBean方法)、依赖注入(AutowiredAnnotationBeanPostProcessor类处理依赖注入)
spring-aop:动态代理,AOP字节码提升
spring-context:事件驱动(ApplicationEvent)、注解驱动(Component、ComponentScan注解),模块驱动(EnableCaching注解)等
spring-expression:Spring表达式语言模块
Spring IOC
IoC 容器的职责
IOC容器的实现
传统IOC 容器的实现
轻量级IOC
管理应用代码的运行,启停 生命周期
快速的启动
容器不需要特殊的配置去进行操作
容器能达到轻量级的内存占用以及最小化API的一个依赖
释放掉一些容器
最大化代码的复用
更大程度的面向对象
更大化的产品化
依赖查找 VS 依赖注入
构造器注入VS. Setter 注入
IOC面试题
什么是 IoC ?
IoC 是反转控制,依赖查找和依赖注入
IOC:控制反转
容器控制对象的生命周期,以及业务对象之间的依赖关系
控制:将对象的控制权交给了Spring,spring控制对象的生命周期,处理对象之间的依赖关系
反转:我们要使用某个对象,不是new创建,而是从容器中获取
DI:依赖注入 依赖的对象无须手动赋值setXXX,而是通过配置赋值
依赖查找和依赖注入的区别?
答:依赖查找是主动或手动的依赖查找方式,通常需要依赖容器或标准API实现。而依赖注入则是手动或自动依赖绑定的方式,无需依赖特定的容器和API
Spring 作为 IoC 容器有什么优势?
典型的 IoC 管理,依赖查找和依赖注入
AOP 抽象
事务抽象
事件机制
SPI 扩展
强大的第三方整合
易测试性
更好的面向对象
Spring IoC 依赖查找
• 根据 Bean 名称查找
• 实时查找
• 延迟查找
• 根据 Bean 类型查找
• 单个 Bean 对象
• 集合 Bean 对象
• 根据 Bean 名称 + 类型查找
• 根据 Java 注解查找
• 单个 Bean 对象
• 集合 Bean 对象
Spring IoC 依赖注入
• 根据 Bean 名称注入
• 根据 Bean 类型注入
• 单个 Bean 对象
• 集合 Bean 对象
• 注入容器內建 Bean 对象
• 注入非 Bean 对象
• 注入类型
• 实时注入
• 延迟注入
Spring IoC 依赖来源
• 自定义 Bean
• 容器內建 Bean 对象
• 容器內建依赖
BeanFactory和ApplicationContext谁才是IOC容器
• BeanFactory 是 Spring 底层 IoC 容器
• ApplicationContext 是具备应用特性的 BeanFactory 超集
ApplicationContext 除了 IoC 容器角色,还有提供:
• 面向切面(AOP)
• 配置元信息(Configuration Metadata)
• 资源管理(Resources)
• 事件(Events)
• 国际化(i18n)
• 注解(Annotations)
• Environment 抽象(Environment Abstraction)
Spring零碎知识点
1、什么是Spring:IOC(DI) AOP
2、Spring特性:
容器化:容器化管理对象的生命周期
组件化:
非侵入性:
易于整合(因为IOC、AOP)
3、Spring中getBean的方式 getBean(String beanId,Clazz clazz):通过beanId和Class获取对象
4、xml中bean标签:id、class、property
<bean id="phone998" class="com.wu.spring.Order">
<property name="productName" value="小米儿手机"></property>
<property name="price" value="998"></property>
</bean>
5、@Autowried:
实现原理:反射注入值
实现方式:先byType,再byName
Qualifier:
配合@Autowried使用,直接指定beanid,无需byType再byName
6、组件扫描:
1、包含扫描
2、排除扫描
3、含有注解扫描
Spring IOC
IOC:控制反转
容器控制对象的生命周期,以及业务对象之间的依赖关系
控制:将对象的控制权交给了Spring,spring控制对象的生命周期,处理对象之间的依赖关系
反转:我们要使用某个对象,不是new创建,而是从容器中获取
DI:依赖注入 依赖的对象无须手动赋值setXXX,而是通过配置赋值
原理:
BeanFactory、ApplicationContext:BeanFactory
IOC创建的对象和new创建的对象有什么区别:
给对象注入值的三种方式:
set
构造
反射
bean的作用域:
sington:单例,创建容器时对象创建(即ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"))
prototye:多例,applicationContext.getBean("phone998", Order.class);时创建
Spring常用的注解
@Controller
@Service
@Mapper @Repository 数据访问层
@Autowired 自动装配
@RequestMapping 请求映射
@PathVariable 映射url 占位
@ResponseBody 返回json值
@Component 定义为组件
Bean初始化的过程
Spring中Bean的生命周期
https://www.jianshu.com/p/1dec08d290c1
实例化 Instantiation
属性赋值 Populate
初始化 Initialization
销毁 Destruction
构造器创建对象
di注入值 set or 反射
后置处理器处理前
初始化bean initMethod
后置处理器处理后
使用对象
容器关闭时,销毁对象 destroyMethod
spring循环依赖:
A、B对象相互依赖,彼此的完整对象创建需要互相依赖,A对象注入了B,B对象注入了A
创建A对象时,将未初始化完全的半成品A对象 赋值的时候提前暴露出来,然后创建B,B创建完成后,找到暴露的A完成整体的实例化。
三级缓存:三级缓存主要处理的是AOP的代理对象,存储的是一个ObjectFactory,是一个工厂
二级缓存;存放半成品对象的容器(解决循环依赖是要一个临时的容器存放半成品对象)
一级缓存:正式的对象
提前暴露 半成品对象
Spring的事务是怎么实现的,原理
@Transactional
声明式事务主要是通过aop实现的,
Connection的commit 和rollback
怎么保证一个事务中,数据不被别处修改
设置事务的隔离级别
Spring Aop
面向切面编程,
SpringBoot
自动装配
@EnableAutoConfiguration,
loadFactoryNames方法加载所有jar包下的META-INF/spring.factories文件,映射
spring.factories用键值对的方式记录了所有需要加入容器的类
启动流程
SpringCloud
Nacos的配置如何实现热更新
在 @Value 注入的变量所在类上添加注解 @RefreshScope
@ConfigurationProperties
Dubbo
Dubbo有哪些层级
10层
服务接口层 service层
服务配置层
服务代理层
服务调用层
服务注册层
服务监控层
Dubbo中,provider超时是3s,Consumer是1s,1s过去会超时吗
dubbo在服务端和消费端都可以设置接口的超时时间,如果同一个接口,两端都进行了设置,消费端的优先级要高于消费端。
Dubbo用的什么协议
用的dubbo协议,也支持http协议
单一长连接和NIO异步通讯,适用于小数据量大并发的服务调用
Dubbo连接和http连接的优缺点
Dubbo序列化
用的Hessian2
ArrayList和LinkedList
ArrayList查找快,增删慢,底层数组,index查找
LinkedList查找慢,增删快,底层链表,开销大,存数据之外还有引用
集合排序底层:
TimeSort:由归并排序(merge sort)和插入排序(insertion sort)组成,
当数组小于一定值时是二分插入排序(binarySort)
归并排序就是一直分,然后排序,最后合并
HashMap内部实现node[]数组
HashMap实现put:
计算key的hash值,看table[i]计算到的位置是否为空,为空直接插入,不为空去添加到链表后面。存在相同key时覆盖value即可
HashMap计算index:
先获取哈希值(哈希算法也叫散列,可以把任意长度值key'通过此算法变换成固定长度的key地址)hashcode,
将hashcode高18位二进制和低18位二进制进行异或运算,得到的结果再和数组长度-1进行与运算
HashMap扩容因子0.75,初始容量16,16*0.75=12,就是在12的时候会扩容至32,且扩容后会重新分配。
如何解决hash冲突
hash冲突就是同一数组下标,所以用链表解决。将新来的数据放在之前的数据的尾节点。而且节点过8之后就会
红黑树(左节点小于根节点,右节点大于根节点)化,提升查找效率。而节点小于6会链化。之所以不在8链化,
是为了防止左右横跳
HashMap的容量是2的倍数
算法需要(n-1)的值尽可能是1,即10000,减一之后是01111,这样计算的值相对离散,避免hash冲突
TreeMap实现sortmap接口,按照key升序排序
为什么用迭代器Iterator
遍历快、安全不暴露内部数据、不过可以用remove
concurrentHashMap线程安全的Hash Map
Collections.unmodifiableCollection(Collection c)方法创建一个只读集合,可以确保集合做参数时不被改变
为什么hashMap适合String、Integer这样的类做key
因为他们都是final修饰的类,保证了key的不可更改性,不会存在获取的hash值不同的情况
自身重写了hashcode()、equals()方法,遵循hashMap的内部规范
d
良好的编码习惯 注释 命名 测试demo
面试官你好,我叫吴昊智,今年26,有三年java开发经验,期间有过小组项目管理经验,就职过两家公司,一家主营房地产交易相关业务,一家主营电商业务。
平时会看博客与技术文档,喜欢研究新技术。个人性格外向开朗,有良好的沟通能力,有良好的编码习惯,对电商、短视频、直播比较熟悉。目前已属于离职阶段。
读写分离 主从复制
JVM和Tomcat调优
线程
一、线程的创建
线程的创建:
四种:
1、继承thread类
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
myThread1.start();
}
2、实现runnable接口
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
//启动 MyThread,需要首先实例化一个 Thread,并传入自己的 MyThread 实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
//事实上,当传入一个 Runnable target 参数给 Thread 后,Thread 的 run()方法就会调用
target.run()
public void run() {
if (target != null) {
target.run();
}
}
3、callable
4、
// 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running ..");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
多线程
同步锁 Sync lock
异步锁 分布式锁 redis
lock有condition 创建多把钥匙 await 和 signal 注意finally要unlock 可重入锁 读写锁
CompletableFuture!
- runAsync方法不支持返回值。
- supplyAsync可以支持返回值。
whenComplete可以处理正常或异常的计算结果,
exceptionally处理异常情况。
thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。
thenAccept方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。
多任务组合:
allOf:等待所有任务完成
anyOf:只要有一个任务完成
二、线程池
线程池的七大参数:核心线程数、最大线程数、队列、拒绝策略、keepAliveTime 空闲线程存活时间、存活时间单位、线程工厂(创建线程)用默认即可
拒绝策略:
1、AbortPolicy:中止策略:拒绝任务时,直接抛出异常,捕获异常决定是否重试或者放弃
2、DiscardPolicy:新任务被拒绝没有任何提升
3、DiscardOldestPolicy:抛弃执行时间最长的线程来执行任务
4、CallerRunsPolicy:新任务提交,线程池没有能力执行,用提交任务的线程执行
执行15个任务,只要三秒内的结果
三、ReentrantLock和synchronized区别
lock conndition 锁的粒度更小 可以创建多把钥匙 控制多个条件下线程的执行顺序
* jdk代理 :通过反射生成代理类,代理类实现接口,所以只能代理接口定义的方法
* CGlib代理:操作字节码生成子类,继承原有的类,所以final修饰的类不能代理,final修饰的方法不能代理
* AOP:springboot默认了用CGlib(配置spring.aop.proxy-target-class=true)
循环依赖 @Lazy 设计之初避免彼此依赖
Java特性:
面向对象(封装,继承,多态)
平台无关性(JVM运行.class文件)
语言(泛型,Lambda)
类库(集合,并发,网络,IO/NIO)
JRE(Java运行环境,JVM,类库)
JDK(Java开发工具,包括JRE,javac,诊断工具)
Java是解析运行吗?
不正确!
1,Java源代码经过Javac编译成.class文件
2,.class文件经JVM解析或编译运行。
(1)解析:.class文件经过JVM内嵌的解析器解析执行。
(2)编译:存在JIT编译器(Just In Time Compile 即时编译器)把经常运行的代码作为"热点代码"编译与本地平台相关的机器码,并进行各种层次的优化。
(3)AOT编译器: Java 9提供的直接将所有代码编译成机器码执行。
面向对象:
封装:类 把属性和方法封装
继承:继承父类的属性与方法
多态:List a = new ArrayList(); 父类引用指向子对象
抽象:把类的共同特征抽象出来
八大基本数据类型:
字符类:char
布尔类:boolean
数值类:整数型:byte short int long 浮点型:float double
Integer的默认值是null,int的默认值是0
==和equals()区别
==, 如果是基础数值类型比较的值相等,引用数值的话就是地址值,指向的是否为同一内存
equals,没有重写比较的是对象的引用,重写了比较的是对象的属性值
String被final修饰,不可继承与重写方法
StringBuffer:线程安全(synchronized )修饰,效率慢
StringBUilder:线程不安全,效率快
Object方法:equals clone hashcode toString wait notify
集合:
ArrayList:动态数组实现,get set 快
LinkedList:链表实现,remove insert 快
spring ioc:控制反转,spring用来帮我们管理创建bean的,管理bean之间的依赖关系,把应用从复杂的依赖关系剥离出来,我们创建对象的时候,只需要处理好配置或者注解
Nginx
nginx
反向代理:proxy_pass
负载均衡:upstream
upstream nginx_boot{
30s内检查心跳发送两次包,未回复就代表该机器宕机,请求分发权重比为1:2
server 192.168.0.000:8080 weight=100 max_fails=2 fail_timeout=30s;
server 192.168.0.000:8090 weight=200 max_fails=2 fail_timeout=30s;
这里的IP请配置成你WEB服务所在的机器IP
}
server {
location / {
root html;
配置一下index的地址,最后加上index.ftl。
index index.html index.htm index.jsp index.ftl;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
请求交给名为nginx_boot的upstream上
proxy_pass http://nginx_boot;
}
}
动静分离:
location ~ .*\.(html|htm|gif|jpg|jpeg|bmp|png|ico|txt|js|css){
root /soft/nginx/static_resources;
expires 7d;
资源压缩:gzip
}
MySql
索引
在用于 where 判断 order 排序和 join 的(on)字段上创建索引
注意在不确定的值时索引会失效
like %x
函数 replace SUBSTR avg
not like
InnoDB
show profile 查看sql指行情况
explain sql 解析sql
慢查询日志
like前不要用%
计算、函数导致索引失效
not null not in 失效
索引使用:
全值匹配我最爱
最佳左前缀法则
计算、函数导致索引失效
范围条件右边的列索引失效 abc b索引用到了范围》 c会失效
is not null无法使用索引,is null可使用索引
关联查询优化
建立索引的时候,应该在从表{被驱动表}上建立!
排序、分组优化
无过滤 不索引!
先排序再分组
索引为什么快
mysql索引通过B+树建立的
索引会排序,二分查找就快了
什么是覆盖索引
https://juejin.cn/post/6844903818056974350#heading-20
什么是聚簇索引
Buffer Pool
buffer pool内存组件,用于缓存表数据及索引,避免每次都对磁盘io影响性能。
redo log会记录buffer pool的数据操作
预读就是 IO 异步读取多个页数据读入 Buffer Pool 的一个过程
修改Buffer Pool Size的值,内存够的情况下,提升性能
一条update语句的过程
1、sql语句传入
2、修改值
3、结果存入buffer pool中
4、记录redo log(对buffer pool做的所有操作记录),并将此条记录设置为prepare状态
5、写入bin log(DDL 和 DML 记录的是操作)
6、commit
7、redo log状态改为commit
慢日志查询+explain分析sql
SHOW VARIABLES LIKE '%slow_query_log%';
set global slow_query_log=1;
mysqldump去获取sql执行最慢的10条sql
得到访问次数最多的10个SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/localhost-slow.log
SQL优化
SQL优化我们可以从最基础的SQL语句本身入手,并进一步从场景、表设计、数据库配置、架构四方面着手优化。
在优化之前,需要判断SQL语句是否是真的很慢。可能因为锁、刷新脏页、高并发、buffer pool设置过小导致SQL偶尔变慢,要判断也很简单,看看是不是频繁出现在慢SQL日志里就行了。
如果确定是SQL语句的问题,那么就可以按照我独创的九步优化套路操作了。
一是规范字段名称,这一步是方便大家理解字段并清晰看到归属表。
二是排除所有的select *语句,包含子查询以及JOIN语句,这一步是减少传输数据。
三是小表驱动大表,这一步需要根据业务来判断是否调整。
四是创建索引,尽量多的使用联合索引,不要创建过多的索引,注意索引失效的情况,比如隐式类型转换、索引列使用函数或参与运算、like左模糊匹配。
五是利用覆盖索引优化。
六是使用where和group by的having来减少查询的数据。
七是通过explain去优化SQL,根据ref key extra等字段的信息来动态调整优化手段。
八是子查询可以换成连接查询,减少临时表创建。
九是调整where语句顺序,过滤数据多的条件放在前面。
Mysql宕机了怎么恢复数据
bin log 修复, redo log
Mysql锁表、锁行
死锁问题如何解决
MySql事务怎么实现的
MySQL事务并发会带来什么问题?
脏读:A事务在第二次读取数据之前被B事务未提交修改,读取到了脏数据,然后B事务回滚了。
不可重复读:A事务在第二次读取数据之前被B事务修改,两次数据读取的内容不同
幻读:A事务在第二次查询数据之前被B事务插入一条新的数据。
所以事务的隔离级别用 Repeatable Read(可重复读),可以解决脏读、不可重复读,InnoDB处理了幻读。
如何解决数据的读一致性问题
MVCC也可以处理一个事务内多次读取数据一致的问题
MVCC:核心思想就是只会查到当前事务之前的数据,之后修改删除的数据不会查到
实现方式:对数据的多版本控制,只能查询到小于等于当前事务id的数据。行数据的有创建版本和删除版本隐藏字段
undo log是回滚日志
读写分离
集群
读写分离
主从复制(基于读写分离)
分库分表 垂直分表(不常用的字段 大数据的字段分开) 垂直分库(根据业务分库) 水平分表(比如说根据id分开,或者根据时间分开) 水平分库(把表放到不同库)
分库分表
redis
redis持久化
redis持久化:
RDB:redis database, rdb持久化通过快照实现的,达到特定条件,redis将内存中的所有数据以二进制的方式生成一份副本存储在硬盘上。
触发条件:
主动触发:
使用save、bgsave命令。 save会让redis进入阻塞状态,直到快照执行完毕。 bgsave是开一个子线程去执行快照,创建子线程的时候会阻塞,创建完之后正常响应请求,
之后子线程创建快照,替换之前的快照。
被动触发:
save m n 在m秒内有n个键发生改变,redis会触发bgsave操作
fullshall,清空redis的时候也会被动触发bgsave
shutdown命令有一个参数指定是否持久化 save/nosave
主从复制,master会执行bgsave,将rdb文件发送给slave
AOF:append only file,rdb相当于冷备(定时、条件备份),aof相当于热备。当redis突然宕机,内存中部分数据没有写入磁盘,造成部分数据丢失。aof就是解决这一问题,
aof将redis的所有写操作命令存入aof文件中。但是当写入一个键时,后边又删除,就造成了命令冗余,这时候用文件重写。重写也分手动和主动,手动:bgrewriteaof,
主动是配置文件auto-aof-rewrite-min-size,达到指定大小的时候主动重写,auto-aof-rewrite-percentage,指定比率,设置100,是上次重写的2倍才开始重写
rdb与aof:
rdb性能更好,适用于恢复大数据
aof秒级内的数据恢复,数据更完整
所以混合持久化:子线程将前半段的rdb文件写入到aof中,简单来说就是前半段是rdb格式的数据,后半段是aof
aof-use-rdb-preamble=yes
Redis的数据结构
为什么redis的效率这么高:数据存在内存中 非阻塞io多路复用
redis的多线程知识网络数据的读写多线程,执行命令还是单线程
redis的数据结构:
String
List 有序列表 分页查询
hash hash person name hash person age hash person gender 存对象
set 无序 自动去重 可以做博主的相同粉丝 不同客户的相同购买倾向
sorted set 有序去重
bitmap
redis的高并发、高可用:
主从架构:单主可以达到几万qps,一主多从,主机负责写,从机负责读。
主从复制:先是全量复制,master生成rdb文件给slave。在生成rdb文件期间,新的写命令会被保存下来,在slave接收完rdb文件后异步发送。
增量复制:发生在全量复制期间,m和s的网络断开,m会根据s的psync命令的offset在baklog查询到数据
哨兵模式:
主备切换:当master出现故障时,会推举一个slave成为master
哨兵至少需要3个才能保证自己的健壮性
一般是配置quanum=1 3、4个哨兵的时候majorty为2,
脑裂:一个master暂时失去网络连接,此时又推选了新的master,但是client还在向旧的master发送数据。当旧master从新连接的时候,
又要同步新master的数据,导致了部分数据丢失。
解决方法:设置min-slaves-max-lag 10 设置主从的同步复制不得超过10s,不然master拒绝写
sdown和odown:主观宕机(一个哨兵) 客观宕机(quanum个哨兵)
哨兵的自动发现机制:通过redis的订阅 与发布功能,每隔2s哨兵会对master、slave对应的sentinel-hello的channel,发送自己的ip port runid 监控配置
缓存雪崩:
事前:哨兵+cluster
事中:限流 降级
事后:数据持久化
缓存穿透:查询到一个不存在的key,也给他缓存
缓存击穿:分布式锁锁住,一次就一个进来,给他创建缓存
Redis的数据库双写不一致
先删除后更新数据库再同步redis
延时双删,之后过5s再删除
Redis的过期策略
redis的过期策略 lru 当内存不足时,删除最少用的key
问题:redis设置超时10s,10s后删除了吗,会在哪?
分布式锁出现死锁怎么解决
Redis缓存和spring的cash本地缓存有什么区别
Redison原理
ElasticSearch
(1) 为什么使用
select * from sku_info where skuName like ‘%?%’;
使用数据库检索的话,会导致索引失效!
因此采用企业级搜索引擎!Es
采用的是倒排索引,{通过value找Id}
什么是索引:检索数据,本身是文件,
什么是倒排索引:把当前字段创建索引的每一个词项和包含当前词项的id做映射。
当前词项和包含词项原数据的id的集合 映射
切词:
规范化:大小写、时态 统一 china China 词项的数量级缩小
去重:
字典序:
概念:
过程:
数据结构:
Term dictionary词项字典:分词后的每一个词,做成字典
Posting list:所有包含当前词项的原数据id,有序数组
查询 小米 手机 nfc
计算命中值,根据命中值大小排序,返回结果
为什么叫倒排:
正排索引:文档到词项的映射,当前文档包含了哪些词项
倒排索引:词项到文档id的映射,包含当前词项的文档id有哪些
先对索引分词,
Mysql为什么用b+树:b+树存储的数据更多,每个磁盘块存储的大小16kb,磁盘存储的指针越多,叶子节点存储的数据就更多。B树非叶子节点也存储数据,存储的指针就少了,比b+树存储的数据就少
B+树三层可以存储多少数据:b+树非叶子节点指针数*叶子节点存储数据行数。
非叶子节点结构(指针+键值) 假设主键是8byte,指针innodb是byte,所以能存储的指针数是:16384/14=1170
行数:假设一行是1k,一个磁盘块可以存16k/1k=16 行
2层时:就是1170*16=18720行
3层时:1170*1170(第二行)*16=21902400
(2) 如何使用
1. 将业务数据保存到es索引库中!
保存:
分类数据:
平台属性:
SkuInfo:
品牌信息:
自定义一个实体类Goods,这个实体类中就包含了上述数据!
在保存之前,一定要将goods 中的每个属性都赋值!
@Document(indexName = "goods" ,type = "info",shards = 3,replicas = 2)
public class Goods {
public interface GoodsRepository extends ElasticsearchRepository<Goods,Long>
利用ES 的客户端ElasticsearchRepository 调用save()方法!
2. 根据用户输入的检索条件动态生产dsl语句!
首先,需要自定义一个实体类:接收用户输入的检索条件SearchParam
其次,判断用户根据哪个条件检索动态生产dsl语句!
查询: query match title
过滤:filter字段
分页:filter + from size
排序:filter +from size+sort
高亮:hightlight+字段
聚合:aggs
调用es的高级客户端RestHighLevelClient!
平台属性,属性值的检索的时候,需要将这两个看做一个整体:所以将其数据类型定义为nested!
3. 根据dsl 语句获取到数据集,渲染到页面!
首先,定义一个实体类来接收数据结果集!
其次,获取到上上一步查询的数据集进行转换成自己需要使用的实体类SearchResponseVo
平台属性集合
品牌集合
商品集合
分页相关数据
A. 平台,品牌数据是从聚合中获取!
B. 通过全文检索的时候,需要将分词之后的数据进行高亮!
这个字段从高亮中获取!
最后,页面渲染!
平台属性集合
品牌集合
商品集合
分页
排序
面包屑
什么是 type index filed 。。
Index: 数据库
Document:行
Field: 列
数据类型:
Keyword不能分词 text可以分词
自我介绍
面试官你好,我叫吴昊智,今年26,从事java开发三年,经历了2家公司,过去一直担任Java主力开发,期间有小组项目管理经验,技术栈主要以Java为主,
平时会看博客和技术文档,喜欢研究一些新技术,目前处于离职阶段。加个擅长
在之前的工作中参与过5个项目的开发,主要任务是负责调研分析需求,代码维护开发,分配子任务工作,平时也会积极跟踪一些线上存在的Bug。
最近一份参与的项目是觅呼购电商,我在本次项目中担任Java研发,主要负责商品详情和订单模块的功能,跟随项目发布了多次迭代,后期也参与过秒杀功能的研发。
这个电商系统,由SpringBoot+SpringCloud框架开发,用到了SpringCloud中gateway、nacos、sentinel、task、Feign、zipkin、Sleuth、组件,注册中心、配置文件采用的nacos,
gateway做网关同时负载均衡、认证授权、限流,feign做服务调用,Sleuth做链路追踪,查询出错服务,sentinel做熔断降级限流,elk做日志采集,rabbitmq做消息管理异步、削峰、解耦
数据库采用mysql5.7同时使用mycat分库分表,缓存采用redis等。目前支持微信、支付宝支付。
springboot的启动原理
互联网 南昌的一些银行办理一些房产抵押的一些
信息交换平台 和政府各部门交换数据 获取分类数据
资金监管 银行资金监管 贷款 办理房产的转移 抵押
@EnableDiscoveryClient
@EnableFeignClients(basePackages= {"com.atguigu.gmall"})
@FeignClient(value = "service-cart",fallback = CartDegradeFeignClient.class)
openfeign的日志
NONE:默认的,不显示任何日志
BASIC:仅记录请求方法、RUL、响应状态码及执行时间
HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
网关 路由 断言 过滤
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
#- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
#- Cookie=username,zhangshuai #并且Cookie是username=zhangshuai才能访问
#- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Method=GET
#- Query=username, \d+ #要有参数名称并且是正整数才能路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service 根据微服务名 动态路由
CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。
AP
sentinel
流控:
1秒只能查询一次,超过就快速失败,报默认错误 qps 单机阈值1 流控模式选直接 流控效果选快速失败
流控模式关联 B惹事把A关了 即设置成B一秒只能访问一次,超过把A关闭
流控模式直接 流控效果预热 单机阈值设为10 预热时长5秒 (即5秒内只能一秒10/3=3次请求 预热嘛,用于秒杀开始)
流控模式直接 流控效果排队等待 超时时间20000
熔断降级:
平均响应时间(秒级) rt
异常数(秒级)
异常比例(分钟级)
热点key限流:
配置指定参数的指定值的限流阈值
指定url的限流
自定义限流兜底逻辑方法,@SentinelResource(value = "customerBlockHandler",blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
@SentinelResource主管配置出错,运行出错该走异常走异常
sentinel重启规则消失,所以sentinel整合nacos,持久化规则。
sentinel的配置下边加个datasource,指定nacos的dataid、groupid、地址
熔断 防止服务雪崩 返回虚拟mock值 因为服务与服务之间调用太多
限流 防止攻击 高并发 一秒一次请求
死信
消息的TTL就是消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL。对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。
如何设置TTL:
我们创建一个队列queue.temp,在Arguments 中添加x-message-ttl 为5000 (单位是毫秒),那所在压在这个队列的消息在5秒后会消失。
描述下购物车到订单的流程:
1、添加商品到购物车是需要登录的,在添加时会去校验客户有无登录。
request.getHeader("userId");
2、添加时,先判断redis缓存中是不是已经添加过了,添加过就数量加1。
订单有效期是多久,怎么取消订单?
订单有效期为24小时,我们采用rabbitmq的延时队列,在生成订单时,发送延时消息,设置消息24小时后触发,然后监听到这个消息后,进行订单的取消。
2)怎么防止订单重复提交?
在进入结算页面是我们生产了一个流水号,然后保存到结算页面的隐藏元素中一份,Redis中存一份,每次用户提交订单时都检查reids中流水号与页面提交的是否相符,如果相等可以提交,当订单保存以后把后台的流水号删除掉。
那么第二次用户用同一个页面提交的话流水号就会匹配失败,无法重复保存订单。
订单超卖问题怎么解决的?(库存只剩1件商品,多个用户同时下单)
我们采用的是下订单不减库存,只验证库存,在支付成功后再去扣减库存,如果库存扣减失败,通知后台进行补货,如果这个商品不能补货,人工客户介入,和买家进行沟通,给予退款或相应补偿。
如果想实现不超卖,就得在下单时进行库存锁定,(可以使用数据库锁方式)然后减库存操作、
缓存预热
状态位控制访问请求
用户提交秒杀请求 用户id、秒杀商品
第一步:秒杀商品导入缓存中
定时任务模块负责发送消息到mq交换机。需要启动秒杀业务时,秒杀业务绑定交换机即可。其他业务也是如此,这样起到很好的扩展。
接收到mq消息后将秒杀商品信息导入缓存中,skuid为key,对象信息为value,同时根据每个商品的数量导入同等数量次数的缓存,stock+skuid做key,商品skuid做value
获取下单码 防止用户非法抢购 用户id加密
发起下单请求,校验下单码、状态位,放入消息队列中
监听到,校验状态位、是否下过单、库存,然后下单
控制超卖:导入商品缓存数据时,同时将商品库存信息导入redis队列中,利用redis队列的原子行,保障不超卖。
状态位
秒杀资格 抢购码的获取:
校验下单码
检验状态位
成立,秒杀用户加入队列,直接返回
前端轮询秒杀状态,查询秒杀结果
判断用户重复下单:setnx redistemplate的setifabsent
秒杀:
上面提到我们要控制库存数量,不能 ,那么如何控制呢?在这里我们提供一种解决方案,那就我们在导入商品缓存数据时,同时将商品库存信息导入队列{list},利用redis队列的原子性,保证库存不超卖
库存加入队列实施方案
1,如果秒杀商品有N个库存,那么我就循环往队列放入N个队列数据
2,秒杀开始时,用户进入,然后就从队列里面出队,只有队列里面有数据,说明就一点有库存(redis队列保证了原子性),队列为空了说明商品售罄
秒杀商品存入队列中
redis订阅模式开启同步状态位
秒杀:
如何保证不会重复提交订单:存在用户点击回退提交订单的可能,就是从订单提交回退到结算。
所以在结算页生成订单流水号存入redis中并返回给前端,格式为userid+uuid+tradeno,每次提交订单时,校验流水号,一致则提交通过并删除redis(这是为了下一个订单),不一致则做友好提示。
消息队列
介绍下Rabbitmq的组成
1:项目中怎么使用消息队列的
解耦:订单的创建,涉及配送系统,发送消息与配送系统解耦
异步:办理业务,要生成一份文件 下单的积分系统 优惠券系统需要异步提升
削峰:秒杀
2:消息队列的优缺点
优点:解耦、异步、削峰
缺点:
1、系统可用性降低:mq挂掉了,系统崩溃
2、增加系统复杂性:保证消息没有重复消费(幂等性)、消息丢失、消息的顺序性
3、一致性问题:a系统处理成功 响应完成,bc写入库成功,d写库失败,数据不一致
3:rabbitmq kafka
kafka适用于大数据的实时计算 适用于公司的产品
4:消息队列的高可用
rabbitmq的镜像复制:
kafka的分区复制
5:消息队列的重复消费
直接kill、进程,导致没同步
重要的数据加上唯一id,存入redis中,第二次进来就会发现
写库的时候,先查后写
6:rabbit的可靠性传输
先开持久化
异步回调confirm模式
7:消息队列的顺序
每个消费者对应一个队列,把同一订单编号的数据放入一个队列中 增删减
延时:
死信队列
JVM
双亲委派:app->ext->boot
加载向上 检查向下 (向上委托父类加载器,直到)
%xmrth#pwd3232
设计模式:
一、谈谈你对设计模式的理解
spring常用设计模式:
1、单例模式:创建线程池、日志对象
2、原型模式
3、模板模式
4、观察者模式
5、工厂模式
6、适配器模式
7、装饰者模式
8、代理模式
9、策略模式
1、首先谈谈设计模式的作用:
一种经验的传承,提高了代码的复用性,提升了软件的开发效率
设计原则 | 简单说明 |
---|---|
单一职责 | 一个类只做一件事情 |
里氏替换原则 | 子类只能拓展父类的功能,不能改变父类的原有的功能 |
依赖倒置原则 | 依赖于抽象而非具体,(面向接口) |
开闭原则 | 对拓展开发,对修改关闭 |
接口隔离原则 | 接口尽量细化,接口中方法尽量少 |
最少知道原则 | 一个对象应该对其他的对象尽可能少的了解,降低依赖 |
2、设计模式的分类
二、常用设计模式
创建型模式
1、单例模式
作用:保证一个类只有一个提供全局访问的实例。
饿汉
public class SingletonInstance {
//直接加final 不会再变了
private final static SingletonInstance singletonInstance = new SingletonInstance();
//私有化构造,防止new实例化对象
private SingletonInstance(){}
//直接就返回了,不会涉及并发问题
public static SingletonInstance getSingletonInstance(){
return singletonInstance;
}
}
懒汉
public class SingletonInstance {
private static SingletonInstance singletonInstance = null;
//私有化构造,防止new实例化对象
private SingletonInstance(){}
//并发情况下容易创建多次
public static SingletonInstance getSingletonInstance(){
if(singletonInstance == null){
singletonInstance = new SingletonInstance();
}
return singletonInstance;
}
}
双重检测锁式
在懒汉基础上解决并发
public class SingletonInstance {
//volatile防止指令重排序
private static volatile SingletonInstance singletonInstance = null;
//私有化构造,防止new实例化对象
private SingletonInstance(){}
//直接就返回了,不会涉及并发问题
public static SingletonInstance getSingletonInstance(){
//直接锁的话耗费性能,先判断一下
if(singletonInstance == null){
synchronized (SingletonInstance.class){
//再次判断,因为第一次的判断有并发风险
if (singletonInstance == null){
//步骤:1、分配内存空间 2、执行构造,实例化对象 3、把对象赋值给这个空间
//如果不加volatile,步骤会是1 3 2
singletonInstance = new SingletonInstance();
}
}
}
return singletonInstance;
}
}
静态内部类
枚举
防反射破解,因为枚举不让反射
public enum SingletonInstance5 {
// 定义一个枚举元素,则这个元素就代表了SingletonInstance5的实例
INSTANCE;
public void singletonOperation(){
// 功能处理
}
}
如何解决反射破解单例
private SingletonInstance(){
//加个判断
if(instance != null){
// 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化
throw new RuntimeException("单例模式只能创建一个对象");
}
}
单例的应用场景
Spring的Bean对象默认是单例
相关的工厂对象如:Spring的BeanFactory、Mybatis的SqlSessionFactory
保存配置文件的对象,Configuration
日志框架 log
数据库连接池
线程池
2、工厂模式
应用场景:用于证书的创建:商品房、二手房、林地、集体用地,不同的证书具体的信息组装方法不同(抽象)
简单工厂和方法工厂着力于某类具体的产品,抽象工厂是对应一个家族的一系列产品
简单工厂
简单工厂:DataFormat 加判断 instance 或者 type=x
public class SimpleFactory {
public static void main(String[] args) {
// 根据对应的类型返回相关产品
CarFactory.createCar("奥迪").run();
CarFactory.createCar("Byd").run();
}
}
// 定义公共的接口
interface Car{void run();}
class Audi implements Car{
@Override
public void run() {
System.out.println("奥迪在跑...");
}
}
class Byd implements Car{
@Override
public void run() {
System.out.println("Byd在跑...");
}
}
// 创建对应的简单工厂类
class CarFactory{
public static Car createCar(String type){
if("奥迪".equals(type)){
return new Audi();
}else if("Byd".equals(type)){
return new Byd();
}else{
throw new RuntimeException("该产品不能生产");
}
}
}
简单工厂对于新增产品是无能为力的!不修改原有代码根本就没办法扩展
普通工厂
普通工厂:又称方法工厂 添加一个方法
一个类实现一个方法
public interface FruitFactory {
Fruit createFruit();//生产水果
}
public class AppleFactory implements FruitFactory {
@Override
public Apple createFruit() {
return new Apple();
}
}
public class PearFactory implements FruitFactory {
@Override
public Pear createFruit() {
return new Pear();
}
}
public static void main(String[] args){
AppleFactory appleFactory = new AppleFactory();
PearFactory pearFactory = new PearFactory();
Apple apple = appleFactory.createFruit();//获得苹果
Pear pear = pearFactory.createFruit();//获得梨
apple.whatIm();
pear.whatIm();
}
抽象工厂
抽象工厂:
//今天王二狗约自己在富士康工作的老乡出来喝酒,没想到老乡最近一直在加班。听说接了个大活,要同时为小米公司和苹果公司代工他们的笔记本电脑和手机生产业务。
//我们先定义要生产的产品,笔记本电脑和手机。
//第一步:定义电脑相关的类
//电脑接口
public abstract class Computer {
public abstract void setOperationSystem();
}
public class MacComputer extends Computer {
@Override
public void setOperationSystem() {
System.out.println("Mac笔记本安装Mac系统");
}
}
public class MiComputer extends Computer {
@Override
public void setOperationSystem() {
System.out.println("小米笔记本安装Win10系统");
}
}
//第二步:定义手机相关的类
//里面有一个为手机安装操作系统的抽象方法,然后定义两个实现类
//手机接口
public abstract class MobilePhone {
public abstract void setOperationSystem();
}
public class IPhone extends MobilePhone {
@Override
public void setOperationSystem() {
System.out.println("苹果手机安装IOS系统");
}
}
public class MiPhone extends MobilePhone {
@Override
public void setOperationSystem() {
System.out.println("小米手机安装Android系统");
}
}
第三步:定义工厂类
注意:这里的工厂不是按照具体的产品类型定义,而是按照品牌家族来定义。
例如现在有两个品牌小米和苹果,那么就需要分别为这两个品牌各定义一个工厂,每个工厂生产这个品牌家族的一系列产品,例如笔记本电脑和手机。
这里定义一个抽象工厂的接口,并提供小米和苹果这两个实现类。
//抽象工厂接口
public interface AbstractFactory {
Computer makeComputer();
MobilePhoto makeMobilePhone();
}
public class AppleFactory implements AbstractFactory {
@Override
public Computer makeComputer() {
return new MacComputer();
}
@Override
public MobilePhoto makeMobilePhone() {
return new IPhone();
}
}
public class XiaoMiFactory implements AbstractFactory {
@Override
public Computer makeComputer() {
return new MiComputer();
}
@Override
public MobilePhoto makeMobilePhone() {
return new MiPhone();
}
}
3、建造者模式
当对象有四个以上的构造参数,且构造是可选时,考虑使用
4、原型模式
使用场景:
当一个类创建的代价很高,例如某个对象里面的数据需要访问数据库才能拿到,但我们需要多次构建这个对象。
构建的多个对象都需要处于某种原始状态
使用要求:类必须实现Cloneable, Serializable接口
浅克隆
浅克隆:克隆了属性值,但是对象中的引用对象还是原对象的。
如user对象中的birth、name字段。修改cloneUser对象中的birth不生效,因为他的引用指向原user的birth引用。而修改cloneUser的name会生效
实现方式:User cloneUser = (User) user.clone();
调用clone()方法即可
深克隆
深克隆:克隆了属性值,是个新对象,引用不是原来的。
修改cloneUser的birth会生效了。
实现方式:序列化和反序列化
//使用序列化和反序列化实现深复制
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(user);
byte[] bytes = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
//克隆好的对象!
User user1 = (User) ois.readObject();
结构型模式
1、代理模式
应用场景:spring的aop
实现方式:
1、jdk代理:基于反射实现,只能代理实现了接口方法的方法
2、cglib:基于字节码序列化实现,对于final方法,无法进行代理。。(springboot默认cglib代理)
2、适配器模式
使用场景:将一个接口转化为客户端想要的接口,从而使两个不兼容的接口可以使用
日志模块
项目中的日志系统比较粗糙,引入新的日志系统,但是接口不兼容
原日志接口
public interface LogFactory {
void debug(String tag,String message);
}
第三方日志接口及实现
public interface NbLogger {
void d(int priority, String message, Object ... obj);
}
//具体提供日志功能的实现类
public class NbLoggerImp implements NbLogger {
@Override
public void d(int priority, String message, Object... obj) {
System.out.println(String.format("牛逼logger记录:%s",message));
}
}
构建适配器类,将三方类库接口转化为系统的目标接口
public class LogAdapter implements LogFactory {
private NbLogger nbLogger;
public LogAdapter(NbLogger nbLogger) {
this.nbLogger = nbLogger;
}
@Override
public void debug(String tag, String message) {
Objects.requireNonNull(nbLogger);
nbLogger.d(1, message);
}
}
LogAdapter 实现了系统的目标接口,同时持有三方库NbLogger的引用。
使用
public class AdapterClient {
public void recordLog() {
LogFactory logFactory = new LogAdapter(new NbLoggerImp());
logFactory.debug("Test", "我将使用牛逼logger打印log");
}
}
3、装饰者模式
使用场景:需要在运行时动态的给一个对象增加额外的职责时候
第一步:先声明一个原始对象的接口
public interface ICoffee {
void makeCoffee();
}
第二步:构建我们的原始对象,此处为原味咖啡对象,它实现了ICoffee接口。
public class OriginalCoffee implements ICoffee {
@Override
public void makeCoffee() {
System.out.print("原味咖啡 ");
}
}
第三步:构建装饰者抽象基类,它要实现与原始对象相同的接口ICoffee,其内部持有一个ICoffee类型的引用,用来接收被装饰的对象
public abstract class CoffeeDecorator implements ICoffee {
private ICoffee coffee;
public CoffeeDecorator(ICoffee coffee){
this.coffee=coffee;
}
@Override
public void makeCoffee() {
coffee.makeCoffee();
}
}
第四步:构建各种装饰者类,他们都继承至装饰者基类 CoffeeDecorator。此处生成了两个,一个是加奶的装饰者,另一个是加糖的装饰者。
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(ICoffee coffee) {
super(coffee);
}
@Override
public void makeCoffee() {
super.makeCoffee();
addMilk();
}
private void addMilk(){
System.out.print("加奶 ");
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(ICoffee coffee) {
super(coffee);
}
@Override
public void makeCoffee() {
super.makeCoffee();
addSugar();
}
private void addSugar(){
System.out.print("加糖");
}
}
第五步:客户端使用
public static void main(String[] args) {
//原味咖啡
ICoffee coffee=new OriginalCoffee();
coffee.makeCoffee();
System.out.println("");
//加奶的咖啡
coffee=new MilkDecorator(coffee);
coffee.makeCoffee();
System.out.println("");
//先加奶后加糖的咖啡
coffee=new SugarDecorator(coffee);
coffee.makeCoffee();
}
输出:
原味咖啡
原味咖啡 加奶
原味咖啡 加奶 加糖
人气推荐:
海报
评价
面试问题
一、自我介绍
面试官你好,我叫吴昊智,今年26,从事java开发三年,经历了2家公司,过去一直担任Java主力开发,期间有小组项目管理经验,技术栈主要以Java为主,
平时会看博客和技术文档,喜欢研究一些新技术,目前处于离职阶段。加个擅长
自我介绍是唯一一段让你把面试主动权握在手里的时间,这时候把你擅长的东西抛出来,将你以前做过的事情说个大概,可以让面试官可以初步了解你而且引导他往你擅长的区域去提问,如果你在这个时间没有提供什么有效的信息,
让面试官自己去挖掘信息去展开提问,说真的不利于你接下来的展开,也容易被多次问到不会的方面导致尴尬
二、介绍最近的项目
介绍项目背景、项目功能和自己负责的功能模块
介绍项目背景、项目使用技术栈和自己负责的功能模块
最近做的一个项目是电商系统,
项目的主要功能有
后台管理、商品详情、商品检索、购物车、单点登录、订单、支付、秒杀等等。
项目的技术栈是
使用springboot整合springcloud以及mybatispus框架开发,
nacos做注册中心、配置中心,
gateway网关做请求负载、请求过滤、统一鉴权和限流,
feign做服务调用,
Sentinel进行服务的熔断和降级,
sleuth做链路追踪,
Fastdfs管理文件资源,
redis数据库进行缓存以及分布式锁的实现
es实现商品的搜素服务
rabbitmq做消息管理
mysql
为什么需要配置中心:
为什么用nacos做注册中心:服务的注册与发现,易于集群管理。服务方推送信息给nacos,消费方订阅信息。nacos心跳监测 监听服务方的状态,一旦出现问题会推送新的订阅信息给消费方
Git命令使用
项目怎么解决并发:微服务架构、nginx的集群部署、页面的静态化、redis缓存、mysql的读写分离、
单点登录
单点登录:负责开发单点登录,首先是在登录时选择在网关处拦截请求进行用户认证,首先先筛选需要登录的请求,这个我们请求路径有规范,
带auth的需要登录,在网关添加过滤器,读取请求cookie中的token,token再加上自定义前缀组合成key去redis获取value值,比对用户id及其ip校验token
1、登录后生成token=(user:login:"+uuid)组装成缓存key,ip、id做jsonobject对象转成string做value,设置存入缓存,时长3天
2、校验的时候,从request的cookie获取token:request.getCookies().get("token"); 或是header
3、token作为key,取缓存值:
String userKey = "user:login:"+token;
String object = (String) redisTemplate.opsForValue().get(userKey)
4、校验缓存值对象,对象我们存了ip、userid,通过request获取真实ip缓存ip对比
5、校验通过将id放入request的header,传递给后台微服务
商品详情
2、商品详情模块
2.1 业务参考话术:
商品详情页,就是以购物者的角度展现一个sku的详情信息。这个页面不同于传统的页面,使用者并不是管理员,需要对信息进行查删改查,取而代之的是点击购买、放入购物车、切换颜色等等。另外一个特点就是该页面的高访问量,虽然只是一个查询操作,
但是由于频繁的访问所以我们必须对其性能进行最大程度的优化。所以我们页面数据放入Redis缓存来提高性能,为了防止缓存击穿,我们采用了分布式锁。由于商品详情展示的数据需要要调用多个查询接口,为了提高响应速度,
我们采用线程异步编排的方式进行接口调用。
2.2 模块相关问题:
Redis是这块的重点之一,有关reids的高频问题,详见高频面试题,这里只说项目相关redis常见问题
1)Redis在你们项目中是怎么用的?
商品详情中的数据放入缓存,价格是实时获取的;
单点登录系统中也用到了redis。因为我们是微服务系统,把用户信息存到redis中便于多系统之间获取共用数据;
我们项目中同时也将购物车的信息设计存储在redis中,用户未登录采用UUID作为Key,value是购物车对象;用户登录之后将商品添加到购物车后存储到redis中,key是用户id,value是购物车 对象;
因为针对评论这块,我们需要一个商品对应多个用户评论,并且按照时间顺序显示评论,为了提高查询效率,因此我们选择了redis的list类型将商品评论放在缓存中;
在统计模块中,我们有个功能是做商品销售的排行榜,因此选择redis的zset结构来实现;
2)缓存击穿解决(分布式锁)
缓存击穿是指对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:如果这个key在大量请求同时进来之前正好失效,那么所有对这个key的数据查询都落到db,
使用分布式锁,采用redis的KEY过期时间实现
aop+分布式锁实现缓存
aop+分布式锁实现缓存:
// 先加锁
RLock lock = redissonClient.getLock(key + ":lock");
// 上锁:
boolean res = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
为什么需要:原来的单机部署转为分布式集群,由于分布式多线程多进程且部署在不同台机器上,使得原来单机部署的并发策略失效,单纯的javaapi无法解决
哪里使用到了分布式锁:
1、读取商品详情的时候,锁住资源,放行一个线程去查询或创建缓存,避免缓存击穿与穿透。
2、积分兑换功能、发放积分功能的冲突,可能正好减积分的时候在加积分,所以对积分修改这部分用锁锁住,lockkey就是userid+Integral
aop:methodsignature提供方法签名,joinpoint提供参数及执行方法
Redison:如果拿到分布式锁的节点宕机,会出现锁死的情况,为避免这种情况,redis都会设置超时。比如30s,而当线程执行时间超过30s就会自动释放,引发问题,
Redisson的看门狗机制解决了防死锁机制而设置的超时时间痛点,会一直延长时间,默认是30s,也可以设置lockwatchdogtimeout参数修改默认时长,设置了leaseTime就会直接过期用不到看门狗机制
加一点点细节:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
异步编排、并行化,提高响应速度
CompletableFuture
商品详情中需要获取很多类数据:分类数据、商品销售属性、价格之类的。所以采用异步编排提高响应速度。
主要用到的是JUC中的completeablefuture类让程序并行。常用的方法有
runAsync 异步执行,无返回值
supplyAsync 异步执行,有返回值
thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。
thenAccept方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。
anyOf 任意一个执行完成,就可以进行下一步动作
allOf 全部完成所有任务,才可以进行下一步任务
whenComplete可以处理正常或异常的计算结果,
exceptionally处理异常情况。
在项目中用到了
supplyAsync()
thenAcceptAsync() (多一个Async代表另起一个线程,并且可以使用线程池)
allOf(线程,线程).join
多任务组合:
allOf:等待所有任务完成
anyOf:只要有一个任务完成
CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
// 获取到的数据是skuInfo
return skuInfo;
},threadPoolExecutor);
skuInfoCompletableFuture.thenAcceptAsync
CompletableFuture.runAsync
// 使用多任务进行组合
CompletableFuture.allOf(
skuInfoCompletableFuture,
categoryViewCompletableFuture,
spuSaleAttrCompletableFuture,
mapCompletableFuture,
priceCompletableFuture,
hotScoreCompletableFuture
).join();
{
// 声明对象
Map<String, Object> result = new HashMap<>();
// Supplier T get();
// 使用异步编排!
CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
// 获取到的数据是skuInfo + skuImageList
SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
// map 中 key 对应的谁? Thymeleaf 获取数据的时候 ${skuInfo.skuName}
result.put("skuInfo",skuInfo);
// 返回数据
return skuInfo;
},threadPoolExecutor);
// Consumer void accept(T t);
CompletableFuture<Void> categoryViewCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((skuInfo -> {
// 获取分类数据
BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
result.put("categoryView", categoryView);
}),threadPoolExecutor);
CompletableFuture<Void> spuSaleAttrCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((skuInfo -> {
// 获取销售属性+销售属性值
List<SpuSaleAttr> spuSaleAttrListCheckBySku = productFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
result.put("spuSaleAttrList", spuSaleAttrListCheckBySku);
}),threadPoolExecutor);
CompletableFuture<Void> mapCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((skuInfo -> {
// 查询销售属性值Id 与skuId 组合的map
Map skuValueIdsMap = productFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
// 将这个map 转换为页面需要的Json 对象
String valueJson = JSON.toJSONString(skuValueIdsMap);
result.put("valuesSkuJson", valueJson);
}),threadPoolExecutor);
CompletableFuture<Void> priceCompletableFuture = CompletableFuture.runAsync(() -> {
// 获取价格
BigDecimal skuPrice = productFeignClient.getSkuPrice(skuId);
// 保存数据
result.put("price", skuPrice);
},threadPoolExecutor);
// 调用热度排名方法
CompletableFuture<Void> hotScoreCompletableFuture = CompletableFuture.runAsync(() -> {
listFeignClient.incrHotScore(skuId);
},threadPoolExecutor);
// 使用多任务进行组合
CompletableFuture.allOf(
skuInfoCompletableFuture,
categoryViewCompletableFuture,
spuSaleAttrCompletableFuture,
mapCompletableFuture,
priceCompletableFuture,
hotScoreCompletableFuture
).join();
return result;
}
购物车
购物车的增删查改
添加、删除、修改、查询
添加:判断购物车中是否有该商品,有则相加,同时更新缓存、数据库
存储方案采用的是redis缓存提高查询速度 mysql持久化
缓存使用那种数据类型存储的?
Hash:为什么使用?
Hash结构适合购物车存储, hset key field value hget key filed
Key = user:userId:cart
Field = skuId
Value = cartInfo
hvals key
订单
订单记录这用户要购买的商品,同时也是一个变现的过程!
1. 回显收货地址列表
2. 送货清单
3. 订单保存
(1) OrderInfo,orderDetail 向这两张表插入数据!
(2) 需要验证价格
(3) 需要验证库存
(4) 防止用户无刷新回退提交订单! 利用流水号!{选说}
验证库存,验证价格:可以使用多线程
(2) 功能实现
回显收货地址列表:通过远程调用userFeignClient 实现的 {参数userId}
送货清单:通过远程调用cartFeignClient 实现的 {参数userId}
保存订单:获取到前端传递的数据直接插入数据库,需要有事务参与!
验证价格:保证用户在支付的时候,商品的价格是最新的
使用的orderPrice 与 skuPrice 做比较!
验证库存:保证用户购买的商品不能出现超卖!
远程调用的仓库系统接口:
知道接口的参数列表 : skuId,skuNum
知道接口的返回值: 1 表示有足够的库存,0 表示没有足够的库存。
用库存的总数-购买数!
为了提高性能:可以使用异步编排!
秒杀
功能实现:
1. 数据初始化
(1) 将当天秒杀的商品放入缓存
(2) 将秒杀的商品数量以list形式存入缓存
(3) 初始化状态位
2. 展示秒杀的列表以及详情
(1) 从缓存获取
3. 秒杀的过程
(1) 获取下单码
(2) 验证下单码
(3) 校验状态位
(4) 将用户秒杀的哪个商品放入队列
(5) 监听队列 {校验状态位,判断用户是否已经购买过商品,减库存【同步状态位】,保存预下单}
(6) 检查checkorder秒杀状态{1秒轮询}
① 判断缓存中是否有用户存在
② 判断用户是否预下单成功 秒杀成功去下单
③ 判断用户是否真正下过订单 秒杀成功去看我的订单
④ 校验状态位
(7) 订单页面信息展示
(8) 提交订单{远程调用order保存订单方法}
(9) 清空当前秒杀商品缓存!
评论区