一 問題
老項目的redis 要切換到redis集羣,所以客戶端從leettuce的RedisAsyncCommands切換到到redisson.
一個常見的hash增長:
public long hincrby(String key,String field,Long amount){
//新舊代碼開關
if( 老的Redis){
老代碼
}else{
新代碼
}
}
redisClient.hincrby(key,field,amount).get()
查看下官網的對應操作手冊:
HDEL | RMap.fastRemove(), RMap.fastRemoveAsync(), RMapReactive.fastRemove(); |
HEXISTS | RMap.containsKey(), RMap.containsKeyAsync(), RMapReactive.containsKey(); |
HGET | RMap.get(), RMap.getAsync(), RMapReactive.get(); |
HSTRLEN | RMap.valueSize(), RMap.valueSizeAsync(), RMapReactive.valueSize(); |
HGETALL | RMap.readAllEntrySet(), RMap.readAllEntrySetAsync(), RMapReactive.readAllEntrySet(); |
HINCRBY | RMap.addAndGet(), RMap.addAndGetAsync(), RMapReactive.addAndGet(); |
HINCRBYFLOAT | RMap.addAndGet(), RMap.addAndGetAsync(), RMapReactive.addAndGet(); |
HKEYS | RMap.readAllKeySet(), RMap.readAllKeySetAsync(), RMapReactive.readAllKeySet(); |
HLEN | RMap.size(), RMap.sizeAsync(), RMapReactive.size(); |
HMGET | RMap.getAll(), RMap.getAllAsync(), RMapReactive.getAll(); |
HMSET | RMap.putAll(), RMap.putAllAsync(), RMapReactive.putAll(); |
HSET | RMap.put(), RMap.putAsync(), RMapReactive.put(); |
HSETNX | RMap.fastPutIfAbsent(), RMap.fastPutIfAbsentAsync, RMapReactive.fastPutIfAbsent(); |
那就是Rmap.addandGet().
RMap<String, Long> map = redissonCilent.getMap(key);
result = map.addAndGet(field,amount);
如果只是api級別的簡單替換,沒啥問題。
但是桌子涉及業務場景的時候,新老redis替換,數據要保證平滑。就是原來假設某個field 有值是9了,再調用就該返回10.
這時候切成新的redis集羣,沒同步就是從0開始,返回1了,這顯然有問題。
因此考慮到平滑的問題,先給對應的值同步老數據。
map.put(field, oldnum );
這時候,打開開關切換到新的redisson集羣,測試就報錯了。
二 分析
錯誤很長,放到最後。看半天還是Google終於找到問題:看下RedissonMap 的底層實現
@Override
public V addAndGet(K key, Number value) {
return get(addAndGetAsync(key, value));
}
@Override
public RFuture<V> addAndGetAsync(final K key, Number value) {
checkKey(key);
checkValue(value);
final RFuture<V> future = addAndGetOperationAsync(key, value);
if (hasNoWriter()) {
return future;
}
MapWriterTask<V> listener = new MapWriterTask<V>() {
@Override
public void execute() {
options.getWriter().write(key, future.getNow());
}
};
return mapWriterFuture(future, listener);
}
//核心
protected RFuture<V> addAndGetOperationAsync(K key, Number value) {
ByteBuf keyState = encodeMapKey(key);
RFuture<V> future = commandExecutor.writeAsync(getName(key), StringCodec.INSTANCE,
new RedisCommand<Object>("HINCRBYFLOAT", new NumberConvertor(value.getClass())),
getName(key), keyState, new BigDecimal(value.toString()).toPlainString());
return future;
}
核心代碼 就是 addAndGetOperationAsync
這裏參數很多。調用了CommandAsyncExecutor 的
<T, R> RFuture<R> writeAsync(String key, Codec codec, RedisCommand<T> command, Object ... params);
這樣看就清晰了,這裏調用的RedisCommand命令是HINCRBYFLOAT,對應的編碼是 StringCodec.INSTANCE。
引用下相關背景知識:
HINCRBYFLOAT key field increment
爲哈希表 key 中的域 field 加上浮點數增量 increment 。
如果哈希表中沒有域 field ,那麼 HINCRBYFLOAT 會先將域 field 的值設爲 0 ,然後再執行加法操作。
如果鍵 key 不存在,那麼 HINCRBYFLOAT 會先創建一個哈希表,再創建域 field ,最後再執行加法操作。
當以下任意一個條件發生時,返回一個錯誤:
- 域 field 的值不是字符串類型(因爲 redis 中的數字和浮點數都以字符串的形式保存,所以它們都屬於字符串類型)
- 域 field 當前的值或給定的增量 increment 不能解釋(parse)爲雙精度浮點數(double precision floating point number)
HINCRBYFLOAT 命令的詳細功能和 INCRBYFLOAT 命令類似,請查看 INCRBYFLOAT 命令獲取更多相關信息。
可用版本:
>= 2.6.0
時間複雜度:
O(1)
返回值:
執行加法操作之後 field 域的值。
我的入參是:Long 類型,我使用了默認編碼:JsonJacksonCodec 。所以才報這個錯誤。
改爲指定的編碼,StringCodec 就可以了。
貼一下:redisson的編碼集
Redisson的對象編碼類是用於將對象進行序列化和反序列化,以實現對該對象在Redis裏的讀取和存儲。Redisson提供了以下幾種的對象編碼應用,以供大家選擇:
編碼類名稱 | 說明 |
---|---|
org.redisson.codec.JsonJacksonCodec |
Jackson JSON 編碼 默認編碼 |
org.redisson.codec.AvroJacksonCodec |
Avro 一個二進制的JSON編碼 |
org.redisson.codec.SmileJacksonCodec |
Smile 另一個二進制的JSON編碼 |
org.redisson.codec.CborJacksonCodec |
CBOR 又一個二進制的JSON編碼 |
org.redisson.codec.MsgPackJacksonCodec |
MsgPack 再來一個二進制的JSON編碼 |
org.redisson.codec.IonJacksonCodec |
Amazon Ion 亞馬遜的Ion編碼,格式與JSON類似 |
org.redisson.codec.KryoCodec |
Kryo 二進制對象序列化編碼 |
org.redisson.codec.SerializationCodec |
JDK序列化編碼 |
org.redisson.codec.FstCodec |
FST 10倍於JDK序列化性能而且100%兼容的編碼 |
org.redisson.codec.LZ4Codec |
LZ4 壓縮型序列化對象編碼 |
org.redisson.codec.SnappyCodec |
Snappy 另一個壓縮型序列化對象編碼 |
org.redisson.client.codec.JsonJacksonMapCodec |
基於Jackson的映射類使用的編碼。可用於避免序列化類的信息,以及用於解決使用byte[] 遇到的問題。 |
org.redisson.client.codec.StringCodec |
純字符串編碼(無轉換) |
org.redisson.client.codec.LongCodec |
純整長型數字編碼(無轉換) |
org.redisson.client.codec.ByteArrayCodec |
字節數組編碼 |
org.redisson.codec.CompositeCodec |
用來組合多種不同編碼在一起 |
最後貼一下錯誤日誌:
hgetFromNewRedis error,key=ee
org.redisson.client.RedisException: Unexpected exception while processing command
at org.redisson.command.CommandAsyncService.convertException(CommandAsyncService.java:324) ~[redisson-3.5.6.jar:na]
at org.redisson.command.CommandAsyncService.get(CommandAsyncService.java:167) ~[redisson-3.5.6.jar:na]
at org.redisson.RedissonObject.get(RedissonObject.java:75) ~[redisson-3.5.6.jar:na]
at org.redisson.RedissonMap.get(RedissonMap.java:251) ~[redisson-3.5.6.jar:na]
at com.benmu.matador.workflow.RedisCommandProxy.hget(RedisCommandProxy.java:220) ~[matador-workflow-1.0.21.jar:na]
at com.benmu.matador.integration.web.controller.DoorController.hget(DoorController.java:150) [DoorController.class:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_71]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_71]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_71]
at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_71]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) [spring-web-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) [spring-web-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) [spring-webmvc-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:817) [spring-webmvc-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:731) [spring-webmvc-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) [spring-webmvc-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) [spring-webmvc-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) [spring-webmvc-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:968) [spring-webmvc-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:859) [spring-webmvc-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:621) [servlet-api.jar:na]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:844) [spring-webmvc-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:728) [servlet-api.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) [catalina.jar:7.0.47]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
at com.benmu.matador.integration.web.security.PrivilegeFilter.doFilter(PrivilegeFilter.java:52) [PrivilegeFilter.class:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
at com.benmu.matador.integration.web.security.LoginFilter.doFilter(LoginFilter.java:47) [LoginFilter.class:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) [tomcat7-websocket.jar:7.0.47]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
at com.benmu.ServletWatcher.doFilter(ServletWatcher.java:137) [common-core-1.3.3.jar:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121) [spring-web-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) [catalina.jar:7.0.47]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [catalina.jar:7.0.47]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) [catalina.jar:7.0.47]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) [catalina.jar:7.0.47]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100) [catalina.jar:7.0.47]
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953) [catalina.jar:7.0.47]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [catalina.jar:7.0.47]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) [catalina.jar:7.0.47]
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041) [tomcat-coyote.jar:7.0.47]
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603) [tomcat-coyote.jar:7.0.47]
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312) [tomcat-coyote.jar:7.0.47]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_71]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_71]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_71]
Caused by: java.lang.IndexOutOfBoundsException: readerIndex(0) + length(4) exceeds writerIndex(1): SlicedAbstractByteBuf(ridx: 0, widx: 1, cap: 1/1, unwrapped: PooledUnsafeDirectByteBuf(ridx: 7, widx: 7, cap: 512))
at io.netty.buffer.AbstractByteBuf.checkReadableBytes0(AbstractByteBuf.java:1411) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.buffer.AbstractByteBuf.readInt(AbstractByteBuf.java:764) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at org.redisson.codec.SnappyCodec$3.decode(SnappyCodec.java:74) ~[redisson-3.5.6.jar:na]
at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:257) ~[redisson-3.5.6.jar:na]
at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:101) ~[redisson-3.5.6.jar:na]
at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:367) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:248) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:334) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:326) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:334) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:326) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:334) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:326) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:334) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:326) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1320) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:334) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:905) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:123) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:563) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:504) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:418) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:390) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:742) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:145) ~[netty-all-4.1.1.Final.jar:4.1.1.Final]
... 1 common frames omitted
我一開始圍繞錯誤日誌搜關鍵詞,netty的數組越界問題,搜出來都是不太想幹的問題。還是去看下底層代碼更有收穫。
希望對你有所幫助。
參考: