资讯详情

面试04——整理

1.如何模拟抽象接口

2.项目负责

3.线程和过程

4.List list =new Arraylist(Array.aslist(“a”,“b”,“c”,“d”));

for(s:list){

s.equls(“a”){

s.removes()

}

}

5.io 异常关闭 应该关闭的地方

6.public class test{

private int value;

public void get(int x){

this.value=x

}

public void increment(){

this.value;

}

}

7.如何管理设备,设备从何而来。

设备怎么接受我们发送的消息。

项目文档

软件自动生成接口文档

kafka组件有哪些

http https 区别

项目如何部署和部署流程?

netty

explain

sql 如何优化索引?sql

悲观乐观

字符串反转 用revers 位与运算符

常用的容器类型

并发容器

jdk 动态代理,class在内存中缓存文件,在运行过程中动态生成代理进行处理,只能基于接口进行代理。 cglib 通过继承代理,动态代理,

还有就是jdk 只能代理接口,局限性 cglib 可以代理类

就我个人而言,我认为局限性只能通过类来处理,但是呢?cglib可以处理方法,如一些逻辑组件,使用代理处理。然后,逻辑组件可以通过切割来适应所有方法。我感觉很酷

所以我通常会写切面处理日志或一些解耦的东西cglib处理的

可见性

对于可见性,Java提供了volatile确保可见性的关键字。当共享变量被时volatile修改时,它将确保修改后的值立即更新到主存,当需要读取其他线程时,它将在内存中读取新值。普通共享变量不能保证可见性,因为不确定普通共享变量修改后何时写入主存。当读取其他线程时,内存可能仍然是原始值,因此不能保证可见性。

另外,通过synchronized和Lock也能保证可见性,synchronized和Lock它可以确保在同一时刻只有一个线程获取锁,然后执行同步代码,并在释放锁之前将变量的修改刷新到主存储器。因此,可以保证可见性。

有序性

在Java在内存模型中,编译器和处理器允许重新排序指令,但重新排序过程不会影响单线程序的执行,而是影响多线程并发执行的正确性。

在Java里面,可以通过volatile关键词保证一定的有序性(具体原理在下一节描述)。另外可以通过synchronized和Lock很明显,要保证有序性,synchronized和Lock确保每时每刻都有一个线程执行同步代码,相当于让线程顺序执行同步代码,自然保证了有序性。另外,Java内存模型有一些先天的有序性,即无需任何手段即可保证的有序性,通常称为 happens-before 原则。如果两个操作的执行顺序不能从happens-before如果推导出原则,就不能保证它们的有序性,虚拟机可以随意重新排序。

volatile本质是在告诉jvm寄存器中当前变量的值不确定,需要从主存中读取,synchronized锁定当前变量,只有当前线程可以访问该变量,其他线程被堵塞. volatile仅用于变量级,synchronized可用于变量,方法. volatile修改变量的可见性只能实现,但没有原子特性,synchronized可以保证变量修改的可见性和原子性. volatile不会造成线程堵塞,而是synchronized可能导致线程堵塞. volatile编译器不会优化标记的变量,synchronized编译器可以优化标记的变量. synchronized它非常强大,既能保证可见性,又能保证原子性volatile不能保证原子性!

考虑到运行速率,java编译器将频繁访问的变量放入缓存中(严格来说应该是工作内存),读取变量从缓存中读取。但在多线程编程中,内存中的值可能与缓存中的值不一致。volatile限制变量只能从内存中读取,以确保所有线程的值都是一致的。但是volatile不能保证原子性,也不能保证线程安全。

select * from (select * from 表名 order by 字段 desc limit 10) 临时表 order by 字段

1. 服务雪崩

在多个微服务之间调用时,假设微服务A调用微服务B和微服务C,微服务B和微服务C调用其他微服务,这就是所谓的。如果微服务的呼叫响应过长或不可用,微服务A的呼叫将占用越来越多的系统资源,导致系统雪崩,所谓的雪崩效应。

2. 服务熔断

熔断机制是处理雪崩效应的微服务链路保护机制。当风扇链路的微服务不可用或响应时间过长时,服务将降级,然后呼叫节点微服务,快速返回错误的响应信息。当检测到节点微服务响应正常时,恢复呼叫链路。

3. 服务降级

服务降级意味着当服务熔断时,服务将不再被调用。此时此刻,客户端需要执行备用逻辑,并返回缺省值。

synchronized

“就是因为synchronized性能低,有人开发了ReentrantLock,性能大大提高,涉及到AQS高并发组件,用它来维护锁的状态,所以不需要使用操作系统来维护,减少上下文切换,基本上是Java操作可以在级别上完成。也用在里面。CAS,自旋等操作可以提高性能。但是线程过程…”

解释为什么jdk1.6以前synchronized性能低下的原因是重量级锁? 为什么是重量级锁?

在此版本之前,加锁操作涉及操作系统进行互斥操作,即将当前线程挂起,然后操作系统进行互斥操作修改mutexLock来完成,然后醒来。操作系统判断线程是否锁定,因此是重量级操作。上下文切换两次挂起唤醒。CPU,降低性能。其实这个版本之前已经考虑过了CAS操作,但默认不打开。

