TEST1

1.分布式锁 42

1.1 redis实现分布式锁

单机存在的问题,集群存在的问题。误删的问题。
在删key的时候一定要删自己的key,不能把别人的给删了。

(1)redis分布式锁

官方叫做RedLock算法,是redis官方支持的分布式锁算法。

这个分布式锁有3个重要的考量点,互斥(只能有一个客户端获取锁),不能死锁,容错(大部分redis节点或者这个锁就可以加可以释放)

第一个最普通的实现方式,如果就是在redis里创建一个key算加锁

SET my:lock 随机值 NX PX 30000,这个命令就ok,这个的NX的意思就是只有key不存在的时候才会设置成功,PX 30000的意思是30秒后锁自动释放。别人创建的时候如果发现已经有了就不能加锁了。

释放锁就是删除key,但是一般可以用lua脚本删除,判断value一样才删除:

关于redis如何执行lua脚本,自行百度

if redis.call(“get”,KEYS[1]) == ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end

为啥要用随机值呢?
因为如果某个客户端获取到了锁,但是阻塞了很长时间才执行完,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除key的话会有问题,所以得用随机值加上面的lua脚本来释放锁。

为什么要设置过期时间?为什么要给锁续期
不设置过期时间如果拿到锁的服务器挂了,那么其他服务器就没法拿到锁了。同时如果过期时间设置的太短了,也会有问题,本机代码没执行完其他服务器又拿到锁了。所以这个时候需要一个wachDog来给锁续期,wachDog是一个守护线程,它每10秒会将锁的过期时间续约到30秒。这样就避免锁的时间不够了。当本机挂了无法主动释放锁,这时候wachDog已经不存在了。所以到时间了会自动释放锁。其他服务器也就拿到锁了。

如果实现重入锁?
在Redisson实现可重入锁的思路,使用Redis的哈希表存储可重入次数,当加锁成功后,使用hset命令,value(重入次数)则是1。如果同一个客户端再次加锁成功,则使用hincrby自增加一。解锁时,先判断可重复次数是否大于0,大于0则减一,否则删除键值,释放锁资源。

解锁如何避免频繁重试?
上面的加锁方法是加锁后立即返回加锁结果,如果加锁失败的情况下,总不可能一直轮询尝试加锁,直到加锁成功为止,这样太过耗费性能。所以需要利用发布订阅的机制进行优化。步骤如下:

当加锁失败后,订阅锁释放的消息,自身进入阻塞状态。
当持有锁的客户端释放锁的时候,发布锁释放的消息。
当进入阻塞等待的其他客户端收到锁释放的消息后,解除阻塞等待状态,再次尝试加锁。

原文链接

但是这样是肯定不行的。因为如果是普通的redis单实例,那就是单点故障。或者是redis普通主从,那redis主从异步复制,如果主节点挂了,key还没同步到从节点,此时从节点切换为主节点,别人就会拿到锁。

第二个问题,RedLock算法

在这里插入图片描述

这个场景是假设有一个redis cluster,有5个redis master实例。然后执行如下步骤获取一把锁:

多节点redis实现的分布式锁算法(RedLock):有效防止单点故障

假设有5个完全独立的redis主服务器

1.获取当前时间戳

2.client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。

比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁

3.client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功

4.如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);

5.如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁

算法示意图如下:
在这里插入图片描述

总结:

1.TTL时长 要大于正常业务执行的时间+获取所有redis服务消耗时间+时钟漂移

2.获取redis所有服务消耗时间要 远小于TTL时间,并且获取成功的锁个数要 在总数的一般以上:N/2+1

3.尝试获取每个redis实例锁时的时间要 远小于TTL时间

4.尝试获取所有锁失败后 重新尝试一定要有一定次数限制

5.在redis崩溃后(无论一个还是所有),要延迟TTL时间重启redis

6.在实现多redis节点时要结合单节点分布式锁算法 共同实现

原文链接

1.2 zookeeper实现分布式锁

可以支持重入,有等待队列避免自旋消耗资源。

使用临时顺序节点,每个客户端监听他前面一个节点的变化。如果自己是头结点,表示获得锁。类似AQS思想。

原文链接

zookeeper和redis实现方式比较
在这里插入图片描述

2.接口幂等性

3.接口顺序性

4.分布式事务

4.1 两阶段提交/XA

在这里插入图片描述

所以这个就是所谓的XA事务,两阶段提交,有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复ok,那么就正式提交事务,在各个数据库上执行操作;如果任何一个数据库回答不ok,那么就回滚事务。

这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。

4.2 TCC方案

在这里插入图片描述
TCC的全程是:Try、Confirm、Cancel。

这个其实是用到了补偿的概念,分为了三个阶段:

  • 1)Try阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留
  • 2)Confirm阶段:这个阶段说的是在各个服务中执行实际的操作
  • 3)Cancel阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作

给大家举个例子吧,比如说跨银行转账的时候,要涉及到两个银行的分布式事务,如果用TCC方案来实现,思路是这样的:

1)Try阶段:先把两个银行账户中的资金给它冻结住就不让操作了
2)Confirm阶段:执行实际的转账操作,A银行账户的资金扣减,B银行账户的资金增加
3)Cancel阶段:如果任何一个银行的操作执行失败,那么就需要回滚进行补偿,就是比如A银行账户如果已经扣减了,但是B银行账户资金增加失败了,那么就得把A银行账户资金给加回去

**比较适合的场景:**这个就是除非你是真的一致性要求太高,是你系统中核心之核心的场景,比如常见的就是资金类的场景,那你可以用TCC方案了,自己编写大量的业务逻辑,自己判断一个事务中的各个环节是否ok,不ok就执行补偿/回滚代码。

而且最好是你的各个业务执行的时间都比较短。

但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码很难维护。

4.3 本地消息表

在这里插入图片描述

国外的ebay搞出来的这么一套思想

这个大概意思是这样的

1)A系统在自己本地一个事务里操作同时,插入一条数据到消息表
2)接着A系统将这个消息发送到MQ中去
3)B系统接收到消息之后,在一个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样保证不会重复处理消息
4)B系统执行成功之后,就会更新自己本地消息表的状态以及A系统消息表的状态
5)如果B系统处理失败了,那么就不会更新消息表状态,那么此时A系统会定时扫描自己的消息表,如果有没处理的消息,会再次发送到MQ中去,让B再次处理
6)这个方案保证了最终一致性,哪怕B事务失败了,但是A会不断重发消息,直到B那边成功为止

这个方案说实话最大的问题就在于严重依赖于数据库的消息表来管理事务啥的???这个会导致如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用

4.4 可靠消息最终一致性方案

在这里插入图片描述

这个的意思,就是干脆不要用本地的消息表了,直接基于MQ来实现事务。比如阿里的RocketMQ就支持消息事务。

大概的意思就是:
1)A系统先发送一个prepared消息到mq,如果这个prepared消息发送失败那么就直接取消操作别执行了
2)如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉mq发送确认消息,如果失败就告诉mq回滚消息
3)如果发送了确认消息,那么此时B系统会接收到确认消息,然后执行本地的事务
4)mq会自动定时轮询所有prepared消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认消息?那是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,但是确认消息发送失败了。
5)这个方案里,要是系统B的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如B系统本地回滚后,想办法通知系统A也回滚;或者是发送报警由人工来手工回滚和补偿

这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用RocketMQ支持的,要不你就自己基于类似ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的

4.5 最大努力通知方案

在这里插入图片描述

这个方案的大致意思就是:

1)系统A本地事务执行完之后,发送个消息到MQ
2)这里会有个专门消费MQ的最大努力通知服务,这个服务会消费MQ然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统B的接口
3)要是系统B执行成功就ok了;要是系统B执行失败了,那么最大努力通知服务就定时尝试重新调用系统B,反复N次,最后还是不行就放弃

5.分库分表

在这里插入图片描述

水平拆分,垂直拆分在这里插入图片描述
分库分表中间件,proxy层中间件,client层中间件。

5.1 不停机迁移mysql数据库

在这里插入图片描述

双写方案

这个是我们常用的一种迁移方案,比较靠谱一些,不用停机,不用看北京凌晨4点的风景

简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,都除了对老库增删改,都加上对新库的增删改,这就是所谓双写,同时写俩库,老库和新库。

然后系统部署之后,新库数据差太远,用之前说的导数工具,跑起来读老库数据写新库,写的时候要根据gmt_modified这类字段判断这条数据最后修改的时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。

接着导万一轮之后,有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环,直到两个库每个表的数据都完全一致为止。

