五种数据结构

redis 深度历险 - 图8

分布式锁

redis 深度历险 - 图9


队列

redis 深度历险 - 图10

延迟队列

redis 深度历险 - 图11

位图

redis 深度历险 - 图12

bitfield

redis 深度历险 - 图13

HyperLogLog

redis 深度历险 - 图14

BloomFilter

redis 深度历险 - 图15

限流

简单限流

redis 深度历险 - 图16image.png

漏桶限流

redis 深度历险 - 图18

地理位置 Geo 模块

redis 深度历险 - 图19

scan

redis 深度历险 - 图20

rehash 操作变化

  • 如果 slot 的二进制值为 xxxx
  • 在 rehash 后,该 slot 的一半值还在 0xxxx, 而另一半就在 1xxxx (xxxx+扩容数)

image.png

扩容、缩容后 scan 遍历顺序变化

  • TODO

扩展

过期策略

redis 深度历险 - 图22

LRU

redis 深度历险 - 图23

异步操作

redis 深度历险 - 图24

jedis 优化使用

  • 硬编码要求使用自带回收连接的 jedis
  • 例子是 springboot + jedis 依赖(木有 redisTemplate)

    初始化

    读取配置文件值类

    1. /**
    2. * 单机版 jedis 实例
    3. */
    4. @Component
    5. public class JedisSinglePropertiesValue {
    6. /**
    7. * 单机服务器host
    8. */
    9. @Value("${spring.redis.host}")
    10. private String host;
    11. /**
    12. * 单机服务器端口
    13. */
    14. @Value("${spring.redis.port}")
    15. private int port;
    16. /**
    17. * 选用哪个数据库
    18. */
    19. @Value("${spring.redis.database}")
    20. private int database;
    21. /**
    22. * 连接超时时间,单位 ms
    23. */
    24. @Value("${spring.redis.timeout}")
    25. private int timeout;
    26. /**
    27. * 连接池最大连接数
    28. */
    29. @Value("${spring.redis.jedis.pool.max-active}")
    30. private int maxActive;
    31. /**
    32. * 连接池最大空闲连接数
    33. */
    34. @Value("${spring.redis.jedis.pool.max-idle}")
    35. private int maxIdle;
    36. /**
    37. * 连接池最小空闲连接数
    38. */
    39. @Value("${spring.redis.jedis.pool.min-idle}")
    40. private int minIdle;
    41. /**
    42. * 连接池最大堵塞等待时间
    43. */
    44. @Value("${spring.redis.jedis.pool.max-wait}")
    45. private int maxWait;
    46. // getter setter
    47. }

工具类赋值类

  1. /**
  2. * 为工具类注入属性的代理工具
  3. */
  4. @Component
  5. public class ReadValueUtil {
  6. public ReadValueUtil(JedisPool jedisPool) {
  7. MyJedisPool.setJedisPoolOnlyOnce(jedisPool);
  8. }
  9. }

配置类

  1. @Configuration
  2. public class JedisConfig {
  3. private static final Logger logger = LoggerFactory.getLogger(JedisConfig.class);
  4. /**
  5. * 属性相关
  6. */
  7. @Autowired
  8. private JedisSinglePropertiesValue singlePropertiesValue;
  9. @Bean
  10. public JedisPool jedisPoolFactory() {
  11. logger.info("单机版 jedis 注入开始");
  12. JedisPoolConfig config = new JedisPoolConfig();
  13. // 高版本 jedis 将 maxActive -> maxTotal
  14. config.setMaxTotal(singlePropertiesValue.getMaxActive());
  15. config.setMaxIdle(singlePropertiesValue.getMaxIdle());
  16. config.setMinIdle(singlePropertiesValue.getMinIdle());
  17. // 高版本 jedis 将 maxWait -> maxWaitMillis
  18. config.setMaxWaitMillis(singlePropertiesValue.getMaxWait());
  19. // jmx 监控,默认 true
  20. config.setJmxEnabled(true);
  21. return new JedisPool(config,
  22. singlePropertiesValue.getHost(),
  23. singlePropertiesValue.getPort(),
  24. singlePropertiesValue.getTimeout());
  25. }
  26. }

