分布式专题(6)- Nosql & Redis

本篇一句话总结:NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”, 泛指非关系型的数据库,可以理解为SQL的一个有力补充。典型代表有MongDB、 Redis、Memcache等。特别是Redis,可以说是目前最火的Nosql数据库之一。

正文开始:

  • 什么是Nosql?
  • 为什么选择Nosql?
  • 怎么用Nosql?

       上面这几个问题,是每个刚接触 Nosql的人都想知道的。下面小兵综合自己的理解和使用情况,在分布式专题里总结一篇关于  Nosql的内容。全文看完,我们对 Nosql也有一定的了解了。

 

什么是Nosql?

NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”, 泛指非关系型的数据库,可以理解为SQL的一个有力补充。典型代表有MongDB、 Redis、Memcache等。特别是Redis,可以说是目前最火的Nosql数据库之一。

 

为什么选择Nosql?

随着 Internet的快速发展,越来越多的网站、应用系统需要支撑海量数据存储,高并发请求、高可用、高可扩展性等特性要求,传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多难以克服的问题。由此,各种各样的NoSQL数据库作为传统关系型数据的一个有力补充得到迅猛发展。此处推荐阅读《从Mysql到Nosql》和《NoSQL 还是 SQL ?》看看sql的发展历史和常用的几大类Nosql数据库。

无论是关系型数据库还是非关系型数据库,本质都是对数据进行增删改查而已,但Nosql和关系数据库之间还是有一些区别的,如在储存方法、存储结构、存储规范、存储扩展、查询方法、事务、性能上,二者都有较大的差异。对比如下:

  • 储存方法:关系数据库是表格式的,因此存储在表的行和列中。在它们之间关联协作存储很容易,并且提取数据很方便。另一方面,Nosql数据库是一个庞大的组。通常存储在数据集中,就像文档、键值对或图形结构一样。
  • 存储结构:关系数据库对应于结构化数据。数据表预先定义结构(列定义),结构描述数据的形式和内容。这对数据建模至关重要,虽然预定义结构带来了可靠性和稳定性,但修改这些数据更加困难。 Nosql数据库基于动态结构,使用和非结构化数据。由于Nosql数据库是动态结构,因此可以轻松适应数据类型和结构的变化。
  • 存储规范:关系数据库的数据存储为了获得更高的规范性,数据被分段为最小的关系表,以避免重复并实现简化的空间利用。虽然管理非常清楚,但是当单个操作设计为多个表时,数据管理有点麻烦。虽然Nosql数据存储在平面数据集中,但数据通常可以复制。单个数据库很少分开,但作为一个整体存储,因此整个数据块更易于读写。
  • 存储扩展:这可能是两者之间最大的区别,关系数据库是垂直扩展,这意味着要提高处理能力,需要使用速度更快的计算机。由于数据存储在关系表中,因此操作中的性能瓶颈可能涉及多个表,需要通过提高计算机性能来克服。虽然有很大的扩展空间,但最终会达到纵向扩张的上限。 Nosql数据库被扩展,其存储自然分布。您可以通过向资源池添加更多常见数据库服务器来共享负载。
  • 查询方法:关系数据库通过结构化查询语言(我们通常称之为SQL)对数据库进行操作。 SQL支持数据库CURD操作非常强大,是业界的标准用法。 Nosql查询使用非结构化查询语言(UnQl)对块中的数据进行操作,这是非标准的。关系数据库表中的主键的概念对应于存储在Nosql中的文档的ID。关系数据库使用预定义的优化方法(例如索引)来加速查询操作,而Nosql更简单,更准确的数据访问模式。
  • 事务:关系数据库遵循ACID规则(原子性、一致性、隔离、耐久性),而Nosql数据库遵循BASE原则(基本可用虚拟、软/灵活事务)(软状态)、最终一致性)。由于关系数据库中数据的高度一致性,对事务的支持非常好。关系数据库支持对事务原子性的细粒度控制,并且易于回滚事务。 Nosql数据库在CAP中是可选的(一致性、可用性、分区容差),因为基于节点的分布式系统难以完全满足,因此对事务的支持不是很好,尽管也可以使用事务,但不是Nosql的闪点。
  • 性能:关系数据库的数据存储在磁盘中,面对高并发读写性能非常差,面对海量数据,效率非常低。 Nosql存储的格式是键值类型,存储在内存中,非常容易存储,数据一致性较弱。 Nosql不需要sql解析来提高读写性能。

 

