当前位置: 首页 > news >正文

Redis的String详解

Redis String数据结构概述

Redis的String类型是最基本、最常用的数据类型,可以存储任何形式的数据,包括字符串、整数、浮点数、甚至二进制数据。String类型的值最大可以存储512MB的数据。

String类型的特点

  • 二进制安全:可以存储任何二进制数据,不限于可打印字符
  • 多种编码方式:根据存储内容自动选择最优编码,节省内存
  • 丰富的操作:支持字符串操作、数值计算、位操作等
  • 原子性操作:所有操作都是原子的,适合计数器等场景

应用场景

场景说明
缓存存储用户会话、页面缓存、对象序列化数据
计数器文章阅读量、用户点赞数、在线人数等
分布式锁利用SETNX命令实现分布式锁
限流器结合过期时间实现API限流
位图用户签到、特征标记等

Redis String的底层实现

Redis String的底层实现主要采用三种编码方式:intembstrraw。Redis会根据存储的值自动选择最合适的编码方式。

底层数据结构

SDS(简单动态字符串)

Redis使用SDS而不是C语言原生字符串来表示字符串值:

struct sdshdr {int len;        // 字符串已使用的长度int free;       // 字符串未使用的长度char buf[];     // 字符数组,用于保存字符串
};

SDS的优势

  • O(1)时间复杂度获取字符串长度
  • 避免缓冲区溢出
  • 减少内存重分配次数
  • 二进制安全

三种编码方式

INT编码

适用条件

  • 存储的值是整数
  • 整数值在LONG_MINLONG_MAX之间(64位系统为-263到263-1)

内存布局

+---------------------+
|     redisObject     |
| type: String        |
| encoding: INT       |
| ptr: 12345          |  <- 整数值直接存储在ptr中
+---------------------+

示例

127.0.0.1:6379> SET counter 100
OK
127.0.0.1:6379> OBJECT ENCODING counter
"int"

EMBSTR编码

适用条件

  • 存储的值是字符串
  • 字符串长度小于等于44字节(Redis 5.0+)

内存布局

+---------------------+---------------------+
|     redisObject     |       sdshdr        |
| type: String        | len: 5, free: 0     |
| encoding: EMBSTR    | buf: "Hello"        |
| ptr: --------------→|                     |
+---------------------+---------------------+

特点

  • RedisObject和SDS在内存中连续存储
  • 只需要一次内存分配
  • 更好的缓存局部性

示例

127.0.0.1:6379> SET short_str "Hello"
OK
127.0.0.1:6379> OBJECT ENCODING short_str
"embstr"

RAW编码

适用条件

  • 存储的值是字符串
  • 字符串长度大于44字节

内存布局

+---------------------+     +---------------------+
|     redisObject     |     |       sdshdr        |
| type: String        |     | len: 50, free: 10   |
| encoding: RAW       |----→| buf: "Long string..."|
| ptr: --------------→|     |                     |
+---------------------+     +---------------------+

特点

  • RedisObject和SDS分开存储
  • 需要两次内存分配
  • 适用于长字符串

示例

127.0.0.1:6379> SET long_str "This is a very long string that exceeds 44 bytes in length..."
OK
127.0.0.1:6379> OBJECT ENCODING long_str
"raw"

编码转换规则

编码方式会根据操作自动转换:

# 初始为INT编码
127.0.0.1:6379> SET num 100
OK
127.0.0.1:6379> OBJECT ENCODING num
"int"# 追加操作后转换为RAW编码
127.0.0.1:6379> APPEND num "abc"
(integer) 6
127.0.0.1:6379> OBJECT ENCODING num
"raw"# EMBSTR在修改时会转换为RAW
127.0.0.1:6379> SET str "hello"
OK
127.0.0.1:6379> OBJECT ENCODING str
"embstr"
127.0.0.1:6379> APPEND str " world"
(integer) 11
127.0.0.1:6379> OBJECT ENCODING str
"raw"

