面试题

1.说说spring事务托管

事务管理可以帮助我们保证数据的一致性,对应企业的实际应用很重要。
Spring的事务机制包括声明式事务和编程式事务。
编程式事务管理:Spring推荐使用TransactionTemplate,实际开发中使用声明式事务较多。
声明式事务管理:将我们从复杂的事务处理中解脱出来,获取连接,关闭连接、事务提交、回滚、异常处理等这些操作都不用我们处理了,Spring都会帮我们处理。
声明式事务管理使用了AOP面向切面编程实现的,本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。
如何管理的:
Spring事务管理主要包括3个接口,Spring的事务主要是由他们三个共同完成的。
1)PlatformTransactionManager:事务管理器–主要用于平台相关事务的管理
主要有三个方法:commit 事务提交;
rollback 事务回滚;
getTransaction 获取事务状态。
2)TransactionDefinition:事务定义信息–用来定义事务相关的属性,给事务管理器PlatformTransactionManager使用
这个接口有下面四个主要方法:
getIsolationLevel:获取隔离级别;
getPropagationBehavior:获取传播行为;
getTimeout:获取超时时间;
isReadOnly:是否只读(保存、更新、删除时属性变为false–可读写,查询时为true–只读)
事务管理器能够根据这个返回值进行优化,这些事务的配置信息,都可以通过配置文件进行配置。
3)TransactionStatus:事务具体运行状态–事务管理过程中,每个时间点事务的状态信息。
例如它的几个方法:
hasSavepoint():返回这个事务内部是否包含一个保存点,
isCompleted():返回该事务是否已完成,也就是说,是否已经提交或回滚
isNewTransaction():判断当前事务是否是一个新事务

声明式事务的优缺点:
优点
不需要在业务逻辑代码中编写事务相关代码,只需要在配置文件配置或使用注解(@Transaction),这种方式没有侵入性。
缺点
声明式事务的最细粒度作用于方法上,如果像代码块也有事务需求,只能变通下,将代码块变为方法

2.springmvc接收的几种方式

@PathVariable
只能接收 URL 路径里的参数。
获取路径参数。即url/{id}这种形式
@RequestParam
获取查询参数。即url?name=这种形式
只能接收 URL 问号后跟着的参数,不管是 GET 还是 POST,虽然一般只有 GET 请求才会在 URL 后边跟参数,问号 ? 后面的部分,使用 & 区分参数。

GET
http://localhost:8080/demo/123?name=suki_rong

@GetMapping("/demo/{id}")
public void demo(@PathVariable(name = "id") String id, @RequestParam(name = "name") String name) {
    System.out.println("id="+id);
    System.out.println("name="+name);
}

输出结果:
id=123
name=suki_rong

@RequestBody

对应的java代码:

@PostMapping(path = "/demo1")
public void demo1(@RequestBody Person person) {
    System.out.println(person.toString());
}

输出结果:
name:suki_rong;age=18;hobby:programing

redis的list和zset区别

Redis支持5种数据类型:
1.string(字符串)
2.hash(哈希)
3.list(列表)
4.set(集合)
5.zset(sorted set:有序集合)
String
1.string:一个key对应一个value
2.string类型是二进制安全的。可以存储任何类型的数据
3.常用命令:get,set,incr,decr,mget等;
hash:
1.hash:一个string类型的field和value的映射表
2.hash特别适合用于存储对象
3.常用命令是hget,hset,hgetall
list:
1.list:简单的字符串列表,底层实现为双向链表
2.可以作为消息队列系统和取最新N个数据的操作
set
1.set:string类型的无序集合,通过hasttable实现。集合内不能出现重复数据。
2.可以交集,并集,差集等
3.实现方式:set的内部实现是一个value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能判断一个成员是否在集合内的原因
4.SADD key member1:添加一个或多个员工,SISMEMBER key:返回给定集合的所有成员
zset:
1.string类型的集合,且不允许重复的成员
2.常用命令zadd,zrange,zrem,zcard
3.而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序
4.实现方式:Redis sorted set 的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap<value,score> 里放的是成员到score的映射,而跳跃表存放的是所有的成员

redis的递增,并发,预热,雪崩,穿透

Redis Incr 命令将 key 中储存的数字值增一。

如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。

如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。

本操作的值限制在 64 位(bit)有符号数字表示之内。

原子自增 线程安全
INCR page_view

配合getset实现原子复位 线程安全
getset page_view 0

