面试知识点总结

left jion和inner join的区别 

left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 。

right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录。

inner join(等值连接) 只返回两个表中联结字段相等的行就。

redis中的 zset 实现原理:redis zset 内部的实现原理_李意成的博客-CSDN博客_redis的zset

死锁排查:JVM问题排查——死锁_m0_46311226的博客-CSDN博客_jvm死锁排查

序列化和反序列化:序列化和反序列化的底层实现原理是什么?_徐刘根的博客-CSDN博客_什么是序列化

字节流和字符流:java 字节流与字符流的区别_afa的专栏-CSDN博客_字节流与字符流的区别

枚举类:使用enum定义后在编译后默认继承了java.lang.Enum类,而不是普通的继承Object类。enum实现了Serializable和Comparable两个接口。且采用enum声明后,该类会被编译器加上final声明(同String),故该类是无法继承的。

线程池类型                             

newCachedThreadPool  核心线程数 = 0,最大线程数 = maxValue; 等待时间 = 60毫秒

newFixedThreadPool  核心线程数 = nThreads,最大线程数 = nThreads; 等待时间 = 0毫秒

newSingleThreadExecutor  核心线程数 = 1,最大线程数 = 1; 等待时间 = 0毫秒

newScheduleThreadPool  核心线程数 = nThread,最大线程数 = maxValue; 等待时间 = 0毫秒

mybatis插件原理  一针见血MyBatis插件机制 - MyBatis中文官网

底层通过拦截器拦截四大对象实现:ParameterHandler参数处理对象,ResultSetHandler结果处理对象,StatementHandler数据库处理对象,Executor:MyBatis的执行器,用于执行增删改查操作。

Callable+Future模式 Java线程池(Callable+Future模式) - 细雨笑澄风 - 博客园

callable 接口和 runnable 接口类似,callable接口有返回值,通过 future 获取线程执行结果。使用线程池 executorService.submit()提交任务;runnable接口没有返回值;

switch表达式 https://www.html.cn/qa/other/20952.html 

byte,short,int,char,String,枚举类型。

内存分页 和 物理分页 https://www.cnblogs.com/cocoxu1992/p/10974325.html

内存分页:先从数据库获取所有的数据,缓存到内存,然后再进行分页;例如:Mybatis 使用 RowBounds 对象进行内存分页

物理分页:使用 sql 语句的 limit 分页参数 在数据库层面执行,返回分页结果;例如:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10;hibernate 物理分页

Mybatis-Paginator ,Mybatis-PageHelper 是 mybaites 物理分页插件

跨域问题 https://www.cnblogs.com/cxygg/p/12419502.html

原因:浏览器对 ajax 请求了不同的域名,端口,协议(包括二级域名),浏览器会拒绝接收数据(虽然数据响应了),这是浏览器的一种安全措施:同源策略,限制访问本站点以外的资源。简单理解:服务端和请求端的地址不一样

解决方案:

响应添加头: Access-Control-Allow-origin : * ;允许访问所有的域名;*可以是任何域名;

1,filter过滤器 response 返回添加 header ;

2,nginx 网关添加

3,@CrossOrigin:用在controller 或方法上

spring-mvc 工作原理 工作流程 组件 SpringMvc工作原理学习总结_风棱的博客-CSDN博客_springmvc的工作原理

DispatcherServlet 前端控制器:请求系列的组件,最后对视图渲染,返回前端;

HandlerMappering 处理器映射器:找到对应的 handle ,返回 HandlerExecutionChain 执行链;

Handle 处理器(controller)

HandlerExecutionChain 执行链

HandlerAdapter 处理器适配器:调用 handle ,返回 modelAndView

ViewResolver 视图解析器 : 根据 modelAndView 进行视图解析,返回 view

@Aspect 切面编程:访问方法 前后 抛出异常 等做处理。

filter 过滤器:对 请求和响应 做过滤处理。

handleInterceptor 拦截器:访问方法 前后 抛出异常

RPC 调用过程 https://blog.csdn.net/daaikuaichuan/article/details/88595202

服务与服务之间的方法调用,通过TCP网络传输,因为网络传输都是字节流(二进制)数据,所以客户端需要将调用的方法和方法参数序列化,将序列化的数据发送给服务端,服务端再把接受到的数据反序列化,通过哈希表找到对应的调用方法,通过本地方法调用,返回调用结果,服务端将返回结果序列化,通过网络传输,将返回结果传输给客户端,客户端再将方法返回结果反序列化,得到方法的调用结果。

主要三大:1,调用id映射;2,序列化和反序列号 ;3,网络传输

bio:同步阻塞

bio ( blocking-io )是java传统的io模型,他是同步阻塞io,一个线程触发io操作后,必须等待这个io操作执行完成,期间不能去做其他事情;

nio:同步非阻塞

nio(non-blocking-io)是同步非阻塞io,一个线程触发io操作后它可以立即返回,但是他需要不断地轮询去获取返回结果;

aio:异步非阻塞

aio(Asynchronous-io)是异步非阻塞io,一个线程触发io操作后她可以立马返回去做其他事情,内核系统将io操作执行完成后会通知线程

多路复用io:异步阻塞

io多路复用:可以理解为异步阻塞io,但官方没这么叫,一个线程可以管理多个连接,不用来回切换;

生活场景:我和陈聪在联调接口,调用陈聪的接口出现问题,于是我在群里找到陈聪。

同步:陈聪问题处理好了,但他不说,需要我主动询问;

异步:陈聪问题处理好了,他主动告诉我;

阻塞:我一直在等待陈聪解决问题,啥事都不干;

非阻塞:等待陈聪解决问题的过程中,可以去干其她事情;

hashcode 计算在hash表中的索引位置,返回 int 型的 hash 值;效率高;不可靠,用于快速比较集合多个元素;

equals 比较两个对象的地址,类似 == ;比较的较全面,效率低,可靠;

联系:equal 确认结果,hashcode 不能确认结果;用来确认两个对象是否相同;