接着当数据完全一致了,就ok了,基于仅仅使用分库分表的最新代码,重新部署一次,不就仅仅基于分库分表在操作了么,还没有几个小时的停机时间,很稳。所以现在基本玩儿数据迁移之类的,都是这么干了。

5.2 如何设计动态扩容缩容的分库分表方案

谈分库分表的扩容,第一次分库分表,就一次性给他分个够,32个库,1024张表,可能对大部分的中小型互联网公司来说,已经可以支撑好几年了

一个实践是利用32 * 32来分库分表,即分为32个库,每个库里一个表分为32张表。一共就是1024张表。根据某个id先根据32取模路由到库,再根据32取模路由到库里的表。

刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个mysql服务器可能建了n个库,比如32个库。后面如果要拆分,就是不断在库和mysql服务器之间做迁移就可以了。然后系统配合改一下配置即可。

比如说最多可以扩展到32个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到1024个数据库服务器,每个数据库服务器上面一个库一个表。因为最多是1024个表么。

这么搞,是不用自己写代码做数据迁移的,都交给dba来搞好了,但是dba确实是需要做一些库表迁移的工作,但是总比你自己写代码,抽数据导数据来的效率高得多了。

哪怕是要减少库的数量,也很简单,其实说白了就是按倍数缩容就可以了,然后修改一下路由规则。

对2 ^ n取模

orderId 模 32 = 库
orderId / 32 模 32 = 表

5.3 分库分表后全局id咋生成

在这里插入图片描述
(1)数据库自增id

这个就是说你的系统里每次得到一个id,都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个id。拿到这个id之后再往对应的分库分表里去写入。

这个方案的好处就是方便简单,谁都会用;缺点就是单库生成自增id,要是高并发的话,就会有瓶颈的;如果你硬是要改进一下,那么就专门开一个服务出来,这个服务每次就拿到当前id最大值,然后自己递增几个id,一次性返回一批id,然后再把当前最大id值修改成递增几个id之后的一个值;但是无论怎么说都是基于单个数据库。

适合的场景:你分库分表就俩原因,要不就是单库并发太高,要不就是单库数据量太大;除非是你并发不高,但是数据量太大导致的分库分表扩容,你可以用这个方案,因为可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键即可。

并发很低,几百/s,但是数据量大,几十亿的数据,所以需要靠分库分表来存放海量的数据

(2)uuid

好处就是本地生成,不要基于数据库来了;不好之处就是,uuid太长了,作为主键性能太差了,不适合用于主键。

适合的场景:如果你是要随机生成个什么文件名了,编号之类的,你可以用uuid,但是作为主键是不能用uuid的。

UUID.randomUUID().toString().replace(“-”, “”) -> sfsdf23423rr234sfdaf

3)获取系统当前时间

这个就是获取当前时间即可,但是问题是,并发很高的时候,比如一秒并发几千,会有重复的情况,这个是肯定不合适的。基本就不用考虑了。

适合的场景:**一般如果用这个方案,是将当前时间跟很多其他的业务字段拼接起来,作为一个id,**如果业务上你觉得可以接受,那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号,订单编号,时间戳 + 用户id + 业务含义编码

(4)snowflake算法

twitter开源的分布式id生成算法,就是把一个64位的long型的id,1个bit是不用的,用其中的41 bit作为毫秒数,用10 bit作为工作机器id,12 bit作为序列号

  • 1 bit:不用,为啥呢?因为二进制里第一个bit为如果是1,那么都是负数,但是我们生成的id都是正数,所以第一个bit统一都是0
  • 41 bit:表示的是时间戳,单位是毫秒。41 bit可以表示的数字多达2^41 - 1,也就是可以标识2 ^ 41 -
    1个毫秒值,换算成年就是表示69年的时间。
  • 10 bit:记录工作机器id,代表的是这个服务最多可以部署在2^10台机器上哪,也就是1024台机器。但是10
    bit里5个bit代表机房id,5个bit代表机器id。意思就是最多代表2 ^ 5个机房(32个机房),每个机房里可以代表2 ^
    5个机器(32台机器)。
  • 12 bit:这个是用来记录同一个毫秒内产生的不同id,12 bit可以代表的最大正整数是2 ^ 12 - 1 =
    4096,也就是说可以用这个12bit代表的数字来区分同一个毫秒内的4096个不同的id

64位的long型的id,64位的long -> 二进制

0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000

2018-01-01 10:00:00 -> 做了一些计算,再换算成一个二进制,41bit来放 -> 0001100 10100010 10111110 10001001 01011100 00

机房id,17 -> 换算成一个二进制 -> 10001

机器id,25 -> 换算成一个二进制 -> 11001

snowflake算法服务,会判断一下,当前这个请求是否是,机房17的机器25,在2175/11/7 12:12:14时间点发送过来的第一个请求,如果是第一个请求

假设,在2175/11/7 12:12:14时间里,机房17的机器25,发送了第二条消息,snowflake算法服务,会发现说机房17的机器25,在2175/11/7 12:12:14时间里,在这一毫秒,之前已经生成过一个id了,此时如果你同一个机房,同一个机器,在同一个毫秒内,再次要求生成一个id,此时我只能把加1

0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000001

比如我们来观察上面的那个,就是一个典型的二进制的64位的id,换算成10进制就是910499571847892992。

5.4 mysql读写分离,主从同步延时咋解决?

(1)如何实现mysql的读写分离?

其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。
(2)MySQL主从复制原理的是啥?

在这里插入图片描述

主库将变更写binlog日志,然后从库连接到主库之后,从库有一个IO线程,将主库的binlog日志拷贝到自己本地,写入一个中继日志中。接着从库中有一个SQL线程会从中继日志读取binlog,然后执行binlog日志中的内容,也就是在自己本地再次执行一遍SQL,这样就可以保证自己跟主库的数据是一样的。

这里有一个非常重要的一点,就是从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行SQL的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。

而且这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。

所以mysql实际上在这一块有两个机制,一个是半同步复制,用来解决主库数据丢失问题;一个是并行复制,用来解决主从同步延时问题。

这个所谓半同步复制,semi-sync复制,指的就是主库写入binlog日志之后,就会将强制此时立即将数据同步到从库,从库将日志写入自己本地的relay log之后,接着会返回一个ack给主库,主库接收到至少一个从库的ack之后才会认为写操作完成了。

所谓并行复制,指的是从库开启多个线程,并行读取relay log中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。

1)主从复制的原理
2)主从延迟问题产生的原因
3)主从复制的数据丢失问题,以及半同步复制的原理
4)并行复制的原理,多库并发重放relay日志,缓解主从延迟问题

(3)mysql主从同步延时问题(精华)

线上确实处理过因为主从同步延时问题,导致的线上的bug,小型的生产事故

show status,Seconds_Behind_Master,你可以看到从库复制主库的数据落后了几ms

其实这块东西我们经常会碰到,就比如说用了mysql主从架构之后,可能会发现,刚写入库的数据结果没查到,结果就完蛋了。。。。

所以实际上你要考虑好应该在什么场景下来用这个mysql主从同步,建议是一般在读远远多于写,而且读的时候一般对数据时效性要求没那么高的时候,用mysql主从同步

所以这个时候,我们可以考虑的一个事情就是,你可以用mysql的并行复制,但是问题是那是库级别的并行,所以有时候作用不是很大

所以这个时候。。通常来说,我们会对于那种写了之后立马就要保证可以查到的场景,采用强制读主库的方式,这样就可以保证你肯定的可以读到数据了吧。其实用一些数据库中间件是没问题的。

一般来说,如果主从延迟较为严重

1、分库,将一个主库拆分为4个主库,每个主库的写并发就500/s,此时主从延迟可以忽略不计
2、打开mysql支持的并行复制,多个库并行复制,如果说某个库的写入并发就是特别高,单库写并发达到了2000/s,并行复制还是没意义。28法则,很多时候比如说,就是少数的几个订单表,写入了2000/s,其他几十个表10/s。
3、重写代码,写代码的同学,要慎重,当时我们其实短期是让那个同学重写了一下代码,插入数据之后,直接就更新,不要查询
4、如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询设置直连主库。不推荐这种方法,你这么搞导致读写分离的意义就丧失了

6.redis并发竞争问题

lua脚本?
在这里插入图片描述

7. redis雪崩,穿透

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
或者说redis挂了。

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃
事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL被打死
事后:redis持久化,快速恢复缓存数据
在这里插入图片描述
穿透:数据库里没有,缓存里也没有
写个空值到缓存
在这里插入图片描述
缓存击穿:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

加个锁,第一个线程去数据库读出来数据之后放到数据库。这样其他线程就能从缓存中拿到数据了。

8.redis布隆过滤器

