首先这个需求要是做得话,大部分情况下如果仅仅是把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的做法都是如此
| name | value |
|---|---|
| ``` | |
| 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>