重载:同一个类中,方法相同,输入输出参数不同

重写:子类继承父类,重写父类的方法;(实现抽象类)

值传递:基础类型(int,string),在传递的过程中,实际传的是变量副本,是两块内存,比如方法栈线程私有内存,副本变量的内存改变不影响其他内存。

引用传递:实际传递的是同一块内存地址,指向的同一块内存,存储在堆内存线程共享。

接口和抽象类

接口interface,有常量,多实现 implements;对行为抽象;不能直接实例化

抽象类 abstract ,有常量,成员属性,静态方法,可以有自己的实现方法,不能直接 new ,可以通过子类继续实例化;抽象类有构造函数;单继承;对类抽象;

设计模式

策略模式:方法或者算法在运行时确定;减少 if-else 。

单例模式:上下文只有一个实例;饿汉模式,懒汉模式。

代理模式:动态,静态;aop切面编程底层逻辑就是动态代理;aop场景(事务,日志,安全)。

工厂模式:隐藏对外创建逻辑。

监听:对外提供接口供外方调用。

BeanFactory:就是IOC容器,所有的Bean都是有它来管理的;另ApplicationContext继承BeanFactory。

FactoryBean:就是工厂类接口,可用来实例化bean; eg: ThreadFactory

BASE理论 https://zhuanlan.zhihu.com/p/147817106

Basically Available(基本可用的):是指在分布式集群节点中,若某个节点宕机,或者数据在节点间复制的过程中,只有部分数据不可用,但不影响整个系统整体的可用性。

Soft state(软状态):这个状态只是一个中间状态,允许数据在节点集群间操作过程中存在存在一个时延,这个中间状态最终会转化为最终状态。

Eventual consistency(最终一致性):指数据在分布式集群节点间操作过程中存在时延,与ACID相反,最终一致性不是强一致性,在经过一定时间后,分布式集群节点间的数据拷贝能达到最终一致的状态。

分布式系统CAP定理:CAP理论的理解 - John_nok - 博客园

分区容错性Partition tolerance:指的分布式系统中的某个节点或者网络分区出现了故障的时候,整个系统仍然能对外提供满足一致性和可用性的服务。也就是说部分故障不影响整体使用。 

可用性Availability: 一直可以正常的做读写操作。简单而言就是客户端一直可以正常访问并得到系统的正常响应。用户角度来看就是不会出现系统操作失败或者访问超时等问题。 (讲的就是任何情况下都可以读,不管有没有写完)

一致性Consistency:在分布式系统完成某写操作后任何读操作,都应该获取到该写操作写入的那个最新的值。相当于要求分布式系统中的各节点时时刻刻保持数据的一致性。(讲的就是写,要等所有节点写完才可以读)

综合看来,写完再读是保证了一致性,但牺牲了可用性。未写完就读是保证了可用性,但牺牲了一致性。

https://mp.weixin.qq.com/s/oH5ytdIcDZXwugNYPXXsUw

zookerper 和 eureka区别 https://www.cnblogs.com/zz-ksw/p/12786067.html

zookerper:一致性和分区容错性,zookerper集群节点是主从关系,主从切换的时候可能导致服务不可用。

eureka:可用性和分区容错性,eureka集群节点是平等关系,不能保证数据的一致性。

in 和 exists 比较:

select * from A where id in(select id from B),A表大,B表小。先查询B表,返回结果集,再查询A表。

select a.* from A a where exists(select 1 from B b where a.id=b.id),A表小,B表大。A表记录一条条过滤where条件,B表返回的是boolea值。

Maven依赖范围:

maven:repositories配置为远程仓库,localRepository为本地仓库。mirror为远程仓库的代理

maven setting 配置仓库,pom.xml中repository不起作用 - 猿起缘灭 - 博客园

系统架构:高性能:使用缓存或消息中间件。高可用性:服务集群。高扩展性:微服务。

并行和并发:

spring boot:

提供自动配置,减少xml配置,集成了各种依赖,且内置了tomcat。

spring-boot-autoconfig:@springBootApplication 会加载 spring.factories 文件,该文件里有大量的默认配置:jdbc,redis,数据源,监听器,aop ,缓存,jpa

参数配置:

application.properties:应用级别的

bootstrap.yml:系统级别的,优先于applicaiton加载。主要加载外部的一些资源:配置中心。

分布式事务的解决方案:参与者,协调者 https://www.cnblogs.com/qdhxhz/p/11167025.html

2PC(两阶段提交 prepar -> commit/rollback)

准备阶段:获取锁,执行DML(增删改查)语句。执行完DML语句,数据只是在内存中,还未 commit 到数据库文件中。(事务预处理)

提交执行阶段:commit/rollback 提交事务或回滚事务。

性能问题:无论是在第一阶段的过程中,还是在第二阶段,所有的参与者资源和协调者资源都是被锁住的,只有当所有节点准备完毕,事务 协调者 才会通知进行全局提交,参与者 进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大。

单节点故障:由于协调者的重要性,一旦 协调者 发生故障。参与者 会一直阻塞下去。尤其在第二阶段,协调者 发生故障,那么所有的 参与者 还都处于锁定事务资源的状态中,而无法继续完成事务操作。(虽然协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题),陷入阻塞状态,造成数据不一致问题。

3PC(三阶段提交 canCommit -> preCommit -> doCommit)https://www.cnblogs.com/qdhxhz/p/11167025.html

canCommit:尝试获取数据库锁。

preCommit:事务预提交(预处理)数据在内存中;

doCommit:commit/rollback提交事务或回滚事务。

优化点:同时在协调者和参与者中都引入超时机制(2PC协调者才有超时机制);3PC把2PC的准备阶段再次一分为二;但是3PC依然没有完全解决数据不一致的问题。协调者超时(未收到所有参与者消息),回滚所有业务,参与者超时(未收到协调者消息),提交事务。暂无具体实现

TCC 两阶段事务补偿(Try、Confirm、Cancel) 开源Hmily https://www.cnblogs.com/jajian/p/10014145.html

