基础篇
认识NoSQL
键值数据库
NoSql数据库
SQL | NoSQL | |
---|---|---|
结构化 约束 | 非结构化 | 数据结构 |
关联的 | 无关联的 | 数据关联 |
SQL查询 | 非SQL | 查询方式 |
ACID特性 | BASE | 事务特性 |
磁盘存储 | 内存存储 | 存储方式 |
垂直 垂直扩展性意味着通过增加单个服务器的计算能力来应对增加的负载。通常涉及到升级硬件,比如增加处理器的数量、提高内存容量、增加存储空间等。也就是说,SQL数据库在扩展时更倾向于“增强一台服务器”的性能,以便处理更多的数据或更高的负载。 | 水平 水平扩展性指的是通过增加更多的服务器节点来处理更多的数据和请求。NoSQL数据库设计时考虑了分布式架构,允许通过将数据分片(sharding)分布到多个服务器上,实现数据库的扩展。这种扩展方式可以使系统通过增加更多的服务器来承载更大的工作负载。 | 扩展性 |
1 数据结构固定 2 相关业务对数据安全性一致性要求较高 | 1 数据结构不固定 2 对一致性、安全性要求不高 3 对性能要求 | 使用场景 |
非结构化:
- 键值类型Redis
- 文档类型MongoDB
- 列类型HBase
- Graph类型Neo4j
认识Redis
Remote Dictionary Server 远程词典服务器,基于内存的键值型NoSQL数据库
特征:
- 键值型,value支持多种不同数据结构
- 单线程,每个命令具备原子性
- 低延迟,速度快
- 基于内存
- IO多路复用
- 良好的编码
- 支持数据持久化
- 定期将数据从内存持久化到磁盘
- 支持主从集群、分片集群
- 多语言客户端
数据结构
五种基本类型
- String
- Hash
- List
- Set
- SortedSet
三种特殊类型
- GEO
- BitMap
- HyperLog
通用命令
help command,例如help keys
- KEYS:查看符合模板的所有key
- KEYS *
- KEYS a*
- 模糊查询,效率慢加上redis还是单节点,容易阻塞,不建议生产环境使用
- DEL:删除指定key
- MSET:批量添加key value
- EXISTS:查看key是否存在
- EXPIRE:给key设置有效期,有效期到期key会被自动删除
- redis是内存存储,定时清除防止越存越多,比如验证码5分钟
- TTL:查看一个key剩余有效期
- -1永久,-2删除
String
value是字符串,但可以根据字符串格式分为3类:
- string
- int 可以做自增自减
- float 可以做自增自减
底层都是字节数组形式存储,只是编码方式不同。字符串类型最大空间不能超过512M
命令
- SET
- 添加或修改键值对
- GET
- 根据key获取value
- MSET
- 批量添加键值对
- MGET
- 根据key获取value
- INCR
- 整形key自增1
- INCRBY
- 整形key自增并指定步长
- incrby key increment
- INCRBYFLOAT
- 浮点类型key自增并指定步长
- incrbyfloat key increment
- SETNX
- 添加键值对,前提是key不存在才执行
- SETEX
- 添加键值对并指定有效期
- setex key seconds value
Key的层级格式
redis没有mysql的table这种,如果冲突了怎么办,比如客户和商品id都是1
key的结构:
允许多个单词形成层级结构,之间用:隔开
- 项目名:业务名:类型:id
例如:
set test:user:1 ‘{“id”:1, “name”:“Jack”, “age”: 21}’
set test:product:1 ‘{“id”:1, “name”:“Jack”, “age”: 21}’
Hash
hash类型,也叫散列,其value是一个无序字典,类似于java中的hashmap结构
String结构是将对象序列化为json字符串后存储,修改某个字段很不方便
hash结构将对象的每个字段独立存储,可以针对单个字段做curd
也就是
KEY VALUE
⬇
KEY VALUE(field value)
命令:
- HSET key field value
- HGET key field
- HMSET
- HMGET
- HGETALL
- HKEYS
- HVALS
- HINCRBY
- HSETNX
List
list类型与java的linkedlist类似,可以看作一个双向链表结构,既可以正向检索也可以反向检索
特征也类似:
- 有序
- 元素可以重复
- 插入和删除快
- 查询速度一般
使用场景:有序数据,如朋友圈点赞
命令:
- LPUSH key element
- LPOP key
- RPUSH key element
- RPOP key
- LRANGE key star end
- LRANGE users 1 2
- 取第2和第3个,也就是1-2
- BLPOP BRPOP
- 和POP类似,没有元素时候要设置等待时间,不是直接返回nil,单位秒
- BLPOP users2 5
- 是一个阻塞式的pop。如果列表为空,它会阻塞(等待)直到列表中有新的元素被插入,或者直到达到指定的超时时间。
- 0表示无限期等待直到列表有元素
如何用List模拟栈
- 入口和出口同一边
- LPUSH和LPOP这种
如何用List模拟队列
- 入口和出口不同边
- LPUSH和RPOP这种
如何用List模拟阻塞队列
- 入口和出口不同边
- 出队时用BLPOP这种
Set
和java中的hashset类似,可以看作value为null 的hashmap。因为也是一个hash表,和hashset特征类似:
- 无序
- 元素不可重复
- 查找快
- 支持交集、并集、差集等
命令:
单个集合
- SADD key member
- 向set添加一个或多个元素
- SREM key member
- 移除set指定元素
- SCARD key
- 返回set元素个数
- SISMEMBER key member
- 判断一个元素是否存在于set中
- SMEMBERS
- 获取set中的所有元素
多个集合
- SINTER key1 key2
- key1和key2 的交集
- SDIFF key1 key2
- 差集
- SUNION key1 key2
- 并集
SortedSet
可排序的set集合,与java中的treeset类似但底层数据结构差别很大。
sortedset每一个元素带有一个score属性,可以基于属性对元素排序,底层的实现是一个跳表SkipList加hash表
特性:
- 可排序
- 元素不重复
- 查询速度快
使用场景:由于可排序特性,常用来实现排行榜
命令:
- ZADD key score member
- 添加一个或多个元素,如果已经存在则更新score值
- ZREM key member
- 删除元素
- ZSCORE key member
- 获取指定元素score值
- ZRANK key member
- 获取指定元素的排名
- ZCARD key
- 获取元素个数
- ZCOUNT key min max
- 统计score值在给定范围的元素个数
- ZINCRBY key increment member
- 统计指定元素自增,步长指定increment
- ZRANGE key min max
- 按照score排序后,获取指定排名范围的元素
- ZRANGEBYSCORE key min max
- 按照score排序后,获取指定score范围内的元素
- ZDIFF ZINTER ZUNION
- 求差集、交集、并集
升序排名,降序则ZREV
Hash HashMap HashSet
1. HashMap 的数据结构
HashMap
是基于哈希表实现的,底层使用数组加链表或红黑树来解决哈希冲突。假设我们有一个 HashMap<String, Integer>
,添加了以下键值对:
HashMap<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
内部的数据结构可以通过一个简单的 Markdown 表格展示:
HashMap
┌───────────┬───────────────────────────┐
│ Bucket(0) │ │
├───────────┼───────────────────────────┤
│ Bucket(1) │ │
├───────────┼───────────────────────────┤
│ Bucket(2) │ │
├───────────┼───────────────────────────┤
│ Bucket(3) │ [banana, 2] │
├───────────┼───────────────────────────┤
│ Bucket(4) │ │
├───────────┼───────────────────────────┤
│ Bucket(5) │ │
├───────────┼───────────────────────────┤
│ Bucket(6) │ [apple, 1] -> [cherry, 3] │
├───────────┼───────────────────────────┤
│ Bucket(7) │ │
└───────────┴───────────────────────────┘
在这个例子中:
apple
和cherry
通过哈希函数计算出来的哈希值冲突,因此它们被放在了同一个桶(bucket 6)中,并通过链表解决冲突。banana
没有冲突,被存放在 bucket 3。
2. HashSet 的数据结构
HashSet
是基于 HashMap
实现的,底层使用 HashMap
存储元素,只关心 key
,而 value
是一个固定的常量 PRESENT
。假设我们有一个 HashSet<String>
,添加了以下元素:
HashSet<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("cherry");
它的内部结构可以表示为:
HashSet
┌───────────┬──────────────────────┐
│ Bucket(0) │ │
├───────────┼──────────────────────┤
│ Bucket(1) │ │
├───────────┼──────────────────────┤
│ Bucket(2) │ │
├───────────┼──────────────────────┤
│ Bucket(3) │ [banana, PRESENT] │
├───────────┼──────────────────────┤
│ Bucket(4) │ │
├───────────┼──────────────────────┤
│ Bucket(5) │ │
├───────────┼──────────────────────┤
│ Bucket(6) │ [apple, PRESENT] -> │
│ │ [cherry, PRESENT] │
├───────────┼──────────────────────┤
│ Bucket(7) │ │
└───────────┴──────────────────────┘
这里可以看到 HashSet
内部实际上是使用 HashMap
来存储数据,每个元素对应于 HashMap
的 key
,而 value
始终是一个 PRESENT
占位符。
3. 哈希表(Hash Table)数据结构
哈希表的基础结构与 HashMap
类似,主要是用哈希函数将键映射到一个桶数组中。桶中可以通过链表或其他结构解决哈希冲突。我们以 HashMap
为例来简化哈希表的结构:
Hash Table
┌───────────┬──────────────────────────┐
│ Bucket(0) │ │
├───────────┼──────────────────────────┤
│ Bucket(1) │ │
├───────────┼──────────────────────────┤
│ Bucket(2) │ │
├───────────┼──────────────────────────┤
│ Bucket(3) │ [Key: banana, Value: 2] │
├───────────┼──────────────────────────┤
│ Bucket(4) │ │
├───────────┼──────────────────────────┤
│ Bucket(5) │ │
├───────────┼──────────────────────────┤
│ Bucket(6) │ [Key: apple, Value: 1] ->│
│ │ [Key: cherry, Value: 3] │
├───────────┼──────────────────────────┤
│ Bucket(7) │ │
└───────────┴──────────────────────────┘
- 哈希表 中每个桶存储键值对,处理冲突时可以使用链表或红黑树来存储多个冲突项。
Java客户端
- Jedis
- lettuce
- Redisson
- java-redis-client
- vertx-redis-client
Spring Data Redis
整合Jedis和lettuce
Jedis
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
package com.sugon.test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
import redis.clients.jedis.Jedis;
import org.junit.jupiter.api.BeforeEach;
public class JedisTest {
//1.建立连接
private Jedis jedis;
@BeforeEach
void setUp() {
jedis = new Jedis("192.168.136.130", 6379);
jedis.auth("123321");
jedis.select(0);
}
//2.测试String
@Test
void testString(){
String result = jedis.set("name","zhangsan");
System.out.println(result);
String name = jedis.get("name");
System.out.println(name);
}
//3.释放
@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}
}
Jedis本身线程不安全,并且频繁创建和销毁有性能损耗,因此使用jedis连接池代替jedis直连
package com.sugon.util;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class JedisConnectionFactory {
private static final JedisPool jedispool;
static {
//配置连接池
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(8);
jedisPoolConfig.setMaxIdle(8);
jedisPoolConfig.setMinIdle(0);
jedisPoolConfig.setMaxWaitMillis(1000);
jedispool = new JedisPool(jedisPoolConfig,
"192.168.136.130", 6379,1000,"123321");
}
public static Jedis getJedis() {
return jedispool.getResource();
}
}
SpringDataRedis
API | 返回值 | 说明 |
---|---|---|
redisTemplate | 通用命令 | |
redisTemplate.opsForValue() | ValueOperations | String |
redisTemplate.opsForHash() | HashOperations | |
redisTemplate.opsForList() | ListOperations | |
redisTemplate.opsForSet() | SetOperations | |
redisTemplate.opsForZSet() | ZSetOperations |
<!-- Redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
spring:
redis:
host: 192.168.136.130
port: 6379
password: 123321
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 100
RedisTemplate
package com.sugon.redisdemo;
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.RedisTemplate;
@SpringBootTest
class RedisDemoApplicationTests {
//1.注入
@Autowired
private RedisTemplate redisTemplate;
//2.测试
@Test
void testString(){
redisTemplate.opsForValue().set("name","zhangsan");
Object name = redisTemplate.opsForValue().get("name");
System.out.println(name);
}
}
RedisTemplate的RedisSerializer
RedisTemplate存储对象的序列化,也就是SpringDataRedis的序列化方式:
- RedisTemplate可以接收任意Object作为值写入Redis,只不过写入前会把Object序列化为字节形式(序列化器),默认是采用JDK序列化(JdkSerializationRedisSerializer)
缺点:
- 可读性差
- 内存占用较大
改变:
StringRedisSerializer 转string,一般key用
GenericJacksonJsonRedisSerializer 转json,一般value用
<!-- Jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
自定义一个redisconfig
package com.sugon.redisdemo.config;
import org.springframework.beans.factory.annotation.Configurable;
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.RedisSerializer;
import sun.net.www.content.text.Generic;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
//创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
//设置连接工厂
template.setConnectionFactory(connectionFactory);
//拆功能键JSON序列化工具
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
//key和hashkey使用string序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(jsonSerializer);
//value和hashvalue使用json序列化
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
return template;
}
}
也可以存储对象
@Test
void testSaveUser(){
redisTemplate.opsForValue().set("user:100",new User("zhangsan",18));
User o = (User)redisTemplate.opsForValue().get("user:100");
System.out.println(o);
}
总结:自定义serializer实现自动序列化和反序列化
问题:还会存入一个额外的东西:对象类的字节码。会占用空间。
- 为了在反序列化时候知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销
StringRedisTemplate
为了节省内存空间,不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。
但是,没有了对象类,当需要存储java对象时,需要手动完成对象的序列化和反序列化。
StringRedisTemplate类,key和value的序列化方式默认是String方式
@Autowired
private StringRedisTemplate stringRedisTemplate;
//JSON工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testStringTemplate() throws JsonProcessingException{
User user = new User("zhangsan",18);
String json = mapper.writeValueAsString(user);
stringRedisTemplate.opsForValue.set("user:200",json);
String val = stringRedisTemplate.opsForValue().get("user:200");
User user1 = mapper.readValue(val,User.class);
System.out.println(user1);
}
操作Hash类型