Redis
1.关于Redis的介绍和理解
1.1 数据库的理解和分类
数据分为**关系型数据库**和**非关系型数据库**,关系型数据库有Oracle,MySQL,SqlServer,DB2
我们平时用的mysql就是关系型数据库,是一个二维的表结构,那么什么是非关系型数据库呢?
NOSQL
NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题。
因为web2.0出现,也就是04年开始(7亿)大部分人使用手机上网,像天猫双11,一秒30w的操作量,如果仅是使用关系型数据库,查询和写入东西的话,一个连接只能支持一秒2000个操作左右,这样会导致数据库压力大而项目崩溃!!因此我们就需要需要支持高并发的非关系数据库,而我们的redis就是非关系数据库之一。
1.2 数据库类型的对比优缺点
关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织
优点:
1、易于维护:都是使用表结构,格式一致;
2、使用方便:SQL语言通用,可用于复杂查询;
3、复杂操作:支持SQL,可用于一个表以及多个表之间非常复杂的查询。
缺点:
1、读写性能比较差,尤其是海量数据的高效率读写;
2、固定的表结构,灵活度稍欠;
3、高并发读写需求,传统关系型数据库来说,硬盘I/O是一个很大的瓶颈。
非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合,可以是文档或者键值对等。
优点:
1、格式灵活:存储数据的格式可以是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,使用灵活,应用场景广泛,而关系型数据库则只支持基础类型。
2、速度快:nosql可以使用硬盘或者随机存储器作为载体,而关系型数据库只能使用硬盘;
3、高扩展性;
4、成本低:nosql数据库部署简单,基本都是开源软件。
缺点:
1、不提供sql支持,学习和使用成本较高;
2、无事务处理;
3、数据结构相对复杂,复杂查询方面稍欠。
1.3 什么是Redis以及优缺点
是以key-value形式存储,和传统的关系型数据库不一样.不一定遵循传统数据库的一些基本要求.(非关系型的,分布式的,开源的,水平可拓展的)
Redis定位是缓存, 提高数据读写速度, 减轻对数据库存储与访问压力(重要!!)
redis支持各种不同方式的排序。与memcached(分布式的高速缓存系统)一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
优点:
对数据高并发读写(直接是内存中进行读写的)
对海量数据的高效率存储和访问
对数据的可拓展性和高可用性.
单线程操作,每个操作都是原子操作,没有并发相关问题(redis 6)
缺点:
redis(ACID(事务)处理非常简单)
无法做太复杂的关系数据库模型
优势:
性能极高 – Redis能支持超过 10W次每秒的读写频率。
丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
丰富的特性 – Redis还支持 publish/subscribe(发布/订阅), 通知, key 过期等等特性。
下面是官方的bench-mark数据:
测试完成了50个并发执行100000个请求。
设置和获取的值是一个256字节字符串。
Linuxbox是运行Linux2.6,这是X3320Xeon2.5ghz。
文本执行使用loopback接口(127.0.0.1)。
结果:读的速度是110000次/s,写的速度是81000次/s。
1.4 redis与其他key-value存储对比
相同点:
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis支持多种数据结构,不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
不同点:
- Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
- Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
2.redis的数据类型以及操作
2.1 String类型
Map<String,String> map
类型命令 key 参数数据
基础命令:
- set key value -> 存入键值对 get key -> 根据键取出值
- incr key -> 把值递增1 decr key -> 把值递减1
- del key -> 根据键删除键值对
- setex key timeout value -> 存入键值对,timeout表示失效时间,单位s
- ttl key->可以查询出当前的key还剩余多长时间过期
- setnx key value -> 如果key已经存在,不做操作, 如果key不存在,直接添加
扩展命令:
- incrby key num -> 偏移值
- mset k1 v1 k2 v2 … -> 批量存入键值对
- mget k1 k2 … -> 批量取出键值
- append key ‘value’ -> 原值后拼接新内容
- setnx key value -> 存入键值对,键存在时不存入
- setex key timeout value -> 存入键值对,timeout表示失效时间,单位s
- ttl ->可以查询出当前的key还剩余多长时间过期
- setrange key index value -> 修改键对应的值,index表示开始的索引位置
2.2 hash类型
Mp<string, Map<string, ?>> map
基础命令:
- hset key hashkey hashvalue -> 存入一个hash对象
- hget key hashkey -> 根据hash对象键取去值
- hexists key hashkey -> 判断hash对象是含有某个键
- hdel key hashkey -> 根据hashkey删除hash对象键值对
扩展命令:
- hincrby key hashkey 递增值 -> 递增hashkey对应的值
- hlen key -> 获取hash对象键的数量
- hkeys key -> 获取hash对象的所有键
- hvals key -> 获取hash对象的所有值
- hgetall key -> 获取hash对象的所有数据
2.3 list类型
Map<String, List>
元素允许重复,且有序
基础命令:
- rpush key value -> 往列表右边添加数据
- lrange key start end -> 范围显示列表数据,全显示则设置0 -1
- lpush key value -> 往列表左边添加数据
- lpop key -> 弹出列表最左边的数据
- rpop key -> 弹出列表最右边的数据
- llen key -> 获取列表长度
扩展命令:
- linsert key before/after refVal newVal -> 参考值之前/后插入数据
- lset key index value -> 根据索引修改数据
- lrem key count value -> 在列表中按照个数删除数据
- ltrim key start end -> 范围截取列表
- lindex key index -> 根据索引取列表中数据
应用场景:
用户收藏文章列表:
xxxx_user_articles:uid [aid1, aid2, aid3…]
xxxx_user_articles:1 -》 【aid1,aid2, 。。。。。】
2.4 set类型
元素不允许重复,无序
基础命令:
- sadd key value -> 往set集合中添加元素
- smembers key -> 列出set集合中的元素
- srem key value -> 删除set集合中的元素
- spop key count -> 随机弹出集合中的元素
扩展命令:
- sdiff key1 key2 -> 返回key1中特有元素(差集)
- sdiffstore var key1 key2 -> 返回key1中特有元素存入另一个set集合
- sinter key1 key2 -> 返回两个set集合的交集
- sinterstore var key1 key2 -> 返回两个set集合的交集存入另一个set集合
- sunion key1 key2 -> 返回两个set集合的并集
- sunionstore var key1 key2 -> 返回两个set集合的并集存入另一个set集合
- smove key1 key2 value -> 把key1中的某元素移入key2中
- scard key -> 返回set集合中元素个数
- sismember key value -> 判断集合是否包含某个值
- srandmember key count -> 随机获取set集合中元素
2.5 zset
zset:Map<String, Hashtable<Integer,String>>
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)用于排序!!!
基础命令:
- zadd key score column -> 存入分数和名称
- zrange key start end -> 按照分数升序输出名称
- zrevrange key start end -> 按照分数降序输出名称
- zincrby key score column -> 偏移名称对应的分数
- zrank key name -> 升序返回排名
- zrevrank key name -> 降序返回排名
- zcard key -> 返回元素个数
扩展命令:
- zrangebyscore key min max [withscores] -> 按照分数范围升序输出名称
- zrevrangebyscore key max min [withscores] -> 按照分数范围降序输出名称
- zrem key name -> 删除名称和分数
- zremrangebyscore key min max [withscores] -> 根据分数范围删除元素
- zremrangebyrank key start end -> 根据排名删除元素
- zcount key min max -> 按照分数范围统计个数
2.6 如何选择使用redis的数据类型?
1>如果要排序选用zset
2>如果数据是多个且允许重复选用list
3>如果数据是多个且不允许重复选用set
4>剩下的使用string
3.redis进阶
3.1 高级指令
基础:
- 返回满足的所有键 keys * (可以模糊查询)
- exists 是否存在指定key
- expire 设置某个key的过期时间.使用ttl查看剩余时间
- persist 取消过期时间
- flushdb 清空当前数据库,flushall清空所有数据库
扩展:
- select 选择数据库 数据库为0到15(一共16个数据库) 默认进入的是0数据库
- move [key] [数据库下标] 讲当前数据中的key转移到其他数据库中
- randomkey 随机返回数据库里的一个key
- rename重命名key
- echo 打印名
- dbsize 查看数据库的key数量
- info 获取数据库信息
- config get 实时传储收到的请求(返回相关的配置信息)
- config get * 返回所有配置
3.2 事务
Redis的事务非常简单,
使用方法如下:
首先使用multi方法打开事务,然后进行设置,这时设置的数据会放队列里进行保存.最后用exec执行.把数据依次存储redis中.使用discard方法取消事务.
相当于只是进行了批量的执行,数据并不会回滚,所以没有我们所认为的事务操作!!!
3.3 redis的持久化机制
Redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中中的数据同步到硬盘来保证持久化.
持久化有两种机制:
RDB方式:
snapshotting(快照)默认方式.将内存中以快照的方式写入到二进制文件中.默认为dump.rdb.可以配置设置自动做快照持久化方式.我们可以配置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的方式存储每次执行的指令,当半夜时,用户的访问量少的时候,就会将这些指令写道硬盘中!!
aof设置:
appendonly yes //启动aof持久化方式有三种修改方式
#appendfsync always//收到命令就立即写到磁盘,效率最慢.但是能保证完全的持久化
#appendfsync everysec//每秒写入磁盘一次,在性能和持久化方面做了很好的折中
#appendfsync no //完全以依赖os 性能最好,持久化没保证
当redis挂掉了怎么办呢?
我们重新开启一台服务器,然后将rdb快照的二进制文件(dump.rdb)进行快速恢复最后一秒前的大部分数据,
然后根据使用aof根据appendonly.aof文件,找到最后一次做快照的时间,然后在这个时间之后的aof的写命令,进行执行,实现数据的恢复!!!
3.4 Redis内存淘汰机制及过期Key处理
https://www.cnblogs.com/maguanyue/p/12090414.html
什么是内存淘汰机制?
Redis的内存淘汰机制:
因为redis是基于内存级别的,当redis内存达到使用上限制的时候,就会启动内存淘汰策略来淘汰一些数据,这样来保证新数据的写入!!
内存淘汰机制分为四大类:
- LRU:最近最少使用(以最近一段时间为基准,淘汰使用次数最少的)
- LFU:最不经常使用(以开始到目前的整个时间段未基准,淘汰使用次数最少的)
- TTL:在redis中设置了过期的数据,如果你快过期了,就会被清理掉
- 随机淘汰:随机删除
如何进行配置?
我们可以在安装目录下的bin下面的redis.windows.conf进行配置
# The default is:
\#
\# maxmemory-policy noeviction //默认策略
\#
\# maxmemory-samples 5
通过maxmemroy-policy可以配置具体的淘汰机制,看了网上很多文章说只有6种,其实有8种,可以看Redis5.0的配置文件,上面有说明:
- volatile:找出已经设置过期时间的数据集
- allkeys:全体数据
- volatile-lru -> 找出已经设置过期时间的数据集,将最近最少使用(被访问到)的数据干掉。
- volatile-ttl -> 找出已经设置过期时间的数据集,将即将过期的数据干掉。
- volatile-random -> 找出已经设置过期时间的数据集,进行无差别攻击,随机干掉数据。
- volatile-lfu -> 找出已经设置过期时间的数据集,将一段时间内,使用次数最少的数据干掉。
-
allkeys-lru ->与第1个差不多,数据集从设置过期时间数据变为全体数据。
6. allkeys-lfu -> 与第4个差不多,数据集从设置过期时间数据变为全体数据。
7. allkeys-random -> 与第3个差不多,数据集从设置过期时间数据变为全体数据。 -
no-enviction -> 什么都不干,报错,告诉你内存不足,这样的好处是可以保证数据不丢失,这也是系统默认的淘汰策略。
过期key的删除策略?
提出问题?
当内存充足的时候,不需要进行淘汰时,那么对于过期的key如何进行删除呢?什么时候删除?
惰性删除(懒惰):当去访问key,才去判断去删除,但是会导致空间的浪费(如果一个key长期不使用,会被一直留在内存中)------CPU友好
定时删除:设置键的过期时间的同时,会创建一个定时器,当达到过期时间带你,立即删除------CPU不友好
定期删除:隔一段时间,对数据进行一次检查,删除里面的过期的key
reids使用的策略?
redis实际使用的是惰性删除和定期删除这两种策略,通过配合可以合理运用使用CPU和避免浪费内村之间取得平衡。
4.实际运用
4.1 jedis的基本使用
4.1.1 入门操作
导入依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
普通Api:
@Test
public void testJedisPool() {
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
System.out.println(jedis);
// 4:关闭资源
jedis.close();
pool.destroy();
}
---------------------------------------------
@Test
public void testTool(){
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
最大连接数, 默认8个
config.setMaxTotal(100);
最大空闲连接数, 默认8个
config.setMaxIdle(20);
//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1
config.setMaxWaitMillis(-1);
//在获取连接的时候检查有效性, 默认false
config.setTestOnBorrow(true);
JedisPool pool = new JedisPool(config,"192.168.122.128",6379,5000,"wolfcode");
Jedis j = pool.getResource();
String name = j.get("name");
System.out.println(name);
j.close();
pool.close();
pool.destroy();
}
4.1.1 关于不同数据类型的操作
1.String
public class StringTest {
//String类型:Map<String, String> map
@Test
public void testJedisPool() {
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
/*
set key value -> 存入键值对
get key -> 根据键取出值
*/
System.out.println("-------------get-----set---------------------");
//存入键值对
jedis.set("name", "lin");
//根据键取出值
System.out.println(jedis.get("name"));
//对同一个键进行赋值,之前的值会被覆盖
jedis.set("name", "junjun");
System.out.println(jedis.get("name"));
/*
incr key -> 把值递增1
decr key -> 把值递减1
*/
System.out.println("-------------incr-----decr---------------------");
jedis.set("age", "18");
jedis.incr("age");
System.out.println(jedis.get("age"));
jedis.decr("age");
System.out.println(jedis.get("age"));
System.out.println("-------------del------------------------------");
//del key -> 根据键删除键值对
jedis.del("name");
System.out.println(jedis.get("name"));
/*
setex key timeout value -> 存入键值对,timeout表示失效时间,单位s
ttl key->可以查询出当前的key还剩余多长时间过期
*/
System.out.println("-------------setex-----------ttl-------------------");
jedis.setex("v", 1, "20");
System.out.println(jedis.ttl("v"));
//setnx key value -> 如果key已经存在,不做操作, 如果key不存在,直接添加
System.out.println("-------------setnx------------------------------");
jedis.setnx("n","100");
System.out.println(jedis.get("n"));
// 4:关闭资源
jedis.close();
pool.destroy();
}
}
2.hash
public class hashTest {
//hash类型:Mp<string, Map<string, ?>> map
@Test
public void testJedisPool() {
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
/*
hset key hashkey hashvalue -> 存入一个hash对象
hget key hashkey -> 根据hash对象键取去值
hexists key hashkey -> 判断hash对象是含有某个键
hdel key hashkey -> 根据hashkey删除hash对象键值对
*/
//存入hash对象
jedis.hset("hashMap", "sex","男");
jedis.hset("hashMap", "sex2","女");
//根据hash对象键取去值
System.out.println(jedis.hget("hashMap", "sex"));
//判断hash对象是含有某个键
System.out.println(jedis.hexists("hashMap","sex2"));
//根据hashkey删除hash对象键值对
jedis.hdel("hashMap", "sex2");
System.out.println(jedis.hget("hashMap", "sex2"));
// 4:关闭资源
jedis.close();
pool.destroy();
}
}
3.list
public class listTest {
//list类型:Map<String, List>
@Test
public void testJedisPool() {
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
/*
rpush key value -> 往列表右边添加数据
lrange key start end -> 范围显示列表数据,全显示则设置0 -1
lpush key value -> 往列表左边添加数据
lpop key -> 弹出列表最左边的数据
rpop key -> 弹出列表最右边的数据
llen key -> 获取列表长度
*/
//往列表右边添加数据
jedis.rpush("student2", "俊俊","binbin");
//范围显示列表数据,全显示则设置0 -1
System.out.println(jedis.lrange("student2", 0,-1));
//往列表左边添加数据
jedis.lpush("student2", "bobo");
//弹出列表最左边的数据
jedis.lpop("student2");
System.out.println(jedis.lrange("student2", 0,-1));
//弹出列表最右边的数据
jedis.rpop("student2");
System.out.println(jedis.lrange("student2", 0,-1));
//获取列表长度
System.out.println(jedis.llen("student2"));
// 4:关闭资源
jedis.close();
pool.destroy();
}
}
4.zset
public class zsetTest {
//zset:Map<String, Hashtable<Integer,String>>
//集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)
@Test
public void testJedisPool() {
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
//zadd key score column -> 存入分数和名称
//zrange key start end -> 按照分数升序输出名称
//zrevrange key start end -> 按照分数降序输出名称
jedis.zadd("Score", 1, "wushaung");
jedis.zadd("Score", 2, "paoge");
jedis.zadd("Score", 3, "junjun");
System.out.println(jedis.zrange("Score", 0,-1));
System.out.println(jedis.zrevrange("Score", 0,-1));
// zincrby key score column -> 偏移名称对应的分数
jedis.zincrby("Score", 3, "junjun");
System.out.println(jedis.zrevrange("Score", 0,-1));
/*
zrank key name -> 升序返回排名
zrevrank key name -> 降序返回排名
zcard key -> 返回元素个数
*/
System.out.println(jedis.zrank("Score", "junjun"));
System.out.println(jedis.zrevrank("Score", "junjun"));
System.out.println(jedis.zcard("Score"));
// 4:关闭资源
jedis.close();
pool.destroy();
}
}
5.set
@Test
public void testPool(){
//1.创建jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
//2.从连接池中获取jedis对象(相当于连接了redis服务的一个连接对象,用于操作redis)
Jedis jedis = pool.getResource();
/* 设置密码-----如果连接的redis服务设置了密码,则这里需要进行配置
jedis.auth(密码)
*/
/*
- sadd key value -> 往set集合中添加元素
- smembers key -> 列出set集合中的元素
- srem key value -> 删除set集合中的元素
- spop key count -> 随机弹出集合中的元素
*/
jedis.sadd("hashSet","小米");
System.out.println(jedis.smembers("hashSet"));
jedis.sadd("hashSet","小米");
System.out.println(jedis.smembers("hashSet"));
jedis.srem("hashSet","小米");
System.out.println(jedis.smembers("hashSet"));
jedis.sadd("hashSet","华为","苹果");
System.out.println(jedis.spop("hashSet"));
System.out.println(jedis.smembers("hashSet"));
//4.关闭资源
jedis.close();
pool.destroy();
}
4.2 集成SpringBoot
4.2.1 入门操作
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
基本操作
#application.properties
spring.redis.host=localhost #默认值
spring.redis.port=6379 #默认值
spring.redis.password=admin #根据情况配置
---------------------------------------
@Autowired
private StringRedisTemplate redisTemplate;
@Test
public void testRedisTemplate() {
// 操作string
redisTemplate.opsForValue().xx();
// 操作hash
redisTemplate.opsForHash().xx();
// 操作list
redisTemplate.opsForList().xx();
// 操作set
redisTemplate.opsForSet().xx();
// 操作zset
redisTemplate.opsForZSet().xx();
}
-----------------------------------------
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set
4.2.2 关于不同数据类型的操作
1.String
@SpringBootTest
public class StringTest {
//String类型:Map<String, String> map
@Autowired
private StringRedisTemplate redisTemplate;
@Test
public void testJedisPool() {
System.out.println("set,get存取值");
//存值
redisTemplate.opsForValue().set("a", "hhh");
//取值
System.out.println(redisTemplate.opsForValue().get("a"));
//再次赋值会被覆盖
redisTemplate.opsForValue().set("a", "HHH");
System.out.println(redisTemplate.opsForValue().get("a"));
System.out.println("值的递增和递减");
redisTemplate.opsForValue().set("b", "18");
//值递增1
redisTemplate.opsForValue().increment("b");
System.out.println(redisTemplate.opsForValue().get("b"));
//值递减1
redisTemplate.opsForValue().decrement("b");
System.out.println(redisTemplate.opsForValue().get("b"));
//根据键删除键值
System.out.println("根据键删除键值对");
redisTemplate.delete("b");
System.out.println(redisTemplate.opsForValue().get("b"));
//存入键值对并且设值失效时间
redisTemplate.opsForValue().set("ll", "dfdfdf");
//创建并且设定时间
// redisTemplate.opsForValue().set("aa", "pp", 10, TimeUnit.SECONDS);
//给对应的键值对设置过期时间
redisTemplate.expire("ll", 1, TimeUnit.SECONDS);
//获取剩余时间
System.out.println("获取剩余时间");
System.out.println(redisTemplate.getExpire("ll"));
//取消过期时间
redisTemplate.persist("ll");
System.out.println(redisTemplate.getExpire("ll"));
//存入键值对,如果已经存在则不添加
redisTemplate.opsForValue().setIfAbsent("rr", "哈哈哈哈哈");
System.out.println(redisTemplate.opsForValue().get("rr"));
}
}
2.hash
@SpringBootTest
public class hashTest {
//hash类型:Mp<string, Map<string, ?>> map
@Autowired
private StringRedisTemplate redisTemplate;
@Test
public void testJedisPool() {
//存入一个hash对象
redisTemplate.opsForHash().put("groupMap", "sex", "男");
//根据hash的键去取对应的值
System.out.println(redisTemplate.opsForHash().get("groupMap", "sex"));
//判断hash对象中是否包含某个键值对
System.out.println(redisTemplate.opsForHash().hasKey("groupMap", "sex"));
//根据hash对象去删除某个键值对
redisTemplate.opsForHash().delete("groupMap", "sex");
System.out.println(redisTemplate.opsForHash().get("groupMap", "sex"));
}
}
3.list
@SpringBootTest
public class listTest {
//list类型:Map<String, List>
@Autowired
private StringRedisTemplate redisTemplate;
@Test
public void testJedisPool() {
//往列表右边添加数据
redisTemplate.opsForList().rightPush("Stu", "junjun");
redisTemplate.opsForList().rightPush("Stu", "bobo");
redisTemplate.opsForList().rightPush("Stu", "binbin");
//范围显示数据
System.out.println(redisTemplate.opsForList().range("Stu", 0, -1));
//往列表左边添加数据
redisTemplate.opsForList().leftPush("Stu", "有毒");
System.out.println(redisTemplate.opsForList().range("Stu", 0, -1));
//从左边弹出元素
redisTemplate.opsForList().leftPop("Stu");
System.out.println(redisTemplate.opsForList().range("Stu", 0, -1));
//从右边弹出元素
redisTemplate.opsForList().rightPop("Stu");
System.out.println(redisTemplate.opsForList().range("Stu", 0, -1));
System.out.println(redisTemplate.opsForList().size("Stu"));
}
}
4.zset
@SpringBootTest
public class zsetTest {
//zset:Map<String, Hashtable<Integer,String>>
//集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)
@Autowired
private StringRedisTemplate redisTemplate;
@Test
public void testJedisPool() {
//存入分数和名称
redisTemplate.opsForZSet().add("hello", "hh", 12);
redisTemplate.opsForZSet().add("hello", "HH", 22);
//按照分数升序获取全部排名的名称
System.out.println(redisTemplate.opsForZSet().range("hello", 0, -1));
//按照分数降序获取全部排名的名称
System.out.println(redisTemplate.opsForZSet().reverseRange("hello", 0, -1));
//偏移名称对应的分数,也就是对某个人的分数进行加减
redisTemplate.opsForZSet().incrementScore("hello", "hh", 20);
//按照分数降序获取全部排名的名称
System.out.println(redisTemplate.opsForZSet().reverseRange("hello", 0, -1));
//获取某个人的分数排名位置----升序
System.out.println(redisTemplate.opsForZSet().rank("hello", "hh"));
//获取某个人的分数排名位置----降序
System.out.println(redisTemplate.opsForZSet().reverseRank("hello", "hh"));
//返回元素的个数‘
System.out.println(redisTemplate.opsForZSet().size("hello"));
}
}
5.set
@SpringBootTest
public class SetTest {
@Autowired
private StringRedisTemplate template;
@Test
public void testString(){
//add
template.opsForSet().add("phone","华为");
//members
System.out.println(template.opsForSet().members("phone"));
//del
template.opsForSet().add("phone","小米");
template.opsForSet().remove("phone","小米");
System.out.println(template.opsForSet().members("phone"));
//pop
template.opsForSet().add("phone","苹果","oppo","vivo");
System.out.println(template.opsForSet().pop("phone", 1));
System.out.println(template.opsForSet().members("phone"));
}
}
https://www.runoob.com/redis/redis-partitioning.html)
关于redis的额外了解
1.HyperLogLog
什么是HyperLongLog?
HyperLongLog是一种基数统计的算法
基数?指的是在一个数据集里面,重复的元素为基数
为什么要使用HyperLogLog?
因为当我们计算基数的时候,元素越多耗费的内存也就越多
而当我们使用HyperLogLog算法的时候,可以消耗很小的内存。
因为HyperLogLog不会存储元素本身,在输入时就计算,且计算基数的空间固定很小
2.发布订阅
什么是发布订阅?
Redis发布(pub)订阅(sub)是一种redis客户端之间的信息通讯模式。
发送者通过发送信息到频道之中
订阅者订阅频道去接受信息
注意:每个客户端可以订阅多个频道,一般消息通讯我使用第三方的消息中间件去使用
3.事务
Redis 事务可以一次执行多个命令, 但不是真正意义上的事务,只是进行了批量操作而已!!!
因此执行的批量操作过程中,失败不回进行回滚,也不会导致后续的指令不执行
- 在执行exec命令前,将批量操作进行放入队列缓存
- 执行exec命令后,整个执行不具有原子性,失败的操作也不回滚
- 执行事务时,其他命令不能进行加入事务序列,事务间互不影响
4.脚本
redis脚本使用lua解释器来执行脚本。在redis2.6以后内嵌了Lua环境,执行脚本命令为eval
5.GEO(位置计算)
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。
6.Stream(消息持久化)
关于Stream?
Redis Stream 是 Redis 5.0 版本新增加的数据结构。
为什么会出现Stream?
因为redis的发布订阅,只是实现了消息的发送以及订阅,但是消息无法进行持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。导致数据的丢失且也无法记录历史信息。
Stream的作用?
- 而Redis Stream 提供了消息的持久化和主备复制功能,
- 可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。
7.管道技术(缓冲请求响应)
Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。
为什么需要管道技术?
- 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
- 服务端处理命令,并将结果返回给客户端。
相当于是同步的模式,只有当服务器处理完则才返回响应,当有一次又多个请求的时候需要多次连接redis进行请求,消耗很多时间。
什么是管道技术?
Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。减少redis的连接,直接缓存响应结果,一起返回!!!提高redis的服务的性能。
8.redis分区
什么叫做分区以及为什么需要?
分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。
如果只使用一个redis实例时,其中保存了服务器中全部的缓存数据,这样会有很大风险,如果单台redis服务宕机了将会影响到整个服务。
解决:我们可以采用分片/分区的技术,将原来一台服务器维护的整个缓存,现在换为由多台服务器共同维护内存空间,也就是分区数据的集群,也叫做分片!!!
关于redis集群
参考:(3条消息) Redis集群详解_变成习惯-CSDN博客_redis集群
Redis哨兵(Sentinel)模式 - 简书 (jianshu.com)
集群有三种模式:
- 主从模式
- Sentinel模式
- Cluster模式
关于主从模式理解?
主从模式指的 主数据库:用来写操作 从数据库:用来读操作
当读写操作导致数据变化时会自动将数据同步给从数据库,从数据库负责读取,以及接受主数据库同步过来的数据
一个masetr可以拥有多个slave,但是一个slave只能对应一个master
当某个slave挂了不影响其他slave的读和maseter的读写,重启后将数据从master同步过来
当master挂掉之后,不影响slave的读且不会在slave节点中重新选一个matser,但redis不再提供写服务,matser重启后redis将重新提供写服务
主从数据怎么同步的呢?
首先当从数据库启动后,会有个数据初始化的一个过程。
- 从节点启动,发送sync命令给主节点
- 主节点接受到后,将后台保存的快照(rdb)和缓存命令(aof)发送给从节点
- 从节点接受后,加载快照文件和执行缓存命令
数据复制初始化后,每当主节点接受到写命令都会同步给从节点,从保证数据的一致性!!!
![image.png](https://img-blog.csdnimg.cn/img_convert/b25a48793058c29cb64a1e4b362ef3b7.png#clientId=ube7c06f6-686f-4&from=paste&height=481&id=u204fdc79&margin=[object Object]&name=image.png&originHeight=481&originWidth=1246&originalType=binary&ratio=1&size=162770&status=done&style=none&taskId=u32d81ca7-4107-4cc6-8f17-70307899e13&width=1246)
关于哨兵模式?
为什么需要哨兵模式呢?
主从模式切换的时候,当主服务宕机后,需要手动把一台服务器切换为主服务器,这就需要人工干预,费时费力,造成一段时间内服务不可用,优先考虑哨兵模式!!!
关于哨兵模式的理解?
首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
用文字描述一下**故障切换(failover)**的过程。
1.假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。
2.当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,
3.投票的结果由一个哨兵发起,进行failover操作。
4.切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
5.这样对于客户端而言,一切都是透明的。
当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
网址
Redis——设置密码访问服务端 - !O0O! - 博客园 (cnblogs.com)
/