首页> 博客> 小白轻松入门Redis
30 04 2019
一、了解Redis
1.1、关于NoSQL

NoSQL的全称是Not only SQL,在过去的几年中,NoSQL数据库一度成为高并发、海量数据存储解决方案的代名词,与之相应的产品也呈现出雨后春笋般的生机。然而在众多产品中能够脱颖而出的却屈指可数,如Redis、MongoDB、BerkeleyDB和memcached等内存数据库。

1.2、关于Redis

Redis,中文网站 。典型的NoSQL数据库服务器,它可以作为服务程序独立运行于自己的服务器主机。在很多时候,人们只是将Redis视为Key/Value数据库服务器,然而事实并非如此,在目前的版本中,Redis除了Key/Value之外还支持List、Set、Hash和Ordered Set等数据结构,因此它的用途也更为宽泛。

Redis的特性:速度快、支持很多语言、持久化、多种数据结构、主从复制以及高可用与分布式

二、Redis的安装
2.1、Redis 在CentOS7下的安装

下载、解压以及编译Redis


#安装流程
wget http://download.redis.io/releases/redis-5.0.4.tar.gz
tar xzf redis-5.0.4.tar.gz
cd redis-5.0.4
make
#遇到[adlist.o]Error 127
yum install gcc
#运行Redis
src/redis-server redis.conf
2.2、Redis 在Windows下的安装

很不幸的是,Redis项目不正式支持Windows。 但是,Microsoft开放技术小组开发和维护这个Windows端口针对Win64,目前从网上找到win环境的最新版本是3.2.100,虽然不是最新版本,但本地开发学习足够用啦。下面就来聊下,Win下如何安装Redis。下载地址

下载完成,解压后的文件结构:

双击redis-server.exe,启动redis-server服务:

redis默认启动6379端口,需要保证本地6379端口没被占用!出现此界面表示redis服务已经正常启动了,这时候就可启动redis客户端进行相关操作了。

双击redis-cli.exe,启动redis-cli客户端:

当然,你也可以将redis安装成win系统服务,这样每次就不需要去双击打开客户端服务端啦(推荐)


#提示successful的字样表示成功,服务不能重复安装
#需要进入你安装包解压的路径中,win10注意cmd要以管理员打开
redis-server --service-install redis.windows-service.conf --loglevel verbose
#安装成功后,可以将解压路径配置到环境变量path中去:
C:\Program Files\MZJ\Redis-x64-3.2.100(这是我本地解压的路径,将这个路径配置到环境变量中,下次执行redis-cli就不需要到指定路径了,全局使用方便)

#常用的命令

卸载服务:redis-server --service-uninstall
开启服务:redis-server --service-start
停止服务:redis-server --service-stop

#注意:如果需要修改配置文件信息,可在redis.windows-service.conf文件(非redis.windows.conf)自定义配置信息,因为Redis安装的服务默认加载的是该文件。当然你启动redis-server的时候也可以指定配置文件
2.3、Redis常用的配置选项

常用配置选项:

  • daemonize yes 配置为守护进程,后台启动,默认为no
  • port 6379 修改默认监听端口
  • bind 127.0.0.1 默认只支持本地访问,改行注释掉则允许所有主机访问redis,正式环境不推荐
  • protected-mode no 关闭保护模式
  • requirepass xxxx 配置redis密码,使用时候需要输入:auth xxxx 进行认证,认证成功后才能操作redis
  • logfile 设置日志文件
  • databases 255 设置redis数据库总量,推荐不超过255
  • dir 设置数据文件储存目录
三、Redis命令
3.1、Redis通用命令
查看客户端连接信息:
	info replication
	info memory
ping
   	PONG 表示数据库正常运行
选择数据库:
    select + number (0-15) ,默认提供16个数据库,可以扩展到256(推荐,尽量不要再大)select 0 			表示选择0号数据库,一次类推
   	keys *   查看数据库中所有的key的值;不推荐在生产环境中使用,keys 命令是阻塞的
            keys he*  keys ph? ....
    dbsize  返回是key 的数量
	exists + key 返回 1 表示key 存在
    del +key 表示删除 key 对应的数据
	expire key +时间(单位秒) 设置key 的有效时间
    ttl + key 查看key是否有效 ,返回-2 表示过期
	flushdb 表示清空当前数据库中所有的数据
	flushall 表示删除所有数据库中的所有数据
	redis-cli shutdown 关闭redis服务器(推荐)
	netstat -tulpn | grep redis 与 kill -9 pid
四、Redis数据结构

Redis中支持五种数据结构

  • String 字符串类型(较多)
  • Hash hash类型(Map)
  • List 列表类型
  • Set 集合类型
  • ZSet 有序集合类型
4.1、Redis字符串类型

特点:

  1. String 类型最大存储不超过512mb,单个key - value 不超过100kb,可以是数字、字符串以及json字符串(javaBean)
  2. String类型结构的应用场景:缓存、秒杀、分布式锁(分布式事务的一致性,分布式事务)、配置中心(统一分布式各系统的配置,统一设置读取)、对象序列化(jackson,gson序列化工具)、计数器(用于统计一些数据,保存为字符串数据类型)

常用的指令:

get  key  获取key的值
set  key  value 设置key 的值为value (重复set ,新的值会把旧的值覆盖掉)                      mset hello world java best  一次性设置多个key的值,这里hello 和java作为key
mget hello java 一次性获取多个key的值
del key 删除key的值
incr/decr key 表示key的值自增/自减1 (有点类似mysql 数据库中的主键)
incrby/decrby key num  增或减少指定的num
4.2、Redis Hash类型

特点:

  1. Hash用于存储结构化数据,可以看做是Map
  2. 典型的应用场景:对象的存储(可实现部分字段更新操作)

常用的指令:

redis 中 hash key 的命名规则:对象类型:id:属性
	hget user:1:info age  表示获取hash中key=age的值
    hset user:1:info age 23 设置hash 中age=23 (多次hset同一个值表示覆盖,成功会返回 0)
	hmset user:1:info height 178 birthday 1992-06-26  设置多个key()height birthday
    hmget user:1:info name age height birthday   获取多个值
    hgetall user:1:info 表示一次性获取所有的值
	hdel user:1:info birthday  删除 birthday  的 value值
	hlen  user:1:info   查看有多少个值
	hexists user:1:info name  查看key是否存在 存在返回 1
4.3、Redis List类型

特点:

  1. List列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾(右边)
  2. 一个列表最多可以包含 2的32次方 - 1 个元素(4294967295, 每个列表超过40亿个元素)。

典型的应用场景:时间轴的展示

常用的指令:

List 相关指令
rpush listkey c b a 右侧插入(每一次从右侧插入,就是从列表的尾部添加)
lpush listkey f e d 左侧插入,就是从列表的头部添加
rpop listkey 右侧元素弹出
lpop listkey 左侧元素弹出
llen listkey 获取从航都
lrange listkey 0 2 (表示从左边第一个元素取到第2个元素)
lrange listkey 1 -1 获取子集 从第二个元素开始 都末端所有元素
4.3、Redis Set类型

特点:

  1. Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
  2. Redis 中集合是通过哈希表实现的,所以添加,删除,查找的速度极快。
  3. 集合中最大的成员数为 2的32次方 - 1 (4294967295,每个集合可存储40多亿个成员)。

典型的应用场景:微关系中,共同关注的人sinter ;如随机弹出srandmember(可以设计奖池),抽奖活动(spop)

常用的指令:

sadd key element 添加集合元素
srem key 移除集合中的指定元素
scard user:1:follow 计算集合数量
smembers user:1:follow 获取所有集合元素 不推荐使用
srandmember user:1:follow 3 随机挑选3个参数
spop user:1:follow 随机弹出元素
sdiff set1 set2  差集
	sdiff user:1:follow user:2:follow
sinter set1 set2  交集
    sinter user:1:follow user:2:follow
sunion set1 set2 并集
    sunion user:1:follow user:2:follow
4.3、Redis Zset类型

特点:是String类型的有序集合,集合成员是唯一的,不能出现重复的数据。

典型的应用场景:排行榜

常用的指令:

zadd key score elements 添加集合中的元素
zadd player:rank 1000 ronaldo 900 messi 800 cronaldo 600 kaka
zrem key element 移除
zscore key element 获取数据
zscore player:rank kaka  (元素排名下标是从0开始)
zcard key 元素总数
zrank key 元素总数
zrange key scope withscores 获得排序索引数据
zrange player:rank 0 -1 withscores
zcount key scope 获得排序数据总量
       zcount player:rank 700 901
zrangebyscore key 获得按分数排序元素
       zrangebyscore player:rank 700 901 withscores
五、Redis客户端和使用
5.1、Redis 客户端

