verysu 设计模式 设计模式 响应式编程 百度开发平台 codeforces leetcode usfca

导航

redis redisson脚本

什么是Redis
Redis是一种非关系型数据库,它将数存储在内存中,大大的提高了数据的查询效率。Redis是单线程请求,所有的操作都是串行执行的,并发情况下不需要考虑数据一致性问题。
redis是一个key-Value结构的数据存储系统个,value支持五种数据类型
String
Hash
List
Set
SortedSet
Redis的数据持久化方式
1 RDB方式
将内存中当前的数据库状态保存到磁盘里面。RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成该RDB文件是的数据库状态。RDB持久化方式效率很快,但是会有数据丢失的可能。
RDB文件生成的方式:
1.执行命令手动生成
SAVE 会阻塞Redis服务器进程,保存完成之前,redis服务器不能进行任何其他操作。
BGSAVE 会创建一个子进程,由子进程负责生成RDB文件。
2.通过配置自动生成
可以设置服务器的save选项,让服务器每个一段时间自动执行一次BGSAVE命令。可以设置多个保存的条件,满足任何 一个条件的时候就行至此那个保存。
2 AOF方式
将数据库的谢明令保存到文件中。AOF持久化方式在redis中默认是关闭的,需要修改配置文件开启。
AOF文件刷新的三种方式:
1.appendfsync always - 每提交一个修改命令都调用 fsync 刷新到 AOF 文件,非常非常慢,但也非常安全
2.appendfsync everysec - 每秒钟都调用 fsync 刷新到 AOF 文件,很快,但可能会丢失一秒以内的数据
3.appendfsync no - 依靠系统自动进行刷新,redis 不主动刷新 AOF,这样最快,但安全性就差
AOF数据恢复方式
redis服务器在启动时,通过载入和执行AOF文件中保存的命令,来恢复服务器关闭之前的状态。
如果同时启用了 RDB 和 AOF 方式,AOF 优先,启动时只加载 AOF 文件恢复数据
Redis的集群
Redis3.0版本之后开始支持集群。集群要求集群中节点必须支持准备模式,也就是说集群中的主节点至少要有一个从节点。
集群中的任意两个主节点之间都是相互连通的,客户端可以与任意一个节点进行连接,然后就可以访问集群中的其他节点。
Redis集群中的主节点之间通过ping-pong来判断节点是否可以连接上。当有一半一闪给定节点去ping一个节点没有回应的时候,则认为该节点宕机,就会连接它的从节点。如果如果某个节点和它的所有的从节点都宕机,集群就会不可用。还有就是,集群中一半以上的主节点宕机,也会认为集群不可用。
集群中数据的存储:
集群常见完成后,会为集群上的每个节点分配哈希槽,哈希槽有16484个,哈希槽在节点上进行平均分配。当我们存储到key到达的时候,热地社会根据CRC16算法得出一个结果没然后对16484取余,余数对应着哈希槽的编号。通过余数,找到对应的哈希槽的节点,然后跳转到这个节点上对数据进行存取操作。
Redis具体使用场景
并发量大,需要频繁查询数据库的操作,可以将数据缓存到redis中,直接从redis中获取。提高查询效率,降低数据库压力
Redis在java中的使用

//创建连接池对象
JedisPool jp = new JedisPool("192.168.25.104",6379);
Jedis jedis = jp.getResource();

//Jedis连接集群

Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.25.104",7001));
nodes.add(new HostAndPort("192.168.25.104",7002));
nodes.add(new HostAndPort("192.168.25.104",7003));
nodes.add(new HostAndPort("192.168.25.104",7004));
nodes.add(new HostAndPort("192.168.25.104",7005));
nodes.add(new HostAndPort("192.168.25.104",7006));
JedisCluster jc = new JedisCluster(nodes);

