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

写完这篇Redis,感觉自己起飞了(一)

程序员星宇 2020-08-15
974


前言


    上节我们通过springboot和redis实现了一个幂等的解决方案,因为涉及到Redis,所以很自然的想到找一下redis的文章读读,也算是一种复习吧,看了好多资料,其实感觉都不是很完美,无意间看到3y哥分享了一篇,觉得不错。

    其实吧本来打算把那篇直接引用过来发表,因为那篇文章我觉得基本上涵盖了所有的知识点但是奈何版权问题审核没法通过,最后还是决定自己手边学习那篇文章,便修改一点东西,添加一些自己的理解,撸几篇文章,毕竟周末时间挺多的。(想偷懒果然还是不行啊)。

    文章打算分三期完成,第一期介绍redis的基础知识,第二期介绍一些redis常见的面试题包括,实际开发中如何解决缓存雪崩等redis常见的问题。第三期就讲讲redis集群的相关。实际上后面两个部分我接触的也不多,也就当一个学习了。


 

 Redis

安装

推荐官方站点:redis.io下载最新版

无脑下一步即可

使用

不用configuration,直接进入目录make就行了

当然如果你想安装到指定的目录记得

make PREFIX=/usr/local/redis install

基本文件介绍

可执行文件作用说明
redis-server服务端
redis-cli客户端
redis-benchmark性能测试工具
redis-check-aofAOF日志文件检测工具(比如断电造成日志损坏,可以检测并修复)
redis-check-dumpRDB持久化文件检测和修复工具
redis-sentinel启动哨兵
redis-tribcluster集群构建工具

基础命令

命令说明
keys  *redis允许模糊查询key  有3个通配符  *、?、[]
del      key删除key
exists kxm判断是否存在
expire key 20设置过期时间 - 秒
pexpire key 20000设置过期时间 - 毫秒
move kxm 2移动key到指定位置库中  2号库
persist key移除过期时间,key将会永久存在   成功设置返回1  否则返回0
pttl key以毫秒为单位返回 key 的剩余的过期时间
ttl key以秒为单位,返回给定 key 的剩余生存时间
randomkey从当前数据库中随机返回一个 key
rename key newkxy更改key的名字,如果重复了会覆盖
renamenx kxm key仅当 newkey 不存在时,将 key 改名为 newkey
type key返回 key 所储存的值的类型
select 0选择第一个库
ping返回PONG 表示连接正常
quit关闭当前连接

字符串命令

命令说明
set key aaa设置指定 key 的值
get key获取指定 key 的值
getrange key 0 1返回 key 中字符串值的子字符  包含 0 和 1 包含关系
getset key aaaaaaaa将给定 key 的值设为 value ,并返回 key 的旧值(old value)
mget key kxm获取所有(一个或多个)给定 key 的值
setex test 5 "this is my test"将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)
setnx test test只有在 key 不存在时设置 key 的值 (用于分布式锁)
strlen test返回 key 所储存的字符串值的长度
mset key1 "1" key2 "2"同时设置一个或多个 key-value 对
msetnx key3 "a" key2 "b"同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在   其中一个失败则全部失败
incr key将 key 中储存的数字值增一 ->  key的值 比如为 数字类型字符串  返回增加后的结果
incrby num 1000将 key 中储存的数字值增指定的值 ->  key的值 比如为 数字类型字符串  返回增加后的结果
decr key同 -> 减一
decrby num 500同 -> 减指定值
append key 1123123如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾  返回字符串长度

哈希(Hash)命令

命令说明
hdel key field1 [field2]删除一个或多个哈希表字段
hexistskey field查看哈希表 key 中,指定的字段是否存在
hget key field获取存储在哈希表中指定字段的值
hgetall key获取在哈希表中指定 key 的所有字段和值
hincrby hash yeary 1为哈希表 key 中的指定字段的整数值加上增量 increment
hkeys hash获取所有哈希表中的字段
hlen hash获取哈希表中字段的数量
hmget hash name year获取所有给定字段的值
hmset hash name "i am kxm" year 24同时将多个 field-value (域-值)对设置到哈希表 key 中
hset hash name kxm将哈希表 key 中的字段 field 的值设为 value
hsetnx key field value只有在字段 field 不存在时,设置哈希表字段的值
hvals hash获取哈希表中所有值
hexists hash name是否存在

编码:  field value 值由 ziplist 及 hashtable 两种编码格式

字段较少的时候采用ziplist,字段较多的时候会变成hashtable编码

列表(List)命令

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)

容量 -> 集合,有序集合也是如此

命令说明
lpush list php将一个值插入到列表头部  返回列表长度
lindex list 0通过索引获取列表中的元素
blpop  key1 [key2 ] timeout移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
brpop  key1 [key2 ] timeout移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
linsert list before 3 4在值 3 前插入 4   前即为顶
linsert list after 4 5在值4 后插入5
llen list获取列表长度
lpop list移出并获取列表的第一个元素
lpush list c++ c将一个或多个值插入到列表头部
lrange list 0 1获取列表指定范围内的元素  包含0和1   -1 代表所有 (lrange list 0 -1)
lrem list 1 c移除list 集合中 值为 c 的  一个元素,  1 代表count 即移除几个
lset list 0 "this is update"通过索引设置列表元素的值
ltrim list 1 5对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除
rpop list移除列表的最后一个元素,返回值为移除的元素
rpush list newvalue3从底部添加新值
rpoplpush list list2转移列表的数据

集合(Set)命令

    Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

命令说明
sadd set java php c c++ python向集合添加一个或多个成员
scard set获取集合的成员数
sdiff key1 [key2]返回给定所有集合的差集  数学含义差集
sdiffstore curr set newset  (sdiffstore destination key1 [key2])把set和 newset的差值存储到curr中
sinter set newset返回给定所有集合的交集
sinterstore curr set newset  (sinterstoredestination key1 [key2])
sismember set c#判断 member 元素是否是集合 key 的成员
smembers set返回集合中的所有成员
srandmember set 2随机抽取两个key (抽奖实现美滋滋)
smove set newtest java (smove source destination member)将 member 元素从 source 集合移动到 destination 集合
sunion set newset返回所有给定集合的并集
srem set java删除
spop set从集合中弹出一个元素
sdiff | sinter | sunion操作:集合间运算:差集

有序集合(sorted set)命令

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

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

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

命令说明
zadd sort 1 java 2 python向有序集合添加一个或多个成员,或者更新已存在成员的分数
zcard sort获取有序集合的成员数
zcount sort 0 1计算在有序集合中指定区间分数的成员数
zincrby sort 500 java有序集合中对指定成员的分数加上增量 increment
zscore sort java返回有序集中,成员的分数值
zrange sort 0 -1获取指定序号的值,-1代表全部
zrangebyscore sort 0 5分数符合范围的值
zrangebyscore sort 0 5 limit 0 1分页 limit  0代表页码,1代表每页显示数量
zrem sort java移除元素
zremrangebyrank sort 0 1按照排名范围删除元素
zremrangebyscore sort 0 1按照分数范围删除元素
zrevrank sort c#返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序

发布订阅

开启两个客户端

A客户端订阅频道:subscribe redisChat                                      (频道名字为 redisChat)

B客户端发布内容:publish redisChat "Hello, this is my wor"   (内容是 hello....)

A客户端即为自动收到内容, 原理图如下:

命令说明
pubsub channels查看当前redis  有多少个频道
pubsub numsub chat1查看某个频道的订阅者数量
unsubscrible chat1退订指定频道
psubscribe java.*订阅一组频道

Redis 事务

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务
  • 命令入队
  • 执行事务

注意:redis事务和数据库事务不同,redis事务出错后最大的特点是,一剩下的命令会继续执行,二出错的数据不会回滚

命令说明
multi标记一个事务开始
exec执行事务
discard事务开始后输入命令入队过程中,中止事务
watch key监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
unwatch取消 WATCH 命令对所有 key 的监视

Redis 服务器命令

命令说明
flushall删除所有数据库的所有key
flushdb删除当前数据库的所有key
save同步保存数据到硬盘

Redis 数据备份与恢复

Redis SAVE 命令用于创建当前数据库的备份

如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。获取 redis 目录可以使用 CONFIG 命令

Redis 性能测试

redis 性能测试的基本命令如下:

redis目录执行:redis-benchmark [option] [option value]

// 会返回各种操作的性能报告(100连接,10000请求)
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000

// 100个字节作为value值进行压测
redis-benchmark -h 127.0.0.1 -p 6379 -q -d 100


Java Redis

Jedis

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


    Jedis配置

      #redis配置开始
      # Redis数据库索引(默认为0)
      spring.redis.database=0
      # Redis服务器地址
      spring.redis.host=redis host
      # Redis服务器连接端口
      spring.redis.port=6379
      # Redis服务器连接密码(默认为空)
      spring.redis.password=
      # 连接池最大连接数(使用负值表示没有限制)
      spring.redis.jedis.pool.max-active=1024
      # 连接池最大阻塞等待时间(使用负值表示没有限制)
      spring.redis.jedis.pool.max-wait=10000
      # 连接池中的最大空闲连接
      spring.redis.jedis.pool.max-idle=200
      # 连接池中的最小空闲连接
      spring.redis.jedis.pool.min-idle=0
      # 连接超时时间(毫秒)
      spring.redis.timeout=10000
      #redis配置结束
      spring.redis.block-when-exhausted=true

      JedisConfig

        package xyRedis.config;


        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.cache.annotation.CachingConfigurerSupport;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import redis.clients.jedis.JedisPool;
        import redis.clients.jedis.JedisPoolConfig;


        /**
        * @Package: xyRedis.config
        * @ClassName: JedisConfig
        * @Author: mengxingyu
        * @Description: Jedis配置类
        * @Date: 2020/8/15 14:15
        */
        @Configuration
        public class JedisConfig extends CachingConfigurerSupport{
        private Logger logger = LoggerFactory.getLogger(JedisConfig.class);


        **
        * SpringSession 需要注意的就是redis需要2.8以上版本,然后开启事件通知,在redis配置文件里面加上
        * notify-keyspace-events Ex
        * Keyspace notifications功能默认是关闭的(默认地,Keyspace 时间通知功能是禁用的,因为它或多或少会使用一些CPU的资源)。
        * 或是使用如下命令:
        * redis-cli config set notify-keyspace-events Egx
        * 如果你的Redis不是你自己维护的,比如你是使用阿里云的Redis数据库,你不能够更改它的配置,那么可以使用如下方法:在applicationContext.xml中配置
        * <util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
        * @return
                 */
        @Value("${spring.redis.host}")
                private String host;
        @Value("${spring.redis.port}")
                private int port;
        @Value("${spring.redis.timeout}")
                private int timeout;
        @Value("${spring.redis.jedis.pool.max-active}")
                private int maxActive;
        @Value("${spring.redis.jedis.pool.max-idle}")
                private int maxIdle;
        @Value("${spring.redis.jedis.pool.min-idle}")
                private int minIdle;
        @Value("${spring.redis.jedis.pool.max-wait}")
                private long maxWaitMillis;
        @Bean
        public JedisPool redisPoolFactory(){
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMinIdle(minIdle);
        JedisPool jedisPool = new JedisPool(jedisPoolConfig,host,port,timeout,null);
        logger.info("JedisPool注入成功!");
        logger.info("redis地址:" + host + ":" + port);
        return jedisPool;
                }
            }

        简单测试

          @RunWith(SpringRunner.class)
          @SpringBootTest
          public class RedisTest {
          @Resource
              JedisPool jedisPool;
          @Test
          public void JedisConfigTest () {
          Jedis jedis = jedisPool.getResource();
                  jedis.set("mxy", "星星星星宇");
              }
          }

          SpringBoot redis staeter RedisTemplate

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

            配置文件参考Jedis的配置文


            RedisConfig

              public class RedisConfig {
              @Bean
              @SuppressWarnings("all")
                  public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
              RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
                      template.setConnectionFactory(factory);
                      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);
                      StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
              key采用String的序列化方式
                      template.setKeySerializer(stringRedisSerializer);
              hash的key也采用String的序列化方式
                      template.setHashKeySerializer(stringRedisSerializer);
              value序列化方式采用jackson
                      template.setValueSerializer(jackson2JsonRedisSerializer);
              hash的value序列化方式采用jackson
              template.setHashValueSerializer(jackson2JsonRedisSerializer);
              template.afterPropertiesSet();
              return template;
              }
              }

              简单测试

                @RunWith(SpringRunner.class)
                @SpringBootTest
                public class RedisTest {
                @Resource
                    RedisTemplate<String,Object> redisTemplate;
                @Test
                public void redisTemplateTest () {
                        redisTemplate.opsForList().rightPush("mxy:first:order", "星星星星宇");
                }
                }

                RedisTemplate相关操作


                    定义了5种数据结构的操作分别是

                1. RedisTemplate.opsForValue()         操作字符串

                2. RedisTemplate.opsForHash()          操作字Hash

                3. RedisTemplate.opsForList()             操作List

                4. RedisTemplate.opsForSet()             操作Set

                5. RedisTemplate.opsForZSet()           操作有序Set

                Jedis和SpringDataRedis区别

                    看了这两个配置文件就在想这个问题,这两个东西作用基本一样又有什么区别呢。

                    其实Jedis是redis官方推荐的面向java操作的Redis客户RedisTemplate是SpringDataRedis中对JedisApi的高度封装。

                    SpringDateRedis的特点就是方便更换Redis的java客户端,比Jedis多了自动管理连接池的特性,方便和其他spring框架配合使用。比如springCache。当然效率方面的话Jedis是优于SpringDataRedis的。什么是序列化?

                RedisTemplate配置序列化

                    

                    再使用SpringDataRedis时,我注意到了好多人在使用SpringDataRedis时都会修改默认的序列化方式,对此我表示很懵逼,虽然知道序列化的作用,但是不清楚为什么要修改系列化方式,于是就开始了刨根问底的探索。

                什么是序列化?

                通俗来讲序列号就是处理对象流的一种机制,也就是很方便的保存java中对象的状态,同时也方便传输。(项目中最常见的就是实体类往往会实现接口Serializable)

                序列化的作用?

                首先    方便传输,速度快,而且很安全,被调用方序列化,调用方反序列化就可以拿到传输前最原始的java对象。

                其次    方便存储,存储成文件或者数据库,下次要有直接反序列化就可以

                场景分析

                    为了更好的理解序列化的重要性,我找了一个项目因为开发人员没有修改RedisTemplate的默认序列化而导致的bug来学习。

                在分布式微服务开发中,不同的项目、微服务共用了一个Redis,但是新项目部署上线后发现读取不到老项目存储的缓存。新项目搭建的时候没有和老乡们使用同一个core包里的redisUtils工具类,老项目使用的时Jedis,新项目换成了springboot封装的redistemplate。

                      @Test
                  public void redisTemplateTest1 () {
                  final String key="mmmmxy";
                  ValueOperations<Serializable,Object> valueOperations = redisTemplate.opsForValue();
                  valueOperations.set(key,"mxytest");


                  }

                      查看源码后会发现

                      出现这些多余的东西原因就是在不设置序列化方式的时候会默认使用JdkSerializationRedisSerializer,所以会出现多余的东西。解决这个的办法就是设置序列化方式。

                       幸运的是找到了一张关于不同序列化方式会产生不同结果的表。能更直观的反映差距。

                  我们这里针对StringRedisSerializer,Jackson2JsonRedisSerializer和JdkSerializationRedisSerializer进行测试。

                  从上面的结果不难看出

                  1,用StringRedisSerializer进行序列化的值,在Java和Redis中保存的内容是一样的

                  2,用Jackson2JsonRedisSerializer进行序列化的值,在Redis中保存的内容,比Java中多了一对双引号。

                  3,用JdkSerializationRedisSerializer进行序列化的值,对于Key-Value的Value来说,是在Redis中是不可读的。对于Hash的Value来说,比Java的内容多了一些字符。(如果Key的Serializer也用和Value相同的Serializer的话,在Redis中保存的内容和上面Value的差异是一样的,所以我们保存时,只用StringRedisSerializer进行序列化)

                  什么是SpringDataRedis配置缓存

                  你知道的越多,你不知道的越多!

                      

                      在学习完前面的东西,在写SpringDataRedis的配置文件时我又发现了一个新的名词SpringCache,关于这个东西我居然好不清楚,所以本着刨根问底的心态,我便又开始了对springCahe的探索。

                      

                  什么是缓存?

                  这个概念其实很简单,在实际开发中缓存就是加速服务响应速度的一种非常有效而且简单的东西。其中最出名的框架就是EhCache、Guava、HazelCast等等。当然Redis作为一种key-value型数据库,由于他的特性也是一种很流行的数据缓存工具。

                  传统缓存的使用方式?  

                  1. 函数执行前检查缓存中是否存在数据,如果存在直接返回数据。

                  2. 如果不存在就去数据库查询数据。

                  3. 最后把数据放入缓存,下次调用函数就可以直接使用缓存


                      单纯从这个流程我们就可以发现如果每个查询都需要我们自己去实现这三步,很明显违背了开发的基本原则,这是一种极其不优雅的解决办法。

                      幸运的是SpringCache为我们解决了这个问题。只需要几个配置就解决了。

                  @Cacheable  配置在方法或类上,作用:本方法执行后,先去缓存看有没有数据,如果没有,从数据库中查找出来,给缓存中存一份,返回结果,下次本方法执行,在缓存未过期情况下,先在缓存中查找,有的话直接返回,没有的话从数据库查找

                  @CachePut   类似于更新操作,即每次不管缓存中有没有结果,都从数据库查找结果,并将结果更新到缓存,并返回结果
                  @CacheEvict 用来清除用在本方法或者类上的缓存数据(用在哪里清除哪里)

                  下面贴上Redis缓存注解配置类



                    /**
                    * @Package: xyRedis.config
                    * @ClassName: RedisCacheConfig
                    * @Author: mengxingyu
                    * @Description:
                    * @Date: 2020/8/15 14:36
                    */
                    @Configuration
                    public class RedisCacheConfig {
                    @Bean
                    public KeyGenerator simpleKeyGenerator() {
                    return (o, method, objects) -> {
                    StringBuilder stringBuilder = new StringBuilder();
                    stringBuilder.append(o.getClass().getSimpleName());
                    stringBuilder.append(".");
                    stringBuilder.append(method.getName());
                    stringBuilder.append("[");
                    for (Object obj : objects) {
                    stringBuilder.append(obj.toString());
                    }
                    stringBuilder.append("]");
                    return stringBuilder.toString();
                    };
                    }


                    @Bean
                    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
                    return new RedisCacheManager(
                    RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                    // 默认策略,未配置的 key 会使用这个
                    this.getRedisCacheConfigurationWithTtl(15),


                    // 指定 key 策略
                    this.getRedisCacheConfigurationMap()
                    );
                    }


                    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
                    Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(16);
                    redisCacheConfigurationMap.put("redisTest", this.getRedisCacheConfigurationWithTtl(15));
                    return redisCacheConfigurationMap;
                    }


                    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
                    Jackson2JsonRedisSerializer<Object> 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);


                    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
                    redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                    RedisSerializationContext
                    .SerializationPair
                    .fromSerializer(jackson2JsonRedisSerializer)
                    ).entryTtl(Duration.ofSeconds(seconds));
                    return redisCacheConfiguration;
                    }
                    }





                    Redis使用场景

                    类型适用场景
                    String缓存,限流,计数器,分布式锁,分布式session
                    Hash存储用户信息,用户主页访问量,组合查询
                    List微博关注人时间轴列表,简单队列
                    Set赞,踩,标签,好友关系
                    Zset排行榜

                    或者简单消息队列,发布订阅实施消息系统等等

                    String - 缓存

                      // 1.Cacheable 注解
                      // controller 调用 service 时自动判断有没有缓存,如果有就走redis缓存直接返回,如果没有则数据库然后自动放入redis中
                      // 可以设置过期时间,KEY生成规则 (KEY生成规则基于 参数的toString方法)
                      @Cacheable(value = "yearScore", key = "#yearScore")
                      @Override
                      public List<YearScore> findBy (YearScore yearScore) {}


                      // 2.手动用缓存
                      if (redis.hasKey(???) {
                      return ....
                      }


                      redis.set(find from DB)...

                      String - 限流 | 计数器

                        // 注:这只是一个最简单的Demo 效率低,耗时旧,但核心就是这个意思
                        // 计数器也是利用单线程incr...等等
                        @RequestMapping("/redisLimit")
                        public String testRedisLimit(String uuid) {
                        if (jedis.get(uuid) != null) {
                        Long incr = jedis.incr(uuid);
                        if (incr > MAX_LIMITTIME) {
                        return "Failure Request";
                        } else {
                        return "Success Request";
                        }
                        }


                        // 设置Key 起始请求为1,10秒过期 -> 实际写法肯定封装过,这里就是随便一写
                        jedis.set(uuid, "1");
                        jedis.expire(uuid, 10);
                        return "Success Request";
                        }

                        String - 分布式锁 (重点)

                          /***
                          * 核心思路:
                          * 分布式服务调用时setnx,返回1证明拿到,用完了删除,返回0就证明被锁,等...
                          * SET KEY value [EX seconds] [PX milliseconds] [NX|XX]
                          * EX second:设置键的过期时间为second秒
                          * PX millisecond:设置键的过期时间为millisecond毫秒
                          * NX:只在键不存在时,才对键进行设置操作
                          * XX:只在键已经存在时,才对键进行设置操作
                          *
                          * 1.设置锁
                          * A. 分布式业务统一Key
                          * B. 设置Key过期时间
                          * C. 设置随机value,利用ThreadLocal 线程私有存储随机value
                          *
                          * 2.业务处理
                          * ...
                          *
                          * 3.解锁
                          * A. 无论如何必须解锁 - finally (超时时间和finally 双保证)
                          * B. 要对比是否是本线程上的锁,所以要对比线程私有value和存储的value是否一致(避免把别人加锁的东西删除了)
                          */
                          @RequestMapping("/redisLock")
                          public String testRedisLock () {
                          try {
                          for(;;){
                          RedisContextHolder.clear();
                          String uuid = UUID.randomUUID().toString();


                          String set = jedis.set(KEY, uuid, "NX", "EX", 1000);
                          RedisContextHolder.setValue(uuid);


                          if (!"OK".equals(set)) {
                          // 进入循环-可以短时间休眠
                          } else {
                          // 获取锁成功 Do Somethings....
                          break;
                          }
                          }
                          } finally {
                          // 解锁 -> 保证获取数据,判断一致以及删除数据三个操作是原子的, 因此如下写法是不符合的
                          /*if (RedisContextHolder.getValue() != null && jedis.get(KEY) != null && RedisContextHolder.getValue().equals(jedis.get(KEY))) {
                          jedis.del(KEY);
                          }*/


                          // 正确姿势 -> 使用Lua脚本,保证原子性
                          String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                          Object eval = jedis.eval(luaScript, Collections.singletonList(KEY), Collections.singletonList(RedisContextHolder.getValue()));
                          }
                          return "锁创建成功-业务处理成功";
                          }

                          String - 分布式Session(重点)

                            // 1.首先明白为什么需要分布式session -> nginx负载均衡 分发到不同的Tomcat,即使利用IP分发,可以利用request获取session,但是其中一个挂了,怎么办?? 所以需要分布式session


                            注意理解其中的区别 A服务-用户校验服务 B服务-业务层


                            情况A:
                            A,B 服务单机部署:
                            cookie:登录成功后,存储信息到cookie,A服务自身通过request设置session,获取session,B服务通过唯一key或者userid 查询数据库获取用户信息


                            cookie+redis:登录成功后,存储信息到cookie,A服务自身通过request设置session,获取session,B服务通过唯一key或者userid 查询redis获取用户信息




                            情况B:
                            A服务多节点部署,B服务多节点部署
                            B服务获取用户信息的方式其实是不重要的,必然要查,要么从数据库,要么从cookie


                            A服务:登录成功后,存储唯一key到cookie, 与此同时,A服务需要把session(KEY-UserInfo)同步到redis中,不能存在单纯的request(否则nginx分发到另一个服务器就完犊子了)


                            官方实现:
                            spring-session-data-redis
                            有一个内置拦截器,拦截request,session通过redis交互,普通使用代码依然是request.getSession.... 但是实际上这个session的值已经被该组件拦截,通过redis进行同步了

                            List 简单队列-栈

                              / /说白了利用redis - list数据结构 支持从左从右push,从左从右pop
                              @Component
                              public class RedisStack {


                              @Resource
                              Jedis jedis;


                              private final static String KEY = "Stack";


                              /** push **/
                              public void push (String value) {
                              jedis.lpush(KEY, value);
                              }


                              /** pop **/
                              public String pop () {
                              return jedis.lpop(KEY);
                              }
                              }
                                @Component
                                public class RedisQueue {


                                @Resource
                                JedisPool jedisPool;


                                private final static String KEY = "Queue";


                                /** push **/
                                public void push (String value) {
                                Jedis jedis = jedisPool.getResource();
                                jedis.lpush(KEY, value);
                                }


                                /** pop **/
                                public String pop () {
                                Jedis jedis = jedisPool.getResource();
                                return jedis.rpop(KEY);
                                }
                                }


                                List 社交类APP - 好友列表

                                  根据时间显示好友,多个好友列表,求交集,并集  显示共同好友等等...
                                  疑问:难道大厂真的用redis存这些数据吗???多大的量啊... 我个人认为实际是数据库存用户id,然后用算法去处理,更省空间
                                  Set 抽奖 | 好友关系(合,并,交集)
                                    // 插入key 及用户id
                                    sadd cat:1 001 002 003 004 005 006


                                    // 返回抽奖参与人数
                                    scard cat:1


                                    // 随机抽取一个
                                    srandmember cat:1


                                    // 随机抽取一人,并移除
                                    spop cat:1

                                    Zset 排行榜

                                      根据分数实现有序列表
                                      微博热搜:每点击一次 分数+1 即可


                                      --- 不用数据库目的是因为避免order by 进行全表扫描

                                      结束语

                                          这篇主要对Redis的基础进行了复习和总结,后续还会展开对Redis的学习,包括仔细探讨Redis分布式锁在高并发场景的问题,学习一些Redis常见的开发问题比如缓存穿透、缓存雪崩等等,当然也不会忘了学习一下Redis的部署相关问题,像什么哨兵啊、什么主从部署啊等等。

                                          当然如果你对源代码感兴趣欢迎去我的Github下载,部分代码还未更新完,三期写完基本上就会全了。

                                      https://github.com/mengxingyu/xingyuCoder02.git

                                              如果文章你发现了文章问题记得联系我,当然如果你有更好的文章也可以发给我,互相学习。

                                              最后一句,看完记得素质三连哦,至少点个赞吧哈哈。






                                      我是星宇,一个满头黑发,渴望秃头的开发,我们下期见!


                                          

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

                                      评论