1. Java 中值传递和引用传递的区别
值传递
在方法的调用过程中,实参把它的实际值传递给 形参 , 此传递过程就是将实参的值复制一份传递到函数中,这样如果在函数中对该值(形参的值)进行了操作将不会影响实参的值。 因为是直接复制,所以这种方式在传递大量数据时,运行效率会特别低下。
引用传递
引用传递弥补了值传递的不足,如果传递的数据量很大,直接复过去的话,会占用大量的内存空间,而引用传递就是将对象的地址值传递过去,函数接收的是原始值的首地址值。 在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。
参考文章:
2. redis 的使用场景
缓存
作为高性能的Key-Value内存数据库,日常开始使用中最常见的使用场景便是作为数据缓存。只需要通过 String 类型将待缓存数据对象通过序列化后缓存起来即可。但使用时也有一些需要注意的点:
- 必须保证不同缓存内容的key不会重复,并且尽可能短。可以使用对象主键等。
- 对于热点数据在数据库新增或查询后将其放入缓存,修改后更新缓存,删除后对应去除缓存。同时也需要对其设置过期时间。
- 合理利用缓存提高接口、网站访问速度,同时能够有效降低数据库压力。
- Redis提供键过期,可以将其作为缓存过期淘汰策略。
计数器
Redis提供了 incr 命令来实现计数功能,通过内存操作性能高,能够很好适用于这些场景。
- 社交业务产品中统计技术功能
- 用户点赞数、关注数、粉丝数
- 帖子点赞数、评论数、热度
- 消息已读、未读,红点消息数
- 话题消息数、帖子数、收藏数
- 统计网站、接口的调用次数等。
限流
限流是一种限制某些操作、接口执行访问速率的操作。例如网关限制接口访问频次,针对于用户维度、全局维度来控制接口的访问频率。
- MQ 防止重复消费同样可以利用 incr 计数,保证时间段内只会被消费一次。
- 接口限流,限制接口时间段内请求次数上限。
- 限流方式:
- 本地令牌桶
- lua 脚本使用 incr 实现redis简单限流
队列
Redis有 list push 和 list pop 这样的命令,可以很方便的执行队列操作。但是一般情况下不使用。
分布式锁
在分布式场景下,对同一资源的并发访问。比如全局ID、库存减少、秒杀、订单状态一致性等。并发量不大情况下可以使用数据库悲观锁、 乐观锁 来实现。但是并发量高的场景中,利用数据库锁机制来控制资源会很大程度影响数据库的性能。此时可以使用Redis的 setnx 功能来编写分布式锁,进行操作是来获取分布式锁,当然实际操作中需要考虑更多细节内容。
- 互斥性。互斥是所得基本特性,同一时刻只能被一个 线程 操作。
- 超时释放。通过超时释放,可以避免死锁,防止不必要的线程等待和浪费资源。 MySQL 的 InnoDB 中 innodblockwait_timeout 参数配置超时释放锁。
- 可重入性。一个线程在持有该锁的情况下可以对其再次请求加锁。防止锁在线程执行完临界操作前释放。
- 高性能和高可用。加锁和解锁的过程需要尽量减少性能消耗,但是也需要保证高可用,避免分布式锁意外失效。
在此说一下分布式锁的实现方案:
- Memcached 分布式锁
- 利用Memcached的add命令,此命令是 原子操作 。只有在key不存在的情况下才能add成功,此时就获得到锁。
- zookeeper 分布式锁
- 利用zookeeper的顺序临时节点来实现分布式锁和等待队列。
- Redis分布式锁
- 基于Redis的分布式锁和Memcached的实现方式类似,Redis是使用其 setnx 命令,此命令同样也是源自操作,只有在key不存在的情况下才可以设置成功,从而获得锁。
排行榜
使用Redis的sorted set操作对热点数据进行排序。展示一段时间点赞量最多的帖子等等。
参考文章:
3.Redis的限流是怎么做的
Redis限流的实现方式有3种,分别是:
- 基于Redis的setnx的操作,给指定的key设置了过期时间;
- 基于Redis的数据结构zset,将请求打造成一个zset数组;
- 基于Redis的令牌桶算法,输出速率大于输入速率,就要限流。
第一种:基于Redis的setnx的操作
在使用Redis分布式锁的时候,是依靠setnx指令,在CAS的操作时同时给指定的key设置了过期时间,限流的主要目的就是为了在单位时间内有且仅有N个数量的请求能够访问程序。因此依靠setnx可以做到这方面。例如我们需要在5秒内限定10个请求,那么我们在setnx的时候可以设置过期时间为5,当请求的setnx数量达到10个的时候就达到了限流效果。这种做法的弊端是很多的,例如当统计1到5秒的时候,但无法统计2到6秒之内,如果需要统计N秒内的M个请求,那么在Redis中需要保持N个key问题。
第二种:基于Redis的数据结构zset
其实限流涉及的最主要的就是滑动窗口,上面也提到1-10怎么变成2-11。其实也就是起始值和末端值都各+1即可。
我们可以将请求打造成一个zset数组,当每一次请求进来的时候,value保持唯一,可以用UUID生成,而score可以用当前时间戳表示,因为score我们可以用来计算当前时间戳之内有多少的请求数量。而zset数据结构也提供了range方法让我们可以很轻易的获取到2个时间戳内有多少请求,缺点就是zset的数据结构会越来越大。实现方式相对也是比较简单的。
第三种:基于Redis的令牌桶算法
提到限流就不得不提到令牌桶算法了。令牌桶算法又称之为水桶算法令牌桶算法提及到输入速率和输出速率,当输出速率大于输入速率,那么就是超出流量限制了。也就是说我们每访问一次请求的时候,可以从Redis中获取一个令牌,如果拿到令牌了,那就说明没超出限制,而如果拿不到,则结果相反。
参考文章:
4.Redis的大Key怎么处理
redis使用会出现大key的场景:
1.单个简单key的存储的value过大;
2. hash 、set、zset、list中存储过多的元素。
大key的风险:
1.读写大key会导致超时严重,甚至阻塞服务。
2.如果删除大key, DEL 命令可能阻塞Redis进程数十秒,使得其他请求阻塞,对应用程序和Redis集群可用性造成严重的影响。
解决问题:
1.单个简单key的存储的value过大的解决方案:
将大key拆分成对个key-value,使用multiGet方法获得值,这样的拆分主要是为了减少单台操作的压力,而是将压力平摊到集群各个实例中,降低单台机器的IO操作。
2.hash、set、zset、list中存储过多的元素的解决方案:
1).类似于第一种场景,使用第一种方案拆分;
2).以hash为例,将原先的hget、hset方法改成(加入固定一个hash桶的数量为10000),先计算field的hash值模取10000,确定该field在哪一个key上。
参考文章:
5.Redis的热Key问题
出现的原因
1.用户消费的数据远大于生产的数据
热key就是某个瞬间有大量的请求去访问redis上某个固定的key,导致缓存击穿,请求都打到了数据库上,压垮了缓存服务和DB服务,从而影响到应用服务可用的可用性。
最常见的就是 微博 的热搜,比如XX明星结婚/出轨。那么关于XX明星的Key就会瞬间增大,就会出现热数据问题。微博也时不时的来个崩溃。
同理,被大量刊发、浏览的热点新闻、热点评论、明星直播等,这些典型的读多写少的场景也会产生热点问题。
2.请求分片集中,超过单Server的性能极限
在服务端读数据进行访问时,往往会对数据进行分片切分,此过程中会在某一主机 Server上对相应的Key进行访问,当访问超过 Server 极限时,就会导致热点Key问题的产生。
危害
1、流量集中,达到物理网卡上限。
当某一热点 Key 的请求在某一主机上超过该主机网卡上限时,由于流量的过度集中,会导致服务器中其它服务无法进行。
2、请求过多,缓存分片服务被打垮。
如果热点过于集中,热点 Key 的缓存过多,超过目前的缓存容量时,就会导致缓存分片服务被打垮现象的产生。
3、DB 击穿,引起业务雪崩。
当缓存服务崩溃后,此时再有请求产生,会缓存到后台 DB 上,由于DB 本身性能较弱,在面临大请求时很容易发生请求穿透现象,会进一步导致雪崩现象,严重影响设备的性能。
解决方案
1、利用二级缓存
比如利用 ehcache 、 spring cache ,甚至是一个 HashMap 都可以。发现热key以后,把热key加载到系统的 JVM 中。
针对这种热key请求,会直接从jvm中取,而不会走到redis层。
假设此时有十万个针对同一个key的请求过来,如果没有本地缓存,这十万个请求就直接怼到同一台redis上了。 现在假设,你的应用层有50台机器,OK,你也有jvm缓存了。这十万个请求平均分散开来,每个机器有2000个请求,会从JVM中取到value值,然后返回数据。避免了十万个请求怼到同一台redis上的情形。
2、读写分离
读写分离 就是将同为 Write 的请求发送到 Master 模块内,而将 Read 的请求发送至 ReadOnly 模块。
而模块中的只读节点还可以进一步扩充,把这个key,在多个redis上都存一份不。有热key请求进来的时候,我们就在有备份的redis上随机选取一台,进行访问取值,返回数据。从而有效解决热点读的问题。
读写分离同时具有可以灵活扩容读热点能力、可以存储大量热点Key、对客户端友好等优点。
参考文章: