Redis支持的几种类型?
String、hash(类似java的map)、list(链表)、set(集合)、zset(无需集合)
Redis有哪几种持久化技术?
1.RDB
RDB包括两种持久化命令:save、bgsave
save命令:在主线程中工作,因此有可能阻塞主线程其他操作redis的请求,尽量避免使用此命令
bgsave命令:从主线程中fork(分叉)出一个子进程,父进程继续处理操作请求。
子进程将数据写入临时文件,并在写完后替换原有的.rdb文件。
在持久化写入临时文件的过程中处理的redis数据也会被复制一份,而不会共享内存。
所以RDB持久化的数据是,fork一个子进程时redis中保存的数据(不含持久化时的数据)
如果redis在RDB持久化过程中宕机,那么可能导致持久化这段时间的数据丢失
2.AOF
需在配置文件中修改appendonly 值为yes
开启AOF持久化技术之后,所执行的指令会根据配置文件的设定记录到appendonly.aof文件中
事实上并不会立即将命令写入到硬盘文件中,而是写入到磁盘缓存中
配置文件:
appendsync everysec(每一秒都AOF持久化到磁盘缓存)
appendsync always(每一次操作数据都持久化)
appendsync no(30秒持久化一次)
RDB和AOF持久化技术的好坏?
RDB由于是在主进程中fork一个子进程来持久化到磁盘文件中,所以不会影响到父进程的处理数据
可是持久化过程中宕机可能会导致数据丢失。
若数据比较不重要可以选择RDB高效率持久化技术,可是安全性较差
AOF由于是每一次数据操作或每一秒都要持久化一次数据到磁盘缓存中。
且AOF的持久化技术是用续写的方式在原有持久化文件的基础上续写,不会因为宕机破坏日志原有内容
所以操作数据的性能效率一定有所影响,可是安全性较强
Redis的特点?
内存数据库,存取速度快,也支持数据的持久化
就算没有持久化数据,也可以将内存中的数据保存在磁盘中,重启的时候可以再次加载使用
Redis支持实时数据备份,就算宕机也能把数据恢复过来
Redis支持事务,操作都是原子性
Redis具有丰富特性:缓存、消息、key-value的自动过期
Redis拓展性强:随时可以加一台新的服务器集群,成本较低
Redis的使用场景?
1.配合关系型数据库做高速缓存
2.社区中的评论、点赞、恢复等等。(使用传统数据库的select语句很耗费性能)
3.网页的排行榜、计数器
4.webMagic爬虫框架,使用RedisSchedule作去重,支持分布式
Redis事务?
Redis的事务不支持回滚,只会跳过错误命令,继续执行队列中剩下的命令
Jedis jedis = pool.getResource(); // 创建一个redis事务对象 Transaction transaction = jedis.multi(); transaction.setnx("haha","aaa"); // 提交事务中的所有操作 transaction.exec(); // 放弃队列中的所有命令 transaction.discard();
通过redis事务实现乐观锁(check-and-set检查再设置)
/* watch("key")监听这个键值对,是否被外界修改过 如果被修改过则watch()-unwatch()之间操作队列全部放弃 */ jedis.watch("haha"); // 创建一个redis事务对象 Transaction transaction = jedis.multi(); transaction.del("haha"); // 提交事务中的所有操作 transaction.exec(); // 结束监听 jedis.unwatch(); // 放弃队列中的所有命令 transaction.discard();
Redis集群?
一台master一定要有至少一台slave,所以redis集群至少要有6台服务器
1.所有redis节点都是彼此互联(ping-pong机制),内部使用二进制协议优化传输速度和带宽
2.节点的fail是通过集群中节点超过半数的节点检测失效时生效的
3.客户端与redis节点直连,不需要要proxy层,客户端连接集群整体即会分配节点
4.redis集群把所有物理节点映射到【0-16383】slot槽上,集群维护node<->slot<->value
集群维护16384个哈希槽,redis会根据集群的节点数,平均分配给每个节点相同的哈希槽。
当客户端往集群中插入一个key-value数据时,会先对key使用CRC16算法求出结果并取16384的余
最终决定此key-value数据存放的位置
投票:
所有的master参与投票,若一个master被超过半数的master节点通信超时,则会被认为此master节点挂掉
如果集群中任意master挂掉,且没有slave可用,则整个集群进入fail状态,(因为【0-16383】不完整)
可用打开cluster-require-full-coverage参数,兼容集群部分节点挂掉
集群超过半数以上的master挂掉,无论是否拥有slave,整个集群进入fail状态
Redis作分布式锁?
public class RedisLock { private final JedisPool jedisPool; // 在ioc容器中已经配置了jedisPool连接池对象 // private String host; // private String port; public RedisLock(JedisPool jedisPool) { this.jedisPool = jedisPool; } /** * 加锁 * * @param lockName 锁的key * @param acquireTimeout 获取超时时间 * @param timeout 锁的超时时间 * @return 锁标识 */ public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) { Jedis jedis = null; String retIdentifier = null; try { // 获取连接 jedis = jedisPool.getResource(); // 随机生成一个value String identifier = UUID.randomUUID().toString(); // 锁名,即key值 String lockKey = "lock:" + lockName; // 超时时间,上锁后超过此时间则自动释放锁 // 预防死锁!! int lockExpire = (int) (timeout / 1000); // 获取锁的超时时间,超过这个时间则放弃获取锁 long end = System.currentTimeMillis() + acquireTimeout; // 判断当前系统时间毫秒值 小于 超时时间 while (System.currentTimeMillis() < end) { if (jedis.setnx(lockKey, identifier) == 1) { // 为这个key一个超时时间,超过时间后自动删除key jedis.expire(lockKey, lockExpire); // 返回value值,用于释放锁时间确认 retIdentifier = identifier; return retIdentifier; } // 返回-1代表key没有设置超时时间,为key设置一个超时时间 if (jedis.ttl(lockKey) == -1) { jedis.expire(lockKey, lockExpire); } try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } catch (JedisException e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } return retIdentifier; } /** * 释放锁 * * @param lockName 锁的key * @param identifier 释放锁的标识 * @return */ public boolean releaseLock(String lockName, String identifier) { Jedis jedis = null; String lockKey = "lock:" + lockName; boolean retFlag = false; try { jedis = jedisPool.getResource(); while (true) { /* 监视lock,准备开始事务 WATCH 命令用于在事务开始之前监视任意数量的键:当调用 EXEC 命令执行事务时 如果任意一个被监视的键已经被其他客户端修改了,那么整个事务不再执行,直接返回失败。 */ jedis.watch(lockKey); // 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁 if (identifier.equals(jedis.get(lockKey))) { /* redis的multi开始一个事务 这个命令唯一做的就是,将客户端的 REDIS_MULTI 选项打开,让客户端从非事务状态切换到事务状态。 将多个命令打包, 然后一次性、按顺序地执行的机制并且事务在执行的期间不会主动中断 服务器在执行完事务中的所有命令之后,才会继续处理其他客户端的其他命令。 */ Transaction transaction = jedis.multi(); transaction.del(lockKey); // 最后由 redis的exec 命令触发事务 List
在用户请求进入高并发业务的方法时,调用获取锁方法,并在用户处理完业务逻辑后释放锁。
保证方法仅能被一个进程所访问,方法仅处理一个用户的请求。
获取锁:
调用此方法时,传入当前方法名作为key、锁自动释放时间和方法超时时间。创建一个uuid随机字符串作锁的value值。
在一个循环中(循环结束条件是系统时间>方法超时时间),调用redis的setnx(key,value)方法往redis中存入锁对象,若返回1则说明存入成功,否则存入失败。存入成功再调用redis的expire(key,timeout)方法为key设置一个自动删除时间。最终返回锁的value随机字符串结束方法。
释放锁:
调用此方法时,传入当前方法名、获取锁时返回的随机字符串。
在死循环中while(true){}开启redis事务,判断随机字符串与redis中存放锁value是否相同,若相同则进入方法体,将删除锁的del()操作放入事务的队列中,调用exec()方法执行事务中的所有操作。若返回的result集合不为空,那么说明释放锁成功。