没有访问过的数据可能会判定为访问过。但是如果是访问过的 数据一定不会被误判为没访问过。

如果布隆过滤器判定这条数据存在,这条数据不一定真的存在。
如果布隆过滤器landing这条数据不存在,那么这条数据一定不存在。

在这里插入图片描述

setbit
原文链接

9.kafka顺序消费

将需要保证顺序的消息根据key放到同一个分区。然后消费者创建多个队列进行消费,一个线程消费一个队列。
在这里插入图片描述

10.kafka重复消费

消费完消息之后宕机了没提交offset。消费者重启之后还会从上次提交的offset出开始消费。所以就产生了重复消费的现象。
在这里插入图片描述
重复消费的幂等性要根据业务来保证。

(1)比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧

(2)比如你是写redis,那没问题了,反正每次都是set,天然幂等性

(3)比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个id写redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。

主键或唯一键约束
还有比如基于数据库的唯一键来保证重复数据不会重复插入多条,我们之前线上系统就有这个问题,就是拿到数据的时候,每次重启可能会有重复,因为kafka消费者还没来得及提交offset,重复数据拿到了以后我们插入的时候,因为有唯一键约束了,所以重复数据只会插入报错,不会导致数据库中出现脏数据。

11.kafka消息丢失

**1)消费端消息丢失:**手动提交offset,消费完消息之后再提交。刚拉到消息就提交offset,然后自己挂了。name这批消息就丢失了。

fllower全部同步完了才算消息投递成功。这样leader挂了消息也不会丢失。

2)kafka弄丢了数据

这块比较常见的一个场景,就是kafka某个broker宕机,然后重新选举partiton的leader时。大家想想,要是此时其他的follower刚好还有些数据没有同步,结果此时leader挂了,然后选举某个follower成leader之后,他不就少了一些数据?这就丢了一些数据啊。

生产环境也遇到过,我们也是,之前kafka的leader机器宕机了,将follower切换为leader之后,就会发现说这个数据就丢了

所以此时一般是要求起码设置如下4个参数:

给这个topic设置replication.factor参数:这个值必须大于1,要求每个partition必须有至少2个副本,一个leader一个follower。

在kafka服务端设置min.insync.replicas参数:这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟自己保持联系,没掉队,这样才能确保leader挂了还有一个follower吧。ISR最小副本数量。

在producer端设置acks=all:这个是要求每条数据,必须是写入所有replica之后,才能认为是写成功了

在producer端设置retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了

我们生产环境就是按照上述要求配置的,这样配置之后,至少在kafka broker端就可以保证在leader所在broker发生故障,进行leader切换时,数据不会丢失

3)生产者会不会弄丢数据

如果按照上述的思路设置了ack=all,一定不会丢,要求是,你的leader接收到消息,所有的follower都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。

12.kafka消息积压

1)先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉
2)新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量
3)然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue
4)接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据
5)这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据
6)等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息在这里插入图片描述

13.消息队列选型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.dubbo网络通信协议

(1)dubbo支持不同的通信协议

1)dubbo协议

dubbo://192.168.0.1:20188

默认就是走dubbo协议的,单一长连接,NIO异步通信,基于hessian作为序列化协议

适用的场景就是:传输数据量很小(每次请求在100kb以内),但是并发量很高

为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就100个连接。然后后面直接基于长连接NIO异步通信,可以支撑高并发请求。

否则如果上亿次请求每次都是短连接的话,服务提供者会扛不住。

而且因为走的是单一长连接,所以传输数据量太大的话,会导致并发能力降低。所以一般建议是传输数据量很小,支撑高并发访问。

2)rmi协议

走java二进制序列化,多个短连接,适合消费者和提供者数量差不多,适用于文件的传输,一般较少用

3)hessian协议

走hessian序列化协议,多个短连接,适用于提供者数量比消费者数量还多,适用于文件的传输,一般较少用

4)http协议

走json序列化

5)webservice

走SOAP文本序列化

(2)dubbo支持的序列化协议

所以dubbo实际基于不同的通信协议,支持hessian、java二进制序列化、json、SOAP文本序列化多种序列化协议。但是hessian是其默认的序列化协议。

15.线上机器突然宕机,线程池的阻塞队列中的请求怎么办?

必然会导致线程池里的积压的任务实际上来说都是会丢失的

如果说你要提交一个任务到线程池里去,在提交之前,麻烦你先在数据库里插入这个任务的信息,更新他的状态:未提交、已提交、已完成。提交成功之后,更新他的状态是已提交状态

系统重启,后台线程去扫描数据库里的未提交和已提交状态的任务,可以把任务的信息读取出来,重新提交到线程池里去,继续进行执行

16.spring事务传播机制

事务的实现原理,事务传播机制,如果说你加了一个@Transactional注解,此时就spring会使用AOP思想,对你的这个方法在执行之前,先去开启事务,执行完毕之后,根据你方法是否报错,来决定回滚还是提交事务

在这里插入图片描述
// 开启一个事务

// 执行方法A的代码,接着执行方法B的代码

// 提交或者回滚事务

// 开启一个事务1

// 执行方法A里的一些代码,doSomethingPre()

// 开启一个事务2

// 执行方法B里的一些代码

// 提交或者回滚事务2

// 执行方法A里的一些代码,doSomethingPost()

// 提交或者回滚事务1

// 开启一个事务

// 执行方法A里的一些代码,doSomethingPre()

// 设置一个回滚点,savepoint

// 执行方法B里的一些代码

// 如果方法B里抛出了异常,此时进行回滚,回滚到之前的savepoint

// 执行方法A里的一些代码,doSomethingPost()

// 提交或者回滚事务

嵌套事务,外层的事务如果回滚,会导致内层的事务也回滚;但是内层的事务如果回滚,仅仅是回滚自己的代码

① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

出去面试,事务传播机制

比如说,我们现在有一段业务逻辑,方法A调用方法B,我希望的是如果说方法A出错了,此时仅仅回滚方法A,不能回滚方法B,必须得用REQUIRES_NEW,传播机制,让他们俩的事务是不同的

方法A调用方法B,如果出错,方法B只能回滚他自己,方法A可以带着方法B一起回滚,NESTED嵌套事务

17.如何排查和处理线上OOM问题

18.TCP三次握手,四次挥手

其中比较重要的字段有:

(1)序号(sequence number):Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。

(2)确认号(acknowledgement number):Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。

(3)标志位(Flags):共6个,即URG、ACK、PSH、RST、SYN、FIN等。具体含义如下:

URG:紧急指针(urgent pointer)有效。
ACK:确认序号有效。
PSH:接收方应该尽快将这个报文交给应用层。
RST:重置连接。
SYN:发起一个新连接。
FIN:释放一个连接。

需要注意的是:

不要将确认序号Ack与标志位中的ACK搞混了。确认方Ack=发起方Seq+1,两端配对。

在这里插入图片描述
在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。

此后客户端和服务器端进行正常的数据传输。这就是“三次握手”的过程。
(2)为啥不是2次或者4次握手呢?

假设两次握手就ok了,要是客户端第一次握手过去,结果卡在某个地方了,没到服务端;完了客户端再次重试发送了第一次握手过去,服务端收到了,ok了,大家来回来去,三次握手建立了连接。

结果,尴尬的是,后来那个卡在哪儿的老的第一次握手发到了服务器,服务器直接就返回一个第二次握手,这个时候服务器开辟了资源准备客户端发送数据啥的,结果呢?客户端根本就不会理睬这个发回去的二次握手,因为之前都通信过了。

但是如果是三次握手,那个二次握手发回去,客户端发现根本不对,就会发送个复位的报文过去,让服务器撤销开辟的资源,别等着了。

因为3次握手就够了,不需要4次或者5次浪费资源了。

在这里插入图片描述

5.为什么客户端在TIME-WAIT阶段要等2MSL?

为的是确认服务器端是否收到客户端发出的ACK确认报文

当客户端发出最后的ACK确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完ACK确认报文之后,会设置一个时长为2MSL的计时器。MSL指的是Maximum Segment Lifetime:一段TCP报文在传输过程中的最大生命周期。2MSL即是服务器端发出为FIN报文和客户端发出的ACK确认报文所能保持有效的最大时长。

服务器端在1MSL内没有收到客户端发出的ACK确认报文,就会再次向客户端发出FIN报文;

如果客户端在2MSL内,再次收到了来自服务器端的FIN报文,说明服务器端由于各种原因没有接收到客户端发出的ACK确认报文。客户端再次向服务器端发出ACK确认报文,计时器重置,重新开始2MSL的计时;
否则客户端在2MSL内没有再次收到来自服务器端的FIN报文,说明服务器端正常接收了ACK确认报文,客户端可以进入CLOSED阶段,完成“四次挥手”。

