持久化
- RDB持久化 全量同步 存储到dump.rdb文件 。(redis.conf文件中,save 900 1,900秒内至少一次修改则保存rdb文件,做同步)
- AOF持久化 增量同步 (conf文件中,每次写操作都会被记录,appendfsync everysec(默认)/always。
淘汰策略
noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错。
allkeys-lru:在主键空间中,优先移除最近未使用的key。(推荐)
volatile-lru:在设置了过期时间的键空间中,优先移除最近未使用的key。
allkeys-random:在主键空间中,随机移除某个key。
volatile-random:在设置了过期时间的键空间中,随机移除某个key。
volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除。
- 设置redis内存大小
在redis.conf文件中,"maxmemory <bytes>" 设置Redis 内存大小的限制,比如:maxmemory 300mb。当数据达到限定大小后,会选择配置的策略淘汰数据
- 设置redis淘汰策略
通过配置"maxmemory-policy"设置Redis的淘汰策略。比如:maxmemory-policy volatile-lru
Redis自动过期机制(如订单超时)
key有效期,过期回调
- 开启:
当我们的key失效时,可以执行我们的客户端回调监听的方法。
需要在Redis中配置: notify-keyspace-events "Ex"
- 部分代码:
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* 待支付
*/
private static final Integer ORDER_STAYPAY = 0;
/**
* 失效
*/
private static final Integer ORDER_INVALID = 2;
@Autowired
private OrderMapper orderMapper;
/**
* Redis失效事件 key
*
* @param message
* @param pattern
*/
@Override
public void onMessage(Message message, byte[] pattern) {
String expiraKey = message.toString();
// 根据key查询 value 如果还还是为待支付状态 将订单改为已经超时~~
OrderEntity orderNumber = orderMapper.getOrderNumber(expiraKey);
System.out.println(expiraKey);
if (orderNumber == null) {
return;
}
if (orderNumber.getOrderStatus().equals(ORDER_STAYPAY)) {
// 将订单状态改为已经失效
orderMapper.updateOrderStatus(expiraKey, ORDER_INVALID);
}
}
}
事务操作
multi 开启事务(开始事务后,所有set存放在队列中,exec后一起提交,提交之后才能查到)
exec 提交事务
watch 可以监听一个或者多个key,在提交事务之前是否有发生了变化 如果发生边了变化就不会提交事务,没有发生变化才可以提交事务(原理 版本号码 乐观锁 )
discard 取消提交事务
例:
watch name
multi
set name xiaoxiao
exec
注意:Redis官方是没有提供回滚方法, 只提供了取消事务。
分布式锁
RedisUtil(获取redis连接)
public class RedisUtil {
//protected static Logger logger = Logger.getLogger(RedisUtil.class);
private static String IP = "127.0.0.1";
//Redis的端口号
private static int PORT = 6379;
//可用连接实例的最大数目,默认值为8;
//如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
private static int MAX_ACTIVE = 100;
//控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
private static int MAX_IDLE = 20;
//等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
private static int MAX_WAIT = 3000;
private static int TIMEOUT = 3000;
//在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
private static boolean TEST_ON_BORROW = true;
//在return给pool时,是否提前进行validate操作;
private static boolean TEST_ON_RETURN = true;
private static JedisPool jedisPool = null;
/**
* redis过期时间,以秒为单位
*/
public final static int EXRP_HOUR = 60 * 60; //一小时
public final static int EXRP_DAY = 60 * 60 * 24; //一天
public final static int EXRP_MONTH = 60 * 60 * 24 * 30; //一个月
/**
* 初始化Redis连接池
*/
private static void initialPool() {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_ACTIVE);
config.setMaxIdle(MAX_IDLE);
config.setMaxWaitMillis(MAX_WAIT);
config.setTestOnBorrow(TEST_ON_BORROW);
jedisPool = new JedisPool(config, IP, PORT, TIMEOUT, null);
} catch (Exception e) {
//logger.error("First create JedisPool error : "+e);
e.getMessage();
}
}
/**
* 在多线程环境同步初始化
*/
private static synchronized void poolInit() {
if (jedisPool == null) {
initialPool();
}
}
/**
* 同步获取Jedis实例
*
* @return Jedis
*/
public synchronized static Jedis getJedis() {
if (jedisPool == null) {
poolInit();
}
Jedis jedis = null;
try {
if (jedisPool != null) {
jedis = jedisPool.getResource();
}
} catch (Exception e) {
e.getMessage();
// logger.error("Get jedis error : "+e);
}
return jedis;
}
/**
* 释放jedis资源
*
* @param jedis
*/
public static void returnResource(final Jedis jedis) {
if (jedis != null && jedisPool != null) {
jedisPool.returnResource(jedis);
}
}
public static Long sadd(String key, String... members) {
Jedis jedis = null;
Long res = null;
try {
jedis = getJedis();
res = jedis.sadd(key, members);
} catch (Exception e) {
//logger.error("sadd error : "+e);
e.getMessage();
}
return res;
}
}
RedisLockUtil(获取事务锁,释放事务锁)
public class RedisLockUtil {
private static int lockSuccess = 1;
/**
* @param lockKey 在Redis中创建的key值
* @param notLockTime 尝试获取锁超时时间
* @return 返回lock成功值
*/
public String getLock(String lockKey, int notLockTime, int timeOut) {
//获取Redis连接
Jedis jedis = RedisUtil.getJedis();
// 计算我们尝试获取锁超时时间
Long endTime = System.currentTimeMillis() + notLockTime;
// 当前系统时间小于endTime说明获取锁没有超时 继续循环 否则情况下推出循环
while (System.currentTimeMillis() < endTime) {
String lockValue = UUID.randomUUID().toString();
// 当多个不同的jvm同时创建一个相同的redisKey 只要谁能够创建成功谁就能够获取锁
if (jedis.setnx(lockKey, lockValue) == lockSuccess) {
// 加上有效期
jedis.expire(lockKey, timeOut / 1000);
return lockValue;
// 退出循环
} // 否则情况下 继续循环
}
try {
if (jedis != null) {
jedis.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 释放锁
* @return
*/
public boolean unLock(String locKey, String lockValue) {
//获取Redis连接
Jedis jedis = RedisUtil.getJedis();
try {
// 判断获取锁的时候保证自己删除自己
if (lockValue.equals(jedis.get(locKey))) {
return jedis.del(locKey) > 0 ? true : false;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
return false;
}
}
OrderService(测试)
public class OrderService {
private static final String LOCK_KEY = "lock";
public static void service() {
// 1.获取锁
RedisLockUtil redisLock = new RedisLockUtil();
String lockValue = redisLock.getLock(LOCK_KEY, 5000, 15000);
if (StringUtils.isEmpty(lockValue)) {
System.out.println(Thread.currentThread().getName() + ",获取锁失败了");
return;
}
// 执行我们的业务逻辑
System.out.println(Thread.currentThread().getName() + ",获取锁成功:lockValue:" + lockValue);
// 3.释放锁
redisLock.unLock(LOCK_KEY, lockValue);
}
public static void main(String[] args) {
service();
}
/***
*
* 尝试获取锁为什么次数限制?
* 如果我们业务逻辑5s 内没有执行完毕呢?
*
* 分场景:
* 1.锁的超时时间根据业务场景来预估
* 2.可以自己延迟锁的时间
* 3.在提交事务的时候检查锁是否已经超时 如果已经超时则回滚(手动回滚)否则提交。
*
* 仅限于单机版本
*/
}
主从复制
- 基本概念
单个Redis如果因为某种原因宕机的话,可能会导致Redis服务不可用。
可以使用主从复制实现一主多从,主节点负责写的操作,从节点负责读的操作,主节点会定期将数据同步到从节点中,保证数据一致性的问题。
- 相关配置
Redis.conf
# replicaof <masterip> <masterport>
slaveof 192.168.212.155(主Redis服务器ip) 6379
masterauth 123456(密码)
查看主从节点命令: info replication
- 主从复制原理过程
1.需要在从redis 服务器配置在slaveof 执行 主redis 服务ip地址和端口号192.168.1.110:6379 (主的服务) 和密码;
2.从Redis 服务器和主Redis 服务器建立Socket 长连接;
3.采用全量和增量形式将数据同步给从 Redis 服务器.
全量:从Redis 首次启动的时候 (二进制执行文件文件)rdb文件
增量:主Redis 每次有新的Set 请求时候 aof日志文件
哨兵机制
- 哨兵机制原理
- 哨兵机制每个10s时间只需要配置监听我们的主节点就可以获取当前整个Redis集群的环境列表,采用info 命令形式。
- 哨兵不建议是单机的,最好每个Redis节点都需要配置哨兵监听。
- 哨兵集群原理是如何:多个哨兵都执行同一个主的master节点,订阅到相同都通道,有新的哨兵加入都会向通道中发送自己服务的信息,该通道的订阅者可以发现新哨兵的加入,随后相互建立长连接。
- Master的故障发现 单个哨兵会向主的master节点发送ping的命令,如果master节点没有及时的响应,哨兵会认为该master节点为“主观不可用状态”会发送给其他都哨兵确认该Master节点是否不可用,当前确认的哨兵节点数>=quorum(可配置),会实现重新选举。