当然,只要操作系统维护锁的状态挂起当前线程,synchronized,竞争也会导致阻塞,阻塞和唤醒操作涉及上下文操作,消耗大量CPU,降低性能。

ResultMap和ResultType的

ResultMap和ResultType但是ResultMap更强一点,ResultMap可以将查询结果映射成复杂类型pojo。

@Transactional失效场景 声明式事务失效的场景有很多,陈某这里只是罗列一下几种常见的场景。

底层数据库引擎不支持事务 如果数据库引擎不支持事务,Spring自然不能支持事务。

在非public使用修饰方法 @Transactional使用的注释是AOP,使用动态代理时,只能针对public代理方法

在整个事务中使用try-catch,异常无法抛出,自然会导致事务失效。

调用类似方法的方法

  • 简单地说,它是一个类别A方法内部调用(未标注声明事务)B方法(标注声明事务),会导致B方法中的事务失效。

原始SSM项目重复扫描导致事务失败 在原始的SSM所有全部配置context:component-scan同时扫描service此时事务将失效。

按照Spring对于配置文件的加载顺序,将首先加载Springmvc加载的配置文件Springmvc配置文件时service但此时事务尚未加载,将导致事务无法成功生效。

juc包括四个并发工具

解决方案很简单,扫描service层的配置设置在Spring配置文件或者其他配置文件中即可juc.CountDownLatch 闭锁

在等待一组线程后恢复执行

ConcurrentHashMap