计数器是 Redis 的原子性自增操作可实现的最直观的模式了,它的想法相当简单:每当某个操作发生时,向 Redis 发送一个 INCR 命令。

比如在一个 web 应用程序中,如果想知道用户在一年中每天的点击量,那么只要将用户 ID 以及相关的日期信息作为键,并在每次用户点击页面时,执行一次自增操作即可。

比如用户名是 peter ,点击时间是 2012 年 3 月 22 日,那么执行命令 INCR peter::2012.3.22 。

可以用以下几种方式扩展这个简单的模式:

可以通过组合使用 INCR 和 EXPIRE ,来达到只在规定的生存时间内进行计数(counting)的目的。
客户端可以通过使用 GETSET 命令原子性地获取计数器的当前值并将计数器清零,更多信息请参考 GETSET 命令。
使用其他自增/自减操作,比如 DECR 和 INCRBY ,用户可以通过执行不同的操作增加或减少计数器的值,比如在游戏中的记分器就可能用到这些命令。
————————————————

什么是缓存雪崩?
数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机。

比如一个雪崩的简单过程:
1、redis集群大面积故障
2、缓存失效,但依然大量请求访问缓存服务redis
3、redis大量失效后,大量请求转向到mysql数据库
4、mysql的调用量暴增,很快就扛不住了,甚至直接宕机
5、由于大量的应用服务依赖mysql和redis的服务,这个时候很快会演变成各服务器集群的雪崩,最后网站彻底崩溃。

在这里插入图片描述
如何预防缓存雪崩:
1.缓存的高可用性
缓存层设计成高可用,防止缓存大面积故障。即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如 Redis Sentinel 和 Redis Cluster等redis集群都实现了高可用。

2.缓存降级
可以利用ehcache等本地缓存(暂时支持),但主要还是对源服务访问进行限流、资源隔离(熔断)、降级等。 当访问量剧增、服务出现问题仍然需要保证服务还是可用的。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级,这里会涉及到运维的配合。
降级的最终目的是保证核心服务可用,即使是有损的。
比如推荐服务中,很多都是个性化的需求,假如个性化需求不能提供服务了,可以降级补充热点数据,不至于造成前端页面是个大空白。
在进行降级之前要对系统进行梳理,比如:哪些业务是核心(必须保证),哪些业务可以容许暂时不提供服务(利用静态页面替换)等,以及配合服务器核心指标,来后设置整体预案,比如:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

3.Redis备份和快速预热
1)Redis数据备份和恢复
2)快速缓存预热

4.提前演练
最后,建议还是在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,对高可用提前预演,提前发现问题。

什么是缓存穿透
缓存穿透是指查询一个一不存在的数据。
例如:从缓存redis没有命中,需要从mysql数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决思路:
如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。设置一个过期时间或者当有值的时候将缓存中的值替换掉即可。
可以给key设置一些格式规则,然后查询之前先过滤掉不符合规则的Key。

什么是缓存并发
这里的并发指的是多个redis的client同时set key引起的并发问题。其实redis自身就是单线程操作,多个client并发操作,按照先到先执行的原则,先到的先执行,其余的阻塞。当然,另外的解决方案是把redis.set操作放在队列中使其串行化,必须的一个一个执行。

什么是缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。
这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决思路:
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
目的就是在系统上线前,将数据加载到缓存中。

SpringBoot是如何加载配置文件的?

前言
本文针对版本2.2.0.RELEASE来分析SpringBoot的配置处理源码,通过查看SpringBoot的源码来弄清楚一些常见的问题比如:

SpringBoot从哪里开始加载配置文件?
SpringBoot从哪些地方加载配置文件?
SpringBoot是如何支持yaml和properties类型的配置文件?
如果要支持json配置应该如何做?
SpringBoot的配置优先级是怎么样的?
placeholder是如何被解析的?
带着我们的问题一起去看一下SpringBoot配置相关的源代码,找出问题的答案。

SpringBoot从哪里开始加载配置文件?
SpringBoot加载配置文件的入口是由ApplicationEnvironmentPreparedEvent事件进入的,SpringBoot会在SpringApplication的构造函数中通过spring.factories文件获取ApplicationListener的实例类:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	...
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    ...
}

spring.factories中有一个ConfigFileApplicationListener类,它会监听ApplicationEnvironmentPreparedEvent然后再加载配置文件 :

# Application Listeners
org.springframework.context.ApplicationListener= org.springframework.boot.context.config.ConfigFileApplicationListener
...

