您的位置:首页 > 数据库 > Redis

redis--分布式锁+分布式定时任务

2019-04-15 10:36 417 查看

1.1:redis特性:
速度快----- 数据存在基于内存,c语言编写,
持久化-----基于内存断电不保存所以对数据的更新将异步的保存到磁盘中
单线程-----无线程冲突

1.2:数据结构(主要前五种):
string(字符串): 字符串,json串,整数或浮点数,string 类型是二进制安全的,意思是 Redis 的 string 可以包含任何数据,
比如图片或者序列化的对象,一个 redis 中字符串 value 最多可以是 512M
hash(哈希):hash 是一个键值对集合,是一个 string 类型的 key和 value 的映射表,key 还是key,
但是value是一个键值对(key-value)。类比于 Java里面的 Map<String,Map<String,Object>> 集合。
list(列表): list 列表,它是简单的字符串列表,按照插入顺序排序,
你可以添加一个元素到列表的头部(左边)或者尾部(右边),它的底层实际上是个链表。
set(集合): Redis 的 set 是 string 类型的无序集合。。
zset(sorted set:有序集合), string 类型的无序集合但他是有序的
BitMaps:位图
HyperLogLog:超小内存唯一值计数
GEO:地理信息定位
支持多种编程语言,功能丰富,主从复制为高可用分布式做基础

1.3:常用的应用场景:
缓存系统,计数器,如转发数评论数,消息队列系统,对消息队列要求不强的
社交网络,排行榜,实时系统, 分布式id生成器 : incre id 单线程不会重复等
yml配置:
redis:
database: 0 redis数据库
host:
port:
password:
jedis:
pool:
max-active: 18
max-wait: -1
max-idle: 16
min-idle: 0
timeout: 1000ms

1.4:常见问题
1.redis的过期策略以及内存淘汰机制
过期策略是定期删除+惰性删除。首先第一点为什不用定时删除策略,定时删除用一个定时器来负责监视key,过期则自动删除,虽然内存及时释放,但十 分消耗cpu资源,在大并发请求下cpu要将时间应用在处理请求而不是删除key上
第二点定期删除和惰性删除是如何工作的:定期删除,redis默认每个100ms检查随机检查,是否有过期的key,有则删除,因为是随机的,所以可能有过期的但未删除,这个时候就需要惰性删除,当你获取key时,检查此key是否过期,过期则删除,第三点两者结合后的弊端:当过期没随机到也没请求还是不被删除
第四点解决方案是采用内存淘汰机制
在redis.conf里: 常用的allkeys-lru 内存淘汰机制 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的
2. 缓存穿透和缓存雪崩(大公司才有)
第一点:缓存穿透(高频率请求缓存中不存在的数据,直接堆到数据库)
1利用互斥锁,如果缓存失效先去获得锁再去请求数据库 2提供利用布隆过滤器判断key是否合法
第二点缓存雪崩 同一时间大面积失效,下一波请求来了---------给失效时间增加随机值,避免集体失效

3.分布式数据库和缓存双写一致的问题
一般防止网络原因的线程安全和查询脏数据的问题:A进行写操作,删除缓存,B查询发现缓存不见了,查询到旧数据写入缓存,A更新数据库。这样永远是脏数据。
解决方案是:先删除缓存再写入数据库然后延时双删,再淘汰缓存,这里防止再淘汰失败,加上重试机制
先淘汰缓存,再写入数据库,休眠一秒(根据写的大致耗时加上几百ms),再淘汰缓存
public void write(String key,Object data){
jedis.delkey(key);
Db.update(data);
Thread.sleep(1000);
jedis.delkey(key);
}
对于主从分离的框架,b查到的是从表的旧数据,那么休眠时间是B更新到A用到的时间加几百ms 这里的删除可以做异步处理的timer,避免影响吞吐量。
这里的重试机制是:启动一个订阅程序去订阅数据库的binlog,mysql工具canal
步骤:1.更新数据库数据 2.数据库将操作信息写入binlog日志当中 3订阅程序提取出所需要的数据以及key
3.另起一段非业务代码,获取该信息 5,尝试删除缓存操作,发现删除失败6.将这些信息发送到信息队列 7,重新从消息队列获取该数据,重试操作
1.cannal的使用:抽取实时数据到任意存储服务
分服务端和消费端
服务端连接至不同的mysql实例,并为每个实例维护一个事件消息队列
客户端可以订阅这些队列中的数据的变更事件,处理并存到数据仓库
使用BinLog和Canal从mysql抽取数据
MySQL Binlog 则是一种实时的数据流,用于主从节点之间的数据复制,我们可以利用它来进行数据抽取
canal会将自己伪装成Mysql的从节点(Slave),并从主节点Master获取BinLog,解析并贮存