怎么用Nosql?

对数据的处理无外乎都是增删改查。本篇主要是介绍Nosql三剑库MongoDb、Memcache和Redis的基本使用。特别是Redis,可以说是目前最火的Nosql数据库,也是面试必问点之一,本篇将花大篇幅对其作出说明。

下面我们通过实例来讲解Memcache、Mongodb即Redis的概念和使用。

 

Memcache

Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。

Memcache的工作流程:

MemCache的工作流程如下:先检查客户端的请求数据是否在memcached中,如有,直接把请求数据返回,不再对数据库进行任何操作;如果请求的数据不在memcached中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到memcached中;每次更新数据库的同时更新memcached中的数据,保证一致性;当分配给memcached内存空间用完之后,会使用LRU(Least Recently Used,最近最少使用)策略加上到期失效策略,失效数据首先被替换,然后再替换掉最近未使用的数据。

Memcache的优点:

  • Memcached可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS(取决于key、value的字节大小以及服务器硬件性能,日常环境中QPS高峰大约在4-6w左右)。适用于最大程度扛量。
  • 支持直接配置为session handle。

Memcache的缺点:

  • 只支持简单的key/value数据结构,不像Redis可以支持丰富的数据类型。
  • 无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失。
  • 无法进行数据同步,不能将MC中的数据迁移到其他MC实例中。
  • Memcache内存分配采用Slab Allocation机制管理内存,value大小分布差异较大时会造成内存利用率降低,并引发低利用率时依然出现踢出等问题。需要用户注重value设计。
  • Memcache默认能接受的key的最大长度是250个字符,单个value的大小被限制在1M byte之内,不适合存储较大的数据。

Memcache的应用场景:

  • 动态系统中减轻数据库负载,提升性能;
  • 做缓存,适合多读少写,大数据量的情况(如人人网大量查询用户信息、好友信息、文章信息等)。

关于Memcache的其它特点和细节,可以查阅《memcached》

Java开发中使用Memcache基本实例,可以参考《Java开发中的Memcache原理及实现》

 

Mongodb

MongoDB是一个跨平台的,基于分布式文件存储的NoSQL数据库,由C++语言编写的。MongoDB是以文档的形式存储数据,数据结构由键值(key:value)对组成,类似JSON。

       MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。MongoDB的结构中,最小的单位为文档(类似MySQL的行),每一个文档用的是BSON形式来存储(类似JSON),文档的上一层为集合(类似MySQL的表),再上一级为库(类似MySQL的数据库)。

mongodb与mysql不同的是,mysql的每一次更新操作都会直接写入硬盘,但是mongo不会,做为内存型数据库,数据操作会先写入内存,然后再会持久化到硬盘中去。具体是在mongodb启动时,专门初始化一个线程不断循环(除非应用crash掉),用于在一定时间周期内来从defer队列中获取要持久化的数据并写入到磁盘的journal(日志)和mongofile(数据)处,当进行CUD操作时,记录(Record类型)都被放入到defer队列中以供延时批量(groupcommit)提交写入,默认的时间周期为90s。

Mongodb的优点:

  • 更高的写负载,MongoDB拥有更高的插入速度。
  • 处理很大的规模的单表,当数据表太大的时候可以很容易的分割表,添加1个新字段不会对旧表格有任何影响,整个过程会非常快速。
  • 高可用性,设置M-S不仅方便而且很快,MongoDB还可以快速、安全及自动化的实现节点(数据中心)故障转移。
  • 查询效率高,MongoDB支持二维空间索引,比如管道,因此可以快速及精确的从指定位置获取数据。MongoDB在启动后会将数据库中的数据以文件映射的方式加载到内存中。如果内存资源相当丰富的话,这将极大地提高数据库的查询速度。
  • 支持持久化,Mongodb做为内存型数据库,数据操作会先写入内存,然后再会持久化到硬盘中去。

Mongodb的缺点:

  • 不支持事务。
  • MongoDB占用内存过大 。
  • MongoDB没有成熟的维护工具。

Mongodb的应用场景

  • 主要解决海量数据的访问效率问题。

关于Mongodb的安装和基本使用,可以查阅《MongoDB入门教程》

Java开发中使用Mongodb的基本实例,可以参考《Java连接mongoDB 并进行增删改查操作》

 

Redis

