Java EE--组件篇 Redis

目录

前言

Redis

下载安装

redis的基本概念

***:Redis为什么设计成单线程的?

***:解释IO多路复用、reactor模型

redis的持久化机制

SpringBoot集成Redis

Redis使用场景分析

Redis高级特性


前言

带着问题学java系列博文之java基础篇。从问题出发,学习java知识。


Redis

来自redis官网——Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

下载安装

linux上redis安装配置

1.下载 link:http://www.redis.cn/download.html

2.上传解压 rz -y tar -zxvf redis-5.0.7.tar.gz

3.编译安装 cd redis-5.0.7 make

进入src cd redis-5.0.7/src make install PREFIX=/usr/local/redis

(个人喜好安装在/usr/local/redis下)

4.安装成功redis路径下会生成bin,一系列命令都在里面

5.创建etc文件夹 复制配置文件 redis.conf

mkdir /usr/local/redis/etc

mv ~/redis-5.0.7/redis.conf /usr/local/redis/etc (将配置文件移动到etc)

6.修改配置文件

vi /usr/local/redis/etc/redis.conf

修改三个地方:

①NETWORK栏目中:开放IP限制,外网访问

注释掉 #bind 127.0.0.1 或者修改为 bind 0.0.0.0

②GENERAL栏目中:配置后台启动

修改 daemonize no 为 daemonize yes

③SECURITY栏目中:设置访问密码

添加 requirepass ***** (注释掉 requirepass foobared,redis默认无密码)

至此redis安装配置完成,可以开启redis测试下:

启动server服务: /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf

ps查看:

 

 

 

启动client进入: /usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 6379(端口默认是6379) -a ****(之前设置的密码)

 

7.加入开机启动

修改开机启动文件:

vi /etc/rc.local

在文件最后添加下面内容(启动redis server的命令):

 

 

 

redis的基本概念

redis是NoSql非关系型数据库,存储在内存中,高性能。存储的是key-value型,类似一个大的map。如果没有做相应的持久化,当redis服务器重启,或者电脑重启,数据会丢失。redis可存储的主要数据类型:字符串 string、哈希类型hash(map)、列表 list、集合set、有序集合 sortedset。

redis高并发,高性能的原因:

       1.Redis是基于内存的,内存的读写速度非常快;

       2.Redis是单线程的,省去了很多上下文切换线程的时间,也没有加锁性能消耗;

       3.Redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间;

       4.Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。

***:Redis为什么设计成单线程的?

官方回答是:因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。虽然Redis设计成单线程,但性能却非常高,普通笔记本部署都可以轻松处理每秒数十万并发请求。此外,Redis使用了良好的io多路复用技术epoll,采用事件驱动、reactor模型,拥有很大的吞吐量。

***:解释IO多路复用、reactor模型

举一个实际场景:老师组织一次随堂测验,30名学生即时作答,老师当堂检查答题结果。老师有三种方式进行测验结果检查:

1.按照学生座位顺序,逐个检查;(单线程,for循环处理所有客户端请求)

2.叫来全校其他的老师帮忙,30名老师分别检查30名学生的答卷;(多线程,一个线程处理一个客户端请求)

3.老师让学生自己提交,谁完成答题,谁就上讲台提交,然后老师进行检查;(多路复用技术,单线程处理所有客户端请求)

第一种方式,虽然简单,而且也是单线程;但是有个致命问题:当检查到某个学生时,他还在作答,这时候老师就得等他作答完毕,才能继续,意味着所有的学生都得等这位同学。

第二种方式,虽然并发很好,可以快速完成检查;但是很显然,资源消耗太大,一场随堂测验居然要找来30位老师进行检查。

第三种方式,就是我们要讲的多路复用技术,虽然只有一位老师进行检查(单线程),但是却没有第一种方式存在的等待问题。

redis服务端处理线程被动等待redis客户端随机到来的请求,有请求到来就进行处理。自己实现的话,可以这样设计:所有客户端socketclient与服务端socketserver连接,然后socketserver接收所有的客户端发送过来的数据,socketserver对这些事件不作任何处理,直接存入阻塞队列;注册了该事件的处理线程则尝试从阻塞队列获取,获取到就进行处理。当然redis底层采用epoll模型的多路复用技术,要比我们设计的优越很多。

 

 

 