所以,客户端要经历时长为2SML的TIME-WAIT阶段;这也是为什么客户端比服务器端晚进入CLOSED阶段的原因

原文链接

19.http协议工作原理

HTTP请求、响应报文格式
HTTP请求报文主要由请求行、请求头部、请求正文3部分组成
在这里插入图片描述
1,请求行

由3部分组成,分别为:请求方法、URL(见备注1)以及协议版本,之间由空格分隔

请求方法包括GET、HEAD、PUT、POST、TRACE、OPTIONS、DELETE以及扩展方法,当然并不是所有的服务器都实现了所有的方法,部分方法即便支持,处于安全性的考虑也是不可用的

协议版本的格式为:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1

2,请求头部

请求头部为请求报文添加了一些附加信息,由“名/值”对组成,每行一对,名和值之间使用冒号分隔

常见请求头如下:

在这里插入图片描述
请求头部的最后会有一个空行,表示请求头部结束,接下来为请求正文,这一行非常重要,必不可少

3,请求正文

可选部分,比如GET请求就没有请求正文

GET请求示例:
在这里插入图片描述
POST请求示例:

在这里插入图片描述
HTTP响应报文格式:

HTTP响应报文主要由状态行、响应头部、响应正文3部分组成
在这里插入图片描述
1,状态行

由3部分组成,分别为:协议版本,状态码,状态码描述,之间由空格分隔

状态代码为3位数字,200299的状态码表示成功,300399的状态码指资源重定向,400499的状态码指客户端请求出错,500599的状态码指服务端出错(HTTP/1.1向协议中引入了信息性状态码,范围为100~199)

这里列举几个常见的:
在这里插入图片描述
2,响应头部
与请求头部类似,为响应报文添加了一些附加信息

常见响应头部如下:
在这里插入图片描述
响应示例:
在这里插入图片描述
http 1.0要指定keep-alive来开启持久连接,默认是短连接,就是浏览器每次请求都要重新建立一次tcp连接,完事儿了就释放tcp连接。早期的网页都很low,没啥东西,就一点文字,就用这个没问题。但是现在,一个网页打开之后,还要加载大量的图片、css、js,这就坑爹了,发送多次请求。

早期,2000年之前,那个时候网页,都很low,当时你打开一个网页,就是说现场底层tcp三次握手,跟网站建立一个tcp连接,然后通过这个tcp连接,发送一次http请求,网站返回一个http响应(网页的html,里面有一大段文字),浏览器收到html渲染成网页,浏览器就走tcp四次挥手,跟网站断开连接了

到了后面,发现说2000之后,2010之后更不用说了,网页发展很迅猛,一个网页包含着大量的css、js、图片等资源。比如你请求一个网页,这个网页的html先过来,过来之后,浏览器再次发起大量的请求去加载css、js、图片,打开一个网页可能浏览器要对网站服务器发送几十次请求。

http 1.0,疯了,刚开始请求网页的html,tcp三次握手建立连接 -> 请求/响应 -> tcp四次挥手断开连接,接着再次要加载css、js、图片,要发送30个请求,上面的过程来30次,30次频繁的建立tcp连接以及释放tcp连接。很慢很慢。

其实最慢的不是说发送请求和获取响应,打开和释放连接,这都是很重的过程

http 1.1默认支持长连接,就是说,浏览器打开一个网页之后,底层的tcp连接就保持着,不会立马断开,之后加载css、js之类的请求,都会基于这个tcp连接来走。http 1.1还支持host头,也就可以支持虚拟主机;而且对断点续传有支持。

浏览器,第一次请求去一个网站的一个页面的时候,就会打开一个tcp连接,接着就在一段时间内都不关闭了,然后接下来这个网页加载css、js、图片大量的请求全部走同一个tcp连接,频繁的发送请求获取响应,最后过了一段时间,这些事儿都完了,然后才会去释放那一个tcp连接。大幅度的提升复杂网页的打开的速度,性能。

加入了管道机制,在同一个TCP连接里,允许多个请求同时发送,增加了并发性,进一步改善了HTTP协议的效率;举例来说,客户端需要请求两个资源。以前的做法是,在同一个TCP连接里面,先发送A请求,然后等待服务器做出回应,收到后再发出B请求。管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B请求。

http 2.0,支持多路复用,基于一个tcp连接并行发送多个请求以及接收响应,解决了http 1.1对同一时间同一个域名的请求有限制的问题。二进制分帧,将传输数据拆分为更小的帧(数据包),frame(数据包,帧),提高了性能,实现低延迟高吞吐。
增加双工模式,即不仅客户端能够同时发送多个请求,服务端也能同时处理多个请求,解决了队头堵塞的问题(HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级)

20.http长连接

2、面试官心里分析

一期学员,在外面面试的时候,正好还碰到了,聊到dubbo,dubbo://协议,是走的长连接,你聊聊什么是长连接?什么是http长连接?

3、面试题剖析

http本身没什么所谓的长连接短连接之说,其实说白了都是http下层的tcp连接是长连接还是短连接,tcp连接保持长连接,那么多个http请求和响应都可以通过一个链接来走。其实http 1.1之后,默认都是走长连接了,就是底层都是一个网页一个tcp连接,一个网页的所有图片、css、js的资源加载,都走底层一个tcp连接,来多次http请求即可。

http 1.0的时候,底层的tcp是短连接,一个网页发起的请求,每个请求都是先tcp三次握手,然后发送请求,获取响应,然后tcp四次挥手断开连接;每个请求,都会先连接再断开。短连接,建立连接之后,发送个请求,直接连接就给断开了

http 1.1,tcp长连接,tcp三次握手,建立了连接,无论有多少次请求都是走一个tcp连接的,走了n多次请求之后,然后tcp连接被释放掉了

21.mysql存储引擎,innodb和myisam的区别

在这里插入图片描述

Innodb引擎

Innodb引擎提供了对数据库ACID事务的支持,并且实现了SQL标准的四种隔离级别。该引擎还提供了行级锁和外键约束,它的设计目标是处理大容量数据库系统,它本身其实就是基于MySQL后台的完整数据库系统,MySQL运行时Innodb会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎不支持FULLTEXT类型的索引,而且它没有保存表的行数,当SELECT COUNT(*) FROM TABLE时需要扫描全表。当需要使用数据库事务时,该引擎当然是首选。由于锁的粒度更小,写操作不会锁定全表,所以在并发较高时,使用Innodb引擎会提升效率。但是使用行级锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表。

主要特点就是支持事务,走聚簇索引,强制要求有主键,支持外键约束,高并发、大数据量、高可用等相关成熟的数据库架构,分库分表、读写分离、主备切换,全部都可以基于innodb存储引擎来玩儿,如果真聊到这儿,其实大家就可以带一带,说你们用innodb存储引擎怎么玩儿分库分表支撑大数据量、高并发的,怎么用读写分离支撑高可用和高并发读的,用上第1季的内容就可以了。

MyISAM引擎

MyISAM是MySQL默认的引擎,但是它没有提供对数据库事务的支持,也不支持行级锁和外键,因此当INSERT(插入)或UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。不过和Innodb不同,MyISAM中存储了表的行数,于是SELECT COUNT(*) FROM TABLE时只需要直接读取已经保存好的值而不需要进行全表扫描。如果表的读操作远远多于写操作且不需要数据库事务的支持,那么MyISAM也是很好的选择。

主要区别:
1、MyISAM是非事务安全的,而InnoDB是事务安全的

2、MyISAM锁的粒度是表级的,而InnoDB支持行级锁

3、MyISAM支持全文类型索引,而InnoDB不支持全文索引

4、MyISAM相对简单,效率上要优于InnoDB,小型应用可以考虑使用MyISAM

5、MyISAM表保存成文件形式,跨平台使用更加方便

常用的两种引擎简单来说:

1、MyISAM管理非事务表,提供高速存储和检索以及全文搜索能力,如果再应用中执行大量select操作,应该选择MyISAM

2、InnoDB用于事务处理,具有ACID事务支持等特性,如果在应用中执行大量insert和update操作,应该选择InnoDB

22.mysql的sql调优一般都有哪些手段

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

我这里其实主要就是讲下怎么看SQL的执行计划,这个是码农必备能力,必须能看懂执行计划,一般其实就是看SQL有没有走索引,你倒是可以在这个环节重点说下你对执行计划这块的理解就ok

explain select * from table,就ok了

table | type | possible_keys | key | key_len | ref | rows | Extra

table:哪个表

type:这个很重要,是说类型,all(全表扫描),const(读常量,最多一条记录匹配),eq_ref(走主键,一般就最多一条记录匹配),index(扫描全部索引),range(扫描部分索引)

