NoSQL技术

486 阅读14分钟

NoSQL是什么?

NoSQL(最初表示Non-SQL,后来有人转解为Not only SQL),是对不同于传统的关系型数据库的数据库管理系统的统称。

允許部分資料使用SQL系統儲存,而其他資料允許使用NOSQL系統儲存。其數據儲存可以不需要固定的表格模式以及元数据(metadata),也經常會避免使用SQL的JOIN操作,一般有水平可扩展性的特征。

NoSQL分类

文档存储

MongoDB、CouchDB等 文档存储一般用类似json的格式存储,存储的内容是文档型的。这样也就有机会对某些字段建立索引,实现关系数据库的某些功能。

图数据库

Neo4J、FlockDB等 图形关系的最佳存储。使用传统关系数据库来解决的话性能低下,而且设计使用不方便。

键值对(key‐value)存储

MemcacheDB、Redis等 可以通过key快速查询到其value。一般来说,存储不管value的格式,照单全收。(Redis包含了其他功能)

列存储

Hbase、Cassandra、Hypertable等 顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的IO优势。

xml数据库

BaseX等 高效的存储XML数据,并支持XML的内部查询语法,比如XQuery,Xpath。

Redis入门

Redis(REmote DIctionary Server) 中文译为远程字典服务

概述

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

运行一个Redis的HelloWorld

因为Redis不推荐在Windows系统上使用,所以这里直接使用Linux系统进行安装。

安装步骤

  1. redis.io/ Redis官网 www.redis.cn/ Redis中文网
  2. 官网Download菜单下有下载的教程
  3. 这里我使用的是Ubuntu系统
$ wget http://download.redis.io/releases/redis-6.0.8.tar.gz
$ tar xzf redis-6.0.8.tar.gz
$ cd redis-6.0.8
$ make
  1. 执行以上命令就在Linux上安装好了Redis
  2. 如果是下载的安装包的话,建议放到opt目录下解压,复制命令 mv 文件名 /opt 解压命令 tar -zxvf 文件名
  3. 如果找不到文件路径的话可以通过 ps -ef|grep redis 查看启动进程 然后使用 sudo ls -l /proc/进程号/cwd 查看文件位置
  4. make之前需要安装gcc
  5. 启动redis-server

性能测试

redis-benchmark工具

image.png

使用起来也是比较便捷

$ redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 10000 -t get
$ redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 10000 -t set

连接测试

127.0.0.1:6379> PING

返回PONG证明连接正常

配置文件那些事!

Redis配置文件的自描述文档 raw.githubusercontent.com/antirez/red…

这里简单叙述下常用的配置

配置描述
daemonize noRedis 默认不是以守护进程的方式运行,可以通过该配置项修改,使用 yes 启用守护进程(Windows 不支持守护线程的配置为 no )
port 6379指定 Redis 监听端口,默认端口为 6379
bind 127.0.0.1绑定的主机地址
timeout 300当客户端闲置多长秒后关闭连接,如果指定为 0 ,表示关闭该功能
databases 16设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
dbfilename dump.rdb指定本地数据库文件名,默认值为 dump.rdb
dir ./指定本地数据库存放目录
include /path/to/local.conf指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
io-threads-do-reads yes读请求启用线程
io-threads目前官方建议:4 核的机器建议设置为 2 或 3 个线程,8 核的建议设置为 6 个线程,线程数一定要小于机器核数,尽量不超过8个。

这里之写了部分配置,详细可以到Redis官网查看 我们也可以使用命令来更改配置文件

127.0.0.1:6379> CONFIG SET loglevel "notice"

也可以获取配置文件信息

127.0.0.1:6379> CONFIG GET loglevel 

image.png

Redis配置文件默认设置了16个数据库

我们可以通过select 数据库索引 切换数据库

通过DBSIZE查看数据库大小

默认使用的是0的数据库

通过keys * 命令可以查看Redis当前数据库的全部key

可以通过flushall清空全部数据库的key

也可以通过flushdb清除当前库的key

