人不行别怪路不平!万丈高楼平地起,要想辉煌靠自己。

1.final 在 java 中有什么作用?

final 修饰的类叫最终类,该类不能被继承。
final 修饰的方法不能被重写。
final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

2.jdk jvm jre 区别

JDK(Java SE Development Kit),Java标准开发包,它提供了编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等。
JRE( Java Runtime Environment) 、Java运行环境,用于解释执行Java的字节码文件。普通用户而只需要安装 JRE(Java Runtime Environment)来运行 Java 程序。而程序开发者必须安装JDK来编译、调试程序。
  JVM(Java Virtual Mechinal),Java虚拟机,是JRE的一部分。它是整个java实现跨平台的最核心的部分,负责解释执行字节码文件,是可运行java字节码文件的虚拟计算机。所有平台的上的JVM向编译器提供相同的接口,而编译器只需要面向虚拟机,生成虚拟机能识别的代码,然后由虚拟机来解释执行。
  当使用Java编译器编译Java程序时,生成的是与平台无关的字节码,这些字节码只面向JVM。不同平台的JVM都是不同的,但它们都提供了相同的接口。JVM是Java程序跨平台的关键部分,只要为不同平台实现了相应的虚拟机,编译后的Java字节码就可以在该平台上运行。
区别与联系
JDK 用于开发,JRE 用于运行java程序 ;如果只是运行Java程序,可以只安装JRE,无序安装JDK。
JDk包含JRE,JDK 和 JRE 中都包含 JVM。
JVM 是 java 编程语言的核心并且具有平台独立性。

4.Eureka与zookeeper的区别 !

分布式系统的三个指标 1、Consistency 一致性 2、Availability 可用性 3、Partition tolerance 分区容错性

著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。在此Zookeeper保证的是CP, 而Eureka则是AP。

zookeeper也可以作为注册中心,用于服务治理(zookeeper还有其他用途,例如:分布式事务锁等) 每启动一个微服务,就会去zk中注册一个临时子节点,每当有一个服务down机,由于是临时接点,此节点会立即被删除,并通知订阅该服务的微服务更新服务列表 (zk上有watch,每当有节点更新,都会通知订阅该服务的微服务更新服务列表) 每当有一个新的微服务注册进来,就会在对应的目录下创建临时子节点,并通知订阅该服务的微服务更新服务列表 , 每个微服务30s向zk获取新的服务列表

每一个微服务中都有eureka client,用于服务的注册于发现 (服务的注册:把自己注册到eureka server) (服务的发现:从eureka server获取自己需要的服务列表) 每一个微服务启动的时候,都需要去eureka server注册 当A服务需要调用B服务时,需要从eureka服务端获取B服务的服务列表,然后把列表缓存到本地,然后根据ribbon的客户端负载均衡规则,从服务列表中取到一个B服务,然后去调用此B服务 当A服务下次再此调用B服务时,如果发现本地已经存储了B的服务列表,就不需要再从eureka服务端获取B服务列表,直接根据ribbon的客户端负载均衡规则,从服务列表中取到一个B服务,然后去调用B服务 微服务,默认每30秒,就会从eureka服务端获取一次最新的服务列表 如果某台微服务down机,或者添加了几台机器, 此时eureka server会通知订阅他的客户端,并让客户端更新服务列表, 而且还会通知其他eureka server更新此信息 心跳检测,微服务每30秒向eureka server发送心跳, eureka server若90s之内都没有收到某个客户端的心跳,则认为此服务出了问题, 会从注册的服务列表中将其删除,并通知订阅它的客户端更新服务列表, 而且还会通知其他eureka server更新此信息 eureka server保护机制,通过打卡开关,可以让eureka server处于保护状态,主要是用于某eureka server由于网络或其他原因,导致接收不到其他微服务的心跳,此时不能盲目的将其他微服务从服务列表中删除。 具体规则:如果一段时间内,85%的服务都没有发送心跳,则此server进入保护状态,此状态下,可以正常接受注册,可以正常提供查询服务,但是不与其他server同步信息,也不会通知订阅它的客户端,这样就不会误杀其他微服务

5.SpringCloudHystrix熔断和降级有什么区别?

服务熔断
  服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
服务降级
  服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。
熔断VS降级
  相同点:
    目标一致 都是从可用性和可靠性出发,为了防止系统崩溃;
    用户体验类似 最终都让用户体验到的是某些功能暂时不可用;
  不同点:
    触发原因不同 服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
    管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始);

6.解决session共享方式主要分为两种。

解决方案:
1.最原始的解决方法是session的共享或者叫session的复制,那么什么叫session共享呢?就是当一台服务器中有session了,那么同时也会给其余的服务器复制相同的一份session,让这些服务器有同样的session。但是这种方法有一个问题,在session复制的时候就会产生一个性能的问题,如果用户数量越多session就是复制得越频发,还有就是你服务器集群的数量越多session复制得也越多。本质上就是你的使用量越多,session的复制也就迅速增长,我们把这个叫做session复制风暴,他会对我们的服务器性能产生很大的影响,所以这种策略我们是不采用的
2.第二种方法是最合适的,但是我们还需要一台服务器, 这台服务器我们需要部署非关系型数据库redis, redis是一个用c语言编写开源的key-value存储系统(nosql),学过java的应该都知道java集合这章中的map吧,map也是key-value(键值对)的形式存储数据的吧,redis和这个是类似的。属于非关系型数据库。现在redis是一个比较火的技术,在redis中他可以把session做一个接管,也就是redis可以和我们的前台这些服务器做一个整合,那么如何整合呢?回到我们上面假设的原始情景中,当互联网用户输入信息点击登录后,我们的请求经过F5和nginx来到前台服务器1,此时服务器1中当前用户session不是服务器1自己创建和管理的而是交给redis服务器来创建和管理。当用户再次点击下载链接时,请求通过F5和nginx被传到了服务器2上,然后服务器2从redis服务器中获取对应的session,判断当前发送请求的用户是否已经登录,这样的话这个问题就很好的解决了。也不会出现session复制风暴的问题。Session服务器也是可以做集群和分布式部署的(包括redis数据库分页,读写分离,主从复制。主负责写,从负责读)。
nginx服务器有两种能力,第一种就是反向代理,刚才已经讲过了。第二种是负载均衡的能力。第三种是静态文件处理能力,这个我们接下来具体讲解。
静态文件处理,如果我们的静态页面即给访问者浏览的页面放在前台服务器上,那么前台服务器处理静态页面的能力是不够的
所以我们不能把静态页面发布到前台服务器(portal)中,我们得发布到nginx服务器上。那么发布到nginx的时候我们是从管理员后台(console)服务器进行发布的,那么我们如何在从管理员后台(console)服务器把静态页面和图片发布到nginx服务器上呢?我们之前做发布的时候是使用webservice来做的,那之前webservice是部署在portal里的,现在如果要把静态页面和图片发布到nginx上,就需要在nginx部署webservice,但是nginx是无法部署webservice的。我们要在console管理员后台和nginx之间做一个静态化发布
第一种方案:使用共享服务器rsingle,console和nginx两台机器上,都有 一个文件夹相互做同步,只要console把静态文件放到自己机器的此文件夹中同时也会同步到对应的nginx机器的文件夹上。
第二种方案:在nginx安装一个tomcat服务,部署webservice服务war包,这个war的功能是专门用来接收静态文件的,部署之后就让这个tomcat来接收静态文件和图片。这个tomcat中得部署我们的webservice服务war包那么webservice服务我们发布了之后console就会调用这里面的服务从而把console中静态文件发送到nginx当中,专门放在一个存储静态文件的文件夹中,这样的话当互联网用户来访问的时候,当请求来到nginx的时候,nginx就会判断,当前请求是.html或者.jpg或者.css结尾的,即请求静态资源的,就不会再把该请求发送给前台服务器中去处理而是直接在自己nginx服务器上处理。

7. HashMap底层,Hash碰撞,Hash扩容

首先是HashMap 数组结构:Hashmap底层是通过数组和链接联合实现的,当我们创建hashmap时会先创建一个数组,当我们用put方法存数据时,先根据key的hashcode值计算出hash值,然后用这个哈希值确定在数组中的位置,再把value值放进去,如果这个位置本来没放东西,就会直接放进去,如果之前就有,就会生成一个链表,把新放入的值放在头部,当用get方法取值时,会先根据key的hashcode值计算出hash值,确定位置,再根据equals方法从该位置上的链表中取出该value值。

hashMap底层基于Entry(jdk1.8之后采用Node,Node继承了Entry)数组实现,在hashMap内部有一个静态的Entry内部类,其属性是key value 和next,在hashMap的内部中有一个Entry[]的线性数组结构,Entry对象在存入Entry数组时会根据Entry对象的key进行hash运算而拿到key值的hashcode码,hashCode码是一个固定的int值,取到hashCode值之后再根据当前数组长度取模,也就是key.hashCode%Entry[].length(高版本采用key.hashCode&( Entry[].length-1),位移运算效率更高),通过计算hash所得到的值就是此Entry对象存在链表数组中的下标,若根据key值所计算的数组下标一致,则在数组中存入最新存入的数据,在这里可能会担心之前存入的值会被覆盖,但是在Entry对象中有一个next属性,会把之前的Entry对象关联起来,形成一个链表结构,所有之前存入的值也不会被覆盖,也就是说假如ABC三个Entry对象的key经过hash所计算的下标都为0时,在链表数组中存储的结构就是C.next=B,B.next=A,Entry[0]=C 数组的下标所指向的是最新存入的Entry对象,而根据最新存入的Entry对象的next属性,可以找到之前存入的Entry对象,此种方式被称为拉链法,链表数组的默认长度是16,装载因子(表示哈希表的元素拥挤程度)默认为0.75f,当数组下标达到初始下标的0.75倍时,则会把链表数组散列扩展两倍。当hashMap在调用get方法时,先根据key值进行hash散列算出其在链表数组种的下标,算出下标之后根据下标返回Entry对象,判断当前Entry对象的key值是否与参数中的key值一致,若不一致则遍历当前下标所关联的Entry链表,直到找到对应的key值,若找不到则返回null。

注释(也要背会):
Hash碰撞:通过hash值计算Entry数组下标,所得到的下标一致即为hash碰撞,jdk1.8之前采用数组+链表的形式解决hash碰撞,当hash碰撞过多后,链表形式的时间复杂度就会增加,所以jdk1.8之后改用红黑树形式来解决hash碰撞,当hash碰撞节点较少时依然采用链表数组形式存储,当碰撞节点较多时(>8个)采用红黑树解决hash碰撞问题。
空间复杂度:空间复杂度是对一个算法在运行过程中所占用的临时内存空间大小的度量。
时间复杂度:时间复杂度是对一个算法的运行所需要消耗时间的计算函数,先找出算法的基本操作,在找出算法的执行次数就可以算出时间复杂度
算法的空间复杂度和时间复杂度用来衡量一个算法的效率,一般情况下,一个算法的时间复杂度和空间复杂度不可兼得。

红黑树:红黑树是一种特殊的二叉查找树,红黑树的每个节点都有存储位来存储节点颜色,是一种自平衡二叉查找树,红黑树的特性:

(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
红黑树的特性,保证了没有一条路径比其他路径长出两倍,因此红黑树是相对平衡的二叉树,在一定程度上降低了查找的时间复杂度。

8.数据库集群,分库分表,mysql主从配置,读写分离,以及如何降低延迟

(1.)数据库集群:
当时我们给数据库也做了优化,配置了主从同步和读写分离;MYSQL主从同步架构是目前使用最多的数据库架构之一,尤其是负载比较大的网站。
原理是:从服务器的IO线程到主服务器获取二进制日志,并在本地保存为中继日志,然后通过SQL线程来在从库上执行中继日志中的内容,从而使从库和主库保持一致。配置好主从同步之后我们还通过AOP来配置了读写分离;读写分离:数据分布,负载均衡,高可用性和容错,备份.
使用:就是我们搭建两台数据库服务器,一台作为主服务器master,两台作为从服务器slave,

(2.)主从同步配置
一:首先要安装三台MySQL服务;端口号不能一致。配置主库mastar,server-id = 1
log-bin=mysql-bin #要生成的二进制日记文件名称
binlog-do-db=test #要同步的数据库
#binlog-ignore-db=mysql
#不同步的数据库,如果指定了binlog-do-db这里应该可以不用指定的
二:配置从库,
(从库1)
server-id = 2 唯一标示 用于区分主从
log-bin = mysql-bin #对主数据库操作会生成对应的二进制文件,从数据库会根据主数据库生成的二进制文件来同步数据,新增,删除,修改。
replicate-do-db=test #用来指定同步的数据库,
(从库2)
server-id = 3 唯一标示 用于区分主从
log-bin = mysql-bin #对主数据库操作会生成对应的二进制文件,从数据库会根据主数据库生成的二进制文件来同步数据,新增,删除,修改。
replicate-do-db=test #用来指定同步的数据库,

三:给主库添加一个用户,并指定replication权限,在主数据库里面运行
show master status; 记下file和position字段对应的参数。
四:在从库设置它的master:
1.首先要先执行stop slave;命令,先停止从库的复制功能;
2.然后设置主从库的一些参数,要对应主数据库里查出来的file和position两个参数,
3.最后开启从库复制功能,start slave;
4.使用show slave status命令来查看主从配置的一些参数;主要看两个,Slave_IO_Running和Slave_Sql_Running ,这两个都是yes的情况下表示配置成功,

(3.)读写分离配置
配置读写分离有两种方式,一种是中间件一种是利用Aop的切面类来实现。
中间件比较麻烦,建议使用spring的动态数据源和Aop来实现;
在应用层支持『当写时默认读操作到主库』,这样如果我们采用这种方案,在写操作后读数据直接从主库拿,不会产生数据复制的延迟问题;应用层解决读写分离,理论支持任意数据库。
当在写事务(即写主库)中读时,也是读主库(即参与到主库操作),这样的优势是可以防止写完后可能读不到刚才写的数据
(4.)如何降低延迟:

  1. MySQL数据库主从同步延迟原理。
    谈到MySQL数据库主从同步延迟原理,得从mysql的数据库主从复制原理说起,mysql的主从复制都是单线程的操作,主库对所有DDL和 DML产生binlog,binlog是顺序写,所以效率很高,slave的Slave_IO_Running线程到主库取日志, 效率比较高,下一步, 问题来了,slave的Slave_SQL_Running线程将主库的DDL和DML操作在slave实施。DML和DDL的IO操作是随即的,不是顺序的,成本高很多,还可能与slave上的其他查询产生lock争用,由于Slave_SQL_Running也是单线程的,所以一个DDL卡主了,需要 执行10分钟,那么所有之后的DDL会等待这个DDL执行完才会继续执行,这就导致了延时。有朋友会问:“主库上那个相同的DDL也需要执行10分,为什 么slave会延时?”,答案是master可以并发,Slave_SQL_Running线程却不可以。

  2. MySQL数据库主从同步延迟是怎么产生的。
    当主库的TPS并发较高时,产生的DDL数量超过slave一个sql线程所能承受的范围,那么延时就产生了,当然还有就是可能与slave的大型query语句产生了锁等待。

  3. MySQL数据库主从同步延迟解决方案
    1、最简单的减少slave同步延时的方案就是在架构上做优化,尽量让主库的DDL快速执行。
    2、还有就是主库是写,对数据安全性较高,比如 sync_binlog=1,innodb_flush_log_at_trx_commit = 1 之类的设置,而slave则不需要这么高的数据安全,完全可以讲sync_binlog设置为0或者关闭binlog,innodb_flushlog也 可以设置为0来提高sql的执行效率。另外就是使用比主库更好的硬件设备作为slave。
    3、主库和同库尽可能在同一个局域网内,交换机网卡采用千兆网卡。 4、主数据库更新完成之后产生的操作日志不是瞬间产生的,我们可以通过设置sync_binlog=1, 让它瞬间产生磁盘日志,(n=1指主数据库只要操作一次,就产生一次磁盘日志,n=10,就是操作10次,产生一次),从数据库可以依赖磁盘日志,瞬间产生同步,可以达到减低延迟的效果。 5、设置主库和从库读取日志失败之后,及时重新建立连接,延迟缩短。
    (5.)分库分表
    分表
    场景:对于大型的互联网应用来说,数据库单表的记录行数可能达到千万级甚至是亿级,并且数据库面临着极高的并发访问。采用Master-Slave复制模式的MySQL架构,
    只能够对数据库的读进行扩展,而对数据库的写入操作还是集中在Master上,并且单个Master挂载的Slave也不可能无限制多,Slave的数量受到Master能力和负载的限制。
    因此,需要对数据库的吞吐能力进行进一步的扩展,以满足高并发访问与海量数据存储的需要!
    对于访问极为频繁且数据量巨大的单表来说,我们首先要做的就是减少单表的记录条数,以便减少数据查询所需要的时间,提高数据库的吞吐,这就是所谓的分表!
    在分表之前,首先需要选择适当的分表策略,使得数据能够较为均衡地分不到多张表中,并且不影响正常的查询!
    对于互联网企业来说,大部分数据都是与用户关联的,因此,用户id是最常用的分表字段。因为大部分查询都需要带上用户id,这样既不影响查询,又能够使数据较为均衡地分布到各个表中
    分库
    场景:分表能够解决单表数据量过大带来的查询效率下降的问题,但是,却无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库master
    服务器无法承载写操作压力时,不管如何扩展slave服务器,此时都没有意义了。
    因此,我们必须换一种思路,对数据库进行拆分,从而提高数据库写入能力,这就是所谓的分库!
    与分表策略相似,分库可以采用通过一个关键字取模的方式,来对数据访问进行路由
    分库分表
    场景:有时数据库可能既面临着高并发访问的压力,又需要面对海量数据的存储问题,这时需要对数据库既采用分表策略,又采用分库策略,以便同时扩展系统的
    并发处理能力,以及提升单表的查询性能,这就是所谓的分库分表。
    分库分表的策略比前面的仅分库或者仅分表的策略要更为复杂,一种分库分表的路由策略如下:

    1. 中间变量 = user_id % (分库数量 * 每个库的表数量)
    2. 库 = 取整数 (中间变量 / 每个库的表数量)
    3. 表 = 中间变量 % 每个库的表数量

同样采用user_id作为路由字段,首先使用user_id 对库数量*每个库表的数量取模,得到一个中间变量;然后使用中间变量除以每个库表的数量,取整,便得到
对应的库;而中间变量对每个库表的数量取模,即得到对应的表。
分库分表策略详细过程如下:
假设将原来的单库单表order拆分成256个库,每个库包含1024个表,那么按照前面所提到的路由策略,对于user_id=262145 的访问,路由的计算过程如下:

  1. 中间变量 = 262145 % (256 * 1024) = 1
  2. 库 = 取整 (1/1024) = 0
  3. 表 = 1 % 1024 = 1
    这就意味着,对于user_id=262145 的订单记录的查询和修改,将被路由到第0个库的第1个order_1表中执行!!!
    业务场景
    分表操作一般对电商项目的用户进行分表操作,按照手机号分为156_user, 183_user,150_user等,当用户用手机号码登录时,在业务层进行分割前三位进行判断,走不同的表查询。同理电商项目的商品表也可以进行分表操作。

9.list 和set 的区别

<1.List接口  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。和下面要提到的Set不同,List允许有相同的元素。

<2.Set接口  Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。  很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素

❤️.List和Set都是接口。他们各自有自己的实现类,有无顺序的实现类,也有有顺序的实现类。最大的不同就是List是可以重复的。而Set是不能重复的。List适合经常追加数据,插入,删除数据。但随即取数效率比较低。Set适合经常地随即储存,插入,删除。但是在遍历时效率比较低。

<4.list,set都是可以使用collections.sort()排序的

10.比较一下Java和JavaSciprt。

JavaScript 与Java是两个公司开发的不同的两个产品。Java 是原Sun Microsystems公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而JavaScript是Netscape公司的产品,为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言。JavaScript的前身是LiveScript;而Java的前身是Oak语言。
下面对两种语言间的异同作如下比较:

  • 基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。
  • 解释和编译:Java的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率)
  • 强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。
  • 代码格式不一样。

11.elasticsearch倒排索引原理

1. 索引的方式:

1.1 正向索引
正排表是以文档的ID为关键字,表中记录文档中每个字的位置信息,查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。
这种组织方法在建立索引的时候结构比较简单,建立比较方便且易于维护;因为索引是基于文档建立的,若是有新的文档加入,直接为该文档建立一个新的索引块,挂接在原来索引文件的后面。若是有文档删除,则直接找到该文档号文档对应的索引信息,将其直接删除。但是在查询的时候需对所有的文档进行扫描以确保没有遗漏,这样就使得检索时间大大延长,检索效率低下。
尽管正排表的工作原理非常的简单,但是由于其检索效率太低,除非在特定情况下,否则实用性价值不大。

1.2 倒排索引
倒排表以字或词为关键字进行索引,表中关键字所对应的记录表项记录了出现这个字或词的所有文档,一个表项就是一个字表段,它记录该文档的ID和字符在该文档中出现的位置情况。
由于每个字或词对应的文档数量在动态变化,所以倒排表的建立和维护都较为复杂,但是在查询的时候由于可以一次得到查询关键字所对应的所有文档,所以效率高于正排表。在全文检索中,检索的快速响应是一个最为关键的性能,而索引建立由于在后台进行,尽管效率相对低一些,但不会影响整个搜索引擎的效率。

1.3 总结
正排索引是从文档到关键字的映射(已知文档求关键字),倒排索引是从关键字到文档的映射.

12.什么是事务,并解释四大特性;

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。

特性:事务是恢复和并发控制的基本单位。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(durability)。指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

13.ArrayList和LinkedList区别及使用场景

区别
ArrayList底层是用数组实现的,可以认为ArrayList是一个可改变大小的数组。随着越来越多的元素被添加到ArrayList中,其规模是动态增加的。
LinkedList底层是通过双向链表实现的, LinkedList和ArrayList相比,增删的速度较快。但是查询和修改值的速度较慢。同时,LinkedList还实现了Queue接口,所以他还提供了offer(),
peek(), poll()等方法。

使用场景
LinkedList更适合从中间插入或者删除(链表的特性)。
ArrayList更适合检索和在末尾插入或删除(数组的特性)。

14.Collection和Collections的区别

java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

15.接口(Interface)与抽象类(Abstract Class)的区别?

接口是公开的,里面不能有私有的方法或变量,是用于让别人使用的,抽象类是可以有私有的方法或者私有的变量,如果一个类中有抽象方法,那么就是抽象类。在java语言中,可以把类或类中方法声明为abstract来表示一个类是抽象类。接口是指一个方法的集合,接口中所有的方法都没有方法体,在java语言中用关键字interface来实现。
包含一个或者多个抽象方法的类必须被声明为抽象类,抽象类可以声明方法的存在而不去实现它,被声明为抽象的方法不能包含方法体。在抽象类的子类中,实现方法必须含有相同或者更低的访问级别。。
接口与抽象类的相同点:
1)都不能被实例化。
2)接口的实现类或抽象类的子类都只有实现了接口或者抽象类中的方法后才能被实例化。
接口与抽象类的不同点:
1)接口只有定义不能有方法的实现,而抽象类可以有定义和实现。
2)一个类可以实现多个接口但是可以继承多个实现类。
3)接口强调特定功能的实现,抽象类强调所属关系。
4)接口中定义的成员变量默认为public static final,只能够有静态的不能被修改的数据成员,而且,必须赋初值,所有的成员方法都是public、abstract。抽象类可以有自己的数据成员变量,也可以有非抽象的成员方法,而且成员方法可以在子类中被重新定义,也可以被重新赋值。
5)接口被运用于实现比较常用的功能便于日后维护或者添加删除方法,抽象类充当于公共类角色。

16.==与equals的区别

==比较两个对象在内存里是不是同一个对象,就是说在内存里的存储位置一致。两个String对象存储的值是一样的,但有可能在内存里存储在不同的地方 .

==比较的是引用而equals方法比较的是内容。public boolean equals(Object obj) 这个方法是由Object对象提供的,可以由子类进行重写。默认的实现只有当对象和自身进行比较时才会返回true,这个时候和 == 是等价的。String, BitSet, Date, 和File都对equals方法进行了重写,对两个String对象 而言,值相等意味着它们包含同样的字符序列。对于基本类型的包装类来说,值相等意味着对应的基本类型的值一样。

17.spring 常用的注入方式有哪些?

Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:

构造方法注入
setter注入
基于注解的注入

18.Redis 和Mongodb 的 区别?

1.Mogodb 是非关系数据,基于bson格式的数据库 ,有关键字可以进行分页,姓名模糊匹配,大于小于等于,mongo是像关系型数据库的非关系型数据库

2.Redis 是一个缓存的技术,mongo的数据都存在硬盘当中,而redis数据是存放在内存当中的,只是 它支持 持久化到硬盘的操作,所以可以理解为 非关系数据库,但需要注意的是,redis是通过键值对的存储方式,不支持模糊,条件查询

19.final,finally,finalize的区别?

final用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,
可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。
算符可以用来决定某对象的类是否实现了接口。

20.GC是什么?为什么要有GC?

GC是垃圾收集的意思(GabageCollection),内存处理是编程人员容易出现问题的地方,忘
记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。

21.接口和抽象类有什么区别?或者问你选择使用接口和抽象类的依据是什么?

接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。
抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。
所以,在高级语言上,一个类只能继承一个类(抽象类),但是可以实现多个接口。

第一点. 接口是抽象类的变体,接口中所有的方法都是抽象的。而抽象类是声明方法的存在而不去实现它的类。
第二点. 接口可以多继承,抽象类不行
第三点. 接口定义方法,不能实现,而抽象类可以实现部分方法。
第四点. 接口中基本数据类型为static 而抽类象不是的。
当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。
抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的
所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度的。

22.spring概述

Spring 是完全面向接口的设计,降低程序耦合性,主要是事务控制并创建bean实例对象。在ssh整合时,充当黏合剂的作用。IOC(Inversion of Control) 控制反转/依赖注入,又称DI(Dependency Injection) (依赖注入)

IOC的作用:产生对象实例,所以它是基于工厂设计模式的

Spring IOC的注入
   通过属性进行注入,通过构造函数进行注入,
   注入对象数组 注入List集合
   注入Map集合 注入Properties类型

Spring IOC 自动绑定模式:
可以设置autowire按以下方式进行绑定
按byType只要类型一致会自动寻找,
按byName自动按属性名称进行自动查找匹配.
AOP 面向方面(切面)编程
AOP是OOP的延续,是Aspect Oriented Programming的缩写,
    意思是面向方面(切面)编程。
   注:OOP(Object-Oriented Programming ) 面向对象编程

