redis问题总结

原创
2020/09/16 20:14
阅读数 67

redis的线程模型:redis是单线程模型,使用epoll多路复用器实现,文件事件处理,然后文件事件分发

redis数据类型:String、List、Hash、Set、Zset

redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set

String

redisTemplate.opsForValue().set("num","123");
redisTemplate.opsForValue().get("num")  
输出结果为123

redisTemplate.opsForValue().set("num","123",10, TimeUnit.SECONDS);
redisTemplate.opsForValue().get("num")设置的是10秒失效,十秒之内查询有结果,十秒之后返回为null

template.opsForValue().getAndSet("getSetTest","test2")//设置键的字符串值并返回其旧值

template.opsForValue().size("key")// 返回key所对应的value值得长度

Hash

删除给定的哈希hashKeys

template.opsForHash().delete("redisHash","name")//结果:1
template.opsForHash().entries("redisHash")//结果:{class=6, age=28.1}

确定哈希hashKey是否存在

template.opsForHash().hasKey("redisHash","666")//结果:true
template.opsForHash().hasKey("redisHash","777")//结果:false

从键中的哈希获取给定hashKey的值

template.opsForHash().get("redisHash","age");//结果:26

获取key所对应的散列表的key,redisHash所对应的散列表为{class=1, name=666, age=27}

template.opsForHash().keys("redisHash")//结果:[name, class, age]

设置散列hashKey的值

template.opsForHash().put("redisHash","name","666");
template.opsForHash().put("redisHash","age",26);
template.opsForHash().put("redisHash","class","6");
template.opsForHash().entries("redisHash")//结果:{age=26, class=6, name=666}

获取整个哈希存储的值根据密钥

template.opsForHash().values("redisHash");//结果:[tom, 26, 6]

List

将所有指定的值插入存储在键的列表的头部
template.opsForList().leftPush("list","java");

批量把一个数组插入到列表中

String[] strs = new String[]{"1","2","3"};
template.opsForList().leftPushAll("list",strs);
template.opsForList().range("list",0,-1)//结果:[3, 2, 1]

void set(K key, long index, V value);
在列表中index的位置设置value值

template.opsForList().range("listRight",0,-1)//结果:[java, python, oc, c++]
template.opsForList().set("listRight",1,"setValue");
template.opsForList().range("listRight",0,-1)//结果:[java, setValue, oc, c++]

从存储在键中的列表中删除等于值的元素的第一个计数事件。
计数参数以下列方式影响操作:
count> 0:删除等于从头到尾移动的值的元素。
count <0:删除等于从尾到头移动的值的元素。
count = 0:删除等于value的所有元素。

template.opsForList().range("listRight",0,-1)//结果:[java, setValue, oc, c++]
template.opsForList().remove("listRight",1,"setValue");//将删除列表中存储的列表中第一次次出现的“setValue”。
template.opsForList().range("listRight",0,-1)//结果:[java, oc, c++]

根据下表获取列表中的值,下标是从0开始的

template.opsForList().range("listRight",0,-1)//结果:[java, oc, c++]
template.opsForList().index("listRight",2)//结果:c++

弹出最左边的元素,弹出之后该值在列表中将不复存在

template.opsForList().range("list",0,-1)//结果:[c++, python, oc, java, c#, c#]
template.opsForList().leftPop("list")//结果:c++
template.opsForList().range("list",0,-1)//结果:[python, oc, java, c#, c#]
 

Set

无序集合中添加元素,返回添加个数
也可以直接在add里面添加多个值 如:template.opsForSet().add(“setTest”,“aaa”,“bbb”)

String[] strs= new String[]{"str1","str2"};
template.opsForSet().add("setTest", strs)//结果:2

移除集合中一个或多个成员
String[] strs = new String[]{"str1","str2"};
template.opsForSet().remove("setTest",strs);//结果:2
移除并返回集合中的一个随机元素
template.opsForSet().pop("setTest");//结果:bbb
template.opsForSet().members("setTest");//结果:[aaa, ccc]

将 member 元素从 source 集合移动到 destination 集合
template.opsForSet().move("setTest","aaa","setTest2");
template.opsForSet().members("setTest")//结果:[ccc]
template.opsForSet().members("setTest2")System.out.println();//结果:[aaa]

返回集合中的所有成员
template.opsForSet().members("setTest")//结果;[ddd, bbb, aaa, ccc]
ZSet

新增一个有序集合,存在的话为false,不存在的话为true
template.opsForZSet().add("zset1","zset-1",1.0);//结果:true
新增一个有序集合
ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<>("zset-5",9.6);
ZSetOperations.TypedTuple<Object> objectTypedTuple2 = new DefaultTypedTuple<>("zset-6",9.9);
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<ZSetOperations.TypedTuple<Object>>();
tuples.add(objectTypedTuple1);
tuples.add(objectTypedTuple2);
template.opsForZSet().add("zset1",tuples)
template.opsForZSet().range("zset1",0,-1)//结果:[zset-1, zset-2, zset-3, zset-4, zset-5, zset-6]
从有序集合中移除一个或者多个元素
template.opsForZSet().range("zset1",0,-1)//结果:[zset-1, zset-2, zset-3, zset-4, zset-5, zset-6]
template.opsForZSet().remove("zset1","zset-6")//结果:1
template.opsForZSet().range("zset1",0,-1)//结果:[zset-1, zset-2, zset-3, zset-4, zset-5]