JDK8中ConcurrentHashMap参考了JDK8 HashMap采用数组实现 链表 红黑树的实现方式来设计,内部大量采用CAS操作并发控制(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个看起来像是优化和安全的线程 HashMap,虽然在 JDK1.8 中还能看到 Segent 的数据结构,但 是已经简化了属性,只是为了兼容旧版本;

JDK1.8的Nod节点中value和next都用volatile修饰,保证并发的可见性。

我们知道,当前的应用都离不开数据库,随着数据库中的数据越来越多,单表突破性能上限记录时,如MySQL单表上线估计在近千万条内,当记录数继续增长时,从性能考虑,则需要进行拆分处理。而拆分分为横向拆分和纵向拆分。一般来说,采用横向拆分较多,这样的表结构是一致的,只是不同的数据存储在不同的数据库表中。其中横向拆分也分为分库和分表。

为什么决定进行分库分表

\1. 根据业务类型,和业务容量的评估,来选择和判断是否使用分库分表。 \2. 当前数据库本事具有的能力,压力的评估。 \3. 数据库的物理隔离,例如减少锁的争用、资源的消耗和隔离等。 \4. 热点表较多,并且数据量大,可能会导致锁争抢,性能下降。 \5. 数据库的高并发,数据库的读写压力过大,可能会导致数据库或系统宕机。 \6. 数据库(MySQL5.7以下)连接数过高,会增加系统压力。 \7. 单表数据量大,如SQL使用不当,会导致io随机读写比例高。查询慢(大表上的B+树太大,扫描太慢,甚至可能需要4层B+树) \8. 备份和恢复时间比较长。

作者:阿里技术 链接:https://www.zhihu.com/question/448775613/answer/1774351830 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1 什么是分库分表?

其实就是字面意思,很好理解:

  • 分库:从单个数据库拆分成多个数据库的过程,将数据散落在多个数据库中。
  • 分表:从单张表拆分成多张表的过程,将数据散落在多张表内。

2 为什么要分库分表?

关键字:提升性能、增加可用性。

从性能上看

随着单库中的数据量越来越大、数据库的查询QPS越来越高,相应的,对数据库的读写所需要的时间也越来越多。数据库的读写性能可能会成为业务发展的瓶颈。对应的,就需要做数据库性能方面的优化。本文中我们只讨论数据库层面的优化,不讨论缓存等应用层优化的手段。

如果数据库的查询QPS过高,就需要考虑拆库,通过分库来分担单个数据库的连接压力。比如,如果查询QPS为3500,假设单库可以支撑1000个连接数的话,那么就可以考虑拆分成4个库,来分散查询连接压力。

如果单表数据量过大,当数据量超过一定量级后,无论是对于数据查询还是数据更新,在经过索引优化等纯数据库层面的传统优化手段之后,还是可能存在性能问题。这是量变产生了质变,这时候就需要去换个思路来解决问题,比如:从数据生产源头、数据处理源头来解决问题,既然数据量很大,那我们就来个分而治之,化整为零。这就产生了分表,把数据按照一定的规则拆分成多张表,来解决单表环境下无法解决的存取性能问题。

从可用性上看

单个数据库如果发生意外,很可能会丢失所有数据。尤其是云时代,很多数据库都跑在虚拟机上,如果虚拟机/宿主机发生意外,则可能造成无法挽回的损失。因此,除了传统的 Master-Slave、Master-Master 等部署层面解决可靠性问题外,我们也可以考虑从数据拆分层面解决此问题。

此处我们以数据库宕机为例:

  • 单库部署情况下,如果数据库宕机,那么故障影响就是100%,而且恢复可能耗时很长。
  • 如果我们拆分成2个库,分别部署在不同的机器上,此时其中1个库宕机,那么故障影响就是50%,还有50%的数据可以继续服务。
  • 如果我们拆分成4个库,分别部署在不同的机器上,此时其中1个库宕机,那么故障影响就是25%,还有75%的数据可以继续服务,恢复耗时也会很短。

当然,我们也不能无限制的拆库,这也是牺牲存储资源来提升性能、可用性的方式,毕竟资源总是有限的。

二 如何分库分表

1 分库?分表?还是既分库又分表?

从第一部分了解到的信息来看,分库分表方案可以分为下面3种:

img

2 如何选择我们自己的切分方案?

如果需要分表,那么分多少张表合适?

由于所有的技术都是为业务服务的,那么,我们就先从数据方面回顾下业务背景。

比如,我们这个业务系统是为了解决会员的咨询诉求,通过我们的XSpace客服平台系统来服务会员,目前主要以同步的离线工单数据作为我们的数据源来构建自己的数据。

假设,每一笔离线工单都会产生对应一笔会员的咨询问题(我们简称:问题单),如果:

  • 在线渠道:每天产生 3w 笔聊天会话,假设,其中50%的会话会生成一笔离线工单,那么每天可生成 3w * 50% = 1.5w 笔工单;
  • 热线渠道:每天产生 2.5w 通电话,假设,其中80%的电话都会产生一笔工单,那么每天可生成 2.5w * 80% = 2w 笔/天;
  • 离线渠道:假设离线渠道每天直接生成 3w 笔;

合计共 1.5w + 2w + 3w = 6.5w 笔/天

考虑到以后可能要继续覆盖的新的业务场景,需要提前预留部分扩展空间,这里我们假设为每天产生 8w 笔问题单。

除问题单外,还有另外2张常用的业务表:用户操作日志表、用户提交的表单数据表。

其中,每笔问题单都会产生多条用户操作日志,根据历史统计数据来可以看到,平均每个问题单大约会产生8条操作日志,我们预留一部分空间,假设每个问题单平均产生约10条用户操作日志。

如果系统设计使用年限5年,那么问题单数据量大约 = 5年 365天/年 8w/天 = 1.46亿,那么估算出的表数量如下:

  • 问题单需要:1.46亿/500w = 29.2 张表,我们就按 32 张表来切分;
  • 操作日志需要 :32 10 = 320 张表,我们就按 32 16 = 512 张表来切分。

如果需要分库,那么分多少库合适?

分库的时候除了要考虑平时的业务峰值读写QPS外,还要考虑到诸如双11大促期间可能达到的峰值,需要提前做好预估。

根据我们的实际业务场景,问题单的数据查询来源主要来自于阿里客服小蜜首页。因此,可以根据历史QPS、RT等数据评估,假设我们只需要3500数据库连接数,如果单库可以承担最高1000个数据库连接,那么我们就可以拆分成4个库。

3 如何对数据进行切分?

根据行业惯例,通常按照 水平切分、垂直切分 两种方式进行切分,当然,有些复杂业务场景也可能选择两者结合的方式。

(1)水平切分

这是一种横向按业务维度切分的方式,比如常见的按会员维度切分,根据一定的规则把不同的会员相关的数据散落在不同的库表中。由于我们的业务场景决定都是从会员视角进行数据读写,所以,我们就选择按照水平方式进行数据库切分。

(2)垂直切分

垂直切分可以简单理解为,把一张表的不同字段拆分到不同的表中。

比如:假设有个小型电商业务,把一个订单相关的商品信息、买卖家信息、支付信息都放在一张大表里。可以考虑通过垂直切分的方式,把商品信息、买家信息、卖家信息、支付信息都单独拆分成独立的表,并通过订单号跟订单基本信息关联起来。

也有一种情况,如果一张表有10个字段,其中只有3个字段需要频繁修改,那么就可以考虑把这3个字段拆分到子表。避免在修改这3个数据时,影响到其余7个字段的查询行锁定。

sql查询数据库最后10条记录并按降序排列

SELECT TOP 10 FROM 表名 ORDER BY 排序列 DESC;SQL的执行顺序先按照你的要求排序,然后才返回查询的内容。例如有一个名为ID自动增长的列,表中有100条数据,列的值得分别是1、2、3、4………9、99、100。那么查询加了DESC你得到的是91到100条,就是最后十条,如果加ASC你得到的将会是1到10,也就是最前面的那几条。记录如果说有先后的话 必然是根据某几个字段进行排序了的你反过来排序就变成求前10条记录了呗,把desc和 asc互换一下 (默认是 asc )oracle 的写法select * from (select * from tab order by col desc ) where rownum <= 10赞同最后10条降序与最前10条升序是一样的如果还想排序,那就按他们说的用临时表。select top 10 * from table 1 order by field1 into table #tempselect * from #temp order by field1 desc //查询结果放临时表select * top 10 from table1 order by field1 asc into tabl temp //再从临时表查询select * from temp order by field1 desc

一.git提交代码步骤

1.拉取远程的代码,先pull,查看有哪些差异。 git pull

2.备份自己的文件,把所有差异还原。

3.再次pull,成功后在具体的文件中,把自己的代码粘贴复制过去,再次pull。

4.提交代码到本地 git add . git commit -m ‘修改注释’

5.推送代码到远程

备注:(1)如果是新增的文件,需要先新增,再从第2步开始。

​ (2)第2/3步骤适用于不会解决冲突的人。

二.工作中常用的git命令

1.回退历史版本

(1)git log 查看提交记录 copy 历史版本id

(2)git reset --hard 复制的历史版本id

(3)如果是取消最近一次的commit 保留本地文件修改 git reset HEAD

(4)回退并推送至远程分支 git push -f origin master

2.回退某个文件

(1)到该文件的文件夹下,打开命令面板

(2)git log 文件名.文件格式

(3)git reset 版本号 文件名.文件格式

(4)如果还想远程也回退版本 git push -f

(5)如果需要放弃本地该文件的修改 git checkout .

3.删除缓存的远程分支列表

(1)git remote prune origin

(2)git fetch -p

(3)git checkout . && git clean -xdf 抛弃本地修改

4.创建分支

在哪个分支运行的命令,就是从哪个分支为基础拉新的分支。

(1)git checkout -b dev 创建dev分支并切换到dev分支

相当于 git branch dev 与 git checkout dev 两个命令

(2)git push origin dev 把dev分支推送至远程

(3)git branch --set-upstream-to origin/dev 把本地当前的分支与远程dev分支 然后就可以用git push 推送代码到远程dev分支了

5.合并分支

切换到想要合并其他分支的分支 一般为master

(1)git checkout master

(2)git merge dev 合并dev分支到master

(3)如果合并之后 dev分支没用了 ,删除dev分支 git branch -d dev

6.添加远程分支

fork代码到私人仓库,从私有仓库拉取的代码后,需要添加远程分支

git remote add 本地远程仓库名称(自己起的有意义能识别的名称) remote-http-adress(远程仓库的克隆地址)

比如远程仓库命名为 remoteApp 仓库地址为http://remote.com,那么命令为:git remote add remoteApp http://remote.com

7.拉取远程分支代码

git pull remoteName branchName

比如git pull remoteApp master

8.查看有哪些分支

git branch -a a可以理解为all 所有

9.git pull出现合并的提示消息,按照如下图片操作,忘记在哪个博客截图的图片了,非原创。

10.修改分支命名

(1)如果还没有推送到远程:git branch -m oldName newName

(2)已经推送到了远程:

1)重命名远程分支对于的本地分支 git branch -m oldName newName