AOP 主要应用于日志记录,性能统计,安全控制,事务处理(项目中使用的)等方面。

Spring中实现AOP技术:
在Spring中可以通过代理模式来实现AOP
代理模式分为
静态代理:一个接口,分别有一个真实实现和一个代理实现。
动态代理:通过代理类的代理,接口和实现类之间可以不直接发生联系,而 可以在运行期(Runtime)实现动态关联。

动态代理有两种实现方式,可以通过jdk的动态代理实现也可以通过cglib
来实现而AOP默认是通过jdk的动态代理来实现的。jdk的动态代理必须要有

接口的支持,而cglib不需要,它是基于类的。

23.Java面对对象的三大特性

封装:将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。
继承:继承是类与类的一种关系,是一种“is a”的关系。java中的继承是单继承,即一个类只有一个父类。
多态:面向对象的最后一个特性就是多态,那么什么是多态呢?多态就是对象的多种形态。

24.重写与重载

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。

25.反射机制

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
· 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
· 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。

26.Java中的String,StringBuilder,StringBuffer三者的区别

先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String,String最慢的原因:String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。
再来说线程安全, 在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的,如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。
总结一下
 String:适用于少量的字符串操作的情况
  StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
  StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

27.java中int 和integer区别

Integer是int的包装类,int则是java的一种基本数据类型
Integer变量必须实例化后才能使用,而int变量不需要
Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 。
Integer的默认值是null,int的默认值是0

28.HashMap和Hashtable有什么区别?

HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
HashMap允许键和值是null,而Hashtable不允许键或者值是null。
Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。
一般认为Hashtable是一个遗留的类。

29.jdk7和jdk8的区别

1.7:
Switch语句支持String类型。
泛型实例化类型自动推断
语法上支持集合,而不一定是数组
新增一些取环境信息的工具方法
boolean类型反转,空指针安全,参与位运算
char值的equals
安全的加减乘除
map集合支持并发请求
1.8
允许在接口中有默认方法实现
lambda表达式和范围
函数式接口
方法和构造函数引用
内置函数式接口
streams 和 parallel streams
map
时间日期API
Annotations

30.Redirect 转发重定向的区别

转发和重定向的区别:
重定向:以前的response中存放的变量全部失效,并进入一个新的response作用域。
转发:以前的request中存放的变量不会失效,就像把两个页面拼到了一起。
重定向是两次请求,重定向后地址栏变为第二次请求的页面,但是response重定向没有rutern
而转发之后地址栏地址不变,请求还是第一次的地址
解决方法
将重定向改为转发
resp.sendRedirect(“frame.html”);
req.getRequestDispatcher(“frame.html”).forward(req, resp);

31.ES分片

分片这块的话当时是根据实际需求进行分片的,因为分片并不是越多越好。分片多浪费存储空间、占用资源、影响性能。这一块的话我们当时参考了网上的文档,ElasticSearch推荐的最大JVM堆空间是30~32G, 所以我们把分片的最大容量限制为30GB, 然后再对分片数量做合理估算。在开始的时候呢,我们是根据节点的数量按照1.5到3倍的原则来创建分片的。例如:如果有3个节点,则创建的分片数量不能超过9个。当性能下降时,增加节点,ES会平衡分片的放置。

32.ES索引库

ES的索引库是一个逻辑概念,它包括了分词列表及文档列表,同一个索引库中存储了相同类型的文档。它就相当于MySQL中的表,或相当于Mongodb中的集合。
索引库刚创建起来是空的,将数据添加到索引库的过程称为索引。在保持数据一致性这一块,Elasticsearch采用了乐观锁来保证数据在多线程操作下的一致性,即当用户对document进行操作时,并不需要对该document做加锁、解锁的操作,只需要指定要操作的版本即可。当版本号一致时,Elasticsearch会允许该操作顺利进行,而当版本号存在冲突时,Elasticsearch会提示冲突并抛出异常。他有两种版本控制,一个是内部版本控制:_version自增长,修改数据后,_version会自动的加1。外部版本控制:为了保持_version与外部版本的控制的数值一致,使用version_type=esternal检查数据当前的version值是否小于请求中的version值。

33.在项目中MQ丢单问题处理

消息队列的话本身也有丢单的问题,对于丢单话,为了防止订单丢失,我们在将订单放入到rabbitMQ中时,同时保存到mongodb数据库中,同时设置一个标识字段0(未对账),消息队列后续处理过程中,调用银行接口成功,则将该标识字段修改为1(已对账)。再写个定时器查询一个月以前的所有订单信息,如果mongodb的信息还有未对账的,就把这个单子重新扔到队列里面,后期在进行调用。

34.RabbitMQ消息队列处理订单

在项目中我们使用的是当下比较火的RabbitMQ技术,为了提高系统的性能,提高并发量,降低这个服务之间的耦合度,提高这个系统对订单的处理速度,这也是我们在使用消息中间件主要原因。当前台订单付款后,我们将这个付款后的订单从redis中取出直接放入定义好的订单消息队列中,然后后台只需要用Rabbtilistener去监测这里面的订单,如果拿到这个订单的话,就是在处理订单的方法里有一个Rabbtiandler去处理这个订单信息,处理新增订单,里面有一些业务,比方说我们需要去订单库里面添加订单信息,里面还需要要改我的库存,改库存信息,还有就是,比方说我要买东西,会要扣除我银行卡里面的钱,商家账户里面需要加钱,这就是订单处理的里面的一些业务。另外还有就是需要调用外部接口服务,比如手机发短信,给客户发邮件订单。

35.处理订单多线程问题

另外说到这个处理订单,还有一个比较重要的需要考虑的问题,就是多线程问题,如果是进来的订单量特别多的话后面用一个线程执行速度会比较慢,我们就可以在这用缓存线程池去开启多个线程就进行处理订单,以此来提高这个订单的处理速度,当然我们还可以通过MQ开启多个线程,开启的方式也很简单,只需要配置mq的容器工厂参数,(通过设置factory.setConcurrentConsumers();设置线程数,factory.setMaxConcurrentConsumers(); 最大线程数)在@RabbitListener注解中指定容器工厂,就可以增加并发处理数量实现多线程处理监听队列,实现多线程处理消息,提高订单的处理速度。

36.IOC的理解

Spring 是完全面向接口的设计,降低程序耦合性(为什么),主要是事务控制并创建bean实例对象。在ssh整合时,充当黏合剂的作用。IOC(Inversion of Control) 控制反转/依赖注入,又称DI(Dependency Injection) (依赖注入) 从架构来讲默认 注入组件都是单例,有效降低java 吃内存的问题。Spring大大降低了 java 语言开发相互的内存。这是项目中使用spring的 主要原因。
IOC的作用:产生对象实例,所以它是基于工厂设计模式的
Spring IOC的注入:
   通过属性进行注入,通过构造函数进行注入,工厂注入
Spring IOC 自动绑定模式:
可以设置autowire按以下方式进行绑定
按byType只要类型一致会自动寻找,
按byName自动按属性名称进行自动查找匹配.

37.AOP的理解

AOP 面向方面(切面)编程
AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向方面(切面)编程。(为啥是OOP的延续) 注:OOP(Object-Oriented Programming ) 面向对象编程。

