搜搜公众号"小易哥学呀学",回复"redis.pdf"
⑵ 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
⑶ 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:
⑷ 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模块。
⑸ [redis 源码走读] sentinel 哨兵 - 节点发现流程
承接上一章 《 [redis 源码走读] sentinel 哨兵 - 原理 》,本章通过 strace 命令从底层抓取 sentinel 工作日志,熟悉节点通信流程,阅读相关源码。
节点之间通过 TCP 建立联系,下图展示了 sentinel A 节点与其它节点的关系。
通过 strace 命令查看 socket 的发送和接收数据日志内容,我们基本可以掌握 sentinel/master/slave 这三个角色是怎么联系起来的。
这样 sentinel 只需要配置 master 的信息,通过 INFO 命令和订阅频道 __sentinel__:hello 就能将集群中所有角色的节点紧密联系在一起。
根据 strace 日志参考上述对应连接关系图。
通过上述分析,我们基本了解了节点之间的通信流程时序,下面来分析一下源码。
sentinel 进程对 sentinel / master / slave 三个角色用数据结构 sentinelRedisInstance 进行管理。
sentinel 进程启动,加载配置,创建对应节点的管理实例 sentinelRedisInstance 。
定时器定期对其它节点进行监控管理。sentinel 利用 hiredis 作为 redis client,链接其它节点进行相互通信。
sentinel 异步重连其它节点。
sentinel 定期发送命令:PING / INFO / PUBLISH。每种命令发送的时间间隔不一样;不同场景下,同一个命令发送时间间隔可能会改变。
命令发送对象 :
sentinel 通过 master / slave 的 INFO 回复,主要下面几件事:
如果文章不错,给个点赞呗 ~ 谢谢。👍
⑹ window 下安装redis后代码怎么使用
1下载安装文件,选择稳定版本 2解压后找到bin目录下的release下的redis-2.8.17 3点击安装exe文件,进行安装。选择好路径,一直到安装结束即可。 4点击Service查看Redis服务是否正确的安装。Windows--》Service.msc。默认的端口为6379。服务已启动。 5使用客户端工具进行连接,出现如下画面即成功。 6使用CMD工具,安装另一个Redis实例服务,端口为6369.需要提前建好6369端口使用的conf文件 如:C:\Users\Gray>E:\redis-2.8.17\redis-server.exe --service-installE:\redis-2.8.17\redis6369.conf --service-name RedisServer6369 --port 6369 试验了几次都没有提示成功的信息,但是查看服务成功了,而且用客户端连接也成功了。 7查看6369端口的redis服务 8使用客户端连接6369 redis服务,出现如下界面表示成功 9至此,大功告成。
⑺ 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
谢谢大家支持!希望多提意见!
⑼ redis之管道应用场景及源码分析
我们都知道,redis的通信是建立在tcp基础上的,也就是说每一次命令(get、set)都需要经过tcp三次握手,而且redis一般都是部署在局域网内,网络开销非常小,针对频次较低的操作,网络开销都是可以忽略的。
在redis通信基础中 我已经讲到了。每一次操作redis的时候我们都需要和服务端建立连接,针对量小的情况下网络延迟都是可以忽略的,但是针对大批量的业务,就会产生雪崩效应。假如一次操作耗时2ms,理论上100万次操作就会有2ms*100万ms延迟,中间加上服务器处理开销,耗时可能更多.对应客户端来讲,这种长时间的耗时是不能接受的。所以为了解决这个问题,redis的管道pipeline就派上用场了。 恰好公司的对账业务使用了redis的sdiff功能,数据量比较大,刚开始没有pipeline导致延迟非常严重。后来wireshark抓包分析原因确实发现不停的建立tcp连接(发送数据,接收数据)。使用pipeline后性能大幅度提升。
可想而知,使用pipeline的性能要比不使用管道快很多倍。
本文就先到这里了。。。
⑽ windows环境下Redis+Spring缓存实例
一、Redis了解
1.1、Redis介绍:
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis数据库完全在内存中,使用磁盘仅用于持久性。相比许多键值数据存储,Redis拥有一套较为丰富的数据类型。Redis可以将数据复制到任意数量的从服务器。
1.2、Redis优点:
(1)异常快速:Redis的速度非常快,每秒能执行约11万集合,每秒约81000+条记录。
(2)支持丰富的数据类型:Redis支持最大多数开发人员已经知道像列表,集合,有序集合,散列数据类型。这使得它非常容易解决各种各样的问题,因为我们知道哪些问题是可以处理通过它的数据类型更好。
(3)操作都是原子性:所有Redis操作是原子的,这保证了如果两个客户端同时访问的Redis服务器将获得更新后的值。
(4)多功能实用工具:Redis是一个多实用的工具,可以在多个用例如缓存,消息,队列使用(Redis原生支持发布/订阅),任何短暂的数据,应用程序,如Web应用程序会话,网页命中计数等。
1.3、Redis缺点:
(1)单线程
(2)耗内存
二、64位windows下Redis安装
Redis官方是不支持windows的,但是Microsoft Open Tech group 在 GitHub上开发了一个Win64的版本,下载地址:https://github.com/MSOpenTech/redis/releases。注意只支持64位哈。
小宝鸽是下载了Redis-x64-3.0.500.msi进行安装。安装过程中全部采取默认即可。
安装完成之后可能已经帮你开启了Redis对应的服务,博主的就是如此。查看资源管理如下,说明已经开启:
已经开启了对应服务的,我们让它保持,下面例子需要用到。如果没有开启的.,我们命令开启,进入Redis的安装目录(博主的是C:Program FilesRedis),然后如下命令开启:
redis-server redis.windows.conf
OK,下面我们进行实例。
三、详细实例
本工程采用的环境:Eclipse + maven + spring + junit
3.1、添加相关依赖(spring+junit+redis依赖),pom.xml:
4.0.0 com.luo redis_project 0.0.1-SNAPSHOT 3.2.8.RELEASE 4.10 org.springframework spring-core ${spring.version} org.springframework spring-webmvc ${spring.version} org.springframework spring-context ${spring.version} org.springframework spring-context-support ${spring.version} org.springframework spring-aop ${spring.version} org.springframework spring-aspects ${spring.version} org.springframework spring-tx ${spring.version} org.springframework spring-jdbc ${spring.version} org.springframework spring-web ${spring.version} junit junit ${junit.version} test org.springframework spring-test ${spring.version} test org.springframework.data spring-data-redis 1.6.1.RELEASE redis.clients jedis 2.7.3
3.2、spring配置文件application.xml:
<"1.0" encoding="UTF-8"> classpath:properties/*.properties
3.3、Redis配置参数,redis.properties:
#redis中心#绑定的主机地址redis.host=127.0.0.1#指定Redis监听端口,默认端口为6379redis.port=6379#授权密码(本例子没有使用)redis.password=123456 #最大空闲数:空闲链接数大于maxIdle时,将进行回收redis.maxIdle=100 #最大连接数:能够同时建立的“最大链接个数”redis.maxActive=300 #最大等待时间:单位msredis.maxWait=1000 #使用连接时,检测连接是否成功 redis.testOnBorrow=true#当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能redis.timeout=10000
3.4、添加接口及对应实现RedisTestService.java和RedisTestServiceImpl.java:
package com.luo.service; public interface RedisTestService { public String getTimestamp(String param);}
package com.luo.service.impl; import org.springframework.stereotype.Service;import com.luo.service.RedisTestService; @Servicepublic class RedisTestServiceImpl implements RedisTestService { public String getTimestamp(String param) { Long timestamp = System.currentTimeMillis(); return timestamp.toString(); } }
3.5、本例采用spring aop切面方式进行缓存,配置已在上面spring配置文件中,对应实现为MethodCacheInterceptor.java:
package com.luo.redis.cache; import java.io.Serializable;import java.util.concurrent.TimeUnit;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations; public class MethodCacheInterceptor implements MethodInterceptor { private RedisTemplate redisTemplate; private Long defaultCacheExpireTime = 10l; // 缓存默认的过期时间,这里设置了10秒 public Object invoke(MethodInvocation invocation) throws Throwable { Object value = null; String targetName = invocation.getThis().getClass().getName(); String methodName = invocation.getMethod().getName(); Object[] arguments = invocation.getArguments(); String key = getCacheKey(targetName, methodName, arguments); try { // 判断是否有缓存 if (exists(key)) { return getCache(key); } // 写入缓存 value = invocation.proceed(); if (value != null) { final String tkey = key; final Object tvalue = value; new Thread(new Runnable() { public void run() { setCache(tkey, tvalue, defaultCacheExpireTime); } }).start(); } } catch (Exception e) { e.printStackTrace(); if (value == null) { return invocation.proceed(); } } return value; } /** * 创建缓存key * * @param targetName * @param methodName * @param arguments */ private String getCacheKey(String targetName, String methodName, Object[] arguments) { StringBuffer sbu = new StringBuffer(); sbu.append(targetName).append("_").append(methodName); if ((arguments != null) && (arguments.length != 0)) { for (int i = 0; i < arguments.length; i++) { sbu.append("_").append(arguments[i]); } } return sbu.toString(); } /** * 判断缓存中是否有对应的value * * @param key * @return */ public boolean exists(final String key) { return redisTemplate.hasKey(key); } /** * 读取缓存 * * @param key * @return */ public Object getCache(final String key) { Object result = null; ValueOperations operations = redisTemplate .opsForValue(); result = operations.get(key); return result; } /** * 写入缓存 * * @param key * @param value * @return */ public boolean setCache(final String key, Object value, Long expireTime) { boolean result = false; try { ValueOperations operations = redisTemplate .opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } public void setRedisTemplate( RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; }}
3.6、单元测试相关类:
package com.luo.baseTest; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; //指定bean注入的配置文件 @ContextConfiguration(locations = { "classpath:application.xml" }) //使用标准的JUnit @RunWith注释来告诉JUnit使用Spring TestRunner @RunWith(SpringJUnit4ClassRunner.class) public class SpringTestCase extends { }
package com.luo.service; import org.junit.Test;import org.springframework.beans.factory.annotation.Autowired; import com.luo.baseTest.SpringTestCase; public class RedisTestServiceTest extends SpringTestCase { @Autowired private RedisTestService redisTestService; @Test public void getTimestampTest() throws InterruptedException{ System.out.println("第一次调用:" + redisTestService.getTimestamp("param")); Thread.sleep(2000); System.out.println("2秒之后调用:" + redisTestService.getTimestamp("param")); Thread.sleep(11000); System.out.println("再过11秒之后调用:" + redisTestService.getTimestamp("param")); } }
3.7、运行结果:
四、源码下载:redis-project().rar
以上就是本文的全部内容,希望对大家的学习有所帮助。