返回有序集中指定成员的排名,其中有序集成员按分数值递增(从小到大)顺序排列
template.opsForZSet().range("zset1",0,-1)//结果:[zset-2, zset-1, zset-3, zset-4, zset-5]
template.opsForZSet().rank("zset1","zset-2")//结果:0   //表明排名第一

通过索引区间返回有序集合成指定区间内的成员,其中有序集成员按分数值递增(从小到大)顺序排列
template.opsForZSet().range("zset1",0,-1)//结果:[zset-2, zset-1, zset-3, zset-4, zset-5]
通过分数返回有序集合指定区间内的成员个数
template.opsForZSet().rangeByScore("zset1",0,5)//结果:[zset-2, zset-1, zset-3]
template.opsForZSet().count("zset1",0,5)//结果:3

获取有序集合的成员数,内部调用的就是zCard方法
template.opsForZSet().size("zset1")//结果:6

获取指定成员的score值
template.opsForZSet().score("zset1","zset-1")//结果:2.2
移除指定索引位置的成员,其中有序集成员按分数值递增(从小到大)顺序排列
template.opsForZSet().range("zset2",0,-1)//结果:[zset-1, zset-2, zset-3, zset-4]
template.opsForZSet().removeRange("zset2",1,2)//结果:2
template.opsForZSet().range("zset2",0,-1)//结果:[zset-1, zset-4]

遍历zset
Cursor<ZSetOperations.TypedTuple<Object>> cursor = template.opsForZSet().scan("zzset1", ScanOptions.NONE);
while (cursor.hasNext()){
   ZSetOperations.TypedTuple<Object> item = cursor.next();
   System.out.println(item.getValue() + ":" + item.getScore());
}
zset-1:1.0
zset-2:2.0
zset-3:3.0
zset-4:6.0

 

redis过期策略:定期删除(每隔100ms随机获取设置过期时间的key,如果过期就删除)+惰性删除(当查询key时,判断是否设置过期时间,如果设置过期时间判断是否过期,过期就删除)

redis内存淘汰机制:LRU算法,6种,1.noeviction:当内存满了,新增key时报错;2.allkeys-lru:删除最近使用最少的key;3.allkeys-random:随机删除key;4.volatile-lru:删除设置过期时间使用最少的key;5.volatile-random:随机删除设置过期时间的key;6.volatile-ttl:删除最早过期时间的key

redis持久化的方式:AOF(日志文件操作追加)、RDB(数据快照);

AOF数据不容易丢失,redis.conf中可以设置操作写 如磁盘的频率,每次操作写入一次还是每1s写入一次,保证了数据的安全性;在日志文件没有rewrite,如果执行flushall,可以手动的恢复数据;AOF最多丢失1s的数据,但是效率也降下来了,因为需要频繁的IO操作,AOF数据文件会比RDB的文件大,重启redis时,恢复数据没有RDB快;

RDB持久化数据,对redis的性能影响很小,redis会fork一个子进程来进行IO操作,而且操作的频率相对于AOF低;redis重启恢复数据,使用RDB恢复的比较快;RDB数据丢失会比AOF多,redis.conf可以设置数据写入磁盘的频率,比如15分钟一个key发生改变就会把数据写入磁盘,如果突然宕机,丢失的数据比较多;

redis高可用,主从复制,在从节点redis.conf中设置salveof 主节点的IP 主节点的port

redis穿透,查询key发现缓存中没有,去数据库发现也没有,当查询量很大的时候,数据库可能崩溃;解决办法使用布隆过滤器或者把查询的空结果也保存到redis中;

redis雪崩,缓存层出现问题,大量的redis失效,导致很多查询打到了数据库上;缓存层出现问题可以使用集群模式保证缓存的高可用,当大量缓存失效时,可以预加载数据,设置不同的失效时间,让数据不会密集的失效;

redis与数据库双写一致:当查询数据时,redis没有数据,则查数据库并把结果缓存到redis中;保存数据的时候,入库然后把数据存入数据库;

redis并发竞争问题,使用分布式锁,可以使用zk或者redis实现;redis实现,就是使用setnx来设置key与value,该方法当key存在时返回0,不会进行创建,当key不存在时,返回1表示创建成功获得锁;java实现如下

redisTemplate.opsForValue().setIfAbsent(key, uuid, 2, TimeUnit.MINUTES);uuid是保证这个key不会被别的线程删除,不会被别人乱删锁,设置过期时间保证不会死锁,锁可以被解开;

使用lua脚本,保证不会被乱解锁,

if redis.call('get',KEYS[1]) == ARGV[1] then
    return redis.call('del',KEYS[1])
else
    return 0
end

redisTemplate.execute(lua脚本, key, uuid);

不建议使用setnx加锁,因为不是原子操作,只能设置值不能设置过期时间, expire(key, seconds):设置key-value的有效期为seconds秒。 如果发生问题没有设置过期时间就会死锁,也不建议直接使用delete解锁,因为会乱删除锁,要使用uuid的标识进行判断删除

 

redis也可以使用redisson来实现分布式锁

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部