自学内容网 自学内容网

Redis基础

基础篇

认识NoSQL

键值数据库

NoSql数据库

SQLNoSQL
结构化 约束非结构化数据结构
关联的无关联的数据关联
SQL查询非SQL查询方式
ACID特性BASE事务特性
磁盘存储内存存储存储方式
垂直
垂直扩展性意味着通过增加单个服务器的计算能力来应对增加的负载。通常涉及到升级硬件,比如增加处理器的数量、提高内存容量、增加存储空间等。也就是说,SQL数据库在扩展时更倾向于“增强一台服务器”的性能,以便处理更多的数据或更高的负载。
水平
水平扩展性指的是通过增加更多的服务器节点来处理更多的数据和请求。NoSQL数据库设计时考虑了分布式架构,允许通过将数据分片(sharding)分布到多个服务器上,实现数据库的扩展。这种扩展方式可以使系统通过增加更多的服务器来承载更大的工作负载。
扩展性
1 数据结构固定
2 相关业务对数据安全性一致性要求较高
1 数据结构不固定
2 对一致性、安全性要求不高
3 对性能要求
使用场景

非结构化:

  • 键值类型Redis
  • 文档类型MongoDB
  • 列类型HBase
  • Graph类型Neo4j

认识Redis

Remote Dictionary Server 远程词典服务器,基于内存的键值型NoSQL数据库

特征:

  • 键值型,value支持多种不同数据结构
  • 单线程,每个命令具备原子性
  • 低延迟,速度快
    • 基于内存
    • IO多路复用
    • 良好的编码
  • 支持数据持久化
    • 定期将数据从内存持久化到磁盘
  • 支持主从集群、分片集群
  • 多语言客户端

数据结构

五种基本类型

  1. String
  2. Hash
  3. List
  4. Set
  5. SortedSet

三种特殊类型

  1. GEO
  2. BitMap
  3. HyperLog

通用命令

help command,例如help keys

  • KEYS:查看符合模板的所有key
    • KEYS *
    • KEYS a*
    • 模糊查询,效率慢加上redis还是单节点,容易阻塞,不建议生产环境使用
  • DEL:删除指定key
  • MSET:批量添加key value
  • EXISTS:查看key是否存在
  • EXPIRE:给key设置有效期,有效期到期key会被自动删除
    • redis是内存存储,定时清除防止越存越多,比如验证码5分钟
  • TTL:查看一个key剩余有效期
    • -1永久,-2删除

String

value是字符串,但可以根据字符串格式分为3类:

  1. string
  2. int 可以做自增自减
  3. float 可以做自增自减

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

命令

  • SET
    • 添加或修改键值对
  • GET
    • 根据key获取value
  • MSET
    • 批量添加键值对
  • MGET
    • 根据key获取value
  • INCR
    • 整形key自增1
  • INCRBY
    • 整形key自增并指定步长
    • incrby key increment
  • INCRBYFLOAT
    • 浮点类型key自增并指定步长
    • incrbyfloat key increment
  • SETNX
    • 添加键值对,前提是key不存在才执行
  • SETEX
    • 添加键值对并指定有效期
    • setex key seconds value

Key的层级格式

redis没有mysql的table这种,如果冲突了怎么办,比如客户和商品id都是1

key的结构:

允许多个单词形成层级结构,之间用:隔开

  • 项目名:业务名:类型:id

例如:

set test:user:1 ‘{“id”:1, “name”:“Jack”, “age”: 21}’

set test:product:1 ‘{“id”:1, “name”:“Jack”, “age”: 21}’

Hash

hash类型,也叫散列,其value是一个无序字典,类似于java中的hashmap结构

String结构是将对象序列化为json字符串后存储,修改某个字段很不方便

hash结构将对象的每个字段独立存储,可以针对单个字段做curd

也就是

KEY VALUE

KEY VALUE(field value)

命令:

  • HSET key field value
  • HGET key field
  • HMSET
  • HMGET
  • HGETALL
  • HKEYS
  • HVALS
  • HINCRBY
  • HSETNX

List

list类型与java的linkedlist类似,可以看作一个双向链表结构,既可以正向检索也可以反向检索

特征也类似:

  • 有序
  • 元素可以重复
  • 插入和删除快
  • 查询速度一般

使用场景:有序数据,如朋友圈点赞

命令:

  • LPUSH key element
  • LPOP key
  • RPUSH key element
  • RPOP key
  • LRANGE key star end
    • LRANGE users 1 2
    • 取第2和第3个,也就是1-2
  • BLPOP BRPOP
    • 和POP类似,没有元素时候要设置等待时间,不是直接返回nil,单位秒
    • BLPOP users2 5
    • 是一个阻塞式的pop。如果列表为空,它会阻塞(等待)直到列表中有新的元素被插入,或者直到达到指定的超时时间。
    • 0表示无限期等待直到列表有元素

如何用List模拟栈

  • 入口和出口同一边
  • LPUSH和LPOP这种

如何用List模拟队列

  • 入口和出口不同边
  • LPUSH和RPOP这种

如何用List模拟阻塞队列

  • 入口和出口不同边
  • 出队时用BLPOP这种

Set

和java中的hashset类似,可以看作value为null 的hashmap。因为也是一个hash表,和hashset特征类似:

  • 无序
  • 元素不可重复
  • 查找快
  • 支持交集、并集、差集等

命令:

单个集合

  • SADD key member
    • 向set添加一个或多个元素
  • SREM key member
    • 移除set指定元素
  • SCARD key
    • 返回set元素个数
  • SISMEMBER key member
    • 判断一个元素是否存在于set中
  • SMEMBERS
    • 获取set中的所有元素

多个集合

  • SINTER key1 key2
    • key1和key2 的交集
  • SDIFF key1 key2
    • 差集
  • SUNION key1 key2
    • 并集

SortedSet

可排序的set集合,与java中的treeset类似但底层数据结构差别很大。

sortedset每一个元素带有一个score属性,可以基于属性对元素排序,底层的实现是一个跳表SkipList加hash表

特性:

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

使用场景:由于可排序特性,常用来实现排行榜

命令:

  • ZADD key score member
    • 添加一个或多个元素,如果已经存在则更新score值
  • ZREM key member
    • 删除元素
  • ZSCORE key member
    • 获取指定元素score值
  • ZRANK key member
    • 获取指定元素的排名
  • ZCARD key
    • 获取元素个数
  • ZCOUNT key min max
    • 统计score值在给定范围的元素个数
  • ZINCRBY key increment member
    • 统计指定元素自增,步长指定increment
  • ZRANGE key min max
    • 按照score排序后,获取指定排名范围的元素
  • ZRANGEBYSCORE key min max
    • 按照score排序后,获取指定score范围内的元素
  • ZDIFF ZINTER ZUNION
    • 求差集、交集、并集

升序排名,降序则ZREV

Hash HashMap HashSet

1. HashMap 的数据结构

HashMap 是基于哈希表实现的,底层使用数组加链表或红黑树来解决哈希冲突。假设我们有一个 HashMap<String, Integer>,添加了以下键值对:

HashMap<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);

内部的数据结构可以通过一个简单的 Markdown 表格展示:

HashMap
┌───────────┬───────────────────────────┐
│ Bucket(0) │                           │
├───────────┼───────────────────────────┤
│ Bucket(1) │                           │
├───────────┼───────────────────────────┤
│ Bucket(2) │                           │
├───────────┼───────────────────────────┤
│ Bucket(3) │ [banana, 2]               │
├───────────┼───────────────────────────┤
│ Bucket(4) │                           │
├───────────┼───────────────────────────┤
│ Bucket(5) │                           │
├───────────┼───────────────────────────┤
│ Bucket(6) │ [apple, 1] -> [cherry, 3] │
├───────────┼───────────────────────────┤
│ Bucket(7) │                           │
└───────────┴───────────────────────────┘

在这个例子中:

  • applecherry 通过哈希函数计算出来的哈希值冲突,因此它们被放在了同一个桶(bucket 6)中,并通过链表解决冲突。
  • banana 没有冲突,被存放在 bucket 3。

2. HashSet 的数据结构

HashSet 是基于 HashMap 实现的,底层使用 HashMap 存储元素,只关心 key,而 value 是一个固定的常量 PRESENT。假设我们有一个 HashSet<String>,添加了以下元素:

HashSet<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("cherry");

它的内部结构可以表示为:

HashSet
┌───────────┬──────────────────────┐
│ Bucket(0) │                      │
├───────────┼──────────────────────┤
│ Bucket(1) │                      │
├───────────┼──────────────────────┤
│ Bucket(2) │                      │
├───────────┼──────────────────────┤
│ Bucket(3) │ [banana, PRESENT]    │
├───────────┼──────────────────────┤
│ Bucket(4) │                      │
├───────────┼──────────────────────┤
│ Bucket(5) │                      │
├───────────┼──────────────────────┤
│ Bucket(6) │ [apple, PRESENT] ->  │
│           │ [cherry, PRESENT]    │
├───────────┼──────────────────────┤
│ Bucket(7) │                      │
└───────────┴──────────────────────┘

这里可以看到 HashSet 内部实际上是使用 HashMap 来存储数据,每个元素对应于 HashMapkey,而 value 始终是一个 PRESENT 占位符。

3. 哈希表(Hash Table)数据结构

哈希表的基础结构与 HashMap 类似,主要是用哈希函数将键映射到一个桶数组中。桶中可以通过链表或其他结构解决哈希冲突。我们以 HashMap 为例来简化哈希表的结构:

Hash Table
┌───────────┬──────────────────────────┐
│ Bucket(0) │                          │
├───────────┼──────────────────────────┤
│ Bucket(1) │                          │
├───────────┼──────────────────────────┤
│ Bucket(2) │                          │
├───────────┼──────────────────────────┤
│ Bucket(3) │ [Key: banana, Value: 2]  │
├───────────┼──────────────────────────┤
│ Bucket(4) │                          │
├───────────┼──────────────────────────┤
│ Bucket(5) │                          │
├───────────┼──────────────────────────┤
│ Bucket(6) │ [Key: apple, Value: 1] ->│
│           │ [Key: cherry, Value: 3]  │
├───────────┼──────────────────────────┤
│ Bucket(7) │                          │
└───────────┴──────────────────────────┘
  • 哈希表 中每个桶存储键值对,处理冲突时可以使用链表或红黑树来存储多个冲突项。

Java客户端

  • Jedis
  • lettuce
  • Redisson
  • java-redis-client
  • vertx-redis-client

Spring Data Redis

整合Jedis和lettuce

Jedis

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.0</version>
</dependency>
package com.sugon.test;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
import redis.clients.jedis.Jedis;
import org.junit.jupiter.api.BeforeEach;

public class JedisTest {
    //1.建立连接
    private Jedis jedis;

    @BeforeEach
    void setUp() {

        jedis = new Jedis("192.168.136.130", 6379);
        jedis.auth("123321");
        jedis.select(0);

    }

    //2.测试String
    @Test
    void testString(){
        String result = jedis.set("name","zhangsan");
        System.out.println(result);
        String name = jedis.get("name");
        System.out.println(name);
    }

    //3.释放
    @AfterEach
    void tearDown() {
        if (jedis != null) {
            jedis.close();
        }
    }

}

Jedis本身线程不安全,并且频繁创建和销毁有性能损耗,因此使用jedis连接池代替jedis直连

package com.sugon.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisConnectionFactory {

    private static final JedisPool jedispool;

    static {
        //配置连接池
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(8);
        jedisPoolConfig.setMaxIdle(8);
        jedisPoolConfig.setMinIdle(0);
        jedisPoolConfig.setMaxWaitMillis(1000);
        jedispool = new JedisPool(jedisPoolConfig,
                "192.168.136.130", 6379,1000,"123321");

    }

    public static Jedis getJedis() {
        return jedispool.getResource();
    }
}

SpringDataRedis

API返回值说明
redisTemplate通用命令
redisTemplate.opsForValue()ValueOperationsString
redisTemplate.opsForHash()HashOperations
redisTemplate.opsForList()ListOperations
redisTemplate.opsForSet()SetOperations
redisTemplate.opsForZSet()ZSetOperations
<!--        Redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
<!--        连接池依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
spring:
  redis:
    host: 192.168.136.130
    port: 6379
    password: 123321
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 100
        

RedisTemplate

package com.sugon.redisdemo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class RedisDemoApplicationTests {

    //1.注入
    @Autowired
    private RedisTemplate redisTemplate;

    //2.测试
    @Test
    void testString(){
        redisTemplate.opsForValue().set("name","zhangsan");
        Object name = redisTemplate.opsForValue().get("name");
        System.out.println(name);
    }

}

RedisTemplate的RedisSerializer

RedisTemplate存储对象的序列化,也就是SpringDataRedis的序列化方式:

  • RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式(序列化器),默认是采用JDK序列化(JdkSerializationRedisSerializer)

缺点:

  • 可读性差
  • 内存占用较大

改变:

StringRedisSerializer 转string,一般key用

GenericJacksonJsonRedisSerializer 转json,一般value用

<!--        Jackson依赖-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>   

自定义一个redisconfig

package com.sugon.redisdemo.config;

import org.springframework.beans.factory.annotation.Configurable;
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 sun.net.www.content.text.Generic;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        //创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //设置连接工厂
        template.setConnectionFactory(connectionFactory);
        //拆功能键JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
        //key和hashkey使用string序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(jsonSerializer);
        //value和hashvalue使用json序列化
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);


        return template;
    }
}

也可以存储对象

@Test
    void testSaveUser(){
        redisTemplate.opsForValue().set("user:100",new User("zhangsan",18));
        User o = (User)redisTemplate.opsForValue().get("user:100");
        System.out.println(o);
    }

总结:自定义serializer实现自动序列化和反序列化

问题:还会存入一个额外的东西:对象类的字节码。会占用空间。

  • 为了在反序列化时候知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销

StringRedisTemplate

为了节省内存空间,不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。

但是,没有了对象类,当需要存储java对象时,需要手动完成对象的序列化和反序列化。

StringRedisTemplate类,key和value的序列化方式默认是String方式

@Autowired
private StringRedisTemplate stringRedisTemplate;
//JSON工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testStringTemplate() throws JsonProcessingException{
    User user = new User("zhangsan",18);
    String json = mapper.writeValueAsString(user);
    stringRedisTemplate.opsForValue.set("user:200",json);
    String val = stringRedisTemplate.opsForValue().get("user:200");
    User user1 = mapper.readValue(val,User.class);
    System.out.println(user1);
}

操作Hash类型


原文地址:https://blog.csdn.net/zzzzzucc/article/details/142882294

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!