possible_keys:显示可能使用的索引

key:实际使用的索引

key_len:使用索引的长度

ref:联合索引的哪一列被用了

rows:一共扫描和返回了多少行

extra:using filesort(需要额外进行排序),using temporary(mysql构建了临时表,比如排序的时候),using where(就是对索引扫出来的数据再次根据where来过滤出了结果,需要回表查询)

23.BIO、NIO、AIO分别都是啥?有什么区别?

3、面试题剖析

3.1 BIO

这个其实就是最传统的网络通信模型,就是BIO,同步阻塞式IO,简单来说大家如果参加过几个月的培训班儿应该都知道这种BIO网络通信方式。就是服务端创建一个ServerSocket,然后客户端用一个Socket去连接那个ServerSocket,然后ServerSocket接收到一个Socket的连接请求就创建一个Socket和一个线程去跟那个Socket进行通信。

然后客户端和服务端的socket,就进行同步阻塞式的通信,客户端socket发送一个请求,服务端socket进行处理后返回响应,响应必须是等处理完后才会返回,在这之前啥事儿也干不了,这可不就是同步么。

这种方式最大的坑在于,每次一个客户端接入,都是要在服务端创建一个线程来服务这个客户端的,这会导致大量的客户端的时候,服务端的线程数量可能达到几千甚至几万,几十万,这会导致服务器端程序的负载过高,最后崩溃死掉。

要么你就是搞一个线程池,固定线程数量来处理请求,但是高并发请求的时候,还是可能会导致各种排队和延时,因为没那么多线程来处理。

21_01_BIO的网络通信原理.png

3.2 NIO

JDK 1.4中引入了NIO,这是一种同步非阻塞的IO,基于Reactor模型。

NIO中有一些概念:

比如Buffer,缓冲区的概念,一般都是将数据写入Buffer中,然后从Buffer中读取数据,有IntBuffer、LongBuffer、CharBuffer等很多种针对基础数据类型的Buffer。

还有Channel,NIO中都是通过Channel来进行数据读写的。

包括Selector,这是多路复用器,selector会不断轮询注册的channel,如果某个channel上发生了读写事件,selector就会将这些channel获取出来,我们通过SelectionKey获取有读写事件的channel,就可以进行IO操作。一个Selector就通过一个线程,就可以轮询成千上万的channel,这就意味着你的服务端可以接入成千上万的客户端。

这块其实相当于就是一个线程处理大量的客户端的请求,通过一个线程轮询大量的channel,每次就获取一批有事件的channel,然后对每个请求启动一个线程处理即可。

这里的核心就是非阻塞,就那个selector一个线程就可以不停轮询channel,所有客户端请求都不会阻塞,直接就会进来,大不了就是等待一下排着队而已。

这里的核心就是因为,一个客户端不是时时刻刻都要发送请求的,没必要死耗着一个线程不放吧,所以NIO的优化思想就是一个请求一个线程。只有某个客户端发送了一个请求的时候,才会启动一个线程来处理。

所以为啥是非阻塞呢?因为无论多少客户端都可以接入服务端,客户端接入并不会耗费一个线程,只会创建一个连接然后注册到selector上去罢了,一个selector线程不断的轮询所有的socket连接,发现有事件了就通知你,然后你就启动一个线程处理一个请求即可,但是这个处理的过程中,你还是要先读取数据,处理,再返回的,这是个同步的过程。

所以NIO是同步非阻塞的。

工作线程,从channel里读数据,是同步的,是工作线程自己去干这个事儿,卡在那儿,专门干读数据的这个活儿,数据没读完,你就卡死在这儿了;然后往channel里写数据,也是你自己去干这个事儿,卡死在这儿了,数据没写完,你就卡在这儿了

21_02_NIO通信原理.png

3.3 AIO

AIO是基于Proactor模型的,就是异步非阻塞模型。

每个连接发送过来的请求,都会绑定一个buffer,然后通知操作系统去异步完成读,此时你的程序是会去干别的事儿的,等操作系统完成数据读取之后,就会回调你的接口,给你操作系统异步读完的数据。

然后你对这个数据处理一下,接着将结果往回写。

写的时候也是给操作系统一个buffer,让操作系统自己获取数据去完成写操作,写完以后再回来通知你。

工作线程,读取数据的时候,是说,你提供给操作系统一个buffer,空的,然后你就可以干别的事儿了,你就把读数据的事儿,交给操作系统去干,操作系统内核,读数据将数据放入buffer中,完事儿了,来回调你的一个接口,告诉你说,ok,buffer交给你了,这个数据我给你读好了

写数据的时候也是一样的的,把放了数据的buffer交给操作系统的内核去处理,你就可以去干别的事儿了,操作系统完成了数据的写之后,级会来回调你,告诉你说,ok,哥儿们,你交给我的数据,我都给你写回到客户端去了

3.4 同步阻塞、同步非阻塞、异步非阻塞

但是这里为啥叫BIO是同步阻塞呢?这个其实不是针对网络编程模型来说的,是针对文件IO操作来说的,因为用BIO的流读写文件,是说你发起个IO请求直接hang死,必须等着搞完了这次IO才能返回

BIO的这个同步阻塞,不是完全针对的网络通信模型去说的,针对的是磁盘文件的IO读写,FileInputStream,BIO,卡在那儿,直到你读写完成了才可以

NIO为啥是同步非阻塞?就是说通过NIO的FileChannel发起个文件IO操作,其实发起之后就返回了,你可以干别的事儿,这就是非阻塞,但是接下来你还得不断的去轮询操作系统,看IO操作完事儿了没有。

你呢也可以使用FileChannel这种NIO的模型,去读写磁盘文件,读数据,发起读数据的请求之后,你不是阻塞住的,你可以干别的事儿,但是你在干别的事儿的同时,还得来时不时的自己去轮询操作系统读数据的状态,看看人家读好了没有

AIO为啥是异步非阻塞?就是说通过AIO发起个文件IO操作之后,你立马就返回可以干别的事儿了,接下来你也不用管了,操作系统自己干完了IO之后,告诉你说ok了。同步就是自己还得主动去轮询操作系统,异步就是操作系统反过来通知你。

你也可以基于AIO的文件读写的api去读写磁盘文件,你发起一个文件读写的操作之后,交给操作系统,你就不去管他了,直到操作系统自己完成之后,会来回调你的一个接口,通知你说,ok,这个数据读好了,那个数据写完了

24.线上服务器CPU 100%了!该怎么排查、定位和解决?

其实核心思路,就是找到这台服务器上,是哪个进程的哪个线程的哪段代码,导致cpu 100了,主要就是考察你是否熟练运用一些线上的命令。

这里我可以给大家说一个我们线上的经验,就是之前有一个bug,是一个很年轻的同学写的,就是我们当时是定了异常日志是写到es里去的

public void log(String message) {

try {

// 往es去写

} catch(Exception e) {

log(message);

}

}

线上事故,es集群出了点问题,没法写,最后出现线上几十台机器,全部因为这一行代码,全体cpu 100%,卡死了

(1)定位耗费cpu的进程

top -c,就可以显示进程列表,然后输入P,按照cpu使用率排序,你会看到类似下面的东西
在这里插入图片描述

大概类似上面这样,能看到哪个进程,CPU负载最高,还有启动这个进程的命令,比如一般就是java啥啥的。

(2)定位耗费cpu的线程

top -Hp 43987,就是输入那个进程id就好了,然后输入P,按照cpu使用率排序,你会看到类似下面的东西
在这里插入图片描述
大概类似上面那样,你就可以看到这个进程里的哪个线程耗费cpu最高

(3)定位哪段代码导致的cpu过高

printf “%x\n” 16872,把线程pid转换成16进制,比如41e8

jstack 43987(进程id) | grep ‘0x41e8’ -C5 --color

这个就是用jstack打印进程的堆栈信息,而且通过grep那个线程的16进制的pid,找到那个线程相关的东西,这个时候就可以在打印出的代码里,看到是哪个类的哪个方法导致的这个cpu 100%的问题

如果是fullGC次数过多导致cpu过高,一是内存消耗过大,导致Full GC次数过多。这时可以通过dump出堆快照来进行分析。
二是内存占用不高,但是Full GC次数还是比较多,此时可能是代码中手动调用 System.gc()导致GC次数过多, 这可以通过添加 -XX:+DisableExplicitGC来禁用JVM对显示GC的响应。

25.深入分析synchronized是如何通过加锁保证原子性的?

类似aqs,有等待队列

在这里插入图片描述

