#Redis使用教程
- Redis键(key)
常用命令 | 用法 |
---|---|
keys * | 查看当前库中所有的key |
exists key | 判断某个key是否存在 |
type key | 判断key的类型 |
del key | 根据key删除数据 |
unlink key | 根据value选择非阻塞删除,仅将key从keyspace与那数据中删除,真正的删除在后续异步操作 |
expire key 10 | 给key设置过期时间 |
ttl key | 查看key还有多少秒过期 -1表示永不过期,-2表示已经过期 |
select | 命令切换数据库 [0-15]个默认为0号库 |
dbsize | 查看当前数据库的key数量 |
flushdb | 清空当前库 |
flushall | 清空所有库 |
- 数据类型
- String是redis最基本的数据类型,当字符串长度小于1M时,会进行成倍的扩容,如果超过1M则每次扩容1M,注意字符串value中最多是512M,是二进制安全的,意味着String可以包含任何数据,如jpg图片
-
set
添加键值对[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a3DqpE5h-1660037645799)(…/cloud-demo/img.png)] 参数 用法 NX 当数据库中key不存在时,可以将key-value添加数据库 XX 当数据库中key存在时,可以将key-value添加数据库,与NX参数互斥 EX key的超时秒数 PX key的超时毫秒数与EX参数互斥
-
常用命令 | 用法 |
---|---|
get | 查询对应键的值 |
append | 将给定的value追加到原值的末尾 |
strlen | 获取值得长度 |
setnx | 只有key不存在时 设置key的值 |
incr | 将key中存储的数字值加1,只能对数字值操作,如果为空,新增值为1 |
decr | 将key中存储的数字值减1,只能对数字值操作,如果为空,新增值为-1 |
incrby/decrby | 将key中存储的数字值增减,自定义步长, incr原子性操作(这里指不会被线程调度机制打断的操作) |
mset | 同时设置一个或多个key-value |
mget | 同时获取一个或多个key对应的value |
msetnx | 只有key不存在时,同时设置一个或多个key-value原子性有一个失败都失败 |
getrange | 获取值的范围类似Java中的substring,前包,后包 |
setrange | 用value覆写 |
setex | 设置键的同时设置过期时间,单位为s |
getset | 以新换旧,设置新值同时获得旧值 |
- Redis列表List
- 单键多值
redis列表是简单的字符串列表,按照插入顺序排序,可以在列表头或者列表尾进行插入,底层是双向链表,两端操作性能高 - 数据结构
List的数据结构是一种quickList。在元素较少时使用连续内存,这种结构是zipList,即压缩列表;当数据较多时才用quickList,是由一小块一小块zipList通过双向指针结合起来
- 单键多值
常用命令 | 用法 |
---|---|
lpush/rpush | 从左边或右边插入一个或多个值 |
lpop/rpop | 从左边或者右边吐出一个值,值在键在,值光键亡 |
rpoplpush | 从 |
lrange | 按照索引下标获取元素(从左到右)0左边第一个 -1右边第一个(0 -1) 表示取所有值 |
lindex | 按照索引下标获取元素(从左到右)从0开始 |
llen | 获取列表长度 |
linsert | 在 |
lrem | 从左边删除第n个value(从左到右) |
lset | 将列表key下标为index的值替换为value |
- Redis集合Set
- set与list相似,特殊在于可以自动排重的,当你需要列表,又不希望重复数据时,可以选set
- 底层是String类型的无序集合,其实是一个value为null的hash表,所以添加,查找,删除都是时间复杂度O(1)
- 数据结构
set数据结构是dict字典,字典是用哈希表实现的
常用命令 | 用法 |
---|---|
sadd | 将一个或多个member元素加入到集合key中,已经存在的member将被忽略 |
smembers | 取出该集合的所有值 |
sismember | 判断集合key是否含有该value的值,有1,没有0 |
scard | 返回该集合的元素个数 |
srem | 删除集合中某个元素 |
spop | 从该集合随机吐出一个值(删除) |
srandmember | 随机从该集合取出n个值,不会从集合中删除 |
smove | 把集合中一个值从一个集合移动到另一个集合 |
sinter | 返回两个集合的交集元素 |
sunion | 返回两个集合的并集元素 |
sdiff | 返回两个集合的差集元素(key1中的,不包含key2中的) |
- Redis哈希hash
- hash是一个键值对集合,是一个String类型的field和value映射表,hash特别适合存储对象,类似Java中的Map
- 数据结构
当长度短时,使用zipList,否则使用hashtable
- hash是一个键值对集合,是一个String类型的field和value映射表,hash特别适合存储对象,类似Java中的Map
常用命令 | 用法 |
---|---|
hset | 给 |
hget | 从 |
hmset | 批量设置hash的值 |
hexists | 查看hash表key中,给定域field是否存在 |
hkeys | 列出该hash集合的所有field |
hvals | 列出该hash集合的所有value |
hincrby | 为hash表key中的域feild的值加上增/减量 (1 -1…) |
hsetnx | 为hash表key中的域feild的值设置为value,当且仅当域feild不存在 |
Redis有序集合Zset |
- 有序集合Zset与普通set相似,是一个没有重复元素的字符串集合
- 有序集合中每个成员都关联一个评分,用来按照最低分到最高分的顺序来排序集合成员,集合成员是唯一的,但是评分可以相同
常用命令 | 用法 |
---|---|
zadd | 将一个或多个member元素及其score值加入有序集合key中 |
zrange | 返回有序集合key中,下标在 |
zrangebyscore key min max [WITHSCORES] [LIMIT offset count] | 返回有序集中,所有score值在min和max之间(包括等于)的成员,有序集合按照score值递增排序 |
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count] | 同上,改为从大到小排序 |
zincrby | 为元素的score加上增量 |
zrem | 删除该元素下,指定值的元素 |
zcount | 统计该集合分数区间内的元素个数 |
zrank | 返回该值在集合中的排名,从0开始 |
- 配置文件
-
units单位:配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit,大小写不敏感
-
includes包含:可以包含其他内容(公共部分参考前端的include)
-
network网络:
- bind默认情况下bind=127.0.0.1只接受本机访问请求,不写的情况下,无限制接收任何IP访问
- protect-mode yes 开启保护模式只能本机访问远程无法访问
- port 6379 端口号默认6379
- tcp-backlog 511 在高并发的情况下需要设置高的backlog来避免客户端连接慢的问题 backlog是连接队列 总和=未完成三次握手队列+已完成三次握手队列
- timeout 0 无操作超时时间,0为永不超时
- tcp-keepalive 300 检查心跳时间
-
general通用:
- daemonize yes是否为后台进程
- pidfile存放pid的位置,每个实例会产生不同的pid文件
- loglevel notice 日志级别
- logfile 设置日志文件输出路径
- databases 16 默认有16个数据库,使用0号库
-
security安全
-
requirpass foobared 设置密码
limits限制 -
maxclients 设置redis同时可以和多少个客户端连接,默认下10000个
-
maxmemory 设置redis可以使用的内存量,建议必须设置,否则服务器会宕1机,一但达到上限,会试图移除内部数据,移除规则可通过maxmemory-policy来指定
-
maxmemory-policy规则 用法 volatile-Iru 使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用) allkeys-lru 在所有集合key中使用LRU算法移除key volatile-random 在过期集合中移除随机的key,只对设置了过期时间的键 allkeys-random 在所有集合key中,移除随机的key Volatile-ttl 移除那些TTL值最小的key,即那些最近要过期的key noeviction 不进行移除。针对写操作,只是返回错误信息 -
maxmemory-sample设置样本数量LRU算法和TTL算法都非精确算法,是估算值,所以要设置样本大小redis会默认检测这么多key并选择其中LRU的那个,一般设置3-7,样本越小越不精准,但性能消耗越小
*Redis的发布和订阅
命令 用法 subscribe channel 订阅channel publish channel message 给channel发送message消息 -
*Redis新数据类型
- BitMaps可以对字符串进行位操作,统计一天内访问用户量,当用户访问多的时候相对于set更节省空间,当用户访问少的时候set更节省空间
常用命令 | 用法 |
---|---|
setbit | 设置BitMaps中某个偏移量的值(0或1)offset偏移量从0开始 |
getbit | 获取BitMaps中某个偏移量的值(0或1)offset偏移量从0开始 |
bitcount | 统计从start字节开始到end字节比特的值为1的个数-1表示最右一个,即倒数第一个,-2表示倒数第二个 |
bitop and(or/not/xor) | bitop是一个复合操作,可以做多个bitmaps的and(交集)or(并集)not(非)xor(异或)操作并将结果保存在destkey中 |
- HyperLogLog可以计算基数,并且占用空间极少,比如统计不重复的网站访问量
常用命令 | 用法 |
---|---|
pfadd | 添加指定元素到HyperLogLog中,成功则返回1,否则返回0,即没有重复添加 |
pfcount | 计算HLL的近似基数,可以计算多个HLL,如用HLL存储每天的UV,计算一周的UV可以用七天的UV合并会自动去重计算即可 |
pfmerge | 将一个或多个HLL合并后的结果存入另一个HLL |
- Geospatial专门针对经纬度或者地理位置进行操作
常用命令 | 用法 |
---|---|
geoadd | 添加地理位置信息(经度,纬度,名称)两极无法添加,一般会下载城市数据通过Java一次性导入,有效经度(-180,180),有效纬度(-85.05112878,85.05112878),当添加时不在范围返回一个错误,已经添加数据无法添加 |
geopos | 获得指定地区的坐标值 |
geodist | 获取两个位置之间的直线距离m(默认,米)/km(千米)/ft(英尺)/mi(英里) |
georadius | 以给定的经纬度为中心,找出某一半径内的元素(经度 纬度 距离 单位) |
- Jedis操作Redis6
- 如果测试发现连接超时,第一种情况配置文件两处没修改,第二种情况防火墙没放行
- 导入Jedis依赖
redis.clients jedis 4.2.3
- 创建Redis对象
Jedis jedis = new Jedis("192.168.1.5",6379);
- 关闭jedis管道
jedis.close();
- SpringBoot整合Redis
- 导入Redis依赖
org.springframework.boot spring-boot-starter-data-redis
- SpringBoot2.X集成Redis所需common-pool2依赖
org.apache.commons commons-pool2
- 配置SpringBoot的redis配置文件
spring: redis: host: 192.168.1.5 #服务器地址 port: 6379 #服务器端口号 database: 0 #Redis数据库索引默认为0 timeout: 1800000 #连接超时时间(毫秒) lettuce: pool: max-active: 20 #最大连接数(使用负值表示没有限制) max-wait: -1 #最大阻塞等待时间(负值表示没有限制) max-idle: 5 #连接池中最大空闲连接 min-idle: 0 #连接池中最小空闲连接
- 添加Redis配置类
@EnableCaching @Configuration public class RedisConfig extends CachingConfigurerSupport { @Bean public RedisTemplateredisTemplate(RedisConnectionFactory factory){ RedisTemplate template = new RedisTemplate<>(); RedisSerializer redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setConnectionFactory(factory); //key序列化方式 template.setKeySerializer(redisSerializer); //value序列化方式 //value序列化方式 template.setValueSerializer(jackson2JsonRedisSerializer); //value hashmap序列化方式 template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory){ RedisSerializer redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解决查询缓存转换异常问题 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); //配置序列化解决乱码问题,过期时间600s RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(600)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } }
- 编写controller
@RestController @RequestMapping("/redisTest") public class RedisTestController { @Autowired private RedisTemplate redisTemplate; @GetMapping public String testRedis(){ //设置值到Redis里 redisTemplate.opsForValue().set("name","jack"); //从Redis中获取值 String name = (String) redisTemplate.opsForValue().get("name"); return name; } }
- Redis的事务操作
- Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事
务在执行的过程中,不会被其他客户端发送来的命令清求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。 - Multi
从输入Multi命令开始,输入的命今都会依次讲入命今队列中,但不会执行。直到输入
Exec后,Redis会将之前的命令队列中的命令依次执行
组队的过程中可以通过discard来放弃组队
- Exec
- Discard
- 事务的错误问题
- 在组队过程中如果任意一个命令出现错误,就不执行了
- 在执行过程中出错,只有出错的那个不执行,其他的依然执行
- 事务的冲突问题
- 场景:很多人知道你的账户,同时用你的账户去抢购
- 悲观锁:每次操作之前先上锁
- 乐观锁:定义版本号,在操作时先检查版本号,版本号正确时,修改数据同时修改版本号
乐观锁常用命令 | 用法 |
---|---|
watch key [key…] | 在执行multi之前,先执行watch key[key2]可以监视一个或多个key,如果在事务执行之前这个或这些key被其他命令所修改,那么事务将被打断 |
unwatch | 取消对key的监视,在执行watch命令后,exec或discard先被执行了,就不需要再执行unwatch了 |
- 事务的三特性
-
单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
-
没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
-
不保证原子性
事务中如果有一条命令执行失败其他的命令仍然会被执行,没有回滚
-
- 秒杀案例
- 判断用户和商品id是否存在
- 连接redis(通过连接池得到jedis对象)
- 拼接用户key和商品key
- 监视库存(watch方法)
- 获取库存如果库存为null,则秒杀还没开始
- 判断是否用户重复秒杀(set集合防止重复,判断值有没有)
- 判断数量如果库存小于1,则已经结束
- 秒杀过程:库存减1,将秒杀成功用户添加到set集合中
- 安装并发工具ab测试并发 yum install httpd-tools
- ab -n 1000(1000个请求) -c 100(并发数量) -p ~/postfile(当前目录的postfile,里面的内容:prodid=0101&(以&结尾,存放发送post请求时携带的参数)) -T ‘application/x-www-form-urlencoded’(类型) http://192.168.1.1:8080/ (请求路径不要写localhost)
- 超卖和连接超时问题
- 设置乐观锁
- 监视库存
- 在秒杀过程使用事务
- 组队操作 1.库存减1,2.将秒杀成功用户添加到set集合中
multi.decr(kckey); multi.sadd(userid,uid);
- 执行操作
List
- 判断multi执行结果为空或者为0则执行失败,关闭jedis
- 设置连接池
- 设置乐观锁
- 乐观锁造成的库存遗留问题(没有买完就显示结束了)
- Lua嵌入式脚本(只支持redis2.6版本以上)类似事务,在执行过程中不能被别人干预,不会被插队
- Redis持久化操作
- RDB(默认开启)在指定时间间隔内将内存的数据集快照写入到磁盘,最后一次数据同步可能会丢失,比较占用磁盘会申请两倍空间
- AOF(默认关闭)以日志的形式记录写操作,写指令,只许追加文件不许改写文件,比RDB更占空间,恢复备份慢,有一定的性能压力,存在潜在bug造成不能恢复
- AOF和RDB同时开启默认使用AOF
- 使用redis-check-aof–fix appendonly.aof进行文件修复
- 同步频率设置
- appendfsync always 每次写操作都同步,性能较差
- appendfsync everysec 每秒同步,如果出现宕机,最后一次数据会丢失
- appendfsync no redis不主动进行同步,同步时机交给操作系统
- 重写压缩操作redis4.0之后才有
- 文件大于64MB才触发
- 文件达到100%开始重写(达到原来的两倍时)
- 写时复制(临时文件覆盖旧文件,和RDB一样)
- 官方建议两个都启用,不建议单独用AOF
- Redis的主从复制
- 特点
- 读写分离,分担压力
- 容灾的快速恢复
- 只能一主(写操作)多从(读操作)(最高领导只能一个,防止命令不一样听谁的)
- 一主多从,从服务器挂掉以后,重启并不能加入到主服务器中,需要重新加入,在加入时会自动把主服务器的数据复制
- 一主多从,主服务挂了,从服务器不变还是从服务器
- 一主多从配置
- 创建/myredis文件夹
- 复制redis.conf配置文件
- 配置一主多从,创建多个配置文件
- 在配置文件中写入内容
- 在配置文件中引入公共文件 include /myredis/redis.conf
- 配置pid文件位置 pidfile /var/run/redis_6379.pid
- 配置端口号 port 6379
- 配置RDB的文件名称 dbfilename dump6379.rdb
- 启动多个redis
- 查看当前服务器运行状况
- 连接上服务器后使用 info replication
- 在从机上使用 slaveof 成为某个服务器的从机
- 薪火相传
- 和一主多从特点一样,不过主服务器工作量会减少,交由从服务
- 如果中间一台从服务器挂了,他的从服务器就也无法进行数据同步
- 反客为主
- 当主服务器挂掉,从服务器可以变成主服务器
- 使用slaveof no one命令让从服务器上位
- 手动完成的不方便,后面有哨兵模式可以自动完成
- 哨兵模式
- 在/myredis目录下新建sentinel.conf文件,名字不能错
- 在配置文件中,只配置一句话:sentinel monitor mymaster 127.0.0.1 6379 1 其中mymaster为监控对象起的服务器名称,1为至少有多少个哨兵同意迁移的数量
- 启动哨兵执行 redis-sentinel /myredis/sentinel.conf
- 原主服务器重启后变成从服务器
- replica-priority 100 值越小越优先,值一样谁和主服务器的数据同步度高选谁(选择偏移量最大的),如果偏移量一样大选择runid最小的(随机生成的)
- Redis集群操作
- 解决的问题
- 解决容量不够的问题,扩容
- 解决并发操作,分担写压力
- 无中心配置相对简单
- 缺点
- 多键操作不能被支持(可能跨插槽)
- 多键事务不被支持,lua脚本不被支持
- 配置
- 删除持久化文件rdb,aof文件都删
- 在配置文件中引入公共文件 include /myredis/redis.conf
- 配置pid文件位置 pidfile /var/run/redis_6379.pid
- 配置端口号 port 6379
- 配置RDB的文件名称 dbfilename dump6379.rdb
- 配置cluster-enable yes 打开集群模式
- 配置cluster-config-file nodes-6379.conf 设置节点配置文件名
- 配置cluster-node-timeout 15000 设置节点失联时间,超过改时间(毫秒),集群自动进行主从切换
- 启动redis redis-server redis6379.conf
- 集群合体,进入到redis的安装目录src中使用命令redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389 192.168.11.101:6390 192.168.11.101:6391此处不要用127.0.0.1用真实ip,replicas 1采用最简单的方式配置集群,一台主机,一台从机,正好三组
- cluset-require-full-coverage yes 一个插槽挂掉其他都挂设置为no的话一个挂掉其他还能工作
- redis可能遇到的问题
-
缓存穿透
- 现象
- 应用服务器压力突然变大
- redis命中率降低了
- 一直查询数据库
- 原因
- redis查询不到数据
- 出现了很多非正常的url访问(访问的url参数不存在),黑客攻击(直接报网警)
- 解决方案
- 对空值缓存:如果查询返回的数据为空(不管数据是否不存在),我们仍然把这个空结果缓存,设置空结果的时间会很短,最多不超过五分钟
- 设置可访问的名单(白名单):使用bitmaps类型定义一个可访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmaps里的id进行比较,如果访问id不在bitmaps里进行拦截,不允许访问
- 采用布隆过滤器:(布隆过滤器(Bloom Filter)是一种很长的二进制向量(位图)和一系列随机映射函数(哈希函数))
- 进行实时监控:当发现redis命中率开始急剧降低,需要排查访问对象和访问数据,和运维人员进行配合,设置访问黑名单
- 现象
-
缓存击穿
- 现象
- 数据库访问压力瞬时增大
- redis里并没有出现大量的过期
- redis处于正常运行状态
- 原因
- redis里某个key过期了,此时还有大量访问使用这个key
- 解决方案
- 预先设置热门数据:在redis高峰访问前,把一些热门数据提前存入到redis里面,加大这些热门数据key的访问时长
- 实时调整:现场监控哪些数据热门,实时调整key的过期时长
- 使用锁:
- 现象
-
缓存雪崩
- 现象
- 数据库压力变大服务器崩溃
- 原因
- 在极少时间段,查询了大量集中过期的key
- 解决方案
- 构建多级缓存架构:nginx缓存+redis缓存+其他缓存(ehcache等)
- 使用锁或队列:用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况。
- 设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
- 将缓存失效时间分散开:比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效
- 现象
-
分布式锁
- 原因
- 一个锁不能锁住集群中其他服务器,即JVM不能跨设备锁
- 实现方案
- 基于数据库实现分布式锁
- 基于缓存(redis等)
- 使用setnx命令进行设置锁(要给锁设置过期时间,防止死锁),del进行删除释放锁,上锁之后突然异常无法设置过期时间(上锁时同时设置值过期时间使用set命令加ex nx 参数)
- 使用uuid防止误删(将别人的锁释放了)
- 通过uuid表示不同的操作,set lock uuid nx ex 10
- 释放锁的时候先判断当前uuid和要释放锁的uuid是否一样
- lua脚本解决原子性误删(判断uuid正确后准备删除时,自动删除了,又执行了手动删除,将其他锁释放掉了)
- 基于Zookeeper
- 优缺点
- 性能:redis最高
- 可靠性:Zookeeper最高
- 原因
- Redis6新功能
- ACL
-
常用命令 用法 acl list 展示当前用户权限列表 acl cat 查看添加权限指令类别 acl whoami 查看当前用户
-
- IO多线程
- 多线程中只用来网络数据的读写和协议的解析,操作命令还是单线程
- 默认不开启在配置文件中修改 io-threads-do-reads yes ;io-threads 4
- 工具支持Cluster
- redis5以上不需要单独安装搭建集群的ruby环境,将redis-trib.rb的功能集成到redis-cli
dang ↩︎