Redis
一、介绍Redis
1.1 现存问题
在单体架构搭建集群之后,会因为Tomcat之间的JVM缓存中的数据无法同步,导致一些问题……
由于单体架构搭建集群后,传统的缓存方案(JVM缓存-Map)失效,暂时没有一个比较好的替换方式……
由于多个JVM的原因,传统的锁失效了……
MySQL对于一 些非结构化数据,没有太好的存储方案,MySQL本身会将数据存储到本地磁盘,读写性能一般…
1.2 NoSQL&Redis介绍
NoSQL(Not Only SQL),对非关系型数据库的一种泛指。
一般市面上,给NoSQL大致分为了4类:
- key-value:Redis,Memcache……
- 文档型:Elasticsearch,MongoDB,Solr……
- 面向列:Hbase……
- 图形化:Neo4j……
Java中用的比较多的Key-Value存储的Redis
- Redis时完全脱离Tomcat之外的一个单独的中间件。
- Redis基于内存存储数据的,查询速度特别快。
- Redis还提供了高性能的读写能力,并且并发能力极强,读的速度是110000次/s,写的速度是81000次/s 。
- Redis还提供了丰富的存储数据的格式,可以满足大多数的非结构化存储。
- Redis还提供了实现锁的机制,甚至由第三方框架Redisson提供的分布式锁的实现方案。
- Redis的出生就时因为作者任务MySQL性能一般,自己写了Redis。
二、安装Redis
docker-compose.yml
version: '3.1' services: redis: image: daocloud.io/library/redis:5.0.9 container_name: redis ports: - 6379:6379
连接Redis服务:
- 官方提供的客户端:
- 进入到redis的容器内部:docker exec -it redis bash
- 采用配置好环境变量的redis-cli连接:redis-cli [-h 127.0.0.1 -p 6379]
- 图形化界面:
- Redis的软件,直接傻瓜式安装redis-desktop-manager连接Redis服务方式
三、Redis存储数据的结构
Redis提供了八种存储数据的结构,只需要掌握五种即可:
- string:value正常存储一个字符串/byte[]
- hash/map:value可以存储map集合
- list:value可以存储一个列表(允许重复,存取有序,有下标)
- set:value可以存储一个集合(不允许重复,存取无序,没有下标)
- zset(sorted-set):value可以存储一个有序的集合(不允许重复,存取会根据score排序,有下标)
- HyperLogLog:去重计数,空间复杂度极低,采用12kb,可以存储2^64数据。时间复杂度是O(n),虽然空间复杂度比较低,但是计算存在误差。
- GEO:存储地理位置的,可以存储经纬度,并且计算两个经纬度之间的距离等等操作……
- 位图bitmap:存储二级制,只存储0,1两个数据。
[wppay]
四、Redis的基础操作命令
常用的几个小命令:
- 存储数据:
set key value
- 获取数据:
get key
- 设置key的生存时间:
expire key second
- 存储数据并设置生存时间:
setex key second value
- 删除key:
del key
五、 Spring操作Redis
Java操作Redis服务的客户端大概有两种
Jedis
:Redis的命令是什么,Java种操作的方法就是什么!Lettuce
:将Redis的命令进行了一些封装,操作原理不变,提供了很多更方便的方式。
5.1 Jedis操作Redis
创建项目:…………
导入依赖:
<dependencies> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
测试:
@Test public void test(){ //1. 连接Redis Jedis jedis = new Jedis("192.168.41.41",6379); //--------------------------------------------- //2. 操作 jedis.set("birthday","2011-11-11"); //--------------------------------------------- //3. 释放资源 jedis.close(); }
5.2 存储对象
5.2.1 存储JSON
导入依赖:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>
创建POJO类:
public class User { private Integer id; private String name; private Date birthday; // set .... get .... }
测试存储
/** * 推荐采用这种方式. * @throws JsonProcessingException */ @Test public void setJSON() throws JsonProcessingException { //1. 创建连接 Jedis jedis = new Jedis("192.168.41.41",6379); //----------------------------------- //2. 操作 User user = new User(1,"王大拿",new Date()); ObjectMapper mapper = new ObjectMapper(); String value = mapper.writeValueAsString(user); jedis.set("user",value); //----------------------------------- //3. 释放资源 jedis.close(); }
5.2.2 存储byte[]
导入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency>
测试存储
/** * 不推荐使用 */ @Test public void setByteArray(){ //1. 创建连接 Jedis jedis = new Jedis("192.168.41.41",6379); //----------------------------------- //2. 操作 User user = new User(1,"王大拿",new Date()); byte[] value = SerializationUtils.serialize(user); byte[] key = SerializationUtils.serialize("user"); jedis.set(key,value); //----------------------------------- //3. 释放资源 jedis.close(); }
5.3 连接池
频繁的创建和销毁Jedis对象,很消耗性能。准备一个大池子,将创建好的Jedis对象存放进去,用的时候获取一个,用完再换回来。
/** * 一定要将jedis.close()放到finally代码块中 */ @Test public void pool(){ //1. 创建连接池 JedisPool pool = new JedisPool("192.168.41.41",6379); Jedis jedis = null; try { //2. 获取连接对象 jedis = pool.getResource(); //3. 操作 String json = jedis.get("user"); System.out.println(json); } finally { //4. 还连接对象 jedis.close(); } }
5.4 管道(批量操作)
如果需要同步MySQL中的数据到Redis服务中,并且数据条数比较多时,可能会在多次请求和响应的过程中耗时过长。希望可以采用批量操作的效果。
@Test public void test(){ //1. 创建连接池 JedisPool pool = new JedisPool("192.168.41.41",6379); Jedis jedis = null; try { //2. 获取连接对象 jedis = pool.getResource(); System.out.println(System.currentTimeMillis()); /** * 3. 不用管道,同步10万条数据操作,49秒 for (int i = 0; i < 100000; i++) { jedis.set("user" + i, UUID.randomUUID().toString()); } */ /** * 3. 用管道,同步10万条数据操作 ,1秒不到 Pipeline pipelined = jedis.pipelined(); for (int i = 0; i < 100000000; i++) { pipelined.set("age" + i,UUID.randomUUID().toString()); } pipelined.sync(); */ System.out.println(System.currentTimeMillis()); } finally { //4. 还连接对象 jedis.close(); } }
任务:
用Redis解决多台Tomcat之间Session数据无法共享的问题:
业务要求:
- 在用户登录成功之后,将用户的信息存储到Redis中。
- 仿照Session,让Redis的数据,存活30分钟。
- 仿照Session和Cookie中的JSessionID的绑定方式,在Cookie中存储Redis中的key,让用户每次请求可以找到Redis中的value。
- 修改拦截器的代码,让拦截器去Redis中查看客户信息,来判断客户端是否登录过。
- Session活跃时,每次操作会重置Session的超时时间。仿照这个操作,重置Redis中key的生存时间。
代码要求:
- 创建JedisPool的操作,交给Spring工厂。
- 因为要求Jedis.close必须放到finally代码块中,将set,get,expire,setex等命令封装一下。
- 设置key的时候,推荐添加一个统一的前缀。(user:)
完成任务:
导入Jedis依赖
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
编写配置文件,构建JedisPool
<!-- 创建Jedis连接池--> <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <constructor-arg name="host" value="${redis.host}"/> <constructor-arg name="port" value="${redis.port}"/> </bean>
编写Jedis的工具类,封装常用命令
package com.qf.air.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; /** * 封装和Redis的基本操作 */ @Component public class JedisUtil { @Autowired private JedisPool pool; // set public void set(String key,String value){ Jedis jedis = null; try { jedis = pool.getResource(); jedis.set(key,value); } finally { jedis.close(); } } // get public String get(String key){ Jedis jedis = null; try { jedis = pool.getResource(); String value = jedis.get(key); return value; } finally { jedis.close(); } } // expire public void expire(String key,int second){ Jedis jedis = null; try { jedis = pool.getResource(); jedis.expire(key,second); } finally { jedis.close(); } } // setex public void setex(String key,int second,String value){ Jedis jedis = null; try { jedis = pool.getResource(); jedis.setex(key,second,value); } finally { jedis.close(); } } // del public void del(String... key){ Jedis jedis = null; try { jedis = pool.getResource(); jedis.del(key); } finally { jedis.close(); } } }
修改登录功能代码
/** * 3. 将登录成功的用户信息存储到Session中 session.setAttribute("user",user); */ //3. 将登录成功的用户信息存储到Redis中 String keySuffix = UUID.randomUUID().toString(); String key = AirConstants.USER_REDIS_PREFIX + keySuffix; ObjectMapper objectMapper = new ObjectMapper(); String value = objectMapper.writeValueAsString(user); jedisUtil.setex(key,AirConstants.USER_TIMEOUT,value); // 为了让客户端下次请求时,可以找到存储在Redis中的value信息 // 需要将keySuffix响应给客户端(响应体,响应头,Cookie) // 以Cookie的形式,将keySuffix写回 Cookie cookie = new Cookie(AirConstants.USER_COOKIE,keySuffix); cookie.setMaxAge(999999999); cookie.setPath("/"); resp.addCookie(cookie);
修改拦截器的前处理,让从Redis中获取用户信息
//前处理 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /** * 1. 从session中查询用户信息 * User user = (User) request.getSession().getAttribute("user"); */ //1. 从Redis中查询用户信息 //1.1 获取Cookie中的userKey信息 String keySuffix = null; String value = null; Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (AirConstants.USER_COOKIE.equals(cookie.getName())) { // 找到了匹配要求的cookie keySuffix = cookie.getValue(); break; } } } //1.2 不为null,查询Redis if (!StringUtils.isEmpty(keySuffix)) { value = jedisUtil.get(AirConstants.USER_REDIS_PREFIX + keySuffix); } //2.如果登录了,return true,如果没登陆 return false if (value != null) { jedisUtil.expire(AirConstants.USER_REDIS_PREFIX + keySuffix,AirConstants.USER_TIMEOUT); return true; } else { response.sendRedirect(request.getContextPath() + "/login.html"); return false; } }
重新部署到Linux中
- 在IDEA中,将项目重新打包
- 将war包复制到Linux中,并替换/usr/local/docker/air_docker中的air.war即可
- 重新执行docker-compose up -d --build
五、SpringBoot操作Redis
(2条消息) RedisTemplate操作Redis,这一篇文章就够了(一)lydms的博客-CSDN博客redistemplate
六、Redis的操作命令(整理)
简单阐述string,hash,list,set,zset,key,db的操作命令
6.1 string
set key value:设置key和value到string结构
get key:基于key获取value
setex key second value:设置key和value的同时,设置生存时间
setnx key value:set if not exists,如果key不存在,正常的set数据,如果key存在,什么事都不做。
set key value [EX second | PX millionSecond] [NX|XX]:实现setex功能的同时,还能够实现setnx的功能
incr|decr key:对key的value进行自增/自减1,要求value必须是数值,或者key不存在也成。
incrby key increment:对key的value进行自增increment大小,要求value必须是数值,或者key不存在也成。
6.2 hash
hash结构大多是用来存储对象,做缓存操作。
hset key field value:存储数据到hash结构。
hmset key field value [field value ...]: 批量存储数据到hash结构中
hget key field:查询hash结构中某一个field对应的value
hgetall key:根据key直接查询整个hash结构数据
hincrby key field increment:针对hash结构中的某一个field对应的value自增
6.3 list
list结构更多的是用来实现各种数据的结构的,栈,队列。
lpush/rpush key value [value ...]:从左侧/右侧插入多个数据到list结构中。
lpop/rpop key:从左侧/右侧弹出一个数据
lrange key start stop:根据索引范围检索出list中的具体数据,start代表起始索引位置,stop结束索引位置,如果查询全部,stop设置-1,代表最后一条。从0开始
6.4 set
set结构一般用来存储用户的token,set结构不允许重复,做筛选时和比较合适。同时还提供了交集,并集,差集操作,无序。
sadd key member [member ...]: 存储数据到set结构中
sismember key member: 判断你给的member是否存在set结构中
6.5 zset(sortedSet)
zset结构是有序的set,在存储数据的同时,要额外设置一个score,根据score进行排序。
点击量,排行榜,基于score的特点来实现一些业务
zadd key score member [score member ...]:存储数据到zset结构
zincrby key increment member:修改指定member的分数
zrange key start stop [withscores]:根据下标检索数据,添加withscores,会同时返回分数
zrevrange key start stop [withscores]:基于score从大到小排序,并根据下标加索数据
zrangebyscore key min max [withscores] [limit offset count]:基于score分数进行范围检索,查询出指定分数范围的数据,并且可以通过limit指定检索的数据的起始位置和条数
zrevrangebyscore key max min [withscores] [limit offset count]: 基于分数从最大分数检索到最小分数
byscore的检索,如果想设置分数的最大值和最小值,要通过 +inf ,-inf来设置
byscore的检索,默认分数是带有等于效果的,如果不想带等于效果,需要 (score
6.6 key
key的命令只针对key,对于value没有任何关系
expire key second:设置key的生存时间,单位是秒
pexpire key millionSecond:设置key的生存时间,单位是毫秒
expireat key timestamp:设置key能活到什么时候,时间戳的单位是秒
pexpireat key timestamp:设置key能活到什么时候,时间戳的单位是毫秒
ttl | pttl key:查看key剩余生存时间,单位为秒 | 毫秒,如果返回-1代表没有设置生存时间,返回-2key不存在
del key [key...]:删除key
keys pattern:查看当前库中有多少个key,并且打印,pattern支持*通配符
6.7 db(了解)
单机版的Redis默认有16个库
select 0~15:切换库
move key db:移动当前库的key到其他库
flushdb:清空当前库的全部数据
flushall:清空Redis服务的全部数据
lastsave:查看到最后一次写操作时间
monitor:监控操作Redis服务的线程
七、Redis的AUTH
现在连接Redis只需要ip和port,导致Redis服务很不安全。
提升Redis服务的安全性,设置Redis服务的密码连接。
基于Redis的配置文件设置Redis服务的密码
采用Docker的数据卷将redis.conf文件映射到容器内部,并且让容器启动时,加载redis.conf文件。
7.1 Redis服务设置密码
修改Redis的docker-compose.yml文件
version: '3.1' services: redis: image: daocloud.io/library/redis:5.0.9 container_name: redis ports: - 6379:6379 volumes: - ./conf/redis.conf:/data/redis.conf command: ['redis-server','redis.conf']
准备yml文件所在目录下的conf中的redis.conf文件
requirepass 密码
先down掉Redis容器,再重新up -d
7.2 客户端AUTH连接Redis
7.2.1 redis-cli
需要再连接之后,先输入auth 密码通过校验后,才可以执行读写等其他操作命令
7.2.2 Jedis
需要给JedisPool设置Redis服务的密码,保证每一个取出的Jedis对象都可以直接和Redis服务交互
通过有参构造设置
air项目中Redis配置的修改
7.2.3 springboot
配置文件中指定redis的密码
八、Redis的持久化(重要)
Redis基于内存进行读写数据,Redis的效率特别高。
内存存储数据不安全,如果Redis宕机,数据会全部丢失。
Redis官方提供了两种持久化机制。
RDB(Redis Data Base):
- RDB以二进制的文件形式,将内存中的数据持久化到本地。
- RDB的执行时机:在多少秒之内,写了多少key就执行一次RDB操作,执行时机无法保证数据的绝对安全
- 特点:虽然无法保证安全,但是占用空间小,恢复数据快
AOF(Append Only File):
- AOF以日志文件的形式,将执行过的写操作持久化到本地。
- AOF的执行时机:
- 每执行一次写操作,立即将写操作记录的日志中
- 每秒执行一次AOF持久化,将写操作记录的到日志中
- 根据操作系统情况,1~100秒左右,将写操作记录的到日志中
- 特点:虽然尽可能的保证了数据安全,但是占用空间大,恢复数据慢
在正常的生产环境中,Redis官方推荐同时开启RDB和AOF
,做到互补的效果
同时开启RDB和AOF时,注意几点:
- AOF恢复数据时,优先级高于RDB
- 在RDB执行持久化时,会根据AOF持久化文件中的日志,进行持久化。
AOF持久化的是日志文件,可能会由于写操作过多,导致日志文件特别大。官方提供了AOF文件重写机制
:
- 自动重写:会在AOF文件为初始大小的一倍并且大于等于64m时,执行一次AOF重写,将日志文件重写成二进制文件。
- 手动重写:执行一个命令来重写,
BGREWRITEAOF
。
在配置文件中配置:
8.1 开启RDB持久化
RDB默认开启,不需要配置开关
RDB持久化文件的位置,名称。并且重写配置RDB的持久化时机
# 持久化文件位置 dir ./ # 持久化文件名称 dbfilename redis.rdb # 持久化时机 save 10 1
8.2 开启AOF持久化
AOF默认关闭,需要手动开启
配置AOF文件持久化位置和名称,以及持久化时机
# 开启AOF持久化 appendonly yes # AOF文件名称 appendfilename "redis.aof" # 持久化时机,每秒执行一次 appendfsync everysec
九、Redis的事务(分布式锁Redisson)
命令行操作Redis的事务
传统的关系型数据库的事务:一次事务操作要么都成功,要么都失败。
Redis的事务:将一系列的命令放到一个队列中统一执行,该成功的成功,该失败的失败,Redis不保证原子性。
开启事务:multi,开启事务后,所有执行的命令会被放到一个队列中。
提交事务:exec ,提交事务后,会将队列中的命令依次执行。
取消事务:discard ,取消事务,会将队列中的命令丢弃。
一般情况Redis的事务要配合watch机制使用
可以在开启Redis事务之前,先通过watch命令监听一个或多个key,在开启事务后,如果监听的key发生改变,直接执行取消事务操作。
监听key:watch key [key ...]
取消监听:unwatch ,如果提交/取消事务,自动执行unwatch
十、Redis的消息队列(了解)
消息队列,应用场景很多,帮助我们实现服务与服务之间的异步通讯……………
10.1 通过list结构实现
因为list结构提供了lpush和rpush以及lpop和rpop
需要让生产者通过lpush推送消息到list结构中
让消费者通过rpop弹出list结构中的数据
问题:list结构只能实现点对点的方式,如果涉及到一对多或者是广播的形式,list结构实现麻烦。
10.2 Redis提供的消息队列
命令行
为了可以实现一对多或者是广播的形式,可以直接使用Redis提供的发布与订阅模式实现
publish channel message,发布message到指定的channel(频道)
subscribe channel [channel ...],订阅一个或多个channel中的message
- 好处:快
- 坏处:无法保证消息的可靠性,并不是专业做消息队列的。
(后面会学习更专业的MQ,Kafka,RocketMQ,RabbitMQ)
十一、Redis的主从架构(了解)
Redis读写性能高,特别是读操作,所以Redis更多的是用来做缓存。
Redis做缓存提供读操作时,也时有性能瓶颈。
通过Redis的主从架构提升单体Redis服务的读性能瓶颈。
搭建主从架构:docker-compose.yml
version: "3.1" services: redis1: image: daocloud.io/library/redis:5.0.9 container_name: redis1 ports: - 7001:6379 volumes: - ./conf/redis1.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis2: image: daocloud.io/library/redis:5.0.9 container_name: redis2 ports: - 7002:6379 volumes: - ./conf/redis2.conf:/usr/local/redis/redis.conf links: - redis1:master command: ["redis-server","/usr/local/redis/redis.conf"] redis3: image: daocloud.io/library/redis:5.0.9 container_name: redis3 ports: - 7003:6379 volumes: - ./conf/redis3.conf:/usr/local/redis/redis.conf links: - redis1:master command: ["redis-server","/usr/local/redis/redis.conf"]
数据卷映射的配置文件
只需要在redis2.conf和redis3.conf配置即可
# 从节点在对应的conf文件中配置master节点所在的ip和port replicaof master 6379
十二、Redis的哨兵(了解)
哨兵是为了解决主从架构中存在的单点故障问题
单点故障:当Master节点挂掉了,整个集群就挂掉了
Master节点提供读与写操作,而Slave节点只提供读操作
搭建主从+哨兵:docker-compose.yml
version: "3.1" services: redis1: image: daocloud.io/library/redis:5.0.9 container_name: redis1 ports: - 7001:6379 volumes: - ./conf/redis1.conf:/usr/local/redis/redis.conf - ./conf/sentinel1.conf:/data/sentinel.conf # 添加的内容 command: ["redis-server","/usr/local/redis/redis.conf"] redis2: image: daocloud.io/library/redis:5.0.9 container_name: redis2 ports: - 7002:6379 volumes: - ./conf/redis2.conf:/usr/local/redis/redis.conf - ./conf/sentinel2.conf:/data/sentinel.conf # 添加的内容 links: - redis1:master command: ["redis-server","/usr/local/redis/redis.conf"] redis3: image: daocloud.io/library/redis:5.0.9 container_name: redis3 ports: - 7003:6379 volumes: - ./conf/redis3.conf:/usr/local/redis/redis.conf - ./conf/sentinel3.conf:/data/sentinel.conf # 添加的内容 links: - redis1:master command: ["redis-server","/usr/local/redis/redis.conf"]
redis.conf跟之前一样,需要添加sentinel.conf文件
# sentinel monitor <master-name> <ip> <redis-port> <quorum> # 哨兵指定Master节点的名称和ip:port,并且设置哨兵投票至少几票成 sentinel monitor 随便写 master 6379 2 # sentinel auth-pass <master-name> <password> # 如果Master节点有密码,设置密码 # Default is 30 seconds. sentinel down-after-milliseconds mymaster 30000
Redis容器启动之后,进入到每一台Redis容器中,启动哨兵
当Master宕机后,通过日志很清晰的查看到投票选举的过程
哨兵在确定新的Master后,会直接修改Redis的配置文件,实现Master选举!
十三、Redis的集群
Redis集群的出现,是为了解决主从 + 哨兵无法提升Redis存储数据的上限问题,以及写操作瓶颈问题。
13.1 Redis集群架构(重要)
13.2 Redis集群搭建
docker-compose.yml文件
# docker-compose.yml version: "3.1" services: redis1: image: daocloud.io/library/redis:5.0.9 container_name: redis1 ports: - 7001:7001 - 17001:17001 volumes: - ./conf/redis1.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis2: image: daocloud.io/library/redis:5.0.9 container_name: redis2 ports: - 7002:7002 - 17002:17002 volumes: - ./conf/redis2.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis3: image: daocloud.io/library/redis:5.0.9 container_name: redis3 ports: - 7003:7003 - 17003:17003 volumes: - ./conf/redis3.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis4: image: daocloud.io/library/redis:5.0.9 container_name: redis4 ports: - 7004:7004 - 17004:17004 volumes: - ./conf/redis4.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis5: image: daocloud.io/library/redis:5.0.9 container_name: redis5 ports: - 7005:7005 - 17005:17005 volumes: - ./conf/redis5.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis6: image: daocloud.io/library/redis:5.0.9 container_name: redis6 ports: - 7006:7006 - 17006:17006 volumes: - ./conf/redis6.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"]
Redis的配置文件
# 端口号 port 7001 # 开启Redis集群配置 cluster-enabled yes # Redis服务在集群中的信息配置 cluster-config-file nodes-7001.conf # Docker搭建Redis集群时,需要的配置 # 宿主机的ip地址 cluster-announce-ip 192.168.41.41 # 宿主机的port cluster-announce-port 7001 # 消息总线的端口 cluster-announce-bus-port 17001
启动6个Redis服务之后,需要通过Redis容器内部的redis-cli命令开启集群
redis-cli --cluster create 192.168.41.41:7001 192.168.41.41:7002 192.168.41.41:7003 192.168.41.41:7004 192.168.41.41:7005 192.168.41.41:7006 --cluster-replicas 1
输入后会展示集群节点分配的详细信息,输入yes之后,会在控制台出现......,如果出现了一整行还没启动成功,说明配置文件有问题,修改一下。
13.3 Jedis连接Redis集群
Jedis提供了JedisCluster对象和Redis集群交互
public static void main(String[] args) { Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("192.168.41.41",7001)); nodes.add(new HostAndPort("192.168.41.41",7002)); nodes.add(new HostAndPort("192.168.41.41",7003)); nodes.add(new HostAndPort("192.168.41.41",7004)); nodes.add(new HostAndPort("192.168.41.41",7005)); nodes.add(new HostAndPort("192.168.41.41",7006)); JedisCluster jedisCluster = new JedisCluster(nodes); jedisCluster.set("key","value"); }
十四、Redis的删除策略(重要)
Redis最佳实践:推荐将所有存储到Redis中的key,设置过期时间。
如果key的生存时间到了,Redis服务会根据删除策略删除已经过期的key:
- 定时删除:Redis服务会每100ms从Redis服务中随机获取3个设置了生存时间的key,查看生存时间是否到期,如果到期直接删除。
- 惰性删除:当有客户端读取设置了生存时间的key时,Redis服务会先查看剩余的生存时间,如果生存时间已经到期,删除当前key,并返回null。
十五、Redis的淘汰策略(重要)
当向Redis服务申请内存存储key-value数据时,如果空间不足,执行淘汰策略:
- volatile-lru:在设置了生存时间的key中,删除最久没有使用的key
- volatile-lfu:在设置了生存时间的key中,删除最少频次使用的key
- volatile-ttl:在设置了生存时间的key中,删除剩余生存时间最少的key
- volatile-random:在设置了生存时间的key中,随机删除一个key
- allkeys-lru:在所有的key中,删除最久没有使用的key
- allkeys-lfu:在所有的key中,删除最少频次使用的key
- allkeys-ramdom:在所有的key中,随机删除一个key
- noeviction(默认):抛出异常……
volatile-lru -> Evict using approximated LRU among the keys with an expire set. allkeys-lru -> Evict any key using approximated LRU. volatile-lfu -> Evict using approximated LFU among the keys with an expire set. allkeys-lfu -> Evict any key using approximated LFU. volatile-random -> Remove a random key among the ones with an expire set. allkeys-random -> Remove a random key, any key. volatile-ttl -> Remove the key with the nearest expire time (minor TTL) noeviction -> Don't evict anything, just return an error on write operations.
十六、常见的缓存问题 (重要)
缓存改变频次不高,经常被查询的数据。
缓存数据大多数是为了避免数据库压力过大,从数据库查询,并存储到缓存中。
16.1 缓存穿透
缓存中没有数据,数据库中也没有。大量的请求导致数据库宕机
16.2 缓存击穿
热点数据生存时间到期,导致大量请求访问数据库,最终数据库宕机
16.3 缓存雪崩
大量的缓存生存时间同时到期,导致大量请求访问数据库,数据库宕机
16.4 缓存倾斜
超级热点数据导致缓存服务宕机
- 限流
- 缓存
- 从节点
16.5 双写一致性
在修改数据库中的数据后,如何保证数据库和缓存中的数据一致
我要修改数据,避免你读到脏数据所以删除了缓存,你再去读缓存的时候为空,从数据库读,读的时候我可能还没有修改,你就读到了脏数据,我这时候修改了数据库的数据,你因为查缓存的时候为空,会去同步你之前查的脏数据,所以我需要延迟一段时间再删除一次缓存。
[/wppay]