del key可以清除指定key

auth password认证密码

可以通过官网查看Redis的命令使用方法

redis.io/commands

Redis数据类型

String字符串类型

我们设置一个数值的key

set kaaa 0

可以通过incr命令让其自增

incr kaaa

但是如果使用incr命令 对存储非数值内容的key进行操作的话则会报错

(error) ERR value is not an integer or out of range

incr命令自增也是有极限的 set big 9223372036854775807 后在执行 incr big 就会出现错误 (error) ERR increment or decrement would overflow

在Java中可以对String类进行的操作,在Redis中同样也可以使用,只不过命令和语法不同

127.0.0.1:6379> set str HelloWorld
OK
127.0.0.1:6379> getrange str 0 4
"Hello"
127.0.0.1:6379> setrange str 5 Kuman
(integer) 10
127.0.0.1:6379> get str
"HelloKuman"

我们还可以在设置key时设置过期时间

127.0.0.1:6379> setex str 5 guoqi
OK
127.0.0.1:6379> ttl str
(integer) 0
127.0.0.1:6379> ttl str
(integer) -2
127.0.0.1:6379> ttl str
(integer) -2
127.0.0.1:6379> ttl str
(integer) -2
127.0.0.1:6379> get str
(nil)
127.0.0.1:6379>

还可以直接在key中设置Json格式字符串

127.0.0.1:6379> mset key1 hello key2 world
OK
127.0.0.1:6379> mget key1 key2
1) "hello"
2) "world"
127.0.0.1:6379> mset key1:name hello key1:age 20
OK
127.0.0.1:6379> mget key1
1) "hello"
127.0.0.1:6379> mget key1:age
1) "20"

还可以先获取key值在设置

127.0.0.1:6379> get p1
(nil)
127.0.0.1:6379> getset p1 200
(nil)
127.0.0.1:6379> getset p1 200
"200"

List类型

向List中添加数据

127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> rpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "three"

使用lpush进行头插,使用rpush进行尾插

127.0.0.1:6379> llen list
(integer) 0
127.0.0.1:6379> rpush list 1
(integer) 1
127.0.0.1:6379> rpush list 2
(integer) 2
127.0.0.1:6379> rpush list 3
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> llen list
(integer) 3
127.0.0.1:6379>

可以通过LLEN命令查看List长度

127.0.0.1:6379> LPOP list
"1"
127.0.0.1:6379>

通过LPOP或者RPOP命令弹出最开始或者最尾部的元素

127.0.0.1:6379> rpush list one
(integer) 1
127.0.0.1:6379> rpush list two
(integer) 2
127.0.0.1:6379> rpush list three
(integer) 3
127.0.0.1:6379> rpush list three
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
3) "three"
4) "three"
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
127.0.0.1:6379>

可以通过lrem移除元素 第一个参数为移除数量 第二个为移除的元素内容

Set类型

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

127.0.0.1:6379> sadd key one
(integer) 1
127.0.0.1:6379> sadd key one
(integer) 0
127.0.0.1:6379> smembers key
1) "one"
127.0.0.1:6379> sadd key two
(integer) 1
127.0.0.1:6379> sadd key three
(integer) 1
127.0.0.1:6379> sadd key one
(integer) 0
127.0.0.1:6379> smembers key
1) "one"
2) "three"
3) "two"
127.0.0.1:6379> SREM key two three
(integer) 2
127.0.0.1:6379> smembers key
1) "one"
127.0.0.1:6379>

因为Set是无序不重复集合所以我们在抽奖机制中可以使用改特性。

127.0.0.1:6379> sadd key one two three
(integer) 3
127.0.0.1:6379> srandmember key
"one"
127.0.0.1:6379> srandmember key
"two"
127.0.0.1:6379> srandmember key
"two"
127.0.0.1:6379> srandmember key
"three"
127.0.0.1:6379> srandmember key
"three"
127.0.0.1:6379> srandmember key
"three"
127.0.0.1:6379>

当然也可以通过多个Set集合通过函数来获取交集并集。

