- window安装:
https://github.com/MSOpenTech/redis/releases
打开cmd,运行redis,命令:
redis-server.exe redis.windows.conf
另打开cmd,连接redis,命令:
redis-cli.exe -h 127.0.0.1 -p 6379
- linux下安装:
http://redis.io/download
$ wget http://download.redis.io/releases/redis-5.0.7.tar.gz
$ tar xzf redis-5.0.7.tar.gz
$ cd redis-2.8.17
$ make
启动服务:
$ cd src
$ ./redis-server
测试客户端程序redis-cli与redis服务交互
$ cd src
$ ./redis-cli
redis>set test a
ok
redis>get test
"a"
1、redis支持的数据类型
- String(字符串),格式:set key value ,get key,一个键最大能储存512M
- Hash(哈希),格式:hmset name key1 value1 key2 value2,hgetall name, hget name key1 ,del object 适合储存对象(相当于Map集合,存储键值对)
- List(列表:双向链表,按照插入顺序排序,元素重复)
格式:lpush name value(在key对应头部添加字符串元素)
rpush name value(在key对应的尾部添加字符串元素),
lrem name index(删除索引对应的值),llen name(key对应list的长度)
lrange name 0 2(根据索引查询list下的元素)
- set(通过哈希表实现,元素不重复,无序集合),格式:sadd name value ,smembers name(查询集合下的元素)
- zset(通过哈希表实现,元素不重复,有序集合),格式:zadd zset 0 a,zadd zset 1 b,zrangebyscore zset 0 2
用SELECT命令更换数据库:
redis> SELECT 1
redis [1] >
exists key (检查key是否存在)
del key (删除key)
GETSET key value(将给定 key 的值设为 value ,并返回 key 的旧值(old value)
2、redis事务
先以multi开启事务,输入多个命令进入事务,最后由exec触发事务事务可以理解为一个批量的打包执行脚本,但批量执行不是原子化操作,中间某条指令的失败不会导致前面的回滚后面的没办法操作
单个redis命令是原子性的,事务并非是原子性的
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set String a
QUEUED
127.0.0.1:6379> get String
QUEUED
127.0.0.1:6379> exec
1) OK
2) "a"
127.0.0.1:6379>
3、客户端如何通过密码验证连接到 redis 服务,并检测服务是否在运行
redis 127.0.0.1:6379> AUTH "password"
OK
redis 127.0.0.1:6379> PING
PONG
4、什么是redis的持久化
持久化就是把内存的数据写到磁盘中,防止服务宕机内存数据丢失
5、使用redis好处
- 速度快:数据存在内存中,类似于hashMap,优势查询与操作的时间复杂度都是O(1),每秒可以处理超过十万次的读写操作
- 支持丰富的数据类型:string,hash,list,set,zset
- 单个value最大是1G,cache最大是1M
- 支持事务:操作不是原子性的,即使某些操作错误,也不会影响其他操作的执行
- 丰富的特性:可用于缓存,按key设置过期时间,过期后将自动删除
6、数据结构的使用场景
- string:计数功能的缓存(微博数,粉丝数)
- hash:储存用户信息,商品信息,单点登录用hash储存用户信息,以cookieId作为key,设置30分钟为缓存的过期时间,很好的模拟像session的效果
- list:可以做简单的消息队列,可以利用lrange命令,做基于redis的分页功能
- set:利用交集,差集,补集可以计算共同喜好,全部喜好,独有的喜好
- Sorted set:排行榜应用,取top
7、redis主从复制,哨兵,集群三个的区别
- 主从复制:读写分离,备份,一个master可以有多个slaves
- 哨兵:监控,自动转移,哨兵发现主服务器挂了,会从slaves中从新选举新的服务器
- 集群:为了解决单机redis容量有限的问题
redis哨兵的主要功能:
- 集群监控:负责监控redis的master与slave进程是否正常(每隔1s每个哨兵会向集群集群(主服务器+从服务器+其他哨兵服务器)进程发送一次ping命令做一次心跳检测)
- 消息通知:如果那个redis实例出现故障,会通知消息给管理员(作为警报)
- 故障转移:如果master node挂了,会转移到slave节点上
- 配置中心: 如果故障发生转移了,通知client客户端新的master地址
8、redis的架构模式,特点
- 单机版:特点(简单),问题(内存容量有限、处理能力有限、无法高可用)
- 主从复制:特点(master/slave数据相同、降低了master读的能力),问题(无法保证高可用、没有解决master写的压力)
- 哨兵:redis sentinel是分布式系统中监控主从服务器,并在主服务器下线自动进行故障转移,3个特性
监控:sentinel会不断的主从服务器运行是否正常
提醒:当sentinel监控到redis服务器有问题时,会通过api向管理员或其他程序发送通知
自动故障迁移:当一个主服务器不能正常工作时,sentinel会进行自动故障迁移的操作
特点(保证高可用、监控各个节点、自动故障迁移),缺点(主从切换需要时间会丢数据、没有解决master写的压力)
- 集群(proxy)
Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器,Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议
特点:
- 多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins
- 支持失败节点自动删除
- 后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致
缺点:增加了新的 proxy,需要维护其高可用 ,failover 逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预
9、redis分布式锁实现
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放
如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!
分布式锁的实现方式:
1.数据库的乐观锁 2.基于redis的分布式锁 3.基于zookeeper的分布式锁
确保分布式锁可用,要满足4个条件:
1.互斥锁,在任何条件下,只有一个客户端持有锁(加锁会判断key是否存在)
2.不会发生死锁,即使一个客户端在持有锁期间崩溃而没有主动解锁,也能保证其他客户端加锁(加锁设置过期时间)
3.具有容错性,只要大部分redis节点可以正常运行,客户端就可以正常加锁解锁
4.解铃还须系铃人,加锁解锁必须是同一个客户端,客户端自己不能解其他客户端加的锁(requestId唯一标识)
加锁:
jedis.set(String key, String value, String nxxx, String expx, int time);
第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?
原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,
我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。
第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间。
总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,
同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。
我们的加锁代码满足我们可靠性里描述的三个条件。首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,
也就是只有一个客户端能持有锁,满足互斥性。其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,
锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,因为我们将value赋值为requestId,
代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端
Redis单机部署的场景,所以容错性我们暂不考虑
解锁:
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
第一行代码,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)
第二行代码,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。
eval()方法是将Lua代码交给Redis服务端执行
eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令
10、使用过Redis做异步队列么,你是怎么用的,有什么缺点
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试
缺点:在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等
11、缓存穿透、缓存雪崩、缓存预热、缓存更新、缓存降级
- 缓存穿透
缓存系统,都是按照key去缓存查询,如果不存在的key(恶意的请求)不断查询redis、数据库,请求量增大,对后端系统造成很大的压力
解决方法:
- 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
- 对一定不存在的key进行**过滤*,可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
- 缓存雪崩
缓存服务器重启或者大量缓存集中在某一个时间段失效,缓存数据失效,会给后端系统带来很大压力,导致系统崩溃
解决方法:
- 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量
- 为key 设置不同的缓存失效时间
-
给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存
- 缓存预热
系统上线,将缓存数据直接加载到缓存系统,用户直接请求缓存的数据;避免用户请求,先查询数据库,然后再将数据缓存
- 缓存更新
- 定时清理过期的缓存
- 用户请求时判断请求所用到的缓存是否过期,过期直接请求数据库,更新缓存
- 缓存降级
服务出现问题,但仍然要保证服务可用,配置开关实现人工降级
12、redis单点登录
单点登录(SSO):用户只需要登录一次就可以访问所有相互信任的应用系统
一个web工程:访问用户信息,写个拦截器,看session中能不能取到用户信息,取不到返回登录页面,用户登录后用户信息保存到session中
如果是分布式的项目(cloud),sso系统可以放到网关里面做处理,因为所有项目都要走网关
或者是一个专门的sso系统,访问先走sso,再走其他的系统
登录流程:
- 登录页面提交用户名密码
- 登录后生成token,相当于原来的jsessionid,字符串,可以使用uuid
- 把用户信息保存到redis,key是token,value就是user对象转换的json字符串
- 使用String类型保存session信息,可以使用“前缀:token”为key
- 设置key的过期时间,模拟Session的过期时间
- 把token写入cookie中
- cookie需要跨域
- cookie的有效期,关闭浏览器失效
- 登录成功
String token = UUID.randomUUID().toString();
jedisClient.set(USER_INFO+":"+token,JsonUtils.objectToJson(user));
jedisClient.expire(USER_INFO+":"+token,SESSION_EXPIRE);
CookieUtils.setCookie(request,response,COOKIE_TOKEN_KEY,token);
通过token查询用户信息,查询不到,用户信息已经过期,查询到数据,说明已经登录(需要重置key的过期时间)
安全退出:需要根据token删除redis中的key
redis项目使用场景:登录后保存用户的信息(单点登录),商户信息(商户信息登录后会经常被用到,先重redis取)
13、redis与memcache的区别
- Redis和Memcache都是将数据存放在内存中,都是内存数据库,不过memcache还可用于缓存其他东西,例如图片、视频等等
- Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储
- 虚拟内存--Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘
- 过期策略--memcache在set时就指定,例如set key1 0 0 8,即永不过期,Redis可以通过例如expire 设定,例如expire name 10
- 分布式--设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从
- 存储数据安全--memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)
- 灾难恢复--memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复
- Redis支持数据的备份,即master-slave模式的数据备份
- 应用场景不一样:Redis出来作为NoSQL数据库使用外,还能用做消息队列、数据堆栈和数据缓存等 Memcached适合于缓存SQL语句、数据集、用户临时性数据、延迟查询数据和session等
14、redis单线程的理解
Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有到达服务端的命令都不会立刻执行,所有的命令都会进入一个队列中,然后逐个执行,并且多个客户端发送的命令的执行顺序是不确定的,但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型
15、Redis的单线程为什么这么快
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
- 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 使用多路I/O复用模型,非阻塞I/O;
- Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
16、为什么不采用多进程或多线程处理
- 多线程处理可能涉及到锁
- 多线程处理会涉及到线程切换而消耗CPU
17、单线程处理的缺点
- 耗时的命令会导致并发的下降,不只是读并发,写并发也会下降
- 无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善
18、Redis不存在线程安全问题
Redis采用了线程封闭的方式,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作(即:多个Redis操作命令)的复合操作来说,依然需要锁,而且有可能是分布式锁。
19、持久化的方式
redis是一个支持持久化的的内存数据库,将内存的数据同步到硬盘保证持久化
- 方式:
- RDB快照(默认):内存中的数据以快照的方式写入一个二进制文件中,默认文件名dump.rdb,RDB是间隔一段时间进行持久化,如果持久化期间redis发生故障,会发生数据丢失
- AOF: 将redis执行的每次写命令记录到单独的日志文件中,当重启redis会重新将持久化的日志中文件恢复数据,AOF的文件比RDB大,恢复的速度慢
如果两种方式同时开启,数据恢复redis会优先选择AOF
优缺点:
- AOF文件比RDB更新频率高,优先使用AOF
- AOF比RDB更安全
- RDB性能比AOF好
- 如果两个都配了优先加载AOF