​ 2)删除远程分支 git push --delete origin oldName

3)上传新命名的本地分支 git push origin newName

4)把修改过后的本地分支与远程分支关联 git branch --set-upstream-to origin/newName

CAS

、对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

2、对于资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。以

死锁的条件

1、互斥条件:一个资源每次只能被一个进程使用;

2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;

3、不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;

4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;

死锁概念及产生原理

​ 概念: 多个并发进程因争夺系统资源而产生相互等待的现象。 ​ 原理: 当一组进程中的每个进程都在等待某个事件发生,而只有这组进程中的其他进程才能触发该事件,这就称这组进程发生了死锁。 ​ 本质原因: ​ 1)、系统资源有限。 ​ 2)、进程推进顺序不合理。

死锁产生的4个必要条件 1、互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。 2、占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。 3、不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。 4、循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。 当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。

避免死锁的几种方式: 设置加锁顺序

设置加锁时限

死锁检测

设置加锁顺序(线程按照一定的顺序加锁): 死锁发生在多个线程需要相同的锁,但是获得不同的顺序。

假如一个线程需要锁,那么他必须按照一定得顺序获得锁。 例如加锁顺序是A->B->C,现在想要线程C想要获取锁,那么他必须等到线程A和线程B获取锁之后才能轮到他获取。(排队执行,获取锁)

缺点: 按照顺序加锁是一种有效的死锁预防机制。但是,这种方式需要你事先知道所有可能会用到的锁,并知道他们之间获取锁的顺序是什么样的。

设置加锁时限:(超时重试) 在获取锁的时候尝试加一个获取锁的时限,超过时限不需要再获取锁,放弃操作(对锁的请求。)。

若一个线程在一定的时间里没有成功的获取到锁,则会进行回退并释放之前获取到的锁,然后等待一段时间后进行重试。在这段等待时间中其他线程有机会尝试获取相同的锁,这样就能保证在没有获取锁的时候继续执行比的事情。

缺点: 但是由于存在锁的超时,通过设置时限并不能确定出现了死锁,每种方法总是有缺陷的。有时为了执行某个任务。某个线程花了很长的时间去执行任务,如果在其他线程看来,可能这个时间已经超过了等待的时限,可能出现了死锁。