try:预处理。锁定需要的资源,比如订单服务,下单过程中,订单状态为支付中;库存冻结,积分预增加

cofirm:确认操作。订单状态为支付成功;库存扣减;积分增加

cancel:回滚操作。订单待支付;库存解冻;积分预增加为0;

每一个操作都需要分别定义TCC方法,适用于公司内部对一致性、实时性要求较高的业务场景

本地消息表 基于本地消息表的分布式事务解决方案总结 - 知乎

下单成功后,下单消息记录在本地表中,下单消息再投入MQ中的A消息队列中,库存服务监听到A队列中的消息进行消费,消费完以后,再向B队列中投入成功消费消息,订单服务监听到B队列中有消息进行消费,消费完以后删除本地表中的消息记录。(精髓:两个队列,一张本地表用来存储消息)

本地消息表目的在于防止消息传递过程中会丢失。定时捞取待消费的消息投入到消息队列中,直到消费成功。需要消息幂等操作。

事务消息 还不知道事务消息吗?这篇文章带你全面扫盲! - 楼下小黑哥 - 博客园

消息中间件RocketMQ(阿里开源)具有事务功能,中间件事务最终确定消费者是否可以消费该消息。消息中间件有事务反查机制,通过反查,获取事务最终状态。适用内部系统

下图中 1 -> 2 -> 3 如果第 3 步出错,会有事务反查机制,消息中间件最终得到本地事务状态

最大努力通知:本地消息表和事务消息。适用外部系统

分布式事务总结:

可以看出 2PC 和 3PC 是一种强一致性事务,不过还是有数据不一致,阻塞等风险,而且只能用在数据库层面。

而 TCC 是一种补偿性事务思想,适用的范围更广,在业务层面实现,因此对业务的侵入性较大,每一个操作都需要实现对应的三个方法。

本地消息表、事务消息和最大努力通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务。

zookeeper分布式锁 Zookeeper 分布式锁 - 图解 - 秒懂_架构师尼恩-CSDN博客_zookeeper分布式锁

ZAB协议详解_xiaocai9999的专栏-CSDN博客_zab协议原理

使用curator-client jar包,对原zookeeper的API进行封装的。性能不高,高并发建议使用redis锁。但是高可靠。核心思想:为所有请求锁的线程(客户端)创建一个临时节点,并进行排队,判断是否排第一个,如果是排第一个则获取锁成功,如果不是排第一位则获取失败,然后创建一个监听排在自己前面的节点,等待前一个节点释放锁再去获取锁,此时该线程进入阻塞:CountDownLatch 执行await阻塞,此时监听方法里有countdown方法,只要监听到事件立马唤醒被阻塞的线程。唤醒后再去判断是否排第一个。

公平可重入锁;curator 中 InterProcessMutex;比如:银行排队的例子

zk中创建和删除节点只能通过Leader服务器来执行,然后Leader服务器需要将数据同步到所有的Follower机器上。

因为zk有数据一致性要求(zab协议,事务:Proposal(主) -> ack(从) -> commit(主)),所以不会出现 redis 集群问题。原子广播消息,主从之间有消息队列,半数返回ack,全局提交。

Proposal :提案

redis分布式锁 https://blog.csdn.net/shuangyueliao/article/details/89344256

底层使用的是lua脚本,保证业务逻辑的原子性(多个业务逻辑使用lua脚本打包发送给redis原子性操作)。

加锁机制:首先查询加锁的key,如果key不存在,则设置一个hash表的数据结构,value值为客户端的ID,且有调用锁定次数1,可以进行重入锁定,并且设置key默认生存时间30秒。

互斥机制:当客户端2加锁当前的key,发现key已经存在,则获取value值,发现id不是自己客户端的ID,这时获取 key 的生存时间,客户端2会自旋尝试获取锁,这是互斥机制

自动延期机制:第一次加锁成功的话,后台会启用一个新的线程,每10S检测一次,如果客户端1还持有锁,会不断的延长锁的生存时间,这是watch dog的自动延期机制。

释放锁机制:删除当前的加锁的key,如果是重入锁,会先减1操作,直到为0删除当前加锁的key。

使用Redisson开源框架,基于Redis实现分布式锁的加锁与释放锁。

(待确认)Redis分布式锁的缺点:如果redis是集群的话,可能发生主从节点的切换,造成多个客户端成功加锁,导致各种脏数据的产生。redis 不能保证数据一致性。

Redis集群 Redis集群详解_变成习惯-CSDN博客_redis集群

1,主从模式,不具备高可用性

主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库,从数据库一般都是只读的,并且接收主数据库同步过来的数据,一个 master 可以拥有多个 slave,但是一个 slave 只能对应一个 master ,slave挂了不影响其他slave的读和 master 的读和写,重新启动后会将数据从master同步过来,master挂了以后,不影响slave的读,但redis不再提供写服务,master 重启后redis将重新对外提供写服务,master挂了以后,不会在slave节点中重新选一个master。

工作机制:

当slave启动后,主动向master发送SYNC命令。master接收到SYNC命令后在后台保存快照(RDB持久化)和保存快照这段时间的命令,然后将保存的快照文件和命令发送给slave。slave接收到快照文件和命令后加载快照文件和执行缓存命令。

复制初始化后,master每次接收到的写命令都会同步发送给slave,保证主从数据一致性。

2,Sentinel 哨兵模式,客户端连接的是 setinel,而不是 redis

解决主从模式不可切换问题;当master挂掉以后,Redis不能提供写操作,sentinel应运而生。

sentinel:哨兵的意思,监控redis集群的运行状况。

sentinel 模式是建立在主从模式的基础上,当master挂了以后,sentinel会在slave中选择一个做为master。

sentinel 也会启动多个,形成一个sentinel集群,他们之间会相互进行监督。

工作机制:

3,Cluster集群模式

