‘壹’ redis之管道应用场景及源码分析
我们都知道,redis的通信是建立在tcp基础上的,也就是说每一次命令(get、set)都需要经过tcp三次握手,而且redis一般都是部署在局域网内,网络开销非常小,针对频次较低的操作,网络开销都是可以忽略的。
在redis通信基础中 我已经讲到了。每一次操作redis的时候我们都需要和服务端建立连接,针对量小的情况下网络延迟都是可以忽略的,但是针对大批量的业务,就会产生雪崩效应。假如一次操作耗时2ms,理论上100万次操作就会有2ms*100万ms延迟,中间加上服务器处理开销,耗时可能更多.对应客户端来讲,这种长时间的耗时是不能接受的。所以为了解决这个问题,redis的管道pipeline就派上用场了。 恰好公司的对账业务使用了redis的sdiff功能,数据量比较大,刚开始没有pipeline导致延迟非常严重。后来wireshark抓包分析原因确实发现不停的建立tcp连接(发送数据,接收数据)。使用pipeline后性能大幅度提升。
可想而知,使用pipeline的性能要比不使用管道快很多倍。
本文就先到这里了。。。
‘贰’ RedisTokenStore 源码解析 以及内存泄漏问题
前端时间,正好在做公司权限相关的架构问题,然后选择了Spring OAuth2来作为公司权限框架,先记录下目前遇到原生问题吧,后续有时间再来整理这个框架的整体脉络;
RedisTokenStore 主要是来做token持久化到redis的工具类
我们先来看下缓存到redis中有哪些key
ACCESS :用来存放 AccessToken 对象(登录的token值,还有登录过期时间,token刷新值)
AUTH_TO_ACCESS :缓存的也是AccessToken 对象,是可以根据用户名和client_id来查找当前用户的AccessToken
AUTH :用来存放用户信息(OAuth2Authentication),有权限信息,用户信息等
ACCESS_TO_REFRESH :可以根据该值,通过AccessToken找到refreshToken
REFRESH :用来存放refreshToken
REFRESH_TO_ACCESS :根据refreshToken来找到AccessToken
CLIENT_ID_TO_ACCESS :存放当前client_id有多少AccessToken
UNAME_TO_ACCESS :当没有做单点登录的话,可以使用该key,根据用户名查找当前用户有多少AccessToken可以使用
根据client_id和用户名,来获取当前用户的accessToken,如果缓存中的OAuth2Authentication已经过期,或者雷勇有变化,则会重新更新缓存;
缓存OAuth2AccessToken 和 OAuth2Authentication 对象,该方法主要在登录时调用,会把上面说的所有key值都缓存起来;
再看下,移除accessToken时
目前主要是看这几个方法,其他方法也挺简单的,主要是一些redis缓存操作的;
client_id_to_access 和 uname_to_access 使用的是set集合,众所周知redis的set集合的过期时间是按照整个key来设置的;
每次登陆时,会先根据client_id和用户名去缓存中查找是否有可使用的AccessToken,如果有则返回缓存中的值,没有则生成新的;
所以每次登陆都会往这两个集合中放入新的accessToken,如果当某个用户在AccessToken有效期内没有操作,则当前用户的登陆信息会被动下线,access 和 auth 中缓存的值都会过期,再次登陆时就查找不到了;
但是如果当前平台用户量不小,那么一直都会有人操作,client_id_to_access 这个集合就会一直续期,那么过期了的accessToken就会一直存在该集合中,且不会减少,造成内存泄漏;
1.自己实现TokenStore,修改client_id_to_access 数据结构
2.写个定时任务,定期扫描client_id_to_access ,清除掉已经过期的token
3.如果不需要知道当前有哪些用户登录,或者该功能已经用了其他方式实现的,可以直接去掉这两个redis key
‘叁’ Redis源码分析之事件循环
本篇我们来讲Redis的事件循环,Redis的事件循环会根据系统选择evport、epoll、kqueue或select来进行IO多路复用,我们这里只分析epoll。
首先我们来看一下Redis的IO多路复用对事件循环(aeEventLoop)提供的接口。
以epoll(ae_epoll.c)为例,先来看一下Redis的IO多路复用的使用过程:
首先需要创建,即调用aeApiCreate:
aeApiState结构体有两个成员,events和epfd。events用于存储就绪的epoll事件,epfd存储epoll的文件描述符。aeApiCreate的主要逻辑是为aeApiState分配存储空间,调用epoll_create系统调用创建epoll并获取描述符,最后将aeApiState赋值给aeEventLoop的apidata。
然后在有新的文件描述符(比如接受了一个新连接)需要加入到epoll中时,调用aeApiAddEvent:
参数fd是需要监视的文件描述符,mask标明是需要监视可读还是可写事件。aeApiAddEvent的主要逻辑是调用系统调用epoll_ctl注册或修改添加事件的监听类型到epoll。
然后在有文件描述符失效或者需要修改监听类型时,调用aeApiDelEvent:
参数fd是需要删除的文件描述符,mask标明是需要删除可读还是可写事件。aeApiDelEvent主要逻辑是调用系统调用epoll_ctl删除或修改删除事件的监听类型到epoll。
然后需要检查是否有就绪的事件,调用aeApiPoll:
tvp是等待时间,一般而言,这个值是0(不是NULL)代表没有就绪事件立即返回。主要逻辑是调用系统调用epoll_wait拿到就绪事件保存到events中,然后将events中的就绪事件复制到事件循环aeEventLoop的fired中,最后返回就绪事件的数量。
我们来分析一下Redis的事件循环(ae.c)。
先看主要接口:
创建过程:
初始化aeEventLoop和aeApiState并返回aeEventLoop。
注册文件事件:
存储到events中并调用aeApiAddEvent注册到epoll中。
注册定时事件:
创建aeTimeEvent并将其插入到定时事件链表的头部。
主循环:
不停的调用aeProcessEvents拉取并处理事件。
接下来看aeProcessEvents的逻辑:
再看一下定时事件的触发,也就是processTimeEvents的逻辑:
遍历注册的定时事件,找出到期的事件并调用处理函数,如果处理函数返回了下次执行的时间,则更新下次触发的时间,否则删除该事件。
‘肆’ Redis实现优先级消息队列及源码
上午写了一篇RabbitMQ做优先级队列的文章,但是RabbitMQ这种专业的消息队列,面对不大的业务是有些杀鸡焉用牛刀的感觉,而且使用RabbitMQ需要的成本相对较高。
所以我中午抽空写了这个Redis实现优先级消息队列的例子。
相比RabbitMQ,更加简洁,更易于理解。
源码地址: https://github.com/SkylerSkr/RedisPriorityMQ
谢谢大家支持!希望多提意见!
‘伍’ windows怎么调试redis源码
Redis对于linux是官方支持的,安装和使用没有什么好说的,普通使用按照官方指导,5分钟以内就能搞定。详情请参考:
http://redis.io/download
但有时候又想在windows下折腾下Redis,可以从redis下载页面看到如下提示(在页面中搜索 "windows"):
[plain] view plain
Win64 Unofficial The Redis project does not directly support Windows,
however the Microsoft Open Tech group develops and maintains
an Windows port targeting Win64.
大意就是 Redis官方是不支持windows的,只是 Microsoft Open Tech group 在 GitHub上开发了一个Win64的版本,项目地址是:
https://github.com/MSOpenTech/redis
打开以后,可以直接使用浏览器下载,或者Git克隆。
可以在项目主页右边找到 zip包下载地址: https://github.com/MSOpenTech/redis/archive/2.8.zip
(注意: dist文件改变了下载地址: https://github.com/MSOpenTech/redis/releases )
‘陆’ redis源码解读:单线程的redis是如何实现高速缓存的
redis可能是最近几年最火的缓存数据库方案了,在各个高并发领域都有应用。
这篇文章,我们将从源代码的角度来分析一下,为何如此一个高性能,高应用的缓存,会是单线程的方案,当然一个方案的高性能,高并发是多方面的综合因素,其它的因素我们将在后续解读。后续分析主要以LINUX操作系统为基础,这也是redis应用最广的平台。
单线程最大的受限是什么?就是CPU,现在服务器一般已经是多CPU,而单线程只能使用到其中的一个核。
redis作为一个网络内存缓存数据库,在实现高性能时,主要有4个点。
1.网络高并发,高流量的数据处理。
一个异步,高效,且对CPU要求不高的网络模型,这个模型主要是由OS来提供的,目前在LINUX最主流使用的是EPOLL,这个网上介绍很多,主要是基于事件驱动的一个异步模型。
2.程序内部的合理构架,调用逻辑,内存管理。
redis在采用纯C实现时,整体调用逻辑很短,但在内存方面,适当的合并了一些对象和对齐,比如sds等,在底层使用了内存池,在不同情况下使用的不太一样。
但整体处理上没有NGINX的内池设计巧妙,当然二者不太一样,NGINX是基于请求释放的逻辑来设计的,因此针对请求,可以一次申请大块,分量使用,再最后统一释放。
3.数据复制的代价,不管是读取数据或是写入数据,一般都是需要有数据复制的过程。
数据复制其实就是一次内存,真正的代价是在于存在大VALUE,当value值长度超过16KB时,性能会开始下降。因为单线程的原因,如果存在一个超大VALUE,比如20MB,则会因为这个请求卡住整个线程,导致后续的请求进不来,虽然后面的请求是能快速处理的小请求。
4.redis中数据结构中算法的代价,有些结构在大数据量时,代价是很高的。
很多时间,大家忽略了算法的运算代码,因为像memcached等这类是完全的KV缓存,不存在什么算法,除了一个KEY的查找定位HASH算法。
而redis不一样,提供了不少高阶的数据对象,这些对象具有上层的一些算法能力,而这些能力是需要比如GEO模块。
‘柒’ MySQL与Redis数据库连接池介绍(图示+源码+代码演示)
数据库连接池(Connection pooling)是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的连接进行申请,使用,释放。
简单的说:创建数据库连接是一个很耗时的操作,也容易对数据库造成安全隐患。所以,在程序初始化的时候,集中创建多个数据库连接,并把他们集中管理,供程序使用,可以保证较快的数据库读写速度,还更加安全可靠。
不使用数据库连接池
如果不使用数据库连接池,对于每一次SQL操作,都要走一遍下面完整的流程:
1.TCP建立连接的三次握手(客户端与 MySQL服务器的连接基于TCP协议)
2.MySQL认证的三次我收
3.真正的SQL执行
4.MySQL的关闭
5.TCP的四次握手关闭
可以看出来,为了执行一条SQL,需要进行大量的初始化与关闭操作
使用数据库连接池
如果使用数据库连接池,那么会 事先申请(初始化)好 相关的数据库连接,然后在之后的SQL操作中会复用这些数据库连接,操作结束之后数据库也不会断开连接,而是将数据库对象放回到数据库连接池中
资源重用:由于数据库连接得到重用,避免了频繁的创建、释放连接引起的性能开销,在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。
更快的系统响应速度:数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。 此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了从数据库连接初始化和释放过程的开销,从而缩减了系统整体响应时间。
统一的连接管理,避免数据库连接泄露:在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄露。
如果说你的服务器CPU是4核i7的,连接池大小应该为((4*2)+1)=9
相关视频推荐
90分钟搞懂数据库连接池技术|linux后台开发
《tcp/ip详解卷一》: 150行代码拉开协议栈实现的篇章
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
需要C/C++ Linux服务器架构师学习资料加qun 812855908 获取(资料包括 C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),免费分享
源码下载
下载方式:https://github.com/dongyusheng/csdn-code/tree/master/db_pool(Github中下载)
db_pool目录下有两个目录,mysql_pool目录为MySQL连接池代码,redis_pool为redis连接池代码
下面介绍mysql_pool
CDBConn解析
概念: 代表一个数据连接对象实例
相关成员:
m_pDBPool:该数据库连接对象所属的数据库连接池
构造函数: 绑定自己所属于哪个数据库连接池
Init()函数: 创建数据库连接句柄
CDBPool解析
概念:代表一个数据库连接池
相关成员:
Init()函数:常见指定数量的数据库实例句柄,然后添加到m_free_list中,供后面使用
GetDBConn()函数: 用于从空闲队列中返回可以使用的数据库连接句柄
RelDBConn()函数: 程序使用完该数据库句柄之后,将句柄放回到空闲队列中
测试之前,将代码中的数据库地址、端口、账号密码等改为自己的(代码中有好几处)
进入MySQL, 创建mysql_pool_test数据库
进入到mysql_pool目录下, 创建一个build目录并进入 :
然后输入如下的命令进行编译
之后就会在目录下生成如下的可执行文件
输入如下两条命令进行测试: 可以看到不使用数据库连接池,整个操作耗时4秒左右;使用连接池之后,整个操作耗时2秒左右,提升了一倍
源码下载
下面介绍redis_pool
测试
进入到redis_pool目录下, 创建一个build目录并进入 :
然后输入如下的命令进行编译
之后就会在目录下生成如下的可执行文件
输入如下的命令进行测试: 可以看到不使用数据库连接池,整个操作耗时182ms;使用连接池之后,整个操作耗时21ms,提升了很多
进入redis,可以看到我们新建的key:
‘捌’ Redis5设计与源码分析.pdf
搜搜公众号"小易哥学呀学",回复"redis.pdf"