暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java项目笔记之Redis

java学途 2021-06-21
419


不点蓝字,我们哪来故事?



昨天的bug:

mongodb的id是ObjectId类型(不提供会自动添加),前台传过来的是String,严瑾起见保存时setId为null,让mongodb自己添加_id。



Redis

键值存储:

这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。Key/value模型对于IT系统来说的优势在于简单、易部署

代表: Redis


非关系型数据库特点:

1.数据模型比较简单.(主要)

2.需要灵活性更强的应用系统

3.对数据库性能要求较高(主要)

4.不需要高度的数据一致性(主要)

5.对于给定key,比较容易映射复杂值的环境.


与关系型数据库区别:

关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织

优点:

  1. 易于维护:都是使用表结构,格式一致;

  2. 使用方便:SQL语言通用,可用于复杂查询;

  3. 复杂操作:支持SQL,可用于一个表以及多个表之间非常复杂的查询。

缺点:

  1. 读写性能比较差,尤其是海量数据的高效率读写;

  2. 固定的表结构,灵活度稍欠;

  3. 高并发读写需求,传统关系型数据库来说,硬盘I/O是一个很大的瓶颈。

非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合,可以是文档或者键值对等。

优点:

  1. 格式灵活:存储数据的格式可以是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,使用灵活,应用场景广泛,而关系型数据库则只支持基础类型。

  2. 速度快:nosql可以使用硬盘或者随机存储器作为载体,而关系型数据库只能使用硬盘;

  3. 高扩展性;

  4. 成本低:nosql数据库部署简单,基本都是开源软件。

缺点:

  1. 不提供sql支持,学习和使用成本较高;

  2. 无事务处理;

  3. 数据结构相对复杂,复杂查询方面稍欠。


什么是Redis

Redis简介:

是以key-value形式存储,和传统的关系型数据库不一样.不一定遵循传统数据库的一些基本要求.(非关系型的,分布式的,开源的,水平可拓展的)

优点:

  • 对数据高并发读写(直接是内存中进行读写的)

  • 对海量数据的高效率存储和访问

  • 对数据的可拓展性和高可用性.

  • 单线程操作,每个操作都是原子操作,没有并发相关问题

缺点:

  • redis(ACID处理非常简单)

  • 无法做太复杂的关系数据库模型

  • Redis是以key-value store存储.

键可以包含:(string)字符串,哈希,(list)链表,(set)集合,(zset)有序集合.这些数据集合都指出push/pop,add/remove及取交集和并集以及更丰富的操作.redis支持各种不同方式排序,为了保证效率,数据都是缓存在内存中.它可以从周期性的把更新的数据写入到磁盘或者把修改操作写入追加的文件中.


Redis定位是缓存,加快数据的访问速度,减轻对数据库存储与访问的压力。

Redis是单线程,那为什么还会快?,节省了到线程之间的频繁切换所花费的时间,所以快。


性能极高 – Redis能支持超过 10W次每秒的读写频率。

丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。

原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。

丰富的特性 – Redis还支持 publish/subscribe(发布/订阅), 通知, key 过期等等特性。


Redis安装:
安装步骤:

傻瓜式安装,下一步,下一步就可以了

Redis默认端口是: 6379

安装好后会自动启动服务器,并且没有密码

进入到安装目录下,使用cmd命令行运行redis-cli.exe程序,出现下面界面表示安装成功。

常用配置:

port:服务的端口号,默认为6379

daemonize:后台运行,值为yes | no 默认为no

requirepass:密码,默认是没有密码

protected-mode:保护模式,值为yes | no默认是yes,不能远程访问该redis服务,如果需要远程访问,则该为no。

bind:绑定的IP地址,默认是绑定本机地址127.0.0.1,如果需要其他IP能访问到该redis服务,则在这个属性添加IP地址,或者直接把该属性注释掉,这样所有的IP地址都可以访问。



Redis数据类型:

Redis命令格式:  键值对

类型命令 key 参数数据

String类型

String类型包含多种类型的特殊类型,并且是二进制安全的.比如序列化的对象进行存储,比如一张图片进行二进制存储.,比如一个简单的字符串,数值等等.

类型命令 key 参数数据

  • set key value -> 存入键值对

  • get key -> 根据键取出值

  • incr key -> 把值递增1

  • decr key -> 把值递减1

  • incrby key num -> 偏移值

  • del key -> 根据键删除键值对

  • setex key timeout value -> 存入键值对,timeout表示*失效时间*,单位s ,用于验证码

  • ttl ->可以查询出当前的key还剩余多长时间过期,用于验证码

  • mset k1 v1 k2 v2 ... -> 批量存入键值对

  • mget k1 k2 ... -> 批量取出键值

  • append key 'value' -> 原值后拼接新内容

  • setnx key value -> 存入键值对,键存在时不存入

  • setex key timeout value -> 存入键值对,timeout表示失效时间,单位s

  • ttl ->可以查询出当前的key还剩余多长时间过期

  • setrange key index value -> 修改键对应的值,index表示开始的索引位置