Redis(Remote Dictionary Server)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。是当前最热门的Nosql数据库之一,也是本篇要主要讲解的内容。以下内容主要是讲解它的安装和基本命令使用,及redis用做数据缓存(商品数据、新闻、热点数据)、单点登录、秒杀、抢购、网站访问排名、应用的模块开发等应用场景。

redis的优势:

  • 运行在内存,性能极高,官方说法Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 支持持久化,即可以将内存中的数据异步写入到硬盘中,同时不影响继续提供服务
  • 支持数据结构丰富(string(字符串),list(链表),set(集合),zset(sorted set - 有序集合))和Hash(哈希类型)

 

Redis安装

1.下载redis安装包

wget http://download.redis.io/releases/redis-3.2.1.tar.gz

2.解压安装包并安装

tar xzf redis-3.2.1.tar.gz
cd redis-3.2.1
make
  • make时报错make: cc: Command not found make: * [adlist.o] Error 127,则需要安装gcc: yum -y install gcc 
  • make时报错fatal error: jemalloc/jemalloc.h: No such file or directory,则需要改命令为:make MALLOC=libc
  • make完后 redis-3.2.1目录下会出现编译后的redis服务程序redis-server,还有用于测试的客户端程序redis-cli,两个程序位于安装目录 src 目录下

3.修改redis为后台运行

vim redis.conf
将daemonize no 改为 daemonize yes,如果不修改,该窗口将无法往下执行操作了
  • redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。

4.启动服务端

cd src
./redis-server ../redis.conf
  • redis的配置文件也是挺重要的内容。可以参考《redis3.2 配置文件详解》

5.启动客户端

./redis-cli
  • 在以上实例中我们连接到本地的 redis 服务。并可执行 PING 命令用于检测 redis 服务是否启动。如果需要在远程 redis 服务上执行命令,同样我们使用的也是 redis-cli 命令:redis-cli -h host -p port

   

6.关闭客户端、服务端

  • 关闭服务端(客户端未连接时): ./redis-cli shutdown
  • 关闭服务端(客户端连接时):shutdown save | nosave , 然后 quit 
  • 关闭客户端:quit,或者 exit,或者 Ctrl + C 

Redis重大版本
Redis借鉴了Linux操作系统对于版本号的命名规则:

  • 版本号第二位如果是奇数,则为非稳定版本(例如2.7、2.9、3.1),如果是偶数,则为稳定版本(例如2.6、2.8、3.0、3.2),目前公司中使用的主流版本是redis3.2。
  • 当前奇数版本就是下一个稳定版本的开发版本,例如2.9版本是3.0版本的开发版本,所以我们在生产环境通常选取偶数版本的Redis。

 

Redis常用命令

Redis是一种键值对(K-V)数据库,Redis能够支持五种数据类型:string(字符串),list(链表),set(集合),zset(有序集合)和Hash(哈希类型),我们先来看下Redis的常用命令,常用命令熟悉了,后面看它实现的功能也就容易理解了。

 

Redis 键命令

Redis是一种键值对(K-V)数据库,它对数据的操作都是基于键,下面我们看看 Redis 键相关的基本命令:

keys pattern 
查找所有符合给定模式( pattern)的 key 。
如 keys * 查找Redis中所有的键,但会影响CPU,且会造成redis锁,注意线上慎用该命令。
Redis在2.8.0版本新增了众望所归的scan操作,从此再也不用担心敲入了keys *, 然后举起双手看着键盘等待漫长的系统卡死了···

exists key 
检查给定 key 是否存在。

type key 
返回 key 所储存的值的类型。(五种类型:string、list、set、zset、hash,key不存在时返回none)

del key
该命令用于在 key 存在时删除 key。

expire key seconds
为给定 key 设置过期时间,以秒计

pexpire key milliseconds 
设置 key 的过期时间以毫秒计。

expireat key timestamp 
expireat 的作用和 expire 类似,都用于为 key 设置过期时间。 不同在于 expireat 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。

pexpireat key milliseconds-timestamp 
设置 key 过期时间的时间戳(unix timestamp) 以毫秒计

ttl key 
以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。( key 不存在时,返回 -2 。 当key 存在但未设置剩余生存时间时,返回 -1)

pttl key 
以毫秒为单位返回 key 的剩余的过期时间。( key 不存在时,返回 -2 。 当key 存在但未设置剩余生存时间时,返回 -1)

persist key 
移除 key 的过期时间,key 将持久保持。

randomkey
从当前数据库中随机返回一个 key 。(不建议使用)

rename key newkey 
修改 key 的名称

renamenx key newkey 
仅当 newkey 不存在时,将 key 改名为 newkey 。

move key db
将当前数据库的 key 移动到给定的数据库 db 当中。

dump key
序列化给定 key ,并返回被序列化的值。(序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。)

restore key ttl serialized-value 
反序列化给定的序列化值,并将它和给定的 key 关联。(参数 ttl 以毫秒为单位为 key 设置生存时间;如果 ttl 为 0 ,那么不设置生存时间)

 

Redis 字符串(string)

Redis字符串(string)类型是最常见到的数据结构,它既可以存储文字(比如“hello world”),又可以存储数字(比如整数10086和浮点数3.14),还可以存储二进制数据(比如10010100),字符串值能存储的最大容量为512M。

实例

127.0.0.1:6379> set strkey hello,string
OK
127.0.0.1:6379> get strkey
"hello,string"

字符串(string)常用命令 

set key value 
设置指定 key 的值。(如果key已存在则覆盖其值)

setnx key value
只有在 key 不存在时设置 key 的值。

get key 
获取指定 key 的值。

getrange key start end 
返回 key 中字符串值的子字符

getset key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

mset key value [key value ...]
同时设置一个或多个 key-value 对。

mget key1 [key2..]
获取所有(一个或多个)给定 key 的值。

setex key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。

psetex key milliseconds value
这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。

strlen key
返回 key 所储存的字符串值的长度。

incr key
将 key 中储存的数字值增一。(若key不存在则生成一个新的key且值为1,若值非数字则报错)

incrby key increment
将 key 所储存的值加上给定的增量值(increment) 。(其它规则同incr key)

decr key
将 key 中储存的数字值减一。(其它规则同incr key)

decrby key decrement
key 所储存的值减去给定的减量值(decrement) 。(其它规则同incr key)

append key value
如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。

 

Redis 列表(list)

Redis列表(list)类型是用来存储多个有序的字符串。在Redis中,可以对列表的两端进行插入(push)和弹出(pop)操作,还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。

Redis 中每个 list 可以存储40多亿个元素(2的32次方-1个)。

实例

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

 列表(list)常用命令 

rpush key value1 [value2] 
在列表中添加一个或多个值,列表不存在时则创建并添加值。

rpushx key value 
为已存在的列表添加值,列表不存在时则不会创建。

lpush key value1 [value2] 
将一个或多个值插入到列表头部,列表不存在时则创建并添加值。

lpushx key value 
将一个值插入到已存在的列表头部,列表不存在时则不会创建。

llen key 
获取列表长度

lindex key index 
通过索引获取列表中的元素,如 lindex key 0 获取列表中的首个元素, lindex key -1 获取列表中的最后一个元素

lset key index value 
通过索引设置列表元素的值

lrange key start stop 
获取列表指定范围内的元素,如 lrange key 0 -1 获取列表中的所有元素

linsert key BEFORE|AFTER value newValue 
在列表的元素前或者后插入元素(newValue),当列表不存在或指定元素(value)不存在于列表中时不执行任何操作。

ltrim key start stop 
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

lrem key count value 
根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。如count为0,则移除表中所有与 VALUE 相等的值;count不为0,则从头或尾起移除|count|个与value相等的元素。

lpop key 
移出并获取列表的第一个元素

blpop key1 [key2 ] timeout 
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

rpop key 
移出并获取列表的最后一个元素

rpoplpush source destination 
移除列表的最后一个元素,并将该元素添加到另一个列表头部并返回。如果destination不存在则创建并添加。

brpop key1 [key2 ] timeout 
移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

brpoplpush source destination timeout 
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它;如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

 

Redis 哈希(hash)

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。

Redis 中每个 hash 可以存储40多亿键值对。

实例

127.0.0.1:6379> hset hashkey hello world
(integer) 1
127.0.0.1:6379> hget hashkey hello
"world"
127.0.0.1:6379> hgetall hashkey
1) "hello"
2) "world"

哈希(hash)常用命令 

HSET key field value 
将哈希表 key 中的字段 field 的值设为 value 。

HSETNX key field value 
只有在字段 field 不存在时,设置哈希表字段的值。

HMSET key field1 value1 [field2 value2 ] 
同时将多个 field-value (域-值)对设置到哈希表 key 中。

HGET key field 
获取存储在哈希表中指定字段的值。

HMGET key field1 [field2] 
获取所有给定字段的值

HGETALL key 
获取在哈希表中指定 key 的所有字段和值

