1. 程式人生 > >複習電商筆記-32-jedis 和Spring整合訪問sentinel

複習電商筆記-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, 表示核心允許分配超過所有實體記憶體和交換空間總和的記憶體

 

Redis實現訊息佇列

 

 

為何Redis可以做訊息佇列

首先redis它的設計是用來做快取的,但是由於它自身的某種特性(下面會詳細討論)使得它可以用來做訊息佇列。它有幾個阻塞式的API可以使用,正是這些阻塞式的API讓他有做訊息佇列的能力。

試想一下在”資料庫解決所有問題“的思路下,不使用訊息佇列也是可以完成你的需求的。我們把任務全部存放在資料庫然後通過不斷的輪詢方式來取任務處理。這種做法雖然可以完成你的任務但是做法很粗劣。但是如果你的資料庫介面提供一個阻塞的方法那麼就可以避免輪詢操作了,你的資料庫也可以用來做訊息佇列,只不過目前的資料庫還沒有這樣的介面。

另外做訊息佇列的其他特性例如FIFO也很容易實現,只需要一個List物件從頭取資料,從尾部塞資料即可實現。

redis能做訊息佇列得益於他list物件blpop brpop介面以及Pub/Sub(釋出/訂閱)的某些介面。他們都是阻塞版的,所以可以用來做訊息佇列。

 

 

耗記憶體

儘管redis對一些資料結構採用了壓縮法儲存,但是用記憶體量還是過高。

 

Redis實現唯一計數的3種方法

唯一計數是網站系統中十分常見的一個功能特性,例如網站需要統計每天訪問的人數 unique visitor (也就是 UV)。計數問題很常見,但解決起來可能十分複雜:一是需要計數的量可能很大,比如大型的站點每天有數百萬的人訪問,資料量相當大;二是通常還希望擴充套件計數的維度,比如除了需要每天的 UV,還想知道每週或每月的 UV,這樣導致計算十分複雜。

在關係資料庫儲存的系統裡,實現唯一計數的方法就是 select count(distinct <item_id>),它十分簡單,但是如果資料量很大,這個語句執行是很慢的。用關係資料庫另外一個問題是插入資料效能也不高。

Redis 解決這類計數問題得心應手,相比關係資料庫速度更快,消耗資源更少,甚至提供了 3 種不同的方法。

 

 

基於 set

Redis 的 set 用於儲存唯一的資料集合,通過它可以快速判斷某一個元素是否存在於集合中,也可以快速計算某一個集合的元素個數,另外和可以合併集合到一個新的集合中。

SISMEMBER key member  		# 判斷 member 是否存在
SADD key member  			# 往集合中加入 member
SCARD key  					# 獲取集合元素個數

基於 set 的方法簡單有效,計數精確,適用面廣,易於理解,它的缺點是消耗資源比較大(當然比起關係資料庫是少很多的),如果元素個數很大(比如上億的計數),消耗記憶體很恐怖。

 

 

基於 bit

Redis 的 bit 可以用於實現比 set 記憶體高度壓縮的計數,它通過一個 bit 1 或 0 來儲存某個元素是否存在資訊。例如網站唯一訪客計數,可以把 user_id 作為 bit 的偏移量 offset,設定為 1 表示有訪問,使用1MB的空間就可以存放800 多萬(1024*1024*8=8388608)使用者的一天訪問計數情況。

SETBIT key offset value  # 設定位資訊

GETBIT key offset         # 獲取位資訊

BITCOUNT key [start end] # 計數

BITOP operation destkey key [key ...]  # 點陣圖合併

基於 bit 的方法比起 set 空間消耗小得多,但是它要求元素能否簡單對映為位偏移,適用面窄了不少,另外它消耗的空間取決於最大偏移量,和計數值無關,如果最大偏移量很大,消耗記憶體也相當可觀。

 

 

基於 HyperLogLog

實現超大資料量精確的唯一計數都是比較困難的,但是如果只是近似的話,計算科學裡有很多高效的演算法,其中 HyperLogLog Counting 就是其中非常著名的演算法,它可以僅僅使用 12 k左右的記憶體,實現上億的唯一計數,而且誤差控制在百分之一左右。

PFADD key element [element ...]  # 加入元素
PFCOUNT key [key ...]   # 計數

這種計數方法真的很神奇,我也沒有徹底弄明白,有興趣可以深入研究相關文章。