有了事件和事件处理的类后,再找出发送事件的地方,就可以搞清楚SpringBoot是怎么加载配置文件的了,SpringBoot在启动之前先初始化好SpringApplicationRunListeners这个类,它会实现SpringApplicationRunListener接口然后对事件进行转发:

class SpringApplicationRunListeners {

	private final Log log;

	private final List<SpringApplicationRunListener> listeners;

	SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
		this.log = log;
		this.listeners = new ArrayList<>(listeners);
	}
 
	void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}
	...
}

获取SpringApplicationRunListeners的代码如下:

private SpringApplicationRunListeners getRunListeners(String[] args) {
	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
	return new SpringApplicationRunListeners(logger,
			getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

同样也会去加载spring.factories文件,该文件有一个EventPublishingRunListener类,该类的作用就是SpringBoot的事件转换成ApplicationEvent发送出去。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

小结
SpringBoot会将事件转换成ApplicationEvent再分发
SpringBoot是通过监听ApplicationEnvironmentPreparedEvent事件来加载配置文件的
ConfigFileApplicationListener是处理配置文件的主要类
SpringBoot从哪些地方加载配置文件?
上面已经分析到ConfigFileApplicationListener是处理配置文件的主要类,然后进一步的查看SpringBoot是从哪些地址加载配置文件,进入ConfigFileApplicationListener类后会有两个默认的常量:

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
private static final String DEFAULT_NAMES = "application";

首先在没有任何配置的情况下,会从DEFAULT_SEARCH_LOCATIONS常量列出来的位置中加载文件名为DEFAULT_NAMES(.properties或yml)的文件,默认位置包括:

classpath根目录(classpath:/)
classpath里面的config文件目录(classpath:/config/)
程序运行目录(file:./)
程序运行目录下的config目录(file:./config/)
上面说的是没有额外配置的情况,SpringBoot足够灵活可以指定配置文件搜索路径、配置文件名,在ConfigFileApplicationListener类中有个getSearchLocations方法,它主要负责获取配置搜索目录:

private Set<String> getSearchLocations() {
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
		return getSearchLocations(CONFIG_LOCATION_PROPERTY);
	}
	Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
	locations.addAll(
			asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
	return locations;
}

它的操作步骤大致如下:

检查是否有spring.config.location属性,如果存在则直接使用它的值
从spring.config.additional-location属性中获取搜索路径
将默认搜索路径添加到搜索集合
这里就可以确定SpringBoot配置的搜索路径有两种情况:如果配置了spring.config.location则直接使用,否则使用spring.config.additional-location的属性值 + 默认搜索路径。

SpringBoot是如何支持yaml和properties类型的配置文件?
SpringBoot的配置支持properties和yaml文件,SpringBoot是如何解析这两种文件的呢,继续分析ConfigFileApplicationListener这个类,里面有个子类叫Loader加载配置文件主要的工作就是由这货负责,但是直接读取properties和yaml并转换成PropertySource还是由里面的PropertySourceLoader负责:

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	...
	this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
			getClass().getClassLoader());
}

构造Loader对象的时候就会先加载PropertySourceLoader,加载方式还是从spring.factories中读取:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

其中配置了两个PropertySourceLoader的实现类:

PropertiesPropertySourceLoader
YamlPropertySourceLoader
看名字就知道是分别负责properties和yaml的啦。

更多:
https://www.cnblogs.com/xwgblog/p/11794207.html

总结
SpringBoot的配置非常灵活配置可以来自文件、环境变量、JVM系统属性、配置中心等等,SpringBoot通过
PropertySource和PropertySources实现属性优先级、CRUD的统一管理,为开发者提供统一的配置抽象。

**

1系列化是干什么的?

**

   序列化简单来说就保存对象在内存中的状态也可以说是实例化变量。这是Java提供的用来保存 Object state,一种保存对象状态的机制。只有实现了serializable接口的类的对象才能被实例化。

2什么情况下会用到序列化?

 1当你想把内存中的对象写入到硬盘时

 2当你想用套接字在网络上传输对象时

 3当你想通过RMI调用对象时

(RMI是什么东西?):RMI总结来说就是远程调用对象,在一个jvm上调用另一个jvm的对象。

3序列化需要注意的事项

 1序列化只保存对象的状态,而不管对象的方法。

 2当一个父类实现了序列化,它的子类也自动实现序列化,不用显示进行实现了。

 3当一个实例对象引用其他对象,当序列化该对象时也把引用的对象进行了实例化。

个人总结:serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可

MQ面试题
https://www.cnblogs.com/qingmuchuanqi48/p/11006760.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值