redis
特点
-
可持久化
-
NoSQL
-
应用于缓存
-
session分离
##应用场景
在Java web中,redis主要用于:
-
存储缓存用的数据;
-
需要高速读/写的场合使用它快速读/写。
应用要求:
-
业务数据常用吗?命中率如何?如果命中率很低,就没有必要写入缓存;
-
该业务数据是读操作多,还是写操作多?如果写操作多,频繁需要写入数据库,也没有必要使用缓存;
-
业务数据大小如何?如果要存储几百兆字节的文件,会给缓存带来很大的压力,这样也没有必要。
redis命令
-
ping
-
get/set
set name value
get name
-
del
-
keys*
-
flushdb
清空所有数据
Jedis
单实例连接redis
-
host,port
-
select(0)//十六库选择
-
set(“key”,“value”)
-
expire(“key”,xx秒)//有效期
-
get(“key”)
-
hset(“库名”,“key”,“value”)
-
hget(“库名”,“key”)
参考代码:
import org.junit.Test;
import redis.clients.jedis.Jedis;
public class AppTest{
@Test
public void testJedisSingle() {
//创建Jedis对象,传递两个参数:第一个host为redis服务器的名称, 第二个为redis服务器的端口号
Jedis jedis = new Jedis("192.168.5.128", 6379);
//进行设值操作
jedis.set("username","zhangsan");
//从redis中取值
String username = jedis.get("username");
System.out.println("username:"+username);
}
}
连接池连接redis
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class AppTest{
@Test
public void testJedisPool(){
//创建JedisPool连接池配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//设置连接池的最大连接数
jedisPoolConfig.setMaxTotal(20);
//设置连接池的最大连接空闲数
jedisPoolConfig.setMaxIdle(2);
//设置redis服务器的主机
String host = "192.168.5.128";
//设置redis服务器的端口
int port = 6379;
//创建JedisPool连接池对象
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port);
//获取Jedis对象
Jedis jedis = jedisPool.getResource();
//存入数据 jedis.set("info", "message");
//获取数据
String info = jedis.get("info");
System.out.println("info:"+info);
//关闭资源
jedisPool.close();
}
}
redis数据类型
String
格式: set key value
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。
##hash
格式: hmset name key1 value1 key2 value2
Redis hash 是一个键值(key=>value)对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
list(双向链表)
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
格式: lpush name value
在 key 对应 list 的头部添加字符串元素
格式: rpush name value
在 key 对应 list 的尾部添加字符串元素
格式: lrem name index
key 对应 list 中删除 count 个和 value 相同的元素
格式: llen name
返回 key 对应 list 的长度
set(散列存储)
格式: sadd name value
Redis的Set是string类型的无序集合。不能重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
##sorted set(有序集合、每个元素带有分数)
格式: zadd name score value
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
redis持久化
RDB
在 redis.conf 中以“sava 时长(秒) 更改键的个数”作为条件进行快照。可以拥有多个条件,不同的条件之间是或的关系。当满足条件时,将改变写入磁盘。
AOF(Append-only file)
需要手动开启的持久化方案,开启参数:“appendonly yes”。开启后每更改一个数据,变化都将写入磁盘,适用于数据太重要不能承担风险的情况。
RDB和AOF的对比
比较:
-
aof文件比rdb更新频率高,优先使用aof还原数据。
-
aof比rdb更安全也更大
-
rdb性能比aof好
-
如果两个都配了,优先加载AOF
持久化的存储结构:
内容是redis通讯协议(RESP)格式的命令文本存储。
RESP
RESP 是redis客户端和服务端之前使用的一种通讯协议;
RESP 的特点:实现简单、快速解析、可读性好
For Simple Strings the first byte of the reply is “+” 回复
For Errors the first byte of the reply is “-” 错误
For Integers the first byte of the reply is “:” 整数
For Bulk Strings the first byte of the reply is “$” 字符串
For Arrays the first byte of the reply is “*” 数组
Redis 架构模式
单机版
问题:
-
内存容量有限
-
处理能力有限
-
无法高可用。
主从复制
主redis中的数据有两个副本(replication)即从redis1和从redis2,即使一台redis 服务器宕机其它两台redis服务也可以继续提供服务。是为了避免硬件损坏的机制。
特点:
-
master/slave 角色
-
master/slave 数据相同降低
-
master 读压力在转交从库
问题:
-
无法保证高可用
-
没有解决 master 写的压力
哨兵
https://www.cnblogs.com/jasontec/p/9699242.html
集群(proxy 型)
https://www.cnblogs.com/jasontec/p/9699242.html
集群(直连型)
https://www.cnblogs.com/jasontec/p/9699242.html
缓存穿透、缓存雪崩与数据击穿
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免?
1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
如何避免?
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
数据击穿
数据击穿指的是一个高热度的键过期瞬间,由于大量访问,也导致sql负荷过高。可以将热门键设为永久
#遗留问题
暂时还看不太懂的问题。
使用过Redis分布式锁么,它是怎么实现的?
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!
使用过Redis做异步队列么,你是怎么用的?有什么缺点?
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
缺点:
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
能不能生产一次消费多次呢?
使用pub/sub主题订阅者模式,可以实现1:N的消息队列。