AOP:主要应用于日志记录,性能统计,安全控制,事务处理(项目中使用的)等方面。
Spring中实现AOP技术:
在Spring中可以通过代理模式来实现AOP代理模式分为:
静态代理:一个接口,分别有一个真实实现和一个代理实现。(代码 缺点)
动态代理:通过代理类的代理,接口和实现类之间可以不直接发生联系,而可以在运行期(Runtime)实现动态关联。(优点)。动态代理有两种实现方式,可以通过jdk的动态代理实现也可以通过cglib 来实现而AOP默认是通过jdk的动态代理来实现的。jdk的动态代理必须要有接口的支持,而cglib不需要,它是基于类的。
概念解释:
切面(Aspect): 有切点(PointCut)和通知(Advice)组成,它既包括横切逻辑的定义,也包括了连接点的定义。
切点(Pointcut):一个切点定位多个类中的多个方法。
通知也叫增强(Advice):由方位和横切逻辑构成,所谓的方位指的是前置通知,后置通知,返回后通知,环绕通知,抛出异常后通知。
连接点(JoinPoint):由切点和方位构成,用来描述在在哪些类的指定方法之前或之后执行。
所谓的方位包括:
前置通知(Before advice):在连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
返回后通知(After returning advice): 在连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
后置通知(After (finally) advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行

38.写入内存而不是写入硬盘

传统硬盘的读写性能是相当差的。SSD硬盘比传统硬盘快100倍。而内存又比SSD硬盘快10倍以上。因此,写入内存而不是写入硬盘,就能使系统的能力提升上千倍。也就是说,原来你的秒杀系统可能需要1000台服务器支撑,现在1台服务器就可以扛住了。
你可能会有这样的疑问:写入内存而不是持久化,那么如果此时计算机宕机了,那么写入的数据不就全部丢失了吗?如果你就这么倒霉碰到服务器宕机,那你就没秒到了,有什么大不了?
最后,后面真正处理秒杀订单时,我们会把信息持久化到硬盘中。因此不会丢失关键数据。
Redis是一个缓存系统,数据写入内存后就返回给客户端了,能够支持这个特性。

39.异步处理而不是同步处理

像秒杀这样短时大并发的系统,在性能负载上有一个明显的波峰和长期的波谷。为了应对相当短时间的大并发而准备大量服务器来应对,在经济上是相当不合算的。
因此,对付秒杀类需求,就应该化同步为异步。用户请求写入内存后立刻返回。后台启动多个线程从内存池中异步读取数据,进行处理。如用户请求可能是1秒钟内进入的,系统实际处理完成可能花30分钟。那么一台服务器在异步情况下其处理能力大于同步情况下1800多倍!
异步处理,通常用MQ(消息队列)来实现。Redis可以看作是一个高性能的MQ。因为它的数据读写都发生在内存中。

40.分布式处理

如果客户还是比较多,还可以使用分布式处理,如果一台服务器撑不住秒杀系统,那么就多用几台服务器。10台不行,就上100台。分布式处理,就是把海量用户的请求分散到多个服务器上。一般使用hash实现均匀分布。redis就是一个这样的产品。因此使用Redis或者Redis Cluster就可以轻松实现一个强大的秒杀系统。 基本上,你用Redis的这些命令就可以了。
RPUSH key value 插入秒杀请求,当插入的秒杀请求数达到上限时,停止所有后续插入。后台启动多个工作线程,使用LPOP key读取秒杀成功者的用户id,进行后续处理。或者使用LRANGE key start end命令读取秒杀成功者的用户id,进行后续处理。
每完成一条秒杀记录的处理,就执行INCR key_num。一旦所有库存处理完毕,就结束该商品的本次秒杀,关闭工作线程,也不再接收秒杀请求。

41.Redis的并发竞争问题如何解决?

单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,利用setnx实现锁.

42.Redis 的主从复制

持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障

43.Redis是单线程的,但Redis为什么这么快?

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO;这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

44.为什么Redis是单线程的?

Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)。

45.持久化策略选择

(1)如果Redis中的数据完全丢弃也没有关系(如Redis完全用作DB层数据的cache),那么无论是单机,还是主从架构,都可以不进行任何持久化。
(2)在单机环境下(对于个人开发者,这种情况可能比较常见),如果可以接受十几分钟或更多的数据丢失,选择RDB对Redis的性能更加有利;如果只能接受秒级别的数据丢失,应该选择AOF。
(3)但在多数情况下,我们都会配置主从环境,slave的存在既可以实现数据的热备,也可以进行读写分离分担Redis读请求,以及在master宕掉后继续提供服务。

46.缓存雪崩问题

存在同一时间内大量键过期(失效),接着来的一大波请求瞬间都落在了数据库中导致连接异常。
解决方案:
1、也是像解决缓存穿透一样加锁排队。
2、建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,并且更新A缓存和B缓存;

47.缓存并发问题

这里的并发指的是多个redis的client同时set key引起的并发问题。比较有效的解决方案就是把redis.set操作放在队列中使其串行化,必须的一个一个执行,具体的代码就不上了,当然加锁也是可以的,至于为什么不用redis中的事务,留给各位看官自己思考探究。

48.Redis分布式

redis支持主从的模式。原则:Master会将数据同步到slave,而slave不会将数据同步到master。Slave启动时会连接master来同步数据。
这是一个典型的分布式读写分离模型。我们可以利用master来插入数据,slave提供检索服务。这样可以有效减少单个机器的并发访问数量

49.读写分离模型

通过增加Slave DB的数量,读的性能可以线性增长。为了避免Master DB的单点故障,集群一般都会采用两台Master DB做双机热备,所以整个集群的读和写的可用性都非常高。读写分离架构的缺陷在于,不管是Master还是Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于Write-intensive类型的应用,读写分离架构并不适合。

50.Redis分布式锁实现

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。**如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?**set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!

51.数据库的主从同步读写分离

当时我们给数据库也做了优化,配置了主从同步和通过mycat进行读写 分离;MYSQL主从同步架构是目前使用最多的数据库架构之一,尤其是负载比较大的网站。
原理是:从服务器的IO线程到主服务器获取二进制日志,并在本地保存为中继日志,然后通过SQL线程来在从库上执行中继日志中的内容,从而使从库和主库保持一致。配置好主从同步之后我们还通过mycat来配置了读写分离;读写分离:数据分布,负载均衡,高可用性和容错,备份.
使用:就是我们搭建两台数据库服务器,一台作为主服务器master,一台作为从服务器slave,
1.要更改mysql数据库中的my-defult.ini文件,将文件名改为my.ini,然后更改里面的配置,包括数据库路径,端口号。编码集、log-bin文件名,开放的数据库等信息。
2.在主数据库,新增一个用户,并指定replication权限45
3.在主数据库里面运行show master status;记下file和position字段对应的参数。
4.在从库设置它的master
5. stop slave; #停止服务再输入#指定用户名密码,将里面的master_log_file和master_log_pos对应刚才show master status记下的参数
6.开启服务,start slave
7. 通过show slave status来查看同步的状态
这样就实现了主从同步。

52.SpringMVC框架

(1)(部分核心配置请求大概流程)springMVC是一个基于注解型的MVC框架。它的核心控制器是dispatcherServlet,在web.xml中配置。用户发起的请求都会被核心控制器拦截,进入springMVC的核心配置文件。在这个xml中,主要配置的是注解的开启和扫描信息。首先要开启注解模式,annotation-driven。并且指定注解所在的路径,通过component-scan标签的base-package来设置。当请求到达后,springMVC会根据请求的Controller名称,通过ActionMapping去找到对应的Controller类中的requestMapping注解,这个注解中有一个value值,需要与请求的名称保持一致。所以请求可以到达action层。
(2)(拦截器简述),当然,springMVC也有自己的拦截器Interceptor。如果需要完成自定义拦截器,则需要写一个类,去继承handlerInterceptor类。重写里面的preHandler方法,在这方法中,通过返回true或者fasle来决定请求继续执行还是被拦截。定义好拦截器类以后,需要将拦截器配置到springMVC配置文件中。
(3)(传参返回数据概述 简单注解)springMVC中,获得参数的方式很简单,只需要在方法的参数列表中定义相关的局部变量就可以了。也可以通过requestParam的注解对参数进行更具体的配置,比如require为true时,要求这个参数必须传递。如果要求传递而没有传值,则报403错误。除了这样的配置以外,requestMapping也有相关的属性配置,比如requestType可以设置成GET或者是POST,指定请求的格式必须是Get请求还是post请求。如果需要传递的是对象类型参数,直接在参数列表中定义相关的对象,页面的参数名称必须要和类型的属性保持一致。Controller接收参数后,则调用service业务逻辑层,完成业务操作。最后,springMVC进行页面跳转时,也很灵活,可以通过string返回一个字符串,也可以通过ModelAndView来进行跳转。ModelAndView中有一个setViewName方法,可以指定跳转的路径,还有一个addObject方法,可以将对象或者集合带到页面进行数据展示。如果是通过string类型跳转,则可以通过request.setAttribute()(model)方法往页面传参。获得request的方式可以直接在参数列表中定义request对象获得,当然response也一样。
在spring-servlet中定义了一个试图解析器,统一配置了跳转路径的前缀和后缀名。

53.Spring概述

我认为spring就是一个框架的集成器,通常使用spring来管理action层和DAO层。
Spring本身有很多的组件,比如:MVC、IOC、AOP、DaoSupport等等。IOC本身也就是一个容器,它管理了所有的bean和bean之间的依赖关系。IOC也叫作控制反转,核心是BeanFactory。也就意味着IOC是基于工厂模式设计的,同时这个工厂生产的bean默认是单例的。如果想修改单例变成多实例,则需要修改bean的scope属性,值是prototype。
在没有使用IOC以前,程序员需要自己在对应的类中new相关依赖的对象。比如UserController依赖于UserService完成业务操作,而UserService又依赖于UserDAO完成数据库操作。所以需要在action中new servcie,在service中new DAO。这样的方式,是由程序员来管理了对象的生命周期和他们之间的依赖关系,耦合度很高,不利于程序的拓展。所以通过IOC来管理bean和依赖关系,可以解耦合。我们将所有的action、service和dao等类定义成IOC的一个bean组件,此时类的实例化就交给了IOC的beanFactory,由工厂来负责实例化bean的对象。
IOC有三种注入方式,属性注入、构造器注入和接口注入。常用的注入方式是属性注入。属性注入就是在bean的标签中,配置property标签设定注入其他的bean。要求注入的bean在被注入的bean中要存在一个全局的私有变量,并提供set方法。这样就可以实现了依赖关系的注入。如果需要很多的bean,则直接注入就可以。如此操作会导致bean标签的配置比较冗余复杂,所以spring提供了autowried的自动装配方式,可以byName也可以byType。后续的版本中,spring还提供了annotation(注解)的方式,不需要再去定义多余的bean标签,而是直接在对应的类上面声明注解就可以了。常用的注解有:@controller、@Service、@Repository、@Component、@AutoWried、@Resource等。
除了IOC以外,项目中通常通过AOP来实现事务控制。AOP就是面向切面编程,一般事务我们会控制在service层,因为一个service有可能会调用到多个DAO层的方法,所以只有当一个service方法执行成功后,再提交或者回滚事务。具体的配置方式是:在applicationContext.xml中,配置aop:config标签,指定事务控制在service层。除此还需要配置事务的管理类transactionManager,将这个transactionManager指定给事务管理的bean,并且配置事务的传播特性、隔离级别、回滚策略以及只读事务read-only等等。
Spring默认的传播特性是如果当前上下文中存在事务则支持当前事务,如果没有事务,则开启一个新的事务。还有另外一个传播特性是在项目中经常用的,REQUIRES_NEW这个配置,这个属性指的是总是开启一个新的事务,如果当前上下文中存在一个事务,则将当前的事务挂起后开启新的事务。比如说:在一个本来是只读事务的操作中,想加入写操作的时候,就使用REQUIRES_NEW。关于事务的隔离级别,一般使用默认的配置提交读。也就是说,事务提交以后,才能访问这条数据。除了事务控制以外,我们通常还可以使用AOP去完成一些特殊操作,比如日志控制、安全校验等等。这么做的目的就是将功能操作的代码从实际的业务逻辑操作出分离出来。实现的方式是通过代理模式,真正完成操作的不是实际的业务对象而是代理对象。
代理模式有静态代理和动态代理,实现的方案也有两种,一种是基于JDK的Proxy代理类,另外一种则通过CGLIB来实现。实现AOP的方式,主要是在applicationContext中定义一个AOP处理类,这就是一个普通的bean,在类中定义要执行的方法。然后去配置一个aop:config标签,在标签中定义aop:aspect切面,在切面中关联刚才定义好的处理类bean。然后在切面标签中配置aop:pointcut切入点,切入点就指的是在哪个类的哪个方法上加入代理事务,然后配置通知模型。AOP的通知模型中包含:前置通知、后置通知、最终通知、异常通知、环绕通知。这几个通知模型表示在方法执行以前、执行以后、最终执行、当遇到异常时执行以及前后都执行。在执行的AOP切面方法中,可以通过JoinPoint连接点来获得当前被代理的对象以及被代理对象要执行的方法和方法的参数。

54.JVM内存结构

虚拟机栈
主要存放对象的引用和局部变量,以及基本数据类型(byte,int,long等)
方法区
存放类定义 结构 方法等数据

堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden(伊甸)空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配,新创建的对象存放在Eden区,垃圾回收时优先回收伊甸区,没有回收的对象会在两个Survivor区互相copy
Pc寄存器(程序计数器):程序计数器是一个以线程私有的一块较小的内存空间,用于记录所属线程所执行的字节码的行号指示器;字节码解释器工作时,通过改变程序计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳准、异常处理、线程恢复等基础功能都需要依赖程序计数器来完成,程序计数器不会有内存溢出风险,随线程的创建而产生,线程的结束而死亡。

55.JVM优化方案

**a.**参数方面根据当前服务器配置设置JVM参数进行参数优化
-Xms设置堆的最小空间大小。
-Xmx设置堆的最大空间大小。
-XX:NewSize设置新生代最小空间大小。
-XX:MaxNewSize设置新生代最大空间大小。
-XX:PermSize设置永久代最小空间大小。
-XX:MaxPermSize设置永久代最大空间大小。
-Xss设置每个线程的堆栈大小
-XX:MaxTenuringThreshold 设置对象在年轻代存活次数,大于该值则进入年老代

**b.**GC垃圾收集器方面选用合适的垃圾回收器
Serial收集器 串行收集器 使用停止复制算法,使用一个线程进行GC,串行,其它工作线程暂停。适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上
Parallel 并行收集器 在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式
CMS 并发收集器 与并行收集器类似,响应比并行gc快很多,但是牺牲了一定的吞吐量。
c.对JVM内存进行监控
使用Jconsole,jProfile,VisualVM等监控工具,对JVM的内存以及运行情况监控,找出对应的问题进行优化

Jconsole : jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用。对垃圾回收算法有很详细的跟踪。详细说明参考这里

JProfiler:商业软件,需要付费。功能强大。

VisualVM:JDK自带,功能强大,与JProfiler类似。推荐。
**d.**减少线程的创建数量,若无法减少,则使用-Xss缩减每个线程的大小
**e.**硬件配置上
根据应用所需,匹配相应配置的硬件服务器,硬件跟不上,再优化也好不到哪去。

56.Sql优化

1.过多的表联查
在拿到执行较慢的sql语句之后,分析其有无冗余的表联查,若存在,则去掉,使用嵌套查询解决多表直接联查问题,可以把多个表的查询结果分开执行,然后再把结果合并到一块。若无法解决表联查问题,可以把表的联查结果写入mongoDB这样的noSQl数据库中,查询时直接走NoSql数据库获取表联查结果,这种适用于查询较多,写入请求较少的情况。
2.数据量较大的表查询未使用索引,或使用了索引,但是索引未生效。
首先使用sql的执行计划查看当前sql语句有无使用索引,使用索引查询的执行类型是什么,使用EXPLAIN关键字对sql语句进行分析,返回结果中的TYPE字段表示索引的使用情况,ALL代表全表扫描,也就是未使用索引,const表示主键索引或唯一索引,查询效率最高,ref引用查找,查找非唯一索引匹配项,range索引范围查找。Key代表当前查询所使用的实际索引。根据sql执行计划分析索引的使用情况,对于有条件的表(数据量超过10万条以上,查询较频繁)添加索引,对于查询添加了所有,应注意一下几点,a.添加索引的字段必须为where或group by中使用的字段,否则sql语句不会执行索引,多个字段同时查询可以考虑组合索引。b.添加索引的字段不能进行运算操作,因为索引的使用是在sql编译器编译时选择的,若对索引字段进行运算,其值只有在sql语句执行时才能确定,所以会导致索引失效。c使用like模糊查询时前后都有%或前面有%时则索引失效,使用组合索引时第一个字段匹配则走索引,第一个字段不匹配则索引失效,各个字段都匹配索引效率最高。字段顺序尽量与索引顺序保持一致d.使用in或not in 也会使索引失效,若非必须使用,可以考虑使用其它方式替代,例如between或exists。E 加索引的字段不能为空值。F 一个表的索引尽量不要超过6个,否则会降低写入表的性能。
3.一次查询返回的数据量较大
考虑需求的合理性,采用分页减少数据量的返回
4.不同数据库之间Sql语句解析器解析方式不同
Mysql查询采用自上而下查询,过滤掉最大数据量的条件必须紧跟where子句之后,缩小查询的范围

57.每秒处理10万订单支付思路

支付系统要处理每秒十万笔订单,需要的是每秒数十万的数据库更新操作(insert加update),这在任何一个独立数据库上都是不可能完成的任务,所以我们首先要做的是对订单表(简称order)进行分库与分表。
分库策略我们选择了“二叉树分库”,所谓“二叉树分库”指的是:我们在进行数据库扩容时,都是以2的倍数进行扩容。并将每个分库中都将order表拆分成10份。
如果要支持每秒处理10万订单,那每秒将至少需要生成10万个订单ID,通过数据库生成自增ID显然无法完成上述要求。所以我们只能通过内存计算获得全局唯一的订单ID。使用Twitter的Snowflake算法,实现了全局唯一ID。订单id由分库分表信息+时间戳+机器号+自增序号组成。这样只根据订单ID,我们也能快速的查询到对应的订单信息。
我们通过对order表uid维度的分库分表,实现了order表的超高并发写入与更新,并能通过uid和订单ID查询订单信息。但作为一个开放的集团支付系统,我们还需要通过业务线ID(又称商户ID,简称bid)来查询订单信息,所以我们引入了bid维度的order表集群,将uid维度的order表集群冗余一份到bid维度的order表集群中,要根据bid查询订单信息时,只需查bid维度的order表集群即可。
我们引入了消息队列进行异步数据同步,来实现数据的最终一致性。当然消息队列的各种异常也会造成数据不一致,所以我们又引入了实时监控服务,实时计算两个集群的数据差异,并进行一致性同步。
没有任何机器或服务能保证在线上稳定运行不出故障。比如某一时间,某一数据库主库宕机,这时我们将不能对该库进行读写操作,线上服务将受到影响。
所谓数据库高可用指的是:当数据库由于各种原因出现问题时,能实时或快速的恢复数据库服务并修补数据,从整个集群的角度看,就像没有出任何问题一样。需要注意的是,这里的恢复数据库服务并不一定是指修复原有数据库,也包括将服务切换到另外备用的数据库。数据库高可用的主要工作是数据库恢复与数据修补
支付系统除了最核心的支付订单表与支付流水表外,还有一些配置信息表和一些用户相关信息表。如果所有的读操作都在数据库上完成,系统性能将大打折扣,所以我们引入了数据分级机制。
我们简单的将支付系统的数据划分成了3级:
1级:订单数据和支付流水数据;这两块数据对实时性和精确性要求很高,所以不添加任何缓存,读写操作将直接操作数据库。
第2级:用户相关数据;这些数据和用户相关,具有读多写少的特征,所以我们使用redis进行缓存。
第3级:支付配置信息;这些数据和用户无关,具有数据量小,频繁读,几乎不修改的特征,所以我们使用本地内存进行缓存。
http请求在进入web集群前,会先经过一层粗细管道。入口端是粗口,入口端是粗口,我们设置最大能支持100万请求每秒,多余的请求会被直接抛弃掉。出口端是细口,我们设置给web集群10万请求每秒。剩余的90万请求会在粗细管道中排队,等待web集群处理完老的请求后,才会有新的请求从管道中出来,给web集群处理。这样web集群处理的请求数每秒永远不会超过10万,在这个负载下,集群中的各个服务都会高校运转,整个集群也不会因为暴增的请求而停止服务。