在大量线程去操作相同的资源的时候,这个情况又是一个不可避免的事情,比如说,现在只有两个线程,一个线程执行的时候,超过了等待的时间,下一个线程会尝试获取相同的锁,避免出现死锁。但是这时候不是两个线程了,可能是几百个线程同时去执行,大的基数让事件出现的概率变大,假如线程还是等待那么长时间,但是多个线程的等待时间就有可能重叠,因此又会出现竞争超时,由于他们的超时发生时间正好赶在了一起,而超时等待的时间又是一致的,那么他们下一次又会竞争,等待,这就又出现了死锁。

死锁检测: 当一个线程获取锁的时候,会在相应的数据结构中记录下来,相同下,如果有线程请求锁,也会在相应的结构中记录下来。当一个线程请求失败时,需要遍历一下这个数据结构检查是否有死锁产生。

例如:线程A请求锁住一个方法1,但是现在这个方法是线程B所有的,这时候线程A可以检查一下线程B是否已经请求了线程A当前所持有的锁,像是一个环,线程A拥有锁1,请求锁2,线程B拥有锁2,请求锁1。 当遍历这个存储结构的时候,如果发现了死锁,一个可行的办法就是释放所有的锁,回退,并且等待一段时间后再次尝试。

缺点: 这个这个方法和上面的超时重试的策略是一样的。但是在大量线程的时候问题还是会出现和设置加锁时限相同的问题。每次线程之间发生竞争。

预防死锁

还有一种解决方法是设置线程优先级,这样其中几个线程回退,其余的线程继续保持着他们获取的锁,也可以尝试随机设置优先级,这样保证线程的执行。

数据的锁定分为两种,第一种叫作悲观锁,第二种叫作乐观锁。

1、悲观锁,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住。【数据锁定:数据将暂时不会得到修改】

2、乐观锁,认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息。让用户决定如何去做。

乐观锁

理解:

  1. 乐观锁是一种思想,具体实现是,表中有一个版本字段,第一次读的时候,获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样。如果一样更新,反之拒绝。

之所以叫乐观,因为这个模式没有从数据库加锁。

  1. 悲观锁是读取的时候为后面的更新加锁,之后再来的读操作都会等待。这种是数据库锁

乐观锁优点程序实现,不会存在死锁等问题。他的适用场景也相对乐观。阻止不了除了程序之外的数据库操作。

悲观锁是数据库实现,他阻止一切数据库操作。

再来说更新数据丢失,所有的读锁都是为了保持数据一致性。乐观锁如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户从新操作。悲观锁则会等待前一个更新完成。这也是区别。具体业务具体分析

MySQL的四种事务隔离级别

**2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

**

**1、事务隔离级别为读提交时,写数据只会锁住相应的行****

Java并发编程:volatile关键字解析

如何保证Redis和数据库双写一致性的问题?

Redis在国内各大公司都很热门,比如新浪、阿里、腾讯、百度、美团、小米等。Redis也是大厂面试最爱问的,尤其是Redis客户端、Redis高级功能、Redis持久化和开发运维常用问题探讨、Redis复制的原理和优化策略、Redis分布式解决方案等。

关于Redis的这8问,你能答上来几个?

1、为什么使用Redis

项目中使用Redis,主要考虑性能和并发。如果仅仅是分布式锁这些,完全可以用中间件ZooKeeper等代替。

性能:

如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用Redis做一个缓冲操作,让请求先访问到Redis,而不是直接访问数据库。

根据交互效果的不同,响应时间没有固定标准。在理想状态下,我们的页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。

并发:

如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用Redis做一个缓冲操作,让请求先访问到Redis,而不是直接访问数据库。

2、使用Redis有什么缺点?

缓存和数据库双写一致性问题
缓存雪崩问题
缓存击穿问题
缓存的并发竞争问题

3、单线程的Redis为什么这么快?

你知道Redis是单线程工作模型吗?

纯内存操作
单线程操作,避免了频繁的上下文切换
采用了非阻塞I/O多路复用机制

4、Redis的数据类型及使用场景

(这5种类型你用到过几个?)

String:一般做一些复杂的计数功能的缓存;

Hash:单点登录;

List:做简单的消息队列的功能;

Set:做全局去重的功能;

SortedSet:做排行榜应用,取TOPN操作;延时任务;做范围查找。

5、Redis过期策略和内存淘汰机制?

正解:Redis采用的是定期删除+惰性删除策略。

为什么不用定时删除策略?
定期删除+惰性删除是如何工作的呢?
采用定期删除+惰性删除就没其他问题了么?

6、Redis和数据库双写一致性问题

(最终一致性和强一致性)

如果对数据有强一致性要求,不能放缓存。

7、如何应对缓存穿透和缓存雪崩问题

缓存穿透:即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

缓存雪崩:即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。

中小型的公司一般遇不到这些问题,但是大并发的项目,流量有几百万左右,这两个问题一定要深刻考虑。

8、如何解决Redis并发竞争Key问题?

这个问题大致就是,同时有多个子系统去set一个key。不太推荐使用redis的事务机制。

(1)如果对这个key操作,不要求顺序

这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可。

(2)如果对这个key操作,要求顺序

