本篇一句话总结:NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”, 泛指非关系型的数据库,可以理解为SQL的一个有力补充。典型代表有MongDB、 Redis、Memcache等。特别是Redis,可以说是目前最火的Nosql数据库之一。
正文开始:
上面这几个问题,是每个刚接触 Nosql的人都想知道的。下面小兵综合自己的理解和使用情况,在分布式专题里总结一篇关于 Nosql的内容。全文看完,我们对 Nosql也有一定的了解了。
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”, 泛指非关系型的数据库,可以理解为SQL的一个有力补充。典型代表有MongDB、 Redis、Memcache等。特别是Redis,可以说是目前最火的Nosql数据库之一。
随着 Internet的快速发展,越来越多的网站、应用系统需要支撑海量数据存储,高并发请求、高可用、高可扩展性等特性要求,传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多难以克服的问题。由此,各种各样的NoSQL数据库作为传统关系型数据的一个有力补充得到迅猛发展。此处推荐阅读《从Mysql到Nosql》和《NoSQL 还是 SQL ?》看看sql的发展历史和常用的几大类Nosql数据库。
无论是关系型数据库还是非关系型数据库,本质都是对数据进行增删改查而已,但Nosql和关系数据库之间还是有一些区别的,如在储存方法、存储结构、存储规范、存储扩展、查询方法、事务、性能上,二者都有较大的差异。对比如下:
对数据的处理无外乎都是增删改查。本篇主要是介绍Nosql三剑库MongoDb、Memcache和Redis的基本使用。特别是Redis,可以说是目前最火的Nosql数据库,也是面试必问点之一,本篇将花大篇幅对其作出说明。
下面我们通过实例来讲解Memcache、Mongodb即Redis的概念和使用。
Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。
Memcache的工作流程:
MemCache的工作流程如下:先检查客户端的请求数据是否在memcached中,如有,直接把请求数据返回,不再对数据库进行任何操作;如果请求的数据不在memcached中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到memcached中;每次更新数据库的同时更新memcached中的数据,保证一致性;当分配给memcached内存空间用完之后,会使用LRU(Least Recently Used,最近最少使用)策略加上到期失效策略,失效数据首先被替换,然后再替换掉最近未使用的数据。
Memcache的优点:
Memcache的缺点:
Memcache的应用场景:
关于Memcache的其它特点和细节,可以查阅《memcached》
Java开发中使用Memcache基本实例,可以参考《Java开发中的Memcache原理及实现》
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的缺点:
Mongodb的应用场景
关于Mongodb的安装和基本使用,可以查阅《MongoDB入门教程》
Java开发中使用Mongodb的基本实例,可以参考《Java连接mongoDB 并进行增删改查操作》
Redis(Remote Dictionary Server)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。是当前最热门的Nosql数据库之一,也是本篇要主要讲解的内容。以下内容主要是讲解它的安装和基本命令使用,及redis用做数据缓存(商品数据、新闻、热点数据)、单点登录、秒杀、抢购、网站访问排名、应用的模块开发等应用场景。
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
3.修改redis为后台运行
vim redis.conf
将daemonize no 改为 daemonize yes,如果不修改,该窗口将无法往下执行操作了
4.启动服务端
cd src
./redis-server ../redis.conf
5.启动客户端
./redis-cli
6.关闭客户端、服务端
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是一种键值对(K-V)数据库,Redis能够支持五种数据类型:string(字符串),list(链表),set(集合),zset(有序集合)和Hash(哈希类型),我们先来看下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)类型是最常见到的数据结构,它既可以存储文字(比如“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中,可以对列表的两端进行插入(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 是一个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 是 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 有序集合和集合一样也是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
有序集合中对指定成员的分数加上增量 incrementzrank 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 Desktop Manager来查看和操作Redis。(需要开放6379端口和去掉配置文件的bind)
在redis官网推荐的三大框架就是:Jedis Redission Lettuce三种解决方案。其中Jedis出现的时间是比较长的,但Jedis是同步阻塞(即当前jedis与redis数据库获取连接后,只有当释放连接后才能允许下一次的连接,所以需要通过连接池来使用Jedis)的方案,接触redis比较早的人可能使用的都是Jedis,但是随着现代系统的多核和异步,为了不断提高的吞吐量,异步非阻塞线程模型大行其道,这里面非常热门的框架就是Netty,Redission和Lettuce都是基于Netty的也就是说他俩都是异步非阻塞的。包括在 SpringBoot 2.x 中已经将 Jedis 换成了 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还需要配置序列化。
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.代码中使用
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
结果如下:
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作为缓存能够有效减少对数据库的访问,从而提高网站访问效率。
我们知道通过 expire命令和 persist命令可以对一个键设定和解除过期时间,那么redis里面对这些key的过期时间和生存时间的信息是怎么保存的呢?
在数据库结构redisDb中的expires字典中保存了数据库中所有键的过期时间,我们称expire这个字典为过期字典。
过期字典是存储在redisDb这个结构里的:
typedef struct redisDb {
...
dict *dict; //数据库键空间,保存着数据库中所有键值对
dict *expires // 过期字典,保存着键的过期时间
...
} redisDb;
从以上结构中可以看到 expire字典(过期字典)和 dict字典(数据库键空间,保存着数据库中所有键值对)是并列的,由此可见expire字典的重要性。
我们知道通过 expire命令和 persist命令可以对一个键设定和解除过期时间。如果一个键是过期的,那它到了过期时间之后是不是马上就从内存中被被删除呢?如果不是,那过期后到底什么时候被删除呢?
针对已经过期的数据,Redis采用定期删除和延迟删除结合的策略。
Redis需要设置最大占用内存吗?答案是不需要的,默认情况下64位系统不限制内存,32位系统最多使用3GB内存。
当然我们也可以手动设置最大占用内存,打开 redis.conf文件,找到 # maxmemory
# maxmemory
maxmemory 3221225472
虽然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
一般来说,有这样一些常用的经验:
- 在所有的 key 都是最近最经常使用,那么就需要选择 allkeys-lru 进行置换最近最不经常使用的 key,如果你不确定使用哪种策略,那么推荐使用 allkeys-lru
- 如果所有的 key 的访问概率都是差不多的,那么可以选用 allkeys-random 策略去置换数据
- 如果对数据有足够的了解,能够为 key 指定 hint(通过expire/ttl指定),那么可以选择 volatile-ttl 进行置换
- 如果你们都不存在内存不足的情况,那么直接使用默认的策略就好。
Redis是内存数据库,基于内存的操作使它的性能非常之高,但是Redis崩掉的话,会导致数据丢失。为了防止服务宕机时内存数据丢失,Redis提供了持久化功能。持久化就是把内存的数据写到磁盘中去,从而有效避免了因进程退出造成数据丢失的问题。
Redis持久化方式
Redis为数据持久化提供了两种方式,RDB(Redis Database,为默认方式)和AOF(Append-only file)。
Redis持久化配置
在我们的 redis.conf 文件中,可以对Redis的持久化方式进行配置。
# 时间策略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 ""
...
其他待补充..