java对象都是分为对象头和实例变量两块的,其中实例变量就是大家平时看到的对象里的那些变量数据。然后对象头包含了两块东西,一个是Mark Word(包含hashCode、锁数据、GC数据,等等),另一个是Class Metadata Address(包含了指向类的元数据的指针)

在Mark Word里就有一个指针,是指向了这个对象实例关联的monitor的地址,这个monitor是c++实现的,不是java实现的。这个monitor实际上是c++实现的一个ObjectMonitor对象,里面包含了一个_owner指针,指向了持有锁的线程。

ObjectMonitor里还有一个entrylist,想要加锁的线程全部先进入这个entrylist等待获取机会尝试加锁,实际有机会加锁的线程,就会设置_owner指针指向自己,然后对_count计数器累加1次

各个线程尝试竞争进行加锁,此时竞争加锁是在JDK 1.6以后优化成了基于CAS来进行加锁,理解为跟之前的Lock API的加锁机制是类似的,CAS操作,操作_count计数器,比如说将_count值尝试从0变为1

如果成功了,那么加锁成功了;如果失败了,那么加锁失败了

然后释放锁的时候,先是对_count计数器递减1,如果为0了就会设置_owner为null,不再

指向自己,代表自己彻底释放锁

如果获取锁的线程执行wait,就会将计数器递减,同时_owner设置为null,然后自己进入waitset中等待唤醒,别人获取了锁执行notify的时候就会唤醒waitset中的线程竞争尝试获取锁

有人会问,那尝试加锁这个过程,也就是对_count计数器累加操作,是怎么执行的?如何保证多线程并发的原子性呢?很简单,JDk 1.6之后,对synchronized内的加锁机制做了大量的优化,这里就是优化为CAS加锁的

你如果说在之前把ReentrantLock底层的源码都读懂了,AQS的机制都读懂了之后,那么synchronized底层的实现差不多的,synchronized的ObjectMonitor的地位就跟ReentrantLock里的AQS是差不多的

总结: 1、synchronized关键字在编译以后会是monitorenter与monitorexit指令。 2、而对应的指令,再往底层走就是进入同步块执行refresh,退出同步块执行flush。 3、整体看来,synchronized的加锁、wait、释放锁与AQS差不多,都是基于一个临界资源去实现的,然后搭配上对应entryList、waitset等等就相当于是AQS中的队列以及condition队列。剩下的就是偏向锁,轻量级锁,重量级锁的差别。

26.Netty的架构原理图能画一下吗,他是如何体现Reactor架构思想的?

27.能说说你对堆外内存的理解吗?堆外内存的优势在哪里?

少一次拷贝
在这里插入图片描述
在这里插入图片描述

28.JDK是如何对堆外内存进行分配和回收的?会发生堆外内存溢出吗?

在这里插入图片描述
-XX:MaxDirectMemorySize:通过JVM参数是可以设置你最大可以使用的堆外内存的大小的,比如说设置堆外内存最大可以使用1GB,此时已经使用了950MB空间了,然后呢,你此时要申请一块80MB的堆外内存

会发现说,堆外内存已经不够了,此时不能直接分配堆外内存了

DirectByteBuffer,这个对象是JVM堆内存里的一个对象,但是这个DirectByteBuffer里面包含指针,引用了一块堆外的内存

1、如果堆外内存足够,就直接预留一部分内存

2、如果堆外内存不足,则将已经被 JVM 垃圾回收的 DirectBuffer 对象的堆外内存释放

3、如果进行一次堆外内存资源回收后,还不够进行本次堆外内存分配的话,则进行 System.gc()

4、如果 9 次尝试后依旧没有足够的可用堆外内存,则抛异常

5、实际分配内存

jvm专栏,或者是对jvm的垃圾回收有一定的理解的话

jvm一般分为young gc和full gc,无论是发生哪种gc,都可能会回收掉一些没有GC roots变量引用的DirectByteBuffer对象,回收掉了之后,就会主动释放他们引用的那些堆外内存,是这样子的

DirectByteBuffer回收,就会回收关联的堆外内存,或者是内部有一个cleaner对象,可以用反射获取他,然后调用他的clean方法来主动释放内存

如果依靠jvm gc机制,可能DirectByteBuffer躲过N次minor gc进入了老年代,然后老年代迟迟没有放满,因此迟迟没有回收,此时可能会导致DirectByteBuffer对象一直在引用堆外内存

这样当你要分配更多的堆外内存时,无法腾出来更多的内存,就会有堆外内存溢出了

堆内内存的OOM一样,out of memory,内存耗尽,实在是没有空闲的内存空间给你来使用了,因为所有的内存此时都别别人在使用,你要申请一块新的内存空间,实在是没有了,所以就OOM

堆外内存的溢出,也是一样的

29.如果不使用零拷贝技术,普通的IO操作在OS层面是如何执行的?

内核态和用户态

从系统安全和保护的角度出发,在进行计算机体系结构设计时,处理机的执行模式一般设定为两种:分别称为内核模式(内核态)和用户模式(用户态)。当处理机处于内核模式执行时,意味着系统除了可以执行一般指令外,还可以执行特权指令,即可以执行访问各种控制寄存器的指令、I/O指令以及程序状态字。

当处理机处于用户模式执行时,只能执行一般指令,而不允许执行特权指令。这样做可以保护核心代码不受用户程序有意和无意的攻击。 显然,处理机在运行期间需要在内核模式和用户模式之前进行切换。
在这里插入图片描述
在这里插入图片描述

30.听说过mmap吗?内存映射技术为什么可以提升IO性能?

在这里插入图片描述
在这里插入图片描述

31.零拷贝技术到底是什么,他是如何提升IO性能的?

在这里插入图片描述
在这里插入图片描述
基于 sendfile + DMA gather copy 系统调用的零拷贝方式,整个拷贝过程会发生 2 次上下文切换、0 次 CPU 拷贝以及 2 次 DMA 拷贝,用户程序读写数据的流程如下:
在这里插入图片描述

用户进程通过 sendfile() 函数向内核(kernel)发起系统调用,上下文从用户态(user space)切换为内核态(kernel space)。

CPU 利用 DMA 控制器将数据从主存或硬盘拷贝到内核空间(kernel space)的读缓冲区(read buffer)。

CPU 把读缓冲区(read buffer)的文件描述符(file descriptor)和数据长度拷贝到网络缓冲区(socket buffer)。

基于已拷贝的文件描述符(file descriptor)和数据长度,CPU 利用 DMA 控制器的 gather/scatter 操作直接批量地将数据从内核的读缓冲区(read buffer)拷贝到网卡进行数据传输。

上下文从内核态(kernel space)切换回用户态(user space),sendfile 系统调用执行返回。

sendfile + DMA gather copy 拷贝方式同样存在用户程序不能对数据进行修改的问题,而且本身需要硬件的支持,它只适用于将数据从文件拷贝到 socket 套接字上的传输过程。

32.分布式事务三阶段提交的思想能说一下吗3PC?

在这里插入图片描述

33.唯一id生成机制中的snowflake算法的时钟回拨问题如何解决?

判断是否发生了时钟回拨,当前时间比我上一次生成id的时间要小,此时就是发生了时钟回拨问题,12:00:00 500 28 1~20,snowflake算法生成的不重复的id,此时会导致生成的id是重复的,这就比较坑了

比较简单容易理解的思路,当前的机器的可能会跟一台基准时间服务器进行时间校准,导致你的机器的时间本来跑的稍微快了一点,此时跟基准时间服务器进行了校准,你的时间回拨回去了,倒退回去了

你在内存里把过去1个小时之内生成的每一毫秒的每台机器生成的id都在内存里保存最大的那个id

12:00:00 500 28 20

12:00:04 300 28 8

如果发生了时钟回拨,此时你看看时钟汇报到了之前的哪一毫秒里去,直接接着在那一毫秒里的最大的id继续自增就可以了,12:00:00 500 28 21

34.zookeeper

34.1 ZooKeeper为了满足分布式系统的需求要有哪些特点?

在这里插入图片描述
ZooKeeper肯定是一套系统,这个系统可以存储元数据,支持Master选举,可以进行分布式协调和通知

集群部署:不可能单机版本

顺序一致性:所有请求全部有序

原子性:要么全部机器都成功,要么全部机器都别成功

数据一致性:无论连接到哪台ZK上去,看到的都是一样的数据,不能有数据不一致

高可用:如果某台机器宕机,要保证数据绝对不能丢失

实时性:一旦数据发生变更,其他人要实时感知到

34.2 为了满足分布式系统的需求,ZooKeeper的架构设计有哪些特点?

在这里插入图片描述
为了实现需要的一些特性,ZooKeeper的架构设计需要有哪些特点?