windows 环境下 有 RDM 0.9.3版本是最新免费版本,类似数据库(navicate)可视化管理工具文末会提供下载!

java 下客户端 Jedis:

  • Jedis是Java语言开发的Redis客户端工具包,用于Java语言与Redis数据进行交互.
  • Jedis只是对Redis命令的封装,掌握Redis命令便可轻易上手Jedis
  • Jedis遵循RESP协议规范开发,具有良好的通用性与可读性
  • jedis 直连: 简单粗暴,适用于少量连接的场景
  • jedis线程不安全,存在连接泄露的可能

Jedis连接池(JedisPool): 对象预先生成,降低开销,便于连接资源进行监管和控制;使用麻烦,参数较多,规划不合理容易出现问题

5.2、Redis的使用

Jedis直连的使用:

  1. pom.xml文件中添加依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xmlvhy</groupId>
    <artifactId>jedis</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
  1. 单元测试类编写:
package com.xmlvhy;

import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.List;
import java.util.Map;

/**
 * @ClassName JedisTest
 * @Description TODO
 * @Author 小莫
 * @Date 2019/04/17 21:57
 * @Version 1.0
 **/
public class JedisTest {

    @Test
    public void testJedis() throws Exception{
        //创建一个redis 通道
        Jedis jedis = new Jedis("127.0.0.1",6379, 1000);

        try {
            //设置登录密码密码
            //jedis.auth("123456");
            //选择第4个数据库,数据库下标从 0  开始
            jedis.select(3);
            jedis.flushDB();//清空第四个数据库
            //jedis.xxx方法名就是命令
            jedis.set("hello","world");
            System.out.println(jedis.get("hello"));
            jedis.mset(new String[]{"a","1","b","2","c","3"});
            List<String> strs = jedis.mget(new String[]{"a", "b", "c"});
            System.out.println(strs);
            System.out.println(jedis.incr("c"));
            Long b = jedis.del("b");
            System.out.println(b);
        } catch (Exception e) {
            throw e;
        }finally {
            //释放连接
            jedis.close();
        }
    }

    @Test
    public void testHash(){
        //创建一个redis 通道
        Jedis jedis = new Jedis("127.0.0.1",6379, 1000);

        try {
            //设置登录密码密码
            //jedis.auth("123456");
            //选择第4个数据库,数据库下标从 0  开始
            jedis.select(3);
            jedis.flushDB();//清空第四个数据库
            //jedis.xxx方法名就是命令
            jedis.hset("user:1:info","name","xiaomo");
            jedis.hset("user:1:info","age","27");
            jedis.hset("user:1:info","sex","男");
            Map<String, String> all = jedis.hgetAll("user:1:info");
            System.out.println(all);
            System.out.println(jedis.hget("user:1:info","name"));
        } catch (Exception e) {
            throw e;
        }finally {
            //释放连接
            jedis.close();
        }
    }
}

Jedis使用连接池jedisPool:

package com.xmlvhy;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @ClassName JedisTestPool
 * @Description TODO
 * @Author 小莫
 * @Date 2019/04/18 9:30
 * @Version 1.0
 **/
public class JedisTestPool {

    @Test
    public void testJedisPool(){
        //创建一个jedis 池
        GenericObjectPoolConfig config = new JedisPoolConfig();
        //设置连接池中最多允许放100个jedis对象
        config.setMaxTotal(100);
        //设置连接池中允许的最大连接数
        //maxIdle 设置一般跟 maxTotal一致即可
        config.setMaxIdle(50);
        //设置连接池中允许的最小连接数
        config.setMinIdle(10);
        //设置借出连接的时候是否测试有效性 推荐false 提升效率
        config.setTestOnBorrow(false);
        //设置归还连接的时候是否测试有效性 推荐false 提升效率
        config.setTestOnReturn(false);
        //创建时候测试有效性,设置true(注意这个属性设置true的话,如果不是本地连接则会报错,是由于自我保护引起的问题)
        config.setTestOnCreate(true);
        //当连接池内jedis无可用资源的时候,是否等待资源
        config.setBlockWhenExhausted(true);
        //没有获取资源时最长等待时间设置 1 秒,1秒后还没有的话就报错
        config.setMaxWaitMillis(1000);

        JedisPool pool = new JedisPool(config,"127.0.0.1",6379);
        Jedis jedis = null;
        try {
            //从连接池中获取(borrow)一个jedis对象
            jedis = pool.getResource();
            //jedis.auth("123456");
            jedis.set("abc","bb");
            String abc = jedis.get("abc");
            System.out.println(abc);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (jedis != null) {
                //在使用连接池的时候,使用close方法不是关闭。而是归还到连接池中
                jedis.close();
            }
        }

    }
}