HLEN key 
获取哈希表中字段的数量

HKEYS key 
获取所有哈希表中的字段

HVALS key 
获取哈希表中所有值

HEXISTS key field 
查看哈希表 key 中,指定的字段是否存在。

HDEL key field1 [field2] 
删除一个或多个哈希表字段

HSCAN key cursor [MATCH pattern] [COUNT count] 
迭代哈希表中的键值对。

HINCRBY key field increment 
为哈希表 key 中的指定字段的整数值加上增量 increment 。

HINCRBYFLOAT key field increment 
为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 

 

Redis 集合(set)

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

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

每个集合可存储40多亿个成员。

实例

127.0.0.1:6379> sadd setkey hello , world
(integer) 3
127.0.0.1:6379> smembers setkey
1) "world"
2) "hello"
3) ","

集合(set)常用命令 

sadd key member1 [member2] 
向集合添加一个或多个成员

smembers key 
返回集合中的所有成员

sismember key member 
判断 member 元素是否是集合 key 的成员

scard key 
获取集合的成员数

srandmember key [count] 
返回集合中一个或多个随机数

spop key 
移除并返回集合中的一个随机元素

srem key member1 [member2] 
移除集合中一个或多个成员

smove source destination member 
将 member 元素从 source 集合移动到 destination 集合

sdiff key1 [key2] 
返回给定所有集合的差集

sdiffstore destination key1 [key2] 
返回给定所有集合的差集并存储在 destination 中

sinter key1 [key2] 
返回给定所有集合的交集

sinterstore destination key1 [key2] 
返回给定所有集合的交集并存储在 destination 中

sunion key1 [key2] 
返回所有给定集合的并集

sunionstore destination key1 [key2] 
所有给定集合的并集存储在 destination 集合中

sscan key cursor [MATCH pattern] [COUNT count] 
迭代集合中的元素  

 

Redis 有序集合(zset)

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 

实例

127.0.0.1:6379> zadd zsetkey 1 hello 2 , 3 world
(integer) 3
127.0.0.1:6379> zrange zsetkey 0 -1
1) "hello"
2) ","
3) "world"

有序集合(zset)常用命令

zadd key score1 member1 [score2 member2] 
向有序集合添加一个或多个成员,或者更新已存在成员的分数

zrange key start stop [WITHSCORES] 
通过索引区间返回有序集合成指定区间内的成员,分数从低到高排序

zrevrange key start stop [WITHSCORES] 
通过索引区间返回有序集合成指定区间内的成员,分数从高到底排序

zrangebyscore key min max [WITHSCORES] [LIMIT] 
通过分数返回有序集合指定区间内的成员,分数从低到高排序

zrevrangebyscore key max min [WITHSCORES] 
返回有序集中指定分数区间内的成员,分数从高到低排序

zrangebylex key min max [LIMIT offset count] 
通过字典区间返回有序集合的成员

zcard key 
获取有序集合的成员数

zcount key min max 
计算在有序集合中指定区间分数的成员数

zlexcount key min max 
在有序集合中计算指定字典区间内成员数量

zscore key member 
返回有序集中,成员的分数值

zincrby key increment member 
有序集合中对指定成员的分数加上增量 increment

zrank key member 
返回有序集合中指定成员的索引

zrevrank key member 
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序

zrem key member [member ...] 
移除有序集合中的一个或多个成员

zremrangebyscore key min max 
移除有序集合中给定的分数区间的所有成员

zremrangebylex key min max 
移除有序集合中给定的字典区间的所有成员

zremrangebyrank key start stop 
移除有序集合中给定的排名区间的所有成员

zinterstore destination numkeys key [key ...] 
计算给定的一个或多个有序集的交集,并将结果集存储在新的有序集合 key 中

zunionstore destination numkeys key [key ...] 
计算给定的一个或多个有序集的并集,并将结果集存储在新的有序集合 key 中

zscan key cursor [MATCH pattern] [COUNT count] 
迭代有序集合中的元素(包括元素成员和元素分值)

由上面的基本命令,我们可以对Redis的五种数据类型有一个基本的了解。五种类型即为字符串(string)、列表(list)、哈希(hash)、集合(set)、有序集合(zset)。建议把以上常用的命令多敲几遍熟悉熟悉!

redis数据类型

另外可以使用Redis可视化工具 Redis Desktop Manager来查看和操作Redis。(需要开放6379端口和去掉配置文件的bind)

redis可视化工具

 