假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.

期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下

系统A key 1 {valueA 3:00} 系统B key 1 {valueB 3:05} 系统C key 1 {valueC 3:10}

那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。

其他方法,比如利用队列,将set方法变成串行访问也可以。总之,灵活变通。

烟哥彩蛋

在面试中如果碰到下列问题,如何应用上本篇的知识呢?先明确一点,我推荐的是Redis Cluster。 OK,开始举例说明

问题1:懂Redis事务么? 正常版:Redis事务是一些列redis命令的集合,blabla… 高调版: 我们在生产上采用的是Redis Cluster集群架构,不同的key是有可能分配在不同的Redis节点上的,在这种情况下Redis的事务机制是不生效的。其次,Redis事务不支持回滚操作,简直是鸡肋!所以基本不用!

问题2:Redis的多数据库机制,了解多少? 正常版:Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,单机下的redis可以支持16个数据库(db0 ~ db15) 高调版: 在Redis Cluster集群架构下只有一个数据库空间,即db0。因此,我们没有使用Redis的多数据库功能!

问题3:Redis集群机制中,你觉得有什么不足的地方吗? 正常版: 不知道 高调版: 假设我有一个key,对应的value是Hash类型的。如果Hash对象非常大,是不支持映射到不同节点的!只能映射到集群中的一个节点上!还有就是做批量操作比较麻烦!

问题4:懂Redis的批量操作么? 正常版: 懂一点。比如mset、mget操作等,blabla 高调版: 我们在生产上采用的是Redis Cluster集群架构,不同的key会划分到不同的slot中,因此直接使用mset或者mget等操作是行不通的。

问题5:那在Redis集群模式下,如何进行批量操作? 正常版:不知道 高调版:这个问题其实可以写一篇文章了,改天写。这里说一种有一个很简单的答法,足够面试用。即: 如果执行的key数量比较少,就不用mget了,就用串行get操作。如果真的需要执行的key很多,就使用Hashtag保证这些key映射到同一台Redis节点上。简单来说语法如下

对于key为{foo}.student1、{foo}.student2,{foo}student3,这类key一定是在同一个redis节点上。因为key中“{}”之间的字符串就是当前key的hash tags, 只有key中{ }中的部分才被用来做hash,因此计算出来的redis节点一定是同一个!

ps:如果你用的是Proxy分片集群架构,例如Codis这种,会将mget/mset的多个key拆分成多个命令发往不同得Redis实例,这里不多说。我推荐答的还是Redis Cluster。

问题6:你们有对Redis做读写分离么? 正常版:没有做,至于原因额。。。额。。。额。。没办法了,硬着头皮扯~ 高调版:不做读写分离。我们用的是Redis Cluster的架构,是属于分片集群的架构。而Redis本身在内存上操作,不会涉及IO吞吐,即使读写分离也不会提升太多性能,Redis在生产上的主要问题是考虑容量,单机最多10-20G,key太多降低Redis性能.因此采用分片集群结构,已经能保证了我们的性能。其次,用上了读写分离后,还要考虑主从一致性,主从延迟等问题,徒增业务复杂度。

list遍历并删除

public void testDel() { List list = Lists.newArrayList(); list.add(1); list.add(2); list.add(2); list.add(2); Iterator iterator = list.iterator(); while (iterator.hasNext()) { Integer integer = iterator.next(); if (integer == 2) iterator.remove(); } }

LeetCode(3):无重复字符的最长子串

“SpringCloud和Dubbo都是当下流行的RPC框架,各自都集成了服务发现和治理组件。SpringCloud用Eureka,Dubbo用Zookeeper。”

单一应用架构,当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的 数据访问框架(ORM)是关键。 垂直应用架构,当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的 Web框架(MVC)是关键。 分布式服务架构,当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的 分布式服务框架(RPC)是关键。 流动计算架构当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的 资源调度和治理中心(SOA)是关键。

  • 理解为啥说“http协议一般会使用JSON报文,消耗会更大”?可以解释一下吗?

  • 数据底层是二进制流,直来直往快一些,无论是json还是xml都需要转换,会影响速率

  • json和xml这些都是字符了,而且里面的很多内容都是多余的,这种格式很直白但是效率不高,不如直接基于字节的协议

  • 1、dubbo由于是二进制的传输,占用带宽会更少

    2、springCloud是http协议传输,带宽会比较多,同时使用http协议一般会使用JSON报文,消耗会更大

    3、dubbo的开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决

    4、springcloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级

    5、dubbo的注册中心可以选择zk,redis等,springcloud的注册中心用eureka或者Consul

    1、dubbo由于是二进制的传输,占用带宽会更少

    2、springCloud是http协议传输,带宽会比较多,同时使用http协议一般会使用JSON报文,消耗会更大

    3、dubbo的开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决

    4、springcloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级

    5、dubbo的注册中心可以选择zk,redis等,springcloud的注册中心用eureka或者Consul ———————————————— 版权声明:本文为CSDN博主「程序大视界」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/xuri24/article/details/89283802

我搭建的kafka集群宕机

