Redis
Redis 是一個(gè)開(kāi)源(BSD許可)的,內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)系統(tǒng),它可以用作數(shù)據(jù)庫(kù)、緩存和消息中間件。 它支持多種類型的數(shù)據(jù)結(jié)構(gòu),如 字符串(strings)、散列(hashes)、 列表(lists)、 集合(sets)、 有序集合(sorted sets)
redis-memcached
- 內(nèi)存管理機(jī)制
- Memcached默認(rèn)使用Slab Allocation機(jī)制管理內(nèi)存,其主要思想是按照預(yù)先規(guī)定的大小, 將分配的內(nèi)存分割成特定長(zhǎng)度的塊以存儲(chǔ)相應(yīng)長(zhǎng)度的key-value數(shù)據(jù)記錄,存在內(nèi)存碎片問(wèn)題。類似于Java虛擬機(jī)對(duì)象內(nèi)存分配的空閑列表方式。
- Redis使用現(xiàn)場(chǎng)申請(qǐng)內(nèi)存的方式來(lái)存儲(chǔ)數(shù)據(jù),并且很少使用free-list等方式來(lái)優(yōu)化內(nèi)存分配,會(huì)在一定程度上存在內(nèi)存碎片。類似于Java虛擬機(jī)對(duì)象內(nèi)存分配的指針碰撞方式
- 數(shù)據(jù)持久化
- memcached不支持內(nèi)存數(shù)據(jù)的持久化操作,所有的數(shù)據(jù)都以in-memory的形式存儲(chǔ)。
- redis支持持久化操作。redis提供了兩種不同的持久化方法來(lái)講數(shù)據(jù)存儲(chǔ)到硬盤(pán)里面
- rdb:屬于全量數(shù)據(jù)備份,備份的是數(shù)據(jù)
- aof:append only if,增量持久化備份,備份的是指令
- 緩存數(shù)據(jù)過(guò)期機(jī)制
- Memcached 在刪除失效主鍵時(shí)采用消極方法,即 Memcached 內(nèi)部也不會(huì)監(jiān)視主鍵是否失效,而是在通過(guò) Get 訪問(wèn)主鍵時(shí)才會(huì)檢查其是否已經(jīng)失效
- Redis 定時(shí)、定期等多種緩存失效機(jī)制,減少內(nèi)存泄漏
- 支持的數(shù)據(jù)類型
- Memcached支持單一數(shù)據(jù)類型,[k,v]
- redis支持五種數(shù)據(jù)類型
redis五種數(shù)據(jù)類型
redis事務(wù)機(jī)制
| command | command-type | desc |
|---|---|---|
| MULTI | 開(kāi)啟事務(wù) | MULTI 開(kāi)始一個(gè)事務(wù),然后將多個(gè)命令入隊(duì)到事務(wù)中 |
| EXEC | 觸發(fā)事務(wù) | EXEC 命令觸發(fā)事務(wù), 一并執(zhí)行事務(wù)中的所有命令 |
| DISCARD | 取消事務(wù) | DISCARD 命令用于取消一個(gè)事務(wù), 清空客戶端的整個(gè)事務(wù)隊(duì)列, 然后將客戶端從事務(wù)狀態(tài)調(diào)整回非事務(wù)狀態(tài), 最后返回字符串 OK 給客戶端 |
| WATCH | 監(jiān)控 | WATCH 命令用于在事務(wù)開(kāi)始之前監(jiān)視任意數(shù)量的鍵;當(dāng)調(diào)用 EXEC 命令執(zhí)行事務(wù)時(shí), 如果任意一個(gè)被監(jiān)視的鍵已經(jīng)被其他客戶端修改了, 那么整個(gè)事務(wù)不再執(zhí)行, 直接返回失敗 |
redis消息訂閱發(fā)布
發(fā)布訂閱類似于信息管道,用來(lái)進(jìn)行系統(tǒng)之間消息解耦,類似于消息中間件RocketMQ、RabbitMQ
- PUBLISH 將信息message發(fā)送到指定的頻道channel。返回收到消息的客戶端數(shù)量
- SUBSCRIBE 訂閱給指定頻道的信息
- UNSUBSCRIBE 取消訂閱指定的頻道,如果不指定,則取消訂閱所有的頻道。
redis事務(wù)ACID
-
原子性(Atomicity)
單個(gè) Redis 命令的執(zhí)行是原子性的,但 Redis 沒(méi)有在事務(wù)上增加任何維持原子性的機(jī)制,所以 Redis 事務(wù)的執(zhí)行并不是原子性的。如果一個(gè)事務(wù)隊(duì)列中的所有命令都被成功地執(zhí)行,那么稱這個(gè)事務(wù)執(zhí)行成功
-
一致性(Consistency)
-
入隊(duì)錯(cuò)誤
在命令入隊(duì)的過(guò)程中,如果客戶端向服務(wù)器發(fā)送了錯(cuò)誤的命令,比如命令的參數(shù)數(shù)量不對(duì),等等, 那么服務(wù)器將向客戶端返回一個(gè)出錯(cuò)信息, 并且將客戶端的事務(wù)狀態(tài)設(shè)為 REDIS_DIRTY_EXEC 。
-
執(zhí)行錯(cuò)誤
如果命令在事務(wù)執(zhí)行的過(guò)程中發(fā)生錯(cuò)誤,比如說(shuō),對(duì)一個(gè)不同類型的 key 執(zhí)行了錯(cuò)誤的操作, 那么 Redis 只會(huì)將錯(cuò)誤包含在事務(wù)的結(jié)果中, 這不會(huì)引起事務(wù)中斷或整個(gè)失敗,不會(huì)影響已執(zhí)行事務(wù)命令的結(jié)果,也不會(huì)影響后面要執(zhí)行的事務(wù)命令, 所以它對(duì)事務(wù)的一致性也沒(méi)有影響
-
-
隔離性(Isolation)
WATCH 命令用于在事務(wù)開(kāi)始之前監(jiān)視任意數(shù)量的鍵: 當(dāng)調(diào)用 EXEC 命令執(zhí)行事務(wù)時(shí), 如果任意一個(gè)被監(jiān)視的鍵已經(jīng)被其他客戶端修改了, 那么整個(gè)事務(wù)不再執(zhí)行, 直接返回失敗
-
持久性(Durability)
因?yàn)槭聞?wù)不過(guò)是用隊(duì)列包裹起了一組 Redis 命令,并沒(méi)有提供任何額外的持久性功能,所以事務(wù)的持久性由 Redis 所使用的持久化模式?jīng)Q定
springboot整合redis
pom.xml
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
配置類
package cn.net.redistext.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
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.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(String.class));
/**Jackson序列化 json占用的內(nèi)存最小 */
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
/**Jdk序列化 JdkSerializationRedisSerializer是最高效的*/
/**String序列化*/
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
/**將key value 進(jìn)行stringRedisSerializer序列化*/
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(stringRedisSerializer);
/**將HashKey HashValue 進(jìn)行序列化*/
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
return stringRedisTemplate;
}
@Bean
public KeyGenerator simpleKeyGenerator() {
return (o, method, objects) -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(o.getClass().getSimpleName());
stringBuilder.append(".");
stringBuilder.append(method.getName());
stringBuilder.append("[");
for (Object obj : objects) {
stringBuilder.append(obj.toString());
}
stringBuilder.append("]");
return stringBuilder.toString();
};
}
}
單節(jié)點(diǎn)-resource.xml
spring.redis.database=0
spring.redis.host=139.224.101.91
spring.redis.port=6379
spring.redis.password=redis123
sentinel-resource.xml
spring.redis.database=0
spring.redis.password=redis123
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=139.224.101.91:26379,139.224.101.91:26380,139.224.101.91:26381
集群-resource.xml
spring.redis.timeout=10000
spring.redis.cluster.nodes=139.224.101.91:7001,139.224.101.91:7002,139.224.101.91:7003,139.224.101.91:7004,139.224.101.91:7005,139.224.101.91:7006
spring.redis.cluster.max-redirects=3
redis分布式鎖
分布式鎖
分布式鎖是控制分布式系統(tǒng)或不同系統(tǒng)之間共同訪問(wèn)共享資源的一種鎖實(shí)現(xiàn)
redis分布式鎖特點(diǎn):
- 互斥
- 不會(huì)發(fā)生死鎖
- 鎖的創(chuàng)建者與釋放者一致
Redis分布式鎖的實(shí)現(xiàn)
setnx和setex簡(jiǎn)單實(shí)現(xiàn)
package cn.net.redistext.schedule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class GetLockByNxEx {
private static Logger logger = LoggerFactory.getLogger(GetLockByNxEx.class);
public static final String GETLOCKBYNXEX_LOCK_PREFIX = "getLockByNxEx_lock_";
@Value("${server.port}")
private String serverPort;
@Resource
private RedisTemplate redisTemplate;
@Scheduled(cron = "1/10 * * * * *")
public void operatorByLock() {
String lockKey = GETLOCKBYNXEX_LOCK_PREFIX + "operatorByLock";
boolean setNxResult = false;
try {
// setnx設(shè)置值
setNxResult = redisTemplate.opsForValue().setIfAbsent(lockKey, serverPort);
// 獲取鎖失敗
if (!setNxResult) {
String keyValue = (String) redisTemplate.opsForValue().get(lockKey);
System.out.println("server port: " + serverPort + "獲取鎖失敗,當(dāng)前鎖被服務(wù)端口:" + keyValue + "占用");
return;
}else {
redisTemplate.opsForValue().set(lockKey, serverPort, 3600);
System.out.println("server port: " + serverPort + "獲取鎖成功");
Thread.sleep(5000);
}
} catch (InterruptedException e) {
logger.error("operatorByLock error errorInfo=" + e.getMessage());
} finally {
if (setNxResult) {
if (redisTemplate.hasKey(lockKey)) {
redisTemplate.delete(lockKey);
System.out.println("server port: " + serverPort + "釋放鎖成功?。。?!");
}
}
}
}
}
問(wèn)題分析1
程序運(yùn)行過(guò)程中,Client通過(guò)setnx設(shè)置值成功,未到setex時(shí),程序宕機(jī)或者redis宕機(jī),這種情況下,除非認(rèn)為干預(yù),不然key無(wú)法刪除,一直存在redis中。setnx和setex并不是原子性操作
解決方法
- 引入lua腳本對(duì)setnx和setex進(jìn)行原子性操作
- 使用redisConnection對(duì)setnx和setex進(jìn)行原子性操作
問(wèn)題分析2
釋放鎖先判斷再刪除,不是原子性操作
解決辦法
- 引入lua腳本,實(shí)現(xiàn)釋放鎖的原子性操作
獲取鎖-Lua腳本實(shí)現(xiàn)
從 Redis 2.6.0 版本開(kāi)始,通過(guò)內(nèi)置的 Lua 解釋器,可以使用 EVAL 命令對(duì) Lua 腳本進(jìn)行求值。Redis 使用單個(gè) Lua 解釋器去運(yùn)行所有腳本,并且, Redis 也保證腳本會(huì)以原子性(atomic)的方式執(zhí)行:當(dāng)某個(gè)腳本正在運(yùn)行的時(shí)候,不會(huì)有其他腳本或 Redis 命令被執(zhí)行。這和使用 MULTI / EXEC 包圍的事務(wù)很類似。在其他別的客戶端看來(lái),腳本的效果(effect)要么是不可見(jiàn)的(not visible),要么就是已完成的(already completed)。
-
resource下引入getLock.lua腳本
-- 從參數(shù)中定義本地變量 local lockKey = KEYS[1] local lockValue = KEYS[2] -- 操作setnx local resultNx = redis.call('SETNX', lockKey, lockValue) if resultNx == true then -- setnx成功之后設(shè)置setex local resultEx = redis.call('SETEX', lockKey, 3600, lockValue) return resultNx else -- setnx失敗直接返回 return resultNx end -
代碼實(shí)現(xiàn)
package cn.net.redistext.schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @Service public class GetLockByLua { private static Logger logger = LoggerFactory.getLogger(GetLockByLua.class); public static final String GETLOCKBYNXEX_LOCK_PREFIX = "getLockByLua_lock_"; private DefaultRedisScript<Boolean> lockScript; @Value("${server.port}") private String serverPort; @Resource private RedisTemplate redisTemplate; @Scheduled(cron = "1/10 * * * * *") public void operatorByLock() { String lockKey = GETLOCKBYNXEX_LOCK_PREFIX + "operatorByLock"; boolean setNxResult = false; try { // setnx設(shè)置值 setNxResult = getLockByLua(lockKey, serverPort); // 獲取鎖失敗 if (!setNxResult) { String keyValue = (String) redisTemplate.opsForValue().get(lockKey); System.out.println("server port: " + serverPort + "獲取鎖失敗,當(dāng)前鎖被服務(wù)端口:" + keyValue + "占用"); return; }else { System.out.println("server port: " + serverPort + "獲取鎖成功"); Thread.sleep(5000); } } catch (InterruptedException e) { logger.error("operatorByLock error errorInfo=" + e.getMessage()); } finally { if (setNxResult) { if (redisTemplate.hasKey(lockKey)) { redisTemplate.delete(lockKey); System.out.println("server port: " + serverPort + "釋放鎖成功?。。?!"); } } } } /** * 獲取鎖 */ private Boolean getLockByLua(String key,String value) { lockScript = new DefaultRedisScript<Boolean>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("getLock.lua"))); lockScript.setResultType(Boolean.class); // 封裝參數(shù) List<Object> keyList = new ArrayList<Object>(); keyList.add(key); keyList.add(value); return (Boolean) redisTemplate.execute(lockScript, keyList); } }
獲取鎖-redisConnection實(shí)現(xiàn)
package cn.net.redistext.schedule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.Resource;
@Service
public class GetLockByConn {
private static Logger logger = LoggerFactory.getLogger(GetLockByConn.class);
public static final String GETLOCKBYNXEX_LOCK_PREFIX = "getLockByNxEx_lock_";
@Value("${server.port}")
private String serverPort;
@Resource
private RedisTemplate redisTemplate;
@Scheduled(cron = "1/10 * * * * *")
public void operatorByLock() {
String lockKey = GETLOCKBYNXEX_LOCK_PREFIX + "operatorByLock";
boolean setNxResult = false;
try {
// setnx設(shè)置值
setNxResult = setLock(lockKey, serverPort, 3600L);
// 獲取鎖失敗
if (!setNxResult) {
String keyValue = (String) redisTemplate.opsForValue().get(lockKey);
System.out.println("server port: " + serverPort + "獲取鎖失敗,當(dāng)前鎖被服務(wù)端口:" + keyValue + "占用");
return;
}else {
System.out.println("server port: " + serverPort + "獲取鎖成功");
Thread.sleep(5000);
}
} catch (InterruptedException e) {
logger.error("operatorByLock error errorInfo=" + e.getMessage());
} finally {
if (setNxResult) {
if (redisTemplate.hasKey(lockKey)) {
redisTemplate.delete(lockKey);
System.out.println("server port: " + serverPort + "釋放鎖成功?。。?!");
}
}
}
}
/**
* 重寫(xiě)redisTemplate的set方法
* <p>
* 命令 SET resource-name anystring NX EX max-lock-time 是一種在 Redis 中實(shí)現(xiàn)鎖的簡(jiǎn)單方法。
* <p>
* 客戶端執(zhí)行以上的命令:
* <p>
* 如果服務(wù)器返回 OK ,那么這個(gè)客戶端獲得鎖。
* 如果服務(wù)器返回 NIL ,那么客戶端獲取鎖失敗,可以在稍后再重試。
*
* @param key 鎖的Key
* @param value 鎖里面的值
* @param expire 超時(shí)時(shí)間(秒)
* @return
*/
private boolean setLock(String key, String value, long expire) {
try {
Boolean result = (Boolean) redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.set(key.getBytes(), value.getBytes(), Expiration.seconds(expire) , RedisStringCommands.SetOption.ifAbsent());
}
});
return result;
} catch (Exception e) {
logger.error("set redis lock exception" + e.getMessage());
}
return false;
}
}
釋放鎖-lua腳本實(shí)現(xiàn)
-
resource下引入unLock.lua腳本
-- 從參數(shù)中定義本地變量 local lockKey = KEYS[1] local lockValue = KEYS[2] -- 獲取key的值 local getValue = redis.call('get', lockKey) if getValue == lockValue then -- 釋放鎖 local delResult = redis.call('del', lockKey) return delResult else return false end -
代碼實(shí)現(xiàn)
package cn.net.redistext.schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @Service public class GetLockByLua { private static Logger logger = LoggerFactory.getLogger(GetLockByLua.class); public static final String GETLOCKBYNXEX_LOCK_PREFIX = "getLockByLua_lock_"; private DefaultRedisScript<Boolean> lockScript; @Value("${server.port}") private String serverPort; @Resource private RedisTemplate redisTemplate; @Scheduled(cron = "1/10 * * * * *") public void operatorByLock() { String lockKey = GETLOCKBYNXEX_LOCK_PREFIX + "operatorByLock"; boolean setNxResult = false; try { // setnx設(shè)置值 setNxResult = getLockByLua(lockKey, serverPort); // 獲取鎖失敗 if (!setNxResult) { String keyValue = (String) redisTemplate.opsForValue().get(lockKey); System.out.println("server port: " + serverPort + "獲取鎖失敗,當(dāng)前鎖被服務(wù)端口:" + keyValue + "占用"); return; }else { System.out.println("server port: " + serverPort + "獲取鎖成功"); Thread.sleep(5000); } } catch (InterruptedException e) { logger.error("operatorByLock error errorInfo=" + e.getMessage()); } finally { if (setNxResult) { unLockByLua(lockKey, serverPort); System.out.println("server port: " + serverPort + "釋放鎖成功!?。?!"); } } } /** * 獲取鎖 */ private Boolean getLockByLua(String key,String value) { lockScript = new DefaultRedisScript<Boolean>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("getLock.lua"))); lockScript.setResultType(Boolean.class); // 封裝參數(shù) List<Object> keyList = new ArrayList<Object>(); keyList.add(key); keyList.add(value); return (Boolean) redisTemplate.execute(lockScript, keyList); } /** * 釋放鎖 */ private Boolean unLockByLua(String key,String value) { lockScript = new DefaultRedisScript<Boolean>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("unlock.lua"))); lockScript.setResultType(Boolean.class); // 封裝參數(shù) List<Object> keyList = new ArrayList<Object>(); keyList.add(key); keyList.add(value); return (Boolean) redisTemplate.execute(lockScript, keyList); } }
過(guò)期key清除策略
-
惰性刪除
只有在訪問(wèn)key時(shí),才會(huì)被發(fā)現(xiàn)并主動(dòng)的過(guò)期;但是如果一個(gè)key不再使用,那么它會(huì)一直存在于內(nèi)存中,造成浪費(fèi)
-
定時(shí)刪除
設(shè)置鍵的過(guò)期時(shí)間的同時(shí),創(chuàng)建一個(gè)定時(shí)器(timer),讓定時(shí)器在鍵的過(guò)期時(shí)間來(lái)臨時(shí),立即執(zhí)行對(duì)鍵的刪除操作
-
定期刪除
隔一段時(shí)間,程序就對(duì)數(shù)據(jù)庫(kù)進(jìn)行一次檢查,刪除一定量的過(guò)期鍵
持久化策略
RDB
RDB 是 Redis 默認(rèn)的持久化方案。在指定的時(shí)間間隔內(nèi),執(zhí)行指定次數(shù)的寫(xiě)操作,則會(huì)將內(nèi)存中的數(shù)據(jù)寫(xiě)入到磁盤(pán)中。即在指定目錄下生成一個(gè)dump.rdb文件。Redis 重啟會(huì)通過(guò)加載dump.rdb文件恢復(fù)數(shù)據(jù)
-
文件配置
# 時(shí)間策略 save 900 1 # 表示900s內(nèi)有1條是寫(xiě)入命令,就進(jìn)行一次備份 save 300 10 # 表示300s內(nèi)有10條寫(xiě)入,就進(jìn)行一次備份 save 60 10000 # 表示60s內(nèi)有10000條寫(xiě)入命令,就進(jìn)行一次備份 # 文件名稱 dbfilename dump.rdb # 如果持久化出錯(cuò),主進(jìn)程是否停止寫(xiě)入 stop-writes-on-bgsave-error yes # 是否壓縮 rdbcompression yes # 導(dǎo)入時(shí)是否檢查 rdbchecksum yes # 文件保存路徑 dir /home/redis/soft/redisdata -
觸發(fā)RDB方式
- 手動(dòng)觸發(fā):使用
save或bgsave命令 - 自動(dòng)觸發(fā):
- 根據(jù)配置文件中save m n觸發(fā)持久化
- 從節(jié)點(diǎn)全量復(fù)制時(shí),主節(jié)點(diǎn)發(fā)送rdb文件給從節(jié)點(diǎn)完成復(fù)制操作,主節(jié)點(diǎn)會(huì)觸發(fā)
bgsave - 執(zhí)行
debug reload時(shí) - 執(zhí)行
shutdown時(shí),如果沒(méi)有開(kāi)啟aof,也會(huì)觸發(fā)
- 手動(dòng)觸發(fā):使用
-
禁用RDB
- 配置文件中使用
save ""
- 配置文件中使用
-
數(shù)據(jù)恢復(fù)
- 將dump.rdb 文件拷貝到redis的安裝目錄的bin目錄下,重啟redis服務(wù)即可。在實(shí)際開(kāi)發(fā)中,一般會(huì)考慮到物理機(jī)硬盤(pán)損壞情況,選擇備份dump.rdb 。
-
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn)
- 適合大規(guī)模的數(shù)據(jù)恢復(fù)。
- 如果業(yè)務(wù)對(duì)數(shù)據(jù)完整性和一致性要求不高,RDB是很好的選擇。
- 缺點(diǎn)
- 數(shù)據(jù)的完整性和一致性不高,因?yàn)镽DB可能在最后一次備份時(shí)宕機(jī)了。
- 備份時(shí)占用內(nèi)存,因?yàn)镽edis 在備份時(shí)會(huì)獨(dú)立創(chuàng)建一個(gè)子進(jìn)程,將數(shù)據(jù)寫(xiě)入到一個(gè)臨時(shí)文件,此時(shí)內(nèi)存中的數(shù)據(jù)是原來(lái)的兩倍,最后再將臨時(shí)文件替換之前的備份文件
- 優(yōu)點(diǎn)
AOF
Redis 默認(rèn)不開(kāi)啟。它的出現(xiàn)是為了彌補(bǔ)RDB的不足(數(shù)據(jù)的不一致性),所以它采用日志的形式來(lái)記錄每個(gè)寫(xiě)操作,并追加到文件中。Redis 重啟的會(huì)根據(jù)日志文件的內(nèi)容將寫(xiě)指令從前到后執(zhí)行一次以完成數(shù)據(jù)的恢復(fù)工作。
-
配置文件
# 是否開(kāi)啟aof appendonly yes # 文件名稱 appendfilename "appendonly.aof" # 同步方式 # always:把每個(gè)寫(xiě)命令都立即同步到aof,很慢,但是很安全 # everysec:每秒同步一次,是折中方案 # no:redis不處理交給OS來(lái)處理,非常快,但是也最不安全 appendfsync everysec # aof重寫(xiě)期間是否同步 no-appendfsync-on-rewrite no # 重寫(xiě)觸發(fā)配置 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 加載aof時(shí)如果有錯(cuò)如何處理, yes表示如果aof尾部文件出問(wèn)題,寫(xiě)log記錄并繼續(xù)執(zhí)行。no表示提示寫(xiě)入等待修復(fù)后寫(xiě)入 aof-load-truncated yes # 文件重寫(xiě)策略 aof-rewrite-incremental-fsync yes -
觸發(fā)機(jī)制
- 根據(jù)配置文件觸發(fā),可以是每次執(zhí)行觸發(fā),可以是每秒觸發(fā),可以不同步。
-
數(shù)據(jù)恢復(fù)
- 正常情況下,將appendonly.aof 文件拷貝到redis的安裝目錄的bin目錄下,重啟redis服務(wù)即可。但在實(shí)際開(kāi)發(fā)中,可能因?yàn)槟承┰驅(qū)е耡ppendonly.aof 文件格式異常,從而導(dǎo)致數(shù)據(jù)還原失敗,可以通過(guò)命令redis-check-aof --fix appendonly.aof 進(jìn)行修復(fù) 。
-
重寫(xiě)機(jī)制
- AOF的工作原理是將寫(xiě)操作追加到文件中,文件的冗余內(nèi)容會(huì)越來(lái)越多。而Redis 的重寫(xiě)機(jī)制是當(dāng)AOF文件的大小超過(guò)所設(shè)定的閾值時(shí),Redis就會(huì)對(duì)AOF文件的內(nèi)容壓縮。
-
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):數(shù)據(jù)的完整性和一致性更高
- 缺點(diǎn):因?yàn)锳OF記錄的內(nèi)容多,文件會(huì)越來(lái)越大,數(shù)據(jù)恢復(fù)也會(huì)越來(lái)越慢
布隆過(guò)濾器
- 概念:
- 優(yōu)點(diǎn):
- 相比于其它的數(shù)據(jù)結(jié)構(gòu),布隆過(guò)濾器在空間和時(shí)間方面都有巨大的優(yōu)勢(shì)。布隆過(guò)濾器存儲(chǔ)空間和插入/查詢時(shí)間都是常數(shù)。另外,散列函數(shù)相互之間沒(méi)有關(guān)系,方便由硬件并行實(shí)現(xiàn)。布隆過(guò)濾器不需要存儲(chǔ)元素本身,在某些對(duì)保密要求非常嚴(yán)格的場(chǎng)合有優(yōu)勢(shì)
- 缺點(diǎn)
- 但是布隆過(guò)濾器的缺點(diǎn)和優(yōu)點(diǎn)一樣明顯。誤算率是其中之一。隨著存入的元素?cái)?shù)量增加,誤算率隨之增加。但是如果元素?cái)?shù)量太少,則使用散列表足矣
google布隆過(guò)濾器實(shí)現(xiàn)
-
maven依賴
<!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>21.0</version> </dependency> -
代碼實(shí)現(xiàn)
import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.PostConstruct; @RestController @RequestMapping("/bloomFilter") @Api(value = "/bloomFilter", tags = {"布隆過(guò)濾器"}) public class BloomFilterController { private BloomFilter<Integer> bf ; @RequestMapping("/isExistUser") @ApiOperation(value = "/isExistUser", httpMethod = "GET", notes = "判斷用戶是否存在") public boolean isExistUser (Integer userId) { return bf.mightContain(userId); } /** * 初始化布隆過(guò)濾器 * 默認(rèn)3%的誤判率 * 誤判率可以自定義 */ @PostConstruct public void initBloomFilter() { int i = 100; bf = BloomFilter.create(Funnels.integerFunnel(), i); for (int i1 = 0; i1 < i; i1++) { bf.put(i1); } } }
redis布隆過(guò)濾器實(shí)現(xiàn)
安裝模板
-
下載并編譯模塊
git clone https://github.com/RedisLabsModules/rebloom cd rebloom make -
redis配置文件添加
loadmodule /home/redis/software/rebloom/redisbloom.so -
常用命令
BF.ADD userId 111 BF.EXISTS userId 111 BF.EXISTS userId 222[redis@iZuf62iexj3ztw81eg1cnoZ bin]$ ./redis-cli 127.0.0.1:6379> BF.ADD userId 111 (error) NOAUTH Authentication required. 127.0.0.1:6379> auth redis123 OK 127.0.0.1:6379> BF.ADD userId 111 (integer) 1 127.0.0.1:6379> BF.EXISTS userId 111 (integer) 1 127.0.0.1:6379> BF.EXISTS userId 222 (integer) 0 127.0.0.1:6379>
代碼實(shí)現(xiàn)(lua)
-
addBloomFilter.lua腳本
-- 從參數(shù)中定義本地變量 local bloomName = KEYS[1] local value = KEYS[2] -- 操作BF.ADD local resultNx = redis.call('BF.ADD', bloomName, value) return resultNx -
existBloomFilter.lua腳本
-- 從參數(shù)中定義本地變量 local bloomName = KEYS[1] local value = KEYS[2] -- 操作BF.EXISTS local resultNx = redis.call('BF.EXISTS', bloomName, value) return resultNx -
java代碼
package cn.net.redistext.controller; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.models.auth.In; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.scripting.support.ResourceScriptSource; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @RestController @RequestMapping("/redisBloomFilter") @Api(value = "/redisBloomFilter", tags = {"redis布隆過(guò)濾器"}) public class RedisBloomFilterController { private static final String BLOOM_FILTER_USER_ID = "USER-INFO"; @Resource private RedisTemplate redisTemplate; @RequestMapping("/addBloomFilter") @ApiOperation(value = "/addBloomFilter", httpMethod = "GET", notes = "redisBloomFilter添加元素") public boolean addBloomFilter(String userId) { DefaultRedisScript<Boolean> addBloomFilter = new DefaultRedisScript<Boolean>(); addBloomFilter.setScriptSource( new ResourceScriptSource(new ClassPathResource("addBloomFilter.lua"))); addBloomFilter.setResultType(Boolean.class); // 封裝參數(shù) List<Object> keyList = new ArrayList<Object>(); keyList.add(BLOOM_FILTER_USER_ID); keyList.add(userId); return (Boolean) redisTemplate.execute(addBloomFilter, keyList); } @RequestMapping("/existBloomFilter") @ApiOperation(value = "/existBloomFilter", httpMethod = "GET", notes = "redisBloomFilter判斷元素是否存在") public boolean existBloomFilter(String userId) { DefaultRedisScript<Boolean> existBloomFilter = new DefaultRedisScript<Boolean>(); existBloomFilter.setScriptSource( new ResourceScriptSource(new ClassPathResource("existBloomFilter.lua"))); existBloomFilter.setResultType(Boolean.class); // 封裝參數(shù) List<Object> keyList = new ArrayList<Object>(); keyList.add(BLOOM_FILTER_USER_ID); keyList.add(userId); return (Boolean) redisTemplate.execute(existBloomFilter, keyList); } }?