无锁缓存,每秒10万并发,究竟如何实现?
有一类业务场景:
(1)超高吞吐量,每秒要处理海量请求;
(2)写多读少,大部分请求是对数据进行修改,少部分请求对数据进行读取;
这类业务,有什么实现技巧么?
接下来,一起听我从案例入手,娓娓道来。
快狗打车,场景举例:
(1)司机地理位置信息会随时变化,可能每几秒钟地理位置要修改一次;
(2)用户打车的时候查看某个司机的地理位置,查询地理位置的频率相对较低;
这里要用到两个接口:
(1)大量修改司机信息:
void SetDriverInfo(long driver_id, DriverInfo info);
(2)相对少量查询司机信息:
DriverInfo GetDriverInfo(long driver_id);
这一类业务,一般怎么实现呢?
具体到底层的实现,往往是一个Map内存缓存:
(1)查询key定长,例如:司机ID;
(2)返回value也定长,例如:司机实体序列化后的二进制串;
即,类似这样的一个kv缓存结构:
Map<driver_id, DriverInfo>
这个kv内存缓存是一个临界资源,对它的并发访问,有什么注意事项么?
临界资源的访问,需要注意加读写锁,实施互斥。
以下,是加锁写入的伪代码:
void SetDriverInfo(long driver_id, DriverInfo info){WriteLock (m_lock);Map<driver_id>= info;UnWriteLock(m_lock);}
画外音:假设info已经序列化。
以下,是加锁读取的伪代码:
DriverInfo GetDriverInfo(long driver_id){DriverInfo t;ReadLock(m_lock);t= Map<driver_id>;UnReadLock(m_lock);return t;}
当吞吐量很高时,上述流程可能存在什么问题?
假设快狗打车有100w司机同时在线,每个司机每5秒更新一次经纬度状态,那么每秒就有20w次写并发操作。
假设快狗打车日订单1000w个,平均每秒大概也有300个下单,对应到查询并发量,大概每秒1000级别的并发读操作。
在这样的吞吐量下(每秒20w写,1k读),锁m_lock会成为潜在瓶颈,导致Map访问效率极低。
有什么潜在的优化方法么?
锁冲突之所以严重,是因为整个Map共用一把锁,锁的粒度太粗。
画外音:可以认为是一个数据库的“库级别锁”。
是否可能进行水平拆分,来降低锁冲突呢?
答案是肯定的。
画外音:类似于数据库里的分库,把一个库锁变成多个库锁,来提高并发,降低锁冲突。
我们可以把1个Map水平切分成N个Map:
void SetDriverInfo(long driver_id, DriverInfo info){i = driver_id % N; // 水平拆分成N份,N个Map,N个锁WriteLock (m_lock[i]); //锁第i把锁Map[i]<driver_id>= info; // 操作第i个MapUnWriteLock (m_lock[i]); // 解锁第i把锁}
如此优化,能否提高性能?
(1)一个Map变成了N个Map,每个Map的并发量,变成了1/N;
(2)同时,每个Map的数据量,变成了1/N;
所以理论上,锁冲突会成平方指数降低,性能会提升。
有没有可能,进一步细化锁粒度,一个元素一把锁呢?
答案也是肯定的。
画外音:可以认为是一个数据库的“库级别锁”,优化为“行级别锁”。
不妨设driver_id是递增生成的,并且假设内存比较大,此时可以把Map优化成Array,并把锁的粒度细化到最细的,每个司机信息一个锁:
void SetDriverInfo(long driver_id, DriverInfo info){index = driver_id;WriteLock (m_lock[index]); //超级大内存,一条记录一个锁,锁行锁Array[index]= info; //driver_id就是Array下标UnWriteLock (m_lock[index]); // 解锁行锁}
这个方案使得锁冲突降到了最低,但锁资源大增,在数据量非常大的情况下,内存往往是装不下的。
画外音:数据量比较小的时候,可以一个元素一把锁,典型的是连接池,每个连接用一把锁表示连接是否可用。
还没有方法进一步降低锁冲突,提升并发量呢?
写多读少的业务,有一种优化方案:无锁缓存,将锁冲突降低到。
无锁缓存,可能存在什么问题?
如果缓存不加锁,读写吞吐量可以达到极限,但是多线程对缓存中同一块定长数据进行写操作时,有可能出现不一致的脏数据。
这个方案为了提高性能,牺牲了一致性。
读取时,获取到了错误的数据,是不能接受的。
画外音:作为缓存,允许cache miss,却不允许读脏数据。
脏数据是如何产生的?
不加锁,在多线程并发写时,可能出现以下情况:
(1)线程1对缓存进行操作,对key想要写入value1;
(2)线程2对缓存进行操作,对key想要写入value2;
(3)不加锁,线程1和线程2对同一个定长区域进行一个并发的写操作,可能每个线程写成功一半,导致出现脏数据产生,最终的结果即不是value1也不是value2,而是一个乱七八糟的不符合预期的值value-unexpected;
如何解决上述问题呢?
本质上,这是一个数据完整性问题。
并发写入的数据分别是value1和value2,读出的数据是value-unexpected,数据被篡改,这本质上是一个数据完整性的问题。
通常如何保证数据的完整性呢?
例如:运维如何保证,从中控机分发到上线机上的二进制没有被篡改?
md5。
又例如:即时通讯系统中,如何保证接受方收到的消息,就是发送方发送的消息?
发送方除了发送消息本身,还要发送消息的签名,接收方收到消息后要校验签名,以确保消息是完整的,未被篡改。
“签名”是一种常见的保证数据完整性的方案。
加入“签名”保证数据的完整性之后,读写流程需要如何升级?
加上签名之后,不但缓存要写入定长value本身,还要写入定长签名(例如16bitCRC校验):
(1)线程1对缓存进行操作,对key想要写入value1,写入签名v1-sign;
(2)线程2对缓存进行操作,对key想要写入value2,写入签名v2-sign;
(3)如果不加锁,线程1和线程2对同一个定长区域进行一个并发的写操作,可能每个线程写成功一半,导致出现脏数据产生,最终的结果即不是value1也不是value2,而是一个乱七八糟的不符合预期的值value-unexpected,但签名,一定是v1-sign或者v2-sign中的任意一个;
画外音:16bit/32bit的写可以保证原子性。
(4)数据读取的时候,不但要取出value,还要像消息接收方收到消息一样,校验一下签名,如果发现签名不一致,缓存则返回NULL,即cache miss;
当然,对应到司机地理位置,除了内存缓存之前,肯定需要timer对缓存中的数据定期落盘,写入数据库,如果cache miss,可以从数据库中读取数据。
巧不巧秒?
总结
当业务满足:
(1)超高并发;
(2)写多读少;
(3)定长value;
时,可以用以下方法来提升吞吐量:
(1)水平拆分来降低锁冲突;
思路:单库变多库。
(2)Map转Array的方式来最小化锁冲突,一条记录一个锁;
思路:库锁变行锁。
(3)无锁,最大化并发;
思路:行锁变无锁,完整性与性能的折衷。
(4)通过签名的方式保证数据的完整性,实现无锁缓存;
思路:写时写签名,读时校验签名。
无锁缓存,每秒10万并发,究竟如何实现?相关推荐
- 每秒10万并发 mysql_亿级流量系统架构之如何设计每秒十万查询的高并发架构
一.前情回顾 上篇文章(亿级流量系统架构之如何设计承载百亿流量的高性能架构)聊了一下系统架构中,百亿流量级别高并发写入场景下,如何承载这种高并发写入,同时如何在高并发写入的背景下还能保证系统的超高性能 ...
- java 无锁缓存_如何在高并发环境下设计出无锁的数据库操作(Java版本)
一个在线2k的游戏,每秒钟并发都吓死人.传统的hibernate直接插库基本上是不可行的.我就一步步推导出一个无锁的数据库操作. 1. 并发中如何无锁. 一个很简单的思路,把并发转化成为单线程.Jav ...
- 每秒 10 万并发的 BI 系统如何频繁发生 Young GC?
作者 | 救火队队长 责编 | 伍杏玲 本文经授权转载自石杉的架构笔记(ID:shishan100) 本周我们的一个重点就是给大家再次强调JVM频繁GC对系统性能的危害性. 因此在分析完JVM发生GC ...
- jvm性能调优实战 - 26一个每秒10万并发的系统如何频繁发生Young GC的
文章目录 业务简介 系统初期 技术痛点:实时自动刷新报表 + 大数据量报表 没什么大影响的频繁Young GC 提升机器配置:运用大内存机器 用G1来优化大内存机器的Young GC性能 小结 思考 ...
- jvm性能调优实战 - 32一个10万并发的BI系统,如何定位和解决频繁Young GC问题?
文章目录 Pre 模拟代码的JVM参数设置 示例Code 如何在windows上执行命令? 通过jstat观察程序的运行状态 Pre jvm性能调优实战 - 26一个每秒10万并发的系统如何频繁发生Y ...
- 1万属性,100亿数据,每秒10万吞吐,架构如何设计?
有一类业务场景,没有固定的schema存储,却有着海量的数据行数,架构上如何来实现这类业务的存储与检索呢?58最核心的数据"帖子"的架构实现技术细节,今天和大家聊一聊. 一.背景描 ...
- 减少访问量_Nginx服务器高性能优化轻松实现10万并发访问量
今天要说的是Nginx服务器高性能优化的配置,如何使Nginx轻松实现10万并发访问量.通常来说,一个正常的 Nginx Linux 服务器可以达到 500,000 – 600,000 次/秒 的请求 ...
- nginx高性能web服务器详解_Nginx服务器高性能优化轻松实现10万并发访问量
原文:https://www.toutiao.com/i6804346550882402828 前面讲了如何配置Nginx虚拟主机,如何配置服务日志等很多基础的内容,大家可以去这里看看nginx系列文 ...
- nginx文件服务器5万并发量,Nginx服务器高性能优化-轻松实现10万并发访问量
今天要说的是Nginx服务器高性能优化的配置,如何使Nginx轻松实现10万并发访问量.通常来说,一个正常的 Nginx Linux 服务器可以达到 500,000 – 600,000 次/秒 的请求 ...
最新文章
- jpa 分页 排序 过滤_使用JPA标准@ViewScoped通过分页,过滤和排序进行Primefaces DataTable延迟加载...
- hdoj 1285 确定比赛名次 【拓扑排序】
- java 调用jira_java调用jira接口
- Python 检测系统时间,k8s版本,redis集群,etcd,mysql,ceph,kafka
- linux命令--vi,vim
- 数学归纳法与算法设计
- E20170414-ms
- [Linux系统编程/网络编程] 笔记目录
- AD9如何设置原点位置
- 法国警告Nobelium网络间谍攻击法国组织
- 图像风格化——感知损失(perceptual loss)(2016)
- Three.js学习四——模型导入
- linux文件系统知识总结、SD卡挂载问题总结
- lisp 梯形展开图_斜截圆柱件钣金展开图的AutoCAD二次开发
- 开源聚合支付平台学习
- 十一、MYSQL数据库备份还原
- 用OpenSSL做自签名的证书(by quqi99)
- 深入理解卷积神经网络(附代码,底层实现)
- 角谷定理。输入一个自然数,若为偶数,则把它除以2,若为奇数,则把它乘以3加1。经过如此有限次运算后,总可以得到自然数值1。求经过多少次可得到自然数1。
- 企业中常用的Nginx网站服务相关配置