极验目前的用户超过7万家网站,日均验证量1亿次,作为一家专注于验证安全服务的公司,极验所要面临的并发压力主要表现在以下几点:
那么极验是如何做到,既保证用户的验证需求量,又尽量快速响应用户的验证请求,还能够扛得住黑产的攻击呢?极验主要从三个方面来解决高并发问题。
降低并发的开销
利用协程处理并发,我们熟知的协程相较于线程来说具有的优点是,能够跨平台跨体系架构,不需要线程上下文切换和原子操作锁定及同步的开销。这样就避免了操作系统调度线程造成的资源浪费。同时协程方便切换控制流,能够简化编程模型,避免异步回调代码的逻辑分割,使得程序的可读性好,有利于后台的维护。像极验这样高并发量同时需要高扩展性的验证服务企业来说,使用协程处理是降低并发开销最合适的方法。
其次极验利用OpenResty过滤非法请求,以及限制不同账户并发。OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了比较精良的 Lua 库、第三方模块以及大多数的依赖项。能够比较便捷地搭建处理超高并发、高扩展性的动态 Web 服务。
提升数据库性能
极验主要通过两个手段来提升数据库的性能:验证的临时数据采用基于分布式Redis和构建嵌入式数据库缓存,实现数据库零查询。
Proxy的Redis存储可能是目前比较常规的存储方法,通过代理将读写压力进行合理分配。codis-proxy基于GO和C语言,并发处理能力比较强。后端基于slot概念支持灵活,还具有对用户透明的扩容和缩容操作,简单便捷,集群管理工具丰富等优势。
但是对于极验来说这样的方式并不是那么合适,存在着以下几点极验必须要考虑的问题。
- 使用代理使得整个结构多了一层不安全因素,一旦代理层出现问题,那么后面的都无法正常运转。
- 代理本身并不具备良好的扩展性,无法自动的进行分配,在运维上有一定的难度。
考虑到这些问题,我们选择采用自己的基于客户端的分布式解决方案,结构如下。
客户端通过一致性hash,写入当前机器与hash环上的下一台机器,实现数据冗余。读取时从当前机器读取,失败则从hash环上下一台机器读取。得益于相对简单的结构,扩容、故障恢复速度会快得多,同时运维成本更低。
在高并发量的情况下,数据库往往成为瓶颈,加上大量挂起等待的协程也会使得数据库的性能大大降低。像极验这样每天有大量的验证数据需要读取,提升数据库性能就显得十分重要。我们的解决方案是进行嵌入式缓存,所有查询完全遵循缓存中的数据,缓存定期与数据库同步。同时缓存直接嵌入服务进程内,实现几乎零开销查询。由于Python的GIL存在,我们利用mmap实现进程间共享内存。
我们在实现这个嵌入式缓存的过程中,完全按照我们业务中遇到的实际问题进行设计,所以可能对于其他业务不是很适用。具体来说,极验的数据库查询主要有三种特性:1.数据几乎只读不写,并且对于数据一致性要求不高。2.数据库查询开销相对计算逻辑比重较大。3.接口并发数长期保持在较高水平,用传统缓存方式的话一旦缓存被穿透(例如恶意伪造不存在的数据)系统将崩溃。基于上面三点特性,我们专门定制了最适合我们自己的缓存,并使得数据库完全不再是系统的瓶颈。
提高计算性能
提高计算性能极验主要采用以下两种方式:
主要性能消耗在数据处理逻辑以及神经网络参数计算
- 利用Cython将计算密集代码编译成扩展模块供Python调用
通过不断调整神经网络的参数和加大训练的迭代次数来保证足够精度下网络规模最小。
在预测时加入DropOut,让部分神经元不参与计算,减少计算量的同时一定程度避免过拟合。
利用小网络学习大网络所提取到的特征加上现代Cpu的SIMD指令集加速计算——使用优化过的Blas库例如OpenBlas等。这样一来,能够很好的控制神经网络的规模。
极验通过以上三个技术手段,来解决高并发问题。目前我们使用不到二十台阿里云服务器的情况下可以做到5w的并发,并且整个架构可以完全快速横向扩展。