Redis的Java连接

在redis官网推荐的三大框架就是:Jedis  Redission  Lettuce三种解决方案。其中Jedis出现的时间是比较长的,但Jedis是同步阻塞(即当前jedis与redis数据库获取连接后,只有当释放连接后才能允许下一次的连接,所以需要通过连接池来使用Jedis)的方案,接触redis比较早的人可能使用的都是Jedis,但是随着现代系统的多核和异步,为了不断提高的吞吐量,异步非阻塞线程模型大行其道,这里面非常热门的框架就是Netty,Redission和Lettuce都是基于Netty的也就是说他俩都是异步非阻塞的。包括在 SpringBoot 2.x 中已经将 Jedis 换成了 Lettuce。

优点:

  • Jedis:比较全面的提供了Redis的操作特性,Jedis中的方法调用是比较底层的暴露的Redis的API,即Jedis中的Java方法基本和Redis的API保持着一致,了解Redis的API,也就能熟练的使用Jedis。
  • Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列
  • Lettuce:主要在一些分布式缓存框架上使用比较多

可伸缩:

  • Jedis:使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
  • Redisson:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作
  • Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作

项目使用方式:

方式一:可以直接使用Jedis客户端,

方式二:Spring Boot为 Redis 的 Lettuce 和 Jedis 客户端库提供了基本的自动配置,并提供 RedisTemplate 类来操作redis。

基本使用实例:

方式一:使用Jedis客户端

1.引入Jedis依赖


    redis.clients
    jedis
    3.0.1

2.代码中使用

package com.jvxb.test.myjedis;

import redis.clients.jedis.Jedis;

public class JedisTest {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("139.155.105.15");
        String strValue = jedis.get("strkey");
        System.out.println(strValue);
    }

}

3.查看结果

方式二:使用Jedis线程池

Jedis实例是非线程安全,多线程下可能会因为共享socket、共享数据流引起异常。故不应该在多线程环境中共用一个Jedis实例。同时也应该避免直接创建多个Jedis实例,因为会导致创建过多的socket连接,性能不高。

要保证线程安全且获得较好的性能。可以使用JedisPool。JedisPool是一个连接池,既能够保证线程安全,又能够保证了较高的效率。我们可以声明一个全局的JedisPool变量来保存JedisPool对象的引用,然后在其它地方使用。要知道。JedisPool是一个线程安全的连接池。

1.引入Jedis依赖(同上)

2.代码中使用。(JedisPool初始化时可以通过JedisPoolConfig配置线程池相关的参数,否则使用默认的JedisPoolConfig配置)

package com.jvxb.test.myjedis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class JedisTest {

    private static final JedisPool jedisPool = new JedisPool("139.155.105.15");

    public static void main(String[] args) {
        Jedis jedis = jedisPool.getResource();
        String strValue = jedis.get("strkey");
        List listValue = jedis.lrange("listkey", 0 , -1);
        Map hashValue = jedis.hgetAll("hashkey");
        Set setValue = jedis.smembers("setkey");
        Set zsetValue = jedis.zrange("zsetkey", 0, -1);
        System.out.println(strValue);
        System.out.println(listValue);
        System.out.println(hashValue);
        System.out.println(setValue);
        System.out.println(zsetValue);
    }

}

3.查看结果

方式三:使用spring-data-redis整合redis

Spring Boot为 Redis 的 Lettuce 和 Jedis 客户端库提供了基本的自动配置,并提供 RedisTemplate 类来操作redis。

1.引入spring-data-redis依赖(1.x时默认为jedis,springboot2.x后默认为lettuce)


    org.springframework.boot
    spring-boot-starter-data-redis

2.配置redis属性

#redis 基本配置
spring.redis.database=0
spring.redis.host=139.155.105.15
spring.redis.password=
spring.redis.port=6379
spring.redis.timeout=5000ms
#以下为2.x后lettuce默认值 
# 连接池最大连接数(使用负值表示没有限制) 默认
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1ms
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0

3.配置序列化方式

上面配置完只能使用StringRedisTemplate,因为spring.redis虽然提供了对list set hash等数据类型的支持,但是没有提供对POJO对象的支持,底层都是把对象序列化后再以字符串的方式存储的,所以使用其他类型的RedisTemplate还需要配置序列化。

  • 关于StringRedisTemplate与RedisTemplate的区别,和redisTemplate几种序列化方式的区别,可以参考这里。
