前言:本文为原创 若有错误欢迎评论!
一.Redis分布式锁
1.注入配置好的序列化方式的redisTemplate
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, String> redisTemplate = new RedisTemplate<String,String>();
redisTemplate.setConnectionFactory(factory);
/**
* 定义好三种序列化方式:Jackson序列化、Jdk序列化、String序列化
*/
/**Jackson序列化 json占用的内存最小 */
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
/**Jdk序列化 JdkSerializationRedisSerializer是最高效的*/
// JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
/**String序列化*/
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
/**
* 设置不同数据类型的序列化方式
* /
/**将key value 进行stringRedisSerializer序列化*/
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(stringRedisSerializer);
/**将HashKey HashValue 进行序列化*/
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
2.在service层建立一个分布式锁的类 先注入RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
3.引入获取当前ip的地址的方法 方便作为分布式每台机器对应的value
private static String getHostIp() {
try {
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()) {
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress ip = (InetAddress) addresses.nextElement();
if (ip != null
&& ip instanceof Inet4Address
&& !ip.isLoopbackAddress() //loopback地址即本机地址,IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255
&& ip.getHostAddress().indexOf(":") == -1) {
return ip.getHostAddress();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
4.setlock()方法:实现同时对redis执行setnx和setex命令–lua脚本(推荐)
- 在resources下新建add.lua:
local lockKey = KEYS[1] local lockValue = KEYS[2] -- setnx info local result_1 = redis.call('SETNX', lockKey, lockValue) if result_1 == true then local result_2= redis.call('SETEX', lockKey,拿到锁的时间单位是秒, lockValue) return result_1 else return false end
- setLock方法:
public Boolean setlock(String key) { //读取脚本文件 lockScript = new DefaultRedisScript<Boolean>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("add.lua"))); lockScript.setResultType(Boolean.class); // 封装参数 List<Object> keyList = new ArrayList<Object>(); keyList.add(key); keyList.add(getHostIp()); //运行脚本文件 并返回结果 Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList); return result; }
5.setlock()方法:实现同时对redis执行setnx和setex命令–Jedis(个人不推荐)
- Jedis是redis的java客户端
public boolean setLock(String key, long expire) { try { Boolean result = redisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.set(key.getBytes(), getHostIp().getBytes(), Expiration.seconds(expire) ,RedisStringCommands.SetOption.ifAbsent()); } }); return result; } catch (Exception e) { logger.error("set redis occured an exception", e); } return false; }
6.releaseLock()方法
- 注意:直接在redis删除这个key的话可能锁已经提前释放 即value已经是另一个ip 所以要再做判断
- 建立unlock.lua:
local lockKey = KEYS[1] local lockValue = KEYS[2] -- get key local result_1 = redis.call('get', lockKey) if result_1 == lockValue then local result_2= redis.call('del', lockKey) return true else return false end
- releaseLock方法
private boolean releaseLock(String key, String value) { //读取脚本文件 lockScript = new DefaultRedisScript<Boolean>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("unlock.lua"))); lockScript.setResultType(Boolean.class); // 封装参数 List<Object> keyList = new ArrayList<Object>(); keyList.add(key); keyList.add(value); //执行脚本文件 Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList); return result; }
7.如何调用
-
在要执行的方法最开始 先定义好锁(redis的key)的名称 与bool类型是否拿到锁的标志
String lock = “LockNxExJob”;
boolean ret = false; -
try/catch和finally要加锁的代码块 然后在try的代码最开始 调用给setlock() 并用定义的ret=返回bool类型(是否拿到锁)
-
判断ret是否为true(即是否拿到了锁)拿到就执行业务逻辑
-
在finally中判断是否拿到锁 拿到执行releaseLock方法
@Test
public void test() {
String lock = LOCK_PREFIX + "JedisNxExJob";
boolean lockRet = false;
try {
lockRet = this.setLock(lock, 600);
//获取锁失败
if (!lockRet) {
String value = (String) redisTemplate.getValue(lock);
//打印当前占用锁的服务器IP
logger.info("jedisLockJob get lock fail,lock belong to:{}", value);
return;
} else {
//获取锁成功
logger.info("jedisLockJob start lock lockNxExJob success");
Thread.sleep(5000);
}
} catch (Exception e) {
logger.error("jedisLockJob lock error", e);
} finally {
//释放锁
if (lockRet) {
logger.info("jedisLockJob release lock success");
releaseLock(lock,getHostIp());
}
}
}
二.Redis读写分离(主从)
- 主节点是可以读和写 从节点只能读 可以减小主节点压力 且从节点是主节点的备份
1.配置redis:
-
进到src同级目录下
-
mkdir redis-replication
-
cd redis-replication
-
mkdir 6379 6380
-
cp …/redis.conf 6379(复制配置文件 启动时带上各自的配置文件)
-
cp …/redis.conf 6380
-
编辑conf文件
在所有节点的redis.conf:daemonize no 改为 yes #设置为守护进程
在从节点的redis.conf中:
slaveof <> <> # 改为 slaveof 主节点ip 主节点端口 port 6379 # 改为 port 从节点 pidfile:/var/run/redis.pid # 改为/var/run/redis_从节点端口号.pid dbfilename dump.rdb # 改为dump_从节点端口号.rdb appendfilename "appendonly.aof" # 改为appendonly_从节点端口号.aof
8.带配置文件启动两个节点
9.redis客户端的几个命令:
info Replication:查看当前客户端信息
slaveof no one:如果是从节点就变为主节点
slaveof ip地址 端口:成为这个ip和端口对应的从节点)
三.Redis高可用架构Sentinel(哨兵机制)
- Sentinel端口:26379
1.配置redis
-
完成二的步骤并启动每个节点
-
进入和redis.conf同级的目录 找到sentinel.conf
-
只复制一份到刚才的redis-replication的目录下
-
编辑
# Sentinel monitor <master-name> <ip><port> <quorum> # <master-name> sentinel要监控的主节点的名称,或者应该说叫集群名字 # <ip><port> 启动时主节点的地址,后面如果主节点下线了会自动故障迁移,所以主节点并不总是该 ip:port # <quorum> 代表要判定主节点最终不可达所需要的票数 sentinel monitor mymaster 127.0.0.1 6379 1 #sentinel认为的主观下线时间 (即:和sentinel一直没有交互的时间) sentinel down-after-milliseconds mymaster 10000 # 故障转移超时时间,作用于各个阶段。 # A) 选出合适从节点 # B) 晋升选出的从节点为主节点 # C) 命令其余从节点复制新的主节点 # D) 等待原主节点恢复后命令它去复制新的主节点 sentinel failover-timeout mymaster 60000 # 故障转移后,有多少个从节点可以同时重新同步主节点 # 该参数主要为了一个主从复制集群全部都在进行复制,从而导致无法对外提供服务 sentinel parallel-syncs mymaster 1 #最后一步:搜索monitor 把系统默认那个注销掉
-
进入src (然后带配置文件启动sentinel)
./redis-sentinel [sentinel的配置文件位置]
启动之后就会直接显示节点的所有信息 可以用:pss-ef|grep redis 看见有开了一个进程
2.SpringBoot整合sentinel
- 通过sentinel的代理 可以通过配置文件设置的主节点地址访问及分配所有节点
在配置文件中加:spring: redis: sentinel: master: 在配置sentinel.conf中用的名字 (如:mymaster) nodes: sentinel的ip地址:26379 (如果有多个用","分割)
四.Redis集群
- 参考我的博客:https://blog.csdn.net/weixin_43934607/article/details/102668918
五.Redis布隆过滤器
- 是一种数据结构 查询的性能更高 且占内存更少 常用作在批量数据中过滤出预先保留的数据 如:秒杀将已经购买的uid放入 然后在后面后面购买的人取出来过滤)
1.安装:https://blog.csdn.net/ChenMMo/article/details/93615438
2.命令(redis的客户端使用):
bf.add key value1(添加 一个key可以添加多个value)
bf.add key value2
bf.madd key value1 value2(一次添加多个key对应的value)
bf.exists key value(判断是否存在 要同时有key和value)
del key(删除的话就是和一般的key一样删除 只是一种数据结构)
3.SpringBoot整合:编写lua脚本使用