优化

封装指令

  1. public interface CallWithJedis {
  2. /**
  3. * <h2> 对参数 jedis 实例的具体业务逻辑 </h2>
  4. *
  5. * @param jedis
  6. */
  7. public abstract void call(Jedis jedis);
  8. }

封装 jedis 回收

  1. /**
  2. * 硬性封装 jedis 的连接归还操作
  3. *
  4. * 可以这样使用
  5. *
  6. * HolderResult<String> ret = new HolderResult<>();
  7. * MyJedisPool.execute((jedis)-> {
  8. * String r = jedis.set("heheda", "555");
  9. * ret.setResult(r);
  10. * });
  11. * System.out.println(ret);
  12. */
  13. public class MyJedisPool {
  14. private static final Logger logger = LoggerFactory.getLogger(MyJedisPool.class);
  15. private static JedisPool jedisPool;
  16. /**
  17. * 只可以初始化一次 JedisPool
  18. */
  19. public static void setJedisPoolOnlyOnce(JedisPool jedisPool) {
  20. if (Objects.isNull(MyJedisPool.jedisPool)) {
  21. MyJedisPool.jedisPool = jedisPool;
  22. } else {
  23. throw new RuntimeException("MyJedis is already set JedisPool, do not repeat");
  24. }
  25. }
  26. /**
  27. * <h2> 强制使用 try with resources 归还 jedis 连接 </h2>
  28. *
  29. * @param call 具体的代码逻辑
  30. */
  31. public static void execute(CallWithJedis call) {
  32. try (Jedis jedis = jedisPool.getResource()) {
  33. call.call(jedis);
  34. } catch (JedisConnectionException e) {
  35. logger.error("连接出错: {}", e.getLocalizedMessage(), e);
  36. // TODO 重试或者记录日志
  37. }
  38. }
  39. }

封装返回结果

  1. /**
  2. * 包装 jedis 的返回结果
  3. *
  4. * 绕过闭包要求 final 和 不可修改的限制
  5. * @param <T>
  6. */
  7. public class HolderResult<T> {
  8. private T result;
  9. public T getResult() {
  10. return result;
  11. }
  12. public void setResult(T result) {
  13. this.result = result;
  14. }
  15. @Override
  16. public String toString() {
  17. return "HolderResult{" +
  18. "result=" + result +
  19. '}';
  20. }
  21. }

.etc lua

  • 如何发送 lua 脚本
  1. 直接发送 Lua到Redis 服务器去执行。
  2. 先把Lua 发送给 Redis, Redis 会对 Lua 脚本进行缓存,然后返回 SHA 32 位编码回来,之后只需要发送 SHAl 和相关参数给 Redis 便可以执行了

    1. 减少网络开销

      运行

      1. @Test
      2. void contextLoads() {
      3. HolderResult<Object> ret = new HolderResult<>();
      4. MyJedisPool.execute((jedis) -> {
      5. ret.setResult(jedis.evalsha(jedis.scriptLoad(readToString("src/main/resources/test.lua"))));
      6. });
      7. System.out.println(ret);
      8. }
      9. public String readToString(String fileName) {
      10. String encoding = "UTF-8";
      11. File file = new File(fileName);
      12. long fileLength = file.length();
      13. byte[] fileContent = new byte[(int) fileLength];
      14. FileInputStream in = null;
      15. try {
      16. in = new FileInputStream(file);
      17. in.read(fileContent);
      18. return new String(fileContent, encoding);
      19. } catch (Exception e) {
      20. e.printStackTrace();
      21. return null;
      22. } finally {
      23. if (in != null) {
      24. try {
      25. in.close();
      26. } catch (IOException e) {
      27. e.printStackTrace();
      28. }
      29. }
      30. }
      31. }

Redis 安全

redis 深度历险 - 图25