Redis-技术专题-Redis知识体系

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1.基本文件说明"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c8/c8df417d1216165831d65a00031e2cd7.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"2.基础命令"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/44/44e02da4e1f6074ee9e842548728d63b.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"3.字符串命令"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e8/e862e54c858061df98afe66101a90f4b.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"4.哈希(Hash)命令"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a8/a86c8f4b7abe2c84ddb5e7d7746992f0.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"编码: field value 值由 ziplist 及 hashtable 两种编码格式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"字段较少的时候采用"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"ziplist"},{"type":"text","text":",字段较多的时候会变成"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"hashtable"},{"type":"text","text":"编码"}]}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"5.列表(List)命令"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 一个列表最多可以包含 "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"232 - 1 "},{"type":"text","text":"个元素 ("},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"4294967295"},{"type":"text","text":", 每个列表超过"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"40"},{"type":"text","text":"亿个元素)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"容量 -> 集合,有序集合也是如此。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6e/6e7776adb9af2e046cff64e4cd5d4052.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"6.集合(Set)命令"}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":" Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/23/23cea99d44b943c05fa77fe4df5411cd.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"7.有序集合(sorted set)命令"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 不同的是每个元素都会关联一个"},{"type":"text","marks":[{"type":"strong"}],"text":"double"},{"type":"text","text":"类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。"}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"有序集合的成员是唯一的,但分数("},{"type":"text","text":"score"},{"type":"text","marks":[{"type":"strong"}],"text":")却可以重复。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/94/944d58a6a9019a80b271a6c60327367d.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"8.发布订阅"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"开启两个客户端"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"A客户端订阅频道:subscribe redisChat (频道名字为 redisChat)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"B客户端发布内容:publish redisChat \"Hello, this is my wor\" (内容是 hello....)"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"A客户端即为自动收到内容, 原理图如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/dd/dddfbfb67c61d912da8ad918a837771b.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/0062974a89fc02b192bd3ac5fb2247f5.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/57/572ad5a6ea461175bb3c807a73e07902.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"9.Redis 事务"}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"批量操作在发送 EXEC 命令前被放入队列缓存"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中"}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 一个事务从开始到执行会经历以下三个阶段:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"开始事务"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"命令入队"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"执行事务"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"注意:redis事务和数据库事务不同,redis事务出错后最大的特点是,一剩下的命令会继续执行,二出错的数据不会回滚"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/af/af63ec8d056d9cdcadb3562016a9ab22.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"10.Redis 服务器命令"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/16/168c17614429783926a971f902e79b12.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"11.Redis 数据备份与恢复"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":" Redis "},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"SAVE"},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}}],"text":" 命令用于创建当前数据库的备份"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 如果需要恢复数据,只需将备份文件 ("},{"type":"text","marks":[{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"dump.rdb"},{"type":"text","text":") 移动到 redis 安装目录并启动服务即可。获取 redis 目录可以使用 "},{"type":"text","marks":[{"type":"strong"}],"text":"CONFIG"},{"type":"text","text":" 命令"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"12.Redis 性能测试"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"redis 性能测试的基本命令如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"redis目录执行:redis-benchmark [option] [option value]\n// 会返回各种操作的性能报告(100连接,10000请求)\nredis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000\n// 100个字节作为value值进行压测\nredis-benchmark -h 127.0.0.1 -p 6379 -q -d 100"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Jedis"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"\n\n redis.clients\n jedis\n 2.8.2\n"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Jedis配置"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"############# redis Config #############\n# Redis数据库索引(默认为0)\nspring.redis.database=0\n# Redis服务器地址\nspring.redis.host=120.79.88.17\n# Redis服务器连接端口\nspring.redis.port=6379\n# Redis服务器连接密码(默认为空)\nspring.redis.password=123456\n# 连接池中的最大空闲连接\nspring.redis.jedis.pool.max-idle=8\n# 连接池中的最小空闲连接\nspring.redis.jedis.pool.min-idle=0"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"JedisConfig"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"@Configuration\npublic class JedisConfig extends CachingConfigurerSupport {\n\n @Value(\"${spring.redis.host}\")\n private String host;\n\n @Value(\"${spring.redis.port}\")\n private int port;\n\n @Value(\"${spring.redis.password}\")\n private String password;\n\n @Value(\"${spring.redis.max-idle}\")\n private Integer maxIdle;\n\n @Value(\"${spring.redis.min-idle}\")\n private Integer minIdle;\n\n @Bean\n public JedisPool redisPoolFactory(){\n JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();\n jedisPoolConfig.setMaxIdle(maxIdle);\n jedisPoolConfig.setMinIdle(minIdle);\n jedisPoolConfig.setMaxWaitMillis(3000L);\n int timeOut = 3;\n return new JedisPool(jedisPoolConfig, host, port, timeOut, password);\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"基础使用"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"@RunWith(SpringRunner.class)\n@SpringBootTest(classes = KerwinBootsApplication.class)\npublic class ApplicationTests {\n @Resource\n JedisPool jedisPool;\n @Test\n public void testJedis () {\n Jedis jedis = jedisPool.getResource();\n jedis.set(\"year\", String.valueOf(24));\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"SpringBoot RedisTemplate"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"\n\n org.springframework.boot\n spring-boot-starter-data-redis\n\n\n\n org.apache.commons\n commons-pool2\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"############# redis Config #############\n# Redis数据库索引(默认为0)\nspring.redis.database=0\n# Redis服务器地址\nspring.redis.host=120.79.88.17\n# Redis服务器连接端口\nspring.redis.port=6379\n# Redis服务器连接密码(默认为空)\nspring.redis.password=123456\n# 连接池最大连接数(使用负值表示没有限制)\nspring.redis.jedis.pool.max-active=200\n# 连接池最大阻塞等待时间(使用负值表示没有限制)\nspring.redis.jedis.pool.max-wait=1000ms\n# 连接池中的最大空闲连接\nspring.redis.jedis.pool.max-idle=8\n# 连接池中的最小空闲连接\nspring.redis.jedis.pool.min-idle=0\n# 连接超时时间(毫秒)\nspring.redis.timeout=1000ms"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// Cache注解配置类\n@Configuration\npublic class RedisCacheConfig {\n\n @Bean\n public KeyGenerator simpleKeyGenerator() {\n return (o, method, objects) -> {\n StringBuilder stringBuilder = new StringBuilder();\n stringBuilder.append(o.getClass().getSimpleName());\n stringBuilder.append(\".\");\n stringBuilder.append(method.getName());\n stringBuilder.append(\"[\");\n for (Object obj : objects) {\n stringBuilder.append(obj.toString());\n }\n stringBuilder.append(\"]\");\n return stringBuilder.toString();\n };\n }\n \n @Bean\n public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {\n return new RedisCacheManager(\n RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),\n // 默认策略,未配置的 key 会使用这个\n this.getRedisCacheConfigurationWithTtl(15),\n // 指定 key 策略\n this.getRedisCacheConfigurationMap()\n );\n }\n \n private Map getRedisCacheConfigurationMap() {\n Map redisCacheConfigurationMap = new HashMap<>(16);\n redisCacheConfigurationMap.put(\"redisTest\", this.getRedisCacheConfigurationWithTtl(15));\n return redisCacheConfigurationMap;\n }\n \n private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {\n Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);\n ObjectMapper om = new ObjectMapper();\n om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);\n om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);\n jackson2JsonRedisSerializer.setObjectMapper(om);\n\n RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();\n redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(\n RedisSerializationContext\n .SerializationPair\n .fromSerializer(jackson2JsonRedisSerializer)\n ).entryTtl(Duration.ofSeconds(seconds));\n return redisCacheConfiguration;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// RedisAutoConfiguration\n@Configuration\n@EnableCaching\npublic class RedisConfig {\n\n @Bean\n @SuppressWarnings(\"all\")\n public RedisTemplate redisTemplate(RedisConnectionFactory factory) {\n\n RedisTemplate template = new RedisTemplate();\n template.setConnectionFactory(factory);\n\n Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);\n\n ObjectMapper om = new ObjectMapper();\n om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);\n om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);\n jackson2JsonRedisSerializer.setObjectMapper(om);\n\n StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();\n\n // key采用String的序列化方式\n template.setKeySerializer(stringRedisSerializer);\n\n // hash的key也采用String的序列化方式\n template.setHashKeySerializer(stringRedisSerializer);\n\n // value序列化方式采用jackson\n template.setValueSerializer(jackson2JsonRedisSerializer);\n\n // hash的value序列化方式采用jackson\n template.setHashValueSerializer(jackson2JsonRedisSerializer);\n template.afterPropertiesSet();\n return template;\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// 基础使用\n@Resource\nRedisTemplate redisTemplate;\n\nredisTemplate.opsForList().rightPush(\"user:1:order\", dataList.get(3).get(\"key\").toString());\n\n// 注解使用\n@Cacheable(value = \"redisTest\")\npublic TestBean testBeanAnnotation () {}\n"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"13.Redis使用场景"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1d/1db1fe511fbb8e26fc545fd0d2877933.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"或者简单消息队列,发布订阅实施消息系统等等"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"String - 缓存"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// 1.Cacheable 注解\n// controller 调用 service 时自动判断有没有缓存,如果有就走redis缓存直接返回,如果没有则数据库然后自动放入redis中\n// 可以设置过期时间,KEY生成规则 (KEY生成规则基于 参数的toString方法)\n@Cacheable(value = \"yearScore\", key = \"#yearScore\")\n@Override\npublic List findBy (YearScore yearScore) {}\n// 2.手动用缓存\nif (redis.hasKey(???) {\n return ....\n} \nredis.set(find from DB)..."}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"String - 限流 | 计数器"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// 注:这只是一个最简单的Demo 效率低,耗时旧,但核心就是这个意思\n// 计数器也是利用单线程incr...等等\n@RequestMapping(\"/redisLimit\")\npublic String testRedisLimit(String uuid) {\n if (jedis.get(uuid) != null) {\n Long incr = jedis.incr(uuid);\n if (incr > MAX_LIMITTIME) {\n return \"Failure Request\";\n } else {\n return \"Success Request\";\n }\n }\n // 设置Key 起始请求为1,10秒过期 -> 实际写法肯定封装过,这里就是随便一写\n jedis.set(uuid, \"1\");\n jedis.expire(uuid, 10);\n return \"Success Request\";\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"String - 分布式锁 (重点)"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"/***\n * 核心思路:\n * 分布式服务调用时setnx,返回1证明拿到,用完了删除,返回0就证明被锁,等...\n * SET KEY value [EX seconds] [PX milliseconds] [NX|XX]\n * EX second:设置键的过期时间为second秒\n * PX millisecond:设置键的过期时间为millisecond毫秒\n * NX:只在键不存在时,才对键进行设置操作\n * XX:只在键已经存在时,才对键进行设置操作\n *\n * 1.设置锁\n * A. 分布式业务统一Key\n * B. 设置Key过期时间\n * C. 设置随机value,利用ThreadLocal 线程私有存储随机value\n *\n * 2.业务处理\n * ...\n *\n * 3.解锁\n * A. 无论如何必须解锁 - finally (超时时间和finally 双保证)\n * B. 要对比是否是本线程上的锁,所以要对比线程私有value和存储的value是否一致(避免把别人加锁的东西删除了)\n */\n@RequestMapping(\"/redisLock\")\npublic String testRedisLock () {\n try {\n for(;;){\n RedisContextHolder.clear();\n String uuid = UUID.randomUUID().toString();\n String set = jedis.set(KEY, uuid, \"NX\", \"EX\", 1000);\n RedisContextHolder.setValue(uuid);\n\n if (!\"OK\".equals(set)) {\n // 进入循环-可以短时间休眠\n } else {\n // 获取锁成功 Do Somethings....\n break;\n }\n }\n } finally {\n // 解锁 -> 保证获取数据,判断一致以及删除数据三个操作是原子的, 因此如下写法是不符合的\n /*if (RedisContextHolder.getValue() != null && jedis.get(KEY) != null && RedisContextHolder.getValue().equals(jedis.get(KEY))) {\n jedis.del(KEY);\n }*/\n\n // 正确姿势 -> 使用Lua脚本,保证原子性\n String luaScript = \"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end\";\n Object eval = jedis.eval(luaScript, Collections.singletonList(KEY), Collections.singletonList(RedisContextHolder.getValue()));\n }\n return \"锁创建成功-业务处理成功\";\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"String - 分布式Session(重点)"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// 1.首先明白为什么需要分布式session -> nginx负载均衡 分发到不同的Tomcat,即使利用IP分发,可以利用request获取session,\n\n但是其中一个挂了,怎么办?? 所以需要分布式session\n注意理解其中的区别 A服务-用户校验服务 B服务-业务层\n\n情况A:\n\nA,B 服务单机部署:\n\ncookie:登录成功后,存储信息到cookie,A服务自身通过request设置session,获取session,B服务通过唯一key或者userid 查询数据库获取用户信息\n\ncookie+redis:登录成功后,存储信息到cookie,A服务自身通过request设置session,获取session,B服务通过唯一key或者userid 查询redis获取用户信息\n\n情况B:\n\nA服务多节点部署,B服务多节点部署\nB服务获取用户信息的方式其实是不重要的,必然要查,要么从数据库,要么从cookie\nA服务:登录成功后,存储唯一key到cookie, 与此同时,A服务需要把session(KEY-UserInfo)同步到redis中,不能存在单纯的request\n(否则nginx分发到另一个服务器就完犊子了)\n\n官方实现:\nspring-session-data-redis\n有一个内置拦截器,拦截request,session通过redis交互,普通使用代码依然是request.getSession.... \n但是实际上这个session的值已经被该组件拦截,通过redis进行同步了\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"List 简单队列-栈"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// 说白了利用redis - list数据结构 支持从左从右push,从左从右pop\n@Component\npublic class RedisStack {\n\n @Resource\n Jedis jedis;\n\n private final static String KEY = \"Stack\";\n\n /** push **/\n public void push (String value) {\n jedis.lpush(KEY, value);\n }\n\n /** pop **/\n public String pop () {\n return jedis.lpop(KEY);\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"@Component\npublic class RedisQueue {\n\n @Resource\n JedisPool jedisPool;\n\n private final static String KEY = \"Queue\";\n\n /** push **/\n public void push (String value) {\n Jedis jedis = jedisPool.getResource();\n jedis.lpush(KEY, value);\n }\n\n /** pop **/\n public String pop () {\n Jedis jedis = jedisPool.getResource();\n return jedis.rpop(KEY);\n }\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"List 社交类APP - 好友列表"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"根据时间显示好友,多个好友列表,求交集,并集 显示共同好友等等...\n疑问:难道大厂真的用redis存这些数据吗???多大的量啊... 我个人认为实际是数据库存用户id,然后用算法去处理,更省空间\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Set 好友关系(合,并,交集)"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// 插入key 及用户id\nsadd cat:1 001 002 003 004 005 006\n\n// 返回抽奖参与人数\nscard cat:1\n\n// 随机抽取一个\nsrandmember cat:1\n\n// 随机抽取一人,并移除\nspop cat:1\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Zset 排行榜"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"根据分数实现有序列表\n微博热搜:每点击一次 分数+1 即可\n--- 不用数据库目的是因为避免order by 进行全表扫描"}]},{"type":"heading","attrs":{"align":null,"level":2}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章