首页 > 技术文章 > 面试问题及知识汇总

wyp1988 2019-12-19 23:12 原文

  • 项目
    • 工作中遇到的印象深刻的问题
      • 介绍一个项目中遇到的问题
      • 挑复杂的,能说清楚就行,不一定是自己做的
      • DDCI的性能优化
        • 基于LDAP的嵌套的group信息进行权限配置和检查,一方面可以放到redis缓存中;另一方面不应该自下而上一层层找所有的group来判断,不如自上而下提前处理好各个group下面最终有哪些人,那么也就知道每个人所属的所有层的group了,然后直接放缓存用于后面的权限判断,每日刷新就行了
        • cpu profiling(和java的jvisualjvm类似)
        • 数据库优化
          • 索引
          • sql语句
      • 新三板的日志模块由于单例模式有问题,导致出现大量异常时日志实例占用过多资源
      • 机器人新闻的异动信息的异步处理,由于没有使用mq,导致自己要另外开线程,如果有mq的话,就可以先放进去,然后有一个后台任务一直处理了。
      • mcapps
        • 压测用状态模式,避免面向过程编程,划分清晰
        • 问题排查
          • rabbitmq用临时队列来检查数据情况
          • 提前研究好整个流程,画好图,如rabbitmq的架构图、微服务的架构图、业务流程交互图
    • 工作中遇到的问题如何排查
      • 先脑子里过一遍流程,按顺序、按可能性一个个排查
      • 也可以第一反应就去看日志、数据库等
    • 对岗位的问题
      • 加班、值班、个人时间?
      • 业务范围
      • 工作职责、和其他职能如前端同事的合作
      • 和其他部门的、和总公司的合作关系
      • 绩效?
      • 项目或需求的排期、流程、风格
      • 上线流程、频率
    • 项目开发性设计,自己设计,自己定规则
    • a.产品设计过程中面对的挑战和难点;b、周围同事对我的评价;c、产品设计考虑的几个点
  • 算法
    • 链表
      • 链表操作(在线写代码)
      • 用单向链表表示十进制整数,求两个正整数的和。
        • 相当于链表倒序相加的变种,先反转链表,再倒序相加,最后再反转即可
      • 两个单链表求和
      • 合并K个有序链表(我依然用了优先队列,也可以使用分治)。
      • 实现一个有顺序的LinkedList,实现deleteAll(int val)方法删除所有值为val的节点,和save(int val)方法添加一个值为val的节点。
      • 单链表非递归翻转,不借助其他数据结构
      • 栈,增加一个max方法;
      • stack实现一个min方法,O(1)的复杂度
    • 数组
      • 一个无序数组找其子序列构成的和最大,要求子序列中的元素在原数组中两两都不相邻
      • 输入一个int数组,返回一个数组,其中奇数都在左边,偶数都在右边
      • 给一个数组和一个整数,输出数组中所有和为给定整数的元素下标
      • 一个有序数组,求一个数出现的次数
      • 两个线程,一个只能存有数组1、2、3和另一个存有a、b、c,然后通过调度,最终结果输出1a2b3c
      • 给个数组,只有重复的,求所有和为21的下标:不用n2的暴力算法,要考虑数字相同的下标,先说思路没问 题才能共享屏幕写
      • 给你一个连续素数数组 让你找出增长比第k小的位置
      • 有序数组 A = [1, 2, 2, 3, 3, 5, 5, 5],b = 5,在A中找出b的序号的上下界,考察思路和代码熟练度
      • k路归并排序
    • 字符串
      • 给定字符串的全排列
      • 最小公共子串得长度,最大不重复子串的长度
      • 是输入[I love byte bytedance] Ilovebytebytedance,然后判断中括号里的单词能否组成括号外的语句中括号里的单词用空格符隔开,括号外的语句不能由单词重叠组成,如果输入这个[I love byte bytedance] Ilovebytedance,预期结果返回true
      • 给出一棵二叉树的根节点,现在有这个二叉树的部分节点,要求这些节点最近的公共祖先。
      • 给定一个二叉树,返回二叉树每层的最左的值。
      • 二叉树镜像
      • 反转二叉树
      • 根据二叉树前序遍历生成avl树
      • 二叉树的bfs
    • LRU
      • LRU cache(实现构造函数与get、put,我是用了LinkedHashMap,如果不用LinkedHashMap该如何设计)。
      • LRU的实现
      • lru原理要非常详细的数据结果和过程结构
      • 编码实现LRU算法
    • 大数据量
      • 大数据量的IP去重
      • 大量数据第K大的数(我说可以用优先队列,问到priorityQueue的底层实现,小顶堆的实现方式(数组)、插入删除过程)。
      • 10G文件1G内存,
      • 一个100G的文件,内存只有8G,如何给文件排序,文件内容都是按行存储的URL
      • 统计大量访问日志(分几百M 和 几百G的场景);得出访问次数最多的前K个人 (单台机器实现)
    • 接雨水问题。用数组表示不同高度的台阶,下雨后(足够多的),能存多少水。
      • 左右两个指针即可
    • 一个n边形(P0到Pn), 每一边都是垂直或水平的线段,给定数值k,以P0为起点将它平均分成k段,请打印出所有的k等分点坐标
      • 先算一遍周长?还是说由于都是垂直或水平,会有捷径?
    • 缺失的第一个正数(leetcode第41题)
    • 现有一个随机数生成器可以生成0到4的数,现在要让你用这个随机数生成器生成0到6的随机数,要保证生成的数概率均匀。
    • 有 N 枚棋子,每个人一次可以拿1到 M 个,谁拿完后棋子的数量为0谁就获胜。现在有1000颗棋子,每次最多拿8个,A 先拿,那么 A 有必胜的拿法吗?第一个人拿完后剩余棋子的数量是8的倍数就必胜,否则就必输。
    • 手里有一副扑克牌。按照下列规则吧他堆放桌上。一,拿出最上面的一张牌,放桌上,然后把接下来的一张牌放在扑克牌的最下面。循环,直到没有手牌。现在已知桌上牌的顺序。求原手牌的顺序
    • 一排盒子,每个盒子上标了能走的最大步数,从第一个盒子开始,判断能否走到最后的盒子
    • 输入一串空格分开的整数,相邻不会重复如:1 2 4 3 5,只返回其中一个峰值就可以,比如返回 4 或者 5
      • 峰值定义:大于相邻两个数字就是峰值;对于头尾两个数字,分别大于相邻的一个数字即算峰值
      • 附加条件 不使用额外的空间
    • 找到一个文件里出现次数最多的的数字,文件大小远大于内存容量
    • 打印重复数字
    • 查找前topK
    • B B+ 跳表区别 时间复杂度
    • 贪心算法
    • ab 轮流抛硬币,谁先抛出正面胜出,先抛胜出概率多少
    • 假设是一个抽奖的游戏,不同的人是有不同的概率倍数,是一个整数,例如1、3、5...输入100万人,要求抽奖抽出来2万个人;并且假设每个人都有一个唯一id
    • 搜索引擎倒排原理及实现方式
  • 策略
    • 分布式
      • 分布锁
      • 分布式ID(讲讲雪花算法怎么实现的)
    • 限流
      • 让你设计一个限流的系统怎么做?
        • 令牌桶
      • 限流。限流算法。令牌桶的不足
    • 任务系统怎么保证任务完成后发奖一定成功
    • 让你设计一个延时任务系统怎么做
      • 说了两个方案,一个是使用 redis 的 ZSET 来实现,考虑分片来抗高并发,使用 redis 的持久化来实现落地,使用 redis 的哨兵实现故障转移。一个是使用时间轮的方法。
    • 设计一个文本编辑器实现插入/删除/查询
    • 头条文章向用户推送避免重复推送的问题
    • 微博限定用户每次输入最多140个字符,用户如果传字符串很长的链接,怎么办
    • 多个人玩贪吃蛇设计
    • 设计群消息已读功能
    • 缓存设计
    • 写一个函数做下抽奖,输入和输出的数据结构自己设计
    • 微信扫码登录
  • 数据库
    • 分库分区分表

      • 数据库分表 / 分库 / 集群
      • 如何分库分表
      • 分库分表
    • 索引

      • 原理及实现
        • 数据库索引实现原理
        • 平衡树、B+树
        • 数据库聊到了索引,B+树(大致结构、为何要用、插入删除大致流程)。
        • 索引的实现方式,为什么会采用这种数据结构
          • 非叶子节点不带数据,这样一个块可以容纳更多的索引项,一是可以降低树的高度,二是一个内部节点可以定位更多的叶子节点。
          • 叶子节点之间通过指针连接,范围扫描将十分简单。而对于B树来说,则需要在叶子节点和内部节点间不停的往返移动。
          • 平衡树比简单的折半查找要好,分布更均匀,树的层数尽可能少
        • 事物都是有两面的, 索引能让数据库查询数据的速度上升, 而使写入数据的速度下降,原因很简单的, 因为平衡树这个结构必须一直维持在一个正确的状态, 增删改数据都会改变平衡树各节点中的索引数据内容,破坏树结构, 因此,在每次数据改变时, DBMS必须去重新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销,也就是为什么索引会给查询以外的操作带来副作用的原因。
      • 聚集索引与非聚集索引(主键索引和非主键索引的区别)
        • 非聚集索引和聚集索引的区别在于, 通过聚集索引可以查到需要查找的数据, 而通过非聚集索引可以查到记录对应的主键值 , 再使用主键的值通过聚集索引查找到需要的数据
      • 联合索引/复合索引(设计索引,为何最左原则)
        • 关键要根据具体业务、代码中的sql语句分析出哪些字段会用于where语句
        • 数据库3个字段的联合索引,在用单字段时能否命中索引
          • 能,但只能命中最左边那个字段的单字段查询
          • 但是如果用两个字段来查询,必须是左边的两个
        • 对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效。
        • 数据库索引:表有3列a,b,c,需支持查询(a,b)(b) (b,c),索引如何建
          • (a,b),(b,c)
        • where a>1 and b>1;where a = 1; where b = 2,如何为这种条件语句建立索引;
          • (a,b),(b)
        • 索引不会包含有NULL值的列
          • 只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。
      • 表扫描(Table Scan)
        • 最慢,即扫描全表
      • hash索引(范围查找为何不适用)。
      • 数据库如何建索引
      • 数据库索引+前缀匹配
      • 主键与索引的联系和区别
    • sql

      • Sql查询第三大的age
      // 2、3名,第二次升序后,把第一名过滤掉了
      SELECT t3.* 
      FROM
      (
          SELECT top 2 t2.* 
          FROM   (
              SELECT 
                  top 3 NAME,
                  SUBJECT,
                  score,
                  stuid 
              FROM stuscore 
              WHERE SUBJECT = '数学'
              ORDER BY score DESC
              ) t2 
          ORDER BY t2.score
      ) t3 
      ORDER BY t3.score DESC
      
      SELECT 
        * 
      FROM
        stuscore 
      WHERE SUBJECT = '数学'
      ORDER BY score DESC 
      LIMIT 2, 1;
      
      • 每门课程都大于80分的学生名字
      //not in 
      SELECT DISTINCT A.name FROM Student A 
      WHERE A.name not in(
      SELECT Distinct S.name FROM Student S WHERE S.score <80)
      
      //not exists
      
      SELECT DISTINCT A.name From Student A  
      where not exists (SELECT 1 From Student S Where  S.score <80 AND S.name =A.name)
      
      SELECT S.name
      FROM Student S
      GROUP BY S.name
      Having MIN(S.score)>=80
      
    • sql优化

      • 数据库查询优化
    • 事务、隔离级别、锁

      • 数据库事务的隔离级别
        • 事务的四大特性(ACID)
          • 1、原子性(Atomicity)
            • 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
          • 2、一致性(Consistency)
            • 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
            • 拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
          • 3、隔离性(Isolation)
            • 隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
            • 即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
          • 4、持久性(Durability)
            • 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
            • 例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,
            • 即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
        • 事务的隔离级别(默认事务级别为可重复读)
          • 总的说,数据库事务无非就两种:读取事务(select)、修改事务(update,insert)。在没有事务隔离控制的时候,多个事务在同一时刻对同一数据的操作可能就会影响到最终期望的结果,通常有四种情况:
            • (1)修改时允许修改(丢失更新)。两个更新事务同时修改一条数据时,很显然这种情况是最严重的了,程序中无论如何也不能出现这种情况,因为它会造成更新的丢失!
            • (2)修改时允许读取(脏读)。一个更新事务更新一条数据时,另一个读取事务读取了还没提交的更新,这种情况下会出现读取到脏数据。更新期间发生读
            • (3)读取时允许修改(不可重复读)。一个读取事务读取一条数据时,另一个更新事务修改了这条数据,这时就会出现不可重现的读取。读之间发生更新
            • (4)读取时允许插入(幻读)。一个读取事务读取时,另一个插入事务(注意此处时插入)插入了一条新数据,这样就可能多读出一条数据,出现幻读。
      • 乐观锁 / 悲观锁
        • 悲观锁(肯定没问题,但是性能差)
          • 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现
        • 乐观锁(更新的时候复杂一点,还要有版本管理)
          • 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制(写时的加1后的版本号要大于前面取到的版本号),其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
    • 自增ID与uuid的优劣

      • 自增主键
        • 自增ID是在设计表时将id字段的值设置为自增的形式,这样当插入一行数据时无需指定id会自动根据前一字段的ID值+1进行填充。在MySQL数据库中,可通过sql语句AUTO_INCREMENT来对特定的字段启用自增赋值 使用自增ID作为主键,能够保证字段的原子性.
        • 优点
          • 数据库自动编号,速度快,而且是增量增长,按顺序存放,对于检索非常有利;
          • 数字型,占用空间小,易排序,在程序中传递也方便;
          • 如果通过非系统增加记录时,可以不用指定该字段,不用担心主键重复问题。
        • 缺点
          • 因为自动增长,在手动要插入指定ID的记录时会显得麻烦,尤其是当系统与其它系统集成时,需要数据导入时,很难保证原系统的ID不发生主键冲突(前提是老系统也是数字型的)。特别是在新系统上线时,新旧系统并行存在,并且是异库异构的数据库的情况下,需要双向同步时,自增主键将是你的噩梦;
          • 在系统集成或割接时,如果新旧系统主键不同是数字型就会导致修改主键数据类型,这也会导致其它有外键关联的表的修改,后果同样很严重;
          • 若系统也是数字型的,在导入时,为了区分新老数据,可能想在老数据主键前统一加一个字符标识(例如“o”,old)来表示这是老数据,那么自动增长的数字型又面临一个挑战。
      • UUID
        • UUID含义是通用唯一识别码 (Universally Unique Identifier),指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。换句话说能够在一定的范围内保证主键id的唯一性。
        • 优点
          • 出现数据拆分、合并存储的时候,能达到全局的唯一性
        • 缺点
          • 影响插入速度, 并且造成硬盘使用率低
          • uuid之间比较大小相对数字慢不少, 影响查询速度。
          • uuid占空间大, 如果你建的索引越多, 影响越严重
    • MySQL

      • 索引
        • mysql索引的使用和原理
        • Mysql的索引结构 聚簇索引
      • 存储引擎
        • 两种存储引擎比较
        • mysql的innodb与myisam区别
        • myisam(默认)
          • MyISAM是MySQL默认的引擎,但是它没有提供对数据库事务的支持,也不支持行级锁和外键,因此当INSERT(插入)或UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。不过和Innodb不同,MyISAM中存储了表的行数,于是SELECT COUNT(*) FROM TABLE时只需要直接读取已经保存好的值而不需要进行全表扫描。如果表的读操作远远多于写操作且不需要数据库事务的支持,那么MyISAM也是很好的选择。
        • innodb(常用)
          • Innodb引擎提供了对数据库ACID事务的支持,并且实现了SQL标准的四种隔离级别。该引擎还提供了行级锁和外键约束,它的设计目标是处理大容量数据库系统,它本身其实就是基于MySQL后台的完整数据库系统,MySQL运行时Innodb会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎不支持FULLTEXT类型的索引,而且它没有保存表的行数,当SELECT COUNT(*) FROM TABLE时需要扫描全表。当需要使用数据库事务时,该引擎当然是首选。由于锁的粒度更小,写操作不会锁定全表,所以在并发较高时,使用Innodb引擎会提升效率。但是使用行级锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表。
      • MySQL 事务的四个隔离级别?
        • 先说了四个级别的区别,然后说了每个级别可能产生的问题
      • binlog 日志和 redolog 日志清楚吗?
        • 说了两个日志的作用以及两阶段提交
        • 日志系统主要有redo log(重做日志)和binlog(归档日志)。redo log是InnoDB存储引擎层的日志,binlog是MySQL Server层记录的日志, 两者都是记录了某些操作的日志(不是所有)自然有些重复(但两者记录的格式不同)。
        • redolog
          • redo log是InnoDB存储引擎层的日志,又称重做日志文件,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。在实例和介质失败(media failure)时,redo log文件就能派上用场,如数据库掉电,InnoDB存储引擎会使用redo log恢复到掉电前的时刻,以此来保证数据的完整性。
          • 在一条更新语句进行执行的时候,InnoDB引擎会把更新记录写到redo log日志中,然后更新内存,此时算是语句执行完了,然后在空闲的时候或者是按照设定的更新策略将redo log中的内容更新到磁盘中,这里涉及到WAL即Write Ahead logging技术,他的关键点是先写日志,再写磁盘。
        • binlog
          • binlog是属于MySQL Server层面的,又称为归档日志,属于逻辑日志,是以二进制的形式记录的是这个语句的原始逻辑,依靠binlog是没有crash-safe能力的
        • 区别
          • redo log是属于innoDB层面,binlog属于MySQL Server层面的,这样在数据库用别的存储引擎时可以达到一致性的要求。
          • redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录的是这个更新语句的原始逻辑
          • redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
          • binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。
    • 数据库表(id, name, parentId)如何组装成树?

    • PostgreSQL

    • ORM

  • Java
    • 基础
      • JDK基础 集合 锁
      • fail-fast
      • string转double
    • JVM
      • GC CMS,CMS的参数
      • JVM的一些排查工具jstat, jstack ,jmap
      • out Of memory Error 跟 stack Over flow Error 的比较
    • 源码
      • Hash相关数据结构
        • Set实现、HashSet
        • 解决哈希冲突的方法(详细说说实现)、哈希一致性算法(为什么是环形结构,聊到应用场景会问到分布式缓存)
        • 问HashSet底层实现的时候自然而然提到HashHashMap、HashMap底层实现、1.8改进、线程安不安全、为什么不安全,解决方式。
        • Hashtable、concurrentHashMap(并发、分段锁、cas、synchronized、锁粒度)
        • HashMap
        • hashmap实现方式
        • 自己设计hash;
      • Map 底层原理
      • java 的 array 跟 list的比较,底层原理上的
    • 多线程
      • 线程池的工作流程。线程的几种状态,什么情况会进入阻塞
      • java多线程知识点,如synchronized,reentrantLock以及两者比较
      • java线程池,submit跟execute的区别
      • java线程池基础知识、自己实现一个线程池
      • 并发控制(乐观锁,悲观锁)
    • 异常处理
    • 设计模式
  • 中间件
    • Redis
      • Redis是单线程的,所以很安全。
      • 数据结构
        • 数据结构有哪些?分别怎么实现的?
        • string
          • 使用场景
            • 存储简单的value值,如用Incrby命令操作一个IP地址的访问次数,用于判断什么时候封锁一个恶意的IP地址
          • Set(key, value)
          • Get(key)
        • hash
          • 使用场景
            • 存储复杂的value值,和使用string相比,避免了修改其中某个字段时,需要反序列化字符串
          • Hset(key, field, value)
          • Hget(key, field)
        • list
          • 使用场景
            • 实现最新消息的排行
            • 模拟消息队列,利用list的push命令,将任务存在list集合中,同时使用另一个pop命令,将任务从集合中取出。
              • 比如电商中的秒杀可以用这种方式来完成一个秒杀活动。
        • set
          • 使用场景
            • 微博中将每个人的好友存在集合(Set)中,避免列表中出现某个好友的两次,这样求两个人的共同好友的操作,只需要求交集即可。
          • 可以自动去重
        • ZSET
          • 使用场景
            • 都是利用了其高效的排序、redis的性能
            • 查看商品详情时,都会有一个综合排名,还可以按照价格进行排名
            • 延迟队列
          • 概念
            • 即Sorted set
            • 可以以某一个条件为权重进行排序
            • Redis有序集合和Redis集合类似,是不包含 相同字符串的合集。它们的差别是,每个有序集合 的成员都关联着一个评分,这个评分用于把有序集 合中的成员按最低分到最高分排列。
            • 使用有序集合,你可以非常快地(O(log(N)))完成添加,删除和更新元素的操作。 因为元素是在插入时就排好序的,所以很快地通过评分(score)或者 位次(position)获得一个范围的元素。 访问有序集合的中间元素同样也是非常快的,因此你可以使用有序集合作为一个没用重复成员的智能列表。 在这个列表中, 你可以轻易地访问任何你需要的东西: 有序的元素,快速的存在性测试,快速访问集合中间元素
          • zset 延时队列怎么实现的
            • 将延迟的时间+系统当前时间,作为元素的score属性的值,在while循环中,每次取zset集合中排在最前面的一个元素,通过判断该元素的score值是否大于或等于当前系统时间,来判定队列中该元素是否已达到延迟的时间。
            • 然后多个线程的环境怎么保证任务不被多个线程抢了?这里可以使用Redis的zrem命令来实现
            • Redis Zrem 命令用于移除有序集中的一个或多个成员,不存在的成员将被忽略。
          • Redis 的 ZSET 怎么实现的?
            • 尽量介绍的全一点,跳跃表加哈希表以及压缩链表
            • 和平衡树(红黑树)等的对比
            • redis zset内部实现
          • Redis 的 ZSET 做排行榜时,如果要实现分数相同时按时间顺序排序怎么实现?
            • 使用Redis Zset来处理活动常用排行榜(精确排行)
            • Redis 排行榜 相同分数根据时间优先排行
            • 默认
              • Redis 提供了按分数进行排序的有序集合。 比如在游戏里面,比如战斗力排行,充值排行,用默认的Redis 实现就可以达到需求。
              • 但是,比如等级排行,大家都是30级,谁先到30级谁第一。Redis 默认实现是,相同分数的成员按字典顺序排序(0 ~9 , A ~Z,a ~ z),所以相同分数排序就不能根据时间优先来排序。
            • 方案
              • 分值由业务分值+时间戳组成(给分值赋值时,业务分值乘以时间戳的位数,然后加上时间戳,得到存到redis中的分值。)。由于redis的分值只有有限的16位,先给正常的业务分值留够位置(比如粉丝数最大可能到亿级,那么就要留9位),那么只剩7位给时间戳了(以秒为单位的话)。由于redis是将分值越大的排名越靠前,而业务上一般需要时间戳越小的越排名靠前说明他更早完成当前业务分值,所以需要用一个常量(秒数)减去当前时间戳(活动开始后的秒数)的差值再作为分值的时间戳部分。而且要保证这个7位是够用的,不然如果活动时间过长的话,7位的秒数可能不够用。
              • 首先我们要理解一个redis的排序,redis的zset中的score值为double类型,精度只有16位,事实上在存储9999999999999999 16位整数的时候,会变为17位的10000000000000000,超过17位会变为科学计数法1e+17。
              • 在活动中,我们要保证用户的分数不能丢失,所以必须保证在16位以内
              • 首先保证数字A-结束时间为一个7位数 ,那么我们要给这个结束时间+1000000 (一百万) 得到数字A,这样能最低限度保证活动结束之前时间戳系数的位数不变为7位数。
              • 然后我们要保证数字A-活动开始时间也是一个七位数,也就是说开始时间和结束时间之间的跨度只能是900w,一旦超过900w,那么数字A减去开始时间就会得到一个8位数。
            • 说了一个将 score 拆成高 32 位和低 32 位,高 32 位存分数,低 32 位存时间的方法。问还有没有其他方法,想不出了
            • 如果有三个,四个排序条件怎么办,这种情况还是推荐使用数据库,就别考虑 Redis了 。Redis 优势在于可以做到实时排行
          • 操作
            • zadd
            • zcard
            • zrange
            • zrank
            • zcount
            • zrem
            • zscore
            • zincrby
            • 等等
      • 持久化
        • RDB
          • 类似于数据库的全量备份,全量替换

          • 在指定的时间间隔内将内存中的所有数据集快照写入磁盘,也就是行话讲的SnapShot快照,它恢复时是将快照文件直接读到内存里,全量的替换原先的文件。

          • Redis会单独创建(fork)一个子进程来进行持久化,不会影响主进程,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。

          • 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB的方式要比AOF方式更加的高效。

          • 操作

            • 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令
          • 过程

            • 通过rdbSave、rdbLoad操作在内存中的数据对象和磁盘中的RDB文件之间进行转换。
          • 配置

            • 打开 redis.conf 文件,找到 SNAPSHOTTING 对应内容
              • RDB核心规则配置(重点)

                • save <指定时间间隔> <执行指定次数更新操作>,满足条件就将内存中的数据同步到硬盘中。官方出厂配置默认是 900秒内有1个更改,300秒内有10个更改以及60秒内有10000个更改,则将内存中的数据快照写入磁盘。
                • 若不想用RDB方案,可以把 save "" 的注释打开,下面三个注释。
                save <seconds> <changes>
                # save ""
                save 900 1  
                save 300 10
                save 60 10000
                
              • 指定本地数据库文件名,一般采用默认的 dump.rdb:dbfilename dump.rdb

              • 指定本地数据库存放目录,一般也用默认配置:dir ./

              • 默认开启数据压缩:rdbcompression yes

          • 优点

            • 节省磁盘空间
            • 恢复速度快
            • 不影响Redis的正常使用,高效。
          • 缺点

            • 虽然Redis在fork时使用了写时拷贝技术,但是如果数据量庞大时还是比较消耗性能的。
            • 粒度比较大,满足一定条件时才会触发备份,那么在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。
            • 最后一次持久化后的数据可能会丢失。
        • AOF
          • 和RDB方式互补

          • 类似于数据库的基于事务日志的恢复机制

          • Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

          • 以日志的形式来记录每个写操作,将Redis执行过得所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件(增量的),Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

          • 操作

            • 触发AOF快照。根据配置文件触发,可以是每次执行触发,可以是每秒触发,可以不同步。
            • 根据AOF文件恢复数据。正常情况下,将appendonly.aof 文件拷贝到redis的安装目录的bin目录下,重启redis服务即可。但在实际开发中,可能因为某些原因导致appendonly.aof 文件格式异常,从而导致数据还原失败,可以通过命令redis-check-aof --fix appendonly.aof 进行修复 。
          • 过程

            • 客户端在进行业务操作时,发送命令请求到服务器,服务器会同时把网络协议格式的命令内容写到AOF文件中
          • 配置

            • redis 默认关闭,开启需要手动把no改为yes:appendonly yes

            • 指定本地数据库文件名,默认值为 appendonly.aof:appendfilename "appendonly.aof"

            • 指定更新日志条件:

              • always:同步持久化,每次发生数据变化会立刻写入到磁盘中。性能较差当数据完整性比较好(慢,安全)
              • everysec:出厂默认推荐,每秒异步记录一次(默认值)
              • no:不同步
              # appendfsync always
              appendfsync everysec
              # appendfsync no
              
            • 配置重写触发机制:

              • 当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。一般都设置为3G,64M太小了。
              auto-aof-rewrite-percentage 100
              auto-aof-rewrite-min-size 64mb
              
          • 优点

            • 备份机制更稳健,丢失数据概率更低
            • 可读的日志文本,通过操作AOF稳健,可以处理误操作
          • 缺点

            • 比起RDB占用更多的磁盘空间
            • 恢复备份速度要慢(重新执行一遍)
            • 每次读写都同步的话,有一定的性能压力
            • 存在个别Bug,造成不能恢复
      • 分布式锁
      • Redis缓存删除策略 & redis内存不足,如何解决
        • 被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
          • 注:被动删除:只有key被操作时(如GET),REDIS才会被动检查该key是否过期,如果过期则删除之并且返回NIL。
        • 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key
          • 过期的
            • Redis会周期性的随机测试一批设置了过期时间的key并进行处理。测试到的已过期的key将被删除。
            • 如果没有设置时间,那缓存就是永不过期
            • 如果设置了过期时间,之后又想让缓存永不过期,使用persist key
            • redis过期怎么实现
              • Redis过期策略---实现原理
              • 三种过期策略
                • 定时删除:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
                • 懒汉式删除:key过期的时候不删除,每次通过key获取值的时候去检查是否过期,若过期,则删除,返回null(用的时候再检查删除)。
                • 定期删除:每隔一段时间执行一次删除过期key操作
          • 内存满了时
            • Redis是基于内存的key-value数据库,因为系统的内存大小有限,所以我们在使用Redis的时候可以配置Redis能使用的最大的内存大小。
              • 通过在Redis安装目录下面的redis.conf配置文件中添加以下配置设置内存大小:maxmemory 100mb
              • Redis支持运行时通过命令动态修改内存大小:config set maxmemory 100mb
            • Redis的内存淘汰(当前已用内存超过maxmemory限定时,触发主动清理策略)
              • Redis内存满了的解决办法
              • 既然可以设置Redis最大占用内存大小,那么配置的内存就有用完的时候。那在内存用完的时候,还继续往Redis里面添加数据不就没内存可用了吗?
              • LRU:表示最近最少使用; LFU:表示最不常用的
              • 实际上Redis定义了几种策略用来处理这种情况:
                • noeviction(默认策略):对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外)
                • volatile-lru:从设置了过期时间的key中使用LRU算法进行淘汰
                • allkeys-lru:从所有key中使用LRU算法进行淘汰
                • volatile-lfu 筛选出设置了有效期的,最不常用的 key;
                • allkeys-lfu 所有 key 中,筛选出最不常用的 key ;
                • allkeys-random:从所有key中随机淘汰数据
                • volatile-random:从设置了过期时间的key中随机淘汰
                • volatile-ttl:在设置了过期时间的key中,根据key的过期时间进行淘汰,越早过期的越优先被淘汰
                • 当使用volatile-lru、volatile-random、volatile-ttl这三种策略时,如果没有key可以被淘汰,则和noeviction一样返回错误。
              • 通过配置文件设置淘汰策略(修改redis.conf文件):maxmemory-policy allkeys-lru
              • 通过命令修改淘汰策略:config set maxmemory-policy allkeys-lru
              • Redis使用的是近似LRU算法,它跟常规的LRU算法还不太一样。近似LRU算法通过随机采样法淘汰数据,每次随机出5(默认)个key,从里面淘汰掉最近最少使用的key。可以通过maxmemory-samples参数修改采样数量:例:maxmemory-samples 10
              • Redis3.0对近似LRU算法进行了一些优化。新算法会维护一个候选池(大小为16),池中的数据根据访问时间进行排序,第一次随机选取的key都会放入池中,随后每次随机选取的key只有在访问时间小于池中最小的时间才会放入池中,直到候选池被放满。当放满后,如果有新的key需要放入,则将池中最后访问时间最大(最近被访问)的移除。
              • LFU算法是Redis4.0里面新加的一种淘汰策略。它的全称是Least Frequently Used,它的核心思想是根据key的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来。
      • Redis的事务以及实现方式
        • Redis的事务中启用的是乐观锁,只负责监测key没有被改动.如果没变正常执行,如果有变事务取消
      • Redis高可用方案
        • Redis高可用方案

        • redis中主从、哨兵和集群这三个有什么区别 ?分别有什么优势?适用于什么场景?在实际工作如何选择?

        • Redis哨兵、复制、集群的设计原理与区别

        • 主从复制(Replication-Sentinel模式)、读写分离、主从架构、redis replication?

          • 如果采用了主从架构,那么建议必须开启master node的持久化

          • 主节点(master)负责读写,从节点(slave)负责读

          • Sentinel(哨兵)+主从复制

            • 简单的主从集群有个问题,就是主节点挂了之后,无法从新选举新的节点作为主节点进行写操作,导致服务不可用。所以接下来介绍Sentinel(哨兵)功能的使用。哨兵是一个独立的进程,哨兵会实时监控master节点的状态,当master不可用时会从slave节点中选出一个作为新的master,并修改其他节点的配置指向到新的master。
            • 当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性
            • 可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能
            • 所以经常和主从复制结合起来用
            • 程序中也要进行配置,如:
            spring:
              redis:
                sentinel:
                  master: mymaster
                  nodes: 192.168.1.167:26379,192.168.1.167:26380,192.168.1.167:26381
                host: 192.168.1.164
                port: 7003
                database: 0
                password: <password>
            
        • Redis集群(Redis-Cluster模式)

          • Redis 集群是一个提供在多个Redis节点间共享数据的程序集。
          • 为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有1-n个从节点。例如master-A节点不可用了,集群便会选举slave-A节点作为新的主节点继续服务。
          • 优势
            • 自动分割数据到不同的节点上。
            • 整个集群的部分节点失败或者不可达的情况下能够继续处理命令。
        • 对比

          • 主从复制是为了数据备份,哨兵是为了高可用,Redis主服务器挂了哨兵可以切换,集群则是因为单实例能力有限,搞多个分散压力,简短总结如下:
          • 主从模式:备份数据、负载均衡,一个Master可以有多个Slaves。
          • sentinel发现master挂了后,就会从slave中重新选举一个master。
          • cluster是为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器。
          • sentinel着眼于高可用,Cluster提高并发量。
          • 1.主从模式:读写分离,备份,一个Master可以有多个Slaves。
          • 2.哨兵sentinel:监控,自动转移,哨兵发现主服务器挂了后,就会从slave中重新选举一个主服务器。
          • 3.集群:为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。
        • redis达到十万的并发

          • redis高可用,保证高并发
          • QPS
          • Redis主要消耗的服务器的内存资源,当Redis内存需求超过机器的最大内存,一台机器就够不用了;另外,当我们用户超过一定数量时,单机的redis读写能力就达到瓶颈;最后我们还要考虑单节点故障的问题,如果单机Redis出现问题我们的系统整体将崩溃。因此,需要分片储存,保障我们系统的高可用。
      • 用redis做限流
    • RabbitMQ
      • 高并发环境下,来不及同步处理用户发送的请求(insert、update之类的请求),那么通过分析,将可以拆成异步的操作的数据,放到mq中,然后通过轮训任务异步处理
      • 只要发送消息队列,就算业务成功
      • 充分利用消费能力,削峰
      • 避免直接同步的接口调用
      • 异步
      • 并行
      • 排队
      • 弊端
        • 消息的不确定性
          • 通过延迟队列、轮询技术来解决
      • 集群
        • RabbitMQ这款消息队列中间件产品本身是基于Erlang编写,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的magic cookie来实现)。因此,RabbitMQ天然支持Clustering。这使得RabbitMQ本身不需要像ActiveMQ、Kafka那样通过ZooKeeper分别来实现HA方案和保存集群的元数据。集群是保证可靠性的一种方式,同时可以通过水平扩展以达到增加消息吞吐量能力的目的。
        • RabbitMQ 的4种集群架构
        • RabbitMQ集群搭建
        • 4种集群架构
          • 主备模式(普通、默认集群配置)
            • 也称为 Warren (兔子窝) 模式。实现 rabbitMQ 的高可用集群,一般在并发和数据量不高的情况下,这种模式非常的好用且简单。
            • 也就是一个主/备方案,主节点提供读写,备用节点不提供读写。如果主节点挂了,就切换到备用节点,原来的备用节点升级为主节点提供读写服务,当原来的主节点恢复运行后,原来的主节点就变成备用节点,和 activeMQ 利用 zookeeper 做主/备一样,也可以一主多备。
            • 使用HaProxy
          • 远程模式
            • 远程模式可以实现双活的一种模式,简称 shovel 模式,所谓的 shovel 就是把消息进行不同数据中心的复制工作,可以跨地域的让两个 MQ 集群互联,远距离通信和复制。
            • Shovel 就是我们可以把消息进行数据中心的复制工作,我们可以跨地域的让两个 MQ 集群互联。
          • 镜像模式
            • 非常经典的 mirror 镜像模式,保证 100% 数据不丢失。在实际工作中也是用得最多的,并且实现非常的简单,一般互联网大厂都会构建这种镜像集群模式。
            • mirror 镜像队列,目的是为了保证 rabbitMQ 数据的高可靠性解决方案,主要就是实现数据的同步,一般来讲是 2 - 3 个节点实现数据同步。对于 100% 数据可靠性解决方案,一般是采用 3 个节点。
            • RabbitMQ默认集群模式,但并不保证队列的高可用性,尽管交换机、绑定这些可以复制到集群里的任何一个节点,但是队列内容不会复制。虽然该模式解决一项目组节点压力,但队列节点宕机直接导致该队列无法应用,只能等待重启,所以要想在队列节点宕机或故障也能正常应用,就要复制队列内容到集群里的每个节点,必须要创建镜像队列。
            • 镜像队列是基于普通的集群模式的,然后再添加一些策略,所以你还是得先配置普通集群,然后才能设置镜像队列,我们就以上面的集群接着做。
          • 多活模式
            • 也是实现异地数据复制的主流模式,因为 shovel 模式配置比较复杂,所以一般来说,实现异地集群的都是采用这种双活 或者 多活模型来实现的。这种模式需要依赖 rabbitMQ 的 federation 插件,可以实现持续的,可靠的 AMQP 数据通信,多活模式在实际配置与应用非常的简单。
            • rabbitMQ 部署架构采用双中心模式(多中心),那么在两套(或多套)数据中心各部署一套 rabbitMQ 集群,各中心的rabbitMQ 服务除了需要为业务提供正常的消息服务外,中心之间还需要实现部分队列消息共享。
      • 持久化
        • 如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。当然还是会有一些小概率事件会导致消息丢失。
        • 队列持久化需要在声明队列时添加参数 durable=True,这样在rabbitmq崩溃时也能保存队列
        • 仅仅使用durable=True ,只能持久化队列,不能持久化消息
        • 消息持久化需要在消息生成时,添加参数 properties=pika.BasicProperties(delivery_mode=2)
      • 多个生产者,多个消费者 并发控制queue
    • Kafka
    • MongoDB
    • ELK(Elasticsearch、Logstash、Kibana)
  • 框架
    • Spring Boot
      • Security
      • JPA
    • Spring Cloud
      • Zuul:网关组件,提供智能路由、访问过滤等功能
      • Ribbon:客户端负载均衡的服务调用组件
  • 计算机网络
    • TCP的拥塞窗口、滑动窗口。
      • 滑动窗口和拥塞窗口
      • 滑动窗口和流量控制和重发机制
        • 所谓流量控制,主要是接收方传递信息给发送方,使其不要发送数据太快,是一种端到端的控制。主要的方式就是返回的ACK中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送
        • 流控只简单地表明了接收方的处理能力,并不能代表中间网络的处理能力
        • 发送方和接收方都有滑动窗口。
      • 拥塞窗口和拥塞控制
        • 拥塞:路由器因无法处理高速到达的流量而被迫丢弃数据信息的现象称为拥塞。
        • 如果一开始把流控窗口内的数据全部发送出去,中间路由器可能一时处理不了如此多的突发流量
        • 拥塞控制算法
          • 慢启动
          • 拥塞避免
          • 超时重传
          • 快速重发
          • 快速恢复
    • UDP / TCP 协议
      • 一个 10M 大小的 buffer 里存满了数据,现在要把这个 buffer 里的数据尽量发出去,可以允许部分丢包,问是用TCP好还是UDP好?为什么?
      • TCP 是面向连接的,UDP 是面向无连接的
        • 关键是因为TCP有序号!
      • UDP程序结构较简单
      • TCP 是面向字节流的,UDP 是基于数据报的
      • TCP 保证数据正确性,UDP 可能丢包
      • TCP 保证数据顺序,UDP 不保证
      • UDP不像TCP那样关心顺序问题、丢包问题、连接维护、流量控制、拥塞控制
    • 三次握手
      • 为了在发送数据之前,确认双方都可以正常收发数据并都准备好了
      • 过程
        • 第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
        • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
        • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
      • 第一次让B明白,A的发送没问题,B的接收没问题。
      • 第二次让A明白,A的发送没问题,A的接收没问题,B的发送没问题,B的接收没问题。
      • 第三次让B明白,A的接收没问题,B的发送没问题。
      • 而这也就是双方各个状态名字的由来,第一次后,A是SYNC-SENT,B是SYNC-RCVD;第二次后,A明白双方都可以了,所以是ESTAB-LISHED;第三次后,B才明白双方都可以了,所以才是ESTAB-LISHED
      • 为什么不能用两次握手进行连接?
        • 3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
        • 现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
      • 如果已经建立了连接,但是客户端突然出现故障了怎么办?
        • TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
    • 四次挥手
      • 为什么连接的时候是三次握手,关闭的时候却是四次握手?
        • 因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
      • 为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
        • 虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
    • Socket
      • socket通讯原理及例程(一看就懂)
      • Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
      • 其实还是基于TCP、UDP协议的,实现了三次握手、四次挥手等过程
      • 套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。
      • 在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据
      • socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现:即socket是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
      • 过程
        • 服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
  • Http
    • Http重定向的具体流程
      • 301 redirect: 301 代表永久性转移(Permanently Moved)
      • 302 redirect: 302 代表暂时性转移(Temporarily Moved )
        • 多用于登陆失效,要跳转到登陆页面
        • 比如项目中用302+location来判断是不是需要重新登陆了(对应的第二次200请求是浏览器自动发起的,js/angular捕获不到)
      • 尽量使用301跳转!301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。他们的不同在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。
      • 转发是服务器行为,重定向是客户端行为。
        • 转发过程
          • 客户浏览器发送http请求——》web服务器接受此请求——》调用内部的一个方法在容器内部完成请求处理和转发动作——》将目标资源发送给客户;
        • 重定向过程
          • 重定向,其实是两次request
          • 客户浏览器发送http请求——》web服务器接受后发送302状态码响应及对应新的location给客户浏览器——》客户浏览器发现是302响应,则自动再发送一个新的http请求(浏览器的自动行为),请求url是新的location地址——》服务器根据此请求寻找资源并发送给客户。
    • 一个完整的 HTTP 请求会涉及到哪些协议?
      • 域名解析
        • 浏览器搜索自己的DNS缓存,缓存中维护一张域名与IP地址的对应表;
        • 若没有,则搜索操作系统的DNS缓存;
        • 若没有,则操作系统将域名发送至本地域名服务器(递归查询方式),本地域名服务器查询自己的DNS缓存,查找成功则返回结果,否则,通过以下方式迭代查找:
        • 本地域名服务器向根域名服务器发起请求,根域名服务器返回com域的顶级域名服务器的地址;
        • 本地域名服务器向com域的顶级域名服务器发起请求,返回权限域名服务器地址;
        • 本地域名服务器向权限域名服务器发起请求,得到IP地址;
        • 本地域名服务器将得到的IP地址返回给操作系统,同时自己将IP地址缓存起来;
        • 操作系统将IP地址返回给浏览器,同时自己也将IP地址缓存起来;
        • 至此,浏览器已经得到了域名对应的IP地址。
      • 发起http请求
        • 传输层,选择传输协议,TCP或者UDP,TCP是可靠的传输控制协议,对HTTP请求进行封装,加入了端口号等信息。发起TCP的3次握手(底层在局域网还有arp等)建立TCP连接。
        • 网络层,通过IP协议将IP地址封装为IP数据报;然后此时会用到ARP协议,主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址,找到目的MAC地址;
        • 数据链路层,把网络层交下来的IP数据报添加首部和尾部,封装为MAC帧,现在根据目的mac开始建立TCP连接,三次握手,接收端在收到物理层上交的比特流后,根据首尾的标记,识别帧的开始和结束,将中间的数据部分上交给网络层,然后层层向上传递到应用层;
      • 服务器端响应http请求,浏览器得到html代码
      • 浏览器解析html代码,并请求html代码中的资源
      • 浏览器对页面进行渲染呈现给用户。
    • http的返回码/状态码
      • 200 OK
      • 201 Created
      • 302 Redirect (Temporarily Moved)
      • 304 Not Modified
      • 401 Forbidden
      • 404 Not Found
      • 500 Internal Server Error
      • 502 Bad Gateway
      • 504
    • http的header
      • HTTP Header消息头详解
      • Accept
      • Accept-Language
      • Accept-Encoding
      • Authorization
      • User-Agent
      • Host
      • Referer
      • Connection
      • Date
      • If-Match
        • 仅当客户端提供的实体与服务器上对应的实体相匹配时,才进行对应的操作。主要用于像 PUT 这样的方法中,仅当从用户上次更新某个资源后,该资源未被修改的情况下,才更新该资源。
      • If-Modified_Since
        • 允许在对应的资源未被修改的情况下返回304未修改
      • Cache-Control
      • Cookie
      • Content
      • Content-Type
      • Content-Length
      • Allow
      • Age
    • http请求方法, 哪些请求方法是幂等的
    • https加密原理
      • 为什么http证书不会被复制下来?浏览器里就能看到啊
        • 因为浏览器会检查域名是否和证书上的一致
        • 同时证书中除了明文部分,还有加密了的摘要,可以用CA公钥去解密,然后验证摘要
      • 过程
        • https基于tls,然后同时使用了对称和非对称加密,引入了CA
        • CA在判明申请者的身份之后,会对已申请的公开密钥做数字签名,然后将这个签名的公开密钥和放入公钥证书分配给服务器公司
        • 服务器会把这个由CA颁发的公钥证书以及公开密钥发送给客户端,以此来和客户端进行通信
        • 接收到证书的客户端可以使用CA的公开密钥对证书上的签名进行认证,一旦认证通过,客户端就可以知道认证服务器公开密钥的是真实有效的CA,并且服务器的公开密钥是值得信赖的
        • CA的公开密钥已经事先植入到浏览器中,客户端通过CA的公开密钥向CA认证服务器的公钥证书上的数字签名的真实性。
      • Https与http区别
        • 协议基础不同:HTTPS在HTTP的基础上加入了SSL层
        • 通讯方式不同:HTTPS在数据通讯之前需要客户端、服务器进行握手(身份验证),建立连接后,传输数据需要经过加密,通信端口为443
      • HTTPS是怎么解决HTTP协议的三大缺点的?
        • 防监听:采用对称加密对数据进行加密,采用非对称加密对对称加密的密钥进行加密
        • 防伪装:通信双方携带证书,证书有第三方颁发,很难伪造
        • 防篡改:采用摘要算法(MD5或是SHA-1),同样的数据由同样的摘要,而只要有一点不同的数据,它的摘要往往不同,只要数据做了篡改,就会被感知到
    • tls的加密流程
      • 密钥交换过程 (也可以称握手过程, 但握手这个词太泛太抽象)
        • TLS的安全性主要是由这个阶段决定的. 通过非对称加密或其它的密钥交换手段, 如DH, ECDH , 来交换对称加密密钥.
        • 这些算法都非常耗CPU, 肯定不能全程用这些算法来加密所有的数据.
      • 数据加密过程
        • 双方拿到密钥之后, 使用对称加密算法进行加解密, 因为对称加密运算快.
      • TLS加密协议完整流程
  • 安全
    • sql注入
      • 参数检查(类型、长度、合法字符等)
      • 使用参数化查询、存储过程等
    • xss
      • 前端安全系列(一):如何防止XSS攻击?
      • XSS 的本质是:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。
      • 用户故意提交到数据库的数据含有脚本,在显示到页面上时运行出现问题(可能实在管理员查看时,不一定就是这个客户)
      • 这个脚本有可能恶意上传别人的cookie信息(虽然可以使用httponly领js无法操作,但请求中会带上)或其他资料
      • 存储型 XSS
      • 反射型 XSS
      • DOM 型 XSS
      • 职责划分
        • 防范存储型和反射型 XSS 是后端 RD 的责任。而 DOM 型 XSS 攻击不发生在后端,是前端 RD 的责任。防范 XSS 是需要后端 RD 和前端 RD 共同参与的系统工程。
        • 转义应该在输出 HTML 时进行,而不是在提交用户输入时。
      • 措施
        • 输入检查,过滤特殊字符(有点难办,因为不确定什么时候要转义,有时一些字符就是正常的)
        • 预防存储型和反射型 XSS 攻击
          • 改成纯前端渲染,把代码和数据分隔开。(即不要在后端使用jsp等方式拼接HTML)
            • 过程
              • 浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据。
              • 然后浏览器执行 HTML 中的 JavaScript。
              • JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上。
            • 在纯前端渲染中,我们会明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。
            • 但纯前端渲染还需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,请参考下文”预防 DOM 型 XSS 攻击“部分)。
            • 在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,我们仍然要面对拼接 HTML 的问题。
          • 对 HTML 做充分转义。
            • HTML 的编码是十分复杂的,在不同的上下文里要使用相应的转义规则。
            • 不同的上下文,如 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等,所需要的转义规则不一致。
            • 业务 RD 需要选取合适的转义库,并针对不同的上下文调用不同的转义规则。
        • 预防 DOM 型 XSS 攻击
          • DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。
          • 在使用 .innerHTML、.outerHTML、document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等。
          • 如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患。
          • DOM 中的内联事件监听器,如 location、onclick、onerror、onload、onmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。
        • Content Security Policy
          • 禁止加载外域代码,防止复杂的攻击逻辑。
          • 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
          • 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
          • 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。
          • 合理使用上报可以及时发现 XSS,利于尽快修复问题。
        • 输入内容长度控制
        • HTTP-only Cookie:
          • 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。
        • 验证码
          • 防止脚本冒充用户提交危险操作。
    • csrf
      • 攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
      • 措施
        • 验证 HTTP Referer 字段
          • 这种方法的显而易见的好处就是简单易行,网站的普通开发人员不需要操心 CSRF 的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以。特别是对于当前现有的系统,不需要改变当前系统的任何已有代码和逻辑,没有风险,非常便捷。
          • 然而,这种方法并非万无一失。Referer 的值是由浏览器提供的,虽然 HTTP 协议上有明确的要求,但是每个浏览器对于 Referer 的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不安全。事实上,对于某些浏览器,比如 IE6 或 FF2,目前已经有一些方法可以篡改 Referer 值。如果 bank.example 网站支持 IE6 浏览器,黑客完全可以把用户浏览器的 Referer 值设为以 bank.example 域名开头的地址,这样就可以通过验证,从而进行 CSRF 攻击。
          • 即便是使用最新的浏览器,黑客无法篡改 Referer 值,这种方法仍然有问题。因为 Referer 值会记录下用户的访问来源,有些用户认为这样会侵犯到他们自己的隐私权,特别是有些组织担心 Referer 值会把组织内网中的某些信息泄露到外网中。因此,用户自己可以设置浏览器使其在发送请求时不再提供 Referer。当他们正常访问银行网站时,网站会因为请求没有 Referer 值而认为是 CSRF 攻击,拒绝合法用户的访问。
        • 在请求地址中添加 token 并验证
          • CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
          • 这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上 ,这样就把 token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token。
          • 该方法还有一个缺点是难以保证 token 本身的安全。特别是在一些论坛之类支持用户自己发表内容的网站,黑客可以在上面发布自己个人网站的地址。由于系统也会在这个地址后面加上 token,黑客可以在自己的网站上得到这个 token,并马上就可以发动 CSRF 攻击。为了避免这一点,系统可以在添加 token 的时候增加一个判断,如果这个链接是链到自己本站的,就在后面添加 token,如果是通向外网则不加。不过,即使这个 csrftoken 不以参数的形式附加在请求之中,黑客的网站也同样可以通过 Referer 来得到这个 token 值以发动 CSRF 攻击。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因。
        • 在 HTTP 头中自定义属性并验证
          • 这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。
          • 然而这种方法的局限性非常大。XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。
    • 对称性加密跟非对称性加密的比较,使用场景
      • 对称加密效率高,但密钥安全性低,常用于对内容加密。
      • 非对称加密效率低,但更安全,常用于对称密钥的交换过程中。需要生成公、私钥。
    • 加密算法RSA
      • RSA是一种非对称加密算法。
      • 使用RSA一般需要产生公钥和私钥,当采用公钥加密时,使用私钥解密(只有私钥拥有者可以解密,加密传输+防伪造);采用私钥加密时,使用公钥解密(只有私钥拥有者可以加密,加密传输+防否认)。
  • 操作系统
    • Linux的Ctrl c发生了什么,与Ctrl z区别,说到了信号、管道、进程通信的方式,进程调度。
    • linux 系统里,一个被打开的文件可以被另一个进程删除吗?
    • epoll和poll,select的区别
  • C++
    • C++ 的动态多态怎么实现的?
    • C++ 的构造函数可以是虚函数吗?
  • Python
    • python协程原理/缺点
    • python GIL, 为什么有GIL 还需要threading
    • python gevent协程调度原理/缺点
    • python的装饰器
  • 其他
    • Hbase 底层原理
    • dubbo的容错机制有哪些、负载均衡机制有哪些
    • redis和memcached

推荐阅读