bytedance面试题1
1. 请谈谈限流器的设计
【Question】
互联网秒杀场景下,很多接口的峰值流量非常非常陡峭,必须做好限流不然会把内部系统打挂。请设计一个分布式限流器,能实现这个功能。
1. 限流器能work,精确度达到 99% 以上
2. 限流阈值大小可设置,大的达到1000w级QPS,小的小到10QPS级别
3. 通用的限流器,不局限于某一业务领域
【Answer】
1. 利用redis的原子自增和过期淘汰策略
- 限流器的计数存放在redis中,用redis的过期淘汰策略实现限流器的计数的定期更新
- 例如针对 接口A 限流 10000 QPS。redis的key为:“接口A”,value为计数值 - 每次接口调用Redis用INC原子自增命令,自增1,并设置过期时间为1s
- 初次调用时,因为redis中该key没有,就直接设置为1,并设置过期时间为1s
- 在这一秒以内的后续调用,每次都自增1 - 客户端拿到自增后的值如果没有超过限制10000,就放行 - 如果超过 10000 限制,就不放行,说明超限了
- 细节实现:为避免超限后无谓的redis调用,第一次发现超限时可以记录该值的TTL时间,例如只过去100ms就有1w个请求过来,剩下的900ms就不用请求redis而是直接返回超限即可。不然这种情况会给redis带去额外无谓的流量,例如前面的例子,不做这个细节逻辑的话,redis的请求量是 10w QPS
- 精度可调节。假如限流阈值很大,比如100w,可以把INC自增步进/步长调整大一些,例如100,那么redis的QPS直接降低100倍,为1w QPS
2. 请设计短网址系统
【Question】
1. 分为两个接口
- 从一个长网址生成一个短网址。需要考虑:同一个长网址,多次创建的短网址是否相同
- 用户访问短网址时,需要能跳回到原始的长网址
2. 需要考虑跨机房部署问题
3. 加分项:考虑跨海域全球部署问题
4. 加分项:考虑统计某个域名下的URL/host访问uv和pv
【Answer】
1. 一开始需要能考虑系统承载容量,例如:
- 每天100亿访问量
- 每天生成1000w条短网址记录
2. 然后考虑短网址的生成算法,方案有很多种
- 最简单实现:自增id实现,这个不可逆,同一个长网址会生成多个短网址
- hash+序号冲突
- 使用kv存储双向对应关系,可逆,但存储用量比较大
3. 302跳转问题,附带可以讨论网址访问量计数问题
4. 加分项1:需要考虑跨机房部署问题
5. 加分项2:考虑跨海域全球部署问题
6. 加分项3:能给出合理的统计需求,例如用hadoop做MR
3. 数据库索引创建FAQ
【Question】
什么时候需要建立索引
什么时候不应该创建索引
试详细说明
【Answer】
## 什么时候需要建立索引 - 作为主键的列上,强制该列的唯一性和组织表中数据的排列结构。 - 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度。 - 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的。 - 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。 - 在经常使用在where子句中的列上面创建索引,加快条件的判断速度。 ## 什么时候不应该创建索引 - 在查询中很少使用或者作为参考的列不应该创建索引。 - 对于那些只有很少数据值的列也不应该增加索引(比如性别,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度)。 - 对于那些定义为text,image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。 - 当修改性能远远大于检索性能时,不应该创建索引,因为修改性能和检索性能是矛盾的。
4. 计数系统设计
【Question】 头条里有各种计数需求,我们希望能够有一个统一的计数服务来满足该需求,主要功能要求根据业务需求查询指定的文章的计数统计(播放数,阅读数,评论数等),要求:支持实时更新各种计数,支持高并发查询,需要给出系统存储设计方案,对外输出接口设计;
【Answer】
+ 方案一:直接使用数据库+cache的方案,方案简单有效,数据量大之后采用分库方案,扩展性有限,但开发和运维成本低,性能方面通过cache优化,存在热点数据(可能导致cache经常被清理); + 方案二:采用redis作为kv存储,查询效率足够高,但需要考虑资源使用问题,假设有10亿的文章,帖子和评论,如何保证以更低的成本满足该需求;主要问题需要较多资源,成本较高,如何设计更好数据结构;(3+) + 方案三:可以自己开发counter模块,需要考虑kv存储方案,value的设计,保证使用更少内存;还需要考虑的点:容灾备份机制;数据扩展问题;(3.5) + 方案四:可能业务方经常新增计数需求,需要考虑counter服务的列扩展性,故设计的数据结构需要考虑列扩展问题;(3.5) + 其他:业务写入QPS可能比较高,考虑写入压力问题,数据写入去重问题,即同一个用户转发之类操作需要幂等性(一定程度保证即可)
5. 请问Redis提供了哪几种持久化方式?
【Question】
Redis提供了哪几种持久化方式?
【Answer】
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
你也可以同时开启两种持久化方式, 在这种情况下, 当Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
6. 请尝试实现抖音直播消息服务设计
【Question】
背景:
消息服务是直播产品中一个非常核心的服务。直播间内的聊天、弹幕、礼物等消息,都是通过消息服务发送。
问题:
- 设计一个消息服务,支持直播间内的用户进行聊天、发弹幕等操作。
- 衡量一个消息服务的核心指标有哪些?
- 基于候选者的方案,如何监控和优化这些核心指标?
- (深入)拉模型的分布式方案
【Answer】
### 方案设计 主流的设计主要有两种:**推模型和拉模型**。(任何一种都是合适的设计) 推模型简述: 基于长连接,直播间内的观众发的消息,异步通过长连接发送给房间内的其他观众。候选人的架构图应该没有硬伤。 深入的问题(言之有理即可): 1. 长连接的架构(协议、鉴权、扩容、重连、到达率等) 2. 消息放大问题如何解决(比如一个有1万人的房间,任何一个人发的消息,都会产生1万个消息,相当于放大了1万倍) 消息聚合、消息多级推送(类似CDN的方式) 3. 直播间用户列表怎么存储和优化 推送消息时,需要房间内所有用户消息,对于观众比较多的房间,需要考虑数据分片、本地缓存等手段进行优化。 拉模型简述: 拉模型类型类似于一个消息队列的模型。每个房间会有一个消息列表,直播间内用户发的消息会放在相应的消息列表中。直播间内的观众通过前端轮询拉消息接口, 把消息拉到客户端展示。 深入的问题(言之有理即可): 1. 房间的消息如何存储(由于消息有时效性,所以只需要存储最近一段时间的数据) 2. 轮询方式如何优化 3. 拉接口如何优化(local cache等) ### 核心指标 消息每秒吞吐量、消息到达率、消息延时(像稳定性这种,属于通用的基本指标)。 ### 核心的优化方式(提供一些方式,其它的只要合理即可): 监控方式: 1. 吞吐量(类似于qps,打metrics等都可以) 2. 到达率:对于推模型,基本等价于长连接的到达率监控;对于拉模型,性价比较高的是只监控主播的(因为只有主播是全程在直播间的) 3. 延时:需要关注手机和服务端的时间不一致的问题 优化: 1. 吞吐量:批量发送、多级推送等 2. 到达率:一般推模型需要重点关注,主要是对于长连接的到达率优化,包含死连接检测等。 3. 延时:一般拉模型需要重点关注,对于每次都拉到消息的房间,减少轮询间隔和长连接拉模式等。 ### 拉模型的分布式方案 类似于一个分布式存储系统,解决水平和垂直扩展的问题。
7. 抖音活跃用户行为分析 (SQL)
【Question】
表A:用户浏览视频日志 user_behavior: date, user_id, video_id, start_time, end_time
表B:视频信息 video_info: video_id, video_duration
表C:用户信息 user_info: user_id, gender
问题:
(1)某一天(如 20200310),观看不同视频个数的前 5 名 user_id ;
(2)观看超过 50 个不同视频的女性用户中,完整观看率最高的 10 个 user_id。
【Answer】
select user_id, count(*) as video_cnt from ( select user_id, video_id from user_behavior where date = '20200310' group by user_id, video_id ) a group by user_id order by video_cnt desc limit 5;
select user_id, count(*) as video_cnt, sum(is_full) * 1.0/count(*) as full_ratio from ( select a.user_id, a.video_id, case when a.max_time >= c.duration then 1 else 0 end as is_full from ( select user_id, video_id, max(end_time -start_time) as max_time from user_behavior where date = '20200310' group by user_id, video_id ) a inner join ( select user_id, gender from user_info where gender = 'female' ) b on a.user_id = b.user_id left outer join video_info c on a.video_id = c.video_id ) a group by user_id having video_cnt >= 50 order by full_ratio desc limit 10
8. 请谈一谈 SQL中INNER、LEFT、RIGHT JOIN的区别
【Question】
SQL中INNER、LEFT、RIGHT JOIN的区别
inner join、left join、right join
【Answer】
A INNER JOIN B ON……:内联操作,将符合ON条件的A表和B表结果均搜索出来,然后合并为一个结果集。 A LEFT JOIN B ON……:左联操作,左联顾名思义是,将符合ON条件的B表结果搜索出来, 然后左联到A表上,然后将合并后的A表输出。 A RIGHT JOIN B ON……:右联操作,右联顾名思义是,将符合ON条件的A表结果搜索出来, 然后右联到B表上,然后将合并后的B表输出。
9. 秒杀系统设计
【Question】 秒杀系统设计 “米粉节”3000万人抢购,峰值QPS100万,请设计抢购系统的技术架构,保证可靠性并且不能超卖。
设计要点: 1 可以支撑QPS 100万,能分析系统的瓶颈 2 如何尽快把商品卖完,但不能超卖 3 考察系统扩展性、容灾能力
【Answer】
设计思路:限流+策略+反作弊,一般情况下候选人想不到完善的设计,我们主要看其设计能否满足前面的需求。 参考:http://www.tuicool.com/articles/e2YVRvA 1 可以满足业务需求,抗住压力,不会超卖 3分 2 能考虑到到各种情况下的灾备方案,系统易于对商品种类,抢购规模进行扩展,3.5分
10. 设计一个rate limiter
【Question】 设计一个限流系统,可以控制一个API或一个资源被访问的流量
【Answer】
方案1:按QPS控制访问流量,在redis中纪录每秒的访问次数,1秒过期 方案2:按时间窗口限制访问次数,保存一个访问队列,定期删除过期的访问,维护一个当前时间窗口内的总访问次数 方案3:Token Bucket思想,控制Bucket的总容量和留入令牌速率,考虑到burst rate问题 方案4:基于方案3还能考虑到平台话的动态配置,熔断策略等
11. 请你设计一个海量评论系统
【Question】 如何设计海量评论存储系统,支持快速大量的写入及任意翻页的需求。 主要是看候选人对“复杂”业务的分解,在实际存储时是否会通过类似二级索引的方式来优化翻页效率;在业务上冷热数据的不同缓存处理;是否会考虑使用缓冲区避免突发流量的写入;偶尔会关联问到全局唯一ID的设计方案。 如果获取评论列表的时候,支持按照一定热度对评论进行排序,又该如何进行设计;
【Answer】
1. 评论大量写入优化方案:id分配策略,写入量太多如何进行扩展(自己实现分片策略,或者采用分布式存储系统,如果选用分布式系统介绍选型方案和实现细节);(3分,如果分片策略或存储系统介绍比较好,理解深刻,可得3.5分) 2. 查询方面如果按照时间逆序,索引可以采用zset方式,但需要考虑单个文章评论索引太多问题,故可以采用自建索引或者利用redis等拆分索引大小;其次需要考虑评论被删除后,索引中大片数据无法使用情况,采用重建索引还是单独提供删除索引?(3,如果能够对数据量较好预估,根据数据量选择合适索引方案得3.5分) 3. 头条环境下对评论做了个性化排序,索引的机制如何进行调整;(加分项目,回答OK,得3.5分) 4. 容错机制考虑;(加分项目)
12. 谈谈分布式cache系统设计方法
【Question】 工业环境中对数据查询访问效率比较大,但数据量有比较大,单机cache无法满足业务需求,故需要实现一个分布式cache系统,满足业务查询效率和数据量要求,请设计这样一个系统。
【Answer】
1. 基础数据结构采用hash或者map的方式均可以,实现可得3分; 2. 考虑线程安全性,同时兼顾查询效率,是否考虑分桶等机制;(3+) 3. 分布式环境下:主要考察分片方案,如何做到使用者透明,怎么解决某一个节点故障问题,雪崩问题;(3.5)
13. 开放API设计
【Question】
题目:平台要对外提供一些开放API接口,设计应该考虑哪些?
开放性题目,可以不限制答案
【Answer】
参考回答要点
- 鉴权,可以追问怎么设计,如果提到公钥私钥,可以追问原理,oath2.0可以追问下怎么设计的
- 防攻击,可以追问怎么设计,考虑ip、cookie单位时间访问频次,怎么控制,有的候选人可能会提令牌桶,可以问问实现过程
- 访问次数限制,按照账号限制次数,分级
- 反爬取,封禁,假数据
- 参数签名,防篡改
- 追问如果有用户隐私信息怎么处理
- https、接口统一规范(错误码、格式)、数据库、cache等,也可以继续追问
14. 推送去重系统设计
【Question】 推送系统去重模块设计:头条有几亿的活跃设备数,运营日常推送时,会针对同一篇新闻进行多维度推送,比如先推送一次北京用户,再补推一次时政用户,再推一次全国用户。同一个用户可能会被匹配到多次,要如何避免用户收到重复的推送。
【Answer】
去重必定会有状态,所以该系统设计的核心在于“状态”数据的存储,特点就是数据量大,去重要求精确。 1. 常规的思路是用分布式KV存储(Redis、Memcache等),去重模块本身无状态。基本合格。3.0 2. 考虑本地Redis或Memcache方案,去重模块按用户分片。3.0+ 3. 能根据状态数据的特性,考虑自己实现存储,并且实现半持久化(程序重启数据不丢),内存+磁盘存储可满足需求,根据面试者存储数据的选择和持久方式的解答,可以3.0+ 或 3.5。 4. 基于共享内存(或类似的磁盘文件映射mmap等方式),并且能较好的给出基于块存储实现Hash的表,以及Hash表的增加和查询。因为数据量大,存储需要考虑很多优化方案,基于bit的操作等。3.5 5. 在4的基础上,考虑到数据数据清除(以天为单位清除)。考虑Android设备可以自己掌握弹窗的,在端上做保底去重。3.5+
15. 系统设计 - 好友共同关注问题
【Question】
现在来了一个新需求,希望在微博用户的个人页面加入共同关注的好友有哪些,在用户搜索结果页加入每一个结果中共同关注的好友数量,需求设计稿如下(下面只做大致介绍):
<p>
</p><p>
</p><p>
</p>
【Answer】
(上面两个需求看似可以用同样的解决方案,实际第一个需求很简单,第二个需求如果用读扩散的方式计算会非常耗时)
回答思路:对于第一个问题可以将关注关系用以下的key表示:uid1_uid2,存在这个key则表示uid1有关注uid2。有了这个模型后,可以使用KV存储关注关系对;
对于第二个问题,一次检索可以有几十个结果(假设为20个),如果当前用户有500个好友(关注的人),则至少需要进行20*500=1w次判断,如果用户有5000个好友,则为10w次判断,使用KV就达不到要求了,这样KV压力会非常大。可以使用布隆过滤器,然后将布隆过滤器放在机器内存中,单机存不下的可以对布隆过滤器做分片,这样在内存中判断,并且使用上线程池分发判断任务,速度就非常快了。通常一个人能关注的账号数量是有限的,正常来说是5000多个,粉丝数没有限制。
即便关注人数真的不设限,我们也可以认为关注数量高的账号很少,因此可以针对这批账号做特殊处理,细节思路就不展开了。
另外是,在微信的公众号与普通用户之间的关注关系对大概为400亿,微博的关注关系量级应该是和这个差不多的,所以布隆过滤器预计单机也能存下来(假设是千分之5的错误率,哈希次数为8的情况下)
16. 设计类似微博timeline功能实现,考虑怎么设计支持「热门」
【Question】 设计类似微博timeline功能,好友发表了最新的微博之后,能够在自己的feed流中按照时间顺序获取到最新的微博,也能通过加载更多功能获取之前历史中的微博数据。 如果考虑支持按照热度对好友微博进行排序展现,热度主要根据用户阅读,评论和点赞数据评估,该系统该如何扩展。
【Answer】
1. 写入系统:设计写入服务,帖子id分配策略(采用全局分配器),如何解决qps突增等情况,写入qps较高时,通过分片等方式提升写入效率,当然目前也可以采用写入效率较高的分布式存储系统;(3) 2. 索引构建:由于要支持timeline或者其他的排序方式,故需要单独的排序策略,这里单独针对timeline排序详细说明,timeline按照发表者时间顺序,一般有3中处理方案:1)拉的方式,按照发布者建立时间索引,需要考虑关注人数较多时,获取索引效率问题;2)推模式:发布的时通过异步消息队列,将消息投递到各个阅读者,此处主要问题推的效率和存储空间的开销;3)混合模式:粉丝较多用户采用拉模式,在线用户直接投递等提高查询效率;(3+~3.5) 3. 2中需要考虑索引数据结构设计,排序cursor值选取问题;(3.5)(细节考察) 4. 如果是热点排序:全局热点比较简单,可以直接维护一个热度计算模块,接入热度事件计算流,如果事件较多,则可以使用分布式实时计算框架提高计算效率,对于热度超过一定阈值的进入到热度排序系统中,按照从高到底排序即可(热度排序系统按照一定量截断多余数据);如果热度支持不同时间段分别组织;其次如果按照话题拆分呢?又如何设置热度排序系统?(扩展,如果回答细节都ok,可以得4分)
17. 数据库 - 统计数据表中每个小时记录数
【Question】
统计数据表中每个小时记录数
例如:数据表
id p_date
1 2017/08/29 00:10:10
2 2017/08/29 01:10:10
3 2017/08/29 01:10:10
4 2017/08/29 02:10:10
5 2017/08/29 01:10:10
……
【Answer】
``` SELECT DATE_FORMAT(p_date, "%Y-%m-%d %H" ), count(*) FROM video.cdn_quality_log group by DATE_FORMAT(p_date, "%Y-%m-%d %H" ) ; ```
18. 请问Redis相比memcached有哪些优势?
【Question】
Redis相比memcached有哪些优势?
【Answer】
1.memcached所有的值均是简单的字符串,Redis作为其替代者,支持更为丰富的数据类型
2.Redis的速度比memcached快很多
3.Redis可以持久化其数据
4.Redis支持数据的备份,即master-slave模式的数据备份。
19. 12306系统售票系统设计
【Question】 设计12306系统的售票模型,实现查询余票和购票的功能。在春运中采用什么样的架构支撑。
【Answer】
业务模型: 有多种解法,每种有各自优缺点: 1 记录任意两个车站的余票数量,需要维护Cn2共105个车站对的余票信息。该方案查询效率非常高,更新时有放大效应,需要更新多个车站对的余票信息。 2 记录每一张席位的售卖记录,查询时遍历每个席位,算出余票信息。该方案更新效率较高,查询时需要遍历。 3 将全程分解为原子区间,记录每个区间的可用车票数目。购买时只要起始站和终点站之间的原子区间有票,那可以出一张票。该方案查询和更新效率都很高,但不能保证买到的是同一个席位。更适合用来模糊查询。 在不考虑性能的情况下,方案可以满足查询和购买的基本需求,3分 性能上满足要求,并给出准确的分析,3.5分 架构: 1 技术选型可以实现需求,对技术栈性能和特性都比较清楚,3分 2 能够对系统瓶颈做出分析,设计比较系统的容灾方案,3.5分
20. 某游戏(王者荣耀)战力排行榜设计
【Question】
功能:
显示某区服所有玩家的战力排行情况,以及我自己的战力排行情况。
<p>
</p>
【Answer】
0) 优秀的候选人会明确,用户量级(1W,1000W,还是1亿),战力力度(100分、1w分还是更细)。
1) 算法层面,桶排序、跳表等
2) 多种实现方法,数据存储、更新的机制,分布式相关,Redis/MySQL
3) 用户体验相关(排行榜更新频率、自己排名的准确性、得分相同的时候如何排名、有人频繁操作装备导致排行榜变动太快),看决策思路,是否需要全服排序等。
4) 追问:如果排名100W之后的玩家想看自己前后的数据,如何设计API等。
21. cache设计
【Question】 设计一个对象cache, 他支持下列两个基本操作: set(id, object), 根据id设置对象; get(id): 根据id得到一个对象; 同时它有下面几个性质: 1: x秒自动过期, 如果cache内的对象, x秒内没有被get或者set过, 则会自动过期; 2: 对象数限制, 该cache可以设置一个n, 表示cache最多能存储的对象数; 3: LRU置换, 当进行set操作时, 如果此时cache内对象数已经到达了n个, 则cache自动将最久未被使用过的那个对象剔除, 腾出空间放置新对象; 请你设计这样一个cache;
【Answer】
内部维护一个链表, list, 其元素为一个三元组(ID, timestamp, obj), 分别为对象ID, 上次被访问时间, 和对象内容; 在维护该list时, 需要保持一个性质, 越靠后的元素越新, 既timestamp越大; 内部再维护一个map, 该map表示一个ID到list节点的索引, 格式为map(ID, node); 对于get(id)操作: 1: 先在map中查找ID对应的list node; 2: 将node从list中取出, 即list.Remove(node); 3: 检查node.timestamp, 如果过期, 则返回null, 表示无数据, 并将ID从map中删除; 4: 如果未过期, 设置node.timestamp = now(), 并将node添加到list尾部, 即list.Append(node); 5: 返回node.obj; 对于set(id, obj)操作: 1: 同get(id)的1~3步操作, 删除对应的ID; 2: 如果此时空间满了, 既对象数为n, 则将list中表头的那个元素删除; 3: 更新list和map: node = new(ID, now(), obj), list.Append(node), map[ID] = node;
22. 关系系统设计
【Question】 今日头条为满足用户阅读或关注感兴趣账号内容,支持用户关注功能,需要实现一个关系系统,该系统主要提供功能:
- 获取用户的关注和粉丝列表;
- 获取用户关注和粉丝计数;
- 给定多个用户,判断用户是否关注,是否为指定用户的粉丝,是否为双向关系;
数据量预估:
- 总关系数据量:50亿;写入QPS:1000qps;各类接口查询QPS:70w qps,其中check relation占50w;
设计问题:
-
业务考察点:
1)给系统接口设计,及相应业务实现逻辑和存储数据结构设计方案; 2)如何解决粉丝数较多时,获取用户粉丝列表查询效率; 3)check relation如何保证数据准确性,特别是粉丝或关注数较多用户;
-
工程考察点:
1)在高QPS和存储量下,之前设计的数据结构和业务会存在什么问题?(开放性,针对候选人设计提问) 2)根据给定数据量和业务请求QPS,评估系统存储和计算资源需求; 3)如何保证基础系统高可用性,需要给出不同场景下依赖故障的高可用解决方案(比如依赖存储故障,单机房故障等)
【Answer】
1. 接口设计:给出满足需求的接口设计(3分); 2. 业务逻辑:支持给出4中功能的系统实现方法 (3分); 3. 存储扩展性:数据库分库分表,或者使用kv系统单独提供均可,如果在kv系统中能够想到value的优化方案可适当加分;(3) 4. 优化: 关注数较多情况下,如何提高查询效率;(3分)
23. Redis过期策略和内存淘汰策略
【Question】
【Answer】
过期策略(redis key过期时间)1、定时过期,每个key都创建一个定时器,到期清除,内存友好,cpu不友好2、惰性过期,使用时才判断是否过期,内存不友好,cpu友好3、定期过期,隔一段时间扫描一部分key,并清除已经过期的key内存淘汰策略Redis缓存内存不足,如何处理新写入的数据1、直接报错2、lru,最近最少使用key3、随机4、过期lru,设置了过期时间里的keys lru5、过期随机6、当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
24. 分布式定时任务管理系统
【Question】 设计一个分布式定时任务管理系统,按照业务需求,解决任务管理和资源分配问题
【Answer】
@谢蒙 补充 1. 任务提交管理(3分) 2. 定时器; 3. 分布式的任务调度; 4. 失败容错机制;
25. 多人协同文档应用项目
【Question】 设计一个多人协作(可同时修改)的文档应用,尽量满足以下需求: 1.多个人可以同时编辑同一份在线文档,尽量做到一个人编辑时不会影响其他人的编辑; 2.文档的修改应尽可能实时地反映在编辑者的界面上,例如A增加了一行,那么B应该能在较短时间内看到这个变更; 3.尽可能减少客户端与后端的数据传输量,并尽可能提高后端处理时的响应速度; 4.编辑的内容应可能做到不丢失,即自动保存功能
请大致说说: 如果让你设计,那么后端架构、存储是怎么样的?客户端的产品逻辑是怎么样的(什么时候保存,有编辑时往服务端发送什么内容,如何获取到对方的修改内容等)
【Answer】
3 分: 1. 能够想到类似 diff 工具,出现同一行冲突时由用户来解决,这样能避免自动合并有可能出错等情况。 2. 采用长连接方式传递修改的 diff, 服务端转换之后传递 diff 到客户端。 3. 针对统一文档的的所有用户 hash 到同一台机器。 3.5 分: 1. 能够采用 Myer’s diff / Operational Transformation 等算法。 2. 传递 diff, 如果有多机处理统一文档的需求,需要保证操作的一致性。
26. 给定sql,索引应该如何建立
【Question】
【Answer】
主要考察联合索引、最左匹配几个知识点。1)最左原则指的就是如果你的 SQL 语句中用到了联合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个联合索引去进行匹配,值得注意的是,当遇到范围查询(>、<、between、like)就会停止匹配。2)Mysql有优化器会自动调整a,b的顺序与索引顺序一致,比如对(a,b)字段建立联合索引,查询条件是b=xx and a=xx,优化器会自动调整查询顺序以匹配索引对(b,a)建立索引。如果你建立的是(a,b)索引,那么只有a字段能用得上索引,毕竟最左匹配原则遇到范围查询就停止匹配。如果对(b,a)建立索引那么两个字段都能用上,优化器会帮我们调整where后a,b的顺序,让我们用上索引。
27. 小于n的最大数
【Question】 给定一个数 n,如 23121;给定一组数字 A 如 {2,4,9},求由 A 中元素组成的、小于 n 的最大数,如小于 23121 的最大数为 22999。
【Answer】
大体思路是,从最高位向最低位构造目标数,用 A 中尽量大的元素(但要小于等于 n 的相应位数字)。一旦目标数中有一位数字小于 n 相应位的数字,剩余低位可用 A 中最大元素填充。 注意: 1. 可能构造出等于 n 的数,需判断后重新构造; 2. 若 A 中没有小于等于 n 最高位数字的元素,则可直接用 A 中最大元素填充低位; 3. 有无解情形。 ## 一些边界情况: ``` n A 结果 23333 {2,3} 3333 23333 {3} 3333 22222 {2} 2222 22222 {2,3} 3333 2 {2} 无解 ```
28. 区间重复值检测
【Question】
在缓存系统中,想要知道过去一段时间被访问次数最多的key。
比如最近5分钟最热的key。
key的数量可能非常大。
【Answer】
基本上,这是一个排序问题,并且通过滑动窗口对key的TTL进行控制。
第一步:
比如想要知道5分钟内访问次数最多的key,可以对每个key进行计数(+1)和排序,然后在TTL5分钟之后计数(-1)进行排序,就可以得到5分钟内的精确的计数。
最快的实现方式:
用redis的zset
第二步:
因为缓存系统,qps非常高(假设100kqps),想要知道过去1个小时的热key,要怎么做。
考察点:
- qps高,需要对zset进行分片,
- 如果用1小时的滑动窗口,ttl需要保存非常大的key列表。对滑动窗口进行切割,只保留topk
其他:
有一种计数方式是 logarithmic access frequency counter 。在缓存中常见,比如redis。在LFU算法中使用。如果结合LFU,然后对TopK的key进行qps统计,也可以。
29. MySQL分库分表环境下全局ID生成方案/学校学生数据库ID设置
【Question】
在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作。在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象。但是当我们对数据库进行了分库分表后,就不能依赖于每个表的自增ID来全局唯一标识这些数据了。因此,我们需要提供一个全局唯一的ID号生成策略来支持分库分表的环境。
针对校招的同学,可以问如何设计学校中学生系统学籍的ID,如果是全国系统如何设计ID
【Answer】
1、数据库自增ID——来自Flicker的解决方案
因为MySQL本身支持auto_increment操作,很自然地,我们会想到借助这个特性来实现这个功能。Flicker在解决全局ID生成方案里就采用了MySQL自增长ID的机制(auto_increment + replace into + MyISAM)。
先创建单独的数据库(eg:ticket),然后创建一个表:
CREATE TABLE Tickets64 (
id bigint(20) unsigned NOT NULL auto_increment,
stub char(1) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY stub (stub)
) ENGINE=MyISAM
当我们插入记录后,执行
SELECT * from Tickets64
,查询结果就是这样的:
+-------------------+------+
| id | stub |
+-------------------+------+
| 72157623227190423 | a |
+-------------------+------+
在我们的应用端需要做下面这两个操作,在一个事务会话里提交
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
这样我们就能拿到不断增长且不重复的ID了。
从高可用角度考虑,接下来就要解决单点故障问题:Flicker启用了两台数据库服务器来生成ID,通过区分auto_increment的起始值和步长来生成奇偶数的ID。
最后,在客户端只需要通过轮询方式取ID就可以了。
- 优点:充分借助数据库的自增ID机制,提供高可靠性,生成的ID有序。
- 缺点:占用两个独立的MySQL实例,有些浪费资源,成本较高。
2、独立的应用程序——来自Twitter的解决方案
Twitter在把存储系统从MySQL迁移到Cassandra的过程中由于Cassandra没有顺序ID生成机制,于是自己开发了一套全局唯一ID生成服务:Snowflake。GitHub地址:https://github.com/twitter/snowflake。根据twitter的业务需求,snowflake系统生成64位的ID。由3部分组成:
41位的时间序列(精确到毫秒,41位的长度可以使用69年)
10位的机器标识(10位的长度最多支持部署1024个节点)
12位的计数顺序号(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
最高位是符号位,始终为0。
- 优点:高性能,低延迟;独立的应用;按时间有序。
- 缺点:需要独立的开发和部署。
如果是学校学生ID:
1、入学年份+专业代码+入学考试分数排名
全国系统:
1、使用学生身份证作为id
2、入学年份+学校代码+专业/班级代码+输入分数排名
或其他方法,只要能叙述清楚这样设计的原因,重点是是否有思路
30. 设计微头条
【Question】 设计一个简化版的微头条系统,包括如下接口:
post(user_id, message_id) # 编号user_id的用户发布1条编号为message_id的消息
get(user_id) # 编号user_id的用户刷新一次,拉去最近8条新feed
follow(user1, user2) # user1关注user2
unfollow(user1, user2) # user1取消关注user2
【Answer】
采用hash表存储user关注列表和user发布的消息列表 因为要去最近8条新消息,所以每条消息要带上时间戳 再get方法时候,拿到该用户所有关注者的message后根据时间戳做堆排序 ``` python class WeiTouTiao(object): def __init__(self): self.subscribe = collections.defaultdict(set) self.message = collections.defaultdict(list) self.time = -1 def post(self, user_id, message_id): self.message[user_id].append((self.time, message_id)) self.time -= 1 def get(self, user_id): tmp = copy.copy(self.message[user_id]) for user in self.subscribe[user_id]: tmp.extend(copy.copy(self.message[user_id])) heapq.heapify(tmp) res = [] while len(res) < 8 and tmp: res.append(heapq.heappop(tmp)[1]) return res def follow(self, user1, user2): if user1 != user2: self.subscribe[user1].add(user2) def unfollow(self, user1, user2): if user1 in self.subscribe and user2 in self.subscribe[user1]: self.subscribe[user1].remove(user2) ```
31. 文件分发系统设计
【Question】
用户不断把文件上传到服务器上 需要设计一个文件分发系统,把这些文件分发到服务器集群上(规模100-10000级别) 要求:速度更快,可靠性更高
【Answer】
出题的本意是各种水平的人都能答,只要用过ftp或者迅雷的人就能有sense。即使应届生也能有思路。 1 最基本解法,需要考虑分级分发,且有基本的容错性考虑(重试,校验之类的,针对网络故障服务器宕机等常见场景的策略) 2 可考虑p2p的方式,进一步的可通过优化p2p分发策略来提升效率 3 考虑系统单点消除,横向扩展,可维护性提升,监控,追查问题效率等要素 4 开放式提问:跨机房怎么办,文件怎么存储,高性能单机服务器的设计,文件传输协议设计
32. fork与stdio缓冲区问题
【Question】
如下代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
printf("line-1\n");
write(STDOUT_FILENO, "line-2\n", 7);
if(fork() == -1){
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
return 0;
}
执行结果如下:
yinhongbo@n8-125-036:~$ ./test
line-1
line-2
yinhongbo@n8-125-036:~$ ./test > test.stdout; cat test.stdout
line-2
line-1
line-1
问题:
1 有重定向和没有重定向下line-1和line-2的输出顺序为什么不同?
2 在有重定向情况下line-1为什么输出了2次
3 有几种方法能避免重复输出,列举几种并说明原因。
【Answer】
1 printf是在用户空间内存维护的缓冲区,缺省输出到终端时是行级缓冲,当重定向到文件时缺省为块级缓冲,printf在重定向后缓冲还在用户空间,write是在内核空间内存维护的缓冲区。因此在有重定向时会write会先于printf输出。
2 原因同上,printf是用户空间的内存中,fork是会复制,当exit时父子进程的各自的缓冲区都有printf的的内容,因此printf的内容会输出2次。
3 有比较多的方法。列举三种:
a) setbuf(stdout, NULL) 关闭stdout的流缓冲功能,但顺序write会先于printf
b) fork前调用fflush(stdout)
c) 子进程使用_exit(status)退出,以便不刷新stdio缓冲区
33. 请问流式Join系统应该怎么设计?
【Question】 在头条的业务场景中,将多条实时数据流拼接成一条流是一个很常见的需求,拼接后的流会作为统计和推荐模型训练的输入数据等。 现在给定Impression和Action这两条数据流,即给用户的推荐内容和用户的反馈,分别抽象成数据结构: Impression: uid, gid, timestamp, context Action: uid, gid, timestamp, staytime 按uid+gid来进行join,并要求维护一个1小时的时间窗口,对一个新出现的uid+gid,最多攒1小时的数据。要求输出: JoinedAction: uid, gid, timestamp, context, sum(staytime)
【Answer】
1. 可以引入某个流式处理框架,如Storm/SparkStreaming/Flink等,利用框架提供的join功能实现基本需求;或者自行设计分布式实现,能说清楚hash和join策略; 3分 2. 在头条的数据规模下(按消息qps100w,单条1k,即每秒1GB输入),能对占用的资源做出预估,需要的cpu/内存/外部存储等;3+ 3. 能考虑到上游数据的消息到达速度不一致,能处理Impression来的更晚的情况;3.5 4. 能考虑到数据一致性,如何在流式job部分或整体挂掉的情况下,保证数据正确性;3.5
34. 事务隔离级别(4种)
【Question】
【Answer】
1、Serializable(串行化):一个事务在执行过程中完全看不到其他事物对数据库所做的更新(事务执行的时候不允许别的事务并发执行,事务只能一个接着一个地执行,而不能并发执行)。 2、Repeatable Read(可重复读):一个事务在执行过程中可以看到其它事务已经提交的新插入的记录,但是不能看到其它事务对已有记录的更新。 3、Read Commited(读已提交数据):一个事务在执行过程中可以看到其它事务已经提交的新插入的记录,而且能看到其它事务已经提交的对已有记录的更新。 4、Read Uncommitted(读未提交数据):一个事务在执行过程中可以看到其它事务没有提交的新插入的记录的更新,而且能看其它事务没有提交到对已有记录的更新。
35. Redis如何实现分布式锁
【Question】
【Answer】
需要考虑过期时间、value的设置、超时锁被释放导致的数据更新覆盖的相关问题。
36. 从输入网址到获得页面的过程 (越详细越好)?
【Question】
【Answer】
浏览器查询 DNS,获取域名对应的IP地址:具体过程包括浏览器搜索自身的DNS缓存、搜索操作系统的DNS缓存、读取本地的Host文件和向本地DNS服务器进行查询等。对于向本地DNS服务器进行查询,如果要查询的域名包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析(此解析具有权威性);如果要查询的域名不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析(此解析不具有权威性)。如果本地域名服务器并未缓存该网址映射关系,那么将根据其设置发起递归查询或者迭代查询; 浏览器获得域名对应的IP地址以后,浏览器向服务器请求建立链接,发起三次握手; TCP/IP链接建立起来后,浏览器向服务器发送HTTP请求; 服务器接收到这个请求,并根据路径参数映射到特定的请求处理器进行处理,并将处理结果及相应的视图返回给浏览器; 浏览器解析并渲染视图,若遇到对js文件、css文件及图片等静态资源的引用,则重复上述步骤并向服务器请求这些资源; 浏览器根据其请求到的资源、数据渲染页面,最终向用户呈现一个完整的页面。
37. 多线程循环打印123
【Question】
启动3个线程,线程1无限循环打印1、线程2无限循环打印2、线程3无限循环打印3,要求按123123…顺序循环打印
【Answer】
方法一:利用ReentrantLock的多Condition实现线程间通信
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class NumberPrint1 { public static void main(String[] args) throws Exception { ReentrantLock lock = new ReentrantLock(); Condition c1 = lock.newCondition(); Condition c2 = lock.newCondition(); Condition c3 = lock.newCondition(); new Thread(new PrintTask(lock, c1, c2, ";1";)).start(); new Thread(new PrintTask(lock, c2, c3, ";2";)).start(); new Thread(new PrintTask(lock, c3, c1, ";3";)).start(); try { lock.lock(); c1.signal(); } finally { lock.unlock(); } Thread.sleep(Integer.MAX_VALUE); } static class PrintTask implements Runnable { private ReentrantLock lock; private Condition condition; private Condition conditionNext; private String str; public PrintTask(ReentrantLock lock, Condition condition, Condition conditionNext, String str) { this.lock = lock; this.condition = condition; this.conditionNext = conditionNext; this.str = str; } @Override public void run() { while(true) { try { lock.lock(); condition.await(); System.out.println(str); conditionNext.signal(); } catch (Exception e) { e.printStackTrace(); } } } } }
方法二:利用volatile关键字实现线程间通信
public class NumberPrint2 { static volatile int flag = 1; public static void main(String[] args) throws Exception { new Thread(new PrintTask(";1";, 1, 2)).start(); new Thread(new PrintTask(";2";, 2, 3)).start(); new Thread(new PrintTask(";3";, 3, 1)).start(); Thread.sleep(Integer.MAX_VALUE); } static class PrintTask
implements Runnable { private String str; private int curr; private int next; public PrintTask(String str, int curr, int next){ this.str = str; this.curr = curr; this.next = next; } @Override public void run() { while(true){ if(flag == curr){ System.out.println(str); flag = next; } } } } }</code></pre>
方法三:利用锁的wait和notify机制实现线程间通信
public class NumberPrint3 { static Object o1 = new Object(); static Object o2 = new Object(); static Object o3 = new Object(); public static void main(String[] args) throws Exception { new Thread(new PrintTask(";1";, o1, o2)).start(); new Thread(new PrintTask(";2";, o2, o3)).start(); new Thread(new PrintTask(";3";, o3, o1)).start(); synchronized (o1) { o1.notify(); } Thread.sleep(Integer.MAX_VALUE); } static class PrintTask implements Runnable { private Object curr; private Object next; private String str; public PrintTask(String str, Object curr, Object next){ this.str = str; this.curr = curr; this.next = next; } @Override public void run() { while(true){ synchronized (curr) { try { curr.wait(); System.out.println(str); synchronized (next) { next.notify(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
方法四:利用cas机制实现线程间通信
import java.util.concurrent.atomic.AtomicInteger; public class NumberPrint4 { static AtomicInteger flag = new AtomicInteger(1); public static void main(String[] args) throws Exception { new Thread(new PrintTask(";1";, 1, 2)).start(); new Thread(new PrintTask(";2";, 2, 3)).start(); new Thread(new PrintTask(";3";, 3, 1)).start(); Thread.sleep(Integer.MAX_VALUE); } static class PrintTask<t> implements Runnable { private String str; private int curr; private int next; public PrintTask(String str, int curr, int next){ this.str = str; this.curr = curr; this.next = next; } @Override public void run() { while(true){ if(flag.get() == curr){ System.out.println(str); flag.set(next); } } } } }
</pre> </details> --- ### 38. 乐观锁与悲观锁的区别 【Question】
--- ### 39. 客户端配置系统设计 【Question】 头条app客户端做了比较多feature,为了对这些功能做评估或者动态控制,需要支持云控ab测试方案,故需要设计一个在线动态配置管理系统设计,该系统要求能比较便捷进行参数动态修改和ab测试,请设计该系统满足该需求,需要考虑动态配置项比较多和易用性。【Answer】
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。[1] 悲观锁假定其他用户企图访问或者改变你正在访问、更改的对象的概率是很高的,因此在悲观锁的环境中,在你开始改变此对象之前就将该对象锁住,并且直到你提交了所作的更改之后才释放锁。悲观的缺陷是不论是页锁还是行锁,加锁的时间可能会很长,这样可能会长时间的限制其他用户的访问,也就是说悲观锁的并发访问性不好。 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。[1] 乐观锁不能解决脏读的问题。 乐观锁则认为其他用户企图改变你正在更改的对象的概率是很小的,因此乐观锁直到你准备提交所作的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁可以用较大的锁粒度获得较好的并发访问性能。但是如果第二个用户恰好在第一个用户提交更改之前读取了该对象,那么当他完成了自己的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不重新读取该对象并作出更改。这说明在乐观锁环境中,会增加并发用户读取对象的次数。 从数据库厂商的角度看,使用乐观的页锁是比较好的,尤其在影响很多行的批量操作中可以放比较少的锁,从而降低对资源的需求提高数据库的性能。再考虑聚集索引。在数据库中记录是按照聚集索引的物理顺序存放的。如果使用页锁,当两个用户同时访问更改位于同一数据页上的相邻两行时,其中一个用户必须等待另一个用户释放锁,这会明显地降低系统的性能。interbase和大多数关系数据库一样,采用的是乐观锁,而且读锁是共享的,写锁是排他的。可以在一个读锁上再放置读锁,但不能再放置写锁;你不能在写锁上再放置任何锁。锁是目前解决多用户并发访问的有效手段。 [乐观锁与悲观锁的区别](http://www.cnblogs.com/Bob-FD/p/3352216.html)--- ### 40. 推进用户历史系统设计 【Question】 推荐系统架构中经常需要保存用户历史,用于数据去重和回放功能,请设计这样一个系统支持快速查询用户一段时间内历史数据,要求支持高并发查询和写入量,其次考虑数据量扩展问题。 主要考察候选人对存储选型,业务数据模型设计的灵活性;【Answer】
1、能清楚的画出整个架构图,基于MySQL做存储,有可视化操作后台直接操作MySQL,API层解析MySQL数据下发数据。3.0 2、API层请求量会很大,考虑到请求MySQL时需要做缓存并给合理的缓存方案(本地缓存即可)。3.0+ 3、配置需求各式各样,如何抽象配置是该系统的关键。和Lua或Python等脚本语言来描述配置比较合适。3.5--- ### 41. 并发、并行、异步的区别? 【Question】【Answer】
1. 历史数据结构设计,可直接利用分布式kv存储系统,按时间对用户的历史分别存储;(3分) 2. 由于历史数据量比较大,可以对历史value可以提前序列化并压缩,牺牲部分cpu降低对存储的需求;(3.5分)--- ### 42. 在分布式环境下,如何防止RocketMQ消息重复消费? 【Question】【Answer】
并发:在一个时间段中同时有多个程序在运行,但其实任一时刻,只有一个程序在CPU上运行,宏观上的并发是通过不断的切换实现的; 多线程:并发运行的一段代码。是实现异步的手段 并行(和串行相比):在多CPU系统中,多个程序无论宏观还是微观上都是同时执行的 异步(和同步相比):同步是顺序执行,异步是在等待某个资源的时候继续做自己的事--- ### 43. 爬虫url去重 【Question】 用爬虫抓取网页时, 一个较为重要的问题, 就是对爬去的网页去重; 请你详细的设计一种数据结构, 用来检验某个URL之前是否已经被爬取过; 并给出每次检验的复杂度, 以及整体的空间复杂度;【Answer】
消费方可以基于分布式锁来解决rocketmq的消息幂等性的问题。用分布式锁可以从纯技术角度messageid,或者业务角度业务主键唯一性都可以实现rocketmq消息消费的幂等性。另外,rocketmq生产方发送消息,本身就保证了消息的幂等性,主要是消费方消费消息要保证幂等性。--- ### 44. 使用B树和B+树的比较 【Question】【Answer】
一般应该会说hash表, 要求他说出hash表详细的设计方案, 比如hash函数怎么设计, 存储数据用什么数据结构; 时间复杂度应该是, O(len(URL)) + O(insert); 前部分表示hash函数的复杂度, 后部分表示将hash值插入到表的复杂度; 后部分通常来说应该是O(1); 如果底层存储用平衡树之类的结构比较坏的可能会到O(logn); 空间复杂度应该是O(n*len(URL)); 更好一点的方法为用字典树, 每次拿到URL都将其插入到该字典树中, 时间复杂度应该是O(len(URL)); 空间复杂度应该小于O(n*len(URL)); 当面对海量数据时, 上述两种方法可能会空间瓶颈; 解决空间复杂度的一个办法是用布隆过滤器;--- ### 45. 实现一个RPC 调度机制 【Question】【Answer】
InnoDB的索引使用的是B+树实现,B+树对比B树的好处: IO次数少:B+树的中间结点只存放索引,数据都存在叶结点中,因此中间结点可以存更多的数据,让索引树更加矮胖; 范围查询效率更高:B树需要中序遍历整个树,只B+树需要遍历叶结点中的链表; 查询效率更加稳定:每次查询都需要从根结点到叶结点,路径长度相同,所以每次查询的效率都差不多假设在一个系统中有A模块和B模块,A模块 需要调用 B模块,A模块会部署在多台机器上,记为A1、A2、A3...,B 模块也会部署在多台机器上,记为 B1、B2、B3...,请设计一个调度的机制,需要满足一下几个需求,
1,A 模块服务启动后,能自动获取并感知部署了B 模块的所有可用机器
2,A 模块请求 B模块的时候,如果B 模块返回了失败,能进行重试,请设计重试机制
3,部署B 模块的机器如果出现异常或者B模块服务出现异常,A模块的机器可自动感知并将异常机器摘除不再请求
--- ### 46. 爬虫种子调度频度设计 【Question】 爬虫一般通过种子页面发现新链接,不断访问种子页,就能发现新的链接。 但不是每次访问种子页,都会提取到新链接,有可能短时间内种子页面并不会变化。 频繁抓取种子页面,即造成资源浪费,又对被抓取站点造成压力,甚至引起封禁。 那么如何设定种子调度频度,才能既不被封,又能及时拿到新内容呢?【Answer】
服务发现 + A模块本地维护黑白名单,失败后拉近黑名单,如果能考虑到探活更好
--- ### 47. 什么是僵尸进程? 【Question】【Answer】
根据具体业务场景、规模大小,可能存在多个不同阶段: 一、人工设定调度间隔,根据人的观察和经验去设定,这个方法在量少的时候还比较容易做 1. 如果是大量抓取,是不现实的,而且人工标也无法考虑是否合适 2. 另外任意的调度间隔,基本只能靠不断轮训扫表实现调度 二、 设定档位,人工标注档位 从1分钟、2分钟、3分钟……1周设定若干档位,将调度周期标准化后,利于整体调度 标注人员也更好统一标准,且利于统计 三、程序自动调整调度周期 建立评估指标,通过程序统计计算来推定调度周期。 指标可以有: 1. 新内容页数/种子抓取次数:该比值越高,种子调度ROI越过,用少量的种子调度次数抓到了更多的内容页 2. 新页发现delay时间:假如所有种子都1天调度一次,肯定指标1会不错,但这样新内容无法尽快被抓取,所以要考虑新页面出现到被抓取到的delay时间 通过上述指标,和历史每次抓取log来分析,做种子调度档位调整。 四、一般爬虫的种子调度周期是固定,但其实当地时间的夜间,一般不会有太多新内容,是可以有区别与白天的调度周期的。 更进一步,一般互联网内容,周期性比较明显,可以做周级别的调度频度分析控制。 延伸问题: Q:如何避免大量种子? A:同一调度周期的hash打平;更进一步还可以做下同一站点内的打平 Q:种子数量筛选,抓到同样的内容页,可能10万种子就够了,但库里有100万,怎么筛选? A:可以根据发现内容页的快慢、站点质量、站点权威性等多方面考虑--- ### 48. 按时限流反抓取系统设计 【Question】 设计一个按时限流的反抓取系统,支持指定多个时间段用户频率控制,要求给出数据结构设计和考虑性能问题【Answer】
一个子进程结束后,它的父进程并没有等待它(调用wait或者waitpid),那么这个子进程将成为一个僵尸进程。僵尸进程是一个已经死亡的进程,但是并没有真正被销毁。它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程表中保留一个位置,记载该进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等)供父进程收集,除此之外,僵尸进程不再占有任何内存空间。这个僵尸进程可能会一直留在系统中直到系统重启。 危害:占用进程号,而系统所能使用的进程号是有限的;占用内存。 以下情况不会产生僵尸进程: 该进程的父进程先结束了。每个进程结束的时候,系统都会扫描是否存在子进程,如果有则用Init进程接管,成为该进程的父进程,并且会调用wait等待其结束。 父进程调用wait或者waitpid等待子进程结束(需要每隔一段时间查询子进程是否结束)。wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。waitpid则可以加入WNOHANG(wait-no-hang)选项,如果没有发现结束的子进程,就会立即返回,不会将调用waitpid的进程阻塞。同时,waitpid还可以选择是等待任一子进程(同wait),还是等待指定pid的子进程,还是等待同一进程组下的任一子进程,还是等待组ID等于pid的任一子进程; 子进程结束时,系统会产生SIGCHLD(signal-child)信号,可以注册一个信号处理函数,在该函数中调用waitpid,等待所有结束的子进程(注意:一般都需要循环调用waitpid,因为在信号处理函数开始执行之前,可能已经有多个子进程结束了,而信号处理函数只执行一次,所以要循环调用将所有结束的子进程回收); 也可以用signal(SIGCLD, SIG_IGN)(signal-ignore)通知内核,表示忽略SIGCHLD信号,那么子进程结束后,内核会进行回收。--- ### 49. MySQL的隔离级别有哪些 【Question】【Answer】
1. 数据模型设计:按照时间分段对用户的访问请求进行统计:(3) 1)统一维护最细粒度的统计数据,然后根据时间段要求求和的方式; 2)根据业务配置规则,按照规则生成对应时间段统计,该方式简化最后的求和步骤,但是同一个业务如果有多个规则会导致存储一定开销; 3)设计具体的数据结构,评估需要的存储开销,如何进行简化; 2. 发抓取系统很多时候是通过较多规则进行控制,如不同的频率控制(粗粒度,细粒度),多维度(IP,用户指定等),设计该系统时候,需要设计好规则的表示方式(待补充)(3.5)MySQL的隔离级别有哪些,请简单描述一下--- ### 50. 小视频上传逻辑-系统设计 【Question】【Answer】
读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。读提交:一个事务提交之后,它做的变更才会被其他事务看到。可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。(数据校对)串行化:顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行MySQL默认隔离级别是可重复读,Oracle默认隔离级别是读提交下面是我们在抖音上经常会看到的小视频截图:
我们平时作为用户在抖音上看到的小视频,实际都是用户上传的小视频,因此每天都有几千万的小视频上传到服务器端。为了保证在移动弱网环境下,能够获得更高的传输效率,我们会使用一些传输策略。
以下是问题:
- 如果减少服务重启过程的影响,从部署和架构上需要做哪些事情?
- 如果分片文件大小很分散该如何优化?
- 还有哪些更好的实现,可以尽量减少磁盘缓存的使用,同时还能和后端存储很好结合起来?
- 描述下常见的对象存储系统架构
--- ### 51. 谈谈你对聚集索引的理解 【Question】【Answer】
- 如果减少服务重启过程的影响,从部署和架构上需要做哪些事情?
- 增加无状态代理层,平滑重启。
- 如果分片文件大小很分散该如何优化?
- 考察对磁盘数据管理的知识
- 还有哪些更好的实现,可以尽量减少磁盘缓存的使用,同时还能和后端存储很好结合起来?
- 滑动窗口+后端分片存储
- 描述下常见的对象存储系统架构
--- ### 52. 在线答题系统-系统设计 【Question】【Answer】
聚集索引:该索引中键值的逻辑顺序决定了表中相应行的物理顺序。 聚集索引确定表中数据的物理顺序。聚集索引类似于电话簿,后者按姓氏排列数据。由于聚集索引规定数据在表中的物理存储顺序,因此一个表只能包含一个聚集索引。但该索引可以包含多个列(组合索引),就像电话簿按姓氏和名字进行组织一样。 聚集索引使用注意事项 定义聚集索引键时使用的列越少越好。 • 包含大量非重复值的列。 .• 使用下列运算符返回一个范围值的查询:BETWEEN、>、>=、< 和 <=。 • 被连续访问的列。 • 回大型结果集的查询。 • 经常被使用联接或 GROUP BY 子句的查询访问的列;一般来说,这些是外键列。对 ORDER BY 或 GROUP BY 子句中指定的列进行索引,可以使 SQL Server 不必对数据进行排序,因为这些行已经排序。这样可以提高查询性能。 • OLTP 类型的应用程序,这些程序要求进行非常快速的单行查找(一般通过主键)。应在主键上创建聚集索引。 聚集索引不适用于: • 频繁更改的列 。这将导致整行移动(因为 SQL Server 必须按物理顺序保留行中的数据值)。这一点要特别注意,因为在大数据量事务处理系统中数据是易失的。 • 宽键 。来自聚集索引的键值由所有非聚集索引作为查找键使用,因此存储在每个非聚集索引的叶条目内。 非聚集索引:数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置。 非聚集索引中的项目按索引键值的顺序存储,而表中的信息按另一种顺序存储(这可以由聚集索引规定)。对于非聚集索引,可以为在表非聚集索引中查找数据时常用的每个列创建一个非聚集索引。有些书籍包含多个索引。例如,一本介绍园艺的书可能会包含一个植物通俗名称索引,和一个植物学名索引,因为这是读者查找信息的两种最常用的方法。 一个通俗的举例,说明两者的区别 其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。 如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。 第一:聚集索引的约束是唯一性,是否要求字段也是唯一的呢? 分析:如果认为是的朋友,可能是受系统默认设置的影响,一般我们指定一个表的主键,如果这个表之前没有聚集索引,同时建立主键时候没有强制指定使用非聚集索引,SQL会默认在此字段上创建一个聚集索引,而主键都是唯一的,所以理所当然的认为创建聚集索引的字段也需要唯一。 结论:聚集索引可以创建在任何一列你想创建的字段上,这是从理论上讲,实际情况并不能随便指定,否则在性能上会是恶梦。 第二:为什么聚集索引可以创建在任何一列上,如果此表没有主键约束,即有可能存在重复行数据呢? 粗一看,这还真是和聚集索引的约束相背,但实际情况真可以创建聚集索引。 分析其原因是:如果未使用 UNIQUE 属性创建聚集索引,数据库引擎将向表自动添加一个四字节 uniqueifier 列。必要时,数据库引擎 将向行自动添加一个 uniqueifier 值,使每个键唯一。此列和列值供内部使用,用户不能查看或访问。 第三:是不是聚集索引就一定要比非聚集索引性能优呢? 如果想查询学分在60-90之间的学生的学分以及姓名,在学分上创建聚集索引是否是最优的呢? 答:否。既然只输出两列,我们可以在学分以及学生姓名上创建联合非聚集索引,此时的索引就形成了覆盖索引,即索引所存储的内容就是最终输出的数据,这种索引在比以学分为聚集索引做查询性能更好。 第四:在数据库中通过什么描述聚集索引与非聚集索引的? 索引是通过二叉树的形式进行描述的,我们可以这样区分聚集与非聚集索引的区别:聚集索引的叶节点就是最终的数据节点,而非聚集索引的叶节仍然是索引节点,但它有一个指向最终数据的指针。 第五:在主键是创建聚集索引的表在数据插入上为什么比主键上创建非聚集索引表速度要慢? 有了上面第四点的认识,我们分析这个问题就有把握了,在有主键的表中插入数据行,由于有主键唯一性的约束,所以需要保证插入的数据没有重复。我们来比较下主键为聚集索引和非聚集索引的查找情况:聚集索引由于索引叶节点就是数据页,所以如果想检查主键的唯一性,需要遍历所有数据节点才行,但非聚集索引不同,由于非聚集索引上已经包含了主键值,所以查找主键唯一性,只需要遍历所有的索引页就行,这比遍历所有数据行减少了不少IO消耗。这就是为什么主键上创建非聚集索引比主键上创建聚集索引在插入数据时要快的真正原因。项目背景
春节期间,在线答题成为了一种比较风靡的移动端游戏新方式。
在线答题的基本逻辑是:app每隔30秒左右出一道选择题,用户有10秒左右的时间作答,答错一道即算失败。所有题目回答成功,则可以参与平分奖金。
增加需求
- 如果为了提升传播效应,增加了一种复活卡的功能,用户通过分享可以获得复活机会。 拥有复活机会的用户,在答题错误之后,可以使用复活机会并复活。 复活机会的次数支持一次或者多次。 回到刚才的代码,你需要怎么修改数据结构和逻辑,来支持这个需求呢?
【Answer】
参考:
- uesr_answer_history中的fail改成int型,保存用户的“答题机会”, 默认用户的答题机会是1, 如果有复活卡等,则可以在初始化用户信息的接口中,把答题机会初始化为1以上(具体取决于一场活动可以使用多少次复活卡) submit接口判断用户答题失败的时候,增加判断答题机会的逻辑, 如果答题失败,则答题机会减一,如果答题机会还没有减到0,则还可以继续答题。
【Answer】
多版本并发控制(Multi-Version Concurrency Control, MVCC),MVCC在每行记录后面都保存有两个隐藏的列,用来存储创建版本号和删除版本号。 创建版本号:创建一个数据行时的事务版本号(事务版本号:事务开始时的系统版本号;系统版本号:每开始一个新的事务,系统版本号就会自动递增); 删除版本号:删除操作时的事务版本号; 各种操作:插入操作时,记录创建版本号; 删除操作时,记录删除版本号; 更新操作时,先记录删除版本号,再新增一行记录创建版本号; 查询操作时,要符合以下条件才能被查询出来:删除版本号未定义或大于当前事务版本号(删除操作是在当前事务启动之后做的);创建版本号小于或等于当前事务版本号(创建操作是事务完成或者在事务启动之前完成) 通过版本号减少了锁的争用,提高了系统性能;可以实现提交读和可重复读两种隔离级别,未提交读无需使用MVCC
【Answer】
1、MyISAM它不支持事务,也不支持外键,尤其是访问速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用基本都可以使用这个引擎来创建表。 2、每个MyISAM在磁盘上存储成3个文件,其中文件名和表名都相同,但是扩展名分别为:.frm(存储表定义) ,YD(MYData,存储数据) , MYI(MYIndex,存储索引),InnoDB,InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比MyISAM的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。 3、memory使用存在内存中的内容来创建表。每个MEMORY表实际对应一个磁盘文件,格式是.frm。MEMORY类型的表访问非常快,因为它到数据是放在内存中的,并且默认使用HASH索引,但是一旦服务器关闭,表中的数据就会丢失,但表还会继续存在。 4、merge存储引擎是一组MyISAM表的组合,这些MyISAM表结构必须完全相同,MERGE表中并没有数据,对MERGE类型的表可以进行查询、更新、删除的操作,这些操作实际上是对内部的MyISAM表进行操作。
【Answer】
对称加密:加密和解密采用相同的密钥。如:DES、RC2、RC4 非对称加密:需要两个密钥:公钥和私钥。如果用公钥加密,需要用私钥才能解密。如:RSA 区别:对称加密速度更快,通常用于大量数据的加密;非对称加密安全性更高(不需要传送私钥)
【Answer】
客户端没有收到ACK确认,会重新发送FIN请求。
【Answer】
使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销: select * from table ...; 当前读读取的是最新的数据,需要加锁。以下第一个语句需要加 S 锁,其它都需要加 X 锁: select * from table where ? lock in share mode; select * from table where ? for update; insert; update; delete;
【Answer】
回表查询:先定位主键值,再定位行记录,它的性能较扫一遍索引树更低解决方法:将被查询的字段,建立到联合索引里,即实现了索引覆盖
【Answer】
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
【Answer】
Linux虚拟内存的实现需要6种机制的支持:地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制和内存共享机制 内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址。当用户程序运行时,如果发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。如果有空闲的内存可供分配,就请求分配内存(于是用到了内存的分配和回收),并把正在使用的物理页记录在缓存中(使用了缓存机制)。如果没有足够的内存可供分配,那么就调用交换机制;腾出一部分内存。另外,在地址映射中要通过TLB(翻译后援存储器)来寻找物理页;交换机制中也要用到交换缓存,并且把物理页内容交换到交换文件中,也要修改页表来映射文件地址。 进程和线程、进程间及线程通信方式、共享内存的使用实现原理
【Answer】
1. 针对table,row和field定义相应类型,field支持类型描述和数据类型转换功能。(3.5) 2. 如果没有抽象出相关类型,但是代码功能实现正确;(3分)
【Answer】
智能指针:实际指行为类似于指针的类对象 ,它的一种通用实现方法是采用引用计数的方法。 1.智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针。 2.每次创建类的新对象时,初始化指针并将引用计数置为1; 3.当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数; 4.对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;这是因为左侧的指针指向了右侧指针所指向的对象,因此右指针所指向的对象的引用计数+1; 5.调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。 6.实现智能指针有两种经典策略:一是引入辅助类,二是使用句柄类。这里主要讲一下引入辅助类的方法
【Answer】
端口不同:HTTP使用的是80端口,HTTPS使用443端口; HTTP(超文本传输协议)信息是明文传输,HTTPS运行在SSL(Secure Socket Layer)之上,添加了加密和认证机制,更加安全; HTTPS由于加密解密会带来更大的CPU和内存开销; HTTPS通信需要证书,一般需要向证书颁发机构(CA)购买
给定视频打分表A,包含视频id、视频主题和视频打分情况三列: video_id,subject,score,
求每个subject视频的score的中位数。
【Answer】
select a.subject,
a.score * (1 - b.float_part) + a.next_score * (b.float_part-0) as median
from
(
select
subject, score
row_number() over(partition by subject order by score asc) as rank,
lead(score,1) over(partition by subject order by score asc) as next_score
from A
) a
inner join
(
select
subject,
floor((count(score) + 1) / 2) as int_part,
(count(score)+1) / 2 % 1 as float_part
from A
group by subject
) b
on a.rank = b.int_part and a.subject=b.subject
group by a.subject
【Answer】
1、厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;2、单例模式:Bean默认为单例模式。3、代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;4、模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。5、观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
【Answer】
一个线程可以拥有多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。 线程进程都是同步机制,而协程则是异步 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
【Answer】
```Javascript // decorator function throttle(time: number = 1000) { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.value = throttleFunc(descriptor.value, time) return descriptor; } } // throttleFunc function throttleFunc(func: Function, time: number): Function { let timerId = null; let timeStamp = 0; let callCount = 0; let args = []; return function () { args = [].slice.call(arguments); const now = Date.now(); if (now - timeStamp > time) { func.apply(this, args); callCount = 0; timeStamp = now; } ++callCount; if (!timerId) { timerId = setTimeout(() => { if (callCount > 1) { func.apply(this, args); } callCount = 0; timerId = null; timeStamp = now; }, time); } } } ```
【Answer】
C++的多态分为静态多态(编译时多态)和动态多态(运行时多态)两大类。静态多态通过重载、模板来实现;动态多态就是通过本文的主角虚函数来体现的。 虚函数实现原理:包括虚函数表、虚函数指针等 虚函数的作用说白了就是:当调用一个虚函数时,被执行的代码必须和调用函数的对象的动态类型相一致。编译器需要做的就是如何高效的实现提供这种特性。不同编译器实现细节也不相同。大多数编译器通过vtbl(virtual table)和vptr(virtual table pointer)来实现的。 当一个类声明了虚函数或者继承了虚函数,这个类就会有自己的vtbl。vtbl实际上就是一个函数指针数组,有的编译器用的是链表,不过方法都是差不多。vtbl数组中的每一个元素对应一个函数指针指向该类的一个虚函数,同时该类的每一个对象都会包含一个vptr,vptr指向该vtbl的地址。
【Answer】
Volatile关键词的第一个特性:易变性。所谓的易变性,在汇编层面反映出来,就是两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是重新从内存中读取。 Volatile关键词的第二个特性:“不可优化”特性。volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行。 Volatile关键词的第三个特性:”顺序性”,能够保证Volatile变量间的顺序性,编译器不会进行乱序优化。 C/C++ Volatile变量,与非Volatile变量之间的操作,是可能被编译器交换顺序的。C/C++ Volatile变量间的操作,是不会被编译器交换顺序的。哪怕将所有的变量全部都声明为volatile,哪怕杜绝了编译器的乱序优化,但是针对生成的汇编代码,CPU有可能仍旧会乱序执行指令,导致程序依赖的逻辑出错,volatile对此无能为力 针对这个多线程的应用,真正正确的做法,是构建一个happens-before语义。 [C/C++ Volatile关键词深度剖析](http://hedengcheng.com/?p=725)
【Answer】
不可以。有两个原因: 首先,可能会出现已失效的连接请求报文段又传到了服务器端。 client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 server。本来这是一个早已失效的报文段。但 server 收到此失效的连接请求报文段后,就误认为是 client 再次发出的一个新的连接请求。于是就向 client 发出确认报文段,同意建立连接。假设不采用 “三次握手”,那么只要 server 发出确认,新的连接就建立了。由于现在 client 并没有发出建立连接的请求,因此不会理睬 server 的确认,也不会向 server 发送数据。但 server 却以为新的运输连接已经建立,并一直等待 client 发来数据。这样,server 的很多资源就白白浪费掉了。采用 “三次握手” 的办法可以防止上述现象发生。例如刚才那种情况,client 不会向 server 的确认发出确认。server 由于收不到确认,就知道 client 并没有要求建立连接。 其次,两次握手无法保证Client正确接收第二次握手的报文(Server无法确认Client是否收到),也无法保证Client和Server之间成功互换初始序列号。
【Answer】
控制变量的存储方式和可见性。 (1)修饰局部变量 一般情况下,对于局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了。但是如果用static进行修饰的话,该变量便存放在静态数据区,其生命周期一直持续到整个程序执行结束。但是在这里要注意的是,虽然用static对局部变量进行修饰过后,其生命周期以及存储空间发生了变化,但是其作用域并没有改变,其仍然是一个局部变量,作用域仅限于该语句块。 (2)修饰全局变量 对于一个全局变量,它既可以在本源文件中被访问到,也可以在同一个工程的其它源文件中被访问(只需用extern进行声明即可)。用static对全局变量进行修饰改变了其作用域的范围,由原来的整个工程可见变为本源文件可见。 (3)修饰函数 用static修饰函数的话,情况与修饰全局变量大同小异,就是改变了函数的作用域。 (4)C++中的static 如果在C++中对类中的某个函数用static进行修饰,则表示该函数属于一个类而不是属于此类的任何特定对象;如果对类中的某个变量进行static修饰,表示该变量为类以及其所有的对象所有。它们在存储空间中都只存在一个副本。可以通过类和对象去调用。
【Answer】
进程(Process)是系统进行资源分配和调度的基本单位,线程(Thread)是CPU调度和分派的基本单位; 线程依赖于进程而存在,一个进程至少有一个线程; 进程有自己的独立地址空间,线程共享所属进程的地址空间; 进程是拥有系统资源的一个独立单位,而线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),和其他线程共享本进程的相关资源如内存、I/O、cpu等; 在进程切换时,涉及到整个当前进程CPU环境的保存环境的设置以及新被调度运行的CPU环境的设置,而线程切换只需保存和设置少量的寄存器的内容,并不涉及存储器管理方面的操作,可见,进程切换的开销远大于线程切换的开销; 线程之间的通信更方便,同一进程下的线程共享全局变量等数据,而进程之间的通信需要以进程间通信(IPC)的方式进行; 多线程程序只要有一个线程崩溃,整个程序就崩溃了,但多进程程序中一个进程崩溃并不会对其它进程造成影响,因为进程有自己的独立地址空间,因此多进程更加健壮 进程操作代码实现,可以参考:多进程 - 廖雪峰的官方网站
【Answer】
动态链接是只建立一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在运行时再装入;而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要库了。
【Answer】
指针与地址的区别? 区别: 1指针意味着已经有一个指针变量存在,他的值是一个地址,指针变量本身也存放在一个长度为四个字节的地址当中,而地址概念本身并不代表有任何变量存在. 2 指针的值,如果没有限制,通常是可以变化的,也可以指向另外一个地址. 地址表示内存空间的一个位置点,他是用来赋给指针的,地址本身是没有大小概念,指针指向变量的大小,取决于地址后面存放的变量类型. 指针与数组名的关系? 其值都是一个地址,但前者是可以移动的,后者是不可变的. 指针和引用的区别(一般都会问到) 相同点:1. 都是地址的概念; 指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。 区别:1. 指针是一个实体,而引用仅是个别名; 2. 引用使用时无需解引用(*),指针需要解引用; 3. 引用只能在定义时被初始化一次,之后不可变;指针可变; 4. 引用没有 const,指针有 const; 5. 引用不能为空,指针可以为空; 6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小; 7. 指针和引用的自增(++)运算意义不一样; 8.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。
【Answer】
事务必须严格分为两个阶段对数据进行加锁和解锁的操作,第一阶段加锁,第二阶段解锁。也就是说一个事务中一旦释放了锁,就不能再申请新锁了。
【Answer】
为了限制不同程序的访问能力,防止一些程序访问其它程序的内存数据,CPU划分了用户态和内核态两个权限等级。 用户态只能受限地访问内存,且不允许访问外围设备,没有占用CPU的能力,CPU资源可以被其它程序获取; 内核态可以访问内存所有数据以及外围设备,也可以进行程序的切换。 所有用户程序都运行在用户态,但有时需要进行一些内核态的操作,比如从硬盘或者键盘读数据,这时就需要进行系统调用,使用陷阱指令,CPU切换到内核态,执行相应的服务,再切换为用户态并返回系统调用的结果。
【Answer】
守护进程最重要的特性是后台运行。 1. 在后台运行。 为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。 if(pid=fork()) exit(0); //是父进程,结束父进程,子进程继续 2. 脱离控制终端,登录会话和进程组 有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长: setsid(); 说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。 3. 禁止进程重新打开控制终端 现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端: if(pid=fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话组长) 4. 关闭打开的文件描述符 进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们: for(i=0;i 关闭打开的文件描述符close(i);> 5. 改变当前工作目录 进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 /tmpchdir("/") 6. 重设文件创建掩模 进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0); 7. 处理SIGCHLD信号 处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。 signal(SIGCHLD,SIG_IGN); 这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。
【Answer】
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 (2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 (3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
【Answer】
简单来说,关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。 非关系型数据库提出另一种理念,例如,以键值对存储,且结构不固定,每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,这 样就不会局限于固定的结构,可以减少一些时间和空间的开销。使用这种方式,用户可以根据需要去添加自己需要的字段,这样,为了获取用户的不同信息,不需要 像关系型数据库中,要对多表进行关联查询。仅需要根据id取出相应的value就可以完成查询。但非关系型数据库由于很少的约束,他也不能够提供像SQL 所提供的where这种对于字段属性值情况的查询。并且难以体现设计的完整性。他只适合存储一些较为简单的数据,对于需要进行较复杂查询的数据,SQL数 据库显的更为合适。 关系型数据库的最大特点就是事务的一致性:传统的关系型数据库读写操作都是事务的,具有ACID的特点,这个特性使得关系型数据库可以用于几乎所有对一致性有要求的系统中,如典型的银行系统。 但是,在网页应用中,尤其是SNS应用中,一致性却不是显得那么重要,用户A看到的内容和用户B看到同一用户C内容更新不一致是可以容忍的,或者 说,两个人看到同一好友的数据更新的时间差那么几秒是可以容忍的,因此,关系型数据库的最大特点在这里已经无用武之地,起码不是那么重要了。 相反地,关系型数据库为了维护一致性所付出的巨大代价就是其读写性能比较差,而像微博、facebook这类SNS的应用,对并发读写能力要求极 高,关系型数据库已经无法应付(在读方面,传统上为了克服关系型数据库缺陷,提高性能,都是增加一级memcache来静态化网页,而在SNS中,变化太 快,memchache已经无能为力了),因此,必须用新的一种数据结构存储来代替关系数据库。 关系数据库的另一个特点就是其具有固定的表结构,因此,其扩展性极差,而在SNS中,系统的升级,功能的增加,往往意味着数据结构巨大变动,这一点关系型数据库也难以应付,需要新的结构化数据存储。 于是,非关系型数据库应运而生,由于不可能用一种数据结构化存储应付所有的新的需求,因此,非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合。 必须强调的是,数据的持久存储,尤其是海量数据的持久存储,还是需要一种关系数据库这员老将。 非关系型数据库分类: 主要分为以下几类: 面向高性能并发读写的key-value数据库: key-value数据库的主要特点即使具有极高的并发读写性能,Redis,Tokyo Cabinet,Flare就是这类的代表 面向海量数据访问的面向文档数据库: 这类数据库的特点是,可以在海量的数据中快速的查询数据,典型代表为MongoDB以及CouchDB 面向可扩展性的分布式数据库: 这类数据库想解决的问题就是传统数据库存在可扩展性上的缺陷,这类数据库可以适应数据量的增加以及数据结构的变化
【Answer】
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0” virtual void funtion1()=0 原因: 1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。 2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。 为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。 定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。 纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。 [虚函数和纯虚函数的区别](http://blog.csdn.net/hackbuteer1/article/details/7558868)
【Answer】
管道(Pipe) 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道; 一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据; 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程) 命名管道 消息队列 信号(Signal) 共享内存 信号量(Semaphore):初始化操作、P操作、V操作;P操作:信号量-1,检测是否小于0,小于则进程进入阻塞状态;V操作:信号量+1,若小于等于0,则从队列中唤醒一个等待的进程进入就绪态 套接字(Socket) 更详细的可以参考(待整理): https://imageslr.github.io/2020/02/26/ipc.html https://www.jianshu.com/p/c1015f5ffa74
【Answer】
哈希索引能以 O(1) 时间进行查找,但是只支持精确查找,无法用于部分查找和范围查找,无法用于排序与分组;B树索引支持大于小于等于查找,范围查找。哈希索引遇到大量哈希值相等的情况后查找效率会降低。哈希索引不支持数据的排序。
【Answer】
它实际上是一个很长的二进制向量和一系列随机映射函数(Hash函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。Bloom Filter广泛的应用于各种需要查询的场合中,如Orocle的数据库,Google的BitTable也用了此技术。 Bloom Filter特点: 不存在漏报(False Negative),即某个元素在某个集合中,肯定能报出来。 可能存在误报(False Positive),即某个元素不在某个集合中,可能也被爆出来。 确定某个元素是否在某个集合中的代价和总的元素数目无关。
【Answer】
优点: 大大加快了数据的检索速度; 可以显著减少查询中分组和排序的时间; 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性; 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起) 缺点: 建立和维护索引耗费时间空间,更新索引很慢。
【Answer】
为什么需要线程同步:线程有时候会和其他线程共享一些资源,比如内存、数据库等。当多个线程同时读写同一份共享资源的时候,可能会发生冲突。因此需要线程的同步,多个线程按顺序访问资源。 互斥量 Mutex:互斥量是内核对象,只有拥有互斥对象的线程才有访问互斥资源的权限。因为互斥对象只有一个,所以可以保证互斥资源不会被多个线程同时访问;当前拥有互斥对象的线程处理完任务后必须将互斥对象交出,以便其他线程访问该资源; 信号量 Semaphore:信号量是内核对象,它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。信号量对象保存了最大资源计数和当前可用资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就减1,只要当前可用资源计数大于0,就可以发出信号量信号,如果为0,则将线程放入一个队列中等待。线程处理完共享资源后,应在离开的同时通过ReleaseSemaphore函数将当前可用资源数加1。如果信号量的取值只能为0或1,那么信号量就成为了互斥量; 事件 Event:允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。事件分为手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒一个等待中的线程,然后自动恢复为未激发状态。 临界区 Critical Section:任意时刻只允许一个线程对临界资源进行访问。拥有临界区对象的线程可以访问该临界资源,其它试图访问该资源的线程将被挂起,直到临界区对象被释放。
【Answer】
Session是服务器端保持状态的方案,Cookie是客户端保持状态的方案 Cookie保存在客户端本地,客户端请求服务器时会将Cookie一起提交;Session保存在服务端,通过检索Sessionid查看状态。保存Sessionid的方式可以采用Cookie,如果禁用了Cookie,可以使用URL重写机制(把会话ID保存在URL中)。
【Answer】
每个程序都拥有自己的地址空间,这个地址空间被分成大小相等的页,这些页被映射到物理内存;但不需要所有的页都在物理内存中,当程序引用到不在物理内存中的页时,由操作系统将缺失的部分装入物理内存。这样,对于程序来说,逻辑上似乎有很大的内存空间,只是实际上有一部分是存储在磁盘上,因此叫做虚拟内存。 虚拟内存的优点是让程序可以获得更多的可用内存。 虚拟内存的实现方式、页表/多级页表、缺页中断、不同的页面淘汰算法:答案。
【Answer】
概念:事务(Transaction)是一个操作序列,不可分割的工作单位,以BEGIN TRANSACTION开始,以ROLLBACK/COMMIT结束 特性(ACID): 原子性(Atomicity):逻辑上是不可分割的操作单元,事务的所有操作要么全部提交成功,要么全部失败回滚(用回滚日志实现,反向执行日志中的操作); 一致性(Consistency):事务的执行必须使数据库保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的; 隔离性(Isolation):一个事务所做的修改在最终提交以前,对其它事务是不可见的(并发执行的事务之间不能相互影响); 持久性(Durability):一旦事务提交成功,对数据的修改是永久性的
【Answer】
string:基本的缓存、计数器list:消息队列、分页hash:存储结构化的数据set:标签、去重sortedSet:排行榜
【Answer】
未提交读(Read Uncommited):在一个事务提交之前,它的执行结果对其它事务也是可见的。会导致脏读、不可重复读、幻读; 提交读(Read Commited):一个事务只能看见已经提交的事务所作的改变。可避免脏读问题; 可重复读(Repeatable Read):可以确保同一个事务在多次读取同样的数据时得到相同的结果。(MySQL的默认隔离级别)。可避免不可重复读; 可串行化(Serializable):强制事务串行执行,使之不可能相互冲突,从而解决幻读问题。可能导致大量的超时现象和锁竞争,实际很少使用。
【Answer】
ProductID,CreateDateCreateDate
【Answer】
分析慢查询日志:记录了在MySQL中响应时间超过阀值long_query_time的SQL语句,通过日志去找出IO大的SQL以及发现未命中索引的SQL 使用 Explain 进行分析:通过explain命令可以得到表的读取顺序、数据读取操作的操作类型、哪些索引可以使用、哪些索引被实际使用、表之间的引用以及被扫描的行数等问题; 应尽量避免在 where 子句中使用!=、<、>操作符或对字段进行null值判断,否则将引擎放弃使用索引而进行全表扫描; 只返回必要的列:最好不要使用 SELECT * 语句; 只返回必要的行:使用 LIMIT 语句来限制返回的数据; 将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有:让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用; 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余的查询; 减少锁竞争
实现类似微信朋友圈的功能,主要是几个功能点:
1,每个人的朋友圈是完全独立的,朋友圈简化成只包含文本信息
2,人和人之间有好友关系
3,某个人发了朋友圈后,其所有好友都可以看见这个朋友圈内容
【Answer】
基本思路:
1,需要将人、朋友圈文本分开成多个数据表存储,最好能给出细节的schema设计
2,好友关系如何存储和维护,采用 map or list 的不同实现有什么差异
3,如何拉取所有好友的朋友圈,是在某个人发了朋友圈的时候进行计算,还是打开朋友圈的时候遍历所有好友?
进阶思路:
1,如果有一个人其好友数非常多,如何保障在尽可能短的时间内拉取到朋友圈
【Answer】
inode包含文件的元信息,具体来说有以下内容: * 文件的字节数 * 文件拥有者的User ID * 文件的Group ID * 文件的读、写、执行权限 * 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。 * 链接数,即有多少文件名指向这个inode * 文件数据block的位置 inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。 每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。 每个inode都有一个号码,操作系统用inode号码来识别不同的文件。 这里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。 表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。 一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。 这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。 ln命令可以创建硬链接:ln 源文件 目标文件 文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的"软链接"(soft link)或者"符号链接(symbolic link)。 这意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错:"No such file or directory"。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode"链接数"不会因此发生变化。 ln -s命令可以创建软链接。:ln -s 源文文件或目录 目标文件或目录 http://www.ruanyifeng.com/blog/2011/12/inode.html
【Answer】
【Answer】
内联函数是代码被插入到调用者代码处的函数。如同 #define 宏,内联函数通过避免被调用的开销来提高执行效率,尤其是它能够通过调用(“过程化集成”)被编译器优化。 宏定义不检查函数参数,返回值什么的,只是展开,相对来说,内联函数会检查参数类型,所以更安全。 内联函数和宏很类似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。 宏是预编译器的输入,然后宏展开之后的结果会送去编译器做语法分析。宏与函数等处于不同的级别,操作不同的实体。宏操作的是 token, 可以进行 token的替换和连接等操作,在语法分析之前起作用。而函数是语言中的概念,会在语法树中创建对应的实体,内联只是函数的一个属性。 对于问题:有了函数要它们何用?答案是:一:函数并不能完全替代宏,有些宏可以在当前作用域生成一些变量,函数做不到。二:内联函数只是函数的一种,内联是给编译器的提示,告诉它最好把这个函数在被调用处展开,省掉一个函数调用的开销(压栈,跳转,返回) 内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样 内联函数必须是和函数体申明在一起,才有效。 [宏定义和内联函数区别](http://www.cnblogs.com/chengxuyuancc/archive/2013/04/04/2999844.html)
【Answer】
1. B+树2. 主要是节点太多,反复读盘影响查询效率100 万节点的平衡二叉树,树高 20。一次查询可能需要访问 20 个数据块。在机械硬盘时代,从磁盘随机读一个数据块需要 10 ms 左右的寻址时间。也就是说,对于一个 100 万行的表,如果使用二叉树来存储,单独访问一个行可能需要 20 个 10 ms 的时间3.a. 有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。b. 所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。c. 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。d. 叶子节点之间用指针相连e. 叶子节点带有卫星数据
【Answer】
以“%(表示任意0个或多个字符)”开头的LIKE语句; OR语句前后没有同时使用索引; 数据类型出现隐式转化(如varchar不加单引号的话可能会自动转换为int型); 对于多列索引,必须满足 最左匹配原则/最左前缀原则 (最左优先,eg:多列索引col1、col2和col3,则 索引生效的情形包括 col1或col1,col2或col1,col2,col3); 如果MySQL估计全表扫描比索引快,则不使用索引(比如非常小的表)
【Answer】
const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。习惯性的使用const,可以避免在函数中对某些不应修改的变量造成可能的改动。 (1)const修饰基本数据类型 1.const修饰一般常量及数组 基本数据类型,修饰符const可以用在类型说明符前,也可以用在类型说明符后,其结果是一样的。在使用这些常量的时候,只要不改变这些常量的值便好。 2.const修饰指针变量*及引用变量& 如果const位于星号*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量; 如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。 (2)const应用到函数中, 1.作为参数的const修饰符 调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,保护了原对象的属性。 [注意]:参数const通常用于参数为指针或引用的情况; 2.作为函数返回值的const修饰符 声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。 (3)const在类中的用法 不能在类声明中初始化const数据成员。正确的使用const实现方法为:const数据成员的初始化只能在类构造函数的初始化表中进行 类中的成员函数:A fun4()const; 其意义上是不能修改所在类的的任何变量。 (4)const修饰类对象,定义常量对象 常量对象只能调用常量函数,别的成员函数都不能调用。 http://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html
【Answer】
脏写更新的数据一段时间后没了A和B先后更新同一条记录,A更新完后使用undo log回滚,导致之后写入的数据也被回滚了脏读读到其他事务未提交的数据又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的,值得注意的是,脏读一般是针对于update操作的幻读前后读取到的记录数量不一致A在执行期间,B向表中插入了数据,A再次查询时,读到的数据内容发生了变化不可重复读前后读取的记录内容不一致事务A读到了事务B还未提交的数据,B回滚后,A再次读,内容就不一样了![]()
互联网秒杀场景下,很多接口的峰值流量非常非常陡峭,必须做好限流不然会把内部系统打挂。请设计一个分布式限流器,能实现这个功能。
1. 限流器能work,精确度达到 99% 以上
2. 限流阈值大小可设置,大的达到1000w级QPS,小的小到10QPS级别
3. 通用的限流器,不局限于某一业务领域
【Answer】
1. 利用redis的原子自增和过期淘汰策略
- 限流器的计数存放在redis中,用redis的过期淘汰策略实现限流器的计数的定期更新
- 例如针对 接口A 限流 10000 QPS。redis的key为:“接口A”,value为计数值 - 每次接口调用Redis用INC原子自增命令,自增1,并设置过期时间为1s
- 初次调用时,因为redis中该key没有,就直接设置为1,并设置过期时间为1s
- 在这一秒以内的后续调用,每次都自增1 - 客户端拿到自增后的值如果没有超过限制10000,就放行 - 如果超过 10000 限制,就不放行,说明超限了
- 细节实现:为避免超限后无谓的redis调用,第一次发现超限时可以记录该值的TTL时间,例如只过去100ms就有1w个请求过来,剩下的900ms就不用请求redis而是直接返回超限即可。不然这种情况会给redis带去额外无谓的流量,例如前面的例子,不做这个细节逻辑的话,redis的请求量是 10w QPS
- 精度可调节。假如限流阈值很大,比如100w,可以把INC自增步进/步长调整大一些,例如100,那么redis的QPS直接降低100倍,为1w QPS
1. 分为两个接口
- 从一个长网址生成一个短网址。需要考虑:同一个长网址,多次创建的短网址是否相同
- 用户访问短网址时,需要能跳回到原始的长网址
2. 需要考虑跨机房部署问题
3. 加分项:考虑跨海域全球部署问题
4. 加分项:考虑统计某个域名下的URL/host访问uv和pv
【Answer】
1. 一开始需要能考虑系统承载容量,例如:
- 每天100亿访问量
- 每天生成1000w条短网址记录
2. 然后考虑短网址的生成算法,方案有很多种
- 最简单实现:自增id实现,这个不可逆,同一个长网址会生成多个短网址
- hash+序号冲突
- 使用kv存储双向对应关系,可逆,但存储用量比较大
3. 302跳转问题,附带可以讨论网址访问量计数问题
4. 加分项1:需要考虑跨机房部署问题
5. 加分项2:考虑跨海域全球部署问题
6. 加分项3:能给出合理的统计需求,例如用hadoop做MR
【Answer】
## 什么时候需要建立索引 - 作为主键的列上,强制该列的唯一性和组织表中数据的排列结构。 - 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度。 - 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的。 - 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。 - 在经常使用在where子句中的列上面创建索引,加快条件的判断速度。 ## 什么时候不应该创建索引 - 在查询中很少使用或者作为参考的列不应该创建索引。 - 对于那些只有很少数据值的列也不应该增加索引(比如性别,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度)。 - 对于那些定义为text,image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。 - 当修改性能远远大于检索性能时,不应该创建索引,因为修改性能和检索性能是矛盾的。
【Answer】
+ 方案一:直接使用数据库+cache的方案,方案简单有效,数据量大之后采用分库方案,扩展性有限,但开发和运维成本低,性能方面通过cache优化,存在热点数据(可能导致cache经常被清理); + 方案二:采用redis作为kv存储,查询效率足够高,但需要考虑资源使用问题,假设有10亿的文章,帖子和评论,如何保证以更低的成本满足该需求;主要问题需要较多资源,成本较高,如何设计更好数据结构;(3+) + 方案三:可以自己开发counter模块,需要考虑kv存储方案,value的设计,保证使用更少内存;还需要考虑的点:容灾备份机制;数据扩展问题;(3.5) + 方案四:可能业务方经常新增计数需求,需要考虑counter服务的列扩展性,故设计的数据结构需要考虑列扩展问题;(3.5) + 其他:业务写入QPS可能比较高,考虑写入压力问题,数据写入去重问题,即同一个用户转发之类操作需要幂等性(一定程度保证即可)
Redis提供了哪几种持久化方式?
【Answer】
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
你也可以同时开启两种持久化方式, 在这种情况下, 当Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
【Answer】
### 方案设计 主流的设计主要有两种:**推模型和拉模型**。(任何一种都是合适的设计) 推模型简述: 基于长连接,直播间内的观众发的消息,异步通过长连接发送给房间内的其他观众。候选人的架构图应该没有硬伤。 深入的问题(言之有理即可): 1. 长连接的架构(协议、鉴权、扩容、重连、到达率等) 2. 消息放大问题如何解决(比如一个有1万人的房间,任何一个人发的消息,都会产生1万个消息,相当于放大了1万倍) 消息聚合、消息多级推送(类似CDN的方式) 3. 直播间用户列表怎么存储和优化 推送消息时,需要房间内所有用户消息,对于观众比较多的房间,需要考虑数据分片、本地缓存等手段进行优化。 拉模型简述: 拉模型类型类似于一个消息队列的模型。每个房间会有一个消息列表,直播间内用户发的消息会放在相应的消息列表中。直播间内的观众通过前端轮询拉消息接口, 把消息拉到客户端展示。 深入的问题(言之有理即可): 1. 房间的消息如何存储(由于消息有时效性,所以只需要存储最近一段时间的数据) 2. 轮询方式如何优化 3. 拉接口如何优化(local cache等) ### 核心指标 消息每秒吞吐量、消息到达率、消息延时(像稳定性这种,属于通用的基本指标)。 ### 核心的优化方式(提供一些方式,其它的只要合理即可): 监控方式: 1. 吞吐量(类似于qps,打metrics等都可以) 2. 到达率:对于推模型,基本等价于长连接的到达率监控;对于拉模型,性价比较高的是只监控主播的(因为只有主播是全程在直播间的) 3. 延时:需要关注手机和服务端的时间不一致的问题 优化: 1. 吞吐量:批量发送、多级推送等 2. 到达率:一般推模型需要重点关注,主要是对于长连接的到达率优化,包含死连接检测等。 3. 延时:一般拉模型需要重点关注,对于每次都拉到消息的房间,减少轮询间隔和长连接拉模式等。 ### 拉模型的分布式方案 类似于一个分布式存储系统,解决水平和垂直扩展的问题。
表A:用户浏览视频日志 user_behavior: date, user_id, video_id, start_time, end_time
表B:视频信息 video_info: video_id, video_duration
表C:用户信息 user_info: user_id, gender
问题:
(1)某一天(如 20200310),观看不同视频个数的前 5 名 user_id ;
(2)观看超过 50 个不同视频的女性用户中,完整观看率最高的 10 个 user_id。
【Answer】
select user_id, count(*) as video_cnt from ( select user_id, video_id from user_behavior where date = '20200310' group by user_id, video_id ) a group by user_id order by video_cnt desc limit 5;
select user_id, count(*) as video_cnt, sum(is_full) * 1.0/count(*) as full_ratio from ( select a.user_id, a.video_id, case when a.max_time >= c.duration then 1 else 0 end as is_full from ( select user_id, video_id, max(end_time -start_time) as max_time from user_behavior where date = '20200310' group by user_id, video_id ) a inner join ( select user_id, gender from user_info where gender = 'female' ) b on a.user_id = b.user_id left outer join video_info c on a.video_id = c.video_id ) a group by user_id having video_cnt >= 50 order by full_ratio desc limit 10
【Answer】
A INNER JOIN B ON……:内联操作,将符合ON条件的A表和B表结果均搜索出来,然后合并为一个结果集。 A LEFT JOIN B ON……:左联操作,左联顾名思义是,将符合ON条件的B表结果搜索出来, 然后左联到A表上,然后将合并后的A表输出。 A RIGHT JOIN B ON……:右联操作,右联顾名思义是,将符合ON条件的A表结果搜索出来, 然后右联到B表上,然后将合并后的B表输出。
【Answer】
设计思路:限流+策略+反作弊,一般情况下候选人想不到完善的设计,我们主要看其设计能否满足前面的需求。 参考:http://www.tuicool.com/articles/e2YVRvA 1 可以满足业务需求,抗住压力,不会超卖 3分 2 能考虑到到各种情况下的灾备方案,系统易于对商品种类,抢购规模进行扩展,3.5分
【Answer】
方案1:按QPS控制访问流量,在redis中纪录每秒的访问次数,1秒过期 方案2:按时间窗口限制访问次数,保存一个访问队列,定期删除过期的访问,维护一个当前时间窗口内的总访问次数 方案3:Token Bucket思想,控制Bucket的总容量和留入令牌速率,考虑到burst rate问题 方案4:基于方案3还能考虑到平台话的动态配置,熔断策略等
【Answer】
1. 评论大量写入优化方案:id分配策略,写入量太多如何进行扩展(自己实现分片策略,或者采用分布式存储系统,如果选用分布式系统介绍选型方案和实现细节);(3分,如果分片策略或存储系统介绍比较好,理解深刻,可得3.5分) 2. 查询方面如果按照时间逆序,索引可以采用zset方式,但需要考虑单个文章评论索引太多问题,故可以采用自建索引或者利用redis等拆分索引大小;其次需要考虑评论被删除后,索引中大片数据无法使用情况,采用重建索引还是单独提供删除索引?(3,如果能够对数据量较好预估,根据数据量选择合适索引方案得3.5分) 3. 头条环境下对评论做了个性化排序,索引的机制如何进行调整;(加分项目,回答OK,得3.5分) 4. 容错机制考虑;(加分项目)
【Answer】
1. 基础数据结构采用hash或者map的方式均可以,实现可得3分; 2. 考虑线程安全性,同时兼顾查询效率,是否考虑分桶等机制;(3+) 3. 分布式环境下:主要考察分片方案,如何做到使用者透明,怎么解决某一个节点故障问题,雪崩问题;(3.5)
题目:平台要对外提供一些开放API接口,设计应该考虑哪些?
开放性题目,可以不限制答案
【Answer】
参考回答要点
- 鉴权,可以追问怎么设计,如果提到公钥私钥,可以追问原理,oath2.0可以追问下怎么设计的
- 防攻击,可以追问怎么设计,考虑ip、cookie单位时间访问频次,怎么控制,有的候选人可能会提令牌桶,可以问问实现过程
- 访问次数限制,按照账号限制次数,分级
- 反爬取,封禁,假数据
- 参数签名,防篡改
- 追问如果有用户隐私信息怎么处理
- https、接口统一规范(错误码、格式)、数据库、cache等,也可以继续追问
【Answer】
去重必定会有状态,所以该系统设计的核心在于“状态”数据的存储,特点就是数据量大,去重要求精确。 1. 常规的思路是用分布式KV存储(Redis、Memcache等),去重模块本身无状态。基本合格。3.0 2. 考虑本地Redis或Memcache方案,去重模块按用户分片。3.0+ 3. 能根据状态数据的特性,考虑自己实现存储,并且实现半持久化(程序重启数据不丢),内存+磁盘存储可满足需求,根据面试者存储数据的选择和持久方式的解答,可以3.0+ 或 3.5。 4. 基于共享内存(或类似的磁盘文件映射mmap等方式),并且能较好的给出基于块存储实现Hash的表,以及Hash表的增加和查询。因为数据量大,存储需要考虑很多优化方案,基于bit的操作等。3.5 5. 在4的基础上,考虑到数据数据清除(以天为单位清除)。考虑Android设备可以自己掌握弹窗的,在端上做保底去重。3.5+
现在来了一个新需求,希望在微博用户的个人页面加入共同关注的好友有哪些,在用户搜索结果页加入每一个结果中共同关注的好友数量,需求设计稿如下(下面只做大致介绍):
【Answer】
(上面两个需求看似可以用同样的解决方案,实际第一个需求很简单,第二个需求如果用读扩散的方式计算会非常耗时)
回答思路:对于第一个问题可以将关注关系用以下的key表示:uid1_uid2,存在这个key则表示uid1有关注uid2。有了这个模型后,可以使用KV存储关注关系对;
对于第二个问题,一次检索可以有几十个结果(假设为20个),如果当前用户有500个好友(关注的人),则至少需要进行20*500=1w次判断,如果用户有5000个好友,则为10w次判断,使用KV就达不到要求了,这样KV压力会非常大。可以使用布隆过滤器,然后将布隆过滤器放在机器内存中,单机存不下的可以对布隆过滤器做分片,这样在内存中判断,并且使用上线程池分发判断任务,速度就非常快了。通常一个人能关注的账号数量是有限的,正常来说是5000多个,粉丝数没有限制。
即便关注人数真的不设限,我们也可以认为关注数量高的账号很少,因此可以针对这批账号做特殊处理,细节思路就不展开了。
另外是,在微信的公众号与普通用户之间的关注关系对大概为400亿,微博的关注关系量级应该是和这个差不多的,所以布隆过滤器预计单机也能存下来(假设是千分之5的错误率,哈希次数为8的情况下)
【Answer】
1. 写入系统:设计写入服务,帖子id分配策略(采用全局分配器),如何解决qps突增等情况,写入qps较高时,通过分片等方式提升写入效率,当然目前也可以采用写入效率较高的分布式存储系统;(3) 2. 索引构建:由于要支持timeline或者其他的排序方式,故需要单独的排序策略,这里单独针对timeline排序详细说明,timeline按照发表者时间顺序,一般有3中处理方案:1)拉的方式,按照发布者建立时间索引,需要考虑关注人数较多时,获取索引效率问题;2)推模式:发布的时通过异步消息队列,将消息投递到各个阅读者,此处主要问题推的效率和存储空间的开销;3)混合模式:粉丝较多用户采用拉模式,在线用户直接投递等提高查询效率;(3+~3.5) 3. 2中需要考虑索引数据结构设计,排序cursor值选取问题;(3.5)(细节考察) 4. 如果是热点排序:全局热点比较简单,可以直接维护一个热度计算模块,接入热度事件计算流,如果事件较多,则可以使用分布式实时计算框架提高计算效率,对于热度超过一定阈值的进入到热度排序系统中,按照从高到底排序即可(热度排序系统按照一定量截断多余数据);如果热度支持不同时间段分别组织;其次如果按照话题拆分呢?又如何设置热度排序系统?(扩展,如果回答细节都ok,可以得4分)
【Answer】
``` SELECT DATE_FORMAT(p_date, "%Y-%m-%d %H" ), count(*) FROM video.cdn_quality_log group by DATE_FORMAT(p_date, "%Y-%m-%d %H" ) ; ```
Redis相比memcached有哪些优势?
【Answer】
1.memcached所有的值均是简单的字符串,Redis作为其替代者,支持更为丰富的数据类型
2.Redis的速度比memcached快很多
3.Redis可以持久化其数据
4.Redis支持数据的备份,即master-slave模式的数据备份。
【Answer】
业务模型: 有多种解法,每种有各自优缺点: 1 记录任意两个车站的余票数量,需要维护Cn2共105个车站对的余票信息。该方案查询效率非常高,更新时有放大效应,需要更新多个车站对的余票信息。 2 记录每一张席位的售卖记录,查询时遍历每个席位,算出余票信息。该方案更新效率较高,查询时需要遍历。 3 将全程分解为原子区间,记录每个区间的可用车票数目。购买时只要起始站和终点站之间的原子区间有票,那可以出一张票。该方案查询和更新效率都很高,但不能保证买到的是同一个席位。更适合用来模糊查询。 在不考虑性能的情况下,方案可以满足查询和购买的基本需求,3分 性能上满足要求,并给出准确的分析,3.5分 架构: 1 技术选型可以实现需求,对技术栈性能和特性都比较清楚,3分 2 能够对系统瓶颈做出分析,设计比较系统的容灾方案,3.5分
功能:
显示某区服所有玩家的战力排行情况,以及我自己的战力排行情况。

【Answer】
0) 优秀的候选人会明确,用户量级(1W,1000W,还是1亿),战力力度(100分、1w分还是更细)。
1) 算法层面,桶排序、跳表等
2) 多种实现方法,数据存储、更新的机制,分布式相关,Redis/MySQL
3) 用户体验相关(排行榜更新频率、自己排名的准确性、得分相同的时候如何排名、有人频繁操作装备导致排行榜变动太快),看决策思路,是否需要全服排序等。
4) 追问:如果排名100W之后的玩家想看自己前后的数据,如何设计API等。
【Answer】
内部维护一个链表, list, 其元素为一个三元组(ID, timestamp, obj), 分别为对象ID, 上次被访问时间, 和对象内容; 在维护该list时, 需要保持一个性质, 越靠后的元素越新, 既timestamp越大; 内部再维护一个map, 该map表示一个ID到list节点的索引, 格式为map(ID, node); 对于get(id)操作: 1: 先在map中查找ID对应的list node; 2: 将node从list中取出, 即list.Remove(node); 3: 检查node.timestamp, 如果过期, 则返回null, 表示无数据, 并将ID从map中删除; 4: 如果未过期, 设置node.timestamp = now(), 并将node添加到list尾部, 即list.Append(node); 5: 返回node.obj; 对于set(id, obj)操作: 1: 同get(id)的1~3步操作, 删除对应的ID; 2: 如果此时空间满了, 既对象数为n, 则将list中表头的那个元素删除; 3: 更新list和map: node = new(ID, now(), obj), list.Append(node), map[ID] = node;
【Answer】
1. 接口设计:给出满足需求的接口设计(3分); 2. 业务逻辑:支持给出4中功能的系统实现方法 (3分); 3. 存储扩展性:数据库分库分表,或者使用kv系统单独提供均可,如果在kv系统中能够想到value的优化方案可适当加分;(3) 4. 优化: 关注数较多情况下,如何提高查询效率;(3分)
【Answer】
过期策略(redis key过期时间)1、定时过期,每个key都创建一个定时器,到期清除,内存友好,cpu不友好2、惰性过期,使用时才判断是否过期,内存不友好,cpu友好3、定期过期,隔一段时间扫描一部分key,并清除已经过期的key内存淘汰策略Redis缓存内存不足,如何处理新写入的数据1、直接报错2、lru,最近最少使用key3、随机4、过期lru,设置了过期时间里的keys lru5、过期随机6、当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
【Answer】
@谢蒙 补充 1. 任务提交管理(3分) 2. 定时器; 3. 分布式的任务调度; 4. 失败容错机制;
【Answer】
3 分: 1. 能够想到类似 diff 工具,出现同一行冲突时由用户来解决,这样能避免自动合并有可能出错等情况。 2. 采用长连接方式传递修改的 diff, 服务端转换之后传递 diff 到客户端。 3. 针对统一文档的的所有用户 hash 到同一台机器。 3.5 分: 1. 能够采用 Myer’s diff / Operational Transformation 等算法。 2. 传递 diff, 如果有多机处理统一文档的需求,需要保证操作的一致性。
【Answer】
主要考察联合索引、最左匹配几个知识点。1)最左原则指的就是如果你的 SQL 语句中用到了联合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个联合索引去进行匹配,值得注意的是,当遇到范围查询(>、<、between、like)就会停止匹配。2)Mysql有优化器会自动调整a,b的顺序与索引顺序一致,比如对(a,b)字段建立联合索引,查询条件是b=xx and a=xx,优化器会自动调整查询顺序以匹配索引对(b,a)建立索引。如果你建立的是(a,b)索引,那么只有a字段能用得上索引,毕竟最左匹配原则遇到范围查询就停止匹配。如果对(b,a)建立索引那么两个字段都能用上,优化器会帮我们调整where后a,b的顺序,让我们用上索引。
【Answer】
大体思路是,从最高位向最低位构造目标数,用 A 中尽量大的元素(但要小于等于 n 的相应位数字)。一旦目标数中有一位数字小于 n 相应位的数字,剩余低位可用 A 中最大元素填充。 注意: 1. 可能构造出等于 n 的数,需判断后重新构造; 2. 若 A 中没有小于等于 n 最高位数字的元素,则可直接用 A 中最大元素填充低位; 3. 有无解情形。 ## 一些边界情况: ``` n A 结果 23333 {2,3} 3333 23333 {3} 3333 22222 {2} 2222 22222 {2,3} 3333 2 {2} 无解 ```
在缓存系统中,想要知道过去一段时间被访问次数最多的key。
比如最近5分钟最热的key。
key的数量可能非常大。
【Answer】
基本上,这是一个排序问题,并且通过滑动窗口对key的TTL进行控制。
第一步:
比如想要知道5分钟内访问次数最多的key,可以对每个key进行计数(+1)和排序,然后在TTL5分钟之后计数(-1)进行排序,就可以得到5分钟内的精确的计数。
最快的实现方式:
用redis的zset
第二步:
因为缓存系统,qps非常高(假设100kqps),想要知道过去1个小时的热key,要怎么做。
考察点:
- qps高,需要对zset进行分片,
- 如果用1小时的滑动窗口,ttl需要保存非常大的key列表。对滑动窗口进行切割,只保留topk
其他:
有一种计数方式是 logarithmic access frequency counter 。在缓存中常见,比如redis。在LFU算法中使用。如果结合LFU,然后对TopK的key进行qps统计,也可以。
在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作。在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象。但是当我们对数据库进行了分库分表后,就不能依赖于每个表的自增ID来全局唯一标识这些数据了。因此,我们需要提供一个全局唯一的ID号生成策略来支持分库分表的环境。
针对校招的同学,可以问如何设计学校中学生系统学籍的ID,如果是全国系统如何设计ID
【Answer】
1、数据库自增ID——来自Flicker的解决方案
因为MySQL本身支持auto_increment操作,很自然地,我们会想到借助这个特性来实现这个功能。Flicker在解决全局ID生成方案里就采用了MySQL自增长ID的机制(auto_increment + replace into + MyISAM)。
先创建单独的数据库(eg:ticket),然后创建一个表:
CREATE TABLE Tickets64 (
id bigint(20) unsigned NOT NULL auto_increment,
stub char(1) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY stub (stub)
) ENGINE=MyISAM
当我们插入记录后,执行
SELECT * from Tickets64
,查询结果就是这样的:
+-------------------+------+
| id | stub |
+-------------------+------+
| 72157623227190423 | a |
+-------------------+------+
在我们的应用端需要做下面这两个操作,在一个事务会话里提交
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
这样我们就能拿到不断增长且不重复的ID了。
从高可用角度考虑,接下来就要解决单点故障问题:Flicker启用了两台数据库服务器来生成ID,通过区分auto_increment的起始值和步长来生成奇偶数的ID。
最后,在客户端只需要通过轮询方式取ID就可以了。
- 优点:充分借助数据库的自增ID机制,提供高可靠性,生成的ID有序。
- 缺点:占用两个独立的MySQL实例,有些浪费资源,成本较高。
2、独立的应用程序——来自Twitter的解决方案
Twitter在把存储系统从MySQL迁移到Cassandra的过程中由于Cassandra没有顺序ID生成机制,于是自己开发了一套全局唯一ID生成服务:Snowflake。GitHub地址:https://github.com/twitter/snowflake。根据twitter的业务需求,snowflake系统生成64位的ID。由3部分组成:
41位的时间序列(精确到毫秒,41位的长度可以使用69年)
10位的机器标识(10位的长度最多支持部署1024个节点)
12位的计数顺序号(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
最高位是符号位,始终为0。
- 优点:高性能,低延迟;独立的应用;按时间有序。
- 缺点:需要独立的开发和部署。
如果是学校学生ID:
1、入学年份+专业代码+入学考试分数排名
全国系统:
1、使用学生身份证作为id
2、入学年份+学校代码+专业/班级代码+输入分数排名
或其他方法,只要能叙述清楚这样设计的原因,重点是是否有思路
【Answer】
采用hash表存储user关注列表和user发布的消息列表 因为要去最近8条新消息,所以每条消息要带上时间戳 再get方法时候,拿到该用户所有关注者的message后根据时间戳做堆排序 ``` python class WeiTouTiao(object): def __init__(self): self.subscribe = collections.defaultdict(set) self.message = collections.defaultdict(list) self.time = -1 def post(self, user_id, message_id): self.message[user_id].append((self.time, message_id)) self.time -= 1 def get(self, user_id): tmp = copy.copy(self.message[user_id]) for user in self.subscribe[user_id]: tmp.extend(copy.copy(self.message[user_id])) heapq.heapify(tmp) res = [] while len(res) < 8 and tmp: res.append(heapq.heappop(tmp)[1]) return res def follow(self, user1, user2): if user1 != user2: self.subscribe[user1].add(user2) def unfollow(self, user1, user2): if user1 in self.subscribe and user2 in self.subscribe[user1]: self.subscribe[user1].remove(user2) ```
【Answer】
出题的本意是各种水平的人都能答,只要用过ftp或者迅雷的人就能有sense。即使应届生也能有思路。 1 最基本解法,需要考虑分级分发,且有基本的容错性考虑(重试,校验之类的,针对网络故障服务器宕机等常见场景的策略) 2 可考虑p2p的方式,进一步的可通过优化p2p分发策略来提升效率 3 考虑系统单点消除,横向扩展,可维护性提升,监控,追查问题效率等要素 4 开放式提问:跨机房怎么办,文件怎么存储,高性能单机服务器的设计,文件传输协议设计
如下代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
printf("line-1\n");
write(STDOUT_FILENO, "line-2\n", 7);
if(fork() == -1){
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
return 0;
}
执行结果如下:
yinhongbo@n8-125-036:~$ ./test
line-1
line-2
yinhongbo@n8-125-036:~$ ./test > test.stdout; cat test.stdout
line-2
line-1
line-1
问题:
1 有重定向和没有重定向下line-1和line-2的输出顺序为什么不同?
2 在有重定向情况下line-1为什么输出了2次
3 有几种方法能避免重复输出,列举几种并说明原因。
【Answer】
1 printf是在用户空间内存维护的缓冲区,缺省输出到终端时是行级缓冲,当重定向到文件时缺省为块级缓冲,printf在重定向后缓冲还在用户空间,write是在内核空间内存维护的缓冲区。因此在有重定向时会write会先于printf输出。
2 原因同上,printf是用户空间的内存中,fork是会复制,当exit时父子进程的各自的缓冲区都有printf的的内容,因此printf的内容会输出2次。
3 有比较多的方法。列举三种:
a) setbuf(stdout, NULL) 关闭stdout的流缓冲功能,但顺序write会先于printf
b) fork前调用fflush(stdout)
c) 子进程使用_exit(status)退出,以便不刷新stdio缓冲区
【Answer】
1. 可以引入某个流式处理框架,如Storm/SparkStreaming/Flink等,利用框架提供的join功能实现基本需求;或者自行设计分布式实现,能说清楚hash和join策略; 3分 2. 在头条的数据规模下(按消息qps100w,单条1k,即每秒1GB输入),能对占用的资源做出预估,需要的cpu/内存/外部存储等;3+ 3. 能考虑到上游数据的消息到达速度不一致,能处理Impression来的更晚的情况;3.5 4. 能考虑到数据一致性,如何在流式job部分或整体挂掉的情况下,保证数据正确性;3.5
【Answer】
1、Serializable(串行化):一个事务在执行过程中完全看不到其他事物对数据库所做的更新(事务执行的时候不允许别的事务并发执行,事务只能一个接着一个地执行,而不能并发执行)。 2、Repeatable Read(可重复读):一个事务在执行过程中可以看到其它事务已经提交的新插入的记录,但是不能看到其它事务对已有记录的更新。 3、Read Commited(读已提交数据):一个事务在执行过程中可以看到其它事务已经提交的新插入的记录,而且能看到其它事务已经提交的对已有记录的更新。 4、Read Uncommitted(读未提交数据):一个事务在执行过程中可以看到其它事务没有提交的新插入的记录的更新,而且能看其它事务没有提交到对已有记录的更新。
【Answer】
需要考虑过期时间、value的设置、超时锁被释放导致的数据更新覆盖的相关问题。
【Answer】
浏览器查询 DNS,获取域名对应的IP地址:具体过程包括浏览器搜索自身的DNS缓存、搜索操作系统的DNS缓存、读取本地的Host文件和向本地DNS服务器进行查询等。对于向本地DNS服务器进行查询,如果要查询的域名包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析(此解析具有权威性);如果要查询的域名不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析(此解析不具有权威性)。如果本地域名服务器并未缓存该网址映射关系,那么将根据其设置发起递归查询或者迭代查询; 浏览器获得域名对应的IP地址以后,浏览器向服务器请求建立链接,发起三次握手; TCP/IP链接建立起来后,浏览器向服务器发送HTTP请求; 服务器接收到这个请求,并根据路径参数映射到特定的请求处理器进行处理,并将处理结果及相应的视图返回给浏览器; 浏览器解析并渲染视图,若遇到对js文件、css文件及图片等静态资源的引用,则重复上述步骤并向服务器请求这些资源; 浏览器根据其请求到的资源、数据渲染页面,最终向用户呈现一个完整的页面。
启动3个线程,线程1无限循环打印1、线程2无限循环打印2、线程3无限循环打印3,要求按123123…顺序循环打印
【Answer】
方法一:利用ReentrantLock的多Condition实现线程间通信
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class NumberPrint1 { public static void main(String[] args) throws Exception { ReentrantLock lock = new ReentrantLock(); Condition c1 = lock.newCondition(); Condition c2 = lock.newCondition(); Condition c3 = lock.newCondition(); new Thread(new PrintTask(lock, c1, c2, ";1";)).start(); new Thread(new PrintTask(lock, c2, c3, ";2";)).start(); new Thread(new PrintTask(lock, c3, c1, ";3";)).start(); try { lock.lock(); c1.signal(); } finally { lock.unlock(); } Thread.sleep(Integer.MAX_VALUE); } static class PrintTask implements Runnable { private ReentrantLock lock; private Condition condition; private Condition conditionNext; private String str; public PrintTask(ReentrantLock lock, Condition condition, Condition conditionNext, String str) { this.lock = lock; this.condition = condition; this.conditionNext = conditionNext; this.str = str; } @Override public void run() { while(true) { try { lock.lock(); condition.await(); System.out.println(str); conditionNext.signal(); } catch (Exception e) { e.printStackTrace(); } } } } }
方法二:利用volatile关键字实现线程间通信
public class NumberPrint2 { static volatile int flag = 1; public static void main(String[] args) throws Exception { new Thread(new PrintTask(";1";, 1, 2)).start(); new Thread(new PrintTask(";2";, 2, 3)).start(); new Thread(new PrintTask(";3";, 3, 1)).start(); Thread.sleep(Integer.MAX_VALUE); } static class PrintTask
implements Runnable { private String str; private int curr; private int next; public PrintTask(String str, int curr, int next){ this.str = str; this.curr = curr; this.next = next; } @Override public void run() { while(true){ if(flag == curr){ System.out.println(str); flag = next; } } } } }</code></pre>
方法三:利用锁的wait和notify机制实现线程间通信
public class NumberPrint3 { static Object o1 = new Object(); static Object o2 = new Object(); static Object o3 = new Object(); public static void main(String[] args) throws Exception { new Thread(new PrintTask(";1";, o1, o2)).start(); new Thread(new PrintTask(";2";, o2, o3)).start(); new Thread(new PrintTask(";3";, o3, o1)).start(); synchronized (o1) { o1.notify(); } Thread.sleep(Integer.MAX_VALUE); } static class PrintTask implements Runnable { private Object curr; private Object next; private String str; public PrintTask(String str, Object curr, Object next){ this.str = str; this.curr = curr; this.next = next; } @Override public void run() { while(true){ synchronized (curr) { try { curr.wait(); System.out.println(str); synchronized (next) { next.notify(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
方法四:利用cas机制实现线程间通信
import java.util.concurrent.atomic.AtomicInteger; public class NumberPrint4 { static AtomicInteger flag = new AtomicInteger(1); public static void main(String[] args) throws Exception { new Thread(new PrintTask(";1";, 1, 2)).start(); new Thread(new PrintTask(";2";, 2, 3)).start(); new Thread(new PrintTask(";3";, 3, 1)).start(); Thread.sleep(Integer.MAX_VALUE); } static class PrintTask<t> implements Runnable { private String str; private int curr; private int next; public PrintTask(String str, int curr, int next){ this.str = str; this.curr = curr; this.next = next; } @Override public void run() { while(true){ if(flag.get() == curr){ System.out.println(str); flag.set(next); } } } } }
</pre> </details> --- ### 138. 乐观锁与悲观锁的区别 【Question】
--- ### 139. 客户端配置系统设计 【Question】 头条app客户端做了比较多feature,为了对这些功能做评估或者动态控制,需要支持云控ab测试方案,故需要设计一个在线动态配置管理系统设计,该系统要求能比较便捷进行参数动态修改和ab测试,请设计该系统满足该需求,需要考虑动态配置项比较多和易用性。【Answer】
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。[1] 悲观锁假定其他用户企图访问或者改变你正在访问、更改的对象的概率是很高的,因此在悲观锁的环境中,在你开始改变此对象之前就将该对象锁住,并且直到你提交了所作的更改之后才释放锁。悲观的缺陷是不论是页锁还是行锁,加锁的时间可能会很长,这样可能会长时间的限制其他用户的访问,也就是说悲观锁的并发访问性不好。 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。[1] 乐观锁不能解决脏读的问题。 乐观锁则认为其他用户企图改变你正在更改的对象的概率是很小的,因此乐观锁直到你准备提交所作的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁可以用较大的锁粒度获得较好的并发访问性能。但是如果第二个用户恰好在第一个用户提交更改之前读取了该对象,那么当他完成了自己的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不重新读取该对象并作出更改。这说明在乐观锁环境中,会增加并发用户读取对象的次数。 从数据库厂商的角度看,使用乐观的页锁是比较好的,尤其在影响很多行的批量操作中可以放比较少的锁,从而降低对资源的需求提高数据库的性能。再考虑聚集索引。在数据库中记录是按照聚集索引的物理顺序存放的。如果使用页锁,当两个用户同时访问更改位于同一数据页上的相邻两行时,其中一个用户必须等待另一个用户释放锁,这会明显地降低系统的性能。interbase和大多数关系数据库一样,采用的是乐观锁,而且读锁是共享的,写锁是排他的。可以在一个读锁上再放置读锁,但不能再放置写锁;你不能在写锁上再放置任何锁。锁是目前解决多用户并发访问的有效手段。 [乐观锁与悲观锁的区别](http://www.cnblogs.com/Bob-FD/p/3352216.html)--- ### 140. 推进用户历史系统设计 【Question】 推荐系统架构中经常需要保存用户历史,用于数据去重和回放功能,请设计这样一个系统支持快速查询用户一段时间内历史数据,要求支持高并发查询和写入量,其次考虑数据量扩展问题。 主要考察候选人对存储选型,业务数据模型设计的灵活性;【Answer】
1、能清楚的画出整个架构图,基于MySQL做存储,有可视化操作后台直接操作MySQL,API层解析MySQL数据下发数据。3.0 2、API层请求量会很大,考虑到请求MySQL时需要做缓存并给合理的缓存方案(本地缓存即可)。3.0+ 3、配置需求各式各样,如何抽象配置是该系统的关键。和Lua或Python等脚本语言来描述配置比较合适。3.5--- ### 141. 并发、并行、异步的区别? 【Question】【Answer】
1. 历史数据结构设计,可直接利用分布式kv存储系统,按时间对用户的历史分别存储;(3分) 2. 由于历史数据量比较大,可以对历史value可以提前序列化并压缩,牺牲部分cpu降低对存储的需求;(3.5分)--- ### 142. 在分布式环境下,如何防止RocketMQ消息重复消费? 【Question】【Answer】
并发:在一个时间段中同时有多个程序在运行,但其实任一时刻,只有一个程序在CPU上运行,宏观上的并发是通过不断的切换实现的; 多线程:并发运行的一段代码。是实现异步的手段 并行(和串行相比):在多CPU系统中,多个程序无论宏观还是微观上都是同时执行的 异步(和同步相比):同步是顺序执行,异步是在等待某个资源的时候继续做自己的事--- ### 143. 爬虫url去重 【Question】 用爬虫抓取网页时, 一个较为重要的问题, 就是对爬去的网页去重; 请你详细的设计一种数据结构, 用来检验某个URL之前是否已经被爬取过; 并给出每次检验的复杂度, 以及整体的空间复杂度;【Answer】
消费方可以基于分布式锁来解决rocketmq的消息幂等性的问题。用分布式锁可以从纯技术角度messageid,或者业务角度业务主键唯一性都可以实现rocketmq消息消费的幂等性。另外,rocketmq生产方发送消息,本身就保证了消息的幂等性,主要是消费方消费消息要保证幂等性。--- ### 144. 使用B树和B+树的比较 【Question】【Answer】
一般应该会说hash表, 要求他说出hash表详细的设计方案, 比如hash函数怎么设计, 存储数据用什么数据结构; 时间复杂度应该是, O(len(URL)) + O(insert); 前部分表示hash函数的复杂度, 后部分表示将hash值插入到表的复杂度; 后部分通常来说应该是O(1); 如果底层存储用平衡树之类的结构比较坏的可能会到O(logn); 空间复杂度应该是O(n*len(URL)); 更好一点的方法为用字典树, 每次拿到URL都将其插入到该字典树中, 时间复杂度应该是O(len(URL)); 空间复杂度应该小于O(n*len(URL)); 当面对海量数据时, 上述两种方法可能会空间瓶颈; 解决空间复杂度的一个办法是用布隆过滤器;--- ### 145. 实现一个RPC 调度机制 【Question】【Answer】
InnoDB的索引使用的是B+树实现,B+树对比B树的好处: IO次数少:B+树的中间结点只存放索引,数据都存在叶结点中,因此中间结点可以存更多的数据,让索引树更加矮胖; 范围查询效率更高:B树需要中序遍历整个树,只B+树需要遍历叶结点中的链表; 查询效率更加稳定:每次查询都需要从根结点到叶结点,路径长度相同,所以每次查询的效率都差不多假设在一个系统中有A模块和B模块,A模块 需要调用 B模块,A模块会部署在多台机器上,记为A1、A2、A3...,B 模块也会部署在多台机器上,记为 B1、B2、B3...,请设计一个调度的机制,需要满足一下几个需求,
1,A 模块服务启动后,能自动获取并感知部署了B 模块的所有可用机器
2,A 模块请求 B模块的时候,如果B 模块返回了失败,能进行重试,请设计重试机制
3,部署B 模块的机器如果出现异常或者B模块服务出现异常,A模块的机器可自动感知并将异常机器摘除不再请求
--- ### 146. 爬虫种子调度频度设计 【Question】 爬虫一般通过种子页面发现新链接,不断访问种子页,就能发现新的链接。 但不是每次访问种子页,都会提取到新链接,有可能短时间内种子页面并不会变化。 频繁抓取种子页面,即造成资源浪费,又对被抓取站点造成压力,甚至引起封禁。 那么如何设定种子调度频度,才能既不被封,又能及时拿到新内容呢?【Answer】
服务发现 + A模块本地维护黑白名单,失败后拉近黑名单,如果能考虑到探活更好
--- ### 147. 什么是僵尸进程? 【Question】【Answer】
根据具体业务场景、规模大小,可能存在多个不同阶段: 一、人工设定调度间隔,根据人的观察和经验去设定,这个方法在量少的时候还比较容易做 1. 如果是大量抓取,是不现实的,而且人工标也无法考虑是否合适 2. 另外任意的调度间隔,基本只能靠不断轮训扫表实现调度 二、 设定档位,人工标注档位 从1分钟、2分钟、3分钟……1周设定若干档位,将调度周期标准化后,利于整体调度 标注人员也更好统一标准,且利于统计 三、程序自动调整调度周期 建立评估指标,通过程序统计计算来推定调度周期。 指标可以有: 1. 新内容页数/种子抓取次数:该比值越高,种子调度ROI越过,用少量的种子调度次数抓到了更多的内容页 2. 新页发现delay时间:假如所有种子都1天调度一次,肯定指标1会不错,但这样新内容无法尽快被抓取,所以要考虑新页面出现到被抓取到的delay时间 通过上述指标,和历史每次抓取log来分析,做种子调度档位调整。 四、一般爬虫的种子调度周期是固定,但其实当地时间的夜间,一般不会有太多新内容,是可以有区别与白天的调度周期的。 更进一步,一般互联网内容,周期性比较明显,可以做周级别的调度频度分析控制。 延伸问题: Q:如何避免大量种子? A:同一调度周期的hash打平;更进一步还可以做下同一站点内的打平 Q:种子数量筛选,抓到同样的内容页,可能10万种子就够了,但库里有100万,怎么筛选? A:可以根据发现内容页的快慢、站点质量、站点权威性等多方面考虑--- ### 148. 按时限流反抓取系统设计 【Question】 设计一个按时限流的反抓取系统,支持指定多个时间段用户频率控制,要求给出数据结构设计和考虑性能问题【Answer】
一个子进程结束后,它的父进程并没有等待它(调用wait或者waitpid),那么这个子进程将成为一个僵尸进程。僵尸进程是一个已经死亡的进程,但是并没有真正被销毁。它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程表中保留一个位置,记载该进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等)供父进程收集,除此之外,僵尸进程不再占有任何内存空间。这个僵尸进程可能会一直留在系统中直到系统重启。 危害:占用进程号,而系统所能使用的进程号是有限的;占用内存。 以下情况不会产生僵尸进程: 该进程的父进程先结束了。每个进程结束的时候,系统都会扫描是否存在子进程,如果有则用Init进程接管,成为该进程的父进程,并且会调用wait等待其结束。 父进程调用wait或者waitpid等待子进程结束(需要每隔一段时间查询子进程是否结束)。wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。waitpid则可以加入WNOHANG(wait-no-hang)选项,如果没有发现结束的子进程,就会立即返回,不会将调用waitpid的进程阻塞。同时,waitpid还可以选择是等待任一子进程(同wait),还是等待指定pid的子进程,还是等待同一进程组下的任一子进程,还是等待组ID等于pid的任一子进程; 子进程结束时,系统会产生SIGCHLD(signal-child)信号,可以注册一个信号处理函数,在该函数中调用waitpid,等待所有结束的子进程(注意:一般都需要循环调用waitpid,因为在信号处理函数开始执行之前,可能已经有多个子进程结束了,而信号处理函数只执行一次,所以要循环调用将所有结束的子进程回收); 也可以用signal(SIGCLD, SIG_IGN)(signal-ignore)通知内核,表示忽略SIGCHLD信号,那么子进程结束后,内核会进行回收。--- ### 149. MySQL的隔离级别有哪些 【Question】【Answer】
1. 数据模型设计:按照时间分段对用户的访问请求进行统计:(3) 1)统一维护最细粒度的统计数据,然后根据时间段要求求和的方式; 2)根据业务配置规则,按照规则生成对应时间段统计,该方式简化最后的求和步骤,但是同一个业务如果有多个规则会导致存储一定开销; 3)设计具体的数据结构,评估需要的存储开销,如何进行简化; 2. 发抓取系统很多时候是通过较多规则进行控制,如不同的频率控制(粗粒度,细粒度),多维度(IP,用户指定等),设计该系统时候,需要设计好规则的表示方式(待补充)(3.5)MySQL的隔离级别有哪些,请简单描述一下--- ### 150. 小视频上传逻辑-系统设计 【Question】【Answer】
读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。读提交:一个事务提交之后,它做的变更才会被其他事务看到。可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。(数据校对)串行化:顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行MySQL默认隔离级别是可重复读,Oracle默认隔离级别是读提交下面是我们在抖音上经常会看到的小视频截图:
我们平时作为用户在抖音上看到的小视频,实际都是用户上传的小视频,因此每天都有几千万的小视频上传到服务器端。为了保证在移动弱网环境下,能够获得更高的传输效率,我们会使用一些传输策略。
以下是问题:
- 如果减少服务重启过程的影响,从部署和架构上需要做哪些事情?
- 如果分片文件大小很分散该如何优化?
- 还有哪些更好的实现,可以尽量减少磁盘缓存的使用,同时还能和后端存储很好结合起来?
- 描述下常见的对象存储系统架构
--- ### 151. 谈谈你对聚集索引的理解 【Question】【Answer】
- 如果减少服务重启过程的影响,从部署和架构上需要做哪些事情?
- 增加无状态代理层,平滑重启。
- 如果分片文件大小很分散该如何优化?
- 考察对磁盘数据管理的知识
- 还有哪些更好的实现,可以尽量减少磁盘缓存的使用,同时还能和后端存储很好结合起来?
- 滑动窗口+后端分片存储
- 描述下常见的对象存储系统架构
--- ### 152. 在线答题系统-系统设计 【Question】【Answer】
聚集索引:该索引中键值的逻辑顺序决定了表中相应行的物理顺序。 聚集索引确定表中数据的物理顺序。聚集索引类似于电话簿,后者按姓氏排列数据。由于聚集索引规定数据在表中的物理存储顺序,因此一个表只能包含一个聚集索引。但该索引可以包含多个列(组合索引),就像电话簿按姓氏和名字进行组织一样。 聚集索引使用注意事项 定义聚集索引键时使用的列越少越好。 • 包含大量非重复值的列。 .• 使用下列运算符返回一个范围值的查询:BETWEEN、>、>=、< 和 <=。 • 被连续访问的列。 • 回大型结果集的查询。 • 经常被使用联接或 GROUP BY 子句的查询访问的列;一般来说,这些是外键列。对 ORDER BY 或 GROUP BY 子句中指定的列进行索引,可以使 SQL Server 不必对数据进行排序,因为这些行已经排序。这样可以提高查询性能。 • OLTP 类型的应用程序,这些程序要求进行非常快速的单行查找(一般通过主键)。应在主键上创建聚集索引。 聚集索引不适用于: • 频繁更改的列 。这将导致整行移动(因为 SQL Server 必须按物理顺序保留行中的数据值)。这一点要特别注意,因为在大数据量事务处理系统中数据是易失的。 • 宽键 。来自聚集索引的键值由所有非聚集索引作为查找键使用,因此存储在每个非聚集索引的叶条目内。 非聚集索引:数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置。 非聚集索引中的项目按索引键值的顺序存储,而表中的信息按另一种顺序存储(这可以由聚集索引规定)。对于非聚集索引,可以为在表非聚集索引中查找数据时常用的每个列创建一个非聚集索引。有些书籍包含多个索引。例如,一本介绍园艺的书可能会包含一个植物通俗名称索引,和一个植物学名索引,因为这是读者查找信息的两种最常用的方法。 一个通俗的举例,说明两者的区别 其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。 如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。 第一:聚集索引的约束是唯一性,是否要求字段也是唯一的呢? 分析:如果认为是的朋友,可能是受系统默认设置的影响,一般我们指定一个表的主键,如果这个表之前没有聚集索引,同时建立主键时候没有强制指定使用非聚集索引,SQL会默认在此字段上创建一个聚集索引,而主键都是唯一的,所以理所当然的认为创建聚集索引的字段也需要唯一。 结论:聚集索引可以创建在任何一列你想创建的字段上,这是从理论上讲,实际情况并不能随便指定,否则在性能上会是恶梦。 第二:为什么聚集索引可以创建在任何一列上,如果此表没有主键约束,即有可能存在重复行数据呢? 粗一看,这还真是和聚集索引的约束相背,但实际情况真可以创建聚集索引。 分析其原因是:如果未使用 UNIQUE 属性创建聚集索引,数据库引擎将向表自动添加一个四字节 uniqueifier 列。必要时,数据库引擎 将向行自动添加一个 uniqueifier 值,使每个键唯一。此列和列值供内部使用,用户不能查看或访问。 第三:是不是聚集索引就一定要比非聚集索引性能优呢? 如果想查询学分在60-90之间的学生的学分以及姓名,在学分上创建聚集索引是否是最优的呢? 答:否。既然只输出两列,我们可以在学分以及学生姓名上创建联合非聚集索引,此时的索引就形成了覆盖索引,即索引所存储的内容就是最终输出的数据,这种索引在比以学分为聚集索引做查询性能更好。 第四:在数据库中通过什么描述聚集索引与非聚集索引的? 索引是通过二叉树的形式进行描述的,我们可以这样区分聚集与非聚集索引的区别:聚集索引的叶节点就是最终的数据节点,而非聚集索引的叶节仍然是索引节点,但它有一个指向最终数据的指针。 第五:在主键是创建聚集索引的表在数据插入上为什么比主键上创建非聚集索引表速度要慢? 有了上面第四点的认识,我们分析这个问题就有把握了,在有主键的表中插入数据行,由于有主键唯一性的约束,所以需要保证插入的数据没有重复。我们来比较下主键为聚集索引和非聚集索引的查找情况:聚集索引由于索引叶节点就是数据页,所以如果想检查主键的唯一性,需要遍历所有数据节点才行,但非聚集索引不同,由于非聚集索引上已经包含了主键值,所以查找主键唯一性,只需要遍历所有的索引页就行,这比遍历所有数据行减少了不少IO消耗。这就是为什么主键上创建非聚集索引比主键上创建聚集索引在插入数据时要快的真正原因。项目背景
春节期间,在线答题成为了一种比较风靡的移动端游戏新方式。
在线答题的基本逻辑是:app每隔30秒左右出一道选择题,用户有10秒左右的时间作答,答错一道即算失败。所有题目回答成功,则可以参与平分奖金。
增加需求
- 如果为了提升传播效应,增加了一种复活卡的功能,用户通过分享可以获得复活机会。 拥有复活机会的用户,在答题错误之后,可以使用复活机会并复活。 复活机会的次数支持一次或者多次。 回到刚才的代码,你需要怎么修改数据结构和逻辑,来支持这个需求呢?
【Answer】
参考:
- uesr_answer_history中的fail改成int型,保存用户的“答题机会”, 默认用户的答题机会是1, 如果有复活卡等,则可以在初始化用户信息的接口中,把答题机会初始化为1以上(具体取决于一场活动可以使用多少次复活卡) submit接口判断用户答题失败的时候,增加判断答题机会的逻辑, 如果答题失败,则答题机会减一,如果答题机会还没有减到0,则还可以继续答题。
【Answer】
多版本并发控制(Multi-Version Concurrency Control, MVCC),MVCC在每行记录后面都保存有两个隐藏的列,用来存储创建版本号和删除版本号。 创建版本号:创建一个数据行时的事务版本号(事务版本号:事务开始时的系统版本号;系统版本号:每开始一个新的事务,系统版本号就会自动递增); 删除版本号:删除操作时的事务版本号; 各种操作:插入操作时,记录创建版本号; 删除操作时,记录删除版本号; 更新操作时,先记录删除版本号,再新增一行记录创建版本号; 查询操作时,要符合以下条件才能被查询出来:删除版本号未定义或大于当前事务版本号(删除操作是在当前事务启动之后做的);创建版本号小于或等于当前事务版本号(创建操作是事务完成或者在事务启动之前完成) 通过版本号减少了锁的争用,提高了系统性能;可以实现提交读和可重复读两种隔离级别,未提交读无需使用MVCC
【Answer】
1、MyISAM它不支持事务,也不支持外键,尤其是访问速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用基本都可以使用这个引擎来创建表。 2、每个MyISAM在磁盘上存储成3个文件,其中文件名和表名都相同,但是扩展名分别为:.frm(存储表定义) ,YD(MYData,存储数据) , MYI(MYIndex,存储索引),InnoDB,InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比MyISAM的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。 3、memory使用存在内存中的内容来创建表。每个MEMORY表实际对应一个磁盘文件,格式是.frm。MEMORY类型的表访问非常快,因为它到数据是放在内存中的,并且默认使用HASH索引,但是一旦服务器关闭,表中的数据就会丢失,但表还会继续存在。 4、merge存储引擎是一组MyISAM表的组合,这些MyISAM表结构必须完全相同,MERGE表中并没有数据,对MERGE类型的表可以进行查询、更新、删除的操作,这些操作实际上是对内部的MyISAM表进行操作。
【Answer】
对称加密:加密和解密采用相同的密钥。如:DES、RC2、RC4 非对称加密:需要两个密钥:公钥和私钥。如果用公钥加密,需要用私钥才能解密。如:RSA 区别:对称加密速度更快,通常用于大量数据的加密;非对称加密安全性更高(不需要传送私钥)
【Answer】
客户端没有收到ACK确认,会重新发送FIN请求。
【Answer】
使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销: select * from table ...; 当前读读取的是最新的数据,需要加锁。以下第一个语句需要加 S 锁,其它都需要加 X 锁: select * from table where ? lock in share mode; select * from table where ? for update; insert; update; delete;
【Answer】
回表查询:先定位主键值,再定位行记录,它的性能较扫一遍索引树更低解决方法:将被查询的字段,建立到联合索引里,即实现了索引覆盖
【Answer】
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
【Answer】
Linux虚拟内存的实现需要6种机制的支持:地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制和内存共享机制 内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址。当用户程序运行时,如果发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。如果有空闲的内存可供分配,就请求分配内存(于是用到了内存的分配和回收),并把正在使用的物理页记录在缓存中(使用了缓存机制)。如果没有足够的内存可供分配,那么就调用交换机制;腾出一部分内存。另外,在地址映射中要通过TLB(翻译后援存储器)来寻找物理页;交换机制中也要用到交换缓存,并且把物理页内容交换到交换文件中,也要修改页表来映射文件地址。 进程和线程、进程间及线程通信方式、共享内存的使用实现原理
【Answer】
1. 针对table,row和field定义相应类型,field支持类型描述和数据类型转换功能。(3.5) 2. 如果没有抽象出相关类型,但是代码功能实现正确;(3分)
【Answer】
智能指针:实际指行为类似于指针的类对象 ,它的一种通用实现方法是采用引用计数的方法。 1.智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针。 2.每次创建类的新对象时,初始化指针并将引用计数置为1; 3.当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数; 4.对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;这是因为左侧的指针指向了右侧指针所指向的对象,因此右指针所指向的对象的引用计数+1; 5.调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。 6.实现智能指针有两种经典策略:一是引入辅助类,二是使用句柄类。这里主要讲一下引入辅助类的方法
【Answer】
端口不同:HTTP使用的是80端口,HTTPS使用443端口; HTTP(超文本传输协议)信息是明文传输,HTTPS运行在SSL(Secure Socket Layer)之上,添加了加密和认证机制,更加安全; HTTPS由于加密解密会带来更大的CPU和内存开销; HTTPS通信需要证书,一般需要向证书颁发机构(CA)购买
给定视频打分表A,包含视频id、视频主题和视频打分情况三列: video_id,subject,score,
求每个subject视频的score的中位数。
【Answer】
select a.subject,
a.score * (1 - b.float_part) + a.next_score * (b.float_part-0) as median
from
(
select
subject, score
row_number() over(partition by subject order by score asc) as rank,
lead(score,1) over(partition by subject order by score asc) as next_score
from A
) a
inner join
(
select
subject,
floor((count(score) + 1) / 2) as int_part,
(count(score)+1) / 2 % 1 as float_part
from A
group by subject
) b
on a.rank = b.int_part and a.subject=b.subject
group by a.subject
【Answer】
1、厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;2、单例模式:Bean默认为单例模式。3、代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;4、模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。5、观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
【Answer】
一个线程可以拥有多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。 线程进程都是同步机制,而协程则是异步 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
【Answer】
```Javascript // decorator function throttle(time: number = 1000) { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.value = throttleFunc(descriptor.value, time) return descriptor; } } // throttleFunc function throttleFunc(func: Function, time: number): Function { let timerId = null; let timeStamp = 0; let callCount = 0; let args = []; return function () { args = [].slice.call(arguments); const now = Date.now(); if (now - timeStamp > time) { func.apply(this, args); callCount = 0; timeStamp = now; } ++callCount; if (!timerId) { timerId = setTimeout(() => { if (callCount > 1) { func.apply(this, args); } callCount = 0; timerId = null; timeStamp = now; }, time); } } } ```
【Answer】
C++的多态分为静态多态(编译时多态)和动态多态(运行时多态)两大类。静态多态通过重载、模板来实现;动态多态就是通过本文的主角虚函数来体现的。 虚函数实现原理:包括虚函数表、虚函数指针等 虚函数的作用说白了就是:当调用一个虚函数时,被执行的代码必须和调用函数的对象的动态类型相一致。编译器需要做的就是如何高效的实现提供这种特性。不同编译器实现细节也不相同。大多数编译器通过vtbl(virtual table)和vptr(virtual table pointer)来实现的。 当一个类声明了虚函数或者继承了虚函数,这个类就会有自己的vtbl。vtbl实际上就是一个函数指针数组,有的编译器用的是链表,不过方法都是差不多。vtbl数组中的每一个元素对应一个函数指针指向该类的一个虚函数,同时该类的每一个对象都会包含一个vptr,vptr指向该vtbl的地址。
【Answer】
Volatile关键词的第一个特性:易变性。所谓的易变性,在汇编层面反映出来,就是两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是重新从内存中读取。 Volatile关键词的第二个特性:“不可优化”特性。volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行。 Volatile关键词的第三个特性:”顺序性”,能够保证Volatile变量间的顺序性,编译器不会进行乱序优化。 C/C++ Volatile变量,与非Volatile变量之间的操作,是可能被编译器交换顺序的。C/C++ Volatile变量间的操作,是不会被编译器交换顺序的。哪怕将所有的变量全部都声明为volatile,哪怕杜绝了编译器的乱序优化,但是针对生成的汇编代码,CPU有可能仍旧会乱序执行指令,导致程序依赖的逻辑出错,volatile对此无能为力 针对这个多线程的应用,真正正确的做法,是构建一个happens-before语义。 [C/C++ Volatile关键词深度剖析](http://hedengcheng.com/?p=725)
【Answer】
不可以。有两个原因: 首先,可能会出现已失效的连接请求报文段又传到了服务器端。 client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 server。本来这是一个早已失效的报文段。但 server 收到此失效的连接请求报文段后,就误认为是 client 再次发出的一个新的连接请求。于是就向 client 发出确认报文段,同意建立连接。假设不采用 “三次握手”,那么只要 server 发出确认,新的连接就建立了。由于现在 client 并没有发出建立连接的请求,因此不会理睬 server 的确认,也不会向 server 发送数据。但 server 却以为新的运输连接已经建立,并一直等待 client 发来数据。这样,server 的很多资源就白白浪费掉了。采用 “三次握手” 的办法可以防止上述现象发生。例如刚才那种情况,client 不会向 server 的确认发出确认。server 由于收不到确认,就知道 client 并没有要求建立连接。 其次,两次握手无法保证Client正确接收第二次握手的报文(Server无法确认Client是否收到),也无法保证Client和Server之间成功互换初始序列号。
【Answer】
控制变量的存储方式和可见性。 (1)修饰局部变量 一般情况下,对于局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了。但是如果用static进行修饰的话,该变量便存放在静态数据区,其生命周期一直持续到整个程序执行结束。但是在这里要注意的是,虽然用static对局部变量进行修饰过后,其生命周期以及存储空间发生了变化,但是其作用域并没有改变,其仍然是一个局部变量,作用域仅限于该语句块。 (2)修饰全局变量 对于一个全局变量,它既可以在本源文件中被访问到,也可以在同一个工程的其它源文件中被访问(只需用extern进行声明即可)。用static对全局变量进行修饰改变了其作用域的范围,由原来的整个工程可见变为本源文件可见。 (3)修饰函数 用static修饰函数的话,情况与修饰全局变量大同小异,就是改变了函数的作用域。 (4)C++中的static 如果在C++中对类中的某个函数用static进行修饰,则表示该函数属于一个类而不是属于此类的任何特定对象;如果对类中的某个变量进行static修饰,表示该变量为类以及其所有的对象所有。它们在存储空间中都只存在一个副本。可以通过类和对象去调用。
【Answer】
进程(Process)是系统进行资源分配和调度的基本单位,线程(Thread)是CPU调度和分派的基本单位; 线程依赖于进程而存在,一个进程至少有一个线程; 进程有自己的独立地址空间,线程共享所属进程的地址空间; 进程是拥有系统资源的一个独立单位,而线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),和其他线程共享本进程的相关资源如内存、I/O、cpu等; 在进程切换时,涉及到整个当前进程CPU环境的保存环境的设置以及新被调度运行的CPU环境的设置,而线程切换只需保存和设置少量的寄存器的内容,并不涉及存储器管理方面的操作,可见,进程切换的开销远大于线程切换的开销; 线程之间的通信更方便,同一进程下的线程共享全局变量等数据,而进程之间的通信需要以进程间通信(IPC)的方式进行; 多线程程序只要有一个线程崩溃,整个程序就崩溃了,但多进程程序中一个进程崩溃并不会对其它进程造成影响,因为进程有自己的独立地址空间,因此多进程更加健壮 进程操作代码实现,可以参考:多进程 - 廖雪峰的官方网站
【Answer】
动态链接是只建立一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在运行时再装入;而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要库了。
【Answer】
指针与地址的区别? 区别: 1指针意味着已经有一个指针变量存在,他的值是一个地址,指针变量本身也存放在一个长度为四个字节的地址当中,而地址概念本身并不代表有任何变量存在. 2 指针的值,如果没有限制,通常是可以变化的,也可以指向另外一个地址. 地址表示内存空间的一个位置点,他是用来赋给指针的,地址本身是没有大小概念,指针指向变量的大小,取决于地址后面存放的变量类型. 指针与数组名的关系? 其值都是一个地址,但前者是可以移动的,后者是不可变的. 指针和引用的区别(一般都会问到) 相同点:1. 都是地址的概念; 指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。 区别:1. 指针是一个实体,而引用仅是个别名; 2. 引用使用时无需解引用(*),指针需要解引用; 3. 引用只能在定义时被初始化一次,之后不可变;指针可变; 4. 引用没有 const,指针有 const; 5. 引用不能为空,指针可以为空; 6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小; 7. 指针和引用的自增(++)运算意义不一样; 8.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。
【Answer】
事务必须严格分为两个阶段对数据进行加锁和解锁的操作,第一阶段加锁,第二阶段解锁。也就是说一个事务中一旦释放了锁,就不能再申请新锁了。
【Answer】
为了限制不同程序的访问能力,防止一些程序访问其它程序的内存数据,CPU划分了用户态和内核态两个权限等级。 用户态只能受限地访问内存,且不允许访问外围设备,没有占用CPU的能力,CPU资源可以被其它程序获取; 内核态可以访问内存所有数据以及外围设备,也可以进行程序的切换。 所有用户程序都运行在用户态,但有时需要进行一些内核态的操作,比如从硬盘或者键盘读数据,这时就需要进行系统调用,使用陷阱指令,CPU切换到内核态,执行相应的服务,再切换为用户态并返回系统调用的结果。
【Answer】
守护进程最重要的特性是后台运行。 1. 在后台运行。 为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。 if(pid=fork()) exit(0); //是父进程,结束父进程,子进程继续 2. 脱离控制终端,登录会话和进程组 有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长: setsid(); 说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。 3. 禁止进程重新打开控制终端 现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端: if(pid=fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话组长) 4. 关闭打开的文件描述符 进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们: for(i=0;i 关闭打开的文件描述符close(i);> 5. 改变当前工作目录 进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 /tmpchdir("/") 6. 重设文件创建掩模 进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0); 7. 处理SIGCHLD信号 处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。 signal(SIGCHLD,SIG_IGN); 这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。
【Answer】
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 (2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 (3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
【Answer】
简单来说,关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。 非关系型数据库提出另一种理念,例如,以键值对存储,且结构不固定,每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,这 样就不会局限于固定的结构,可以减少一些时间和空间的开销。使用这种方式,用户可以根据需要去添加自己需要的字段,这样,为了获取用户的不同信息,不需要 像关系型数据库中,要对多表进行关联查询。仅需要根据id取出相应的value就可以完成查询。但非关系型数据库由于很少的约束,他也不能够提供像SQL 所提供的where这种对于字段属性值情况的查询。并且难以体现设计的完整性。他只适合存储一些较为简单的数据,对于需要进行较复杂查询的数据,SQL数 据库显的更为合适。 关系型数据库的最大特点就是事务的一致性:传统的关系型数据库读写操作都是事务的,具有ACID的特点,这个特性使得关系型数据库可以用于几乎所有对一致性有要求的系统中,如典型的银行系统。 但是,在网页应用中,尤其是SNS应用中,一致性却不是显得那么重要,用户A看到的内容和用户B看到同一用户C内容更新不一致是可以容忍的,或者 说,两个人看到同一好友的数据更新的时间差那么几秒是可以容忍的,因此,关系型数据库的最大特点在这里已经无用武之地,起码不是那么重要了。 相反地,关系型数据库为了维护一致性所付出的巨大代价就是其读写性能比较差,而像微博、facebook这类SNS的应用,对并发读写能力要求极 高,关系型数据库已经无法应付(在读方面,传统上为了克服关系型数据库缺陷,提高性能,都是增加一级memcache来静态化网页,而在SNS中,变化太 快,memchache已经无能为力了),因此,必须用新的一种数据结构存储来代替关系数据库。 关系数据库的另一个特点就是其具有固定的表结构,因此,其扩展性极差,而在SNS中,系统的升级,功能的增加,往往意味着数据结构巨大变动,这一点关系型数据库也难以应付,需要新的结构化数据存储。 于是,非关系型数据库应运而生,由于不可能用一种数据结构化存储应付所有的新的需求,因此,非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合。 必须强调的是,数据的持久存储,尤其是海量数据的持久存储,还是需要一种关系数据库这员老将。 非关系型数据库分类: 主要分为以下几类: 面向高性能并发读写的key-value数据库: key-value数据库的主要特点即使具有极高的并发读写性能,Redis,Tokyo Cabinet,Flare就是这类的代表 面向海量数据访问的面向文档数据库: 这类数据库的特点是,可以在海量的数据中快速的查询数据,典型代表为MongoDB以及CouchDB 面向可扩展性的分布式数据库: 这类数据库想解决的问题就是传统数据库存在可扩展性上的缺陷,这类数据库可以适应数据量的增加以及数据结构的变化
【Answer】
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0” virtual void funtion1()=0 原因: 1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。 2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。 为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。 定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。 纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。 [虚函数和纯虚函数的区别](http://blog.csdn.net/hackbuteer1/article/details/7558868)
【Answer】
管道(Pipe) 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道; 一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据; 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程) 命名管道 消息队列 信号(Signal) 共享内存 信号量(Semaphore):初始化操作、P操作、V操作;P操作:信号量-1,检测是否小于0,小于则进程进入阻塞状态;V操作:信号量+1,若小于等于0,则从队列中唤醒一个等待的进程进入就绪态 套接字(Socket) 更详细的可以参考(待整理): https://imageslr.github.io/2020/02/26/ipc.html https://www.jianshu.com/p/c1015f5ffa74
【Answer】
哈希索引能以 O(1) 时间进行查找,但是只支持精确查找,无法用于部分查找和范围查找,无法用于排序与分组;B树索引支持大于小于等于查找,范围查找。哈希索引遇到大量哈希值相等的情况后查找效率会降低。哈希索引不支持数据的排序。
【Answer】
它实际上是一个很长的二进制向量和一系列随机映射函数(Hash函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。Bloom Filter广泛的应用于各种需要查询的场合中,如Orocle的数据库,Google的BitTable也用了此技术。 Bloom Filter特点: 不存在漏报(False Negative),即某个元素在某个集合中,肯定能报出来。 可能存在误报(False Positive),即某个元素不在某个集合中,可能也被爆出来。 确定某个元素是否在某个集合中的代价和总的元素数目无关。
【Answer】
优点: 大大加快了数据的检索速度; 可以显著减少查询中分组和排序的时间; 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性; 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起) 缺点: 建立和维护索引耗费时间空间,更新索引很慢。
【Answer】
为什么需要线程同步:线程有时候会和其他线程共享一些资源,比如内存、数据库等。当多个线程同时读写同一份共享资源的时候,可能会发生冲突。因此需要线程的同步,多个线程按顺序访问资源。 互斥量 Mutex:互斥量是内核对象,只有拥有互斥对象的线程才有访问互斥资源的权限。因为互斥对象只有一个,所以可以保证互斥资源不会被多个线程同时访问;当前拥有互斥对象的线程处理完任务后必须将互斥对象交出,以便其他线程访问该资源; 信号量 Semaphore:信号量是内核对象,它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。信号量对象保存了最大资源计数和当前可用资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就减1,只要当前可用资源计数大于0,就可以发出信号量信号,如果为0,则将线程放入一个队列中等待。线程处理完共享资源后,应在离开的同时通过ReleaseSemaphore函数将当前可用资源数加1。如果信号量的取值只能为0或1,那么信号量就成为了互斥量; 事件 Event:允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。事件分为手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒一个等待中的线程,然后自动恢复为未激发状态。 临界区 Critical Section:任意时刻只允许一个线程对临界资源进行访问。拥有临界区对象的线程可以访问该临界资源,其它试图访问该资源的线程将被挂起,直到临界区对象被释放。
【Answer】
Session是服务器端保持状态的方案,Cookie是客户端保持状态的方案 Cookie保存在客户端本地,客户端请求服务器时会将Cookie一起提交;Session保存在服务端,通过检索Sessionid查看状态。保存Sessionid的方式可以采用Cookie,如果禁用了Cookie,可以使用URL重写机制(把会话ID保存在URL中)。
【Answer】
每个程序都拥有自己的地址空间,这个地址空间被分成大小相等的页,这些页被映射到物理内存;但不需要所有的页都在物理内存中,当程序引用到不在物理内存中的页时,由操作系统将缺失的部分装入物理内存。这样,对于程序来说,逻辑上似乎有很大的内存空间,只是实际上有一部分是存储在磁盘上,因此叫做虚拟内存。 虚拟内存的优点是让程序可以获得更多的可用内存。 虚拟内存的实现方式、页表/多级页表、缺页中断、不同的页面淘汰算法:答案。
【Answer】
概念:事务(Transaction)是一个操作序列,不可分割的工作单位,以BEGIN TRANSACTION开始,以ROLLBACK/COMMIT结束 特性(ACID): 原子性(Atomicity):逻辑上是不可分割的操作单元,事务的所有操作要么全部提交成功,要么全部失败回滚(用回滚日志实现,反向执行日志中的操作); 一致性(Consistency):事务的执行必须使数据库保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的; 隔离性(Isolation):一个事务所做的修改在最终提交以前,对其它事务是不可见的(并发执行的事务之间不能相互影响); 持久性(Durability):一旦事务提交成功,对数据的修改是永久性的
【Answer】
string:基本的缓存、计数器list:消息队列、分页hash:存储结构化的数据set:标签、去重sortedSet:排行榜
【Answer】
未提交读(Read Uncommited):在一个事务提交之前,它的执行结果对其它事务也是可见的。会导致脏读、不可重复读、幻读; 提交读(Read Commited):一个事务只能看见已经提交的事务所作的改变。可避免脏读问题; 可重复读(Repeatable Read):可以确保同一个事务在多次读取同样的数据时得到相同的结果。(MySQL的默认隔离级别)。可避免不可重复读; 可串行化(Serializable):强制事务串行执行,使之不可能相互冲突,从而解决幻读问题。可能导致大量的超时现象和锁竞争,实际很少使用。
【Answer】
ProductID,CreateDateCreateDate
【Answer】
分析慢查询日志:记录了在MySQL中响应时间超过阀值long_query_time的SQL语句,通过日志去找出IO大的SQL以及发现未命中索引的SQL 使用 Explain 进行分析:通过explain命令可以得到表的读取顺序、数据读取操作的操作类型、哪些索引可以使用、哪些索引被实际使用、表之间的引用以及被扫描的行数等问题; 应尽量避免在 where 子句中使用!=、<、>操作符或对字段进行null值判断,否则将引擎放弃使用索引而进行全表扫描; 只返回必要的列:最好不要使用 SELECT * 语句; 只返回必要的行:使用 LIMIT 语句来限制返回的数据; 将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有:让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用; 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余的查询; 减少锁竞争
实现类似微信朋友圈的功能,主要是几个功能点:
1,每个人的朋友圈是完全独立的,朋友圈简化成只包含文本信息
2,人和人之间有好友关系
3,某个人发了朋友圈后,其所有好友都可以看见这个朋友圈内容
【Answer】
基本思路:
1,需要将人、朋友圈文本分开成多个数据表存储,最好能给出细节的schema设计
2,好友关系如何存储和维护,采用 map or list 的不同实现有什么差异
3,如何拉取所有好友的朋友圈,是在某个人发了朋友圈的时候进行计算,还是打开朋友圈的时候遍历所有好友?
进阶思路:
1,如果有一个人其好友数非常多,如何保障在尽可能短的时间内拉取到朋友圈
【Answer】
inode包含文件的元信息,具体来说有以下内容: * 文件的字节数 * 文件拥有者的User ID * 文件的Group ID * 文件的读、写、执行权限 * 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。 * 链接数,即有多少文件名指向这个inode * 文件数据block的位置 inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。 每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。 每个inode都有一个号码,操作系统用inode号码来识别不同的文件。 这里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。 表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。 一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。 这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。 ln命令可以创建硬链接:ln 源文件 目标文件 文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的"软链接"(soft link)或者"符号链接(symbolic link)。 这意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错:"No such file or directory"。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode"链接数"不会因此发生变化。 ln -s命令可以创建软链接。:ln -s 源文文件或目录 目标文件或目录 http://www.ruanyifeng.com/blog/2011/12/inode.html
【Answer】
【Answer】
内联函数是代码被插入到调用者代码处的函数。如同 #define 宏,内联函数通过避免被调用的开销来提高执行效率,尤其是它能够通过调用(“过程化集成”)被编译器优化。 宏定义不检查函数参数,返回值什么的,只是展开,相对来说,内联函数会检查参数类型,所以更安全。 内联函数和宏很类似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。 宏是预编译器的输入,然后宏展开之后的结果会送去编译器做语法分析。宏与函数等处于不同的级别,操作不同的实体。宏操作的是 token, 可以进行 token的替换和连接等操作,在语法分析之前起作用。而函数是语言中的概念,会在语法树中创建对应的实体,内联只是函数的一个属性。 对于问题:有了函数要它们何用?答案是:一:函数并不能完全替代宏,有些宏可以在当前作用域生成一些变量,函数做不到。二:内联函数只是函数的一种,内联是给编译器的提示,告诉它最好把这个函数在被调用处展开,省掉一个函数调用的开销(压栈,跳转,返回) 内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样 内联函数必须是和函数体申明在一起,才有效。 [宏定义和内联函数区别](http://www.cnblogs.com/chengxuyuancc/archive/2013/04/04/2999844.html)
【Answer】
1. B+树2. 主要是节点太多,反复读盘影响查询效率100 万节点的平衡二叉树,树高 20。一次查询可能需要访问 20 个数据块。在机械硬盘时代,从磁盘随机读一个数据块需要 10 ms 左右的寻址时间。也就是说,对于一个 100 万行的表,如果使用二叉树来存储,单独访问一个行可能需要 20 个 10 ms 的时间3.a. 有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。b. 所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。c. 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。d. 叶子节点之间用指针相连e. 叶子节点带有卫星数据
【Answer】
以“%(表示任意0个或多个字符)”开头的LIKE语句; OR语句前后没有同时使用索引; 数据类型出现隐式转化(如varchar不加单引号的话可能会自动转换为int型); 对于多列索引,必须满足 最左匹配原则/最左前缀原则 (最左优先,eg:多列索引col1、col2和col3,则 索引生效的情形包括 col1或col1,col2或col1,col2,col3); 如果MySQL估计全表扫描比索引快,则不使用索引(比如非常小的表)
【Answer】
const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。习惯性的使用const,可以避免在函数中对某些不应修改的变量造成可能的改动。 (1)const修饰基本数据类型 1.const修饰一般常量及数组 基本数据类型,修饰符const可以用在类型说明符前,也可以用在类型说明符后,其结果是一样的。在使用这些常量的时候,只要不改变这些常量的值便好。 2.const修饰指针变量*及引用变量& 如果const位于星号*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量; 如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。 (2)const应用到函数中, 1.作为参数的const修饰符 调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,保护了原对象的属性。 [注意]:参数const通常用于参数为指针或引用的情况; 2.作为函数返回值的const修饰符 声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。 (3)const在类中的用法 不能在类声明中初始化const数据成员。正确的使用const实现方法为:const数据成员的初始化只能在类构造函数的初始化表中进行 类中的成员函数:A fun4()const; 其意义上是不能修改所在类的的任何变量。 (4)const修饰类对象,定义常量对象 常量对象只能调用常量函数,别的成员函数都不能调用。 http://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html
【Answer】
脏写更新的数据一段时间后没了A和B先后更新同一条记录,A更新完后使用undo log回滚,导致之后写入的数据也被回滚了脏读读到其他事务未提交的数据又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的,值得注意的是,脏读一般是针对于update操作的幻读前后读取到的记录数量不一致A在执行期间,B向表中插入了数据,A再次查询时,读到的数据内容发生了变化不可重复读前后读取的记录内容不一致事务A读到了事务B还未提交的数据,B回滚后,A再次读,内容就不一样了![]()
互联网秒杀场景下,很多接口的峰值流量非常非常陡峭,必须做好限流不然会把内部系统打挂。请设计一个分布式限流器,能实现这个功能。
1. 限流器能work,精确度达到 99% 以上
2. 限流阈值大小可设置,大的达到1000w级QPS,小的小到10QPS级别
3. 通用的限流器,不局限于某一业务领域
【Answer】
1. 利用redis的原子自增和过期淘汰策略
- 限流器的计数存放在redis中,用redis的过期淘汰策略实现限流器的计数的定期更新
- 例如针对 接口A 限流 10000 QPS。redis的key为:“接口A”,value为计数值 - 每次接口调用Redis用INC原子自增命令,自增1,并设置过期时间为1s
- 初次调用时,因为redis中该key没有,就直接设置为1,并设置过期时间为1s
- 在这一秒以内的后续调用,每次都自增1 - 客户端拿到自增后的值如果没有超过限制10000,就放行 - 如果超过 10000 限制,就不放行,说明超限了
- 细节实现:为避免超限后无谓的redis调用,第一次发现超限时可以记录该值的TTL时间,例如只过去100ms就有1w个请求过来,剩下的900ms就不用请求redis而是直接返回超限即可。不然这种情况会给redis带去额外无谓的流量,例如前面的例子,不做这个细节逻辑的话,redis的请求量是 10w QPS
- 精度可调节。假如限流阈值很大,比如100w,可以把INC自增步进/步长调整大一些,例如100,那么redis的QPS直接降低100倍,为1w QPS
1. 分为两个接口
- 从一个长网址生成一个短网址。需要考虑:同一个长网址,多次创建的短网址是否相同
- 用户访问短网址时,需要能跳回到原始的长网址
2. 需要考虑跨机房部署问题
3. 加分项:考虑跨海域全球部署问题
4. 加分项:考虑统计某个域名下的URL/host访问uv和pv
【Answer】
1. 一开始需要能考虑系统承载容量,例如:
- 每天100亿访问量
- 每天生成1000w条短网址记录
2. 然后考虑短网址的生成算法,方案有很多种
- 最简单实现:自增id实现,这个不可逆,同一个长网址会生成多个短网址
- hash+序号冲突
- 使用kv存储双向对应关系,可逆,但存储用量比较大
3. 302跳转问题,附带可以讨论网址访问量计数问题
4. 加分项1:需要考虑跨机房部署问题
5. 加分项2:考虑跨海域全球部署问题
6. 加分项3:能给出合理的统计需求,例如用hadoop做MR
【Answer】
## 什么时候需要建立索引 - 作为主键的列上,强制该列的唯一性和组织表中数据的排列结构。 - 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度。 - 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的。 - 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。 - 在经常使用在where子句中的列上面创建索引,加快条件的判断速度。 ## 什么时候不应该创建索引 - 在查询中很少使用或者作为参考的列不应该创建索引。 - 对于那些只有很少数据值的列也不应该增加索引(比如性别,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度)。 - 对于那些定义为text,image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。 - 当修改性能远远大于检索性能时,不应该创建索引,因为修改性能和检索性能是矛盾的。
【Answer】
+ 方案一:直接使用数据库+cache的方案,方案简单有效,数据量大之后采用分库方案,扩展性有限,但开发和运维成本低,性能方面通过cache优化,存在热点数据(可能导致cache经常被清理); + 方案二:采用redis作为kv存储,查询效率足够高,但需要考虑资源使用问题,假设有10亿的文章,帖子和评论,如何保证以更低的成本满足该需求;主要问题需要较多资源,成本较高,如何设计更好数据结构;(3+) + 方案三:可以自己开发counter模块,需要考虑kv存储方案,value的设计,保证使用更少内存;还需要考虑的点:容灾备份机制;数据扩展问题;(3.5) + 方案四:可能业务方经常新增计数需求,需要考虑counter服务的列扩展性,故设计的数据结构需要考虑列扩展问题;(3.5) + 其他:业务写入QPS可能比较高,考虑写入压力问题,数据写入去重问题,即同一个用户转发之类操作需要幂等性(一定程度保证即可)
Redis提供了哪几种持久化方式?
【Answer】
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
你也可以同时开启两种持久化方式, 在这种情况下, 当Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
【Answer】
### 方案设计 主流的设计主要有两种:**推模型和拉模型**。(任何一种都是合适的设计) 推模型简述: 基于长连接,直播间内的观众发的消息,异步通过长连接发送给房间内的其他观众。候选人的架构图应该没有硬伤。 深入的问题(言之有理即可): 1. 长连接的架构(协议、鉴权、扩容、重连、到达率等) 2. 消息放大问题如何解决(比如一个有1万人的房间,任何一个人发的消息,都会产生1万个消息,相当于放大了1万倍) 消息聚合、消息多级推送(类似CDN的方式) 3. 直播间用户列表怎么存储和优化 推送消息时,需要房间内所有用户消息,对于观众比较多的房间,需要考虑数据分片、本地缓存等手段进行优化。 拉模型简述: 拉模型类型类似于一个消息队列的模型。每个房间会有一个消息列表,直播间内用户发的消息会放在相应的消息列表中。直播间内的观众通过前端轮询拉消息接口, 把消息拉到客户端展示。 深入的问题(言之有理即可): 1. 房间的消息如何存储(由于消息有时效性,所以只需要存储最近一段时间的数据) 2. 轮询方式如何优化 3. 拉接口如何优化(local cache等) ### 核心指标 消息每秒吞吐量、消息到达率、消息延时(像稳定性这种,属于通用的基本指标)。 ### 核心的优化方式(提供一些方式,其它的只要合理即可): 监控方式: 1. 吞吐量(类似于qps,打metrics等都可以) 2. 到达率:对于推模型,基本等价于长连接的到达率监控;对于拉模型,性价比较高的是只监控主播的(因为只有主播是全程在直播间的) 3. 延时:需要关注手机和服务端的时间不一致的问题 优化: 1. 吞吐量:批量发送、多级推送等 2. 到达率:一般推模型需要重点关注,主要是对于长连接的到达率优化,包含死连接检测等。 3. 延时:一般拉模型需要重点关注,对于每次都拉到消息的房间,减少轮询间隔和长连接拉模式等。 ### 拉模型的分布式方案 类似于一个分布式存储系统,解决水平和垂直扩展的问题。
表A:用户浏览视频日志 user_behavior: date, user_id, video_id, start_time, end_time
表B:视频信息 video_info: video_id, video_duration
表C:用户信息 user_info: user_id, gender
问题:
(1)某一天(如 20200310),观看不同视频个数的前 5 名 user_id ;
(2)观看超过 50 个不同视频的女性用户中,完整观看率最高的 10 个 user_id。
【Answer】
select user_id, count(*) as video_cnt from ( select user_id, video_id from user_behavior where date = '20200310' group by user_id, video_id ) a group by user_id order by video_cnt desc limit 5;
select user_id, count(*) as video_cnt, sum(is_full) * 1.0/count(*) as full_ratio from ( select a.user_id, a.video_id, case when a.max_time >= c.duration then 1 else 0 end as is_full from ( select user_id, video_id, max(end_time -start_time) as max_time from user_behavior where date = '20200310' group by user_id, video_id ) a inner join ( select user_id, gender from user_info where gender = 'female' ) b on a.user_id = b.user_id left outer join video_info c on a.video_id = c.video_id ) a group by user_id having video_cnt >= 50 order by full_ratio desc limit 10
【Answer】
A INNER JOIN B ON……:内联操作,将符合ON条件的A表和B表结果均搜索出来,然后合并为一个结果集。 A LEFT JOIN B ON……:左联操作,左联顾名思义是,将符合ON条件的B表结果搜索出来, 然后左联到A表上,然后将合并后的A表输出。 A RIGHT JOIN B ON……:右联操作,右联顾名思义是,将符合ON条件的A表结果搜索出来, 然后右联到B表上,然后将合并后的B表输出。
【Answer】
设计思路:限流+策略+反作弊,一般情况下候选人想不到完善的设计,我们主要看其设计能否满足前面的需求。 参考:http://www.tuicool.com/articles/e2YVRvA 1 可以满足业务需求,抗住压力,不会超卖 3分 2 能考虑到到各种情况下的灾备方案,系统易于对商品种类,抢购规模进行扩展,3.5分
【Answer】
方案1:按QPS控制访问流量,在redis中纪录每秒的访问次数,1秒过期 方案2:按时间窗口限制访问次数,保存一个访问队列,定期删除过期的访问,维护一个当前时间窗口内的总访问次数 方案3:Token Bucket思想,控制Bucket的总容量和留入令牌速率,考虑到burst rate问题 方案4:基于方案3还能考虑到平台话的动态配置,熔断策略等
【Answer】
1. 评论大量写入优化方案:id分配策略,写入量太多如何进行扩展(自己实现分片策略,或者采用分布式存储系统,如果选用分布式系统介绍选型方案和实现细节);(3分,如果分片策略或存储系统介绍比较好,理解深刻,可得3.5分) 2. 查询方面如果按照时间逆序,索引可以采用zset方式,但需要考虑单个文章评论索引太多问题,故可以采用自建索引或者利用redis等拆分索引大小;其次需要考虑评论被删除后,索引中大片数据无法使用情况,采用重建索引还是单独提供删除索引?(3,如果能够对数据量较好预估,根据数据量选择合适索引方案得3.5分) 3. 头条环境下对评论做了个性化排序,索引的机制如何进行调整;(加分项目,回答OK,得3.5分) 4. 容错机制考虑;(加分项目)
【Answer】
1. 基础数据结构采用hash或者map的方式均可以,实现可得3分; 2. 考虑线程安全性,同时兼顾查询效率,是否考虑分桶等机制;(3+) 3. 分布式环境下:主要考察分片方案,如何做到使用者透明,怎么解决某一个节点故障问题,雪崩问题;(3.5)
题目:平台要对外提供一些开放API接口,设计应该考虑哪些?
开放性题目,可以不限制答案
【Answer】
参考回答要点
- 鉴权,可以追问怎么设计,如果提到公钥私钥,可以追问原理,oath2.0可以追问下怎么设计的
- 防攻击,可以追问怎么设计,考虑ip、cookie单位时间访问频次,怎么控制,有的候选人可能会提令牌桶,可以问问实现过程
- 访问次数限制,按照账号限制次数,分级
- 反爬取,封禁,假数据
- 参数签名,防篡改
- 追问如果有用户隐私信息怎么处理
- https、接口统一规范(错误码、格式)、数据库、cache等,也可以继续追问
【Answer】
去重必定会有状态,所以该系统设计的核心在于“状态”数据的存储,特点就是数据量大,去重要求精确。 1. 常规的思路是用分布式KV存储(Redis、Memcache等),去重模块本身无状态。基本合格。3.0 2. 考虑本地Redis或Memcache方案,去重模块按用户分片。3.0+ 3. 能根据状态数据的特性,考虑自己实现存储,并且实现半持久化(程序重启数据不丢),内存+磁盘存储可满足需求,根据面试者存储数据的选择和持久方式的解答,可以3.0+ 或 3.5。 4. 基于共享内存(或类似的磁盘文件映射mmap等方式),并且能较好的给出基于块存储实现Hash的表,以及Hash表的增加和查询。因为数据量大,存储需要考虑很多优化方案,基于bit的操作等。3.5 5. 在4的基础上,考虑到数据数据清除(以天为单位清除)。考虑Android设备可以自己掌握弹窗的,在端上做保底去重。3.5+
现在来了一个新需求,希望在微博用户的个人页面加入共同关注的好友有哪些,在用户搜索结果页加入每一个结果中共同关注的好友数量,需求设计稿如下(下面只做大致介绍):
【Answer】
(上面两个需求看似可以用同样的解决方案,实际第一个需求很简单,第二个需求如果用读扩散的方式计算会非常耗时)
回答思路:对于第一个问题可以将关注关系用以下的key表示:uid1_uid2,存在这个key则表示uid1有关注uid2。有了这个模型后,可以使用KV存储关注关系对;
对于第二个问题,一次检索可以有几十个结果(假设为20个),如果当前用户有500个好友(关注的人),则至少需要进行20*500=1w次判断,如果用户有5000个好友,则为10w次判断,使用KV就达不到要求了,这样KV压力会非常大。可以使用布隆过滤器,然后将布隆过滤器放在机器内存中,单机存不下的可以对布隆过滤器做分片,这样在内存中判断,并且使用上线程池分发判断任务,速度就非常快了。通常一个人能关注的账号数量是有限的,正常来说是5000多个,粉丝数没有限制。
即便关注人数真的不设限,我们也可以认为关注数量高的账号很少,因此可以针对这批账号做特殊处理,细节思路就不展开了。
另外是,在微信的公众号与普通用户之间的关注关系对大概为400亿,微博的关注关系量级应该是和这个差不多的,所以布隆过滤器预计单机也能存下来(假设是千分之5的错误率,哈希次数为8的情况下)
【Answer】
1. 写入系统:设计写入服务,帖子id分配策略(采用全局分配器),如何解决qps突增等情况,写入qps较高时,通过分片等方式提升写入效率,当然目前也可以采用写入效率较高的分布式存储系统;(3) 2. 索引构建:由于要支持timeline或者其他的排序方式,故需要单独的排序策略,这里单独针对timeline排序详细说明,timeline按照发表者时间顺序,一般有3中处理方案:1)拉的方式,按照发布者建立时间索引,需要考虑关注人数较多时,获取索引效率问题;2)推模式:发布的时通过异步消息队列,将消息投递到各个阅读者,此处主要问题推的效率和存储空间的开销;3)混合模式:粉丝较多用户采用拉模式,在线用户直接投递等提高查询效率;(3+~3.5) 3. 2中需要考虑索引数据结构设计,排序cursor值选取问题;(3.5)(细节考察) 4. 如果是热点排序:全局热点比较简单,可以直接维护一个热度计算模块,接入热度事件计算流,如果事件较多,则可以使用分布式实时计算框架提高计算效率,对于热度超过一定阈值的进入到热度排序系统中,按照从高到底排序即可(热度排序系统按照一定量截断多余数据);如果热度支持不同时间段分别组织;其次如果按照话题拆分呢?又如何设置热度排序系统?(扩展,如果回答细节都ok,可以得4分)
【Answer】
``` SELECT DATE_FORMAT(p_date, "%Y-%m-%d %H" ), count(*) FROM video.cdn_quality_log group by DATE_FORMAT(p_date, "%Y-%m-%d %H" ) ; ```
Redis相比memcached有哪些优势?
【Answer】
1.memcached所有的值均是简单的字符串,Redis作为其替代者,支持更为丰富的数据类型
2.Redis的速度比memcached快很多
3.Redis可以持久化其数据
4.Redis支持数据的备份,即master-slave模式的数据备份。
【Answer】
业务模型: 有多种解法,每种有各自优缺点: 1 记录任意两个车站的余票数量,需要维护Cn2共105个车站对的余票信息。该方案查询效率非常高,更新时有放大效应,需要更新多个车站对的余票信息。 2 记录每一张席位的售卖记录,查询时遍历每个席位,算出余票信息。该方案更新效率较高,查询时需要遍历。 3 将全程分解为原子区间,记录每个区间的可用车票数目。购买时只要起始站和终点站之间的原子区间有票,那可以出一张票。该方案查询和更新效率都很高,但不能保证买到的是同一个席位。更适合用来模糊查询。 在不考虑性能的情况下,方案可以满足查询和购买的基本需求,3分 性能上满足要求,并给出准确的分析,3.5分 架构: 1 技术选型可以实现需求,对技术栈性能和特性都比较清楚,3分 2 能够对系统瓶颈做出分析,设计比较系统的容灾方案,3.5分
功能:
显示某区服所有玩家的战力排行情况,以及我自己的战力排行情况。

【Answer】
0) 优秀的候选人会明确,用户量级(1W,1000W,还是1亿),战力力度(100分、1w分还是更细)。
1) 算法层面,桶排序、跳表等
2) 多种实现方法,数据存储、更新的机制,分布式相关,Redis/MySQL
3) 用户体验相关(排行榜更新频率、自己排名的准确性、得分相同的时候如何排名、有人频繁操作装备导致排行榜变动太快),看决策思路,是否需要全服排序等。
4) 追问:如果排名100W之后的玩家想看自己前后的数据,如何设计API等。
【Answer】
内部维护一个链表, list, 其元素为一个三元组(ID, timestamp, obj), 分别为对象ID, 上次被访问时间, 和对象内容; 在维护该list时, 需要保持一个性质, 越靠后的元素越新, 既timestamp越大; 内部再维护一个map, 该map表示一个ID到list节点的索引, 格式为map(ID, node); 对于get(id)操作: 1: 先在map中查找ID对应的list node; 2: 将node从list中取出, 即list.Remove(node); 3: 检查node.timestamp, 如果过期, 则返回null, 表示无数据, 并将ID从map中删除; 4: 如果未过期, 设置node.timestamp = now(), 并将node添加到list尾部, 即list.Append(node); 5: 返回node.obj; 对于set(id, obj)操作: 1: 同get(id)的1~3步操作, 删除对应的ID; 2: 如果此时空间满了, 既对象数为n, 则将list中表头的那个元素删除; 3: 更新list和map: node = new(ID, now(), obj), list.Append(node), map[ID] = node;
【Answer】
1. 接口设计:给出满足需求的接口设计(3分); 2. 业务逻辑:支持给出4中功能的系统实现方法 (3分); 3. 存储扩展性:数据库分库分表,或者使用kv系统单独提供均可,如果在kv系统中能够想到value的优化方案可适当加分;(3) 4. 优化: 关注数较多情况下,如何提高查询效率;(3分)
【Answer】
过期策略(redis key过期时间)1、定时过期,每个key都创建一个定时器,到期清除,内存友好,cpu不友好2、惰性过期,使用时才判断是否过期,内存不友好,cpu友好3、定期过期,隔一段时间扫描一部分key,并清除已经过期的key内存淘汰策略Redis缓存内存不足,如何处理新写入的数据1、直接报错2、lru,最近最少使用key3、随机4、过期lru,设置了过期时间里的keys lru5、过期随机6、当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
【Answer】
@谢蒙 补充 1. 任务提交管理(3分) 2. 定时器; 3. 分布式的任务调度; 4. 失败容错机制;
【Answer】
3 分: 1. 能够想到类似 diff 工具,出现同一行冲突时由用户来解决,这样能避免自动合并有可能出错等情况。 2. 采用长连接方式传递修改的 diff, 服务端转换之后传递 diff 到客户端。 3. 针对统一文档的的所有用户 hash 到同一台机器。 3.5 分: 1. 能够采用 Myer’s diff / Operational Transformation 等算法。 2. 传递 diff, 如果有多机处理统一文档的需求,需要保证操作的一致性。
【Answer】
主要考察联合索引、最左匹配几个知识点。1)最左原则指的就是如果你的 SQL 语句中用到了联合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个联合索引去进行匹配,值得注意的是,当遇到范围查询(>、<、between、like)就会停止匹配。2)Mysql有优化器会自动调整a,b的顺序与索引顺序一致,比如对(a,b)字段建立联合索引,查询条件是b=xx and a=xx,优化器会自动调整查询顺序以匹配索引对(b,a)建立索引。如果你建立的是(a,b)索引,那么只有a字段能用得上索引,毕竟最左匹配原则遇到范围查询就停止匹配。如果对(b,a)建立索引那么两个字段都能用上,优化器会帮我们调整where后a,b的顺序,让我们用上索引。
【Answer】
大体思路是,从最高位向最低位构造目标数,用 A 中尽量大的元素(但要小于等于 n 的相应位数字)。一旦目标数中有一位数字小于 n 相应位的数字,剩余低位可用 A 中最大元素填充。 注意: 1. 可能构造出等于 n 的数,需判断后重新构造; 2. 若 A 中没有小于等于 n 最高位数字的元素,则可直接用 A 中最大元素填充低位; 3. 有无解情形。 ## 一些边界情况: ``` n A 结果 23333 {2,3} 3333 23333 {3} 3333 22222 {2} 2222 22222 {2,3} 3333 2 {2} 无解 ```
在缓存系统中,想要知道过去一段时间被访问次数最多的key。
比如最近5分钟最热的key。
key的数量可能非常大。
【Answer】
基本上,这是一个排序问题,并且通过滑动窗口对key的TTL进行控制。
第一步:
比如想要知道5分钟内访问次数最多的key,可以对每个key进行计数(+1)和排序,然后在TTL5分钟之后计数(-1)进行排序,就可以得到5分钟内的精确的计数。
最快的实现方式:
用redis的zset
第二步:
因为缓存系统,qps非常高(假设100kqps),想要知道过去1个小时的热key,要怎么做。
考察点:
- qps高,需要对zset进行分片,
- 如果用1小时的滑动窗口,ttl需要保存非常大的key列表。对滑动窗口进行切割,只保留topk
其他:
有一种计数方式是 logarithmic access frequency counter 。在缓存中常见,比如redis。在LFU算法中使用。如果结合LFU,然后对TopK的key进行qps统计,也可以。
在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作。在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象。但是当我们对数据库进行了分库分表后,就不能依赖于每个表的自增ID来全局唯一标识这些数据了。因此,我们需要提供一个全局唯一的ID号生成策略来支持分库分表的环境。
针对校招的同学,可以问如何设计学校中学生系统学籍的ID,如果是全国系统如何设计ID
【Answer】
1、数据库自增ID——来自Flicker的解决方案
因为MySQL本身支持auto_increment操作,很自然地,我们会想到借助这个特性来实现这个功能。Flicker在解决全局ID生成方案里就采用了MySQL自增长ID的机制(auto_increment + replace into + MyISAM)。
先创建单独的数据库(eg:ticket),然后创建一个表:
CREATE TABLE Tickets64 (
id bigint(20) unsigned NOT NULL auto_increment,
stub char(1) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY stub (stub)
) ENGINE=MyISAM
当我们插入记录后,执行
SELECT * from Tickets64
,查询结果就是这样的:
+-------------------+------+
| id | stub |
+-------------------+------+
| 72157623227190423 | a |
+-------------------+------+
在我们的应用端需要做下面这两个操作,在一个事务会话里提交
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
这样我们就能拿到不断增长且不重复的ID了。
从高可用角度考虑,接下来就要解决单点故障问题:Flicker启用了两台数据库服务器来生成ID,通过区分auto_increment的起始值和步长来生成奇偶数的ID。
最后,在客户端只需要通过轮询方式取ID就可以了。
- 优点:充分借助数据库的自增ID机制,提供高可靠性,生成的ID有序。
- 缺点:占用两个独立的MySQL实例,有些浪费资源,成本较高。
2、独立的应用程序——来自Twitter的解决方案
Twitter在把存储系统从MySQL迁移到Cassandra的过程中由于Cassandra没有顺序ID生成机制,于是自己开发了一套全局唯一ID生成服务:Snowflake。GitHub地址:https://github.com/twitter/snowflake。根据twitter的业务需求,snowflake系统生成64位的ID。由3部分组成:
41位的时间序列(精确到毫秒,41位的长度可以使用69年)
10位的机器标识(10位的长度最多支持部署1024个节点)
12位的计数顺序号(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
最高位是符号位,始终为0。
- 优点:高性能,低延迟;独立的应用;按时间有序。
- 缺点:需要独立的开发和部署。
如果是学校学生ID:
1、入学年份+专业代码+入学考试分数排名
全国系统:
1、使用学生身份证作为id
2、入学年份+学校代码+专业/班级代码+输入分数排名
或其他方法,只要能叙述清楚这样设计的原因,重点是是否有思路
【Answer】
采用hash表存储user关注列表和user发布的消息列表 因为要去最近8条新消息,所以每条消息要带上时间戳 再get方法时候,拿到该用户所有关注者的message后根据时间戳做堆排序 ``` python class WeiTouTiao(object): def __init__(self): self.subscribe = collections.defaultdict(set) self.message = collections.defaultdict(list) self.time = -1 def post(self, user_id, message_id): self.message[user_id].append((self.time, message_id)) self.time -= 1 def get(self, user_id): tmp = copy.copy(self.message[user_id]) for user in self.subscribe[user_id]: tmp.extend(copy.copy(self.message[user_id])) heapq.heapify(tmp) res = [] while len(res) < 8 and tmp: res.append(heapq.heappop(tmp)[1]) return res def follow(self, user1, user2): if user1 != user2: self.subscribe[user1].add(user2) def unfollow(self, user1, user2): if user1 in self.subscribe and user2 in self.subscribe[user1]: self.subscribe[user1].remove(user2) ```
【Answer】
出题的本意是各种水平的人都能答,只要用过ftp或者迅雷的人就能有sense。即使应届生也能有思路。 1 最基本解法,需要考虑分级分发,且有基本的容错性考虑(重试,校验之类的,针对网络故障服务器宕机等常见场景的策略) 2 可考虑p2p的方式,进一步的可通过优化p2p分发策略来提升效率 3 考虑系统单点消除,横向扩展,可维护性提升,监控,追查问题效率等要素 4 开放式提问:跨机房怎么办,文件怎么存储,高性能单机服务器的设计,文件传输协议设计
如下代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
printf("line-1\n");
write(STDOUT_FILENO, "line-2\n", 7);
if(fork() == -1){
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
return 0;
}
执行结果如下:
yinhongbo@n8-125-036:~$ ./test
line-1
line-2
yinhongbo@n8-125-036:~$ ./test > test.stdout; cat test.stdout
line-2
line-1
line-1
问题:
1 有重定向和没有重定向下line-1和line-2的输出顺序为什么不同?
2 在有重定向情况下line-1为什么输出了2次
3 有几种方法能避免重复输出,列举几种并说明原因。
【Answer】
1 printf是在用户空间内存维护的缓冲区,缺省输出到终端时是行级缓冲,当重定向到文件时缺省为块级缓冲,printf在重定向后缓冲还在用户空间,write是在内核空间内存维护的缓冲区。因此在有重定向时会write会先于printf输出。
2 原因同上,printf是用户空间的内存中,fork是会复制,当exit时父子进程的各自的缓冲区都有printf的的内容,因此printf的内容会输出2次。
3 有比较多的方法。列举三种:
a) setbuf(stdout, NULL) 关闭stdout的流缓冲功能,但顺序write会先于printf
b) fork前调用fflush(stdout)
c) 子进程使用_exit(status)退出,以便不刷新stdio缓冲区
【Answer】
1. 可以引入某个流式处理框架,如Storm/SparkStreaming/Flink等,利用框架提供的join功能实现基本需求;或者自行设计分布式实现,能说清楚hash和join策略; 3分 2. 在头条的数据规模下(按消息qps100w,单条1k,即每秒1GB输入),能对占用的资源做出预估,需要的cpu/内存/外部存储等;3+ 3. 能考虑到上游数据的消息到达速度不一致,能处理Impression来的更晚的情况;3.5 4. 能考虑到数据一致性,如何在流式job部分或整体挂掉的情况下,保证数据正确性;3.5
【Answer】
1、Serializable(串行化):一个事务在执行过程中完全看不到其他事物对数据库所做的更新(事务执行的时候不允许别的事务并发执行,事务只能一个接着一个地执行,而不能并发执行)。 2、Repeatable Read(可重复读):一个事务在执行过程中可以看到其它事务已经提交的新插入的记录,但是不能看到其它事务对已有记录的更新。 3、Read Commited(读已提交数据):一个事务在执行过程中可以看到其它事务已经提交的新插入的记录,而且能看到其它事务已经提交的对已有记录的更新。 4、Read Uncommitted(读未提交数据):一个事务在执行过程中可以看到其它事务没有提交的新插入的记录的更新,而且能看其它事务没有提交到对已有记录的更新。
【Answer】
需要考虑过期时间、value的设置、超时锁被释放导致的数据更新覆盖的相关问题。
【Answer】
浏览器查询 DNS,获取域名对应的IP地址:具体过程包括浏览器搜索自身的DNS缓存、搜索操作系统的DNS缓存、读取本地的Host文件和向本地DNS服务器进行查询等。对于向本地DNS服务器进行查询,如果要查询的域名包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析(此解析具有权威性);如果要查询的域名不由本地DNS服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个IP地址映射,完成域名解析(此解析不具有权威性)。如果本地域名服务器并未缓存该网址映射关系,那么将根据其设置发起递归查询或者迭代查询; 浏览器获得域名对应的IP地址以后,浏览器向服务器请求建立链接,发起三次握手; TCP/IP链接建立起来后,浏览器向服务器发送HTTP请求; 服务器接收到这个请求,并根据路径参数映射到特定的请求处理器进行处理,并将处理结果及相应的视图返回给浏览器; 浏览器解析并渲染视图,若遇到对js文件、css文件及图片等静态资源的引用,则重复上述步骤并向服务器请求这些资源; 浏览器根据其请求到的资源、数据渲染页面,最终向用户呈现一个完整的页面。
启动3个线程,线程1无限循环打印1、线程2无限循环打印2、线程3无限循环打印3,要求按123123…顺序循环打印
【Answer】
方法一:利用ReentrantLock的多Condition实现线程间通信
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class NumberPrint1 { public static void main(String[] args) throws Exception { ReentrantLock lock = new ReentrantLock(); Condition c1 = lock.newCondition(); Condition c2 = lock.newCondition(); Condition c3 = lock.newCondition(); new Thread(new PrintTask(lock, c1, c2, ";1";)).start(); new Thread(new PrintTask(lock, c2, c3, ";2";)).start(); new Thread(new PrintTask(lock, c3, c1, ";3";)).start(); try { lock.lock(); c1.signal(); } finally { lock.unlock(); } Thread.sleep(Integer.MAX_VALUE); } static class PrintTask implements Runnable { private ReentrantLock lock; private Condition condition; private Condition conditionNext; private String str; public PrintTask(ReentrantLock lock, Condition condition, Condition conditionNext, String str) { this.lock = lock; this.condition = condition; this.conditionNext = conditionNext; this.str = str; } @Override public void run() { while(true) { try { lock.lock(); condition.await(); System.out.println(str); conditionNext.signal(); } catch (Exception e) { e.printStackTrace(); } } } } }
方法二:利用volatile关键字实现线程间通信
public class NumberPrint2 { static volatile int flag = 1; public static void main(String[] args) throws Exception { new Thread(new PrintTask(";1";, 1, 2)).start(); new Thread(new PrintTask(";2";, 2, 3)).start(); new Thread(new PrintTask(";3";, 3, 1)).start(); Thread.sleep(Integer.MAX_VALUE); } static class PrintTask
implements Runnable { private String str; private int curr; private int next; public PrintTask(String str, int curr, int next){ this.str = str; this.curr = curr; this.next = next; } @Override public void run() { while(true){ if(flag == curr){ System.out.println(str); flag = next; } } } } }</code></pre>
方法三:利用锁的wait和notify机制实现线程间通信
public class NumberPrint3 { static Object o1 = new Object(); static Object o2 = new Object(); static Object o3 = new Object(); public static void main(String[] args) throws Exception { new Thread(new PrintTask(";1";, o1, o2)).start(); new Thread(new PrintTask(";2";, o2, o3)).start(); new Thread(new PrintTask(";3";, o3, o1)).start(); synchronized (o1) { o1.notify(); } Thread.sleep(Integer.MAX_VALUE); } static class PrintTask implements Runnable { private Object curr; private Object next; private String str; public PrintTask(String str, Object curr, Object next){ this.str = str; this.curr = curr; this.next = next; } @Override public void run() { while(true){ synchronized (curr) { try { curr.wait(); System.out.println(str); synchronized (next) { next.notify(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
方法四:利用cas机制实现线程间通信
import java.util.concurrent.atomic.AtomicInteger; public class NumberPrint4 { static AtomicInteger flag = new AtomicInteger(1); public static void main(String[] args) throws Exception { new Thread(new PrintTask(";1";, 1, 2)).start(); new Thread(new PrintTask(";2";, 2, 3)).start(); new Thread(new PrintTask(";3";, 3, 1)).start(); Thread.sleep(Integer.MAX_VALUE); } static class PrintTask<t> implements Runnable { private String str; private int curr; private int next; public PrintTask(String str, int curr, int next){ this.str = str; this.curr = curr; this.next = next; } @Override public void run() { while(true){ if(flag.get() == curr){ System.out.println(str); flag.set(next); } } } } }
</pre> </details> --- ### 238. 乐观锁与悲观锁的区别 【Question】
--- ### 239. 客户端配置系统设计 【Question】 头条app客户端做了比较多feature,为了对这些功能做评估或者动态控制,需要支持云控ab测试方案,故需要设计一个在线动态配置管理系统设计,该系统要求能比较便捷进行参数动态修改和ab测试,请设计该系统满足该需求,需要考虑动态配置项比较多和易用性。【Answer】
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。[1] 悲观锁假定其他用户企图访问或者改变你正在访问、更改的对象的概率是很高的,因此在悲观锁的环境中,在你开始改变此对象之前就将该对象锁住,并且直到你提交了所作的更改之后才释放锁。悲观的缺陷是不论是页锁还是行锁,加锁的时间可能会很长,这样可能会长时间的限制其他用户的访问,也就是说悲观锁的并发访问性不好。 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。[1] 乐观锁不能解决脏读的问题。 乐观锁则认为其他用户企图改变你正在更改的对象的概率是很小的,因此乐观锁直到你准备提交所作的更改时才将对象锁住,当你读取以及改变该对象时并不加锁。可见乐观锁加锁的时间要比悲观锁短,乐观锁可以用较大的锁粒度获得较好的并发访问性能。但是如果第二个用户恰好在第一个用户提交更改之前读取了该对象,那么当他完成了自己的更改进行提交时,数据库就会发现该对象已经变化了,这样,第二个用户不得不重新读取该对象并作出更改。这说明在乐观锁环境中,会增加并发用户读取对象的次数。 从数据库厂商的角度看,使用乐观的页锁是比较好的,尤其在影响很多行的批量操作中可以放比较少的锁,从而降低对资源的需求提高数据库的性能。再考虑聚集索引。在数据库中记录是按照聚集索引的物理顺序存放的。如果使用页锁,当两个用户同时访问更改位于同一数据页上的相邻两行时,其中一个用户必须等待另一个用户释放锁,这会明显地降低系统的性能。interbase和大多数关系数据库一样,采用的是乐观锁,而且读锁是共享的,写锁是排他的。可以在一个读锁上再放置读锁,但不能再放置写锁;你不能在写锁上再放置任何锁。锁是目前解决多用户并发访问的有效手段。 [乐观锁与悲观锁的区别](http://www.cnblogs.com/Bob-FD/p/3352216.html)--- ### 240. 推进用户历史系统设计 【Question】 推荐系统架构中经常需要保存用户历史,用于数据去重和回放功能,请设计这样一个系统支持快速查询用户一段时间内历史数据,要求支持高并发查询和写入量,其次考虑数据量扩展问题。 主要考察候选人对存储选型,业务数据模型设计的灵活性;【Answer】
1、能清楚的画出整个架构图,基于MySQL做存储,有可视化操作后台直接操作MySQL,API层解析MySQL数据下发数据。3.0 2、API层请求量会很大,考虑到请求MySQL时需要做缓存并给合理的缓存方案(本地缓存即可)。3.0+ 3、配置需求各式各样,如何抽象配置是该系统的关键。和Lua或Python等脚本语言来描述配置比较合适。3.5--- ### 241. 并发、并行、异步的区别? 【Question】【Answer】
1. 历史数据结构设计,可直接利用分布式kv存储系统,按时间对用户的历史分别存储;(3分) 2. 由于历史数据量比较大,可以对历史value可以提前序列化并压缩,牺牲部分cpu降低对存储的需求;(3.5分)--- ### 242. 在分布式环境下,如何防止RocketMQ消息重复消费? 【Question】【Answer】
并发:在一个时间段中同时有多个程序在运行,但其实任一时刻,只有一个程序在CPU上运行,宏观上的并发是通过不断的切换实现的; 多线程:并发运行的一段代码。是实现异步的手段 并行(和串行相比):在多CPU系统中,多个程序无论宏观还是微观上都是同时执行的 异步(和同步相比):同步是顺序执行,异步是在等待某个资源的时候继续做自己的事--- ### 243. 爬虫url去重 【Question】 用爬虫抓取网页时, 一个较为重要的问题, 就是对爬去的网页去重; 请你详细的设计一种数据结构, 用来检验某个URL之前是否已经被爬取过; 并给出每次检验的复杂度, 以及整体的空间复杂度;【Answer】
消费方可以基于分布式锁来解决rocketmq的消息幂等性的问题。用分布式锁可以从纯技术角度messageid,或者业务角度业务主键唯一性都可以实现rocketmq消息消费的幂等性。另外,rocketmq生产方发送消息,本身就保证了消息的幂等性,主要是消费方消费消息要保证幂等性。--- ### 244. 使用B树和B+树的比较 【Question】【Answer】
一般应该会说hash表, 要求他说出hash表详细的设计方案, 比如hash函数怎么设计, 存储数据用什么数据结构; 时间复杂度应该是, O(len(URL)) + O(insert); 前部分表示hash函数的复杂度, 后部分表示将hash值插入到表的复杂度; 后部分通常来说应该是O(1); 如果底层存储用平衡树之类的结构比较坏的可能会到O(logn); 空间复杂度应该是O(n*len(URL)); 更好一点的方法为用字典树, 每次拿到URL都将其插入到该字典树中, 时间复杂度应该是O(len(URL)); 空间复杂度应该小于O(n*len(URL)); 当面对海量数据时, 上述两种方法可能会空间瓶颈; 解决空间复杂度的一个办法是用布隆过滤器;--- ### 245. 实现一个RPC 调度机制 【Question】【Answer】
InnoDB的索引使用的是B+树实现,B+树对比B树的好处: IO次数少:B+树的中间结点只存放索引,数据都存在叶结点中,因此中间结点可以存更多的数据,让索引树更加矮胖; 范围查询效率更高:B树需要中序遍历整个树,只B+树需要遍历叶结点中的链表; 查询效率更加稳定:每次查询都需要从根结点到叶结点,路径长度相同,所以每次查询的效率都差不多假设在一个系统中有A模块和B模块,A模块 需要调用 B模块,A模块会部署在多台机器上,记为A1、A2、A3...,B 模块也会部署在多台机器上,记为 B1、B2、B3...,请设计一个调度的机制,需要满足一下几个需求,
1,A 模块服务启动后,能自动获取并感知部署了B 模块的所有可用机器
2,A 模块请求 B模块的时候,如果B 模块返回了失败,能进行重试,请设计重试机制
3,部署B 模块的机器如果出现异常或者B模块服务出现异常,A模块的机器可自动感知并将异常机器摘除不再请求
--- ### 246. 爬虫种子调度频度设计 【Question】 爬虫一般通过种子页面发现新链接,不断访问种子页,就能发现新的链接。 但不是每次访问种子页,都会提取到新链接,有可能短时间内种子页面并不会变化。 频繁抓取种子页面,即造成资源浪费,又对被抓取站点造成压力,甚至引起封禁。 那么如何设定种子调度频度,才能既不被封,又能及时拿到新内容呢?【Answer】
服务发现 + A模块本地维护黑白名单,失败后拉近黑名单,如果能考虑到探活更好
--- ### 247. 什么是僵尸进程? 【Question】【Answer】
根据具体业务场景、规模大小,可能存在多个不同阶段: 一、人工设定调度间隔,根据人的观察和经验去设定,这个方法在量少的时候还比较容易做 1. 如果是大量抓取,是不现实的,而且人工标也无法考虑是否合适 2. 另外任意的调度间隔,基本只能靠不断轮训扫表实现调度 二、 设定档位,人工标注档位 从1分钟、2分钟、3分钟……1周设定若干档位,将调度周期标准化后,利于整体调度 标注人员也更好统一标准,且利于统计 三、程序自动调整调度周期 建立评估指标,通过程序统计计算来推定调度周期。 指标可以有: 1. 新内容页数/种子抓取次数:该比值越高,种子调度ROI越过,用少量的种子调度次数抓到了更多的内容页 2. 新页发现delay时间:假如所有种子都1天调度一次,肯定指标1会不错,但这样新内容无法尽快被抓取,所以要考虑新页面出现到被抓取到的delay时间 通过上述指标,和历史每次抓取log来分析,做种子调度档位调整。 四、一般爬虫的种子调度周期是固定,但其实当地时间的夜间,一般不会有太多新内容,是可以有区别与白天的调度周期的。 更进一步,一般互联网内容,周期性比较明显,可以做周级别的调度频度分析控制。 延伸问题: Q:如何避免大量种子? A:同一调度周期的hash打平;更进一步还可以做下同一站点内的打平 Q:种子数量筛选,抓到同样的内容页,可能10万种子就够了,但库里有100万,怎么筛选? A:可以根据发现内容页的快慢、站点质量、站点权威性等多方面考虑--- ### 248. 按时限流反抓取系统设计 【Question】 设计一个按时限流的反抓取系统,支持指定多个时间段用户频率控制,要求给出数据结构设计和考虑性能问题【Answer】
一个子进程结束后,它的父进程并没有等待它(调用wait或者waitpid),那么这个子进程将成为一个僵尸进程。僵尸进程是一个已经死亡的进程,但是并没有真正被销毁。它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程表中保留一个位置,记载该进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等)供父进程收集,除此之外,僵尸进程不再占有任何内存空间。这个僵尸进程可能会一直留在系统中直到系统重启。 危害:占用进程号,而系统所能使用的进程号是有限的;占用内存。 以下情况不会产生僵尸进程: 该进程的父进程先结束了。每个进程结束的时候,系统都会扫描是否存在子进程,如果有则用Init进程接管,成为该进程的父进程,并且会调用wait等待其结束。 父进程调用wait或者waitpid等待子进程结束(需要每隔一段时间查询子进程是否结束)。wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。waitpid则可以加入WNOHANG(wait-no-hang)选项,如果没有发现结束的子进程,就会立即返回,不会将调用waitpid的进程阻塞。同时,waitpid还可以选择是等待任一子进程(同wait),还是等待指定pid的子进程,还是等待同一进程组下的任一子进程,还是等待组ID等于pid的任一子进程; 子进程结束时,系统会产生SIGCHLD(signal-child)信号,可以注册一个信号处理函数,在该函数中调用waitpid,等待所有结束的子进程(注意:一般都需要循环调用waitpid,因为在信号处理函数开始执行之前,可能已经有多个子进程结束了,而信号处理函数只执行一次,所以要循环调用将所有结束的子进程回收); 也可以用signal(SIGCLD, SIG_IGN)(signal-ignore)通知内核,表示忽略SIGCHLD信号,那么子进程结束后,内核会进行回收。--- ### 249. MySQL的隔离级别有哪些 【Question】【Answer】
1. 数据模型设计:按照时间分段对用户的访问请求进行统计:(3) 1)统一维护最细粒度的统计数据,然后根据时间段要求求和的方式; 2)根据业务配置规则,按照规则生成对应时间段统计,该方式简化最后的求和步骤,但是同一个业务如果有多个规则会导致存储一定开销; 3)设计具体的数据结构,评估需要的存储开销,如何进行简化; 2. 发抓取系统很多时候是通过较多规则进行控制,如不同的频率控制(粗粒度,细粒度),多维度(IP,用户指定等),设计该系统时候,需要设计好规则的表示方式(待补充)(3.5)MySQL的隔离级别有哪些,请简单描述一下--- ### 250. 小视频上传逻辑-系统设计 【Question】【Answer】
读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。读提交:一个事务提交之后,它做的变更才会被其他事务看到。可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。(数据校对)串行化:顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行MySQL默认隔离级别是可重复读,Oracle默认隔离级别是读提交下面是我们在抖音上经常会看到的小视频截图:
我们平时作为用户在抖音上看到的小视频,实际都是用户上传的小视频,因此每天都有几千万的小视频上传到服务器端。为了保证在移动弱网环境下,能够获得更高的传输效率,我们会使用一些传输策略。
以下是问题:
- 如果减少服务重启过程的影响,从部署和架构上需要做哪些事情?
- 如果分片文件大小很分散该如何优化?
- 还有哪些更好的实现,可以尽量减少磁盘缓存的使用,同时还能和后端存储很好结合起来?
- 描述下常见的对象存储系统架构
--- ### 251. 谈谈你对聚集索引的理解 【Question】【Answer】
- 如果减少服务重启过程的影响,从部署和架构上需要做哪些事情?
- 增加无状态代理层,平滑重启。
- 如果分片文件大小很分散该如何优化?
- 考察对磁盘数据管理的知识
- 还有哪些更好的实现,可以尽量减少磁盘缓存的使用,同时还能和后端存储很好结合起来?
- 滑动窗口+后端分片存储
- 描述下常见的对象存储系统架构
--- ### 252. 在线答题系统-系统设计 【Question】【Answer】
聚集索引:该索引中键值的逻辑顺序决定了表中相应行的物理顺序。 聚集索引确定表中数据的物理顺序。聚集索引类似于电话簿,后者按姓氏排列数据。由于聚集索引规定数据在表中的物理存储顺序,因此一个表只能包含一个聚集索引。但该索引可以包含多个列(组合索引),就像电话簿按姓氏和名字进行组织一样。 聚集索引使用注意事项 定义聚集索引键时使用的列越少越好。 • 包含大量非重复值的列。 .• 使用下列运算符返回一个范围值的查询:BETWEEN、>、>=、< 和 <=。 • 被连续访问的列。 • 回大型结果集的查询。 • 经常被使用联接或 GROUP BY 子句的查询访问的列;一般来说,这些是外键列。对 ORDER BY 或 GROUP BY 子句中指定的列进行索引,可以使 SQL Server 不必对数据进行排序,因为这些行已经排序。这样可以提高查询性能。 • OLTP 类型的应用程序,这些程序要求进行非常快速的单行查找(一般通过主键)。应在主键上创建聚集索引。 聚集索引不适用于: • 频繁更改的列 。这将导致整行移动(因为 SQL Server 必须按物理顺序保留行中的数据值)。这一点要特别注意,因为在大数据量事务处理系统中数据是易失的。 • 宽键 。来自聚集索引的键值由所有非聚集索引作为查找键使用,因此存储在每个非聚集索引的叶条目内。 非聚集索引:数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置。 非聚集索引中的项目按索引键值的顺序存储,而表中的信息按另一种顺序存储(这可以由聚集索引规定)。对于非聚集索引,可以为在表非聚集索引中查找数据时常用的每个列创建一个非聚集索引。有些书籍包含多个索引。例如,一本介绍园艺的书可能会包含一个植物通俗名称索引,和一个植物学名索引,因为这是读者查找信息的两种最常用的方法。 一个通俗的举例,说明两者的区别 其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。 如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。 第一:聚集索引的约束是唯一性,是否要求字段也是唯一的呢? 分析:如果认为是的朋友,可能是受系统默认设置的影响,一般我们指定一个表的主键,如果这个表之前没有聚集索引,同时建立主键时候没有强制指定使用非聚集索引,SQL会默认在此字段上创建一个聚集索引,而主键都是唯一的,所以理所当然的认为创建聚集索引的字段也需要唯一。 结论:聚集索引可以创建在任何一列你想创建的字段上,这是从理论上讲,实际情况并不能随便指定,否则在性能上会是恶梦。 第二:为什么聚集索引可以创建在任何一列上,如果此表没有主键约束,即有可能存在重复行数据呢? 粗一看,这还真是和聚集索引的约束相背,但实际情况真可以创建聚集索引。 分析其原因是:如果未使用 UNIQUE 属性创建聚集索引,数据库引擎将向表自动添加一个四字节 uniqueifier 列。必要时,数据库引擎 将向行自动添加一个 uniqueifier 值,使每个键唯一。此列和列值供内部使用,用户不能查看或访问。 第三:是不是聚集索引就一定要比非聚集索引性能优呢? 如果想查询学分在60-90之间的学生的学分以及姓名,在学分上创建聚集索引是否是最优的呢? 答:否。既然只输出两列,我们可以在学分以及学生姓名上创建联合非聚集索引,此时的索引就形成了覆盖索引,即索引所存储的内容就是最终输出的数据,这种索引在比以学分为聚集索引做查询性能更好。 第四:在数据库中通过什么描述聚集索引与非聚集索引的? 索引是通过二叉树的形式进行描述的,我们可以这样区分聚集与非聚集索引的区别:聚集索引的叶节点就是最终的数据节点,而非聚集索引的叶节仍然是索引节点,但它有一个指向最终数据的指针。 第五:在主键是创建聚集索引的表在数据插入上为什么比主键上创建非聚集索引表速度要慢? 有了上面第四点的认识,我们分析这个问题就有把握了,在有主键的表中插入数据行,由于有主键唯一性的约束,所以需要保证插入的数据没有重复。我们来比较下主键为聚集索引和非聚集索引的查找情况:聚集索引由于索引叶节点就是数据页,所以如果想检查主键的唯一性,需要遍历所有数据节点才行,但非聚集索引不同,由于非聚集索引上已经包含了主键值,所以查找主键唯一性,只需要遍历所有的索引页就行,这比遍历所有数据行减少了不少IO消耗。这就是为什么主键上创建非聚集索引比主键上创建聚集索引在插入数据时要快的真正原因。项目背景
春节期间,在线答题成为了一种比较风靡的移动端游戏新方式。
在线答题的基本逻辑是:app每隔30秒左右出一道选择题,用户有10秒左右的时间作答,答错一道即算失败。所有题目回答成功,则可以参与平分奖金。
增加需求
- 如果为了提升传播效应,增加了一种复活卡的功能,用户通过分享可以获得复活机会。 拥有复活机会的用户,在答题错误之后,可以使用复活机会并复活。 复活机会的次数支持一次或者多次。 回到刚才的代码,你需要怎么修改数据结构和逻辑,来支持这个需求呢?
【Answer】
参考:
- uesr_answer_history中的fail改成int型,保存用户的“答题机会”, 默认用户的答题机会是1, 如果有复活卡等,则可以在初始化用户信息的接口中,把答题机会初始化为1以上(具体取决于一场活动可以使用多少次复活卡) submit接口判断用户答题失败的时候,增加判断答题机会的逻辑, 如果答题失败,则答题机会减一,如果答题机会还没有减到0,则还可以继续答题。
【Answer】
多版本并发控制(Multi-Version Concurrency Control, MVCC),MVCC在每行记录后面都保存有两个隐藏的列,用来存储创建版本号和删除版本号。 创建版本号:创建一个数据行时的事务版本号(事务版本号:事务开始时的系统版本号;系统版本号:每开始一个新的事务,系统版本号就会自动递增); 删除版本号:删除操作时的事务版本号; 各种操作:插入操作时,记录创建版本号; 删除操作时,记录删除版本号; 更新操作时,先记录删除版本号,再新增一行记录创建版本号; 查询操作时,要符合以下条件才能被查询出来:删除版本号未定义或大于当前事务版本号(删除操作是在当前事务启动之后做的);创建版本号小于或等于当前事务版本号(创建操作是事务完成或者在事务启动之前完成) 通过版本号减少了锁的争用,提高了系统性能;可以实现提交读和可重复读两种隔离级别,未提交读无需使用MVCC
【Answer】
1、MyISAM它不支持事务,也不支持外键,尤其是访问速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用基本都可以使用这个引擎来创建表。 2、每个MyISAM在磁盘上存储成3个文件,其中文件名和表名都相同,但是扩展名分别为:.frm(存储表定义) ,YD(MYData,存储数据) , MYI(MYIndex,存储索引),InnoDB,InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比MyISAM的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。 3、memory使用存在内存中的内容来创建表。每个MEMORY表实际对应一个磁盘文件,格式是.frm。MEMORY类型的表访问非常快,因为它到数据是放在内存中的,并且默认使用HASH索引,但是一旦服务器关闭,表中的数据就会丢失,但表还会继续存在。 4、merge存储引擎是一组MyISAM表的组合,这些MyISAM表结构必须完全相同,MERGE表中并没有数据,对MERGE类型的表可以进行查询、更新、删除的操作,这些操作实际上是对内部的MyISAM表进行操作。
【Answer】
对称加密:加密和解密采用相同的密钥。如:DES、RC2、RC4 非对称加密:需要两个密钥:公钥和私钥。如果用公钥加密,需要用私钥才能解密。如:RSA 区别:对称加密速度更快,通常用于大量数据的加密;非对称加密安全性更高(不需要传送私钥)
【Answer】
客户端没有收到ACK确认,会重新发送FIN请求。
【Answer】
使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销: select * from table ...; 当前读读取的是最新的数据,需要加锁。以下第一个语句需要加 S 锁,其它都需要加 X 锁: select * from table where ? lock in share mode; select * from table where ? for update; insert; update; delete;
【Answer】
回表查询:先定位主键值,再定位行记录,它的性能较扫一遍索引树更低解决方法:将被查询的字段,建立到联合索引里,即实现了索引覆盖
【Answer】
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
【Answer】
Linux虚拟内存的实现需要6种机制的支持:地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制和内存共享机制 内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址。当用户程序运行时,如果发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。如果有空闲的内存可供分配,就请求分配内存(于是用到了内存的分配和回收),并把正在使用的物理页记录在缓存中(使用了缓存机制)。如果没有足够的内存可供分配,那么就调用交换机制;腾出一部分内存。另外,在地址映射中要通过TLB(翻译后援存储器)来寻找物理页;交换机制中也要用到交换缓存,并且把物理页内容交换到交换文件中,也要修改页表来映射文件地址。 进程和线程、进程间及线程通信方式、共享内存的使用实现原理
【Answer】
1. 针对table,row和field定义相应类型,field支持类型描述和数据类型转换功能。(3.5) 2. 如果没有抽象出相关类型,但是代码功能实现正确;(3分)
【Answer】
智能指针:实际指行为类似于指针的类对象 ,它的一种通用实现方法是采用引用计数的方法。 1.智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针。 2.每次创建类的新对象时,初始化指针并将引用计数置为1; 3.当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数; 4.对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;这是因为左侧的指针指向了右侧指针所指向的对象,因此右指针所指向的对象的引用计数+1; 5.调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。 6.实现智能指针有两种经典策略:一是引入辅助类,二是使用句柄类。这里主要讲一下引入辅助类的方法
【Answer】
端口不同:HTTP使用的是80端口,HTTPS使用443端口; HTTP(超文本传输协议)信息是明文传输,HTTPS运行在SSL(Secure Socket Layer)之上,添加了加密和认证机制,更加安全; HTTPS由于加密解密会带来更大的CPU和内存开销; HTTPS通信需要证书,一般需要向证书颁发机构(CA)购买
给定视频打分表A,包含视频id、视频主题和视频打分情况三列: video_id,subject,score,
求每个subject视频的score的中位数。
【Answer】
select a.subject,
a.score * (1 - b.float_part) + a.next_score * (b.float_part-0) as median
from
(
select
subject, score
row_number() over(partition by subject order by score asc) as rank,
lead(score,1) over(partition by subject order by score asc) as next_score
from A
) a
inner join
(
select
subject,
floor((count(score) + 1) / 2) as int_part,
(count(score)+1) / 2 % 1 as float_part
from A
group by subject
) b
on a.rank = b.int_part and a.subject=b.subject
group by a.subject
【Answer】
1、厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;2、单例模式:Bean默认为单例模式。3、代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;4、模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。5、观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
【Answer】
一个线程可以拥有多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。 线程进程都是同步机制,而协程则是异步 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
【Answer】
```Javascript // decorator function throttle(time: number = 1000) { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.value = throttleFunc(descriptor.value, time) return descriptor; } } // throttleFunc function throttleFunc(func: Function, time: number): Function { let timerId = null; let timeStamp = 0; let callCount = 0; let args = []; return function () { args = [].slice.call(arguments); const now = Date.now(); if (now - timeStamp > time) { func.apply(this, args); callCount = 0; timeStamp = now; } ++callCount; if (!timerId) { timerId = setTimeout(() => { if (callCount > 1) { func.apply(this, args); } callCount = 0; timerId = null; timeStamp = now; }, time); } } } ```
【Answer】
C++的多态分为静态多态(编译时多态)和动态多态(运行时多态)两大类。静态多态通过重载、模板来实现;动态多态就是通过本文的主角虚函数来体现的。 虚函数实现原理:包括虚函数表、虚函数指针等 虚函数的作用说白了就是:当调用一个虚函数时,被执行的代码必须和调用函数的对象的动态类型相一致。编译器需要做的就是如何高效的实现提供这种特性。不同编译器实现细节也不相同。大多数编译器通过vtbl(virtual table)和vptr(virtual table pointer)来实现的。 当一个类声明了虚函数或者继承了虚函数,这个类就会有自己的vtbl。vtbl实际上就是一个函数指针数组,有的编译器用的是链表,不过方法都是差不多。vtbl数组中的每一个元素对应一个函数指针指向该类的一个虚函数,同时该类的每一个对象都会包含一个vptr,vptr指向该vtbl的地址。
【Answer】
Volatile关键词的第一个特性:易变性。所谓的易变性,在汇编层面反映出来,就是两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是重新从内存中读取。 Volatile关键词的第二个特性:“不可优化”特性。volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行。 Volatile关键词的第三个特性:”顺序性”,能够保证Volatile变量间的顺序性,编译器不会进行乱序优化。 C/C++ Volatile变量,与非Volatile变量之间的操作,是可能被编译器交换顺序的。C/C++ Volatile变量间的操作,是不会被编译器交换顺序的。哪怕将所有的变量全部都声明为volatile,哪怕杜绝了编译器的乱序优化,但是针对生成的汇编代码,CPU有可能仍旧会乱序执行指令,导致程序依赖的逻辑出错,volatile对此无能为力 针对这个多线程的应用,真正正确的做法,是构建一个happens-before语义。 [C/C++ Volatile关键词深度剖析](http://hedengcheng.com/?p=725)
【Answer】
不可以。有两个原因: 首先,可能会出现已失效的连接请求报文段又传到了服务器端。 client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 server。本来这是一个早已失效的报文段。但 server 收到此失效的连接请求报文段后,就误认为是 client 再次发出的一个新的连接请求。于是就向 client 发出确认报文段,同意建立连接。假设不采用 “三次握手”,那么只要 server 发出确认,新的连接就建立了。由于现在 client 并没有发出建立连接的请求,因此不会理睬 server 的确认,也不会向 server 发送数据。但 server 却以为新的运输连接已经建立,并一直等待 client 发来数据。这样,server 的很多资源就白白浪费掉了。采用 “三次握手” 的办法可以防止上述现象发生。例如刚才那种情况,client 不会向 server 的确认发出确认。server 由于收不到确认,就知道 client 并没有要求建立连接。 其次,两次握手无法保证Client正确接收第二次握手的报文(Server无法确认Client是否收到),也无法保证Client和Server之间成功互换初始序列号。
【Answer】
控制变量的存储方式和可见性。 (1)修饰局部变量 一般情况下,对于局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了。但是如果用static进行修饰的话,该变量便存放在静态数据区,其生命周期一直持续到整个程序执行结束。但是在这里要注意的是,虽然用static对局部变量进行修饰过后,其生命周期以及存储空间发生了变化,但是其作用域并没有改变,其仍然是一个局部变量,作用域仅限于该语句块。 (2)修饰全局变量 对于一个全局变量,它既可以在本源文件中被访问到,也可以在同一个工程的其它源文件中被访问(只需用extern进行声明即可)。用static对全局变量进行修饰改变了其作用域的范围,由原来的整个工程可见变为本源文件可见。 (3)修饰函数 用static修饰函数的话,情况与修饰全局变量大同小异,就是改变了函数的作用域。 (4)C++中的static 如果在C++中对类中的某个函数用static进行修饰,则表示该函数属于一个类而不是属于此类的任何特定对象;如果对类中的某个变量进行static修饰,表示该变量为类以及其所有的对象所有。它们在存储空间中都只存在一个副本。可以通过类和对象去调用。
【Answer】
进程(Process)是系统进行资源分配和调度的基本单位,线程(Thread)是CPU调度和分派的基本单位; 线程依赖于进程而存在,一个进程至少有一个线程; 进程有自己的独立地址空间,线程共享所属进程的地址空间; 进程是拥有系统资源的一个独立单位,而线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),和其他线程共享本进程的相关资源如内存、I/O、cpu等; 在进程切换时,涉及到整个当前进程CPU环境的保存环境的设置以及新被调度运行的CPU环境的设置,而线程切换只需保存和设置少量的寄存器的内容,并不涉及存储器管理方面的操作,可见,进程切换的开销远大于线程切换的开销; 线程之间的通信更方便,同一进程下的线程共享全局变量等数据,而进程之间的通信需要以进程间通信(IPC)的方式进行; 多线程程序只要有一个线程崩溃,整个程序就崩溃了,但多进程程序中一个进程崩溃并不会对其它进程造成影响,因为进程有自己的独立地址空间,因此多进程更加健壮 进程操作代码实现,可以参考:多进程 - 廖雪峰的官方网站
【Answer】
动态链接是只建立一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在运行时再装入;而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要库了。
【Answer】
指针与地址的区别? 区别: 1指针意味着已经有一个指针变量存在,他的值是一个地址,指针变量本身也存放在一个长度为四个字节的地址当中,而地址概念本身并不代表有任何变量存在. 2 指针的值,如果没有限制,通常是可以变化的,也可以指向另外一个地址. 地址表示内存空间的一个位置点,他是用来赋给指针的,地址本身是没有大小概念,指针指向变量的大小,取决于地址后面存放的变量类型. 指针与数组名的关系? 其值都是一个地址,但前者是可以移动的,后者是不可变的. 指针和引用的区别(一般都会问到) 相同点:1. 都是地址的概念; 指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。 区别:1. 指针是一个实体,而引用仅是个别名; 2. 引用使用时无需解引用(*),指针需要解引用; 3. 引用只能在定义时被初始化一次,之后不可变;指针可变; 4. 引用没有 const,指针有 const; 5. 引用不能为空,指针可以为空; 6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小; 7. 指针和引用的自增(++)运算意义不一样; 8.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。
【Answer】
事务必须严格分为两个阶段对数据进行加锁和解锁的操作,第一阶段加锁,第二阶段解锁。也就是说一个事务中一旦释放了锁,就不能再申请新锁了。
【Answer】
为了限制不同程序的访问能力,防止一些程序访问其它程序的内存数据,CPU划分了用户态和内核态两个权限等级。 用户态只能受限地访问内存,且不允许访问外围设备,没有占用CPU的能力,CPU资源可以被其它程序获取; 内核态可以访问内存所有数据以及外围设备,也可以进行程序的切换。 所有用户程序都运行在用户态,但有时需要进行一些内核态的操作,比如从硬盘或者键盘读数据,这时就需要进行系统调用,使用陷阱指令,CPU切换到内核态,执行相应的服务,再切换为用户态并返回系统调用的结果。
【Answer】
守护进程最重要的特性是后台运行。 1. 在后台运行。 为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。 if(pid=fork()) exit(0); //是父进程,结束父进程,子进程继续 2. 脱离控制终端,登录会话和进程组 有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长: setsid(); 说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。 3. 禁止进程重新打开控制终端 现在,进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端: if(pid=fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话组长) 4. 关闭打开的文件描述符 进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们: for(i=0;i 关闭打开的文件描述符close(i);> 5. 改变当前工作目录 进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 /tmpchdir("/") 6. 重设文件创建掩模 进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0); 7. 处理SIGCHLD信号 处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。 signal(SIGCHLD,SIG_IGN); 这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。
【Answer】
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 (2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 (3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
【Answer】
简单来说,关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。 非关系型数据库提出另一种理念,例如,以键值对存储,且结构不固定,每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,这 样就不会局限于固定的结构,可以减少一些时间和空间的开销。使用这种方式,用户可以根据需要去添加自己需要的字段,这样,为了获取用户的不同信息,不需要 像关系型数据库中,要对多表进行关联查询。仅需要根据id取出相应的value就可以完成查询。但非关系型数据库由于很少的约束,他也不能够提供像SQL 所提供的where这种对于字段属性值情况的查询。并且难以体现设计的完整性。他只适合存储一些较为简单的数据,对于需要进行较复杂查询的数据,SQL数 据库显的更为合适。 关系型数据库的最大特点就是事务的一致性:传统的关系型数据库读写操作都是事务的,具有ACID的特点,这个特性使得关系型数据库可以用于几乎所有对一致性有要求的系统中,如典型的银行系统。 但是,在网页应用中,尤其是SNS应用中,一致性却不是显得那么重要,用户A看到的内容和用户B看到同一用户C内容更新不一致是可以容忍的,或者 说,两个人看到同一好友的数据更新的时间差那么几秒是可以容忍的,因此,关系型数据库的最大特点在这里已经无用武之地,起码不是那么重要了。 相反地,关系型数据库为了维护一致性所付出的巨大代价就是其读写性能比较差,而像微博、facebook这类SNS的应用,对并发读写能力要求极 高,关系型数据库已经无法应付(在读方面,传统上为了克服关系型数据库缺陷,提高性能,都是增加一级memcache来静态化网页,而在SNS中,变化太 快,memchache已经无能为力了),因此,必须用新的一种数据结构存储来代替关系数据库。 关系数据库的另一个特点就是其具有固定的表结构,因此,其扩展性极差,而在SNS中,系统的升级,功能的增加,往往意味着数据结构巨大变动,这一点关系型数据库也难以应付,需要新的结构化数据存储。 于是,非关系型数据库应运而生,由于不可能用一种数据结构化存储应付所有的新的需求,因此,非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合。 必须强调的是,数据的持久存储,尤其是海量数据的持久存储,还是需要一种关系数据库这员老将。 非关系型数据库分类: 主要分为以下几类: 面向高性能并发读写的key-value数据库: key-value数据库的主要特点即使具有极高的并发读写性能,Redis,Tokyo Cabinet,Flare就是这类的代表 面向海量数据访问的面向文档数据库: 这类数据库的特点是,可以在海量的数据中快速的查询数据,典型代表为MongoDB以及CouchDB 面向可扩展性的分布式数据库: 这类数据库想解决的问题就是传统数据库存在可扩展性上的缺陷,这类数据库可以适应数据量的增加以及数据结构的变化
【Answer】
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0” virtual void funtion1()=0 原因: 1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。 2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。 为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。 定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。 纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。 [虚函数和纯虚函数的区别](http://blog.csdn.net/hackbuteer1/article/details/7558868)
【Answer】
管道(Pipe) 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道; 一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据; 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程) 命名管道 消息队列 信号(Signal) 共享内存 信号量(Semaphore):初始化操作、P操作、V操作;P操作:信号量-1,检测是否小于0,小于则进程进入阻塞状态;V操作:信号量+1,若小于等于0,则从队列中唤醒一个等待的进程进入就绪态 套接字(Socket) 更详细的可以参考(待整理): https://imageslr.github.io/2020/02/26/ipc.html https://www.jianshu.com/p/c1015f5ffa74
【Answer】
哈希索引能以 O(1) 时间进行查找,但是只支持精确查找,无法用于部分查找和范围查找,无法用于排序与分组;B树索引支持大于小于等于查找,范围查找。哈希索引遇到大量哈希值相等的情况后查找效率会降低。哈希索引不支持数据的排序。
【Answer】
它实际上是一个很长的二进制向量和一系列随机映射函数(Hash函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。Bloom Filter广泛的应用于各种需要查询的场合中,如Orocle的数据库,Google的BitTable也用了此技术。 Bloom Filter特点: 不存在漏报(False Negative),即某个元素在某个集合中,肯定能报出来。 可能存在误报(False Positive),即某个元素不在某个集合中,可能也被爆出来。 确定某个元素是否在某个集合中的代价和总的元素数目无关。
【Answer】
优点: 大大加快了数据的检索速度; 可以显著减少查询中分组和排序的时间; 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性; 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起) 缺点: 建立和维护索引耗费时间空间,更新索引很慢。
【Answer】
为什么需要线程同步:线程有时候会和其他线程共享一些资源,比如内存、数据库等。当多个线程同时读写同一份共享资源的时候,可能会发生冲突。因此需要线程的同步,多个线程按顺序访问资源。 互斥量 Mutex:互斥量是内核对象,只有拥有互斥对象的线程才有访问互斥资源的权限。因为互斥对象只有一个,所以可以保证互斥资源不会被多个线程同时访问;当前拥有互斥对象的线程处理完任务后必须将互斥对象交出,以便其他线程访问该资源; 信号量 Semaphore:信号量是内核对象,它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。信号量对象保存了最大资源计数和当前可用资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就减1,只要当前可用资源计数大于0,就可以发出信号量信号,如果为0,则将线程放入一个队列中等待。线程处理完共享资源后,应在离开的同时通过ReleaseSemaphore函数将当前可用资源数加1。如果信号量的取值只能为0或1,那么信号量就成为了互斥量; 事件 Event:允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。事件分为手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒一个等待中的线程,然后自动恢复为未激发状态。 临界区 Critical Section:任意时刻只允许一个线程对临界资源进行访问。拥有临界区对象的线程可以访问该临界资源,其它试图访问该资源的线程将被挂起,直到临界区对象被释放。
【Answer】
Session是服务器端保持状态的方案,Cookie是客户端保持状态的方案 Cookie保存在客户端本地,客户端请求服务器时会将Cookie一起提交;Session保存在服务端,通过检索Sessionid查看状态。保存Sessionid的方式可以采用Cookie,如果禁用了Cookie,可以使用URL重写机制(把会话ID保存在URL中)。
【Answer】
每个程序都拥有自己的地址空间,这个地址空间被分成大小相等的页,这些页被映射到物理内存;但不需要所有的页都在物理内存中,当程序引用到不在物理内存中的页时,由操作系统将缺失的部分装入物理内存。这样,对于程序来说,逻辑上似乎有很大的内存空间,只是实际上有一部分是存储在磁盘上,因此叫做虚拟内存。 虚拟内存的优点是让程序可以获得更多的可用内存。 虚拟内存的实现方式、页表/多级页表、缺页中断、不同的页面淘汰算法:答案。
【Answer】
概念:事务(Transaction)是一个操作序列,不可分割的工作单位,以BEGIN TRANSACTION开始,以ROLLBACK/COMMIT结束 特性(ACID): 原子性(Atomicity):逻辑上是不可分割的操作单元,事务的所有操作要么全部提交成功,要么全部失败回滚(用回滚日志实现,反向执行日志中的操作); 一致性(Consistency):事务的执行必须使数据库保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的; 隔离性(Isolation):一个事务所做的修改在最终提交以前,对其它事务是不可见的(并发执行的事务之间不能相互影响); 持久性(Durability):一旦事务提交成功,对数据的修改是永久性的
【Answer】
string:基本的缓存、计数器list:消息队列、分页hash:存储结构化的数据set:标签、去重sortedSet:排行榜
【Answer】
未提交读(Read Uncommited):在一个事务提交之前,它的执行结果对其它事务也是可见的。会导致脏读、不可重复读、幻读; 提交读(Read Commited):一个事务只能看见已经提交的事务所作的改变。可避免脏读问题; 可重复读(Repeatable Read):可以确保同一个事务在多次读取同样的数据时得到相同的结果。(MySQL的默认隔离级别)。可避免不可重复读; 可串行化(Serializable):强制事务串行执行,使之不可能相互冲突,从而解决幻读问题。可能导致大量的超时现象和锁竞争,实际很少使用。
【Answer】
ProductID,CreateDateCreateDate
【Answer】
分析慢查询日志:记录了在MySQL中响应时间超过阀值long_query_time的SQL语句,通过日志去找出IO大的SQL以及发现未命中索引的SQL 使用 Explain 进行分析:通过explain命令可以得到表的读取顺序、数据读取操作的操作类型、哪些索引可以使用、哪些索引被实际使用、表之间的引用以及被扫描的行数等问题; 应尽量避免在 where 子句中使用!=、<、>操作符或对字段进行null值判断,否则将引擎放弃使用索引而进行全表扫描; 只返回必要的列:最好不要使用 SELECT * 语句; 只返回必要的行:使用 LIMIT 语句来限制返回的数据; 将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有:让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用; 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余的查询; 减少锁竞争
实现类似微信朋友圈的功能,主要是几个功能点:
1,每个人的朋友圈是完全独立的,朋友圈简化成只包含文本信息
2,人和人之间有好友关系
3,某个人发了朋友圈后,其所有好友都可以看见这个朋友圈内容
【Answer】
基本思路:
1,需要将人、朋友圈文本分开成多个数据表存储,最好能给出细节的schema设计
2,好友关系如何存储和维护,采用 map or list 的不同实现有什么差异
3,如何拉取所有好友的朋友圈,是在某个人发了朋友圈的时候进行计算,还是打开朋友圈的时候遍历所有好友?
进阶思路:
1,如果有一个人其好友数非常多,如何保障在尽可能短的时间内拉取到朋友圈
【Answer】
inode包含文件的元信息,具体来说有以下内容: * 文件的字节数 * 文件拥有者的User ID * 文件的Group ID * 文件的读、写、执行权限 * 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。 * 链接数,即有多少文件名指向这个inode * 文件数据block的位置 inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。 每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。 每个inode都有一个号码,操作系统用inode号码来识别不同的文件。 这里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。 表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。 一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。 这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。 ln命令可以创建硬链接:ln 源文件 目标文件 文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的"软链接"(soft link)或者"符号链接(symbolic link)。 这意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错:"No such file or directory"。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode"链接数"不会因此发生变化。 ln -s命令可以创建软链接。:ln -s 源文文件或目录 目标文件或目录 http://www.ruanyifeng.com/blog/2011/12/inode.html
【Answer】
【Answer】
内联函数是代码被插入到调用者代码处的函数。如同 #define 宏,内联函数通过避免被调用的开销来提高执行效率,尤其是它能够通过调用(“过程化集成”)被编译器优化。 宏定义不检查函数参数,返回值什么的,只是展开,相对来说,内联函数会检查参数类型,所以更安全。 内联函数和宏很类似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。 宏是预编译器的输入,然后宏展开之后的结果会送去编译器做语法分析。宏与函数等处于不同的级别,操作不同的实体。宏操作的是 token, 可以进行 token的替换和连接等操作,在语法分析之前起作用。而函数是语言中的概念,会在语法树中创建对应的实体,内联只是函数的一个属性。 对于问题:有了函数要它们何用?答案是:一:函数并不能完全替代宏,有些宏可以在当前作用域生成一些变量,函数做不到。二:内联函数只是函数的一种,内联是给编译器的提示,告诉它最好把这个函数在被调用处展开,省掉一个函数调用的开销(压栈,跳转,返回) 内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样 内联函数必须是和函数体申明在一起,才有效。 [宏定义和内联函数区别](http://www.cnblogs.com/chengxuyuancc/archive/2013/04/04/2999844.html)
【Answer】
1. B+树2. 主要是节点太多,反复读盘影响查询效率100 万节点的平衡二叉树,树高 20。一次查询可能需要访问 20 个数据块。在机械硬盘时代,从磁盘随机读一个数据块需要 10 ms 左右的寻址时间。也就是说,对于一个 100 万行的表,如果使用二叉树来存储,单独访问一个行可能需要 20 个 10 ms 的时间3.a. 有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。b. 所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。c. 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。d. 叶子节点之间用指针相连e. 叶子节点带有卫星数据
【Answer】
以“%(表示任意0个或多个字符)”开头的LIKE语句; OR语句前后没有同时使用索引; 数据类型出现隐式转化(如varchar不加单引号的话可能会自动转换为int型); 对于多列索引,必须满足 最左匹配原则/最左前缀原则 (最左优先,eg:多列索引col1、col2和col3,则 索引生效的情形包括 col1或col1,col2或col1,col2,col3); 如果MySQL估计全表扫描比索引快,则不使用索引(比如非常小的表)
【Answer】
const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。习惯性的使用const,可以避免在函数中对某些不应修改的变量造成可能的改动。 (1)const修饰基本数据类型 1.const修饰一般常量及数组 基本数据类型,修饰符const可以用在类型说明符前,也可以用在类型说明符后,其结果是一样的。在使用这些常量的时候,只要不改变这些常量的值便好。 2.const修饰指针变量*及引用变量& 如果const位于星号*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量; 如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。 (2)const应用到函数中, 1.作为参数的const修饰符 调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,保护了原对象的属性。 [注意]:参数const通常用于参数为指针或引用的情况; 2.作为函数返回值的const修饰符 声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。 (3)const在类中的用法 不能在类声明中初始化const数据成员。正确的使用const实现方法为:const数据成员的初始化只能在类构造函数的初始化表中进行 类中的成员函数:A fun4()const; 其意义上是不能修改所在类的的任何变量。 (4)const修饰类对象,定义常量对象 常量对象只能调用常量函数,别的成员函数都不能调用。 http://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html
【Answer】
脏写更新的数据一段时间后没了A和B先后更新同一条记录,A更新完后使用undo log回滚,导致之后写入的数据也被回滚了脏读读到其他事务未提交的数据又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的,值得注意的是,脏读一般是针对于update操作的幻读前后读取到的记录数量不一致A在执行期间,B向表中插入了数据,A再次查询时,读到的数据内容发生了变化不可重复读前后读取的记录内容不一致事务A读到了事务B还未提交的数据,B回滚后,A再次读,内容就不一样了![]()