我們?cè)谑褂肑edis的時(shí)候猾瘸,如果不考慮使用場(chǎng)景做出一些合理的設(shè)置是會(huì)產(chǎn)生不少問(wèn)題的,另外合理的JedisPool資源池參數(shù)設(shè)置能為業(yè)務(wù)使用Redis保駕護(hù)航散吵。
1.無(wú)法從連接池獲取到Jedis連接
1.1異常堆棧
1) 連接池參數(shù)blockWhenExhausted = true(默認(rèn))
如果連接池沒(méi)有可用Jedis連接奸绷,會(huì)等待maxWaitMillis(毫秒),依然沒(méi)有獲取到可用Jedis連接期揪,會(huì)拋出如下異常:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
…
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)
2) 連接池參數(shù)blockWhenExhausted = false
設(shè)置如果連接池沒(méi)有可用Jedis連接悟衩,立即拋出異常:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
…
Caused by: java.util.NoSuchElementException: Pool exhausted at org.apache.commons.pool2
.impl.GenericObjectPool.borrowObject
(GenericObjectPool.java:464)
異常描述及解決方法
連接過(guò)程如下圖所示:
這種沒(méi)有可用連接的異常是客戶端沒(méi)有從連接池(最大maxTotal個(gè))拿到可用Jedis連接造成的箱歧,具體可能有如下原因:
1) 連接泄露 (較為常見(jiàn))
JedisPool默認(rèn)的maxTotal=8,下面的代碼從JedisPool中借了8次Jedis一膨,但是沒(méi)有歸還呀邢,當(dāng)?shù)?次(jedisPool.getResource().ping())
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
//向JedisPool借用8次連接,但是沒(méi)有執(zhí)行歸還操作汞幢。
for (int i = 0; i < 8; i++) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.ping();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
jedisPool.getResource().ping();
所以推薦使用的代碼規(guī)范是:
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//具體的命令
jedis.executeCommand()
} catch (Exception e) {
//如果命令有key最好把key也在錯(cuò)誤日志打印出來(lái)驼鹅,對(duì)于集群版來(lái)說(shuō)通過(guò)key可以幫助定位到具體節(jié)點(diǎn)。
logger.error(e.getMessage(), e);
} finally {
//注意這里不是關(guān)閉連接森篷,在JedisPool模式下输钩,Jedis會(huì)被歸還給資源池。`
if (jedis != null)
jedis.close();
}
2) 業(yè)務(wù)并發(fā)量大仲智,maxTotal確實(shí)設(shè)置小了
舉個(gè)例子:
一次命令時(shí)間(borrow|return resource + Jedis執(zhí)行命令(含網(wǎng)絡(luò)) )的平均耗時(shí)約為1ms买乃,一個(gè)連接的QPS大約是1000,業(yè)務(wù)期望的QPS是50000钓辆,那么理論上需要的資源池大小是50000 / 1000 = 50個(gè)剪验,實(shí)際maxTotal可以根據(jù)理論值進(jìn)行微調(diào)。
3) Jedis連接釋放的太慢
例如Redis發(fā)生了阻塞(例如慢查詢等原因)前联,所有連接在超時(shí)時(shí)間范圍內(nèi)等待功戚,并發(fā)量較大時(shí),會(huì)造成連接池資源不足似嗤。
4)nf_conntrack丟包問(wèn)題
這種錯(cuò)誤典型的異常如下:nf_conntrack: table full, dropping packet
通過(guò)dmesg檢查客戶端是否有異常啸臀,如果發(fā)生nf_conntract丟包可以通過(guò)修改設(shè)置sysctl -w net.netfilter.nf_conntrack_max=120000
5)TIME_WAIT問(wèn)題
通過(guò)ss -s 查看time wait鏈接是否過(guò)多,如果TIME_WAIT過(guò)多可以修改以下參數(shù):
sysctl -w net ipv4.tcp_max_tw_buckets=18000
sysctl -w net ipv4.tcp_tw_recycle=1
6)DNS問(wèn)題
通過(guò)在/etc/hosts文件直接綁定host地址烁落,綁定完成之后查看問(wèn)題是否還存在乘粒,如果還存在則不是DNS解析問(wèn)題
7)連接拒絕
還有一種情況,從池子里拿連接伤塌,由于沒(méi)有空閑連接灯萍,需要重新生成一個(gè)Jedis連接,但是連接被拒絕:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
at redis.clients.util.Pool.getResource(Pool.java:50)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:99)
at TestAdmin.main(TestAdmin.java:14)
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused
at redis.clients.jedis.Connection.connect(Connection.java:164)
at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:80)
at redis.clients.jedis.BinaryJedis.connect(BinaryJedis.java:1676)
at redis.clients.jedis.JedisFactory.makeObject(JedisFactory.java:87)
at org.apache.commons.pool2.impl.GenericObjectPool.create(GenericObjectPool.java:861)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:435)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:363)
at redis.clients.util.Pool.getResource(Pool.java:48)
... 2 more
Caused by: java.net.ConnectException: Connection refused
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:579)
at redis.clients.jedis.Connection.connect(Connection.java:158)
... 9 more
這里可以看到實(shí)際是一個(gè)Socket連接每聪,一般這種需要檢查Redis的域名配置是否正確旦棉,排查該段時(shí)間網(wǎng)絡(luò)是否正常。
2. 客戶端緩沖異常
2.1異常堆棧
典型的異常信息如下:
redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.
at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:199)
at redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:40)
at redis.clients.jedis.Protocol.process(Protocol.java:151)
2.2異常描述及解決方法
這個(gè)異常是客戶端緩沖區(qū)異常药薯,產(chǎn)生這個(gè)問(wèn)題可能有三個(gè)原因他爸。
1)多個(gè)線程使用一個(gè)Jedis連接(常見(jiàn)原因)
正常的情況是一個(gè)線程使用一個(gè)Jedis連接,可以使用JedisPool管理Jedis連接果善,實(shí)現(xiàn)線程安全诊笤,防止出現(xiàn)這種情況,例如下面代碼中兩個(gè)線程用了一個(gè)Jedis連接:
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
jedis.get("hello");
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
jedis.hget("haskey", "f");
}
}
}).start();
這種問(wèn)題需要排查自身代碼是否使用JedisPool管理Jedis連接巾陕,是否存在并發(fā)操作Jedis的情況讨跟。
2)客戶緩沖區(qū)滿了
Redis有三種客戶端緩沖區(qū):
1)普通客戶端緩沖區(qū)(normal):用于接受普通的命令纪他,例如get、set晾匠、mset茶袒、hgetall、zrange等
2)slave客戶端緩沖區(qū)(slave):用于同步master節(jié)點(diǎn)的寫命令凉馆,完成復(fù)制薪寓。
3)發(fā)布訂閱緩沖區(qū)(pubsub):pubsub不是普通的命令,因此有單獨(dú)的緩沖區(qū)澜共。
這種問(wèn)題需要排查阿里云Redis中timeout=0向叉,也就是不會(huì)主動(dòng)關(guān)閉空閑連接,緩沖區(qū)設(shè)置為0 0 0 也就是不會(huì)對(duì)客戶端緩沖區(qū)進(jìn)行限制嗦董,一般不會(huì)有問(wèn)題母谎。
3)長(zhǎng)時(shí)間閑置連接被服務(wù)端主動(dòng)斷開(kāi)
這種問(wèn)題的處理和問(wèn)題2客戶端緩沖區(qū)滿是一樣的,也要查下timeout配置京革,還有連接池的空閑檢測(cè)奇唤。
3.非法客戶端地址
3.1異常堆棧
Caused by: redis.clients.jedis.exceptions.JedisDataException: ERR illegal address
at redis.clients.jedis.Protocol.processError(Protocol.java:117)
at redis.clients.jedis.Protocol.process(Protocol.java:151)
at redis.clients.jedis.Protocol.read(Protocol.java:205)
3.2異常描述及解決方法
這種異常就是Redis實(shí)例配置了白名單,但當(dāng)前訪問(wèn)Redis的客戶端(IP)不在白名單中匹摇。解決的話就是添加客戶端IP到白名單中咬扇。
4.客戶端連接數(shù)達(dá)到最大值
4.1異常堆棧信息
redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached
4.2異常描述及解決方法
客戶端連接數(shù)超過(guò)了Redis實(shí)例配置的最大maxclients
提工單幫助臨時(shí)調(diào)大最大連接數(shù),并讓客戶找到連接數(shù)暴漲的原因(從連接最多的客戶端開(kāi)始看廊勃,看看連接池配置有什么問(wèn)題)冗栗。
5.客戶端讀寫超時(shí)
5.1異常堆棧信息
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException:
Read timed out
5.2異常描述及解決方法
該問(wèn)題原因可能有如下幾種:
(1) 讀寫超時(shí)設(shè)置的過(guò)短。
(2) 有慢查詢或者Redis發(fā)生阻塞供搀。
(3) 網(wǎng)絡(luò)不穩(wěn)定。
6.密碼相關(guān)的異常
6.1異常堆棧信息
Redis設(shè)置了密碼钠至,客戶端請(qǐng)求沒(méi)傳密碼:
Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required.
at redis.clients.jedis.Protocol.processError(Protocol.java:127)
at redis.clients.jedis.Protocol.process(Protocol.java:161)
at redis.clients.jedis.Protocol.read(Protocol.java:215)
Redis沒(méi)有設(shè)置密碼葛虐,客戶端傳了密碼:
Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: ERR Client sent AUTH, but no password is set
at redis.clients.jedis.Protocol.processError(Protocol.java:127)`
at redis.clients.jedis.Protocol.process(Protocol.java:161)`
at redis.clients.jedis.Protocol.read(Protocol.java:215)`
客戶端傳了錯(cuò)誤的密碼:
redis.clients.jedis.exceptions.JedisDataException: ERR invalid password
at redis.clients.jedis.Protocol.processError(Protocol.java:117)`
at redis.clients.jedis.Protocol.process(Protocol.java:151)`
at redis.clients.jedis.Protocol.read(Protocol.java:205)`
6.2解決方法
密碼問(wèn)題的解決方法及時(shí)檢查密碼是否正確,還是是否設(shè)置了密碼
7.事務(wù)異常
7.1. 異常堆棧
redis.clients.jedis.exceptions.JedisDataException: EXECABORT Transaction discarded because of previous errors
7.2. 異常描述及解決方法
這個(gè)是Redis的事務(wù)異常:事務(wù)中包含了錯(cuò)誤的命令棉钧,例如下面這個(gè)sett是個(gè)不存在的命令屿脐。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sett key world
(error) ERR unknown command 'sett'
127.0.0.1:6379> incr counter
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
這種錯(cuò)誤就是自己檢查錯(cuò)誤命令修改就可以了
8.類轉(zhuǎn)換錯(cuò)誤
8.1異常堆棧
java.lang.ClassCastException: java.lang.Long cannot be cast to java.util.List
at redis.clients.jedis.Connection.getBinaryMultiBulkReply(Connection.java:199)
at redis.clients.jedis.Jedis.hgetAll(Jedis.java:851)
at redis.clients.jedis.ShardedJedis.hgetAll(ShardedJedis.java:198)
java.lang.ClassCastException: java.util.ArrayList cannot be cast to [B
at redis.clients.jedis.Connection.getBinaryBulkReply(Connection.java:182)
at redis.clients.jedis.Connection.getBulkReply(Connection.java:171)
at redis.clients.jedis.Jedis.rpop(Jedis.java:1109)
at redis.clients.jedis.ShardedJedis.rpop(ShardedJedis.java:258)
8.2異常描述及解決方法
檢查代碼中類涉及強(qiáng)制轉(zhuǎn)換的部分。
9.命令使用錯(cuò)誤
9.1.異常堆棧
Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: WRONGTYPE Operation against a key holding the wrong kind of value
at redis.clients.jedis.Protocol.processError(Protocol.java:127)
at redis.clients.jedis.Protocol.process(Protocol.java:161)
at redis.clients.jedis.Protocol.read(Protocol.java:215)
9.2異常描述及解決方法
例如key="hello"是字符串類型的鍵宪卿,而hgetAll是哈希類型的鍵的诵,所以出現(xiàn)了錯(cuò)誤。
jedis.set("hello","world");
jedis.hgetAll("hello");
這種錯(cuò)誤就是自己檢查錯(cuò)誤命令修改就可以了佑钾。
10.Redis使用的內(nèi)存超過(guò)maxmemory配置
10.1異常堆棧
redis.clients.jedis.exceptions.JedisDataException: OOM command not allowed when used memory > 'maxmemory'.
10.2異常描述及解決方法
Redis節(jié)點(diǎn)(如果是集群西疤,則是其中一個(gè)節(jié)點(diǎn))使用大于該實(shí)例的內(nèi)存規(guī)格(maxmemory配置)。
原因可能有以下幾個(gè):
1)業(yè)務(wù)數(shù)據(jù)正常增加
2)客戶端緩沖區(qū)異常:例如使用了monitor休溶、pub/sub使用不當(dāng)?shù)鹊?br>
3)純緩存使用場(chǎng)景代赁,但是maxmemory-policy配置有誤(例如沒(méi)有過(guò)期鍵的業(yè)務(wù)配置volatile-lru)
緊急處理扰她,可以臨時(shí)提工單幫助臨時(shí)調(diào)整maxmemory,后續(xù)咨詢用戶是否升配或者調(diào)整配置芭碍。最終的解決還是要確認(rèn)內(nèi)存增大原因徒役。
11.Redis正在加載持久化文件
11.1.異常堆棧
redis.clients.jedis.exceptions.JedisDataException: LOADING Redis is loading the dataset in memory
11.2異常描述及解決方法
Jedis調(diào)用Redis時(shí),如果Redis正在加載持久化文件窖壕,無(wú)法進(jìn)行正常的讀寫忧勿,解決方法就是提交阿里云工單處理。
12.Lua腳本超時(shí)
12.1異常堆棧
redis.clients.jedis.exceptions.JedisDataException: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
12.2異常描述及解決方法
如果Redis當(dāng)前正在執(zhí)行Lua腳本瞻讽,并且超過(guò)了lua-time-limit鸳吸,此時(shí)Jedis調(diào)用Redis時(shí),會(huì)收到這種異常卸夕。解決辦法就是按照異常提示:You can only call SCRIPT KILL or SHUTDOWN NOSAVE. (使用script kill:kill掉Lua腳本)层释。
13.連接超時(shí)
13.1.異常堆棧
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
13.2異常描述及解決方法
可能產(chǎn)生的原因:
1)連接超時(shí)設(shè)置的過(guò)短。
2)tcp-backlog滿快集,造成新的連接失敗贡羔。
3)客戶端與服務(wù)端網(wǎng)絡(luò)不正常。
這種問(wèn)題需要客戶提供連接超時(shí)時(shí)間个初,提交工單定位相關(guān)原因乖寒。
14.Lua腳本寫超時(shí)
14.1.異常堆棧
(error) UNKILLABLE Sorry the script already executed write commands against the dataset.
You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.
14.2.異常描述及解決方法
如果Redis當(dāng)前正在執(zhí)行Lua腳本,并且超過(guò)了lua-time-limit院溺,并且已經(jīng)執(zhí)行過(guò)寫命令楣嘁,此時(shí)Jedis調(diào)用Redis時(shí),會(huì)收到上面的異常珍逸,這種錯(cuò)誤需要提交工單做緊急處理逐虚,管理員要做重啟或者切換Redis節(jié)點(diǎn)。
15.類加載錯(cuò)誤
15.1.異常堆棧
例如下面的這種找不到類和方法
Exception in thread "commons-pool-EvictionTimer" java.lang.NoClassDefFoundError: redis/clients/util/IOUtils
at redis.clients.jedis.Connection.disconnect(Connection.java:226)`
at redis.clients.jedis.BinaryClient.disconnect(BinaryClient.java:941)`
at redis.clients.jedis.BinaryJedis.disconnect(BinaryJedis.java:1771)`
at redis.clients.jedis.JedisFactory.destroyObject(JedisFactory.java:91)`
at org.apache.commons.pool2.impl.GenericObjectPool.destroy(GenericObjectPool.java:897)`
at org.apache.commons.pool2.impl.GenericObjectPool.evict(GenericObjectPool.java:793)`
at org.apache.commons.pool2.impl.BaseGenericObjectPool$Evictor.run(BaseGenericObjectPool.java:1036)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
Caused by: java.lang.ClassNotFoundException: redis.clients.util.IOUtils
15.2.異常描述及解決方法
運(yùn)行時(shí)谆膳,Jedis執(zhí)行命令叭爱,拋出異常:某個(gè)類找不到。一般此類問(wèn)題都是由于加載多個(gè)jedis版本(例如jedis 2.9.0和jedis 2.6)漱病,在編譯期代碼未出現(xiàn)問(wèn)題买雾,但類加載器在運(yùn)行時(shí)加載了低版本的Jedis,造成運(yùn)行時(shí)找不到類杨帽。
通常此類問(wèn)題漓穿,可以將重復(fù)的jedis排除掉,例如利用maven的依賴樹(shù)注盈,把無(wú)用的依賴去掉或者exclusion掉晃危。
16.服務(wù)端命令不支持
16.1.異常堆棧
例如客戶端執(zhí)行了geoadd命令,但是服務(wù)端返回不支持此命令
redis.clients.jedis.exceptions.JedisDataException: ERR unknown command 'GEOADD'
16.2.異常描述及解決方法
該命令不能被Redis端識(shí)別老客,有可能有兩個(gè)原因:
1)社區(qū)版的一些命令山害,阿里云Redis的不支持纠俭,或者只在某些小版本上支持(例如geoadd是Redis 3.2添加的地理信息api)。
2)命令本身是錯(cuò)誤的(不過(guò)對(duì)于Jedis來(lái)說(shuō)還好浪慌,不支持直接組裝命令冤荆,每個(gè)API都有固定的函數(shù))。
解決辦法是看是否有Redis版本支持該命令权纤,如支持可以讓客戶做小版本升級(jí)钓简。
17.pipeline錯(cuò)誤使用
17.1.異常堆棧
redis.clients.jedis.exceptions.JedisDataException: Please close pipeline or multi block before calling this method.
17.2.異常描述及解決方法
在pipeline.sync()執(zhí)行之前,通過(guò)response.get()獲取值汹想,在pipeline.sync()執(zhí)行前外邓,命令沒(méi)有執(zhí)行(可以通過(guò)monitor做驗(yàn)證),下面代碼就會(huì)引起上述異常
Jedis jedis = new Jedis("127.0.0.1", 6379);
Pipeline pipeline = jedis.pipelined();
pipeline.set("hello", "world");
pipeline.set("java", "jedis");
Response pipeString = pipeline.get("java");
//這個(gè)get必須在sync之后古掏,如果是批量獲取值建議直接用List objectList = pipeline.syncAndReturnAll();
System.out.println(pipeString.get());
//命令此時(shí)真正執(zhí)行
pipeline.sync();
Jedis中Reponse中g(shù)et()方法损话,有個(gè)判斷:如果set=false就會(huì)報(bào)錯(cuò),而response中的set初始化為false.
public T get() {
// if response has dependency response and dependency is not built,
// build it first and no more!!
if (dependency != null && dependency.set && !dependency.built) {
dependency.build();
}
if (!set) {
throw new JedisDataException(
"Please close pipeline or multi block before calling this method.");
}
if (!built) {
build();
}
if (exception != null) {
throw exception;
}
return response;
}
pipeline.sync()會(huì)每個(gè)結(jié)果設(shè)置set=true
public void sync() {
if (getPipelinedResponseLength() > 0) {
List unformatted = client.getAll();
for (Object o : unformatted) {
generateResponse(o);
}
}
}
其中g(shù)enerateResponse(o):
protected Response generateResponse(Object data) {
Response response = pipelinedResponses.poll();
if (response != null) {
response.set(data);
}
return response;
}
其中response.set(data);
public void set(Object data) {
this.data = data;
set = true;
}
實(shí)際上對(duì)于批量結(jié)果的解析槽唾,建議使用pipeline.syncAndReturnAll()來(lái)實(shí)現(xiàn)丧枪,下面操作模擬了批量hgetAll
/**
* pipeline模擬批量hgetAll
* @param keyList
* @return
*/
public Map> mHgetAll(List keyList) {
// 1.生成pipeline對(duì)象
Pipeline pipeline = jedis.pipelined();
// 2.pipeline執(zhí)行命令,注意此時(shí)命令并未真正執(zhí)行
for (String key : keyList) {
pipeline.hgetAll(key);
}
// 3.執(zhí)行命令 syncAndReturnAll()返回結(jié)果
List objectList = pipeline.syncAndReturnAll();
if (objectList == null || objectList.isEmpty()) {
return Collections.emptyMap();
}
// 4.解析結(jié)果
Map> resultMap = new HashMap>();
for (int i = 0; i < objectList.size(); i++) {
Object object = objectList.get(i);
Map map = (Map) object;
String key = keyList.get(i);
resultMap.put(key, map);
}
return resultMap;
}
18.普通用戶沒(méi)有權(quán)限執(zhí)行
18.1.異常堆棧
命令role不能被普通用戶執(zhí)行庞萍,可以參考暫未開(kāi)放的Redis命令
redis.clients.jedis.exceptions.JedisDataException: ERR command role not support for normal user
18.2異常描述及解決方法
這個(gè)錯(cuò)誤是因?yàn)槊顩](méi)有對(duì)普通用戶開(kāi)放拧烦,不能使用,只能聯(lián)系管理員钝计。
參考資料:
https://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==&mid=2247484932&idx=1&sn=0b7547deb4ba179ed077bdb5fc45935b&chksm=9bd0ab9caca7228a3d65154d843c58a78cb211dd70eb58df68886f9393681681ab8fc25f33ae&scene=21#wechat_redirect
https://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==&mid=2247484938&idx=1&sn=bea2ef85795c5a2e812c3c1f66c5f1ac&chksm=9bd0ab92aca722841a72aa2e9bf43f167a68fc31752c0def1d21a0d7adfe455a6e3d9c30fc86&scene=21#wechat_redirect