Java-redis-一些概念及说明

日常redis用的比较频繁,但是还没对redis做一些笔记.今天想起来,正好写一下.
ps : redis的安装、基本概念、配置就不做说明.

下面进入正题.

redis为单线程,那么为什么设计单线程?有什么优点呢?

官方给的解释貌似是因为redis很快,没必要为了多线程而损失单线程的优点.当然,也可能是谬论!>_<
单进程单线程好处 
1> 代码更清晰,处理逻辑更简单
2> 不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
3> 不存在多进程或者多线程导致的切换而消耗CPU
单进程单线程弊端
1> 无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善;当然这个问题可以通过创建多个redis实例来解决.

redis默认字符串以及优点

redis默认字符串为简单动态字符串(simple dynamic string)sds
与c语言字符串优点[估计有人会说,redis怎么扯出来c语言了 >_<]
优点 : 
1> 计算length:c中不记录length,要遍历数组才能得到string的长度,时间复杂度为O(n);SDS则记录了lenth,时间复杂度则为O(1)
2> 缓冲区溢出:C在字符串创建时,已经分配了指定的缓冲区内存。在修改的时候,都需要手动的判断内存大小是否允许修改后的值。如果忽视了手动判断大小,直接拼接则容易造成,缓冲区内存溢出。SDS则会在修改时,会自动判断大小,从而避免了缓冲区溢出的可能。
3> 减少了内存重新分配次数。sds加入了free,采用空间预分配和惰性空间释放来优化分配次数。不想java一样,String类型,放在了常量池中。free 用来记录修改后,未使用的大小。这就是为了方便,惰性空间释放。
4> 二进制安全的
5> 兼容部分C的函数,尾部也保留空字符
与java中String有所区别
1> java 中string是final的,java中string被存放在常量池中,不能修改,因为java内存由虚拟机动态内存管理。

redis雪崩

问题 : 
缓存在同一时间内大量键过期(失效),接着来的一大波请求瞬间都落在了数据库中导致连接异常。
解决 : 
1> 像解决缓存穿透一样加锁排队,实现同上;
2> 建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,并且更新A缓存和B缓存;
3> 设置缓存超时时间的时候加上一个随机的时间长度,比如这个缓存key的超时时间是固定的5分钟加上随机的2分钟,这样可从一定程度上避免雪崩问题;

redis缓存穿透

问题 : 
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决 : 
1> 布隆过滤 : 对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃。还有最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
   适用范围 : 可以用来实现数据字典,进行数据的判重,或者集合求交集
2> 缓存空对象. 将 null 变成一个值.
      1.空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
      2.缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

还有另外一种缓存穿透 : 
高并发同时请求,可能都会从数据库获取,想达到第一次请求从数据库中获取,其他9999个请求从redis中获取这种效果。
解决 : 
从redis获取使用双重检测锁[至于具体什么是双重检测锁,我想用单例的都知道吧,就不过多进行说明 >_<]

redis setex和set 的区别

SETEX命令 : SETEX key seconds value  将value关联到key, 并将key的生存时间设为seconds(以秒为单位)。如果key 已经存在,SETEX将重写旧值;
相当于 : SET('key', 'value'); EXPIRE('key','seconds');  # 设置生存时间

redis分布式锁机制

分布式锁机制网上有大量说明,这里我就只贴一下核心代码吧.
简单说明一下 : redis锁使用的lua脚本,set使用的setnx.实现方案看代码就行,没什么复杂的就不过多说明.
"if (redis.call('exists', KEYS[1]) == 0) then " +
                  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              "return redis.call('pttl', KEYS[1]);",

springboot使用RedisTemplate整合redis锁(springboot整合redis不做说明) :

RediHelp : 

import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import redis.clients.jedis.Jedis;

public class RediHelp {
	protected RedisTemplate<String, String> redisTemplate;
	private static final Logger log = LoggerFactory.getLogger(RediHelp.class);
	