Sorted Set类型

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。 但是ZSet会通过一个double类型来记录顺序,如果设置的数值一样则最后一次添加的在上方。

127.0.0.1:6379> zadd key 1 thrww
(integer) 1
127.0.0.1:6379> zadd key 1 three
(integer) 1
127.0.0.1:6379> zrange key 0 -1
1) "three"
2) "thrww"
127.0.0.1:6379> zrange key 0 -1
1) "three"
2) "thrww"
127.0.0.1:6379> zadd key 1 one
(integer) 1
127.0.0.1:6379> zadd key 1.1 two
(integer) 1
127.0.0.1:6379> zrange key 0 -1
1) "one"
2) "two"
127.0.0.1:6379> zadd key 0.9 three
(integer) 1
127.0.0.1:6379> zrange key 0 -1
1) "three"
2) "one"
3) "two"
127.0.0.1:6379> zadd key 0.9 three
(integer) 0
127.0.0.1:6379> zrange key 0 -1
1) "three"
2) "one"
3) "two"
127.0.0.1:6379> zadd key 0.9 thrww
(integer) 1
127.0.0.1:6379> zrange key 0 -1
1) "three"
2) "thrww"
3) "one"
4) "two"
127.0.0.1:6379> zadd key 1.1 three
(integer) 0
127.0.0.1:6379> zrange key 0 -1
1) "thrww"
2) "one"
3) "three"
4) "two"
127.0.0.1:6379>

Hash类型

Key-Map类型

Get Set 数据

127.0.0.1:6379> hset map name kuman
(integer) 1
127.0.0.1:6379> hget map name
"kuman"
127.0.0.1:6379>

获取全部key

127.0.0.1:6379> hkeys map
1) "name"
127.0.0.1:6379>

获取全部key中的值

127.0.0.1:6379> hkeys map
1) "name"
127.0.0.1:6379> hset map old 20
(integer) 1
127.0.0.1:6379> hgetall map
1) "name"
2) "kuman"
3) "old"
4) "20"
127.0.0.1:6379>

Redis特殊数据类型

geospatial 地理位置

我们可以将地理位置的经纬度存入Redis

127.0.0.1:6379> geoadd china 123.497752 41.721028 shenyang
(integer) 1
127.0.0.1:6379> geoadd china 116.408981 39.91132 beijing
(integer) 1
127.0.0.1:6379> geopos china shenyang
1) 1) "123.49775165319442749"
   2) "41.72102741833828077"
127.0.0.1:6379> geopos china beijing
1) 1) "116.40897899866104126"
   2) "39.91132015636852515"
127.0.0.1:6379>

这里存入了一个北京的经纬度和沈阳的经纬度 我们还可以用GEO计算两个地址的直线距离,单位是Km

127.0.0.1:6379> geodist china shenyang beijing
"629518.2488"
127.0.0.1:6379>

甚至可以将经纬度直接转换为GeoHash

127.0.0.1:6379> geohash china beijing
1) "wx4g0cwkmc0"

问题?

假如在中国一共有五个人正在使用我们开发的软件,我们希望使用软件的人可以看到距离自己半径10km之内的用户,我们就可以通过Geo来实现。

127.0.0.1:6379> geoadd china 112.123412 34.135233 user_1
(integer) 1
127.0.0.1:6379> geoadd china 112.123412 34.135233 user_2
(integer) 1
127.0.0.1:6379> geoadd china 60.123412 34.135233 user_3
(integer) 1
127.0.0.1:6379> geoadd china 60.123412 35.135233 user_4
(integer) 1
127.0.0.1:6379> geoadd china 39.123412 35.135233 user_5
(integer) 1
127.0.0.1:6379> georadius china 112.123124 34.123523 10 km
1) "user_1"
2) "user_2"
127.0.0.1:6379>

我们通过输入当前用户的经纬度以及半径大小就可以找到半径10km之内的用户了。

127.0.0.1:6379> georadiusbymember china user_1 10 km
1) "user_1"
2) "user_2"
127.0.0.1:6379>