应用场景:

  1. 缓存功能:字符串最经典的使用场景,redis作为缓存层,Mysql作为储存层,绝大部分请求数据都是redis中获取,由于redis具有支撑高并发特性,所以缓存通常能起到加速读写和降低 后端压力的作用。

  2. 计数器:许多运用都会使用redis作为计数的基础工具,他可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他的数据源。如:视频播放数系统就是使用redis作为视频播放数计数的基础组件。

  3. 共享session:出于负载均衡的考虑,分布式服务会将用户信息的访问均衡到不同服务器上,用户刷新一次访问可能会需要重新登录,为避免这个问题可以用redis将用户session集中管理,在这种模式下只要保证redis的高可用和扩展性的,每次获取用户更新或查询登录信息都直接从redis中集中获取。

  4. 限速/防刷:处于安全考虑,每次进行登录时让用户输入手机验证码,为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率。


hash类型

Hash类型是String类型的field和value的映射表.或者说是一个String集合.它特别适合存储对象,相比较而言,讲一个对象存储在Hash类型里要比存储在String类型里占用更少的内存空间,并方便存储整个对象

  • hset key hashkey hashvalue -> 存入一个hash对象

  • hget key hashkey -> 根据hash对象键取去值

  • hexists key hashkey -> 判断hash对象是含有某个键

  • hincrby key hashkey 递增值 -> 递增hashkey对应的值

  • hlen key -> 获取hash对象键的数量

  • hkeys key -> 获取hash对象的所有键

  • hvals key -> 获取hash对象的所有值

  • hgetall key -> 获取hash对象的所有数据

同样有hsetnx,其作用跟用法和setnx一样

应用场景

哈希结构相对于字符串序列化缓存信息更加直观,并且在更新操作上更加便捷。

所以常常用于用户信息等管理,但是哈希类型和关系型数据库有所不同,哈希类型是稀疏的,而关系型数据库是完全结构化的,关系型数据库可以做复杂的关系查询,而redis去模拟关系型复杂查询开发困难,维护成本高

开发中不建议使用hash,如果非要使用,建议使用对象,对象.toJsonString()

list类型

Redis中的List类似Java中的queue,也可以当做List来用.List类型是一个链表结构的集合,其主要功能有push,pop,获取元素等.更详细的说,List类型是一个双端链表的结构,我们可以通过相关操作进行集合的头部或者尾部添加删除元素,list的设计非常简单精巧,即可以作为栈,又可以作为队列.满足绝大多数需求.


  • rpush key value -> 往列表右边添加数据

  • lpush key value -> 往列表左边添加数据

  • lpop key -> 弹出列表最左边的数据

  • rpop key -> 弹出列表最右边的数据

  • lrange key start end -> 范围显示列表数据,全显示则设置0 -1

  • llen key -> 获取列表长度

  • linsert key before/after refVal newVal -> 参考值之前/后插入数据

  • lset key index value -> 根据索引修改数据

  • lrem key count value -> 在列表中按照个数删除数据

  • ltrim key start end -> 范围截取列表

  • lindex key index -> 根据索引取列表中数据


应用场景:

1.消息队列:redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端是用lupsh从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞时的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性.

2.文章列表:每个用户都有属于自己的文章列表,现在需要分页展示文章列表,此时可以考虑使用列表,列表不但有序,同时支持按照索引范围获取元素。

3.朋友圈点赞;(面试瞎吹自己做过朋友圈点赞的功能)      规定:朋友圈内容的格式:            1,内容:  user:x:post:x content来存储;
           2,点赞:  post:x:good list来存储;
     1,创建一条朋友圈内容:set user:1:post:91 'hello redis';
     2,点赞:                    lpush post:91:good '{id:1,name:stef,img:xxx.jpg}'
                   lpush post:91:good '{id:2,name:lanxw,img:xxx.jpg}'
                   lpush post:91:good '{id:3,name:fly,img:xxx.jpg}'
     3,查看有多少人点赞: llen post:91:good
     4,查看哪些人点赞:lrange post:91:good 0 -1
思考,如果用数据库实现这个功能,SQL会多复杂??

4.回帖     1,创建一个帖子:set user:1:post:90 'wohenshuai'
    2,创建一个回帖:set postreply:1 'nonono'
    3,把回帖和帖子关联:lpush post:90:replies 1
    4,再来一条回帖:set postreply:2 'hehe'
                              lpush post:90:replies 2
    5,查询帖子的回帖:lrange post:90:replies 0 -1
                              get postreply:2


set类型

Set集合是string类型的无序集合,set是通过hashtable实现的,对集合我们可以取交集,并集,差集.

  • sadd key value -> 往set集合中添加元素

  • smembers key -> 列出set集合中的元素

  • srem key value -> 删除set集合中的元素

  • spop key count -> 随机弹出集合中的元素

  • sdiff key1 key2 -> 返回key1中特有元素(差集)diffrient

  • sdiffstore var key1 key2 -> 返回key1中特有元素存入另一个set集合

  • sinter key1 key2 -> 返回两个set集合的交集

  • sinterstore var key1 key2 -> 返回两个set集合的交集存入另一个set集合

  • sunion key1 key2 -> 返回两个set集合的并集

  • sunionstore var key1 key2 -> 返回两个set集合的并集存入另一个set集合

  • smove key1 key2 value -> 把key1中的某元素移入key2中

  • scard key -> 返回set集合中元素个数

  • sismember key value -> 判断集合是否包含某个值

  • srandmember key count -> 随机获取set集合中元素


应用场景

1,去重;

2,抽奖:随机pop几个           1,准备一个抽奖池:sadd luckydraw 1 2 3 4 5 6 7 8 9 10 11 12 13
          2,抽3个三等奖:spop luckydraw 3
          3,抽2个二等奖:spop luckydraw 2
  4,抽1个二等奖:spop luckydraw 1

3,做set运算(好友推荐推荐交集)        1,初始化好友圈  

 sadd user:1:friends 'user:2' 'user:3' 'user:5'
         sadd user:2:friends 'user:1' 'user:3' 'user:6'
         sadd user:3:friends 'user:1' 'user:7' 'user:8'
       2,把user:1的好友的好友集合做并集;  

sunion user:2:friends user:3:friends
       user:1 user:3 user:6 user:7 user:8
       3,让这个并集和user:1的好友集合做差集;           合并:  sunionstore aa user:2:friends user:3:friends
          差集:  sdiff aa user:1:friends
            user:1 user:6 user:7 user:8
       4,从差集中去掉自己           sdiffstore bb aa user:1:friends
          srem bb user:1
          user:6 user:7 user:8
       5,随机选取推荐1个好友           srandmember aa 1


zset类型

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

  • zadd key num name -> 存入分数和名称

  • zrange key start end -> 按照分数升序输出名称

  • zrevrange key start end -> 按照分数降序输出名称

  • zincrby key num name -> 偏移名称对应的分数

  • zrank key name -> 升序返回排名

  • zrevrank key name -> 降序返回排名

  • zcard key -> 返回元素个数

  • zrangebyscore key min max [withscores] -> 按照分数范围升序输出名称

  • zrevrangebyscore key max min [withscores] -> 按照分数范围降序输出名称

  • zrem key name -> 删除名称和分数

  • zremrangebyscore key max min [withscores] -> 根据分数范围删除元素

  • zremrangebyrank key start end -> 根据排名删除元素

  • zcount key min max -> 按照分数范围统计个数


应用场景

排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。


总结

什么时候用到缓存?

  • 频繁修改的数据(DML),减少对数据库的操作;

  • 不需要经常修改的数据(DQL),经常查询查询,减少对数据库的操作。


1: 项目操作涉及到缓存操作, 首选 redis

2: 如果确定使用redis, 此时需要考虑使用哪个数据类型

1>如果要排序选用zset

2>如果数据是多个且允许重复选用list

3>如果数据是多个且不允许重复选用set

4>剩下的使用string

3:怎么设计 key  与 value值

key特点:唯一 有效性 可识别性 灵活

value:灵活



redis高级命令:全局的命令
  • 返回满足的所有键 keys * (可以模糊查询)

  • exists 是否存在指定key

  • expire 设置某个key的过期时间.使用ttl查看剩余时间

  • persist 取消过期时间

  • flushdb 清空当前数据库,flushall清空所有数据库

  • select 选择数据库 数据库为0到15(一共16个数据库) 默认进入的是0数据库

  • move [key] [数据库下标] 讲当前数据中的key转移到其他数据库中

  • randomkey 随机返回数据库里的一个key

  • rename重命名key

  • echo 打印名

  • dbsize 查看数据库的key数量

  • info 获取数据库信息

  • config get 实时传储收到的请求(返回相关的配置信息)

  • config get * 返回所有配置


redis安全性

因为redis速度非常快,所以在一台比较好的服务器下,一个外部用户在一秒内可以进行15w次的密码尝试,这意味你需要设定非常强大的密码来防止暴力破解.

vi编辑redis.conf文件,找到下面进行保存修改

requirepass [密码]

重启服务器 pkill redis-server

再次进入127.0.01:6379>keys *

