Redis入门

一.初识Redis

1.什么是Redis与Redis配置

Redis是一种键值型NoSql数据库。有两个关键词:
*键值型
*NoSql
特点:C语言开发        基于内存        开源        键值对(key-value)结构数据库
高性能,官方提供的数据是可以达到100000+的QPS(每秒内查询次数),简单说就是查询快。
但因为是基于内存的,内存又有限,所以一般用来存热点数据(短时间或者经常访问)。

【1】键值型

指Redis中存储的数据都是以key,value堆的形式存储,而value的形式多种多样,可以是字符串,数值,甚至是json:

【2】NoSql


NoSql可以翻译做Not Only Sql(不仅仅是SQL),或者是No Sql(非Sql的)数据库。称被之为非关系型数据库。NoSql数据库并不是要取代非关系型数据库,而是关系型数据库的补充。

(1)结构化与非结构化


传统关系型数据库是结构化数据,每一张表都有严格的约束信息:字段名,字段数据类型,字段约束等等信息,插入的数据必须遵循这些约束:

 而NoSql数据库格式没有严格要求,可以是键值型,文档型,甚至可以是图格型。

(2)关联与非关联


传统数据库表与表之间往往存在关联,例如外键。而非关系型数据库不存在关联关系,要维护关系只能靠代码业务逻辑和数据之间的耦合。

(3)查询

非关系型数据库语法差异大。

(4)事务

非关系型数据库往往不支持事务,或者不能严格保证ACID的特性。

应用场景:缓存,任务队列,消息队列,分布式锁等。

2.Redis下载与安装


 

3.Redis服务启动与停止

【1】启动服务

(1)Linux服务端:Linux中使用redis-server命令启动redis服务,默认端口号为6379。(在所有位置都能使用redis-server)
ctrl+c停止Redis服务;或者ps -ef | grep 'redis'查看对应的进程     kill -9 进程号来停止Redis服务。

相关操作:。

(2)Linux客户端:定位到redis文件夹,然后输入redis-cli就可以启动客户端

(1)Windows服务端:在Windows上点击redis-server.exe,就可以启动服务端,
(2)Windows客户端:然后再点击redis-cli.exe就可以启用客户端输入命令。

【2】相关操作

通过配置将redis-server挂后台运行
通过配置实现开机自启
修改配置文件让客户端需要密码认证
修改配置文件让服务端可以被远程客户端连接
修改完配置文件后怎么重启redis
怎么用控制台让Windows的redis-cli远程连接linux的redis-server

图形化界面
GitHub上的大神编写了Redis的图形化桌面客户端,地https://github.com/uglide/RedisDesktopManager

不过该仓库提供的是RedisDesktopManager的源码,并未提供windows安装包。

在下面这个仓库可以找到安装包:https://github.com/lework/RedisDesktopManager-Windows/releases

二.Redis数据类型

1.Redis的类型有哪些

Redis存储的是key-value数据类型,其中Key是字符串类型,value包含了很多不同的类型。

不同类型的命令称为一个group,我们也可以通过help命令来查看各种不同group的命令。
 

接下来,我们就学习常见的五种基本数据类型的相关命令:

2.通用命令

通用指令是部分数据类型的,都可以使用的指令,常见的有:

- KEYS:查看符合模板的所有key
- DEL:删除一个指定的key
- EXISTS:判断key是否存在
- EXPIRE:给一个key设置有效期,有效期到期时该key会被自动删除
- TTL:查看一个KEY的剩余有效期

通过help [command] 可以查看一个命令的具体用法。

3.Key的命名

Redis没有类似MySQL中的Table的概念,我们该如何区分不同类型的key呢?

例如,需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1,此时如果使用id作为key,那就会冲突了,该怎么办?

我们可以通过给key添加前缀加以区分,不过这个前缀不是随便加的,有一定的规范:

Redis的key允许有多个单词形成层级结构,多个单词之间用':'隔开,格式如下:

[业务名称]:[数据名]:[id]

这个格式并非固定,也可以根据自己的需求来删除或添加词条。这样以来,我们就可以把不同类型的数据区分开了。从而避免了key的冲突问题。

例如:

