複習電商筆記-32-jedis 和Spring整合訪問sentinel-常見問題
jedis 和Spring整合訪問sentinel
jedis和spring整合訪問sentinel需要一個整合包,這個整合包是通過spring-data支援。整合後會建立RedisTemplate物件,在偽service中就可以呼叫。
SpringData
Spring Data 作為SpringSource的其中一個父專案, 旨在統一和簡化對各型別持久化儲存, 而不拘泥於是關係型資料庫還是NoSQL 資料儲存。
Spring Data 專案旨在為大家提供一種通用的編碼模式。
引入依賴包
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.4.1.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.6.2</version> </dependency>
整合配置檔案applicationContext-sentinel.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="${redis.maxTotal}" /> <property name="minIdle" value="${redis.minIdle}" /> <property name="maxIdle" value="${redis.maxIdle}" /> </bean> <bean id="sentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration"> <property name="master"> <bean class="org.springframework.data.redis.connection.RedisNode"> <property name="name" value="${redis.sentinel.masterName}"></property> </bean> </property> <property name="sentinels"> <set> <bean class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.sentinel1.host}"></constructor-arg> <constructor-arg name="port" value="${redis.sentinel1.port}" type="int"></constructor-arg> </bean> <bean class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.sentinel2.host}"></constructor-arg> <constructor-arg name="port" value="${redis.sentinel2.port}" type="int"></constructor-arg> </bean> </set> </property> </bean> <!-- p:password="${redis.sentinel.password}" --> <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg> <constructor-arg name="poolConfig" ref="poolConfig"></constructor-arg> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="connectionFactory" /> </bean> </beans>
屬性配置檔案redis-sentinel.properties
注意一個坑,屬性檔案中不能有空格,redis原始碼中不會去過濾空格,導致如果有空格就無法連線錯誤Can connect to sentinel, but mymaster seems to be not monitored。
redis.minIdle=300
redis.maxIdle=500
redis.maxTotal=5000
redis.sentinel1.host=192.168.163.200
redis.sentinel1.port=26379
redis.sentinel2.host=192.168.163.200
redis.sentinel2.port=26380
redis.sentinel.masterName=mymaster
redis.sentinel.password=123456
偽service類
package com.jt.common.service;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class RedisSentinelService {
private Logger logger = LoggerFactory.getLogger("RedisSentinelService");
//有的工程需要,有的工程不需要。設定required=false,有就注入,沒有就不注入。
@Autowired(required = false)
RedisTemplate<?, ?> redisTemplate;
// 執行緒池
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
256, 256, 30L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
new BasicThreadFactory.Builder().daemon(true)
.namingPattern("redis-oper-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy());
public void set(final String key, final String value) {
redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection)
throws DataAccessException {
connection.set(
redisTemplate.getStringSerializer().serialize(key),
redisTemplate.getStringSerializer().serialize(value));
return null;
}
});
}
//設定值後並設定過期時間
public void set(final String key, final String value, final long seconds) {
redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection)
throws DataAccessException {
connection.set(
redisTemplate.getStringSerializer().serialize(key),
redisTemplate.getStringSerializer().serialize(value));
connection.expire(redisTemplate.getStringSerializer().serialize(key), seconds);
return null;
}
});
}
public String get(final String key) {
return redisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection)
throws DataAccessException {
byte[] byteKye = redisTemplate.getStringSerializer().serialize(
key);
if (connection.exists(byteKye)) {
byte[] byteValue = connection.get(byteKye);
String value = redisTemplate.getStringSerializer()
.deserialize(byteValue);
logger.debug("get key:" + key + ",value:" + value);
return value;
}
logger.error("valus does not exist!,key:"+key);
return null;
}
});
}
public void delete(final String key) {
redisTemplate.execute(new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) {
connection.del(redisTemplate.getStringSerializer().serialize(
key));
return null;
}
});
}
/**
* 執行緒池併發操作redis
*
* @param keyvalue
*/
public void mulitThreadSaveAndFind(final String keyvalue) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
set(keyvalue, keyvalue);
get(keyvalue);
} catch (Throwable th) {
// 防禦性容錯,避免高併發下的一些問題
logger.error("", th);
}
}
});
}
}
呼叫程式碼
就把實現類換下即可,其它呼叫不變。
@Autowired
private RedisSentinelService redisService;
常見問題
kill和pkill的區別
在測試Redis3.0.0叢集的時候偶然遇到的情況。在停止Redis服務時,我分別用了pkill redis-server和kill -9 redis-pid的方式停止Redis服務,但Redis的日誌輸出卻不一樣。使用pkill停止Redis時,輸入的日誌如下。說明Redis是正常退出的。
而使用kill -9停止Redis時,Redis沒有任何日誌輸出,說明用kill命令停止Redis服務是不對的。
從上面的日誌也可以看出,如果要Redis正常退出,需要給Redis發出一個SIGTERM訊號。而pkill是將含有引數的所有程序kill掉,如果要kill單個程序,並且發出SIGTERM命令可不可以呢?答案是可以的,通過kill -15 redis-pid。
pkill redis- #殺掉redis-開頭的服務
kill -15 redis-pid
下面順便說一下pkill和kill:
pkill:通過名稱和其它屬性查詢或者發訊號給程序。
kill:可以通過kill -l命令檢視到kill有64個引數,常用的5個如下:
kill引數值 |
含義 |
1=SIGHUP |
本訊號在使用者終端連線(正常或非正常)結束時發出, 通常是在終端的控制程序結束時, 通知同一session內的各個作業, 這時它們與控制終端不再關聯。 |
2=SIGINT |
程式終止(interrupt)訊號, 在使用者鍵入INTR字元(通常是Ctrl-C)時發出,用於通知前臺程序組終止程序。 |
3=SIGQUIT |
和SIGINT類似, 但由QUIT字元(通常是Ctrl-\)來控制. 程序在因收到SIGQUIT退出時會產生core檔案, 在這個意義上類似於一個程式錯誤訊號。 |
9=SIGKILL |
用來立即結束程式的執行. 本訊號不能被阻塞、處理和忽略。如果管理員發現某個程序終止不了,可嘗試傳送這個訊號。 |
15=SIGTERM |
程式結束(terminate)訊號, 與SIGKILL不同的是該訊號可以被阻塞和處理。通常用來要求程式自己正常退出,shell命令kill預設產生這個訊號。如果程序終止不了,我們才會嘗試SIGKILL。 |
快取中的資料能否丟失?
可以,它快取的是資料庫中的資料,如果快取宕機,使用者依然可以訪問資料庫獲取到所需要的資料。
不可以,在海量資料時,現在電商系統已經對快取的依賴性非常高。有一種情況。當海量的請求過來時,快取宕機,海量的請求繼續湧向資料庫,資料庫伺服器宕機。將資料庫伺服器重啟,重啟後,剛起來,海量的請求又來了資料庫伺服器都無法啟動。這種情況稱為雪崩(快取擊穿)。
怎麼解決呢?
必須使用分散式快取。叢集,可以通過多臺的伺服器分擔快取。這時如果一臺伺服器宕機,這時少量的請求湧向資料庫,這時資料庫可以承擔。不會宕機。如果訪問壓力還非常巨大,可以繼續增加伺服器。然後分佈的備份內容。形成快取的主從。前面的方案還會有少量的快取的資料丟失,但高可用後資料就不會丟失。
現在企業開發快取有趣現象:
在企業中還有利用memcache的,然後被區域性的逐漸的被redis替換。
問題:資料傾斜
由於key的設定不當,導致對key雜湊後,不夠均勻。例如有3個節點,資料大量落到一個節點上,其他節點數量很少。如何發現和解決呢?可以去做實驗看看key的分佈情況,也有redis的監控工具可以進行觀察。通過觀察發現有這種資料傾斜的情況,又如何辦呢?換一個key的組成即可。
問題:redis能否替代mysql?
不能,redis NO-SQL no SQL,none SQL。它沒有複雜結構,不支援強關係型資料。
例如:關係型資料:部門和使用者。一個部門下有多個使用者,一個使用者從屬一個部門。非結構化資料:html/xml/json、視訊、圖片
根據應用場景分類儲存:結構化的依然使用mysql傳統結構化資料庫。對非結構化但是很大的儲存到mongodb(視訊、word/excel/pdf等檔案)。對非結構的但是需要快速查詢的memCache或者Redis中(json)。對海量的非結構化的資料,還想對其進行類似關係型資料的分析hbase(列)。對需要分詞檢索的使用solr或者elasticSearch。
DENIED異常
redis.clients.jedis.exceptions.JedisDataException: DENIED Redis is running in protected
mode because protected mode is enabled, no bind address was specified, no authentication
password is requested to clients. In this mode connections are only accepted from the
loopback interface. If you want to connect from external computers to Redis you may adopt
one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG
SET protected-mode no' from the loopback interface by connecting to Redis from the same
host the server is running, however MAKE SURE Redis is not publicly accessible from
internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively
you can just disable the protected mode by editing the Redis configuration file, and
setting the protected mode option to 'no', and then restarting the server. 3) If you
started the server manually just for testing, restart it with the '--protected-mode no'
option. 4) Setup a bind address or an authentication password. NOTE: You only need to do
one of the above things in order for the server to start accepting connections from the
outside.
解決辦法:
登入後執行
127.0.0.1:6479> config set protected-mode no
保護模式3.2.5版本要求更加嚴格,關閉即可。
sentinel directive while not in sentinel mode異常
[[email protected] redis-3.2.4]# redis-server sentinel.conf
*** FATAL CONFIG FILE ERROR ***
Reading the configuration file, at line 69
>>> 'sentinel monitor mymaster 127.0.0.1 6379 2'
sentinel directive while not in sentinel mode
啟動方式不對,redis-sentinel而不是redis-server
Increased maximum number of open files to 10032
Increased maximum number of open files to 10032 (it was originally set to 1024).
4395:M 09 Nov 00:46:35.768 # Creating Server TCP listening socket 192.168.163.1:6379: bind: Cannot assign requested address
新裝的linux預設只有1024,當負載較大時,會經常出現error: too many open files
ulimit -a:使用可以檢視當前系統的所有限制值
vim /etc/security/limits.conf
在檔案的末尾加上
* soft nofile 65535
* hard nofile 65535
執行su或者重新關閉連線使用者再執行ulimit -a就可以檢視修改後的結果。
overcommit_memory
WARNING overcommit_memory is set to 0! Background save may fail under low memory condition.
To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run
the command 'sysctl vm.overcommit_memory=1' for this to take effect.
兩個解決方法(overcommit_memory)
1. echo "vm.overcommit_memory=1" > /etc/sysctl.conf 或 vi /etcsysctl.conf , 然後reboot重啟機器
2. echo 1 > /proc/sys/vm/overcommit_memory 不需要啟機器就生效
overcommit_memory引數說明:
設定記憶體分配策略(可選,根據伺服器的實際情況進行設定)
/proc/sys/vm/overcommit_memory
可選值:0、1、2。
0, 表示核心將檢查是否有足夠的可用記憶體供應用程序使用;如果有足夠的可用記憶體,記憶體申請允許;否則,記憶體申請失敗,並把錯誤返回給應用程序。
1, 表示核心允許分配所有的實體記憶體,而不管當前的記憶體狀態如何。
2, 表示核心允許分配超過所有實體記憶體和交換空間總和的記憶體