我搭建的kafka集群中包含3个节点,当我先随机杀掉两个节点时,第3个节点成为leader,整个集群仍然可用,当我再杀掉唯一的节点时,kafka集群不可用。

但是我发现应用程序的日志一直在重连最后宕机的leader,并没有重连之前两个宕机的节点。

然后,我恢复了先杀掉的两个kafka节点,此时kafka集群可用,但我发现kafka客户端仍然没有重连恢复的节点,而是一直保持与最后死掉leader的重连。

但是其他节点恢复后成为新的leader了,客户端也没有重连,也就造成了全部节点宕机进行恢复时,必须恢复最后宕机的leader,否则,kafka集群虽然已经可用,但是应用程序仍然无法正常使用。

不知道这个问题怎么解决。

Linux,免费开源,多用户多任务系统。基于Linux有多个版本的衍生。RedHat、Ubuntu、Debian

安装VMware或VirtualBox虚拟机。具体安装步骤,找百度。

再安装Ubuntu。具体安装步骤,找百度。

安装完后,可以看到Linux系统的目录结构,见链接http://www.cnblogs.com/laov/p/3409875.html

ls   显示文件或目录

-l 列出文件详细信息l(list)

-a 列出当前目录下所有文件及目录,包括隐藏的a(all)

mkdir 创建目录

-p 创建目录,若无父目录,则创建p(parent)

cd 切换目录

touch 创建空文件

echo 创建带有内容的文件。

cat 查看文件内容

cp 拷贝

mv 移动或重命名

rm 删除文件

-r 递归删除,可删除子目录及文件

-f 强制删除

find 在文件系统中搜索某文件

wc 统计文本中行数、字数、字符数

grep 在文本文件中查找某个字符串

rmdir 删除空目录

tree 树形结构显示目录,需要安装tree包

pwd 显示当前目录

ln 创建链接文件

more、less 分页显示文本文件内容

head、tail 显示文件头、尾内容

ctrl+alt+F1 命令行全屏模式

stat 显示指定文件的详细信息,比ls更详细

who 显示在线登陆用户

whoami 显示当前操作用户

hostname 显示主机名

uname 显示系统信息

top 动态显示当前耗费资源最多进程信息

ps 显示瞬间进程状态 ps -aux

du 查看目录大小 du -h /home带有单位显示目录信息

df 查看磁盘大小 df -h 带有单位显示磁盘信息

ifconfig 查看网络情况

ping 测试网络连通

netstat 显示网络状态信息

man 命令不会用了,找男人 如:man ls

clear 清屏

alias 对命令重命名 如:alias showmeit=“ps -aux” ,另外解除使用unaliax showmeit

kill 杀死进程,可以先用ps 或 top命令查看进程的id,然后再用kill命令杀死进程。

gzip:

bzip2:

tar: 打包压缩

-c 归档文件

-x 压缩文件

-z gzip压缩文件

-j bzip2压缩文件

-v 显示压缩或解压缩过程 v(view)

-f 使用档名

例:

tar -cvf /home/abc.tar /home/abc 只打包,不压缩

tar -zcvf /home/abc.tar.gz /home/abc 打包,并用gzip压缩

tar -jcvf /home/abc.tar.bz2 /home/abc 打包,并用bzip2压缩

当然,如果想解压缩,就直接替换上面的命令 tar -cvf / tar -zcvf / tar -jcvf 中的“c” 换成“x” 就可以了。

shutdown

-r 关机重启

-h 关机不重启

now 立刻关机

halt 关机

reboot 重启

将一个命令的标准输出作为另一个命令的标准输入。也就是把几个命令组合起来使用,后一个命令除以前一个命令的结果。

