2019秋招面试复习 项目重点提问

1. 使用redis做为分布式锁的注意事项

来源 http://www.javashuo.com/article/p-ruynfosf-cu.htmlhtml

Redis分布式锁实现的三个核心要素nginx

1. 加锁web

setnx(key,1)当一个线程执行setnx返回1,说明key本来不存在,该线程成功获得了锁,当其余线程执行setnx返回0,说明key已经存在,该线程抢锁失败。redis

2.解锁算法

当获得锁的线程执行完任务,须要释放锁,以便其余线程能够进入。释放锁的最简单方式是执行del指令,del(key)释放锁以后,其余线程就能够继续执行setnx命令来得到锁。数据库

3.锁超时后端

若是一个获得锁的线程在执行任务的过程当中挂掉,来不及显式地释放锁,这块资源将会永远被锁住,别的线程再也别想进来。设计模式

因此,setnx的key必须设置一个超时时间,以保证即便没有被显式释放,这把锁也要在必定时间后自动释放。setnx不支持超时参数,因此须要额外的指令,expire(key, 30)浏览器

if(setnx(key,1) == 1){
    expire(key,30try {
        do something ......
    }catch()
  {
  }
  finally {
       del(key)
    }

}

问题 1 setnx和expire的非原子性
缓存

设想一个极端场景,当某线程执行setnx,成功获得了锁:setnx刚执行成功,还将来得及执行expire指令,该节点挂了。这样一来,这把锁就没有设置过时时间,变得“长生不老”,别的线程再也没法得到锁了。

解决:

setnx指令自己是不支持传入超时时间的,Redis 2.6.12以上版本为set指令增长了可选参数,伪代码以下:set(key,1,30,NX,这样就能够取代setnx指令

问题 2 超时后使用del 致使误删其余线程的锁:

假如某线程成功获得了锁,而且设置的超时时间是30秒。

若是某些缘由致使线程B执行的很慢很慢,过了30秒都没执行完,这时候锁过时自动释放,线程B获得了锁。随后,线程A执行完了任务,线程A接着执行del指令来释放锁。但这时候线程B还没执行完,线程A实际上删除的是线程B加的锁

解决:

能够在del释放锁以前作一个判断,验证当前的锁是否是本身加的锁。

至于具体的实现,能够在加锁的时候把当前的线程ID当作value,并在删除以前验证key对应的value是否是本身线程的ID。

 1 加锁:
 2 String threadId = Thread.currentThread().getId()
 3 set(key,threadId ,30,NX)
 4 
 5 doSomething.....
 6  
 7 解锁:
 8 if(threadId .equals(redisClient.get(key))){
 9     del(key)
10 }

可是,这样作又隐含了一个新的问题,if判断和释放锁是两个独立操做,不是原子性的

能够经过使用Lua脚本 来实现两个语句的原子性。

 

可能出现的问题及解决方法:

1. 在执行业务时,多个并发的请求,致使同个资源被访问被屡次执行加锁
2. 加锁后,业务并发访问这个线程大部分都返回了失败,只有少部分获取到锁的线程才能处理,这种状况下是很是不通情理的(使用reddison已经内部实现的自旋锁来进行锁的等待,获取不到锁就while循环一直尝试加锁,知道锁的获取成功才返回结果,可是这是悲观锁)
3. 加完锁后,每次获取完锁时就对一个特定值+1,执行完后对特定值进行释放,可是线程拿到锁后,抛出异常时,没法执行最后释放锁操做加finally块执行释放锁操做
4. 加了finally块后,这个块中的业务失败了,或者程序挂了,redis链接失败了,没法释放锁(对锁加超时时间
5. 加了超时时间后,在持有锁这个业务中的执行时间比超时时间长,在业务执行的时候,锁超时释放了,这时,其余的请求的线程就能获取到这个锁了,出现了线程的重复进入
对redis锁的key值用一个UUID来设计,同个线程内获取这个锁都须要生成一个惟一的id,释放时只能让同个线程内存放的id匹配释放
,第二个线程执行了业务,并且这个业务执行后,比第一个线程快,而且锁超时解锁解了第一个锁
获取到这个锁的时候,另外开辟一个线程,好比超时时间是10秒,这个线程就每5秒就查询一下这个锁是否失效,若是没有失效就增长5秒中,保持这个锁的有效性

 

 

高并发设计思路

参考 http://www.javashuo.com/article/p-ngkmxygo-gt.html

http链接池+NIO+线程池(多生产者多消费者)(反向代理服务器,一致性哈希算法)+阻塞队列+数据库链接池+缓存(主从、集群)+数据库(集群、分库主从)。

细节: 

一、设置http链接池,能够下降延迟,提升客户端响应时间。还能够链接池复用,支持更大的并发量。

二、把一些静态资源先加载到浏览器缓存里面,减小服务器端的压力

三、能够对服务器端的数据进行压缩

四、反向代理服务器能够保护服务器的安全,来自互联网的请求必需通过代理服务器。因此也能够在代理服务器放一些静态数据,当用户第一次访问静态内容时,静态内容就被缓存在反向代理服务器上,其余用户请求进来时,就能够直接返回,减轻web服务器负载压力。

五、NIO模型(是在Linux仍是Windows系统下,Windows建议用AIO,Linux系统下AIO的底层也是基于epoll多路复用,差异不大,LF的区别)

六、线程池(根据线程池处理不一样性质的任务,要有不一样性质的线程池,IO密集型,CPU*2。CPU密集型,CPU+1.多生产者多消费这模型)线程池还须要考虑:a.先设置一个最大线程数量和最小线程数量,进行性能评估,压测。b.线程池阻塞队列的大小要有界,不然服务器压力过大。c.须考虑线程池的失败策略,失败后的补偿。d.后台批处理服务须与线上面向用户的服务进行分离。

七、阻塞队列,由于NIO第二个阶段会引发用户线程的阻塞,好比可能等待JDBC链接数据库,所以在这里用一个阻塞队列,线程把请求放到阻塞队列里面,这个线程就能够回归线程池,处理别的事情了。是一个生产者消费者模型

八、建一个数据库链接池,主要是为了减小资源的消耗、减小延迟。

九、数据存储部分,1)根据实际状况设置索引和优化SQL语句。2)幂等、乐观、悲观。3)防止SQL注入攻击。4)一个事务当中操做不要过多,可能会阻塞,进而累积形成数据库的故障。5)数据量太大,查询的时间利用limit关键字进行分页处理,防止结果集太大,让应用OOM。

十、使用缓存,减小数据库的访问次数,提升并发量。1)缓存的结构,LRU,链表(集合类存放超时对象),大小,时间。2)核心业务和非核心业务进行分离,减小相互影响的可能性,不要使用共享缓存。3)不经常使用的数据不要使用缓存。4)夜间查询一天之类搜索频率比较高的词汇,结合AI进行预测,预测的结果预先放到缓存里面。5)考虑分布式缓存数据库:Redis、memcached,防止本地缓存内存溢出。Redis的主从同步,读写分离、负载均衡。主从+一级二级缓存+哨兵。哨兵是Redis 的高可用性解决方案:由一个或多个哨兵实例 组成的哨兵 系统能够监视任意多个主服务器,以及这些主服务器属下的全部从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。

十一、主从有瓶颈,会有延迟、主服务器压力过大。考虑集群、分库分表。考虑用一致性哈希算法实现分布式缓存数据库。数据迁移量小,引入虚拟节点、防止数据倾斜。

十二、 如有重复数据,布隆过滤器去重。

1三、数据库存储文件名之类的,服务器保存实际的数据。

保持数据一致性的方法:1. 消息队列  2. 用同一个数据库,A.B,C用同一个数据库。 3。 用Redis缓存,把一些数据放到缓存。

 

2. 高并发系统设计2

来源 http://www.javashuo.com/article/p-rxyoqhdb-n.html

1、系统架构扩展
系统的扩展性能够提供系统的性能。表明系统可以容纳更高的负载、更大的数据集,而且系统是可维护的。扩展能够分为两种:

垂直扩展(stade up),提升单一的机器性能配置,如添加内存、更换更强的处理器等等。
2.水平扩展(out),横向添加新机器。

水平扩展比垂直扩展有更强大的扩展性,但水平扩展也来了更高的维护成本。实践中须要根据具体状况来寻求一个平衡点。

2、静态化技术
采用预处理方式将页面静态化,存储在磁盘,不须要链接数据库读取数据,能够提升服务端性能

3、使用缓存服务器减小IO
使用Redis,Memache内存服务器,在内存中存储数据,减小磁盘IO读取量

4、引入微服务器框架Dubbo,SpringCloud
将业务模块切分为多个微服务,托管在微服务框架中,提升负载均衡与容错处理

5、使用数据库集群技术
数据库层面采用主从复制模式、集群模式、采用分区表将数据平均分配到多个磁盘控制器。采用读写分离设计模式

6、图片存储在分布文件系统或CDN服务中
静态资源(图片、视频、网页)采用分布式存储,或者使用CDN服务分发,系统须要设计为先后端分离。

7、业务处理采用NIO技术
采用非阻塞IO技术,借鉴Dubbo使用Netty框架。

8、应用服务调优(Tomcat,Weblogic,Websphere)
通用的配置是设置JVM参数,内存各分区大小,垃圾回收线程多少。再根据不一样应用服务器的配置参数,优化应用服务器。

9、负载均衡
使用了水平扩展之如何将大量的请求“均衡”到咱们的扩展机器上

两种负载均衡模式:有状态(若有携带session)和无状态

两种负载均衡方式:硬件均衡和软件均衡

硬件均衡比较简单,一般接入一个设备便可,以后的均衡和故障检测等等都由硬件自动完成。成本较高。

软件均衡则是经过软件来转发各类请求,更加容易的定义转发规则,有较多的开源产品选择。

第四层和第七层

常常在负载均衡中看到第四层和第七层这两个名词。它们其实是指它们各自工做时所处理的网络协议的层数(使用ISO模型)。

第四层是数据传输层,包括TCP和UDP,第七层则是应用层,一般web中为HTTP应用。如Apache、nginx等支持第七层的均衡,并且可配置性都至关强大,可以适应较复杂的应用。例如能够简单的将流量分担到各个负载机上,也能够定义一套业务规则,将应用划分为不一样的池,每一个池处理某些固定规则的URL。

对比软件均衡与硬件均衡,能够发现它们各自的优缺点:

硬件均衡成本比较高,软件均衡多数可使用免费的开源软件来实现。

硬件均衡对于故障检测比软件均衡更增强大、快速。在采用硬件均衡时,一旦某台机器出现故障,立刻就能够检测出来并当即屏蔽。

硬件均衡能够快速的添加机器(接入硬件接口便可),而软件均衡除了添加机器外还要添加一些配置信息,以将某些流量导入到新的机器。

软件均衡能够定义很是复杂的业务规则,而硬件均衡在这方面相对较弱。

多数的硬件均衡方案都有捆绑附加的一些服务如HTTPS加速、DOS防火墙等等。

10、操做系统优化虚拟内存调优、可用文件句柄多少配置,

相关文章
相关标签/搜索