目录
二、Java有哪些锁?区别在哪?底层如何实现的?为什么非公平锁效率高?
二十四、Redis集群时分布式锁的问题(主节点锁信息未同步至从节点,主宕机)
一、Switch数据类型支持哪些?
byte short char int Byte Short Char Integer String Enum
二、Java有哪些锁?区别在哪?底层如何实现的?为什么非公平锁效率高?
1.公平锁/非公平锁
Java ReentrantLock,通过构造函数指定该锁是否是公平锁, Synchronized 非公平锁
2.可重入锁
Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
3.独享锁/共享锁
对于Java ReentrantLock和Synchronized而言,其是独享锁。ReadWriteLock,其读锁是共享锁,其写锁是独享锁
4.互斥锁/读写锁
ReentrantLock / ReadWriteLock
5.乐观锁/悲观锁
不加锁的并发操作一定会出问题的认知就是悲观锁;乐观的认为,不加锁的并发操作是没有事情的。
6.分段锁
ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
7.偏向锁/轻量级锁/重量级锁
偏向锁:是一种针对加锁操作的优化手段。在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了消除数据在无竞争情况下锁重入(CAS操作)的开销而引入偏向锁。对于没有锁竞争的场合,偏向锁有很好的优化效果。JVM启用了偏向锁模式:jdk6之后默认开启新创建对象的Mark Word中的Thread Id为0,说明此时处于可偏向但未偏向任何线程,也叫做匿名偏向状态(anonymously biased)。
轻量级锁:倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段,此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间多个线程访问同一把锁的场合,就会导致轻量级锁膨胀为重量级锁。轻量级锁在降级的时候直接变为无锁状态!(查看之前在同步代码块外调用hashCode()方法)
重量级锁:轻量级锁经过一次自选如果没有获取到锁,直接膨胀为重量级锁。重量级锁是基于 Monitor 机制,并且在 Monitor 中记录 hashCode。
8.自旋锁
自旋锁:当一个线程尝试去获取某一把锁的时候,如果这个锁已经被另外一个线程占有了,那么此线程就无法获取这把锁,该线程会等待,间隔一段时间后再次尝试获取。这种采用循环加锁,等待锁释放的机制就称为自旋锁(spinlock)
三、Java内存模型
在运行时数据区里,会根据用途进行划分:
1.Java虚拟机栈(栈区)
2.本地方法栈
3.Java堆(堆区)
4.方法区
5.程序计数器
Java堆
Java虚拟机栈
本地方法栈
十分类似Java虚拟机栈,与Java虚拟机区别在于:服务对象,即Java虚拟机栈为执行 Java 方法服务;本地方法栈为执行 Native方法服务
方法区
注:其内部包含一个运行时常量池,具体介绍如下:
程序计数器
补充:
JVM的内存模型、对象创建、对象内存分配、对象内存回收、cms垃圾收集器_cms大对象内存分配-CSDN博客
Java 内存模型(JMM)_java jmm模型-CSDN博客
四、线程池使用场景及其核心参数说明
使用场景
线程池的使用场景可以根据并发量和任务执行时长两个维度来划分:
高并发、任务执行时间短:
线程池中的线程数可以设置得较少,以减少线程上下文的切换。
这种场景下,线程池的主要作用是异步处理任务,避免主线程阻塞,从而提高响应速度。
高并发、任务执行时间长:
解决这种类型任务的关键在于整体架构的设计,而不是仅仅依赖线程池。
可能需要在业务逻辑中分析并使用中间件(如消息队列MQ)对任务进行拆分和解耦。
在这种情况下,可以将可预见的会发生阻塞操作的代码块放入线程池中执行,以异步非阻塞的方式快速响应。
并发不高、任务执行时间短:
线程池中的线程数可以设置得较少,以减少线程上下文的切换。
并发不高、任务执行时间长:
需要区分任务类型为IO密集型任务和CPU密集型任务。
对于IO密集型任务,由于IO操作不占用CPU,可以加大线程池中的线程数目,让CPU处理更多的业务。
对于CPU密集型任务,线程池线程数可以设置为少一些(以CPU核数+1为准),以减少线程上下文的切换。
核心参数
1.CorePoolSize 服务器核数
2.MaxPoolSize 建议:根据IO密集还是CPU密集;cpu密集设置服务器核数,减少线程切换时间,IO,两倍服务器核数
3.QueueCapacity 队列大小:根据业务需求设置大小
4.KeepAliveSeconds 默认 60s,根据服务器能力,可以适当加长为120s
5.ThreadNamePrefix 线程名前缀
6.WaitForTasksToCompleteOnShutdown 等待所有任务结束后再关闭线程池
7.RejectedExecutionHandler 拒绝策略
拒绝策略
1.默认AbortPolicy 丢弃任务并抛出RejectedExecutionException异常
2.DiscardPolicy 丢弃任务不抛异常
3.DiscardOldestPolicy 丢弃队列最前任务放最新任务
4.CallerRunsPolicy 主线程处理当前任务
5.自定义策略DefaultRejectedHandler 队列当前长度超过80%,阻塞主线程
五、Threadlocal原理和使用场景
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
- 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
六、实现多线程通讯
使用 volatile 关键字
基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想。大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。
使用 Object 类的 wait()/notify()
Object 类提供了线程间通信的方法:wait()、notify()、notifyAll(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。
使用JUC工具类 CountDownLatch
public class TestSync {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
List<String> list = new ArrayList<>();
//线程A
Thread threadA = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5)
countDownLatch.countDown();
}
});
//线程B
Thread threadB = new Thread(() -> {
while (true) {
if (list.size() != 5) {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程B收到通知,开始执行自己的业务...");
break;
}
});
//需要先启动线程B
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再启动线程A
threadA.start();
}
}
使用 ReentrantLock 结合 Condition
public class TestSync {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
List<String> list = new ArrayList<>();
//线程A
Thread threadA = new Thread(() -> {
lock.lock();
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5)
condition.signal();
}
lock.unlock();
});
//线程B
Thread threadB = new Thread(() -> {
lock.lock();
if (list.size() != 5) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程B收到通知,开始执行自己的业务...");
lock.unlock();
});
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadA.start();
}
}
这种方式使用起来并不是很好,代码编写复杂,而且线程 B 在被 A 唤醒之后由于没有获取锁还是不能立即执行,也就是说,A 在唤醒操作之后,并不释放锁。这种方法跟 Object 的 wait()/notify()
一样。
基本 LockSupport 实现线程间的阻塞和唤醒
LockSupport
是一种非常灵活的实现线程间阻塞和唤醒的工具,使用它不用关注是等待线程先进行还是唤醒线程先运行,但是得知道线程的名字。
public class TestSync {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//线程B
final Thread threadB = new Thread(() -> {
if (list.size() != 5) {
LockSupport.park();
}
System.out.println("线程B收到通知,开始执行自己的业务...");
});
//线程A
Thread threadA = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
list.add("abc");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5)
LockSupport.unpark(threadB);
}
});
threadA.start();
threadB.start();
}
}
七、Mybatis初始化和执行原理
1.创建SqlSessionFactoryBuilder对象,调用build(inputstream)方法读取并解析配置文件,返回SqlSessionFactory对象
2.由SqlSessionFactory创建SqlSession 对象,没有手动设置的话事务默认开启
3.调用SqlSession中的api,传入Statement Id和参数,内部进行复杂的处理,最后调用jdbc执行SQL语句,封装结果返回。
1、读取MyBatis配置文件
mybatis-config.xml为MyBatis的全局配置文件,配置了MyBatis的运行环境等信息,例如数据库连接信息。
2、加载映射文件(SQL映射文件,一般是XXXMapper.xml)
该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。XXXMapper.xml可以在mybatis-config.xml文件可以加载多个映射文件,每个文件对应数据库中的一张表。
3、构造会话工厂
通过MyBatis的环境等配置信息构建会话工厂SqlSessionFactory。
4、创建会话对象
由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
5、Executor执行器
MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
6、MappedStatement对象
在 Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
7、输入参数映射
输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
8、输出结果映射
输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。
八、Spring mvc初始化和执行原理
Spring MVC是基于Servlet的技术,它提供了核心类DispatcherServlet和相关的组件,围绕这个核心类有一个完整的流程
(1)浏览器提交请求经web容器(比如tomcat)转发到中央调度器dispatcherServlet。
(2)中央调度器调用处理器映射器handerMapping,处理器映射器根据请求的url找到处理该请求对应的处理器hander及相关的拦截器intercepter,将它们封装成一个处理器执行链并返回给中央调度器
(3)中央调度器根据处理器执行链中的处理器找到对应的处理器适配器handerAdaptor
(4)处理适配器调用处理器执行对应的方法并将返回的结果封装为一个对象modelAndView中返回给中央处理器,当然在处理器执行方法前如果方法有拦截器的话会先依次执行拦截器的prehander方法,方法执行结束后会依次执行拦截器的posthander方法。
(5)中央调度器获取到modelAndView对象后,调用视图解析器viewResolver,将modelAndView封装为视图对象
(6)中央调度器获取到视图对象后,进行渲染,生成最后的响应返回给浏览器。
九、Springboot如何自定义starter
1、引入项目的配置依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
2、创建xxxService类,完成相关的操作逻辑
public class StringService {
private String str1;
private String str2;
private String default_str;
public String getStr1() {
return str1;
}
public void setStr1(String str1) {
this.str1 = str1;
}
public String getStr2() {
return str2;
}
public void setStr2(String str2) {
this.str2 = str2;
}
public String getDefault_str() {
return default_str;
}
public void setDefault_str(String default_str) {
this.default_str = default_str;
}
public String addStr(){
if(str1 != null){
if(str2 != null){
return str1 + "," + str2;
}
return str1;
}
return default_str;
}
}
3、 定义xxxProperties类,属性配置类,完成属性配置相关的操作,比如设置属性前缀,用于在application.properties中配置
//指定项目在属性文件中配置的前缀为str,即可以在属性文件中通过 str.str1=springboot,就可以改变属性类字段 str1 的值了
@SuppressWarnings("StringProperties")
@ConfigurationProperties(prefix = "str")
public class StringProperties {
public static final String DEFAULT_STR1 = "I know, you need me";
public static final String DEFAULT_STR2 = "but I also need you";
private String str1 = DEFAULT_STR1;
private String str2 = DEFAULT_STR2;
public String getStr1() {
return str1;
}
public void setStr1(String str1) {
this.str1 = str1;
}
public String getStr2() {
return str2;
}
public void setStr2(String str2) {
this.str2 = str2;
}
}
4、定义xxxAutoConfiguration类,自动配置类,用于完成Bean创建等工作
// 定义 java 配置类
@Configuration
//引入StringService
@ConditionalOnClass({StringService.class})
// 将 application.properties 的相关的属性字段与该类一一对应,并生成 Bean
@EnableConfigurationProperties(StringProperties.class)
public class StringAutoConfiguration {
// 注入属性类
@Autowired
private StringProperties stringProperties;
@Bean
// 当容器没有这个 Bean 的时候才创建这个 Bean
@ConditionalOnMissingBean(StringService.class)
public StringService helloworldService() {
StringService stringService = new StringService();
stringService.setStr1(stringProperties.getStr1());
stringService.setStr2(stringProperties.getStr2());
return stringService;
}
}
5、在resources下创建目录META-INF,在 META-INF 目录下创建 spring.factories,在SpringBoot启动时会根据此文件来加载项目的自动化配置类
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.lhf.springboot.config.StringAutoConfiguration
验证:其他项目中使用自定义的Starter
- 在新项目中引入自定义Starter依赖配置
<!--引入自定义Starter-->
<dependency>
<groupId>com.lhf.springboot</groupId>
<artifactId>spring-boot-starter-string</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
- 编写一个简单的Controller
@RestController
public class StringController {
@Autowired
private StringService stringService; //引入自定义Starter中的StringService
@RequestMapping("/")
public String addString(){
return stringService.addStr();
}
}
- 编写属性配置文件,内容如下:
#配置自定义的属性信息
str.str1=为什么我的眼里常含泪水
str.str2=那是因为我对你爱的深沉
- 启动项目进行访问,效果如图:
十、项目中使用了springcloud哪些组件及其原理
服务发现——Netflix Eureka
Applecation-server :服务提供者
Application-cliene:服务消费者
服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。
Eureka Server 进入自我保护机制,会出现以下几种情况:
(1) Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
(2) Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
(3) 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
客服端负载均衡——Netflix Ribbon
一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。当然我们也可以直接使用负载均衡组件,Ribbon。
Ribbon是Netiflix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为。为Ribbon配置服务提供者地址列表后,Ribbon就可以基于某种负载均衡算法,自动的帮助服务消费者去请求。
Ribbon的负载均衡算法有:轮询、随机等,默认是轮询。
断路器——Netflix Hystrix
Hystix是分布式系统的一个延迟和容错的开源库,它可以进行熔断、降级、限流、监控,可以避免分布式系统的级联故障和雪崩效应。
服务熔断:熔断是直接调用降级方法。不调用目标方法,无需等待接口调用超时才返回结果。
服务降级:降级是调用目标方法,由于目标方法调用超时或者异常,才调用降级方法。
使用:服务降级是在消费端和feign一起使用,默认降级的配置不是开启的(feign.hystrix.enabled=false),服务熔断是在服务端使用,对服务端的controller进行熔断,默认熔断的配置是开启的(spring.cloud.circuit.breaker.enabled=true)。
//业务类代码的方法上
@Override
@HystrixCommand(
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "60000"),//指定多久超时,单位毫秒。超时进fallback
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//判断熔断的最少请求数,默认是10;只有在一个统计窗口内处理的请求数量达到这个阈值,才会进行熔断与否的判断
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),//判断熔断的阈值,默认值50,表示在一个统计窗口内有50%的请求处理失败,会触发熔断
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000") //熔断多少毫秒后开始尝试请求 默认5000ms
})
public BaseSearchResultDTO<ResPerspectiveStr> getPerspectiveData(ReqGetTransPerspectiveDataDTO reqGetTransPerspectiveDataDTO) throws Exception {...}
服务网关——Netflix Zuul
Zuul作为微服务的网关,对微服务的访问进行控制,它可以进行路由、过滤、鉴权、代理请求
分布式配置——Spring Cloud Config
SpringcloudConfig是微服务中的配置中心,对微服务中多个自服务的配置进行统一的管理,可以对配置的读取、加密、解密等操作。
服务间的通信——spring cloud feign
Feign继承了Ribbon,使用接口的方式进行服务调用。
在配置类上,加上@EnableFeginClients,那么该注解是基于@Import注解,注册有关Fegin的解析注册类,这个类是实现 ImportBeanDefinitionRegistrar 这个接口,重写registryBeanDefinition 方法。他会扫描所有加了@FeginClient 的接口,然后针对这个注解的接口生成动态代理,然后你针对fegin的动态代理去调用他方法的时候,此时会在底层生成http协议格式的请求,使用HttpURLConnection进行调用。
网关组件——Gateway
网关组件:根据微服务名称,创建动态路进行转发,实现动态路由功能
当服务注册时,不需要重启服务端,动态去做负载均衡、服务发现,只需要加上服务名称即可
Geteway 的核心逻辑:路由转发+执行过滤器链
Gateway是web网关,处理的是Http请求
拓展问题:
1.Eureka与Zookeeper的区别在哪?
- zookeeper保证的是CP,在Zookeeper集群中,当发生网络故障导致master节点和slave节点失联时,剩余的slave节点会进行leader选举,而在选举的过程中,zookeeper集群不可用,不能对外提供注册和查询的服务。主从节点数据同步的时候不能对外提供服务。
- Eureka保证的是AP,在Eureka集群中,某些节点挂掉,只要有一个Eureka节点存在,就可以对外提供注册和查询服务,但是可能注册信息不是最新的(不保证强一致性)。
2 .Eureka与Nacos的区别?
Nacos既支持AP也支持CP,默认使用AP和Eureka一样。
3.负载均衡算法有哪些?
随机、轮询、响应时间权重、重试等,还可以实现IRule接口,自定义负载均衡算法。
4.Ribbon和Feign的区别?
- 启动类上加的注解不同,Ribbon用的是@RibbonClients;Feign用的是@EnableFeignClients
- 服务的指定位置不同,Ribbon是在@RibbonClient上指定服务名称;Feign是在接口的@FeignClient上指定。
- 调用方式不同,Ribbon需要自己构建http请求,模拟http请求,然后使用RestTempate进行调用;Feign采用接口的方式调用。
5.Feign和OpenFeign的区别?
OpenFeign在feign的基础上支持了SpringMVC的注解,如@RequestMapping等等,OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
6.什么是服务雪崩?
服务雪崩就是服务A调用服务B,服务B调用服务C,服务C挂掉了,导致服务B、C超时受影响,导致服务A也超时,对服务造成级联的影响。即下游服务挂掉或者超时,导致上游调用服务大面积受到影响,阻塞、超时,进而导致雪崩效应。
7.Hystix和Sentinel的区别?
8.限流算法有几种?
计数器、滑动窗口计数器、漏桶法、令牌桶
9.Zuul和gateway的区别?
- Zuul1.0是阻塞式的api,不支持长连接,而gateway支持异步。
- Zuul没有提供限流、负载均衡等支持,而gateway支持。
- 它们都是web网关,处理http请求,底层都是servlet。
10.Config和Nacos的区别?
- Config大部分集合git使用,配置动态变更需要依赖SpringCloudBus消息总线来通知所有Client变化;并且没有可视化界面。
- Nacos采用长连接,一旦配置变更,会迅速通知Client进行变更,速度较快;提供可视化界面。
十一、Redis数据类型
string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)
十二、Redis持久化
持久化机制RDB和AOF,RDB是把当前reids库中的快照保存到磁盘一份,AOF是把所有命令操作追加到日志文件中的方式;
可以添加以上三个配置,在上面有三个配置选项 分别是
每隔900秒 有一条key进行变更,
每个300秒 有10条key进行变更,
每隔60秒 有10000条key进行变更。
以上任意条达成条件后,就会重新生成一个dump.rdb文件。就是当前redis内存中完整的快照,这个操作也被称之为snapshotting,快照也可以手动调用save或者bgsave命令,同步或异步执行rdb快照生成
save可以设置为多个,也就是多个snapshotting检查点,每到一个检查点,就会去check一下,检查是否有指定数量的key发生了变更,如果有那么就会生成一个新的dump.rdb文件。
十三、Redis淘汰策略
(1)volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
(2)volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
(3)volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
(4)volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。
(5)allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
(6)allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。
(7)allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
(8) no-enviction(驱逐):禁止驱逐数据,这也是默认策略。意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失。
十四、Redis集群方式
主从复制,哨兵模式和集群
十五、Redis中string底层结构
int:当存储的字符串全是数字时,此时使用int方式来存储;
embstr:当存储的字符串长度小于44个字符时,此时使用embstr方式来存储;
raw:当存储的字符串长度大于44个字符时,此时使用raw方式来存储;
十六、MQ工作方式
简单模式、工作模式、消息发布和订阅、路由模式、主题模式
十七、MySQL数据库引擎比较
MyISAM、InnoDB、MERGE、MEMORY(HEAP)、BDB(BerkeleyDB)、EXAMPLE、FEDERATED、ARCHIVE、CSV、BLACKHOLE
MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。MyISAM在所有MySQL配置里被支持,它是默认的存储引擎,除非你配置MySQL默认使用另外一个引擎。
MEMORY存储引擎提供“内存中”表。MERGE存储引擎允许集合将被处理同样的MyISAM表作为一个单独的表。就像MyISAM一样,MEMORY和MERGE存储引擎处理非事务表,这两个引擎也都被默认包含在MySQL中。
注:MEMORY存储引擎正式地被确定为HEAP引擎。
InnoDB和BDB存储引擎提供事务安全表。BDB被包含在为支持它的操作系统发布的MySQL-Max二进制分发版里。InnoDB也默认被包括在所 有MySQL 5.1二进制分发版里,你可以按照喜好通过配置MySQL来允许或禁止任一引擎。
EXAMPLE存储引擎是一个“存根”引擎,它不做什么。你可以用这个引擎创建表,但没有数据被存储于其中或从其中检索。这个引擎的目的是服务,在 MySQL源代码中的一个例子,它演示说明如何开始编写新存储引擎。同样,它的主要兴趣是对开发者。
NDB Cluster是被MySQL Cluster用来实现分割到多台计算机上的表的存储引擎。它在MySQL-Max 5.1二进制分发版里提供。这个存储引擎当前只被Linux, Solaris, 和Mac OS X 支持。在未来的MySQL分发版中,我们想要添加其它平台对这个引擎的支持,包括Windws。
ARCHIVE存储引擎被用来无索引地,非常小地覆盖存储的大量数据。
CSV存储引擎把数据以逗号分隔的格式存储在文本文件中。
BLACKHOLE存储引擎接受但不存储数据,并且检索总是返回一个空集。
FEDERATED存储引擎把数据存在远程数据库中。在MySQL 5.1中,它只和MySQL一起工作,使用MySQL C Client API。在未来的分发版中,我们想要让它使用其它驱动器或客户端连接方法连接到另外的数据源。
当你创建一个新表的时候,你可以通过添加一个ENGINE 或TYPE 选项到CREATE TABLE语句来告诉MySQL你要创建什么类型的表:
CREATE TABLE t (i INT) ENGINE = INNODB;
CREATE TABLE t (i INT) TYPE = MEMORY;
十八、Mysql事务隔离级别说明
修改 set session TRANSACTION ISOLATION LEVEL read UNCOMMITTED;
事务隔离级别:
read-uncommitted 未提交读
read-COMMITTED 提交读
repeatable-read 可重复读 默认隔离级别 select @@tx_isolation;查看
serializable 序列化
十九、项目中使用哪些设计模式
单例模式 工厂模式 策略,模板,观察者等
二十、批量导出的1000个任务,如何实现
多线程执行业务、分页查询,Excel多sheet导出;
二十一、什么场景会出现堆、栈溢出
堆内存中的空间不足 导致堆内存溢出;
超过98%的时间用来做GC并且回收了不到2%的堆内存 导致堆内存溢出;
unable to create new native thread 不能创建新的线程 导致堆内存溢出;linux非root账号默认1024个线程;
Metaspace 元数据区(Metaspace) 已被用满 导致堆内存溢出;
局部数组过大。当函数内部的数组过大时,有可能导致栈溢出。
递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致栈溢出。
指针或数组越界。这种情况最常见,例如进行字符串拷贝,或处理用户输入等等。
二十二、一般怎么优化慢查询
利用explain 关键字查看执行计划:
索引没起作用。
优化数据库结构,列多的表拆分多表和中间表。
分解关联查询。
优化业务变成单表查询。
二十三、什么情况下会使得索引失效
条件中有or,即使其中有条件带索引也不会使用。
对于多列索引,不是使用的第一部分,则不会使用索引。
like查询是以%开头
如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。
如果mysql估计使用全表扫描要比使用索引快,则不使用索引。
二十四、Redis集群时分布式锁的问题(主节点锁信息未同步至从节点,主宕机)
Redlock实现
在Redis的分布式环境中,我们假设有N个Redis master。
这些节点完全互相独立,不存在主从复制或者其他集群协调机制。
我们确保将在N个实例上使用与在Redis单实例下相同方法获取和释放锁。
现在我们假设有5个Redis master节点,同时我们需要在5台服务器上面运行这些Redis实例,这样保证他们不会同时都宕掉。
为了取到锁,客户端应该执行以下操作:
获取当前Unix时间,以毫秒为单位。
依次尝试从5个实例,使用相同的key和具有唯一性的value(例如UUID)获取锁。
当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。
例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。
这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。
如果服务器端没有在规定时间内响应,客户端应该尽快尝试去另外一个Redis实例请求获取锁。
客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。
当且仅当从大多数(N/2+1,这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。
redisson已经有对redlock算法封装,RedissonRedLock类,可以直接使用redLock.tryLock()或者redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS)方法。