58.MongoDB(商品评论)

商品评论这一块在电商项目中重要的,他主要是指的顾客在网站上购买过该产品后,对该产品撰写的产品评论,主要内容其实就是购买过程和使用过程的体现,还有产品本身的评价,还有物流速度,服务态度等。其实整个电商平台很多模块主要是围绕着营销来运营的,好多用户现在就是买商品的同时,先会看一下商品评论。
商品评论这一块主要涉及到的内容有,属性集,产品和属性集关联,产品评论,另外就是和产品、产品sku关联,评论属性评分,产品评论和客户,评论回复,产品统计;另外还有就是标签(或称为话题),附件,匿名评论。
因为订单交易完成后需要评论,考虑到评论不断增加,后期评论数据比较大,所以评论这块我们采用了mongodb数据库,之所以采用mongodb是因为:MongoDB是NoSQL的非关系型数据库,易于扩展,可以进行分布式文件存储,适用于大数据量、高并发、弱事务的互联网应用,因此我在项目中使用它来存储电商产品详情页的评论信息(评论id,商品id,标题,评分,内容,评论人信息,评论的发布时间,评论标签组)并且为了提高可用性和高并发用了3台服务器做了mongodb的副本集,其中一台作为主节点,另外两台作为副本节点,这样在任何一台mongodb服务器宕机时就会自动进行故障转移,不会影响应用程序对mongodb的操作,为了减轻主节点的读写压力过大的问题,我还对mongodb副本集做了读写分离,使写操作在主节点进行,读取操作在副本节点进行。为了控制留言,我们留言的界面设置在了订单状态,只有状态为5,也就是交易成功收货后才能评论,并在评论成功后将订单状态改为6。

59.熔断隔离有几种方式?除了线程隔离还有那些?

Hystrix提供了两种隔离模式:线程池隔离模式、信号量隔离模式。
线程池隔离模式:使用一个线程池来存储当前请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求先入线程池队列。这种方式要为每个依赖服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
信号量隔离模式:使用一个原子计数器(或信号量)记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃该类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)

60.死锁

1>.Lock是一个接口,而Synchronized是Java中的关键字,Synchronized是内置语言的实现。
2>.Synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁的发生,而lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁的现象,因此使用lock是需要在finally块中释放锁。
3>.lock可以让等待锁的线程响应中断,而Synchronized却不行,使用Synchronized时,等待的线程会一直等待下去,不能够响应中断。
4>.通过Lock可以知道有没有成功获取锁,而Synchronized却无法办到
5>.Lock可以提高多个线程进行读操作的效率
在性能上来说,如果竞争资源不激烈,两者性能是差不多的,而当竞争资源非常激烈时,此时lock的性能是要远远优于Synchronized

61.秒杀/超卖 购物车