我项目中key的定义:

    public static final String USER_INFO_KEY="user:info:";

    public static final Long USER_INFO_TTL=30L;

    public static final String USER_BLACKLIST_KEY="user:blacklist:";

    public static final Long USER_BLACKLIST_TTL = 30L;

    public static final String EMAIL_VERIFYCODE_KEY="email:verifycode:";

    public static final Long EMAIL_VERIFYCODE_TTL=120L;

    public static final String MUSIC_RANKLIST_KEY="music:rankList:";

并且,在Redis的桌面客户端中,还会以相同前缀作为层级结构,让数据看起来层次分明,关系清晰。

key的长度不超过44字节:key是string类型,底层编码包含int、embstr和raw三种。embstr在小于44字节使用,采用连续内存空间,内存占用更小。当字节数大于44字节时,会转为raw模式存储,在raw模式下,内存空间不是连续的,而是采用一个指针指向了另外一段内存空间,在这段空间里存储SDS内容,这样空间不连续,访问的时候性能也就会收到影响,还有可能产生内存碎片

3.类型详解

1.String类型--主要用来存储整数,浮点数和字符串这些普通数据类型以及对象

String类型,也就是字符串类型,是Redis中最简单的存储类型。

其value是字符串,不过根据字符串的格式不同,又可以分为3类:
string:普通字符串
int:整数类型,可以做自增、自减操作
float:浮点类型,可以做自增、自减操作

不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m.

如果Value是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储。

2.Hash类型--主要用来存储hash表和对象

Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。

String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便。

Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD:

简单说存储的值是键值对,键值对套键值对,哈哈哈

问题:存储对象什么时候用String,什么时候用Hash

String一般用于缓存简单数据和对象,而Hash一般用于缓存Hash表和对象。

1.如果你经常需要更新对象的部分字段而不是整个对象,hash类型可以避免读取和写入整个对象的开销。例如,用户的在线状态、最后登录时间等信息需要独立更新。

2.对于包含多个字段的小对象,使用hash类型可以比使用多个string类型更节省内存。Redis对小的hash对象进行了优化,使其能够更高效地存储。

简单说在存储对象的内存方面一个String类型优于一个Hash类型优于多个字段分开存储的String类型

示例:

假设你有一个用户对象,包含用户名、年龄和地址三个字段:

使用String类型--这种方式适用于你不需要频繁更新单个字段的情况

SET user:1001 '{"username": "alice", "age": 30, "address": "Wonderland"}'

使用Hash类型:这种方式适用于你需要独立操作各个字段的情况

HMSET user:1001 username "alice" age 30 address "Wonderland"

比如只更新用户的地址:

HSET user:1001 address "New Wonderland"

总结来说,选择使用string类型还是hash类型主要取决于你的数据结构和操作需求。对于简单值和需要整体操作的数据,使用string类型更合适;对于多字段对象和需要部分更新的数据,使用hash类型更高效。

3.List类型--常用来存储一个有序数据

Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。

特征也与LinkedList类似:
- 有序
- 元素可以重复
- 插入和删除快
- 查询速度一般

常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。

4.Set类型--集合

Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:
-无序
-元素不可重复
-查找快
-支持交集、并集、差集等功能

5.SortedSet类型--不可重复,便于排序

Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。

SortedSet具备下列特性:

- 可排序
- 元素不重复
- 查询速度快

因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。

更多命令详情可以到这个网站学习:redis命令手册

三.在Java中操作Reidis

1.介绍

Redis的Java客户端很多,官方推荐的有三种:Jedis,Lettuce,Redisson

Spring对Redis客户端进行了整合,提供了Spring Data Redis,在Spring Boot项目中还提供了对应的Start,即spring-boot-start-data-redis。 

2.Jedis(只是简单介绍一下)

导入依赖与一个小demo

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.8.0</version>
</dependency>
import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.Set;

public class jedisTest {
    @Test
    public void testRedis(){
        //1.获取连接
        Jedis jedis=new Jedis("localhost",6379);
        //2.执行具体的操作
        //存放
        jedis.set("username","xiaoming");
        //获取
        String value=jedis.get("username");
        System.out.println(value);
        //删除
        //jedis.del("username");
        jedis.hset("myhash","addr","bj");
        String hValue = jedis.hget( "myhash", "addr");
        System.out.println(hValue);

        Set<String> keys=jedis.keys("*");
        for(String key:keys){
            System.out.println(key);
        }
        //3.关闭连接
        jedis.close();
    }
}

