每日分享
Never give up. Never let things out of your control dictate who you are.
永不言弃。不能让那些超出你能力范围的事情干扰到你对自己的判断。
小闫语录:
每个人的精力都是有限的,不可能面面俱到,事事精通,那么我们为何不只抓一点,无限延伸呢?不要指望一个数学家熟知中华上下五千年,更不要指望一个历史学家破解哥德巴赫猜想。术业有专攻,你自有属于你的平台。多尝试,多失败,只有这样你才能找到你的舞台,大放异彩,加油。
面试题
1.谈谈你对Nginx中负载均衡的理解。
答:负载均衡简单的来说就是将任务分摊到不同的服务器中,从而使业务处理更加的高效。Nginx中负载均衡策略有轮询,当然这也是默认的方式,就是按顺序向后端的服务器进行任务分发;还有权重,通过设置权重使得一些硬件条件较好的服务器处理的业务多一点; ip_hash
则是基于客户端IP分配业务,确保了相同的客户端的请求一直发送到相同的服务器,以上就是一些常见的负载均衡策略。
2.数据库优化的措施,你们项目开发中做过哪些优化?
答:数据库的优化措施有很多,常见的有优化索引、SQL语句;设计表的时候严格根据数据库的设计范式来设计数据库;使用缓存,将经常访问且不需要经常变化的数据放到缓存中,节约磁盘IO;优化硬件,采用固态等;垂直分表,就是将不经常读取的数据放到一张表中,节约磁盘IO;主从分离,读写分离;选择合适的引擎;不采用全文索引等措施。
我们在项目开发过程中尽量少的使用外键,因为外键约束会影响插入和删除性能;使用缓存,减少对数据库的访问;需要多次连接数据库的一个页面,将需要的数据一次性的取出,减少对数据库的查询次数。在我们查询操作中尽量避免全表扫描,避免使用游标,因为游标的效率很差,还避免大事务操作,提高并发能力。
3.redis五种数据类型底层实现?
redis中有五种数据类型:字符串、列表、哈希、集合以及有序集合。
redis底层有简单字符串、链表、字典、跳跃表、整数集合、压缩列表等数据结构,但是,不是直接使用他们构建键值对的,而是基于这些数据结构创建了一个对象系统,这些对象系统就是咱们的五种数据类型。通过这五种不同类型的对象,Redis可以在执行命令之前,根据对象的类型判断一个对象是否可以执行给定的命令,而且可以针对不同的场景,为对象设置多种不同的数据结构,从而优化对象在不同场景下的使用效率。
在Redis中,键总是一个字符串对象,而值可以是字符串、列表、集合等对象,所以我们通常说的键为字符串键,表示的是这个键对应的值为字符串对象,我们说一个键为集合键时,表示的是这个键对应的值为集合对象。
首先是字符串对象,它的编码可以是int,raw或者embstr。其中int 编码是用来保存整数值,raw编码是用来保存长字符串,而embstr是用来保存短字符串。其中的长短字符串以44个字节为界限进行区分,当然这是redis3.2之后的版本才改的。编码的转化中,值得注意的几点是redis中对于浮点数类型作为字符串进行保存,需要的时候再将它转换成浮点数类型;int编码保存的值不是整数或大小超过了long类型(int就是可以用long类型表示的整数)的范围时,自动转化为raw。redis中embstr由于考虑到内存分配时的缺陷,只能用于读。所以修改embstr对象时,会先转化为raw在进行修改。
列表对象编码可以是压缩列表,也可以是双端链表。当列表保存元素个数小于512个且每个元素长度小于64个字节时,采用压缩列表编码;除此之外的所有情况使用双端链表编码。
双端链表:首先解释一下什么是链表,就是存储数据的时候,每一个节点中保存着下一个节点的指针,存储十分灵活,任意添加数据时,节约内存开销。双端链表就是链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为O(1)。
压缩列表:由一系列特殊编码的连续内存块组成的顺序性数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。
简单的理解:去电影院买票看电影,压缩列表要的是连号的座位,双端链表只要有座位就行,管它连号不连号。
哈希对象,底层是压缩列表和hashtable实现的。而hashtable 编码的哈希表对象底层使用字典数据结构,哈希对象中的每个键值对都使用一个字典键值对。同样,当列表保存元素个数小于512个且每个元素长度小于64个字节时,采用压缩列表编码;除此之外的所有情况使用hashtable 编码。
集合对象的编码可以是 intset 或者 hashtable。intset 编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合中。hashtable 编码的集合对象使用 字典作为底层实现,字典的每个键都是一个字符串对象,这里的每个字符串对象就是一个集合中的元素,而字典的值则全部设置为 null。当集合对象中所有元素都是整数,且所有元素数量不超过512时,采用intset编码。除此之外使用hashtable编码。
有序集合的编码可以是 ziplist 或者 skiplist。ziplist 编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。skiplist 编码的有序集合对象使用 zset 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表。字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object 属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,造成内存的浪费。压缩列表的使用条件同上,除此之外使用跳跃表。
其实有序集合单独使用字典或跳跃表其中一种数据结构都可以实现,但是这里使用两种数据结构组合起来,原因是假如我们单独使用 字典,虽然能以 O(1) 的时间复杂度查找成员的分值,但是因为字典是以无序的方式来保存集合元素,所以每次进行范围操作的时候都要进行排序;假如我们单独使用跳跃表来实现,虽然能执行范围操作,但是查找操作有 O(1)的复杂度变为了O(logN)。因此Redis使用了两种数据结构来共同实现有序集合。
4.MySQL引擎有哪些了解,用过什么?
答:主流的引擎有两个,分别是InnoDB和MyISAM。其中InnoDB支持事务,支持外键约束,它还支持行锁(比如select…for update语句,会触发行锁,但是锁定的是索引不是记录)。MyISAM不支持事务,不支持外键,它是数据库默认的引擎。InnoDB保存表的行数,如果看这个表有多少行的时候,InnoDB扫描整张表,MyISAM则是直接读取保存的行数即可。删除表的时候InnoDB是一行一行的删,而MyISAM则是重建表。InnoDB适合频繁修改以及安全性要求较高的应用,MyISAM适合查询为主的应用。在我们的项目中使用的是InnoDB。
5.缓存穿透、缓存击穿、缓存雪崩?
答:缓存穿透指的是缓存和数据库中该数据没有,但是用户不断的发起请求(如发起id为-1或者id特别大不存在该数据的请求),从而使得数据库压力过大。这样就要考虑是不是受到了攻击。解决方法就是接口层增加校验,对id进行校验,过滤非法请求;如果对方执着于同一个ID暴力攻击,那么我们可以在缓存中将key-value写成key-null,缓存有效时间设置的短一点。
缓存击穿指的是缓存中没有,但是数据库中有(一般就是缓存时间到期了)的数据,这时并发用户特别多,缓存读不到,同时去数据库读数据,造成数据库压力瞬间增大的现象。解决的方法就是热点数据永远不过期;另一种方法就是牺牲一点用户体验保护数据库,加互斥锁。
缓存雪崩指的是缓存中数据大规模的到期,而查询数据量巨大,引发数据库压力过大。你也许会想,这不是缓存击穿吗?不是的,缓存击穿是用户查询同一条数据,而缓存雪崩则是用户查询不同的数据。解决方案就是缓存数据的时间设置为随机,防止同一时间大量数据过期;如果缓存数据采用分布式部署,那么热点数据给其他缓存数据库中也分点,雨露均沾嘛;还可以将热点数据设置为永不过期。
6.异步任务除了celery还涉及到哪些?为什么选择celery?
异步任务可以使用threading模块实现多线程,进而实现多任务。还可以使用asyncio包实现异步任务,它本质是采用了协程。还有基于redis的异步任务队列RQ等等。但是兼顾性能、功能、实用性、降低耦合以及可扩展等等综合因素采用了celery。
celery是生产者消费者模型,它拥有三个至关重要的模块,一个任务发出者,一个中间人,一个执行者。任务发出者发出任务,放到中间人的消息队列中(项目中使用redis数据库),然后执行者一监听到任务就立马执行。
7.Django中中间件是如何使用的?
1.首先需要定义一个中间件的工厂函数,然后返回一个可以被调用的中间件。其中中间件工厂函数需要接收一个可以调用的 get_response
对象,返回的中间件也是一个可以被调用的对象,并且像视图一样接收一个request对象参数,返回一个Response对象。下面是一个实例:
def simple_middleware(get_response): # 此处编写的代码仅在Django第一次配置和初始化的时候执行一次。 def middleware(request): # 此处编写的代码会在每个请求处理视图前被调用。 response = get_response(request) # 此处编写的代码会在每个请求处理视图之后被调用。 return response return middleware
2.定义好中间件之后,需要在settings.py文件中添加注册中间件。
MIDDLEWARE = [ ... 'users.middleware.simple_middleware', # 添加中间件 ]
8.项目上线后,服务器有没有什么容灾措施?
可以将数据异地备份;将项目部署到不同平台的服务器上等等,防止数据丢失。
9.如何提高并发性能?
答:可以使用动态页面的静态化;增加缓存;垂直分表;数据库的主从分离读写;分库分表;异步读取;异步编程等。数据库的优化其实也是在提高并发性能。
10.利用代码实现一个简单的TCP服务器?
import socket # 创建套接字 tcp_server_socket = socket(socket.AF_INET,socket.SOCK_STREAM) # 本地的信息 address = ('', 8888) # 绑定地址 tcp_server_socket.bind(address) # 设置监听 # 使用socket创建的套接字默认是属性是主动的,使用listen将其变为被动的,这样就可以接收到别人的连接了 # 最大等待连接数我么设置为128,这是一个经验值,前人趟的坑,我们就不要在进去了 tcp_server_socket.listen(128) # 如果有新的客户端来连接服务器,那么就产生一个新的套接字专门为这个客户端服务 # client_socket用来为这个客户端服务 # tcp_server_socket就可以省下来专门等待其他新客户端的连接 client_socket,clientAddr = tcp_server_socket.accept() # 接收对方发送过来的数据 recv_data = client_socket.recv(1024) print('接收到的数据为:',recv_data.decode('gbk')) # 发送一些数据到客户端 client_socket.send('welcome to 小闫笔记'.encode('gbk')) # 关闭为这个客户端服务的套接字,只要关闭了,就意味着不能再为这个客户端服务了,如果还需服务,只能再次进行重新连接。 client_socket.close()