其他命令
info:查看redis系统信息,包括库的信息 Keyspace
select:Keyspace中选择db

flushdb:删除当前的db
flushall:删除所有的db
quit:退出cli
(以下k代表key值,v代表value值,n代表name值)
键命令
exists:exitsts k 查看是否存在在这个key
ttl:ttl k 查看key的剩余生存时间,-1代表不存过期时间,-2不存在或已到期
type:type k 查看key的类型
randomkey: randomkey 随机随机返回一个key
rename:rename k newk 改变key的名字,如果修改的名称已经存在则直接覆盖存在同名的key
renamenx:renamenx k newk 改变key的名字,如果修改的名称已经存在则会返回0代表修改失败
5种数据结构
string
set:set k v
get:get k
del:del k
setex: setex k 100 v ex是expired的缩写,设置key值的过期时间是100秒
psetex: setex k 100 v ex是expired的缩写,设置key值的过期时间是100毫秒
getrange:getrange k 0 3 截取key值对应的value,例如value是redis,截取0到3返回就是redi
getset: getset k v 先get key的value值并返回,然后set key 的value值
mset: mset k1 v1 k2 v2 k3 v3 设置多个key的value
mget: mget k1 k2 k3 获取多个key的value
setnx: setnx k v 判断是否已经存在k值,存在则设置失败返回0,成功则是设置成功返回1
msetnx: mset k1 v1 k2 v2 k3 v3 设置多个key的value,先判断这些key是否存在,只要有一个不存在就命令就会整个失败,具有原子性
strlen: strlen k 获取key对应value的长度
incr: incr k 使k对应的value值加1,并返回结果
incrby: incrby k 100 使k对应的value值加100,并返回结果
decr: decr k 使k对应的value值减1,并返回结果
decrby: decrby k 100 使k对应的value值减100,并返回结果
append: append k av 在key对应的value值后边最近字符串
hash
hset:hset n k v 设置一个哈希的名字及这个哈希的key和value
hexists:hexists n k 查询一个哈希中的key是否是存在的,返回0不存在,1存在
hget:hget n k 获取一个哈希中的key对应的value值,没有值存在就返回nil(null)
hgetall:hgetall n 获取一个哈希中所有的key value值
hkeys:hkeys n 获取一个哈希中所有的key值
hvals:hvals n 获取一个哈希中所有的value值
hlen:hlen n 获取一个哈希中的 key的数量
hmset: hmset n k1 v1 k2 v2 k3 v3 设置一个哈希中多个key-value键值对
hmget:hmget n k1 k2 k3 获取一个哈希中指定数量的key1 key2 key2对应的value值
hdel:hdel n k1 k2 删除一个哈希中指定数量和名称的key
hsetnx: hsetnx n k newv 设置一个哈希中对应的key,并且会判断key值是否存在,存在则操作失败
list
lpush: lpush n v1 v2 v3 v4 v5 将多个v值按顺序排列成list列表
llen:llen n获取list的长度
lrange:lrange n 0 3 在一个list中获取list中0到3的元素 设置为0到-1时就是获取所有的元素
lset: lset n 0 nv0 在一个list中设置第0个元素的值为nv0
lindex:lindex n 5 在一个通过index索引list中 获取到
lpop:lpop n 在一个list中移除list最前面的元素。并返回移除元素的值
rpop:rpop n 在一个list中移除list最后面的元素。并返回移除元素的值
set
set是一个不存在重复值并且无序的数据类型,和java的特点一样
sadd:sadd n v1 v2 v3 v4 设置一个set的元素,这里的元素是不允许重复的
scard:scard n 获取set中的元素个数
smembers:smembers n 查看set中的元素
sdiff:sdiff n1 n2 获取set1和set2的差集,返回set1的元素在set2中不存在的
sinter:sinter n1 n2 获取set1和set2的交集,返回set1的元素在set2中存在的
sunion:sunion n1 n2 获取set1和set2的并集,返回set1的元素在set2元素的合并然后去重的结果
srandom:srandmember n 2 获取一个set集合的x个随机数
sismember:sismemeber n v 查询一个元素是否是属于一个set集合中的元素
srem:srem n v1 v2 v3 移除一个set集合的v1 v2 v3元素
spop:spop n 移除一个set集合中的某个元素,并将其返回
sorted set
sorted set是一个有序,不可重复的集合,查询效率和set一样,通过分数保证集合的顺序
zadd:zadd n 100 v1 200 v2 300 v3 设置一个zset的元素,并设置元素的分数用于排名,按照分数从小到大排序
zcard:zcard n 查询zset的元素个数
zscore:zscore n v 查询元素在一个zset中的分数
zcount:zcount n 100 300 查询一个zset中在100到300分的分数区间的元素个数
zrank:zrank n v 查询一个元素在一个zset中的索引值
zincrby:zincrby n 1000 v 给一个zset中的v元素增加分数1000
zrange:zrange n 0 100 取出前0到100的元素 0到-1表示取出所有元素
zrange ....withscores:zrange n 0 100 withscores 取出前0到100的元素 0到-1表示取出所有元素,并且带上分数
Redis的过期策略和内存淘汰机制
过期删除
删除过期的键,这种策略对cpu的性能影响比较大
在Redis中使用的是定期删除和惰性删除
定期删除+惰性删除
定期删除
所谓定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意,这里可不是每隔100ms就遍历所有的设置过期时间的key,那样就是一场性能上的灾难。实际上redis是每隔100ms随机抽取一些key来检查和删除的。
惰性删除
但是,定期删除可能会导致很多过期key到了时间并没有被删除掉,所以就得靠惰性删除了。这就是说,在你获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下
通过上述两种手段结合起来,保证过期的key一定会被干掉。
但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了,怎么办?
答案是:走内存淘汰机制。
内存淘汰机制
redis.conf中配置:
maxmemory-policy allkeys-lru
noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
Redis为什么这么快
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路 I/O 复用模型,非阻塞 IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
缓存雪崩,击穿,穿透
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
1.在服务器端,接收参数时业务接口中过滤不合法的值,null,负值,和空值进行检测和空值。
2.布隆过滤器:类似于哈希表的一种算法,用所有可能的查询条件生成一个bitmap,在进行数据库查询之前会使用这个bitmap进行过滤,如果不在其中则直接过滤,从而减轻数据库层面的压力。
3.空值缓存:一种比较简单的解决办法,在第一次查询完不存在的数据后,将该key与对应的空值也放入缓存中,只不过设定为较短的失效时间,例如几分钟,这样则可以应对短时间的大量的该key攻击,设置为较短的失效时间是因为该值可能业务无关,存在意义不大,且该次的查询也未必是攻击者发起,无过久存储的必要,故可以早点失效。
缓存雪崩
因为缓存服务挂掉或者热点缓存大面积同时失效,大量的请求都去查数据库,导致数据库连接不够或者数据库处理不过来,从而导致整个系统不可用。
解决方案:
加锁排队、 设置过期标志更新缓存 、 设置过期标志更新缓存 、二级缓存(引入一致性问题)、 预热、 缓存与服务降级。
1.线程互斥:只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据才可以,每个时刻只有一个线程在执行请求,减轻了db的压力,但缺点也很明显,降低了系统的qps。
2.交错失效时间:批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效
缓存击穿
缓存击穿实际上是缓存雪崩的一个特例,缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。击穿与雪崩的区别即在于击穿是对于某一特定的热点数据来说,而雪崩是全部数据。
解决方案:
1.缓存设置不过期。
从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是物理不过期。但是它会遇到一个数据更新的问题,或者说数据不一致的问题。
在value中存储过期时间,在编码处理的时候,有条件(过期时间小于一分钟)对缓存数据进行更新,这个方案对性能最佳。
2.使用锁工具(分布式锁)
缓存预热
缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决思路:
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
3、定时刷新缓存;
缓存热点重建优化
场景:
- 当一个 key 是一个热点 key( 同一个key,同一个时候有几十万请求),并发量非常高
- 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次 IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存 ( 如下图),造成后端负载加大,甚至可能会让应用崩溃。

解决方法
一、分布式的互斥步锁

代码演示
String setHotKey(String hotkey) {
// 从Redis中获取数据
String value = cache.get(hotkey);
// 如果value为空,则开始重构缓存
if(value == null) {
// 只允许一个线程重构缓存,
String lockHotKey = "lockHotKey :" + key;
//使用nx,并设置过期时间ex防止死锁
if(cache.set(lockHotKey , "1", "ex 180", "nx")) {
// 从数据源获取数据
value = JSON.toString(dbMapper.selectByKey(hotkey));
// 回写Redis,并设置过期时间
cache.setex(hotkey, timeout, value);
// 删除lockHotKey
cache.delete(lockHotKey);
}else {
//获取锁失败的他线程休息50毫秒,后重试
Thread.sleep(50);
setHotKey(hotkey);
}
}
return value;
}
优点:思路简单,数据一致性好
缺点:使用锁需要等待,对系统产生了一定的资源的开销,只是数据库的压力减轻了,还可能因为操作不当而造成线程死锁问题。
二、让热键永远不过期
- 缓存层面:不要设置key过期时间,不会出现热点key过期后产生的问题
- 功能层面:为每个value添加逻辑过期时间,当逻辑判断value过期后,会使用单独的线程去构建缓存

代码演示
String setHotKey(final String hotkey) {
V v = cache.get(hotkey);
String value = v.getValue();
// 逻辑过期时间
long oldTimeout = v.getTimeout();
// 如果逻辑过期时间小于当前时间,开始后台构建
if(v.oldTimeout <= System.currentTimeMillis()) {
String lockHotKey = "lockHotKey :" + key;
if(cache.set(lockHotKey , "1", "ex 180", "nx")) {
// 重构缓存
threadPool.execute(new Runnable(){
public void run() {
newValue = JSON.toString(dbMapper.selectByKey(hotkey));;
cache.setex(hotkey, newValue , newTimeout);
cache.delete(lockHotKey);
}
});
}
}
return value;
}
优点:解决了互斥锁对系统资源的开销,无需同步等待,不会死锁
缺点:数据一致性差
各个数据类型的应用场景
String应用场景
分布式锁

List应用场景

Hash应用场景


Set应用场景


Zset应用场景

网友评论