redis的单线程
网络IO和键值对读写是单线程,6.0后网络IO多线程,键值对读写仍然是单线程。
redis快的原因
1、基于内存
2、网络请求和键值对读写单线程,避免线程切换的开销避免加锁
3、数据结构丰富
redis应用场景:
1、做热点数据的缓存。
2、登陆分布式session缓存token
2、点赞统计,排行
2、分布式锁。
3、断电丢失数据可以的话,可以当数据库。
redis缓存一致性
这里说的是redis和mysql的一致性。
更新操作有两个问题:缓存是直接删掉还是更新,先更新数据库数据还是先删缓存。
更新数据的时 缓存是直接删掉还是更新:
1、直接删掉缓存,等下一次的读操作把数据库的数据同步到缓存。(第一次读不会命中缓存)
2、更新数据库时,顺便把缓存也更新了。(更新了却不读白白消耗了时间和性能)
如果写很多,读却很少,那么第2种方式就做了白白做了很多更新缓存的操作。
如果读很多,那么第一种方式不能命中缓存,又要做同步的操作。
建议直接删掉缓存。
更新数据的时,用删掉缓存的方式,先更新数据库数据还是先删缓存:
1、先操作数据库,数据库更新完,在删掉缓存前,读操作会短暂的命中缓存,读到更新前的数据。(短暂的不一致影响小)
2、更新线程先删掉缓存,在更新数据库还没提交,这时候来一个读线程很快的把旧数据同步回缓存, 直到下一个写线程来之前读线程都会命中缓存的旧数据。(影响大)
建议先更新数据库后删除缓存。
如果想硬用先删缓存的方式可以用延迟双删:
1、 删除缓存key
2、 更新数据库
3、 睡眠一个合适的时间
4、再次删掉这个key(尽管有读线程很快同步回缓存了旧数据也会被删掉,后面就不会读旧的数据,但是还是推荐先操作数据库)
//写操作
public void write(String key, Object data) {
Redis.delKey(key); //1、先删除缓存
MySQL.updateData(data); //2、在更新mysql
Thread.sleep(100); //3、睡眠一个合适的时间
Redis.delKey(key); //4、在删掉可能被读取同步回缓存的旧数据
}
如果数据库是读写分离的怎么办:
可以用中间件读取主库bin log。然后在对这个数据的修改去把redis缓存主动更新下。
redis分布式锁
为什么要做分布式锁?
如果一个应用不是单点的,而是分布式部署,那么每个JVM的实例会有各自的内存,锁住的也是各自实例的锁,也就失效了。
只有redis能做吗?
不仅redis能做,Zookeeper和MySQL也能做。
redis做分布式锁注意什么?
1、原子性:加锁、解锁一定要保证原子性:(原子API)
2、不能死锁:给锁一个过期时间,保证这个锁一定能释放:(设置过期时间)
3、加锁与解锁应该是同一个人:(解锁判断valueID)
4、过期时间到了会释放锁,但是加锁的人没执行完,看门狗续key过期时间(watch dog)
redis做分布式锁的实现方式?
加锁:
key是锁,value存的客户端ID,如果这key不存在才设置这个key,并且设置过期时间
public class RedisTool {
//加锁是否成功
private static final String LOCK_SUCCESS = "OK";
//不存在既上锁,存在即不做操作。
private static final String SET_IF_NOT_EXIST = "NX";
//设置过期时间
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
//jedis的原子操作API
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
解锁:
首先获取锁对应的value值,检查是否与requestId相等,如果相等才是加锁的客户端来解锁(删除key)
public class RedisTool {
private static final Long RELEASE_SUCCESS = 1L;
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//因为解锁也要保证原子,所以用eval方法交给redis服务端执行
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
补充:
多机Redis用Redisson(我没用过)Redisson好像有现成的API 。lock、unlock。
watch dog:如果我过期时间10s,但是程序执行了15s,我还没执行完的时候这样别人可以拿到锁,需要我自己过期前续10s。
MySQL分布式锁:利用主键,或者唯一索引唯一性互斥。谁成功添加了这个ID谁拿到锁。
redis事务
redis事务没有原子性:是把一串命令执行,事务里命令执行成功了的命令就是成功了,执行失败的命令就是失败了。不会因为事务里某条命令的失败而回滚。
MULTI:开启事务
EXEC:提交事务
DISCARD:放弃事务
WATCH:监控一个或多个key。key在(EXEC)之前被其他用户修改过,重新获取最新数据操作
UNWATCH:取消监控
redis事务很鸡肋,redis发布订阅也很鸡肋
redis持久化RDB和AOF
RDB全量备份:(故障丢失数据、恢复数据快、体积小)
AOF命令追加:(故障丢失最多修饰一条命令,有命令重写,恢复数据慢相当于在执行一遍)
(4.0之后)混合持久化:在重写策略上做优化。重写的时,把当前内存数据以rdb形式放到到aof文件头部,重写完之后的写命令用增量日志放到aof文件尾,以此往复。这样RDB恢复快,增量数据AOF也完整。
AOF追加策略:
always: 每写入一个命令都追加一次。
everysec: 每一秒都追加一次(默认)。
no: 操作系统自己决定什么时候追加。
1redis有什么操作,
能对永久的键设置过期时间
固定过期,当前设置多久后过期两种
能判断某个键存不存在
String有incr和decr
设置键的过期时间:expire key seconds
能得到key的剩余生存时间:ttl key
删除一个或者多个,key不存在会忽略不会报错正常执行 del key [key…]
String:
理解几个字母的意思
m是多个
nx用的时候键必须不存在
xx键必须存在
设置一个键且赋值,键如果存在会覆盖值:set key value
可以追加,这个命令如果key不存在也不会报错,直接创建这个key:append key value
返回字符串的长度 key不存在没关系:strlen key
可以同时设置一个key value或者多对key value:mset key value [key value…]
可以一次性获取一个或多个值:mget key [key …]
2、 不常用操作有:
2用PAIClinet应该注意什么
用1.8try()的括号释放连接,但是要实现CloseAble接口的close方法
Jedis是否只支持单节点(redis单节点)