某生物科技公司
1.代码实现删除一个 list 里面的重复元素
方法1:最简单容易的方法
此方法基于遍历整个列表,将第一个元素添加到新列表中。
# Python 3 code to demonstrate
# removing duplicated from list
# using naive methods
# initializing list
test_list = [1, 3, 5, 6, 3, 5, 6, 1]
print ("The original list is : " + str(test_list))
# using naive method to remove duplicated from list
res = []
for i in test_list:
if i not in res:
res.append(i)
# printing list after removal
print ("The list after removing duplicates : " + str(res))
输出结果:
原始列表是:[1, 3, 5, 6, 3, 5, 6, 1]
删除重复项后的列表:[1, 3, 5, 6]
方法2:理解列表****
这个方法其实是第一种方法的简化版,它使用了列表推导式,可以用一行代码代替上面的循环方法。
def remove_duplicates(lst):
return [x for i, x in enumerate(lst) if x not in lst[:i]]
方法3:使用 set()
这是从列表中删除重复元素的最流行的方法。但是,这种方法最大的缺点之一是set后列表中元素的顺序不再和原来一样。
# Python 3 code to demonstrate
# removing duplicated from list
# using set()
# initializing list
test_list = [1, 5, 3, 6, 3, 5, 6, 1]
print ("The original list is : " + str(test_list))
# using set()to remove duplicated from list
test_list = list(set(test_list))
# printing list after removal
# distorted ordering
print ("The list after removing duplicates : " + str(test_list))
输出结果:
原始列表是:[1, 5, 3, 6, 3, 5, 6, 1]
删除重复项后的列表:[1, 3, 5, 6]
方法 4:使用列表理解 + enumerate()
此方法使用枚举根据列表理解删除重复元素。通过检查该元素是否已存在于列表中来跳过该元素。此方法保持列表中元素的顺序。
示例代码:
# Python 3 code to demonstrate
# removing duplicated from list
# using list comprehension + enumerate()
# initializing list
test_list = [1, 5, 3, 6, 3, 5, 6, 1]
print ("The original list is : " + str(test_list))
# using list comprehension + enumerate()
# to remove duplicated from list
res = [i for n, i in enumerate(test_list) if i not in test_list[:n]]
# printing list after removal
print ("The list after removing duplicates : " + str(res))
方法 5:使用 collections.OrderedDict.fromkeys()
这是完成特殊任务的最快方式。它首先删除列表中的重复项并返回一个字典,最后将其转换为列表。此方法也可用于字符串,之后列表中元素的顺序也发生了变化。
# Python 3 code to demonstrate
# removing duplicated from list
# using collections.OrderedDict.fromkeys()
from collections import OrderedDict
# initializing list
test_list = [1, 5, 3, 6, 3, 5, 6, 1]
print ("The original list is : " + str(test_list))
# using collections.OrderedDict.fromkeys()
# to remove duplicated from list
res = list(OrderedDict.fromkeys(test_list))
# printing list after removal
print ("The list after removing duplicates : " + str(res))
2.如何知道一个 python 对象的类型
1、使用type
tyep(123) 直接给出所属的类型
2、isinstance 可以检查一个对象是否为某种类型,或者某个类型的子类
用法:isinstance([],list)
isinstance(2,int)
3、检查对象的__class__属性
每个对象都有一个__class__属性指向创建它的类
>>> 1.__class__
<class 'int'>
3.用 re 匹配文本的时候,<,*>和<,*?>有什么区别?
在Python中使用正则表达式匹配HTML标签时,`<.*>`和`<.*?>`的区别主要体现在匹配的贪婪(greedy)与非贪婪(non-greedy)模式上。贪婪模式会尽可能多地匹配字符,而非贪婪模式会尽可能少地匹配字符。
贪婪模式 `<.*>`
-
`<.*>` 是贪婪匹配,它会尽可能多地匹配字符。
-
在HTML中,如果你有嵌套的标签,贪婪匹配可能会匹配到最外层的结束标签,而不是你期望的第一个结束标签。
示例
假设你有以下HTML字符串:
```html
"<div><p>Example</p></div>"
```
使用贪婪匹配 `<.*>`:```python
import re
html = "<div><p>Example</p></div>"
pattern = r"<.*>"
matches = re.findall(pattern, html)
print(matches)
输出:
```python
['<div><p>Example</p></div>']
```
可以看到,它匹配了整个字符串,因为贪婪匹配会尽可能多地匹配字符。
非贪婪模式 `<.*?>`
-
`<.*?>` 是非贪婪匹配,它会尽可能少地匹配字符。
-
在HTML中,这意味着它会匹配到最早的结束标签。
示例
同样的HTML字符串:
"<div><p>Example</p></div>"
使用非贪婪匹配 `<.*?>`:```python
import re
html = "<div><p>Example</p></div>"
pattern = r"<.*?>"
matches = re.findall(pattern, html)
print(matches)
输出:
```python
['<div>', '<p>', '</p>', '</div>']
```
可以看到,非贪婪匹配会匹配到最早的结束标签,因此它匹配到了每个单独的标签。
结论
-
`<.*>` 会贪婪地匹配,可能会导致匹配结果过多。
-
`<.*?>` 会非贪婪地匹配,通常在处理嵌套标签时更合适。
在处理HTML或XML时,使用非贪婪匹配通常会得到更准确的结果,但需要注意的是,正则表达式在处理复杂的HTML或XML结构时并不是最佳选择,使用专门的解析器如BeautifulSoup或lxml会更加可靠和方便。
4.什么是 PEP8
PEP8是Python Enhancement Proposal 8的缩写,它是Python编程语言中的一项官方建议。PEP8规定了Python代码的编写风格和规范,旨在提高代码的可读性和可维护性。
PEP8涵盖了很多方面,包括代码布局、命名约定、注释风格、导入语句等。下面是一些PEP8的主要指导原则:
1.缩进:使用四个空格进行缩进,而不是制表符。
2.行长度:每行代码不应超过79个字符,可以使用括号或反斜杠换行。
3.空格:在运算符和逗号周围加上空格,但不要在括号内加空格。
4.命名规范:变量和函数名应使用小写字母,多个单词之间用下划线分隔(snake_case);类名应使用驼峰命名法(CamelCase)。
5.注释:使用清晰的注释来解释代码的意图和功能。
6.导入语句:每个导入应位于独立的行上,按照标准库模块、第三方库模块和本地模块的顺序导入,每个部分之间应留有空行。
PEP8的目标是使Python代码具有一致的风格,从而提高代码的可读性,并使不同开发者之间的代码更易于理解和协作。符合PEP8规范的代码更易于维护和修改,并且能够与其他遵循PEP8的代码库更好地集成。
5.Django里的MTV架构是什么?
MVC介绍
MVC
全拼为Model-View-Controller
MVC
核心思想 : 解耦- 让不同的模块之间降低耦合, 增强代码的可扩展性和可移植性, 实现更好的向后续版本的兼容
- 开发原则 : 高内聚, 低耦合
MVC
解析M
全拼为Model
, 主要封装对数据库层的访问, 内嵌ORM框架, 实现面向对象的编程来操作数据库.V
全拼为View
, 用于封装结果, 内嵌了模板引擎, 实现动态展示数据.C
全拼为Controller
, 用于接收GET或POST请求, 处理业务逻辑, 与Model和View交互, 返回结果.
- 当前主流的开发语言如
Java、PHP、Python、...
中都有MVC
设计模式
![](https://i-blog.csdnimg.cn/direct/1b501fec8fc34325bec683559601627c.png)
MVT介绍
-
MVT
全拼为Model-View-Template
-
MVT
核心思想 : 解耦 -
MVT
解析M (模型)
全拼为Model
, 与MVC中的M功能相同, 负责数据处理, 内嵌了ORM框架.V (视图)
全拼为View
, 与MVC中的C功能相同, 接收HttpRequest, 业务处理,返回HttpResponse.T (模板)
全拼为Template
, 与MVC中的V功能相同, 负责封装构造要返回的html, 内嵌了模板引擎.
-
MVT和MVC
差异就在于黑箭头标识出来的部分.
6.queryset F 和Q的作用
Django的对象关系映射系统(Object-Relational Mapper, ORM)提供了丰富的数据查询接口, 让你无需使用原生SQL语句即可通过对模型的简单操作实现对数据库里的数据进行增删改查。查询得到的结果叫查询集(QuerySet), 所以这个接口被称为QuerySet API。
from .models import Article
article = Article(title="My first article", body="My first article body")
article.save()
7.contenttypes 是什么?通常我们用来解决什么问题?
contenttypes 是Django内置的一个应用, 可以追踪项目中所有app和model的对应关系,并记录在ContentType表中 。
8.谈谈 RESTful 和 Django REST framework
RESTful
RESTful(Representational State Transfer)是一种架构风格或设计原则,用于构建可扩展和易于维护的 Web 服务。它有以下几个关键特性:
-
资源:RESTful API 将数据和功能视为资源。每个资源由一个唯一的 URI(Uniform Resource Identifier)标识。
-
HTTP 动词:使用标准的 HTTP 动词来操作资源,例如 GET(读取),POST(创建),PUT(更新),DELETE(删除)。
-
无状态:每个请求都是独立的,服务器不保存客户端的状态。客户端每次请求时必须包含所有必要的信息。
-
表现层状态转化(HATEOAS):响应中包含指向相关资源的链接,允许客户端通过链接进行导航。
-
统一接口:通过一致的接口进行交互,简化了客户端和服务器之间的通信。
Django REST framework (DRF)
Django REST framework 是一个强大且灵活的工具包,用于构建 Web API,它是基于 Django 框架构建的。它提供了一些特性,使得构建 RESTful API 更加容易和高效。以下是 Django REST framework 的一些关键特性:
-
视图集和路由:提供了视图集和路由功能,可以轻松地将模型的所有 CRUD 操作映射到 URL。
-
序列化:提供了强大的序列化工具,可以将复杂的 Django 查询集和模型实例转换为 JSON 格式,反之亦然。
-
认证和权限:内置了多种认证和权限机制,例如 Token 认证、OAuth、权限类等。
-
浏览器可浏览 API:提供了一个 Web 浏览器界面,可以直接在浏览器中测试 API,方便开发和调试。
-
自定义视图和路由:允许开发者自定义视图和路由,以满足特定需求。
-
支持多种渲染和解析:支持多种内容类型的渲染和解析,包括 JSON、XML 等。
-
节流(Throttle)和过滤:提供了请求节流和过滤功能,方便管理 API 流量和数据访问。
总结
-
RESTful 是一种架构风格和设计原则,指导如何设计和实现 API,使其具有可扩展性、易维护性和一致性。
-
Django REST framework 是一个框架和工具包,帮助开发者在 Django 中快速、轻松地构建 RESTful API。它实现了 RESTful 架构风格的许多原则和最佳实践。
9.什么是redis,有什么用,一般的使用场景是什么?
9.5 谈谈MYSQL?
mysql是一个关系型数据库,是建立在关系模型基础上,由多张相互连接的二维表相组成,
而二维表指的是由行和列组成的表,类似于excel表格
,简单来说,基于二维表存储数据的数据库就变成关系型数据库,不是基于二维表存储数据的数据库就是非关系型数据库。
特点:使用表存储结构,格式统一,便于维护。
使用sql(结构化查询语言)操作,标准统一,使用方便。
![](https://i-blog.csdnimg.cn/direct/1a7b0895f65944c1ab456d5eecf65d15.png)
mysql的基础结构图是分为四层、连接层、服务层、引擎层、存储层。
连接层的话,这部分管理连接,权限验证。
第二层是服务层,这一层主要完成大多数的核心服务功能:编写sql接口并完成缓存的查询(如果缓存有数据则直接从缓存提取数据),sql的分析和优化包括分析器(词法分析、语法分析)、优化器(执行计划生成,索引选择)所有跨存储引擎的功能也在这层实现,比如函数、过程等。
第3层是引擎层,负责mysql中数据的存储和提取,服务器通过api和存储引擎进行交互,不同的存储引擎有不同的作用。我们可以根据自己的需要来选取合适的存储引擎。
第4层是存储层,指的是系统的文件系统与存储引擎完成交互并存储数据在上面。
存储引擎就是存储数据、建立索引、更新/查询数据等的技术,存储引擎是基于表的
mysql的常用存储引擎是InnoDB和MyISam,
默认的存储引擎是InnoDB,InnoDB的特点是支持事务、使用行级锁提高并发性能、支持外键保证数据的完整性和可靠性。文件,使用后缀idb存储该表的结构、数据和索引
其次是MyISam,MyISam是早期的默认存储引擎。
特点:不支持事务,不支持外键、不支持行锁。
访问速度快、使用三个文件分别存储结构、数据和索引。
10.请说一些单元测试的了解
11.你在项目的时候有遇到什么困难或者印象深刻的东西
龙象文化传播有限公司
1、前后端怎么处理重复数据提交
后端处理:
-
使用唯一标识符和幂等操作:
- 后端在处理提交数据时,可以要求前端提供一个唯一的标识符(例如请求ID或者时间戳等),并在处理请求时检查该标识符的唯一性。如果已经处理过相同标识符的请求,后端可以忽略或者返回相同的响应。这种方式称为幂等操作。
-
数据库唯一约束:
- 在数据库层面设置唯一约束,确保某些字段或者组合字段的数值在数据库中唯一。这样即使前端重复提交了相同数据,数据库也不会接受重复的数据。
-
Token机制:
- 使用令牌(Token)来防止重复提交。后端在处理第一次请求时生成一个唯一的Token,并将其返回给前端。前端在后续的请求中携带该Token,后端在接收到请求时检查Token的有效性,以确定是否是重复提交。
-
设置请求过期时间:
- 在处理请求时,可以设置一个有效期限制,超过有效期的重复请求将被拒绝。
前端处理:
-
禁用提交按钮:
- 在第一次提交后,立即禁用提交按钮,避免用户多次点击。
-
显示加载状态:
- 在提交过程中显示加载状态或者进度条,告知用户当前请求正在处理中。
-
防抖动(Debouncing):
- 在用户频繁操作时,可以使用防抖动技术,确保只有最后一次操作会真正触发提交。
-
前端校验:
- 在前端进行一些基本的数据校验,确保无效的数据不会提交给后端。
-
提交确认:
- 在提交前弹出确认框,确保用户意识到他们的操作将要提交数据。
综合处理:
- 综合利用前后端的多种手段来处理重复提交问题,可以提高系统的稳定性和用户体验。通常情况下,前端和后端都应该考虑处理重复提交问题,以保证系统数据的一致性和安全性。
2、redis的使用场景
缓存(Cache)
Redis的第一个应用场景是Redis作为缓存对象来加速Web应用的访问。
![](https://img-blog.csdnimg.cn/img_convert/26b8d4b82e159193dcdd1abd4ed7ad27.png)
在该场景下,有一些存储于数据库中的数据会被频繁访问,如果频繁的访问数据库,数据库负载会升高,同时由于数据库IO比较慢,应用程序的响应会比较差。此时,如果引入Redis来存储这些被频繁访问的数据,就可以有效的降低数据库的负载,同时提高应用程序的请求响应。
会话存储(Session)
使用Redis来存储会话(Session)数据,可以实现在无状态的服务器之间共享用户相关的状态数据数据。
![](https://img-blog.csdnimg.cn/img_convert/f41d977f1faf71a0fb504d7fb5d4aaf2.png)
当用户登录Web应用时候,将会话数据存储于Redis,并将唯一的会话ID(Session ID)返回到客户端的Cookie中。当用户再向应用发送请求时,会将此会话ID包含在请求中。无状态的Web服务器,根据这个会话ID从Redis中搜索相关的会话数据来进一步请求处理。
这里需要注意的是,Redis是内存数据库,如果采用单实例部署。那么当Redis服务器故障重启之后,所有的Session会话会消失,用户不得不重新登录来获取新的Session。所以,当拿Redis来存储Session的时候,建议采用主从的集群模式来部署。这样,即使主服务器挂了,马上有从库接管流量,不影响用户的使用。
分布式锁(Distributed Lock)
当我们在应用中部署了多个节点,这些节点需要操作同一个资源的时候会存在竞争。此时,我们可以使用Redis来作为分布式锁,以协调多个节点对共享资源的操作。
![](https://img-blog.csdnimg.cn/img_convert/937855c677a6fb5cf69726abb8dd572c.png)
这里主要是用Redis的原子操作命令:SETNX
,该命令仅允许key不存在的时候才能设置key。
下图展示了一个简单用例。Client 1通过SETNX
命令尝试创建lock 1234abcd
。如果当前还没有这个key,那么将返回1。Client 1获得锁,就可以执行对共享资源的操作,操作完成之后,删除刚刚创建的lock(释放分布式锁)。如果Client 1在执行SETNX
命令的时候,返回了0,说明有其他客户端占用了这key,那么等待一段时间(等其他节点释放)之后再尝试。
![](https://img-blog.csdnimg.cn/img_convert/defc1111f7e77aeeee6564739fa6448a.png)
上面这个简单实现虽然可以满足很多用例,但它并不具备良好的容错机制。如果要在生产上是用的话,更推荐采用一些更高质量的分布式锁实现。比如,Java平台的话,可以选择:Redisson.
速率限制器(Rate Limiter)
由于Redis提供了计数器功能,所以我们可以通过该能力,配合超时时间,来实现速率限制器,最常见的场景就是服务端是用的请求限流。
一个基本的限速实现如下图:
![](https://img-blog.csdnimg.cn/img_convert/ff5de07641b5bd32b3eb16d59aeb08d6.png)
根据用户id或者ip来作为key,使用INCR
命令来记录用户的请求数量。然后将该请求数量与允许的请求上限数量做比较,只有低于限制的时候,才会执行请求处理。如果超过限制,就拒绝请求。
同时,请求数量的计数器需要设置一个时间窗口,比如:1分钟。也就是没过一分钟时间,计数器将被清零,重新计数。所以,当一个时间窗口中被限流之后,等到下一个时间窗口,就能恢复继续请求。以实现限制速率的效果。
![](https://img-blog.csdnimg.cn/img_convert/a8d386797e620f704f06a348e04468ce.png)
除了时间窗算法之外,漏桶算法也能通过Redis来实现。
![](https://img-blog.csdnimg.cn/img_convert/505e77985ed4bd1f69004800d8d60103.png)
排行榜(Rank/Leaderboard)
由于Redis提供了排序集合(Sorted Sets)的功能,所以很多游戏应用采用Redis来实现各种排行榜功能。
![](https://img-blog.csdnimg.cn/img_convert/c65e170a152ef89ad8d85e0f80860837.png)
排序集合是唯一元素(比如:用户id)的集合,每个元素按分数排序,这样可以快速的按分数来检索元素
![](https://img-blog.csdnimg.cn/img_convert/9988701e040852cbd513502cd8738ea1.png)
小结
Redis的应用非常广泛,这里仅总结了一些常见的用法。除此之外,还有很多有意思的应用,这取决于业务场景。大家可以举一反三。
![](https://img-blog.csdnimg.cn/img_convert/bfb8d067ac78ff6ac97b3da92bcde36a.png)
悦谦科技
1、谈谈rpc和http
HTTP协议(Hyper Text Transfer Protocol)超文本传输协议:
一个用于在网络上交换信息的标准协议,它定义了客户端(例如浏览器)和服务器之间的通信方式。如平时上网在浏览器上敲个网址url就能访问网页,这里用到的就是HTTP协议。
明确 HTTP 是一个协议,是一个超文本传输协议,不是运输通道。它基于 TCP/IP 来传输文本、图片、视频、音频等。HTTP 不提供数据包的传输功能,也就是数据包从浏览器到服务端再来回的传输和HTTP没关系。
HTTP最早版本在1989年提出,经过多年发展,到1996年发布的HTTP/1.1版本一直沿用至今,而在2015年也出现了HTTP/2.0。
RPC(Remote Procedure Call)远程过程调用:
能够调用位于另一个地址空间(通常是网络上的另一台计算机)中的函数,而对程序员来说是透明的。仅仅是远程调用,还不算是RPC,因为RPC强调的是过程调用,调用的过程对用户而言是应该是透明的,用户不应该关心调用的细节,可以像调用本地服务一样调用远程服务。所以RPC一定要对调用的过程进行封装。
它本身并不是一个具体的协议,只是一种协议的规范,明确的说是概念、机制或者思想,它并没有具体实现,只有按照RPC通信协议规范实现的通信框架,也就是RPC框架,才是协议的具体实现,比如Dubbo、gRPC等。它包括了:接口规范+序列化反序列化规范+通信协议等
2、tcp的连接方式
- TCP简介
TCP协议广泛应用于可靠性要求较高的应用场景,如网页浏览、文件传输、电子邮件等。它提供了可靠的数据传输和流控制机制,能够确保数据的完整性和有序性。然而,由于TCP协议在传输过程中引入了较多的控制信息,因此相比于UDP协议,TCP的传输速度较慢。
- TCP和UDP的区别
TCP UDP
有连接 无连接
可靠传输 不可靠传输
面向字节流 面向数据报
全双工 全双工
是否连接: 可以想象成打电话,比如A给B打电话,只有建立好连接才能通信(交换数据),这种是有连接.而是用微信发消息,不需要建立连接则是无连接
是否可靠传输: 这里的可靠,并不是指A给B的数据一定完全能让B收到,只能确保B是不是收到了
字节流和数据报: TCP和文件IO一样基于 "流",UDP则是以"数据报"为基本单位
全双工: 一个通道,双向通信
参考链接:https://blog.csdn.net/m0_63463510/article/details/132164808
3、内存IO、网络IO、硬盘IO和CPU io的速度
它们的速度排序通常是:CPU IO > 内存IO > 硬盘IO > 网络IO。以下是对每种IO类型的详细解释和速度排序原因:
-
CPU IO
- 定义:CPU IO指的是中央处理器(CPU)与其内部组件(如寄存器和缓存)之间的数据传输。
- 速度:最快,因为数据传输在CPU内部进行,不涉及外部硬件。CPU内部的寄存器和缓存的访问时间通常在纳秒级(ns)。
- 示例:CPU从寄存器读取数据或将数据写入寄存器。
-
内存IO
- 定义:内存IO指的是CPU与主内存(RAM)之间的数据传输。
- 速度:较快,但比CPU IO慢。内存访问时间通常在几十纳秒到几百纳秒之间,取决于内存类型(如DDR4、DDR5等)和系统架构。
- 示例:CPU从RAM读取数据或将数据写入RAM。
-
硬盘IO
- 定义:硬盘IO指的是数据在存储设备(如HDD或SSD)与内存之间的传输。
- 速度:相比内存IO慢得多。传统机械硬盘(HDD)的访问时间通常在几毫秒(ms),而固态硬盘(SSD)的访问时间可以在微秒(μs)到几毫秒之间。
- 示例:从硬盘读取文件到内存或将数据从内存写入硬盘。
-
网络IO
- 定义:网络IO指的是通过网络接口进行的数据传输,涉及计算机之间或计算机与服务器之间的数据交换。
- 速度:最慢,因为涉及网络延迟、带宽限制和数据包传输。局域网(LAN)内的网络IO可能在微秒到毫秒级,而广域网(WAN)或互联网的网络IO可能在几十毫秒甚至更高。
- 示例:下载文件、发送电子邮件或通过网络进行数据通信。
速度排序总结
- CPU IO:纳秒级(最快)
- 内存IO:几十纳秒到几百纳秒
- 硬盘IO :
- SSD:微秒到几毫秒
- HDD:几毫秒
- 网络IO:微秒到几十毫秒或更高(最慢)
4、谈谈http的版本差异
HTTP(Hypertext Transfer Protocol)是用于传输和交换超文本(例如 HTML)的应用层协议。随着时间的推移,HTTP协议经历了多个版本的演变,每个版本都带来了不同的改进和功能。下面是几个主要版本之间的差异概述:
HTTP/0.9
- 发布时间: 1991年
- 特点:
- 最初版本,仅支持GET请求。
- 没有请求头、响应头等标准化的元数据。
- 每个连接只能处理一个请求,请求后服务器关闭连接。
HTTP/1.0
- 发布时间: 1996年
- 特点:
- 引入了多种请求方法,包括GET、POST、HEAD。
- 引入了请求头、响应头,以及状态码(如200、404)。
- 支持长连接,但默认情况下仍然是短连接,即每个请求一个连接。
HTTP/1.1
- 发布时间: 1999年
- 特点:
- 引入了持久连接(Persistent Connections),即默认使用长连接,一个连接可以传输多个请求和响应。
- 引入了管道化(Pipelining),允许在一个连接上同时发送多个请求,但存在实现上的复杂性和安全性问题,因此并未广泛实现。
- 引入了请求和响应的分块传输编码(Chunked Transfer Encoding),支持更高效的数据传输。
- 引入了Host头字段,支持虚拟主机。
HTTP/2
- 发布时间: 2015年
- 特点:
- 二进制协议:HTTP/2 使用二进制格式传输数据,而不是文本格式,提高了效率和安全性。
- 多路复用:允许在一个连接上并发交错发送多个请求和响应,避免了 HTTP/1.x 中的队头阻塞问题。
- 头部压缩:使用HPACK算法对请求和响应头进行压缩,减少数据传输量。
- 服务器推送:服务器可以在客户端请求之前主动向客户端推送资源,提高加载速度。
- 优化 HTTP/1.x 中的性能瓶颈,如多个TCP连接和头部明文传输等问题。
HTTP/3
- 发布时间: 2020年(IETF标准)
- 特点:
- 基于QUIC协议(Quick UDP Internet Connections),不再依赖TCP。
- 提供更低的连接建立延迟和更好的拥塞控制。
- 类似HTTP/2的多路复用和头部压缩,但在协议层面提供了更好的性能和安全性。
2、vue3的插槽是什么?
Vue 组件通过插槽的方式实现内容的分法,它允许我们在父组件中编写 DOM 并在子组件渲染时把 DOM 添加到子组件的插槽中,使用起来非常方便。
在实现上,Vue 组件的插槽内容会被编译为插槽函数,插槽函数的返回值就是向槽位填充的内容。<slot>
标签则会被编译为插槽函数的调用,通过执行对应的插槽函数,得到外部向槽位填充的内容(即虚拟 DOM),最后将该内容渲染到槽位中。
参考:https://zhuanlan.zhihu.com/p/693009138
django的中间件有哪些?
- SecurityMiddleware
django.middleware.security.SecurityMiddleware:
安全中间件负责处理与网站安全相关的任务
例如设置HTTP头部,防止跨站脚本攻击(XSS),点击劫持等。
它可以通过配置自定义安全策略来确保网站的安全性。
(2) SessionMiddleware
django.contrib.sessions.middleware.SessionMiddleware:
会话中间件负责处理用户会话的创建之间存储和检索用户数据。
它基于浏览器提供的Cookie或URL传递的会话ID进行会话跟踪,并将会话数据存储在后端数据库或缓存中,以实现用户状态的跨请求保持。
(3) CommonMiddleware
django.middleware.common.CommonMiddleware:
通用中间件提供了一些常见而关键的HTTP请求处理功能
例如,根据请求的HTTP头信息设置语言、时区等。
此外,它还处理静态文件的serving,包括收集静态文件,为其生成URL,并在开发模式下提供静态文件的serving。
(4) CsrfViewMiddleware
django.middleware.csrf.CsrfViewMiddleware:
CSRF(Cross-Site Request Forgery)中间件用于防止跨站请求伪造攻击。
它在每个POST请求中验证一个CSRF标记,确保请求是通过合法的表单提交得到的,从而保护用户免受恶意站点的攻击。
(5) AuthenticationMiddleware
django.contrib.auth.middleware.AuthenticationMiddleware:
认证中间件负责处理用户身份认证相关的任务
例如将认证信息关联到请求对象上,为每个请求提供一个user对象,以便在请求处理过程中轻松地获取和使用用户身份信息。
(6) MessageMiddleware
django.contrib.messages.middleware.MessageMiddleware:
消息中间件用于在请求处理过程中存储和传递临时的、一次性的用户消息。
它允许在HTTP重定向之间跨请求传递消息,例如成功或错误提示,以改善用户体验。
(7) XFrameOptionsMiddleware
django.middleware.clickjacking.XFrameOptionsMiddleware:
点击劫持中间件用于防止页面被嵌入到其他网站中,从而提供一定的点击劫持保护。
它通过设置X-Frame-Options HTTP头部来限制页面的显示方式,从而防止恶意网页通过iframe等方式嵌入当前网页。
参考链接:https://blog.csdn.net/qq_53842456/article/details/134584025
谈谈redis的使用场景
如上所述。
技术面(简历)
Mongodb的索引存储结构是什么?
MongoDB的索引底层存储结构与传统关系型数据库MySQL的B+树索引有所不同。MongoDB使用的主要索引存储结构是B树(B-tree),而不是B+树。
MongoDB的索引存储结构
- **B树(B-tree)**:
-
MongoDB的默认索引结构是B树。B树是一种自平衡的多路搜索树,它的每个节点可以包含多个键值对,相比于二叉树,B树可以支持更大的数据存储和更高效的查找。
-
在B树中,每个节点都有多个子节点,且子节点范围在一个节点的两个边界之间。这使得B树能够在非常高效的时间复杂度下进行查找、插入和删除操作。
- **复合索引**:
-
MongoDB支持复合索引,即将多个字段组合成一个索引。这种索引可以在查询中同时使用多个字段,提高查询效率。
-
复合索引在B树中按照字段顺序依次建立,每个键值对都包含多个字段的值,而不是像B+树那样只在叶子节点包含数据。
- **全文索引和地理空间索引**:
-
- MongoDB还支持全文索引和地理空间索引,这些索引在内部也是基于B树来实现的,但是在查询语义上有所不同,例如全文索引可以支持文本搜索,而地理空间索引可以支持地理位置相关的查询。
总结
与MySQL使用B+树索引不同,MongoDB的索引底层存储结构是B树。B树的设计使得MongoDB能够高效地处理大量数据和复杂查询,同时支持复合索引、全文索引和地理空间索引等特性,为开发者提供了灵活和高效的数据访问方式。
谈谈Python 协程 和 IO多路复用
IO多路复用是一种技术,它允许单个或少数线程同时管理多个IO连接,从而提高系统的并发处理能力和效率。 这种技术通过使用少量的线程来维护大量的网络请求,避免了为每个请求创建一个新线程的开销,同时也解决了阻塞和非阻塞IO模型的局限性。
基本概念 :在传统的阻塞IO模型中,当一个线程等待某个IO操作完成时,它不能执行其他任务,直到数据准备好或发生错误。非阻塞IO虽然允许线程继续执行其他任务,但在轮询检查数据是否准备好时,会浪费CPU资源。IO多路复用通过监控多个网络请求,当有数据准备就绪时再分配对应的线程去读取数据,从而实现了高效的数据处理。
实现方式 :IO多路复用主要通过select、poll和epoll等函数实现。这些函数允许程序同时监控多个文件描述符(如网络连接),当其中一个或多个文件描述符准备好进行读/写操作时,程序可以得到通知并进行处理。这种方式减少了不必要的CPU占用和系统调用的开销。
优势 :使用IO多路复用技术可以显著提高服务器的并发处理能力,尤其是在处理大量并发连接时。它使得服务器能够更有效地利用现有的硬件资源,从而提供更好的性能和响应速度。
Django的功能有哪些?
Web应用开发:Django提供了一套完整的开发工具和框架,可以帮助开发者快速构建功能丰富、高效稳定的Web应用。它包含了许多常用的组件和功能,如URL路由、模板引擎、表单处理、用户认证、数据库操作等,大大简化了Web应用的开发流程。
数据库管理:Django提供了强大的数据库管理功能,支持多种常用数据库后端,如MySQL、PostgreSQL、SQLite等。它提供了ORM(对象关系映射)工具,可以方便地进行数据库操作,不需要编写复杂的SQL语句,提高了开发效率。
后台管理:Django自带了一个强大的后台管理系统,可以快速生成管理界面,方便管理和操作数据。开发者只需要定义数据模型,Django就会自动生成对应的管理界面,支持数据的增删改查等操作。
API开发:Django可以用于开发RESTful API,支持数据的序列化和反序列化,可以方便地与前端或其他应用进行数据交互。它提供了一些常用的API开发工具和库,如Django REST framework,可以快速构建强大的API。
安全性和性能:Django注重安全性和性能优化,提供了一些安全机制和最佳实践,如跨站点请求伪造(CSRF)防护、SQL注入防护、密码加密等。同时,Django也提供了一些性能优化的功能和工具,如缓存机制、数据库查询优化等,可以提高应用的性能和响应速度。
Java的多线程怎么实现?
实现方式很简单,只需要创建一个类去继承Thread类然后重写run方法,在main方法中调用该类实例对象的start方法即可实现多线程并发。
- 实现Runable接口
- 使用Callable和Future接口(与Runnable类似,但可以有返回值,并且可以处理异常)
谈谈java的线程池?
使用线程池的优点
(1)降低资源的消耗:线程可以重复使用,不需要在创建线程和消耗线程上浪费资源;
(2)提高响应速度:任务到达时,线程可以复用已有的线程,及时响应;
(3)可管理性:无限制的创建线程会降低系统效率,线程池可以对线程进行管理、监控、调优。
线程池的参数是什么?
ThreadPoolExecutor是线程池最核心的一个类,我们来看它参数最完整的构造类,代码如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
达到最大线程maximumPoolSize后(此时队列已经存满),再有新任务提交,执行的处理策略。
Python用concurrent.futures来创建线程池
参数为:
· max_workers:
指定线程池中允许的最大线程数。默认为 None,表示根据需要自动调整线程数。
· thread_name_prefix:
线程名字的前缀。如果指定了此参数,每个线程的名字将以指定的前缀开头,帮助区分线程池中的不同线程。
· initializer:
如果指定了,将调用此可调用对象初始化每个线程。默认为 None,表示不执行任何初始化操作。
· initargs:
传递给 initializer 函数的参数元组。默认为空元组 ()。
IOC、AOP、Bean循环依赖是什么?
· IOC(Inversion of Control,控制反转):
· IOC 是一种设计模式,其核心思想是反转程序的控制权,即由框架控制程序的执行流程,而不是传统的应用程序直接控制。在实践中,IOC 通常通过依赖注入(Dependency Injection,DI)来实现,即框架负责创建和管理对象之间的依赖关系,而不是由程序员在代码中显式地创建对象或管理依赖。
· AOP(Aspect-Oriented Programming,面向切面编程):
· AOP 是一种编程范式,用于分离关注点(cross-cutting concerns),如日志记录、事务管理等,与核心业务逻辑的关注点分离。AOP 的核心概念是切面(Aspect),它通过在代码中插入横切点(join points)和通知(advice)来实现这种分离,从而提高代码的模块化和可维护性。
· Bean循环依赖:
· 在Spring框架中,Bean循环依赖指的是两个或多个Bean之间相互依赖形成的循环依赖关系。具体来说,当Bean A依赖于Bean B,同时Bean B又依赖于Bean A时,就形成了Bean的循环依赖。Spring框架通过使用三级缓存(三级Map)来解决循环依赖问题:第一级缓存存放正在创建的Bean实例,第二级缓存存放已经创建但未初始化的Bean实例,第三级缓存存放已经创建且已经初始化的Bean实例。
MySQL索引,存储引擎,事务及其原理,MVCC,锁机制是什么?怎么调优?
MySQL索引
MySQL索引是一种用于加快数据检索速度的数据结构,它类似于书籍的目录,可以快速找到需要的数据。常见的索引类型包括B-tree索引、哈希索引、全文索引等。索引的设计要考虑数据的访问模式和查询需求,过多或不合适的索引可能导致性能下降。
MySQL支持多种存储引擎,例如InnoDB、MyISAM、Memory等,每种存储引擎具有不同的特性和适用场景。例如,InnoDB支持事务和行级锁,适合事务处理,而MyISAM适合读密集型操作。
事务是数据库操作的一个逻辑单元,要么全部执行成功,要么全部回滚(撤销)。事务通过ACID特性(原子性、一致性、隔离性、持久性)来保证数据的完整性和并发控制。
MVCC(多版本并发控制)
MVCC是一种数据库并发控制的机制,用于在读取操作时保证事务的隔离性而不会阻塞其他事务。通过保存数据的历史版本,读操作可以访问数据的快照,从而提高并发性能。
redis持久化策略和哨兵机制,常见缓存 是什么? 高并发场景,高可用,高性能 是什么?
Redis支持两种主要的持久化策略:RDB(Redis Database Backup)和AOF(Append-Only File)。
RDB持久化:定期将内存中的数据快照保存到磁盘上的RDB文件中。适合用于备份、灾难恢复等场景,可以节省磁盘空间。
AOF持久化:通过记录Redis服务器接收到的每个写操作来记录数据变更,以追加的方式将写操作记录到文件中。适合用于数据的持久化和恢复,保证每个写操作的持久性。
高性能,指的是查询快
高可用(High Availability),高可用指的是在节点故障时,服务仍然能正常运行或者进行降级后提供部分服务
高并发(redis cluster.集群)
R abbit 削峰限流,日志收集和分发 是什么?
日志收集: 集群实现请求分流,由于线上或多或少会出现请求失败或系统异常,为了查看失败请求的日志信息,我们得将所有服务的日志文件都打开来进行问题的定位分析,操作起来非常麻烦。因此,我们开发组决定设计一套日志查看系统来解决上述问题。
项目使用的 Logback 日志框架和 RabbitMQ 消息队列,这两者正好可以进行整合。
因此,我们可以将项目代码中的日志输出到 RabbitMQ 队列中,通过 Logstash 读取队列数据,最后再输出到一个日志文件中。
b站 python开发模拟题目
技术面 面试题(Python)
自我介绍以及你在上一家公司是做什么内容的
面试官你好,我叫陈祖旭,毕业于广东技术师范大学,从事python全栈开发半年,经历了1家公司,负责web全栈开发、自动化工具脚本开发,技术栈主要以Python为主,学习过java和JavaScript,其中使用Python做过一些辅助小工具(爬dejob平台,科学上网自动签到、),平时也看些博客(写博客)和视频,有一定的web3开发经验,大学曾通过web3赚取一些收入,喜欢研究一些新技术,目前处于已离职阶段。
你在这家公司做过哪些项目?
我曾负责开发射雕游戏的数据管理系统。这个游戏有大量复杂的数据需要处理,来自不同的开发同事导出的数据需要统一管理和处理。
我的任务是设计并实现一个高效的数据管理系统,能够有效接收、处理和展示各种格式的游戏数据,确保数据准确反映游戏中的各种元素和动态变化。
· 搭建了前后端开发环境,选择并配置了适合的框架和第三方库,同时配置Linux服务器环境,以确保开发流程顺利进行。
· 利用asyncio和aiomysql的事件循环机制和异步IO,定时将数万条游戏数据插入到MySQL数据库,最大限度地利用Linux节点的CPU、内存和磁盘资源,避免资源浪费。
· 参与项目需求分析和数据库设计,负责开发任务数据、资源分布、产业资源、奖励查询和角色库等多个模块。
· 使用多线程将数据分批处理,并通过优化查询语句和使用Set来替代list进行重复数据查询,以及将字段字符串拼接并进行base64编码,大幅提升了文件导入速度。
· 独立开发了整个项目的前端部分,负责实现界面效果、交互和功能,并封装了基于axios的网络请求类,提高了代码的复用性。
· 对已完成的功能模块进行了白盒测试、接口测试和性能测试,确保系统的稳定性和可靠性。
通过以上努力,成功建立了一个高效的游戏数据管理系统。团队成员能够方便地导入和处理他们的数据,并通过直观的界面快速查看和分析游戏数据。这显著提升了团队的工作效率,保证了游戏开发进度和质量,同时增强了团队的协作和沟通。
这些项目都使用了哪些技术?
第一个项目python、django、numpy、requests、pandas 、pytz 、openpyxl、mysql、mongodb、xlrd
在最近的项目中你担任什么角色,是核心开发还是辅助开发?
核心开发
作为这个项目的核心开发,模块基本我负责开发。
项目详情:NPC占名系统
输入信息查询mysql已有的数据,渲染到el-table
Excel,可下载最新的npc数据的xlsx表格,也可以上传新的excel表格上去,渲染到el-table,可以保存这最新的数据
qa工具
公告发布,发布最新的游戏开发进度公告
盒子消息推送,访问盒子的http接口
资源分布
可以手动定位获取地图上的资源分布
装扮
使用初始角色模型,对其进行游戏穿搭,例如切换不同的眼型、
套装(道袍、圆领袍、齐胸襦裙、宋制三件套)、帽子、发型等,可在线观看穿搭后的效果。
角色库
输入角色id或角色名获取角色詳情
立绘:
加载失败
名字:
角色种类
角色种类
体型
体型种类
声优:
年龄:
身份:
音色:
性格:
集群控制
cmd 游戏指令查看平台
查看游戏开发一些内部指令
任务数据
任务场景查询、任务副本信息查询、任务物品查询、主任务查询
模拟抽卡
模拟游戏抽到的概率
抽卡参数
基础参数
抽取次数:
2
卡池类型:
3
up池编号:
1
伪随机值:
0.00001
非酋参数
欧皇参数
抽取结果
单次结果
暂无数据
统计数据区域
落入伪随机区间次数:4
落入伪随机区间概率:0.05
欧皇次数:1
非酋次数:0
第几次抽标记为欧皇:[ 44 ]
第几次抽标记为非酋:[]
第几次落入伪随机区:
产业资源
产业游戏资源
产业需求
产业实装
这个模块是指,玩家自建房产、资产。
产业游戏资源是指基建、家具、交互景观、建筑、名家经营这类模型路径、名称的存储
产业需求是指游戏玩家需要另外的资源
产业实装是根据游戏玩家需要的已经上线的资源
奖励查询
查询所有的游戏奖励信息,包括任务奖励、活动奖励、等级奖励等
模型信息
存储所有的游戏模型
RoleInfo存储所有角色的背景介绍
总共11个模块:NPC占名系统、Qa工具、资源分布、装扮、角色库、集群控制、CMD、任务数据、模拟抽卡、产业资源、奖励查询、模型信息、RoleInfo
你觉得你最近这个项目还有哪些技术上的优化点,为什么?
请描述一下你熟悉的所有web开发框架,他们分别有什么优缺点?
Vue.js 是一个轻量级、灵活且易于学习
- 易学易用:简洁的语法和清晰的文档使得
- 灵活的和渐进式:可以推论
- 双向数据和组件化:简化了数据与视图的同步,提高了代码的可维护性和可用性。
- 虚拟DOM和性能:通过虚拟DOM技术和响应式数据绑定提供了优秀的性能表现。
- 活跃的社区和丰富的形态:强大的社区支持和愚蠢的插件和工具。
然而,Vue.js 也存在一些挑战和限制:
- 宇宙巨人:相比竞争对手,某些特定领域支持可能不足。
- 复杂应用优势人力资源管理曲线:在处理复杂状态和高级特性时,需要额外的学习和库支持。
- 版本的变动:在版本升级时引入不兼容的变化,需要开发团队额外的成本。
Django是一个功能强大、适合快速开发和数据驱动应用的Web框架。它的优势在于大量开放的即用功能、高安全性以及庞大的社区支持。然而,Django在校园里也有一些缺点,如学习曲线较陡、过于"重量级"以及在处理高并发时可能出现的性能问题。
简述MVC和MVT
同上
-
Flask框架中的Local对象和threading.local对象的区别:
- Flask中的Local对象 (
flask.g
和flask.current_app
) 是线程安全的上下文变量,适用于处理同一线程中的请求上下文。 threading.local
对象是 Python 标准库中的线程本地存储,也用于存储线程特定的数据,但需要手动管理生命周期和清理。
- Flask中的Local对象 (
-
在Flask框架中实现WebSocket需要什么组件?
- Flask-SocketIO 或者 Flask-Sockets 可以用来在 Flask 中实现 WebSocket。
-
beautifulsoup在项目中如何使用的?
- BeautifulSoup 用于解析 HTML/XML 数据,可以通过其提供的 API 来遍历和操作文档树,从中提取所需数据。
-
selenium在项目中如何使用?
- Selenium 用于自动化浏览器操作,可用于测试和爬虫。在项目中,可以编写脚本来控制浏览器,执行用户行为或者爬取动态内容。
-
scrapy的生命周期,以及如何实现记录爬虫的深度?
- Scrapy 的生命周期包括从请求生成到处理完成的整个过程。可以通过自定义中间件或者扩展来记录爬虫的深度,比如在请求的 meta 中存储深度信息。
-
django开发中数据做过什么优化?
- 数据库查询的优化(索引、优化查询语句)、缓存使用、减少数据库连接次数、优化 ORM 使用等。
-
浅拷贝和深拷贝之间的区别是什么?
- 浅拷贝复制对象及其引用,但不复制对象中的嵌套对象;深拷贝复制对象及其所有嵌套对象,生成完全独立的新对象。
-
列表和元组之间的区别是什么?
- 列表可变,元组不可变;列表使用方括号
[ ]
,元组使用圆括号( )
;列表适合用于存储多个相同类型的数据,元组适合用于不可变的数据集合。
- 列表可变,元组不可变;列表使用方括号
-
请解释一下Python中的三元运算子:
- Python 的三元运算子形式为
value_if_true if condition else value_if_false
,根据条件的真假返回不同的值。
- Python 的三元运算子形式为
-
python里match与search的区别是什么?
match
从字符串开始位置匹配,search
在整个字符串中搜索匹配项。
-
python的pass是什么?
pass
是一个空语句,不做任何操作,主要用于语法完整性,或者作为占位符。
-
python中的闭包是什么?
- 闭包是指在一个函数内部定义的函数,可以访问该函数的局部变量,并且可以在函数外部调用和使用。
-
在python中如何实现单例模式?
- 可以使用装饰器、元类、模块级别的变量等方式实现单例模式,确保一个类只有一个实例存在。
-
说一下python中变量的作用域。
- Python 中变量的作用域分为全局作用域和局部作用域,可以通过
global
和nonlocal
关键字修改作用域。
- Python 中变量的作用域分为全局作用域和局部作用域,可以通过
-
项目之初有没有做过压测,使用什么样的压测方案?
- 压测一般使用 Apache Bench (ab)、JMeter、locust 等工具,测试系统在不同负载下的性能表现。
-
你在生产环境中最常用的10个类似命令是什么?
- 例如
ls
,ps
,grep
,top
,tail
,awk
,sed
,chmod
,cp
,mv
等,用于文件操作、进程管理、文本处理等。
- 例如
-
python函数参数调用的使用,是值传递还是引用传递?
- Python 的函数参数传递是对象的引用传递,但是对于不可变类型(如整数、字符串、元组),相当于值传递。
-
请描述一下Python的垃圾回收机制和内存管理?
- Python 使用引用计数和标记清除两种机制进行垃圾回收,同时有自动内存管理机制(通过 Python 解释器的内存管理器来管理对象的分配和释放)。
-
redis支持几种数据类型?
- Redis 支持字符串 (String)、哈希 (Hash)、列表 (List)、集合 (Set) 和有序集合 (Sorted Set)。
-
redis缓存淘汰的策略是什么?
- Redis 缓存淘汰策略包括 LRU (Least Recently Used)、LFU (Least Frequently Used)、TTL (Time-To-Live)。
-
mysql中的myisam与innodb的区别?
- MyISAM 支持全文索引和压缩,但不支持事务和行级锁;InnoDB 支持事务、行级锁和外键约束。
-
在python项目开发中,如何避免mysql注入,能解释一下线程池的工作原理。
- 避免 SQL 注入可以使用参数化查询或者 ORM 框架;线程池通过维护一定数量的线程,管理并复用这些线程来执行异步任务,提高效率。
-
请问你们在项目中有没有使用到数据库线程池,都是怎么来实现的?
- 可以使用 Python 标准库的
ThreadPoolExecutor
或者第三方库来实现数据库连接的线程池管理,确保数据库连接的复用和效率。
- 可以使用 Python 标准库的
-
在你开发的项目中,有没有进行过数据库优化,都是怎么进行优化的?
- 数据库优化可以包括索引优化、查询优化、缓存优化、分区表等措施,根据具体情况选择合适的优化方案。
-
现在要求在一个月的时间内把python2的项目完全转换到python3,都有哪些地方该注意的?
- 需要注意的点包括语法差异(比如 print 函数)、标准库的改动、字符串和字节处理、Unicode 支持、除法操作、迭代器和生成器等的改变,以及依赖库的兼容性等
redis面试题
1.说说 Redis 基本数据类型有哪些吧
1.字符串:redis没有直接使用C语言传统的字符串表示,而是自己实现的叫做简单动态字符串SDS的抽象类型。C语言的字符串不记录自身的长度信息,而SDS则保存了长度信息,这样将获取字符串长度的时间由O(N)降低到了O(1),同时可以避免缓冲区溢出和减少修改字符串长度时所需的内存重分配次数。
2.链表linkedlist:redis链表是一个双向无环链表结构,很多发布订阅、慢查询、监视器功能都是使用到了链表来实现,每个链表的节点由一个listNode结构来表示,每个节点都有指向前置节点和后置节点的指针,同时表头节点的前置和后置节点都指向NULL。
3.字典hashtable:用于保存键值对的抽象数据结构。redis使用hash表作为底层实现,每个字典带有两个hash表,供平时使用和rehash时使用,hash表使用链地址法来解决键冲突,被分配到同一个索引位置的多个键值对会形成一个单向链表,在对hash表进行扩容或者缩容的时候,为了服务的可用性,rehash的过程不是一次性完成的,而是渐进式的。
4.跳跃表skiplist:跳跃表是有序集合的底层实现之一,redis中在实现有序集合键和集群节点的内部结构中都是用到了跳跃表。redis跳跃表由zskiplist和zskiplistNode组成,zskiplist用于保存跳跃表信息(表头、表尾节点、长度等),zskiplistNode用于表示表跳跃节点,每个跳跃表的层高都是1-32的随机数,在同一个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是唯一的,节点按照分值大小排序,如果分值相同,则按照成员对象的大小排序。
5.整数集合intset:用于保存整数值的集合抽象数据结构,不会出现重复元素,底层实现为数组。
6.压缩列表ziplist:压缩列表是为节约内存而开发的顺序性数据结构,他可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
基于这些基础的数据结构,redis封装了自己的对象系统,包含字符串对象string、列表对象list、哈希对象hash、集合对象set、有序集合对象zset,每种对象都用到了至少一种基础的数据结构。
redis通过encoding属性设置对象的编码形式来提升灵活性和效率,基于不同的场景redis会自动做出优化。不同对象的编码如下:
1.字符串对象string:int整数、embstr编码的简单动态字符串、raw简单动态字符串
2.列表对象list:ziplist、linkedlist
3.哈希对象hash:ziplist、hashtable
4.集合对象set:intset、hashtable
5.有序集合对象zset:ziplist、skiplist
2.Redis 为什么快呢?
redis的速度非常的快,单机的redis就可以支撑每秒10几万的并发,相对于mysql来说,性能是mysql的几十倍。速度快的原因主要有几点:
1.完全基于内存操作
2.C语言实现,优化过的数据结构,基于几种基础的数据结构,redis做了大量的优化,性能极高
3.使用单线程,无上下文的切换成本
4.基于非阻塞的IO多路复用机制
3.那为什么 Redis6.0 之后又改用多线程呢?
redis使用多线程并非是完全摒弃单线程,redis还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程。
这样做的目的是因为redis的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高redis的性能。
4.知道什么是热 key 吗?热 key 问题怎么解决?
所谓热key问题就是,突然有几十万的请求去访问redis上的某个特定key,那么这样会造成流量过于集中,达到物理网卡上限,从而导致这台redis的服务器宕机引发雪崩。
![](https://img-blog.csdnimg.cn/img_convert/ad977d36a789193dee7f9f12c88bfae4.png)
针对热key的解决方案:
1.提前把热key打散到不同的服务器,降低压力
2.加入二级缓存,提前加载热key数据到内存中,如果redis宕机,走内存查询
5.什么是缓存击穿、缓存穿透、缓存雪崩?
缓存击穿
缓存击穿的概念就是单个key并发访问过高,过期时导致所有请求直接打到db上,这个和热key的问题比较类似,只是说的点在于过期导致请求全部打到DB上而已。
解决方案:
1.加锁更新,比如请求查询A,发现缓存中没有,对A这个key加锁,同时去数据库查询数据,写入缓存,再返回给用户,这样后面的请求就可以从缓存中拿到数据了。
2.将过期时间组合写在value中,通过异步的方式不断的刷新过期时间,防止此类现象。
3、用久缓存;
![](https://img-blog.csdnimg.cn/img_convert/354be5b379a2b85e8ac124007150c3a3.png)
缓存穿透
缓存穿透是指查询不存在缓存中的数据,每次请求都会打到DB,就像缓存不存在一样。
![](https://img-blog.csdnimg.cn/img_convert/a8e8dbf8f853dc5adefc8a947448fa67.png)
1、无意义数据放入缓存,下一次相同请求就会命中缓存; 2、IP过滤; 3、参数校验;
4、针对这个问题,加一层布隆过滤器。布隆过滤器的原理是在你存入数据的时候,会通过散列函数将它映射为一个位数组中的K个点,同时把他们置为1。
这样当用户再次来查询A,而A在布隆过滤器值为0,直接返回,就不会产生击穿请求打到DB了。
显然,使用布隆过滤器之后会有一个问题就是误判,因为它本身是一个数组,可能会有多个值落到同一个位置,那么理论上来说只要我们的数组长度够长,误判的概率就会越低,这种问题就根据实际情况来就好了。
![](https://img-blog.csdnimg.cn/img_convert/66ff2b241b19c8227402c3d98ef11219.png)
缓存雪崩
当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上,这样可能导致整个系统的崩溃,称为雪崩。雪崩和击穿、热key的问题不太一样的是,他是指大规模的缓存都过期失效了。
![](https://img-blog.csdnimg.cn/img_convert/01f9277b6a7ed4234e7184b248088af9.png)
针对雪崩几个解决方案:
1.针对不同key设置不同的过期时间,避免同时过期
2.限流,如果redis宕机,可以限流,避免同时刻大量请求打崩DB
3.二级缓存,同热key的方案。
6.Redis 的过期策略有哪些?
redis主要有2种过期删除策略
惰性删除
惰性删除指的是当我们查询key的时候才对key进行检测,如果已经达到过期时间,则删除。显然,他有一个缺点就是如果这些过期的key没有被访问,那么他就一直无法被删除,而且一直占用内存。
![](https://img-blog.csdnimg.cn/img_convert/3e24bb3d97d79e44d6691e46c1358dcf.png)
定期删除
定期删除指的是redis每隔一段时间对数据库做一次检查,删除里面的过期key。由于不可能对所有key去做轮询来删除,所以redis会每次随机取一些key去做检查和删除。
7.那么定期+惰性都没有删除过期的 key 怎么办?
假设redis每次定期随机查询key的时候没有删掉,这些key也没有做查询的话,就会导致这些key一直保存在redis里面无法被删除,这时候就会走到redis的内存淘汰机制。
1.volatile-lru:从已设置过期时间的key中,移除最近最少使用的key进行淘汰
2.volatile-ttl:从已设置过期时间的key中,移除将要过期的key
3.volatile-random:从已设置过期时间的key中随机选择key淘汰
4.allkeys-lru:从key中选择最近最少使用的进行淘汰
5.allkeys-random:从key中随机选择key进行淘汰
6.noeviction:当内存达到阈值的时候,新写入操作报错
8.持久化方式有哪些?有什么区别?
redis持久化方案分为RDB和AOF两种。
RDB
RDB持久化可以手动执行也可以根据配置定期执行,它的作用是将某个时间点上的数据库状态保存到RDB文件中,RDB文件是一个压缩的二进制文件,通过它可以还原某个时刻数据库的状态。由于RDB文件是保存在硬盘上的,所以即使redis崩溃或者退出,只要RDB文件存在,就可以用它来恢复还原数据库的状态。
可以通过SAVE或者BGSAVE来生成RDB文件。
SAVE命令会阻塞redis进程,直到RDB文件生成完毕,在进程阻塞期间,redis不能处理任何命令请求,这显然是不合适的。
BGSAVE则是会fork出一个子进程,然后由子进程去负责生成RDB文件,父进程还可以继续处理命令请求,不会阻塞进程。
AOF
AOF和RDB不同,AOF是通过保存redis服务器所执行的写命令来记录数据库状态的。
AOF通过追加、写入、同步三个步骤来实现持久化机制。
1.当AOF持久化处于激活状态,服务器执行完写命令之后,写命令将会被追加append到aof_buf缓冲区的末尾
2.在服务器每结束一个事件循环之前,将会调用flushAppendOnlyFile函数决定是否要将aof_buf的内容保存到AOF文件中,可以通过配置appendfsync来决定。
always ##aof_buf内容写入并同步到AOF文件
everysec ##将aof_buf中内容写入到AOF文件,如果上次同步AOF文件时间距离现在超过1秒,则再次对AOF文件进行同步
no ##将aof_buf内容写入AOF文件,但是并不对AOF文件进行同步,同步时间由操作系统决定
如果不设置,默认选项将会是everysec,因为always来说虽然最安全(只会丢失一次事件循环的写命令),但是性能较差,而everysec模式只不过会可能丢失1秒钟的数据,而no模式的效率和everysec相仿,但是会丢失上次同步AOF文件之后的所有写命令数据。
9.怎么实现 Redis 的高可用?
要想实现高可用,一台机器肯定是不够的,而redis要保证高可用,有2个可选方案。
主从架构
主从模式是最简单的实现高可用的方案,核心就是主从同步。主从同步的原理如下:
1.slave发送sync命令到master
2.master收到sync之后,执行bgsave,生成RDB全量文件
3.master把slave的写命令记录到缓存
4.bgsave执行完毕之后,发送RDB文件到slave,slave执行
5.master发送缓存中的写命令到slave,slave执行
![](https://img-blog.csdnimg.cn/img_convert/d04a8d24f73281a5cd99ea494c32e67c.png)
这里我写的这个命令是sync,但是在redis2.8版本之后已经使用psync来替代sync了,原因是sync命令非常消耗系统资源,而psync的效率更高。
哨兵
基于主从方案的缺点还是很明显的,假设master宕机,那么就不能写入数据,那么slave也就失去了作用,整个架构就不可用了,除非你手动切换,主要原因就是因为没有自动故障转移机制。而哨兵(sentinel)的功能比单纯的主从架构全面的多了,它具备自动故障转移、集群监控、消息通知等功能。
哨兵可以同时监视多个主从服务器,并且在被监视的master下线时,自动将某个slave提升为master,然后由新的master继续接收命令。整个过程如下:
1.初始化sentinel,将普通的redis代码替换成sentinel专用代码
2.初始化masters字典和服务器信息,服务器信息主要保存ip:port,并记录实例的地址和ID
3.创建和master的两个连接,命令连接和订阅连接,并且订阅sentinel:hello频道
4.每隔10秒向master发送info命令,获取master和它下面所有slave的当前信息
5.当发现master有新的slave之后,sentinel和新的slave同样建立两个连接,同时每个10秒发送info命令,更新master信息
6.sentinel每隔1秒向所有服务器发送ping命令,如果某台服务器在配置的响应时间内连续返回无效回复,将会被标记为下线状态
7.选举出领头sentinel,领头sentinel需要半数以上的sentinel同意
8.领头sentinel从已下线的的master所有slave中挑选一个,将其转换为master
9.让所有的slave改为从新的master复制数据
10.将原来的master设置为新的master的从服务器,当原来master重新回复连接时,就变成了新master的从服务器
sentinel会每隔1秒向所有实例(包括主从服务器和其他sentinel)发送ping命令,并且根据回复判断是否已经下线,这种方式叫做主观下线。当判断为主观下线时,就会向其他监视的sentinel询问,如果超过半数的投票认为已经是下线状态,则会标记为客观下线状态,同时触发故障转移。
10.能说说 redis 集群的原理吗?
如果说依靠哨兵可以实现redis的高可用,如果还想在支持高并发同时容纳海量的数据,那就需要redis集群。redis集群是redis提供的分布式数据存储方案,集群通过数据分片sharding来进行数据的共享,同时提供复制和故障转移的功能。
节点
一个redis集群由多个节点node组成,而多个node之间通过cluster meet命令来进行连接,节点的握手过程:
1.节点A收到客户端的cluster meet命令
2.A根据收到的IP地址和端口号,向B发送一条meet消息
3.节点B收到meet消息返回pong
4.A知道B收到了meet消息,返回一条ping消息,握手成功
5.最后,节点A将会通过gossip协议把节点B的信息传播给集群中的其他节点,其他节点也将和B进行握手
![](https://img-blog.csdnimg.cn/img_convert/62b7ba27aee1cda245d3ff99a3aa0747.png)
槽 slot
redis通过集群分片的形式来保存数据,整个集群数据库被分为16384个slot,集群中的每个节点可以处理0-16383个slot,当数据库16384个slot都有节点在处理时,集群处于上线状态,反之只要有一个slot没有得到处理都会处理下线状态。通过cluster addslots命令可以将slot指派给对应节点处理。
slot是一个位数组,数组的长度是16384/8=2048,而数组的每一位用1表示被节点处理,0表示不处理,如图所示的话表示A节点处理0-7的slot。
![](https://img-blog.csdnimg.cn/img_convert/897e7c6e358d139baaf192c7fd1b2435.png)
当客户端向节点发送命令,如果刚好找到slot属于当前节点,那么节点就执行命令,反之,则会返回一个MOVED命令到客户端指引客户端转向正确的节点。(MOVED过程是自动的)
![](https://img-blog.csdnimg.cn/img_convert/0beb2f85bb4b073b7b3b0eea1de645f9.png)
如果增加或者移出节点,对于slot的重新分配也是非常方便的,redis提供了工具帮助实现slot的迁移,整个过程是完全在线的,不需要停止服务。
故障转移
如果节点A向节点B发送ping消息,节点B没有在规定的时间内响应pong,那么节点A会标记节点B为pfail疑似下线状态,同时把B的状态通过消息的形式发送给其他节点,如果超过半数以上的节点都标记B为pfail状态,B就会被标记为fail下线状态,此时将会发生故障转移,优先从复制数据较多的从节点选择一个成为主节点,并且接管下线节点的slot,整个过程和哨兵非常类似,都是基于Raft协议做选举。
11.了解 Redis 事务机制吗?
redis通过MULTI、EXEC、WATCH等命令来实现事务机制,事务执行过程将一系列多个命令按照顺序一次性执行,并且在执行期间,事务不会被中断,也不会去执行客户端的其他请求,直到所有命令执行完毕。事务的执行过程如下:
1.服务端收到客户端请求,事务以MULTI开始
2.如果客户端正处于事务状态,则会把事务放入队列同时返回给客户端QUEUED,反之则直接执行这个命令
3.当收到客户端EXEC命令时,WATCH命令监视整个事务中的key是否有被修改,如果有则返回空回复到客户端表示失败,否则redis会遍历整个事务队列,执行队列中保存的所有命令,最后返回结果给客户端
WATCH的机制本身是一个CAS的机制,被监视的key会被保存到一个链表中,如果某个key被修改,那么REDIS_DIRTY_CAS标志将会被打开,这时服务器会拒绝执行事务。