集群化部署:3~5台机器组成一个集群,每台机器都在内存保存了zk的全部数据,机器之间互相通信同步数据,客户端连接任何一台机器都可以

树形结构的数据模型:znode,树形结构,数据模型简单,纯内存保存

数据结构就跟我们的文件系统是类似的,是有层级关系的树形的文件系统的数据结构

znode可以认为是一个节点而已

create /usr/local/uid

create /usr/local/test_file

uid:可以写入一些数据的值,比如说hello world

test_file:也可以写入一些数据的值

**顺序写:**集群中只有一台机器可以写,所有机器都可以读,所有写请求都会分配一个zk集群全局的唯一递增编号,zxid,保证各种客户端发起的写请求都是有顺序的

**数据一致性:**任何一台zk机器收到了写请求之后都会同步给其他机器,保证数据的强一致,你连接到任何一台zk机器看到的数据都是一致的

**高性能:**每台zk机器都在内存维护数据,所以zk集群绝对是高并发高性能的,如果你让zk部署在高配置物理机上,一个3台机器的zk集群抗下每秒几万请求没有问题

**高可用:**哪怕集群中挂掉不超过一半的机器,都能保证可用,数据不会丢失,3台机器可以挂1台,5台机器可以挂2台

**高并发:**高性能决定的,只要基于纯内存数据结构来处理,并发能力是很高的,只有一台机器进行写,但是高配置的物理机,比如16核32G,写入几万QPS,读,所有机器都可以读,3台机器的话,起码可以支撑十几万QPS

34.3 ZooKeeper集群的三种角色:Leader、Follower、Observer

在这里插入图片描述
通常来说ZooKeeper集群里有三种角色的机器

集群启动自动选举一个Leader出来,只有Leader是可以写的,Follower是只能同步数据和提供数据的读取,Leader挂了,Follower可以继续选举出来Leader,Observer也只能读但是Observer不参与选举

34.4 客户端与ZooKeeper之间的长连接和会话是什么?

在这里插入图片描述
zk集群启动之后,自己分配好角色,然后客户端就会跟zk建立连接,是TCP长连接

也就建立了一个会话,就是session,可以通过心跳感知到会话是否存在,有一个sessionTimeout,意思就是如果连接断开了,只要客户端在指定时间内重新连接zk一台机器,就能继续保持session,否则session就超时了

34.5 ZooKeeper的数据模型:znode和节点类型

临时节点,持久节点,顺序节点

34.6 ZooKeeper最核心的一个机制:Watcher监听回调

在这里插入图片描述

34.7 zk到底通过什么协议在集群间进行数据一致性同步?

在整个zk的架构和工作原理中,有一个非常关键的环节,就是zk集群的数据同步是用什么协议做的?其实用的是特别设计的ZAB协议,ZooKeeper Atomic Broadcast,就是ZooKeeper原子广播协议

34.8 ZAB的核心思想介绍:主从同步机制和崩溃恢复机制

两阶段提交
在这里插入图片描述

34.9 从zk集群启动到数据同步再到崩溃恢复的ZAB协议流程

zk集群启动的时候,进入恢复模式,选举一个leader出来,然后leader等待集群中过半的follower跟他进行数据同步,只要过半follower完成数据同步,接着就退出恢复模式,可以对外提供服务了

只要有超过一半的机器,认可你是leader,你就可以被选举为leader

只有leader可以接受写请求,但是客户端可以随便连接leader或者follower,如果客户端连接到follower,follower会把写请求转发给leader

leader收到写请求,就把请求同步给所有的follower,过半follower都说收到了,就再发commit给所有的follower,让大家提交这个请求事务

如果突然leader宕机了,会进入恢复模式,重新选举一个leader,只要过半的机器都承认你是leader,就可以选举出来一个leader,所以zk很重要的一点是主要宕机的机器数量小于一半,他就可以正常工作

因为主要有过半的机器存活下来,就可以选举新的leader

新leader重新等待过半follower跟他同步,完了重新进入消息广播模式

集群启动:恢复模式,leader选举(过半机器选举机制) + 数据同步

消息写入:消息广播模式,leader采用2PC模式的过半写机制,给follower进行同步

崩溃恢复:恢复模式,leader/follower宕机,只要剩余机器超过一半,集群宕机不超过一半的机器,就可以选举新的leader,数据同步

34.10 采用了2PC两阶段提交思想的ZAB消息广播流程

在这里插入图片描述
每一个消息广播的时候,都是2PC思想走的,先是发起事务Proposal的广播,就是事务提议,仅仅只是个提议而已,各个follower返回ack,过半follower都ack了,就直接发起commit消息到全部follower上去,让大家提交

发起一个事务proposal之前,leader会分配一个全局唯一递增的事务id,zxid,通过这个可以严格保证顺序

leader会为每个follower创建一个队列,里面放入要发送给follower的事务proposal,这是保证了一个同步的顺序性

每个follower收到一个事务proposal之后,就需要立即写入本地磁盘日志中,写入成功之后就可以保证数据不会丢失了,然后返回一个ack给leader,然后过半follower都返回了ack,leader推送commit消息给全部follower

leader自己也会进行commit操作

commit之后,就意味这个数据可以被读取到了

34.11 ZooKeeper到底是强一致性还是最终一致性?

强一致性:只要写入一条数据,立马无论从zk哪台机器上都可以立马读到这条数据,强一致性,你的写入操作卡住,直到leader和全部follower都进行了commit之后,才能让写入操作返回,认为写入成功了

此时只要写入成功,无论你从哪个zk机器查询,都是能查到的,强一致性

明显,ZAB协议机制,zk一定不是强一致性

最终一致性:写入一条数据,方法返回,告诉你写入成功了,此时有可能你立马去其他zk机器上查是查不到的,短暂时间是不一致的,但是过一会儿,最终一定会让其他机器同步这条数据,最终一定是可以查到的

研究了ZooKeeper的ZAB协议之后,你会发现,其实过半follower对事务proposal返回ack,就会发送commit给所有follower了,只要follower或者leader进行了commit,这个数据就会被客户端读取到了

那么有没有可能,此时有的follower已经commit了,但是有的follower还没有commit?绝对会的,所以有可能其实某个客户端连接到follower01,可以读取到刚commit的数据,但是有的客户端连接到follower02在这个时间还没法读取到

所以zk不是强一致的,不是说leader必须保证一条数据被全部follower都commit了才会让你读取到数据,而是过程中可能你会在不同的follower上读取到不一致的数据,但是最终一定会全部commit后一致,让你读到一致的数据的

zk官方给自己的定义:顺序一致性

因此zk是最终一致性的,但是其实他比最终一致性更好一点,出去要说是顺序一致性的,因为leader一定会保证所有的proposal同步到follower上都是按照顺序来走的,起码顺序不会乱

但是全部follower的数据一致确实是最终才能实现一致的

如果要求强一致性,可以手动调用zk的sync()操作

34.12 ZAB协议下一种可能存在的数据一致性问题

Leader收到了过半的follower的ack,接着leader自己commit了,还没来得及发送commit给所有follower自己就挂了,这个时候相当于leader的数据跟所有follower是不一致的,你得保证全部follower最终都得commit

另外一个,leader可能会自己收到了一个请求,结果没来得及发送proposal给所有follower之前就宕机了,此时这个Leader上的请求应该是要被丢弃掉的

所以在leader崩溃的时候,就会选举一个拥有事务id最大的机器作为leader,他得检查事务日志,如果发现自己磁盘日志里有一个proposal,但是还没提交,说明肯定是之前的leader没来得及发送commit就挂了

此时他就得作为leader为这个proposal发送commit到其他所有的follower中去,这个就保证了之前老leader提交的事务已经会最终同步提交到所有follower里去

然后对于第二种情况,如果老leader自己磁盘日志里有一个事务proposal,他启动之后跟新leader进行同步,发现这个事务proposal其实是不应该存在的,就直接丢弃掉就可以了

34.13 崩溃恢复时选举出来的Leader是如何跟其他Follower进行同步的?

新选举出来一个leader之后,本身人家会挑选已经收到的事务zxid里最大的那个follower作为新的leader。

5个机器,1leader + 4个follower

1个leader把proposal发送给4个follower,其中3个folower(过半)都收到了proposal返回ack了,第四个follower没收到proposal

此时leader执行commit之后自己挂了,commit没法送给其他的follower,commit刚发送给一个follower

剩余的4个follower,只要3个人投票一个人当leader,就是leader

假设那3个收到proposal的follower都投票第四台没有收到proposal的follower当心的leader?这条数据一定永久性丢失了

选择一个拥有事务zxid最大的机器作为新Leader

其他的follower就会跟他进行同步,他给每个follower准备一个队列,然后把所有的proposal都发送给follower,只要过半follower都ack了,就会发送commit给那个follower

所谓的commit操作,就是把这条数据加入内存中的znode树形数据结构里去,然后就对外可以看到了,也会去通知一些监听这个znode的人

如果一个follower跟leader完全同步了,就会加入leader的同步follower列表中去,然后过半follower都同步完毕了,就可以对外继续提供服务了

34.14 对于需要丢弃的消息是如何在ZAB协议中进行处理的?

每一条事务的zxid是64位的,高32位是leader的epoch,就认为是leader的版本吧;低32位才是自增长的zxid

老leader发送出去的proposal,高32位是1,低32位是11358

如果一个leader自己刚把一个proposal写入本地磁盘日志,就宕机了,没来得及发送给全部的follower,此时新leader选举出来,他会的epoch会自增长一位

proposal,高32位是2,低32位是继续自增长的zxid

然后老leader恢复了连接到集群是follower了,此时发现自己比新leader多出来一条proposal,但是自己的epoch比新leader的epoch低了,所以就会丢弃掉这条数据

启动的时候,过半机器选举leader,数据同步

对外提供服务的时候,2PC + 过半写机制,顺序一致性(最终的一致性)

崩溃恢复,剩余机器过半,重新选举leader,有数据不一致的情况,针对两种情况自行进行处理,保证数据是一致的(磁盘日志文件、zxid的高32位)

34.15 ZooKeeper的Observer节点是用来干什么的?

Observer节点是不参与leader选举的,他也不参与ZAB协议同步时候的过半follower ack的那个环节,他只是单纯的接收数据,同步数据,可能数据存在一定的不一致的问题,但是是只读的

leader在进行数据同步的时候,observer是不参与到过半写机制里去

所以大家思考一个问题了

zk集群无论多少台机器,只能是一个leader进行写,单机写入最多每秒上万QPS,这是没法扩展的,所以zk是适合写少的场景

但是读呢?follower起码有2个或者4个,读你起码可以有每秒几万QPS,没问题,那如果读请求更多呢?此时你可以引入Observer节点,他就只是同步数据,提供读服务,可以无限的扩展机器

34.16 ZooKeeper为什么只能是小集群部署?为什么适合读多写少场景?

为什么zk的leader和follower只能是三五台机器,小集群部署?因为你想,假设你有1个leader + 20个follower,21台机器,你觉得靠谱吗?不靠谱,因为follower要参与到ZAB的写请求过半ack里去

如果你有20个follower,一个写请求出去,要起码等待10台以上的Follower返回ack,才能发送commit,才能告诉你写请求成功了,性能是极差的

所以zk的这个ZAB协议就决定了一般其实就是1个leader + 2个follower的小集群就够了,写请求是无法扩展的,读请求如果量大,可以加observer机器,最终就是适合读多写少的场景

主要就是用于分布式系统的一些协调工作

这也就让大家知道了,很多互联网公司里,不少系统乱用zk,以为zk可以承载高并发写,结果每秒几万写请求下去,zk的leader机器直接可能就挂掉了,扛不住那么大的请求量,zk一旦挂掉,连带的kafka等系统会全部挂掉

zk适合读多写少的,zk集群挂掉了

leader写入压力过大, 最终导致集群挂掉了,对一个公司的技术平台是有重大打击的,hbase、kafka之类的一些技术都是强依赖zk的,dubbo + zk去做服务框架的话,有上万甚至几十瓦的服务实例的时候

大量的服务的上线、注册、心跳的压力,达到了每秒几万,甚至上十万,zk的单个leader写入是扛不住那么大的压力的

一般适合写比较少

读比较多,observer节点去线性扩展他的高并发读的能力

34.17 ZooKeeper特性的总结

集群模式部署

一般奇数节点,因为你5台机器可以挂2台,6台机器也是挂2台,不能超过一半的机器挂掉,所以5台和6台效果一致,那奇数节点可以减少机器开销,小集群部署,读多写少

主从架构:Leader、Follower、Observer(一般刚开始没必要用)

内存数据模型:znode,多种节点类型

客户端跟zk进行长连接,TCP,心跳,维持session

zxid,高32位,低32位

ZAB协议,2PC,过半ack + 磁盘日志写,commit + 写内存数据结构

支持Watcher机制,监听回调通知

顺序一致性:消息按顺序同步,但是最终才会一致,不是强一致

高性能,2PC中的过半写机制,纯内存的数据结构,znode

高可用,follower宕机没影响,leader宕机有数据不一致问题,新选举的leader会自动处理,正常运行,但是在恢复模式期间,可能有一小段时间是没法写入zk的

高并发,单机leader写,Observer可以线性扩展读QPS

35. 假设让你来负责微信朋友圈这样的社交系统,应该如何设计?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

36.微信朋友圈是如何对好友显示权限进行控制的?

发送朋友圈的时候,可以通过几种方式进行谁可以看你这条朋友圈的权限的控制,你发的时候可以选择屏蔽谁,对哪个标签下的人开放

这条朋友圈的权限到了后台之后,会有一个离线批处理的程序跑起来,对最近发的一波朋友圈都找他们的朋友圈的权限的设置看一下,此时就会对你允许看到的好友,此时就在他们的时间线里插入这条朋友圈数据,那么这样的话,只有你允许的好友的时间线里才有你这条朋友圈

比如说王五发的朋友圈16931可以允许张三和李四看到,设置了一个标签组,标签名称是老铁三人组,里面就正好有张三和李四

张三 发表朋友圈的时间戳 朋友圈16931 王五

张三 发表朋友圈的时间戳 朋友圈16384 李四

李四 发表朋友圈的时间戳 朋友圈16931 王五

在redis里可以设置张三的朋友圈是有变动的一个状态,在上次拉取朋友圈的时间点之后的一些朋友圈都从时间线表里拉取出来,刷朋友圈的时候,如果说你的网速要是不太好的话,你会发现这样一个场景

就是你最新的一些朋友发的朋友圈是显示出来了,但是视频和图片都是一片灰色,仅仅能看到他的文字和其他的一些东西,比如说点赞之类的,图片和视频死活看不到,都是一片灰色,反正我自己网速不好的时候经常看到这样的情况

假设王五之前发了一条朋友圈,设置李四可以看到的,李四之前确实是看到了这条朋友圈的,但是有个问题,王五后来跟李四吵了一架,关系变得非常的不好,王五就对李四设置了一个朋友圈的权限,就是自己的朋友圈不允许李四看到,甚至可能会直接拉黑/删除李四这个好友,这个就够狠了

你设置自己的朋友圈对所有朋友都是仅仅三天之内可见

就是说你跟李四之间的朋友圈的权限总设置或者是朋友之间的关系,有了变化,或者是你的自己的朋友圈对外展示的总权限有了变化,此时每次如果有变动,那么这些设置,包括你对每个朋友的朋友圈权限的设置,跟朋友的关系,自己的朋友圈的总权限,这些设置都会统统的缓存起来

包括缓存在你自己的客户端本地,也可以缓存在你的朋友的客户端本地

缓存就有点扯了,换设备不就没用了吗。朋友圈权限发生变化时按一定规则同步你的朋友的时间线表?

37.如何设计高并发的朋友圈点赞系统架构?

我看到了你的朋友圈,此时我就可以对你的朋友圈去进行一个点赞,也可以取消点赞,假设要设计成支撑高并发的点赞系统,应该如何设计?

朋友圈的点赞和评论,是独立的数据,其实比如点赞,都是可以基于redis来做的,每个朋友圈里对应一个set数据结构,里面放谁给你点赞了,这样每条朋友圈的点赞人和点赞数量直接从redis出就可以了,smembers和scard

评论也是可以存表里的,都是以朋友圈为粒度来存储

那么刷朋友圈的时候,比如说你好友和你,另外一个好友都是好友,此时你好友刷到了你的朋友圈,就可以把另外一个好友对你的点赞和评论都拉出来,展示在客户端下面就可以了,这个展示过程可以是动态的

你是王五,你的朋友圈被张三点赞了,李四跟你们也是好朋友,此时李四刷朋友圈看到了王五发的这条朋友圈,此时你可以在后台,对这条朋友圈的set用张三做一个sismember操作,就是判断一下你们俩的所有共同好友,有哪些人对这条朋友圈点赞了

此时就可以看出来这条朋友圈被你们的共同好友多少人点赞了,哪些人点赞了

比如你另外一个好友是否对这条朋友圈点赞了,直接sismember就可以判断出来,这样整个你基于redis,他都是非常高性能的

38.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值