最近找工作,每天更新面试题目。。。。
2021-10-19
什么是静态代理
所谓静态代理也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就已经确定了。
优点:
静态代理对(代理类)隐藏了被代理类(目标类接口)的具体实现类,在一定程度上实现了解耦合,同时也隐藏了具体的代码实现,提高了安全性!
缺点:
代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,需要为每一种方法都进行代理,就会产生很多的代理类,就会产生类爆炸类过多,静态代理在程序规模稍大时就无法胜任。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。
什么是动态代理
动态代理不同于静态代理在程序运行前就已经存在代理,而是在需要的时候才去创建,而不是提前创建好,比如明星的助理有多个,需要什么就找对应的助理。
动态代理优缺点:
优点:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandlerinvoke)。这样,在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使类职责更加单一,复用性更强。
缺点:
1、JDK原生动态代理是Java原生支持的,不需要任何外部依赖,但是它只能基于接口进行代理(因为它已经继承了Proxy了,Java不支持多继承),如果被代理类没有实现接口,则无法被代理。
2、CGLIB通过继承的方式进行代理、无论目标对象没有没实现接口都可以代理,但是无法处理private和final的情况(private和final修饰的方法不能被重写)
JDK动态代理和CGLIB动态代理区别
Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib,具体使用哪种方式生成由AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。
JDK 动态接口代理
- JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。
InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类
的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy 利用 InvocationHandler 动态创建
一个符合某一接口的实例,生成目标类的代理对象。
CGLib 动态代理 - :CGLib 全称为 Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展 Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新的 class。和 JDK 动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过 CGLib 创建动态代理。
什么是正向代理,什么是反向代理
正向代理即是客户端代理, 代理客户端, 服务端不知道实际发起请求的客户端
反向代理即是服务端代理, 代理服务端, 客户端不知道实际提供服务的服务端
正向代理中,proxy和client同属一个LAN,对server透明;
反向代理中,proxy和server同属一个LAN,对client透明。
实际上proxy在两种代理中做的事都是代为收发请求和响应,不过从结构上来看正好左右互换了下,所以把后出现的那种代理方式叫成了反向代理
为什么要用代理
代理就是给目标类提供一个代理对象,由代理对象控制目标对象的引用。(比如从西安买飞机票到厦门,可以去机场买,也可以去代售点买,代售点就代理了机场的售票窗口,我们就不用去机场买票了)
java的代理就是不直接访问目标,而是通过一个中间层来访问目标
代理有啥好处呢?①通过代理对象的方式间接的访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性,②通过代理对象对原有的业务增强。
当我们需要引入其它一些功能。比如在业务层记录一些方法的调用日志,我们就可以创建一个代理类,每次调用目标方法的时候,先通过代理类记录了当前调用的时间和IP地址,是谁调用此方法用了多长时间。
可能有人会说为什么要加个代理类?直接在原来类的方法里面加上记录日志这个逻辑不就可以了么?
这样就会出现问题了,当某一天有些类的一些方法不需要记录日志了,我们就要去修改这些类的每一个方法。在程序设计中类的单一性原则问题,这个原则很简单,就是每个类的功能尽可能的单一。为什么要单一,因为只有功能单一这个类被改动的可能性才会最小。
如果我们做了代理,我们就可以把目标的代理类删掉就可以了,这样我们也就不用去修改目标类了。
策略模式(策略类膨胀的时候怎么解决),单例模式(几种实现方式),工厂模式,代理模式
如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题
单例模式的实现方式:
1、懒汉式,线程不安全
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2、懒汉式,线程安全
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3、饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
4、双检锁/双重校验锁(DCL,即 double-checked locking)
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
5、静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
6、枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
工厂模式
代理模式
参考JDK动态代理和CGlib动态代理
GC垃圾回收的几种算法
如何确定垃圾
- 引用计数法
- 可达性分析
垃圾回收算法
- 标记清除算法(Mark-Sweep)
- 复制算法(copying)
- 标记整理算法(Mark-Compact)
- 分代收集算法
GC 垃圾收集器
- Serial 垃圾收集器(单线程、复制算法)
- ParNew 垃圾收集器(Serial+多线程)
- Parallel Scavenge 收集器(多线程复制算法、高效)
- Serial Old 收集器(单线程标记整理算法 )
- Parallel Old 收集器(多线程标记整理算法)
- CMS 收集器(多线程标记清除算法)
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。 - G1 收集器
如保证线程安全
可参考–链接–> https://blog.csdn.net/weixin_40459875/article/details/80290875
线程安全在三个方面体现
1.原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
2.可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。
原子性—atomic
JDK里面提供了很多atomic类,比如AtomicInteger, AtomicLong, AtomicBoolean等等,这些类本身可以通过CAS来保证操作的原子性
原子性—synchronized
synchronized是一种同步锁,通过锁实现原子操作。
JDK提供锁分两种:一种是synchronized,依赖JVM实现锁,因此在这个关键字作用对象的作用范围内是同一时刻只能有一个线程进行操作;另一种是LOCK,是JDK提供的代码层面的锁,依赖CPU指令,代表性的是ReentrantLock。
synchronized修饰的对象有四种:
(1)修饰代码块,作用于调用的对象;
(2)修饰方法,作用于调用的对象;
(3)修饰静态方法,作用于所有对象;
(4)修饰类,作用于所有对象。
可见性—volatile
对于可见性,JVM提供了synchronized和volatile。这里我们看volatile。
(1)volatile的可见性是通过内存屏障和禁止重排序实现的
volatile会在写操作时,会在写操作后加一条store屏障指令,将本地内存中的共享变量值刷新到主内存:
volatile在进行读操作时,会在读操作前加一条load指令,从内存中读取共享变量:
(2)但是volatile不是原子性的,进行++操作不是安全的
(3)volatile适用的场景
既然volatile不适用于计数,那么volatile适用于哪些场景呢:
- 对变量的写操作不依赖于当前值
- 该变量没有包含在具有其他变量不变的式子中
因此,volatile适用于状态标记量:如下
线程1负责初始化,线程2不断查询inited值,当线程1初始化完成后,线程2就可以检测到inited为true了。
有序性
有序性是指,在JMM中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
可以通过volatile、synchronized、lock保证有序性。
另外,JMM具有先天的有序性,即不需要通过任何手段就可以得到保证的有序性。这称为happens-before原则。
如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性。虚拟机可以随意地对它们进行重排序。
happens-before原则:
1.程序次序规则:在一个单独的线程中,按照程序代码书写的顺序执行。
2.锁定规则:一个unlock操作happen—before后面对同一个锁的lock操作。
3.volatile变量规则:对一个volatile变量的写操作happen—before后面对该变量的读操作。
4.线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。
5.线程终止规则:线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
6.线程中断规则:对线程interrupt()方法的调用happen—before发生于被中断线程的代码检测到中断时事件的发生。
7.对象终结规则:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。
8.传递性:如果操作A happen—before操作B,操作B happen—before操作C,那么可以得出A happen—before操作C。
JVM调优的经历
如何查看 JVM 参数默认值
jps -v 可以查看 jvm 进程显示指定的参数
使用 -XX:+PrintFlagsFinal 可以看到 JVM 所有参数的值
jinfo 可以实时查看和调整虚拟机各项参数
jvm 调优的工具
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。
jconsole:用于对 JVM 中的内存、线程和类等进行监控;
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。
常用的 jvm 调优的参数都有哪些
-Xms2g:初始化推大小为 2g;
-Xmx2g:堆最大内存为 2g;
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息
-XX:NewSize和-XX:MaxNewSize
设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设置为一样。
-XX:SurvivorRatio
设置Eden和一个Survivor的比值,这个会涉及到优化。
-XX:+PrintTenuringDistribution
参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。
-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
设置对象晋升到老年代年龄的最小值和最大值,每个对象在经历过一次Minor GC之后,年龄就加1。
原子包的的实现过程
Redis的几种数据结构
基础:字符串(String)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)
还有HyperLoglog、流、地理坐标等
什么是缓存雪崩、什么是缓存穿透,该如何避免
缓存雪崩
缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间所有原本应该访问缓存的请求都去查询数据库了,而对数据库 CPU 和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。一般有三种处理办法:
- 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
- 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
- 为 key 设置不同的缓存失效时间。
缓存穿透
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库
缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
缓存更新
缓存更新除了缓存服务器自带的缓存失效策略之外(Redis 默认的有 6 中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
谈谈对spring的认识,springbean的生命周期
对spring的认识
生命周期:
实例化
- 实例化一个 Bean,也就是我们常说的 new。
IOC 依赖注入 - 按照 Spring 上下文对实例化的 Bean 进行配置,也就是 IOC 注入。
setBeanName 实现 - 如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String)方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值
BeanFactoryAware 实现 - 如果这个 Bean 已经实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory,setBeanFactory(BeanFactory)传递的是 Spring 工厂自身(可以用这个方式来获取其它 Bean,只需在 Spring 配置文件中配置一个普通的 Bean 就可以)。
ApplicationContextAware 实现 - 如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用setApplicationContext(ApplicationContext)方法,传入 Spring 上下文(同样这个方式也可以实现步骤 4 的内容,但比 4 更好,因为 ApplicationContext 是 BeanFactory 的子接口,有更多的实现方法)
postProcessBeforeInitialization 接口实现-初始化预处理 - 如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor 经常被用作是 Bean 内容的更改,并且由于这个是在 Bean 初始化结束时调用那个的方法,也可以被应用于内存或缓存技术。
init-method - 如果 Bean 在 Spring 配置文件中配置了 init-method 属性会自动调用其配置的初始化方法。
postProcessAfterInitialization - 如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用postProcessAfterInitialization(Object obj, String s)方法。
注:以上工作完成以后就可以应用这个 Bean 了,那这个 Bean 是一个 Singleton 的,所以一般情况下我们调用同一个 id 的 Bean 会是在内容地址相同的实例,当然在 Spring 配置文件中也可以配置非 Singleton。
Destroy 过期自动清理阶段 - 当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用那个其实现的 destroy()方法;
destroy-method 自配置清理 - 最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法。
IOC原理
Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化Bean 并建立 Bean 之间的依赖关系。 Spring 的 IoC 容器在完成这些底层工作的基础上,还提供了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。
Ioc 提供了一个bean容器,容器会帮助我们创建对象,不需要我们手动的创建,IOC容器有个非常强大的功能叫做依赖注入,通过写java代码创建bean,通过type等方式自动的注入,正是有了这个依赖注入使得我们IOC有了一个强大的功能叫解耦。
AOP原理
“横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect”,即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来
AOP:Aspect Oriented Programming 面向切面编程
OOP:Object Oriented Programming 面向对象编程
面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。AOP是Spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC做补充。通俗点说的话就是在程序运行期间,将某段代码动态切入到指定方法的指定位置进行运行的这种编程方式。
AOP的应用场景
- 日志管理
- 权限认证
- 安全检查
- 事务控制
AOP 核心概念
1、切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
2、横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
3、连接点(joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
4、切入点(pointcut):对连接点进行拦截的定义
5、通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
6、目标对象:代理的目标对象
7、织入(weave):将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法
或字段。
MVC流程
MVC 原理
Spring 的模型-视图-控制器(MVC)框架是围绕一个 DispatcherServlet 来设计的,这个 Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等,甚至还能支持文件上传。
MVC流程
Http 请求到 DispatcherServlet
(1) 客户端请求提交到 DispatcherServlet。
HandlerMapping 寻找处理器
(2) 由 DispatcherServlet 控制器查询一个或多个 HandlerMapping,找到处理请求的Controller。
调用处理器 Controller
(3) DispatcherServlet 将请求提交到 Controller。
Controller 调用业务逻辑处理后,返回 ModelAndView
(4)(5)调用业务处理和返回结果:Controller 调用业务逻辑处理后,返回 ModelAndView。
DispatcherServlet 查询 ModelAndView
(6)(7)处理视图映射并返回模型: DispatcherServlet 查询一个或多个 ViewResoler 视图解析器,找到 ModelAndView 指定的视图。
ModelAndView 反馈浏览器 HTTP
(8) Http 响应:视图负责将结果显示到客户端。
mybatis缓存
Mybatis 中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存是指 SqlSession 级别的缓存,当在同一个 SqlSession 中进行相同的 SQL 语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存 1024 条 SQL。二级缓存是指可以跨 SqlSession 的缓存。是 mapper 级别的缓存,对于 mapper 级别的缓存不同的sqlsession 是可以共享的。
Mybatis 的一级缓存原理(sqlsession 级别)
第一次发出一个查询 sql,sql 查询结果写入 sqlsession 的一级缓存中,缓存使用的数据结构是一个 map。
key:MapperID+offset+limit+Sql+所有的入参
value:用户信息
同一个 sqlsession 再次发出相同的 sql,就从缓存中取出数据。如果两次中间出现 commit 操作(修改、添加、删除),本 sqlsession 中的一级缓存区域全部清空,下次再去缓存中查询不到所以要从数据库查询,从数据库查询到再写入缓存。
二级缓存原理(mapper 基本)
二级缓存的范围是 mapper 级别(mapper 同一个命名空间),mapper 以命名空间为单位创建缓存数据结构,结构是 map。mybatis 的二级缓存是通过 CacheExecutor 实现的。CacheExecutor其实是 Executor 的代理对象。所有的查询操作,在 CacheExecutor 中都会先匹配缓存中是否存在,不存在则查询数据库。
key:MapperID+offset+limit+Sql+所有的入参
具体使用需要配置:
- Mybatis 全局配置中启用二级缓存配置
- 在对应的 Mapper.xml 中配置 cache 节点
- 在对应的 select 查询节点中添加 useCache=true
kafka原理及作用
Apache Kafka 是由Apache 开发的一种发布订阅消息系统;是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景。
- broker:Kafka 服务器,负责消息存储和转发
- topic:消息类别,Kafka 按照 topic 来分类消息
- partition:topic 的分区,一个 topic 可以包含多个 partition,topic 消息保存在各个partition 上
- offset:消息在日志中的位置,可以理解是消息在 partition 上的偏移量,也是代表该消息的唯一序号
- Producer:消息生产者
- Consumer:消息消费者
- Consumer Group:消费者分组,每个 Consumer 必须属于一个 group
- Zookeeper:保存着集群 broker、topic、partition 等 meta 数据;另外,还负责 broker 故障发现,partition leader 选举,负载均衡等功能
Kafka的特性: - 高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition, consumer group 对partition进行consume操作。
- 可扩展性:kafka集群支持热扩展
- 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
- 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
- 高并发:支持数千个客户端同时读写
消息中间件的两种实现模式
JMS (Java Message Service) 对消息的发送和接收定义了两种模式: - 点对点模式:消息的生产和消费者均只有一个,消息由生产者将消息发送到消息队列(queue)中,然后消息消费者从队列中取出消息进行消费,消息被取出后,queue中不再保存该消息。
- 发布订阅模式:消息的生产和消费者可能有多个,使用主题(Topic)来对消息进行分类,生产者将消息发送到主题,多个消费者均可以对这个主题进行消费。类似于对多个消费者做广播。
常见的消息中间件Active MQ, Rabbit MQ , Kafka中,只有Active 完全实现了上述JMS的规范,Kafka则通过消费组和主题分区的方式让发布订阅模型同时也具有了点对点模式的消息收发能力。事实上没有完全按上述JMS规范设计Rabbit MQ,和Kafka反而更优秀,其中Kafka在完全按照分布式的思想来设计的,在大数据和高可用上有着天然优势。
使用场景:
- 消息传输:即用作消息中间件
- 行为日志跟踪:
Kafka 最早就是用于重建用户行为数据追踪系统的。很多网站上的用户操作都会以消息的形式发送到Kafka 的某个对应的topic 上。这些点击流蕴含了巨大的商业价值, 事实上,目前就有很多创业公司使用机器学习或其他实时处理框架来帮助收集并分析用户的点击流数据。鉴于这种点击流数据量是很大的, Kafka 超强的吞吐量特性此时就有了用武之地 - 审计数据收集:
很多企业和组织都需要对关键的操作和运维进行监控和审计。这就需要从各个运维应用程序处实时汇总操作步骤信息进行集中式管理。在这种使用场景下,你会发现Kafka 是非常适合的解决方案,它可以便捷地对多路消息进行实时收集,同时由于其持久化的特性,使得后续离线审计成为可能。 - 日志收集:
这可能是Kafka 最常见的使用方式了一一日志收集汇总解决方案。每个企业都会产生大量的服务日志,这些日志分散在不同的机器上。我们可以使用Kafka 对它们进行全量收集,井集中送往下游的分布式存储中(比如HDF S 等) 。比起其他主流的日志抽取框架Kafka 有更好的性能,而且提供了完备的可靠性解决方案,同时还保持了低延时的特点。 - 流处理:
很多用户接触到Kafka 都是因为它的消息队列功能。自0.10.0.0 版本开始, Kafka 社区推出了一个全新的流式处理组件Kafka Streams 。这标志着Kafka 正式进入流式处理框架俱乐部。相比老牌流式处理框架Apache Storm 、Apache Samza,或是最近风头正劲的Spark Strearming,抑或是Apache Flink, Kafka Streams 的竞争力如何?让我们拭目以待。
数据结构-位图介绍
位图的原理就是用一个 bit 来标识一个数字是否存在,采用一个 bit 来存储一个数据,所以这样可以大大的节省空间。 bitmap 是很常用的数据结构,比如用于 Bloom Filter 中;用于无重复整数的排序等等。bitmap 通常基于数组来实现,数组中每个元素可以看成是一系列二进制数,所有元素组成更大的二进制集合
spring关于bean的常用注解
管理bean注解分类
在Spring中,主要用于管理bean的注解分为四大类:
1.用于创建对象。
2.用于给对象的属性注入值。
3.用于改变作用的范围。
4.用于定义生命周期。
Spring中创建对象的有四个:分别是@Component,@Controller,@Service,@Repository。
@Component注解:把资源让Spring来管理,相当于xml中的配置的Bean。属性:value:指定Bean中的id。如果不指定value属性,默认Bean的id是当前类的类名,首字母小写。在开发中的场景是这样的,其实是在实现类中加入即可
而其它的三个注解都是针对一个衍生注解,它们的作用及属性都是一模一样的。只不过提供了更加明确的语义化。
@Controller:一般用于表现层的注解。
@Service:一般用于业务层的注解。
@responsitory:一般用于持久层的注解。
用法与@Component相同
给对象的属性注入值得问题
spring给我们提出了注入数据的注解有:@Value,@Autowired,@Qualifier,@Resource。
@Value:注入基本数据类型和String类型数据,它的属性value用于指定值。
@Autowired这个用法是比较重要的,它能够自动按照类型注入。当使用注解注入属性时,set方法可以省略。它只能注入其他Bean类型。当有多个类型匹配时,使用要注入的对象变量名称作为Bean的id,在Spring容器中查找,找到了也可以注入成功,找不到就报错。这句话是不是很难理解。其实所表达的意思是这样的,这个注解的是自动注入的意思,写注入的对象,这个对象就是Bean中的id,让Spring自己去查找,找到说明可以用,找不到则报错
@Qualifier:官方是这样介绍的,在自动按照类型的基础上,再按照Bean的id注入。它在给字段注入时不能独立使用,必须和@Autowired一起使用,但是给方法参数注入时,可以独立使用。属性:指定Bean的id。应用场景:如果一个接口有两个设置多个实现类,如果对其注入的话,优先会注入与Bean的id与属性名一样的Bean,如果像注入指定的Bean,则需要用@Qualifier注解名字注入。
@Resource:直接按照Bean的id注入,它只能注入其它的Bean类型。属性:name指定Bean的id。
@Scope用于改变作用域:指定Bean的作用域。属性:value指定范围的值。默认是单例的,如果设置为多列,只需在类中加@Scope(“prototype”)
用于生命周期相关的注解
@PostConstruct注解,加在方法上指定Bean对象创建好之后,调用该方法初始化对象,类似于xml的init-method方法。
@PreDestroy注解,指定Bean销毁之前,调用该方法,类似与xml的destroy-method方法。注意的是如果你要想看当前的效果,就必须要调用ClassPathXmlApplicationContext.close( )方法,同时scope的值要是singleton。是在销毁之前执行。
volatile关键字的作用及和sychronized的区别
volatile
volatile关键字的作用就是保证了可见性和有序性(不保证原子性)
可见性: 如果一个共享变量被volatile关键字修饰,那么如果一个线程修改了这个共享变量后,其他线程是立马可知的。
WHY? 比如,线程A修改了自己的共享变量副本,这时如果该共享变量没有被volatile修饰,那么本次修改不一定会马上将修改结果刷新到主存中,如果此时B去主存中读取共享变量的值,那么这个值就是没有被A修改之前的值。如果该共享变量被volatile修饰了,那么本次修改结果会强制立刻刷新到主存中,如果此时B去主存中读取共享变量的值,那么这个值就是被A修改之后的值了
有序性: volatile能禁止指令重新排序,在指令重排序优化时,在volatile变量之前的指令不能在volatile之后执行,在volatile之后的指令也不能在volatile之前执行,所以它保证了有序性。
synchronized
synchronized关键字的作用
synchronized提供了同步锁的概念,被synchronized修饰的代码段可以防止被多个线程同时执行,必须一个线程把synchronized修饰的代码段都执行完毕了,其他的线程才能开始执行这段代码。
因为synchronized保证了在同一时刻,只能有一个线程执行同步代码块,所以执行同步代码块的时候相当于是单线程操作了,那么线程的可见性、原子性、有序性(线程之间的执行顺序)它都能保证了
volatile关键字和synchronized关键字的区别
(1)、volatile只能作用于变量,使用范围较小。synchronized可以用在变量、方法、类、同步代码块等,使用范围比较广。
(2)、volatile只能保证可见性和有序性,不能保证原子性。而可见性、有序性、原子性synchronized都可以包证。
(3)、volatile不会造成线程阻塞。synchronized可能会造成线程阻塞。
如何合理的设置线程池
当线程池的核心线程数量过大或者过小时
当线程池中核心线程数量过大时,线程与线程之间会争取CPU资源,这样就会导致上下文切换。过多的上下文切换会增加线程的执行时间,影响了整体执行的效率;
多线程编程中一般线程的个数都大于CPU核心的个数,而一个CPU核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效的执行,CPU采取的策略是为了每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让其他线程使用,这个过程就属于一次上下文切换。
当线程池中的核心线程数量过少时,如果同一时间有大量任务需要处理,可能会导致大量任务在任务队列中排队等待执行,甚至会出现队列满了之后任务无法执行的情况,或者大量任务堆积在任务队列导致内存溢出(OOM)。
任务性质
主要分为CPU密集型(计算密集型)任务、I/O密集型任务、混合型任务:
CPU密集型(计算密集型):系统的I/O读写效率高于CPU效率,大部分的情况是CPU有许多运算需要处理,使用率很高。但I/O执行很快。
I/O密集型:系统的CPU性能比磁盘读写效能要高很多,大多数情况是CPU在等I/O的读写操作,此时CPU的使用率并不高;
混合型任务:既包含CPU密集型又包含I/O密集型。
合理设置核心线程数
CPU密集型:核心线程数=CPU核心数(或 核心线程数=CPU核心数+1)
I/O密集型:核心线程数=2*CPU核心数(或 核心线程数=CPU核心数/(1-阻塞系数))
混合型:核心线程数=(线程等待时间/线程CPU时间+1)*CPU核心数
对于CPU密集型任务,由于CPU密集型任务的性质,导致CPU的使用率很高,如果线程池中的核心线程数量过多,会增加上下文切换的次数,带来额外的开销。因此,一般情况下线程池的核心线程数量等于CPU核心数+1。(注:这里核心线程数不是等于CPU核心数,是因为考虑CPU密集型任务由于某些原因而暂停,此时有额外的线程能确保CPU这个时刻不会浪费。但同时也会增加一个CPU上下文切换,因此核心线程数是等于CPU核心数?还是CPU核心数+1?可以根据实际情况来确定)
对于I/O密集型任务,由于I/O密集型任务CPU使用率并不是很高,可以让CPU在等待I/O操作的时去处理别的任务,充分利用CPU。因此,一般情况下线程的核心线程数等于2*CPU核心数。(注:有些公司会考虑所需要的CPU阻塞系数,即核心线程数=CPU核心数/(1-阻塞系数))
对于混合型任务,由于包含2种类型的任务,故混合型任务的线程数与线程时间有关。一般情况下:线程池的核心线程数=(线程等待时间/线程CPU时间+1)*CPU核心数;在某种特定的情况下还可以将任务分为I/O密集型任务和CPU密集型任务,分别让不同的线程池去处理,但有一个前提–分开后2种任务的执行时间相差不太大。
jvm类加载过程
Set能不能作为Map中的key?为什么?
不可以:
set元素和map的key底层都是红黑树这种数据结构,红黑树必须保持有序性
int,double,string这些类型无论是放在set中或者是作为map的key都没问题,这是因为这些类型重载了<运算符,作为set或者map的key的必要条件是重载<运算符或者传入比较的函数对象
目前不知道
2021-10-20
事务失效的几种情况
1、spring的事务注解@Transactional只能放在public修饰的方法上才起作用,如果放在其他非public(private,protected)方法上,虽然不报错,但是事务不起作用
2、如果采用spring+spring mvc,则context:component-scan重复扫描问题可能会引起事务失败。
如果spring和mvc的配置文件中都扫描了service层,那么事务就会失效。
原因:因为按照spring配置文件的加载顺序来讲,先加载springmvc配置文件,再加载spring配置文件,我们的事物一般都在srping配置文件中进行配置,如果此时在加载srpingMVC配置文件的时候,把servlce也给注册了,但是此时事物还没加载,也就导致后面的事物无法成功注入到service中。所以把对service的扫描放在spring配置文件中或是其他配置文件中。
3、如使用mysql且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,可以改成InnoDB引擎
4、 @Transactional 注解开启配置,必须放到listener里加载,如果放到DispatcherServlet的配置里,事务也是不起作用的。
5、Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。
6、在业务代码中如果抛出RuntimeException异常,事务回滚;但是抛出Exception,事务不回滚;
7、如果在加有事务的方法内,使用了try…catch…语句块对异常进行了捕获,而catch语句块没有throw new RuntimeExecption异常,事务也不会回滚。
8、在类A里面有方法a 和方法b, 然后方法b上面用 @Transactional加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。原因是在同一个类之中,方法互相调用,切面无效 ,而不仅仅是事务。这里事务之所以无效,是因为spring的事务是通过aop实现的。
事务嵌套怎么处理
嵌套事物成功总结
1、内外都无try Catch的时候,外部异常,全部回滚。
2、内外都无try Catch的时候,内部异常,全部回滚。
3、外部有try Catch时候,内部异常,全部回滚
4、内部有try Catch,外部异常,全部回滚
5、友情提示:外层方法中调取其他接口,或者另外开启线程的操作,一定放到最后!!!(因为调取接口不能回滚,一定要最后来处理)
总结:由于上面的异常被捕获导致,很多事务回滚失败。如果一定要将捕获,请捕获后又抛出 RuntimeException(默认为异常捕获RuntimeException) 。
嵌套事物失败总结
问题:Controller层有一个方法,调用service层的methodA方法,methodA方法调用同类中的methodB方法,前提是methodA方法未加事务,methodB方法有加事务,里面有插入和修改两个方法,问如果methodB方法里面发生异常,事务是否起作用
答案:事务不起作用
分析:
@Transactional是基于动态代理的方式给sql加事务,当容器扫描到这个注解的时候,会给该注解所在的类创建一个代理类,使用cglib代理
它会给带有注解的那个方法的前面开启事务,当我们通过A方法调用B方法的时候,实际上是执行代理类中的A方法,因为代理类中的A方法,直接调用的就是原有类中的方法,并没有加事务,所以B方法里的代码事务是不起作用的
解决办法
给A和B方法分别都加上事务
使用不同的类调用方法
在类上加上事务(非常规操作)
声明式事务,事务的传播特性
在事务控制方面,主要有两个分类:
编程式事务:在代码中直接加入处理事务的逻辑,可能需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法
声明式事务:在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。spring的AOP恰好可以完成此功能:事务管理代码的固定模式作为一种横切关注点,通过AOP方法模块化,进而实现声明式事务。
声明式事务的简单配置
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的bean的形式声明在Spring IOC容器中。下图是spring提供的事务管理器
声明式事务的简单配置
在配置文件中添加事务管理器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd ">
<context:component-scan base-package="com.mashibing"></context:component-scan>
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!--事务控制-->
<!--配置事务管理器的bean-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启基于注解的事务控制模式,依赖tx名称空间-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
事务配置的属性
isolation:设置事务的隔离级别
propagation:事务的传播行为
noRollbackFor:那些异常事务可以不回滚
noRollbackForClassName:填写的参数是全类名
rollbackFor:哪些异常事务需要回滚
rollbackForClassName:填写的参数是全类名
readOnly:设置事务是否为只读事务
timeout:事务超出指定执行时长后自动终止并回滚,单位是秒
事务的传播特性
事务的传播特性指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行?
spring的事务传播行为一共有7种:
总结:
1、事务传播级别是REQUIRED,当checkout()被调用时(假定被另一类中commit()调用),如果checkout()中的代码抛出异常,即便被捕获,commit()中的其他代码都会roll back
2、是REQUIRES_NEW,如果checkout()中的代码抛出异常,并且被捕获,commit()中的其他代码不会roll back;如果commit()中的其他代码抛出异常,而且没有捕获,不会导致checkout()回滚
3、是NESTED,如果checkout()中的代码抛出异常,并且被捕获,commit()中的其他代码不会roll back;如果commit()中的其他代码抛出异常,而且没有捕获,会导致checkout()回滚
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.
单例模式的实现的原理(为什么每次获取到的都是同一个对象),如何保证单例
谈谈对spring的理解及spring的好处
深拷贝和浅拷贝区别是什么,深拷贝和浅拷贝是如何实现的
如何实现注解
#{} 和${} 的区别
spring你对他的了解有什么
1、IOC、AOP底层原理
2、spring的生命周期,如何帮我们做管理的
自动装配的注解@AutoWired,@Resource区别
1、@AutoWired:是spring中提供的注解,@Resource:是jdk中定义的注解,依靠的是java的标准
2、@AutoWired默认是按照类型进行装配,默认情况下要求依赖的对象必须存在,@Resource默认是按照名字进行匹配的,同时可以指定name属性。
3、@AutoWired只适合spring框架,而@Resource扩展性更好
4、@Resource扩展性好,而@AutoWired支持的框架比较单一
切入点表达式通配符
在使用表达式的时候,除了之前的写法之外,还可以使用通配符的方式:
:
1、匹配一个或者多个字符
execution( public int com.mashibing.inter.My*alculator.(int,int))
2、匹配任意一个参数,
execution( public int com.mashibing.inter.MyCalculator.*(int,*))
3、只能匹配一层路径,如果项目路径下有多层目录,那么只能匹配一层路径
4、权限位置不能使用,如果想表示全部权限,那么不写即可
execution( * com.mashibing.inter.MyCalculator.*(int,*))
5、返回值可以使用来代替
…:
1、匹配多个参数,任意类型参数
execution( * com.mashibing.inter.MyCalculator.*(…))
2、匹配任意多层路径
execution( * com.mashibing…MyCalculator.*(…))
在写表达式的时候,可以有N多种写法,但是有一种最偷懒和最精确的方式:
最偷懒的方式:execution( *(…)) 或者 execution(* *.*(…))
最精确的方式:execution( public int com.mashibing.inter.MyCalculator.add(int,int))
除此之外,在表达式中还支持 &&、||、!的方式
&&:两个表达式同时
execution( public int com.mashibing.inter.MyCalculator.(…)) && execution(* *.*(int,int) )
||:任意满足一个表达式即可
execution( public int com.mashibing.inter.MyCalculator.(…)) && execution(* *.*(int,int) )
!:只要不是这个位置都可以进行切入
&&:两个表达式同时
execution( public int com.mashibing.inter.MyCalculator.*(…))
什么情况下回发生内存溢出
Redis持久化的几种方式
RDB、AOF、混合持久化
RDB
广度优先和深度优先的区别
2021-10-21
什么是乐观锁,什么是悲观锁,各种锁在java中具体应用场景有哪些,各自对应如何实现?
spring循环依赖解决方案有哪些
springMVC开发过程中,service层可以调用controller层吗
请求量大,对同张表频繁事务操作造成该表无法读取,这种情况怎么解决
线程池的实现原理是什么
在java中Lock接口跟synchronized块相比,优势是什么
java的反射机制是什么?有哪些应用场景
Redis是多线程还是单线程
思路:在早期版本redis就是单线程的,accept和worker是共用一个线程,高版本的redis为了提高吞吐量,accept优化成了多线程,大师worker任然是单线程
redis和MongoDB的区别是什么
spring框架下,都用了哪些设计模式
spring循环依赖
分布式如何保证数据的一致性
2021-10-22
shiro的鉴权流程
1.Shiro的作用
答: Shiro是一个权限管理框架.提供了身份认证、授权、密码加密和回话管理.
2.Shiro中Subject、SecurityManager、Realm的作用.
Subject:主体,代表了当前“用户”,表示要和应用交互的东西.
SecurityManager: 安全管理器. Shiro中所有的操作都是通过安全管理器操作的.
内部会把Subject发出的请求转发到对应的内部组件中完成具体功能
Realm: 域,shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作(通常我们使用自定义的Realm)
- Shiro认证的流程.(Subject,SecurityManager,Authenticator,Realm做了哪些事情)
答: 1.将需要登录的账号和密码封装成UsernamePasswordToken
2.然后从SecurityUtils中获取Subject对象,然后调用login方法,把token作为参数传入.
3.调用subject.login()之后,会交给SecurityManager进行请求的处理.
4.安全管理器SecurityManager会把请求转发给认证器Authenticator
5.认证器Authenticator会调用Realm中的方法并把token作为参数传入
6.我们需要在Realm中根据用户名从数据库中查询用户信息并封装成认证信息对象SimpleAuthenticationInfo
7.如果返回的SimpleAuthenticationInfo对象为空会抛出异常
8.认证器Authenticator会拿到传入token中的password和返回认证信息对象SimpleAuthenticationInfo中的password进行比对,如果不一致则抛出异常.
4.Shiro鉴权的流程.(Subject,SecurityManager,Realm做了哪些事情)
答:1.会先判断用户是否已经登录,如果没登录返回false,登录继续后续流程
2.然后会通过安全管理器securityManager判断当前登录用户是否有该权限.
3.安全管理器securityManager会调用Realm中的方法获取当前用户拥有的角色/权限集合
4.然后判断用户的角色/权限集合中是否包含当前判断的权限,如果包含返回true,否则返回false.
Redis持久化的几种方式
Redis中使用到的锁
-
redis加锁分类
redis能用的的加锁命令分表是INCR、SETNX、SET -
第一种锁命令INCR
这种加锁的思路是, key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作进行加一。
然后其它用户在执行 INCR 操作进行加一时,如果返回的数大于 1 ,说明这个锁正在被使用当中。
1、 客户端A请求服务器获取key的值为1表示获取了锁
2、 客户端B也去请求服务器获取key的值为2表示获取锁失败
3、 客户端A执行代码完成,删除锁
4、 客户端B在等待一段时间后在去请求的时候获取key的值为1表示获取锁成功
5、 客户端B执行代码完成,删除锁r e d i s − > i n c r ( redis->incr( redis−>incr(key);
r e d i s − > e x p i r e ( redis->expire( redis−>expire(key, $ttl); //设置生成时间为1秒 -
第二种锁SETNX
这种加锁的思路是,如果 key 不存在,将 key 设置为 value
如果 key 已存在,则 SETNX 不做任何动作1、 客户端A请求服务器设置key的值,如果设置成功就表示加锁成功
2、 客户端B也去请求服务器设置key的值,如果返回失败,那么就代表加锁失败
3、 客户端A执行代码完成,删除锁
4、 客户端B在等待一段时间后在去请求设置key的值,设置成功
5、 客户端B执行代码完成,删除锁r e d i s − > s e t N X ( redis->setNX( redis−>setNX(key, $value);
r e d i s − > e x p i r e ( redis->expire( redis−>expire(key, $ttl); -
第三种锁SET
上面两种方法都有一个问题,会发现,都需要设置 key 过期。那么为什么要设置key过期呢?如果请求执行因为某些原因意外退出了,导致创建了锁但是没有删除锁,那么这个锁将一直存在,以至于以后缓存再也得不到更新。于是乎我们需要给锁加一个过期时间以防不测。
但是借助 Expire 来设置就不是原子性操作了。所以还可以通过事务来确保原子性,但是还是有些问题,所以官方就引用了另外一个,使用 SET 命令本身已经从版本 2.6.12 开始包含了设置过期时间的功能。1、 客户端A请求服务器设置key的值,如果设置成功就表示加锁成功
2、 客户端B也去请求服务器设置key的值,如果返回失败,那么就代表加锁失败
3、 客户端A执行代码完成,删除锁
4、 客户端B在等待一段时间后在去请求设置key的值,设置成功
5、 客户端B执行代码完成,删除锁r e d i s − > s e t ( redis->set( redis−>set(key, $value, array(‘nx’, ‘ex’ => $ttl)); //ex表示秒
-
其它问题
虽然上面一步已经满足了我们的需求,但是还是要考虑其它问题?
1、 redis发现锁失败了要怎么办?中断请求还是循环请求?
2、 循环请求的话,如果有一个获取了锁,其它的在去获取锁的时候,是不是容易发生抢锁的可能?
3、 锁提前过期后,客户端A还没执行完,然后客户端B获取到了锁,这时候客户端A执行完了,会不会在删锁的时候把B的锁给删掉? -
解决办法
针对问题1:使用循环请求,循环请求去获取锁
针对问题2:针对第二个问题,在循环请求获取锁的时候,加入睡眠功能,等待几毫秒在执行循环
针对问题3:在加锁的时候存入的key是随机的。这样的话,每次在删除key的时候判断下存入的key里的value和自己存的是否一样
开源 RPC 框架有哪些
一类是跟某种特定语言平台绑定的,另一类是与语言无关即跨语言平台的。
跟语言平台绑定的开源 RPC 框架主要有下面几种。
Dubbo:国内最早开源的 RPC 框架,由阿里巴巴公司开发并于 2011 年末对外开源,仅支持 Java 语言。
Motan:微博内部使用的 RPC 框架,于 2016 年对外开源,仅支持 Java 语言。
Tars:腾讯内部使用的 RPC 框架,于 2017 年对外开源,仅支持 C++ 语言。
Spring Cloud:国外 Pivotal 公司 2014 年对外开源的 RPC 框架,仅支持 Java 语言
而跨语言平台的开源 RPC 框架主要有以下几种。
gRPC:Google 于 2015 年对外开源的跨语言 RPC 框架,支持多种语言。
Thrift:最初是由 Facebook 开发的内部系统跨语言的 RPC 框架,2007 年贡献给了 Apache 基金,成为 Apache 开源项目之一,支持多种语言。
hprose:一个MIT开源许可的新型轻量级跨语言跨平台的面向对象的高性能远程动态通讯中间件。它支持众多语言:nodeJs, C++, .NET, Java, Delphi, Objective-C, ActionScript, JavaScript, ASP, PHP, Python, Ruby, Perl, Golang 。
如果你的业务场景仅仅局限于一种语言的话,可以选择跟语言绑定的 RPC 框架中的一种;
如果涉及多个语言平台之间的相互调用,就应该选择跨语言平台的 RPC 框架
JVM如何进行调优
Mysql如何进行调优
mybatis的事务管理
redis的集群模式
实际操作题
- 100万条数据,用三台redis如何缓存到redis当中,保证数据不重复
- 10个线程对数据库中的某条数据进行操作,如何保证只有一个线程操作完成(比如讲一条数据的某个值从0变为1)
文章评论