一. 简单命令示例
- String字符串类型,一个key对应value最大可以存512
- 简单命令使用示例
//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 操作示例
基础
- 操作 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);
}
三. 使用场景举例
- 分布式锁 setnx
- 点赞 DECR
统计点击次数
- 使用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;
}
}
四. 底层分析
-
在redis中一个string最大存储512
-
注意点String同一个数据类型有三种编码格式: int, embstr, raw (出现三种数据类型的原因是为了精确的利用并解决内存)
-
int: 保存long型(长整型)的64位,8个字节有符号整数,最多19位,例如"set key 11"存储的是一个纯数字类型,到底层就使用int编码格式
-
redis在启动时会预先建立10000个分别存储了0-9999的redisObject共享变量,当我们set 字符串为0-10000之间的话则使用共享变量即可,不需要再建立新对象,并且当前存储的数字字符串如果不超过"9223372036854775807"使用int编码格式,既redisObject中type=“REDIS_STRING”, encoding=“OBJ_ENCODING_INT”,整个结构如下
-
注意点有个范围,当set 的数字超过"9223372036854775807"再次查看这个key的编码就会变为embstr
-
补充点: 只有整数才会使用int编码格式,如果是浮点数,redis会现将浮点数转化为字符串值,然后进行保存
-
embstr编码格式又叫做SDS简单动态字符串,保存长度小于44字节的字符串,embstr既embedded string 又叫做嵌入式字符串,此时redisObject中的type=“OBJ_STRING”, encoding =“OBJ_ENCODING_ENBSTR”,整个redisObject结构在原int基础上嵌入了sds结构,如下图
-
raw编码格式: 保存大于44字节的字符串
-
注意点:在编码格式为embstr时,embsetr实际是只读的,当我们对embstr格式数据进行修改时,会先将其转换为raw格式,既所有修改后embstr格式的数据都是raw,type=“REDIS_STRING”, encoding=“OBJ_ENCODING_RAW”
-
示例
-
总结:
SDS 嵌入式动态字符串
- redis使用c编写,在c语言中使用buf数组存储字符串,而redis在c基础上重新封装了一个存储字符串的结构也就是SDS ,有叫做简单动态字符串
- SDS结构解释: (只是拿sdshdr8做个示例,在sds.h源码中有各个该结构)
- 查看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属性,表示以分配而未使用的空间大小,这样就不需要考虑内存分配的问题
再次总结
- redis使用c编写, c语言中没有字符串类型, 封装了专门的SDS对象,用来存储,内部存在
- buf[]数组属性: 字节数组,用于保存实际数据
- len属性:当前保存数据的buf数组长度
- free属性: 数组中未使用的字节数量
- alloc属性: 当前字符串数组总共占用分配内存大小
- 基于以上属性sds动态字符串具有了以下优点:
- 常数复杂度获取字符串长度: 在获取字符串长度时,可以直接通过sds的len属性获取,复杂度为0(1)
- 减少缓冲区溢出: 修改sds时,会检查 free属性剩余空间的大小,如果不足将会额外申请(也就是空间预分配机制)
- 实现了空间预分配机制: 在对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
- 惰性释放: 当sds需要缩短字符串时, 并不立即回收内存,而是使用 free 属性,将这些字节的数量记录起来,下次使用时再回收
- 二进制安全: SDS的API都是二进制安全的,存放在buf数组里的数据
- 根据空间分配机制了解到sds的扩容策略:
- 若剩余空闲长度 avail 大于新增内容的长度 addlen,直接在数组 buf 末尾追加即可,无须扩容
- 若剩余空闲长度 avail 小于或等于新增内容的长度 addlen,则分情况讨论:新增后总长度 len+addlen <1MB 的,按新长度的 2倍 扩容;新增后总长度 len+addlen > 1MB 的,按新长度加上 1MB 扩容。