Spring Data 教程 – Redis

1. Redis简介

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的API。Redis 是一个高性能的key-value数据库。 redis的出现,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。

Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。

redis的key都是字符串String类型,它的value是多样化的,如下图:

redis数据类型 ENCODING返回的编码 底层对应的数据结构
string int long类型的整数
string embstr embstr编码的简单动态字符串
string raw 简单动态字符串
list ziplist 压缩列表
list linkedlist 双向链表
hash ziplist 压缩列表
hash ht 字典
set intset 整数集合
set ht 字典
zset ziplist 压缩列表
zset skiplist 跳表

2. Redis的五种数据类型

2.1 字符串对象(String)

字符串对象的模型:

redis底层提供了三种不同的数据结构实现字符串对象,根据不同的数据自动选择合适的数据结构。这里的字符串对象并不是指的纯粹的字符串,数字也是可以的。

  • int:当数据是long类型的整数字符串时,底层使用long类型的整数实现。这个值会直接存储在字符串对象的ptr属性中,同时OBJECT ENCODING为int。

  • raw:当数据为长度大于44字节的字符串时,底层使用简单动态字符串实现,说到这里就不得不提下redis的简单随机字符串(Simple Dynamic String,SDS),SDS有三个属性,free,len和buf。free存的是还剩多少空间,len存的是目前字符串长度,不包含结尾的空字符。buf是一个list,存放真实字符串数据,包含free和空字符。针对SDS本文不做详细介绍,欢迎点击SDS了解。

  • embstr:当数据为长度小于44字节的字符串时,底层使用embstr编码的简单动态字符串实现。相比于raw,embstr内存分配只需要一次就可完成,分配的是一块连续的内存空间。

2.2 列表对象(List)

列表对象的模型:

redis中的列表对象经常被用作消息队列使用,底层采用ziplist和linkedlist实现。大家使用的时候当作链表使用就可以了。

  • ziplist

    列表对象使用ziplist编码需要满足两个要求,一是所有字符串长度都小于设定值值64字节(可以在配置文件中修改list-max-ziplist-value字段改变)。二是所存元素数量小于设定值512个(可以在配置文件中修改list-max-ziplist-entries字段改变)。ziplist类似与python中的list,占用一段连续的内存地址,由此减小指针内存占用。

    zlbytes:占内存总数

    zltail:到尾部的偏移量

    zllen:内部节点数

    node:节点

    zlend:尾部标识

    previous_entry_length:前一节点的长度

    encoding:数据类型

    content:真实数据

    遍历的时候会根据zlbytes和zltail直接找到尾部节点nodeN,然后根据每个节点的previous_entry_length反向遍历。增加和删除节点会导致其他节点连锁更新,因为每个节点都存储了前一节点的长度。

  • linkedlist

    linkedlist有三个属性,head,tail和len。head指向链表的头部,tail指向链表的尾部,len为链表的长度。

2.3 哈希类型对象(Hash)

哈希类型对象的模型:

redis的value类型hash类型,其实就是map类型,就是在值的位置放一个map类型的数据。大家想详细了解一下,可以参考一下这篇文章://www.jianshu.com/p/658365f0abfc

2.4 集合对象(Set)

集合对象类型的模型:

Set类型的value保证每个值都不重复。

redis中的集合对象底层有两种实现方式,分别有整数集合和hashtable。当所有元素都是整数且元素数小于512(可在配置文件中set-max-intset-entries字段配置)时采用整数集合实现,其余情况都采用hashtable实现。hashtable请移驾上文链接查阅,接下来介绍整数集合intset。intset有三个属性,encoding:记录数字的类型,有int16,int32和int64等,length:记录集合的长度,content:存储具体数据。具体结构如下图:

2.5 有序集合对象

有序集合对象(zset)和集合对象(set)没有很大区别,仅仅是多了一个分数(score)用来排序。

redis中的有序集合底层采用ziplist和skiplist跳表实现,当所有字符串长度都小于设定值值64字节(可以在配置文件中修改list-max-ziplist-value字段改变),并且所存元素数量小于设定值512个(可以在配置文件中修改list-max-ziplist-entries字段改变)使用ziplist实现,其他情况均使用skiplist实现,跳跃表的实现原理这里偷个懒,给大家推荐一篇写的非常好的博客,点击查看跳跃表原理

3. Redis的安装

可以去官网或者中文网下载Redis。redis的windows版本现在已经不更新了,所以我们安装redis的6.0.3版本,这个版本支持的东西很多,在此次教程中,我们只对redis的五种数据类型做解释和学习。

官网://redis.io/

中文网://www.redis.net.cn/

本教程安装的redis版本为6.0.3版本,redis使用C语言编写的,CentOS7的gcc自带版本为4.8.5,而redis6.0+需要的gcc版本为5.3及以上,所以需要升级gcc版本。

下载Linux版本的tar.gz包,解压以后进入解压产生的包:

cd redis-6.0.3

发现没有bin目录,这里需要通过make进行安装。

# 先检查gcc的环境 
gcc -v 
# 查看gcc版本 
yum -y install centos-release-scl 
# 升级到9.1版本 
yum -y install devtoolset-9-gcc devtoolset-9-gcc- c++ devtoolset-9-binutils 

scl enable devtoolset-9 bash 
#以上为临时启用,如果要长期使用gcc 9.1的话: 
echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile 
# 进入redis解压文件 
make 
# 6.0的坑,gcc版本 9.0 以上
# 等待完毕

执行完make操作之后,就可以在redis目录看到src目录了。进入src目录后就可以看到redis-serverredis-cli

这里建议将Redis的配置文件复制,保留一份原生的配置文件。

redis的配置大家可以在网上搜一下常用的配置,在这里给大家推荐一个常用的配置,比较详细:

//blog.csdn.net/ymrfzr/article/details/51362125

到这里redis就可以启动并且正常访问了。

注意:一定要将redis的IP地址绑定注释掉,允许所有的IP地址访问,不然我们从Windows访问就访问不了。

注释掉下面的这一行:

同时关闭Redis的服务保护模式,将protected-mode设置为no。如下:

4. Spring Boot 整合 Redis

  • 4.1 搭建工程,引入依赖

    搭建工程的操作我这里就不在写出来了。直接上pom.xml

    <!--springboot父工程-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
    <dependencies>
        <!--springboot-web组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <!--redis整合springboot组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.3.0.RELEASE</version>
        </dependency>
        <!--lombok组件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
    </dependencies>
    
  • 4.2 redis的配置

    项目的配置文件,application.yml

    butterflytri:
      host: 127.0.0.1
    server:
      port: 8080 # 应用端口
      servlet:
        context-path: /butterflytri # 应用映射
    spring:
      application:
        name: redis # 应用名称
      redis:
        host: ${butterflytri.host} # redis地址
        port: 6379 # redis端口,默认是6379
        timeout: 10000 # 连接超时时间(ms)
        database: 0 # redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
        jedis: # 使用连接redis的工具-jedis
          pool:
            max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 默认 8
            max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
            max-idle: 8 # 连接池中的最大空闲连接 默认 8
            min-idle: 0 # 连接池中的最小空闲连接 默认 0
    

    另外还有额外的配置类RedisConfig.java

    package com.butterflytri.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    /**
     * @author: WJF
     * @date: 2020/5/24
     * @description: RedisConfig
     */
    
    @Configuration
    public class RedisConfig {
    
        /**
         * redis键值对的值的序列化方式:通用方式
         * @return RedisSerializer
         */
        private RedisSerializer redisValueSerializer() {
            return new GenericJackson2JsonRedisSerializer();
        }
    
        /**
         * redis键值对的健的序列化方式:所有的健都是字符串
         * @return RedisSerializer
         */
        private RedisSerializer redisKeySerializer() {
            return new StringRedisSerializer();
        }
    
        @Bean("redisTemplate")
        public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            redisTemplate.setKeySerializer(redisKeySerializer());
            redisTemplate.setValueSerializer(redisValueSerializer());
            return redisTemplate;
        }
    
    }
    
  • 4.3 redisTemplate的使用

    value类型的值的CRUD:

    ValueServiceImpl.java

    package com.butterflytri.service.impl;
    
    import com.butterflytri.service.ValueService;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    /**
     * @author: WJF
     * @date: 2020/5/27
     * @description: ValueServiceImpl
     */
    @Service
    public class ValueServiceImpl implements ValueService {
    
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
    
        @Override
        public void addValue(String key, Object value) {
            redisTemplate.opsForValue().set(key,value);
        }
    
        @Override
        public Object get(String key) {
            return redisTemplate.opsForValue().get(key);
        }
    
        @Override
        public Object update(String key, Object newValue) {
            return redisTemplate.opsForValue().getAndSet(key,newValue);
        }
    
        @Override
        public void delete(String key) {
            redisTemplate.delete(key);
        }
    }	
    

    List类型的值的CRUD:

    这里我加了枚举类型用来控制增加的位置,因为List类型对应的是链表。

    ListServiceImpl.java

    package com.butterflytri.service.impl;
    
    import com.butterflytri.enums.OpsType;
    import com.butterflytri.service.ListService;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    /**
     * @author: WJF
     * @date: 2020/5/28
     * @description: ListServiceImpl
     */
    @Service
    public class ListServiceImpl implements ListService {
    
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
    
        @Override
        public void addList(String key, List<Object> list, OpsType type) {
            switch (type) {
                case RIGHT:
                    redisTemplate.opsForList().rightPushAll(key, list);
                    break;
                case LEFT:
                    redisTemplate.opsForList().leftPushAll(key, list);
                    break;
                default:
                    throw new RuntimeException("type不能为null");
            }
        }
    
        @Override
        public void add(String redisKey, Object value, OpsType type) {
            switch (type) {
                case RIGHT:
                    redisTemplate.opsForList().rightPush(redisKey, value);
                    break;
                case LEFT:
                    redisTemplate.opsForList().leftPush(redisKey, value);
                    break;
                default:
                    throw new RuntimeException("type不能为null");
            }
        }
    
        @Override
        public List<Object> get(String key) {
            return redisTemplate.opsForList().range(key, 0, -1);
        }
    
        @Override
        public Object update(String key, Object value, Integer index) {
            Object obj = redisTemplate.opsForList().index(key, index);
            redisTemplate.opsForList().set(key,index,value);
            return obj;
        }
    
        @Override
        public void delete(String key) {
            redisTemplate.delete(key);
        }
    
        @Override
        public void deleteValue(String redisKey, OpsType type) {
            switch (type) {
                case RIGHT:
                    redisTemplate.opsForList().rightPop(redisKey);
                    break;
                case LEFT:
                    redisTemplate.opsForList().leftPop(redisKey);
                    break;
                default:
                    throw new RuntimeException("type不能为null");
            }
        }
    }
    

    Hash类型的值的CRUD:

    hash类型是我们使用最常用的类型。

    HashServiceImpl.java:

    package com.butterflytri.service.impl;
    
    import com.butterflytri.service.HashService;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.Map;
    
    /**
     * @author: WJF
     * @date: 2020/5/28
     * @description: HashServiceImpl
     */
    @Service
    public class HashServiceImpl implements HashService {
    
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
    
        @Override
        public void addHashAll(String key, Map<String, Object> value) {
            redisTemplate.opsForHash().putAll(key, value);
        }
    
        @Override
        public void addHash(String redisKey, String key, Object value) {
            redisTemplate.opsForHash().put(redisKey, key, value);
        }
    
        @Override
        public Object get(String redisKey, String key) {
            return redisTemplate.opsForHash().get(redisKey, key);
        }
    
        @Override
        public Object update(String redisKey, String key, Object value) {
            Object obj = this.get(redisKey, key);
            this.delete(redisKey,key);
            redisTemplate.opsForHash().put(redisKey, key, value);
            return obj;
        }
    
        @Override
        public void delete(String redisKey, String key) {
            redisTemplate.opsForHash().delete(redisKey, key);
        }
    
        @Override
        public void deleteAll(String redisKey) {
            redisTemplate.delete(redisKey);
        }
    }
    

    Set的值的CRUD:

    package com.butterflytri.service.impl;
    
    import com.butterflytri.service.SetService;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.Set;
    
    /**
     * @author: WJF
     * @date: 2020/5/28
     * @description: SetServiceImpl
     */
    @Service
    public class SetServiceImpl implements SetService {
    
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
    
    
        @Override
        public void addAll(String key, Set<Object> set) {
            redisTemplate.opsForSet().add(key,set);
        }
    
        @Override
        public void add(String key, Object value) {
            redisTemplate.opsForSet().add(key,value);
        }
    
        @Override
        public Set<Object> findAll(String key) {
            return redisTemplate.opsForSet().members(key);
        }
    
        @Override
        public void deleteValue(String key, Object value) {
            redisTemplate.opsForSet().remove(key,value);
        }
    
        @Override
        public void delete(String key) {
            redisTemplate.delete(key);
        }
    }
    

    ZSet类型的值的CRUD:

    package com.butterflytri.service.impl;
    
    import com.butterflytri.service.SortedSetService;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.LinkedHashSet;
    
    /**
     * @author: WJF
     * @date: 2020/5/28
     * @description: SortedSetServiceImpl
     */
    @Service
    public class SortedSetServiceImpl implements SortedSetService {
    
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
    
        @Override
        public void add(String key, String value, Double score) {
            redisTemplate.opsForZSet().add(key, value, score);
        }
    
        @Override
        public LinkedHashSet<Object> findAll(String key) {
            return (LinkedHashSet<Object>) redisTemplate.opsForZSet().range(key,0,-1);
        }
    
        @Override
        public Long count(String key, Double scoreFrom, Double scoreTo) {
            return redisTemplate.opsForZSet().count(key,scoreFrom,scoreTo);
        }
    
        @Override
        public LinkedHashSet<Object> findByScore(String key, Double scoreFrom, Double scoreTo) {
            return (LinkedHashSet<Object>) redisTemplate.opsForZSet().rangeByScore(key,scoreFrom,scoreTo);
        }
    
        @Override
        public Long rank(String key, Object value) {
            return redisTemplate.opsForZSet().rank(key,value);
        }
    
        @Override
        public void remove(String key, String value) {
            redisTemplate.opsForZSet().remove(key,value);
        }
    
        @Override
        public void delete(String key) {
            redisTemplate.delete(key);
        }
    
    }
    

    redis的Java客户端有很多,在这里我们使用的是jedis,还有一个很好的Java语言的客户端叫lettuce,大家可以去了解一下,Spring从不重复造轮子,只会简化轮子的使用,redisTemplate就是一个超级简单的使用实现。到这里redis整合Spring Boot 就结束了。

5. 项目地址

本项目传送门:

此教程会一直更新下去,觉得博主写的可以的话,关注一下,也可以更方便下次来学习。