概述
通过增加分布式缓存进行解决并发读写数据库,数据库压力过大导致性能下降
Redis有16个数据库 0-15
数据类型
Redis数据库作为Key/value结构的数据存储系统,为了便于对数据进行管理,提供了多种数据类型,基于指定类型存储项目中产生的数据
常用数据类型
字符串 / 散列 / 列表 / 集合 / 有序集合
Redis版本说明
Redis次版本号(第一个小数点后的数字),偶数为稳定版本,奇数非稳定版本
Redis相关网址
Bootnb 相关:https://www.runoob.com/redis/redis-tutorial.html
Redis 官网:https://redis.io/
源码地址:https://github.com/redis/redis
Redis 在线测试:http://try.redis.io/
Redis 命令参考:http://doc.redisfans.com/
指令
登录Redis服务
//本地登录
redis-cli
//远程登录
redis-cli -h ip -p port -a password
查看Redis信息
info
关闭Redis
//默认持久化保存后关闭
shutdown
//不持久化关闭
shutdown nosave
查看所有Key
keys *
清除数据库数据
//清除当前数据库数据
flushdb
//清除所有数据库数据
flushall
设置Key生命周期
expire key seconds
expire key 10
关闭Key生命周期
Persist key
查看Key生命周期
ttl key
key不存在返回 -2
key没有设置生命周期返回 -1
key设置了生命周期返回具体秒数
Key/Value存储
存
set key value
取
get key
String类型操作
字符串长度支持512M,基于此类型可以实现博客的数字统计,将日志不断的追加到指定key,实现一个分布式自增Id,实现博客的点赞操作
自增/自增步长
//如果key不存在会默认创建并+1
incr key
incrby key 2
自减/自减步长
decr num
decrby num 2
追加
//向尾部追加值,如果键不存在则创建
append key value
value的长度
//返回value的长度,不存在返回0,如果是空串返回也是0
strlen key
设置/获取
//设置多个
mset key1 value1 key2 value2 key3 value3
//获得多个
mget key1 key2 key3
Hash类型
Redis散列类型相当于Java的HashMap,实现原理与HashMap一致,一般用于存取对象信息
设置/获取
hset key field value
hget key field
//多个
hmset key field1 value1 field2 value2
hmget key field1 field2
//获取所有
hgetall key
自增
hincrby key field increment
hincrby key 属性 步长
判断属性是否存在
//没有则返回0 有则返回1
hexists key field
删除属性
hdel key value
只获取key或value
hkeys key
hvals key
List类型
Redis的List类型相当于 LinkedList 其原理是一个双向链表,支持正向/反向/便利操作,插入删除速度快,经常用来实现热销榜最新评论等
在头部添加字符串元素
lpush key "element"
在尾部添加字符串元素
rpush key "value"
从头部移除元素,并返回删除元素
//删除一个
lpop key
从尾部移除元素,并返回删除元素
//删除一个
rpop key
查看list元素
lrange key StrIndex EndIndex
删除元素
del key
特定位置之前或之后添加字符串元素
//在value1之前添加value2
linsert key before "value1" "value2"
//在value1之后添加value2
linsert key after "value1" "value2"
修改指定下标元素
lset key index "value"
lset key 0 "xxx"
删除count个指定value
count > 0 从头部开始删除
count < 0 从尾部开始删除
count = 0 全部删除
lrem key count "value"
lrem key 2 "xxx"
保留指定key的值范围内的数据
ltrim key StrIndex EndIndex
ltrim key 0 -1
返回key对应list的长度
llen key
返回指定下标的元素
lindex key index
lindex key 0
移动
//移除lis1尾部元素并添加到lis2的头部
rpoplpush lis1 lis2
set类型
Redis的Set 与Java的HashSet一致,集合成员都是唯一的不允许出现重复数据
添加元素
sadd key value
获取内容
smembers key
随机移除返回一个元素
spop key
获取个数,返回长度
scard key
移动一个元素到另一个集合
127.0.0.1:6379> sadd internet amoeba nginx redis
(integer) 3
127.0.0.1:6379> sadd bigdata hadopp spark rabbitmq
(integer) 3
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "nginx"
127.0.0.1:6379> smembers bigdata
1) "hadopp"
2) "spark"
3) "rabbitmq"
127.0.0.1:6379> smove bigdata internet rabbitmq
(integer) 1
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "rabbitmq"
4) "nginx"
127.0.0.1:6379> smembers bigdata
1) "hadopp"
2) "spark"
127.0.0.1:6379>
合并集合
sunino key key
Java操作Redis
配置文件说明
xxxx/conf/redis.conf文件
...
# 默认只允许本地链接,如果需要远程链接需要注释
bind 127.0.0.1
# 默认开启保护模式,需要将其关闭该为no
protected-mode yes
Jedis
导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
测试链接
测试是否链接,如果ping返回的值是PONG则表示链接成功
public class TestJedis {
@Test
public void TestConnection(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.auth("密码");
String ping = jedis.ping();
System.out.println(ping);
}
}
字符串练习
@Test
public void TestString() throws InterruptedException {
//建立链接
Jedis jedis = new Jedis("192.168.126.129", 6379);
//存储数据
jedis.set("user1","heJian1");
jedis.set("user2","heJian2");
jedis.set("age","1");
//更新数据
jedis.expire("user1",1); //设置有效时长
jedis.incr("age");
Thread.sleep(1000); //故意睡1秒
//获取数据
String user1 = jedis.get("user1");
String user2 = jedis.get("user2");
String age = jedis.get("age");
System.out.println(user1+":-:-:"+user2+":-:-:"+age);
//释放资源
jedis.close();
}
json数据练习
@Test
public void TestJson(){
//对象数据
Map<String,Object> map = new HashMap();
map.put("name","hejian");
map.put("age","12");
map.put("sex","男");
//转换为Json
Gson gson = new Gson();
String jsonStr = gson.toJson(map);
//建立链接
Jedis jedis = new Jedis("192.168.126.129", 6379);
//设置数据
jedis.set("user",jsonStr);
//获取数据
String user = jedis.get("user");
//转换数据 Json -Map
Map<String,Object> map1 = gson.fromJson(user, Map.class);
System.out.println(map1);
//关闭资源
jedis.close();
}
hashMap数据练习
@Test
public void TestHash(){
//建立链接
Jedis jedis = new Jedis("192.168.126.129", 6379);
//手动存入数据
jedis.hset("user","name","hejian");
jedis.hset("user","age","18");
//获取数据
String name = jedis.hget("user", "name");
String age = jedis.hget("user", "age");
System.out.println(name);
System.out.println(age);
//存入Map
Map<String,String> map = new HashMap<String, String>();
map.put("cat","xiaohong");
map.put("dog","wangwang");
jedis.hset("animal",map);
Map<String, String> animal = jedis.hgetAll("animal");
System.out.println(animal);
//关闭资源
jedis.close();
}
List类型操作
@Test
public void TestList(){
//建立链接
Jedis jedis = new Jedis("192.168.126.129", 6379);
//操作
jedis.lpush("list1","A","B","C");
int len = jedis.llen("list1").intValue();
System.out.println(len);
List<String> list1 = jedis.rpop("list1", len);
System.out.println(list1);
//阻塞式队列
List<String> one = jedis.brpop(40, "list1");
System.out.println(one);
List<String> two = jedis.brpop(40, "list1");
System.out.println(two);
List<String> three = jedis.brpop(40, "list1");
System.out.println(three);
List<String> four = jedis.brpop(40, "list1");
System.out.println(four);
//释放资源
jedis.close();
}
Set类型操作
@Test
public void Testset(){
//建立链接
Jedis jedis = new Jedis("192.168.126.129", 6379);
//操作数据
jedis.sadd("user","1","1","2");
Set<String> user = jedis.smembers("user");
System.out.println(user);
//关闭资源
jedis.close();
}
链接池
配置文件-链接池配置
spring:
jedis: #连接池
pool:
max-idle: 8
max-wait: 0
@Test
public void TestJedisPool(){
JedisPoolConfig config = new JedisPoolConfig();
//链接池最大连接数
config.setMaxTotal(1000);
//设置空闲连接数
config.setMaxIdle(60);
config.setMinIdle(20);
//设置最大阻塞时间
config.setMaxWaitMillis(500);
JedisPool jedisPool = new JedisPool(config, "192.168.126.129", 6379);
Jedis resource = jedisPool.getResource();
resource.set("test","hj");
String test = resource.get("test");
System.out.println(test);
}
Redis Template
依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
</dependencies>
配置文件
spring:
redis:
host: 192.168.126.129
port: 6379
定制RedisTemp对象
RedisTemplate 默认采用JDK的序列化机制,如果不想使用JDK的序列化可以进行定制RedisTemp对象的序列化方式
package com.jt.redis.config;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
//1.构建RedisTemplate对象
RedisTemplate<Object,Object> redisTemplate=new RedisTemplate<>();
//2.设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
//3.定义序列化方式(在这里选择jackson)
Jackson2JsonRedisSerializer redisSerializer= new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper=new ObjectMapper();
//设置要序列化的域(属性)
//any表示任意级别访问修饰符修饰的属性 private,public,protected
objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
//启动输入域检查(类不能是final修饰的)
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
redisSerializer.setObjectMapper(objectMapper);
//4.设置RedisTemplate的序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(redisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(redisSerializer);
//spring规范中假如修改bean对象的默认特性,建议调用一下afterPropertiesSet()
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
package com.jt.redis.pojo;
import java.io.Serializable;
public class Blog implements Serializable {//{"id":10,"title":"redis"}
private static final long serialVersionUID = -6721670401642138021L;
private Integer id;
private String title;
public Blog(){
System.out.println("Blog()");
}
public Blog(Integer id,String title){
this.id=id;
this.title=title;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "Blog{" +
"id=" + id +
", title='" + title + '\'' +
'}';
}
}
@Test
void testJsonOper() throws JsonProcessingException {
ValueOperations valueOperations = redisTemplate.opsForValue();
Blog blog=new Blog(10,"study redis");
valueOperations.set("blog",blog);//序列化
blog=(Blog)valueOperations.get("blog");//反序列化
System.out.println("blog="+blog);
}
StringRedisTemplate
依赖注入
@Autowired
private RedisTemplate redisTemplate;
测试链接
@Test
void TestConnection(){
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
String ping = connection.ping();
System.out.println(ping);
}
字符串操作
@Test
void TestRedisStringOper(){
ValueOperations<String, String> svo = redisTemplate.opsForValue();
svo.set("ip","192.168.126.129");
//有效时长,及单位
svo.set("port","6379",1, TimeUnit.SECONDS);
svo.increment("port");
String ip = svo.get("ip");
String port = svo.get("port");
System.out.println(ip);
System.out.println(port);
}
Set类型操作
@Test
void TestRedisSet(){
SetOperations<String, String> SSO = redisTemplate.opsForSet();
SSO.add("setKey","A","B","C","C");
Set<String> setKey = SSO.members("setKey");
System.out.println(setKey);
}
List类型操作
@Test
void TestList(){
ListOperations<String, String> sslo = redisTemplate.opsForList();
//存数据
sslo.leftPush("listkey","100");
sslo.leftPushAll("listkey","100","200","300");
sslo.rightPush("listkey","800");
//查数据
List<String> listkey = sslo.range("listkey", 0, -1);
System.out.println(listkey);
//取数据
String listkey1 = sslo.leftPop("listkey");
System.out.println(listkey1);
//查数据
List<String> listkey2 = sslo.range("listkey", 0, -1);
System.out.println(listkey2);
}
Hash类型操作
@Test
void TestHash(){
HashOperations<String, Object, Object> sooho = redisTemplate.opsForHash();
Map<String,String> user = new HashMap();
user.put("name","hejian3");
user.put("age","16");
sooho.putAll("user",user);
sooho.put("user","sex","男");
Object name = sooho.get("user", "name");
System.out.println(name);
Map<Object, Object> user1 = sooho.entries("user");
System.out.println(user1);
}
案例
package com.tedu;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.Jedis;
import java.util.UUID;
@SpringBootTest
public class SingleSignOn {
static String token;
void doGetResource(){
if(token==null){
System.out.println("没有token,请登录");
return;
}
Jedis jedis = new Jedis("192.168.126.129", 6379);
String user = jedis.get(token);
jedis.close();
if(user==null){
System.out.println("登录超时");
return;
}
System.out.println("访问资源成功");
}
void doLogin(String name,String password){
if("root".equals(name)&&"123".equals(password)){
System.out.println("登录成功");
String token = UUID.randomUUID().toString();
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.set(token,name);
jedis.expire(token,1);
jedis.close();
SingleSignOn.token = token;
return;
}
System.out.println("账号密码不对");
}
@Test
void test(){
SingleSignOn s = new SingleSignOn();
s.doGetResource();
s.doLogin("root","123");
s.doGetResource();
}
}
数据持久化操作
为了解决Redis因故障(宕机/断电等等)导致不可用,从而数据丢失问题
持久化的方式
RDB
概述
Rdb方式可以通过手动(save-阻塞式 , bsave-异步)或周期性方式保存redis中的key/value的一种机制,Rdb方式一般为redis的默认数据持久化方式,系统启动时会自动开启这一种方式的持久化机制
配置
# 这里表示每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前redis内存中完整的数据快照,这个操作也被称之为snapshotting(快照)。
save 60 1000
# 持久化 rdb文件遇到问题时,主进程是否接受写入,yes 表示停止写入,如果是no 表示redis继续提供服务。
stop-writes-on-bgsave-error yes
# 在进行快照镜像时,是否进行压缩。yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间。
rdbcompression yes
# 一个CRC64的校验就被放在了文件末尾,当存储或者加载rbd文件的时候会有一个10%左右的性能下降,为了达到性能的最大化,你可以关掉这个配置项。
rdbchecksum yes
# 快照的文件名
dbfilename dump.rdb
# 存放快照的目录
dir /var/lib/redis
模拟场景
场景一 :使用shutdown 关机
结果: 会保存数据(因为shutdown关机 是一种安全退出模式,在退出之前会将内存中的数据立即生成一份rdb快照)
场景二:kill线程
结果:不会保存
场景三:手动保存后再kill线程
结果:会保存
区别
Save:
执行一个同步保存操作,将当前Redis实例的所有数据快照以Rdb文件形式保存在磁盘
BGsave:
执行之后立即返回Ok,然后Redis会出一个新子进程用来负责将数据保存到磁盘
优点
1、 Rdb 会生成多个数据文件,每个数据文件都代表某一时刻的redis的数据,适合做冷备,可以将这种完整文件发送到远程云服务,按预定的备份策略来定期备份redis中的数据
2、Rdb 对Redis对外提供的读写服务影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程操作数据到磁盘
3、相对于AOF持久化机制,RDB数据文件来重启和恢复,速度更快
AOF
概述
通过记录操作日志的方式,记录redis数据的一种持久化机制,默认该机制是关闭的
配置
# 是否开启AOF,默认关闭
appendonly yes
# 指定 AOF 文件名
appendfilename appendonly.aof
# Redis支持三种刷写模式:
# appendfsync always #每次收到写命令就立即强制写入磁盘,类似MySQL的sync_binlog=1,是最安全的。但该模式下速度也是最慢的,一般不推荐使用。
appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做平衡,推荐该方式。
# appendfsync no #完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,不推荐。
#在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。
#设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes
no-appendfsync-on-rewrite yes
#当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
#当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。
auto-aof-rewrite-min-size 64mb
如果理解AOF方式的rewrite操作?
redis中的数据其实是有限的,很多数据可能会自动过期,可能会被用户删除,可以会被redis用缓存清除算法清理掉,也就是说redis的数据是不断淘汰旧的,只有常用的一部分才会保留在redis内存中,所以写日志还停留在AOF中,AOF日志文件就一个,所以会不断的膨胀
所以AOF会自动每个一段时间做rewrite操作,例如日志中有100w的操作而redis中只有10w条数据,AOF就会基于当前10w条数据构建一套新的日志,覆盖之前老的日志,确保AOF日志不会过大
优点
1、AOF可以更好的保证数据不丢失,一般AOF每隔一秒,通过后台线程执行一次fsync操作,最多丢失一秒的数据
2、AOF日志文件通过append-only模式写入,所以没有任何磁盘寻址开销,写入性能非常高,文件不易破损
3、AOF日志文件过大的时候,出现后台重写操作,也不会影响客户端的读写,因为在rewrite log时,会对其中的值导出压缩,创建一份需要恢复数据的最小日志,在创建日志文件的时候,老日志还是正常写入,当新的merge后的日志文件ready时在交换新老日志
4、AOF日志通过易读的方式进行记录,该特性非常适合做灾难恢复,比如不小心执行了flushall操作,只要这个时候还没有重写就可以拷贝AOF文件把 flushall操作删除在将AOF放回即可
备份方案
1、既要使用rdb也要使用aof ,rdb做冷备,aof保证数据丢失量达到最小
事务
https://blog.csdn.net/maitian_2008/article/details/119480944
主从 - 哨兵 - 集群
https://blog.csdn.net/maitian_2008/article/details/119482237