Redis String操作命令

基本操作命令

  • SET key value [EX seconds] [PX milliseconds] [NX|XX]:设置键值对

    127.0.0.1:6379> SET user:1:name "Tom" EX 3600
    OK
    
  • GET key:获取键对应的值

    127.0.0.1:6379> GET user:1:name
    "Tom"
    
  • DEL key:删除键

    127.0.0.1:6379> DEL user:1:name
    (integer) 1
    
  • EXISTS key:判断键是否存在

    127.0.0.1:6379> EXISTS user:1:name
    (integer) 0
    

批量操作命令

  • MSET key value [key value …]:批量设置键值对

    127.0.0.1:6379> MSET user:1:name "Tom" user:1:age 25 user:1:city "Shanghai"
    OK
    
  • MGET key [key …]:批量获取值

    127.0.0.1:6379> MGET user:1:name user:1:age user:1:city
    1) "Tom"
    2) "25"
    3) "Shanghai"
    

数字操作命令

  • INCR key:将键的整数值加1

    127.0.0.1:6379> INCR counter
    (integer) 1
    
  • DECR key:将键的整数值减1

    127.0.0.1:6379> DECR counter
    (integer) 0
    
  • INCRBY key increment:将键的值加上整数增量

    127.0.0.1:6379> INCRBY counter 5
    (integer) 5
    
  • INCRBYFLOAT key increment:将键的值加上浮点数增量

    127.0.0.1:6379> INCRBYFLOAT price 1.5
    "6.5"
    

字符串操作命令

  • APPEND key value:将值追加到现有值的末尾

    127.0.0.1:6379> APPEND greeting " World"
    (integer) 11
    
  • STRLEN key:获取值的长度

    127.0.0.1:6379> STRLEN greeting
    (integer) 11
    
  • GETRANGE key start end:获取子字符串

    127.0.0.1:6379> GETRANGE greeting 0 4
    "Hello"
    
  • SETRANGE key offset value:从偏移量开始覆盖字符串

    127.0.0.1:6379> SETRANGE greeting 6 "Redis"
    (integer) 11
    

位操作命令

  • SETBIT key offset value:设置或清除位的值

    127.0.0.1:6379> SETBIT user:1:login:2023 10 1
    (integer) 0
    
  • GETBIT key offset:获取位的值

    127.0.0.1:6379> GETBIT user:1:login:2023 10
    (integer) 1
    
  • BITCOUNT key [start end]:统计值为1的位数

    127.0.0.1:6379> BITCOUNT user:1:login:2023
    (integer) 1
    

高级操作命令

  • SETEX key seconds value:设置键值对并指定过期时间(秒)

    127.0.0.1:6379> SETEX session:123 3600 "user_data"
    OK
    
  • PSETEX key milliseconds value:设置键值对并指定过期时间(毫秒)

    127.0.0.1:6379> PSETEX temp:key 5000 "temporary_data"
    OK
    
  • SETNX key value:键不存在时才设置

    127.0.0.1:6379> SETNX lock:resource 1
    (integer) 1
    
  • GETSET key value:设置新值并返回旧值

    127.0.0.1:6379> GETSET counter 100
    "50"
    

Java中操作Redis String

StringRedisTemplate配置

@Configuration
public class RedisConfig {@Beanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {StringRedisTemplate template = new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);// 设置序列化器template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());return template;}
}

基本操作示例

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Component
public class RedisStringService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 设置值public void set(String key, String value) {stringRedisTemplate.opsForValue().set(key, value);}// 设置值并指定过期时间public void setWithExpire(String key, String value, long timeout, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, value, timeout, unit);}// 获取值public String get(String key) {return stringRedisTemplate.opsForValue().get(key);}// 设置值(仅当键不存在时)public Boolean setIfAbsent(String key, String value) {return stringRedisTemplate.opsForValue().setIfAbsent(key, value);}// 删除键public Boolean delete(String key) {return stringRedisTemplate.delete(key);}// 判断键是否存在public Boolean hasKey(String key) {return stringRedisTemplate.hasKey(key);}// 设置过期时间public Boolean expire(String key, long timeout, TimeUnit unit) {return stringRedisTemplate.expire(key, timeout, unit);}// 获取剩余过期时间public Long getExpire(String key, TimeUnit unit) {return stringRedisTemplate.getExpire(key, unit);}
}