Spring框架中使用Redis

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.bjsxt.JedisDao" />
<!-- jedisPool的配置信息 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大连接数 -->
<property name="maxTotal" value="30" />
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="10" />
<!-- 每次释放连接的最大数目 -->
<property name="numTestsPerEvictionRun" value="1024" />
<!-- 释放连接的扫描间隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<!-- 连接最小空闲时间 -->
<property name="minEvictableIdleTimeMillis" value="1800000" />
<!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
<property name="softMinEvictableIdleTimeMillis" value="10000" />
<!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
<property name="maxWaitMillis" value="1500" />
<!-- 在获取连接的时候检查有效性, 默认false -->
<property name="testOnBorrow" value="true" />
<!-- 在空闲时检查有效性, 默认false -->
<property name="testWhileIdle" value="true" />
<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
<property name="blockWhenExhausted" value="false" />
</bean>
<!-- 配置JedisPool 加载spring容器的时候创建JedisPool对象放到spring容器中 -->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="poolConfig">
<ref bean="poolConfig"/>
</constructor-arg>
<constructor-arg name="host">
<value>192.168.25.104</value>
</constructor-arg>
<constructor-arg name="port">
<value>6379</value>
</constructor-arg>
</bean>
<!-- 创建JedisDaoImpl对象放到spring容器中 -->
<!-- <bean id="jedisDaoImpl" class="com.bjsxt.JedisDao.impl.JedisDaoImpl"></bean> -->

<!-- 配置JedisCluster -->
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg name="nodes">
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host">
<value>192.168.25.104</value>
</constructor-arg>
<constructor-arg name="port">
<value>7001</value>
</constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host">
<value>192.168.25.104</value>
</constructor-arg>
<constructor-arg name="port">
<value>7002</value>
</constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host">
<value>192.168.25.104</value>
</constructor-arg>
<constructor-arg name="port">
<value>7003</value>
</constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host">
<value>192.168.25.104</value>
</constructor-arg>
<constructor-arg name="port">
<value>7004</value>
</constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host">
<value>192.168.25.104</value>
</constructor-arg>
<constructor-arg name="port">
<value>7005</value>
</constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host">
<value>192.168.25.104</value>
</constructor-arg>
<constructor-arg name="port">
<value>7006</value>
</constructor-arg>
</bean>
</set>
</constructor-arg>
</bean>
<bean id="jedisDaoImplCluster" class="com.bjsxt.JedisDao.impl.JedisDaoImplCluster"></bean>
</beans>

 

1、缓存雪崩

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
  3. 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

2、缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  3. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

附加

对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。Bitmap:典型的就是哈希表 缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。

布隆过滤器(推荐)

就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。

3、缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 设置热点数据永远不过期。
  2. 加互斥锁,互斥锁

4、缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决方案

  1. 直接写个缓存刷新页面,上线时手工操作一下;
  2. 数据量不大,可以在项目启动的时候自动进行加载;
  3. 定时刷新缓存;

5、缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

  1. 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
  2. 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
  3. 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
  4. 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

6、热点数据和冷数据

热点数据,缓存才有价值

对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存

对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。

数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。

那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。

7、缓存热点key

缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案

对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询

脚本redisson3.1.13通过脚本查结果

redissonClient
(List)this.redissonClient.getScript(StringCodec.INSTANCE).eval(Mode.READ_ONLY, "function string.startwith(String,Start)\n return string.sub(String,1,string.len(Start))==Start\nend\n\nlocal likeName = ARGV[1]\nlocal rightsList = cjson.decode(ARGV[2])\nlocal type = 3\nlocal maxValue = 300\nlocal resultsFilterOK = {};\nlocal len = redis.call('llen', 'positions')\nlocal resultsNum = 0\nlocal resultsWithParent = {};\nfor i=0,len-1 do\n local infoStr = redis.call('lindex', 'positions', i)\n local info = cjson.decode(infoStr)\n local curType = info.type\n local curName = info.name\n if(curType == type and string.match(curName, likeName)) then\n for k, v in ipairs(rightsList) do\n if(string.startwith(info.code, v)) then\n resultsNum = resultsNum + 1\n if(resultsNum <= maxValue) then\n table.insert(resultsFilterOK, info)\n end\n break\n end\n end\n end\n if(resultsNum >= maxValue) then\n break\n end\nend\n\nfor i=0,len-1 do\n local infoStr = redis.call('lindex', 'positions', i)\n local info = cjson.decode(infoStr)\n local code = info.code\n for k, v in ipairs(resultsFilterOK) do\n if(string.startwith(v.code, code)) then\n table.insert(resultsWithParent, cjson.encode(info))\n end\n end\nend\nreturn resultsWithParent", ReturnType.MAPVALUELIST, Collections.emptyList(), new Object[]{positionNameLike, codeParam});

posted on 2020-01-08 12:04  泳之  阅读(159)  评论(0编辑  收藏  举报

我是谁? 回答错误