redis的持久化机制

redis的数据存取都是直接内存操作,如果不做持久化,一旦机器重启或者异常崩溃等,数据将全部丢失。所以redis也支持几种持久化机制,对内存中的数据进行持久化,保存到本地文件中。

1.RDB:默认的方式,一定的时间间隔中,检测key的变化情况,然后持久化数据。(可以修改配置,自定义检查的时间间隔,以及key变化的数量。比如,自定义检查间隔10分钟,key变化数量100条。则redis每隔10分钟会检查一次,如果发现key变化的数量超过100,则进行一次持久化。)

2.AOF:日志记录的方式,可以记录每一条命令的操作,每一次命令操作后都持久化数据.

*:修改conf文件, appendonly no(关闭 aof) appendonly yes(开启aof)

appendfsync always:每一次都进行持久化

appendfsync everysec:每隔一秒进行一次持久化

appendfsync no:不进行持久化

 

从这两种持久化机制可以看出,其实redis是无法保证数据一定不丢失的,不管是RDB还是AOF都存在一定程度的数据丢失风险。

 

SpringBoot集成Redis

spring框架对redis进行了集成封装,spring-data-redis模块,封装了RedisTemplate模板类,便于我们使用。

1.依赖导入:

        <!--redis启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- lettuce pool 缓存连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!--jackson序列化器,实现自定义template-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

2.信息配置(application.yml):

spring:
  redis:
    database: 0            #数据库索引(默认是0)
    host: 192.168.218.130  #redis服务器地址
    port: 6379             #端口
    password: xxx     #密码
    timeout: 10000         #连接超时时间(ms)
    # lettuce连接池
    lettuce:
      pool:
        max-active: 8      #连接池最大的连接数(负值表示没限制)
        max-wait: 10000    #最大的阻塞等待时间(负值表示没限制 ms)
        max-idle: 8        #最大的空闲连接数
        min-idle: 0        #最小的空闲连接数
      shutdown-timeout: 100 #关闭超时时间(ms)

3.自定义模板(可选)

因为spring为我们封装的redistemplate默认是redistemplate<String,String>类型,使用起来不是很方便(存储非string的数据,还需要先进行对象转为字符串的操作)。所以一般都是自定义一个模板,redistemplate<String,Object>。如下,借助configuration注解,向spring框架注入自定义的redistemplate实例。

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<String,Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        //设置key序列化器 string序列化器
        template.setKeySerializer(new StringRedisSerializer());
        //设置hash的key序列化器
        template.setHashKeySerializer(new StringRedisSerializer());

        //此种序列化方式结果清晰、容易阅读、存储字节少、速度快,所以推荐使用
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //设置value的序列化器(jackson序列化器,object转换为json)
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

4.使用自定义的redistemplate模板,封装一系列的数据存储操作工具类:(可选)