数字操作示例

@Component
public class RedisNumberService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 递增操作public Long increment(String key) {return stringRedisTemplate.opsForValue().increment(key);}// 递增指定值public Long incrementBy(String key, long delta) {return stringRedisTemplate.opsForValue().increment(key, delta);}// 递减操作public Long decrement(String key) {return stringRedisTemplate.opsForValue().decrement(key);}// 递减指定值public Long decrementBy(String key, long delta) {return stringRedisTemplate.opsForValue().decrement(key, delta);}// 浮点数递增public Double incrementByFloat(String key, double delta) {return stringRedisTemplate.opsForValue().increment(key, delta);}
}

批量操作示例

@Component
public class RedisBatchService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 批量设置public void multiSet(Map<String, String> keyValueMap) {stringRedisTemplate.opsForValue().multiSet(keyValueMap);}// 批量获取public List<String> multiGet(List<String> keys) {return stringRedisTemplate.opsForValue().multiGet(keys);}// 批量设置(仅当所有键都不存在时)public Boolean multiSetIfAbsent(Map<String, String> keyValueMap) {return stringRedisTemplate.opsForValue().multiSetIfAbsent(keyValueMap);}
}

位操作示例

@Component
public class RedisBitService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 设置位值public Boolean setBit(String key, long offset, boolean value) {return stringRedisTemplate.opsForValue().setBit(key, offset, value);}// 获取位值public Boolean getBit(String key, long offset) {return stringRedisTemplate.opsForValue().getBit(key, offset);}// 统计位值为1的数量public Long bitCount(String key) {return stringRedisTemplate.opsForValue().size(key) == null ? 0 : stringRedisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes()));}// 在指定范围内统计位值为1的数量public Long bitCount(String key, long start, long end) {return stringRedisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes(), start, end));}
}

高级操作示例

@Component
public class RedisAdvancedService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 获取并设置public String getAndSet(String key, String value) {return stringRedisTemplate.opsForValue().getAndSet(key, value);}// 获取字符串长度public Long size(String key) {return stringRedisTemplate.opsForValue().size(key);}// 追加字符串public Integer append(String key, String value) {return stringRedisTemplate.opsForValue().append(key, value);}// 获取子字符串public String getRange(String key, long start, long end) {return stringRedisTemplate.opsForValue().get(key, start, end);}// 设置子字符串public void setRange(String key, String value, long offset) {stringRedisTemplate.opsForValue().set(key, value, offset);}
}

实际应用

@Service
public class UserSessionService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private static final String SESSION_PREFIX = "session:";private static final long SESSION_TIMEOUT = 30 * 60; // 30分钟// 创建用户会话public void createUserSession(String sessionId, String userData) {String key = SESSION_PREFIX + sessionId;stringRedisTemplate.opsForValue().set(key, userData, SESSION_TIMEOUT, TimeUnit.SECONDS);}// 获取用户会话public String getUserSession(String sessionId) {String key = SESSION_PREFIX + sessionId;return stringRedisTemplate.opsForValue().get(key);}// 刷新会话过期时间public void refreshSession(String sessionId) {String key = SESSION_PREFIX + sessionId;stringRedisTemplate.expire(key, SESSION_TIMEOUT, TimeUnit.SECONDS);}// 删除用户会话public void deleteUserSession(String sessionId) {String key = SESSION_PREFIX + sessionId;stringRedisTemplate.delete(key);}
}@Service
public class ArticleService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private static final String VIEW_COUNT_PREFIX = "article:view:";// 增加文章阅读量public Long incrementArticleView(Long articleId) {String key = VIEW_COUNT_PREFIX + articleId;return stringRedisTemplate.opsForValue().increment(key);}// 获取文章阅读量public Long getArticleViewCount(Long articleId) {String key = VIEW_COUNT_PREFIX + articleId;String count = stringRedisTemplate.opsForValue().get(key);return count == null ? 0L : Long.parseLong(count);}
}

底层源码

Redis对象创建源码

// redis/src/object.c#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44// 创建String对象
robj *createStringObject(const char *ptr, size_t len) {if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)return createEmbeddedStringObject(ptr,len);elsereturn createRawStringObject(ptr,len);
}// 创建INT编码的String对象
robj *createStringObjectFromLongLong(long long value) {robj *o;// 尝试使用共享的整数对象if (value >= 0 && value < OBJ_SHARED_INTEGERS) {o = shared.integers[value];} else {// 创建新的整数对象o = createObject(OBJ_STRING, NULL);o->encoding = OBJ_ENCODING_INT;o->ptr = (void*)((long)value);}return o;
}// 创建EMBSTR编码的String对象
robj *createEmbeddedStringObject(const char *ptr, size_t len) {robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);struct sdshdr8 *sh = (void*)(o+1);o->type = OBJ_STRING;o->encoding = OBJ_ENCODING_EMBSTR;o->ptr = sh+1;o->refcount = 1;// 设置SDS属性sh->len = len;sh->alloc = len;sh->flags = SDS_TYPE_8;if (ptr) {memcpy(sh->buf,ptr,len);sh->buf[len] = '\0';} else {memset(sh->buf,0,len+1);}return o;
}

编码转换源码

// redis/src/object.c// 检查编码并尝试转换
robj *tryObjectEncoding(robj *o) {long value;sds s = o->ptr;size_t len;// 确保是RAW或EMBSTR编码if (!sdsEncodedObject(o)) return o;// 尝试转换为INT编码len = sdslen(s);if (len <= 20 && string2l(s,len,&value)) {// 如果值在共享整数范围内,使用共享对象if ((value >= 0 && value < OBJ_SHARED_INTEGERS) && server.maxmemory == 0) {decrRefCount(o);return shared.integers[value];} else {// 转换为INT编码o->encoding = OBJ_ENCODING_INT;sdsfree(o->ptr);o->ptr = (void*)value;return o;}}// 如果字符串很小且是RAW编码,尝试转换为EMBSTRif (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {robj *emb;if (o->encoding == OBJ_ENCODING_EMBSTR) return o;emb = createEmbeddedStringObject(s,sdslen(s));decrRefCount(o);return emb;}// 尝试缩减SDS的未使用空间if (sdsavail(s) > len/10) {o->ptr = sdsRemoveFreeSpace(o->ptr);}return o;
}

SET命令处理源码

// redis/src/t_string.cvoid setCommand(client *c) {int j;robj *expire = NULL;int unit = UNIT_SECONDS;int flags = OBJ_SET_NO_FLAGS;// 解析命令参数for (j = 3; j < c->argc; j++) {char *a = c->argv[j]->ptr;robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];if ((a[0] == 'n' || a[0] == 'N') &&(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&!(flags & OBJ_SET_XX)) {flags |= OBJ_SET_NX;} else if ((a[0] == 'x' || a[0] == 'X') &&(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&!(flags & OBJ_SET_NX)) {flags |= OBJ_SET_XX;} else if (!strcasecmp(c->argv[j]->ptr,"ex") && next) {unit = UNIT_SECONDS;expire = next;j++;} else if (!strcasecmp(c->argv[j]->ptr,"px") && next) {unit = UNIT_MILLISECONDS;expire = next;j++;} else {addReply(c,shared.syntaxerr);return;}}// 检查NX/XX条件c->argv[2] = tryObjectEncoding(c->argv[2]);if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,c->argv[1]) != NULL) ||(flags & OBJ_SET_XX && lookupKeyWrite(c->db,c->argv[1]) == NULL)) {addReply(c, shared.nullbulk);return;}// 设置键值对setKey(c->db,c->argv[1],c->argv[2]);server.dirty++;// 设置过期时间if (expire) {setExpire(c,c->db,c->argv[1],mstime()+strtoll(expire->ptr,NULL,10)*((unit == UNIT_SECONDS) ? 1000 : 1));}addReply(c, shared.ok);
}

