Redis的入门学习

什么是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数据时不是直接从内存中读取吗,为什么会用到连接池呢。

扫描二维码关注公众号,回复: 1063405 查看本文章

原来服务器向数据库访问数据也需要建立连接的。正如上面引用的文章所说,Redis是基于C/S模式的。当服务器向Redis数据库访问数据时,需要先建立连接,才能进行通信。

也正如上面引用的文章所说,Redis读取数据速度很快,而建立连接的过程却很耗时,因此可以使用连接池,从而减少不必要的开销(这个不必要的开销就是指建立连接、释放连接的时间损耗),这样才能真正发挥出Redis的缓存作用。

如何使用redis

毕竟redis就是一个数据库,因此首先得安装redis服务器,其次要启动redis服务器,然后通过下面这一行代码就可以建立连接了:

Jedis jedis = new Jedis("localhost");

然后就可以操作数据库了:

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.html
https://www.cnblogs.com/tuojunjie/p/6226441.html
http://blog.csdn.net/dreamwbt/article/details/76167340

猜你喜欢

转载自blog.csdn.net/weixin_30531261/article/details/79612477