当然也可以直接通过元素查找。

因为GEO本质是储存在了一个集合中,所以我们可以使用ZREM key member [member...]命令将元素删除

Hyperloglog 基数

Hyperloglog实际能实现的功能,使用Set都可以实现,但是HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

127.0.0.1:6379> PFADD count 7.7.7.7 8.8.8.8 9.9.9.9
(integer) 1
127.0.0.1:6379> PFADD count1 7.7.7.7 8.8.8.8 6.6.6.6
(integer) 1
127.0.0.1:6379> PFCOUNT count
(integer) 3
127.0.0.1:6379> PFCOUNT count1
(integer) 3
127.0.0.1:6379> PFMERGE count count1
OK
127.0.0.1:6379> PFCOUNT count1
(integer) 3
127.0.0.1:6379> PFCOUNT count
(integer) 4
127.0.0.1:6379>

Bitmaps 位图

Redis允许使用二进制数据的Key(binary keys) 和二进制数据的Value(binary values)。Bitmap就是二进制数据的value。Redis的 setbit(key, offset, value)操作对指定的key的value的指定偏移(offset)的位置1或0,时间复杂度是O(1)。

127.0.0.1:6379> setbit key1 0 1
(integer) 0
127.0.0.1:6379> setbit key1 1 0
(integer) 0
127.0.0.1:6379> setbit key1 2 1
(integer) 0
127.0.0.1:6379> bitcount key1 0 2
(integer) 2
127.0.0.1:6379>

bitmap的最大偏移量2^32

127.0.0.1:6379> setbit key1 4294967296 1
(error) ERR bit offset is not an integer or out of range
127.0.0.1:6379> setbit key1 4294967295 1
(integer) 0
(1.73s)

我们可以通过bitMaps统计用户行为或者统计一些其他数据。

Redis事务

Redis单条命令执行是原子性的,但是Redis的事务是不保证原子性的。并且Reids没有事务隔离的概念,但是Redis也是有解决办法的,Reids给我们提供了乐观锁的解决方案。

命令用处
MULTI「放弃执行队列中的命令」,你可以理解为Mysql的回滚操作,「并且将当前的状态从事务状态改为非事务状态」。
DISCARD「事务开始的命令」,执行该命令后,后面执行的对Redis数据类型的「操作命令都会顺序的放进队列中」,等待执行EXEC命令后队列中的命令才会被执行
EXEC执行该命令后「表示顺序执行队列中的命令」,执行完后并将结果显示在客户端,「将当前状态从事务状态改为非事务状态」。若是执行该命令之前有key被执行WATCH命令并且又被其它客户端修改,那么就会放弃执行队列中的所有命令,在客户端显示报错信息,若是没有修改就会执行队列中的所有命令。
WATCH key表示指定监视某个key,「该命令只能在MULTI命令之前执行」,如果监视的key被其他客户端修改,「EXEC将会放弃执行队列中的所有命令」
UNWATCH「取消监视之前通过WATCH 命令监视的key」,通过执行EXEC 、DISCARD 两个命令之前监视的key也会被取消监视

事务使用示例

127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr one
QUEUED
127.0.0.1:6379> incr two
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
127.0.0.1:6379>

乐观锁示例

我们开两个终端来模拟多个用户在同时操作

终端1监视数据

127.0.0.1:6379> watch test
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr test
QUEUED
127.0.0.1:6379>

终端2修改数据

127.0.0.1:6379> incr test
(integer) 1
127.0.0.1:6379>

终端1提交事务

127.0.0.1:6379> watch test
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr test
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379>

可以看到Redis给我们返回了nil,证明提交失败。 即使事务中有某条/某些命令执行失败了, 事务队列中的其他命令仍然会继续执行。

通过Maven导入jar包,pom文件添加

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.6.1</version>
</dependency>

Java链接Reids

package com.kuman.test;
import java.util.Set;
import redis.clients.jedis.Jedis;
public class JredisTest {
	