@Component
public final class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    //=======================common============================

    /**
     * 指定缓存时间
     * @param key 键
     * @param time 时间(s)
     * @return
     */
    public boolean expire(String key,long time){
        try {
            if (time>0){
                redisTemplate.expire(key,time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 获取过期时间
     * @param key 键
     * @return 时间(s)返回0代表永久有效
     */
    public long getExpire(String key){
        return redisTemplate.getExpire(key);
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true存在,false不存在
     */
    public boolean hasKey(String key){
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除缓存
     * @param keys 可以传递多个key
     */
    @SuppressWarnings("unchecked")
    public void del(String... keys){
        if (keys != null && keys.length>0){
            if (keys.length == 1){
                redisTemplate.delete(keys[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(keys));
            }
        }
    }

    //=======================String============================
    /**
     * 普通缓存获取值
     * @param key 键
     * @return 值
     */
    public Object get(String key){
        return key == null ? null:redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存存入
     * @param key 键
     * @param value 值
     * @return true成功,false失败
     */
    public boolean set(String key,Object value){
        try {
            redisTemplate.opsForValue().set(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(s)time要大于0,否则将使用默认时长(无限期)
     * @return true成功,false失败
     */
    public boolean set(String key,Object value,long time){
        try {
            if (time > 0){
                redisTemplate.opsForValue().set(key,value,time);
            } else {
                set(key,value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增(要求key对应的value必须可以转换成Double型)
     * @param key 键
     * @param delta 递增数值
     * @return 递增之后的数值
     */
    public double incr(String key,double delta){
        if (delta < 0){
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key,delta);
    }

    /**
     * 递减(要求key对应的value必须可以转换成Double型)
     * @param key 键
     * @param delta 递减数值
     * @return 递减后的数值
     *
     * 由于decrement方法仅支持long类型,所以这里还是使用increment,取反因子
     */
    public double decr(String key,double delta){
        if (delta < 0){
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key,-delta);
    }


    //=======================Map============================

    /**
     * hashGet(获取map中的值)
     * @param key 键
     * @param item 项
     * @return 值
     */
    public Object hGet(String key,String item){
        return redisTemplate.opsForHash().get(key,item);
    }

    /**
     * 向hash表中存入一条数据,不存在将创建hash表
     * @param key 键
     * @param item 项
     * @param value 值
     * @return
     */
    public boolean hSet(String key,String item,Object value){
        try {
            redisTemplate.opsForHash().put(key,item,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向hash表中存入一条数据,不存在将创建hash表
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时长
     * @return
     */
    public boolean hSet(String key,String item,Object value,long time){
        try {
            redisTemplate.opsForHash().put(key,item,value);
            if (time > 0){
                expire(key,time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取hashkey对应的map
     * @param key 键
     * @return map
     */
    public Map<Object, Object> hmGet(String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * hashset 保存map
     * @param key 键
     * @param map 数据
     * @return
     */
    public boolean hmSet(String key,Map<String,Object> map){
        try {
            redisTemplate.opsForHash().putAll(key,map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * hashset 保存map
     * @param key 键
     * @param map 数据
     * @param time 时长
     * @return
     */
    public boolean hmSet(String key,Map<String,Object> map,long time){
        try {
            redisTemplate.opsForHash().putAll(key,map);
            if (time > 0){
                expire(key,time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hashmap中的item项
     * @param key 键
     * @param item 项,可以多个
     */
    public void hDel(String key,Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }

    /**
     * 判断hashmap中是否有该项
     * @param key 键
     * @param item 项
     * @return
     */
    public boolean hHasItem(String key,Object item){
        return redisTemplate.opsForHash().hasKey(key,item);
    }

    /**
     * hash递增,如果不存在会创建一个,再返回新增之后的值
     * @param key 键
     * @param item 项
     * @param delta 递增因子
     * @return
     */
    public double hIncr(String key,String item,double delta){
        return redisTemplate.opsForHash().increment(key,item,delta);
    }

    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param delta 递减因子
     * @return
     */
    public double hDecr(String key,String item,double delta){
        return redisTemplate.opsForHash().increment(key,item,-delta);
    }


    //=======================Set============================

    /**
     * 根据key获取set中所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key){
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 存入set集合
     * @param key 键
     * @param values 值
     * @return 成功个数
     */
    public long sSet(String key,Object... values){
        return redisTemplate.opsForSet().add(key,values);
    }

    /**
     * 存入set集合,并设定有效时长
     * @param key 键
     * @param time 时长
     * @param values 值
     * @return 成功个数
     */
    public long sSet(String key,long time,Object... values){
        long count = redisTemplate.opsForSet().add(key,values);
        if (time > 0){
            expire(key,time);
        }
        return count;
    }

    /**
     * 判断value是否存在
     * @param key
     * @param value
     * @return
     */
    public boolean sHasItem(String key,Object value){
        return redisTemplate.opsForSet().isMember(key,value);
    }

    /**
     * 获取set的size
     * @param key 键
     * @return
     */
    public long sSize(String key){
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 移除set中的value
     * @param key
     * @param values
     * @return
     */
    public long sRemove(String key,Object... values){
        return redisTemplate.opsForSet().remove(key,values);
    }


    //=======================list============================

    /**
     * 获取list中的内容
     * @param key 键
     * @param start 开始下标
     * @param end 结束下标 (0,-1)代表整个list
     * @return
     */
    public List<Object> lRange(String key,long start,long end){
        return redisTemplate.opsForList().range(key,start,end);
    }

    /**
     * 获取list的size
     * @param key 键
     * @return
     */
    public long lSize(String key){
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 取索引index的值
     * @param key
     * @param index 0表头,1第二个元素,-1表尾,-2倒数第二个元素
     * @return
     */
    public Object lIndex(String key,long index){
        return redisTemplate.opsForList().index(key,index);
    }

    /**
     * 向list中存入值(从列表尾部添加)
     * @param key 键
     * @param value 值
     */
    public void lRightPush(String key,Object value){
        redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 向list中存入值,并设定有效时长(从列表尾部添加)
     * @param key 键
     * @param value 值
     * @param time 时长
     */
    public void lRightPush(String key,Object value,long time){
        redisTemplate.opsForList().rightPush(key,value);
        expire(key,time);
    }

    /**
     * 向list中存入多个值(从列表尾部添加)
     * @param key 键
     * @param values 值
     */
    public void lRightPushAll(String key,List<Object> values){
        redisTemplate.opsForList().rightPushAll(key, values);
    }

    /**
     * 向list中存入值,并设定有效时长(从列表尾部添加)
     * @param key 键
     * @param values 值
     * @param time 时长
     */
    public void lRightPushAll(String key,List<Object> values,long time){
        redisTemplate.opsForList().rightPushAll(key,values);
        expire(key,time);
    }

    /**
     * 修改index下标对应的值
     * @param key 键
     * @param index 下标
     * @param value 值
     */
    public void lUpdateIndex(String key,long index,Object value){
        if (Math.abs(index) < lSize(key)){
            redisTemplate.opsForList().set(key,index,value);
        } else {
            //下标越界
        }
    }

    /**
     * 向list中存入值(从列表头部添加)
     * @param key 键
     * @param value 值
     */
    public void lLeftPush(String key,Object value){
        redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 向list中存入值,并设定有效时长(从列表头部添加)
     * @param key 键
     * @param value 值
     * @param time 时长
     */
    public void lLeftPush(String key,Object value,long time){
        lLeftPush(key,value);
        expire(key,time);
    }

    /**
     * 向list中存入多个值(从列表头部添加)
     * @param key 键
     * @param values 值
     */
    public void lLeftPushAll(String key,List<Object> values){
        redisTemplate.opsForList().leftPushAll(key, values);
    }

    /**
     * 向list中存入值,并设定有效时长(从列表头部添加)
     * @param key 键
     * @param values 值
     * @param time 时长
     */
    public void lLeftPushAll(String key,List<Object> values,long time){
        redisTemplate.opsForList().leftPushAll(key,values);
        expire(key,time);
    }

    /**
     * 移除列表中count个值为value的元素
     * @param key 键
     * @param value 值
     * @param count 限定个数
     * @return 实际移除的个数
     */
    public long lRemove(String key,Object value,long count){
        return redisTemplate.opsForList().remove(key, count, value);
    }
}

至此,springboot集成redis完成。

 

Redis使用场景分析

redis使用key-value模式存储,数据直接存储在内存中,数据操作简单、高效;但是由于查询数据全都依赖key,给复杂查询带来诸多不便。因此适用场景主要是数据量小,需要频繁、高速读写数据,数据结构简单,查询条件简单;比如用户权限认证的token等系统需要频繁读写的运行期关键数据的存储,就非常适合用redis。

redis还被用于缓存介质,比如mybatis、hibernate框架都支持二级缓存,如果不进行存储配置,它们的二级缓存默认都是存储在当前后台服务内存中,可以使用redis作为存储介质,减轻当前后台服务的压力。

在分布式系统中,redis还被用来做为分布式锁的一种实现方式:某个系统建立一个分布式锁(在redis中存入指定key,value为随机值,并设置key不允许重复创建),其他系统都不断轮询尝试获取锁(创建key),获得锁的系统执行任务,执行完毕则释放锁(删除key)。

 

 

 

Redis高级特性

redis哨兵、分区、高可用;

redis事务;

利用redis作为mybatis、hibernate的二级缓存存储介质;

未完待续……


以上系个人理解,如果存在错误,欢迎大家指正。原创不易,转载请注明出处!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值