Spring 环境下使用JedisPool:

  1. pom.xml中添加相关依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xmlvhy</groupId>
    <artifactId>spring-jedis</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!--引用 spring-data-redis-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
		<!--单元测试-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
		<!--日志框架-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>1.7.2</version>
        </dependency>
		<!--jackson用于序列化和反序列化-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>
		<!--封装了javaBean相关的操作-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>
    </dependencies>
</project>
  1. applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--连接池配置类-->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="100"/>
        <property name="maxIdle" value="100"/>
        <property name="minIdle" value="10"/>
        <property name="maxWaitMillis" value="1000"/>
        <property name="blockWhenExhausted" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="testOnCreate" value="false"/>
    </bean>

    <!--sentinel配置核心-->
    <bean id="sentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
        <property name="master">
            <bean class="org.springframework.data.redis.connection.RedisNode">
                <property name="name" value="mymaster"/>
            </bean>
        </property>
        <property name="sentinels">
            <set>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="127.0.0.1"/>
                    <constructor-arg name="port" value="26379"/>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="127.0.0.1"/>
                    <constructor-arg name="port" value="26380"/>
                </bean>
                <bean class="org.springframework.data.redis.connection.RedisNode">
                    <constructor-arg name="host" value="127.0.0.1"/>
                    <constructor-arg name="port" value="26381"/>
                </bean>
            </set>
        </property>
    </bean>

    <!--配置jedis
        usePool = false 表示jedis直连,true表示使用连接池
    -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:usePool="true" p:hostName="127.0.0.1" p:port="6666" p:database="2" p:poolConfig-ref="poolConfig"/>

    <!--<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:usePool="true" p:database="2" p:poolConfig-ref="poolConfig">-->
        <!--TODO:windows环境下执行提示失败-->
        <!--&lt;!&ndash;整合sentinel配置,通知jedisconnectionFactory 使用sentinel创建jedis连接&ndash;&gt;-->
        <!--<constructor-arg index="0" ref="sentinelConfiguration"/>-->
    <!--</bean>-->

    <!--jedis 核心操作类 redisTemplate 本质上就是jedis封装,在jedis基础上进行了大幅度的简化,并且
    对连接池友好,允许自动回收连接
    在JedisPool 中如果没有调用 close方法可能会出现连接泄露的问题,但在spring-date-redis中则不会
    -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connectionFactory-ref="jedisConnectionFactory">
        <property name="keySerializer">
            <!--设置 StringRedisSerializer 字符串原本序列化进行保存-->
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <!--设置 值序列化采用原文字符串的序列化方式-->
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
        <property name="hashKeySerializer">
            <!--设置 StringRedisSerializer 字符串原本序列化进行保存-->
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="hashValueSerializer">
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
        </property>
    </bean>
</beans>
  1. 单元测试
package com.xmlvhy.springredis;

import com.xmlvhy.entity.User;
import org.apache.commons.beanutils.BeanUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;

/**
 * @ClassName SpringRedisTests
 * @Description TODO
 * @Author 小莫
 * @Date 2019/04/18 10:30
 * @Version 1.0
 **/
@RunWith(SpringJUnit4ClassRunner.class) //启动时候初始化spring ioc 容器
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringRedisTests {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void test() {
        //对string 字符串操作的类
        //默认情况下Spring-data-redis 会采用jdk序列化的方式将所有key value 进行二次序列化
        //这样会导致可读性差,通常我们需要字符串或json的方式来保存对象
        redisTemplate.opsForValue().set("a", "b1111");
        String a = (String) redisTemplate.opsForValue().get("a");
        System.out.println(a);
    }

    //string 序列化
    @Test
    public void testObjectSerializer() {
        User user = new User("u1", "test1");
        User user1 = new User("u2", "test2");

        redisTemplate.opsForValue().set("user:u1", user);
        redisTemplate.opsForValue().set("user:u2", user1);

        //spring data redis 底层提供jackson 进行序列化
    }

    //string 反序列化
    @Test
    public void testObjectDeserializer() {
        //这里注意需要实体类中有一个空的默认构造方法
        User u1 = (User) redisTemplate.opsForValue().get("user:u1");
        System.out.println(u1);
    }

    //hash 序列化
    @Test
    public void testHashSerializer() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        User user = new User("u3", "test3");
        Map<String, String> map = BeanUtils.describe(user);
        //将javaBean 转化为 map
        redisTemplate.opsForHash().putAll("user:m1", map);
    }

    //hash 反序列化
    @Test
    public void testHashDeserializer() throws InvocationTargetException, IllegalAccessException {
        //拿到所有值
        Map map = redisTemplate.opsForHash().entries("user:m1");
        User user = new User();
        BeanUtils.populate(user, map);
        System.out.println(user.getUsername());
    }

    @Test
    public void testList() {
        for (int i = 0; i < 10; i++) {
            User u = new User("u" + i, "p" + i);
            redisTemplate.opsForList().rightPush("VipUserRank", u);
        }
        //下标是从0开始的
        List<Object> list = redisTemplate.opsForList().range("VipUserRank", 1l, 5l);
        System.out.println(list);
    }

    @Test
    //数据库层面的命令要使用 execute 接口回调实现
    public void testFlashdb() {
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                redisConnection.flushDb();
                return null;
            }
        });
    }
}
5.3、SpringBoot和Spring Cache

Redis在我们程序中最重要的应用便是缓存,利用内存的高吞吐解决数据查询慢的问题; Spring Cache是Spring生态的一员,用于对主流缓存组件进行一致性集成.通过暴露统一的接口,让我们轻松的使用并进行组件之间的切换。

声明式缓存

  • 声明式缓存通俗来说是采用注解的形式对当前应用的”非侵入式”扩展
  • 声明式缓存是Spring Cache的默认支持. 底层采用Spring AOP技术实现

直接上代码:

  1. pom.xml中添加相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xmllvhy</groupId>
    <artifactId>spring-cache</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cache</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  1. 主程序入口,SpringCacheApplication 添加 @EnableCaching 注解,表示启用spring cache缓存

  2. application.properties 中配置jedisPool

spring.redis.database=2
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-idle=100
spring.redis.jedis.pool.min-idle=10
spring.redis.jedis.pool.max-wait=1000ms
  1. RedisCacheConfig 序列化相关配置
package com.xmllvhy.spring.cache.config;

import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.stereotype.Component;

/**
 * @ClassName RedisCacheConfig
 * @Description TODO
 * @Author 小莫
 * @Date 2019/04/18 14:15
 * @Version 1.0
 **/
@Component
public class RedisCacheConfig {
    //@Bean
    //public RedisCacheConfiguration redisCacheConfiguration() {
    //    //加载redis缓存的默认配置
    //    RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
    //    //使用jackson 进行json 序列化,jdk默认序列化是二进制的方式,那样出现乱码不易查看
    //    configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
    //    return configuration;
    //}

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {

        //初始化一个RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);

        //以下这种方式可以序列化,但是如果对集合或者javabean反序列化时候会出错
        //Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);

        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());

        RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    }
}
  1. service类,集成cache相关业务逻辑
package com.xmllvhy.spring.cache.service;

import com.xmllvhy.spring.cache.entity.Emp;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @ClassName EmpService
 * @Description TODO
 * @Author 小莫
 * @Date 2019/04/18 13:58
 * @Version 1.0
 **/
@Service
public class EmpService {
    //对于默认情况下,redis对象的序列化使用的是 jdk序列化,必须要求实体类实现seriliziable 接口
    //cacheable 会将方法的返回值序列化后存储到redis,key就是参数执行的字符串
    //cacheable 的用途就是在执行方法前检查对应的key是否存在,存在则直接从redis中取出来不执行方法中的代码
    //没有对应的key则执行方法代码,并将返回的值序列化保存在缓存中
    //condition 代表条件成立的时候才执行缓存的数据
    @Cacheable(value = "emp",key = "#empno"/*,condition = "#empno == 1000"*/)
    public Emp findByEmpNo(Integer empno){
        System.out.println("执行了findByEmpNo方法:empno" + empno);
        return new Emp(empno,"xiaomo",new Date(),1000f,"研发部");
    }


    //冒号分割
    @Cacheable(value = "emp:rank:salary")
    public List<Emp> getEmpRank(){
        System.out.println("第一次获取数据");
        List list = new ArrayList();
        for(int i =0; i<10;i++){
            list.add(new Emp(i,"emp"+i,new Date(),500+i*100f,"SALES"));
        }
        return list;
    }

    //CachePut 中的代码都会被执行
    //CachePut 作用是不管redis是否存在key,都把返回的数据进行保存(强制更新)
    @CachePut(value = "emp",key = "#emp.empno")
    public Emp create(Emp emp){
        System.out.println("正在创建"+emp.getEmpno()+ " 的员工信息");
        return emp;
    }
    //CachePut 作用是不管redis是否存在key,都把返回的数据进行保存(强制更新)
    //update 也是 cache put 有则更新无则创建
    @CachePut(value = "emp",key = "#emp.empno")
    public Emp update(Emp emp){
        System.out.println("正在更新"+emp.getEmpno()+ " 的员工信息");
        return emp;
    }

    //CacheEvict 从缓存中删除指定key的数据
    @CacheEvict(value = "emp",key = "#empno")
    public void delete(Integer empno){
        System.out.println("正在删除"+ empno + " 的员工信息");
    }
}
  1. 单元测试类
package com.xmllvhy.spring.cache;

import com.xmllvhy.spring.cache.entity.Emp;
import com.xmllvhy.spring.cache.service.EmpService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest //spring boot 中,springboot test 的作用就是在junit启动的时候自动初始化springboot 的ioc容器
public class SpringCacheApplicationTests {

    @Autowired
    private EmpService empService;
    @Test
    public void contextLoads() {

    }
    @Test
    public void findByEmpno(){
        empService.findByEmpNo(1000);
        empService.findByEmpNo(1000);
        empService.findByEmpNo(1000);
        empService.findByEmpNo(1000);
        Emp emp = empService.findByEmpNo(1000);
        System.out.println(emp.getName());
        System.out.println(emp.getBirthday());
    }
    @Test
    public void testEmpRank(){
        List<Emp> list = empService.getEmpRank();
        String name = list.get(list.size() - 1).getName();
        System.out.println(name);
    }
    @Test
    public void testCreate(){
        empService.create(new Emp(1001,"xiaomo",new Date(),1111f,"sales"));
    }
    @Test
    public void testUpdate(){
        empService.create(new Emp(1001,"xiaomo-update",new Date(),1111f,"sales"));
    }
    @Test
    public void testDelete(){
        empService.delete(1000);
    }
}
六、提供Redis一个工具类(jedisPool)
  1. 初始化配置 redisTemplate 和 cacheManager
package com.zhly.sso.auth.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @ClassName RedisCacheConfig
 * @Description TODO
 * @Author 小莫
 * @Date 2019/04/18 14:15
 * @Version 1.0
 **/
@Configuration
@EnableCaching
public class CacheRedisConfig {

    /**
     *功能描述: 配置 redis 缓存管理
     * @Author 小莫
     * @Date 17:44 2019/04/20
     * @Param [connectionFactory]
     * @return org.springframework.cache.CacheManager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        //初始化一个RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
        //以下这种方式可以序列化,但是如果对集合或者javabean反序列化时候会出错
        //Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);

        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());

        RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    }

    /**
     *功能描述: 配置 redis 操作类 redisTemplate
     * @Author 小莫
     * @Date 17:45 2019/04/20
     * @Param [factory]
     * @return org.springframework.data.redis.core.RedisTemplate<java.lang.String,java.lang.Object>
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        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);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}
  1. 封装 redisTemplate 对五种数据类型的操作(RedisUtil.java)
package com.zhly.sso.auth.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName RedisUtil
 * @Description TODO redis 工具类
 * 这里直接通过注入 redisTemplate 来封装redis的相关操作
 * @Author 小莫
 * @Date 2019/04/20 17:46
 * @Version 1.0
 **/
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @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;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

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

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }
    // ============================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  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    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 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    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;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    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表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    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;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

以上就是我学习 Redis 的一些总结!

参考学习

来自老齐的IT加油站课程学习
	官网:http://www.itlaoqi.com/
https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html

本篇文章涉及的 源码下载

类似文章

  1. SpringCloud入门系列之服务链路追踪Sleuth&Zipkin
  2. SpringCloud入门系列之微服务之间的通信
  3. SpringCloud入门系列之API网关
  4. SpringCloud入门系列之配置中心
  5. SpringCloud入门系列之Eureka注册中心

评论区

| 0 评论

还没有评论,快来抢沙发吧!