文章目录
实战应用
推荐系统
比如音乐推荐,有两种方式:
- 找口味相似的用户,推荐他们爱听的歌
- 推荐跟喜爱歌曲特征相似的歌
基于相似用户推荐
对用户的歌曲进行评分:5(单曲循环)、4(喜爱)、3(收藏)、2(搜索)、1(听完)、0(没听过)、-1(跳过),则:
欧几里得距离越小,用户口味越相近,更优先推荐用户口味相近的人听过的歌曲
基于相似歌曲推荐
同理对歌曲计算欧几里得距离:
数据库索引
设计考虑
- 数据是格式化数据还是非格式化数据,对于非结构化数据,需要预处理提取关键词,对关键词构建索引
- 数据是静态数据还是动态数据,对于静态数据只需考虑查询效率,对于动态数据还需考虑更新索引成本
- 数据量大还是小,对于少量索引存内存效率高,若索引过大则需存在磁盘上
- 是否支持区间查找
常用索引结构
- 散列表:增删改查时间复杂度O(1),适用于构建键值数据库,如Redis、Memcache,在内存中构建
- 红黑树:增删改查复杂度O(logn),适用于构建内存索引,如磁盘块索引
- B+树:更适用构建在磁盘中的索引,因为相比红黑树高度低,IO次数更少,如MySQL、Oracle
- 跳表:可以很好平衡索引对内存的消耗及其查询效率,如Redis中的有序集合
- 布隆过滤器、位图:用于加速索引查询效率
Redis背后数据结构
Redis常见结构包括字符串、 列表、字典、集合、有序集合
列表
列表支持存储一组数据,当列表中单个数据小于 64 字节且列表数据个数少于 512 个时,采用压缩列表实现,否则采用双向循环链表
压缩列表(类似于网络协议中的报文),特点是节省内存、支持多种类型数据,而且数据连续存储,读取效率高
字典
用来存储一组数据对,每对包含键、值两部分 ,当键、值均小于 64 字节且字典中键值对少于 512 个时,采用压缩列表实现,否则采用散列表
散列表采用MurmurHash2作为哈希函数,采用链表法避免hash冲突,支持动态扩容和缩容(当装载因子大于1扩大为原来2倍;当装载因子小于0.1就缩小为字典中数据个数的2倍)
集合
用于存储一组不重复数据,当数据都为整数且个数不超过512,采用有序数组实现,否则采用散列表
有序集合
所有数据都小于64字节且个数小于128个,采用压缩列表实现,否则采用跳表
搜索引擎
搜索引擎大致可以分为四个部分:搜集、分析、索引、查询,搜集就是利用爬虫爬取网页, 分析就是通过网页抽取分词来构建临时索引,索引通过临时索引来构建倒排索引, 查询负责响应用户的请求,根据倒排索引获取相关网页并返回给用户
搜集
利用广度优先搜素(BFS), 先将一些网页放入队列,每次取出一个网页进行爬取,若其中包含另一个页面链接就解析出来添加到队列中(可以把页面看做一串字符,利用字符串匹配算法获取里面的网页链接)
把队列持久化到文件,优点是断电后网页链接不会丢失,还支持断点续爬
使用布隆过滤器避免重复爬取,避免宕机可以定期将布隆过滤器持久化到文件
对每个网页分唯一ID编号,然后将网页链接和编号间对应关系单独持久化到一个文件
把爬到的所有原始网页存储到文件中(按一定格式存放网页编号、网页大小、网页内容),文件大小超过1G自动创建新文件存储
分析
抽取网页文本信息:
- 去掉js、css格式及下拉框内容(利用AC自动机一次性查找并删除
<style>、<script>、<option>
三个标签及里面的内容 - 去除所有html标签
分词并创建临时索引:
- 对于英文网页:通过空格、标点符号将单词分隔开
- 对于中文网页:基于词库建立字典树,然后采用最长匹配规则匹配来分割词语
最后将得到的单词编号和网页编号对应关系写入文件作为临时索引,单词编号和对应单词存放到散列表中(便于查找是否已有)
索引
将临时索引构建成倒排索引,用于记录每个单词及包含它的网页列表
由于临时索引很大无法一次性加载到内存,所以采用基于磁盘的多路归并排序,最后汇总成倒排索引
最后还需持久化单词编号在倒排索引中的偏移,使得我们可以快速从倒排索引中找到单词及对应网页列表
查询
我们把之前记录网页链接和网页编号间对应关系、单词和单词编号间对应关系、单词编号在倒排索引中偏移的三个文件加载到内存并组织成散列表(占用内存少,便于快速查找)
用户查询流程:
- 对用户输入内容进行分词处理,得到k个单词
- 根据得到的k个单词查询对应k个单词编号
- 根据单词编号查询对应单词编号在倒排索引中的偏移
- 在倒排索引中查询得到k个单词编号对应的网页列表(IO操作)
- 将k个网页列表按照出现次数进行合并排序(次数越多表明关联性越强)
- 最后拿网页列表中的网页编号去查询对应网页(IO操作)
接口鉴权
在分布式中,对于RPC、Http接口,常需要鉴权来判定用户是否有接口访问权限。 比如http接口,当应用访问时系统会根据请求URL在规则中匹配,若成功就说明允许访问,否则拒绝服务
精确匹配
只有当请求 URL 跟规则中某条精确匹配时,请求才会处理
我们可以先把规则初始化排序,每次匹配进行二分查找
前缀匹配
只要某条规则可以匹配请求 URL 的前缀,请求就会处理
我们可以对规则构建字典树,子节点按顺序排列,对子节点的向下查找可以通过二分
限流
固定时间窗口限流
设置一个计数器每隔1秒清零,每当来一个请求计数器++,若计数器超过阈值K,则拒绝后续请求
缺点:请求集中于上个1秒的最后和下个1秒的开始,导致爆掉
滑动时间窗口限流
可以实现在任意1秒请求数都不超过阈值k
维护一个K大小的循环队列,设最早请求下标为p,最晚请求下标为q。若(q+1)%K!=p说明队列未满,直接将新请求加入队列,然后q = (q+1)%K;
若(q+1)%K==p则代表队列已满,此时查看p坐标下的请求时间是否超过1秒,若超过则清理,然后p=(p+1)%K,q = (q+1)%K;若未超过则拒绝新请求