redis线上动态增加密码认证

274 阅读2分钟

首先这个需求要是做得话,大部分情况下如果仅仅是把redis当做cache的话不会有很多的问题,然后如果是当做存储使用的话,其中的问题还不少,首先从网上找到了这两篇内容:

1.基于proxy代理实现:www.dazhuanlan.com/2020/01/20/…

2.修改jedis源代码:blog.csdn.net/TVwR8OfV0P/…

而我们使用的是lettuce,基于netty编写的,如果要是修改源码的话,那么后续版本的更新迭代就不好实现了,因此,在使用层面的SDK做了动态的链接转换

1、自定义client的封装:

对于lettuce的使用建议,官方给的有提示:Advanced usage

  • netty线程池大小参数不填都有默认值
    -- I/O Thread Pool Size = the number of available processors(Minimum 3)
    -- Computation Thread Pool Size = the number of available processors(Minimum 3)
  • 高级设置没有预留接口,因为官方警告⚠️
    -- Values for the advanced options are listed in the table below and should not be changed unless there is a truly good reason to do so. -- 除非确实有充分的理由,否则请勿更改。

所以都有采用了默认的配置,默认值为:

如果地址都不配置默认连接127.0.0.1:6379,lettuce和spring的做法都是如此

namevalue
```
redisUrls
| ```
zkServers
```                 | --                                                                 |
| ```
namePath
```                  | --                                                                 |
| ```
password
```                  | --                                                                 |
| ```
ioThreadPoolSize
```          | ```
Runtime.getRuntime().availableProcessors()
```                 |
| ```
computationThreadPoolSize
``` | ```
Runtime.getRuntime().availableProcessors()
```                 |
| ```
metrics
```                   | --                                                                 |
| ```
clientOptions
```             | --<https://github.com/lettuce-io/lettuce-core/wiki/Client-Options> |

### 2、动态增加密码处理:

| `private` `RedisClusterCommands<String, String> buildRedisClusterCommand() {``    ``try` `{``        ``buildRedisClusterClient();``        ``statefulRedisClusterConnection = redisClusterClient.connect();``        ``statefulRedisClusterConnection.sync().ping();``    ``} ``catch` `(RedisException ex) {``        ``if` `(RedisExceptionMesage.NOAUTH.equals(ex.getMessage()) \|\| ex.getMessage().startsWith(RedisExceptionMesage.NOPWD)) {``            ``buildRedisClusterClientWithPWD();``            ``statefulRedisClusterConnection = redisClusterClientWithPWD.connect();``        ``} ``else` `{``            ``throw` `ex;``        ``}``    ``}``    ``return` `statefulRedisClusterConnection.sync();``}` |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

connection获取的操作中添加了try-catch处理,只有当异常为指定类型的异常时,才会进行处理进行密码尝试

| `private` `<T> T executeSync(FuncToDoWithNameSync<T> f, String funcName) {``    ``Long start = System.nanoTime();``    ``T result;``    ``try` `{``        ``result = f.apply(redisClusterCommands);``    ``} ``catch` `(RedisException ex) {``        ``if` `(RedisExceptionMesage.NOAUTH.equals(ex.getMessage())) {``            ``synchronized` `(redisClusterCommands) {``                ``redisClusterCommands = buildRedisClusterCommand();``            ``}``            ``result = f.apply(redisClusterCommands);``        ``} ``else` `{``            ``throw` `ex;``        ``}``    ``}``    ``Long end = System.nanoTime();``    ``metrics.timer(funcName).update(end - start, TimeUnit.NANOSECONDS);``    ``return` `result;``}` |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

redisClusterCommands进行了加锁处理,防止在多线程情况下有多个请求同时发生连接转换

| `public` `static` `final` `String NOAUTH = ``"NOAUTH Authentication required."``;` `public` `static` `final` `String NOPWD = ``"Cannot retrieve initial cluster partitions from initial URIs"``;` |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

异常的message内容来自lettuce的源码:[ClusterTopologyRefresh](https://github.com/lettuce-io/lettuce-core/blob/b978238db8a33c8860819b73e528f016f940e992/src/main/java/io/lettuce/core/cluster/topology/ClusterTopologyRefresh.java)

### 3、测试用例:

覆盖测试(省略)、动态测试和覆盖测试:

| `@Test``public` `void` `testGet(){``    ``for` `(``int` `i = ``0``; i < ``1000000000``; i++) {``        ``logger.info(``"-------:"` `+ i);``        ``Assert.assertEquals(``"v1"``, redisCluster.get(``"k1"``));``    ``}``}` `@Test``public` `void` `testGetPerformance() {``    ``CountDownLatch countDownLatch = ``new` `CountDownLatch(``2``);``    ``Thread t1 = ``new` `Thread(``new` `Runnable() {``        ``@Override``        ``public` `void` `run() {``            ``for` `(``int` `i = ``0``; i < ``1000000000``; i++) {``                ``logger.info(``"------- t1:"` `+ i);``                ``Assert.assertEquals(``"v1"``, redisCluster.get(``"k1"``));``            ``}``            ``countDownLatch.countDown();``        ``}``    ``});``    ``Thread t2 = ``new` `Thread(``new` `Runnable() {``        ``@Override``        ``public` `void` `run() {``            ``for` `(``int` `i = ``0``; i < ``1000000000``; i++) {``                ``logger.info(``"+++++++ t2:"` `+ i);``                ``Assert.assertEquals(``"v1"``, redisCluster.get(``"k1"``));``            ``}``            ``countDownLatch.countDown();``        ``}``    ``});``    ``t1.setName(``"t1"``);``    ``t2.setName(``"t2"``);``    ``t1.start();``    ``t2.start();``    ``while` `(t1.isAlive() && t2.isAlive() && countDownLatch.getCount() > ``0``) {``        ``try` `{``            ``Thread.sleep(``1``);``        ``} ``catch` `(InterruptedException e) {``            ``e.printStackTrace();``        ``}``    ``}``}` |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

### 总结:

其实好的实现应该是基于代理,但是由于redis-cluster的使用在调研的初期没有选择这个方案,因此导致线上已经有了大量的使用,所以只能进行代码层面做一些事情

这篇文章讲了网易IM做的基于java的代理:<https://zhuanlan.zhihu.com/p/134274709>