笔记
redis
各种数据结构的使用
- 字符类型
一个字符类型的key默认存储的最大容量是512m
set key value
get key
incr key
append key value
strlen key 获取key对应的value长度
mget key key 同时获得多个key的value - 列表类型
list,可以存储一个有序的字符串列表,可以用来做分布式消息队列
lpush/rpush
llen num
lrange key start stop
lpop/rpop - 散列类型
hash, 比较适合存储对象
hash key value
hset key field value
hexists key field 判断字段是否存在。存在返回1,不存在返回0 - 集合类型
set跟list不一样的店。集合类型不能存重复的数据,而且是无序的
sadd key member [member …] 增加数据
srem key member 删除元素
smembers key 获取所有数据
sdiff key key 对多个集合执行差集运算 - 有序集合
zadd key score member
如果两个元素的score相同的话,那么根据(0<9<A<Z<a<z)方式从小到大
分布式锁的解决方案
- 数据库锁
数据库,通过唯一约束
lock(
id int(11),
method_name varchar(100),
remark varchar(1000),
modify_time timestamp
unique key uk_mn(method_name) -- 唯一约束
)
获取锁的伪代码
try{
exec insert into lock(methodName, remark)
values('method', 'desc');
return true;
} catch (DuplicateException e){
return false;
}
//释放锁
delete from lock where methodName='xxx';
需要考虑的问题
- 锁没有失效时间,一旦释放锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得锁
- 锁是非阻塞的,数据的insert操作,一旦插入失败就会直接报错
- 锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁
基于缓存的分布式锁实现
public class RedisManager {
private static JedisPool jedisPool;
static {
JedicPoolConfig jpc = new JedicPoolConfig();
jpc.setMaxTotal(20);
jpc.setMaxIdle(10);
jedisPool = new JedisPool(jpc, "192.168.11.140", 6379);
}
public static Jedis getJedis() throws Exception {
if (null != jedisPoll) {
return jedisPool.getResource();
}
throw new Exception("");
}
}
//获得锁
public String getLock(String key, int timeout) {
try {
Jedis jedis = RedisManager.getJedis();
String value = UUID.randomUUID().toString();
long end = System.currentTimeMillis() + timeout;
while (System.currentTimeMillis() < end) { //阻塞
if (jedis.setnx(key,value) == 1) {
jedis.expire(key, timeout);
//锁设置成功,redis操作成功
return value;
}
if (jedis.ttl(key) == -1) {//检测过期时间
jedis.expire(key, timeout);
}
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//释放锁
public boolean releaseLock(String key, String value) {
try {
Jedis jedis = RedisManager.getJedis();
while(true) {
jedis.watch(key);
if(value.equals(jedis.get(key)) {
Transaction ts = jedis.multi();
ts.del(key);
List<Object> list = ts.exec();
if(list == null) {
continue;
}
return true;
}
jedis.unwatch();
break;
}
} catch (Exception e) {
}
}
redis持久化
RDB的持久化策略:按照规则定时将内存的数据同步到磁盘
redis在指定情况下回触发快照
- 自己配置的快照规则
save [second] [changes] 当在second秒内被更改的key的数量大于changes时,执行快照 - save或者bgsave
save:执行内存的数据同步到磁盘操作,这个操作会阻塞客户端的请求
bgsave:在后台异步执行快照操作,不会阻塞客户的请求 - 执行flushall的时候
清除内存的所有数据,只要快照的规则不为空,那么redis会执行快照 - 执行复制的时候
快照的实现原理
- redis使用fork函数复制一份当前进程的副本(子进程)
- 父进程继续接收并处理客户端发来的命令,而子进程开始将内存中数据写入硬盘中的临时文件
- 当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,一次快照操作完成
RDB的优缺点
- 使用RDB方式实现持久化,一单redis异常退出,就会丢失最后一次快照以后更改的所有数据
- fork比较耗时,造成一段时间内停止客户端请求
AOF
aof将redis执行的每一条写命令追加到硬盘文件中;
aof文件损坏以后如何修复
- 为现有的aof文件创建一个备份
- 使用redis附带的redis-check-aof程序,进行aof文件恢复
RDB和AOF,如何选择
如果可以承受数分钟的数据丢失,可以只使用RDB持久化。如果同时使用,redis重启时,会优先使用aof文件还原数据