	public RedisTemplate<String, String> getRedisTemplate() {
		return redisTemplate;
	}
	
	/** 
     * 获取redis的分布式锁,内部实现使用了redis的setnx。如果锁定失败返回null,如果锁定成功则返回RedisLock对象,调用方需要调用RedisLock.unlock()方法来释放锁 
     * <span style="color:red;">此方法在获取失败时会自动重试指定的次数,由于多次等待会阻塞当前线程,请尽量避免使用此方法</span> 
     * @param key 要锁定的key 
     * @param expireSeconds key的失效时间 
     * @param maxRetryTimes 最大重试次数,如果获取锁失败,会自动尝试重新获取锁; 
     * @param retryIntervalTimeMillis 每次重试之前sleep等待的毫秒数 
     * @return 获得的锁对象(如果为null表示获取锁失败),后续可以调用该对象的unlock方法来释放锁. 
     */  
	private static final String COMPARE_AND_DELETE =  
            "if redis.call('get',KEYS[1]) == ARGV[1]\n" +  
            "then\n" +  
            "    return redis.call('del',KEYS[1])\n" +  
            "else\n" +  
            "    return 0\n" +  
            "end";
	// 锁失效时间
    private static final long expireSeconds = 3; 
 // 重试次数
    private static final int maxRetryTimes = 1; 
    private static final long retryIntervalTimeMillis = 1;
    public RedisLock getLock(final String key){  
    	final String value = "-1";
    	int maxTimes = maxRetryTimes + 1;  
        for(int i = 0;i < maxTimes; i++) {  
            String status = getRedisTemplate().execute(new RedisCallback<String>() {  
                @Override  
                public String doInRedis(RedisConnection connection) throws DataAccessException {  
                    Jedis jedis = (Jedis) connection.getNativeConnection(); 
                    String status = jedis.set(key, value, "nx", "ex", expireSeconds);  
                    return status;
                }  
            });  
            //抢到锁  
            if ("OK".equals(status)) {
                return new RedisLockInner(getRedisTemplate(), key, value);  
            }  
            if(retryIntervalTimeMillis > 0) {  
                try {  
                    Thread.sleep(retryIntervalTimeMillis);  
                } catch (InterruptedException e) {  
                	log.warn("error:{}.",e.getMessage());
                	Thread.currentThread().interrupt();
                    break;  
                }  
            }  
            if(Thread.currentThread().isInterrupted()){  
                break;  
            }  
        }  
        return null;  
    }  
    private class RedisLockInner implements RedisLock{  
        private RedisTemplate<String, String> redisTemplate;  
        private String key;
        private String expectedValue;
        protected RedisLockInner(RedisTemplate<String, String> redisTemplate, String key, String expectedValue){  
            this.redisTemplate = redisTemplate;  
            this.key = key;  
            this.expectedValue = expectedValue;  
        }  
        /** 
         * 释放redis分布式锁 
         */  
        @Override  
        public void unlock(){  
            List<String> keys = Collections.singletonList(key);  
            redisTemplate.execute(new DefaultRedisScript<>(COMPARE_AND_DELETE, String.class), keys, expectedValue);  
        }
        @Override  
        public void close() throws Exception {  
            this.unlock();  
        }  
    }  
}

RedisLock : 

/**
 * Redis的分布式锁对象
 */
public interface RedisLock extends AutoCloseable {
	/**
	 * 释放分布式锁
	 */
	void unlock();
}

RedisUtil : 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
/**
 * 增加redis分布式锁机制

// 获取锁[默认锁过期时间为3秒]
RedisLock redisLock = null;
try{
    redisLock = redisUtil.getLock("redis_lock");
	if(redisLock!=null) {
		// 获取到锁,执行业务逻辑代码
	}else {
		// 未获取到锁
	}
}cache(Exception e){
}finally{
	// 释放锁
	if(redisLock!=null){
		redisLock.unlock();
	}
}
 * @author anyunfeng
 *
 */
@Component
public class RedisUtil extends RediHelp{
	@Autowired
	private RedisTemplate<String, String> redisTemplate;
	
	@Override
	public RedisTemplate<String, String> getRedisTemplate() {
		return redisTemplate;
	}
}

redis支持哪些类型

这个其实没什么好说的 >_<
string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。 

mysql与redis实现事务同步

1> 分别处理
   针对某些对数据一致性要求不是特别高的情况下,可以将这些数据放入Redis,请求来了直接查询Redis,例如近期回复、历史排名这种实时性不强的业务。而针对那些强实时性的业务,例如虚拟货币、物品购买件数等等,则直接穿透Redis至MySQL上,等到MySQL上写入成功,再同步更新到Redis上去。这样既可以起到Redis的分流大量查询请求的作用,又保证了关键数据的一致性。
2> 高并发情况下
   此时如果写入请求较多,则直接写入Redis中去,然后间隔一段时间,批量将所有的写入请求,刷新到MySQL中去;如果此时写入请求不多,则可以在每次写入Redis,都立刻将该命令同步至MySQL中去。这两种方法有利有弊,需要根据不同的场景来权衡
3> 基于订阅binlog的同步机制
   阿里巴巴的一款开源框架canal,提供了一种发布/ 订阅模式的同步机制,通过该框架我们可以对MySQL的binlog进行订阅,这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。值得注意的是,binlog需要手动打开,并且不会记录关于MySQL查询的命令和操作。

redis的一些面试题

Redis哈希槽的概念 : 
   Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
Redis中的管道有什么用 : 
   一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。
Redis如何做内存优化 : 
   尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面.
Redis常见性能问题和解决方案 : 
     1> Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
	 2> 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
	 3> 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
	 4> 尽量避免在压力很大的主库上增加从库
	 5> 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
Redis提供了哪几种持久化方式 : 
     1> RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.
	 2> AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
	 3> 如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
	 4> 你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
五种I/O模型介绍 : 
     1> 阻塞I/O模型 : 老李去火车站买票,排队三天买到一张退票。耗费:在车站吃喝拉撒睡 3天,其他事一件没干。
	 2> 非阻塞I/O模型 : 老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。 耗费:往返车站6次,路上6小时,其他时间做了好多事。
	 3> I/O复用模型 : 
	    1> select/poll : 老李去火车站买票,委托黄牛,然后每隔6小时电话黄牛询问,黄牛三天内买到票,然后老李去火车站交钱领票。 耗费:往返车站2次,路上2小时,黄牛手续费100元,打电话17次
	    2> epoll : 老李去火车站买票,委托黄牛,黄牛买到后即通知老李去领,然后老李去火车站交钱领票。  耗费:往返车站2次,路上2小时,黄牛手续费100元,无需打电话
	 4> 信号驱动I/O模型 : 老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李,然后老李去火车站交钱领票。  耗费:往返车站2次,路上2小时,免黄牛费100元,无需打电话
	 5> 异步I/O模型 : 老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李并快递送票上门。  耗费:往返车站1次,路上1小时,免黄牛费100元,无需打电话
	 1同2的区别是:自己轮询
	 2同3的区别是:委托黄牛
	 3同4的区别是:电话代替黄牛
	 4同5的区别是:电话通知是自取还是送票上门

多路复用API,多路复用最常用的就是select和epoll,多路复用的最大的特点就是多路,一个select可以同时处理多个套接字的读写请求,这时候IO的阻塞点往往是多路复用的API,而不是IO操作本身
在redis中的IO模型就是使用多路复用API进程客户端的检测,多路复用API可以同时监听多个客户端的读写操作,多路复用API会检测客户端的请求是读操作还是写操作,往往API会有一个timeout,在这个时间内redis线程会阻塞,进行套接字的监听,redis会给每个客户端套接字匹配一个指令队列,按照队列进行处理,同时也会将操作结果放到输出队列
正是通过这种多路复用的思想进行非阻塞的IO,这样才保证了redis的高效

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小安灬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值