例:grep -r “close” /home/* | more 在home目录下所有文件中查找,包括close的文件,并分页输出。

(Debian Package)管理工具,软件包名以.deb后缀。这种方法适合系统不能联网的情况下。

比如安装tree命令的安装包,先将tree.deb传到Linux系统中。再使用如下命令安装。

sudo dpkg -i tree_1.5.3-1_i386.deb 安装软件

sudo dpkg -r tree 卸载软件

注:将tree.deb传到Linux系统中,有多种方式。VMwareTool,使用挂载方式;使用winSCP工具等;

(Advanced Packaging Tool)高级软件工具。这种方法适合系统能够连接互联网的情况。

依然以tree为例

sudo apt-get install tree 安装tree

sudo apt-get remove tree 卸载tree

sudo apt-get update 更新软件

sudo apt-get upgrade

将.文件转为.文件

.rpm为RedHat使用的软件格式。在Ubuntu下不能直接使用,所以需要转换一下。

sudo alien abc.rpm

vim三种模式:命令模式、插入模式、编辑模式。使用ESC或i或:来切换模式。

命令模式下:

:q 退出

:q! 强制退出

:wq 保存并退出

:set number 显示行号

:set nonumber 隐藏行号

/apache 在文档中查找apache 按n跳到下一个,shift+n上一个

yyp 复制光标所在行,并粘贴

h(左移一个字符←)、j(下一行↓)、k(上一行↑)、l(右移一个字符→)

/etc/passwd 存储用户账号

/etc/group 存储组账号

/etc/shadow 存储用户账号的密码

/etc/gshadow 存储用户组账号的密码

useradd 用户名

userdel 用户名

adduser 用户名

groupadd 组名

groupdel 组名

passwd root 给root设置密码

su root

su - root

/etc/profile 系统环境变量

bash_profile 用户环境变量

.bashrc 用户环境变量

su user 切换用户,加载配置文件.bashrc

su - user 切换用户,加载配置文件/etc/profile ,加载bash_profile

sudo chown [-R] owner[:group] {File|Directory}

例如:还以jdk-7u21-linux-i586.tar.gz为例。属于用户hadoop,组hadoop

要想切换此文件所属的用户及组。可以使用命令。

sudo chown root:root jdk-7u21-linux-i586.tar.gz

三种基本权限

R 读 数值表示为4

W 写 数值表示为2

X 可执行 数值表示为1

如图所示,jdk-7u21-linux-i586.tar.gz文件的权限为-rw-rw-r–

-rw-rw-r–一共十个字符,分成四段。

第一个字符“-”表示普通文件;这个位置还可能会出现“l”链接;“d”表示目录

第二三四个字符“rw-”表示当前所属用户的权限。 所以用数值表示为4+2=6

第五六七个字符“rw-”表示当前所属组的权限。 所以用数值表示为4+2=6

第八九十个字符“r–”表示其他用户权限。 所以用数值表示为4

所以操作此文件的权限用数值表示为664

sudo chmod [u所属用户 g所属组 o其他用户 a所有用户] [+增加权限 -减少权限] [r w x] 目录名

例如:有一个文件filename,权限为“-rw-r----x” ,将权限值改为"-rwxrw-r-x",用数值表示为765

sudo chmod u+x g+w o+r filename

上面的例子可以用数值表示

sudo chmod 765 filename

首先,说大概说一下事务传播行为,随后讲事务失效,具体分析同一个类里方法调用造成事务失效的情况,再到事务传播行为应该在不同类的事务方法传播,最后讲会如何传播。

0. 事务传播行为大概认识

什么叫?听起来挺高端的,其实很简单。 即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。

事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。 例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

我后面还会进一步分析,先别急。在具体深入讲这个传播行为前。我必须先把失效给讲了。

1.事务失效的几种可能

不生效的原因是AOP导致的,重新组建字节码的时候,没有办法再次判断嵌套里面的那个方法是否有注解,这是AOP相关问题,与本文阐述的事务本身无关,但是其特性会让人觉得是spring事务自身的问题

1、标注事务的方法不是public的

2、你的异常类型不是unchecked异常 如果我想check异常也想回滚怎么办,需要注解上面写明异常类型即可

@Transactional(rollbackFor=Exception.class) 1 类似的还有norollbackFor,自定义不回滚的异常

3、数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的

4、是否开启了对注解的解析

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

5、spring是否扫描到你这个包,如下是扫描到org.test下面的包

<context:component-scan base-package="org.test" ></context:component-scan>

6、检查是不是同一个类中的方法调用(如a方法调用同一个类中的b方法)

这点非常重要,同一个类的方法调用,会导致事务无效。

7、UNCHECKED异常是不是被你catch住了

2.为什么同一个类的方法调用会导致事务无效呢

下面这段代码,事务会失效,并不会回滚。 原因就在addUser(“13522203330”); 其实是this.addUser(“13522203330”); 而这个THIS,并不是被SPRING 用CGLIB增强的类。也就是没有被代理,自然没有做事务了。

@Service
public class TransactionalAopServiceImpl implements TransactionalAopService { 
        

    @Autowired
    private OrderDao orderDao;
    @Autowired
    private UserDao userDao;

    public void addOrder() { 
        
        orderDao.insert(OrderModel.builder()
                .userId("YK_002") //游客编号
                .phone("13522203330")
                .orderId("ORDER_2018042602")
                .amount(10000L)
                .build());
        //默开用户
        System.out.println("--->"+this.getClass());
        addUser("13522203330");
    }

    @Transactional
    public void addUser(String phone) { 
        
        userDao.insert(UserModel.builder().userName("zhangsan").userPhone(phone).build());
        throw new RuntimeException();
    }
}

破解方法 在调用addUser(……)之前添加下面的代码

TransactionalAopService service = (TransactionalAopService) AopContext.currentProxy(); //获取代理对象
service.addUser("13522203330"); //通过代理对象调用addUser,做异步增强

这里还不算完,如果就这样运行,那肯定会报错。

在@EnableAspectJAutoProxy添加属性值。

@EnableAspectJAutoProxy(exposeProxy = true)

建议可以先看我的AOP那章,究其源头是在于THIS 和SERVICE 是不同的类了。 如果对代理对象和当前对象有点懵的话,可以加上下面的两行代码:

System.out.println("------>代理对象:"+service.getClass()); System.out.println("------>当前对象:"+this.getClass()); 得到的结果:

--
        标签: 2090p变送器来袭du电力变送器80du电力变送器du20电力变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台