redis缓存

Redis缓存

Redis 作为数据查询

redis mysql缓存模式一般采用cache Aside Pattern模式(该模式不是能够实现,缓存实现写入到数据库中,如果需要这样的,可以使用ignite缓存):

  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中
  • 命中:应用程序从cache中取数据,取到后返回
  • 更新:先把数据存到数据库中,成功后,再让缓存失效(更新数据库时,并不更新Redis缓存,而是在失效操作)。
    作为缓存,需要应对几个问题
  • 缓存穿透,缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。 key不存在时,大量的数据进来查询DB
  • 缓存雪崩,某个时间点上,大量缓存都失效了,大量请求落在数据库上,而数据库无法承受造成雪崩
  • 缓存击穿,在并发的情况下,大量请求同时请求某一个key,而该key失效了,并发的请求就会落在数据库上,而后压垮数据库

解决这些问题的方法:

  • 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
  • 缓存雪崩的解决方法:缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
  • 缓存击穿:原因是在某一时刻大量并发访问失效key时最后访问数据了,可以使用分布式锁,软过期,本地锁的方式。分布式锁,使用redis中setnx方式获取锁,只有一个获取到setnx设置的锁才能获取到key。本地锁,利用程序中锁方式,实现只有一个线程能够去获取key,软过期是redis存储缓存不使用缓存服务器提供的过期时间,而是将失效时间设置到key对应的value中,在获取时,找不到key,则利用后台线程去访问数据并刷新缓存,但是其他线程就可能获取失效的数据,能够有一定容忍性的项目可以使用。

缓存的配置
作为缓存查询,存储的内存和使用的过期策略也是很重要的

# redis配置文件中
maxmemory 50mb   # 设置最大内存
maxmemory-policy volatile-lru    # 设置过期策略
    noeviction:达到内存限额后返回错误,客户尝试可以导致更多内存使用的命令(大部分写命令,但DEL和一些例外)
    allkeys-lru:为了给新增加的数据腾出空间,驱逐键先试图移除一部分最近使用较少的(LRC)。
    volatile-lru:为了给新增加的数据腾出空间,驱逐键先试图移除一部分最近使用较少的(LRC),但只限于过期设置键。
    allkeys-random: 为了给新增加的数据腾出空间,驱逐任意键
    volatile-random: 为了给新增加的数据腾出空间,驱逐任意键,但只限于有过期设置的驱逐键。
    volatile-ttl: 为了给新增加的数据腾出空间,驱逐键只有秘钥过期设置,并且首先尝试缩短存活时间的驱逐键

缓存做为资源

缓存作为资源存储,数据库则作为一种备份,每一次数据的更新则必须要更新到缓存中。而作为资源则必须要要某些时刻只有一个线程去访问。本地锁,在访问资源时上锁,如Reentrantlock中lock和unlock方法。也可以使用redis分布式锁。

	/** 尝试获得注册时的分布式锁. */
	public boolean tryLock4Reg()
	{
		long ts = Cfg.clock;
		while (!__tryLock__(DbRedis.KEY_PREFIX_REGLOCK))
		{
			Misc.sleep(50);
			if (Cfg.clock - ts > DbRedis.USR_OPER_LOCK_TIMEOUT + 500)
				return false;
		}
		return true;
	}

	/** 释放分布式锁. */
	public void unLock4Reg()
	{
		this.delVal(DbRedis.KEY_PREFIX_REGLOCK);
	}

	/** 尝试获得分布式锁. */
	private boolean __tryLock__(String key)
	{
		Jedis jedis = null;
		try
		{
			jedis = this.getJedis();
			String val = Cfg.clock + "";
			if (jedis.setnx(key, val) == 1L)/* 不存在设置值. */
				return true;
			String oldVal = jedis.get(key);
			if (oldVal == null)/* 锁刚被释放. */
				return jedis.setnx(key, val) == 1L;
			if (Cfg.clock - Misc.forceLongO(oldVal) < DbRedis.USR_OPER_LOCK_TIMEOUT)/* 锁未被超时. */
				return false;
			/* 锁超时. */
			oldVal = jedis.getSet(key, val);
			if (oldVal == null)/* 锁被释放. */
				return jedis.setnx(key, val) == 1L;
			return Cfg.clock - Misc.forceLongO(oldVal) >= DbRedis.USR_OPER_LOCK_TIMEOUT;/* 锁已经超时,并且没有任何线程干扰. */
		} finally
		{
			this.close(jedis);
		}
	}
    /** 删除一个值. */
	public void delVal(String key)
	{
		Jedis jedis = null;
		try
		{
			jedis = this.getJedis();
			jedis.del(key);
		} finally
		{
			this.close(jedis);
		}
	}

锁资源有一点需要注意的,资源锁住后,之前的并行操作,在该处都变成了串行操作,如果粒度太大,那么占用锁资源的时间消耗将非常大,需要选取尽可能小的粒度。

原文地址:https://www.cnblogs.com/skyice/p/10280266.html