GET命令处理源码

// redis/src/t_string.cvoid getCommand(client *c) {getGenericCommand(c);
}int getGenericCommand(client *c) {robj *o;// 查找键if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)return C_OK;// 检查类型是否为Stringif (o->type != OBJ_STRING) {addReply(c,shared.wrongtypeerr);return C_ERR;} else {addReplyBulk(c,o);return C_OK;}
}

INCR命令处理源码

// redis/src/t_string.cvoid incrCommand(client *c) {incrDecrCommand(c,1);
}void incrDecrCommand(client *c, long long incr) {long long value, oldvalue;robj *o, *new;// 查找现有值o = lookupKeyWrite(c->db,c->argv[1]);if (o != NULL && checkType(c,o,OBJ_STRING)) return;if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;// 检查溢出oldvalue = value;if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||(incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {addReplyError(c,"increment or decrement would overflow");return;}value += incr;// 如果原值是INT编码且新值也在共享整数范围内,使用共享对象if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&(value < 0 || value >= OBJ_SHARED_INTEGERS) &&value >= LONG_MIN && value <= LONG_MAX) {new = o;o->ptr = (void*)((long)value);} else {new = createStringObjectFromLongLong(value);if (o) {dbReplace(c->db,c->argv[1],new);} else {dbAdd(c->db,c->argv[1],new);}}signalModifiedKey(c->db,c->argv[1]);notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);server.dirty++;addReply(c,shared.colon);addReply(c,new);addReply(c,shared.crlf);
}
http://www.dtcms.com/a/466072.html

相关文章:

  • MySQL事务隔离级别详解从读未提交到可串行化
  • 网站域名注册空间app外包
  • 赣州网站推广公司微网站建设是什么
  • 图扑 HT 架构下 AR 应用开发与行业解决方案实现
  • 测试实战心得
  • 网页网站设计价格为什么我做的视频网站播放不了
  • 前端框架深度解析:Vue.js 3 从 Composition API 到生态升级,解锁企业级开发新能力
  • DataX适合全量同步和简单的增量场景
  • 实体门店怎么使用小程序?实体店如何做小程序店铺?
  • 服装公司网站建设方案渭南做网站博创互联
  • 基于GPS/PTP/gPTP的自动驾驶数据同步授时方案
  • 福田网站建设龙岗网站建设龙岗网站建设龙岗网站建设中关村手机之家官网
  • solr负查询失效
  • GSPO如何消除高方差且不依赖routing replay
  • 南宁电子推广网站河南网站建设技术公司
  • 泰安房产网站建设设计网页公司哪家好
  • R语言基础保姆教程01--从工具到数据类型
  • MySQL索引失效揭秘:隐式类型转换的规则与案例!
  • Mysql杂志(三十)——索引失效情况
  • 百度企业网站建设wordpress 数据库设计
  • 10.程序地址空间_1
  • 6.0 Labview中的类面向对象编程-类的使用(OOP)
  • 上海精品网站建设想设计一个公司的网站
  • 【计算机】常见的缓存和查看方法
  • Linux 进程间通信机制详解
  • 低轨卫星光模块控制中的MCU芯片抗辐照性能研究
  • 网站建设faq男人和女人做哪个网站
  • 网站优化排名易下拉系统如何让网站自适应
  • CTF攻防世界WEB精选基础入门:xff_referer
  • 做presentation的网站wordpress搜索框去掉