简述对Redis的理解
redis是一个基于内存的高性能Key-Value数据库。redis定位是缓存, 提高数据读写速度, 减轻对数据库存储与访问压力。
优点:
读写速度快
支持丰富的数据类型
对数据有可拓展性和高可用性
单线程操作,每个操作都是原子操作,没有并发相关问题
缺点:
ACID处理非常简单,不支持事务回滚
无法做太复杂的关系数据库模型
应用场景有:
数据缓存
会话缓存
时效性数据
访问频率
计数器
社交列表
记录用户判定信息
交集,并集,差集
热门列表与排行榜
最新动态
消息队列
分布式锁
redis常用命令
String命令
hash命令
list命令
set命令
zset命令
全局命令
redis key的设计与类型选用
类型选用:
一般情况
1.如果要排序选用zset
2.如果数据是多个且允许重复选用list
3.如果数据是多个且不允许重复选用set
4.剩下的使用string
特殊情况
有些公司约定: 所有的redis的key跟value都使用字符串(排除使用zset场景)
1.如果要排序选用zset
2.剩下的使用string
java list — redis list
java list ---- redis JSON.toJsonString(list)
java set — redis set
java set ----redis JSON.toJsonString(set)
特殊情况的优点
java操作中如果使用各种类型: list set 其他的 操作redis时需要明确指定的泛型, 麻烦
所以有些公司统一规范, 统一使用字符串, 减少泛型操作
key的设计:
1:唯一性
2:可读性
设计keyvalue 缓存用户收藏文章列表
key value
article_favor:uid1 [1,2,3,4]
article_favor:uid2 [1,2,3,4]
3:灵活性
4:时效性
redis持久化策略-RDB AOF
RDB
1.RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
Snapshotting(快照)可以配置设置自动做快照持久化方式.我们可以配置redis在n秒内如果超过m个key就修改自动做快照.
Snapshotting设置:
save 900 1 #900秒内如果超过1个Key被修改则发起快照保存
save 300 10 #300秒内如果超过10个key被修改,则发起快照保存
save 60 10000
AOF
2.append-only file (缩写aof)的方式,由于快照方式是在一定时间间隔做一次,所以可能发生reids意外down的情况就会丢失最后一次快照后的所有修改的数据。
AOF比快照方式有更好的持久化性,是由于在使用aof时,redis会将每一个收到的写命令都通过write函数追加到命令中,当redis重新启动时会重新执行文件中保存的写命令来在内存中重建这个数据库的内容。这个文件在bin目录下:appendonly.aof
aof不是立即写到硬盘中,可以通过配置文件修改强制写到硬盘中.
aof设置:
appendonly yes //启动aof持久化方式有三种修改方式
#appendfsync always//收到命令就立即写到磁盘,效率最慢.但是能保证完全的持久化
#appendfsync everysec//每秒写入磁盘一次,在性能和持久化方面做了很好的折中
#appendfsync no //完全以依赖os 性能最好,持久化没保证
https://baijiahao.baidu.com/s?id=1654694618189745916&wfr=spider&for=pc
Redis内存淘汰机制及过期Key处理
Redis内存淘汰机制
Redis内存淘汰机制是指当内存使用达到上限(可通过maxmemory配置,0为不限制,即服务器内存上限),根据一定的算法来决定淘汰掉哪些数据,以保证新数据的存入。
常见的内存淘汰机制分为四大类:
1. LRU:LRU是Least recently used,最近最少使用的意思,简单的理解就是从数据库中删除最近最少访问的数据,该算法认为,你长期不用的数据,那么被再次访问的概率也就很小了,淘汰的数据为最长时间没有被使用,仅与时间相关。**
**
2. LFU:LFU是Least Frequently Used,最不经常使用的意思,简单的理解就是淘汰一段时间内,使用次数最少的数据,这个与频次和时间相关。
3. TTL:Redis中,有的数据是设置了过期时间的,而设置了过期时间的这部分数据,就是该算法要解决的对象。如果你快过期了,不好意思,我内存现在不够了,反正你也要退休了,提前送你一程,把你干掉吧。
4. 随机淘汰:生死有命,富贵在天,是否被干掉,全凭天意了。
通过maxmemroy-policy可以配置具体的淘汰机制,看了网上很多文章说只有6种,其实有8种,可以看Redis5.0的配置文件,上面有说明:
1. volatile-lru -> 找出已经设置过期时间的数据集,将最近最少使用(被访问到)的数据干掉。
2. volatile-ttl -> 找出已经设置过期时间的数据集,将即将过期的数据干掉。
3. volatile-random -> 找出已经设置过期时间的数据集,进行无差别攻击,随机干掉数据。
4. volatile-lfu -> 找出已经设置过期时间的数据集,将一段时间内,使用次数最少的数据干掉。
5. allkeys-lru ->与第1个差不多,数据集从设置过期时间数据变为全体数据。
6. allkeys-lfu -> 与第4个差不多,数据集从设置过期时间数据变为全体数据。
7. allkeys-random -> 与第3个差不多,数据集从设置过期时间数据变为全体数据。
8. no-enviction -> 什么都不干,报错,告诉你内存不足,这样的好处是可以保证数据不丢失,这也是系统默认的淘汰策略。
Redis过期Key清除策略
Redis中大家会对存入的数据设置过期时间,那么这些数据如果过期了,Redis是怎么样把他们消灭掉的呢?我们一起来探讨一下。下面介绍三种清除策略:
惰性删除:当访问Key时,才去判断它是否过期,如果过期,直接干掉。这种方式对CPU很友好,但是一个key如果长期不用,一直存在内存里,会造成内存浪费。
定时删除:设置键的过期时间的同时,创建一个定时器,当到达过期时间点,立即执行对Key的删除操作,这种方式最不友好。
定期删除:隔一段时间,对数据进行一次检查,删除里面的过期Key,至于要删除多少过期Key,检查多少数据,则由算法决定。举个例子方便大家理解:Redis每秒随机取100个数据进行过期检查,删除检查数据中所有已经过期的Key,如果过期的Key占比大于总数的25%,也就是超过25个,再重复上述检查操作。
Redis服务器实际使用的是惰性删除和定期删除两种策略:通过配合使用这两种删除策略,可以很好地在合理使用CPU和避免浪费内存之间取得平衡。
JAVA操作Redis
添加依赖
<dependencies>
<!--Jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
测试类Jedis
public class Test {
public static void main(String[] args) {
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
System.out.println(jedis);
//String
System.out.println("String---------------");
//存入键值对
jedis.set("age","18");
//把值递增1
jedis.incr("age");
//根据键取出值
System.out.println("递增后:"+jedis.get("age"));
//把值递减1
jedis.decr("age");
System.out.println("递减后:"+jedis.get("age"));
//存入键值对,timeout表示失效时间,单位s
jedis.setex("age",60,"25");
//根据键删除键值对
jedis.del("age");
//Hash
System.out.println("Hash---------------");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("name","James");
hashMap.put("sex","1");
//存入一个hash对象
jedis.hmset("user",hashMap);
//根据hash对象键取去值
System.out.println(jedis.hmget("user", "name"));
//判断hash对象是含有某个键
System.out.println(jedis.hexists("user", "name"));
//根据hashkey删除hash对象键值对
jedis.hdel("user","sex");
//List
System.out.println("List---------------");
//往列表右边添加数据
jedis.rpush("list","1","2","3");
//往列表左边添加数据
jedis.lpush("list","0");
//范围显示列表数据,全显示则设置0 -1
List<String> list = jedis.lrange("list", 0, -1);
list.forEach(System.out::println);
//弹出列表最左边的数据
jedis.lpop("list");
//弹出列表最右边的数据
jedis.rpop("list");
jedis.lrange("list", 0, -1).forEach(System.out::println);
//获取列表长度
System.out.println(jedis.llen("list"));
//Set
System.out.println("Set---------------");
//往set集合中添加元素
jedis.sadd("set3","7","8","9");
jedis.sadd("set4","9","10","11");
//列出set集合中的元素
System.out.println(jedis.smembers("set3"));
System.out.println("---------------");
//返回两个set集合的交集
jedis.sinter("set3","set4").forEach(System.out::println);
System.out.println("---------------");
//返回两个set集合的并集
jedis.sunion("set3","set4").forEach(System.out::println);
System.out.println("---------------");
//返回set集合中元素个数
jedis.sdiff("set3","set4").forEach(System.out::println);
//随机弹出集合中的元素
jedis.spop("set4",2);
//ZSet
System.out.println("ZSet---------------");
HashMap<String, Double> map = new HashMap<>();
map.put("a",300.0);
map.put("b",300.0);
map.put("c",300.0);
//存入分数和名称
jedis.zadd("theKing",map);
//偏移名称对应的分数
jedis.zincrby("theKing",700.0,"b");
//按照分数升序输出名称
jedis.zrange("theKing",0,-1).forEach(System.out::println);
//按照分数降序输出名称
jedis.zrevrange("theKing",0,-1).forEach(System.out::println);
//升序返回排名
System.out.println(jedis.zrank("theKing", "b"));
//升序返回排名
System.out.println(jedis.zrevrank("theKing", "b"));
System.out.println(jedis.zcard("theKing"));
//获取redis中所有的key
System.out.println(jedis.keys("*"));
// 4:关闭资源
jedis.close();
pool.destroy();
}
}
综合案例-文章阅读数统计
添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/>
</parent>
<dependencies>
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--集成SpringBoot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
静态页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/jquery/jquery.js"></script>
<script>
/*页面加载完成时,发送异步请求获取阅读量*/
$(function () {
$.get("/article/readNum",{id:1},function (data) {
$("#sp").html(data);
})
})
</script>
</head>
<body>
<span style="color: red" id="sp">0</span>
<br/>
<p>
Redis是一种高级key-value数据库。它跟memcached类似,不过数据可以持久化,而且支持的数据类型很丰富。
</p>
</body>
</html>
后端-控制器
@RestController
@RequestMapping("article")
public class ArticleController {
@Autowired
private IArticleService articleService;
@GetMapping("/readNum")
public Object readNum(Long id){
return articleService.getReadNum(id);
}
}
后端-接口
@Service
public class ArticleServiceImpl implements IArticleService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public int getReadNum(Long id) {
Long readNum = stringRedisTemplate.opsForValue().increment("readNum", 1);
return readNum.intValue();
}
}
#### 后端-接口
```java
@Service
public class ArticleServiceImpl implements IArticleService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public int getReadNum(Long id) {
Long readNum = stringRedisTemplate.opsForValue().increment("readNum", 1);
return readNum.intValue();
}
}