package com.jvxb.test.myjedis;

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 RedisTemplateConfig {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //修改键的序列化规则,将默认的JdkSerializationRedisSerializer修改为StringRedisSerializer
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        //修改值的序列化规则,将默认的JdkSerializationRedisSerializer修改为Jackson2JsonRedisSerializer
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

4.代码中使用

  • 可以在redis服务器中使用 flushdb 或者 flushall 命令来清掉原有数据
package com.jvxb.test;

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.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@RunWith(SpringRunner.class)
@SpringBootTest
public class CommonTest {

	@Autowired
	RedisTemplate redisTemplate;

	@Test
	public void contextLoads() {
		//redisTemplate操作字符串类型,并可设置过期时间(默认为永久)
		redisTemplate.opsForValue().set("strkey", "hello,world", 30, TimeUnit.SECONDS);
		Object strValue = redisTemplate.opsForValue().get("strkey");
		System.out.println(strValue);
		//redisTemplate操作列表类型
		redisTemplate.opsForList().rightPushAll("listkey", "hello", ",", "world");
		List listValue = redisTemplate.opsForList().range("listkey", 0 , -1);
		System.out.println(listValue);
		//redisTemplate操作哈希类型
		redisTemplate.opsForHash().put("hashkey", "hello", "world");
		List hashValue = redisTemplate.opsForHash().values("hashkey");
		System.out.println(hashValue);
		//redisTemplate操作集合类型
		redisTemplate.opsForSet().add("setkey", "hello", ",", "world");
		Set setValue = redisTemplate.opsForSet().members("setkey");
		System.out.println(setValue);
		//redisTemplate操作有序集合类型
		redisTemplate.opsForZSet().add("zsetkey", "hello", 1);
		redisTemplate.opsForZSet().add("zsetkey", ",", 2);
		redisTemplate.opsForZSet().add("zsetkey", "world", 3);
		Set zsetValue = redisTemplate.opsForZSet().range("zsetkey", 0, -1);
		System.out.println(zsetValue);
	}

}
 
  

结果如下: 

分布式专题(6)- Nosql & Redis_第1张图片

 

Redis作为缓存

redis最常用的一个作用就是作为高速缓存使用,下面我们看看redis作为缓存的一个实例。

写入缓存:

@Service(value="userService")
@Transactional
public class UserServiceImpl implements UserService{
 
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UserMapper userMapper;
    @Override
    public int addUser(User user) {
        int i = userMapper.insert(user);
        if(i > 0){
            redisTemplate.opsForValue().set("user : " + user.getUserId(), user);
        }
        return i;
    }
}

使用缓存:

@Override
public User findUser(int userId){
    User result = (User) redisTemplate.opsForValue().get("user : " + userId);
    if(result == null){
        result = userMapper.findUser(userId);
        redisTemplate.opsForValue().set("user : " + user.getUserId(), result);
    }
    return result;
}

以上两段代码就是redis作为缓存时最基本的代码了,其流程如下图,相信谁都能看懂,此处不多做解释。使用Redis作为缓存能够有效减少对数据库的访问,从而提高网站访问效率。

redis作为缓存流程图

 

 

Redis内存相关策略

Redis保存过期时间

我们知道通过 expire命令和 persist命令可以对一个键设定和解除过期时间,那么redis里面对这些key的过期时间和生存时间的信息是怎么保存的呢?

在数据库结构redisDb中的expires字典中保存了数据库中所有键的过期时间,我们称expire这个字典为过期字典。

  1. 过期字典是一个指针,指向键空间的某个键对象。
  2. 过期字典的值是一个longlong类型的整数,这个整数保存了键所指向的数据库键的过期时间–一个毫秒级的 UNIX 时间戳。

过期字典是存储在redisDb这个结构里的:

typedef struct redisDb {
    ...
    
    dict *dict;     //数据库键空间,保存着数据库中所有键值对
    dict *expires      // 过期字典,保存着键的过期时间
    ...
} redisDb;

从以上结构中可以看到 expire字典(过期字典)和 dict字典(数据库键空间,保存着数据库中所有键值对)是并列的,由此可见expire字典的重要性。

Redis数据过期策略

我们知道通过 expire命令和 persist命令可以对一个键设定和解除过期时间。如果一个键是过期的,那它到了过期时间之后是不是马上就从内存中被被删除呢?如果不是,那过期后到底什么时候被删除呢?

针对已经过期的数据,Redis采用定期删除和延迟删除结合的策略。

  • 延迟删除:某个键值过期后,此键值不会马上被删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除。所以惰性删除的缺点很明显:浪费内存。
  • 定期删除:每隔一段时间执行一次删除操作。但由于定期检查所有的key是否过期会带来性能问题,因此定期删除策略使用的是随机抽查。定时删除从某种程度上也有效地减少了因延迟删除带来的内存浪费。

Redis最大占用内存

Redis需要设置最大占用内存吗?答案是不需要的,默认情况下64位系统不限制内存,32位系统最多使用3GB内存。

当然我们也可以手动设置最大占用内存,打开 redis.conf文件,找到 # maxmemory ,在它下面设置即可(注意是字节),一般推荐Redis设置内存为最大物理内存的四分之三,如设置最大占用内存为3G:

# maxmemory  
maxmemory 3221225472

 

  •  也可以在redis客户端连接后通过命令: CONFIG SET maxmemory 100MB 的形式设置最大内存

Redis最大内存置换策略

虽然Redis采用了定期删除和延迟删除数据的策略,但随着不断使用,内存占用肯定还是会越来越大。那Redis内存使用达到设置的maxmemory大小,或者耗尽机器最大内存时,Redis又是怎么处理的呢?这时Redis就要根据置换策略来处理了。

针对达到最大内存的情况,Redis默认采用的置换策略是直接返回错误。(报OOM,Out of Memory,内存溢出错误

当然我们也可以手动设置修改最大内存策略,打开 redis.conf文件,找到 maxmemory-policy ,在它下面设置即可,redis给我们提供了6种置换策略。

# volatile-lru -> remove the key with an expire set using an LRU algorithm
:回收最近最少使用的键,但仅限于在过期字典中的键
# allkeys-lru -> remove any key according to the LRU algorithm
:回收最近最少使用的键
# volatile-random -> remove a random key with an expire set 
:随机回收,但仅限于在过期字典中的键
# allkeys-random -> remove a random key, any key 
:随机回收,在所有的键中
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
:回收在过期字典的键,并且优先回收存活时间(TTL)较短的键
# noeviction -> don't expire at all, just return an error on write operations 
:返回错误

# The default is:
#
# maxmemory-policy noeviction

  • LRU(Least Recently Used)指的是最近最少使用算法。

一般来说,有这样一些常用的经验:

  • 在所有的 key 都是最近最经常使用,那么就需要选择 allkeys-lru 进行置换最近最不经常使用的 key,如果你不确定使用哪种策略,那么推荐使用 allkeys-lru
  • 如果所有的 key 的访问概率都是差不多的,那么可以选用 allkeys-random 策略去置换数据
  • 如果对数据有足够的了解,能够为 key 指定 hint(通过expire/ttl指定),那么可以选择 volatile-ttl 进行置换
  • 如果你们都不存在内存不足的情况,那么直接使用默认的策略就好。

 

Redis持久化机制

Redis是内存数据库,基于内存的操作使它的性能非常之高,但是Redis崩掉的话,会导致数据丢失。为了防止服务宕机时内存数据丢失,Redis提供了持久化功能。持久化就是把内存的数据写到磁盘中去,从而有效避免了因进程退出造成数据丢失的问题。

Redis持久化方式

Redis为数据持久化提供了两种方式,RDB(Redis Database,为默认方式)和AOF(Append-only file)。

  • RDB:按照规则定时将内存的数据同步到磁盘。
  • AOF:将Redis执行的每一条写命令追加到硬盘文件中。

Redis持久化配置

在我们的 redis.conf 文件中,可以对Redis的持久化方式进行配置。

  • 按RDB持久化方式的默认配置如下:

# 时间策略1:900秒内改变1条数据,自动生成RDB文件
save 900 1
# 时间策略2:300秒内改变10条数据,自动生成RDB文件
save 300 10
# 时间策略3:60秒内改变1000条数据,自动生成RDB文件
save 60 10000
# bgsave发生错误,停止写入
stop-writes-on-bgsave-error yes
# rdb文件采用压缩格式
rdbcompression yes
# 导入时对rdb文件进行校验
rdbchecksum yes
# 指定rdb文件文件名
dbfilename dump.rdb
# 指定rdb文件目录
dir ./

# 当然如果想要禁用RDB配置,也是非常容易的,只需要在save的最后一行写上:save ""

  • 按AOF持久化方式的默认配置如下:

...

 

 

其他待补充..

 

你可能感兴趣的:(分布式专题)