    public static void main(String[] args) {
            Jedis jedis = new Jedis("192.168.158.130",6379);
            jedis.flushDB();
            jedis.sadd("test", "JRedis测试数据1");
            jedis.sadd("test", "JRedis测试数据2");
            Set<String> set = jedis.smembers("test");
            for (String str : set) {  
                  System.out.println(str);  
            }  
    }
}

out

JRedis测试数据1
JRedis测试数据2

使用事务,其实和我们正常在命令行中使用Reids没什么区别。

package com.kuman.test;
import java.util.Set;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class JredisTest {
	
    public static void main(String[] args) {
            @SuppressWarnings("resource")
            Jedis jedis = new Jedis("192.168.158.130",6379);
            jedis.flushDB();

            Transaction transaction =  jedis.multi();
            try {
                    transaction.sadd("test", "JRedis测试数据1");
                    transaction.sadd("test", "JRedis测试数据2");
                    //提交事务
                    transaction.exec();
            } catch (Exception e) {
                    //放弃事务
                    transaction.discard();
            }

            //输出set中的数据
            Set<String> set = jedis.smembers("test");
            for (String str : set) {  
                  System.out.println(str);  
            } 
            //关闭连接
            jedis.close();
    }
}

我们使用info clients命令可以查看当前连接数量

127.0.0.1:6379> info clients
# Clients
connected_clients:2
client_recent_max_input_buffer:2
client_recent_max_output_buffer:0
blocked_clients:0
tracking_clients:0
clients_in_timeout_table:0
127.0.0.1:6379>

如果我们不关闭连接,并且程序一直处于运行状态连接就会一直被打开。

CONFIG GET maxclients 可以查看最大连接数量

SpringBoot整合Redis

首先创建一个SpringBoot项目,并且在创建时勾选上Redis的各种包。

注意 在 Spring Boot 1.x 版本默认使用的是 jedis ,而在 Spring Boot 2.x 版本默认使用的就是Lettuce。

然后通过查看Jar包的源码可以看到默认配置文件

image.png

双击点开查看源码

image.png

我的配置使用的基本都是默认配置,所以这里我只该了host地址。

在SpringBoot配置文件中配置地址

image.png

然后创建一个测试类进行测试

image.png

image.png

然后让我们用Junit运行一下看看结果。

CONFIG SET protected-mode no可以关闭Reids的保护模式

image.png

可以看到控制台成功的打印出了123456

要是想要详细理解类中的方法的话推荐直接去查看 手册

自定义RedisTemplate

package com.kuman.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
    factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String,
        Object>();
        template.setConnectionFactory(factory);
        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
        Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new
        StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
} 

如果报NoUniqueBeanDefinitionException的话可以使用@Qualifier注解解决

image.png

这样就解决了在库中存储时是16进制的问题

持久化RDB与AOF

RDB(Redis Database)

存储为RDB文件时机

执行save和bgsave命令

配置文件设置save 规则,自动间隔性执行bgsave命令

主从复制时,从库全量复制同步主库数据,主库会执行bgsave

执行flushall命令清空服务器数据

执行shutdown命令关闭Redis时,会执行save命令

适合大规模数据恢复,并且对数据完整性要求不高的情况。fork进程复制数据时会占用一定的内存。

AOF(Append Only File)

配置文件

数据完整性要求较高,对比RDB性能差一点,数据恢复较慢,安全性更高。

############################## APPEND ONLY MODE ###############################
#是否开启AOF,默认关闭(no)
appendonly yes

#指定 AOF 文件名
appendfilename appendonly.aof

#Redis支持三种不同的刷写模式:
#appendfsync always #每次收到写命令就立即强制写入磁盘,是最有保证的完全的持久化,但速度也是最慢的,一般不推荐使用。
#appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,是受推荐的方式。
#appendfsync no     #完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,不被推荐。

#在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。
#设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no
no-appendfsync-on-rewrite no 

#当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
#当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。
auto-aof-rewrite-min-size 64mb

Reids发布订阅