Redis:NoSQL数据库
NoSQL:
目前市场主流数据存储都是使用关系型数据库。每次操作关系型数据库时都是I/O操作,I/O操作是主要影响程序执行性能原因之一,连接数据库关闭数据库都是消耗性能的过程。关系型数据库索引数据结构都是树状结构,当数据量特别大时,导致树深度比较深,当深度深时查询性能会大大降低。尽量减少对数据库的操作,能够明显的提升程序运行效率。
针对上面的问题,市场上就出现了各种NoSQL(Not Only SQL,不仅仅可以使用关系型数据库)数据库,它们的宣传口号:不是什么样的场景都必须使用关系型数据库,一些特定的场景使用NoSQL数据库更好。
常见NoSQL数据库:
memcached :键值对,内存型数据库,所有数据都在内存中。
Redis:和Memcached类似,还具备持久化能力。
HBase:以列作为存储。
MongoDB:以Document做存储。
Redis:
存储方式是键值对:key: value
Redis的底层是C语言编写的
Redis是把数据存储到内存里;
Redis以slot(槽)作为数据存储单元,每个槽中可以存储N多个键值对。Redis中固定具有16384。理论上可以实现一个槽是一个Redis。每个向Redis存储数据的key都会进行crc16算法得出一个值后对16384取余就是这个key存放的slot位置。
Redis常用命令:
Redis的常用数据类型:
1.String 字符串
2.Hash 哈希表
3.List 列表
4.Set 集合
5.Zset 有序集合
通用操作:
1.Exists 判断key是不是存在,语法:exists key名称, 返回值:成功返回1,失败返回0
2.Expire 设置key的过期时间,单位是秒,语法:expire key 秒数, 返回值:成功返回1,失败返回0
3.Ttl 查看key的过期时间, 语法ttl key,语法:返回值:剩余时间,如果不过期-1,如果数据不存在-2,过期时间单位是秒
4.Persist 删除已有的有效期,语法: persist key
5.Del 根据key删除键值对,可以批量删除,不推荐使用。集群环境下redis不支持批量删除。语法:del key […],返回值:被删除的key的数量
6.Keys* 命令:keys* 查看所有存在的key
7.Flushall 清空redis中所有的键值对,语法flushall
8.Dbsize 返回redis中数据库中的键值对数量
9.Select 切换使用的数据库,Redis默认有16个数据库,编号是0~15。默认连接redis使用的是0号数据库,可以自由切换数据库,select 数据库
字符串值(String)
1.Set:设置指定的key的值,如果key不存在是新增效果,如果key存在是修改效果,键值对是永远存在的;
2.Get:获取key的值
3.Setnx:当且仅当key不存在时才新增,如果key存在,将不修改值;
底层:
setnx具备分布式锁能力。在编写代码时如果调用setnx,时会对代码进行加锁。直到删除该key时会解锁。
setnx();// 加锁
// 代码
del();//解锁。
如果在并发访问时第一个线程setnx()时发现没有指定key会正常向下运行。其他线程在执行setnx()时发现有这个key就会等待,等待第一个线程删除key时才会继续向下执行。
常见的锁
锁:在Java中可以通过锁,让多线程执行时某个代码块或方法甚至类是线程安全的。通俗点说:一个线程访问,别的线程需要等待。
线程锁:同一个应用。多线程访问时添加的锁。synchronized(自动释放)或Lock(手动释放)
进程锁:不同进程(一个进程就是一个应用)需要访问同一个资源时,可以通过添加进程锁进行实现。
分布式锁:在分布式项目中不同项目访问同一个资源时,可以通过添加分布式锁保证线程安全。常见的分布式锁有两种:Redis的分布式锁和Zookeeper的分布式锁(通过调用Zookeeper的API给Zookeeper集群添加一个节点。如果节点能添加继续向下执行,执行结束删除该节点。如果其他线程发现该节点已经添加,会阻塞等待该节点删除才继续向下执行。)。
4.Setex:设置key的存活时间,无论是否存在指定key都能新增,如果存在key覆盖旧值。同时必须指定过期时间。语法:setex key time value
5.Mset:批量设置,一次性设置多个键值对;语法:mset key1 value1 key2 value2 …
6.Mget:批量查询,一次性查询多个key的value;语法:mget key1 key2 …
Hash表
给key中的field设置值;
Hset:语法:hset key field value…
返回值:成功 field数量 失败 0
Hget:获取hash表的key的field的值
Hmset:给key中多个value赋值
Hmget:一次获取多个key的field值
Hvals:获取key中所有field的值
Hgetall:获取所有field和value
Hdel:删除一个key的field
Hkeys:获取hash中所有的field
List 列表
Rpush:向列表末尾插入一个或多个值
Lrange:返回列表中指定区间的值。可以用-1代表末尾,list下标有两种计数方式,
头->尾:0,1,2,3… 尾->头:-1,-2,-3…
Lpush:将一个或多个值插入列表的前面
Llen:获取列表长度
Lrem:删除列表中的元素。Count为整数从左向右删除的数量,负数从右向左删除;
Set集合
set和java中集合一样。不允许重复值,如果插入重复值,后新增返回结果为0。
Sadd:向集合内添加元素,不允许重复
Scard:返回集合元素数量
Smemebers:查看集合中的元素
Srem:删除集合中的元素
SortSet 有序集合
有序集合中每个value都有一个分数(score),根据分数进行排序。同分数的,按照value的asc码升序排列。
Zadd:向有序集合中添加数据
Zrange:返回区间内容,withscore 带分数
Zcard:返回集合中元素的数量
Zrem:删除集合中元素
Redis的持久化策略:
Redis不仅仅是一个内存型数据库,还具备持久化能力。
Redis每次启动时都会从硬盘存储文件中把数据读取到内存中。运行过程中操作的数据都是内存中的数据。
一共包含两种持久化策略:RDB 和 AOF
RDB(Redis DataBase)
rdb模式是默认模式,可以在指定的时间间隔内生成数据快照(snapshot),默认保存到dump.rdb文件中。当redis重启后会自动加载dump.rdb文件中内容到内存中。
用户可以使用SAVE(同步)或BGSAVE(异步)手动保存数据。
可以设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令,可以通过save选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行BGSAVE命令。
例如:
save 900 1
save 300 10
save 60 10000
那么只要满足以下三个条件中的任意一个,BGSAVE命令就会被执行。计时单位是必须要执行的时间,save 900 1 ,每900秒检测一次。在并发量越高的项目中Redis的时间参数设置的值要越小。
服务器在900秒之内,对数据库进行了至少1次修改
服务器在300秒之内,对数据库进行了至少10次修改
服务器在60秒之内,对数据库进行了至少10000次修改。
优点
rdb文件是一个紧凑文件,直接使用rdb文件就可以还原数据。
数据保存会由一个子进程进行保存,不影响父进程做其他事情。
恢复数据的效率要高于aof
总结:性能要高于AOF
缺点
每次保存点之间导致redis不可意料的关闭,可能会丢失数据。
由于每次保存数据都需要fork()子进程,在数据量比较大时可能会比较耗费性能。
AOF(AppendOnly File)
AOF默认是关闭的,需要在配置文件redis.conf中开启AOF。Redis支持AOF和RDB同时生效,如果同时存在,AOF优先级高于RDB(Redis重新启动时会使用AOF进行数据恢复)
AOF原理:监听执行的命令,如果发现执行了修改数据的操作,同时直接同步到数据库文件中,同时会把命令记录到日志中。即使突然出现问题,由于日志文件中已经记录命令,下一次启动时也可以按照日志进行恢复数据,由于内存数据和硬盘数据实时同步,即使出现意外情况也需要担心。
优点
相对RDB数据更加安全。
缺点
相同数据集AOF要大于RDB。
相对RDB可能会慢一些。
开启办法
修改redis.conf中。
appendonly yes 开启aof
appendfilename 设置aof数据文件,名称随意。
Java通过spring-data-redis操作rediis
首先添加spring-boot-starter-data-redis的pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>new_redis_client</artifactId>
<groupId>com.lzl</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>client_data_redis</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.3</version>
</dependency>
</dependencies>
</project>
创建连接redis的配置文件:
spring:
redis:
host: 192.168.142.130
port: 6379
创建RedisTemplate的配置类:
package com.lzl.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class MyDataRedisConfiguration {
/**
* springboot-starter-data-redis 会默认提供一个redisTemplate类型的对象,
* 连接的地址通过配置文件设置,默认localhost:6379
* 默认提供的RedisTemplate的类型是RedisTemplate<Object,Object>
* key,value的类型可以是Object的兼容类型
* 默认的redistemplate提供的序列化工具有固定的配置,
* key是字符串序列化,value是jdk的Serializable提供的序列化工具
* @return
*/
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
//redisConnectionFactory是spring-data-redis提供的连接工厂对象.Template只负责读写,不负责连接
//连接工厂,由springboot-starter-data-redis自动创建,连接的配置由配置文件决定
RedisTemplate<String , Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// 增加序列化工具,改变key和value的序列化方式
// 设置key的序列化器,为字符串序列化器,只处理字符串,且不会通过Serializable实现。
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置value的序列化器,为JSON序列化器。序列化结果是: {"属性名": "属性值", "@class": "包名.类型"}
// 必须依赖jackson
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//设置hash数据的field和value的序列化器
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
创建启动类:
package com.lzl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MdataRedisApp {
public static void main(String[] args) {
SpringApplication.run(MdataRedisApp.class,args);
}
}
创建test测试类:
package com.lzl.test;
import com.lzl.MdataRedisApp;
import com.lzl.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SpringBootTest(classes = {MdataRedisApp.class})
@RunWith(SpringRunner.class)
public class TestDataRedis {
//由configuration创建
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Test
public void testDel(){
//单独删除
redisTemplate.delete("myyUsers");
//批量删除
redisTemplate.delete(Arrays.asList("myUsers2","data-redis-2"));
}
@Test
public void testList2String(){
List<User> users = new ArrayList<>();
users.add(new User("admin",20,"male"));
users.add(new User("admin4",20,"male"));
users.add(new User("admin5",20,"male"));
users.add(new User("admin6",20,"male"));
redisTemplate.opsForValue().set("myUsers2",users);
List<User> myUsers2 = (List<User>) redisTemplate.opsForValue().get("myUsers2");
System.out.println(myUsers2);
}
//不推荐使用
@Test
public void testList(){
redisTemplate.opsForList().rightPushAll("myUsers",
new User("admin",20,"male"),
new User("admin1",20,"male"),
new User("admin2",20,"male"),
new User("admin3",20,"male")
);
List<Object> myUsers = redisTemplate.opsForList().range("myUsers", 0, -1);
System.out.println(myUsers);
}
@Test
public void testHash(){
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put("stu2","name","lzl");
Map<String,String> map = new HashMap<>();
map.put("gender","man");
map.put("age","20");
hash.putAll("stu2",map);
Map<Object, Object> stu = hash.entries("stu2");
System.out.println(stu);
}
@Test
public void testExpire(){
redisTemplate.expire("data-redis-1",50, TimeUnit.SECONDS);
Long expire = redisTemplate.getExpire("data-resis-1");
System.out.println("剩余有效期:"+expire);
//永久保存
redisTemplate.persist("data-redis-1");
}
@Test
public void testGet(){
//因为RedisTemplate泛型是<String,Object>,所以查询结果类型一定是Object
System.out.println(redisTemplate.opsForValue().get("data-redis-1"));
System.out.println(redisTemplate.opsForValue().get("data-redis-2"));
Object value = redisTemplate.opsForValue().get("data-redis-2");
System.out.println(value.getClass().getName());
}
@Test
public void testSet(){
//opsForxxx获取某数据类型的读写访问操作对象
//opsForValue对应的是字符串读写
//opsForList() List列表读写
ValueOperations<String, Object> operations = redisTemplate.opsForValue();
operations.set("data-redis-1","测试spring-data-redis");
operations.set("data-redis-2",new User("张三",20,"男"));
}
}