分布式锁和解决分布式定时任务和并发争夺key问题
一.前言:
分布式锁一般有三种实现方式:
1. 数据库乐观锁;
2. 基于Redis的分布式锁;
3.基于ZooKeeper的分布式锁。

二.使用redis的优势
–利用redis的自动过期(不会死锁)和分布式锁的特性,key是唯一的,单线程
分布式锁的要求:互斥性:只能一个持有,加锁和解锁必须同一个,不会发生死锁,容错性

三.redis分布式锁的实现
加锁和解锁原理就是
做A这件事需要锁机制,那么商议谁做A这件事就在redis里以A为key设立个值。如果别人想做这件事就去redis里看看有没有这个key如 果没有就去做。分布式定时任务,则要挑选一台来执行,而不是各个服务都去执行
对于分布式定时任务,我们主要做到3点
只在一台机器上执行定时任务,某台宕机后下一次执行能进行故障迁移,定位正在执行的机器

先引入jedis依赖

2.redisconfig配置redis


3.QuartzAop

里面一共有三个方法,其中webtoolUtil是封装的获取geiIpUtil的方法
第一个方法checkLock方法,给锁加上固定的过期时间的方法

第二个方法是checkStatus,拿到key和ip等信息

public boolean checkStatus(String lockKey){
String key =lockKey;
try {
while (true) { // 这个接口必然是并发的,所以加分布式锁
boolean lock = checkLock(key,1); // 一秒的超时时间
if (lock) {
break; // 获取到锁,才能跳出
}
}
String ip="";
try {
ip = WebToolUtils.getLocalIP();
} catch (UnknownHostException e) {
log.error(e.getMessage());
return false;
} catch (SocketException e) {
log.error(e.getMessage());
return false;
}
String ipAndPort = ip+":"+serverPort;
// 获取服务器上的工作ip
String currentIpAndPort = (String)redisTemplate.opsForValue().get(key);
log.info(“缓存服务器上面的key值”+key);
log.info(“缓存器上面的工作ip”+currentIpAndPort);
log.info(“本服务器ip”+ipAndPort);
// 如果为空的时候,设置进去
if(currentIpAndPort == null){
https://RedisUtil.setex(key, ip, 10);
redisTemplate.opsForValue().set(key, ipAndPort, 10, TimeUnit.SECONDS);
return true;
}
// 就是当前机器,则返回true
if(currentIpAndPort.equals(ipAndPort)){
return true;
}else{
log.info(“没有得到执行权”);
return false;

}
} catch (Exception e) {
log.info("没有得到执行权");
log.error(e.getMessage());
return false;
}
}

@Around("@annotation(org.springframework.scheduling.annotation.Scheduled)")
public void around(ProceedingJoinPoint jp) throws Throwable{
if(checkStatus("schedular_root:carControl:"+jp.getSignature().getName())){ //环绕执行,前置获得锁
String ip = WebToolUtils.getLocalIP();
log.info("现在正在执行"+jp.getSignature()+":"+ip);
log.info("执行方法名"+jp.getSignature().getName());
jp.proceed();                                                                                                      //让目标方法执行
redisTemplate.delete("lock:schedular_root:carControl:"+jp.getSignature().getName());//方法执行后删除
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: