1.两个list取交集
2.arraylist和linkedlist的区别
(1)ArrayList和LinkedList可想从名字分析,它们一个是Array(动态数组)的数据结构,一个是Link(链表)的数据结构,此外,它们两个都是对List接口的实现。
前者是数组队列,相当于动态数组;后者为双向链表结构,也可当作堆栈、队列、双端队列
(2)当随机访问List时(get和set操作),ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
(3)当对数据进行增加和删除的操作时(add和remove操作),LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。
(4)从利用效率来看,ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。
(5)ArrayList主要控件开销在于需要在lList列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。
3.同步锁在同步静态方法和同步实例方法的区别,会不会自测,怎么自测
4.线程池的参数有哪些
5.spring循环依赖
6.springboot ,spring,springcloud区别
7.redis主从复制
8.什么样的字段用索引,in走索引吗
9.条件注解
10.springboot怎么自动配置
11.bigdecimal的api
12.hashmap的时间复杂度
13.锁升级
14.zk原理
15.mq堆积和重复消息处理
16.jvm和索引原理 b+树为什么效率高
17.redis缓存穿透、缓存击穿、缓存雪崩区别和解决方案
18.线程池
19.String 为什么不能重写 双亲委派机制
20.currenthashmap与hashmap
21.synchronized与lock的区别
22.root gc 包含什么
23.mysql二级缓存
24.bean生命周期
25.什么时候需要用到工厂模式
26.mongodb在使用过程中有什么问题
27.mysql如何实现快速查找
28.redis如何做持久化
持久化的概念:把数据放到断电也不会丢失的设备上。
**Redis实现持久化有两种方法:**RDB快照和AOF
RDB快照
redis是默认做数据持久化的,默认的方式是快照(snapshotting),把内存的数据写入本地的二进制文件dump.rdb文件中。
快照持久化实现原理:
Redis借助了fork命令的copy on write机制。在生成快照时,将当前进程fork出一个子进程,然后在子进程中循环所有的数据,将数据写成为RDB文件,使用redis的save命令调用这个过程。
快照的配置有下面三个级别,配置是在redis的配置文件中。
RDB快照持久化的优点:
1)RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如您可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题您也可以根据需求恢复到不同版本的数据集.
2). RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.
3). RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。
4). 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.
RDB快照持久化的缺点:
一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的,从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。
AOF
AOF日志的全称是append only file,从名字上我们就能看出来,它是一个追加写入的日志文件.
实现原理:
在使用aof时,redis会将每一个收到的写命令都通过write函数追加到文件中,当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容.Redis还能对AOF文件通过BGREWRITEAOF 重写,使得AOF文件的体积不至于过大.
由于AOF是把操作日志写入日志文件中,那么AOF的操作安全性如何保证?可以在配置文件中通过配置告诉redis我们想要使用fsync函数强制写入到磁盘的时间。
配置分为三种:
1). appendfsync no
当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。
2). appendfsync everysec
当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。但是当这一次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就不管会执行多长时间都会进行。这时候由于在fsync时文件描述符会被阻塞,所以当前的写操作就会阻塞。所以,结论就是,在绝大多数情况下,Redis会每隔一秒进行一次fsync。在最坏的情况下,两秒钟会进行一次fsync操作。这一操作在大多数数据库系统中被称为group commit,就是组合多次写操作的数据,一次性将日志写到磁盘。
3). appednfsync always
当设置appendfsync为always时,每一次写操作都会调用一次fsync,这时数据是最安全的,当然,由于每次都会执行fsync,所以其性能也会受到影响。
AOF的优点:
1). 使用AOF会更加持久化数据。
2). redis可以在aof文件过大时使用BGREWRITEAOF 进行重写
3). 保存的aof日志格式文件是按照redis协议的格式保存,很容易读取。
AOF的缺点:
1). 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积
2). 速度没有RDB速度快。
3). 在写入内存数据的同时将操作命令保存到日志文件,在一个并发更改上万的系统中,命令日志是一个非常庞大的数据,管理维护成本非常高,恢复重建时间会非常长,这样导致失去aof高可用性本意。另外更重要的是Redis是一个内存数据结构模型,所有的优势都是建立在对内存复杂数据结构高效的原子操作上,这样就看出aof是一个非常不协调的部分。其实aof目的主要是数据可靠性及高可用性
29.Redis为什么比MySQL快?底层原理
(1)redis是基于内存存储的,而mysql是基于磁盘存储的
(2)redis存储的是k-v格式的数据,时间复杂度是O(1)常数阶,而mysql引擎的底层实现时B+Tree,时间复杂度是O(logn)对数阶,所以redis会比mysql快一点点。
(3)Mysql数据存储是存储在表中,查找数据时要先对表进行全局扫描或根据索引查找,这涉及到磁盘的查找,磁盘查找如果是单点查找可能会快点,但是顺序查找就比较慢。而redis不用这么麻烦,本身就是存储在内存中,会根据数据在内存的位置直接取出。
(4)Redis是单线程的多路复用IO,单线程避免了线程切换的开销,而多路复用IO避免了IO等待的开销,在多核处理器下提高处理器的使用效率可以对数据进行分区,然后每个处理器处理不同的数据。
30.Redis有哪些特点?如何向Redis中存储数据?如何设置长连接?
Redis特点:
(1)速度快。Redis是用C语言实现的,所有数据存储在内存中以键值对形式保存。
(2)持久化
Redis的所有数据存储在内存中,对数据的更新将异步地保存到磁盘上。
(3)支持多种数据结构
Redis支持五种数据结构:String、List、Set、Hash、Zset
(4)支持多种编程语言。Java、php、Python、Ruby、Lua、Node.js
(5)功能丰富。除了支持五种数据结构之外,还支持事务、流水线、发布/订阅、消息队列等功能。
(6)源码简单,约23000行C语言源代码。
(7)主从复制
主服务器(master)执行添加、修改、删除,从服务器执行查询。
(8)高可用及分布式 支持高可用和分布式
如何存储数据,使用python操作数据库
长连接概念:
所谓长连接,指在一个TCP连接上可以连续发送多个数据包,在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接。
短链接概念:
短连接是指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接,即每次TCP连接只完成一对 CMPP消息的发送。
设置长连接:
修改redis.conf的tcp-keepalive=1, 默认为0,表示禁止长连接。
31.SQL优化
SQL优化一般理解是让SQL运行更快。实现MySQL优化可以从下面这些角度实现:
(1) 在查询频率高的字段建立索引和缓存,对于经常查询的数据建立索引会提升查询速度,同时把经常用的数据进行缓存,避免对数据库的多次查询减少磁盘的IO,节约时间。
(2)在where查询子语句上尽量避免对字段的NULL值判断,否则数据库引擎将会放弃索引而使用全表扫描. 如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select id from t where num=0
(3)应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
(4)应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
(5)in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
(6)下面的查询也将导致全表扫描:
select id from t where name like ‘%abc%’
(7)应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
(8)应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)=‘abc’–name以abc开头的id
应改为:
select id from t where name like ‘abc%’
(9)不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
(10)在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,
否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
(11)不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)
(12)很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
(13)并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,
如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
(14)索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
(15)尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
(16)尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间, 其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
(17)任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
(18)避免频繁创建和删除临时表,以减少系统表资源的消耗。
(19)临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
(20)在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,
以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
(21)如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
(22)尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
(23)使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
(24)与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。
在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
(25)尽量避免大事务操作,提高系统并发能力。
(26)尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
32.token和JWT的区别
结论:
最直观的:token需要查库验证token 是否有效,而JWT不用查库或者少查库,直接在服务端进行校验,并且不用查库。因为用户的信息及加密信息在第二部分payload和第三部分签证中已经生成,只要在服务端进行校验就行,并且校验也是JWT自己实现的。
TOKEN
概念: 令牌, 是访问资源的凭证。
Token的认证流程:
1). 用户输入用户名和密码,发送给服务器。
2). 服务器验证用户名和密码,正确的话就返回一个签名过的token(token 可以认为就是个长长的字符串),浏览器客户端拿到这个token。
3). 后续每次请求中,浏览器会把token作为http header发送给服务器,服务器验证签名是否有效,如果有效那么认证就成功,可以返回客户端需要的数据。
特点:
这种方式的特点就是客户端的token中自己保留有大量信息,服务器没有存储这些信息。
JWT
概念:
JWT是json web token缩写。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。
组成:
WT包含三个部分: Header头部,Payload负载和Signature签名。由三部分生成token,三部分之间用“.”号做分割。 列如 : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
1). Header 在Header中通常包含了两部分:type:代表token的类型,这里使用的是JWT类型。 alg:使用的Hash算法,例如HMAC SHA256或RSA.
{ “alg”: “HS256”, “typ”: “JWT” } 这会被经过base64Url编码形成第一部分
2). Payload token的第二个部分是荷载信息,它包含一些声明Claim(实体的描述,通常是一个User信息,还包括一些其他的元数据) 声明分三类: 1)Reserved Claims,这是一套预定义的声明,并不是必须的,这是一套易于使用、操作性强的声明。包括:iss(issuer)、exp(expiration time)、sub(subject)、aud(audience)等 2)Plubic Claims, 3)Private Claims,交换信息的双方自定义的声明 { “sub”: “1234567890”, “name”: “John Doe”, “admin”: true } 同样经过Base64Url编码后形成第二部分
3). signature 使用header中指定的算法将编码后的header、编码后的payload、一个secret进行加密。 例如使用的是HMAC SHA256算法,大致流程类似于: HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret) 这个signature字段被用来确认JWT信息的发送者是谁,并保证信息没有被修改 。
例如下面这个例子:
现在有一个接口/viptest只能是vip用户访问,我们看看服务端如何根据Token判断用户是否有效。
普通token版:
1). 查库判断是否过期
2). 查库判断时候是VIP
JWT 版本:
假如payload部分如下:
{
“exp”: 1518624000,
“isVip”: true,
“uid”:1
}
1). 解析JWT
2). 判断签名是否正确,根据生成签名时使用的密钥和加密算法,只要这一步过了就说明是payload是可信的
3). 判断JWT token是否过期,根据exp,判断是否是VIP,根据isVip
JWT版是没有查库的,他所需要的基础信息可以直接放到JWT里,服务端只要判断签名是否正确就可以判断出该用户是否可以访问该接口,当然JWT里的内容也不是无限多的,其他更多的信息我们就可以通过id去查数据库
33.阿里云oss上存储了什么东西
阿里云对象存储服务(Object Storage Service,简称OSS)为您提供基于网络的数据存取服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种非结构化数据文件。
阿里云OSS将数据文件以对象(object)的形式上传到存储空间(bucket)中。您可以进行以下操作:
1). 创建一个或者多个存储空间,向每个存储空间中添加一个或多个文件。
2). 通过获取已上传文件的地址进行文件的分享和下载。
3). 通过修改存储空间或文件的属性或元信息来设置相应的访问权限。
4). 在阿里云管理控制台执行基本和高级OSS任务。
5). 使用阿里云开发工具包或直接在应用程序中进行RESTful API调用执行基本和高级OSS任务。
34.不同应用服务器,session怎么共享.docx
主要可以考虑下面几个方法,每个方法都有优缺点,具体实施时根据业务选择:
1).通过数据库mysql共享session
a.采用一台专门的mysql服务器来存储所有的session信息。
用户访问随机的web服务器时,会去这个专门的数据库服务器check一下session的情况,以达到session同步的目的。
缺点就是:依懒性太强,mysql服务器无法工作,影响整个系统;
b.将存放session的数据表与业务的数据表放在同一个库。如果mysql做了主从,需要每一个库都需要存在这个表,并且需要数据实时同步。
缺点:用数据库来同步session,会加大数据库的负担,数据库本来就是容易产生瓶颈的地方,如果把session还放到数据库里面,无疑是雪上加霜。上面的二种方法,第一点方法较好,把放session的表独立开来,减轻了真正数据库的负担 。但是session一般的查询频率较高,放在数据库中查询性能也不是很好,不推荐使用这种方式。
2).通过cookie共享session
把用户访问页面产生的session放到cookie里面,就是以cookie为中转站。
当访问服务器A时,登录成功之后将产生的session信息存放在cookie中;当访问请求分配到服务器B时,服务器B先判断服务器有没有这个session,如果没有,在去看看客户端的cookie里面有没有这个session,如果cookie里面有,就把cookie里面的sessoin同步到web服务器B,这样就可以实现session的同步了。
缺点:cookie的安全性不高,容易伪造、客户端禁止使用cookie等都可能造成无法共享session。
3).通过服务器之间的数据同步session
使用一台作为用户的登录服务器,当用户登录成功之后,会将session写到当前服务器上,我们通过脚本或者守护进程将session同步到其他服务器上,这时当用户跳转到其他服务器,session一致,也就不用再次登录。
缺陷:速度慢,同步session有延迟性,可能导致跳转服务器之后,session未同步。而且单向同步时,登录服务器宕机,整个系统都不能正常运行。
4).通过NFS共享Session
选择一台公共的NFS服务器(Network File Server)做共享服务器,所有的Web服务器登陆的时候把session数据写到这台服务器上,那么所有的session数据其实都是保存在这台NFS服务器上的,不论用户访问那太Web服务器,都要来这台服务器获取session数据,那么就能够实现共享session数据了。
缺点:依赖性太强,如果NFS服务器down掉了,那么大家都无法工作了,当然,可以考虑多台NFS服务器同步的形式。
5).通过memcache同步session
memcache可以做分布式,如果没有这功能,他也不能用来做session同步。他可以把web服务器中的内存组合起来,成为一个"内存池",不管是哪个服务器产生的sessoin都可以放到这个"内存池"中,其他的都可以使用。
优点:以这种方式来同步session,不会加大数据库的负担,并且安全性比用cookie大大的提高,把session放到内存里面,比从文件中读取要快很多。
缺点:memcache把内存分成很多种规格的存储块,有块就有大小,这种方式也就决定了,memcache不能完全利用内存,会产生内存碎片,如果存储块不足,还会产生内存溢出。
6).通过redis共享session
redis与memcache一样,都是将数据放在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
根据实际开发应用,一般选择使用memcache或redis方式来共享session.
35.存储过程和触发器是什么?
存储过程简介
我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。
注意: 存储过程跟触发器有点类似,都是一组SQL集,但是存储过程是主动调用的,且功能比触发器更加强大,触发器是某件事触发后自动调用。
存储过程的特征:
1). 有输入输出参数,可以声明变量,有if/else, case,while等控制语句,通过编写存储过程,可以实现复杂的逻辑功能;
2). 函数的普遍特性:模块化,封装,代码复用;
3). 速度快,只有首次执行需经过编译和优化步骤,后续被调用可以直接执行,省去以上步骤;
存储过程的使用场景:存储过程处理比较复杂的业务时比较实用。
复杂的业务逻辑需要多条 SQL 语句。这些语句要分别地从客户机发送到服务器,当客户机和服务器之间的操作很多时,将产生大量的网络传输。如果将这些操作放在一个存储过程中,那么客户机和服务器之间的网络传输就会大大减少,降低了网络负载。
存储过程的优点:
1). 增强了sql语言的功能和灵活性。存储过程可以使用流控制语句进行编写,有很强的灵活性也可以完成复杂的计算。
2). 存储过程允许标准组件是编程。存储过程被创建后,可以在程序中被多次调用,而不必重新编写该存储过程的SQL语句。而且数据库专业人员可以随时对存储过程进行修改,对应用程序源代码毫无影响。
3). 存储过程能实现较快的执行速度。如果某一操作包含大量的Transaction-SQL代码或分别被多次执行,那么存储过程要比批处理的执行速度快很多。因为存储过程是预编译的。在首次运行一个存储过程时查询,优化器对其进行分析优化,并且给出最终被存储在系统表中的执行计划。而批处理的Transaction-SQL语句在每次运行时都要进行编译和优化,速度相对要慢一些。
存储过程的缺点:
1). 消耗内存:如果使用大量存储过程,那么使用这些存储过程的每个连接的内存使用量将会大大增加;此外,如果您在存储过程中过度使用大量逻辑操作,则CPU使用率也会增加,因为数据库服务器的设计不当于逻辑运算。
2). 难维护和难调试:mysq不支持存储过程的调试;存储过程学习难度大只是部分DBA的必备开发技能,对于普通的使用者维护难度加大。
3). 语法不支持跨数据库使用:Mysql和Oracle的存储过程的语法不通用,学习成本增加。
存储过程的使用:创建、调用
解释: MySQL默认以";"为分隔符,如果没有声明分割符,则编译器会把存储过程当成SQL语句进行处理,因此编译过程会报错,所以要事先用“DELIMITER ” 声 明 当 前 段 分 隔 符 , 让 编 译 器 把 两 个 " ”声明当前段分隔符,让编译器把两个" ”声明当前段分隔符,让编译器把两个""之间的内容当做存储过程的代码,不会执行这些代码;“DELIMITER ;”的意为把分隔符还原。
刚才创建存储过程hi()时发现是函数,可以往里传入参数,参数共有三种:INT,OUT,INOUT.
• IN参数的值必须在调用存储过程时指定,在存储过程中修改该参数的值不能被返回,为默认值
• OUT:该值可在存储过程内部被改变,并可返回
• INOUT:调用时指定,并且可被改变和返回
以下案例仅供参考:
IN 案例
OUT 案例
ONOUT 例子
36.订单支付时数据库数据是否会减少?
在创建订单时数据库中的商品数据已经减少,在订单未支付时数据库中的商品数据是没变化,只是把此订单中商品的信息展示而已
37.多线程在项目中的使用?项目中多线程同时操作某段代码怎么处理?
多线程一般在使用在进行I0操作时,基于这个结论,提供以下几个使用场景:
1). 比如一个业务逻辑需要并行的操作几个文件的读写,还得是同步执行,不能异步执行,这时候就可以开启多线程来读写这几个文件
2). 视图中需要请求多个第三方接口,仍然也是要求同步的,不能异步,这时候也可以用多线程去并行请求多个第三方接口
3). 比如在订单系统中,订单提交后就要修改商品的库存、商品的销量等这样的操作。
实现:
方法1:可以使用锁把这段代码锁住,让多线程排队依次操作这段代码。但是这个方法效率比较低。
方法2:使用乐观锁,对于上个线程处理后通知其他线程来执行这段代码
38.高并发分布式实际使用的经验
高并发:系统同时并行处理的请求数量。
web网站都是追求在同时尽可能多的处理请求,我们可以从以下几个方面考虑尽可能提高网站的并发量:
(1) 购买硬件提高服务器配置
(2)减少数据库访问次数,
(3)文件和数据库分离(比如页面静态化),
(4)大数据分布式存储(比如:Mysql的主从配置读写分离),
(5)服务器的集群负载均衡,
(6)页面缓存的使用(对于页面使用局部页面缓存或数据缓存),
(7)nosql内存数据库代替关系型数据库等
高并发项目中的例子:
1). 省市区的三级联动的数据,这些我们经常使用却不经常变化的数据,我们一般是一次全部读取出来缓存到web服务器的本地进行保存,假如有更新就重新读取;
2). 对于首页商品经常变化的数据使用redis进行缓存,比如说广告,可以使用缓存把广告查询出来的数据进行缓存,对于首页展示广告的部位使用局部缓存。来提高页面响应的速度。
分布式的案例
1).项目中MySQL主从配置读写分离
在项目中为了提高数据库的查询速度,我们使用主从配置来实现MySQL的读写分离。但是虽然我们这么做能提高一部分的性能,假如用户的请求依旧很多,还是有访问的瓶颈,所以建议使用基于内存的非关系型数据库,比如Mongo或Redis.
39.工作中对数据库设计有什么经验
以下描述仅仅是个人经验总结,仅供参考!!!
我认为数据库设计分为库和表的设计,所以从这两方面着手介绍。
库的设计
(1)数据库名称要明确,可以加前缀或后缀的方式,使其看起来有业务含义,比如数据库名称可以为Business_DB(业务数据库)。
(2)在一个企业中,如果依赖很多产品,但是每个产品都使用同一套用户,那么应该将用户单独构建一个库,叫做企业用户中心。
(3)不同类型的数据应该分开管理,例如,财务数据库,业务数据库等。
(4)由于存储过程在不同的数据库中,支持方式不一样,因此不建议过多使用和使用复杂的存储过程。为数据库服务器降低压力,不要让数据库处理过多的业务逻辑,将业务逻辑处理放到应用程序中。
表设计
1). 数据库表命名,将业务和基础表区分,采用驼峰表示法等。
2). 数据不要物理删除,应该加一个标志位,以防用户后悔时,能够恢复 。
3). 添加时间,有添加时间可以明确知道记录什么时候添加的
4). 修改时间,可以知道记录什么时候被修改了,一旦数据出现问题,可以根据修改时间来定位问题。比如某人在这个时间做了哪些事。
5). 基本表及其字段之间的关系, 应尽量满足第三范式。但是,满足第三范式的数据库设计,往往不是 最好的设计。为了提高数据库的运行效率,常常需要降低范式标准:适当增加冗余,达到以空间换时间 的目的
6). 若两个实体之间存在多对多的关系,则应消除这种关系。消除的办法是,在两者之间增加第三个实 体。这样,原来一个多对多的关系,现在变为两个一对多的关系。要将原来两个实体的属性合理地分配 到三个实体中去
7).对于主键的设计:
1) 不建议用多个字段做主键,单个表还可以,但是关联关系就会有问题,主键自增是高性能的。导入导出就有问题
2) 一般情况下,如果有两个外键,不建议采用两个外键作为联合主建,另建一个字段作为主键。除非这条记录没有逻辑删除标志,且该表永远只有一条此联合主键的记录。
3)一般而言,一个实体不能既无主键又无外键。在E—R 图中, 处于叶子部位的实体, 可以定义主键,也可以不定义主键(因为它无子孙), 但必须要有外键(因为它有父亲)。
40.后台站点的用户日活跃度如何统计?除了记录用户的登录时间进行筛选,还有没有别的方法
用户的日活跃度用处:
主要是给运营人员来使用, 用户网站的优化或者网站的推送(SEO)
实现:
维护一张用户表,里面有4列:guid, starttime, endtime, num,分别是用户的guid,第一次访问时间,最后一次访问时间,访问天数;
表中guid是唯一的; 把当天数据去重后,与库中得历史记录进行join,如果guid在历史库出现过,则将endtime更新为当天时间,num加一; 否则,这是一个新用户,插入历史库,starttime, endtime都为当天时间,num初始值为1。
只要num变化就说明用户是活跃得。
1). 根据uid,去重,结合用户的历史表,存在就是一个老用户: 就把startime,更新为登录的时间,endtime就记录为退出登录的时间; num +1;
2). 根据用户id,是新的用户,就把starttime记录为刚才注册的时间,把endtime记录为退出的时间。num+1;
41.抢购时的并发如何处理
抢购系统也就是秒杀系统,要处理秒杀系统的的并发问题,并不是某一个方面就能实现的,要多方面采取措施来实现。首先我们要清楚,高并发发生在哪些部分,然后正对不同的模块来进行优化。一般会发生在下图红色部分。
如何解决上面问题呢?主要从两个方面入手:
(1)将请求尽量拦截在系统上游(不要让锁冲突落到数据库上去)。传统秒杀系统之所以挂,请求都压倒了后端数据层,数据读写锁冲突严重,并发高响应慢,几乎所有请求都超时,流量虽大,下单成功的有效流量甚小。以12306为例,一趟火车其实只有2000张票,200w个人来买,基本没有人能买成功,请求有效率为0。
(2)充分利用缓存,秒杀买票,这是一个典型的读多写少的应用场景,大部分请求是车次查询,票查询,下单和支付才是写请求。一趟火车其实只有2000张票,200w个人来买,最多2000个人下单成功,其他人都是查询库存,写比例只有0.1%,读比例占99.9%,非常适合使用缓存来优化。好,后续讲讲怎么个“将请求尽量拦截在系统上游”法,以及怎么个“缓存”法,讲讲细节.
具体实施可以从几个方面考虑:
前端方面
1). 把详情页部署到CDN节点上
用户在秒杀开始之前,会对详情页做大量的刷新操作。 所以我们将详情页部署到CDN上,CDN将详情页做静态化处理。 这样其实用户访问的静态页的html已经不在秒杀系统上了,而在CDN节点上。
2. 禁止重复提交请求、对用户请求限流
(a)禁止重复提交
用户点击“查询”或者“购票”后,按钮置灰,禁止用户重复提交请求;
(b)用户限流
限制用户在x秒之内只能提交一次请求;
后端方面
(1)根据用户id限制访问频率,对同样请求返回同一个页面
秒杀活动都会要求用户进行登录, 对于同一个UID在X秒内发来的多次请求进行计数或者去重,限制在X秒内只有一个请求到达后端接口,其余的请求怎么办呢?使用页面缓存,X秒内到达后端接口的请求,均返回同一页面。如此限流,既能保证用户有良好的用户体验(没有返回404)又能保证系统的健壮性(利用页面缓存,把请求拦截在站点层了)。
(2)服务层进行优化
(a)采用消息队列缓存请求:既然服务层知道库存只有100台手机,那完全没有必要把100W个请求都传递到数据库,那么可以先把这些请求都写到消息队列缓存一下,数据库层订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。
(b) 利用缓存应对读请求:对类似于12306等购票业务,是典型的读多写少业务,大部分请求是查询请求,所以可以利用缓存(Redis/Memcached)分担数据库压力。
© 利用缓存应对写请求: 缓存也是可以应对写请求的,比如我们就可以把数据库中的库存数据转移到Redis缓存中,所有减库存操作都在Redis中进行,然后再通过后台进程把Redis中的用户秒杀请求同步到数据库中。
业务方面
1). 分时分段销售,将流量均摊
例如,原来统一10点,现在8点, 9点,…每隔半个小时放出一批
2). 请求多时,对数据粒度进行缓存
以12306购票为例子,你去购票,对于余票查询这个业务,票剩了58张,还是26张,你真的关注么,其实我们只关心有票和无票?流量大的时候,做一个粗粒度的“有票”“无票”缓存即可
3). 业务逻辑的异步处理
例如下单业务与支付业务的分离,下单成功后可以将支付业务单独异步处理,与抢购的业务分离,减少对业务处理的压力。
42.如何根据用户历史浏览记录进行推荐?
基于用户行为的商品推荐、基于协同过滤的推荐系统。
实现“打包销售”
主要思路如上图:
1). 根据用户正在浏览或添加到购物车的商品的id,来过滤出购买过改商品的用户
2). 根据购买过该商品的用户集合,在用户集合中根据每个用户的订单信息,查找出该用户的订单商品信息,及用户的历史商品浏览信息
3). 根据所有用户的商品信息,找到所有的商品,然后对商品按照商品的销量进行排序,把排名靠前的商品,写入缓存或者数据库
4). 当用户登录时,根据用户的id,把上一步写入缓存或数据库的商品信息,取出,推送给用户。
43.如何设置待支付倒计时30分钟?
这实现起来方法很多,下面仅提供思路:
1). 在创建订单时,添加额外字段,记录订单的创建时间。当用户进入此订单页面时就显示倒计时。前端获取当前时间,然后减去订单的创建时间,如果大于30分钟就取消订单,取消订单就修改商品的库存和销量; 如果没有超时就继续倒计时。
2). 可以使用定时任务,在创建订单时就生成一个定时任务,设定为30分钟后用户未支付就取消订单,取消订单就把商品的库存和销量做相应的修改。
上面2中方案没有考虑取消订单后商品的回库。
3). 使用异步任务实现。在生成订单时同时调用spring的定时器,只要传入30分钟的时间,时间到了订单未支付,就取消订单。把异步任务和事务搭配使用,没有修改库存成功就回滚事务。
44.说一下TCP的三次握手客户端在访问服务器的什么?在三次握手中服务器内部发生了什么事情?
建立起一个TCP连接需要经过“三次握手”:
1) Client首先发送一个连接试探,ACK=0 表示确认号无效,SYN = 1 表示这是一个连接请求或连接接受报文,同时表示这个数据报不能携带数据,seq = x 表示Client自己的初始序号(seq = 0 就代表这是第0号帧),这时候Client进入syn_sent状态,表示客户端等待服务器的回复
2) Server监听到连接请求报文后,如同意建立连接,则向Client发送确认。TCP报文首部中的SYN 和 ACK都置1 ,ack = x + 1表示期望收到对方下一个报文段的第一个数据字节序号是x+1,同时表明x为止的所有数据都已正确收到(ack=1其实是ack=0+1,也就是期望客户端的第1个帧),seq = y 表示Server 自己的初始序号(seq=0就代表这是服务器这边发出的第0号帧)。这时服务器进入syn_rcvd,表示服务器已经收到Client的连接请求,等待client的确认。
3) Client收到确认后还需再次发送确认,同时携带要发送给Server的数据。ACK 置1 表示确认号ack= y + 1 有效(代表期望收到服务器的第1个帧),Client自己的序号seq= x + 1(表示这就是我的第1个帧,相对于第0个帧来说的),一旦收到Client的确认之后,这个TCP连接就进入Established状态,就可以发起http请求了。
注意:
1). 三次握手,并没有传递数据,建立连接后才进入到数据传输的状态。
2). SYN(synchronous)是TCP/IP建立连接时使用的握手信号。在客户机和服务器之间建立正常的TCP网络连接时,客户机首先发出一个SYN消息,服务器使用SYN+ACK应答表示接收到了这个消息,最后客户机再以ACK消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。
45.添加购物车时商品库存是否减少
没有,仅仅是把商品信息添加到cookie或redis中,没有对商品的数量进行更改
对于登录的用户:把用户的id和商品的id,添加到购物车中, redis;此时未对 商品的库存进行操作;
对于匿名用户:
商品的id及商品信息写入到cookie中,也未对商品的库存进行操作。
46.图片管理为什么使用FastDFS
FastDFS比七牛云等云端存储会便宜一些。
FastDFS是开源的轻量级分布式文件存储系统。它解决了大数据量存储和负载均衡等问题。特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。
优势:
1). 只能通过专用的API访问,不支持posix,降低了系统的复杂度,处理效率高
2). 支持在线扩容,增强系统的可扩展性
3). 支持软RAID,增强系统的并发处理能力及数据容错能力。Storage是按照分组来存储文件,同组内的服务器上存储相同的文件,不同组存储不同的文件。Storage-server之间不会互相通信。
4). 主备Tracker,增强系统的可用性。
5). 支持主从文件,支持自定义扩展名
6).文件存储不分块,上传的文件和os文件系统中的文件一一对应。
7). 相同内容的文件只存储一份,节约磁盘存储空间。对于上传相同的文件会使用散列方式处理文件内容,假如是一致就不会存储后上传的文件,只是把原来上传的文件在Storage中存储的id和ULR返回给客户端。
47.为什么使用页面静态化不使用缓存
页面静态化是将数据库静态化到页面,客户端访问不需要查询数据库,主要存放形式是静态化文件资源,存储于硬盘;用户请求的是指定目录下的静态页面,页面中有数据。
缓存是将数据存储于服务器内存,二者存放位置和形式不一样。
这二者使用主要看业务场景及网站优化的点,比如说秒杀的时候,肯会都要把也页面进行静态化,放到CDN上面,这样能在前端就能抗住大量的并发请求;但是在广告页面的广告数据我们就可以使用页面缓存来实现,同样不用对数据库进行查询,只要访问内存就行。
48.项目中redis宕机如何解决
在主从模式下宕机要分为区分来看:(主从复制,冷热备份)
(1)slave从redis宕机
在Redis中从库重新启动后会自动加入到主从架构中,自动完成同步数据;
如果从数据库实现了持久化,只要重新假如到主从架构中会实现增量同步。
(2)Master 宕机
假如主从都没数据持久化,此时千万不要立马重启服务,否则可能会造成数据丢失,正确的操作如下:
1). 在slave数据上执行SLAVEOF ON ONE,来断开主从关系并把slave升级为主库
2). 此时重新启动主数据库,执行SLAVEOF,把它设置为从库,自动备份数据。
以上过程很容易配置错误,可以使用简单的方法:redis的哨兵(sentinel)的功能。
哨兵(sentinel)的原理:
Redis提供了sentinel(哨兵)机制通过sentinel模式启动redis后,自动监控master/slave的运行状态,基本原理是:心跳机制+投票裁决。
每个sentinel会向其它sentinal、master、slave定时发送消息,以确认对方是否“活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的“主观认为宕机” Subjective Down,简称SDOWN)。
若"哨兵群"中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称ODOWN),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。
补充:
哨兵的配置:
1). 复制redis中sentinel.conf,根据情况进行配置
可以使用哨兵搭建高可用服务器,哨兵模式还提供其他功能,比如监控、通知、为客户端提供配置等。
49.消息队列如何理解?
从以下几个方面回答此问题:
什么是消息队列?
“消息队列”是在消息的传输过程中保存消息的容器。
“消息”是在两台计算机间传送的数据单位。消息可以非常简单,例如只包含文本字符串;也可以更复杂,可能包含嵌入对象。
消息被发送到队列中。“消息队列”是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中传到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。
为什么需要消息队列?
主要原因是由于在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达MySQL,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。通过使用消息队列,我们可以异步处理请求,从而缓解系统的压力。
总结:消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。目前在生产环境,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ等
应用的场景:
常用的使用场景:异步处理,应用解耦,流量削锋和消息通讯等业务场景。
以异步处理应用场景为例:用户注册后,需要发注册邮件和注册短信,传统的做法由两种:串行方式、并行方式。
(1)串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。
(2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。
假设三个业务节点每个使用50毫秒钟,不考虑网络等其他开销,则串行方式的时间是150毫秒,并行的时间可能是100毫秒。
因为CPU在单位时间内处理的请求数是一定的,假设CPU1秒内吞吐量是100次。则串行方式1秒内CPU可处理的请求量是7次(1000/150)。并行方式处理的请求量是10次(1000/100)。
如以上案例描述,传统的方式系统的性能(并发量,吞吐量,响应时间)会有瓶颈。如何解决这个问题呢?
使用消息队列,把不是必须的业务逻辑,异步处理。
按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍。
消息队列的缺点:
**系统可用性降低:**系统引入的外部依赖越多,越容易挂掉,例如A只要调用BCD的接口就好了,但是加入MQ后,万一MQ挂掉,整个系统就不能使用了。
**系统复杂性提高:**硬生生加个MQ进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?
**一致性问题:**A系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是BCD三个系统那里,BD两个系统写库成功了,结果C系统写库失败了,咋整?你这数据就不一致了
50.悲观锁,乐观锁,分布式锁,都有什么技巧?
悲观锁是判断在前,使用在后
java中的重量级锁 synchronize
数据库行锁
乐观锁是使用在前,判断在后
java中的轻量级锁 volatile和CAS
数据库版本号
分布式锁(redis锁)
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制.乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition 机制的其实都是提供的乐观锁.
简而言之,就是使用在前,判断在后,下面看一段伪代码
悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 block 直到它拿到锁.传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁.
简而言之就是判断在前,使用在后,依旧我们来看一段伪代码
扣减操作案例
这里举一个非常常见的例子,在高并发情况下余额扣减,或者类似商品库存扣减,也可以是资金账户的余额扣减。扣减操作会发生什么问题呢?很容易可以看到,可能会发生的问题是扣减导致的超卖,也就是扣减成了负数。
举个例子,比如我的库存数据只有100个。并发情况下第1笔请求卖出100个,第2批卖出100元,导致当前的库存数量为负数。遇到这种场景应该如何破解呢?这里列举四种方案。
方案1:同步排它锁
这时候很容易想到最简单的方案:同步排它锁(synchronize)。但是排他锁的缺点很明显:
其中一个缺点是,线程串行导致的性能问题,性能消耗比较大。
另一个缺点是无法解决分布式部署情况下跨进程问题;
方案2:数据库行锁
第二我们可能会想到,那用数据库行锁来锁住这条数据,这种方案相比排它锁解决了跨进程的问题,但是依然有缺点。
其中一个缺点就是性能问题,在数据库层面会一直阻塞,直到事务提交,这里也是串行执行;
第二个需要注意设置事务的隔离级别是Read Committed,否则并发情况下,另外的事务无法看到提交的数据,依然会导致超卖问题;
缺点三是容易打满数据库连接,如果事务中有第三方接口交互(存在超时的可能性),会导致这个事务的连接一直阻塞,打满数据库连接。
最后一个缺点,容易产生交叉死锁,如果多个业务的加锁控制不好,就会发生AB两条记录的交叉死锁。
方案3:redis分布式锁
前面的方案本质上是把数据库当作分布式锁来使用,所以同样的道理,redis,zookeeper都相当于数据库的一种锁,其实当遇到加锁问题,代码本身无论是synchronize或者各种lock使用起来都比较复杂,所以思路是把代码处理一致性的问难题交给一个能够帮助你处理一致性的问题的专业组件,比如数据库,比如redis,比如zookeeper等。
这里我们分析下分布式锁的优缺点:
优点:
可以避免大量对数据库排他锁的征用,提高系统的响应能力;
缺点:
设置锁和设置超时时间的原子性;
不设置超时时间的缺点;
服务宕机或线程阻塞超时的情况;
超时时间设置不合理的情况;
加锁和过期设置的原子性
redis加锁的命令setnx,设置锁的过期时间是expire,解锁的命令是del,但是2.6.12之前的版本中,加锁和设置锁过期命令是两个操作,不具备原子性。如果setnx设置完key-value之后,还没有来得及使用expire来设置过期时间,当前线程挂掉了或者线程阻塞,会导致当前线程设置的key一直有效,后续的线程无法正常使用setnx获取锁,导致死锁。
针对这个问题,redis2.6.12以上的版本增加了可选的参数,可以在加锁的同时设置key的过期时间,保证了加锁和过期操作原子性的。
但是,即使解决了原子性的问题,业务上同样会遇到一些极端的问题,比如分布式环境下,A获取到了锁之后,因为线程A的业务代码耗时过长,导致锁的超时时间,锁自动失效。后续线程B就意外的持有了锁,之后线程A再次恢复执行,直接用del命令释放锁,这样就错误的将线程B同样Key的锁误删除了。代码耗时过长还是比较常见的场景,假如你的代码中有外部通讯接口调用,就容易产生这样的场景。
设置合理的时长
刚才讲到的线程超时阻塞的情况,那么如果不设置时长呢,当然也不行,如果线程持有锁的过程中突然服务宕机了,这样锁就永远无法失效了。同样的也存在锁超时时间设置是否合理的问题,如果设置所持有时间过长会影响性能,如果设置时间过短,有可能业务阻塞没有处理完成,是否可以合理的设置锁的时间?
续命锁
这是一个很不容易解决的问题,不过有一个办法能解决这个问题,那就是续命锁,我们可以先给锁设置一个超时时间,然后启动一个守护线程,让守护线程在一段时间之后重新去设置这个锁的超时时间,续命锁的实现过程就是写一个守护线程,然后去判断对象锁的情况,快失效的时候,再次进行重新加锁,但是一定要判断锁的对象是同一个,不能乱续。
同样,主线程业务执行完了,守护线程也需要销毁,避免资源浪费,使用续命锁的方案相对比较而言更复杂,所以如果业务比较简单,可以根据经验类比,合理的设置锁的超时时间就行。
方案4:数据库乐观锁
数据库乐观锁加锁的一个原则就是尽量想办法减少锁的范围。锁的范围越大,性能越差,数据库的锁就是把锁的范围减小到了最小。我们看下面的伪代码
我们可以看到修改前的代码是没有where条件的。修改后,再加where条件判断:总库存大于将被扣减的库存。
如果更新条数返回0,说明在执行过程中被其他线程抢先执行扣减,并且避免了扣减为负数。
但是这种方案还会涉及一个问题,如果在之前的update代码中,以及其他的业务逻辑中还有一些其他的数据库写操作的话,那这部分数据如何回滚呢?
我的建议是这样的,你可以选择下面这两种写法:
利用事务回滚写法:
我们先给业务方法增加事务,方法在扣减库存影响条数为零的时候扔出一个异常,这样对他之前的业务代码也会回滚。
reduce()
{
select total_amount from table_1
if(total_amount < amount ){
return failed.
}
//其他业务逻辑
int num = update total_amount = total_amount - amount where total_amount > amount; if(num==0) throw Exception;}
第二种写法
reduce()
{
//其他业务逻辑
int num = update total_amount = total_amount - amount where total_amount > amount; if(num ==1 ){
//业务逻辑.
} else{ throw Exception; }
首先执行update业务逻辑,如果执行成功了再去执行逻辑操作,这种方案是我相对比较建议的方案。在并发情况下对共享资源扣减操作可以使用这种方法,但是这里需要引出一个问题,比如说万一其他业务逻辑中的业务,因为特殊原因失败了该怎么办呢?比如说在扣减过程中服务OOM了怎么办?
我只能说这些非常极端的情况,比如突然宕机中间数据都丢了,这种极少数的情况下只能人工介入,如果所有的极端情况都考虑到,也不现实。我们讨论的重点是并发情况下,共享资源的操作如何加锁的问题。
总结
最后我来给你总结一下,如果你可以非常熟练的解决这类问题,第一时间肯定想到的是:数据库版本号解决方案或者分布式锁的解决方案;但是如果你是一个初学者,相信你一定会第一时间考虑到Java中提供的同步锁或者数据库行锁。
50.单例模式的七种写法
1.饿汉模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
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;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
4.双重检查模式(DCL)
public class Singleton {
private volatile static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance== null) {
synchronized (Singleton.class) {
if (instance== null) {
instance= new Singleton();
}
}
}
return singleton;
}
}
5.静态内部单例模式
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
6.枚举单例
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}
7.使用容器实现单例模式
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;
}
}