Redis的入门学习
2018-03-19 15:34
375 查看
什么是redis?
Redis 是一个基于内存的高性能key-value数据库。Reids的特点
Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等。另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。
Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
参考文章:https://www.cnblogs.com/Survivalist/p/8119891.html
Redis支持的数据类型
Redis通过Key-Value的单值不同类型来区分, 支持以下5种类型:String
List
Set
Sorted Set
hash
为什么要使用Redis
为了提高效率。对于一些不怎么需要变动同时又经常要访问的数据,就可以放到redis数据库中,这样就不用每次都去mysql数据库中读取了。为什么从redis数据库中读取效率就比去mysql数据库中读取更高效率了呢。因为redis数据库的数据是放在内存中的,而mysql数据库的数据是放在硬盘中的,访问内存速度当然比访问硬盘要快。因此说访问redis缓存的效率比从mysql数据库中读取数据更高效。
redis为什么要使用连接池
现在学习redis,看着网上的入门示例,发现很多都会使用连接池。虽然之前也会用到mysql,也会用到c3p0连接池。但其实不懂,为什么要这么做,尽管有了解过连接池的作用。
看了《Redis连接池理解》这篇文章才恍然大悟。以下是原文中的一段:
首先Redis也是一种数据库,它基于C/S模式,因此如果需要使用必须建立连接,稍微熟悉网络的人应该都清楚地知道为什么需要建立连接,C/S模式本身就是一种远程通信的交互模式,因此Redis服务器可以单独作为一个数据库服务器来独立存在。假设Redis服务器与客户端分处在异地,虽然基于内存的Redis数据库有着超高的性能,但是底层的网络通信却占用了一次数据请求的大量时间,因为每次数据交互都需要先建立连接,假设一次数据交互总共用时30ms,超高性能的Redis数据库处理数据所花的时间可能不到1ms,也即是说前期的连接占用了29ms,连接池则可以实现在客户端建立多个链接并且不释放,当需要使用连接的时候通过一定的算法获取已经建立的连接,使用完了以后则还给连接池,这就免去了数据库连接所占用的时间。
我一直理所当然的觉得服务器和数据库都是在同一台机器上的,因此我在想Redis的数据是放在内存中,服务器访问redis数据时不是直接从内存中读取吗,为什么会用到连接池呢。
原来服务器向数据库访问数据也需要建立连接的。正如上面引用的文章所说,Redis是基于C/S模式的。当服务器向Redis数据库访问数据时,需要先建立连接,才能进行通信。
也正如上面引用的文章所说,Redis读取数据速度很快,而建立连接的过程却很耗时,因此可以使用连接池,从而减少不必要的开销(这个不必要的开销就是指建立连接、释放连接的时间损耗),这样才能真正发挥出Redis的缓存作用。
如何使用redis
毕竟redis就是一个数据库,因此首先得安装redis服务器,其次要启动redis服务器,然后通过下面这一行代码就可以建立连接了:Jedis jedis = new Jedis("localhost");
然后就可以操作数据库了:
e4e2
jedis.set("tom","我是tom啊");//存储数据 jedis.set("jack","我是jack。");//存储数据 System.out.println(jedis.keys("*"));//获取所有key System.out.println(jedis.get("tom"));//获取指定key对应的value
这也太简洁了吧???比mysql简洁好多啊。
使用例子:
public static void main(String[] args) { Jedis jedis = new Jedis("localhost"); String[] str = {"/rbac/role/add", "/rbac/role/update","/rbac/role/delete","/rbac/role/query"}; jedis.sadd("userid:100",str);//可以直接将数组放进去 jedis.get("userid100");//set获取value不是用get方法,而是用smembers方法啊 System.out.println(jedis.smembers("userid:100")); }
redis的使用
flushDb可以清空缓存expire可以设置生存时间
ttl可以查看剩余时间,其中-1表示永远有效,-2表示不存在该key
set的相关用法:
sadd增加元素
sismember判断是否成员之一
smemebers输出所有成员
具体使用参考:https://www.cnblogs.com/tuojunjie/p/6226441.html
下面这种用法是错误的:
if(jedis.smembers("userId:"+employeeId)!=null)
因为jedis的smembers方法返回的是一个set对象,必然不为空。要判断缓存中是否存在key为
"userId:"+employeeId对应的value,要这么做:
Set<String> set = jedis.smembers("userId:"+employeeId); if(!set.isEmpty())
应用场景
在我做的人事管理系统中,比方说员工tom,假如它只是普通员工,那么它的权限就只能是文件的下载以及查看自己的信息,无权修改其他人的信息、无权去设置权限分配、无权去删除员工记录等等。尽管,员工tom登录进人事管理系统的界面,无法进到分配、修改权限的界面。但是如果员工tom是一个技术人员,那么它就可以借助工具直接发起删除员工记录的请求,如在地址栏上输出这么一个请求:
http://localhost:8080/employee/delete?ids=1
那么,如果没有对用户权限进行拦截处理,那么当员工tom借助工具发起这么一个请求,就能直接把员工表中id为1的员工信息给删掉了。
这就是BUG。因此要引入权限控制。权限控制在这里并不做过多介绍,总之,可以使用拦截器,通过用户的id获取其对应的权限,只有在用户有权限的前提下才能执行相应的操作。像这里,员工tom没有删除员工信息的权限,因此该请求会被拦截器拦下。
然而,因为要对每一个请求都进行拦截判断,而如果每次判断都需要从数据库中读取该用户对应的权限的话,那么效率是很低的。
而如果使用redis缓存,将用户的权限暂存到redis中。这样,就不用每次都去访问关系型数据库(硬盘)了,直接访问redis(内存)。访问内存效率自然比访问硬盘要快,因此使用redis缓存效率将更高。
具体代码
jedis依赖<!--redis--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.1.0</version> </dependency>
拦截器类实现如下:
package hrm.interceptor; //省略引入的包 public class AuthorizeInterceptor implements HandlerInterceptor{ @Autowired private PermissionService permissionService; private Logger logger = LoggerFactory.getLogger(AuthorizeInterceptor.class); /** * 大体描述:获取请求路径以及用户对应的id,然后判断缓存是否有对应记录(即判断用户是否有权限) * 若有,直接在缓存中查询, * 若没有,则查询数据库,获取用户对应的权限url,并存入缓存,再判断用户是否有权限 * @param httpServletRequest * @param httpServletResponse * @param o * @return * @throws Exception */ public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { logger.info("进入AuthorizeInterceptor,拦截请求。"); String path = httpServletRequest.getServletPath();//获取请求的路径 Integer employeeId = ((User)httpServletRequest.getSession().getAttribute("user_session")).getId(); //先看看缓存中是否有对应记录 Jedis jedis = null; JedisPool pool = null; try{ pool = RedisUtil.getJedisPool(); jedis = pool.getResource(); logger.info("该redis-set的key为userId:"+employeeId); Set<String> set = jedis.smembers("userId:"+employeeId); //如果缓存命中,则直接在缓存中处理 if(!set.isEmpty()){ logger.info("缓存命中!"); logger.info("该用户的请求的url为:"+path); logger.info("而其权限url为:"+jedis.smembers("userId:"+employeeId)); //判断是否有权限 if(jedis.sismember("userId:"+employeeId,path)){ logger.info("该用户拥有该路径权限,允许访问"); jedis.expire("userId:"+employeeId,3*60);//每次访问,都刷新缓存时间,三分钟 return true; } else{ logger.info("该用户没有该权限,因此该请求属于非法请求"); return false; } } }catch (Exception e){ pool.returnBrokenResource(jedis); e.printStackTrace(); logger.info("AuthorizeInterceptor中命中缓存处理中出现异常"); }finally { pool.returnResource(jedis);//将连接放回到连接池中 } //如果缓存没命中,才会执行这里 //则查询数据库,获取该用户拥有的权限。并将权限存储到缓存中 List<Permission> list = permissionService.queryPermissionUrlByUserId(employeeId);//查询数据库,获取用户对应的权限url StringBuffer sb = new StringBuffer(); for(Permission permission:list){ sb.append(permission.getUrl()).append("\r\n"); } String[] urls = sb.toString().split("\r\n"); //通过split方法分割url,\n表示换行符 //先将记录存到缓存中 try{ jedis = pool.getResource(); jedis.sadd("userId:"+employeeId,urls); jedis.expire("userId:"+employeeId,3*60);//缓存三分钟 }catch (Exception e){ pool.returnBrokenResource(jedis); e.printStackTrace(); logger.info("AuthorizeInterceptor中将数据存入redis缓存时出现异常"); }finally { pool.returnResource(jedis);//将连接放回到连接池中 } //用于打印日志---------------------------------------------------- StringBuffer stringBuffer = new StringBuffer(); for(String url:urls){ stringBuffer.append(url).append(","); } logger.info("该用户拥有的权限为:"+stringBuffer.toString()); logger.info("请求路径为:"+path); //用于打印日志---------------------------------------------------- //从数据库中获取权限url之后,要判断用户是否有权限 for(String url:urls){ if(path.contains(url)){ logger.info("该用户拥有该路径权限,允许访问"); return true; } } logger.info("该用户没有该路径的权限,拒绝访问"); return false; } public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } }
redis线程池相关的代码为:
package hrm.redis; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * 单例 */ public class RedisUtil { private RedisUtil(){}; private static volatile JedisPool jedisPool = null; //返回jedisPool public static JedisPool getJedisPool(){ if(jedisPool==null){ synchronized (JedisPool.class){//不能锁住jedisPool对象,因为jedisPool为空 if(jedisPool==null){ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxActive(500);//最大连接数 jedisPoolConfig.setMaxIdle(5);//最大空闲数 jedisPoolConfig.setMaxWait(1000*100);//最长等待时间(过了该时间就回收该连接) jedisPoolConfig.setTestOnBorrow(true); jedisPool = new JedisPool(jedisPoolConfig,"localhost"); } } } return jedisPool; } //返回jedis连接到连接池中 public static void returnResource(JedisPool jedisPool, Jedis jedis){ if(jedis != null) jedisPool.returnResource(jedis); } }
另外,由于使用了缓存,当管理员对权限控制模块进行修改之后,要做清空redis缓存的操作。否则,就会出现这样的情况:管理员明明已经收回了员工jack的修改员工信息的权限,该员工jack还能继续修改员工的信息。(缓存造成的影响)
总结
现在只是简单的学习使用redis,只是将redis用作读缓存,也没有进行写操作以及数据落地操作。推荐文章
redis的详细介绍以及和memcached的区别https://www.cnblogs.com/Survivalist/p/8119891.html
redis的各种操作
https://www.cnblogs.com/tuojunjie/p/6226441.html
参考文章
https://www.cnblogs.com/Survivalist/p/8119891.htmlhttps://www.cnblogs.com/tuojunjie/p/6226441.html
http://blog.csdn.net/dreamwbt/article/details/76167340
相关文章推荐
- Redis学习1_Redis快速入门
- Redis学习笔记1--入门篇
- Redis学习笔记1--入门篇
- 07-Redis入门介绍学习
- Redis3.0.5学习笔记(一)基础入门
- redis入门指南 学习笔记(二) Redis的多数据库
- Redis学习1之快速入门
- 【Redis学习】Redis入门安装及使用
- Clojure 学习入门(9)—— 连接redis
- Redis的入门学习指南
- Redis学习 - 入门
- Redis客户端开发包:Jedis学习-入门
- Redis专题之快速入门实践(无需安装,在线学习)
- Redis入门学习
- 学习Redis之Jedis入门
- Redis入门学习笔记一
- Redis入门学习(进行中..)
- redis入门学习笔记
- Jedis操作redis入门学习
- Redis入门 -学习笔记(1)