为什么需要redis?
- CRUD百分之80为查询,数据不会频繁变更,目前项目都是分布式项目,所以使用分布式缓存数据库–redis
- redis是单线程NIO模型,多个请求按先后顺序排队,按个执行,吞吐量大概10w/s,它的核心配置文件redis.config,默认端口号6379,默认有16个数据库,编号0-15,值的数据类型有string,hash,list,set四种
redis的基本命令
启动 redis-server(占用运行窗口) redis-server &(不占用 运行窗口)
查询端口号 ps -ef|grep redis
进去 redis-cli
检查是否安装正常 ping
退出 exit或者ctr +c
退出客户端 shutdown
查找key值 keys*
存 set K V
查 get K
查以x开头的 key keys x*
查以x开头的key,后面只有一个字符 key x?
切换数据库 select ? -----?取值范围【0-15】
删除 del K1,K2
判断键是否存在 exists K 返回1存在,0不存在
判断值类型 type K
清空当前数据库 flushdb
清空所有数据库 flushall
操作string命令
存储多个键值对 mset K V K V K V
查询多个值 mget K K K K
自增命令 incr K(默认加一) incr K 10(默认加十)
自建命令 decrby K 按步长加同上
原来值后追加字符串 append K V
设置键值对过期时间 expire K 20 (单位:毫米)
查看还有多久过时 ttl K
持久 presist K
操作hash数据(应用场景:存对象,修改)
这里用User对象演示
存 hset user username tony(注意有符号的值需要转义的值需要“V”)
查 hget user username
查对象 hgetall user
该 hset user username tom
自增 hincrby user age 1
判断是否存在 hexists user username
删除对象某一属性 hdel user username
获取对象所有K hkeys user
获取对象的V hvals user
Redis的两种灾难恢复模式
- RDB:fork一个进程,遍历hashtable,利用copy on write ,把整个db dump保存下来。save,shutdown,slave(主从复制)命令会触发这个操作,颗粒度比较大,如果save,shutdown,slave之前cash了,则中间数据丢失。
- AOF:把写操作指令,持续写到一个类似日志文件中,颗粒度小,cash之后,只要cash之前没有来得及做日志操作没办法恢复。崩溃后利用日志恢复。
redis的事务管理
-
乐观锁:(类似于mysql中的乐观锁,在数据库表当中增加一个version字段,将次版本号一次读出,之后更新时候,对版本号加一,此时,将提交数据的版本号与数据库对应记录的版本号经行对比,如果提交数据的版本号大于数据库的版本号,则给予跟新,否则,认为数据过期)
-
redis也采用类似的机制,使用watch命令规定监视给定的key,当exec时候如果出现的key从调用watch版本发生变化,则整个事务失败,也可以调用watch多次监视多个key,这样就可以对指定的key加乐观锁了。注意watch的key是对整个连接有效的,事务也一样,如果连接断开了,监视和事务将会被自动清除。当然exec,discard,unwatch命令都会清除连接中的所有监视
-
概念:redis是单线程的,提交命令时,其他命令无法插入其中,轻松利用单线程实现了事务。那如果执行多个redis命令呢?自然就没有事务的保证,于是redis有了下列相关的命令来实现事务管理,来实现分布式事务
multi 开启事务 exec 提交事务 discard 取消事务 watch 监控,如果监控发生变化,则提交事务时会失效 unwatch 去掉监控
-
redis保证一个事务中所有命令要么全部执行,要么不执行。如果发生exec命令前用户掉线,则redis会清空事务队列,事务中的命令也不会执行。当用户输入exec后掉线,也没有关系,因为redis已经记录了所有执行命令到队列当中了。
典型业务场景:秒杀
客户端1:set ticket 1 (设置票数为一) watch ticket(乐观锁,对值进行监控,发现票剩一个) multi(开启事务) decr ticket(票减一) exec(提交事务,此时如果票数为0,即其他客户端抢到票,提交事务失败)
转账业务等
注意:抛出异常事务自动取消,事务从所有操作命令不会执行。
redis的管道-数据海量导入(数据导入非常快)
-
redis-cli工具支持管道文本海量导入。 比如:set name tony 会分成四个命令
*3(代表总共三个部分) $3 (代表后面命令长度) $set $4 $name $4 $tony 最后演化一行结果为 *3/r/n$3/r/n $set/r/n$4/r/n$name/r/n$4/r/n$tony
-
然后把文本文件上传到redis服务器,执行:cat 文件名.txt |redis-cli --pipe
-
典型应用:缓存预热–数据达到几百GB时,服务器宕机时,如果让程序来缓存,无疑对数据库造成巨大压力,而实现对热点数据管道导入,减少对数据库的压力,让整个数据库更加稳健的应对海量用户访问,所以我们要将预热的数据写成TXT文件。
redis 分片(利用jedisAPI实现,创建ShardedJedisPool)
-
把多个节点结合成一个大区域来使用,理论上有了分片操作,形成分布式内存数据库,内存无限大。
-
放入数据时,多个节点是否平分数据?多个节点数据是不一致的
-
基于hash取余算法,数据还是不均匀的。hash一致性算法,基本保证数据均匀分布
- hash取余算法: hash(object.key)%N,随object.key和N发生变化而变化
缺点:如果节点发生变化,几乎重新分配,意味着已经分配好的数据都要迁移到新节点上,那么redis数据库压力剧增。但节点变化在集群中是再正常不过的,那么怎么解决问题呢?
- hash取余算法: hash(object.key)%N,随object.key和N发生变化而变化
-
一般好的hash算法有四大标准:
- 1.平衡性-哈希的结果能够尽可能分不到所以的缓存中去
- 2.单调性-指已经有一些内容通过哈希算法添加到对应的缓存中,又有新的缓存加入系统。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的缓存去
- 3.分散性-在分布式系统中,终端可能看不到所有缓存,而是只能看到一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端的缓存范围不同,从而导致哈希结果不一致,最终结果相同的内容被不同终端缓存。这种情况应该被避免
- 4.负载-对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容,与分散性一致,这种情况也是要被避免的。
-
hash一致性算法:
- hash环设计:从0到2^32,通过这样的设计把对象(数据)和服务器映射到同一个哈希空间中,按照顺时针转动,根据object.key的哈希值,存到最近的redis服务器中。hash环是不会变更的。
- 节点(服务器)的添加和节点(服务器)的删除,hash一致性算法保持单调性的同时,还使数据的迁移代价了最小,这样的算法对分布式集群来说非常合适,满足了单调,分散性,负载。保证不了平衡性
- 虚拟节点设计:为了尽可能满足平衡性。虚拟节点是实际节点,在hash空间的复制品,一个实际节点对应了若干个虚拟节点,这样就解决了节点(机器)再hash环上分布不均匀。
- hash环设计:从0到2^32,通过这样的设计把对象(数据)和服务器映射到同一个哈希空间中,按照顺时针转动,根据object.key的哈希值,存到最近的redis服务器中。hash环是不会变更的。
redis常见的面试总结:
- redis和MemCached区别?
- 都是分布式内存数据库
- redis是单线程的,MemCached采用多线程机制,一般来说多线程比单线程好。但缓存200G数据,它们两个性能再伯仲之间。redis也有多进程方案,在同一台机器上面开启多个服务器,比如分片(6个节点性能最佳)
- redis支持数据落地,两种故障模式:RDB、AOF。MemCached只保存在内存中,断电数据丢失
- redis支持类型丰富:string/hash/list/set/zset。MemCached只支持string
- redis可支持string类型512M/hash 2^32。MemCached只支持10M
- redis缓存穿透怎么解决?
- 缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求怼到数据库上,从而数据库连接异常
- 解决方案:
- 1.互斥锁,当缓存失效的时候,先去获得锁,得到锁了,再去请求数据库,没有得到锁,则休息一段时间(性能差)
- 2.采用异步更新策略,无论Key是否取得值,都直接返回。Value值中维护一个缓存失效时间,缓存如果过期,异步启动一个线程去读取数据库,更新缓存。需要做缓存预热处理(项目启动时加载缓存)操作,数据可能不会被及时更新
- 3.提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断请求的key是否有效,如果不合法直接返回
- redis缓存雪崩问题解决?
- 缓存雪崩,即缓存同一时间大面积失效,这个时候又来了一波请求,结果怼到数据库,从而导致数据库连接异常(设置key过期时间,大面积同时失效)
- 解决方案:
- 1.给缓存的失效时间,加上一个随机值,避免集体失效
- 2.使用互斥锁,但是该方案吞吐量明显下降
- 3.双缓存,我们有两个缓存A和B,缓存A的失效时间为20分钟,缓存B不设置失效时间。自己做缓存预热操作。双缓存的实现过程:从缓存A读取数据库,有则直接返回,A没有数据,直接读B,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存A和缓存B。