一、Bitmaps操作命令与UV统计案例
例:(id=0,2,4,6,8)5个用户对网站进行访问(UV)。
1.setbit 设置值
setbit key offset value
设置键的第 offset 个位的值(从0算起)。
假设现在有20个用户,userid=0,2,4,6,8的用户对网站进行了访问,存储键名为日期。
2.getbit 获取值
getbit key offset
获取键的第 offset位的值(从0开始算),比如获取userid=8的用户是否在2024(年/这天)访问过,返回0说明没有访问过:
当然offset是不存在的,也会返回0。
3.bitcount 获取Bitmaps指定范围值为1的个数
bitcount [start] [end]
下面操作计算9.30号和10.1号这天的独立访问用户数量
[start]和[end]代表起始和结束字节数
4.bitop Bitmaps 间的运算
BITOP operation destkey key [key ...]
- operation:指定要执行的按位操作类型,可以是
AND
、OR
、XOR
、NOT
。AND
:逻辑与操作。OR
:逻辑或操作。XOR
:逻辑异或操作。NOT
:逻辑非操作(注意:NOT
操作只能对单个key
进行操作)。
- destkey:指定存储操作结果的目标位图的键名。
- key:指定参与操作的位图键名,可以是一个或多个(对于
NOT
操作,只能是一个)。
假设我们有两个位图key1
和key2
,我们想要对它们执行OR
操作,并将结果存储在destkey
中。
现代计算机用二进制(位)作为信息的基础单位,1个字节等于8位,例如“big”字符串是由3个字节组成,但实际在计算机存储时将其用二进制表示,“big”分别对应的ASCII码分别是98、105、103,对应的二进制分别是01100010、01101001和 01100111。
许多开发语言都提供了操作位的功能,合理地使用位能够有效地提高内存使用率和开发效率。Redis提供了Bitmaps这个“数据结构”可以实现对位的操作。把数据结构加上引号主要因为:
Bitmaps本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的位进行操作。
Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把 Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量。
5.Bitmaps优势
假设网站有1亿用户,每天独立访问的用户有5千万,如果每天用集合类型和 Bitmaps分别存储活跃用户,很明显,假如用户id是Long型,64位,则集合类型占据的空间为64位x50 000 000= 400MB,而Bitmaps则需要1位×100 000 000=12.5MB,可见Bitmaps能节省很多的内存空间。、
l
6.面试题和场景
1、目前有10亿数量的自然数,乱序排列,需要对其排序。限制条件-在32位机器上面完成,内存限制为 2G。如何完成?
答:在32位机器上,其内存地址空间通常限制在4GB以下(实际可用内存可能因操作系统和其他因素而减少),但题目已明确给出内存限制为2GB。面对10亿数量的自然数排序问题,由于每个自然数至少需要4字节(如果是32位整数)的存储空间,10亿个自然数将占用大约40GB的内存空间,这远远超出了2GB的限制。因此,传统的内存排序算法(如快速排序、归并排序等)在此场景下不适用。
为了解决这个问题,我们可以采用外部排序算法,特别是归并排序的外部版本,这种算法可以分批次地将数据读入内存进行排序,然后再将排序后的数据写回外部存储(如硬盘)。以下是具体步骤:
- 数据分割:
- 将10亿个自然数分割成多个小块,每块的大小应确保能够完全加载到2GB的内存中。由于每个整数占用4字节,大约可以加载500,000,000(约5亿)个整数到内存中(考虑到操作系统和其他进程的内存占用,实际数字可能更少)。
- 内部排序:
- 对每个内存块中的数据进行排序。可以使用任何适合内部排序的算法,如快速排序、堆排序或归并排序等。
- 归并排序的外部版本:
- 重复地将排序后的内存块合并成更大的块,直到所有块合并成一个有序的大块。
- 在归并过程中,每次从每个待合并的块中读取一小部分数据到内存中,进行归并排序,然后将结果写回外部存储。
- 优化读写操作:
- 使用高效的I/O库和算法来减少磁盘读写次数,例如使用缓冲区和批量读写操作。
- 可能需要实现一个自定义的归并排序过程,以优化内存使用和数据交换的效率。
- 考虑使用数据库或外部存储系统:
- 如果可能,考虑使用数据库系统(如SQLite,尽管它可能不是为此类大规模数据排序而设计的)或外部存储系统(如HDFS,但这通常用于分布式环境),这些系统可能提供了一些内置的优化来处理大规模数据。
- 并行处理:
- 如果硬件资源允许(尽管在32位机器上可能有限),可以尝试使用多线程或多进程来并行处理数据块,以加速排序过程。
- 检查硬件和操作系统的限制:
- 确保操作系统和硬件设置(如磁盘I/O性能、内存管理等)能够支持这种大规模的数据处理。
总之,面对如此大规模的数据排序任务,在内存和硬件资源受限的情况下,外部排序算法是最佳的选择。通过优化数据分割、内存使用、I/O操作和可能的并行处理,可以在合理的时间内完成排序任务。
2、如何快速在亿级黑名单中快速定位URL地址是否在黑名单中?(每条URL平均64字节)
答:在亿级甚至百亿级的黑名单中快速定位URL地址是否存在于黑名单中,是一个对性能要求极高的任务。由于每条URL平均占用64字节,直接存储整个黑名单并进行遍历查找将非常消耗内存和时间。针对这个问题,可以考虑使用布隆过滤器(Bloom Filter)这种高效的数据结构。
布隆过滤器简介
布隆过滤器是一种空间效率很高的概率型数据结构,用于判断一个元素是否在一个集合中。它的核心思想是利用多个哈希函数将元素映射到一个足够大的位数组中,并标记相应的位置为1。在查询时,通过同样的哈希函数计算待查询元素的哈希值,并检查位数组中对应的位置是否都为1。如果所有位置都为1,则认为元素可能存在于集合中(存在误判率);如果有一个位置不为1,则肯定不存在于集合中。
实现步骤
确定布隆过滤器的大小:根据黑名单中URL的数量(如100亿条)和预期的误判率(如万分之一以下),计算出布隆过滤器需要的位数组大小。这通常涉及到一些数学公式和计算。
选择哈希函数:布隆过滤器需要多个哈希函数来降低误判率。这些哈希函数应该尽可能地将输入值均匀地映射到位数组的不同位置上。常见的哈希函数包括MD5、SHA1等,但在这里需要实现或选择适用于布隆过滤器的哈希函数。
构建布隆过滤器:遍历黑名单中的所有URL,对每个URL使用选定的哈希函数计算哈希值,并将位数组中对应的位置标记为1。
查询操作:对于需要查询的URL,使用相同的哈希函数计算哈希值,并检查位数组中对应的位置是否都为1。如果都为1,则认为URL可能存在于黑名单中;如果有一个不为1,则肯定不存在于黑名单中。
注意事项
- 误判率:布隆过滤器允许一定的误判率,即可能将不在黑名单中的URL误判为在黑名单中。这是布隆过滤器的一个固有特性,但通过增加位数组的大小和哈希函数的数量,可以降低误判率。
- 空间效率:布隆过滤器相比传统的哈希表等数据结构,在空间效率上有很大优势。即使数据量非常大,布隆过滤器所需的空间也远小于直接存储所有元素所需的空间。
- 实现方式:布隆过滤器可以使用多种编程语言实现,如Java、Python等。在实现时,需要注意哈希函数的选择和位数组的管理。
3、需要进行用户登陆行为分析,来确定用户的活跃情况?
答:
进行用户登录行为分析是评估用户活跃情况的重要手段之一,它有助于了解用户的使用习惯、粘性以及潜在的用户行为模式。以下是一些步骤和考虑因素,用于进行用户登录行为分析:
1. 数据收集
- 登录日志:首先,确保系统能够记录详细的登录日志,包括登录时间、登录地点(IP地址或地理位置)、登录方式(如网页、APP、第三方平台登录等)、登录结果(成功或失败)等信息。
- 用户信息:收集用户的基本信息,如用户ID、注册时间、性别、年龄、地区等,以便进行多维度的分析。
2. 数据清洗与预处理
- 去重:确保登录记录中没有重复数据。
- 异常值处理:识别并处理异常登录行为,如短时间内多次登录失败、异常地理位置登录等。
- 时间格式化:统一时间格式,便于后续的时间序列分析。
3. 活跃指标定义
- 登录频率:统计用户在一定时间内的登录次数,如日登录次数、周登录次数、月登录次数等。
- 活跃天数:统计用户在一定时间周期内(如一个月)有多少天进行了登录。
- 登录时长:如果可能,统计用户每次登录的会话时长,了解用户的使用深度。
- 回流用户:识别长时间未登录后重新登录的用户,评估用户留存情况。
4. 数据分析
- 趋势分析:观察登录行为随时间的变化趋势,了解用户活跃度的周期性变化。
- 用户画像:基于用户信息和登录行为,构建用户画像,分析不同用户群体的活跃特点。
- 行为模式识别:通过聚类分析等方法,识别用户的登录行为模式,如固定时间段登录、周末活跃等。
- 相关性分析:探索登录行为与其他用户行为(如购买、浏览等)之间的相关性,了解登录行为对业务的影响。
5. 结果应用
- 运营优化:根据分析结果,调整运营策略,如针对活跃时间段推送优惠信息、优化登录流程等。
- 个性化推荐:根据用户画像和行为模式,提供个性化的推荐和服务。
- 用户留存:针对回流用户和潜在流失用户,采取相应措施提高用户留存率。
6. 持续优化
- 反馈循环:建立反馈机制,定期回顾分析结果和运营效果,不断优化分析模型和运营策略。
- 技术升级:关注大数据和人工智能领域的新技术,如机器学习、深度学习等,探索在用户登录行为分析中的应用。
通过以上步骤,可以对用户的登录行为进行全面深入的分析,从而准确评估用户的活跃情况,为企业的运营决策提供有力支持。
4、网络爬虫-如何判断URL是否被爬过?
答:
在网络爬虫中,判断一个URL是否已经被爬过是确保数据不重复抓取、减少服务器负载和遵守robots.txt规则的重要步骤。以下是几种常见的方法来跟踪和判断URL的爬取状态:
1. 使用哈希集合(HashSet)
最简单的方法是在内存中维护一个哈希集合,用于存储已经爬取过的URL。每当爬虫遇到一个新的URL时,就检查这个URL是否已经存在于哈希集合中。如果存在,则跳过该URL;如果不存在,则添加到集合中并进行爬取。这种方法适用于数据量不是特别大的情况,因为哈希集合需要全部加载到内存中。
2. 数据库或外部存储
对于需要处理大量URL的情况,可以将已爬取的URL存储到数据库或外部存储系统(如Redis、MongoDB等)中。每当爬虫遇到一个URL时,就查询数据库或外部存储以检查该URL是否已被爬取。这种方法可以处理大规模数据,但需要考虑到查询性能和数据一致性的问题。
3. 文件记录
另一种简单的方法是使用文件来记录已爬取的URL。每当爬虫爬取一个URL时,就将该URL写入到一个文本文件中。在后续的爬取过程中,先检查该URL是否已存在于文件中。这种方法实现简单,但可能面临性能瓶颈,特别是在处理大量URL时。
4. 使用爬虫框架的内置功能
许多现代爬虫框架(如Scrapy)都提供了内置的URL去重机制。这些框架通常使用哈希集合或数据库来跟踪已爬取的URL,并提供了方便的API来配置和使用这些机制。使用这些内置功能可以大大简化开发过程,并提高爬虫的效率。
5. 分布式爬虫中的去重
在分布式爬虫系统中,由于多个爬虫实例可能同时运行,因此需要一种跨实例的去重机制。这通常通过共享存储(如Redis)来实现,每个爬虫实例都将已爬取的URL存入共享存储中,并在爬取新URL之前进行查询。
6. 遵守robots.txt
虽然robots.txt文件主要定义了哪些URL不应该被爬虫访问,但它也可以作为判断URL是否应该被爬取的一个参考。在爬取之前,应该检查目标网站的robots.txt文件,并遵守其中的规则。
结论
选择哪种方法来判断URL是否被爬过取决于具体的应用场景和需求。在开发爬虫时,应该综合考虑数据规模、性能要求、可扩展性和可维护性等因素,选择最适合自己的方法。同时,也应该遵守相关的法律法规和网站政策,确保爬虫的合法性和合规性。
5、快速定位用户属性(黑名单、白名单等)
快速定位用户属性,如黑名单、白名单等,是许多系统中常见的需求,尤其是在需要控制访问权限、防止欺诈或进行个性化服务时。以下是一些策略和技术,可以帮助你高效地实现这一目标:
1. 使用数据库索引
对于存储在数据库中的用户属性信息(如用户ID、用户名等),确保为这些字段建立索引。索引可以极大地加快查询速度,特别是在处理大量数据时。通过索引,数据库系统可以快速定位到包含特定用户属性信息的记录。
2. 缓存机制
对于频繁查询的用户属性,如黑名单和白名单,可以考虑使用缓存机制来减少数据库的访问次数。将查询结果存储在内存中的缓存系统中(如Redis、Memcached等),并在每次查询时首先检查缓存中是否存在结果。如果缓存中存在结果,则直接返回,无需访问数据库;如果不存在,则查询数据库并将结果添加到缓存中。
3. 分布式缓存和存储
在分布式系统中,可以使用分布式缓存和存储系统来存储和查询用户属性。这些系统通常具有更高的可用性和可扩展性,能够处理更大的数据量和更高的并发请求。通过将用户属性信息存储在分布式缓存中,可以实现在多个节点之间快速共享和查询数据。
4. 哈希表或布隆过滤器
对于黑名单和白名单等需要快速判断用户是否存在的场景,可以使用哈希表或布隆过滤器等数据结构。哈希表可以提供几乎常数的查询时间复杂度,但可能会占用较多的内存空间。布隆过滤器则是一种空间效率更高的概率型数据结构,可以允许一定程度的误判率来换取更高的空间效率。
5. 规则引擎
在一些复杂的系统中,用户属性可能不仅仅是简单的黑白名单关系,还可能涉及到多个条件和规则的组合。这时,可以使用规则引擎来定义和管理这些规则。规则引擎可以根据用户属性、行为和环境等因素动态地评估用户是否满足特定条件,并据此作出相应的决策。
6. 实时数据流处理
如果用户属性信息是通过实时数据流的方式获取的(如用户行为日志、实时交易数据等),则可以使用实时数据流处理技术来快速处理和分析这些数据。通过将这些数据实时地推送到处理系统中,并应用相应的算法和模型来识别用户属性,可以实现对用户属性的快速定位和更新。
7. 高效的数据结构和算法
在设计和实现用户属性查询系统时,应该选择高效的数据结构和算法来优化查询性能。例如,可以使用平衡二叉树(如AVL树、红黑树等)来存储和查询有序的用户属性信息;可以使用前缀树(Trie树)来高效地存储和查询字符串类型的用户属性信息;可以使用并行算法来加速大规模数据的处理过程等。
综上所述,快速定位用户属性需要综合考虑数据规模、查询频率、系统架构和性能要求等多个因素。通过选择合适的技术和策略,可以实现高效、准确和可扩展的用户属性查询系统。
6、数据存储在磁盘中,如何避免大量的无效IO?
在数据存储在磁盘中时,无效IO(即不必要的磁盘读写操作)是导致性能瓶颈和资源浪费的主要原因之一。为了避免大量的无效IO,可以采取以下几种策略:
1. 使用缓存机制
内存缓存:将频繁访问的数据缓存在内存中,减少对磁盘的访问次数。当需要读取数据时,首先检查缓存中是否存在所需数据,如果存在则直接返回,避免磁盘IO。
磁盘缓存:现代操作系统和磁盘控制器通常都有内置的磁盘缓存机制,它们会自动将最近访问的数据保存在磁盘缓存中。合理利用这些缓存可以减少对磁盘的直接访问。
2. 优化数据访问模式
顺序访问:尽可能地将数据组织成可以顺序访问的形式,因为顺序访问的磁盘IO效率远高于随机访问。
批量处理:将多个小的读写请求合并成较大的请求进行处理,可以减少IO操作的次数和开销。
3. 使用合适的数据结构和索引
选择合适的数据结构:根据数据的访问模式和更新频率选择合适的数据结构,如使用B树、哈希表等索引结构来加速数据查找。
优化索引:确保索引是最新的,并且针对查询进行了优化。过时的或未优化的索引可能会导致额外的磁盘IO。
4. 压缩数据
数据压缩:对存储在磁盘上的数据进行压缩可以减少所需的磁盘空间,并可能减少IO操作的次数(因为需要读写的数据量减少了)。但是,压缩和解压过程也会消耗CPU资源,因此需要权衡利弊。
5. 异步IO和并发处理
异步IO:使用异步IO可以允许程序在等待磁盘操作完成时继续执行其他任务,从而提高程序的响应性和吞吐量。
并发处理:利用多核处理器的优势,通过并发或并行处理来加速IO操作。例如,可以使用多线程或多进程来同时处理多个IO请求。
6. 预测和预取
预测:根据应用程序的访问模式来预测未来可能需要的数据,并提前将这些数据加载到缓存中。
预取:在需要之前,将预测到的数据从磁盘预取到缓存中,以减少实际访问时的等待时间。
7. 监控和调整
性能监控:定期监控系统的IO性能,识别出性能瓶颈和无效IO的来源。
动态调整:根据监控结果动态调整缓存大小、索引策略、并发级别等参数,以优化IO性能。
8. 磁盘优化
RAID配置:使用RAID(冗余阵列独立磁盘)配置可以提高磁盘的可靠性和性能。不同的RAID级别适用于不同的应用场景,例如RAID 0可以提高读写速度,而RAID 1可以提供数据冗余。
磁盘碎片整理:定期进行磁盘碎片整理可以减少磁盘寻道时间,提高读写效率。
综上所述,避免大量的无效IO需要从多个方面入手,包括使用缓存、优化数据访问模式、选择合适的数据结构和索引、压缩数据、异步IO和并发处理、预测和预取、监控和调整以及磁盘优化等。通过综合运用这些策略,可以显著提高磁盘IO的性能和效率。
传统数据结构的不足
当然有人会想,我直接将网页URL存入数据库进行查找不就好了,或者建立一个哈希表进行查找不就OK了。
当数据量小的时候,这么思考是对的,
确实可以将值映射到 HashMap 的 Key,然后可以在 O(1) 的时间复杂度内返回结果,效率奇高。但是 HashMap 的实现也有缺点,例如存储容量占比高,考虑到负载因子的存在,通常空间是不能被用满的,举个例子如果一个1000万HashMap,Key=String(长度不超过16字符,且重复性极小),Value=Integer,会占据多少空间呢?1.2个G。实际上,1000万个int型,只需要40M左右空间,占比3%,1000万个Integer,需要161M左右空间,占比13.3%。可见一旦你的值很多例如上亿的时候,那HashMap 占据的内存大小就变得很可观了。
但如果整个网页黑名单系统包含100亿个网页URL,在数据库查找是很费时的,并且如果每个URL空间为64B,那么需要内存为640GB,一般的服务器很难达到这个需求。