sentinel模式基本可以满足一般生产的需求,具备高可用性。但是当数据量过大到一台服务器存放不下的情况时,主从模式或sentinel模式就不能满足需求了,这个时候需要对存储的数据进行分片,将数据存储到多个Redis实例中。cluster模式的出现就是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。cluster可以说是sentinel和主从模式的结合体,通过cluster可以实现主从和master重选功能,所以如果配置两个副本三个分片的话,就需要六个Redis实例。因为Redis的数据是根据一定规则分配到cluster的不同机器的,当数据量过大时,可以新增机器进行扩容。(可以使用数据淘汰机制解决单机数据容量问题)

缓存 https://blog.csdn.net/kongtiao5/article/details/82771694

缓存穿透:指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。1,对该id进行缓存值为空,有效期30秒。缓存数据库都没有数据

缓存击穿:指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。1,设置热点数据永远不过期。2,使用互斥锁【使用tryLock方法获取锁,如果没有获取到重新再执行一遍该方法,直到获取为止,互斥锁不会导致大量线程阻塞等待情况】【过期的key访问的时候会被删除】缓存数据过期,数据库有数据

缓存雪崩:指缓存中大量的数据都到过期时间了,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。1,缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。缓存所有数据同一时间过期

++i 先加 1 再参加业务;

i++ 先参加业务再加 1。

spring循环依赖 A-->B-->A https://blog.csdn.net/u010853261/article/details/77940767

1,实例化对象A,调用构造函数,放入缓存中,为原始对象,对象的属性字段为null

2,填充对象A的字段B属性,发现B对象还未实例化,则进行B对象实例化,实例化完B对象以后进行字段的填充,发现缓存中存在A对象进行填充。B对象创建完放入缓存中。

3,继续初始化A对象。

Bean的五中作用域:singleton(单例)、prototype(原型)、request(请求)、session(会话)、global session(全局会话)

bean生命周期:bean的创建要经过一系列的操作:从bean的实例化到销毁,中间要调用该bean实现的多个接口方法,以及与该bean相关的接口BeanPostProcessor 。

Spring Bean的生命周期

1,首先执行bean的构造方法,
2,BeanPostProcessor的postProcessBeforeInitialization方法
3,InitializingBean的afterPropertiesSet方法
4,@Bean注解的initMethod方法

5,BeanPostProcessor的postProcessAfterInitialization方法
6,DisposableBean的destroy方法
7,@Bean注解的destroyMethod方法

深入理解spring生命周期与BeanPostProcessor的实现原理_varyall的专栏-CSDN博客_beanpostprocessor的设计模式

生命周期顺序:@Constructor > @Autowired > @PostConstruct

aop切面编程 https://blog.csdn.net/u012326462/article/details/81293186

为了降低代码耦合性和重复性,使用了切面编程,例如安全,事务,日志等,底层实现原理就是动态代理。通过实现InvocationHandler接口,在该接口里调用被实现类的方法前后加上相关的一些操作。再使用Proxy.newProxyInstance()代理类。

饿汉模式:定义一个类变量,直接new一个对象,在类加载的时候就进行初始化,且提供一个对外访问的静态方法。类变量 private static class = new class();

懒汉模式:定义一个类变量,然后定义一个静态方法初始化该静态变量,如果为空进行初始化,否则直接返回。类变量 private static class = null;

区别:饿汉模式主要在类加载的时候进行初始化,而懒汉模式在使用时才初始化。两者都是单列模式

重定向:request.getRequestDispatcher("/demo.jsp").forward(request,response);

发生在客户端发生,速度慢,两次请求,在地址栏有变化,可能在不同的服务器下完成。

转发:response.sendRedirect(request.getContextPath + "/demo.jsp")

发生在服务器端,速度快,地址栏无变化。

锁的升级:首先是无锁状态,如果一个资源只有一个线程进行操作(访问代码块获取锁对象),则在实例对象头里记录当前线程ID,这时候升级为偏向锁,当前线程一直拥有该锁,且无需加锁和释放锁。如果有少量线程访问当前资源,则锁升级为轻量级锁,当前线程会一直自旋,自旋相对阻塞代价小,阻塞涉及到了用户状态和内核状态的转换,耗资源。自旋到一定次数还未获取到锁后,则升级为重量级锁。无锁状态-->偏向锁状态-->轻量级锁状态-->重量级锁状态。锁不可以降级,但是偏向锁可以被重置为无锁状态。

消息中间件投递模式 https://developer.51cto.com/art/201911/606122.htm

拉取消息:拉取频率和时间不好控制

推送消息:消费能力不好控制。流量控制

消息队列

mq作用:解耦异步,流量削峰

消息重复消费问题:保持幂等性,消费成功的消息记录在日志中,重复消费直接返回。消息需要唯一 ID。

事务底层实现:通过 InnoDB 日志和锁来保证。

隔离性:数据库锁的机制,mysql InnoDb引擎中update,delete,insert语句自动加排他锁(共享锁),行锁对事务要求高。表锁对事务要求低,以查询为主。如 MyISAM,MEMORY

持久性:Redo Log,将新数据备份到redo log日志中,事务提交后,只需持久化redo log。

原子性、一致性: Undo Log ,修改数据之前,先备份数据,备份到undo log日志中。出现回滚的话,使用该备份恢复数据。

TCP三次握手:首先两次握手是必须的,第一次是客户端向服务端发送连接请求,服务收到请求后是可以进行连接的,但是客户端是不知道的,这时服务端需要反馈客户端第一次的连接请求,告诉客户端是可以连接的,这时第二次握手。第三次握手是为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误:client发送的第一次连接请求报文因为网络原因发生延迟,server认为是新的连接请求,同意建立连接,但cliend不认为是建立新的连接,就不会予以理睬。如果不进行三次握手,server一直在等待client发送数据,造成资源浪费。

redis和MemCache区别:持久化,集群上,数据类型 redis的5种数据类型_living_ren的博客-CSDN博客_redis的五种数据类型

set 不重复

sort set 有序

list 可重复

finally:try和catch语句中有return返回值X,X先进行保存,等待finally执行完后返回X值。

如果finally语句中有return 返回值Y,则方法最终返回Y值。引用类型则会修改里面的属性值。

mysql引擎

innoDB:支持索引,支持事物,行级别锁,以及外键。存储限制64TB,默认引擎

MYLSALM:支持索引,不支持事物,读写效率较高。存储限制256TB,数据仓库使用该引擎。

Memory:支持索引,不支持事物,存储在内存中,存储临时数据。内存大小限制

Archive:不支持事物,索引。存储空间没有限制

varchar和char区别

char:定长字段,长度固定,可用空格填充,最大存储255个字符

varchar:不定长字段,长度可按实际字符长度存储。最大存储65532个字符

SQL执行流程 一分钟学会数据库sql查询原理及查询执行sql流程

连接器 -》 分析器 -》 优化器 -》 执行器

一:客户端将sql发送给数据库服务器。(连接器)

二:服务器解析sql语句(分析器)

1,查询缓存,如果在缓存中查询到该执行计划,执行该计划返回查询数据。

2,语法校验(符合解析树规则) -> 语义校验(例:表列是否存在)-> 获取对象锁 (表加锁) -> 校验用户权限。

3,优化sql(优化器)

关联查询先查哪张表,多个索引使用哪个索引,确定执行路径,放在缓存里。

4,执行sql(执行器)

explain执行计划 explain执行计划详解_eagle89的专栏-CSDN博客_explain执行计划

id : select 序列号,执行顺序:大 -> 小

select_type

simple : 简单查询

primary :复杂查询,包括子查询,union;id > 2

devived : 起源,衍生查询;from (子查询)

dependent subquery : 依赖查询 in (子查询)

subquery:子查询 = (子查询)

union : 联合查询

table

N 表示 id 序列号

1,2 表示 id 序列号

type 访问类型

null :不访问表或索引

system :只有一行数据

const :通过索引查询

eq_ref :唯一索引扫描

ref :普通索引扫描,非唯一索引扫描

ref_or_null :类似 ref ,可搜索值为 null 的行

index_merge :索引联合优化,比如 where id = 11011 or tenant_id = 8888;

range : 范围扫描

index : 扫描索引表

all :扫描全表

possible_keys : 可能使用的索引

key :实际使用的索引

key_len:索引长度;多列索引根据这个字段判断是否使用所有的索引列

ref :索引使用的列

rows : 需要扫描的行数

数据库调优/SQL调优 https://www.zhihu.com/question/36431635

1,读数据

使用索引,查询表的字段尽量被覆盖,不用再回表;索引字段上注意表达式,函数使用,会使索引失效。

查询需要的字段,尽量不使用 * 查询所有的数据。

left join代替子查询,因为join查询不会在内存中创建临时表。

limit 只返回必要的行数据

缓存重复查询的数据

2,写数据

字段尽量使用默认值,null占用的空间更大;数值类型代替文本类型,数值类型处理速度更快。

只更新需要更新的字段。

mysql 深度分页

100万条数据,获取最后10条数据,分页速度更慢;思路:现查询 ID ,再查询记录

SELECT * FROM table_name a,(SELECT id FROM table_name limit 7000,10) b WHERE a.id = b.id

构造器

1,静态构造器:加载class文件时调用,执行所有静态变量和静态语句块。修饰符:static

2,构造函数:new操作符,Constructor.newInstance(),Object.clone(),ObjectInputStream.getObject()。

java对象内存布局 https://zhuanlan.zhihu.com/p/50984945

对象头:gc年龄,对象的hashcode,锁的信息,线程id,类型指针:该类的类型

对象实际数据

对象填充 : 满足 对象 占用的字节数 为 8 的倍数;内存管理要求对象的大小必须是8字节的整数倍

加载区别 https://blog.csdn.net/fn_2015/article/details/79422367

Class.forName():加载且被初始化;执行class中的静态代码块

ClassLoader.laodClass():加载且不会被初始化;不会执行class中的静态代码块

threadLocal:set和get方法,设置线程变量副本。获取当前线程 里面的 ThreadLocalMap,使用threadLocal作为ThreadLocalMap的key,获取entry里面的value值。

threadLocal内存泄漏 ThreadLocal 内存泄漏 代码演示 实例演示_刘本龙的专栏-CSDN博客_threadlocal内存泄漏实例

 value 值无法被访问:因为 key = null ,且无法被 gc : 因为 value = 对象; threadLocal 作为 value 的 key ,且是弱引用。

问题在于 threadLocal 实例是否能被回收,被回收的话,就会造成内存泄漏。回收 threadLocal 的条件:没有强引用。

remove() :key = null,value = null ; set() , get() 最后的条件也是如此 key = null,value = null ;

因为threadLocal作为弱引用指向threadLocal实例,弱引用指向的threadLocal实例可被gc回收,导致threadLocal指向为空的内存,但是获取value值需要使用threadLocal,此时threadLocal为空,value值占用内存一直没有被释放,因为value一直在线程里的threadLocalMap里面。内存泄漏:无法使用且无法释放的内存。

核心:threadLocal只是作为线程操作的读写入口和key,使用完后被gc后,写入的值无法被找到和回收。

https://blog.csdn.net/liubenlong007/article/details/107046897

JVM引用类型 https://www.cnblogs.com/liyutian/p/9690974.html

强引用 Strong Reference: GC 不回收。

软引用 Soft Reference:非必需但仍有用的对象,内存不足的时候回收;被 SoftReference 所关联的对象会被回收(若对象仍被强引用指向,仍不能回收);

弱引用 Weak Reference:无论内存是否足够都会被 GC 回收

虚引用 Phantom Reference:不能通过该引用获得对象, gc 时返回一个通知。

HashMap 掘金

数据结构:数组+链表 + 红黑树;

