商品数量有限,用户下单商品会被扣除,如何进行扣减库存
假设某个商品库存是100,其实不扣减应该是预占库存,有三个方案
1,用户加入购车车时进行库存预占:只有一个用户会将100个商品加入购物车
2,用户提交订单时进行库存预占:只能有一个用户将100个商品提交订单成功,其他人均提示库存不足
3,如果用户提交订单并且支付成功时进行库存预占:将有100个人生成订单,但只有一个可以支付成功,其他订单失败,会生成无效订单
采用方案二:提交订单成功就会进行库存数量的预占,预占成功了才会进行下面的支付
预占库存实现方案
订单都会有对应的库存流水表,流水表跟订单SKU关联的,流水会有预占库存的状态
1,预占状态 2,扣减状态 3,释放状态
当订单创建成功之后,订单流水会插入此时的状态是预占状态,同时会扣减库存表的可用库存数量,如果支付成功后,库存服务会收到消息,状态会变为扣减状态;支付失败,库存会收到支付失败消息,就会释放库存或者叫库存回滚,恢复这个库存表的可用库存数量。
订单和预占库存是强一致性事务,成功必须是一致性的,这里我们可以采用Seata来做分布式事务来保证订单和库存的一致性
Redis设计方案,库存流水状态的变法
1,预占状态 2,扣减状态 3,释放状态
所有的库存变化都会有基础流水的,流水是有状态的,库存表进行扣减库存的时候会检查对应流水状态,如果是预占状态才会进行扣减
默认插入流水状态就是预占状态,其他两种就是扣减和释放状态,
当支付成功后,流水状态更新为扣减状态就可以了,接口保证迷整性
支付失败那流水状态就更新为释放状态
在大促销情况下,如果订单交易量很大,那么对预占库存的吞吐量要求很高,其实就是对db压力很大,会怎么优化呢?
可以用Redis来做存储库存,保证Redis的库存和DB内的可用库存最终一致就可以了
当一个订单请求过来,会调用库存服务,我们可以进行SKU的拆解,查看Redis中是否有每个SKU库存记录,如果没有可以从DB库存表中去初始化可用库存到Redis
比如查询DB内SKU为001的可用库存为10,那么就会初始化到Redis的可用库存为10,预占库存为0,初始化时会加分布式锁,lua脚本来检查,如果有任何SKU没有通过这个检查或者预占操作,那么回滚,发送订单库存流水记录到MQ,同步库存任务消费MQ,通过MQ来扣减库存DB中对应的SKU的可用库存量,最后释放SKU的Redis内预占库存
支付取消后的并发问题
某个SKU的扣减可用库存时候,支付取消了就会释放预占库存
1,可以判断流水还是预占状态就再次发送消息到MQ吗,然后重新消费MQ
2,利用MQ顺序特性,预占消息,支付成功消息是顺序的没然后顺序消费就可以了
哪些情况会遇到预占库存的回滚
1,用户未支付,订单自动取消
2,用户支付成功然后取消
3,系统识别为异常订单,强行取消
4,订单系统和其他服务耦合,比如扣减积分失败了,就会回滚订单
出现库存热点数据,如何解决
归根到底就是分片,让原本一个数据实例切分到更多的实例上就可以了
可以通过SKU+随机数这种方式切分库存量,进而让流量分散开来降低单个实例的压力,客户端也要做相应的适配
数据库数据量很大,解决方案
我们其实进行了归档的处理,一般订单用户只会查询半年或者一年的订单,我们可以将之前的一些订单存储归档到单独的数据表中,来降低这个表的数据量大小,降低查找或查询的延迟,业务设计的时候要考虑对应的判断和适配,去查询最近一年的数据呢还是一年之前的数据
大流量情况下高并发如何解决
为这个表做读写分离,来降低读写锁的冲突,同时我们也进行了一个分库分表,来降低磁盘的IO压力,每个库呢是在不同服务器,来降低不同mysql实例对资源的争抢
读写分离的主从同步是有延迟,怎么解决数据延迟
首先主从的机器的配置都是相同的配置,专库专用,主库就是给主库用的,从库就是给从库用的,不会在从库上跑一些其他任务或复杂的sql语句或定时任务,来占用从库的资源导致数据同步的延迟的时间增大,正常情况下会我们延迟的时间会控制在10毫秒或者10毫秒以内。在业务高峰期,用户下单完成后可能看不到从库的订单,设计订单支付成功的页面,用户在支付完成之后他会看到一个成功的页面,只有在点击查看订单详情页面的时候才会看到他订单的详情,在这个过程这个数据已经同步到从库那边了,用户就可以看到相应的订单详情信息了
如果延迟超过了到用户点击查看的时间,依旧没有同步过去,如何解决
可以将最近的订单存到Redis里面,用户查询的时候可以走Redis进行查看,列表可以是你的数据库和你的Redis进行合并,进行筛选,Redis可以设置失效时间,比数据库这个主从同步时间长一些就可以了
分库分表怎么做的
c端呢我们是用户,用户要看自己的订单,我们就按照用户Id进行Hash取模进行分库分表。b端是用商家ID进行Hash取模进行分库分表,中间件用Sharding-Sphere,我们用它这个Client模式
如果只有订单id情况,如何知道订单在哪张表
有一个基于基因法进行分库分表,具体实现是记得不是很清晰,它是通过一定的算法
里面有一定的位数来存储库啊表啊还有订单ID的一些信息,来了之后呢通过一定的算法,定位到你这个订单所在的库和表
如何从mysql同步到Es
采用canal中间件,伪装成mysql的从节点然后解析这个binlog,就可以得到实时的数据变化,然后再写入到MQ,最后消费MQ把他写到ES里面,在我们项目中,生成订单或者订单发生改变的时候已经将他发送到MQ了,没有采用canal方案
MQ宕机或者消息丢失,如何补偿数据
我们会记录这个消费这个MQoffset,如果出现这个任务宕机,那么重启之后还可以继续消费,MQ宕掉的话我们后台也有按照某些功能或条件,去查询你想要的补偿数据,然后手动同步到ES
订单状态的改变是有顺序的,如何保证顺序消费
如果线程a从MQ拿到了比如订单003消息,不知道什么原因阻塞了,之后订单003状态更新了,接着线程b从MQ拿到003最新消息,然后优先执行写到ES里,然后线程a由阻塞状态恢复了然后继续执行这个消费消息,写到Es这个订单的状态就被覆盖了
解决:更新时候看一下状态机再对比一下时间就可以了
MQ消费的幂等如何保证
订单都有订单Id或者订单编号,先拿到订单id去Es中查找,然后进行幂等相关判断,
比如这个id加状态去判断是否存在啊,判断确认是新的数据就可以写入到这个ES或者更新ES数据,在检查和更新的过程中会加分布式锁处理
如何防止重复创建订单
一般都会经过网关,然后再到业务服务,再到数据服务
app会向后端请求一个token,这个token等同于订单id,为了不泄露这个订单id所以返回一个token,所以返回信息的时候会带上这个token,如果后端查询到这个token,看cache内是否有这个关联的订单id,如果没有就创建订单,否则就返回错误信息
mysql中innodb存储引擎
innodb是mysql众多存储引擎之一,室友这个执行引擎进行调用的,innodb是基于b+tree实现的,而且他锁的力度更小而且支持行锁的,支持事务。它是分主键索引,二级索引
b+tree
b+tree又称为n叉树,主要是innodb想在每层存储更多数据,每层存储数据越多这个书的高度就会越低,那么查询磁盘IO的次数减少,b+tree的主键索引的非叶子节点存储的是主键id,每层可以存储更多的数据;而叶子节点存储的数据,而且是按照索引进行排序的,索引最好是自增的防止这种叶分裂或者数据移动
sql查询结果长时间不返回,会是什么情况
1,大概率表被锁住了,一般会执行show processlist命令查看当前语句
2,可能在等待flush table,可能还是在等待update语句的释放行锁,update是一个循环语句,那么只能等待update释放锁
3,走了全表扫描,没建立索引,或者索引不合适
4,关于MVCC相关
为什么要创建索引,创建索引考虑问题
索引是提高查询效率的数据结构
1,在 where 及 order by 涉及的列上建立索引
2,不要对数据库中某个含有大量重复的值的字段建立索引
3,索引的列类型要尽量小,小的话占的内存就小
4,尽量使用覆盖索引进行查询,避免回表带来的磁盘IO
5,让索引以列名的形式单独存在搜索条件中,这样有利于优化器去选择索引
6,主键尽量自增
7,删除一些冗余或重复的索引
索引的好处:
1,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性
2,可以大大加快数据的索引速度
3,使用分组和排序子句时,可以显著减少查询中分组,排序时间
4,提高系统性能
5,加速表与表之间连接
带来的问题:
1,维护索引需要耗费数据库资源
2,索引需要占用物理空间,索引字段越多,创建的索引越多
3,对表中的数据进行增删改时,也要维护索引,降低数据的维护时间
什么情况会导致索引失效:
1,not null/null会导致索引失效
2,like关键字
3,索引上进行函数运算
4,自动类型转换过程会使索引变得不起作用
复合索引
将数据库中的多个字段组合起来形成的一个索引就是复合索引
本质:最左匹配原则
目的:能够形成索引覆盖,提高where查询的效率
复合索引的第一个字段必须出现查询语句中,这样索引才能够被使用
普通索引和唯一索引使用场景有什么区别
查询:
比如select Id from user wehere name = ‘’
在name上创建了普通索引,对于普通索引来说满足条件第一条记录还需要继续查找下一条记录直到不满足记录才会停止查询
对于唯一索引来说查到满足条件就不会继续查找了
更新:
唯一索引会判断表中是否存在这条记录
mysql innodb引擎下的隔离级别
读未提交:事务还没有提交的时候,它做的变更就能被别的事务看到
读提交:事务提交后,它做的更新才能被其他事务看到
可重复读:事务执行中,看到的事务和事务启动的时刻这个瞬间看到的数据是一致的,未提交变更对其他事务是不可见的
串行化:对同一行记录写会加锁,读也会加锁,读写冲突的时候后面访问的事务必须等待前一个事务执行完成后才能执行
mabatis缓存机制
一级缓存是SqlSession级别的缓存,当执行相同的查询时,如果缓存中已有数据,则直接返回缓存中的数据,避免了对数据库的重复访问。二级缓存是Mapper级别的缓存,它跨SqlSession,可以在不同的SqlSession之间共享数据。MyBatis还提供了缓存的配置选项,如缓存大小、缓存类型、缓存清理策略等,可以根据实际需求进行配置。同时,MyBatis还支持自定义缓存,可以通过实现Cache接口来创建自己的缓存实现。
一级:
一级缓存的作用域是当前的SqlSession(Mybatis中的sql对象,封装了jdbc的增删改查),但是SqlSession的生命周期比较短暂,所以一级缓存提升性能有限
二级:
在Mapper.xml 中配置标签:
消息队列MQ
MQ(实现起来也就是消息+队列)
把消息放到队列里,用队列做存储消息的介质队列
生成者负责投递消息到消息队列中。
消费者负责去消息队列中取消息,也叫消费消息
场景:双11是购物狂节,用户下单后,订单系统需要通知库存系统,
传统的做法就是订单系统调用库存系统的接口这种做法有一个缺点:1,当库存系统出现故障时,订单就会失败 2,订单系统和库存系统高耦合
引入消息队列
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
库存系统:订阅下单的消息,获取下单消息,进行库操作。
就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失
流量削峰
流量削峰一般在秒杀活动中应用广泛
场景:秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。
1.可以控制活动人数,超过此一定阀值的订单直接丢弃(我为什么秒杀一次都没有成功过呢^^)
2.可以缓解短时间的高流量压垮应用
1.用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面.
2.秒杀业务根据消息队列中的请求信息,再做后续处理.
RabbitMQ如何保证消息不丢失
rabbitmq重启后,之前的数据丢失了。所以必须开启持久化将消息持久化到磁盘,这样就算rabbitmq挂了,恢复之后会自动读取之前存储的数据
解决办法
1,开启事务功能,
可以选择使用rabbitmq提供的事物功能,就是生产者在发送数据之前开启事物,然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会受到异常报错,这时就可以回滚事物,然后尝试重新发送;如果收到了消息,那么就可以提交事物
2,开启confirm模式
在生产者开启了confirm模式之后,每次写的消息都会分配一个唯一的id,然后如何写入了rabbitmq之中,rabbitmq会给你回传一个ack消息,告诉你这个消息发送OK了;如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息失败了,你可以进行重试。而且你可以结合这个机制知道自己在内存里维护每个消息的id,如果超过一定时间还没接收到这个消息的回调,那么你可以进行重发。
事务机制是同步的,你提交了一个事物之后会阻塞住,但是confirm机制是异步的,发送消息之后可以接着发送下一个消息,然后rabbitmq会回调告知成功与否。
一般在生产者这块避免丢失,都是用confirm机制
消费者丢失消息如何解决
使用rabbitmq提供的ack机制,首先关闭rabbitmq的自动ack,然后每次在确保处理完这个消息之后,在代码里手动调用ack。这样就可以避免消息还没有处理完就ack
Redis
1,为什么要使用Redis缓存,你都用过redis做什么?
Redis是一个分布式的缓存中间件,使用缓存我从三个方向介绍使用的原因:
1.缓存可以提高网站并发读写能力。
2.缓存可以解决跨进程的内存计算问题
3.可以解决跨进程的分布锁问题
1,一个电商的首页需要的并发量非常高,我们如何提供首页的并发能力呢,其根本思路就是减少用户对tomcat服务器的访问压力。用户访问首先从nginx缓存获取数据,如果获取不到通过lua脚本查询Redis,如果Redis有数据就将数据同步到nginx缓存中,然后响应客户端。如果redis没有数据才会查询mysql数据库。使用nginx+redis两层缓存的目的一是提高并发量,二是减少缓存雪崩
2,缓存可以解决内存计算问题,比如一个网站上传的图片到七牛云,只有保存到数据库的图片才是有用的图片,但操作七牛云和操作数据库的是两个JVM进程时如何计算呢?把两者数据读取到redisset集合,求两个集合的差值就是垃圾图片,然后调用api删除这些图片。在比如商城的购物车数据库是跨JVM进程的,如何存储计算呢?可以通过redis的hash结构存储并计算。
3,跨进程操作共享数据,如何解决多线程安全问题呢,此时JVM线程锁已经失效,可以使用RedissonClient提供的分布式锁实现,商城的超卖问题就可以通过这个解决
Redis特点
1,读写速度快,读最高可达110000次/s 写最高81000次/s
2,支持丰富的数据类型,key-value 、 strings、 lists
3,持久化,可以将内存中的数据保存在磁盘中,重启时再次加载进内存进行使用,定期快照,日志记录方式保存(类似增量备份)
4,原子性:redis 所有操作都是原子性的。
5,支持数据备份:即 master-salve 模式的数据备份
Redis五种数据类型及其常见操作
1,string:String类型是最基本类型key-value,可以用于数据缓存,做计数器(incrby),求粉丝数,分布式锁的底层原理(setnx)
List类型是一个双向链表结构,可以用于消息队列,最新列表等能。
Set类型是一个无序集合,自动去重复,可以做一些去重复计算,集合计算等。
Hash类型底层是Hash结构,可以用于对象存储并计算,比如用来存购物车数据。
Zset是一个有权重的Set集合,可以利用权重做些排行榜等需求。
Redis的发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
相关命令:
subscribe 主题名字 订阅主题
publish 主题名字 消息 发送消息
如果发布者发布频道消息时,订阅者还有没有订阅频道是收不到消息的
如何解决redis的慢查询问题?
慢查询,顾名思义就是比较慢的查询,可以通过慢查询日志获得问题是所在
showlog get n 获得前n条慢查询日志
showlog len 获得慢查询日志长度
config set slowlog-log-slower-than 1000 设置慢查询阀值 单位微妙
config set slowlog-max-len 1200 设置慢查询命令长度
config rewrire 保存设置
Redis内存淘汰策略
当 Redis 内存使用达到最大内存限制时,如果继续进行写入操作会导致 Redis 服务崩溃。因此,为了保证 Redis 服务的稳定性,Redis 在内存使用达到最大限制时采取一系列措施,如内存淘汰、警告等
Redis如何保持一致性
Redis 主要提供主从库模式,来保证数据副本的一致性,主从库之间采用的是读写分离的模式。
读操作 : 主库,从库都可以接收
写操作 : 首先到主库进行执行,之后通过写操作由主库同步给从库
主从库采用读写分离,所有数据的修改只会在主库上进行,不用协调三个实例。主库有了新的数据后,会同步给从库,这样,主从库的数据就是一致的
Redis命中机制和淘汰机制
命中机制:查询数据可以查询到,例如查询100条可以查询到20条即命中20条
淘汰机制:Redis缓存的是高热数据,若负载高于限制则淘汰一些最近没有访问的数据,即删除
缓存穿透,缓存击穿,缓存雪崩
缓存穿透(缓存和数据库中都没有的数据,而用户不断发起请求)
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿(缓存中没有但数据库中有的数据)
设置热点数据永远不过期。
加互斥锁,互斥锁参考代码如下:
缓存雪崩(指缓存中数据大批量到过期时间,而查询数据量巨大)
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
设置热点数据永远不过期
Redis分布式锁
为什么需要锁
原因其实很简单:因为我们想让同一时刻只有一个线程在执行某段代码。
方案一:SET NX key value + expire(设置锁的过期时间)
SET NX key value:当key不存在的时候,给key加锁,并设置值value
expire:给指定的key设置过期时间,避免死锁问题
del key:手动释放key的锁
方案二:使用Lua脚本
将set NX和expire通过一次网络IO发送给Redis,一次性的在Redis服务端内执行完成,中间不穿插其他执行
写一段Lua脚本发送给Redis服务端,让Redis服务端内原子的执行Lua脚本
方案三:SET的扩展命令(SET EX PX NX)
通常使用 set 命令来实现
set key value PX milliseconds NX
方案四:SET EX PX NX + 校验唯一随机值,再删除
方案五:Redisson
lock.lock()
Redis 分布式锁过期了,还没处理完怎么办
1,守护线程“续命”:加长锁的过期时间,并加一个子线程,每10秒确认线程是否在线,如果有则延长过期时间。Redisson 里面就实现了这个方案,使用“看门狗”定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间
2,给锁加UUID,线程id作为key
3,超时回滚:当我们解锁时发现锁已经被其他线程获取了,说明此时我们执行的操作已经是“不安全”的了,此时需要进行回滚,并返回失败
Redis两种持久化方式
1、RDB持久化: 在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
2、AOF持久化: 记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集
死锁的原因
在申请锁时发生了交叉闭环申请
多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环。
例如:线程在获得了锁A并且没有释放的情况下去申请锁B,这时,另一个线程已经获得了锁B,在释放锁B之前又要先获得锁A,因此闭环发生,陷入死锁循环
如何定位,避免死锁
并发下线程因为相互等待对方资源,导致“永久”阻塞的现象
解决定位:
打开命令窗口,查找当前程序的进程,结合jstack+进程id,打印出当前正在死锁的进程
保证线程安全的前提下,解锁死锁
破坏下面其中之一条件就可以解决死锁
1,互斥:共享资源只能被一个线程占用互斥锁
2,占有且等待:线程当前占有至少一个资源并还想请求其他线程持有的其他资源就会造成等待 。 一等待对方释放资源
3,不可抢占:资源只能由持有它的线程自愿释放,其他线程不可强行占有该资源 。一无法释放对方资源
4,循环等待:线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程T1占有的资源,就是循环等待 -两个线程互相等待
springCloud各个组件
Eureka:服务治理组件,包括服务端的注册中心和客户端的服务发现机制;
Ribbon:负载均衡的服务调用组件,具有多种负载均衡调用策略;
Hystrix:服务容错组件,实现了断路器模式,为依赖服务的出错和延迟提供了容错能力;
Feign:基于Ribbon和Hystrix的声明式服务调用组件;
Zuul:API网关组件,对请求提供路由及过滤功能。
SpringCloud : 两个微服务进程之间通信(远程调用)
- 加依赖(依赖tcloud-user-provider)
- 启动类中声明RestTemplate
- 配置微服务端口
在application.yml中添加 - Controller中远程调用
@GetMapping(“/user/{name}”)
SpringBoot和SpringMVC对比
SpringBoot的作用是简化了项目的搭建和开发过程。
- SpringBoot内置了服务器,可以直接启动
- SpringBoot是热部署的
- SpringBoot用自动装配实现了0配置
- SpringBoot整合了各种框架
SpringBoot和SpringCloud的区别
1,SpringBoot专注于快速方便的开发单个个体微服务。
2,SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,
3,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务
4,SpringBoot可以离开SpringCloud独立使用开发项目, 但是SpringCloud离不开SpringBoot ,属于依赖的关系
5,SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
springcloud gateway限流
API网关:gateway
1,统一对外接口 2,统一鉴权 3,服务祖册与授权 4,服务限流 5,全链路跟踪
在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击。
流程:默认的限流方式各项限流参数都是在配置文件中配置
数据库添加一个流控表,有需要限流的URL,最大限流限制数、时间范围等字段。通过页面维护这个表的数据。gateway中写一个全局过滤器中,收到请求后,用URL去数据库中查询、或者从缓存查询,得到需要限制的参数,再调用写好的限流方法实现限流。限流方法用Redis的Zset数据结构实现的滑动窗口算法
过程:添加依赖-配置文件-redis工具类-Redis实现的滑动窗口的限流算法
服务注册和发现是什么意思
当我们开始一个项目时,我们通常在属性文件中进行所有的配置。随着越来越多的服务开发和部署,添加和修改这些属性变得更加复杂。有些服务可能会下降,而某些位置可能会发生变化。手动更改属性可能会产生问题。 Eureka 服务注册和发现可以在这种情况下提供帮助。由于所有服务都在 Eureka 服务器上注册并通过调用 Eureka 服务器完成查找,因此无需处理服务地点的任何更改和处理。
服务熔断
服务熔断 是指 由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。
断路由
当一个服务调用另一个服务由于网络原因或自 原因出现问题,调用者就会等 调用者的响应 当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)
断路器有完全打开状态:一段时间内 到一定的次数无法调用 并且多次监 没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务
半开:短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭
关闭:当服务一直处于正常状 能正常调用
路由(Route)
- id,路由标识符,区别于其他 Route。
- uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
- predicate,断言(谓词)的作用是进行条件判断,只有断言都返回真,才会执行路由。
- filter,过滤器用于修改请求和响应信息
网关解决方案
1,Nginx+lua:Nginx做反向代理和静态资源服务器
2,kong:对Nginx+lua的封装
3,Traefix:GO语言开发的比较适合K8S
4,SpringCloud Gateway
5, SpringCloud Neflix Zuul:1.0单线程阻塞IO,不支持长连接
限流
为什么限流,是防止用户恶意刷新接口
常见的限流:
1、Netflix的hystrix
2、阿里系开源的sentinel
3、说白了限流,为了处理高并发接口那些方式:队列,线程,线程池,消息队列、 kafka、中间件、sentinel:直接拒绝、Warm Up、匀速排队等
降级
降级其实当网站出现高并发时,丢车保帅的一个策略。
降级分为:内容降级,限流降级,限速降级
集群
集群就是一组相互独立的计算机,通过高速的网络组成一个计算机系统,每个集群节点都是运行其自己进程的一个独立服务器。对网络用户来讲,网站后端就是一个单一的系统,协同起来向用户提供系统资源,系统服务。通过网络连接组合成一个组合来共同完一个任务
服务降级和熔断如何实现?
服务降级和熔断是一种常见的微服务治理机制,用于应对系统故障、资源不足或服务过载等情况。实现服务降级和熔断的方法有多种,其中一些常见的方法包括:
超时设置:设置请求的最大等待时间,超过该时间则认为服务不可用,进行降级处理。
错误率限制:监控服务的错误率,当错误率超过设定的阈值时,进行降级或熔断处理。
限流策略:通过限制并发请求数量或请求速率来控制服务的负载,避免过载导致服务不可用。
断路器模式:在服务出现故障或超时时,打开断路器,停止向该服务发送请求,直接返回预设的降级响应。
降级响应:在服务不可用或降级时,返回预设的响应结果,如缓存数据、默认值或错误提示
HashMap特性
1,继承AbstractMap抽象类,实现Map接口以及Cloneable, java.io.Serializable克隆和序列化
2,底层是由数组+链表组成的哈希表,JDK1.8链表长度超过8并且table数组大小大于64时才会将链表优化为红黑树
3,增删改查的效率都比较高,但多线程环境下是不安全的,可能存在问题
4,存储的元素是键值对,key键是唯一的,并且允许为key/value为null但不保证顺序
5,通过key的hash值计算出需要存放在哈希表中的数组位置index
6,默认初始化容量大小为0,第一次调用put真正给默认大小16,每次扩容oldCap << 1即原来容量的2倍
7,常用的API方法 put(key,value)/get(key)/size()/isEmpty()/containsKey(key)/remove(key)
8,底层源码关键属性table、threshold、loadFactor、modCount、size
HashMap先计算出hash数组,在hash冲突后使用链表
QPS过万,redis大量连接超时怎么解决
1、首先和负责redis同学排查,先排除redis本身的问题
2、服务自查
3, 慢请求: 查看监控,如果有慢请求的时间和发生问题的时间匹配,则可能存在「大key」问题
什么是Nginx,有什么优势和功能
Nginx 是高性能的 HTTP 和反向代理的服务器,处理高并发能力是十分强大的,能经受高负载的考验,有报告表明能支持高达 50,000 个并发连接数
Nginx主要提供功能:
http服务器
反向代理服务器
负载均衡服务器
动静分离配置
缓存数据
简述一下什么是正向代理,什么是反向代理
正向代理代理的是客户端访问服务端,比如防火墙,反向代理代理的是服务端,等待客户端访问代理服务
什么是Nginx的负载均衡
nginx反向代理tomcat服务集群,当客户端访问nginx服务器时,由nginx负载均衡去访问tomcat集群中的某一个节点
都用过哪些Docker命令
镜像操作
容器操作
数据卷操作
自定义镜像操作
网络操作
镜像操作的常用命令
docker pull 拉取镜像
docker push 推送镜像
docker images 查看所有镜像
docker inspect 镜像名 查看镜像详细信息
docker rmi 镜像名 删除镜像
docker build 自定义镜像
docker save 保存镜像
docker load 加载镜像
单例模式如何保证单例
通过双重锁机制和volatile关键字保证了单例
双重锁:给进程一把锁,确保当一个线程位于代码的临界区时,另一个线程不进入临界区
volatile :作为指令关键字,确保本条指令不会因编译器的优化而省略
1,保证变量可见性:一个线程修改某变量,新值对其他线程来说立即可见
2,禁止指令重排序
class Singleton {
private volatile static Singleton instance = null;
private Singleton(){ }
public static Singleton getInstance(){
if(instance ==null){
synchronized(Singltton.class){
if(instance ==null ){
instance = new Singleton();
}}}}}
sql执行流程
1,链接:建立连接
2,查询状态
3,查询缓存
4,查询优化处理
5,查询执行引擎
5,返回结果给客户端
读写分离
读写分离:
基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。
数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库
读写分离:为保证数据库数据的一致性,我们要求所有对于数据库的更新操作都是针对主数据库的,但是读操作是可以针对从数据库来进行。大多数站点的数据库读操作比写操作更加密集,而且查询条件相对复杂,数据库的大部分性能消耗在查询操作上了
所以读写分离,解决的是,数据库的写入,影响了查询的效率
强引用、软引用、弱引用、虚引用
1.强引用,垃圾回收器不回收。当内存不足,Java 虚拟机抛出 OutOfMemoryError 错误
2.软引用,内存空间足够,不回收。内存不足,回收。可实现内存敏感的高速缓存。 如果软引用的对象被垃圾回收,jvm把软引用加入关联引用队列。
软引用加速垃圾回收,维护系统安全,防止(OOM)等问题的产生。
3.弱引用,弱、软区别——弱对象生命周期更短。不管内存空间是否足够,都回收。
4.虚引用,虚引用不决定对象生命周期。在任何时候可能被垃圾回收。虚引用跟踪对象被垃圾回收的活动
类加载机制
1,加载机制是指类的加载、链接、初始化的过程
加载:将字节码文件中的.class文件,通过类加载器,加载进运行时数据区的方法区内,并创建一个大的Class对象
2,链接:
① 验证(比如说验证字节码文件开头是CAFFBABA,版本号等)
② 准备(为类变量赋予默认的初始化值, 使用static+final修饰, 且显示赋值不涉及到方法或者构造器调用的基本数据类型或者String类型的显示赋值都在准备阶段)
③ 解析:将类中的符号引用变成直接引用(符号引号在字节码文件的常量池中)
3,初始化:为类变量赋予正确的初始化值, 执行Clinit方法
垃圾回收常用算法
1,标记阶段:引用计数法
2,标记阶段:可达性分析算法
3,清除阶段:复制算法
4,清除阶段:标记清除
5,清除阶段:标记整理
如何判断对象是否死亡
引用计数法 :对象 + 一个引用计数器
暂时不用,因为难解决对象之间相互循环引用的问题
可达性分析算法
什么情况下会发生栈内存溢出
1,局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出
2,递归调用层次太多
谈谈对锁机制的理解
锁机制是一种对资源的保护措施,它确保只有持有锁的对象才能操作资源。在数据库中,锁机制用于保证数据的一致性和隔离性。根据不同的维度,锁可以分为多种类型,如表级锁、行级锁、页级锁;共享锁、排他锁;乐观锁、悲观锁等。表级锁锁住整个表,限制性强,适用于某些特定操作。行级锁锁住某个数据行,力度最细,允许其他事务访问表中未被锁住的行。页级锁介于表级锁和行级锁之间。共享锁允许事务读取一行数据,但阻止其他事务获取该行的排他锁。排他锁则允许当前事务对数据进行查询或修改,并阻止其他事务对数据进行任何操作。乐观锁假定冲突不太可能发生,不立即锁定资源,而是在提交更新时检查数据是否会被其他事务修改。悲观锁则假定冲突会发生,因此在事务开始时立即锁定资源
SQL性能优化
创建索引、避免索引失效、选择合适的锁粒度、分页查询优化、避免使用’select *'、使用EXPLAIN分析执行计划以及SHOW PROFILE分析性能。通过这些方法,可以提升SQL执行效率,适应高并发场景
1,选用适合的Oracle优化器
RULE(基于规则)、 COST(基于成本) 、CHOOSE(选择性)
2,访问Table的方式
全表扫描:顺序地访问表中每条记录,一次读入多个数据块的方式优化全表扫描
通过rowid访问表:采用索引实现了数据和存放数据的物理位置(ROWID)之间的联系
3,选择最有效率的表名顺序:ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,当ORACLE处理多个表时,会运用排序及合并的方式连接它们,并且是从右往左的顺序处理FROM子句
4,用Where子句替换Having子句
避免使用HAVING子句,HAVING 只会在检索出所有记录之后才对结果集进行过滤。这个处理需要排序、总计等操作
5, 使用表的别名
6,使用索引
优点:提高效率 主键的唯一性验证
代价:需要空间存储 定期维护
ALTER INDEX rebuild
7,用Union替换OR:对索引列使用OR将造成全表扫描
8,用in代替OR:
9,避免在索引列上使用is null和is not null
mybatis分页原理
MyBatis的分页原理是通过拦截器实现的。具体来说,MyBatis通过编写自定义的拦截器,可以拦截SQL语句的执行,并在执行前或执行后对SQL进行处理。当执行分页查询时,拦截器会拦截查询语句,并根据传入的分页参数(如页码、每页记录数等),修改查询语句,添加上LIMIT或ROWNUM等关键字,从而实现分页查询的功能
高并发解决
流量优化:防盗链处理(把一些恶意的请求拒之门外)
前端优化:减少HTTP请求、添加异步请求、启用浏览器的缓存和文件压缩、CDN加速、建立独立的图片服务器
1,微服务拆分
分布式架构会从一个拆分为多个系统,每个系统都有独立的数据库等,通过这样的横向扩展,就可以支撑更大的并发量。
2,负载均衡
是一种分布式系统架构中的技术,用于将网络请求或任务分散到多个服务器或资源上。
比如:当系统面临大量用户访问,负载过高的时候,通常会使用增加服务器数量来进行横向扩展来提高整个系统的处理能力
多个节点横向扩展,通过多台服务器来承担并发压力
3,分布式缓存
大部分的高并发场景,都是读多写少,要想提高数据的访问速度,那系统必须得加缓存。
缓存的读写效率,远远大于数据库的读写效率
4,异步处理
对于一些耗时的操作,比如下订单后的发短信,并发量大的情况下同步操作极为耗时,需要改造为异步请求
与同步处理相比,异步处理不会阻塞主线程的执行,允许主线程继续执行其他任务,而异步任务在后台或其他线程中完成。
异步处理可以实现多个任务的并行执行,提高系统的并发处理能力
5,分库分表
海量数据的存储和访问成为了系统设计的瓶颈问题,一张表超过了亿级数据,都会考虑拆分。
日益增长的业务数据,无疑对数据库造成了相当大的负载,这里就会涉及到垂直拆分和水平拆分等
6,消息队列
RabbitMQ、Kafka、RocketMQ
消息队列(Message Queue)是一种在分布式系统中用于异步通信的架构模式,它可以实现解耦、异步处理、削峰填谷等目标
当系统面临突发的大量请求时,可以将请求暂时存储在消息队列中,然后按照系统的处理能力逐步消费和处理。
这种方式可以避免系统被过载和崩溃,适用于流量波动较大的场景,特别适用于高并发的场景
7,限流和熔断
保护核心系统的安全性
限流和熔断是分布式系统中常用的两种流量控制和容错机制。
用于保护系统免受异常情况下的影响,提高系统的稳定性和可用性
8,数据库优化
优化数据库的设计、索引、查询语句等,提高数据库的读写性能。
Java线程数过多会造成什么异常
1,线程的生命周期开销非常高
2,消耗过多的CPU资源
如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争CPU资源时还将产生其他性能的开销。
3,降低稳定性
JVM在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承受着多个因素制约,包括JVM的启动参数、Thread构造函数中请求栈的大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出OutOfMemoryError异常
线程相关的方法
wait(): 强迫一个线程等待。
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级
Java多线程应用场景
1,用于分解任务,提高计算效率
2,通过后台线程进行耗时操作,保持用户界面的流畅性
3,在计划任务和定时任务中,实现异步任务的批量执行和延迟执行
4,在高并发的Web服务器中,使用线程池管理,例如使用线程来处理HTTP请求
什么是线程池
线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。
使用线程池主要有以下优点:
降低资源消耗
提高响应速度
提高线程的可管理性
通过 ThreadPoolExecutor 创建的线程池
通过 Executors 创建的线程池
线程池的创建方式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过 ThreadPoolExecutor 创建的)
线程池使用场景
在接收mqtt消息时,消息会有很多,开启异步线程,相当于你需要消费大量的消息的时候不需要考虑其顺序性,一般是自己定义线程池,用多线程的方式去消费,但是如果你的模块中有多个方法需要使用多线程,你就需要定义多次
异步线程池
创建线程池配置类(可以放在特定的服务中,创建特定的线程池)
ThreadPoolTaskExecutor
核心线程数,最大线程数,任务队列大小,线程的空闲时间,新创建的线程名称的前缀
用法:
要现在启动类和配置类中添加@EnableAsync注解
然后在需要异步的方法中添加@Async注解将方法异步化
ThreadPoolExecutor,可以实现对并发任务的控制和管理,提高系统的并发处理能力
创建线程池的核心参数
corePoolSize 核心线程数
maximumPoolSize 最大线程数
keepAliveTime 空闲线程存活时间
unit 对应keepAliveTime的计量单位
workQueue 工作队列
threadFactory 线程工厂,用于创建线程可以指定线程名、是否为daemon线程等
handler 拒绝策略
为什么不建议用Executors
FixedThreadPool底层创建的队列喂LinkedBlodckingQueue是一个无界阻塞队列,会占用很多内存,会导致内存不足
SingleThreadExecutor同上,也是会耗尽内存
会造成内存不足,而且不能定义线程名字
线程池有哪几种状态,每种状态表示什么
Running:表示线程池正常运行,能接受任务,也能处理队列中的任务
ShutDown:调用shutdown()此方法后,表示线程池处于关闭状态,此状态下不会接受新任务,但是会继续把队列中的任务处理完
Stop:调用shutdownnow()此方法后,表示线程池正在停止状态,此状态下不会接受新任务,也不会处理队列中的任务,并且正在运行的线程也会被中断
Tidying:线程池中没有线程在运行后,线程池状态会自动变成Tidying,并且调用terminated(),该方法是空方法,留着扩展用
terminated:terminated()方法执行完后,线程池状态会变成terminated
ThreadLocal有哪些应用场景,底层如何实现
ThreadLocal是java中提供的线程本地储存机制,可以利用这个机制将数据缓存在某个线程内部
底层是通过ThreadLocalMap实现的,每个Thread对象都有一个ThreadLocalMap,Map的Key是ThreadLocal对象,value是需要缓存的值
应用场景:连接管理
问题:内存泄漏
解决方法:使用ThreadLocal方法后,手动调用remove()方法
MySQL和Oracle的区别
spring如何解决循环依赖问题
实例化(createBeanInstance):调用对应的构造方法构造对象
填充属性(populateBean):填充属性。
初始化(initializeBean):调用spring xml中指定的init方法
简述一下你了解的设计模式
1,工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
2,代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache代理、防火墙代理、同步化代理、智能引用代理。
3,适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。
4,观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现
获得一个类的类对象有哪些方式
1:类型.class,例如:String.class
2:对象.getClass(),例如:“hello”.getClass()
3:Class.forName(),例如:Class.forName(“java.lang.String”)
实现同步的方式
lock,synchronized,分布式锁
1.同步方法
2.同步代码块
IOC,DI
IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的
Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。
DI:Dependency Injection 依赖注入,在 Spring 框架负责创建 Bean 对象时,动态的将依赖对象注入到 Bean 组件
Spring中AOP的应用场景、Aop原理、好处
原理:AOP是面向切面编程,是通过动态代理的方式为程序添加统一功能,集中解决一些公共问题。
优点:
1,各个步骤之间的良好隔离性耦合性大大降低
2,源代码无关性,再扩展功能的同时不对源码进行修改操作
应用场景:
Authentication 权限、Caching 缓存、Context passing 内容传递、Error handling 错误处理Lazy loading懒加载、Debugging调试、logging, tracing, profiling and monitoring 记录跟踪优化 校准、Performance optimization 性能优化、Persistence 持久化、Resource pooling 资源池、Synchronization 同步、Transactions 事务
Spring Bean 的生命周期
Bean容器找到Spring配置文件中Bean的定义;
Bean容器利用java 反射机制实例化Bean;
Bean容器为实例化的Bean设置属性值;
如果Bean实现了BeanNameAware接口,则执行setBeanName方法;
如果Bean实现了BeanClassLoaderAware接口,则执行setBeanClassLoader方法;
如果Bean实现了BeanFactoryAware接口,则执行setBeanFactory方法;
如果 ……真的,到这我经常忘记,但前面三个Aware接口肯定能记住;
如果Bean实现了ApplicationContextAware接口,则执行setApplicationContext方法;
如果加载了BeanPostProcessor相关实现类,则执行postProcessBeforeInitialization方法;
如果Bean实现了InitializingBean接口,则执行afterPropertiesSet方法;
如果Bean定义初始化方法(PostConstruct注解或者配置的init-method),则执行定义的初始化方法;
如果加载了BeanPostProcessor相关实现类,则执行postProcessAfterInitialization方法;
当要销毁这个Bean时,如果Bean实现了DisposableBean接口,则执行destroy方法。
当要销毁这个Bean时,如果自定义了销毁方法(PreDestroy注解或者配置destroy-method),则执行定义的销毁方法。
事务隔离级别(5种)
未提交读(read uncommited) :脏读,不可重复读,虚读都有可能发生
已提交读 (read commited):避免脏读。但是不可重复读和虚读有可能发生
可重复读 (repeatable read) :避免脏读和不可重复读.但是虚读有可能发生.
串行化的 (serializable) :避免以上所有读问题.
Mysql 默认:可重复读
Oracle 默认:读已提交
Bean 注入属性有哪几种方式
spring 支持构造器注入和 setter 方法注入
构造器注入,通过 元素完成注入
setter 方法注入, 通过
pring 配置 bean 实例化有哪些方式?
1)使用类构造器实例化(默认无参数)
2)使用静态工厂方法实例化(简单工厂模式)
3)使用实例工厂方法实例化(工厂方法模式)
//先创建工厂实例 bean3Facory,再通过工厂实例创建目标 bean 实例
Arraylist在循环的时候能否删除?
循环中删除会导致被删元素后续元素的索引发生变化,后续元素都会往前挪一位
在循环里不删,记录下来需要被删的元素,待循环结束后再去统一删除
Integer与int的区别
int是java提供的8种原始数据类型之一。Java为每个原始类型提供了封装类,Integer是java为int提供的封装类。int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况
列出一些你常见的运行时异常?
NullPointerException (空指针异常)
ClassCastException (类转换异常)
ArithmeticException(算术异常)
IllegalArgumentException (非法参数异常)
IndexOutOfBoundsException (下标越界异常)
SecurityException (安全异常)
重定向跟转发区别
转发发送一次请求,重定向两次请求
转发地址栏不变,重定向地址栏发生改变
转发只能访问内部资源,重定向可访问外部资源
&和&&的区别:
&(1)按位与;(2)逻辑与。&&短路与运算
如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算
==跟equals
==比较两个对象的值是否相等
equals比较两个独立对象的内容是否相等
接口跟抽象类
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然
eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
Math.round(11.5),Math.round(-11.5)
12,-11
HashTable跟HashMap区别
HashTable的方法是同步的,HashMap不能同步
HashTable是继承自Dictionary类,而HashMap是继承自AbstractMap类
HashTable不允许null值(key和value都不可以),HashMap允许使用null值
HashTable使用Enumeration遍历,HashMap使用Iterator进行遍历
HashTable中hash数组初始化大小及扩容方式不同
Java中有的io流
字节流和字符流。字节流继承于InputStream、OutputStream,
字符流继承于Reader、Writer
字节流存在内存和文件中,字符流存在内存
处理文件类型用字符流,非文件型用字节流,比如图片,视频,音频
Spring管理事务有几种方式?
1,编程式事务,TransactionTemplate
2,声明式事务,@Transactional
通过使用 @Transactional 注解来声明事务。Spring 会自动管理事务的开始、提交和回滚。
在 @Transactional 注解中,可以配置事务的传播行为、隔离级别、超时设置等
spring 常用注解
@Component
@Controller:对应表现层的 Bean
@Scope(“prototype”):表示将 Action 的范围声明为原型
@Service:对应的是业务层 Bean
@Repository:对应数据访问层 Bean
代理的作用和实现
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
java反射原理
java虚拟机运行时内存有个叫方法区,主要作用是存储被装载的类的类型信息。每装载一个 类的时候,java 就会创建一个该类的Class对象实例。我们就可以通过这个实例,来访问这个类的信息
#{},${}
预编译处理,字符串替换
防止sql注入
为什么重写hashcode()方法
1,hashCode主要用于提升查询效率,来确定在散列结构中对象的存储地址,使用hashcode方法提前校验,可以避免每一次比对都调用equals方法,提高效率
2,保证是同一个对象,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况
String和StringBuilder、StringBuffer的区别
String 被final修饰。不可变类
StringBuffer和StringBuilder二者的功能和方法完全是等价的,都表示内容可以被修改的字符串
StringBuffer线程安全,StringBuilder线程不安全
StringBuffer的公开方法都是被synchronized 修饰的,而 StringBuilder 并没有。
StringBuffer与StringBuilder都继承自AbstractStringBuilder。
StringBuffer从JDK1.0就有了,StringBuilder是JDK5.0才出现
三者执行速度:StringBuilder > StringBuffer > String
构造器
构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload
防重复提交操作
1,按用户维度统一添加redis分布式锁@CacheThreadArg防止短时间的并发操作
2,加令牌token,提交订单的时候加上token,后台进行校验,如果存在就阻塞
sychronized和Lock不同
java中的一个关键字,JDK提供的一个类
自动加锁和释放锁,需要手动
锁的是对象
序列化和反序列化
序列化:把Java对象转换为字节序列的过程
反序列:把字节序列恢复为Java对象的过程