Redis是一个基于内存的Key-Value非关系型数据库,由C语言进行编写。redis
Redis通常做为分布式缓存框架、分布式下的SESSION分离、分布式锁的实现等等。数据库
Redis速度快的缘由:基于内存、单线程、多路复用。 缓存
Redis中提供了五种数据结构,分别是String、Hash、List、Set、ZSet,每种数据结构底层都是经过字符串来进行实现。安全
Key对应的Value是一个字符串类型。服务器
#设置字符串类型的Key
set key value
#仅当Key不存在时设置字符串类型的Key
setnx key value
#设置字符串类型的Key并添加过时时间
setex key second value
#获取Key对应的Value
get key
#让Key对应的Value值递增1
incr key
#让Key对应的Value值递减1
decr key
#让Key对应的Value递增指定的数值
incrby key num
#让Key对应的Value递减指定的数值
decrby key num
#往Key对应的Value中追加字符串
append key str
*在使用set、append命令时,若是Key不存在则建立,不然替换Key对应的Value值。数据结构
*在使用incr、incrby、decr、decrby命令时,若是Key不存在则初始化为0后再进行操做,若是Key对应的Value不是数值类型字符串,那么将会报错。并发
Key对应的Value是一个哈希类型,每一个哈希类型中都包含若干个键值对(属性名与属性值)app
#往Hash中添加一个属性
hset key field value
#仅当Key不存在时往Hash中添加一个属性
hsetnx key field value
#往Hash中添加多个属性
hmset key field1 value1 field2 value2
#获取Hash中指定的一个属性
hget key field
#获取Hash中指定的多个属性
hmget key field1 field2
#获取Hash中全部的属性
hgetall key
#让Hash中指定的属性递增指定的数值(属性值必须是数值类型)
hincrby key field num
#判断Hash中指定的属性是否存在
hexists key field
#获取Hash中属性的个数
hlen key
#获取Hash中全部的属性名
hkeys key
#获取Hash中全部的属性值
hvals key
#删除Hash中指定的多个属性
hdel key field1 field2
*在使用hset、hmset命令时,若是Key不存在则建立,不然往Key对应的Hash中添加属性,若是属性名相同则替换属性值。框架
*Hash中只有hincryby命令,没有相似String的incr、decr、decrby命令。dom
*当使用hdel命令删除了Hash中的全部属性,那么此时Key也会被删除。
Key对应的Value是一个List类型(有序可重复),Redis中的List使用链表的方式进行实现,所以其在插入和删除操做时性能要比顺序表的高(头指针指向链表的第一个结点)
#从链表的左侧添加元素 lpush key value #从链表的右侧添加元素 rpush key value #仅当Key存在时从链表的左侧添加元素 lpushx key value #仅当Key存在时从链表的右侧添加元素 rpushx key value #获取链表中指定索引范围的元素(从链表的左侧开始遍历,包括begin和end的位置,若是end为-1表示倒数第一个元素) lrange key begin end #从链表的左侧弹出一个元素 lpop key #从链表的右侧弹出一个元素 rpop key #获取链表中元素的个数 llen key #删除链表中指定个数个Value(若count为正数,则从链表的左侧开始删除指定个数个Value,若count为负数,则从链表的右侧开始删除指定个数个Value,若count为0,则删除链表中全部指定的Value) lrem key count value #设置链表中指定索引的值 lset key index value #从链表的右侧弹出元素并将其放入到其余链表的左侧(通常用在消息队列的备份) rpoplpush key otherKey
*当使用lpush、rpush命令时,若是Key不存在则建立,不然往Key对应的链表中追加元素。
*经过Redis的List能够实现队列和栈结构,当遵循lpush、rpop时,此时为队列结构,当遵循lpush、lpop时,此时为栈结构。
Key对应的Value是一个Set(无序不可重复)
#往Set中添加元素
sadd key value
#删除Set中指定的元素
srem key value
#查看Set中的元素
smembers key
#判断Set中是否包含某个元素
sismemeber key value
#返回Set中元素的个数
scard set
#返回两个Set的交集
sinter set1 set2
#返回两个Set的并集
sunion set1 set2
#返回Set1中Set2没有的元素(补集)
sdiff set1 set2
#将Set1和Set2的交集放入到新的Set中
sinterstore destSet set1 set2
#将Set1和Set2的并集放入到新的Set中
sunionstore destSet set1 set2
#将Set1中Set2没有的元素放入到新的Set中
sdiffstore destSet set1 set2
*在使用sadd命令时,若是Key不存在则建立,不然往Key对应的Set中追加元素。
Key对应的Value是一个带分数的Set(有序不可重复),其中分数能够相同但Value不能相同。
#往ZSet中添加元素 zadd key score value #获取ZSet中指定Value的分数 zscore key value #返回ZSet中元素的个数 zcard key #获取ZSet中指定索引范围的元素(包括begin和end的位置,end为-1时表示倒数第一个元素) zrange key begin end #获取ZSet中指定索引范围的元素以及分数,返回的元素按照分数从小到大排序 zrange key begin end withscores #获取ZSet中指定索引范围的元素以及分数,返回的元素按照分数从大到小进行排序 zrevrange key begin end withscores #获取ZSet中指定分数范围的元素(包括begin和end的位置) zrangebyscore key begin end #获取ZSet中指定分数范围的元素并限制返回的个数 zrangebyscore key begin end limit num #返回ZSet中指定分数范围元素的个数 zcount key begin end #删除ZSet中指定的元素 zrem key value #删除ZSet中指定分数范围的元素(包括begin和end的位置) zremrangebyscore key begin end #让ZSet中指定元素的分数递增指定的值 zincrby key score value
*当使用zadd命令时,若是Key不存在则建立,不然往Key对应的ZSet中添加元素。
*Set、ZSet中没有相似String、Hash、List的,当Key不存在或存在时才进行操做的命令。
#查看Redis中的Key(支持通配符,*表明任意个字符,?表明任意一个字符) keys pattern #删除Key del key #判断Key是否存在 exists key #对Key进行重命名 rename oldKey newKey #对Key设置过时时间 expire key seconds #查看Key的有效时间(若Key没有设置过时时间则返回-1) ttl key #查看Key的类型 type key
Redis中提供了事务的功能,Redis会将事务中的全部命令看成一个原子执行,即一个事务在执行时不会被其余命令所干扰。
#开启事务
multi
#执行事务
exec
#放弃事务
discard
*在Shell中事务与客户端进行绑定,当一个客户端开启了事务后,该客户端事务以后的命令都将放入一个新的队列中,使用exec命令执行事务,其余客户端的命令并不会进入此事务,在Jedis中经过Transaction实例封装一个事务,不受影响。
因为Redis是基于内存的Key-Value非关系型数据库,所以当Redis服务挂掉后因为内存被释放会致使数据丢失,此时可使用Redis的持久化功能。
RDB持久化方式即Redis每隔必定时间,就会将当前内存中的全部Key-Value写入到磁盘文件中(全量写入),当Redis服务重启时,读取RDB文件自动进行数据的恢复。
RDB持久化方式是默认开启的,能够经过redis.conf配置文件中修改相关配置。
#在900秒内若是至少有1个Key发生变化,那么执行一次写入操做 save 900 1 #在300秒内若是至少有10个Key发生改变,那么执行一次写入操做 save 300 10 #在60秒内若是至少有10000个Key发生改变,那么执行一次写入操做 save 60 10000 #rdb文件的保存路径(相对于redis.conf文件) dir ./ #rdb文件的名称 dbfilename dump.rdb
*使用RDB持久化方式有很大可能会发生Key的修改将来得及写入到磁盘中服务器就宕机了(能够调整默认的同步策略)
*RDB持久化方式由Redis进程执行fork操做建立子进程来完成,所以阻塞只会发生在fork阶段,客户端能够经过save、bgsave命令手动触发RDB操做,其中save命令会阻塞redis进程直到RDB持久化完成,而bgsave命令由redis进程fork子进程进行完成。
AOF持久化方式即Redis将全部的Key-Value操做都写入到日志文件中(追加,增量写入),当Redis服务重启时,读取AOF文件自动进行数据的恢复。
AOF持久化方式提供了三种同步策略:每修改同步、每秒同步、不一样步。
#开启AOF方式 appendonly yes #AOF文件名 appendfilename "appendonly.aof" #AOF同步策略 #每修改同步 appendsync always #每秒同步 appendsync everysec #不一样步 appendsync no
*AOF持久化方式也是由Redis进程执行fork操做建立子进程来完成,而且当日志文件达到必定大小时Redis会对其压缩(重写)
*当同时使用RDB和AOF持久化方式时,数据的恢复将固定使用AOF的。
RDB持久化方式与AOF持久化方式的对比
1.文件大小:AOF持久化方式所产生的日志文件要比RDB持久化方式所产生的rdb文件要大。
2.安全性:AOF持久化方式数据丢失的可能性要比RDB持久化方式低。
3.效率:AOF持久化方式在进行数据恢复时的效率要比RDB持久化方式的低。
Redis中能够对Key设置过时时间,当Key已经到达过时时间时,并不会当即被删除,而是经过Redis的过时策略进行处理。
Redis中使用惰性删除和主动删除两种过时策略:
惰性删除:当访问一个已通过期的Key时,将该Key删除。
主动删除:Redis定时删除缓存中部分过时的Key,经过抽样的方式保证Redis中过时的Key在低于25%如下。
*Redis定时删除缓存中部分过时的Key是经过Redis常规任务处理的,常规任务还包含其余一些任务处理,能够经过修改redis.conf配置文件中的hz参数来调整Redis常规任务的执行频率,默认值是10,表示每秒执行10次,其取值为1~500,但Redis不建议该值超过100,不然会影响其余的业务请求,形成延时。
当Redis中使用的内存已经超过所容许的最大值时(maxmemory),会根据预先设置的淘汰策略主动删除缓存中部分Key。
Redis中提供了六种淘汰策略:
1.allkeys-lru(推荐):删除部分最近最少使用的Key。
2.volatile-lru:在设置了过时时间的Key中,删除部分最近最少使用的Key。
3.allkeys-random:随机删除部分Key。
4.volatile-random:随机删除部分设置了过时时间的Key。
5.volatile-ttl:删除部分即将过时的Key。
6.noeviction:不清除,对于写请求直接返回错误。
*能够经过redis.conf配置文件中的maxmemory-policy参数设置Redis的淘汰策略。
通常经过Redis中的一个Key来实现分布式锁,当Key已经存在时,表示当前锁已经被其它线程锁持有,当Key不存在时,设置Key,表示获取锁资源。
分布式锁可能面临的问题:
1.当系统宕机时如何保证锁可以被释放?
*经过对Key设置过时时间,当Key已通过期并再次访问时,经过惰性删除过时策略删除该Key。
2.怎样保证获取锁时的同步问题?
*经过使用Redis提供的set(String key, String value, String nxxx, String expx, long time)命令,将判断Key是否存在以及设置Key做为一个原子操做。
3.怎样保证释放的锁是当前线程的?
*给Key设置一个表明当前线程的Value,当删除该Key时判断Value是否属于当前线程的。
/** * 获取锁 */
public boolean getLock(String key, String value, int second) { return "OK".equalsIgnoreCase(jedis.set(key, value, "nx", "ex", second)); } /** * 释放锁 */
public boolean unLock(String key, String value) { if (value.equals(jedis.get(key))) { jedis.del(key); return true; } return false; }
Redis计数器即便用incr、incrby、decr、decrby命令,将Key对应的Value递增或递减指定的数值。
频次控制(登陆错误次数)
1.用户输入用户名以及密码,首先判断表明用户的Key是否存在,仅当Key不存在或Key的值小于所容许的最大登陆错误次数时,执行登陆操做,不然不容许登陆。
2.执行登陆操做,若是登陆成功则将用户实体放入HttpSession,不然当密码错误时,使用incr命令自增1(Key不存在时初始化为0再操做),并根据命令返回值判断当前的错误次数是否已经达到了所容许的最大值,若达到了则设置Key的过时时间。
数量控制(秒杀、抢购)
1.系统在抢购前初始化该Key对应的Value。
2.在进行抢购时直接使用decr命令,并根据返回值判断是否抢购成功,当返回值大于等于0,表示抢购成功,不然抢购失败。
*依赖incr、incrby、decr、decrby命令的返回值,能够避免并发致使的同步问题(不须要获取值再判断是否大于0,由于不是原子操做,会有同步问题)
*通常对于频次控制、数量统计,使用incr、incrby命令,对于数量控制使用decr、decrby命令。
缓存雪崩即Redis中有大部分的Key集中过时,那么此时全部的客户端请求都会去到数据库中,从而增长了数据库的压力。
*解决缓存雪崩的方法是对于被频繁访问的Key不设置过时时间,对于不频繁访问的Key分散设置过时时间。
缓存穿透即不断查询Redis中不存在Key,从而占用Redis以及数据库的链接,容易被恶意占用资源。
*解决缓存穿透的方法是当数据库查询不到记录时,也将该Key放入到缓存中,其Value是一个空字符串。
缓存击穿即Redis中一个被高并发访问的Key忽然过时,那么此时将会有大量的请求直接去到数据库,从而增长了数据库的压力。
*解决缓存击穿的方法是对于被频繁访问的Key不设置过时时间。
1.下载Redis源码并进行解压
2.因为Redis是使用C语言编写的,所以须要安装gcc编译器,并使用make命令进行编译
make
3.进行redis的安装
make PREFIX=/usr/redis install
*安装完后生成bin目录,里面包含一些Redis的可执行命令。
4.将redis源码目录下的redis.conf配置文件复制到安装目录中,并将配置文件中的daemonize改成yes,经过后台的方式启动Redis
5.启动Redis Server
*Redis Server是一个进程,包含了若干个线程,但只有一个线程用于处理客户端的请求(Redis单线程处理任务,不会出现共享变量不一致的问题)
6.启动Redis Client
*Redis Server中包含16个数据库,编号从0~15,每一个数据库之间的数据相互独立,客户端默认链接的是第一个数据库,能够经过select num命令修改链接的数据库。
Jedis是Redis官方推荐的Redis Java客户端类库。
1.导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.1</version>
</dependency>
2.获取Redis链接
//1.直接建立Jedis实例
Jedis jedis = new Jedis(String host , int port); //2.经过Jedis链接池来管理链接
JedisPoolConfig poolConfig = new JedisPoolConfig(); //最大链接数
poolConfig.setMaxTotal(int num); //最大空闲链接数
poolConfig.setMaxIdle(int num); //建立链接池对象
JedisPool jedisPool = new JedisPool(poolConfig , String host , int port); //获取Redis链接
Jedis jedis = jedisPool.getResource();
*当Redis链接使用完毕后须要手动关闭。
/** * @Auther: ZHUANGHAOTANG * @Date: 2019/4/2 17:11 * @Description: */
public class RedisUtils { private static final String host = "192.168.1.80"; private static final int port = 6379; private static JedisPool jedisPool = null; static { JedisPoolConfig poolConfig = new JedisPoolConfig(); //最大链接数
poolConfig.setMaxTotal(10); //最大空闲链接数
poolConfig.setMaxIdle(5); //建立链接池对象
jedisPool = new JedisPool(poolConfig, host, port); } public static Jedis getConnection() { return jedisPool.getResource(); } }