(error)NOAUTH Authentication required.

会发现没有权限进行查询auth [密码]

输入密码则成功进入

每次进入的时候都需要输入免密,还有种简单的方式:

redis-cli -a [密码]


redis事务[拓展]

Redis的事务非常简单,使用方法如下:

首先是使用multi方法打开事务,然后进行设置,这时设置的数据会放到队列里进行保存.最后使用exec执行.把数据依次存储到redis中.使用discard方法取消事务.


redis持久化机制(面试)

Redis是一个支持持久化的内存数据库,也就是说redis需要经常将缓存中的数据同步到硬盘来保证持久化。

Redis持久化的两种方式:

  1. RDB方式

    1.snapshotting(快照)默认方式.将内存中以快照的方式写入到二进制文件中.默认为dump.rdb.可以配置设置自动做快照持久化方式.我们可以配置redis在n秒内如果超过m个key就修改自动做快照.Snapshotting设置:save 900 1  #900秒内如何超过1个Key被修改则发起快照保存save 300 10 #300秒内如果超过10个key被修改,则发起快照保存save 60 10000

  2. AOF方式

    2.append-only file (缩写aof)的方式,由于快照方式是在一定时间间隔做一次,所以可能发生reids意外down的情况就会丢失最后一次快照后的所有修改的数据.aof比快照方式有更好的持久化性,是由于在使用aof时,redis会将每一个收到的写命令都通过write函数追加到命令中,当redis重新启动时会重新执行文件中保存的写命令来在内存中重建这个数据库的内容.这个文件在bin目录下:appendonly.aofaof不是立即写到硬盘中,可以通过配置文件修改强制写到硬盘中.aof设置:appendonly yes 启动aof持久化方式有三种修改方式#appendfsync always//收到命令就立即写到磁盘,效率最慢.但是能保证完全的持久化#appendfsync everysec//每秒写入磁盘一次,在性能和持久化方面做了很好的折中#appendfsync no 完全以依赖os 性能最好,持久化没保证


Redis的key淘汰机制

过期策略

我们set key的时候,都可以给一个expire time,就是过期时间,指定这个key比如说只能存活1个小时,我们自己可以指定缓存到期就失效。

如果假设你设置一个一批key只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的?

答案是:定期删除+惰性删除

所谓定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。

注意,这里可不是每隔100ms就遍历所有的设置过期时间的key,那样就是一场性能上的灾难。

实际上redis是每隔100ms随机抽取一些key来检查和删除的。

但是,定期删除可能会导致很多过期key到了时间并没有被删除掉,所以就得靠惰性删除了。

这就是说,在你获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。

并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下

通过上述两种手段结合起来,保证过期的key一定会被干掉。

但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?

如果大量过期key堆积在内存里,导致redis内存块耗尽了,怎么办?

答案是:走内存淘汰机制。


内存淘汰机制

如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧

  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)

  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧

  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)

  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key

  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除


Jedis基本使用

创建SpringBoot项目,添加相关依赖;

     @Test
    public void testJedisPool() {
    // 1:创建Jedis连接池
    JedisPool pool = new JedisPool("localhost", 6379);
    // 2:从连接池中获取Jedis对象
    Jedis jedis = pool.getResource();
    /* 设置密码
    jedis.auth(密码); */

    // 3:TODO
    //前后都是固定的写法:这里的jedies对象的方法与Redis的命令一样

    // 4:关闭资源
    jedis.close();
    pool.destroy();
    }


    SpringBoot集成Redis:

    导入依赖:

           <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>


      配置:

      注入StringRedisTemplate
      ,因为开发中约定:key、value都是String;(类比:MongoTemplate)

         @Autowired
        private StringRedisTemplate redisTemplate;

        @Test
        public void testRedisTemplate() {
        // 操作string
        redisTemplate.opsForValue().xx();
        // 操作hash
        redisTemplate.opsForHash().xx();
        // 操作list
        redisTemplate.opsForList().xx();
        // 操作set
        redisTemplate.opsForSet().xx();
        // 操作zset
        redisTemplate.opsForZSet().xx();
        }


        总结:

        • redisTemplate.opsForValue();//操作字符串

        • redisTemplate.opsForHash();//操作hash

        • redisTemplate.opsForList();//操作list

        • redisTemplate.opsForSet();//操作set

        • redisTemplate.opsForZSet();//操作有序set




        java学途

        只分享有用的Java技术资料 

        扫描二维码关注公众号

         


        笔记|学习资料|面试笔试题|经验分享 

        如有任何需求或问题欢迎骚扰。微信号:JL2020aini

        或扫描下方二维码添加小编微信

         




        小伙砸,欢迎再看分享给其他小伙伴!共同进步!




        文章转载自java学途,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论