3.Spring Data Redis入门(底层是对jedis做的封装)

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis。

  • 提供了对不同Redis客户端的整合(Lettuce和Jedis)

  • 提供了RedisTemplate统一API来操作Redis

  • 支持Redis的发布订阅模型

  • 支持Redis哨兵和Redis集群

  • 支持基于Lettuce的响应式编程

  • 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化

  • 支持基于Redis的JDKCollection实现

SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:

【1】导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

【2】Spring Data Redis的使用非常简单,只需要用@Autowired或@Resource注入RedisTemplate就可以调用RedisTemplate里面的方法操作redis了。

例如下面在redis中添加了键值对city:beijing

@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringDataRedisTest {
    @Resource
    private RedisTemplate redisTemplate;
    @Test
    public void testString(){
        redisTemplate.opsForValue().set("city","beijing");
    }
}

【3】配置

spring.redis.host=localhost
spring.redis.port=6379
#spring.redis.password=123456
#redis为我们提供16个数据库(具体数量可以在配置文件下载),默认操作0号数据库
spring.redis.database=0 
#Redis连接池配置
##最大连接数
#spring.redis.jedis.pool.max-active
##连接池最小空塞等待时间
#spring.redis.jedis.pool.max-wait
##连接池中的最大空闲时间
#spring.redis.jedis.pool.max-idle
##连接池中的最小空闲时间
#spring.redis.jedis.pool.min-idle

注:连接池配置

Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。

【4】添加配置类,替换JDK序列化为JSON序列化

前面的知识--序列化:将对象转化为字符串

打开redis客户端执行key *命令,我们会发现key不是city。因为在写入前会把Object序列化为字节形式,默认是采用JDK序列化。

类似于下面这样:

缺点:
- 可读性差
- 内存占用较大

我们可以自定义RedisTemplate的序列化方式,代码如下:

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        // 创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置连接工厂
        template.setConnectionFactory(connectionFactory);
        // 创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = 
            							new GenericJackson2JsonRedisSerializer();
        // 设置Key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置Value的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        // 返回
        return template;
    }
}

这里采用了JSON序列化来代替默认的JDK序列化方式。最终结果类似下图:

整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。

【5】StringRedisTemplate--无序列化,自己手动序列化

为了节省内存空间,我们可以不使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。

因为存入和读取时的序列化及反序列化都是我们自己实现的,SpringDataRedis就不会将class信息写入Redis了。

这种用法比较普遍,因此SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。

省去了我们自定义RedisTemplate的序列化方式的步骤,而是直接使用【一般redis存储字符串类型都会使用StringRedisTemplate】

@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON序列化工具
private static final ObjectMapper mapper = new ObjectMapper();

@Test
void testSaveUser() throws JsonProcessingException {
    // 创建对象
    User user = new User("虎哥", 21);
    // 手动序列化
    String json = mapper.writeValueAsString(user);
    // 写入数据
    stringRedisTemplate.opsForValue().set("user:200", json);

    // 获取数据
    String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
    // 手动反序列化
    User user1 = mapper.readValue(jsonUser, User.class);
    System.out.println("user1 = " + user1);
}

【6】我们还会将RedisTemplate的方法进一步封装为工具类

这是我在网上找的一个工具类:

package com.flyingpig.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public long deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 删除Hash中的数据
     * 
     * @param key
     * @param hkey
     */
    public void delCacheMapValue(final String key, final String hkey)
    {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key, hkey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}

然后的话我们在别的类中

@Autowired
private RedisCache redisCache;

就可以使用这个工具类了。例如我们可以在登录的使用将用户信息存入redis中:

//如果认证通过了,使用userid生成一个jwt jwt存入ResponseResult返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userid);
//把完整的用户信息存入redis  userid作为key
redisCache.setCacheObject("login:"+userid,loginUser);

4.Spring Cache框架

Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。

Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:
【1】EHCache
【2】Caffeine
【3】Redis(常用)

起步依赖
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
常用注解

在SpringCache中提供了很多缓存操作的注解,常见的是以下的几个:

注解说明
@EnableCaching开启缓存注解功能,通常加在启动类上
@Cacheable在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中
@CachePut将方法的返回值放到缓存中
@CacheEvict将一条或多条数据从缓存中删除
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值