redis 二. string 应用场景及底层分析

一. 简单命令示例

  1. String字符串类型,一个key对应value最大可以存512
  2. 简单命令使用示例
//1.单个键值对插入与获取
set k1 v1
get k1

//2.一次多个键值对插入与获取
mset k1 v1 k2 v2 k3 v3
mget k1 k2 k3

//3.递增数字 INCR key,每执行一次该命令,对应该key的值累计加1
//存储 ki 对应value 为1
set ki 1
//每执行一次对应ki的值累计加1(思考该方法可以用在什么功能上? 点赞量)
INCR ki 

//4.递减数字 DECR key,每执行一次该命令,对应该key的值累计减1
DECR ki

//5.获取字符串长度
STRLEN k1

//6.分布式锁
setnx key value

//EX: 指定key多少秒后过期
//PX: 指定key多好毫秒后过期
//NX: 当key不存在时才创建,等同于setnx
//XX: 当key不存在时才创建,存在则覆盖
#set key value [EX seconds][PX milliseconds][NX|XX]
//使用举例
set orderK aaa ex 10 nx

二. java 操作示例

基础

  1. 操作 String 类型,二进制安全的。意思是可以包含任何数据。比如jpg图片或者序列化的对象 ,最基本的数据类型,一个键最大能存储512MB。
	@Test
    public void test01() {

        //创建操作redis中String类型数据的对象 ValueOperations
        ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();

        //1.添加数据set()添加数据(如果添加这个数据在库中已经存在就变为了修改)
        valueOperations.set("username", "mingming");

        /*2.以层级关系目录形式添加数据*/
        valueOperations.set("user:01", "wwww");
        valueOperations.set("user:01", "cccc");

        //3.添加多条数据multiSet()
        //map中的key为redis的key,map中的值为redis的值
        Map<String,String> stringMap=new HashMap<>();
        stringMap.put("数据1","值1");
        stringMap.put("数据2","值2");
        stringMap.put("数据3","值3");

        valueOperations.multiSet(stringMap);

        //4.查询
        Object username=valueOperations.get("username");

        //5.查询多条,以集合中的数据为key进行查询
        List<String> keyStr=new ArrayList<>();
        keyStr.add("数据1");
        keyStr.add("数据2");
        keyStr.add("数据3");
        List<String> valL=valueOperations.multiGet(keyStr);

        //6.删除指定key数据
        stringRedisTemplate.delete("username");

        //6.模糊匹配
        Set<String> keys = stringRedisTemplate.keys("noteUserListenedPoi:" + "*");

        //7.批量删除
        stringRedisTemplate.delete(keys);

    }

setnx() 与 getset()

  • setnx() 向redis中存储一个key-value,如果redis库中已存在,当前存储失败返回0,如果不存在,存储成功,返回1
  • get(key) 获取key的值,如果存在,则返回;如果不存在,则返回nil;
  • getset()命令:这个命令主要有两个参数 getset(key, newValue)。该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。
 	@Autowired
    public Jedis jedis;

    @Autowired
    public JedisPool jedisPool;

    @Test
    public void test() {
        //1.建立redis连接
        Jedis conn = jedisPool.getResource();

        long currentTime = System.currentTimeMillis();//当前时
        String lockTimeDuration = String.valueOf(currentTime);
        //将当前时间作为value 存储到redis中,存储成功返回1,否则返回0
        Long i = jedis.setnx("keys", lockTimeDuration);
    }

三. 使用场景举例

  1. 分布式锁 setnx
  2. 点赞 DECR

统计点击次数

  1. 使用String类型,与incr 命令实现喜欢的文章,热搜案例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ArticleController {

    public static final String likeK = "acticle";

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 使用String类型,调用increment(key)对一个key进行累计加1,用来统计点击量等
     * 注意点: 该方式适用与qps不高的小厂,假设qps超过10万,例如热搜,一瞬间可能会打爆redis
     * //解决方式一: 有些统计精确度不高的,该累计数字到一定程度后就不回继续累计直接返回,例如显示"10w+"
     * //解决方式二: 适用redis的set类型,时间片去解决
     * @param acticleId 
     * @return
     */
    @GetMapping("/likeActicle")
    public Long likeArticle(String acticleId) {
        String key = likeK + acticleId;
        long l = stringRedisTemplate.opsForValue().increment(key);
        return l;
    }
}

四. 底层分析

  1. 在redis中一个string最大存储512

  2. 注意点String同一个数据类型有三种编码格式: int, embstr, raw (出现三种数据类型的原因是为了精确的利用并解决内存)

  3. int: 保存long型(长整型)的64位,8个字节有符号整数,最多19位,例如"set key 11"存储的是一个纯数字类型,到底层就使用int编码格式

  4. redis在启动时会预先建立10000个分别存储了0-9999的redisObject共享变量,当我们set 字符串为0-10000之间的话则使用共享变量即可,不需要再建立新对象,并且当前存储的数字字符串如果不超过"9223372036854775807"使用int编码格式,既redisObject中type=“REDIS_STRING”, encoding=“OBJ_ENCODING_INT”,整个结构如下
    在这里插入图片描述

  5. 注意点有个范围,当set 的数字超过"9223372036854775807"再次查看这个key的编码就会变为embstr
    在这里插入图片描述

  6. 补充点: 只有整数才会使用int编码格式,如果是浮点数,redis会现将浮点数转化为字符串值,然后进行保存
    在这里插入图片描述

  7. embstr编码格式又叫做SDS简单动态字符串,保存长度小于44字节的字符串,embstr既embedded string 又叫做嵌入式字符串,此时redisObject中的type=“OBJ_STRING”, encoding =“OBJ_ENCODING_ENBSTR”,整个redisObject结构在原int基础上嵌入了sds结构,如下图
    在这里插入图片描述

  8. raw编码格式: 保存大于44字节的字符串

  9. 注意点:在编码格式为embstr时,embsetr实际是只读的,当我们对embstr格式数据进行修改时,会先将其转换为raw格式,既所有修改后embstr格式的数据都是raw,type=“REDIS_STRING”, encoding=“OBJ_ENCODING_RAW”
    在这里插入图片描述
    在这里插入图片描述

  10. 示例在这里插入图片描述

  11. 总结:
    在这里插入图片描述

SDS 嵌入式动态字符串

  1. redis使用c编写,在c语言中使用buf数组存储字符串,而redis在c基础上重新封装了一个存储字符串的结构也就是SDS ,有叫做简单动态字符串
    在这里插入图片描述
  2. SDS结构解释: (只是拿sdshdr8做个示例,在sds.h源码中有各个该结构)
    在这里插入图片描述
  3. 查看sds.h源码发现提供了多种sdshdr5,8,16,64…存储字符串接口,为什么没有选择c的buf数组存储字符串,而是分装了多个sdshdr结构体
    在这里插入图片描述

1)通过不同sdshdX结构体存储不同大小的字符串,所以提供了多个sdshdX结构体
2)封装新结构体sds,存储字符串的好处是: c语言中没有java中的String类型,在c中适用字符串时通过char[]数组实现的,假设想要获取一个字符串的长度,需要从头开始遍历,到’\0’,而sds结构体中有一个len属性,表示当前字符串的长度,可以在O(1)情况下直接拿到,不需要像c以前一样遍历一遍字符串才可以
3)sds结构体中有alloc属性表示字符串最大字节长度,free属性,表示以分配而未使用的空间大小,这样就不需要考虑内存分配的问题
在这里插入图片描述

再次总结

  1. redis使用c编写, c语言中没有字符串类型, 封装了专门的SDS对象,用来存储,内部存在
  1. buf[]数组属性: 字节数组,用于保存实际数据
  2. len属性:当前保存数据的buf数组长度
  3. free属性: 数组中未使用的字节数量
  4. alloc属性: 当前字符串数组总共占用分配内存大小
  1. 基于以上属性sds动态字符串具有了以下优点:
  1. 常数复杂度获取字符串长度: 在获取字符串长度时,可以直接通过sds的len属性获取,复杂度为0(1)
  2. 减少缓冲区溢出: 修改sds时,会检查 free属性剩余空间的大小,如果不足将会额外申请(也就是空间预分配机制)
  3. 实现了空间预分配机制: 在对SDS 进行修改后会判断(也就是下面的扩容策略)

3.1 newlen 如果小于 1MB时,会分配 alloc 等于 2 * newlen,此时 free = len, 举例: SDS修改后len变为13个字节,程序也会分配 13 字节的未使用空间,SDS的 buf 数组此时长度将变成 13 + 13 + 1 = 27 字节(额外的一字节用于保存空字符)
3.2 newlen 如果大于等于 1MB时,会分配 1MB 的未使用空间。举例: SDS修改后len 变成30MB,程序会分配 1MB 的未使用空间,SDS 的 buf 数组的实际长度将为 30MB + 1MB + 1byte

  1. 惰性释放: 当sds需要缩短字符串时, 并不立即回收内存,而是使用 free 属性,将这些字节的数量记录起来,下次使用时再回收
  2. 二进制安全: SDS的API都是二进制安全的,存放在buf数组里的数据
  1. 根据空间分配机制了解到sds的扩容策略:
  1. 若剩余空闲长度 avail 大于新增内容的长度 addlen,直接在数组 buf 末尾追加即可,无须扩容
  2. 若剩余空闲长度 avail 小于或等于新增内容的长度 addlen,则分情况讨论:新增后总长度 len+addlen <1MB 的,按新长度的 2倍 扩容;新增后总长度 len+addlen > 1MB 的,按新长度加上 1MB 扩容。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值