秒杀就是限时抢购:对抢购开始时间用定时器处理–要求所定时间必须是后台代码时间,因为前台代码时间是根据客户端时间来的,可以任意修改,如果不设置定时开始抢购的话,会有js高手会通过修改前台代码,或使用其他技术直接访问url路径进行购买的问题,先在前台先做一个倒计时图片作为伪路径,当到抢购时间时自动请求后台,再后台进行判断,是否确实到了抢购的时间,如果是的话就将伪路径改为真实的请求路径
在抢购的时候为了防止竞争对手的恶意购买,通过前台使用for循环无限次的刷新购买–在点击抢购时加入了然进行其他的操作 如领取该商品的优惠券,填写一些不必要的信息等可以防止高并发,和恶意的购买…
为了减少高并发我们在做后台处理时对允许访问的请求数量进行限制(通过redis对请求数量进行统计如只放过100个请求,当超过规定数量时0,提示用户商品已售完,然后再对这一百个请求进行处理(放入消息队列中,让其按顺序进行操作数据库,将排队储存引擎提前到进入数据库前, 并且采用乐观锁,和inndb储存引擎)
防止超买的话可通过sql语句进行控制如根据购买请求执行修改库存量时的语句,进行条件限制即库存量必须大于0语句如下:update t_clothing set count-1 where count >0;阻止其余的请求再操作数据库。将执行完修改操作的100个请求,根据用户的id存入redis缓存中,并设置过期时间,如规定时间内付款没有完成,提示用户付款过期且将商品的库存量增加1个;当用户支付成功且通过第三方(银行)也确定支付成功后,可进行安排发货,并将购买成功信息返回给用户。
Redis解决秒杀问题:
秒杀系统,是典型的短时大量突发访问类问题。对这类问题,有三种优化性能的思路:
1、写入内存而不是写入硬盘
传统硬盘的读写性能是相当差的。SSD硬盘比传统硬盘快100倍。而内存又比SSD硬盘快10倍以上。因此,写入内存而不是写入硬盘,就能使系统的能力提升上千倍。也就是说,原来你的秒杀系统可能需要1000台服务器支撑,现在1台服务器就可以扛住了。
你可能会有这样的疑问:写入内存而不是持久化,那么如果此时计算机宕机了,那么写入的数据不就全部丢失了吗?如果你就这么倒霉碰到服务器宕机,那你就没秒到了,有什么大不了?
最后,后面真正处理秒杀订单时,我们会把信息持久化到硬盘中。因此不会丢失关键数据。
Redis是一个缓存系统,数据写入内存后就返回给客户端了,能够支持这个特性。
2、异步处理而不是同步处理
像秒杀这样短时大并发的系统,在性能负载上有一个明显的波峰和长期的波谷。为了应对相当短时间的大并发而准备大量服务器来应对,在经济上是相当不合算的。
因此,对付秒杀类需求,就应该化同步为异步。用户请求写入内存后立刻返回。后台启动多个线程从内存池中异步读取数据,进行处理。如用户请求可能是1秒钟内进入的,系统实际处理完成可能花30分钟。那么一台服务器在异步情况下其处理能力大于同步情况下1800多倍!
异步处理,通常用MQ(消息队列)来实现。Redis可以看作是一个高性能的MQ。因为它的数据读写都发生在内存中。
3、分布式处理
如果客户还是比较多,还可以使用分布式处理,如果一台服务器撑不住秒杀系统,那么就多用几台服务器。10台不行,就上100台。分布式处理,就是把海量用户的请求分散到多个服务器上。一般使用hash实现均匀分布。redis就是一个这样的产品。
因此使用Redis或者Redis Cluster就可以轻松实现一个强大的秒杀系统。
基本上,你用Redis的这些命令就可以了。
RPUSH key value
插入秒杀请求
当插入的秒杀请求数达到上限时,停止所有后续插入。后台启动多个工作线程,使用LPOP key读取秒杀成功者的用户id,进行后续处理。或者使用LRANGE key start end命令读取秒杀成功者的用户id,进行后续处理。
每完成一条秒杀记录的处理,就执行INCR key_num。一旦所有库存处理完毕,就结束该商品的本次秒杀,关闭工作线程,也不再接收秒杀请求。
如果实现了以上功能仍然撑不住的话,就从源头触发,导致秒杀失败可能是由于脚本攻击或者交换机支撑不住,控制同一ip地址多次发起请求,比如在一秒内,最多支持一个ip发起两到三次请求,其他请求直接扔掉即可这样就可以减轻交换机压力。交换机无法支撑的话,我们可以配多台交换机为我们的秒杀系统服务,用户通过这些交换机访问后面数据中心的Redis Cluster进行秒杀作业。
关于商品加入购物车,我们当时考虑到怕顾客来回操作购物车,而且购物车里的东西未必购买.考虑到数据库压力的问题,当时决定采用redis来做这个购物车。这个购物车采用的是淘宝的模式就是在用户在未登录状态下,可以查看商品,当在详情页点击“加入购物车”或者“查看购物车”的时候我们会判断用户是否登录,如果没有登录我们会弹出一个弹框,让用户先登录,成功后跳转到登录页面,并且把商品的信息传到后台,进行保存,如果用户是登录状态,直接将商品信息传到后台,进行保存,在保存时候我们选择的是保存到redis缓存当中,(redis的读取速度可以达到11万次每秒,写的速度达到8万次每秒)将信息传到后台之后,我们会将数据全部都存放在一个对象当中,在将对象存放在list当中,然后以用户id(唯一标示)为key值,list集合为value值存放在缓存当中,当用户有新的商品添加的时候,我们通过用户id查询出来原来的信息 ,将新的信息存放在list集合中去,然后将集合在放入缓存当中,但是这个过程中需要有一点注意的地方,就是当新添加的商品本来在购物中存在,这时我们就需要进行一个判断。如果存在我们只要在原来的基础 上数量添加就行了;当用户不需要这件商品的时候点击删除的时候,我们会将商品的id获取到,同时也获取到用户id,通过用户id查询出该用户的购物车商品,然后将要删除的商品从list集合当中删除掉,再保存回去;
二为相关商品推送。
当时我们考虑到商品的销量采用的交叉销售当用户把商品加入购物车后,我们需要在购物车推荐一些热卖的类似商品。当时做的时候通过获取购物车的商品名称、品牌、类型、描述等一些信息去solr服务器进行搜索相关商品再通过逻辑判断和排序,把最接近的商品和销量高的、浏览高的在购物车进行展示
相关问题
订单支付问题
当用户点击“提交订单”时会将redis中的信息取出来放进jms消息队列的生产者里,根据不同的商家,将订单进行拆单处理路由到不同的队列里.消费端通过获取队列里的消息后操作数据库 插入到数据库的订单表中,并清除reidis中该购物车的信息,同时将订单表中该条记录的状态设置为1(未付款),设置过期时间,在规定时间内用户可以选择进行支付,支付成功后会将订单的状态改为2,并将财付通返回的交易信息存入数据库的交易记录表中,同时调用webservice接口发送短信通知用户支付成功,此时的订单已进入后台去处理,前台再查看订单时,
如果在规定时间内,用户没有支付,我们会取消订单,这样是避免其他用户不能购买到商品,我们会根据状态显示不同的信息,比如:状态1是未支付订单,状态为2的是支付成功等待审核状态,当后台审核通过状态会改为3,当发货时状态会改为4,这时客户就可以确认收货,确认收货后状态改为5。注意:订单支付采用的是 微信(腾讯),支付宝(淘宝)
如何添加到购物车,
关于购物车,我们当时考虑到怕水军来回CRUD(增读更删)购物车,对数据库造成巨大压力,当时决定采用redis来做这个购物车。
这个购物车当时想到了两种情况一种是淘宝的模式就是在用户在未登录状态下,可以查看商品,当在详情页点击“加入购物车”或者“查看购物车”的时候我们会判断用户是否登录,如果没有登录我们会弹出一个弹框,让用户先登录,成功后跳转到登录页面,并且把商品的信息传到后台,进行保存,如果用户是登录状态,直接将商品信息传到后台,进行保存,另一种就是京东不管用户是否登录都可以添加和查看购物车,先将数据全部都保存在cookie中去,然后当用户登录上去以后就会直接将cookie中的信息保存在缓存中;
我们当时有这样一个考虑,就是第一个人在查看商品添加到了购物车,但是没有登录,只是保存在了cookie中,后来第二个人也来上jd,结果第一个人的购物车商品保存在了第二个人的账户中,存在弊端,最后我们还是选用了淘宝的模式;
在保存时候我们选择的是保存到redis缓存当中,将信息传到后台之后,我们会将数据全部都存放在一个对象当中,在将对象存放在list当中,然后以用户id(唯一标示)为键,list集合为值存放在缓存当中,当用户有新的商品添加的时候,我们通过用户id查询出来原来的信息 ,将新的信息存放在list集合中去,然后将集合在放入缓存当中,但是这个过程中需要有一点注意的地方,就是当新添加的商品本来在购物中存在,这时我们就需要进行一个判断。如果存在我们只要在原来的基础上数量添加就行了;
购物车商品的删除,点击删除的时候,我们会将商品的id获取到,同时也获取到用户id,通过用户id查询出该用户的购物车商品,然后将要删除的商品从list集合当中删除掉,再保存回去
存储内容是什么
我们的购物车,用的是redis 来实现的。 当加入购物车的时候 用户id作为redis 的key,产品集合作为redis的value。商品存的是 ,商品id 商品名称,和商品购买数量。
当加入商品到购物车的时候,首先判断当前用户id对应的的产品集合里面是否含有当前产品,有则数量加一。没有则新添加该商品。

62.分布式事务、分布式锁

分布式事务:
因为我们项目比较大访问用户也比较多,我们把表都用mycat进行拆分了,我们当时拆分的方式是(说下第29题),我们在支付的时候,和下单的时候都用到了分布式事务.比如实时支付吧,一笔支付,是对买家账户进行扣款,同时对卖家账户进行加钱,这些操作必须在一个事务里执行,要么全部成功,要么全部失败。而对于买家账户属于买家中心,对应的是买家数据库,而卖家账户属于卖家中心,对应的是卖家数据库,对不同数据库的操作必然需要引入分布式事务。还有就是用户下单买家在电商平台下单,往往会涉及到两个动作,一个是扣库存,第二个是更新订单状态,库存和订单一般属于不同的数据库,需要使用分布式事务保证数据一致性。我们使用的解决方案是使用支付宝用得那个TCC补偿性分布式事务解决方案.
(what TCC是什么?)
TCC是三个英文单词的首字母缩写,分别对应Try、Confirm和Cancel三种操作,这三种操作的业务含义如下:
Try:预留业务资源
Confirm:确认执行业务操作
Cancel:取消执行业务操作

1、Try:尝试执行业务。
完成所有业务检查(一致性)
预留必须业务资源(准隔离性)
2、Confirm:确认执行业务。
真正执行业务
不做任何业务检查
只使用Try阶段预留的业务资源
3、Cancel:取消执行业务
释放Try阶段预留的业务资源
TCC的原理
我给你用这个账务拆分为说一下TCC吧,比如说我们账务拆分的业务场景是,分别位于三个不同分库的帐户A、B、C,A账户和B账户一起向C账户转帐共80元:
1、Try:尝试执行业务。
完成所有业务检查(一致性):检查A、B、C的帐户状态是否正常,帐户A的余额是否不少于30元,帐户B的余额是否不少于50元。
预留必须业务资源(准隔离性):帐户A的冻结金额增加30元,帐户B的冻结金额增加50元,这样就保证不会出现其他并发进程扣减了这两个帐户的余额而导致在后续的真正转帐操作过程中,帐户A和B的可用余额不够的情况。
2、Confirm:确认执行业务。
真正执行业务:如果Try阶段帐户A、B、C状态正常,且帐户A、B余额够用,则执行帐户A给账户C转账30元、帐户B给账户C转账50元的转帐操作。
不做任何业务检查:这时已经不需要做业务检查,Try阶段已经完成了业务检查。
只使用Try阶段预留的业务资源:只需要使用Try阶段帐户A和帐户B冻结的金额即可。
3、Cancel:取消执行业务
释放Try阶段预留的业务资源:如果Try阶段部分成功,比如帐户A的余额够用,且冻结相应金额成功,帐户B的余额不够而冻结失败,则需要对帐户A做Cancel操作,将帐户A被冻结的金额解冻掉。

(How TCC 怎么用的)
Github上有他们的源码,我们直接把源码挡下来,安装到我们本地的仓库里,用的时候我们把需要使用分布式事务的代码,上加上@Compensable注解,里面还有一些其他的属性配置上就可以了

分布式锁:
为了保证一个方法在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLcok或synchronized)进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
分布式锁应该具备哪些条件:

  1. 获取锁和释放锁的性能要好
      2. 判断是否获得锁必须是原子性的,否则可能导致多个请求都获取到锁
      3. 网络中断或宕机无法释放锁时,锁必须被清楚,不然会发生死锁
       4. 可重入一个线程中可以多次获取同一把锁,比如一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁;
       5.阻塞锁和非阻塞锁,阻塞锁即没有获取到锁,则继续等待获取锁;非阻塞锁即没有获取到锁后,不继续等待,直接返回锁失败。

悲观锁、
悲观锁,正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
之所以叫做悲观锁,是因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。
悲观锁主要分为共享锁或排他锁
共享锁【Shared lock】又称为读锁,简称S锁。顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
排他锁【Exclusive lock】又称为写锁,简称X锁。顾名思义,排他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据行读取和修改。
悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。
悲观锁
但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。
乐观锁、
乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。

使用场景
悲观锁:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
总结:两种所各有优缺点,读取频繁使用乐观锁,写入频繁使用悲观锁。

63.Java 中堆和栈有什么区别?

JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页