存取原理:检查数组是否为空或长度为0,则进行扩容(复制数组)。put需 算出位置 index=hashCode(key) & (length -1),使用equesl()比较两个key,相同则替换值,不同则插入(1.7头插入:扩容容易造成环形链表,1.8尾插入避免环形链表,但在多线程下是不安全的)。get 算出位置index=hashCode(key) & (length -1),使用equals()比较两个key,相同则返回值,链表迭代比较。

Java7和Java8的区别

1.7 节点是entry;头插入;

1.8 节点是node,尾插入法,引入红黑树,大于 8 会自动转化为红黑树,小于 6 重新变为链表。

HashMap的扩容方式

创建一个新的Entry空数组,长度是原数组的2倍;

ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组;

阀值:threshold = capacity * load factor,capacity = 16 ,load factor = 0.75

ConcurrentHashMap 并发容器

1.8是数组+链表+红黑树结构,使用Synchronized关键字。

jdk1.7 ConcurrentHashMap是Segment数组和HashEntry数组组成,segment的分段锁机制,继承reentrantLock锁控制并发。

红黑树平衡二叉树,左右高度差不多。特性:节点是黑或者红,根节点和叶子节点是黑色,节点 = 红色,他的子节点 = 黑色

区别:红黑树大致平衡,不是完全平衡,可三次旋转解决平衡问题,平衡二叉树绝对平衡,多次旋转

线程中断 https://www.cnblogs.com/onlywujun/p/3565082.html

void thread.interrupt() :其作用是中断此线程(此线程不一定是当前线程,而是指调用该方法的Thread实例所代表的线程),但实际上只是给线程设置一个中断标志,线程仍会继续运行。

boolean interrupted() :作用是测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,第二次再调用时中断状态已经被清除,将返回一个false。

boolean isInterrupted():作用是只测试此线程是否被中断 ,不清除中断状态

catch执行完程序继续往下执行。

Object.wait, Thread.sleep方法,会不断的轮询监听 interrupted 标志位,发现其设置为true后,会停止阻塞并抛出 InterruptedException异常。

抛中断异常方法:Thread.sleep(),Thread.join(),Object.wait(),Condition.await()。

lock 和 synchronized 区别 https://www.cnblogs.com/simpleDi/p/11517552.html

synchronized : 静态方法/代码块 锁定的是 class 类锁,非静态方法/代码块 锁定的是 this 对象锁,JVM 层面实现,异常时可释放锁,wait 和 执行完 自动释放锁,等待过程中无法响应中断,非公平锁;

lock : jdk 层面的锁,要手动释放锁,否则容易发生死锁,可响应中断,公平和非公平锁;

wait-notify java并发编程基础之等待通知机制wait/notify原理分析_tom有cat-CSDN博客

配合 synchronized(非公平锁,可重入,不可中断)使用,始于一个线程,结束于另一个线程。wait线程调用wait方法,wait线程阻塞,进入等待队列中。notify线程调用notify,会唤醒wait线程,但是wait线程不会立马执行,因为notify线程还没有释放锁,wairt线程会进入同步队列中。等待notify线程执行结束后释放锁,wait线程获取锁后再执行。

ReentrantLock ReentrantLock原理_路漫漫,水迢迢-CSDN博客_reentrantlock

CAS原子操作+AQS同步器来实现。通过CAS尝试获取锁,如果此时已经有线程占据了锁,那就加入AQS队列(先进先出)并且被挂起。当锁被释放之后,唤醒下一个线程,然后CAS再次尝试获取锁。ReentrantLock和synchronized都是可重入锁。state=0表示锁未被占用,反之则被占用。 独占锁

AbstractQueuedSynchronizer.acquire():获取锁 -> 入队 -> 获取锁 -> 挂起

CountDownLatch 计数器 https://www.jianshu.com/p/e233bb37d2e6

1,使一个线程等待其他线程各自执行完毕后再执行;

2,通过一个计数器来实现的,计数器的初始值是线程的数量;每当一个线程执行完毕后,计数器的值就-1,当计数器的值为 0 时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。countDown() 和 await()

shouldParkAfterFailedAcquire():找到前驱状态为 -1 下的节点排队等候;

【车做满人司机才可以发动车子走】

Semaphore 信号量 https://zhuanlan.zhihu.com/p/98593407

信号量,资源使用限制;acquire() -1 和 release() +1 ;共享锁

tryReleaseShared():尝试释放令牌

【停车场例子】

CyclicBarrier 循环栅栏 Java并发编程之CyclicBarrier详解_Stay Hungry, Stay Foolish-CSDN博客_cyclicbarrier深入理解CyclicBarrier原理_晨初听雨的博客-CSDN博客_cyclicbarrier

循环栅栏,可循环利用的屏障。调用 await 方法阻塞(--count 减1),最后一个线程调用 await 方法(count == 0)后唤醒所有的线程,进入下一代;

【旅行团等所有人到齐了才开始出发】

CyclicBarrier:内部基于 Condition 实现,可重复使用;使用 --count 计数。

CountDownLatch:内部使用AQS实现,使用 --state 计数。

区别:AQS是同步队列,Condition里有多个等待队列,Condition 唤醒时将等待队列里头一个节点转移至同步队列尾部且继续阻塞;Condition 阻塞时将当前线程封装一个节点加入等待队列尾部同时释放锁唤醒同步队列的头节点。

Condition https://blog.csdn.net/a1439775520/article/details/98471610

Condition.await()阻塞且释放锁。condition.signal()唤醒被阻塞的线程。在 lock 里面使用;Condition:等待队列 FIFO

类似object.wait 和 object.notify 需 在 synchronize 里面使用。

CAS:乐观锁compare And swap先比较再替换;存在ABA问题和长时间循环cpu开销大问题;版本号;当前内存存储的值和期望值一样,就更新当前内存的值。

AQS:AbstractQueuedSynchronizer队列同步器。两个队列:同步队列和条件等待队列(可以多个)。同时队列保持着一个状态值。

dubbo协议:dubbo://,rmi://,hessian://,http://,webservice://,thrift://,memcached://,redis://,rest://,grpc://。

Java原生序列化:需要实现Serializable接口。

Spring Cloud Netflix:分布式服务治理框架

Eureka:服务治理组件为什么Eureka Client获取服务实例这么慢 - 炎泽 - 博客园,包含eureka service和eureka client,service是注册中心,client是服务提供者和服务调用者。Eureka原理及工作流程_Alisa的博客-CSDN博客_eureka原理及执行流程

Eureka 注册延迟_简简单单Onlinezuozuo-CSDN博客_eureka 延迟注册

程序员笔记|详解Eureka 缓存机制 - 宜信技术 - 博客园

心跳续约:由client告诉service(定时心跳);

Hystrix https://www.cnblogs.com/qdhxhz/p/9581440.html

容错管理组件,超时或异常情况;

@EnableCircuitBreaker 开启熔断器

@HystrixCommand(fallbackMethod = "loadFallback") 断路器注解

@FeignClient(name = "USERSERVICE", fallback = UserServiceFallback.class) 断路器注解

服务熔断 -> 服务降级:配置 fallback 回调,返回一个缺省值;

Hystrix配置

circuitBreaker.requestVolumeThreshold //滑动窗口的大小,默认为20

circuitBreaker.sleepWindowInMilliseconds //过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟

circuitBreaker.errorThresholdPercentage //错误率,默认50%

每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。

Ribbon:负载均衡组件,默认轮询算法。

负载均衡算法:为 随机,轮询,哈希,最小压力,权重。

Feign:声明式服务调用组件,基于RibbonHystrix的,是一款客户端HTTP调用组件

Feign远程调用原理 - 简书

Zuul https://www.cnblogs.com/jing99/p/11696192.html

微服务网关组件,统一入口,负载均衡,鉴权;继承 ZuulFilter

pre filters :前置过滤器;请求被路由之前执行;请求认证,负载均衡,日志

routing filters :路由后过滤器;路由之后,调用远程服务之前执行。

post filters :后置过滤器;调用远程服务之后执行;统计服务性能,响应做处理等;

error filters :异常过滤器;其他过滤器器发生异常或远程服务调用超时 触发执行;异常处理

分布式任务调度 参考XXL-JOB快速入门 - 简书

启动调度中心,执行器(主动或被动)添加到调度中心里,调度中心进行任务调用,任务执行完回调给调度中心。

单点登录 单点登录(SSO)看这一篇就够了-阿里云开发者社区

客户端请求到认证中心获取token,再去请求服务端,服务端拿着token到认证中心验证,通过则放行。客户端首次访问服务端,引导客户端到认证中心登录认证。

索引失效 索引失效的7种情况 - liehen2046 - 博客园

未建索引,or,不符合最左匹配原则,like %开头,表达式或函数,类型转换(varchar == number),varchar字段无单引号(phone = 13515651145),not in ,not exist ,is null;

当创建(a,b,c)联合索引时,相当于创建了(a)单列索引,(a,b)联合索引以及(a,b,c)联合索引 ,这是就是最左匹配原则

bitMap 位图数据结构

有10亿整数(内存需4G)需查找其中一个整数是否存在,则使用位图数据结构。通过整数的商和余计算出该整数的位置(数组的每个元素存储多个二进制0和1),再通过位运算把0调整为1。1表示存在0不存在。

dubbo

启动注册中心后,客户端和服务端向注册中心注册服务和订阅服务,客户端根据负载均衡调用服务;注册中心会推送更新后的服务到客户端。

threadpoolExceutor ThreadPoolExcutor 原理探究 - huansky - 博客园

corePoolSize 核心线程数量;maximumPoolSize 最大的线程数量

keepAliveTime 等待任务时间(allowCoreThreadTimeOut  = true 核心线程超时回收)

unit 时间单位

workQueue 任务队列

处理任务流出:corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝

拒绝策略

AbortPolicy:抛出异常。默认

DiscardPolicy:空方法。不执行

CallerRunsPolicy:直接调用run,不启用新的线程执行。

DiscardOldestPolicy:丢弃队列旧的任务,再执行

discard:丢弃;policy:策略;

线程池状态

running:接收任务 + 处理队列任务

shutdown:不接收任务 + 处理队列任务

stop:不接收任务 + 不处理队列任务

tidying :任务已执行完。所有线程已销毁。

terminated :终止状态

mybatis #{}和${}区别

${}只是简单的占位符,字符的替换;不能防止sql注入。

#{}是预编译的过程,可以防止sql注入。eg: where id in (#{ids}) 是查询不到的结果,而where id in (${ids})是有结果的。

mybatis 缓存 https://www.cnblogs.com/wuzhenzhao/p/11103043.html

一级缓存(本地缓存)是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在sqlSession对象中有一个数据结构(BaseExecutor  -> PerpetualCache -> HashMap)用于缓存数据;SqlSession 对象之间的数据相互隔离

SqlSession会话只要有更新操作(update,delete,insert),就会清空 PerpetualCache 对象里的数据。

二级缓存

二级缓存是namespace 级别的缓存,多个SqlSession可以共享同一个二级缓存CachingExecutor;

更新操作会清空缓存(update,delete,insert);使用LRU清除缓存;

查询顺序:二级缓存   -> 一级缓存 -> 数据库。

B树和B+树

二叉树会产生退化现象(链表),提出平衡二叉树

再提出怎么样让每一层放的节点多一些来减少遍历高度,引申出m叉树

m叉搜索树同样会有退化现象,引出m叉平衡树,即B树

这个时候每个节点既放了key又放了value.怎样使每个节点放尽可能多的key值,以减少遍历高度也就是访问磁盘的次数

可以将每个节点只放key值,将value值放在叶子节点,在叶子节点的value值增加指向相邻节点的指针,这就是优化后的B+树

mysql索引

主键索引:一张表中最多有一个主键索引,而且该字段值不能为NULL,不能重复。

唯一索引:一张表中可以有多个唯一索引,而且字段可以为NULL,但是不能有重复值。

普通索引:一张表中可以有多个普通索引,而且值可以为NULL,并且值可以重复。

全文索引:全文索引就是将该字段的信息加以拆分和组合,形成一份清单,和sphinx全文索引一样。

复合索引:一个索引如果建立在多个字段上,那该索引就称为复合索引。

说明:其实这些索引所用的数据结构都是一样的B+Tree结构。只是他们对字段信息的约束条件不一样。

聚簇索引

主键索引;索引和数据保存在同一个B+树中。按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页,叶子节点存放着一整行的数据。

非聚簇索引

复合索引,前缀索引,唯一索引;索引文件和数据文件是分离的;索引文件仅保存数据记录的地址

覆盖索引:从索引中就能查询到的字段数据,不需要回表查询。

应该是多条件查询建立联合索引效率更高,单索引效率低一些

spring事务传播行为 看完就明白_spring事务的7种传播行为_gnixlee的博客-CSDN博客_事务传播行为

PROPAGATION_REQUIRED 默认的传播行为

required:必要的

require_new:新建事务,挂起当前事务;相互不干扰

supports:支持,可有可无

supports_not:不支持事物;挂起当前事务;

never:不要事务,若有抛异常;

mandatory:强制性的,若无抛异常

newsted:嵌套事务需要外层提交事务,嵌套事务异常不影响外层事务;而外层事务影响嵌套事务;

事务的隔离级别

脏读:一个事务读到另一个事务未提交的数据

不可重复读:一个事务读到另一个事务已经提交的 update 数据,导致一个事务中多次查询结果不一致

幻读:一个事务读到另一个事务已经提交的 insert 数据,导致一个事务中多次查询结果不一致

mysql:REPEATABLE-READ 默认的可重复读

oracle : READ COMMITTED 默认的读已提交

redis 持久化机制

rdb内存快照:dump.rdb的二进制文件。启动优先级低,体积小,恢复快。容易丢失数据

aof:操作命令记录进appendonly.aof文件。启动优先级高,体积大,恢复慢。

rdb过期key不入库,aof过期数据可能入库。

过期删除策略 -> 针对过期数据

定时删除:创建定时器,定时删除。

惰性删除:获取key的时候检测删除。

定期删除:定期对设置过期时间的key检测删除。

淘汰策略 -> 针对内存不足

volatile-lru:从已设置过期时间的数据集中挑选出最近最少使用的数据淘汰。

volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。

volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。

allkeys-lru:从数据集中挑选最近最少使用的数据淘汰。

allkeys-random:从数据集中选择任意数据淘汰。

no-enviction:不删除。这也是系统默认的一种淘汰策略。

产生死锁条件

1,互斥:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

2,保持与请求:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

3,不可剥夺:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

4,循环等待: 若干进程间形成首尾相接循环等待资源的关系

volatile的两点作用 https://www.cnblogs.com/xd502djj/p/9873067.html

1,可见性:读取物理内存最新的值到CPU缓存,运算结束后同步到主内存。物理内存和CPU高速缓存数据保持一致

2,禁止指令重排

线程栈副本 --> 主内存(堆)

gc算法

标记-清除(mark-Sweep):标记无用对象,然后进行清除回收。缺点:产生垃圾碎片。

复制(copy):按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象标记复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。

标记-整理(mark-compact):标记无用对象,让所有存活的对象都移向一端,然后直接清除掉端边界以外的内存

gc类型

full gc:清理堆(新生代;老年代;方法区;)空间。执行速度慢。

minor gc:新生代gc,执行速度快,频率高;(复制算法)

major gc:老年代gc,连着新生代一起执行;执行速度慢;(标记整理;标记清除)

对象是否存活

引用计数法:对象里有计数器,该方法已被弃用;不能解决对象相互引用问题;

可达性分析算法 gc root 通过 heap.dump 日志文件获取。

类加载的过程. JVM优化及面试热点分析_weixin_51297617的博客-CSDN博客_jvm热点

链接{验证,准备,解析}

校验:验证class文件合法性。

准备:分配空间;赋默认值。eg:静态变量分配空间,int = 0;

解析:符号引用转换直接引用(内存地址)。

初始化:静态代码的执行。

JVM内存优化

新生代频繁GC : 可以调整新生代内存大小。

JVM内存模型:虚拟机栈,本地方法栈,程序计数器。堆,方法区

PermGen space :永久代,1.8版本后被元空间取代。方法区的一种实现。

JVM之 方法区、永久代(PermGen space)、元空间(Metaspace)三者的区别_那年那些事儿-CSDN博客_jvm元空间和方法区

方法区:永久带 1.7--> 元空间1.8(本地内存){类信息,静态变量,编译代码;常量池在堆里}

虚拟机栈:Java栈,每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(stack Frame) ,对应着一次次的Java方法调用。

栈溢出:StackOverFlowError 方法调用链过长导致;超出虚拟机规定的调用深度,递归调用。

内存溢出:OutOfMemeryError 内存不足。

栈:调用方法创建栈帧,入栈和出栈,参数,局部参数,返回参数,对象引用等保存在栈帧,类似局部变量区表。

类加载器(创建Class对象)https://blog.csdn.net/m0_38075425/article/details/81627349

Bootstrp ClassLoader:启动类加载器,加载 jre -> lib(jar包,包括:ExtClassLoader,AppClassLoader);c++实现。

Launcher.ExtClassLoader:扩展类加载器,加载 jre -> lib -> ext(jar包)。

Launcher.AppClassLoader:应用类加载器,加载 classpath 路径 jar 包。

关系:AppClassLoader -> ExtClassLoader -> Bootstrp ClassLoader

双亲委托机制:

检查缓存是否已有该类,委托父类加载,一直委托到启动类加载器,父类未加载此类,自己才去加载此类,放入缓存中。

对象地址:System.identityHashCode

string.intern():返回常量池中的字符串的内存地址。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Addison_Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值